blob: 9d3bd53e402b282ea6d1b0df19983577132b68da [file] [log] [blame]
David Pursell5cadeae2014-11-20 16:58:51 -08001// 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 "libchromeos/chromeos/file_utils.h"
6
7#include <fcntl.h>
David Pursell5cadeae2014-11-20 16:58:51 -08008#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
16namespace chromeos {
17
18namespace {
19
20enum {
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.
29static_assert(base::FILE_PERMISSION_READ_BY_USER == S_IRUSR,
30 "base file permissions don't match unistd.h permissions");
31static_assert(base::FILE_PERMISSION_WRITE_BY_USER == S_IWUSR,
32 "base file permissions don't match unistd.h permissions");
33static_assert(base::FILE_PERMISSION_EXECUTE_BY_USER == S_IXUSR,
34 "base file permissions don't match unistd.h permissions");
35static_assert(base::FILE_PERMISSION_READ_BY_GROUP == S_IRGRP,
36 "base file permissions don't match unistd.h permissions");
37static_assert(base::FILE_PERMISSION_WRITE_BY_GROUP == S_IWGRP,
38 "base file permissions don't match unistd.h permissions");
39static_assert(base::FILE_PERMISSION_EXECUTE_BY_GROUP == S_IXGRP,
40 "base file permissions don't match unistd.h permissions");
41static_assert(base::FILE_PERMISSION_READ_BY_OTHERS == S_IROTH,
42 "base file permissions don't match unistd.h permissions");
43static_assert(base::FILE_PERMISSION_WRITE_BY_OTHERS == S_IWOTH,
44 "base file permissions don't match unistd.h permissions");
45static_assert(base::FILE_PERMISSION_EXECUTE_BY_OTHERS == S_IXOTH,
46 "base file permissions don't match unistd.h permissions");
47
48enum 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.
57RegularFileOrDeleteResult 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 &&
Alex Vakulenko05d29042015-01-13 09:39:25 -080070 S_ISREG(file_stat.st_mode) && file_stat.st_uid == uid &&
David Pursell5cadeae2014-11-20 16:58:51 -080071 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.
94bool 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
133bool 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
159bool TouchFile(const base::FilePath& path) {
160 // Use TouchFile() instead of TouchFileInternal() to explicitly set
161 // permissions to 600 in case umask is set strangely.
David Pursellbf74b6a2014-12-10 15:05:31 -0800162 return TouchFile(path, kPermissions600, geteuid(), getegid());
David Pursell5cadeae2014-11-20 16:58:51 -0800163}
164
165} // namespace chromeos