1 // Copyright 2014 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "brillo/file_utils.h"
6
7 #include <fcntl.h>
8 #include <unistd.h>
9
10 #include <base/files/file_path.h>
11 #include <base/files/file_util.h>
12 #include <base/files/scoped_file.h>
13 #include <base/logging.h>
14 #include <base/posix/eintr_wrapper.h>
15 #include <base/rand_util.h>
16 #include <base/strings/string_number_conversions.h>
17 #include <base/time/time.h>
18
19 namespace brillo {
20
21 namespace {
22
23 // Log sync(), fsync(), etc. calls that take this many seconds or longer.
24 constexpr const base::TimeDelta kLongSync = base::TimeDelta::FromSeconds(10);
25
26 enum {
27 kPermissions600 = S_IRUSR | S_IWUSR,
28 kPermissions777 = S_IRWXU | S_IRWXG | S_IRWXO
29 };
30
31 // Verify that base file permission enums are compatible with S_Ixxx. If these
32 // asserts ever fail, we'll need to ensure that users of these functions switch
33 // away from using base permission enums and add a note to the function comments
34 // indicating that base enums can not be used.
35 static_assert(base::FILE_PERMISSION_READ_BY_USER == S_IRUSR,
36 "base file permissions don't match unistd.h permissions");
37 static_assert(base::FILE_PERMISSION_WRITE_BY_USER == S_IWUSR,
38 "base file permissions don't match unistd.h permissions");
39 static_assert(base::FILE_PERMISSION_EXECUTE_BY_USER == S_IXUSR,
40 "base file permissions don't match unistd.h permissions");
41 static_assert(base::FILE_PERMISSION_READ_BY_GROUP == S_IRGRP,
42 "base file permissions don't match unistd.h permissions");
43 static_assert(base::FILE_PERMISSION_WRITE_BY_GROUP == S_IWGRP,
44 "base file permissions don't match unistd.h permissions");
45 static_assert(base::FILE_PERMISSION_EXECUTE_BY_GROUP == S_IXGRP,
46 "base file permissions don't match unistd.h permissions");
47 static_assert(base::FILE_PERMISSION_READ_BY_OTHERS == S_IROTH,
48 "base file permissions don't match unistd.h permissions");
49 static_assert(base::FILE_PERMISSION_WRITE_BY_OTHERS == S_IWOTH,
50 "base file permissions don't match unistd.h permissions");
51 static_assert(base::FILE_PERMISSION_EXECUTE_BY_OTHERS == S_IXOTH,
52 "base file permissions don't match unistd.h permissions");
53
54 enum RegularFileOrDeleteResult {
55 kFailure = 0, // Failed to delete whatever was at the path.
56 kRegularFile = 1, // Regular file existed and was unchanged.
57 kEmpty = 2 // Anything that was at the path has been deleted.
58 };
59
60 // Checks if a regular file owned by |uid| and |gid| exists at |path|, otherwise
61 // deletes anything that might be at |path|. Returns a RegularFileOrDeleteResult
62 // enum indicating what is at |path| after the function finishes.
RegularFileOrDelete(const base::FilePath & path,uid_t uid,gid_t gid)63 RegularFileOrDeleteResult RegularFileOrDelete(const base::FilePath& path,
64 uid_t uid,
65 gid_t gid) {
66 // Check for symlinks by setting O_NOFOLLOW and checking for ELOOP. This lets
67 // us use the safer fstat() instead of having to use lstat().
68 base::ScopedFD scoped_fd(HANDLE_EINTR(openat(
69 AT_FDCWD, path.value().c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW)));
70 bool path_not_empty = (errno == ELOOP || scoped_fd != -1);
71
72 // If there is a file/directory at |path|, see if it matches our criteria.
73 if (scoped_fd != -1) {
74 struct stat file_stat;
75 if (fstat(scoped_fd.get(), &file_stat) != -1 &&
76 S_ISREG(file_stat.st_mode) && file_stat.st_uid == uid &&
77 file_stat.st_gid == gid) {
78 return kRegularFile;
79 }
80 }
81
82 // If we get here and anything was at |path|, try to delete it so we can put
83 // our file there.
84 if (path_not_empty) {
85 if (!base::DeleteFile(path, true)) {
86 PLOG(WARNING) << "Failed to delete entity at \"" << path.value() << '"';
87 return kFailure;
88 }
89 }
90
91 return kEmpty;
92 }
93
94 // Handles common touch functionality but also provides an optional |fd_out|
95 // so that any further modifications to the file (e.g. permissions) can safely
96 // use the fd rather than the path. |fd_out| will only be set if a new file
97 // is created, otherwise it will be unchanged.
98 // If |fd_out| is null, this function will close the file, otherwise it's
99 // expected that |fd_out| will close the file when it goes out of scope.
TouchFileInternal(const base::FilePath & path,uid_t uid,gid_t gid,base::ScopedFD * fd_out)100 bool TouchFileInternal(const base::FilePath& path,
101 uid_t uid,
102 gid_t gid,
103 base::ScopedFD* fd_out) {
104 RegularFileOrDeleteResult result = RegularFileOrDelete(path, uid, gid);
105 switch (result) {
106 case kFailure:
107 return false;
108 case kRegularFile:
109 return true;
110 case kEmpty:
111 break;
112 }
113
114 // base::CreateDirectory() returns true if the directory already existed.
115 if (!base::CreateDirectory(path.DirName())) {
116 PLOG(WARNING) << "Failed to create directory for \"" << path.value() << '"';
117 return false;
118 }
119
120 // Create the file as owner-only initially.
121 base::ScopedFD scoped_fd(HANDLE_EINTR(openat(
122 AT_FDCWD, path.value().c_str(),
123 O_RDONLY | O_NOFOLLOW | O_CREAT | O_EXCL | O_CLOEXEC, kPermissions600)));
124 if (scoped_fd == -1) {
125 PLOG(WARNING) << "Failed to create file \"" << path.value() << '"';
126 return false;
127 }
128
129 if (fd_out) {
130 fd_out->swap(scoped_fd);
131 }
132 return true;
133 }
134
GetRandomSuffix()135 std::string GetRandomSuffix() {
136 const int kBufferSize = 6;
137 unsigned char buffer[kBufferSize];
138 base::RandBytes(buffer, arraysize(buffer));
139 std::string suffix;
140 for (int i = 0; i < kBufferSize; ++i) {
141 int random_value = buffer[i] % (2 * 26 + 10);
142 if (random_value < 26) {
143 suffix.push_back('a' + random_value);
144 } else if (random_value < 2 * 26) {
145 suffix.push_back('A' + random_value - 26);
146 } else {
147 suffix.push_back('0' + random_value - 2 * 26);
148 }
149 }
150 return suffix;
151 }
152
153 } // namespace
154
TouchFile(const base::FilePath & path,int new_file_permissions,uid_t uid,gid_t gid)155 bool TouchFile(const base::FilePath& path,
156 int new_file_permissions,
157 uid_t uid,
158 gid_t gid) {
159 // Make sure |permissions| doesn't have any out-of-range bits.
160 if (new_file_permissions & ~kPermissions777) {
161 LOG(WARNING) << "Illegal permissions: " << new_file_permissions;
162 return false;
163 }
164
165 base::ScopedFD scoped_fd;
166 if (!TouchFileInternal(path, uid, gid, &scoped_fd)) {
167 return false;
168 }
169
170 // scoped_fd is valid only if a new file was created.
171 if (scoped_fd != -1 &&
172 HANDLE_EINTR(fchmod(scoped_fd.get(), new_file_permissions)) == -1) {
173 PLOG(WARNING) << "Failed to set permissions for \"" << path.value() << '"';
174 base::DeleteFile(path, false);
175 return false;
176 }
177
178 return true;
179 }
180
TouchFile(const base::FilePath & path)181 bool TouchFile(const base::FilePath& path) {
182 // Use TouchFile() instead of TouchFileInternal() to explicitly set
183 // permissions to 600 in case umask is set strangely.
184 return TouchFile(path, kPermissions600, geteuid(), getegid());
185 }
186
WriteBlobToFile(const base::FilePath & path,const Blob & blob)187 bool WriteBlobToFile(const base::FilePath& path, const Blob& blob) {
188 return WriteToFile(path, reinterpret_cast<const char*>(blob.data()),
189 blob.size());
190 }
191
WriteStringToFile(const base::FilePath & path,const std::string & data)192 bool WriteStringToFile(const base::FilePath& path, const std::string& data) {
193 return WriteToFile(path, data.data(), data.size());
194 }
195
WriteToFile(const base::FilePath & path,const char * data,size_t size)196 bool WriteToFile(const base::FilePath& path, const char* data, size_t size) {
197 if (!base::DirectoryExists(path.DirName())) {
198 if (!base::CreateDirectory(path.DirName())) {
199 LOG(ERROR) << "Cannot create directory: " << path.DirName().value();
200 return false;
201 }
202 }
203 // base::WriteFile takes an int size.
204 if (size > std::numeric_limits<int>::max()) {
205 LOG(ERROR) << "Cannot write to " << path.value()
206 << ". Data is too large: " << size << " bytes.";
207 return false;
208 }
209
210 int data_written = base::WriteFile(path, data, size);
211 return data_written == static_cast<int>(size);
212 }
213
SyncFileOrDirectory(const base::FilePath & path,bool is_directory,bool data_sync)214 bool SyncFileOrDirectory(const base::FilePath& path,
215 bool is_directory,
216 bool data_sync) {
217 const base::TimeTicks start = base::TimeTicks::Now();
218 data_sync = data_sync && !is_directory;
219
220 int flags = (is_directory ? O_RDONLY | O_DIRECTORY : O_WRONLY);
221 int fd = HANDLE_EINTR(open(path.value().c_str(), flags));
222 if (fd < 0) {
223 PLOG(WARNING) << "Could not open " << path.value() << " for syncing";
224 return false;
225 }
226 // POSIX specifies EINTR as a possible return value of fsync() but not for
227 // fdatasync(). To be on the safe side, it is handled in both cases.
228 int result =
229 (data_sync ? HANDLE_EINTR(fdatasync(fd)) : HANDLE_EINTR(fsync(fd)));
230 if (result < 0) {
231 PLOG(WARNING) << "Failed to sync " << path.value();
232 close(fd);
233 return false;
234 }
235 // close() may not be retried on error.
236 result = IGNORE_EINTR(close(fd));
237 if (result < 0) {
238 PLOG(WARNING) << "Failed to close after sync " << path.value();
239 return false;
240 }
241
242 const base::TimeDelta delta = base::TimeTicks::Now() - start;
243 if (delta > kLongSync) {
244 LOG(WARNING) << "Long " << (data_sync ? "fdatasync" : "fsync") << "() of "
245 << path.value() << ": " << delta.InSeconds() << " seconds";
246 }
247
248 return true;
249 }
250
WriteToFileAtomic(const base::FilePath & path,const char * data,size_t size,mode_t mode)251 bool WriteToFileAtomic(const base::FilePath& path,
252 const char* data,
253 size_t size,
254 mode_t mode) {
255 if (!base::DirectoryExists(path.DirName())) {
256 if (!base::CreateDirectory(path.DirName())) {
257 LOG(ERROR) << "Cannot create directory: " << path.DirName().value();
258 return false;
259 }
260 }
261 std::string random_suffix = GetRandomSuffix();
262 if (random_suffix.empty()) {
263 PLOG(WARNING) << "Could not compute random suffix";
264 return false;
265 }
266 std::string temp_name = path.AddExtension(random_suffix).value();
267 int fd =
268 HANDLE_EINTR(open(temp_name.c_str(), O_CREAT | O_EXCL | O_WRONLY, mode));
269 if (fd < 0) {
270 PLOG(WARNING) << "Could not open " << temp_name << " for atomic write";
271 unlink(temp_name.c_str());
272 return false;
273 }
274
275 size_t position = 0;
276 while (position < size) {
277 ssize_t bytes_written =
278 HANDLE_EINTR(write(fd, data + position, size - position));
279 if (bytes_written < 0) {
280 PLOG(WARNING) << "Could not write " << temp_name;
281 close(fd);
282 unlink(temp_name.c_str());
283 return false;
284 }
285 position += bytes_written;
286 }
287
288 if (HANDLE_EINTR(fdatasync(fd)) < 0) {
289 PLOG(WARNING) << "Could not fsync " << temp_name;
290 close(fd);
291 unlink(temp_name.c_str());
292 return false;
293 }
294 if (close(fd) < 0) {
295 PLOG(WARNING) << "Could not close " << temp_name;
296 unlink(temp_name.c_str());
297 return false;
298 }
299
300 if (rename(temp_name.c_str(), path.value().c_str()) < 0) {
301 PLOG(WARNING) << "Could not close " << temp_name;
302 unlink(temp_name.c_str());
303 return false;
304 }
305
306 return true;
307 }
308
WriteBlobToFileAtomic(const base::FilePath & path,const Blob & blob,mode_t mode)309 bool WriteBlobToFileAtomic(const base::FilePath& path,
310 const Blob& blob,
311 mode_t mode) {
312 return WriteToFileAtomic(path, reinterpret_cast<const char*>(blob.data()),
313 blob.size(), mode);
314 }
315
316 } // namespace brillo
317