/* $Id: isomakerimport.cpp 73097 2018-07-12 21:06:33Z vboxsync $ */ /** @file * IPRT - ISO Image Maker, Import Existing Image. */ /* * Copyright (C) 2017 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; * you can redistribute it and/or modify it under the terms of the GNU * General Public License (GPL) as published by the Free Software * Foundation, in version 2 as it comes in the "COPYING" file of the * VirtualBox OSE distribution. VirtualBox OSE is distributed in the * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. * * The contents of this file may alternatively be used under the terms * of the Common Development and Distribution License Version 1.0 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the * VirtualBox OSE 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. */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP RTLOGGROUP_FS #include "internal/iprt.h" #include #include #include #include #include #include #include #include #include #include #include #include #include /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ /** Max directory depth. */ #define RTFSISOMK_IMPORT_MAX_DEPTH 32 /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** * Block to file translation node. */ typedef struct RTFSISOMKIMPBLOCK2FILE { /** AVL tree node containing the first block number of the file. * Block number is relative to the start of the import image. */ AVLU32NODECORE Core; /** The configuration index of the file. */ uint32_t idxObj; /** Namespaces the file has been seen in already (RTFSISOMAKER_NAMESPACE_XXX). */ uint32_t fNamespaces; /** Pointer to the next file with the same block number. */ struct RTFSISOMKIMPBLOCK2FILE *pNext; } RTFSISOMKIMPBLOCK2FILE; /** Pointer to a block-2-file translation node. */ typedef RTFSISOMKIMPBLOCK2FILE *PRTFSISOMKIMPBLOCK2FILE; /** * Directory todo list entry. */ typedef struct RTFSISOMKIMPDIR { /** List stuff. */ RTLISTNODE Entry; /** The directory configuration index with hIsoMaker. */ uint32_t idxObj; /** The directory data block number. */ uint32_t offDirBlock; /** The directory size (in bytes). */ uint32_t cbDir; /** The depth of this directory. */ uint8_t cDepth; } RTFSISOMKIMPDIR; /** Pointer to a directory todo list entry. */ typedef RTFSISOMKIMPDIR *PRTFSISOMKIMPDIR; /** * ISO maker ISO importer state. */ typedef struct RTFSISOMKIMPORTER { /** The destination ISO maker. */ RTFSISOMAKER hIsoMaker; /** RTFSISOMK_IMPORT_F_XXX. */ uint32_t fFlags; /** The status code of the whole import. * This notes down the first error status. */ int rc; /** Pointer to error info return structure. */ PRTERRINFO pErrInfo; /** The source file. */ RTVFSFILE hSrcFile; /** The size of the source file. */ uint64_t cbSrcFile; /** The number of 2KB blocks in the source file. */ uint64_t cBlocksInSrcFile; /** The import source index of hSrcFile in hIsoMaker. UINT32_MAX till adding * the first file. */ uint32_t idxSrcFile; /** The root of the tree for converting data block numbers to files * (PRTFSISOMKIMPBLOCK2FILE). This is essential when importing boot files and * the 2nd namespace (joliet, udf, hfs) so that we avoid duplicating data. */ AVLU32TREE Block2FileRoot; /** The block offset of the primary volume descriptor. */ uint32_t offPrimaryVolDesc; /** The primary volume space size in blocks. */ uint32_t cBlocksInPrimaryVolumeSpace; /** The primary volume space size in bytes. */ uint64_t cbPrimaryVolumeSpace; /** The number of volumes in the set. */ uint32_t cVolumesInSet; /** The primary volume sequence ID. */ uint32_t idPrimaryVol; /** Set if we've already seen a joliet volume descriptor. */ bool fSeenJoliet; /** The name of the TRANS.TBL in the import media (must ignore). */ const char *pszTransTbl; /** Pointer to the import results structure (output). */ PRTFSISOMAKERIMPORTRESULTS pResults; /** Sector buffer for volume descriptors and such. */ union { uint8_t ab[ISO9660_SECTOR_SIZE]; ISO9660VOLDESCHDR VolDescHdr; ISO9660PRIMARYVOLDESC PrimVolDesc; ISO9660SUPVOLDESC SupVolDesc; ISO9660BOOTRECORDELTORITO ElToritoDesc; } uSectorBuf; /** Name buffer. */ char szNameBuf[_2K]; /** A somewhat larger buffer. */ uint8_t abBuf[_64K]; /** @name Rock Ridge stuff * @{ */ /** Set if we've see the SP entry. */ bool fSuspSeenSP; /** Set if we've seen the last 'NM' entry. */ bool fSeenLastNM; /** Set if we've seen the last 'SL' entry. */ bool fSeenLastSL; /** The SUSP skip into system area offset. */ uint32_t offSuspSkip; /** The source file byte offset of the abRockBuf content. */ uint64_t offRockBuf; /** Name buffer for rock ridge. */ char szRockNameBuf[_2K]; /** Symlink target name buffer for rock ridge. */ char szRockSymlinkTargetBuf[_2K]; /** A buffer for reading rock ridge continuation blocks into */ uint8_t abRockBuf[ISO9660_SECTOR_SIZE]; /** @} */ } RTFSISOMKIMPORTER; /** Pointer to an ISO maker ISO importer state. */ typedef RTFSISOMKIMPORTER *PRTFSISOMKIMPORTER; /* * The following is also found in iso9660vfs.cpp: * The following is also found in iso9660vfs.cpp: * The following is also found in iso9660vfs.cpp: */ /** * Converts a ISO 9660 binary timestamp into an IPRT timesspec. * * @param pTimeSpec Where to return the IRPT time. * @param pIso9660 The ISO 9660 binary timestamp. */ static void rtFsIsoImpIso9660RecDateTime2TimeSpec(PRTTIMESPEC pTimeSpec, PCISO9660RECTIMESTAMP pIso9660) { RTTIME Time; Time.fFlags = RTTIME_FLAGS_TYPE_UTC; Time.offUTC = 0; Time.i32Year = pIso9660->bYear + 1900; Time.u8Month = RT_MIN(RT_MAX(pIso9660->bMonth, 1), 12); Time.u8MonthDay = RT_MIN(RT_MAX(pIso9660->bDay, 1), 31); Time.u8WeekDay = UINT8_MAX; Time.u16YearDay = 0; Time.u8Hour = RT_MIN(pIso9660->bHour, 23); Time.u8Minute = RT_MIN(pIso9660->bMinute, 59); Time.u8Second = RT_MIN(pIso9660->bSecond, 59); Time.u32Nanosecond = 0; RTTimeImplode(pTimeSpec, RTTimeNormalize(&Time)); /* Only apply the UTC offset if it's within reasons. */ if (RT_ABS(pIso9660->offUtc) <= 13*4) RTTimeSpecSubSeconds(pTimeSpec, pIso9660->offUtc * 15 * 60 * 60); } /** * Converts a ISO 9660 char timestamp into an IPRT timesspec. * * @returns true if valid, false if not. * @param pTimeSpec Where to return the IRPT time. * @param pIso9660 The ISO 9660 char timestamp. */ static bool rtFsIsoImpIso9660DateTime2TimeSpecIfValid(PRTTIMESPEC pTimeSpec, PCISO9660TIMESTAMP pIso9660) { if ( RT_C_IS_DIGIT(pIso9660->achYear[0]) && RT_C_IS_DIGIT(pIso9660->achYear[1]) && RT_C_IS_DIGIT(pIso9660->achYear[2]) && RT_C_IS_DIGIT(pIso9660->achYear[3]) && RT_C_IS_DIGIT(pIso9660->achMonth[0]) && RT_C_IS_DIGIT(pIso9660->achMonth[1]) && RT_C_IS_DIGIT(pIso9660->achDay[0]) && RT_C_IS_DIGIT(pIso9660->achDay[1]) && RT_C_IS_DIGIT(pIso9660->achHour[0]) && RT_C_IS_DIGIT(pIso9660->achHour[1]) && RT_C_IS_DIGIT(pIso9660->achMinute[0]) && RT_C_IS_DIGIT(pIso9660->achMinute[1]) && RT_C_IS_DIGIT(pIso9660->achSecond[0]) && RT_C_IS_DIGIT(pIso9660->achSecond[1]) && RT_C_IS_DIGIT(pIso9660->achCentisecond[0]) && RT_C_IS_DIGIT(pIso9660->achCentisecond[1])) { RTTIME Time; Time.fFlags = RTTIME_FLAGS_TYPE_UTC; Time.offUTC = 0; Time.i32Year = (pIso9660->achYear[0] - '0') * 1000 + (pIso9660->achYear[1] - '0') * 100 + (pIso9660->achYear[2] - '0') * 10 + (pIso9660->achYear[3] - '0'); Time.u8Month = (pIso9660->achMonth[0] - '0') * 10 + (pIso9660->achMonth[1] - '0'); Time.u8MonthDay = (pIso9660->achDay[0] - '0') * 10 + (pIso9660->achDay[1] - '0'); Time.u8WeekDay = UINT8_MAX; Time.u16YearDay = 0; Time.u8Hour = (pIso9660->achHour[0] - '0') * 10 + (pIso9660->achHour[1] - '0'); Time.u8Minute = (pIso9660->achMinute[0] - '0') * 10 + (pIso9660->achMinute[1] - '0'); Time.u8Second = (pIso9660->achSecond[0] - '0') * 10 + (pIso9660->achSecond[1] - '0'); Time.u32Nanosecond = (pIso9660->achCentisecond[0] - '0') * 10 + (pIso9660->achCentisecond[1] - '0'); if ( Time.u8Month > 1 && Time.u8Month <= 12 && Time.u8MonthDay > 1 && Time.u8MonthDay <= 31 && Time.u8Hour < 60 && Time.u8Minute < 60 && Time.u8Second < 60 && Time.u32Nanosecond < 100) { if (Time.i32Year <= 1677) Time.i32Year = 1677; else if (Time.i32Year <= 2261) Time.i32Year = 2261; Time.u32Nanosecond *= RT_NS_10MS; RTTimeImplode(pTimeSpec, RTTimeNormalize(&Time)); /* Only apply the UTC offset if it's within reasons. */ if (RT_ABS(pIso9660->offUtc) <= 13*4) RTTimeSpecSubSeconds(pTimeSpec, pIso9660->offUtc * 15 * 60 * 60); return true; } } return false; } /* end of duplicated static functions. */ /** * Wrapper around RTErrInfoSetV. * * @returns rc * @param pThis The importer instance. * @param rc The status code to set. * @param pszFormat The format string detailing the error. * @param va Argument to the format string. */ static int rtFsIsoImpErrorV(PRTFSISOMKIMPORTER pThis, int rc, const char *pszFormat, va_list va) { va_list vaCopy; va_copy(vaCopy, va); LogRel(("RTFsIsoMkImport error %Rrc: %N\n", rc, pszFormat, &vaCopy)); va_end(vaCopy); if (RT_SUCCESS(pThis->rc)) { pThis->rc = rc; rc = RTErrInfoSetV(pThis->pErrInfo, rc, pszFormat, va); } pThis->pResults->cErrors++; return rc; } /** * Wrapper around RTErrInfoSetF. * * @returns rc * @param pThis The importer instance. * @param rc The status code to set. * @param pszFormat The format string detailing the error. * @param ... Argument to the format string. */ static int rtFsIsoImpError(PRTFSISOMKIMPORTER pThis, int rc, const char *pszFormat, ...) { va_list va; va_start(va, pszFormat); rc = rtFsIsoImpErrorV(pThis, rc, pszFormat, va); va_end(va); return rc; } /** * Callback for destroying a RTFSISOMKIMPBLOCK2FILE node. * * @returns VINF_SUCCESS * @param pNode The node to destroy. * @param pvUser Ignored. */ static DECLCALLBACK(int) rtFsIsoMakerImportDestroyData2File(PAVLU32NODECORE pNode, void *pvUser) { PRTFSISOMKIMPBLOCK2FILE pBlock2File = (PRTFSISOMKIMPBLOCK2FILE)pNode; if (pBlock2File) { PRTFSISOMKIMPBLOCK2FILE pNext; while ((pNext = pBlock2File->pNext) != NULL) { pBlock2File->pNext = pNext->pNext; pNext->pNext = NULL; RTMemFree(pNext); } RTMemFree(pNode); } RT_NOREF(pvUser); return VINF_SUCCESS; } /** * Adds a symbolic link and names it given its ISO-9660 directory record and * parent. * * @returns IPRT status code (safe to ignore). * @param pThis The importer instance. * @param pDirRec The directory record. * @param pObjInfo Object information. * @param fNamespace The namespace flag. * @param idxParent Parent directory. * @param pszName The name. * @param pszRockName The rock ridge name. Empty if not present. * @param pszTarget The symbolic link target. */ static int rtFsIsoImportProcessIso9660AddAndNameSymlink(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec, PRTFSOBJINFO pObjInfo, uint32_t fNamespace, uint32_t idxParent, const char *pszName, const char *pszRockName, const char *pszTarget) { NOREF(pDirRec); Assert(!(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY)); Assert(RTFS_IS_SYMLINK(pObjInfo->Attr.fMode)); uint32_t idxObj; int rc = RTFsIsoMakerAddUnnamedSymlink(pThis->hIsoMaker, pObjInfo, pszTarget, &idxObj); if (RT_SUCCESS(rc)) { Log3((" --> added symlink #%#x (-> %s)\n", idxObj, pszTarget)); pThis->pResults->cAddedSymlinks++; /* * Enter the object into the namespace. */ rc = RTFsIsoMakerObjSetNameAndParent(pThis->hIsoMaker, idxObj, idxParent, fNamespace, pszName); if (RT_SUCCESS(rc)) { pThis->pResults->cAddedNames++; if (*pszRockName != '\0' && strcmp(pszName, pszRockName) != 0) { rc = RTFsIsoMakerObjSetRockName(pThis->hIsoMaker, idxObj, fNamespace, pszRockName); if (RT_FAILURE(rc)) rc = rtFsIsoImpError(pThis, rc, "Error setting rock ridge name for symlink '%s' to '%s'", pszName, pszRockName); } } else rc = rtFsIsoImpError(pThis, rc, "Error naming symlink '%s' (-> %s): %Rrc", pszName, pszTarget, rc); } else rc = rtFsIsoImpError(pThis, rc, "Error adding symbolic link '%s' (-> %s): %Rrc", pszName, pszTarget, rc); return rc; } /** * Adds a directory and names it given its ISO-9660 directory record and parent. * * @returns IPRT status code (safe to ignore). * @param pThis The importer instance. * @param pDirRec The directory record. * @param pObjInfo Object information. * @param cbData The actual directory data size. (Always same as in the * directory record, but this what we do for files below.) * @param fNamespace The namespace flag. * @param idxParent Parent directory. * @param pszName The name. * @param pszRockName The rock ridge name. Empty if not present. * @param cDepth The depth to add it with. * @param pTodoList The todo list (for directories). */ static int rtFsIsoImportProcessIso9660AddAndNameDirectory(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec, PCRTFSOBJINFO pObjInfo, uint64_t cbData, uint32_t fNamespace, uint32_t idxParent, const char *pszName, const char *pszRockName, uint8_t cDepth, PRTLISTANCHOR pTodoList) { Assert(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY); uint32_t idxObj; int rc = RTFsIsoMakerAddUnnamedDir(pThis->hIsoMaker, pObjInfo, &idxObj); if (RT_SUCCESS(rc)) { Log3((" --> added directory #%#x\n", idxObj)); pThis->pResults->cAddedDirs++; /* * Enter the object into the namespace. */ rc = RTFsIsoMakerObjSetNameAndParent(pThis->hIsoMaker, idxObj, idxParent, fNamespace, pszName); if (RT_SUCCESS(rc)) { pThis->pResults->cAddedNames++; if (*pszRockName != '\0' && strcmp(pszName, pszRockName) != 0) rc = RTFsIsoMakerObjSetRockName(pThis->hIsoMaker, idxObj, fNamespace, pszRockName); if (RT_SUCCESS(rc)) { /* * Push it onto the traversal stack. */ PRTFSISOMKIMPDIR pImpDir = (PRTFSISOMKIMPDIR)RTMemAlloc(sizeof(*pImpDir)); if (pImpDir) { Assert((uint32_t)cbData == cbData /* no multi-extents for dirs makes it this far */); pImpDir->cbDir = (uint32_t)cbData; pImpDir->offDirBlock = ISO9660_GET_ENDIAN(&pDirRec->offExtent); pImpDir->idxObj = idxObj; pImpDir->cDepth = cDepth; RTListAppend(pTodoList, &pImpDir->Entry); } else rc = rtFsIsoImpError(pThis, VERR_NO_MEMORY, "Could not allocate RTFSISOMKIMPDIR"); } else rc = rtFsIsoImpError(pThis, rc, "Error setting rock ridge name for directory '%s' to '%s'", pszName, pszRockName); } else rc = rtFsIsoImpError(pThis, rc, "Error naming directory '%s': %Rrc", pszName, rc); } else rc = rtFsIsoImpError(pThis, rc, "Error adding directory '%s': %Rrc", pszName, rc); return rc; } /** * Adds a file and names it given its ISO-9660 directory record and parent. * * @returns IPRT status code (safe to ignore). * @param pThis The importer instance. * @param pDirRec The directory record. * @param pObjInfo Object information. * @param cbData The actual file data size. * @param fNamespace The namespace flag. * @param idxParent Parent directory. * @param pszName The name. * @param pszRockName The rock ridge name. Empty if not present. */ static int rtFsIsoImportProcessIso9660AddAndNameFile(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec, PRTFSOBJINFO pObjInfo, uint64_t cbData, uint32_t fNamespace, uint32_t idxParent, const char *pszName, const char *pszRockName) { int rc; /* * First we must make sure the common source file has been added. */ if (pThis->idxSrcFile != UINT32_MAX) { /* likely */ } else { rc = RTFsIsoMakerAddCommonSourceFile(pThis->hIsoMaker, pThis->hSrcFile, &pThis->idxSrcFile); if (RT_FAILURE(rc)) return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerAddCommonSourceFile failed: %Rrc", rc); Assert(pThis->idxSrcFile != UINT32_MAX); } /* * Lookup the data block if the file has a non-zero length. The aim is to * find files across namespaces while bearing in mind that files in the same * namespace may share data storage, i.e. what in a traditional unix file * system would be called hardlinked. Problem is that the core engine doesn't * do hardlinking yet and assume each file has exactly one name per namespace. */ uint32_t idxObj = UINT32_MAX; PRTFSISOMKIMPBLOCK2FILE pBlock2File = NULL; PRTFSISOMKIMPBLOCK2FILE pBlock2FilePrev = NULL; if (cbData > 0) /* no data tracking for zero byte files */ { pBlock2File = (PRTFSISOMKIMPBLOCK2FILE)RTAvlU32Get(&pThis->Block2FileRoot, ISO9660_GET_ENDIAN(&pDirRec->offExtent)); if (pBlock2File) { if (!(pBlock2File->fNamespaces & fNamespace)) { pBlock2File->fNamespaces |= fNamespace; idxObj = pBlock2File->idxObj; } else { do { pBlock2FilePrev = pBlock2File; pBlock2File = pBlock2File->pNext; } while (pBlock2File && (pBlock2File->fNamespaces & fNamespace)); if (pBlock2File) { pBlock2File->fNamespaces |= fNamespace; idxObj = pBlock2File->idxObj; } } } } /* * If the above lookup didn't succeed, add a new file with a lookup record. */ if (idxObj == UINT32_MAX) { pObjInfo->cbObject = pObjInfo->cbAllocated = cbData; rc = RTFsIsoMakerAddUnnamedFileWithCommonSrc(pThis->hIsoMaker, pThis->idxSrcFile, ISO9660_GET_ENDIAN(&pDirRec->offExtent) * (uint64_t)ISO9660_SECTOR_SIZE, cbData, pObjInfo, &idxObj); if (RT_FAILURE(rc)) return rtFsIsoImpError(pThis, rc, "Error adding file '%s': %Rrc", pszName, rc); Assert(idxObj != UINT32_MAX); /* Update statistics. */ pThis->pResults->cAddedFiles++; if (cbData > 0) { pThis->pResults->cbAddedDataBlocks += RT_ALIGN_64(cbData, ISO9660_SECTOR_SIZE); /* Lookup record. */ pBlock2File = (PRTFSISOMKIMPBLOCK2FILE)RTMemAlloc(sizeof(*pBlock2File)); AssertReturn(pBlock2File, rtFsIsoImpError(pThis, VERR_NO_MEMORY, "Could not allocate RTFSISOMKIMPBLOCK2FILE")); pBlock2File->idxObj = idxObj; pBlock2File->Core.Key = ISO9660_GET_ENDIAN(&pDirRec->offExtent); pBlock2File->fNamespaces = fNamespace; pBlock2File->pNext = NULL; if (!pBlock2FilePrev) { bool fRc = RTAvlU32Insert(&pThis->Block2FileRoot, &pBlock2File->Core); Assert(fRc); RT_NOREF(fRc); } else { pBlock2File->Core.pLeft = NULL; pBlock2File->Core.pRight = NULL; pBlock2FilePrev->pNext = pBlock2File; } } } /* * Enter the object into the namespace. */ rc = RTFsIsoMakerObjSetNameAndParent(pThis->hIsoMaker, idxObj, idxParent, fNamespace, pszName); if (RT_SUCCESS(rc)) { pThis->pResults->cAddedNames++; if (*pszRockName != '\0' && strcmp(pszName, pszRockName) != 0) { rc = RTFsIsoMakerObjSetRockName(pThis->hIsoMaker, idxObj, fNamespace, pszRockName); if (RT_FAILURE(rc)) rc = rtFsIsoImpError(pThis, rc, "Error setting rock ridge name for file '%s' to '%s'", pszName, pszRockName); } } else return rtFsIsoImpError(pThis, rc, "Error naming file '%s': %Rrc", pszName, rc); return VINF_SUCCESS; } /** * Parses rock ridge information if present in the directory entry. * * @param pThis The importer instance. * @param pObjInfo The object information to improve upon. * @param pbSys The system area of the directory record. * @param cbSys The number of bytes present in the sys area. * @param fUnicode Indicates which namespace we're working on. * @param fIsFirstDirRec Set if this is the '.' directory entry in the * root directory. (Some entries applies only to * it.) * @param fContinuationRecord Set if we're processing a continuation record in * living in the abRockBuf. */ static void rtFsIsoImportProcessIso9660TreeWorkerParseRockRidge(PRTFSISOMKIMPORTER pThis, PRTFSOBJINFO pObjInfo, uint8_t const *pbSys, size_t cbSys, bool fUnicode, bool fIsFirstDirRec, bool fContinuationRecord) { RT_NOREF(pObjInfo); /* * Do skipping if specified. */ if (pThis->offSuspSkip) { if (cbSys <= pThis->offSuspSkip) return; pbSys += pThis->offSuspSkip; cbSys -= pThis->offSuspSkip; } while (cbSys >= 4) { /* * Check header length and advance the sys variables. */ PCISO9660SUSPUNION pUnion = (PCISO9660SUSPUNION)pbSys; if (pUnion->Hdr.cbEntry > cbSys) { LogRel(("rtFsIsoImportProcessIso9660TreeWorkerParseRockRidge: cbEntry=%#x cbSys=%#x (%#x %#x)\n", pUnion->Hdr.cbEntry, cbSys, pUnion->Hdr.bSig1, pUnion->Hdr.bSig2)); return; } pbSys += pUnion->Hdr.cbEntry; cbSys -= pUnion->Hdr.cbEntry; /* * Process fields. */ #define MAKE_SIG(a_bSig1, a_bSig2) \ ( ((uint16_t)(a_bSig1) & 0x1f) \ | (((uint16_t)(a_bSig2) ^ 0x40) << 5) \ | ((((uint16_t)(a_bSig1) ^ 0x40) & 0xe0) << (5 + 8)) ) uint16_t const uSig = MAKE_SIG(pUnion->Hdr.bSig1, pUnion->Hdr.bSig2); switch (uSig) { /* * System use sharing protocol entries. */ case MAKE_SIG(ISO9660SUSPCE_SIG1, ISO9660SUSPCE_SIG2): { if (RT_BE2H_U32(pUnion->CE.offBlock.be) != RT_LE2H_U32(pUnion->CE.offBlock.le)) LogRel(("rtFsIsoImport/Rock: Invalid CE offBlock field: be=%#x vs le=%#x\n", RT_BE2H_U32(pUnion->CE.offBlock.be), RT_LE2H_U32(pUnion->CE.offBlock.le))); else if (RT_BE2H_U32(pUnion->CE.cbData.be) != RT_LE2H_U32(pUnion->CE.cbData.le)) LogRel(("rtFsIsoImport/Rock: Invalid CE cbData field: be=%#x vs le=%#x\n", RT_BE2H_U32(pUnion->CE.cbData.be), RT_LE2H_U32(pUnion->CE.cbData.le))); else if (RT_BE2H_U32(pUnion->CE.offData.be) != RT_LE2H_U32(pUnion->CE.offData.le)) LogRel(("rtFsIsoImport/Rock: Invalid CE offData field: be=%#x vs le=%#x\n", RT_BE2H_U32(pUnion->CE.offData.be), RT_LE2H_U32(pUnion->CE.offData.le))); else if (!fContinuationRecord) { uint64_t offData = ISO9660_GET_ENDIAN(&pUnion->CE.offBlock) * (uint64_t)ISO9660_SECTOR_SIZE; offData += ISO9660_GET_ENDIAN(&pUnion->CE.offData); uint32_t cbData = ISO9660_GET_ENDIAN(&pUnion->CE.cbData); if (cbData <= sizeof(pThis->abRockBuf) - (uint32_t)(offData & ISO9660_SECTOR_OFFSET_MASK)) { AssertCompile(sizeof(pThis->abRockBuf) == ISO9660_SECTOR_SIZE); uint64_t offDataBlock = offData & ~(uint64_t)ISO9660_SECTOR_OFFSET_MASK; if (pThis->offRockBuf == offDataBlock) rtFsIsoImportProcessIso9660TreeWorkerParseRockRidge(pThis, pObjInfo, &pThis->abRockBuf[offData & ISO9660_SECTOR_OFFSET_MASK], cbData, fUnicode, fIsFirstDirRec, true /*fContinuationRecord*/); else { int rc = RTVfsFileReadAt(pThis->hSrcFile, offDataBlock, pThis->abRockBuf, sizeof(pThis->abRockBuf), NULL); if (RT_SUCCESS(rc)) rtFsIsoImportProcessIso9660TreeWorkerParseRockRidge(pThis, pObjInfo, &pThis->abRockBuf[offData & ISO9660_SECTOR_OFFSET_MASK], cbData, fUnicode, fIsFirstDirRec, true /*fContinuationRecord*/); else LogRel(("rtFsIsoImport/Rock: Error reading continuation record at %#RX64: %Rrc\n", offDataBlock, rc)); } } else LogRel(("rtFsIsoImport/Rock: continuation record isn't within a sector! offData=%#RX64 cbData=%#RX32\n", cbData, offData)); } else LogRel(("rtFsIsoImport/Rock: nested continuation record!\n")); break; } case MAKE_SIG(ISO9660SUSPSP_SIG1, ISO9660SUSPSP_SIG2): /* SP */ if ( pUnion->Hdr.cbEntry != ISO9660SUSPSP_LEN || pUnion->Hdr.bVersion != ISO9660SUSPSP_VER || pUnion->SP.bCheck1 != ISO9660SUSPSP_CHECK1 || pUnion->SP.bCheck2 != ISO9660SUSPSP_CHECK2 || pUnion->SP.cbSkip > UINT8_MAX - RT_UOFFSETOF(ISO9660DIRREC, achFileId[1])) LogRel(("rtFsIsoImport/Rock: Malformed 'SP' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x), bCheck1=%#x (vs %#x), bCheck2=%#x (vs %#x), cbSkip=%#x (vs max %#x)\n", pUnion->Hdr.cbEntry, ISO9660SUSPSP_LEN, pUnion->Hdr.bVersion, ISO9660SUSPSP_VER, pUnion->SP.bCheck1, ISO9660SUSPSP_CHECK1, pUnion->SP.bCheck2, ISO9660SUSPSP_CHECK2, pUnion->SP.cbSkip, UINT8_MAX - RT_UOFFSETOF(ISO9660DIRREC, achFileId[1]) )); else if (!fIsFirstDirRec) LogRel(("rtFsIsoImport/Rock: Ignorining 'SP' entry in non-root directory record\n")); else if (pThis->fSuspSeenSP) LogRel(("rtFsIsoImport/Rock: Ignorining additional 'SP' entry\n")); else { pThis->offSuspSkip = pUnion->SP.cbSkip; if (pUnion->SP.cbSkip != 0) LogRel(("rtFsIsoImport/Rock: SP: cbSkip=%#x\n", pUnion->SP.cbSkip)); } break; case MAKE_SIG(ISO9660SUSPER_SIG1, ISO9660SUSPER_SIG2): /* ER */ if ( pUnion->Hdr.cbEntry > RT_UOFFSETOF(ISO9660SUSPER, achPayload) + (uint32_t)pUnion->ER.cchIdentifier + (uint32_t)pUnion->ER.cchDescription + (uint32_t)pUnion->ER.cchSource || pUnion->Hdr.bVersion != ISO9660SUSPER_VER) LogRel(("rtFsIsoImport/Rock: Malformed 'ER' entry: cbEntry=%#x bVersion=%#x (vs %#x) cchIdentifier=%#x cchDescription=%#x cchSource=%#x\n", pUnion->Hdr.cbEntry, pUnion->Hdr.bVersion, ISO9660SUSPER_VER, pUnion->ER.cchIdentifier, pUnion->ER.cchDescription, pUnion->ER.cchSource)); else if (!fIsFirstDirRec) LogRel(("rtFsIsoImport/Rock: Ignorining 'ER' entry in non-root directory record\n")); else if ( pUnion->ER.bVersion == 1 /* RRIP detection */ && ( (pUnion->ER.cchIdentifier >= 4 && strncmp(pUnion->ER.achPayload, ISO9660_RRIP_ID, 4 /*RRIP*/) == 0) || (pUnion->ER.cchIdentifier >= 10 && strncmp(pUnion->ER.achPayload, RT_STR_TUPLE(ISO9660_RRIP_1_12_ID)) == 0) )) { LogRel(("rtFsIsoImport/Rock: Rock Ridge 'ER' entry: v%u id='%.*s' desc='%.*s' source='%.*s'\n", pUnion->ER.bVersion, pUnion->ER.cchIdentifier, pUnion->ER.achPayload, pUnion->ER.cchDescription, &pUnion->ER.achPayload[pUnion->ER.cchIdentifier], pUnion->ER.cchSource, &pUnion->ER.achPayload[pUnion->ER.cchIdentifier + pUnion->ER.cchDescription])); if (!fUnicode) { int rc = RTFsIsoMakerSetRockRidgeLevel(pThis->hIsoMaker, 2); if (RT_FAILURE(rc)) LogRel(("rtFsIsoImport/Rock: RTFsIsoMakerSetRockRidgeLevel(,2) failed: %Rrc\n", rc)); } else { int rc = RTFsIsoMakerSetJolietRockRidgeLevel(pThis->hIsoMaker, 2); if (RT_FAILURE(rc)) LogRel(("rtFsIsoImport/Rock: RTFsIsoMakerSetJolietRockRidgeLevel(,2) failed: %Rrc\n", rc)); } } else LogRel(("rtFsIsoImport/Rock: Unknown extension in 'ER' entry: v%u id='%.*s' desc='%.*s' source='%.*s'\n", pUnion->ER.bVersion, pUnion->ER.cchIdentifier, pUnion->ER.achPayload, pUnion->ER.cchDescription, &pUnion->ER.achPayload[pUnion->ER.cchIdentifier], pUnion->ER.cchSource, &pUnion->ER.achPayload[pUnion->ER.cchIdentifier + pUnion->ER.cchDescription])); break; case MAKE_SIG(ISO9660SUSPPD_SIG1, ISO9660SUSPPD_SIG2): /* PD - ignored */ case MAKE_SIG(ISO9660SUSPST_SIG1, ISO9660SUSPST_SIG2): /* ST - ignore for now */ case MAKE_SIG(ISO9660SUSPES_SIG1, ISO9660SUSPES_SIG2): /* ES - ignore for now */ break; /* * Rock ridge interchange protocol entries. */ case MAKE_SIG(ISO9660RRIPRR_SIG1, ISO9660RRIPRR_SIG2): /* RR */ if ( pUnion->RR.Hdr.cbEntry != ISO9660RRIPRR_LEN || pUnion->RR.Hdr.bVersion != ISO9660RRIPRR_VER) LogRel(("rtFsIsoImport/Rock: Malformed 'RR' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) fFlags=%#x\n", pUnion->RR.Hdr.cbEntry, ISO9660RRIPRR_LEN, pUnion->RR.Hdr.bVersion, ISO9660RRIPRR_VER, pUnion->RR.fFlags)); /* else: ignore it */ break; case MAKE_SIG(ISO9660RRIPPX_SIG1, ISO9660RRIPPX_SIG2): /* PX */ if ( ( pUnion->PX.Hdr.cbEntry != ISO9660RRIPPX_LEN && pUnion->PX.Hdr.cbEntry != ISO9660RRIPPX_LEN_NO_INODE) || pUnion->PX.Hdr.bVersion != ISO9660RRIPPX_VER || RT_BE2H_U32(pUnion->PX.fMode.be) != RT_LE2H_U32(pUnion->PX.fMode.le) || RT_BE2H_U32(pUnion->PX.cHardlinks.be) != RT_LE2H_U32(pUnion->PX.cHardlinks.le) || RT_BE2H_U32(pUnion->PX.uid.be) != RT_LE2H_U32(pUnion->PX.uid.le) || RT_BE2H_U32(pUnion->PX.gid.be) != RT_LE2H_U32(pUnion->PX.gid.le) || ( pUnion->PX.Hdr.cbEntry == ISO9660RRIPPX_LEN && RT_BE2H_U32(pUnion->PX.INode.be) != RT_LE2H_U32(pUnion->PX.INode.le)) ) LogRel(("rtFsIsoImport/Rock: Malformed 'PX' entry: cbEntry=%#x (vs %#x or %#x), bVersion=%#x (vs %#x) fMode=%#x/%#x cHardlinks=%#x/%#x uid=%#x/%#x gid=%#x/%#x inode=%#x/%#x\n", pUnion->PX.Hdr.cbEntry, ISO9660RRIPPX_LEN, ISO9660RRIPPX_LEN_NO_INODE, pUnion->PX.Hdr.bVersion, ISO9660RRIPPX_VER, RT_BE2H_U32(pUnion->PX.fMode.be), RT_LE2H_U32(pUnion->PX.fMode.le), RT_BE2H_U32(pUnion->PX.cHardlinks.be), RT_LE2H_U32(pUnion->PX.cHardlinks.le), RT_BE2H_U32(pUnion->PX.uid.be), RT_LE2H_U32(pUnion->PX.uid.le), RT_BE2H_U32(pUnion->PX.gid.be), RT_LE2H_U32(pUnion->PX.gid.le), pUnion->PX.Hdr.cbEntry == ISO9660RRIPPX_LEN ? RT_BE2H_U32(pUnion->PX.INode.be) : 0, pUnion->PX.Hdr.cbEntry == ISO9660RRIPPX_LEN ? RT_LE2H_U32(pUnion->PX.INode.le) : 0 )); else { if (RTFS_IS_DIRECTORY(ISO9660_GET_ENDIAN(&pUnion->PX.fMode)) == RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode)) pObjInfo->Attr.fMode = ISO9660_GET_ENDIAN(&pUnion->PX.fMode); else LogRel(("rtFsIsoImport/Rock: 'PX' entry changes directory-ness: fMode=%#x, existing %#x; ignored\n", ISO9660_GET_ENDIAN(&pUnion->PX.fMode), pObjInfo->Attr.fMode)); pObjInfo->Attr.u.Unix.cHardlinks = ISO9660_GET_ENDIAN(&pUnion->PX.cHardlinks); pObjInfo->Attr.u.Unix.uid = ISO9660_GET_ENDIAN(&pUnion->PX.uid); pObjInfo->Attr.u.Unix.gid = ISO9660_GET_ENDIAN(&pUnion->PX.gid); /* ignore inode */ } break; case MAKE_SIG(ISO9660RRIPPN_SIG1, ISO9660RRIPPN_SIG2): /* PN */ if ( pUnion->PN.Hdr.cbEntry != ISO9660RRIPPN_LEN || pUnion->PN.Hdr.bVersion != ISO9660RRIPPN_VER || RT_BE2H_U32(pUnion->PN.Major.be) != RT_LE2H_U32(pUnion->PN.Major.le) || RT_BE2H_U32(pUnion->PN.Minor.be) != RT_LE2H_U32(pUnion->PN.Minor.le)) LogRel(("rtFsIsoImport/Rock: Malformed 'PN' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) Major=%#x/%#x Minor=%#x/%#x\n", pUnion->PN.Hdr.cbEntry, ISO9660RRIPPN_LEN, pUnion->PN.Hdr.bVersion, ISO9660RRIPPN_VER, RT_BE2H_U32(pUnion->PN.Major.be), RT_LE2H_U32(pUnion->PN.Major.le), RT_BE2H_U32(pUnion->PN.Minor.be), RT_LE2H_U32(pUnion->PN.Minor.le) )); else if (RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode)) LogRel(("rtFsIsoImport/Rock: Ignorning 'PN' entry for directory (%#x/%#x)\n", ISO9660_GET_ENDIAN(&pUnion->PN.Major), ISO9660_GET_ENDIAN(&pUnion->PN.Minor) )); else pObjInfo->Attr.u.Unix.Device = RTDEV_MAKE(ISO9660_GET_ENDIAN(&pUnion->PN.Major), ISO9660_GET_ENDIAN(&pUnion->PN.Minor)); break; case MAKE_SIG(ISO9660RRIPTF_SIG1, ISO9660RRIPTF_SIG2): /* TF */ if ( pUnion->TF.Hdr.bVersion != ISO9660RRIPTF_VER || pUnion->TF.Hdr.cbEntry < Iso9660RripTfCalcLength(pUnion->TF.fFlags)) LogRel(("rtFsIsoImport/Rock: Malformed 'TF' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) fFlags=%#x\n", pUnion->TF.Hdr.cbEntry, Iso9660RripTfCalcLength(pUnion->TF.fFlags), pUnion->TF.Hdr.bVersion, ISO9660RRIPTF_VER, RT_BE2H_U32(pUnion->TF.fFlags) )); else if (!(pUnion->TF.fFlags & ISO9660RRIPTF_F_LONG_FORM)) { PCISO9660RECTIMESTAMP pTimestamp = (PCISO9660RECTIMESTAMP)&pUnion->TF.abPayload[0]; if (pUnion->TF.fFlags & ISO9660RRIPTF_F_BIRTH) { rtFsIsoImpIso9660RecDateTime2TimeSpec(&pObjInfo->BirthTime, pTimestamp); pTimestamp++; } if (pUnion->TF.fFlags & ISO9660RRIPTF_F_MODIFY) { rtFsIsoImpIso9660RecDateTime2TimeSpec(&pObjInfo->ModificationTime, pTimestamp); pTimestamp++; } if (pUnion->TF.fFlags & ISO9660RRIPTF_F_ACCESS) { rtFsIsoImpIso9660RecDateTime2TimeSpec(&pObjInfo->AccessTime, pTimestamp); pTimestamp++; } if (pUnion->TF.fFlags & ISO9660RRIPTF_F_CHANGE) { rtFsIsoImpIso9660RecDateTime2TimeSpec(&pObjInfo->ChangeTime, pTimestamp); pTimestamp++; } } else { PCISO9660TIMESTAMP pTimestamp = (PCISO9660TIMESTAMP)&pUnion->TF.abPayload[0]; if (pUnion->TF.fFlags & ISO9660RRIPTF_F_BIRTH) { rtFsIsoImpIso9660DateTime2TimeSpecIfValid(&pObjInfo->BirthTime, pTimestamp); pTimestamp++; } if (pUnion->TF.fFlags & ISO9660RRIPTF_F_MODIFY) { rtFsIsoImpIso9660DateTime2TimeSpecIfValid(&pObjInfo->ModificationTime, pTimestamp); pTimestamp++; } if (pUnion->TF.fFlags & ISO9660RRIPTF_F_ACCESS) { rtFsIsoImpIso9660DateTime2TimeSpecIfValid(&pObjInfo->AccessTime, pTimestamp); pTimestamp++; } if (pUnion->TF.fFlags & ISO9660RRIPTF_F_CHANGE) { rtFsIsoImpIso9660DateTime2TimeSpecIfValid(&pObjInfo->ChangeTime, pTimestamp); pTimestamp++; } } break; case MAKE_SIG(ISO9660RRIPSF_SIG1, ISO9660RRIPSF_SIG2): /* SF */ LogRel(("rtFsIsoImport/Rock: Sparse file support not yet implemented!\n")); break; case MAKE_SIG(ISO9660RRIPSL_SIG1, ISO9660RRIPSL_SIG2): /* SL */ if ( pUnion->SL.Hdr.bVersion != ISO9660RRIPSL_VER || pUnion->SL.Hdr.cbEntry < RT_UOFFSETOF(ISO9660RRIPSL, abComponents[2]) || (pUnion->SL.fFlags & ~ISO9660RRIP_SL_F_CONTINUE) || (pUnion->SL.abComponents[0] & ISO9660RRIP_SL_C_RESERVED_MASK) ) LogRel(("rtFsIsoImport/Rock: Malformed 'SL' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) fFlags=%#x comp[0].fFlags=%#x\n", pUnion->SL.Hdr.cbEntry, RT_UOFFSETOF(ISO9660RRIPSL, abComponents[2]), pUnion->SL.Hdr.bVersion, ISO9660RRIPSL_VER, pUnion->SL.fFlags, pUnion->SL.abComponents[0])); else if (pThis->fSeenLastSL) LogRel(("rtFsIsoImport/Rock: Unexpected 'SL!' entry\n")); else { pThis->fSeenLastSL = !(pUnion->SL.fFlags & ISO9660RRIP_SL_F_CONTINUE); /* used in loop */ size_t offDst = strlen(pThis->szRockSymlinkTargetBuf); uint8_t const *pbSrc = &pUnion->SL.abComponents[0]; uint8_t cbSrcLeft = pUnion->SL.Hdr.cbEntry - RT_UOFFSETOF(ISO9660RRIPSL, abComponents); while (cbSrcLeft >= 2) { uint8_t const fFlags = pbSrc[0]; uint8_t cchCopy = pbSrc[1]; uint8_t const cbSkip = cchCopy + 2; if (cbSkip > cbSrcLeft) { LogRel(("rtFsIsoImport/Rock: Malformed 'SL' component: component flags=%#x, component length+2=%#x vs %#x left\n", fFlags, cbSkip, cbSrcLeft)); break; } const char *pszCopy; switch (fFlags & ~ISO9660RRIP_SL_C_CONTINUE) { case 0: pszCopy = (const char *)&pbSrc[2]; break; case ISO9660RRIP_SL_C_CURRENT: if (cchCopy != 0) LogRel(("rtFsIsoImport/Rock: Malformed 'SL' component: CURRENT + %u bytes, ignoring bytes\n", cchCopy)); pszCopy = "."; cchCopy = 1; break; case ISO9660RRIP_SL_C_PARENT: if (cchCopy != 0) LogRel(("rtFsIsoImport/Rock: Malformed 'SL' component: PARENT + %u bytes, ignoring bytes\n", cchCopy)); pszCopy = ".."; cchCopy = 2; break; case ISO9660RRIP_SL_C_ROOT: if (cchCopy != 0) LogRel(("rtFsIsoImport/Rock: Malformed 'SL' component: ROOT + %u bytes, ignoring bytes\n", cchCopy)); pszCopy = "/"; cchCopy = 1; break; default: LogRel(("rtFsIsoImport/Rock: Malformed 'SL' component: component flags=%#x (bad), component length=%#x vs %#x left\n", fFlags, cchCopy, cbSrcLeft)); pszCopy = NULL; cchCopy = 0; break; } if (offDst + cchCopy < sizeof(pThis->szRockSymlinkTargetBuf)) { memcpy(&pThis->szRockSymlinkTargetBuf[offDst], pszCopy, cchCopy); offDst += cchCopy; } else { LogRel(("rtFsIsoImport/Rock: 'SL' constructs a too long target! '%.*s%.*s'\n", offDst, pThis->szRockSymlinkTargetBuf, cchCopy, pszCopy)); memcpy(&pThis->szRockSymlinkTargetBuf[offDst], pszCopy, sizeof(pThis->szRockSymlinkTargetBuf) - offDst - 1); offDst = sizeof(pThis->szRockSymlinkTargetBuf) - 1; break; } /* Advance */ pbSrc += cbSkip; cbSrcLeft -= cbSkip; /* Append slash if appropriate. */ if ( !(fFlags & ISO9660RRIP_SL_C_CONTINUE) && (cbSrcLeft >= 2 || !pThis->fSeenLastSL) ) { if (offDst + 1 < sizeof(pThis->szRockSymlinkTargetBuf)) pThis->szRockSymlinkTargetBuf[offDst++] = '/'; else { LogRel(("rtFsIsoImport/Rock: 'SL' constructs a too long target! '%.*s/'\n", offDst, pThis->szRockSymlinkTargetBuf)); break; } } } pThis->szRockSymlinkTargetBuf[offDst] = '\0'; /* Purge the encoding as we don't want invalid UTF-8 floating around. */ RTStrPurgeEncoding(pThis->szRockSymlinkTargetBuf); } break; case MAKE_SIG(ISO9660RRIPNM_SIG1, ISO9660RRIPNM_SIG2): /* NM */ if ( pUnion->NM.Hdr.bVersion != ISO9660RRIPNM_VER || pUnion->NM.Hdr.cbEntry < RT_UOFFSETOF(ISO9660RRIPNM, achName) || (pUnion->NM.fFlags & ISO9660RRIP_NM_F_RESERVED_MASK) ) LogRel(("rtFsIsoImport/Rock: Malformed 'NM' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) fFlags=%#x %.*Rhxs\n", pUnion->NM.Hdr.cbEntry, RT_UOFFSETOF(ISO9660RRIPNM, achName), pUnion->NM.Hdr.bVersion, ISO9660RRIPNM_VER, pUnion->NM.fFlags, pUnion->NM.Hdr.cbEntry - RT_MIN(pUnion->NM.Hdr.cbEntry, RT_UOFFSETOF(ISO9660RRIPNM, achName)), &pUnion->NM.achName[0] )); else if (pThis->fSeenLastNM) LogRel(("rtFsIsoImport/Rock: Unexpected 'NM' entry!\n")); else { pThis->fSeenLastNM = !(pUnion->NM.fFlags & ISO9660RRIP_NM_F_CONTINUE); uint8_t const cchName = pUnion->NM.Hdr.cbEntry - (uint8_t)RT_UOFFSETOF(ISO9660RRIPNM, achName); if (pUnion->NM.fFlags & (ISO9660RRIP_NM_F_CURRENT | ISO9660RRIP_NM_F_PARENT)) { if (cchName == 0 && pThis->szRockNameBuf[0] == '\0') Log(("rtFsIsoImport/Rock: Ignoring 'NM' entry for '.' and '..'\n")); else LogRel(("rtFsIsoImport/Rock: Ignoring malformed 'NM' using '.' or '..': fFlags=%#x cchName=%#x %.*Rhxs; szRockNameBuf='%s'\n", pUnion->NM.fFlags, cchName, cchName, pUnion->NM.achName, pThis->szRockNameBuf)); pThis->szRockNameBuf[0] = '\0'; pThis->fSeenLastNM = true; } else { size_t offDst = strlen(pThis->szRockNameBuf); if (offDst + cchName < sizeof(pThis->szRockNameBuf)) { memcpy(&pThis->szRockNameBuf[offDst], pUnion->NM.achName, cchName); pThis->szRockNameBuf[offDst + cchName] = '\0'; /* Purge the encoding as we don't want invalid UTF-8 floating around. */ RTStrPurgeEncoding(pThis->szRockSymlinkTargetBuf); } else { LogRel(("rtFsIsoImport/Rock: 'NM' constructs a too long name, ignoring it all: '%s%.*s'\n", pThis->szRockNameBuf, cchName, pUnion->NM.achName)); pThis->szRockNameBuf[0] = '\0'; pThis->fSeenLastNM = true; } } } break; case MAKE_SIG(ISO9660RRIPCL_SIG1, ISO9660RRIPCL_SIG2): /* CL - just warn for now. */ case MAKE_SIG(ISO9660RRIPPL_SIG1, ISO9660RRIPPL_SIG2): /* PL - just warn for now. */ case MAKE_SIG(ISO9660RRIPRE_SIG1, ISO9660RRIPRE_SIG2): /* RE - just warn for now. */ LogRel(("rtFsIsoImport/Rock: Ignorning directory relocation entry '%c%c'!\n", pUnion->Hdr.bSig1, pUnion->Hdr.bSig2)); break; default: LogRel(("rtFsIsoImport/Rock: Unknown SUSP entry: %#x %#x, %#x bytes, v%u\n", pUnion->Hdr.bSig1, pUnion->Hdr.bSig2, pUnion->Hdr.cbEntry, pUnion->Hdr.bVersion)); break; #undef MAKE_SIG } } } /** * Deals with the special '.' entry in the root directory. * * @returns IPRT status code. * @param pThis The import instance. * @param pDirRec The root directory record. * @param fUnicode Indicates which namespace we're working on. */ static int rtFsIsoImportProcessIso9660TreeWorkerDoRockForRoot(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec, bool fUnicode) { uint8_t const cbSys = pDirRec->cbDirRec - RT_UOFFSETOF(ISO9660DIRREC, achFileId) - pDirRec->bFileIdLength - !(pDirRec->bFileIdLength & 1); uint8_t const * const pbSys = (uint8_t const *)&pDirRec->achFileId[pDirRec->bFileIdLength + !(pDirRec->bFileIdLength & 1)]; if (cbSys > 4) { RTFSOBJINFO ObjInfo; ObjInfo.cbObject = 0; ObjInfo.cbAllocated = 0; rtFsIsoImpIso9660RecDateTime2TimeSpec(&ObjInfo.AccessTime, &pDirRec->RecTime); ObjInfo.ModificationTime = ObjInfo.AccessTime; ObjInfo.ChangeTime = ObjInfo.AccessTime; ObjInfo.BirthTime = ObjInfo.AccessTime; ObjInfo.Attr.fMode = RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY | 0555; ObjInfo.Attr.enmAdditional = RTFSOBJATTRADD_UNIX; ObjInfo.Attr.u.Unix.uid = NIL_RTUID; ObjInfo.Attr.u.Unix.gid = NIL_RTGID; ObjInfo.Attr.u.Unix.cHardlinks = 2; ObjInfo.Attr.u.Unix.INodeIdDevice = 0; ObjInfo.Attr.u.Unix.INodeId = 0; ObjInfo.Attr.u.Unix.fFlags = 0; ObjInfo.Attr.u.Unix.GenerationId = 0; ObjInfo.Attr.u.Unix.Device = 0; rtFsIsoImportProcessIso9660TreeWorkerParseRockRidge(pThis, &ObjInfo, pbSys, cbSys, fUnicode, true /*fIsFirstDirRec*/, false /*fContinuationRecord*/); /** @todo Update root dir attribs. Need API. */ } return VINF_SUCCESS; } /** * Validates a directory record. * * @returns IPRT status code (safe to ignore, see pThis->rc). * @param pThis The importer instance. * @param pDirRec The directory record to validate. * @param cbMax The maximum size. */ static int rtFsIsoImportValidateDirRec(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec, uint32_t cbMax) { /* * Validate dual fields. */ if (RT_LE2H_U32(pDirRec->cbData.le) != RT_BE2H_U32(pDirRec->cbData.be)) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_DIR_REC, "Invalid dir rec size field: {%#RX32,%#RX32}", RT_BE2H_U32(pDirRec->cbData.be), RT_LE2H_U32(pDirRec->cbData.le)); if (RT_LE2H_U32(pDirRec->offExtent.le) != RT_BE2H_U32(pDirRec->offExtent.be)) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_DIR_REC, "Invalid dir rec extent field: {%#RX32,%#RX32}", RT_BE2H_U32(pDirRec->offExtent.be), RT_LE2H_U32(pDirRec->offExtent.le)); if (RT_LE2H_U16(pDirRec->VolumeSeqNo.le) != RT_BE2H_U16(pDirRec->VolumeSeqNo.be)) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_DIR_REC, "Invalid dir rec volume sequence ID field: {%#RX16,%#RX16}", RT_BE2H_U16(pDirRec->VolumeSeqNo.be), RT_LE2H_U16(pDirRec->VolumeSeqNo.le)); /* * Check values. */ if (ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo) != pThis->idPrimaryVol) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_DIR_REC_VOLUME_SEQ_NO, "Expected dir rec to have same volume sequence number as primary volume: %#x, expected %#x", ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo), pThis->idPrimaryVol); if (ISO9660_GET_ENDIAN(&pDirRec->offExtent) >= pThis->cBlocksInPrimaryVolumeSpace) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_DIR_REC_EXTENT_OUT_OF_BOUNDS, "Invalid dir rec extent: %#RX32, max %#RX32", ISO9660_GET_ENDIAN(&pDirRec->offExtent), pThis->cBlocksInPrimaryVolumeSpace); if (pDirRec->cbDirRec < RT_UOFFSETOF(ISO9660DIRREC, achFileId) + pDirRec->bFileIdLength) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_DIR_REC_LENGTH, "Dir record size is too small: %#x (min %#x)", pDirRec->cbDirRec, RT_UOFFSETOF(ISO9660DIRREC, achFileId) + pDirRec->bFileIdLength); if (pDirRec->cbDirRec > cbMax) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_DIR_REC_LENGTH, "Dir record size is too big: %#x (max %#x)", pDirRec->cbDirRec, cbMax); if ( (pDirRec->fFileFlags & (ISO9660_FILE_FLAGS_MULTI_EXTENT | ISO9660_FILE_FLAGS_DIRECTORY)) == (ISO9660_FILE_FLAGS_MULTI_EXTENT | ISO9660_FILE_FLAGS_DIRECTORY)) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_DIR_WITH_MORE_EXTENTS, "Multi-extent directories are not supported (cbData=%#RX32 offExtent=%#RX32)", ISO9660_GET_ENDIAN(&pDirRec->cbData), ISO9660_GET_ENDIAN(&pDirRec->offExtent)); return VINF_SUCCESS; } /** * Validates a dot or dot-dot directory record. * * @returns IPRT status code (safe to ignore, see pThis->rc). * @param pThis The importer instance. * @param pDirRec The dot directory record to validate. * @param cbMax The maximum size. * @param bName The name byte (0x00: '.', 0x01: '..'). */ static int rtFsIsoImportValidateDotDirRec(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec, uint32_t cbMax, uint8_t bName) { int rc = rtFsIsoImportValidateDirRec(pThis, pDirRec, cbMax); if (RT_SUCCESS(rc)) { if (pDirRec->bFileIdLength != 1) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_DOT_DIR_REC_BAD_NAME_LENGTH, "Invalid dot dir rec file id length: %u", pDirRec->bFileIdLength); if ((uint8_t)pDirRec->achFileId[0] != bName) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_DOT_DIR_REC_BAD_NAME, "Invalid dot dir rec file id: %#x, expected %#x", pDirRec->achFileId[0], bName); } return rc; } /** * rtFsIsoImportProcessIso9660TreeWorker helper that reads more data. * * @returns IPRT status code. * @param pThis The importer instance. * @param ppDirRec Pointer to the directory record pointer (in/out). * @param pcbChunk Pointer to the cbChunk variable (in/out). * @param pcbDir Pointer to the cbDir variable (in/out). This indicates * how much we've left to read from the directory. * @param poffNext Pointer to the offNext variable (in/out). This * indicates where the next chunk of directory data is in * the input file. */ static int rtFsIsoImportProcessIso9660TreeWorkerReadMore(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC *ppDirRec, uint32_t *pcbChunk, uint32_t *pcbDir, uint64_t *poffNext) { uint32_t cbChunk = *pcbChunk; *ppDirRec = (PCISO9660DIRREC)memmove(&pThis->abBuf[ISO9660_SECTOR_SIZE - cbChunk], *ppDirRec, cbChunk); Assert(!(*poffNext & (ISO9660_SECTOR_SIZE - 1))); uint32_t cbToRead = RT_MIN(*pcbDir, sizeof(pThis->abBuf) - ISO9660_SECTOR_SIZE); int rc = RTVfsFileReadAt(pThis->hSrcFile, *poffNext, &pThis->abBuf[ISO9660_SECTOR_SIZE], cbToRead, NULL); if (RT_SUCCESS(rc)) { Log3(("rtFsIsoImportProcessIso9660TreeWorker: Read %#zx more bytes @%#RX64, now got @%#RX64 LB %#RX32\n", cbToRead, *poffNext, *poffNext - cbChunk, cbChunk + cbToRead)); *poffNext += cbToRead; *pcbDir -= cbToRead; *pcbChunk = cbChunk + cbToRead; return VINF_SUCCESS; } return rtFsIsoImpError(pThis, rc, "Error reading %#RX32 bytes at %#RX64 (dir): %Rrc", *poffNext, cbToRead); } /** * rtFsIsoImportProcessIso9660TreeWorker helper that deals with skipping to the * next sector when cbDirRec is zero. * * @returns IPRT status code. * @retval VERR_NO_MORE_FILES when we reaches the end of the directory. * @param pThis The importer instance. * @param ppDirRec Pointer to the directory record pointer (in/out). * @param pcbChunk Pointer to the cbChunk variable (in/out). Indicates how * much we've left to process starting and pDirRec. * @param pcbDir Pointer to the cbDir variable (in/out). This indicates * how much we've left to read from the directory. * @param poffNext Pointer to the offNext variable (in/out). This * indicates where the next chunk of directory data is in * the input file. */ static int rtFsIsoImportProcessIso9660TreeWorkerHandleZeroSizedDirRec(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC *ppDirRec, uint32_t *pcbChunk, uint32_t *pcbDir, uint64_t *poffNext) { uint32_t cbChunk = *pcbChunk; uint64_t offChunk = *poffNext - cbChunk; uint32_t cbSkip = ISO9660_SECTOR_SIZE - ((uint32_t)offChunk & (ISO9660_SECTOR_SIZE - 1)); if (cbSkip < cbChunk) { *ppDirRec = (PCISO9660DIRREC)((uintptr_t)*ppDirRec + cbSkip); *pcbChunk = cbChunk -= cbSkip; if ( cbChunk > UINT8_MAX || *pcbDir == 0) { Log3(("rtFsIsoImportProcessIso9660TreeWorker: cbDirRec=0 --> jumped %#RX32 to @%#RX64 LB %#RX32\n", cbSkip, *poffNext - cbChunk, cbChunk)); return VINF_SUCCESS; } Log3(("rtFsIsoImportProcessIso9660TreeWorker: cbDirRec=0 --> jumped %#RX32 to @%#RX64 LB %#RX32, but needs to read more\n", cbSkip, *poffNext - cbChunk, cbChunk)); return rtFsIsoImportProcessIso9660TreeWorkerReadMore(pThis, ppDirRec, pcbChunk, pcbDir, poffNext); } /* ASSUMES we're working in multiples of sectors! */ if (*pcbDir == 0) { *pcbChunk = 0; return VERR_NO_MORE_FILES; } /* End of chunk, read the next sectors. */ Assert(!(*poffNext & (ISO9660_SECTOR_SIZE - 1))); uint32_t cbToRead = RT_MIN(*pcbDir, sizeof(pThis->abBuf)); int rc = RTVfsFileReadAt(pThis->hSrcFile, *poffNext, pThis->abBuf, cbToRead, NULL); if (RT_SUCCESS(rc)) { Log3(("rtFsIsoImportProcessIso9660TreeWorker: cbDirRec=0 --> Read %#zx more bytes @%#RX64, now got @%#RX64 LB %#RX32\n", cbToRead, *poffNext, *poffNext - cbChunk, cbChunk + cbToRead)); *poffNext += cbToRead; *pcbDir -= cbToRead; *pcbChunk = cbChunk + cbToRead; *ppDirRec = (PCISO9660DIRREC)&pThis->abBuf[0]; return VINF_SUCCESS; } return rtFsIsoImpError(pThis, rc, "Error reading %#RX32 bytes at %#RX64 (dir): %Rrc", *poffNext, cbToRead); } /** * Deals with a single directory. * * @returns IPRT status code (safe to ignore, see pThis->rc). * @param pThis The importer instance. * @param idxDir The configuration index for the directory. * @param offDirBlock The offset of the directory data. * @param cbDir The size of the directory data. * @param cDepth The depth of the directory. * @param fUnicode Set if it's a unicode (UTF-16BE) encoded * directory. * @param pTodoList The todo-list to add sub-directories to. */ static int rtFsIsoImportProcessIso9660TreeWorker(PRTFSISOMKIMPORTER pThis, uint32_t idxDir, uint32_t offDirBlock, uint32_t cbDir, uint8_t cDepth, bool fUnicode, PRTLISTANCHOR pTodoList) { /* * Restrict the depth to try avoid loops. */ if (cDepth > RTFSISOMK_IMPORT_MAX_DEPTH) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_TOO_DEEP_DIR_TREE, "Dir at %#x LB %#x is too deep", offDirBlock, cbDir); /* * Read the first chunk into the big buffer. */ uint32_t cbChunk = RT_MIN(cbDir, sizeof(pThis->abBuf)); uint64_t offNext = (uint64_t)offDirBlock * ISO9660_SECTOR_SIZE; int rc = RTVfsFileReadAt(pThis->hSrcFile, offNext, pThis->abBuf, cbChunk, NULL); if (RT_FAILURE(rc)) return rtFsIsoImpError(pThis, rc, "Error reading directory at %#RX64 (%#RX32 / %#RX32): %Rrc", offNext, cbChunk, cbDir); cbDir -= cbChunk; offNext += cbChunk; /* * Skip the current and parent directory entries. */ PCISO9660DIRREC pDirRec = (PCISO9660DIRREC)&pThis->abBuf[0]; rc = rtFsIsoImportValidateDotDirRec(pThis, pDirRec, cbChunk, 0x00); if (RT_FAILURE(rc)) return rc; if ( cDepth == 0 && !(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_ROCK_RIDGE) && pDirRec->cbDirRec > RT_UOFFSETOF(ISO9660DIRREC, achFileId[1])) { rc = rtFsIsoImportProcessIso9660TreeWorkerDoRockForRoot(pThis, pDirRec, fUnicode); if (RT_FAILURE(rc)) return rc; } cbChunk -= pDirRec->cbDirRec; pDirRec = (PCISO9660DIRREC)((uintptr_t)pDirRec + pDirRec->cbDirRec); rc = rtFsIsoImportValidateDotDirRec(pThis, pDirRec, cbChunk, 0x01); if (RT_FAILURE(rc)) return rc; cbChunk -= pDirRec->cbDirRec; pDirRec = (PCISO9660DIRREC)((uintptr_t)pDirRec + pDirRec->cbDirRec); /* * Work our way thru all the directory records. */ Log3(("rtFsIsoImportProcessIso9660TreeWorker: Starting at @%#RX64 LB %#RX32 (out of %#RX32) in %#x\n", offNext - cbChunk, cbChunk, cbChunk + cbDir, idxDir)); const uint32_t fNamespace = fUnicode ? RTFSISOMAKER_NAMESPACE_JOLIET : RTFSISOMAKER_NAMESPACE_ISO_9660; while ( cbChunk > 0 || cbDir > 0) { /* * Do we need to read some more? */ if ( cbChunk > UINT8_MAX || cbDir == 0) { /* No, we don't. */ } else { rc = rtFsIsoImportProcessIso9660TreeWorkerReadMore(pThis, &pDirRec, &cbChunk, &cbDir, &offNext); if (RT_FAILURE(rc)) return rc; } /* If null length, skip to the next sector. May have to read some then. */ if (pDirRec->cbDirRec != 0) { /* likely */ } else { rc = rtFsIsoImportProcessIso9660TreeWorkerHandleZeroSizedDirRec(pThis, &pDirRec, &cbChunk, &cbDir, &offNext); if (RT_FAILURE(rc)) { if (rc == VERR_NO_MORE_FILES) break; return rc; } if (pDirRec->cbDirRec == 0) continue; } /* * Validate the directory record. Give up if not valid since we're * likely to get error with subsequent record too. */ uint8_t const cbSys = pDirRec->cbDirRec - RT_UOFFSETOF(ISO9660DIRREC, achFileId) - pDirRec->bFileIdLength - !(pDirRec->bFileIdLength & 1); uint8_t const * const pbSys = (uint8_t const *)&pDirRec->achFileId[pDirRec->bFileIdLength + !(pDirRec->bFileIdLength & 1)]; Log3(("pDirRec=&abBuf[%#07zx]: @%#010RX64 cb=%#04x ff=%#04x off=%#010RX32 cb=%#010RX32 cbSys=%#x id=%.*Rhxs\n", (uintptr_t)pDirRec - (uintptr_t)&pThis->abBuf[0], offNext - cbChunk, pDirRec->cbDirRec, pDirRec->fFileFlags, ISO9660_GET_ENDIAN(&pDirRec->offExtent), ISO9660_GET_ENDIAN(&pDirRec->cbData), cbSys, pDirRec->bFileIdLength, pDirRec->achFileId)); rc = rtFsIsoImportValidateDirRec(pThis, pDirRec, cbChunk); if (RT_FAILURE(rc)) return rc; /* This early calculation of the next record is due to multi-extent handling further down. */ uint32_t cbChunkNew = cbChunk - pDirRec->cbDirRec; PCISO9660DIRREC pDirRecNext = (PCISO9660DIRREC)((uintptr_t)pDirRec + pDirRec->cbDirRec); /* Start Collecting object info. */ RTFSOBJINFO ObjInfo; ObjInfo.cbObject = ISO9660_GET_ENDIAN(&pDirRec->cbData); ObjInfo.cbAllocated = ObjInfo.cbObject; rtFsIsoImpIso9660RecDateTime2TimeSpec(&ObjInfo.AccessTime, &pDirRec->RecTime); ObjInfo.ModificationTime = ObjInfo.AccessTime; ObjInfo.ChangeTime = ObjInfo.AccessTime; ObjInfo.BirthTime = ObjInfo.AccessTime; ObjInfo.Attr.fMode = pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY ? RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY | 0555 : RTFS_TYPE_FILE | RTFS_DOS_ARCHIVED | 0444; ObjInfo.Attr.enmAdditional = RTFSOBJATTRADD_UNIX; ObjInfo.Attr.u.Unix.uid = NIL_RTUID; ObjInfo.Attr.u.Unix.gid = NIL_RTGID; ObjInfo.Attr.u.Unix.cHardlinks = 1; ObjInfo.Attr.u.Unix.INodeIdDevice = 0; ObjInfo.Attr.u.Unix.INodeId = 0; ObjInfo.Attr.u.Unix.fFlags = 0; ObjInfo.Attr.u.Unix.GenerationId = 0; ObjInfo.Attr.u.Unix.Device = 0; /* * Convert the name into the name buffer (szNameBuf). */ if (!fUnicode) { memcpy(pThis->szNameBuf, pDirRec->achFileId, pDirRec->bFileIdLength); pThis->szNameBuf[pDirRec->bFileIdLength] = '\0'; rc = RTStrValidateEncoding(pThis->szNameBuf); } else { char *pszDst = pThis->szNameBuf; rc = RTUtf16BigToUtf8Ex((PRTUTF16)pDirRec->achFileId, pDirRec->bFileIdLength / sizeof(RTUTF16), &pszDst, sizeof(pThis->szNameBuf), NULL); } if (RT_SUCCESS(rc)) { /* Drop the version from the name. */ size_t cchName = strlen(pThis->szNameBuf); if ( !(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY) && cchName > 2 && RT_C_IS_DIGIT(pThis->szNameBuf[cchName - 1])) { uint32_t offName = 2; while ( offName <= 5 && offName + 1 < cchName && RT_C_IS_DIGIT(pThis->szNameBuf[cchName - offName])) offName++; if ( offName + 1 < cchName && pThis->szNameBuf[cchName - offName] == ';') { RTStrToUInt32Full(&pThis->szNameBuf[cchName - offName + 1], 10, &ObjInfo.Attr.u.Unix.GenerationId); pThis->szNameBuf[cchName - offName] = '\0'; } } Log3((" --> name='%s'\n", pThis->szNameBuf)); pThis->szRockNameBuf[0] = '\0'; pThis->szRockSymlinkTargetBuf[0] = '\0'; if (cbSys > 0 && !(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_ROCK_RIDGE)) { pThis->fSeenLastNM = false; pThis->fSeenLastSL = false; pThis->szRockNameBuf[0] = '\0'; pThis->szRockSymlinkTargetBuf[0] = '\0'; rtFsIsoImportProcessIso9660TreeWorkerParseRockRidge(pThis, &ObjInfo, pbSys, cbSys, fUnicode, false /*fContinuationRecord*/, false /*fIsFirstDirRec*/); } /* * Deal with multi-extent files (usually large ones). We currently only * handle files where the data is in single continuous chunk and only split * up into multiple directory records because of data type limitations. */ uint8_t abDirRecCopy[256]; uint64_t cbData = ISO9660_GET_ENDIAN(&pDirRec->cbData); if (!(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT)) { /* likely */ } else { if (cbData & (ISO9660_SECTOR_SIZE - 1)) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MISALIGNED_MULTI_EXTENT, "The size of non-final multi-extent record #0x0 isn't block aligned: %#RX64", cbData); /* Make a copy of the first directory record so we don't overwrite it when reading in more records below. */ pDirRec = (PCISO9660DIRREC)memcpy(abDirRecCopy, pDirRec, pDirRec->cbDirRec); /* Process extent records. */ uint32_t cDirRecs = 1; uint32_t offNextBlock = ISO9660_GET_ENDIAN(&pDirRec->offExtent) + ISO9660_GET_ENDIAN(&pDirRec->cbData) / ISO9660_SECTOR_SIZE; while ( cbChunkNew > 0 || cbDir > 0) { /* Read more? Skip? */ if ( cbChunkNew <= UINT8_MAX && cbDir != 0) { rc = rtFsIsoImportProcessIso9660TreeWorkerReadMore(pThis, &pDirRecNext, &cbChunkNew, &cbDir, &offNext); if (RT_FAILURE(rc)) return rc; } if (pDirRecNext->cbDirRec == 0) { rc = rtFsIsoImportProcessIso9660TreeWorkerHandleZeroSizedDirRec(pThis, &pDirRecNext, &cbChunkNew, &cbDir, &offNext); if (RT_FAILURE(rc)) { if (rc == VERR_NO_MORE_FILES) break; return rc; } if (pDirRecNext->cbDirRec == 0) continue; } /* Check the next record. */ rc = rtFsIsoImportValidateDirRec(pThis, pDirRecNext, cbChunkNew); if (RT_FAILURE(rc)) return rc; if (pDirRecNext->bFileIdLength != pDirRec->bFileIdLength) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MISMATCHING_MULTI_EXTENT_REC, "Multi-extent record #%#x differs from the first: bFileIdLength is %#x, expected %#x", cDirRecs, pDirRecNext->bFileIdLength, pDirRec->bFileIdLength); if (memcmp(pDirRecNext->achFileId, pDirRec->achFileId, pDirRec->bFileIdLength) != 0) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MISMATCHING_MULTI_EXTENT_REC, "Multi-extent record #%#x differs from the first: achFileId is %.*Rhxs, expected %.*Rhxs", cDirRecs, pDirRecNext->bFileIdLength, pDirRecNext->achFileId, pDirRec->bFileIdLength, pDirRec->achFileId); if (ISO9660_GET_ENDIAN(&pDirRecNext->VolumeSeqNo) != ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo)) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MISMATCHING_MULTI_EXTENT_REC, "Multi-extent record #%#x differs from the first: VolumeSeqNo is %#x, expected %#x", cDirRecs, ISO9660_GET_ENDIAN(&pDirRecNext->VolumeSeqNo), ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo)); if ( (pDirRecNext->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT) && (ISO9660_GET_ENDIAN(&pDirRecNext->cbData) & (ISO9660_SECTOR_SIZE - 1)) ) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MISALIGNED_MULTI_EXTENT, "The size of non-final multi-extent record #%#x isn't block aligned: %#RX32", cDirRecs, ISO9660_GET_ENDIAN(&pDirRecNext->cbData)); /* Check that the data is contiguous, then add the data. */ if (ISO9660_GET_ENDIAN(&pDirRecNext->offExtent) == offNextBlock) cbData += ISO9660_GET_ENDIAN(&pDirRecNext->cbData); else return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_NON_CONTIGUOUS_MULTI_EXTENT, "Multi-extent record #%#x isn't contiguous: offExtent=%#RX32, expected %#RX32", cDirRecs, ISO9660_GET_ENDIAN(&pDirRecNext->offExtent), offNextBlock); /* Advance. */ cDirRecs++; bool fDone = !(pDirRecNext->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT); offNext += ISO9660_GET_ENDIAN(&pDirRecNext->cbData) / ISO9660_SECTOR_SIZE; cbChunkNew -= pDirRecNext->cbDirRec; pDirRecNext = (PCISO9660DIRREC)((uintptr_t)pDirRecNext + pDirRecNext->cbDirRec); if (fDone) break; } } if (RT_SUCCESS(rc)) { /* * Add the object. */ if (pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY) rtFsIsoImportProcessIso9660AddAndNameDirectory(pThis, pDirRec, &ObjInfo, cbData, fNamespace, idxDir, pThis->szNameBuf, pThis->szRockNameBuf, cDepth + 1, pTodoList); else if (pThis->szRockSymlinkTargetBuf[0] == '\0') { if (strcmp(pThis->szNameBuf, pThis->pszTransTbl) != 0) rtFsIsoImportProcessIso9660AddAndNameFile(pThis, pDirRec, &ObjInfo, cbData, fNamespace, idxDir, pThis->szNameBuf, pThis->szRockNameBuf); } else rtFsIsoImportProcessIso9660AddAndNameSymlink(pThis, pDirRec, &ObjInfo, fNamespace, idxDir, pThis->szNameBuf, pThis->szRockNameBuf, pThis->szRockSymlinkTargetBuf); } } else rtFsIsoImpError(pThis, rc, "Invalid name at %#RX64: %.Rhxs", offNext - cbChunk, pDirRec->bFileIdLength, pDirRec->achFileId); /* * Advance to the next directory record. */ cbChunk = cbChunkNew; pDirRec = pDirRecNext; } return VINF_SUCCESS; } /** * Deals with a directory tree. * * This is implemented by tracking directories that needs to be processed in a * todo list, so no recursive calls, however it uses a bit of heap. * * @returns IPRT status code (safe to ignore, see pThis->rc). * @param pThis The importer instance. * @param offDirBlock The offset of the root directory data. * @param cbDir The size of the root directory data. * @param fUnicode Set if it's a unicode (UTF-16BE) encoded * directory. */ static int rtFsIsoImportProcessIso9660Tree(PRTFSISOMKIMPORTER pThis, uint32_t offDirBlock, uint32_t cbDir, bool fUnicode) { /* * Reset some parsing state. */ pThis->offSuspSkip = 0; pThis->fSuspSeenSP = false; pThis->pszTransTbl = "TRANS.TBL"; /** @todo query this from the iso maker! */ /* * Make sure we've got a root in the namespace. */ uint32_t idxDir = RTFsIsoMakerGetObjIdxForPath(pThis->hIsoMaker, !fUnicode ? RTFSISOMAKER_NAMESPACE_ISO_9660 : RTFSISOMAKER_NAMESPACE_JOLIET, "/"); if (idxDir == UINT32_MAX) { idxDir = RTFSISOMAKER_CFG_IDX_ROOT; int rc = RTFsIsoMakerObjSetPath(pThis->hIsoMaker, RTFSISOMAKER_CFG_IDX_ROOT, !fUnicode ? RTFSISOMAKER_NAMESPACE_ISO_9660 : RTFSISOMAKER_NAMESPACE_JOLIET, "/"); if (RT_FAILURE(rc)) return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerObjSetPath failed on root dir: %Rrc", rc); } Assert(idxDir == RTFSISOMAKER_CFG_IDX_ROOT); /* * Directories. */ int rc = VINF_SUCCESS; uint8_t cDepth = 0; RTLISTANCHOR TodoList; RTListInit(&TodoList); for (;;) { int rc2 = rtFsIsoImportProcessIso9660TreeWorker(pThis, idxDir, offDirBlock, cbDir, cDepth, fUnicode, &TodoList); if (RT_FAILURE(rc2) && RT_SUCCESS(rc)) rc = rc2; /* * Pop the next directory. */ PRTFSISOMKIMPDIR pNext = RTListRemoveLast(&TodoList, RTFSISOMKIMPDIR, Entry); if (!pNext) break; idxDir = pNext->idxObj; offDirBlock = pNext->offDirBlock; cbDir = pNext->cbDir; cDepth = pNext->cDepth; RTMemFree(pNext); } return rc; } /** * Imports a UTF-16BE string property from the joliet volume descriptor. * * The fields are normally space filled and padded, but we also consider zero * bytes are fillers. If the field only contains padding, the string property * will remain unchanged. * * @returns IPRT status code (ignorable). * @param pThis The importer instance. * @param pachField Pointer to the field. The structure type * is 'char' for hysterical raisins, while the * real type is 'RTUTF16'. * @param cchField The field length. * @param enmStringProp The corresponding string property. * * @note Clobbers pThis->pbBuf! */ static int rtFsIsoImportUtf16BigStringField(PRTFSISOMKIMPORTER pThis, const char *pachField, size_t cchField, RTFSISOMAKERSTRINGPROP enmStringProp) { /* * Scan the field from the end as this way we know the result length if we find anything. */ PCRTUTF16 pwcField = (PCRTUTF16)pachField; size_t cwcField = cchField / sizeof(RTUTF16); /* ignores any odd field byte */ size_t off = cwcField; while (off-- > 0) { RTUTF16 wc = RT_BE2H_U16(pwcField[off]); if (wc == ' ' || wc == '\0') { /* likely */ } else { /* * Convert to UTF-16. */ char *pszCopy = (char *)pThis->abBuf; int rc = RTUtf16BigToUtf8Ex(pwcField, off + 1, &pszCopy, sizeof(pThis->abBuf), NULL); if (RT_SUCCESS(rc)) { rc = RTFsIsoMakerSetStringProp(pThis->hIsoMaker, enmStringProp, RTFSISOMAKER_NAMESPACE_JOLIET, pszCopy); if (RT_SUCCESS(rc)) return VINF_SUCCESS; return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerSetStringProp failed setting field %d to '%s': %Rrc", enmStringProp, pszCopy, rc); } return rtFsIsoImpError(pThis, rc, "RTUtf16BigToUtf8Ex failed converting field %d to UTF-8: %Rrc - %.*Rhxs", enmStringProp, rc, off * sizeof(RTUTF16), pwcField); } } return VINF_SUCCESS; } /** * Imports a string property from the primary volume descriptor. * * The fields are normally space filled and padded, but we also consider zero * bytes are fillers. If the field only contains padding, the string property * will remain unchanged. * * @returns IPRT status code (ignorable). * @param pThis The importer instance. * @param pachField Pointer to the field. * @param cchField The field length. * @param enmStringProp The corresponding string property. * * @note Clobbers pThis->pbBuf! */ static int rtFsIsoImportAsciiStringField(PRTFSISOMKIMPORTER pThis, const char *pachField, size_t cchField, RTFSISOMAKERSTRINGPROP enmStringProp) { /* * Scan the field from the end as this way we know the result length if we find anything. */ size_t off = cchField; while (off-- > 0) { char ch = pachField[off]; if (ch == ' ' || ch == '\0') { /* likely */ } else { /* * Make a copy of the string in abBuf, purge the encoding. */ off++; char *pszCopy = (char *)pThis->abBuf; memcpy(pszCopy, pachField, off); pszCopy[off] = '\0'; RTStrPurgeEncoding(pszCopy); int rc = RTFsIsoMakerSetStringProp(pThis->hIsoMaker, enmStringProp, RTFSISOMAKER_NAMESPACE_ISO_9660, pszCopy); if (RT_SUCCESS(rc)) return VINF_SUCCESS; return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerSetStringProp failed setting field %d to '%s': %Rrc", enmStringProp, pszCopy, rc); } } return VINF_SUCCESS; } /** * Validates a root directory record. * * @returns IPRT status code (safe to ignore, see pThis->rc). * @param pThis The importer instance. * @param pDirRec The root directory record to validate. */ static int rtFsIsoImportValidateRootDirRec(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec) { /* * Validate dual fields. */ if (RT_LE2H_U32(pDirRec->cbData.le) != RT_BE2H_U32(pDirRec->cbData.be)) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_ROOT_DIR_REC, "Invalid root dir size: {%#RX32,%#RX32}", RT_BE2H_U32(pDirRec->cbData.be), RT_LE2H_U32(pDirRec->cbData.le)); if (RT_LE2H_U32(pDirRec->offExtent.le) != RT_BE2H_U32(pDirRec->offExtent.be)) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_ROOT_DIR_REC, "Invalid root dir extent: {%#RX32,%#RX32}", RT_BE2H_U32(pDirRec->offExtent.be), RT_LE2H_U32(pDirRec->offExtent.le)); if (RT_LE2H_U16(pDirRec->VolumeSeqNo.le) != RT_BE2H_U16(pDirRec->VolumeSeqNo.be)) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_ROOT_DIR_REC, "Invalid root dir volume sequence ID: {%#RX16,%#RX16}", RT_BE2H_U16(pDirRec->VolumeSeqNo.be), RT_LE2H_U16(pDirRec->VolumeSeqNo.le)); /* * Check values. */ if (ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo) != pThis->idPrimaryVol) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_ROOT_VOLUME_SEQ_NO, "Expected root dir to have same volume sequence number as primary volume: %#x, expected %#x", ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo), pThis->idPrimaryVol); if (ISO9660_GET_ENDIAN(&pDirRec->cbData) == 0) return RTErrInfoSet(pThis->pErrInfo, VERR_ISOMK_IMPORT_ZERO_SIZED_ROOT_DIR, "Zero sized root dir"); if (ISO9660_GET_ENDIAN(&pDirRec->offExtent) >= pThis->cBlocksInPrimaryVolumeSpace) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_ROOT_DIR_EXTENT_OUT_OF_BOUNDS, "Invalid root dir extent: %#RX32, max %#RX32", ISO9660_GET_ENDIAN(&pDirRec->offExtent), pThis->cBlocksInPrimaryVolumeSpace); if (pDirRec->cbDirRec < RT_UOFFSETOF(ISO9660DIRREC, achFileId)) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_ROOT_DIR_REC_LENGTH, "Root dir record size is too small: %#x (min %#x)", pDirRec->cbDirRec, RT_UOFFSETOF(ISO9660DIRREC, achFileId)); if (!(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY)) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_ROOT_DIR_WITHOUT_DIR_FLAG, "Root dir is not flagged as directory: %#x", pDirRec->fFileFlags); if (pDirRec->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_ROOT_DIR_IS_MULTI_EXTENT, "Root dir is cannot be multi-extent: %#x", pDirRec->fFileFlags); return VINF_SUCCESS; } /** * Processes a primary volume descriptor, importing all files and stuff. * * @returns IPRT status code (safe to ignore, see pThis->rc). * @param pThis The importer instance. * @param pVolDesc The primary volume descriptor. */ static int rtFsIsoImportProcessPrimaryDesc(PRTFSISOMKIMPORTER pThis, PISO9660PRIMARYVOLDESC pVolDesc) { /* * Validate dual fields first. */ if (pVolDesc->bFileStructureVersion != ISO9660_FILE_STRUCTURE_VERSION) return rtFsIsoImpError(pThis, VERR_IOSMK_IMPORT_PRIMARY_VOL_DESC_VER, "Unsupported file structure version: %#x", pVolDesc->bFileStructureVersion); if (RT_LE2H_U16(pVolDesc->cbLogicalBlock.le) != RT_BE2H_U16(pVolDesc->cbLogicalBlock.be)) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_PRIMARY_VOL_DESC, "Mismatching logical block size: {%#RX16,%#RX16}", RT_BE2H_U16(pVolDesc->cbLogicalBlock.be), RT_LE2H_U16(pVolDesc->cbLogicalBlock.le)); if (RT_LE2H_U32(pVolDesc->VolumeSpaceSize.le) != RT_BE2H_U32(pVolDesc->VolumeSpaceSize.be)) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_PRIMARY_VOL_DESC, "Mismatching volume space size: {%#RX32,%#RX32}", RT_BE2H_U32(pVolDesc->VolumeSpaceSize.be), RT_LE2H_U32(pVolDesc->VolumeSpaceSize.le)); if (RT_LE2H_U16(pVolDesc->cVolumesInSet.le) != RT_BE2H_U16(pVolDesc->cVolumesInSet.be)) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_PRIMARY_VOL_DESC, "Mismatching volumes in set: {%#RX16,%#RX16}", RT_BE2H_U16(pVolDesc->cVolumesInSet.be), RT_LE2H_U16(pVolDesc->cVolumesInSet.le)); if (RT_LE2H_U16(pVolDesc->VolumeSeqNo.le) != RT_BE2H_U16(pVolDesc->VolumeSeqNo.be)) { /* Hack alert! An Windows NT 3.1 ISO was found to not have the big endian bit set here, so work around it. */ if ( pVolDesc->VolumeSeqNo.be == 0 && pVolDesc->VolumeSeqNo.le == RT_H2LE_U16_C(1)) pVolDesc->VolumeSeqNo.be = RT_H2BE_U16_C(1); else return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_PRIMARY_VOL_DESC, "Mismatching volume sequence no.: {%#RX16,%#RX16}", RT_BE2H_U16(pVolDesc->VolumeSeqNo.be), RT_LE2H_U16(pVolDesc->VolumeSeqNo.le)); } if (RT_LE2H_U32(pVolDesc->cbPathTable.le) != RT_BE2H_U32(pVolDesc->cbPathTable.be)) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_PRIMARY_VOL_DESC, "Mismatching path table size: {%#RX32,%#RX32}", RT_BE2H_U32(pVolDesc->cbPathTable.be), RT_LE2H_U32(pVolDesc->cbPathTable.le)); /* * Validate field values against our expectations. */ if (ISO9660_GET_ENDIAN(&pVolDesc->cbLogicalBlock) != ISO9660_SECTOR_SIZE) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_LOGICAL_BLOCK_SIZE_NOT_2KB, "Unsupported block size: %#x", ISO9660_GET_ENDIAN(&pVolDesc->cbLogicalBlock)); if (ISO9660_GET_ENDIAN(&pVolDesc->cVolumesInSet) != 1) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MORE_THAN_ONE_VOLUME_IN_SET, "Volumes in set: %#x", ISO9660_GET_ENDIAN(&pVolDesc->cVolumesInSet)); if (ISO9660_GET_ENDIAN(&pVolDesc->VolumeSeqNo) != 1) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_INVALID_VOLUMNE_SEQ_NO, "Unexpected volume sequence number: %#x", ISO9660_GET_ENDIAN(&pVolDesc->VolumeSeqNo)); /* * Gather info we need. */ pThis->cBlocksInPrimaryVolumeSpace = ISO9660_GET_ENDIAN(&pVolDesc->VolumeSpaceSize); pThis->cbPrimaryVolumeSpace = pThis->cBlocksInPrimaryVolumeSpace * (uint64_t)ISO9660_SECTOR_SIZE; pThis->cVolumesInSet = ISO9660_GET_ENDIAN(&pVolDesc->cVolumesInSet); pThis->idPrimaryVol = ISO9660_GET_ENDIAN(&pVolDesc->VolumeSeqNo); /* * Validate the root directory record. */ int rc = rtFsIsoImportValidateRootDirRec(pThis, &pVolDesc->RootDir.DirRec); if (RT_SUCCESS(rc)) { /* * Import stuff if present and not opted out. */ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_SYSTEM_ID)) rtFsIsoImportAsciiStringField(pThis, pVolDesc->achSystemId, sizeof(pVolDesc->achSystemId), RTFSISOMAKERSTRINGPROP_SYSTEM_ID); if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_VOLUME_ID)) rtFsIsoImportAsciiStringField(pThis, pVolDesc->achVolumeId, sizeof(pVolDesc->achVolumeId), RTFSISOMAKERSTRINGPROP_VOLUME_ID); if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_VOLUME_SET_ID)) rtFsIsoImportAsciiStringField(pThis, pVolDesc->achVolumeSetId, sizeof(pVolDesc->achVolumeSetId), RTFSISOMAKERSTRINGPROP_VOLUME_SET_ID); if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_PUBLISHER_ID)) rtFsIsoImportAsciiStringField(pThis, pVolDesc->achPublisherId, sizeof(pVolDesc->achPublisherId), RTFSISOMAKERSTRINGPROP_PUBLISHER_ID); if (pThis->fFlags & RTFSISOMK_IMPORT_F_DATA_PREPARER_ID) rtFsIsoImportAsciiStringField(pThis, pVolDesc->achDataPreparerId, sizeof(pVolDesc->achDataPreparerId), RTFSISOMAKERSTRINGPROP_DATA_PREPARER_ID); if (pThis->fFlags & RTFSISOMK_IMPORT_F_APPLICATION_ID) rtFsIsoImportAsciiStringField(pThis, pVolDesc->achApplicationId, sizeof(pVolDesc->achApplicationId), RTFSISOMAKERSTRINGPROP_APPLICATION_ID); if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_COPYRIGHT_FID)) rtFsIsoImportAsciiStringField(pThis, pVolDesc->achCopyrightFileId, sizeof(pVolDesc->achCopyrightFileId), RTFSISOMAKERSTRINGPROP_COPYRIGHT_FILE_ID); if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_ABSTRACT_FID)) rtFsIsoImportAsciiStringField(pThis, pVolDesc->achAbstractFileId, sizeof(pVolDesc->achAbstractFileId), RTFSISOMAKERSTRINGPROP_ABSTRACT_FILE_ID); if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_BIBLIO_FID)) rtFsIsoImportAsciiStringField(pThis, pVolDesc->achBibliographicFileId, sizeof(pVolDesc->achBibliographicFileId), RTFSISOMAKERSTRINGPROP_BIBLIOGRAPHIC_FILE_ID); /* * Process the directory tree. */ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_PRIMARY_ISO)) rc = rtFsIsoImportProcessIso9660Tree(pThis, ISO9660_GET_ENDIAN(&pVolDesc->RootDir.DirRec.offExtent), ISO9660_GET_ENDIAN(&pVolDesc->RootDir.DirRec.cbData), false /*fUnicode*/); } return rc; } /** * Processes a secondary volume descriptor, if it is joliet we'll importing all * the files and stuff. * * @returns IPRT status code (safe to ignore, see pThis->rc). * @param pThis The importer instance. * @param pVolDesc The primary volume descriptor. */ static int rtFsIsoImportProcessSupplementaryDesc(PRTFSISOMKIMPORTER pThis, PISO9660SUPVOLDESC pVolDesc) { /* * Validate dual fields first. */ if (pVolDesc->bFileStructureVersion != ISO9660_FILE_STRUCTURE_VERSION) return rtFsIsoImpError(pThis, VERR_IOSMK_IMPORT_SUP_VOL_DESC_VER, "Unsupported file structure version: %#x", pVolDesc->bFileStructureVersion); if (RT_LE2H_U16(pVolDesc->cbLogicalBlock.le) != RT_BE2H_U16(pVolDesc->cbLogicalBlock.be)) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_SUP_VOL_DESC, "Mismatching logical block size: {%#RX16,%#RX16}", RT_BE2H_U16(pVolDesc->cbLogicalBlock.be), RT_LE2H_U16(pVolDesc->cbLogicalBlock.le)); if (RT_LE2H_U32(pVolDesc->VolumeSpaceSize.le) != RT_BE2H_U32(pVolDesc->VolumeSpaceSize.be)) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_SUP_VOL_DESC, "Mismatching volume space size: {%#RX32,%#RX32}", RT_BE2H_U32(pVolDesc->VolumeSpaceSize.be), RT_LE2H_U32(pVolDesc->VolumeSpaceSize.le)); if (RT_LE2H_U16(pVolDesc->cVolumesInSet.le) != RT_BE2H_U16(pVolDesc->cVolumesInSet.be)) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_SUP_VOL_DESC, "Mismatching volumes in set: {%#RX16,%#RX16}", RT_BE2H_U16(pVolDesc->cVolumesInSet.be), RT_LE2H_U16(pVolDesc->cVolumesInSet.le)); if (RT_LE2H_U16(pVolDesc->VolumeSeqNo.le) != RT_BE2H_U16(pVolDesc->VolumeSeqNo.be)) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_SUP_VOL_DESC, "Mismatching volume sequence no.: {%#RX16,%#RX16}", RT_BE2H_U16(pVolDesc->VolumeSeqNo.be), RT_LE2H_U16(pVolDesc->VolumeSeqNo.le)); if (RT_LE2H_U32(pVolDesc->cbPathTable.le) != RT_BE2H_U32(pVolDesc->cbPathTable.be)) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_SUP_VOL_DESC, "Mismatching path table size: {%#RX32,%#RX32}", RT_BE2H_U32(pVolDesc->cbPathTable.be), RT_LE2H_U32(pVolDesc->cbPathTable.le)); /* * Validate field values against our expectations. */ if (ISO9660_GET_ENDIAN(&pVolDesc->cbLogicalBlock) != ISO9660_SECTOR_SIZE) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_LOGICAL_BLOCK_SIZE_NOT_2KB, "Unsupported block size: %#x", ISO9660_GET_ENDIAN(&pVolDesc->cbLogicalBlock)); if (ISO9660_GET_ENDIAN(&pVolDesc->cVolumesInSet) != pThis->cVolumesInSet) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_VOLUME_IN_SET_MISMATCH, "Volumes in set: %#x, expected %#x", ISO9660_GET_ENDIAN(&pVolDesc->cVolumesInSet), pThis->cVolumesInSet); if (ISO9660_GET_ENDIAN(&pVolDesc->VolumeSeqNo) != pThis->idPrimaryVol) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_INVALID_VOLUMNE_SEQ_NO, "Unexpected volume sequence number: %#x (expected %#x)", ISO9660_GET_ENDIAN(&pVolDesc->VolumeSeqNo), pThis->idPrimaryVol); if (ISO9660_GET_ENDIAN(&pVolDesc->VolumeSpaceSize) != pThis->cBlocksInPrimaryVolumeSpace) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_INVALID_VOLUMNE_SEQ_NO, "Volume space size differs between primary and supplementary descriptors: %#x, primary %#x", ISO9660_GET_ENDIAN(&pVolDesc->VolumeSpaceSize), pThis->cBlocksInPrimaryVolumeSpace); /* * Validate the root directory record. */ int rc = rtFsIsoImportValidateRootDirRec(pThis, &pVolDesc->RootDir.DirRec); if (RT_FAILURE(rc)) return rc; /* * Is this a joliet descriptor? Ignore if not. */ uint8_t uJolietLevel = 0; if ( pVolDesc->abEscapeSequences[0] == ISO9660_JOLIET_ESC_SEQ_0 && pVolDesc->abEscapeSequences[1] == ISO9660_JOLIET_ESC_SEQ_1) switch (pVolDesc->abEscapeSequences[2]) { case ISO9660_JOLIET_ESC_SEQ_2_LEVEL_1: uJolietLevel = 1; break; case ISO9660_JOLIET_ESC_SEQ_2_LEVEL_2: uJolietLevel = 2; break; case ISO9660_JOLIET_ESC_SEQ_2_LEVEL_3: uJolietLevel = 3; break; default: Log(("rtFsIsoImportProcessSupplementaryDesc: last joliet escape sequence byte doesn't match: %#x\n", pVolDesc->abEscapeSequences[2])); } if (uJolietLevel == 0) return VINF_SUCCESS; /* * Only one joliet descriptor. */ if (pThis->fSeenJoliet) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MULTIPLE_JOLIET_VOL_DESCS, "More than one Joliet volume descriptor is not supported"); pThis->fSeenJoliet = true; /* * Import stuff if present and not opted out. */ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_SYSTEM_ID)) rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achSystemId, sizeof(pVolDesc->achSystemId), RTFSISOMAKERSTRINGPROP_SYSTEM_ID); if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_J_VOLUME_ID)) rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achVolumeId, sizeof(pVolDesc->achVolumeId), RTFSISOMAKERSTRINGPROP_VOLUME_ID); if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_J_VOLUME_SET_ID)) rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achVolumeSetId, sizeof(pVolDesc->achVolumeSetId), RTFSISOMAKERSTRINGPROP_VOLUME_SET_ID); if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_J_PUBLISHER_ID)) rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achPublisherId, sizeof(pVolDesc->achPublisherId), RTFSISOMAKERSTRINGPROP_PUBLISHER_ID); if (pThis->fFlags & RTFSISOMK_IMPORT_F_J_DATA_PREPARER_ID) rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achDataPreparerId, sizeof(pVolDesc->achDataPreparerId), RTFSISOMAKERSTRINGPROP_DATA_PREPARER_ID); if (pThis->fFlags & RTFSISOMK_IMPORT_F_J_APPLICATION_ID) rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achApplicationId, sizeof(pVolDesc->achApplicationId), RTFSISOMAKERSTRINGPROP_APPLICATION_ID); if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_J_COPYRIGHT_FID)) rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achCopyrightFileId, sizeof(pVolDesc->achCopyrightFileId), RTFSISOMAKERSTRINGPROP_COPYRIGHT_FILE_ID); if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_J_ABSTRACT_FID)) rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achAbstractFileId, sizeof(pVolDesc->achAbstractFileId), RTFSISOMAKERSTRINGPROP_ABSTRACT_FILE_ID); if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_J_BIBLIO_FID)) rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achBibliographicFileId, sizeof(pVolDesc->achBibliographicFileId), RTFSISOMAKERSTRINGPROP_BIBLIOGRAPHIC_FILE_ID); /* * Process the directory tree. */ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_JOLIET)) return rtFsIsoImportProcessIso9660Tree(pThis, ISO9660_GET_ENDIAN(&pVolDesc->RootDir.DirRec.offExtent), ISO9660_GET_ENDIAN(&pVolDesc->RootDir.DirRec.cbData), true /*fUnicode*/); return VINF_SUCCESS; } /** * Checks out an El Torito boot image to see if it requires info table patching. * * @returns IPRT status code (ignored). * @param pThis The ISO importer instance. * @param idxImageObj The configuration index of the image. * @param offBootImage The block offset of the image. */ static int rtFsIsoImportProcessElToritoImage(PRTFSISOMKIMPORTER pThis, uint32_t idxImageObj, uint32_t offBootImage) { ISO9660SYSLINUXINFOTABLE InfoTable; int rc = RTVfsFileReadAt(pThis->hSrcFile, offBootImage * (uint64_t)ISO9660_SECTOR_SIZE + ISO9660SYSLINUXINFOTABLE_OFFSET, &InfoTable, sizeof(InfoTable), NULL); if (RT_SUCCESS(rc)) { if ( RT_LE2H_U32(InfoTable.offBootFile) == offBootImage && RT_LE2H_U32(InfoTable.offPrimaryVolDesc) == pThis->offPrimaryVolDesc && ASMMemIsAllU8(&InfoTable.auReserved[0], sizeof(InfoTable.auReserved), 0) ) { rc = RTFsIsoMakerObjEnableBootInfoTablePatching(pThis->hIsoMaker, idxImageObj, true /*fEnable*/); if (RT_FAILURE(rc)) return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerObjEnableBootInfoTablePatching failed: %Rrc", rc); } } return VINF_SUCCESS; } /** * Processes a boot catalog default or section entry. * * @returns IPRT status code (ignored). * @param pThis The ISO importer instance. * @param iEntry The boot catalog entry number. This is 1 for * the default entry, and 3+ for section entries. * @param cMaxEntries Maximum number of entries. * @param pEntry The entry to process. * @param pcSkip Where to return the number of extension entries to skip. */ static int rtFsIsoImportProcessElToritoSectionEntry(PRTFSISOMKIMPORTER pThis, uint32_t iEntry, uint32_t cMaxEntries, PCISO9660ELTORITOSECTIONENTRY pEntry, uint32_t *pcSkip) { *pcSkip = 0; /* * Check the boot indicator type for entry 1. */ if ( pEntry->bBootIndicator != ISO9660_ELTORITO_BOOT_INDICATOR_BOOTABLE && pEntry->bBootIndicator != ISO9660_ELTORITO_BOOT_INDICATOR_NOT_BOOTABLE) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_DEF_ENTRY_INVALID_BOOT_IND, "Default boot catalog entry has an invalid boot indicator: %#x", pEntry->bBootIndicator); /* * Check the media type and flags. */ uint32_t cbDefaultSize; uint8_t bMediaType = pEntry->bBootMediaType; switch (bMediaType & ISO9660_ELTORITO_BOOT_MEDIA_TYPE_MASK) { case ISO9660_ELTORITO_BOOT_MEDIA_TYPE_FLOPPY_1_2_MB: cbDefaultSize = 512 * 80 * 15 * 2; break; case ISO9660_ELTORITO_BOOT_MEDIA_TYPE_FLOPPY_1_44_MB: cbDefaultSize = 512 * 80 * 18 * 2; break; case ISO9660_ELTORITO_BOOT_MEDIA_TYPE_FLOPPY_2_88_MB: cbDefaultSize = 512 * 80 * 36 * 2; break; case ISO9660_ELTORITO_BOOT_MEDIA_TYPE_NO_EMULATION: case ISO9660_ELTORITO_BOOT_MEDIA_TYPE_HARD_DISK: cbDefaultSize = 0; break; default: return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_INVALID_BOOT_MEDIA_TYPE, "Boot catalog entry #%#x has an invalid boot media type: %#x", bMediaType); } if (iEntry == 1) { if (bMediaType & ISO9660_ELTORITO_BOOT_MEDIA_F_MASK) { rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_DEF_ENTRY_INVALID_FLAGS, "Boot catalog entry #%#x has an invalid boot media type: %#x", bMediaType); bMediaType &= ~ISO9660_ELTORITO_BOOT_MEDIA_F_MASK; } } else { if (bMediaType & ISO9660_ELTORITO_BOOT_MEDIA_F_RESERVED) { rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_ENTRY_RESERVED_FLAG, "Boot catalog entry #%#x has an invalid boot media type: %#x", bMediaType); bMediaType &= ~ISO9660_ELTORITO_BOOT_MEDIA_F_RESERVED; } } /* * Complain if bUnused is used. */ if (pEntry->bUnused != 0) rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_ENTRY_USES_UNUSED_FIELD, "Boot catalog entry #%#x has a non-zero unused field: %#x", pEntry->bUnused); /* * Check out the boot image offset and turn that into an index of a file */ uint32_t offBootImage = RT_LE2H_U32(pEntry->offBootImage); if (offBootImage >= pThis->cBlocksInSrcFile) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_ENTRY_IMAGE_OUT_OF_BOUNDS, "Boot catalog entry #%#x has an out of bound boot image block number: %#RX32, max %#RX32", offBootImage, pThis->cBlocksInPrimaryVolumeSpace); int rc; uint32_t idxImageObj; PRTFSISOMKIMPBLOCK2FILE pBlock2File = (PRTFSISOMKIMPBLOCK2FILE)RTAvlU32Get(&pThis->Block2FileRoot, offBootImage); if (pBlock2File) idxImageObj = pBlock2File->idxObj; else { if (cbDefaultSize == 0) { pBlock2File = (PRTFSISOMKIMPBLOCK2FILE)RTAvlU32GetBestFit(&pThis->Block2FileRoot, offBootImage, true /*fAbove*/); if (pBlock2File) cbDefaultSize = RT_MIN(pBlock2File->Core.Key - offBootImage, UINT32_MAX / ISO9660_SECTOR_SIZE + 1) * ISO9660_SECTOR_SIZE; else if (offBootImage < pThis->cBlocksInSrcFile) cbDefaultSize = RT_MIN(pThis->cBlocksInSrcFile - offBootImage, UINT32_MAX / ISO9660_SECTOR_SIZE + 1) * ISO9660_SECTOR_SIZE; else return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_ENTRY_UNKNOWN_IMAGE_SIZE, "Boot catalog entry #%#x has an invalid boot media type: %#x", bMediaType); } if (pThis->idxSrcFile != UINT32_MAX) { rc = RTFsIsoMakerAddCommonSourceFile(pThis->hIsoMaker, pThis->hSrcFile, &pThis->idxSrcFile); if (RT_FAILURE(rc)) return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerAddCommonSourceFile failed: %Rrc", rc); Assert(pThis->idxSrcFile != UINT32_MAX); } rc = RTFsIsoMakerAddUnnamedFileWithCommonSrc(pThis->hIsoMaker, pThis->idxSrcFile, offBootImage * (uint64_t)ISO9660_SECTOR_SIZE, cbDefaultSize, NULL, &idxImageObj); if (RT_FAILURE(rc)) return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerAddUnnamedFileWithCommonSrc failed on boot entry #%#x: %Rrc", iEntry, rc); } /* * Deal with selection criteria. Use the last sector of abBuf to gather it * into a single data chunk. */ size_t cbSelCrit = 0; uint8_t *pbSelCrit = &pThis->abBuf[sizeof(pThis->abBuf) - ISO9660_SECTOR_SIZE]; if (pEntry->bSelectionCriteriaType != ISO9660_ELTORITO_SEL_CRIT_TYPE_NONE) { memcpy(pbSelCrit, pEntry->abSelectionCriteria, sizeof(pEntry->abSelectionCriteria)); cbSelCrit = sizeof(pEntry->abSelectionCriteria); if ( (bMediaType & ISO9660_ELTORITO_BOOT_MEDIA_F_CONTINUATION) && iEntry + 1 < cMaxEntries) { uint32_t iExtEntry = iEntry + 1; PCISO9660ELTORITOSECTIONENTRYEXT pExtEntry = (PCISO9660ELTORITOSECTIONENTRYEXT)pEntry; for (;;) { pExtEntry++; if (pExtEntry->bExtensionId != ISO9660_ELTORITO_SECTION_ENTRY_EXT_ID) { rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_EXT_ENTRY_INVALID_ID, "Invalid header ID for extension entry #%#x: %#x", iExtEntry, pExtEntry->bExtensionId); break; } *pcSkip += 1; memcpy(&pbSelCrit[cbSelCrit], pExtEntry->abSelectionCriteria, sizeof(pExtEntry->abSelectionCriteria)); cbSelCrit += sizeof(pExtEntry->abSelectionCriteria); if (pExtEntry->fFlags & ISO9660_ELTORITO_SECTION_ENTRY_EXT_F_UNUSED_MASK) rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_EXT_ENTRY_UNDEFINED_FLAGS, "Boot catalog extension entry #%#x uses undefined flags: %#x", iExtEntry, pExtEntry->fFlags); iExtEntry++; if (!(pExtEntry->fFlags & ISO9660_ELTORITO_SECTION_ENTRY_EXT_F_MORE)) break; if (iExtEntry >= cMaxEntries) { rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_EXT_ENTRY_END_OF_SECTOR, "Boot catalog extension entry #%#x sets the MORE flag, but we have reached the end of the boot catalog sector"); break; } } Assert(*pcSkip = iExtEntry - iEntry); } else if (bMediaType & ISO9660_ELTORITO_BOOT_MEDIA_F_CONTINUATION) rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_ENTRY_CONTINUATION_EOS, "Boot catalog extension entry #%#x sets the MORE flag, but we have reached the end of the boot catalog sector"); } else if (bMediaType & ISO9660_ELTORITO_BOOT_MEDIA_F_CONTINUATION) rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_ENTRY_CONTINUATION_WITH_NONE, "Boot catalog entry #%#x uses the continuation flag with selection criteria NONE", iEntry); /* * Add the entry. */ rc = RTFsIsoMakerBootCatSetSectionEntry(pThis->hIsoMaker, iEntry, idxImageObj, bMediaType, pEntry->bSystemType, pEntry->bBootIndicator == ISO9660_ELTORITO_BOOT_INDICATOR_BOOTABLE, pEntry->uLoadSeg, pEntry->cEmulatedSectorsToLoad, pEntry->bSelectionCriteriaType, pbSelCrit, cbSelCrit); if (RT_SUCCESS(rc)) { pThis->pResults->cBootCatEntries += 1 + *pcSkip; rc = rtFsIsoImportProcessElToritoImage(pThis, idxImageObj, offBootImage); } else rtFsIsoImpError(pThis, rc, "RTFsIsoMakerBootCatSetSectionEntry failed for entry #%#x: %Rrc", iEntry, rc); return rc; } /** * Processes a boot catalog section header entry. * * @returns IPRT status code (ignored). * @param pThis The ISO importer instance. * @param iEntry The boot catalog entry number. * @param pEntry The entry to process. */ static int rtFsIsoImportProcessElToritoSectionHeader(PRTFSISOMKIMPORTER pThis, uint32_t iEntry, PCISO9660ELTORITOSECTIONHEADER pEntry, char pszId[32]) { Assert(pEntry->bHeaderId == ISO9660_ELTORITO_HEADER_ID_SECTION_HEADER); /* Deal with the string. ASSUME it doesn't contain zeros in non-terminal positions. */ if (pEntry->achSectionId[0] == '\0') pszId = NULL; else { memcpy(pszId, pEntry->achSectionId, sizeof(pEntry->achSectionId)); pszId[sizeof(pEntry->achSectionId)] = '\0'; } int rc = RTFsIsoMakerBootCatSetSectionHeaderEntry(pThis->hIsoMaker, iEntry, RT_LE2H_U16(pEntry->cEntries), pEntry->bPlatformId, pszId); if (RT_SUCCESS(rc)) pThis->pResults->cBootCatEntries++; else rtFsIsoImpError(pThis, rc, "RTFsIsoMakerBootCatSetSectionHeaderEntry failed for entry #%#x (bPlatformId=%#x cEntries=%#x): %Rrc", iEntry, RT_LE2H_U16(pEntry->cEntries), pEntry->bPlatformId, rc); return rc; } /** * Processes a El Torito volume descriptor. * * @returns IPRT status code (ignorable). * @param pThis The ISO importer instance. * @param pVolDesc The volume descriptor to process. */ static int rtFsIsoImportProcessElToritoDesc(PRTFSISOMKIMPORTER pThis, PISO9660BOOTRECORDELTORITO pVolDesc) { /* * Read the boot catalog into the abBuf. */ uint32_t offBootCatalog = RT_LE2H_U32(pVolDesc->offBootCatalog); if (offBootCatalog >= pThis->cBlocksInPrimaryVolumeSpace) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_BAD_OUT_OF_BOUNDS, "Boot catalog block number is out of bounds: %#RX32, max %#RX32", offBootCatalog, pThis->cBlocksInPrimaryVolumeSpace); int rc = RTVfsFileReadAt(pThis->hSrcFile, offBootCatalog * (uint64_t)ISO9660_SECTOR_SIZE, pThis->abBuf, ISO9660_SECTOR_SIZE, NULL); if (RT_FAILURE(rc)) return rtFsIsoImpError(pThis, rc, "Error reading boot catalog at block #%#RX32: %Rrc", offBootCatalog, rc); /* * Process the 'validation entry'. */ PCISO9660ELTORITOVALIDATIONENTRY pValEntry = (PCISO9660ELTORITOVALIDATIONENTRY)&pThis->abBuf[0]; if (pValEntry->bHeaderId != ISO9660_ELTORITO_HEADER_ID_VALIDATION_ENTRY) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_BAD_VALIDATION_HEADER_ID, "Invalid boot catalog validation entry header ID: %#x, expected %#x", pValEntry->bHeaderId, ISO9660_ELTORITO_HEADER_ID_VALIDATION_ENTRY); if ( pValEntry->bKey1 != ISO9660_ELTORITO_KEY_BYTE_1 || pValEntry->bKey2 != ISO9660_ELTORITO_KEY_BYTE_2) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_BAD_VALIDATION_KEYS, "Invalid boot catalog validation entry keys: %#x %#x, expected %#x %#x", pValEntry->bKey1, pValEntry->bKey2, ISO9660_ELTORITO_KEY_BYTE_1, ISO9660_ELTORITO_KEY_BYTE_2); /* Check the checksum (should sum up to be zero). */ uint16_t uChecksum = 0; uint16_t const *pu16 = (uint16_t const *)pValEntry; size_t cLeft = sizeof(*pValEntry) / sizeof(uint16_t); while (cLeft-- > 0) { uChecksum += RT_LE2H_U16(*pu16); pu16++; } if (uChecksum != 0) return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_BAD_VALIDATION_CHECKSUM, "Invalid boot catalog validation entry checksum: %#x, expected 0", uChecksum); /* The string ID. ASSUME no leading zeros in valid strings. */ const char *pszId = NULL; char szId[32]; if (pValEntry->achId[0] != '\0') { memcpy(szId, pValEntry->achId, sizeof(pValEntry->achId)); szId[sizeof(pValEntry->achId)] = '\0'; pszId = szId; } /* * Before we tell the ISO maker about the validation entry, we need to sort * out the file backing the boot catalog. This isn't fatal if it fails. */ PRTFSISOMKIMPBLOCK2FILE pBlock2File = (PRTFSISOMKIMPBLOCK2FILE)RTAvlU32Get(&pThis->Block2FileRoot, offBootCatalog); if (pBlock2File) { rc = RTFsIsoMakerBootCatSetFile(pThis->hIsoMaker, pBlock2File->idxObj); if (RT_FAILURE(rc)) rtFsIsoImpError(pThis, rc, "RTFsIsoMakerBootCatSetFile failed: %Rrc", rc); } /* * Set the validation entry. */ rc = RTFsIsoMakerBootCatSetValidationEntry(pThis->hIsoMaker, pValEntry->bPlatformId, pszId); if (RT_FAILURE(rc)) return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerBootCatSetValidationEntry(,%#x,%s) failed: %Rrc", pValEntry->bPlatformId, pszId); Assert(pThis->pResults->cBootCatEntries == UINT32_MAX); pThis->pResults->cBootCatEntries = 0; /* * Process the default entry and any subsequent entries. */ bool fSeenFinal = false; uint32_t const cMaxEntries = ISO9660_SECTOR_SIZE / ISO9660_ELTORITO_ENTRY_SIZE; for (uint32_t iEntry = 1; iEntry < cMaxEntries; iEntry++) { uint8_t const *pbEntry = &pThis->abBuf[iEntry * ISO9660_ELTORITO_ENTRY_SIZE]; uint8_t const idHeader = *pbEntry; /* KLUDGE ALERT! Older ISO images, like RHEL5-Server-20070208.0-x86_64-DVD.iso lacks terminator entry. So, quietly stop with an entry that's all zeros. */ if ( idHeader == ISO9660_ELTORITO_BOOT_INDICATOR_NOT_BOOTABLE /* 0x00 */ && iEntry != 1 /* default */ && ASMMemIsZero(pbEntry, ISO9660_ELTORITO_ENTRY_SIZE)) return rc; if ( iEntry == 1 /* default*/ || idHeader == ISO9660_ELTORITO_BOOT_INDICATOR_BOOTABLE || idHeader == ISO9660_ELTORITO_BOOT_INDICATOR_NOT_BOOTABLE) { uint32_t cSkip = 0; rtFsIsoImportProcessElToritoSectionEntry(pThis, iEntry, cMaxEntries, (PCISO9660ELTORITOSECTIONENTRY)pbEntry, &cSkip); iEntry += cSkip; } else if (idHeader == ISO9660_ELTORITO_HEADER_ID_SECTION_HEADER) rtFsIsoImportProcessElToritoSectionHeader(pThis, iEntry, (PCISO9660ELTORITOSECTIONHEADER)pbEntry, szId); else if (idHeader == ISO9660_ELTORITO_HEADER_ID_FINAL_SECTION_HEADER) { fSeenFinal = true; break; } else rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_UNKNOWN_HEADER_ID, "Unknown boot catalog header ID for entry #%#x: %#x", iEntry, idHeader); } if (!fSeenFinal) rc = rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_MISSING_FINAL_OR_TOO_BIG, "Boot catalog is probably larger than a sector, or it's missing the final section header entry"); return rc; } /** * Imports an existing ISO. * * Just like other source files, the existing image must remain present and * unmodified till the ISO maker is done with it. * * @returns IRPT status code. * @param hIsoMaker The ISO maker handle. * @param hIsoFile VFS file handle to the existing image to import / clone. * @param fFlags Reserved for the future, MBZ. * @param poffError Where to return the position in @a pszIso * causing trouble when opening it for reading. * Optional. * @param pErrInfo Where to return additional error information. * Optional. */ RTDECL(int) RTFsIsoMakerImport(RTFSISOMAKER hIsoMaker, RTVFSFILE hIsoFile, uint32_t fFlags, PRTFSISOMAKERIMPORTRESULTS pResults, PRTERRINFO pErrInfo) { /* * Validate input. */ AssertPtrReturn(pResults, VERR_INVALID_POINTER); pResults->cAddedNames = 0; pResults->cAddedDirs = 0; pResults->cbAddedDataBlocks = 0; pResults->cAddedFiles = 0; pResults->cAddedSymlinks = 0; pResults->cBootCatEntries = UINT32_MAX; pResults->cbSysArea = 0; pResults->cErrors = 0; AssertReturn(!(fFlags & ~RTFSISOMK_IMPORT_F_VALID_MASK), VERR_INVALID_FLAGS); /* * Get the file size. */ uint64_t cbSrcFile = 0; int rc = RTVfsFileGetSize(hIsoFile, &cbSrcFile); if (RT_SUCCESS(rc)) { /* * Allocate and init the importer state. */ PRTFSISOMKIMPORTER pThis = (PRTFSISOMKIMPORTER)RTMemAllocZ(sizeof(*pThis)); if (pThis) { pThis->hIsoMaker = hIsoMaker; pThis->fFlags = fFlags; pThis->rc = VINF_SUCCESS; pThis->pErrInfo = pErrInfo; pThis->hSrcFile = hIsoFile; pThis->cbSrcFile = cbSrcFile; pThis->cBlocksInSrcFile = cbSrcFile / ISO9660_SECTOR_SIZE; pThis->idxSrcFile = UINT32_MAX; //pThis->Block2FileRoot = NULL; //pThis->cBlocksInPrimaryVolumeSpace = 0; //pThis->cbPrimaryVolumeSpace = 0 //pThis->cVolumesInSet = 0; //pThis->idPrimaryVol = 0; //pThis->fSeenJoliet = false; pThis->pResults = pResults; //pThis->fSuspSeenSP = false; //pThis->offSuspSkip = 0; pThis->offRockBuf = UINT64_MAX; /* * Check if this looks like a plausible ISO by checking out the first volume descriptor. */ rc = RTVfsFileReadAt(hIsoFile, _32K, &pThis->uSectorBuf.PrimVolDesc, sizeof(pThis->uSectorBuf.PrimVolDesc), NULL); if (RT_SUCCESS(rc)) { if ( pThis->uSectorBuf.VolDescHdr.achStdId[0] == ISO9660VOLDESC_STD_ID_0 && pThis->uSectorBuf.VolDescHdr.achStdId[1] == ISO9660VOLDESC_STD_ID_1 && pThis->uSectorBuf.VolDescHdr.achStdId[2] == ISO9660VOLDESC_STD_ID_2 && pThis->uSectorBuf.VolDescHdr.achStdId[3] == ISO9660VOLDESC_STD_ID_3 && pThis->uSectorBuf.VolDescHdr.achStdId[4] == ISO9660VOLDESC_STD_ID_4 && ( pThis->uSectorBuf.VolDescHdr.bDescType == ISO9660VOLDESC_TYPE_PRIMARY || pThis->uSectorBuf.VolDescHdr.bDescType == ISO9660VOLDESC_TYPE_BOOT_RECORD) ) { /* * Process the volume descriptors using the sector buffer, starting * with the one we've already got sitting there. We postpone processing * the el torito one till after the others, so we can name files and size * referenced in it. */ uint32_t cPrimaryVolDescs = 0; uint32_t iElTorito = UINT32_MAX; uint32_t iVolDesc = 0; for (;;) { switch (pThis->uSectorBuf.VolDescHdr.bDescType) { case ISO9660VOLDESC_TYPE_PRIMARY: cPrimaryVolDescs++; if (cPrimaryVolDescs == 1) { pThis->offPrimaryVolDesc = _32K / ISO9660_SECTOR_SIZE + iVolDesc; rtFsIsoImportProcessPrimaryDesc(pThis, &pThis->uSectorBuf.PrimVolDesc); } else rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MULTIPLE_PRIMARY_VOL_DESCS, "Only a single primary volume descriptor is currently supported"); break; case ISO9660VOLDESC_TYPE_SUPPLEMENTARY: if (cPrimaryVolDescs > 0) rtFsIsoImportProcessSupplementaryDesc(pThis, &pThis->uSectorBuf.SupVolDesc); else rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_SUPPLEMENTARY_BEFORE_PRIMARY, "Primary volume descriptor expected before any supplementary descriptors!"); break; case ISO9660VOLDESC_TYPE_BOOT_RECORD: if (strcmp(pThis->uSectorBuf.ElToritoDesc.achBootSystemId, ISO9660BOOTRECORDELTORITO_BOOT_SYSTEM_ID) == 0) { if (iElTorito == UINT32_MAX) iElTorito = iVolDesc; else rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MULTIPLE_EL_TORITO_DESCS, "Only a single El Torito descriptor exepcted!"); } break; case ISO9660VOLDESC_TYPE_PARTITION: /* ignore for now */ break; case ISO9660VOLDESC_TYPE_TERMINATOR: AssertFailed(); break; } /* * Read the next volume descriptor and check the signature. */ iVolDesc++; if (iVolDesc >= 32) { rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_TOO_MANY_VOL_DESCS, "Parses at most 32 volume descriptors"); break; } rc = RTVfsFileReadAt(hIsoFile, _32K + iVolDesc * ISO9660_SECTOR_SIZE, &pThis->uSectorBuf, sizeof(pThis->uSectorBuf), NULL); if (RT_FAILURE(rc)) { rtFsIsoImpError(pThis, rc, "Error reading the volume descriptor #%u at %#RX32: %Rrc", iVolDesc, _32K + iVolDesc * ISO9660_SECTOR_SIZE, rc); break; } if ( pThis->uSectorBuf.VolDescHdr.achStdId[0] != ISO9660VOLDESC_STD_ID_0 || pThis->uSectorBuf.VolDescHdr.achStdId[1] != ISO9660VOLDESC_STD_ID_1 || pThis->uSectorBuf.VolDescHdr.achStdId[2] != ISO9660VOLDESC_STD_ID_2 || pThis->uSectorBuf.VolDescHdr.achStdId[3] != ISO9660VOLDESC_STD_ID_3 || pThis->uSectorBuf.VolDescHdr.achStdId[4] != ISO9660VOLDESC_STD_ID_4) { rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_INVALID_VOL_DESC_HDR, "Invalid volume descriptor header #%u at %#RX32: %.*Rhxs", iVolDesc, _32K + iVolDesc * ISO9660_SECTOR_SIZE, (int)sizeof(pThis->uSectorBuf.VolDescHdr), &pThis->uSectorBuf.VolDescHdr); break; } /** @todo UDF support. */ if (pThis->uSectorBuf.VolDescHdr.bDescType == ISO9660VOLDESC_TYPE_TERMINATOR) break; } /* * Process the system area. */ if (RT_SUCCESS(pThis->rc) || pThis->idxSrcFile != UINT32_MAX) { rc = RTVfsFileReadAt(hIsoFile, 0, pThis->abBuf, _32K, NULL); if (RT_SUCCESS(rc)) { if (!ASMMemIsAllU8(pThis->abBuf, _32K, 0)) { /* Drop zero sectors from the end. */ uint32_t cbSysArea = _32K; while ( cbSysArea >= ISO9660_SECTOR_SIZE && ASMMemIsAllU8(&pThis->abBuf[cbSysArea - ISO9660_SECTOR_SIZE], ISO9660_SECTOR_SIZE, 0)) cbSysArea -= ISO9660_SECTOR_SIZE; /** @todo HFS */ pThis->pResults->cbSysArea = cbSysArea; rc = RTFsIsoMakerSetSysAreaContent(hIsoMaker, pThis->abBuf, cbSysArea, 0); if (RT_FAILURE(rc)) rtFsIsoImpError(pThis, rc, "RTFsIsoMakerSetSysAreaContent failed: %Rrc", rc); } } else rtFsIsoImpError(pThis, rc, "Error reading the system area (0..32KB): %Rrc", rc); } /* * Do the El Torito descriptor. */ if ( iElTorito != UINT32_MAX && !(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_BOOT) && (RT_SUCCESS(pThis->rc) || pThis->idxSrcFile != UINT32_MAX)) { rc = RTVfsFileReadAt(hIsoFile, _32K + iElTorito * ISO9660_SECTOR_SIZE, &pThis->uSectorBuf, sizeof(pThis->uSectorBuf), NULL); if (RT_SUCCESS(rc)) rtFsIsoImportProcessElToritoDesc(pThis, &pThis->uSectorBuf.ElToritoDesc); else rtFsIsoImpError(pThis, rc, "Error reading the El Torito volume descriptor at %#RX32: %Rrc", _32K + iElTorito * ISO9660_SECTOR_SIZE, rc); } /* * Return the first error status. */ rc = pThis->rc; } else rc = RTErrInfoSetF(pErrInfo, VERR_ISOMK_IMPORT_UNKNOWN_FORMAT, "Invalid volume descriptor header: %.*Rhxs", (int)sizeof(pThis->uSectorBuf.VolDescHdr), &pThis->uSectorBuf.VolDescHdr); } /* * Destroy the state. */ RTAvlU32Destroy(&pThis->Block2FileRoot, rtFsIsoMakerImportDestroyData2File, NULL); RTMemFree(pThis); } else rc = VERR_NO_MEMORY; } return rc; }