Home | History | Annotate | Download | only in brillo
      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 
     16 namespace brillo {
     17 
     18 namespace {
     19 
     20 enum {
     21   kPermissions600 = S_IRUSR | S_IWUSR,
     22   kPermissions777 = S_IRWXU | S_IRWXG | S_IRWXO
     23 };
     24 
     25 // Verify that base file permission enums are compatible with S_Ixxx. If these
     26 // asserts ever fail, we'll need to ensure that users of these functions switch
     27 // away from using base permission enums and add a note to the function comments
     28 // indicating that base enums can not be used.
     29 static_assert(base::FILE_PERMISSION_READ_BY_USER == S_IRUSR,
     30               "base file permissions don't match unistd.h permissions");
     31 static_assert(base::FILE_PERMISSION_WRITE_BY_USER == S_IWUSR,
     32               "base file permissions don't match unistd.h permissions");
     33 static_assert(base::FILE_PERMISSION_EXECUTE_BY_USER == S_IXUSR,
     34               "base file permissions don't match unistd.h permissions");
     35 static_assert(base::FILE_PERMISSION_READ_BY_GROUP == S_IRGRP,
     36               "base file permissions don't match unistd.h permissions");
     37 static_assert(base::FILE_PERMISSION_WRITE_BY_GROUP == S_IWGRP,
     38               "base file permissions don't match unistd.h permissions");
     39 static_assert(base::FILE_PERMISSION_EXECUTE_BY_GROUP == S_IXGRP,
     40               "base file permissions don't match unistd.h permissions");
     41 static_assert(base::FILE_PERMISSION_READ_BY_OTHERS == S_IROTH,
     42               "base file permissions don't match unistd.h permissions");
     43 static_assert(base::FILE_PERMISSION_WRITE_BY_OTHERS == S_IWOTH,
     44               "base file permissions don't match unistd.h permissions");
     45 static_assert(base::FILE_PERMISSION_EXECUTE_BY_OTHERS == S_IXOTH,
     46               "base file permissions don't match unistd.h permissions");
     47 
     48 enum RegularFileOrDeleteResult {
     49   kFailure = 0,      // Failed to delete whatever was at the path.
     50   kRegularFile = 1,  // Regular file existed and was unchanged.
     51   kEmpty = 2         // Anything that was at the path has been deleted.
     52 };
     53 
     54 // Checks if a regular file owned by |uid| and |gid| exists at |path|, otherwise
     55 // deletes anything that might be at |path|. Returns a RegularFileOrDeleteResult
     56 // enum indicating what is at |path| after the function finishes.
     57 RegularFileOrDeleteResult RegularFileOrDelete(const base::FilePath& path,
     58                                               uid_t uid,
     59                                               gid_t gid) {
     60   // Check for symlinks by setting O_NOFOLLOW and checking for ELOOP. This lets
     61   // us use the safer fstat() instead of having to use lstat().
     62   base::ScopedFD scoped_fd(HANDLE_EINTR(openat(
     63       AT_FDCWD, path.value().c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW)));
     64   bool path_not_empty = (errno == ELOOP || scoped_fd != -1);
     65 
     66   // If there is a file/directory at |path|, see if it matches our criteria.
     67   if (scoped_fd != -1) {
     68     struct stat file_stat;
     69     if (fstat(scoped_fd.get(), &file_stat) != -1 &&
     70         S_ISREG(file_stat.st_mode) && file_stat.st_uid == uid &&
     71         file_stat.st_gid == gid) {
     72       return kRegularFile;
     73     }
     74   }
     75 
     76   // If we get here and anything was at |path|, try to delete it so we can put
     77   // our file there.
     78   if (path_not_empty) {
     79     if (!base::DeleteFile(path, true)) {
     80       PLOG(WARNING) << "Failed to delete entity at \"" << path.value() << '"';
     81       return kFailure;
     82     }
     83   }
     84 
     85   return kEmpty;
     86 }
     87 
     88 // Handles common touch functionality but also provides an optional |fd_out|
     89 // so that any further modifications to the file (e.g. permissions) can safely
     90 // use the fd rather than the path. |fd_out| will only be set if a new file
     91 // is created, otherwise it will be unchanged.
     92 // If |fd_out| is null, this function will close the file, otherwise it's
     93 // expected that |fd_out| will close the file when it goes out of scope.
     94 bool TouchFileInternal(const base::FilePath& path,
     95                        uid_t uid,
     96                        gid_t gid,
     97                        base::ScopedFD* fd_out) {
     98   RegularFileOrDeleteResult result = RegularFileOrDelete(path, uid, gid);
     99   switch (result) {
    100     case kFailure:
    101       return false;
    102     case kRegularFile:
    103       return true;
    104     case kEmpty:
    105       break;
    106   }
    107 
    108   // base::CreateDirectory() returns true if the directory already existed.
    109   if (!base::CreateDirectory(path.DirName())) {
    110     PLOG(WARNING) << "Failed to create directory for \"" << path.value() << '"';
    111     return false;
    112   }
    113 
    114   // Create the file as owner-only initially.
    115   base::ScopedFD scoped_fd(
    116       HANDLE_EINTR(openat(AT_FDCWD,
    117                           path.value().c_str(),
    118                           O_RDONLY | O_NOFOLLOW | O_CREAT | O_EXCL | O_CLOEXEC,
    119                           kPermissions600)));
    120   if (scoped_fd == -1) {
    121     PLOG(WARNING) << "Failed to create file \"" << path.value() << '"';
    122     return false;
    123   }
    124 
    125   if (fd_out) {
    126     fd_out->swap(scoped_fd);
    127   }
    128   return true;
    129 }
    130 
    131 }  // namespace
    132 
    133 bool TouchFile(const base::FilePath& path,
    134                int new_file_permissions,
    135                uid_t uid,
    136                gid_t gid) {
    137   // Make sure |permissions| doesn't have any out-of-range bits.
    138   if (new_file_permissions & ~kPermissions777) {
    139     LOG(WARNING) << "Illegal permissions: " << new_file_permissions;
    140     return false;
    141   }
    142 
    143   base::ScopedFD scoped_fd;
    144   if (!TouchFileInternal(path, uid, gid, &scoped_fd)) {
    145     return false;
    146   }
    147 
    148   // scoped_fd is valid only if a new file was created.
    149   if (scoped_fd != -1 &&
    150       HANDLE_EINTR(fchmod(scoped_fd.get(), new_file_permissions)) == -1) {
    151     PLOG(WARNING) << "Failed to set permissions for \"" << path.value() << '"';
    152     base::DeleteFile(path, false);
    153     return false;
    154   }
    155 
    156   return true;
    157 }
    158 
    159 bool TouchFile(const base::FilePath& path) {
    160   // Use TouchFile() instead of TouchFileInternal() to explicitly set
    161   // permissions to 600 in case umask is set strangely.
    162   return TouchFile(path, kPermissions600, geteuid(), getegid());
    163 }
    164 
    165 }  // namespace brillo
    166