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