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