1 // Copyright 2020 The Chromium 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 "components/services/storage/public/cpp/filesystem/filesystem_impl.h"
6
7 #include <set>
8 #include <vector>
9
10 #include "base/check.h"
11 #include "base/files/file.h"
12 #include "base/files/file_enumerator.h"
13 #include "base/files/file_util.h"
14 #include "base/files/important_file_writer.h"
15 #include "base/no_destructor.h"
16 #include "base/notreached.h"
17 #include "base/stl_util.h"
18 #include "base/synchronization/lock.h"
19 #include "build/build_config.h"
20 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
21
22 namespace storage {
23
24 namespace {
25
26 // Retains a mapping of lock file paths which have been locked by
27 // |FilesystemImpl::LockFile| and not yet released.
28 class LockTable {
29 public:
30 LockTable() = default;
31 LockTable(const LockTable&) = delete;
32 LockTable& operator=(const LockTable&) = delete;
33 ~LockTable() = default;
34
AddLock(const base::FilePath & path)35 bool AddLock(const base::FilePath& path) {
36 DCHECK(path.IsAbsolute());
37 base::AutoLock lock(lock_);
38 auto result = lock_paths_.insert(path.NormalizePathSeparators());
39 return result.second;
40 }
41
RemoveLock(const base::FilePath & path)42 void RemoveLock(const base::FilePath& path) {
43 const base::FilePath normalized_path = path.NormalizePathSeparators();
44 base::AutoLock lock(lock_);
45 DCHECK(base::Contains(lock_paths_, normalized_path));
46 lock_paths_.erase(normalized_path);
47 }
48
49 private:
50 base::Lock lock_;
51 std::set<base::FilePath> lock_paths_ GUARDED_BY(lock_);
52 };
53
54 // Get the global singleton instance of LockTable. This returned object is
55 // thread-safe.
GetLockTable()56 LockTable& GetLockTable() {
57 static base::NoDestructor<LockTable> table;
58 return *table;
59 }
60
61 class FileLockImpl : public mojom::FileLock {
62 public:
FileLockImpl(const base::FilePath & path,base::File file)63 FileLockImpl(const base::FilePath& path, base::File file)
64 : path_(path), file_(std::move(file)) {
65 DCHECK(file_.IsValid());
66 }
67
~FileLockImpl()68 ~FileLockImpl() override {
69 if (file_.IsValid())
70 GetLockTable().RemoveLock(path_);
71 }
72
73 // mojom::FileLock implementation:
Release(ReleaseCallback callback)74 void Release(ReleaseCallback callback) override {
75 if (!file_.IsValid()) {
76 std::move(callback).Run(base::File::FILE_ERROR_INVALID_OPERATION);
77 return;
78 }
79
80 #if defined(OS_FUCHSIA)
81 std::move(callback).Run(base::File::FILE_OK);
82 #else
83 std::move(callback).Run(file_.Unlock());
84 #endif
85 GetLockTable().RemoveLock(path_);
86 file_.Close();
87 }
88
89 private:
90 const base::FilePath path_;
91 base::File file_;
92 };
93
94 } // namespace
95
FilesystemImpl(const base::FilePath & root)96 FilesystemImpl::FilesystemImpl(const base::FilePath& root) : root_(root) {}
97
98 FilesystemImpl::~FilesystemImpl() = default;
99
Clone(mojo::PendingReceiver<mojom::Directory> receiver)100 void FilesystemImpl::Clone(mojo::PendingReceiver<mojom::Directory> receiver) {
101 mojo::MakeSelfOwnedReceiver(std::make_unique<FilesystemImpl>(root_),
102 std::move(receiver));
103 }
104
PathExists(const base::FilePath & path,PathExistsCallback callback)105 void FilesystemImpl::PathExists(const base::FilePath& path,
106 PathExistsCallback callback) {
107 std::move(callback).Run(base::PathExists(MakeAbsolute(path)));
108 }
109
GetEntries(const base::FilePath & path,mojom::GetEntriesMode mode,GetEntriesCallback callback)110 void FilesystemImpl::GetEntries(const base::FilePath& path,
111 mojom::GetEntriesMode mode,
112 GetEntriesCallback callback) {
113 const base::FilePath full_path = MakeAbsolute(path);
114 FileErrorOr<std::vector<base::FilePath>> result =
115 GetDirectoryEntries(full_path, mode);
116 if (result.is_error()) {
117 std::move(callback).Run(result.error(), std::vector<base::FilePath>());
118 return;
119 }
120
121 // Fix up the absolute paths to be relative to |path|.
122 std::vector<base::FilePath> entries;
123 std::vector<base::FilePath::StringType> root_components;
124 full_path.GetComponents(&root_components);
125 const size_t num_components_to_strip = root_components.size();
126 for (const auto& entry : result.value()) {
127 std::vector<base::FilePath::StringType> components;
128 entry.GetComponents(&components);
129 base::FilePath relative_path;
130 for (size_t i = num_components_to_strip; i < components.size(); ++i)
131 relative_path = relative_path.Append(components[i]);
132 entries.push_back(std::move(relative_path));
133 }
134 std::move(callback).Run(base::File::FILE_OK, entries);
135 }
136
OpenFile(const base::FilePath & path,mojom::FileOpenMode mode,mojom::FileReadAccess read_access,mojom::FileWriteAccess write_access,OpenFileCallback callback)137 void FilesystemImpl::OpenFile(const base::FilePath& path,
138 mojom::FileOpenMode mode,
139 mojom::FileReadAccess read_access,
140 mojom::FileWriteAccess write_access,
141 OpenFileCallback callback) {
142 uint32_t flags = 0;
143 switch (mode) {
144 case mojom::FileOpenMode::kOpenIfExists:
145 flags |= base::File::FLAG_OPEN;
146 break;
147 case mojom::FileOpenMode::kCreateAndOpenOnlyIfNotExists:
148 flags |= base::File::FLAG_CREATE;
149 break;
150 case mojom::FileOpenMode::kAlwaysOpen:
151 flags |= base::File::FLAG_OPEN_ALWAYS;
152 break;
153 case mojom::FileOpenMode::kAlwaysCreate:
154 flags |= base::File::FLAG_CREATE_ALWAYS;
155 break;
156 case mojom::FileOpenMode::kOpenIfExistsAndTruncate:
157 flags |= base::File::FLAG_OPEN_TRUNCATED;
158 break;
159 default:
160 NOTREACHED();
161 return;
162 }
163
164 switch (read_access) {
165 case mojom::FileReadAccess::kReadNotAllowed:
166 break;
167 case mojom::FileReadAccess::kReadAllowed:
168 flags |= base::File::FLAG_READ;
169 break;
170 default:
171 NOTREACHED();
172 break;
173 }
174
175 switch (write_access) {
176 case mojom::FileWriteAccess::kWriteNotAllowed:
177 break;
178 case mojom::FileWriteAccess::kWriteAllowed:
179 flags |= base::File::FLAG_WRITE;
180 break;
181 case mojom::FileWriteAccess::kAppendOnly:
182 flags |= base::File::FLAG_APPEND;
183 break;
184 default:
185 NOTREACHED();
186 break;
187 }
188
189 const base::FilePath full_path = MakeAbsolute(path);
190 base::File file(full_path, flags);
191 base::File::Error error = base::File::FILE_OK;
192 if (!file.IsValid())
193 error = file.error_details();
194 std::move(callback).Run(error, std::move(file));
195 }
196
WriteFileAtomically(const base::FilePath & path,const std::string & contents,WriteFileAtomicallyCallback callback)197 void FilesystemImpl::WriteFileAtomically(const base::FilePath& path,
198 const std::string& contents,
199 WriteFileAtomicallyCallback callback) {
200 std::move(callback).Run(base::ImportantFileWriter::WriteFileAtomically(
201 MakeAbsolute(path), std::move(contents)));
202 }
203
CreateDirectory(const base::FilePath & path,CreateDirectoryCallback callback)204 void FilesystemImpl::CreateDirectory(const base::FilePath& path,
205 CreateDirectoryCallback callback) {
206 base::File::Error error = base::File::FILE_OK;
207 base::CreateDirectoryAndGetError(MakeAbsolute(path), &error);
208 std::move(callback).Run(error);
209 }
210
DeleteFile(const base::FilePath & path,DeleteFileCallback callback)211 void FilesystemImpl::DeleteFile(const base::FilePath& path,
212 DeleteFileCallback callback) {
213 std::move(callback).Run(base::DeleteFile(MakeAbsolute(path)));
214 }
215
DeletePathRecursively(const base::FilePath & path,DeletePathRecursivelyCallback callback)216 void FilesystemImpl::DeletePathRecursively(
217 const base::FilePath& path,
218 DeletePathRecursivelyCallback callback) {
219 std::move(callback).Run(base::DeletePathRecursively(MakeAbsolute(path)));
220 }
221
GetFileInfo(const base::FilePath & path,GetFileInfoCallback callback)222 void FilesystemImpl::GetFileInfo(const base::FilePath& path,
223 GetFileInfoCallback callback) {
224 base::File::Info info;
225 if (base::GetFileInfo(MakeAbsolute(path), &info))
226 std::move(callback).Run(std::move(info));
227 else
228 std::move(callback).Run(base::nullopt);
229 }
230
GetPathAccess(const base::FilePath & path,GetPathAccessCallback callback)231 void FilesystemImpl::GetPathAccess(const base::FilePath& path,
232 GetPathAccessCallback callback) {
233 std::move(callback).Run(GetPathAccessLocal(MakeAbsolute(path)));
234 }
235
GetMaximumPathComponentLength(const base::FilePath & path,GetMaximumPathComponentLengthCallback callback)236 void FilesystemImpl::GetMaximumPathComponentLength(
237 const base::FilePath& path,
238 GetMaximumPathComponentLengthCallback callback) {
239 int len = base::GetMaximumPathComponentLength(MakeAbsolute(path));
240 bool success = len != -1;
241 return std::move(callback).Run(success, len);
242 }
243
RenameFile(const base::FilePath & old_path,const base::FilePath & new_path,RenameFileCallback callback)244 void FilesystemImpl::RenameFile(const base::FilePath& old_path,
245 const base::FilePath& new_path,
246 RenameFileCallback callback) {
247 base::File::Error error = base::File::FILE_OK;
248 base::ReplaceFile(MakeAbsolute(old_path), MakeAbsolute(new_path), &error);
249 std::move(callback).Run(error);
250 }
251
LockFile(const base::FilePath & path,LockFileCallback callback)252 void FilesystemImpl::LockFile(const base::FilePath& path,
253 LockFileCallback callback) {
254 FileErrorOr<base::File> result = LockFileLocal(MakeAbsolute(path));
255 if (result.is_error()) {
256 std::move(callback).Run(result.error(), mojo::NullRemote());
257 return;
258 }
259
260 mojo::PendingRemote<mojom::FileLock> lock;
261 mojo::MakeSelfOwnedReceiver(
262 std::make_unique<FileLockImpl>(MakeAbsolute(path),
263 std::move(result.value())),
264 lock.InitWithNewPipeAndPassReceiver());
265 std::move(callback).Run(base::File::FILE_OK, std::move(lock));
266 }
267
SetOpenedFileLength(base::File file,uint64_t length,SetOpenedFileLengthCallback callback)268 void FilesystemImpl::SetOpenedFileLength(base::File file,
269 uint64_t length,
270 SetOpenedFileLengthCallback callback) {
271 bool success = file.SetLength(length);
272 std::move(callback).Run(success, std::move(file));
273 }
274
275 // static
LockFileLocal(const base::FilePath & path)276 FileErrorOr<base::File> FilesystemImpl::LockFileLocal(
277 const base::FilePath& path) {
278 DCHECK(path.IsAbsolute());
279 base::File file(path, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ |
280 base::File::FLAG_WRITE);
281 if (!file.IsValid())
282 return file.error_details();
283
284 if (!GetLockTable().AddLock(path))
285 return base::File::FILE_ERROR_IN_USE;
286
287 #if !defined(OS_FUCHSIA)
288 base::File::Error error = file.Lock(base::File::LockMode::kExclusive);
289 if (error != base::File::FILE_OK)
290 return error;
291 #endif
292
293 return file;
294 }
295
296 // static
UnlockFileLocal(const base::FilePath & path)297 void FilesystemImpl::UnlockFileLocal(const base::FilePath& path) {
298 GetLockTable().RemoveLock(path);
299 }
300
301 // static
GetPathAccessLocal(const base::FilePath & path)302 mojom::PathAccessInfoPtr FilesystemImpl::GetPathAccessLocal(
303 const base::FilePath& path) {
304 mojom::PathAccessInfoPtr info;
305 #if defined(OS_WIN)
306 uint32_t attributes = ::GetFileAttributes(path.value().c_str());
307 if (attributes != INVALID_FILE_ATTRIBUTES) {
308 info = mojom::PathAccessInfo::New();
309 info->can_read = true;
310 if ((attributes & FILE_ATTRIBUTE_READONLY) == 0)
311 info->can_write = true;
312 }
313 #elif defined(OS_POSIX) || defined(OS_FUCHSIA)
314 const char* const c_path = path.value().c_str();
315 if (!access(c_path, F_OK)) {
316 info = mojom::PathAccessInfo::New();
317 info->can_read = !access(c_path, R_OK);
318 info->can_write = !access(c_path, W_OK);
319 }
320 #endif
321 return info;
322 }
323
324 // static
GetDirectoryEntries(const base::FilePath & path,mojom::GetEntriesMode mode)325 FileErrorOr<std::vector<base::FilePath>> FilesystemImpl::GetDirectoryEntries(
326 const base::FilePath& path,
327 mojom::GetEntriesMode mode) {
328 DCHECK(path.IsAbsolute());
329 int file_types = base::FileEnumerator::FILES;
330 if (mode == mojom::GetEntriesMode::kFilesAndDirectories)
331 file_types |= base::FileEnumerator::DIRECTORIES;
332 base::FileEnumerator enumerator(
333 path, /*recursive=*/false, file_types,
334 /*pattern=*/base::FilePath::StringType(),
335 base::FileEnumerator::FolderSearchPolicy::ALL,
336 base::FileEnumerator::ErrorPolicy::STOP_ENUMERATION);
337 std::vector<base::FilePath> entries;
338 for (base::FilePath path = enumerator.Next(); !path.empty();
339 path = enumerator.Next()) {
340 entries.push_back(path);
341 }
342 if (enumerator.GetError() != base::File::FILE_OK)
343 return enumerator.GetError();
344 return entries;
345 }
346
MakeAbsolute(const base::FilePath & path) const347 base::FilePath FilesystemImpl::MakeAbsolute(const base::FilePath& path) const {
348 // The DCHECK is a reasonable assertion: this object is only called into via
349 // Mojo, and type-map traits for |storage.mojom.StrictRelativePath| ensure
350 // that messages can only reach this object if they carry strictly relative
351 // paths.
352 DCHECK(!path.IsAbsolute());
353 return root_.Append(path);
354 }
355
356 } // namespace storage
357