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 <limits>
11 #include <utility>
12 #include <vector>
13
14 #include <base/files/file_enumerator.h>
15 #include <base/files/file_path.h>
16 #include <base/files/file_util.h>
17 #include <base/logging.h>
18 #include <base/posix/eintr_wrapper.h>
19 #include <base/rand_util.h>
20 #include <base/stl_util.h>
21 #include <base/strings/string_number_conversions.h>
22 #include <base/strings/stringprintf.h>
23 #include <base/time/time.h>
24
25 namespace brillo {
26
27 namespace {
28
29 // Log sync(), fsync(), etc. calls that take this many seconds or longer.
30 constexpr const base::TimeDelta kLongSync = base::TimeDelta::FromSeconds(10);
31
32 enum {
33 kPermissions600 = S_IRUSR | S_IWUSR,
34 kPermissions777 = S_IRWXU | S_IRWXG | S_IRWXO,
35 kPermissions755 = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
36 };
37
38 // Verify that base file permission enums are compatible with S_Ixxx. If these
39 // asserts ever fail, we'll need to ensure that users of these functions switch
40 // away from using base permission enums and add a note to the function comments
41 // indicating that base enums can not be used.
42 static_assert(base::FILE_PERMISSION_READ_BY_USER == S_IRUSR,
43 "base file permissions don't match unistd.h permissions");
44 static_assert(base::FILE_PERMISSION_WRITE_BY_USER == S_IWUSR,
45 "base file permissions don't match unistd.h permissions");
46 static_assert(base::FILE_PERMISSION_EXECUTE_BY_USER == S_IXUSR,
47 "base file permissions don't match unistd.h permissions");
48 static_assert(base::FILE_PERMISSION_READ_BY_GROUP == S_IRGRP,
49 "base file permissions don't match unistd.h permissions");
50 static_assert(base::FILE_PERMISSION_WRITE_BY_GROUP == S_IWGRP,
51 "base file permissions don't match unistd.h permissions");
52 static_assert(base::FILE_PERMISSION_EXECUTE_BY_GROUP == S_IXGRP,
53 "base file permissions don't match unistd.h permissions");
54 static_assert(base::FILE_PERMISSION_READ_BY_OTHERS == S_IROTH,
55 "base file permissions don't match unistd.h permissions");
56 static_assert(base::FILE_PERMISSION_WRITE_BY_OTHERS == S_IWOTH,
57 "base file permissions don't match unistd.h permissions");
58 static_assert(base::FILE_PERMISSION_EXECUTE_BY_OTHERS == S_IXOTH,
59 "base file permissions don't match unistd.h permissions");
60
61 enum RegularFileOrDeleteResult {
62 kFailure = 0, // Failed to delete whatever was at the path.
63 kRegularFile = 1, // Regular file existed and was unchanged.
64 kEmpty = 2 // Anything that was at the path has been deleted.
65 };
66
67 // Checks if a regular file owned by |uid| and |gid| exists at |path|, otherwise
68 // deletes anything that might be at |path|. Returns a RegularFileOrDeleteResult
69 // enum indicating what is at |path| after the function finishes.
RegularFileOrDelete(const base::FilePath & path,uid_t uid,gid_t gid)70 RegularFileOrDeleteResult RegularFileOrDelete(const base::FilePath& path,
71 uid_t uid,
72 gid_t gid) {
73 // Check for symlinks by setting O_NOFOLLOW and checking for ELOOP. This lets
74 // us use the safer fstat() instead of having to use lstat().
75 base::ScopedFD scoped_fd(HANDLE_EINTR(openat(
76 AT_FDCWD, path.value().c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW)));
77 bool path_not_empty = (errno == ELOOP || scoped_fd != -1);
78
79 // If there is a file/directory at |path|, see if it matches our criteria.
80 if (scoped_fd != -1) {
81 struct stat file_stat;
82 if (fstat(scoped_fd.get(), &file_stat) != -1 &&
83 S_ISREG(file_stat.st_mode) && file_stat.st_uid == uid &&
84 file_stat.st_gid == gid) {
85 return kRegularFile;
86 }
87 }
88
89 // If we get here and anything was at |path|, try to delete it so we can put
90 // our file there.
91 if (path_not_empty) {
92 if (!base::DeleteFile(path, true)) {
93 PLOG(WARNING) << "Failed to delete entity at \"" << path.value() << '"';
94 return kFailure;
95 }
96 }
97
98 return kEmpty;
99 }
100
101 // Handles common touch functionality but also provides an optional |fd_out|
102 // so that any further modifications to the file (e.g. permissions) can safely
103 // use the fd rather than the path. |fd_out| will only be set if a new file
104 // is created, otherwise it will be unchanged.
105 // If |fd_out| is null, this function will close the file, otherwise it's
106 // 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)107 bool TouchFileInternal(const base::FilePath& path,
108 uid_t uid,
109 gid_t gid,
110 base::ScopedFD* fd_out) {
111 RegularFileOrDeleteResult result = RegularFileOrDelete(path, uid, gid);
112 switch (result) {
113 case kFailure:
114 return false;
115 case kRegularFile:
116 return true;
117 case kEmpty:
118 break;
119 }
120
121 // base::CreateDirectory() returns true if the directory already existed.
122 if (!base::CreateDirectory(path.DirName())) {
123 PLOG(WARNING) << "Failed to create directory for \"" << path.value() << '"';
124 return false;
125 }
126
127 // Create the file as owner-only initially.
128 base::ScopedFD scoped_fd(HANDLE_EINTR(openat(
129 AT_FDCWD, path.value().c_str(),
130 O_RDONLY | O_NOFOLLOW | O_CREAT | O_EXCL | O_CLOEXEC, kPermissions600)));
131 if (scoped_fd == -1) {
132 PLOG(WARNING) << "Failed to create file \"" << path.value() << '"';
133 return false;
134 }
135
136 if (fd_out) {
137 fd_out->swap(scoped_fd);
138 }
139 return true;
140 }
141
GetRandomSuffix()142 std::string GetRandomSuffix() {
143 const int kBufferSize = 6;
144 unsigned char buffer[kBufferSize];
145 base::RandBytes(buffer, base::size(buffer));
146 std::string suffix;
147 for (int i = 0; i < kBufferSize; ++i) {
148 int random_value = buffer[i] % (2 * 26 + 10);
149 if (random_value < 26) {
150 suffix.push_back('a' + random_value);
151 } else if (random_value < 2 * 26) {
152 suffix.push_back('A' + random_value - 26);
153 } else {
154 suffix.push_back('0' + random_value - 2 * 26);
155 }
156 }
157 return suffix;
158 }
159
OpenPathComponentInternal(int parent_fd,const std::string & file,int flags,mode_t mode)160 base::ScopedFD OpenPathComponentInternal(int parent_fd,
161 const std::string& file,
162 int flags,
163 mode_t mode) {
164 DCHECK(file == "/" || file.find("/") == std::string::npos);
165 base::ScopedFD fd;
166
167 // O_NONBLOCK is used to avoid hanging on edge cases (e.g. a serial port with
168 // flow control, or a FIFO without a writer).
169 if (parent_fd >= 0 || parent_fd == AT_FDCWD) {
170 fd.reset(HANDLE_EINTR(openat(parent_fd, file.c_str(),
171 flags | O_NONBLOCK | O_NOFOLLOW | O_CLOEXEC,
172 mode)));
173 } else if (file == "/") {
174 fd.reset(HANDLE_EINTR(open(
175 file.c_str(),
176 flags | O_RDONLY | O_DIRECTORY | O_NONBLOCK | O_NOFOLLOW | O_CLOEXEC,
177 mode)));
178 }
179
180 if (!fd.is_valid()) {
181 // open(2) fails with ELOOP when the last component of the |path| is a
182 // symlink. It fails with ENXIO when |path| is a FIFO and |flags| is for
183 // writing because of the O_NONBLOCK flag added above.
184 if (errno == ELOOP || errno == ENXIO) {
185 PLOG(WARNING) << "Failed to open " << file << " safely.";
186 } else {
187 PLOG(WARNING) << "Failed to open " << file << ".";
188 }
189 return base::ScopedFD();
190 }
191
192 // Remove the O_NONBLOCK flag unless the original |flags| have it.
193 if ((flags & O_NONBLOCK) == 0) {
194 flags = fcntl(fd.get(), F_GETFL);
195 if (flags == -1) {
196 PLOG(ERROR) << "Failed to get fd flags for " << file;
197 return base::ScopedFD();
198 }
199 if (fcntl(fd.get(), F_SETFL, flags & ~O_NONBLOCK)) {
200 PLOG(ERROR) << "Failed to set fd flags for " << file;
201 return base::ScopedFD();
202 }
203 }
204
205 return fd;
206 }
207
OpenSafelyInternal(int parent_fd,const base::FilePath & path,int flags,mode_t mode)208 base::ScopedFD OpenSafelyInternal(int parent_fd,
209 const base::FilePath& path,
210 int flags,
211 mode_t mode) {
212 std::vector<std::string> components;
213 path.GetComponents(&components);
214
215 auto itr = components.begin();
216 if (itr == components.end()) {
217 LOG(ERROR) << "A path is required.";
218 return base::ScopedFD(); // This is an invalid fd.
219 }
220
221 base::ScopedFD child_fd;
222 int parent_flags = flags | O_NONBLOCK | O_RDONLY | O_DIRECTORY | O_PATH;
223 for (; itr + 1 != components.end(); ++itr) {
224 child_fd = OpenPathComponentInternal(parent_fd, *itr, parent_flags, 0);
225 if (!child_fd.is_valid()) {
226 return base::ScopedFD();
227 }
228 parent_fd = child_fd.get();
229 }
230
231 return OpenPathComponentInternal(parent_fd, *itr, flags, mode);
232 }
233
234 } // namespace
235
TouchFile(const base::FilePath & path,int new_file_permissions,uid_t uid,gid_t gid)236 bool TouchFile(const base::FilePath& path,
237 int new_file_permissions,
238 uid_t uid,
239 gid_t gid) {
240 // Make sure |permissions| doesn't have any out-of-range bits.
241 if (new_file_permissions & ~kPermissions777) {
242 LOG(WARNING) << "Illegal permissions: " << new_file_permissions;
243 return false;
244 }
245
246 base::ScopedFD scoped_fd;
247 if (!TouchFileInternal(path, uid, gid, &scoped_fd)) {
248 return false;
249 }
250
251 // scoped_fd is valid only if a new file was created.
252 if (scoped_fd != -1 &&
253 HANDLE_EINTR(fchmod(scoped_fd.get(), new_file_permissions)) == -1) {
254 PLOG(WARNING) << "Failed to set permissions for \"" << path.value() << '"';
255 base::DeleteFile(path, false);
256 return false;
257 }
258
259 return true;
260 }
261
TouchFile(const base::FilePath & path)262 bool TouchFile(const base::FilePath& path) {
263 // Use TouchFile() instead of TouchFileInternal() to explicitly set
264 // permissions to 600 in case umask is set strangely.
265 return TouchFile(path, kPermissions600, geteuid(), getegid());
266 }
267
OpenSafely(const base::FilePath & path,int flags,mode_t mode)268 base::ScopedFD OpenSafely(const base::FilePath& path, int flags, mode_t mode) {
269 if (!path.IsAbsolute()) {
270 LOG(ERROR) << "An absolute path is required.";
271 return base::ScopedFD(); // This is an invalid fd.
272 }
273
274 base::ScopedFD fd(OpenSafelyInternal(-1, path, flags, mode));
275 if (!fd.is_valid())
276 return base::ScopedFD();
277
278 // Ensure the opened file is a regular file or directory.
279 struct stat st;
280 if (fstat(fd.get(), &st) < 0) {
281 PLOG(ERROR) << "Failed to fstat " << path.value();
282 return base::ScopedFD();
283 }
284
285 // This detects a FIFO opened for reading, for example.
286 if (flags & O_DIRECTORY) {
287 if (!S_ISDIR(st.st_mode)) {
288 LOG(ERROR) << path.value() << " is not a directory: " << st.st_mode;
289 return base::ScopedFD();
290 }
291 } else if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) {
292 LOG(ERROR) << path.value()
293 << " is not a regular file or directory: " << st.st_mode;
294 return base::ScopedFD();
295 }
296
297 return fd;
298 }
299
OpenAtSafely(int parent_fd,const base::FilePath & path,int flags,mode_t mode)300 base::ScopedFD OpenAtSafely(int parent_fd,
301 const base::FilePath& path,
302 int flags,
303 mode_t mode) {
304 base::ScopedFD fd(OpenSafelyInternal(parent_fd, path, flags, mode));
305 if (!fd.is_valid())
306 return base::ScopedFD();
307
308 // Ensure the opened file is a regular file or directory.
309 struct stat st;
310 if (fstat(fd.get(), &st) < 0) {
311 PLOG(ERROR) << "Failed to fstat " << path.value();
312 return base::ScopedFD();
313 }
314
315 // This detects a FIFO opened for reading, for example.
316 if (flags & O_DIRECTORY) {
317 if (!S_ISDIR(st.st_mode)) {
318 LOG(ERROR) << path.value() << " is not a directory: " << st.st_mode;
319 return base::ScopedFD();
320 }
321 } else if (!S_ISREG(st.st_mode)) {
322 LOG(ERROR) << path.value() << " is not a regular file: " << st.st_mode;
323 return base::ScopedFD();
324 }
325
326 return fd;
327 }
328
OpenFifoSafely(const base::FilePath & path,int flags,mode_t mode)329 base::ScopedFD OpenFifoSafely(const base::FilePath& path,
330 int flags,
331 mode_t mode) {
332 if (!path.IsAbsolute()) {
333 LOG(ERROR) << "An absolute path is required.";
334 return base::ScopedFD(); // This is an invalid fd.
335 }
336
337 base::ScopedFD fd(OpenSafelyInternal(-1, path, flags, mode));
338 if (!fd.is_valid())
339 return base::ScopedFD();
340
341 // Ensure the opened file is a FIFO.
342 struct stat st;
343 if (fstat(fd.get(), &st) < 0) {
344 PLOG(ERROR) << "Failed to fstat " << path.value();
345 return base::ScopedFD();
346 }
347
348 if (!S_ISFIFO(st.st_mode)) {
349 LOG(ERROR) << path.value() << " is not a FIFO: " << st.st_mode;
350 return base::ScopedFD();
351 }
352
353 return fd;
354 }
355
MkdirRecursively(const base::FilePath & full_path,mode_t mode)356 base::ScopedFD MkdirRecursively(const base::FilePath& full_path, mode_t mode) {
357 std::vector<std::string> components;
358 full_path.GetComponents(&components);
359
360 auto itr = components.begin();
361 if (!full_path.IsAbsolute() || itr == components.end()) {
362 LOG(ERROR) << "An absolute path is required.";
363 return base::ScopedFD(); // This is an invalid fd.
364 }
365
366 base::ScopedFD parent_fd;
367 int parent_flags = O_NONBLOCK | O_RDONLY | O_DIRECTORY | O_PATH;
368 while (itr + 1 != components.end()) {
369 base::ScopedFD child(
370 OpenPathComponentInternal(parent_fd.get(), *itr, parent_flags, 0));
371 if (!child.is_valid()) {
372 return base::ScopedFD();
373 }
374 parent_fd = std::move(child);
375
376 ++itr;
377
378 // Try to create the directory. Note that Chromium's MkdirRecursively() uses
379 // 0700, but we use 0755.
380 if (mkdirat(parent_fd.get(), itr->c_str(), mode) != 0) {
381 if (errno != EEXIST) {
382 PLOG(ERROR) << "Failed to mkdirat " << *itr
383 << ": full_path=" << full_path.value();
384 return base::ScopedFD();
385 }
386 }
387 }
388
389 return OpenPathComponentInternal(parent_fd.get(), *itr,
390 O_RDONLY | O_DIRECTORY, 0);
391 }
392
WriteStringToFile(const base::FilePath & path,const std::string & data)393 bool WriteStringToFile(const base::FilePath& path, const std::string& data) {
394 return WriteToFile(path, data.data(), data.size());
395 }
396
WriteToFile(const base::FilePath & path,const char * data,size_t size)397 bool WriteToFile(const base::FilePath& path, const char* data, size_t size) {
398 if (!base::DirectoryExists(path.DirName())) {
399 if (!base::CreateDirectory(path.DirName())) {
400 LOG(ERROR) << "Cannot create directory: " << path.DirName().value();
401 return false;
402 }
403 }
404 // base::WriteFile takes an int size.
405 if (size > std::numeric_limits<int>::max()) {
406 LOG(ERROR) << "Cannot write to " << path.value()
407 << ". Data is too large: " << size << " bytes.";
408 return false;
409 }
410
411 int data_written = base::WriteFile(path, data, size);
412 return data_written == static_cast<int>(size);
413 }
414
SyncFileOrDirectory(const base::FilePath & path,bool is_directory,bool data_sync)415 bool SyncFileOrDirectory(const base::FilePath& path,
416 bool is_directory,
417 bool data_sync) {
418 const base::TimeTicks start = base::TimeTicks::Now();
419 data_sync = data_sync && !is_directory;
420
421 int flags = (is_directory ? O_RDONLY | O_DIRECTORY : O_WRONLY);
422 int fd = HANDLE_EINTR(open(path.value().c_str(), flags));
423 if (fd < 0) {
424 PLOG(WARNING) << "Could not open " << path.value() << " for syncing";
425 return false;
426 }
427 // POSIX specifies EINTR as a possible return value of fsync() but not for
428 // fdatasync(). To be on the safe side, it is handled in both cases.
429 int result =
430 (data_sync ? HANDLE_EINTR(fdatasync(fd)) : HANDLE_EINTR(fsync(fd)));
431 if (result < 0) {
432 PLOG(WARNING) << "Failed to sync " << path.value();
433 close(fd);
434 return false;
435 }
436 // close() may not be retried on error.
437 result = IGNORE_EINTR(close(fd));
438 if (result < 0) {
439 PLOG(WARNING) << "Failed to close after sync " << path.value();
440 return false;
441 }
442
443 const base::TimeDelta delta = base::TimeTicks::Now() - start;
444 if (delta > kLongSync) {
445 LOG(WARNING) << "Long " << (data_sync ? "fdatasync" : "fsync") << "() of "
446 << path.value() << ": " << delta.InSeconds() << " seconds";
447 }
448
449 return true;
450 }
451
WriteToFileAtomic(const base::FilePath & path,const char * data,size_t size,mode_t mode)452 bool WriteToFileAtomic(const base::FilePath& path,
453 const char* data,
454 size_t size,
455 mode_t mode) {
456 if (!base::DirectoryExists(path.DirName())) {
457 if (!base::CreateDirectory(path.DirName())) {
458 LOG(ERROR) << "Cannot create directory: " << path.DirName().value();
459 return false;
460 }
461 }
462 std::string random_suffix = GetRandomSuffix();
463 if (random_suffix.empty()) {
464 PLOG(WARNING) << "Could not compute random suffix";
465 return false;
466 }
467 std::string temp_name = path.AddExtension(random_suffix).value();
468 int fd =
469 HANDLE_EINTR(open(temp_name.c_str(), O_CREAT | O_EXCL | O_WRONLY, mode));
470 if (fd < 0) {
471 PLOG(WARNING) << "Could not open " << temp_name << " for atomic write";
472 unlink(temp_name.c_str());
473 return false;
474 }
475
476 size_t position = 0;
477 while (position < size) {
478 ssize_t bytes_written =
479 HANDLE_EINTR(write(fd, data + position, size - position));
480 if (bytes_written < 0) {
481 PLOG(WARNING) << "Could not write " << temp_name;
482 close(fd);
483 unlink(temp_name.c_str());
484 return false;
485 }
486 position += bytes_written;
487 }
488
489 if (HANDLE_EINTR(fdatasync(fd)) < 0) {
490 PLOG(WARNING) << "Could not fsync " << temp_name;
491 close(fd);
492 unlink(temp_name.c_str());
493 return false;
494 }
495 if (close(fd) < 0) {
496 PLOG(WARNING) << "Could not close " << temp_name;
497 unlink(temp_name.c_str());
498 return false;
499 }
500
501 if (rename(temp_name.c_str(), path.value().c_str()) < 0) {
502 PLOG(WARNING) << "Could not close " << temp_name;
503 unlink(temp_name.c_str());
504 return false;
505 }
506
507 return true;
508 }
509
ComputeDirectoryDiskUsage(const base::FilePath & root_path)510 int64_t ComputeDirectoryDiskUsage(const base::FilePath& root_path) {
511 constexpr size_t S_BLKSIZE = 512;
512 int64_t running_blocks = 0;
513 base::FileEnumerator file_iter(root_path, true,
514 base::FileEnumerator::FILES |
515 base::FileEnumerator::DIRECTORIES |
516 base::FileEnumerator::SHOW_SYM_LINKS);
517 while (!file_iter.Next().empty()) {
518 // st_blocks in struct stat is the number of S_BLKSIZE (512) bytes sized
519 // blocks occupied by this file.
520 running_blocks += file_iter.GetInfo().stat().st_blocks;
521 }
522 // Each block is S_BLKSIZE (512) bytes so *S_BLKSIZE.
523 return running_blocks * S_BLKSIZE;
524 }
525
526 } // namespace brillo
527