• 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 #include <base/rand_util.h>
16 #include <base/strings/string_number_conversions.h>
17 #include <base/time/time.h>
18 
19 namespace brillo {
20 
21 namespace {
22 
23 // Log sync(), fsync(), etc. calls that take this many seconds or longer.
24 constexpr const base::TimeDelta kLongSync = base::TimeDelta::FromSeconds(10);
25 
26 enum {
27   kPermissions600 = S_IRUSR | S_IWUSR,
28   kPermissions777 = S_IRWXU | S_IRWXG | S_IRWXO
29 };
30 
31 // Verify that base file permission enums are compatible with S_Ixxx. If these
32 // asserts ever fail, we'll need to ensure that users of these functions switch
33 // away from using base permission enums and add a note to the function comments
34 // indicating that base enums can not be used.
35 static_assert(base::FILE_PERMISSION_READ_BY_USER == S_IRUSR,
36               "base file permissions don't match unistd.h permissions");
37 static_assert(base::FILE_PERMISSION_WRITE_BY_USER == S_IWUSR,
38               "base file permissions don't match unistd.h permissions");
39 static_assert(base::FILE_PERMISSION_EXECUTE_BY_USER == S_IXUSR,
40               "base file permissions don't match unistd.h permissions");
41 static_assert(base::FILE_PERMISSION_READ_BY_GROUP == S_IRGRP,
42               "base file permissions don't match unistd.h permissions");
43 static_assert(base::FILE_PERMISSION_WRITE_BY_GROUP == S_IWGRP,
44               "base file permissions don't match unistd.h permissions");
45 static_assert(base::FILE_PERMISSION_EXECUTE_BY_GROUP == S_IXGRP,
46               "base file permissions don't match unistd.h permissions");
47 static_assert(base::FILE_PERMISSION_READ_BY_OTHERS == S_IROTH,
48               "base file permissions don't match unistd.h permissions");
49 static_assert(base::FILE_PERMISSION_WRITE_BY_OTHERS == S_IWOTH,
50               "base file permissions don't match unistd.h permissions");
51 static_assert(base::FILE_PERMISSION_EXECUTE_BY_OTHERS == S_IXOTH,
52               "base file permissions don't match unistd.h permissions");
53 
54 enum RegularFileOrDeleteResult {
55   kFailure = 0,      // Failed to delete whatever was at the path.
56   kRegularFile = 1,  // Regular file existed and was unchanged.
57   kEmpty = 2         // Anything that was at the path has been deleted.
58 };
59 
60 // Checks if a regular file owned by |uid| and |gid| exists at |path|, otherwise
61 // deletes anything that might be at |path|. Returns a RegularFileOrDeleteResult
62 // enum indicating what is at |path| after the function finishes.
RegularFileOrDelete(const base::FilePath & path,uid_t uid,gid_t gid)63 RegularFileOrDeleteResult RegularFileOrDelete(const base::FilePath& path,
64                                               uid_t uid,
65                                               gid_t gid) {
66   // Check for symlinks by setting O_NOFOLLOW and checking for ELOOP. This lets
67   // us use the safer fstat() instead of having to use lstat().
68   base::ScopedFD scoped_fd(HANDLE_EINTR(openat(
69       AT_FDCWD, path.value().c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW)));
70   bool path_not_empty = (errno == ELOOP || scoped_fd != -1);
71 
72   // If there is a file/directory at |path|, see if it matches our criteria.
73   if (scoped_fd != -1) {
74     struct stat file_stat;
75     if (fstat(scoped_fd.get(), &file_stat) != -1 &&
76         S_ISREG(file_stat.st_mode) && file_stat.st_uid == uid &&
77         file_stat.st_gid == gid) {
78       return kRegularFile;
79     }
80   }
81 
82   // If we get here and anything was at |path|, try to delete it so we can put
83   // our file there.
84   if (path_not_empty) {
85     if (!base::DeleteFile(path, true)) {
86       PLOG(WARNING) << "Failed to delete entity at \"" << path.value() << '"';
87       return kFailure;
88     }
89   }
90 
91   return kEmpty;
92 }
93 
94 // Handles common touch functionality but also provides an optional |fd_out|
95 // so that any further modifications to the file (e.g. permissions) can safely
96 // use the fd rather than the path. |fd_out| will only be set if a new file
97 // is created, otherwise it will be unchanged.
98 // If |fd_out| is null, this function will close the file, otherwise it's
99 // 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)100 bool TouchFileInternal(const base::FilePath& path,
101                        uid_t uid,
102                        gid_t gid,
103                        base::ScopedFD* fd_out) {
104   RegularFileOrDeleteResult result = RegularFileOrDelete(path, uid, gid);
105   switch (result) {
106     case kFailure:
107       return false;
108     case kRegularFile:
109       return true;
110     case kEmpty:
111       break;
112   }
113 
114   // base::CreateDirectory() returns true if the directory already existed.
115   if (!base::CreateDirectory(path.DirName())) {
116     PLOG(WARNING) << "Failed to create directory for \"" << path.value() << '"';
117     return false;
118   }
119 
120   // Create the file as owner-only initially.
121   base::ScopedFD scoped_fd(HANDLE_EINTR(openat(
122       AT_FDCWD, path.value().c_str(),
123       O_RDONLY | O_NOFOLLOW | O_CREAT | O_EXCL | O_CLOEXEC, kPermissions600)));
124   if (scoped_fd == -1) {
125     PLOG(WARNING) << "Failed to create file \"" << path.value() << '"';
126     return false;
127   }
128 
129   if (fd_out) {
130     fd_out->swap(scoped_fd);
131   }
132   return true;
133 }
134 
GetRandomSuffix()135 std::string GetRandomSuffix() {
136   const int kBufferSize = 6;
137   unsigned char buffer[kBufferSize];
138   base::RandBytes(buffer, arraysize(buffer));
139   std::string suffix;
140   for (int i = 0; i < kBufferSize; ++i) {
141     int random_value = buffer[i] % (2 * 26 + 10);
142     if (random_value < 26) {
143       suffix.push_back('a' + random_value);
144     } else if (random_value < 2 * 26) {
145       suffix.push_back('A' + random_value - 26);
146     } else {
147       suffix.push_back('0' + random_value - 2 * 26);
148     }
149   }
150   return suffix;
151 }
152 
153 }  // namespace
154 
TouchFile(const base::FilePath & path,int new_file_permissions,uid_t uid,gid_t gid)155 bool TouchFile(const base::FilePath& path,
156                int new_file_permissions,
157                uid_t uid,
158                gid_t gid) {
159   // Make sure |permissions| doesn't have any out-of-range bits.
160   if (new_file_permissions & ~kPermissions777) {
161     LOG(WARNING) << "Illegal permissions: " << new_file_permissions;
162     return false;
163   }
164 
165   base::ScopedFD scoped_fd;
166   if (!TouchFileInternal(path, uid, gid, &scoped_fd)) {
167     return false;
168   }
169 
170   // scoped_fd is valid only if a new file was created.
171   if (scoped_fd != -1 &&
172       HANDLE_EINTR(fchmod(scoped_fd.get(), new_file_permissions)) == -1) {
173     PLOG(WARNING) << "Failed to set permissions for \"" << path.value() << '"';
174     base::DeleteFile(path, false);
175     return false;
176   }
177 
178   return true;
179 }
180 
TouchFile(const base::FilePath & path)181 bool TouchFile(const base::FilePath& path) {
182   // Use TouchFile() instead of TouchFileInternal() to explicitly set
183   // permissions to 600 in case umask is set strangely.
184   return TouchFile(path, kPermissions600, geteuid(), getegid());
185 }
186 
WriteBlobToFile(const base::FilePath & path,const Blob & blob)187 bool WriteBlobToFile(const base::FilePath& path, const Blob& blob) {
188   return WriteToFile(path, reinterpret_cast<const char*>(blob.data()),
189                      blob.size());
190 }
191 
WriteStringToFile(const base::FilePath & path,const std::string & data)192 bool WriteStringToFile(const base::FilePath& path, const std::string& data) {
193   return WriteToFile(path, data.data(), data.size());
194 }
195 
WriteToFile(const base::FilePath & path,const char * data,size_t size)196 bool WriteToFile(const base::FilePath& path, const char* data, size_t size) {
197   if (!base::DirectoryExists(path.DirName())) {
198     if (!base::CreateDirectory(path.DirName())) {
199       LOG(ERROR) << "Cannot create directory: " << path.DirName().value();
200       return false;
201     }
202   }
203   // base::WriteFile takes an int size.
204   if (size > std::numeric_limits<int>::max()) {
205     LOG(ERROR) << "Cannot write to " << path.value()
206                << ". Data is too large: " << size << " bytes.";
207     return false;
208   }
209 
210   int data_written = base::WriteFile(path, data, size);
211   return data_written == static_cast<int>(size);
212 }
213 
SyncFileOrDirectory(const base::FilePath & path,bool is_directory,bool data_sync)214 bool SyncFileOrDirectory(const base::FilePath& path,
215                          bool is_directory,
216                          bool data_sync) {
217   const base::TimeTicks start = base::TimeTicks::Now();
218   data_sync = data_sync && !is_directory;
219 
220   int flags = (is_directory ? O_RDONLY | O_DIRECTORY : O_WRONLY);
221   int fd = HANDLE_EINTR(open(path.value().c_str(), flags));
222   if (fd < 0) {
223     PLOG(WARNING) << "Could not open " << path.value() << " for syncing";
224     return false;
225   }
226   // POSIX specifies EINTR as a possible return value of fsync() but not for
227   // fdatasync().  To be on the safe side, it is handled in both cases.
228   int result =
229       (data_sync ? HANDLE_EINTR(fdatasync(fd)) : HANDLE_EINTR(fsync(fd)));
230   if (result < 0) {
231     PLOG(WARNING) << "Failed to sync " << path.value();
232     close(fd);
233     return false;
234   }
235   // close() may not be retried on error.
236   result = IGNORE_EINTR(close(fd));
237   if (result < 0) {
238     PLOG(WARNING) << "Failed to close after sync " << path.value();
239     return false;
240   }
241 
242   const base::TimeDelta delta = base::TimeTicks::Now() - start;
243   if (delta > kLongSync) {
244     LOG(WARNING) << "Long " << (data_sync ? "fdatasync" : "fsync") << "() of "
245                  << path.value() << ": " << delta.InSeconds() << " seconds";
246   }
247 
248   return true;
249 }
250 
WriteToFileAtomic(const base::FilePath & path,const char * data,size_t size,mode_t mode)251 bool WriteToFileAtomic(const base::FilePath& path,
252                        const char* data,
253                        size_t size,
254                        mode_t mode) {
255   if (!base::DirectoryExists(path.DirName())) {
256     if (!base::CreateDirectory(path.DirName())) {
257       LOG(ERROR) << "Cannot create directory: " << path.DirName().value();
258       return false;
259     }
260   }
261   std::string random_suffix = GetRandomSuffix();
262   if (random_suffix.empty()) {
263     PLOG(WARNING) << "Could not compute random suffix";
264     return false;
265   }
266   std::string temp_name = path.AddExtension(random_suffix).value();
267   int fd =
268       HANDLE_EINTR(open(temp_name.c_str(), O_CREAT | O_EXCL | O_WRONLY, mode));
269   if (fd < 0) {
270     PLOG(WARNING) << "Could not open " << temp_name << " for atomic write";
271     unlink(temp_name.c_str());
272     return false;
273   }
274 
275   size_t position = 0;
276   while (position < size) {
277     ssize_t bytes_written =
278         HANDLE_EINTR(write(fd, data + position, size - position));
279     if (bytes_written < 0) {
280       PLOG(WARNING) << "Could not write " << temp_name;
281       close(fd);
282       unlink(temp_name.c_str());
283       return false;
284     }
285     position += bytes_written;
286   }
287 
288   if (HANDLE_EINTR(fdatasync(fd)) < 0) {
289     PLOG(WARNING) << "Could not fsync " << temp_name;
290     close(fd);
291     unlink(temp_name.c_str());
292     return false;
293   }
294   if (close(fd) < 0) {
295     PLOG(WARNING) << "Could not close " << temp_name;
296     unlink(temp_name.c_str());
297     return false;
298   }
299 
300   if (rename(temp_name.c_str(), path.value().c_str()) < 0) {
301     PLOG(WARNING) << "Could not close " << temp_name;
302     unlink(temp_name.c_str());
303     return false;
304   }
305 
306   return true;
307 }
308 
WriteBlobToFileAtomic(const base::FilePath & path,const Blob & blob,mode_t mode)309 bool WriteBlobToFileAtomic(const base::FilePath& path,
310                            const Blob& blob,
311                            mode_t mode) {
312   return WriteToFileAtomic(path, reinterpret_cast<const char*>(blob.data()),
313                            blob.size(), mode);
314 }
315 
316 }  // namespace brillo
317