FileSystemDatabaseManagerVersion002.cpp (32618B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "FileSystemDatabaseManagerVersion002.h" 8 9 #include "ErrorList.h" 10 #include "FileSystemContentTypeGuess.h" 11 #include "FileSystemDataManager.h" 12 #include "FileSystemFileManager.h" 13 #include "FileSystemHashSource.h" 14 #include "FileSystemHashStorageFunction.h" 15 #include "FileSystemParentTypes.h" 16 #include "ResultStatement.h" 17 #include "StartedTransaction.h" 18 #include "mozStorageHelper.h" 19 #include "mozilla/dom/FileSystemDataManager.h" 20 #include "mozilla/dom/FileSystemHandle.h" 21 #include "mozilla/dom/FileSystemLog.h" 22 #include "mozilla/dom/FileSystemTypes.h" 23 #include "mozilla/dom/PFileSystemManager.h" 24 #include "mozilla/dom/QMResult.h" 25 #include "mozilla/dom/quota/Client.h" 26 #include "mozilla/dom/quota/QuotaCommon.h" 27 #include "mozilla/dom/quota/QuotaManager.h" 28 #include "mozilla/dom/quota/QuotaObject.h" 29 #include "mozilla/dom/quota/ResultExtensions.h" 30 31 namespace mozilla::dom::fs::data { 32 33 namespace { 34 35 Result<FileId, QMResult> GetFileId002(const FileSystemConnection& aConnection, 36 const EntryId& aEntryId) { 37 const nsLiteralCString fileIdQuery = 38 "SELECT fileId FROM MainFiles WHERE handle = :entryId ;"_ns; 39 40 QM_TRY_UNWRAP(ResultStatement stmt, 41 ResultStatement::Create(aConnection, fileIdQuery)); 42 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEntryId))); 43 QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep()); 44 45 if (!moreResults) { 46 return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); 47 } 48 49 QM_TRY_INSPECT(const FileId& fileId, stmt.GetFileIdByColumn(/* Column */ 0u)); 50 51 return fileId; 52 } 53 54 Result<bool, QMResult> DoesFileIdExist(const FileSystemConnection& aConnection, 55 const FileId& aFileId) { 56 MOZ_ASSERT(!aFileId.IsEmpty()); 57 58 const nsLiteralCString existsQuery = 59 "SELECT EXISTS " 60 "(SELECT 1 FROM FileIds WHERE fileId = :handle ) " 61 ";"_ns; 62 63 QM_TRY_RETURN( 64 ApplyEntryExistsQuery(aConnection, existsQuery, aFileId.Value())); 65 } 66 67 nsresult RehashFile(const FileSystemConnection& aConnection, 68 const EntryId& aEntryId, 69 const FileSystemChildMetadata& aNewDesignation, 70 const ContentType& aNewType) { 71 QM_TRY_INSPECT(const EntryId& newId, 72 FileSystemHashSource::GenerateHash( 73 aNewDesignation.parentId(), aNewDesignation.childName())); 74 75 // The destination should be empty at this point: either we exited because 76 // overwrite was not desired, or the existing content was removed. 77 const nsLiteralCString insertNewEntryQuery = 78 "INSERT INTO Entries ( handle, parent ) " 79 "VALUES ( :newId, :newParent ) " 80 ";"_ns; 81 82 const nsLiteralCString insertNewFileAndTypeQuery = 83 "INSERT INTO Files ( handle, type, name ) " 84 "VALUES ( :newId, :type, :newName ) " 85 ";"_ns; 86 87 const nsLiteralCString insertNewFileKeepTypeQuery = 88 "INSERT INTO Files ( handle, type, name ) " 89 "SELECT :newId, type, :newName FROM Files " 90 "WHERE handle = :oldId ;"_ns; 91 92 const auto& insertNewFileQuery = aNewType.IsVoid() 93 ? insertNewFileKeepTypeQuery 94 : insertNewFileAndTypeQuery; 95 96 const nsLiteralCString updateFileMappingsQuery = 97 "UPDATE FileIds SET handle = :newId WHERE handle = :handle ;"_ns; 98 99 const nsLiteralCString updateMainFilesQuery = 100 "UPDATE MainFiles SET handle = :newId WHERE handle = :handle ;"_ns; 101 102 const nsLiteralCString cleanupOldEntryQuery = 103 "DELETE FROM Entries WHERE handle = :handle ;"_ns; 104 105 QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(aConnection)); 106 107 { 108 QM_TRY_UNWRAP(ResultStatement stmt, 109 ResultStatement::Create(aConnection, insertNewEntryQuery)); 110 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("newId"_ns, newId))); 111 QM_TRY(QM_TO_RESULT( 112 stmt.BindEntryIdByName("newParent"_ns, aNewDesignation.parentId()))); 113 QM_TRY(QM_TO_RESULT(stmt.Execute())); 114 } 115 116 { 117 QM_TRY_UNWRAP(ResultStatement stmt, 118 ResultStatement::Create(aConnection, insertNewFileQuery)); 119 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("newId"_ns, newId))); 120 if (aNewType.IsVoid()) { 121 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("oldId"_ns, aEntryId))); 122 } else { 123 QM_TRY(QM_TO_RESULT(stmt.BindContentTypeByName("type"_ns, aNewType))); 124 } 125 QM_TRY(QM_TO_RESULT( 126 stmt.BindNameByName("newName"_ns, aNewDesignation.childName()))); 127 QM_TRY(QM_TO_RESULT(stmt.Execute())); 128 } 129 130 { 131 QM_TRY_UNWRAP( 132 ResultStatement stmt, 133 ResultStatement::Create(aConnection, updateFileMappingsQuery)); 134 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("newId"_ns, newId))); 135 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId))); 136 QM_TRY(QM_TO_RESULT(stmt.Execute())); 137 } 138 139 { 140 QM_TRY_UNWRAP(ResultStatement stmt, 141 ResultStatement::Create(aConnection, updateMainFilesQuery)); 142 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("newId"_ns, newId))); 143 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId))); 144 QM_TRY(QM_TO_RESULT(stmt.Execute())); 145 } 146 147 { 148 QM_TRY_UNWRAP(ResultStatement stmt, 149 ResultStatement::Create(aConnection, cleanupOldEntryQuery)); 150 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId))); 151 QM_TRY(QM_TO_RESULT(stmt.Execute())); 152 } 153 154 QM_TRY(QM_TO_RESULT(transaction.Commit())); 155 156 return NS_OK; 157 } 158 159 nsresult RehashDirectory(const FileSystemConnection& aConnection, 160 const EntryId& aEntryId, 161 const FileSystemChildMetadata& aNewDesignation) { 162 // This name won't match up with the entryId for the old path but 163 // it will be removed at the end 164 const nsLiteralCString updateNameQuery = 165 "UPDATE Directories SET name = :newName WHERE handle = :handle " 166 "; "_ns; 167 168 const nsLiteralCString calculateHashesQuery = 169 "CREATE TEMPORARY TABLE ParentChildHash AS " 170 "WITH RECURSIVE " 171 "rehashMap( depth, isFile, handle, parent, name, hash ) AS ( " 172 "SELECT 0, isFile, handle, parent, name, hashEntry( :newParent, name ) " 173 "FROM EntryNames WHERE handle = :handle UNION SELECT " 174 "1 + depth, EntryNames.isFile, EntryNames.handle, EntryNames.parent, " 175 "EntryNames.name, hashEntry( rehashMap.hash, EntryNames.name ) " 176 "FROM rehashMap, EntryNames WHERE rehashMap.handle = EntryNames.parent ) " 177 "SELECT depth, isFile, handle, parent, name, hash FROM rehashMap " 178 ";"_ns; 179 180 const nsLiteralCString createIndexByDepthQuery = 181 "CREATE INDEX indexOnDepth ON ParentChildHash ( depth ); "_ns; 182 183 // To avoid constraint violation, we insert new entries under the old parent. 184 // The destination should be empty at this point: either we exited because 185 // overwrite was not desired, or the existing content was removed. 186 const nsLiteralCString insertNewEntriesQuery = 187 "INSERT INTO Entries ( handle, parent ) " 188 "SELECT hash, :parent FROM ParentChildHash " 189 ";"_ns; 190 191 const nsLiteralCString insertNewDirectoriesQuery = 192 "INSERT INTO Directories ( handle, name ) " 193 "SELECT hash, name FROM ParentChildHash WHERE isFile = 0 " 194 "ORDER BY depth " 195 ";"_ns; 196 197 const nsLiteralCString insertNewFilesQuery = 198 "INSERT INTO Files ( handle, type, name ) " 199 "SELECT ParentChildHash.hash, Files.type, ParentChildHash.name " 200 "FROM ParentChildHash INNER JOIN Files USING (handle) " 201 "WHERE ParentChildHash.isFile = 1 " 202 ";"_ns; 203 204 const nsLiteralCString updateFileMappingsQuery = 205 "UPDATE FileIds " 206 "SET handle = CASE WHEN replacement.isMain IS NULL THEN NULL " 207 "ELSE replacement.hash END " 208 "FROM ( SELECT ParentChildHash.handle AS handle, " 209 "ParentChildHash.hash AS hash, " 210 "MainFiles.handle AS isMain " 211 "FROM ParentChildHash LEFT JOIN MainFiles " 212 "ON ParentChildHash.handle = MainFiles.handle ) AS replacement " 213 "WHERE FileIds.handle = replacement.handle " 214 ";"_ns; 215 216 const nsLiteralCString updateMainFilesQuery = 217 "UPDATE MainFiles SET handle = hash " 218 "FROM ( SELECT handle, hash FROM ParentChildHash ) AS replacement " 219 "WHERE MainFiles.handle = replacement.handle " 220 ";"_ns; 221 222 // Now fix the parents 223 const nsLiteralCString updateEntryMappingsQuery = 224 "UPDATE Entries SET parent = hash " 225 "FROM ( SELECT Lhs.hash AS handle, Rhs.hash AS hash, Lhs.depth AS depth " 226 "FROM ParentChildHash AS Lhs " 227 "INNER JOIN ParentChildHash AS Rhs " 228 "ON Rhs.handle = Lhs.parent ORDER BY depth ) AS replacement " 229 "WHERE Entries.handle = replacement.handle " 230 ";"_ns; 231 232 const nsLiteralCString cleanupOldEntriesQuery = 233 "DELETE FROM Entries WHERE handle = :handle " 234 ";"_ns; 235 236 // Index is automatically deleted 237 const nsLiteralCString cleanupTemporaries = 238 "DROP TABLE ParentChildHash " 239 ";"_ns; 240 241 nsCOMPtr<mozIStorageFunction> rehashFunction = 242 new data::FileSystemHashStorageFunction(); 243 QM_TRY(MOZ_TO_RESULT(aConnection->CreateFunction("hashEntry"_ns, 244 /* number of arguments */ 2, 245 rehashFunction))); 246 auto finallyRemoveFunction = MakeScopeExit([&aConnection]() { 247 QM_WARNONLY_TRY(MOZ_TO_RESULT(aConnection->RemoveFunction("hashEntry"_ns))); 248 }); 249 250 QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(aConnection)); 251 252 { 253 QM_TRY_UNWRAP(ResultStatement stmt, 254 ResultStatement::Create(aConnection, updateNameQuery)); 255 QM_TRY(QM_TO_RESULT( 256 stmt.BindNameByName("newName"_ns, aNewDesignation.childName()))); 257 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId))); 258 QM_TRY(QM_TO_RESULT(stmt.Execute())); 259 } 260 261 { 262 QM_TRY_UNWRAP(ResultStatement stmt, 263 ResultStatement::Create(aConnection, calculateHashesQuery)); 264 QM_TRY(QM_TO_RESULT( 265 stmt.BindEntryIdByName("newParent"_ns, aNewDesignation.parentId()))); 266 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId))); 267 QM_TRY(QM_TO_RESULT(stmt.Execute())); 268 } 269 270 QM_TRY(QM_TO_RESULT(aConnection->ExecuteSimpleSQL(createIndexByDepthQuery))); 271 272 { 273 QM_TRY_UNWRAP(ResultStatement stmt, 274 ResultStatement::Create(aConnection, insertNewEntriesQuery)); 275 QM_TRY(QM_TO_RESULT( 276 stmt.BindEntryIdByName("parent"_ns, aNewDesignation.parentId()))); 277 QM_TRY(QM_TO_RESULT(stmt.Execute())); 278 } 279 280 { 281 QM_TRY_UNWRAP( 282 ResultStatement stmt, 283 ResultStatement::Create(aConnection, insertNewDirectoriesQuery)); 284 QM_TRY(QM_TO_RESULT(stmt.Execute())); 285 } 286 287 { 288 QM_TRY_UNWRAP(ResultStatement stmt, 289 ResultStatement::Create(aConnection, insertNewFilesQuery)); 290 QM_TRY(QM_TO_RESULT(stmt.Execute())); 291 } 292 293 { 294 QM_TRY_UNWRAP( 295 ResultStatement stmt, 296 ResultStatement::Create(aConnection, updateFileMappingsQuery)); 297 QM_TRY(QM_TO_RESULT(stmt.Execute())); 298 } 299 300 { 301 QM_TRY_UNWRAP(ResultStatement stmt, 302 ResultStatement::Create(aConnection, updateMainFilesQuery)); 303 QM_TRY(QM_TO_RESULT(stmt.Execute())); 304 } 305 306 { 307 QM_TRY_UNWRAP( 308 ResultStatement stmt, 309 ResultStatement::Create(aConnection, updateEntryMappingsQuery)); 310 QM_TRY(QM_TO_RESULT(stmt.Execute())); 311 } 312 313 { 314 QM_TRY_UNWRAP(ResultStatement stmt, 315 ResultStatement::Create(aConnection, cleanupOldEntriesQuery)); 316 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId))); 317 QM_TRY(QM_TO_RESULT(stmt.Execute())); 318 } 319 320 { 321 QM_TRY_UNWRAP(ResultStatement stmt, 322 ResultStatement::Create(aConnection, cleanupTemporaries)); 323 QM_TRY(QM_TO_RESULT(stmt.Execute())); 324 } 325 326 QM_TRY(QM_TO_RESULT(transaction.Commit())); 327 328 return NS_OK; 329 } 330 331 /** 332 * @brief Each entryId is interpreted as a large integer, which is increased 333 * until an unused value is found. This process is in principle infallible. 334 * The files associated with a given path will form a cluster next to the 335 * entryId which could be used for recovery because our hash function is 336 * expected to distribute all clusters far from each other. 337 */ 338 Result<FileId, QMResult> GetNextFreeFileId( 339 const FileSystemConnection& aConnection, 340 const FileSystemFileManager& aFileManager, const EntryId& aEntryId) { 341 MOZ_ASSERT(32u == aEntryId.Length()); 342 343 auto DoesExist = [&aConnection, &aFileManager]( 344 const FileId& aId) -> Result<bool, QMResult> { 345 QM_TRY_INSPECT(const nsCOMPtr<nsIFile>& diskFile, 346 aFileManager.GetFile(aId)); 347 348 bool result = true; 349 QM_TRY(QM_TO_RESULT(diskFile->Exists(&result))); 350 if (result) { 351 return true; 352 } 353 354 QM_TRY_RETURN(DoesFileIdExist(aConnection, aId)); 355 }; 356 357 auto Next = [](FileId& aId) { 358 // Using a larger integer would make fileIds depend on platform endianness. 359 using IntegerType = uint8_t; 360 constexpr int32_t bufferSize = 32 / sizeof(IntegerType); 361 using IdBuffer = std::array<IntegerType, bufferSize>; 362 363 auto Increase = [](IdBuffer& aIn) { 364 for (int i = 0; i < bufferSize; ++i) { 365 if (1u + aIn[i] != 0u) { 366 ++aIn[i]; 367 return; 368 } 369 aIn[i] = 0u; 370 } 371 }; 372 373 DebugOnly<nsCString> original = aId.Value(); 374 Increase(*reinterpret_cast<IdBuffer*>(aId.mValue.BeginWriting())); 375 MOZ_ASSERT(!aId.Value().Equals(original)); 376 }; 377 378 FileId id = FileId(aEntryId); 379 380 while (true) { 381 QM_WARNONLY_TRY_UNWRAP(Maybe<bool> maybeExists, DoesExist(id)); 382 if (maybeExists.isSome() && !maybeExists.value()) { 383 return id; 384 } 385 386 Next(id); 387 } 388 } 389 390 Result<FileId, QMResult> AddNewFileId(const FileSystemConnection& aConnection, 391 const FileSystemFileManager& aFileManager, 392 const EntryId& aEntryId) { 393 QM_TRY_INSPECT(const FileId& nextFreeId, 394 GetNextFreeFileId(aConnection, aFileManager, aEntryId)); 395 396 const nsLiteralCString insertNewFileIdQuery = 397 "INSERT INTO FileIds ( fileId, handle ) " 398 "VALUES ( :fileId, :entryId ) " 399 "; "_ns; 400 401 QM_TRY_UNWRAP(ResultStatement stmt, 402 ResultStatement::Create(aConnection, insertNewFileIdQuery)); 403 QM_TRY(QM_TO_RESULT(stmt.BindFileIdByName("fileId"_ns, nextFreeId))); 404 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEntryId))); 405 406 QM_TRY(QM_TO_RESULT(stmt.Execute())); 407 408 return nextFreeId; 409 } 410 411 /** 412 * @brief Get recorded usage or zero if nothing was ever written to the file. 413 * Removing files is only allowed when there is no lock on the file, and their 414 * usage is either correctly recorded in the database during unlock, or nothing, 415 * or they remain in tracked state and the quota manager assumes their usage to 416 * be equal to the latest recorded value. In all cases, the latest recorded 417 * value (or nothing) is the correct amount of quota to be released. 418 */ 419 Result<Usage, QMResult> GetKnownUsage(const FileSystemConnection& aConnection, 420 const FileId& aFileId) { 421 const nsLiteralCString trackedUsageQuery = 422 "SELECT usage FROM Usages WHERE handle = :handle ;"_ns; 423 424 QM_TRY_UNWRAP(ResultStatement stmt, 425 ResultStatement::Create(aConnection, trackedUsageQuery)); 426 QM_TRY(QM_TO_RESULT(stmt.BindFileIdByName("handle"_ns, aFileId))); 427 428 QM_TRY_UNWRAP(const bool moreResults, stmt.ExecuteStep()); 429 if (!moreResults) { 430 return 0; 431 } 432 433 QM_TRY_RETURN(stmt.GetUsageByColumn(/* Column */ 0u)); 434 } 435 436 } // namespace 437 438 /* static */ 439 nsresult FileSystemDatabaseManagerVersion002::RescanTrackedUsages( 440 const FileSystemConnection& aConnection, 441 const quota::OriginMetadata& aOriginMetadata) { 442 return FileSystemDatabaseManagerVersion001::RescanTrackedUsages( 443 aConnection, aOriginMetadata); 444 } 445 446 /* static */ 447 Result<Usage, QMResult> FileSystemDatabaseManagerVersion002::GetFileUsage( 448 const FileSystemConnection& aConnection) { 449 return FileSystemDatabaseManagerVersion001::GetFileUsage(aConnection); 450 } 451 452 nsresult FileSystemDatabaseManagerVersion002::GetFile( 453 const EntryId& aEntryId, const FileId& aFileId, const FileMode& aMode, 454 ContentType& aType, TimeStamp& lastModifiedMilliSeconds, 455 nsTArray<Name>& aPath, nsCOMPtr<nsIFile>& aFile) const { 456 MOZ_ASSERT(!aFileId.IsEmpty()); 457 458 const FileSystemEntryPair endPoints(mRootEntry, aEntryId); 459 QM_TRY_UNWRAP(aPath, ResolveReversedPath(mConnection, endPoints)); 460 if (aPath.IsEmpty()) { 461 return NS_ERROR_DOM_NOT_FOUND_ERR; 462 } 463 464 QM_TRY(MOZ_TO_RESULT(GetFileAttributes(mConnection, aEntryId, aType))); 465 466 if (aMode == FileMode::SHARED_FROM_COPY) { 467 QM_WARNONLY_TRY_UNWRAP(Maybe<FileId> mainFileId, GetFileId(aEntryId)); 468 if (mainFileId) { 469 QM_TRY_UNWRAP(aFile, 470 mFileManager->CreateFileFrom(aFileId, mainFileId.value())); 471 int64_t fileSize = 0; 472 QM_TRY(QM_TO_RESULT(aFile->GetFileSize(&fileSize))); 473 UpdateCachedQuotaUsage(aFileId, 0, fileSize); 474 } else { 475 // LockShared/EnsureTemporaryFileId has provided a brand new fileId. 476 QM_TRY_UNWRAP(aFile, mFileManager->GetOrCreateFile(aFileId)); 477 } 478 } else { 479 MOZ_ASSERT(aMode == FileMode::EXCLUSIVE || 480 aMode == FileMode::SHARED_FROM_EMPTY); 481 482 QM_TRY_UNWRAP(aFile, mFileManager->GetOrCreateFile(aFileId)); 483 } 484 485 PRTime lastModTime = 0; 486 QM_TRY(MOZ_TO_RESULT(aFile->GetLastModifiedTime(&lastModTime))); 487 lastModifiedMilliSeconds = static_cast<TimeStamp>(lastModTime); 488 489 aPath.Reverse(); 490 491 return NS_OK; 492 } 493 494 Result<Ok, QMResult> 495 FileSystemDatabaseManagerVersion002::DeprecateSharedLocksForDirectory( 496 const FileSystemChildMetadata& aNewDesignation) { 497 // We will deprecate all shared locks whose entries (locators) 498 // are about to get overwritten at the destination. 499 // 500 // If a non-empty directory gets overwritten with a different 501 // directory layout, closing a WritableFileStream for an invalid path 502 // will quietly turn into abort. 503 // 504 // If a deprecated handle is explicitly closed and the associated 505 // path is valid, it will overwrite the file at the destination, 506 // unless the destination is exclusively locked. 507 // 508 // This behavior allows one to use WritableFileStreams for 509 // switching between contexts with last-writer-wins semantics. 510 // 511 // To match stricter application requirements, WebLocks can be used 512 // to disallow such use as necessary. 513 QM_TRY_INSPECT(const auto& destinationId, GetEntryId(aNewDesignation)); 514 QM_TRY_INSPECT(const auto& descendants, 515 FindFileEntriesUnderDirectory(destinationId)); 516 for (const auto& entryFile : descendants) { 517 mDataManager->DeprecateSharedLocks(entryFile.first, entryFile.second); 518 } 519 520 return Ok{}; 521 } 522 523 Result<EntryId, QMResult> FileSystemDatabaseManagerVersion002::RenameEntry( 524 const FileSystemEntryMetadata& aHandle, const Name& aNewName) { 525 MOZ_ASSERT(!aNewName.IsEmpty()); 526 527 const auto& entryId = aHandle.entryId(); 528 MOZ_ASSERT(!entryId.IsEmpty()); 529 530 // Can't rename root 531 if (mRootEntry == entryId) { 532 return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); 533 } 534 535 // Verify the source exists 536 QM_TRY_UNWRAP(bool isFile, IsFile(mConnection, entryId), 537 Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR))); 538 539 // Are we actually renaming? 540 if (aHandle.entryName() == aNewName) { 541 return entryId; 542 } 543 544 QM_TRY_UNWRAP(EntryId parentId, FindParent(mConnection, entryId)); 545 FileSystemChildMetadata newDesignation(parentId, aNewName); 546 547 if (isFile) { 548 // This will fail if there there is a locked file at the destination. 549 QM_TRY(QM_TO_RESULT(PrepareRenameEntry(mConnection, mDataManager, aHandle, 550 aNewName, isFile))); 551 552 const ContentType type = DetermineContentType(aNewName); 553 QM_TRY( 554 QM_TO_RESULT(RehashFile(mConnection, entryId, newDesignation, type))); 555 } else { 556 QM_TRY(DeprecateSharedLocksForDirectory(newDesignation)); 557 558 // We remove all not deprecated items from the destination. 559 // This will fail if the destination contains locked files. 560 QM_TRY(QM_TO_RESULT(PrepareRenameEntry(mConnection, mDataManager, aHandle, 561 aNewName, isFile))); 562 563 QM_TRY(QM_TO_RESULT(RehashDirectory(mConnection, entryId, newDesignation))); 564 } 565 566 QM_TRY_UNWRAP(DebugOnly<EntryId> dbId, 567 FindEntryId(mConnection, newDesignation, isFile)); 568 QM_TRY_UNWRAP(EntryId generated, 569 FileSystemHashSource::GenerateHash(parentId, aNewName)); 570 MOZ_ASSERT(static_cast<EntryId&>(dbId).Equals(generated)); 571 572 return generated; 573 } 574 575 Result<EntryId, QMResult> FileSystemDatabaseManagerVersion002::MoveEntry( 576 const FileSystemEntryMetadata& aHandle, 577 const FileSystemChildMetadata& aNewDesignation) { 578 MOZ_ASSERT(!aHandle.entryId().IsEmpty()); 579 580 const auto& entryId = aHandle.entryId(); 581 582 if (mRootEntry == entryId) { 583 return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); 584 } 585 586 // Verify the source exists 587 QM_TRY_UNWRAP(bool isFile, IsFile(mConnection, entryId), 588 Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR))); 589 590 // If the rename doesn't change the name or directory, just return success. 591 // XXX Needs to be added to the spec 592 QM_WARNONLY_TRY_UNWRAP(Maybe<bool> maybeSame, 593 IsSame(mConnection, aHandle, aNewDesignation, isFile)); 594 if (maybeSame && maybeSame.value()) { 595 return entryId; 596 } 597 598 if (isFile) { 599 QM_TRY(QM_TO_RESULT(PrepareMoveEntry(mConnection, mDataManager, aHandle, 600 aNewDesignation, isFile))); 601 602 const ContentType type = DetermineContentType(aNewDesignation.childName()); 603 QM_TRY( 604 QM_TO_RESULT(RehashFile(mConnection, entryId, aNewDesignation, type))); 605 } else { 606 QM_TRY(DeprecateSharedLocksForDirectory(aNewDesignation)); 607 608 QM_TRY(QM_TO_RESULT(PrepareMoveEntry(mConnection, mDataManager, aHandle, 609 aNewDesignation, isFile))); 610 611 QM_TRY( 612 QM_TO_RESULT(RehashDirectory(mConnection, entryId, aNewDesignation))); 613 } 614 615 QM_TRY_UNWRAP(DebugOnly<EntryId> dbId, 616 FindEntryId(mConnection, aNewDesignation, isFile)); 617 QM_TRY_UNWRAP(EntryId generated, 618 FileSystemHashSource::GenerateHash( 619 aNewDesignation.parentId(), aNewDesignation.childName())); 620 MOZ_ASSERT(static_cast<EntryId&>(dbId).Equals(generated)); 621 622 return generated; 623 } 624 625 Result<EntryId, QMResult> FileSystemDatabaseManagerVersion002::GetEntryId( 626 const FileSystemChildMetadata& aHandle) const { 627 return fs::data::GetEntryHandle(aHandle); 628 } 629 630 Result<EntryId, QMResult> FileSystemDatabaseManagerVersion002::GetEntryId( 631 const FileId& aFileId) const { 632 const nsLiteralCString getEntryIdQuery = 633 "SELECT handle FROM FileIds WHERE fileId = :fileId ;"_ns; 634 635 QM_TRY_UNWRAP(ResultStatement stmt, 636 ResultStatement::Create(mConnection, getEntryIdQuery)); 637 QM_TRY(QM_TO_RESULT(stmt.BindFileIdByName("fileId"_ns, aFileId))); 638 QM_TRY_UNWRAP(bool hasEntries, stmt.ExecuteStep()); 639 640 if (!hasEntries || stmt.IsNullByColumn(/* Column */ 0u)) { 641 return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); 642 } 643 644 QM_TRY_RETURN(stmt.GetEntryIdByColumn(/* Column */ 0u)); 645 } 646 647 Result<FileId, QMResult> FileSystemDatabaseManagerVersion002::EnsureFileId( 648 const EntryId& aEntryId) { 649 QM_TRY_UNWRAP(const bool exists, DoesFileExist(aEntryId)); 650 if (!exists) { 651 return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); 652 } 653 654 QM_TRY_UNWRAP(Maybe<FileId> maybeMainFileId, 655 QM_OR_ELSE_LOG_VERBOSE_IF( 656 // Expression. 657 GetFileId(aEntryId).map([](auto mainFileId) { 658 return Some(std::move(mainFileId)); 659 }), 660 // Predicate. 661 IsSpecificError<NS_ERROR_DOM_NOT_FOUND_ERR>, 662 // Fallback. 663 ([](const auto&) -> Result<Maybe<FileId>, QMResult> { 664 return Maybe<FileId>{}; 665 }))); 666 667 if (maybeMainFileId) { 668 return *maybeMainFileId; 669 } 670 671 QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(mConnection)); 672 673 QM_TRY_INSPECT(const FileId& fileId, 674 AddNewFileId(mConnection, *mFileManager, aEntryId)); 675 676 QM_TRY(QM_TO_RESULT(MergeFileId(aEntryId, fileId, /* aAbort */ false))); 677 678 QM_TRY(QM_TO_RESULT(transaction.Commit())); 679 680 return fileId; 681 } 682 683 Result<FileId, QMResult> 684 FileSystemDatabaseManagerVersion002::EnsureTemporaryFileId( 685 const EntryId& aEntryId) { 686 QM_TRY_UNWRAP(const bool exists, DoesFileExist(aEntryId)); 687 if (!exists) { 688 return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); 689 } 690 691 QM_TRY_RETURN(AddNewFileId(mConnection, *mFileManager, aEntryId)); 692 } 693 694 Result<FileId, QMResult> FileSystemDatabaseManagerVersion002::GetFileId( 695 const EntryId& aEntryId) const { 696 MOZ_ASSERT(mConnection); 697 return data::GetFileId002(mConnection, aEntryId); 698 } 699 700 nsresult FileSystemDatabaseManagerVersion002::MergeFileId( 701 const EntryId& aEntryId, const FileId& aFileId, bool aAbort) { 702 MOZ_ASSERT(mConnection); 703 704 auto doCleanUp = [this](const FileId& aCleanable) -> nsresult { 705 // We need to clean up the old main file. 706 QM_TRY_UNWRAP(Usage usage, 707 GetKnownUsage(mConnection, aCleanable).mapErr(toNSResult)); 708 709 QM_WARNONLY_TRY_UNWRAP(Maybe<Usage> removedUsage, 710 mFileManager->RemoveFile(aCleanable)); 711 712 if (removedUsage) { 713 // Removal of file data was ok, update the related fileId and usage 714 QM_WARNONLY_TRY(QM_TO_RESULT(RemoveFileId(aCleanable))); 715 716 if (usage > 0) { // Performance! 717 DecreaseCachedQuotaUsage(usage); 718 } 719 720 // We only check the most common case. This can fail spuriously if an 721 // external application writes to the file, or OS reports zero size due to 722 // corruption. 723 MOZ_ASSERT_IF(0 == mFilesOfUnknownUsage, usage == removedUsage.value()); 724 725 return NS_OK; 726 } 727 728 // Removal failed 729 const nsLiteralCString forgetCleanable = 730 "UPDATE FileIds SET handle = NULL WHERE fileId = :fileId ; "_ns; 731 732 QM_TRY_UNWRAP(ResultStatement stmt, 733 ResultStatement::Create(mConnection, forgetCleanable) 734 .mapErr(toNSResult)); 735 QM_TRY(MOZ_TO_RESULT(stmt.BindFileIdByName("fileId"_ns, aCleanable))); 736 QM_TRY(MOZ_TO_RESULT(stmt.Execute())); 737 738 TryRemoveDuringIdleMaintenance({aCleanable}); 739 740 return NS_OK; 741 }; 742 743 if (aAbort) { 744 QM_TRY(MOZ_TO_RESULT(doCleanUp(aFileId))); 745 746 return NS_OK; 747 } 748 749 QM_TRY_UNWRAP( 750 Maybe<FileId> maybeOldFileId, 751 QM_OR_ELSE_LOG_VERBOSE_IF( 752 // Expression. 753 GetFileId(aEntryId) 754 .map([](auto oldFileId) { return Some(std::move(oldFileId)); }) 755 .mapErr(toNSResult), 756 // Predicate. 757 IsSpecificError<NS_ERROR_DOM_NOT_FOUND_ERR>, 758 // Fallback. 759 ErrToDefaultOk<Maybe<FileId>>)); 760 761 if (maybeOldFileId && *maybeOldFileId == aFileId) { 762 return NS_OK; // Nothing to do 763 } 764 765 // Main file changed 766 const nsLiteralCString flagAsMainFileQuery = 767 "INSERT INTO MainFiles ( handle, fileId ) " 768 "VALUES ( :entryId, :fileId ) " 769 "ON CONFLICT (handle) " 770 "DO UPDATE SET fileId = excluded.fileId " 771 "; "_ns; 772 773 QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(mConnection)); 774 775 QM_TRY_UNWRAP(ResultStatement stmt, 776 ResultStatement::Create(mConnection, flagAsMainFileQuery) 777 .mapErr(toNSResult)); 778 QM_TRY(MOZ_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEntryId))); 779 QM_TRY(MOZ_TO_RESULT(stmt.BindFileIdByName("fileId"_ns, aFileId))); 780 781 QM_TRY(MOZ_TO_RESULT(stmt.Execute())); 782 783 if (!maybeOldFileId) { 784 // We successfully added a new main file and there is nothing to clean up. 785 QM_TRY(MOZ_TO_RESULT(transaction.Commit())); 786 787 return NS_OK; 788 } 789 790 MOZ_ASSERT(maybeOldFileId); 791 MOZ_ASSERT(*maybeOldFileId != aFileId); 792 793 QM_TRY(MOZ_TO_RESULT(doCleanUp(*maybeOldFileId))); 794 795 // If the old fileId and usage were not deleted, main file update fails. 796 QM_TRY(MOZ_TO_RESULT(transaction.Commit())); 797 798 return NS_OK; 799 } 800 801 Result<bool, QMResult> FileSystemDatabaseManagerVersion002::DoesFileIdExist( 802 const FileId& aFileId) const { 803 QM_TRY_RETURN(data::DoesFileIdExist(mConnection, aFileId)); 804 } 805 806 nsresult FileSystemDatabaseManagerVersion002::RemoveFileId( 807 const FileId& aFileId) { 808 const nsLiteralCString removeFileIdQuery = 809 "DELETE FROM FileIds " 810 "WHERE fileId = :fileId " 811 ";"_ns; 812 813 QM_TRY_UNWRAP(ResultStatement stmt, 814 ResultStatement::Create(mConnection, removeFileIdQuery) 815 .mapErr(toNSResult)); 816 817 QM_TRY(MOZ_TO_RESULT(stmt.BindEntryIdByName("fileId"_ns, aFileId.Value()))); 818 819 return stmt.Execute(); 820 } 821 822 /** 823 * Technically, this function may return exclusively locked files. 824 * It is meant to assist move and remove operations after it has been 825 * already checked that the directory does not contain exclusively locked files. 826 * Perhaps it these checks could be unified one day? 827 * Until then, since this is not expected in those use cases, there is a 828 * debug assert to alert about the presence of exclusive locks. 829 */ 830 Result<std::pair<nsTArray<FileId>, Usage>, QMResult> 831 FileSystemDatabaseManagerVersion002::FindFilesWithoutDeprecatedLocksUnderEntry( 832 const EntryId& aEntryId) const { 833 const nsLiteralCString descendantsQuery = 834 "WITH RECURSIVE traceChildren(handle, parent) AS ( " 835 "SELECT handle, parent FROM Entries WHERE handle = :handle " 836 "UNION " 837 "SELECT Entries.handle, Entries.parent FROM traceChildren, Entries " 838 "WHERE traceChildren.handle = Entries.parent ) " 839 "SELECT FileIds.handle, FileIds.fileId, Usages.usage " 840 "FROM traceChildren INNER JOIN FileIds USING (handle) " 841 "INNER JOIN Usages ON Usages.handle = FileIds.fileId " 842 ";"_ns; 843 844 nsTArray<FileId> descendants; 845 Usage usage{0}; 846 { 847 QM_TRY_UNWRAP(ResultStatement stmt, 848 ResultStatement::Create(mConnection, descendantsQuery)); 849 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId))); 850 851 while (true) { 852 QM_TRY_INSPECT(const bool& moreResults, stmt.ExecuteStep()); 853 if (!moreResults) { 854 break; 855 } 856 857 QM_TRY_INSPECT(const EntryId& entryId, 858 stmt.GetEntryIdByColumn(/* Column */ 0u)); 859 QM_TRY_INSPECT(const FileId& fileId, 860 stmt.GetFileIdByColumn(/* Column */ 1u)); 861 if (!mDataManager->IsLockedWithDeprecatedSharedLock(entryId, fileId)) { 862 QM_TRY_INSPECT(const FileId& fileId, 863 stmt.GetFileIdByColumn(/* Column */ 1u)); 864 descendants.AppendElement(fileId); 865 866 QM_TRY_INSPECT(const Usage& fileUsage, 867 stmt.GetUsageByColumn(/* Column */ 2u)); 868 usage += fileUsage; 869 } 870 } 871 } 872 873 return std::make_pair(std::move(descendants), usage); 874 } 875 876 Result<nsTArray<std::pair<EntryId, FileId>>, QMResult> 877 FileSystemDatabaseManagerVersion002::FindFileEntriesUnderDirectory( 878 const EntryId& aEntryId) const { 879 const nsLiteralCString descendantsQuery = 880 "WITH RECURSIVE traceChildren(handle, parent) AS ( " 881 "SELECT handle, parent FROM Entries WHERE handle = :handle " 882 "UNION " 883 "SELECT Entries.handle, Entries.parent FROM traceChildren, Entries " 884 "WHERE traceChildren.handle = Entries.parent ) " 885 "SELECT FileIds.handle, FileIds.fileId " 886 "FROM traceChildren INNER JOIN FileIds USING(handle) " 887 ";"_ns; 888 889 nsTArray<std::pair<EntryId, FileId>> descendants; 890 { 891 QM_TRY_UNWRAP(ResultStatement stmt, 892 ResultStatement::Create(mConnection, descendantsQuery)); 893 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId))); 894 895 while (true) { 896 QM_TRY_INSPECT(const bool& moreResults, stmt.ExecuteStep()); 897 if (!moreResults) { 898 break; 899 } 900 901 QM_TRY_UNWRAP(EntryId entryId, stmt.GetEntryIdByColumn(/* Column */ 0u)); 902 QM_TRY_UNWRAP(FileId fileId, stmt.GetFileIdByColumn(/* Column */ 1u)); 903 descendants.AppendElement( 904 std::make_pair(std::move(entryId), std::move(fileId))); 905 } 906 } 907 908 return descendants; 909 } 910 911 } // namespace mozilla::dom::fs::data