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/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 "webkit/browser/fileapi/external_mount_points.h"
24 namespace file_system_provider {
27 // Maximum number of file systems to be mounted in the same time, per profile.
28 const size_t kMaxFileSystems = 16;
30 // Default factory for provided file systems. |profile| must not be NULL.
31 ProvidedFileSystemInterface* CreateProvidedFileSystem(
33 const ProvidedFileSystemInfo& file_system_info) {
35 return new ProvidedFileSystem(profile, file_system_info);
40 const char kPrefKeyFileSystemId[] = "file-system-id";
41 const char kPrefKeyDisplayName[] = "display-name";
42 const char kPrefKeyWritable[] = "writable";
44 void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
45 registry->RegisterDictionaryPref(
46 prefs::kFileSystemProviderMounted,
47 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
50 Service::Service(Profile* profile,
51 extensions::ExtensionRegistry* extension_registry)
53 extension_registry_(extension_registry),
54 file_system_factory_(base::Bind(CreateProvidedFileSystem)),
55 weak_ptr_factory_(this) {
56 extension_registry_->AddObserver(this);
60 extension_registry_->RemoveObserver(this);
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();
73 const bool unmount_result = UnmountFileSystem(
74 extension_id, file_system_id, UNMOUNT_REASON_SHUTDOWN);
75 DCHECK(unmount_result);
78 DCHECK_EQ(0u, file_system_map_.size());
79 STLDeleteValues(&file_system_map_);
83 Service* Service::Get(content::BrowserContext* context) {
84 return ServiceFactory::Get(context);
87 void Service::AddObserver(Observer* observer) {
89 observers_.AddObserver(observer);
92 void Service::RemoveObserver(Observer* observer) {
94 observers_.RemoveObserver(observer);
97 void Service::SetFileSystemFactoryForTesting(
98 const FileSystemFactoryCallback& factory_callback) {
99 DCHECK(!factory_callback.is_null());
100 file_system_factory_ = factory_callback;
103 bool Service::MountFileSystem(const std::string& extension_id,
104 const std::string& file_system_id,
105 const std::string& display_name,
107 DCHECK(thread_checker_.CalledOnValidThread());
109 // If already exists a file system provided by the same extension with this
111 if (GetProvidedFileSystem(extension_id, file_system_id)) {
112 FOR_EACH_OBSERVER(Observer,
114 OnProvidedFileSystemMount(ProvidedFileSystemInfo(),
115 base::File::FILE_ERROR_EXISTS));
119 // Restrict number of file systems to prevent system abusing.
120 if (file_system_map_.size() + 1 > kMaxFileSystems) {
124 OnProvidedFileSystemMount(ProvidedFileSystemInfo(),
125 base::File::FILE_ERROR_TOO_MANY_OPENED));
129 fileapi::ExternalMountPoints* const mount_points =
130 fileapi::ExternalMountPoints::GetSystemInstance();
131 DCHECK(mount_points);
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();
139 if (!mount_points->RegisterFileSystem(mount_point_name,
140 fileapi::kFileSystemTypeProvided,
141 fileapi::FileSystemMountOption(),
146 OnProvidedFileSystemMount(ProvidedFileSystemInfo(),
147 base::File::FILE_ERROR_INVALID_OPERATION));
151 // Store the file system descriptor. Use the mount point name as the file
152 // system provider file system id.
154 // file_system_id = hello_world
155 // mount_point_name = b33f1337-hello_world-5aa5
157 // mount_path = /provided/b33f1337-hello_world-5aa5
158 ProvidedFileSystemInfo file_system_info(
159 extension_id, file_system_id, display_name, writable, mount_path);
161 ProvidedFileSystemInterface* file_system =
162 file_system_factory_.Run(profile_, file_system_info);
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);
172 OnProvidedFileSystemMount(file_system_info, base::File::FILE_OK));
177 bool Service::UnmountFileSystem(const std::string& extension_id,
178 const std::string& file_system_id,
179 UnmountReason reason) {
180 DCHECK(thread_checker_.CalledOnValidThread());
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;
189 OnProvidedFileSystemUnmount(empty_file_system_info,
190 base::File::FILE_ERROR_NOT_FOUND));
194 fileapi::ExternalMountPoints* const mount_points =
195 fileapi::ExternalMountPoints::GetSystemInstance();
196 DCHECK(mount_points);
198 const ProvidedFileSystemInfo& file_system_info =
199 file_system_it->second->GetFileSystemInfo();
201 const std::string mount_point_name =
202 file_system_info.mount_path().BaseName().value();
203 if (!mount_points->RevokeFileSystem(mount_point_name)) {
207 OnProvidedFileSystemUnmount(file_system_info,
208 base::File::FILE_ERROR_INVALID_OPERATION));
215 OnProvidedFileSystemUnmount(file_system_info, base::File::FILE_OK));
217 mount_point_name_to_key_map_.erase(mount_point_name);
219 if (reason == UNMOUNT_REASON_USER) {
220 ForgetFileSystem(file_system_info.extension_id(),
221 file_system_info.file_system_id());
224 delete file_system_it->second;
225 file_system_map_.erase(file_system_it);
230 bool Service::RequestUnmount(const std::string& extension_id,
231 const std::string& file_system_id) {
232 DCHECK(thread_checker_.CalledOnValidThread());
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())
239 file_system_it->second->RequestUnmount(
240 base::Bind(&Service::OnRequestUnmountStatus,
241 weak_ptr_factory_.GetWeakPtr(),
242 file_system_it->second->GetFileSystemInfo()));
246 std::vector<ProvidedFileSystemInfo> Service::GetProvidedFileSystemInfoList() {
247 DCHECK(thread_checker_.CalledOnValidThread());
249 std::vector<ProvidedFileSystemInfo> result;
250 for (ProvidedFileSystemMap::const_iterator it = file_system_map_.begin();
251 it != file_system_map_.end();
253 result.push_back(it->second->GetFileSystemInfo());
258 ProvidedFileSystemInterface* Service::GetProvidedFileSystem(
259 const std::string& extension_id,
260 const std::string& file_system_id) {
261 DCHECK(thread_checker_.CalledOnValidThread());
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())
268 return file_system_it->second;
271 void 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.
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);
295 void Service::OnExtensionLoaded(content::BrowserContext* browser_context,
296 const extensions::Extension* extension) {
297 RestoreFileSystems(extension->id());
300 ProvidedFileSystemInterface* Service::GetProvidedFileSystem(
301 const std::string& mount_point_name) {
302 DCHECK(thread_checker_.CalledOnValidThread());
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())
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())
314 return file_system_it->second;
317 void 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,
326 OnProvidedFileSystemUnmount(file_system_info, error));
330 void 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());
340 PrefService* const pref_service = profile_->GetPrefs();
341 DCHECK(pref_service);
343 DictionaryPrefUpdate dict_update(pref_service,
344 prefs::kFileSystemProviderMounted);
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);
354 file_systems_per_extension->SetWithoutPathExpansion(
355 file_system_info.file_system_id(), file_system);
358 void 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);
363 DictionaryPrefUpdate dict_update(pref_service,
364 prefs::kFileSystemProviderMounted);
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.
371 file_systems_per_extension->RemoveWithoutPathExpansion(file_system_id, NULL);
372 if (!file_systems_per_extension->size())
373 dict_update->Remove(extension_id, NULL);
376 void Service::RestoreFileSystems(const std::string& extension_id) {
377 PrefService* const pref_service = profile_->GetPrefs();
378 DCHECK(pref_service);
380 const base::DictionaryValue* const file_systems =
381 pref_service->GetDictionary(prefs::kFileSystemProviderMounted);
382 DCHECK(file_systems);
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.
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());
394 for (base::DictionaryValue::Iterator it(*file_systems_per_extension_copy);
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);
403 std::string file_system_id;
404 std::string display_name;
407 if (!file_system_value->GetAsDictionary(&file_system) ||
408 !file_system->GetStringWithoutPathExpansion(kPrefKeyFileSystemId,
410 !file_system->GetStringWithoutPathExpansion(kPrefKeyDisplayName,
412 !file_system->GetBooleanWithoutPathExpansion(kPrefKeyWritable,
414 file_system_id.empty() || display_name.empty()) {
416 << "Malformed provided file system information in preferences.";
421 MountFileSystem(extension_id, file_system_id, display_name, writable);
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);
433 } // namespace file_system_provider
434 } // namespace chromeos