Home | History | Annotate | Download | only in file_system_provider
      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