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 23 namespace chromeos { 24 namespace file_system_provider { 25 namespace { 26 27 // Maximum number of file systems to be mounted in the same time, per profile. 28 const size_t kMaxFileSystems = 16; 29 30 // Default factory for provided file systems. |profile| must not be NULL. 31 ProvidedFileSystemInterface* 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 40 const char kPrefKeyFileSystemId[] = "file-system-id"; 41 const char kPrefKeyDisplayName[] = "display-name"; 42 const char kPrefKeyWritable[] = "writable"; 43 44 void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) { 45 registry->RegisterDictionaryPref( 46 prefs::kFileSystemProviderMounted, 47 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 48 } 49 50 Service::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 59 Service::~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 83 Service* Service::Get(content::BrowserContext* context) { 84 return ServiceFactory::Get(context); 85 } 86 87 void Service::AddObserver(Observer* observer) { 88 DCHECK(observer); 89 observers_.AddObserver(observer); 90 } 91 92 void Service::RemoveObserver(Observer* observer) { 93 DCHECK(observer); 94 observers_.RemoveObserver(observer); 95 } 96 97 void Service::SetFileSystemFactoryForTesting( 98 const FileSystemFactoryCallback& factory_callback) { 99 DCHECK(!factory_callback.is_null()); 100 file_system_factory_ = factory_callback; 101 } 102 103 bool 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 177 bool 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 230 bool 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 246 std::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 258 ProvidedFileSystemInterface* 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 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. 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 295 void Service::OnExtensionLoaded(content::BrowserContext* browser_context, 296 const extensions::Extension* extension) { 297 RestoreFileSystems(extension->id()); 298 } 299 300 ProvidedFileSystemInterface* 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 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, 325 observers_, 326 OnProvidedFileSystemUnmount(file_system_info, error)); 327 } 328 } 329 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()); 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 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); 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 376 void 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