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 "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/registry.h"
16 #include "chrome/browser/chromeos/file_system_provider/registry_interface.h"
17 #include "chrome/browser/chromeos/file_system_provider/service_factory.h"
18 #include "chrome/browser/chromeos/file_system_provider/throttled_file_system.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 #include "storage/common/fileapi/file_system_mount_option.h"
25 namespace file_system_provider
{
28 // Maximum number of file systems to be mounted in the same time, per profile.
29 const size_t kMaxFileSystems
= 16;
31 // Default factory for provided file systems. |profile| must not be NULL.
32 ProvidedFileSystemInterface
* CreateProvidedFileSystem(
34 const ProvidedFileSystemInfo
& file_system_info
) {
36 return new ThrottledFileSystem(
37 make_scoped_ptr(new ProvidedFileSystem(profile
, file_system_info
)));
42 Service::Service(Profile
* profile
,
43 extensions::ExtensionRegistry
* extension_registry
)
45 extension_registry_(extension_registry
),
46 file_system_factory_(base::Bind(&CreateProvidedFileSystem
)),
47 registry_(new Registry(profile
)),
48 weak_ptr_factory_(this) {
49 extension_registry_
->AddObserver(this);
53 extension_registry_
->RemoveObserver(this);
55 // Provided file systems should be already unmounted because of receiving
56 // OnExtensionUnload calls for each installed extension. However, for tests
57 // we may still have mounted extensions.
58 // TODO(mtomasz): Create a TestingService class and remove this code.
59 ProvidedFileSystemMap::iterator it
= file_system_map_
.begin();
60 while (it
!= file_system_map_
.end()) {
61 const std::string file_system_id
=
62 it
->second
->GetFileSystemInfo().file_system_id();
63 const std::string extension_id
=
64 it
->second
->GetFileSystemInfo().extension_id();
66 const base::File::Error unmount_result
= UnmountFileSystem(
67 extension_id
, file_system_id
, UNMOUNT_REASON_SHUTDOWN
);
68 DCHECK_EQ(base::File::FILE_OK
, unmount_result
);
71 DCHECK_EQ(0u, file_system_map_
.size());
72 STLDeleteValues(&file_system_map_
);
76 Service
* Service::Get(content::BrowserContext
* context
) {
77 return ServiceFactory::Get(context
);
80 void Service::AddObserver(Observer
* observer
) {
82 observers_
.AddObserver(observer
);
85 void Service::RemoveObserver(Observer
* observer
) {
87 observers_
.RemoveObserver(observer
);
90 void Service::SetFileSystemFactoryForTesting(
91 const FileSystemFactoryCallback
& factory_callback
) {
92 DCHECK(!factory_callback
.is_null());
93 file_system_factory_
= factory_callback
;
96 void Service::SetRegistryForTesting(scoped_ptr
<RegistryInterface
> registry
) {
98 registry_
.reset(registry
.release());
101 base::File::Error
Service::MountFileSystem(const std::string
& extension_id
,
102 const MountOptions
& options
) {
103 return MountFileSystemInternal(extension_id
, options
, MOUNT_CONTEXT_USER
);
106 base::File::Error
Service::MountFileSystemInternal(
107 const std::string
& extension_id
,
108 const MountOptions
& options
,
109 MountContext context
) {
110 DCHECK(thread_checker_
.CalledOnValidThread());
112 // If already exists a file system provided by the same extension with this
114 if (GetProvidedFileSystem(extension_id
, options
.file_system_id
)) {
116 Observer
, observers_
,
117 OnProvidedFileSystemMount(ProvidedFileSystemInfo(), context
,
118 base::File::FILE_ERROR_EXISTS
));
119 return base::File::FILE_ERROR_EXISTS
;
122 // Restrict number of file systems to prevent system abusing.
123 if (file_system_map_
.size() + 1 > kMaxFileSystems
) {
125 Observer
, observers_
,
126 OnProvidedFileSystemMount(ProvidedFileSystemInfo(), context
,
127 base::File::FILE_ERROR_TOO_MANY_OPENED
));
128 return base::File::FILE_ERROR_TOO_MANY_OPENED
;
131 storage::ExternalMountPoints
* const mount_points
=
132 storage::ExternalMountPoints::GetSystemInstance();
133 DCHECK(mount_points
);
135 // The mount point path and name are unique per system, since they are system
136 // wide. This is necessary for copying between profiles.
137 const base::FilePath
& mount_path
=
138 util::GetMountPath(profile_
, extension_id
, options
.file_system_id
);
139 const std::string mount_point_name
= mount_path
.BaseName().AsUTF8Unsafe();
141 if (!mount_points
->RegisterFileSystem(
142 mount_point_name
, storage::kFileSystemTypeProvided
,
143 storage::FileSystemMountOption(
144 storage::FlushPolicy::FLUSH_ON_COMPLETION
),
147 Observer
, observers_
,
148 OnProvidedFileSystemMount(ProvidedFileSystemInfo(), context
,
149 base::File::FILE_ERROR_INVALID_OPERATION
));
150 return base::File::FILE_ERROR_INVALID_OPERATION
;
153 // Store the file system descriptor. Use the mount point name as the file
154 // system provider file system id.
156 // file_system_id = hello_world
157 // mount_point_name = b33f1337-hello_world-5aa5
159 // supports_notify_tag = false
160 // mount_path = /provided/b33f1337-hello_world-5aa5
161 ProvidedFileSystemInfo
file_system_info(extension_id
, options
, mount_path
);
163 ProvidedFileSystemInterface
* file_system
=
164 file_system_factory_
.Run(profile_
, file_system_info
);
166 file_system_map_
[FileSystemKey(extension_id
, options
.file_system_id
)] =
168 mount_point_name_to_key_map_
[mount_point_name
] =
169 FileSystemKey(extension_id
, options
.file_system_id
);
170 registry_
->RememberFileSystem(file_system_info
, *file_system
->GetWatchers());
172 FOR_EACH_OBSERVER(Observer
, observers_
,
173 OnProvidedFileSystemMount(file_system_info
, context
,
174 base::File::FILE_OK
));
176 return base::File::FILE_OK
;
179 base::File::Error
Service::UnmountFileSystem(const std::string
& extension_id
,
180 const std::string
& file_system_id
,
181 UnmountReason reason
) {
182 DCHECK(thread_checker_
.CalledOnValidThread());
184 const ProvidedFileSystemMap::iterator file_system_it
=
185 file_system_map_
.find(FileSystemKey(extension_id
, file_system_id
));
186 if (file_system_it
== file_system_map_
.end()) {
187 const ProvidedFileSystemInfo empty_file_system_info
;
191 OnProvidedFileSystemUnmount(empty_file_system_info
,
192 base::File::FILE_ERROR_NOT_FOUND
));
193 return base::File::FILE_ERROR_NOT_FOUND
;
196 storage::ExternalMountPoints
* const mount_points
=
197 storage::ExternalMountPoints::GetSystemInstance();
198 DCHECK(mount_points
);
200 const ProvidedFileSystemInfo
& file_system_info
=
201 file_system_it
->second
->GetFileSystemInfo();
203 const std::string mount_point_name
=
204 file_system_info
.mount_path().BaseName().value();
205 if (!mount_points
->RevokeFileSystem(mount_point_name
)) {
209 OnProvidedFileSystemUnmount(file_system_info
,
210 base::File::FILE_ERROR_INVALID_OPERATION
));
211 return base::File::FILE_ERROR_INVALID_OPERATION
;
217 OnProvidedFileSystemUnmount(file_system_info
, base::File::FILE_OK
));
219 mount_point_name_to_key_map_
.erase(mount_point_name
);
221 if (reason
== UNMOUNT_REASON_USER
) {
222 registry_
->ForgetFileSystem(file_system_info
.extension_id(),
223 file_system_info
.file_system_id());
226 delete file_system_it
->second
;
227 file_system_map_
.erase(file_system_it
);
229 return base::File::FILE_OK
;
232 bool Service::RequestUnmount(const std::string
& extension_id
,
233 const std::string
& file_system_id
) {
234 DCHECK(thread_checker_
.CalledOnValidThread());
236 ProvidedFileSystemMap::iterator file_system_it
=
237 file_system_map_
.find(FileSystemKey(extension_id
, file_system_id
));
238 if (file_system_it
== file_system_map_
.end())
241 file_system_it
->second
->RequestUnmount(
242 base::Bind(&Service::OnRequestUnmountStatus
,
243 weak_ptr_factory_
.GetWeakPtr(),
244 file_system_it
->second
->GetFileSystemInfo()));
248 std::vector
<ProvidedFileSystemInfo
> Service::GetProvidedFileSystemInfoList() {
249 DCHECK(thread_checker_
.CalledOnValidThread());
251 std::vector
<ProvidedFileSystemInfo
> result
;
252 for (ProvidedFileSystemMap::const_iterator it
= file_system_map_
.begin();
253 it
!= file_system_map_
.end();
255 result
.push_back(it
->second
->GetFileSystemInfo());
260 ProvidedFileSystemInterface
* Service::GetProvidedFileSystem(
261 const std::string
& extension_id
,
262 const std::string
& file_system_id
) {
263 DCHECK(thread_checker_
.CalledOnValidThread());
265 const ProvidedFileSystemMap::const_iterator file_system_it
=
266 file_system_map_
.find(FileSystemKey(extension_id
, file_system_id
));
267 if (file_system_it
== file_system_map_
.end())
270 return file_system_it
->second
;
273 void Service::OnExtensionUnloaded(
274 content::BrowserContext
* browser_context
,
275 const extensions::Extension
* extension
,
276 extensions::UnloadedExtensionInfo::Reason reason
) {
277 // Unmount all of the provided file systems associated with this extension.
278 ProvidedFileSystemMap::iterator it
= file_system_map_
.begin();
279 while (it
!= file_system_map_
.end()) {
280 const ProvidedFileSystemInfo
& file_system_info
=
281 it
->second
->GetFileSystemInfo();
282 // Advance the iterator beforehand, otherwise it will become invalidated
283 // by the UnmountFileSystem() call.
285 if (file_system_info
.extension_id() == extension
->id()) {
286 const base::File::Error unmount_result
= UnmountFileSystem(
287 file_system_info
.extension_id(), file_system_info
.file_system_id(),
288 reason
== extensions::UnloadedExtensionInfo::REASON_PROFILE_SHUTDOWN
289 ? UNMOUNT_REASON_SHUTDOWN
290 : UNMOUNT_REASON_USER
);
291 DCHECK_EQ(base::File::FILE_OK
, unmount_result
);
296 void Service::OnExtensionLoaded(content::BrowserContext
* browser_context
,
297 const extensions::Extension
* extension
) {
298 scoped_ptr
<RegistryInterface::RestoredFileSystems
> restored_file_systems
=
299 registry_
->RestoreFileSystems(extension
->id());
301 for (const auto& restored_file_system
: *restored_file_systems
) {
302 const base::File::Error result
= MountFileSystemInternal(
303 restored_file_system
.extension_id
, restored_file_system
.options
,
304 MOUNT_CONTEXT_RESTORE
);
305 if (result
!= base::File::FILE_OK
) {
306 LOG(ERROR
) << "Failed to restore a provided file system from "
307 << "registry: " << restored_file_system
.extension_id
<< ", "
308 << restored_file_system
.options
.file_system_id
<< ", "
309 << restored_file_system
.options
.display_name
<< ".";
310 // Since remounting of the file system failed, then remove it from
311 // preferences to avoid remounting it over and over again with a failure.
312 registry_
->ForgetFileSystem(restored_file_system
.extension_id
,
313 restored_file_system
.options
.file_system_id
);
317 ProvidedFileSystemInterface
* const file_system
=
318 GetProvidedFileSystem(restored_file_system
.extension_id
,
319 restored_file_system
.options
.file_system_id
);
321 file_system
->GetWatchers()->insert(restored_file_system
.watchers
.begin(),
322 restored_file_system
.watchers
.end());
326 ProvidedFileSystemInterface
* Service::GetProvidedFileSystem(
327 const std::string
& mount_point_name
) {
328 DCHECK(thread_checker_
.CalledOnValidThread());
330 const MountPointNameToKeyMap::const_iterator mapping_it
=
331 mount_point_name_to_key_map_
.find(mount_point_name
);
332 if (mapping_it
== mount_point_name_to_key_map_
.end())
335 const ProvidedFileSystemMap::const_iterator file_system_it
=
336 file_system_map_
.find(mapping_it
->second
);
337 if (file_system_it
== file_system_map_
.end())
340 return file_system_it
->second
;
343 void Service::OnRequestUnmountStatus(
344 const ProvidedFileSystemInfo
& file_system_info
,
345 base::File::Error error
) {
346 // Notify observers about failure in unmounting, since mount() will not be
347 // called by the provided file system. In case of success mount() will be
348 // invoked, and observers notified, so there is no need to call them now.
349 if (error
!= base::File::FILE_OK
) {
350 FOR_EACH_OBSERVER(Observer
,
352 OnProvidedFileSystemUnmount(file_system_info
, error
));
356 void Service::OnWatcherChanged(const ProvidedFileSystemInfo
& file_system_info
,
357 const Watcher
& watcher
,
358 storage::WatcherManager::ChangeType change_type
,
359 const Changes
& changes
,
360 const base::Closure
& callback
) {
364 void Service::OnWatcherTagUpdated(
365 const ProvidedFileSystemInfo
& file_system_info
,
366 const Watcher
& watcher
) {
367 PrefService
* const pref_service
= profile_
->GetPrefs();
368 DCHECK(pref_service
);
370 registry_
->UpdateWatcherTag(file_system_info
, watcher
);
373 void Service::OnWatcherListChanged(
374 const ProvidedFileSystemInfo
& file_system_info
,
375 const Watchers
& watchers
) {
376 registry_
->RememberFileSystem(file_system_info
, watchers
);
379 } // namespace file_system_provider
380 } // namespace chromeos