Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / storage / browser / fileapi / file_system_context.cc
1 // Copyright (c) 2012 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 "storage/browser/fileapi/file_system_context.h"
6
7 #include "base/bind.h"
8 #include "base/single_thread_task_runner.h"
9 #include "base/stl_util.h"
10 #include "base/task_runner_util.h"
11 #include "net/url_request/url_request.h"
12 #include "storage/browser/blob/file_stream_reader.h"
13 #include "storage/browser/fileapi/copy_or_move_file_validator.h"
14 #include "storage/browser/fileapi/external_mount_points.h"
15 #include "storage/browser/fileapi/file_permission_policy.h"
16 #include "storage/browser/fileapi/file_stream_writer.h"
17 #include "storage/browser/fileapi/file_system_file_util.h"
18 #include "storage/browser/fileapi/file_system_operation.h"
19 #include "storage/browser/fileapi/file_system_operation_runner.h"
20 #include "storage/browser/fileapi/file_system_options.h"
21 #include "storage/browser/fileapi/file_system_quota_client.h"
22 #include "storage/browser/fileapi/file_system_url.h"
23 #include "storage/browser/fileapi/isolated_context.h"
24 #include "storage/browser/fileapi/isolated_file_system_backend.h"
25 #include "storage/browser/fileapi/mount_points.h"
26 #include "storage/browser/fileapi/quota/quota_reservation.h"
27 #include "storage/browser/fileapi/sandbox_file_system_backend.h"
28 #include "storage/browser/quota/quota_manager_proxy.h"
29 #include "storage/browser/quota/special_storage_policy.h"
30 #include "storage/common/fileapi/file_system_info.h"
31 #include "storage/common/fileapi/file_system_util.h"
32 #include "url/gurl.h"
33
34 using storage::QuotaClient;
35
36 namespace storage {
37
38 namespace {
39
40 QuotaClient* CreateQuotaClient(
41     FileSystemContext* context,
42     bool is_incognito) {
43   return new FileSystemQuotaClient(context, is_incognito);
44 }
45
46
47 void DidGetMetadataForResolveURL(
48     const base::FilePath& path,
49     const FileSystemContext::ResolveURLCallback& callback,
50     const FileSystemInfo& info,
51     base::File::Error error,
52     const base::File::Info& file_info) {
53   if (error != base::File::FILE_OK) {
54     if (error == base::File::FILE_ERROR_NOT_FOUND) {
55       callback.Run(base::File::FILE_OK, info, path,
56                    FileSystemContext::RESOLVED_ENTRY_NOT_FOUND);
57     } else {
58       callback.Run(error, FileSystemInfo(), base::FilePath(),
59                    FileSystemContext::RESOLVED_ENTRY_NOT_FOUND);
60     }
61     return;
62   }
63   callback.Run(error, info, path, file_info.is_directory ?
64       FileSystemContext::RESOLVED_ENTRY_DIRECTORY :
65       FileSystemContext::RESOLVED_ENTRY_FILE);
66 }
67
68 void RelayResolveURLCallback(
69     scoped_refptr<base::MessageLoopProxy> message_loop,
70     const FileSystemContext::ResolveURLCallback& callback,
71     base::File::Error result,
72     const FileSystemInfo& info,
73     const base::FilePath& file_path,
74     FileSystemContext::ResolvedEntryType type) {
75   message_loop->PostTask(
76       FROM_HERE, base::Bind(callback, result, info, file_path, type));
77 }
78
79 }  // namespace
80
81 // static
82 int FileSystemContext::GetPermissionPolicy(FileSystemType type) {
83   switch (type) {
84     case kFileSystemTypeTemporary:
85     case kFileSystemTypePersistent:
86     case kFileSystemTypeSyncable:
87       return FILE_PERMISSION_SANDBOX;
88
89     case kFileSystemTypeDrive:
90     case kFileSystemTypeNativeForPlatformApp:
91     case kFileSystemTypeNativeLocal:
92     case kFileSystemTypeCloudDevice:
93     case kFileSystemTypeProvided:
94     case kFileSystemTypeDeviceMediaAsFileStorage:
95       return FILE_PERMISSION_USE_FILE_PERMISSION;
96
97     case kFileSystemTypeRestrictedNativeLocal:
98       return FILE_PERMISSION_READ_ONLY |
99              FILE_PERMISSION_USE_FILE_PERMISSION;
100
101     case kFileSystemTypeDeviceMedia:
102     case kFileSystemTypeIphoto:
103     case kFileSystemTypeItunes:
104     case kFileSystemTypeNativeMedia:
105     case kFileSystemTypePicasa:
106       return FILE_PERMISSION_USE_FILE_PERMISSION;
107
108     // Following types are only accessed via IsolatedFileSystem, and
109     // don't have their own permission policies.
110     case kFileSystemTypeDragged:
111     case kFileSystemTypeForTransientFile:
112     case kFileSystemTypePluginPrivate:
113       return FILE_PERMISSION_ALWAYS_DENY;
114
115     // Following types only appear as mount_type, and will not be
116     // queried for their permission policies.
117     case kFileSystemTypeIsolated:
118     case kFileSystemTypeExternal:
119       return FILE_PERMISSION_ALWAYS_DENY;
120
121     // Following types should not be used to access files by FileAPI clients.
122     case kFileSystemTypeTest:
123     case kFileSystemTypeSyncableForInternalSync:
124     case kFileSystemInternalTypeEnumEnd:
125     case kFileSystemInternalTypeEnumStart:
126     case kFileSystemTypeUnknown:
127       return FILE_PERMISSION_ALWAYS_DENY;
128   }
129   NOTREACHED();
130   return FILE_PERMISSION_ALWAYS_DENY;
131 }
132
133 FileSystemContext::FileSystemContext(
134     base::SingleThreadTaskRunner* io_task_runner,
135     base::SequencedTaskRunner* file_task_runner,
136     ExternalMountPoints* external_mount_points,
137     storage::SpecialStoragePolicy* special_storage_policy,
138     storage::QuotaManagerProxy* quota_manager_proxy,
139     ScopedVector<FileSystemBackend> additional_backends,
140     const std::vector<URLRequestAutoMountHandler>& auto_mount_handlers,
141     const base::FilePath& partition_path,
142     const FileSystemOptions& options)
143     : io_task_runner_(io_task_runner),
144       default_file_task_runner_(file_task_runner),
145       quota_manager_proxy_(quota_manager_proxy),
146       sandbox_delegate_(
147           new SandboxFileSystemBackendDelegate(quota_manager_proxy,
148                                                file_task_runner,
149                                                partition_path,
150                                                special_storage_policy,
151                                                options)),
152       sandbox_backend_(new SandboxFileSystemBackend(sandbox_delegate_.get())),
153       isolated_backend_(new IsolatedFileSystemBackend()),
154       plugin_private_backend_(
155           new PluginPrivateFileSystemBackend(file_task_runner,
156                                              partition_path,
157                                              special_storage_policy,
158                                              options)),
159       additional_backends_(additional_backends.Pass()),
160       auto_mount_handlers_(auto_mount_handlers),
161       external_mount_points_(external_mount_points),
162       partition_path_(partition_path),
163       is_incognito_(options.is_incognito()),
164       operation_runner_(new FileSystemOperationRunner(this)) {
165   RegisterBackend(sandbox_backend_.get());
166   RegisterBackend(isolated_backend_.get());
167   RegisterBackend(plugin_private_backend_.get());
168
169   for (ScopedVector<FileSystemBackend>::const_iterator iter =
170           additional_backends_.begin();
171        iter != additional_backends_.end(); ++iter) {
172     RegisterBackend(*iter);
173   }
174
175   if (quota_manager_proxy) {
176     // Quota client assumes all backends have registered.
177     quota_manager_proxy->RegisterClient(CreateQuotaClient(
178             this, options.is_incognito()));
179   }
180
181   sandbox_backend_->Initialize(this);
182   isolated_backend_->Initialize(this);
183   plugin_private_backend_->Initialize(this);
184   for (ScopedVector<FileSystemBackend>::const_iterator iter =
185           additional_backends_.begin();
186        iter != additional_backends_.end(); ++iter) {
187     (*iter)->Initialize(this);
188   }
189
190   // Additional mount points must be added before regular system-wide
191   // mount points.
192   if (external_mount_points)
193     url_crackers_.push_back(external_mount_points);
194   url_crackers_.push_back(ExternalMountPoints::GetSystemInstance());
195   url_crackers_.push_back(IsolatedContext::GetInstance());
196 }
197
198 bool FileSystemContext::DeleteDataForOriginOnFileTaskRunner(
199     const GURL& origin_url) {
200   DCHECK(default_file_task_runner()->RunsTasksOnCurrentThread());
201   DCHECK(origin_url == origin_url.GetOrigin());
202
203   bool success = true;
204   for (FileSystemBackendMap::iterator iter = backend_map_.begin();
205        iter != backend_map_.end();
206        ++iter) {
207     FileSystemBackend* backend = iter->second;
208     if (!backend->GetQuotaUtil())
209       continue;
210     if (backend->GetQuotaUtil()->DeleteOriginDataOnFileTaskRunner(
211             this, quota_manager_proxy(), origin_url, iter->first)
212             != base::File::FILE_OK) {
213       // Continue the loop, but record the failure.
214       success = false;
215     }
216   }
217
218   return success;
219 }
220
221 scoped_refptr<QuotaReservation>
222 FileSystemContext::CreateQuotaReservationOnFileTaskRunner(
223     const GURL& origin_url,
224     FileSystemType type) {
225   DCHECK(default_file_task_runner()->RunsTasksOnCurrentThread());
226   FileSystemBackend* backend = GetFileSystemBackend(type);
227   if (!backend || !backend->GetQuotaUtil())
228     return scoped_refptr<QuotaReservation>();
229   return backend->GetQuotaUtil()->CreateQuotaReservationOnFileTaskRunner(
230       origin_url, type);
231 }
232
233 void FileSystemContext::Shutdown() {
234   if (!io_task_runner_->RunsTasksOnCurrentThread()) {
235     io_task_runner_->PostTask(
236         FROM_HERE, base::Bind(&FileSystemContext::Shutdown,
237                               make_scoped_refptr(this)));
238     return;
239   }
240   operation_runner_->Shutdown();
241 }
242
243 FileSystemQuotaUtil*
244 FileSystemContext::GetQuotaUtil(FileSystemType type) const {
245   FileSystemBackend* backend = GetFileSystemBackend(type);
246   if (!backend)
247     return NULL;
248   return backend->GetQuotaUtil();
249 }
250
251 AsyncFileUtil* FileSystemContext::GetAsyncFileUtil(
252     FileSystemType type) const {
253   FileSystemBackend* backend = GetFileSystemBackend(type);
254   if (!backend)
255     return NULL;
256   return backend->GetAsyncFileUtil(type);
257 }
258
259 CopyOrMoveFileValidatorFactory*
260 FileSystemContext::GetCopyOrMoveFileValidatorFactory(
261     FileSystemType type, base::File::Error* error_code) const {
262   DCHECK(error_code);
263   *error_code = base::File::FILE_OK;
264   FileSystemBackend* backend = GetFileSystemBackend(type);
265   if (!backend)
266     return NULL;
267   return backend->GetCopyOrMoveFileValidatorFactory(
268       type, error_code);
269 }
270
271 FileSystemBackend* FileSystemContext::GetFileSystemBackend(
272     FileSystemType type) const {
273   FileSystemBackendMap::const_iterator found = backend_map_.find(type);
274   if (found != backend_map_.end())
275     return found->second;
276   NOTREACHED() << "Unknown filesystem type: " << type;
277   return NULL;
278 }
279
280 WatcherManager* FileSystemContext::GetWatcherManager(
281     FileSystemType type) const {
282   FileSystemBackend* backend = GetFileSystemBackend(type);
283   if (!backend)
284     return NULL;
285   return backend->GetWatcherManager(type);
286 }
287
288 bool FileSystemContext::IsSandboxFileSystem(FileSystemType type) const {
289   FileSystemBackendMap::const_iterator found = backend_map_.find(type);
290   return found != backend_map_.end() && found->second->GetQuotaUtil();
291 }
292
293 const UpdateObserverList* FileSystemContext::GetUpdateObservers(
294     FileSystemType type) const {
295   FileSystemBackend* backend = GetFileSystemBackend(type);
296   return backend->GetUpdateObservers(type);
297 }
298
299 const ChangeObserverList* FileSystemContext::GetChangeObservers(
300     FileSystemType type) const {
301   FileSystemBackend* backend = GetFileSystemBackend(type);
302   return backend->GetChangeObservers(type);
303 }
304
305 const AccessObserverList* FileSystemContext::GetAccessObservers(
306     FileSystemType type) const {
307   FileSystemBackend* backend = GetFileSystemBackend(type);
308   return backend->GetAccessObservers(type);
309 }
310
311 void FileSystemContext::GetFileSystemTypes(
312     std::vector<FileSystemType>* types) const {
313   types->clear();
314   for (FileSystemBackendMap::const_iterator iter = backend_map_.begin();
315        iter != backend_map_.end(); ++iter)
316     types->push_back(iter->first);
317 }
318
319 ExternalFileSystemBackend*
320 FileSystemContext::external_backend() const {
321   return static_cast<ExternalFileSystemBackend*>(
322       GetFileSystemBackend(kFileSystemTypeExternal));
323 }
324
325 void FileSystemContext::OpenFileSystem(
326     const GURL& origin_url,
327     FileSystemType type,
328     OpenFileSystemMode mode,
329     const OpenFileSystemCallback& callback) {
330   DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
331   DCHECK(!callback.is_null());
332
333   if (!FileSystemContext::IsSandboxFileSystem(type)) {
334     // Disallow opening a non-sandboxed filesystem.
335     callback.Run(GURL(), std::string(), base::File::FILE_ERROR_SECURITY);
336     return;
337   }
338
339   FileSystemBackend* backend = GetFileSystemBackend(type);
340   if (!backend) {
341     callback.Run(GURL(), std::string(), base::File::FILE_ERROR_SECURITY);
342     return;
343   }
344
345   backend->ResolveURL(
346       CreateCrackedFileSystemURL(origin_url, type, base::FilePath()),
347       mode,
348       callback);
349 }
350
351 void FileSystemContext::ResolveURL(
352     const FileSystemURL& url,
353     const ResolveURLCallback& callback) {
354   DCHECK(!callback.is_null());
355
356   // If not on IO thread, forward before passing the task to the backend.
357   if (!io_task_runner_->RunsTasksOnCurrentThread()) {
358     ResolveURLCallback relay_callback =
359         base::Bind(&RelayResolveURLCallback,
360                    base::MessageLoopProxy::current(), callback);
361     io_task_runner_->PostTask(
362         FROM_HERE,
363         base::Bind(&FileSystemContext::ResolveURL, this, url, relay_callback));
364     return;
365   }
366
367   FileSystemBackend* backend = GetFileSystemBackend(url.type());
368   if (!backend) {
369     callback.Run(base::File::FILE_ERROR_SECURITY,
370                  FileSystemInfo(), base::FilePath(),
371                  FileSystemContext::RESOLVED_ENTRY_NOT_FOUND);
372     return;
373   }
374
375   backend->ResolveURL(
376       url,
377       OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT,
378       base::Bind(&FileSystemContext::DidOpenFileSystemForResolveURL,
379                  this,
380                  url,
381                  callback));
382 }
383
384 void FileSystemContext::AttemptAutoMountForURLRequest(
385     const net::URLRequest* url_request,
386     const std::string& storage_domain,
387     const StatusCallback& callback) {
388   FileSystemURL filesystem_url(url_request->url());
389   if (filesystem_url.type() == kFileSystemTypeExternal) {
390     for (size_t i = 0; i < auto_mount_handlers_.size(); i++) {
391       if (auto_mount_handlers_[i].Run(url_request, filesystem_url,
392                                       storage_domain, callback)) {
393         return;
394       }
395     }
396   }
397   callback.Run(base::File::FILE_ERROR_NOT_FOUND);
398 }
399
400 void FileSystemContext::DeleteFileSystem(
401     const GURL& origin_url,
402     FileSystemType type,
403     const StatusCallback& callback) {
404   DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
405   DCHECK(origin_url == origin_url.GetOrigin());
406   DCHECK(!callback.is_null());
407
408   FileSystemBackend* backend = GetFileSystemBackend(type);
409   if (!backend) {
410     callback.Run(base::File::FILE_ERROR_SECURITY);
411     return;
412   }
413   if (!backend->GetQuotaUtil()) {
414     callback.Run(base::File::FILE_ERROR_INVALID_OPERATION);
415     return;
416   }
417
418   base::PostTaskAndReplyWithResult(
419       default_file_task_runner(),
420       FROM_HERE,
421       // It is safe to pass Unretained(quota_util) since context owns it.
422       base::Bind(&FileSystemQuotaUtil::DeleteOriginDataOnFileTaskRunner,
423                  base::Unretained(backend->GetQuotaUtil()),
424                  make_scoped_refptr(this),
425                  base::Unretained(quota_manager_proxy()),
426                  origin_url,
427                  type),
428       callback);
429 }
430
431 scoped_ptr<storage::FileStreamReader> FileSystemContext::CreateFileStreamReader(
432     const FileSystemURL& url,
433     int64 offset,
434     int64 max_bytes_to_read,
435     const base::Time& expected_modification_time) {
436   if (!url.is_valid())
437     return scoped_ptr<storage::FileStreamReader>();
438   FileSystemBackend* backend = GetFileSystemBackend(url.type());
439   if (!backend)
440     return scoped_ptr<storage::FileStreamReader>();
441   return backend->CreateFileStreamReader(
442       url, offset, max_bytes_to_read, expected_modification_time, this);
443 }
444
445 scoped_ptr<FileStreamWriter> FileSystemContext::CreateFileStreamWriter(
446     const FileSystemURL& url,
447     int64 offset) {
448   if (!url.is_valid())
449     return scoped_ptr<FileStreamWriter>();
450   FileSystemBackend* backend = GetFileSystemBackend(url.type());
451   if (!backend)
452     return scoped_ptr<FileStreamWriter>();
453   return backend->CreateFileStreamWriter(url, offset, this);
454 }
455
456 scoped_ptr<FileSystemOperationRunner>
457 FileSystemContext::CreateFileSystemOperationRunner() {
458   return make_scoped_ptr(new FileSystemOperationRunner(this));
459 }
460
461 FileSystemURL FileSystemContext::CrackURL(const GURL& url) const {
462   return CrackFileSystemURL(FileSystemURL(url));
463 }
464
465 FileSystemURL FileSystemContext::CreateCrackedFileSystemURL(
466     const GURL& origin,
467     FileSystemType type,
468     const base::FilePath& path) const {
469   return CrackFileSystemURL(FileSystemURL(origin, type, path));
470 }
471
472 #if defined(OS_CHROMEOS)
473 void FileSystemContext::EnableTemporaryFileSystemInIncognito() {
474   sandbox_backend_->set_enable_temporary_file_system_in_incognito(true);
475 }
476 #endif
477
478 bool FileSystemContext::CanServeURLRequest(const FileSystemURL& url) const {
479   // We never support accessing files in isolated filesystems via an URL.
480   if (url.mount_type() == kFileSystemTypeIsolated)
481     return false;
482 #if defined(OS_CHROMEOS)
483   if (url.type() == kFileSystemTypeTemporary &&
484       sandbox_backend_->enable_temporary_file_system_in_incognito()) {
485     return true;
486   }
487 #endif
488   return !is_incognito_ || !FileSystemContext::IsSandboxFileSystem(url.type());
489 }
490
491 bool FileSystemContext::ShouldFlushOnWriteCompletion(
492     FileSystemType type) const {
493   if (IsSandboxFileSystem(type)) {
494     // Disable Flush() for each write operation on SandboxFileSystems since it
495     // hurts the performance, assuming the FileSystems are stored in a local
496     // disk, we don't need to keep calling fsync() for it.
497     // On the other hand, other FileSystems that may stored on a removable media
498     // should be Flush()ed as soon as a write operation is completed, so that
499     // written data is saved over sudden media removal.
500     return false;
501   }
502   return true;
503 }
504
505 void FileSystemContext::OpenPluginPrivateFileSystem(
506     const GURL& origin_url,
507     FileSystemType type,
508     const std::string& filesystem_id,
509     const std::string& plugin_id,
510     OpenFileSystemMode mode,
511     const StatusCallback& callback) {
512   DCHECK(plugin_private_backend_);
513   plugin_private_backend_->OpenPrivateFileSystem(
514       origin_url, type, filesystem_id, plugin_id, mode, callback);
515 }
516
517 FileSystemContext::~FileSystemContext() {
518 }
519
520 void FileSystemContext::DeleteOnCorrectThread() const {
521   if (!io_task_runner_->RunsTasksOnCurrentThread() &&
522       io_task_runner_->DeleteSoon(FROM_HERE, this)) {
523     return;
524   }
525   delete this;
526 }
527
528 FileSystemOperation* FileSystemContext::CreateFileSystemOperation(
529     const FileSystemURL& url, base::File::Error* error_code) {
530   if (!url.is_valid()) {
531     if (error_code)
532       *error_code = base::File::FILE_ERROR_INVALID_URL;
533     return NULL;
534   }
535
536   FileSystemBackend* backend = GetFileSystemBackend(url.type());
537   if (!backend) {
538     if (error_code)
539       *error_code = base::File::FILE_ERROR_FAILED;
540     return NULL;
541   }
542
543   base::File::Error fs_error = base::File::FILE_OK;
544   FileSystemOperation* operation =
545       backend->CreateFileSystemOperation(url, this, &fs_error);
546
547   if (error_code)
548     *error_code = fs_error;
549   return operation;
550 }
551
552 FileSystemURL FileSystemContext::CrackFileSystemURL(
553     const FileSystemURL& url) const {
554   if (!url.is_valid())
555     return FileSystemURL();
556
557   // The returned value in case there is no crackers which can crack the url.
558   // This is valid situation for non isolated/external file systems.
559   FileSystemURL current = url;
560
561   // File system may be mounted multiple times (e.g., an isolated filesystem on
562   // top of an external filesystem). Hence cracking needs to be iterated.
563   for (;;) {
564     FileSystemURL cracked = current;
565     for (size_t i = 0; i < url_crackers_.size(); ++i) {
566       if (!url_crackers_[i]->HandlesFileSystemMountType(current.type()))
567         continue;
568       cracked = url_crackers_[i]->CrackFileSystemURL(current);
569       if (cracked.is_valid())
570         break;
571     }
572     if (cracked == current)
573       break;
574     current = cracked;
575   }
576   return current;
577 }
578
579 void FileSystemContext::RegisterBackend(FileSystemBackend* backend) {
580   const FileSystemType mount_types[] = {
581     kFileSystemTypeTemporary,
582     kFileSystemTypePersistent,
583     kFileSystemTypeIsolated,
584     kFileSystemTypeExternal,
585   };
586   // Register file system backends for public mount types.
587   for (size_t j = 0; j < ARRAYSIZE_UNSAFE(mount_types); ++j) {
588     if (backend->CanHandleType(mount_types[j])) {
589       const bool inserted = backend_map_.insert(
590           std::make_pair(mount_types[j], backend)).second;
591       DCHECK(inserted);
592     }
593   }
594   // Register file system backends for internal types.
595   for (int t = kFileSystemInternalTypeEnumStart + 1;
596        t < kFileSystemInternalTypeEnumEnd; ++t) {
597     FileSystemType type = static_cast<FileSystemType>(t);
598     if (backend->CanHandleType(type)) {
599       const bool inserted = backend_map_.insert(
600           std::make_pair(type, backend)).second;
601       DCHECK(inserted);
602     }
603   }
604 }
605
606 void FileSystemContext::DidOpenFileSystemForResolveURL(
607     const FileSystemURL& url,
608     const FileSystemContext::ResolveURLCallback& callback,
609     const GURL& filesystem_root,
610     const std::string& filesystem_name,
611     base::File::Error error) {
612   DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
613
614   if (error != base::File::FILE_OK) {
615     callback.Run(error, FileSystemInfo(), base::FilePath(),
616                  FileSystemContext::RESOLVED_ENTRY_NOT_FOUND);
617     return;
618   }
619
620   storage::FileSystemInfo info(
621       filesystem_name, filesystem_root, url.mount_type());
622
623   // Extract the virtual path not containing a filesystem type part from |url|.
624   base::FilePath parent = CrackURL(filesystem_root).virtual_path();
625   base::FilePath child = url.virtual_path();
626   base::FilePath path;
627
628   if (parent.empty()) {
629     path = child;
630   } else if (parent != child) {
631     bool result = parent.AppendRelativePath(child, &path);
632     DCHECK(result);
633   }
634
635   operation_runner()->GetMetadata(
636       url, base::Bind(&DidGetMetadataForResolveURL, path, callback, info));
637 }
638
639 }  // namespace storage