FileSystemDatabaseManagerVersion001.cpp (55030B)
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 "FileSystemDatabaseManagerVersion001.h" 8 9 #include "ErrorList.h" 10 #include "FileSystemContentTypeGuess.h" 11 #include "FileSystemDataManager.h" 12 #include "FileSystemFileManager.h" 13 #include "FileSystemParentTypes.h" 14 #include "ResultStatement.h" 15 #include "StartedTransaction.h" 16 #include "mozStorageHelper.h" 17 #include "mozilla/CheckedInt.h" 18 #include "mozilla/dom/FileSystemDataManager.h" 19 #include "mozilla/dom/FileSystemHandle.h" 20 #include "mozilla/dom/FileSystemLog.h" 21 #include "mozilla/dom/FileSystemTypes.h" 22 #include "mozilla/dom/PFileSystemManager.h" 23 #include "mozilla/dom/quota/Client.h" 24 #include "mozilla/dom/quota/QuotaCommon.h" 25 #include "mozilla/dom/quota/QuotaManager.h" 26 #include "mozilla/dom/quota/QuotaObject.h" 27 #include "mozilla/dom/quota/ResultExtensions.h" 28 #include "nsString.h" 29 30 namespace mozilla::dom { 31 32 using FileSystemEntries = nsTArray<fs::FileSystemEntryMetadata>; 33 34 namespace fs::data { 35 36 namespace { 37 38 constexpr const nsLiteralCString gDescendantsQuery = 39 "WITH RECURSIVE traceChildren(handle, parent) AS ( " 40 "SELECT handle, parent " 41 "FROM Entries " 42 "WHERE handle=:handle " 43 "UNION " 44 "SELECT Entries.handle, Entries.parent FROM traceChildren, Entries " 45 "WHERE traceChildren.handle=Entries.parent ) " 46 "SELECT handle, Usages.usage " 47 "FROM traceChildren INNER JOIN Files USING (handle) " 48 "INNER JOIN Usages USING (handle) " 49 ";"_ns; 50 51 Result<bool, QMResult> DoesFileExist(const FileSystemConnection& aConnection, 52 const EntryId& aEntryId) { 53 MOZ_ASSERT(!aEntryId.IsEmpty()); 54 55 const nsCString existsQuery = 56 "SELECT EXISTS " 57 "(SELECT 1 FROM Files WHERE handle = :handle ) " 58 ";"_ns; 59 60 QM_TRY_RETURN(ApplyEntryExistsQuery(aConnection, existsQuery, aEntryId)); 61 } 62 63 Result<bool, QMResult> IsDirectoryEmpty(const FileSystemConnection& mConnection, 64 const EntryId& aEntryId) { 65 const nsLiteralCString isDirEmptyQuery = 66 "SELECT EXISTS (" 67 "SELECT 1 FROM Entries WHERE parent = :parent " 68 ");"_ns; 69 70 QM_TRY_UNWRAP(ResultStatement stmt, 71 ResultStatement::Create(mConnection, isDirEmptyQuery)); 72 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aEntryId))); 73 QM_TRY_UNWRAP(bool childrenExist, stmt.YesOrNoQuery()); 74 75 return !childrenExist; 76 } 77 78 Result<bool, QMResult> DoesDirectoryExist( 79 const FileSystemConnection& mConnection, 80 const FileSystemChildMetadata& aHandle) { 81 MOZ_ASSERT(!aHandle.parentId().IsEmpty()); 82 83 const nsCString existsQuery = 84 "SELECT EXISTS " 85 "(SELECT 1 FROM Directories INNER JOIN Entries USING (handle) " 86 "WHERE Directories.name = :name AND Entries.parent = :parent ) " 87 ";"_ns; 88 89 QM_TRY_RETURN(ApplyEntryExistsQuery(mConnection, existsQuery, aHandle)); 90 } 91 92 Result<bool, QMResult> DoesDirectoryExist( 93 const FileSystemConnection& mConnection, const EntryId& aEntry) { 94 MOZ_ASSERT(!aEntry.IsEmpty()); 95 96 const nsCString existsQuery = 97 "SELECT EXISTS " 98 "(SELECT 1 FROM Directories WHERE handle = :handle ) " 99 ";"_ns; 100 101 QM_TRY_RETURN(ApplyEntryExistsQuery(mConnection, existsQuery, aEntry)); 102 } 103 104 Result<bool, QMResult> IsAncestor(const FileSystemConnection& aConnection, 105 const FileSystemEntryPair& aEndpoints) { 106 const nsCString pathQuery = 107 "WITH RECURSIVE followPath(handle, parent) AS ( " 108 "SELECT handle, parent " 109 "FROM Entries " 110 "WHERE handle=:entryId " 111 "UNION " 112 "SELECT Entries.handle, Entries.parent FROM followPath, Entries " 113 "WHERE followPath.parent=Entries.handle ) " 114 "SELECT EXISTS " 115 "(SELECT 1 FROM followPath " 116 "WHERE handle=:possibleAncestor ) " 117 ";"_ns; 118 119 QM_TRY_UNWRAP(ResultStatement stmt, 120 ResultStatement::Create(aConnection, pathQuery)); 121 QM_TRY( 122 QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEndpoints.childId()))); 123 QM_TRY(QM_TO_RESULT( 124 stmt.BindEntryIdByName("possibleAncestor"_ns, aEndpoints.parentId()))); 125 126 return stmt.YesOrNoQuery(); 127 } 128 129 Result<bool, QMResult> DoesFileExist(const FileSystemConnection& aConnection, 130 const FileSystemChildMetadata& aHandle) { 131 MOZ_ASSERT(!aHandle.parentId().IsEmpty()); 132 133 const nsCString existsQuery = 134 "SELECT EXISTS " 135 "(SELECT 1 FROM Files INNER JOIN Entries USING (handle) " 136 "WHERE Files.name = :name AND Entries.parent = :parent ) " 137 ";"_ns; 138 139 QM_TRY_RETURN(ApplyEntryExistsQuery(aConnection, existsQuery, aHandle)); 140 } 141 142 nsresult GetEntries(const FileSystemConnection& aConnection, 143 const nsACString& aUnboundStmt, const EntryId& aParent, 144 PageNumber aPage, bool aDirectory, 145 FileSystemEntries& aEntries) { 146 // The entries inside a directory are sent to the child process in batches 147 // of pageSize items. Large value ensures that iteration is less often delayed 148 // by IPC messaging and querying the database. 149 // TODO: The current value 1024 is not optimized. 150 // TODO: Value "pageSize" is shared with the iterator implementation and 151 // should be defined in a common place. 152 const int32_t pageSize = 1024; 153 154 QM_TRY_UNWRAP(bool exists, DoesDirectoryExist(aConnection, aParent)); 155 if (!exists) { 156 return NS_ERROR_DOM_NOT_FOUND_ERR; 157 } 158 159 QM_TRY_UNWRAP(ResultStatement stmt, 160 ResultStatement::Create(aConnection, aUnboundStmt)); 161 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aParent))); 162 QM_TRY(QM_TO_RESULT(stmt.BindPageNumberByName("pageSize"_ns, pageSize))); 163 QM_TRY(QM_TO_RESULT( 164 stmt.BindPageNumberByName("pageOffset"_ns, aPage * pageSize))); 165 166 QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep()); 167 168 while (moreResults) { 169 QM_TRY_UNWRAP(EntryId entryId, stmt.GetEntryIdByColumn(/* Column */ 0u)); 170 QM_TRY_UNWRAP(Name entryName, stmt.GetNameByColumn(/* Column */ 1u)); 171 172 FileSystemEntryMetadata metadata(entryId, entryName, aDirectory); 173 aEntries.AppendElement(metadata); 174 175 QM_TRY_UNWRAP(moreResults, stmt.ExecuteStep()); 176 } 177 178 return NS_OK; 179 } 180 181 Result<EntryId, QMResult> GetUniqueEntryId( 182 const FileSystemConnection& aConnection, 183 const FileSystemChildMetadata& aHandle) { 184 const nsCString existsQuery = 185 "SELECT EXISTS " 186 "(SELECT 1 FROM Entries " 187 "WHERE handle = :handle )" 188 ";"_ns; 189 190 FileSystemChildMetadata generatorInput = aHandle; 191 192 const size_t maxRounds = 1024u; 193 194 for (size_t hangGuard = 0u; hangGuard < maxRounds; ++hangGuard) { 195 QM_TRY_UNWRAP(EntryId entryId, fs::data::GetEntryHandle(generatorInput)); 196 QM_TRY_UNWRAP(ResultStatement stmt, 197 ResultStatement::Create(aConnection, existsQuery)); 198 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId))); 199 200 QM_TRY_UNWRAP(bool alreadyInUse, stmt.YesOrNoQuery()); 201 202 if (!alreadyInUse) { 203 return entryId; 204 } 205 206 generatorInput.parentId() = entryId; 207 } 208 209 return Err(QMResult(NS_ERROR_UNEXPECTED)); 210 } 211 212 nsresult PerformRename(const FileSystemConnection& aConnection, 213 const FileSystemEntryMetadata& aHandle, 214 const Name& aNewName, const ContentType& aNewType, 215 const nsLiteralCString& aNameUpdateQuery) { 216 MOZ_ASSERT(!aHandle.entryId().IsEmpty()); 217 MOZ_ASSERT(IsValidName(aHandle.entryName())); 218 219 // same-name is checked in RenameEntry() 220 if (!IsValidName(aNewName)) { 221 return NS_ERROR_DOM_TYPE_MISMATCH_ERR; 222 } 223 224 // TODO: This should fail when handle doesn't exist - the 225 // explicit file or directory existence queries are redundant 226 QM_TRY_UNWRAP(ResultStatement stmt, 227 ResultStatement::Create(aConnection, aNameUpdateQuery) 228 .mapErr(toNSResult)); 229 if (!aNewType.IsVoid()) { 230 QM_TRY(MOZ_TO_RESULT(stmt.BindContentTypeByName("type"_ns, aNewType))); 231 } 232 QM_TRY(MOZ_TO_RESULT(stmt.BindNameByName("name"_ns, aNewName))); 233 QM_TRY(MOZ_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aHandle.entryId()))); 234 QM_TRY(MOZ_TO_RESULT(stmt.Execute())); 235 236 return NS_OK; 237 } 238 239 nsresult PerformRenameDirectory(const FileSystemConnection& aConnection, 240 const FileSystemEntryMetadata& aHandle, 241 const Name& aNewName) { 242 const nsLiteralCString updateDirectoryNameQuery = 243 "UPDATE Directories " 244 "SET name = :name " 245 "WHERE handle = :handle " 246 ";"_ns; 247 248 return PerformRename(aConnection, aHandle, aNewName, VoidCString(), 249 updateDirectoryNameQuery); 250 } 251 252 nsresult PerformRenameFile(const FileSystemConnection& aConnection, 253 const FileSystemEntryMetadata& aHandle, 254 const Name& aNewName, const ContentType& aNewType) { 255 const nsLiteralCString updateFileTypeAndNameQuery = 256 "UPDATE Files SET type = :type, name = :name " 257 "WHERE handle = :handle ;"_ns; 258 259 const nsLiteralCString updateFileNameQuery = 260 "UPDATE Files SET name = :name WHERE handle = :handle ;"_ns; 261 262 if (aNewType.IsVoid()) { 263 return PerformRename(aConnection, aHandle, aNewName, aNewType, 264 updateFileNameQuery); 265 } 266 267 return PerformRename(aConnection, aHandle, aNewName, aNewType, 268 updateFileTypeAndNameQuery); 269 } 270 271 template <class HandlerType> 272 nsresult SetUsageTrackingImpl(const FileSystemConnection& aConnection, 273 const FileId& aFileId, bool aTracked, 274 HandlerType&& aOnMissingFile) { 275 const nsLiteralCString setTrackedQuery = 276 "INSERT INTO Usages " 277 "( handle, tracked ) " 278 "VALUES " 279 "( :handle, :tracked ) " 280 "ON CONFLICT(handle) DO " 281 "UPDATE SET tracked = excluded.tracked " 282 ";"_ns; 283 284 const nsresult customReturnValue = 285 aTracked ? NS_ERROR_DOM_NOT_FOUND_ERR : NS_OK; 286 287 QM_TRY_UNWRAP(ResultStatement stmt, 288 ResultStatement::Create(aConnection, setTrackedQuery)); 289 QM_TRY(MOZ_TO_RESULT(stmt.BindFileIdByName("handle"_ns, aFileId))); 290 QM_TRY(MOZ_TO_RESULT(stmt.BindBooleanByName("tracked"_ns, aTracked))); 291 QM_TRY(MOZ_TO_RESULT(stmt.Execute()), customReturnValue, 292 std::forward<HandlerType>(aOnMissingFile)); 293 294 return NS_OK; 295 } 296 297 Result<nsTArray<FileId>, QMResult> GetTrackedFiles( 298 const FileSystemConnection& aConnection) { 299 // The same query works for both 001 and 002 schemas because handle is 300 // an entry id and later on a file id, respectively. 301 static const nsLiteralCString getTrackedFilesQuery = 302 "SELECT handle FROM Usages WHERE tracked = TRUE;"_ns; 303 nsTArray<FileId> trackedFiles; 304 305 QM_TRY_UNWRAP(ResultStatement stmt, 306 ResultStatement::Create(aConnection, getTrackedFilesQuery)); 307 QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep()); 308 309 while (moreResults) { 310 QM_TRY_UNWRAP(FileId fileId, stmt.GetFileIdByColumn(/* Column */ 0u)); 311 312 trackedFiles.AppendElement(fileId); // TODO: fallible? 313 314 QM_TRY_UNWRAP(moreResults, stmt.ExecuteStep()); 315 } 316 317 return trackedFiles; 318 } 319 320 /** This handles the file not found error by assigning 0 usage to the dangling 321 * handle and puts the handle to a non-tracked state. Otherwise, when the 322 * file or database cannot be reached, the file remains in the tracked state. 323 */ 324 template <class QuotaCacheUpdate> 325 nsresult UpdateUsageForFileEntry(const FileSystemConnection& aConnection, 326 const FileSystemFileManager& aFileManager, 327 const FileId& aFileId, 328 const nsLiteralCString& aUpdateQuery, 329 QuotaCacheUpdate&& aUpdateCache) { 330 QM_TRY_INSPECT(const auto& fileHandle, aFileManager.GetFile(aFileId)); 331 332 // A file could have changed in a way which doesn't allow to read its size. 333 QM_TRY_UNWRAP( 334 const Usage fileSize, 335 QM_OR_ELSE_WARN_IF( 336 // Expression. 337 MOZ_TO_RESULT_INVOKE_MEMBER(fileHandle, GetFileSize), 338 // Predicate. 339 ([](const nsresult rv) { return rv == NS_ERROR_FILE_NOT_FOUND; }), 340 // Fallback. If the file does no longer exist, treat it as 0-sized. 341 ErrToDefaultOk<Usage>)); 342 343 QM_TRY(MOZ_TO_RESULT(aUpdateCache(fileSize))); 344 345 // No transaction as one statement succeeds or fails atomically 346 QM_TRY_UNWRAP(ResultStatement stmt, 347 ResultStatement::Create(aConnection, aUpdateQuery)); 348 349 QM_TRY(MOZ_TO_RESULT(stmt.BindFileIdByName("handle"_ns, aFileId))); 350 351 QM_TRY(MOZ_TO_RESULT(stmt.BindUsageByName("usage"_ns, fileSize))); 352 353 QM_TRY(MOZ_TO_RESULT(stmt.Execute())); 354 355 return NS_OK; 356 } 357 358 nsresult UpdateUsageUnsetTracked(const FileSystemConnection& aConnection, 359 const FileSystemFileManager& aFileManager, 360 const FileId& aFileId) { 361 static const nsLiteralCString updateUsagesUnsetTrackedQuery = 362 "UPDATE Usages SET usage = :usage, tracked = FALSE " 363 "WHERE handle = :handle;"_ns; 364 365 auto noCacheUpdateNeeded = [](auto) { return NS_OK; }; 366 367 return UpdateUsageForFileEntry(aConnection, aFileManager, aFileId, 368 updateUsagesUnsetTrackedQuery, 369 std::move(noCacheUpdateNeeded)); 370 } 371 372 /** 373 * @brief Get the recorded usage only if the file is in tracked state. 374 * During origin initialization, if the usage on disk is unreadable, the latest 375 * recorded usage is reported to the quota manager for the tracked files. 376 * To allow writing, we attempt to update the real usage with one database and 377 * one file size query. 378 */ 379 Result<Maybe<Usage>, QMResult> GetMaybeTrackedUsage( 380 const FileSystemConnection& aConnection, const FileId& aFileId) { 381 const nsLiteralCString trackedUsageQuery = 382 "SELECT usage FROM Usages WHERE tracked = TRUE AND handle = :handle " 383 ");"_ns; 384 385 QM_TRY_UNWRAP(ResultStatement stmt, 386 ResultStatement::Create(aConnection, trackedUsageQuery)); 387 QM_TRY(QM_TO_RESULT(stmt.BindFileIdByName("handle"_ns, aFileId))); 388 389 QM_TRY_UNWRAP(const bool moreResults, stmt.ExecuteStep()); 390 if (!moreResults) { 391 return Maybe<Usage>(Nothing()); 392 } 393 394 QM_TRY_UNWRAP(Usage trackedUsage, stmt.GetUsageByColumn(/* Column */ 0u)); 395 396 return Some(trackedUsage); 397 } 398 399 Result<bool, nsresult> ScanTrackedFiles( 400 const FileSystemConnection& aConnection, 401 const FileSystemFileManager& aFileManager) { 402 QM_TRY_INSPECT(const nsTArray<FileId>& trackedFiles, 403 GetTrackedFiles(aConnection).mapErr(toNSResult)); 404 405 bool ok = true; 406 for (const auto& fileId : trackedFiles) { 407 // On success, tracked is set to false, otherwise its value is kept (= true) 408 QM_WARNONLY_TRY(MOZ_TO_RESULT(UpdateUsageUnsetTracked( 409 aConnection, aFileManager, fileId)), 410 [&ok](const auto& /*aRv*/) { ok = false; }); 411 } 412 413 return ok; 414 } 415 416 Result<Ok, QMResult> DeleteEntry(const FileSystemConnection& aConnection, 417 const EntryId& aEntryId) { 418 // If it's a directory, deleting the handle will cascade 419 const nsLiteralCString deleteEntryQuery = 420 "DELETE FROM Entries " 421 "WHERE handle = :handle " 422 ";"_ns; 423 424 QM_TRY_UNWRAP(ResultStatement stmt, 425 ResultStatement::Create(aConnection, deleteEntryQuery)); 426 427 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId))); 428 429 QM_TRY(QM_TO_RESULT(stmt.Execute())); 430 431 return Ok{}; 432 } 433 434 Result<int32_t, QMResult> GetTrackedFilesCount( 435 const FileSystemConnection& aConnection) { 436 // TODO: We could query the count directly 437 QM_TRY_INSPECT(const auto& trackedFiles, GetTrackedFiles(aConnection)); 438 439 CheckedInt32 checkedFileCount = trackedFiles.Length(); 440 QM_TRY(OkIf(checkedFileCount.isValid()), 441 Err(QMResult(NS_ERROR_ILLEGAL_VALUE))); 442 443 return checkedFileCount.value(); 444 } 445 446 void LogWithFilename(const FileSystemFileManager& aFileManager, 447 const char* aFormat, const FileId& aFileId) { 448 if (!LOG_ENABLED()) { 449 return; 450 } 451 452 QM_TRY_INSPECT(const auto& localFile, aFileManager.GetFile(aFileId), QM_VOID); 453 454 nsAutoString localPath; 455 QM_TRY(MOZ_TO_RESULT(localFile->GetPath(localPath)), QM_VOID); 456 LOG((aFormat, NS_ConvertUTF16toUTF8(localPath).get())); 457 } 458 459 Result<bool, QMResult> IsAnyDescendantLocked( 460 const FileSystemConnection& aConnection, 461 const FileSystemDataManager& aDataManager, const EntryId& aEntryId) { 462 constexpr const nsLiteralCString descendantsQuery = 463 "WITH RECURSIVE traceChildren(handle, parent) AS ( " 464 "SELECT handle, parent " 465 "FROM Entries " 466 "WHERE handle=:handle " 467 "UNION " 468 "SELECT Entries.handle, Entries.parent FROM traceChildren, Entries " 469 "WHERE traceChildren.handle=Entries.parent ) " 470 "SELECT handle, Files.name " 471 "FROM traceChildren INNER JOIN Files USING (handle) " 472 ";"_ns; 473 474 QM_TRY_UNWRAP(ResultStatement stmt, 475 ResultStatement::Create(aConnection, descendantsQuery)); 476 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId))); 477 QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep()); 478 479 while (moreResults) { 480 // Works only for version 001 481 QM_TRY_INSPECT(const EntryId& entryId, 482 stmt.GetEntryIdByColumn(/* Column */ 0u)); 483 484 QM_TRY_UNWRAP(const bool isLocked, aDataManager.IsLocked(entryId), true); 485 if (isLocked) { 486 return true; 487 } 488 489 QM_TRY_UNWRAP(moreResults, stmt.ExecuteStep()); 490 } 491 492 return false; 493 } 494 495 } // namespace 496 497 FileSystemDatabaseManagerVersion001::FileSystemDatabaseManagerVersion001( 498 FileSystemDataManager* aDataManager, FileSystemConnection&& aConnection, 499 UniquePtr<FileSystemFileManager>&& aFileManager, const EntryId& aRootEntry) 500 : mDataManager(aDataManager), 501 mConnection(aConnection), 502 mFileManager(std::move(aFileManager)), 503 mRootEntry(aRootEntry), 504 mClientMetadata(aDataManager->OriginMetadataRef(), 505 quota::Client::FILESYSTEM), 506 mFilesOfUnknownUsage(-1) {} 507 508 /* static */ 509 nsresult FileSystemDatabaseManagerVersion001::RescanTrackedUsages( 510 const FileSystemConnection& aConnection, 511 const quota::OriginMetadata& aOriginMetadata) { 512 QM_TRY_UNWRAP(UniquePtr<FileSystemFileManager> fileManager, 513 data::FileSystemFileManager::CreateFileSystemFileManager( 514 aOriginMetadata)); 515 516 QM_TRY_UNWRAP(bool ok, ScanTrackedFiles(aConnection, *fileManager)); 517 if (ok) { 518 return NS_OK; 519 } 520 521 // Retry once without explicit delay 522 QM_TRY_UNWRAP(ok, ScanTrackedFiles(aConnection, *fileManager)); 523 if (!ok) { 524 return NS_ERROR_UNEXPECTED; 525 } 526 527 return NS_OK; 528 } 529 530 /* static */ 531 Result<Usage, QMResult> FileSystemDatabaseManagerVersion001::GetFileUsage( 532 const FileSystemConnection& aConnection) { 533 const nsLiteralCString sumUsagesQuery = "SELECT sum(usage) FROM Usages;"_ns; 534 535 QM_TRY_UNWRAP(ResultStatement stmt, 536 ResultStatement::Create(aConnection, sumUsagesQuery)); 537 538 QM_TRY_UNWRAP(const bool moreResults, stmt.ExecuteStep()); 539 if (!moreResults) { 540 return Err(QMResult(NS_ERROR_DOM_FILE_NOT_READABLE_ERR)); 541 } 542 543 QM_TRY_UNWRAP(Usage totalFiles, stmt.GetUsageByColumn(/* Column */ 0u)); 544 545 return totalFiles; 546 } 547 548 Result<quota::UsageInfo, QMResult> 549 FileSystemDatabaseManagerVersion001::GetUsage() const { 550 return FileSystemDatabaseManager::GetUsage(mConnection, mClientMetadata); 551 } 552 553 nsresult FileSystemDatabaseManagerVersion001::UpdateUsage( 554 const FileId& aFileId) { 555 // We don't track directories or non-existent files. 556 QM_TRY_UNWRAP(bool fileExists, DoesFileIdExist(aFileId).mapErr(toNSResult)); 557 if (!fileExists) { 558 return NS_OK; // May be deleted before update, no assert 559 } 560 561 QM_TRY_UNWRAP(nsCOMPtr<nsIFile> file, mFileManager->GetFile(aFileId)); 562 MOZ_ASSERT(file); 563 564 Usage fileSize = 0; 565 bool exists = false; 566 QM_TRY(MOZ_TO_RESULT(file->Exists(&exists))); 567 if (exists) { 568 QM_TRY(MOZ_TO_RESULT(file->GetFileSize(&fileSize))); 569 } 570 571 QM_TRY(MOZ_TO_RESULT(UpdateUsageInDatabase(aFileId, fileSize))); 572 573 return NS_OK; 574 } 575 576 Result<EntryId, QMResult> 577 FileSystemDatabaseManagerVersion001::GetOrCreateDirectory( 578 const FileSystemChildMetadata& aHandle, bool aCreate) { 579 MOZ_ASSERT(!aHandle.parentId().IsEmpty()); 580 581 const auto& name = aHandle.childName(); 582 // Belt and suspenders: check here as well as in child. 583 if (!IsValidName(name)) { 584 return Err(QMResult(NS_ERROR_DOM_TYPE_MISMATCH_ERR)); 585 } 586 MOZ_ASSERT(!(name.IsVoid() || name.IsEmpty())); 587 588 bool exists = true; 589 QM_TRY_UNWRAP(exists, data::DoesFileExist(mConnection, aHandle)); 590 591 // By spec, we don't allow a file and a directory 592 // to have the same name and parent 593 if (exists) { 594 return Err(QMResult(NS_ERROR_DOM_TYPE_MISMATCH_ERR)); 595 } 596 597 QM_TRY_UNWRAP(exists, DoesDirectoryExist(mConnection, aHandle)); 598 599 // exists as directory 600 if (exists) { 601 return FindEntryId(mConnection, aHandle, false); 602 } 603 604 if (!aCreate) { 605 return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); 606 } 607 608 const nsLiteralCString insertEntryQuery = 609 "INSERT OR IGNORE INTO Entries " 610 "( handle, parent ) " 611 "VALUES " 612 "( :handle, :parent ) " 613 ";"_ns; 614 615 const nsLiteralCString insertDirectoryQuery = 616 "INSERT OR IGNORE INTO Directories " 617 "( handle, name ) " 618 "VALUES " 619 "( :handle, :name ) " 620 ";"_ns; 621 622 QM_TRY_INSPECT(const EntryId& entryId, GetEntryId(aHandle)); 623 MOZ_ASSERT(!entryId.IsEmpty()); 624 625 QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(mConnection)); 626 627 { 628 QM_TRY_UNWRAP(ResultStatement stmt, 629 ResultStatement::Create(mConnection, insertEntryQuery)); 630 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId))); 631 QM_TRY( 632 QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aHandle.parentId()))); 633 QM_TRY(QM_TO_RESULT(stmt.Execute())); 634 } 635 636 { 637 QM_TRY_UNWRAP(ResultStatement stmt, 638 ResultStatement::Create(mConnection, insertDirectoryQuery)); 639 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId))); 640 QM_TRY(QM_TO_RESULT(stmt.BindNameByName("name"_ns, name))); 641 QM_TRY(QM_TO_RESULT(stmt.Execute())); 642 } 643 644 QM_TRY(QM_TO_RESULT(transaction.Commit())); 645 646 QM_TRY_UNWRAP(DebugOnly<bool> doesItExistNow, 647 DoesDirectoryExist(mConnection, aHandle)); 648 MOZ_ASSERT(doesItExistNow); 649 650 return entryId; 651 } 652 653 Result<EntryId, QMResult> FileSystemDatabaseManagerVersion001::GetOrCreateFile( 654 const FileSystemChildMetadata& aHandle, bool aCreate) { 655 MOZ_ASSERT(!aHandle.parentId().IsEmpty()); 656 657 const auto& name = aHandle.childName(); 658 // Belt and suspenders: check here as well as in child. 659 if (!IsValidName(name)) { 660 return Err(QMResult(NS_ERROR_DOM_TYPE_MISMATCH_ERR)); 661 } 662 MOZ_ASSERT(!(name.IsVoid() || name.IsEmpty())); 663 664 QM_TRY_UNWRAP(bool exists, DoesDirectoryExist(mConnection, aHandle)); 665 666 // By spec, we don't allow a file and a directory 667 // to have the same name and parent 668 QM_TRY(OkIf(!exists), Err(QMResult(NS_ERROR_DOM_TYPE_MISMATCH_ERR))); 669 670 QM_TRY_UNWRAP(exists, data::DoesFileExist(mConnection, aHandle)); 671 672 if (exists) { 673 QM_TRY_RETURN(FindEntryId(mConnection, aHandle, /* aIsFile */ true)); 674 } 675 676 if (!aCreate) { 677 return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); 678 } 679 680 const nsLiteralCString insertEntryQuery = 681 "INSERT INTO Entries " 682 "( handle, parent ) " 683 "VALUES " 684 "( :handle, :parent ) " 685 ";"_ns; 686 687 const nsLiteralCString insertFileQuery = 688 "INSERT INTO Files " 689 "( handle, type, name ) " 690 "VALUES " 691 "( :handle, :type, :name ) " 692 ";"_ns; 693 694 QM_TRY_INSPECT(const EntryId& entryId, GetEntryId(aHandle)); 695 MOZ_ASSERT(!entryId.IsEmpty()); 696 697 const ContentType type = DetermineContentType(name); 698 699 QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(mConnection)); 700 701 { 702 QM_TRY_UNWRAP(ResultStatement stmt, 703 ResultStatement::Create(mConnection, insertEntryQuery)); 704 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId))); 705 QM_TRY( 706 QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aHandle.parentId()))); 707 QM_TRY(QM_TO_RESULT(stmt.Execute()), QM_PROPAGATE, 708 ([this, &aHandle](const auto& aRv) { 709 QM_TRY_UNWRAP(bool parentExists, 710 DoesDirectoryExist(mConnection, aHandle.parentId()), 711 QM_VOID); 712 QM_TRY(OkIf(parentExists), QM_VOID); 713 })); 714 } 715 716 { 717 QM_TRY_UNWRAP(ResultStatement stmt, 718 ResultStatement::Create(mConnection, insertFileQuery)); 719 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId))); 720 QM_TRY(QM_TO_RESULT(stmt.BindContentTypeByName("type"_ns, type))); 721 QM_TRY(QM_TO_RESULT(stmt.BindNameByName("name"_ns, name))); 722 QM_TRY(QM_TO_RESULT(stmt.Execute())); 723 } 724 725 QM_TRY(QM_TO_RESULT(transaction.Commit())); 726 727 return entryId; 728 } 729 730 nsresult FileSystemDatabaseManagerVersion001::GetFile( 731 const EntryId& aEntryId, const FileId& aFileId, const FileMode& aMode, 732 ContentType& aType, TimeStamp& lastModifiedMilliSeconds, 733 nsTArray<Name>& aPath, nsCOMPtr<nsIFile>& aFile) const { 734 MOZ_ASSERT(!aFileId.IsEmpty()); 735 MOZ_ASSERT(aMode == FileMode::EXCLUSIVE); 736 737 const FileSystemEntryPair endPoints(mRootEntry, aEntryId); 738 QM_TRY_UNWRAP(aPath, ResolveReversedPath(mConnection, endPoints)); 739 if (aPath.IsEmpty()) { 740 return NS_ERROR_DOM_NOT_FOUND_ERR; 741 } 742 743 QM_TRY(MOZ_TO_RESULT(GetFileAttributes(mConnection, aEntryId, aType))); 744 QM_TRY_UNWRAP(aFile, mFileManager->GetOrCreateFile(aFileId)); 745 746 PRTime lastModTime = 0; 747 QM_TRY(MOZ_TO_RESULT(aFile->GetLastModifiedTime(&lastModTime))); 748 lastModifiedMilliSeconds = static_cast<TimeStamp>(lastModTime); 749 750 aPath.Reverse(); 751 752 return NS_OK; 753 } 754 755 Result<FileSystemDirectoryListing, QMResult> 756 FileSystemDatabaseManagerVersion001::GetDirectoryEntries( 757 const EntryId& aParent, PageNumber aPage) const { 758 // TODO: Offset is reported to have bad performance - see Bug 1780386. 759 const nsCString directoriesQuery = 760 "SELECT Dirs.handle, Dirs.name " 761 "FROM Directories AS Dirs " 762 "INNER JOIN ( " 763 "SELECT handle " 764 "FROM Entries " 765 "WHERE parent = :parent " 766 "LIMIT :pageSize " 767 "OFFSET :pageOffset ) " 768 "AS Ents " 769 "ON Dirs.handle = Ents.handle " 770 ";"_ns; 771 const nsCString filesQuery = 772 "SELECT Files.handle, Files.name " 773 "FROM Files " 774 "INNER JOIN ( " 775 "SELECT handle " 776 "FROM Entries " 777 "WHERE parent = :parent " 778 "LIMIT :pageSize " 779 "OFFSET :pageOffset ) " 780 "AS Ents " 781 "ON Files.handle = Ents.handle " 782 ";"_ns; 783 784 FileSystemDirectoryListing entries; 785 QM_TRY( 786 QM_TO_RESULT(GetEntries(mConnection, directoriesQuery, aParent, aPage, 787 /* aDirectory */ true, entries.directories()))); 788 789 QM_TRY(QM_TO_RESULT(GetEntries(mConnection, filesQuery, aParent, aPage, 790 /* aDirectory */ false, entries.files()))); 791 792 return entries; 793 } 794 795 Result<bool, QMResult> FileSystemDatabaseManagerVersion001::RemoveDirectoryImpl( 796 const EntryId& entryId) { 797 using FileIdArrayAndUsage = std::pair<nsTArray<FileId>, Usage>; 798 799 // Caller has checked that there are no exclusively locked descendants. 800 QM_TRY_INSPECT(const FileIdArrayAndUsage& unlockedFileInfo, 801 FindFilesWithoutDeprecatedLocksUnderEntry(entryId)); 802 803 const nsTArray<FileId>& descendants = unlockedFileInfo.first; 804 // We remove the files which have no open WritableFileStream child actors. 805 // The files left behind get removed at the end of the child actor lifecycle. 806 nsTArray<FileId> failedRemovals; 807 QM_TRY_UNWRAP(DebugOnly<Usage> removedUsage, 808 mFileManager->RemoveFiles(descendants, failedRemovals)); 809 810 const Usage usage = unlockedFileInfo.second; 811 // Usage is for the current main file but we remove temporary files too. 812 MOZ_ASSERT_IF(failedRemovals.IsEmpty() && (0 == mFilesOfUnknownUsage), 813 usage <= removedUsage); 814 815 TryRemoveDuringIdleMaintenance(failedRemovals); 816 817 auto isInFailedRemovals = [&failedRemovals](const auto& aFileId) { 818 return failedRemovals.cend() != 819 std::find_if(failedRemovals.cbegin(), failedRemovals.cend(), 820 [&aFileId](const auto& aFailedRemoval) { 821 return aFileId == aFailedRemoval; 822 }); 823 }; 824 825 for (const auto& fileId : descendants) { 826 if (!isInFailedRemovals(fileId)) { 827 QM_WARNONLY_TRY(QM_TO_RESULT(RemoveFileId(fileId))); 828 } 829 } 830 831 if (usage > 0) { // Performance! 832 DecreaseCachedQuotaUsage(usage); 833 } 834 835 QM_TRY(DeleteEntry(mConnection, entryId)); 836 837 return true; 838 } 839 840 Result<bool, QMResult> FileSystemDatabaseManagerVersion001::RemoveDirectory( 841 const FileSystemChildMetadata& aHandle, bool aRecursive) { 842 MOZ_ASSERT(!aHandle.parentId().IsEmpty()); 843 844 if (aHandle.childName().IsEmpty()) { 845 return false; 846 } 847 848 DebugOnly<Name> name = aHandle.childName(); 849 MOZ_ASSERT(!name.inspect().IsVoid()); 850 851 QM_TRY_UNWRAP(bool exists, DoesDirectoryExist(mConnection, aHandle)); 852 853 if (!exists) { 854 return false; 855 } 856 857 // At this point, entry exists and is a directory. 858 QM_TRY_UNWRAP(EntryId entryId, FindEntryId(mConnection, aHandle, false)); 859 MOZ_ASSERT(!entryId.IsEmpty()); 860 861 QM_TRY_UNWRAP(bool isEmpty, IsDirectoryEmpty(mConnection, entryId)); 862 863 MOZ_ASSERT(mDataManager); 864 QM_TRY_UNWRAP(const bool isLocked, 865 IsAnyDescendantLocked(mConnection, *mDataManager, entryId)); 866 867 // XXX If there are any locked file entries, we exit here. 868 // There has been talk that the spec might allow unconditional 869 // overwrites in the future. 870 QM_TRY(OkIf(!isLocked), 871 Err(QMResult(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR))); 872 873 if (!aRecursive && !isEmpty) { 874 return Err(QMResult(NS_ERROR_DOM_INVALID_MODIFICATION_ERR)); 875 } 876 877 QM_TRY_RETURN(RemoveDirectoryImpl(entryId)); 878 } 879 880 Result<bool, QMResult> FileSystemDatabaseManagerVersion001::RemoveFile( 881 const FileSystemChildMetadata& aHandle) { 882 MOZ_ASSERT(!aHandle.parentId().IsEmpty()); 883 884 if (aHandle.childName().IsEmpty()) { 885 return false; 886 } 887 888 DebugOnly<Name> name = aHandle.childName(); 889 MOZ_ASSERT(!name.inspect().IsVoid()); 890 891 // Make it more evident that we won't remove directories 892 QM_TRY_UNWRAP(bool exists, data::DoesFileExist(mConnection, aHandle)); 893 894 if (!exists) { 895 return false; 896 } 897 898 // At this point, entry exists and is a file 899 QM_TRY_UNWRAP(EntryId entryId, FindEntryId(mConnection, aHandle, true)); 900 MOZ_ASSERT(!entryId.IsEmpty()); 901 902 // XXX This code assumes the spec question is resolved to state 903 // removing an in-use file should fail. If it shouldn't fail, we need to 904 // do something to neuter all the extant FileAccessHandles/WritableFileStreams 905 // that reference it 906 QM_TRY_UNWRAP(const bool isLocked, mDataManager->IsLocked(entryId)); 907 if (isLocked) { 908 LOG(("Trying to remove in-use file")); 909 return Err(QMResult(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR)); 910 } 911 912 using FileIdArrayAndUsage = std::pair<nsTArray<FileId>, Usage>; 913 QM_TRY_INSPECT(const FileIdArrayAndUsage& unlockedFileInfo, 914 FindFilesWithoutDeprecatedLocksUnderEntry(entryId)); 915 916 const nsTArray<FileId>& diskItems = unlockedFileInfo.first; 917 nsTArray<FileId> failedRemovals; 918 QM_TRY_UNWRAP(DebugOnly<Usage> removedUsage, 919 mFileManager->RemoveFiles(diskItems, failedRemovals)); 920 921 const Usage usage = unlockedFileInfo.second; 922 // We only check the most common case. This can fail spuriously if an external 923 // application writes to the file, or OS reports zero size due to corruption. 924 MOZ_ASSERT_IF(failedRemovals.IsEmpty() && (0 == mFilesOfUnknownUsage), 925 usage == removedUsage); 926 927 TryRemoveDuringIdleMaintenance(failedRemovals); 928 929 auto isInFailedRemovals = [&failedRemovals](const auto& aFileId) { 930 return failedRemovals.cend() != 931 std::find_if(failedRemovals.cbegin(), failedRemovals.cend(), 932 [&aFileId](const auto& aFailedRemoval) { 933 return aFileId == aFailedRemoval; 934 }); 935 }; 936 937 for (const auto& fileId : diskItems) { 938 if (!isInFailedRemovals(fileId)) { 939 QM_WARNONLY_TRY(QM_TO_RESULT(RemoveFileId(fileId))); 940 } 941 } 942 943 if (usage > 0) { // Performance! 944 DecreaseCachedQuotaUsage(usage); 945 } 946 947 QM_TRY(DeleteEntry(mConnection, entryId)); 948 949 return true; 950 } 951 952 Result<EntryId, QMResult> FileSystemDatabaseManagerVersion001::RenameEntry( 953 const FileSystemEntryMetadata& aHandle, const Name& aNewName) { 954 const auto& entryId = aHandle.entryId(); 955 956 // Can't rename root 957 if (mRootEntry == entryId) { 958 return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); 959 } 960 961 // Verify the source exists 962 QM_TRY_UNWRAP(bool isFile, IsFile(mConnection, entryId), 963 Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR))); 964 965 // Are we actually renaming? 966 if (aHandle.entryName() == aNewName) { 967 return entryId; 968 } 969 970 QM_TRY(QM_TO_RESULT(PrepareRenameEntry(mConnection, mDataManager, aHandle, 971 aNewName, isFile))); 972 973 QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(mConnection)); 974 975 if (isFile) { 976 const ContentType type = DetermineContentType(aNewName); 977 QM_TRY( 978 QM_TO_RESULT(PerformRenameFile(mConnection, aHandle, aNewName, type))); 979 } else { 980 QM_TRY( 981 QM_TO_RESULT(PerformRenameDirectory(mConnection, aHandle, aNewName))); 982 } 983 984 QM_TRY(QM_TO_RESULT(transaction.Commit())); 985 986 return entryId; 987 } 988 989 Result<EntryId, QMResult> FileSystemDatabaseManagerVersion001::MoveEntry( 990 const FileSystemEntryMetadata& aHandle, 991 const FileSystemChildMetadata& aNewDesignation) { 992 const auto& entryId = aHandle.entryId(); 993 MOZ_ASSERT(!entryId.IsEmpty()); 994 995 if (mRootEntry == entryId) { 996 return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); 997 } 998 999 // Verify the source exists 1000 QM_TRY_UNWRAP(bool isFile, IsFile(mConnection, entryId), 1001 Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR))); 1002 1003 // If the rename doesn't change the name or directory, just return success. 1004 // XXX Needs to be added to the spec 1005 QM_WARNONLY_TRY_UNWRAP(Maybe<bool> maybeSame, 1006 IsSame(mConnection, aHandle, aNewDesignation, isFile)); 1007 if (maybeSame && maybeSame.value()) { 1008 return entryId; 1009 } 1010 1011 QM_TRY(QM_TO_RESULT(PrepareMoveEntry(mConnection, mDataManager, aHandle, 1012 aNewDesignation, isFile))); 1013 1014 const nsLiteralCString updateEntryParentQuery = 1015 "UPDATE Entries " 1016 "SET parent = :parent " 1017 "WHERE handle = :handle " 1018 ";"_ns; 1019 1020 QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(mConnection)); 1021 1022 { 1023 // We always change the parent because it's simpler than checking if the 1024 // parent needs to be changed 1025 QM_TRY_UNWRAP(ResultStatement stmt, 1026 ResultStatement::Create(mConnection, updateEntryParentQuery)); 1027 QM_TRY(QM_TO_RESULT( 1028 stmt.BindEntryIdByName("parent"_ns, aNewDesignation.parentId()))); 1029 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId))); 1030 QM_TRY(QM_TO_RESULT(stmt.Execute())); 1031 } 1032 1033 const Name& newName = aNewDesignation.childName(); 1034 1035 // Are we actually renaming? 1036 if (aHandle.entryName() == newName) { 1037 QM_TRY(QM_TO_RESULT(transaction.Commit())); 1038 1039 return entryId; 1040 } 1041 1042 if (isFile) { 1043 const ContentType type = DetermineContentType(newName); 1044 QM_TRY( 1045 QM_TO_RESULT(PerformRenameFile(mConnection, aHandle, newName, type))); 1046 } else { 1047 QM_TRY(QM_TO_RESULT(PerformRenameDirectory(mConnection, aHandle, newName))); 1048 } 1049 1050 QM_TRY(QM_TO_RESULT(transaction.Commit())); 1051 1052 return entryId; 1053 } 1054 1055 Result<Path, QMResult> FileSystemDatabaseManagerVersion001::Resolve( 1056 const FileSystemEntryPair& aEndpoints) const { 1057 QM_TRY_UNWRAP(Path path, ResolveReversedPath(mConnection, aEndpoints)); 1058 // Note: if not an ancestor, returns null 1059 1060 path.Reverse(); 1061 return path; 1062 } 1063 1064 Result<bool, QMResult> FileSystemDatabaseManagerVersion001::DoesFileExist( 1065 const EntryId& aEntryId) const { 1066 MOZ_ASSERT(!aEntryId.IsEmpty()); 1067 1068 QM_TRY_RETURN(data::DoesFileExist(mConnection, aEntryId)); 1069 } 1070 1071 Result<EntryId, QMResult> FileSystemDatabaseManagerVersion001::GetEntryId( 1072 const FileSystemChildMetadata& aHandle) const { 1073 return GetUniqueEntryId(mConnection, aHandle); 1074 } 1075 1076 Result<EntryId, QMResult> FileSystemDatabaseManagerVersion001::GetEntryId( 1077 const FileId& aFileId) const { 1078 return aFileId.Value(); 1079 } 1080 1081 Result<FileId, QMResult> FileSystemDatabaseManagerVersion001::EnsureFileId( 1082 const EntryId& aEntryId) { 1083 return FileId(aEntryId); 1084 } 1085 1086 Result<FileId, QMResult> 1087 FileSystemDatabaseManagerVersion001::EnsureTemporaryFileId( 1088 const EntryId& aEntryId) { 1089 return FileId(aEntryId); 1090 } 1091 1092 Result<FileId, QMResult> FileSystemDatabaseManagerVersion001::GetFileId( 1093 const EntryId& aEntryId) const { 1094 return FileId(aEntryId); 1095 } 1096 1097 nsresult FileSystemDatabaseManagerVersion001::MergeFileId( 1098 const EntryId& /* aEntryId */, const FileId& /* aFileId */, 1099 bool /* aAbort */) { 1100 // Version 001 should always use exclusive mode and not get here. 1101 return NS_ERROR_NOT_IMPLEMENTED; 1102 } 1103 1104 void FileSystemDatabaseManagerVersion001::Close() { mConnection->Close(); } 1105 1106 nsresult FileSystemDatabaseManagerVersion001::BeginUsageTracking( 1107 const FileId& aFileId) { 1108 MOZ_ASSERT(!aFileId.IsEmpty()); 1109 1110 // If file is already tracked but we cannot read its size, error. 1111 // If file does not exist, this will succeed because usage is zero. 1112 QM_TRY(EnsureUsageIsKnown(aFileId)); 1113 1114 // If file does not exist, set usage tracking to true fails with 1115 // file not found error. 1116 QM_TRY(MOZ_TO_RESULT(SetUsageTracking(aFileId, true))); 1117 1118 return NS_OK; 1119 } 1120 1121 nsresult FileSystemDatabaseManagerVersion001::EndUsageTracking( 1122 const FileId& aFileId) { 1123 // This is expected to fail only if database is unreachable. 1124 QM_TRY(MOZ_TO_RESULT(SetUsageTracking(aFileId, false))); 1125 1126 return NS_OK; 1127 } 1128 1129 Result<bool, QMResult> FileSystemDatabaseManagerVersion001::DoesFileIdExist( 1130 const FileId& aFileId) const { 1131 MOZ_ASSERT(!aFileId.IsEmpty()); 1132 1133 QM_TRY_RETURN(DoesFileExist(aFileId.Value())); 1134 } 1135 1136 nsresult FileSystemDatabaseManagerVersion001::RemoveFileId( 1137 const FileId& /* aFileId */) { 1138 return NS_OK; 1139 } 1140 1141 /** 1142 * @brief Get the sum of usages for all file descendants of a directory entry. 1143 * We obtain the values with one query, which is presumably better than having a 1144 * separate query for each individual descendant. 1145 * TODO: Check if this is true 1146 * 1147 * Please see GetFileUsage documentation for why we use the latest recorded 1148 * value from the database instead of the file size property from the disk. 1149 */ 1150 Result<std::pair<nsTArray<FileId>, Usage>, QMResult> 1151 FileSystemDatabaseManagerVersion001::FindFilesWithoutDeprecatedLocksUnderEntry( 1152 const EntryId& aEntryId) const { 1153 nsTArray<FileId> descendants; 1154 Usage usage{0}; 1155 { 1156 QM_TRY_UNWRAP(ResultStatement stmt, 1157 ResultStatement::Create(mConnection, gDescendantsQuery)); 1158 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId))); 1159 QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep()); 1160 1161 while (true) { 1162 if (!moreResults) { 1163 break; 1164 } 1165 1166 // Works only for version 001 1167 QM_TRY_INSPECT(const FileId& fileId, 1168 stmt.GetFileIdByColumn(/* Column */ 0u)); 1169 1170 // FileId equals EntryId for version 001 1171 if (!mDataManager->IsLockedWithDeprecatedSharedLock(fileId.Value(), 1172 fileId)) { 1173 QM_TRY_UNWRAP(DebugOnly<bool> isLocked, 1174 mDataManager->IsLocked(fileId.Value())); 1175 MOZ_ASSERT(!isLocked); 1176 descendants.AppendElement(fileId); 1177 1178 QM_TRY_INSPECT(const Usage& fileUsage, 1179 stmt.GetUsageByColumn(/* Column */ 1u)); 1180 usage += fileUsage; 1181 } 1182 1183 QM_TRY_UNWRAP(moreResults, stmt.ExecuteStep()); 1184 } 1185 } 1186 1187 return std::make_pair(std::move(descendants), usage); 1188 } 1189 1190 Result<nsTArray<std::pair<EntryId, FileId>>, QMResult> 1191 FileSystemDatabaseManagerVersion001::FindFileEntriesUnderDirectory( 1192 const EntryId& aEntryId) const { 1193 return Err(QMResult(NS_ERROR_NOT_IMPLEMENTED)); 1194 } 1195 1196 nsresult FileSystemDatabaseManagerVersion001::SetUsageTracking( 1197 const FileId& aFileId, bool aTracked) { 1198 auto onMissingFile = [this, &aFileId](const auto& aRv) { 1199 // Usages constrains entryId to be present in Files 1200 MOZ_ASSERT(NS_ERROR_STORAGE_CONSTRAINT == ToNSResult(aRv)); 1201 1202 // The query *should* fail if and only if file does not exist 1203 QM_TRY_UNWRAP(DebugOnly<bool> fileExists, DoesFileIdExist(aFileId), 1204 QM_VOID); 1205 MOZ_ASSERT(!fileExists); 1206 }; 1207 1208 return SetUsageTrackingImpl(mConnection, aFileId, aTracked, onMissingFile); 1209 } 1210 1211 nsresult FileSystemDatabaseManagerVersion001::UpdateUsageInDatabase( 1212 const FileId& aFileId, Usage aNewDiskUsage) { 1213 const nsLiteralCString updateUsageQuery = 1214 "INSERT INTO Usages " 1215 "( handle, usage ) " 1216 "VALUES " 1217 "( :handle, :usage ) " 1218 "ON CONFLICT(handle) DO " 1219 "UPDATE SET usage = excluded.usage " 1220 ";"_ns; 1221 1222 QM_TRY_UNWRAP(ResultStatement stmt, 1223 ResultStatement::Create(mConnection, updateUsageQuery)); 1224 QM_TRY(MOZ_TO_RESULT(stmt.BindUsageByName("usage"_ns, aNewDiskUsage))); 1225 QM_TRY(MOZ_TO_RESULT(stmt.BindFileIdByName("handle"_ns, aFileId))); 1226 QM_TRY(MOZ_TO_RESULT(stmt.Execute())); 1227 1228 return NS_OK; 1229 } 1230 1231 Result<Ok, QMResult> FileSystemDatabaseManagerVersion001::EnsureUsageIsKnown( 1232 const FileId& aFileId) { 1233 if (mFilesOfUnknownUsage < 0) { // Lazy initialization 1234 QM_TRY_UNWRAP(mFilesOfUnknownUsage, GetTrackedFilesCount(mConnection)); 1235 } 1236 1237 if (mFilesOfUnknownUsage == 0) { 1238 return Ok{}; 1239 } 1240 1241 QM_TRY_UNWRAP(Maybe<Usage> oldUsage, 1242 GetMaybeTrackedUsage(mConnection, aFileId)); 1243 if (oldUsage.isNothing()) { 1244 return Ok{}; // Usage is 0 or it was successfully recorded at unlocking. 1245 } 1246 1247 auto quotaCacheUpdate = [this, &aFileId, 1248 oldSize = oldUsage.value()](Usage aNewSize) { 1249 return UpdateCachedQuotaUsage(aFileId, oldSize, aNewSize); 1250 }; 1251 1252 static const nsLiteralCString updateUsagesKeepTrackedQuery = 1253 "UPDATE Usages SET usage = :usage WHERE handle = :handle;"_ns; 1254 1255 // If usage update fails, we log an error and keep things the way they were. 1256 QM_TRY(QM_TO_RESULT(UpdateUsageForFileEntry( 1257 mConnection, *mFileManager, aFileId, updateUsagesKeepTrackedQuery, 1258 std::move(quotaCacheUpdate))), 1259 Err(QMResult(NS_ERROR_DOM_FILE_NOT_READABLE_ERR)), 1260 ([this, &aFileId](const auto& /*aRv*/) { 1261 LogWithFilename(*mFileManager, "Could not read the size of file %s", 1262 aFileId); 1263 })); 1264 1265 // We read and updated the quota usage successfully. 1266 --mFilesOfUnknownUsage; 1267 MOZ_ASSERT(mFilesOfUnknownUsage >= 0); 1268 1269 return Ok{}; 1270 } 1271 1272 void FileSystemDatabaseManagerVersion001::DecreaseCachedQuotaUsage( 1273 int64_t aDelta) { 1274 quota::QuotaManager* quotaManager = quota::QuotaManager::Get(); 1275 MOZ_ASSERT(quotaManager); 1276 1277 quotaManager->DecreaseUsageForClient(mClientMetadata, aDelta); 1278 } 1279 1280 nsresult FileSystemDatabaseManagerVersion001::UpdateCachedQuotaUsage( 1281 const FileId& aFileId, Usage aOldUsage, Usage aNewUsage) const { 1282 quota::QuotaManager* quotaManager = quota::QuotaManager::Get(); 1283 MOZ_ASSERT(quotaManager); 1284 1285 QM_TRY_UNWRAP(nsCOMPtr<nsIFile> fileObj, 1286 mFileManager->GetFile(aFileId).mapErr(toNSResult)); 1287 1288 RefPtr<quota::QuotaObject> quotaObject = quotaManager->GetQuotaObject( 1289 quota::PERSISTENCE_TYPE_DEFAULT, mClientMetadata, 1290 quota::Client::FILESYSTEM, fileObj, aOldUsage); 1291 MOZ_ASSERT(quotaObject); 1292 1293 QM_TRY(OkIf(quotaObject->MaybeUpdateSize(aNewUsage, /* aTruncate */ true)), 1294 NS_ERROR_FILE_NO_DEVICE_SPACE); 1295 1296 return NS_OK; 1297 } 1298 1299 nsresult FileSystemDatabaseManagerVersion001::ClearDestinationIfNotLocked( 1300 const FileSystemConnection& aConnection, 1301 const FileSystemDataManager* const aDataManager, 1302 const FileSystemEntryMetadata& aHandle, 1303 const FileSystemChildMetadata& aNewDesignation) { 1304 // If the destination file exists, fail explicitly. Spec author plans to 1305 // revise the spec 1306 QM_TRY_UNWRAP(bool exists, data::DoesFileExist(aConnection, aNewDesignation)); 1307 if (exists) { 1308 QM_TRY_INSPECT(const EntryId& destId, 1309 FindEntryId(aConnection, aNewDesignation, true)); 1310 QM_TRY_UNWRAP(const bool isLocked, aDataManager->IsLocked(destId)); 1311 if (isLocked) { 1312 LOG(("Trying to overwrite in-use file")); 1313 return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR; 1314 } 1315 1316 QM_TRY_UNWRAP(DebugOnly<bool> isRemoved, RemoveFile(aNewDesignation)); 1317 MOZ_ASSERT(isRemoved); 1318 } else { 1319 QM_TRY_UNWRAP(exists, DoesDirectoryExist(aConnection, aNewDesignation)); 1320 if (exists) { 1321 QM_TRY_INSPECT(const EntryId& destId, 1322 FindEntryId(aConnection, aNewDesignation, false)); 1323 1324 MOZ_ASSERT(aDataManager); 1325 QM_TRY_UNWRAP(const bool isLocked, 1326 IsAnyDescendantLocked(mConnection, *aDataManager, destId)); 1327 1328 // XXX If there are any locked file entries, we exit here. 1329 // There has been talk that the spec might allow unconditional 1330 // overwrites in the future. 1331 QM_TRY(OkIf(!isLocked), 1332 Err(QMResult(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR))); 1333 1334 QM_TRY_UNWRAP(DebugOnly<bool> isRemoved, 1335 MOZ_TO_RESULT(RemoveDirectoryImpl(destId))); 1336 1337 MOZ_ASSERT(isRemoved); 1338 } 1339 } 1340 1341 return NS_OK; 1342 } 1343 1344 nsresult FileSystemDatabaseManagerVersion001::PrepareRenameEntry( 1345 const FileSystemConnection& aConnection, 1346 const FileSystemDataManager* const aDataManager, 1347 const FileSystemEntryMetadata& aHandle, const Name& aNewName, 1348 bool aIsFile) { 1349 const EntryId& entryId = aHandle.entryId(); 1350 1351 // At this point, entry exists 1352 if (aIsFile) { 1353 QM_TRY_UNWRAP(const bool isLocked, aDataManager->IsLocked(entryId)); 1354 if (isLocked) { 1355 LOG(("Trying to move in-use file")); 1356 return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR; 1357 } 1358 } 1359 1360 // If the destination file exists, fail explicitly. 1361 FileSystemChildMetadata destination; 1362 QM_TRY_UNWRAP(EntryId parent, FindParent(mConnection, entryId)); 1363 destination.parentId() = parent; 1364 destination.childName() = aNewName; 1365 1366 QM_TRY(MOZ_TO_RESULT(ClearDestinationIfNotLocked(mConnection, mDataManager, 1367 aHandle, destination))); 1368 1369 return NS_OK; 1370 } 1371 1372 nsresult FileSystemDatabaseManagerVersion001::PrepareMoveEntry( 1373 const FileSystemConnection& aConnection, 1374 const FileSystemDataManager* const aDataManager, 1375 const FileSystemEntryMetadata& aHandle, 1376 const FileSystemChildMetadata& aNewDesignation, bool aIsFile) { 1377 const EntryId& entryId = aHandle.entryId(); 1378 1379 // At this point, entry exists 1380 if (aIsFile) { 1381 QM_TRY_UNWRAP(const bool isLocked, aDataManager->IsLocked(entryId)); 1382 if (isLocked) { 1383 LOG(("Trying to move in-use file")); 1384 return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR; 1385 } 1386 } 1387 1388 QM_TRY(QM_TO_RESULT(ClearDestinationIfNotLocked(aConnection, aDataManager, 1389 aHandle, aNewDesignation))); 1390 1391 // XXX: This should be before clearing the target 1392 1393 // To prevent cyclic paths, we check that there is no path from 1394 // the item to be moved to the destination folder. 1395 QM_TRY_UNWRAP(const bool isDestinationUnderSelf, 1396 IsAncestor(aConnection, {entryId, aNewDesignation.parentId()})); 1397 if (isDestinationUnderSelf) { 1398 return NS_ERROR_DOM_INVALID_MODIFICATION_ERR; 1399 } 1400 1401 return NS_OK; 1402 } 1403 1404 /** 1405 * Free functions 1406 */ 1407 1408 Result<bool, QMResult> ApplyEntryExistsQuery( 1409 const FileSystemConnection& aConnection, const nsACString& aQuery, 1410 const FileSystemChildMetadata& aHandle) { 1411 QM_TRY_UNWRAP(ResultStatement stmt, 1412 ResultStatement::Create(aConnection, aQuery)); 1413 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aHandle.parentId()))); 1414 QM_TRY(QM_TO_RESULT(stmt.BindNameByName("name"_ns, aHandle.childName()))); 1415 1416 return stmt.YesOrNoQuery(); 1417 } 1418 1419 Result<bool, QMResult> ApplyEntryExistsQuery( 1420 const FileSystemConnection& aConnection, const nsACString& aQuery, 1421 const EntryId& aEntry) { 1422 QM_TRY_UNWRAP(ResultStatement stmt, 1423 ResultStatement::Create(aConnection, aQuery)); 1424 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntry))); 1425 1426 return stmt.YesOrNoQuery(); 1427 } 1428 1429 Result<bool, QMResult> IsFile(const FileSystemConnection& aConnection, 1430 const EntryId& aEntryId) { 1431 QM_TRY_UNWRAP(bool exists, DoesFileExist(aConnection, aEntryId)); 1432 if (exists) { 1433 return true; 1434 } 1435 1436 QM_TRY_UNWRAP(exists, DoesDirectoryExist(aConnection, aEntryId)); 1437 if (exists) { 1438 return false; 1439 } 1440 1441 // Doesn't exist 1442 return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); 1443 } 1444 1445 Result<EntryId, QMResult> FindEntryId(const FileSystemConnection& aConnection, 1446 const FileSystemChildMetadata& aHandle, 1447 bool aIsFile) { 1448 const nsCString aDirectoryQuery = 1449 "SELECT Entries.handle FROM Directories " 1450 "INNER JOIN Entries USING (handle) " 1451 "WHERE Directories.name = :name AND Entries.parent = :parent " 1452 ";"_ns; 1453 1454 const nsCString aFileQuery = 1455 "SELECT Entries.handle FROM Files INNER JOIN Entries USING (handle) " 1456 "WHERE Files.name = :name AND Entries.parent = :parent " 1457 ";"_ns; 1458 1459 QM_TRY_UNWRAP(ResultStatement stmt, 1460 ResultStatement::Create( 1461 aConnection, aIsFile ? aFileQuery : aDirectoryQuery)); 1462 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aHandle.parentId()))); 1463 QM_TRY(QM_TO_RESULT(stmt.BindNameByName("name"_ns, aHandle.childName()))); 1464 QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep()); 1465 1466 if (!moreResults) { 1467 return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); 1468 } 1469 1470 QM_TRY_UNWRAP(EntryId entryId, stmt.GetEntryIdByColumn(/* Column */ 0u)); 1471 1472 return entryId; 1473 } 1474 1475 Result<EntryId, QMResult> FindParent(const FileSystemConnection& aConnection, 1476 const EntryId& aEntryId) { 1477 const nsCString aParentQuery = 1478 "SELECT handle FROM Entries " 1479 "WHERE handle IN ( " 1480 "SELECT parent FROM Entries WHERE " 1481 "handle = :entryId ) " 1482 ";"_ns; 1483 1484 QM_TRY_UNWRAP(ResultStatement stmt, 1485 ResultStatement::Create(aConnection, aParentQuery)); 1486 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEntryId))); 1487 QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep()); 1488 1489 if (!moreResults) { 1490 return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); 1491 } 1492 1493 QM_TRY_UNWRAP(EntryId parentId, stmt.GetEntryIdByColumn(/* Column */ 0u)); 1494 return parentId; 1495 } 1496 1497 Result<bool, QMResult> IsSame(const FileSystemConnection& aConnection, 1498 const FileSystemEntryMetadata& aHandle, 1499 const FileSystemChildMetadata& aNewHandle, 1500 bool aIsFile) { 1501 MOZ_ASSERT(!aNewHandle.parentId().IsEmpty()); 1502 1503 // Typically aNewHandle does not exist which is not an error 1504 QM_TRY_RETURN(QM_OR_ELSE_LOG_VERBOSE_IF( 1505 // Expression. 1506 FindEntryId(aConnection, aNewHandle, aIsFile) 1507 .map([&aHandle](const EntryId& entryId) { 1508 return entryId == aHandle.entryId(); 1509 }), 1510 // Predicate. 1511 IsSpecificError<NS_ERROR_DOM_NOT_FOUND_ERR>, 1512 // Fallback. 1513 ErrToOkFromQMResult<false>)); 1514 } 1515 1516 Result<Path, QMResult> ResolveReversedPath( 1517 const FileSystemConnection& aConnection, 1518 const FileSystemEntryPair& aEndpoints) { 1519 const nsLiteralCString pathQuery = 1520 "WITH RECURSIVE followPath(handle, parent) AS ( " 1521 "SELECT handle, parent " 1522 "FROM Entries " 1523 "WHERE handle=:entryId " 1524 "UNION " 1525 "SELECT Entries.handle, Entries.parent FROM followPath, Entries " 1526 "WHERE followPath.parent=Entries.handle ) " 1527 "SELECT COALESCE(Directories.name, Files.name), handle " 1528 "FROM followPath " 1529 "LEFT JOIN Directories USING(handle) " 1530 "LEFT JOIN Files USING(handle);"_ns; 1531 1532 QM_TRY_UNWRAP(ResultStatement stmt, 1533 ResultStatement::Create(aConnection, pathQuery)); 1534 QM_TRY( 1535 QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEndpoints.childId()))); 1536 QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep()); 1537 1538 Path pathResult; 1539 while (moreResults) { 1540 QM_TRY_UNWRAP(Name entryName, stmt.GetNameByColumn(/* Column */ 0u)); 1541 QM_TRY_UNWRAP(EntryId entryId, stmt.GetEntryIdByColumn(/* Column */ 1u)); 1542 1543 if (aEndpoints.parentId() == entryId) { 1544 return pathResult; 1545 } 1546 pathResult.AppendElement(entryName); 1547 1548 QM_TRY_UNWRAP(moreResults, stmt.ExecuteStep()); 1549 } 1550 1551 // Spec wants us to return 'null' for not-an-ancestor case 1552 pathResult.Clear(); 1553 return pathResult; 1554 } 1555 1556 nsresult GetFileAttributes(const FileSystemConnection& aConnection, 1557 const EntryId& aEntryId, ContentType& aType) { 1558 const nsLiteralCString getFileLocation = 1559 "SELECT type FROM Files INNER JOIN Entries USING(handle) " 1560 "WHERE handle = :entryId " 1561 ";"_ns; 1562 1563 QM_TRY_UNWRAP(ResultStatement stmt, 1564 ResultStatement::Create(aConnection, getFileLocation)); 1565 QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEntryId))); 1566 QM_TRY_UNWRAP(bool hasEntries, stmt.ExecuteStep()); 1567 1568 // Type is an optional attribute 1569 if (!hasEntries || stmt.IsNullByColumn(/* Column */ 0u)) { 1570 return NS_OK; 1571 } 1572 1573 QM_TRY_UNWRAP(aType, stmt.GetContentTypeByColumn(/* Column */ 0u)); 1574 1575 return NS_OK; 1576 } 1577 1578 // TODO: Implement idle maintenance 1579 void TryRemoveDuringIdleMaintenance( 1580 const nsTArray<FileId>& /* aItemToRemove */) { 1581 // Not implemented 1582 } 1583 1584 ContentType DetermineContentType(const Name& aName) { 1585 QM_TRY_UNWRAP( 1586 auto typeResult, 1587 QM_OR_ELSE_LOG_VERBOSE( 1588 FileSystemContentTypeGuess::FromPath(aName), 1589 ([](const auto& aRv) -> Result<ContentType, QMResult> { 1590 const nsresult rv = ToNSResult(aRv); 1591 switch (rv) { 1592 case NS_ERROR_FAILURE: /* There is an unknown new extension. */ 1593 return ContentType(""_ns); /* We clear the old extension. */ 1594 1595 case NS_ERROR_INVALID_ARG: /* The name is garbled. */ 1596 [[fallthrough]]; 1597 case NS_ERROR_NOT_AVAILABLE: /* There is no extension. */ 1598 return VoidCString(); /* We keep the old extension. */ 1599 1600 default: 1601 MOZ_ASSERT_UNREACHABLE("Should never get here!"); 1602 return Err(aRv); 1603 } 1604 })), 1605 ContentType(""_ns)); 1606 1607 return typeResult; 1608 } 1609 1610 } // namespace fs::data 1611 1612 } // namespace mozilla::dom