• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
RegularFileOrDelete(const base::FilePath & path,uid_t uid,gid_t gid)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.
TouchFileInternal(const base::FilePath & path,uid_t uid,gid_t gid,base::ScopedFD * fd_out)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 
TouchFile(const base::FilePath & path,int new_file_permissions,uid_t uid,gid_t gid)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 
TouchFile(const base::FilePath & path)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