1// Copyright 2014 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 "chrome/browser/chromeos/file_system_provider/service.h"
6
7#include "base/files/file_path.h"
8#include "base/prefs/pref_service.h"
9#include "base/prefs/scoped_user_pref_update.h"
10#include "base/stl_util.h"
11#include "chrome/browser/chromeos/file_system_provider/mount_path_util.h"
12#include "chrome/browser/chromeos/file_system_provider/observer.h"
13#include "chrome/browser/chromeos/file_system_provider/provided_file_system.h"
14#include "chrome/browser/chromeos/file_system_provider/provided_file_system_info.h"
15#include "chrome/browser/chromeos/file_system_provider/provided_file_system_interface.h"
16#include "chrome/browser/chromeos/file_system_provider/service_factory.h"
17#include "chrome/common/pref_names.h"
18#include "components/pref_registry/pref_registry_syncable.h"
19#include "extensions/browser/extension_registry.h"
20#include "extensions/browser/extension_system.h"
21#include "storage/browser/fileapi/external_mount_points.h"
22
23namespace chromeos {
24namespace file_system_provider {
25namespace {
26
27// Maximum number of file systems to be mounted in the same time, per profile.
28const size_t kMaxFileSystems = 16;
29
30// Default factory for provided file systems. |profile| must not be NULL.
31ProvidedFileSystemInterface* CreateProvidedFileSystem(
32    Profile* profile,
33    const ProvidedFileSystemInfo& file_system_info) {
34  DCHECK(profile);
35  return new ProvidedFileSystem(profile, file_system_info);
36}
37
38}  // namespace
39
40const char kPrefKeyFileSystemId[] = "file-system-id";
41const char kPrefKeyDisplayName[] = "display-name";
42const char kPrefKeyWritable[] = "writable";
43
44void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
45  registry->RegisterDictionaryPref(
46      prefs::kFileSystemProviderMounted,
47      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
48}
49
50Service::Service(Profile* profile,
51                 extensions::ExtensionRegistry* extension_registry)
52    : profile_(profile),
53      extension_registry_(extension_registry),
54      file_system_factory_(base::Bind(CreateProvidedFileSystem)),
55      weak_ptr_factory_(this) {
56  extension_registry_->AddObserver(this);
57}
58
59Service::~Service() {
60  extension_registry_->RemoveObserver(this);
61
62  // Provided file systems should be already unmounted because of receiving
63  // OnExtensionUnload calls for each installed extension. However, for tests
64  // we may still have mounted extensions.
65  // TODO(mtomasz): Create a TestingService class and remove this code.
66  ProvidedFileSystemMap::iterator it = file_system_map_.begin();
67  while (it != file_system_map_.end()) {
68    const std::string file_system_id =
69        it->second->GetFileSystemInfo().file_system_id();
70    const std::string extension_id =
71        it->second->GetFileSystemInfo().extension_id();
72    ++it;
73    const bool unmount_result = UnmountFileSystem(
74        extension_id, file_system_id, UNMOUNT_REASON_SHUTDOWN);
75    DCHECK(unmount_result);
76  }
77
78  DCHECK_EQ(0u, file_system_map_.size());
79  STLDeleteValues(&file_system_map_);
80}
81
82// static
83Service* Service::Get(content::BrowserContext* context) {
84  return ServiceFactory::Get(context);
85}
86
87void Service::AddObserver(Observer* observer) {
88  DCHECK(observer);
89  observers_.AddObserver(observer);
90}
91
92void Service::RemoveObserver(Observer* observer) {
93  DCHECK(observer);
94  observers_.RemoveObserver(observer);
95}
96
97void Service::SetFileSystemFactoryForTesting(
98    const FileSystemFactoryCallback& factory_callback) {
99  DCHECK(!factory_callback.is_null());
100  file_system_factory_ = factory_callback;
101}
102
103bool Service::MountFileSystem(const std::string& extension_id,
104                              const std::string& file_system_id,
105                              const std::string& display_name,
106                              bool writable) {
107  DCHECK(thread_checker_.CalledOnValidThread());
108
109  // If already exists a file system provided by the same extension with this
110  // id, then abort.
111  if (GetProvidedFileSystem(extension_id, file_system_id)) {
112    FOR_EACH_OBSERVER(Observer,
113                      observers_,
114                      OnProvidedFileSystemMount(ProvidedFileSystemInfo(),
115                                                base::File::FILE_ERROR_EXISTS));
116    return false;
117  }
118
119  // Restrict number of file systems to prevent system abusing.
120  if (file_system_map_.size() + 1 > kMaxFileSystems) {
121    FOR_EACH_OBSERVER(
122        Observer,
123        observers_,
124        OnProvidedFileSystemMount(ProvidedFileSystemInfo(),
125                                  base::File::FILE_ERROR_TOO_MANY_OPENED));
126    return false;
127  }
128
129  storage::ExternalMountPoints* const mount_points =
130      storage::ExternalMountPoints::GetSystemInstance();
131  DCHECK(mount_points);
132
133  // The mount point path and name are unique per system, since they are system
134  // wide. This is necessary for copying between profiles.
135  const base::FilePath& mount_path =
136      util::GetMountPath(profile_, extension_id, file_system_id);
137  const std::string mount_point_name = mount_path.BaseName().AsUTF8Unsafe();
138
139  if (!mount_points->RegisterFileSystem(mount_point_name,
140                                        storage::kFileSystemTypeProvided,
141                                        storage::FileSystemMountOption(),
142                                        mount_path)) {
143    FOR_EACH_OBSERVER(
144        Observer,
145        observers_,
146        OnProvidedFileSystemMount(ProvidedFileSystemInfo(),
147                                  base::File::FILE_ERROR_INVALID_OPERATION));
148    return false;
149  }
150
151  // Store the file system descriptor. Use the mount point name as the file
152  // system provider file system id.
153  // Examples:
154  //   file_system_id = hello_world
155  //   mount_point_name =  b33f1337-hello_world-5aa5
156  //   writable = false
157  //   mount_path = /provided/b33f1337-hello_world-5aa5
158  ProvidedFileSystemInfo file_system_info(
159      extension_id, file_system_id, display_name, writable, mount_path);
160
161  ProvidedFileSystemInterface* file_system =
162      file_system_factory_.Run(profile_, file_system_info);
163  DCHECK(file_system);
164  file_system_map_[FileSystemKey(extension_id, file_system_id)] = file_system;
165  mount_point_name_to_key_map_[mount_point_name] =
166      FileSystemKey(extension_id, file_system_id);
167  RememberFileSystem(file_system_info);
168
169  FOR_EACH_OBSERVER(
170      Observer,
171      observers_,
172      OnProvidedFileSystemMount(file_system_info, base::File::FILE_OK));
173
174  return true;
175}
176
177bool Service::UnmountFileSystem(const std::string& extension_id,
178                                const std::string& file_system_id,
179                                UnmountReason reason) {
180  DCHECK(thread_checker_.CalledOnValidThread());
181
182  const ProvidedFileSystemMap::iterator file_system_it =
183      file_system_map_.find(FileSystemKey(extension_id, file_system_id));
184  if (file_system_it == file_system_map_.end()) {
185    const ProvidedFileSystemInfo empty_file_system_info;
186    FOR_EACH_OBSERVER(
187        Observer,
188        observers_,
189        OnProvidedFileSystemUnmount(empty_file_system_info,
190                                    base::File::FILE_ERROR_NOT_FOUND));
191    return false;
192  }
193
194  storage::ExternalMountPoints* const mount_points =
195      storage::ExternalMountPoints::GetSystemInstance();
196  DCHECK(mount_points);
197
198  const ProvidedFileSystemInfo& file_system_info =
199      file_system_it->second->GetFileSystemInfo();
200
201  const std::string mount_point_name =
202      file_system_info.mount_path().BaseName().value();
203  if (!mount_points->RevokeFileSystem(mount_point_name)) {
204    FOR_EACH_OBSERVER(
205        Observer,
206        observers_,
207        OnProvidedFileSystemUnmount(file_system_info,
208                                    base::File::FILE_ERROR_INVALID_OPERATION));
209    return false;
210  }
211
212  FOR_EACH_OBSERVER(
213      Observer,
214      observers_,
215      OnProvidedFileSystemUnmount(file_system_info, base::File::FILE_OK));
216
217  mount_point_name_to_key_map_.erase(mount_point_name);
218
219  if (reason == UNMOUNT_REASON_USER) {
220    ForgetFileSystem(file_system_info.extension_id(),
221                     file_system_info.file_system_id());
222  }
223
224  delete file_system_it->second;
225  file_system_map_.erase(file_system_it);
226
227  return true;
228}
229
230bool Service::RequestUnmount(const std::string& extension_id,
231                             const std::string& file_system_id) {
232  DCHECK(thread_checker_.CalledOnValidThread());
233
234  ProvidedFileSystemMap::iterator file_system_it =
235      file_system_map_.find(FileSystemKey(extension_id, file_system_id));
236  if (file_system_it == file_system_map_.end())
237    return false;
238
239  file_system_it->second->RequestUnmount(
240      base::Bind(&Service::OnRequestUnmountStatus,
241                 weak_ptr_factory_.GetWeakPtr(),
242                 file_system_it->second->GetFileSystemInfo()));
243  return true;
244}
245
246std::vector<ProvidedFileSystemInfo> Service::GetProvidedFileSystemInfoList() {
247  DCHECK(thread_checker_.CalledOnValidThread());
248
249  std::vector<ProvidedFileSystemInfo> result;
250  for (ProvidedFileSystemMap::const_iterator it = file_system_map_.begin();
251       it != file_system_map_.end();
252       ++it) {
253    result.push_back(it->second->GetFileSystemInfo());
254  }
255  return result;
256}
257
258ProvidedFileSystemInterface* Service::GetProvidedFileSystem(
259    const std::string& extension_id,
260    const std::string& file_system_id) {
261  DCHECK(thread_checker_.CalledOnValidThread());
262
263  const ProvidedFileSystemMap::const_iterator file_system_it =
264      file_system_map_.find(FileSystemKey(extension_id, file_system_id));
265  if (file_system_it == file_system_map_.end())
266    return NULL;
267
268  return file_system_it->second;
269}
270
271void Service::OnExtensionUnloaded(
272    content::BrowserContext* browser_context,
273    const extensions::Extension* extension,
274    extensions::UnloadedExtensionInfo::Reason reason) {
275  // Unmount all of the provided file systems associated with this extension.
276  ProvidedFileSystemMap::iterator it = file_system_map_.begin();
277  while (it != file_system_map_.end()) {
278    const ProvidedFileSystemInfo& file_system_info =
279        it->second->GetFileSystemInfo();
280    // Advance the iterator beforehand, otherwise it will become invalidated
281    // by the UnmountFileSystem() call.
282    ++it;
283    if (file_system_info.extension_id() == extension->id()) {
284      const bool unmount_result = UnmountFileSystem(
285          file_system_info.extension_id(),
286          file_system_info.file_system_id(),
287          reason == extensions::UnloadedExtensionInfo::REASON_PROFILE_SHUTDOWN
288              ? UNMOUNT_REASON_SHUTDOWN
289              : UNMOUNT_REASON_USER);
290      DCHECK(unmount_result);
291    }
292  }
293}
294
295void Service::OnExtensionLoaded(content::BrowserContext* browser_context,
296                                const extensions::Extension* extension) {
297  RestoreFileSystems(extension->id());
298}
299
300ProvidedFileSystemInterface* Service::GetProvidedFileSystem(
301    const std::string& mount_point_name) {
302  DCHECK(thread_checker_.CalledOnValidThread());
303
304  const MountPointNameToKeyMap::const_iterator mapping_it =
305      mount_point_name_to_key_map_.find(mount_point_name);
306  if (mapping_it == mount_point_name_to_key_map_.end())
307    return NULL;
308
309  const ProvidedFileSystemMap::const_iterator file_system_it =
310      file_system_map_.find(mapping_it->second);
311  if (file_system_it == file_system_map_.end())
312    return NULL;
313
314  return file_system_it->second;
315}
316
317void Service::OnRequestUnmountStatus(
318    const ProvidedFileSystemInfo& file_system_info,
319    base::File::Error error) {
320  // Notify observers about failure in unmounting, since mount() will not be
321  // called by the provided file system. In case of success mount() will be
322  // invoked, and observers notified, so there is no need to call them now.
323  if (error != base::File::FILE_OK) {
324    FOR_EACH_OBSERVER(Observer,
325                      observers_,
326                      OnProvidedFileSystemUnmount(file_system_info, error));
327  }
328}
329
330void Service::RememberFileSystem(
331    const ProvidedFileSystemInfo& file_system_info) {
332  base::DictionaryValue* file_system = new base::DictionaryValue();
333  file_system->SetStringWithoutPathExpansion(kPrefKeyFileSystemId,
334                                             file_system_info.file_system_id());
335  file_system->SetStringWithoutPathExpansion(kPrefKeyDisplayName,
336                                             file_system_info.display_name());
337  file_system->SetBooleanWithoutPathExpansion(kPrefKeyWritable,
338                                              file_system_info.writable());
339
340  PrefService* const pref_service = profile_->GetPrefs();
341  DCHECK(pref_service);
342
343  DictionaryPrefUpdate dict_update(pref_service,
344                                   prefs::kFileSystemProviderMounted);
345
346  base::DictionaryValue* file_systems_per_extension = NULL;
347  if (!dict_update->GetDictionaryWithoutPathExpansion(
348          file_system_info.extension_id(), &file_systems_per_extension)) {
349    file_systems_per_extension = new base::DictionaryValue();
350    dict_update->SetWithoutPathExpansion(file_system_info.extension_id(),
351                                         file_systems_per_extension);
352  }
353
354  file_systems_per_extension->SetWithoutPathExpansion(
355      file_system_info.file_system_id(), file_system);
356}
357
358void Service::ForgetFileSystem(const std::string& extension_id,
359                               const std::string& file_system_id) {
360  PrefService* const pref_service = profile_->GetPrefs();
361  DCHECK(pref_service);
362
363  DictionaryPrefUpdate dict_update(pref_service,
364                                   prefs::kFileSystemProviderMounted);
365
366  base::DictionaryValue* file_systems_per_extension = NULL;
367  if (!dict_update->GetDictionaryWithoutPathExpansion(
368          extension_id, &file_systems_per_extension))
369    return;  // Nothing to forget.
370
371  file_systems_per_extension->RemoveWithoutPathExpansion(file_system_id, NULL);
372  if (!file_systems_per_extension->size())
373    dict_update->Remove(extension_id, NULL);
374}
375
376void Service::RestoreFileSystems(const std::string& extension_id) {
377  PrefService* const pref_service = profile_->GetPrefs();
378  DCHECK(pref_service);
379
380  const base::DictionaryValue* const file_systems =
381      pref_service->GetDictionary(prefs::kFileSystemProviderMounted);
382  DCHECK(file_systems);
383
384  const base::DictionaryValue* file_systems_per_extension = NULL;
385  if (!file_systems->GetDictionaryWithoutPathExpansion(
386          extension_id, &file_systems_per_extension))
387    return;  // Nothing to restore.
388
389  // Use a copy of the dictionary, since the original one may be modified while
390  // iterating over it.
391  scoped_ptr<const base::DictionaryValue> file_systems_per_extension_copy(
392      file_systems_per_extension->DeepCopy());
393
394  for (base::DictionaryValue::Iterator it(*file_systems_per_extension_copy);
395       !it.IsAtEnd();
396       it.Advance()) {
397    const base::Value* file_system_value = NULL;
398    const base::DictionaryValue* file_system = NULL;
399    file_systems_per_extension_copy->GetWithoutPathExpansion(
400        it.key(), &file_system_value);
401    DCHECK(file_system_value);
402
403    std::string file_system_id;
404    std::string display_name;
405    bool writable;
406
407    if (!file_system_value->GetAsDictionary(&file_system) ||
408        !file_system->GetStringWithoutPathExpansion(kPrefKeyFileSystemId,
409                                                    &file_system_id) ||
410        !file_system->GetStringWithoutPathExpansion(kPrefKeyDisplayName,
411                                                    &display_name) ||
412        !file_system->GetBooleanWithoutPathExpansion(kPrefKeyWritable,
413                                                     &writable) ||
414        file_system_id.empty() || display_name.empty()) {
415      LOG(ERROR)
416          << "Malformed provided file system information in preferences.";
417      continue;
418    }
419
420    const bool result =
421        MountFileSystem(extension_id, file_system_id, display_name, writable);
422    if (!result) {
423      LOG(ERROR) << "Failed to restore a provided file system from "
424                 << "preferences: " << extension_id << ", " << file_system_id
425                 << ", " << display_name << ".";
426      // Since remounting of the file system failed, then remove it from
427      // preferences to avoid remounting it over and over again with a failure.
428      ForgetFileSystem(extension_id, file_system_id);
429    }
430  }
431}
432
433}  // namespace file_system_provider
434}  // namespace chromeos
435