xref: /aosp_15_r20/external/libbrillo/brillo/file_utils.cc (revision 1a96fba65179ea7d3f56207137718607415c5953)
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