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