LCOV - code coverage report
Current view: top level - dom/media/gmp - GMPDiskStorage.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 236 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 17 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2             : /* This Source Code Form is subject to the terms of the Mozilla Public
       3             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       4             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       5             : 
       6             : #include "plhash.h"
       7             : #include "nsDirectoryServiceUtils.h"
       8             : #include "nsDirectoryServiceDefs.h"
       9             : #include "nsAppDirectoryServiceDefs.h"
      10             : #include "GMPParent.h"
      11             : #include "gmp-storage.h"
      12             : #include "mozilla/Unused.h"
      13             : #include "mozilla/EndianUtils.h"
      14             : #include "nsClassHashtable.h"
      15             : #include "prio.h"
      16             : #include "mozIGeckoMediaPluginService.h"
      17             : #include "nsContentCID.h"
      18             : #include "nsServiceManagerUtils.h"
      19             : #include "nsISimpleEnumerator.h"
      20             : 
      21             : namespace mozilla {
      22             : 
      23             : #ifdef LOG
      24             : #undef LOG
      25             : #endif
      26             : 
      27             : extern LogModule* GetGMPLog();
      28             : 
      29             : #define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
      30             : #define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
      31             : 
      32             : namespace gmp {
      33             : 
      34             : // We store the records for a given GMP as files in the profile dir.
      35             : // $profileDir/gmp/$platform/$gmpName/storage/$nodeId/
      36             : static nsresult
      37           0 : GetGMPStorageDir(nsIFile** aTempDir,
      38             :                  const nsString& aGMPName,
      39             :                  const nsCString& aNodeId)
      40             : {
      41           0 :   if (NS_WARN_IF(!aTempDir)) {
      42           0 :     return NS_ERROR_INVALID_ARG;
      43             :   }
      44             : 
      45             :   nsCOMPtr<mozIGeckoMediaPluginChromeService> mps =
      46           0 :     do_GetService("@mozilla.org/gecko-media-plugin-service;1");
      47           0 :   if (NS_WARN_IF(!mps)) {
      48           0 :     return NS_ERROR_FAILURE;
      49             :   }
      50             : 
      51           0 :   nsCOMPtr<nsIFile> tmpFile;
      52           0 :   nsresult rv = mps->GetStorageDir(getter_AddRefs(tmpFile));
      53           0 :   if (NS_WARN_IF(NS_FAILED(rv))) {
      54           0 :     return rv;
      55             :   }
      56             : 
      57           0 :   rv = tmpFile->Append(aGMPName);
      58           0 :   if (NS_WARN_IF(NS_FAILED(rv))) {
      59           0 :     return rv;
      60             :   }
      61             : 
      62           0 :   rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
      63           0 :   if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
      64           0 :     return rv;
      65             :   }
      66             : 
      67           0 :   rv = tmpFile->AppendNative(NS_LITERAL_CSTRING("storage"));
      68           0 :   if (NS_WARN_IF(NS_FAILED(rv))) {
      69           0 :     return rv;
      70             :   }
      71             : 
      72           0 :   rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
      73           0 :   if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
      74           0 :     return rv;
      75             :   }
      76             : 
      77           0 :   rv = tmpFile->AppendNative(aNodeId);
      78           0 :   if (NS_WARN_IF(NS_FAILED(rv))) {
      79           0 :     return rv;
      80             :   }
      81             : 
      82           0 :   rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
      83           0 :   if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
      84           0 :     return rv;
      85             :   }
      86             : 
      87           0 :   tmpFile.forget(aTempDir);
      88             : 
      89           0 :   return NS_OK;
      90             : }
      91             : 
      92             : // Disk-backed GMP storage. Records are stored in files on disk in
      93             : // the profile directory. The record name is a hash of the filename,
      94             : // and we resolve hash collisions by just adding 1 to the hash code.
      95             : // The format of records on disk is:
      96             : //   4 byte, uint32_t $recordNameLength, in little-endian byte order,
      97             : //   record name (i.e. $recordNameLength bytes, no null terminator)
      98             : //   record bytes (entire remainder of file)
      99             : class GMPDiskStorage : public GMPStorage {
     100             : public:
     101           0 :   explicit GMPDiskStorage(const nsCString& aNodeId,
     102             :                           const nsString& aGMPName)
     103           0 :     : mNodeId(aNodeId)
     104           0 :     , mGMPName(aGMPName)
     105             :   {
     106           0 :   }
     107             : 
     108           0 :   ~GMPDiskStorage() {
     109             :     // Close all open file handles.
     110           0 :     for (auto iter = mRecords.ConstIter(); !iter.Done(); iter.Next()) {
     111           0 :       Record* record = iter.UserData();
     112           0 :       if (record->mFileDesc) {
     113           0 :         PR_Close(record->mFileDesc);
     114           0 :         record->mFileDesc = nullptr;
     115             :       }
     116             :     }
     117           0 :   }
     118             : 
     119           0 :   nsresult Init() {
     120             :     // Build our index of records on disk.
     121           0 :     nsCOMPtr<nsIFile> storageDir;
     122           0 :     nsresult rv = GetGMPStorageDir(getter_AddRefs(storageDir), mGMPName, mNodeId);
     123           0 :     if (NS_WARN_IF(NS_FAILED(rv))) {
     124           0 :       return NS_ERROR_FAILURE;
     125             :     }
     126             : 
     127           0 :     DirectoryEnumerator iter(storageDir, DirectoryEnumerator::FilesAndDirs);
     128           0 :     for (nsCOMPtr<nsIFile> dirEntry; (dirEntry = iter.Next()) != nullptr;) {
     129           0 :       PRFileDesc* fd = nullptr;
     130           0 :       if (NS_FAILED(dirEntry->OpenNSPRFileDesc(PR_RDONLY, 0, &fd))) {
     131           0 :         continue;
     132             :       }
     133           0 :       int32_t recordLength = 0;
     134           0 :       nsCString recordName;
     135           0 :       nsresult err = ReadRecordMetadata(fd, recordLength, recordName);
     136           0 :       PR_Close(fd);
     137           0 :       if (NS_FAILED(err)) {
     138             :         // File is not a valid storage file. Don't index it. Delete the file,
     139             :         // to make our indexing faster in future.
     140           0 :         dirEntry->Remove(false);
     141           0 :         continue;
     142             :       }
     143             : 
     144           0 :       nsAutoString filename;
     145           0 :       rv = dirEntry->GetLeafName(filename);
     146           0 :       if (NS_FAILED(rv)) {
     147           0 :         continue;
     148             :       }
     149             : 
     150           0 :       mRecords.Put(recordName, new Record(filename, recordName));
     151             :     }
     152             : 
     153           0 :     return NS_OK;
     154             :   }
     155             : 
     156           0 :   GMPErr Open(const nsCString& aRecordName) override
     157             :   {
     158           0 :     MOZ_ASSERT(!IsOpen(aRecordName));
     159             :     nsresult rv;
     160           0 :     Record* record = nullptr;
     161           0 :     if (!mRecords.Get(aRecordName, &record)) {
     162             :       // New file.
     163           0 :       nsAutoString filename;
     164           0 :       rv = GetUnusedFilename(aRecordName, filename);
     165           0 :       if (NS_WARN_IF(NS_FAILED(rv))) {
     166           0 :         return GMPGenericErr;
     167             :       }
     168           0 :       record = new Record(filename, aRecordName);
     169           0 :       mRecords.Put(aRecordName, record);
     170             :     }
     171             : 
     172           0 :     MOZ_ASSERT(record);
     173           0 :     if (record->mFileDesc) {
     174           0 :       NS_WARNING("Tried to open already open record");
     175           0 :       return GMPRecordInUse;
     176             :     }
     177             : 
     178           0 :     rv = OpenStorageFile(record->mFilename, ReadWrite, &record->mFileDesc);
     179           0 :     if (NS_WARN_IF(NS_FAILED(rv))) {
     180           0 :       return GMPGenericErr;
     181             :     }
     182             : 
     183           0 :     MOZ_ASSERT(IsOpen(aRecordName));
     184             : 
     185           0 :     return GMPNoErr;
     186             :   }
     187             : 
     188           0 :   bool IsOpen(const nsCString& aRecordName) const override {
     189             :     // We are open if we have a record indexed, and it has a valid
     190             :     // file descriptor.
     191           0 :     const Record* record = mRecords.Get(aRecordName);
     192           0 :     return record && !!record->mFileDesc;
     193             :   }
     194             : 
     195           0 :   GMPErr Read(const nsCString& aRecordName,
     196             :               nsTArray<uint8_t>& aOutBytes) override
     197             :   {
     198           0 :     if (!IsOpen(aRecordName)) {
     199           0 :       return GMPClosedErr;
     200             :     }
     201             : 
     202           0 :     Record* record = nullptr;
     203           0 :     mRecords.Get(aRecordName, &record);
     204           0 :     MOZ_ASSERT(record && !!record->mFileDesc); // IsOpen() guarantees this.
     205             : 
     206             :     // Our error strategy is to report records with invalid contents as
     207             :     // containing 0 bytes. Zero length records are considered "deleted" by
     208             :     // the GMPStorage API.
     209           0 :     aOutBytes.SetLength(0);
     210             : 
     211           0 :     int32_t recordLength = 0;
     212           0 :     nsCString recordName;
     213           0 :     nsresult err = ReadRecordMetadata(record->mFileDesc,
     214             :                                       recordLength,
     215           0 :                                       recordName);
     216           0 :     if (NS_FAILED(err) || recordLength == 0) {
     217             :       // We failed to read the record metadata. Or the record is 0 length.
     218             :       // Treat damaged records as empty.
     219             :       // ReadRecordMetadata() could fail if the GMP opened a new record and
     220             :       // tried to read it before anything was written to it..
     221           0 :       return GMPNoErr;
     222             :     }
     223             : 
     224           0 :     if (!aRecordName.Equals(recordName)) {
     225           0 :       NS_WARNING("Record file contains some other record's contents!");
     226           0 :       return GMPRecordCorrupted;
     227             :     }
     228             : 
     229             :     // After calling ReadRecordMetadata, we should be ready to read the
     230             :     // record data.
     231           0 :     if (PR_Available(record->mFileDesc) != recordLength) {
     232           0 :       NS_WARNING("Record file length mismatch!");
     233           0 :       return GMPRecordCorrupted;
     234             :     }
     235             : 
     236           0 :     aOutBytes.SetLength(recordLength);
     237           0 :     int32_t bytesRead = PR_Read(record->mFileDesc, aOutBytes.Elements(), recordLength);
     238           0 :     return (bytesRead == recordLength) ? GMPNoErr : GMPRecordCorrupted;
     239             :   }
     240             : 
     241           0 :   GMPErr Write(const nsCString& aRecordName,
     242             :                const nsTArray<uint8_t>& aBytes) override
     243             :   {
     244           0 :     if (!IsOpen(aRecordName)) {
     245           0 :       return GMPClosedErr;
     246             :     }
     247             : 
     248           0 :     Record* record = nullptr;
     249           0 :     mRecords.Get(aRecordName, &record);
     250           0 :     MOZ_ASSERT(record && !!record->mFileDesc); // IsOpen() guarantees this.
     251             : 
     252             :     // Write operations overwrite the entire record. So close it now.
     253           0 :     PR_Close(record->mFileDesc);
     254           0 :     record->mFileDesc = nullptr;
     255             : 
     256             :     // Writing 0 bytes means removing (deleting) the file.
     257           0 :     if (aBytes.Length() == 0) {
     258           0 :       nsresult rv = RemoveStorageFile(record->mFilename);
     259           0 :       if (NS_WARN_IF(NS_FAILED(rv))) {
     260             :         // Could not delete file -> Continue with trying to erase the contents.
     261             :       } else {
     262           0 :         return GMPNoErr;
     263             :       }
     264             :     }
     265             : 
     266             :     // Write operations overwrite the entire record. So re-open the file
     267             :     // in truncate mode, to clear its contents.
     268           0 :     if (NS_FAILED(OpenStorageFile(record->mFilename,
     269             :                                   Truncate,
     270             :                                   &record->mFileDesc))) {
     271           0 :       return GMPGenericErr;
     272             :     }
     273             : 
     274             :     // Store the length of the record name followed by the record name
     275             :     // at the start of the file.
     276           0 :     int32_t bytesWritten = 0;
     277           0 :     char buf[sizeof(uint32_t)] = {0};
     278           0 :     LittleEndian::writeUint32(buf, aRecordName.Length());
     279           0 :     bytesWritten = PR_Write(record->mFileDesc, buf, MOZ_ARRAY_LENGTH(buf));
     280           0 :     if (bytesWritten != MOZ_ARRAY_LENGTH(buf)) {
     281           0 :       NS_WARNING("Failed to write GMPStorage record name length.");
     282           0 :       return GMPRecordCorrupted;
     283             :     }
     284           0 :     bytesWritten = PR_Write(record->mFileDesc,
     285           0 :                             aRecordName.get(),
     286           0 :                             aRecordName.Length());
     287           0 :     if (bytesWritten != (int32_t)aRecordName.Length()) {
     288           0 :       NS_WARNING("Failed to write GMPStorage record name.");
     289           0 :       return GMPRecordCorrupted;
     290             :     }
     291             : 
     292           0 :     bytesWritten = PR_Write(record->mFileDesc, aBytes.Elements(), aBytes.Length());
     293           0 :     if (bytesWritten != (int32_t)aBytes.Length()) {
     294           0 :       NS_WARNING("Failed to write GMPStorage record data.");
     295           0 :       return GMPRecordCorrupted;
     296             :     }
     297             : 
     298             :     // Try to sync the file to disk, so that in the event of a crash,
     299             :     // the record is less likely to be corrupted.
     300           0 :     PR_Sync(record->mFileDesc);
     301             : 
     302           0 :     return GMPNoErr;
     303             :   }
     304             : 
     305           0 :   void Close(const nsCString& aRecordName) override
     306             :   {
     307           0 :     Record* record = nullptr;
     308           0 :     mRecords.Get(aRecordName, &record);
     309           0 :     if (record && !!record->mFileDesc) {
     310           0 :       PR_Close(record->mFileDesc);
     311           0 :       record->mFileDesc = nullptr;
     312             :     }
     313           0 :     MOZ_ASSERT(!IsOpen(aRecordName));
     314           0 :   }
     315             : 
     316             : private:
     317             : 
     318             :   // We store records in a file which is a hash of the record name.
     319             :   // If there is a hash collision, we just keep adding 1 to the hash
     320             :   // code, until we find a free slot.
     321           0 :   nsresult GetUnusedFilename(const nsACString& aRecordName,
     322             :                              nsString& aOutFilename)
     323             :   {
     324           0 :     nsCOMPtr<nsIFile> storageDir;
     325           0 :     nsresult rv = GetGMPStorageDir(getter_AddRefs(storageDir), mGMPName, mNodeId);
     326           0 :     if (NS_WARN_IF(NS_FAILED(rv))) {
     327           0 :       return rv;
     328             :     }
     329             : 
     330           0 :     uint64_t recordNameHash = HashString(PromiseFlatCString(aRecordName).get());
     331           0 :     for (int i = 0; i < 1000000; i++) {
     332           0 :       nsCOMPtr<nsIFile> f;
     333           0 :       rv = storageDir->Clone(getter_AddRefs(f));
     334           0 :       if (NS_WARN_IF(NS_FAILED(rv))) {
     335           0 :         return rv;
     336             :       }
     337           0 :       nsAutoString hashStr;
     338           0 :       hashStr.AppendInt(recordNameHash);
     339           0 :       rv = f->Append(hashStr);
     340           0 :       if (NS_WARN_IF(NS_FAILED(rv))) {
     341           0 :         return rv;
     342             :       }
     343           0 :       bool exists = false;
     344           0 :       f->Exists(&exists);
     345           0 :       if (!exists) {
     346             :         // Filename not in use, we can write into this file.
     347           0 :         aOutFilename = hashStr;
     348           0 :         return NS_OK;
     349             :       } else {
     350             :         // Hash collision; just increment the hash name and try that again.
     351           0 :         ++recordNameHash;
     352           0 :         continue;
     353             :       }
     354             :     }
     355             :     // Somehow, we've managed to completely fail to find a vacant file name.
     356             :     // Give up.
     357           0 :     NS_WARNING("GetUnusedFilename had extreme hash collision!");
     358           0 :     return NS_ERROR_FAILURE;
     359             :   }
     360             : 
     361             :   enum OpenFileMode  { ReadWrite, Truncate };
     362             : 
     363           0 :   nsresult OpenStorageFile(const nsAString& aFileLeafName,
     364             :                            const OpenFileMode aMode,
     365             :                            PRFileDesc** aOutFD)
     366             :   {
     367           0 :     MOZ_ASSERT(aOutFD);
     368             : 
     369           0 :     nsCOMPtr<nsIFile> f;
     370           0 :     nsresult rv = GetGMPStorageDir(getter_AddRefs(f), mGMPName, mNodeId);
     371           0 :     if (NS_WARN_IF(NS_FAILED(rv))) {
     372           0 :       return rv;
     373             :     }
     374           0 :     f->Append(aFileLeafName);
     375             : 
     376           0 :     auto mode = PR_RDWR | PR_CREATE_FILE;
     377           0 :     if (aMode == Truncate) {
     378           0 :       mode |= PR_TRUNCATE;
     379             :     }
     380             : 
     381           0 :     return f->OpenNSPRFileDesc(mode, PR_IRWXU, aOutFD);
     382             :   }
     383             : 
     384           0 :   nsresult ReadRecordMetadata(PRFileDesc* aFd,
     385             :                               int32_t& aOutRecordLength,
     386             :                               nsACString& aOutRecordName)
     387             :   {
     388           0 :     int32_t offset = PR_Seek(aFd, 0, PR_SEEK_END);
     389           0 :     PR_Seek(aFd, 0, PR_SEEK_SET);
     390             : 
     391           0 :     if (offset < 0 || offset > GMP_MAX_RECORD_SIZE) {
     392             :       // Refuse to read big records, or records where we can't get a length.
     393           0 :       return NS_ERROR_FAILURE;
     394             :     }
     395           0 :     const uint32_t fileLength = static_cast<uint32_t>(offset);
     396             : 
     397             :     // At the start of the file the length of the record name is stored in a
     398             :     // uint32_t (little endian byte order) followed by the record name at the
     399             :     // start of the file. The record name is not null terminated. The remainder
     400             :     // of the file is the record's data.
     401             : 
     402           0 :     if (fileLength < sizeof(uint32_t)) {
     403             :       // Record file doesn't have enough contents to store the record name
     404             :       // length. Fail.
     405           0 :       return NS_ERROR_FAILURE;
     406             :     }
     407             : 
     408             :     // Read length, and convert to host byte order.
     409           0 :     uint32_t recordNameLength = 0;
     410           0 :     char buf[sizeof(recordNameLength)] = { 0 };
     411           0 :     int32_t bytesRead = PR_Read(aFd, &buf, sizeof(recordNameLength));
     412           0 :     recordNameLength = LittleEndian::readUint32(buf);
     413           0 :     if (sizeof(recordNameLength) != bytesRead ||
     414           0 :         recordNameLength == 0 ||
     415           0 :         recordNameLength + sizeof(recordNameLength) > fileLength ||
     416             :         recordNameLength > GMP_MAX_RECORD_NAME_SIZE) {
     417             :       // Record file has invalid contents. Fail.
     418           0 :       return NS_ERROR_FAILURE;
     419             :     }
     420             : 
     421           0 :     nsCString recordName;
     422           0 :     recordName.SetLength(recordNameLength);
     423           0 :     bytesRead = PR_Read(aFd, recordName.BeginWriting(), recordNameLength);
     424           0 :     if ((uint32_t)bytesRead != recordNameLength) {
     425             :       // Read failed.
     426           0 :       return NS_ERROR_FAILURE;
     427             :     }
     428             : 
     429           0 :     MOZ_ASSERT(fileLength >= sizeof(recordNameLength) + recordNameLength);
     430           0 :     int32_t recordLength = fileLength - (sizeof(recordNameLength) + recordNameLength);
     431             : 
     432           0 :     aOutRecordLength = recordLength;
     433           0 :     aOutRecordName = recordName;
     434             : 
     435             :     // Read cursor should be positioned after the record name, before the record contents.
     436           0 :     if (PR_Seek(aFd, 0, PR_SEEK_CUR) != (int32_t)(sizeof(recordNameLength) + recordNameLength)) {
     437           0 :       NS_WARNING("Read cursor mismatch after ReadRecordMetadata()");
     438           0 :       return NS_ERROR_FAILURE;
     439             :     }
     440             : 
     441           0 :     return NS_OK;
     442             :   }
     443             : 
     444           0 :   nsresult RemoveStorageFile(const nsString& aFilename)
     445             :   {
     446           0 :     nsCOMPtr<nsIFile> f;
     447           0 :     nsresult rv = GetGMPStorageDir(getter_AddRefs(f), mGMPName, mNodeId);
     448           0 :     if (NS_WARN_IF(NS_FAILED(rv))) {
     449           0 :       return rv;
     450             :     }
     451           0 :     rv = f->Append(aFilename);
     452           0 :     if (NS_WARN_IF(NS_FAILED(rv))) {
     453           0 :       return rv;
     454             :     }
     455           0 :     return f->Remove(/* bool recursive= */ false);
     456             :   }
     457             : 
     458             :   struct Record {
     459           0 :     Record(const nsAString& aFilename,
     460             :            const nsACString& aRecordName)
     461           0 :       : mFilename(aFilename)
     462             :       , mRecordName(aRecordName)
     463           0 :       , mFileDesc(0)
     464           0 :     {}
     465           0 :     ~Record() {
     466           0 :       MOZ_ASSERT(!mFileDesc);
     467           0 :     }
     468             :     nsString mFilename;
     469             :     nsCString mRecordName;
     470             :     PRFileDesc* mFileDesc;
     471             :   };
     472             : 
     473             :   // Hash record name to record data.
     474             :   nsClassHashtable<nsCStringHashKey, Record> mRecords;
     475             :   const nsCString mNodeId;
     476             :   const nsString mGMPName;
     477             : };
     478             : 
     479           0 : already_AddRefed<GMPStorage> CreateGMPDiskStorage(const nsCString& aNodeId,
     480             :                                                   const nsString& aGMPName)
     481             : {
     482           0 :   RefPtr<GMPDiskStorage> storage(new GMPDiskStorage(aNodeId, aGMPName));
     483           0 :   if (NS_FAILED(storage->Init())) {
     484           0 :     NS_WARNING("Failed to initialize on disk GMP storage");
     485           0 :     return nullptr;
     486             :   }
     487           0 :   return storage.forget();
     488             : }
     489             : 
     490             : } // namespace gmp
     491             : } // namespace mozilla

Generated by: LCOV version 1.13