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.
5 #include "chrome/browser/chromeos/file_system_provider/service.h"
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 "base/values.h"
12 #include "chrome/browser/chromeos/file_system_provider/mount_path_util.h"
13 #include "chrome/browser/chromeos/file_system_provider/observer.h"
14 #include "chrome/browser/chromeos/file_system_provider/provided_file_system.h"
15 #include "chrome/browser/chromeos/file_system_provider/provided_file_system_info.h"
16 #include "chrome/browser/chromeos/file_system_provider/registry.h"
17 #include "chrome/browser/chromeos/file_system_provider/registry_interface.h"
18 #include "chrome/browser/chromeos/file_system_provider/service_factory.h"
19 #include "chrome/browser/chromeos/file_system_provider/throttled_file_system.h"
20 #include "extensions/browser/event_router.h"
21 #include "extensions/browser/extension_registry.h"
22 #include "extensions/browser/extension_system.h"
23 #include "extensions/common/permissions/api_permission.h"
24 #include "extensions/common/permissions/permissions_data.h"
25 #include "storage/browser/fileapi/external_mount_points.h"
26 #include "storage/common/fileapi/file_system_mount_option.h"
29 namespace file_system_provider
{
32 // Maximum number of file systems to be mounted in the same time, per profile.
33 const size_t kMaxFileSystems
= 16;
35 // Default factory for provided file systems. |profile| must not be NULL.
36 ProvidedFileSystemInterface
* CreateProvidedFileSystem(
38 const ProvidedFileSystemInfo
& file_system_info
) {
40 return new ThrottledFileSystem(
41 make_scoped_ptr(new ProvidedFileSystem(profile
, file_system_info
)));
46 ProvidingExtensionInfo::ProvidingExtensionInfo() {
49 ProvidingExtensionInfo::~ProvidingExtensionInfo() {
52 Service::Service(Profile
* profile
,
53 extensions::ExtensionRegistry
* extension_registry
)
55 extension_registry_(extension_registry
),
56 file_system_factory_(base::Bind(&CreateProvidedFileSystem
)),
57 registry_(new Registry(profile
)),
58 weak_ptr_factory_(this) {
59 extension_registry_
->AddObserver(this);
63 extension_registry_
->RemoveObserver(this);
65 // Provided file systems should be already unmounted because of receiving
66 // OnExtensionUnload calls for each installed extension. However, for tests
67 // we may still have mounted extensions.
68 // TODO(mtomasz): Create a TestingService class and remove this code.
69 ProvidedFileSystemMap::iterator it
= file_system_map_
.begin();
70 while (it
!= file_system_map_
.end()) {
71 const std::string file_system_id
=
72 it
->second
->GetFileSystemInfo().file_system_id();
73 const std::string extension_id
=
74 it
->second
->GetFileSystemInfo().extension_id();
76 const base::File::Error unmount_result
= UnmountFileSystem(
77 extension_id
, file_system_id
, UNMOUNT_REASON_SHUTDOWN
);
78 DCHECK_EQ(base::File::FILE_OK
, unmount_result
);
81 DCHECK_EQ(0u, file_system_map_
.size());
82 STLDeleteValues(&file_system_map_
);
86 Service
* Service::Get(content::BrowserContext
* context
) {
87 return ServiceFactory::Get(context
);
90 void Service::AddObserver(Observer
* observer
) {
92 observers_
.AddObserver(observer
);
95 void Service::RemoveObserver(Observer
* observer
) {
97 observers_
.RemoveObserver(observer
);
100 void Service::SetFileSystemFactoryForTesting(
101 const FileSystemFactoryCallback
& factory_callback
) {
102 DCHECK(!factory_callback
.is_null());
103 file_system_factory_
= factory_callback
;
106 void Service::SetRegistryForTesting(scoped_ptr
<RegistryInterface
> registry
) {
108 registry_
.reset(registry
.release());
111 base::File::Error
Service::MountFileSystem(const std::string
& extension_id
,
112 const MountOptions
& options
) {
113 return MountFileSystemInternal(extension_id
, options
, MOUNT_CONTEXT_USER
);
116 base::File::Error
Service::MountFileSystemInternal(
117 const std::string
& extension_id
,
118 const MountOptions
& options
,
119 MountContext context
) {
120 DCHECK(thread_checker_
.CalledOnValidThread());
122 // If already exists a file system provided by the same extension with this
124 if (GetProvidedFileSystem(extension_id
, options
.file_system_id
)) {
126 Observer
, observers_
,
127 OnProvidedFileSystemMount(ProvidedFileSystemInfo(), context
,
128 base::File::FILE_ERROR_EXISTS
));
129 return base::File::FILE_ERROR_EXISTS
;
132 // Restrict number of file systems to prevent system abusing.
133 if (file_system_map_
.size() + 1 > kMaxFileSystems
) {
135 Observer
, observers_
,
136 OnProvidedFileSystemMount(ProvidedFileSystemInfo(), context
,
137 base::File::FILE_ERROR_TOO_MANY_OPENED
));
138 return base::File::FILE_ERROR_TOO_MANY_OPENED
;
141 storage::ExternalMountPoints
* const mount_points
=
142 storage::ExternalMountPoints::GetSystemInstance();
143 DCHECK(mount_points
);
145 // The mount point path and name are unique per system, since they are system
146 // wide. This is necessary for copying between profiles.
147 const base::FilePath
& mount_path
=
148 util::GetMountPath(profile_
, extension_id
, options
.file_system_id
);
149 const std::string mount_point_name
= mount_path
.BaseName().AsUTF8Unsafe();
151 if (!mount_points
->RegisterFileSystem(
152 mount_point_name
, storage::kFileSystemTypeProvided
,
153 storage::FileSystemMountOption(
154 storage::FlushPolicy::FLUSH_ON_COMPLETION
),
157 Observer
, observers_
,
158 OnProvidedFileSystemMount(ProvidedFileSystemInfo(), context
,
159 base::File::FILE_ERROR_INVALID_OPERATION
));
160 return base::File::FILE_ERROR_INVALID_OPERATION
;
163 ProvidingExtensionInfo provider_info
;
164 // TODO(mtomasz): Set up a testing extension in unit tests.
165 GetProvidingExtensionInfo(extension_id
, &provider_info
);
166 // Store the file system descriptor. Use the mount point name as the file
167 // system provider file system id.
169 // file_system_id = hello_world
170 // mount_point_name = b33f1337-hello_world-5aa5
172 // supports_notify_tag = false
173 // mount_path = /provided/b33f1337-hello_world-5aa5
174 // configurable = true
176 // source = SOURCE_FILE
177 ProvidedFileSystemInfo
file_system_info(
178 extension_id
, options
, mount_path
,
179 provider_info
.capabilities
.configurable(),
180 provider_info
.capabilities
.watchable(),
181 provider_info
.capabilities
.source());
183 ProvidedFileSystemInterface
* file_system
=
184 file_system_factory_
.Run(profile_
, file_system_info
);
186 file_system_map_
[FileSystemKey(extension_id
, options
.file_system_id
)] =
188 mount_point_name_to_key_map_
[mount_point_name
] =
189 FileSystemKey(extension_id
, options
.file_system_id
);
190 registry_
->RememberFileSystem(file_system_info
, *file_system
->GetWatchers());
192 FOR_EACH_OBSERVER(Observer
, observers_
,
193 OnProvidedFileSystemMount(file_system_info
, context
,
194 base::File::FILE_OK
));
196 return base::File::FILE_OK
;
199 base::File::Error
Service::UnmountFileSystem(const std::string
& extension_id
,
200 const std::string
& file_system_id
,
201 UnmountReason reason
) {
202 DCHECK(thread_checker_
.CalledOnValidThread());
204 const ProvidedFileSystemMap::iterator file_system_it
=
205 file_system_map_
.find(FileSystemKey(extension_id
, file_system_id
));
206 if (file_system_it
== file_system_map_
.end()) {
207 const ProvidedFileSystemInfo empty_file_system_info
;
211 OnProvidedFileSystemUnmount(empty_file_system_info
,
212 base::File::FILE_ERROR_NOT_FOUND
));
213 return base::File::FILE_ERROR_NOT_FOUND
;
216 storage::ExternalMountPoints
* const mount_points
=
217 storage::ExternalMountPoints::GetSystemInstance();
218 DCHECK(mount_points
);
220 const ProvidedFileSystemInfo
& file_system_info
=
221 file_system_it
->second
->GetFileSystemInfo();
223 const std::string mount_point_name
=
224 file_system_info
.mount_path().BaseName().value();
225 if (!mount_points
->RevokeFileSystem(mount_point_name
)) {
229 OnProvidedFileSystemUnmount(file_system_info
,
230 base::File::FILE_ERROR_INVALID_OPERATION
));
231 return base::File::FILE_ERROR_INVALID_OPERATION
;
237 OnProvidedFileSystemUnmount(file_system_info
, base::File::FILE_OK
));
239 mount_point_name_to_key_map_
.erase(mount_point_name
);
241 if (reason
== UNMOUNT_REASON_USER
) {
242 registry_
->ForgetFileSystem(file_system_info
.extension_id(),
243 file_system_info
.file_system_id());
246 delete file_system_it
->second
;
247 file_system_map_
.erase(file_system_it
);
249 return base::File::FILE_OK
;
252 bool Service::RequestUnmount(const std::string
& extension_id
,
253 const std::string
& file_system_id
) {
254 DCHECK(thread_checker_
.CalledOnValidThread());
256 ProvidedFileSystemMap::iterator file_system_it
=
257 file_system_map_
.find(FileSystemKey(extension_id
, file_system_id
));
258 if (file_system_it
== file_system_map_
.end())
261 file_system_it
->second
->RequestUnmount(
262 base::Bind(&Service::OnRequestUnmountStatus
,
263 weak_ptr_factory_
.GetWeakPtr(),
264 file_system_it
->second
->GetFileSystemInfo()));
268 bool Service::RequestMount(const std::string
& extension_id
) {
269 DCHECK(thread_checker_
.CalledOnValidThread());
271 extensions::EventRouter
* const event_router
=
272 extensions::EventRouter::Get(profile_
);
273 DCHECK(event_router
);
275 if (!event_router
->ExtensionHasEventListener(
276 extension_id
, extensions::api::file_system_provider::
277 OnMountRequested::kEventName
)) {
281 event_router
->DispatchEventToExtension(
283 make_scoped_ptr(new extensions::Event(
284 extensions::events::FILE_SYSTEM_PROVIDER_ON_MOUNT_REQUESTED
,
285 extensions::api::file_system_provider::OnMountRequested::kEventName
,
286 scoped_ptr
<base::ListValue
>(new base::ListValue()))));
291 std::vector
<ProvidedFileSystemInfo
> Service::GetProvidedFileSystemInfoList() {
292 DCHECK(thread_checker_
.CalledOnValidThread());
294 std::vector
<ProvidedFileSystemInfo
> result
;
295 for (ProvidedFileSystemMap::const_iterator it
= file_system_map_
.begin();
296 it
!= file_system_map_
.end();
298 result
.push_back(it
->second
->GetFileSystemInfo());
303 ProvidedFileSystemInterface
* Service::GetProvidedFileSystem(
304 const std::string
& extension_id
,
305 const std::string
& file_system_id
) {
306 DCHECK(thread_checker_
.CalledOnValidThread());
308 const ProvidedFileSystemMap::const_iterator file_system_it
=
309 file_system_map_
.find(FileSystemKey(extension_id
, file_system_id
));
310 if (file_system_it
== file_system_map_
.end())
313 return file_system_it
->second
;
316 std::vector
<ProvidingExtensionInfo
> Service::GetProvidingExtensionInfoList()
318 extensions::ExtensionRegistry
* const registry
=
319 extensions::ExtensionRegistry::Get(profile_
);
322 std::vector
<ProvidingExtensionInfo
> result
;
323 for (const auto& extension
: registry
->enabled_extensions()) {
324 ProvidingExtensionInfo info
;
325 if (GetProvidingExtensionInfo(extension
->id(), &info
))
326 result
.push_back(info
);
332 bool Service::GetProvidingExtensionInfo(const std::string
& extension_id
,
333 ProvidingExtensionInfo
* result
) const {
335 extensions::ExtensionRegistry
* const registry
=
336 extensions::ExtensionRegistry::Get(profile_
);
339 const extensions::Extension
* const extension
= registry
->GetExtensionById(
340 extension_id
, extensions::ExtensionRegistry::ENABLED
);
342 !extension
->permissions_data()->HasAPIPermission(
343 extensions::APIPermission::kFileSystemProvider
)) {
347 result
->extension_id
= extension
->id();
348 result
->name
= extension
->name();
349 const extensions::FileSystemProviderCapabilities
* const capabilities
=
350 extensions::FileSystemProviderCapabilities::Get(extension
);
351 DCHECK(capabilities
);
352 result
->capabilities
= *capabilities
;
357 void Service::OnExtensionUnloaded(
358 content::BrowserContext
* browser_context
,
359 const extensions::Extension
* extension
,
360 extensions::UnloadedExtensionInfo::Reason reason
) {
361 // Unmount all of the provided file systems associated with this extension.
362 ProvidedFileSystemMap::iterator it
= file_system_map_
.begin();
363 while (it
!= file_system_map_
.end()) {
364 const ProvidedFileSystemInfo
& file_system_info
=
365 it
->second
->GetFileSystemInfo();
366 // Advance the iterator beforehand, otherwise it will become invalidated
367 // by the UnmountFileSystem() call.
369 if (file_system_info
.extension_id() == extension
->id()) {
370 const base::File::Error unmount_result
= UnmountFileSystem(
371 file_system_info
.extension_id(), file_system_info
.file_system_id(),
372 reason
== extensions::UnloadedExtensionInfo::REASON_PROFILE_SHUTDOWN
373 ? UNMOUNT_REASON_SHUTDOWN
374 : UNMOUNT_REASON_USER
);
375 DCHECK_EQ(base::File::FILE_OK
, unmount_result
);
380 void Service::OnExtensionLoaded(content::BrowserContext
* browser_context
,
381 const extensions::Extension
* extension
) {
382 scoped_ptr
<RegistryInterface::RestoredFileSystems
> restored_file_systems
=
383 registry_
->RestoreFileSystems(extension
->id());
385 for (const auto& restored_file_system
: *restored_file_systems
) {
386 const base::File::Error result
= MountFileSystemInternal(
387 restored_file_system
.extension_id
, restored_file_system
.options
,
388 MOUNT_CONTEXT_RESTORE
);
389 if (result
!= base::File::FILE_OK
) {
390 LOG(ERROR
) << "Failed to restore a provided file system from "
391 << "registry: " << restored_file_system
.extension_id
<< ", "
392 << restored_file_system
.options
.file_system_id
<< ", "
393 << restored_file_system
.options
.display_name
<< ".";
394 // Since remounting of the file system failed, then remove it from
395 // preferences to avoid remounting it over and over again with a failure.
396 registry_
->ForgetFileSystem(restored_file_system
.extension_id
,
397 restored_file_system
.options
.file_system_id
);
401 ProvidedFileSystemInterface
* const file_system
=
402 GetProvidedFileSystem(restored_file_system
.extension_id
,
403 restored_file_system
.options
.file_system_id
);
405 file_system
->GetWatchers()->insert(restored_file_system
.watchers
.begin(),
406 restored_file_system
.watchers
.end());
410 ProvidedFileSystemInterface
* Service::GetProvidedFileSystem(
411 const std::string
& mount_point_name
) {
412 DCHECK(thread_checker_
.CalledOnValidThread());
414 const MountPointNameToKeyMap::const_iterator mapping_it
=
415 mount_point_name_to_key_map_
.find(mount_point_name
);
416 if (mapping_it
== mount_point_name_to_key_map_
.end())
419 const ProvidedFileSystemMap::const_iterator file_system_it
=
420 file_system_map_
.find(mapping_it
->second
);
421 if (file_system_it
== file_system_map_
.end())
424 return file_system_it
->second
;
427 void Service::OnRequestUnmountStatus(
428 const ProvidedFileSystemInfo
& file_system_info
,
429 base::File::Error error
) {
430 // Notify observers about failure in unmounting, since mount() will not be
431 // called by the provided file system. In case of success mount() will be
432 // invoked, and observers notified, so there is no need to call them now.
433 if (error
!= base::File::FILE_OK
) {
434 FOR_EACH_OBSERVER(Observer
,
436 OnProvidedFileSystemUnmount(file_system_info
, error
));
440 void Service::OnWatcherChanged(const ProvidedFileSystemInfo
& file_system_info
,
441 const Watcher
& watcher
,
442 storage::WatcherManager::ChangeType change_type
,
443 const Changes
& changes
,
444 const base::Closure
& callback
) {
448 void Service::OnWatcherTagUpdated(
449 const ProvidedFileSystemInfo
& file_system_info
,
450 const Watcher
& watcher
) {
451 PrefService
* const pref_service
= profile_
->GetPrefs();
452 DCHECK(pref_service
);
454 registry_
->UpdateWatcherTag(file_system_info
, watcher
);
457 void Service::OnWatcherListChanged(
458 const ProvidedFileSystemInfo
& file_system_info
,
459 const Watchers
& watchers
) {
460 registry_
->RememberFileSystem(file_system_info
, watchers
);
463 } // namespace file_system_provider
464 } // namespace chromeos