FileSystemFileManager.cpp (11938B)
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 "FileSystemFileManager.h" 8 9 #include "FileSystemDataManager.h" 10 #include "FileSystemHashSource.h" 11 #include "FileSystemParentTypes.h" 12 #include "mozilla/Assertions.h" 13 #include "mozilla/Result.h" 14 #include "mozilla/ResultVariant.h" 15 #include "mozilla/dom/quota/QuotaManager.h" 16 #include "mozilla/dom/quota/ResultExtensions.h" 17 #include "nsCOMPtr.h" 18 #include "nsHashKeys.h" 19 #include "nsIFile.h" 20 #include "nsIFileProtocolHandler.h" 21 #include "nsIFileURL.h" 22 #include "nsIURIMutator.h" 23 #include "nsTHashMap.h" 24 #include "nsXPCOM.h" 25 26 namespace mozilla::dom::fs::data { 27 28 namespace { 29 30 constexpr nsLiteralString kDatabaseFileName = u"metadata.sqlite"_ns; 31 32 Result<nsCOMPtr<nsIFile>, QMResult> GetFileDestination( 33 const nsCOMPtr<nsIFile>& aTopDirectory, const FileId& aFileId) { 34 MOZ_ASSERT(32u == aFileId.Value().Length()); 35 36 nsCOMPtr<nsIFile> destination; 37 38 // nsIFile Clone is not a constant method 39 QM_TRY(QM_TO_RESULT(aTopDirectory->Clone(getter_AddRefs(destination)))); 40 41 QM_TRY_UNWRAP(Name encoded, FileSystemHashSource::EncodeHash(aFileId)); 42 43 MOZ_ALWAYS_TRUE(IsAscii(encoded)); 44 45 nsString relativePath; 46 relativePath.Append(Substring(encoded, 0, 2)); 47 48 QM_TRY(QM_TO_RESULT(destination->AppendRelativePath(relativePath))); 49 50 QM_TRY(QM_TO_RESULT(destination->AppendRelativePath(encoded))); 51 52 return destination; 53 } 54 55 Result<nsCOMPtr<nsIFile>, QMResult> GetOrCreateFileImpl( 56 const nsAString& aFilePath) { 57 MOZ_ASSERT(!aFilePath.IsEmpty()); 58 59 QM_TRY_UNWRAP(nsCOMPtr<nsIFile> result, 60 QM_TO_RESULT_TRANSFORM(quota::QM_NewLocalFile(aFilePath))); 61 62 bool exists = true; 63 QM_TRY(QM_TO_RESULT(result->Exists(&exists))); 64 65 if (!exists) { 66 QM_TRY(QM_TO_RESULT(result->Create(nsIFile::NORMAL_FILE_TYPE, 0644))); 67 68 return result; 69 } 70 71 bool isDirectory = true; 72 QM_TRY(QM_TO_RESULT(result->IsDirectory(&isDirectory))); 73 QM_TRY(OkIf(!isDirectory), Err(QMResult(NS_ERROR_FILE_IS_DIRECTORY))); 74 75 return result; 76 } 77 78 Result<nsCOMPtr<nsIFile>, QMResult> GetFile( 79 const nsCOMPtr<nsIFile>& aTopDirectory, const FileId& aFileId) { 80 MOZ_ASSERT(!aFileId.IsEmpty()); 81 82 QM_TRY_UNWRAP(nsCOMPtr<nsIFile> pathObject, 83 GetFileDestination(aTopDirectory, aFileId)); 84 85 nsString desiredPath; 86 QM_TRY(QM_TO_RESULT(pathObject->GetPath(desiredPath))); 87 88 QM_TRY_RETURN(QM_TO_RESULT_TRANSFORM(quota::QM_NewLocalFile(desiredPath))); 89 } 90 91 Result<nsCOMPtr<nsIFile>, QMResult> GetOrCreateFile( 92 const nsCOMPtr<nsIFile>& aTopDirectory, const FileId& aFileId) { 93 MOZ_ASSERT(!aFileId.IsEmpty()); 94 95 QM_TRY_UNWRAP(nsCOMPtr<nsIFile> pathObject, 96 GetFileDestination(aTopDirectory, aFileId)); 97 98 nsString desiredPath; 99 QM_TRY(QM_TO_RESULT(pathObject->GetPath(desiredPath))); 100 101 QM_TRY_UNWRAP(nsCOMPtr<nsIFile> result, GetOrCreateFileImpl(desiredPath)); 102 103 return result; 104 } 105 106 nsresult RemoveFileObject(const nsCOMPtr<nsIFile>& aFilePtr) { 107 // If we cannot tell whether the object is file or directory, or it is a 108 // directory, it is abandoned as an unknown object. If an attempt is made to 109 // create a new object with the same path on disk, we regenerate the FileId 110 // until the collision is resolved. 111 112 bool isFile = false; 113 QM_TRY(MOZ_TO_RESULT(aFilePtr->IsFile(&isFile))); 114 115 QM_TRY(OkIf(isFile), NS_ERROR_FILE_IS_DIRECTORY); 116 117 QM_TRY(QM_TO_RESULT(aFilePtr->Remove(/* recursive */ false))); 118 119 return NS_OK; 120 } 121 122 #ifdef DEBUG 123 // Unused in release builds 124 Result<Usage, QMResult> GetFileSize(const nsCOMPtr<nsIFile>& aFileObject) { 125 bool exists = false; 126 QM_TRY(QM_TO_RESULT(aFileObject->Exists(&exists))); 127 128 if (!exists) { 129 return 0; 130 } 131 132 bool isFile = false; 133 QM_TRY(QM_TO_RESULT(aFileObject->IsFile(&isFile))); 134 135 // We never create directories with this path: this is an unknown object 136 // and the file does not exist 137 QM_TRY(OkIf(isFile), 0); 138 139 QM_TRY_UNWRAP(Usage fileSize, 140 QM_TO_RESULT_INVOKE_MEMBER(aFileObject, GetFileSize)); 141 142 return fileSize; 143 } 144 #endif 145 146 } // namespace 147 148 Result<nsCOMPtr<nsIFile>, QMResult> GetFileSystemDirectory( 149 const quota::OriginMetadata& aOriginMetadata) { 150 MOZ_ASSERT(aOriginMetadata.mPersistenceType == 151 quota::PERSISTENCE_TYPE_DEFAULT); 152 153 quota::QuotaManager* quotaManager = quota::QuotaManager::Get(); 154 MOZ_ASSERT(quotaManager); 155 156 QM_TRY_UNWRAP(nsCOMPtr<nsIFile> fileSystemDirectory, 157 QM_TO_RESULT_TRANSFORM( 158 quotaManager->GetOriginDirectory(aOriginMetadata))); 159 160 QM_TRY(QM_TO_RESULT(fileSystemDirectory->AppendRelativePath( 161 NS_LITERAL_STRING_FROM_CSTRING(FILESYSTEM_DIRECTORY_NAME)))); 162 163 return fileSystemDirectory; 164 } 165 166 nsresult EnsureFileSystemDirectory( 167 const quota::OriginMetadata& aOriginMetadata) { 168 quota::QuotaManager* quotaManager = quota::QuotaManager::Get(); 169 MOZ_ASSERT(quotaManager); 170 171 QM_TRY_INSPECT( 172 const auto& fileSystemDirectory, 173 quotaManager->GetOrCreateTemporaryOriginDirectory(aOriginMetadata)); 174 175 QM_TRY(QM_TO_RESULT(fileSystemDirectory->AppendRelativePath( 176 NS_LITERAL_STRING_FROM_CSTRING(FILESYSTEM_DIRECTORY_NAME)))); 177 178 bool exists = true; 179 QM_TRY(QM_TO_RESULT(fileSystemDirectory->Exists(&exists))); 180 181 if (!exists) { 182 QM_TRY(QM_TO_RESULT( 183 fileSystemDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755))); 184 185 return NS_OK; 186 } 187 188 bool isDirectory = true; 189 QM_TRY(QM_TO_RESULT(fileSystemDirectory->IsDirectory(&isDirectory))); 190 QM_TRY(OkIf(isDirectory), NS_ERROR_FILE_NOT_DIRECTORY); 191 192 return NS_OK; 193 } 194 195 Result<nsCOMPtr<nsIFile>, QMResult> GetDatabaseFile( 196 const quota::OriginMetadata& aOriginMetadata) { 197 MOZ_ASSERT(!aOriginMetadata.mOrigin.IsEmpty()); 198 199 QM_TRY_UNWRAP(nsCOMPtr<nsIFile> databaseFile, 200 GetFileSystemDirectory(aOriginMetadata)); 201 202 QM_TRY(QM_TO_RESULT(databaseFile->AppendRelativePath(kDatabaseFileName))); 203 204 return databaseFile; 205 } 206 207 /** 208 * TODO: This is almost identical to the corresponding function of IndexedDB 209 */ 210 Result<nsCOMPtr<nsIFileURL>, QMResult> GetDatabaseFileURL( 211 const quota::OriginMetadata& aOriginMetadata, 212 const int64_t aDirectoryLockId) { 213 MOZ_ASSERT(aDirectoryLockId >= -1); 214 215 QM_TRY_UNWRAP(nsCOMPtr<nsIFile> databaseFile, 216 GetDatabaseFile(aOriginMetadata)); 217 218 QM_TRY_INSPECT( 219 const auto& protocolHandler, 220 QM_TO_RESULT_TRANSFORM(MOZ_TO_RESULT_GET_TYPED( 221 nsCOMPtr<nsIProtocolHandler>, MOZ_SELECT_OVERLOAD(do_GetService), 222 NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "file"))); 223 224 QM_TRY_INSPECT(const auto& fileHandler, 225 QM_TO_RESULT_TRANSFORM(MOZ_TO_RESULT_GET_TYPED( 226 nsCOMPtr<nsIFileProtocolHandler>, 227 MOZ_SELECT_OVERLOAD(do_QueryInterface), protocolHandler))); 228 229 QM_TRY_INSPECT(const auto& mutator, 230 QM_TO_RESULT_TRANSFORM(MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( 231 nsCOMPtr<nsIURIMutator>, fileHandler, NewFileURIMutator, 232 databaseFile))); 233 234 // aDirectoryLockId should only be -1 when we are called from 235 // FileSystemQuotaClient::InitOrigin when the temporary storage hasn't been 236 // initialized yet. At that time, the in-memory objects (e.g. OriginInfo) are 237 // only being created so it doesn't make sense to tunnel quota information to 238 // QuotaVFS to get corresponding QuotaObject instances for SQLite files. 239 const nsCString directoryLockIdClause = 240 "&directoryLockId="_ns + IntToCString(aDirectoryLockId); 241 242 nsCOMPtr<nsIFileURL> result; 243 QM_TRY(QM_TO_RESULT(NS_MutateURI(mutator) 244 .SetQuery("cache=private"_ns + directoryLockIdClause) 245 .Finalize(result))); 246 247 return result; 248 } 249 250 /* static */ 251 Result<FileSystemFileManager, QMResult> 252 FileSystemFileManager::CreateFileSystemFileManager( 253 nsCOMPtr<nsIFile>&& topDirectory) { 254 return FileSystemFileManager(std::move(topDirectory)); 255 } 256 257 /* static */ 258 Result<UniquePtr<FileSystemFileManager>, QMResult> 259 FileSystemFileManager::CreateFileSystemFileManager( 260 const quota::OriginMetadata& aOriginMetadata) { 261 QM_TRY_UNWRAP(nsCOMPtr<nsIFile> topDirectory, 262 GetFileSystemDirectory(aOriginMetadata)); 263 264 return MakeUnique<FileSystemFileManager>( 265 FileSystemFileManager(std::move(topDirectory))); 266 } 267 268 FileSystemFileManager::FileSystemFileManager(nsCOMPtr<nsIFile>&& aTopDirectory) 269 : mTopDirectory(std::move(aTopDirectory)) {} 270 271 Result<nsCOMPtr<nsIFile>, QMResult> FileSystemFileManager::GetFile( 272 const FileId& aFileId) const { 273 return data::GetFile(mTopDirectory, aFileId); 274 } 275 276 Result<nsCOMPtr<nsIFile>, QMResult> FileSystemFileManager::GetOrCreateFile( 277 const FileId& aFileId) { 278 return data::GetOrCreateFile(mTopDirectory, aFileId); 279 } 280 281 Result<nsCOMPtr<nsIFile>, QMResult> FileSystemFileManager::CreateFileFrom( 282 const FileId& aDestinationFileId, const FileId& aSourceFileId) { 283 MOZ_ASSERT(!aDestinationFileId.IsEmpty()); 284 MOZ_ASSERT(!aSourceFileId.IsEmpty()); 285 286 QM_TRY_UNWRAP(nsCOMPtr<nsIFile> original, GetFile(aSourceFileId)); 287 288 QM_TRY_UNWRAP(nsCOMPtr<nsIFile> destination, 289 GetFileDestination(mTopDirectory, aDestinationFileId)); 290 291 nsAutoString leafName; 292 QM_TRY(QM_TO_RESULT(destination->GetLeafName(leafName))); 293 294 nsCOMPtr<nsIFile> destParent; 295 QM_TRY(QM_TO_RESULT(destination->GetParent(getter_AddRefs(destParent)))); 296 297 QM_TRY(QM_TO_RESULT(original->CopyTo(destParent, leafName))); 298 299 #ifdef DEBUG 300 bool exists = false; 301 QM_TRY(QM_TO_RESULT(destination->Exists(&exists))); 302 MOZ_ASSERT(exists); 303 304 int64_t destSize = 0; 305 QM_TRY(QM_TO_RESULT(destination->GetFileSize(&destSize))); 306 307 int64_t origSize = 0; 308 QM_TRY(QM_TO_RESULT(original->GetFileSize(&origSize))); 309 310 MOZ_ASSERT(destSize == origSize); 311 #endif 312 313 return destination; 314 } 315 316 Result<Usage, QMResult> FileSystemFileManager::RemoveFile( 317 const FileId& aFileId) { 318 MOZ_ASSERT(!aFileId.IsEmpty()); 319 QM_TRY_UNWRAP(nsCOMPtr<nsIFile> pathObject, 320 GetFileDestination(mTopDirectory, aFileId)); 321 322 bool exists = false; 323 QM_TRY(QM_TO_RESULT(pathObject->Exists(&exists))); 324 325 if (!exists) { 326 return 0; 327 } 328 329 bool isFile = false; 330 QM_TRY(QM_TO_RESULT(pathObject->IsFile(&isFile))); 331 332 // We could handle this also as a nonexistent file. 333 if (!isFile) { 334 return Err(QMResult(NS_ERROR_FILE_IS_DIRECTORY)); 335 } 336 337 Usage totalUsage = 0; 338 #ifdef DEBUG 339 QM_TRY_UNWRAP(totalUsage, 340 QM_TO_RESULT_INVOKE_MEMBER(pathObject, GetFileSize)); 341 #endif 342 343 QM_TRY(QM_TO_RESULT(pathObject->Remove(/* recursive */ false))); 344 345 return totalUsage; 346 } 347 348 Result<DebugOnly<Usage>, QMResult> FileSystemFileManager::RemoveFiles( 349 const nsTArray<FileId>& aFileIds, nsTArray<FileId>& aFailedRemovals) { 350 if (aFileIds.IsEmpty()) { 351 return DebugOnly<Usage>(0); 352 } 353 354 CheckedInt64 totalUsage = 0; 355 for (const auto& someId : aFileIds) { 356 QM_WARNONLY_TRY_UNWRAP(Maybe<nsCOMPtr<nsIFile>> maybeFile, 357 GetFileDestination(mTopDirectory, someId)); 358 if (!maybeFile) { 359 aFailedRemovals.AppendElement(someId); 360 continue; 361 } 362 nsCOMPtr<nsIFile> fileObject = maybeFile.value(); 363 364 // Size recorded at close is checked to be equal to the sum of sizes on disk 365 #ifdef DEBUG 366 QM_WARNONLY_TRY_UNWRAP(Maybe<Usage> fileSize, GetFileSize(fileObject)); 367 if (!fileSize) { 368 aFailedRemovals.AppendElement(someId); 369 continue; 370 } 371 totalUsage += fileSize.value(); 372 #endif 373 374 QM_WARNONLY_TRY_UNWRAP(Maybe<Ok> ok, 375 MOZ_TO_RESULT(RemoveFileObject(fileObject))); 376 if (!ok) { 377 aFailedRemovals.AppendElement(someId); 378 } 379 } 380 381 MOZ_ASSERT(totalUsage.isValid()); 382 383 return DebugOnly<Usage>(totalUsage.value()); 384 } 385 386 } // namespace mozilla::dom::fs::data