/* $Id: pdbvfs.cpp 101653 2023-10-30 11:08:56Z vboxsync $ */ /** @file * IPRT - PDB Virtual Filesystem (read only). */ /* * Copyright (C) 2023 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * The contents of this file may alternatively be used under the terms * of the Common Development and Distribution License Version 1.0 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included * in the VirtualBox distribution, in which case the provisions of the * CDDL are applicable instead of those of the GPL. * * You may elect to license modified versions of this file under the * terms and conditions of either the GPL or the CDDL or both. * * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP RTLOGGROUP_FS #include "internal/iprt.h" #include #include #include #include #include #include #include #include #include #include #include #include #include /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** Pointer to an ISO volume (VFS instance data). */ typedef struct RTFSPDBVOL *PRTFSPDBVOL; /** Pointer to a const ISO volume (VFS instance data). */ typedef struct RTFSPDBVOL const *PCRTFSPDBVOL; /** * Substream info. */ typedef struct RTFSPDBSUBSTREAM { /** The parent stream. */ uint32_t idxStream; /** The substream index within the parent. */ uint8_t idxSubstream; /** The offset of the start of the substream within the parent. */ uint32_t offSubstream; /** The size of the substream. */ uint32_t cbSubstream; /** The name of the substream (including the parent one at the start). * Readonly string constant, as substreams are all hardcoded. */ const char *pszName; } RTFSPDBSUBSTREAM; typedef RTFSPDBSUBSTREAM *PRTFSPDBSUBSTREAM; typedef RTFSPDBSUBSTREAM const *PCRTFSPDBSUBSTREAM; /** * Stream info. */ typedef struct RTFSPDBSTREAMINFO { /** The stream name. * Unnamed streams will be set to NULL. Standard streams are assigned * names (C-litterals) the rest is matched up with RTFSPDBVOL::pszzNames * content. */ const char *pszName; /** Size of the stream. */ uint32_t cbStream; /** Number of pages in the stream. */ uint32_t cPages; /** Pointer to the page map for the stream (within RTFSPDBVOL::pbRoot). */ union { void const *pv; PCRTPDB20PAGE pa20; PCRTPDB70PAGE pa70; } PageMap; /** Number of substreams. */ uint8_t cSubstreams; /** The index into RTFSPDBVOL::aSubstreams of the first one. */ uint8_t idxFirstSubstream; } RTFSPDBSTREAMINFO; typedef RTFSPDBSTREAMINFO *PRTFSPDBSTREAMINFO; typedef RTFSPDBSTREAMINFO const *PCRTFSPDBSTREAMINFO; /** * Private data for a VFS file object. */ typedef struct RTFSPDBFILEOBJ { /** Pointer to the PDB volume data. */ PRTFSPDBVOL pPdb; /** The stream number (0 based). */ uint32_t idxStream; /** The start offset of the substream this file object represents. This is * zero for full streams. */ uint32_t offSubstream; /** Size of the stream (or substream). */ uint32_t cbStream; /** Number of pages in the stream (the whole stream, not just the substream). */ uint32_t cPages; /** Pointer to the page map for the stream (within RTFSPDBVOL::pbRoot) * (again the whole stream, not just the substream). */ union { void const *pv; PCRTPDB20PAGE pa20; PCRTPDB70PAGE pa70; } PageMap; /** The current file offset. */ uint64_t offFile; } RTFSPDBFILEOBJ; typedef RTFSPDBFILEOBJ *PRTFSPDBFILEOBJ; /** * Private data for a VFS directory object. */ typedef struct RTFSPDBDIROBJ { /** Pointer to the PDB volume data. */ PRTFSPDBVOL pPdb; /** The next stream and substream number to return info for when reading. */ union { uint32_t idxNext; struct { /** This is zero for the whole stream and non-zero for substreams. */ uint32_t idxNextSubstream : 8; /** The next stream number. */ uint32_t idxNextStream : 24; } s; } u; } RTFSPDBDIROBJ; typedef RTFSPDBDIROBJ *PRTFSPDBDIROBJ; /** * Indicates which PDB version we're accessing. */ typedef enum RTFSPDBVER { /** Invalid zero value. */ RTFSPDBVER_INVALID = 0, /** Accessing a v2.0 PDB. */ RTFSPDBVER_2, /** Accessing a v7.0 PDB. */ RTFSPDBVER_7 } RTFSPDBVER; /** * A PDB volume. */ typedef struct RTFSPDBVOL { /** Handle to itself. */ RTVFS hVfsSelf; /** The file, partition, or whatever backing the PDB file. */ RTVFSFILE hVfsBacking; /** The size of the backing thingy. */ uint64_t cbBacking; /** The size of the backing thingy in sectors (cbSector). */ uint64_t cBackingPages; /** Flags. */ uint32_t fFlags; /** The page size (in bytes). */ uint32_t cbPage; /** The format version. */ RTFSPDBVER enmVersion; /** Number of streams. */ uint32_t cStreams; /** The size of the root stream. */ uint32_t cbRoot; /** Pointer to the root directory bytes. */ uint8_t *pbRoot; /** @name PDB metadata from stream \#1. * @{ */ /** The PDB age. */ uint32_t uAge; /** The PDB timestamp. */ uint32_t uTimestamp; /** The PDB UUID. */ RTUUID Uuid; /** The VC date (see RTPDB70NAMES::uVersion and RTPDB20NAMES::uVersion). */ uint32_t uVcDate; /** Size of the name string table. */ uint32_t cbNames; /** Name string table. */ char *pszzNames; /** @} */ /** The DBI header from stream \#3. */ RTPDBDBIHDR DbiHdr; /** Extra per-stream info. We've do individual allocations here in case we * want to add write support. */ PRTFSPDBSTREAMINFO *papStreamInfo; /** Number of substreams in aSubstreams. */ uint8_t cSubstreams; /** Substreams (only from the DBI atm.). */ RTFSPDBSUBSTREAM aSubstreams[8]; } RTFSPDBVOL; /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ static DECLCALLBACK(int) rtFsPdbVol_OpenRoot(void *pvThis, PRTVFSDIR phVfsDir); /** * Helper for methods returning file information. */ static void rtFsPdbPopulateObjInfo(PRTFSPDBVOL pPdb, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr, uint32_t cbStream, uint32_t idxStream, uint8_t idxSubstream, bool fIsDir) { RTTimeSpecSetNano(&pObjInfo->AccessTime, 0); RTTimeSpecSetNano(&pObjInfo->ModificationTime, 0); RTTimeSpecSetNano(&pObjInfo->ChangeTime, 0); RTTimeSpecSetNano(&pObjInfo->BirthTime, 0); pObjInfo->cbObject = cbStream == UINT32_MAX ? 0 : cbStream; pObjInfo->cbAllocated = (uint64_t)RTPdbSizeToPages(cbStream, pPdb->cbPage) * pPdb->cbPage; pObjInfo->Attr.fMode = RTFS_UNIX_IRUSR | RTFS_UNIX_IRGRP | RTFS_UNIX_IROTH | RTFS_DOS_READONLY; if (!fIsDir) pObjInfo->Attr.fMode |= RTFS_TYPE_FILE; else pObjInfo->Attr.fMode |= RTFS_TYPE_DIRECTORY | RTFS_UNIX_IXUSR | RTFS_UNIX_IXGRP | RTFS_UNIX_IXOTH; pObjInfo->Attr.enmAdditional = enmAddAttr; switch (enmAddAttr) { case RTFSOBJATTRADD_NOTHING: RT_FALL_THRU(); case RTFSOBJATTRADD_UNIX: pObjInfo->Attr.u.Unix.uid = NIL_RTUID; pObjInfo->Attr.u.Unix.gid = NIL_RTGID; pObjInfo->Attr.u.Unix.cHardlinks = 1 + fIsDir; pObjInfo->Attr.u.Unix.INodeIdDevice = 0; pObjInfo->Attr.u.Unix.INodeId = idxStream | ((uint64_t)idxSubstream << 32); pObjInfo->Attr.u.Unix.fFlags = 0; pObjInfo->Attr.u.Unix.GenerationId = 0; pObjInfo->Attr.u.Unix.Device = 0; break; case RTFSOBJATTRADD_UNIX_OWNER: pObjInfo->Attr.u.UnixOwner.uid = 0; pObjInfo->Attr.u.UnixOwner.szName[0] = '\0'; break; case RTFSOBJATTRADD_UNIX_GROUP: pObjInfo->Attr.u.UnixGroup.gid = 0; pObjInfo->Attr.u.UnixGroup.szName[0] = '\0'; break; case RTFSOBJATTRADD_EASIZE: pObjInfo->Attr.u.EASize.cb = 0; break; default: AssertFailedBreak(); } } /** * Helper methods for opening a stream. * * This is used internally w/o any associated RTVFSFILE handle for reading * stream \#1 containing PDB metadata. */ static void rtFsPdbPopulateFileObj(PRTFSPDBFILEOBJ pNewFile, PRTFSPDBVOL pPdb, uint32_t idxStream, uint32_t cbStream, void const *pvPageMap) { pNewFile->pPdb = pPdb; pNewFile->idxStream = idxStream; pNewFile->offSubstream = 0; pNewFile->cbStream = cbStream; pNewFile->PageMap.pv = pvPageMap; pNewFile->cPages = RTPdbSizeToPages(cbStream, pPdb->cbPage); pNewFile->offFile = 0; } /** * Helper methods for opening a stream or substream. */ static void rtFsPdbPopulateFileObjFromInfo(PRTFSPDBFILEOBJ pNewFile, PRTFSPDBVOL pPdb, uint32_t idxStream, PCRTFSPDBSUBSTREAM pSubstream) { PCRTFSPDBSTREAMINFO pInfo = pPdb->papStreamInfo[idxStream]; rtFsPdbPopulateFileObj(pNewFile, pPdb, idxStream, pInfo->cbStream, pInfo->PageMap.pv); Assert(pInfo->cPages == pNewFile->cPages); if (pSubstream) { pNewFile->offSubstream = pSubstream->offSubstream; pNewFile->cbStream = pSubstream->cbSubstream; } } /** * @interface_method_impl{RTVFSOBJOPS,pfnClose} */ static DECLCALLBACK(int) rtFsPdbFile_Close(void *pvThis) { PRTFSPDBFILEOBJ const pThis = (PRTFSPDBFILEOBJ)pvThis; LogFlow(("rtFsPdbFile_Close(%p/%p)\n", pThis, pThis->pPdb)); pThis->pPdb = NULL; return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo} */ static DECLCALLBACK(int) rtFsPdbFile_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) { PRTFSPDBFILEOBJ const pThis = (PRTFSPDBFILEOBJ)pvThis; rtFsPdbPopulateObjInfo(pThis->pPdb, pObjInfo, enmAddAttr, pThis->cbStream, pThis->idxStream, 0, false /*fIsDir*/); return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead} */ static DECLCALLBACK(int) rtFsPdbFile_Read(void *pvThis, RTFOFF off, PRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead) { PRTFSPDBFILEOBJ const pThis = (PRTFSPDBFILEOBJ)pvThis; PRTFSPDBVOL const pPdb = pThis->pPdb; AssertReturn(pSgBuf->cSegs == 1, VERR_INTERNAL_ERROR_3); AssertReturn(pPdb, VERR_INTERNAL_ERROR_2); RT_NOREF(fBlocking); /* Apply default offset and switch to unsigned offset variable. */ uint64_t offFile; if (off == -1) offFile = pThis->offFile; else { AssertReturn(off >= 0, VERR_INTERNAL_ERROR_3); offFile = (uint64_t)off; } /* * Check for EOF and figure out how much to read. */ if (offFile >= pThis->cbStream) { if (pcbRead) { *pcbRead = 0; return VINF_EOF; } return VERR_EOF; } int rcRet = VINF_SUCCESS; size_t cbToRead = RTSgBufCalcLengthLeft(pSgBuf); if ( cbToRead > pThis->cbStream || offFile + cbToRead > pThis->cbStream) { if (!pcbRead) return VERR_EOF; cbToRead = (size_t)(pThis->cbStream - offFile); rcRet = VINF_EOF; } /* * Do it page by page, buffer segment by buffer segment, whatever is smaller. */ uint64_t const offStart = offFile; while (cbToRead > 0) { uint32_t iPageMap = (uint32_t)((offFile + pThis->offSubstream) / pPdb->cbPage); uint32_t const offInPage = (uint32_t)((offFile + pThis->offSubstream) % pPdb->cbPage); size_t cbLeftInPage = pPdb->cbPage - offInPage; if (cbLeftInPage > cbToRead) cbLeftInPage = cbToRead; void * const pvDst = RTSgBufGetCurrentSegment(pSgBuf, cbLeftInPage, &cbLeftInPage); AssertReturn(pvDst, VERR_INTERNAL_ERROR_4); uint64_t const offPageInFile = (pPdb->enmVersion == RTFSPDBVER_2 ? pThis->PageMap.pa20[iPageMap] : pThis->PageMap.pa70[iPageMap]) * (uint64_t)pPdb->cbPage; int rcRead = RTVfsFileReadAt(pPdb->hVfsBacking, offPageInFile + offInPage, pvDst, cbLeftInPage, NULL /*pcbRead*/); if (RT_SUCCESS(rcRead)) { size_t cbAssert = RTSgBufAdvance(pSgBuf, cbLeftInPage); Assert(cbAssert == cbLeftInPage); RT_NOREF(cbAssert); offFile += cbLeftInPage; cbToRead -= cbLeftInPage; } /* If we can return the number of bytes we've read, we'll advance the file offset. Otherwise we won't and return immediately. */ else if (!pcbRead) return rcRead; else { rcRet = rcRead != VERR_EOF ? rcRead : VERR_READ_ERROR; break; } } /* * Update the file position and stuff. */ pThis->offFile = offFile; if (pcbRead) *pcbRead = offFile - offStart; return rcRet; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush} */ static DECLCALLBACK(int) rtFsPdbFile_Flush(void *pvThis) { RT_NOREF(pvThis); return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell} */ static DECLCALLBACK(int) rtFsPdbFile_Tell(void *pvThis, PRTFOFF poffActual) { PRTFSPDBFILEOBJ const pThis = (PRTFSPDBFILEOBJ)pvThis; *poffActual = pThis->offFile; return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSFILEOPS,pfnSeek} */ static DECLCALLBACK(int) rtFsPdbFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual) { PRTFSPDBFILEOBJ const pThis = (PRTFSPDBFILEOBJ)pvThis; RTFOFF offNew; switch (uMethod) { case RTFILE_SEEK_BEGIN: offNew = offSeek; break; case RTFILE_SEEK_END: offNew = (RTFOFF)pThis->cbStream + offSeek; break; case RTFILE_SEEK_CURRENT: offNew = (RTFOFF)pThis->offFile + offSeek; break; default: return VERR_INVALID_PARAMETER; } if (offNew >= 0) { pThis->offFile = offNew; *poffActual = offNew; return VINF_SUCCESS; } return VERR_NEGATIVE_SEEK; } /** * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize} */ static DECLCALLBACK(int) rtFsPdbFile_QuerySize(void *pvThis, uint64_t *pcbFile) { PRTFSPDBFILEOBJ const pThis = (PRTFSPDBFILEOBJ)pvThis; *pcbFile = pThis->cbStream; return VINF_SUCCESS; } /** * PDB FS file operations. */ DECL_HIDDEN_CONST(const RTVFSFILEOPS) g_rtFsPdbFileOps = { { /* Stream */ { /* Obj */ RTVFSOBJOPS_VERSION, RTVFSOBJTYPE_FILE, "PDB File", rtFsPdbFile_Close, rtFsPdbFile_QueryInfo, NULL, RTVFSOBJOPS_VERSION }, RTVFSIOSTREAMOPS_VERSION, RTVFSIOSTREAMOPS_FEAT_NO_SG, rtFsPdbFile_Read, NULL /*pfnWrite*/, rtFsPdbFile_Flush, NULL /*pfnPollOne*/, rtFsPdbFile_Tell, NULL /*pfnSkip*/, NULL /*pfnZeroFill*/, RTVFSIOSTREAMOPS_VERSION, }, RTVFSFILEOPS_VERSION, 0, { /* ObjSet */ RTVFSOBJSETOPS_VERSION, RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj), NULL /*SetMode*/, NULL /*SetTimes*/, NULL /*SetOwner*/, RTVFSOBJSETOPS_VERSION }, rtFsPdbFile_Seek, rtFsPdbFile_QuerySize, NULL /*SetSize*/, NULL /*QueryMaxSize*/, RTVFSFILEOPS_VERSION }; /** * @interface_method_impl{RTVFSOBJOPS,pfnClose} */ static DECLCALLBACK(int) rtFsPdbDir_Close(void *pvThis) { PRTFSPDBDIROBJ const pThis = (PRTFSPDBDIROBJ)pvThis; LogFlow(("rtFsPdbDir_Close(%p/%p)\n", pThis, pThis->pPdb)); pThis->pPdb = NULL; return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo} */ static DECLCALLBACK(int) rtFsPdbDir_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) { PRTFSPDBDIROBJ const pThis = (PRTFSPDBDIROBJ)pvThis; PRTFSPDBVOL const pPdb = pThis->pPdb; rtFsPdbPopulateObjInfo(pPdb, pObjInfo, enmAddAttr, pPdb->cbRoot, 0 /* root dir is stream zero */, 0 /*idxSubstream*/, true /*fIsDir*/); return VINF_SUCCESS; } /** * Helper for looping up a substream name. */ DECLINLINE(PCRTFSPDBSUBSTREAM) rtFsPdbDirLookupSubstream(PRTFSPDBVOL pPdb, uint32_t idxStream, const char *pszName) { uint8_t const idxFirst = idxStream != UINT32_MAX ? pPdb->papStreamInfo[idxStream]->idxFirstSubstream : 0; uint8_t cLeft = idxStream != UINT32_MAX ? pPdb->papStreamInfo[idxStream]->cSubstreams : pPdb->cSubstreams; while (cLeft-- > 0) { PCRTFSPDBSUBSTREAM pSubstream = &pPdb->aSubstreams[idxFirst + cLeft]; if (strcmp(pSubstream->pszName, pszName) == 0) return pSubstream; } return NULL; } /** * @interface_method_impl{RTVFSDIROPS,pfnOpen} */ static DECLCALLBACK(int) rtFsPdbDir_Open(void *pvThis, const char *pszEntry, uint64_t fOpen, uint32_t fFlags, PRTVFSOBJ phVfsObj) { PRTFSPDBDIROBJ const pThis = (PRTFSPDBDIROBJ)pvThis; PRTFSPDBVOL const pPdb = pThis->pPdb; int rc; /* * We cannot create or replace anything, just open stuff. */ if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE) { /* likely */ } else return VERR_WRITE_PROTECT; /* * Special cases '.' and '..' */ if ( pszEntry[0] == '.' && ( pszEntry[1] == '\0' || (pszEntry[1] == '.' && pszEntry[2] == '\0'))) { if (fFlags & RTVFSOBJ_F_OPEN_DIRECTORY) { RTVFSDIR hVfsDir; rc = rtFsPdbVol_OpenRoot(pPdb, &hVfsDir); if (RT_SUCCESS(rc)) { *phVfsObj = RTVfsObjFromDir(hVfsDir); RTVfsDirRelease(hVfsDir); AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3); } } else rc = VERR_IS_A_DIRECTORY; return rc; } /* * The given filename can be: * - just the stream index: "1"; * - just the name provided it doesn't start with a digit: "pdb"; * - or the combination of the two: "1-pdb". */ uint32_t idxStream; PCRTFSPDBSUBSTREAM pSubstream = NULL; if (RT_C_IS_DIGIT(*pszEntry)) { char *pszNext; rc = RTStrToUInt32Ex(pszEntry, &pszNext, 10, &idxStream); if ( (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS) || (*pszNext != '\0' && *pszNext != '-') || idxStream >= pPdb->cStreams) { Log2(("rtFsPdbDir_Open: RTStrToUInt32Ex(%s,) -> %Rrc\n", pszEntry, VERR_PATH_NOT_FOUND)); return VERR_PATH_NOT_FOUND; } if ( *pszNext == '-' && RTStrCmp(pszNext + 1, pPdb->papStreamInfo[idxStream]->pszName) != 0) { pSubstream = rtFsPdbDirLookupSubstream(pPdb, idxStream, pszNext + 1); if (pSubstream) Assert(pSubstream->idxStream == idxStream); else { Log2(("rtFsPdbDir_Open: idxStream=%#x name mismatch '%s', expected '%s'\n", idxStream, pszEntry, pPdb->papStreamInfo[idxStream]->pszName)); return VERR_PATH_NOT_FOUND; } } } else { for (idxStream = 0; idxStream < pPdb->cStreams; idxStream++) { const char * const pszStreamName = pPdb->papStreamInfo[idxStream]->pszName; if (pszStreamName && strcmp(pszEntry, pszStreamName) == 0) break; } if (idxStream >= pPdb->cStreams) { pSubstream = rtFsPdbDirLookupSubstream(pPdb, UINT32_MAX, pszEntry); if (pSubstream) idxStream = pSubstream->idxStream; else { Log2(("rtFsPdbDir_Open: '%s' not found in name table\n", pszEntry)); return VERR_PATH_NOT_FOUND; } } } /* * If opening a file, create a new file object and return it. */ if (fFlags & RTVFSOBJ_F_OPEN_FILE) { RTVFSFILE hVfsFile = NIL_RTVFSFILE; PRTFSPDBFILEOBJ pNewFile; rc = RTVfsNewFile(&g_rtFsPdbFileOps, sizeof(*pNewFile), fOpen, pPdb->hVfsSelf, NIL_RTVFSLOCK /*use volume lock*/, &hVfsFile, (void **)&pNewFile); if (RT_SUCCESS(rc)) { rtFsPdbPopulateFileObjFromInfo(pNewFile, pPdb, idxStream, pSubstream); /* Convert it to a file object. */ *phVfsObj = RTVfsObjFromFile(hVfsFile); RTVfsFileRelease(hVfsFile); AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3); LogFlow(("rtFsPdbDir_Open: idxStream=%#x cbStream=%#RX64\n", idxStream, pNewFile->cbStream)); } } else rc = VERR_IS_A_FILE; return rc; } /** * @interface_method_impl{RTVFSDIROPS,pfnRewindDir} */ static DECLCALLBACK(int) rtFsPdbDir_RewindDir(void *pvThis) { PRTFSPDBDIROBJ pThis = (PRTFSPDBDIROBJ)pvThis; pThis->u.idxNext = 0; return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSDIROPS,pfnReadDir} */ static DECLCALLBACK(int) rtFsPdbDir_ReadDir(void *pvThis, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry, RTFSOBJATTRADD enmAddAttr) { PRTFSPDBDIROBJ const pThis = (PRTFSPDBDIROBJ)pvThis; PRTFSPDBVOL const pPdb = pThis->pPdb; uint32_t const idxStream = pThis->u.s.idxNextStream; uint32_t const idxSubstream = pThis->u.s.idxNextSubstream; if (idxStream < pPdb->cStreams) { PCRTFSPDBSTREAMINFO const pStreamInfo = pPdb->papStreamInfo[idxStream]; PCRTFSPDBSUBSTREAM const pSubstreamInfo = !idxSubstream ? NULL : &pPdb->aSubstreams[pStreamInfo->idxFirstSubstream + idxSubstream - 1]; /* * Do names first as they may cause overflows. */ char szStreamNo[64]; ssize_t const cchStreamNo = RTStrFormatU32(szStreamNo, sizeof(szStreamNo), idxStream, 10, 0, 0, 0); Assert(cchStreamNo > 0); /* Provide a more descriptive name if possible. */ const char *pszOtherName = !idxSubstream ? pStreamInfo->pszName : pSubstreamInfo->pszName; size_t const cchOtherName = pszOtherName ? strlen(pszOtherName) : 0; /* Do the name stuff. */ size_t const cchName = cchOtherName ? cchStreamNo + 1 + cchOtherName : cchStreamNo; size_t const cbNeeded = RT_UOFFSETOF(RTDIRENTRYEX, szName) + cchName + 1; if (*pcbDirEntry < cbNeeded) { Log3(("rtFsPdbDir_ReadDir: VERR_BUFFER_OVERFLOW - cbDst=%zu cbNeeded=%zu\n", *pcbDirEntry, cbNeeded)); *pcbDirEntry = cbNeeded; return VERR_BUFFER_OVERFLOW; } pDirEntry->cbName = (uint16_t)cchName; memcpy(pDirEntry->szName, szStreamNo, cchStreamNo); if (cchOtherName) { pDirEntry->szName[cchStreamNo] = '-'; memcpy(&pDirEntry->szName[cchStreamNo + 1], pszOtherName, cchOtherName); } pDirEntry->szName[cchName] = '\0'; if (cchOtherName && !idxSubstream) { Assert(cchStreamNo <= 8); pDirEntry->cwcShortName = cchStreamNo; szStreamNo[cchStreamNo] = '\0'; int rc = RTUtf16CopyAscii(pDirEntry->wszShortName, RT_ELEMENTS(pDirEntry->wszShortName), szStreamNo); AssertRC(rc); } else { pDirEntry->cwcShortName = 0; pDirEntry->wszShortName[0] = '\0'; } /* Provide the other info. */ rtFsPdbPopulateObjInfo(pPdb, &pDirEntry->Info, enmAddAttr, !pSubstreamInfo ? pStreamInfo->cbStream : pSubstreamInfo->cbSubstream, idxStream, idxSubstream, false /*fIsDir*/); /* Advance the directory location and return. */ if (idxSubstream >= pStreamInfo->cSubstreams) { pThis->u.s.idxNextSubstream = 0; pThis->u.s.idxNextStream = idxStream + 1; } else pThis->u.s.idxNextSubstream = idxSubstream + 1; return VINF_SUCCESS; } Log3(("rtFsPdbDir_ReadDir9660: idxNext=%#x: VERR_NO_MORE_FILES\n", pThis->u.idxNext)); return VERR_NO_MORE_FILES; } /** * PDB (root) directory operations. */ static const RTVFSDIROPS g_rtFsPdbDirOps = { { /* Obj */ RTVFSOBJOPS_VERSION, RTVFSOBJTYPE_DIR, "PDB Dir", rtFsPdbDir_Close, rtFsPdbDir_QueryInfo, NULL, RTVFSOBJOPS_VERSION }, RTVFSDIROPS_VERSION, 0, { /* ObjSet */ RTVFSOBJSETOPS_VERSION, RT_UOFFSETOF(RTVFSDIROPS, ObjSet) - RT_UOFFSETOF(RTVFSDIROPS, Obj), NULL /*SetMode*/, NULL /*SetTimes*/, NULL /*SetOwner*/, RTVFSOBJSETOPS_VERSION }, rtFsPdbDir_Open, NULL /* pfnFollowAbsoluteSymlink */, NULL /* pfnOpenFile */, NULL /* pfnOpenDir */, NULL /* pfnCreateDir */, NULL /* pfnOpenSymlink */, NULL /* pfnCreateSymlink */, NULL /* pfnQueryEntryInfo */, NULL /* pfnUnlinkEntry */, NULL /* pfnRenameEntry */, rtFsPdbDir_RewindDir, rtFsPdbDir_ReadDir, RTVFSDIROPS_VERSION, }; /** * @interface_method_impl{RTVFSOBJOPS::Obj,pfnClose} */ static DECLCALLBACK(int) rtFsPdbVol_Close(void *pvThis) { PRTFSPDBVOL pThis = (PRTFSPDBVOL)pvThis; Log(("rtFsPdbVol_Close(%p)\n", pThis)); RTVfsFileRelease(pThis->hVfsBacking); pThis->hVfsBacking = NIL_RTVFSFILE; RTMemFree(pThis->pbRoot); pThis->pbRoot = NULL; RTMemFree(pThis->pszzNames); pThis->pszzNames = NULL; if (pThis->papStreamInfo) { uint32_t idxStream = pThis->cStreams; while (idxStream-- > 0) RTMemFree(pThis->papStreamInfo[idxStream]); RTMemFree(pThis->papStreamInfo); pThis->papStreamInfo = NULL; } pThis->cStreams = 0; return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSOBJOPS::Obj,pfnQueryInfo} */ static DECLCALLBACK(int) rtFsPdbVol_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) { RT_NOREF(pvThis, pObjInfo, enmAddAttr); return VERR_WRONG_TYPE; } /** * @interface_method_impl{RTVFSOBJOPS::Obj,pfnQueryInfoEx} */ static DECLCALLBACK(int) rtFsPdbVol_QueryInfoEx(void *pvThis, RTVFSQIEX enmInfo, void *pvInfo, size_t cbInfo, size_t *pcbRet) { PRTFSPDBVOL pThis = (PRTFSPDBVOL)pvThis; LogFlow(("rtFsPdbVol_QueryInfo(%p, %d,, %#zx,)\n", pThis, enmInfo, cbInfo)); RT_NOREF(pThis, pvInfo, cbInfo, pcbRet); ssize_t cchRet; switch (enmInfo) { /* This is the same as the symbol chache subdir name: */ case RTVFSQIEX_VOL_LABEL: if ( pThis->enmVersion == RTFSPDBVER_2 || RTUuidIsNull(&pThis->Uuid)) cchRet = RTStrPrintf2((char *)pvInfo, cbInfo, "%08X%x", pThis->uTimestamp, pThis->uAge); else cchRet = RTStrPrintf2((char *)pvInfo, cbInfo, "%08X%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X%x", pThis->Uuid.Gen.u32TimeLow, pThis->Uuid.Gen.u16TimeMid, pThis->Uuid.Gen.u16TimeHiAndVersion, pThis->Uuid.Gen.u8ClockSeqHiAndReserved, pThis->Uuid.Gen.u8ClockSeqLow, pThis->Uuid.Gen.au8Node[0], pThis->Uuid.Gen.au8Node[1], pThis->Uuid.Gen.au8Node[2], pThis->Uuid.Gen.au8Node[3], pThis->Uuid.Gen.au8Node[4], pThis->Uuid.Gen.au8Node[5], pThis->uAge); break; /* This exposes the PDB and VC versions: */ case RTVFSQIEX_VOL_LABEL_ALT: cchRet = RTStrPrintf2((char *)pvInfo, cbInfo, "pdb-v%u-%u", pThis->enmVersion == RTFSPDBVER_2 ? 2 : 7, pThis->uVcDate); break; case RTVFSQIEX_VOL_SERIAL: if (cbInfo == sizeof(uint64_t) || cbInfo == sizeof(uint32_t)) { *pcbRet = cbInfo; ((uint32_t *)pvInfo)[0] = pThis->uTimestamp; if (cbInfo == sizeof(uint64_t)) ((uint32_t *)pvInfo)[1] = pThis->uAge; return VINF_SUCCESS; } if ( pThis->enmVersion != RTFSPDBVER_2 && !RTUuidIsNull(&pThis->Uuid)) { *pcbRet = sizeof(RTUUID); if (cbInfo == sizeof(RTUUID)) { *(PRTUUID)pvInfo = pThis->Uuid; return VINF_SUCCESS; } } else *pcbRet = sizeof(uint64_t); return cbInfo < *pcbRet ? VERR_BUFFER_OVERFLOW : VERR_BUFFER_UNDERFLOW; default: return VERR_NOT_SUPPORTED; } if (cchRet > 0) { *pcbRet = (size_t)cchRet; return VINF_SUCCESS; } *pcbRet = (size_t)-cchRet; return VERR_BUFFER_OVERFLOW; } /** * @interface_method_impl{RTVFSOPS,pfnOpenRoot} */ static DECLCALLBACK(int) rtFsPdbVol_OpenRoot(void *pvThis, PRTVFSDIR phVfsDir) { PRTFSPDBVOL pThis = (PRTFSPDBVOL)pvThis; PRTFSPDBDIROBJ pNewDir; int rc = RTVfsNewDir(&g_rtFsPdbDirOps, sizeof(*pNewDir), 0 /*fFlags*/, pThis->hVfsSelf, NIL_RTVFSLOCK /*use volume lock*/, phVfsDir, (void **)&pNewDir); if (RT_SUCCESS(rc)) { pNewDir->pPdb = pThis; pNewDir->u.idxNext = 0; return VINF_SUCCESS; } return rc; } /** * @interface_method_impl{RTVFSOPS,pfnQueryRangeState} */ static DECLCALLBACK(int) rtFsPdbVol_QueryRangeState(void *pvThis, uint64_t off, size_t cb, bool *pfUsed) { RT_NOREF(pvThis, off, cb, pfUsed); return VERR_NOT_IMPLEMENTED; } DECL_HIDDEN_CONST(const RTVFSOPS) g_rtFsPdbVolOps = { { /* Obj */ RTVFSOBJOPS_VERSION, RTVFSOBJTYPE_VFS, "PDB", rtFsPdbVol_Close, rtFsPdbVol_QueryInfo, rtFsPdbVol_QueryInfoEx, RTVFSOBJOPS_VERSION }, RTVFSOPS_VERSION, 0 /* fFeatures */, rtFsPdbVol_OpenRoot, rtFsPdbVol_QueryRangeState, RTVFSOPS_VERSION }; /** * Internal helper for reading an entire stream into a heap block. * * Use RTMemTmpFree to free the returned memory when done. */ static int rtFsPdbVolReadStreamToHeap(PRTFSPDBVOL pThis, uint32_t idxStream, uint8_t **ppbStream, uint32_t *pcbStream, PRTERRINFO pErrInfo) { *ppbStream = NULL; *pcbStream = 0; if (idxStream >= pThis->cStreams) return RTERRINFO_LOG_SET_F(pErrInfo, VERR_NOT_FOUND, "idxStream=%#x not found, max %#x", idxStream, pThis->cStreams); /* * Fake a handle and reuse the file read code. */ RTFSPDBFILEOBJ FileObj; rtFsPdbPopulateFileObjFromInfo(&FileObj, pThis, idxStream, NULL); size_t const cbDst = (size_t)FileObj.cbStream + !FileObj.cbStream; uint8_t * const pbDst = (uint8_t *)RTMemTmpAllocZ(cbDst); if (!pbDst) return RTERRINFO_LOG_SET_F(pErrInfo, VERR_NO_MEMORY, "Failed to allocate memory for reading stream #%x: %#zx bytes", idxStream, cbDst); RTSGSEG SgSeg = { pbDst, FileObj.cbStream }; RTSGBUF SgBuf; RTSgBufInit(&SgBuf, &SgSeg, 1); int rc = rtFsPdbFile_Read(&FileObj, 0, &SgBuf, true /*fBlocking*/, NULL); if (RT_SUCCESS(rc)) { *ppbStream = pbDst; *pcbStream = FileObj.cbStream; } else RTMemTmpFree(pbDst); return rc; } /** * Internal helper for reading from a stream. */ static int rtFsPdbVolReadStreamAt(PRTFSPDBVOL pThis, uint32_t idxStream, uint32_t offStream, void *pvBuf, size_t cbToRead, size_t *pcbRead) { RTFSPDBFILEOBJ FileObj; rtFsPdbPopulateFileObjFromInfo(&FileObj, pThis, idxStream, NULL); RTSGSEG SgSeg = { pvBuf, cbToRead }; RTSGBUF SgBuf; RTSgBufInit(&SgBuf, &SgSeg, 1); return rtFsPdbFile_Read(&FileObj, offStream, &SgBuf, true /*fBlocking*/, pcbRead); } /** * Adds a substream. */ static void rtFsPdbVolAddSubstream(PRTFSPDBVOL pThis, uint32_t idxStream, uint8_t idxSubstream, uint32_t offSubstream, uint32_t cbSubstream, const char *pszName) { /* * Sanity checking. */ Assert(idxSubstream > 0); /* zero is used for the parent stream when enumerating directories. */ AssertReturnVoid(idxStream < pThis->cStreams); PRTFSPDBSTREAMINFO const pStreamInfo = pThis->papStreamInfo[idxStream]; Assert(RTStrStartsWith(pszName, pStreamInfo->pszName)); AssertReturnVoid(offSubstream < pStreamInfo->cbStream); AssertStmt(cbSubstream <= pStreamInfo->cbStream - offSubstream, cbSubstream = pStreamInfo->cbStream - offSubstream); AssertReturnVoid(pThis->cSubstreams < RT_ELEMENTS(pThis->aSubstreams)); PRTFSPDBSUBSTREAM const pSubstreamInfo = &pThis->aSubstreams[pThis->cSubstreams]; Assert( pThis->cSubstreams == 0 || pSubstreamInfo[-1].idxStream < idxStream || ( pSubstreamInfo[-1].idxStream == idxStream && pSubstreamInfo[-1].idxSubstream < idxSubstream && pSubstreamInfo[-1].offSubstream <= offSubstream)); /* * Add it. */ pSubstreamInfo->idxStream = idxStream; pSubstreamInfo->idxSubstream = idxSubstream; pSubstreamInfo->offSubstream = offSubstream; pSubstreamInfo->cbSubstream = cbSubstream; pSubstreamInfo->pszName = pszName; if (!pStreamInfo->cSubstreams) pStreamInfo->idxFirstSubstream = pThis->cSubstreams; pStreamInfo->cSubstreams++; pThis->cSubstreams++; } /** * Helper for rtFsPdbVolLoadStream3 to set standard stream names. */ static void rtFsPdbVolSetDefaultStreamName(PRTFSPDBVOL pThis, uint32_t idxStream, const char *pszDefaultName) { if (idxStream < pThis->cStreams) { /* We override entries from the string table, as the standard stream names must be assumed to work when the streams are present! */ if ((uintptr_t)pThis->papStreamInfo[idxStream]->pszName - (uintptr_t)pThis->pszzNames < pThis->cbNames) Log(("rtFsPdbVolSetDefaultStreamName: Overriding string table name for stream %#x: %s -> %s\n", idxStream, pThis->papStreamInfo[idxStream]->pszName, pszDefaultName)); pThis->papStreamInfo[idxStream]->pszName = pszDefaultName; } } /** * Worker for rtFsPdbVolTryInit that extracts info from the DBI stream. */ static int rtFsPdbVolLoadStream3(PRTFSPDBVOL pThis, PRTERRINFO pErrInfo) { /* On the offchance that the DBI stream is missing... */ if (3 >= pThis->cStreams) return VINF_SUCCESS; /* * Read the header. */ union { RTPDBDBIHDR New; RTPDBDBIHDROLD Old; uint16_t aidxStream[32]; } Hdr; size_t cbRead = 0; int rc = rtFsPdbVolReadStreamAt(pThis, 3, 0, &Hdr, sizeof(Hdr.New), &cbRead); if (RT_FAILURE(rc)) return RTERRINFO_LOG_SET_F(pErrInfo, rc, "Error eeading the DBI header"); if (Hdr.New.uSignature == RTPDBDBIHDR_SIGNATURE) { Log(("rtFsPdbVolLoadStream3: New DBI header\n")); if (cbRead != sizeof(Hdr.New)) return RTERRINFO_LOG_SET_F(pErrInfo, rc, "Bogus DBI header size: cbRead=%#zx, expected %#zx", cbRead, sizeof(Hdr.New)); if (Hdr.New.uVcVersion < RTPDBDBIHDR_VCVER_50) return RTERRINFO_LOG_SET_F(pErrInfo, rc, "Bogus DBI header version: %u (%#x), expected min %u", Hdr.New.uVcVersion, Hdr.New.uVcVersion, RTPDBDBIHDR_VCVER_50); if (Hdr.New.uVcVersion < RTPDBDBIHDR_VCVER_60) Hdr.New.cbEditContinueSubstream = 0; pThis->DbiHdr = Hdr.New; } else { /* Convert old header to new to new. */ if (cbRead < sizeof(Hdr.Old)) return RTERRINFO_LOG_SET_F(pErrInfo, rc, "Bogus DBI header size: cbRead=%#zx, expected %#zx", cbRead, sizeof(Hdr.Old)); Log(("rtFsPdbVolLoadStream3: Old DBI header\n")); RT_ZERO(pThis->DbiHdr); pThis->DbiHdr.idxMFC = UINT16_MAX; pThis->DbiHdr.idxGlobalStream = Hdr.Old.idxGlobalStream; pThis->DbiHdr.idxPublicStream = Hdr.Old.idxPublicStream; pThis->DbiHdr.idxSymRecStream = Hdr.Old.idxSymRecStream; pThis->DbiHdr.cbModInfoSubstream = Hdr.Old.cbModInfoSubstream; pThis->DbiHdr.cbSectContribSubstream = Hdr.Old.cbSectContribSubstream; pThis->DbiHdr.cbSrcInfoSubstream = Hdr.Old.cbSrcInfoSubstream; pThis->DbiHdr.uAge = pThis->uAge; } Log2((" DBI Hdr uSignature = %#x\n", pThis->DbiHdr.uSignature)); Log2((" DBI Hdr uVcVersion = %#x\n", pThis->DbiHdr.uVcVersion)); if (pThis->DbiHdr.uAge == pThis->uAge) Log2((" DBI Hdr uAge = %#x (same as PDB header)\n", pThis->DbiHdr.uAge)); else Log2((" DBI Hdr uAge = %#x - differs from DB.uAge %#x!\n", pThis->DbiHdr.uAge, pThis->uAge)); Log2((" DBI Hdr idxGlobalStream = %#x\n", pThis->DbiHdr.idxGlobalStream)); if (pThis->DbiHdr.PdbDllVer.New.fNewVerFmt) { Log2((" DBI Hdr PdbDllVer.u16 = %#x - %u.%u (new)\n", pThis->DbiHdr.PdbDllVer.u16, pThis->DbiHdr.PdbDllVer.New.uMajor, pThis->DbiHdr.PdbDllVer.New.uMinor)); Log2((" DBI Hdr uPdbDllBuild = %u (%#x)\n", pThis->DbiHdr.uPdbDllBuild, pThis->DbiHdr.uPdbDllBuild)); Log2((" DBI Hdr uPdbDllRBuild = %u (%#x)\n", pThis->DbiHdr.uPdbDllRBuild, pThis->DbiHdr.uPdbDllRBuild)); } else { Log2((" DBI Hdr PdbDllVer = %#x - %u.%u.%u (old)\n", pThis->DbiHdr.PdbDllVer.u16, pThis->DbiHdr.PdbDllVer.Old.uMajor, pThis->DbiHdr.PdbDllVer.Old.uMinor, pThis->DbiHdr.PdbDllVer.Old.uRBuild)); if (pThis->DbiHdr.uPdbDllBuild) Log2((" DBI Hdr uPdbDllBuild = %u (%#x)!!\n", pThis->DbiHdr.uPdbDllBuild, pThis->DbiHdr.uPdbDllBuild)); if (pThis->DbiHdr.uPdbDllRBuild) Log2((" DBI Hdr uPdbDllRBuild = %u (%#x)!!\n", pThis->DbiHdr.uPdbDllRBuild, pThis->DbiHdr.uPdbDllRBuild)); } Log2((" DBI Hdr idxPublicStream = %#x\n", pThis->DbiHdr.idxPublicStream)); Log2((" DBI Hdr idxSymRecStream = %#x\n", pThis->DbiHdr.idxSymRecStream)); Log2((" DBI Hdr cbModInfoSubstream = %#x\n", pThis->DbiHdr.cbModInfoSubstream)); Log2((" DBI Hdr cbSectContribSubstream = %#x\n", pThis->DbiHdr.cbSectContribSubstream)); Log2((" DBI Hdr cbSectionMapSubstream = %#x\n", pThis->DbiHdr.cbSectionMapSubstream)); Log2((" DBI Hdr cbSrcInfoSubstream = %#x\n", pThis->DbiHdr.cbSrcInfoSubstream)); Log2((" DBI Hdr cbTypeServerMapSubstream = %#x\n", pThis->DbiHdr.cbTypeServerMapSubstream)); Log2((" DBI Hdr idxMFC = %#x\n", pThis->DbiHdr.idxMFC)); Log2((" DBI Hdr cbOptDbgHdr = %#x\n", pThis->DbiHdr.cbOptDbgHdr)); Log2((" DBI Hdr cbEditContinueSubstream = %#x\n", pThis->DbiHdr.cbEditContinueSubstream)); Log2((" DBI Hdr fFlags = %#x%s%s%s%s%s\n", pThis->DbiHdr.fFlags, pThis->DbiHdr.fFlags ? " -" : "", pThis->DbiHdr.fFlags & RTPDBDBIHDR_F_INCREMENTAL_LINK ? " IncrmentalLink" : "", pThis->DbiHdr.fFlags & RTPDBDBIHDR_F_PRIVATE_SYMS_STRIPPED ? " PrivateSymsStripped" : "", pThis->DbiHdr.fFlags & RTPDBDBIHDR_F_CONFLICTING_TYPES ? " ConflictingTypes" : "", pThis->DbiHdr.fFlags & RTPDBDBIHDR_F_RESERVED ? " !ReservedFlagsSet!" : "")); Log2((" DBI Hdr uMachine = %#x\n", pThis->DbiHdr.uMachine)); Log2((" DBI Hdr uReserved = %#x\n", pThis->DbiHdr.uReserved)); /* * Apply new standard stream names. */ rtFsPdbVolSetDefaultStreamName(pThis, pThis->DbiHdr.idxGlobalStream, "global-symbol-hash"); rtFsPdbVolSetDefaultStreamName(pThis, pThis->DbiHdr.idxPublicStream, "public-symbol-hash"); rtFsPdbVolSetDefaultStreamName(pThis, pThis->DbiHdr.idxSymRecStream, "symbol-records"); /* * Fill in substream info. */ uint32_t offSubstream = sizeof(Hdr.New); if (pThis->DbiHdr.cbModInfoSubstream) { rtFsPdbVolAddSubstream(pThis, 3, 1, offSubstream, pThis->DbiHdr.cbModInfoSubstream, "dbi-module-info"); offSubstream += pThis->DbiHdr.cbModInfoSubstream; } if (pThis->DbiHdr.cbSectContribSubstream) { rtFsPdbVolAddSubstream(pThis, 3, 2, offSubstream, pThis->DbiHdr.cbSectContribSubstream, "dbi-section-contributions"); offSubstream += pThis->DbiHdr.cbSectContribSubstream; } if (pThis->DbiHdr.cbSectionMapSubstream) { rtFsPdbVolAddSubstream(pThis, 3, 3, offSubstream, pThis->DbiHdr.cbSectionMapSubstream, "dbi-section-map"); offSubstream += pThis->DbiHdr.cbSectionMapSubstream; } if (pThis->DbiHdr.cbSrcInfoSubstream) { rtFsPdbVolAddSubstream(pThis, 3, 4, offSubstream, pThis->DbiHdr.cbSrcInfoSubstream, "dbi-source-info"); offSubstream += pThis->DbiHdr.cbSrcInfoSubstream; } if (pThis->DbiHdr.cbTypeServerMapSubstream) { rtFsPdbVolAddSubstream(pThis, 3, 5, offSubstream, pThis->DbiHdr.cbTypeServerMapSubstream, "dbi-type-server-map"); offSubstream += pThis->DbiHdr.cbTypeServerMapSubstream; } if (pThis->DbiHdr.cbEditContinueSubstream) { rtFsPdbVolAddSubstream(pThis, 3, 6, offSubstream, pThis->DbiHdr.cbEditContinueSubstream, "dbi-continue-and-edit"); offSubstream += pThis->DbiHdr.cbEditContinueSubstream; } uint32_t const offOptDbgHdr = offSubstream; if (pThis->DbiHdr.cbOptDbgHdr) { rtFsPdbVolAddSubstream(pThis, 3, 7, offSubstream, pThis->DbiHdr.cbOptDbgHdr, "dbi-optional-header"); offSubstream += pThis->DbiHdr.cbOptDbgHdr; } if (offSubstream < pThis->papStreamInfo[3]->cbStream) rtFsPdbVolAddSubstream(pThis, 3, 8, offSubstream, pThis->papStreamInfo[3]->cbStream - offSubstream, "dbi-unknown"); /* * Read the optional header if present, it identifies a bunch of standard streams. */ if (pThis->DbiHdr.cbOptDbgHdr > 1) { RT_ZERO(Hdr); rc = rtFsPdbVolReadStreamAt(pThis, 3, offOptDbgHdr, &Hdr, RT_MIN(sizeof(Hdr), pThis->DbiHdr.cbOptDbgHdr), &cbRead); if (RT_FAILURE(rc)) return RTERRINFO_LOG_SET_F(pErrInfo, rc, "Error eeading the DBI optional header at %#x LB %#x", offOptDbgHdr, pThis->DbiHdr.cbOptDbgHdr); static const char * const s_apszIdxNames[] = { /* [RTPDBDBIOPT_IDX_FPO_MASM] = */ "image-fpo-masm-section", /* [RTPDBDBIOPT_IDX_EXCEPTION] = */ "image-exception", /* [RTPDBDBIOPT_IDX_FIXUP] = */ "image-fixup", /* [RTPDBDBIOPT_IDX_OMAP_TO_SRC] = */ "omap-to-src", /* [RTPDBDBIOPT_IDX_OMAP_FROM_SRC] = */ "omap-from-src", /* [RTPDBDBIOPT_IDX_SECTION_HEADERS] = */ "image-section-headers", /* [RTPDBDBIOPT_IDX_CLR_TOKEN_ID_MAP] = */ "clr-token-id-map", /* [RTPDBDBIOPT_IDX_XDATA] = */ "image-xdata-section", /* [RTPDBDBIOPT_IDX_PDATA] = */ "image-pdata-section", /* [RTPDBDBIOPT_IDX_FPO] = */ "image-fpo", /* [RTPDBDBIOPT_IDX_ORG_SECTION_HEADERS] = */ "image-orginal-section-headers", }; uint32_t idxName = pThis->DbiHdr.cbOptDbgHdr / 2; if (idxName > RT_ELEMENTS(s_apszIdxNames)) { #ifdef LOG_ENABLED while (idxName-- > RT_ELEMENTS(s_apszIdxNames)) Log(("Unknown DBI optional header entry %u: %#x\n", idxName, Hdr.aidxStream[idxName])); #endif idxName = RT_ELEMENTS(s_apszIdxNames); } while (idxName-- > 0) rtFsPdbVolSetDefaultStreamName(pThis, Hdr.aidxStream[idxName], s_apszIdxNames[idxName]); } return VINF_SUCCESS; } /** * Worker for rtFsPdbVolLoadStream1 that parses the PDB metadata stream. */ static int rtFsPdbVolLoadStream1Inner(PRTFSPDBVOL pThis, uint8_t const *pb, size_t cb, PRTERRINFO pErrInfo) { /* * Process the header part. */ if (pThis->enmVersion == RTFSPDBVER_2) { PCRTPDB20NAMES pHdr = (PCRTPDB20NAMES)pb; if (cb < sizeof(*pHdr)) return RTERRINFO_LOG_SET_F(pErrInfo, VERR_NO_MEMORY, "Stream #1 is smaller than expected: %#x, vs min %#zx", cb, sizeof(*pHdr)); pThis->uTimestamp = pHdr->uTimestamp; pThis->uAge = pHdr->uAge; pThis->uVcDate = pHdr->uVersion; pThis->cbNames = pHdr->cbNames; pb += sizeof(*pHdr); cb -= sizeof(*pHdr); } else { PCRTPDB70NAMES pHdr = (PCRTPDB70NAMES)pb; if (cb < sizeof(*pHdr)) return RTERRINFO_LOG_SET_F(pErrInfo, VERR_NO_MEMORY, "Stream #1 is smaller than expected: %#x, vs min %#zx", cb, sizeof(*pHdr)); pThis->uTimestamp = pHdr->uTimestamp; pThis->uAge = pHdr->uAge; pThis->uVcDate = pHdr->uVersion; pThis->Uuid = pHdr->Uuid; pThis->cbNames = pHdr->cbNames; pb += sizeof(*pHdr); cb -= sizeof(*pHdr); } /* * Set default stream names that depends on the VC date. */ /** @todo */ /* * Load the string table if present. */ if (pThis->cbNames == 0) return VINF_SUCCESS; if (cb < pThis->cbNames) return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "Bogus string table: size given as %#x, but only %#x bytes left", pThis->cbNames, cb); pThis->pszzNames = (char *)RTMemDupEx(pb, pThis->cbNames, 2 /* two extra zero bytes */); if (!pThis->pszzNames) return RTERRINFO_LOG_SET_F(pErrInfo, VERR_NO_MEMORY, "Failed to allocate string table: %#x + 2 bytes", pThis->cbNames); pb += pThis->cbNames; cb -= pThis->cbNames; /** @todo MIPS format variation may have alignment padding here. */ /* * What follows now is the hash table mapping string table offset to stream * numbers. This is frequently misaligned, so we take care to load the * stuff byte-by-byte on architectures which are sensive to such things. * * Structure description: https://llvm.org/docs/PDB/HashTable.html */ if (cb < 4 * sizeof(uint32_t)) return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "Bogus hash table: Min size of 16 bytes, but only %#x bytes left", cb); #if defined(RT_ARCH_X86) || defined(RT_ARCH_AMD64) # define GET_U32(a_pb, a_off) *(uint32_t const *)&a_pb[(a_off)] #else # define GET_U32(a_pb, a_off) RT_MAKE_U32_FROM_U8(a_pb[0 + (a_off)], a_pb[1 + (a_off)], a_pb[2 + (a_off)], a_pb[3 + (a_off)]) #endif /* Sizes (irrelevant to us as we're not reconstructing the actual hash table yet): */ uint32_t const cTabSize = GET_U32(pb, 0); uint32_t const cTabCapacity = GET_U32(pb, 4); if (cTabSize > cTabCapacity) return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "Bogus hash table: cTabSize=%#x > cTabCapacity=%#x", cTabSize, cTabCapacity); if (cTabSize == 0) return VINF_SUCCESS; pb += 8; cb -= 8; /* Present bit vector: */ uint32_t const cPresentVec = RT_MAKE_U32_FROM_U8(pb[0], pb[1], pb[2], pb[3]); pb += 4; cb -= 4; if ((uint64_t)cPresentVec + 1 > cb / 4) return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "Bogus hash table: cPresentVec=%#x + 1 delete vec > %#x", cPresentVec, cb / 4); uint8_t const * const pbPresentBits = pb; pb += cPresentVec * 4; cb -= cPresentVec * 4; /* Scan the present vector as it gives the number of key/value pairs following the deleted bit vector. */ uint64_t cPresent = 0; for (uint32_t off = 0; off < cPresentVec; off += 4) { uint32_t uWord = GET_U32(pbPresentBits, off); while (uWord) { cPresent += uWord & 1; uWord >>= 1; } } /* Deleted vector (irrelevant to us): */ uint32_t const cDeletedVec = RT_MAKE_U32_FROM_U8(pb[0], pb[1], pb[2], pb[3]); pb += 4; cb -= 4; if ((uint64_t)cDeletedVec + cPresent > cb / 4) return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "Bogus hash table: cDeletedVec=%#x cPresent=%#RX64 > %#x", cDeletedVec, cPresent, cb / 4); pb += cDeletedVec * 4; cb -= cDeletedVec * 4; /* What remains is cPresent pairs of string table offset and stream IDs. */ Assert(cb / 4 >= cPresent); for (uint32_t i = 0, off = 0; i < cPresent; i++, off += 8) { uint32_t const offString = GET_U32(pb, off); uint32_t const idxStream = GET_U32(pb, off + 4); if (offString < pThis->cbNames) { if (idxStream < pThis->cStreams) { /* Skip leading slashes. In-string slashes are removed afterwards. */ const char *pszName = &pThis->pszzNames[offString]; while (*pszName == '/' || *pszName == '\\') pszName++; if (pszName[0]) pThis->papStreamInfo[idxStream]->pszName = pszName; } else return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "Bogus hash table entry %#x: offString=%#x, max %#x (offString=%#x '%s')", i, idxStream, pThis->cStreams, offString, &pThis->pszzNames[offString]); } else return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "Bogus hash table entry %#x: offString=%#x, max %#x (idxStream=%#x)", i, offString, pThis->cbNames, idxStream); } /* * Sanitize strings and convert any in-string slashes to underscores to * avoid VFS confusion. This has to be done after loading the hash table * so the slash skipping there works correctly should anyone do sub-string * optimizations involving slashes. */ for (uint32_t idxStream = 0; idxStream < pThis->cStreams; idxStream++) { char *pszName = (char *)pThis->papStreamInfo[idxStream]->pszName; if (pszName) { RTStrPurgeEncoding(pszName); pszName = strpbrk(pszName, "/\\"); while (pszName) { *pszName = '_'; pszName = strpbrk(pszName + 1, "/\\"); } } } return VINF_SUCCESS; } /** * Worker for rtFsPdbVolTryInit that loads the PDB metadata from stream \#1. */ static int rtFsPdbVolLoadStream1(PRTFSPDBVOL pThis, PRTERRINFO pErrInfo) { /* * Assign default stream names based on basic PDB version. */ switch (pThis->cStreams) { default: case 5: if (pThis->enmVersion == RTFSPDBVER_7) /** @todo condition? */ pThis->papStreamInfo[4]->pszName = "name-map"; RT_FALL_THRU(); case 4: pThis->papStreamInfo[3]->pszName = "dbi"; RT_FALL_THRU(); case 3: pThis->papStreamInfo[2]->pszName = "tpi"; RT_FALL_THRU(); case 2: pThis->papStreamInfo[1]->pszName = "pdb"; RT_FALL_THRU(); case 1: pThis->papStreamInfo[0]->pszName = "root"; break; } if (pThis->cStreams >= 2) { /* * Read the stream into temporary heap memory and process it. */ uint8_t *pbStream = NULL; uint32_t cbStream = 0; int rc = rtFsPdbVolReadStreamToHeap(pThis, 1, &pbStream, &cbStream, pErrInfo); if (RT_SUCCESS(rc)) { rc = rtFsPdbVolLoadStream1Inner(pThis, pbStream, cbStream, pErrInfo); RTMemTmpFree(pbStream); } return rc; } return VINF_SUCCESS; } /** * Helper for rtFsPdbVolTryInit. */ static int rtFsPdbVolAllocInitialStreamInfo(PRTFSPDBVOL pThis, PRTERRINFO pErrInfo) { pThis->papStreamInfo = (PRTFSPDBSTREAMINFO *)RTMemAllocZ(sizeof(pThis->papStreamInfo[0]) * pThis->cStreams); if (pThis->papStreamInfo) { for (uint32_t idxStream = 0; idxStream < pThis->cStreams; idxStream++) { pThis->papStreamInfo[idxStream] = (PRTFSPDBSTREAMINFO)RTMemAllocZ(sizeof(*pThis->papStreamInfo[0])); if (pThis->papStreamInfo[idxStream]) { } else return RTERRINFO_LOG_SET_F(pErrInfo, VERR_NO_MEMORY, "Failed to allocate RTFSPDBSTREAMINFO #%u", idxStream); } return VINF_SUCCESS; } return RTERRINFO_LOG_SET_F(pErrInfo, VERR_NO_MEMORY, "Failed to allocate papStreamInfo array with %u entries", pThis->cStreams); } /** * Worker for RTFsPdbVolOpen. * * @returns IPRT status code. * @param pThis The PDB VFS instance to initialize. * @param hVfsSelf The PDB VFS handle (no reference consumed). * @param hVfsBacking The file backing the alleged PDB file system. * Reference is consumed (via rtFsPdbVol_Close). * @param fFlags Flags, RTFSPDB_F_XXX. * @param pErrInfo Where to return additional error info. Can be NULL. */ static int rtFsPdbVolTryInit(PRTFSPDBVOL pThis, RTVFS hVfsSelf, RTVFSFILE hVfsBacking, uint32_t fFlags, PRTERRINFO pErrInfo) { /* * First initialize the state so that rtFsPdbVol_Close won't trip up. */ pThis->hVfsSelf = hVfsSelf; pThis->hVfsBacking = hVfsBacking; /* Caller referenced it for us, we consume it; rtFsPdbVol_Close releases it. */ pThis->cbBacking = 0; pThis->cBackingPages = 0; pThis->fFlags = fFlags; pThis->cbPage = 0; pThis->enmVersion = RTFSPDBVER_INVALID; pThis->uAge = 0; pThis->uTimestamp = 0; RTUuidClear(&pThis->Uuid); pThis->cbNames = 0; pThis->pszzNames = NULL; pThis->papStreamInfo = NULL; pThis->cSubstreams = 0; /* * Do init stuff that may fail. */ int rc = RTVfsFileQuerySize(hVfsBacking, &pThis->cbBacking); if (RT_FAILURE(rc)) return rc; /* * Read, validate and load the file header. */ union { RTPDB70HDR Hdr70; RTPDB20HDR Hdr20; } Buf; RT_ZERO(Buf); rc = RTVfsFileReadAt(hVfsBacking, 0, &Buf, sizeof(Buf), NULL); if (RT_FAILURE(rc)) return RTERRINFO_LOG_SET_F(pErrInfo, rc, "Unable to file header"); uint32_t cPages; uint32_t cbMinRootStream; uint64_t offRootPageMap; if (memcmp(Buf.Hdr70.szSignature, RTPDB_SIGNATURE_700, sizeof(Buf.Hdr70.szSignature)) == 0) { pThis->enmVersion = RTFSPDBVER_7; pThis->cbPage = Buf.Hdr70.cbPage; pThis->cbRoot = Buf.Hdr70.cbRoot; cPages = Buf.Hdr70.cPages; cbMinRootStream = RT_UOFFSETOF(RTPDB70ROOT, aStreams[4]) + sizeof(RTPDB70PAGE) * 4; offRootPageMap = Buf.Hdr70.iRootPages * pThis->cbPage; } else if (memcmp(Buf.Hdr20.szSignature, RTPDB_SIGNATURE_200, sizeof(Buf.Hdr20.szSignature)) == 0) { pThis->enmVersion = RTFSPDBVER_2; pThis->cbPage = Buf.Hdr20.cbPage; pThis->cbRoot = Buf.Hdr20.RootStream.cbStream; cPages = Buf.Hdr20.cPages; cbMinRootStream = RT_UOFFSETOF(RTPDB20ROOT, aStreams[4]) + sizeof(RTPDB20PAGE) * 4; /** @todo ?? */ offRootPageMap = RT_UOFFSETOF(RTPDB20HDR, aiRootPageMap); } else return RTERRINFO_LOG_SET_F(pErrInfo, VERR_INVALID_MAGIC, "Unknown file header signature: %.44Rhxs", Buf.Hdr70.szSignature); if ( pThis->cbPage != _4K && pThis->cbPage != _8K && pThis->cbPage != _16K && pThis->cbPage != _32K && pThis->cbPage != _64K && pThis->cbPage != _2K && pThis->cbPage != _1K && pThis->cbPage != 512) return RTERRINFO_LOG_SET_F(pErrInfo, rc, "Unsupported page size: %#x", pThis->cbPage); pThis->cBackingPages = pThis->cbBacking / pThis->cbPage; if (cPages != pThis->cBackingPages) return RTERRINFO_LOG_SET_F(pErrInfo, rc, "Unexpected page count: %#x, expected %#llx (page size %#x)", cPages, pThis->cBackingPages, pThis->cbPage); if (offRootPageMap > pThis->cbBacking - pThis->cbPage) return RTERRINFO_LOG_SET_F(pErrInfo, rc, "Bogus root page map start: %#llx (file size %#llx)", offRootPageMap, pThis->cbBacking); if (pThis->cbRoot < cbMinRootStream) return RTERRINFO_LOG_SET_F(pErrInfo, rc, "Root stream is smaller than expected: %#x, expected at least %#x", pThis->cbRoot, cbMinRootStream); if (pThis->cbRoot > _64M) return RTERRINFO_LOG_SET_F(pErrInfo, rc, "Root stream is too large: %#x, max supported %#x", pThis->cbRoot, _64M); if (pThis->cbRoot > pThis->cbBacking) return RTERRINFO_LOG_SET_F(pErrInfo, rc, "Root stream is too large: %#x, file size %#llx", pThis->cbRoot, pThis->cbBacking); /* * Load the root stream into memory. */ uint32_t const cRootPages = (pThis->cbRoot + pThis->cbPage - 1) / pThis->cbPage; pThis->pbRoot = (uint8_t *)RTMemAllocZ(RT_ALIGN_32(pThis->cbRoot, 64)); if (!pThis->pbRoot) return RTERRINFO_LOG_SET_F(pErrInfo, rc, "Failed to allocate root stream backing: %#x bytes", RT_ALIGN_32(pThis->cbRoot, 64)); /* Get the root page map. */ size_t const cbPageMap = cRootPages * (pThis->enmVersion == RTFSPDBVER_2 ? sizeof(RTPDB20PAGE) : sizeof(RTPDB70PAGE)); void * const pvPageMap = RTMemTmpAlloc(cbPageMap); if (!pvPageMap) return RTERRINFO_LOG_SET_F(pErrInfo, rc, "Failed to allocate %#zx bytes for the root page map", cbPageMap); rc = RTVfsFileReadAt(hVfsBacking, offRootPageMap, pvPageMap, cbPageMap, NULL); if (RT_SUCCESS(rc)) { /* Validate the page map. */ for (uint32_t iPageMap = 0; iPageMap < cRootPages; iPageMap++) { RTPDB70PAGE const iPageNo = pThis->enmVersion == RTFSPDBVER_2 ? ((PCRTPDB20PAGE)pvPageMap)[iPageMap] : ((PCRTPDB70PAGE)pvPageMap)[iPageMap]; if (iPageNo > 0 && iPageNo < pThis->cBackingPages) continue; rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_OUT_OF_RANGE, "Root page map entry %#x is out of bounds: %#x (max %#llx)", iPageMap, iPageNo, pThis->cBackingPages); break; } /* Read using regular file reader. */ RTFSPDBFILEOBJ FileObj; rtFsPdbPopulateFileObj(&FileObj, pThis, 0, pThis->cbRoot, pvPageMap); RTSGSEG SgSeg = { pThis->pbRoot, pThis->cbRoot }; RTSGBUF SgBuf; RTSgBufInit(&SgBuf, &SgSeg, 1); rc = rtFsPdbFile_Read(&FileObj, 0, &SgBuf, true /*fBlocking*/, NULL); } RTMemTmpFree(pvPageMap); if (RT_FAILURE(rc)) return rc; /* * Validate the root stream. */ if (pThis->enmVersion == RTFSPDBVER_2) { PCRTPDB20ROOT const pRoot = (PCRTPDB20ROOT)pThis->pbRoot; /* Stream count. */ if ( RT_UOFFSETOF_DYN(RTPDB20ROOT, aStreams[pRoot->cStreams]) <= pThis->cbRoot && pRoot->cStreams >= 1) pThis->cStreams = pRoot->cStreams; else return RTERRINFO_LOG_SET_F(pErrInfo, VERR_OUT_OF_RANGE, "Bogus root stream count: %#x (cbRoot=%#x)", pRoot->cStreams, pThis->cbRoot); rc = rtFsPdbVolAllocInitialStreamInfo(pThis, pErrInfo); if (RT_FAILURE(rc)) return rc; /* Validate stream data and page map while populating the stream info table. */ PCRTPDB20PAGE const pauPageMap = (PCRTPDB20PAGE)&pRoot->aStreams[pThis->cStreams]; uint32_t const cPageMapMax = pThis->cbRoot - RT_UOFFSETOF_DYN(RTPDB20ROOT, aStreams[pThis->cStreams]); uint32_t iPageMap = 0; for (uint32_t idxStream = 0; idxStream < pThis->cStreams; idxStream++) { uint32_t cStreamPages = RTPdbSizeToPages(pRoot->aStreams[idxStream].cbStream, pThis->cbPage); if (iPageMap + cStreamPages <= cPageMapMax) { PRTFSPDBSTREAMINFO const pInfo = pThis->papStreamInfo[idxStream]; pInfo->cbStream = pRoot->aStreams[idxStream].cbStream; pInfo->cPages = cStreamPages; pInfo->PageMap.pa20 = &pauPageMap[iPageMap]; while (cStreamPages-- > 0) if (pauPageMap[iPageMap] == 0 || pauPageMap[iPageMap] < pThis->cBackingPages) iPageMap++; else return RTERRINFO_LOG_SET_F(pErrInfo, VERR_OUT_OF_RANGE, "Bogus page map entry %#x belonging to stream %#x: %#x (max %#llx)", iPageMap, idxStream, pauPageMap[iPageMap], pThis->cBackingPages); } else return RTERRINFO_LOG_SET_F(pErrInfo, VERR_OUT_OF_RANGE, "Stream %#x exceeds the page map space: cbStream=%#x iPageMap=%#x cPageMapMax=%#x", idxStream, pRoot->aStreams[idxStream].cbStream, iPageMap, cPageMapMax); } } else { PCRTPDB70ROOT const pRoot = (PCRTPDB70ROOT)pThis->pbRoot; /* Stream count. */ if ( RT_UOFFSETOF_DYN(RTPDB70ROOT, aStreams[pRoot->cStreams]) <= pThis->cbRoot && pRoot->cStreams >= 1) pThis->cStreams = pRoot->cStreams; else return RTERRINFO_LOG_SET_F(pErrInfo, VERR_OUT_OF_RANGE, "Bogus root stream count: %#x (cbRoot=%#x)", pRoot->cStreams, pThis->cbRoot); rc = rtFsPdbVolAllocInitialStreamInfo(pThis, pErrInfo); if (RT_FAILURE(rc)) return rc; /* Validate stream data and page map while populating the stream info table. */ PCRTPDB70PAGE const pauPageMap = (PCRTPDB70PAGE)&pRoot->aStreams[pThis->cStreams]; uint32_t const cPageMapMax = pThis->cbRoot - RT_UOFFSETOF_DYN(RTPDB70ROOT, aStreams[pThis->cStreams]); uint32_t iPageMap = 0; for (uint32_t idxStream = 0; idxStream < pThis->cStreams; idxStream++) { uint32_t cStreamPages = RTPdbSizeToPages(pRoot->aStreams[idxStream].cbStream, pThis->cbPage); if (iPageMap + cStreamPages <= cPageMapMax) { PRTFSPDBSTREAMINFO const pInfo = pThis->papStreamInfo[idxStream]; pInfo->cbStream = pRoot->aStreams[idxStream].cbStream; pInfo->cPages = cStreamPages; pInfo->PageMap.pa70 = &pauPageMap[iPageMap]; while (cStreamPages-- > 0) if (pauPageMap[iPageMap] == 0 || pauPageMap[iPageMap] < pThis->cBackingPages) iPageMap++; else return RTERRINFO_LOG_SET_F(pErrInfo, VERR_OUT_OF_RANGE, "Bogus page map entry %#x belonging to stream %#x: %#x (max %#llx)", iPageMap, idxStream, pauPageMap[iPageMap], pThis->cBackingPages); } else return RTERRINFO_LOG_SET_F(pErrInfo, VERR_OUT_OF_RANGE, "Stream %#x exceeds the page map space: cbStream=%#x iPageMap=%#x cPageMapMax=%#x", idxStream, pRoot->aStreams[idxStream].cbStream, iPageMap, cPageMapMax); } } /* * Load info from streams #1 (PDB & names) and #3 (DBI). */ rc = rtFsPdbVolLoadStream1(pThis, pErrInfo); if (RT_SUCCESS(rc)) rc = rtFsPdbVolLoadStream3(pThis, pErrInfo); return rc; } RTDECL(int) RTFsPdbVolOpen(RTVFSFILE hVfsFileIn, uint32_t fFlags, PRTVFS phVfs, PRTERRINFO pErrInfo) { /* * Quick input validation. */ AssertPtrReturn(phVfs, VERR_INVALID_POINTER); *phVfs = NIL_RTVFS; AssertReturn(!(fFlags & ~RTFSPDB_F_NO_NAMES), VERR_INVALID_FLAGS); uint32_t cRefs = RTVfsFileRetain(hVfsFileIn); AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE); /* * Create a new PDB VFS instance and try initialize it using the given input file. */ RTVFS hVfs = NIL_RTVFS; PRTFSPDBVOL pThis = NULL; int rc = RTVfsNew(&g_rtFsPdbVolOps, sizeof(RTFSPDBVOL), NIL_RTVFS, RTVFSLOCK_CREATE_RW, &hVfs, (void **)&pThis); if (RT_SUCCESS(rc)) { rc = rtFsPdbVolTryInit(pThis, hVfs, hVfsFileIn, fFlags, pErrInfo); if (RT_SUCCESS(rc)) *phVfs = hVfs; else RTVfsRelease(hVfs); } else RTVfsFileRelease(hVfsFileIn); return rc; } /** * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate} */ static DECLCALLBACK(int) rtVfsChainPdbFsVol_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec, PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo) { RT_NOREF(pProviderReg, pSpec); /* * Basic checks. */ if (pElement->enmTypeIn != RTVFSOBJTYPE_FILE) return pElement->enmTypeIn == RTVFSOBJTYPE_INVALID ? VERR_VFS_CHAIN_CANNOT_BE_FIRST_ELEMENT : VERR_VFS_CHAIN_TAKES_FILE; if ( pElement->enmType != RTVFSOBJTYPE_VFS && pElement->enmType != RTVFSOBJTYPE_DIR) return VERR_VFS_CHAIN_ONLY_DIR_OR_VFS; if (pElement->cArgs > 1) return VERR_VFS_CHAIN_AT_MOST_ONE_ARG; /* * Parse the flag if present, save in pElement->uProvider. */ uint32_t fFlags = 0; if (pElement->cArgs > 0) { for (uint32_t iArg = 0; iArg < pElement->cArgs; iArg++) { const char *psz = pElement->paArgs[iArg].psz; if (*psz) { if (!strcmp(psz, "nonames")) fFlags |= RTFSPDB_F_NO_NAMES; else { *poffError = pElement->paArgs[iArg].offSpec; return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Only knows: 'nojoliet' and 'norock'"); } } } } pElement->uProvider = fFlags; return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate} */ static DECLCALLBACK(int) rtVfsChainPdbFsVol_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj, PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo) { RT_NOREF(pProviderReg, pSpec, poffError); int rc; RTVFSFILE hVfsFileIn = RTVfsObjToFile(hPrevVfsObj); if (hVfsFileIn != NIL_RTVFSFILE) { RTVFS hVfs; rc = RTFsPdbVolOpen(hVfsFileIn, pElement->uProvider, &hVfs, pErrInfo); RTVfsFileRelease(hVfsFileIn); if (RT_SUCCESS(rc)) { *phVfsObj = RTVfsObjFromVfs(hVfs); RTVfsRelease(hVfs); if (*phVfsObj != NIL_RTVFSOBJ) return VINF_SUCCESS; rc = VERR_VFS_CHAIN_CAST_FAILED; } } else rc = VERR_VFS_CHAIN_CAST_FAILED; return rc; } /** * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement} */ static DECLCALLBACK(bool) rtVfsChainPdbFsVol_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement, PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement) { RT_NOREF(pProviderReg, pSpec, pReuseSpec); if ( pElement->paArgs[0].uProvider == pReuseElement->paArgs[0].uProvider || !pReuseElement->paArgs[0].uProvider) return true; return false; } /** VFS chain element 'file'. */ static RTVFSCHAINELEMENTREG g_rtVfsChainPdbFsVolReg = { /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION, /* fReserved = */ 0, /* pszName = */ "pdbfs", /* ListEntry = */ { NULL, NULL }, /* pszHelp = */ "Open a PDB file system, requires a file object on the left side.\n", /* pfnValidate = */ rtVfsChainPdbFsVol_Validate, /* pfnInstantiate = */ rtVfsChainPdbFsVol_Instantiate, /* pfnCanReuseElement = */ rtVfsChainPdbFsVol_CanReuseElement, /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION }; RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainPdbFsVolReg, rtVfsChainPdbFsVolReg);