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