1 /*
2 * Copyright 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "os/files.h"
18
19 #include <fcntl.h>
20 #include <libgen.h>
21 #include <sys/stat.h>
22 #include <unistd.h>
23
24 #include <cerrno>
25 #include <cstring>
26 #include <fstream>
27 #include <streambuf>
28 #include <string>
29
30 #include "os/log.h"
31
32 namespace {
33
HandleError(const std::string & temp_path,int * dir_fd,FILE ** fp)34 void HandleError(const std::string& temp_path, int* dir_fd, FILE** fp) {
35 // This indicates there is a write issue. Unlink as partial data is not
36 // acceptable.
37 unlink(temp_path.c_str());
38 if (*fp) {
39 fclose(*fp);
40 *fp = nullptr;
41 }
42 if (*dir_fd != -1) {
43 close(*dir_fd);
44 *dir_fd = -1;
45 }
46 }
47
48 } // namespace
49
50 namespace bluetooth {
51 namespace os {
52
FileExists(const std::string & path)53 bool FileExists(const std::string& path) {
54 std::ifstream input(path, std::ios::binary | std::ios::ate);
55 return input.good();
56 }
57
RenameFile(const std::string & from,const std::string & to)58 bool RenameFile(const std::string& from, const std::string& to) {
59 if (std::rename(from.c_str(), to.c_str()) != 0) {
60 LOG_ERROR("unable to rename file from '%s' to '%s', error: %s", from.c_str(), to.c_str(), strerror(errno));
61 return false;
62 }
63 return true;
64 }
65
ReadSmallFile(const std::string & path)66 std::optional<std::string> ReadSmallFile(const std::string& path) {
67 std::ifstream input(path, std::ios::binary | std::ios::ate);
68 if (!input) {
69 LOG_WARN("Failed to open file '%s', error: %s", path.c_str(), strerror(errno));
70 return std::nullopt;
71 }
72 auto file_size = input.tellg();
73 if (file_size < 0) {
74 LOG_WARN("Failed to get file size for '%s', error: %s", path.c_str(), strerror(errno));
75 return std::nullopt;
76 }
77 std::string result(file_size, '\0');
78 if (!input.seekg(0)) {
79 LOG_WARN("Failed to go back to the beginning of file '%s', error: %s", path.c_str(), strerror(errno));
80 return std::nullopt;
81 }
82 if (!input.read(result.data(), result.size())) {
83 LOG_WARN("Failed to read file '%s', error: %s", path.c_str(), strerror(errno));
84 return std::nullopt;
85 }
86 input.close();
87 return result;
88 }
89
WriteToFile(const std::string & path,const std::string & data)90 bool WriteToFile(const std::string& path, const std::string& data) {
91 ASSERT(!path.empty());
92 // Steps to ensure content of data gets to disk:
93 //
94 // 1) Open and write to temp file (e.g. bt_config.conf.new).
95 // 2) Flush the stream buffer to the temp file.
96 // 3) Sync the temp file to disk with fsync().
97 // 4) Rename temp file to actual config file (e.g. bt_config.conf).
98 // This ensures atomic update.
99 // 5) Sync directory that has the conf file with fsync().
100 // This ensures directory entries are up-to-date.
101 //
102 // We are using traditional C type file methods because C++ std::filesystem and std::ofstream do not support:
103 // - Operation on directories
104 // - fsync() to ensure content is written to disk
105
106 // Build temp config file based on config file (e.g. bt_config.conf.new).
107 const std::string temp_path = path + ".new";
108
109 // Extract directory from file path (e.g. /data/misc/bluedroid).
110 // libc++fs is not supported in APEX yet and hence cannot use std::filesystem::path::parent_path
111 std::string directory_path;
112 {
113 // Make a temporary variable as inputs to dirname() will be modified and return value points to input char array
114 // temp_path_for_dir must not be destroyed until results from dirname is appended to directory_path
115 std::string temp_path_for_dir(path);
116 directory_path.append(dirname(temp_path_for_dir.data()));
117 }
118 if (directory_path.empty()) {
119 LOG_ERROR("error extracting directory from '%s', error: %s", path.c_str(), strerror(errno));
120 return false;
121 }
122
123 int dir_fd = open(directory_path.c_str(), O_RDONLY | O_DIRECTORY);
124 if (dir_fd < 0) {
125 LOG_ERROR("unable to open dir '%s', error: %s", directory_path.c_str(), strerror(errno));
126 return false;
127 }
128
129 FILE* fp = std::fopen(temp_path.c_str(), "wt");
130 if (!fp) {
131 LOG_ERROR("unable to write to file '%s', error: %s", temp_path.c_str(), strerror(errno));
132 HandleError(temp_path, &dir_fd, &fp);
133 return false;
134 }
135
136 if (std::fprintf(fp, "%s", data.c_str()) < 0) {
137 LOG_ERROR("unable to write to file '%s', error: %s", temp_path.c_str(), strerror(errno));
138 HandleError(temp_path, &dir_fd, &fp);
139 return false;
140 }
141
142 // Flush the stream buffer to the temp file.
143 if (std::fflush(fp) != 0) {
144 LOG_ERROR("unable to write flush buffer to file '%s', error: %s", temp_path.c_str(), strerror(errno));
145 HandleError(temp_path, &dir_fd, &fp);
146 return false;
147 }
148
149 // Sync written temp file out to disk. fsync() is blocking until data makes it
150 // to disk.
151 if (fsync(fileno(fp)) != 0) {
152 LOG_WARN("unable to fsync file '%s', error: %s", temp_path.c_str(), strerror(errno));
153 // Allow fsync to fail and continue
154 }
155
156 if (std::fclose(fp) != 0) {
157 LOG_ERROR("unable to close file '%s', error: %s", temp_path.c_str(), strerror(errno));
158 HandleError(temp_path, &dir_fd, &fp);
159 return false;
160 }
161 fp = nullptr;
162
163 // Change the file's permissions to Read/Write by User and Group
164 if (chmod(temp_path.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) != 0) {
165 LOG_ERROR("unable to change file permissions '%s', error: %s", temp_path.c_str(), strerror(errno));
166
167 struct stat dirstat {};
168 if (fstat(dir_fd, &dirstat) == 0) {
169 LOG_ERROR("dir st_mode = 0x%02x", dirstat.st_mode);
170 LOG_ERROR("dir uid = %d", dirstat.st_uid);
171 LOG_ERROR("dir gid = %d", dirstat.st_gid);
172 } else {
173 LOG_ERROR("unable to call fstat on the directory, error: %s", strerror(errno));
174 }
175
176 struct stat filestat {};
177 if (stat(temp_path.c_str(), &filestat) == 0) {
178 LOG_ERROR("file st_mode = 0x%02x", filestat.st_mode);
179 LOG_ERROR("file uid = %d", filestat.st_uid);
180 LOG_ERROR("file gid = %d", filestat.st_gid);
181 } else {
182 LOG_ERROR("unable to call stat, error: %s", strerror(errno));
183 }
184
185 HandleError(temp_path, &dir_fd, &fp);
186 return false;
187 }
188
189 // Rename written temp file to the actual config file.
190 if (std::rename(temp_path.c_str(), path.c_str()) != 0) {
191 LOG_ERROR("unable to commit file from '%s' to '%s', error: %s", temp_path.c_str(), path.c_str(), strerror(errno));
192 HandleError(temp_path, &dir_fd, &fp);
193 return false;
194 }
195
196 // This should ensure the directory is updated as well.
197 if (fsync(dir_fd) != 0) {
198 LOG_WARN("unable to fsync dir '%s', error: %s", directory_path.c_str(), strerror(errno));
199 }
200
201 if (close(dir_fd) != 0) {
202 LOG_ERROR("unable to close dir '%s', error: %s", directory_path.c_str(), strerror(errno));
203 HandleError(temp_path, &dir_fd, &fp);
204 return false;
205 }
206 return true;
207 }
208
RemoveFile(const std::string & path)209 bool RemoveFile(const std::string& path) {
210 if (remove(path.c_str()) != 0) {
211 LOG_ERROR("unable to remove file '%s', error: %s", path.c_str(), strerror(errno));
212 return false;
213 }
214 return true;
215 }
216
FileCreatedTime(const std::string & path)217 std::optional<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> FileCreatedTime(
218 const std::string& path) {
219 struct stat file_info;
220 if (stat(path.c_str(), &file_info) != 0) {
221 LOG_ERROR("unable to read '%s' file metadata, error: %s", path.c_str(), strerror(errno));
222 return std::nullopt;
223 }
224 using namespace std::chrono;
225 using namespace std::chrono_literals;
226 auto created_ts = file_info.st_ctim;
227 auto d = seconds{created_ts.tv_sec} + nanoseconds{created_ts.tv_nsec};
228 return time_point<system_clock>(duration_cast<system_clock::duration>(d));
229 }
230
231 } // namespace os
232 } // namespace bluetooth