summaryrefslogtreecommitdiffstats
path: root/src/VBox/Runtime/common/fs/isovfs.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Runtime/common/fs/isovfs.cpp')
-rw-r--r--src/VBox/Runtime/common/fs/isovfs.cpp7209
1 files changed, 7209 insertions, 0 deletions
diff --git a/src/VBox/Runtime/common/fs/isovfs.cpp b/src/VBox/Runtime/common/fs/isovfs.cpp
new file mode 100644
index 00000000..81e17d03
--- /dev/null
+++ b/src/VBox/Runtime/common/fs/isovfs.cpp
@@ -0,0 +1,7209 @@
+/* $Id: isovfs.cpp $ */
+/** @file
+ * IPRT - ISO 9660 and UDF Virtual Filesystem (read only).
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_FS
+#include "internal/iprt.h"
+#include <iprt/fsvfs.h>
+
+#include <iprt/alloca.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/crc.h>
+#include <iprt/critsect.h>
+#include <iprt/ctype.h>
+#include <iprt/file.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/poll.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+#include <iprt/uni.h>
+#include <iprt/utf16.h>
+#include <iprt/formats/iso9660.h>
+#include <iprt/formats/udf.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The maximum logical block size. */
+#define RTFSISO_MAX_LOGICAL_BLOCK_SIZE _16K
+/** Max directory size. */
+#if ARCH_BITS == 32
+# define RTFSISO_MAX_DIR_SIZE _32M
+#else
+# define RTFSISO_MAX_DIR_SIZE _64M
+#endif
+
+/** Check if an entity ID field equals the given ID string. */
+#define UDF_ENTITY_ID_EQUALS(a_pEntityId, a_szId) \
+ ( memcmp(&(a_pEntityId)->achIdentifier[0], a_szId, RT_MIN(sizeof(a_szId), sizeof(a_pEntityId)->achIdentifier)) == 0 )
+/** Checks if a character set indicator indicates OSTA compressed unicode. */
+#define UDF_IS_CHAR_SET_OSTA(a_pCharSet) \
+ ( (a_pCharSet)->uType == UDF_CHAR_SET_OSTA_COMPRESSED_UNICODE \
+ && memcmp((a_pCharSet)->abInfo, UDF_CHAR_SET_OSTA_COMPRESSED_UNICODE_INFO, \
+ sizeof(UDF_CHAR_SET_OSTA_COMPRESSED_UNICODE_INFO)) == 0 )
+
+
+/** @name UDF structure logging macros
+ * @{ */
+#define UDF_LOG2_MEMBER(a_pStruct, a_szFmt, a_Member) \
+ Log2(("ISO/UDF: %-32s %" a_szFmt "\n", #a_Member ":", (a_pStruct)->a_Member))
+#define UDF_LOG2_MEMBER_EX(a_pStruct, a_szFmt, a_Member, a_cchIndent) \
+ Log2(("ISO/UDF: %*s%-32s %" a_szFmt "\n", a_cchIndent, "", #a_Member ":", (a_pStruct)->a_Member))
+#define UDF_LOG2_MEMBER_ENTITY_ID_EX(a_pStruct, a_Member, a_cchIndent) \
+ Log2(("ISO/UDF: %*s%-32s '%.23s' fFlags=%#06x Suffix=%.8Rhxs\n", a_cchIndent, "", #a_Member ":", \
+ (a_pStruct)->a_Member.achIdentifier, (a_pStruct)->a_Member.fFlags, &(a_pStruct)->a_Member.Suffix))
+#define UDF_LOG2_MEMBER_ENTITY_ID(a_pStruct, a_Member) UDF_LOG2_MEMBER_ENTITY_ID_EX(a_pStruct, a_Member, 0)
+#define UDF_LOG2_MEMBER_EXTENTAD(a_pStruct, a_Member) \
+ Log2(("ISO/UDF: %-32s sector %#010RX32 LB %#010RX32\n", #a_Member ":", (a_pStruct)->a_Member.off, (a_pStruct)->a_Member.cb))
+#define UDF_LOG2_MEMBER_SHORTAD(a_pStruct, a_Member) \
+ Log2(("ISO/UDF: %-32s sector %#010RX32 LB %#010RX32 %s\n", #a_Member ":", (a_pStruct)->a_Member.off, (a_pStruct)->a_Member.cb, \
+ (a_pStruct)->a_Member.uType == UDF_AD_TYPE_RECORDED_AND_ALLOCATED ? "alloced+recorded" \
+ : (a_pStruct)->a_Member.uType == UDF_AD_TYPE_ONLY_ALLOCATED ? "alloced" \
+ : (a_pStruct)->a_Member.uType == UDF_AD_TYPE_FREE ? "free" : "next" ))
+#define UDF_LOG2_MEMBER_LONGAD(a_pStruct, a_Member) \
+ Log2(("ISO/UDF: %-32s partition %#RX16, block %#010RX32 LB %#010RX32 %s idUnique=%#010RX32 fFlags=%#RX16\n", #a_Member ":", \
+ (a_pStruct)->a_Member.Location.uPartitionNo, (a_pStruct)->a_Member.Location.off, (a_pStruct)->a_Member.cb, \
+ (a_pStruct)->a_Member.uType == UDF_AD_TYPE_RECORDED_AND_ALLOCATED ? "alloced+recorded" \
+ : (a_pStruct)->a_Member.uType == UDF_AD_TYPE_ONLY_ALLOCATED ? "alloced" \
+ : (a_pStruct)->a_Member.uType == UDF_AD_TYPE_FREE ? "free" : "next", \
+ (a_pStruct)->a_Member.ImplementationUse.Fid.idUnique, (a_pStruct)->a_Member.ImplementationUse.Fid.fFlags ))
+#define UDF_LOG2_MEMBER_LBADDR(a_pStruct, a_Member) \
+ Log2(("ISO/UDF: %-32s block %#010RX32 in partition %#06RX16\n", #a_Member ":", \
+ (a_pStruct)->a_Member.off, (a_pStruct)->a_Member.uPartitionNo))
+
+#define UDF_LOG2_MEMBER_TIMESTAMP(a_pStruct, a_Member) \
+ Log2(("ISO/UDF: %-32s %04d-%02u-%02u %02u:%02u:%02u.%02u%02u%02u offUtc=%d type=%#x\n", #a_Member ":", \
+ (a_pStruct)->a_Member.iYear, (a_pStruct)->a_Member.uMonth, (a_pStruct)->a_Member.uDay, \
+ (a_pStruct)->a_Member.uHour, (a_pStruct)->a_Member.uMinute, (a_pStruct)->a_Member.uSecond, \
+ (a_pStruct)->a_Member.cCentiseconds, (a_pStruct)->a_Member.cHundredsOfMicroseconds, \
+ (a_pStruct)->a_Member.cMicroseconds, (a_pStruct)->a_Member.offUtcInMin, (a_pStruct)->a_Member.fType ))
+#define UDF_LOG2_MEMBER_CHARSPEC(a_pStruct, a_Member) \
+ do { \
+ if ( (a_pStruct)->a_Member.uType == UDF_CHAR_SET_OSTA_COMPRESSED_UNICODE \
+ && memcmp(&(a_pStruct)->a_Member.abInfo[0], UDF_CHAR_SET_OSTA_COMPRESSED_UNICODE_INFO, \
+ sizeof(UDF_CHAR_SET_OSTA_COMPRESSED_UNICODE_INFO)) == 0) \
+ Log2(("ISO/UDF: %-32s OSTA COMPRESSED UNICODE INFO\n", #a_Member ":")); \
+ else if (ASMMemIsZero(&(a_pStruct)->a_Member, sizeof((a_pStruct)->a_Member))) \
+ Log2(("ISO/UDF: %-32s all zeros\n", #a_Member ":")); \
+ else \
+ Log2(("ISO/UDF: %-32s %#x info: %.63Rhxs\n", #a_Member ":", \
+ (a_pStruct)->a_Member.uType, (a_pStruct)->a_Member.abInfo)); \
+ } while (0)
+#define UDF_LOG2_MEMBER_DSTRING(a_pStruct, a_Member) \
+ do { \
+ if ((a_pStruct)->a_Member[0] == 8) \
+ Log2(("ISO/UDF: %-32s 8: '%s' len=%u (actual=%u)\n", #a_Member ":", &(a_pStruct)->a_Member[1], \
+ (a_pStruct)->a_Member[sizeof((a_pStruct)->a_Member) - 1], \
+ RTStrNLen(&(a_pStruct)->a_Member[1], sizeof((a_pStruct)->a_Member) - 2) + 1 )); \
+ else if ((a_pStruct)->a_Member[0] == 16) \
+ { \
+ PCRTUTF16 pwszTmp = (PCRTUTF16)&(a_pStruct)->a_Member[1]; \
+ char *pszTmp = NULL; \
+ RTUtf16BigToUtf8Ex(pwszTmp, (sizeof((a_pStruct)->a_Member) - 2) / sizeof(RTUTF16), &pszTmp, 0, NULL); \
+ Log2(("ISO/UDF: %-32s 16: '%s' len=%u (actual=%u)\n", #a_Member ":", pszTmp, \
+ (a_pStruct)->a_Member[sizeof((a_pStruct)->a_Member) - 1], \
+ RTUtf16NLen(pwszTmp, (sizeof((a_pStruct)->a_Member) - 2) / sizeof(RTUTF16)) * sizeof(RTUTF16) + 1 /*??*/ )); \
+ RTStrFree(pszTmp); \
+ } \
+ else if (ASMMemIsZero(&(a_pStruct)->a_Member[0], sizeof((a_pStruct)->a_Member))) \
+ Log2(("ISO/UDF: %-32s empty\n", #a_Member ":")); \
+ else \
+ Log2(("ISO/UDF: %-32s bad: %.*Rhxs\n", #a_Member ":", sizeof((a_pStruct)->a_Member), &(a_pStruct)->a_Member[0] )); \
+ } while (0)
+/** @} */
+
+/** Compresses SUSP and rock ridge extension signatures in the hope of
+ * reducing switch table size. */
+#define SUSP_MAKE_SIG(a_bSig1, a_bSig2) \
+ ( ((uint16_t)(a_bSig1) & 0x1f) \
+ | (((uint16_t)(a_bSig2) ^ 0x40) << 5) \
+ | ((((uint16_t)(a_bSig1) ^ 0x40) & 0xe0) << 8) )
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to an ISO volume (VFS instance data). */
+typedef struct RTFSISOVOL *PRTFSISOVOL;
+/** Pointer to a const ISO volume (VFS instance data). */
+typedef struct RTFSISOVOL const *PCRTFSISOVOL;
+
+/** Pointer to a ISO directory instance. */
+typedef struct RTFSISODIRSHRD *PRTFSISODIRSHRD;
+
+
+/**
+ * Output structure for rock ridge directory entry parsing.
+ */
+typedef struct RTFSISOROCKINFO
+{
+ /** Set if the parse info is valid. */
+ bool fValid;
+ /** Set if we've see the SP entry. */
+ bool fSuspSeenSP : 1;
+ /** Set if we've seen the last 'NM' entry. */
+ bool fSeenLastNM : 1;
+ /** Set if we've seen the last 'SL' entry. */
+ bool fSeenLastSL : 1;
+ /** Symbolic link target overflowed. */
+ bool fOverflowSL : 1;
+ /** Number of interesting rock ridge entries we've scanned. */
+ uint16_t cRockEntries;
+ /** The name length. */
+ uint16_t cchName;
+ /** The Symbolic link target name length. */
+ uint16_t cchLinkTarget;
+ /** Object info. */
+ RTFSOBJINFO Info;
+ /** The rock ridge name. */
+ char szName[2048];
+ /** Symbolic link target name. */
+ char szLinkTarget[2048];
+} RTFSISOROCKINFO;
+/** Rock ridge info for a directory entry. */
+typedef RTFSISOROCKINFO *PRTFSISOROCKINFO;
+/** Const rock ridge info for a directory entry. */
+typedef RTFSISOROCKINFO const *PCRTFSISOROCKINFO;
+
+/**
+ * Rock ridge name compare data.
+ */
+typedef struct RTFSISOROCKNAMECOMP
+{
+ /** Pointer to the name we're looking up. */
+ const char *pszEntry;
+ /** The length of the name. */
+ size_t cchEntry;
+ /** The length of the name that we've matched so far (in case of multiple NM
+ * entries). */
+ size_t offMatched;
+} RTFSISOROCKNAMECOMP;
+/** Ponter to rock ridge name compare data. */
+typedef RTFSISOROCKNAMECOMP *PRTFSISOROCKNAMECOMP;
+
+
+/**
+ * ISO extent (internal to the VFS not a disk structure).
+ */
+typedef struct RTFSISOEXTENT
+{
+ /** The disk or partition byte offset.
+ * This is set to UINT64_MAX for parts of sparse files that aren't recorded.*/
+ uint64_t off;
+ /** The size of the extent in bytes. */
+ uint64_t cbExtent;
+ /** UDF virtual partition number, UINT32_MAX for ISO 9660. */
+ uint32_t idxPart;
+ /** Reserved. */
+ uint32_t uReserved;
+} RTFSISOEXTENT;
+/** Pointer to an ISO 9660 extent. */
+typedef RTFSISOEXTENT *PRTFSISOEXTENT;
+/** Pointer to a const ISO 9660 extent. */
+typedef RTFSISOEXTENT const *PCRTFSISOEXTENT;
+
+
+/**
+ * ISO file system object, shared part.
+ */
+typedef struct RTFSISOCORE
+{
+ /** The parent directory keeps a list of open objects (RTFSISOCORE). */
+ RTLISTNODE Entry;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** The parent directory (not released till all children are close). */
+ PRTFSISODIRSHRD pParentDir;
+ /** The byte offset of the first directory record.
+ * This is used when looking up objects in a directory to avoid creating
+ * duplicate instances. */
+ uint64_t offDirRec;
+ /** Attributes. */
+ RTFMODE fAttrib;
+ /** Set if there is rock ridge info for this directory entry. */
+ bool fHaveRockInfo;
+ /** The object size. */
+ uint64_t cbObject;
+ /** The access time. */
+ RTTIMESPEC AccessTime;
+ /** The modificaton time. */
+ RTTIMESPEC ModificationTime;
+ /** The change time. */
+ RTTIMESPEC ChangeTime;
+ /** The birth time. */
+ RTTIMESPEC BirthTime;
+ /** The i-node ID. */
+ RTINODE idINode;
+ /** Pointer to the volume. */
+ PRTFSISOVOL pVol;
+ /** The version number. */
+ uint32_t uVersion;
+ /** Number of extents. */
+ uint32_t cExtents;
+ /** The first extent. */
+ RTFSISOEXTENT FirstExtent;
+ /** Array of additional extents. */
+ PRTFSISOEXTENT paExtents;
+} RTFSISOCORE;
+typedef RTFSISOCORE *PRTFSISOCORE;
+
+/**
+ * ISO file, shared data.
+ */
+typedef struct RTFSISOFILESHRD
+{
+ /** Core ISO9660 object info. */
+ RTFSISOCORE Core;
+} RTFSISOFILESHRD;
+/** Pointer to a ISO 9660 file object. */
+typedef RTFSISOFILESHRD *PRTFSISOFILESHRD;
+
+
+/**
+ * ISO directory, shared data.
+ *
+ * We will always read in the whole directory just to keep things really simple.
+ */
+typedef struct RTFSISODIRSHRD
+{
+ /** Core ISO 9660 object info. */
+ RTFSISOCORE Core;
+ /** Open child objects (RTFSISOCORE). */
+ RTLISTNODE OpenChildren;
+
+ /** Pointer to the directory content. */
+ uint8_t *pbDir;
+ /** The size of the directory content (duplicate of Core.cbObject). */
+ uint32_t cbDir;
+} RTFSISODIRSHRD;
+/** Pointer to a ISO directory instance. */
+typedef RTFSISODIRSHRD *PRTFSISODIRSHRD;
+
+
+/**
+ * Private data for a VFS file object.
+ */
+typedef struct RTFSISOFILEOBJ
+{
+ /** Pointer to the shared data. */
+ PRTFSISOFILESHRD pShared;
+ /** The current file offset. */
+ uint64_t offFile;
+} RTFSISOFILEOBJ;
+typedef RTFSISOFILEOBJ *PRTFSISOFILEOBJ;
+
+/**
+ * Private data for a VFS directory object.
+ */
+typedef struct RTFSISODIROBJ
+{
+ /** Pointer to the shared data. */
+ PRTFSISODIRSHRD pShared;
+ /** The current directory offset. */
+ uint32_t offDir;
+} RTFSISODIROBJ;
+typedef RTFSISODIROBJ *PRTFSISODIROBJ;
+
+/** Pointer to info about a UDF volume. */
+typedef struct RTFSISOUDFVOLINFO *PRTFSISOUDFVOLINFO;
+
+
+/** @name RTFSISO_UDF_PMAP_T_XXX
+ * @{ */
+#define RTFSISO_UDF_PMAP_T_PLAIN 1
+#define RTFSISO_UDF_PMAP_T_VPM_15 2
+#define RTFSISO_UDF_PMAP_T_VPM_20 3
+#define RTFSISO_UDF_PMAP_T_SPM 4
+#define RTFSISO_UDF_PMAP_T_MPM 5
+/** @} */
+
+/**
+ * Information about a logical UDF partition.
+ *
+ * This combins information from the partition descriptor, the UDFPARTMAPTYPE1
+ * and the UDFPARTMAPTYPE2 structure.
+ */
+typedef struct RTFSISOVOLUDFPMAP
+{
+ /** Partition starting location as a byte offset. */
+ uint64_t offByteLocation;
+ /** Partition starting location (logical sector number). */
+ uint32_t offLocation;
+ /** Number of sectors. */
+ uint32_t cSectors;
+
+ /** Partition descriptor index (for processing). */
+ uint16_t idxPartDesc;
+ /** Offset info the map table. */
+ uint16_t offMapTable;
+ /** Partition number (not index). */
+ uint16_t uPartitionNo;
+ /** Partition number (not index). */
+ uint16_t uVolumeSeqNo;
+
+ /** The access type (UDF_PART_ACCESS_TYPE_XXX). */
+ uint32_t uAccessType;
+ /** Partition flags (UDF_PARTITION_FLAGS_XXX). */
+ uint16_t fFlags;
+ /** RTFSISO_UDF_PMAP_T_XXX. */
+ uint8_t bType;
+ /** Set if Hdr is valid. */
+ bool fHaveHdr;
+ /** Copy of UDFPARTITIONDESC::ContentsUse::Hdr. */
+ UDFPARTITIONHDRDESC Hdr;
+
+} RTFSISOVOLUDFPMAP;
+typedef RTFSISOVOLUDFPMAP *PRTFSISOVOLUDFPMAP;
+
+/**
+ * Information about a UDF volume (/ volume set).
+ *
+ * This combines information from the primary and logical descriptors.
+ *
+ * @note There is only one volume per volume set in the current UDF
+ * implementation. So, this can be considered a volume and a volume set.
+ */
+typedef struct RTFSISOUDFVOLINFO
+{
+ /** The extent containing the file set descriptor. */
+ UDFLONGAD FileSetDescriptor;
+
+ /** The root directory location (from the file set descriptor). */
+ UDFLONGAD RootDirIcb;
+ /** Location of the system stream directory associated with the file set. */
+ UDFLONGAD SystemStreamDirIcb;
+
+ /** The logical block size on this volume. */
+ uint32_t cbBlock;
+ /** The log2 of cbBlock. */
+ uint32_t cShiftBlock;
+ /** Flags (UDF_PVD_FLAGS_XXX). */
+ uint16_t fFlags;
+
+ /** Number of partitions mapp in this volume. */
+ uint16_t cPartitions;
+ /** Partitions in this volume. */
+ PRTFSISOVOLUDFPMAP paPartitions;
+
+ /** The volume ID string. */
+ UDFDSTRING achLogicalVolumeID[128];
+} RTFSISOUDFVOLINFO;
+
+
+/**
+ * Indicates which of the possible content types we're accessing.
+ */
+typedef enum RTFSISOVOLTYPE
+{
+ /** Invalid zero value. */
+ RTFSISOVOLTYPE_INVALID = 0,
+ /** Accessing the primary ISO-9660 volume. */
+ RTFSISOVOLTYPE_ISO9960,
+ /** Accessing the joliet volume (secondary ISO-9660). */
+ RTFSISOVOLTYPE_JOLIET,
+ /** Accessing the UDF volume. */
+ RTFSISOVOLTYPE_UDF
+} RTFSISOVOLTYPE;
+
+/**
+ * A ISO volume.
+ */
+typedef struct RTFSISOVOL
+{
+ /** Handle to itself. */
+ RTVFS hVfsSelf;
+ /** The file, partition, or whatever backing the ISO 9660 volume. */
+ RTVFSFILE hVfsBacking;
+ /** The size of the backing thingy. */
+ uint64_t cbBacking;
+ /** The size of the backing thingy in sectors (cbSector). */
+ uint64_t cBackingSectors;
+ /** Flags. */
+ uint32_t fFlags;
+ /** The sector size (in bytes). */
+ uint32_t cbSector;
+ /** What we're accessing. */
+ RTFSISOVOLTYPE enmType;
+
+ /** @name ISO 9660 specific data
+ * @{ */
+ /** The size of a logical block in bytes. */
+ uint32_t cbBlock;
+ /** 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;
+ /** The offset of the primary volume descriptor. */
+ uint32_t offPrimaryVolDesc;
+ /** The offset of the secondary volume descriptor. */
+ uint32_t offSecondaryVolDesc;
+ /** Set if using UTF16-2 (joliet). */
+ bool fIsUtf16;
+ /** @} */
+
+ /** UDF specific data. */
+ struct
+ {
+ /** Volume information. */
+ RTFSISOUDFVOLINFO VolInfo;
+ /** The UDF level. */
+ uint8_t uLevel;
+ } Udf;
+
+ /** The root directory shared data. */
+ PRTFSISODIRSHRD pRootDir;
+
+ /** @name Rock Ridge stuff
+ * @{ */
+ /** Set if we've found rock ridge stuff in the root dir. */
+ bool fHaveRock;
+ /** The SUSP skip into system area offset. */
+ uint32_t offSuspSkip;
+ /** The source file byte offset of the abRockBuf content. */
+ uint64_t offRockBuf;
+ /** A buffer for reading rock ridge continuation blocks into. */
+ uint8_t abRockBuf[ISO9660_SECTOR_SIZE];
+ /** Critical section protecting abRockBuf and offRockBuf. */
+ RTCRITSECT RockBufLock;
+ /** @} */
+} RTFSISOVOL;
+
+
+/**
+ * Info gathered from a VDS sequence.
+ */
+typedef struct RTFSISOVDSINFO
+{
+ /** Number of entries in apPrimaryVols. */
+ uint32_t cPrimaryVols;
+ /** Number of entries in apLogicalVols. */
+ uint32_t cLogicalVols;
+ /** Number of entries in apPartitions. */
+ uint32_t cPartitions;
+ /** Pointer to primary volume descriptors (native endian). */
+ PUDFPRIMARYVOLUMEDESC apPrimaryVols[8];
+ /** Pointer to logical volume descriptors (native endian). */
+ PUDFLOGICALVOLUMEDESC apLogicalVols[8];
+ /** Pointer to partition descriptors (native endian). */
+ PUDFPARTITIONDESC apPartitions[16];
+
+ /** Created after scanning the sequence (here for cleanup purposes). */
+ PRTFSISOVOLUDFPMAP paPartMaps;
+} RTFSISOVDSINFO;
+/** Pointer to VDS sequence info. */
+typedef RTFSISOVDSINFO *PRTFSISOVDSINFO;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void rtFsIsoDirShrd_AddOpenChild(PRTFSISODIRSHRD pDir, PRTFSISOCORE pChild);
+static void rtFsIsoDirShrd_RemoveOpenChild(PRTFSISODIRSHRD pDir, PRTFSISOCORE pChild);
+static int rtFsIsoDir_NewWithShared(PRTFSISOVOL pThis, PRTFSISODIRSHRD pShared, PRTVFSDIR phVfsDir);
+static int rtFsIsoDir_New9660(PRTFSISOVOL pThis, PRTFSISODIRSHRD pParentDir, PCISO9660DIRREC pDirRec,
+ uint32_t cDirRecs, uint64_t offDirRec, PCRTFSISOROCKINFO pRockInfo, PRTVFSDIR phVfsDir);
+static int rtFsIsoDir_NewUdf(PRTFSISOVOL pThis, PRTFSISODIRSHRD pParentDir, PCUDFFILEIDDESC pFid, PRTVFSDIR phVfsDir);
+static PRTFSISOCORE rtFsIsoDir_LookupShared(PRTFSISODIRSHRD pThis, uint64_t offDirRec);
+
+static int rtFsIsoVolValidateUdfDescCrc(PCUDFTAG pTag, size_t cbDesc, PRTERRINFO pErrInfo);
+static int rtFsIsoVolValidateUdfDescTag(PCUDFTAG pTag, uint16_t idTag, uint32_t offTag, PRTERRINFO pErrInfo);
+static int rtFsIsoVolValidateUdfDescTagAndCrc(PCUDFTAG pTag, size_t cbDesc, uint16_t idTag, uint32_t offTag, PRTERRINFO pErrInfo);
+
+
+/**
+ * UDF virtual partition read function.
+ *
+ * This deals with all the fun related to block mapping and such.
+ *
+ * @returns VBox status code.
+ * @param pThis The instance.
+ * @param idxPart The virtual partition number.
+ * @param idxBlock The block number.
+ * @param offByteAddend The byte offset relative to the block.
+ * @param pvBuf The output buffer.
+ * @param cbToRead The number of bytes to read.
+ */
+static int rtFsIsoVolUdfVpRead(PRTFSISOVOL pThis, uint32_t idxPart, uint32_t idxBlock, uint64_t offByteAddend,
+ void *pvBuf, size_t cbToRead)
+{
+ uint64_t const offByte = ((uint64_t)idxBlock << pThis->Udf.VolInfo.cShiftBlock) + offByteAddend;
+
+ int rc;
+ if (idxPart < pThis->Udf.VolInfo.cPartitions)
+ {
+ PRTFSISOVOLUDFPMAP pPart = &pThis->Udf.VolInfo.paPartitions[idxPart];
+ switch (pPart->bType)
+ {
+ case RTFSISO_UDF_PMAP_T_PLAIN:
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offByte + pPart->offByteLocation, pvBuf, cbToRead, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ Log3(("ISO/UDF: Read %#x bytes at %#RX64 (%#x:%#RX64)\n",
+ cbToRead, offByte + pPart->offByteLocation, idxPart, offByte));
+ return VINF_SUCCESS;
+ }
+ Log(("ISO/UDF: Error reading %#x bytes at %#RX64 (%#x:%#RX64): %Rrc\n",
+ cbToRead, offByte + pPart->offByteLocation, idxPart, offByte, rc));
+ break;
+
+ default:
+ AssertFailed();
+ rc = VERR_ISOFS_IPE_1;
+ break;
+ }
+ }
+ else
+ {
+ Log(("ISO/UDF: Invalid partition index %#x (offset %#RX64), max partitions %#x\n",
+ idxPart, offByte, pThis->Udf.VolInfo.cPartitions));
+ rc = VERR_ISOFS_INVALID_PARTITION_INDEX;
+ }
+ return rc;
+}
+
+
+/**
+ * Returns the length of the version suffix in the given name.
+ *
+ * @returns Number of UTF16-BE chars in the version suffix.
+ * @param pawcName The name to examine.
+ * @param cwcName The length of the name.
+ * @param puValue Where to return the value.
+ */
+static size_t rtFsIso9660GetVersionLengthUtf16Big(PCRTUTF16 pawcName, size_t cwcName, uint32_t *puValue)
+{
+ *puValue = 0;
+
+ /* -1: */
+ if (cwcName <= 2)
+ return 0;
+ RTUTF16 wc1 = RT_BE2H_U16(pawcName[cwcName - 1]);
+ if (!RT_C_IS_DIGIT(wc1))
+ return 0;
+ Assert(wc1 < 0x3a); /* ASSUMES the RT_C_IS_DIGIT macro works just fine on wide chars too. */
+
+ /* -2: */
+ RTUTF16 wc2 = RT_BE2H_U16(pawcName[cwcName - 2]);
+ if (wc2 == ';')
+ {
+ *puValue = wc1 - '0';
+ return 2;
+ }
+ if (!RT_C_IS_DIGIT(wc2) || cwcName <= 3)
+ return 0;
+
+ /* -3: */
+ RTUTF16 wc3 = RT_BE2H_U16(pawcName[cwcName - 3]);
+ if (wc3 == ';')
+ {
+ *puValue = (wc1 - '0')
+ + (wc2 - '0') * 10;
+ return 3;
+ }
+ if (!RT_C_IS_DIGIT(wc3) || cwcName <= 4)
+ return 0;
+
+ /* -4: */
+ RTUTF16 wc4 = RT_BE2H_U16(pawcName[cwcName - 4]);
+ if (wc4 == ';')
+ {
+ *puValue = (wc1 - '0')
+ + (wc2 - '0') * 10
+ + (wc3 - '0') * 100;
+ return 4;
+ }
+ if (!RT_C_IS_DIGIT(wc4) || cwcName <= 5)
+ return 0;
+
+ /* -5: */
+ RTUTF16 wc5 = RT_BE2H_U16(pawcName[cwcName - 5]);
+ if (wc5 == ';')
+ {
+ *puValue = (wc1 - '0')
+ + (wc2 - '0') * 10
+ + (wc3 - '0') * 100
+ + (wc4 - '0') * 1000;
+ return 5;
+ }
+ if (!RT_C_IS_DIGIT(wc5) || cwcName <= 6)
+ return 0;
+
+ /* -6: */
+ RTUTF16 wc6 = RT_BE2H_U16(pawcName[cwcName - 6]);
+ if (wc6 == ';')
+ {
+ *puValue = (wc1 - '0')
+ + (wc2 - '0') * 10
+ + (wc3 - '0') * 100
+ + (wc4 - '0') * 1000
+ + (wc5 - '0') * 10000;
+ return 6;
+ }
+ return 0;
+}
+
+
+/**
+ * Returns the length of the version suffix in the given name.
+ *
+ * @returns Number of chars in the version suffix.
+ * @param pachName The name to examine.
+ * @param cchName The length of the name.
+ * @param puValue Where to return the value.
+ */
+static size_t rtFsIso9660GetVersionLengthAscii(const char *pachName, size_t cchName, uint32_t *puValue)
+{
+ *puValue = 0;
+
+ /* -1: */
+ if (cchName <= 2)
+ return 0;
+ char ch1 = pachName[cchName - 1];
+ if (!RT_C_IS_DIGIT(ch1))
+ return 0;
+
+ /* -2: */
+ char ch2 = pachName[cchName - 2];
+ if (ch2 == ';')
+ {
+ *puValue = ch1 - '0';
+ return 2;
+ }
+ if (!RT_C_IS_DIGIT(ch2) || cchName <= 3)
+ return 0;
+
+ /* -3: */
+ char ch3 = pachName[cchName - 3];
+ if (ch3 == ';')
+ {
+ *puValue = (ch1 - '0')
+ + (ch2 - '0') * 10;
+ return 3;
+ }
+ if (!RT_C_IS_DIGIT(ch3) || cchName <= 4)
+ return 0;
+
+ /* -4: */
+ char ch4 = pachName[cchName - 4];
+ if (ch4 == ';')
+ {
+ *puValue = (ch1 - '0')
+ + (ch2 - '0') * 10
+ + (ch3 - '0') * 100;
+ return 4;
+ }
+ if (!RT_C_IS_DIGIT(ch4) || cchName <= 5)
+ return 0;
+
+ /* -5: */
+ char ch5 = pachName[cchName - 5];
+ if (ch5 == ';')
+ {
+ *puValue = (ch1 - '0')
+ + (ch2 - '0') * 10
+ + (ch3 - '0') * 100
+ + (ch4 - '0') * 1000;
+ return 5;
+ }
+ if (!RT_C_IS_DIGIT(ch5) || cchName <= 6)
+ return 0;
+
+ /* -6: */
+ if (pachName[cchName - 6] == ';')
+ {
+ *puValue = (ch1 - '0')
+ + (ch2 - '0') * 10
+ + (ch3 - '0') * 100
+ + (ch4 - '0') * 1000
+ + (ch5 - '0') * 10000;
+ return 6;
+ }
+ return 0;
+}
+
+
+/**
+ * Converts an 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 rtFsIso9660DateTime2TimeSpec(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 rtFsIso9660DateTime2TimeSpecIfValid(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;
+}
+
+
+/**
+ * Converts an UDF timestamp into an IPRT timesspec.
+ *
+ * @param pTimeSpec Where to return the IRPT time.
+ * @param pUdf The UDF timestamp.
+ */
+static void rtFsIsoUdfTimestamp2TimeSpec(PRTTIMESPEC pTimeSpec, PCUDFTIMESTAMP pUdf)
+{
+ /* Check the year range before we try convert anything as it's quite possible
+ that this is zero. */
+ if ( pUdf->iYear > 1678
+ && pUdf->iYear < 2262)
+ {
+ RTTIME Time;
+ Time.fFlags = RTTIME_FLAGS_TYPE_UTC;
+ Time.offUTC = 0;
+ Time.i32Year = pUdf->iYear;
+ Time.u8Month = RT_MIN(RT_MAX(pUdf->uMonth, 1), 12);
+ Time.u8MonthDay = RT_MIN(RT_MAX(pUdf->uDay, 1), 31);
+ Time.u8WeekDay = UINT8_MAX;
+ Time.u16YearDay = 0;
+ Time.u8Hour = RT_MIN(pUdf->uHour, 23);
+ Time.u8Minute = RT_MIN(pUdf->uMinute, 59);
+ Time.u8Second = RT_MIN(pUdf->uSecond, 59);
+ Time.u32Nanosecond = pUdf->cCentiseconds * UINT32_C(10000000)
+ + pUdf->cHundredsOfMicroseconds * UINT32_C(100000)
+ + pUdf->cMicroseconds * UINT32_C(1000);
+ RTTimeImplode(pTimeSpec, RTTimeNormalize(&Time));
+
+ /* Only apply the UTC offset if it's within reasons. */
+ if (RT_ABS(pUdf->offUtcInMin) <= 13*60)
+ RTTimeSpecSubSeconds(pTimeSpec, pUdf->offUtcInMin * 60);
+ }
+ else
+ RTTimeSpecSetNano(pTimeSpec, 0);
+}
+
+
+/**
+ * Initialization of a RTFSISOCORE structure from a directory record.
+ *
+ * @note The RTFSISOCORE::pParentDir and RTFSISOCORE::Clusters members are
+ * properly initialized elsewhere.
+ *
+ * @returns IRPT status code. Either VINF_SUCCESS or VERR_NO_MEMORY, the latter
+ * only if @a cDirRecs is above 1.
+ * @param pCore The structure to initialize.
+ * @param pDirRec The primary directory record.
+ * @param cDirRecs Number of directory records.
+ * @param offDirRec The offset of the primary directory record.
+ * @param uVersion The file version number.
+ * @param pRockInfo Optional rock ridge info for the entry.
+ * @param pVol The volume.
+ */
+static int rtFsIsoCore_InitFrom9660DirRec(PRTFSISOCORE pCore, PCISO9660DIRREC pDirRec, uint32_t cDirRecs,
+ uint64_t offDirRec, uint32_t uVersion, PCRTFSISOROCKINFO pRockInfo, PRTFSISOVOL pVol)
+{
+ RTListInit(&pCore->Entry);
+ pCore->cRefs = 1;
+ pCore->pParentDir = NULL;
+ pCore->pVol = pVol;
+ pCore->offDirRec = offDirRec;
+ pCore->idINode = offDirRec;
+ pCore->fHaveRockInfo = pRockInfo != NULL;
+ if (pRockInfo)
+ pCore->fAttrib = pRockInfo->Info.Attr.fMode;
+ else
+ pCore->fAttrib = pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY
+ ? 0755 | RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY
+ : 0644 | RTFS_TYPE_FILE;
+ if (pDirRec->fFileFlags & ISO9660_FILE_FLAGS_HIDDEN)
+ pCore->fAttrib |= RTFS_DOS_HIDDEN;
+ pCore->cbObject = ISO9660_GET_ENDIAN(&pDirRec->cbData);
+ pCore->uVersion = uVersion;
+ pCore->cExtents = 1;
+ pCore->FirstExtent.cbExtent = pCore->cbObject;
+ pCore->FirstExtent.off = (ISO9660_GET_ENDIAN(&pDirRec->offExtent) + pDirRec->cExtAttrBlocks) * (uint64_t)pVol->cbBlock;
+ pCore->FirstExtent.idxPart = UINT32_MAX;
+ pCore->FirstExtent.uReserved = 0;
+
+ if (pRockInfo)
+ {
+ pCore->BirthTime = pRockInfo->Info.BirthTime;
+ pCore->ModificationTime = pRockInfo->Info.ModificationTime;
+ pCore->AccessTime = pRockInfo->Info.AccessTime;
+ pCore->ChangeTime = pRockInfo->Info.ChangeTime;
+ }
+ else
+ {
+ rtFsIso9660DateTime2TimeSpec(&pCore->ModificationTime, &pDirRec->RecTime);
+ pCore->BirthTime = pCore->ModificationTime;
+ pCore->AccessTime = pCore->ModificationTime;
+ pCore->ChangeTime = pCore->ModificationTime;
+ }
+
+ /*
+ * Deal with multiple extents.
+ */
+ if (RT_LIKELY(cDirRecs == 1))
+ { /* done */ }
+ else
+ {
+ PRTFSISOEXTENT pCurExtent = &pCore->FirstExtent;
+ while (cDirRecs > 1)
+ {
+ offDirRec += pDirRec->cbDirRec;
+ pDirRec = (PCISO9660DIRREC)((uintptr_t)pDirRec + pDirRec->cbDirRec);
+ if (pDirRec->cbDirRec != 0)
+ {
+ uint64_t offDisk = ISO9660_GET_ENDIAN(&pDirRec->offExtent) * (uint64_t)pVol->cbBlock;
+ uint32_t cbExtent = ISO9660_GET_ENDIAN(&pDirRec->cbData);
+ pCore->cbObject += cbExtent;
+
+ if (pCurExtent->off + pCurExtent->cbExtent == offDisk)
+ pCurExtent->cbExtent += cbExtent;
+ else
+ {
+ void *pvNew = RTMemRealloc(pCore->paExtents, pCore->cExtents * sizeof(pCore->paExtents[0]));
+ if (pvNew)
+ pCore->paExtents = (PRTFSISOEXTENT)pvNew;
+ else
+ {
+ RTMemFree(pCore->paExtents);
+ return VERR_NO_MEMORY;
+ }
+ pCurExtent = &pCore->paExtents[pCore->cExtents - 1];
+ pCurExtent->cbExtent = cbExtent;
+ pCurExtent->off = offDisk;
+ pCurExtent->idxPart = UINT32_MAX;
+ pCurExtent->uReserved = 0;
+ pCore->cExtents++;
+ }
+ cDirRecs--;
+ }
+ else
+ {
+ uint64_t cbSkip = (offDirRec + pVol->cbSector) & ~(uint64_t)(pVol->cbSector - 1U);
+ offDirRec += cbSkip;
+ pDirRec = (PCISO9660DIRREC)((uintptr_t)pDirRec + (size_t)cbSkip);
+ }
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Initalizes the allocation extends of a core structure.
+ *
+ * @returns IPRT status code
+ * @param pCore The core structure.
+ * @param pbAllocDescs Pointer to the allocation descriptor data.
+ * @param cbAllocDescs The size of the allocation descriptor data.
+ * @param fIcbTagFlags The ICB tag flags.
+ * @param idxDefaultPart The default data partition.
+ * @param offAllocDescs The disk byte offset corresponding to @a pbAllocDesc
+ * in case it's used as data storage (type 3).
+ * @param pVol The volume instance data.
+ */
+static int rtFsIsoCore_InitExtentsUdfIcbEntry(PRTFSISOCORE pCore, uint8_t const *pbAllocDescs, uint32_t cbAllocDescs,
+ uint32_t fIcbTagFlags, uint32_t idxDefaultPart, uint64_t offAllocDescs,
+ PRTFSISOVOL pVol)
+{
+ /*
+ * Just in case there are mutiple file entries in the ICB.
+ */
+ if (pCore->paExtents != NULL)
+ {
+ LogRelMax(45, ("ISO/UDF: Re-reading extents - multiple file entries?\n"));
+ RTMemFree(pCore->paExtents);
+ pCore->paExtents = NULL;
+ }
+
+ /*
+ * Figure the (minimal) size of an allocation descriptor, deal with the
+ * embedded storage and invalid descriptor types.
+ */
+ uint32_t cbOneDesc;
+ switch (fIcbTagFlags & UDF_ICB_FLAGS_AD_TYPE_MASK)
+ {
+ case UDF_ICB_FLAGS_AD_TYPE_EMBEDDED:
+ pCore->cExtents = 1;
+ pCore->FirstExtent.cbExtent = cbAllocDescs;
+ pCore->FirstExtent.off = offAllocDescs;
+ pCore->FirstExtent.idxPart = idxDefaultPart;
+ return VINF_SUCCESS;
+
+ case UDF_ICB_FLAGS_AD_TYPE_SHORT: cbOneDesc = sizeof(UDFSHORTAD); break;
+ case UDF_ICB_FLAGS_AD_TYPE_LONG: cbOneDesc = sizeof(UDFLONGAD); break;
+ case UDF_ICB_FLAGS_AD_TYPE_EXTENDED: cbOneDesc = sizeof(UDFEXTAD); break;
+
+ default:
+ LogRelMax(45, ("ISO/UDF: Unknown allocation descriptor type %#x\n", fIcbTagFlags));
+ return VERR_ISO_FS_UNKNOWN_AD_TYPE;
+ }
+ if (cbAllocDescs >= cbOneDesc)
+ {
+ /*
+ * Loop thru the allocation descriptors.
+ */
+ PRTFSISOEXTENT pCurExtent = NULL;
+ union
+ {
+ uint8_t const *pb;
+ PCUDFSHORTAD pShort;
+ PCUDFLONGAD pLong;
+ PCUDFEXTAD pExt;
+ } uPtr;
+ uPtr.pb = pbAllocDescs;
+ do
+ {
+ /* Extract the information we need from the descriptor. */
+ uint32_t idxBlock;
+ uint32_t idxPart;
+ uint32_t cb;
+ uint8_t uType;
+ switch (fIcbTagFlags & UDF_ICB_FLAGS_AD_TYPE_MASK)
+ {
+ case UDF_ICB_FLAGS_AD_TYPE_SHORT:
+ uType = uPtr.pShort->uType;
+ cb = uPtr.pShort->cb;
+ idxBlock = uPtr.pShort->off;
+ idxPart = idxDefaultPart;
+ cbAllocDescs -= sizeof(*uPtr.pShort);
+ uPtr.pShort++;
+ break;
+ case UDF_ICB_FLAGS_AD_TYPE_LONG:
+ uType = uPtr.pLong->uType;
+ cb = uPtr.pLong->cb;
+ idxBlock = uPtr.pLong->Location.off;
+ idxPart = uPtr.pLong->Location.uPartitionNo;
+ cbAllocDescs -= sizeof(*uPtr.pLong);
+ uPtr.pLong++;
+ break;
+ case UDF_ICB_FLAGS_AD_TYPE_EXTENDED:
+ if ( uPtr.pExt->cbInformation > cbAllocDescs
+ || uPtr.pExt->cbInformation < sizeof(*uPtr.pExt))
+ return VERR_ISOFS_BAD_EXTAD;
+ uType = uPtr.pExt->uType;
+ cb = uPtr.pExt->cb;
+ idxBlock = uPtr.pExt->Location.off;
+ idxPart = uPtr.pExt->Location.uPartitionNo;
+ cbAllocDescs -= uPtr.pExt->cbInformation;
+ uPtr.pb += uPtr.pExt->cbInformation;
+ break;
+ default:
+ AssertFailedReturn(VERR_IPE_NOT_REACHED_DEFAULT_CASE);
+ }
+
+ /* Check if we can extend the current extent. This is useful since
+ the descriptors can typically only cover 1GB. */
+ uint64_t const off = (uint64_t)idxBlock << pVol->Udf.VolInfo.cShiftBlock;
+ if ( pCurExtent != NULL
+ && ( pCurExtent->off != UINT64_MAX
+ ? uType == UDF_AD_TYPE_RECORDED_AND_ALLOCATED
+ && pCurExtent->off + pCurExtent->cbExtent == off
+ && pCurExtent->idxPart == idxPart
+ : uType != UDF_AD_TYPE_RECORDED_AND_ALLOCATED) )
+ pCurExtent->cbExtent += cb;
+ else
+ {
+ /* Allocate a new descriptor. */
+ if (pCore->cExtents == 0)
+ {
+ pCore->cExtents = 1;
+ pCurExtent = &pCore->FirstExtent;
+ }
+ else
+ {
+ void *pvNew = RTMemRealloc(pCore->paExtents, pCore->cExtents * sizeof(pCore->paExtents[0]));
+ if (pvNew)
+ pCore->paExtents = (PRTFSISOEXTENT)pvNew;
+ else
+ {
+ RTMemFree(pCore->paExtents);
+ pCore->paExtents = NULL;
+ pCore->cExtents = 0;
+ return VERR_NO_MEMORY;
+ }
+ pCurExtent = &pCore->paExtents[pCore->cExtents - 1];
+ pCore->cExtents++;
+ }
+
+ /* Initialize it. */
+ if (uType == UDF_AD_TYPE_RECORDED_AND_ALLOCATED)
+ {
+ pCurExtent->off = off;
+ pCurExtent->idxPart = idxPart;
+ }
+ else
+ {
+ pCurExtent->off = UINT64_MAX;
+ pCurExtent->idxPart = UINT32_MAX;
+ }
+ pCurExtent->cbExtent = cb;
+ pCurExtent->uReserved = 0;
+ }
+ } while (cbAllocDescs >= cbOneDesc);
+
+ if (cbAllocDescs > 0)
+ LogRelMax(45,("ISO/UDF: Warning! %u bytes left in allocation descriptor: %.*Rhxs\n", cbAllocDescs, cbAllocDescs, uPtr.pb));
+ }
+ else
+ {
+ /*
+ * Zero descriptors
+ */
+ pCore->cExtents = 0;
+ pCore->FirstExtent.off = UINT64_MAX;
+ pCore->FirstExtent.cbExtent = 0;
+ pCore->FirstExtent.idxPart = UINT32_MAX;
+
+ if (cbAllocDescs > 0)
+ LogRelMax(45, ("ISO/UDF: Warning! Allocation descriptor area is shorted than one descriptor: %#u vs %#u: %.*Rhxs\n",
+ cbAllocDescs, cbOneDesc, cbAllocDescs, pbAllocDescs));
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Converts ICB flags, ICB file type and file entry permissions to an IPRT file
+ * mode mask.
+ *
+ * @returns IPRT status ocde
+ * @param fIcbTagFlags The ICB flags.
+ * @param bFileType The ICB file type.
+ * @param fPermission The file entry permission mask.
+ * @param pfAttrib Where to return the IRPT file mode mask.
+ */
+static int rtFsIsoCore_UdfStuffToFileMode(uint32_t fIcbTagFlags, uint8_t bFileType, uint32_t fPermission, PRTFMODE pfAttrib)
+{
+ /*
+ * Type:
+ */
+ RTFMODE fAttrib;
+ switch (bFileType)
+ {
+ case UDF_FILE_TYPE_DIRECTORY:
+ fAttrib = RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY;
+ break;
+
+ case UDF_FILE_TYPE_REGULAR_FILE:
+ case UDF_FILE_TYPE_REAL_TIME_FILE:
+ fAttrib = RTFS_TYPE_FILE;
+ break;
+
+ case UDF_FILE_TYPE_SYMBOLIC_LINK:
+ fAttrib = RTFS_TYPE_SYMLINK;
+ break;
+
+ case UDF_FILE_TYPE_BLOCK_DEVICE:
+ fAttrib = RTFS_TYPE_DEV_BLOCK;
+ break;
+ case UDF_FILE_TYPE_CHARACTER_DEVICE:
+ fAttrib = RTFS_TYPE_DEV_CHAR;
+ break;
+
+ case UDF_FILE_TYPE_FIFO:
+ fAttrib = RTFS_TYPE_FIFO;
+ break;
+
+ case UDF_FILE_TYPE_SOCKET:
+ fAttrib = RTFS_TYPE_SOCKET;
+ break;
+
+ case UDF_FILE_TYPE_STREAM_DIRECTORY:
+ case UDF_FILE_TYPE_EXTENDED_ATTRIBUTES:
+ case UDF_FILE_TYPE_TERMINAL_ENTRY:
+ case UDF_FILE_TYPE_VAT:
+ case UDF_FILE_TYPE_METADATA_FILE:
+ case UDF_FILE_TYPE_METADATA_MIRROR_FILE:
+ case UDF_FILE_TYPE_METADATA_BITMAP_FILE:
+ case UDF_FILE_TYPE_NOT_SPECIFIED:
+ case UDF_FILE_TYPE_INDIRECT_ENTRY:
+ case UDF_FILE_TYPE_UNALLOCATED_SPACE_ENTRY:
+ case UDF_FILE_TYPE_PARTITION_INTEGRITY_ENTRY:
+ LogRelMax(45, ("ISO/UDF: Warning! Wrong file type: %#x\n", bFileType));
+ return VERR_ISOFS_WRONG_FILE_TYPE;
+
+ default:
+ LogRelMax(45, ("ISO/UDF: Warning! Unknown file type: %#x\n", bFileType));
+ return VERR_ISOFS_UNKNOWN_FILE_TYPE;
+ }
+
+ /*
+ * Permissions:
+ */
+ if (fPermission & UDF_PERM_OTH_EXEC)
+ fAttrib |= RTFS_UNIX_IXOTH;
+ if (fPermission & UDF_PERM_OTH_READ)
+ fAttrib |= RTFS_UNIX_IROTH;
+ if (fPermission & UDF_PERM_OTH_WRITE)
+ fAttrib |= RTFS_UNIX_IWOTH;
+
+ if (fPermission & UDF_PERM_GRP_EXEC)
+ fAttrib |= RTFS_UNIX_IXGRP;
+ if (fPermission & UDF_PERM_GRP_READ)
+ fAttrib |= RTFS_UNIX_IRGRP;
+ if (fPermission & UDF_PERM_GRP_WRITE)
+ fAttrib |= RTFS_UNIX_IWGRP;
+
+ if (fPermission & UDF_PERM_USR_EXEC)
+ fAttrib |= RTFS_UNIX_IXUSR;
+ if (fPermission & UDF_PERM_USR_READ)
+ fAttrib |= RTFS_UNIX_IRUSR;
+ if (fPermission & UDF_PERM_USR_WRITE)
+ fAttrib |= RTFS_UNIX_IWUSR;
+
+ if ( !(fAttrib & (UDF_PERM_OTH_WRITE | UDF_PERM_GRP_WRITE | UDF_PERM_USR_WRITE))
+ && (fAttrib & (UDF_PERM_OTH_READ | UDF_PERM_GRP_READ | UDF_PERM_USR_READ)) )
+ fAttrib |= RTFS_DOS_READONLY;
+
+ /*
+ * Attributes:
+ */
+ if (fIcbTagFlags & UDF_ICB_FLAGS_ARCHIVE)
+ fAttrib |= RTFS_DOS_ARCHIVED;
+ if (fIcbTagFlags & UDF_ICB_FLAGS_SYSTEM)
+ fAttrib |= RTFS_DOS_SYSTEM;
+ if (fIcbTagFlags & UDF_ICB_FLAGS_ARCHIVE)
+ fAttrib |= RTFS_DOS_ARCHIVED;
+
+ if (fIcbTagFlags & UDF_ICB_FLAGS_SET_UID)
+ fAttrib |= RTFS_UNIX_ISUID;
+ if (fIcbTagFlags & UDF_ICB_FLAGS_SET_GID)
+ fAttrib |= RTFS_UNIX_ISGID;
+ if (fIcbTagFlags & UDF_ICB_FLAGS_STICKY)
+ fAttrib |= RTFS_UNIX_ISTXT;
+
+ /* Warn about weird flags. */
+ if (fIcbTagFlags & UDF_ICB_FLAGS_TRANSFORMED)
+ LogRelMax(45, ("ISO/UDF: Warning! UDF_ICB_FLAGS_TRANSFORMED!\n"));
+ if (fIcbTagFlags & UDF_ICB_FLAGS_MULTI_VERSIONS)
+ LogRelMax(45, ("ISO/UDF: Warning! UDF_ICB_FLAGS_MULTI_VERSIONS!\n"));
+ if (fIcbTagFlags & UDF_ICB_FLAGS_STREAM)
+ LogRelMax(45, ("ISO/UDF: Warning! UDF_ICB_FLAGS_STREAM!\n"));
+ if (fIcbTagFlags & UDF_ICB_FLAGS_RESERVED_MASK)
+ LogRelMax(45, ("ISO/UDF: Warning! UDF_ICB_FLAGS_RESERVED_MASK (%#x)!\n", fIcbTagFlags & UDF_ICB_FLAGS_RESERVED_MASK));
+
+ *pfAttrib = fAttrib;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Initialize/update a core object structure from an UDF extended file entry.
+ *
+ * @returns IPRT status code
+ * @param pCore The core object structure to initialize.
+ * @param pFileEntry The file entry.
+ * @param idxDefaultPart The default data partition.
+ * @param pcProcessed Variable to increment on success.
+ * @param pVol The volume instance.
+ */
+static int rtFsIsoCore_InitFromUdfIcbExFileEntry(PRTFSISOCORE pCore, PCUDFEXFILEENTRY pFileEntry, uint32_t idxDefaultPart,
+ uint32_t *pcProcessed, PRTFSISOVOL pVol)
+{
+#ifdef LOG_ENABLED
+ /*
+ * Log it.
+ */
+ if (LogIs2Enabled())
+ {
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", IcbTag.cEntiresBeforeThis);
+ UDF_LOG2_MEMBER(pFileEntry, "#06RX16", IcbTag.uStrategyType);
+ UDF_LOG2_MEMBER(pFileEntry, "#04RX8", IcbTag.abStrategyParams[0]);
+ UDF_LOG2_MEMBER(pFileEntry, "#04RX8", IcbTag.abStrategyParams[1]);
+ UDF_LOG2_MEMBER(pFileEntry, "#06RX16", IcbTag.cMaxEntries);
+ UDF_LOG2_MEMBER(pFileEntry, "#04RX8", IcbTag.bReserved);
+ UDF_LOG2_MEMBER(pFileEntry, "#04RX8", IcbTag.bFileType);
+ UDF_LOG2_MEMBER_LBADDR(pFileEntry, IcbTag.ParentIcb);
+ UDF_LOG2_MEMBER(pFileEntry, "#06RX16", IcbTag.fFlags);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", uid);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", gid);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", fPermissions);
+ UDF_LOG2_MEMBER(pFileEntry, "#06RX16", cHardlinks);
+ UDF_LOG2_MEMBER(pFileEntry, "#04RX8", uRecordFormat);
+ UDF_LOG2_MEMBER(pFileEntry, "#04RX8", fRecordDisplayAttribs);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", cbRecord);
+ UDF_LOG2_MEMBER(pFileEntry, "#018RX64", cbData);
+ UDF_LOG2_MEMBER(pFileEntry, "#018RX64", cbObject);
+ UDF_LOG2_MEMBER(pFileEntry, "#018RX64", cLogicalBlocks);
+ UDF_LOG2_MEMBER_TIMESTAMP(pFileEntry, AccessTime);
+ UDF_LOG2_MEMBER_TIMESTAMP(pFileEntry, ModificationTime);
+ UDF_LOG2_MEMBER_TIMESTAMP(pFileEntry, BirthTime);
+ UDF_LOG2_MEMBER_TIMESTAMP(pFileEntry, ChangeTime);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", uCheckpoint);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", uReserved);
+ UDF_LOG2_MEMBER_LONGAD(pFileEntry, ExtAttribIcb);
+ UDF_LOG2_MEMBER_LONGAD(pFileEntry, StreamDirIcb);
+ UDF_LOG2_MEMBER_ENTITY_ID(pFileEntry, idImplementation);
+ UDF_LOG2_MEMBER(pFileEntry, "#018RX64", INodeId);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", cbExtAttribs);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", cbAllocDescs);
+ if (pFileEntry->cbExtAttribs > 0)
+ Log2((pFileEntry->cbExtAttribs <= 16 ? "ISO/UDF: %-32s %.*Rhxs\n" : "ISO/UDF: %-32s\n%.*RhxD\n",
+ "abExtAttribs:", pFileEntry->cbExtAttribs, pFileEntry->abExtAttribs));
+ if (pFileEntry->cbAllocDescs > 0)
+ switch (pFileEntry->IcbTag.fFlags & UDF_ICB_FLAGS_AD_TYPE_MASK)
+ {
+ case UDF_ICB_FLAGS_AD_TYPE_SHORT:
+ {
+ PCUDFSHORTAD paDescs = (PCUDFSHORTAD)&pFileEntry->abExtAttribs[pFileEntry->cbExtAttribs];
+ uint32_t cDescs = pFileEntry->cbAllocDescs / sizeof(paDescs[0]);
+ for (uint32_t i = 0; i < cDescs; i++)
+ Log2(("ISO/UDF: ShortAD[%u]: %#010RX32 LB %#010RX32; type=%u\n",
+ i, paDescs[i].off, paDescs[i].cb, paDescs[i].uType));
+ break;
+ }
+ case UDF_ICB_FLAGS_AD_TYPE_LONG:
+ {
+ PCUDFLONGAD paDescs = (PCUDFLONGAD)&pFileEntry->abExtAttribs[pFileEntry->cbExtAttribs];
+ uint32_t cDescs = pFileEntry->cbAllocDescs / sizeof(paDescs[0]);
+ for (uint32_t i = 0; i < cDescs; i++)
+ Log2(("ISO/UDF: LongAD[%u]: %#06RX16:%#010RX32 LB %#010RX32; type=%u iu=%.6Rhxs\n",
+ i, paDescs[i].Location.uPartitionNo, paDescs[i].Location.off,
+ paDescs[i].cb, paDescs[i].uType, &paDescs[i].ImplementationUse));
+ break;
+ }
+ default:
+ Log2(("ISO/UDF: %-32s Type=%u\n%.*RhxD\n",
+ "abExtAttribs:", pFileEntry->IcbTag.fFlags & UDF_ICB_FLAGS_AD_TYPE_MASK,
+ pFileEntry->cbAllocDescs, &pFileEntry->abExtAttribs[pFileEntry->cbExtAttribs]));
+ break;
+ }
+ }
+#endif
+
+ /*
+ * Basic sanity checking of what we use.
+ */
+ if ( RT_UOFFSETOF(UDFFILEENTRY, abExtAttribs) + pFileEntry->cbExtAttribs + pFileEntry->cbAllocDescs
+ > pVol->Udf.VolInfo.cbBlock
+ || (pFileEntry->cbExtAttribs & 3) != 0
+ || pFileEntry->cbExtAttribs >= pVol->Udf.VolInfo.cbBlock
+ || (pFileEntry->cbAllocDescs & 3) != 0
+ || pFileEntry->cbAllocDescs >= pVol->Udf.VolInfo.cbBlock)
+ {
+ LogRelMax(45, ("ISO/UDF: Extended file entry (ICB) is bad size values: cbAllocDesc=%#x cbExtAttribs=%#x (cbBlock=%#x)\n",
+ pFileEntry->cbAllocDescs, pFileEntry->cbExtAttribs, pVol->Udf.VolInfo.cbBlock));
+ return VERR_ISOFS_BAD_FILE_ENTRY;
+ }
+
+ //pCore->uid = pFileEntry->uid;
+ //pCore->gid = pFileEntry->gid;
+ //pCore->cHardlinks = RT_MIN(pFileEntry->cHardlinks, 1);
+ pCore->cbObject = pFileEntry->cbData;
+ //pCore->cbAllocated = pFileEntry->cLogicalBlocks << pVol->Udf.VolInfo.cShiftBlock;
+ pCore->idINode = pFileEntry->INodeId;
+
+ rtFsIsoUdfTimestamp2TimeSpec(&pCore->AccessTime, &pFileEntry->AccessTime);
+ rtFsIsoUdfTimestamp2TimeSpec(&pCore->ModificationTime, &pFileEntry->ModificationTime);
+ rtFsIsoUdfTimestamp2TimeSpec(&pCore->BirthTime, &pFileEntry->BirthTime);
+ rtFsIsoUdfTimestamp2TimeSpec(&pCore->ChangeTime, &pFileEntry->ChangeTime);
+
+ if ( pFileEntry->uRecordFormat
+ || pFileEntry->fRecordDisplayAttribs
+ || pFileEntry->cbRecord)
+ LogRelMax(45, ("ISO/UDF: uRecordFormat=%#x fRecordDisplayAttribs=%#x cbRecord=%#x\n",
+ pFileEntry->uRecordFormat, pFileEntry->fRecordDisplayAttribs, pFileEntry->cbRecord));
+
+ /*
+ * Conver the file mode.
+ */
+ int rc = rtFsIsoCore_UdfStuffToFileMode(pFileEntry->IcbTag.fFlags, pFileEntry->IcbTag.bFileType,
+ pFileEntry->fPermissions, &pCore->fAttrib);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Convert extent info.
+ */
+ rc = rtFsIsoCore_InitExtentsUdfIcbEntry(pCore,
+ &pFileEntry->abExtAttribs[pFileEntry->cbExtAttribs],
+ pFileEntry->cbAllocDescs,
+ pFileEntry->IcbTag.fFlags,
+ idxDefaultPart,
+ ((uint64_t)pFileEntry->Tag.offTag << pVol->Udf.VolInfo.cShiftBlock)
+ + RT_UOFFSETOF(UDFFILEENTRY, abExtAttribs) + pFileEntry->cbExtAttribs,
+ pVol);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * We're good.
+ */
+ *pcProcessed += 1;
+ return VINF_SUCCESS;
+ }
+
+ /* Just in case. */
+ if (pCore->paExtents)
+ {
+ RTMemFree(pCore->paExtents);
+ pCore->paExtents = NULL;
+ }
+ pCore->cExtents = 0;
+ }
+ return rc;
+}
+
+
+/**
+ * Initialize/update a core object structure from an UDF file entry.
+ *
+ * @returns IPRT status code
+ * @param pCore The core object structure to initialize.
+ * @param pFileEntry The file entry.
+ * @param idxDefaultPart The default data partition.
+ * @param pcProcessed Variable to increment on success.
+ * @param pVol The volume instance.
+ */
+static int rtFsIsoCore_InitFromUdfIcbFileEntry(PRTFSISOCORE pCore, PCUDFFILEENTRY pFileEntry, uint32_t idxDefaultPart,
+ uint32_t *pcProcessed, PRTFSISOVOL pVol)
+{
+#ifdef LOG_ENABLED
+ /*
+ * Log it.
+ */
+ if (LogIs2Enabled())
+ {
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", IcbTag.cEntiresBeforeThis);
+ UDF_LOG2_MEMBER(pFileEntry, "#06RX16", IcbTag.uStrategyType);
+ UDF_LOG2_MEMBER(pFileEntry, "#04RX8", IcbTag.abStrategyParams[0]);
+ UDF_LOG2_MEMBER(pFileEntry, "#04RX8", IcbTag.abStrategyParams[1]);
+ UDF_LOG2_MEMBER(pFileEntry, "#06RX16", IcbTag.cMaxEntries);
+ UDF_LOG2_MEMBER(pFileEntry, "#04RX8", IcbTag.bReserved);
+ UDF_LOG2_MEMBER(pFileEntry, "#04RX8", IcbTag.bFileType);
+ UDF_LOG2_MEMBER_LBADDR(pFileEntry, IcbTag.ParentIcb);
+ UDF_LOG2_MEMBER(pFileEntry, "#06RX16", IcbTag.fFlags);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", uid);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", gid);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", fPermissions);
+ UDF_LOG2_MEMBER(pFileEntry, "#06RX16", cHardlinks);
+ UDF_LOG2_MEMBER(pFileEntry, "#04RX8", uRecordFormat);
+ UDF_LOG2_MEMBER(pFileEntry, "#04RX8", fRecordDisplayAttribs);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", cbRecord);
+ UDF_LOG2_MEMBER(pFileEntry, "#018RX64", cbData);
+ UDF_LOG2_MEMBER(pFileEntry, "#018RX64", cLogicalBlocks);
+ UDF_LOG2_MEMBER_TIMESTAMP(pFileEntry, AccessTime);
+ UDF_LOG2_MEMBER_TIMESTAMP(pFileEntry, ModificationTime);
+ UDF_LOG2_MEMBER_TIMESTAMP(pFileEntry, ChangeTime);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", uCheckpoint);
+ UDF_LOG2_MEMBER_LONGAD(pFileEntry, ExtAttribIcb);
+ UDF_LOG2_MEMBER_ENTITY_ID(pFileEntry, idImplementation);
+ UDF_LOG2_MEMBER(pFileEntry, "#018RX64", INodeId);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", cbExtAttribs);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", cbAllocDescs);
+ if (pFileEntry->cbExtAttribs > 0)
+ Log2((pFileEntry->cbExtAttribs <= 16 ? "ISO/UDF: %-32s %.*Rhxs\n" : "ISO/UDF: %-32s\n%.*RhxD\n",
+ "abExtAttribs:", pFileEntry->cbExtAttribs, pFileEntry->abExtAttribs));
+ if (pFileEntry->cbAllocDescs > 0)
+ switch (pFileEntry->IcbTag.fFlags & UDF_ICB_FLAGS_AD_TYPE_MASK)
+ {
+ case UDF_ICB_FLAGS_AD_TYPE_SHORT:
+ {
+ PCUDFSHORTAD paDescs = (PCUDFSHORTAD)&pFileEntry->abExtAttribs[pFileEntry->cbExtAttribs];
+ uint32_t cDescs = pFileEntry->cbAllocDescs / sizeof(paDescs[0]);
+ for (uint32_t i = 0; i < cDescs; i++)
+ Log2(("ISO/UDF: ShortAD[%u]: %#010RX32 LB %#010RX32; type=%u\n",
+ i, paDescs[i].off, paDescs[i].cb, paDescs[i].uType));
+ break;
+ }
+ case UDF_ICB_FLAGS_AD_TYPE_LONG:
+ {
+ PCUDFLONGAD paDescs = (PCUDFLONGAD)&pFileEntry->abExtAttribs[pFileEntry->cbExtAttribs];
+ uint32_t cDescs = pFileEntry->cbAllocDescs / sizeof(paDescs[0]);
+ for (uint32_t i = 0; i < cDescs; i++)
+ Log2(("ISO/UDF: LongAD[%u]: %#06RX16:%#010RX32 LB %#010RX32; type=%u iu=%.6Rhxs\n",
+ i, paDescs[i].Location.uPartitionNo, paDescs[i].Location.off,
+ paDescs[i].cb, paDescs[i].uType, &paDescs[i].ImplementationUse));
+ break;
+ }
+ default:
+ Log2(("ISO/UDF: %-32s Type=%u\n%.*RhxD\n",
+ "abExtAttribs:", pFileEntry->IcbTag.fFlags & UDF_ICB_FLAGS_AD_TYPE_MASK,
+ pFileEntry->cbAllocDescs, &pFileEntry->abExtAttribs[pFileEntry->cbExtAttribs]));
+ break;
+ }
+ }
+#endif
+
+ /*
+ * Basic sanity checking of what we use.
+ */
+ if ( RT_UOFFSETOF(UDFFILEENTRY, abExtAttribs) + pFileEntry->cbExtAttribs + pFileEntry->cbAllocDescs
+ > pVol->Udf.VolInfo.cbBlock
+ || (pFileEntry->cbExtAttribs & 3) != 0
+ || pFileEntry->cbExtAttribs >= pVol->Udf.VolInfo.cbBlock
+ || (pFileEntry->cbAllocDescs & 3) != 0
+ || pFileEntry->cbAllocDescs >= pVol->Udf.VolInfo.cbBlock)
+ {
+ LogRelMax(45, ("ISO/UDF: File entry (ICB) is bad size values: cbAllocDesc=%#x cbExtAttribs=%#x (cbBlock=%#x)\n",
+ pFileEntry->cbAllocDescs, pFileEntry->cbExtAttribs, pVol->Udf.VolInfo.cbBlock));
+ return VERR_ISOFS_BAD_FILE_ENTRY;
+ }
+
+ //pCore->uid = pFileEntry->uid;
+ //pCore->gid = pFileEntry->gid;
+ //pCore->cHardlinks = RT_MIN(pFileEntry->cHardlinks, 1);
+ pCore->cbObject = pFileEntry->cbData;
+ //pCore->cbAllocated = pFileEntry->cLogicalBlocks << pVol->Udf.VolInfo.cShiftBlock;
+ pCore->idINode = pFileEntry->INodeId;
+
+ rtFsIsoUdfTimestamp2TimeSpec(&pCore->AccessTime, &pFileEntry->AccessTime);
+ rtFsIsoUdfTimestamp2TimeSpec(&pCore->ModificationTime, &pFileEntry->ModificationTime);
+ rtFsIsoUdfTimestamp2TimeSpec(&pCore->ChangeTime, &pFileEntry->ChangeTime);
+ pCore->BirthTime = pCore->ModificationTime;
+ if (RTTimeSpecCompare(&pCore->BirthTime, &pCore->ChangeTime) > 0)
+ pCore->BirthTime = pCore->ChangeTime;
+ if (RTTimeSpecCompare(&pCore->BirthTime, &pCore->AccessTime) > 0)
+ pCore->BirthTime = pCore->AccessTime;
+
+ if ( pFileEntry->uRecordFormat
+ || pFileEntry->fRecordDisplayAttribs
+ || pFileEntry->cbRecord)
+ LogRelMax(45, ("ISO/UDF: uRecordFormat=%#x fRecordDisplayAttribs=%#x cbRecord=%#x\n",
+ pFileEntry->uRecordFormat, pFileEntry->fRecordDisplayAttribs, pFileEntry->cbRecord));
+
+ /*
+ * Conver the file mode.
+ */
+ int rc = rtFsIsoCore_UdfStuffToFileMode(pFileEntry->IcbTag.fFlags, pFileEntry->IcbTag.bFileType,
+ pFileEntry->fPermissions, &pCore->fAttrib);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Convert extent info.
+ */
+ rc = rtFsIsoCore_InitExtentsUdfIcbEntry(pCore,
+ &pFileEntry->abExtAttribs[pFileEntry->cbExtAttribs],
+ pFileEntry->cbAllocDescs,
+ pFileEntry->IcbTag.fFlags,
+ idxDefaultPart,
+ ((uint64_t)pFileEntry->Tag.offTag << pVol->Udf.VolInfo.cShiftBlock)
+ + RT_UOFFSETOF(UDFFILEENTRY, abExtAttribs) + pFileEntry->cbExtAttribs,
+ pVol);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * We're good.
+ */
+ *pcProcessed += 1;
+ return VINF_SUCCESS;
+ }
+
+ /* Just in case. */
+ if (pCore->paExtents)
+ {
+ RTMemFree(pCore->paExtents);
+ pCore->paExtents = NULL;
+ }
+ pCore->cExtents = 0;
+ }
+ return rc;
+}
+
+
+/**
+ * Recursive helper for rtFsIsoCore_InitFromUdfIcbAndFileIdDesc.
+ *
+ * @returns IRPT status code.
+ * @param pCore The core structure to initialize.
+ * @param AllocDesc The ICB allocation descriptor.
+ * @param pbBuf The buffer, one logical block in size.
+ * @param cNestings The number of recursive nestings (should be zero).
+ * @param pcProcessed Variable to update when we've processed something
+ * useful.
+ * @param pcIndirections Variable tracing the number of indirections we've
+ * taken during the processing. This is used to
+ * prevent us from looping forever on a bad chain
+ * @param pVol The volue instance data.
+ */
+static int rtFsIsoCore_InitFromUdfIcbRecursive(PRTFSISOCORE pCore, UDFLONGAD AllocDesc, uint8_t *pbBuf, uint32_t cNestings,
+ uint32_t *pcProcessed, uint32_t *pcIndirections, PRTFSISOVOL pVol)
+{
+ if (cNestings >= 8)
+ return VERR_ISOFS_TOO_DEEP_ICB_RECURSION;
+
+ for (;;)
+ {
+ if (*pcIndirections >= 32)
+ return VERR_ISOFS_TOO_MANY_ICB_INDIRECTIONS;
+
+ /*
+ * Check the basic validity of the allocation descriptor.
+ */
+ if ( AllocDesc.uType == UDF_AD_TYPE_RECORDED_AND_ALLOCATED
+ && AllocDesc.cb >= sizeof(UDFICBTAG) )
+ { /* likely */ }
+ else if (AllocDesc.uType != UDF_AD_TYPE_RECORDED_AND_ALLOCATED)
+ {
+ Log(("ISO/UDF: ICB has alloc type %d!\n", AllocDesc.uType));
+ return VINF_SUCCESS;
+ }
+ else
+ {
+ LogRelMax(45, ("ISO/UDF: ICB is too small: %u bytes\n", AllocDesc.cb));
+ return AllocDesc.cb == 0 ? VINF_SUCCESS : VERR_ISOFS_ICB_ENTRY_TOO_SMALL;
+ }
+
+ /*
+ * Process it block by block.
+ */
+ uint32_t cBlocks = (AllocDesc.cb + pVol->Udf.VolInfo.cbBlock - 1) >> pVol->Udf.VolInfo.cShiftBlock;
+ for (uint32_t idxBlock = 0; ; idxBlock++)
+ {
+ /*
+ * Read a block
+ */
+ size_t cbToRead = RT_MIN(pVol->Udf.VolInfo.cbBlock, AllocDesc.cb);
+ int rc = rtFsIsoVolUdfVpRead(pVol, AllocDesc.Location.uPartitionNo, AllocDesc.Location.off + idxBlock, 0,
+ pbBuf, cbToRead);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (cbToRead < pVol->Udf.VolInfo.cbBlock)
+ RT_BZERO(&pbBuf[cbToRead], pVol->Udf.VolInfo.cbBlock - cbToRead);
+
+ /*
+ * Verify the TAG.
+ */
+ PUDFICBHDR pHdr = (PUDFICBHDR)pbBuf;
+ rc = rtFsIsoVolValidateUdfDescTagAndCrc(&pHdr->Tag, pVol->Udf.VolInfo.cbBlock, UINT16_MAX,
+ AllocDesc.Location.off + idxBlock, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Do specific processing.
+ */
+ if (pHdr->Tag.idTag == UDF_TAG_ID_FILE_ENTRY)
+ rc = rtFsIsoCore_InitFromUdfIcbFileEntry(pCore, (PCUDFFILEENTRY)pHdr, AllocDesc.Location.uPartitionNo,
+ pcProcessed, pVol);
+ else if (pHdr->Tag.idTag == UDF_TAG_ID_EXTENDED_FILE_ENTRY)
+ rc = rtFsIsoCore_InitFromUdfIcbExFileEntry(pCore, (PCUDFEXFILEENTRY)pHdr, AllocDesc.Location.uPartitionNo,
+ pcProcessed, pVol);
+ else if (pHdr->Tag.idTag == UDF_TAG_ID_INDIRECT_ENTRY)
+ {
+ PUDFINDIRECTENTRY pIndir = (PUDFINDIRECTENTRY)pHdr;
+ *pcIndirections += 1;
+ if (pIndir->IndirectIcb.cb != 0)
+ {
+ if (idxBlock + 1 == cBlocks)
+ {
+ AllocDesc = pIndir->IndirectIcb;
+ Log2(("ISO/UDF: ICB: Indirect entry - looping: %x:%#010RX32 LB %#x; uType=%d\n",
+ AllocDesc.Location.uPartitionNo, AllocDesc.Location.off, AllocDesc.cb, AllocDesc.uType));
+ break;
+ }
+ Log2(("ISO/UDF: ICB: Indirect entry - recursing: %x:%#010RX32 LB %#x; uType=%d\n",
+ pIndir->IndirectIcb.Location.uPartitionNo, pIndir->IndirectIcb.Location.off,
+ pIndir->IndirectIcb.cb, pIndir->IndirectIcb.uType));
+ rc = rtFsIsoCore_InitFromUdfIcbRecursive(pCore, pIndir->IndirectIcb, pbBuf, cNestings,
+ pcProcessed, pcIndirections, pVol);
+ }
+ else
+ Log(("ISO/UDF: zero length indirect entry\n"));
+ }
+ else if (pHdr->Tag.idTag == UDF_TAG_ID_TERMINAL_ENTRY)
+ {
+ Log2(("ISO/UDF: Terminal ICB entry\n"));
+ return VINF_SUCCESS;
+ }
+ else if (pHdr->Tag.idTag == UDF_TAG_ID_UNALLOCATED_SPACE_ENTRY)
+ {
+ Log2(("ISO/UDF: Unallocated space entry: skipping\n"));
+ /* Ignore since we don't do writing (UDFUNALLOCATEDSPACEENTRY) */
+ }
+ else
+ {
+ LogRelMax(90, ("ISO/UDF: Unknown ICB type %#x\n", pHdr->Tag.idTag));
+ return VERR_ISOFS_UNSUPPORTED_ICB;
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Advance.
+ */
+ if (idxBlock + 1 >= cBlocks)
+ return VINF_SUCCESS;
+ }
+
+ /* If we get here, we've jumped thru an indirect entry. */
+ }
+ /* never reached */
+}
+
+
+
+/**
+ * Initialize a core structure from an UDF ICB range and optionally a file ID.
+ *
+ * @returns IPRT status code.
+ * @param pCore The core structure to initialize.
+ * Caller must've ZEROed this structure!
+ * @param pAllocDesc The ICB allocation descriptor.
+ * @param pFid The file ID descriptor. Optional.
+ * @param offInDir The offset of the file ID descriptor in the
+ * parent directory. This is used when looking up
+ * shared directory objects. (Pass 0 for root.)
+ * @param pVol The instance.
+ *
+ * @note Caller must check for UDF_FILE_FLAGS_DELETED before calling if the
+ * object is supposed to be used for real stuff.
+ */
+static int rtFsIsoCore_InitFromUdfIcbAndFileIdDesc(PRTFSISOCORE pCore, PCUDFLONGAD pAllocDesc,
+ PCUDFFILEIDDESC pFid, uintptr_t offInDir, PRTFSISOVOL pVol)
+{
+ Assert(pCore->cRefs == 0);
+ Assert(pCore->cExtents == 0);
+ Assert(pCore->paExtents == NULL);
+ Assert(pCore->pVol == NULL);
+
+ /*
+ * Some size sanity checking.
+ */
+ if (pAllocDesc->cb <= _64K)
+ {
+ if (pAllocDesc->cb >= sizeof(UDFICBHDR))
+ { /* likely */ }
+ else
+ {
+ Log(("rtFsIsoCore_InitFromUdfIcbAndFileIdDesc: ICB too small: %#04x:%010RX32 LB %#x\n",
+ pAllocDesc->Location.uPartitionNo, pAllocDesc->Location.off, pAllocDesc->cb));
+ return VERR_ISOFS_ICB_TOO_SMALL;
+ }
+ }
+ else
+ {
+ Log(("rtFsIsoCore_InitFromUdfIcbAndFileIdDesc: ICB too big: %#04x:%010RX32 LB %#x\n",
+ pAllocDesc->Location.uPartitionNo, pAllocDesc->Location.off, pAllocDesc->cb));
+ return VERR_ISOFS_ICB_TOO_BIG;
+ }
+
+ /*
+ * Allocate a temporary buffer, one logical block in size.
+ */
+ uint8_t * const pbBuf = (uint8_t *)RTMemTmpAlloc(pVol->Udf.VolInfo.cbBlock);
+ if (pbBuf)
+ {
+ uint32_t cProcessed = 0;
+ uint32_t cIndirections = 0;
+ int rc = rtFsIsoCore_InitFromUdfIcbRecursive(pCore, *pAllocDesc, pbBuf, 0, &cProcessed, &cIndirections, pVol);
+ RTMemTmpFree(pbBuf);
+ if (RT_SUCCESS(rc))
+ {
+ if (cProcessed > 0)
+ {
+ if (pFid)
+ {
+ if (pFid->fFlags & UDF_FILE_FLAGS_HIDDEN)
+ pCore->fAttrib |= RTFS_DOS_HIDDEN;
+ if (pFid->fFlags & UDF_FILE_FLAGS_DELETED)
+ pCore->fAttrib = (pCore->fAttrib & ~RTFS_TYPE_MASK) | RTFS_TYPE_WHITEOUT;
+ }
+
+ pCore->cRefs = 1;
+ pCore->pVol = pVol;
+ pCore->offDirRec = offInDir;
+ return VINF_SUCCESS;
+ }
+ rc = VERR_ISOFS_NO_DIRECT_ICB_ENTRIES;
+ }
+
+ /* White-out fix. Caller must be checking for UDF_FILE_FLAGS_DELETED */
+ if ( pFid
+ && (pFid->fFlags & UDF_FILE_FLAGS_DELETED))
+ {
+ pCore->fAttrib = (pCore->fAttrib & ~RTFS_TYPE_MASK) | RTFS_TYPE_WHITEOUT;
+ return VINF_SUCCESS;
+ }
+ return rc;
+ }
+
+ pCore->pVol = NULL;
+ return VERR_NO_TMP_MEMORY;
+}
+
+
+/**
+ * Simple UDF read function.
+ *
+ * This deals with extent mappings as well as virtual partition related block
+ * mapping and such.
+ *
+ * @returns VBox status code.
+ * @param pCore The core object to read data from.
+ * @param offRead The offset to start reading at.
+ * @param pvBuf The output buffer.
+ * @param cbToRead The number of bytes to read.
+ * @param pcbRead Where to return the number of bytes read.
+ * @param poffPosMov Where to return the number of bytes to move the read
+ * position. Optional. (Essentially same as pcbRead
+ * except without the behavior change.)
+ */
+static int rtFsIsoCore_ReadWorker(PRTFSISOCORE pCore, uint64_t offRead, void *pvBuf, size_t cbToRead,
+ size_t *pcbRead, size_t *poffPosMov)
+{
+ /*
+ * Check for EOF.
+ */
+ if (offRead >= pCore->cbObject)
+ {
+ if (poffPosMov)
+ *poffPosMov = 0;
+ if (pcbRead)
+ {
+ *pcbRead = 0;
+ return VINF_EOF;
+ }
+ return VERR_EOF;
+ }
+ int rcRet = VINF_SUCCESS;
+ if ( cbToRead > pCore->cbObject
+ || offRead + cbToRead > pCore->cbObject)
+ {
+ if (!pcbRead)
+ {
+ if (poffPosMov)
+ *poffPosMov = 0;
+ return VERR_EOF;
+ }
+ cbToRead = pCore->cbObject - offRead;
+ rcRet = VINF_EOF;
+ }
+
+ uint64_t cbActual = 0;
+
+ /*
+ * Don't bother looking up the extent if we're not going to
+ * read anything from it.
+ */
+ if (cbToRead > 0)
+ {
+ /*
+ * Locate the first extent.
+ */
+ uint64_t offExtent = 0;
+ uint32_t iExtent = 0;
+ PCRTFSISOEXTENT pCurExtent = &pCore->FirstExtent;
+ if (offRead < pCurExtent->cbExtent)
+ { /* likely */ }
+ else
+ do
+ {
+ offExtent += pCurExtent->cbExtent;
+ pCurExtent = &pCore->paExtents[iExtent++];
+ if (iExtent >= pCore->cExtents)
+ {
+ memset(pvBuf, 0, cbToRead);
+
+ if (pcbRead)
+ *pcbRead = cbToRead;
+ if (poffPosMov)
+ *poffPosMov = cbToRead;
+ return rcRet;
+ }
+ } while (offExtent < offRead);
+ Assert(offRead - offExtent < pCurExtent->cbExtent);
+
+ /*
+ * Do the reading part.
+ */
+ PRTFSISOVOL pVol = pCore->pVol;
+ for (;;)
+ {
+ uint64_t offIntoExtent = offRead - offExtent;
+ size_t cbThisRead = pCurExtent->cbExtent - offIntoExtent;
+ if (cbThisRead > cbToRead)
+ cbThisRead = cbToRead;
+
+ if (pCurExtent->off == UINT64_MAX)
+ RT_BZERO(pvBuf, cbThisRead);
+ else
+ {
+ int rc2;
+ if (pCurExtent->idxPart == UINT32_MAX)
+ rc2 = RTVfsFileReadAt(pVol->hVfsBacking, pCurExtent->off + offIntoExtent, pvBuf, cbThisRead, NULL);
+ else
+ {
+ Assert(pVol->enmType == RTFSISOVOLTYPE_UDF);
+ if (pCurExtent->idxPart < pVol->Udf.VolInfo.cPartitions)
+ {
+ PRTFSISOVOLUDFPMAP pPart = &pVol->Udf.VolInfo.paPartitions[pCurExtent->idxPart];
+ switch (pPart->bType)
+ {
+ case RTFSISO_UDF_PMAP_T_PLAIN:
+ rc2 = RTVfsFileReadAt(pVol->hVfsBacking, pPart->offByteLocation + pCurExtent->off + offIntoExtent,
+ pvBuf, cbThisRead, NULL);
+ break;
+
+ default:
+ AssertFailed();
+ rc2 = VERR_ISOFS_IPE_1;
+ break;
+ }
+ }
+ else
+ {
+ Log(("ISO/UDF: Invalid partition index %#x (offset %#RX64), max partitions %#x; iExtent=%#x\n",
+ pCurExtent->idxPart, pCurExtent->off + offIntoExtent, pVol->Udf.VolInfo.cPartitions, iExtent));
+ rc2 = VERR_ISOFS_INVALID_PARTITION_INDEX;
+ }
+ }
+ if (RT_FAILURE(rc2))
+ {
+ rcRet = rc2;
+ break;
+ }
+ }
+
+ /*
+ * Advance the buffer position and check if we're done (probable).
+ */
+ cbActual += cbThisRead;
+ cbToRead -= cbThisRead;
+ if (!cbToRead)
+ break;
+ pvBuf = (uint8_t *)pvBuf + cbThisRead;
+
+ /*
+ * Advance to the next extent.
+ */
+ offExtent += pCurExtent->cbExtent;
+ pCurExtent = &pCore->paExtents[iExtent++];
+ if (iExtent >= pCore->cExtents)
+ {
+ memset(pvBuf, 0, cbToRead);
+ cbActual += cbToRead;
+ break;
+ }
+ }
+ }
+ else
+ Assert(rcRet == VINF_SUCCESS);
+
+ if (poffPosMov)
+ *poffPosMov = cbActual;
+ if (pcbRead)
+ *pcbRead = cbActual;
+ return rcRet;
+}
+
+
+/**
+ * Worker for rtFsIsoFile_QueryInfo and rtFsIsoDir_QueryInfo.
+ */
+static int rtFsIsoCore_QueryInfo(PRTFSISOCORE pCore, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ pObjInfo->cbObject = pCore->cbObject;
+ pObjInfo->cbAllocated = RT_ALIGN_64(pCore->cbObject, pCore->pVol->cbBlock);
+ pObjInfo->AccessTime = pCore->AccessTime;
+ pObjInfo->ModificationTime = pCore->ModificationTime;
+ pObjInfo->ChangeTime = pCore->ChangeTime;
+ pObjInfo->BirthTime = pCore->BirthTime;
+ pObjInfo->Attr.fMode = pCore->fAttrib;
+ pObjInfo->Attr.enmAdditional = enmAddAttr;
+
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_NOTHING: RT_FALL_THRU();
+ case RTFSOBJATTRADD_UNIX:
+ pObjInfo->Attr.u.Unix.uid = NIL_RTUID;
+ pObjInfo->Attr.u.Unix.gid = NIL_RTGID;
+ pObjInfo->Attr.u.Unix.cHardlinks = 1;
+ pObjInfo->Attr.u.Unix.INodeIdDevice = 0;
+ pObjInfo->Attr.u.Unix.INodeId = pCore->idINode;
+ pObjInfo->Attr.u.Unix.fFlags = 0;
+ pObjInfo->Attr.u.Unix.GenerationId = pCore->uVersion;
+ pObjInfo->Attr.u.Unix.Device = 0;
+ break;
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ pObjInfo->Attr.u.UnixOwner.uid = 0;
+ pObjInfo->Attr.u.UnixOwner.szName[0] = '\0';
+ break;
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ pObjInfo->Attr.u.UnixGroup.gid = 0;
+ pObjInfo->Attr.u.UnixGroup.szName[0] = '\0';
+ break;
+ case RTFSOBJATTRADD_EASIZE:
+ pObjInfo->Attr.u.EASize.cb = 0;
+ break;
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+
+ if ( pCore->fHaveRockInfo
+ && enmAddAttr != RTFSOBJATTRADD_NOTHING)
+ {
+ /** @todo Read the the rock info for this entry. */
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for rtFsIsoFile_Close and rtFsIsoDir_Close that does common work.
+ *
+ * @param pCore The common shared structure.
+ */
+static void rtFsIsoCore_Destroy(PRTFSISOCORE pCore)
+{
+ if (pCore->pParentDir)
+ rtFsIsoDirShrd_RemoveOpenChild(pCore->pParentDir, pCore);
+ if (pCore->paExtents)
+ {
+ RTMemFree(pCore->paExtents);
+ pCore->paExtents = NULL;
+ }
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsIsoFile_Close(void *pvThis)
+{
+ PRTFSISOFILEOBJ pThis = (PRTFSISOFILEOBJ)pvThis;
+ LogFlow(("rtFsIsoFile_Close(%p/%p)\n", pThis, pThis->pShared));
+
+ PRTFSISOFILESHRD pShared = pThis->pShared;
+ pThis->pShared = NULL;
+ if (pShared)
+ {
+ if (ASMAtomicDecU32(&pShared->Core.cRefs) == 0)
+ {
+ LogFlow(("rtFsIsoFile_Close: Destroying shared structure %p\n", pShared));
+ rtFsIsoCore_Destroy(&pShared->Core);
+ RTMemFree(pShared);
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsIsoFile_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSISOFILEOBJ pThis = (PRTFSISOFILEOBJ)pvThis;
+ return rtFsIsoCore_QueryInfo(&pThis->pShared->Core, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtFsIsoFile_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTFSISOFILEOBJ pThis = (PRTFSISOFILEOBJ)pvThis;
+ PRTFSISOFILESHRD pShared = pThis->pShared;
+ AssertReturn(pSgBuf->cSegs == 1, VERR_INTERNAL_ERROR_3);
+ RT_NOREF(fBlocking);
+
+#if 1
+ /* Apply default offset. */
+ if (off == -1)
+ off = pThis->offFile;
+ else
+ AssertReturn(off >= 0, VERR_INTERNAL_ERROR_3);
+
+ /* Do the read. */
+ size_t offDelta = 0;
+ int rc = rtFsIsoCore_ReadWorker(&pShared->Core, off, (uint8_t *)pSgBuf->paSegs[0].pvSeg,
+ pSgBuf->paSegs[0].cbSeg, pcbRead, &offDelta);
+
+ /* Update the file position and return. */
+ pThis->offFile = off + offDelta;
+ return rc;
+#else
+
+
+ /*
+ * Check for EOF.
+ */
+ if (off == -1)
+ off = pThis->offFile;
+ if ((uint64_t)off >= pShared->Core.cbObject)
+ {
+ if (pcbRead)
+ {
+ *pcbRead = 0;
+ return VINF_EOF;
+ }
+ return VERR_EOF;
+ }
+
+ if (pShared->Core.pVol->enmType == RTFSISOVOLTYPE_UDF)
+ {
+ return VERR_ISOFS_UDF_NOT_IMPLEMENTED;
+ }
+
+ /*
+ * Simple case: File has a single extent.
+ */
+ int rc = VINF_SUCCESS;
+ size_t cbRead = 0;
+ uint64_t cbFileLeft = pShared->Core.cbObject - (uint64_t)off;
+ size_t cbLeft = pSgBuf->paSegs[0].cbSeg;
+ uint8_t *pbDst = (uint8_t *)pSgBuf->paSegs[0].pvSeg;
+ if (pShared->Core.cExtents == 1)
+ {
+ if (cbLeft > 0)
+ {
+ size_t cbToRead = cbLeft;
+ if (cbToRead > cbFileLeft)
+ cbToRead = (size_t)cbFileLeft;
+ rc = RTVfsFileReadAt(pShared->Core.pVol->hVfsBacking, pShared->Core.FirstExtent.off + off, pbDst, cbToRead, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ off += cbToRead;
+ pbDst += cbToRead;
+ cbRead += cbToRead;
+ cbFileLeft -= cbToRead;
+ cbLeft -= cbToRead;
+ }
+ }
+ }
+ /*
+ * Complicated case: Work the file content extent by extent.
+ */
+ else
+ {
+ return VERR_NOT_IMPLEMENTED; /** @todo multi-extent stuff . */
+ }
+
+ /* Update the offset and return. */
+ pThis->offFile = off;
+ if (pcbRead)
+ *pcbRead = cbRead;
+ return VINF_SUCCESS;
+#endif
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtFsIsoFile_Flush(void *pvThis)
+{
+ RT_NOREF(pvThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
+ */
+static DECLCALLBACK(int) rtFsIsoFile_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr,
+ uint32_t *pfRetEvents)
+{
+ NOREF(pvThis);
+ int rc;
+ if (fEvents != RTPOLL_EVT_ERROR)
+ {
+ *pfRetEvents = fEvents & ~RTPOLL_EVT_ERROR;
+ rc = VINF_SUCCESS;
+ }
+ else if (fIntr)
+ rc = RTThreadSleep(cMillies);
+ else
+ {
+ uint64_t uMsStart = RTTimeMilliTS();
+ do
+ rc = RTThreadSleep(cMillies);
+ while ( rc == VERR_INTERRUPTED
+ && !fIntr
+ && RTTimeMilliTS() - uMsStart < cMillies);
+ if (rc == VERR_INTERRUPTED)
+ rc = VERR_TIMEOUT;
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtFsIsoFile_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTFSISOFILEOBJ pThis = (PRTFSISOFILEOBJ)pvThis;
+ *poffActual = pThis->offFile;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSeek}
+ */
+static DECLCALLBACK(int) rtFsIsoFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual)
+{
+ PRTFSISOFILEOBJ pThis = (PRTFSISOFILEOBJ)pvThis;
+ RTFOFF offNew;
+ switch (uMethod)
+ {
+ case RTFILE_SEEK_BEGIN:
+ offNew = offSeek;
+ break;
+ case RTFILE_SEEK_END:
+ offNew = (RTFOFF)pThis->pShared->Core.cbObject + offSeek;
+ break;
+ case RTFILE_SEEK_CURRENT:
+ offNew = (RTFOFF)pThis->offFile + offSeek;
+ break;
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+ if (offNew >= 0)
+ {
+ pThis->offFile = offNew;
+ *poffActual = offNew;
+ return VINF_SUCCESS;
+ }
+ return VERR_NEGATIVE_SEEK;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize}
+ */
+static DECLCALLBACK(int) rtFsIsoFile_QuerySize(void *pvThis, uint64_t *pcbFile)
+{
+ PRTFSISOFILEOBJ pThis = (PRTFSISOFILEOBJ)pvThis;
+ *pcbFile = pThis->pShared->Core.cbObject;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * ISO FS file operations.
+ */
+DECL_HIDDEN_CONST(const RTVFSFILEOPS) g_rtFsIsoFileOps =
+{
+ { /* Stream */
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FILE,
+ "FatFile",
+ rtFsIsoFile_Close,
+ rtFsIsoFile_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtFsIsoFile_Read,
+ NULL /*Write*/,
+ rtFsIsoFile_Flush,
+ rtFsIsoFile_PollOne,
+ rtFsIsoFile_Tell,
+ NULL /*pfnSkip*/,
+ NULL /*pfnZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+ },
+ RTVFSFILEOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj),
+ NULL /*SetMode*/,
+ NULL /*SetTimes*/,
+ NULL /*SetOwner*/,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtFsIsoFile_Seek,
+ rtFsIsoFile_QuerySize,
+ NULL /*SetSize*/,
+ NULL /*QueryMaxSize*/,
+ RTVFSFILEOPS_VERSION
+};
+
+
+/**
+ * Instantiates a new file, from ISO 9660 info.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO volume instance.
+ * @param pParentDir The parent directory (shared part).
+ * @param pDirRec The directory record.
+ * @param cDirRecs Number of directory records if more than one.
+ * @param offDirRec The byte offset of the directory record.
+ * @param offEntryInDir The byte offset of the directory entry in the parent
+ * directory.
+ * @param fOpen RTFILE_O_XXX flags.
+ * @param uVersion The file version number (since the caller already
+ * parsed the filename, we don't want to repeat the
+ * effort here).
+ * @param pRockInfo Optional rock ridge info for the file.
+ * @param phVfsFile Where to return the file handle.
+ */
+static int rtFsIsoFile_New9660(PRTFSISOVOL pThis, PRTFSISODIRSHRD pParentDir, PCISO9660DIRREC pDirRec, uint32_t cDirRecs,
+ uint64_t offDirRec, uint64_t fOpen, uint32_t uVersion, PCRTFSISOROCKINFO pRockInfo,
+ PRTVFSFILE phVfsFile)
+{
+ AssertPtr(pParentDir);
+
+ /*
+ * Create a VFS object.
+ */
+ PRTFSISOFILEOBJ pNewFile;
+ int rc = RTVfsNewFile(&g_rtFsIsoFileOps, sizeof(*pNewFile), fOpen, pThis->hVfsSelf, NIL_RTVFSLOCK /*use volume lock*/,
+ phVfsFile, (void **)&pNewFile);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Look for existing shared object, create a new one if necessary.
+ */
+ PRTFSISOFILESHRD pShared = (PRTFSISOFILESHRD)rtFsIsoDir_LookupShared(pParentDir, offDirRec);
+ if (pShared)
+ {
+ LogFlow(("rtFsIsoFile_New9660: cbObject=%#RX64 First Extent: off=%#RX64 cb=%#RX64\n",
+ pShared->Core.cbObject, pShared->Core.FirstExtent.off, pShared->Core.FirstExtent.cbExtent));
+ pNewFile->offFile = 0;
+ pNewFile->pShared = pShared;
+ return VINF_SUCCESS;
+ }
+
+ pShared = (PRTFSISOFILESHRD)RTMemAllocZ(sizeof(*pShared));
+ if (pShared)
+ {
+ rc = rtFsIsoCore_InitFrom9660DirRec(&pShared->Core, pDirRec, cDirRecs, offDirRec, uVersion, pRockInfo, pThis);
+ if (RT_SUCCESS(rc))
+ {
+ rtFsIsoDirShrd_AddOpenChild(pParentDir, &pShared->Core);
+ LogFlow(("rtFsIsoFile_New9660: cbObject=%#RX64 First Extent: off=%#RX64 cb=%#RX64\n",
+ pShared->Core.cbObject, pShared->Core.FirstExtent.off, pShared->Core.FirstExtent.cbExtent));
+ pNewFile->offFile = 0;
+ pNewFile->pShared = pShared;
+ return VINF_SUCCESS;
+ }
+ RTMemFree(pShared);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ /* Destroy the file object. */
+ pNewFile->offFile = 0;
+ pNewFile->pShared = NULL;
+ RTVfsFileRelease(*phVfsFile);
+ }
+ *phVfsFile = NIL_RTVFSFILE;
+ return rc;
+}
+
+
+/**
+ * Instantiates a new file, from UDF info.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO volume instance.
+ * @param pParentDir The parent directory (shared part).
+ * @param pFid The file ID descriptor. (Points to parent directory
+ * content.)
+ * @param fOpen RTFILE_O_XXX flags.
+ * @param phVfsFile Where to return the file handle.
+ */
+static int rtFsIsoFile_NewUdf(PRTFSISOVOL pThis, PRTFSISODIRSHRD pParentDir, PCUDFFILEIDDESC pFid,
+ uint64_t fOpen, PRTVFSFILE phVfsFile)
+{
+ AssertPtr(pParentDir);
+ uintptr_t const offInDir = (uintptr_t)pFid - (uintptr_t)pParentDir->pbDir;
+ Assert(offInDir < pParentDir->cbDir);
+ Assert(!(pFid->fFlags & UDF_FILE_FLAGS_DELETED));
+ Assert(!(pFid->fFlags & UDF_FILE_FLAGS_DIRECTORY));
+
+ /*
+ * Create a VFS object.
+ */
+ PRTFSISOFILEOBJ pNewFile;
+ int rc = RTVfsNewFile(&g_rtFsIsoFileOps, sizeof(*pNewFile), fOpen, pThis->hVfsSelf, NIL_RTVFSLOCK /*use volume lock*/,
+ phVfsFile, (void **)&pNewFile);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Look for existing shared object. Make sure it's a file.
+ */
+ PRTFSISOFILESHRD pShared = (PRTFSISOFILESHRD)rtFsIsoDir_LookupShared(pParentDir, offInDir);
+ if (pShared)
+ {
+ if (!RTFS_IS_FILE(pShared->Core.fAttrib))
+ {
+ LogFlow(("rtFsIsoFile_NewUdf: cbObject=%#RX64 First Extent: off=%#RX64 cb=%#RX64\n",
+ pShared->Core.cbObject, pShared->Core.FirstExtent.off, pShared->Core.FirstExtent.cbExtent));
+ pNewFile->offFile = 0;
+ pNewFile->pShared = pShared;
+ return VINF_SUCCESS;
+ }
+ }
+ /*
+ * Create a shared object for this alleged file.
+ */
+ else
+ {
+ pShared = (PRTFSISOFILESHRD)RTMemAllocZ(sizeof(*pShared));
+ if (pShared)
+ {
+ rc = rtFsIsoCore_InitFromUdfIcbAndFileIdDesc(&pShared->Core, &pFid->Icb, pFid, offInDir, pThis);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_FILE(pShared->Core.fAttrib))
+ {
+ rtFsIsoDirShrd_AddOpenChild(pParentDir, &pShared->Core);
+
+ LogFlow(("rtFsIsoFile_NewUdf: cbObject=%#RX64 First Extent: off=%#RX64 cb=%#RX64\n",
+ pShared->Core.cbObject, pShared->Core.FirstExtent.off, pShared->Core.FirstExtent.cbExtent));
+ pNewFile->offFile = 0;
+ pNewFile->pShared = pShared;
+ return VINF_SUCCESS;
+ }
+ rtFsIsoCore_Destroy(&pShared->Core);
+ }
+ RTMemFree(pShared);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ /* Destroy the file object. */
+ pNewFile->offFile = 0;
+ pNewFile->pShared = NULL;
+ RTVfsFileRelease(*phVfsFile);
+ }
+ *phVfsFile = NIL_RTVFSFILE;
+ return rc;
+}
+
+
+/**
+ * Looks up the shared structure for a child.
+ *
+ * @returns Referenced pointer to the shared structure, NULL if not found.
+ * @param pThis The directory.
+ * @param offDirRec The directory record offset of the child.
+ */
+static PRTFSISOCORE rtFsIsoDir_LookupShared(PRTFSISODIRSHRD pThis, uint64_t offDirRec)
+{
+ PRTFSISOCORE pCur;
+ RTListForEach(&pThis->OpenChildren, pCur, RTFSISOCORE, Entry)
+ {
+ if (pCur->offDirRec == offDirRec)
+ {
+ uint32_t cRefs = ASMAtomicIncU32(&pCur->cRefs);
+ Assert(cRefs > 1); RT_NOREF(cRefs);
+ return pCur;
+ }
+ }
+ return NULL;
+}
+
+
+#ifdef RT_STRICT
+/**
+ * Checks if @a pNext is an extent of @a pFirst.
+ *
+ * @returns true if @a pNext is the next extent, false if not
+ * @param pFirst The directory record describing the first or the
+ * previous extent.
+ * @param pNext The directory record alleged to be the next extent.
+ */
+DECLINLINE(bool) rtFsIsoDir_Is9660DirRecNextExtent(PCISO9660DIRREC pFirst, PCISO9660DIRREC pNext)
+{
+ if (RT_LIKELY(pNext->bFileIdLength == pFirst->bFileIdLength))
+ {
+ if (RT_LIKELY((pNext->fFileFlags | ISO9660_FILE_FLAGS_MULTI_EXTENT) == pFirst->fFileFlags))
+ {
+ if (RT_LIKELY(memcmp(pNext->achFileId, pFirst->achFileId, pNext->bFileIdLength) == 0))
+ return true;
+ }
+ }
+ return false;
+}
+#endif /* RT_STRICT */
+
+
+/**
+ * Parses rock ridge information if present in the directory entry.
+ *
+ * @param pVol The volume structure.
+ * @param pParseInfo Parse info and output.
+ * @param pbSys The system area of the directory record.
+ * @param cbSys The number of bytes present in the sys area.
+ * @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 rtFsIsoDirShrd_ParseRockRidgeData(PRTFSISOVOL pVol, PRTFSISOROCKINFO pParseInfo, uint8_t const *pbSys,
+ size_t cbSys, bool fIsFirstDirRec, bool fContinuationRecord)
+{
+ while (cbSys >= 4)
+ {
+ /*
+ * Check header length and advance the sys variables.
+ */
+ PCISO9660SUSPUNION pUnion = (PCISO9660SUSPUNION)pbSys;
+ if ( pUnion->Hdr.cbEntry > cbSys
+ || pUnion->Hdr.cbEntry < sizeof(pUnion->Hdr))
+ {
+ Log4(("rtFsIsoDir_ParseRockRidgeData: cbEntry=%#x cbSys=%#x (%#x %#x)\n",
+ pUnion->Hdr.cbEntry, cbSys, pUnion->Hdr.bSig1, pUnion->Hdr.bSig2));
+ break;
+ }
+ pbSys += pUnion->Hdr.cbEntry;
+ cbSys -= pUnion->Hdr.cbEntry;
+
+ /*
+ * Process fields.
+ */
+ uint16_t const uSig = SUSP_MAKE_SIG(pUnion->Hdr.bSig1, pUnion->Hdr.bSig2);
+ switch (uSig)
+ {
+ /*
+ * System use sharing protocol entries.
+ */
+ case SUSP_MAKE_SIG(ISO9660SUSPCE_SIG1, ISO9660SUSPCE_SIG2):
+ {
+ if (RT_BE2H_U32(pUnion->CE.offBlock.be) != RT_LE2H_U32(pUnion->CE.offBlock.le))
+ Log4(("rtFsIsoDir_ParseRockRidgeData: 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))
+ Log4(("rtFsIsoDir_ParseRockRidgeData: 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))
+ Log4(("rtFsIsoDir_ParseRockRidgeData: 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(pVol->abRockBuf) - (uint32_t)(offData & ISO9660_SECTOR_OFFSET_MASK))
+ {
+ RTCritSectEnter(&pVol->RockBufLock);
+
+ AssertCompile(sizeof(pVol->abRockBuf) == ISO9660_SECTOR_SIZE);
+ uint64_t offDataBlock = offData & ~(uint64_t)ISO9660_SECTOR_OFFSET_MASK;
+ if (pVol->offRockBuf == offDataBlock)
+ rtFsIsoDirShrd_ParseRockRidgeData(pVol, pParseInfo,
+ &pVol->abRockBuf[offData & ISO9660_SECTOR_OFFSET_MASK],
+ cbData, fIsFirstDirRec, true /*fContinuationRecord*/);
+ else
+ {
+ int rc = RTVfsFileReadAt(pVol->hVfsBacking, offDataBlock,
+ pVol->abRockBuf, sizeof(pVol->abRockBuf), NULL);
+ if (RT_SUCCESS(rc))
+ rtFsIsoDirShrd_ParseRockRidgeData(pVol, pParseInfo,
+ &pVol->abRockBuf[offData & ISO9660_SECTOR_OFFSET_MASK],
+ cbData, fIsFirstDirRec, true /*fContinuationRecord*/);
+ else
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Error reading continuation record at %#RX64: %Rrc\n",
+ offDataBlock, rc));
+ }
+
+ RTCritSectLeave(&pVol->RockBufLock);
+ }
+ else
+ Log4(("rtFsIsoDir_ParseRockRidgeData: continuation record isn't within a sector! offData=%#RX64 cbData=%#RX32\n",
+ cbData, offData));
+ }
+ else
+ Log4(("rtFsIsoDir_ParseRockRidgeData: nested continuation record!\n"));
+ break;
+ }
+
+ case SUSP_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]))
+ Log4(("rtFsIsoDir_ParseRockRidgeData: 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)
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Ignorining 'SP' entry in non-root directory record\n"));
+ else if (pParseInfo->fSuspSeenSP)
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Ignorining additional 'SP' entry\n"));
+ else
+ {
+ pVol->offSuspSkip = pUnion->SP.cbSkip;
+ if (pUnion->SP.cbSkip != 0)
+ Log4(("rtFsIsoDir_ParseRockRidgeData: SP: cbSkip=%#x\n", pUnion->SP.cbSkip));
+ }
+ break;
+
+ case SUSP_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)
+ Log4(("rtFsIsoDir_ParseRockRidgeData: 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)
+ Log4(("rtFsIsoDir_ParseRockRidgeData: 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) ))
+ {
+ Log4(("rtFsIsoDir_ParseRockRidgeData: 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]));
+ pVol->fHaveRock = true;
+ pParseInfo->cRockEntries++;
+ }
+ else
+ Log4(("rtFsIsoDir_ParseRockRidgeData: 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 SUSP_MAKE_SIG(ISO9660SUSPPD_SIG1, ISO9660SUSPPD_SIG2): /* PD - ignored */
+ case SUSP_MAKE_SIG(ISO9660SUSPST_SIG1, ISO9660SUSPST_SIG2): /* ST - ignore for now */
+ case SUSP_MAKE_SIG(ISO9660SUSPES_SIG1, ISO9660SUSPES_SIG2): /* ES - ignore for now */
+ break;
+
+ /*
+ * Rock ridge interchange protocol entries.
+ */
+ case SUSP_MAKE_SIG(ISO9660RRIPRR_SIG1, ISO9660RRIPRR_SIG2): /* RR */
+ if ( pUnion->RR.Hdr.cbEntry != ISO9660RRIPRR_LEN
+ || pUnion->RR.Hdr.bVersion != ISO9660RRIPRR_VER)
+ Log4(("rtFsIsoDir_ParseRockRidgeData: 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
+ pParseInfo->cRockEntries++; /* otherwise ignored */
+ break;
+
+ case SUSP_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)) )
+ Log4(("rtFsIsoDir_ParseRockRidgeData: 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(pParseInfo->Info.Attr.fMode))
+ pParseInfo->Info.Attr.fMode = ISO9660_GET_ENDIAN(&pUnion->PX.fMode);
+ else
+ Log4(("rtFsIsoDir_ParseRockRidgeData: 'PX' entry changes directory-ness: fMode=%#x, existing %#x; ignored\n",
+ ISO9660_GET_ENDIAN(&pUnion->PX.fMode), pParseInfo->Info.Attr.fMode));
+ pParseInfo->Info.Attr.u.Unix.cHardlinks = ISO9660_GET_ENDIAN(&pUnion->PX.cHardlinks);
+ pParseInfo->Info.Attr.u.Unix.uid = ISO9660_GET_ENDIAN(&pUnion->PX.uid);
+ pParseInfo->Info.Attr.u.Unix.gid = ISO9660_GET_ENDIAN(&pUnion->PX.gid);
+ /* ignore inode */
+ pParseInfo->cRockEntries++;
+ }
+ break;
+
+ case SUSP_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))
+ Log4(("rtFsIsoDir_ParseRockRidgeData: 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(pParseInfo->Info.Attr.fMode))
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Ignorning 'PN' entry for directory (%#x/%#x)\n",
+ ISO9660_GET_ENDIAN(&pUnion->PN.Major), ISO9660_GET_ENDIAN(&pUnion->PN.Minor) ));
+ else
+ {
+ pParseInfo->Info.Attr.u.Unix.Device = RTDEV_MAKE(ISO9660_GET_ENDIAN(&pUnion->PN.Major),
+ ISO9660_GET_ENDIAN(&pUnion->PN.Minor));
+ pParseInfo->cRockEntries++;
+ }
+ break;
+
+ case SUSP_MAKE_SIG(ISO9660RRIPTF_SIG1, ISO9660RRIPTF_SIG2): /* TF */
+ if ( pUnion->TF.Hdr.bVersion != ISO9660RRIPTF_VER
+ || pUnion->TF.Hdr.cbEntry < Iso9660RripTfCalcLength(pUnion->TF.fFlags))
+ Log4(("rtFsIsoDir_ParseRockRidgeData: 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)
+ {
+ rtFsIso9660DateTime2TimeSpec(&pParseInfo->Info.BirthTime, pTimestamp);
+ pTimestamp++;
+ }
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_MODIFY)
+ {
+ rtFsIso9660DateTime2TimeSpec(&pParseInfo->Info.ModificationTime, pTimestamp);
+ pTimestamp++;
+ }
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_ACCESS)
+ {
+ rtFsIso9660DateTime2TimeSpec(&pParseInfo->Info.AccessTime, pTimestamp);
+ pTimestamp++;
+ }
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_CHANGE)
+ {
+ rtFsIso9660DateTime2TimeSpec(&pParseInfo->Info.ChangeTime, pTimestamp);
+ pTimestamp++;
+ }
+ pParseInfo->cRockEntries++;
+ }
+ else
+ {
+ PCISO9660TIMESTAMP pTimestamp = (PCISO9660TIMESTAMP)&pUnion->TF.abPayload[0];
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_BIRTH)
+ {
+ rtFsIso9660DateTime2TimeSpecIfValid(&pParseInfo->Info.BirthTime, pTimestamp);
+ pTimestamp++;
+ }
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_MODIFY)
+ {
+ rtFsIso9660DateTime2TimeSpecIfValid(&pParseInfo->Info.ModificationTime, pTimestamp);
+ pTimestamp++;
+ }
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_ACCESS)
+ {
+ rtFsIso9660DateTime2TimeSpecIfValid(&pParseInfo->Info.AccessTime, pTimestamp);
+ pTimestamp++;
+ }
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_CHANGE)
+ {
+ rtFsIso9660DateTime2TimeSpecIfValid(&pParseInfo->Info.ChangeTime, pTimestamp);
+ pTimestamp++;
+ }
+ pParseInfo->cRockEntries++;
+ }
+ break;
+
+ case SUSP_MAKE_SIG(ISO9660RRIPSF_SIG1, ISO9660RRIPSF_SIG2): /* SF */
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Sparse file support not yet implemented!\n"));
+ break;
+
+ case SUSP_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) )
+ Log4(("rtFsIsoDir_ParseRockRidgeData: 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 (pParseInfo->fSeenLastSL)
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Unexpected 'SL!' entry\n"));
+ else
+ {
+ pParseInfo->cRockEntries++;
+ pParseInfo->fSeenLastSL = !(pUnion->SL.fFlags & ISO9660RRIP_SL_F_CONTINUE); /* used in loop */
+
+ size_t offDst = pParseInfo->cchLinkTarget;
+ 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)
+ {
+ Log4(("rtFsIsoDir_ParseRockRidgeData: 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)
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Malformed 'SL' component: CURRENT + %u bytes, ignoring bytes\n", cchCopy));
+ pszCopy = ".";
+ cchCopy = 1;
+ break;
+
+ case ISO9660RRIP_SL_C_PARENT:
+ if (cchCopy != 0)
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Malformed 'SL' component: PARENT + %u bytes, ignoring bytes\n", cchCopy));
+ pszCopy = "..";
+ cchCopy = 2;
+ break;
+
+ case ISO9660RRIP_SL_C_ROOT:
+ if (cchCopy != 0)
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Malformed 'SL' component: ROOT + %u bytes, ignoring bytes\n", cchCopy));
+ pszCopy = "/";
+ cchCopy = 1;
+ break;
+
+ default:
+ Log4(("rtFsIsoDir_ParseRockRidgeData: 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(pParseInfo->szLinkTarget))
+ {
+ memcpy(&pParseInfo->szLinkTarget[offDst], pszCopy, cchCopy);
+ offDst += cchCopy;
+ }
+ else
+ {
+ Log4(("rtFsIsoDir_ParseRockRidgeData: 'SL' constructs a too long target! '%.*s%.*s'\n",
+ offDst, pParseInfo->szLinkTarget, cchCopy, pszCopy));
+ memcpy(&pParseInfo->szLinkTarget[offDst], pszCopy, sizeof(pParseInfo->szLinkTarget) - offDst - 1);
+ offDst = sizeof(pParseInfo->szLinkTarget) - 1;
+ pParseInfo->fOverflowSL = true;
+ break;
+ }
+
+ /* Advance */
+ pbSrc += cbSkip;
+ cbSrcLeft -= cbSkip;
+
+ /* Append slash if appropriate. */
+ if ( !(fFlags & ISO9660RRIP_SL_C_CONTINUE)
+ && (cbSrcLeft >= 2 || !pParseInfo->fSeenLastSL) )
+ {
+ if (offDst + 1 < sizeof(pParseInfo->szLinkTarget))
+ pParseInfo->szLinkTarget[offDst++] = '/';
+ else
+ {
+ Log4(("rtFsIsoDir_ParseRockRidgeData: 'SL' constructs a too long target! '%.*s/'\n",
+ offDst, pParseInfo->szLinkTarget));
+ pParseInfo->fOverflowSL = true;
+ break;
+ }
+ }
+ }
+ Assert(offDst < sizeof(pParseInfo->szLinkTarget));
+ pParseInfo->szLinkTarget[offDst] = '\0';
+ pParseInfo->cchLinkTarget = (uint16_t)offDst;
+ }
+ break;
+
+ case SUSP_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) )
+ Log4(("rtFsIsoDir_ParseRockRidgeData: 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 (pParseInfo->fSeenLastNM)
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Unexpected 'NM' entry!\n"));
+ else
+ {
+ pParseInfo->cRockEntries++;
+ pParseInfo->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 && pParseInfo->szName[0] == '\0')
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Ignoring 'NM' entry for '.' and '..'\n"));
+ else
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Ignoring malformed 'NM' using '.' or '..': fFlags=%#x cchName=%#x %.*Rhxs; szRockNameBuf='%s'\n",
+ pUnion->NM.fFlags, cchName, cchName, pUnion->NM.achName, pParseInfo->szName));
+ pParseInfo->szName[0] = '\0';
+ pParseInfo->cchName = 0;
+ pParseInfo->fSeenLastNM = true;
+ }
+ else
+ {
+ size_t offDst = pParseInfo->cchName;
+ if (offDst + cchName < sizeof(pParseInfo->szName))
+ {
+ memcpy(&pParseInfo->szName[offDst], pUnion->NM.achName, cchName);
+ offDst += cchName;
+ pParseInfo->szName[offDst] = '\0';
+ pParseInfo->cchName = (uint16_t)offDst;
+ }
+ else
+ {
+ Log4(("rtFsIsoDir_ParseRockRidgeData: 'NM' constructs a too long name, ignoring it all: '%s%.*s'\n",
+ pParseInfo->szName, cchName, pUnion->NM.achName));
+ pParseInfo->szName[0] = '\0';
+ pParseInfo->cchName = 0;
+ pParseInfo->fSeenLastNM = true;
+ }
+ }
+ }
+ break;
+
+ case SUSP_MAKE_SIG(ISO9660RRIPCL_SIG1, ISO9660RRIPCL_SIG2): /* CL - just warn for now. */
+ case SUSP_MAKE_SIG(ISO9660RRIPPL_SIG1, ISO9660RRIPPL_SIG2): /* PL - just warn for now. */
+ case SUSP_MAKE_SIG(ISO9660RRIPRE_SIG1, ISO9660RRIPRE_SIG2): /* RE - just warn for now. */
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Ignorning directory relocation entry '%c%c'!\n", pUnion->Hdr.bSig1, pUnion->Hdr.bSig2));
+ break;
+
+ default:
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Unknown SUSP entry: %#x %#x, %#x bytes, v%u\n",
+ pUnion->Hdr.bSig1, pUnion->Hdr.bSig2, pUnion->Hdr.cbEntry, pUnion->Hdr.bVersion));
+ break;
+ }
+ }
+
+ /*
+ * Set the valid flag if we found anything of interest.
+ */
+ if (pParseInfo->cRockEntries > 1)
+ pParseInfo->fValid = true;
+}
+
+
+/**
+ * Initializes the rock info structure with info from the standard ISO-9660
+ * directory record.
+ *
+ * @param pRockInfo The structure to initialize.
+ * @param pDirRec The directory record to take basic data from.
+ */
+static void rtFsIsoDirShrd_InitRockInfo(PRTFSISOROCKINFO pRockInfo, PCISO9660DIRREC pDirRec)
+{
+ pRockInfo->fValid = false;
+ pRockInfo->fSuspSeenSP = false;
+ pRockInfo->fSeenLastNM = false;
+ pRockInfo->fSeenLastSL = false;
+ pRockInfo->fOverflowSL = false;
+ pRockInfo->cRockEntries = 0;
+ pRockInfo->cchName = 0;
+ pRockInfo->cchLinkTarget = 0;
+ pRockInfo->szName[0] = '\0';
+ pRockInfo->szName[sizeof(pRockInfo->szName) - 1] = '\0';
+ pRockInfo->szLinkTarget[0] = '\0';
+ pRockInfo->szLinkTarget[sizeof(pRockInfo->szLinkTarget) - 1] = '\0';
+ pRockInfo->Info.cbObject = ISO9660_GET_ENDIAN(&pDirRec->cbData);
+ pRockInfo->Info.cbAllocated = pRockInfo->Info.cbObject;
+ rtFsIso9660DateTime2TimeSpec(&pRockInfo->Info.AccessTime, &pDirRec->RecTime);
+ pRockInfo->Info.ModificationTime = pRockInfo->Info.AccessTime;
+ pRockInfo->Info.ChangeTime = pRockInfo->Info.AccessTime;
+ pRockInfo->Info.BirthTime = pRockInfo->Info.AccessTime;
+ pRockInfo->Info.Attr.fMode = pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY
+ ? RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY | 0555
+ : RTFS_TYPE_FILE | RTFS_DOS_ARCHIVED | 0444;
+ if (pDirRec->fFileFlags & ISO9660_FILE_FLAGS_HIDDEN)
+ pRockInfo->Info.Attr.fMode |= RTFS_DOS_HIDDEN;
+ pRockInfo->Info.Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
+ pRockInfo->Info.Attr.u.Unix.uid = NIL_RTUID;
+ pRockInfo->Info.Attr.u.Unix.gid = NIL_RTGID;
+ pRockInfo->Info.Attr.u.Unix.cHardlinks = 1;
+ pRockInfo->Info.Attr.u.Unix.INodeIdDevice = 0;
+ pRockInfo->Info.Attr.u.Unix.INodeId = 0;
+ pRockInfo->Info.Attr.u.Unix.fFlags = 0;
+ pRockInfo->Info.Attr.u.Unix.GenerationId = 0;
+ pRockInfo->Info.Attr.u.Unix.Device = 0;
+}
+
+
+static void rtFsIsoDirShrd_ParseRockForDirRec(PRTFSISODIRSHRD pThis, PCISO9660DIRREC pDirRec, PRTFSISOROCKINFO pRockInfo)
+{
+ rtFsIsoDirShrd_InitRockInfo(pRockInfo, pDirRec); /* Always! */
+
+ PRTFSISOVOL const pVol = pThis->Core.pVol;
+ uint8_t cbSys = pDirRec->cbDirRec - RT_UOFFSETOF(ISO9660DIRREC, achFileId)
+ - pDirRec->bFileIdLength - !(pDirRec->bFileIdLength & 1);
+ uint8_t const *pbSys = (uint8_t const *)&pDirRec->achFileId[pDirRec->bFileIdLength + !(pDirRec->bFileIdLength & 1)];
+ if (cbSys >= 4 + pVol->offSuspSkip)
+ {
+ pbSys += pVol->offSuspSkip;
+ cbSys -= pVol->offSuspSkip;
+ rtFsIsoDirShrd_ParseRockRidgeData(pVol, pRockInfo, pbSys, cbSys,
+ false /*fIsFirstDirRec*/, false /*fContinuationRecord*/);
+ }
+}
+
+
+static void rtFsIsoDirShrd_ParseRockForRoot(PRTFSISODIRSHRD pThis, PCISO9660DIRREC pDirRec)
+{
+ 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)
+ {
+ RTFSISOROCKINFO RockInfo;
+ rtFsIsoDirShrd_InitRockInfo(&RockInfo, pDirRec);
+ rtFsIsoDirShrd_ParseRockRidgeData(pThis->Core.pVol, &RockInfo, pbSys, cbSys,
+ true /*fIsFirstDirRec*/, false /*fContinuationRecord*/);
+ if (RockInfo.fValid)
+ {
+ pThis->Core.fHaveRockInfo = true;
+ pThis->Core.BirthTime = RockInfo.Info.BirthTime;
+ pThis->Core.ChangeTime = RockInfo.Info.ChangeTime;
+ pThis->Core.AccessTime = RockInfo.Info.AccessTime;
+ pThis->Core.ModificationTime = RockInfo.Info.ModificationTime;
+ if (RTFS_IS_DIRECTORY(RockInfo.Info.Attr.fMode))
+ pThis->Core.fAttrib = RockInfo.Info.Attr.fMode;
+ }
+ }
+}
+
+
+/**
+ * Compares rock ridge information if present in the directory entry.
+ *
+ * @param pThis The shared directory structure.
+ * @param pbSys The system area of the directory record.
+ * @param cbSys The number of bytes present in the sys area.
+ * @param pNameCmp The name comparsion data.
+ * @param fContinuationRecord Set if we're processing a continuation record in
+ * living in the abRockBuf.
+ */
+static int rtFsIsoDirShrd_CompareRockRidgeName(PRTFSISODIRSHRD pThis, uint8_t const *pbSys, size_t cbSys,
+ PRTFSISOROCKNAMECOMP pNameCmp, bool fContinuationRecord)
+{
+ PRTFSISOVOL const pVol = pThis->Core.pVol;
+
+ /*
+ * Do skipping if specified.
+ */
+ if (pVol->offSuspSkip)
+ {
+ if (cbSys <= pVol->offSuspSkip)
+ return fContinuationRecord ? VERR_MORE_DATA : VERR_MISMATCH;
+ pbSys += pVol->offSuspSkip;
+ cbSys -= pVol->offSuspSkip;
+ }
+
+ while (cbSys >= 4)
+ {
+ /*
+ * Check header length and advance the sys variables.
+ */
+ PCISO9660SUSPUNION pUnion = (PCISO9660SUSPUNION)pbSys;
+ if ( pUnion->Hdr.cbEntry > cbSys
+ && pUnion->Hdr.cbEntry < sizeof(pUnion->Hdr))
+ {
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: cbEntry=%#x cbSys=%#x (%#x %#x)\n",
+ pUnion->Hdr.cbEntry, cbSys, pUnion->Hdr.bSig1, pUnion->Hdr.bSig2));
+ break;
+ }
+ pbSys += pUnion->Hdr.cbEntry;
+ cbSys -= pUnion->Hdr.cbEntry;
+
+ /*
+ * Process the fields we need, nothing else.
+ */
+ uint16_t const uSig = SUSP_MAKE_SIG(pUnion->Hdr.bSig1, pUnion->Hdr.bSig2);
+
+
+ /*
+ * CE - continuation entry
+ */
+ if (uSig == SUSP_MAKE_SIG(ISO9660SUSPCE_SIG1, ISO9660SUSPCE_SIG2))
+ {
+ if (RT_BE2H_U32(pUnion->CE.offBlock.be) != RT_LE2H_U32(pUnion->CE.offBlock.le))
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: 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))
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: 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))
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: 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(pVol->abRockBuf) - (uint32_t)(offData & ISO9660_SECTOR_OFFSET_MASK))
+ {
+ RTCritSectEnter(&pVol->RockBufLock);
+
+ AssertCompile(sizeof(pVol->abRockBuf) == ISO9660_SECTOR_SIZE);
+ uint64_t offDataBlock = offData & ~(uint64_t)ISO9660_SECTOR_OFFSET_MASK;
+ int rc;
+ if (pVol->offRockBuf == offDataBlock)
+ rc = rtFsIsoDirShrd_CompareRockRidgeName(pThis, &pVol->abRockBuf[offData & ISO9660_SECTOR_OFFSET_MASK],
+ cbData, pNameCmp, true /*fContinuationRecord*/);
+ else
+ {
+ rc = RTVfsFileReadAt(pVol->hVfsBacking, offDataBlock, pVol->abRockBuf, sizeof(pVol->abRockBuf), NULL);
+ if (RT_SUCCESS(rc))
+ rc = rtFsIsoDirShrd_CompareRockRidgeName(pThis, &pVol->abRockBuf[offData & ISO9660_SECTOR_OFFSET_MASK],
+ cbData, pNameCmp, true /*fContinuationRecord*/);
+ else
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: Error reading continuation record at %#RX64: %Rrc\n",
+ offDataBlock, rc));
+ }
+
+ RTCritSectLeave(&pVol->RockBufLock);
+ if (rc != VERR_MORE_DATA)
+ return rc;
+ }
+ else
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: continuation record isn't within a sector! offData=%#RX64 cbData=%#RX32\n",
+ cbData, offData));
+ }
+ else
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: nested continuation record!\n"));
+ }
+ /*
+ * NM - Name entry.
+ *
+ * The character set is supposed to be limited to the portable filename
+ * character set defined in section 2.2.2.60 of POSIX.1: A-Za-z0-9._-
+ * If there are any other characters used, we consider them as UTF-8
+ * for reasons of simplicitiy, however we do not make any effort dealing
+ * with codepoint encodings across NM records for now because it is
+ * probably a complete waste of time.
+ */
+ else if (uSig == SUSP_MAKE_SIG(ISO9660RRIPNM_SIG1, ISO9660RRIPNM_SIG2))
+ {
+ if ( pUnion->NM.Hdr.bVersion != ISO9660RRIPNM_VER
+ || pUnion->NM.Hdr.cbEntry < RT_UOFFSETOF(ISO9660RRIPNM, achName)
+ || (pUnion->NM.fFlags & ISO9660RRIP_NM_F_RESERVED_MASK) )
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: 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
+ {
+ 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)))
+ { /* likely */ }
+ else
+ {
+ if (cchName == 0)
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: Ignoring 'NM' entry for '.' and '..'\n"));
+ else
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: Ignoring malformed 'NM' using '.' or '..': fFlags=%#x cchName=%#x %.*Rhxs\n",
+ pUnion->NM.fFlags, cchName, cchName, pUnion->NM.achName));
+ pNameCmp->offMatched = ~(size_t)0 / 2;
+ return VERR_MISMATCH;
+ }
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: 'NM': fFlags=%#x cchName=%#x '%.*s' (%.*Rhxs); offMatched=%#zx cchEntry=%#zx\n",
+ pUnion->NM.fFlags, cchName, cchName, pUnion->NM.achName, cchName, pUnion->NM.achName, pNameCmp->offMatched, pNameCmp->cchEntry));
+ AssertReturn(pNameCmp->offMatched < pNameCmp->cchEntry, VERR_MISMATCH);
+
+ if (RTStrNICmp(&pNameCmp->pszEntry[pNameCmp->offMatched], pUnion->NM.achName, cchName) == 0)
+ {
+ /** @todo Incorrectly ASSUMES all upper and lower codepoints have the same
+ * encoding length. However, since this shouldn't be UTF-8, but plain
+ * limited ASCII that's not really all that important. */
+ pNameCmp->offMatched += cchName;
+ if (!(pUnion->NM.fFlags & ISO9660RRIP_NM_F_CONTINUE))
+ {
+ if (pNameCmp->offMatched >= pNameCmp->cchEntry)
+ {
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: 'NM': returning VINF_SUCCESS\n"));
+ return VINF_SUCCESS;
+ }
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: 'NM': returning VERR_MISMATCH - %zu unmatched bytes\n",
+ pNameCmp->cchEntry - pNameCmp->offMatched));
+ return VERR_MISMATCH;
+ }
+ if (pNameCmp->offMatched >= pNameCmp->cchEntry)
+ {
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: 'NM': returning VERR_MISMATCH - match full name but ISO9660RRIP_NM_F_CONTINUE is set!\n"));
+ return VERR_MISMATCH;
+ }
+ }
+ else
+ {
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: 'NM': returning VERR_MISMATCH - mismatch\n"));
+ pNameCmp->offMatched = ~(size_t)0 / 2;
+ return VERR_MISMATCH;
+ }
+ }
+ }
+ }
+ return fContinuationRecord ? VERR_MORE_DATA : VERR_MISMATCH;
+}
+
+
+/**
+ * Worker for rtFsIsoDir_FindEntry9660 that compares a name with the rock ridge
+ * info in the directory record, if present.
+ *
+ * @returns true if equal, false if not.
+ * @param pThis The directory.
+ * @param pDirRec The directory record.
+ * @param pszEntry The string to compare with.
+ * @param cbEntry The length of @a pszEntry including terminator.
+ */
+static bool rtFsIsoDir_IsEntryEqualRock(PRTFSISODIRSHRD pThis, PCISO9660DIRREC pDirRec, const char *pszEntry, size_t cbEntry)
+{
+ /*
+ * Is there room for any rock ridge data?
+ */
+ 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)
+ {
+ RTFSISOROCKNAMECOMP NameCmp;
+ NameCmp.pszEntry = pszEntry;
+ NameCmp.cchEntry = cbEntry - 1;
+ NameCmp.offMatched = 0;
+ int rc = rtFsIsoDirShrd_CompareRockRidgeName(pThis, pbSys, cbSys, &NameCmp, false /*fContinuationRecord*/);
+ if (rc == VINF_SUCCESS)
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Worker for rtFsIsoDir_FindEntry9660 that compares a UTF-16BE name with a
+ * directory record.
+ *
+ * @returns true if equal, false if not.
+ * @param pDirRec The directory record.
+ * @param pwszEntry The UTF-16BE string to compare with.
+ * @param cbEntry The compare string length in bytes (sans zero
+ * terminator).
+ * @param cwcEntry The compare string length in RTUTF16 units.
+ * @param puVersion Where to return any file version number.
+ */
+DECL_FORCE_INLINE(bool) rtFsIsoDir_IsEntryEqualUtf16Big(PCISO9660DIRREC pDirRec, PCRTUTF16 pwszEntry, size_t cbEntry,
+ size_t cwcEntry, uint32_t *puVersion)
+{
+ /* ASSUME directories cannot have any version tags. */
+ if (pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY)
+ {
+ if (RT_LIKELY(pDirRec->bFileIdLength != cbEntry))
+ return false;
+ if (RT_LIKELY(RTUtf16BigNICmp((PCRTUTF16)pDirRec->achFileId, pwszEntry, cwcEntry) != 0))
+ return false;
+ }
+ else
+ {
+ size_t cbNameDelta = (size_t)pDirRec->bFileIdLength - cbEntry;
+ if (RT_LIKELY(cbNameDelta > (size_t)12 /* ;12345 */))
+ return false;
+ if (cbNameDelta == 0)
+ {
+ if (RT_LIKELY(RTUtf16BigNICmp((PCRTUTF16)pDirRec->achFileId, pwszEntry, cwcEntry) != 0))
+ return false;
+ *puVersion = 1;
+ }
+ else
+ {
+ if (RT_LIKELY(RT_MAKE_U16(pDirRec->achFileId[cbEntry + 1], pDirRec->achFileId[cbEntry]) != ';'))
+ return false;
+ if (RT_LIKELY(RTUtf16BigNICmp((PCRTUTF16)pDirRec->achFileId, pwszEntry, cwcEntry) != 0))
+ return false;
+ uint32_t uVersion;
+ size_t cwcVersion = rtFsIso9660GetVersionLengthUtf16Big((PCRTUTF16)pDirRec->achFileId,
+ pDirRec->bFileIdLength, &uVersion);
+ if (RT_LIKELY(cwcVersion * sizeof(RTUTF16) == cbNameDelta))
+ *puVersion = uVersion;
+ else
+ return false;
+ }
+ }
+
+ /* (No need to check for dot and dot-dot here, because cbEntry must be a
+ multiple of two.) */
+ Assert(!(cbEntry & 1));
+ return true;
+}
+
+
+/**
+ * Worker for rtFsIsoDir_FindEntry9660 that compares an ASCII name with a
+ * directory record.
+ *
+ * @returns true if equal, false if not.
+ * @param pDirRec The directory record.
+ * @param pszEntry The uppercased ASCII string to compare with.
+ * @param cchEntry The length of the compare string.
+ * @param puVersion Where to return any file version number.
+ *
+ * @note We're using RTStrNICmpAscii here because of non-conforming ISOs with
+ * entirely lowercase name or mixed cased names.
+ */
+DECL_FORCE_INLINE(bool) rtFsIsoDir_IsEntryEqualAscii(PCISO9660DIRREC pDirRec, const char *pszEntry, size_t cchEntry,
+ uint32_t *puVersion)
+{
+ /* ASSUME directories cannot have any version tags. */
+ if (pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY)
+ {
+ if (RT_LIKELY(pDirRec->bFileIdLength != cchEntry))
+ return false;
+ if (RT_LIKELY(RTStrNICmpAscii(pDirRec->achFileId, pszEntry, cchEntry) != 0))
+ return false;
+ }
+ else
+ {
+ size_t cchNameDelta = (size_t)pDirRec->bFileIdLength - cchEntry;
+ if (RT_LIKELY(cchNameDelta > (size_t)6 /* ;12345 */))
+ return false;
+ if (cchNameDelta == 0)
+ {
+ if (RT_LIKELY(RTStrNICmpAscii(pDirRec->achFileId, pszEntry, cchEntry) != 0))
+ return false;
+ *puVersion = 1;
+ }
+ else
+ {
+ if (RT_LIKELY(pDirRec->achFileId[cchEntry] != ';'))
+ return false;
+ if (RT_LIKELY(RTStrNICmpAscii(pDirRec->achFileId, pszEntry, cchEntry) != 0))
+ return false;
+ uint32_t uVersion;
+ size_t cchVersion = rtFsIso9660GetVersionLengthAscii(pDirRec->achFileId, pDirRec->bFileIdLength, &uVersion);
+ if (RT_LIKELY(cchVersion == cchNameDelta))
+ *puVersion = uVersion;
+ else
+ return false;
+ }
+ }
+
+ /* Don't match the 'dot' and 'dot-dot' directory records. */
+ if (RT_LIKELY( pDirRec->bFileIdLength != 1
+ || (uint8_t)pDirRec->achFileId[0] > (uint8_t)0x01))
+ return true;
+ return false;
+}
+
+
+/**
+ * Locates a directory entry in a directory.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_FILE_NOT_FOUND if not found.
+ * @param pThis The directory to search.
+ * @param pszEntry The entry to look for.
+ * @param poffDirRec Where to return the offset of the directory record
+ * on the disk.
+ * @param ppDirRec Where to return the pointer to the directory record
+ * (the whole directory is buffered).
+ * @param pcDirRecs Where to return the number of directory records
+ * related to this entry.
+ * @param pfMode Where to return the file type, rock ridge adjusted.
+ * @param puVersion Where to return the file version number.
+ * @param pRockInfo Where to return rock ridge info. This is NULL if
+ * the volume didn't advertise any rock ridge info.
+ */
+static int rtFsIsoDir_FindEntry9660(PRTFSISODIRSHRD pThis, const char *pszEntry, uint64_t *poffDirRec, PCISO9660DIRREC *ppDirRec,
+ uint32_t *pcDirRecs, PRTFMODE pfMode, uint32_t *puVersion, PRTFSISOROCKINFO pRockInfo)
+{
+ Assert(pThis->Core.pVol->enmType != RTFSISOVOLTYPE_UDF);
+
+ /* Set return values. */
+ *poffDirRec = UINT64_MAX;
+ *ppDirRec = NULL;
+ *pcDirRecs = 1;
+ *pfMode = UINT32_MAX;
+ *puVersion = 0;
+ if (pRockInfo)
+ pRockInfo->fValid = false;
+
+ /*
+ * If we're in UTF-16BE mode, convert the input name to UTF-16BE. Otherwise try
+ * uppercase it into a ISO 9660 compliant name.
+ */
+ int rc;
+ bool const fIsUtf16 = pThis->Core.pVol->fIsUtf16;
+ size_t cwcEntry = 0;
+ size_t cbEntry = 0;
+ size_t cchUpper = ~(size_t)0;
+ union
+ {
+ RTUTF16 wszEntry[260 + 1];
+ struct
+ {
+ char szUpper[255 + 1];
+ char szRock[260 + 1];
+ } s;
+ } uBuf;
+ if (fIsUtf16)
+ {
+ PRTUTF16 pwszEntry = uBuf.wszEntry;
+ rc = RTStrToUtf16BigEx(pszEntry, RTSTR_MAX, &pwszEntry, RT_ELEMENTS(uBuf.wszEntry), &cwcEntry);
+ if (RT_FAILURE(rc))
+ return rc == VERR_BUFFER_OVERFLOW ? VERR_FILENAME_TOO_LONG : rc;
+ cbEntry = cwcEntry * 2;
+ }
+ else
+ {
+ rc = RTStrCopy(uBuf.s.szUpper, sizeof(uBuf.s.szUpper), pszEntry);
+ if (RT_FAILURE(rc))
+ return rc == VERR_BUFFER_OVERFLOW ? VERR_FILENAME_TOO_LONG : rc;
+ RTStrToUpper(uBuf.s.szUpper);
+ cchUpper = strlen(uBuf.s.szUpper);
+ cbEntry = strlen(pszEntry) + 1;
+ }
+
+ /*
+ * Scan the directory buffer by buffer.
+ */
+ uint32_t offEntryInDir = 0;
+ uint32_t const cbDir = pThis->Core.cbObject;
+ while (offEntryInDir + RT_UOFFSETOF(ISO9660DIRREC, achFileId) <= cbDir)
+ {
+ PCISO9660DIRREC pDirRec = (PCISO9660DIRREC)&pThis->pbDir[offEntryInDir];
+
+ /* If null length, skip to the next sector. */
+ if (pDirRec->cbDirRec == 0)
+ offEntryInDir = (offEntryInDir + pThis->Core.pVol->cbSector) & ~(pThis->Core.pVol->cbSector - 1U);
+ else
+ {
+ /*
+ * Try match the filename.
+ */
+ /** @todo not sure if it's a great idea to match both name spaces... */
+ if (RT_LIKELY( fIsUtf16
+ ? !rtFsIsoDir_IsEntryEqualUtf16Big(pDirRec, uBuf.wszEntry, cbEntry, cwcEntry, puVersion)
+ && ( !pRockInfo
+ || !rtFsIsoDir_IsEntryEqualRock(pThis, pDirRec, pszEntry, cbEntry))
+ : ( !pRockInfo
+ || !rtFsIsoDir_IsEntryEqualRock(pThis, pDirRec, pszEntry, cbEntry))
+ && !rtFsIsoDir_IsEntryEqualAscii(pDirRec, uBuf.s.szUpper, cchUpper, puVersion) ))
+ {
+ /* Advance */
+ offEntryInDir += pDirRec->cbDirRec;
+ continue;
+ }
+
+ /*
+ * Get info for the entry.
+ */
+ if (!pRockInfo)
+ *pfMode = pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY
+ ? 0755 | RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY
+ : 0644 | RTFS_TYPE_FILE;
+ else
+ {
+ rtFsIsoDirShrd_ParseRockForDirRec(pThis, pDirRec, pRockInfo);
+ *pfMode = pRockInfo->Info.Attr.fMode;
+ }
+ *poffDirRec = pThis->Core.FirstExtent.off + offEntryInDir;
+ *ppDirRec = pDirRec;
+
+ /*
+ * Deal with the unlikely scenario of multi extent records.
+ */
+ if (!(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT))
+ *pcDirRecs = 1;
+ else
+ {
+ offEntryInDir += pDirRec->cbDirRec;
+
+ uint32_t cDirRecs = 1;
+ while (offEntryInDir + RT_UOFFSETOF(ISO9660DIRREC, achFileId) <= cbDir)
+ {
+ PCISO9660DIRREC pDirRec2 = (PCISO9660DIRREC)&pThis->pbDir[offEntryInDir];
+ if (pDirRec2->cbDirRec != 0)
+ {
+ Assert(rtFsIsoDir_Is9660DirRecNextExtent(pDirRec, pDirRec2));
+ cDirRecs++;
+ if (!(pDirRec2->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT))
+ break;
+ offEntryInDir += pDirRec2->cbDirRec;
+ }
+ else
+ offEntryInDir = (offEntryInDir + pThis->Core.pVol->cbSector) & ~(pThis->Core.pVol->cbSector - 1U);
+ }
+
+ *pcDirRecs = cDirRecs;
+ }
+ return VINF_SUCCESS;
+ }
+ }
+
+ return VERR_FILE_NOT_FOUND;
+}
+
+
+/**
+ * Locates a directory entry in a directory.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_FILE_NOT_FOUND if not found.
+ * @param pThis The directory to search.
+ * @param pszEntry The entry to look for.
+ * @param ppFid Where to return the pointer to the file ID entry.
+ * (Points to the directory content.)
+ */
+static int rtFsIsoDir_FindEntryUdf(PRTFSISODIRSHRD pThis, const char *pszEntry, PCUDFFILEIDDESC *ppFid)
+{
+ Assert(pThis->Core.pVol->enmType == RTFSISOVOLTYPE_UDF);
+ *ppFid = NULL;
+
+ /*
+ * Recode the entry name as 8-bit (if possible) and 16-bit strings.
+ * This also disposes of entries that definitely are too long.
+ */
+ size_t cb8Bit;
+ bool fSimple;
+ size_t cb16Bit;
+ size_t cwc16Bit;
+ uint8_t ab8Bit[255];
+ RTUTF16 wsz16Bit[255];
+
+ /* 16-bit */
+ PRTUTF16 pwsz16Bit = wsz16Bit;
+ int rc = RTStrToUtf16BigEx(pszEntry, RTSTR_MAX, &pwsz16Bit, RT_ELEMENTS(wsz16Bit), &cwc16Bit);
+ if (RT_SUCCESS(rc))
+ cb16Bit = 1 + cwc16Bit * sizeof(RTUTF16);
+ else
+ return rc == VERR_BUFFER_OVERFLOW ? VERR_FILENAME_TOO_LONG : rc;
+
+ /* 8-bit (can't possibly overflow) */
+ fSimple = true;
+ cb8Bit = 0;
+ const char *pszSrc = pszEntry;
+ for (;;)
+ {
+ RTUNICP uc;
+ int rc2 = RTStrGetCpEx(&pszSrc, &uc);
+ AssertRCReturn(rc2, rc2);
+ if (uc <= 0x7f)
+ {
+ if (uc)
+ ab8Bit[cb8Bit++] = (uint8_t)uc;
+ else
+ break;
+ }
+ else if (uc <= 0xff)
+ {
+ ab8Bit[cb8Bit++] = (uint8_t)uc;
+ fSimple = false;
+ }
+ else
+ {
+ cb8Bit = UINT32_MAX / 2;
+ break;
+ }
+ }
+ Assert(cb8Bit <= sizeof(ab8Bit) || cb8Bit == UINT32_MAX / 2);
+ cb8Bit++;
+
+ /*
+ * Scan the directory content.
+ */
+ uint32_t offDesc = 0;
+ uint32_t const cbDir = pThis->Core.cbObject;
+ while (offDesc + RT_UOFFSETOF(UDFFILEIDDESC, abImplementationUse) <= cbDir)
+ {
+ PCUDFFILEIDDESC pFid = (PCUDFFILEIDDESC)&pThis->pbDir[offDesc];
+ uint32_t const cbFid = UDFFILEIDDESC_GET_SIZE(pFid);
+ if ( offDesc + cbFid <= cbDir
+ && pFid->Tag.idTag == UDF_TAG_ID_FILE_ID_DESC)
+ { /* likely */ }
+ else
+ break;
+
+ uint8_t const *pbName = UDFFILEIDDESC_2_NAME(pFid);
+ if (*pbName == 16)
+ {
+ if (cb16Bit == pFid->cbName)
+ {
+ if (RTUtf16BigNICmp((PCRTUTF16)(&pbName[1]), wsz16Bit, cwc16Bit) == 0)
+ {
+ *ppFid = pFid;
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ else if (*pbName == 8)
+ {
+ if ( cb8Bit == pFid->cbName
+ && cb8Bit != UINT16_MAX)
+ {
+ if (fSimple)
+ {
+ if (RTStrNICmp((const char *)&pbName[1], (const char *)ab8Bit, cb8Bit - 1) == 0)
+ {
+ *ppFid = pFid;
+ return VINF_SUCCESS;
+ }
+ }
+ else
+ {
+ size_t cch = cb8Bit - 1;
+ size_t off;
+ for (off = 0; off < cch; off++)
+ {
+ RTUNICP uc1 = ab8Bit[off];
+ RTUNICP uc2 = pbName[off + 1];
+ if ( uc1 == uc2
+ || RTUniCpToLower(uc1) == RTUniCpToLower(uc2)
+ || RTUniCpToUpper(uc1) == RTUniCpToUpper(uc2))
+ { /* matches */ }
+ else
+ break;
+ }
+ if (off == cch)
+ {
+ *ppFid = pFid;
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ }
+
+ /* advance */
+ offDesc += cbFid;
+ }
+
+ return VERR_FILE_NOT_FOUND;
+}
+
+
+/**
+ * Releases a reference to a shared directory structure.
+ *
+ * @param pShared The shared directory structure.
+ */
+static void rtFsIsoDirShrd_Release(PRTFSISODIRSHRD pShared)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pShared->Core.cRefs);
+ Assert(cRefs < UINT32_MAX / 2);
+ if (cRefs == 0)
+ {
+ LogFlow(("rtFsIsoDirShrd_Release: Destroying shared structure %p\n", pShared));
+ Assert(pShared->Core.cRefs == 0);
+ if (pShared->pbDir)
+ {
+ RTMemFree(pShared->pbDir);
+ pShared->pbDir = NULL;
+ }
+ rtFsIsoCore_Destroy(&pShared->Core);
+ RTMemFree(pShared);
+ }
+}
+
+
+/**
+ * Retains a reference to a shared directory structure.
+ *
+ * @param pShared The shared directory structure.
+ */
+static void rtFsIsoDirShrd_Retain(PRTFSISODIRSHRD pShared)
+{
+ uint32_t cRefs = ASMAtomicIncU32(&pShared->Core.cRefs);
+ Assert(cRefs > 1); NOREF(cRefs);
+}
+
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsIsoDir_Close(void *pvThis)
+{
+ PRTFSISODIROBJ pThis = (PRTFSISODIROBJ)pvThis;
+ LogFlow(("rtFsIsoDir_Close(%p/%p)\n", pThis, pThis->pShared));
+
+ PRTFSISODIRSHRD pShared = pThis->pShared;
+ pThis->pShared = NULL;
+ if (pShared)
+ rtFsIsoDirShrd_Release(pShared);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsIsoDir_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSISODIROBJ pThis = (PRTFSISODIROBJ)pvThis;
+ return rtFsIsoCore_QueryInfo(&pThis->pShared->Core, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpen}
+ */
+static DECLCALLBACK(int) rtFsIsoDir_Open(void *pvThis, const char *pszEntry, uint64_t fOpen,
+ uint32_t fFlags, PRTVFSOBJ phVfsObj)
+{
+ PRTFSISODIROBJ pThis = (PRTFSISODIROBJ)pvThis;
+ PRTFSISODIRSHRD pShared = pThis->pShared;
+ int rc;
+
+ /*
+ * We cannot create or replace anything, just open stuff.
+ */
+ if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE)
+ { /* likely */ }
+ else
+ return VERR_WRITE_PROTECT;
+
+ /*
+ * Special cases '.' and '..'
+ */
+ if (pszEntry[0] == '.')
+ {
+ PRTFSISODIRSHRD pSharedToOpen;
+ if (pszEntry[1] == '\0')
+ pSharedToOpen = pShared;
+ else if (pszEntry[1] == '.' && pszEntry[2] == '\0')
+ {
+ pSharedToOpen = pShared->Core.pParentDir;
+ if (!pSharedToOpen)
+ pSharedToOpen = pShared;
+ }
+ else
+ pSharedToOpen = NULL;
+ if (pSharedToOpen)
+ {
+ if (fFlags & RTVFSOBJ_F_OPEN_DIRECTORY)
+ {
+ rtFsIsoDirShrd_Retain(pSharedToOpen);
+ RTVFSDIR hVfsDir;
+ rc = rtFsIsoDir_NewWithShared(pShared->Core.pVol, pSharedToOpen, &hVfsDir);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromDir(hVfsDir);
+ RTVfsDirRelease(hVfsDir);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ else
+ rc = VERR_IS_A_DIRECTORY;
+ return rc;
+ }
+ }
+
+ /*
+ * Try open whatever it is.
+ */
+ if (pShared->Core.pVol->enmType != RTFSISOVOLTYPE_UDF)
+ {
+
+ /*
+ * ISO 9660
+ */
+ PCISO9660DIRREC pDirRec;
+ uint64_t offDirRec;
+ uint32_t cDirRecs;
+ RTFMODE fMode;
+ uint32_t uVersion;
+ PRTFSISOROCKINFO pRockInfo = NULL;
+ if (pShared->Core.pVol->fHaveRock)
+ pRockInfo = (PRTFSISOROCKINFO)alloca(sizeof(*pRockInfo));
+ rc = rtFsIsoDir_FindEntry9660(pShared, pszEntry, &offDirRec, &pDirRec, &cDirRecs, &fMode, &uVersion, pRockInfo);
+ Log2(("rtFsIsoDir_Open: FindEntry9660(,%s,) -> %Rrc\n", pszEntry, rc));
+ if (RT_SUCCESS(rc))
+ {
+ switch (fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_FILE:
+ if (fFlags & RTVFSOBJ_F_OPEN_FILE)
+ {
+ RTVFSFILE hVfsFile;
+ rc = rtFsIsoFile_New9660(pShared->Core.pVol, pShared, pDirRec, cDirRecs, offDirRec, fOpen,
+ uVersion, pRockInfo && pRockInfo->fValid ? pRockInfo : NULL, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromFile(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ else
+ rc = VERR_IS_A_FILE;
+ break;
+
+ case RTFS_TYPE_DIRECTORY:
+ if (fFlags & RTVFSOBJ_F_OPEN_DIRECTORY)
+ {
+ RTVFSDIR hVfsDir;
+ rc = rtFsIsoDir_New9660(pShared->Core.pVol, pShared, pDirRec, cDirRecs, offDirRec,
+ pRockInfo && pRockInfo->fValid ? pRockInfo : NULL, &hVfsDir);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromDir(hVfsDir);
+ RTVfsDirRelease(hVfsDir);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ else
+ rc = VERR_IS_A_DIRECTORY;
+ break;
+
+ case RTFS_TYPE_SYMLINK:
+ case RTFS_TYPE_DEV_BLOCK:
+ case RTFS_TYPE_DEV_CHAR:
+ case RTFS_TYPE_FIFO:
+ case RTFS_TYPE_SOCKET:
+ case RTFS_TYPE_WHITEOUT:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+
+ default:
+ rc = VERR_PATH_NOT_FOUND;
+ break;
+ }
+ }
+ }
+ else
+ {
+ /*
+ * UDF
+ */
+ PCUDFFILEIDDESC pFid;
+ rc = rtFsIsoDir_FindEntryUdf(pShared, pszEntry, &pFid);
+ Log2(("rtFsIsoDir_Open: FindEntryUdf(,%s,) -> %Rrc\n", pszEntry, rc));
+ if (RT_SUCCESS(rc))
+ {
+ if (!(pFid->fFlags & UDF_FILE_FLAGS_DELETED))
+ {
+ if (!(pFid->fFlags & UDF_FILE_FLAGS_DIRECTORY))
+ {
+ if (fFlags & RTVFSOBJ_F_OPEN_FILE)
+ {
+ RTVFSFILE hVfsFile;
+ rc = rtFsIsoFile_NewUdf(pShared->Core.pVol, pShared, pFid, fOpen, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromFile(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ else
+ rc = VERR_IS_A_FILE;
+ }
+ else
+ {
+ if (fFlags & RTVFSOBJ_F_OPEN_DIRECTORY)
+ {
+ RTVFSDIR hVfsDir;
+ rc = rtFsIsoDir_NewUdf(pShared->Core.pVol, pShared, pFid, &hVfsDir);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromDir(hVfsDir);
+ RTVfsDirRelease(hVfsDir);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ else
+ rc = VERR_IS_A_DIRECTORY;
+ }
+ }
+ /* We treat UDF_FILE_FLAGS_DELETED like RTFS_TYPE_WHITEOUT for now. */
+ else
+ rc = VERR_PATH_NOT_FOUND;
+ }
+ }
+ return rc;
+
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnCreateDir}
+ */
+static DECLCALLBACK(int) rtFsIsoDir_CreateDir(void *pvThis, const char *pszSubDir, RTFMODE fMode, PRTVFSDIR phVfsDir)
+{
+ RT_NOREF(pvThis, pszSubDir, fMode, phVfsDir);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpenSymlink}
+ */
+static DECLCALLBACK(int) rtFsIsoDir_OpenSymlink(void *pvThis, const char *pszSymlink, PRTVFSSYMLINK phVfsSymlink)
+{
+ RT_NOREF(pvThis, pszSymlink, phVfsSymlink);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnCreateSymlink}
+ */
+static DECLCALLBACK(int) rtFsIsoDir_CreateSymlink(void *pvThis, const char *pszSymlink, const char *pszTarget,
+ RTSYMLINKTYPE enmType, PRTVFSSYMLINK phVfsSymlink)
+{
+ RT_NOREF(pvThis, pszSymlink, pszTarget, enmType, phVfsSymlink);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnUnlinkEntry}
+ */
+static DECLCALLBACK(int) rtFsIsoDir_UnlinkEntry(void *pvThis, const char *pszEntry, RTFMODE fType)
+{
+ RT_NOREF(pvThis, pszEntry, fType);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRenameEntry}
+ */
+static DECLCALLBACK(int) rtFsIsoDir_RenameEntry(void *pvThis, const char *pszEntry, RTFMODE fType, const char *pszNewName)
+{
+ RT_NOREF(pvThis, pszEntry, fType, pszNewName);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRewindDir}
+ */
+static DECLCALLBACK(int) rtFsIsoDir_RewindDir(void *pvThis)
+{
+ PRTFSISODIROBJ pThis = (PRTFSISODIROBJ)pvThis;
+ pThis->offDir = 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * The ISO 9660 worker for rtFsIsoDir_ReadDir
+ */
+static int rtFsIsoDir_ReadDir9660(PRTFSISODIROBJ pThis, PRTFSISODIRSHRD pShared, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry,
+ RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSISOROCKINFO pRockInfo = NULL;
+ if (pShared->Core.pVol->fHaveRock)
+ pRockInfo = (PRTFSISOROCKINFO)alloca(sizeof(*pRockInfo));
+
+ while (pThis->offDir + RT_UOFFSETOF(ISO9660DIRREC, achFileId) <= pShared->cbDir)
+ {
+ PCISO9660DIRREC pDirRec = (PCISO9660DIRREC)&pShared->pbDir[pThis->offDir];
+
+ /* If null length, skip to the next sector. */
+ if (pDirRec->cbDirRec == 0)
+ pThis->offDir = (pThis->offDir + pShared->Core.pVol->cbSector) & ~(pShared->Core.pVol->cbSector - 1U);
+ else
+ {
+ /*
+ * Do names first as they may cause overflows.
+ */
+ uint32_t uVersion = 0;
+ if ( pDirRec->bFileIdLength == 1
+ && pDirRec->achFileId[0] == '\0')
+ {
+ if (*pcbDirEntry < RT_UOFFSETOF(RTDIRENTRYEX, szName) + 2)
+ {
+ *pcbDirEntry = RT_UOFFSETOF(RTDIRENTRYEX, szName) + 2;
+ Log3(("rtFsIsoDir_ReadDir9660: VERR_BUFFER_OVERFLOW (dot)\n"));
+ return VERR_BUFFER_OVERFLOW;
+ }
+ pDirEntry->cbName = 1;
+ pDirEntry->szName[0] = '.';
+ pDirEntry->szName[1] = '\0';
+ }
+ else if ( pDirRec->bFileIdLength == 1
+ && pDirRec->achFileId[0] == '\1')
+ {
+ if (*pcbDirEntry < RT_UOFFSETOF(RTDIRENTRYEX, szName) + 3)
+ {
+ *pcbDirEntry = RT_UOFFSETOF(RTDIRENTRYEX, szName) + 3;
+ Log3(("rtFsIsoDir_ReadDir9660: VERR_BUFFER_OVERFLOW (dot-dot)\n"));
+ return VERR_BUFFER_OVERFLOW;
+ }
+ pDirEntry->cbName = 2;
+ pDirEntry->szName[0] = '.';
+ pDirEntry->szName[1] = '.';
+ pDirEntry->szName[2] = '\0';
+ }
+ else if (pShared->Core.pVol->fIsUtf16)
+ {
+ PCRTUTF16 pawcSrc = (PCRTUTF16)&pDirRec->achFileId[0];
+ size_t cwcSrc = pDirRec->bFileIdLength / sizeof(RTUTF16);
+ size_t cwcVer = !(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY)
+ ? rtFsIso9660GetVersionLengthUtf16Big(pawcSrc, cwcSrc, &uVersion) : 0;
+ size_t cchNeeded = 0;
+ size_t cbDst = *pcbDirEntry - RT_UOFFSETOF(RTDIRENTRYEX, szName);
+ char *pszDst = pDirEntry->szName;
+
+ int rc = RTUtf16BigToUtf8Ex(pawcSrc, cwcSrc - cwcVer, &pszDst, cbDst, &cchNeeded);
+ if (RT_SUCCESS(rc))
+ pDirEntry->cbName = (uint16_t)cchNeeded;
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ *pcbDirEntry = RT_UOFFSETOF(RTDIRENTRYEX, szName) + cchNeeded + 1;
+ Log3(("rtFsIsoDir_ReadDir9660: VERR_BUFFER_OVERFLOW - cbDst=%zu cchNeeded=%zu (UTF-16BE)\n", cbDst, cchNeeded));
+ return VERR_BUFFER_OVERFLOW;
+ }
+ else
+ {
+ ssize_t cchNeeded2 = RTStrPrintf2(pszDst, cbDst, "bad-name-%#x", pThis->offDir);
+ if (cchNeeded2 >= 0)
+ pDirEntry->cbName = (uint16_t)cchNeeded2;
+ else
+ {
+ *pcbDirEntry = RT_UOFFSETOF(RTDIRENTRYEX, szName) + (size_t)-cchNeeded2;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ }
+ }
+ else
+ {
+ /* This is supposed to be upper case ASCII, however, purge the encoding anyway. */
+ size_t cchVer = !(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY)
+ ? rtFsIso9660GetVersionLengthAscii(pDirRec->achFileId, pDirRec->bFileIdLength, &uVersion) : 0;
+ size_t cchName = pDirRec->bFileIdLength - cchVer;
+ size_t cbNeeded = RT_UOFFSETOF(RTDIRENTRYEX, szName) + cchName + 1;
+ if (*pcbDirEntry < cbNeeded)
+ {
+ Log3(("rtFsIsoDir_ReadDir9660: VERR_BUFFER_OVERFLOW - cbDst=%zu cbNeeded=%zu (ASCII)\n", *pcbDirEntry, cbNeeded));
+ *pcbDirEntry = cbNeeded;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ pDirEntry->cbName = (uint16_t)cchName;
+ memcpy(pDirEntry->szName, pDirRec->achFileId, cchName);
+ pDirEntry->szName[cchName] = '\0';
+ RTStrPurgeEncoding(pDirEntry->szName);
+ }
+ pDirEntry->cwcShortName = 0;
+ pDirEntry->wszShortName[0] = '\0';
+
+ /*
+ * To avoid duplicating code in rtFsIsoCore_InitFrom9660DirRec and
+ * rtFsIsoCore_QueryInfo, we create a dummy RTFSISOCORE on the stack.
+ */
+ RTFSISOCORE TmpObj;
+ RT_ZERO(TmpObj);
+ rtFsIsoCore_InitFrom9660DirRec(&TmpObj, pDirRec, 1 /* cDirRecs - see below why 1 */,
+ pThis->offDir + pShared->Core.FirstExtent.off, uVersion, NULL, pShared->Core.pVol);
+ int rc = rtFsIsoCore_QueryInfo(&TmpObj, &pDirEntry->Info, enmAddAttr);
+
+ /*
+ * Look for rock ridge info associated with this entry
+ * and merge that into the record.
+ */
+ if (pRockInfo)
+ {
+ rtFsIsoDirShrd_ParseRockForDirRec(pShared, pDirRec, pRockInfo);
+ if (pRockInfo->fValid)
+ {
+ if ( pRockInfo->fSeenLastNM
+ && pRockInfo->cchName > 0
+ && !pShared->Core.pVol->fIsUtf16
+ && ( pDirRec->bFileIdLength != 1
+ || ( pDirRec->achFileId[0] != '\0' /* . */
+ && pDirRec->achFileId[0] != '\1'))) /* .. */
+ {
+ size_t const cchName = pRockInfo->cchName;
+ Assert(strlen(pRockInfo->szName) == cchName);
+ size_t const cbNeeded = RT_UOFFSETOF(RTDIRENTRYEX, szName) + cchName + 1;
+ if (*pcbDirEntry < cbNeeded)
+ {
+ Log3(("rtFsIsoDir_ReadDir9660: VERR_BUFFER_OVERFLOW - cbDst=%zu cbNeeded=%zu (Rock)\n", *pcbDirEntry, cbNeeded));
+ *pcbDirEntry = cbNeeded;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ pDirEntry->cbName = (uint16_t)cchName;
+ memcpy(pDirEntry->szName, pRockInfo->szName, cchName);
+ pDirEntry->szName[cchName] = '\0';
+
+ RTStrPurgeEncoding(pDirEntry->szName);
+ }
+ }
+ }
+
+ /*
+ * Update the directory location and handle multi extent records.
+ *
+ * Multi extent records only affect the file size and the directory location,
+ * so we deal with it here instead of involving rtFsIsoCore_InitFrom9660DirRec
+ * which would potentially require freeing memory and such.
+ */
+ if (!(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT))
+ {
+ Log3(("rtFsIsoDir_ReadDir9660: offDir=%#07x: %s (rc=%Rrc)\n", pThis->offDir, pDirEntry->szName, rc));
+ pThis->offDir += pDirRec->cbDirRec;
+ }
+ else
+ {
+ uint32_t cExtents = 1;
+ uint32_t offDir = pThis->offDir + pDirRec->cbDirRec;
+ while (offDir + RT_UOFFSETOF(ISO9660DIRREC, achFileId) <= pShared->cbDir)
+ {
+ PCISO9660DIRREC pDirRec2 = (PCISO9660DIRREC)&pShared->pbDir[offDir];
+ if (pDirRec2->cbDirRec != 0)
+ {
+ pDirEntry->Info.cbObject += ISO9660_GET_ENDIAN(&pDirRec2->cbData);
+ offDir += pDirRec2->cbDirRec;
+ cExtents++;
+ if (!(pDirRec2->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT))
+ break;
+ }
+ else
+ offDir = (offDir + pShared->Core.pVol->cbSector) & ~(pShared->Core.pVol->cbSector - 1U);
+ }
+ Log3(("rtFsIsoDir_ReadDir9660: offDir=%#07x, %u extents ending at %#07x: %s (rc=%Rrc)\n",
+ pThis->offDir, cExtents, offDir, pDirEntry->szName, rc));
+ pThis->offDir = offDir;
+ }
+
+ return rc;
+ }
+ }
+
+ Log3(("rtFsIsoDir_ReadDir9660: offDir=%#07x: VERR_NO_MORE_FILES\n", pThis->offDir));
+ return VERR_NO_MORE_FILES;
+}
+
+
+/**
+ * The UDF worker for rtFsIsoDir_ReadDir
+ */
+static int rtFsIsoDir_ReadDirUdf(PRTFSISODIROBJ pThis, PRTFSISODIRSHRD pShared, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry,
+ RTFSOBJATTRADD enmAddAttr)
+{
+ /*
+ * At offset zero we've got the '.' entry. This has to be generated
+ * manually as it's not part of the directory content. The directory
+ * offset has to be faked for this too, so offDir == 0 indicates the '.'
+ * entry whereas offDir == 1 is the first file id descriptor.
+ */
+ if (pThis->offDir == 0)
+ {
+ if (*pcbDirEntry < RT_UOFFSETOF(RTDIRENTRYEX, szName) + 2)
+ {
+ *pcbDirEntry = RT_UOFFSETOF(RTDIRENTRYEX, szName) + 2;
+ Log3(("rtFsIsoDir_ReadDirUdf: VERR_BUFFER_OVERFLOW (dot)\n"));
+ return VERR_BUFFER_OVERFLOW;
+ }
+ pDirEntry->cbName = 1;
+ pDirEntry->szName[0] = '.';
+ pDirEntry->szName[1] = '\0';
+ pDirEntry->cwcShortName = 0;
+ pDirEntry->wszShortName[0] = '\0';
+
+ int rc = rtFsIsoCore_QueryInfo(&pShared->Core, &pDirEntry->Info, enmAddAttr);
+
+ Log3(("rtFsIsoDir_ReadDirUdf: offDir=%#07x: %s (rc=%Rrc)\n", pThis->offDir, pDirEntry->szName, rc));
+ pThis->offDir = 1;
+ return rc;
+ }
+
+ /*
+ * Do the directory content.
+ */
+ while (pThis->offDir + RT_UOFFSETOF(UDFFILEIDDESC, abImplementationUse) <= pShared->cbDir + 1)
+ {
+ PCUDFFILEIDDESC pFid = (PCUDFFILEIDDESC)&pShared->pbDir[pThis->offDir - 1];
+ uint32_t const cbFid = UDFFILEIDDESC_GET_SIZE(pFid);
+
+ if (pThis->offDir + cbFid <= pShared->cbDir + 1)
+ { /* likely */ }
+ else
+ break;
+
+ /*
+ * Do names first as they may cause overflows.
+ */
+ if (pFid->cbName > 1)
+ {
+ uint8_t const *pbName = UDFFILEIDDESC_2_NAME(pFid);
+ uint32_t cbSrc = pFid->cbName;
+ if (*pbName == 8)
+ {
+ /* Figure out the UTF-8 length first. */
+ bool fSimple = true;
+ uint32_t cchDst = 0;
+ for (uint32_t offSrc = 1; offSrc < cbSrc; offSrc++)
+ if (!(pbName[offSrc] & 0x80))
+ cchDst++;
+ else
+ {
+ cchDst += 2;
+ fSimple = false;
+ }
+
+ size_t cbNeeded = RT_UOFFSETOF(RTDIRENTRYEX, szName) + cchDst + 1;
+ if (*pcbDirEntry >= cbNeeded)
+ {
+ if (fSimple)
+ {
+ Assert(cbSrc - 1 == cchDst);
+ memcpy(pDirEntry->szName, &pbName[1], cchDst);
+ pDirEntry->szName[cchDst] = '\0';
+ }
+ else
+ {
+ char *pszDst = pDirEntry->szName;
+ for (uint32_t offSrc = 1; offSrc < cbSrc; offSrc++)
+ pszDst = RTStrPutCp(pszDst, pbName[offSrc]);
+ *pszDst = '\0';
+ Assert((size_t)(pszDst - &pDirEntry->szName[0]) == cchDst);
+ }
+ }
+ else
+ {
+ Log3(("rtFsIsoDir_ReadDirUdf: VERR_BUFFER_OVERFLOW - cbDst=%zu cbNeeded=%zu (8-bit)\n", *pcbDirEntry, cbNeeded));
+ *pcbDirEntry = cbNeeded;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ }
+ else
+ {
+ /* Let RTUtf16BigToUtf8Ex do the bounds checking. */
+ char *pszDst = pDirEntry->szName;
+ size_t cbDst = *pcbDirEntry - RT_UOFFSETOF(RTDIRENTRYEX, szName);
+ size_t cchNeeded = 0;
+ int rc;
+ if (*pbName == 16)
+ rc = RTUtf16BigToUtf8Ex((PCRTUTF16)(pbName + 1), (cbSrc - 1) / sizeof(RTUTF16), &pszDst, cbDst, &cchNeeded);
+ else
+ rc = VERR_INVALID_NAME;
+ if (RT_SUCCESS(rc))
+ pDirEntry->cbName = (uint16_t)cchNeeded;
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ *pcbDirEntry = RT_UOFFSETOF(RTDIRENTRYEX, szName) + cchNeeded + 1;
+ Log3(("rtFsIsoDir_ReadDirUdf: VERR_BUFFER_OVERFLOW - cbDst=%zu cchNeeded=%zu (16-bit)\n", cbDst, cchNeeded));
+ return VERR_BUFFER_OVERFLOW;
+ }
+ else
+ {
+ LogRelMax(90, ("ISO/UDF: Malformed directory entry name at %#x: %.*Rhxs\n", pThis->offDir - 1, cbSrc, pbName));
+ ssize_t cchNeeded2 = RTStrPrintf2(pszDst, cbDst, "bad-name-%#x", pThis->offDir - 1);
+ if (cchNeeded2 >= 0)
+ pDirEntry->cbName = (uint16_t)cchNeeded2;
+ else
+ {
+ *pcbDirEntry = RT_UOFFSETOF(RTDIRENTRYEX, szName) + (size_t)-cchNeeded2;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ }
+ }
+ }
+ else if (pFid->fFlags & UDF_FILE_FLAGS_PARENT)
+ {
+ size_t cbNeeded = RT_UOFFSETOF(RTDIRENTRYEX, szName) + 2 + 1;
+ if (*pcbDirEntry < cbNeeded)
+ {
+ Log3(("rtFsIsoDir_ReadDirUdf: VERR_BUFFER_OVERFLOW - cbDst=%zu cbNeeded=%zu (dot-dot)\n", *pcbDirEntry, cbNeeded));
+ *pcbDirEntry = cbNeeded;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ pDirEntry->cbName = 2;
+ pDirEntry->szName[0] = '.';
+ pDirEntry->szName[1] = '.';
+ pDirEntry->szName[2] = '\0';
+ }
+ else
+ {
+ size_t cbNeeded = RT_UOFFSETOF(RTDIRENTRYEX, szName) + 1;
+ if (*pcbDirEntry < cbNeeded)
+ {
+ Log3(("rtFsIsoDir_ReadDirUdf: VERR_BUFFER_OVERFLOW - cbDst=%zu cbNeeded=%zu (empty)\n", *pcbDirEntry, cbNeeded));
+ *pcbDirEntry = cbNeeded;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ pDirEntry->cbName = 0;
+ pDirEntry->szName[0] = '\0';
+ }
+
+ pDirEntry->cwcShortName = 0;
+ pDirEntry->wszShortName[0] = '\0';
+
+ /*
+ * To avoid duplicating code in rtFsIsoCore_InitUdf and
+ * rtFsIsoCore_QueryInfo, we create a dummy RTFSISOCORE on the stack.
+ */
+ RTFSISOCORE TmpObj;
+ RT_ZERO(TmpObj);
+ int rc = rtFsIsoCore_InitFromUdfIcbAndFileIdDesc(&TmpObj, &pFid->Icb, pFid, pThis->offDir - 1, pShared->Core.pVol);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsIsoCore_QueryInfo(&TmpObj, &pDirEntry->Info, enmAddAttr);
+ rtFsIsoCore_Destroy(&TmpObj);
+ }
+
+ /*
+ * Update.
+ */
+ Log3(("rtFsIsoDir_ReadDirUdf: offDir=%#07x: %s (rc=%Rrc)\n", pThis->offDir, pDirEntry->szName, rc));
+ pThis->offDir += cbFid;
+
+ return rc;
+ }
+
+ Log3(("rtFsIsoDir_ReadDirUdf: offDir=%#07x: VERR_NO_MORE_FILES\n", pThis->offDir));
+ return VERR_NO_MORE_FILES;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnReadDir}
+ */
+static DECLCALLBACK(int) rtFsIsoDir_ReadDir(void *pvThis, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry,
+ RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSISODIROBJ pThis = (PRTFSISODIROBJ)pvThis;
+ PRTFSISODIRSHRD pShared = pThis->pShared;
+ int rc;
+ if (pShared->Core.pVol->enmType != RTFSISOVOLTYPE_UDF)
+ rc = rtFsIsoDir_ReadDir9660(pThis, pShared, pDirEntry, pcbDirEntry, enmAddAttr);
+ else
+ rc = rtFsIsoDir_ReadDirUdf(pThis, pShared, pDirEntry, pcbDirEntry, enmAddAttr);
+ return rc;
+}
+
+
+/**
+ * ISO file operations.
+ */
+static const RTVFSDIROPS g_rtFsIsoDirOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_DIR,
+ "ISO 9660 Dir",
+ rtFsIsoDir_Close,
+ rtFsIsoDir_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSDIROPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSDIROPS, ObjSet) - RT_UOFFSETOF(RTVFSDIROPS, Obj),
+ NULL /*SetMode*/,
+ NULL /*SetTimes*/,
+ NULL /*SetOwner*/,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtFsIsoDir_Open,
+ NULL /* pfnFollowAbsoluteSymlink */,
+ NULL /* pfnOpenFile */,
+ NULL /* pfnOpenDir */,
+ rtFsIsoDir_CreateDir,
+ rtFsIsoDir_OpenSymlink,
+ rtFsIsoDir_CreateSymlink,
+ NULL /* pfnQueryEntryInfo */,
+ rtFsIsoDir_UnlinkEntry,
+ rtFsIsoDir_RenameEntry,
+ rtFsIsoDir_RewindDir,
+ rtFsIsoDir_ReadDir,
+ RTVFSDIROPS_VERSION,
+};
+
+
+/**
+ * Adds an open child to the parent directory's shared structure.
+ *
+ * Maintains an additional reference to the parent dir to prevent it from going
+ * away. If @a pDir is the root directory, it also ensures the volume is
+ * referenced and sticks around until the last open object is gone.
+ *
+ * @param pDir The directory.
+ * @param pChild The child being opened.
+ * @sa rtFsIsoDirShrd_RemoveOpenChild
+ */
+static void rtFsIsoDirShrd_AddOpenChild(PRTFSISODIRSHRD pDir, PRTFSISOCORE pChild)
+{
+ rtFsIsoDirShrd_Retain(pDir);
+
+ RTListAppend(&pDir->OpenChildren, &pChild->Entry);
+ pChild->pParentDir = pDir;
+}
+
+
+/**
+ * Removes an open child to the parent directory.
+ *
+ * @param pDir The directory.
+ * @param pChild The child being removed.
+ *
+ * @remarks This is the very last thing you do as it may cause a few other
+ * objects to be released recursively (parent dir and the volume).
+ *
+ * @sa rtFsIsoDirShrd_AddOpenChild
+ */
+static void rtFsIsoDirShrd_RemoveOpenChild(PRTFSISODIRSHRD pDir, PRTFSISOCORE pChild)
+{
+ AssertReturnVoid(pChild->pParentDir == pDir);
+ RTListNodeRemove(&pChild->Entry);
+ pChild->pParentDir = NULL;
+
+ rtFsIsoDirShrd_Release(pDir);
+}
+
+
+#ifdef LOG_ENABLED
+/**
+ * Logs the content of a directory.
+ */
+static void rtFsIsoDirShrd_Log9660Content(PRTFSISODIRSHRD pThis)
+{
+ if (LogIs2Enabled())
+ {
+ uint32_t offRec = 0;
+ while (offRec < pThis->cbDir)
+ {
+ PCISO9660DIRREC pDirRec = (PCISO9660DIRREC)&pThis->pbDir[offRec];
+ if (pDirRec->cbDirRec == 0)
+ break;
+
+ RTUTF16 wszName[128];
+ if (pThis->Core.pVol->fIsUtf16)
+ {
+ PRTUTF16 pwszDst = &wszName[pDirRec->bFileIdLength / sizeof(RTUTF16)];
+ PCRTUTF16 pwszSrc = (PCRTUTF16)&pDirRec->achFileId[pDirRec->bFileIdLength];
+ pwszSrc--;
+ *pwszDst-- = '\0';
+ while ((uintptr_t)pwszDst >= (uintptr_t)&wszName[0])
+ {
+ *pwszDst = RT_BE2H_U16(*pwszSrc);
+ pwszDst--;
+ pwszSrc--;
+ }
+ }
+ else
+ {
+ PRTUTF16 pwszDst = wszName;
+ for (uint32_t off = 0; off < pDirRec->bFileIdLength; off++)
+ *pwszDst++ = pDirRec->achFileId[off];
+ *pwszDst = '\0';
+ }
+
+ Log2(("ISO9660: %04x: rec=%#x ea=%#x cb=%#010RX32 off=%#010RX32 fl=%#04x %04u-%02u-%02u %02u:%02u:%02u%+03d unit=%#x igap=%#x idVol=%#x '%ls'\n",
+ offRec,
+ pDirRec->cbDirRec,
+ pDirRec->cExtAttrBlocks,
+ ISO9660_GET_ENDIAN(&pDirRec->cbData),
+ ISO9660_GET_ENDIAN(&pDirRec->offExtent),
+ pDirRec->fFileFlags,
+ pDirRec->RecTime.bYear + 1900,
+ pDirRec->RecTime.bMonth,
+ pDirRec->RecTime.bDay,
+ pDirRec->RecTime.bHour,
+ pDirRec->RecTime.bMinute,
+ pDirRec->RecTime.bSecond,
+ pDirRec->RecTime.offUtc*4/60,
+ pDirRec->bFileUnitSize,
+ pDirRec->bInterleaveGapSize,
+ ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo),
+ wszName));
+
+ uint32_t offSysUse = RT_UOFFSETOF_DYN(ISO9660DIRREC, achFileId[pDirRec->bFileIdLength])
+ + !(pDirRec->bFileIdLength & 1);
+ if (offSysUse < pDirRec->cbDirRec)
+ {
+ Log2(("ISO9660: system use (%#x bytes):\n%.*RhxD\n", pDirRec->cbDirRec - offSysUse,
+ pDirRec->cbDirRec - offSysUse, (uint8_t *)pDirRec + offSysUse));
+ }
+
+ /* advance */
+ offRec += pDirRec->cbDirRec;
+ }
+ }
+}
+#endif /* LOG_ENABLED */
+
+
+/**
+ * Instantiates a new shared directory structure, given 9660 records.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO volume instance.
+ * @param pParentDir The parent directory. This is NULL for the root
+ * directory.
+ * @param pDirRec The directory record. Will access @a cDirRecs
+ * records.
+ * @param cDirRecs Number of directory records if more than one.
+ * @param offDirRec The byte offset of the directory record.
+ * @param pRockInfo Optional pointer to rock ridge info for the entry.
+ * @param ppShared Where to return the shared directory structure.
+ */
+static int rtFsIsoDirShrd_New9660(PRTFSISOVOL pThis, PRTFSISODIRSHRD pParentDir, PCISO9660DIRREC pDirRec,
+ uint32_t cDirRecs, uint64_t offDirRec, PCRTFSISOROCKINFO pRockInfo, PRTFSISODIRSHRD *ppShared)
+{
+ /*
+ * Allocate a new structure and initialize it.
+ */
+ int rc = VERR_NO_MEMORY;
+ PRTFSISODIRSHRD pShared = (PRTFSISODIRSHRD)RTMemAllocZ(sizeof(*pShared));
+ if (pShared)
+ {
+ rc = rtFsIsoCore_InitFrom9660DirRec(&pShared->Core, pDirRec, cDirRecs, offDirRec, 0 /*uVersion*/, pRockInfo, pThis);
+ if (RT_SUCCESS(rc))
+ {
+ RTListInit(&pShared->OpenChildren);
+ pShared->cbDir = ISO9660_GET_ENDIAN(&pDirRec->cbData);
+ pShared->pbDir = (uint8_t *)RTMemAllocZ(pShared->cbDir + 256);
+ if (pShared->pbDir)
+ {
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, pShared->Core.FirstExtent.off, pShared->pbDir, pShared->cbDir, NULL);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ rtFsIsoDirShrd_Log9660Content(pShared);
+#endif
+
+ /*
+ * If this is the root directory, check if rock ridge info is present.
+ */
+ if ( !pParentDir
+ && !(pThis->fFlags & RTFSISO9660_F_NO_ROCK)
+ && pShared->cbDir > RT_UOFFSETOF(ISO9660DIRREC, achFileId[1]))
+ {
+ PCISO9660DIRREC pDirRec0 = (PCISO9660DIRREC)pShared->pbDir;
+ if ( pDirRec0->bFileIdLength == 1
+ && pDirRec0->achFileId[0] == 0
+ && pDirRec0->cbDirRec > RT_UOFFSETOF(ISO9660DIRREC, achFileId[1]))
+ rtFsIsoDirShrd_ParseRockForRoot(pShared, pDirRec0);
+ }
+
+ /*
+ * Link into parent directory so we can use it to update
+ * our directory entry.
+ */
+ if (pParentDir)
+ rtFsIsoDirShrd_AddOpenChild(pParentDir, &pShared->Core);
+ *ppShared = pShared;
+ return VINF_SUCCESS;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ RTMemFree(pShared);
+ }
+ *ppShared = NULL;
+ return rc;
+}
+
+
+#ifdef LOG_ENABLED
+/**
+ * Logs the content of a directory.
+ */
+static void rtFsIsoDirShrd_LogUdfContent(PRTFSISODIRSHRD pThis)
+{
+ if (LogIs2Enabled())
+ {
+ uint32_t offDesc = 0;
+ while (offDesc + RT_UOFFSETOF(UDFFILEIDDESC, abImplementationUse) < pThis->cbDir)
+ {
+ PCUDFFILEIDDESC pFid = (PCUDFFILEIDDESC)&pThis->pbDir[offDesc];
+ uint32_t const cbFid = UDFFILEIDDESC_GET_SIZE(pFid);
+ if (offDesc + cbFid > pThis->cbDir)
+ break;
+
+ uint32_t cwcName = 0;
+ RTUTF16 wszName[260];
+ if (pFid->cbName > 0)
+ {
+ uint8_t const *pbName = UDFFILEIDDESC_2_NAME(pFid);
+ uint32_t offSrc = 1;
+ if (*pbName == 8)
+ while (offSrc < pFid->cbName)
+ {
+ wszName[cwcName] = pbName[offSrc];
+ cwcName++;
+ offSrc++;
+ }
+ else if (*pbName == 16)
+ while (offSrc + 1 <= pFid->cbName)
+ {
+ wszName[cwcName] = RT_MAKE_U16(pbName[offSrc + 1], pbName[offSrc]);
+ cwcName++;
+ offSrc += 2;
+ }
+ else
+ {
+ RTUtf16CopyAscii(wszName, RT_ELEMENTS(wszName), "<bad type>");
+ cwcName = 10;
+ }
+ }
+ else if (pFid->fFlags & UDF_FILE_FLAGS_PARENT)
+ {
+ wszName[0] = '.';
+ wszName[1] = '.';
+ cwcName = 2;
+ }
+ else
+ {
+ RTUtf16CopyAscii(wszName, RT_ELEMENTS(wszName), "<empty>");
+ cwcName = 7;
+ }
+ wszName[cwcName] = '\0';
+
+ Log2(("ISO/UDF: %04x: fFlags=%#x uVer=%u Icb={%#04x:%#010RX32 LB %#06x t=%u} cbName=%#04x cbIU=%#x '%ls'\n",
+ offDesc,
+ pFid->fFlags,
+ pFid->uVersion,
+ pFid->Icb.Location.uPartitionNo,
+ pFid->Icb.Location.off,
+ pFid->Icb.cb,
+ pFid->Icb.uType,
+ pFid->cbName,
+ pFid->cbImplementationUse,
+ wszName));
+ int rc = rtFsIsoVolValidateUdfDescTagAndCrc(&pFid->Tag, pThis->cbDir - offDesc,
+ UDF_TAG_ID_FILE_ID_DESC, pFid->Tag.offTag, NULL);
+ if (RT_FAILURE(rc))
+ Log2(("ISO/UDF: Bad Tag: %Rrc - idTag=%#x\n", rc, pFid->Tag.idTag));
+ if (pFid->cbImplementationUse > 32)
+ Log2(("ISO/UDF: impl use (%#x bytes):\n%.*RhxD\n",
+ pFid->cbImplementationUse, pFid->cbImplementationUse, pFid->abImplementationUse));
+ else if (pFid->cbImplementationUse > 0)
+ Log2(("ISO/UDF: impl use (%#x bytes): %.*Rhxs\n",
+ pFid->cbImplementationUse, pFid->cbImplementationUse, pFid->abImplementationUse));
+
+ /* advance */
+ offDesc += cbFid;
+ }
+
+ if (offDesc < pThis->cbDir)
+ Log2(("ISO/UDF: warning! %#x trailing bytes in directory:\n%.*RhxD\n",
+ pThis->cbDir - offDesc, pThis->cbDir - offDesc, &pThis->pbDir[offDesc]));
+ }
+}
+#endif /* LOG_ENABLED */
+
+
+/**
+ * Instantiates a new shared directory structure, given UDF descriptors.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO volume instance.
+ * @param pParentDir The parent directory. This is NULL for the root
+ * directory.
+ * @param pAllocDesc The allocation descriptor for the directory ICB.
+ * @param pFileIdDesc The file ID descriptor. This is NULL for the root.
+ * @param offInDir The offset of the file ID descriptor in the parent
+ * directory. This is used when looking up shared
+ * directory objects. (Pass 0 for root.)
+ * @param ppShared Where to return the shared directory structure.
+ */
+static int rtFsIsoDirShrd_NewUdf(PRTFSISOVOL pThis, PRTFSISODIRSHRD pParentDir, PCUDFLONGAD pAllocDesc,
+ PCUDFFILEIDDESC pFileIdDesc, uintptr_t offInDir, PRTFSISODIRSHRD *ppShared)
+{
+ /*
+ * Allocate a new structure and initialize it.
+ */
+ int rc = VERR_NO_MEMORY;
+ PRTFSISODIRSHRD pShared = (PRTFSISODIRSHRD)RTMemAllocZ(sizeof(*pShared));
+ if (pShared)
+ {
+ rc = rtFsIsoCore_InitFromUdfIcbAndFileIdDesc(&pShared->Core, pAllocDesc, pFileIdDesc, offInDir, pThis);
+ if (RT_SUCCESS(rc))
+ {
+ RTListInit(&pShared->OpenChildren);
+
+ if (pShared->Core.cbObject < RTFSISO_MAX_DIR_SIZE)
+ {
+ pShared->cbDir = (uint32_t)pShared->Core.cbObject;
+ pShared->pbDir = (uint8_t *)RTMemAllocZ(RT_MAX(RT_ALIGN_32(pShared->cbDir, 512), 512));
+ if (pShared->pbDir)
+ {
+ rc = rtFsIsoCore_ReadWorker(&pShared->Core, 0, pShared->pbDir, pShared->cbDir, NULL, NULL);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ rtFsIsoDirShrd_LogUdfContent(pShared);
+#endif
+
+ /*
+ * Link into parent directory so we can use it to update
+ * our directory entry.
+ */
+ if (pParentDir)
+ rtFsIsoDirShrd_AddOpenChild(pParentDir, &pShared->Core);
+ *ppShared = pShared;
+ return VINF_SUCCESS;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ RTMemFree(pShared);
+ }
+
+ *ppShared = NULL;
+ return rc;
+}
+
+
+/**
+ * Instantiates a new directory with a shared structure presupplied.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO volume instance.
+ * @param pShared Referenced pointer to the shared structure. The
+ * reference is always CONSUMED.
+ * @param phVfsDir Where to return the directory handle.
+ */
+static int rtFsIsoDir_NewWithShared(PRTFSISOVOL pThis, PRTFSISODIRSHRD pShared, PRTVFSDIR phVfsDir)
+{
+ /*
+ * Create VFS object around the shared structure.
+ */
+ PRTFSISODIROBJ pNewDir;
+ int rc = RTVfsNewDir(&g_rtFsIsoDirOps, sizeof(*pNewDir), 0 /*fFlags*/, pThis->hVfsSelf,
+ NIL_RTVFSLOCK /*use volume lock*/, phVfsDir, (void **)&pNewDir);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Look for existing shared object, create a new one if necessary.
+ * We CONSUME a reference to pShared here.
+ */
+ pNewDir->offDir = 0;
+ pNewDir->pShared = pShared;
+ return VINF_SUCCESS;
+ }
+
+ rtFsIsoDirShrd_Release(pShared);
+ *phVfsDir = NIL_RTVFSDIR;
+ return rc;
+}
+
+
+
+/**
+ * Instantiates a new directory VFS instance for ISO 9660, creating the shared
+ * structure as necessary.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO volume instance.
+ * @param pParentDir The parent directory. This is NULL for the root
+ * directory.
+ * @param pDirRec The directory record.
+ * @param cDirRecs Number of directory records if more than one.
+ * @param offDirRec The byte offset of the directory record.
+ * @param pRockInfo Optional pointer to rock ridge info for the entry.
+ * @param phVfsDir Where to return the directory handle.
+ */
+static int rtFsIsoDir_New9660(PRTFSISOVOL pThis, PRTFSISODIRSHRD pParentDir, PCISO9660DIRREC pDirRec,
+ uint32_t cDirRecs, uint64_t offDirRec, PCRTFSISOROCKINFO pRockInfo, PRTVFSDIR phVfsDir)
+{
+ /*
+ * Look for existing shared object, create a new one if necessary.
+ */
+ PRTFSISODIRSHRD pShared = (PRTFSISODIRSHRD)rtFsIsoDir_LookupShared(pParentDir, offDirRec);
+ if (!pShared)
+ {
+ int rc = rtFsIsoDirShrd_New9660(pThis, pParentDir, pDirRec, cDirRecs, offDirRec, pRockInfo, &pShared);
+ if (RT_FAILURE(rc))
+ {
+ *phVfsDir = NIL_RTVFSDIR;
+ return rc;
+ }
+ }
+ return rtFsIsoDir_NewWithShared(pThis, pShared, phVfsDir);
+}
+
+
+/**
+ * Instantiates a new directory VFS instance for UDF, creating the shared
+ * structure as necessary.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO volume instance.
+ * @param pParentDir The parent directory.
+ * @param pFid The file ID descriptor for the directory.
+ * @param phVfsDir Where to return the directory handle.
+ */
+static int rtFsIsoDir_NewUdf(PRTFSISOVOL pThis, PRTFSISODIRSHRD pParentDir, PCUDFFILEIDDESC pFid, PRTVFSDIR phVfsDir)
+{
+ Assert(pFid);
+ Assert(pParentDir);
+ uintptr_t const offInDir = (uintptr_t)pFid - (uintptr_t)pParentDir->pbDir;
+ Assert(offInDir < pParentDir->cbDir);
+
+ /*
+ * Look for existing shared object, create a new one if necessary.
+ */
+ PRTFSISODIRSHRD pShared = (PRTFSISODIRSHRD)rtFsIsoDir_LookupShared(pParentDir, offInDir);
+ if (!pShared)
+ {
+ int rc = rtFsIsoDirShrd_NewUdf(pThis, pParentDir, &pFid->Icb, pFid, offInDir, &pShared);
+ if (RT_FAILURE(rc))
+ {
+ *phVfsDir = NIL_RTVFSDIR;
+ return rc;
+ }
+ }
+ return rtFsIsoDir_NewWithShared(pThis, pShared, phVfsDir);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsIsoVol_Close(void *pvThis)
+{
+ PRTFSISOVOL pThis = (PRTFSISOVOL)pvThis;
+ Log(("rtFsIsoVol_Close(%p)\n", pThis));
+
+ if (pThis->pRootDir)
+ {
+ Assert(RTListIsEmpty(&pThis->pRootDir->OpenChildren));
+ Assert(pThis->pRootDir->Core.cRefs == 1);
+ rtFsIsoDirShrd_Release(pThis->pRootDir);
+ pThis->pRootDir = NULL;
+ }
+
+ RTVfsFileRelease(pThis->hVfsBacking);
+ pThis->hVfsBacking = NIL_RTVFSFILE;
+
+ if (RTCritSectIsInitialized(&pThis->RockBufLock))
+ RTCritSectDelete(&pThis->RockBufLock);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsIsoVol_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ RT_NOREF(pvThis, pObjInfo, enmAddAttr);
+ return VERR_WRONG_TYPE;
+}
+
+
+static int rtFsIsoVol_ReturnUdfDString(const char *pachSrc, size_t cchSrc, void *pvDst, size_t cbDst, size_t *pcbRet)
+{
+ char *pszDst = (char *)pvDst;
+
+ if (pachSrc[0] == 8)
+ {
+ size_t const cchText = RT_MIN((uint8_t)pachSrc[cchSrc - 1], cchSrc - 2);
+ size_t const cchActual = RTStrNLen(&pachSrc[1], cchText);
+ *pcbRet = cchActual + 1;
+ int rc = RTStrCopyEx(pszDst, cbDst, &pachSrc[1], cchActual);
+ if (cbDst > 0)
+ RTStrPurgeEncoding(pszDst);
+ return rc;
+ }
+
+ if (pachSrc[0] == 16)
+ {
+ PCRTUTF16 pwszSrc = (PCRTUTF16)&pachSrc[1];
+ if (cchSrc > 0)
+ return RTUtf16BigToUtf8Ex(pwszSrc, (cchSrc - 2) / sizeof(RTUTF16), &pszDst, cchSrc, pcbRet);
+ int rc = RTUtf16CalcUtf8LenEx(pwszSrc, (cchSrc - 2) / sizeof(RTUTF16), pcbRet);
+ if (RT_SUCCESS(rc))
+ {
+ *pcbRet += 1;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ return rc;
+ }
+
+ if (ASMMemIsZero(pachSrc, cchSrc))
+ {
+ *pcbRet = 1;
+ if (cbDst >= 1)
+ {
+ *pszDst = '\0';
+ return VINF_SUCCESS;
+ }
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ *pcbRet = 0;
+ return VERR_INVALID_UTF8_ENCODING; /** @todo better status here */
+}
+
+
+/**
+ * For now this is a sanitized version of rtFsIsoVolGetMaybeUtf16Be, which is
+ * probably not correct or anything, but will have to do for now.
+ */
+static int rtFsIsoVol_ReturnIso9660D1String(const char *pachSrc, size_t cchSrc, void *pvDst, size_t cbDst, size_t *pcbRet)
+{
+ char *pszDst = (char *)pvDst;
+
+ /*
+ * Check if it may be some UTF16 variant by scanning for zero bytes
+ * (ISO-9660 doesn't allow zeros).
+ */
+ size_t cFirstZeros = 0;
+ size_t cSecondZeros = 0;
+ for (size_t off = 0; off + 1 < cchSrc; off += 2)
+ {
+ cFirstZeros += pachSrc[off] == '\0';
+ cSecondZeros += pachSrc[off + 1] == '\0';
+ }
+ if (cFirstZeros > cSecondZeros)
+ {
+ /*
+ * UTF-16BE / UTC-2BE:
+ */
+ if (cchSrc & 1)
+ {
+ AssertReturn(pachSrc[cchSrc - 1] == '\0' || pachSrc[cchSrc - 1] == ' ', VERR_INVALID_UTF16_ENCODING);
+ cchSrc--;
+ }
+ while ( cchSrc >= 2
+ && pachSrc[cchSrc - 1] == ' '
+ && pachSrc[cchSrc - 2] == '\0')
+ cchSrc -= 2;
+
+ if (cbDst > 0)
+ return RTUtf16BigToUtf8Ex((PCRTUTF16)pachSrc, cchSrc / sizeof(RTUTF16), &pszDst, cbDst, pcbRet);
+ int rc = RTUtf16BigCalcUtf8LenEx((PCRTUTF16)pachSrc, cchSrc / sizeof(RTUTF16), pcbRet);
+ if (RT_SUCCESS(rc))
+ {
+ *pcbRet += 1;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ return rc;
+ }
+
+ if (cSecondZeros > 0)
+ {
+ /*
+ * Little endian UTF-16 / UCS-2.
+ */
+ if (cchSrc & 1)
+ {
+ AssertReturn(pachSrc[cchSrc - 1] == '\0' || pachSrc[cchSrc - 1] == ' ', VERR_INVALID_UTF16_ENCODING);
+ cchSrc--;
+ }
+ while ( cchSrc >= 2
+ && pachSrc[cchSrc - 1] == '\0'
+ && pachSrc[cchSrc - 2] == ' ')
+ cchSrc -= 2;
+
+ if (cbDst)
+ return RTUtf16LittleToUtf8Ex((PCRTUTF16)pachSrc, cchSrc / sizeof(RTUTF16), &pszDst, cbDst, pcbRet);
+ int rc = RTUtf16LittleCalcUtf8LenEx((PCRTUTF16)pachSrc, cchSrc / sizeof(RTUTF16), pcbRet);
+ if (RT_SUCCESS(rc))
+ {
+ *pcbRet += 1;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ return rc;
+ }
+
+ /*
+ * ASSUME UTF-8/ASCII.
+ */
+ while ( cchSrc > 0
+ && pachSrc[cchSrc - 1] == ' ')
+ cchSrc--;
+
+ *pcbRet = cchSrc + 1;
+ int rc = RTStrCopyEx(pszDst, cbDst, pachSrc, cchSrc);
+ if (cbDst > 0)
+ RTStrPurgeEncoding(pszDst);
+ return rc;
+}
+
+
+static int rtFsIsoVol_ReturnIso9660DString(const char *pachSrc, size_t cchSrc, void *pvDst, size_t cbDst, size_t *pcbRet)
+{
+ /* Lazy bird: */
+ return rtFsIsoVol_ReturnIso9660D1String(pachSrc, cchSrc, pvDst, cbDst, pcbRet);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnQueryInfoEx}
+ */
+static DECLCALLBACK(int) rtFsIsoVol_QueryInfoEx(void *pvThis, RTVFSQIEX enmInfo, void *pvInfo, size_t cbInfo, size_t *pcbRet)
+{
+ PRTFSISOVOL pThis = (PRTFSISOVOL)pvThis;
+ LogFlow(("rtFsIsoVol_QueryInfo(%p, %d,, %#zx,)\n", pThis, enmInfo, cbInfo));
+
+ union
+ {
+ uint8_t ab[RTFSISO_MAX_LOGICAL_BLOCK_SIZE];
+ ISO9660PRIMARYVOLDESC PriVolDesc;
+ ISO9660SUPVOLDESC SupVolDesc;
+ } uBuf;
+
+ switch (enmInfo)
+ {
+ case RTVFSQIEX_VOL_LABEL:
+ case RTVFSQIEX_VOL_LABEL_ALT:
+ {
+ if (pThis->enmType == RTFSISOVOLTYPE_UDF
+ && ( enmInfo == RTVFSQIEX_VOL_LABEL
+ || pThis->offPrimaryVolDesc == 0))
+ return rtFsIsoVol_ReturnUdfDString(pThis->Udf.VolInfo.achLogicalVolumeID,
+ sizeof(pThis->Udf.VolInfo.achLogicalVolumeID), pvInfo, cbInfo, pcbRet);
+
+ bool const fPrimary = enmInfo == RTVFSQIEX_VOL_LABEL_ALT
+ || pThis->enmType == RTFSISOVOLTYPE_ISO9960;
+
+ int rc = RTVfsFileReadAt(pThis->hVfsBacking,
+ fPrimary ? pThis->offPrimaryVolDesc : pThis->offSecondaryVolDesc,
+ uBuf.ab, RT_MAX(RT_MIN(pThis->cbSector, sizeof(uBuf)), sizeof(uBuf.PriVolDesc)), NULL);
+ AssertRCReturn(rc, rc);
+
+ if (fPrimary)
+ return rtFsIsoVol_ReturnIso9660DString(uBuf.PriVolDesc.achVolumeId, sizeof(uBuf.PriVolDesc.achVolumeId),
+ pvInfo, cbInfo, pcbRet);
+ return rtFsIsoVol_ReturnIso9660D1String(uBuf.SupVolDesc.achVolumeId, sizeof(uBuf.SupVolDesc.achVolumeId),
+ pvInfo, cbInfo, pcbRet);
+ }
+
+ default:
+ return VERR_NOT_SUPPORTED;
+
+ }
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOPS,pfnOpenRoot}
+ */
+static DECLCALLBACK(int) rtFsIsoVol_OpenRoot(void *pvThis, PRTVFSDIR phVfsDir)
+{
+ PRTFSISOVOL pThis = (PRTFSISOVOL)pvThis;
+
+ rtFsIsoDirShrd_Retain(pThis->pRootDir); /* consumed by the next call */
+ return rtFsIsoDir_NewWithShared(pThis, pThis->pRootDir, phVfsDir);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOPS,pfnQueryRangeState}
+ */
+static DECLCALLBACK(int) rtFsIsoVol_QueryRangeState(void *pvThis, uint64_t off, size_t cb, bool *pfUsed)
+{
+ RT_NOREF(pvThis, off, cb, pfUsed);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+DECL_HIDDEN_CONST(const RTVFSOPS) g_rtFsIsoVolOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_VFS,
+ "ISO 9660/UDF",
+ rtFsIsoVol_Close,
+ rtFsIsoVol_QueryInfo,
+ rtFsIsoVol_QueryInfoEx,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSOPS_VERSION,
+ 0 /* fFeatures */,
+ rtFsIsoVol_OpenRoot,
+ rtFsIsoVol_QueryRangeState,
+ RTVFSOPS_VERSION
+};
+
+
+/**
+ * Checks the descriptor tag and CRC.
+ *
+ * @retval IPRT status code.
+ * @retval VERR_ISOFS_TAG_IS_ALL_ZEROS
+ * @retval VERR_MISMATCH
+ * @retval VERR_ISOFS_UNSUPPORTED_TAG_VERSION
+ * @retval VERR_ISOFS_TAG_SECTOR_MISMATCH
+ * @retval VERR_ISOFS_BAD_TAG_CHECKSUM
+ *
+ * @param pTag The tag to check.
+ * @param idTag The expected descriptor tag ID, UINT16_MAX matches any
+ * tag ID.
+ * @param offTag The sector offset of the tag.
+ * @param pErrInfo Where to return extended error info.
+ */
+static int rtFsIsoVolValidateUdfDescTag(PCUDFTAG pTag, uint16_t idTag, uint32_t offTag, PRTERRINFO pErrInfo)
+{
+ /*
+ * Checksum the tag first.
+ */
+ const uint8_t *pbTag = (const uint8_t *)pTag;
+ uint8_t const bChecksum = pbTag[0]
+ + pbTag[1]
+ + pbTag[2]
+ + pbTag[3]
+ + pbTag[5] /* skipping byte 4 as that's the checksum. */
+ + pbTag[6]
+ + pbTag[7]
+ + pbTag[8]
+ + pbTag[9]
+ + pbTag[10]
+ + pbTag[11]
+ + pbTag[12]
+ + pbTag[13]
+ + pbTag[14]
+ + pbTag[15];
+ if (pTag->uChecksum == bChecksum)
+ {
+ /*
+ * Do the matching.
+ */
+ if ( pTag->uVersion == 3
+ || pTag->uVersion == 2)
+ {
+ if ( pTag->idTag == idTag
+ || idTag == UINT16_MAX)
+ {
+ if (pTag->offTag == offTag)
+ {
+ //Log3(("ISO/UDF: Valid descriptor %#06x at %#010RX32; cbDescriptorCrc=%#06RX32 uTagSerialNo=%#x\n",
+ // pTag->idTag, offTag, pTag->cbDescriptorCrc, pTag->uTagSerialNo));
+ return VINF_SUCCESS;
+ }
+
+ Log(("rtFsIsoVolValidateUdfDescTag(,%#x,%#010RX32,): Sector mismatch: %#RX32 (%.*Rhxs)\n",
+ idTag, offTag, pTag->offTag, sizeof(*pTag), pTag));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_TAG_SECTOR_MISMATCH,
+ "Descriptor tag sector number mismatch: %#x, expected %#x (%.*Rhxs)",
+ pTag->offTag, offTag, sizeof(*pTag), pTag);
+ }
+ Log(("rtFsIsoVolValidateUdfDescTag(,%#x,%#010RX32,): Tag ID mismatch: %#x (%.*Rhxs)\n",
+ idTag, offTag, pTag->idTag, sizeof(*pTag), pTag));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_MISMATCH, "Descriptor tag ID mismatch: %#x, expected %#x (%.*Rhxs)",
+ pTag->idTag, idTag, sizeof(*pTag), pTag);
+ }
+ if (ASMMemIsZero(pTag, sizeof(*pTag)))
+ {
+ Log(("rtFsIsoVolValidateUdfDescTag(,%#x,%#010RX32,): All zeros\n", idTag, offTag));
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_ISOFS_TAG_IS_ALL_ZEROS, "Descriptor is all zeros");
+ }
+
+ Log(("rtFsIsoVolValidateUdfDescTag(,%#x,%#010RX32,): Unsupported version: %#x (%.*Rhxs)\n",
+ idTag, offTag, pTag->uVersion, sizeof(*pTag), pTag));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_UNSUPPORTED_TAG_VERSION, "Unsupported descriptor tag version: %#x, expected 2 or 3 (%.*Rhxs)",
+ pTag->uVersion, sizeof(*pTag), pTag);
+ }
+ Log(("rtFsIsoVolValidateUdfDescTag(,%#x,%#010RX32,): checksum error: %#x, calc %#x (%.*Rhxs)\n",
+ idTag, offTag, pTag->uChecksum, bChecksum, sizeof(*pTag), pTag));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_BAD_TAG_CHECKSUM,
+ "Descriptor tag checksum error: %#x, calculated %#x (%.*Rhxs)",
+ pTag->uChecksum, bChecksum, sizeof(*pTag), pTag);
+}
+
+
+/**
+ * Checks the descriptor CRC.
+ *
+ * @retval VINF_SUCCESS
+ * @retval VERR_ISOFS_INSUFFICIENT_DATA_FOR_DESC_CRC
+ * @retval VERR_ISOFS_DESC_CRC_MISMATCH
+ *
+ * @param pTag The descriptor buffer to checksum.
+ * @param cbDesc The size of the descriptor buffer.
+ * @param pErrInfo Where to return extended error info.
+ */
+static int rtFsIsoVolValidateUdfDescCrc(PCUDFTAG pTag, size_t cbDesc, PRTERRINFO pErrInfo)
+{
+ if (pTag->cbDescriptorCrc + sizeof(*pTag) <= cbDesc)
+ {
+ uint16_t uCrc = RTCrc16Ccitt(pTag + 1, pTag->cbDescriptorCrc);
+ if (pTag->uDescriptorCrc == uCrc)
+ return VINF_SUCCESS;
+
+ Log(("rtFsIsoVolValidateUdfDescCrc(,%#x,%#010RX32,): Descriptor CRC mismatch: expected %#x, calculated %#x (cbDescriptorCrc=%#x)\n",
+ pTag->idTag, pTag->offTag, pTag->uDescriptorCrc, uCrc, pTag->cbDescriptorCrc));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_DESC_CRC_MISMATCH,
+ "Descriptor CRC mismatch: exepcted %#x, calculated %#x (cbDescriptor=%#x, idTag=%#x, offTag=%#010RX32)",
+ pTag->uDescriptorCrc, uCrc, pTag->cbDescriptorCrc, pTag->idTag, pTag->offTag);
+ }
+
+ Log(("rtFsIsoVolValidateUdfDescCrc(,%#x,%#010RX32,): Insufficient data to CRC: cbDescriptorCrc=%#x cbDesc=%#zx\n",
+ pTag->idTag, pTag->offTag, pTag->cbDescriptorCrc, cbDesc));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_INSUFFICIENT_DATA_FOR_DESC_CRC,
+ "Insufficient data to CRC: cbDescriptorCrc=%#x cbDesc=%#zx (idTag=%#x, offTag=%#010RX32)",
+ pTag->cbDescriptorCrc, cbDesc, pTag->idTag, pTag->offTag);
+}
+
+
+/**
+ * Checks the descriptor tag and CRC.
+ *
+ * @retval VINF_SUCCESS
+ * @retval VERR_ISOFS_INSUFFICIENT_DATA_FOR_DESC_CRC
+ * @retval VERR_ISOFS_TAG_IS_ALL_ZEROS
+ * @retval VERR_MISMATCH
+ * @retval VERR_ISOFS_UNSUPPORTED_TAG_VERSION
+ * @retval VERR_ISOFS_TAG_SECTOR_MISMATCH
+ * @retval VERR_ISOFS_BAD_TAG_CHECKSUM
+ * @retval VERR_ISOFS_DESC_CRC_MISMATCH
+ *
+ * @param pTag The descriptor buffer to check the tag of and to
+ * checksum.
+ * @param cbDesc The size of the descriptor buffer.
+ * @param idTag The expected descriptor tag ID, UINT16_MAX
+ * matches any tag ID.
+ * @param offTag The sector offset of the tag.
+ * @param pErrInfo Where to return extended error info.
+ */
+static int rtFsIsoVolValidateUdfDescTagAndCrc(PCUDFTAG pTag, size_t cbDesc, uint16_t idTag, uint32_t offTag, PRTERRINFO pErrInfo)
+{
+ int rc = rtFsIsoVolValidateUdfDescTag(pTag, idTag, offTag, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFsIsoVolValidateUdfDescCrc(pTag, cbDesc, pErrInfo);
+ return rc;
+}
+
+
+
+
+static int rtFsIsoVolProcessUdfFileSetDescs(PRTFSISOVOL pThis, uint8_t *pbBuf, size_t cbBuf, PRTERRINFO pErrInfo)
+{
+
+ /*
+ * We assume there is a single file descriptor and don't bother checking what comes next.
+ */
+ PUDFFILESETDESC pFsd = (PUDFFILESETDESC)pbBuf;
+ Assert(cbBuf > sizeof(*pFsd)); NOREF(cbBuf);
+ RT_ZERO(*pFsd);
+ size_t cbToRead = RT_MAX(pThis->Udf.VolInfo.FileSetDescriptor.cb, sizeof(*pFsd));
+ int rc = rtFsIsoVolUdfVpRead(pThis, pThis->Udf.VolInfo.FileSetDescriptor.Location.uPartitionNo,
+ pThis->Udf.VolInfo.FileSetDescriptor.Location.off, 0, pFsd, cbToRead);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsIsoVolValidateUdfDescTagAndCrc(&pFsd->Tag, cbToRead, UDF_TAG_ID_FILE_SET_DESC,
+ pThis->Udf.VolInfo.FileSetDescriptor.Location.off, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ Log(("ISO/UDF: File set descriptor at %#RX32 (%#RX32:%#RX32)\n", pFsd->Tag.offTag,
+ pThis->Udf.VolInfo.FileSetDescriptor.Location.uPartitionNo,
+ pThis->Udf.VolInfo.FileSetDescriptor.Location.off));
+ if (LogIs2Enabled())
+ {
+ UDF_LOG2_MEMBER_TIMESTAMP(pFsd, RecordingTimestamp);
+ UDF_LOG2_MEMBER(pFsd, "#06RX16", uInterchangeLevel);
+ UDF_LOG2_MEMBER(pFsd, "#06RX16", uMaxInterchangeLevel);
+ UDF_LOG2_MEMBER(pFsd, "#010RX32", fCharacterSets);
+ UDF_LOG2_MEMBER(pFsd, "#010RX32", fMaxCharacterSets);
+ UDF_LOG2_MEMBER(pFsd, "#010RX32", uFileSetNo);
+ UDF_LOG2_MEMBER(pFsd, "#010RX32", uFileSetDescNo);
+ UDF_LOG2_MEMBER_CHARSPEC(pFsd, LogicalVolumeIDCharSet);
+ UDF_LOG2_MEMBER_DSTRING(pFsd, achLogicalVolumeID);
+ UDF_LOG2_MEMBER_CHARSPEC(pFsd, FileSetCharSet);
+ UDF_LOG2_MEMBER_DSTRING(pFsd, achFileSetID);
+ UDF_LOG2_MEMBER_DSTRING(pFsd, achCopyrightFile);
+ UDF_LOG2_MEMBER_DSTRING(pFsd, achAbstractFile);
+ UDF_LOG2_MEMBER_LONGAD(pFsd, RootDirIcb);
+ UDF_LOG2_MEMBER_ENTITY_ID(pFsd, idDomain);
+ UDF_LOG2_MEMBER_LONGAD(pFsd, NextExtent);
+ UDF_LOG2_MEMBER_LONGAD(pFsd, SystemStreamDirIcb);
+ if (!ASMMemIsZero(&pFsd->abReserved[0], sizeof(pFsd->abReserved)))
+ UDF_LOG2_MEMBER(pFsd, ".32Rhxs", abReserved);
+ }
+#endif
+
+ /*
+ * Do some basic sanity checking.
+ */
+ if (!UDF_IS_CHAR_SET_OSTA(&pFsd->FileSetCharSet))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_FSD_UNSUPPORTED_CHAR_SET,
+ "Invalid file set charset %.64Rhxs", &pFsd->FileSetCharSet);
+ if ( pFsd->RootDirIcb.cb == 0
+ || pFsd->RootDirIcb.uType != UDF_AD_TYPE_RECORDED_AND_ALLOCATED)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_FSD_ZERO_ROOT_DIR,
+ "Root Dir ICB location is zero or malformed: uType=%#x cb=%#x loc=%#x:%#RX32",
+ pFsd->RootDirIcb.uType, pFsd->RootDirIcb.cb,
+ pFsd->RootDirIcb.Location.uPartitionNo, pFsd->RootDirIcb.Location.off);
+ if ( pFsd->NextExtent.cb != 0
+ && pFsd->NextExtent.uType == UDF_AD_TYPE_RECORDED_AND_ALLOCATED)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_FSD_NEXT_EXTENT,
+ "NextExtent isn't zero: uType=%#x cb=%#x loc=%#x:%#RX32",
+ pFsd->NextExtent.uType, pFsd->NextExtent.cb,
+ pFsd->NextExtent.Location.uPartitionNo, pFsd->NextExtent.Location.off);
+
+ /*
+ * Copy the information we need.
+ */
+ pThis->Udf.VolInfo.RootDirIcb = pFsd->RootDirIcb;
+ if ( pFsd->SystemStreamDirIcb.cb > 0
+ && pFsd->SystemStreamDirIcb.uType == UDF_AD_TYPE_RECORDED_AND_ALLOCATED)
+ pThis->Udf.VolInfo.SystemStreamDirIcb = pFsd->SystemStreamDirIcb;
+ else
+ RT_ZERO(pThis->Udf.VolInfo.SystemStreamDirIcb);
+ return VINF_SUCCESS;
+ }
+ return rc;
+ }
+ return RTERRINFO_LOG_SET(pErrInfo, rc, "Error reading file set descriptor");
+}
+
+
+/**
+ * Check validatity and extract information from the descriptors in the VDS seq.
+ *
+ * @returns IPRT status code
+ * @param pThis The instance.
+ * @param pInfo The VDS sequence info.
+ * @param pErrInfo Where to return extended error info.
+ */
+static int rtFsIsoVolProcessUdfVdsSeqInfo(PRTFSISOVOL pThis, PRTFSISOVDSINFO pInfo, PRTERRINFO pErrInfo)
+{
+ /*
+ * Check the basic descriptor counts.
+ */
+ PUDFPRIMARYVOLUMEDESC pPvd;
+ if (pInfo->cPrimaryVols == 1)
+ pPvd = pInfo->apPrimaryVols[0];
+ else
+ {
+ if (pInfo->cPrimaryVols == 0)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_ISOFS_NO_PVD, "No primary volume descriptor was found");
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_MULTIPLE_PVDS,
+ "More than one primary volume descriptor was found: %u", pInfo->cPrimaryVols);
+ }
+
+ PUDFLOGICALVOLUMEDESC pLvd;
+ if (pInfo->cLogicalVols == 1)
+ pLvd = pInfo->apLogicalVols[0];
+ else
+ {
+ if (pInfo->cLogicalVols == 0)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_ISOFS_NO_LVD, "No logical volume descriptor was found");
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_MULTIPLE_LVDS,
+ "More than one logical volume descriptor was found: %u", pInfo->cLogicalVols);
+ }
+
+#if 0
+ if (pInfo->cPartitions == 0)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_ISOFS_NO_PD, "No partition descriptors was found");
+#endif
+
+ /*
+ * Check out the partition map in the logical volume descriptor.
+ * Produce the mapping table while going about that.
+ */
+ if (pLvd->cPartitionMaps > 64)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_TOO_MANY_PART_MAPS,
+ "Too many partition maps: %u (max 64)", pLvd->cPartitionMaps);
+
+ PRTFSISOVOLUDFPMAP paPartMaps = NULL;
+ if (pLvd->cPartitionMaps > 0)
+ {
+ pInfo->paPartMaps = paPartMaps = (PRTFSISOVOLUDFPMAP)RTMemAllocZ(sizeof(paPartMaps[0]) * pLvd->cPartitionMaps);
+ if (!paPartMaps)
+ return VERR_NO_MEMORY;
+ }
+ uint32_t cPartMaps = 0;
+
+ if (pLvd->cbMapTable)
+ {
+ uint32_t off = 0;
+ while (off + sizeof(UDFPARTMAPHDR) <= pLvd->cbMapTable)
+ {
+ PCUDFPARTMAPHDR pHdr = (PCUDFPARTMAPHDR)&pLvd->abPartitionMaps[off];
+
+ /*
+ * Bounds checking.
+ */
+ if (off + pHdr->cb > pLvd->cbMapTable)
+ {
+ if (cPartMaps < pLvd->cbMapTable)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_MALFORMED_PART_MAP_TABLE,
+ "Incomplete partition map entry at offset %#x: cb=%#x -> offEnd=%#x cbMapTable=%#x (type=%#x)",
+ off, pHdr->cb, off + pHdr->cb, pLvd->cbMapTable, pHdr->bType);
+ LogRel(("ISO/UDF: Warning: Incomplete partition map entry at offset %#x: cb=%#x -> offEnd=%#x cbMapTable=%#x (type=%#x)\n",
+ off, pHdr->cb, off + pHdr->cb, pLvd->cbMapTable, pHdr->bType));
+ break;
+ }
+ if (cPartMaps >= pLvd->cPartitionMaps)
+ {
+ LogRel(("ISO/UDF: Warning: LVD::cPartitionMaps is %u but there are more bytes in the table. (off=%#x cb=%#x cbMapTable=%#x bType=%#x)\n",
+ cPartMaps - pLvd->cPartitionMaps, off, pHdr->cb, pLvd->cbMapTable, pHdr->bType));
+ break;
+ }
+
+ /*
+ * Extract relevant info out of the entry.
+ */
+ paPartMaps[cPartMaps].offMapTable = (uint16_t)off;
+ uint16_t uPartitionNo;
+ if (pHdr->bType == 1)
+ {
+ PCUDFPARTMAPTYPE1 pType1 = (PCUDFPARTMAPTYPE1)pHdr;
+ paPartMaps[cPartMaps].uVolumeSeqNo = pType1->uVolumeSeqNo;
+ paPartMaps[cPartMaps].bType = RTFSISO_UDF_PMAP_T_PLAIN;
+ uPartitionNo = pType1->uPartitionNo;
+ }
+ else if (pHdr->bType == 2)
+ {
+ PCUDFPARTMAPTYPE2 pType2 = (PCUDFPARTMAPTYPE2)pHdr;
+ if (UDF_ENTITY_ID_EQUALS(&pType2->idPartitionType, UDF_ENTITY_ID_VPM_PARTITION_TYPE))
+ {
+ paPartMaps[cPartMaps].bType = pType2->idPartitionType.Suffix.Udf.uUdfRevision >= 0x200
+ ? RTFSISO_UDF_PMAP_T_VPM_20 : RTFSISO_UDF_PMAP_T_VPM_15;
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_VPM_NOT_SUPPORTED, "Partition type '%.23s' (%#x) not supported",
+ pType2->idPartitionType.achIdentifier, pType2->idPartitionType.Suffix.Udf.uUdfRevision);
+ }
+ else if (UDF_ENTITY_ID_EQUALS(&pType2->idPartitionType, UDF_ENTITY_ID_SPM_PARTITION_TYPE))
+ {
+ paPartMaps[cPartMaps].bType = RTFSISO_UDF_PMAP_T_SPM;
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_SPM_NOT_SUPPORTED, "Partition type '%.23s' (%#x) not supported",
+ pType2->idPartitionType.achIdentifier, pType2->idPartitionType.Suffix.Udf.uUdfRevision);
+ }
+ else if (UDF_ENTITY_ID_EQUALS(&pType2->idPartitionType, UDF_ENTITY_ID_MPM_PARTITION_TYPE))
+ {
+ paPartMaps[cPartMaps].bType = RTFSISO_UDF_PMAP_T_MPM;
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_MPM_NOT_SUPPORTED, "Partition type '%.23s' (%#x) not supported",
+ pType2->idPartitionType.achIdentifier, pType2->idPartitionType.Suffix.Udf.uUdfRevision);
+ }
+ else
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_UNKNOWN_PART_MAP_TYPE_ID,
+ "Unknown partition map ID for #%u @ %#x: %.23s",
+ cPartMaps, off, pType2->idPartitionType.achIdentifier);
+#if 0 /* unreachable code */
+ paPartMaps[cPartMaps].uVolumeSeqNo = pType2->uVolumeSeqNo;
+ uPartitionNo = pType2->uPartitionNo;
+#endif
+ }
+ else
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_UNKNOWN_PART_MAP_ENTRY_TYPE,
+ "Unknown partition map entry type #%u @ %#x: %u", cPartMaps, off, pHdr->bType);
+ paPartMaps[cPartMaps].uPartitionNo = uPartitionNo;
+
+ /*
+ * Lookup the partition number and retrieve the relevant info from the partition descriptor.
+ */
+ uint32_t i = pInfo->cPartitions;
+ while (i-- > 0)
+ {
+ PUDFPARTITIONDESC pPd = pInfo->apPartitions[i];
+ if (paPartMaps[cPartMaps].uPartitionNo == pPd->uPartitionNo)
+ {
+ paPartMaps[cPartMaps].idxPartDesc = (uint16_t)i;
+ paPartMaps[cPartMaps].cSectors = pPd->cSectors;
+ paPartMaps[cPartMaps].offLocation = pPd->offLocation;
+ paPartMaps[cPartMaps].offByteLocation = (uint64_t)pPd->offLocation * pThis->cbSector;
+ paPartMaps[cPartMaps].fFlags = pPd->fFlags;
+ paPartMaps[cPartMaps].uAccessType = pPd->uAccessType;
+ if (!UDF_ENTITY_ID_EQUALS(&pPd->PartitionContents, UDF_ENTITY_ID_PD_PARTITION_CONTENTS_UDF))
+ paPartMaps[cPartMaps].fHaveHdr = false;
+ else
+ {
+ paPartMaps[cPartMaps].fHaveHdr = true;
+ paPartMaps[cPartMaps].Hdr = pPd->ContentsUse.Hdr;
+ }
+ break;
+ }
+ }
+ if (i > pInfo->cPartitions)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_PARTITION_NOT_FOUND,
+ "Partition #%u (%#x) specified by mapping entry #%u (@ %#x) was not found! (int-type %u)",
+ uPartitionNo, uPartitionNo, cPartMaps, off, paPartMaps[cPartMaps].bType);
+
+ /*
+ * Advance.
+ */
+ cPartMaps++;
+ off += pHdr->cb;
+ }
+
+ if (cPartMaps < pLvd->cPartitionMaps)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_INCOMPLETE_PART_MAP_TABLE,
+ "Only found %u of the %u announced partition mapping table entries",
+ cPartMaps, pLvd->cPartitionMaps);
+ }
+
+ /* It might be theoretically possible to not use virtual partitions for
+ accessing data, so just warn if there aren't any. */
+ if (cPartMaps == 0)
+ LogRel(("ISO/UDF: Warning: No partition maps!\n"));
+
+ /*
+ * Check out the logical volume descriptor.
+ */
+ if ( pLvd->cbLogicalBlock < pThis->cbSector
+ || pLvd->cbLogicalBlock > RTFSISO_MAX_LOGICAL_BLOCK_SIZE
+ || (pLvd->cbLogicalBlock % pThis->cbSector) != 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_UNSUPPORTED_LOGICAL_BLOCK_SIZE,
+ "Logical block size of %#x is not supported with a sector size of %#x",
+ pLvd->cbLogicalBlock, pThis->cbSector);
+
+ if (!UDF_ENTITY_ID_EQUALS(&pLvd->idDomain, UDF_ENTITY_ID_LVD_DOMAIN))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_BAD_LVD_DOMAIN_ID,
+ "Unsupported domain ID in logical volume descriptor: '%.23s'", pLvd->idDomain.achIdentifier);
+
+ if ( pLvd->ContentsUse.FileSetDescriptor.uType != UDF_AD_TYPE_RECORDED_AND_ALLOCATED
+ || pLvd->ContentsUse.FileSetDescriptor.cb == 0
+ || pLvd->ContentsUse.FileSetDescriptor.Location.uPartitionNo >= cPartMaps)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_BAD_LVD_FILE_SET_DESC_LOCATION,
+ "Malformed file set descriptor location (type=%u cb=%#x part=%#x)",
+ pLvd->ContentsUse.FileSetDescriptor.uType,
+ pLvd->ContentsUse.FileSetDescriptor.cb,
+ pLvd->ContentsUse.FileSetDescriptor.Location.uPartitionNo);
+
+ bool fLvdHaveVolId = !ASMMemIsZero(pLvd->achLogicalVolumeID, sizeof(pLvd->achLogicalVolumeID));
+ if ( fLvdHaveVolId
+ && !UDF_IS_CHAR_SET_OSTA(&pLvd->DescCharSet))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_BAD_LVD_DESC_CHAR_SET,
+ "Logical volume ID is not using OSTA compressed unicode");
+
+ /*
+ * We can ignore much, if not all of the primary volume descriptor.
+ */
+
+ /*
+ * We're good. So copy over the data.
+ */
+ pThis->Udf.VolInfo.FileSetDescriptor = pLvd->ContentsUse.FileSetDescriptor;
+ pThis->Udf.VolInfo.cbBlock = pLvd->cbLogicalBlock;
+ pThis->Udf.VolInfo.cShiftBlock = 9;
+ while (pThis->Udf.VolInfo.cbBlock != RT_BIT_32(pThis->Udf.VolInfo.cShiftBlock))
+ pThis->Udf.VolInfo.cShiftBlock++;
+ pThis->Udf.VolInfo.fFlags = pPvd->fFlags;
+ pThis->Udf.VolInfo.cPartitions = cPartMaps;
+ pThis->Udf.VolInfo.paPartitions = paPartMaps;
+ pInfo->paPartMaps = NULL;
+ if (fLvdHaveVolId)
+ memcpy(pThis->Udf.VolInfo.achLogicalVolumeID, pLvd->achLogicalVolumeID, sizeof(pThis->Udf.VolInfo.achLogicalVolumeID));
+ else
+ RT_ZERO(pThis->Udf.VolInfo.achLogicalVolumeID);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Processes a primary volume descriptor in the VDS (UDF).
+ *
+ * @returns IPRT status code.
+ * @param pInfo Where we gather descriptor information.
+ * @param pDesc The descriptor.
+ * @param pErrInfo Where to return extended error information.
+ */
+//cmd: kmk VBoxRT && kmk_redirect -E VBOX_LOG_DEST="nofile stderr" -E VBOX_LOG="rt_fs=~0" -E VBOX_LOG_FLAGS="unbuffered enabled" -- e:\vbox\svn\trunk\out\win.amd64\debug\bin\tools\RTLs.exe :iprtvfs:file(open,d:\Downloads\en_windows_10_enterprise_version_1703_updated_march_2017_x64_dvd_10189290.iso,r):vfs(isofs):/ -la
+static int rtFsIsoVolProcessUdfPrimaryVolDesc(PRTFSISOVDSINFO pInfo, PCUDFPRIMARYVOLUMEDESC pDesc, PRTERRINFO pErrInfo)
+{
+#ifdef LOG_ENABLED
+ Log(("ISO/UDF: Primary volume descriptor at sector %#RX32\n", pDesc->Tag.offTag));
+ if (LogIs2Enabled())
+ {
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", uVolumeDescSeqNo);
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", uPrimaryVolumeDescNo);
+ UDF_LOG2_MEMBER_DSTRING(pDesc, achVolumeID);
+ UDF_LOG2_MEMBER(pDesc, "#06RX16", uVolumeSeqNo);
+ UDF_LOG2_MEMBER(pDesc, "#06RX16", uVolumeSeqNo);
+ UDF_LOG2_MEMBER(pDesc, "#06RX16", uMaxVolumeSeqNo);
+ UDF_LOG2_MEMBER(pDesc, "#06RX16", uInterchangeLevel);
+ UDF_LOG2_MEMBER(pDesc, "#06RX16", uMaxInterchangeLevel);
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", fCharacterSets);
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", fMaxCharacterSets);
+ UDF_LOG2_MEMBER_DSTRING(pDesc, achVolumeSetID);
+ UDF_LOG2_MEMBER_CHARSPEC(pDesc, DescCharSet);
+ UDF_LOG2_MEMBER_CHARSPEC(pDesc, ExplanatoryCharSet);
+ UDF_LOG2_MEMBER_EXTENTAD(pDesc, VolumeAbstract);
+ UDF_LOG2_MEMBER_EXTENTAD(pDesc, VolumeCopyrightNotice);
+ UDF_LOG2_MEMBER_ENTITY_ID(pDesc, idApplication);
+ UDF_LOG2_MEMBER_TIMESTAMP(pDesc, RecordingTimestamp);
+ UDF_LOG2_MEMBER_ENTITY_ID(pDesc, idImplementation);
+ if (!ASMMemIsZero(&pDesc->abImplementationUse, sizeof(pDesc->abImplementationUse)))
+ Log2(("ISO/UDF: %-32s %.64Rhxs\n", "abReserved[64]:", &pDesc->abImplementationUse[0]));
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", offPredecessorVolDescSeq);
+ UDF_LOG2_MEMBER(pDesc, "#06RX16", fFlags);
+ if (!ASMMemIsZero(&pDesc->abReserved, sizeof(pDesc->abReserved)))
+ Log2(("ISO/UDF: %-32s %.22Rhxs\n", "abReserved[22]:", &pDesc->abReserved[0]));
+ }
+#endif
+
+ /*
+ * Check if this is a new revision of an existing primary volume descriptor.
+ */
+ PUDFPRIMARYVOLUMEDESC pEndianConvert = NULL;
+ uint32_t i = pInfo->cPrimaryVols;
+ while (i--> 0)
+ {
+ if ( memcmp(pDesc->achVolumeID, pInfo->apPrimaryVols[i]->achVolumeID, sizeof(pDesc->achVolumeID)) == 0
+ && memcmp(&pDesc->DescCharSet, &pInfo->apPrimaryVols[i]->DescCharSet, sizeof(pDesc->DescCharSet)) == 0)
+ {
+ if (RT_LE2H_U32(pDesc->uVolumeDescSeqNo) >= pInfo->apPrimaryVols[i]->uVolumeDescSeqNo)
+ {
+ Log(("ISO/UDF: Primary descriptor prevails over previous! (%u >= %u)\n",
+ RT_LE2H_U32(pDesc->uVolumeDescSeqNo), pInfo->apPartitions[i]->uVolumeDescSeqNo));
+ pEndianConvert = pInfo->apPrimaryVols[i];
+ memcpy(pEndianConvert, pDesc, sizeof(*pDesc));
+ }
+ else
+ Log(("ISO/UDF: Primary descriptor has lower sequence number than the previous! (%u < %u)\n",
+ RT_LE2H_U32(pDesc->uVolumeDescSeqNo), pInfo->apPartitions[i]->uVolumeDescSeqNo));
+ break;
+ }
+ }
+ if (i >= pInfo->cPrimaryVols)
+ {
+ /*
+ * It wasn't. Append it.
+ */
+ i = pInfo->cPrimaryVols;
+ if (i < RT_ELEMENTS(pInfo->apPrimaryVols))
+ {
+ pInfo->apPrimaryVols[i] = pEndianConvert = (PUDFPRIMARYVOLUMEDESC)RTMemDup(pDesc, sizeof(*pDesc));
+ if (pEndianConvert)
+ pInfo->cPrimaryVols = i + 1;
+ else
+ return VERR_NO_MEMORY;
+ Log2(("ISO/UDF: ++New primary descriptor.\n"));
+ }
+ else
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_ISOFS_TOO_MANY_PVDS, "Encountered too many primary volume descriptors");
+ }
+
+#ifdef RT_BIG_ENDIAN
+ /*
+ * Do endian conversion of the descriptor.
+ */
+ if (pEndianConvert)
+ {
+ AssertFailed();
+ }
+#else
+ RT_NOREF(pEndianConvert);
+#endif
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Processes an logical volume descriptor in the VDS (UDF).
+ *
+ * @returns IPRT status code.
+ * @param pInfo Where we gather descriptor information.
+ * @param pDesc The descriptor.
+ * @param cbSector The sector size (UDF defines the logical and physical
+ * sector size to be the same).
+ * @param pErrInfo Where to return extended error information.
+ */
+static int rtFsIsoVolProcessUdfLogicalVolumeDesc(PRTFSISOVDSINFO pInfo, PCUDFLOGICALVOLUMEDESC pDesc,
+ uint32_t cbSector, PRTERRINFO pErrInfo)
+{
+#ifdef LOG_ENABLED
+ Log(("ISO/UDF: Logical volume descriptor at sector %#RX32\n", pDesc->Tag.offTag));
+ if (LogIs2Enabled())
+ {
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", uVolumeDescSeqNo);
+ UDF_LOG2_MEMBER_CHARSPEC(pDesc, DescCharSet);
+ UDF_LOG2_MEMBER_DSTRING(pDesc, achLogicalVolumeID);
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", cbLogicalBlock);
+ UDF_LOG2_MEMBER_ENTITY_ID(pDesc, idDomain);
+ if (UDF_ENTITY_ID_EQUALS(&pDesc->idDomain, UDF_ENTITY_ID_LVD_DOMAIN))
+ UDF_LOG2_MEMBER_LONGAD(pDesc, ContentsUse.FileSetDescriptor);
+ else if (!ASMMemIsZero(&pDesc->ContentsUse.ab[0], sizeof(pDesc->ContentsUse.ab)))
+ Log2(("ISO/UDF: %-32s %.16Rhxs\n", "ContentsUse.ab[16]:", &pDesc->ContentsUse.ab[0]));
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", cbMapTable);
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", cPartitionMaps);
+ UDF_LOG2_MEMBER_ENTITY_ID(pDesc, idImplementation);
+ if (!ASMMemIsZero(&pDesc->ImplementationUse.ab[0], sizeof(pDesc->ImplementationUse.ab)))
+ Log2(("ISO/UDF: %-32s\n%.128RhxD\n", "ImplementationUse.ab[128]:", &pDesc->ImplementationUse.ab[0]));
+ UDF_LOG2_MEMBER_EXTENTAD(pDesc, IntegritySeqExtent);
+ if (pDesc->cbMapTable)
+ {
+ Log2(("ISO/UDF: %-32s\n", "abPartitionMaps"));
+ uint32_t iMap = 0;
+ uint32_t off = 0;
+ while (off + sizeof(UDFPARTMAPHDR) <= pDesc->cbMapTable)
+ {
+ PCUDFPARTMAPHDR pHdr = (PCUDFPARTMAPHDR)&pDesc->abPartitionMaps[off];
+ Log2(("ISO/UDF: %02u @ %#05x: type %u, length %u\n", iMap, off, pHdr->bType, pHdr->cb));
+ if (off + pHdr->cb > pDesc->cbMapTable)
+ {
+ Log2(("ISO/UDF: BAD! Entry is %d bytes too long!\n", off + pHdr->cb - pDesc->cbMapTable));
+ break;
+ }
+ if (pHdr->bType == 1)
+ {
+ PCUDFPARTMAPTYPE1 pType1 = (PCUDFPARTMAPTYPE1)pHdr;
+ UDF_LOG2_MEMBER_EX(pType1, "#06RX16", uVolumeSeqNo, 5);
+ UDF_LOG2_MEMBER_EX(pType1, "#06RX16", uPartitionNo, 5);
+ }
+ else if (pHdr->bType == 2)
+ {
+ PCUDFPARTMAPTYPE2 pType2 = (PCUDFPARTMAPTYPE2)pHdr;
+ UDF_LOG2_MEMBER_ENTITY_ID_EX(pType2, idPartitionType, 5);
+ UDF_LOG2_MEMBER_EX(pType2, "#06RX16", uVolumeSeqNo, 5);
+ UDF_LOG2_MEMBER_EX(pType2, "#06RX16", uPartitionNo, 5);
+ if (UDF_ENTITY_ID_EQUALS(&pType2->idPartitionType, UDF_ENTITY_ID_SPM_PARTITION_TYPE))
+ {
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#06RX16", Spm.cBlocksPerPacket, 5);
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#04RX8", Spm.cSparingTables, 5);
+ if (pType2->u.Spm.bReserved2)
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#04RX8", Spm.bReserved2, 5);
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#010RX32", Spm.cbSparingTable, 5);
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#010RX32", Spm.aoffSparingTables[0], 5);
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#010RX32", Spm.aoffSparingTables[1], 5);
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#010RX32", Spm.aoffSparingTables[2], 5);
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#010RX32", Spm.aoffSparingTables[3], 5);
+ }
+ else if (UDF_ENTITY_ID_EQUALS(&pType2->idPartitionType, UDF_ENTITY_ID_MPM_PARTITION_TYPE))
+ {
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#010RX32", Mpm.offMetadataFile, 5);
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#010RX32", Mpm.offMetadataMirrorFile, 5);
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#010RX32", Mpm.offMetadataBitmapFile, 5);
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#010RX32", Mpm.cBlocksAllocationUnit, 5);
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#06RX16", Mpm.cBlocksAlignmentUnit, 5);
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#04RX8", Mpm.fFlags, 5);
+ if (!ASMMemIsZero(pType2->u.Mpm.abReserved2, sizeof(pType2->u.Mpm.abReserved2)))
+ UDF_LOG2_MEMBER_EX(&pType2->u, ".5Rhxs", Mpm.abReserved2, 5);
+ }
+ }
+ else
+ Log2(("ISO/UDF: BAD! Unknown type!\n"));
+
+ /* advance */
+ off += pHdr->cb;
+ iMap++;
+ }
+ }
+ }
+#endif
+
+ /*
+ * Check if this is a newer revision of an existing primary volume descriptor.
+ */
+ size_t cbDesc = (size_t)pDesc->cbMapTable + RT_UOFFSETOF(UDFLOGICALVOLUMEDESC, abPartitionMaps);
+ if ( pDesc->cbMapTable >= (UINT32_MAX >> 1)
+ || cbDesc > cbSector)
+ {
+ Log(("ISO/UDF: Logical volume descriptor is too big: %#zx (cbSector=%#x)\n", cbDesc, cbSector));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_TOO_BIT_PARTMAP_IN_LVD,
+ "Logical volume descriptor is too big: %#zx (cbSector=%#x)\n", cbDesc, cbSector);
+ }
+
+ PUDFLOGICALVOLUMEDESC pEndianConvert = NULL;
+ uint32_t i = pInfo->cLogicalVols;
+ while (i--> 0)
+ if ( memcmp(pDesc->achLogicalVolumeID, pInfo->apLogicalVols[i]->achLogicalVolumeID,
+ sizeof(pDesc->achLogicalVolumeID)) == 0
+ && memcmp(&pDesc->DescCharSet, &pInfo->apLogicalVols[i]->DescCharSet,
+ sizeof(pDesc->DescCharSet)) == 0)
+ {
+ if (RT_LE2H_U32(pDesc->uVolumeDescSeqNo) >= pInfo->apLogicalVols[i]->uVolumeDescSeqNo)
+ {
+ Log(("ISO/UDF: Logical descriptor prevails over previous! (%u >= %u)\n",
+ RT_LE2H_U32(pDesc->uVolumeDescSeqNo), pInfo->apLogicalVols[i]->uVolumeDescSeqNo));
+ pEndianConvert = (PUDFLOGICALVOLUMEDESC)RTMemDup(pDesc, cbDesc);
+ if (!pEndianConvert)
+ return VERR_NO_MEMORY;
+ RTMemFree(pInfo->apLogicalVols[i]);
+ pInfo->apLogicalVols[i] = pEndianConvert;
+ }
+ else
+ Log(("ISO/UDF: Logical descriptor has lower sequence number than the previous! (%u >= %u)\n",
+ RT_LE2H_U32(pDesc->uVolumeDescSeqNo), pInfo->apLogicalVols[i]->uVolumeDescSeqNo));
+ break;
+ }
+ if (i >= pInfo->cLogicalVols)
+ {
+ /*
+ * It wasn't. Append it.
+ */
+ i = pInfo->cLogicalVols;
+ if (i < RT_ELEMENTS(pInfo->apLogicalVols))
+ {
+ pInfo->apLogicalVols[i] = pEndianConvert = (PUDFLOGICALVOLUMEDESC)RTMemDup(pDesc, cbDesc);
+ if (pEndianConvert)
+ pInfo->cLogicalVols = i + 1;
+ else
+ return VERR_NO_MEMORY;
+ Log2(("ISO/UDF: ++New logical volume descriptor.\n"));
+ }
+ else
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_ISOFS_TOO_MANY_LVDS, "Too many logical volume descriptors");
+ }
+
+#ifdef RT_BIG_ENDIAN
+ /*
+ * Do endian conversion of the descriptor.
+ */
+ if (pEndianConvert)
+ {
+ AssertFailed();
+ }
+#else
+ RT_NOREF(pEndianConvert);
+#endif
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Processes an partition descriptor in the VDS (UDF).
+ *
+ * @returns IPRT status code.
+ * @param pInfo Where we gather descriptor information.
+ * @param pDesc The descriptor.
+ * @param pErrInfo Where to return extended error information.
+ */
+static int rtFsIsoVolProcessUdfPartitionDesc(PRTFSISOVDSINFO pInfo, PCUDFPARTITIONDESC pDesc, PRTERRINFO pErrInfo)
+{
+#ifdef LOG_ENABLED
+ Log(("ISO/UDF: Partition descriptor at sector %#RX32\n", pDesc->Tag.offTag));
+ if (LogIs2Enabled())
+ {
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", uVolumeDescSeqNo);
+ UDF_LOG2_MEMBER(pDesc, "#06RX16", fFlags);
+ UDF_LOG2_MEMBER(pDesc, "#06RX16", uPartitionNo);
+ UDF_LOG2_MEMBER_ENTITY_ID(pDesc, PartitionContents);
+ if (UDF_ENTITY_ID_EQUALS(&pDesc->PartitionContents, UDF_ENTITY_ID_PD_PARTITION_CONTENTS_UDF))
+ {
+ UDF_LOG2_MEMBER_SHORTAD(&pDesc->ContentsUse, Hdr.UnallocatedSpaceTable);
+ UDF_LOG2_MEMBER_SHORTAD(&pDesc->ContentsUse, Hdr.UnallocatedSpaceBitmap);
+ UDF_LOG2_MEMBER_SHORTAD(&pDesc->ContentsUse, Hdr.PartitionIntegrityTable);
+ UDF_LOG2_MEMBER_SHORTAD(&pDesc->ContentsUse, Hdr.FreedSpaceTable);
+ UDF_LOG2_MEMBER_SHORTAD(&pDesc->ContentsUse, Hdr.FreedSpaceBitmap);
+ if (!ASMMemIsZero(&pDesc->ContentsUse.Hdr.abReserved[0], sizeof(pDesc->ContentsUse.Hdr.abReserved)))
+ Log2(("ISO/UDF: %-32s\n%.88RhxD\n", "Hdr.abReserved[88]:", &pDesc->ContentsUse.Hdr.abReserved[0]));
+ }
+ else if (!ASMMemIsZero(&pDesc->ContentsUse.ab[0], sizeof(pDesc->ContentsUse.ab)))
+ Log2(("ISO/UDF: %-32s\n%.128RhxD\n", "ContentsUse.ab[128]:", &pDesc->ContentsUse.ab[0]));
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", uAccessType);
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", offLocation);
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", cSectors);
+ UDF_LOG2_MEMBER_ENTITY_ID(pDesc, idImplementation);
+ if (!ASMMemIsZero(&pDesc->ImplementationUse.ab[0], sizeof(pDesc->ImplementationUse.ab)))
+ Log2(("ISO/UDF: %-32s\n%.128RhxD\n", "ImplementationUse.ab[128]:", &pDesc->ImplementationUse.ab[0]));
+
+ if (!ASMMemIsZero(&pDesc->abReserved[0], sizeof(pDesc->abReserved)))
+ Log2(("ISO/UDF: %-32s\n%.156RhxD\n", "ImplementationUse.ab[156]:", &pDesc->abReserved[0]));
+ }
+#endif
+
+ /*
+ * Check if this is a newer revision of an existing primary volume descriptor.
+ */
+ PUDFPARTITIONDESC pEndianConvert = NULL;
+ uint32_t i = pInfo->cPartitions;
+ while (i--> 0)
+ if (pDesc->uPartitionNo == pInfo->apPartitions[i]->uPartitionNo)
+ {
+ if (RT_LE2H_U32(pDesc->uVolumeDescSeqNo) >= pInfo->apPartitions[i]->uVolumeDescSeqNo)
+ {
+ Log(("ISO/UDF: Partition descriptor for part %#u prevails over previous! (%u >= %u)\n",
+ pDesc->uPartitionNo, RT_LE2H_U32(pDesc->uVolumeDescSeqNo), pInfo->apPartitions[i]->uVolumeDescSeqNo));
+ pEndianConvert = pInfo->apPartitions[i];
+ memcpy(pEndianConvert, pDesc, sizeof(*pDesc));
+ }
+ else
+ Log(("ISO/UDF: Partition descriptor for part %#u has a lower sequence number than the previous! (%u < %u)\n",
+ pDesc->uPartitionNo, RT_LE2H_U32(pDesc->uVolumeDescSeqNo), pInfo->apPartitions[i]->uVolumeDescSeqNo));
+ break;
+ }
+ if (i >= pInfo->cPartitions)
+ {
+ /*
+ * It wasn't. Append it.
+ */
+ i = pInfo->cPartitions;
+ if (i < RT_ELEMENTS(pInfo->apPartitions))
+ {
+ pInfo->apPartitions[i] = pEndianConvert = (PUDFPARTITIONDESC)RTMemDup(pDesc, sizeof(*pDesc));
+ if (pEndianConvert)
+ pInfo->cPartitions = i + 1;
+ else
+ return VERR_NO_MEMORY;
+ Log2(("ISO/UDF: ++New partition descriptor.\n"));
+ }
+ else
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_ISOFS_TOO_MANY_PDS, "Too many physical volume descriptors");
+ }
+
+#ifdef RT_BIG_ENDIAN
+ /*
+ * Do endian conversion of the descriptor.
+ */
+ if (pEndianConvert)
+ {
+ AssertFailed();
+ }
+#else
+ RT_NOREF(pEndianConvert);
+#endif
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Processes an implementation use descriptor in the VDS (UDF).
+ *
+ * @returns IPRT status code.
+ * @param pInfo Where we gather descriptor information.
+ * @param pDesc The descriptor.
+ * @param pErrInfo Where to return extended error information.
+ */
+static int rtFsIsoVolProcessUdfImplUseVolDesc(PRTFSISOVDSINFO pInfo, PCUDFIMPLEMENTATIONUSEVOLUMEDESC pDesc, PRTERRINFO pErrInfo)
+{
+#ifdef LOG_ENABLED
+ Log(("ISO/UDF: Implementation use volume descriptor at sector %#RX32\n", pDesc->Tag.offTag));
+ if (LogIs2Enabled())
+ {
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", uVolumeDescSeqNo);
+ UDF_LOG2_MEMBER_ENTITY_ID(pDesc, idImplementation);
+ if (UDF_ENTITY_ID_EQUALS(&pDesc->idImplementation, UDF_ENTITY_ID_IUVD_IMPLEMENTATION))
+ {
+ UDF_LOG2_MEMBER_CHARSPEC(&pDesc->ImplementationUse, Lvi.Charset);
+ UDF_LOG2_MEMBER_DSTRING(&pDesc->ImplementationUse, Lvi.achVolumeID);
+ UDF_LOG2_MEMBER_DSTRING(&pDesc->ImplementationUse, Lvi.achInfo1);
+ UDF_LOG2_MEMBER_DSTRING(&pDesc->ImplementationUse, Lvi.achInfo2);
+ UDF_LOG2_MEMBER_DSTRING(&pDesc->ImplementationUse, Lvi.achInfo3);
+ UDF_LOG2_MEMBER_ENTITY_ID(&pDesc->ImplementationUse, Lvi.idImplementation);
+ if (!ASMMemIsZero(&pDesc->ImplementationUse.Lvi.abUse[0], sizeof(pDesc->ImplementationUse.Lvi.abUse)))
+ Log2(("ISO/UDF: %-32s\n%.128RhxD\n", "Lvi.abUse[128]:", &pDesc->ImplementationUse.Lvi.abUse[0]));
+ }
+ else if (!ASMMemIsZero(&pDesc->ImplementationUse.ab[0], sizeof(pDesc->ImplementationUse.ab)))
+ Log2(("ISO/UDF: %-32s\n%.460RhxD\n", "ImplementationUse.ab[460]:", &pDesc->ImplementationUse.ab[0]));
+ }
+#endif
+
+ RT_NOREF(pInfo, pDesc, pErrInfo);
+ return VINF_SUCCESS;
+}
+
+
+
+typedef struct RTFSISOSEENSEQENCES
+{
+ /** Number of sequences we've seen thus far. */
+ uint32_t cSequences;
+ /** The per sequence data. */
+ struct
+ {
+ uint64_t off; /**< Byte offset of the sequence. */
+ uint32_t cb; /**< Size of the sequence. */
+ } aSequences[8];
+} RTFSISOSEENSEQENCES;
+typedef RTFSISOSEENSEQENCES *PRTFSISOSEENSEQENCES;
+
+
+
+/**
+ * Process a VDS sequence, recursively dealing with volume descriptor pointers.
+ *
+ * This function only gathers information from the sequence, handling the
+ * prevailing descriptor fun.
+ *
+ * @returns IPRT status code.
+ * @param pThis The instance.
+ * @param pInfo Where to store info from the VDS sequence.
+ * @param offSeq The byte offset of the sequence.
+ * @param cbSeq The length of the sequence.
+ * @param pbBuf Read buffer.
+ * @param cbBuf Size of the read buffer. This is at least one
+ * sector big.
+ * @param cNestings The VDS nesting depth.
+ * @param pErrInfo Where to return extended error info.
+ */
+static int rtFsIsoVolReadAndProcessUdfVdsSeq(PRTFSISOVOL pThis, PRTFSISOVDSINFO pInfo, uint64_t offSeq, uint32_t cbSeq,
+ uint8_t *pbBuf, size_t cbBuf, uint32_t cNestings, PRTERRINFO pErrInfo)
+{
+ AssertReturn(cbBuf >= pThis->cbSector, VERR_INTERNAL_ERROR);
+
+ /*
+ * Check nesting depth.
+ */
+ if (cNestings > 5)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_TOO_MUCH_DATA, "The volume descriptor sequence (VDS) is nested too deeply.");
+
+
+ /*
+ * Do the processing sector by sector to keep things simple.
+ */
+ uint32_t offInSeq = 0;
+ while (offInSeq < cbSeq)
+ {
+ int rc;
+
+ /*
+ * Read the next sector. Zero pad if less that a sector.
+ */
+ Assert((offInSeq & (pThis->cbSector - 1)) == 0);
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offSeq + offInSeq, pbBuf, pThis->cbSector, NULL);
+ if (RT_FAILURE(rc))
+ return RTERRINFO_LOG_SET_F(pErrInfo, rc, "Error reading VDS content at %RX64 (LB %#x): %Rrc",
+ offSeq + offInSeq, pThis->cbSector, rc);
+ if (cbSeq - offInSeq < pThis->cbSector)
+ memset(&pbBuf[cbSeq - offInSeq], 0, pThis->cbSector - (cbSeq - offInSeq));
+
+ /*
+ * Check tag.
+ */
+ PCUDFTAG pTag = (PCUDFTAG)pbBuf;
+ rc = rtFsIsoVolValidateUdfDescTagAndCrc(pTag, pThis->cbSector, UINT16_MAX, (offSeq + offInSeq) / pThis->cbSector, pErrInfo);
+ if ( RT_SUCCESS(rc)
+ || ( rc == VERR_ISOFS_INSUFFICIENT_DATA_FOR_DESC_CRC
+ && ( pTag->idTag == UDF_TAG_ID_LOGICAL_VOLUME_INTEGRITY_DESC
+ || pTag->idTag == UDF_TAG_ID_LOGICAL_VOLUME_DESC
+ || pTag->idTag == UDF_TAG_ID_UNALLOCATED_SPACE_DESC
+ )
+ )
+ )
+ {
+ switch (pTag->idTag)
+ {
+ case UDF_TAG_ID_PRIMARY_VOL_DESC:
+ rc = rtFsIsoVolProcessUdfPrimaryVolDesc(pInfo, (PCUDFPRIMARYVOLUMEDESC)pTag, pErrInfo);
+ break;
+
+ case UDF_TAG_ID_IMPLEMENTATION_USE_VOLUME_DESC:
+ rc = rtFsIsoVolProcessUdfImplUseVolDesc(pInfo, (PCUDFIMPLEMENTATIONUSEVOLUMEDESC)pTag, pErrInfo);
+ break;
+
+ case UDF_TAG_ID_PARTITION_DESC:
+ rc = rtFsIsoVolProcessUdfPartitionDesc(pInfo, (PCUDFPARTITIONDESC)pTag, pErrInfo);
+ break;
+
+ case UDF_TAG_ID_LOGICAL_VOLUME_DESC:
+ if (rc != VERR_ISOFS_INSUFFICIENT_DATA_FOR_DESC_CRC)
+ rc = rtFsIsoVolProcessUdfLogicalVolumeDesc(pInfo, (PCUDFLOGICALVOLUMEDESC)pTag,
+ pThis->cbSector, pErrInfo);
+ else
+ rc = VERR_ISOFS_TOO_BIT_PARTMAP_IN_LVD;
+ break;
+
+ case UDF_TAG_ID_LOGICAL_VOLUME_INTEGRITY_DESC:
+ Log(("ISO/UDF: Ignoring logical volume integrity descriptor at offset %#RX64.\n", offSeq + offInSeq));
+ rc = VINF_SUCCESS;
+ break;
+
+ case UDF_TAG_ID_UNALLOCATED_SPACE_DESC:
+ Log(("ISO/UDF: Ignoring unallocated space descriptor at offset %#RX64.\n", offSeq + offInSeq));
+ rc = VINF_SUCCESS;
+ break;
+
+ case UDF_TAG_ID_ANCHOR_VOLUME_DESC_PTR:
+ Log(("ISO/UDF: Ignoring AVDP in VDS (at offset %#RX64).\n", offSeq + offInSeq));
+ rc = VINF_SUCCESS;
+ break;
+
+ case UDF_TAG_ID_VOLUME_DESC_PTR:
+ {
+ PCUDFVOLUMEDESCPTR pVdp = (PCUDFVOLUMEDESCPTR)pTag;
+ Log(("ISO/UDF: Processing volume descriptor pointer at offset %#RX64: %#x LB %#x (seq %#x); cNestings=%d\n",
+ offSeq + offInSeq, pVdp->NextVolumeDescSeq.off, pVdp->NextVolumeDescSeq.cb,
+ pVdp->uVolumeDescSeqNo, cNestings));
+ rc = rtFsIsoVolReadAndProcessUdfVdsSeq(pThis, pInfo, (uint64_t)pVdp->NextVolumeDescSeq.off * pThis->cbSector,
+ pVdp->NextVolumeDescSeq.cb, pbBuf, cbBuf, cNestings + 1, pErrInfo);
+ break;
+ }
+
+ case UDF_TAG_ID_TERMINATING_DESC:
+ Log(("ISO/UDF: Terminating descriptor at offset %#RX64\n", offSeq + offInSeq));
+ return VINF_SUCCESS;
+
+ default:
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_UNEXPECTED_VDS_DESC,
+ "Unexpected/unknown VDS descriptor %#x at byte offset %#RX64",
+ pThis->cbSector, offSeq + offInSeq);
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ /* The descriptor sequence is usually zero padded to 16 sectors. Just
+ ignore zero descriptors. */
+ else if (rc != VERR_ISOFS_TAG_IS_ALL_ZEROS)
+ return rc;
+
+ /*
+ * Advance.
+ */
+ offInSeq += pThis->cbSector;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * Processes a volume descriptor sequence (VDS).
+ *
+ * @returns IPRT status code.
+ * @param pThis The instance.
+ * @param offSeq The byte offset of the sequence.
+ * @param cbSeq The length of the sequence.
+ * @param pSeenSequences Structure where to keep track of VDSes we've already
+ * processed, to avoid redoing one that we don't
+ * understand.
+ * @param pbBuf Read buffer.
+ * @param cbBuf Size of the read buffer. This is at least one
+ * sector big.
+ * @param pErrInfo Where to report extended error information.
+ */
+static int rtFsIsoVolReadAndProcessUdfVds(PRTFSISOVOL pThis, uint64_t offSeq, uint32_t cbSeq,
+ PRTFSISOSEENSEQENCES pSeenSequences, uint8_t *pbBuf, size_t cbBuf,
+ PRTERRINFO pErrInfo)
+{
+ /*
+ * Skip if already seen.
+ */
+ uint32_t i = pSeenSequences->cSequences;
+ while (i-- > 0)
+ if ( pSeenSequences->aSequences[i].off == offSeq
+ && pSeenSequences->aSequences[i].cb == cbSeq)
+ return VERR_NOT_FOUND;
+
+ /* Not seen, so add it. */
+ Assert(pSeenSequences->cSequences + 1 <= RT_ELEMENTS(pSeenSequences->aSequences));
+ pSeenSequences->aSequences[pSeenSequences->cSequences].cb = cbSeq;
+ pSeenSequences->aSequences[pSeenSequences->cSequences].off = offSeq;
+ pSeenSequences->cSequences++;
+
+ LogFlow(("ISO/UDF: Processing anchor volume descriptor sequence at offset %#RX64 LB %#RX32\n", offSeq, cbSeq));
+
+ /*
+ * Gather relevant descriptor info from the VDS then process it and on
+ * success copy it into the instance.
+ *
+ * The processing has to be done in a different function because there may
+ * be links to sub-sequences that needs to be processed. We do this by
+ * recursing and check that we don't go to deep.
+ */
+ RTFSISOVDSINFO Info;
+ RT_ZERO(Info);
+ int rc = rtFsIsoVolReadAndProcessUdfVdsSeq(pThis, &Info, offSeq, cbSeq, pbBuf, cbBuf, 0, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsIsoVolProcessUdfVdsSeqInfo(pThis, &Info, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFsIsoVolProcessUdfFileSetDescs(pThis, pbBuf, cbBuf, pErrInfo);
+ }
+
+ /*
+ * Clean up info.
+ */
+ i = Info.cPrimaryVols;
+ while (i-- > 0)
+ RTMemFree(Info.apPrimaryVols[i]);
+
+ i = Info.cLogicalVols;
+ while (i-- > 0)
+ RTMemFree(Info.apLogicalVols[i]);
+
+ i = Info.cPartitions;
+ while (i-- > 0)
+ RTMemFree(Info.apPartitions[i]);
+
+ RTMemFree(Info.paPartMaps);
+
+ return rc;
+}
+
+
+static int rtFsIsoVolReadAndHandleUdfAvdp(PRTFSISOVOL pThis, uint64_t offAvdp, uint8_t *pbBuf, size_t cbBuf,
+ PRTFSISOSEENSEQENCES pSeenSequences, PRTERRINFO pErrInfo)
+{
+ /*
+ * Try read the descriptor and validate its tag.
+ */
+ PUDFANCHORVOLUMEDESCPTR pAvdp = (PUDFANCHORVOLUMEDESCPTR)pbBuf;
+ size_t cbAvdpRead = RT_MIN(pThis->cbSector, cbBuf);
+ int rc = RTVfsFileReadAt(pThis->hVfsBacking, offAvdp, pAvdp, cbAvdpRead, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsIsoVolValidateUdfDescTag(&pAvdp->Tag, UDF_TAG_ID_ANCHOR_VOLUME_DESC_PTR, offAvdp / pThis->cbSector, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ Log2(("ISO/UDF: AVDP: MainVolumeDescSeq=%#RX32 LB %#RX32, ReserveVolumeDescSeq=%#RX32 LB %#RX32\n",
+ pAvdp->MainVolumeDescSeq.off, pAvdp->MainVolumeDescSeq.cb,
+ pAvdp->ReserveVolumeDescSeq.off, pAvdp->ReserveVolumeDescSeq.cb));
+
+ /*
+ * Try the main sequence if it looks sane.
+ */
+ UDFEXTENTAD const ReserveVolumeDescSeq = pAvdp->ReserveVolumeDescSeq;
+ if ( pAvdp->MainVolumeDescSeq.off < pThis->cBackingSectors
+ && (uint64_t)pAvdp->MainVolumeDescSeq.off
+ + (pAvdp->MainVolumeDescSeq.cb + pThis->cbSector - 1) / pThis->cbSector
+ <= pThis->cBackingSectors)
+ {
+ rc = rtFsIsoVolReadAndProcessUdfVds(pThis, (uint64_t)pAvdp->MainVolumeDescSeq.off * pThis->cbSector,
+ pAvdp->MainVolumeDescSeq.cb, pSeenSequences, pbBuf, cbBuf, pErrInfo);
+ if (RT_SUCCESS(rc))
+ return rc;
+ }
+ else
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_NOT_FOUND,
+ "MainVolumeDescSeq is out of bounds: sector %#RX32 LB %#RX32 bytes, image is %#RX64 sectors",
+ pAvdp->MainVolumeDescSeq.off, pAvdp->MainVolumeDescSeq.cb, pThis->cBackingSectors);
+ if (ReserveVolumeDescSeq.cb > 0)
+ {
+ if ( ReserveVolumeDescSeq.off < pThis->cBackingSectors
+ && (uint64_t)ReserveVolumeDescSeq.off
+ + (ReserveVolumeDescSeq.cb + pThis->cbSector - 1) / pThis->cbSector
+ <= pThis->cBackingSectors)
+ {
+ rc = rtFsIsoVolReadAndProcessUdfVds(pThis, (uint64_t)ReserveVolumeDescSeq.off * pThis->cbSector,
+ ReserveVolumeDescSeq.cb, pSeenSequences, pbBuf, cbBuf, pErrInfo);
+ if (RT_SUCCESS(rc))
+ return rc;
+ }
+ else if (RT_SUCCESS(rc))
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_NOT_FOUND,
+ "ReserveVolumeDescSeq is out of bounds: sector %#RX32 LB %#RX32 bytes, image is %#RX64 sectors",
+ ReserveVolumeDescSeq.off, ReserveVolumeDescSeq.cb, pThis->cBackingSectors);
+ }
+ }
+ }
+ else
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, rc,
+ "Error reading sector at offset %#RX64 (anchor volume descriptor pointer): %Rrc", offAvdp, rc);
+
+ return rc;
+}
+
+
+/**
+ * Goes looking for UDF when we've seens a volume recognition sequence.
+ *
+ * @returns IPRT status code.
+ * @param pThis The volume instance data.
+ * @param puUdfLevel The UDF level indicated by the VRS.
+ * @param offUdfBootVolDesc The offset of the BOOT2 descriptor, UINT64_MAX
+ * if not encountered.
+ * @param pbBuf Buffer for reading into.
+ * @param cbBuf The size of the buffer. At least one sector.
+ * @param pErrInfo Where to return extended error info.
+ */
+static int rtFsIsoVolHandleUdfDetection(PRTFSISOVOL pThis, uint8_t *puUdfLevel, uint64_t offUdfBootVolDesc,
+ uint8_t *pbBuf, size_t cbBuf, PRTERRINFO pErrInfo)
+{
+ NOREF(offUdfBootVolDesc);
+
+ /*
+ * There are up to three anchor volume descriptor pointers that can give us
+ * two different descriptor sequences each. Usually, the different AVDP
+ * structures points to the same two sequences. The idea here is that
+ * sectors may deteriorate and become unreadable, and we're supposed to try
+ * out alternative sectors to get the job done. If we really took this
+ * seriously, we could try read all sequences in parallel and use the
+ * sectors that are good. However, we'll try keep things reasonably simple
+ * since we'll most likely be reading from hard disks rather than optical
+ * media.
+ *
+ * We keep track of which sequences we've processed so we don't try to do it
+ * again when alternative AVDP sectors points to the same sequences.
+ */
+ pThis->Udf.uLevel = *puUdfLevel;
+ RTFSISOSEENSEQENCES SeenSequences;
+ RT_ZERO(SeenSequences);
+ int rc1 = rtFsIsoVolReadAndHandleUdfAvdp(pThis, 256 * pThis->cbSector, pbBuf, cbBuf,
+ &SeenSequences, pErrInfo);
+ if (RT_SUCCESS(rc1))
+ return rc1;
+
+ int rc2 = rtFsIsoVolReadAndHandleUdfAvdp(pThis, pThis->cbBacking - 256 * pThis->cbSector,
+ pbBuf, cbBuf, &SeenSequences, pErrInfo);
+ if (RT_SUCCESS(rc2))
+ return rc2;
+
+ int rc3 = rtFsIsoVolReadAndHandleUdfAvdp(pThis, pThis->cbBacking - pThis->cbSector,
+ pbBuf, cbBuf, &SeenSequences, pErrInfo);
+ if (RT_SUCCESS(rc3))
+ return rc3;
+
+ /*
+ * Return failure if the alternatives have been excluded.
+ *
+ * Note! The error info won't be correct here.
+ */
+ pThis->Udf.uLevel = *puUdfLevel = 0;
+
+ if (RTFSISO9660_F_IS_ONLY_TYPE(pThis->fFlags, RTFSISO9660_F_NO_UDF))
+ return rc1 != VERR_NOT_FOUND ? rc1 : rc2 != VERR_NOT_FOUND ? rc2 : rc3;
+ return VINF_SUCCESS;
+}
+
+
+
+#ifdef LOG_ENABLED
+
+/** Logging helper. */
+static size_t rtFsIsoVolGetStrippedLength(const char *pachField, size_t cchField)
+{
+ while (cchField > 0 && pachField[cchField - 1] == ' ')
+ cchField--;
+ return cchField;
+}
+
+/** Logging helper. */
+static char *rtFsIsoVolGetMaybeUtf16Be(const char *pachField, size_t cchField, char *pszDst, size_t cbDst)
+{
+ /* Check the format by looking for zero bytes. ISO-9660 doesn't allow zeros.
+ This doesn't have to be a UTF-16BE string. */
+ size_t cFirstZeros = 0;
+ size_t cSecondZeros = 0;
+ for (size_t off = 0; off + 1 < cchField; off += 2)
+ {
+ cFirstZeros += pachField[off] == '\0';
+ cSecondZeros += pachField[off + 1] == '\0';
+ }
+
+ int rc = VINF_SUCCESS;
+ char *pszTmp = &pszDst[10];
+ size_t cchRet = 0;
+ if (cFirstZeros > cSecondZeros)
+ {
+ /* UTF-16BE / UTC-2BE: */
+ if (cchField & 1)
+ {
+ if (pachField[cchField - 1] == '\0' || pachField[cchField - 1] == ' ')
+ cchField--;
+ else
+ rc = VERR_INVALID_UTF16_ENCODING;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ while ( cchField >= 2
+ && pachField[cchField - 1] == ' '
+ && pachField[cchField - 2] == '\0')
+ cchField -= 2;
+
+ rc = RTUtf16BigToUtf8Ex((PCRTUTF16)pachField, cchField / sizeof(RTUTF16), &pszTmp, cbDst - 10 - 1, &cchRet);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pszDst[0] = 'U';
+ pszDst[1] = 'T';
+ pszDst[2] = 'F';
+ pszDst[3] = '-';
+ pszDst[4] = '1';
+ pszDst[5] = '6';
+ pszDst[6] = 'B';
+ pszDst[7] = 'E';
+ pszDst[8] = ':';
+ pszDst[9] = '\'';
+ pszDst[10 + cchRet] = '\'';
+ pszDst[10 + cchRet + 1] = '\0';
+ }
+ else
+ RTStrPrintf(pszDst, cbDst, "UTF-16BE: %.*Rhxs", cchField, pachField);
+ }
+ else if (cSecondZeros > 0)
+ {
+ /* Little endian UTF-16 / UCS-2 (ASSUMES host is little endian, sorry) */
+ if (cchField & 1)
+ {
+ if (pachField[cchField - 1] == '\0' || pachField[cchField - 1] == ' ')
+ cchField--;
+ else
+ rc = VERR_INVALID_UTF16_ENCODING;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ while ( cchField >= 2
+ && pachField[cchField - 1] == '\0'
+ && pachField[cchField - 2] == ' ')
+ cchField -= 2;
+
+ rc = RTUtf16ToUtf8Ex((PCRTUTF16)pachField, cchField / sizeof(RTUTF16), &pszTmp, cbDst - 10 - 1, &cchRet);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pszDst[0] = 'U';
+ pszDst[1] = 'T';
+ pszDst[2] = 'F';
+ pszDst[3] = '-';
+ pszDst[4] = '1';
+ pszDst[5] = '6';
+ pszDst[6] = 'L';
+ pszDst[7] = 'E';
+ pszDst[8] = ':';
+ pszDst[9] = '\'';
+ pszDst[10 + cchRet] = '\'';
+ pszDst[10 + cchRet + 1] = '\0';
+ }
+ else
+ RTStrPrintf(pszDst, cbDst, "UTF-16LE: %.*Rhxs", cchField, pachField);
+ }
+ else
+ {
+ /* ASSUME UTF-8/ASCII. */
+ while ( cchField > 0
+ && pachField[cchField - 1] == ' ')
+ cchField--;
+ rc = RTStrValidateEncodingEx(pachField, cchField, RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
+ if (RT_SUCCESS(rc))
+ RTStrPrintf(pszDst, cbDst, "UTF-8: '%.*s'", cchField, pachField);
+ else
+ RTStrPrintf(pszDst, cbDst, "UNK-8: %.*Rhxs", cchField, pachField);
+ }
+ return pszDst;
+}
+
+
+/**
+ * Logs the primary or supplementary volume descriptor
+ *
+ * @param pVolDesc The descriptor.
+ */
+static void rtFsIsoVolLogPrimarySupplementaryVolDesc(PCISO9660SUPVOLDESC pVolDesc)
+{
+ if (LogIs2Enabled())
+ {
+ char szTmp[384];
+ Log2(("ISO9660: fVolumeFlags: %#RX8\n", pVolDesc->fVolumeFlags));
+ Log2(("ISO9660: achSystemId: %s\n", rtFsIsoVolGetMaybeUtf16Be(pVolDesc->achSystemId, sizeof(pVolDesc->achSystemId), szTmp, sizeof(szTmp)) ));
+ Log2(("ISO9660: achVolumeId: %s\n", rtFsIsoVolGetMaybeUtf16Be(pVolDesc->achVolumeId, sizeof(pVolDesc->achVolumeId), szTmp, sizeof(szTmp)) ));
+ Log2(("ISO9660: Unused73: {%#RX32,%#RX32}\n", RT_BE2H_U32(pVolDesc->Unused73.be), RT_LE2H_U32(pVolDesc->Unused73.le)));
+ Log2(("ISO9660: VolumeSpaceSize: {%#RX32,%#RX32}\n", RT_BE2H_U32(pVolDesc->VolumeSpaceSize.be), RT_LE2H_U32(pVolDesc->VolumeSpaceSize.le)));
+ Log2(("ISO9660: abEscapeSequences: '%.*s'\n", rtFsIsoVolGetStrippedLength((char *)pVolDesc->abEscapeSequences, sizeof(pVolDesc->abEscapeSequences)), pVolDesc->abEscapeSequences));
+ Log2(("ISO9660: cVolumesInSet: {%#RX16,%#RX16}\n", RT_BE2H_U16(pVolDesc->cVolumesInSet.be), RT_LE2H_U16(pVolDesc->cVolumesInSet.le)));
+ Log2(("ISO9660: VolumeSeqNo: {%#RX16,%#RX16}\n", RT_BE2H_U16(pVolDesc->VolumeSeqNo.be), RT_LE2H_U16(pVolDesc->VolumeSeqNo.le)));
+ Log2(("ISO9660: cbLogicalBlock: {%#RX16,%#RX16}\n", RT_BE2H_U16(pVolDesc->cbLogicalBlock.be), RT_LE2H_U16(pVolDesc->cbLogicalBlock.le)));
+ Log2(("ISO9660: cbPathTable: {%#RX32,%#RX32}\n", RT_BE2H_U32(pVolDesc->cbPathTable.be), RT_LE2H_U32(pVolDesc->cbPathTable.le)));
+ Log2(("ISO9660: offTypeLPathTable: %#RX32\n", RT_LE2H_U32(pVolDesc->offTypeLPathTable)));
+ Log2(("ISO9660: offOptionalTypeLPathTable: %#RX32\n", RT_LE2H_U32(pVolDesc->offOptionalTypeLPathTable)));
+ Log2(("ISO9660: offTypeMPathTable: %#RX32\n", RT_BE2H_U32(pVolDesc->offTypeMPathTable)));
+ Log2(("ISO9660: offOptionalTypeMPathTable: %#RX32\n", RT_BE2H_U32(pVolDesc->offOptionalTypeMPathTable)));
+ Log2(("ISO9660: achVolumeSetId: %s\n", rtFsIsoVolGetMaybeUtf16Be(pVolDesc->achVolumeSetId, sizeof(pVolDesc->achVolumeSetId), szTmp, sizeof(szTmp)) ));
+ Log2(("ISO9660: achPublisherId: %s\n", rtFsIsoVolGetMaybeUtf16Be(pVolDesc->achPublisherId, sizeof(pVolDesc->achPublisherId), szTmp, sizeof(szTmp)) ));
+ Log2(("ISO9660: achDataPreparerId: %s\n", rtFsIsoVolGetMaybeUtf16Be(pVolDesc->achDataPreparerId, sizeof(pVolDesc->achDataPreparerId), szTmp, sizeof(szTmp)) ));
+ Log2(("ISO9660: achApplicationId: %s\n", rtFsIsoVolGetMaybeUtf16Be(pVolDesc->achApplicationId, sizeof(pVolDesc->achApplicationId), szTmp, sizeof(szTmp)) ));
+ Log2(("ISO9660: achCopyrightFileId: %s\n", rtFsIsoVolGetMaybeUtf16Be(pVolDesc->achCopyrightFileId, sizeof(pVolDesc->achCopyrightFileId), szTmp, sizeof(szTmp)) ));
+ Log2(("ISO9660: achAbstractFileId: %s\n", rtFsIsoVolGetMaybeUtf16Be(pVolDesc->achAbstractFileId, sizeof(pVolDesc->achAbstractFileId), szTmp, sizeof(szTmp)) ));
+ Log2(("ISO9660: achBibliographicFileId: %s\n", rtFsIsoVolGetMaybeUtf16Be(pVolDesc->achBibliographicFileId, sizeof(pVolDesc->achBibliographicFileId), szTmp, sizeof(szTmp)) ));
+ Log2(("ISO9660: BirthTime: %.4s-%.2s-%.2s %.2s:%.2s:%.2s.%.2s%+03d\n",
+ pVolDesc->BirthTime.achYear,
+ pVolDesc->BirthTime.achMonth,
+ pVolDesc->BirthTime.achDay,
+ pVolDesc->BirthTime.achHour,
+ pVolDesc->BirthTime.achMinute,
+ pVolDesc->BirthTime.achSecond,
+ pVolDesc->BirthTime.achCentisecond,
+ pVolDesc->BirthTime.offUtc*4/60));
+ Log2(("ISO9660: ModifyTime: %.4s-%.2s-%.2s %.2s:%.2s:%.2s.%.2s%+03d\n",
+ pVolDesc->ModifyTime.achYear,
+ pVolDesc->ModifyTime.achMonth,
+ pVolDesc->ModifyTime.achDay,
+ pVolDesc->ModifyTime.achHour,
+ pVolDesc->ModifyTime.achMinute,
+ pVolDesc->ModifyTime.achSecond,
+ pVolDesc->ModifyTime.achCentisecond,
+ pVolDesc->ModifyTime.offUtc*4/60));
+ Log2(("ISO9660: ExpireTime: %.4s-%.2s-%.2s %.2s:%.2s:%.2s.%.2s%+03d\n",
+ pVolDesc->ExpireTime.achYear,
+ pVolDesc->ExpireTime.achMonth,
+ pVolDesc->ExpireTime.achDay,
+ pVolDesc->ExpireTime.achHour,
+ pVolDesc->ExpireTime.achMinute,
+ pVolDesc->ExpireTime.achSecond,
+ pVolDesc->ExpireTime.achCentisecond,
+ pVolDesc->ExpireTime.offUtc*4/60));
+ Log2(("ISO9660: EffectiveTime: %.4s-%.2s-%.2s %.2s:%.2s:%.2s.%.2s%+03d\n",
+ pVolDesc->EffectiveTime.achYear,
+ pVolDesc->EffectiveTime.achMonth,
+ pVolDesc->EffectiveTime.achDay,
+ pVolDesc->EffectiveTime.achHour,
+ pVolDesc->EffectiveTime.achMinute,
+ pVolDesc->EffectiveTime.achSecond,
+ pVolDesc->EffectiveTime.achCentisecond,
+ pVolDesc->EffectiveTime.offUtc*4/60));
+ Log2(("ISO9660: bFileStructureVersion: %#RX8\n", pVolDesc->bFileStructureVersion));
+ Log2(("ISO9660: bReserved883: %#RX8\n", pVolDesc->bReserved883));
+
+ Log2(("ISO9660: RootDir.cbDirRec: %#RX8\n", pVolDesc->RootDir.DirRec.cbDirRec));
+ Log2(("ISO9660: RootDir.cExtAttrBlocks: %#RX8\n", pVolDesc->RootDir.DirRec.cExtAttrBlocks));
+ Log2(("ISO9660: RootDir.offExtent: {%#RX32,%#RX32}\n", RT_BE2H_U32(pVolDesc->RootDir.DirRec.offExtent.be), RT_LE2H_U32(pVolDesc->RootDir.DirRec.offExtent.le)));
+ Log2(("ISO9660: RootDir.cbData: {%#RX32,%#RX32}\n", RT_BE2H_U32(pVolDesc->RootDir.DirRec.cbData.be), RT_LE2H_U32(pVolDesc->RootDir.DirRec.cbData.le)));
+ Log2(("ISO9660: RootDir.RecTime: %04u-%02u-%02u %02u:%02u:%02u%+03d\n",
+ pVolDesc->RootDir.DirRec.RecTime.bYear + 1900,
+ pVolDesc->RootDir.DirRec.RecTime.bMonth,
+ pVolDesc->RootDir.DirRec.RecTime.bDay,
+ pVolDesc->RootDir.DirRec.RecTime.bHour,
+ pVolDesc->RootDir.DirRec.RecTime.bMinute,
+ pVolDesc->RootDir.DirRec.RecTime.bSecond,
+ pVolDesc->RootDir.DirRec.RecTime.offUtc*4/60));
+ Log2(("ISO9660: RootDir.RecTime.fFileFlags: %RX8\n", pVolDesc->RootDir.DirRec.fFileFlags));
+ Log2(("ISO9660: RootDir.RecTime.bFileUnitSize: %RX8\n", pVolDesc->RootDir.DirRec.bFileUnitSize));
+ Log2(("ISO9660: RootDir.RecTime.bInterleaveGapSize: %RX8\n", pVolDesc->RootDir.DirRec.bInterleaveGapSize));
+ Log2(("ISO9660: RootDir.RecTime.VolumeSeqNo: {%#RX16,%#RX16}\n", RT_BE2H_U16(pVolDesc->RootDir.DirRec.VolumeSeqNo.be), RT_LE2H_U16(pVolDesc->RootDir.DirRec.VolumeSeqNo.le)));
+ Log2(("ISO9660: RootDir.RecTime.bFileIdLength: %RX8\n", pVolDesc->RootDir.DirRec.bFileIdLength));
+ Log2(("ISO9660: RootDir.RecTime.achFileId: '%.*s'\n", pVolDesc->RootDir.DirRec.bFileIdLength, pVolDesc->RootDir.DirRec.achFileId));
+ uint32_t offSysUse = RT_UOFFSETOF_DYN(ISO9660DIRREC, achFileId[pVolDesc->RootDir.DirRec.bFileIdLength])
+ + !(pVolDesc->RootDir.DirRec.bFileIdLength & 1);
+ if (offSysUse < pVolDesc->RootDir.DirRec.cbDirRec)
+ {
+ Log2(("ISO9660: RootDir System Use:\n%.*RhxD\n",
+ pVolDesc->RootDir.DirRec.cbDirRec - offSysUse, &pVolDesc->RootDir.ab[offSysUse]));
+ }
+ }
+}
+
+#endif /* LOG_ENABLED */
+
+/**
+ * Deal with a root directory from a primary or supplemental descriptor.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO 9660 instance being initialized.
+ * @param pRootDir The root directory record to check out.
+ * @param pDstRootDir Where to store a copy of the root dir record.
+ * @param pErrInfo Where to return additional error info. Can be NULL.
+ */
+static int rtFsIsoVolHandleRootDir(PRTFSISOVOL pThis, PCISO9660DIRREC pRootDir,
+ PISO9660DIRREC pDstRootDir, PRTERRINFO pErrInfo)
+{
+ if (pRootDir->cbDirRec < RT_UOFFSETOF(ISO9660DIRREC, achFileId))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Root dir record size is too small: %#x (min %#x)",
+ pRootDir->cbDirRec, RT_UOFFSETOF(ISO9660DIRREC, achFileId));
+
+ if (!(pRootDir->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Root dir is not flagged as directory: %#x", pRootDir->fFileFlags);
+ if (pRootDir->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Root dir is cannot be multi-extent: %#x", pRootDir->fFileFlags);
+
+ if (RT_LE2H_U32(pRootDir->cbData.le) != RT_BE2H_U32(pRootDir->cbData.be))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Invalid root dir size: {%#RX32,%#RX32}",
+ RT_BE2H_U32(pRootDir->cbData.be), RT_LE2H_U32(pRootDir->cbData.le));
+ if (RT_LE2H_U32(pRootDir->cbData.le) == 0)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Zero sized root dir");
+
+ if (RT_LE2H_U32(pRootDir->offExtent.le) != RT_BE2H_U32(pRootDir->offExtent.be))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Invalid root dir extent: {%#RX32,%#RX32}",
+ RT_BE2H_U32(pRootDir->offExtent.be), RT_LE2H_U32(pRootDir->offExtent.le));
+
+ if (RT_LE2H_U16(pRootDir->VolumeSeqNo.le) != RT_BE2H_U16(pRootDir->VolumeSeqNo.be))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Invalid root dir volume sequence ID: {%#RX16,%#RX16}",
+ RT_BE2H_U16(pRootDir->VolumeSeqNo.be), RT_LE2H_U16(pRootDir->VolumeSeqNo.le));
+ if (RT_LE2H_U16(pRootDir->VolumeSeqNo.le) != pThis->idPrimaryVol)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Expected root dir to have same volume sequence number as primary volume: %#x, expected %#x",
+ RT_LE2H_U16(pRootDir->VolumeSeqNo.le), pThis->idPrimaryVol);
+
+ /*
+ * Seems okay, copy it.
+ */
+ *pDstRootDir = *pRootDir;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Deal with a primary volume descriptor.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO 9660 instance being initialized.
+ * @param pVolDesc The volume descriptor to handle.
+ * @param offVolDesc The disk offset of the volume descriptor.
+ * @param pRootDir Where to return a copy of the root directory record.
+ * @param poffRootDirRec Where to return the disk offset of the root dir.
+ * @param pErrInfo Where to return additional error info. Can be NULL.
+ */
+static int rtFsIsoVolHandlePrimaryVolDesc(PRTFSISOVOL pThis, PCISO9660PRIMARYVOLDESC pVolDesc, uint32_t offVolDesc,
+ PISO9660DIRREC pRootDir, uint64_t *poffRootDirRec, PRTERRINFO pErrInfo)
+{
+ if (pVolDesc->bFileStructureVersion != ISO9660_FILE_STRUCTURE_VERSION)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Unsupported file structure version: %#x", pVolDesc->bFileStructureVersion);
+
+ /*
+ * Take down the location of the primary volume descriptor so we can get
+ * the volume lable and other info from it later.
+ */
+ pThis->offPrimaryVolDesc = offVolDesc;
+
+ /*
+ * We need the block size ...
+ */
+ pThis->cbBlock = RT_LE2H_U16(pVolDesc->cbLogicalBlock.le);
+ if ( pThis->cbBlock != RT_BE2H_U16(pVolDesc->cbLogicalBlock.be)
+ || !RT_IS_POWER_OF_TWO(pThis->cbBlock)
+ || pThis->cbBlock / pThis->cbSector < 1)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Invalid logical block size: {%#RX16,%#RX16}",
+ RT_BE2H_U16(pVolDesc->cbLogicalBlock.be), RT_LE2H_U16(pVolDesc->cbLogicalBlock.le));
+ if (pThis->cbBlock / pThis->cbSector > 128)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "Unsupported block size: %#x\n", pThis->cbBlock);
+
+ /*
+ * ... volume space size ...
+ */
+ pThis->cBlocksInPrimaryVolumeSpace = RT_LE2H_U32(pVolDesc->VolumeSpaceSize.le);
+ if (pThis->cBlocksInPrimaryVolumeSpace != RT_BE2H_U32(pVolDesc->VolumeSpaceSize.be))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Invalid volume space size: {%#RX32,%#RX32}",
+ RT_BE2H_U32(pVolDesc->VolumeSpaceSize.be), RT_LE2H_U32(pVolDesc->VolumeSpaceSize.le));
+ pThis->cbPrimaryVolumeSpace = pThis->cBlocksInPrimaryVolumeSpace * (uint64_t)pThis->cbBlock;
+
+ /*
+ * ... number of volumes in the set ...
+ */
+ pThis->cVolumesInSet = RT_LE2H_U16(pVolDesc->cVolumesInSet.le);
+ if ( pThis->cVolumesInSet != RT_BE2H_U16(pVolDesc->cVolumesInSet.be)
+ || pThis->cVolumesInSet == 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Invalid volume set size: {%#RX16,%#RX16}",
+ RT_BE2H_U16(pVolDesc->cVolumesInSet.be), RT_LE2H_U16(pVolDesc->cVolumesInSet.le));
+ if (pThis->cVolumesInSet > 32)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "Too large volume set size: %#x\n", pThis->cVolumesInSet);
+
+ /*
+ * ... primary volume sequence ID ...
+ */
+ pThis->idPrimaryVol = RT_LE2H_U16(pVolDesc->VolumeSeqNo.le);
+ if (pThis->idPrimaryVol != RT_BE2H_U16(pVolDesc->VolumeSeqNo.be))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Invalid volume sequence ID: {%#RX16,%#RX16}",
+ RT_BE2H_U16(pVolDesc->VolumeSeqNo.be), RT_LE2H_U16(pVolDesc->VolumeSeqNo.le));
+ if ( pThis->idPrimaryVol > pThis->cVolumesInSet
+ || pThis->idPrimaryVol < 1)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Volume sequence ID out of of bound: %#x (1..%#x)\n", pThis->idPrimaryVol, pThis->cVolumesInSet);
+
+ /*
+ * ... and the root directory record.
+ */
+ *poffRootDirRec = offVolDesc + RT_UOFFSETOF(ISO9660PRIMARYVOLDESC, RootDir.DirRec);
+ return rtFsIsoVolHandleRootDir(pThis, &pVolDesc->RootDir.DirRec, pRootDir, pErrInfo);
+}
+
+
+/**
+ * Deal with a supplementary volume descriptor.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO 9660 instance being initialized.
+ * @param pVolDesc The volume descriptor to handle.
+ * @param offVolDesc The disk offset of the volume descriptor.
+ * @param pbUcs2Level Where to return the joliet level, if found. Caller
+ * initializes this to zero, we'll return 1, 2 or 3 if
+ * joliet was detected.
+ * @param pRootDir Where to return the root directory, if found.
+ * @param poffRootDirRec Where to return the disk offset of the root dir.
+ * @param pErrInfo Where to return additional error info. Can be NULL.
+ */
+static int rtFsIsoVolHandleSupplementaryVolDesc(PRTFSISOVOL pThis, PCISO9660SUPVOLDESC pVolDesc, uint32_t offVolDesc,
+ uint8_t *pbUcs2Level, PISO9660DIRREC pRootDir, uint64_t *poffRootDirRec,
+ PRTERRINFO pErrInfo)
+{
+ if (pVolDesc->bFileStructureVersion != ISO9660_FILE_STRUCTURE_VERSION)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Unsupported file structure version: %#x", pVolDesc->bFileStructureVersion);
+
+ /*
+ * Is this a joliet volume descriptor? If not, we probably don't need to
+ * care about it.
+ */
+ if ( pVolDesc->abEscapeSequences[0] != ISO9660_JOLIET_ESC_SEQ_0
+ || pVolDesc->abEscapeSequences[1] != ISO9660_JOLIET_ESC_SEQ_1
+ || ( pVolDesc->abEscapeSequences[2] != ISO9660_JOLIET_ESC_SEQ_2_LEVEL_1
+ && pVolDesc->abEscapeSequences[2] != ISO9660_JOLIET_ESC_SEQ_2_LEVEL_2
+ && pVolDesc->abEscapeSequences[2] != ISO9660_JOLIET_ESC_SEQ_2_LEVEL_3))
+ return VINF_SUCCESS;
+
+ /*
+ * Skip if joliet is unwanted.
+ */
+ if (pThis->fFlags & RTFSISO9660_F_NO_JOLIET)
+ return VINF_SUCCESS;
+
+ /*
+ * Check that the joliet descriptor matches the primary one.
+ * Note! These are our assumptions and may be wrong.
+ */
+ if (pThis->cbBlock == 0)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Supplementary joliet volume descriptor is not supported when appearing before the primary volume descriptor");
+ if (ISO9660_GET_ENDIAN(&pVolDesc->cbLogicalBlock) != pThis->cbBlock)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Logical block size for joliet volume descriptor differs from primary: %#RX16 vs %#RX16\n",
+ ISO9660_GET_ENDIAN(&pVolDesc->cbLogicalBlock), pThis->cbBlock);
+#if 0 /* Not necessary. */
+ /* Used to be !=, changed to > for ubuntu 20.10 and later. Wonder if they exclude a few files
+ and thus end up with a different total. Obviously, this test is a big bogus, as we don't
+ really seem to care about the value at all... */
+ if (ISO9660_GET_ENDIAN(&pVolDesc->VolumeSpaceSize) > pThis->cBlocksInPrimaryVolumeSpace)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Volume space size for joliet volume descriptor differs from primary: %#RX32 vs %#RX32\n",
+ ISO9660_GET_ENDIAN(&pVolDesc->VolumeSpaceSize), pThis->cBlocksInPrimaryVolumeSpace);
+#endif
+ if (ISO9660_GET_ENDIAN(&pVolDesc->cVolumesInSet) != pThis->cVolumesInSet)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Volume set size for joliet volume descriptor differs from primary: %#RX16 vs %#RX32\n",
+ ISO9660_GET_ENDIAN(&pVolDesc->cVolumesInSet), pThis->cVolumesInSet);
+ if (ISO9660_GET_ENDIAN(&pVolDesc->VolumeSeqNo) != pThis->idPrimaryVol)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Volume sequence ID for joliet volume descriptor differs from primary: %#RX16 vs %#RX32\n",
+ ISO9660_GET_ENDIAN(&pVolDesc->VolumeSeqNo), pThis->idPrimaryVol);
+
+ if (*pbUcs2Level != 0)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "More than one supplementary joliet volume descriptor");
+
+ /*
+ * Switch to the joliet root dir as it has UTF-16 stuff in it.
+ */
+ int rc = rtFsIsoVolHandleRootDir(pThis, &pVolDesc->RootDir.DirRec, pRootDir, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ *poffRootDirRec = offVolDesc + RT_UOFFSETOF(ISO9660SUPVOLDESC, RootDir.DirRec);
+ *pbUcs2Level = pVolDesc->abEscapeSequences[2] == ISO9660_JOLIET_ESC_SEQ_2_LEVEL_1 ? 1
+ : pVolDesc->abEscapeSequences[2] == ISO9660_JOLIET_ESC_SEQ_2_LEVEL_2 ? 2 : 3;
+ Log(("ISO9660: Joliet with UCS-2 level %u\n", *pbUcs2Level));
+
+ /*
+ * Take down the location of the secondary volume descriptor so we can get
+ * the volume lable and other info from it later.
+ */
+ pThis->offSecondaryVolDesc = offVolDesc;
+ }
+ return rc;
+}
+
+
+
+/**
+ * Worker for RTFsIso9660VolOpen.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO VFS instance to initialize.
+ * @param hVfsSelf The ISO VFS handle (no reference consumed).
+ * @param hVfsBacking The file backing the alleged ISO file system.
+ * Reference is consumed (via rtFsIsoVol_Close).
+ * @param fFlags Flags, RTFSISO9660_F_XXX.
+ * @param pErrInfo Where to return additional error info. Can be NULL.
+ */
+static int rtFsIsoVolTryInit(PRTFSISOVOL pThis, RTVFS hVfsSelf, RTVFSFILE hVfsBacking, uint32_t fFlags, PRTERRINFO pErrInfo)
+{
+ uint32_t const cbSector = 2048;
+
+ /*
+ * First initialize the state so that rtFsIsoVol_Close won't trip up.
+ */
+ pThis->hVfsSelf = hVfsSelf;
+ pThis->hVfsBacking = hVfsBacking; /* Caller referenced it for us, we consume it; rtFsIsoVol_Close releases it. */
+ pThis->cbBacking = 0;
+ pThis->cBackingSectors = 0;
+ pThis->fFlags = fFlags;
+ pThis->cbSector = cbSector;
+ pThis->cbBlock = 0;
+ pThis->cBlocksInPrimaryVolumeSpace = 0;
+ pThis->cbPrimaryVolumeSpace = 0;
+ pThis->cVolumesInSet = 0;
+ pThis->idPrimaryVol = UINT32_MAX;
+ pThis->fIsUtf16 = false;
+ pThis->pRootDir = NULL;
+ pThis->fHaveRock = false;
+ pThis->offSuspSkip = 0;
+ pThis->offRockBuf = UINT64_MAX;
+
+ /*
+ * Do init stuff that may fail.
+ */
+ int rc = RTCritSectInit(&pThis->RockBufLock);
+ AssertRCReturn(rc, rc);
+
+ rc = RTVfsFileQuerySize(hVfsBacking, &pThis->cbBacking);
+ if (RT_SUCCESS(rc))
+ pThis->cBackingSectors = pThis->cbBacking / pThis->cbSector;
+ else
+ return rc;
+
+ /*
+ * Read the volume descriptors starting at logical sector 16.
+ */
+ union
+ {
+ uint8_t ab[RTFSISO_MAX_LOGICAL_BLOCK_SIZE];
+ uint16_t au16[RTFSISO_MAX_LOGICAL_BLOCK_SIZE / 2];
+ uint32_t au32[RTFSISO_MAX_LOGICAL_BLOCK_SIZE / 4];
+ ISO9660VOLDESCHDR VolDescHdr;
+ ISO9660BOOTRECORD BootRecord;
+ ISO9660PRIMARYVOLDESC PrimaryVolDesc;
+ ISO9660SUPVOLDESC SupVolDesc;
+ ISO9660VOLPARTDESC VolPartDesc;
+ } Buf;
+ RT_ZERO(Buf);
+
+ uint64_t offRootDirRec = UINT64_MAX;
+ ISO9660DIRREC RootDir;
+ RT_ZERO(RootDir);
+
+ uint64_t offJolietRootDirRec = UINT64_MAX;
+ uint8_t bJolietUcs2Level = 0;
+ ISO9660DIRREC JolietRootDir;
+ RT_ZERO(JolietRootDir);
+
+ uint8_t uUdfLevel = 0;
+ uint64_t offUdfBootVolDesc = UINT64_MAX;
+
+ uint32_t cPrimaryVolDescs = 0;
+ uint32_t cSupplementaryVolDescs = 0;
+ uint32_t cBootRecordVolDescs = 0;
+ uint32_t offVolDesc = 16 * cbSector;
+ enum
+ {
+ kStateStart = 0,
+ kStateNoSeq,
+ kStateCdSeq,
+ kStateUdfSeq
+ } enmState = kStateStart;
+ for (uint32_t iVolDesc = 0; ; iVolDesc++, offVolDesc += cbSector)
+ {
+ if (iVolDesc > 32)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "More than 32 volume descriptors, doesn't seem right...");
+
+ /* Read the next one and check the signature. */
+ rc = RTVfsFileReadAt(hVfsBacking, offVolDesc, &Buf, cbSector, NULL);
+ if (RT_FAILURE(rc))
+ return RTERRINFO_LOG_SET_F(pErrInfo, rc, "Unable to read volume descriptor #%u", iVolDesc);
+
+#define MATCH_STD_ID(a_achStdId1, a_szStdId2) \
+ ( (a_achStdId1)[0] == (a_szStdId2)[0] \
+ && (a_achStdId1)[1] == (a_szStdId2)[1] \
+ && (a_achStdId1)[2] == (a_szStdId2)[2] \
+ && (a_achStdId1)[3] == (a_szStdId2)[3] \
+ && (a_achStdId1)[4] == (a_szStdId2)[4] )
+#define MATCH_HDR(a_pStd, a_bType2, a_szStdId2, a_bVer2) \
+ ( MATCH_STD_ID((a_pStd)->achStdId, a_szStdId2) \
+ && (a_pStd)->bDescType == (a_bType2) \
+ && (a_pStd)->bDescVersion == (a_bVer2) )
+
+ /*
+ * ISO 9660 ("CD001").
+ */
+ if ( ( enmState == kStateStart
+ || enmState == kStateCdSeq
+ || enmState == kStateNoSeq)
+ && MATCH_STD_ID(Buf.VolDescHdr.achStdId, ISO9660VOLDESC_STD_ID) )
+ {
+ enmState = kStateCdSeq;
+
+ /* Do type specific handling. */
+ Log(("ISO9660: volume desc #%u: type=%#x\n", iVolDesc, Buf.VolDescHdr.bDescType));
+ if (Buf.VolDescHdr.bDescType == ISO9660VOLDESC_TYPE_PRIMARY)
+ {
+ cPrimaryVolDescs++;
+ if (Buf.VolDescHdr.bDescVersion != ISO9660PRIMARYVOLDESC_VERSION)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Unsupported primary volume descriptor version: %#x", Buf.VolDescHdr.bDescVersion);
+#ifdef LOG_ENABLED
+ rtFsIsoVolLogPrimarySupplementaryVolDesc(&Buf.SupVolDesc);
+#endif
+ if (cPrimaryVolDescs == 1)
+ rc = rtFsIsoVolHandlePrimaryVolDesc(pThis, &Buf.PrimaryVolDesc, offVolDesc, &RootDir, &offRootDirRec, pErrInfo);
+ else if (cPrimaryVolDescs == 2)
+ Log(("ISO9660: ignoring 2nd primary descriptor\n")); /* so we can read the w2k3 ifs kit */
+ else
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "More than one primary volume descriptor");
+ }
+ else if (Buf.VolDescHdr.bDescType == ISO9660VOLDESC_TYPE_SUPPLEMENTARY)
+ {
+ cSupplementaryVolDescs++;
+ if (Buf.VolDescHdr.bDescVersion != ISO9660SUPVOLDESC_VERSION)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Unsupported supplemental volume descriptor version: %#x", Buf.VolDescHdr.bDescVersion);
+#ifdef LOG_ENABLED
+ rtFsIsoVolLogPrimarySupplementaryVolDesc(&Buf.SupVolDesc);
+#endif
+ rc = rtFsIsoVolHandleSupplementaryVolDesc(pThis, &Buf.SupVolDesc, offVolDesc, &bJolietUcs2Level, &JolietRootDir,
+ &offJolietRootDirRec, pErrInfo);
+ }
+ else if (Buf.VolDescHdr.bDescType == ISO9660VOLDESC_TYPE_BOOT_RECORD)
+ {
+ cBootRecordVolDescs++;
+ }
+ else if (Buf.VolDescHdr.bDescType == ISO9660VOLDESC_TYPE_TERMINATOR)
+ {
+ if (!cPrimaryVolDescs)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "No primary volume descriptor");
+ enmState = kStateNoSeq;
+ }
+ else
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Unknown volume descriptor: %#x", Buf.VolDescHdr.bDescType);
+ }
+ /*
+ * UDF volume recognition sequence (VRS).
+ */
+ else if ( ( enmState == kStateNoSeq
+ || enmState == kStateStart)
+ && MATCH_HDR(&Buf.VolDescHdr, UDF_EXT_VOL_DESC_TYPE, UDF_EXT_VOL_DESC_STD_ID_BEGIN, UDF_EXT_VOL_DESC_VERSION) )
+ {
+ if (uUdfLevel == 0)
+ enmState = kStateUdfSeq;
+ else
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Only one BEA01 sequence is supported");
+ }
+ else if ( enmState == kStateUdfSeq
+ && MATCH_HDR(&Buf.VolDescHdr, UDF_EXT_VOL_DESC_TYPE, UDF_EXT_VOL_DESC_STD_ID_NSR_02, UDF_EXT_VOL_DESC_VERSION) )
+ uUdfLevel = 2;
+ else if ( enmState == kStateUdfSeq
+ && MATCH_HDR(&Buf.VolDescHdr, UDF_EXT_VOL_DESC_TYPE, UDF_EXT_VOL_DESC_STD_ID_NSR_03, UDF_EXT_VOL_DESC_VERSION) )
+ uUdfLevel = 3;
+ else if ( enmState == kStateUdfSeq
+ && MATCH_HDR(&Buf.VolDescHdr, UDF_EXT_VOL_DESC_TYPE, UDF_EXT_VOL_DESC_STD_ID_BOOT, UDF_EXT_VOL_DESC_VERSION) )
+ {
+ if (offUdfBootVolDesc == UINT64_MAX)
+ offUdfBootVolDesc = iVolDesc * cbSector;
+ else
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Only one BOOT2 descriptor is supported");
+ }
+ else if ( enmState == kStateUdfSeq
+ && MATCH_HDR(&Buf.VolDescHdr, UDF_EXT_VOL_DESC_TYPE, UDF_EXT_VOL_DESC_STD_ID_TERM, UDF_EXT_VOL_DESC_VERSION) )
+ {
+ if (uUdfLevel != 0)
+ enmState = kStateNoSeq;
+ else
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Found BEA01 & TEA01, but no NSR02 or NSR03 descriptors");
+ }
+ /*
+ * Unknown, probably the end.
+ */
+ else if (enmState == kStateNoSeq)
+ break;
+ else if (enmState == kStateStart)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
+ "Not ISO? Unable to recognize volume descriptor signature: %.5Rhxs", Buf.VolDescHdr.achStdId);
+ else if (enmState == kStateCdSeq)
+ {
+#if 1
+ /* The warp server for ebusiness update ISOs known as ACP2 & MCP2 ends up here,
+ as they do in deed miss a terminator volume descriptor and we're now at the
+ root directory already. Just detect this, ignore it and get on with things. */
+ Log(("rtFsIsoVolTryInit: Ignoring missing ISO 9660 terminator volume descriptor (found %.5Rhxs).\n",
+ Buf.VolDescHdr.achStdId));
+ break;
+#else
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Missing ISO 9660 terminator volume descriptor? (Found %.5Rhxs)", Buf.VolDescHdr.achStdId);
+#endif
+ }
+ else if (enmState == kStateUdfSeq)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Missing UDF terminator volume descriptor? (Found %.5Rhxs)", Buf.VolDescHdr.achStdId);
+ else
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
+ "Unknown volume descriptor signature found at sector %u: %.5Rhxs",
+ 16 + iVolDesc, Buf.VolDescHdr.achStdId);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * If we found a UDF VRS and are interested in UDF, we have more work to do here.
+ */
+ if (uUdfLevel > 0 && !(fFlags & RTFSISO9660_F_NO_UDF))
+ {
+ Log(("rtFsIsoVolTryInit: uUdfLevel=%d\n", uUdfLevel));
+ rc = rtFsIsoVolHandleUdfDetection(pThis, &uUdfLevel, offUdfBootVolDesc, Buf.ab, sizeof(Buf), pErrInfo);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Decide which to prefer.
+ *
+ * By default we pick UDF over any of the two ISO 9960, there is currently
+ * no way to override this without using the RTFSISO9660_F_NO_XXX options.
+ *
+ * If there isn't UDF, we may be faced with choosing between joliet and
+ * rock ridge. The joliet option is generally favorable as we don't have
+ * to guess wrt to the file name encoding. So, we'll pick that for now.
+ *
+ * Note! Should we change this preference for joliet, there fun wrt making sure
+ * there really is rock ridge stuff in the primary volume as well as
+ * making sure there really is anything of value in the primary volume.
+ */
+ if (uUdfLevel > 0)
+ {
+ pThis->enmType = RTFSISOVOLTYPE_UDF;
+ rc = rtFsIsoDirShrd_NewUdf(pThis, NULL /*pParent*/, &pThis->Udf.VolInfo.RootDirIcb,
+ NULL /*pFileIdDesc*/, 0 /*offInDir*/, &pThis->pRootDir);
+ /** @todo fall back on failure? */
+ return rc;
+ }
+ if (bJolietUcs2Level != 0)
+ {
+ pThis->enmType = RTFSISOVOLTYPE_JOLIET;
+ pThis->fIsUtf16 = true;
+ return rtFsIsoDirShrd_New9660(pThis, NULL, &JolietRootDir, 1, offJolietRootDirRec, NULL, &pThis->pRootDir);
+ }
+ pThis->enmType = RTFSISOVOLTYPE_ISO9960;
+ return rtFsIsoDirShrd_New9660(pThis, NULL, &RootDir, 1, offRootDirRec, NULL, &pThis->pRootDir);
+}
+
+
+/**
+ * Opens an ISO 9660 file system volume.
+ *
+ * @returns IPRT status code.
+ * @param hVfsFileIn The file or device backing the volume.
+ * @param fFlags RTFSISO9660_F_XXX.
+ * @param phVfs Where to return the virtual file system handle.
+ * @param pErrInfo Where to return additional error information.
+ */
+RTDECL(int) RTFsIso9660VolOpen(RTVFSFILE hVfsFileIn, uint32_t fFlags, PRTVFS phVfs, PRTERRINFO pErrInfo)
+{
+ /*
+ * Quick input validation.
+ */
+ AssertPtrReturn(phVfs, VERR_INVALID_POINTER);
+ *phVfs = NIL_RTVFS;
+ AssertReturn(!(fFlags & ~RTFSISO9660_F_VALID_MASK), VERR_INVALID_FLAGS);
+
+ uint32_t cRefs = RTVfsFileRetain(hVfsFileIn);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Create a new ISO VFS instance and try initialize it using the given input file.
+ */
+ RTVFS hVfs = NIL_RTVFS;
+ PRTFSISOVOL pThis = NULL;
+ int rc = RTVfsNew(&g_rtFsIsoVolOps, sizeof(RTFSISOVOL), NIL_RTVFS, RTVFSLOCK_CREATE_RW, &hVfs, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsIsoVolTryInit(pThis, hVfs, hVfsFileIn, fFlags, pErrInfo);
+ if (RT_SUCCESS(rc))
+ *phVfs = hVfs;
+ else
+ RTVfsRelease(hVfs);
+ }
+ else
+ RTVfsFileRelease(hVfsFileIn);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate}
+ */
+static DECLCALLBACK(int) rtVfsChainIsoFsVol_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec,
+ PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, pSpec);
+
+ /*
+ * Basic checks.
+ */
+ if (pElement->enmTypeIn != RTVFSOBJTYPE_FILE)
+ return pElement->enmTypeIn == RTVFSOBJTYPE_INVALID ? VERR_VFS_CHAIN_CANNOT_BE_FIRST_ELEMENT : VERR_VFS_CHAIN_TAKES_FILE;
+ if ( pElement->enmType != RTVFSOBJTYPE_VFS
+ && pElement->enmType != RTVFSOBJTYPE_DIR)
+ return VERR_VFS_CHAIN_ONLY_DIR_OR_VFS;
+ if (pElement->cArgs > 1)
+ return VERR_VFS_CHAIN_AT_MOST_ONE_ARG;
+
+ /*
+ * Parse the flag if present, save in pElement->uProvider.
+ */
+ uint32_t fFlags = 0;
+ if (pElement->cArgs > 0)
+ {
+ for (uint32_t iArg = 0; iArg < pElement->cArgs; iArg++)
+ {
+ const char *psz = pElement->paArgs[iArg].psz;
+ if (*psz)
+ {
+ if (!strcmp(psz, "nojoliet"))
+ fFlags |= RTFSISO9660_F_NO_JOLIET;
+ else if (!strcmp(psz, "norock"))
+ fFlags |= RTFSISO9660_F_NO_ROCK;
+ else if (!strcmp(psz, "noudf"))
+ fFlags |= RTFSISO9660_F_NO_UDF;
+ else
+ {
+ *poffError = pElement->paArgs[iArg].offSpec;
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Only knows: 'nojoliet' and 'norock'");
+ }
+ }
+ }
+ }
+
+ pElement->uProvider = fFlags;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate}
+ */
+static DECLCALLBACK(int) rtVfsChainIsoFsVol_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec,
+ PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj,
+ PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, pSpec, poffError);
+
+ int rc;
+ RTVFSFILE hVfsFileIn = RTVfsObjToFile(hPrevVfsObj);
+ if (hVfsFileIn != NIL_RTVFSFILE)
+ {
+ RTVFS hVfs;
+ rc = RTFsIso9660VolOpen(hVfsFileIn, pElement->uProvider, &hVfs, pErrInfo);
+ RTVfsFileRelease(hVfsFileIn);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromVfs(hVfs);
+ RTVfsRelease(hVfs);
+ if (*phVfsObj != NIL_RTVFSOBJ)
+ return VINF_SUCCESS;
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ }
+ else
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement}
+ */
+static DECLCALLBACK(bool) rtVfsChainIsoFsVol_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg,
+ PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement,
+ PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement)
+{
+ RT_NOREF(pProviderReg, pSpec, pReuseSpec);
+ if ( pElement->paArgs[0].uProvider == pReuseElement->paArgs[0].uProvider
+ || !pReuseElement->paArgs[0].uProvider)
+ return true;
+ return false;
+}
+
+
+/** VFS chain element 'file'. */
+static RTVFSCHAINELEMENTREG g_rtVfsChainIsoFsVolReg =
+{
+ /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION,
+ /* fReserved = */ 0,
+ /* pszName = */ "isofs",
+ /* ListEntry = */ { NULL, NULL },
+ /* pszHelp = */ "Open a ISO 9660 or UDF file system, requires a file object on the left side.\n"
+ "The 'noudf' option make it ignore any UDF.\n"
+ "The 'nojoliet' option make it ignore any joliet supplemental volume.\n"
+ "The 'norock' option make it ignore any rock ridge info.\n",
+ /* pfnValidate = */ rtVfsChainIsoFsVol_Validate,
+ /* pfnInstantiate = */ rtVfsChainIsoFsVol_Instantiate,
+ /* pfnCanReuseElement = */ rtVfsChainIsoFsVol_CanReuseElement,
+ /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION
+};
+
+RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainIsoFsVolReg, rtVfsChainIsoFsVolReg);
+