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/event_router.h"
20 #include "extensions/browser/extension_registry.h"
21 #include "extensions/browser/extension_system.h"
22 #include "webkit/browser/fileapi/external_mount_points.h"
23
24 namespace chromeos {
25 namespace file_system_provider {
26 namespace {
27
28 // Maximum number of file systems to be mounted in the same time, per profile.
29 const size_t kMaxFileSystems = 16;
30
31 // Default factory for provided file systems. The |event_router| must not be
32 // NULL.
CreateProvidedFileSystem(extensions::EventRouter * event_router,const ProvidedFileSystemInfo & file_system_info)33 ProvidedFileSystemInterface* CreateProvidedFileSystem(
34 extensions::EventRouter* event_router,
35 const ProvidedFileSystemInfo& file_system_info) {
36 DCHECK(event_router);
37 return new ProvidedFileSystem(event_router, file_system_info);
38 }
39
40 } // namespace
41
42 const char kPrefKeyFileSystemId[] = "file-system-id";
43 const char kPrefKeyFileSystemName[] = "file-system-name";
44
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)45 void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
46 registry->RegisterDictionaryPref(
47 prefs::kFileSystemProviderMounted,
48 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
49 }
50
Service(Profile * profile,extensions::ExtensionRegistry * extension_registry)51 Service::Service(Profile* profile,
52 extensions::ExtensionRegistry* extension_registry)
53 : profile_(profile),
54 extension_registry_(extension_registry),
55 file_system_factory_(base::Bind(CreateProvidedFileSystem)),
56 weak_ptr_factory_(this) {
57 extension_registry_->AddObserver(this);
58 }
59
~Service()60 Service::~Service() {
61 extension_registry_->RemoveObserver(this);
62 RememberFileSystems();
63
64 ProvidedFileSystemMap::iterator it = file_system_map_.begin();
65 while (it != file_system_map_.end()) {
66 const std::string file_system_id =
67 it->second->GetFileSystemInfo().file_system_id();
68 const std::string extension_id =
69 it->second->GetFileSystemInfo().extension_id();
70 ++it;
71 UnmountFileSystem(extension_id, file_system_id);
72 }
73
74 DCHECK_EQ(0u, file_system_map_.size());
75 STLDeleteValues(&file_system_map_);
76 }
77
78 // static
Get(content::BrowserContext * context)79 Service* Service::Get(content::BrowserContext* context) {
80 return ServiceFactory::Get(context);
81 }
82
AddObserver(Observer * observer)83 void Service::AddObserver(Observer* observer) {
84 DCHECK(observer);
85 observers_.AddObserver(observer);
86 }
87
RemoveObserver(Observer * observer)88 void Service::RemoveObserver(Observer* observer) {
89 DCHECK(observer);
90 observers_.RemoveObserver(observer);
91 }
92
SetFileSystemFactoryForTesting(const FileSystemFactoryCallback & factory_callback)93 void Service::SetFileSystemFactoryForTesting(
94 const FileSystemFactoryCallback& factory_callback) {
95 DCHECK(!factory_callback.is_null());
96 file_system_factory_ = factory_callback;
97 }
98
MountFileSystem(const std::string & extension_id,const std::string & file_system_id,const std::string & file_system_name)99 bool Service::MountFileSystem(const std::string& extension_id,
100 const std::string& file_system_id,
101 const std::string& file_system_name) {
102 DCHECK(thread_checker_.CalledOnValidThread());
103
104 // If already exists a file system provided by the same extension with this
105 // id, then abort.
106 if (GetProvidedFileSystem(extension_id, file_system_id)) {
107 FOR_EACH_OBSERVER(Observer,
108 observers_,
109 OnProvidedFileSystemMount(ProvidedFileSystemInfo(),
110 base::File::FILE_ERROR_EXISTS));
111 return false;
112 }
113
114 // Restrict number of file systems to prevent system abusing.
115 if (file_system_map_.size() + 1 > kMaxFileSystems) {
116 FOR_EACH_OBSERVER(
117 Observer,
118 observers_,
119 OnProvidedFileSystemMount(ProvidedFileSystemInfo(),
120 base::File::FILE_ERROR_TOO_MANY_OPENED));
121 return false;
122 }
123
124 fileapi::ExternalMountPoints* const mount_points =
125 fileapi::ExternalMountPoints::GetSystemInstance();
126 DCHECK(mount_points);
127
128 // The mount point path and name are unique per system, since they are system
129 // wide. This is necessary for copying between profiles.
130 const base::FilePath& mount_path =
131 util::GetMountPath(profile_, extension_id, file_system_id);
132 const std::string mount_point_name = mount_path.BaseName().AsUTF8Unsafe();
133
134 if (!mount_points->RegisterFileSystem(mount_point_name,
135 fileapi::kFileSystemTypeProvided,
136 fileapi::FileSystemMountOption(),
137 mount_path)) {
138 FOR_EACH_OBSERVER(
139 Observer,
140 observers_,
141 OnProvidedFileSystemMount(ProvidedFileSystemInfo(),
142 base::File::FILE_ERROR_INVALID_OPERATION));
143 return false;
144 }
145
146 // Store the file system descriptor. Use the mount point name as the file
147 // system provider file system id.
148 // Examples:
149 // file_system_id = 41
150 // mount_point_name = b33f1337-41-5aa5
151 // mount_path = /provided/b33f1337-41-5aa5
152 ProvidedFileSystemInfo file_system_info(
153 extension_id, file_system_id, file_system_name, mount_path);
154
155 // The event router may be NULL for unit tests.
156 extensions::EventRouter* router = extensions::EventRouter::Get(profile_);
157
158 ProvidedFileSystemInterface* file_system =
159 file_system_factory_.Run(router, file_system_info);
160 DCHECK(file_system);
161 file_system_map_[FileSystemKey(extension_id, file_system_id)] = file_system;
162 mount_point_name_to_key_map_[mount_point_name] =
163 FileSystemKey(extension_id, file_system_id);
164
165 FOR_EACH_OBSERVER(
166 Observer,
167 observers_,
168 OnProvidedFileSystemMount(file_system_info, base::File::FILE_OK));
169
170 return true;
171 }
172
UnmountFileSystem(const std::string & extension_id,const std::string & file_system_id)173 bool Service::UnmountFileSystem(const std::string& extension_id,
174 const std::string& file_system_id) {
175 DCHECK(thread_checker_.CalledOnValidThread());
176
177 const ProvidedFileSystemMap::iterator file_system_it =
178 file_system_map_.find(FileSystemKey(extension_id, file_system_id));
179 if (file_system_it == file_system_map_.end()) {
180 const ProvidedFileSystemInfo empty_file_system_info;
181 FOR_EACH_OBSERVER(
182 Observer,
183 observers_,
184 OnProvidedFileSystemUnmount(empty_file_system_info,
185 base::File::FILE_ERROR_NOT_FOUND));
186 return false;
187 }
188
189 fileapi::ExternalMountPoints* const mount_points =
190 fileapi::ExternalMountPoints::GetSystemInstance();
191 DCHECK(mount_points);
192
193 const ProvidedFileSystemInfo& file_system_info =
194 file_system_it->second->GetFileSystemInfo();
195
196 const std::string mount_point_name =
197 file_system_info.mount_path().BaseName().value();
198 if (!mount_points->RevokeFileSystem(mount_point_name)) {
199 FOR_EACH_OBSERVER(
200 Observer,
201 observers_,
202 OnProvidedFileSystemUnmount(file_system_info,
203 base::File::FILE_ERROR_INVALID_OPERATION));
204 return false;
205 }
206
207 FOR_EACH_OBSERVER(
208 Observer,
209 observers_,
210 OnProvidedFileSystemUnmount(file_system_info, base::File::FILE_OK));
211
212 mount_point_name_to_key_map_.erase(mount_point_name);
213
214 delete file_system_it->second;
215 file_system_map_.erase(file_system_it);
216
217 return true;
218 }
219
RequestUnmount(const std::string & extension_id,const std::string & file_system_id)220 bool Service::RequestUnmount(const std::string& extension_id,
221 const std::string& file_system_id) {
222 DCHECK(thread_checker_.CalledOnValidThread());
223
224 ProvidedFileSystemMap::iterator file_system_it =
225 file_system_map_.find(FileSystemKey(extension_id, file_system_id));
226 if (file_system_it == file_system_map_.end())
227 return false;
228
229 file_system_it->second->RequestUnmount(
230 base::Bind(&Service::OnRequestUnmountStatus,
231 weak_ptr_factory_.GetWeakPtr(),
232 file_system_it->second->GetFileSystemInfo()));
233 return true;
234 }
235
GetProvidedFileSystemInfoList()236 std::vector<ProvidedFileSystemInfo> Service::GetProvidedFileSystemInfoList() {
237 DCHECK(thread_checker_.CalledOnValidThread());
238
239 std::vector<ProvidedFileSystemInfo> result;
240 for (ProvidedFileSystemMap::const_iterator it = file_system_map_.begin();
241 it != file_system_map_.end();
242 ++it) {
243 result.push_back(it->second->GetFileSystemInfo());
244 }
245 return result;
246 }
247
GetProvidedFileSystem(const std::string & extension_id,const std::string & file_system_id)248 ProvidedFileSystemInterface* Service::GetProvidedFileSystem(
249 const std::string& extension_id,
250 const std::string& file_system_id) {
251 DCHECK(thread_checker_.CalledOnValidThread());
252
253 const ProvidedFileSystemMap::const_iterator file_system_it =
254 file_system_map_.find(FileSystemKey(extension_id, file_system_id));
255 if (file_system_it == file_system_map_.end())
256 return NULL;
257
258 return file_system_it->second;
259 }
260
OnExtensionUnloaded(content::BrowserContext * browser_context,const extensions::Extension * extension,extensions::UnloadedExtensionInfo::Reason reason)261 void Service::OnExtensionUnloaded(
262 content::BrowserContext* browser_context,
263 const extensions::Extension* extension,
264 extensions::UnloadedExtensionInfo::Reason reason) {
265 // If the reason is not a profile shutdown, then forget the mounted file
266 // systems from preferences.
267 if (reason != extensions::UnloadedExtensionInfo::REASON_PROFILE_SHUTDOWN)
268 ForgetFileSystems(extension->id());
269
270 // Unmount all of the provided file systems associated with this extension.
271 ProvidedFileSystemMap::iterator it = file_system_map_.begin();
272 while (it != file_system_map_.end()) {
273 const ProvidedFileSystemInfo& file_system_info =
274 it->second->GetFileSystemInfo();
275 // Advance the iterator beforehand, otherwise it will become invalidated
276 // by the UnmountFileSystem() call.
277 ++it;
278 if (file_system_info.extension_id() == extension->id()) {
279 bool result = UnmountFileSystem(file_system_info.extension_id(),
280 file_system_info.file_system_id());
281 DCHECK(result);
282 }
283 }
284 }
285
OnExtensionLoaded(content::BrowserContext * browser_context,const extensions::Extension * extension)286 void Service::OnExtensionLoaded(content::BrowserContext* browser_context,
287 const extensions::Extension* extension) {
288 RestoreFileSystems(extension->id());
289 }
290
GetProvidedFileSystem(const std::string & mount_point_name)291 ProvidedFileSystemInterface* Service::GetProvidedFileSystem(
292 const std::string& mount_point_name) {
293 DCHECK(thread_checker_.CalledOnValidThread());
294
295 const MountPointNameToKeyMap::const_iterator mapping_it =
296 mount_point_name_to_key_map_.find(mount_point_name);
297 if (mapping_it == mount_point_name_to_key_map_.end())
298 return NULL;
299
300 const ProvidedFileSystemMap::const_iterator file_system_it =
301 file_system_map_.find(mapping_it->second);
302 if (file_system_it == file_system_map_.end())
303 return NULL;
304
305 return file_system_it->second;
306 }
307
OnRequestUnmountStatus(const ProvidedFileSystemInfo & file_system_info,base::File::Error error)308 void Service::OnRequestUnmountStatus(
309 const ProvidedFileSystemInfo& file_system_info,
310 base::File::Error error) {
311 // Notify observers about failure in unmounting, since mount() will not be
312 // called by the provided file system. In case of success mount() will be
313 // invoked, and observers notified, so there is no need to call them now.
314 if (error != base::File::FILE_OK) {
315 FOR_EACH_OBSERVER(Observer,
316 observers_,
317 OnProvidedFileSystemUnmount(file_system_info, error));
318 }
319 }
320
RememberFileSystems()321 void Service::RememberFileSystems() {
322 base::DictionaryValue extensions;
323 const std::vector<ProvidedFileSystemInfo> file_system_info_list =
324 GetProvidedFileSystemInfoList();
325
326 for (std::vector<ProvidedFileSystemInfo>::const_iterator it =
327 file_system_info_list.begin();
328 it != file_system_info_list.end();
329 ++it) {
330 base::ListValue* file_systems = NULL;
331 if (!extensions.GetList(it->extension_id(), &file_systems)) {
332 file_systems = new base::ListValue();
333 extensions.Set(it->extension_id(), file_systems);
334 }
335
336 base::DictionaryValue* file_system = new base::DictionaryValue();
337 file_system->SetString(kPrefKeyFileSystemId, it->file_system_id());
338 file_system->SetString(kPrefKeyFileSystemName, it->file_system_name());
339 file_systems->Append(file_system);
340 }
341
342 PrefService* pref_service = profile_->GetPrefs();
343 DCHECK(pref_service);
344 pref_service->Set(prefs::kFileSystemProviderMounted, extensions);
345 pref_service->CommitPendingWrite();
346 }
347
ForgetFileSystems(const std::string & extension_id)348 void Service::ForgetFileSystems(const std::string& extension_id) {
349 PrefService* pref_service = profile_->GetPrefs();
350 DCHECK(pref_service);
351
352 DictionaryPrefUpdate update(pref_service, prefs::kFileSystemProviderMounted);
353 base::DictionaryValue* extensions = update.Get();
354 DCHECK(extensions);
355
356 extensions->Remove(extension_id, NULL);
357 }
358
RestoreFileSystems(const std::string & extension_id)359 void Service::RestoreFileSystems(const std::string& extension_id) {
360 PrefService* pref_service = profile_->GetPrefs();
361 DCHECK(pref_service);
362
363 const base::DictionaryValue* extensions =
364 pref_service->GetDictionary(prefs::kFileSystemProviderMounted);
365 DCHECK(extensions);
366
367 const base::ListValue* file_systems = NULL;
368
369 if (!extensions->GetList(extension_id, &file_systems))
370 return;
371
372 for (size_t i = 0; i < file_systems->GetSize(); ++i) {
373 const base::DictionaryValue* file_system = NULL;
374 file_systems->GetDictionary(i, &file_system);
375 DCHECK(file_system);
376
377 std::string file_system_id;
378 file_system->GetString(kPrefKeyFileSystemId, &file_system_id);
379 DCHECK(!file_system_id.empty());
380
381 std::string file_system_name;
382 file_system->GetString(kPrefKeyFileSystemName, &file_system_name);
383 DCHECK(!file_system_name.empty());
384
385 if (file_system_id.empty() || file_system_name.empty()) {
386 LOG(ERROR)
387 << "Malformed provided file system information in preferences.";
388 continue;
389 }
390
391 const bool result =
392 MountFileSystem(extension_id, file_system_id, file_system_name);
393 if (!result) {
394 LOG(ERROR) << "Failed to restore a provided file system from "
395 << "preferences: " << extension_id << ", " << file_system_id
396 << ", " << file_system_name << ".";
397 }
398 }
399 }
400
401 } // namespace file_system_provider
402 } // namespace chromeos
403