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 640