summaryrefslogtreecommitdiffstats
path: root/src/VBox/Runtime/common/fs
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Runtime/common/fs')
-rw-r--r--src/VBox/Runtime/common/fs/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/fs/RTFsCmdLs.cpp1862
-rw-r--r--src/VBox/Runtime/common/fs/extvfs.cpp2860
-rw-r--r--src/VBox/Runtime/common/fs/fatvfs.cpp6374
-rw-r--r--src/VBox/Runtime/common/fs/isomaker.cpp7585
-rw-r--r--src/VBox/Runtime/common/fs/isomakercmd-man.xml590
-rw-r--r--src/VBox/Runtime/common/fs/isomakercmd.cpp3689
-rw-r--r--src/VBox/Runtime/common/fs/isomakerimport.cpp2738
-rw-r--r--src/VBox/Runtime/common/fs/isovfs.cpp7209
-rw-r--r--src/VBox/Runtime/common/fs/ntfsvfs.cpp5698
-rw-r--r--src/VBox/Runtime/common/fs/xfsvfs.cpp2460
11 files changed, 41065 insertions, 0 deletions
diff --git a/src/VBox/Runtime/common/fs/Makefile.kup b/src/VBox/Runtime/common/fs/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/fs/Makefile.kup
diff --git a/src/VBox/Runtime/common/fs/RTFsCmdLs.cpp b/src/VBox/Runtime/common/fs/RTFsCmdLs.cpp
new file mode 100644
index 00000000..6373a8e6
--- /dev/null
+++ b/src/VBox/Runtime/common/fs/RTFsCmdLs.cpp
@@ -0,0 +1,1862 @@
+/* $Id: RTFsCmdLs.cpp $ */
+/** @file
+ * IPRT - /bin/ls like utility for testing the VFS code.
+ */
+
+/*
+ * 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 *
+*********************************************************************************************************************************/
+#include <iprt/vfs.h>
+
+#include <iprt/buildconfig.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/sort.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Display entry.
+ */
+typedef struct RTCMDLSENTRY
+{
+ /** The information about the entry. */
+ RTFSOBJINFO Info;
+ /** Symbolic link target (allocated after the name). */
+ const char *pszTarget;
+ /** Owner if applicable(allocated after the name). */
+ const char *pszOwner;
+ /** Group if applicable (allocated after the name). */
+ const char *pszGroup;
+ /** The length of szName. */
+ size_t cchName;
+ /** The entry name. */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ char szName[RT_FLEXIBLE_ARRAY];
+} RTCMDLSENTRY;
+/** Pointer to a ls display entry. */
+typedef RTCMDLSENTRY *PRTCMDLSENTRY;
+/** Pointer to a ls display entry pointer. */
+typedef PRTCMDLSENTRY *PPRTCMDLSENTRY;
+
+
+/**
+ * Collection of display entries.
+ */
+typedef struct RTCMDLSCOLLECTION
+{
+ /** Current size of papEntries. */
+ size_t cEntries;
+ /** Memory allocated for papEntries. */
+ size_t cEntriesAllocated;
+ /** Current entries pending sorting and display. */
+ PPRTCMDLSENTRY papEntries;
+
+ /** Total number of bytes allocated for the above entries. */
+ uint64_t cbTotalAllocated;
+ /** Total number of file content bytes. */
+ uint64_t cbTotalFiles;
+
+ /** The collection name (path). */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ char szName[RT_FLEXIBLE_ARRAY];
+} RTCMDLSCOLLECTION;
+/** Pointer to a display entry collection. */
+typedef RTCMDLSCOLLECTION *PRTCMDLSCOLLECTION;
+/** Pointer to a display entry collection pointer. */
+typedef PRTCMDLSCOLLECTION *PPRTCMDLSCOLLECTION;
+
+
+/** Sorting. */
+typedef enum RTCMDLSSORT
+{
+ RTCMDLSSORT_INVALID = 0,
+ RTCMDLSSORT_NONE,
+ RTCMDLSSORT_NAME,
+ RTCMDLSSORT_EXTENSION,
+ RTCMDLSSORT_SIZE,
+ RTCMDLSSORT_TIME,
+ RTCMDLSSORT_VERSION
+} RTCMDLSSORT;
+
+/** Time selection. */
+typedef enum RTCMDLSTIME
+{
+ RTCMDLSTIME_INVALID = 0,
+ RTCMDLSTIME_BTIME,
+ RTCMDLSTIME_CTIME,
+ RTCMDLSTIME_MTIME,
+ RTCMDLSTIME_ATIME
+} RTCMDLSTIME;
+
+/** Time display style. */
+typedef enum RTCMDLSTIMESTYLE
+{
+ RTCMDLSTIMESTYLE_INVALID = 0,
+ RTCMDLSTIMESTYLE_FULL_ISO,
+ RTCMDLSTIMESTYLE_LONG_ISO,
+ RTCMDLSTIMESTYLE_ISO,
+ RTCMDLSTIMESTYLE_LOCALE,
+ RTCMDLSTIMESTYLE_CUSTOM
+} RTCMDLSTIMESTYLE;
+
+/** Coloring selection. */
+typedef enum RTCMDLSCOLOR
+{
+ RTCMDLSCOLOR_INVALID = 0,
+ RTCMDLSCOLOR_NONE
+} RTCMDLSCOLOR;
+
+/** Formatting. */
+typedef enum RTCMDLSFORMAT
+{
+ RTCMDLSFORMAT_INVALID = 0,
+ RTCMDLSFORMAT_COLS_VERTICAL, /**< -C/default */
+ RTCMDLSFORMAT_COLS_HORIZONTAL, /**< -x */
+ RTCMDLSFORMAT_COMMAS, /**< -m */
+ RTCMDLSFORMAT_SINGLE, /**< -1 */
+ RTCMDLSFORMAT_LONG, /**< -l */
+ RTCMDLSFORMAT_MACHINE_READABLE /**< --machine-readable */
+} RTCMDLSFORMAT;
+
+
+/**
+ * LS command options and state.
+ */
+typedef struct RTCMDLSOPTS
+{
+ /** @name Traversal.
+ * @{ */
+ bool fFollowSymlinksInDirs; /**< -L */
+ bool fFollowSymlinkToAnyArgs;
+ bool fFollowSymlinkToDirArgs;
+ bool fFollowDirectoryArgs; /**< Inverse -d/--directory. */
+ bool fRecursive; /**< -R */
+ /** @} */
+
+
+ /** @name Filtering.
+ * @{ */
+ bool fShowHidden; /**< -a/--all or -A/--almost-all */
+ bool fShowDotAndDotDot; /**< -a vs -A */
+ bool fShowBackups; /**< Inverse -B/--ignore-backups (*~). */
+ /** @} */
+
+ /** @name Sorting
+ * @{ */
+ RTCMDLSSORT enmSort; /**< --sort */
+ bool fReverseSort; /**< -r */
+ bool fGroupDirectoriesFirst; /**< fGroupDirectoriesFirst */
+ /** @} */
+
+ /** @name Formatting
+ * @{ */
+ RTCMDLSFORMAT enmFormat; /**< --format */
+
+ bool fEscapeNonGraphicChars; /**< -b, --escape */
+ bool fEscapeControlChars;
+ bool fHideControlChars; /**< -q/--hide-control-chars, --show-control-chars */
+
+ bool fHumanReadableSizes; /**< -h */
+ bool fSiUnits; /**< --si */
+ uint32_t cbBlock; /**< --block-size=N, -k */
+
+ bool fShowOwner;
+ bool fShowGroup;
+ bool fNumericalIds; /**< -n */
+ bool fShowINode;
+ bool fShowAllocatedSize; /**< -s */
+ uint8_t cchTab; /**< -T */
+ uint32_t cchWidth; /**< -w */
+
+ RTCMDLSCOLOR enmColor; /**< --color */
+
+ RTCMDLSTIME enmTime; /**< --time */
+ RTCMDLSTIMESTYLE enmTimeStyle; /**< --time-style, --full-time */
+ const char *pszTimeCustom; /**< --time-style=+xxx */
+ /** @} */
+
+ /** @name State
+ * @{ */
+ /** Current size of papCollections. */
+ size_t cCollections;
+ /** Memory allocated for papCollections. */
+ size_t cCollectionsAllocated;
+ /** Current entry collection pending display, the last may also be pending
+ * sorting. */
+ PPRTCMDLSCOLLECTION papCollections;
+ /** @} */
+} RTCMDLSOPTS;
+/** Pointer to ls options and state. */
+typedef RTCMDLSOPTS *PRTCMDLSOPTS;
+
+
+
+
+/** @callback_method_impl{FNRTSORTCMP, Dirs first + Unsorted} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstUnsorted(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ return !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpName(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ return RTStrCmp(pEntry1->szName, pEntry2->szName);
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Dirs first + Name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstName(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, extension} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpExtension(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = RTStrCmp(RTPathSuffix(pEntry1->szName), RTPathSuffix(pEntry2->szName));
+ if (!iDiff)
+ iDiff = RTStrCmp(pEntry1->szName, pEntry2->szName);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Dirs first + Ext + Name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstExtension(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpExtension(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Allocated size + Name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpAllocated(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ if (pEntry1->Info.cbAllocated == pEntry2->Info.cbAllocated)
+ return rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser);
+ return pEntry1->Info.cbAllocated < pEntry2->Info.cbAllocated ? -1 : 1;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Dirs first + Allocated size + Name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstAllocated(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpAllocated(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Content size + Name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpSize(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ if (pEntry1->Info.cbObject == pEntry2->Info.cbObject)
+ return rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser);
+ return pEntry1->Info.cbObject < pEntry2->Info.cbObject ? -1 : 1;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Dirs first + Content size + Name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstSize(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpSize(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Modification time + name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpMTime(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = RTTimeSpecCompare(&pEntry1->Info.ModificationTime, &pEntry2->Info.ModificationTime);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Dirs first + Modification time + Name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstMTime(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpMTime(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Birth time + name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpBTime(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = RTTimeSpecCompare(&pEntry1->Info.BirthTime, &pEntry2->Info.BirthTime);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Dirs first + Birth time + Name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstBTime(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpBTime(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Change time + name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpCTime(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = RTTimeSpecCompare(&pEntry1->Info.ChangeTime, &pEntry2->Info.ChangeTime);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Dirs first + Change time + Name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstCTime(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpCTime(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Accessed time + name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpATime(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = RTTimeSpecCompare(&pEntry1->Info.AccessTime, &pEntry2->Info.AccessTime);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Dirs first + Accessed time + Name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstATime(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpATime(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Name as version} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpVersion(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ return RTStrVersionCompare(pEntry1->szName, pEntry2->szName);
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Dirs first + Name as version} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstVersion(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpVersion(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/**
+ * Sorts the entries in the collections according the sorting options.
+ *
+ * @param pOpts The options and state.
+ */
+static void rtCmdLsSortCollections(PRTCMDLSOPTS pOpts)
+{
+ /*
+ * Sort the entries in each collection.
+ */
+ PFNRTSORTCMP pfnCmp;
+ switch (pOpts->enmSort)
+ {
+ case RTCMDLSSORT_NONE:
+ pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstUnsorted : NULL;
+ break;
+ default: AssertFailed(); RT_FALL_THRU();
+ case RTCMDLSSORT_NAME:
+ pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstName : rtCmdLsEntryCmpName;
+ break;
+ case RTCMDLSSORT_EXTENSION:
+ pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstExtension : rtCmdLsEntryCmpExtension;
+ break;
+ case RTCMDLSSORT_SIZE:
+ if (pOpts->fShowAllocatedSize)
+ pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstAllocated : rtCmdLsEntryCmpAllocated;
+ else
+ pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstSize : rtCmdLsEntryCmpSize;
+ break;
+ case RTCMDLSSORT_TIME:
+ switch (pOpts->enmTime)
+ {
+ default: AssertFailed(); RT_FALL_THRU();
+ case RTCMDLSTIME_MTIME: pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstMTime : rtCmdLsEntryCmpMTime; break;
+ case RTCMDLSTIME_BTIME: pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstBTime : rtCmdLsEntryCmpBTime; break;
+ case RTCMDLSTIME_CTIME: pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstCTime : rtCmdLsEntryCmpCTime; break;
+ case RTCMDLSTIME_ATIME: pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstATime : rtCmdLsEntryCmpATime; break;
+ }
+ break;
+ case RTCMDLSSORT_VERSION:
+ pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstVersion : rtCmdLsEntryCmpVersion;
+ break;
+ }
+ if (pfnCmp)
+ {
+ /*
+ * Walk thru the collections and sort their entries.
+ */
+ size_t i = pOpts->cCollections;
+ while (i-- > 0)
+ {
+ PRTCMDLSCOLLECTION pCollection = pOpts->papCollections[i];
+ RTSortApvShell((void **)pCollection->papEntries, pCollection->cEntries, pfnCmp, NULL);
+
+ if (pOpts->fReverseSort)
+ {
+ PPRTCMDLSENTRY papEntries = pCollection->papEntries;
+ size_t iHead = 0;
+ size_t iTail = pCollection->cEntries;
+ while (iHead < iTail)
+ {
+ PRTCMDLSENTRY pTmp = papEntries[iHead];
+ papEntries[iHead] = papEntries[iTail];
+ papEntries[iTail] = pTmp;
+ iHead++;
+ iTail--;
+ }
+ }
+ }
+ }
+
+ /** @todo sort the collections too, except for the first one. */
+}
+
+
+/**
+ * Format human readable size.
+ */
+static const char *rtCmdLsFormatSizeHumanReadable(PRTCMDLSOPTS pOpts, uint64_t cb, char *pszDst, size_t cbDst)
+{
+ if (pOpts->fHumanReadableSizes)
+ {
+ if (!pOpts->fSiUnits)
+ {
+ size_t cch = RTStrPrintf(pszDst, cbDst, "%Rhub", cb);
+ if (pszDst[cch - 1] == 'i')
+ pszDst[cch - 1] = '\0'; /* drop the trailing 'i' */
+ }
+ else
+ RTStrPrintf(pszDst, cbDst, "%Rhui", cb);
+ }
+ else if (pOpts->cbBlock)
+ RTStrFormatU64(pszDst, cbDst, (cb + pOpts->cbBlock - 1) / pOpts->cbBlock, 10, 0, 0, 0);
+ else
+ RTStrFormatU64(pszDst, cbDst, cb, 10, 0, 0, 0);
+ return pszDst;
+}
+
+
+/**
+ * Format block count.
+ */
+static const char *rtCmdLsFormatBlocks(PRTCMDLSOPTS pOpts, uint64_t cb, char *pszDst, size_t cbDst)
+{
+ if (pOpts->fHumanReadableSizes)
+ return rtCmdLsFormatSizeHumanReadable(pOpts, cb, pszDst, cbDst);
+
+ uint32_t cbBlock = pOpts->cbBlock;
+ if (cbBlock == 0)
+ cbBlock = _1K;
+ RTStrFormatU64(pszDst, cbDst, (cb + cbBlock / 2 - 1) / cbBlock, 10, 0, 0, 0);
+ return pszDst;
+}
+
+
+/**
+ * Format file size.
+ */
+static const char *rtCmdLsFormatSize(PRTCMDLSOPTS pOpts, uint64_t cb, char *pszDst, size_t cbDst)
+{
+ if (pOpts->fHumanReadableSizes)
+ return rtCmdLsFormatSizeHumanReadable(pOpts, cb, pszDst, cbDst);
+ if (pOpts->cbBlock > 0)
+ return rtCmdLsFormatBlocks(pOpts, cb, pszDst, cbDst);
+ RTStrFormatU64(pszDst, cbDst, cb, 10, 0, 0, 0);
+ return pszDst;
+}
+
+
+/**
+ * Format name, i.e. escape, hide, quote stuff.
+ */
+static const char *rtCmdLsFormatName(PRTCMDLSOPTS pOpts, const char *pszName, char *pszDst, size_t cbDst)
+{
+ if ( !pOpts->fEscapeNonGraphicChars
+ && !pOpts->fEscapeControlChars
+ && !pOpts->fHideControlChars)
+ return pszName;
+ /** @todo implement name formatting. */
+ RT_NOREF(pszDst, cbDst);
+ return pszName;
+}
+
+
+/**
+ * Figures out the length for a 32-bit number when formatted as decimal.
+ * @returns Number of digits.
+ * @param uValue The number.
+ */
+DECLINLINE(size_t) rtCmdLsDecimalFormatLengthU32(uint32_t uValue)
+{
+ if (uValue < 10)
+ return 1;
+ if (uValue < 100)
+ return 2;
+ if (uValue < 1000)
+ return 3;
+ if (uValue < 10000)
+ return 4;
+ if (uValue < 100000)
+ return 5;
+ if (uValue < 1000000)
+ return 6;
+ if (uValue < 10000000)
+ return 7;
+ if (uValue < 100000000)
+ return 8;
+ if (uValue < 1000000000)
+ return 9;
+ return 10;
+}
+
+
+/**
+ * Formats the given group ID according to the specified options.
+ *
+ * @returns pszDst
+ * @param pOpts The options and state.
+ * @param gid The GID to format.
+ * @param pszOwner The owner returned by the FS.
+ * @param pszDst The output buffer.
+ * @param cbDst The output buffer size.
+ */
+static const char *rtCmdLsDecimalFormatGroup(PRTCMDLSOPTS pOpts, RTGID gid, const char *pszGroup, char *pszDst, size_t cbDst)
+{
+ if (!pOpts->fNumericalIds)
+ {
+ if (pszGroup)
+ {
+ RTStrCopy(pszDst, cbDst, pszGroup);
+ return pszDst;
+ }
+ if (gid == NIL_RTGID)
+ return "<Nil>";
+ }
+ RTStrFormatU64(pszDst, cbDst, gid, 10, 0, 0, 0);
+ return pszDst;
+}
+
+
+/**
+ * Formats the given user ID according to the specified options.
+ *
+ * @returns pszDst
+ * @param pOpts The options and state.
+ * @param uid The UID to format.
+ * @param pszOwner The owner returned by the FS.
+ * @param pszDst The output buffer.
+ * @param cbDst The output buffer size.
+ */
+static const char *rtCmdLsDecimalFormatOwner(PRTCMDLSOPTS pOpts, RTUID uid, const char *pszOwner, char *pszDst, size_t cbDst)
+{
+ if (!pOpts->fNumericalIds)
+ {
+ if (pszOwner)
+ {
+ RTStrCopy(pszDst, cbDst, pszOwner);
+ return pszDst;
+ }
+ if (uid == NIL_RTUID)
+ return "<Nil>";
+ }
+ RTStrFormatU64(pszDst, cbDst, uid, 10, 0, 0, 0);
+ return pszDst;
+}
+
+
+/**
+ * Formats the given timestamp according to the desired --time-style.
+ *
+ * @returns pszDst
+ * @param pOpts The options and state.
+ * @param pTimestamp The timestamp.
+ * @param pszDst The output buffer.
+ * @param cbDst The output buffer size.
+ */
+static const char *rtCmdLsFormatTimestamp(PRTCMDLSOPTS pOpts, PCRTTIMESPEC pTimestamp, char *pszDst, size_t cbDst)
+{
+ /** @todo timestamp formatting according to the given style. */
+ RT_NOREF(pOpts);
+ return RTTimeSpecToString(pTimestamp, pszDst, cbDst);
+}
+
+
+
+/**
+ * RTCMDLSFORMAT_MACHINE_READABLE: --machine-readable
+ */
+static RTEXITCODE rtCmdLsDisplayCollectionInMachineReadableFormat(PRTCMDLSOPTS pOpts, PRTCMDLSCOLLECTION pCollection,
+ char *pszTmp, size_t cbTmp)
+{
+ RT_NOREF(pOpts, pCollection, pszTmp, cbTmp);
+ RTMsgError("Machine readable format not implemented\n");
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * RTCMDLSFORMAT_COMMAS: -m
+ */
+static RTEXITCODE rtCmdLsDisplayCollectionInCvsFormat(PRTCMDLSOPTS pOpts, PRTCMDLSCOLLECTION pCollection,
+ char *pszTmp, size_t cbTmp)
+{
+ RT_NOREF(pOpts, pCollection, pszTmp, cbTmp);
+ RTMsgError("Table output formats not implemented\n");
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * RTCMDLSFORMAT_LONG: -l
+ */
+static RTEXITCODE rtCmdLsDisplayCollectionInLongFormat(PRTCMDLSOPTS pOpts, PRTCMDLSCOLLECTION pCollection,
+ char *pszTmp, size_t cbTmp, size_t cchAllocatedCol)
+{
+ /*
+ * Figure the width of the size, the link count, the uid, the gid, and the inode columns.
+ */
+ size_t cchSizeCol = 1;
+ size_t cchLinkCol = 1;
+ size_t cchUidCol = pOpts->fShowOwner ? 1 : 0;
+ size_t cchGidCol = pOpts->fShowGroup ? 1 : 0;
+ size_t cchINodeCol = pOpts->fShowINode ? 1 : 0;
+
+ size_t i = pCollection->cEntries;
+ while (i-- > 0)
+ {
+ PRTCMDLSENTRY pEntry = pCollection->papEntries[i];
+
+ rtCmdLsFormatSize(pOpts, pEntry->Info.cbObject, pszTmp, cbTmp);
+ size_t cchTmp = strlen(pszTmp);
+ if (cchTmp > cchSizeCol)
+ cchSizeCol = cchTmp;
+
+ cchTmp = rtCmdLsDecimalFormatLengthU32(pEntry->Info.Attr.u.Unix.cHardlinks) + 1;
+ if (cchTmp > cchLinkCol)
+ cchLinkCol = cchTmp;
+
+ if (pOpts->fShowOwner)
+ {
+ rtCmdLsDecimalFormatOwner(pOpts, pEntry->Info.Attr.u.Unix.uid, pEntry->pszOwner, pszTmp, cbTmp);
+ cchTmp = strlen(pszTmp);
+ if (cchTmp > cchUidCol)
+ cchUidCol = cchTmp;
+ }
+
+ if (pOpts->fShowGroup)
+ {
+ rtCmdLsDecimalFormatGroup(pOpts, pEntry->Info.Attr.u.Unix.gid, pEntry->pszGroup, pszTmp, cbTmp);
+ cchTmp = strlen(pszTmp);
+ if (cchTmp > cchGidCol)
+ cchGidCol = cchTmp;
+ }
+
+ if (pOpts->fShowINode)
+ {
+ cchTmp = RTStrFormatU64(pszTmp, cchTmp, pEntry->Info.Attr.u.Unix.INodeId, 10, 0, 0, 0);
+ if (cchTmp > cchINodeCol)
+ cchINodeCol = cchTmp;
+ }
+ }
+
+ /*
+ * Determin time member offset.
+ */
+ size_t offTime;
+ switch (pOpts->enmTime)
+ {
+ default: AssertFailed(); RT_FALL_THRU();
+ case RTCMDLSTIME_MTIME: offTime = RT_UOFFSETOF(RTCMDLSENTRY, Info.ModificationTime); break;
+ case RTCMDLSTIME_BTIME: offTime = RT_UOFFSETOF(RTCMDLSENTRY, Info.BirthTime); break;
+ case RTCMDLSTIME_CTIME: offTime = RT_UOFFSETOF(RTCMDLSENTRY, Info.ChangeTime); break;
+ case RTCMDLSTIME_ATIME: offTime = RT_UOFFSETOF(RTCMDLSENTRY, Info.AccessTime); break;
+ }
+
+ /*
+ * Display the entries.
+ */
+ for (i = 0; i < pCollection->cEntries; i++)
+ {
+ PRTCMDLSENTRY pEntry = pCollection->papEntries[i];
+
+ if (cchINodeCol)
+ RTPrintf("%*RU64 ", cchINodeCol, pEntry->Info.Attr.u.Unix.INodeId);
+ if (cchAllocatedCol)
+ RTPrintf("%*s ", cchAllocatedCol, rtCmdLsFormatBlocks(pOpts, pEntry->Info.cbAllocated, pszTmp, cbTmp));
+
+ RTFMODE fMode = pEntry->Info.Attr.fMode;
+ switch (fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_FIFO: RTPrintf("f"); break;
+ case RTFS_TYPE_DEV_CHAR: RTPrintf("c"); break;
+ case RTFS_TYPE_DIRECTORY: RTPrintf("d"); break;
+ case RTFS_TYPE_DEV_BLOCK: RTPrintf("b"); break;
+ case RTFS_TYPE_FILE: RTPrintf("-"); break;
+ case RTFS_TYPE_SYMLINK: RTPrintf("l"); break;
+ case RTFS_TYPE_SOCKET: RTPrintf("s"); break;
+ case RTFS_TYPE_WHITEOUT: RTPrintf("w"); break;
+ default: RTPrintf("?"); AssertFailed(); break;
+ }
+ /** @todo sticy bits++ */
+ RTPrintf("%c%c%c",
+ fMode & RTFS_UNIX_IRUSR ? 'r' : '-',
+ fMode & RTFS_UNIX_IWUSR ? 'w' : '-',
+ fMode & RTFS_UNIX_IXUSR ? 'x' : '-');
+ RTPrintf("%c%c%c",
+ fMode & RTFS_UNIX_IRGRP ? 'r' : '-',
+ fMode & RTFS_UNIX_IWGRP ? 'w' : '-',
+ fMode & RTFS_UNIX_IXGRP ? 'x' : '-');
+ RTPrintf("%c%c%c",
+ fMode & RTFS_UNIX_IROTH ? 'r' : '-',
+ fMode & RTFS_UNIX_IWOTH ? 'w' : '-',
+ fMode & RTFS_UNIX_IXOTH ? 'x' : '-');
+ if (1)
+ {
+ RTPrintf(" %c%c%c%c%c%c%c%c%c%c%c%c%c%c",
+ fMode & RTFS_DOS_READONLY ? 'R' : '-',
+ fMode & RTFS_DOS_HIDDEN ? 'H' : '-',
+ fMode & RTFS_DOS_SYSTEM ? 'S' : '-',
+ fMode & RTFS_DOS_DIRECTORY ? 'D' : '-',
+ fMode & RTFS_DOS_ARCHIVED ? 'A' : '-',
+ fMode & RTFS_DOS_NT_DEVICE ? 'd' : '-',
+ fMode & RTFS_DOS_NT_NORMAL ? 'N' : '-',
+ fMode & RTFS_DOS_NT_TEMPORARY ? 'T' : '-',
+ fMode & RTFS_DOS_NT_SPARSE_FILE ? 'P' : '-',
+ fMode & RTFS_DOS_NT_REPARSE_POINT ? 'J' : '-',
+ fMode & RTFS_DOS_NT_COMPRESSED ? 'C' : '-',
+ fMode & RTFS_DOS_NT_OFFLINE ? 'O' : '-',
+ fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-',
+ fMode & RTFS_DOS_NT_ENCRYPTED ? 'E' : '-');
+ }
+ RTPrintf(" %*u", cchLinkCol, pEntry->Info.Attr.u.Unix.cHardlinks);
+ if (cchUidCol)
+ RTPrintf(" %*s", cchUidCol,
+ rtCmdLsDecimalFormatOwner(pOpts, pEntry->Info.Attr.u.Unix.uid, pEntry->pszOwner, pszTmp, cbTmp));
+ if (cchGidCol)
+ RTPrintf(" %*s", cchGidCol,
+ rtCmdLsDecimalFormatGroup(pOpts, pEntry->Info.Attr.u.Unix.gid, pEntry->pszGroup, pszTmp, cbTmp));
+ RTPrintf(" %*s", cchSizeCol, rtCmdLsFormatSize(pOpts, pEntry->Info.cbObject, pszTmp, cbTmp));
+
+ PCRTTIMESPEC pTime = (PCRTTIMESPEC)((uintptr_t)pEntry + offTime);
+ RTPrintf(" %s", rtCmdLsFormatTimestamp(pOpts, pTime, pszTmp, cbTmp));
+
+ RTPrintf(" %s\n", rtCmdLsFormatName(pOpts, pEntry->szName, pszTmp, cbTmp));
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * RTCMDLSFORMAT_SINGLE: -1
+ */
+static RTEXITCODE rtCmdLsDisplayCollectionInSingleFormat(PRTCMDLSOPTS pOpts, PRTCMDLSCOLLECTION pCollection,
+ char *pszTmp, size_t cbTmp, size_t cchAllocatedCol)
+{
+ if (cchAllocatedCol > 0)
+ for (size_t i = 0; i < pCollection->cEntries; i++)
+ {
+ PRTCMDLSENTRY pEntry = pCollection->papEntries[i];
+ RTPrintf("%*s %s\n",
+ cchAllocatedCol, rtCmdLsFormatBlocks(pOpts, pEntry->Info.cbAllocated, pszTmp, cbTmp / 4),
+ rtCmdLsFormatName(pOpts, pEntry->szName, &pszTmp[cbTmp / 4], cbTmp / 4 * 3));
+ }
+ else
+ for (size_t i = 0; i < pCollection->cEntries; i++)
+ {
+ PRTCMDLSENTRY pEntry = pCollection->papEntries[i];
+ RTPrintf("%s\n", rtCmdLsFormatName(pOpts, pEntry->szName, pszTmp, cbTmp));
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * RTCMDLSFORMAT_COLS_VERTICAL: default, -C; RTCMDLSFORMAT_COLS_HORIZONTAL: -x
+ */
+static RTEXITCODE rtCmdLsDisplayCollectionInTableFormat(PRTCMDLSOPTS pOpts, PRTCMDLSCOLLECTION pCollection,
+ char *pszTmp, size_t cbTmp, size_t cchAllocatedCol)
+{
+ RT_NOREF(pOpts, pCollection, pszTmp, cbTmp, cchAllocatedCol);
+ RTMsgError("Table output formats not implemented\n");
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Does the actual displaying of the entry collections.
+ *
+ * @returns Program exit code.
+ * @param pOpts The options and state.
+ */
+static RTEXITCODE rtCmdLsDisplayCollections(PRTCMDLSOPTS pOpts)
+{
+ rtCmdLsSortCollections(pOpts);
+
+ bool const fNeedCollectionName = pOpts->cCollections > 2
+ || ( pOpts->cCollections == 2
+ && pOpts->papCollections[0]->cEntries > 0);
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ for (size_t iCollection = 0; iCollection < pOpts->cCollections; iCollection++)
+ {
+ PRTCMDLSCOLLECTION pCollection = pOpts->papCollections[iCollection];
+ char szTmp[RTPATH_MAX*2];
+
+ /* The header. */
+ if (iCollection != 0)
+ {
+ if ( iCollection > 1
+ || pOpts->papCollections[0]->cEntries > 0)
+ RTPrintf("\n");
+ if (fNeedCollectionName)
+ RTPrintf("%s:\n", rtCmdLsFormatName(pOpts, pCollection->szName, szTmp, sizeof(szTmp)));
+ RTPrintf("total %s\n", rtCmdLsFormatBlocks(pOpts, pCollection->cbTotalAllocated, szTmp, sizeof(szTmp)));
+ }
+
+ /* Format the entries. */
+ RTEXITCODE rcExit2;
+ if (pOpts->enmFormat == RTCMDLSFORMAT_MACHINE_READABLE)
+ rcExit2 = rtCmdLsDisplayCollectionInMachineReadableFormat(pOpts, pCollection, szTmp, sizeof(szTmp));
+ else if (pOpts->enmFormat == RTCMDLSFORMAT_COMMAS)
+ rcExit2 = rtCmdLsDisplayCollectionInCvsFormat(pOpts, pCollection, szTmp, sizeof(szTmp));
+ else
+ {
+ /* If the allocated size is requested, calculate the column width. */
+ size_t cchAllocatedCol = 0;
+ if (pOpts->fShowAllocatedSize)
+ {
+ size_t i = pCollection->cEntries;
+ while (i-- > 0)
+ {
+ rtCmdLsFormatBlocks(pOpts, pCollection->papEntries[i]->Info.cbAllocated, szTmp, sizeof(szTmp));
+ size_t cchTmp = strlen(szTmp);
+ if (cchTmp > cchAllocatedCol)
+ cchAllocatedCol = cchTmp;
+ }
+ }
+
+ /* Do the individual formatting. */
+ if (pOpts->enmFormat == RTCMDLSFORMAT_LONG)
+ rcExit2 = rtCmdLsDisplayCollectionInLongFormat(pOpts, pCollection, szTmp, sizeof(szTmp), cchAllocatedCol);
+ else if (pOpts->enmFormat == RTCMDLSFORMAT_SINGLE)
+ rcExit2 = rtCmdLsDisplayCollectionInSingleFormat(pOpts, pCollection, szTmp, sizeof(szTmp), cchAllocatedCol);
+ else
+ rcExit2 = rtCmdLsDisplayCollectionInTableFormat(pOpts, pCollection, szTmp, sizeof(szTmp), cchAllocatedCol);
+ }
+ if (rcExit2 != RTEXITCODE_SUCCESS)
+ rcExit = rcExit2;
+ }
+ return rcExit;
+}
+
+
+/**
+ * Frees all collections and their entries.
+ * @param pOpts The options and state.
+ */
+static void rtCmdLsFreeCollections(PRTCMDLSOPTS pOpts)
+{
+ size_t i = pOpts->cCollections;
+ while (i-- > 0)
+ {
+ PRTCMDLSCOLLECTION pCollection = pOpts->papCollections[i];
+ PPRTCMDLSENTRY papEntries = pCollection->papEntries;
+ size_t j = pCollection->cEntries;
+ while (j-- > 0)
+ {
+ RTMemFree(papEntries[j]);
+ papEntries[j] = NULL;
+ }
+ RTMemFree(papEntries);
+ pCollection->papEntries = NULL;
+ pCollection->cEntries = 0;
+ pCollection->cEntriesAllocated = 0;
+ RTMemFree(pCollection);
+ pOpts->papCollections[i] = NULL;
+ }
+
+ RTMemFree(pOpts->papCollections);
+ pOpts->papCollections = NULL;
+ pOpts->cCollections = 0;
+ pOpts->cCollectionsAllocated = 0;
+}
+
+
+/**
+ * Allocates a new collection.
+ *
+ * @returns Pointer to the collection.
+ * @param pOpts The options and state.
+ * @param pszName The collection name. Empty for special first
+ * collection.
+ */
+static PRTCMDLSCOLLECTION rtCmdLsNewCollection(PRTCMDLSOPTS pOpts, const char *pszName)
+{
+ /* Grow the pointer table? */
+ if (pOpts->cCollections >= pOpts->cCollectionsAllocated)
+ {
+ size_t cNew = pOpts->cCollectionsAllocated ? pOpts->cCollectionsAllocated * 2 : 16;
+ void *pvNew = RTMemRealloc(pOpts->papCollections, cNew * sizeof(pOpts->papCollections[0]));
+ if (!pvNew)
+ {
+ RTMsgError("Out of memory! (resize collections)");
+ return NULL;
+ }
+ pOpts->cCollectionsAllocated = cNew;
+ pOpts->papCollections = (PPRTCMDLSCOLLECTION)pvNew;
+
+ /* If this is the first time and pszName isn't empty, add the zero'th
+ entry for the command line stuff (hardcoded first collection). */
+ if ( pOpts->cCollections == 0
+ && *pszName)
+ {
+ PRTCMDLSCOLLECTION pCollection = (PRTCMDLSCOLLECTION)RTMemAllocZ(RT_UOFFSETOF(RTCMDLSCOLLECTION, szName[1]));
+ if (!pCollection)
+ {
+ RTMsgError("Out of memory! (collection)");
+ return NULL;
+ }
+ pOpts->papCollections[0] = pCollection;
+ pOpts->cCollections = 1;
+ }
+ }
+
+ /* Add new collection. */
+ size_t cbName = strlen(pszName) + 1;
+ PRTCMDLSCOLLECTION pCollection = (PRTCMDLSCOLLECTION)RTMemAllocZ(RT_UOFFSETOF_DYN(RTCMDLSCOLLECTION, szName[cbName]));
+ if (pCollection)
+ {
+ memcpy(pCollection->szName, pszName, cbName);
+ pOpts->papCollections[pOpts->cCollections++] = pCollection;
+ }
+ else
+ RTMsgError("Out of memory! (collection)");
+ return pCollection;
+}
+
+
+/**
+ * Adds one entry to a collection.
+ * @returns Program exit code
+ * @param pCollection The collection.
+ * @param pszEntry The entry name.
+ * @param pInfo The entry info.
+ * @param pszOwner The owner name if available, otherwise NULL.
+ * @param pszGroup The group anme if available, otherwise NULL.
+ * @param pszTarget The symbolic link target if applicable and
+ * available, otherwise NULL.
+ */
+static RTEXITCODE rtCmdLsAddOne(PRTCMDLSCOLLECTION pCollection, const char *pszEntry, PRTFSOBJINFO pInfo,
+ const char *pszOwner, const char *pszGroup, const char *pszTarget)
+{
+
+ /* Make sure there is space in the collection for the new entry. */
+ if (pCollection->cEntries >= pCollection->cEntriesAllocated)
+ {
+ size_t cNew = pCollection->cEntriesAllocated ? pCollection->cEntriesAllocated * 2 : 16;
+ void *pvNew = RTMemRealloc(pCollection->papEntries, cNew * sizeof(pCollection->papEntries[0]));
+ if (!pvNew)
+ return RTMsgErrorExitFailure("Out of memory! (resize entries)");
+ pCollection->papEntries = (PPRTCMDLSENTRY)pvNew;
+ pCollection->cEntriesAllocated = cNew;
+ }
+
+ /* Create and insert a new entry. */
+ size_t const cchEntry = strlen(pszEntry);
+ size_t const cbOwner = pszOwner ? strlen(pszOwner) + 1 : 0;
+ size_t const cbGroup = pszGroup ? strlen(pszGroup) + 1 : 0;
+ size_t const cbTarget = pszTarget ? strlen(pszTarget) + 1 : 0;
+ size_t const cbEntry = RT_UOFFSETOF_DYN(RTCMDLSENTRY, szName[cchEntry + 1 + cbOwner + cbGroup + cbTarget]);
+ PRTCMDLSENTRY pEntry = (PRTCMDLSENTRY)RTMemAlloc(cbEntry);
+ if (pEntry)
+ {
+ pEntry->Info = *pInfo;
+ pEntry->pszTarget = NULL; /** @todo symbolic links. */
+ pEntry->pszOwner = NULL;
+ pEntry->pszGroup = NULL;
+ pEntry->cchName = cchEntry;
+ memcpy(pEntry->szName, pszEntry, cchEntry);
+ pEntry->szName[cchEntry] = '\0';
+
+ char *psz = &pEntry->szName[cchEntry + 1];
+ if (pszTarget)
+ {
+ pEntry->pszTarget = psz;
+ memcpy(psz, pszTarget, cbTarget);
+ psz += cbTarget;
+ }
+ if (pszOwner)
+ {
+ pEntry->pszOwner = psz;
+ memcpy(psz, pszOwner, cbOwner);
+ psz += cbOwner;
+ }
+ if (pszGroup)
+ {
+ pEntry->pszGroup = psz;
+ memcpy(psz, pszGroup, cbGroup);
+ }
+
+ pCollection->papEntries[pCollection->cEntries++] = pEntry;
+ pCollection->cbTotalAllocated += pEntry->Info.cbAllocated;
+ pCollection->cbTotalFiles += pEntry->Info.cbObject;
+ return RTEXITCODE_SUCCESS;
+ }
+ return RTMsgErrorExitFailure("Out of memory! (entry)");
+}
+
+
+/**
+ * Checks if the entry is to be filtered out.
+ *
+ * @returns true if filtered out, false if included.
+ * @param pOpts The options and state.
+ * @param pszEntry The entry name.
+ * @param pInfo The entry info.
+ */
+static bool rtCmdLsIsFilteredOut(PRTCMDLSOPTS pOpts, const char *pszEntry, PCRTFSOBJINFO pInfo)
+{
+ /*
+ * Should we filter out this entry?
+ */
+ if ( !pOpts->fShowHidden
+ && (pInfo->Attr.fMode & RTFS_DOS_HIDDEN))
+ return true;
+
+ size_t const cchEntry = strlen(pszEntry);
+ if ( !pOpts->fShowDotAndDotDot
+ && cchEntry <= 2
+ && pszEntry[0] == '.'
+ && ( cchEntry == 1
+ || pszEntry[1] == '.' ))
+ return true;
+
+ if ( !pOpts->fShowBackups
+ && pszEntry[cchEntry - 1] == '~')
+ return true;
+ return false;
+}
+
+
+/**
+ * Processes a directory, recursing into subdirectories if desired.
+ *
+ * @returns Program exit code.
+ * @param pOpts The options.
+ * @param hVfsDir The directory.
+ * @param pszPath Path buffer, RTPATH_MAX in size.
+ * @param cchPath The length of the current path.
+ * @param pInfo The parent information.
+ */
+static RTEXITCODE rtCmdLsProcessDirectory(PRTCMDLSOPTS pOpts, RTVFSDIR hVfsDir, char *pszPath, size_t cchPath, PCRTFSOBJINFO pInfo)
+{
+ /*
+ * Create a new collection for this directory.
+ */
+ RT_NOREF(pInfo);
+ PRTCMDLSCOLLECTION pCollection = rtCmdLsNewCollection(pOpts, pszPath);
+ if (!pCollection)
+ return RTEXITCODE_FAILURE;
+
+ /*
+ * Process the directory entries.
+ */
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ size_t cbDirEntryAlloced = sizeof(RTDIRENTRYEX);
+ PRTDIRENTRYEX pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
+ if (!pDirEntry)
+ return RTMsgErrorExitFailure("Out of memory! (direntry buffer)");
+
+ for (;;)
+ {
+ /*
+ * Read the next entry.
+ */
+ size_t cbDirEntry = cbDirEntryAlloced;
+ int rc = RTVfsDirReadEx(hVfsDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_UNIX);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ RTMemTmpFree(pDirEntry);
+ cbDirEntryAlloced = RT_ALIGN_Z(RT_MIN(cbDirEntry, cbDirEntryAlloced) + 64, 64);
+ pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
+ if (pDirEntry)
+ continue;
+ rcExit = RTMsgErrorExitFailure("Out of memory (direntry buffer)");
+ }
+ else if (rc != VERR_NO_MORE_FILES)
+ rcExit = RTMsgErrorExitFailure("RTVfsDirReadEx failed: %Rrc\n", rc);
+ break;
+ }
+
+ /*
+ * Process the entry.
+ */
+ if (rtCmdLsIsFilteredOut(pOpts, pDirEntry->szName, &pDirEntry->Info))
+ continue;
+
+
+ const char *pszOwner = NULL;
+ RTFSOBJINFO OwnerInfo;
+ if (pDirEntry->Info.Attr.u.Unix.uid != NIL_RTUID && pOpts->fShowOwner)
+ {
+ rc = RTVfsDirQueryPathInfo(hVfsDir, pDirEntry->szName, &OwnerInfo, RTFSOBJATTRADD_UNIX_OWNER, RTPATH_F_ON_LINK);
+ if (RT_SUCCESS(rc) && OwnerInfo.Attr.u.UnixOwner.szName[0])
+ pszOwner = &OwnerInfo.Attr.u.UnixOwner.szName[0];
+ }
+
+ const char *pszGroup = NULL;
+ RTFSOBJINFO GroupInfo;
+ if (pDirEntry->Info.Attr.u.Unix.gid != NIL_RTGID && pOpts->fShowGroup)
+ {
+ rc = RTVfsDirQueryPathInfo(hVfsDir, pDirEntry->szName, &GroupInfo, RTFSOBJATTRADD_UNIX_GROUP, RTPATH_F_ON_LINK);
+ if (RT_SUCCESS(rc) && GroupInfo.Attr.u.UnixGroup.szName[0])
+ pszGroup = &GroupInfo.Attr.u.UnixGroup.szName[0];
+ }
+
+ RTEXITCODE rcExit2 = rtCmdLsAddOne(pCollection, pDirEntry->szName, &pDirEntry->Info, pszOwner, pszGroup, NULL);
+ if (rcExit2 != RTEXITCODE_SUCCESS)
+ rcExit = rcExit2;
+ }
+
+ RTMemTmpFree(pDirEntry);
+
+ /*
+ * Recurse into subdirectories if requested.
+ */
+ if (pOpts->fRecursive)
+ {
+ for (uint32_t i = 0; i < pCollection->cEntries; i++)
+ {
+ PRTCMDLSENTRY pEntry = pCollection->papEntries[i];
+ if (RTFS_IS_SYMLINK(pEntry->Info.Attr.fMode))
+ {
+ if (!pOpts->fFollowSymlinksInDirs)
+ continue;
+ /** @todo implement following symbolic links in the tree. */
+ continue;
+ }
+ else if ( !RTFS_IS_DIRECTORY(pEntry->Info.Attr.fMode)
+ || ( pEntry->szName[0] == '.'
+ && ( pEntry->szName[1] == '\0'
+ || ( pEntry->szName[1] == '.'
+ && pEntry->szName[2] == '\0'))) )
+ continue;
+
+ /* Open subdirectory and process it. */
+ RTVFSDIR hSubDir;
+ int rc = RTVfsDirOpenDir(hVfsDir, pEntry->szName, 0 /*fFlags*/, &hSubDir);
+ if (RT_SUCCESS(rc))
+ {
+ if (cchPath + 1 + pEntry->cchName + 1 < RTPATH_MAX)
+ {
+ pszPath[cchPath] = RTPATH_SLASH;
+ memcpy(&pszPath[cchPath + 1], pEntry->szName, pEntry->cchName + 1);
+ RTEXITCODE rcExit2 = rtCmdLsProcessDirectory(pOpts, hSubDir, pszPath,
+ cchPath + 1 + pEntry->cchName, &pEntry->Info);
+ if (rcExit2 != RTEXITCODE_SUCCESS)
+ rcExit = rcExit2;
+ pszPath[cchPath] = '\0';
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("Too deep recursion: %s%c%s", pszPath, RTPATH_SLASH, pEntry->szName);
+ RTVfsDirRelease(hSubDir);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("RTVfsDirOpenDir failed on %s in %s: %Rrc\n", pEntry->szName, pszPath, rc);
+ }
+ }
+ return rcExit;
+}
+
+
+/**
+ * Processes one argument.
+ *
+ * @returns Program exit code.
+ * @param pOpts The options.
+ * @param pszArg The argument.
+ */
+static RTEXITCODE rtCmdLsProcessArgument(PRTCMDLSOPTS pOpts, const char *pszArg)
+{
+ /*
+ * Query info about the object 'pszArg' indicates.
+ */
+ RTERRINFOSTATIC ErrInfo;
+ uint32_t offError;
+ RTFSOBJINFO Info;
+ uint32_t fPath = pOpts->fFollowSymlinkToAnyArgs ? RTPATH_F_FOLLOW_LINK : RTPATH_F_ON_LINK;
+ int rc = RTVfsChainQueryInfo(pszArg, &Info, RTFSOBJATTRADD_UNIX, fPath, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainQueryInfo", pszArg, rc, offError, &ErrInfo.Core);
+
+ /* Symbolic links requires special handling of course. */
+ if (RTFS_IS_SYMLINK(Info.Attr.fMode))
+ {
+ if (pOpts->fFollowSymlinkToDirArgs)
+ {
+ RTFSOBJINFO Info2;
+ rc = RTVfsChainQueryInfo(pszArg, &Info2, RTFSOBJATTRADD_UNIX, RTPATH_F_FOLLOW_LINK,
+ &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc) && !RTFS_IS_DIRECTORY(Info.Attr.fMode))
+ Info = Info2;
+ }
+ }
+
+ /*
+ * If it's not a directory or we've been told to process directories
+ * without going into them, just add it to the default collection.
+ */
+ if ( !pOpts->fFollowDirectoryArgs
+ || !RTFS_IS_DIRECTORY(Info.Attr.fMode))
+ {
+ if ( pOpts->cCollections > 0
+ || rtCmdLsNewCollection(pOpts, "") != NULL)
+ {
+ const char *pszOwner = NULL;
+ RTFSOBJINFO OwnerInfo;
+ if (Info.Attr.u.Unix.uid != NIL_RTUID && pOpts->fShowOwner)
+ {
+ rc = RTVfsChainQueryInfo(pszArg, &OwnerInfo, RTFSOBJATTRADD_UNIX_OWNER, fPath, NULL, NULL);
+ if (RT_SUCCESS(rc) && OwnerInfo.Attr.u.UnixOwner.szName[0])
+ pszOwner = &OwnerInfo.Attr.u.UnixOwner.szName[0];
+ }
+
+ const char *pszGroup = NULL;
+ RTFSOBJINFO GroupInfo;
+ if (Info.Attr.u.Unix.gid != NIL_RTGID && pOpts->fShowGroup)
+ {
+ rc = RTVfsChainQueryInfo(pszArg, &GroupInfo, RTFSOBJATTRADD_UNIX_GROUP, fPath, NULL, NULL);
+ if (RT_SUCCESS(rc) && GroupInfo.Attr.u.UnixGroup.szName[0])
+ pszGroup = &GroupInfo.Attr.u.UnixGroup.szName[0];
+ }
+
+ return rtCmdLsAddOne(pOpts->papCollections[0], pszArg, &Info, pszOwner, pszGroup, NULL);
+ }
+ return RTEXITCODE_FAILURE;
+ }
+
+ /*
+ * Open the directory.
+ */
+ RTVFSDIR hVfsDir;
+ rc = RTVfsChainOpenDir(pszArg, 0 /*fFlags*/, &hVfsDir, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenDir", pszArg, rc, offError, &ErrInfo.Core);
+
+ RTEXITCODE rcExit;
+ char szPath[RTPATH_MAX];
+ size_t cchPath = strlen(pszArg);
+ if (cchPath < sizeof(szPath))
+ {
+ memcpy(szPath, pszArg, cchPath + 1);
+ rcExit = rtCmdLsProcessDirectory(pOpts, hVfsDir, szPath, cchPath, &Info);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("Too long argument: %s", pszArg);
+ RTVfsDirRelease(hVfsDir);
+ return rcExit;
+}
+
+
+/**
+ * A /bin/ls clone.
+ *
+ * @returns Program exit code.
+ *
+ * @param cArgs The number of arguments.
+ * @param papszArgs The argument vector. (Note that this may be
+ * reordered, so the memory must be writable.)
+ */
+RTR3DECL(RTEXITCODE) RTFsCmdLs(unsigned cArgs, char **papszArgs)
+{
+ /*
+ * Parse the command line.
+ */
+#define OPT_AUTHOR 1000
+#define OPT_BLOCK_SIZE 1001
+#define OPT_COLOR 1002
+#define OPT_FILE_TYPE 1003
+#define OPT_FORMAT 1004
+#define OPT_FULL_TIME 1005
+#define OPT_GROUP_DIRECTORIES_FIRST 1006
+#define OPT_SI 1007
+#define OPT_DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR 1008
+#define OPT_HIDE 1009
+#define OPT_INDICATOR_STYLE 1010
+#define OPT_MACHINE_READABLE 1011
+#define OPT_SHOW_CONTROL_CHARS 1012
+#define OPT_QUOTING_STYLE 1013
+#define OPT_SORT 1014
+#define OPT_TIME 1015
+#define OPT_TIME_STYLE 1016
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--all", 'a', RTGETOPT_REQ_NOTHING },
+ { "--almost-all", 'A', RTGETOPT_REQ_NOTHING },
+ //{ "--author", OPT_AUTHOR, RTGETOPT_REQ_NOTHING },
+ { "--escape", 'b', RTGETOPT_REQ_NOTHING },
+ { "--block-size", OPT_BLOCK_SIZE, RTGETOPT_REQ_UINT32 },
+ { "--ctime", 'c', RTGETOPT_REQ_NOTHING },
+ //{ "--columns", 'C', RTGETOPT_REQ_NOTHING },
+ //{ "--color", OPT_COLOR, RTGETOPT_OPT_STRING },
+ { "--directory", 'd', RTGETOPT_REQ_NOTHING },
+ //{ "--dired", 'D', RTGETOPT_REQ_NOTHING },
+ { "--dash-f", 'f', RTGETOPT_REQ_NOTHING },
+ //{ "--classify", 'F', RTGETOPT_REQ_NOTHING },
+ //{ "--file-type", OPT_FILE_TYPE, RTGETOPT_REQ_NOTHING },
+ { "--format", OPT_FORMAT, RTGETOPT_REQ_STRING },
+ { "--full-time", OPT_FULL_TIME, RTGETOPT_REQ_NOTHING },
+ { "--dash-g", 'g', RTGETOPT_REQ_NOTHING },
+ { "--group-directories-first", OPT_GROUP_DIRECTORIES_FIRST, RTGETOPT_REQ_NOTHING },
+ { "--no-group", 'G', RTGETOPT_REQ_NOTHING },
+ { "--human-readable", 'h', RTGETOPT_REQ_NOTHING },
+ { "--si", OPT_SI, RTGETOPT_REQ_NOTHING },
+ { "--dereference-command-line", 'H', RTGETOPT_REQ_NOTHING },
+ { "--dereference-command-line-symlink-to-dir", OPT_DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR, RTGETOPT_REQ_NOTHING },
+ //{ "--hide" OPT_HIDE, RTGETOPT_REQ_STRING },
+ //{ "--indicator-style" OPT_INDICATOR_STYLE, RTGETOPT_REQ_STRING },
+ { "--inode", 'i', RTGETOPT_REQ_NOTHING },
+ { "--block-size-1kib", 'k', RTGETOPT_REQ_NOTHING },
+ { "--long", 'l', RTGETOPT_REQ_NOTHING },
+ { "--dereference", 'L', RTGETOPT_REQ_NOTHING },
+ { "--format-commas", 'm', RTGETOPT_REQ_NOTHING },
+ { "--machinereadable", OPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING },
+ { "--machine-readable", OPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING },
+ { "--numeric-uid-gid", 'n', RTGETOPT_REQ_NOTHING },
+ { "--literal", 'N', RTGETOPT_REQ_NOTHING },
+ { "--long-without-group-info", 'o', RTGETOPT_REQ_NOTHING },
+ //{ "--indicator-style", 'p', RTGETOPT_REQ_STRING },
+ { "--hide-control-chars", 'q', RTGETOPT_REQ_NOTHING },
+ { "--show-control-chars", OPT_SHOW_CONTROL_CHARS, RTGETOPT_REQ_NOTHING },
+ //{ "--quote-name", 'Q', RTGETOPT_REQ_NOTHING },
+ //{ "--quoting-style", OPT_QUOTING_STYLE, RTGETOPT_REQ_STRING },
+ { "--reverse", 'r', RTGETOPT_REQ_NOTHING },
+ { "--recursive", 'R', RTGETOPT_REQ_NOTHING },
+ { "--size", 's', RTGETOPT_REQ_NOTHING },
+ { "--sort-by-size", 'S', RTGETOPT_REQ_NOTHING },
+ { "--sort", OPT_SORT, RTGETOPT_REQ_STRING },
+ { "--time", OPT_TIME, RTGETOPT_REQ_STRING },
+ { "--time-style", OPT_TIME_STYLE, RTGETOPT_REQ_STRING },
+ { "--sort-by-time", 't', RTGETOPT_REQ_NOTHING },
+ { "--tabsize", 'T', RTGETOPT_REQ_UINT8 },
+ { "--atime", 'u', RTGETOPT_REQ_NOTHING },
+ { "--unsorted", 'U', RTGETOPT_REQ_NOTHING },
+ { "--version-sort", 'v', RTGETOPT_REQ_NOTHING },
+ { "--width", 'w', RTGETOPT_REQ_UINT32 },
+ { "--list-by-line", 'x', RTGETOPT_REQ_NOTHING },
+ { "--sort-by-extension", 'X', RTGETOPT_REQ_NOTHING },
+ { "--one-file-per-line", '1', RTGETOPT_REQ_NOTHING },
+ { "--help", '?', RTGETOPT_REQ_NOTHING },
+ };
+
+ RTCMDLSOPTS Opts;
+ Opts.fFollowSymlinksInDirs = false;
+ Opts.fFollowSymlinkToAnyArgs = false;
+ Opts.fFollowSymlinkToDirArgs = false;
+ Opts.fFollowDirectoryArgs = true;
+ Opts.fRecursive = false;
+ Opts.fShowHidden = false;
+ Opts.fShowDotAndDotDot = false;
+ Opts.fShowBackups = true;
+ Opts.enmSort = RTCMDLSSORT_NAME;
+ Opts.fReverseSort = false;
+ Opts.fGroupDirectoriesFirst = false;
+ Opts.enmFormat = RTCMDLSFORMAT_COLS_VERTICAL;
+ Opts.fEscapeNonGraphicChars = false;
+ Opts.fEscapeControlChars = true;
+ Opts.fHideControlChars = false;
+ Opts.fHumanReadableSizes = false; /**< -h */
+ Opts.fSiUnits = false;
+ Opts.cbBlock = 0;
+ Opts.fShowOwner = true;
+ Opts.fShowGroup = true;
+ Opts.fNumericalIds = false;
+ Opts.fShowINode = false;
+ Opts.fShowAllocatedSize = false;
+ Opts.cchTab = 8;
+ Opts.cchWidth = 80;
+ Opts.enmColor = RTCMDLSCOLOR_NONE;
+ Opts.enmTime = RTCMDLSTIME_MTIME;
+ Opts.enmTimeStyle = RTCMDLSTIMESTYLE_LOCALE;
+ Opts.pszTimeCustom = NULL;
+
+ Opts.cCollections = 0;
+ Opts.cCollectionsAllocated = 0;
+ Opts.papCollections = NULL;
+
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ unsigned cProcessed = 0;
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "RTGetOptInit: %Rrc", rc);
+
+ for (;;)
+ {
+ RTGETOPTUNION ValueUnion;
+ int chOpt = RTGetOpt(&GetState, &ValueUnion);
+ switch (chOpt)
+ {
+ case 0:
+ /* When reaching the end of arguments without having processed any
+ files/dirs/whatever yet, we do the current directory. */
+ if (cProcessed > 0)
+ {
+ RTEXITCODE rcExit2 = rtCmdLsDisplayCollections(&Opts);
+ if (rcExit2 != RTEXITCODE_SUCCESS)
+ rcExit = rcExit2;
+ rtCmdLsFreeCollections(&Opts);
+ return rcExit;
+ }
+ ValueUnion.psz = ".";
+ RT_FALL_THRU();
+ case VINF_GETOPT_NOT_OPTION:
+ {
+ RTEXITCODE rcExit2 = rtCmdLsProcessArgument(&Opts, ValueUnion.psz);
+ if (rcExit2 != RTEXITCODE_SUCCESS)
+ rcExit = rcExit2;
+ cProcessed++;
+ break;
+ }
+
+ case 'a':
+ Opts.fShowHidden = true;
+ Opts.fShowDotAndDotDot = true;
+ break;
+
+ case 'A':
+ Opts.fShowHidden = true;
+ Opts.fShowDotAndDotDot = false;
+ break;
+
+ case 'b':
+ Opts.fEscapeNonGraphicChars = true;
+ break;
+
+ case OPT_BLOCK_SIZE:
+ if (!ValueUnion.u32)
+ {
+ Assert(!Opts.papCollections);
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid block size: %u", ValueUnion.u32);
+ }
+ Opts.cbBlock = ValueUnion.u32;
+ Opts.fHumanReadableSizes = false;
+ Opts.fSiUnits = false;
+ break;
+
+ case 'c':
+ Opts.enmTime = RTCMDLSTIME_CTIME;
+ break;
+
+ case 'C':
+ Opts.enmFormat = RTCMDLSFORMAT_COLS_VERTICAL;
+ break;
+
+ case 'd':
+ Opts.fFollowDirectoryArgs = false;
+ Opts.fFollowSymlinkToAnyArgs = false;
+ Opts.fFollowSymlinkToDirArgs = false;
+ Opts.fRecursive = false;
+ break;
+
+ case 'f':
+ Opts.fShowHidden = true;
+ Opts.fShowDotAndDotDot = true;
+ if (Opts.enmFormat == RTCMDLSFORMAT_LONG)
+ Opts.enmFormat = RTCMDLSFORMAT_COLS_VERTICAL;
+ Opts.enmColor = RTCMDLSCOLOR_NONE;
+ Opts.enmSort = RTCMDLSSORT_NONE;
+ break;
+
+ case OPT_FORMAT:
+ if ( strcmp(ValueUnion.psz, "across") == 0
+ || strcmp(ValueUnion.psz, "horizontal") == 0)
+ Opts.enmFormat = RTCMDLSFORMAT_COLS_HORIZONTAL;
+ else if (strcmp(ValueUnion.psz, "commas") == 0)
+ Opts.enmFormat = RTCMDLSFORMAT_COMMAS;
+ else if ( strcmp(ValueUnion.psz, "long") == 0
+ || strcmp(ValueUnion.psz, "verbose") == 0)
+ Opts.enmFormat = RTCMDLSFORMAT_LONG;
+ else if (strcmp(ValueUnion.psz, "single-column") == 0)
+ Opts.enmFormat = RTCMDLSFORMAT_SINGLE;
+ else if (strcmp(ValueUnion.psz, "vertical") == 0)
+ Opts.enmFormat = RTCMDLSFORMAT_COLS_VERTICAL;
+ else if (strcmp(ValueUnion.psz, "machine-readable") == 0)
+ Opts.enmFormat = RTCMDLSFORMAT_MACHINE_READABLE;
+ else
+ {
+ Assert(!Opts.papCollections);
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown format: %s", ValueUnion.psz);
+ }
+ break;
+
+ case OPT_FULL_TIME:
+ Opts.enmFormat = RTCMDLSFORMAT_LONG;
+ Opts.enmTimeStyle = RTCMDLSTIMESTYLE_FULL_ISO;
+ break;
+
+ case 'g':
+ Opts.enmFormat = RTCMDLSFORMAT_LONG;
+ Opts.fShowOwner = false;
+ break;
+
+ case OPT_GROUP_DIRECTORIES_FIRST:
+ Opts.fGroupDirectoriesFirst = true;
+ break;
+
+ case 'G':
+ Opts.fShowGroup = false;
+ break;
+
+ case 'h':
+ Opts.fHumanReadableSizes = true;
+ Opts.fSiUnits = false;
+ break;
+
+ case OPT_SI:
+ Opts.fHumanReadableSizes = true;
+ Opts.fSiUnits = true;
+ break;
+
+ case 'H':
+ Opts.fFollowSymlinkToAnyArgs = true;
+ Opts.fFollowSymlinkToDirArgs = true;
+ break;
+
+ case OPT_DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR:
+ Opts.fFollowSymlinkToAnyArgs = false;
+ Opts.fFollowSymlinkToDirArgs = true;
+ break;
+
+ case 'i':
+ Opts.fShowINode = true;
+ break;
+
+ case 'k':
+ Opts.cbBlock = _1K;
+ Opts.fHumanReadableSizes = false;
+ Opts.fSiUnits = false;
+ break;
+
+ case 'l':
+ Opts.enmFormat = RTCMDLSFORMAT_LONG;
+ break;
+
+ case 'L':
+ Opts.fFollowSymlinksInDirs = true;
+ Opts.fFollowSymlinkToAnyArgs = true;
+ Opts.fFollowSymlinkToDirArgs = true;
+ break;
+
+ case 'm':
+ Opts.enmFormat = RTCMDLSFORMAT_COMMAS;
+ break;
+
+ case OPT_MACHINE_READABLE:
+ Opts.enmFormat = RTCMDLSFORMAT_MACHINE_READABLE;
+ break;
+
+ case 'n':
+ Opts.fNumericalIds = true;
+ break;
+
+ case 'N':
+ Opts.fEscapeNonGraphicChars = false;
+ Opts.fEscapeControlChars = false;
+ Opts.fHideControlChars = false;
+ break;
+
+ case 'o':
+ Opts.enmFormat = RTCMDLSFORMAT_LONG;
+ Opts.fShowGroup = false;
+ break;
+
+ case 'q':
+ Opts.fHideControlChars = true;
+ break;
+
+ case OPT_SHOW_CONTROL_CHARS:
+ Opts.fHideControlChars = true;
+ break;
+
+ case 'r':
+ Opts.fReverseSort = true;
+ break;
+
+ case 'R':
+ Opts.fRecursive = true;
+ break;
+
+ case 's':
+ Opts.fShowAllocatedSize = true;
+ break;
+
+ case 'S':
+ Opts.enmSort = RTCMDLSSORT_SIZE;
+ break;
+
+ case OPT_SORT:
+ if (strcmp(ValueUnion.psz, "none") == 0)
+ Opts.enmSort = RTCMDLSSORT_NONE;
+ else if (strcmp(ValueUnion.psz, "extension") == 0)
+ Opts.enmSort = RTCMDLSSORT_EXTENSION;
+ else if (strcmp(ValueUnion.psz, "size") == 0)
+ Opts.enmSort = RTCMDLSSORT_SIZE;
+ else if (strcmp(ValueUnion.psz, "time") == 0)
+ Opts.enmSort = RTCMDLSSORT_TIME;
+ else if (strcmp(ValueUnion.psz, "version") == 0)
+ Opts.enmSort = RTCMDLSSORT_VERSION;
+ else
+ {
+ Assert(!Opts.papCollections);
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown sort by: %s", ValueUnion.psz);
+ }
+ break;
+
+ case OPT_TIME:
+ if ( strcmp(ValueUnion.psz, "btime") == 0
+ || strcmp(ValueUnion.psz, "birth") == 0)
+ Opts.enmTime = RTCMDLSTIME_BTIME;
+ else if ( strcmp(ValueUnion.psz, "ctime") == 0
+ || strcmp(ValueUnion.psz, "status") == 0)
+ Opts.enmTime = RTCMDLSTIME_CTIME;
+ else if ( strcmp(ValueUnion.psz, "mtime") == 0
+ || strcmp(ValueUnion.psz, "write") == 0
+ || strcmp(ValueUnion.psz, "modify") == 0)
+ Opts.enmTime = RTCMDLSTIME_MTIME;
+ else if ( strcmp(ValueUnion.psz, "atime") == 0
+ || strcmp(ValueUnion.psz, "access") == 0
+ || strcmp(ValueUnion.psz, "use") == 0)
+ Opts.enmTime = RTCMDLSTIME_ATIME;
+ else
+ {
+ Assert(!Opts.papCollections);
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown time attribute: %s", ValueUnion.psz);
+ }
+ break;
+
+ case OPT_TIME_STYLE:
+ if (strcmp(ValueUnion.psz, "full-iso") == 0)
+ Opts.enmTimeStyle = RTCMDLSTIMESTYLE_FULL_ISO;
+ else if (strcmp(ValueUnion.psz, "long-iso") == 0)
+ Opts.enmTimeStyle = RTCMDLSTIMESTYLE_LONG_ISO;
+ else if (strcmp(ValueUnion.psz, "iso") == 0)
+ Opts.enmTimeStyle = RTCMDLSTIMESTYLE_ISO;
+ else if (strcmp(ValueUnion.psz, "locale") == 0)
+ Opts.enmTimeStyle = RTCMDLSTIMESTYLE_LOCALE;
+ else if (*ValueUnion.psz == '+')
+ {
+ Opts.enmTimeStyle = RTCMDLSTIMESTYLE_CUSTOM;
+ Opts.pszTimeCustom = ValueUnion.psz;
+ }
+ else
+ {
+ Assert(!Opts.papCollections);
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown sort by: %s", ValueUnion.psz);
+ }
+ break;
+
+ case 't':
+ Opts.enmSort = RTCMDLSSORT_TIME;
+ break;
+
+ case 'T':
+ Opts.cchTab = ValueUnion.u8;
+ break;
+
+ case 'u':
+ Opts.enmTime = RTCMDLSTIME_ATIME;
+ break;
+
+ case 'U':
+ Opts.enmSort = RTCMDLSSORT_NONE;
+ break;
+
+ case 'v':
+ Opts.enmSort = RTCMDLSSORT_VERSION;
+ break;
+
+ case 'w':
+ Opts.cchWidth = ValueUnion.u32;
+ break;
+
+ case 'x':
+ Opts.enmFormat = RTCMDLSFORMAT_COLS_HORIZONTAL;
+ break;
+
+ case 'X':
+ Opts.enmSort = RTCMDLSSORT_EXTENSION;
+ break;
+
+ case '1':
+ Opts.enmFormat = RTCMDLSFORMAT_SINGLE;
+ break;
+
+ case '?':
+ {
+ RTPrintf("Usage: to be written\n"
+ "Options dump:\n");
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aOptions); i++)
+ if (s_aOptions[i].iShort < 127 && s_aOptions[i].iShort >= 0x20)
+ RTPrintf(" -%c,%s\n", s_aOptions[i].iShort, s_aOptions[i].pszLong);
+ else
+ RTPrintf(" %s\n", s_aOptions[i].pszLong);
+#ifdef RT_OS_WINDOWS
+ const char *pszProgNm = RTPathFilename(papszArgs[0]);
+ RTPrintf("\n"
+ "The path prefix '\\\\:iprtnt:\\' can be used to access the NT namespace.\n"
+ "To list devices: %s -la \\\\:iprtnt:\\Device\n"
+ "To list win32 devices: %s -la \\\\:iprtnt:\\GLOBAL??\n"
+ "To list the root (hack/bug): %s -la \\\\:iprtnt:\\\n",
+ pszProgNm, pszProgNm, pszProgNm);
+#endif
+ Assert(!Opts.papCollections);
+ return RTEXITCODE_SUCCESS;
+ }
+
+ case 'V':
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ Assert(!Opts.papCollections);
+ return RTEXITCODE_SUCCESS;
+
+ default:
+ Assert(!Opts.papCollections);
+ return RTGetOptPrintError(chOpt, &ValueUnion);
+ }
+ }
+}
+
diff --git a/src/VBox/Runtime/common/fs/extvfs.cpp b/src/VBox/Runtime/common/fs/extvfs.cpp
new file mode 100644
index 00000000..9f2d2b01
--- /dev/null
+++ b/src/VBox/Runtime/common/fs/extvfs.cpp
@@ -0,0 +1,2860 @@
+/* $Id: extvfs.cpp $ */
+/** @file
+ * IPRT - Ext2/3/4 Virtual Filesystem.
+ */
+
+/*
+ * Copyright (C) 2012-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 <iprt/fsvfs.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/avl.h>
+#include <iprt/file.h>
+#include <iprt/err.h>
+#include <iprt/list.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+#include <iprt/formats/ext.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The maximum block group cache size (in bytes). */
+#if ARCH_BITS >= 64
+# define RTFSEXT_MAX_BLOCK_GROUP_CACHE_SIZE _512K
+#else
+# define RTFSEXT_MAX_BLOCK_GROUP_CACHE_SIZE _128K
+#endif
+/** The maximum inode cache size (in bytes). */
+#if ARCH_BITS >= 64
+# define RTFSEXT_MAX_INODE_CACHE_SIZE _512K
+#else
+# define RTFSEXT_MAX_INODE_CACHE_SIZE _128K
+#endif
+/** The maximum extent/block map cache size (in bytes). */
+#if ARCH_BITS >= 64
+# define RTFSEXT_MAX_BLOCK_CACHE_SIZE _512K
+#else
+# define RTFSEXT_MAX_BLOCK_CACHE_SIZE _128K
+#endif
+
+/** All supported incompatible features. */
+#define RTFSEXT_INCOMPAT_FEATURES_SUPP ( EXT_SB_FEAT_INCOMPAT_DIR_FILETYPE | EXT_SB_FEAT_INCOMPAT_EXTENTS | EXT_SB_FEAT_INCOMPAT_64BIT \
+ | EXT_SB_FEAT_INCOMPAT_FLEX_BG)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to the ext filesystem data. */
+typedef struct RTFSEXTVOL *PRTFSEXTVOL;
+
+
+/**
+ * Cached block group descriptor data.
+ */
+typedef struct RTFSEXTBLKGRP
+{
+ /** AVL tree node, indexed by the block group number. */
+ AVLU32NODECORE Core;
+ /** List node for the LRU list used for eviction. */
+ RTLISTNODE NdLru;
+ /** Reference counter. */
+ volatile uint32_t cRefs;
+ /** Block number where the inode table is store. */
+ uint64_t iBlockInodeTbl;
+ /** Pointer to the inode bitmap. */
+ uint8_t *pabInodeBitmap;
+ /** Block bitmap - variable in size (depends on the block size
+ * and number of blocks per group). */
+ uint8_t abBlockBitmap[1];
+} RTFSEXTBLKGRP;
+/** Pointer to block group descriptor data. */
+typedef RTFSEXTBLKGRP *PRTFSEXTBLKGRP;
+
+
+/**
+ * In-memory inode.
+ */
+typedef struct RTFSEXTINODE
+{
+ /** AVL tree node, indexed by the inode number. */
+ AVLU32NODECORE Core;
+ /** List node for the inode LRU list used for eviction. */
+ RTLISTNODE NdLru;
+ /** Reference counter. */
+ volatile uint32_t cRefs;
+ /** Byte offset in the backing file where the inode is stored.. */
+ uint64_t offInode;
+ /** Inode data. */
+ RTFSOBJINFO ObjInfo;
+ /** Inode flags (copied from the on disk inode). */
+ uint32_t fFlags;
+ /** Copy of the block map/extent tree. */
+ uint32_t aiBlocks[EXT_INODE_BLOCK_ENTRIES];
+} RTFSEXTINODE;
+/** Pointer to an in-memory inode. */
+typedef RTFSEXTINODE *PRTFSEXTINODE;
+
+
+/**
+ * Block cache entry.
+ */
+typedef struct RTFSEXTBLOCKENTRY
+{
+ /** AVL tree node, indexed by the filesystem block number. */
+ AVLU64NODECORE Core;
+ /** List node for the inode LRU list used for eviction. */
+ RTLISTNODE NdLru;
+ /** Reference counter. */
+ volatile uint32_t cRefs;
+ /** The block data. */
+ uint8_t abData[1];
+} RTFSEXTBLOCKENTRY;
+/** Pointer to a block cache entry. */
+typedef RTFSEXTBLOCKENTRY *PRTFSEXTBLOCKENTRY;
+
+
+/**
+ * Open directory instance.
+ */
+typedef struct RTFSEXTDIR
+{
+ /** Volume this directory belongs to. */
+ PRTFSEXTVOL pVol;
+ /** The underlying inode structure. */
+ PRTFSEXTINODE pInode;
+ /** Set if we've reached the end of the directory enumeration. */
+ bool fNoMoreFiles;
+ /** Current offset into the directory where the next entry should be read. */
+ uint64_t offEntry;
+ /** Next entry index (for logging purposes). */
+ uint32_t idxEntry;
+} RTFSEXTDIR;
+/** Pointer to an open directory instance. */
+typedef RTFSEXTDIR *PRTFSEXTDIR;
+
+
+/**
+ * Open file instance.
+ */
+typedef struct RTFSEXTFILE
+{
+ /** Volume this directory belongs to. */
+ PRTFSEXTVOL pVol;
+ /** The underlying inode structure. */
+ PRTFSEXTINODE pInode;
+ /** Current offset into the file for I/O. */
+ RTFOFF offFile;
+} RTFSEXTFILE;
+/** Pointer to an open file instance. */
+typedef RTFSEXTFILE *PRTFSEXTFILE;
+
+
+/**
+ * Ext2/3/4 filesystem volume.
+ */
+typedef struct RTFSEXTVOL
+{
+ /** Handle to itself. */
+ RTVFS hVfsSelf;
+ /** The file, partition, or whatever backing the ext volume. */
+ RTVFSFILE hVfsBacking;
+ /** The size of the backing thingy. */
+ uint64_t cbBacking;
+
+ /** RTVFSMNT_F_XXX. */
+ uint32_t fMntFlags;
+ /** RTFSEXTVFS_F_XXX (currently none defined). */
+ uint32_t fExtFlags;
+
+ /** Flag whether the filesystem is 64bit. */
+ bool f64Bit;
+ /** Size of one block. */
+ size_t cbBlock;
+ /** Number of bits to shift left for fast conversion of block numbers to offsets. */
+ uint32_t cBlockShift;
+ /** Number of blocks in one group. */
+ uint32_t cBlocksPerGroup;
+ /** Number of inodes in each block group. */
+ uint32_t cInodesPerGroup;
+ /** Number of blocks groups in the volume. */
+ uint32_t cBlockGroups;
+ /** Size of the block bitmap. */
+ size_t cbBlockBitmap;
+ /** Size of the inode bitmap. */
+ size_t cbInodeBitmap;
+ /** Size of block group descriptor. */
+ size_t cbBlkGrpDesc;
+ /** Size of an inode. */
+ size_t cbInode;
+
+ /** Incompatible features selected for this filesystem. */
+ uint32_t fFeaturesIncompat;
+
+ /** @name Block group cache.
+ * @{ */
+ /** LRU list anchor. */
+ RTLISTANCHOR LstBlockGroupLru;
+ /** Root of the cached block group tree. */
+ AVLU32TREE BlockGroupRoot;
+ /** Size of the cached block groups. */
+ size_t cbBlockGroups;
+ /** @} */
+
+ /** @name Inode cache.
+ * @{ */
+ /** LRU list anchor for the inode cache. */
+ RTLISTANCHOR LstInodeLru;
+ /** Root of the cached inode tree. */
+ AVLU32TREE InodeRoot;
+ /** Size of the cached inodes. */
+ size_t cbInodes;
+ /** @} */
+
+ /** @name Block cache.
+ * @{ */
+ /** LRU list anchor for the block cache. */
+ RTLISTANCHOR LstBlockLru;
+ /** Root of the cached block tree. */
+ AVLU64TREE BlockRoot;
+ /** Size of cached blocks. */
+ size_t cbBlocks;
+ /** @} */
+} RTFSEXTVOL;
+
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int rtFsExtVol_OpenDirByInode(PRTFSEXTVOL pThis, uint32_t iInode, PRTVFSDIR phVfsDir);
+
+#ifdef LOG_ENABLED
+
+/**
+ * Logs the ext filesystem superblock.
+ *
+ * @param pSb Pointer to the superblock.
+ */
+static void rtFsExtSb_Log(PCEXTSUPERBLOCK pSb)
+{
+ if (LogIs2Enabled())
+ {
+ RTTIMESPEC Spec;
+ char sz[80];
+
+ Log2(("EXT: Superblock:\n"));
+ Log2(("EXT: cInodesTotal %RU32\n", RT_LE2H_U32(pSb->cInodesTotal)));
+ Log2(("EXT: cBlocksTotalLow %RU32\n", RT_LE2H_U32(pSb->cBlocksTotalLow)));
+ Log2(("EXT: cBlocksRsvdForSuperUserLow %RU32\n", RT_LE2H_U32(pSb->cBlocksRsvdForSuperUserLow)));
+ Log2(("EXT: cBlocksFreeLow %RU32\n", RT_LE2H_U32(pSb->cBlocksFreeLow)));
+ Log2(("EXT: cInodesFree %RU32\n", RT_LE2H_U32(pSb->cInodesFree)));
+ Log2(("EXT: iBlockOfSuperblock %RU32\n", RT_LE2H_U32(pSb->iBlockOfSuperblock)));
+ Log2(("EXT: cLogBlockSize %RU32\n", RT_LE2H_U32(pSb->cLogBlockSize)));
+ Log2(("EXT: cLogClusterSize %RU32\n", RT_LE2H_U32(pSb->cLogClusterSize)));
+ Log2(("EXT: cBlocksPerGroup %RU32\n", RT_LE2H_U32(pSb->cBlocksPerGroup)));
+ Log2(("EXT: cClustersPerBlockGroup %RU32\n", RT_LE2H_U32(pSb->cClustersPerBlockGroup)));
+ Log2(("EXT: cInodesPerBlockGroup %RU32\n", RT_LE2H_U32(pSb->cInodesPerBlockGroup)));
+ Log2(("EXT: u32LastMountTime %#RX32 %s\n", RT_LE2H_U32(pSb->u32LastMountTime),
+ RTTimeSpecToString(RTTimeSpecSetSeconds(&Spec, RT_LE2H_U32(pSb->u32LastMountTime)), sz, sizeof(sz))));
+ Log2(("EXT: u32LastWrittenTime %#RX32 %s\n", RT_LE2H_U32(pSb->u32LastWrittenTime),
+ RTTimeSpecToString(RTTimeSpecSetSeconds(&Spec, RT_LE2H_U32(pSb->u32LastWrittenTime)), sz, sizeof(sz))));
+ Log2(("EXT: cMountsSinceLastCheck %RU16\n", RT_LE2H_U32(pSb->cMountsSinceLastCheck)));
+ Log2(("EXT: cMaxMountsUntilCheck %RU16\n", RT_LE2H_U32(pSb->cMaxMountsUntilCheck)));
+ Log2(("EXT: u16Signature %#RX16\n", RT_LE2H_U32(pSb->u16Signature)));
+ Log2(("EXT: u16FilesystemState %#RX16\n", RT_LE2H_U32(pSb->u16FilesystemState)));
+ Log2(("EXT: u16ActionOnError %#RX16\n", RT_LE2H_U32(pSb->u16ActionOnError)));
+ Log2(("EXT: u16RevLvlMinor %#RX16\n", RT_LE2H_U32(pSb->u16RevLvlMinor)));
+ Log2(("EXT: u32LastCheckTime %#RX32 %s\n", RT_LE2H_U32(pSb->u32LastCheckTime),
+ RTTimeSpecToString(RTTimeSpecSetSeconds(&Spec, RT_LE2H_U32(pSb->u32LastCheckTime)), sz, sizeof(sz))));
+ Log2(("EXT: u32CheckInterval %RU32\n", RT_LE2H_U32(pSb->u32CheckInterval)));
+ Log2(("EXT: u32OsIdCreator %#RX32\n", RT_LE2H_U32(pSb->u32OsIdCreator)));
+ Log2(("EXT: u32RevLvl %#RX32\n", RT_LE2H_U32(pSb->u32RevLvl)));
+ Log2(("EXT: u16UidReservedBlocks %#RX16\n", RT_LE2H_U32(pSb->u16UidReservedBlocks)));
+ Log2(("EXT: u16GidReservedBlocks %#RX16\n", RT_LE2H_U32(pSb->u16GidReservedBlocks)));
+ if (RT_LE2H_U32(pSb->u32RevLvl) == EXT_SB_REV_V2_DYN_INODE_SZ)
+ {
+ Log2(("EXT: iFirstInodeNonRsvd %#RX32\n", RT_LE2H_U32(pSb->iFirstInodeNonRsvd)));
+ Log2(("EXT: cbInode %#RX16\n", RT_LE2H_U32(pSb->cbInode)));
+ Log2(("EXT: iBlkGrpSb %#RX16\n", RT_LE2H_U32(pSb->iBlkGrpSb)));
+ Log2(("EXT: fFeaturesCompat %#RX32%s%s%s%s%s%s%s%s%s%s\n", RT_LE2H_U32(pSb->fFeaturesCompat),
+ RT_LE2H_U32(pSb->fFeaturesCompat) & EXT_SB_FEAT_COMPAT_DIR_PREALLOC ? " dir-prealloc" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompat) & EXT_SB_FEAT_COMPAT_IMAGIC_INODES ? " imagic-inode" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompat) & EXT_SB_FEAT_COMPAT_HAS_JOURNAL ? " has-journal" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompat) & EXT_SB_FEAT_COMPAT_EXT_ATTR ? " ext-attrs" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompat) & EXT_SB_FEAT_COMPAT_RESIZE_INODE ? " resize-inode" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompat) & EXT_SB_FEAT_COMPAT_DIR_INDEX ? " dir-index" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompat) & EXT_SB_FEAT_COMPAT_LAZY_BG ? " lazy-bg" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompat) & EXT_SB_FEAT_COMPAT_EXCLUDE_INODE ? " excl-inode" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompat) & EXT_SB_FEAT_COMPAT_EXCLUDE_BITMAP ? " excl-bitmap" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompat) & EXT_SB_FEAT_COMPAT_SPARSE_SUPER2 ? " sparse-super2" : ""));
+ Log2(("EXT: fFeaturesIncompat %#RX32%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", RT_LE2H_U32(pSb->fFeaturesIncompat),
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_COMPRESSION ? " compression" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_DIR_FILETYPE ? " dir-filetype" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_RECOVER ? " recovery" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_JOURNAL_DEV ? " journal-dev" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_META_BG ? " meta-bg" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_EXTENTS ? " extents" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_64BIT ? " 64bit" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_MMP ? " mmp" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_FLEX_BG ? " flex-bg" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_EXT_ATTR_INODE ? " extattr-inode" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_DIRDATA ? " dir-data" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_CSUM_SEED ? " csum-seed" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_LARGE_DIR ? " large-dir" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_INLINE_DATA ? " inline-data" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_ENCRYPT ? " encrypt" : ""));
+ Log2(("EXT: fFeaturesCompatRo %#RX32%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", RT_LE2H_U32(pSb->fFeaturesCompatRo),
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_SPARSE_SUPER ? " sparse-super" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_LARGE_FILE ? " large-file" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_BTREE_DIR ? " btree-dir" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_HUGE_FILE ? " huge-file" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_GDT_CHSKUM ? " gdt-chksum" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_DIR_NLINK ? " dir-nlink" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_EXTRA_INODE_SZ ? " extra-inode" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_HAS_SNAPSHOTS ? " snapshots" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_QUOTA ? " quota" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_BIGALLOC ? " big-alloc" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_METADATA_CHKSUM ? " meta-chksum" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_REPLICA ? " replica" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_READONLY ? " ro" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_PROJECT ? " project" : ""));
+ Log2(("EXT: au8Uuid <todo>\n"));
+ Log2(("EXT: achVolumeName %16s\n", &pSb->achVolumeName[0]));
+ Log2(("EXT: achLastMounted %64s\n", &pSb->achLastMounted[0]));
+ Log2(("EXT: u32AlgoUsageBitmap %#RX32\n", RT_LE2H_U32(pSb->u32AlgoUsageBitmap)));
+ Log2(("EXT: cBlocksPrealloc %RU8\n", pSb->cBlocksPrealloc));
+ Log2(("EXT: cBlocksPreallocDirectory %RU8\n", pSb->cBlocksPreallocDirectory));
+ Log2(("EXT: cGdtEntriesRsvd %RU16\n", pSb->cGdtEntriesRsvd));
+ Log2(("EXT: au8JournalUuid <todo>\n"));
+ Log2(("EXT: iJournalInode %#RX32\n", RT_LE2H_U32(pSb->iJournalInode)));
+ Log2(("EXT: u32JournalDev %#RX32\n", RT_LE2H_U32(pSb->u32JournalDev)));
+ Log2(("EXT: u32LastOrphan %#RX32\n", RT_LE2H_U32(pSb->u32LastOrphan)));
+ Log2(("EXT: au32HashSeedHtree[0] %#RX32\n", RT_LE2H_U32(pSb->au32HashSeedHtree[0])));
+ Log2(("EXT: au32HashSeedHtree[1] %#RX32\n", RT_LE2H_U32(pSb->au32HashSeedHtree[1])));
+ Log2(("EXT: au32HashSeedHtree[2] %#RX32\n", RT_LE2H_U32(pSb->au32HashSeedHtree[2])));
+ Log2(("EXT: au32HashSeedHtree[3] %#RX32\n", RT_LE2H_U32(pSb->au32HashSeedHtree[3])));
+ Log2(("EXT: u8HashVersionDef %#RX8\n", pSb->u8HashVersionDef));
+ Log2(("EXT: u8JnlBackupType %#RX8\n", pSb->u8JnlBackupType));
+ Log2(("EXT: cbGroupDesc %RU16\n", RT_LE2H_U16(pSb->cbGroupDesc)));
+ Log2(("EXT: fMntOptsDef %#RX32\n", RT_LE2H_U32(pSb->fMntOptsDef)));
+ Log2(("EXT: iFirstMetaBg %#RX32\n", RT_LE2H_U32(pSb->iFirstMetaBg)));
+ Log2(("EXT: u32TimeFsCreation %#RX32 %s\n", RT_LE2H_U32(pSb->u32TimeFsCreation),
+ RTTimeSpecToString(RTTimeSpecSetSeconds(&Spec, RT_LE2H_U32(pSb->u32TimeFsCreation)), sz, sizeof(sz))));
+ for (unsigned i = 0; i < RT_ELEMENTS(pSb->au32JnlBlocks); i++)
+ Log2(("EXT: au32JnlBlocks[%u] %#RX32\n", i, RT_LE2H_U32(pSb->au32JnlBlocks[i])));
+ Log2(("EXT: cBlocksTotalHigh %#RX32\n", RT_LE2H_U32(pSb->cBlocksTotalHigh)));
+ Log2(("EXT: cBlocksRsvdForSuperUserHigh %#RX32\n", RT_LE2H_U32(pSb->cBlocksRsvdForSuperUserHigh)));
+ Log2(("EXT: cBlocksFreeHigh %#RX32\n", RT_LE2H_U32(pSb->cBlocksFreeHigh)));
+ Log2(("EXT: cbInodesExtraMin %#RX16\n", RT_LE2H_U16(pSb->cbInodesExtraMin)));
+ Log2(("EXT: cbNewInodesRsv %#RX16\n", RT_LE2H_U16(pSb->cbInodesExtraMin)));
+ Log2(("EXT: fFlags %#RX32\n", RT_LE2H_U32(pSb->fFlags)));
+ Log2(("EXT: cRaidStride %RU16\n", RT_LE2H_U16(pSb->cRaidStride)));
+ Log2(("EXT: cSecMmpInterval %RU16\n", RT_LE2H_U16(pSb->cSecMmpInterval)));
+ Log2(("EXT: iMmpBlock %#RX64\n", RT_LE2H_U64(pSb->iMmpBlock)));
+ Log2(("EXT: cRaidStrideWidth %#RX32\n", RT_LE2H_U32(pSb->cRaidStrideWidth)));
+ Log2(("EXT: cLogGroupsPerFlex %RU8\n", pSb->cLogGroupsPerFlex));
+ Log2(("EXT: u8ChksumType %RX8\n", pSb->u8ChksumType));
+ Log2(("EXT: cKbWritten %#RX64\n", RT_LE2H_U64(pSb->cKbWritten)));
+ Log2(("EXT: iSnapshotInode %#RX32\n", RT_LE2H_U32(pSb->iSnapshotInode)));
+ Log2(("EXT: iSnapshotId %#RX32\n", RT_LE2H_U32(pSb->iSnapshotId)));
+ Log2(("EXT: cSnapshotRsvdBlocks %#RX64\n", RT_LE2H_U64(pSb->cSnapshotRsvdBlocks)));
+ Log2(("EXT: iSnapshotListInode %#RX32\n", RT_LE2H_U32(pSb->iSnapshotListInode)));
+ Log2(("EXT: cErrorsSeen %#RX32\n", RT_LE2H_U32(pSb->cErrorsSeen)));
+ Log2(("EXT: [...]\n")); /** @todo Missing fields if becoming interesting. */
+ Log2(("EXT: iInodeLostFound %#RX32\n", RT_LE2H_U32(pSb->iInodeLostFound)));
+ Log2(("EXT: iInodeProjQuota %#RX32\n", RT_LE2H_U32(pSb->iInodeProjQuota)));
+ Log2(("EXT: u32ChksumSeed %#RX32\n", RT_LE2H_U32(pSb->u32ChksumSeed)));
+ Log2(("EXT: [...]\n")); /** @todo Missing fields if becoming interesting. */
+ Log2(("EXT: u32Chksum %#RX32\n", RT_LE2H_U32(pSb->u32Chksum)));
+ }
+ }
+}
+
+
+/**
+ * Logs a ext filesystem block group descriptor.
+ *
+ * @param pThis The ext volume instance.
+ * @param iBlockGroup Block group number.
+ * @param pBlockGroup Pointer to the block group.
+ */
+static void rtFsExtBlockGroup_Log(PRTFSEXTVOL pThis, uint32_t iBlockGroup, PCEXTBLOCKGROUPDESC pBlockGroup)
+{
+ if (LogIs2Enabled())
+ {
+ uint64_t iBlockStart = (uint64_t)iBlockGroup * pThis->cBlocksPerGroup;
+ Log2(("EXT: Block group %#RX32 (blocks %#RX64 to %#RX64):\n",
+ iBlockGroup, iBlockStart, iBlockStart + pThis->cBlocksPerGroup - 1));
+ Log2(("EXT: offBlockBitmapLow %#RX32\n", RT_LE2H_U32(pBlockGroup->v32.offBlockBitmapLow)));
+ Log2(("EXT: offInodeBitmapLow %#RX32\n", RT_LE2H_U32(pBlockGroup->v32.offInodeBitmapLow)));
+ Log2(("EXT: offInodeTableLow %#RX32\n", RT_LE2H_U32(pBlockGroup->v32.offInodeTableLow)));
+ Log2(("EXT: cBlocksFreeLow %#RX16\n", RT_LE2H_U16(pBlockGroup->v32.cBlocksFreeLow)));
+ Log2(("EXT: cInodesFreeLow %#RX16\n", RT_LE2H_U16(pBlockGroup->v32.cInodesFreeLow)));
+ Log2(("EXT: cDirectoriesLow %#RX16\n", RT_LE2H_U16(pBlockGroup->v32.cDirectoriesLow)));
+ Log2(("EXT: fFlags %#RX16\n", RT_LE2H_U16(pBlockGroup->v32.fFlags)));
+ Log2(("EXT: offSnapshotExclBitmapLow %#RX32\n", RT_LE2H_U32(pBlockGroup->v32.offSnapshotExclBitmapLow)));
+ Log2(("EXT: u16ChksumBlockBitmapLow %#RX16\n", RT_LE2H_U16(pBlockGroup->v32.u16ChksumBlockBitmapLow)));
+ Log2(("EXT: u16ChksumInodeBitmapLow %#RX16\n", RT_LE2H_U16(pBlockGroup->v32.u16ChksumInodeBitmapLow)));
+ Log2(("EXT: cInodeTblUnusedLow %#RX16\n", RT_LE2H_U16(pBlockGroup->v32.cInodeTblUnusedLow)));
+ Log2(("EXT: u16Chksum %#RX16\n", RT_LE2H_U16(pBlockGroup->v32.u16Chksum)));
+ if (pThis->cbBlkGrpDesc == sizeof(EXTBLOCKGROUPDESC64))
+ {
+ Log2(("EXT: offBlockBitmapHigh %#RX32\n", RT_LE2H_U32(pBlockGroup->v64.offBlockBitmapHigh)));
+ Log2(("EXT: offInodeBitmapHigh %#RX32\n", RT_LE2H_U32(pBlockGroup->v64.offInodeBitmapHigh)));
+ Log2(("EXT: offInodeTableHigh %#RX32\n", RT_LE2H_U32(pBlockGroup->v64.offInodeTableHigh)));
+ Log2(("EXT: cBlocksFreeHigh %#RX16\n", RT_LE2H_U16(pBlockGroup->v64.cBlocksFreeHigh)));
+ Log2(("EXT: cInodesFreeHigh %#RX16\n", RT_LE2H_U16(pBlockGroup->v64.cInodesFreeHigh)));
+ Log2(("EXT: cDirectoriesHigh %#RX16\n", RT_LE2H_U16(pBlockGroup->v64.cDirectoriesHigh)));
+ Log2(("EXT: cInodeTblUnusedHigh %#RX16\n", RT_LE2H_U16(pBlockGroup->v64.cInodeTblUnusedHigh)));
+ Log2(("EXT: offSnapshotExclBitmapHigh %#RX32\n", RT_LE2H_U32(pBlockGroup->v64.offSnapshotExclBitmapHigh)));
+ Log2(("EXT: u16ChksumBlockBitmapHigh %#RX16\n", RT_LE2H_U16(pBlockGroup->v64.u16ChksumBlockBitmapHigh)));
+ Log2(("EXT: u16ChksumInodeBitmapHigh %#RX16\n", RT_LE2H_U16(pBlockGroup->v64.u16ChksumInodeBitmapHigh)));
+ }
+ }
+}
+
+
+/**
+ * Logs a ext filesystem inode.
+ *
+ * @param pThis The ext volume instance.
+ * @param iInode Inode number.
+ * @param pInode Pointer to the inode.
+ */
+static void rtFsExtInode_Log(PRTFSEXTVOL pThis, uint32_t iInode, PCEXTINODECOMB pInode)
+{
+ if (LogIs2Enabled())
+ {
+ RTTIMESPEC Spec;
+ char sz[80];
+
+ Log2(("EXT: Inode %#RX32:\n", iInode));
+ Log2(("EXT: fMode %#RX16\n", RT_LE2H_U16(pInode->Core.fMode)));
+ Log2(("EXT: uUidLow %#RX16\n", RT_LE2H_U16(pInode->Core.uUidLow)));
+ Log2(("EXT: cbSizeLow %#RX32\n", RT_LE2H_U32(pInode->Core.cbSizeLow)));
+ Log2(("EXT: u32TimeLastAccess %#RX32 %s\n", RT_LE2H_U32(pInode->Core.u32TimeLastAccess),
+ RTTimeSpecToString(RTTimeSpecSetSeconds(&Spec, RT_LE2H_U32(pInode->Core.u32TimeLastAccess)), sz, sizeof(sz))));
+ Log2(("EXT: u32TimeLastChange %#RX32 %s\n", RT_LE2H_U32(pInode->Core.u32TimeLastChange),
+ RTTimeSpecToString(RTTimeSpecSetSeconds(&Spec, RT_LE2H_U32(pInode->Core.u32TimeLastChange)), sz, sizeof(sz))));
+ Log2(("EXT: u32TimeLastModification %#RX32 %s\n", RT_LE2H_U32(pInode->Core.u32TimeLastModification),
+ RTTimeSpecToString(RTTimeSpecSetSeconds(&Spec, RT_LE2H_U32(pInode->Core.u32TimeLastModification)), sz, sizeof(sz))));
+ Log2(("EXT: u32TimeDeletion %#RX32 %s\n", RT_LE2H_U32(pInode->Core.u32TimeDeletion),
+ RTTimeSpecToString(RTTimeSpecSetSeconds(&Spec, RT_LE2H_U32(pInode->Core.u32TimeDeletion)), sz, sizeof(sz))));
+ Log2(("EXT: uGidLow %#RX16\n", RT_LE2H_U16(pInode->Core.uGidLow)));
+ Log2(("EXT: cHardLinks %#RU16\n", RT_LE2H_U16(pInode->Core.cHardLinks)));
+ Log2(("EXT: cBlocksLow %#RX32\n", RT_LE2H_U32(pInode->Core.cBlocksLow)));
+ Log2(("EXT: fFlags %#RX32\n", RT_LE2H_U32(pInode->Core.fFlags)));
+ Log2(("EXT: Osd1.u32LnxVersion %#RX32\n", RT_LE2H_U32(pInode->Core.Osd1.u32LnxVersion)));
+ for (unsigned i = 0; i < RT_ELEMENTS(pInode->Core.au32Block); i++)
+ Log2(("EXT: au32Block[%u] %#RX32\n", i, RT_LE2H_U32(pInode->Core.au32Block[i])));
+ Log2(("EXT: u32Version %#RX32\n", RT_LE2H_U32(pInode->Core.u32Version)));
+ Log2(("EXT: offExtAttrLow %#RX32\n", RT_LE2H_U32(pInode->Core.offExtAttrLow)));
+ Log2(("EXT: cbSizeHigh %#RX32\n", RT_LE2H_U32(pInode->Core.cbSizeHigh)));
+ Log2(("EXT: u32FragmentAddrObs %#RX32\n", RT_LE2H_U32(pInode->Core.u32FragmentAddrObs)));
+ Log2(("EXT: Osd2.Lnx.cBlocksHigh %#RX32\n", RT_LE2H_U32(pInode->Core.Osd2.Lnx.cBlocksHigh)));
+ Log2(("EXT: Osd2.Lnx.offExtAttrHigh %#RX32\n", RT_LE2H_U32(pInode->Core.Osd2.Lnx.offExtAttrHigh)));
+ Log2(("EXT: Osd2.Lnx.uUidHigh %#RX16\n", RT_LE2H_U16(pInode->Core.Osd2.Lnx.uUidHigh)));
+ Log2(("EXT: Osd2.Lnx.uGidHigh %#RX16\n", RT_LE2H_U16(pInode->Core.Osd2.Lnx.uGidHigh)));
+ Log2(("EXT: Osd2.Lnx.u16ChksumLow %#RX16\n", RT_LE2H_U16(pInode->Core.Osd2.Lnx.u16ChksumLow)));
+
+ if (pThis->cbInode >= sizeof(EXTINODECOMB))
+ {
+ Log2(("EXT: cbInodeExtra %#RU16\n", RT_LE2H_U16(pInode->Extra.cbInodeExtra)));
+ Log2(("EXT: u16ChksumHigh %#RX16\n", RT_LE2H_U16(pInode->Extra.u16ChksumHigh)));
+ Log2(("EXT: u32ExtraTimeLastChange %#RX32\n", RT_LE2H_U16(pInode->Extra.u32ExtraTimeLastChange)));
+ Log2(("EXT: u32ExtraTimeLastModification %#RX32\n", RT_LE2H_U16(pInode->Extra.u32ExtraTimeLastModification)));
+ Log2(("EXT: u32ExtraTimeLastAccess %#RX32\n", RT_LE2H_U16(pInode->Extra.u32ExtraTimeLastAccess)));
+ Log2(("EXT: u32TimeCreation %#RX32 %s\n", RT_LE2H_U32(pInode->Extra.u32TimeCreation),
+ RTTimeSpecToString(RTTimeSpecSetSeconds(&Spec, RT_LE2H_U32(pInode->Extra.u32TimeCreation)), sz, sizeof(sz))));
+ Log2(("EXT: u32ExtraTimeCreation %#RX32\n", RT_LE2H_U16(pInode->Extra.u32ExtraTimeCreation)));
+ Log2(("EXT: u32VersionHigh %#RX32\n", RT_LE2H_U16(pInode->Extra.u32VersionHigh)));
+ Log2(("EXT: u32ProjectId %#RX32\n", RT_LE2H_U16(pInode->Extra.u32ProjectId)));
+ }
+ }
+}
+
+
+/**
+ * Logs a ext filesystem directory entry.
+ *
+ * @param pThis The ext volume instance.
+ * @param idxDirEntry Directory entry index number.
+ * @param pDirEntry The directory entry.
+ */
+static void rtFsExtDirEntry_Log(PRTFSEXTVOL pThis, uint32_t idxDirEntry, PCEXTDIRENTRYEX pDirEntry)
+{
+ if (LogIs2Enabled())
+ {
+ int cbName = 0;
+
+ Log2(("EXT: Directory entry %#RX32:\n", idxDirEntry));
+ Log2(("EXT: iInodeRef %#RX32\n", RT_LE2H_U32(pDirEntry->Core.iInodeRef)));
+ Log2(("EXT: cbRecord %#RX32\n", RT_LE2H_U32(pDirEntry->Core.cbRecord)));
+ if (pThis->fFeaturesIncompat & EXT_SB_FEAT_INCOMPAT_DIR_FILETYPE)
+ {
+ Log2(("EXT: cbName %#RU8\n", pDirEntry->Core.u.v2.cbName));
+ Log2(("EXT: uType %#RX8\n", pDirEntry->Core.u.v2.uType));
+ cbName = pDirEntry->Core.u.v2.cbName;
+ }
+ else
+ {
+ Log2(("EXT: cbName %#RU16\n", RT_LE2H_U16(pDirEntry->Core.u.v1.cbName)));
+ cbName = RT_LE2H_U16(pDirEntry->Core.u.v1.cbName);
+ }
+ Log2(("EXT: achName %*s\n", cbName, &pDirEntry->Core.achName[0]));
+ }
+}
+
+
+/**
+ * Logs an extent header.
+ *
+ * @param pExtentHdr The extent header node.
+ */
+static void rtFsExtExtentHdr_Log(PCEXTEXTENTHDR pExtentHdr)
+{
+ if (LogIs2Enabled())
+ {
+ Log2(("EXT: Extent header:\n"));
+ Log2(("EXT: u16Magic %#RX16\n", RT_LE2H_U32(pExtentHdr->u16Magic)));
+ Log2(("EXT: cEntries %#RX16\n", RT_LE2H_U32(pExtentHdr->cEntries)));
+ Log2(("EXT: cMax %#RX16\n", RT_LE2H_U32(pExtentHdr->cMax)));
+ Log2(("EXT: uDepth %#RX16\n", RT_LE2H_U32(pExtentHdr->uDepth)));
+ Log2(("EXT: cGeneration %#RX32\n", RT_LE2H_U32(pExtentHdr->cGeneration)));
+ }
+}
+
+
+/**
+ * Logs an extent index node.
+ *
+ * @param pExtentIdx The extent index node.
+ */
+static void rtFsExtExtentIdx_Log(PCEXTEXTENTIDX pExtentIdx)
+{
+ if (LogIs2Enabled())
+ {
+ Log2(("EXT: Extent index node:\n"));
+ Log2(("EXT: iBlock %#RX32\n", RT_LE2H_U32(pExtentIdx->iBlock)));
+ Log2(("EXT: offChildLow %#RX32\n", RT_LE2H_U32(pExtentIdx->offChildLow)));
+ Log2(("EXT: offChildHigh %#RX16\n", RT_LE2H_U16(pExtentIdx->offChildHigh)));
+ }
+}
+
+
+/**
+ * Logs an extent.
+ *
+ * @param pExtent The extent.
+ */
+static void rtFsExtExtent_Log(PCEXTEXTENT pExtent)
+{
+ if (LogIs2Enabled())
+ {
+ Log2(("EXT: Extent:\n"));
+ Log2(("EXT: iBlock %#RX32\n", RT_LE2H_U32(pExtent->iBlock)));
+ Log2(("EXT: cBlocks %#RX16\n", RT_LE2H_U16(pExtent->cBlocks)));
+ Log2(("EXT: offStartHigh %#RX16\n", RT_LE2H_U32(pExtent->offStartHigh)));
+ Log2(("EXT: offStartLow %#RX16\n", RT_LE2H_U16(pExtent->offStartLow)));
+ }
+}
+
+#endif /* LOG_ENABLED */
+
+/**
+ * Converts a block number to a byte offset.
+ *
+ * @returns Offset in bytes for the given block number.
+ * @param pThis The ext volume instance.
+ * @param iBlock The block number to convert.
+ */
+DECLINLINE(uint64_t) rtFsExtBlockIdxToDiskOffset(PRTFSEXTVOL pThis, uint64_t iBlock)
+{
+ return iBlock << pThis->cBlockShift;
+}
+
+
+/**
+ * Converts a byte offset to a block number.
+ *
+ * @returns Block number.
+ * @param pThis The ext volume instance.
+ * @param iBlock The offset to convert.
+ */
+DECLINLINE(uint64_t) rtFsExtDiskOffsetToBlockIdx(PRTFSEXTVOL pThis, uint64_t off)
+{
+ return off >> pThis->cBlockShift;
+}
+
+
+/**
+ * Creates the proper block number from the given low and high parts in case a 64bit
+ * filesystem is used.
+ *
+ * @returns 64bit block number.
+ * @param pThis The ext volume instance.
+ * @param uLow The lower 32bit part.
+ * @param uHigh The upper 32bit part.
+ */
+DECLINLINE(uint64_t) rtFsExtBlockFromLowHigh(PRTFSEXTVOL pThis, uint32_t uLow, uint32_t uHigh)
+{
+ return pThis->f64Bit ? RT_MAKE_U64(uLow, uHigh): uLow;
+}
+
+
+/**
+ * Converts the given high and low parts of the block number to a byte offset.
+ *
+ * @returns Offset in bytes for the given block number.
+ * @param uLow The lower 32bit part of the block number.
+ * @param uHigh The upper 32bit part of the block number.
+ */
+DECLINLINE(uint64_t) rtFsExtBlockIdxLowHighToDiskOffset(PRTFSEXTVOL pThis, uint32_t uLow, uint32_t uHigh)
+{
+ uint64_t iBlock = rtFsExtBlockFromLowHigh(pThis, uLow, uHigh);
+ return rtFsExtBlockIdxToDiskOffset(pThis, iBlock);
+}
+
+
+/**
+ * Allocates a new block group.
+ *
+ * @returns Pointer to the new block group descriptor or NULL if out of memory.
+ * @param pThis The ext volume instance.
+ * @param cbAlloc How much to allocate.
+ * @param iBlockGroup Block group number.
+ */
+static PRTFSEXTBLOCKENTRY rtFsExtVol_BlockAlloc(PRTFSEXTVOL pThis, size_t cbAlloc, uint64_t iBlock)
+{
+ PRTFSEXTBLOCKENTRY pBlock = (PRTFSEXTBLOCKENTRY)RTMemAllocZ(cbAlloc);
+ if (RT_LIKELY(pBlock))
+ {
+ pBlock->Core.Key = iBlock;
+ pBlock->cRefs = 0;
+ pThis->cbBlocks += cbAlloc;
+ }
+
+ return pBlock;
+}
+
+
+/**
+ * Returns a new block entry utilizing the cache if possible.
+ *
+ * @returns Pointer to the new block entry or NULL if out of memory.
+ * @param pThis The ext volume instance.
+ * @param iBlock Block number.
+ */
+static PRTFSEXTBLOCKENTRY rtFsExtVol_BlockGetNew(PRTFSEXTVOL pThis, uint64_t iBlock)
+{
+ PRTFSEXTBLOCKENTRY pBlock = NULL;
+ size_t cbAlloc = RT_UOFFSETOF_DYN(RTFSEXTBLOCKENTRY, abData[pThis->cbBlock]);
+ if (pThis->cbBlocks + cbAlloc <= RTFSEXT_MAX_BLOCK_CACHE_SIZE)
+ pBlock = rtFsExtVol_BlockAlloc(pThis, cbAlloc, iBlock);
+ else
+ {
+ pBlock = RTListRemoveLast(&pThis->LstBlockLru, RTFSEXTBLOCKENTRY, NdLru);
+ if (!pBlock)
+ pBlock = rtFsExtVol_BlockAlloc(pThis, cbAlloc, iBlock);
+ else
+ {
+ /* Remove the block group from the tree because it gets a new key. */
+ PAVLU64NODECORE pCore = RTAvlU64Remove(&pThis->BlockRoot, pBlock->Core.Key);
+ Assert(pCore == &pBlock->Core); RT_NOREF(pCore);
+ }
+ }
+
+ Assert(!pBlock->cRefs);
+ pBlock->Core.Key = iBlock;
+ pBlock->cRefs = 1;
+
+ return pBlock;
+}
+
+
+/**
+ * Frees the given block.
+ *
+ * @param pThis The ext volume instance.
+ * @param pBlock The block to free.
+ */
+static void rtFsExtVol_BlockFree(PRTFSEXTVOL pThis, PRTFSEXTBLOCKENTRY pBlock)
+{
+ Assert(!pBlock->cRefs);
+
+ /*
+ * Put it into the cache if the limit wasn't exceeded, otherwise the block group
+ * is freed right away.
+ */
+ if (pThis->cbBlocks <= RTFSEXT_MAX_BLOCK_CACHE_SIZE)
+ {
+ /* Put onto the LRU list. */
+ RTListPrepend(&pThis->LstBlockLru, &pBlock->NdLru);
+ }
+ else
+ {
+ /* Remove from the tree and free memory. */
+ PAVLU64NODECORE pCore = RTAvlU64Remove(&pThis->BlockRoot, pBlock->Core.Key);
+ Assert(pCore == &pBlock->Core); RT_NOREF(pCore);
+ RTMemFree(pBlock);
+ pThis->cbBlocks -= RT_UOFFSETOF_DYN(RTFSEXTBLOCKENTRY, abData[pThis->cbBlock]);
+ }
+}
+
+
+/**
+ * Gets the specified block data from the volume.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param iBlock The filesystem block to load.
+ * @param ppBlock Where to return the pointer to the block entry on success.
+ * @param ppvData Where to return the pointer to the block data on success.
+ */
+static int rtFsExtVol_BlockLoad(PRTFSEXTVOL pThis, uint64_t iBlock, PRTFSEXTBLOCKENTRY *ppBlock, void **ppvData)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Try to fetch the block group from the cache first. */
+ PRTFSEXTBLOCKENTRY pBlock = (PRTFSEXTBLOCKENTRY)RTAvlU64Get(&pThis->BlockRoot, iBlock);
+ if (!pBlock)
+ {
+ /* Slow path, load from disk. */
+ pBlock = rtFsExtVol_BlockGetNew(pThis, iBlock);
+ if (RT_LIKELY(pBlock))
+ {
+ uint64_t offRead = rtFsExtBlockIdxToDiskOffset(pThis, iBlock);
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offRead, &pBlock->abData[0], pThis->cbBlock, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ bool fIns = RTAvlU64Insert(&pThis->BlockRoot, &pBlock->Core);
+ Assert(fIns); RT_NOREF(fIns);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ /* Remove from current LRU list position and add to the beginning. */
+ uint32_t cRefs = ASMAtomicIncU32(&pBlock->cRefs);
+ if (cRefs == 1) /* Blocks get removed from the LRU list if they are referenced. */
+ RTListNodeRemove(&pBlock->NdLru);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ *ppBlock = pBlock;
+ *ppvData = &pBlock->abData[0];
+ }
+ else if (pBlock)
+ {
+ ASMAtomicDecU32(&pBlock->cRefs);
+ rtFsExtVol_BlockFree(pThis, pBlock); /* Free the block. */
+ }
+
+ return rc;
+}
+
+
+/**
+ * Releases a reference of the given block.
+ *
+ * @param pThis The ext volume instance.
+ * @param pBlock The block to release.
+ */
+static void rtFsExtVol_BlockRelease(PRTFSEXTVOL pThis, PRTFSEXTBLOCKENTRY pBlock)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pBlock->cRefs);
+ if (!cRefs)
+ rtFsExtVol_BlockFree(pThis, pBlock);
+}
+
+
+/**
+ * Allocates a new block group.
+ *
+ * @returns Pointer to the new block group descriptor or NULL if out of memory.
+ * @param pThis The ext volume instance.
+ * @param cbAlloc How much to allocate.
+ * @param iBlockGroup Block group number.
+ */
+static PRTFSEXTBLKGRP rtFsExtBlockGroupAlloc(PRTFSEXTVOL pThis, size_t cbAlloc, uint32_t iBlockGroup)
+{
+ PRTFSEXTBLKGRP pBlockGroup = (PRTFSEXTBLKGRP)RTMemAllocZ(cbAlloc);
+ if (RT_LIKELY(pBlockGroup))
+ {
+ pBlockGroup->Core.Key = iBlockGroup;
+ pBlockGroup->cRefs = 0;
+ pBlockGroup->pabInodeBitmap = &pBlockGroup->abBlockBitmap[pThis->cbBlockBitmap];
+ pThis->cbBlockGroups += cbAlloc;
+ }
+
+ return pBlockGroup;
+}
+
+
+/**
+ * Frees the given block group.
+ *
+ * @param pThis The ext volume instance.
+ * @param pBlockGroup The block group to free.
+ */
+static void rtFsExtBlockGroupFree(PRTFSEXTVOL pThis, PRTFSEXTBLKGRP pBlockGroup)
+{
+ Assert(!pBlockGroup->cRefs);
+
+ /*
+ * Put it into the cache if the limit wasn't exceeded, otherwise the block group
+ * is freed right away.
+ */
+ if (pThis->cbBlockGroups <= RTFSEXT_MAX_BLOCK_GROUP_CACHE_SIZE)
+ {
+ /* Put onto the LRU list. */
+ RTListPrepend(&pThis->LstBlockGroupLru, &pBlockGroup->NdLru);
+ }
+ else
+ {
+ /* Remove from the tree and free memory. */
+ PAVLU32NODECORE pCore = RTAvlU32Remove(&pThis->BlockGroupRoot, pBlockGroup->Core.Key);
+ Assert(pCore == &pBlockGroup->Core); RT_NOREF(pCore);
+ RTMemFree(pBlockGroup);
+ pThis->cbBlockGroups -= sizeof(RTFSEXTBLKGRP) + pThis->cbBlockBitmap + pThis->cbInodeBitmap;
+ }
+}
+
+
+/**
+ * Returns a new block group utilizing the cache if possible.
+ *
+ * @returns Pointer to the new block group descriptor or NULL if out of memory.
+ * @param pThis The ext volume instance.
+ * @param iBlockGroup Block group number.
+ */
+static PRTFSEXTBLKGRP rtFsExtBlockGroupGetNew(PRTFSEXTVOL pThis, uint32_t iBlockGroup)
+{
+ PRTFSEXTBLKGRP pBlockGroup = NULL;
+ size_t cbAlloc = sizeof(RTFSEXTBLKGRP) + pThis->cbBlockBitmap + pThis->cbInodeBitmap;
+ if (pThis->cbBlockGroups + cbAlloc <= RTFSEXT_MAX_BLOCK_GROUP_CACHE_SIZE)
+ pBlockGroup = rtFsExtBlockGroupAlloc(pThis, cbAlloc, iBlockGroup);
+ else
+ {
+ pBlockGroup = RTListRemoveLast(&pThis->LstBlockGroupLru, RTFSEXTBLKGRP, NdLru);
+ if (!pBlockGroup)
+ pBlockGroup = rtFsExtBlockGroupAlloc(pThis, cbAlloc, iBlockGroup);
+ else
+ {
+ /* Remove the block group from the tree because it gets a new key. */
+ PAVLU32NODECORE pCore = RTAvlU32Remove(&pThis->BlockGroupRoot, pBlockGroup->Core.Key);
+ Assert(pCore == &pBlockGroup->Core); RT_NOREF(pCore);
+ }
+ }
+
+ Assert(!pBlockGroup->cRefs);
+ pBlockGroup->Core.Key = iBlockGroup;
+ pBlockGroup->cRefs = 1;
+
+ return pBlockGroup;
+}
+
+
+/**
+ * Loads the given block group number and returns it on success.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param iBlockGroup The block group to load.
+ * @param ppBlockGroup Where to store the block group on success.
+ */
+static int rtFsExtBlockGroupLoad(PRTFSEXTVOL pThis, uint32_t iBlockGroup, PRTFSEXTBLKGRP *ppBlockGroup)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Try to fetch the block group from the cache first. */
+ PRTFSEXTBLKGRP pBlockGroup = (PRTFSEXTBLKGRP)RTAvlU32Get(&pThis->BlockGroupRoot, iBlockGroup);
+ if (!pBlockGroup)
+ {
+ /* Slow path, load from disk. */
+ pBlockGroup = rtFsExtBlockGroupGetNew(pThis, iBlockGroup);
+ if (RT_LIKELY(pBlockGroup))
+ {
+ uint64_t offRead = rtFsExtBlockIdxToDiskOffset(pThis, pThis->cbBlock == _1K ? 2 : 1)
+ + (uint64_t)iBlockGroup * pThis->cbBlkGrpDesc;
+ EXTBLOCKGROUPDESC BlockGroupDesc;
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offRead, &BlockGroupDesc, pThis->cbBlkGrpDesc, NULL);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ rtFsExtBlockGroup_Log(pThis, iBlockGroup, &BlockGroupDesc);
+#endif
+ pBlockGroup->iBlockInodeTbl = RT_LE2H_U32(BlockGroupDesc.v32.offInodeTableLow)
+ | ((pThis->cbBlkGrpDesc == sizeof(EXTBLOCKGROUPDESC64))
+ ? (uint64_t)RT_LE2H_U32(BlockGroupDesc.v64.offInodeTableHigh) << 32
+ : 0);
+
+ offRead = rtFsExtBlockIdxLowHighToDiskOffset(pThis, RT_LE2H_U32(BlockGroupDesc.v32.offBlockBitmapLow),
+ RT_LE2H_U32(BlockGroupDesc.v64.offBlockBitmapHigh));
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offRead, &pBlockGroup->abBlockBitmap[0], pThis->cbBlockBitmap, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ offRead = rtFsExtBlockIdxLowHighToDiskOffset(pThis, RT_LE2H_U32(BlockGroupDesc.v32.offInodeBitmapLow),
+ RT_LE2H_U32(BlockGroupDesc.v64.offInodeBitmapHigh));
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offRead, &pBlockGroup->pabInodeBitmap[0], pThis->cbInodeBitmap, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ bool fIns = RTAvlU32Insert(&pThis->BlockGroupRoot, &pBlockGroup->Core);
+ Assert(fIns); RT_NOREF(fIns);
+ }
+ }
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ /* Remove from current LRU list position and add to the beginning. */
+ uint32_t cRefs = ASMAtomicIncU32(&pBlockGroup->cRefs);
+ if (cRefs == 1) /* Block groups get removed from the LRU list if they are referenced. */
+ RTListNodeRemove(&pBlockGroup->NdLru);
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppBlockGroup = pBlockGroup;
+ else if (pBlockGroup)
+ {
+ ASMAtomicDecU32(&pBlockGroup->cRefs);
+ rtFsExtBlockGroupFree(pThis, pBlockGroup); /* Free the block group. */
+ }
+
+ return rc;
+}
+
+
+/**
+ * Releases a reference of the given block group.
+ *
+ * @param pThis The ext volume instance.
+ * @param pBlockGroup The block group to release.
+ */
+static void rtFsExtBlockGroupRelease(PRTFSEXTVOL pThis, PRTFSEXTBLKGRP pBlockGroup)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pBlockGroup->cRefs);
+ if (!cRefs)
+ rtFsExtBlockGroupFree(pThis, pBlockGroup);
+}
+
+
+/**
+ * Allocates a new inode.
+ *
+ * @returns Pointer to the new inode or NULL if out of memory.
+ * @param pThis The ext volume instance.
+ * @param iInode Inode number.
+ */
+static PRTFSEXTINODE rtFsExtInodeAlloc(PRTFSEXTVOL pThis, uint32_t iInode)
+{
+ PRTFSEXTINODE pInode = (PRTFSEXTINODE)RTMemAllocZ(sizeof(RTFSEXTINODE));
+ if (RT_LIKELY(pInode))
+ {
+ pInode->Core.Key = iInode;
+ pInode->cRefs = 0;
+ pThis->cbInodes += sizeof(RTFSEXTINODE);
+ }
+
+ return pInode;
+}
+
+
+/**
+ * Frees the given inode.
+ *
+ * @param pThis The ext volume instance.
+ * @param pInode The inode to free.
+ */
+static void rtFsExtInodeFree(PRTFSEXTVOL pThis, PRTFSEXTINODE pInode)
+{
+ Assert(!pInode->cRefs);
+
+ /*
+ * Put it into the cache if the limit wasn't exceeded, otherwise the inode
+ * is freed right away.
+ */
+ if (pThis->cbInodes <= RTFSEXT_MAX_INODE_CACHE_SIZE)
+ {
+ /* Put onto the LRU list. */
+ RTListPrepend(&pThis->LstInodeLru, &pInode->NdLru);
+ }
+ else
+ {
+ /* Remove from the tree and free memory. */
+ PAVLU32NODECORE pCore = RTAvlU32Remove(&pThis->InodeRoot, pInode->Core.Key);
+ Assert(pCore == &pInode->Core); RT_NOREF(pCore);
+ RTMemFree(pInode);
+ pThis->cbInodes -= sizeof(RTFSEXTINODE);
+ }
+}
+
+
+/**
+ * Returns a new inodep utilizing the cache if possible.
+ *
+ * @returns Pointer to the new inode or NULL if out of memory.
+ * @param pThis The ext volume instance.
+ * @param iInode Inode number.
+ */
+static PRTFSEXTINODE rtFsExtInodeGetNew(PRTFSEXTVOL pThis, uint32_t iInode)
+{
+ PRTFSEXTINODE pInode = NULL;
+ if (pThis->cbInodes + sizeof(RTFSEXTINODE) <= RTFSEXT_MAX_INODE_CACHE_SIZE)
+ pInode = rtFsExtInodeAlloc(pThis, iInode);
+ else
+ {
+ pInode = RTListRemoveLast(&pThis->LstInodeLru, RTFSEXTINODE, NdLru);
+ if (!pInode)
+ pInode = rtFsExtInodeAlloc(pThis, iInode);
+ else
+ {
+ /* Remove the block group from the tree because it gets a new key. */
+ PAVLU32NODECORE pCore = RTAvlU32Remove(&pThis->InodeRoot, pInode->Core.Key);
+ Assert(pCore == &pInode->Core); RT_NOREF(pCore);
+ }
+ }
+
+ Assert(!pInode->cRefs);
+ pInode->Core.Key = iInode;
+ pInode->cRefs = 1;
+
+ return pInode;
+}
+
+
+/**
+ * Loads the given inode number and returns it on success.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param iInode The inode to load.
+ * @param ppInode Where to store the inode on success.
+ */
+static int rtFsExtInodeLoad(PRTFSEXTVOL pThis, uint32_t iInode, PRTFSEXTINODE *ppInode)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Try to fetch the inode from the cache first. */
+ PRTFSEXTINODE pInode = (PRTFSEXTINODE)RTAvlU32Get(&pThis->InodeRoot, iInode);
+ if (!pInode)
+ {
+ /* Slow path, load from disk. */
+ pInode = rtFsExtInodeGetNew(pThis, iInode);
+ if (RT_LIKELY(pInode))
+ {
+ /* Calculate the block group and load that one first to get at the inode table location. */
+ PRTFSEXTBLKGRP pBlockGroup = NULL;
+ rc = rtFsExtBlockGroupLoad(pThis, (iInode - 1) / pThis->cInodesPerGroup, &pBlockGroup);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t idxInodeInTbl = (iInode - 1) % pThis->cInodesPerGroup;
+ uint64_t offRead = rtFsExtBlockIdxToDiskOffset(pThis, pBlockGroup->iBlockInodeTbl)
+ + idxInodeInTbl * pThis->cbInode;
+
+ /* Release block group here already as it is not required. */
+ rtFsExtBlockGroupRelease(pThis, pBlockGroup);
+
+ EXTINODECOMB Inode;
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offRead, &Inode, RT_MIN(sizeof(Inode), pThis->cbInode), NULL);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ rtFsExtInode_Log(pThis, iInode, &Inode);
+#endif
+ pInode->offInode = offRead;
+ pInode->fFlags = RT_LE2H_U32(Inode.Core.fFlags);
+ pInode->ObjInfo.cbObject = (uint64_t)RT_LE2H_U32(Inode.Core.cbSizeHigh) << 32
+ | (uint64_t)RT_LE2H_U32(Inode.Core.cbSizeLow);
+ pInode->ObjInfo.cbAllocated = ( (uint64_t)RT_LE2H_U16(Inode.Core.Osd2.Lnx.cBlocksHigh) << 32
+ | (uint64_t)RT_LE2H_U32(Inode.Core.cBlocksLow)) * pThis->cbBlock;
+ RTTimeSpecSetSeconds(&pInode->ObjInfo.AccessTime, RT_LE2H_U32(Inode.Core.u32TimeLastAccess));
+ RTTimeSpecSetSeconds(&pInode->ObjInfo.ModificationTime, RT_LE2H_U32(Inode.Core.u32TimeLastModification));
+ RTTimeSpecSetSeconds(&pInode->ObjInfo.ChangeTime, RT_LE2H_U32(Inode.Core.u32TimeLastChange));
+ pInode->ObjInfo.Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
+ pInode->ObjInfo.Attr.u.Unix.uid = (uint32_t)RT_LE2H_U16(Inode.Core.Osd2.Lnx.uUidHigh) << 16
+ | (uint32_t)RT_LE2H_U16(Inode.Core.uUidLow);
+ pInode->ObjInfo.Attr.u.Unix.gid = (uint32_t)RT_LE2H_U16(Inode.Core.Osd2.Lnx.uGidHigh) << 16
+ | (uint32_t)RT_LE2H_U16(Inode.Core.uGidLow);
+ pInode->ObjInfo.Attr.u.Unix.cHardlinks = RT_LE2H_U16(Inode.Core.cHardLinks);
+ pInode->ObjInfo.Attr.u.Unix.INodeIdDevice = 0;
+ pInode->ObjInfo.Attr.u.Unix.INodeId = iInode;
+ pInode->ObjInfo.Attr.u.Unix.fFlags = 0;
+ pInode->ObjInfo.Attr.u.Unix.GenerationId = RT_LE2H_U32(Inode.Core.u32Version);
+ pInode->ObjInfo.Attr.u.Unix.Device = 0;
+ if (pThis->cbInode >= sizeof(EXTINODECOMB))
+ RTTimeSpecSetSeconds(&pInode->ObjInfo.BirthTime, RT_LE2H_U32(Inode.Extra.u32TimeCreation));
+ else
+ RTTimeSpecSetSeconds(&pInode->ObjInfo.BirthTime, RT_LE2H_U32(Inode.Core.u32TimeLastChange));
+ for (unsigned i = 0; i < RT_ELEMENTS(pInode->aiBlocks); i++)
+ pInode->aiBlocks[i] = RT_LE2H_U32(Inode.Core.au32Block[i]);
+
+ /* Fill in the mode. */
+ pInode->ObjInfo.Attr.fMode = 0;
+ uint32_t fInodeMode = RT_LE2H_U32(Inode.Core.fMode);
+ switch (EXT_INODE_MODE_TYPE_GET_TYPE(fInodeMode))
+ {
+ case EXT_INODE_MODE_TYPE_FIFO:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_FIFO;
+ break;
+ case EXT_INODE_MODE_TYPE_CHAR:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_DEV_CHAR;
+ break;
+ case EXT_INODE_MODE_TYPE_DIR:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_DIRECTORY;
+ break;
+ case EXT_INODE_MODE_TYPE_BLOCK:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_DEV_BLOCK;
+ break;
+ case EXT_INODE_MODE_TYPE_REGULAR:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_FILE;
+ break;
+ case EXT_INODE_MODE_TYPE_SYMLINK:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_SYMLINK;
+ break;
+ case EXT_INODE_MODE_TYPE_SOCKET:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_SOCKET;
+ break;
+ default:
+ rc = VERR_VFS_BOGUS_FORMAT;
+ }
+ if (fInodeMode & EXT_INODE_MODE_EXEC_OTHER)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IXOTH;
+ if (fInodeMode & EXT_INODE_MODE_WRITE_OTHER)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IWOTH;
+ if (fInodeMode & EXT_INODE_MODE_READ_OTHER)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IROTH;
+ if (fInodeMode & EXT_INODE_MODE_EXEC_GROUP)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IXGRP;
+ if (fInodeMode & EXT_INODE_MODE_WRITE_GROUP)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IWGRP;
+ if (fInodeMode & EXT_INODE_MODE_READ_GROUP)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IRGRP;
+ if (fInodeMode & EXT_INODE_MODE_EXEC_OWNER)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IXUSR;
+ if (fInodeMode & EXT_INODE_MODE_WRITE_OWNER)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IWUSR;
+ if (fInodeMode & EXT_INODE_MODE_READ_OWNER)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IRUSR;
+ if (fInodeMode & EXT_INODE_MODE_STICKY)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_ISTXT;
+ if (fInodeMode & EXT_INODE_MODE_SET_GROUP_ID)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_ISGID;
+ if (fInodeMode & EXT_INODE_MODE_SET_USER_ID)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_ISUID;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ bool fIns = RTAvlU32Insert(&pThis->InodeRoot, &pInode->Core);
+ Assert(fIns); RT_NOREF(fIns);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ /* Remove from current LRU list position and add to the beginning. */
+ uint32_t cRefs = ASMAtomicIncU32(&pInode->cRefs);
+ if (cRefs == 1) /* Inodes get removed from the LRU list if they are referenced. */
+ RTListNodeRemove(&pInode->NdLru);
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppInode = pInode;
+ else if (pInode)
+ {
+ ASMAtomicDecU32(&pInode->cRefs);
+ rtFsExtInodeFree(pThis, pInode); /* Free the inode. */
+ }
+
+ return rc;
+}
+
+
+/**
+ * Releases a reference of the given inode.
+ *
+ * @param pThis The ext volume instance.
+ * @param pInode The inode to release.
+ */
+static void rtFsExtInodeRelease(PRTFSEXTVOL pThis, PRTFSEXTINODE pInode)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pInode->cRefs);
+ if (!cRefs)
+ rtFsExtInodeFree(pThis, pInode);
+}
+
+
+/**
+ * Worker for various QueryInfo methods.
+ *
+ * @returns IPRT status code.
+ * @param pInode The inode structure to return info for.
+ * @param pObjInfo Where to return object info.
+ * @param enmAddAttr What additional info to return.
+ */
+static int rtFsExtInode_QueryInfo(PRTFSEXTINODE pInode, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ RT_ZERO(*pObjInfo);
+
+ pObjInfo->cbObject = pInode->ObjInfo.cbObject;
+ pObjInfo->cbAllocated = pInode->ObjInfo.cbAllocated;
+ pObjInfo->AccessTime = pInode->ObjInfo.AccessTime;
+ pObjInfo->ModificationTime = pInode->ObjInfo.ModificationTime;
+ pObjInfo->ChangeTime = pInode->ObjInfo.ChangeTime;
+ pObjInfo->BirthTime = pInode->ObjInfo.BirthTime;
+ pObjInfo->Attr.fMode = pInode->ObjInfo.Attr.fMode;
+ pObjInfo->Attr.enmAdditional = enmAddAttr;
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_UNIX:
+ memcpy(&pObjInfo->Attr.u.Unix, &pInode->ObjInfo.Attr.u.Unix, sizeof(pInode->ObjInfo.Attr.u.Unix));
+ break;
+
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ pObjInfo->Attr.u.UnixOwner.uid = pInode->ObjInfo.Attr.u.Unix.uid;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ pObjInfo->Attr.u.UnixGroup.gid = pInode->ObjInfo.Attr.u.Unix.gid;
+ break;
+
+ default:
+ break;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Validates a given extent header.
+ *
+ * @returns Flag whether the extent header appears to be valid.
+ * @param pExtentHdr The extent header to validate.
+ */
+DECLINLINE(bool) rtFsExtInode_ExtentHdrValidate(PCEXTEXTENTHDR pExtentHdr)
+{
+ return RT_LE2H_U16(pExtentHdr->u16Magic) == EXT_EXTENT_HDR_MAGIC
+ && RT_LE2H_U16(pExtentHdr->cEntries) <= RT_LE2H_U16(pExtentHdr->cMax)
+ && RT_LE2H_U16(pExtentHdr->uDepth) <= EXT_EXTENT_HDR_DEPTH_MAX;
+}
+
+
+/**
+ * Parses the given extent, checking whether it intersects with the given block.
+ *
+ * @returns Flag whether the extent maps the given range (at least partly).
+ * @param pExtent The extent to parse.
+ * @param iBlock The starting inode block to map.
+ * @param cBlocks Number of blocks requested.
+ * @param piBlockFs Where to store the filesystem block on success.
+ * @param pcBlocks Where to store the number of contiguous blocks on success.
+ * @param pfSparse Where to store the sparse flag on success.
+ */
+DECLINLINE(bool) rtFsExtInode_ExtentParse(PCEXTEXTENT pExtent, uint64_t iBlock, size_t cBlocks,
+ uint64_t *piBlockFs, size_t *pcBlocks, bool *pfSparse)
+{
+#ifdef LOG_ENABLED
+ rtFsExtExtent_Log(pExtent);
+#endif
+
+ uint32_t iExtentBlock = RT_LE2H_U32(pExtent->iBlock);
+ uint16_t cExtentLength = RT_LE2H_U16(pExtent->cBlocks);
+
+ /* Length over EXT_EXTENT_LENGTH_LIMIT blocks indicate a sparse extent. */
+ if (cExtentLength > EXT_EXTENT_LENGTH_LIMIT)
+ {
+ *pfSparse = true;
+ cExtentLength -= EXT_EXTENT_LENGTH_LIMIT;
+ }
+ else
+ *pfSparse = false;
+
+ if ( iExtentBlock <= iBlock
+ && iExtentBlock + cExtentLength > iBlock)
+ {
+ uint32_t iBlockRel = iBlock - iExtentBlock;
+ *pcBlocks = RT_MIN(cBlocks, cExtentLength - iBlockRel);
+ *piBlockFs = ( ((uint64_t)RT_LE2H_U16(pExtent->offStartHigh)) << 32
+ | ((uint64_t)RT_LE2H_U32(pExtent->offStartLow))) + iBlockRel;
+ return true;
+ }
+
+ return false;
+}
+
+
+/**
+ * Locates the location of the next level in the extent tree mapping the given block.
+ *
+ * @returns Filesystem block number where the next level of the extent is stored.
+ * @param paExtentIdx Pointer to the array of extent index nodes.
+ * @param cEntries Number of entries in the extent index node array.
+ * @param iBlock The block to resolve.
+ */
+DECLINLINE(uint64_t) rtFsExtInode_ExtentIndexLocateNextLvl(PCEXTEXTENTIDX paExtentIdx, uint16_t cEntries, uint64_t iBlock)
+{
+ for (uint32_t i = 1; i < cEntries; i++)
+ {
+ PCEXTEXTENTIDX pPrev = &paExtentIdx[i - 1];
+ PCEXTEXTENTIDX pCur = &paExtentIdx[i];
+
+#ifdef LOG_ENABLED
+ rtFsExtExtentIdx_Log(pPrev);
+#endif
+
+ if ( RT_LE2H_U32(pPrev->iBlock) <= iBlock
+ && RT_LE2H_U32(pCur->iBlock) > iBlock)
+ return (uint64_t)RT_LE2H_U16(pPrev->offChildHigh) << 32
+ | (uint64_t)RT_LE2H_U32(pPrev->offChildLow);
+ }
+
+ /* Nothing found so far, the blast extent index must cover the block as the array is sorted. */
+ PCEXTEXTENTIDX pLast = &paExtentIdx[cEntries - 1];
+#ifdef LOG_ENABLED
+ rtFsExtExtentIdx_Log(pLast);
+#endif
+
+ return (uint64_t)RT_LE2H_U16(pLast->offChildHigh) << 32
+ | (uint64_t)RT_LE2H_U32(pLast->offChildLow);
+}
+
+
+/**
+ * Maps the given inode block to the destination filesystem block using the embedded extent tree.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param pInode The inode structure to read from.
+ * @param iBlock The starting inode block to map.
+ * @param cBlocks Number of blocks requested.
+ * @param piBlockFs Where to store the filesystem block on success.
+ * @param pcBlocks Where to store the number of contiguous blocks on success.
+ * @param pfSparse Where to store the sparse flag on success.
+ *
+ * @todo Optimize
+ */
+static int rtFsExtInode_MapBlockToFsViaExtent(PRTFSEXTVOL pThis, PRTFSEXTINODE pInode, uint64_t iBlock, size_t cBlocks,
+ uint64_t *piBlockFs, size_t *pcBlocks, bool *pfSparse)
+{
+ int rc = VINF_SUCCESS;
+
+ /* The root of the extent tree is located in the block data of the inode. */
+ PCEXTEXTENTHDR pExtentHdr = (PCEXTEXTENTHDR)&pInode->aiBlocks[0];
+
+#ifdef LOG_ENABLED
+ rtFsExtExtentHdr_Log(pExtentHdr);
+#endif
+
+ /*
+ * Some validation, the top level is located inside the inode block data
+ * and has a maxmimum of 4 entries.
+ */
+ if ( rtFsExtInode_ExtentHdrValidate(pExtentHdr)
+ && RT_LE2H_U16(pExtentHdr->cMax) <= 4)
+ {
+ uint16_t uDepthCur = RT_LE2H_U16(pExtentHdr->uDepth);
+ if (!uDepthCur)
+ {
+ PCEXTEXTENT pExtent = (PCEXTEXTENT)(pExtentHdr + 1);
+
+ rc = VERR_VFS_BOGUS_FORMAT;
+ for (uint32_t i = 0; i < RT_LE2H_U16(pExtentHdr->cEntries); i++)
+ {
+ /* Check whether the extent intersects with the block. */
+ if (rtFsExtInode_ExtentParse(pExtent, iBlock, cBlocks, piBlockFs, pcBlocks, pfSparse))
+ {
+ rc = VINF_SUCCESS;
+ break;
+ }
+ pExtent++;
+ }
+ }
+ else
+ {
+ uint8_t *pbExtent = NULL;
+ PRTFSEXTBLOCKENTRY pBlock = NULL;
+ uint64_t iBlockNext = 0;
+ PCEXTEXTENTIDX paExtentIdx = (PCEXTEXTENTIDX)(pExtentHdr + 1);
+ uint16_t cEntries = RT_LE2H_U16(pExtentHdr->cEntries);
+
+ /* Descend the tree until we reached the leaf nodes. */
+ do
+ {
+ iBlockNext = rtFsExtInode_ExtentIndexLocateNextLvl(paExtentIdx, cEntries, iBlock);
+ /* Read in the full block. */
+ rc = rtFsExtVol_BlockLoad(pThis, iBlockNext, &pBlock, (void **)&pbExtent);
+ if (RT_SUCCESS(rc))
+ {
+ pExtentHdr = (PCEXTEXTENTHDR)pbExtent;
+
+#ifdef LOG_ENABLED
+ rtFsExtExtentHdr_Log(pExtentHdr);
+#endif
+
+ if ( rtFsExtInode_ExtentHdrValidate(pExtentHdr)
+ && RT_LE2H_U16(pExtentHdr->cMax) <= (pThis->cbBlock - sizeof(EXTEXTENTHDR)) / sizeof(EXTEXTENTIDX)
+ && RT_LE2H_U16(pExtentHdr->uDepth) == uDepthCur - 1)
+ {
+ uDepthCur--;
+ cEntries = RT_LE2H_U16(pExtentHdr->cEntries);
+ paExtentIdx = (PCEXTEXTENTIDX)(pExtentHdr + 1);
+ if (uDepthCur)
+ rtFsExtVol_BlockRelease(pThis, pBlock);
+ }
+ else
+ rc = VERR_VFS_BOGUS_FORMAT;
+ }
+ }
+ while ( uDepthCur > 0
+ && RT_SUCCESS(rc));
+
+ if (RT_SUCCESS(rc))
+ {
+ Assert(!uDepthCur);
+
+ /* We reached the leaf nodes. */
+ PCEXTEXTENT pExtent = (PCEXTEXTENT)(pExtentHdr + 1);
+ for (uint32_t i = 0; i < RT_LE2H_U16(pExtentHdr->cEntries); i++)
+ {
+ /* Check whether the extent intersects with the block. */
+ if (rtFsExtInode_ExtentParse(pExtent, iBlock, cBlocks, piBlockFs, pcBlocks, pfSparse))
+ {
+ rc = VINF_SUCCESS;
+ break;
+ }
+ pExtent++;
+ }
+ }
+
+ if (pBlock)
+ rtFsExtVol_BlockRelease(pThis, pBlock);
+ }
+ }
+ else
+ rc = VERR_VFS_BOGUS_FORMAT;
+
+ return rc;
+}
+
+
+/**
+ * Maps the given inode block to the destination filesystem block using the original block mapping scheme.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param pInode The inode structure to read from.
+ * @param iBlock The inode block to map.
+ * @param cBlocks Number of blocks requested.
+ * @param piBlockFs Where to store the filesystem block on success.
+ * @param pcBlocks Where to store the number of contiguous blocks on success.
+ * @param pfSparse Where to store the sparse flag on success.
+ *
+ * @todo Optimize and handle sparse files.
+ */
+static int rtFsExtInode_MapBlockToFsViaBlockMap(PRTFSEXTVOL pThis, PRTFSEXTINODE pInode, uint64_t iBlock, size_t cBlocks,
+ uint64_t *piBlockFs, size_t *pcBlocks, bool *pfSparse)
+{
+ int rc = VINF_SUCCESS;
+ RT_NOREF(cBlocks);
+
+ *pfSparse = false;
+ *pcBlocks = 1;
+
+ /* The first 12 inode blocks are directly mapped from the inode. */
+ if (iBlock <= 11)
+ *piBlockFs = pInode->aiBlocks[iBlock];
+ else
+ {
+ uint32_t cEntriesPerBlockMap = (uint32_t)(pThis->cbBlock >> sizeof(uint32_t));
+
+ if (iBlock <= cEntriesPerBlockMap + 11)
+ {
+ /* Indirect block. */
+ PRTFSEXTBLOCKENTRY pBlock = NULL;
+ uint32_t *paBlockMap = NULL;
+ rc = rtFsExtVol_BlockLoad(pThis, pInode->aiBlocks[12], &pBlock, (void **)&paBlockMap);
+ if (RT_SUCCESS(rc))
+ {
+ *piBlockFs = RT_LE2H_U32(paBlockMap[iBlock - 12]);
+ rtFsExtVol_BlockRelease(pThis, pBlock);
+ }
+ }
+ else if (iBlock <= cEntriesPerBlockMap * cEntriesPerBlockMap + cEntriesPerBlockMap + 11)
+ {
+ /* Double indirect block. */
+ PRTFSEXTBLOCKENTRY pBlock = NULL;
+ uint32_t *paBlockMap = NULL;
+
+ iBlock -= 12 + cEntriesPerBlockMap;
+ rc = rtFsExtVol_BlockLoad(pThis, pInode->aiBlocks[13], &pBlock, (void **)&paBlockMap);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t idxBlockL2 = iBlock / cEntriesPerBlockMap;
+ uint32_t idxBlockL1 = iBlock % cEntriesPerBlockMap;
+ uint32_t iBlockNext = RT_LE2H_U32(paBlockMap[idxBlockL2]);
+
+ rtFsExtVol_BlockRelease(pThis, pBlock);
+ rc = rtFsExtVol_BlockLoad(pThis, iBlockNext, &pBlock, (void **)&paBlockMap);
+ if (RT_SUCCESS(rc))
+ {
+ *piBlockFs = RT_LE2H_U32(paBlockMap[idxBlockL1]);
+ rtFsExtVol_BlockRelease(pThis, pBlock);
+ }
+ }
+ }
+ else
+ {
+ /* Triple indirect block. */
+ PRTFSEXTBLOCKENTRY pBlock = NULL;
+ uint32_t *paBlockMap = NULL;
+
+ iBlock -= 12 + cEntriesPerBlockMap * cEntriesPerBlockMap + cEntriesPerBlockMap;
+ rc = rtFsExtVol_BlockLoad(pThis, pInode->aiBlocks[14], &pBlock, (void **)&paBlockMap);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t idxBlockL3 = iBlock / (cEntriesPerBlockMap * cEntriesPerBlockMap);
+ uint32_t iBlockNext = RT_LE2H_U32(paBlockMap[idxBlockL3]);
+
+ rtFsExtVol_BlockRelease(pThis, pBlock);
+ rc = rtFsExtVol_BlockLoad(pThis, iBlockNext, &pBlock, (void **)&paBlockMap);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t idxBlockL2 = (iBlock % (cEntriesPerBlockMap * cEntriesPerBlockMap)) / cEntriesPerBlockMap;
+ uint32_t idxBlockL1 = iBlock % cEntriesPerBlockMap;
+ iBlockNext = RT_LE2H_U32(paBlockMap[idxBlockL2]);
+
+ rtFsExtVol_BlockRelease(pThis, pBlock);
+ rc = rtFsExtVol_BlockLoad(pThis, iBlockNext, &pBlock, (void **)&paBlockMap);
+ if (RT_SUCCESS(rc))
+ {
+ *piBlockFs = RT_LE2H_U32(paBlockMap[idxBlockL1]);
+ rtFsExtVol_BlockRelease(pThis, pBlock);
+ }
+ }
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Maps the given inode block to the destination filesystem block.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param pInode The inode structure to read from.
+ * @param iBlock The inode block to map.
+ * @param cBlocks Number of blocks requested.
+ * @param piBlockFs Where to store the filesystem block on success.
+ * @param pcBlocks Where to store the number of contiguous blocks on success.
+ * @param pfSparse Where to store the sparse flag on success.
+ *
+ * @todo Optimize
+ */
+static int rtFsExtInode_MapBlockToFs(PRTFSEXTVOL pThis, PRTFSEXTINODE pInode, uint64_t iBlock, size_t cBlocks,
+ uint64_t *piBlockFs, size_t *pcBlocks, bool *pfSparse)
+{
+ if (pInode->fFlags & EXT_INODE_F_EXTENTS)
+ return rtFsExtInode_MapBlockToFsViaExtent(pThis, pInode, iBlock, cBlocks, piBlockFs, pcBlocks, pfSparse);
+ else
+ return rtFsExtInode_MapBlockToFsViaBlockMap(pThis, pInode, iBlock, cBlocks, piBlockFs, pcBlocks, pfSparse);
+}
+
+
+/**
+ * Reads data from the given inode at the given byte offset.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param pInode The inode structure to read from.
+ * @param off The byte offset to start reading from.
+ * @param pvBuf Where to store the read data to.
+ * @param pcbRead Where to return the amount of data read.
+ */
+static int rtFsExtInode_Read(PRTFSEXTVOL pThis, PRTFSEXTINODE pInode, uint64_t off, void *pvBuf, size_t cbRead, size_t *pcbRead)
+{
+ int rc = VINF_SUCCESS;
+ uint8_t *pbBuf = (uint8_t *)pvBuf;
+
+ if (((uint64_t)pInode->ObjInfo.cbObject < off + cbRead))
+ {
+ if (!pcbRead)
+ return VERR_EOF;
+ else
+ cbRead = (uint64_t)pInode->ObjInfo.cbObject - off;
+ }
+
+ while ( cbRead
+ && RT_SUCCESS(rc))
+ {
+ uint64_t iBlockStart = rtFsExtDiskOffsetToBlockIdx(pThis, off);
+ uint32_t offBlockStart = off % pThis->cbBlock;
+
+ /* Resolve the inode block to the proper filesystem block. */
+ uint64_t iBlockFs = 0;
+ size_t cBlocks = 0;
+ bool fSparse = false;
+ rc = rtFsExtInode_MapBlockToFs(pThis, pInode, iBlockStart, 1, &iBlockFs, &cBlocks, &fSparse);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cBlocks == 1);
+
+ size_t cbThisRead = RT_MIN(cbRead, pThis->cbBlock - offBlockStart);
+
+ if (!fSparse)
+ {
+ uint64_t offRead = rtFsExtBlockIdxToDiskOffset(pThis, iBlockFs);
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offRead + offBlockStart, pbBuf, cbThisRead, NULL);
+ }
+ else
+ memset(pbBuf, 0, cbThisRead);
+
+ if (RT_SUCCESS(rc))
+ {
+ pbBuf += cbThisRead;
+ cbRead -= cbThisRead;
+ off += cbThisRead;
+ if (pcbRead)
+ *pcbRead += cbThisRead;
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+
+/*
+ *
+ * File operations.
+ * File operations.
+ * File operations.
+ *
+ */
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsExtFile_Close(void *pvThis)
+{
+ PRTFSEXTFILE pThis = (PRTFSEXTFILE)pvThis;
+ LogFlow(("rtFsExtFile_Close(%p/%p)\n", pThis, pThis->pInode));
+
+ rtFsExtInodeRelease(pThis->pVol, pThis->pInode);
+ pThis->pInode = NULL;
+ pThis->pVol = NULL;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsExtFile_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSEXTFILE pThis = (PRTFSEXTFILE)pvThis;
+ return rtFsExtInode_QueryInfo(pThis->pInode, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtFsExtFile_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTFSEXTFILE pThis = (PRTFSEXTFILE)pvThis;
+ AssertReturn(pSgBuf->cSegs == 1, VERR_INTERNAL_ERROR_3);
+ RT_NOREF(fBlocking);
+
+ if (off == -1)
+ off = pThis->offFile;
+ else
+ AssertReturn(off >= 0, VERR_INTERNAL_ERROR_3);
+
+ int rc;
+ size_t cbRead = pSgBuf->paSegs[0].cbSeg;
+ if (!pcbRead)
+ {
+ rc = rtFsExtInode_Read(pThis->pVol, pThis->pInode, (uint64_t)off, pSgBuf->paSegs[0].pvSeg, cbRead, NULL);
+ if (RT_SUCCESS(rc))
+ pThis->offFile = off + cbRead;
+ Log6(("rtFsExtFile_Read: off=%#RX64 cbSeg=%#x -> %Rrc\n", off, pSgBuf->paSegs[0].cbSeg, rc));
+ }
+ else
+ {
+ PRTFSEXTINODE pInode = pThis->pInode;
+ if (off >= pInode->ObjInfo.cbObject)
+ {
+ *pcbRead = 0;
+ rc = VINF_EOF;
+ }
+ else
+ {
+ if ((uint64_t)off + cbRead <= (uint64_t)pInode->ObjInfo.cbObject)
+ rc = rtFsExtInode_Read(pThis->pVol, pThis->pInode, (uint64_t)off, pSgBuf->paSegs[0].pvSeg, cbRead, NULL);
+ else
+ {
+ /* Return VINF_EOF if beyond end-of-file. */
+ cbRead = (size_t)(pInode->ObjInfo.cbObject - off);
+ rc = rtFsExtInode_Read(pThis->pVol, pThis->pInode, off, pSgBuf->paSegs[0].pvSeg, cbRead, NULL);
+ if (RT_SUCCESS(rc))
+ rc = VINF_EOF;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pThis->offFile = off + cbRead;
+ *pcbRead = cbRead;
+ }
+ else
+ *pcbRead = 0;
+ }
+ Log6(("rtFsExtFile_Read: off=%#RX64 cbSeg=%#x -> %Rrc *pcbRead=%#x\n", off, pSgBuf->paSegs[0].cbSeg, rc, *pcbRead));
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtFsExtFile_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ RT_NOREF(pvThis, off, pSgBuf, fBlocking, pcbWritten);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtFsExtFile_Flush(void *pvThis)
+{
+ RT_NOREF(pvThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtFsExtFile_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTFSEXTFILE pThis = (PRTFSEXTFILE)pvThis;
+ *poffActual = pThis->offFile;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtFsExtFile_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ RT_NOREF(pvThis, fMode, fMask);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtFsExtFile_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ RT_NOREF(pvThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtFsExtFile_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ RT_NOREF(pvThis, uid, gid);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSeek}
+ */
+static DECLCALLBACK(int) rtFsExtFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual)
+{
+ PRTFSEXTFILE pThis = (PRTFSEXTFILE)pvThis;
+ RTFOFF offNew;
+ switch (uMethod)
+ {
+ case RTFILE_SEEK_BEGIN:
+ offNew = offSeek;
+ break;
+ case RTFILE_SEEK_END:
+ offNew = pThis->pInode->ObjInfo.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) rtFsExtFile_QuerySize(void *pvThis, uint64_t *pcbFile)
+{
+ PRTFSEXTFILE pThis = (PRTFSEXTFILE)pvThis;
+ *pcbFile = (uint64_t)pThis->pInode->ObjInfo.cbObject;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSetSize}
+ */
+static DECLCALLBACK(int) rtFsExtFile_SetSize(void *pvThis, uint64_t cbFile, uint32_t fFlags)
+{
+ RT_NOREF(pvThis, cbFile, fFlags);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQueryMaxSize}
+ */
+static DECLCALLBACK(int) rtFsExtFile_QueryMaxSize(void *pvThis, uint64_t *pcbMax)
+{
+ RT_NOREF(pvThis);
+ *pcbMax = INT64_MAX; /** @todo */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * EXT file operations.
+ */
+static const RTVFSFILEOPS g_rtFsExtFileOps =
+{
+ { /* Stream */
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FILE,
+ "EXT File",
+ rtFsExtFile_Close,
+ rtFsExtFile_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtFsExtFile_Read,
+ rtFsExtFile_Write,
+ rtFsExtFile_Flush,
+ NULL /*PollOne*/,
+ rtFsExtFile_Tell,
+ NULL /*pfnSkip*/,
+ NULL /*pfnZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+ },
+ RTVFSFILEOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj),
+ rtFsExtFile_SetMode,
+ rtFsExtFile_SetTimes,
+ rtFsExtFile_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtFsExtFile_Seek,
+ rtFsExtFile_QuerySize,
+ rtFsExtFile_SetSize,
+ rtFsExtFile_QueryMaxSize,
+ RTVFSFILEOPS_VERSION
+};
+
+
+/**
+ * Creates a new VFS file from the given regular file inode.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param fOpen Open flags passed.
+ * @param iInode The inode for the file.
+ * @param phVfsFile Where to store the VFS file handle on success.
+ * @param pErrInfo Where to record additional error information on error, optional.
+ * @param pszWhat Logging prefix.
+ */
+static int rtFsExtVol_NewFile(PRTFSEXTVOL pThis, uint64_t fOpen, uint32_t iInode,
+ PRTVFSFILE phVfsFile, PRTERRINFO pErrInfo, const char *pszWhat)
+{
+ /*
+ * Load the inode and check that it really is a file.
+ */
+ PRTFSEXTINODE pInode = NULL;
+ int rc = rtFsExtInodeLoad(pThis, iInode, &pInode);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_FILE(pInode->ObjInfo.Attr.fMode))
+ {
+ PRTFSEXTFILE pNewFile;
+ rc = RTVfsNewFile(&g_rtFsExtFileOps, sizeof(*pNewFile), fOpen, pThis->hVfsSelf, NIL_RTVFSLOCK,
+ phVfsFile, (void **)&pNewFile);
+ if (RT_SUCCESS(rc))
+ {
+ pNewFile->pVol = pThis;
+ pNewFile->pInode = pInode;
+ pNewFile->offFile = 0;
+ }
+ }
+ else
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_NOT_A_FILE, "%s: fMode=%#RX32", pszWhat, pInode->ObjInfo.Attr.fMode);
+
+ if (RT_FAILURE(rc))
+ rtFsExtInodeRelease(pThis, pInode);
+ }
+
+ return rc;
+}
+
+
+
+/*
+ *
+ * EXT directory code.
+ * EXT directory code.
+ * EXT directory code.
+ *
+ */
+
+/**
+ * Looks up an entry in the given directory inode.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param pInode The directory inode structure to.
+ * @param pszEntry The entry to lookup.
+ * @param piInode Where to store the inode number if the entry was found.
+ */
+static int rtFsExtDir_Lookup(PRTFSEXTVOL pThis, PRTFSEXTINODE pInode, const char *pszEntry, uint32_t *piInode)
+{
+ uint64_t offEntry = 0;
+ int rc = VERR_FILE_NOT_FOUND;
+ uint32_t idxDirEntry = 0;
+ size_t cchEntry = strlen(pszEntry);
+
+ if (cchEntry > 255)
+ return VERR_FILENAME_TOO_LONG;
+
+ while (offEntry < (uint64_t)pInode->ObjInfo.cbObject)
+ {
+ EXTDIRENTRYEX DirEntry;
+ size_t cbThis = RT_MIN(sizeof(DirEntry), (uint64_t)pInode->ObjInfo.cbObject - offEntry);
+ int rc2 = rtFsExtInode_Read(pThis, pInode, offEntry, &DirEntry, cbThis, NULL);
+ if (RT_SUCCESS(rc2))
+ {
+#ifdef LOG_ENABLED
+ rtFsExtDirEntry_Log(pThis, idxDirEntry, &DirEntry);
+#endif
+
+ uint16_t cbName = pThis->fFeaturesIncompat & EXT_SB_FEAT_INCOMPAT_DIR_FILETYPE
+ ? DirEntry.Core.u.v2.cbName
+ : RT_LE2H_U16(DirEntry.Core.u.v1.cbName);
+ if ( cchEntry == cbName
+ && !memcmp(pszEntry, &DirEntry.Core.achName[0], cchEntry))
+ {
+ *piInode = RT_LE2H_U32(DirEntry.Core.iInodeRef);
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ offEntry += RT_LE2H_U16(DirEntry.Core.cbRecord);
+ idxDirEntry++;
+ }
+ else
+ {
+ rc = rc2;
+ break;
+ }
+ }
+
+ return rc;
+}
+
+
+
+/*
+ *
+ * Directory instance methods
+ * Directory instance methods
+ * Directory instance methods
+ *
+ */
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsExtDir_Close(void *pvThis)
+{
+ PRTFSEXTDIR pThis = (PRTFSEXTDIR)pvThis;
+ LogFlowFunc(("pThis=%p\n", pThis));
+ rtFsExtInodeRelease(pThis->pVol, pThis->pInode);
+ pThis->pInode = NULL;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsExtDir_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSEXTDIR pThis = (PRTFSEXTDIR)pvThis;
+ LogFlowFunc(("\n"));
+ return rtFsExtInode_QueryInfo(pThis->pInode, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtFsExtDir_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ LogFlowFunc(("\n"));
+ RT_NOREF(pvThis, fMode, fMask);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtFsExtDir_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ LogFlowFunc(("\n"));
+ RT_NOREF(pvThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtFsExtDir_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ LogFlowFunc(("\n"));
+ RT_NOREF(pvThis, uid, gid);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpen}
+ */
+static DECLCALLBACK(int) rtFsExtDir_Open(void *pvThis, const char *pszEntry, uint64_t fOpen,
+ uint32_t fFlags, PRTVFSOBJ phVfsObj)
+{
+ LogFlowFunc(("pszEntry='%s' fOpen=%#RX64 fFlags=%#x\n", pszEntry, fOpen, fFlags));
+ PRTFSEXTDIR pThis = (PRTFSEXTDIR)pvThis;
+ PRTFSEXTVOL pVol = pThis->pVol;
+ int rc = VINF_SUCCESS;
+
+ RT_NOREF(fFlags);
+
+ /*
+ * 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;
+
+ /*
+ * Lookup the entry.
+ */
+ uint32_t iInode = 0;
+ rc = rtFsExtDir_Lookup(pVol, pThis->pInode, pszEntry, &iInode);
+ if (RT_SUCCESS(rc))
+ {
+ PRTFSEXTINODE pInode = NULL;
+ rc = rtFsExtInodeLoad(pVol, iInode, &pInode);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_DIRECTORY(pInode->ObjInfo.Attr.fMode))
+ {
+ RTVFSDIR hVfsDir;
+ rc = rtFsExtVol_OpenDirByInode(pVol, iInode, &hVfsDir);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromDir(hVfsDir);
+ RTVfsDirRelease(hVfsDir);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ else if (RTFS_IS_FILE(pInode->ObjInfo.Attr.fMode))
+ {
+ RTVFSFILE hVfsFile;
+ rc = rtFsExtVol_NewFile(pVol, fOpen, iInode, &hVfsFile, NULL, pszEntry);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromFile(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+
+ LogFlow(("rtFsExtDir_Open(%s): returns %Rrc\n", pszEntry, rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnCreateDir}
+ */
+static DECLCALLBACK(int) rtFsExtDir_CreateDir(void *pvThis, const char *pszSubDir, RTFMODE fMode, PRTVFSDIR phVfsDir)
+{
+ RT_NOREF(pvThis, pszSubDir, fMode, phVfsDir);
+ LogFlowFunc(("\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpenSymlink}
+ */
+static DECLCALLBACK(int) rtFsExtDir_OpenSymlink(void *pvThis, const char *pszSymlink, PRTVFSSYMLINK phVfsSymlink)
+{
+ RT_NOREF(pvThis, pszSymlink, phVfsSymlink);
+ LogFlowFunc(("\n"));
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnCreateSymlink}
+ */
+static DECLCALLBACK(int) rtFsExtDir_CreateSymlink(void *pvThis, const char *pszSymlink, const char *pszTarget,
+ RTSYMLINKTYPE enmType, PRTVFSSYMLINK phVfsSymlink)
+{
+ RT_NOREF(pvThis, pszSymlink, pszTarget, enmType, phVfsSymlink);
+ LogFlowFunc(("\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnUnlinkEntry}
+ */
+static DECLCALLBACK(int) rtFsExtDir_UnlinkEntry(void *pvThis, const char *pszEntry, RTFMODE fType)
+{
+ RT_NOREF(pvThis, pszEntry, fType);
+ LogFlowFunc(("\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRenameEntry}
+ */
+static DECLCALLBACK(int) rtFsExtDir_RenameEntry(void *pvThis, const char *pszEntry, RTFMODE fType, const char *pszNewName)
+{
+ RT_NOREF(pvThis, pszEntry, fType, pszNewName);
+ LogFlowFunc(("\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRewindDir}
+ */
+static DECLCALLBACK(int) rtFsExtDir_RewindDir(void *pvThis)
+{
+ PRTFSEXTDIR pThis = (PRTFSEXTDIR)pvThis;
+ LogFlowFunc(("\n"));
+
+ pThis->fNoMoreFiles = false;
+ pThis->offEntry = 0;
+ pThis->idxEntry = 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnReadDir}
+ */
+static DECLCALLBACK(int) rtFsExtDir_ReadDir(void *pvThis, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry,
+ RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSEXTDIR pThis = (PRTFSEXTDIR)pvThis;
+ PRTFSEXTINODE pInode = pThis->pInode;
+ LogFlowFunc(("\n"));
+
+ if (pThis->fNoMoreFiles)
+ return VERR_NO_MORE_FILES;
+
+ EXTDIRENTRYEX DirEntry;
+ size_t cbThis = RT_MIN(sizeof(DirEntry), (uint64_t)pInode->ObjInfo.cbObject - pThis->offEntry);
+ int rc = rtFsExtInode_Read(pThis->pVol, pInode, pThis->offEntry, &DirEntry, cbThis, NULL);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ rtFsExtDirEntry_Log(pThis->pVol, pThis->idxEntry, &DirEntry);
+#endif
+
+ /* 0 inode entry means unused entry. */
+ /** @todo Can there be unused entries somewhere in the middle? */
+ uint32_t iInodeRef = RT_LE2H_U32(DirEntry.Core.iInodeRef);
+ if (iInodeRef != 0)
+ {
+ uint16_t cbName = pThis->pVol->fFeaturesIncompat & EXT_SB_FEAT_INCOMPAT_DIR_FILETYPE
+ ? DirEntry.Core.u.v2.cbName
+ : RT_LE2H_U16(DirEntry.Core.u.v1.cbName);
+
+ if (cbName <= 255)
+ {
+ size_t const cbDirEntry = *pcbDirEntry;
+
+ *pcbDirEntry = RT_UOFFSETOF_DYN(RTDIRENTRYEX, szName[cbName + 2]);
+ if (*pcbDirEntry <= cbDirEntry)
+ {
+ /* Load the referenced inode. */
+ PRTFSEXTINODE pInodeRef;
+ rc = rtFsExtInodeLoad(pThis->pVol, iInodeRef, &pInodeRef);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy(&pDirEntry->szName[0], &DirEntry.Core.achName[0], cbName);
+ pDirEntry->szName[cbName] = '\0';
+ pDirEntry->cbName = cbName;
+ rc = rtFsExtInode_QueryInfo(pInodeRef, &pDirEntry->Info, enmAddAttr);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->offEntry += RT_LE2H_U16(DirEntry.Core.cbRecord);
+ pThis->idxEntry++;
+ rtFsExtInodeRelease(pThis->pVol, pInodeRef);
+ return VINF_SUCCESS;
+ }
+ rtFsExtInodeRelease(pThis->pVol, pInodeRef);
+ }
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ else
+ rc = VERR_FILENAME_TOO_LONG;
+ }
+ else
+ {
+ rc = VERR_NO_MORE_FILES;
+ LogFlowFunc(("no more files\n"));
+ pThis->fNoMoreFiles = true;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * EXT directory operations.
+ */
+static const RTVFSDIROPS g_rtFsExtDirOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_DIR,
+ "EXT Dir",
+ rtFsExtDir_Close,
+ rtFsExtDir_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSDIROPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSDIROPS, ObjSet) - RT_UOFFSETOF(RTVFSDIROPS, Obj),
+ rtFsExtDir_SetMode,
+ rtFsExtDir_SetTimes,
+ rtFsExtDir_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtFsExtDir_Open,
+ NULL /* pfnFollowAbsoluteSymlink */,
+ NULL /* pfnOpenFile */,
+ NULL /* pfnOpenDir */,
+ rtFsExtDir_CreateDir,
+ rtFsExtDir_OpenSymlink,
+ rtFsExtDir_CreateSymlink,
+ NULL /* pfnQueryEntryInfo */,
+ rtFsExtDir_UnlinkEntry,
+ rtFsExtDir_RenameEntry,
+ rtFsExtDir_RewindDir,
+ rtFsExtDir_ReadDir,
+ RTVFSDIROPS_VERSION,
+};
+
+
+/**
+ * Opens a directory by the given inode.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param iInode The inode to open.
+ * @param phVfsDir Where to store the handle to the VFS directory on success.
+ */
+static int rtFsExtVol_OpenDirByInode(PRTFSEXTVOL pThis, uint32_t iInode, PRTVFSDIR phVfsDir)
+{
+ PRTFSEXTINODE pInode = NULL;
+ int rc = rtFsExtInodeLoad(pThis, iInode, &pInode);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_DIRECTORY(pInode->ObjInfo.Attr.fMode))
+ {
+ PRTFSEXTDIR pNewDir;
+ rc = RTVfsNewDir(&g_rtFsExtDirOps, sizeof(*pNewDir), 0 /*fFlags*/, pThis->hVfsSelf, NIL_RTVFSLOCK,
+ phVfsDir, (void **)&pNewDir);
+ if (RT_SUCCESS(rc))
+ {
+ pNewDir->fNoMoreFiles = false;
+ pNewDir->pVol = pThis;
+ pNewDir->pInode = pInode;
+ }
+ }
+ else
+ rc = VERR_VFS_BOGUS_FORMAT;
+
+ if (RT_FAILURE(rc))
+ rtFsExtInodeRelease(pThis, pInode);
+ }
+
+ return rc;
+}
+
+
+
+/*
+ *
+ * Volume level code.
+ * Volume level code.
+ * Volume level code.
+ *
+ */
+
+/**
+ * Checks whether the block range in the given block group is in use by checking the
+ * block bitmap.
+ *
+ * @returns Flag whether the range is in use.
+ * @param pBlkGrpDesc The block group to check for.
+ * @param iBlockStart The starting block to check relative from the beginning of the block group.
+ * @param cBlocks How many blocks to check.
+ */
+static bool rtFsExtIsBlockRangeInUse(PRTFSEXTBLKGRP pBlkGrpDesc, uint64_t iBlockStart, size_t cBlocks)
+{
+ /** @todo Optimize with ASMBitFirstSet(). */
+ while (cBlocks)
+ {
+ uint32_t idxByte = iBlockStart / 8;
+ uint32_t iBit = iBlockStart % 8;
+
+ if (pBlkGrpDesc->abBlockBitmap[idxByte] & RT_BIT(iBit))
+ return true;
+
+ cBlocks--;
+ iBlockStart++;
+ }
+
+ return false;
+}
+
+
+static DECLCALLBACK(int) rtFsExtVolBlockGroupTreeDestroy(PAVLU32NODECORE pCore, void *pvUser)
+{
+ RT_NOREF(pvUser);
+
+ PRTFSEXTBLKGRP pBlockGroup = (PRTFSEXTBLKGRP)pCore;
+ Assert(!pBlockGroup->cRefs);
+ RTMemFree(pBlockGroup);
+ return VINF_SUCCESS;
+}
+
+
+static DECLCALLBACK(int) rtFsExtVolInodeTreeDestroy(PAVLU32NODECORE pCore, void *pvUser)
+{
+ RT_NOREF(pvUser);
+
+ PRTFSEXTINODE pInode = (PRTFSEXTINODE)pCore;
+ Assert(!pInode->cRefs);
+ RTMemFree(pInode);
+ return VINF_SUCCESS;
+}
+
+
+static DECLCALLBACK(int) rtFsExtVolBlockTreeDestroy(PAVLU64NODECORE pCore, void *pvUser)
+{
+ RT_NOREF(pvUser);
+
+ PRTFSEXTBLOCKENTRY pBlock = (PRTFSEXTBLOCKENTRY)pCore;
+ Assert(!pBlock->cRefs);
+ RTMemFree(pBlock);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsExtVol_Close(void *pvThis)
+{
+ PRTFSEXTVOL pThis = (PRTFSEXTVOL)pvThis;
+
+ /* Destroy the block group tree. */
+ RTAvlU32Destroy(&pThis->BlockGroupRoot, rtFsExtVolBlockGroupTreeDestroy, pThis);
+ pThis->BlockGroupRoot = NULL;
+ RTListInit(&pThis->LstBlockGroupLru);
+
+ /* Destroy the inode tree. */
+ RTAvlU32Destroy(&pThis->InodeRoot, rtFsExtVolInodeTreeDestroy, pThis);
+ pThis->InodeRoot = NULL;
+ RTListInit(&pThis->LstInodeLru);
+
+ /* Destroy the block cache tree. */
+ RTAvlU64Destroy(&pThis->BlockRoot, rtFsExtVolBlockTreeDestroy, pThis);
+ pThis->BlockRoot = NULL;
+ RTListInit(&pThis->LstBlockLru);
+
+ /*
+ * Backing file and handles.
+ */
+ RTVfsFileRelease(pThis->hVfsBacking);
+ pThis->hVfsBacking = NIL_RTVFSFILE;
+ pThis->hVfsSelf = NIL_RTVFS;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsExtVol_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ RT_NOREF(pvThis, pObjInfo, enmAddAttr);
+ return VERR_WRONG_TYPE;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnOpenRoot}
+ */
+static DECLCALLBACK(int) rtFsExtVol_OpenRoot(void *pvThis, PRTVFSDIR phVfsDir)
+{
+ PRTFSEXTVOL pThis = (PRTFSEXTVOL)pvThis;
+ int rc = rtFsExtVol_OpenDirByInode(pThis, EXT_INODE_NR_ROOT_DIR, phVfsDir);
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnQueryRangeState}
+ */
+static DECLCALLBACK(int) rtFsExtVol_QueryRangeState(void *pvThis, uint64_t off, size_t cb, bool *pfUsed)
+{
+ int rc = VINF_SUCCESS;
+ PRTFSEXTVOL pThis = (PRTFSEXTVOL)pvThis;
+
+ *pfUsed = false;
+
+ uint64_t iBlock = rtFsExtDiskOffsetToBlockIdx(pThis, off);
+ uint64_t cBlocks = rtFsExtDiskOffsetToBlockIdx(pThis, cb) + (cb % pThis->cbBlock ? 1 : 0);
+ while (cBlocks > 0)
+ {
+ uint32_t const iBlockGroup = iBlock / pThis->cBlocksPerGroup;
+ uint32_t const iBlockRelStart = iBlock - iBlockGroup * pThis->cBlocksPerGroup;
+ PRTFSEXTBLKGRP pBlockGroup = NULL;
+
+ rc = rtFsExtBlockGroupLoad(pThis, iBlockGroup, &pBlockGroup);
+ if (RT_FAILURE(rc))
+ break;
+
+ uint64_t cBlocksThis = RT_MIN(cBlocks, iBlockRelStart - pThis->cBlocksPerGroup);
+ if (rtFsExtIsBlockRangeInUse(pBlockGroup, iBlockRelStart, cBlocksThis))
+ {
+ *pfUsed = true;
+ break;
+ }
+
+ rtFsExtBlockGroupRelease(pThis, pBlockGroup);
+ cBlocks -= cBlocksThis;
+ iBlock += cBlocksThis;
+ }
+
+ return rc;
+}
+
+
+DECL_HIDDEN_CONST(const RTVFSOPS) g_rtFsExtVolOps =
+{
+ /* .Obj = */
+ {
+ /* .uVersion = */ RTVFSOBJOPS_VERSION,
+ /* .enmType = */ RTVFSOBJTYPE_VFS,
+ /* .pszName = */ "ExtVol",
+ /* .pfnClose = */ rtFsExtVol_Close,
+ /* .pfnQueryInfo = */ rtFsExtVol_QueryInfo,
+ /* .pfnQueryInfoEx = */ NULL,
+ /* .uEndMarker = */ RTVFSOBJOPS_VERSION
+ },
+ /* .uVersion = */ RTVFSOPS_VERSION,
+ /* .fFeatures = */ 0,
+ /* .pfnOpenRoot = */ rtFsExtVol_OpenRoot,
+ /* .pfnQueryRangeState = */ rtFsExtVol_QueryRangeState,
+ /* .uEndMarker = */ RTVFSOPS_VERSION
+};
+
+
+
+/**
+ * Loads the parameters from the given original ext2 format superblock (EXT_SB_REV_ORIG).
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param pSb The superblock to load.
+ * @param pErrInfo Where to return additional error info.
+ */
+static int rtFsExtVolLoadAndParseSuperBlockV0(PRTFSEXTVOL pThis, PCEXTSUPERBLOCK pSb, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pErrInfo);
+
+ /*
+ * Linux never supported a differing cluster (also called fragment) size for
+ * the original ext2 layout so we reject such filesystems as it is not clear what
+ * the purpose is really.
+ */
+ if (RT_LE2H_U32(pSb->cLogBlockSize) != RT_LE2H_U32(pSb->cLogClusterSize))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "EXT filesystem cluster and block size differ");
+
+ pThis->f64Bit = false;
+ pThis->cBlockShift = 10 + RT_LE2H_U32(pSb->cLogBlockSize);
+ pThis->cbBlock = UINT64_C(1) << pThis->cBlockShift;
+ pThis->cbInode = sizeof(EXTINODE);
+ pThis->cbBlkGrpDesc = sizeof(EXTBLOCKGROUPDESC32);
+ pThis->cBlocksPerGroup = RT_LE2H_U32(pSb->cBlocksPerGroup);
+ pThis->cInodesPerGroup = RT_LE2H_U32(pSb->cInodesPerBlockGroup);
+ pThis->cBlockGroups = RT_LE2H_U32(pSb->cBlocksTotalLow) / pThis->cBlocksPerGroup;
+ pThis->cbBlockBitmap = pThis->cBlocksPerGroup / 8;
+ if (pThis->cBlocksPerGroup % 8)
+ pThis->cbBlockBitmap++;
+ pThis->cbInodeBitmap = pThis->cInodesPerGroup / 8;
+ if (pThis->cInodesPerGroup % 8)
+ pThis->cbInodeBitmap++;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Loads the parameters from the given ext superblock (EXT_SB_REV_V2_DYN_INODE_SZ).
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param pSb The superblock to load.
+ * @param pErrInfo Where to return additional error info.
+ */
+static int rtFsExtVolLoadAndParseSuperBlockV1(PRTFSEXTVOL pThis, PCEXTSUPERBLOCK pSb, PRTERRINFO pErrInfo)
+{
+ if ((RT_LE2H_U32(pSb->fFeaturesIncompat) & ~RTFSEXT_INCOMPAT_FEATURES_SUPP) != 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "EXT filesystem contains unsupported incompatible features: %RX32",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & ~RTFSEXT_INCOMPAT_FEATURES_SUPP);
+ if ( RT_LE2H_U32(pSb->fFeaturesCompatRo) != 0
+ && !(pThis->fMntFlags & RTVFSMNT_F_READ_ONLY))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "EXT filesystem contains unsupported readonly features: %RX32",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo));
+
+ pThis->fFeaturesIncompat = RT_LE2H_U32(pSb->fFeaturesIncompat);
+ pThis->f64Bit = RT_BOOL(pThis->fFeaturesIncompat & EXT_SB_FEAT_INCOMPAT_64BIT);
+ pThis->cBlockShift = 10 + RT_LE2H_U32(pSb->cLogBlockSize);
+ pThis->cbBlock = UINT64_C(1) << pThis->cBlockShift;
+ pThis->cbInode = RT_LE2H_U16(pSb->cbInode);
+ pThis->cbBlkGrpDesc = pThis->f64Bit ? RT_LE2H_U16(pSb->cbGroupDesc) : sizeof(EXTBLOCKGROUPDESC32);
+ pThis->cBlocksPerGroup = RT_LE2H_U32(pSb->cBlocksPerGroup);
+ pThis->cInodesPerGroup = RT_LE2H_U32(pSb->cInodesPerBlockGroup);
+ pThis->cBlockGroups = RT_LE2H_U32(pSb->cBlocksTotalLow) / pThis->cBlocksPerGroup;
+ pThis->cbBlockBitmap = pThis->cBlocksPerGroup / 8;
+ if (pThis->cBlocksPerGroup % 8)
+ pThis->cbBlockBitmap++;
+ pThis->cbInodeBitmap = pThis->cInodesPerGroup / 8;
+ if (pThis->cInodesPerGroup % 8)
+ pThis->cbInodeBitmap++;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Loads and parses the superblock of the filesystem.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param pErrInfo Where to return additional error info.
+ */
+static int rtFsExtVolLoadAndParseSuperblock(PRTFSEXTVOL pThis, PRTERRINFO pErrInfo)
+{
+ int rc = VINF_SUCCESS;
+ EXTSUPERBLOCK Sb;
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, EXT_SB_OFFSET, &Sb, sizeof(EXTSUPERBLOCK), NULL);
+ if (RT_FAILURE(rc))
+ return RTERRINFO_LOG_SET(pErrInfo, rc, "Error reading super block");
+
+ /* Validate the superblock. */
+ if (RT_LE2H_U16(Sb.u16Signature) != EXT_SB_SIGNATURE)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "Not EXT - Signature mismatch: %RX16", RT_LE2H_U16(Sb.u16Signature));
+
+#ifdef LOG_ENABLED
+ rtFsExtSb_Log(&Sb);
+#endif
+
+ if (RT_LE2H_U16(Sb.u16FilesystemState) == EXT_SB_STATE_ERRORS)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "EXT filesystem contains errors");
+
+ if (RT_LE2H_U32(Sb.u32RevLvl) == EXT_SB_REV_ORIG)
+ rc = rtFsExtVolLoadAndParseSuperBlockV0(pThis, &Sb, pErrInfo);
+ else
+ rc = rtFsExtVolLoadAndParseSuperBlockV1(pThis, &Sb, pErrInfo);
+
+ return rc;
+}
+
+
+RTDECL(int) RTFsExtVolOpen(RTVFSFILE hVfsFileIn, uint32_t fMntFlags, uint32_t fExtFlags, PRTVFS phVfs, PRTERRINFO pErrInfo)
+{
+ AssertPtrReturn(phVfs, VERR_INVALID_POINTER);
+ AssertReturn(!(fMntFlags & ~RTVFSMNT_F_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertReturn(!fExtFlags, VERR_INVALID_FLAGS);
+
+ uint32_t cRefs = RTVfsFileRetain(hVfsFileIn);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Create a VFS instance and initialize the data so rtFsExtVol_Close works.
+ */
+ RTVFS hVfs;
+ PRTFSEXTVOL pThis;
+ int rc = RTVfsNew(&g_rtFsExtVolOps, sizeof(*pThis), NIL_RTVFS, RTVFSLOCK_CREATE_RW, &hVfs, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->hVfsBacking = hVfsFileIn;
+ pThis->hVfsSelf = hVfs;
+ pThis->fMntFlags = fMntFlags;
+ pThis->fExtFlags = fExtFlags;
+ pThis->BlockGroupRoot = NULL;
+ pThis->InodeRoot = NULL;
+ pThis->BlockRoot = NULL;
+ pThis->cbBlockGroups = 0;
+ pThis->cbInodes = 0;
+ pThis->cbBlocks = 0;
+ RTListInit(&pThis->LstBlockGroupLru);
+ RTListInit(&pThis->LstInodeLru);
+ RTListInit(&pThis->LstBlockLru);
+
+ rc = RTVfsFileQuerySize(pThis->hVfsBacking, &pThis->cbBacking);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsExtVolLoadAndParseSuperblock(pThis, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfs = hVfs;
+ return VINF_SUCCESS;
+ }
+ }
+
+ RTVfsRelease(hVfs);
+ *phVfs = NIL_RTVFS;
+ }
+ else
+ RTVfsFileRelease(hVfsFileIn);
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate}
+ */
+static DECLCALLBACK(int) rtVfsChainExtVol_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec,
+ PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg);
+
+ /*
+ * 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.
+ */
+ bool fReadOnly = (pSpec->fOpenFile & RTFILE_O_ACCESS_MASK) == RTFILE_O_READ;
+ if (pElement->cArgs > 0)
+ {
+ const char *psz = pElement->paArgs[0].psz;
+ if (*psz)
+ {
+ if (!strcmp(psz, "ro"))
+ fReadOnly = true;
+ else if (!strcmp(psz, "rw"))
+ fReadOnly = false;
+ else
+ {
+ *poffError = pElement->paArgs[0].offSpec;
+ return RTErrInfoSet(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Expected 'ro' or 'rw' as argument");
+ }
+ }
+ }
+
+ pElement->uProvider = fReadOnly ? RTVFSMNT_F_READ_ONLY : 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate}
+ */
+static DECLCALLBACK(int) rtVfsChainExtVol_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 = RTFsExtVolOpen(hVfsFileIn, (uint32_t)pElement->uProvider, (uint32_t)(pElement->uProvider >> 32), &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) rtVfsChainExtVol_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 'ext'. */
+static RTVFSCHAINELEMENTREG g_rtVfsChainExtVolReg =
+{
+ /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION,
+ /* fReserved = */ 0,
+ /* pszName = */ "ext",
+ /* ListEntry = */ { NULL, NULL },
+ /* pszHelp = */ "Open a EXT file system, requires a file object on the left side.\n"
+ "First argument is an optional 'ro' (read-only) or 'rw' (read-write) flag.\n",
+ /* pfnValidate = */ rtVfsChainExtVol_Validate,
+ /* pfnInstantiate = */ rtVfsChainExtVol_Instantiate,
+ /* pfnCanReuseElement = */ rtVfsChainExtVol_CanReuseElement,
+ /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION
+};
+
+RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainExtVolReg, rtVfsChainExtVolReg);
+
diff --git a/src/VBox/Runtime/common/fs/fatvfs.cpp b/src/VBox/Runtime/common/fs/fatvfs.cpp
new file mode 100644
index 00000000..e7cb5b46
--- /dev/null
+++ b/src/VBox/Runtime/common/fs/fatvfs.cpp
@@ -0,0 +1,6374 @@
+/* $Id: fatvfs.cpp $ */
+/** @file
+ * IPRT - FAT Virtual Filesystem.
+ */
+
+/*
+ * 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/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/file.h>
+#include <iprt/err.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/poll.h>
+#include <iprt/rand.h>
+#include <iprt/string.h>
+#include <iprt/sg.h>
+#include <iprt/thread.h>
+#include <iprt/uni.h>
+#include <iprt/utf16.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+#include <iprt/zero.h>
+#include <iprt/formats/fat.h>
+
+#include "internal/fs.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/**
+ * Gets the cluster from a directory entry.
+ *
+ * @param a_pDirEntry Pointer to the directory entry.
+ * @param a_pVol Pointer to the volume.
+ */
+#define RTFSFAT_GET_CLUSTER(a_pDirEntry, a_pVol) \
+ ( (a_pVol)->enmFatType >= RTFSFATTYPE_FAT32 \
+ ? RT_MAKE_U32((a_pDirEntry)->idxCluster, (a_pDirEntry)->u.idxClusterHigh) \
+ : (a_pDirEntry)->idxCluster )
+
+/**
+ * Rotates a unsigned 8-bit value one bit to the right.
+ *
+ * @returns Rotated 8-bit value.
+ * @param a_bValue The value to rotate.
+ */
+#define RTFSFAT_ROT_R1_U8(a_bValue) (((a_bValue) >> 1) | (uint8_t)((a_bValue) << 7))
+
+
+/** Maximum number of characters we will create in a long file name. */
+#define RTFSFAT_MAX_LFN_CHARS 255
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to a FAT directory instance. */
+typedef struct RTFSFATDIRSHRD *PRTFSFATDIRSHRD;
+/** Pointer to a FAT volume (VFS instance data). */
+typedef struct RTFSFATVOL *PRTFSFATVOL;
+
+
+/** The number of entire in a chain part. */
+#define RTFSFATCHAINPART_ENTRIES (256U - 4U)
+
+/**
+ * A part of the cluster chain covering up to 252 clusters.
+ */
+typedef struct RTFSFATCHAINPART
+{
+ /** List entry. */
+ RTLISTNODE ListEntry;
+ /** Chain entries. */
+ uint32_t aEntries[RTFSFATCHAINPART_ENTRIES];
+} RTFSFATCHAINPART;
+AssertCompile(sizeof(RTFSFATCHAINPART) <= _1K);
+typedef RTFSFATCHAINPART *PRTFSFATCHAINPART;
+typedef RTFSFATCHAINPART const *PCRTFSFATCHAINPART;
+
+
+/**
+ * A FAT cluster chain.
+ */
+typedef struct RTFSFATCHAIN
+{
+ /** The chain size in bytes. */
+ uint32_t cbChain;
+ /** The chain size in entries. */
+ uint32_t cClusters;
+ /** The cluster size. */
+ uint32_t cbCluster;
+ /** The shift count for converting between clusters and bytes. */
+ uint8_t cClusterByteShift;
+ /** List of chain parts (RTFSFATCHAINPART). */
+ RTLISTANCHOR ListParts;
+} RTFSFATCHAIN;
+/** Pointer to a FAT chain. */
+typedef RTFSFATCHAIN *PRTFSFATCHAIN;
+/** Pointer to a const FAT chain. */
+typedef RTFSFATCHAIN const *PCRTFSFATCHAIN;
+
+
+/**
+ * FAT file system object (common part to files and dirs (shared)).
+ */
+typedef struct RTFSFATOBJ
+{
+ /** The parent directory keeps a list of open objects (RTFSFATOBJ). */
+ RTLISTNODE Entry;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** The parent directory (not released till all children are close). */
+ PRTFSFATDIRSHRD pParentDir;
+ /** The byte offset of the directory entry in the parent dir.
+ * This is set to UINT32_MAX for the root directory. */
+ uint32_t offEntryInDir;
+ /** Attributes. */
+ RTFMODE fAttrib;
+ /** The object size. */
+ uint32_t cbObject;
+ /** The access time. */
+ RTTIMESPEC AccessTime;
+ /** The modificaton time. */
+ RTTIMESPEC ModificationTime;
+ /** The birth time. */
+ RTTIMESPEC BirthTime;
+ /** Cluster chain. */
+ RTFSFATCHAIN Clusters;
+ /** Pointer to the volume. */
+ struct RTFSFATVOL *pVol;
+ /** Set if we've maybe dirtied the FAT. */
+ bool fMaybeDirtyFat;
+ /** Set if we've maybe dirtied the directory entry. */
+ bool fMaybeDirtyDirEnt;
+} RTFSFATOBJ;
+/** Poitner to a FAT file system object. */
+typedef RTFSFATOBJ *PRTFSFATOBJ;
+
+/**
+ * Shared FAT file data.
+ */
+typedef struct RTFSFATFILESHRD
+{
+ /** Core FAT object info. */
+ RTFSFATOBJ Core;
+} RTFSFATFILESHRD;
+/** Pointer to shared FAT file data. */
+typedef RTFSFATFILESHRD *PRTFSFATFILESHRD;
+
+
+/**
+ * Per handle data for a FAT file.
+ */
+typedef struct RTFSFATFILE
+{
+ /** Pointer to the shared data. */
+ PRTFSFATFILESHRD pShared;
+ /** The current file offset. */
+ uint32_t offFile;
+} RTFSFATFILE;
+/** Pointer to the per handle data of a FAT file. */
+typedef RTFSFATFILE *PRTFSFATFILE;
+
+
+/**
+ * FAT shared directory structure.
+ *
+ * We work directories in one of two buffering modes. If there are few entries
+ * or if it's the FAT12/16 root directory, we map the whole thing into memory.
+ * If it's too large, we use an inefficient sector buffer for now.
+ *
+ * Directory entry updates happens exclusively via the directory, so any open
+ * files or subdirs have a parent reference for doing that. The parent OTOH,
+ * keeps a list of open children.
+ */
+typedef struct RTFSFATDIRSHRD
+{
+ /** Core FAT object info. */
+ RTFSFATOBJ Core;
+ /** Open child objects (RTFSFATOBJ). */
+ RTLISTNODE OpenChildren;
+
+ /** Number of directory entries. */
+ uint32_t cEntries;
+
+ /** If fully buffered. */
+ bool fFullyBuffered;
+ /** Set if this is a linear root directory. */
+ bool fIsLinearRootDir;
+ /** The size of the memory paEntries points at. */
+ uint32_t cbAllocatedForEntries;
+
+ /** Pointer to the directory buffer.
+ * In fully buffering mode, this is the whole of the directory. Otherwise it's
+ * just a sector worth of buffers. */
+ PFATDIRENTRYUNION paEntries;
+ /** The disk offset corresponding to what paEntries points to.
+ * UINT64_MAX if notthing read into paEntries yet. */
+ uint64_t offEntriesOnDisk;
+ union
+ {
+ /** Data for the full buffered mode.
+ * No need to messing around with clusters here, as we only uses this for
+ * directories with a contiguous mapping on the disk.
+ * So, if we grow a directory in a non-contiguous manner, we have to switch
+ * to sector buffering on the fly. */
+ struct
+ {
+ /** Number of sectors mapped by paEntries and pbDirtySectors. */
+ uint32_t cSectors;
+ /** Number of dirty sectors. */
+ uint32_t cDirtySectors;
+ /** Dirty sector bitmap (one bit per sector). */
+ uint8_t *pbDirtySectors;
+ } Full;
+ /** The simple sector buffering.
+ * This only works for clusters, so no FAT12/16 root directory fun. */
+ struct
+ {
+ /** The directory offset, UINT32_MAX if invalid. */
+ uint32_t offInDir;
+ /** Dirty flag. */
+ bool fDirty;
+ } Simple;
+ } u;
+} RTFSFATDIRSHRD;
+/** Pointer to a shared FAT directory instance. */
+typedef RTFSFATDIRSHRD *PRTFSFATDIRSHRD;
+
+
+/**
+ * The per handle FAT directory data.
+ */
+typedef struct RTFSFATDIR
+{
+ /** Core FAT object info. */
+ PRTFSFATDIRSHRD pShared;
+ /** The current directory offset. */
+ uint32_t offDir;
+} RTFSFATDIR;
+/** Pointer to a per handle FAT directory data. */
+typedef RTFSFATDIR *PRTFSFATDIR;
+
+
+/**
+ * File allocation table cache entry.
+ */
+typedef struct RTFSFATCLUSTERMAPENTRY
+{
+ /** The byte offset into the fat, UINT32_MAX if invalid entry. */
+ uint32_t offFat;
+ /** Pointer to the data. */
+ uint8_t *pbData;
+ /** Dirty bitmap. Indexed by byte offset right shifted by
+ * RTFSFATCLUSTERMAPCACHE::cDirtyShift. */
+ uint64_t bmDirty;
+} RTFSFATCLUSTERMAPENTRY;
+/** Pointer to a file allocation table cache entry. */
+typedef RTFSFATCLUSTERMAPENTRY *PRTFSFATCLUSTERMAPENTRY;
+
+/**
+ * File allocation table cache.
+ */
+typedef struct RTFSFATCLUSTERMAPCACHE
+{
+ /** Number of cache entries (power of two). */
+ uint32_t cEntries;
+ /** This shift count to use in the first step of the index calculation. */
+ uint32_t cEntryIndexShift;
+ /** The AND mask to use in the second step of the index calculation. */
+ uint32_t fEntryIndexMask;
+ /** The max size of data in a cache entry (power of two). */
+ uint32_t cbEntry;
+ /** The AND mask to use to get the entry offset. */
+ uint32_t fEntryOffsetMask;
+ /** Dirty bitmap shift count. */
+ uint32_t cDirtyShift;
+ /** The dirty cache line size (multiple of two). */
+ uint32_t cbDirtyLine;
+ /** The FAT size. */
+ uint32_t cbFat;
+ /** The Number of clusters in the FAT. */
+ uint32_t cClusters;
+ /** Cluster allocation search hint. */
+ uint32_t idxAllocHint;
+ /** Pointer to the volume (for disk access). */
+ PRTFSFATVOL pVol;
+ /** The cache name. */
+ const char *pszName;
+ /** Cache entries. */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ RTFSFATCLUSTERMAPENTRY aEntries[RT_FLEXIBLE_ARRAY];
+} RTFSFATCLUSTERMAPCACHE;
+/** Pointer to a FAT linear metadata cache. */
+typedef RTFSFATCLUSTERMAPCACHE *PRTFSFATCLUSTERMAPCACHE;
+
+
+/**
+ * BPB version.
+ */
+typedef enum RTFSFATBPBVER
+{
+ RTFSFATBPBVER_INVALID = 0,
+ RTFSFATBPBVER_NO_BPB,
+ RTFSFATBPBVER_DOS_2_0,
+ //RTFSFATBPBVER_DOS_3_2, - we don't try identify this one.
+ RTFSFATBPBVER_DOS_3_31,
+ RTFSFATBPBVER_EXT_28,
+ RTFSFATBPBVER_EXT_29,
+ RTFSFATBPBVER_FAT32_28,
+ RTFSFATBPBVER_FAT32_29,
+ RTFSFATBPBVER_END
+} RTFSFATBPBVER;
+
+
+/**
+ * A FAT volume.
+ */
+typedef struct RTFSFATVOL
+{
+ /** Handle to itself. */
+ RTVFS hVfsSelf;
+ /** The file, partition, or whatever backing the FAT volume. */
+ RTVFSFILE hVfsBacking;
+ /** The size of the backing thingy. */
+ uint64_t cbBacking;
+ /** Byte offset of the bootsector relative to the start of the file. */
+ uint64_t offBootSector;
+ /** The UTC offset in nanoseconds to use for this file system (FAT traditionally
+ * stores timestamps in local time).
+ * @remarks This may need improving later. */
+ int64_t offNanoUTC;
+ /** The UTC offset in minutes to use for this file system (FAT traditionally
+ * stores timestamps in local time).
+ * @remarks This may need improving later. */
+ int32_t offMinUTC;
+ /** Set if read-only mode. */
+ bool fReadOnly;
+ /** Media byte. */
+ uint8_t bMedia;
+ /** Reserved sectors. */
+ uint32_t cReservedSectors;
+ /** The BPB version. Gives us an idea of the FAT file system version. */
+ RTFSFATBPBVER enmBpbVersion;
+
+ /** Logical sector size. */
+ uint32_t cbSector;
+ /** The shift count for converting between sectors and bytes. */
+ uint8_t cSectorByteShift;
+ /** The shift count for converting between clusters and bytes. */
+ uint8_t cClusterByteShift;
+ /** The cluster size in bytes. */
+ uint32_t cbCluster;
+ /** The number of data clusters, including the two reserved ones. */
+ uint32_t cClusters;
+ /** The offset of the first cluster. */
+ uint64_t offFirstCluster;
+ /** The total size from the BPB, in bytes. */
+ uint64_t cbTotalSize;
+
+ /** The FAT type. */
+ RTFSFATTYPE enmFatType;
+
+ /** Number of FAT entries (clusters). */
+ uint32_t cFatEntries;
+ /** The size of a FAT, in bytes. */
+ uint32_t cbFat;
+ /** Number of FATs. */
+ uint32_t cFats;
+ /** The end of chain marker used by the formatter (FAT entry \#2). */
+ uint32_t idxEndOfChain;
+ /** The maximum last cluster supported by the FAT format. */
+ uint32_t idxMaxLastCluster;
+ /** FAT byte offsets. */
+ uint64_t aoffFats[8];
+ /** Pointer to the FAT (cluster map) cache. */
+ PRTFSFATCLUSTERMAPCACHE pFatCache;
+
+ /** The root directory byte offset. */
+ uint64_t offRootDir;
+ /** Root directory cluster, UINT32_MAX if not FAT32. */
+ uint32_t idxRootDirCluster;
+ /** Number of root directory entries, if fixed. UINT32_MAX for FAT32. */
+ uint32_t cRootDirEntries;
+ /** The size of the root directory, rounded up to the nearest sector size. */
+ uint32_t cbRootDir;
+ /** The root directory data (shared). */
+ PRTFSFATDIRSHRD pRootDir;
+
+ /** Serial number. */
+ uint32_t uSerialNo;
+ /** The stripped volume label, if included in EBPB. */
+ char szLabel[12];
+ /** The file system type from the EBPB (also stripped). */
+ char szType[9];
+ /** Number of FAT32 boot sector copies. */
+ uint8_t cBootSectorCopies;
+ /** FAT32 flags. */
+ uint16_t fFat32Flags;
+ /** Offset of the FAT32 boot sector copies, UINT64_MAX if none. */
+ uint64_t offBootSectorCopies;
+
+ /** The FAT32 info sector byte offset, UINT64_MAX if not present. */
+ uint64_t offFat32InfoSector;
+ /** The FAT32 info sector if offFat32InfoSector isn't UINT64_MAX. */
+ FAT32INFOSECTOR Fat32InfoSector;
+} RTFSFATVOL;
+/** Pointer to a const FAT volume (VFS instance data). */
+typedef RTFSFATVOL const *PCRTFSFATVOL;
+
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/**
+ * Codepage 437 translation table with invalid 8.3 characters marked as 0xffff or 0xfffe.
+ *
+ * The 0xfffe notation is used for characters that are valid in long file names but not short.
+ *
+ * @remarks The valid first 128 entries are 1:1 with unicode.
+ * @remarks Lower case characters are all marked invalid.
+ */
+static RTUTF16 g_awchFatCp437ValidChars[] =
+{ /* 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f */
+ 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
+ 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0xffff, 0xfffe, 0xfffe, 0x002d, 0xfffe, 0xffff,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0xffff, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xffff,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0xfffe, 0xffff, 0xfffe, 0x005e, 0x005f,
+ 0x0060, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe,
+ 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xffff, 0xffff, 0xffff, 0x007e, 0xffff,
+ 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5,
+ 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192,
+ 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
+ 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
+ 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
+ 0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
+ 0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0
+};
+AssertCompileSize(g_awchFatCp437ValidChars, 256*2);
+
+/**
+ * Codepage 437 translation table without invalid 8.3. character markings.
+ */
+static RTUTF16 g_awchFatCp437Chars[] =
+{ /* 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f */
+ 0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022, 0x25d8, 0x25cb, 0x25d9, 0x2642, 0x2640, 0x266a, 0x266b, 0x263c,
+ 0x25ba, 0x25c4, 0x2195, 0x203c, 0x00b6, 0x00a7, 0x25ac, 0x21a8, 0x2191, 0x2193, 0x2192, 0x2190, 0x221f, 0x2194, 0x25b2, 0x25bc,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2302,
+ 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5,
+ 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192,
+ 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
+ 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
+ 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
+ 0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
+ 0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0
+};
+AssertCompileSize(g_awchFatCp437Chars, 256*2);
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static PRTFSFATOBJ rtFsFatDirShrd_LookupShared(PRTFSFATDIRSHRD pThis, uint32_t offEntryInDir);
+static void rtFsFatDirShrd_AddOpenChild(PRTFSFATDIRSHRD pDir, PRTFSFATOBJ pChild);
+static void rtFsFatDirShrd_RemoveOpenChild(PRTFSFATDIRSHRD pDir, PRTFSFATOBJ pChild);
+static int rtFsFatDirShrd_GetEntryForUpdate(PRTFSFATDIRSHRD pThis, uint32_t offEntryInDir,
+ PFATDIRENTRY *ppDirEntry, uint32_t *puWriteLock);
+static int rtFsFatDirShrd_PutEntryAfterUpdate(PRTFSFATDIRSHRD pThis, PFATDIRENTRY pDirEntry, uint32_t uWriteLock);
+static int rtFsFatDirShrd_Flush(PRTFSFATDIRSHRD pThis);
+static int rtFsFatDir_NewWithShared(PRTFSFATVOL pThis, PRTFSFATDIRSHRD pShared, PRTVFSDIR phVfsDir);
+static int rtFsFatDir_New(PRTFSFATVOL pThis, PRTFSFATDIRSHRD pParentDir, PCFATDIRENTRY pDirEntry, uint32_t offEntryInDir,
+ uint32_t idxCluster, uint64_t offDisk, uint32_t cbDir, PRTVFSDIR phVfsDir);
+
+
+/**
+ * Convers a cluster to a disk offset.
+ *
+ * @returns Disk byte offset, UINT64_MAX on invalid cluster.
+ * @param pThis The FAT volume instance.
+ * @param idxCluster The cluster number.
+ */
+DECLINLINE(uint64_t) rtFsFatClusterToDiskOffset(PRTFSFATVOL pThis, uint32_t idxCluster)
+{
+ AssertReturn(idxCluster >= FAT_FIRST_DATA_CLUSTER, UINT64_MAX);
+ AssertReturn(idxCluster < pThis->cClusters, UINT64_MAX);
+ return (idxCluster - FAT_FIRST_DATA_CLUSTER) * (uint64_t)pThis->cbCluster
+ + pThis->offFirstCluster;
+}
+
+
+#ifdef RT_STRICT
+/**
+ * Assert chain consistency.
+ */
+static bool rtFsFatChain_AssertValid(PCRTFSFATCHAIN pChain)
+{
+ bool fRc = true;
+ uint32_t cParts = 0;
+ PRTFSFATCHAINPART pPart;
+ RTListForEach(&pChain->ListParts, pPart, RTFSFATCHAINPART, ListEntry)
+ cParts++;
+
+ uint32_t cExpected = (pChain->cClusters + RTFSFATCHAINPART_ENTRIES - 1) / RTFSFATCHAINPART_ENTRIES;
+ AssertMsgStmt(cExpected == cParts, ("cExpected=%#x cParts=%#x\n", cExpected, cParts), fRc = false);
+ AssertMsgStmt(pChain->cbChain == (pChain->cClusters << pChain->cClusterByteShift),
+ ("cExpected=%#x cParts=%#x\n", cExpected, cParts), fRc = false);
+ return fRc;
+}
+#endif /* RT_STRICT */
+
+
+/**
+ * Initializes an empty cluster chain.
+ *
+ * @param pChain The chain.
+ * @param pVol The volume.
+ */
+static void rtFsFatChain_InitEmpty(PRTFSFATCHAIN pChain, PRTFSFATVOL pVol)
+{
+ pChain->cbCluster = pVol->cbCluster;
+ pChain->cClusterByteShift = pVol->cClusterByteShift;
+ pChain->cbChain = 0;
+ pChain->cClusters = 0;
+ RTListInit(&pChain->ListParts);
+}
+
+
+/**
+ * Deletes a chain, freeing it's resources.
+ *
+ * @param pChain The chain.
+ */
+static void rtFsFatChain_Delete(PRTFSFATCHAIN pChain)
+{
+ Assert(RT_IS_POWER_OF_TWO(pChain->cbCluster));
+ Assert(RT_BIT_32(pChain->cClusterByteShift) == pChain->cbCluster);
+
+ PRTFSFATCHAINPART pPart = RTListRemoveLast(&pChain->ListParts, RTFSFATCHAINPART, ListEntry);
+ while (pPart)
+ {
+ RTMemFree(pPart);
+ pPart = RTListRemoveLast(&pChain->ListParts, RTFSFATCHAINPART, ListEntry);
+ }
+
+ pChain->cbChain = 0;
+ pChain->cClusters = 0;
+}
+
+
+/**
+ * Appends a cluster to a cluster chain.
+ *
+ * @returns IPRT status code.
+ * @param pChain The chain.
+ * @param idxCluster The cluster to append.
+ */
+static int rtFsFatChain_Append(PRTFSFATCHAIN pChain, uint32_t idxCluster)
+{
+ PRTFSFATCHAINPART pPart;
+ uint32_t idxLast = pChain->cClusters % RTFSFATCHAINPART_ENTRIES;
+ if (idxLast != 0)
+ pPart = RTListGetLast(&pChain->ListParts, RTFSFATCHAINPART, ListEntry);
+ else
+ {
+ pPart = (PRTFSFATCHAINPART)RTMemAllocZ(sizeof(*pPart));
+ if (!pPart)
+ return VERR_NO_MEMORY;
+ RTListAppend(&pChain->ListParts, &pPart->ListEntry);
+ }
+ pPart->aEntries[idxLast] = idxCluster;
+ pChain->cClusters++;
+ pChain->cbChain += pChain->cbCluster;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Reduces the number of clusters in the chain to @a cClusters.
+ *
+ * @param pChain The chain.
+ * @param cClustersNew The new cluster count. Must be equal or smaller to
+ * the current number of clusters.
+ */
+static void rtFsFatChain_Shrink(PRTFSFATCHAIN pChain, uint32_t cClustersNew)
+{
+ uint32_t cOldParts = (pChain->cClusters + RTFSFATCHAINPART_ENTRIES - 1) / RTFSFATCHAINPART_ENTRIES;
+ uint32_t cNewParts = (cClustersNew + RTFSFATCHAINPART_ENTRIES - 1) / RTFSFATCHAINPART_ENTRIES;
+ Assert(cOldParts >= cNewParts);
+ while (cOldParts-- > cNewParts)
+ RTMemFree(RTListRemoveLast(&pChain->ListParts, RTFSFATCHAINPART, ListEntry));
+ pChain->cClusters = cClustersNew;
+ pChain->cbChain = cClustersNew << pChain->cClusterByteShift;
+ Assert(rtFsFatChain_AssertValid(pChain));
+}
+
+
+
+/**
+ * Converts a file offset to a disk offset.
+ *
+ * The disk offset is only valid until the end of the cluster it is within.
+ *
+ * @returns Disk offset. UINT64_MAX if invalid file offset.
+ * @param pChain The chain.
+ * @param offFile The file offset.
+ * @param pVol The volume.
+ */
+static uint64_t rtFsFatChain_FileOffsetToDiskOff(PCRTFSFATCHAIN pChain, uint32_t offFile, PCRTFSFATVOL pVol)
+{
+ uint32_t idxCluster = offFile >> pChain->cClusterByteShift;
+ if (idxCluster < pChain->cClusters)
+ {
+ PRTFSFATCHAINPART pPart = RTListGetFirst(&pChain->ListParts, RTFSFATCHAINPART, ListEntry);
+ while (idxCluster >= RTFSFATCHAINPART_ENTRIES)
+ {
+ idxCluster -= RTFSFATCHAINPART_ENTRIES;
+ pPart = RTListGetNext(&pChain->ListParts, pPart, RTFSFATCHAINPART, ListEntry);
+ }
+ return pVol->offFirstCluster
+ + ((uint64_t)(pPart->aEntries[idxCluster] - FAT_FIRST_DATA_CLUSTER) << pChain->cClusterByteShift)
+ + (offFile & (pChain->cbCluster - 1));
+ }
+ return UINT64_MAX;
+}
+
+
+/**
+ * Checks if the cluster chain is contiguous on the disk.
+ *
+ * @returns true / false.
+ * @param pChain The chain.
+ */
+static bool rtFsFatChain_IsContiguous(PCRTFSFATCHAIN pChain)
+{
+ if (pChain->cClusters <= 1)
+ return true;
+
+ PRTFSFATCHAINPART pPart = RTListGetFirst(&pChain->ListParts, RTFSFATCHAINPART, ListEntry);
+ uint32_t idxNext = pPart->aEntries[0];
+ uint32_t cLeft = pChain->cClusters;
+ for (;;)
+ {
+ uint32_t const cInPart = RT_MIN(cLeft, RTFSFATCHAINPART_ENTRIES);
+ for (uint32_t iPart = 0; iPart < cInPart; iPart++)
+ if (pPart->aEntries[iPart] == idxNext)
+ idxNext++;
+ else
+ return false;
+ cLeft -= cInPart;
+ if (!cLeft)
+ return true;
+ pPart = RTListGetNext(&pChain->ListParts, pPart, RTFSFATCHAINPART, ListEntry);
+ }
+}
+
+
+/**
+ * Gets a cluster array index.
+ *
+ * This works the chain thing as an indexed array.
+ *
+ * @returns The cluster number, UINT32_MAX if out of bounds.
+ * @param pChain The chain.
+ * @param idx The index.
+ */
+static uint32_t rtFsFatChain_GetClusterByIndex(PCRTFSFATCHAIN pChain, uint32_t idx)
+{
+ if (idx < pChain->cClusters)
+ {
+ /*
+ * In the first part?
+ */
+ PRTFSFATCHAINPART pPart;
+ if (idx < RTFSFATCHAINPART_ENTRIES)
+ {
+ pPart = RTListGetFirst(&pChain->ListParts, RTFSFATCHAINPART, ListEntry);
+ return pPart->aEntries[idx];
+ }
+
+ /*
+ * In the last part?
+ */
+ uint32_t cParts = (pChain->cClusters + RTFSFATCHAINPART_ENTRIES - 1) / RTFSFATCHAINPART_ENTRIES;
+ uint32_t idxPart = idx / RTFSFATCHAINPART_ENTRIES;
+ uint32_t idxInPart = idx % RTFSFATCHAINPART_ENTRIES;
+ if (idxPart + 1 == cParts)
+ pPart = RTListGetLast(&pChain->ListParts, RTFSFATCHAINPART, ListEntry);
+ else
+ {
+ /*
+ * No, do linear search from the start, skipping the first part.
+ */
+ pPart = RTListGetFirst(&pChain->ListParts, RTFSFATCHAINPART, ListEntry);
+ while (idxPart-- > 0)
+ pPart = RTListGetNext(&pChain->ListParts, pPart, RTFSFATCHAINPART, ListEntry);
+ }
+
+ return pPart->aEntries[idxInPart];
+ }
+ return UINT32_MAX;
+}
+
+
+/**
+ * Gets the first cluster.
+ *
+ * @returns The cluster number, UINT32_MAX if empty
+ * @param pChain The chain.
+ */
+static uint32_t rtFsFatChain_GetFirstCluster(PCRTFSFATCHAIN pChain)
+{
+ if (pChain->cClusters > 0)
+ {
+ PRTFSFATCHAINPART pPart = RTListGetFirst(&pChain->ListParts, RTFSFATCHAINPART, ListEntry);
+ return pPart->aEntries[0];
+ }
+ return UINT32_MAX;
+}
+
+
+
+/**
+ * Gets the last cluster.
+ *
+ * @returns The cluster number, UINT32_MAX if empty
+ * @param pChain The chain.
+ */
+static uint32_t rtFsFatChain_GetLastCluster(PCRTFSFATCHAIN pChain)
+{
+ if (pChain->cClusters > 0)
+ {
+ PRTFSFATCHAINPART pPart = RTListGetLast(&pChain->ListParts, RTFSFATCHAINPART, ListEntry);
+ return pPart->aEntries[(pChain->cClusters - 1) % RTFSFATCHAINPART_ENTRIES];
+ }
+ return UINT32_MAX;
+}
+
+
+/**
+ * Creates a cache for the file allocation table (cluster map).
+ *
+ * @returns Pointer to the cache.
+ * @param pThis The FAT volume instance.
+ * @param pbFirst512FatBytes The first 512 bytes of the first FAT.
+ */
+static int rtFsFatClusterMap_Create(PRTFSFATVOL pThis, uint8_t const *pbFirst512FatBytes, PRTERRINFO pErrInfo)
+{
+ Assert(RT_ALIGN_32(pThis->cbFat, pThis->cbSector) == pThis->cbFat);
+ Assert(pThis->cbFat != 0);
+
+ /*
+ * Figure the cache size. Keeping it _very_ simple for now as we just need
+ * something that works, not anything the performs like crazy.
+ *
+ * Note! Lowering the max cache size below 128KB will break ASSUMPTIONS in the FAT16
+ * and eventually FAT12 code.
+ */
+ uint32_t cEntries;
+ uint32_t cEntryIndexShift;
+ uint32_t fEntryIndexMask;
+ uint32_t cbEntry = pThis->cbFat;
+ uint32_t fEntryOffsetMask;
+ if (cbEntry <= _512K)
+ {
+ cEntries = 1;
+ cEntryIndexShift = 0;
+ fEntryIndexMask = 0;
+ fEntryOffsetMask = UINT32_MAX;
+ }
+ else
+ {
+ Assert(pThis->cbSector < _512K / 8);
+ cEntries = 8;
+ cEntryIndexShift = 9;
+ fEntryIndexMask = cEntries - 1;
+ AssertReturn(RT_IS_POWER_OF_TWO(cEntries), VERR_INTERNAL_ERROR_4);
+
+ cbEntry = pThis->cbSector;
+ fEntryOffsetMask = pThis->cbSector - 1;
+ AssertReturn(RT_IS_POWER_OF_TWO(cbEntry), VERR_INTERNAL_ERROR_5);
+ }
+
+ /*
+ * Allocate and initialize it all.
+ */
+ PRTFSFATCLUSTERMAPCACHE pFatCache;
+ pFatCache = (PRTFSFATCLUSTERMAPCACHE)RTMemAllocZ(RT_UOFFSETOF_DYN(RTFSFATCLUSTERMAPCACHE, aEntries[cEntries]));
+ pThis->pFatCache = pFatCache;
+ if (!pFatCache)
+ return RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "Failed to allocate FAT cache");
+ pFatCache->cEntries = cEntries;
+ pFatCache->fEntryIndexMask = fEntryIndexMask;
+ pFatCache->cEntryIndexShift = cEntryIndexShift;
+ pFatCache->cbEntry = cbEntry;
+ pFatCache->fEntryOffsetMask = fEntryOffsetMask;
+ pFatCache->pVol = pThis;
+ pFatCache->cbFat = pThis->cbFat;
+ pFatCache->cClusters = pThis->cClusters;
+
+ unsigned i = cEntries;
+ while (i-- > 0)
+ {
+ pFatCache->aEntries[i].pbData = (uint8_t *)RTMemAlloc(cbEntry);
+ if (pFatCache->aEntries[i].pbData == NULL)
+ {
+ for (i++; i < cEntries; i++)
+ RTMemFree(pFatCache->aEntries[i].pbData);
+ RTMemFree(pFatCache);
+ return RTErrInfoSetF(pErrInfo, VERR_NO_MEMORY, "Failed to allocate FAT cache entry (%#x bytes)", cbEntry);
+ }
+
+ pFatCache->aEntries[i].offFat = UINT32_MAX;
+ pFatCache->aEntries[i].bmDirty = 0;
+ }
+ Log3(("rtFsFatClusterMap_Create: cbFat=%#RX32 cEntries=%RU32 cEntryIndexShift=%RU32 fEntryIndexMask=%#RX32\n",
+ pFatCache->cbFat, pFatCache->cEntries, pFatCache->cEntryIndexShift, pFatCache->fEntryIndexMask));
+ Log3(("rtFsFatClusterMap_Create: cbEntries=%#RX32 fEntryOffsetMask=%#RX32\n", pFatCache->cbEntry, pFatCache->fEntryOffsetMask));
+
+ /*
+ * Calc the dirty shift factor.
+ */
+ cbEntry /= 64;
+ if (cbEntry < pThis->cbSector)
+ cbEntry = pThis->cbSector;
+
+ pFatCache->cDirtyShift = 1;
+ pFatCache->cbDirtyLine = 1;
+ while (pFatCache->cbDirtyLine < cbEntry)
+ {
+ pFatCache->cDirtyShift++;
+ pFatCache->cbDirtyLine <<= 1;
+ }
+ Assert(pFatCache->cEntries == 1 || pFatCache->cbDirtyLine == pThis->cbSector);
+ Log3(("rtFsFatClusterMap_Create: cbDirtyLine=%#RX32 cDirtyShift=%u\n", pFatCache->cbDirtyLine, pFatCache->cDirtyShift));
+
+ /*
+ * Fill the cache if single entry or entry size is 512.
+ */
+ if (pFatCache->cEntries == 1 || pFatCache->cbEntry == 512)
+ {
+ memcpy(pFatCache->aEntries[0].pbData, pbFirst512FatBytes, RT_MIN(512, pFatCache->cbEntry));
+ if (pFatCache->cbEntry > 512)
+ {
+ int rc = RTVfsFileReadAt(pThis->hVfsBacking, pThis->aoffFats[0] + 512,
+ &pFatCache->aEntries[0].pbData[512], pFatCache->cbEntry - 512, NULL);
+ if (RT_FAILURE(rc))
+ return RTErrInfoSet(pErrInfo, rc, "Error reading FAT into memory");
+ }
+ pFatCache->aEntries[0].offFat = 0;
+ pFatCache->aEntries[0].bmDirty = 0;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for rtFsFatClusterMap_Flush and rtFsFatClusterMap_FlushEntry.
+ *
+ * @returns IPRT status code. On failure, we're currently kind of screwed.
+ * @param pThis The FAT volume instance.
+ * @param iFirstEntry Entry to start flushing at.
+ * @param iLastEntry Last entry to flush.
+ */
+static int rtFsFatClusterMap_FlushWorker(PRTFSFATVOL pThis, uint32_t const iFirstEntry, uint32_t const iLastEntry)
+{
+ PRTFSFATCLUSTERMAPCACHE pFatCache = pThis->pFatCache;
+ Log3(("rtFsFatClusterMap_FlushWorker: %p %#x %#x\n", pThis, iFirstEntry, iLastEntry));
+
+ /*
+ * Walk the cache entries, accumulating segments to flush.
+ */
+ int rc = VINF_SUCCESS;
+ uint64_t off = UINT64_MAX;
+ uint64_t offEdge = UINT64_MAX;
+ RTSGSEG aSgSegs[8];
+ RT_ZERO(aSgSegs); /* Initialization required for GCC >= 11. */
+ RTSGBUF SgBuf;
+ RTSgBufInit(&SgBuf, aSgSegs, RT_ELEMENTS(aSgSegs));
+ SgBuf.cSegs = 0; /** @todo RTSgBuf API is stupid, make it smarter. */
+
+ for (uint32_t iFatCopy = 0; iFatCopy < pThis->cFats; iFatCopy++)
+ {
+ for (uint32_t iEntry = iFirstEntry; iEntry <= iLastEntry; iEntry++)
+ {
+ uint64_t bmDirty = pFatCache->aEntries[iEntry].bmDirty;
+ if ( bmDirty != 0
+ && pFatCache->aEntries[iEntry].offFat != UINT32_MAX)
+ {
+ uint32_t offEntry = 0;
+ uint64_t iDirtyLine = 1;
+ while (offEntry < pFatCache->cbEntry)
+ {
+ if (pFatCache->aEntries[iEntry].bmDirty & iDirtyLine)
+ {
+ /*
+ * Found dirty cache line.
+ */
+ uint64_t offDirtyLine = pThis->aoffFats[iFatCopy] + pFatCache->aEntries[iEntry].offFat + offEntry;
+
+ /* Can we simply extend the last segment? */
+ if ( offDirtyLine == offEdge
+ && offEntry)
+ {
+ Assert(SgBuf.cSegs > 0);
+ Assert( (uintptr_t)aSgSegs[SgBuf.cSegs - 1].pvSeg + aSgSegs[SgBuf.cSegs - 1].cbSeg
+ == (uintptr_t)&pFatCache->aEntries[iEntry].pbData[offEntry]);
+ aSgSegs[SgBuf.cSegs - 1].cbSeg += pFatCache->cbDirtyLine;
+ offEdge += pFatCache->cbDirtyLine;
+ }
+ else
+ {
+ /* Starting new job? */
+ if (off == UINT64_MAX)
+ {
+ off = offDirtyLine;
+ Assert(SgBuf.cSegs == 0);
+ }
+ /* flush if not adjacent or if we're out of segments. */
+ else if ( offDirtyLine != offEdge
+ || SgBuf.cSegs >= RT_ELEMENTS(aSgSegs))
+ {
+ int rc2 = RTVfsFileSgWrite(pThis->hVfsBacking, off, &SgBuf, true /*fBlocking*/, NULL);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ RTSgBufReset(&SgBuf);
+ SgBuf.cSegs = 0;
+ off = offDirtyLine;
+ }
+
+ /* Append segment. */
+ aSgSegs[SgBuf.cSegs].cbSeg = pFatCache->cbDirtyLine;
+ aSgSegs[SgBuf.cSegs].pvSeg = &pFatCache->aEntries[iEntry].pbData[offEntry];
+ SgBuf.cSegs++;
+ offEdge = offDirtyLine + pFatCache->cbDirtyLine;
+ }
+
+ bmDirty &= ~iDirtyLine;
+ if (!bmDirty)
+ break;
+ }
+ iDirtyLine <<= 1;
+ offEntry += pFatCache->cbDirtyLine;
+ }
+ Assert(!bmDirty);
+ }
+ }
+ }
+
+ /*
+ * Final flush job.
+ */
+ if (SgBuf.cSegs > 0)
+ {
+ int rc2 = RTVfsFileSgWrite(pThis->hVfsBacking, off, &SgBuf, true /*fBlocking*/, NULL);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ /*
+ * Clear the dirty flags on success.
+ */
+ if (RT_SUCCESS(rc))
+ for (uint32_t iEntry = iFirstEntry; iEntry <= iLastEntry; iEntry++)
+ pFatCache->aEntries[iEntry].bmDirty = 0;
+
+ return rc;
+}
+
+
+/**
+ * Flushes out all dirty lines in the entire file allocation table cache.
+ *
+ * @returns IPRT status code. On failure, we're currently kind of screwed.
+ * @param pThis The FAT volume instance.
+ */
+static int rtFsFatClusterMap_Flush(PRTFSFATVOL pThis)
+{
+ return rtFsFatClusterMap_FlushWorker(pThis, 0, pThis->pFatCache->cEntries - 1);
+}
+
+
+/**
+ * Flushes out all dirty lines in the file allocation table (cluster map) cache
+ * entry.
+ *
+ * This is typically called prior to reusing the cache entry.
+ *
+ * @returns IPRT status code. On failure, we're currently kind of screwed.
+ * @param pFatCache The FAT cache
+ * @param iEntry The cache entry to flush.
+ */
+static int rtFsFatClusterMap_FlushEntry(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t iEntry)
+{
+ return rtFsFatClusterMap_FlushWorker(pFatCache->pVol, iEntry, iEntry);
+}
+
+
+/**
+ * Gets a pointer to a FAT entry.
+ *
+ * @returns IPRT status code. On failure, we're currently kind of screwed.
+ * @param pFatCache The FAT cache.
+ * @param offFat The FAT byte offset to get the entry off.
+ * @param ppbEntry Where to return the pointer to the entry.
+ */
+static int rtFsFatClusterMap_GetEntry(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t offFat, uint8_t **ppbEntry)
+{
+ int rc;
+ if (offFat < pFatCache->cbFat)
+ {
+ uint32_t const iEntry = (offFat >> pFatCache->cEntryIndexShift) & pFatCache->fEntryIndexMask;
+ uint32_t const offInEntry = offFat & pFatCache->fEntryOffsetMask;
+ uint32_t const offFatEntry = offFat - offInEntry;
+
+ *ppbEntry = pFatCache->aEntries[iEntry].pbData + offInEntry;
+
+ /* If it's already ready, return immediately. */
+ if (pFatCache->aEntries[iEntry].offFat == offFatEntry)
+ {
+ Log3(("rtFsFatClusterMap_GetEntry: Hit entry %u for offFat=%#RX32\n", iEntry, offFat));
+ return VINF_SUCCESS;
+ }
+
+ /* Do we need to flush it? */
+ rc = VINF_SUCCESS;
+ if ( pFatCache->aEntries[iEntry].bmDirty != 0
+ && pFatCache->aEntries[iEntry].offFat != UINT32_MAX)
+ {
+ Log3(("rtFsFatClusterMap_GetEntry: Flushing entry %u for offFat=%#RX32\n", iEntry, offFat));
+ rc = rtFsFatClusterMap_FlushEntry(pFatCache, iEntry);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pFatCache->aEntries[iEntry].bmDirty = 0;
+
+ /* Read in the entry from disk */
+ rc = RTVfsFileReadAt(pFatCache->pVol->hVfsBacking, pFatCache->pVol->aoffFats[0] + offFatEntry,
+ pFatCache->aEntries[iEntry].pbData, pFatCache->cbEntry, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ Log3(("rtFsFatClusterMap_GetEntry: Loaded entry %u for offFat=%#RX32\n", iEntry, offFat));
+ pFatCache->aEntries[iEntry].offFat = offFatEntry;
+ return VINF_SUCCESS;
+ }
+ /** @todo We can try other FAT copies here... */
+ LogRel(("rtFsFatClusterMap_GetEntry: Error loading entry %u for offFat=%#RX32 (%#64RX32 LB %#x): %Rrc\n",
+ iEntry, offFat, pFatCache->pVol->aoffFats[0] + offFatEntry, pFatCache->cbEntry, rc));
+ pFatCache->aEntries[iEntry].offFat = UINT32_MAX;
+ }
+ }
+ else
+ rc = VERR_OUT_OF_RANGE;
+ *ppbEntry = NULL;
+ return rc;
+}
+
+
+/**
+ * Gets a pointer to a FAT entry, extended version.
+ *
+ * @returns IPRT status code. On failure, we're currently kind of screwed.
+ * @param pFatCache The FAT cache.
+ * @param offFat The FAT byte offset to get the entry off.
+ * @param ppbEntry Where to return the pointer to the entry.
+ * @param pidxEntry Where to return the entry index.
+ */
+static int rtFsFatClusterMap_GetEntryEx(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t offFat,
+ uint8_t **ppbEntry, uint32_t *pidxEntry)
+{
+ int rc;
+ if (offFat < pFatCache->cbFat)
+ {
+ uint32_t const iEntry = (offFat >> pFatCache->cEntryIndexShift) & pFatCache->fEntryIndexMask;
+ uint32_t const offInEntry = offFat & pFatCache->fEntryOffsetMask;
+ uint32_t const offFatEntry = offFat - offInEntry;
+
+ *ppbEntry = pFatCache->aEntries[iEntry].pbData + offInEntry;
+ *pidxEntry = iEntry;
+
+ /* If it's already ready, return immediately. */
+ if (pFatCache->aEntries[iEntry].offFat == offFatEntry)
+ {
+ Log3(("rtFsFatClusterMap_GetEntryEx: Hit entry %u for offFat=%#RX32\n", iEntry, offFat));
+ return VINF_SUCCESS;
+ }
+
+ /* Do we need to flush it? */
+ rc = VINF_SUCCESS;
+ if ( pFatCache->aEntries[iEntry].bmDirty != 0
+ && pFatCache->aEntries[iEntry].offFat != UINT32_MAX)
+ {
+ Log3(("rtFsFatClusterMap_GetEntryEx: Flushing entry %u for offFat=%#RX32\n", iEntry, offFat));
+ rc = rtFsFatClusterMap_FlushEntry(pFatCache, iEntry);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pFatCache->aEntries[iEntry].bmDirty = 0;
+
+ /* Read in the entry from disk */
+ rc = RTVfsFileReadAt(pFatCache->pVol->hVfsBacking, pFatCache->pVol->aoffFats[0] + offFatEntry,
+ pFatCache->aEntries[iEntry].pbData, pFatCache->cbEntry, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ Log3(("rtFsFatClusterMap_GetEntryEx: Loaded entry %u for offFat=%#RX32\n", iEntry, offFat));
+ pFatCache->aEntries[iEntry].offFat = offFatEntry;
+ return VINF_SUCCESS;
+ }
+ /** @todo We can try other FAT copies here... */
+ LogRel(("rtFsFatClusterMap_GetEntryEx: Error loading entry %u for offFat=%#RX32 (%#64RX32 LB %#x): %Rrc\n",
+ iEntry, offFat, pFatCache->pVol->aoffFats[0] + offFatEntry, pFatCache->cbEntry, rc));
+ pFatCache->aEntries[iEntry].offFat = UINT32_MAX;
+ }
+ }
+ else
+ rc = VERR_OUT_OF_RANGE;
+ *ppbEntry = NULL;
+ *pidxEntry = UINT32_MAX;
+ return rc;
+}
+
+
+/**
+ * Destroys the file allcation table cache, first flushing any dirty lines.
+ *
+ * @returns IRPT status code from flush (we've destroyed it regardless of the
+ * status code).
+ * @param pThis The FAT volume instance which cluster map shall be
+ * destroyed.
+ */
+static int rtFsFatClusterMap_Destroy(PRTFSFATVOL pThis)
+{
+ int rc = VINF_SUCCESS;
+ PRTFSFATCLUSTERMAPCACHE pFatCache = pThis->pFatCache;
+ if (pFatCache)
+ {
+ /* flush stuff. */
+ rc = rtFsFatClusterMap_Flush(pThis);
+
+ /* free everything. */
+ uint32_t i = pFatCache->cEntries;
+ while (i-- > 0)
+ {
+ RTMemFree(pFatCache->aEntries[i].pbData);
+ pFatCache->aEntries[i].pbData = NULL;
+ }
+ pFatCache->cEntries = 0;
+ RTMemFree(pFatCache);
+
+ pThis->pFatCache = NULL;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Worker for rtFsFatClusterMap_ReadClusterChain handling FAT12.
+ */
+static int rtFsFatClusterMap_Fat12_ReadClusterChain(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxCluster, PRTFSFATCHAIN pChain)
+{
+ /* ASSUME that for FAT12 we cache the whole FAT in a single entry. That
+ way we don't need to deal with entries in different sectors and whatnot. */
+ AssertReturn(pFatCache->cEntries == 1, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pFatCache->cbEntry == pFatCache->cbFat, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pFatCache->aEntries[0].offFat == 0, VERR_INTERNAL_ERROR_4);
+
+ /* Special case for empty files. */
+ if (idxCluster == 0)
+ return VINF_SUCCESS;
+
+ /* Work cluster by cluster. */
+ uint8_t const *pbFat = pFatCache->aEntries[0].pbData;
+ for (;;)
+ {
+ /* Validate the cluster, checking for end of file. */
+ if ((uint32_t)(idxCluster - FAT_FIRST_DATA_CLUSTER) >= pFatCache->cClusters)
+ {
+ if (idxCluster >= FAT_FIRST_FAT12_EOC)
+ return VINF_SUCCESS;
+ Log(("Fat/ReadChain12: bogus cluster %#x vs %#x total\n", idxCluster, pFatCache->cClusters));
+ return VERR_VFS_BOGUS_OFFSET;
+ }
+
+ /* Add cluster to chain. */
+ int rc = rtFsFatChain_Append(pChain, idxCluster);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Next cluster. */
+#ifdef LOG_ENABLED
+ const uint32_t idxPrevCluster = idxCluster;
+#endif
+ bool fOdd = idxCluster & 1;
+ uint32_t offFat = idxCluster * 3 / 2;
+ idxCluster = RT_MAKE_U16(pbFat[offFat], pbFat[offFat + 1]);
+ if (fOdd)
+ idxCluster >>= 4;
+ else
+ idxCluster &= 0x0fff;
+ Log4(("Fat/ReadChain12: [%#x] %#x (next: %#x)\n", pChain->cClusters - 1, idxPrevCluster, idxCluster));
+ }
+}
+
+
+/**
+ * Worker for rtFsFatClusterMap_ReadClusterChain handling FAT16.
+ */
+static int rtFsFatClusterMap_Fat16_ReadClusterChain(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxCluster, PRTFSFATCHAIN pChain)
+{
+ /* ASSUME that for FAT16 we cache the whole FAT in a single entry. That
+ way we don't need to deal with entries in different sectors and whatnot. */
+ AssertReturn(pFatCache->cEntries == 1, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pFatCache->cbEntry == pFatCache->cbFat, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pFatCache->aEntries[0].offFat == 0, VERR_INTERNAL_ERROR_4);
+
+ /* Special case for empty files. */
+ if (idxCluster == 0)
+ return VINF_SUCCESS;
+
+ /* Work cluster by cluster. */
+ uint8_t const *pbFat = pFatCache->aEntries[0].pbData;
+ for (;;)
+ {
+ /* Validate the cluster, checking for end of file. */
+ if ((uint32_t)(idxCluster - FAT_FIRST_DATA_CLUSTER) >= pFatCache->cClusters)
+ {
+ if (idxCluster >= FAT_FIRST_FAT16_EOC)
+ return VINF_SUCCESS;
+ Log(("Fat/ReadChain16: bogus cluster %#x vs %#x total\n", idxCluster, pFatCache->cClusters));
+ return VERR_VFS_BOGUS_OFFSET;
+ }
+
+ /* Add cluster to chain. */
+ int rc = rtFsFatChain_Append(pChain, idxCluster);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Next cluster. */
+ idxCluster = RT_MAKE_U16(pbFat[idxCluster * 2], pbFat[idxCluster * 2 + 1]);
+ }
+}
+
+
+/**
+ * Worker for rtFsFatClusterMap_ReadClusterChain handling FAT32.
+ */
+static int rtFsFatClusterMap_Fat32_ReadClusterChain(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxCluster, PRTFSFATCHAIN pChain)
+{
+ /* Special case for empty files. */
+ if (idxCluster == 0)
+ return VINF_SUCCESS;
+
+ /* Work cluster by cluster. */
+ for (;;)
+ {
+ /* Validate the cluster, checking for end of file. */
+ if ((uint32_t)(idxCluster - FAT_FIRST_DATA_CLUSTER) >= pFatCache->cClusters)
+ {
+ if (idxCluster >= FAT_FIRST_FAT32_EOC)
+ return VINF_SUCCESS;
+ Log(("Fat/ReadChain32: bogus cluster %#x vs %#x total\n", idxCluster, pFatCache->cClusters));
+ return VERR_VFS_BOGUS_OFFSET;
+ }
+
+ /* Add cluster to chain. */
+ int rc = rtFsFatChain_Append(pChain, idxCluster);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Get the next cluster. */
+ uint8_t *pbEntry;
+ rc = rtFsFatClusterMap_GetEntry(pFatCache, idxCluster * 4, &pbEntry);
+ if (RT_SUCCESS(rc))
+ idxCluster = RT_MAKE_U32_FROM_U8(pbEntry[0], pbEntry[1], pbEntry[2], pbEntry[3]);
+ else
+ return rc;
+ }
+}
+
+
+/**
+ * Reads a cluster chain into memory
+ *
+ * @returns IPRT status code.
+ * @param pThis The FAT volume instance.
+ * @param idxFirstCluster The first cluster.
+ * @param pChain The chain element to read into (and thereby
+ * initialize).
+ */
+static int rtFsFatClusterMap_ReadClusterChain(PRTFSFATVOL pThis, uint32_t idxFirstCluster, PRTFSFATCHAIN pChain)
+{
+ pChain->cbCluster = pThis->cbCluster;
+ pChain->cClusterByteShift = pThis->cClusterByteShift;
+ pChain->cClusters = 0;
+ pChain->cbChain = 0;
+ RTListInit(&pChain->ListParts);
+ switch (pThis->enmFatType)
+ {
+ case RTFSFATTYPE_FAT12: return rtFsFatClusterMap_Fat12_ReadClusterChain(pThis->pFatCache, idxFirstCluster, pChain);
+ case RTFSFATTYPE_FAT16: return rtFsFatClusterMap_Fat16_ReadClusterChain(pThis->pFatCache, idxFirstCluster, pChain);
+ case RTFSFATTYPE_FAT32: return rtFsFatClusterMap_Fat32_ReadClusterChain(pThis->pFatCache, idxFirstCluster, pChain);
+ default:
+ AssertFailedReturn(VERR_INTERNAL_ERROR_2);
+ }
+}
+
+
+/**
+ * Sets bmDirty for entry @a iEntry.
+ *
+ * @param pFatCache The FAT cache.
+ * @param iEntry The cache entry.
+ * @param offEntry The offset into the cache entry that was dirtied.
+ */
+DECLINLINE(void) rtFsFatClusterMap_SetDirtyByte(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t iEntry, uint32_t offEntry)
+{
+ uint8_t iLine = offEntry / pFatCache->cbDirtyLine;
+ pFatCache->aEntries[iEntry].bmDirty |= RT_BIT_64(iLine);
+}
+
+/**
+ * Sets bmDirty for entry @a iEntry.
+ *
+ * @param pFatCache The FAT cache.
+ * @param iEntry The cache entry.
+ * @param pbIntoEntry Pointer into the cache entry that was dirtied.
+ */
+DECLINLINE(void) rtFsFatClusterMap_SetDirtyByteByPtr(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t iEntry, uint8_t *pbIntoEntry)
+{
+ uintptr_t offEntry = pbIntoEntry - pFatCache->aEntries[iEntry].pbData;
+ Assert(offEntry < pFatCache->cbEntry);
+ rtFsFatClusterMap_SetDirtyByte(pFatCache, iEntry, (uint32_t)offEntry);
+}
+
+
+/** Sets a FAT12 cluster value. */
+static int rtFsFatClusterMap_SetCluster12(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxCluster, uint32_t uValue)
+{
+ /* ASSUME that for FAT12 we cache the whole FAT in a single entry. That
+ way we don't need to deal with entries in different sectors and whatnot. */
+ AssertReturn(pFatCache->cEntries == 1, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pFatCache->cbEntry == pFatCache->cbFat, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pFatCache->aEntries[0].offFat == 0, VERR_INTERNAL_ERROR_4);
+ AssertReturn(uValue < 0x1000, VERR_INTERNAL_ERROR_2);
+
+ /* Make the change. */
+ uint8_t *pbFat = pFatCache->aEntries[0].pbData;
+ uint32_t offFat = idxCluster * 3 / 2;
+ if (idxCluster & 1)
+ {
+ Log3(("Fat/SetCluster12: [%#x]: %#x -> %#x\n", idxCluster, (((pbFat[offFat]) & 0xf0) >> 4) | ((unsigned)pbFat[offFat + 1] << 4), uValue));
+ pbFat[offFat] = ((uint8_t)0x0f & pbFat[offFat]) | ((uint8_t)uValue << 4);
+ pbFat[offFat + 1] = (uint8_t)(uValue >> 4);
+ }
+ else
+ {
+ Log3(("Fat/SetCluster12: [%#x]: %#x -> %#x\n", idxCluster, pbFat[offFat] | ((pbFat[offFat + 1] & 0x0f) << 8), uValue));
+ pbFat[offFat] = (uint8_t)uValue;
+ pbFat[offFat + 1] = ((uint8_t)0xf0 & pbFat[offFat + 1]) | (uint8_t)(uValue >> 8);
+ }
+
+ /* Update the dirty bits. */
+ rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFat);
+ rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFat + 1);
+
+ return VINF_SUCCESS;
+}
+
+
+/** Sets a FAT16 cluster value. */
+static int rtFsFatClusterMap_SetCluster16(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxCluster, uint32_t uValue)
+{
+ /* ASSUME that for FAT16 we cache the whole FAT in a single entry. */
+ AssertReturn(pFatCache->cEntries == 1, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pFatCache->cbEntry == pFatCache->cbFat, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pFatCache->aEntries[0].offFat == 0, VERR_INTERNAL_ERROR_4);
+ AssertReturn(uValue < 0x10000, VERR_INTERNAL_ERROR_2);
+
+ /* Make the change. */
+ uint8_t *pbFat = pFatCache->aEntries[0].pbData;
+ uint32_t offFat = idxCluster * 2;
+ pbFat[offFat] = (uint8_t)idxCluster;
+ pbFat[offFat + 1] = (uint8_t)(idxCluster >> 8);
+
+ /* Update the dirty bits. */
+ rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFat);
+
+ return VINF_SUCCESS;
+}
+
+
+/** Sets a FAT32 cluster value. */
+static int rtFsFatClusterMap_SetCluster32(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxCluster, uint32_t uValue)
+{
+ AssertReturn(uValue < 0x10000000, VERR_INTERNAL_ERROR_2);
+
+ /* Get the fat cache entry. */
+ uint8_t *pbEntry;
+ uint32_t idxEntry;
+ int rc = rtFsFatClusterMap_GetEntryEx(pFatCache, idxCluster * 4, &pbEntry, &idxEntry);
+ if (RT_SUCCESS(rc))
+ {
+ /* Make the change. */
+ pbEntry[0] = (uint8_t)idxCluster;
+ pbEntry[1] = (uint8_t)(idxCluster >> 8);
+ pbEntry[2] = (uint8_t)(idxCluster >> 16);
+ pbEntry[3] = (uint8_t)(idxCluster >> 24);
+
+ /* Update the dirty bits. */
+ rtFsFatClusterMap_SetDirtyByteByPtr(pFatCache, idxEntry, pbEntry);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Marks the cluster @a idxCluster as the end of the cluster chain.
+ *
+ * @returns IPRT status code
+ * @param pThis The FAT volume instance.
+ * @param idxCluster The cluster to end the chain with.
+ */
+static int rtFsFatClusterMap_SetEndOfChain(PRTFSFATVOL pThis, uint32_t idxCluster)
+{
+ AssertReturn(idxCluster >= FAT_FIRST_DATA_CLUSTER, VERR_VFS_BOGUS_OFFSET);
+ AssertMsgReturn(idxCluster < pThis->cClusters, ("idxCluster=%#x cClusters=%#x\n", idxCluster, pThis->cClusters),
+ VERR_VFS_BOGUS_OFFSET);
+ switch (pThis->enmFatType)
+ {
+ case RTFSFATTYPE_FAT12: return rtFsFatClusterMap_SetCluster12(pThis->pFatCache, idxCluster, FAT_FIRST_FAT12_EOC);
+ case RTFSFATTYPE_FAT16: return rtFsFatClusterMap_SetCluster16(pThis->pFatCache, idxCluster, FAT_FIRST_FAT16_EOC);
+ case RTFSFATTYPE_FAT32: return rtFsFatClusterMap_SetCluster32(pThis->pFatCache, idxCluster, FAT_FIRST_FAT32_EOC);
+ default: AssertFailedReturn(VERR_INTERNAL_ERROR_3);
+ }
+}
+
+
+/**
+ * Marks the cluster @a idxCluster as free.
+ * @returns IPRT status code
+ * @param pThis The FAT volume instance.
+ * @param idxCluster The cluster to free.
+ */
+static int rtFsFatClusterMap_FreeCluster(PRTFSFATVOL pThis, uint32_t idxCluster)
+{
+ AssertReturn(idxCluster >= FAT_FIRST_DATA_CLUSTER, VERR_VFS_BOGUS_OFFSET);
+ AssertReturn(idxCluster < pThis->cClusters, VERR_VFS_BOGUS_OFFSET);
+ switch (pThis->enmFatType)
+ {
+ case RTFSFATTYPE_FAT12: return rtFsFatClusterMap_SetCluster12(pThis->pFatCache, idxCluster, 0);
+ case RTFSFATTYPE_FAT16: return rtFsFatClusterMap_SetCluster16(pThis->pFatCache, idxCluster, 0);
+ case RTFSFATTYPE_FAT32: return rtFsFatClusterMap_SetCluster32(pThis->pFatCache, idxCluster, 0);
+ default: AssertFailedReturn(VERR_INTERNAL_ERROR_3);
+ }
+}
+
+
+/**
+ * Worker for rtFsFatClusterMap_AllocateCluster that handles FAT12.
+ */
+static int rtFsFatClusterMap_AllocateCluster12(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxPrevCluster, uint32_t *pidxCluster)
+{
+ /* ASSUME that for FAT12 we cache the whole FAT in a single entry. That
+ way we don't need to deal with entries in different sectors and whatnot. */
+ AssertReturn(pFatCache->cEntries == 1, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pFatCache->cbEntry == pFatCache->cbFat, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pFatCache->aEntries[0].offFat == 0, VERR_INTERNAL_ERROR_4);
+
+ /*
+ * Check that the previous cluster is a valid chain end.
+ */
+ uint8_t *pbFat = pFatCache->aEntries[0].pbData;
+ uint32_t offFatPrev;
+ if (idxPrevCluster != UINT32_MAX)
+ {
+ offFatPrev = idxPrevCluster * 3 / 2;
+ AssertReturn(offFatPrev + 1 < pFatCache->cbFat, VERR_INTERNAL_ERROR_3);
+ uint32_t idxPrevValue;
+ if (idxPrevCluster & 1)
+ idxPrevValue = (pbFat[offFatPrev] >> 4) | ((uint32_t)pbFat[offFatPrev + 1] << 4);
+ else
+ idxPrevValue = pbFat[offFatPrev] | ((uint32_t)(pbFat[offFatPrev + 1] & 0x0f) << 8);
+ AssertReturn(idxPrevValue >= FAT_FIRST_FAT12_EOC, VERR_VFS_BOGUS_OFFSET);
+ }
+ else
+ offFatPrev = UINT32_MAX;
+
+ /*
+ * Search cluster by cluster from the start (it's small, so easy trumps
+ * complicated optimizations).
+ */
+ uint32_t idxCluster = FAT_FIRST_DATA_CLUSTER;
+ uint32_t offFat = 3;
+ while (idxCluster < pFatCache->cClusters)
+ {
+ if (idxCluster & 1)
+ {
+ if ( (pbFat[offFat] & 0xf0) != 0
+ || pbFat[offFat + 1] != 0)
+ {
+ offFat += 2;
+ idxCluster++;
+ continue;
+ }
+
+ /* Set EOC. */
+ pbFat[offFat] |= 0xf0;
+ pbFat[offFat + 1] = 0xff;
+ }
+ else
+ {
+ if ( pbFat[offFat]
+ || pbFat[offFat + 1] & 0x0f)
+ {
+ offFat += 1;
+ idxCluster++;
+ continue;
+ }
+
+ /* Set EOC. */
+ pbFat[offFat] = 0xff;
+ pbFat[offFat + 1] |= 0x0f;
+ }
+
+ /* Update the dirty bits. */
+ rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFat);
+ rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFat + 1);
+
+ /* Chain it onto the previous cluster. */
+ if (idxPrevCluster != UINT32_MAX)
+ {
+ if (idxPrevCluster & 1)
+ {
+ pbFat[offFatPrev] = (pbFat[offFatPrev] & (uint8_t)0x0f) | (uint8_t)(idxCluster << 4);
+ pbFat[offFatPrev + 1] = (uint8_t)(idxCluster >> 4);
+ }
+ else
+ {
+ pbFat[offFatPrev] = (uint8_t)idxCluster;
+ pbFat[offFatPrev + 1] = (pbFat[offFatPrev + 1] & (uint8_t)0xf0) | ((uint8_t)(idxCluster >> 8) & (uint8_t)0x0f);
+ }
+ rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFatPrev);
+ rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFatPrev + 1);
+ }
+
+ *pidxCluster = idxCluster;
+ return VINF_SUCCESS;
+ }
+
+ return VERR_DISK_FULL;
+}
+
+
+/**
+ * Worker for rtFsFatClusterMap_AllocateCluster that handles FAT16.
+ */
+static int rtFsFatClusterMap_AllocateCluster16(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxPrevCluster, uint32_t *pidxCluster)
+{
+ /* ASSUME that for FAT16 we cache the whole FAT in a single entry. */
+ AssertReturn(pFatCache->cEntries == 1, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pFatCache->cbEntry == pFatCache->cbFat, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pFatCache->aEntries[0].offFat == 0, VERR_INTERNAL_ERROR_4);
+
+ /*
+ * Check that the previous cluster is a valid chain end.
+ */
+ uint8_t *pbFat = pFatCache->aEntries[0].pbData;
+ uint32_t offFatPrev;
+ if (idxPrevCluster != UINT32_MAX)
+ {
+ offFatPrev = idxPrevCluster * 2;
+ AssertReturn(offFatPrev + 1 < pFatCache->cbFat, VERR_INTERNAL_ERROR_3);
+ uint32_t idxPrevValue = RT_MAKE_U16(pbFat[offFatPrev], pbFat[offFatPrev + 1]);
+ AssertReturn(idxPrevValue >= FAT_FIRST_FAT16_EOC, VERR_VFS_BOGUS_OFFSET);
+ }
+ else
+ offFatPrev = UINT32_MAX;
+
+ /*
+ * We start searching at idxAllocHint and continues to the end. The next
+ * iteration starts searching from the start and up to idxAllocHint.
+ */
+ uint32_t idxCluster = RT_MIN(pFatCache->idxAllocHint, FAT_FIRST_DATA_CLUSTER);
+ uint32_t offFat = idxCluster * 2;
+ uint32_t cClusters = pFatCache->cClusters;
+ for (uint32_t i = 0; i < 2; i++)
+ {
+ while (idxCluster < cClusters)
+ {
+ if ( pbFat[offFat + 0] != 0x00
+ || pbFat[offFat + 1] != 0x00)
+ {
+ /* In use - advance to the next one. */
+ offFat += 2;
+ idxCluster++;
+ }
+ else
+ {
+ /*
+ * Found one. Grab it.
+ */
+ /* Set EOC. */
+ pbFat[offFat + 0] = 0xff;
+ pbFat[offFat + 1] = 0xff;
+ rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFat);
+
+ /* Chain it onto the previous cluster (if any). */
+ if (idxPrevCluster != UINT32_MAX)
+ {
+ pbFat[offFatPrev + 0] = (uint8_t)idxCluster;
+ pbFat[offFatPrev + 1] = (uint8_t)(idxCluster >> 8);
+ rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFatPrev);
+ }
+
+ /* Update the allocation hint. */
+ pFatCache->idxAllocHint = idxCluster + 1;
+
+ /* Done. */
+ *pidxCluster = idxCluster;
+ return VINF_SUCCESS;
+ }
+ }
+
+ /* Wrap around to the start of the map. */
+ cClusters = RT_MIN(pFatCache->idxAllocHint, pFatCache->cClusters);
+ idxCluster = FAT_FIRST_DATA_CLUSTER;
+ offFat = 4;
+ }
+
+ return VERR_DISK_FULL;
+}
+
+
+/**
+ * Worker for rtFsFatClusterMap_AllocateCluster that handles FAT32.
+ */
+static int rtFsFatClusterMap_AllocateCluster32(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxPrevCluster, uint32_t *pidxCluster)
+{
+ /*
+ * Check that the previous cluster is a valid chain end.
+ */
+ int rc;
+ uint8_t *pbEntry;
+ if (idxPrevCluster != UINT32_MAX)
+ {
+ rc = rtFsFatClusterMap_GetEntry(pFatCache, idxPrevCluster * 4, &pbEntry);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t idxPrevValue = RT_MAKE_U32_FROM_U8(pbEntry[0], pbEntry[1], pbEntry[2], pbEntry[3]);
+ AssertReturn(idxPrevValue >= FAT_FIRST_FAT32_EOC, VERR_VFS_BOGUS_OFFSET);
+ }
+ else
+ return rc;
+ }
+
+ /*
+ * We start searching at idxAllocHint and continues to the end. The next
+ * iteration starts searching from the start and up to idxAllocHint.
+ */
+ uint32_t idxCluster = RT_MIN(pFatCache->idxAllocHint, FAT_FIRST_DATA_CLUSTER);
+ uint32_t offFat = idxCluster * 4;
+ uint32_t cClusters = pFatCache->cClusters;
+ for (uint32_t i = 0; i < 2; i++)
+ {
+ while (idxCluster < cClusters)
+ {
+ /* Note! This could be done in cache entry chunks. */
+ uint32_t idxEntry;
+ rc = rtFsFatClusterMap_GetEntryEx(pFatCache, offFat, &pbEntry, &idxEntry);
+ if (RT_SUCCESS(rc))
+ {
+ if ( pbEntry[0] != 0x00
+ || pbEntry[1] != 0x00
+ || pbEntry[2] != 0x00
+ || pbEntry[3] != 0x00)
+ {
+ /* In use - advance to the next one. */
+ offFat += 4;
+ idxCluster++;
+ }
+ else
+ {
+ /*
+ * Found one. Grab it.
+ */
+ /* Set EOC. */
+ pbEntry[0] = 0xff;
+ pbEntry[1] = 0xff;
+ pbEntry[2] = 0xff;
+ pbEntry[3] = 0x0f;
+ rtFsFatClusterMap_SetDirtyByteByPtr(pFatCache, idxEntry, pbEntry);
+
+ /* Chain it on the previous cluster (if any). */
+ if (idxPrevCluster != UINT32_MAX)
+ {
+ rc = rtFsFatClusterMap_GetEntryEx(pFatCache, idxPrevCluster * 4, &pbEntry, &idxEntry);
+ if (RT_SUCCESS(rc))
+ {
+ pbEntry[0] = (uint8_t)idxCluster;
+ pbEntry[1] = (uint8_t)(idxCluster >> 8);
+ pbEntry[2] = (uint8_t)(idxCluster >> 16);
+ pbEntry[3] = (uint8_t)(idxCluster >> 24);
+ rtFsFatClusterMap_SetDirtyByteByPtr(pFatCache, idxEntry, pbEntry);
+ }
+ else
+ {
+ /* Try free the cluster. */
+ int rc2 = rtFsFatClusterMap_GetEntryEx(pFatCache, offFat, &pbEntry, &idxEntry);
+ if (RT_SUCCESS(rc2))
+ {
+ pbEntry[0] = 0;
+ pbEntry[1] = 0;
+ pbEntry[2] = 0;
+ pbEntry[3] = 0;
+ rtFsFatClusterMap_SetDirtyByteByPtr(pFatCache, idxEntry, pbEntry);
+ }
+ return rc;
+ }
+ }
+
+ /* Update the allocation hint. */
+ pFatCache->idxAllocHint = idxCluster + 1;
+
+ /* Done. */
+ *pidxCluster = idxCluster;
+ return VINF_SUCCESS;
+ }
+ }
+ }
+
+ /* Wrap around to the start of the map. */
+ cClusters = RT_MIN(pFatCache->idxAllocHint, pFatCache->cClusters);
+ idxCluster = FAT_FIRST_DATA_CLUSTER;
+ offFat = 4;
+ }
+
+ return VERR_DISK_FULL;
+}
+
+
+/**
+ * Allocates a cluster an appends it to the chain given by @a idxPrevCluster.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_DISK_FULL if no more available clusters.
+ * @param pThis The FAT volume instance.
+ * @param idxPrevCluster The previous cluster, UINT32_MAX if first.
+ * @param pidxCluster Where to return the cluster number on success.
+ */
+static int rtFsFatClusterMap_AllocateCluster(PRTFSFATVOL pThis, uint32_t idxPrevCluster, uint32_t *pidxCluster)
+{
+ AssertReturn(idxPrevCluster == UINT32_MAX || (idxPrevCluster >= FAT_FIRST_DATA_CLUSTER && idxPrevCluster < pThis->cClusters),
+ VERR_INTERNAL_ERROR_5);
+ *pidxCluster = UINT32_MAX;
+ switch (pThis->enmFatType)
+ {
+ case RTFSFATTYPE_FAT12: return rtFsFatClusterMap_AllocateCluster12(pThis->pFatCache, idxPrevCluster, pidxCluster);
+ case RTFSFATTYPE_FAT16: return rtFsFatClusterMap_AllocateCluster16(pThis->pFatCache, idxPrevCluster, pidxCluster);
+ case RTFSFATTYPE_FAT32: return rtFsFatClusterMap_AllocateCluster32(pThis->pFatCache, idxPrevCluster, pidxCluster);
+ default: AssertFailedReturn(VERR_INTERNAL_ERROR_3);
+ }
+}
+
+
+/**
+ * Allocates clusters.
+ *
+ * Will free the clusters if it fails to allocate all of them.
+ *
+ * @returns IPRT status code.
+ * @param pThis The FAT volume instance.
+ * @param pChain The chain.
+ * @param cClusters Number of clusters to add to the chain.
+ */
+static int rtFsFatClusterMap_AllocateMoreClusters(PRTFSFATVOL pThis, PRTFSFATCHAIN pChain, uint32_t cClusters)
+{
+ int rc = VINF_SUCCESS;
+ uint32_t const cOldClustersInChain = pChain->cClusters;
+ uint32_t const idxOldLastCluster = rtFsFatChain_GetLastCluster(pChain);
+ uint32_t idxPrevCluster = idxOldLastCluster;
+ uint32_t iCluster = 0;
+ while (iCluster < cClusters)
+ {
+ uint32_t idxCluster;
+ rc = rtFsFatClusterMap_AllocateCluster(pThis, idxPrevCluster, &idxCluster);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsFatChain_Append(pChain, idxCluster);
+ if (RT_SUCCESS(rc))
+ {
+ /* next */
+ iCluster++;
+ continue;
+ }
+
+ /* Bail out, freeing any clusters we've managed to allocate by now. */
+ rtFsFatClusterMap_FreeCluster(pThis, idxCluster);
+ }
+ if (idxOldLastCluster != UINT32_MAX)
+ rtFsFatClusterMap_SetEndOfChain(pThis, idxOldLastCluster);
+ while (iCluster-- > 0)
+ rtFsFatClusterMap_FreeCluster(pThis, rtFsFatChain_GetClusterByIndex(pChain, cOldClustersInChain + iCluster));
+ rtFsFatChain_Shrink(pChain, iCluster);
+ break;
+ }
+ return rc;
+}
+
+
+
+/**
+ * Converts a FAT timestamp into an IPRT timesspec.
+ *
+ * @param pTimeSpec Where to return the IRPT time.
+ * @param uDate The date part of the FAT timestamp.
+ * @param uTime The time part of the FAT timestamp.
+ * @param cCentiseconds Centiseconds part if applicable (0 otherwise).
+ * @param pVol The volume.
+ */
+static void rtFsFatDateTime2TimeSpec(PRTTIMESPEC pTimeSpec, uint16_t uDate, uint16_t uTime,
+ uint8_t cCentiseconds, PCRTFSFATVOL pVol)
+{
+ RTTIME Time;
+ Time.fFlags = RTTIME_FLAGS_TYPE_UTC;
+ Time.offUTC = 0;
+ Time.i32Year = 1980 + (uDate >> 9);
+ Time.u8Month = RT_MAX((uDate >> 5) & 0xf, 1);
+ Time.u8MonthDay = RT_MAX(uDate & 0x1f, 1);
+ Time.u8WeekDay = UINT8_MAX;
+ Time.u16YearDay = 0;
+ Time.u8Hour = uTime >> 11;
+ Time.u8Minute = (uTime >> 5) & 0x3f;
+ Time.u8Second = (uTime & 0x1f) << 1;
+ Time.u32Nanosecond = 0;
+ if (cCentiseconds > 0 && cCentiseconds < 200) /* screw complicated stuff for now. */
+ {
+ if (cCentiseconds >= 100)
+ {
+ cCentiseconds -= 100;
+ Time.u8Second++;
+ }
+ Time.u32Nanosecond = cCentiseconds * UINT64_C(100000000);
+ }
+
+ RTTimeImplode(pTimeSpec, RTTimeNormalize(&Time));
+ RTTimeSpecSubNano(pTimeSpec, pVol->offNanoUTC);
+}
+
+
+/**
+ * Converts an IPRT timespec to a FAT timestamp.
+ *
+ * @returns The centiseconds part.
+ * @param pVol The volume.
+ * @param pTimeSpec The IPRT timespec to convert (UTC).
+ * @param puDate Where to return the date part of the FAT timestamp.
+ * @param puTime Where to return the time part of the FAT timestamp.
+ */
+static uint8_t rtFsFatTimeSpec2FatDateTime(PCRTFSFATVOL pVol, PCRTTIMESPEC pTimeSpec, uint16_t *puDate, uint16_t *puTime)
+{
+ RTTIMESPEC TimeSpec = *pTimeSpec;
+ RTTIME Time;
+ RTTimeExplode(&Time, RTTimeSpecSubNano(&TimeSpec, pVol->offNanoUTC));
+
+ if (puDate)
+ *puDate = ((uint16_t)(RT_MAX(Time.i32Year, 1980) - 1980) << 9)
+ | (Time.u8Month << 5)
+ | Time.u8MonthDay;
+ if (puTime)
+ *puTime = ((uint16_t)Time.u8Hour << 11)
+ | (Time.u8Minute << 5)
+ | (Time.u8Second >> 1);
+ return (Time.u8Second & 1) * 100 + Time.u32Nanosecond / 10000000;
+
+}
+
+
+/**
+ * Gets the current FAT timestamp.
+ *
+ * @returns The centiseconds part.
+ * @param pVol The volume.
+ * @param puDate Where to return the date part of the FAT timestamp.
+ * @param puTime Where to return the time part of the FAT timestamp.
+ */
+static uint8_t rtFsFatCurrentFatDateTime(PCRTFSFATVOL pVol, uint16_t *puDate, uint16_t *puTime)
+{
+ RTTIMESPEC TimeSpec;
+ return rtFsFatTimeSpec2FatDateTime(pVol, RTTimeNow(&TimeSpec), puDate, puTime);
+}
+
+
+/**
+ * Initialization of a RTFSFATOBJ structure from a FAT directory entry.
+ *
+ * @note The RTFSFATOBJ::pParentDir and RTFSFATOBJ::Clusters members are
+ * properly initialized elsewhere.
+ *
+ * @param pObj The structure to initialize.
+ * @param pDirEntry The directory entry.
+ * @param offEntryInDir The offset in the parent directory.
+ * @param pVol The volume.
+ */
+static void rtFsFatObj_InitFromDirEntry(PRTFSFATOBJ pObj, PCFATDIRENTRY pDirEntry, uint32_t offEntryInDir, PRTFSFATVOL pVol)
+{
+ RTListInit(&pObj->Entry);
+ pObj->cRefs = 1;
+ pObj->pParentDir = NULL;
+ pObj->pVol = pVol;
+ pObj->offEntryInDir = offEntryInDir;
+ pObj->fAttrib = ((RTFMODE)pDirEntry->fAttrib << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_OS2;
+ pObj->fAttrib = rtFsModeFromDos(pObj->fAttrib, (char *)&pDirEntry->achName[0], sizeof(pDirEntry->achName), 0, 0);
+ pObj->cbObject = pDirEntry->cbFile;
+ pObj->fMaybeDirtyFat = false;
+ pObj->fMaybeDirtyDirEnt = false;
+ rtFsFatDateTime2TimeSpec(&pObj->ModificationTime, pDirEntry->uModifyDate, pDirEntry->uModifyTime, 0, pVol);
+ rtFsFatDateTime2TimeSpec(&pObj->BirthTime, pDirEntry->uBirthDate, pDirEntry->uBirthTime, pDirEntry->uBirthCentiseconds, pVol);
+ rtFsFatDateTime2TimeSpec(&pObj->AccessTime, pDirEntry->uAccessDate, 0, 0, pVol);
+}
+
+
+/**
+ * Dummy initialization of a RTFSFATOBJ structure.
+ *
+ * @note The RTFSFATOBJ::pParentDir and RTFSFATOBJ::Clusters members are
+ * properly initialized elsewhere.
+ *
+ * @param pObj The structure to initialize.
+ * @param cbObject The object size.
+ * @param fAttrib The attributes.
+ * @param pVol The volume.
+ */
+static void rtFsFatObj_InitDummy(PRTFSFATOBJ pObj, uint32_t cbObject, RTFMODE fAttrib, PRTFSFATVOL pVol)
+{
+ RTListInit(&pObj->Entry);
+ pObj->cRefs = 1;
+ pObj->pParentDir = NULL;
+ pObj->pVol = pVol;
+ pObj->offEntryInDir = UINT32_MAX;
+ pObj->fAttrib = fAttrib;
+ pObj->cbObject = cbObject;
+ pObj->fMaybeDirtyFat = false;
+ pObj->fMaybeDirtyDirEnt = false;
+ RTTimeSpecSetDosSeconds(&pObj->AccessTime, 0);
+ RTTimeSpecSetDosSeconds(&pObj->ModificationTime, 0);
+ RTTimeSpecSetDosSeconds(&pObj->BirthTime, 0);
+}
+
+
+/**
+ * Flushes FAT object meta data.
+ *
+ * @returns IPRT status code
+ * @param pObj The common object structure.
+ */
+static int rtFsFatObj_FlushMetaData(PRTFSFATOBJ pObj)
+{
+ int rc = VINF_SUCCESS;
+ if (pObj->fMaybeDirtyFat)
+ {
+ rc = rtFsFatClusterMap_Flush(pObj->pVol);
+ if (RT_SUCCESS(rc))
+ pObj->fMaybeDirtyFat = false;
+ }
+ if (pObj->fMaybeDirtyDirEnt)
+ {
+ int rc2 = rtFsFatDirShrd_Flush(pObj->pParentDir);
+ if (RT_SUCCESS(rc2))
+ pObj->fMaybeDirtyDirEnt = false;
+ else if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ return rc;
+}
+
+
+/**
+ * Worker for rtFsFatFile_Close and rtFsFatDir_Close that does common work.
+ *
+ * @returns IPRT status code.
+ * @param pObj The common object structure.
+ */
+static int rtFsFatObj_Close(PRTFSFATOBJ pObj)
+{
+ int rc = rtFsFatObj_FlushMetaData(pObj);
+ if (pObj->pParentDir)
+ rtFsFatDirShrd_RemoveOpenChild(pObj->pParentDir, pObj);
+ rtFsFatChain_Delete(&pObj->Clusters);
+ return rc;
+}
+
+
+/**
+ * Worker for rtFsFatFile_QueryInfo and rtFsFatDir_QueryInfo
+ */
+static int rtFsFatObj_QueryInfo(PRTFSFATOBJ pThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ LogFlow(("rtFsFatObj_QueryInfo: %p fMode=%#x\n", pThis, pThis->fAttrib));
+
+ pObjInfo->cbObject = pThis->cbObject;
+ pObjInfo->cbAllocated = pThis->Clusters.cbChain;
+ pObjInfo->AccessTime = pThis->AccessTime;
+ pObjInfo->ModificationTime = pThis->ModificationTime;
+ pObjInfo->ChangeTime = pThis->ModificationTime;
+ pObjInfo->BirthTime = pThis->BirthTime;
+ pObjInfo->Attr.fMode = pThis->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 = 0; /* Could probably use the directory entry offset. */
+ pObjInfo->Attr.u.Unix.fFlags = 0;
+ pObjInfo->Attr.u.Unix.GenerationId = 0;
+ pObjInfo->Attr.u.Unix.Device = 0;
+ break;
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ pObjInfo->Attr.u.UnixOwner.uid = 0;
+ pObjInfo->Attr.u.UnixOwner.szName[0] = '\0';
+ break;
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ pObjInfo->Attr.u.UnixGroup.gid = 0;
+ pObjInfo->Attr.u.UnixGroup.szName[0] = '\0';
+ break;
+ case RTFSOBJATTRADD_EASIZE:
+ pObjInfo->Attr.u.EASize.cb = 0;
+ break;
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for rtFsFatFile_SetMode and rtFsFatDir_SetMode.
+ */
+static int rtFsFatObj_SetMode(PRTFSFATOBJ pThis, RTFMODE fMode, RTFMODE fMask)
+{
+#if 0
+ if (fMask != ~RTFS_TYPE_MASK)
+ {
+ fMode |= ~fMask & ObjInfo.Attr.fMode;
+ }
+#else
+ RT_NOREF(pThis, fMode, fMask);
+ return VERR_NOT_IMPLEMENTED;
+#endif
+}
+
+
+/**
+ * Worker for rtFsFatFile_SetTimes and rtFsFatDir_SetTimes.
+ */
+static int rtFsFatObj_SetTimes(PRTFSFATOBJ pThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+#if 0
+ PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
+#else
+ RT_NOREF(pThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+ return VERR_NOT_IMPLEMENTED;
+#endif
+}
+
+
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsFatFile_Close(void *pvThis)
+{
+ PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
+ LogFlow(("rtFsFatFile_Close(%p/%p)\n", pThis, pThis->pShared));
+
+ PRTFSFATFILESHRD pShared = pThis->pShared;
+ pThis->pShared = NULL;
+
+ int rc = VINF_SUCCESS;
+ if (pShared)
+ {
+ if (ASMAtomicDecU32(&pShared->Core.cRefs) == 0)
+ {
+ LogFlow(("rtFsFatFile_Close: Destroying shared structure %p\n", pShared));
+ rc = rtFsFatObj_Close(&pShared->Core);
+ RTMemFree(pShared);
+ }
+ else
+ rc = rtFsFatObj_FlushMetaData(&pShared->Core);
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsFatFile_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
+ return rtFsFatObj_QueryInfo(&pThis->pShared->Core, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtFsFatFile_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
+ PRTFSFATFILESHRD pShared = pThis->pShared;
+ AssertReturn(pSgBuf->cSegs != 0, VERR_INTERNAL_ERROR_3);
+ RT_NOREF(fBlocking);
+
+ /*
+ * 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;
+ }
+
+ /*
+ * Do the reading cluster by cluster.
+ */
+ int rc = VINF_SUCCESS;
+ uint32_t cbFileLeft = pShared->Core.cbObject - (uint32_t)off;
+ uint32_t cbRead = 0;
+ size_t cbLeft = pSgBuf->paSegs[0].cbSeg;
+ uint8_t *pbDst = (uint8_t *)pSgBuf->paSegs[0].pvSeg;
+ while (cbLeft > 0)
+ {
+ if (cbFileLeft > 0)
+ {
+ uint64_t offDisk = rtFsFatChain_FileOffsetToDiskOff(&pShared->Core.Clusters, (uint32_t)off, pShared->Core.pVol);
+ if (offDisk != UINT64_MAX)
+ {
+ uint32_t cbToRead = pShared->Core.Clusters.cbCluster - ((uint32_t)off & (pShared->Core.Clusters.cbCluster - 1));
+ if (cbToRead > cbLeft)
+ cbToRead = (uint32_t)cbLeft;
+ if (cbToRead > cbFileLeft)
+ cbToRead = cbFileLeft;
+ rc = RTVfsFileReadAt(pShared->Core.pVol->hVfsBacking, offDisk, pbDst, cbToRead, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ off += cbToRead;
+ pbDst += cbToRead;
+ cbRead += cbToRead;
+ cbFileLeft -= cbToRead;
+ cbLeft -= cbToRead;
+ continue;
+ }
+ }
+ else
+ rc = VERR_VFS_BOGUS_OFFSET;
+ }
+ else
+ rc = pcbRead ? VINF_EOF : VERR_EOF;
+ break;
+ }
+
+ /* Update the offset and return. */
+ pThis->offFile = off;
+ if (pcbRead)
+ *pcbRead = cbRead;
+ return rc;
+}
+
+
+/**
+ * Changes the size of a file or directory FAT object.
+ *
+ * @returns IPRT status code
+ * @param pObj The common object.
+ * @param cbFile The new file size.
+ */
+static int rtFsFatObj_SetSize(PRTFSFATOBJ pObj, uint32_t cbFile)
+{
+ AssertReturn( ((pObj->cbObject + pObj->Clusters.cbCluster - 1) >> pObj->Clusters.cClusterByteShift)
+ == pObj->Clusters.cClusters, VERR_INTERNAL_ERROR_3);
+
+ /*
+ * Do nothing if the size didn't change.
+ */
+ if (pObj->cbObject == cbFile)
+ return VINF_SUCCESS;
+
+ /*
+ * Do we need to allocate or free clusters?
+ */
+ int rc = VINF_SUCCESS;
+ uint32_t const cClustersNew = (cbFile + pObj->Clusters.cbCluster - 1) >> pObj->Clusters.cClusterByteShift;
+ AssertReturn(pObj->pParentDir, VERR_INTERNAL_ERROR_2);
+ if (pObj->Clusters.cClusters == cClustersNew)
+ { /* likely when writing small bits at a time. */ }
+ else if (pObj->Clusters.cClusters < cClustersNew)
+ {
+ /* Allocate and append new clusters. */
+ do
+ {
+ uint32_t idxCluster;
+ rc = rtFsFatClusterMap_AllocateCluster(pObj->pVol, rtFsFatChain_GetLastCluster(&pObj->Clusters), &idxCluster);
+ if (RT_SUCCESS(rc))
+ rc = rtFsFatChain_Append(&pObj->Clusters, idxCluster);
+ } while (pObj->Clusters.cClusters < cClustersNew && RT_SUCCESS(rc));
+ pObj->fMaybeDirtyFat = true;
+ }
+ else
+ {
+ /* Free clusters we don't need any more. */
+ if (cClustersNew > 0)
+ rc = rtFsFatClusterMap_SetEndOfChain(pObj->pVol, rtFsFatChain_GetClusterByIndex(&pObj->Clusters, cClustersNew - 1));
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t iClusterToFree = cClustersNew;
+ while (iClusterToFree < pObj->Clusters.cClusters && RT_SUCCESS(rc))
+ {
+ rc = rtFsFatClusterMap_FreeCluster(pObj->pVol, rtFsFatChain_GetClusterByIndex(&pObj->Clusters, iClusterToFree));
+ iClusterToFree++;
+ }
+
+ rtFsFatChain_Shrink(&pObj->Clusters, cClustersNew);
+ }
+ pObj->fMaybeDirtyFat = true;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Update the object size, since we've got the right number of clusters backing it now.
+ */
+ pObj->cbObject = cbFile;
+
+ /*
+ * Update the directory entry.
+ */
+ uint32_t uWriteLock;
+ PFATDIRENTRY pDirEntry;
+ rc = rtFsFatDirShrd_GetEntryForUpdate(pObj->pParentDir, pObj->offEntryInDir, &pDirEntry, &uWriteLock);
+ if (RT_SUCCESS(rc))
+ {
+ pDirEntry->cbFile = cbFile;
+ uint32_t idxFirstCluster;
+ if (cClustersNew == 0)
+ idxFirstCluster = 0; /** @todo figure out if setting the cluster to 0 is the right way to deal with empty files... */
+ else
+ idxFirstCluster = rtFsFatChain_GetFirstCluster(&pObj->Clusters);
+ pDirEntry->idxCluster = (uint16_t)idxFirstCluster;
+ if (pObj->pVol->enmFatType >= RTFSFATTYPE_FAT32)
+ pDirEntry->u.idxClusterHigh = (uint16_t)(idxFirstCluster >> 16);
+
+ rc = rtFsFatDirShrd_PutEntryAfterUpdate(pObj->pParentDir, pDirEntry, uWriteLock);
+ pObj->fMaybeDirtyDirEnt = true;
+ }
+ }
+ Log3(("rtFsFatObj_SetSize: Returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtFsFatFile_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
+ PRTFSFATFILESHRD pShared = pThis->pShared;
+ PRTFSFATVOL pVol = pShared->Core.pVol;
+ AssertReturn(pSgBuf->cSegs != 0, VERR_INTERNAL_ERROR_3);
+ RT_NOREF(fBlocking);
+
+ if (pVol->fReadOnly)
+ return VERR_WRITE_PROTECT;
+
+ if (off == -1)
+ off = pThis->offFile;
+
+ /*
+ * Do the reading cluster by cluster.
+ */
+ int rc = VINF_SUCCESS;
+ uint32_t cbWritten = 0;
+ size_t cbLeft = pSgBuf->paSegs[0].cbSeg;
+ uint8_t const *pbSrc = (uint8_t const *)pSgBuf->paSegs[0].pvSeg;
+ while (cbLeft > 0)
+ {
+ /* Figure out how much we can write. Checking for max file size and such. */
+ uint32_t cbToWrite = pShared->Core.Clusters.cbCluster - ((uint32_t)off & (pShared->Core.Clusters.cbCluster - 1));
+ if (cbToWrite > cbLeft)
+ cbToWrite = (uint32_t)cbLeft;
+ uint64_t offNew = (uint64_t)off + cbToWrite;
+ if (offNew < _4G)
+ { /*likely*/ }
+ else if ((uint64_t)off < _4G - 1U)
+ cbToWrite = _4G - 1U - off;
+ else
+ {
+ rc = VERR_FILE_TOO_BIG;
+ break;
+ }
+
+ /* Grow the file? */
+ if ((uint32_t)offNew > pShared->Core.cbObject)
+ {
+ rc = rtFsFatObj_SetSize(&pShared->Core, (uint32_t)offNew);
+ if (RT_SUCCESS(rc))
+ { /* likely */}
+ else
+ break;
+ }
+
+ /* Figure the disk offset. */
+ uint64_t offDisk = rtFsFatChain_FileOffsetToDiskOff(&pShared->Core.Clusters, (uint32_t)off, pVol);
+ if (offDisk != UINT64_MAX)
+ {
+ rc = RTVfsFileWriteAt(pVol->hVfsBacking, offDisk, pbSrc, cbToWrite, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ off += cbToWrite;
+ pbSrc += cbToWrite;
+ cbWritten += cbToWrite;
+ cbLeft -= cbToWrite;
+ }
+ else
+ break;
+ }
+ else
+ {
+ rc = VERR_VFS_BOGUS_OFFSET;
+ break;
+ }
+ }
+
+ /* Update the offset and return. */
+ pThis->offFile = off;
+ if (pcbWritten)
+ *pcbWritten = cbWritten;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtFsFatFile_Flush(void *pvThis)
+{
+ PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
+ PRTFSFATFILESHRD pShared = pThis->pShared;
+ int rc1 = rtFsFatObj_FlushMetaData(&pShared->Core);
+ int rc2 = RTVfsFileFlush(pShared->Core.pVol->hVfsBacking);
+ return RT_FAILURE(rc1) ? rc1 : rc2;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
+ */
+static DECLCALLBACK(int) rtFsFatFile_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) rtFsFatFile_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
+ *poffActual = pThis->offFile;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtFsFatFile_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
+ return rtFsFatObj_SetMode(&pThis->pShared->Core, fMode, fMask);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtFsFatFile_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
+ return rtFsFatObj_SetTimes(&pThis->pShared->Core, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtFsFatFile_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ RT_NOREF(pvThis, uid, gid);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSeek}
+ */
+static DECLCALLBACK(int) rtFsFatFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual)
+{
+ PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
+ PRTFSFATFILESHRD pShared = pThis->pShared;
+
+ RTFOFF offNew;
+ switch (uMethod)
+ {
+ case RTFILE_SEEK_BEGIN:
+ offNew = offSeek;
+ break;
+ case RTFILE_SEEK_END:
+ offNew = (RTFOFF)pShared->Core.cbObject + offSeek;
+ break;
+ case RTFILE_SEEK_CURRENT:
+ offNew = (RTFOFF)pThis->offFile + offSeek;
+ break;
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+ if (offNew >= 0)
+ {
+ if (offNew <= _4G)
+ {
+ pThis->offFile = offNew;
+ *poffActual = offNew;
+ return VINF_SUCCESS;
+ }
+ return VERR_OUT_OF_RANGE;
+ }
+ return VERR_NEGATIVE_SEEK;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize}
+ */
+static DECLCALLBACK(int) rtFsFatFile_QuerySize(void *pvThis, uint64_t *pcbFile)
+{
+ PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
+ PRTFSFATFILESHRD pShared = pThis->pShared;
+ *pcbFile = pShared->Core.cbObject;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSetSize}
+ */
+static DECLCALLBACK(int) rtFsFatFile_SetSize(void *pvThis, uint64_t cbFile, uint32_t fFlags)
+{
+ PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
+ PRTFSFATFILESHRD pShared = pThis->pShared;
+ AssertReturn(!fFlags, VERR_NOT_SUPPORTED);
+ if (cbFile > UINT32_MAX)
+ return VERR_FILE_TOO_BIG;
+ return rtFsFatObj_SetSize(&pShared->Core, (uint32_t)cbFile);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQueryMaxSize}
+ */
+static DECLCALLBACK(int) rtFsFatFile_QueryMaxSize(void *pvThis, uint64_t *pcbMax)
+{
+ RT_NOREF(pvThis);
+ *pcbMax = UINT32_MAX;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * FAT file operations.
+ */
+DECL_HIDDEN_CONST(const RTVFSFILEOPS) g_rtFsFatFileOps =
+{
+ { /* Stream */
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FILE,
+ "FatFile",
+ rtFsFatFile_Close,
+ rtFsFatFile_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtFsFatFile_Read,
+ rtFsFatFile_Write,
+ rtFsFatFile_Flush,
+ rtFsFatFile_PollOne,
+ rtFsFatFile_Tell,
+ NULL /*pfnSkip*/,
+ NULL /*pfnZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+ },
+ RTVFSFILEOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj),
+ rtFsFatFile_SetMode,
+ rtFsFatFile_SetTimes,
+ rtFsFatFile_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtFsFatFile_Seek,
+ rtFsFatFile_QuerySize,
+ rtFsFatFile_SetSize,
+ rtFsFatFile_QueryMaxSize,
+ RTVFSFILEOPS_VERSION
+};
+
+
+/**
+ * Instantiates a new file.
+ *
+ * @returns IPRT status code.
+ * @param pThis The FAT volume instance.
+ * @param pParentDir The parent directory.
+ * @param pDirEntry The parent directory entry.
+ * @param offEntryInDir The byte offset of the directory entry in the parent
+ * directory.
+ * @param fOpen RTFILE_O_XXX flags.
+ * @param phVfsFile Where to return the file handle.
+ */
+static int rtFsFatFile_New(PRTFSFATVOL pThis, PRTFSFATDIRSHRD pParentDir, PCFATDIRENTRY pDirEntry, uint32_t offEntryInDir,
+ uint64_t fOpen, PRTVFSFILE phVfsFile)
+{
+ AssertPtr(pParentDir);
+ Assert(!(offEntryInDir & (sizeof(FATDIRENTRY) - 1)));
+
+ PRTFSFATFILE pNewFile;
+ int rc = RTVfsNewFile(&g_rtFsFatFileOps, sizeof(*pNewFile), fOpen, pThis->hVfsSelf, NIL_RTVFSLOCK /*use volume lock*/,
+ phVfsFile, (void **)&pNewFile);
+ if (RT_SUCCESS(rc))
+ {
+ pNewFile->offFile = 0;
+ pNewFile->pShared = NULL;
+
+ /*
+ * Look for existing shared object, create a new one if necessary.
+ */
+ PRTFSFATFILESHRD pShared = (PRTFSFATFILESHRD)rtFsFatDirShrd_LookupShared(pParentDir, offEntryInDir);
+ if (pShared)
+ {
+ LogFlow(("rtFsFatFile_New: cbObject=%#RX32 \n", pShared->Core.cbObject));
+ pNewFile->pShared = pShared;
+ return VINF_SUCCESS;
+ }
+
+ pShared = (PRTFSFATFILESHRD)RTMemAllocZ(sizeof(*pShared));
+ if (pShared)
+ {
+ rtFsFatObj_InitFromDirEntry(&pShared->Core, pDirEntry, offEntryInDir, pThis);
+ pNewFile->pShared = pShared;
+
+ rc = rtFsFatClusterMap_ReadClusterChain(pThis, RTFSFAT_GET_CLUSTER(pDirEntry, pThis), &pShared->Core.Clusters);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Link into parent directory so we can use it to update
+ * our directory entry.
+ */
+ rtFsFatDirShrd_AddOpenChild(pParentDir, &pShared->Core);
+
+ /*
+ * Should we truncate the file or anything of that sort?
+ */
+ if ( (fOpen & RTFILE_O_TRUNCATE)
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE)
+ {
+ Log3(("rtFsFatFile_New: calling rtFsFatObj_SetSize to zap the file size.\n"));
+ rc = rtFsFatObj_SetSize(&pShared->Core, 0);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ LogFlow(("rtFsFatFile_New: cbObject=%#RX32 pShared=%p\n", pShared->Core.cbObject, pShared));
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ /* Destroy the file object. */
+ 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 offEntryInDir The directory record offset of the child.
+ */
+static PRTFSFATOBJ rtFsFatDirShrd_LookupShared(PRTFSFATDIRSHRD pThis, uint32_t offEntryInDir)
+{
+ PRTFSFATOBJ pCur;
+ RTListForEach(&pThis->OpenChildren, pCur, RTFSFATOBJ, Entry)
+ {
+ if (pCur->offEntryInDir == offEntryInDir)
+ {
+ uint32_t cRefs = ASMAtomicIncU32(&pCur->cRefs);
+ Assert(cRefs > 1); RT_NOREF(cRefs);
+ return pCur;
+ }
+ }
+ return NULL;
+}
+
+
+/**
+ * Flush directory changes when having a fully buffered directory.
+ *
+ * @returns IPRT status code
+ * @param pThis The directory.
+ */
+static int rtFsFatDirShrd_FlushFullyBuffered(PRTFSFATDIRSHRD pThis)
+{
+ Assert(pThis->fFullyBuffered);
+ uint32_t const cbSector = pThis->Core.pVol->cbSector;
+ RTVFSFILE const hVfsBacking = pThis->Core.pVol->hVfsBacking;
+ int rc = VINF_SUCCESS;
+ for (uint32_t i = 0; i < pThis->u.Full.cSectors; i++)
+ if (ASMBitTest(pThis->u.Full.pbDirtySectors, i))
+ {
+ int rc2 = RTVfsFileWriteAt(hVfsBacking, pThis->offEntriesOnDisk + i * cbSector,
+ (uint8_t *)pThis->paEntries + i * cbSector, cbSector, NULL);
+ if (RT_SUCCESS(rc2))
+ ASMBitClear(pThis->u.Full.pbDirtySectors, i);
+ else if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ return rc;
+}
+
+
+/**
+ * Flush directory changes when using simple buffering.
+ *
+ * @returns IPRT status code
+ * @param pThis The directory.
+ */
+static int rtFsFatDirShrd_FlushSimple(PRTFSFATDIRSHRD pThis)
+{
+ Assert(!pThis->fFullyBuffered);
+ int rc;
+ if ( !pThis->u.Simple.fDirty
+ || pThis->offEntriesOnDisk != UINT64_MAX)
+ rc = VINF_SUCCESS;
+ else
+ {
+ Assert(pThis->u.Simple.offInDir != UINT32_MAX);
+ rc = RTVfsFileWriteAt(pThis->Core.pVol->hVfsBacking, pThis->offEntriesOnDisk,
+ pThis->paEntries, pThis->Core.pVol->cbSector, NULL);
+ if (RT_SUCCESS(rc))
+ pThis->u.Simple.fDirty = false;
+ }
+ return rc;
+}
+
+
+/**
+ * Flush directory changes.
+ *
+ * @returns IPRT status code
+ * @param pThis The directory.
+ */
+static int rtFsFatDirShrd_Flush(PRTFSFATDIRSHRD pThis)
+{
+ if (pThis->fFullyBuffered)
+ return rtFsFatDirShrd_FlushFullyBuffered(pThis);
+ return rtFsFatDirShrd_FlushSimple(pThis);
+}
+
+
+/**
+ * Gets one or more entires at @a offEntryInDir.
+ *
+ * Common worker for rtFsFatDirShrd_GetEntriesAt and rtFsFatDirShrd_GetEntryForUpdate
+ *
+ * @returns IPRT status code.
+ * @param pThis The directory.
+ * @param offEntryInDir The directory offset in bytes.
+ * @param fForUpdate Whether it's for updating.
+ * @param ppaEntries Where to return pointer to the entry at
+ * @a offEntryInDir.
+ * @param pcEntries Where to return the number of entries
+ * @a *ppaEntries points to.
+ * @param puBufferReadLock Where to return the buffer read lock handle.
+ * Call rtFsFatDirShrd_ReleaseBufferAfterReading when
+ * done.
+ */
+static int rtFsFatDirShrd_GetEntriesAtCommon(PRTFSFATDIRSHRD pThis, uint32_t offEntryInDir, bool fForUpdate,
+ PFATDIRENTRYUNION *ppaEntries, uint32_t *pcEntries, uint32_t *puLock)
+{
+ *puLock = UINT32_MAX;
+
+ int rc;
+ Assert(RT_ALIGN_32(offEntryInDir, sizeof(FATDIRENTRY)) == offEntryInDir);
+ Assert(pThis->Core.cbObject / sizeof(FATDIRENTRY) == pThis->cEntries);
+ uint32_t const idxEntryInDir = offEntryInDir / sizeof(FATDIRENTRY);
+ if (idxEntryInDir < pThis->cEntries)
+ {
+ if (pThis->fFullyBuffered)
+ {
+ /*
+ * Fully buffered: Return pointer to all the entires starting at offEntryInDir.
+ */
+ *ppaEntries = &pThis->paEntries[idxEntryInDir];
+ *pcEntries = pThis->cEntries - idxEntryInDir;
+ *puLock = !fForUpdate ? 1 : UINT32_C(0x80000001);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ /*
+ * Simple buffering: If hit, return the number of entries.
+ */
+ PRTFSFATVOL pVol = pThis->Core.pVol;
+ uint32_t off = offEntryInDir - pThis->u.Simple.offInDir;
+ if (off < pVol->cbSector)
+ {
+ *ppaEntries = &pThis->paEntries[off / sizeof(FATDIRENTRY)];
+ *pcEntries = (pVol->cbSector - off) / sizeof(FATDIRENTRY);
+ *puLock = !fForUpdate ? 1 : UINT32_C(0x80000001);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ /*
+ * Simple buffering: Miss.
+ * Flush dirty. Read in new sector. Return entries in sector starting
+ * at offEntryInDir.
+ */
+ if (!pThis->u.Simple.fDirty)
+ rc = VINF_SUCCESS;
+ else
+ rc = rtFsFatDirShrd_FlushSimple(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ off = offEntryInDir & (pVol->cbSector - 1);
+ pThis->u.Simple.offInDir = (offEntryInDir & ~(pVol->cbSector - 1));
+ pThis->offEntriesOnDisk = rtFsFatChain_FileOffsetToDiskOff(&pThis->Core.Clusters, pThis->u.Simple.offInDir,
+ pThis->Core.pVol);
+ rc = RTVfsFileReadAt(pThis->Core.pVol->hVfsBacking, pThis->offEntriesOnDisk,
+ pThis->paEntries, pVol->cbSector, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ *ppaEntries = &pThis->paEntries[off / sizeof(FATDIRENTRY)];
+ *pcEntries = (pVol->cbSector - off) / sizeof(FATDIRENTRY);
+ *puLock = !fForUpdate ? 1 : UINT32_C(0x80000001);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ pThis->u.Simple.offInDir = UINT32_MAX;
+ pThis->offEntriesOnDisk = UINT64_MAX;
+ }
+ }
+ }
+ }
+ }
+ else
+ rc = VERR_FILE_NOT_FOUND;
+ return rc;
+}
+
+
+/**
+ * Puts back a directory entry after updating it, releasing the write lock and
+ * marking it dirty.
+ *
+ * @returns IPRT status code
+ * @param pThis The directory.
+ * @param pDirEntry The directory entry.
+ * @param uWriteLock The write lock.
+ */
+static int rtFsFatDirShrd_PutEntryAfterUpdate(PRTFSFATDIRSHRD pThis, PFATDIRENTRY pDirEntry, uint32_t uWriteLock)
+{
+ Assert(uWriteLock == UINT32_C(0x80000001));
+ RT_NOREF(uWriteLock);
+ if (pThis->fFullyBuffered)
+ {
+ uint32_t idxSector = ((uintptr_t)pDirEntry - (uintptr_t)pThis->paEntries) / pThis->Core.pVol->cbSector;
+ ASMBitSet(pThis->u.Full.pbDirtySectors, idxSector);
+ }
+ else
+ pThis->u.Simple.fDirty = true;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Gets the pointer to the given directory entry for the purpose of updating it.
+ *
+ * Call rtFsFatDirShrd_PutEntryAfterUpdate afterwards.
+ *
+ * @returns IPRT status code.
+ * @param pThis The directory.
+ * @param offEntryInDir The byte offset of the directory entry, within the
+ * directory.
+ * @param ppDirEntry Where to return the pointer to the directory entry.
+ * @param puWriteLock Where to return the write lock.
+ */
+static int rtFsFatDirShrd_GetEntryForUpdate(PRTFSFATDIRSHRD pThis, uint32_t offEntryInDir, PFATDIRENTRY *ppDirEntry,
+ uint32_t *puWriteLock)
+{
+ uint32_t cEntriesIgn;
+ return rtFsFatDirShrd_GetEntriesAtCommon(pThis, offEntryInDir, true /*fForUpdate*/, (PFATDIRENTRYUNION *)ppDirEntry,
+ &cEntriesIgn, puWriteLock);
+}
+
+
+/**
+ * Release a directory buffer after done reading from it.
+ *
+ * This is currently just a placeholder.
+ *
+ * @param pThis The directory.
+ * @param uBufferReadLock The buffer lock.
+ */
+static void rtFsFatDirShrd_ReleaseBufferAfterReading(PRTFSFATDIRSHRD pThis, uint32_t uBufferReadLock)
+{
+ RT_NOREF(pThis, uBufferReadLock);
+ Assert(uBufferReadLock == 1);
+}
+
+
+/**
+ * Gets one or more entires at @a offEntryInDir.
+ *
+ * @returns IPRT status code.
+ * @param pThis The directory.
+ * @param offEntryInDir The directory offset in bytes.
+ * @param ppaEntries Where to return pointer to the entry at
+ * @a offEntryInDir.
+ * @param pcEntries Where to return the number of entries
+ * @a *ppaEntries points to.
+ * @param puBufferReadLock Where to return the buffer read lock handle.
+ * Call rtFsFatDirShrd_ReleaseBufferAfterReading when
+ * done.
+ */
+static int rtFsFatDirShrd_GetEntriesAt(PRTFSFATDIRSHRD pThis, uint32_t offEntryInDir,
+ PCFATDIRENTRYUNION *ppaEntries, uint32_t *pcEntries, uint32_t *puBufferReadLock)
+{
+ return rtFsFatDirShrd_GetEntriesAtCommon(pThis, offEntryInDir, false /*fForUpdate*/, (PFATDIRENTRYUNION *)ppaEntries,
+ pcEntries, puBufferReadLock);
+}
+
+
+/**
+ * Translates a unicode codepoint to an uppercased CP437 index.
+ *
+ * @returns CP437 index if valie, UINT16_MAX if not.
+ * @param uc The codepoint to convert.
+ */
+static uint16_t rtFsFatUnicodeCodepointToUpperCodepage(RTUNICP uc)
+{
+ /*
+ * The first 128 chars have 1:1 translation for valid FAT chars.
+ */
+ if (uc < 128)
+ {
+ if (g_awchFatCp437ValidChars[uc] == uc)
+ return (uint16_t)uc;
+ if (RT_C_IS_LOWER(uc))
+ return uc - 0x20;
+ return UINT16_MAX;
+ }
+
+ /*
+ * Try for uppercased, settle for lower case if no upper case variant in the table.
+ * This is really expensive, btw.
+ */
+ RTUNICP ucUpper = RTUniCpToUpper(uc);
+ for (unsigned i = 128; i < 256; i++)
+ if (g_awchFatCp437ValidChars[i] == ucUpper)
+ return i;
+ if (ucUpper != uc)
+ for (unsigned i = 128; i < 256; i++)
+ if (g_awchFatCp437ValidChars[i] == uc)
+ return i;
+ return UINT16_MAX;
+}
+
+
+/**
+ * Convert filename string to 8-dot-3 format, doing necessary ASCII uppercasing
+ * and such.
+ *
+ * @returns true if 8.3 formattable name, false if not.
+ * @param pszName8Dot3 Where to return the 8-dot-3 name when returning
+ * @c true. Filled with zero on false. 8+3+1 bytes.
+ * @param pszName The filename to convert.
+ */
+static bool rtFsFatDir_StringTo8Dot3(char *pszName8Dot3, const char *pszName)
+{
+ /*
+ * Don't try convert names with more than 12 unicode chars in them.
+ */
+ size_t const cucName = RTStrUniLen(pszName);
+ if (cucName <= 12 && cucName > 0)
+ {
+ /*
+ * Recode the input string as CP437, uppercasing it, validating the
+ * name, formatting it as a FAT directory entry string.
+ */
+ size_t offDst = 0;
+ bool fExt = false;
+ for (;;)
+ {
+ RTUNICP uc;
+ int rc = RTStrGetCpEx(&pszName, &uc);
+ if (RT_SUCCESS(rc))
+ {
+ if (uc)
+ {
+ if (offDst < 8+3)
+ {
+ uint16_t idxCp = rtFsFatUnicodeCodepointToUpperCodepage(uc);
+ if (idxCp != UINT16_MAX)
+ {
+ pszName8Dot3[offDst++] = (char)idxCp;
+ Assert(uc != '.');
+ continue;
+ }
+
+ /* Maybe the dot? */
+ if ( uc == '.'
+ && !fExt
+ && offDst <= 8)
+ {
+ fExt = true;
+ while (offDst < 8)
+ pszName8Dot3[offDst++] = ' ';
+ continue;
+ }
+ }
+ }
+ /* String terminator: Check length, pad and convert 0xe5. */
+ else if (offDst <= (size_t)(fExt ? 8 + 3 : 8))
+ {
+ while (offDst < 8 + 3)
+ pszName8Dot3[offDst++] = ' ';
+ Assert(offDst == 8 + 3);
+ pszName8Dot3[offDst] = '\0';
+
+ if ((uint8_t)pszName8Dot3[0] == FATDIRENTRY_CH0_DELETED)
+ pszName8Dot3[0] = FATDIRENTRY_CH0_ESC_E5;
+ return true;
+ }
+ }
+ /* invalid */
+ break;
+ }
+ }
+ memset(&pszName8Dot3[0], 0, 8+3+1);
+ return false;
+}
+
+
+/**
+ * Calculates the checksum of a directory entry.
+ * @returns Checksum.
+ * @param pDirEntry The directory entry to checksum.
+ */
+static uint8_t rtFsFatDir_CalcChecksum(PCFATDIRENTRY pDirEntry)
+{
+ uint8_t bChecksum = pDirEntry->achName[0];
+ for (uint8_t off = 1; off < RT_ELEMENTS(pDirEntry->achName); off++)
+ {
+ bChecksum = RTFSFAT_ROT_R1_U8(bChecksum);
+ bChecksum += pDirEntry->achName[off];
+ }
+ return bChecksum;
+}
+
+
+/**
+ * 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 poffEntryInDir Where to return the offset of the directory
+ * entry.
+ * @param pfLong Where to return long name indicator.
+ * @param pDirEntry Where to return a copy of the directory entry.
+ */
+static int rtFsFatDirShrd_FindEntry(PRTFSFATDIRSHRD pThis, const char *pszEntry, uint32_t *poffEntryInDir, bool *pfLong,
+ PFATDIRENTRY pDirEntry)
+{
+ /* Set return values. */
+ *pfLong = false;
+ *poffEntryInDir = UINT32_MAX;
+
+ /*
+ * Turn pszEntry into a 8.3 filename, if possible.
+ */
+ char szName8Dot3[8+3+1];
+ bool fIs8Dot3Name = rtFsFatDir_StringTo8Dot3(szName8Dot3, pszEntry);
+
+ /*
+ * Scan the directory buffer by buffer.
+ */
+ RTUTF16 wszName[260+1];
+ uint8_t bChecksum = UINT8_MAX;
+ uint8_t idNextSlot = UINT8_MAX;
+ size_t cwcName = 0;
+ uint32_t offEntryInDir = 0;
+ uint32_t const cbDir = pThis->Core.cbObject;
+ Assert(RT_ALIGN_32(cbDir, sizeof(*pDirEntry)) == cbDir);
+ AssertCompile(FATDIRNAMESLOT_MAX_SLOTS * FATDIRNAMESLOT_CHARS_PER_SLOT < RT_ELEMENTS(wszName));
+ wszName[260] = '\0';
+
+ while (offEntryInDir < cbDir)
+ {
+ /* Get chunk of entries starting at offEntryInDir. */
+ uint32_t uBufferLock = UINT32_MAX;
+ uint32_t cEntries = 0;
+ PCFATDIRENTRYUNION paEntries = NULL;
+ int rc = rtFsFatDirShrd_GetEntriesAt(pThis, offEntryInDir, &paEntries, &cEntries, &uBufferLock);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Now work thru each of the entries.
+ */
+ for (uint32_t iEntry = 0; iEntry < cEntries; iEntry++, offEntryInDir += sizeof(FATDIRENTRY))
+ {
+ switch ((uint8_t)paEntries[iEntry].Entry.achName[0])
+ {
+ default:
+ break;
+ case FATDIRENTRY_CH0_DELETED:
+ cwcName = 0;
+ continue;
+ case FATDIRENTRY_CH0_END_OF_DIR:
+ if (pThis->Core.pVol->enmBpbVersion >= RTFSFATBPBVER_DOS_2_0)
+ {
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock);
+ return VERR_FILE_NOT_FOUND;
+ }
+ cwcName = 0;
+ break; /* Technically a valid entry before DOS 2.0, or so some claim. */
+ }
+
+ /*
+ * Check for long filename slot.
+ */
+ if ( paEntries[iEntry].Slot.fAttrib == FAT_ATTR_NAME_SLOT
+ && paEntries[iEntry].Slot.idxZero == 0
+ && paEntries[iEntry].Slot.fZero == 0
+ && (paEntries[iEntry].Slot.idSlot & ~FATDIRNAMESLOT_FIRST_SLOT_FLAG) <= FATDIRNAMESLOT_HIGHEST_SLOT_ID
+ && (paEntries[iEntry].Slot.idSlot & ~FATDIRNAMESLOT_FIRST_SLOT_FLAG) != 0)
+ {
+ /* New slot? */
+ if (paEntries[iEntry].Slot.idSlot & FATDIRNAMESLOT_FIRST_SLOT_FLAG)
+ {
+ idNextSlot = paEntries[iEntry].Slot.idSlot & ~FATDIRNAMESLOT_FIRST_SLOT_FLAG;
+ bChecksum = paEntries[iEntry].Slot.bChecksum;
+ cwcName = idNextSlot * FATDIRNAMESLOT_CHARS_PER_SLOT;
+ wszName[cwcName] = '\0';
+ }
+ /* Is valid next entry? */
+ else if ( paEntries[iEntry].Slot.idSlot == idNextSlot
+ && paEntries[iEntry].Slot.bChecksum == bChecksum)
+ { /* likely */ }
+ else
+ cwcName = 0;
+ if (cwcName)
+ {
+ idNextSlot--;
+ size_t offName = idNextSlot * FATDIRNAMESLOT_CHARS_PER_SLOT;
+ memcpy(&wszName[offName], paEntries[iEntry].Slot.awcName0, sizeof(paEntries[iEntry].Slot.awcName0));
+ memcpy(&wszName[offName + 5], paEntries[iEntry].Slot.awcName1, sizeof(paEntries[iEntry].Slot.awcName1));
+ memcpy(&wszName[offName + 5 + 6], paEntries[iEntry].Slot.awcName2, sizeof(paEntries[iEntry].Slot.awcName2));
+ }
+ }
+ /*
+ * Regular directory entry. Do the matching, first 8.3 then long name.
+ */
+ else if ( fIs8Dot3Name
+ && !(paEntries[iEntry].Entry.fAttrib & FAT_ATTR_VOLUME)
+ && memcmp(paEntries[iEntry].Entry.achName, szName8Dot3, sizeof(paEntries[iEntry].Entry.achName)) == 0)
+ {
+ *poffEntryInDir = offEntryInDir;
+ *pDirEntry = paEntries[iEntry].Entry;
+ *pfLong = false;
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock);
+ return VINF_SUCCESS;
+ }
+ else if ( cwcName != 0
+ && idNextSlot == 0
+ && !(paEntries[iEntry].Entry.fAttrib & FAT_ATTR_VOLUME)
+ && rtFsFatDir_CalcChecksum(&paEntries[iEntry].Entry) == bChecksum
+ && RTUtf16ICmpUtf8(wszName, pszEntry) == 0)
+ {
+ *poffEntryInDir = offEntryInDir;
+ *pDirEntry = paEntries[iEntry].Entry;
+ *pfLong = true;
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock);
+ return VINF_SUCCESS;
+ }
+ else
+ cwcName = 0;
+ }
+
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock);
+ }
+
+ return VERR_FILE_NOT_FOUND;
+}
+
+
+/**
+ * Watered down version of rtFsFatDirShrd_FindEntry that is used by the short name
+ * generator to check for duplicates.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_FILE_NOT_FOUND if not found.
+ * @retval VINF_SUCCESS if found.
+ * @param pThis The directory to search.
+ * @param pszEntry The entry to look for.
+ */
+static int rtFsFatDirShrd_FindEntryShort(PRTFSFATDIRSHRD pThis, const char *pszName8Dot3)
+{
+ Assert(strlen(pszName8Dot3) == 8+3);
+
+ /*
+ * Scan the directory buffer by buffer.
+ */
+ uint32_t offEntryInDir = 0;
+ uint32_t const cbDir = pThis->Core.cbObject;
+ Assert(RT_ALIGN_32(cbDir, sizeof(FATDIRENTRY)) == cbDir);
+
+ while (offEntryInDir < cbDir)
+ {
+ /* Get chunk of entries starting at offEntryInDir. */
+ uint32_t uBufferLock = UINT32_MAX;
+ uint32_t cEntries = 0;
+ PCFATDIRENTRYUNION paEntries = NULL;
+ int rc = rtFsFatDirShrd_GetEntriesAt(pThis, offEntryInDir, &paEntries, &cEntries, &uBufferLock);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Now work thru each of the entries.
+ */
+ for (uint32_t iEntry = 0; iEntry < cEntries; iEntry++, offEntryInDir += sizeof(FATDIRENTRY))
+ {
+ switch ((uint8_t)paEntries[iEntry].Entry.achName[0])
+ {
+ default:
+ break;
+ case FATDIRENTRY_CH0_DELETED:
+ continue;
+ case FATDIRENTRY_CH0_END_OF_DIR:
+ if (pThis->Core.pVol->enmBpbVersion >= RTFSFATBPBVER_DOS_2_0)
+ {
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock);
+ return VERR_FILE_NOT_FOUND;
+ }
+ break; /* Technically a valid entry before DOS 2.0, or so some claim. */
+ }
+
+ /*
+ * Skip long filename slots.
+ */
+ if ( paEntries[iEntry].Slot.fAttrib == FAT_ATTR_NAME_SLOT
+ && paEntries[iEntry].Slot.idxZero == 0
+ && paEntries[iEntry].Slot.fZero == 0
+ && (paEntries[iEntry].Slot.idSlot & ~FATDIRNAMESLOT_FIRST_SLOT_FLAG) <= FATDIRNAMESLOT_HIGHEST_SLOT_ID
+ && (paEntries[iEntry].Slot.idSlot & ~FATDIRNAMESLOT_FIRST_SLOT_FLAG) != 0)
+ { /* skipped */ }
+ /*
+ * Regular directory entry. Do the matching, first 8.3 then long name.
+ */
+ else if (memcmp(paEntries[iEntry].Entry.achName, pszName8Dot3, sizeof(paEntries[iEntry].Entry.achName)) == 0)
+ {
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock);
+ return VINF_SUCCESS;
+ }
+ }
+
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock);
+ }
+
+ return VERR_FILE_NOT_FOUND;
+}
+
+
+/**
+ * Calculates the FATDIRENTRY::fCase flags for the given name.
+ *
+ * ASSUMES that the name is a 8.3 name.
+ *
+ * @returns Case flag mask.
+ * @param pszName The name.
+ */
+static uint8_t rtFsFatDir_CalcCaseFlags(const char *pszName)
+{
+ uint8_t bRet = FATDIRENTRY_CASE_F_LOWER_BASE | FATDIRENTRY_CASE_F_LOWER_EXT;
+ uint8_t bCurrent = FATDIRENTRY_CASE_F_LOWER_BASE;
+ for (;;)
+ {
+ RTUNICP uc;
+ int rc = RTStrGetCpEx(&pszName, &uc);
+ if (RT_SUCCESS(rc))
+ {
+ if (uc != 0)
+ {
+ if (uc != '.')
+ {
+ if (RTUniCpIsUpper(uc))
+ {
+ bRet &= ~bCurrent;
+ if (!bRet)
+ return 0;
+ }
+ }
+ else
+ bCurrent = FATDIRENTRY_CASE_F_LOWER_EXT;
+ }
+ else if (bCurrent == FATDIRENTRY_CASE_F_LOWER_BASE)
+ return bRet & ~FATDIRENTRY_CASE_F_LOWER_EXT;
+ else
+ return bRet;
+ }
+ else
+ return 0;
+ }
+}
+
+
+/**
+ * Checks if we need to generate a long name for @a pszEntry.
+ *
+ * @returns true if we need to, false if we don't.
+ * @param pszEntry The UTF-8 directory entry entry name.
+ * @param fIs8Dot3Name Whether we've managed to create a 8-dot-3 name.
+ * @param pDirEntry The directory entry with the 8-dot-3 name when
+ * fIs8Dot3Name is set.
+ */
+static bool rtFsFatDir_NeedLongName(const char *pszEntry, bool fIs8Dot3Name, PCFATDIRENTRY pDirEntry)
+{
+ /*
+ * Check the easy ways out first.
+ */
+
+ /* If we couldn't make a straight 8-dot-3 name out of it, the we
+ must do the long name thing. No question. */
+ if (!fIs8Dot3Name)
+ return true;
+
+ /* If both lower case flags are set, then the whole name must be
+ lowercased, so we won't need a long entry. */
+ if (pDirEntry->fCase == (FATDIRENTRY_CASE_F_LOWER_BASE | FATDIRENTRY_CASE_F_LOWER_EXT))
+ return false;
+
+ /*
+ * Okay, check out the whole string then, part by part. (This is code
+ * similar to rtFsFatDir_CalcCaseFlags.)
+ */
+ uint8_t fCurrent = pDirEntry->fCase & FATDIRENTRY_CASE_F_LOWER_BASE;
+ for (;;)
+ {
+ RTUNICP uc;
+ int rc = RTStrGetCpEx(&pszEntry, &uc);
+ if (RT_SUCCESS(rc))
+ {
+ if (uc != 0)
+ {
+ if (uc != '.')
+ {
+ if ( fCurrent
+ || !RTUniCpIsLower(uc))
+ { /* okay */ }
+ else
+ return true;
+ }
+ else
+ fCurrent = pDirEntry->fCase & FATDIRENTRY_CASE_F_LOWER_EXT;
+ }
+ /* It checked out to the end, so we don't need a long name. */
+ else
+ return false;
+ }
+ else
+ return true;
+ }
+}
+
+
+/**
+ * Checks if the given long name is valid for a long file name or not.
+ *
+ * Encoding, length and character set limitations are checked.
+ *
+ * @returns IRPT status code.
+ * @param pwszEntry The long filename.
+ * @param cwc The length of the filename in UTF-16 chars.
+ */
+static int rtFsFatDir_ValidateLongName(PCRTUTF16 pwszEntry, size_t cwc)
+{
+ /* Length limitation. */
+ if (cwc <= RTFSFAT_MAX_LFN_CHARS)
+ {
+ /* Character set limitations. */
+ for (size_t off = 0; off < cwc; off++)
+ {
+ RTUTF16 wc = pwszEntry[off];
+ if (wc < 128)
+ {
+ if (g_awchFatCp437ValidChars[wc] <= UINT16_C(0xfffe))
+ { /* likely */ }
+ else
+ return VERR_INVALID_NAME;
+ }
+ }
+
+ /* Name limitations. */
+ if ( cwc == 1
+ && pwszEntry[0] == '.')
+ return VERR_INVALID_NAME;
+ if ( cwc == 2
+ && pwszEntry[0] == '.'
+ && pwszEntry[1] == '.')
+ return VERR_INVALID_NAME;
+
+ /** @todo Check for more invalid names, also in the 8.3 case! */
+ return VINF_SUCCESS;
+ }
+ return VERR_FILENAME_TOO_LONG;
+}
+
+
+/**
+ * Worker for rtFsFatDirShrd_GenerateShortName.
+ */
+static void rtFsFatDir_CopyShortName(char *pszDst, uint32_t cchDst, const char *pszSrc, size_t cchSrc, char chPad)
+{
+ /* Copy from source. */
+ if (cchSrc > 0)
+ {
+ const char *pszSrcEnd = &pszSrc[cchSrc];
+ while (cchDst > 0 && pszSrc != pszSrcEnd)
+ {
+ RTUNICP uc;
+ int rc = RTStrGetCpEx(&pszSrc, &uc);
+ if (RT_SUCCESS(rc))
+ {
+ if (uc < 128)
+ {
+ if (g_awchFatCp437ValidChars[uc] != uc)
+ {
+ if (uc)
+ {
+ uc = RTUniCpToUpper(uc);
+ if (g_awchFatCp437ValidChars[uc] != uc)
+ uc = '_';
+ }
+ else
+ break;
+ }
+ }
+ else
+ uc = '_';
+ }
+ else
+ uc = '_';
+
+ *pszDst++ = (char)uc;
+ cchDst--;
+ }
+ }
+
+ /* Pad the remaining space. */
+ while (cchDst-- > 0)
+ *pszDst++ = chPad;
+}
+
+
+/**
+ * Generates a short filename.
+ *
+ * @returns IPRT status code.
+ * @param pThis The directory.
+ * @param pszEntry The long name (UTF-8).
+ * @param pDirEntry Where to put the short name.
+ */
+static int rtFsFatDirShrd_GenerateShortName(PRTFSFATDIRSHRD pThis, const char *pszEntry, PFATDIRENTRY pDirEntry)
+{
+ /* Do some input parsing. */
+ const char *pszExt = RTPathSuffix(pszEntry);
+ size_t const cchBasename = pszExt ? pszExt - pszEntry : strlen(pszEntry);
+ size_t const cchExt = pszExt ? strlen(++pszExt) : 0;
+
+ /* Fill in the extension first. It stays the same. */
+ char szShortName[8+3+1];
+ rtFsFatDir_CopyShortName(&szShortName[8], 3, pszExt, cchExt, ' ');
+ szShortName[8+3] = '\0';
+
+ /*
+ * First try single digit 1..9.
+ */
+ rtFsFatDir_CopyShortName(szShortName, 6, pszEntry, cchBasename, '_');
+ szShortName[6] = '~';
+ for (uint32_t iLastDigit = 1; iLastDigit < 10; iLastDigit++)
+ {
+ szShortName[7] = iLastDigit + '0';
+ int rc = rtFsFatDirShrd_FindEntryShort(pThis, szShortName);
+ if (rc == VERR_FILE_NOT_FOUND)
+ {
+ memcpy(pDirEntry->achName, szShortName, sizeof(pDirEntry->achName));
+ return VINF_SUCCESS;
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * First try two digits 10..99.
+ */
+ szShortName[5] = '~';
+ for (uint32_t iFirstDigit = 1; iFirstDigit < 10; iFirstDigit++)
+ for (uint32_t iLastDigit = 0; iLastDigit < 10; iLastDigit++)
+ {
+ szShortName[6] = iFirstDigit + '0';
+ szShortName[7] = iLastDigit + '0';
+ int rc = rtFsFatDirShrd_FindEntryShort(pThis, szShortName);
+ if (rc == VERR_FILE_NOT_FOUND)
+ {
+ memcpy(pDirEntry->achName, szShortName, sizeof(pDirEntry->achName));
+ return VINF_SUCCESS;
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Okay, do random numbers then.
+ */
+ szShortName[2] = '~';
+ for (uint32_t i = 0; i < 8192; i++)
+ {
+ char szHex[68];
+ ssize_t cchHex = RTStrFormatU32(szHex, sizeof(szHex), RTRandU32(), 16, 5, 0, RTSTR_F_CAPITAL | RTSTR_F_WIDTH | RTSTR_F_ZEROPAD);
+ AssertReturn(cchHex >= 5, VERR_NET_NOT_UNIQUE_NAME);
+ szShortName[7] = szHex[cchHex - 1];
+ szShortName[6] = szHex[cchHex - 2];
+ szShortName[5] = szHex[cchHex - 3];
+ szShortName[4] = szHex[cchHex - 4];
+ szShortName[3] = szHex[cchHex - 5];
+ int rc = rtFsFatDirShrd_FindEntryShort(pThis, szShortName);
+ if (rc == VERR_FILE_NOT_FOUND)
+ {
+ memcpy(pDirEntry->achName, szShortName, sizeof(pDirEntry->achName));
+ return VINF_SUCCESS;
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ return VERR_NET_NOT_UNIQUE_NAME;
+}
+
+
+/**
+ * Considers whether we need to create a long name or not.
+ *
+ * If a long name is needed and the name wasn't 8-dot-3 compatible, a 8-dot-3
+ * name will be generated and stored in *pDirEntry.
+ *
+ * @returns IPRT status code
+ * @param pThis The directory.
+ * @param pszEntry The name.
+ * @param fIs8Dot3Name Whether we have a 8-dot-3 name already.
+ * @param pDirEntry Where to return the generated 8-dot-3 name.
+ * @param paSlots Where to return the long name entries. The array
+ * can hold at least FATDIRNAMESLOT_MAX_SLOTS entries.
+ * @param pcSlots Where to return the actual number of slots used.
+ */
+static int rtFsFatDirShrd_MaybeCreateLongNameAndShortAlias(PRTFSFATDIRSHRD pThis, const char *pszEntry, bool fIs8Dot3Name,
+ PFATDIRENTRY pDirEntry, PFATDIRNAMESLOT paSlots, uint32_t *pcSlots)
+{
+ RT_NOREF(pThis, pDirEntry, paSlots, pszEntry);
+
+ /*
+ * If we don't need to create a long name, return immediately.
+ */
+ if (!rtFsFatDir_NeedLongName(pszEntry, fIs8Dot3Name, pDirEntry))
+ {
+ *pcSlots = 0;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Convert the name to UTF-16 and figure it's length (this validates the
+ * input encoding). Then do long name validation (length, charset limitation).
+ */
+ RTUTF16 wszEntry[FATDIRNAMESLOT_MAX_SLOTS * FATDIRNAMESLOT_CHARS_PER_SLOT + 4];
+ PRTUTF16 pwszEntry = wszEntry;
+ size_t cwcEntry;
+ int rc = RTStrToUtf16Ex(pszEntry, RTSTR_MAX, &pwszEntry, RT_ELEMENTS(wszEntry), &cwcEntry);
+ if (RT_SUCCESS(rc))
+ rc = rtFsFatDir_ValidateLongName(pwszEntry, cwcEntry);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Generate a short name if we need to.
+ */
+ if (!fIs8Dot3Name)
+ rc = rtFsFatDirShrd_GenerateShortName(pThis, pszEntry, pDirEntry);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Fill in the long name slots. First we pad the wszEntry with 0xffff
+ * until it is a multiple of of the slot count. That way we can copy
+ * the name straight into the entry without constaints.
+ */
+ memset(&wszEntry[cwcEntry + 1], 0xff,
+ RT_MIN(sizeof(wszEntry) - (cwcEntry + 1) * sizeof(RTUTF16),
+ FATDIRNAMESLOT_CHARS_PER_SLOT * sizeof(RTUTF16)));
+
+ uint8_t const bChecksum = rtFsFatDir_CalcChecksum(pDirEntry);
+ size_t const cSlots = (cwcEntry + FATDIRNAMESLOT_CHARS_PER_SLOT - 1) / FATDIRNAMESLOT_CHARS_PER_SLOT;
+ size_t iSlot = cSlots;
+ PCRTUTF16 pwszSrc = wszEntry;
+ while (iSlot-- > 0)
+ {
+ memcpy(paSlots[iSlot].awcName0, pwszSrc, sizeof(paSlots[iSlot].awcName0));
+ pwszSrc += RT_ELEMENTS(paSlots[iSlot].awcName0);
+ memcpy(paSlots[iSlot].awcName1, pwszSrc, sizeof(paSlots[iSlot].awcName1));
+ pwszSrc += RT_ELEMENTS(paSlots[iSlot].awcName1);
+ memcpy(paSlots[iSlot].awcName2, pwszSrc, sizeof(paSlots[iSlot].awcName2));
+ pwszSrc += RT_ELEMENTS(paSlots[iSlot].awcName2);
+
+ paSlots[iSlot].idSlot = (uint8_t)(cSlots - iSlot);
+ paSlots[iSlot].fAttrib = FAT_ATTR_NAME_SLOT;
+ paSlots[iSlot].fZero = 0;
+ paSlots[iSlot].idxZero = 0;
+ paSlots[iSlot].bChecksum = bChecksum;
+ }
+ paSlots[0].idSlot |= FATDIRNAMESLOT_FIRST_SLOT_FLAG;
+ *pcSlots = (uint32_t)cSlots;
+ return VINF_SUCCESS;
+ }
+ }
+ *pcSlots = UINT32_MAX;
+ return rc;
+}
+
+
+/**
+ * Searches the directory for a given number of free directory entries.
+ *
+ * The free entries must be consecutive of course.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_DISK_FULL if no space was found, *pcFreeTail set.
+ * @param pThis The directory to search.
+ * @param cEntriesNeeded How many entries we need.
+ * @param poffEntryInDir Where to return the offset of the first entry we
+ * found.
+ * @param pcFreeTail Where to return the number of free entries at the
+ * end of the directory when VERR_DISK_FULL is
+ * returned.
+ */
+static int rtFsFatChain_FindFreeEntries(PRTFSFATDIRSHRD pThis, uint32_t cEntriesNeeded,
+ uint32_t *poffEntryInDir, uint32_t *pcFreeTail)
+{
+ /* First try make gcc happy. */
+ *pcFreeTail = 0;
+ *poffEntryInDir = UINT32_MAX;
+
+ /*
+ * Scan the whole directory, buffer by buffer.
+ */
+ uint32_t offStartFreeEntries = UINT32_MAX;
+ uint32_t cFreeEntries = 0;
+ uint32_t offEntryInDir = 0;
+ uint32_t const cbDir = pThis->Core.cbObject;
+ Assert(RT_ALIGN_32(cbDir, sizeof(FATDIRENTRY)) == cbDir);
+ while (offEntryInDir < cbDir)
+ {
+ /* Get chunk of entries starting at offEntryInDir. */
+ uint32_t uBufferLock = UINT32_MAX;
+ uint32_t cEntries = 0;
+ PCFATDIRENTRYUNION paEntries = NULL;
+ int rc = rtFsFatDirShrd_GetEntriesAt(pThis, offEntryInDir, &paEntries, &cEntries, &uBufferLock);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Now work thru each of the entries.
+ */
+ for (uint32_t iEntry = 0; iEntry < cEntries; iEntry++, offEntryInDir += sizeof(FATDIRENTRY))
+ {
+ uint8_t const bFirst = paEntries[iEntry].Entry.achName[0];
+ if ( bFirst == FATDIRENTRY_CH0_DELETED
+ || bFirst == FATDIRENTRY_CH0_END_OF_DIR)
+ {
+ if (offStartFreeEntries != UINT32_MAX)
+ cFreeEntries++;
+ else
+ {
+ offStartFreeEntries = offEntryInDir;
+ cFreeEntries = 1;
+ }
+ if (cFreeEntries >= cEntriesNeeded)
+ {
+ *pcFreeTail = cEntriesNeeded;
+ *poffEntryInDir = offStartFreeEntries;
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock);
+ return VINF_SUCCESS;
+ }
+
+ if (bFirst == FATDIRENTRY_CH0_END_OF_DIR)
+ {
+ if (pThis->Core.pVol->enmBpbVersion >= RTFSFATBPBVER_DOS_2_0)
+ {
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock);
+ *pcFreeTail = cFreeEntries = (cbDir - offStartFreeEntries) / sizeof(FATDIRENTRY);
+ if (cFreeEntries >= cEntriesNeeded)
+ {
+ *poffEntryInDir = offStartFreeEntries;
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock);
+ return VINF_SUCCESS;
+ }
+ return VERR_DISK_FULL;
+ }
+ }
+ }
+ else if (offStartFreeEntries != UINT32_MAX)
+ {
+ offStartFreeEntries = UINT32_MAX;
+ cFreeEntries = 0;
+ }
+ }
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock);
+ }
+ *pcFreeTail = cFreeEntries;
+ return VERR_DISK_FULL;
+}
+
+
+/**
+ * Try grow the directory.
+ *
+ * This is not called on the root directory.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_DISK_FULL if we failed to allocated new space.
+ * @param pThis The directory to grow.
+ * @param cMinNewEntries The minimum number of new entries to allocated.
+ */
+static int rtFsFatChain_GrowDirectory(PRTFSFATDIRSHRD pThis, uint32_t cMinNewEntries)
+{
+ RT_NOREF(pThis, cMinNewEntries);
+ return VERR_DISK_FULL;
+}
+
+
+/**
+ * Inserts a directory with zero of more long name slots preceeding it.
+ *
+ * @returns IPRT status code.
+ * @param pThis The directory.
+ * @param pDirEntry The directory entry.
+ * @param paSlots The long name slots.
+ * @param cSlots The number of long name slots.
+ * @param poffEntryInDir Where to return the directory offset.
+ */
+static int rtFsFatChain_InsertEntries(PRTFSFATDIRSHRD pThis, PCFATDIRENTRY pDirEntry, PFATDIRNAMESLOT paSlots, uint32_t cSlots,
+ uint32_t *poffEntryInDir)
+{
+ uint32_t const cTotalEntries = cSlots + 1;
+
+ /*
+ * Find somewhere to put the entries. Try extend the directory if we're
+ * not successful at first.
+ */
+ uint32_t cFreeTailEntries;
+ uint32_t offFirstInDir;
+ int rc = rtFsFatChain_FindFreeEntries(pThis, cTotalEntries, &offFirstInDir, &cFreeTailEntries);
+ if (rc == VERR_DISK_FULL)
+ {
+ Assert(cFreeTailEntries < cTotalEntries);
+
+ /* Try grow it and use the newly allocated space. */
+ if ( pThis->Core.pParentDir
+ && pThis->cEntries < _64K /* Don't grow beyond 64K entries */)
+ {
+ offFirstInDir = pThis->Core.cbObject - cFreeTailEntries * sizeof(FATDIRENTRY);
+ rc = rtFsFatChain_GrowDirectory(pThis, cTotalEntries - cFreeTailEntries);
+ }
+
+ if (rc == VERR_DISK_FULL)
+ {
+ /** @todo Try compact the directory if we couldn't grow it. */
+ }
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Update the directory.
+ */
+ uint32_t offCurrent = offFirstInDir;
+ for (uint32_t iSrcSlot = 0; iSrcSlot < cTotalEntries; iSrcSlot++, offCurrent += sizeof(FATDIRENTRY))
+ {
+ uint32_t uBufferLock;
+ PFATDIRENTRY pDstEntry;
+ rc = rtFsFatDirShrd_GetEntryForUpdate(pThis, offCurrent, &pDstEntry, &uBufferLock);
+ if (RT_SUCCESS(rc))
+ {
+ if (iSrcSlot < cSlots)
+ memcpy(pDstEntry, &paSlots[iSrcSlot], sizeof(*pDstEntry));
+ else
+ memcpy(pDstEntry, pDirEntry, sizeof(*pDstEntry));
+ rc = rtFsFatDirShrd_PutEntryAfterUpdate(pThis, pDstEntry, uBufferLock);
+ if (RT_SUCCESS(rc))
+ continue;
+
+ /*
+ * Bail out: Try mark any edited entries as deleted.
+ */
+ iSrcSlot++;
+ }
+ while (iSrcSlot-- > 0)
+ {
+ int rc2 = rtFsFatDirShrd_GetEntryForUpdate(pThis, offFirstInDir + iSrcSlot * sizeof(FATDIRENTRY),
+ &pDstEntry, &uBufferLock);
+ if (RT_SUCCESS(rc2))
+ {
+ pDstEntry->achName[0] = FATDIRENTRY_CH0_DELETED;
+ rtFsFatDirShrd_PutEntryAfterUpdate(pThis, pDstEntry, uBufferLock);
+ }
+ }
+ *poffEntryInDir = UINT32_MAX;
+ return rc;
+ }
+ AssertRC(rc);
+
+ /*
+ * Successfully inserted all.
+ */
+ *poffEntryInDir = offFirstInDir + cSlots * sizeof(FATDIRENTRY);
+ return VINF_SUCCESS;
+ }
+
+ *poffEntryInDir = UINT32_MAX;
+ return rc;
+}
+
+
+
+/**
+ * Creates a new directory entry.
+ *
+ * @returns IPRT status code
+ * @param pThis The directory.
+ * @param pszEntry The name of the new entry.
+ * @param fAttrib The attributes.
+ * @param cbInitial The initialize size.
+ * @param poffEntryInDir Where to return the offset of the directory entry.
+ * @param pDirEntry Where to return a copy of the directory entry.
+ *
+ * @remarks ASSUMES caller has already called rtFsFatDirShrd_FindEntry to make sure
+ * the entry doesn't exist.
+ */
+static int rtFsFatDirShrd_CreateEntry(PRTFSFATDIRSHRD pThis, const char *pszEntry, uint8_t fAttrib, uint32_t cbInitial,
+ uint32_t *poffEntryInDir, PFATDIRENTRY pDirEntry)
+{
+ PRTFSFATVOL pVol = pThis->Core.pVol;
+ *poffEntryInDir = UINT32_MAX;
+ if (pVol->fReadOnly)
+ return VERR_WRITE_PROTECT;
+
+ /*
+ * Create the directory entries on the stack.
+ */
+ bool fIs8Dot3Name = rtFsFatDir_StringTo8Dot3((char *)pDirEntry->achName, pszEntry);
+ pDirEntry->fAttrib = fAttrib;
+ pDirEntry->fCase = fIs8Dot3Name ? rtFsFatDir_CalcCaseFlags(pszEntry) : 0;
+ pDirEntry->uBirthCentiseconds = rtFsFatCurrentFatDateTime(pVol, &pDirEntry->uBirthDate, &pDirEntry->uBirthTime);
+ pDirEntry->uAccessDate = pDirEntry->uBirthDate;
+ pDirEntry->uModifyDate = pDirEntry->uBirthDate;
+ pDirEntry->uModifyTime = pDirEntry->uBirthTime;
+ pDirEntry->idxCluster = 0; /* Will fill this in later if cbInitial is non-zero. */
+ pDirEntry->u.idxClusterHigh = 0;
+ pDirEntry->cbFile = cbInitial;
+
+ /*
+ * Create long filename slots if necessary.
+ */
+ uint32_t cSlots = UINT32_MAX;
+ FATDIRNAMESLOT aSlots[FATDIRNAMESLOT_MAX_SLOTS];
+ AssertCompile(RTFSFAT_MAX_LFN_CHARS < RT_ELEMENTS(aSlots) * FATDIRNAMESLOT_CHARS_PER_SLOT);
+ int rc = rtFsFatDirShrd_MaybeCreateLongNameAndShortAlias(pThis, pszEntry, fIs8Dot3Name, pDirEntry, aSlots, &cSlots);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cSlots <= FATDIRNAMESLOT_MAX_SLOTS);
+
+ /*
+ * Allocate initial clusters if requested.
+ */
+ RTFSFATCHAIN Clusters;
+ rtFsFatChain_InitEmpty(&Clusters, pVol);
+ if (cbInitial > 0)
+ {
+ rc = rtFsFatClusterMap_AllocateMoreClusters(pVol, &Clusters,
+ (cbInitial + Clusters.cbCluster - 1) >> Clusters.cClusterByteShift);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t idxFirstCluster = rtFsFatChain_GetFirstCluster(&Clusters);
+ pDirEntry->idxCluster = (uint16_t)idxFirstCluster;
+ if (pVol->enmFatType >= RTFSFATTYPE_FAT32)
+ pDirEntry->u.idxClusterHigh = (uint16_t)(idxFirstCluster >> 16);
+ }
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Insert the directory entry and name slots.
+ */
+ rc = rtFsFatChain_InsertEntries(pThis, pDirEntry, aSlots, cSlots, poffEntryInDir);
+ if (RT_SUCCESS(rc))
+ {
+ rtFsFatChain_Delete(&Clusters);
+ return VINF_SUCCESS;
+ }
+
+ for (uint32_t iClusterToFree = 0; iClusterToFree < Clusters.cClusters; iClusterToFree++)
+ rtFsFatClusterMap_FreeCluster(pVol, rtFsFatChain_GetClusterByIndex(&Clusters, iClusterToFree));
+ rtFsFatChain_Delete(&Clusters);
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Releases a reference to a shared directory structure.
+ *
+ * @param pShared The shared directory structure.
+ */
+static int rtFsFatDirShrd_Release(PRTFSFATDIRSHRD pShared)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pShared->Core.cRefs);
+ Assert(cRefs < UINT32_MAX / 2);
+ if (cRefs == 0)
+ {
+ LogFlow(("rtFsFatDirShrd_Release: Destroying shared structure %p\n", pShared));
+ Assert(pShared->Core.cRefs == 0);
+
+ int rc;
+ if (pShared->paEntries)
+ {
+ rc = rtFsFatDirShrd_Flush(pShared);
+ RTMemFree(pShared->paEntries);
+ pShared->paEntries = NULL;
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ if ( pShared->fFullyBuffered
+ && pShared->u.Full.pbDirtySectors)
+ {
+ RTMemFree(pShared->u.Full.pbDirtySectors);
+ pShared->u.Full.pbDirtySectors = NULL;
+ }
+
+ int rc2 = rtFsFatObj_Close(&pShared->Core);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ RTMemFree(pShared);
+ return rc;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Retains a reference to a shared directory structure.
+ *
+ * @param pShared The shared directory structure.
+ */
+static void rtFsFatDirShrd_Retain(PRTFSFATDIRSHRD pShared)
+{
+ uint32_t cRefs = ASMAtomicIncU32(&pShared->Core.cRefs);
+ Assert(cRefs > 1); NOREF(cRefs);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsFatDir_Close(void *pvThis)
+{
+ PRTFSFATDIR pThis = (PRTFSFATDIR)pvThis;
+ PRTFSFATDIRSHRD pShared = pThis->pShared;
+ pThis->pShared = NULL;
+ if (pShared)
+ return rtFsFatDirShrd_Release(pShared);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsFatDir_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSFATDIR pThis = (PRTFSFATDIR)pvThis;
+ return rtFsFatObj_QueryInfo(&pThis->pShared->Core, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtFsFatDir_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ PRTFSFATDIR pThis = (PRTFSFATDIR)pvThis;
+ return rtFsFatObj_SetMode(&pThis->pShared->Core, fMode, fMask);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtFsFatDir_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ PRTFSFATDIR pThis = (PRTFSFATDIR)pvThis;
+ return rtFsFatObj_SetTimes(&pThis->pShared->Core, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtFsFatDir_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ RT_NOREF(pvThis, uid, gid);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpen}
+ */
+static DECLCALLBACK(int) rtFsFatDir_Open(void *pvThis, const char *pszEntry, uint64_t fOpen,
+ uint32_t fFlags, PRTVFSOBJ phVfsObj)
+{
+ PRTFSFATDIR pThis = (PRTFSFATDIR)pvThis;
+ PRTFSFATDIRSHRD pShared = pThis->pShared;
+ int rc;
+
+ /*
+ * Special cases '.' and '.'
+ */
+ if (pszEntry[0] == '.')
+ {
+ PRTFSFATDIRSHRD 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)
+ {
+ if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE)
+ {
+ rtFsFatDirShrd_Retain(pSharedToOpen);
+ RTVFSDIR hVfsDir;
+ rc = rtFsFatDir_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_ACCESS_DENIED;
+ }
+ else
+ rc = VERR_IS_A_DIRECTORY;
+ return rc;
+ }
+ }
+
+ /*
+ * Try open existing file.
+ */
+ uint32_t offEntryInDir;
+ bool fLong;
+ FATDIRENTRY DirEntry;
+ rc = rtFsFatDirShrd_FindEntry(pShared, pszEntry, &offEntryInDir, &fLong, &DirEntry);
+ if (RT_SUCCESS(rc))
+ {
+ switch (DirEntry.fAttrib & (FAT_ATTR_DIRECTORY | FAT_ATTR_VOLUME))
+ {
+ case 0:
+ if (fFlags & RTVFSOBJ_F_OPEN_FILE)
+ {
+ if ( !(DirEntry.fAttrib & FAT_ATTR_READONLY)
+ || !(fOpen & RTFILE_O_WRITE))
+ {
+ if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE)
+ {
+ RTVFSFILE hVfsFile;
+ rc = rtFsFatFile_New(pShared->Core.pVol, pShared, &DirEntry, offEntryInDir, fOpen, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromFile(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ else
+ rc = VERR_ALREADY_EXISTS;
+ }
+ else
+ rc = VERR_ACCESS_DENIED;
+ }
+ else
+ rc = VERR_IS_A_FILE;
+ break;
+
+ case FAT_ATTR_DIRECTORY:
+ if (fFlags & RTVFSOBJ_F_OPEN_DIRECTORY)
+ {
+ if ( !(DirEntry.fAttrib & FAT_ATTR_READONLY)
+ || !(fOpen & RTFILE_O_WRITE))
+ {
+ if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE)
+ {
+ RTVFSDIR hVfsDir;
+ rc = rtFsFatDir_New(pShared->Core.pVol, pShared, &DirEntry, offEntryInDir,
+ RTFSFAT_GET_CLUSTER(&DirEntry, pShared->Core.pVol), UINT64_MAX /*offDisk*/,
+ DirEntry.cbFile, &hVfsDir);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromDir(hVfsDir);
+ RTVfsDirRelease(hVfsDir);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ else if ((fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE)
+ rc = VERR_INVALID_FUNCTION;
+ else
+ rc = VERR_ALREADY_EXISTS;
+ }
+ else
+ rc = VERR_ACCESS_DENIED;
+ }
+ else
+ rc = VERR_IS_A_DIRECTORY;
+ break;
+
+ default:
+ rc = VERR_PATH_NOT_FOUND;
+ break;
+ }
+ }
+ /*
+ * Create a file or directory?
+ */
+ else if (rc == VERR_FILE_NOT_FOUND)
+ {
+ if ( ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE)
+ && (fFlags & RTVFSOBJ_F_CREATE_MASK) != RTVFSOBJ_F_CREATE_NOTHING)
+ {
+ if ((fFlags & RTVFSOBJ_F_CREATE_MASK) == RTVFSOBJ_F_CREATE_FILE)
+ {
+ rc = rtFsFatDirShrd_CreateEntry(pShared, pszEntry, FAT_ATTR_ARCHIVE, 0 /*cbInitial*/, &offEntryInDir, &DirEntry);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSFILE hVfsFile;
+ rc = rtFsFatFile_New(pShared->Core.pVol, pShared, &DirEntry, offEntryInDir, fOpen, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromFile(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ }
+ else if ((fFlags & RTVFSOBJ_F_CREATE_MASK) == RTVFSOBJ_F_CREATE_DIRECTORY)
+ {
+ rc = rtFsFatDirShrd_CreateEntry(pShared, pszEntry, FAT_ATTR_ARCHIVE | FAT_ATTR_DIRECTORY,
+ pShared->Core.pVol->cbCluster, &offEntryInDir, &DirEntry);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSDIR hVfsDir;
+ rc = rtFsFatDir_New(pShared->Core.pVol, pShared, &DirEntry, offEntryInDir,
+ RTFSFAT_GET_CLUSTER(&DirEntry, pShared->Core.pVol), UINT64_MAX /*offDisk*/,
+ DirEntry.cbFile, &hVfsDir);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromDir(hVfsDir);
+ RTVfsDirRelease(hVfsDir);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ }
+ else
+ rc = VERR_VFS_UNSUPPORTED_CREATE_TYPE;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpenSymlink}
+ */
+static DECLCALLBACK(int) rtFsFatDir_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) rtFsFatDir_CreateSymlink(void *pvThis, const char *pszSymlink, const char *pszTarget,
+ RTSYMLINKTYPE enmType, PRTVFSSYMLINK phVfsSymlink)
+{
+ RT_NOREF(pvThis, pszSymlink, pszTarget, enmType, phVfsSymlink);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnUnlinkEntry}
+ */
+static DECLCALLBACK(int) rtFsFatDir_UnlinkEntry(void *pvThis, const char *pszEntry, RTFMODE fType)
+{
+ RT_NOREF(pvThis, pszEntry, fType);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRenameEntry}
+ */
+static DECLCALLBACK(int) rtFsFatDir_RenameEntry(void *pvThis, const char *pszEntry, RTFMODE fType, const char *pszNewName)
+{
+ RT_NOREF(pvThis, pszEntry, fType, pszNewName);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRewindDir}
+ */
+static DECLCALLBACK(int) rtFsFatDir_RewindDir(void *pvThis)
+{
+ PRTFSFATDIR pThis = (PRTFSFATDIR)pvThis;
+ pThis->offDir = 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Calculates the UTF-8 length of the name in the given directory entry.
+ *
+ * @returns The length in characters (bytes), excluding terminator.
+ * @param pShared The shared directory structure (for codepage).
+ * @param pEntry The directory entry.
+ */
+static size_t rtFsFatDir_CalcUtf8LengthForDirEntry(PRTFSFATDIRSHRD pShared, PCFATDIRENTRY pEntry)
+{
+ RT_NOREF(pShared);
+ PCRTUTF16 g_pawcMap = &g_awchFatCp437Chars[0];
+
+ /* The base name (this won't work with DBCS, but that's not a concern at the moment). */
+ size_t offSrc = 8;
+ while (offSrc > 1 && RTUniCpIsSpace(g_pawcMap[pEntry->achName[offSrc - 1]]))
+ offSrc--;
+
+ size_t cchRet = 0;
+ while (offSrc-- > 0)
+ cchRet += RTStrCpSize(g_pawcMap[pEntry->achName[offSrc]]);
+
+ /* Extension. */
+ offSrc = 11;
+ while (offSrc > 8 && RTUniCpIsSpace(g_pawcMap[pEntry->achName[offSrc - 1]]))
+ offSrc--;
+ if (offSrc > 8)
+ {
+ cchRet += 1; /* '.' */
+ while (offSrc-- > 8)
+ cchRet += RTStrCpSize(g_pawcMap[pEntry->achName[offSrc]]);
+ }
+
+ return cchRet;
+}
+
+
+/**
+ * Copies the name from the directory entry into a UTF-16 buffer.
+ *
+ * @returns Number of UTF-16 items written (excluding terminator).
+ * @param pShared The shared directory structure (for codepage).
+ * @param pEntry The directory entry.
+ * @param pwszDst The destination buffer.
+ * @param cwcDst The destination buffer size.
+ */
+static uint16_t rtFsFatDir_CopyDirEntryToUtf16(PRTFSFATDIRSHRD pShared, PCFATDIRENTRY pEntry, PRTUTF16 pwszDst, size_t cwcDst)
+{
+ Assert(cwcDst > 0);
+
+ RT_NOREF(pShared);
+ PCRTUTF16 g_pawcMap = &g_awchFatCp437Chars[0];
+
+ /* The base name (this won't work with DBCS, but that's not a concern at the moment). */
+ size_t cchSrc = 8;
+ while (cchSrc > 1 && RTUniCpIsSpace(g_pawcMap[pEntry->achName[cchSrc - 1]]))
+ cchSrc--;
+
+ size_t offDst = 0;
+ for (size_t offSrc = 0; offSrc < cchSrc; offSrc++)
+ {
+ AssertReturnStmt(offDst + 1 < cwcDst, pwszDst[cwcDst - 1] = '\0', (uint16_t)cwcDst);
+ pwszDst[offDst++] = g_pawcMap[pEntry->achName[offSrc]];
+ }
+
+ /* Extension. */
+ cchSrc = 3;
+ while (cchSrc > 0 && RTUniCpIsSpace(g_pawcMap[pEntry->achName[8 + cchSrc - 1]]))
+ cchSrc--;
+ if (cchSrc > 0)
+ {
+ AssertReturnStmt(offDst + 1 < cwcDst, pwszDst[cwcDst - 1] = '\0', (uint16_t)cwcDst);
+ pwszDst[offDst++] = '.';
+
+ for (size_t offSrc = 0; offSrc < cchSrc; offSrc++)
+ {
+ AssertReturnStmt(offDst + 1 < cwcDst, pwszDst[cwcDst - 1] = '\0', (uint16_t)cwcDst);
+ pwszDst[offDst++] = g_pawcMap[pEntry->achName[8 + offSrc]];
+ }
+ }
+
+ pwszDst[offDst] = '\0';
+ return (uint16_t)offDst;
+}
+
+
+/**
+ * Copies the name from the directory entry into a UTF-8 buffer.
+ *
+ * @returns Number of UTF-16 items written (excluding terminator).
+ * @param pShared The shared directory structure (for codepage).
+ * @param pEntry The directory entry.
+ * @param pszDst The destination buffer.
+ * @param cbDst The destination buffer size.
+ */
+static uint16_t rtFsFatDir_CopyDirEntryToUtf8(PRTFSFATDIRSHRD pShared, PCFATDIRENTRY pEntry, char *pszDst, size_t cbDst)
+{
+ Assert(cbDst > 0);
+
+ RT_NOREF(pShared);
+ PCRTUTF16 g_pawcMap = &g_awchFatCp437Chars[0];
+
+ /* The base name (this won't work with DBCS, but that's not a concern at the moment). */
+ size_t cchSrc = 8;
+ while (cchSrc > 1 && RTUniCpIsSpace(g_pawcMap[pEntry->achName[cchSrc - 1]]))
+ cchSrc--;
+
+ char * const pszDstEnd = pszDst + cbDst;
+ char *pszCurDst = pszDst;
+ for (size_t offSrc = 0; offSrc < cchSrc; offSrc++)
+ {
+ RTUNICP const uc = g_pawcMap[pEntry->achName[offSrc]];
+ size_t cbCp = RTStrCpSize(uc);
+ AssertReturnStmt(cbCp < (size_t)(pszDstEnd - pszCurDst), *pszCurDst = '\0', (uint16_t)(pszDstEnd - pszCurDst));
+ pszCurDst = RTStrPutCp(pszCurDst, uc);
+ }
+
+ /* Extension. */
+ cchSrc = 3;
+ while (cchSrc > 0 && RTUniCpIsSpace(g_pawcMap[pEntry->achName[8 + cchSrc - 1]]))
+ cchSrc--;
+ if (cchSrc > 0)
+ {
+ AssertReturnStmt(1U < (size_t)(pszDstEnd - pszCurDst), *pszCurDst = '\0', (uint16_t)(pszDstEnd - pszCurDst));
+ *pszCurDst++ = '.';
+
+ for (size_t offSrc = 0; offSrc < cchSrc; offSrc++)
+ {
+ RTUNICP const uc = g_pawcMap[pEntry->achName[8 + offSrc]];
+ size_t cbCp = RTStrCpSize(uc);
+ AssertReturnStmt(cbCp < (size_t)(pszDstEnd - pszCurDst), *pszCurDst = '\0', (uint16_t)(pszDstEnd - pszCurDst));
+ pszCurDst = RTStrPutCp(pszCurDst, uc);
+ }
+ }
+
+ *pszCurDst = '\0';
+ return (uint16_t)(pszDstEnd - pszCurDst);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnReadDir}
+ */
+static DECLCALLBACK(int) rtFsFatDir_ReadDir(void *pvThis, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry,
+ RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSFATDIR pThis = (PRTFSFATDIR)pvThis;
+ PRTFSFATDIRSHRD pShared = pThis->pShared;
+
+ /*
+ * Fake '.' and '..' entries (required for root, we do it everywhere).
+ */
+ if (pThis->offDir < 2)
+ {
+ size_t cbNeeded = RT_UOFFSETOF_DYN(RTDIRENTRYEX, szName[pThis->offDir + 2]);
+ if (cbNeeded < *pcbDirEntry)
+ *pcbDirEntry = cbNeeded;
+ else
+ {
+ *pcbDirEntry = cbNeeded;
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ int rc;
+ if ( pThis->offDir == 0
+ || pShared->Core.pParentDir == NULL)
+ rc = rtFsFatObj_QueryInfo(&pShared->Core, &pDirEntry->Info, enmAddAttr);
+ else
+ rc = rtFsFatObj_QueryInfo(&pShared->Core.pParentDir->Core, &pDirEntry->Info, enmAddAttr);
+
+ pDirEntry->cwcShortName = 0;
+ pDirEntry->wszShortName[0] = '\0';
+ pDirEntry->szName[0] = '.';
+ pDirEntry->szName[1] = '.';
+ pDirEntry->szName[++pThis->offDir] = '\0';
+ pDirEntry->cbName = pThis->offDir;
+ return rc;
+ }
+ if ( pThis->offDir == 2
+ && pShared->cEntries >= 2)
+ {
+ /* Skip '.' and '..' entries if present. */
+ uint32_t uBufferLock = UINT32_MAX;
+ uint32_t cEntries = 0;
+ PCFATDIRENTRYUNION paEntries = NULL;
+ int rc = rtFsFatDirShrd_GetEntriesAt(pShared, 0, &paEntries, &cEntries, &uBufferLock);
+ if (RT_FAILURE(rc))
+ return rc;
+ if ( (paEntries[0].Entry.fAttrib & FAT_ATTR_DIRECTORY)
+ && memcmp(paEntries[0].Entry.achName, RT_STR_TUPLE(". ")) == 0)
+ {
+ if ( (paEntries[1].Entry.fAttrib & FAT_ATTR_DIRECTORY)
+ && memcmp(paEntries[1].Entry.achName, RT_STR_TUPLE(".. ")) == 0)
+ pThis->offDir += sizeof(paEntries[0]) * 2;
+ else
+ pThis->offDir += sizeof(paEntries[0]);
+ }
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pShared, uBufferLock);
+ }
+
+ /*
+ * Scan the directory buffer by buffer.
+ */
+ RTUTF16 wszName[260+1];
+ uint8_t bChecksum = UINT8_MAX;
+ uint8_t idNextSlot = UINT8_MAX;
+ size_t cwcName = 0;
+ uint32_t offEntryInDir = pThis->offDir - 2;
+ uint32_t const cbDir = pShared->Core.cbObject;
+ Assert(RT_ALIGN_32(cbDir, sizeof(*pDirEntry)) == cbDir);
+ AssertCompile(FATDIRNAMESLOT_MAX_SLOTS * FATDIRNAMESLOT_CHARS_PER_SLOT < RT_ELEMENTS(wszName));
+ wszName[260] = '\0';
+
+ while (offEntryInDir < cbDir)
+ {
+ /* Get chunk of entries starting at offEntryInDir. */
+ uint32_t uBufferLock = UINT32_MAX;
+ uint32_t cEntries = 0;
+ PCFATDIRENTRYUNION paEntries = NULL;
+ int rc = rtFsFatDirShrd_GetEntriesAt(pShared, offEntryInDir, &paEntries, &cEntries, &uBufferLock);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Now work thru each of the entries.
+ */
+ for (uint32_t iEntry = 0; iEntry < cEntries; iEntry++, offEntryInDir += sizeof(FATDIRENTRY))
+ {
+ switch ((uint8_t)paEntries[iEntry].Entry.achName[0])
+ {
+ default:
+ break;
+ case FATDIRENTRY_CH0_DELETED:
+ cwcName = 0;
+ continue;
+ case FATDIRENTRY_CH0_END_OF_DIR:
+ if (pShared->Core.pVol->enmBpbVersion >= RTFSFATBPBVER_DOS_2_0)
+ {
+ pThis->offDir = cbDir + 2;
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pShared, uBufferLock);
+ return VERR_NO_MORE_FILES;
+ }
+ cwcName = 0;
+ break; /* Technically a valid entry before DOS 2.0, or so some claim. */
+ }
+
+ /*
+ * Check for long filename slot.
+ */
+ if ( paEntries[iEntry].Slot.fAttrib == FAT_ATTR_NAME_SLOT
+ && paEntries[iEntry].Slot.idxZero == 0
+ && paEntries[iEntry].Slot.fZero == 0
+ && (paEntries[iEntry].Slot.idSlot & ~FATDIRNAMESLOT_FIRST_SLOT_FLAG) <= FATDIRNAMESLOT_HIGHEST_SLOT_ID
+ && (paEntries[iEntry].Slot.idSlot & ~FATDIRNAMESLOT_FIRST_SLOT_FLAG) != 0)
+ {
+ /* New slot? */
+ if (paEntries[iEntry].Slot.idSlot & FATDIRNAMESLOT_FIRST_SLOT_FLAG)
+ {
+ idNextSlot = paEntries[iEntry].Slot.idSlot & ~FATDIRNAMESLOT_FIRST_SLOT_FLAG;
+ bChecksum = paEntries[iEntry].Slot.bChecksum;
+ cwcName = idNextSlot * FATDIRNAMESLOT_CHARS_PER_SLOT;
+ wszName[cwcName] = '\0';
+ }
+ /* Is valid next entry? */
+ else if ( paEntries[iEntry].Slot.idSlot == idNextSlot
+ && paEntries[iEntry].Slot.bChecksum == bChecksum)
+ { /* likely */ }
+ else
+ cwcName = 0;
+ if (cwcName)
+ {
+ idNextSlot--;
+ size_t offName = idNextSlot * FATDIRNAMESLOT_CHARS_PER_SLOT;
+ memcpy(&wszName[offName], paEntries[iEntry].Slot.awcName0, sizeof(paEntries[iEntry].Slot.awcName0));
+ memcpy(&wszName[offName + 5], paEntries[iEntry].Slot.awcName1, sizeof(paEntries[iEntry].Slot.awcName1));
+ memcpy(&wszName[offName + 5 + 6], paEntries[iEntry].Slot.awcName2, sizeof(paEntries[iEntry].Slot.awcName2));
+ }
+ }
+ /*
+ * Got a regular directory entry. Try return it to the caller if not volume label.
+ */
+ else if (!(paEntries[iEntry].Entry.fAttrib & FAT_ATTR_VOLUME))
+ {
+ /* Do the length calc and check for overflows. */
+ bool fLongName = false;
+ size_t cchName = 0;
+ if ( cwcName != 0
+ && idNextSlot == 0
+ && rtFsFatDir_CalcChecksum(&paEntries[iEntry].Entry) == bChecksum)
+ {
+ rc = RTUtf16CalcUtf8LenEx(wszName, cwcName, &cchName);
+ if (RT_SUCCESS(rc))
+ fLongName = true;
+ }
+ if (!fLongName)
+ cchName = rtFsFatDir_CalcUtf8LengthForDirEntry(pShared, &paEntries[iEntry].Entry);
+ size_t cbNeeded = RT_UOFFSETOF_DYN(RTDIRENTRYEX, szName[cchName + 1]);
+ if (cbNeeded <= *pcbDirEntry)
+ *pcbDirEntry = cbNeeded;
+ else
+ {
+ *pcbDirEntry = cbNeeded;
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ /* To avoid duplicating code in rtFsFatObj_InitFromDirRec and
+ rtFsFatObj_QueryInfo, we create a dummy RTFSFATOBJ on the stack. */
+ RTFSFATOBJ TmpObj;
+ RT_ZERO(TmpObj);
+ rtFsFatObj_InitFromDirEntry(&TmpObj, &paEntries[iEntry].Entry, offEntryInDir, pShared->Core.pVol);
+
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pShared, uBufferLock);
+
+ rc = rtFsFatObj_QueryInfo(&TmpObj, &pDirEntry->Info, enmAddAttr);
+
+ /* Copy out the names. */
+ pDirEntry->cbName = (uint16_t)cchName;
+ if (fLongName)
+ {
+ char *pszDst = &pDirEntry->szName[0];
+ int rc2 = RTUtf16ToUtf8Ex(wszName, cwcName, &pszDst, cchName + 1, NULL);
+ AssertRC(rc2);
+
+ pDirEntry->cwcShortName = rtFsFatDir_CopyDirEntryToUtf16(pShared, &paEntries[iEntry].Entry,
+ pDirEntry->wszShortName,
+ RT_ELEMENTS(pDirEntry->wszShortName));
+ }
+ else
+ {
+ rtFsFatDir_CopyDirEntryToUtf8(pShared, &paEntries[iEntry].Entry, &pDirEntry->szName[0], cchName + 1);
+ pDirEntry->wszShortName[0] = '\0';
+ pDirEntry->cwcShortName = 0;
+ }
+
+ if (RT_SUCCESS(rc))
+ pThis->offDir = offEntryInDir + sizeof(paEntries[iEntry]) + 2;
+ Assert(RTStrValidateEncoding(pDirEntry->szName) == VINF_SUCCESS);
+ return rc;
+ }
+ else
+ cwcName = 0;
+ }
+
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pShared, uBufferLock);
+ }
+
+ pThis->offDir = cbDir + 2;
+ return VERR_NO_MORE_FILES;
+}
+
+
+/**
+ * FAT directory operations.
+ */
+static const RTVFSDIROPS g_rtFsFatDirOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_DIR,
+ "FatDir",
+ rtFsFatDir_Close,
+ rtFsFatDir_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSDIROPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSDIROPS, ObjSet) - RT_UOFFSETOF(RTVFSDIROPS, Obj),
+ rtFsFatDir_SetMode,
+ rtFsFatDir_SetTimes,
+ rtFsFatDir_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtFsFatDir_Open,
+ NULL /* pfnFollowAbsoluteSymlink */,
+ NULL /* pfnOpenFile*/,
+ NULL /* pfnOpenDir */,
+ NULL /* pfnCreateDir */,
+ rtFsFatDir_OpenSymlink,
+ rtFsFatDir_CreateSymlink,
+ NULL /* pfnQueryEntryInfo */,
+ rtFsFatDir_UnlinkEntry,
+ rtFsFatDir_RenameEntry,
+ rtFsFatDir_RewindDir,
+ rtFsFatDir_ReadDir,
+ RTVFSDIROPS_VERSION,
+};
+
+
+
+
+/**
+ * Adds an open child to the parent directory.
+ *
+ * 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 rtFsFatDirShrd_RemoveOpenChild
+ */
+static void rtFsFatDirShrd_AddOpenChild(PRTFSFATDIRSHRD pDir, PRTFSFATOBJ pChild)
+{
+ rtFsFatDirShrd_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 rtFsFatDirShrd_AddOpenChild
+ */
+static void rtFsFatDirShrd_RemoveOpenChild(PRTFSFATDIRSHRD pDir, PRTFSFATOBJ pChild)
+{
+ AssertReturnVoid(pChild->pParentDir == pDir);
+ RTListNodeRemove(&pChild->Entry);
+ pChild->pParentDir = NULL;
+
+ rtFsFatDirShrd_Release(pDir);
+}
+
+
+/**
+ * Instantiates a new shared directory instance.
+ *
+ * @returns IPRT status code.
+ * @param pThis The FAT volume instance.
+ * @param pParentDir The parent directory. This is NULL for the root
+ * directory.
+ * @param pDirEntry The parent directory entry. This is NULL for the
+ * root directory.
+ * @param offEntryInDir The byte offset of the directory entry in the parent
+ * directory. UINT32_MAX if root directory.
+ * @param idxCluster The cluster where the directory content is to be
+ * found. This can be UINT32_MAX if a root FAT12/16
+ * directory.
+ * @param offDisk The disk byte offset of the FAT12/16 root directory.
+ * This is UINT64_MAX if idxCluster is given.
+ * @param cbDir The size of the directory.
+ * @param ppSharedDir Where to return shared FAT directory instance.
+ */
+static int rtFsFatDirShrd_New(PRTFSFATVOL pThis, PRTFSFATDIRSHRD pParentDir, PCFATDIRENTRY pDirEntry, uint32_t offEntryInDir,
+ uint32_t idxCluster, uint64_t offDisk, uint32_t cbDir, PRTFSFATDIRSHRD *ppSharedDir)
+{
+ Assert((idxCluster == UINT32_MAX) != (offDisk == UINT64_MAX));
+ Assert((pDirEntry == NULL) == (offEntryInDir == UINT32_MAX));
+ *ppSharedDir = NULL;
+
+ int rc = VERR_NO_MEMORY;
+ PRTFSFATDIRSHRD pShared = (PRTFSFATDIRSHRD)RTMemAllocZ(sizeof(*pShared));
+ if (pShared)
+ {
+ /*
+ * Initialize it all so rtFsFatDir_Close doesn't trip up in anyway.
+ */
+ RTListInit(&pShared->OpenChildren);
+ if (pDirEntry)
+ rtFsFatObj_InitFromDirEntry(&pShared->Core, pDirEntry, offEntryInDir, pThis);
+ else
+ rtFsFatObj_InitDummy(&pShared->Core, cbDir, RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY | RTFS_UNIX_ALL_PERMS, pThis);
+
+ pShared->cEntries = cbDir / sizeof(FATDIRENTRY);
+ pShared->fIsLinearRootDir = idxCluster == UINT32_MAX;
+ pShared->fFullyBuffered = pShared->fIsLinearRootDir;
+ pShared->paEntries = NULL;
+ pShared->offEntriesOnDisk = UINT64_MAX;
+ if (pShared->fFullyBuffered)
+ pShared->cbAllocatedForEntries = RT_ALIGN_32(cbDir, pThis->cbSector);
+ else
+ pShared->cbAllocatedForEntries = pThis->cbSector;
+
+ /*
+ * If clustered backing, read the chain and see if we cannot still do the full buffering.
+ */
+ if (idxCluster != UINT32_MAX)
+ {
+ rc = rtFsFatClusterMap_ReadClusterChain(pThis, idxCluster, &pShared->Core.Clusters);
+ if (RT_SUCCESS(rc))
+ {
+ if ( pShared->Core.Clusters.cClusters >= 1
+ && pShared->Core.Clusters.cbChain <= _64K
+ && rtFsFatChain_IsContiguous(&pShared->Core.Clusters))
+ {
+ Assert(pShared->Core.Clusters.cbChain >= cbDir);
+ pShared->cbAllocatedForEntries = pShared->Core.Clusters.cbChain;
+ pShared->fFullyBuffered = true;
+ }
+
+ /* DOS doesn't set a size on directores, so use the cluster length instead. */
+ if ( cbDir == 0
+ && pShared->Core.Clusters.cbChain > 0)
+ {
+ cbDir = pShared->Core.Clusters.cbChain;
+ pShared->Core.cbObject = cbDir;
+ pShared->cEntries = cbDir / sizeof(FATDIRENTRY);
+ if (pShared->fFullyBuffered)
+ pShared->cbAllocatedForEntries = RT_ALIGN_32(cbDir, pThis->cbSector);
+ }
+ }
+ }
+ else
+ {
+ rtFsFatChain_InitEmpty(&pShared->Core.Clusters, pThis);
+ rc = VINF_SUCCESS;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate and initialize the buffering. Fill the buffer.
+ */
+ pShared->paEntries = (PFATDIRENTRYUNION)RTMemAlloc(pShared->cbAllocatedForEntries);
+ if (!pShared->paEntries)
+ {
+ if (pShared->fFullyBuffered && !pShared->fIsLinearRootDir)
+ {
+ pShared->fFullyBuffered = false;
+ pShared->cbAllocatedForEntries = pThis->cbSector;
+ pShared->paEntries = (PFATDIRENTRYUNION)RTMemAlloc(pShared->cbAllocatedForEntries);
+ }
+ if (!pShared->paEntries)
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pShared->fFullyBuffered)
+ {
+ pShared->u.Full.cDirtySectors = 0;
+ pShared->u.Full.cSectors = pShared->cbAllocatedForEntries / pThis->cbSector;
+ pShared->u.Full.pbDirtySectors = (uint8_t *)RTMemAllocZ((pShared->u.Full.cSectors + 63) / 8);
+ if (pShared->u.Full.pbDirtySectors)
+ pShared->offEntriesOnDisk = offDisk != UINT64_MAX ? offDisk
+ : rtFsFatClusterToDiskOffset(pThis, idxCluster);
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ pShared->offEntriesOnDisk = rtFsFatClusterToDiskOffset(pThis, idxCluster);
+ pShared->u.Simple.offInDir = 0;
+ pShared->u.Simple.fDirty = false;
+ }
+ if (RT_SUCCESS(rc))
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, pShared->offEntriesOnDisk,
+ pShared->paEntries, pShared->cbAllocatedForEntries, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Link into parent directory so we can use it to update
+ * our directory entry.
+ */
+ if (pParentDir)
+ rtFsFatDirShrd_AddOpenChild(pParentDir, &pShared->Core);
+ *ppSharedDir = pShared;
+ return VINF_SUCCESS;
+ }
+ }
+
+ /* Free the buffer on failure so rtFsFatDir_Close doesn't try do anything with it. */
+ RTMemFree(pShared->paEntries);
+ pShared->paEntries = NULL;
+ }
+
+ Assert(pShared->Core.cRefs == 1);
+ rtFsFatDirShrd_Release(pShared);
+ }
+ return rc;
+}
+
+
+/**
+ * Instantiates a new directory with a shared structure presupplied.
+ *
+ * @returns IPRT status code.
+ * @param pThis The FAT 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 rtFsFatDir_NewWithShared(PRTFSFATVOL pThis, PRTFSFATDIRSHRD pShared, PRTVFSDIR phVfsDir)
+{
+ /*
+ * Create VFS object around the shared structure.
+ */
+ PRTFSFATDIR pNewDir;
+ int rc = RTVfsNewDir(&g_rtFsFatDirOps, 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;
+ }
+
+ rtFsFatDirShrd_Release(pShared);
+ *phVfsDir = NIL_RTVFSDIR;
+ return rc;
+}
+
+
+
+/**
+ * Instantiates a new directory VFS, creating the shared structure as necessary.
+ *
+ * @returns IPRT status code.
+ * @param pThis The FAT volume instance.
+ * @param pParentDir The parent directory. This is NULL for the root
+ * directory.
+ * @param pDirEntry The parent directory entry. This is NULL for the
+ * root directory.
+ * @param offEntryInDir The byte offset of the directory entry in the parent
+ * directory. UINT32_MAX if root directory.
+ * @param idxCluster The cluster where the directory content is to be
+ * found. This can be UINT32_MAX if a root FAT12/16
+ * directory.
+ * @param offDisk The disk byte offset of the FAT12/16 root directory.
+ * This is UINT64_MAX if idxCluster is given.
+ * @param cbDir The size of the directory.
+ * @param phVfsDir Where to return the directory handle.
+ */
+static int rtFsFatDir_New(PRTFSFATVOL pThis, PRTFSFATDIRSHRD pParentDir, PCFATDIRENTRY pDirEntry, uint32_t offEntryInDir,
+ uint32_t idxCluster, uint64_t offDisk, uint32_t cbDir, PRTVFSDIR phVfsDir)
+{
+ /*
+ * Look for existing shared object, create a new one if necessary.
+ */
+ PRTFSFATDIRSHRD pShared = (PRTFSFATDIRSHRD)rtFsFatDirShrd_LookupShared(pParentDir, offEntryInDir);
+ if (!pShared)
+ {
+ int rc = rtFsFatDirShrd_New(pThis, pParentDir, pDirEntry, offEntryInDir, idxCluster, offDisk, cbDir, &pShared);
+ if (RT_FAILURE(rc))
+ {
+ *phVfsDir = NIL_RTVFSDIR;
+ return rc;
+ }
+ }
+ return rtFsFatDir_NewWithShared(pThis, pShared, phVfsDir);
+}
+
+
+
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsFatVol_Close(void *pvThis)
+{
+ PRTFSFATVOL pThis = (PRTFSFATVOL)pvThis;
+ LogFlow(("rtFsFatVol_Close(%p)\n", pThis));
+
+ int rc = VINF_SUCCESS;
+ if (pThis->pRootDir != NULL)
+ {
+ Assert(RTListIsEmpty(&pThis->pRootDir->OpenChildren));
+ Assert(pThis->pRootDir->Core.cRefs == 1);
+ rc = rtFsFatDirShrd_Release(pThis->pRootDir);
+ pThis->pRootDir = NULL;
+ }
+
+ int rc2 = rtFsFatClusterMap_Destroy(pThis);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ RTVfsFileRelease(pThis->hVfsBacking);
+ pThis->hVfsBacking = NIL_RTVFSFILE;
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsFatVol_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ RT_NOREF(pvThis, pObjInfo, enmAddAttr);
+ return VERR_WRONG_TYPE;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOPS,pfnOpenRoot}
+ */
+static DECLCALLBACK(int) rtFsFatVol_OpenRoot(void *pvThis, PRTVFSDIR phVfsDir)
+{
+ PRTFSFATVOL pThis = (PRTFSFATVOL)pvThis;
+
+ rtFsFatDirShrd_Retain(pThis->pRootDir); /* consumed by the next call */
+ return rtFsFatDir_NewWithShared(pThis, pThis->pRootDir, phVfsDir);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOPS,pfnQueryRangeState}
+ */
+static DECLCALLBACK(int) rtFsFatVol_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_rtFsFatVolOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_VFS,
+ "FatVol",
+ rtFsFatVol_Close,
+ rtFsFatVol_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSOPS_VERSION,
+ 0 /* fFeatures */,
+ rtFsFatVol_OpenRoot,
+ rtFsFatVol_QueryRangeState,
+ RTVFSOPS_VERSION
+};
+
+
+/**
+ * Tries to detect a DOS 1.x formatted image and fills in the BPB fields.
+ *
+ * There is no BPB here, but fortunately, there isn't much variety.
+ *
+ * @returns IPRT status code.
+ * @param pThis The FAT volume instance, BPB derived fields are filled
+ * in on success.
+ * @param pBootSector The boot sector.
+ * @param pbFatSector Points to the FAT sector, or whatever is 512 bytes after
+ * the boot sector.
+ * @param pErrInfo Where to return additional error information.
+ */
+static int rtFsFatVolTryInitDos1x(PRTFSFATVOL pThis, PCFATBOOTSECTOR pBootSector, uint8_t const *pbFatSector,
+ PRTERRINFO pErrInfo)
+{
+ /*
+ * PC-DOS 1.0 does a 2fh byte short jump w/o any NOP following it.
+ * Instead the following are three words and a 9 byte build date
+ * string. The remaining space is zero filled.
+ *
+ * Note! No idea how this would look like for 8" floppies, only got 5"1/4'.
+ *
+ * ASSUME all non-BPB disks are using this format.
+ */
+ if ( pBootSector->abJmp[0] != 0xeb /* jmp rel8 */
+ || pBootSector->abJmp[1] < 0x2f
+ || pBootSector->abJmp[1] >= 0x80
+ || pBootSector->abJmp[2] == 0x90 /* nop */)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
+ "No DOS v1.0 bootsector either - invalid jmp: %.3Rhxs", pBootSector->abJmp);
+ uint32_t const offJump = 2 + pBootSector->abJmp[1];
+ uint32_t const offFirstZero = 2 /*jmp */ + 3 * 2 /* words */ + 9 /* date string */;
+ Assert(offFirstZero >= RT_UOFFSETOF(FATBOOTSECTOR, Bpb));
+ uint32_t const cbZeroPad = RT_MIN(offJump - offFirstZero,
+ sizeof(pBootSector->Bpb.Bpb20) - (offFirstZero - RT_UOFFSETOF(FATBOOTSECTOR, Bpb)));
+
+ if (!ASMMemIsAllU8((uint8_t const *)pBootSector + offFirstZero, cbZeroPad, 0))
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
+ "No DOS v1.0 bootsector either - expected zero padding %#x LB %#x: %.*Rhxs",
+ offFirstZero, cbZeroPad, cbZeroPad, (uint8_t const *)pBootSector + offFirstZero);
+
+ /*
+ * Check the FAT ID so we can tell if this is double or single sided,
+ * as well as being a valid FAT12 start.
+ */
+ if ( (pbFatSector[0] != 0xfe && pbFatSector[0] != 0xff)
+ || pbFatSector[1] != 0xff
+ || pbFatSector[2] != 0xff)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
+ "No DOS v1.0 bootsector either - unexpected start of FAT: %.3Rhxs", pbFatSector);
+
+ /*
+ * Fixed DOS 1.0 config.
+ */
+ pThis->enmFatType = RTFSFATTYPE_FAT12;
+ pThis->enmBpbVersion = RTFSFATBPBVER_NO_BPB;
+ pThis->bMedia = pbFatSector[0];
+ pThis->cReservedSectors = 1;
+ pThis->cbSector = 512;
+ pThis->cbCluster = pThis->bMedia == 0xfe ? 1024 : 512;
+ pThis->cFats = 2;
+ pThis->cbFat = 512;
+ pThis->aoffFats[0] = pThis->offBootSector + pThis->cReservedSectors * 512;
+ pThis->aoffFats[1] = pThis->aoffFats[0] + pThis->cbFat;
+ pThis->offRootDir = pThis->aoffFats[1] + pThis->cbFat;
+ pThis->cRootDirEntries = 512;
+ pThis->offFirstCluster = pThis->offRootDir + RT_ALIGN_32(pThis->cRootDirEntries * sizeof(FATDIRENTRY),
+ pThis->cbSector);
+ pThis->cbTotalSize = pThis->bMedia == 0xfe ? 8 * 1 * 40 * 512 : 8 * 2 * 40 * 512;
+ pThis->cClusters = (pThis->cbTotalSize - (pThis->offFirstCluster - pThis->offBootSector)) / pThis->cbCluster;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for rtFsFatVolTryInitDos2Plus that handles remaining BPB fields.
+ *
+ * @returns IPRT status code.
+ * @param pThis The FAT volume instance, BPB derived fields are filled
+ * in on success.
+ * @param pBootSector The boot sector.
+ * @param fMaybe331 Set if it could be a DOS v3.31 BPB.
+ * @param pErrInfo Where to return additional error information.
+ */
+static int rtFsFatVolTryInitDos2PlusBpb(PRTFSFATVOL pThis, PCFATBOOTSECTOR pBootSector, bool fMaybe331, PRTERRINFO pErrInfo)
+{
+ pThis->enmBpbVersion = RTFSFATBPBVER_DOS_2_0;
+
+ /*
+ * Figure total sector count. Could both be zero, in which case we have to
+ * fall back on the size of the backing stuff.
+ */
+ if (pBootSector->Bpb.Bpb20.cTotalSectors16 != 0)
+ pThis->cbTotalSize = pBootSector->Bpb.Bpb20.cTotalSectors16 * pThis->cbSector;
+ else if ( pBootSector->Bpb.Bpb331.cTotalSectors32 != 0
+ && fMaybe331)
+ {
+ pThis->enmBpbVersion = RTFSFATBPBVER_DOS_3_31;
+ pThis->cbTotalSize = pBootSector->Bpb.Bpb331.cTotalSectors32 * (uint64_t)pThis->cbSector;
+ }
+ else
+ pThis->cbTotalSize = pThis->cbBacking - pThis->offBootSector;
+ if (pThis->cReservedSectors * pThis->cbSector >= pThis->cbTotalSize)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bogus FAT12/16 total or reserved sector count: %#x vs %#x",
+ pThis->cReservedSectors, pThis->cbTotalSize / pThis->cbSector);
+
+ /*
+ * The fat size. Complete FAT offsets.
+ */
+ if ( pBootSector->Bpb.Bpb20.cSectorsPerFat == 0
+ || ((uint32_t)pBootSector->Bpb.Bpb20.cSectorsPerFat * pThis->cFats + 1) * pThis->cbSector > pThis->cbTotalSize)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus FAT12/16 sectors per FAT: %#x (total sectors %#RX64)",
+ pBootSector->Bpb.Bpb20.cSectorsPerFat, pThis->cbTotalSize / pThis->cbSector);
+ pThis->cbFat = pBootSector->Bpb.Bpb20.cSectorsPerFat * pThis->cbSector;
+
+ AssertReturn(pThis->cFats < RT_ELEMENTS(pThis->aoffFats), VERR_VFS_BOGUS_FORMAT);
+ for (unsigned iFat = 1; iFat <= pThis->cFats; iFat++)
+ pThis->aoffFats[iFat] = pThis->aoffFats[iFat - 1] + pThis->cbFat;
+
+ /*
+ * Do root directory calculations.
+ */
+ pThis->idxRootDirCluster = UINT32_MAX;
+ pThis->offRootDir = pThis->aoffFats[pThis->cFats];
+ if (pThis->cRootDirEntries == 0)
+ return RTErrInfoSet(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Zero FAT12/16 root directory size");
+ pThis->cbRootDir = pThis->cRootDirEntries * sizeof(FATDIRENTRY);
+ pThis->cbRootDir = RT_ALIGN_32(pThis->cbRootDir, pThis->cbSector);
+
+ /*
+ * First cluster and cluster count checks and calcs. Determin FAT type.
+ */
+ pThis->offFirstCluster = pThis->offRootDir + pThis->cbRootDir;
+ uint64_t cbSystemStuff = pThis->offFirstCluster - pThis->offBootSector;
+ if (cbSystemStuff >= pThis->cbTotalSize)
+ return RTErrInfoSet(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus FAT12/16 total size, root dir, or fat size");
+ pThis->cClusters = (pThis->cbTotalSize - cbSystemStuff) / pThis->cbCluster;
+
+ if (pThis->cClusters >= FAT_MAX_FAT16_DATA_CLUSTERS)
+ {
+ pThis->cClusters = FAT_MAX_FAT16_DATA_CLUSTERS;
+ pThis->enmFatType = RTFSFATTYPE_FAT16;
+ }
+ else if (pThis->cClusters >= FAT_MIN_FAT16_DATA_CLUSTERS)
+ pThis->enmFatType = RTFSFATTYPE_FAT16;
+ else
+ pThis->enmFatType = RTFSFATTYPE_FAT12; /** @todo Not sure if this is entirely the right way to go about it... */
+
+ uint32_t cClustersPerFat;
+ if (pThis->enmFatType == RTFSFATTYPE_FAT16)
+ cClustersPerFat = pThis->cbFat / 2;
+ else
+ cClustersPerFat = pThis->cbFat * 2 / 3;
+ if (pThis->cClusters > cClustersPerFat)
+ pThis->cClusters = cClustersPerFat;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for rtFsFatVolTryInitDos2Plus and rtFsFatVolTryInitDos2PlusFat32 that
+ * handles common extended BPBs fields.
+ *
+ * @returns IPRT status code.
+ * @param pThis The FAT volume instance.
+ * @param bExtSignature The extended BPB signature.
+ * @param uSerialNumber The serial number.
+ * @param pachLabel Pointer to the volume label field.
+ * @param pachType Pointer to the file system type field.
+ */
+static void rtFsFatVolInitCommonEbpbBits(PRTFSFATVOL pThis, uint8_t bExtSignature, uint32_t uSerialNumber,
+ char const *pachLabel, char const *pachType)
+{
+ pThis->uSerialNo = uSerialNumber;
+ if (bExtSignature == FATEBPB_SIGNATURE)
+ {
+ memcpy(pThis->szLabel, pachLabel, RT_SIZEOFMEMB(FATEBPB, achLabel));
+ pThis->szLabel[RT_SIZEOFMEMB(FATEBPB, achLabel)] = '\0';
+ RTStrStrip(pThis->szLabel);
+
+ memcpy(pThis->szType, pachType, RT_SIZEOFMEMB(FATEBPB, achType));
+ pThis->szType[RT_SIZEOFMEMB(FATEBPB, achType)] = '\0';
+ RTStrStrip(pThis->szType);
+ }
+ else
+ {
+ pThis->szLabel[0] = '\0';
+ pThis->szType[0] = '\0';
+ }
+}
+
+
+/**
+ * Worker for rtFsFatVolTryInitDos2Plus that deals with FAT32.
+ *
+ * @returns IPRT status code.
+ * @param pThis The FAT volume instance, BPB derived fields are filled
+ * in on success.
+ * @param pBootSector The boot sector.
+ * @param pErrInfo Where to return additional error information.
+ */
+static int rtFsFatVolTryInitDos2PlusFat32(PRTFSFATVOL pThis, PCFATBOOTSECTOR pBootSector, PRTERRINFO pErrInfo)
+{
+ pThis->enmFatType = RTFSFATTYPE_FAT32;
+ pThis->enmBpbVersion = pBootSector->Bpb.Fat32Ebpb.bExtSignature == FATEBPB_SIGNATURE
+ ? RTFSFATBPBVER_FAT32_29 : RTFSFATBPBVER_FAT32_28;
+ pThis->fFat32Flags = pBootSector->Bpb.Fat32Ebpb.fFlags;
+
+ if (pBootSector->Bpb.Fat32Ebpb.uVersion != FAT32EBPB_VERSION_0_0)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Unsupported FAT32 version: %d.%d (%#x)",
+ RT_HI_U8(pBootSector->Bpb.Fat32Ebpb.uVersion), RT_LO_U8(pBootSector->Bpb.Fat32Ebpb.uVersion),
+ pBootSector->Bpb.Fat32Ebpb.uVersion);
+
+ /*
+ * Figure total sector count. We expected it to be filled in.
+ */
+ bool fUsing64BitTotalSectorCount = false;
+ if (pBootSector->Bpb.Fat32Ebpb.Bpb.cTotalSectors16 != 0)
+ pThis->cbTotalSize = pBootSector->Bpb.Fat32Ebpb.Bpb.cTotalSectors16 * pThis->cbSector;
+ else if (pBootSector->Bpb.Fat32Ebpb.Bpb.cTotalSectors32 != 0)
+ pThis->cbTotalSize = pBootSector->Bpb.Fat32Ebpb.Bpb.cTotalSectors32 * (uint64_t)pThis->cbSector;
+ else if ( pBootSector->Bpb.Fat32Ebpb.u.cTotalSectors64 <= UINT64_MAX / 512
+ && pBootSector->Bpb.Fat32Ebpb.u.cTotalSectors64 > 3
+ && pBootSector->Bpb.Fat32Ebpb.bExtSignature != FATEBPB_SIGNATURE_OLD)
+ {
+ pThis->cbTotalSize = pBootSector->Bpb.Fat32Ebpb.u.cTotalSectors64 * pThis->cbSector;
+ fUsing64BitTotalSectorCount = true;
+ }
+ else
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "FAT32 total sector count out of range: %#RX64",
+ pBootSector->Bpb.Fat32Ebpb.u.cTotalSectors64);
+ if (pThis->cReservedSectors * pThis->cbSector >= pThis->cbTotalSize)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bogus FAT32 total or reserved sector count: %#x vs %#x",
+ pThis->cReservedSectors, pThis->cbTotalSize / pThis->cbSector);
+
+ /*
+ * Fat size. We check the 16-bit field even if it probably should be zero all the time.
+ */
+ if (pBootSector->Bpb.Fat32Ebpb.Bpb.cSectorsPerFat != 0)
+ {
+ if ( pBootSector->Bpb.Fat32Ebpb.cSectorsPerFat32 != 0
+ && pBootSector->Bpb.Fat32Ebpb.cSectorsPerFat32 != pBootSector->Bpb.Fat32Ebpb.Bpb.cSectorsPerFat)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Both 16-bit and 32-bit FAT size fields are set: %#RX16 vs %#RX32",
+ pBootSector->Bpb.Fat32Ebpb.Bpb.cSectorsPerFat, pBootSector->Bpb.Fat32Ebpb.cSectorsPerFat32);
+ pThis->cbFat = pBootSector->Bpb.Fat32Ebpb.Bpb.cSectorsPerFat * pThis->cbSector;
+ }
+ else
+ {
+ uint64_t cbFat = pBootSector->Bpb.Fat32Ebpb.cSectorsPerFat32 * (uint64_t)pThis->cbSector;
+ if ( cbFat == 0
+ || cbFat >= FAT_MAX_FAT32_TOTAL_CLUSTERS * 4 + pThis->cbSector * 16)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bogus 32-bit FAT size: %#RX32", pBootSector->Bpb.Fat32Ebpb.cSectorsPerFat32);
+ pThis->cbFat = (uint32_t)cbFat;
+ }
+
+ /*
+ * Complete the FAT offsets and first cluster offset, then calculate number
+ * of data clusters.
+ */
+ AssertReturn(pThis->cFats < RT_ELEMENTS(pThis->aoffFats), VERR_VFS_BOGUS_FORMAT);
+ for (unsigned iFat = 1; iFat <= pThis->cFats; iFat++)
+ pThis->aoffFats[iFat] = pThis->aoffFats[iFat - 1] + pThis->cbFat;
+ pThis->offFirstCluster = pThis->aoffFats[pThis->cFats];
+
+ if (pThis->offFirstCluster - pThis->offBootSector >= pThis->cbTotalSize)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bogus 32-bit FAT size or total sector count: cFats=%d cbFat=%#x cbTotalSize=%#x",
+ pThis->cFats, pThis->cbFat, pThis->cbTotalSize);
+
+ uint64_t cClusters = (pThis->cbTotalSize - (pThis->offFirstCluster - pThis->offBootSector)) / pThis->cbCluster;
+ if (cClusters <= FAT_MAX_FAT32_DATA_CLUSTERS)
+ pThis->cClusters = (uint32_t)cClusters;
+ else
+ pThis->cClusters = FAT_MAX_FAT32_DATA_CLUSTERS;
+ if (pThis->cClusters > (pThis->cbFat / 4 - FAT_FIRST_DATA_CLUSTER))
+ pThis->cClusters = (pThis->cbFat / 4 - FAT_FIRST_DATA_CLUSTER);
+
+ /*
+ * Root dir cluster.
+ */
+ if ( pBootSector->Bpb.Fat32Ebpb.uRootDirCluster < FAT_FIRST_DATA_CLUSTER
+ || pBootSector->Bpb.Fat32Ebpb.uRootDirCluster >= pThis->cClusters)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bogus FAT32 root directory cluster: %#x", pBootSector->Bpb.Fat32Ebpb.uRootDirCluster);
+ pThis->idxRootDirCluster = pBootSector->Bpb.Fat32Ebpb.uRootDirCluster;
+ pThis->offRootDir = pThis->offFirstCluster
+ + (pBootSector->Bpb.Fat32Ebpb.uRootDirCluster - FAT_FIRST_DATA_CLUSTER) * pThis->cbCluster;
+
+ /*
+ * Info sector.
+ */
+ if ( pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo == 0
+ || pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo == UINT16_MAX)
+ pThis->offFat32InfoSector = UINT64_MAX;
+ else if (pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo >= pThis->cReservedSectors)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bogus FAT32 info sector number: %#x (reserved sectors %#x)",
+ pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo, pThis->cReservedSectors);
+ else
+ {
+ pThis->offFat32InfoSector = pThis->cbSector * pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo + pThis->offBootSector;
+ int rc = RTVfsFileReadAt(pThis->hVfsBacking, pThis->offFat32InfoSector,
+ &pThis->Fat32InfoSector, sizeof(pThis->Fat32InfoSector), NULL);
+ if (RT_FAILURE(rc))
+ return RTErrInfoSetF(pErrInfo, rc, "Failed to read FAT32 info sector at offset %#RX64", pThis->offFat32InfoSector);
+ if ( pThis->Fat32InfoSector.uSignature1 != FAT32INFOSECTOR_SIGNATURE_1
+ || pThis->Fat32InfoSector.uSignature2 != FAT32INFOSECTOR_SIGNATURE_2
+ || pThis->Fat32InfoSector.uSignature3 != FAT32INFOSECTOR_SIGNATURE_3)
+ return RTErrInfoSetF(pErrInfo, rc, "FAT32 info sector signature mismatch: %#x %#x %#x",
+ pThis->Fat32InfoSector.uSignature1, pThis->Fat32InfoSector.uSignature2,
+ pThis->Fat32InfoSector.uSignature3);
+ }
+
+ /*
+ * Boot sector copy.
+ */
+ if ( pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo == 0
+ || pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo == UINT16_MAX)
+ {
+ pThis->cBootSectorCopies = 0;
+ pThis->offBootSectorCopies = UINT64_MAX;
+ }
+ else if (pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo >= pThis->cReservedSectors)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bogus FAT32 info boot sector copy location: %#x (reserved sectors %#x)",
+ pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo, pThis->cReservedSectors);
+ else
+ {
+ /** @todo not sure if cbSector is correct here. */
+ pThis->cBootSectorCopies = 3;
+ if ( (uint32_t)pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo + pThis->cBootSectorCopies
+ > pThis->cReservedSectors)
+ pThis->cBootSectorCopies = (uint8_t)(pThis->cReservedSectors - pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo);
+ pThis->offBootSectorCopies = pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo * pThis->cbSector + pThis->offBootSector;
+ if ( pThis->offFat32InfoSector != UINT64_MAX
+ && pThis->offFat32InfoSector - pThis->offBootSectorCopies < (uint64_t)(pThis->cBootSectorCopies * pThis->cbSector))
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "FAT32 info sector and boot sector copies overlap: %#x vs %#x",
+ pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo, pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo);
+ }
+
+ /*
+ * Serial number, label and type.
+ */
+ rtFsFatVolInitCommonEbpbBits(pThis, pBootSector->Bpb.Fat32Ebpb.bExtSignature, pBootSector->Bpb.Fat32Ebpb.uSerialNumber,
+ pBootSector->Bpb.Fat32Ebpb.achLabel,
+ fUsing64BitTotalSectorCount ? pBootSector->achOemName : pBootSector->Bpb.Fat32Ebpb.achLabel);
+ if (pThis->szType[0] == '\0')
+ memcpy(pThis->szType, "FAT32", 6);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Tries to detect a DOS 2.0+ formatted image and fills in the BPB fields.
+ *
+ * We ASSUME BPB here, but need to figure out which version of the BPB it is,
+ * which is lots of fun.
+ *
+ * @returns IPRT status code.
+ * @param pThis The FAT volume instance, BPB derived fields are filled
+ * in on success.
+ * @param pBootSector The boot sector.
+ * @param pbFatSector Points to the FAT sector, or whatever is 512 bytes after
+ * the boot sector. On successful return it will contain
+ * the first FAT sector.
+ * @param pErrInfo Where to return additional error information.
+ */
+static int rtFsFatVolTryInitDos2Plus(PRTFSFATVOL pThis, PCFATBOOTSECTOR pBootSector, uint8_t *pbFatSector, PRTERRINFO pErrInfo)
+{
+ /*
+ * Check if we've got a known jump instruction first, because that will
+ * give us a max (E)BPB size hint.
+ */
+ uint8_t offJmp = UINT8_MAX;
+ if ( pBootSector->abJmp[0] == 0xeb
+ && pBootSector->abJmp[1] <= 0x7f)
+ offJmp = pBootSector->abJmp[1] + 2;
+ else if ( pBootSector->abJmp[0] == 0x90
+ && pBootSector->abJmp[1] == 0xeb
+ && pBootSector->abJmp[2] <= 0x7f)
+ offJmp = pBootSector->abJmp[2] + 3;
+ else if ( pBootSector->abJmp[0] == 0xe9
+ && pBootSector->abJmp[2] <= 0x7f)
+ offJmp = RT_MIN(127, RT_MAKE_U16(pBootSector->abJmp[1], pBootSector->abJmp[2]));
+ uint8_t const cbMaxBpb = offJmp - RT_UOFFSETOF(FATBOOTSECTOR, Bpb);
+
+ /*
+ * Do the basic DOS v2.0 BPB fields.
+ */
+ if (cbMaxBpb < sizeof(FATBPB20))
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
+ "DOS signature, but jmp too short for any BPB: %#x (max %#x BPB)", offJmp, cbMaxBpb);
+
+ if (pBootSector->Bpb.Bpb20.cFats == 0)
+ return RTErrInfoSet(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "DOS signature, number of FATs is zero, so not FAT file system");
+ if (pBootSector->Bpb.Bpb20.cFats > 4)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "DOS signature, too many FATs: %#x", pBootSector->Bpb.Bpb20.cFats);
+ pThis->cFats = pBootSector->Bpb.Bpb20.cFats;
+
+ if (!FATBPB_MEDIA_IS_VALID(pBootSector->Bpb.Bpb20.bMedia))
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
+ "DOS signature, invalid media byte: %#x", pBootSector->Bpb.Bpb20.bMedia);
+ pThis->bMedia = pBootSector->Bpb.Bpb20.bMedia;
+
+ if (!RT_IS_POWER_OF_TWO(pBootSector->Bpb.Bpb20.cbSector))
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
+ "DOS signature, sector size not power of two: %#x", pBootSector->Bpb.Bpb20.cbSector);
+ if ( pBootSector->Bpb.Bpb20.cbSector != 512
+ && pBootSector->Bpb.Bpb20.cbSector != 4096
+ && pBootSector->Bpb.Bpb20.cbSector != 1024
+ && pBootSector->Bpb.Bpb20.cbSector != 128)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
+ "DOS signature, unsupported sector size: %#x", pBootSector->Bpb.Bpb20.cbSector);
+ pThis->cbSector = pBootSector->Bpb.Bpb20.cbSector;
+
+ if ( !RT_IS_POWER_OF_TWO(pBootSector->Bpb.Bpb20.cSectorsPerCluster)
+ || !pBootSector->Bpb.Bpb20.cSectorsPerCluster)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "DOS signature, cluster size not non-zero power of two: %#x",
+ pBootSector->Bpb.Bpb20.cSectorsPerCluster);
+ pThis->cbCluster = pBootSector->Bpb.Bpb20.cSectorsPerCluster * pThis->cbSector;
+
+ uint64_t const cMaxRoot = (pThis->cbBacking - pThis->offBootSector - 512) / sizeof(FATDIRENTRY); /* we'll check again later. */
+ if (pBootSector->Bpb.Bpb20.cMaxRootDirEntries >= cMaxRoot)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "DOS signature, too many root entries: %#x (max %#RX64)",
+ pBootSector->Bpb.Bpb20.cSectorsPerCluster, cMaxRoot);
+ pThis->cRootDirEntries = pBootSector->Bpb.Bpb20.cMaxRootDirEntries;
+
+ if ( pBootSector->Bpb.Bpb20.cReservedSectors == 0
+ || pBootSector->Bpb.Bpb20.cReservedSectors >= _32K)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "DOS signature, bogus reserved sector count: %#x", pBootSector->Bpb.Bpb20.cReservedSectors);
+ pThis->cReservedSectors = pBootSector->Bpb.Bpb20.cReservedSectors;
+ pThis->aoffFats[0] = pThis->offBootSector + pThis->cReservedSectors * pThis->cbSector;
+
+ /*
+ * Jump ahead and check for FAT32 EBPB.
+ * If found, we simply ASSUME it's a FAT32 file system.
+ */
+ int rc;
+ if ( ( sizeof(FAT32EBPB) <= cbMaxBpb
+ && pBootSector->Bpb.Fat32Ebpb.bExtSignature == FATEBPB_SIGNATURE)
+ || ( RT_UOFFSETOF(FAT32EBPB, achLabel) <= cbMaxBpb
+ && pBootSector->Bpb.Fat32Ebpb.bExtSignature == FATEBPB_SIGNATURE_OLD) )
+ {
+ rc = rtFsFatVolTryInitDos2PlusFat32(pThis, pBootSector, pErrInfo);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ else
+ {
+ /*
+ * Check for extended BPB, otherwise we'll have to make qualified guesses
+ * about what kind of BPB we're up against based on jmp offset and zero fields.
+ * ASSUMES either FAT16 or FAT12.
+ */
+ if ( ( sizeof(FATEBPB) <= cbMaxBpb
+ && pBootSector->Bpb.Ebpb.bExtSignature == FATEBPB_SIGNATURE)
+ || ( RT_UOFFSETOF(FATEBPB, achLabel) <= cbMaxBpb
+ && pBootSector->Bpb.Ebpb.bExtSignature == FATEBPB_SIGNATURE_OLD) )
+ {
+ rtFsFatVolInitCommonEbpbBits(pThis, pBootSector->Bpb.Ebpb.bExtSignature, pBootSector->Bpb.Ebpb.uSerialNumber,
+ pBootSector->Bpb.Ebpb.achLabel, pBootSector->Bpb.Ebpb.achType);
+ rc = rtFsFatVolTryInitDos2PlusBpb(pThis, pBootSector, true /*fMaybe331*/, pErrInfo);
+ pThis->enmBpbVersion = pBootSector->Bpb.Ebpb.bExtSignature == FATEBPB_SIGNATURE
+ ? RTFSFATBPBVER_EXT_29 : RTFSFATBPBVER_EXT_28;
+ }
+ else
+ rc = rtFsFatVolTryInitDos2PlusBpb(pThis, pBootSector, cbMaxBpb >= sizeof(FATBPB331), pErrInfo);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (pThis->szType[0] == '\0')
+ memcpy(pThis->szType, pThis->enmFatType == RTFSFATTYPE_FAT12 ? "FAT12" : "FAT16", 6);
+ }
+
+ /*
+ * Check the FAT ID. May have to read a bit of the FAT into the buffer.
+ */
+ if (pThis->aoffFats[0] != pThis->offBootSector + 512)
+ {
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, pThis->aoffFats[0], pbFatSector, 512, NULL);
+ if (RT_FAILURE(rc))
+ return RTErrInfoSet(pErrInfo, rc, "error reading first FAT sector");
+ }
+ if (pbFatSector[0] != pThis->bMedia)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Media byte and FAT ID mismatch: %#x vs %#x (%.7Rhxs)", pbFatSector[0], pThis->bMedia, pbFatSector);
+ uint32_t idxOurEndOfChain;
+ switch (pThis->enmFatType)
+ {
+ case RTFSFATTYPE_FAT12:
+ if ((pbFatSector[1] & 0xf) != 0xf)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus FAT ID patting (FAT12): %.3Rhxs", pbFatSector);
+ pThis->idxMaxLastCluster = FAT_LAST_FAT12_DATA_CLUSTER;
+ pThis->idxEndOfChain = (pbFatSector[1] >> 4) | ((uint32_t)pbFatSector[2] << 4);
+ idxOurEndOfChain = FAT_FIRST_FAT12_EOC | 0xf;
+ break;
+
+ case RTFSFATTYPE_FAT16:
+ if (pbFatSector[1] != 0xff)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus FAT ID patting (FAT16): %.4Rhxs", pbFatSector);
+ pThis->idxMaxLastCluster = FAT_LAST_FAT16_DATA_CLUSTER;
+ pThis->idxEndOfChain = RT_MAKE_U16(pbFatSector[2], pbFatSector[3]);
+ idxOurEndOfChain = FAT_FIRST_FAT16_EOC | 0xf;
+ break;
+
+ case RTFSFATTYPE_FAT32:
+ if ( pbFatSector[1] != 0xff
+ || pbFatSector[2] != 0xff
+ || (pbFatSector[3] & 0x0f) != 0x0f)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus FAT ID patting (FAT32): %.8Rhxs", pbFatSector);
+ pThis->idxMaxLastCluster = FAT_LAST_FAT32_DATA_CLUSTER;
+ pThis->idxEndOfChain = RT_MAKE_U32_FROM_U8(pbFatSector[4], pbFatSector[5], pbFatSector[6], pbFatSector[7]);
+ idxOurEndOfChain = FAT_FIRST_FAT32_EOC | 0xf;
+ break;
+
+ default: AssertFailedReturn(VERR_INTERNAL_ERROR_2);
+ }
+
+ if (pThis->idxEndOfChain <= pThis->idxMaxLastCluster)
+ {
+ Log(("rtFsFatVolTryInitDos2Plus: Bogus idxEndOfChain=%#x, using %#x instead\n", pThis->idxEndOfChain, idxOurEndOfChain));
+ pThis->idxEndOfChain = idxOurEndOfChain;
+ }
+
+ RT_NOREF(pbFatSector);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Given a power of two value @a cb return exponent value.
+ *
+ * @returns Shift count
+ * @param cb The value.
+ */
+static uint8_t rtFsFatVolCalcByteShiftCount(uint32_t cb)
+{
+ Assert(RT_IS_POWER_OF_TWO(cb));
+ unsigned iBit = ASMBitFirstSetU32(cb);
+ Assert(iBit >= 1);
+ iBit--;
+ return iBit;
+}
+
+
+/**
+ * Worker for RTFsFatVolOpen.
+ *
+ * @returns IPRT status code.
+ * @param pThis The FAT VFS instance to initialize.
+ * @param hVfsSelf The FAT VFS handle (no reference consumed).
+ * @param hVfsBacking The file backing the alleged FAT file system.
+ * Reference is consumed (via rtFsFatVol_Destroy).
+ * @param fReadOnly Readonly or readwrite mount.
+ * @param offBootSector The boot sector offset in bytes.
+ * @param pErrInfo Where to return additional error info. Can be NULL.
+ */
+static int rtFsFatVolTryInit(PRTFSFATVOL pThis, RTVFS hVfsSelf, RTVFSFILE hVfsBacking,
+ bool fReadOnly, uint64_t offBootSector, PRTERRINFO pErrInfo)
+{
+ /*
+ * First initialize the state so that rtFsFatVol_Destroy won't trip up.
+ */
+ pThis->hVfsSelf = hVfsSelf;
+ pThis->hVfsBacking = hVfsBacking; /* Caller referenced it for us, we consume it; rtFsFatVol_Destroy releases it. */
+ pThis->cbBacking = 0;
+ pThis->offBootSector = offBootSector;
+ pThis->offNanoUTC = RTTimeLocalDeltaNano();
+ pThis->offMinUTC = pThis->offNanoUTC / RT_NS_1MIN;
+ pThis->fReadOnly = fReadOnly;
+ pThis->cReservedSectors = 1;
+
+ pThis->cbSector = 512;
+ pThis->cbCluster = 512;
+ pThis->cClusters = 0;
+ pThis->offFirstCluster = 0;
+ pThis->cbTotalSize = 0;
+
+ pThis->enmFatType = RTFSFATTYPE_INVALID;
+ pThis->cFatEntries = 0;
+ pThis->cFats = 0;
+ pThis->cbFat = 0;
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aoffFats); i++)
+ pThis->aoffFats[i] = UINT64_MAX;
+ pThis->pFatCache = NULL;
+
+ pThis->offRootDir = UINT64_MAX;
+ pThis->idxRootDirCluster = UINT32_MAX;
+ pThis->cRootDirEntries = UINT32_MAX;
+ pThis->cbRootDir = 0;
+ pThis->pRootDir = NULL;
+
+ pThis->uSerialNo = 0;
+ pThis->szLabel[0] = '\0';
+ pThis->szType[0] = '\0';
+ pThis->cBootSectorCopies = 0;
+ pThis->fFat32Flags = 0;
+ pThis->offBootSectorCopies = UINT64_MAX;
+ pThis->offFat32InfoSector = UINT64_MAX;
+ RT_ZERO(pThis->Fat32InfoSector);
+
+ /*
+ * Get stuff that may fail.
+ */
+ int rc = RTVfsFileQuerySize(hVfsBacking, &pThis->cbBacking);
+ if (RT_FAILURE(rc))
+ return rc;
+ pThis->cbTotalSize = pThis->cbBacking - pThis->offBootSector;
+
+ /*
+ * Read the boot sector and the following sector (start of the allocation
+ * table unless it a FAT32 FS). We'll then validate the boot sector and
+ * start of the FAT, expanding the BPB into the instance data.
+ */
+ union
+ {
+ uint8_t ab[512*2];
+ uint16_t au16[512*2 / 2];
+ uint32_t au32[512*2 / 4];
+ FATBOOTSECTOR BootSector;
+ FAT32INFOSECTOR InfoSector;
+ } Buf;
+ RT_ZERO(Buf);
+
+ rc = RTVfsFileReadAt(hVfsBacking, offBootSector, &Buf.BootSector, 512 * 2, NULL);
+ if (RT_FAILURE(rc))
+ return RTErrInfoSet(pErrInfo, rc, "Unable to read bootsect");
+
+ /*
+ * Extract info from the BPB and validate the two special FAT entries.
+ *
+ * Check the DOS signature first. The PC-DOS 1.0 boot floppy does not have
+ * a signature and we ASSUME this is the case for all floppies formated by it.
+ */
+ if (Buf.BootSector.uSignature != FATBOOTSECTOR_SIGNATURE)
+ {
+ if (Buf.BootSector.uSignature != 0)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "No DOS bootsector signature: %#06x", Buf.BootSector.uSignature);
+ rc = rtFsFatVolTryInitDos1x(pThis, &Buf.BootSector, &Buf.ab[512], pErrInfo);
+ }
+ else
+ rc = rtFsFatVolTryInitDos2Plus(pThis, &Buf.BootSector, &Buf.ab[512], pErrInfo);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Calc shift counts.
+ */
+ pThis->cSectorByteShift = rtFsFatVolCalcByteShiftCount(pThis->cbSector);
+ pThis->cClusterByteShift = rtFsFatVolCalcByteShiftCount(pThis->cbCluster);
+
+ /*
+ * Setup the FAT cache.
+ */
+ rc = rtFsFatClusterMap_Create(pThis, &Buf.ab[512], pErrInfo);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Create the root directory fun.
+ */
+ if (pThis->idxRootDirCluster == UINT32_MAX)
+ rc = rtFsFatDirShrd_New(pThis, NULL /*pParentDir*/, NULL /*pDirEntry*/, UINT32_MAX /*offEntryInDir*/,
+ UINT32_MAX, pThis->offRootDir, pThis->cbRootDir, &pThis->pRootDir);
+ else
+ rc = rtFsFatDirShrd_New(pThis, NULL /*pParentDir*/, NULL /*pDirEntry*/, UINT32_MAX /*offEntryInDir*/,
+ pThis->idxRootDirCluster, UINT64_MAX, pThis->cbRootDir, &pThis->pRootDir);
+ return rc;
+}
+
+
+/**
+ * Opens a FAT file system volume.
+ *
+ * @returns IPRT status code.
+ * @param hVfsFileIn The file or device backing the volume.
+ * @param fReadOnly Whether to mount it read-only.
+ * @param offBootSector The offset of the boot sector relative to the start
+ * of @a hVfsFileIn. Pass 0 for floppies.
+ * @param phVfs Where to return the virtual file system handle.
+ * @param pErrInfo Where to return additional error information.
+ */
+RTDECL(int) RTFsFatVolOpen(RTVFSFILE hVfsFileIn, bool fReadOnly, uint64_t offBootSector, PRTVFS phVfs, PRTERRINFO pErrInfo)
+{
+ /*
+ * Quick input validation.
+ */
+ AssertPtrReturn(phVfs, VERR_INVALID_POINTER);
+ *phVfs = NIL_RTVFS;
+
+ uint32_t cRefs = RTVfsFileRetain(hVfsFileIn);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Create a new FAT VFS instance and try initialize it using the given input file.
+ */
+ RTVFS hVfs = NIL_RTVFS;
+ void *pvThis = NULL;
+ int rc = RTVfsNew(&g_rtFsFatVolOps, sizeof(RTFSFATVOL), NIL_RTVFS, RTVFSLOCK_CREATE_RW, &hVfs, &pvThis);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsFatVolTryInit((PRTFSFATVOL)pvThis, hVfs, hVfsFileIn, fReadOnly, offBootSector, pErrInfo);
+ if (RT_SUCCESS(rc))
+ *phVfs = hVfs;
+ else
+ RTVfsRelease(hVfs);
+ }
+ else
+ RTVfsFileRelease(hVfsFileIn);
+ return rc;
+}
+
+
+
+
+/**
+ * Fills a range in the file with zeros in the most efficient manner.
+ *
+ * @returns IPRT status code.
+ * @param hVfsFile The file to write to.
+ * @param off Where to start filling with zeros.
+ * @param cbZeros How many zero blocks to write.
+ */
+static int rtFsFatVolWriteZeros(RTVFSFILE hVfsFile, uint64_t off, uint32_t cbZeros)
+{
+ while (cbZeros > 0)
+ {
+ uint32_t cbToWrite = sizeof(g_abRTZero64K);
+ if (cbToWrite > cbZeros)
+ cbToWrite = cbZeros;
+ int rc = RTVfsFileWriteAt(hVfsFile, off, g_abRTZero64K, cbToWrite, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ off += cbToWrite;
+ cbZeros -= cbToWrite;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Formats a FAT volume.
+ *
+ * @returns IRPT status code.
+ * @param hVfsFile The volume file.
+ * @param offVol The offset into @a hVfsFile of the file.
+ * Typically 0.
+ * @param cbVol The size of the volume. Pass 0 if the rest of
+ * hVfsFile should be used.
+ * @param fFlags See RTFSFATVOL_FMT_F_XXX.
+ * @param cbSector The logical sector size. Must be power of two.
+ * Optional, pass zero to use 512.
+ * @param cSectorsPerCluster Number of sectors per cluster. Power of two.
+ * Optional, pass zero to auto detect.
+ * @param enmFatType The FAT type (12, 16, 32) to use.
+ * Optional, pass RTFSFATTYPE_INVALID for default.
+ * @param cHeads The number of heads to report in the BPB.
+ * Optional, pass zero to auto detect.
+ * @param cSectorsPerTrack The number of sectors per track to put in the
+ * BPB. Optional, pass zero to auto detect.
+ * @param bMedia The media byte value and FAT ID to use.
+ * Optional, pass zero to auto detect.
+ * @param cRootDirEntries Number of root directory entries.
+ * Optional, pass zero to auto detect.
+ * @param cHiddenSectors Number of hidden sectors. Pass 0 for
+ * unpartitioned media.
+ * @param pErrInfo Additional error information, maybe. Optional.
+ */
+RTDECL(int) RTFsFatVolFormat(RTVFSFILE hVfsFile, uint64_t offVol, uint64_t cbVol, uint32_t fFlags, uint16_t cbSector,
+ uint16_t cSectorsPerCluster, RTFSFATTYPE enmFatType, uint32_t cHeads, uint32_t cSectorsPerTrack,
+ uint8_t bMedia, uint16_t cRootDirEntries, uint32_t cHiddenSectors, PRTERRINFO pErrInfo)
+{
+ int rc;
+ uint32_t cFats = 2;
+
+ /*
+ * Validate input.
+ */
+ if (!cbSector)
+ cbSector = 512;
+ else
+ AssertMsgReturn( cbSector == 128
+ || cbSector == 512
+ || cbSector == 1024
+ || cbSector == 4096,
+ ("cbSector=%#x\n", cbSector),
+ VERR_INVALID_PARAMETER);
+ AssertMsgReturn(cSectorsPerCluster == 0 || (cSectorsPerCluster <= 128 && RT_IS_POWER_OF_TWO(cSectorsPerCluster)),
+ ("cSectorsPerCluster=%#x\n", cSectorsPerCluster), VERR_INVALID_PARAMETER);
+ if (bMedia != 0)
+ {
+ AssertMsgReturn(FAT_ID_IS_VALID(bMedia), ("bMedia=%#x\n", bMedia), VERR_INVALID_PARAMETER);
+ AssertMsgReturn(FATBPB_MEDIA_IS_VALID(bMedia), ("bMedia=%#x\n", bMedia), VERR_INVALID_PARAMETER);
+ }
+ AssertReturn(!(fFlags & ~RTFSFATVOL_FMT_F_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertReturn(enmFatType >= RTFSFATTYPE_INVALID && enmFatType < RTFSFATTYPE_END, VERR_INVALID_PARAMETER);
+
+ if (!cbVol)
+ {
+ uint64_t cbFile;
+ rc = RTVfsFileQuerySize(hVfsFile, &cbFile);
+ AssertRCReturn(rc, rc);
+ AssertMsgReturn(cbFile > offVol, ("cbFile=%#RX64 offVol=%#RX64\n", cbFile, offVol), VERR_INVALID_PARAMETER);
+ cbVol = cbFile - offVol;
+ }
+ uint64_t const cSectorsInVol = cbVol / cbSector;
+
+ /*
+ * Guess defaults if necessary.
+ */
+ if (!cSectorsPerCluster || !cHeads || !cSectorsPerTrack || !bMedia || !cRootDirEntries)
+ {
+ static struct
+ {
+ uint64_t cbVol;
+ uint8_t bMedia;
+ uint8_t cHeads;
+ uint8_t cSectorsPerTrack;
+ uint8_t cSectorsPerCluster;
+ uint16_t cRootDirEntries;
+ } s_aDefaults[] =
+ {
+ /* cbVol, bMedia, cHeads, cSectorsPTrk, cSectorsPClstr, cRootDirEntries */
+ { 163840, 0xfe, /* cyl: 40,*/ 1, 8, 1, 64 },
+ { 184320, 0xfc, /* cyl: 40,*/ 1, 9, 2, 64 },
+ { 327680, 0xff, /* cyl: 40,*/ 2, 8, 2, 112 },
+ { 368640, 0xfd, /* cyl: 40,*/ 2, 9, 2, 112 },
+ { 737280, 0xf9, /* cyl: 80,*/ 2, 9, 2, 112 },
+ { 1228800, 0xf9, /* cyl: 80,*/ 2, 15, 2, 112 },
+ { 1474560, 0xf0, /* cyl: 80,*/ 2, 18, 1, 224 },
+ { 2949120, 0xf0, /* cyl: 80,*/ 2, 36, 2, 224 },
+ { 528482304, 0xf8, /* cyl: 1024,*/ 16, 63, 0, 512 }, // 504MB limit
+ { UINT64_C(7927234560), 0xf8, /* cyl: 1024,*/ 240, 63, 0, 512 }, // 7.3GB limit
+ { UINT64_C(8422686720), 0xf8, /* cyl: 1024,*/ 255, 63, 0, 512 }, // 7.84GB limit
+
+ };
+ uint32_t iDefault = 0;
+ while ( iDefault < RT_ELEMENTS(s_aDefaults) - 1U
+ && cbVol > s_aDefaults[iDefault].cbVol)
+ iDefault++;
+ if (!cHeads)
+ cHeads = s_aDefaults[iDefault].cHeads;
+ if (!cSectorsPerTrack)
+ cSectorsPerTrack = s_aDefaults[iDefault].cSectorsPerTrack;
+ if (!bMedia)
+ bMedia = s_aDefaults[iDefault].bMedia;
+ if (!cRootDirEntries)
+ cRootDirEntries = s_aDefaults[iDefault].cRootDirEntries;
+ if (!cSectorsPerCluster)
+ {
+ cSectorsPerCluster = s_aDefaults[iDefault].cSectorsPerCluster;
+ if (!cSectorsPerCluster)
+ {
+ uint32_t cbFat12Overhead = cbSector /* boot sector */
+ + RT_ALIGN_32(FAT_MAX_FAT12_TOTAL_CLUSTERS * 3 / 2, cbSector) * cFats /* FATs */
+ + RT_ALIGN_32(cRootDirEntries * sizeof(FATDIRENTRY), cbSector) /* root dir */;
+ uint32_t cbFat16Overhead = cbSector /* boot sector */
+ + RT_ALIGN_32(FAT_MAX_FAT16_TOTAL_CLUSTERS * 2, cbSector) * cFats /* FATs */
+ + RT_ALIGN_32(cRootDirEntries * sizeof(FATDIRENTRY), cbSector) /* root dir */;
+
+ if ( enmFatType == RTFSFATTYPE_FAT12
+ || cbVol <= cbFat12Overhead + FAT_MAX_FAT12_DATA_CLUSTERS * 4 * cbSector)
+ {
+ enmFatType = RTFSFATTYPE_FAT12;
+ cSectorsPerCluster = 1;
+ while ( cSectorsPerCluster < 128
+ && cSectorsInVol
+ > cbFat12Overhead / cbSector
+ + (uint32_t)cSectorsPerCluster * FAT_MAX_FAT12_DATA_CLUSTERS
+ + cSectorsPerCluster - 1)
+ cSectorsPerCluster <<= 1;
+ }
+ else if ( enmFatType == RTFSFATTYPE_FAT16
+ || cbVol <= cbFat16Overhead + FAT_MAX_FAT16_DATA_CLUSTERS * 128 * cbSector)
+ {
+ enmFatType = RTFSFATTYPE_FAT16;
+ cSectorsPerCluster = 1;
+ while ( cSectorsPerCluster < 128
+ && cSectorsInVol
+ > cbFat12Overhead / cbSector
+ + (uint32_t)cSectorsPerCluster * FAT_MAX_FAT16_DATA_CLUSTERS
+ + cSectorsPerCluster - 1)
+ cSectorsPerCluster <<= 1;
+ }
+ else
+ {
+ /* The target here is keeping the FAT size below 8MB. Seems windows
+ likes a minimum 4KB cluster size as wells as a max of 32KB (googling). */
+ enmFatType = RTFSFATTYPE_FAT32;
+ uint32_t cbFat32Overhead = cbSector * 32 /* boot sector, info sector, boot sector copies, reserved sectors */
+ + _8M * cFats;
+ if (cbSector >= _4K)
+ cSectorsPerCluster = 1;
+ else
+ cSectorsPerCluster = _4K / cbSector;
+ while ( cSectorsPerCluster < 128
+ && cSectorsPerCluster * cbSector < _32K
+ && cSectorsInVol > cbFat32Overhead / cbSector + (uint64_t)cSectorsPerCluster * _2M)
+ cSectorsPerCluster <<= 1;
+ }
+ }
+ }
+ }
+ Assert(cSectorsPerCluster);
+ Assert(cRootDirEntries);
+ uint32_t cbRootDir = RT_ALIGN_32(cRootDirEntries * sizeof(FATDIRENTRY), cbSector);
+ uint32_t const cbCluster = cSectorsPerCluster * cbSector;
+
+ /*
+ * If we haven't figured out the FAT type yet, do so.
+ * The file system code determins the FAT based on cluster counts,
+ * so we must do so here too.
+ */
+ if (enmFatType == RTFSFATTYPE_INVALID)
+ {
+ uint32_t cbFat12Overhead = cbSector /* boot sector */
+ + RT_ALIGN_32(FAT_MAX_FAT12_TOTAL_CLUSTERS * 3 / 2, cbSector) * cFats /* FATs */
+ + RT_ALIGN_32(cRootDirEntries * sizeof(FATDIRENTRY), cbSector) /* root dir */;
+ if ( cbVol <= cbFat12Overhead + cbCluster
+ || (cbVol - cbFat12Overhead) / cbCluster <= FAT_MAX_FAT12_DATA_CLUSTERS)
+ enmFatType = RTFSFATTYPE_FAT12;
+ else
+ {
+ uint32_t cbFat16Overhead = cbSector /* boot sector */
+ + RT_ALIGN_32(FAT_MAX_FAT16_TOTAL_CLUSTERS * 2, cbSector) * cFats /* FATs */
+ + cbRootDir;
+ if ( cbVol <= cbFat16Overhead + cbCluster
+ || (cbVol - cbFat16Overhead) / cbCluster <= FAT_MAX_FAT16_DATA_CLUSTERS)
+ enmFatType = RTFSFATTYPE_FAT16;
+ else
+ enmFatType = RTFSFATTYPE_FAT32;
+ }
+ }
+ if (enmFatType == RTFSFATTYPE_FAT32)
+ cbRootDir = cbCluster;
+
+ /*
+ * Calculate the FAT size and number of data cluster.
+ *
+ * Since the FAT size depends on how many data clusters there are, we start
+ * with a minimum FAT size and maximum clust count, then recalucate it. The
+ * result isn't necessarily stable, so we will only retry stabalizing the
+ * result a few times.
+ */
+ uint32_t cbReservedFixed = enmFatType == RTFSFATTYPE_FAT32 ? 32 * cbSector : cbSector + cbRootDir;
+ uint32_t cbFat = cbSector;
+ if (cbReservedFixed + cbFat * cFats >= cbVol)
+ return RTErrInfoSetF(pErrInfo, VERR_DISK_FULL, "volume is too small (cbVol=%#RX64 rsvd=%#x cbFat=%#x cFat=%#x)",
+ cbVol, cbReservedFixed, cbFat, cFats);
+ uint32_t cMaxClusters = enmFatType == RTFSFATTYPE_FAT12 ? FAT_MAX_FAT12_DATA_CLUSTERS
+ : enmFatType == RTFSFATTYPE_FAT16 ? FAT_MAX_FAT16_DATA_CLUSTERS
+ : FAT_MAX_FAT12_DATA_CLUSTERS;
+ uint32_t cClusters = (uint32_t)RT_MIN((cbVol - cbReservedFixed - cbFat * cFats) / cbCluster, cMaxClusters);
+ uint32_t cPrevClusters;
+ uint32_t cTries = 4;
+ do
+ {
+ cPrevClusters = cClusters;
+ switch (enmFatType)
+ {
+ case RTFSFATTYPE_FAT12:
+ cbFat = (uint32_t)RT_MIN(FAT_MAX_FAT12_TOTAL_CLUSTERS, cClusters) * 3 / 2;
+ break;
+ case RTFSFATTYPE_FAT16:
+ cbFat = (uint32_t)RT_MIN(FAT_MAX_FAT16_TOTAL_CLUSTERS, cClusters) * 2;
+ break;
+ case RTFSFATTYPE_FAT32:
+ cbFat = (uint32_t)RT_MIN(FAT_MAX_FAT32_TOTAL_CLUSTERS, cClusters) * 4;
+ cbFat = RT_ALIGN_32(cbFat, _4K);
+ break;
+ default:
+ AssertFailedReturn(VERR_INTERNAL_ERROR_2);
+ }
+ cbFat = RT_ALIGN_32(cbFat, cbSector);
+ if (cbReservedFixed + cbFat * cFats >= cbVol)
+ return RTErrInfoSetF(pErrInfo, VERR_DISK_FULL, "volume is too small (cbVol=%#RX64 rsvd=%#x cbFat=%#x cFat=%#x)",
+ cbVol, cbReservedFixed, cbFat, cFats);
+ cClusters = (uint32_t)RT_MIN((cbVol - cbReservedFixed - cbFat * cFats) / cbCluster, cMaxClusters);
+ } while ( cClusters != cPrevClusters
+ && cTries-- > 0);
+ uint64_t const cTotalSectors = cClusters * (uint64_t)cSectorsPerCluster + (cbReservedFixed + cbFat * cFats) / cbSector;
+
+ /*
+ * Check that the file system type and cluster count matches up. If they
+ * don't the type will be misdetected.
+ *
+ * Note! These assertions could trigger if the above calculations are wrong.
+ */
+ switch (enmFatType)
+ {
+ case RTFSFATTYPE_FAT12:
+ AssertMsgReturn(cClusters >= FAT_MIN_FAT12_DATA_CLUSTERS && cClusters <= FAT_MAX_FAT12_DATA_CLUSTERS,
+ ("cClusters=%#x\n", cClusters), VERR_OUT_OF_RANGE);
+ break;
+ case RTFSFATTYPE_FAT16:
+ AssertMsgReturn(cClusters >= FAT_MIN_FAT16_DATA_CLUSTERS && cClusters <= FAT_MAX_FAT16_DATA_CLUSTERS,
+ ("cClusters=%#x\n", cClusters), VERR_OUT_OF_RANGE);
+ break;
+ case RTFSFATTYPE_FAT32:
+ AssertMsgReturn(cClusters >= FAT_MIN_FAT32_DATA_CLUSTERS && cClusters <= FAT_MAX_FAT32_DATA_CLUSTERS,
+ ("cClusters=%#x\n", cClusters), VERR_OUT_OF_RANGE);
+ RT_FALL_THRU();
+ default:
+ AssertFailedReturn(VERR_INTERNAL_ERROR_2);
+ }
+
+ /*
+ * Okay, create the boot sector.
+ */
+ size_t cbBuf = RT_MAX(RT_MAX(_64K, cbCluster), cbSector * 2U);
+ uint8_t *pbBuf = (uint8_t *)RTMemTmpAllocZ(cbBuf);
+ AssertReturn(pbBuf, VERR_NO_TMP_MEMORY);
+
+ const char *pszLastOp = "boot sector";
+ PFATBOOTSECTOR pBootSector = (PFATBOOTSECTOR)pbBuf;
+ pBootSector->abJmp[0] = 0xeb;
+ pBootSector->abJmp[1] = RT_UOFFSETOF(FATBOOTSECTOR, Bpb)
+ + (enmFatType == RTFSFATTYPE_FAT32 ? sizeof(FAT32EBPB) : sizeof(FATEBPB)) - 2;
+ pBootSector->abJmp[2] = 0x90;
+ memcpy(pBootSector->achOemName, enmFatType == RTFSFATTYPE_FAT32 ? "FAT32 " : "IPRT 6.2", sizeof(pBootSector->achOemName));
+ pBootSector->Bpb.Bpb331.cbSector = (uint16_t)cbSector;
+ pBootSector->Bpb.Bpb331.cSectorsPerCluster = (uint8_t)cSectorsPerCluster;
+ pBootSector->Bpb.Bpb331.cReservedSectors = enmFatType == RTFSFATTYPE_FAT32 ? cbReservedFixed / cbSector : 1;
+ pBootSector->Bpb.Bpb331.cFats = (uint8_t)cFats;
+ pBootSector->Bpb.Bpb331.cMaxRootDirEntries = enmFatType == RTFSFATTYPE_FAT32 ? 0 : cRootDirEntries;
+ pBootSector->Bpb.Bpb331.cTotalSectors16 = cTotalSectors <= UINT16_MAX ? (uint16_t)cTotalSectors : 0;
+ pBootSector->Bpb.Bpb331.bMedia = bMedia;
+ pBootSector->Bpb.Bpb331.cSectorsPerFat = enmFatType == RTFSFATTYPE_FAT32 ? 0 : cbFat / cbSector;
+ pBootSector->Bpb.Bpb331.cSectorsPerTrack = cSectorsPerTrack;
+ pBootSector->Bpb.Bpb331.cTracksPerCylinder = cHeads;
+ pBootSector->Bpb.Bpb331.cHiddenSectors = cHiddenSectors;
+ /* XP barfs if both cTotalSectors32 and cTotalSectors16 are set */
+ pBootSector->Bpb.Bpb331.cTotalSectors32 = cTotalSectors <= UINT32_MAX && pBootSector->Bpb.Bpb331.cTotalSectors16 == 0
+ ? (uint32_t)cTotalSectors : 0;
+ if (enmFatType != RTFSFATTYPE_FAT32)
+ {
+ pBootSector->Bpb.Ebpb.bInt13Drive = 0;
+ pBootSector->Bpb.Ebpb.bReserved = 0;
+ pBootSector->Bpb.Ebpb.bExtSignature = FATEBPB_SIGNATURE;
+ pBootSector->Bpb.Ebpb.uSerialNumber = RTRandU32();
+ memset(pBootSector->Bpb.Ebpb.achLabel, ' ', sizeof(pBootSector->Bpb.Ebpb.achLabel));
+ memcpy(pBootSector->Bpb.Ebpb.achType, enmFatType == RTFSFATTYPE_FAT12 ? "FAT12 " : "FAT16 ",
+ sizeof(pBootSector->Bpb.Ebpb.achType));
+ }
+ else
+ {
+ pBootSector->Bpb.Fat32Ebpb.cSectorsPerFat32 = cbFat / cbSector;
+ pBootSector->Bpb.Fat32Ebpb.fFlags = 0;
+ pBootSector->Bpb.Fat32Ebpb.uVersion = FAT32EBPB_VERSION_0_0;
+ pBootSector->Bpb.Fat32Ebpb.uRootDirCluster = FAT_FIRST_DATA_CLUSTER;
+ pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo = 1;
+ pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo = 6;
+ RT_ZERO(pBootSector->Bpb.Fat32Ebpb.abReserved);
+
+ pBootSector->Bpb.Fat32Ebpb.bInt13Drive = 0;
+ pBootSector->Bpb.Fat32Ebpb.bReserved = 0;
+ pBootSector->Bpb.Fat32Ebpb.bExtSignature = FATEBPB_SIGNATURE;
+ pBootSector->Bpb.Fat32Ebpb.uSerialNumber = RTRandU32();
+ memset(pBootSector->Bpb.Fat32Ebpb.achLabel, ' ', sizeof(pBootSector->Bpb.Fat32Ebpb.achLabel));
+ if (cTotalSectors > UINT32_MAX)
+ pBootSector->Bpb.Fat32Ebpb.u.cTotalSectors64 = cTotalSectors;
+ else
+ memcpy(pBootSector->Bpb.Fat32Ebpb.u.achType, "FAT32 ", sizeof(pBootSector->Bpb.Fat32Ebpb.u.achType));
+ }
+ pbBuf[pBootSector->abJmp[1] + 2 + 0] = 0xcd; /* int 18h */ /** @todo find/implement booting of next boot device. */
+ pbBuf[pBootSector->abJmp[1] + 2 + 1] = 0x18;
+ pbBuf[pBootSector->abJmp[1] + 2 + 2] = 0xcc; /* int3 */
+ pbBuf[pBootSector->abJmp[1] + 2 + 3] = 0xcc;
+
+ pBootSector->uSignature = FATBOOTSECTOR_SIGNATURE;
+ if (cbSector != sizeof(*pBootSector))
+ *(uint16_t *)&pbBuf[cbSector - 2] = FATBOOTSECTOR_SIGNATURE; /** @todo figure out how disks with non-512 byte sectors work! */
+
+ rc = RTVfsFileWriteAt(hVfsFile, offVol, pBootSector, cbSector, NULL);
+ uint32_t const offFirstFat = pBootSector->Bpb.Bpb331.cReservedSectors * cbSector;
+
+ /*
+ * Write the FAT32 info sector, 3 boot sector copies, and zero fill
+ * the other reserved sectors.
+ */
+ if (RT_SUCCESS(rc) && enmFatType == RTFSFATTYPE_FAT32)
+ {
+ pszLastOp = "fat32 info sector";
+ PFAT32INFOSECTOR pInfoSector = (PFAT32INFOSECTOR)&pbBuf[cbSector]; /* preserve the boot sector. */
+ RT_ZERO(*pInfoSector);
+ pInfoSector->uSignature1 = FAT32INFOSECTOR_SIGNATURE_1;
+ pInfoSector->uSignature2 = FAT32INFOSECTOR_SIGNATURE_2;
+ pInfoSector->uSignature3 = FAT32INFOSECTOR_SIGNATURE_3;
+ pInfoSector->cFreeClusters = cClusters - 1; /* ASSUMES 1 cluster for the root dir. */
+ pInfoSector->cLastAllocatedCluster = FAT_FIRST_DATA_CLUSTER;
+ rc = RTVfsFileWriteAt(hVfsFile, offVol + cbSector, pInfoSector, cbSector, NULL);
+
+ uint32_t iSector = 2;
+ if (RT_SUCCESS(rc))
+ {
+ pszLastOp = "fat32 unused reserved sectors";
+ rc = rtFsFatVolWriteZeros(hVfsFile, offVol + iSector * cbSector,
+ (pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo - iSector) * cbSector);
+ iSector = pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pszLastOp = "boot sector copy";
+ for (uint32_t i = 0; i < 3 && RT_SUCCESS(rc); i++, iSector++)
+ rc = RTVfsFileWriteAt(hVfsFile, offVol + iSector * cbSector, pBootSector, cbSector, NULL);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pszLastOp = "fat32 unused reserved sectors";
+ rc = rtFsFatVolWriteZeros(hVfsFile, offVol + iSector * cbSector,
+ (pBootSector->Bpb.Bpb331.cReservedSectors - iSector) * cbSector);
+ }
+ }
+
+ /*
+ * The FATs.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ pszLastOp = "fat";
+ pBootSector = NULL; /* invalid */
+ RT_BZERO(pbBuf, cbSector);
+ switch (enmFatType)
+ {
+ case RTFSFATTYPE_FAT32:
+ pbBuf[11] = 0x0f; /* EOC for root dir*/
+ pbBuf[10] = 0xff;
+ pbBuf[9] = 0xff;
+ pbBuf[8] = 0xff;
+ pbBuf[7] = 0x0f; /* Formatter's EOC, followed by signed extend FAT ID. */
+ pbBuf[6] = 0xff;
+ pbBuf[5] = 0xff;
+ pbBuf[4] = 0xff;
+ RT_FALL_THRU();
+ case RTFSFATTYPE_FAT16:
+ pbBuf[3] = 0xff;
+ RT_FALL_THRU();
+ case RTFSFATTYPE_FAT12:
+ pbBuf[2] = 0xff;
+ pbBuf[1] = 0xff;
+ pbBuf[0] = bMedia; /* FAT ID */
+ break;
+ default: AssertFailed();
+ }
+ for (uint32_t iFatCopy = 0; iFatCopy < cFats && RT_SUCCESS(rc); iFatCopy++)
+ {
+ rc = RTVfsFileWriteAt(hVfsFile, offVol + offFirstFat + cbFat * iFatCopy, pbBuf, cbSector, NULL);
+ if (RT_SUCCESS(rc) && cbFat > cbSector)
+ rc = rtFsFatVolWriteZeros(hVfsFile, offVol + offFirstFat + cbFat * iFatCopy + cbSector, cbFat - cbSector);
+ }
+ }
+
+ /*
+ * The root directory.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo any mandatory directory entries we need to fill in here? */
+ pszLastOp = "root dir";
+ rc = rtFsFatVolWriteZeros(hVfsFile, offVol + offFirstFat + cbFat * cFats, cbRootDir);
+ }
+
+ /*
+ * If long format, fill the rest of the disk with 0xf6.
+ */
+ AssertCompile(RTFSFATVOL_FMT_F_QUICK != 0);
+ if (RT_SUCCESS(rc) && !(fFlags & RTFSFATVOL_FMT_F_QUICK))
+ {
+ pszLastOp = "formatting data clusters";
+ uint64_t offCur = offFirstFat + cbFat * cFats + cbRootDir;
+ uint64_t cbLeft = cTotalSectors * cbSector;
+ if (cbVol - cbLeft <= _256K) /* HACK ALERT! Format to end of volume if it's a cluster rounding thing. */
+ cbLeft = cbVol;
+ if (cbLeft > offCur)
+ {
+ cbLeft -= offCur;
+ offCur += offVol;
+
+ memset(pbBuf, 0xf6, cbBuf);
+ while (cbLeft > 0)
+ {
+ size_t cbToWrite = cbLeft >= cbBuf ? cbBuf : (size_t)cbLeft;
+ rc = RTVfsFileWriteAt(hVfsFile, offCur, pbBuf, cbToWrite, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ offCur += cbToWrite;
+ cbLeft -= cbToWrite;
+ }
+ else
+ break;
+ }
+ }
+ }
+
+ /*
+ * Done.
+ */
+ RTMemTmpFree(pbBuf);
+ if (RT_SUCCESS(rc))
+ return rc;
+ return RTErrInfoSet(pErrInfo, rc, pszLastOp);
+}
+
+
+/**
+ * Formats a 1.44MB floppy image.
+ *
+ * @returns IPRT status code.
+ * @param hVfsFile The image.
+ */
+RTDECL(int) RTFsFatVolFormat144(RTVFSFILE hVfsFile, bool fQuick)
+{
+ return RTFsFatVolFormat(hVfsFile, 0 /*offVol*/, 1474560, fQuick ? RTFSFATVOL_FMT_F_QUICK : RTFSFATVOL_FMT_F_FULL,
+ 512 /*cbSector*/, 1 /*cSectorsPerCluster*/, RTFSFATTYPE_FAT12, 2 /*cHeads*/, 18 /*cSectors*/,
+ 0xf0 /*bMedia*/, 224 /*cRootDirEntries*/, 0 /*cHiddenSectors*/, NULL /*pErrInfo*/);
+}
+
+
+/**
+ * Formats a 2.88MB floppy image.
+ *
+ * @returns IPRT status code.
+ * @param hVfsFile The image.
+ */
+RTDECL(int) RTFsFatVolFormat288(RTVFSFILE hVfsFile, bool fQuick)
+{
+ return RTFsFatVolFormat(hVfsFile, 0 /*offVol*/, 2949120, fQuick ? RTFSFATVOL_FMT_F_QUICK : RTFSFATVOL_FMT_F_FULL,
+ 512 /*cbSector*/, 2 /*cSectorsPerCluster*/, RTFSFATTYPE_FAT12, 2 /*cHeads*/, 36 /*cSectors*/,
+ 0xf0 /*bMedia*/, 224 /*cRootDirEntries*/, 0 /*cHiddenSectors*/, NULL /*pErrInfo*/);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate}
+ */
+static DECLCALLBACK(int) rtVfsChainFatVol_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec,
+ PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg);
+
+ /*
+ * 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.
+ */
+ bool fReadOnly = (pSpec->fOpenFile & RTFILE_O_ACCESS_MASK) == RTFILE_O_READ;
+ if (pElement->cArgs > 0)
+ {
+ const char *psz = pElement->paArgs[0].psz;
+ if (*psz)
+ {
+ if (!strcmp(psz, "ro"))
+ fReadOnly = true;
+ else if (!strcmp(psz, "rw"))
+ fReadOnly = false;
+ else
+ {
+ *poffError = pElement->paArgs[0].offSpec;
+ return RTErrInfoSet(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Expected 'ro' or 'rw' as argument");
+ }
+ }
+ }
+
+ pElement->uProvider = fReadOnly;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate}
+ */
+static DECLCALLBACK(int) rtVfsChainFatVol_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 = RTFsFatVolOpen(hVfsFileIn, pElement->uProvider != false, 0, &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) rtVfsChainFatVol_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_rtVfsChainFatVolReg =
+{
+ /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION,
+ /* fReserved = */ 0,
+ /* pszName = */ "fat",
+ /* ListEntry = */ { NULL, NULL },
+ /* pszHelp = */ "Open a FAT file system, requires a file object on the left side.\n"
+ "First argument is an optional 'ro' (read-only) or 'rw' (read-write) flag.\n",
+ /* pfnValidate = */ rtVfsChainFatVol_Validate,
+ /* pfnInstantiate = */ rtVfsChainFatVol_Instantiate,
+ /* pfnCanReuseElement = */ rtVfsChainFatVol_CanReuseElement,
+ /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION
+};
+
+RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainFatVolReg, rtVfsChainFatVolReg);
+
diff --git a/src/VBox/Runtime/common/fs/isomaker.cpp b/src/VBox/Runtime/common/fs/isomaker.cpp
new file mode 100644
index 00000000..9a1c4f8d
--- /dev/null
+++ b/src/VBox/Runtime/common/fs/isomaker.cpp
@@ -0,0 +1,7585 @@
+/* $Id: isomaker.cpp $ */
+/** @file
+ * IPRT - ISO Image Maker.
+ */
+
+/*
+ * 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/fsisomaker.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/buildconfig.h>
+#include <iprt/err.h>
+#include <iprt/ctype.h>
+#include <iprt/md5.h>
+#include <iprt/file.h>
+#include <iprt/list.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+#include <iprt/zero.h>
+#include <iprt/formats/iso9660.h>
+
+#include <internal/magics.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Asserts valid handle, returns @a a_rcRet if not. */
+#define RTFSISOMAKER_ASSERT_VALID_HANDLE_RET_EX(a_pThis, a_rcRet) \
+ do { AssertPtrReturn(a_pThis, a_rcRet); \
+ AssertReturn((a_pThis)->uMagic == RTFSISOMAKERINT_MAGIC, a_rcRet); \
+ } while (0)
+
+/** Asserts valid handle, returns VERR_INVALID_HANDLE if not. */
+#define RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(a_pThis) RTFSISOMAKER_ASSERT_VALID_HANDLE_RET_EX(a_pThis, VERR_INVALID_HANDLE)
+
+/** The sector size. */
+#define RTFSISOMAKER_SECTOR_SIZE _2K
+/** The sector offset mask. */
+#define RTFSISOMAKER_SECTOR_OFFSET_MASK (_2K - 1)
+/** Maximum number of objects. */
+#define RTFSISOMAKER_MAX_OBJECTS _16M
+/** Maximum number of objects per directory. */
+#define RTFSISOMAKER_MAX_OBJECTS_PER_DIR _256K /**< @todo check limit */
+
+/** Number of bytes to store per dir record when using multiple extents. */
+#define RTFSISOMAKER_MAX_ISO9660_EXTENT_SIZE UINT32_C(0xfffff800)
+
+/** UTF-8 name buffer. */
+#define RTFSISOMAKER_MAX_NAME_BUF 768
+
+/** Max symbolic link target length. */
+#define RTFSISOMAKER_MAX_SYMLINK_TARGET_LEN 260
+
+/** TRANS.TBL left padding length.
+ * We keep the amount of padding low to avoid wasing memory when generating
+ * these long obsolete files. */
+#define RTFSISOMAKER_TRANS_TBL_LEFT_PAD 12
+
+/** Tests if @a a_ch is in the set of d-characters. */
+#define RTFSISOMAKER_IS_IN_D_CHARS(a_ch) (RT_C_IS_UPPER(a_ch) || RT_C_IS_DIGIT(a_ch) || (a_ch) == '_')
+
+/** Tests if @a a_ch is in the set of d-characters when uppercased. */
+#define RTFSISOMAKER_IS_UPPER_IN_D_CHARS(a_ch) (RT_C_IS_ALNUM(a_ch) || (a_ch) == '_')
+
+
+/** Calculates the path table record size given the name length.
+ * @note The root directory length is 1 (name byte is 0x00), we make sure this
+ * is the case in rtFsIsoMakerNormalizeNameForNamespace. */
+#define RTFSISOMAKER_CALC_PATHREC_SIZE(a_cbNameInDirRec) \
+ ( RT_UOFFSETOF_DYN(ISO9660PATHREC, achDirId[(a_cbNameInDirRec) + ((a_cbNameInDirRec) & 1)]) )
+
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to an ISO maker object name space node. */
+typedef struct RTFSISOMAKERNAME *PRTFSISOMAKERNAME;
+/** Pointer to a const ISO maker object name space node. */
+typedef struct RTFSISOMAKERNAME const *PCRTFSISOMAKERNAME;
+/** Pointer to an ISO maker object name space node pointer. */
+typedef PRTFSISOMAKERNAME *PPRTFSISOMAKERNAME;
+
+/** Pointer to a common ISO image maker file system object. */
+typedef struct RTFSISOMAKEROBJ *PRTFSISOMAKEROBJ;
+/** Pointer to a const common ISO image maker file system object. */
+typedef struct RTFSISOMAKEROBJ const *PCRTFSISOMAKEROBJ;
+
+/** Pointer to a ISO maker file object. */
+typedef struct RTFSISOMAKERFILE *PRTFSISOMAKERFILE;
+/** Pointer to a const ISO maker file object. */
+typedef struct RTFSISOMAKERFILE const *PCRTFSISOMAKERFILE;
+
+/**
+ * Filesystem object type.
+ */
+typedef enum RTFSISOMAKEROBJTYPE
+{
+ RTFSISOMAKEROBJTYPE_INVALID = 0,
+ RTFSISOMAKEROBJTYPE_DIR,
+ RTFSISOMAKEROBJTYPE_FILE,
+ RTFSISOMAKEROBJTYPE_SYMLINK,
+ RTFSISOMAKEROBJTYPE_END
+} RTFSISOMAKEROBJTYPE;
+
+/**
+ * Extra name space information required for directories.
+ */
+typedef struct RTFSISOMAKERNAMEDIR
+{
+ /** The location of the directory data. */
+ uint64_t offDir;
+ /** The size of the directory. */
+ uint32_t cbDir;
+ /** Number of children. */
+ uint32_t cChildren;
+ /** Sorted array of children. */
+ PPRTFSISOMAKERNAME papChildren;
+ /** The translate table file. */
+ PRTFSISOMAKERFILE pTransTblFile;
+
+ /** The offset in the path table (ISO-9660).
+ * This is set when finalizing the image. */
+ uint32_t offPathTable;
+ /** The path table identifier of this directory (ISO-9660).
+ * This is set when finalizing the image. */
+ uint16_t idPathTable;
+ /** The size of the first directory record (0x00 - '.'). */
+ uint8_t cbDirRec00;
+ /** The size of the second directory record (0x01 - '..'). */
+ uint8_t cbDirRec01;
+ /** Pointer to back to the namespace node this belongs to (for the finalized
+ * entry list). */
+ PRTFSISOMAKERNAME pName;
+ /** Entry in the list of finalized directories. */
+ RTLISTNODE FinalizedEntry;
+} RTFSISOMAKERNAMEDIR;
+/** Pointer to directory specfic namespace node info. */
+typedef RTFSISOMAKERNAMEDIR *PRTFSISOMAKERNAMEDIR;
+/** Pointer to const directory specfic namespace node info. */
+typedef const RTFSISOMAKERNAMEDIR *PCRTFSISOMAKERNAMEDIR;
+
+
+/**
+ * ISO maker object namespace node.
+ */
+typedef struct RTFSISOMAKERNAME
+{
+ /** Pointer to the file system object. */
+ PRTFSISOMAKEROBJ pObj;
+ /** Pointer to the partent directory, NULL if root dir. */
+ PRTFSISOMAKERNAME pParent;
+
+ /** Pointer to the directory information if this is a directory, NULL if not a
+ * directory. This is allocated together with this structure, so it doesn't need
+ * freeing. */
+ PRTFSISOMAKERNAMEDIR pDir;
+
+ /** The name specified when creating this namespace node. Helps navigating
+ * the namespace when we mangle or otherwise change the names.
+ * Allocated together with of this structure, no spearate free necessary. */
+ const char *pszSpecNm;
+
+ /** Alternative rock ridge name. */
+ char *pszRockRidgeNm;
+ /** Alternative TRANS.TBL name. */
+ char *pszTransNm;
+ /** Length of pszSpecNm. */
+ uint16_t cchSpecNm;
+ /** Length of pszRockRidgeNm. */
+ uint16_t cchRockRidgeNm;
+ /** Length of pszTransNm. */
+ uint16_t cchTransNm;
+
+ /** The depth in the namespace tree of this name. */
+ uint8_t uDepth;
+ /** Set if pszTransNm is allocated separately. Normally same as pszSpecNm. */
+ bool fRockRidgeNmAlloced : 1;
+ /** Set if pszTransNm is allocated separately. Normally same as pszSpecNm. */
+ bool fTransNmAlloced : 1;
+ /** Set if we need to emit an ER entry (root only). */
+ bool fRockNeedER : 1;
+ /** Set if we need to emit a RR entry in the directory record. */
+ bool fRockNeedRRInDirRec : 1;
+ /** Set if we need to emit a RR entry in the spill file. */
+ bool fRockNeedRRInSpill : 1;
+
+ /** The mode mask.
+ * Starts out as a copy of RTFSISOMAKEROBJ::fMode. */
+ RTFMODE fMode;
+ /** The owner ID.
+ * Starts out as a copy of RTFSISOMAKEROBJ::uid. */
+ RTUID uid;
+ /** The group ID.
+ * Starts out as a copy of RTFSISOMAKEROBJ::gid. */
+ RTGID gid;
+ /** The device number if a character or block device.
+ * This is for Rock Ridge. */
+ RTDEV Device;
+ /** The number of hardlinks to report in the file stats.
+ * This is for Rock Ridge. */
+ uint32_t cHardlinks;
+
+ /** The offset of the directory entry in the parent directory. */
+ uint32_t offDirRec;
+ /** Size of the directory record (ISO-9660).
+ * This is set when the image is being finalized. */
+ uint16_t cbDirRec;
+ /** Number of directory records needed to cover the entire file size. */
+ uint16_t cDirRecs;
+ /** The total directory record size (cbDirRec * cDirRecs), including end of
+ * sector zero padding. */
+ uint16_t cbDirRecTotal;
+
+ /** Rock ridge flags (ISO9660RRIP_RR_F_XXX). */
+ uint8_t fRockEntries;
+ /** Number of rock ridge data bytes in the directory record. Unaligned! */
+ uint8_t cbRockInDirRec;
+ /** Rock ridge spill file data offset, UINT32_MAX if placed in dir record. */
+ uint32_t offRockSpill;
+ /** Size of rock data in spill file. */
+ uint16_t cbRockSpill;
+
+ /** The number of bytes the name requires in the directory record. */
+ uint16_t cbNameInDirRec;
+ /** The name length. */
+ uint16_t cchName;
+ /** The name. */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ char szName[RT_FLEXIBLE_ARRAY];
+} RTFSISOMAKERNAME;
+
+/**
+ * A ISO maker namespace.
+ */
+typedef struct RTFSISOMAKERNAMESPACE
+{
+ /** The namespace root. */
+ PRTFSISOMAKERNAME pRoot;
+ /** Total number of name nodes in the namespace. */
+ uint32_t cNames;
+ /** Total number of directories in the namespace.
+ * @note Appears to be unused. */
+ uint32_t cDirs;
+ /** The namespace selector (RTFSISOMAKER_NAMESPACE_XXX). */
+ uint32_t fNamespace;
+ /** Offset into RTFSISOMAKERNAMESPACE of the name member. */
+ uint32_t offName;
+ /** The configuration level for this name space.
+ * - For UDF and HFS namespaces this is either @c true or @c false.
+ * - For the primary ISO-9660 namespace this is 1, 2, or 3.
+ * - For the joliet namespace this 0 (joliet disabled), 1, 2, or 3. */
+ uint8_t uLevel;
+ /** The rock ridge level: 1 - enabled; 2 - with ER tag.
+ * Linux behaves a little different when seeing the ER tag. */
+ uint8_t uRockRidgeLevel;
+ /** The TRANS.TBL filename if enabled, NULL if disabled.
+ * When not NULL, this may be pointing to heap or g_szTransTbl. */
+ char *pszTransTbl;
+ /** The system ID (ISO9660PRIMARYVOLDESC::achSystemId). Empty if NULL.
+ * When not NULL, this may be pointing to heap of g_szSystemId. */
+ char *pszSystemId;
+ /** The volume ID / label (ISO9660PRIMARYVOLDESC::achVolumeId).
+ * A string representation of RTFSISOMAKERINT::ImageCreationTime if NULL. */
+ char *pszVolumeId;
+ /** The volume set ID (ISO9660PRIMARYVOLDESC::achVolumeSetId). Empty if NULL. */
+ char *pszVolumeSetId;
+ /** The publisher ID or (root) file reference (ISO9660PRIMARYVOLDESC::achPublisherId). Empty if NULL. */
+ char *pszPublisherId;
+ /* The data preperer ID or (root) file reference (ISO9660PRIMARYVOLDESC::achDataPreparerId). Empty if NULL. */
+ char *pszDataPreparerId;
+ /* The application ID or (root) file reference (ISO9660PRIMARYVOLDESC::achApplicationId).
+ * Defaults to g_szAppIdPrimaryIso or g_szAppIdJoliet. */
+ char *pszApplicationId;
+ /** The copyright (root) file identifier (ISO9660PRIMARYVOLDESC::achCopyrightFileId). None if NULL. */
+ char *pszCopyrightFileId;
+ /** The abstract (root) file identifier (ISO9660PRIMARYVOLDESC::achAbstractFileId). None if NULL. */
+ char *pszAbstractFileId;
+ /** The bibliographic (root) file identifier (ISO9660PRIMARYVOLDESC::achBibliographicFileId). None if NULL. */
+ char *pszBibliographicFileId;
+} RTFSISOMAKERNAMESPACE;
+/** Pointer to a namespace. */
+typedef RTFSISOMAKERNAMESPACE *PRTFSISOMAKERNAMESPACE;
+/** Pointer to a const namespace. */
+typedef RTFSISOMAKERNAMESPACE const *PCRTFSISOMAKERNAMESPACE;
+
+
+/**
+ * Common base structure for the file system objects.
+ *
+ * The times are shared across all namespaces, while the uid, gid and mode are
+ * duplicates in each namespace.
+ */
+typedef struct RTFSISOMAKEROBJ
+{
+ /** The linear list entry of the image content. */
+ RTLISTNODE Entry;
+ /** The object index. */
+ uint32_t idxObj;
+ /** The type of this object. */
+ RTFSISOMAKEROBJTYPE enmType;
+
+ /** The primary ISO-9660 name space name. */
+ PRTFSISOMAKERNAME pPrimaryName;
+ /** The joliet name space name. */
+ PRTFSISOMAKERNAME pJolietName;
+ /** The UDF name space name. */
+ PRTFSISOMAKERNAME pUdfName;
+ /** The HFS name space name. */
+ PRTFSISOMAKERNAME pHfsName;
+
+ /** Birth (creation) time. */
+ RTTIMESPEC BirthTime;
+ /** Attribute change time. */
+ RTTIMESPEC ChangeTime;
+ /** Modification time. */
+ RTTIMESPEC ModificationTime;
+ /** Accessed time. */
+ RTTIMESPEC AccessedTime;
+
+ /** Owner ID. */
+ RTUID uid;
+ /** Group ID. */
+ RTGID gid;
+ /** Attributes (unix permissions bits mainly). */
+ RTFMODE fMode;
+
+ /** Used to make sure things like the boot catalog stays in the image even if
+ * it's not mapped into any of the namespaces. */
+ uint32_t cNotOrphan;
+} RTFSISOMAKEROBJ;
+
+
+/**
+ * File source type.
+ */
+typedef enum RTFSISOMAKERSRCTYPE
+{
+ RTFSISOMAKERSRCTYPE_INVALID = 0,
+ RTFSISOMAKERSRCTYPE_PATH,
+ RTFSISOMAKERSRCTYPE_VFS_FILE,
+ RTFSISOMAKERSRCTYPE_COMMON,
+ RTFSISOMAKERSRCTYPE_TRANS_TBL,
+ RTFSISOMAKERSRCTYPE_RR_SPILL,
+ RTFSISOMAKERSRCTYPE_END
+} RTFSISOMAKERSRCTYPE;
+
+/**
+ * ISO maker file object.
+ */
+typedef struct RTFSISOMAKERFILE
+{
+ /** The common bit. */
+ RTFSISOMAKEROBJ Core;
+ /** The file data size. */
+ uint64_t cbData;
+ /** Byte offset of the data in the image.
+ * UINT64_MAX until the location is finalized. */
+ uint64_t offData;
+
+ /** The type of source object. */
+ RTFSISOMAKERSRCTYPE enmSrcType;
+ /** The source data. */
+ union
+ {
+ /** Path to the source file.
+ * Allocated together with this structure. */
+ const char *pszSrcPath;
+ /** Source VFS file. */
+ RTVFSFILE hVfsFile;
+ /** Source is a part of a common VFS file. */
+ struct
+ {
+ /** The offset into the file */
+ uint64_t offData;
+ /** The index of the common file. */
+ uint32_t idxSrc;
+ } Common;
+ /** The directory the translation table belongs to. */
+ PRTFSISOMAKERNAME pTransTblDir;
+ /** The namespace for a rock ridge spill file.. */
+ PRTFSISOMAKERNAMESPACE pRockSpillNamespace;
+ } u;
+
+ /** Boot info table to patch into the file.
+ * This is calculated during file finalization as it needs the file location. */
+ PISO9660SYSLINUXINFOTABLE pBootInfoTable;
+
+ /** Entry in the list of finalized directories. */
+ RTLISTNODE FinalizedEntry;
+} RTFSISOMAKERFILE;
+
+
+/**
+ * ISO maker directory object.
+ *
+ * Unlike files, the allocation info is name space specific and lives in the
+ * corresponding RTFSISOMAKERNAMEDIR structures.
+ */
+typedef struct RTFSISOMAKERDIR
+{
+ /** The common bit. */
+ RTFSISOMAKEROBJ Core;
+} RTFSISOMAKERDIR;
+/** Pointer to an ISO maker directory object. */
+typedef RTFSISOMAKERDIR *PRTFSISOMAKERDIR;
+
+
+/**
+ * ISO maker symlink object.
+ */
+typedef struct RTFSISOMAKERSYMLINK
+{
+ /** The common bit. */
+ RTFSISOMAKEROBJ Core;
+ /** The size of the rock ridge 'SL' records for this link. */
+ uint16_t cbSlRockRidge;
+ /** The symbolic link target length. */
+ uint16_t cchTarget;
+ /** The symbolic link target. */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ char szTarget[RT_FLEXIBLE_ARRAY];
+} RTFSISOMAKERSYMLINK;
+/** Pointer to an ISO maker directory object. */
+typedef RTFSISOMAKERSYMLINK *PRTFSISOMAKERSYMLINK;
+/** Pointer to a const ISO maker directory object. */
+typedef const RTFSISOMAKERSYMLINK *PCRTFSISOMAKERSYMLINK;
+
+
+
+/**
+ * Instance data for a ISO image maker.
+ */
+typedef struct RTFSISOMAKERINT
+{
+ /** Magic value (RTFSISOMAKERINT_MAGIC). */
+ uint32_t uMagic;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+
+ /** Set after we've been fed the first bit of content.
+ * This means that the namespace configuration has been finalized and can no
+ * longer be changed because it's simply too much work to do adjustments
+ * after having started to add files. */
+ bool fSeenContent;
+ /** Set once we've finalized the image structures.
+ * After this no more changes are allowed. */
+ bool fFinalized;
+
+ /** The primary ISO-9660 namespace. */
+ RTFSISOMAKERNAMESPACE PrimaryIso;
+ /** The joliet namespace. */
+ RTFSISOMAKERNAMESPACE Joliet;
+ /** The UDF namespace. */
+ RTFSISOMAKERNAMESPACE Udf;
+ /** The hybrid HFS+ namespace. */
+ RTFSISOMAKERNAMESPACE Hfs;
+
+ /** The list of objects (RTFSISOMAKEROBJ). */
+ RTLISTANCHOR ObjectHead;
+ /** Number of objects in the image (ObjectHead).
+ * This is used to number them, i.e. create RTFSISOMAKEROBJ::idxObj. */
+ uint32_t cObjects;
+
+ /** Amount of file data. */
+ uint64_t cbData;
+ /** Number of volume descriptors. */
+ uint32_t cVolumeDescriptors;
+ /** The image (trail) padding in bytes. */
+ uint32_t cbImagePadding;
+
+ /** The 'now' timestamp we use for the whole image.
+ * This way we'll save lots of RTTimeNow calls and have similar timestamps
+ * over the whole image. */
+ RTTIMESPEC ImageCreationTime;
+ /** Indicates strict or non-strict attribute handling style.
+ * See RTFsIsoMakerSetAttributeStyle() for details. */
+ bool fStrictAttributeStyle;
+ /** The default owner ID. */
+ RTUID uidDefault;
+ /** The default group ID. */
+ RTGID gidDefault;
+ /** The default file mode mask. */
+ RTFMODE fDefaultFileMode;
+ /** The default file mode mask. */
+ RTFMODE fDefaultDirMode;
+
+ /** Forced file mode mask (permissions only). */
+ RTFMODE fForcedFileMode;
+ /** Set if fForcedFileMode is active. */
+ bool fForcedFileModeActive;
+ /** Set if fForcedDirMode is active. */
+ bool fForcedDirModeActive;
+ /** Forced directory mode mask (permissions only). */
+ RTFMODE fForcedDirMode;
+
+ /** Number of common source files. */
+ uint32_t cCommonSources;
+ /** Array of common source file handles. */
+ PRTVFSFILE paCommonSources;
+
+ /** @name Boot related stuff
+ * @{ */
+ /** The boot catalog file. */
+ PRTFSISOMAKERFILE pBootCatFile;
+ /** Per boot catalog entry data needed for updating offsets when finalizing. */
+ struct
+ {
+ /** The type (ISO9660_ELTORITO_HEADER_ID_VALIDATION_ENTRY,
+ * ISO9660_ELTORITO_HEADER_ID_SECTION_HEADER,
+ * ISO9660_ELTORITO_HEADER_ID_FINAL_SECTION_HEADER,
+ * ISO9660_ELTORITO_BOOT_INDICATOR_BOOTABLE or
+ * ISO9660_ELTORITO_BOOT_INDICATOR_NOT_BOOTABLE). */
+ uint8_t bType;
+ /** Number of entries related to this one. This is zero for unused entries,
+ * 2 for the validation entry, 2+ for section headers, and 1 for images. */
+ uint8_t cEntries;
+ /** The boot file. */
+ PRTFSISOMAKERFILE pBootFile;
+ } aBootCatEntries[64];
+ /** @} */
+
+ /** @name Finalized image stuff
+ * @{ */
+ /** The finalized image size. */
+ uint64_t cbFinalizedImage;
+ /** System area content (sectors 0 thur 15). This is NULL if the system area
+ * are all zeros, which is often the case. Hybrid ISOs have an MBR followed by
+ * a GUID partition table here, helping making the image bootable when
+ * transfered to a USB stick. */
+ uint8_t *pbSysArea;
+ /** Number of non-zero system area bytes pointed to by pbSysArea. */
+ size_t cbSysArea;
+
+ /** Pointer to the buffer holding the volume descriptors. */
+ uint8_t *pbVolDescs;
+ /** Pointer to the primary volume descriptor. */
+ PISO9660PRIMARYVOLDESC pPrimaryVolDesc;
+ /** El Torito volume descriptor. */
+ PISO9660BOOTRECORDELTORITO pElToritoDesc;
+ /** Pointer to the primary volume descriptor. */
+ PISO9660SUPVOLDESC pJolietVolDesc;
+ /** Terminating ISO-9660 volume descriptor. */
+ PISO9660VOLDESCHDR pTerminatorVolDesc;
+
+ /** Finalized ISO-9660 directory structures. */
+ struct RTFSISOMAKERFINALIZEDDIRS
+ {
+ /** The image byte offset of the first directory. */
+ uint64_t offDirs;
+ /** The image byte offset of the little endian path table.
+ * This always follows offDirs. */
+ uint64_t offPathTableL;
+ /** The image byte offset of the big endian path table.
+ * This always follows offPathTableL. */
+ uint64_t offPathTableM;
+ /** The size of the path table. */
+ uint32_t cbPathTable;
+ /** List of finalized directories for this namespace.
+ * The list is in path table order so it can be generated on the fly. The
+ * directories will be ordered in the same way. */
+ RTLISTANCHOR FinalizedDirs;
+ /** Rock ridge spill file. */
+ PRTFSISOMAKERFILE pRRSpillFile;
+ }
+ /** The finalized directory data for the primary ISO-9660 namespace. */
+ PrimaryIsoDirs,
+ /** The finalized directory data for the joliet namespace. */
+ JolietDirs;
+
+ /** The image byte offset of the first file. */
+ uint64_t offFirstFile;
+ /** Finalized file head (RTFSISOMAKERFILE).
+ * The list is ordered by disk location. Files are following the
+ * directories and path tables. */
+ RTLISTANCHOR FinalizedFiles;
+ /** @} */
+
+} RTFSISOMAKERINT;
+/** Pointer to an ISO maker instance. */
+typedef RTFSISOMAKERINT *PRTFSISOMAKERINT;
+
+/** Pointer to the data for finalized ISO-9660 (primary / joliet) dirs. */
+typedef struct RTFSISOMAKERINT::RTFSISOMAKERFINALIZEDDIRS *PRTFSISOMAKERFINALIZEDDIRS;
+
+
+/**
+ * Instance data of an ISO maker output file.
+ */
+typedef struct RTFSISOMAKEROUTPUTFILE
+{
+ /** The ISO maker (owns a reference). */
+ PRTFSISOMAKERINT pIsoMaker;
+ /** The current file position. */
+ uint64_t offCurPos;
+ /** Current file hint. */
+ PRTFSISOMAKERFILE pFileHint;
+ /** Source file corresponding to pFileHint.
+ * This is used when dealing with a RTFSISOMAKERSRCTYPE_VFS_FILE or
+ * RTFSISOMAKERSRCTYPE_TRANS_TBL file. */
+ RTVFSFILE hVfsSrcFile;
+ /** Current directory hint for the primary ISO namespace. */
+ PRTFSISOMAKERNAMEDIR pDirHintPrimaryIso;
+ /** Current directory hint for the joliet namespace. */
+ PRTFSISOMAKERNAMEDIR pDirHintJoliet;
+ /** Joliet directory child index hint. */
+ uint32_t iChildPrimaryIso;
+ /** Joliet directory child index hint. */
+ uint32_t iChildJoliet;
+} RTFSISOMAKEROUTPUTFILE;
+/** Pointer to the instance data of an ISO maker output file. */
+typedef RTFSISOMAKEROUTPUTFILE *PRTFSISOMAKEROUTPUTFILE;
+
+
+/**
+ * Directory entry type.
+ */
+typedef enum RTFSISOMAKERDIRTYPE
+{
+ /** Invalid directory entry. */
+ RTFSISOMAKERDIRTYPE_INVALID = 0,
+ /** Entry for the current directory, aka ".". */
+ RTFSISOMAKERDIRTYPE_CURRENT,
+ /** Entry for the parent directory, aka "..". */
+ RTFSISOMAKERDIRTYPE_PARENT,
+ /** Entry for a regular directory entry. */
+ RTFSISOMAKERDIRTYPE_OTHER
+} RTFSISOMAKERDIRTYPE;
+
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Help for iterating over namespaces.
+ */
+static const struct
+{
+ /** The RTFSISOMAKER_NAMESPACE_XXX indicator. */
+ uint32_t fNamespace;
+ /** Offset into RTFSISOMAKERINT of the namespace member. */
+ uintptr_t offNamespace;
+ /** Offset into RTFSISOMAKERNAMESPACE of the name member. */
+ uintptr_t offName;
+ /** Namespace name for debugging purposes. */
+ const char *pszName;
+} g_aRTFsIsoNamespaces[] =
+{
+ { RTFSISOMAKER_NAMESPACE_ISO_9660, RT_UOFFSETOF(RTFSISOMAKERINT, PrimaryIso), RT_UOFFSETOF(RTFSISOMAKEROBJ, pPrimaryName), "iso-9660" },
+ { RTFSISOMAKER_NAMESPACE_JOLIET, RT_UOFFSETOF(RTFSISOMAKERINT, Joliet), RT_UOFFSETOF(RTFSISOMAKEROBJ, pJolietName), "joliet" },
+ { RTFSISOMAKER_NAMESPACE_UDF, RT_UOFFSETOF(RTFSISOMAKERINT, Udf), RT_UOFFSETOF(RTFSISOMAKEROBJ, pUdfName), "udf" },
+ { RTFSISOMAKER_NAMESPACE_HFS, RT_UOFFSETOF(RTFSISOMAKERINT, Hfs), RT_UOFFSETOF(RTFSISOMAKEROBJ, pHfsName), "hfs" },
+};
+
+/**
+ * Translates a single namespace flag (RTFSISOMAKER_NAMESPACE_XXX) to an
+ * index into g_aRTFsIsoNamespaces.
+ */
+static const uint8_t g_aidxRTFsIsoNamespaceFlagToIdx[] =
+{
+ /*[0] = */ RT_ELEMENTS(g_aRTFsIsoNamespaces),
+ /*[RTFSISOMAKER_NAMESPACE_ISO_9660] = */ 0,
+ /*[RTFSISOMAKER_NAMESPACE_JOLIET] = */ 1,
+ /*[3] = */ RT_ELEMENTS(g_aRTFsIsoNamespaces),
+ /*[RTFSISOMAKER_NAMESPACE_UDF] = */ 2,
+ /*[5] = */ RT_ELEMENTS(g_aRTFsIsoNamespaces),
+ /*[6] = */ RT_ELEMENTS(g_aRTFsIsoNamespaces),
+ /*[7] = */ RT_ELEMENTS(g_aRTFsIsoNamespaces),
+ /*[RTFSISOMAKER_NAMESPACE_HFS] = */ 3,
+ /*[9] = */ RT_ELEMENTS(g_aRTFsIsoNamespaces),
+ /*[10] = */ RT_ELEMENTS(g_aRTFsIsoNamespaces),
+ /*[11] = */ RT_ELEMENTS(g_aRTFsIsoNamespaces),
+ /*[12] = */ RT_ELEMENTS(g_aRTFsIsoNamespaces),
+ /*[13] = */ RT_ELEMENTS(g_aRTFsIsoNamespaces),
+ /*[14] = */ RT_ELEMENTS(g_aRTFsIsoNamespaces),
+ /*[15] = */ RT_ELEMENTS(g_aRTFsIsoNamespaces),
+};
+
+/** The default translation table filename. */
+static const char g_szTransTbl[] = "TRANS.TBL";
+/** The default application ID for the primary ISO-9660 volume descriptor. */
+static char g_szAppIdPrimaryIso[64] = "";
+/** The default application ID for the joliet volume descriptor. */
+static char g_szAppIdJoliet[64] = "";
+/** The default system ID the primary ISO-9660 volume descriptor. */
+static char g_szSystemId[64] = "";
+
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int rtFsIsoMakerObjSetName(PRTFSISOMAKERINT pThis, PRTFSISOMAKERNAMESPACE pNamespace, PRTFSISOMAKEROBJ pObj,
+ PRTFSISOMAKERNAME pParent, const char *pchSpec, size_t cchSpec, bool fNoNormalize,
+ PPRTFSISOMAKERNAME ppNewName);
+static int rtFsIsoMakerObjUnsetName(PRTFSISOMAKERINT pThis, PRTFSISOMAKERNAMESPACE pNamespace, PRTFSISOMAKEROBJ pObj);
+static int rtFsIsoMakerAddUnnamedDirWorker(PRTFSISOMAKERINT pThis, PCRTFSOBJINFO pObjInfo, PRTFSISOMAKERDIR *ppDir);
+static int rtFsIsoMakerAddUnnamedFileWorker(PRTFSISOMAKERINT pThis, PCRTFSOBJINFO pObjInfo, size_t cbExtra,
+ PRTFSISOMAKERFILE *ppFile);
+static int rtFsIsoMakerObjRemoveWorker(PRTFSISOMAKERINT pThis, PRTFSISOMAKEROBJ pObj);
+
+static ssize_t rtFsIsoMakerOutFile_RockRidgeGenSL(const char *pszTarget, uint8_t *pbBuf, size_t cbBuf);
+static DECLCALLBACK(int) rtFsIsoMakerOutFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual);
+
+
+
+/**
+ * Creates an ISO maker instance.
+ *
+ * @returns IPRT status code.
+ * @param phIsoMaker Where to return the handle to the new ISO maker.
+ */
+RTDECL(int) RTFsIsoMakerCreate(PRTFSISOMAKER phIsoMaker)
+{
+ /*
+ * Do some integrity checks first.
+ */
+ AssertReturn(g_aRTFsIsoNamespaces[g_aidxRTFsIsoNamespaceFlagToIdx[RTFSISOMAKER_NAMESPACE_ISO_9660]].fNamespace == RTFSISOMAKER_NAMESPACE_ISO_9660,
+ VERR_ISOMK_IPE_TABLE);
+ AssertReturn(g_aRTFsIsoNamespaces[g_aidxRTFsIsoNamespaceFlagToIdx[RTFSISOMAKER_NAMESPACE_JOLIET]].fNamespace == RTFSISOMAKER_NAMESPACE_JOLIET,
+ VERR_ISOMK_IPE_TABLE);
+ AssertReturn(g_aRTFsIsoNamespaces[g_aidxRTFsIsoNamespaceFlagToIdx[RTFSISOMAKER_NAMESPACE_UDF]].fNamespace == RTFSISOMAKER_NAMESPACE_UDF,
+ VERR_ISOMK_IPE_TABLE);
+ AssertReturn(g_aRTFsIsoNamespaces[g_aidxRTFsIsoNamespaceFlagToIdx[RTFSISOMAKER_NAMESPACE_HFS]].fNamespace == RTFSISOMAKER_NAMESPACE_HFS,
+ VERR_ISOMK_IPE_TABLE);
+
+ if (g_szAppIdPrimaryIso[0] == '\0')
+ RTStrPrintf(g_szAppIdPrimaryIso, sizeof(g_szAppIdPrimaryIso), "IPRT ISO MAKER V%u.%u.%u R%s",
+ RTBldCfgVersionMajor(), RTBldCfgVersionMinor(), RTBldCfgVersionBuild(), RTBldCfgRevisionStr());
+ if (g_szAppIdJoliet[0] == '\0')
+ RTStrPrintf(g_szAppIdJoliet, sizeof(g_szAppIdJoliet),
+ "IPRT ISO Maker v%s r%s", RTBldCfgVersion(), RTBldCfgRevisionStr());
+ if (g_szSystemId[0] == '\0')
+ {
+ RTStrCopy(g_szSystemId, sizeof(g_szSystemId), RTBldCfgTargetDotArch());
+ RTStrToUpper(g_szSystemId);
+ }
+
+ /*
+ * Create the instance with defaults.
+ */
+ int rc;
+ PRTFSISOMAKERINT pThis = (PRTFSISOMAKERINT)RTMemAllocZ(sizeof(*pThis));
+ if (pThis)
+ {
+ pThis->uMagic = RTFSISOMAKERINT_MAGIC;
+ pThis->cRefs = 1;
+ //pThis->fSeenContent = false;
+ //pThis->fFinalized = false;
+
+ pThis->PrimaryIso.fNamespace = RTFSISOMAKER_NAMESPACE_ISO_9660;
+ pThis->PrimaryIso.offName = RT_UOFFSETOF(RTFSISOMAKEROBJ, pPrimaryName);
+ pThis->PrimaryIso.uLevel = 3; /* 30 char names, large files */
+ pThis->PrimaryIso.uRockRidgeLevel = 1;
+ pThis->PrimaryIso.pszTransTbl = (char *)g_szTransTbl;
+ pThis->PrimaryIso.pszSystemId = g_szSystemId;
+ //pThis->PrimaryIso.pszVolumeId = NULL;
+ //pThis->PrimaryIso.pszSetVolumeId = NULL;
+ //pThis->PrimaryIso.pszPublisherId = NULL;
+ //pThis->PrimaryIso.pszDataPreparerId = NULL;
+ pThis->PrimaryIso.pszApplicationId = g_szAppIdPrimaryIso;
+ //pThis->PrimaryIso.pszCopyrightFileId = NULL;
+ //pThis->PrimaryIso.pszAbstractFileId = NULL;
+ //pThis->PrimaryIso.pszBibliographicFileId = NULL;
+
+ pThis->Joliet.fNamespace = RTFSISOMAKER_NAMESPACE_JOLIET;
+ pThis->Joliet.offName = RT_UOFFSETOF(RTFSISOMAKEROBJ, pJolietName);
+ pThis->Joliet.uLevel = 3;
+ //pThis->Joliet.uRockRidgeLevel = 0;
+ //pThis->Joliet.pszTransTbl = NULL;
+ //pThis->Joliet.pszSystemId = NULL;
+ //pThis->Joliet.pszVolumeId = NULL;
+ //pThis->Joliet.pszSetVolumeId = NULL;
+ //pThis->Joliet.pszPublisherId = NULL;
+ //pThis->Joliet.pszDataPreparerId = NULL;
+ pThis->Joliet.pszApplicationId = g_szAppIdJoliet;
+ //pThis->Joliet.pszCopyrightFileId = NULL;
+ //pThis->Joliet.pszAbstractFileId = NULL;
+ //pThis->Joliet.pszBibliographicFileId = NULL;
+
+ pThis->Udf.fNamespace = RTFSISOMAKER_NAMESPACE_UDF;
+ pThis->Udf.offName = RT_UOFFSETOF(RTFSISOMAKEROBJ, pUdfName);
+ //pThis->Udf.uLevel = 0;
+ //pThis->Udf.uRockRidgeLevel = 0;
+ //pThis->Udf.pszTransTbl = NULL;
+ //pThis->Udf.uRockRidgeLevel = 0;
+ //pThis->Udf.pszTransTbl = NULL;
+ //pThis->Udf.pszSystemId = NULL;
+ //pThis->Udf.pszVolumeId = NULL;
+ //pThis->Udf.pszSetVolumeId = NULL;
+ //pThis->Udf.pszPublisherId = NULL;
+ //pThis->Udf.pszDataPreparerId = NULL;
+ //pThis->Udf.pszApplicationId = NULL;
+ //pThis->Udf.pszCopyrightFileId = NULL;
+ //pThis->Udf.pszAbstractFileId = NULL;
+ //pThis->Udf.pszBibliographicFileId = NULL;
+
+ pThis->Hfs.fNamespace = RTFSISOMAKER_NAMESPACE_HFS;
+ pThis->Hfs.offName = RT_UOFFSETOF(RTFSISOMAKEROBJ, pHfsName);
+ //pThis->Hfs.uLevel = 0;
+ //pThis->Hfs.uRockRidgeLevel = 0;
+ //pThis->Hfs.pszTransTbl = NULL;
+ //pThis->Hfs.pszSystemId = NULL;
+ //pThis->Hfs.pszVolumeId = NULL;
+ //pThis->Hfs.pszSetVolumeId = NULL;
+ //pThis->Hfs.pszPublisherId = NULL;
+ //pThis->Hfs.pszDataPreparerId = NULL;
+ //pThis->Hfs.pszApplicationId = NULL;
+ //pThis->Hfs.pszCopyrightFileId = NULL;
+ //pThis->Hfs.pszAbstractFileId = NULL;
+ //pThis->Hfs.pszBibliographicFileId = NULL;
+
+ RTListInit(&pThis->ObjectHead);
+ //pThis->cObjects = 0;
+ //pThis->cbData = 0;
+
+ pThis->cVolumeDescriptors = 3; /* primary, secondary joliet, terminator. */
+ pThis->cbImagePadding = 150 * RTFSISOMAKER_SECTOR_SIZE;
+
+ //pThis->fStrictAttributeStyle = false;
+ //pThis->uidDefault = 0;
+ //pThis->gidDefault = 0;
+ pThis->fDefaultFileMode = 0444 | RTFS_TYPE_FILE | RTFS_DOS_ARCHIVED | RTFS_DOS_READONLY;
+ pThis->fDefaultDirMode = 0555 | RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY | RTFS_DOS_READONLY;
+
+ //pThis->fForcedFileMode = 0;
+ //pThis->fForcedFileModeActive = false;
+ //pThis->fForcedDirModeActive = false;
+ //pThis->fForcedDirMode = 0;
+
+ //pThis->cCommonSources = 0;
+ //pThis->paCommonSources = NULL;
+
+ //pThis->pBootCatFile = NULL;
+
+ pThis->cbFinalizedImage = UINT64_MAX;
+ //pThis->pbSysArea = NULL;
+ //pThis->cbSysArea = 0;
+ //pThis->pbVolDescs = NULL;
+ //pThis->pPrimaryVolDesc = NULL;
+ //pThis->pElToritoDesc = NULL;
+ //pThis->pJolietVolDesc = NULL;
+
+ pThis->PrimaryIsoDirs.offDirs = UINT64_MAX;
+ pThis->PrimaryIsoDirs.offPathTableL = UINT64_MAX;
+ pThis->PrimaryIsoDirs.offPathTableM = UINT64_MAX;
+ pThis->PrimaryIsoDirs.cbPathTable = 0;
+ RTListInit(&pThis->PrimaryIsoDirs.FinalizedDirs);
+ //pThis->PrimaryIsoDirs.pRRSpillFile = NULL;
+
+ pThis->JolietDirs.offDirs = UINT64_MAX;
+ pThis->JolietDirs.offPathTableL = UINT64_MAX;
+ pThis->JolietDirs.offPathTableM = UINT64_MAX;
+ pThis->JolietDirs.cbPathTable = 0;
+ RTListInit(&pThis->JolietDirs.FinalizedDirs);
+ //pThis->JolietDirs.pRRSpillFile = NULL;
+
+ pThis->offFirstFile = UINT64_MAX;
+ RTListInit(&pThis->FinalizedFiles);
+
+ RTTimeNow(&pThis->ImageCreationTime);
+
+ /*
+ * Add the root directory node with idObj == 0.
+ */
+ PRTFSISOMAKERDIR pDirRoot;
+ rc = rtFsIsoMakerAddUnnamedDirWorker(pThis, NULL /*pObjInfo*/, &pDirRoot);
+ if (RT_SUCCESS(rc))
+ {
+ *phIsoMaker = pThis;
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+
+/**
+ * Frees an object.
+ *
+ * This is a worker for rtFsIsoMakerDestroy and RTFsIsoMakerObjRemove.
+ *
+ * @param pObj The object to free.
+ */
+DECLINLINE(void) rtFsIsoMakerObjDestroy(PRTFSISOMAKEROBJ pObj)
+{
+ if (pObj->enmType == RTFSISOMAKEROBJTYPE_FILE)
+ {
+ PRTFSISOMAKERFILE pFile = (PRTFSISOMAKERFILE)pObj;
+ switch (pFile->enmSrcType)
+ {
+ case RTFSISOMAKERSRCTYPE_PATH:
+ pFile->u.pszSrcPath = NULL;
+ break;
+
+ case RTFSISOMAKERSRCTYPE_TRANS_TBL:
+ pFile->u.pTransTblDir = NULL;
+ break;
+
+ case RTFSISOMAKERSRCTYPE_VFS_FILE:
+ RTVfsFileRelease(pFile->u.hVfsFile);
+ pFile->u.hVfsFile = NIL_RTVFSFILE;
+ break;
+
+ case RTFSISOMAKERSRCTYPE_COMMON:
+ case RTFSISOMAKERSRCTYPE_RR_SPILL:
+ break;
+
+ case RTFSISOMAKERSRCTYPE_INVALID:
+ case RTFSISOMAKERSRCTYPE_END:
+ AssertFailed();
+ break;
+
+ /* no default, want warnings */
+ }
+ if (pFile->pBootInfoTable)
+ {
+ RTMemFree(pFile->pBootInfoTable);
+ pFile->pBootInfoTable = NULL;
+ }
+ }
+
+ RTMemFree(pObj);
+}
+
+
+/**
+ * Frees a namespace node.
+ *
+ * This is a worker for rtFsIsoMakerDestroyTree and rtFsIsoMakerObjUnsetName.
+ *
+ * @param pName The node to free.
+ */
+DECLINLINE(void) rtFsIsoMakerDestroyName(PRTFSISOMAKERNAME pName)
+{
+ if (pName->fRockRidgeNmAlloced)
+ {
+ RTMemFree(pName->pszRockRidgeNm);
+ pName->pszRockRidgeNm = NULL;
+ }
+ if (pName->fTransNmAlloced)
+ {
+ RTMemFree(pName->pszTransNm);
+ pName->pszTransNm = NULL;
+ }
+ PRTFSISOMAKERNAMEDIR pDir = pName->pDir;
+ if (pDir != NULL)
+ {
+ Assert(pDir->cChildren == 0);
+ RTMemFree(pDir->papChildren);
+ pDir->papChildren = NULL;
+ }
+ RTMemFree(pName);
+}
+
+
+/**
+ * Destroys a namespace.
+ *
+ * @param pNamespace The namespace to destroy.
+ */
+static void rtFsIsoMakerDestroyTree(PRTFSISOMAKERNAMESPACE pNamespace)
+{
+ /*
+ * Recursively destroy the tree first.
+ */
+ PRTFSISOMAKERNAME pCur = pNamespace->pRoot;
+ if (pCur)
+ {
+ Assert(!pCur->pParent);
+ for (;;)
+ {
+ if ( pCur->pDir
+ && pCur->pDir->cChildren)
+ pCur = pCur->pDir->papChildren[pCur->pDir->cChildren - 1];
+ else
+ {
+ PRTFSISOMAKERNAME pNext = pCur->pParent;
+ rtFsIsoMakerDestroyName(pCur);
+
+ /* Unlink from parent, we're the last entry. */
+ if (pNext)
+ {
+ Assert(pNext->pDir->cChildren > 0);
+ pNext->pDir->cChildren--;
+ Assert(pNext->pDir->papChildren[pNext->pDir->cChildren] == pCur);
+ pNext->pDir->papChildren[pNext->pDir->cChildren] = NULL;
+ pCur = pNext;
+ }
+ else
+ {
+ Assert(pNamespace->pRoot == pCur);
+ break;
+ }
+ }
+ }
+ pNamespace->pRoot = NULL;
+ }
+
+ /*
+ * Free the translation table filename if allocated.
+ */
+ if (pNamespace->pszTransTbl)
+ {
+ if (pNamespace->pszTransTbl != g_szTransTbl)
+ RTStrFree(pNamespace->pszTransTbl);
+ pNamespace->pszTransTbl = NULL;
+ }
+
+ /*
+ * Free string IDs.
+ */
+ if (pNamespace->pszSystemId)
+ {
+ if (pNamespace->pszSystemId != g_szSystemId)
+ RTStrFree(pNamespace->pszSystemId);
+ pNamespace->pszSystemId = NULL;
+ }
+
+ if (pNamespace->pszVolumeId)
+ {
+ RTStrFree(pNamespace->pszVolumeId);
+ pNamespace->pszVolumeId = NULL;
+ }
+
+ if (pNamespace->pszVolumeSetId)
+ {
+ RTStrFree(pNamespace->pszVolumeSetId);
+ pNamespace->pszVolumeSetId = NULL;
+ }
+
+ if (pNamespace->pszPublisherId)
+ {
+ RTStrFree(pNamespace->pszPublisherId);
+ pNamespace->pszPublisherId = NULL;
+ }
+
+ if (pNamespace->pszDataPreparerId)
+ {
+ RTStrFree(pNamespace->pszDataPreparerId);
+ pNamespace->pszDataPreparerId = NULL;
+ }
+
+ if (pNamespace->pszApplicationId)
+ {
+ if ( pNamespace->pszApplicationId != g_szAppIdPrimaryIso
+ && pNamespace->pszApplicationId != g_szAppIdJoliet)
+ RTStrFree(pNamespace->pszApplicationId);
+ pNamespace->pszApplicationId = NULL;
+ }
+
+ if (pNamespace->pszCopyrightFileId)
+ {
+ RTStrFree(pNamespace->pszCopyrightFileId);
+ pNamespace->pszCopyrightFileId = NULL;
+ }
+
+ if (pNamespace->pszAbstractFileId)
+ {
+ RTStrFree(pNamespace->pszAbstractFileId);
+ pNamespace->pszAbstractFileId = NULL;
+ }
+
+ if (pNamespace->pszBibliographicFileId)
+ {
+ RTStrFree(pNamespace->pszBibliographicFileId);
+ pNamespace->pszBibliographicFileId = NULL;
+ }
+}
+
+
+/**
+ * Destroys an ISO maker instance.
+ *
+ * @param pThis The ISO maker instance to destroy.
+ */
+static void rtFsIsoMakerDestroy(PRTFSISOMAKERINT pThis)
+{
+ rtFsIsoMakerDestroyTree(&pThis->PrimaryIso);
+ rtFsIsoMakerDestroyTree(&pThis->Joliet);
+ rtFsIsoMakerDestroyTree(&pThis->Udf);
+ rtFsIsoMakerDestroyTree(&pThis->Hfs);
+
+ PRTFSISOMAKEROBJ pCur;
+ PRTFSISOMAKEROBJ pNext;
+ RTListForEachSafe(&pThis->ObjectHead, pCur, pNext, RTFSISOMAKEROBJ, Entry)
+ {
+ RTListNodeRemove(&pCur->Entry);
+ rtFsIsoMakerObjDestroy(pCur);
+ }
+
+ if (pThis->paCommonSources)
+ {
+ RTMemFree(pThis->paCommonSources);
+ pThis->paCommonSources = NULL;
+ }
+
+ if (pThis->pbVolDescs)
+ {
+ RTMemFree(pThis->pbVolDescs);
+ pThis->pbVolDescs = NULL;
+ }
+
+ if (pThis->pbSysArea)
+ {
+ RTMemFree(pThis->pbSysArea);
+ pThis->pbSysArea = NULL;
+ }
+
+ pThis->uMagic = ~RTFSISOMAKERINT_MAGIC;
+ RTMemFree(pThis);
+}
+
+
+/**
+ * Retains a references to an ISO maker instance.
+ *
+ * @returns New reference count on success, UINT32_MAX if invalid handle.
+ * @param hIsoMaker The ISO maker handle.
+ */
+RTDECL(uint32_t) RTFsIsoMakerRetain(RTFSISOMAKER hIsoMaker)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->uMagic == RTFSISOMAKERINT_MAGIC, UINT32_MAX);
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ Assert(cRefs > 1);
+ Assert(cRefs < _64K);
+ return cRefs;
+}
+
+
+/**
+ * Releases a references to an ISO maker instance.
+ *
+ * @returns New reference count on success, UINT32_MAX if invalid handle.
+ * @param hIsoMaker The ISO maker handle. NIL is ignored.
+ */
+RTDECL(uint32_t) RTFsIsoMakerRelease(RTFSISOMAKER hIsoMaker)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ uint32_t cRefs;
+ if (pThis == NIL_RTFSISOMAKER)
+ cRefs = 0;
+ else
+ {
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->uMagic == RTFSISOMAKERINT_MAGIC, UINT32_MAX);
+ cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ Assert(cRefs < _64K);
+ if (!cRefs)
+ rtFsIsoMakerDestroy(pThis);
+ }
+ return cRefs;
+}
+
+
+/**
+ * Sets the ISO-9660 level.
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param uIsoLevel The level, 1-3.
+ */
+RTDECL(int) RTFsIsoMakerSetIso9660Level(RTFSISOMAKER hIsoMaker, uint8_t uIsoLevel)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn(uIsoLevel <= 3, VERR_INVALID_PARAMETER);
+ AssertReturn(uIsoLevel > 0, VERR_INVALID_PARAMETER); /* currently not possible to disable this */
+ AssertReturn(!pThis->fSeenContent, VERR_WRONG_ORDER);
+
+ pThis->PrimaryIso.uLevel = uIsoLevel;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Gets the ISO-9660 level.
+ *
+ * @returns The level, UINT8_MAX if invalid handle.
+ * @param hIsoMaker The ISO maker handle.
+ */
+RTDECL(uint8_t) RTFsIsoMakerGetIso9660Level(RTFSISOMAKER hIsoMaker)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET_EX(pThis, UINT8_MAX);
+ return pThis->PrimaryIso.uLevel;
+}
+
+
+/**
+ * Sets the joliet level.
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param uJolietLevel The joliet UCS-2 level 1-3, or 0 to disable
+ * joliet.
+ */
+RTDECL(int) RTFsIsoMakerSetJolietUcs2Level(RTFSISOMAKER hIsoMaker, uint8_t uJolietLevel)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn(uJolietLevel <= 3, VERR_INVALID_PARAMETER);
+ AssertReturn(!pThis->fSeenContent, VERR_WRONG_ORDER);
+
+ if (pThis->Joliet.uLevel != uJolietLevel)
+ {
+ if (uJolietLevel == 0)
+ pThis->cVolumeDescriptors--;
+ else if (pThis->Joliet.uLevel == 0)
+ pThis->cVolumeDescriptors++;
+ pThis->Joliet.uLevel = uJolietLevel;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Sets the rock ridge support level (on the primary ISO-9660 namespace).
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param uLevel 0 if disabled, 1 to just enable, 2 to enable and
+ * write the ER tag.
+ */
+RTDECL(int) RTFsIsoMakerSetRockRidgeLevel(RTFSISOMAKER hIsoMaker, uint8_t uLevel)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn(uLevel <= 2, VERR_INVALID_PARAMETER);
+ AssertReturn( !pThis->fSeenContent
+ || (uLevel >= pThis->PrimaryIso.uRockRidgeLevel && pThis->PrimaryIso.uRockRidgeLevel > 0), VERR_WRONG_ORDER);
+ AssertReturn(!pThis->fSeenContent, VERR_WRONG_ORDER);
+
+ pThis->PrimaryIso.uRockRidgeLevel = uLevel;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Sets the rock ridge support level on the joliet namespace (experimental).
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param uLevel 0 if disabled, 1 to just enable, 2 to enable and
+ * write the ER tag.
+ */
+RTDECL(int) RTFsIsoMakerSetJolietRockRidgeLevel(RTFSISOMAKER hIsoMaker, uint8_t uLevel)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn(uLevel <= 2, VERR_INVALID_PARAMETER);
+ AssertReturn( !pThis->fSeenContent
+ || (uLevel >= pThis->Joliet.uRockRidgeLevel && pThis->Joliet.uRockRidgeLevel > 0), VERR_WRONG_ORDER);
+
+ pThis->Joliet.uRockRidgeLevel = uLevel;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Gets the rock ridge support level (on the primary ISO-9660 namespace).
+ *
+ * @returns 0 if disabled, 1 just enabled, 2 if enabled with ER tag, and
+ * UINT8_MAX if the handle is invalid.
+ * @param hIsoMaker The ISO maker handle.
+ */
+RTDECL(uint8_t) RTFsIsoMakerGetRockRidgeLevel(RTFSISOMAKER hIsoMaker)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET_EX(pThis, UINT8_MAX);
+ return pThis->PrimaryIso.uRockRidgeLevel;
+}
+
+
+/**
+ * Gets the rock ridge support level on the joliet namespace (experimental).
+ *
+ * @returns 0 if disabled, 1 just enabled, 2 if enabled with ER tag, and
+ * UINT8_MAX if the handle is invalid.
+ * @param hIsoMaker The ISO maker handle.
+ */
+RTDECL(uint8_t) RTFsIsoMakerGetJolietRockRidgeLevel(RTFSISOMAKER hIsoMaker)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET_EX(pThis, UINT8_MAX);
+ return pThis->Joliet.uRockRidgeLevel;
+}
+
+
+/**
+ * Changes the file attribute (mode, owner, group) inherit style (from source).
+ *
+ * The strict style will use the exact attributes from the source, where as the
+ * non-strict (aka rational and default) style will use 0 for the owner and
+ * group IDs and normalize the mode bits along the lines of 'chmod a=rX',
+ * stripping set-uid/gid bitson files but preserving sticky ones on directories.
+ *
+ * When disabling strict style, the default dir and file modes will be restored
+ * to default values.
+ *
+ * @returns IRPT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param fStrict Indicates strict (true) or non-strict (false)
+ * style.
+ */
+RTDECL(int) RTFsIsoMakerSetAttribInheritStyle(RTFSISOMAKER hIsoMaker, bool fStrict)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+
+ pThis->fStrictAttributeStyle = fStrict;
+ if (!fStrict)
+ {
+ pThis->fDefaultFileMode = 0444 | RTFS_TYPE_FILE | RTFS_DOS_ARCHIVED | RTFS_DOS_READONLY;
+ pThis->fDefaultDirMode = 0555 | RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY | RTFS_DOS_READONLY;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Sets the default file mode settings.
+ *
+ * @returns IRPT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param fMode The default file mode.
+ */
+RTDECL(int) RTFsIsoMakerSetDefaultFileMode(RTFSISOMAKER hIsoMaker, RTFMODE fMode)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ Assert(!(fMode & ~RTFS_UNIX_ALL_PERMS));
+
+ pThis->fDefaultFileMode &= ~RTFS_UNIX_ALL_PERMS;
+ pThis->fDefaultFileMode |= fMode & RTFS_UNIX_ALL_PERMS;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Sets the default dir mode settings.
+ *
+ * @returns IRPT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param fMode The default dir mode.
+ */
+RTDECL(int) RTFsIsoMakerSetDefaultDirMode(RTFSISOMAKER hIsoMaker, RTFMODE fMode)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ Assert(!(fMode & ~RTFS_UNIX_ALL_PERMS));
+
+ pThis->fDefaultDirMode &= ~RTFS_UNIX_ALL_PERMS;
+ pThis->fDefaultDirMode |= fMode & RTFS_UNIX_ALL_PERMS;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Sets the forced file mode, if @a fForce is true also the default mode is set.
+ *
+ * @returns IRPT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param fMode The file mode.
+ * @param fForce Indicate whether forced mode is active or not.
+ */
+RTDECL(int) RTFsIsoMakerSetForcedFileMode(RTFSISOMAKER hIsoMaker, RTFMODE fMode, bool fForce)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ Assert(!(fMode & ~RTFS_UNIX_ALL_PERMS));
+
+ pThis->fForcedFileMode = fMode & RTFS_UNIX_ALL_PERMS;
+ pThis->fForcedFileModeActive = fForce;
+ if (fForce)
+ {
+ pThis->fDefaultFileMode &= ~RTFS_UNIX_ALL_PERMS;
+ pThis->fDefaultFileMode |= fMode & RTFS_UNIX_ALL_PERMS;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Sets the forced dir mode, if @a fForce is true also the default mode is set.
+ *
+ * @returns IRPT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param fMode The dir mode.
+ * @param fForce Indicate whether forced mode is active or not.
+ */
+RTDECL(int) RTFsIsoMakerSetForcedDirMode(RTFSISOMAKER hIsoMaker, RTFMODE fMode, bool fForce)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ Assert(!(fMode & ~RTFS_UNIX_ALL_PERMS));
+
+ pThis->fForcedDirModeActive = fForce;
+ pThis->fForcedDirMode = fMode & RTFS_UNIX_ALL_PERMS;
+ if (fForce)
+ {
+ pThis->fDefaultDirMode &= ~RTFS_UNIX_ALL_PERMS;
+ pThis->fDefaultDirMode |= fMode & RTFS_UNIX_ALL_PERMS;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Sets the content of the system area, i.e. the first 32KB of the image.
+ *
+ * This can be used to put generic boot related stuff.
+ *
+ * @note Other settings may overwrite parts of the content (yet to be
+ * determined which).
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param pvContent The content to put in the system area.
+ * @param cbContent The size of the content.
+ * @param off The offset into the system area.
+ */
+RTDECL(int) RTFsIsoMakerSetSysAreaContent(RTFSISOMAKER hIsoMaker, void const *pvContent, size_t cbContent, uint32_t off)
+{
+ /*
+ * Validate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+ AssertReturn(cbContent > 0, VERR_OUT_OF_RANGE);
+ AssertReturn(cbContent <= _32K, VERR_OUT_OF_RANGE);
+ AssertReturn(off < _32K, VERR_OUT_OF_RANGE);
+ size_t cbSysArea = off + cbContent;
+ AssertReturn(cbSysArea <= _32K, VERR_OUT_OF_RANGE);
+
+ /*
+ * Adjust the allocation and copy over the new/additional content.
+ */
+ if (pThis->cbSysArea < cbSysArea)
+ {
+ void *pvNew = RTMemRealloc(pThis->pbSysArea, cbSysArea);
+ AssertReturn(pvNew, VERR_NO_MEMORY);
+ pThis->pbSysArea = (uint8_t *)pvNew;
+ memset(&pThis->pbSysArea[pThis->cbSysArea], 0, cbSysArea - pThis->cbSysArea);
+ }
+
+ memcpy(&pThis->pbSysArea[off], pvContent, cbContent);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Sets a string property in one or more namespaces.
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param enmStringProp The string property to set.
+ * @param fNamespaces The namespaces to set it in.
+ * @param pszValue The value to set it to. NULL is treated like an
+ * empty string. The value will be silently truncated
+ * to fit the available space.
+ */
+RTDECL(int) RTFsIsoMakerSetStringProp(RTFSISOMAKER hIsoMaker, RTFSISOMAKERSTRINGPROP enmStringProp,
+ uint32_t fNamespaces, const char *pszValue)
+{
+ /*
+ * Validate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn( enmStringProp > RTFSISOMAKERSTRINGPROP_INVALID
+ && enmStringProp < RTFSISOMAKERSTRINGPROP_END, VERR_INVALID_PARAMETER);
+ AssertReturn(!(fNamespaces & ~RTFSISOMAKER_NAMESPACE_VALID_MASK), VERR_INVALID_FLAGS);
+ if (pszValue)
+ {
+ AssertPtrReturn(pszValue, VERR_INVALID_POINTER);
+ if (*pszValue == '\0')
+ pszValue = NULL;
+ }
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ /*
+ * Work the namespaces.
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aRTFsIsoNamespaces); i++)
+ if (fNamespaces & g_aRTFsIsoNamespaces[i].fNamespace)
+ {
+ PRTFSISOMAKERNAMESPACE pNamespace = (PRTFSISOMAKERNAMESPACE)((uintptr_t)pThis + g_aRTFsIsoNamespaces[i].offNamespace);
+ if (pNamespace->uLevel > 0)
+ {
+ /* Get a pointer to the field. */
+ char **ppszValue;
+ switch (enmStringProp)
+ {
+ case RTFSISOMAKERSTRINGPROP_SYSTEM_ID: ppszValue = &pNamespace->pszSystemId; break;
+ case RTFSISOMAKERSTRINGPROP_VOLUME_ID: ppszValue = &pNamespace->pszVolumeId; break;
+ case RTFSISOMAKERSTRINGPROP_VOLUME_SET_ID: ppszValue = &pNamespace->pszVolumeSetId; break;
+ case RTFSISOMAKERSTRINGPROP_PUBLISHER_ID: ppszValue = &pNamespace->pszPublisherId; break;
+ case RTFSISOMAKERSTRINGPROP_DATA_PREPARER_ID: ppszValue = &pNamespace->pszDataPreparerId; break;
+ case RTFSISOMAKERSTRINGPROP_APPLICATION_ID: ppszValue = &pNamespace->pszApplicationId; break;
+ case RTFSISOMAKERSTRINGPROP_COPYRIGHT_FILE_ID: ppszValue = &pNamespace->pszCopyrightFileId; break;
+ case RTFSISOMAKERSTRINGPROP_ABSTRACT_FILE_ID: ppszValue = &pNamespace->pszAbstractFileId; break;
+ case RTFSISOMAKERSTRINGPROP_BIBLIOGRAPHIC_FILE_ID: ppszValue = &pNamespace->pszBibliographicFileId; break;
+ default: AssertFailedReturn(VERR_IPE_NOT_REACHED_DEFAULT_CASE);
+ }
+
+ /* Free the old value. */
+ char *pszOld = *ppszValue;
+ if ( pszOld
+ && pszOld != g_szAppIdPrimaryIso
+ && pszOld != g_szAppIdJoliet
+ && pszOld != g_szSystemId)
+ RTStrFree(pszOld);
+
+ /* Set the new value. */
+ if (!pszValue)
+ *ppszValue = NULL;
+ else
+ {
+ *ppszValue = RTStrDup(pszValue);
+ AssertReturn(*ppszValue, VERR_NO_STR_MEMORY);
+ }
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Specifies image padding.
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param cSectors Number of sectors to pad the image with.
+ */
+RTDECL(int) RTFsIsoMakerSetImagePadding(RTFSISOMAKER hIsoMaker, uint32_t cSectors)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn(cSectors <= _64K, VERR_OUT_OF_RANGE);
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ pThis->cbImagePadding = cSectors * RTFSISOMAKER_SECTOR_SIZE;
+ return VINF_SUCCESS;
+}
+
+
+
+
+
+/*
+ *
+ * Name space related internals.
+ * Name space related internals.
+ * Name space related internals.
+ *
+ */
+
+
+/**
+ * Gets the pointer to the name member for the given namespace.
+ *
+ * @returns Pointer to name member.
+ * @param pObj The object to find a name member in.
+ * @param pNamespace The namespace which name to calculate.
+ */
+DECLINLINE(PPRTFSISOMAKERNAME) rtFsIsoMakerObjGetNameForNamespace(PRTFSISOMAKEROBJ pObj, PCRTFSISOMAKERNAMESPACE pNamespace)
+{
+ return (PPRTFSISOMAKERNAME)((uintptr_t)pObj + pNamespace->offName);
+}
+
+
+/**
+ * Locates a child object by its namespace name.
+ *
+ * @returns Pointer to the child if found, NULL if not.
+ * @param pDirObj The directory object to search.
+ * @param pszEntry The (namespace) entry name.
+ * @param cchEntry The length of the name.
+ */
+static PRTFSISOMAKERNAME rtFsIsoMakerFindObjInDir(PRTFSISOMAKERNAME pDirObj, const char *pszEntry, size_t cchEntry)
+{
+ if (pDirObj)
+ {
+ PRTFSISOMAKERNAMEDIR pDir = pDirObj->pDir;
+ AssertReturn(pDir, NULL);
+
+ uint32_t i = pDir->cChildren;
+ while (i-- > 0)
+ {
+ PRTFSISOMAKERNAME pChild = pDir->papChildren[i];
+ if ( pChild->cchName == cchEntry
+ && RTStrNICmp(pChild->szName, pszEntry, cchEntry) == 0)
+ return pChild;
+ }
+ }
+ return NULL;
+}
+
+
+/**
+ * Compares the two names according to ISO-9660 directory sorting rules.
+ *
+ * As long as we don't want to do case insensitive joliet sorting, this works
+ * for joliet names to, I think.
+ *
+ * @returns 0 if equal, -1 if pszName1 comes first, 1 if pszName2 comes first.
+ * @param pszName1 The first name.
+ * @param pszName2 The second name.
+ */
+DECLINLINE(int) rtFsIsoMakerCompareIso9660Names(const char *pszName1, const char *pszName2)
+{
+ for (;;)
+ {
+ char const ch1 = *pszName1++;
+ char const ch2 = *pszName2++;
+ if (ch1 == ch2)
+ {
+ if (ch1)
+ { /* likely */ }
+ else
+ return 0;
+ }
+ else if (ch1 == ';' || ch2 == ';')
+ return ch1 == ';' ? -1 : 1;
+ else if (ch1 == '.' || ch2 == '.')
+ return ch1 == '.' ? -1 : 1;
+ else
+ return (unsigned char)ch1 < (unsigned char)ch2 ? -1 : 1;
+ }
+}
+
+
+/**
+ * Finds the index into papChildren where the given name should be inserted.
+ *
+ * @returns Index of the given name.
+ * @param pNamespace The namspace.
+ * @param pParent The parent namespace node.
+ * @param pszName The name.
+ */
+static uint32_t rtFsIsoMakerFindInsertIndex(PRTFSISOMAKERNAMESPACE pNamespace, PRTFSISOMAKERNAME pParent, const char *pszName)
+{
+ uint32_t idxRet = pParent->pDir->cChildren;
+ if (idxRet > 0)
+ {
+ /*
+ * The idea is to do binary search using a namespace specific compare
+ * function. However, it looks like we can get away with using the
+ * same compare function for all namespaces.
+ */
+ uint32_t idxStart = 0;
+ uint32_t idxEnd = idxRet;
+ PPRTFSISOMAKERNAME papChildren = pParent->pDir->papChildren;
+ switch (pNamespace->fNamespace)
+ {
+ case RTFSISOMAKER_NAMESPACE_ISO_9660:
+ case RTFSISOMAKER_NAMESPACE_JOLIET:
+ case RTFSISOMAKER_NAMESPACE_UDF:
+ case RTFSISOMAKER_NAMESPACE_HFS:
+ for (;;)
+ {
+ idxRet = idxStart + (idxEnd - idxStart) / 2;
+ PRTFSISOMAKERNAME pCur = papChildren[idxRet];
+ int iDiff = rtFsIsoMakerCompareIso9660Names(pszName, pCur->szName);
+ if (iDiff < 0)
+ {
+ if (idxRet > idxStart)
+ idxEnd = idxRet;
+ else
+ break;
+ }
+ else
+ {
+ idxRet++;
+ if ( iDiff != 0
+ && idxRet < idxEnd)
+ idxStart = idxRet;
+ else
+ break;
+ }
+ }
+ break;
+
+ default:
+ AssertFailed();
+ break;
+ }
+ }
+ return idxRet;
+}
+
+
+
+/**
+ * Locates a child entry by its specified name.
+ *
+ * @returns Pointer to the child if found, NULL if not.
+ * @param pDirName The directory name to search.
+ * @param pszEntry The (specified) entry name.
+ * @param cchEntry The length of the name.
+ */
+static PRTFSISOMAKERNAME rtFsIsoMakerFindEntryInDirBySpec(PRTFSISOMAKERNAME pDirName, const char *pszEntry, size_t cchEntry)
+{
+ if (pDirName)
+ {
+ PRTFSISOMAKERNAMEDIR pDir = pDirName->pDir;
+ AssertReturn(pDir, NULL);
+
+ uint32_t i = pDir->cChildren;
+ while (i-- > 0)
+ {
+ PRTFSISOMAKERNAME pChild = pDir->papChildren[i];
+ if ( pChild->cchSpecNm == cchEntry
+ && RTStrNICmp(pChild->pszSpecNm, pszEntry, cchEntry) == 0)
+ return pChild;
+ }
+ }
+ return NULL;
+}
+
+
+/**
+ * Locates a subdir object in any namespace by its specified name.
+ *
+ * This is used to avoid having one instance of RTFSISOMAKERDIR in each
+ * namespace for the same directory.
+ *
+ * @returns Pointer to the subdir object if found, NULL if not.
+ * @param pDirObj The directory object to search.
+ * @param pszEntry The (specified) entry name.
+ * @param cchEntry The length of the name.
+ * @param fSkipNamespaces Namespaces to skip.
+ * @sa rtFsIsoMakerFindEntryInDirBySpec
+ */
+static PRTFSISOMAKERDIR rtFsIsoMakerFindSubdirBySpec(PRTFSISOMAKERDIR pDirObj, const char *pszEntry, size_t cchEntry,
+ uint32_t fSkipNamespaces)
+{
+ AssertReturn(pDirObj, NULL);
+ AssertReturn(pDirObj->Core.enmType == RTFSISOMAKEROBJTYPE_DIR, NULL);
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aRTFsIsoNamespaces); i++)
+ if (!(fSkipNamespaces & g_aRTFsIsoNamespaces[i].fNamespace))
+ {
+ PRTFSISOMAKERNAME pDirName = *(PPRTFSISOMAKERNAME)((uintptr_t)pDirObj + g_aRTFsIsoNamespaces[i].offName);
+ if (pDirName)
+ {
+ PRTFSISOMAKERNAMEDIR pDir = pDirName->pDir;
+ AssertStmt(pDir, continue);
+
+ uint32_t iChild = pDir->cChildren;
+ while (iChild-- > 0)
+ {
+ PRTFSISOMAKERNAME pChild = pDir->papChildren[iChild];
+ if ( pChild->cchSpecNm == cchEntry
+ && pChild->pDir != NULL
+ && RTStrNICmp(pChild->pszSpecNm, pszEntry, cchEntry) == 0)
+ return (PRTFSISOMAKERDIR)pChild->pObj;
+ }
+ }
+ }
+ return NULL;
+}
+
+
+/**
+ * Walks the given path by specified object names in a namespace.
+ *
+ * @returns IPRT status code.
+ * @param pNamespace The namespace to walk the path in.
+ * @param pszPath The path to walk.
+ * @param ppName Where to return the name node that the path ends with.
+ */
+static int rtFsIsoMakerWalkPathBySpec(PRTFSISOMAKERNAMESPACE pNamespace, const char *pszPath, PPRTFSISOMAKERNAME ppName)
+{
+ *ppName = NULL;
+ AssertReturn(RTPATH_IS_SLASH(*pszPath), VERR_INVALID_NAME);
+
+ /*
+ * Deal with the special case of the root.
+ */
+ while (RTPATH_IS_SLASH(*pszPath))
+ pszPath++;
+
+ PRTFSISOMAKERNAME pCur = pNamespace->pRoot;
+ if (!pCur)
+ return *pszPath ? VERR_PATH_NOT_FOUND : VERR_FILE_NOT_FOUND;
+ if (!*pszPath)
+ {
+ *ppName = pCur;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Now, do the rest of the path.
+ */
+ for (;;)
+ {
+ /*
+ * Find the end of the component.
+ */
+ char ch;
+ size_t cchComponent = 0;
+ while ((ch = pszPath[cchComponent]) != '\0' && !RTPATH_IS_SLASH(ch))
+ cchComponent++;
+ if (!cchComponent)
+ {
+ *ppName = pCur;
+ return VINF_SUCCESS;
+ }
+
+ size_t offNext = cchComponent;
+ while (RTPATH_IS_SLASH(ch))
+ ch = pszPath[++offNext];
+
+ /*
+ * Deal with dot and dot-dot.
+ */
+ if (cchComponent == 1 && pszPath[0] == '.')
+ { /* nothing to do */ }
+ else if (cchComponent == 2 && pszPath[0] == '.' && pszPath[1] == '.')
+ {
+ if (pCur->pParent)
+ pCur = pCur->pParent;
+ }
+ /*
+ * Look up the name.
+ */
+ else
+ {
+ PRTFSISOMAKERNAME pChild = rtFsIsoMakerFindEntryInDirBySpec(pCur, pszPath, cchComponent);
+ if (!pChild)
+ return pszPath[offNext] ? VERR_PATH_NOT_FOUND : VERR_FILE_NOT_FOUND;
+ if ( (offNext > cchComponent)
+ && !pChild->pDir)
+ return VERR_NOT_A_DIRECTORY;
+ pCur = pChild;
+ }
+
+ /*
+ * Skip ahead in the path.
+ */
+ pszPath += offNext;
+ }
+}
+
+
+/**
+ * Copy and convert a name to valid ISO-9660 (d-characters only).
+ *
+ * Worker for rtFsIsoMakerNormalizeNameForNamespace. ASSUMES it deals with
+ * dots.
+ *
+ * @returns Length of the resulting string.
+ * @param pszDst The output buffer.
+ * @param cchDstMax The maximum number of (d-chars) to put in the output
+ * buffer.
+ * @param pchSrc The UTF-8 source string (not neccessarily terminated).
+ * @param cchSrc The maximum number of chars to copy from the source
+ * string.
+ */
+static size_t rtFsIsoMakerCopyIso9660Name(char *pszDst, size_t cchDstMax, const char *pchSrc, size_t cchSrc)
+{
+ const char *pchSrcIn = pchSrc;
+ size_t offDst = 0;
+ while ((size_t)(pchSrc - pchSrcIn) < cchSrc)
+ {
+ RTUNICP uc;
+ int rc = RTStrGetCpEx(&pchSrc, &uc);
+ if (RT_SUCCESS(rc))
+ {
+ if ( uc < 128
+ && RTFSISOMAKER_IS_UPPER_IN_D_CHARS((char)uc))
+ {
+ pszDst[offDst++] = RT_C_TO_UPPER((char)uc);
+ if (offDst >= cchDstMax)
+ break;
+ }
+ }
+ }
+ pszDst[offDst] = '\0';
+ return offDst;
+}
+
+
+/**
+ * Normalizes a name for the primary ISO-9660 namespace.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ * @param pParent The parent directory. NULL if root.
+ * @param pchSrc The specified name to normalize (not necessarily zero
+ * terminated).
+ * @param cchSrc The length of the specified name.
+ * @param fNoNormalize Don't normalize the name very strictly (imported or
+ * such).
+ * @param fIsDir Indicates whether it's a directory or file (like).
+ * @param pszDst The output buffer. Must be at least 32 bytes.
+ * @param cbDst The size of the output buffer.
+ * @param pcchDst Where to return the length of the returned string (i.e.
+ * not counting the terminator).
+ * @param pcbInDirRec Where to return the name size in the directory record.
+ */
+static int rtFsIsoMakerNormalizeNameForPrimaryIso9660(PRTFSISOMAKERINT pThis, PRTFSISOMAKERNAME pParent,
+ const char *pchSrc, size_t cchSrc, bool fNoNormalize, bool fIsDir,
+ char *pszDst, size_t cbDst, size_t *pcchDst, size_t *pcbInDirRec)
+{
+ AssertReturn(cbDst > ISO9660_MAX_NAME_LEN + 2, VERR_ISOMK_IPE_BUFFER_SIZE);
+
+ /* Skip leading dots. */
+ while (cchSrc > 0 && *pchSrc == '.')
+ pchSrc++, cchSrc--;
+ if (!cchSrc)
+ {
+ pchSrc = "DOTS";
+ cchSrc = 4;
+ }
+
+ /*
+ * Produce a first name.
+ */
+ uint8_t const uIsoLevel = !fNoNormalize ? pThis->PrimaryIso.uLevel : RT_MAX(pThis->PrimaryIso.uLevel, 3);
+ size_t cchDst;
+ size_t offDstDot;
+ if (fIsDir && !fNoNormalize)
+ offDstDot = cchDst = rtFsIsoMakerCopyIso9660Name(pszDst, uIsoLevel >= 2 ? ISO9660_MAX_NAME_LEN : 8,
+ pchSrc, cchSrc);
+ else
+ {
+ /* Look for the last dot and try preserve the extension when doing the conversion. */
+ size_t offLastDot = cchSrc;
+ for (size_t off = 0; off < cchSrc; off++)
+ if (pchSrc[off] == '.')
+ offLastDot = off;
+
+ if (fNoNormalize)
+ {
+ /* Try preserve the imported name, though, put the foot down if too long. */
+ offDstDot = offLastDot;
+ cchDst = cchSrc;
+ if (cchSrc > ISO9660_MAX_NAME_LEN)
+ {
+ cchDst = ISO9660_MAX_NAME_LEN;
+ if (offDstDot > cchDst)
+ offDstDot = cchDst;
+ }
+ memcpy(pszDst, pchSrc, cchDst);
+ pszDst[cchDst] = '\0';
+ }
+ else if (offLastDot == cchSrc)
+ offDstDot = cchDst = rtFsIsoMakerCopyIso9660Name(pszDst, uIsoLevel >= 2 ? ISO9660_MAX_NAME_LEN : 8,
+ pchSrc, cchSrc);
+ else
+ {
+ const char * const pchSrcExt = &pchSrc[offLastDot + 1];
+ size_t const cchSrcExt = cchSrc - offLastDot - 1;
+ if (uIsoLevel < 2)
+ {
+ cchDst = rtFsIsoMakerCopyIso9660Name(pszDst, 8, pchSrc, cchSrc);
+ offDstDot = cchDst;
+ pszDst[cchDst++] = '.';
+ cchDst += rtFsIsoMakerCopyIso9660Name(&pszDst[cchDst], 3, pchSrcExt, cchSrcExt);
+ }
+ else
+ {
+ size_t cchDstExt = rtFsIsoMakerCopyIso9660Name(pszDst, ISO9660_MAX_NAME_LEN - 2, pchSrcExt, cchSrcExt);
+ if (cchDstExt > 0)
+ {
+ size_t cchBasename = rtFsIsoMakerCopyIso9660Name(pszDst, ISO9660_MAX_NAME_LEN - 2,
+ pchSrc, offLastDot);
+ if (cchBasename + 1 + cchDstExt <= ISO9660_MAX_NAME_LEN)
+ cchDst = cchBasename;
+ else
+ cchDst = ISO9660_MAX_NAME_LEN - 1 - RT_MIN(cchDstExt, 4);
+ offDstDot = cchDst;
+ pszDst[cchDst++] = '.';
+ cchDst += rtFsIsoMakerCopyIso9660Name(&pszDst[cchDst], ISO9660_MAX_NAME_LEN - 1 - cchDst,
+ pchSrcExt, cchSrcExt);
+ }
+ else
+ offDstDot = cchDst = rtFsIsoMakerCopyIso9660Name(pszDst, ISO9660_MAX_NAME_LEN, pchSrc, cchSrc);
+ }
+ }
+ }
+
+ /* Append version if not directory */
+ if (!fIsDir)
+ {
+ pszDst[cchDst++] = ';';
+ pszDst[cchDst++] = '1';
+ pszDst[cchDst] = '\0';
+ }
+
+ /*
+ * Unique name?
+ */
+ if (!rtFsIsoMakerFindObjInDir(pParent, pszDst, cchDst))
+ {
+ *pcchDst = cchDst;
+ *pcbInDirRec = cchDst;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Mangle the name till we've got a unique one.
+ */
+ size_t const cchMaxBasename = (uIsoLevel >= 2 ? ISO9660_MAX_NAME_LEN : 8) - (cchDst - offDstDot);
+ size_t cchInserted = 0;
+ for (uint32_t i = 0; i < _32K; i++)
+ {
+ /* Add a numberic infix. */
+ char szOrd[64];
+ size_t cchOrd = RTStrFormatU32(szOrd, sizeof(szOrd), i + 1, 10, -1, -1, 0 /*fFlags*/);
+ Assert((ssize_t)cchOrd > 0);
+
+ /* Do we need to shuffle the suffix? */
+ if (cchOrd > cchInserted)
+ {
+ if (offDstDot < cchMaxBasename)
+ {
+ memmove(&pszDst[offDstDot + 1], &pszDst[offDstDot], cchDst + 1 - offDstDot);
+ cchDst++;
+ offDstDot++;
+ }
+ cchInserted = cchOrd;
+ }
+
+ /* Insert the new infix and try again. */
+ memcpy(&pszDst[offDstDot - cchOrd], szOrd, cchOrd);
+ if (!rtFsIsoMakerFindObjInDir(pParent, pszDst, cchDst))
+ {
+ *pcchDst = cchDst;
+ *pcbInDirRec = cchDst;
+ return VINF_SUCCESS;
+ }
+ }
+ AssertFailed();
+ return VERR_DUPLICATE;
+}
+
+
+/**
+ * Normalizes a name for the specified name space.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ * @param pNamespace The namespace which rules to normalize it according to.
+ * @param pParent The parent directory. NULL if root.
+ * @param pchSrc The specified name to normalize (not necessarily zero
+ * terminated).
+ * @param cchSrc The length of the specified name.
+ * @param fIsDir Indicates whether it's a directory or file (like).
+ * @param fNoNormalize Don't normalize the name very strictly (imported or
+ * such).
+ * @param pszDst The output buffer. Must be at least 32 bytes.
+ * @param cbDst The size of the output buffer.
+ * @param pcchDst Where to return the length of the returned string (i.e.
+ * not counting the terminator).
+ * @param pcbInDirRec Where to return the name size in the directory record.
+ */
+static int rtFsIsoMakerNormalizeNameForNamespace(PRTFSISOMAKERINT pThis, PRTFSISOMAKERNAMESPACE pNamespace,
+ PRTFSISOMAKERNAME pParent, const char *pchSrc, size_t cchSrc,
+ bool fNoNormalize, bool fIsDir,
+ char *pszDst, size_t cbDst, size_t *pcchDst, size_t *pcbInDirRec)
+{
+ if (cchSrc > 0)
+ {
+ /*
+ * Check that the object doesn't already exist.
+ */
+ AssertReturn(!rtFsIsoMakerFindEntryInDirBySpec(pParent, pchSrc, cchSrc), VERR_ALREADY_EXISTS);
+ switch (pNamespace->fNamespace)
+ {
+ /*
+ * This one is a lot of work, so separate function.
+ */
+ case RTFSISOMAKER_NAMESPACE_ISO_9660:
+ return rtFsIsoMakerNormalizeNameForPrimaryIso9660(pThis, pParent, pchSrc, cchSrc, fNoNormalize, fIsDir,
+ pszDst, cbDst, pcchDst, pcbInDirRec);
+
+ /*
+ * At the moment we don't give darn about UCS-2 limitations here...
+ */
+ case RTFSISOMAKER_NAMESPACE_JOLIET:
+ {
+/** @todo Joliet name limit and check for duplicates. */
+ AssertReturn(cbDst > cchSrc, VERR_BUFFER_OVERFLOW);
+ memcpy(pszDst, pchSrc, cchSrc);
+ pszDst[cchSrc] = '\0';
+ *pcchDst = cchSrc;
+ *pcbInDirRec = RTStrCalcUtf16Len(pszDst) * sizeof(RTUTF16);
+ return VINF_SUCCESS;
+ }
+
+ case RTFSISOMAKER_NAMESPACE_UDF:
+ case RTFSISOMAKER_NAMESPACE_HFS:
+ AssertFailedReturn(VERR_NOT_IMPLEMENTED);
+
+ default:
+ AssertFailedReturn(VERR_IPE_NOT_REACHED_DEFAULT_CASE);
+ }
+ }
+ else
+ {
+ /*
+ * Root special case.
+ *
+ * For ISO-9660 and joliet, we enter it with a length of 1 byte. The
+ * value byte value is zero. The path tables we generate won't be
+ * accepted by windows unless we do this.
+ */
+ *pszDst = '\0';
+ *pcchDst = 0;
+ *pcbInDirRec = pNamespace->fNamespace & (RTFSISOMAKER_NAMESPACE_ISO_9660 | RTFSISOMAKER_NAMESPACE_JOLIET) ? 1 : 0;
+ AssertReturn(!pParent, VERR_ISOMK_IPE_NAMESPACE_3);
+ return VINF_SUCCESS;
+ }
+}
+
+
+/**
+ * Creates a TRANS.TBL file object for a newly named directory.
+ *
+ * The file is associated with the namespace node for the directory. The file
+ * will be generated on the fly from the directory object.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ * @param pNamespace The namespace.
+ * @param pDirName The new name space node for the directory.
+ */
+static int rtFsIsoMakerAddTransTblFileToNewDir(PRTFSISOMAKERINT pThis, PRTFSISOMAKERNAMESPACE pNamespace,
+ PRTFSISOMAKERNAME pDirName)
+{
+ /*
+ * Create a file object for it.
+ */
+ PRTFSISOMAKERFILE pFile;
+ int rc = rtFsIsoMakerAddUnnamedFileWorker(pThis, NULL, 0, &pFile);
+ if (RT_SUCCESS(rc))
+ {
+ pFile->enmSrcType = RTFSISOMAKERSRCTYPE_TRANS_TBL;
+ pFile->u.pTransTblDir = pDirName;
+ pFile->pBootInfoTable = NULL;
+ pDirName->pDir->pTransTblFile = pFile;
+
+ /*
+ * Add it to the directory.
+ */
+ PRTFSISOMAKERNAME pTransTblNm;
+ rc = rtFsIsoMakerObjSetName(pThis, pNamespace, &pFile->Core, pDirName, pNamespace->pszTransTbl,
+ strlen(pNamespace->pszTransTbl), false /*fNoNormalize*/, &pTransTblNm);
+ if (RT_SUCCESS(rc))
+ {
+ pTransTblNm->cchTransNm = 0;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Bail.
+ */
+ pDirName->pDir->pTransTblFile = NULL;
+ rtFsIsoMakerObjRemoveWorker(pThis, &pFile->Core);
+ }
+ return rc;
+}
+
+
+/**
+ * Sets the name of an object in a namespace.
+ *
+ * If the object is already named in the name space, it will first be removed
+ * from that namespace. Should we run out of memory or into normalization
+ * issues after removing it, its original state will _not_ be restored.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ * @param pNamespace The namespace.
+ * @param pObj The object to name.
+ * @param pParent The parent namespace entry
+ * @param pchSpec The specified name (not necessarily terminated).
+ * @param cchSpec The specified name length.
+ * @param fNoNormalize Don't normalize the name (imported or such).
+ * @param ppNewName Where to return the name entry. Optional.
+ */
+static int rtFsIsoMakerObjSetName(PRTFSISOMAKERINT pThis, PRTFSISOMAKERNAMESPACE pNamespace, PRTFSISOMAKEROBJ pObj,
+ PRTFSISOMAKERNAME pParent, const char *pchSpec, size_t cchSpec, bool fNoNormalize,
+ PPRTFSISOMAKERNAME ppNewName)
+{
+ Assert(cchSpec < _32K);
+
+ /*
+ * If this is a file, check the size against the ISO level.
+ * This ASSUMES that only files which size we already know will be 4GB+ sized.
+ */
+ if ( (pNamespace->fNamespace & RTFSISOMAKER_NAMESPACE_ISO_9660)
+ && pNamespace->uLevel < 3
+ && pObj->enmType == RTFSISOMAKEROBJTYPE_FILE)
+ {
+ PRTFSISOMAKERFILE pFile = (PRTFSISOMAKERFILE)pObj;
+ if (pFile->cbData >= _4G)
+ return VERR_ISOMK_FILE_TOO_BIG_REQ_ISO_LEVEL_3;
+ }
+
+ /*
+ * If this is a symbolic link, refuse to add it to a namespace that isn't
+ * configured to support symbolic links.
+ */
+ if ( pObj->enmType == RTFSISOMAKEROBJTYPE_SYMLINK
+ && (pNamespace->fNamespace & (RTFSISOMAKER_NAMESPACE_ISO_9660 | RTFSISOMAKER_NAMESPACE_JOLIET))
+ && pNamespace->uRockRidgeLevel == 0)
+ return VERR_ISOMK_SYMLINK_REQ_ROCK_RIDGE;
+
+ /*
+ * If the object is already named, unset that name before continuing.
+ */
+ if (*rtFsIsoMakerObjGetNameForNamespace(pObj, pNamespace))
+ {
+ int rc = rtFsIsoMakerObjUnsetName(pThis, pNamespace, pObj);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * To avoid need to revert anything, make sure papChildren in the parent is
+ * large enough. If root object, make sure we haven't got a root already.
+ */
+ if (pParent)
+ {
+ AssertReturn(pParent->pDir, VERR_ISOMK_IPE_NAMESPACE_1);
+ uint32_t cChildren = pParent->pDir->cChildren;
+ if (cChildren & 31)
+ { /* likely */ }
+ else
+ {
+ AssertReturn(cChildren < RTFSISOMAKER_MAX_OBJECTS_PER_DIR, VERR_TOO_MUCH_DATA);
+ void *pvNew = RTMemRealloc(pParent->pDir->papChildren, (cChildren + 32) * sizeof(pParent->pDir->papChildren[0]));
+ AssertReturn(pvNew, VERR_NO_MEMORY);
+ pParent->pDir->papChildren = (PPRTFSISOMAKERNAME)pvNew;
+ }
+ }
+ else
+ AssertReturn(pNamespace->pRoot == NULL, VERR_ISOMK_IPE_NAMESPACE_2);
+
+ /*
+ * Normalize the name for this namespace.
+ */
+ size_t cchName = 0;
+ size_t cbNameInDirRec = 0;
+ char szName[RTFSISOMAKER_MAX_NAME_BUF];
+ int rc = rtFsIsoMakerNormalizeNameForNamespace(pThis, pNamespace, pParent, pchSpec, cchSpec, fNoNormalize,
+ pObj->enmType == RTFSISOMAKEROBJTYPE_DIR,
+ szName, sizeof(szName), &cchName, &cbNameInDirRec);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cbNameInDirRec > 0);
+
+ size_t cbName = sizeof(RTFSISOMAKERNAME)
+ + cchName + 1
+ + cchSpec + 1;
+ if (pObj->enmType == RTFSISOMAKEROBJTYPE_DIR)
+ cbName = RT_ALIGN_Z(cbName, 8) + sizeof(RTFSISOMAKERNAMEDIR);
+ PRTFSISOMAKERNAME pName = (PRTFSISOMAKERNAME)RTMemAllocZ(cbName);
+ if (pName)
+ {
+ pName->pObj = pObj;
+ pName->pParent = pParent;
+ pName->cbNameInDirRec = (uint16_t)cbNameInDirRec;
+ pName->cchName = (uint16_t)cchName;
+
+ char *pszDst = &pName->szName[cchName + 1];
+ memcpy(pszDst, pchSpec, cchSpec);
+ pszDst[cchSpec] = '\0';
+ pName->pszSpecNm = pszDst;
+ pName->pszRockRidgeNm = pszDst;
+ pName->pszTransNm = pszDst;
+ pName->cchSpecNm = (uint16_t)cchSpec;
+ pName->cchRockRidgeNm = (uint16_t)cchSpec;
+ pName->cchTransNm = (uint16_t)cchSpec;
+ pName->uDepth = pParent ? pParent->uDepth + 1 : 0;
+ pName->fRockRidgeNmAlloced = false;
+ pName->fTransNmAlloced = false;
+ pName->fRockNeedER = false;
+ pName->fRockNeedRRInDirRec = false;
+ pName->fRockNeedRRInSpill = false;
+
+ pName->fMode = pObj->fMode;
+ pName->uid = pObj->uid;
+ pName->gid = pObj->gid;
+ pName->Device = 0;
+ pName->cHardlinks = 1;
+ pName->offDirRec = UINT32_MAX;
+ pName->cbDirRec = 0;
+ pName->cDirRecs = 1;
+ pName->cbDirRecTotal = 0;
+ pName->fRockEntries = 0;
+ pName->cbRockInDirRec = 0;
+ pName->offRockSpill = UINT32_MAX;
+ pName->cbRockSpill = 0;
+
+ memcpy(pName->szName, szName, cchName);
+ pName->szName[cchName] = '\0';
+
+ if (pObj->enmType != RTFSISOMAKEROBJTYPE_DIR)
+ pName->pDir = NULL;
+ else
+ {
+ size_t offDir = RT_UOFFSETOF(RTFSISOMAKERNAME, szName) + cchName + 1 + cchSpec + 1;
+ offDir = RT_ALIGN_Z(offDir, 8);
+ PRTFSISOMAKERNAMEDIR pDir = (PRTFSISOMAKERNAMEDIR)((uintptr_t)pName + offDir);
+ pDir->offDir = UINT64_MAX;
+ pDir->cbDir = 0;
+ pDir->cChildren = 0;
+ pDir->papChildren = NULL;
+ pDir->pTransTblFile = NULL;
+ pDir->pName = pName;
+ pDir->offPathTable = UINT32_MAX;
+ pDir->idPathTable = UINT16_MAX;
+ pDir->cbDirRec00 = 0;
+ pDir->cbDirRec01 = 0;
+ RTListInit(&pDir->FinalizedEntry);
+ pName->pDir = pDir;
+
+ /* Create the TRANS.TBL file object and enter it into this directory as the first entry. */
+ if (pNamespace->pszTransTbl)
+ {
+ rc = rtFsIsoMakerAddTransTblFileToNewDir(pThis, pNamespace, pName);
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pName);
+ return rc;
+ }
+ }
+ }
+
+ /*
+ * Do the linking and stats. We practice insertion sorting.
+ */
+ if (pParent)
+ {
+ uint32_t idxName = rtFsIsoMakerFindInsertIndex(pNamespace, pParent, pName->szName);
+ uint32_t cChildren = pParent->pDir->cChildren;
+ if (idxName < cChildren)
+ memmove(&pParent->pDir->papChildren[idxName + 1], &pParent->pDir->papChildren[idxName],
+ (cChildren - idxName) * sizeof(pParent->pDir->papChildren[0]));
+ pParent->pDir->papChildren[idxName] = pName;
+ pParent->pDir->cChildren++;
+ }
+ else
+ pNamespace->pRoot = pName;
+ *rtFsIsoMakerObjGetNameForNamespace(pObj, pNamespace) = pName;
+ pNamespace->cNames++;
+
+ /*
+ * Done.
+ */
+ if (ppNewName)
+ *ppNewName = pName;
+ return VINF_SUCCESS;
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Walks the path up to the parent, creating missing directories as needed.
+ *
+ * As usual, we walk the specified names rather than the mangled ones.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ * @param pNamespace The namespace to walk.
+ * @param pszPath The path to walk.
+ * @param ppParent Where to return the pointer to the parent
+ * namespace node.
+ * @param ppszEntry Where to return the pointer to the final name component.
+ * @param pcchEntry Where to return the length of the final name component.
+ */
+static int rtFsIsoMakerCreatePathToParent(PRTFSISOMAKERINT pThis, PRTFSISOMAKERNAMESPACE pNamespace, const char *pszPath,
+ PPRTFSISOMAKERNAME ppParent, const char **ppszEntry, size_t *pcchEntry)
+{
+ *ppParent = NULL; /* shut up gcc */
+ *ppszEntry = NULL; /* shut up gcc */
+ *pcchEntry = 0; /* shut up gcc */
+
+ int rc;
+ AssertReturn(RTPATH_IS_SLASH(*pszPath), VERR_ISOMK_IPE_ROOT_SLASH);
+
+ /*
+ * Deal with the special case of the root.
+ */
+ while (RTPATH_IS_SLASH(*pszPath))
+ pszPath++;
+ AssertReturn(*pszPath, VERR_ISOMK_IPE_EMPTY_PATH); /* We should not be called on a root path. */
+
+ PRTFSISOMAKERNAME pParent = pNamespace->pRoot;
+ if (!pParent)
+ {
+ PRTFSISOMAKERDIR pDir = RTListGetFirst(&pThis->ObjectHead, RTFSISOMAKERDIR, Core.Entry);
+#ifdef RT_STRICT
+ Assert(pDir);
+ Assert(pDir->Core.idxObj == 0);
+ Assert(pDir->Core.enmType == RTFSISOMAKEROBJTYPE_DIR);
+ Assert(*rtFsIsoMakerObjGetNameForNamespace(&pDir->Core, pNamespace) == NULL);
+#endif
+
+ rc = rtFsIsoMakerObjSetName(pThis, pNamespace, &pDir->Core, NULL /*pParent*/, "", 0, false /*fNoNormalize*/, &pParent);
+ AssertRCReturn(rc, rc);
+ pParent = pNamespace->pRoot;
+ AssertReturn(pParent, VERR_ISOMK_IPE_NAMESPACE_4);
+ }
+
+ /*
+ * Now, do the rest of the path.
+ */
+ for (;;)
+ {
+ /*
+ * Find the end of the component and see if its the final one or not.
+ */
+ char ch;
+ size_t cchComponent = 0;
+ while ((ch = pszPath[cchComponent]) != '\0' && !RTPATH_IS_SLASH(ch))
+ cchComponent++;
+ AssertReturn(cchComponent > 0, VERR_ISOMK_IPE_EMPTY_COMPONENT);
+
+ size_t offNext = cchComponent;
+ while (RTPATH_IS_SLASH(ch))
+ ch = pszPath[++offNext];
+
+ if (ch == '\0')
+ {
+ /*
+ * Final component. Make sure it is not dot or dot-dot before returning.
+ */
+ AssertReturn( pszPath[0] != '.'
+ || cchComponent > 2
+ || ( cchComponent == 2
+ && pszPath[1] != '.'),
+ VERR_INVALID_NAME);
+
+ *ppParent = pParent;
+ *ppszEntry = pszPath;
+ *pcchEntry = cchComponent;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Deal with dot and dot-dot.
+ */
+ if (cchComponent == 1 && pszPath[0] == '.')
+ { /* nothing to do */ }
+ else if (cchComponent == 2 && pszPath[0] == '.' && pszPath[1] == '.')
+ {
+ if (pParent->pParent)
+ pParent = pParent->pParent;
+ }
+ /*
+ * Look it up.
+ */
+ else
+ {
+ PRTFSISOMAKERNAME pChild = rtFsIsoMakerFindEntryInDirBySpec(pParent, pszPath, cchComponent);
+ if (pChild)
+ {
+ if (pChild->pDir)
+ pParent = pChild;
+ else
+ return VERR_NOT_A_DIRECTORY;
+ }
+ else
+ {
+ /* Try see if we've got a directory with the same spec name in a different namespace.
+ (We don't want to waste heap by creating a directory instance per namespace.) */
+ PRTFSISOMAKERDIR pChildObj = rtFsIsoMakerFindSubdirBySpec((PRTFSISOMAKERDIR)pParent->pObj,
+ pszPath, cchComponent, pNamespace->fNamespace);
+ if (pChildObj)
+ {
+ PPRTFSISOMAKERNAME ppChildName = rtFsIsoMakerObjGetNameForNamespace(&pChildObj->Core, pNamespace);
+ if (!*ppChildName)
+ {
+ rc = rtFsIsoMakerObjSetName(pThis, pNamespace, &pChildObj->Core, pParent, pszPath, cchComponent,
+ false /*fNoNormalize*/, &pChild);
+ if (RT_FAILURE(rc))
+ return rc;
+ AssertReturn(pChild != NULL, VERR_ISOMK_IPE_NAMESPACE_5);
+ }
+ }
+ /* If we didn't have luck in other namespaces, create a new directory. */
+ if (!pChild)
+ {
+ rc = rtFsIsoMakerAddUnnamedDirWorker(pThis, NULL /*pObjInfo*/, &pChildObj);
+ if (RT_SUCCESS(rc))
+ rc = rtFsIsoMakerObjSetName(pThis, pNamespace, &pChildObj->Core, pParent, pszPath, cchComponent,
+ false /*fNoNormalize*/, &pChild);
+ if (RT_FAILURE(rc))
+ return rc;
+ AssertReturn(pChild != NULL, VERR_ISOMK_IPE_NAMESPACE_5);
+ }
+ pParent = pChild;
+ }
+ }
+
+ /*
+ * Skip ahead in the path.
+ */
+ pszPath += offNext;
+ }
+}
+
+
+/**
+ * Worker for RTFsIsoMakerObjSetPath that operates on a single namespace.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ * @param pNamespace The namespace to name it in.
+ * @param pObj The filesystem object to name.
+ * @param pszPath The path to the entry in the namespace.
+ */
+static int rtFsIsoMakerObjSetPathInOne(PRTFSISOMAKERINT pThis, PRTFSISOMAKERNAMESPACE pNamespace,
+ PRTFSISOMAKEROBJ pObj, const char *pszPath)
+{
+ AssertReturn(*rtFsIsoMakerObjGetNameForNamespace(pObj, pNamespace) == NULL, VERR_WRONG_ORDER);
+ AssertReturn(RTPATH_IS_SLASH(*pszPath), VERR_ISOMK_IPE_ROOT_SLASH);
+
+ /*
+ * Figure out where the parent is.
+ * This will create missing parent name space entries and directory nodes.
+ */
+ PRTFSISOMAKERNAME pParent;
+ const char *pszEntry;
+ size_t cchEntry;
+ int rc;
+ if (pszPath[1] != '\0')
+ rc = rtFsIsoMakerCreatePathToParent(pThis, pNamespace, pszPath, &pParent, &pszEntry, &cchEntry);
+ else
+ {
+ /*
+ * Special case for the root directory.
+ */
+ Assert(pObj->enmType == RTFSISOMAKEROBJTYPE_DIR);
+ AssertReturn(pNamespace->pRoot == NULL, VERR_WRONG_ORDER);
+ pszEntry = "/";
+ cchEntry = 0;
+ pParent = NULL;
+ rc = VINF_SUCCESS;
+ }
+
+ /*
+ * Do the job on the final path component.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ AssertReturn(!RTPATH_IS_SLASH(pszEntry[cchEntry]) || pObj->enmType == RTFSISOMAKEROBJTYPE_DIR,
+ VERR_NOT_A_DIRECTORY);
+ rc = rtFsIsoMakerObjSetName(pThis, pNamespace, pObj, pParent, pszEntry, cchEntry, false /*fNoNormalize*/, NULL);
+ }
+ return rc;
+}
+
+
+/**
+ * Removes an object from the given namespace.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ * @param pNamespace The namespace.
+ * @param pObj The object to name.
+ */
+static int rtFsIsoMakerObjUnsetName(PRTFSISOMAKERINT pThis, PRTFSISOMAKERNAMESPACE pNamespace, PRTFSISOMAKEROBJ pObj)
+{
+ LogFlow(("rtFsIsoMakerObjUnsetName: idxObj=#%#x\n", pObj->idxObj));
+
+ /*
+ * First check if there is anything to do here at all.
+ */
+ PPRTFSISOMAKERNAME ppName = rtFsIsoMakerObjGetNameForNamespace(pObj, pNamespace);
+ PRTFSISOMAKERNAME pName = *ppName;
+ if (!pName)
+ return VINF_SUCCESS;
+
+ /*
+ * We don't support this on the root.
+ */
+ AssertReturn(pName->pParent, VERR_ACCESS_DENIED);
+
+ /*
+ * If this is a directory, we're in for some real fun here as we need to
+ * unset the names of all the children too.
+ */
+ PRTFSISOMAKERNAMEDIR pDir = pName->pDir;
+ if (pDir)
+ {
+ uint32_t iChild = pDir->cChildren;
+ while (iChild-- > 0)
+ {
+ int rc = rtFsIsoMakerObjUnsetName(pThis, pNamespace, pDir->papChildren[iChild]->pObj);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ AssertReturn(pDir->cChildren == 0, VERR_DIR_NOT_EMPTY);
+ }
+
+ /*
+ * Unlink the pName from the parent.
+ */
+ pDir = pName->pParent->pDir;
+ uint32_t iChild = pDir->cChildren;
+ while (iChild-- > 0)
+ if (pDir->papChildren[iChild] == pName)
+ {
+ uint32_t cToMove = pDir->cChildren - iChild - 1;
+ if (cToMove > 0)
+ memmove(&pDir->papChildren[iChild], &pDir->papChildren[iChild + 1], cToMove * sizeof(pDir->papChildren[0]));
+ pDir->cChildren--;
+ pNamespace->cNames--;
+
+ /*
+ * NULL the name member in the object and free the structure.
+ */
+ *ppName = NULL;
+ RTMemFree(pName);
+
+ return VINF_SUCCESS;
+ }
+
+ /* Not found. This can't happen. */
+ AssertFailed();
+ return VERR_ISOMK_IPE_NAMESPACE_6;
+}
+
+
+/**
+ * Gets currently populated namespaces.
+ *
+ * @returns Set of namespaces (RTFSISOMAKER_NAMESPACE_XXX), UINT32_MAX on error.
+ * @param hIsoMaker The ISO maker handle.
+ */
+RTDECL(uint32_t) RTFsIsoMakerGetPopulatedNamespaces(RTFSISOMAKER hIsoMaker)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET_EX(pThis, UINT32_MAX);
+
+ uint32_t fRet = 0;
+ if (pThis->PrimaryIso.cNames > 0)
+ fRet |= RTFSISOMAKER_NAMESPACE_ISO_9660;
+ if (pThis->Joliet.cNames > 0)
+ fRet |= RTFSISOMAKER_NAMESPACE_JOLIET;
+ if (pThis->Udf.cNames > 0)
+ fRet |= RTFSISOMAKER_NAMESPACE_UDF;
+ if (pThis->Hfs.cNames > 0)
+ fRet |= RTFSISOMAKER_NAMESPACE_HFS;
+
+ return fRet;
+}
+
+
+
+
+/*
+ *
+ * Object level config
+ * Object level config
+ * Object level config
+ *
+ */
+
+
+/**
+ * Translates an object index number to an object pointer, slow path.
+ *
+ * @returns Pointer to object, NULL if not found.
+ * @param pThis The ISO maker instance.
+ * @param idxObj The object index too resolve.
+ */
+DECL_NO_INLINE(static, PRTFSISOMAKEROBJ) rtFsIsoMakerIndexToObjSlow(PRTFSISOMAKERINT pThis, uint32_t idxObj)
+{
+ PRTFSISOMAKEROBJ pObj;
+ RTListForEachReverse(&pThis->ObjectHead, pObj, RTFSISOMAKEROBJ, Entry)
+ {
+ if (pObj->idxObj == idxObj)
+ return pObj;
+ }
+ return NULL;
+}
+
+
+/**
+ * Translates an object index number to an object pointer.
+ *
+ * @returns Pointer to object, NULL if not found.
+ * @param pThis The ISO maker instance.
+ * @param idxObj The object index too resolve.
+ */
+DECLINLINE(PRTFSISOMAKEROBJ) rtFsIsoMakerIndexToObj(PRTFSISOMAKERINT pThis, uint32_t idxObj)
+{
+ PRTFSISOMAKEROBJ pObj = RTListGetLast(&pThis->ObjectHead, RTFSISOMAKEROBJ, Entry);
+ if (!pObj || RT_LIKELY(pObj->idxObj == idxObj))
+ return pObj;
+ return rtFsIsoMakerIndexToObjSlow(pThis, idxObj);
+}
+
+
+/**
+ * Resolves a path into a object ID.
+ *
+ * This will be doing the looking up using the specified object names rather
+ * than the version adjusted and mangled according to the namespace setup.
+ *
+ * @returns The object ID corresponding to @a pszPath, or UINT32_MAX if not
+ * found or invalid parameters.
+ * @param hIsoMaker The ISO maker instance.
+ * @param fNamespaces The namespace to resolve @a pszPath in. It's
+ * possible to specify multiple namespaces here, of
+ * course, but that's inefficient.
+ * @param pszPath The path to the object.
+ */
+RTDECL(uint32_t) RTFsIsoMakerGetObjIdxForPath(RTFSISOMAKER hIsoMaker, uint32_t fNamespaces, const char *pszPath)
+{
+ /*
+ * Validate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET_EX(pThis, UINT32_MAX);
+
+ /*
+ * Do the searching.
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aRTFsIsoNamespaces); i++)
+ if (fNamespaces & g_aRTFsIsoNamespaces[i].fNamespace)
+ {
+ PRTFSISOMAKERNAMESPACE pNamespace = (PRTFSISOMAKERNAMESPACE)((uintptr_t)pThis + g_aRTFsIsoNamespaces[i].offNamespace);
+ if (pNamespace->pRoot)
+ {
+ PRTFSISOMAKERNAME pName;
+ int rc = rtFsIsoMakerWalkPathBySpec(pNamespace, pszPath, &pName);
+ if (RT_SUCCESS(rc))
+ return pName->pObj->idxObj;
+ }
+ }
+
+ return UINT32_MAX;
+}
+
+
+/**
+ * Removes the specified object from the image.
+ *
+ * This is a worker for RTFsIsoMakerObjRemove and
+ * rtFsIsoMakerFinalizeRemoveOrphans.
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker instance.
+ * @param pObj The object to remove from the image.
+ */
+static int rtFsIsoMakerObjRemoveWorker(PRTFSISOMAKERINT pThis, PRTFSISOMAKEROBJ pObj)
+{
+ /*
+ * Don't allow removing trans.tbl files and the boot catalog.
+ */
+ if (pObj->enmType == RTFSISOMAKEROBJTYPE_FILE)
+ {
+ PRTFSISOMAKERFILE pFile = (PRTFSISOMAKERFILE)pObj;
+ if (pFile->enmSrcType == RTFSISOMAKERSRCTYPE_TRANS_TBL)
+ return VWRN_DANGLING_OBJECTS; /* HACK ALERT! AssertReturn(pFile->enmSrcType != RTFSISOMAKERSRCTYPE_TRANS_TBL, VERR_ACCESS_DENIED); */
+ AssertReturn(pFile != pThis->pBootCatFile, VERR_ACCESS_DENIED);
+ }
+
+ /*
+ * Remove the object from all name spaces.
+ */
+ int rc = VINF_SUCCESS;
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aRTFsIsoNamespaces); i++)
+ {
+ PRTFSISOMAKERNAMESPACE pNamespace = (PRTFSISOMAKERNAMESPACE)((uintptr_t)pThis + g_aRTFsIsoNamespaces[i].offNamespace);
+ int rc2 = rtFsIsoMakerObjUnsetName(pThis, pNamespace, pObj);
+ if (RT_SUCCESS(rc2) || RT_FAILURE(rc))
+ continue;
+ rc = rc2;
+ }
+
+ /*
+ * If that succeeded, remove the object itself.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ RTListNodeRemove(&pObj->Entry);
+ if (pObj->enmType == RTFSISOMAKEROBJTYPE_FILE)
+ {
+ uint64_t cbData = ((PRTFSISOMAKERFILE)pObj)->cbData;
+ pThis->cbData -= RT_ALIGN_64(cbData, RTFSISOMAKER_SECTOR_SIZE);
+ }
+ pThis->cObjects--;
+ rtFsIsoMakerObjDestroy(pObj);
+ }
+ return rc;
+}
+
+
+/**
+ * Removes the specified object from the image.
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker instance.
+ * @param idxObj The index of the object to remove.
+ */
+RTDECL(int) RTFsIsoMakerObjRemove(RTFSISOMAKER hIsoMaker, uint32_t idxObj)
+{
+ /*
+ * Validate and translate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ PRTFSISOMAKEROBJ pObj = rtFsIsoMakerIndexToObj(pThis, idxObj);
+ AssertReturn(pObj, VERR_OUT_OF_RANGE);
+ AssertReturn( pObj->enmType != RTFSISOMAKEROBJTYPE_FILE
+ || ((PRTFSISOMAKERFILE)pObj)->enmSrcType != RTFSISOMAKERSRCTYPE_RR_SPILL, VERR_ACCESS_DENIED);
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ /*
+ * Call worker.
+ */
+ return rtFsIsoMakerObjRemoveWorker(pThis, pObj);
+}
+
+
+/**
+ * Sets the path (name) of an object in the selected namespaces.
+ *
+ * The name will be transformed as necessary.
+ *
+ * The initial implementation does not allow this function to be called more
+ * than once on an object.
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param idxObj The configuration index of to name.
+ * @param fNamespaces The namespaces to apply the path to
+ * (RTFSISOMAKER_NAMESPACE_XXX).
+ * @param pszPath The path.
+ */
+RTDECL(int) RTFsIsoMakerObjSetPath(RTFSISOMAKER hIsoMaker, uint32_t idxObj, uint32_t fNamespaces, const char *pszPath)
+{
+ /*
+ * Validate and translate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn(!(fNamespaces & ~RTFSISOMAKER_NAMESPACE_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn(RTPATH_IS_SLASH(*pszPath), VERR_INVALID_NAME);
+ PRTFSISOMAKEROBJ pObj = rtFsIsoMakerIndexToObj(pThis, idxObj);
+ AssertReturn(pObj, VERR_OUT_OF_RANGE);
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ /*
+ * Execute requested actions.
+ */
+ uint32_t cAdded = 0;
+ int rc = VINF_SUCCESS;
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aRTFsIsoNamespaces); i++)
+ if (fNamespaces & g_aRTFsIsoNamespaces[i].fNamespace)
+ {
+ PRTFSISOMAKERNAMESPACE pNamespace = (PRTFSISOMAKERNAMESPACE)((uintptr_t)pThis + g_aRTFsIsoNamespaces[i].offNamespace);
+ if (pNamespace->uLevel > 0)
+ {
+ int rc2 = rtFsIsoMakerObjSetPathInOne(pThis, pNamespace, pObj, pszPath);
+ if (RT_SUCCESS(rc2))
+ cAdded++;
+ else if (RT_SUCCESS(rc) || rc == VERR_ISOMK_SYMLINK_REQ_ROCK_RIDGE)
+ rc = rc2;
+ }
+ }
+ return rc != VERR_ISOMK_SYMLINK_REQ_ROCK_RIDGE || cAdded == 0 ? rc : VINF_ISOMK_SYMLINK_REQ_ROCK_RIDGE;
+}
+
+
+/**
+ * Sets the name of an object in the selected namespaces, placing it under the
+ * given directory.
+ *
+ * The name will be transformed as necessary.
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param idxObj The configuration index of to name.
+ * @param idxParentObj The parent directory object.
+ * @param fNamespaces The namespaces to apply the path to
+ * (RTFSISOMAKER_NAMESPACE_XXX).
+ * @param pszName The name.
+ * @param fNoNormalize Don't normalize the name (imported or such).
+ */
+RTDECL(int) RTFsIsoMakerObjSetNameAndParent(RTFSISOMAKER hIsoMaker, uint32_t idxObj, uint32_t idxParentObj,
+ uint32_t fNamespaces, const char *pszName, bool fNoNormalize)
+{
+ /*
+ * Validate and translate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn(!(fNamespaces & ~RTFSISOMAKER_NAMESPACE_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ size_t cchName = strlen(pszName);
+ AssertReturn(cchName > 0, VERR_INVALID_NAME);
+ AssertReturn(memchr(pszName, '/', cchName) == NULL, VERR_INVALID_NAME);
+ PRTFSISOMAKEROBJ pObj = rtFsIsoMakerIndexToObj(pThis, idxObj);
+ AssertReturn(pObj, VERR_OUT_OF_RANGE);
+ PRTFSISOMAKEROBJ pParentObj = rtFsIsoMakerIndexToObj(pThis, idxParentObj);
+ AssertReturn(pParentObj, VERR_OUT_OF_RANGE);
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ /*
+ * Execute requested actions.
+ */
+ uint32_t cAdded = 0;
+ int rc = VINF_SUCCESS;
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aRTFsIsoNamespaces); i++)
+ if (fNamespaces & g_aRTFsIsoNamespaces[i].fNamespace)
+ {
+ PRTFSISOMAKERNAMESPACE pNamespace = (PRTFSISOMAKERNAMESPACE)((uintptr_t)pThis + g_aRTFsIsoNamespaces[i].offNamespace);
+ if (pNamespace->uLevel > 0)
+ {
+ PRTFSISOMAKERNAME pParentName = *rtFsIsoMakerObjGetNameForNamespace(pParentObj, pNamespace);
+ if (pParentName)
+ {
+ int rc2 = rtFsIsoMakerObjSetName(pThis, pNamespace, pObj, pParentName, pszName, cchName,
+ fNoNormalize, NULL /*ppNewName*/);
+ if (RT_SUCCESS(rc2))
+ cAdded++;
+ else if (RT_SUCCESS(rc) || rc == VERR_ISOMK_SYMLINK_REQ_ROCK_RIDGE)
+ rc = rc2;
+ }
+ }
+ }
+ return rc != VERR_ISOMK_SYMLINK_REQ_ROCK_RIDGE || cAdded == 0 ? rc : VINF_ISOMK_SYMLINK_REQ_ROCK_RIDGE;
+}
+
+
+/**
+ * Changes the rock ridge name for the object in the selected namespaces.
+ *
+ * The object must already be enetered into the namespaces by
+ * RTFsIsoMakerObjSetNameAndParent, RTFsIsoMakerObjSetPath or similar.
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param idxObj The configuration index of to name.
+ * @param fNamespaces The namespaces to apply the path to
+ * (RTFSISOMAKER_NAMESPACE_XXX).
+ * @param pszRockName The rock ridge name. Passing NULL will restore
+ * it back to the specified name, while an empty
+ * string will restore it to the namespace name.
+ */
+RTDECL(int) RTFsIsoMakerObjSetRockName(RTFSISOMAKER hIsoMaker, uint32_t idxObj, uint32_t fNamespaces, const char *pszRockName)
+{
+ /*
+ * Validate and translate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn(!(fNamespaces & ~RTFSISOMAKER_NAMESPACE_VALID_MASK), VERR_INVALID_FLAGS);
+ size_t cchRockName;
+ if (pszRockName)
+ {
+ AssertPtrReturn(pszRockName, VERR_INVALID_POINTER);
+ cchRockName = strlen(pszRockName);
+ AssertReturn(cchRockName < _1K, VERR_FILENAME_TOO_LONG);
+ AssertReturn(memchr(pszRockName, '/', cchRockName) == NULL, VERR_INVALID_NAME);
+ }
+ else
+ cchRockName = 0;
+ PRTFSISOMAKEROBJ pObj = rtFsIsoMakerIndexToObj(pThis, idxObj);
+ AssertReturn(pObj, VERR_OUT_OF_RANGE);
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ /*
+ * Execute requested actions.
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aRTFsIsoNamespaces); i++)
+ if (fNamespaces & g_aRTFsIsoNamespaces[i].fNamespace)
+ {
+ PRTFSISOMAKERNAMESPACE pNamespace = (PRTFSISOMAKERNAMESPACE)((uintptr_t)pThis + g_aRTFsIsoNamespaces[i].offNamespace);
+ if ( pNamespace->uLevel > 0
+ && pNamespace->uRockRidgeLevel > 0)
+ {
+ PRTFSISOMAKERNAME pName = *rtFsIsoMakerObjGetNameForNamespace(pObj, pNamespace);
+ if (pName)
+ {
+ /* Free the old rock ridge name. */
+ if (pName->fRockRidgeNmAlloced)
+ {
+ RTMemFree(pName->pszRockRidgeNm);
+ pName->pszRockRidgeNm = NULL;
+ pName->fRockRidgeNmAlloced = false;
+ }
+
+ /* Set new rock ridge name. */
+ if (cchRockName > 0)
+ {
+ pName->pszRockRidgeNm = (char *)RTMemDup(pszRockName, cchRockName + 1);
+ if (!pName->pszRockRidgeNm)
+ {
+ pName->pszRockRidgeNm = (char *)pName->pszSpecNm;
+ pName->cchRockRidgeNm = pName->cchSpecNm;
+ return VERR_NO_MEMORY;
+ }
+ pName->cchRockRidgeNm = (uint16_t)cchRockName;
+ pName->fRockRidgeNmAlloced = true;
+ }
+ else if (pszRockName == NULL)
+ {
+ pName->pszRockRidgeNm = (char *)pName->pszSpecNm;
+ pName->cchRockRidgeNm = pName->cchSpecNm;
+ }
+ else
+ {
+ pName->pszRockRidgeNm = pName->szName;
+ pName->cchRockRidgeNm = pName->cchName;
+ }
+ }
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Enables or disable syslinux boot info table patching of a file.
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param idxObj The configuration index.
+ * @param fEnable Whether to enable or disable patching.
+ */
+RTDECL(int) RTFsIsoMakerObjEnableBootInfoTablePatching(RTFSISOMAKER hIsoMaker, uint32_t idxObj, bool fEnable)
+{
+ /*
+ * Validate and translate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+ PRTFSISOMAKEROBJ pObj = rtFsIsoMakerIndexToObj(pThis, idxObj);
+ AssertReturn(pObj, VERR_OUT_OF_RANGE);
+ AssertReturn(pObj->enmType == RTFSISOMAKEROBJTYPE_FILE, VERR_WRONG_TYPE);
+ PRTFSISOMAKERFILE pFile = (PRTFSISOMAKERFILE)pObj;
+ AssertReturn( pFile->enmSrcType == RTFSISOMAKERSRCTYPE_PATH
+ || pFile->enmSrcType == RTFSISOMAKERSRCTYPE_VFS_FILE
+ || pFile->enmSrcType == RTFSISOMAKERSRCTYPE_COMMON,
+ VERR_WRONG_TYPE);
+
+ /*
+ * Do the job.
+ */
+ if (fEnable)
+ {
+ if (!pFile->pBootInfoTable)
+ {
+ pFile->pBootInfoTable = (PISO9660SYSLINUXINFOTABLE)RTMemAllocZ(sizeof(*pFile->pBootInfoTable));
+ AssertReturn(pFile->pBootInfoTable, VERR_NO_MEMORY);
+ }
+ }
+ else if (pFile->pBootInfoTable)
+ {
+ RTMemFree(pFile->pBootInfoTable);
+ pFile->pBootInfoTable = NULL;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Gets the data size of an object.
+ *
+ * Currently only supported on file objects.
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param idxObj The configuration index.
+ * @param pcbData Where to return the size.
+ */
+RTDECL(int) RTFsIsoMakerObjQueryDataSize(RTFSISOMAKER hIsoMaker, uint32_t idxObj, uint64_t *pcbData)
+{
+ /*
+ * Validate and translate input.
+ */
+ AssertPtrReturn(pcbData, VERR_INVALID_POINTER);
+ *pcbData = UINT64_MAX;
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ PRTFSISOMAKEROBJ pObj = rtFsIsoMakerIndexToObj(pThis, idxObj);
+ AssertReturn(pObj, VERR_OUT_OF_RANGE);
+
+ /*
+ * Do the job.
+ */
+ if (pObj->enmType == RTFSISOMAKEROBJTYPE_FILE)
+ {
+ PRTFSISOMAKERFILE pFile = (PRTFSISOMAKERFILE)pObj;
+ if ( pFile->enmSrcType != RTFSISOMAKERSRCTYPE_TRANS_TBL
+ && pFile->enmSrcType != RTFSISOMAKERSRCTYPE_RR_SPILL)
+ {
+ *pcbData = ((PRTFSISOMAKERFILE)pObj)->cbData;
+ return VINF_SUCCESS;
+ }
+ }
+ return VERR_WRONG_TYPE;
+}
+
+
+/**
+ * Initalizes the common part of a file system object and links it into global
+ * chain.
+ *
+ * @returns IPRT status code
+ * @param pThis The ISO maker instance.
+ * @param pObj The common object.
+ * @param enmType The object type.
+ * @param pObjInfo The object information (typically source).
+ * Optional.
+ */
+static int rtFsIsoMakerInitCommonObj(PRTFSISOMAKERINT pThis, PRTFSISOMAKEROBJ pObj,
+ RTFSISOMAKEROBJTYPE enmType, PCRTFSOBJINFO pObjInfo)
+{
+ Assert(!pThis->fFinalized);
+ AssertReturn(pThis->cObjects < RTFSISOMAKER_MAX_OBJECTS, VERR_OUT_OF_RANGE);
+
+ pObj->enmType = enmType;
+ pObj->pPrimaryName = NULL;
+ pObj->pJolietName = NULL;
+ pObj->pUdfName = NULL;
+ pObj->pHfsName = NULL;
+ pObj->idxObj = pThis->cObjects++;
+ pObj->cNotOrphan = 0;
+ if (pObjInfo)
+ {
+ pObj->BirthTime = pObjInfo->BirthTime;
+ pObj->ChangeTime = pObjInfo->ChangeTime;
+ pObj->ModificationTime = pObjInfo->ModificationTime;
+ pObj->AccessedTime = pObjInfo->AccessTime;
+ if (!pThis->fStrictAttributeStyle)
+ {
+ if (enmType == RTFSISOMAKEROBJTYPE_DIR)
+ pObj->fMode = (pObjInfo->Attr.fMode & ~07222) | 0555;
+ else
+ {
+ pObj->fMode = (pObjInfo->Attr.fMode & ~00222) | 0444;
+ if (pObj->fMode & 0111)
+ pObj->fMode |= 0111;
+ }
+ pObj->uid = pThis->uidDefault;
+ pObj->gid = pThis->gidDefault;
+ }
+ else
+ {
+ pObj->fMode = pObjInfo->Attr.fMode;
+ pObj->uid = pObjInfo->Attr.u.Unix.uid != NIL_RTUID ? pObjInfo->Attr.u.Unix.uid : pThis->uidDefault;
+ pObj->gid = pObjInfo->Attr.u.Unix.gid != NIL_RTGID ? pObjInfo->Attr.u.Unix.gid : pThis->gidDefault;
+ }
+ if (enmType == RTFSISOMAKEROBJTYPE_DIR ? pThis->fForcedDirModeActive : pThis->fForcedFileModeActive)
+ pObj->fMode = (pObj->fMode & ~RTFS_UNIX_ALL_PERMS)
+ | (enmType == RTFSISOMAKEROBJTYPE_DIR ? pThis->fForcedDirMode : pThis->fForcedFileMode);
+ }
+ else
+ {
+ pObj->BirthTime = pThis->ImageCreationTime;
+ pObj->ChangeTime = pThis->ImageCreationTime;
+ pObj->ModificationTime = pThis->ImageCreationTime;
+ pObj->AccessedTime = pThis->ImageCreationTime;
+ pObj->fMode = enmType == RTFSISOMAKEROBJTYPE_DIR ? pThis->fDefaultDirMode : pThis->fDefaultFileMode;
+ pObj->uid = pThis->uidDefault;
+ pObj->gid = pThis->gidDefault;
+ }
+
+ RTListAppend(&pThis->ObjectHead, &pObj->Entry);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Internal function for adding an unnamed directory.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO make instance.
+ * @param pObjInfo Pointer to object attributes, must be set to
+ * UNIX. The size and hardlink counts are ignored.
+ * Optional.
+ * @param ppDir Where to return the directory.
+ */
+static int rtFsIsoMakerAddUnnamedDirWorker(PRTFSISOMAKERINT pThis, PCRTFSOBJINFO pObjInfo, PRTFSISOMAKERDIR *ppDir)
+{
+ PRTFSISOMAKERDIR pDir = (PRTFSISOMAKERDIR)RTMemAllocZ(sizeof(*pDir));
+ AssertReturn(pDir, VERR_NO_MEMORY);
+ int rc = rtFsIsoMakerInitCommonObj(pThis, &pDir->Core, RTFSISOMAKEROBJTYPE_DIR, pObjInfo);
+ if (RT_SUCCESS(rc))
+ {
+ *ppDir = pDir;
+ return VINF_SUCCESS;
+ }
+ RTMemFree(pDir);
+ return rc;
+
+}
+
+
+/**
+ * Adds an unnamed directory to the image.
+ *
+ * The directory must explictly be entered into the desired namespaces.
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param pObjInfo Pointer to object attributes, must be set to
+ * UNIX. The size and hardlink counts are ignored.
+ * Optional.
+ * @param pidxObj Where to return the configuration index of the
+ * directory.
+ * @sa RTFsIsoMakerAddDir, RTFsIsoMakerObjSetPath
+ */
+RTDECL(int) RTFsIsoMakerAddUnnamedDir(RTFSISOMAKER hIsoMaker, PCRTFSOBJINFO pObjInfo, uint32_t *pidxObj)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pidxObj, VERR_INVALID_POINTER);
+ if (pObjInfo)
+ {
+ AssertPtrReturn(pObjInfo, VERR_INVALID_POINTER);
+ AssertReturn(pObjInfo->Attr.enmAdditional == RTFSOBJATTRADD_UNIX, VERR_INVALID_PARAMETER);
+ AssertReturn(RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode), VERR_INVALID_FLAGS);
+ }
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ PRTFSISOMAKERDIR pDir;
+ int rc = rtFsIsoMakerAddUnnamedDirWorker(pThis, pObjInfo, &pDir);
+ *pidxObj = RT_SUCCESS(rc) ? pDir->Core.idxObj : UINT32_MAX;
+ return rc;
+}
+
+
+/**
+ * Adds a directory to the image in all namespaces and default attributes.
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param pszDir The path (UTF-8) to the directory in the ISO.
+ *
+ * @param pidxObj Where to return the configuration index of the
+ * directory. Optional.
+ * @sa RTFsIsoMakerAddUnnamedDir, RTFsIsoMakerObjSetPath
+ */
+RTDECL(int) RTFsIsoMakerAddDir(RTFSISOMAKER hIsoMaker, const char *pszDir, uint32_t *pidxObj)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pszDir, VERR_INVALID_POINTER);
+ AssertReturn(RTPATH_IS_SLASH(*pszDir), VERR_INVALID_NAME);
+
+ uint32_t idxObj;
+ int rc = RTFsIsoMakerAddUnnamedDir(hIsoMaker, NULL /*pObjInfo*/, &idxObj);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFsIsoMakerObjSetPath(hIsoMaker, idxObj, RTFSISOMAKER_NAMESPACE_ALL, pszDir);
+ if (RT_SUCCESS(rc))
+ {
+ if (pidxObj)
+ *pidxObj = idxObj;
+ }
+ else
+ RTFsIsoMakerObjRemove(hIsoMaker, idxObj);
+ }
+ return rc;
+}
+
+
+/**
+ * Internal function for adding an unnamed file.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO make instance.
+ * @param pObjInfo Object information. Optional.
+ * @param cbExtra Extra space for additional data (e.g. source
+ * path string copy).
+ * @param ppFile Where to return the file.
+ */
+static int rtFsIsoMakerAddUnnamedFileWorker(PRTFSISOMAKERINT pThis, PCRTFSOBJINFO pObjInfo, size_t cbExtra,
+ PRTFSISOMAKERFILE *ppFile)
+{
+ PRTFSISOMAKERFILE pFile = (PRTFSISOMAKERFILE)RTMemAllocZ(sizeof(*pFile) + cbExtra);
+ AssertReturn(pFile, VERR_NO_MEMORY);
+ int rc = rtFsIsoMakerInitCommonObj(pThis, &pFile->Core, RTFSISOMAKEROBJTYPE_FILE, pObjInfo);
+ if (RT_SUCCESS(rc))
+ {
+ pFile->cbData = pObjInfo ? pObjInfo->cbObject : 0;
+ pThis->cbData += RT_ALIGN_64(pFile->cbData, RTFSISOMAKER_SECTOR_SIZE);
+ pFile->offData = UINT64_MAX;
+ pFile->enmSrcType = RTFSISOMAKERSRCTYPE_INVALID;
+ pFile->u.pszSrcPath = NULL;
+ pFile->pBootInfoTable = NULL;
+ RTListInit(&pFile->FinalizedEntry);
+
+ *ppFile = pFile;
+ return VINF_SUCCESS;
+ }
+ RTMemFree(pFile);
+ return rc;
+
+}
+
+
+/**
+ * Adds an unnamed file to the image that's backed by a host file.
+ *
+ * The file must explictly be entered into the desired namespaces.
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param pszSrcFile The source file path. VFS chain spec allowed.
+ * @param pidxObj Where to return the configuration index of the
+ * directory.
+ * @sa RTFsIsoMakerAddFile, RTFsIsoMakerObjSetPath
+ */
+RTDECL(int) RTFsIsoMakerAddUnnamedFileWithSrcPath(RTFSISOMAKER hIsoMaker, const char *pszSrcFile, uint32_t *pidxObj)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pidxObj, VERR_INVALID_POINTER);
+ *pidxObj = UINT32_MAX;
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ /*
+ * Check that the source file exists and is a file.
+ */
+ uint32_t offError = 0;
+ RTFSOBJINFO ObjInfo;
+ int rc = RTVfsChainQueryInfo(pszSrcFile, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_FOLLOW_LINK, &offError, NULL);
+ AssertMsgRCReturn(rc, ("%s -> %Rrc offError=%u\n", pszSrcFile, rc, offError), rc);
+ AssertMsgReturn(RTFS_IS_FILE(ObjInfo.Attr.fMode), ("%#x - %s\n", ObjInfo.Attr.fMode, pszSrcFile), VERR_NOT_A_FILE);
+
+ /*
+ * Create a file object for it.
+ */
+ size_t const cbSrcFile = strlen(pszSrcFile) + 1;
+ PRTFSISOMAKERFILE pFile;
+ rc = rtFsIsoMakerAddUnnamedFileWorker(pThis, &ObjInfo, cbSrcFile, &pFile);
+ if (RT_SUCCESS(rc))
+ {
+ pFile->enmSrcType = RTFSISOMAKERSRCTYPE_PATH;
+ pFile->u.pszSrcPath = (char *)memcpy(pFile + 1, pszSrcFile, cbSrcFile);
+
+ *pidxObj = pFile->Core.idxObj;
+ }
+ return rc;
+}
+
+
+/**
+ * Adds an unnamed file to the image that's backed by a VFS file.
+ *
+ * The file must explictly be entered into the desired namespaces.
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param hVfsFileSrc The source file handle.
+ * @param pidxObj Where to return the configuration index of the
+ * directory.
+ * @sa RTFsIsoMakerAddUnnamedFileWithSrcPath, RTFsIsoMakerObjSetPath
+ */
+RTDECL(int) RTFsIsoMakerAddUnnamedFileWithVfsFile(RTFSISOMAKER hIsoMaker, RTVFSFILE hVfsFileSrc, uint32_t *pidxObj)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pidxObj, VERR_INVALID_POINTER);
+ *pidxObj = UINT32_MAX;
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ /*
+ * Get the VFS file info. This implicitly validates the handle.
+ */
+ RTFSOBJINFO ObjInfo;
+ int rc = RTVfsFileQueryInfo(hVfsFileSrc, &ObjInfo, RTFSOBJATTRADD_UNIX);
+ AssertMsgRCReturn(rc, ("RTVfsFileQueryInfo(%p) -> %Rrc\n", hVfsFileSrc, rc), rc);
+
+ /*
+ * Retain a reference to the file.
+ */
+ uint32_t cRefs = RTVfsFileRetain(hVfsFileSrc);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Create a file object for it.
+ */
+ PRTFSISOMAKERFILE pFile;
+ rc = rtFsIsoMakerAddUnnamedFileWorker(pThis, &ObjInfo, 0, &pFile);
+ if (RT_SUCCESS(rc))
+ {
+ pFile->enmSrcType = RTFSISOMAKERSRCTYPE_VFS_FILE;
+ pFile->u.hVfsFile = hVfsFileSrc;
+
+ *pidxObj = pFile->Core.idxObj;
+ }
+ else
+ RTVfsFileRelease(hVfsFileSrc);
+ return rc;
+}
+
+
+/**
+ * Adds an unnamed file to the image that's backed by a portion of a common
+ * source file.
+ *
+ * The file must explictly be entered into the desired namespaces.
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param idxCommonSrc The common source file index.
+ * @param offData The offset of the data in the source file.
+ * @param cbData The file size.
+ * @param pObjInfo Pointer to file info. Optional.
+ * @param pidxObj Where to return the configuration index of the
+ * directory.
+ * @sa RTFsIsoMakerAddUnnamedFileWithSrcPath, RTFsIsoMakerObjSetPath
+ */
+RTDECL(int) RTFsIsoMakerAddUnnamedFileWithCommonSrc(RTFSISOMAKER hIsoMaker, uint32_t idxCommonSrc,
+ uint64_t offData, uint64_t cbData, PCRTFSOBJINFO pObjInfo, uint32_t *pidxObj)
+{
+ /*
+ * Validate and fake input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pidxObj, VERR_INVALID_POINTER);
+ *pidxObj = UINT32_MAX;
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+ AssertReturn(idxCommonSrc < pThis->cCommonSources, VERR_INVALID_PARAMETER);
+ AssertReturn(offData < (uint64_t)RTFOFF_MAX, VERR_OUT_OF_RANGE);
+ AssertReturn(cbData < (uint64_t)RTFOFF_MAX, VERR_OUT_OF_RANGE);
+ AssertReturn(offData + cbData < (uint64_t)RTFOFF_MAX, VERR_OUT_OF_RANGE);
+ RTFSOBJINFO ObjInfo;
+ if (!pObjInfo)
+ {
+ ObjInfo.cbObject = cbData;
+ ObjInfo.cbAllocated = cbData;
+ ObjInfo.BirthTime = pThis->ImageCreationTime;
+ ObjInfo.ChangeTime = pThis->ImageCreationTime;
+ ObjInfo.ModificationTime = pThis->ImageCreationTime;
+ ObjInfo.AccessTime = pThis->ImageCreationTime;
+ ObjInfo.Attr.fMode = pThis->fDefaultFileMode;
+ ObjInfo.Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
+ ObjInfo.Attr.u.Unix.uid = NIL_RTUID;
+ ObjInfo.Attr.u.Unix.gid = NIL_RTGID;
+ ObjInfo.Attr.u.Unix.cHardlinks = 1;
+ ObjInfo.Attr.u.Unix.INodeIdDevice = 0;
+ ObjInfo.Attr.u.Unix.INodeId = 0;
+ ObjInfo.Attr.u.Unix.fFlags = 0;
+ ObjInfo.Attr.u.Unix.GenerationId = 0;
+ ObjInfo.Attr.u.Unix.Device = 0;
+ pObjInfo = &ObjInfo;
+ }
+ else
+ {
+ AssertPtrReturn(pObjInfo, VERR_INVALID_POINTER);
+ AssertReturn(pObjInfo->Attr.enmAdditional == RTFSOBJATTRADD_UNIX, VERR_WRONG_TYPE);
+ AssertReturn((uint64_t)pObjInfo->cbObject == cbData, VERR_INVALID_PARAMETER);
+ }
+
+ /*
+ * Create a file object for it.
+ */
+ PRTFSISOMAKERFILE pFile;
+ int rc = rtFsIsoMakerAddUnnamedFileWorker(pThis, pObjInfo, 0, &pFile);
+ if (RT_SUCCESS(rc))
+ {
+ pFile->enmSrcType = RTFSISOMAKERSRCTYPE_COMMON;
+ pFile->u.Common.idxSrc = idxCommonSrc;
+ pFile->u.Common.offData = offData;
+
+ *pidxObj = pFile->Core.idxObj;
+ }
+ return rc;
+}
+
+
+/**
+ * Adds a common source file.
+ *
+ * Using RTFsIsoMakerAddUnnamedFileWithCommonSrc a sections common source file
+ * can be referenced to make up other files. The typical use case is when
+ * importing data from an existing ISO.
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param hVfsFile VFS handle of the common source. (A reference
+ * is added, none consumed.)
+ * @param pidxCommonSrc Where to return the assigned common source
+ * index. This is used to reference the file.
+ * @sa RTFsIsoMakerAddUnnamedFileWithCommonSrc
+ */
+RTDECL(int) RTFsIsoMakerAddCommonSourceFile(RTFSISOMAKER hIsoMaker, RTVFSFILE hVfsFile, uint32_t *pidxCommonSrc)
+{
+ /*
+ * Validate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pidxCommonSrc, VERR_INVALID_POINTER);
+ *pidxCommonSrc = UINT32_MAX;
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ /*
+ * Resize the common source array if necessary.
+ */
+ if ((pThis->cCommonSources & 15) == 0)
+ {
+ void *pvNew = RTMemRealloc(pThis->paCommonSources, (pThis->cCommonSources + 16) * sizeof(pThis->paCommonSources[0]));
+ AssertReturn(pvNew, VERR_NO_MEMORY);
+ pThis->paCommonSources = (PRTVFSFILE)pvNew;
+ }
+
+ /*
+ * Retain a reference to the source file, thereby validating the handle.
+ * Then add it to the array.
+ */
+ uint32_t cRefs = RTVfsFileRetain(hVfsFile);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ uint32_t idx = pThis->cCommonSources++;
+ pThis->paCommonSources[idx] = hVfsFile;
+
+ *pidxCommonSrc = idx;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Adds a file that's backed by a host file to the image in all namespaces and
+ * with attributes taken from the source file.
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param pszFile The path to the file in the image.
+ * @param pszSrcFile The source file path. VFS chain spec allowed.
+ * @param pidxObj Where to return the configuration index of the file.
+ * Optional
+ * @sa RTFsIsoMakerAddFileWithVfsFile,
+ * RTFsIsoMakerAddUnnamedFileWithSrcPath
+ */
+RTDECL(int) RTFsIsoMakerAddFileWithSrcPath(RTFSISOMAKER hIsoMaker, const char *pszFile, const char *pszSrcFile, uint32_t *pidxObj)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pszFile, VERR_INVALID_POINTER);
+ AssertReturn(RTPATH_IS_SLASH(*pszFile), VERR_INVALID_NAME);
+
+ uint32_t idxObj;
+ int rc = RTFsIsoMakerAddUnnamedFileWithSrcPath(hIsoMaker, pszSrcFile, &idxObj);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFsIsoMakerObjSetPath(hIsoMaker, idxObj, RTFSISOMAKER_NAMESPACE_ALL, pszFile);
+ if (RT_SUCCESS(rc))
+ {
+ if (pidxObj)
+ *pidxObj = idxObj;
+ }
+ else
+ RTFsIsoMakerObjRemove(hIsoMaker, idxObj);
+ }
+ return rc;
+}
+
+
+/**
+ * Adds a file that's backed by a VFS file to the image in all namespaces and
+ * with attributes taken from the source file.
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param pszFile The path to the file in the image.
+ * @param hVfsFileSrc The source file handle.
+ * @param pidxObj Where to return the configuration index of the file.
+ * Optional.
+ * @sa RTFsIsoMakerAddUnnamedFileWithVfsFile,
+ * RTFsIsoMakerAddFileWithSrcPath
+ */
+RTDECL(int) RTFsIsoMakerAddFileWithVfsFile(RTFSISOMAKER hIsoMaker, const char *pszFile, RTVFSFILE hVfsFileSrc, uint32_t *pidxObj)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pszFile, VERR_INVALID_POINTER);
+ AssertReturn(RTPATH_IS_SLASH(*pszFile), VERR_INVALID_NAME);
+
+ uint32_t idxObj;
+ int rc = RTFsIsoMakerAddUnnamedFileWithVfsFile(hIsoMaker, hVfsFileSrc, &idxObj);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFsIsoMakerObjSetPath(hIsoMaker, idxObj, RTFSISOMAKER_NAMESPACE_ALL, pszFile);
+ if (RT_SUCCESS(rc))
+ {
+ if (pidxObj)
+ *pidxObj = idxObj;
+ }
+ else
+ RTFsIsoMakerObjRemove(hIsoMaker, idxObj);
+ }
+ return rc;
+}
+
+
+/**
+ * Adds an unnamed symbolic link to the image.
+ *
+ * The symlink must explictly be entered into the desired namespaces. Please
+ * note that it is not possible to enter a symbolic link into an ISO 9660
+ * namespace where rock ridge extensions are disabled, since symbolic links
+ * depend on rock ridge. For HFS and UDF there is no such requirement.
+ *
+ * Will fail if no namespace is configured that supports symlinks.
+ *
+ * @returns IPRT status code
+ * @retval VERR_ISOMK_SYMLINK_SUPPORT_DISABLED if not supported.
+ * @param hIsoMaker The ISO maker handle.
+ * @param pObjInfo Pointer to object attributes, must be set to
+ * UNIX. The size and hardlink counts are ignored.
+ * Optional.
+ * @param pszTarget The symbolic link target (UTF-8).
+ * @param pidxObj Where to return the configuration index of the
+ * directory.
+ * @sa RTFsIsoMakerAddSymlink, RTFsIsoMakerObjSetPath
+ */
+RTDECL(int) RTFsIsoMakerAddUnnamedSymlink(RTFSISOMAKER hIsoMaker, PCRTFSOBJINFO pObjInfo, const char *pszTarget, uint32_t *pidxObj)
+{
+ /*
+ * Validate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pidxObj, VERR_INVALID_POINTER);
+ if (pObjInfo)
+ {
+ AssertPtrReturn(pObjInfo, VERR_INVALID_POINTER);
+ AssertReturn(pObjInfo->Attr.enmAdditional == RTFSOBJATTRADD_UNIX, VERR_INVALID_PARAMETER);
+ AssertReturn(RTFS_IS_SYMLINK(pObjInfo->Attr.fMode), VERR_INVALID_FLAGS);
+ }
+ AssertPtrReturn(pszTarget, VERR_INVALID_POINTER);
+ size_t cchTarget = strlen(pszTarget);
+ AssertReturn(cchTarget > 0, VERR_INVALID_NAME);
+ AssertReturn(cchTarget < RTFSISOMAKER_MAX_SYMLINK_TARGET_LEN, VERR_FILENAME_TOO_LONG);
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ /*
+ * Check that symlinks are supported by some namespace.
+ */
+ AssertReturn( (pThis->PrimaryIso.uLevel > 0 && pThis->PrimaryIso.uRockRidgeLevel > 0)
+ || (pThis->Joliet.uLevel > 0 && pThis->Joliet.uRockRidgeLevel > 0)
+ || pThis->Udf.uLevel > 0
+ || pThis->Hfs.uLevel > 0,
+ VERR_ISOMK_SYMLINK_SUPPORT_DISABLED);
+
+ /*
+ * Calculate the size of the SL entries.
+ */
+ uint8_t abTmp[_2K + RTFSISOMAKER_MAX_SYMLINK_TARGET_LEN * 3];
+ ssize_t cbSlRockRidge = rtFsIsoMakerOutFile_RockRidgeGenSL(pszTarget, abTmp, sizeof(abTmp));
+ AssertReturn(cbSlRockRidge > 0, (int)cbSlRockRidge);
+
+ /*
+ * Do the adding.
+ */
+ PRTFSISOMAKERSYMLINK pSymlink = (PRTFSISOMAKERSYMLINK)RTMemAllocZ(RT_UOFFSETOF_DYN(RTFSISOMAKERSYMLINK, szTarget[cchTarget + 1]));
+ AssertReturn(pSymlink, VERR_NO_MEMORY);
+ int rc = rtFsIsoMakerInitCommonObj(pThis, &pSymlink->Core, RTFSISOMAKEROBJTYPE_SYMLINK, pObjInfo);
+ if (RT_SUCCESS(rc))
+ {
+ pSymlink->cchTarget = (uint16_t)cchTarget;
+ pSymlink->cbSlRockRidge = (uint16_t)cbSlRockRidge;
+ memcpy(pSymlink->szTarget, pszTarget, cchTarget);
+ pSymlink->szTarget[cchTarget] = '\0';
+
+ *pidxObj = pSymlink->Core.idxObj;
+ return VINF_SUCCESS;
+ }
+ RTMemFree(pSymlink);
+ return rc;
+}
+
+
+/**
+ * Adds a directory to the image in all namespaces and default attributes.
+ *
+ * Will fail if no namespace is configured that supports symlinks.
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param pszSymlink The path (UTF-8) to the symlink in the ISO.
+ * @param pszTarget The symlink target (UTF-8).
+ * @param pidxObj Where to return the configuration index of the
+ * directory. Optional.
+ * @sa RTFsIsoMakerAddUnnamedSymlink, RTFsIsoMakerObjSetPath
+ */
+RTDECL(int) RTFsIsoMakerAddSymlink(RTFSISOMAKER hIsoMaker, const char *pszSymlink, const char *pszTarget, uint32_t *pidxObj)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pszSymlink, VERR_INVALID_POINTER);
+ AssertReturn(RTPATH_IS_SLASH(*pszSymlink), VERR_INVALID_NAME);
+
+ uint32_t idxObj;
+ int rc = RTFsIsoMakerAddUnnamedSymlink(hIsoMaker, NULL /*pObjInfo*/, pszTarget, &idxObj);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFsIsoMakerObjSetPath(hIsoMaker, idxObj, RTFSISOMAKER_NAMESPACE_ALL, pszSymlink);
+ if (RT_SUCCESS(rc))
+ {
+ if (pidxObj)
+ *pidxObj = idxObj;
+ }
+ else
+ RTFsIsoMakerObjRemove(hIsoMaker, idxObj);
+ }
+ return rc;
+
+}
+
+
+
+/*
+ *
+ * Name space level object config.
+ * Name space level object config.
+ * Name space level object config.
+ *
+ */
+
+
+/**
+ * Modifies the mode mask for a given path in one or more namespaces.
+ *
+ * The mode mask is used by rock ridge, UDF and HFS.
+ *
+ * @returns IPRT status code.
+ * @retval VWRN_NOT_FOUND if the path wasn't found in any of the specified
+ * namespaces.
+ *
+ * @param hIsoMaker The ISO maker handler.
+ * @param pszPath The path which mode mask should be modified.
+ * @param fNamespaces The namespaces to set it in.
+ * @param fSet The mode bits to set.
+ * @param fUnset The mode bits to clear (applied first).
+ * @param fFlags Reserved, MBZ.
+ * @param pcHits Where to return number of paths found. Optional.
+ */
+RTDECL(int) RTFsIsoMakerSetPathMode(RTFSISOMAKER hIsoMaker, const char *pszPath, uint32_t fNamespaces,
+ RTFMODE fSet, RTFMODE fUnset, uint32_t fFlags, uint32_t *pcHits)
+{
+ /*
+ * Validate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn(RTPATH_IS_SLASH(*pszPath), VERR_INVALID_NAME);
+ AssertReturn(!(fNamespaces & ~RTFSISOMAKER_NAMESPACE_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertReturn(!(fSet & ~07777), VERR_INVALID_PARAMETER);
+ AssertReturn(!(fUnset & ~07777), VERR_INVALID_PARAMETER);
+ AssertReturn(!fFlags, VERR_INVALID_FLAGS);
+ AssertPtrNullReturn(pcHits, VERR_INVALID_POINTER);
+
+ /*
+ * Make the changes namespace by namespace.
+ */
+ uint32_t cHits = 0;
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aRTFsIsoNamespaces); i++)
+ if (fNamespaces & g_aRTFsIsoNamespaces[i].fNamespace)
+ {
+ PRTFSISOMAKERNAMESPACE pNamespace = (PRTFSISOMAKERNAMESPACE)((uintptr_t)pThis + g_aRTFsIsoNamespaces[i].offNamespace);
+ if (pNamespace->uLevel > 0)
+ {
+ PRTFSISOMAKERNAME pName;
+ int rc = rtFsIsoMakerWalkPathBySpec(pNamespace, pszPath, &pName);
+ if (RT_SUCCESS(rc))
+ {
+ pName->fMode = (pName->fMode & ~fUnset) | fSet;
+ cHits++;
+ }
+ }
+ }
+
+ if (pcHits)
+ *pcHits = cHits;
+ if (cHits > 0)
+ return VINF_SUCCESS;
+ return VWRN_NOT_FOUND;
+}
+
+
+/**
+ * Modifies the owner ID for a given path in one or more namespaces.
+ *
+ * The owner ID is used by rock ridge, UDF and HFS.
+ *
+ * @returns IPRT status code.
+ * @retval VWRN_NOT_FOUND if the path wasn't found in any of the specified
+ * namespaces.
+ *
+ * @param hIsoMaker The ISO maker handler.
+ * @param pszPath The path which mode mask should be modified.
+ * @param fNamespaces The namespaces to set it in.
+ * @param idOwner The new owner ID to set.
+ * @param pcHits Where to return number of paths found. Optional.
+ */
+RTDECL(int) RTFsIsoMakerSetPathOwnerId(RTFSISOMAKER hIsoMaker, const char *pszPath, uint32_t fNamespaces,
+ RTUID idOwner, uint32_t *pcHits)
+{
+ /*
+ * Validate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn(RTPATH_IS_SLASH(*pszPath), VERR_INVALID_NAME);
+ AssertReturn(!(fNamespaces & ~RTFSISOMAKER_NAMESPACE_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertPtrNullReturn(pcHits, VERR_INVALID_POINTER);
+
+ /*
+ * Make the changes namespace by namespace.
+ */
+ uint32_t cHits = 0;
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aRTFsIsoNamespaces); i++)
+ if (fNamespaces & g_aRTFsIsoNamespaces[i].fNamespace)
+ {
+ PRTFSISOMAKERNAMESPACE pNamespace = (PRTFSISOMAKERNAMESPACE)((uintptr_t)pThis + g_aRTFsIsoNamespaces[i].offNamespace);
+ if (pNamespace->uLevel > 0)
+ {
+ PRTFSISOMAKERNAME pName;
+ int rc = rtFsIsoMakerWalkPathBySpec(pNamespace, pszPath, &pName);
+ if (RT_SUCCESS(rc))
+ {
+ pName->uid = idOwner;
+ cHits++;
+ }
+ }
+ }
+
+ if (pcHits)
+ *pcHits = cHits;
+ if (cHits > 0)
+ return VINF_SUCCESS;
+ return VWRN_NOT_FOUND;
+}
+
+
+/**
+ * Modifies the group ID for a given path in one or more namespaces.
+ *
+ * The group ID is used by rock ridge, UDF and HFS.
+ *
+ * @returns IPRT status code.
+ * @retval VWRN_NOT_FOUND if the path wasn't found in any of the specified
+ * namespaces.
+ *
+ * @param hIsoMaker The ISO maker handler.
+ * @param pszPath The path which mode mask should be modified.
+ * @param fNamespaces The namespaces to set it in.
+ * @param idGroup The new group ID to set.
+ * @param pcHits Where to return number of paths found. Optional.
+ */
+RTDECL(int) RTFsIsoMakerSetPathGroupId(RTFSISOMAKER hIsoMaker, const char *pszPath, uint32_t fNamespaces,
+ RTGID idGroup, uint32_t *pcHits)
+{
+ /*
+ * Validate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn(RTPATH_IS_SLASH(*pszPath), VERR_INVALID_NAME);
+ AssertReturn(!(fNamespaces & ~RTFSISOMAKER_NAMESPACE_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertPtrNullReturn(pcHits, VERR_INVALID_POINTER);
+
+ /*
+ * Make the changes namespace by namespace.
+ */
+ uint32_t cHits = 0;
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aRTFsIsoNamespaces); i++)
+ if (fNamespaces & g_aRTFsIsoNamespaces[i].fNamespace)
+ {
+ PRTFSISOMAKERNAMESPACE pNamespace = (PRTFSISOMAKERNAMESPACE)((uintptr_t)pThis + g_aRTFsIsoNamespaces[i].offNamespace);
+ if (pNamespace->uLevel > 0)
+ {
+ PRTFSISOMAKERNAME pName;
+ int rc = rtFsIsoMakerWalkPathBySpec(pNamespace, pszPath, &pName);
+ if (RT_SUCCESS(rc))
+ {
+ pName->gid = idGroup;
+ cHits++;
+ }
+ }
+ }
+
+ if (pcHits)
+ *pcHits = cHits;
+ if (cHits > 0)
+ return VINF_SUCCESS;
+ return VWRN_NOT_FOUND;
+}
+
+
+
+
+
+
+/*
+ *
+ * El Torito Booting.
+ * El Torito Booting.
+ * El Torito Booting.
+ * El Torito Booting.
+ *
+ */
+
+/**
+ * Ensures that we've got a boot catalog file.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ */
+static int rtFsIsoMakerEnsureBootCatFile(PRTFSISOMAKERINT pThis)
+{
+ if (pThis->pBootCatFile)
+ return VINF_SUCCESS;
+
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ /* Create a VFS memory file for backing up the file. */
+ RTVFSFILE hVfsFile;
+ int rc = RTVfsMemFileCreate(NIL_RTVFSIOSTREAM, RTFSISOMAKER_SECTOR_SIZE, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ /* Create an unnamed VFS backed file and mark it as non-orphaned. */
+ PRTFSISOMAKERFILE pFile;
+ rc = rtFsIsoMakerAddUnnamedFileWorker(pThis, NULL, 0, &pFile);
+ if (RT_SUCCESS(rc))
+ {
+ pFile->enmSrcType = RTFSISOMAKERSRCTYPE_VFS_FILE;
+ pFile->u.hVfsFile = hVfsFile;
+ pFile->Core.cNotOrphan = 1;
+
+ /* Save file pointer and allocate a volume descriptor. */
+ pThis->pBootCatFile = pFile;
+ pThis->cVolumeDescriptors++;
+
+ return VINF_SUCCESS;
+ }
+ RTVfsFileRelease(hVfsFile);
+ }
+ return rc;
+}
+
+
+/**
+ * Queries the configuration index of the boot catalog file object.
+ *
+ * The boot catalog file is created as necessary, thus this have to be a query
+ * rather than a getter since object creation may fail.
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param pidxObj Where to return the configuration index.
+ */
+RTDECL(int) RTFsIsoMakerQueryObjIdxForBootCatalog(RTFSISOMAKER hIsoMaker, uint32_t *pidxObj)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtrReturn(pidxObj, VERR_INVALID_POINTER);
+ *pidxObj = UINT32_MAX;
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+
+ /*
+ * Do the job.
+ */
+ int rc = rtFsIsoMakerEnsureBootCatFile(pThis);
+ if (RT_SUCCESS(rc))
+ *pidxObj = pThis->pBootCatFile->Core.idxObj;
+ return rc;
+}
+
+
+/**
+ * Sets the boot catalog backing file.
+ *
+ * The content of the given file will be discarded and replaced with the boot
+ * catalog, the naming and file attributes (other than size) will be retained.
+ *
+ * This API exists mainly to assist when importing ISOs.
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param idxObj The configuration index of the file.
+ */
+RTDECL(int) RTFsIsoMakerBootCatSetFile(RTFSISOMAKER hIsoMaker, uint32_t idxObj)
+{
+ /*
+ * Validate and translate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+
+ PRTFSISOMAKEROBJ pObj = rtFsIsoMakerIndexToObj(pThis, idxObj);
+ AssertReturn(pObj, VERR_OUT_OF_RANGE);
+ AssertReturn(pObj->enmType == RTFSISOMAKEROBJTYPE_FILE, VERR_WRONG_TYPE);
+ PRTFSISOMAKERFILE pFile = (PRTFSISOMAKERFILE)pObj;
+ AssertReturn( pFile->enmSrcType == RTFSISOMAKERSRCTYPE_PATH
+ || pFile->enmSrcType == RTFSISOMAKERSRCTYPE_COMMON
+ || pFile->enmSrcType == RTFSISOMAKERSRCTYPE_VFS_FILE,
+ VERR_WRONG_TYPE);
+
+ /*
+ * To reduce the possible combinations here, make sure there is a boot cat
+ * file that we're "replacing".
+ */
+ int rc = rtFsIsoMakerEnsureBootCatFile(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Grab a reference to the boot cat memory VFS so we can destroy it
+ * later using regular destructors.
+ */
+ PRTFSISOMAKERFILE pOldFile = pThis->pBootCatFile;
+ RTVFSFILE hVfsFile = pOldFile->u.hVfsFile;
+ uint32_t cRefs = RTVfsFileRetain(hVfsFile);
+ if (cRefs != UINT32_MAX)
+ {
+ /*
+ * Try remove the existing boot file.
+ */
+ pOldFile->Core.cNotOrphan--;
+ pThis->pBootCatFile = NULL;
+ rc = rtFsIsoMakerObjRemoveWorker(pThis, &pOldFile->Core);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Just morph pFile into a boot catalog file.
+ */
+ if (pFile->enmSrcType == RTFSISOMAKERSRCTYPE_VFS_FILE)
+ {
+ RTVfsFileRelease(pFile->u.hVfsFile);
+ pFile->u.hVfsFile = NIL_RTVFSFILE;
+ }
+
+ pThis->cbData -= RT_ALIGN_64(pFile->cbData, RTFSISOMAKER_SECTOR_SIZE);
+ pFile->cbData = 0;
+ pFile->Core.cNotOrphan++;
+ pFile->enmSrcType = RTFSISOMAKERSRCTYPE_VFS_FILE;
+ pFile->u.hVfsFile = hVfsFile;
+
+ pThis->pBootCatFile = pFile;
+
+ return VINF_SUCCESS;
+ }
+
+ pThis->pBootCatFile = pOldFile;
+ pOldFile->Core.cNotOrphan++;
+ RTVfsFileRelease(hVfsFile);
+ }
+ else
+ rc = VERR_ISOMK_IPE_BOOT_CAT_FILE;
+ }
+ return rc;
+}
+
+
+/**
+ * Set the validation entry of the boot catalog (this is the first entry).
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param idPlatform The platform ID
+ * (ISO9660_ELTORITO_PLATFORM_ID_XXX).
+ * @param pszString CD/DVD-ROM identifier. Optional.
+ */
+RTDECL(int) RTFsIsoMakerBootCatSetValidationEntry(RTFSISOMAKER hIsoMaker, uint8_t idPlatform, const char *pszString)
+{
+ /*
+ * Validate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ size_t cchString = 0;
+ if (pszString)
+ {
+ cchString = RTStrCalcLatin1Len(pszString);
+ AssertReturn(cchString < RT_SIZEOFMEMB(ISO9660ELTORITOVALIDATIONENTRY, achId), VERR_OUT_OF_RANGE);
+ }
+
+ /*
+ * Make sure we've got a boot file.
+ */
+ int rc = rtFsIsoMakerEnsureBootCatFile(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Construct the entry data.
+ */
+ ISO9660ELTORITOVALIDATIONENTRY Entry;
+ Entry.bHeaderId = ISO9660_ELTORITO_HEADER_ID_VALIDATION_ENTRY;
+ Entry.bPlatformId = idPlatform;
+ Entry.u16Reserved = 0;
+ RT_ZERO(Entry.achId);
+ if (cchString)
+ {
+ char *pszTmp = Entry.achId;
+ rc = RTStrToLatin1Ex(pszString, RTSTR_MAX, &pszTmp, sizeof(Entry.achId), NULL);
+ AssertRC(rc);
+ }
+ Entry.u16Checksum = 0;
+ Entry.bKey1 = ISO9660_ELTORITO_KEY_BYTE_1;
+ Entry.bKey2 = ISO9660_ELTORITO_KEY_BYTE_2;
+
+ /* Calc checksum. */
+ uint16_t uSum = 0;
+ uint16_t const *pu16Src = (uint16_t const *)&Entry;
+ uint16_t cLeft = sizeof(Entry) / sizeof(uint16_t);
+ while (cLeft-- > 0)
+ {
+ uSum += RT_LE2H_U16(*pu16Src);
+ pu16Src++;
+ }
+ Entry.u16Checksum = RT_H2LE_U16((uint16_t)0 - uSum);
+
+ /*
+ * Write the entry and update our internal tracker.
+ */
+ rc = RTVfsFileWriteAt(pThis->pBootCatFile->u.hVfsFile, 0, &Entry, sizeof(Entry), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->aBootCatEntries[0].bType = ISO9660_ELTORITO_HEADER_ID_VALIDATION_ENTRY;
+ pThis->aBootCatEntries[0].cEntries = 2;
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Set the validation entry of the boot catalog (this is the first entry).
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param idxBootCat The boot catalog entry. Zero and two are
+ * invalid. Must be less than 63.
+ * @param idxImageObj The configuration index of the boot image.
+ * @param bBootMediaType The media type and flag (not for entry 1)
+ * (ISO9660_ELTORITO_BOOT_MEDIA_TYPE_XXX,
+ * ISO9660_ELTORITO_BOOT_MEDIA_F_XXX).
+ * @param bSystemType The partitiona table system ID.
+ * @param fBootable Whether it's a bootable entry or if we just want
+ * the BIOS to setup the emulation without booting
+ * it.
+ * @param uLoadSeg The load address divided by 0x10 (i.e. the real
+ * mode segment number).
+ * @param cSectorsToLoad Number of emulated sectors to load.
+ * @param bSelCritType The selection criteria type, if none pass
+ * ISO9660_ELTORITO_SEL_CRIT_TYPE_NONE.
+ * @param pvSelCritData Pointer to the selection criteria data.
+ * @param cbSelCritData Size of the selection criteria data.
+ */
+RTDECL(int) RTFsIsoMakerBootCatSetSectionEntry(RTFSISOMAKER hIsoMaker, uint32_t idxBootCat, uint32_t idxImageObj,
+ uint8_t bBootMediaType, uint8_t bSystemType, bool fBootable,
+ uint16_t uLoadSeg, uint16_t cSectorsToLoad,
+ uint8_t bSelCritType, void const *pvSelCritData, size_t cbSelCritData)
+{
+ /*
+ * Validate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ PRTFSISOMAKERFILE pFile = (PRTFSISOMAKERFILE)rtFsIsoMakerIndexToObj(pThis, idxImageObj);
+ AssertReturn(pFile, VERR_OUT_OF_RANGE);
+ AssertReturn((bBootMediaType & ISO9660_ELTORITO_BOOT_MEDIA_TYPE_MASK) <= ISO9660_ELTORITO_BOOT_MEDIA_TYPE_HARD_DISK,
+ VERR_INVALID_PARAMETER);
+ AssertReturn(!(bBootMediaType & ISO9660_ELTORITO_BOOT_MEDIA_F_MASK) || idxBootCat != 1,
+ VERR_INVALID_PARAMETER);
+
+ AssertReturn(idxBootCat != 0 && idxBootCat != 2 && idxBootCat < RT_ELEMENTS(pThis->aBootCatEntries) - 1U, VERR_OUT_OF_RANGE);
+
+ size_t cExtEntries = 0;
+ if (bSelCritType == ISO9660_ELTORITO_SEL_CRIT_TYPE_NONE)
+ AssertReturn(cbSelCritData == 0, VERR_INVALID_PARAMETER);
+ else
+ {
+ AssertReturn(idxBootCat > 2, VERR_INVALID_PARAMETER);
+ if (cbSelCritData > 0)
+ {
+ AssertPtrReturn(pvSelCritData, VERR_INVALID_POINTER);
+
+ if (cbSelCritData <= RT_SIZEOFMEMB(ISO9660ELTORITOSECTIONENTRY, abSelectionCriteria))
+ cExtEntries = 0;
+ else
+ {
+ cExtEntries = (cbSelCritData - RT_SIZEOFMEMB(ISO9660ELTORITOSECTIONENTRY, abSelectionCriteria)
+ + RT_SIZEOFMEMB(ISO9660ELTORITOSECTIONENTRYEXT, abSelectionCriteria) - 1)
+ / RT_SIZEOFMEMB(ISO9660ELTORITOSECTIONENTRYEXT, abSelectionCriteria);
+ AssertReturn(cExtEntries + 1 < RT_ELEMENTS(pThis->aBootCatEntries) - 1, VERR_TOO_MUCH_DATA);
+ }
+ }
+ }
+
+ /*
+ * Make sure we've got a boot file.
+ */
+ int rc = rtFsIsoMakerEnsureBootCatFile(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Construct the entry.
+ */
+ union
+ {
+ ISO9660ELTORITOSECTIONENTRY Entry;
+ ISO9660ELTORITOSECTIONENTRYEXT ExtEntry;
+ } u;
+ u.Entry.bBootIndicator = fBootable ? ISO9660_ELTORITO_BOOT_INDICATOR_BOOTABLE
+ : ISO9660_ELTORITO_BOOT_INDICATOR_NOT_BOOTABLE;
+ u.Entry.bBootMediaType = bBootMediaType;
+ u.Entry.uLoadSeg = RT_H2LE_U16(uLoadSeg);
+ u.Entry.bSystemType = cExtEntries == 0
+ ? bSystemType & ~ISO9660_ELTORITO_BOOT_MEDIA_F_CONTINUATION
+ : bSystemType | ISO9660_ELTORITO_BOOT_MEDIA_F_CONTINUATION;
+ u.Entry.bUnused = 0;
+ u.Entry.cEmulatedSectorsToLoad = RT_H2LE_U16(cSectorsToLoad);
+ u.Entry.offBootImage = 0;
+ u.Entry.bSelectionCriteriaType = bSelCritType;
+ RT_ZERO(u.Entry.abSelectionCriteria);
+ if (cbSelCritData > 0)
+ memcpy(u.Entry.abSelectionCriteria, pvSelCritData, RT_MIN(cbSelCritData, sizeof(u.Entry.abSelectionCriteria)));
+
+ /*
+ * Write it and update our internal tracker.
+ */
+ rc = RTVfsFileWriteAt(pThis->pBootCatFile->u.hVfsFile, ISO9660_ELTORITO_ENTRY_SIZE * idxBootCat,
+ &u.Entry, sizeof(u.Entry), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->aBootCatEntries[idxBootCat].pBootFile != pFile)
+ {
+ if (pThis->aBootCatEntries[idxBootCat].pBootFile)
+ pThis->aBootCatEntries[idxBootCat].pBootFile->Core.cNotOrphan--;
+ pFile->Core.cNotOrphan++;
+ pThis->aBootCatEntries[idxBootCat].pBootFile = pFile;
+ }
+
+ pThis->aBootCatEntries[idxBootCat].bType = u.Entry.bBootIndicator;
+ pThis->aBootCatEntries[idxBootCat].cEntries = 1;
+ }
+
+ /*
+ * Do add further extension entries with selection criteria.
+ */
+ if (cExtEntries)
+ {
+ uint8_t const *pbSrc = (uint8_t const *)pvSelCritData;
+ size_t cbSrc = cbSelCritData;
+ pbSrc += sizeof(u.Entry.abSelectionCriteria);
+ cbSrc -= sizeof(u.Entry.abSelectionCriteria);
+
+ while (cbSrc > 0)
+ {
+ u.ExtEntry.bExtensionId = ISO9660_ELTORITO_SECTION_ENTRY_EXT_ID;
+ if (cbSrc > sizeof(u.ExtEntry.abSelectionCriteria))
+ {
+ u.ExtEntry.fFlags = ISO9660_ELTORITO_SECTION_ENTRY_EXT_F_MORE;
+ memcpy(u.ExtEntry.abSelectionCriteria, pbSrc, sizeof(u.ExtEntry.abSelectionCriteria));
+ pbSrc += sizeof(u.ExtEntry.abSelectionCriteria);
+ cbSrc -= sizeof(u.ExtEntry.abSelectionCriteria);
+ }
+ else
+ {
+ u.ExtEntry.fFlags = 0;
+ RT_ZERO(u.ExtEntry.abSelectionCriteria);
+ memcpy(u.ExtEntry.abSelectionCriteria, pbSrc, cbSrc);
+ cbSrc = 0;
+ }
+
+ idxBootCat++;
+ rc = RTVfsFileWriteAt(pThis->pBootCatFile->u.hVfsFile, ISO9660_ELTORITO_ENTRY_SIZE * idxBootCat,
+ &u.Entry, sizeof(u.Entry), NULL);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* update the internal tracker. */
+ if (pThis->aBootCatEntries[idxBootCat].pBootFile)
+ {
+ pThis->aBootCatEntries[idxBootCat].pBootFile->Core.cNotOrphan--;
+ pThis->aBootCatEntries[idxBootCat].pBootFile = NULL;
+ }
+
+ pThis->aBootCatEntries[idxBootCat].bType = ISO9660_ELTORITO_SECTION_ENTRY_EXT_ID;
+ pThis->aBootCatEntries[idxBootCat].cEntries = 1;
+ }
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Set the validation entry of the boot catalog (this is the first entry).
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param idxBootCat The boot catalog entry.
+ * @param cEntries Number of entries in the section.
+ * @param idPlatform The platform ID
+ * (ISO9660_ELTORITO_PLATFORM_ID_XXX).
+ * @param pszString Section identifier or something. Optional.
+ */
+RTDECL(int) RTFsIsoMakerBootCatSetSectionHeaderEntry(RTFSISOMAKER hIsoMaker, uint32_t idxBootCat, uint32_t cEntries,
+ uint8_t idPlatform, const char *pszString)
+{
+ /*
+ * Validate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+
+ AssertReturn(idxBootCat >= 2 && idxBootCat < RT_ELEMENTS(pThis->aBootCatEntries) - 1U, VERR_OUT_OF_RANGE);
+ AssertReturn(cEntries < RT_ELEMENTS(pThis->aBootCatEntries) - 2U - 1U, VERR_OUT_OF_RANGE);
+ AssertReturn(idxBootCat + cEntries + 1 < RT_ELEMENTS(pThis->aBootCatEntries), VERR_OUT_OF_RANGE);
+
+ size_t cchString = 0;
+ if (pszString)
+ {
+ cchString = RTStrCalcLatin1Len(pszString);
+ AssertReturn(cchString < RT_SIZEOFMEMB(ISO9660ELTORITOVALIDATIONENTRY, achId), VERR_OUT_OF_RANGE);
+ }
+
+ /*
+ * Make sure we've got a boot file.
+ */
+ int rc = rtFsIsoMakerEnsureBootCatFile(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Construct the entry data.
+ */
+ ISO9660ELTORITOSECTIONHEADER Entry;
+ Entry.bHeaderId = ISO9660_ELTORITO_HEADER_ID_SECTION_HEADER;
+ Entry.bPlatformId = idPlatform;
+ Entry.cEntries = RT_H2LE_U16(cEntries);
+ RT_ZERO(Entry.achSectionId);
+ if (cchString)
+ {
+ char *pszTmp = Entry.achSectionId;
+ rc = RTStrToLatin1Ex(pszString, RTSTR_MAX, &pszTmp, sizeof(Entry.achSectionId), NULL);
+ AssertRC(rc);
+ }
+
+ /*
+ * Write the entry and update our internal tracker.
+ */
+ rc = RTVfsFileWriteAt(pThis->pBootCatFile->u.hVfsFile, ISO9660_ELTORITO_ENTRY_SIZE * idxBootCat,
+ &Entry, sizeof(Entry), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->aBootCatEntries[idxBootCat].pBootFile != NULL)
+ {
+ pThis->aBootCatEntries[idxBootCat].pBootFile->Core.cNotOrphan--;
+ pThis->aBootCatEntries[idxBootCat].pBootFile = NULL;
+ }
+
+ pThis->aBootCatEntries[idxBootCat].bType = ISO9660_ELTORITO_HEADER_ID_SECTION_HEADER;
+ pThis->aBootCatEntries[idxBootCat].cEntries = cEntries + 1;
+ }
+ }
+ return rc;
+}
+
+
+
+
+
+/*
+ *
+ * Image finalization.
+ * Image finalization.
+ * Image finalization.
+ *
+ */
+
+
+/**
+ * Remove any orphaned object from the disk.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ */
+static int rtFsIsoMakerFinalizeRemoveOrphans(PRTFSISOMAKERINT pThis)
+{
+ for (;;)
+ {
+ uint32_t cRemoved = 0;
+ PRTFSISOMAKEROBJ pCur;
+ PRTFSISOMAKEROBJ pNext;
+ RTListForEachSafe(&pThis->ObjectHead, pCur, pNext, RTFSISOMAKEROBJ, Entry)
+ {
+ if ( pCur->pPrimaryName
+ || pCur->pJolietName
+ || pCur->pUdfName
+ || pCur->pHfsName
+ || pCur->cNotOrphan > 0)
+ { /* likely */ }
+ else
+ {
+ Log4(("rtFsIsoMakerFinalizeRemoveOrphans: %#x cbData=%#RX64\n", pCur->idxObj,
+ pCur->enmType == RTFSISOMAKEROBJTYPE_FILE ? ((PRTFSISOMAKERFILE)(pCur))->cbData : 0));
+ int rc = rtFsIsoMakerObjRemoveWorker(pThis, pCur);
+ if (RT_SUCCESS(rc))
+ {
+ if (rc != VWRN_DANGLING_OBJECTS) /** */
+ cRemoved++;
+ }
+ else
+ return rc;
+ }
+ }
+ if (!cRemoved)
+ return VINF_SUCCESS;
+ }
+}
+
+
+/**
+ * Finalizes the El Torito boot stuff, part 1.
+ *
+ * This includes generating the boot catalog data and fixing the location of all
+ * related image files.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ */
+static int rtFsIsoMakerFinalizeBootStuffPart1(PRTFSISOMAKERINT pThis)
+{
+ /*
+ * Anything?
+ */
+ if (!pThis->pBootCatFile)
+ return VINF_SUCCESS;
+
+ /*
+ * Validate the boot catalog file.
+ */
+ AssertReturn(pThis->aBootCatEntries[0].bType == ISO9660_ELTORITO_HEADER_ID_VALIDATION_ENTRY,
+ VERR_ISOMK_BOOT_CAT_NO_VALIDATION_ENTRY);
+ AssertReturn(pThis->aBootCatEntries[1].pBootFile != NULL, VERR_ISOMK_BOOT_CAT_NO_DEFAULT_ENTRY);
+
+ /* Check any sections following the default one. */
+ uint32_t cEntries = 2;
+ while ( cEntries < RT_ELEMENTS(pThis->aBootCatEntries) - 1U
+ && pThis->aBootCatEntries[cEntries].cEntries > 0)
+ {
+ AssertReturn(pThis->aBootCatEntries[cEntries].bType == ISO9660_ELTORITO_HEADER_ID_SECTION_HEADER,
+ VERR_ISOMK_BOOT_CAT_EXPECTED_SECTION_HEADER);
+ for (uint32_t i = 1; i < pThis->aBootCatEntries[cEntries].cEntries; i++)
+ AssertReturn(pThis->aBootCatEntries[cEntries].pBootFile != NULL,
+ pThis->aBootCatEntries[cEntries].cEntries == 0
+ ? VERR_ISOMK_BOOT_CAT_EMPTY_ENTRY : VERR_ISOMK_BOOT_CAT_INVALID_SECTION_SIZE);
+ cEntries += pThis->aBootCatEntries[cEntries].cEntries;
+ }
+
+ /* Save for size setting. */
+ uint32_t const cEntriesInFile = cEntries + 1;
+
+ /* Check that the remaining entries are empty. */
+ while (cEntries < RT_ELEMENTS(pThis->aBootCatEntries))
+ {
+ AssertReturn(pThis->aBootCatEntries[cEntries].cEntries == 0, VERR_ISOMK_BOOT_CAT_ERRATIC_ENTRY);
+ cEntries++;
+ }
+
+ /*
+ * Fixate the size of the boot catalog file.
+ */
+ pThis->pBootCatFile->cbData = cEntriesInFile * ISO9660_ELTORITO_ENTRY_SIZE;
+ pThis->cbData += RT_ALIGN_32(cEntriesInFile * ISO9660_ELTORITO_ENTRY_SIZE, RTFSISOMAKER_SECTOR_SIZE);
+
+ /*
+ * Move up the boot images and boot catalog to the start of the image.
+ */
+ for (uint32_t i = RT_ELEMENTS(pThis->aBootCatEntries) - 2; i > 0; i--)
+ if (pThis->aBootCatEntries[i].pBootFile)
+ {
+ RTListNodeRemove(&pThis->aBootCatEntries[i].pBootFile->Core.Entry);
+ RTListPrepend(&pThis->ObjectHead, &pThis->aBootCatEntries[i].pBootFile->Core.Entry);
+ }
+
+ /* The boot catalog comes first. */
+ RTListNodeRemove(&pThis->pBootCatFile->Core.Entry);
+ RTListPrepend(&pThis->ObjectHead, &pThis->pBootCatFile->Core.Entry);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Finalizes the El Torito boot stuff, part 1.
+ *
+ * This includes generating the boot catalog data and fixing the location of all
+ * related image files.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ */
+static int rtFsIsoMakerFinalizeBootStuffPart2(PRTFSISOMAKERINT pThis)
+{
+ /*
+ * Anything?
+ */
+ if (!pThis->pBootCatFile)
+ return VINF_SUCCESS;
+
+ /*
+ * Fill in the descriptor.
+ */
+ PISO9660BOOTRECORDELTORITO pDesc = pThis->pElToritoDesc;
+ pDesc->Hdr.bDescType = ISO9660VOLDESC_TYPE_BOOT_RECORD;
+ pDesc->Hdr.bDescVersion = ISO9660PRIMARYVOLDESC_VERSION;
+ memcpy(pDesc->Hdr.achStdId, ISO9660VOLDESC_STD_ID, sizeof(pDesc->Hdr.achStdId));
+ memcpy(pDesc->achBootSystemId, RT_STR_TUPLE(ISO9660BOOTRECORDELTORITO_BOOT_SYSTEM_ID));
+ pDesc->offBootCatalog = RT_H2LE_U32((uint32_t)(pThis->pBootCatFile->offData / RTFSISOMAKER_SECTOR_SIZE));
+
+ /*
+ * Update the image file locations.
+ */
+ uint32_t cEntries = 2;
+ for (uint32_t i = 1; i < RT_ELEMENTS(pThis->aBootCatEntries) - 1; i++)
+ if (pThis->aBootCatEntries[i].pBootFile)
+ {
+ uint32_t off = pThis->aBootCatEntries[i].pBootFile->offData / RTFSISOMAKER_SECTOR_SIZE;
+ off = RT_H2LE_U32(off);
+ int rc = RTVfsFileWriteAt(pThis->pBootCatFile->u.hVfsFile,
+ i * ISO9660_ELTORITO_ENTRY_SIZE + RT_UOFFSETOF(ISO9660ELTORITOSECTIONENTRY, offBootImage),
+ &off, sizeof(off), NULL /*pcbWritten*/);
+ AssertRCReturn(rc, rc);
+ if (i == cEntries)
+ cEntries = i + 1;
+ }
+
+ /*
+ * Write end section.
+ */
+ ISO9660ELTORITOSECTIONHEADER Entry;
+ Entry.bHeaderId = ISO9660_ELTORITO_HEADER_ID_FINAL_SECTION_HEADER;
+ Entry.bPlatformId = ISO9660_ELTORITO_PLATFORM_ID_X86;
+ Entry.cEntries = 0;
+ RT_ZERO(Entry.achSectionId);
+ int rc = RTVfsFileWriteAt(pThis->pBootCatFile->u.hVfsFile, cEntries * ISO9660_ELTORITO_ENTRY_SIZE,
+ &Entry, sizeof(Entry), NULL /*pcbWritten*/);
+ AssertRCReturn(rc, rc);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Gathers the dirs for an ISO-9660 namespace (e.g. primary or joliet).
+ *
+ * @param pNamespace The namespace.
+ * @param pFinalizedDirs The finalized directory structure. The
+ * FinalizedDirs will be worked here.
+ */
+static void rtFsIsoMakerFinalizeGatherDirs(PRTFSISOMAKERNAMESPACE pNamespace, PRTFSISOMAKERFINALIZEDDIRS pFinalizedDirs)
+{
+ RTListInit(&pFinalizedDirs->FinalizedDirs);
+
+ /*
+ * Enter the root directory (if we got one).
+ */
+ if (!pNamespace->pRoot)
+ return;
+ PRTFSISOMAKERNAMEDIR pCurDir = pNamespace->pRoot->pDir;
+ RTListAppend(&pFinalizedDirs->FinalizedDirs, &pCurDir->FinalizedEntry);
+ do
+ {
+ /*
+ * Scan pCurDir and add directories. We don't need to sort anything
+ * here because the directory is already in path table compatible order.
+ */
+ uint32_t cLeft = pCurDir->cChildren;
+ PPRTFSISOMAKERNAME ppChild = pCurDir->papChildren;
+ while (cLeft-- > 0)
+ {
+ PRTFSISOMAKERNAME pChild = *ppChild++;
+ if (pChild->pDir)
+ RTListAppend(&pFinalizedDirs->FinalizedDirs, &pChild->pDir->FinalizedEntry);
+ }
+
+ /*
+ * Advance to the next directory.
+ */
+ pCurDir = RTListGetNext(&pFinalizedDirs->FinalizedDirs, pCurDir, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ } while (pCurDir);
+}
+
+
+/**
+ * Allocates space in the rock ridge spill file.
+ *
+ * @returns Spill file offset, UINT32_MAX on failure.
+ * @param pRRSpillFile The spill file.
+ * @param cbRock Number of bytes to allocate.
+ */
+static uint32_t rtFsIsoMakerFinalizeAllocRockRidgeSpill(PRTFSISOMAKERFILE pRRSpillFile, uint32_t cbRock)
+{
+ uint32_t off = pRRSpillFile->cbData;
+ if (ISO9660_SECTOR_SIZE - (pRRSpillFile->cbData & ISO9660_SECTOR_OFFSET_MASK) >= cbRock)
+ { /* likely */ }
+ else
+ {
+ off |= ISO9660_SECTOR_OFFSET_MASK;
+ off++;
+ AssertLogRelReturn(off > 0, UINT32_MAX);
+ pRRSpillFile->cbData = off;
+ }
+ pRRSpillFile->cbData += RT_ALIGN_32(cbRock, 4);
+ return off;
+}
+
+
+/**
+ * Finalizes a directory entry (i.e. namespace node).
+ *
+ * This calculates the directory record size.
+ *
+ * @returns IPRT status code.
+ * @param pFinalizedDirs .
+ * @param pName The directory entry to finalize.
+ * @param offInDir The offset in the directory of this record.
+ * @param uRockRidgeLevel This is the rock ridge level.
+ * @param fIsRoot Set if this is the root.
+ */
+static int rtFsIsoMakerFinalizeIsoDirectoryEntry(PRTFSISOMAKERFINALIZEDDIRS pFinalizedDirs, PRTFSISOMAKERNAME pName,
+ uint32_t offInDir, uint8_t uRockRidgeLevel, bool fIsRoot)
+{
+ /* Set directory and translation table offsets. (These are for
+ helping generating data blocks later.) */
+ pName->offDirRec = offInDir;
+
+ /* Calculate the minimal directory record size. */
+ size_t cbDirRec = RT_UOFFSETOF(ISO9660DIRREC, achFileId) + pName->cbNameInDirRec + !(pName->cbNameInDirRec & 1);
+ AssertReturn(cbDirRec <= UINT8_MAX, VERR_FILENAME_TOO_LONG);
+
+ pName->cbDirRec = (uint8_t)cbDirRec;
+ pName->cDirRecs = 1;
+ if (pName->pObj->enmType == RTFSISOMAKEROBJTYPE_FILE)
+ {
+ PRTFSISOMAKERFILE pFile = (PRTFSISOMAKERFILE)pName->pObj;
+ if (pFile->cbData > UINT32_MAX)
+ pName->cDirRecs = (pFile->cbData + RTFSISOMAKER_MAX_ISO9660_EXTENT_SIZE - 1) / RTFSISOMAKER_MAX_ISO9660_EXTENT_SIZE;
+ }
+
+ /*
+ * Calculate the size of the rock ridge bits we need.
+ */
+ if (uRockRidgeLevel > 0)
+ {
+ uint16_t cbRock = 0;
+ uint8_t fFlags = 0;
+
+ /* Level two starts with a 'RR' entry. */
+ if (uRockRidgeLevel >= 2)
+ cbRock += sizeof(ISO9660RRIPRR);
+
+ /* We always do 'PX' and 'TF' w/ 4 timestamps. */
+ cbRock += sizeof(ISO9660RRIPPX)
+ + RT_UOFFSETOF(ISO9660RRIPTF, abPayload) + 4 * sizeof(ISO9660RECTIMESTAMP);
+ fFlags |= ISO9660RRIP_RR_F_PX | ISO9660RRIP_RR_F_TF;
+
+ /* Devices needs 'PN'. */
+ if ( RTFS_IS_DEV_BLOCK(pName->pObj->fMode)
+ || RTFS_IS_DEV_CHAR(pName->pObj->fMode))
+ {
+ cbRock += sizeof(ISO9660RRIPPN);
+ fFlags |= ISO9660RRIP_RR_F_PN;
+ }
+
+ /* Usually we need a 'NM' entry too. */
+ if ( pName->pszRockRidgeNm != pName->szName
+ && pName->cchRockRidgeNm > 0
+ && ( pName->cbNameInDirRec != 1
+ || (uint8_t)pName->szName[0] > (uint8_t)0x01) ) /** @todo only root dir ever uses an ID byte here? [RR NM ./..] */
+ {
+ uint16_t cchNm = pName->cchRockRidgeNm;
+ while (cchNm > ISO9660RRIPNM_MAX_NAME_LEN)
+ {
+ cbRock += (uint16_t)RT_UOFFSETOF(ISO9660RRIPNM, achName) + ISO9660RRIPNM_MAX_NAME_LEN;
+ cchNm -= ISO9660RRIPNM_MAX_NAME_LEN;
+ }
+ cbRock += (uint16_t)RT_UOFFSETOF(ISO9660RRIPNM, achName) + cchNm;
+ fFlags |= ISO9660RRIP_RR_F_NM;
+ }
+
+ /* Symbolic links needs a 'SL' entry. */
+ if (pName->pObj->enmType == RTFSISOMAKEROBJTYPE_SYMLINK)
+ {
+ PRTFSISOMAKERSYMLINK pSymlink = (PRTFSISOMAKERSYMLINK)pName->pObj;
+ cbRock += pSymlink->cbSlRockRidge;
+ fFlags |= ISO9660RRIP_RR_F_SL;
+ }
+
+ /*
+ * Decide where stuff goes. The '.' record of the root dir is special.
+ */
+ pName->fRockEntries = fFlags;
+ if (!fIsRoot)
+ {
+ if (pName->cbDirRec + cbRock < UINT8_MAX)
+ {
+ pName->cbRockInDirRec = cbRock;
+ pName->cbRockSpill = 0;
+ pName->fRockNeedRRInDirRec = uRockRidgeLevel >= 2;
+ pName->fRockNeedRRInSpill = false;
+ }
+ else if (pName->cbDirRec + sizeof(ISO9660SUSPCE) < UINT8_MAX)
+ {
+ /* Try fit the 'RR' entry in the directory record, but don't bother with anything else. */
+ if (uRockRidgeLevel >= 2 && pName->cbDirRec + sizeof(ISO9660SUSPCE) + sizeof(ISO9660RRIPRR) < UINT8_MAX)
+ {
+ pName->cbRockInDirRec = (uint16_t)(sizeof(ISO9660SUSPCE) + sizeof(ISO9660RRIPRR));
+ cbRock -= sizeof(ISO9660RRIPRR);
+ pName->cbRockSpill = cbRock;
+ pName->fRockNeedRRInDirRec = true;
+ pName->fRockNeedRRInSpill = false;
+ }
+ else
+ {
+ pName->cbRockInDirRec = (uint16_t)sizeof(ISO9660SUSPCE);
+ pName->cbRockSpill = cbRock;
+ pName->fRockNeedRRInDirRec = false;
+ pName->fRockNeedRRInSpill = uRockRidgeLevel >= 2;
+ }
+ pName->offRockSpill = rtFsIsoMakerFinalizeAllocRockRidgeSpill(pFinalizedDirs->pRRSpillFile, cbRock);
+ AssertReturn(pName->offRockSpill != UINT32_MAX, VERR_ISOMK_RR_SPILL_FILE_FULL);
+ }
+ else
+ {
+ LogRel(("RTFsIsoMaker: no space for 'CE' entry: cbDirRec=%#x bytes, name=%s (%#x bytes)\n",
+ pName->cbDirRec, pName->szName, pName->cbNameInDirRec));
+ return VERR_ISOMK_RR_NO_SPACE_FOR_CE;
+ }
+ }
+ else
+ {
+ /* The root starts with a 'SP' record to indicate that SUSP is being used,
+ this is always in the directory record. If we add a 'ER' record (big) too,
+ we put all but 'SP' and 'ER' in the spill file too keep things simple. */
+ if (uRockRidgeLevel < 2)
+ {
+ Assert(!(fFlags & (ISO9660RRIP_RR_F_NM | ISO9660RRIP_RR_F_SL | ISO9660RRIP_RR_F_CL | ISO9660RRIP_RR_F_PL | ISO9660RRIP_RR_F_RE)));
+ cbRock += sizeof(ISO9660SUSPSP);
+ Assert(pName->cbDirRec + cbRock < UINT8_MAX);
+ pName->cbRockInDirRec = cbRock;
+ pName->cbRockSpill = 0;
+ pName->fRockNeedER = false;
+ pName->fRockNeedRRInDirRec = false;
+ pName->fRockNeedRRInSpill = false;
+ }
+ else
+ {
+ pName->cbRockInDirRec = (uint16_t)(sizeof(ISO9660SUSPSP) + sizeof(ISO9660SUSPCE));
+ pName->fRockNeedER = true;
+ pName->fRockNeedRRInSpill = true;
+ pName->fRockNeedRRInDirRec = false;
+ cbRock += ISO9660_RRIP_ER_LEN;
+ pName->cbRockSpill = cbRock;
+ pName->offRockSpill = rtFsIsoMakerFinalizeAllocRockRidgeSpill(pFinalizedDirs->pRRSpillFile, cbRock);
+ }
+ }
+ pName->cbDirRec += pName->cbRockInDirRec + (pName->cbRockInDirRec & 1);
+ Assert(pName->cbDirRec < UINT8_MAX);
+ }
+
+ pName->cbDirRecTotal = pName->cbDirRec * pName->cDirRecs;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Finalizes either a primary and secondary ISO namespace.
+ *
+ * @returns IPRT status code
+ * @param pThis The ISO maker instance.
+ * @param pNamespace The namespace.
+ * @param pFinalizedDirs The finalized directories structure for the
+ * namespace.
+ * @param poffData The data offset. We will allocate blocks for the
+ * directories and the path tables.
+ */
+static int rtFsIsoMakerFinalizeDirectoriesInIsoNamespace(PRTFSISOMAKERINT pThis, PRTFSISOMAKERNAMESPACE pNamespace,
+ PRTFSISOMAKERFINALIZEDDIRS pFinalizedDirs, uint64_t *poffData)
+{
+ int rc;
+
+ /* The directory data comes first, so take down it's offset. */
+ pFinalizedDirs->offDirs = *poffData;
+
+ /*
+ * Reset the rock ridge spill file (in case we allow finalizing more than once)
+ * and create a new spill file if rock ridge is enabled. The directory entry
+ * finalize function uses this as a clue that rock ridge is enabled.
+ */
+ if (pFinalizedDirs->pRRSpillFile)
+ {
+ pFinalizedDirs->pRRSpillFile->Core.cNotOrphan = 0;
+ rtFsIsoMakerObjRemoveWorker(pThis, &pFinalizedDirs->pRRSpillFile->Core);
+ pFinalizedDirs->pRRSpillFile = NULL;
+ }
+ if (pNamespace->uRockRidgeLevel > 0)
+ {
+ rc = rtFsIsoMakerAddUnnamedFileWorker(pThis, NULL, 0, &pFinalizedDirs->pRRSpillFile);
+ AssertRCReturn(rc, rc);
+ pFinalizedDirs->pRRSpillFile->enmSrcType = RTFSISOMAKERSRCTYPE_RR_SPILL;
+ pFinalizedDirs->pRRSpillFile->u.pRockSpillNamespace = pNamespace;
+ pFinalizedDirs->pRRSpillFile->Core.cNotOrphan = 1;
+ }
+
+ uint16_t idPathTable = 1;
+ uint32_t cbPathTable = 0;
+ if (pNamespace->pRoot)
+ {
+ /*
+ * Precalc the directory record size for the root directory.
+ */
+ rc = rtFsIsoMakerFinalizeIsoDirectoryEntry(pFinalizedDirs, pNamespace->pRoot, 0 /*offInDir*/,
+ pNamespace->uRockRidgeLevel, true /*fIsRoot*/);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Work thru the directories.
+ */
+ PRTFSISOMAKERNAMEDIR pCurDir;
+ RTListForEach(&pFinalizedDirs->FinalizedDirs, pCurDir, RTFSISOMAKERNAMEDIR, FinalizedEntry)
+ {
+ PRTFSISOMAKERNAME pCurName = pCurDir->pName;
+ PRTFSISOMAKERNAME pParentName = pCurName->pParent ? pCurName->pParent : pCurName;
+
+ /* We don't do anything special for the special '.' and '..' directory
+ entries, instead we use the directory entry in the parent directory
+ with a 1 byte name (00 or 01). */
+ /** @todo r=bird: This causes trouble with RR NM records, since we'll be
+ * emitting the real directory name rather than '.' or '..' (or
+ * whatever we should be emitting for these two special dirs).
+ * FreeBSD got confused with this. The RTFSISOMAKERDIRTYPE stuff is a
+ * workaround for this, however it doesn't hold up if we have to use
+ * the spill file. [RR NM ./..] */
+ Assert(pCurName->cbDirRec != 0);
+ Assert(pParentName->cbDirRec != 0);
+ pCurDir->cbDirRec00 = pCurName->cbDirRec - pCurName->cbNameInDirRec - !(pCurName->cbNameInDirRec & 1) + 1;
+ pCurDir->cbDirRec01 = pParentName->cbDirRec - pParentName->cbNameInDirRec - !(pParentName->cbNameInDirRec & 1) + 1;
+
+ uint32_t offInDir = (uint32_t)pCurDir->cbDirRec00 + pCurDir->cbDirRec01;
+
+ /* Finalize the directory entries. */
+ uint32_t cSubDirs = 0;
+ uint32_t cbTransTbl = 0;
+ uint32_t cLeft = pCurDir->cChildren;
+ PPRTFSISOMAKERNAME ppChild = pCurDir->papChildren;
+ while (cLeft-- > 0)
+ {
+ PRTFSISOMAKERNAME pChild = *ppChild++;
+ rc = rtFsIsoMakerFinalizeIsoDirectoryEntry(pFinalizedDirs, pChild, offInDir,
+ pNamespace->uRockRidgeLevel, false /*fIsRoot*/);
+ AssertRCReturn(rc, rc);
+
+ if ((RTFSISOMAKER_SECTOR_SIZE - (offInDir & RTFSISOMAKER_SECTOR_OFFSET_MASK)) < pChild->cbDirRecTotal)
+ {
+ Assert(ppChild[-1] == pChild && &ppChild[-1] != pCurDir->papChildren);
+ if ( pChild->cDirRecs == 1
+ || pChild->cDirRecs <= RTFSISOMAKER_SECTOR_SIZE / pChild->cbDirRec)
+ {
+ ppChild[-2]->cbDirRecTotal += RTFSISOMAKER_SECTOR_SIZE - (offInDir & RTFSISOMAKER_SECTOR_OFFSET_MASK);
+ offInDir = (offInDir | RTFSISOMAKER_SECTOR_OFFSET_MASK) + 1; /* doesn't fit, skip to next sector. */
+ Log4(("rtFsIsoMakerFinalizeDirectoriesInIsoNamespace: zero padding dir rec @%#x: %#x -> %#x; offset %#x -> %#x\n",
+ ppChild[-2]->offDirRec, ppChild[-2]->cbDirRec, ppChild[-2]->cbDirRecTotal, pChild->offDirRec, offInDir));
+ pChild->offDirRec = offInDir;
+ }
+ /* else: too complicated and ulikely, so whatever. */
+ }
+
+ offInDir += pChild->cbDirRecTotal;
+ if (pChild->cchTransNm)
+ cbTransTbl += 2 /* type & space*/
+ + RT_MAX(pChild->cchName, RTFSISOMAKER_TRANS_TBL_LEFT_PAD)
+ + 1 /* tab */
+ + pChild->cchTransNm
+ + 1 /* newline */;
+
+ if (RTFS_IS_DIRECTORY(pChild->fMode))
+ cSubDirs++;
+ }
+
+ /* Set the directory size and location, advancing the data offset. */
+ pCurDir->cbDir = offInDir;
+ pCurDir->offDir = *poffData;
+ *poffData += RT_ALIGN_32(offInDir, RTFSISOMAKER_SECTOR_SIZE);
+
+ /* Set the translation table file size. */
+ if (pCurDir->pTransTblFile)
+ {
+ pCurDir->pTransTblFile->cbData = cbTransTbl;
+ pThis->cbData += RT_ALIGN_32(cbTransTbl, RTFSISOMAKER_SECTOR_SIZE);
+ }
+
+ /* Add to the path table size calculation. */
+ pCurDir->offPathTable = cbPathTable;
+ pCurDir->idPathTable = idPathTable++;
+ cbPathTable += RTFSISOMAKER_CALC_PATHREC_SIZE(pCurName->cbNameInDirRec);
+
+ /* Set the hardlink count. */
+ pCurName->cHardlinks = cSubDirs + 2;
+
+ Log4(("rtFsIsoMakerFinalizeDirectoriesInIsoNamespace: idxObj=#%#x cbDir=%#08x cChildren=%#05x %s\n",
+ pCurDir->pName->pObj->idxObj, pCurDir->cbDir, pCurDir->cChildren, pCurDir->pName->szName));
+ }
+ }
+
+ /*
+ * Remove rock ridge spill file if we haven't got any spill.
+ * If we have, round the size up to a whole sector to avoid the slow path
+ * when reading from it.
+ */
+ if (pFinalizedDirs->pRRSpillFile)
+ {
+ if (pFinalizedDirs->pRRSpillFile->cbData > 0)
+ {
+ pFinalizedDirs->pRRSpillFile->cbData = RT_ALIGN_64(pFinalizedDirs->pRRSpillFile->cbData, ISO9660_SECTOR_SIZE);
+ pThis->cbData += pFinalizedDirs->pRRSpillFile->cbData;
+ }
+ else
+ {
+ rc = rtFsIsoMakerObjRemoveWorker(pThis, &pFinalizedDirs->pRRSpillFile->Core);
+ if (RT_SUCCESS(rc))
+ pFinalizedDirs->pRRSpillFile = NULL;
+ }
+ }
+
+ /*
+ * Calculate the path table offsets and move past them.
+ */
+ pFinalizedDirs->cbPathTable = cbPathTable;
+ pFinalizedDirs->offPathTableL = *poffData;
+ *poffData += RT_ALIGN_64(cbPathTable, RTFSISOMAKER_SECTOR_SIZE);
+
+ pFinalizedDirs->offPathTableM = *poffData;
+ *poffData += RT_ALIGN_64(cbPathTable, RTFSISOMAKER_SECTOR_SIZE);
+
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * Finalizes directories and related stuff.
+ *
+ * This will not generate actual directory data, but calculate the size of it
+ * once it's generated. Ditto for the path tables. The exception is the rock
+ * ridge spill file, which will be generated in memory.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ * @param poffData The data offset (in/out).
+ */
+static int rtFsIsoMakerFinalizeDirectories(PRTFSISOMAKERINT pThis, uint64_t *poffData)
+{
+ /*
+ * Locate the directories, width first, inserting them in the finalized lists so
+ * we can process them efficiently.
+ */
+ rtFsIsoMakerFinalizeGatherDirs(&pThis->PrimaryIso, &pThis->PrimaryIsoDirs);
+ rtFsIsoMakerFinalizeGatherDirs(&pThis->Joliet, &pThis->JolietDirs);
+
+ /*
+ * Process the primary ISO and joliet namespaces.
+ */
+ int rc = rtFsIsoMakerFinalizeDirectoriesInIsoNamespace(pThis, &pThis->PrimaryIso, &pThis->PrimaryIsoDirs, poffData);
+ if (RT_SUCCESS(rc))
+ rc = rtFsIsoMakerFinalizeDirectoriesInIsoNamespace(pThis, &pThis->Joliet, &pThis->JolietDirs, poffData);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Later: UDF, HFS.
+ */
+ }
+ return rc;
+}
+
+
+/**
+ * Finalizes data allocations.
+ *
+ * This will set the RTFSISOMAKERFILE::offData members.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ * @param poffData The data offset (in/out).
+ */
+static int rtFsIsoMakerFinalizeData(PRTFSISOMAKERINT pThis, uint64_t *poffData)
+{
+ pThis->offFirstFile = *poffData;
+
+ /*
+ * We currently does not have any ordering prioritizing implemented, so we
+ * just store files in the order they were added.
+ */
+ PRTFSISOMAKEROBJ pCur;
+ RTListForEach(&pThis->ObjectHead, pCur, RTFSISOMAKEROBJ, Entry)
+ {
+ if (pCur->enmType == RTFSISOMAKEROBJTYPE_FILE)
+ {
+ PRTFSISOMAKERFILE pCurFile = (PRTFSISOMAKERFILE)pCur;
+ if (pCurFile->offData == UINT64_MAX)
+ {
+ pCurFile->offData = *poffData;
+ *poffData += RT_ALIGN_64(pCurFile->cbData, RTFSISOMAKER_SECTOR_SIZE);
+ RTListAppend(&pThis->FinalizedFiles, &pCurFile->FinalizedEntry);
+ Log4(("rtFsIsoMakerFinalizeData: %#x @%#RX64 cbData=%#RX64\n", pCurFile->Core.idxObj, pCurFile->offData, pCurFile->cbData));
+ }
+
+ /*
+ * Create the boot info table.
+ */
+ if (pCurFile->pBootInfoTable)
+ {
+ /*
+ * Checksum the file.
+ */
+ int rc;
+ RTVFSFILE hVfsFile;
+ uint64_t offBase;
+ switch (pCurFile->enmSrcType)
+ {
+ case RTFSISOMAKERSRCTYPE_PATH:
+ rc = RTVfsChainOpenFile(pCurFile->u.pszSrcPath, RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ &hVfsFile, NULL, NULL);
+ AssertMsgRCReturn(rc, ("%s -> %Rrc\n", pCurFile->u.pszSrcPath, rc), rc);
+ offBase = 0;
+ break;
+ case RTFSISOMAKERSRCTYPE_VFS_FILE:
+ hVfsFile = pCurFile->u.hVfsFile;
+ offBase = 0;
+ rc = VINF_SUCCESS;
+ break;
+ case RTFSISOMAKERSRCTYPE_COMMON:
+ hVfsFile = pThis->paCommonSources[pCurFile->u.Common.idxSrc];
+ offBase = pCurFile->u.Common.offData;
+ rc = VINF_SUCCESS;
+ break;
+ default:
+ AssertMsgFailedReturn(("enmSrcType=%d\n", pCurFile->enmSrcType), VERR_IPE_NOT_REACHED_DEFAULT_CASE);
+ }
+
+ uint32_t uChecksum = 0;
+ uint32_t off = 64;
+ uint32_t cbLeft = RT_MAX(64, (uint32_t)pCurFile->cbData) - 64;
+ while (cbLeft > 0)
+ {
+ union
+ {
+ uint8_t ab[_16K];
+ uint32_t au32[_16K / sizeof(uint32_t)];
+ } uBuf;
+ uint32_t cbRead = RT_MIN(sizeof(uBuf), cbLeft);
+ if (cbRead & 3)
+ RT_ZERO(uBuf);
+ rc = RTVfsFileReadAt(hVfsFile, offBase + off, &uBuf, cbRead, NULL);
+ if (RT_FAILURE(rc))
+ break;
+
+ size_t i = RT_ALIGN_Z(cbRead, sizeof(uint32_t)) / sizeof(uint32_t);
+ while (i-- > 0)
+ uChecksum += RT_LE2H_U32(uBuf.au32[i]);
+
+ off += cbRead;
+ cbLeft -= cbRead;
+ }
+
+ if (pCurFile->enmSrcType == RTFSISOMAKERSRCTYPE_PATH)
+ RTVfsFileRelease(hVfsFile);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Populate the structure.
+ */
+ pCurFile->pBootInfoTable->offPrimaryVolDesc = RT_H2LE_U32(16);
+ pCurFile->pBootInfoTable->offBootFile = RT_H2LE_U32((uint32_t)(pCurFile->offData / RTFSISOMAKER_SECTOR_SIZE));
+ pCurFile->pBootInfoTable->cbBootFile = RT_H2LE_U32((uint32_t)pCurFile->cbData);
+ pCurFile->pBootInfoTable->uChecksum = RT_H2LE_U32(uChecksum);
+ RT_ZERO(pCurFile->pBootInfoTable->auReserved);
+ }
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Copies the given string as UTF-16 and pad unused space in the destination
+ * with spaces.
+ *
+ * @param pachDst The destination field. C type is char, but real life
+ * type is UTF-16 / UCS-2.
+ * @param cchDst The size of the destination field.
+ * @param pszSrc The source string. NULL is treated like empty string.
+ */
+static void rtFsIsoMakerFinalizeCopyAsUtf16BigAndSpacePad(char *pachDst, size_t cchDst, const char *pszSrc)
+{
+ size_t cwcSrc = 0;
+ if (pszSrc)
+ {
+ RTUTF16 wszSrc[256];
+ PRTUTF16 pwszSrc = wszSrc;
+ int rc = RTStrToUtf16BigEx(pszSrc, RTSTR_MAX, &pwszSrc, RT_ELEMENTS(wszSrc), &cwcSrc);
+ AssertRCStmt(rc, cwcSrc = 0);
+
+ if (cwcSrc > cchDst / sizeof(RTUTF16))
+ cwcSrc = cchDst / sizeof(RTUTF16);
+ memcpy(pachDst, wszSrc, cwcSrc * sizeof(RTUTF16));
+ }
+
+ /* Space padding. Note! cchDst can be an odd number. */
+ size_t cchWritten = cwcSrc * sizeof(RTUTF16);
+ if (cchWritten < cchDst)
+ {
+ while (cchWritten + 2 <= cchDst)
+ {
+ pachDst[cchWritten++] = '\0';
+ pachDst[cchWritten++] = ' ';
+ }
+ if (cchWritten < cchDst)
+ pachDst[cchWritten] = '\0';
+ }
+}
+
+
+/**
+ * Copies the given string and pad unused space in the destination with spaces.
+ *
+ * @param pachDst The destination field.
+ * @param cchDst The size of the destination field.
+ * @param pszSrc The source string. NULL is treated like empty string.
+ */
+static void rtFsIsoMakerFinalizeCopyAndSpacePad(char *pachDst, size_t cchDst, const char *pszSrc)
+{
+ size_t cchSrc;
+ if (!pszSrc)
+ cchSrc = 0;
+ else
+ {
+ cchSrc = strlen(pszSrc);
+ if (cchSrc > cchDst)
+ cchSrc = cchDst;
+ memcpy(pachDst, pszSrc, cchSrc);
+ }
+ if (cchSrc < cchDst)
+ memset(&pachDst[cchSrc], ' ', cchDst - cchSrc);
+}
+
+
+/**
+ * Formats a timespec as an ISO-9660 ascii timestamp.
+ *
+ * @param pTime The timespec to format.
+ * @param pIsoTs The ISO-9660 timestamp destination buffer.
+ */
+static void rtFsIsoMakerTimespecToIso9660Timestamp(PCRTTIMESPEC pTime, PISO9660TIMESTAMP pIsoTs)
+{
+ RTTIME Exploded;
+ RTTimeExplode(&Exploded, pTime);
+
+ char szTmp[64];
+#define FORMAT_FIELD(a_achDst, a_uSrc) \
+ do { \
+ RTStrFormatU32(szTmp, sizeof(szTmp), a_uSrc, 10, sizeof(a_achDst), sizeof(a_achDst), \
+ RTSTR_F_ZEROPAD | RTSTR_F_WIDTH | RTSTR_F_PRECISION); \
+ memcpy(a_achDst, szTmp, sizeof(a_achDst)); \
+ } while (0)
+ FORMAT_FIELD(pIsoTs->achYear, Exploded.i32Year);
+ FORMAT_FIELD(pIsoTs->achMonth, Exploded.u8Month);
+ FORMAT_FIELD(pIsoTs->achDay, Exploded.u8MonthDay);
+ FORMAT_FIELD(pIsoTs->achHour, Exploded.u8Hour);
+ FORMAT_FIELD(pIsoTs->achMinute, Exploded.u8Minute);
+ FORMAT_FIELD(pIsoTs->achSecond, Exploded.u8Second);
+ FORMAT_FIELD(pIsoTs->achCentisecond, Exploded.u32Nanosecond / RT_NS_10MS);
+#undef FORMAT_FIELD
+ pIsoTs->offUtc = 0;
+}
+
+/**
+ * Formats zero ISO-9660 ascii timestamp (treated as not specified).
+ *
+ * @param pIsoTs The ISO-9660 timestamp destination buffer.
+ */
+static void rtFsIsoMakerZero9660Timestamp(PISO9660TIMESTAMP pIsoTs)
+{
+ memset(pIsoTs, '0', RT_UOFFSETOF(ISO9660TIMESTAMP, offUtc));
+ pIsoTs->offUtc = 0;
+}
+
+
+/**
+ * Formats a timespec as an ISO-9660 record timestamp.
+ *
+ * @param pTime The timespec to format.
+ * @param pIsoTs The ISO-9660 timestamp destination buffer.
+ */
+static void rtFsIsoMakerTimespecToIso9660RecTimestamp(PCRTTIMESPEC pTime, PISO9660RECTIMESTAMP pIsoRecTs)
+{
+ RTTIME Exploded;
+ RTTimeExplode(&Exploded, pTime);
+
+ pIsoRecTs->bYear = Exploded.i32Year >= 1900 ? Exploded.i32Year - 1900 : 0;
+ pIsoRecTs->bMonth = Exploded.u8Month;
+ pIsoRecTs->bDay = Exploded.u8MonthDay;
+ pIsoRecTs->bHour = Exploded.u8Hour;
+ pIsoRecTs->bMinute = Exploded.u8Minute;
+ pIsoRecTs->bSecond = Exploded.u8Second;
+ pIsoRecTs->offUtc = 0;
+}
+
+
+/**
+ * Allocate and prepare the volume descriptors.
+ *
+ * What's not done here gets done later by rtFsIsoMakerFinalizeBootStuffPart2,
+ * or at teh very end of the finalization by
+ * rtFsIsoMakerFinalizeVolumeDescriptors.
+ *
+ * @returns IPRT status code
+ * @param pThis The ISO maker instance.
+ */
+static int rtFsIsoMakerFinalizePrepVolumeDescriptors(PRTFSISOMAKERINT pThis)
+{
+ /*
+ * Allocate and calc pointers.
+ */
+ RTMemFree(pThis->pbVolDescs);
+ pThis->pbVolDescs = (uint8_t *)RTMemAllocZ(pThis->cVolumeDescriptors * RTFSISOMAKER_SECTOR_SIZE);
+ AssertReturn(pThis->pbVolDescs, VERR_NO_MEMORY);
+
+ uint32_t offVolDescs = 0;
+
+ pThis->pPrimaryVolDesc = (PISO9660PRIMARYVOLDESC)&pThis->pbVolDescs[offVolDescs];
+ offVolDescs += RTFSISOMAKER_SECTOR_SIZE;
+
+ if (!pThis->pBootCatFile)
+ pThis->pElToritoDesc = NULL;
+ else
+ {
+ pThis->pElToritoDesc = (PISO9660BOOTRECORDELTORITO)&pThis->pbVolDescs[offVolDescs];
+ offVolDescs += RTFSISOMAKER_SECTOR_SIZE;
+ }
+
+ if (!pThis->Joliet.uLevel)
+ pThis->pJolietVolDesc = NULL;
+ else
+ {
+ pThis->pJolietVolDesc = (PISO9660SUPVOLDESC)&pThis->pbVolDescs[offVolDescs];
+ offVolDescs += RTFSISOMAKER_SECTOR_SIZE;
+ }
+
+ pThis->pTerminatorVolDesc = (PISO9660VOLDESCHDR)&pThis->pbVolDescs[offVolDescs];
+ offVolDescs += RTFSISOMAKER_SECTOR_SIZE;
+
+ if (pThis->Udf.uLevel > 0)
+ {
+ /** @todo UDF descriptors. */
+ }
+ AssertReturn(offVolDescs == pThis->cVolumeDescriptors * RTFSISOMAKER_SECTOR_SIZE, VERR_ISOMK_IPE_DESC_COUNT);
+
+ /*
+ * This may be needed later.
+ */
+ char szImageCreationTime[42];
+ RTTimeSpecToString(&pThis->ImageCreationTime, szImageCreationTime, sizeof(szImageCreationTime));
+
+ /*
+ * Initialize the primary descriptor.
+ */
+ PISO9660PRIMARYVOLDESC pPrimary = pThis->pPrimaryVolDesc;
+
+ pPrimary->Hdr.bDescType = ISO9660VOLDESC_TYPE_PRIMARY;
+ pPrimary->Hdr.bDescVersion = ISO9660PRIMARYVOLDESC_VERSION;
+ memcpy(pPrimary->Hdr.achStdId, ISO9660VOLDESC_STD_ID, sizeof(pPrimary->Hdr.achStdId));
+ //pPrimary->bPadding8 = 0;
+ rtFsIsoMakerFinalizeCopyAndSpacePad(pPrimary->achSystemId, sizeof(pPrimary->achSystemId), pThis->PrimaryIso.pszSystemId);
+ rtFsIsoMakerFinalizeCopyAndSpacePad(pPrimary->achVolumeId, sizeof(pPrimary->achVolumeId),
+ pThis->PrimaryIso.pszVolumeId ? pThis->PrimaryIso.pszVolumeId : szImageCreationTime);
+ //pPrimary->Unused73 = {0}
+ //pPrimary->VolumeSpaceSize = later
+ //pPrimary->abUnused89 = {0}
+ pPrimary->cVolumesInSet.be = RT_H2BE_U16_C(1);
+ pPrimary->cVolumesInSet.le = RT_H2LE_U16_C(1);
+ pPrimary->VolumeSeqNo.be = RT_H2BE_U16_C(1);
+ pPrimary->VolumeSeqNo.le = RT_H2LE_U16_C(1);
+ pPrimary->cbLogicalBlock.be = RT_H2BE_U16_C(RTFSISOMAKER_SECTOR_SIZE);
+ pPrimary->cbLogicalBlock.le = RT_H2LE_U16_C(RTFSISOMAKER_SECTOR_SIZE);
+ //pPrimary->cbPathTable = later
+ //pPrimary->offTypeLPathTable = later
+ //pPrimary->offOptionalTypeLPathTable = {0}
+ //pPrimary->offTypeMPathTable = later
+ //pPrimary->offOptionalTypeMPathTable = {0}
+ //pPrimary->RootDir = later
+ rtFsIsoMakerFinalizeCopyAndSpacePad(pPrimary->achVolumeSetId, sizeof(pPrimary->achVolumeSetId),
+ pThis->PrimaryIso.pszVolumeSetId);
+ rtFsIsoMakerFinalizeCopyAndSpacePad(pPrimary->achPublisherId, sizeof(pPrimary->achPublisherId),
+ pThis->PrimaryIso.pszPublisherId);
+ rtFsIsoMakerFinalizeCopyAndSpacePad(pPrimary->achDataPreparerId, sizeof(pPrimary->achDataPreparerId),
+ pThis->PrimaryIso.pszDataPreparerId);
+ rtFsIsoMakerFinalizeCopyAndSpacePad(pPrimary->achApplicationId, sizeof(pPrimary->achApplicationId),
+ pThis->PrimaryIso.pszApplicationId);
+ rtFsIsoMakerFinalizeCopyAndSpacePad(pPrimary->achCopyrightFileId, sizeof(pPrimary->achCopyrightFileId),
+ pThis->PrimaryIso.pszCopyrightFileId);
+ rtFsIsoMakerFinalizeCopyAndSpacePad(pPrimary->achAbstractFileId, sizeof(pPrimary->achAbstractFileId),
+ pThis->PrimaryIso.pszAbstractFileId);
+ rtFsIsoMakerFinalizeCopyAndSpacePad(pPrimary->achBibliographicFileId, sizeof(pPrimary->achBibliographicFileId),
+ pThis->PrimaryIso.pszBibliographicFileId);
+ rtFsIsoMakerTimespecToIso9660Timestamp(&pThis->ImageCreationTime, &pPrimary->BirthTime);
+ rtFsIsoMakerTimespecToIso9660Timestamp(&pThis->ImageCreationTime, &pPrimary->ModifyTime);
+ rtFsIsoMakerZero9660Timestamp(&pPrimary->ExpireTime);
+ rtFsIsoMakerZero9660Timestamp(&pPrimary->EffectiveTime);
+ pPrimary->bFileStructureVersion = ISO9660_FILE_STRUCTURE_VERSION;
+ //pPrimary->bReserved883 = 0;
+ //RT_ZERO(pPrimary->abAppUse);
+ //RT_ZERO(pPrimary->abReserved1396);
+
+ /*
+ * Initialize the joliet descriptor if included.
+ */
+ PISO9660SUPVOLDESC pJoliet = pThis->pJolietVolDesc;
+ if (pJoliet)
+ {
+ pJoliet->Hdr.bDescType = ISO9660VOLDESC_TYPE_SUPPLEMENTARY;
+ pJoliet->Hdr.bDescVersion = ISO9660SUPVOLDESC_VERSION;
+ memcpy(pJoliet->Hdr.achStdId, ISO9660VOLDESC_STD_ID, sizeof(pJoliet->Hdr.achStdId));
+ pJoliet->fVolumeFlags = ISO9660SUPVOLDESC_VOL_F_ESC_ONLY_REG;
+ rtFsIsoMakerFinalizeCopyAsUtf16BigAndSpacePad(pJoliet->achSystemId, sizeof(pJoliet->achSystemId), pThis->Joliet.pszSystemId);
+ rtFsIsoMakerFinalizeCopyAsUtf16BigAndSpacePad(pJoliet->achVolumeId, sizeof(pJoliet->achVolumeId),
+ pThis->Joliet.pszVolumeId ? pThis->Joliet.pszVolumeId : szImageCreationTime);
+ //pJoliet->Unused73 = {0}
+ //pJoliet->VolumeSpaceSize = later
+ memset(pJoliet->abEscapeSequences, ' ', sizeof(pJoliet->abEscapeSequences));
+ pJoliet->abEscapeSequences[0] = ISO9660_JOLIET_ESC_SEQ_0;
+ pJoliet->abEscapeSequences[1] = ISO9660_JOLIET_ESC_SEQ_1;
+ pJoliet->abEscapeSequences[2] = pThis->Joliet.uLevel == 1 ? ISO9660_JOLIET_ESC_SEQ_2_LEVEL_1
+ : pThis->Joliet.uLevel == 2 ? ISO9660_JOLIET_ESC_SEQ_2_LEVEL_2
+ : ISO9660_JOLIET_ESC_SEQ_2_LEVEL_3;
+ pJoliet->cVolumesInSet.be = RT_H2BE_U16_C(1);
+ pJoliet->cVolumesInSet.le = RT_H2LE_U16_C(1);
+ pJoliet->VolumeSeqNo.be = RT_H2BE_U16_C(1);
+ pJoliet->VolumeSeqNo.le = RT_H2LE_U16_C(1);
+ pJoliet->cbLogicalBlock.be = RT_H2BE_U16_C(RTFSISOMAKER_SECTOR_SIZE);
+ pJoliet->cbLogicalBlock.le = RT_H2LE_U16_C(RTFSISOMAKER_SECTOR_SIZE);
+ //pJoliet->cbPathTable = later
+ //pJoliet->offTypeLPathTable = later
+ //pJoliet->offOptionalTypeLPathTable = {0}
+ //pJoliet->offTypeMPathTable = later
+ //pJoliet->offOptionalTypeMPathTable = {0}
+ //pJoliet->RootDir = later
+ rtFsIsoMakerFinalizeCopyAsUtf16BigAndSpacePad(pJoliet->achVolumeSetId, sizeof(pJoliet->achVolumeSetId),
+ pThis->Joliet.pszVolumeSetId);
+ rtFsIsoMakerFinalizeCopyAsUtf16BigAndSpacePad(pJoliet->achPublisherId, sizeof(pJoliet->achPublisherId),
+ pThis->Joliet.pszPublisherId);
+ rtFsIsoMakerFinalizeCopyAsUtf16BigAndSpacePad(pJoliet->achDataPreparerId, sizeof(pJoliet->achDataPreparerId),
+ pThis->Joliet.pszDataPreparerId);
+ rtFsIsoMakerFinalizeCopyAsUtf16BigAndSpacePad(pJoliet->achApplicationId, sizeof(pJoliet->achApplicationId),
+ pThis->Joliet.pszApplicationId);
+ rtFsIsoMakerFinalizeCopyAsUtf16BigAndSpacePad(pJoliet->achCopyrightFileId, sizeof(pJoliet->achCopyrightFileId),
+ pThis->Joliet.pszCopyrightFileId);
+ rtFsIsoMakerFinalizeCopyAsUtf16BigAndSpacePad(pJoliet->achAbstractFileId, sizeof(pJoliet->achAbstractFileId),
+ pThis->Joliet.pszAbstractFileId);
+ rtFsIsoMakerFinalizeCopyAsUtf16BigAndSpacePad(pJoliet->achBibliographicFileId, sizeof(pJoliet->achBibliographicFileId),
+ pThis->Joliet.pszBibliographicFileId);
+ rtFsIsoMakerTimespecToIso9660Timestamp(&pThis->ImageCreationTime, &pJoliet->BirthTime);
+ rtFsIsoMakerTimespecToIso9660Timestamp(&pThis->ImageCreationTime, &pJoliet->ModifyTime);
+ rtFsIsoMakerZero9660Timestamp(&pJoliet->ExpireTime);
+ rtFsIsoMakerZero9660Timestamp(&pJoliet->EffectiveTime);
+ pJoliet->bFileStructureVersion = ISO9660_FILE_STRUCTURE_VERSION;
+ //pJoliet->bReserved883 = 0;
+ //RT_ZERO(pJoliet->abAppUse);
+ //RT_ZERO(pJoliet->abReserved1396);
+ }
+
+ /*
+ * The ISO-9660 terminator descriptor.
+ */
+ pThis->pTerminatorVolDesc->bDescType = ISO9660VOLDESC_TYPE_TERMINATOR;
+ pThis->pTerminatorVolDesc->bDescVersion = 1;
+ memcpy(pThis->pTerminatorVolDesc->achStdId, ISO9660VOLDESC_STD_ID, sizeof(pThis->pTerminatorVolDesc->achStdId));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Finalizes the volume descriptors.
+ *
+ * This will set the RTFSISOMAKERFILE::offData members.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ */
+static int rtFsIsoMakerFinalizeVolumeDescriptors(PRTFSISOMAKERINT pThis)
+{
+ AssertReturn(pThis->pbVolDescs && pThis->pPrimaryVolDesc && pThis->pTerminatorVolDesc, VERR_ISOMK_IPE_FINALIZE_1);
+
+ /*
+ * Primary descriptor.
+ */
+ PISO9660PRIMARYVOLDESC pPrimary = pThis->pPrimaryVolDesc;
+
+ pPrimary->VolumeSpaceSize.be = RT_H2BE_U32(pThis->cbFinalizedImage / RTFSISOMAKER_SECTOR_SIZE);
+ pPrimary->VolumeSpaceSize.le = RT_H2LE_U32(pThis->cbFinalizedImage / RTFSISOMAKER_SECTOR_SIZE);
+ pPrimary->cbPathTable.be = RT_H2BE_U32(pThis->PrimaryIsoDirs.cbPathTable);
+ pPrimary->cbPathTable.le = RT_H2LE_U32(pThis->PrimaryIsoDirs.cbPathTable);
+ pPrimary->offTypeLPathTable = RT_H2LE_U32(pThis->PrimaryIsoDirs.offPathTableL / RTFSISOMAKER_SECTOR_SIZE);
+ pPrimary->offTypeMPathTable = RT_H2BE_U32(pThis->PrimaryIsoDirs.offPathTableM / RTFSISOMAKER_SECTOR_SIZE);
+ pPrimary->RootDir.DirRec.cbDirRec = sizeof(pPrimary->RootDir);
+ pPrimary->RootDir.DirRec.cExtAttrBlocks = 0;
+ pPrimary->RootDir.DirRec.offExtent.be = RT_H2BE_U32(pThis->PrimaryIso.pRoot->pDir->offDir / RTFSISOMAKER_SECTOR_SIZE);
+ pPrimary->RootDir.DirRec.offExtent.le = RT_H2LE_U32(pThis->PrimaryIso.pRoot->pDir->offDir / RTFSISOMAKER_SECTOR_SIZE);
+ pPrimary->RootDir.DirRec.cbData.be = RT_H2BE_U32(pThis->PrimaryIso.pRoot->pDir->cbDir);
+ pPrimary->RootDir.DirRec.cbData.le = RT_H2LE_U32(pThis->PrimaryIso.pRoot->pDir->cbDir);
+ rtFsIsoMakerTimespecToIso9660RecTimestamp(&pThis->PrimaryIso.pRoot->pObj->BirthTime, &pPrimary->RootDir.DirRec.RecTime);
+ pPrimary->RootDir.DirRec.fFileFlags = ISO9660_FILE_FLAGS_DIRECTORY;
+ pPrimary->RootDir.DirRec.bFileUnitSize = 0;
+ pPrimary->RootDir.DirRec.bInterleaveGapSize = 0;
+ pPrimary->RootDir.DirRec.VolumeSeqNo.be = RT_H2BE_U16_C(1);
+ pPrimary->RootDir.DirRec.VolumeSeqNo.le = RT_H2LE_U16_C(1);
+ pPrimary->RootDir.DirRec.bFileIdLength = 1;
+ pPrimary->RootDir.DirRec.achFileId[0] = 0x00;
+
+ /*
+ * Initialize the joliet descriptor if included.
+ */
+ PISO9660SUPVOLDESC pJoliet = pThis->pJolietVolDesc;
+ if (pJoliet)
+ {
+ pJoliet->VolumeSpaceSize = pPrimary->VolumeSpaceSize;
+ pJoliet->cbPathTable.be = RT_H2BE_U32(pThis->JolietDirs.cbPathTable);
+ pJoliet->cbPathTable.le = RT_H2LE_U32(pThis->JolietDirs.cbPathTable);
+ pJoliet->offTypeLPathTable = RT_H2LE_U32(pThis->JolietDirs.offPathTableL / RTFSISOMAKER_SECTOR_SIZE);
+ pJoliet->offTypeMPathTable = RT_H2BE_U32(pThis->JolietDirs.offPathTableM / RTFSISOMAKER_SECTOR_SIZE);
+ pJoliet->RootDir.DirRec.cbDirRec = sizeof(pJoliet->RootDir);
+ pJoliet->RootDir.DirRec.cExtAttrBlocks = 0;
+ pJoliet->RootDir.DirRec.offExtent.be = RT_H2BE_U32(pThis->Joliet.pRoot->pDir->offDir / RTFSISOMAKER_SECTOR_SIZE);
+ pJoliet->RootDir.DirRec.offExtent.le = RT_H2LE_U32(pThis->Joliet.pRoot->pDir->offDir / RTFSISOMAKER_SECTOR_SIZE);
+ pJoliet->RootDir.DirRec.cbData.be = RT_H2BE_U32(pThis->Joliet.pRoot->pDir->cbDir);
+ pJoliet->RootDir.DirRec.cbData.le = RT_H2LE_U32(pThis->Joliet.pRoot->pDir->cbDir);
+ rtFsIsoMakerTimespecToIso9660RecTimestamp(&pThis->Joliet.pRoot->pObj->BirthTime, &pJoliet->RootDir.DirRec.RecTime);
+ pJoliet->RootDir.DirRec.fFileFlags = ISO9660_FILE_FLAGS_DIRECTORY;
+ pJoliet->RootDir.DirRec.bFileUnitSize = 0;
+ pJoliet->RootDir.DirRec.bInterleaveGapSize = 0;
+ pJoliet->RootDir.DirRec.VolumeSeqNo.be = RT_H2BE_U16_C(1);
+ pJoliet->RootDir.DirRec.VolumeSeqNo.le = RT_H2LE_U16_C(1);
+ pJoliet->RootDir.DirRec.bFileIdLength = 1;
+ pJoliet->RootDir.DirRec.achFileId[0] = 0x00;
+ }
+
+#if 0 /* this doesn't quite fool it. */
+ /*
+ * isomd5sum fake.
+ */
+ if (1)
+ {
+ uint8_t abDigest[RTMD5_HASH_SIZE];
+ if (pThis->cbSysArea == 0)
+ RTMd5(g_abRTZero4K, ISO9660_SECTOR_SIZE, abDigest);
+ else
+ {
+ RTMD5CONTEXT Ctx;
+ RTMd5Init(&Ctx);
+ RTMd5Update(&Ctx, pThis->pbSysArea, RT_MIN(pThis->cbSysArea, ISO9660_SECTOR_SIZE));
+ if (pThis->cbSysArea < ISO9660_SECTOR_SIZE)
+ RTMd5Update(&Ctx, g_abRTZero4K, ISO9660_SECTOR_SIZE - pThis->cbSysArea);
+ RTMd5Final(abDigest, &Ctx);
+ }
+ char szFakeHash[RTMD5_DIGEST_LEN + 1];
+ RTMd5ToString(abDigest, szFakeHash, sizeof(szFakeHash));
+
+ size_t cch = RTStrPrintf((char *)&pPrimary->abAppUse[0], sizeof(pPrimary->abAppUse),
+ "ISO MD5SUM = %s;SKIPSECTORS = %u;RHLISOSTATUS=1;THIS IS JUST A FAKE!",
+ szFakeHash, pThis->cbFinalizedImage / RTFSISOMAKER_SECTOR_SIZE - 1);
+ memset(&pPrimary->abAppUse[cch], ' ', sizeof(pPrimary->abAppUse) - cch);
+ }
+#endif
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Finalizes the image.
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ */
+RTDECL(int) RTFsIsoMakerFinalize(RTFSISOMAKER hIsoMaker)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ /*
+ * Remove orphaned objects and allocate volume descriptors.
+ */
+ int rc = rtFsIsoMakerFinalizeRemoveOrphans(pThis);
+ if (RT_FAILURE(rc))
+ return rc;
+ AssertReturn(pThis->cObjects > 0, VERR_NO_DATA);
+
+ /* The primary ISO-9660 namespace must be explicitly disabled (for now),
+ so we return VERR_NO_DATA if no root dir. */
+ AssertReturn(pThis->PrimaryIso.pRoot || pThis->PrimaryIso.uLevel == 0, VERR_NO_DATA);
+
+ /* Automatically disable the joliet namespace if it is empty (no root dir). */
+ if (!pThis->Joliet.pRoot && pThis->Joliet.uLevel > 0)
+ {
+ pThis->Joliet.uLevel = 0;
+ pThis->cVolumeDescriptors--;
+ }
+
+ rc = rtFsIsoMakerFinalizePrepVolumeDescriptors(pThis);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * If there is any boot related stuff to be included, it ends up right after
+ * the descriptors.
+ */
+ uint64_t offData = _32K + pThis->cVolumeDescriptors * RTFSISOMAKER_SECTOR_SIZE;
+ rc = rtFsIsoMakerFinalizeBootStuffPart1(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Directories and path tables comes next.
+ */
+ rc = rtFsIsoMakerFinalizeDirectories(pThis, &offData);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Then we store the file data.
+ */
+ rc = rtFsIsoMakerFinalizeData(pThis, &offData);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cbFinalizedImage = offData + pThis->cbImagePadding;
+
+ /*
+ * Do a 2nd pass over the boot stuff to finalize locations.
+ */
+ rc = rtFsIsoMakerFinalizeBootStuffPart2(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Finally, finalize the volume descriptors as they depend on some of the
+ * block allocations done in the previous steps.
+ */
+ rc = rtFsIsoMakerFinalizeVolumeDescriptors(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->fFinalized = true;
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ }
+ }
+ return rc;
+}
+
+
+
+
+
+/*
+ *
+ * Image I/O.
+ * Image I/O.
+ * Image I/O.
+ *
+ */
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsIsoMakerOutFile_Close(void *pvThis)
+{
+ PRTFSISOMAKEROUTPUTFILE pThis = (PRTFSISOMAKEROUTPUTFILE)pvThis;
+
+ RTFsIsoMakerRelease(pThis->pIsoMaker);
+ pThis->pIsoMaker = NULL;
+
+ if (pThis->hVfsSrcFile != NIL_RTVFSFILE)
+ {
+ RTVfsFileRelease(pThis->hVfsSrcFile);
+ pThis->hVfsSrcFile = NIL_RTVFSFILE;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsIsoMakerOutFile_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSISOMAKEROUTPUTFILE pThis = (PRTFSISOMAKEROUTPUTFILE)pvThis;
+ PRTFSISOMAKERINT pIsoMaker = pThis->pIsoMaker;
+
+
+ pObjInfo->cbObject = pIsoMaker->cbFinalizedImage;
+ pObjInfo->cbAllocated = pIsoMaker->cbFinalizedImage;
+ pObjInfo->AccessTime = pIsoMaker->ImageCreationTime;
+ pObjInfo->ModificationTime = pIsoMaker->ImageCreationTime;
+ pObjInfo->ChangeTime = pIsoMaker->ImageCreationTime;
+ pObjInfo->BirthTime = pIsoMaker->ImageCreationTime;
+ pObjInfo->Attr.fMode = 0444 | RTFS_TYPE_FILE | RTFS_DOS_READONLY;
+
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_NOTHING:
+ enmAddAttr = RTFSOBJATTRADD_UNIX;
+ 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 = 0;
+ pObjInfo->Attr.u.Unix.fFlags = 0;
+ pObjInfo->Attr.u.Unix.GenerationId = 0;
+ pObjInfo->Attr.u.Unix.Device = 0;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ pObjInfo->Attr.u.UnixOwner.uid = NIL_RTUID;
+ pObjInfo->Attr.u.UnixOwner.szName[0] = '\0';
+ break;
+
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ pObjInfo->Attr.u.UnixGroup.gid = NIL_RTGID;
+ pObjInfo->Attr.u.UnixGroup.szName[0] = '\0';
+ break;
+
+ case RTFSOBJATTRADD_EASIZE:
+ pObjInfo->Attr.u.EASize.cb = 0;
+ break;
+
+ default:
+ AssertFailedReturn(VERR_INVALID_PARAMETER);
+ }
+ pObjInfo->Attr.enmAdditional = enmAddAttr;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Generates the 'SL' records for a symbolic link.
+ *
+ * This is used both when generating directories records, spill file data and
+ * when creating the symbolic link.
+ *
+ * @returns Number of bytes produced. Negative IPRT status if buffer overflow.
+ * @param pszTarget The symbolic link target to encode.
+ * @param pbBuf The output buffer.
+ * @param cbBuf The size of the output buffer.
+ */
+static ssize_t rtFsIsoMakerOutFile_RockRidgeGenSL(const char *pszTarget, uint8_t *pbBuf, size_t cbBuf)
+{
+ Assert(*pszTarget != '\0');
+
+ PISO9660RRIPSL pEntry = (PISO9660RRIPSL)pbBuf;
+ pEntry->Hdr.bSig1 = ISO9660RRIPSL_SIG1;
+ pEntry->Hdr.bSig2 = ISO9660RRIPSL_SIG2;
+ pEntry->Hdr.cbEntry = 0; /* set later. */
+ pEntry->Hdr.bVersion = ISO9660RRIPSL_VER;
+ pEntry->fFlags = 0;
+ size_t offEntry = 0;
+ size_t off = RT_UOFFSETOF(ISO9660RRIPSL, abComponents);
+
+ /* Does it start with a root slash? */
+ if (RTPATH_IS_SLASH(*pszTarget))
+ {
+ pbBuf[off++] = ISO9660RRIP_SL_C_ROOT;
+ pbBuf[off++] = 0;
+ pszTarget++;
+ }
+
+ for (;;)
+ {
+ /* Find the end of the component. */
+ size_t cchComponent = 0;
+ char ch;
+ while ((ch = pszTarget[cchComponent]) != '\0' && !RTPATH_IS_SLASH(ch))
+ cchComponent++;
+
+ /* Check for dots and figure out how much space we need. */
+ uint8_t fFlags;
+ size_t cbNeeded;
+ if (cchComponent == 1 && *pszTarget == '.')
+ {
+ fFlags = ISO9660RRIP_SL_C_CURRENT;
+ cbNeeded = 2;
+ }
+ else if (cchComponent == 2 && pszTarget[0] == '.' && pszTarget[1] == '.')
+ {
+ fFlags = ISO9660RRIP_SL_C_PARENT;
+ cbNeeded = 2;
+ }
+ else
+ {
+ fFlags = 0;
+ cbNeeded = 2 + cchComponent;
+ }
+
+ /* Split the SL record if we're out of space. */
+ if ( off - offEntry + cbNeeded < UINT8_MAX
+ && off + cbNeeded <= cbBuf)
+ { /* likely */ }
+ else if (cbNeeded + RT_UOFFSETOF(ISO9660RRIPSL, abComponents) < UINT8_MAX)
+ {
+ AssertReturn(off + cbNeeded + RT_UOFFSETOF(ISO9660RRIPSL, abComponents) <= cbBuf, VERR_BUFFER_OVERFLOW);
+ Assert(off - offEntry < UINT8_MAX);
+ pEntry->Hdr.cbEntry = (uint8_t)(off - offEntry);
+ pEntry->fFlags |= ISO9660RRIP_SL_F_CONTINUE;
+
+ offEntry = off;
+ pEntry = (PISO9660RRIPSL)&pbBuf[off];
+ pEntry->Hdr.bSig1 = ISO9660RRIPSL_SIG1;
+ pEntry->Hdr.bSig2 = ISO9660RRIPSL_SIG2;
+ pEntry->Hdr.cbEntry = 0; /* set later. */
+ pEntry->Hdr.bVersion = ISO9660RRIPSL_VER;
+ pEntry->fFlags = 0;
+ }
+ else
+ {
+ /* Special case: component doesn't fit in a single SL entry. */
+ do
+ {
+ if (off - offEntry + 3 < UINT8_MAX)
+ {
+ size_t cchLeft = UINT8_MAX - 1 - (off - offEntry) - 2;
+ size_t cchToCopy = RT_MIN(cchLeft, cchComponent);
+ AssertReturn(off + 2 + cchToCopy <= cbBuf, VERR_BUFFER_OVERFLOW);
+ pbBuf[off++] = cchToCopy < cchComponent ? ISO9660RRIP_SL_C_CONTINUE : 0;
+ pbBuf[off++] = (uint8_t)cchToCopy;
+ memcpy(&pbBuf[off], pszTarget, cchToCopy);
+ off += cchToCopy;
+ pszTarget += cchToCopy;
+ cchComponent -= cchToCopy;
+ if (!cchComponent)
+ break;
+ }
+
+ Assert(off - offEntry < UINT8_MAX);
+ pEntry->Hdr.cbEntry = (uint8_t)(off - offEntry);
+ pEntry->fFlags |= ISO9660RRIP_SL_F_CONTINUE;
+
+ AssertReturn(off + 2 + cchComponent + RT_UOFFSETOF(ISO9660RRIPSL, abComponents) <= cbBuf, VERR_BUFFER_OVERFLOW);
+ offEntry = off;
+ pEntry = (PISO9660RRIPSL)&pbBuf[off];
+ pEntry->Hdr.bSig1 = ISO9660RRIPSL_SIG1;
+ pEntry->Hdr.bSig2 = ISO9660RRIPSL_SIG2;
+ pEntry->Hdr.cbEntry = 0; /* set later. */
+ pEntry->Hdr.bVersion = ISO9660RRIPSL_VER;
+ pEntry->fFlags = 0;
+ } while (cchComponent > 0);
+ if (ch == '\0')
+ break;
+ pszTarget++;
+ continue;
+ }
+
+ /* Produce the record. */
+ pbBuf[off++] = fFlags;
+ pbBuf[off++] = (uint8_t)(cbNeeded - 2);
+ if (cchComponent > 0)
+ {
+ memcpy(&pbBuf[off], pszTarget, cbNeeded - 2);
+ off += cbNeeded - 2;
+ }
+
+ if (ch == '\0')
+ break;
+ pszTarget += cchComponent + 1;
+ }
+
+ Assert(off - offEntry < UINT8_MAX);
+ pEntry->Hdr.cbEntry = (uint8_t)(off - offEntry);
+ return off;
+}
+
+
+/**
+ * Generates rock ridge data.
+ *
+ * This is used both for the directory record and for the spill file ('CE').
+ *
+ * @param pName The name to generate rock ridge info for.
+ * @param pbSys The output buffer.
+ * @param cbSys The size of the output buffer.
+ * @param fInSpill Indicates whether we're in a spill file (true) or
+ * directory record (false).
+ * @param enmDirType The kind of directory entry this is.
+ */
+static void rtFsIosMakerOutFile_GenerateRockRidge(PRTFSISOMAKERNAME pName, uint8_t *pbSys, size_t cbSys,
+ bool fInSpill, RTFSISOMAKERDIRTYPE enmDirType)
+{
+ /*
+ * Deal with records specific to the root directory '.' entry.
+ */
+ if (pName->pParent != NULL)
+ { /* likely */ }
+ else
+ {
+ if (!fInSpill)
+ {
+ PISO9660SUSPSP pSP = (PISO9660SUSPSP)pbSys;
+ Assert(cbSys >= sizeof(*pSP));
+ pSP->Hdr.bSig1 = ISO9660SUSPSP_SIG1;
+ pSP->Hdr.bSig2 = ISO9660SUSPSP_SIG2;
+ pSP->Hdr.cbEntry = ISO9660SUSPSP_LEN;
+ pSP->Hdr.bVersion = ISO9660SUSPSP_VER;
+ pSP->bCheck1 = ISO9660SUSPSP_CHECK1;
+ pSP->bCheck2 = ISO9660SUSPSP_CHECK2;
+ pSP->cbSkip = 0;
+ pbSys += sizeof(*pSP);
+ cbSys -= sizeof(*pSP);
+ }
+ if (pName->fRockNeedER)
+ {
+ PISO9660SUSPER pER = (PISO9660SUSPER)pbSys;
+ Assert(cbSys >= ISO9660_RRIP_ER_LEN);
+ AssertCompile(ISO9660_RRIP_ER_LEN < UINT8_MAX);
+ pER->Hdr.bSig1 = ISO9660SUSPER_SIG1;
+ pER->Hdr.bSig2 = ISO9660SUSPER_SIG2;
+ pER->Hdr.cbEntry = ISO9660_RRIP_ER_LEN;
+ pER->Hdr.bVersion = ISO9660SUSPER_VER;
+ pER->cchIdentifier = sizeof(ISO9660_RRIP_ID) - 1;
+ pER->cchDescription = sizeof(ISO9660_RRIP_DESC) - 1;
+ pER->cchSource = sizeof(ISO9660_RRIP_SRC) - 1;
+ pER->bVersion = ISO9660_RRIP_VER;
+ char *pchDst = &pER->achPayload[0]; /* we do this to shut up annoying clang. */
+ memcpy(pchDst, RT_STR_TUPLE(ISO9660_RRIP_ID));
+ pchDst += sizeof(ISO9660_RRIP_ID) - 1;
+ memcpy(pchDst, RT_STR_TUPLE(ISO9660_RRIP_DESC));
+ pchDst += sizeof(ISO9660_RRIP_DESC) - 1;
+ memcpy(pchDst, RT_STR_TUPLE(ISO9660_RRIP_SRC));
+ pbSys += ISO9660_RRIP_ER_LEN;
+ cbSys -= ISO9660_RRIP_ER_LEN;
+ }
+ }
+
+ /*
+ * Deal with common stuff.
+ */
+ if (!fInSpill ? pName->fRockNeedRRInDirRec : pName->fRockNeedRRInSpill)
+ {
+ PISO9660RRIPRR pRR = (PISO9660RRIPRR)pbSys;
+ Assert(cbSys >= sizeof(*pRR));
+ pRR->Hdr.bSig1 = ISO9660RRIPRR_SIG1;
+ pRR->Hdr.bSig2 = ISO9660RRIPRR_SIG2;
+ pRR->Hdr.cbEntry = ISO9660RRIPRR_LEN;
+ pRR->Hdr.bVersion = ISO9660RRIPRR_VER;
+ pRR->fFlags = pName->fRockEntries;
+ pbSys += sizeof(*pRR);
+ cbSys -= sizeof(*pRR);
+ }
+
+ /*
+ * The following entries all end up in the spill or fully in
+ * the directory record.
+ */
+ if (fInSpill || pName->cbRockSpill == 0)
+ {
+ if (pName->fRockEntries & ISO9660RRIP_RR_F_PX)
+ {
+ PISO9660RRIPPX pPX = (PISO9660RRIPPX)pbSys;
+ Assert(cbSys >= sizeof(*pPX));
+ pPX->Hdr.bSig1 = ISO9660RRIPPX_SIG1;
+ pPX->Hdr.bSig2 = ISO9660RRIPPX_SIG2;
+ pPX->Hdr.cbEntry = ISO9660RRIPPX_LEN;
+ pPX->Hdr.bVersion = ISO9660RRIPPX_VER;
+ pPX->fMode.be = RT_H2BE_U32((uint32_t)(pName->fMode & RTFS_UNIX_MASK));
+ pPX->fMode.le = RT_H2LE_U32((uint32_t)(pName->fMode & RTFS_UNIX_MASK));
+ pPX->cHardlinks.be = RT_H2BE_U32((uint32_t)pName->cHardlinks);
+ pPX->cHardlinks.le = RT_H2LE_U32((uint32_t)pName->cHardlinks);
+ pPX->uid.be = RT_H2BE_U32((uint32_t)pName->uid);
+ pPX->uid.le = RT_H2LE_U32((uint32_t)pName->uid);
+ pPX->gid.be = RT_H2BE_U32((uint32_t)pName->gid);
+ pPX->gid.le = RT_H2LE_U32((uint32_t)pName->gid);
+#if 0 /* This is confusing solaris. Looks like it has code assuming inode numbers are block numbers and ends up mistaking files for the root dir. Sigh. */
+ pPX->INode.be = RT_H2BE_U32((uint32_t)pName->pObj->idxObj + 1); /* Don't use zero - isoinfo doesn't like it. */
+ pPX->INode.le = RT_H2LE_U32((uint32_t)pName->pObj->idxObj + 1);
+#else
+ pPX->INode.be = 0;
+ pPX->INode.le = 0;
+#endif
+ pbSys += sizeof(*pPX);
+ cbSys -= sizeof(*pPX);
+ }
+
+ if (pName->fRockEntries & ISO9660RRIP_RR_F_TF)
+ {
+ PISO9660RRIPTF pTF = (PISO9660RRIPTF)pbSys;
+ pTF->Hdr.bSig1 = ISO9660RRIPTF_SIG1;
+ pTF->Hdr.bSig2 = ISO9660RRIPTF_SIG2;
+ pTF->Hdr.cbEntry = Iso9660RripTfCalcLength(ISO9660RRIPTF_F_BIRTH | ISO9660RRIPTF_F_MODIFY | ISO9660RRIPTF_F_ACCESS | ISO9660RRIPTF_F_CHANGE);
+ Assert(cbSys >= pTF->Hdr.cbEntry);
+ pTF->Hdr.bVersion = ISO9660RRIPTF_VER;
+ pTF->fFlags = ISO9660RRIPTF_F_BIRTH | ISO9660RRIPTF_F_MODIFY | ISO9660RRIPTF_F_ACCESS | ISO9660RRIPTF_F_CHANGE;
+ PISO9660RECTIMESTAMP paTimestamps = (PISO9660RECTIMESTAMP)&pTF->abPayload[0];
+ rtFsIsoMakerTimespecToIso9660RecTimestamp(&pName->pObj->BirthTime, &paTimestamps[0]);
+ rtFsIsoMakerTimespecToIso9660RecTimestamp(&pName->pObj->ModificationTime, &paTimestamps[1]);
+ rtFsIsoMakerTimespecToIso9660RecTimestamp(&pName->pObj->AccessedTime, &paTimestamps[2]);
+ rtFsIsoMakerTimespecToIso9660RecTimestamp(&pName->pObj->ChangeTime, &paTimestamps[3]);
+ cbSys -= pTF->Hdr.cbEntry;
+ pbSys += pTF->Hdr.cbEntry;
+ }
+
+ if (pName->fRockEntries & ISO9660RRIP_RR_F_PN)
+ {
+ PISO9660RRIPPN pPN = (PISO9660RRIPPN)pbSys;
+ Assert(cbSys >= sizeof(*pPN));
+ pPN->Hdr.bSig1 = ISO9660RRIPPN_SIG1;
+ pPN->Hdr.bSig2 = ISO9660RRIPPN_SIG2;
+ pPN->Hdr.cbEntry = ISO9660RRIPPN_LEN;
+ pPN->Hdr.bVersion = ISO9660RRIPPN_VER;
+ pPN->Major.be = RT_H2BE_U32((uint32_t)RTDEV_MAJOR(pName->Device));
+ pPN->Major.le = RT_H2LE_U32((uint32_t)RTDEV_MAJOR(pName->Device));
+ pPN->Minor.be = RT_H2BE_U32((uint32_t)RTDEV_MINOR(pName->Device));
+ pPN->Minor.le = RT_H2LE_U32((uint32_t)RTDEV_MINOR(pName->Device));
+ cbSys -= sizeof(*pPN);
+ pbSys += sizeof(*pPN);
+ }
+
+ if (pName->fRockEntries & ISO9660RRIP_RR_F_NM)
+ {
+ size_t cchSrc = pName->cchRockRidgeNm;
+ const char *pszSrc = pName->pszRockRidgeNm;
+ for (;;)
+ {
+ size_t cchThis = RT_MIN(cchSrc, ISO9660RRIPNM_MAX_NAME_LEN);
+ PISO9660RRIPNM pNM = (PISO9660RRIPNM)pbSys;
+ Assert(cbSys >= RT_UOFFSETOF_DYN(ISO9660RRIPNM, achName[cchThis]));
+ pNM->Hdr.bSig1 = ISO9660RRIPNM_SIG1;
+ pNM->Hdr.bSig2 = ISO9660RRIPNM_SIG2;
+ pNM->Hdr.cbEntry = (uint8_t)(RT_UOFFSETOF(ISO9660RRIPNM, achName) + cchThis);
+ pNM->Hdr.bVersion = ISO9660RRIPNM_VER;
+ pNM->fFlags = cchThis == cchSrc ? 0 : ISO9660RRIP_NM_F_CONTINUE;
+ /** @todo r=bird: This only works when not using the spill file. The spill
+ * file entry will be shared between the original and all the '.' and
+ * '..' entries. FreeBSD gets confused by this w/o the
+ * ISO9660RRIP_NM_F_CURRENT and ISO9660RRIP_NM_F_PARENT flags. */
+ if (enmDirType == RTFSISOMAKERDIRTYPE_CURRENT)
+ pNM->fFlags |= ISO9660RRIP_NM_F_CURRENT;
+ else if (enmDirType == RTFSISOMAKERDIRTYPE_PARENT)
+ pNM->fFlags |= ISO9660RRIP_NM_F_PARENT;
+ memcpy(&pNM->achName[0], pszSrc, cchThis);
+ pbSys += RT_UOFFSETOF(ISO9660RRIPNM, achName) + cchThis;
+ cbSys -= RT_UOFFSETOF(ISO9660RRIPNM, achName) + cchThis;
+ cchSrc -= cchThis;
+ if (!cchSrc)
+ break;
+ }
+ }
+
+ if (pName->fRockEntries & ISO9660RRIP_RR_F_SL)
+ {
+ AssertReturnVoid(pName->pObj->enmType == RTFSISOMAKEROBJTYPE_SYMLINK);
+ PCRTFSISOMAKERSYMLINK pSymlink = (PCRTFSISOMAKERSYMLINK)pName->pObj;
+
+ ssize_t cbSlRockRidge = rtFsIsoMakerOutFile_RockRidgeGenSL(pSymlink->szTarget, pbSys, cbSys);
+ AssertReturnVoid(cbSlRockRidge > 0);
+ Assert(cbSys >= (size_t)cbSlRockRidge);
+ pbSys += (size_t)cbSlRockRidge;
+ cbSys -= (size_t)cbSlRockRidge;
+ }
+ }
+
+ /* finally, zero padding. */
+ if (cbSys & 1)
+ {
+ *pbSys++ = '\0';
+ cbSys--;
+ }
+
+ Assert(!fInSpill ? cbSys == 0 : cbSys < _2G);
+}
+
+
+
+
+/**
+ * Reads one or more sectors from a rock ridge spill file.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker output file instance. We use the
+ * directory pointer hints and child index hints
+ * @param pIsoMaker The ISO maker.
+ * @param pFile The rock ridge spill file.
+ * @param offInFile The offset into the spill file. This is sector aligned.
+ * @param pbBuf The output buffer.
+ * @param cbToRead The number of bytes to tread. This is sector aligned.
+ */
+static int rtFsIsoMakerOutFile_RockRidgeSpillReadSectors(PRTFSISOMAKEROUTPUTFILE pThis, PRTFSISOMAKERINT pIsoMaker,
+ PRTFSISOMAKERFILE pFile, uint32_t offInFile, uint8_t *pbBuf,
+ size_t cbToRead)
+{
+ /*
+ * We're only working multiple of ISO 9660 sectors.
+ *
+ * The spill of one directory record will always fit entirely within a
+ * sector, we make sure about that during finalization. There may be
+ * zero padding between spill data sequences, especially on the sector
+ * boundrary.
+ */
+ Assert((offInFile & ISO9660_SECTOR_OFFSET_MASK) == 0);
+ Assert((cbToRead & ISO9660_SECTOR_OFFSET_MASK) == 0);
+ Assert(cbToRead >= ISO9660_SECTOR_SIZE);
+
+ /*
+ * We generate a sector at a time.
+ *
+ * So, we start by locating the first directory/child in the block offInFile
+ * is pointing to.
+ */
+ PRTFSISOMAKERFINALIZEDDIRS pFinalizedDirs;
+ PRTFSISOMAKERNAMEDIR *ppDirHint;
+ uint32_t *pidxChildHint;
+ if (pFile->u.pRockSpillNamespace->fNamespace & RTFSISOMAKER_NAMESPACE_ISO_9660)
+ {
+ pFinalizedDirs = &pIsoMaker->PrimaryIsoDirs;
+ ppDirHint = &pThis->pDirHintPrimaryIso;
+ pidxChildHint = &pThis->iChildPrimaryIso;
+ }
+ else
+ {
+ pFinalizedDirs = &pIsoMaker->JolietDirs;
+ ppDirHint = &pThis->pDirHintJoliet;
+ pidxChildHint = &pThis->iChildJoliet;
+ }
+
+ /* Special case: '.' record in root dir */
+ uint32_t idxChild = *pidxChildHint;
+ PRTFSISOMAKERNAMEDIR pDir = *ppDirHint;
+ if ( offInFile == 0
+ && (pDir = RTListGetFirst(&pFinalizedDirs->FinalizedDirs, RTFSISOMAKERNAMEDIR, FinalizedEntry)) != NULL
+ && pDir->pName->cbRockSpill > 0)
+ {
+ AssertReturn(pDir, VERR_ISOMK_IPE_RR_READ);
+ AssertReturn(pDir->pName->offRockSpill == 0, VERR_ISOMK_IPE_RR_READ);
+ idxChild = 0;
+ }
+ else
+ {
+ /* Establish where to start searching from. */
+ if ( !pDir
+ || idxChild >= pDir->cChildren
+ || pDir->papChildren[idxChild]->cbRockSpill == 0)
+ {
+ idxChild = 0;
+ pDir = RTListGetFirst(&pFinalizedDirs->FinalizedDirs, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ AssertReturn(pDir, VERR_ISOMK_IPE_RR_READ);
+ }
+
+ if (pDir->papChildren[idxChild]->offRockSpill == offInFile)
+ { /* hit, no need to search */ }
+ else if (pDir->papChildren[idxChild]->offRockSpill < offInFile)
+ {
+ /* search forwards */
+ for (;;)
+ {
+ idxChild++;
+ while ( idxChild < pDir->cChildren
+ && ( pDir->papChildren[idxChild]->offRockSpill < offInFile
+ || pDir->papChildren[idxChild]->cbRockSpill == 0) )
+ idxChild++;
+ if (idxChild < pDir->cChildren)
+ break;
+ pDir = RTListGetNext(&pFinalizedDirs->FinalizedDirs, pDir, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ AssertReturn(pDir, VERR_ISOMK_IPE_RR_READ);
+ }
+ Assert(pDir->papChildren[idxChild]->offRockSpill == offInFile);
+ }
+ else
+ {
+ /* search backwards (no root dir concerns here) */
+ for (;;)
+ {
+ while ( idxChild > 0
+ && ( pDir->papChildren[idxChild - 1]->offRockSpill >= offInFile
+ || pDir->papChildren[idxChild - 1]->cbRockSpill == 0) )
+ idxChild--;
+ if (pDir->papChildren[idxChild]->offRockSpill == offInFile)
+ break;
+ pDir = RTListGetPrev(&pFinalizedDirs->FinalizedDirs, pDir, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ AssertReturn(pDir, VERR_ISOMK_IPE_RR_READ);
+ }
+ Assert(pDir->papChildren[idxChild]->offRockSpill == offInFile);
+ }
+ }
+
+ /*
+ * Produce data.
+ */
+ while (cbToRead > 0)
+ {
+ PRTFSISOMAKERNAME pChild;
+ if ( offInFile > 0
+ || pDir->pName->cbRockSpill == 0
+ || pDir->pName->pParent != NULL)
+ {
+ pChild = pDir->papChildren[idxChild];
+ AssertReturn(pChild->offRockSpill == offInFile, VERR_ISOMK_IPE_RR_READ);
+ AssertReturn(pChild->cbRockSpill > 0, VERR_ISOMK_IPE_RR_READ);
+ idxChild++;
+ }
+ else
+ { /* root dir special case. */
+ pChild = pDir->pName;
+ Assert(idxChild == 0);
+ Assert(pChild->pParent == NULL);
+ }
+
+ AssertReturn(cbToRead >= pChild->cbRockSpill, VERR_ISOMK_IPE_RR_READ);
+ /** @todo r=bird: using RTFSISOMAKERDIRTYPE_OTHER is correct as we don't seem to
+ * have separate name entries for '.' and '..'. However it means that if
+ * any directory ends up in the spill file we'll end up with the wrong
+ * data for the '.' and '..' entries. [RR NM ./..] */
+ rtFsIosMakerOutFile_GenerateRockRidge(pDir->pName, pbBuf, cbToRead, true /*fInSpill*/, RTFSISOMAKERDIRTYPE_OTHER);
+ cbToRead -= pChild->cbRockSpill;
+ pbBuf += pChild->cbRockSpill;
+ offInFile += pChild->cbRockSpill;
+
+ /* Advance to the next name, if any. */
+ uint32_t offNext = UINT32_MAX;
+ do
+ {
+ while (idxChild < pDir->cChildren)
+ {
+ pChild = pDir->papChildren[idxChild];
+ if (pChild->cbRockSpill == 0)
+ Assert(pChild->offRockSpill == UINT32_MAX);
+ else
+ {
+ offNext = pChild->offRockSpill;
+ AssertReturn(offNext >= offInFile, VERR_ISOMK_IPE_RR_READ);
+ AssertReturn(offNext < pFile->cbData, VERR_ISOMK_IPE_RR_READ);
+ break;
+ }
+ idxChild++;
+ }
+ if (offNext != UINT32_MAX)
+ break;
+ pDir = RTListGetNext(&pFinalizedDirs->FinalizedDirs, pDir, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ idxChild = 0;
+ } while (pDir != NULL);
+
+ if (offNext != UINT32_MAX)
+ {
+ uint32_t cbToZero = offNext - offInFile;
+ if (cbToRead > cbToZero)
+ RT_BZERO(pbBuf, cbToZero);
+ else
+ {
+ RT_BZERO(pbBuf, cbToRead);
+ *ppDirHint = pDir;
+ *pidxChildHint = idxChild;
+ break;
+ }
+ cbToRead -= cbToZero;
+ pbBuf += cbToZero;
+ offInFile += cbToZero;
+ }
+ else
+ {
+ RT_BZERO(pbBuf, cbToRead);
+ *ppDirHint = NULL;
+ *pidxChildHint = UINT32_MAX;
+ break;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Deals with reads that aren't an exact multiple of sectors.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker output file instance. We use the
+ * directory pointer hints and child index hints
+ * @param pIsoMaker The ISO maker.
+ * @param pFile The rock ridge spill file.
+ * @param offInFile The offset into the spill file.
+ * @param pbBuf The output buffer.
+ * @param cbToRead The number of bytes to tread.
+ */
+static int rtFsIsoMakerOutFile_RockRidgeSpillReadUnaligned(PRTFSISOMAKEROUTPUTFILE pThis, PRTFSISOMAKERINT pIsoMaker,
+ PRTFSISOMAKERFILE pFile, uint32_t offInFile, uint8_t *pbBuf,
+ uint32_t cbToRead)
+{
+ for (;;)
+ {
+ /*
+ * Deal with unnaligned file offsets and sub-sector sized reads.
+ */
+ if ( (offInFile & ISO9660_SECTOR_OFFSET_MASK)
+ || cbToRead < ISO9660_SECTOR_SIZE)
+ {
+ uint8_t abSectorBuf[ISO9660_SECTOR_SIZE];
+ int rc = rtFsIsoMakerOutFile_RockRidgeSpillReadSectors(pThis, pIsoMaker, pFile,
+ offInFile & ~(uint32_t)ISO9660_SECTOR_OFFSET_MASK,
+ abSectorBuf, sizeof(abSectorBuf));
+ if (RT_FAILURE(rc))
+ return rc;
+ uint32_t offSrcBuf = (size_t)offInFile & (size_t)ISO9660_SECTOR_OFFSET_MASK;
+ uint32_t cbToCopy = RT_MIN(ISO9660_SECTOR_SIZE - offSrcBuf, cbToRead);
+ memcpy(pbBuf, &abSectorBuf[offSrcBuf], cbToCopy);
+ if (cbToCopy >= cbToRead)
+ return VINF_SUCCESS;
+ cbToRead -= cbToCopy;
+ offInFile += cbToCopy;
+ pbBuf += cbToCopy;
+ }
+
+ /*
+ * The offset is aligned now, so try read some sectors directly into the buffer.
+ */
+ AssertContinue((offInFile & ISO9660_SECTOR_OFFSET_MASK) == 0);
+ if (cbToRead >= ISO9660_SECTOR_SIZE)
+ {
+ uint32_t cbFullSectors = cbToRead & ~(uint32_t)ISO9660_SECTOR_OFFSET_MASK;
+ int rc = rtFsIsoMakerOutFile_RockRidgeSpillReadSectors(pThis, pIsoMaker, pFile, offInFile, pbBuf, cbFullSectors);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (cbFullSectors >= cbToRead)
+ return VINF_SUCCESS;
+ cbToRead -= cbFullSectors;
+ offInFile += cbFullSectors;
+ pbBuf += cbFullSectors;
+ }
+ }
+}
+
+
+
+/**
+ * Produces the content of a TRANS.TBL file as a memory file.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker output file instance. The file is
+ * returned as pThis->hVfsSrcFile.
+ * @param pFile The TRANS.TBL file.
+ */
+static int rtFsIsoMakerOutFile_ProduceTransTbl(PRTFSISOMAKEROUTPUTFILE pThis, PRTFSISOMAKERFILE pFile)
+{
+ /*
+ * Create memory file instance.
+ */
+ RTVFSFILE hVfsFile;
+ int rc = RTVfsMemFileCreate(NIL_RTVFSIOSTREAM, pFile->cbData, &hVfsFile);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Produce the file content.
+ */
+ PRTFSISOMAKERNAME *ppChild = pFile->u.pTransTblDir->pDir->papChildren;
+ uint32_t cLeft = pFile->u.pTransTblDir->pDir->cChildren;
+ while (cLeft-- > 0)
+ {
+ PRTFSISOMAKERNAME pChild = *ppChild++;
+ if (pChild->cchTransNm)
+ {
+ /** @todo TRANS.TBL codeset, currently using UTF-8 which is probably not it.
+ * However, nobody uses this stuff any more, so who cares. */
+ char szEntry[RTFSISOMAKER_MAX_NAME_BUF * 2 + 128];
+ size_t cchEntry = RTStrPrintf(szEntry, sizeof(szEntry), "%c %-*s\t%s\n", pChild->pDir ? 'D' : 'F',
+ RTFSISOMAKER_TRANS_TBL_LEFT_PAD, pChild->szName, pChild->pszTransNm);
+ rc = RTVfsFileWrite(hVfsFile, szEntry, cchEntry, NULL);
+ if (RT_FAILURE(rc))
+ {
+ RTVfsFileRelease(hVfsFile);
+ return rc;
+ }
+ }
+ }
+
+ /*
+ * Check that the size matches our estimate.
+ */
+ uint64_t cbResult = 0;
+ rc = RTVfsFileQuerySize(hVfsFile, &cbResult);
+ if (RT_SUCCESS(rc) && cbResult == pFile->cbData)
+ {
+ pThis->hVfsSrcFile = hVfsFile;
+ return VINF_SUCCESS;
+ }
+
+ AssertMsgFailed(("rc=%Rrc, cbResult=%#RX64 cbData=%#RX64\n", rc, cbResult, pFile->cbData));
+ RTVfsFileRelease(hVfsFile);
+ return VERR_ISOMK_IPE_PRODUCE_TRANS_TBL;
+}
+
+
+
+/**
+ * Reads file data.
+ *
+ * @returns IPRT status code
+ * @param pThis The instance data for the VFS file. We use this to
+ * keep hints about where we are and we which source
+ * file we've opened/created.
+ * @param pIsoMaker The ISO maker instance.
+ * @param offUnsigned The ISO image byte offset of the requested data.
+ * @param pbBuf The output buffer.
+ * @param cbBuf How much to read.
+ * @param pcbDone Where to return how much was read.
+ */
+static int rtFsIsoMakerOutFile_ReadFileData(PRTFSISOMAKEROUTPUTFILE pThis, PRTFSISOMAKERINT pIsoMaker, uint64_t offUnsigned,
+ uint8_t *pbBuf, size_t cbBuf, size_t *pcbDone)
+{
+ *pcbDone = 0;
+
+ /*
+ * Figure out which file. We keep a hint in the instance.
+ */
+ uint64_t offInFile;
+ PRTFSISOMAKERFILE pFile = pThis->pFileHint;
+ if (!pFile)
+ {
+ pFile = RTListGetFirst(&pIsoMaker->FinalizedFiles, RTFSISOMAKERFILE, FinalizedEntry);
+ AssertReturn(pFile, VERR_ISOMK_IPE_READ_FILE_DATA_1);
+ }
+ if ((offInFile = offUnsigned - pFile->offData) < RT_ALIGN_64(pFile->cbData, RTFSISOMAKER_SECTOR_SIZE))
+ { /* hit */ }
+ else if (offUnsigned >= pFile->offData)
+ {
+ /* Seek forwards. */
+ do
+ {
+ pFile = RTListGetNext(&pIsoMaker->FinalizedFiles, pFile, RTFSISOMAKERFILE, FinalizedEntry);
+ AssertReturn(pFile, VERR_ISOMK_IPE_READ_FILE_DATA_2);
+ } while ((offInFile = offUnsigned - pFile->offData) >= RT_ALIGN_64(pFile->cbData, RTFSISOMAKER_SECTOR_SIZE));
+ }
+ else
+ {
+ /* Seek backwards. */
+ do
+ {
+ pFile = RTListGetPrev(&pIsoMaker->FinalizedFiles, pFile, RTFSISOMAKERFILE, FinalizedEntry);
+ AssertReturn(pFile, VERR_ISOMK_IPE_READ_FILE_DATA_3);
+ } while ((offInFile = offUnsigned - pFile->offData) >= RT_ALIGN_64(pFile->cbData, RTFSISOMAKER_SECTOR_SIZE));
+ }
+
+ /*
+ * Update the hint/current file.
+ */
+ if (pThis->pFileHint != pFile)
+ {
+ pThis->pFileHint = pFile;
+ if (pThis->hVfsSrcFile != NIL_RTVFSFILE)
+ {
+ RTVfsFileRelease(pThis->hVfsSrcFile);
+ pThis->hVfsSrcFile = NIL_RTVFSFILE;
+ }
+ }
+
+ /*
+ * Produce data bits according to the source type.
+ */
+ if (offInFile < pFile->cbData)
+ {
+ int rc;
+ size_t cbToRead = RT_MIN(cbBuf, pFile->cbData - offInFile);
+
+ switch (pFile->enmSrcType)
+ {
+ case RTFSISOMAKERSRCTYPE_PATH:
+ if (pThis->hVfsSrcFile == NIL_RTVFSFILE)
+ {
+ rc = RTVfsChainOpenFile(pFile->u.pszSrcPath, RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ &pThis->hVfsSrcFile, NULL, NULL);
+ AssertMsgRCReturn(rc, ("%s -> %Rrc\n", pFile->u.pszSrcPath, rc), rc);
+ }
+ rc = RTVfsFileReadAt(pThis->hVfsSrcFile, offInFile, pbBuf, cbToRead, NULL);
+ AssertRC(rc);
+ break;
+
+ case RTFSISOMAKERSRCTYPE_VFS_FILE:
+ rc = RTVfsFileReadAt(pFile->u.hVfsFile, offInFile, pbBuf, cbToRead, NULL);
+ AssertRC(rc);
+ break;
+
+ case RTFSISOMAKERSRCTYPE_COMMON:
+ rc = RTVfsFileReadAt(pIsoMaker->paCommonSources[pFile->u.Common.idxSrc],
+ pFile->u.Common.offData + offInFile, pbBuf, cbToRead, NULL);
+ AssertRC(rc);
+ break;
+
+ case RTFSISOMAKERSRCTYPE_TRANS_TBL:
+ if (pThis->hVfsSrcFile == NIL_RTVFSFILE)
+ {
+ rc = rtFsIsoMakerOutFile_ProduceTransTbl(pThis, pFile);
+ AssertRCReturn(rc, rc);
+ }
+ rc = RTVfsFileReadAt(pThis->hVfsSrcFile, offInFile, pbBuf, cbToRead, NULL);
+ AssertRC(rc);
+ break;
+
+ case RTFSISOMAKERSRCTYPE_RR_SPILL:
+ Assert(pFile->cbData < UINT32_MAX);
+ if ( !(offInFile & ISO9660_SECTOR_OFFSET_MASK)
+ && !(cbToRead & ISO9660_SECTOR_OFFSET_MASK)
+ && cbToRead > 0)
+ rc = rtFsIsoMakerOutFile_RockRidgeSpillReadSectors(pThis, pIsoMaker, pFile, (uint32_t)offInFile,
+ pbBuf, (uint32_t)cbToRead);
+ else
+ rc = rtFsIsoMakerOutFile_RockRidgeSpillReadUnaligned(pThis, pIsoMaker, pFile, (uint32_t)offInFile,
+ pbBuf, (uint32_t)cbToRead);
+ break;
+
+ default:
+ AssertFailedReturn(VERR_IPE_NOT_REACHED_DEFAULT_CASE);
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+ *pcbDone = cbToRead;
+
+ /*
+ * Do boot info table patching.
+ */
+ if ( pFile->pBootInfoTable
+ && offInFile < 64
+ && offInFile + cbToRead > 8)
+ {
+ size_t offInBuf = offInFile < 8 ? 8 - (size_t)offInFile : 0;
+ size_t offInTab = offInFile <= 8 ? 0 : (size_t)offInFile - 8;
+ size_t cbToCopy = RT_MIN(sizeof(*pFile->pBootInfoTable) - offInTab, cbToRead - offInBuf);
+ memcpy(&pbBuf[offInBuf], (uint8_t *)pFile->pBootInfoTable + offInTab, cbToCopy);
+ }
+
+ /*
+ * Check if we're into the zero padding at the end of the file now.
+ */
+ if ( cbToRead < cbBuf
+ && (pFile->cbData & RTFSISOMAKER_SECTOR_OFFSET_MASK)
+ && offInFile + cbToRead == pFile->cbData)
+ {
+ cbBuf -= cbToRead;
+ pbBuf += cbToRead;
+ size_t cbZeros = RT_MIN(cbBuf, RTFSISOMAKER_SECTOR_SIZE - (pFile->cbData & RTFSISOMAKER_SECTOR_OFFSET_MASK));
+ memset(pbBuf, 0, cbZeros);
+ *pcbDone += cbZeros;
+ }
+ }
+ else
+ {
+ size_t cbZeros = RT_MIN(cbBuf, RT_ALIGN_64(pFile->cbData, RTFSISOMAKER_SECTOR_SIZE) - offInFile);
+ memset(pbBuf, 0, cbZeros);
+ *pcbDone = cbZeros;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Generates ISO-9660 path table record into the specified buffer.
+ *
+ * @returns Number of bytes copied into the buffer.
+ * @param pName The directory namespace node.
+ * @param fUnicode Set if the name should be translated to big endian
+ * UTF-16 / UCS-2, i.e. we're in the joliet namespace.
+ * @param pbBuf The buffer. This is large enough to hold the path
+ * record (use RTFSISOMAKER_CALC_PATHREC_SIZE) and a zero
+ * RTUTF16 terminator if @a fUnicode is true.
+ */
+static uint32_t rtFsIsoMakerOutFile_GeneratePathRec(PRTFSISOMAKERNAME pName, bool fUnicode, bool fLittleEndian, uint8_t *pbBuf)
+{
+ PISO9660PATHREC pPathRec = (PISO9660PATHREC)pbBuf;
+ pPathRec->cbDirId = pName->cbNameInDirRec;
+ pPathRec->cbExtAttr = 0;
+ if (fLittleEndian)
+ {
+ pPathRec->offExtent = RT_H2LE_U32(pName->pDir->offDir / RTFSISOMAKER_SECTOR_SIZE);
+ pPathRec->idParentRec = RT_H2LE_U16(pName->pParent ? pName->pParent->pDir->idPathTable : 1);
+ }
+ else
+ {
+ pPathRec->offExtent = RT_H2BE_U32(pName->pDir->offDir / RTFSISOMAKER_SECTOR_SIZE);
+ pPathRec->idParentRec = RT_H2BE_U16(pName->pParent ? pName->pParent->pDir->idPathTable : 1);
+ }
+ if (!fUnicode)
+ {
+ memcpy(&pPathRec->achDirId[0], pName->szName, pName->cbNameInDirRec);
+ if (pName->cbNameInDirRec & 1)
+ pPathRec->achDirId[pName->cbNameInDirRec] = '\0';
+ }
+ else
+ {
+ /* Caller made sure there is space for a zero terminator character. */
+ PRTUTF16 pwszTmp = (PRTUTF16)&pPathRec->achDirId[0];
+ size_t cwcResult = 0;
+ int rc = RTStrToUtf16BigEx(pName->szName, RTSTR_MAX, &pwszTmp, pName->cbNameInDirRec / sizeof(RTUTF16) + 1, &cwcResult);
+ AssertRC(rc);
+ Assert( cwcResult * sizeof(RTUTF16) == pName->cbNameInDirRec
+ || (!pName->pParent && cwcResult == 0 && pName->cbNameInDirRec == 1) );
+
+ }
+ return RTFSISOMAKER_CALC_PATHREC_SIZE(pName->cbNameInDirRec);
+}
+
+
+/**
+ * Deals with situations where the destination buffer doesn't cover the whole
+ * path table record.
+ *
+ * @returns Number of bytes copied into the buffer.
+ * @param pName The directory namespace node.
+ * @param fUnicode Set if the name should be translated to big endian
+ * UTF-16 / UCS-2, i.e. we're in the joliet namespace.
+ * @param offInRec The offset into the path table record.
+ * @param pbBuf The buffer.
+ * @param cbBuf The buffer size.
+ */
+static uint32_t rtFsIsoMakerOutFile_GeneratePathRecPartial(PRTFSISOMAKERNAME pName, bool fUnicode, bool fLittleEndian,
+ uint32_t offInRec, uint8_t *pbBuf, size_t cbBuf)
+{
+ uint8_t abTmpRec[256];
+ size_t cbToCopy = rtFsIsoMakerOutFile_GeneratePathRec(pName, fUnicode, fLittleEndian, abTmpRec);
+ cbToCopy = RT_MIN(cbBuf, cbToCopy - offInRec);
+ memcpy(pbBuf, &abTmpRec[offInRec], cbToCopy);
+ return (uint32_t)cbToCopy;
+}
+
+
+/**
+ * Generate path table records.
+ *
+ * This will generate record up to the end of the table. However, it will not
+ * supply the zero padding in the last sector, the caller is expected to take
+ * care of that.
+ *
+ * @returns Number of bytes written to the buffer.
+ * @param ppDirHint Pointer to the directory hint for the namespace.
+ * @param pFinalizedDirs The finalized directory data for the namespace.
+ * @param fUnicode Set if the name should be translated to big endian
+ * UTF-16 / UCS-2, i.e. we're in the joliet namespace.
+ * @param fLittleEndian Set if we're generating little endian records, clear
+ * if big endian records.
+ * @param offInTable Offset into the path table.
+ * @param pbBuf The output buffer.
+ * @param cbBuf The buffer size.
+ */
+static size_t rtFsIsoMakerOutFile_ReadPathTable(PRTFSISOMAKERNAMEDIR *ppDirHint, PRTFSISOMAKERFINALIZEDDIRS pFinalizedDirs,
+ bool fUnicode, bool fLittleEndian, uint32_t offInTable,
+ uint8_t *pbBuf, size_t cbBuf)
+{
+ /*
+ * Figure out which directory to start with. We keep a hint in the instance.
+ */
+ PRTFSISOMAKERNAMEDIR pDir = *ppDirHint;
+ if (!pDir)
+ {
+ pDir = RTListGetFirst(&pFinalizedDirs->FinalizedDirs, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ AssertReturnStmt(pDir, *pbBuf = 0xff, 1);
+ }
+ if (offInTable - pDir->offPathTable < RTFSISOMAKER_CALC_PATHREC_SIZE(pDir->pName->cbNameInDirRec))
+ { /* hit */ }
+ /* Seek forwards: */
+ else if (offInTable > pDir->offPathTable)
+ do
+ {
+ pDir = RTListGetNext(&pFinalizedDirs->FinalizedDirs, pDir, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ AssertReturnStmt(pDir, *pbBuf = 0xff, 1);
+ } while (offInTable - pDir->offPathTable >= RTFSISOMAKER_CALC_PATHREC_SIZE(pDir->pName->cbNameInDirRec));
+ /* Back to the start: */
+ else if (offInTable == 0)
+ {
+ pDir = RTListGetFirst(&pFinalizedDirs->FinalizedDirs, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ AssertReturnStmt(pDir, *pbBuf = 0xff, 1);
+ }
+ /* Seek backwards: */
+ else
+ do
+ {
+ pDir = RTListGetPrev(&pFinalizedDirs->FinalizedDirs, pDir, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ AssertReturnStmt(pDir, *pbBuf = 0xff, 1);
+ } while (offInTable - pDir->offPathTable >= RTFSISOMAKER_CALC_PATHREC_SIZE(pDir->pName->cbNameInDirRec));
+
+ /*
+ * Generate content.
+ */
+ size_t cbDone = 0;
+ while ( cbBuf > 0
+ && pDir)
+ {
+ PRTFSISOMAKERNAME pName = pDir->pName;
+ uint8_t cbRec = RTFSISOMAKER_CALC_PATHREC_SIZE(pName->cbNameInDirRec);
+ uint32_t cbCopied;
+ if ( offInTable == pDir->offPathTable
+ && cbBuf >= cbRec + fUnicode * 2U)
+ cbCopied = rtFsIsoMakerOutFile_GeneratePathRec(pName, fUnicode, fLittleEndian, pbBuf);
+ else
+ cbCopied = rtFsIsoMakerOutFile_GeneratePathRecPartial(pName, fUnicode, fLittleEndian,
+ offInTable - pDir->offPathTable, pbBuf, cbBuf);
+ cbDone += cbCopied;
+ offInTable += cbCopied;
+ pbBuf += cbCopied;
+ cbBuf -= cbCopied;
+ pDir = RTListGetNext(&pFinalizedDirs->FinalizedDirs, pDir, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ }
+
+ /*
+ * Update the hint.
+ */
+ *ppDirHint = pDir;
+
+ return cbDone;
+}
+
+
+/**
+ * Generates ISO-9660 directory record into the specified buffer.
+ *
+ * The caller must deal with multi-extent copying and end of sector zero
+ * padding.
+ *
+ * @returns Number of bytes copied into the buffer (pName->cbDirRec).
+ * @param pName The namespace node.
+ * @param fUnicode Set if the name should be translated to big endian
+ * UTF-16BE / UCS-2BE, i.e. we're in the joliet namespace.
+ * @param pbBuf The buffer. This is at least pName->cbDirRec bytes
+ * big (i.e. at most 256 bytes).
+ * @param pFinalizedDirs The finalized directory data for the namespace.
+ * @param enmDirType The kind of directory entry this is.
+ */
+static uint32_t rtFsIsoMakerOutFile_GenerateDirRec(PRTFSISOMAKERNAME pName, bool fUnicode, uint8_t *pbBuf,
+ PRTFSISOMAKERFINALIZEDDIRS pFinalizedDirs, RTFSISOMAKERDIRTYPE enmDirType)
+{
+ /*
+ * Emit a standard ISO-9660 directory record.
+ */
+ PISO9660DIRREC pDirRec = (PISO9660DIRREC)pbBuf;
+ PCRTFSISOMAKEROBJ pObj = pName->pObj;
+ PCRTFSISOMAKERNAMEDIR pDir = pName->pDir;
+ if (pDir)
+ {
+ pDirRec->offExtent.be = RT_H2BE_U32(pDir->offDir / RTFSISOMAKER_SECTOR_SIZE);
+ pDirRec->offExtent.le = RT_H2LE_U32(pDir->offDir / RTFSISOMAKER_SECTOR_SIZE);
+ pDirRec->cbData.be = RT_H2BE_U32(pDir->cbDir);
+ pDirRec->cbData.le = RT_H2LE_U32(pDir->cbDir);
+ pDirRec->fFileFlags = ISO9660_FILE_FLAGS_DIRECTORY;
+ }
+ else if (pObj->enmType == RTFSISOMAKEROBJTYPE_FILE)
+ {
+ PRTFSISOMAKERFILE pFile = (PRTFSISOMAKERFILE)pObj;
+ pDirRec->offExtent.be = RT_H2BE_U32(pFile->offData / RTFSISOMAKER_SECTOR_SIZE);
+ pDirRec->offExtent.le = RT_H2LE_U32(pFile->offData / RTFSISOMAKER_SECTOR_SIZE);
+ pDirRec->cbData.be = RT_H2BE_U32(pFile->cbData);
+ pDirRec->cbData.le = RT_H2LE_U32(pFile->cbData);
+ pDirRec->fFileFlags = 0;
+ }
+ else
+ {
+ pDirRec->offExtent.be = 0;
+ pDirRec->offExtent.le = 0;
+ pDirRec->cbData.be = 0;
+ pDirRec->cbData.le = 0;
+ pDirRec->fFileFlags = 0;
+ }
+ rtFsIsoMakerTimespecToIso9660RecTimestamp(&pObj->BirthTime, &pDirRec->RecTime);
+
+ pDirRec->cbDirRec = pName->cbDirRec;
+ pDirRec->cExtAttrBlocks = 0;
+ pDirRec->bFileUnitSize = 0;
+ pDirRec->bInterleaveGapSize = 0;
+ pDirRec->VolumeSeqNo.be = RT_H2BE_U16_C(1);
+ pDirRec->VolumeSeqNo.le = RT_H2LE_U16_C(1);
+ pDirRec->bFileIdLength = pName->cbNameInDirRec;
+
+ if (!fUnicode)
+ {
+ memcpy(&pDirRec->achFileId[0], pName->szName, pName->cbNameInDirRec);
+ if (!(pName->cbNameInDirRec & 1))
+ pDirRec->achFileId[pName->cbNameInDirRec] = '\0';
+ }
+ else
+ {
+ /* Convert to big endian UTF-16. We're using a separate buffer here
+ because of zero terminator (none in pDirRec) and misalignment. */
+ RTUTF16 wszTmp[128];
+ PRTUTF16 pwszTmp = &wszTmp[0];
+ size_t cwcResult = 0;
+ int rc = RTStrToUtf16BigEx(pName->szName, RTSTR_MAX, &pwszTmp, RT_ELEMENTS(wszTmp), &cwcResult);
+ AssertRC(rc);
+ Assert( cwcResult * sizeof(RTUTF16) == pName->cbNameInDirRec
+ || (!pName->pParent && cwcResult == 0 && pName->cbNameInDirRec == 1) );
+ memcpy(&pDirRec->achFileId[0], pwszTmp, pName->cbNameInDirRec);
+ pDirRec->achFileId[pName->cbNameInDirRec] = '\0';
+ }
+
+ /*
+ * Rock ridge fields if enabled.
+ */
+ if (pName->cbRockInDirRec > 0)
+ {
+ uint8_t *pbSys = (uint8_t *)&pDirRec->achFileId[pName->cbNameInDirRec + !(pName->cbNameInDirRec & 1)];
+ size_t cbSys = &pbBuf[pName->cbDirRec] - pbSys;
+ Assert(cbSys >= pName->cbRockInDirRec);
+ if (cbSys > pName->cbRockInDirRec)
+ RT_BZERO(&pbSys[pName->cbRockInDirRec], cbSys - pName->cbRockInDirRec);
+ if (pName->cbRockSpill == 0)
+ rtFsIosMakerOutFile_GenerateRockRidge(pName, pbSys, cbSys, false /*fInSpill*/, enmDirType);
+ else
+ {
+ /* Maybe emit SP and RR entry, before emitting the CE entry. */
+ if (pName->pParent == NULL)
+ {
+ PISO9660SUSPSP pSP = (PISO9660SUSPSP)pbSys;
+ pSP->Hdr.bSig1 = ISO9660SUSPSP_SIG1;
+ pSP->Hdr.bSig2 = ISO9660SUSPSP_SIG2;
+ pSP->Hdr.cbEntry = ISO9660SUSPSP_LEN;
+ pSP->Hdr.bVersion = ISO9660SUSPSP_VER;
+ pSP->bCheck1 = ISO9660SUSPSP_CHECK1;
+ pSP->bCheck2 = ISO9660SUSPSP_CHECK2;
+ pSP->cbSkip = 0;
+ pbSys += sizeof(*pSP);
+ cbSys -= sizeof(*pSP);
+ }
+ if (pName->fRockNeedRRInDirRec)
+ {
+ PISO9660RRIPRR pRR = (PISO9660RRIPRR)pbSys;
+ pRR->Hdr.bSig1 = ISO9660RRIPRR_SIG1;
+ pRR->Hdr.bSig2 = ISO9660RRIPRR_SIG2;
+ pRR->Hdr.cbEntry = ISO9660RRIPRR_LEN;
+ pRR->Hdr.bVersion = ISO9660RRIPRR_VER;
+ pRR->fFlags = pName->fRockEntries;
+ pbSys += sizeof(*pRR);
+ cbSys -= sizeof(*pRR);
+ }
+ PISO9660SUSPCE pCE = (PISO9660SUSPCE)pbSys;
+ pCE->Hdr.bSig1 = ISO9660SUSPCE_SIG1;
+ pCE->Hdr.bSig2 = ISO9660SUSPCE_SIG2;
+ pCE->Hdr.cbEntry = ISO9660SUSPCE_LEN;
+ pCE->Hdr.bVersion = ISO9660SUSPCE_VER;
+ uint64_t offData = pFinalizedDirs->pRRSpillFile->offData + pName->offRockSpill;
+ pCE->offBlock.be = RT_H2BE_U32((uint32_t)(offData / ISO9660_SECTOR_SIZE));
+ pCE->offBlock.le = RT_H2LE_U32((uint32_t)(offData / ISO9660_SECTOR_SIZE));
+ pCE->offData.be = RT_H2BE_U32((uint32_t)(offData & ISO9660_SECTOR_OFFSET_MASK));
+ pCE->offData.le = RT_H2LE_U32((uint32_t)(offData & ISO9660_SECTOR_OFFSET_MASK));
+ pCE->cbData.be = RT_H2BE_U32((uint32_t)pName->cbRockSpill);
+ pCE->cbData.le = RT_H2LE_U32((uint32_t)pName->cbRockSpill);
+ Assert(cbSys >= sizeof(*pCE));
+ }
+ }
+
+ return pName->cbDirRec;
+}
+
+
+/**
+ * Generates ISO-9660 directory records into the specified buffer.
+ *
+ * @returns Number of bytes copied into the buffer.
+ * @param pName The namespace node.
+ * @param fUnicode Set if the name should be translated to big endian
+ * UTF-16BE / UCS-2BE, i.e. we're in the joliet namespace.
+ * @param pbBuf The buffer. This is at least pName->cbDirRecTotal
+ * bytes big.
+ * @param pFinalizedDirs The finalized directory data for the namespace.
+ */
+static uint32_t rtFsIsoMakerOutFile_GenerateDirRecDirect(PRTFSISOMAKERNAME pName, bool fUnicode, uint8_t *pbBuf,
+ PRTFSISOMAKERFINALIZEDDIRS pFinalizedDirs)
+{
+ /*
+ * Normally there is just a single record without any zero padding.
+ */
+ uint32_t cbReturn = rtFsIsoMakerOutFile_GenerateDirRec(pName, fUnicode, pbBuf, pFinalizedDirs, RTFSISOMAKERDIRTYPE_OTHER);
+ if (RT_LIKELY(pName->cbDirRecTotal == cbReturn))
+ return cbReturn;
+ Assert(cbReturn < pName->cbDirRecTotal);
+
+ /*
+ * Deal with multiple records.
+ */
+ if (pName->cDirRecs > 1)
+ {
+ Assert(pName->pObj->enmType == RTFSISOMAKEROBJTYPE_FILE);
+ PRTFSISOMAKERFILE pFile = (PRTFSISOMAKERFILE)pName->pObj;
+
+ /* Set max size and duplicate the first directory record cDirRecs - 1 times. */
+ uint32_t const cbOne = cbReturn;
+ PISO9660DIRREC pDirRec = (PISO9660DIRREC)pbBuf;
+ pDirRec->cbData.be = RT_H2BE_U32_C(RTFSISOMAKER_MAX_ISO9660_EXTENT_SIZE);
+ pDirRec->cbData.le = RT_H2LE_U32_C(RTFSISOMAKER_MAX_ISO9660_EXTENT_SIZE);
+ pDirRec->fFileFlags |= ISO9660_FILE_FLAGS_MULTI_EXTENT;
+
+ PISO9660DIRREC pCurDirRec = pDirRec;
+ uint32_t offExtent = (uint32_t)(pFile->offData / RTFSISOMAKER_SECTOR_SIZE);
+ Assert(offExtent == ISO9660_GET_ENDIAN(&pDirRec->offExtent));
+ for (uint32_t iDirRec = 1; iDirRec < pName->cDirRecs; iDirRec++)
+ {
+ pCurDirRec = (PISO9660DIRREC)memcpy(&pbBuf[cbReturn], pDirRec, cbOne);
+
+ offExtent += RTFSISOMAKER_MAX_ISO9660_EXTENT_SIZE / RTFSISOMAKER_SECTOR_SIZE;
+ pCurDirRec->offExtent.le = RT_H2LE_U32(offExtent);
+
+ cbReturn += cbOne;
+ }
+ Assert(cbReturn <= pName->cbDirRecTotal);
+
+ /* Adjust the size in the final record. */
+ uint32_t cbDataLast = (uint32_t)(pFile->cbData % RTFSISOMAKER_MAX_ISO9660_EXTENT_SIZE);
+ pCurDirRec->cbData.be = RT_H2BE_U32(cbDataLast);
+ pCurDirRec->cbData.le = RT_H2LE_U32(cbDataLast);
+ pCurDirRec->fFileFlags &= ~ISO9660_FILE_FLAGS_MULTI_EXTENT;
+ }
+
+ /*
+ * Do end of sector zero padding.
+ */
+ if (cbReturn < pName->cbDirRecTotal)
+ memset(&pbBuf[cbReturn], 0, (uint32_t)pName->cbDirRecTotal - cbReturn);
+
+ return pName->cbDirRecTotal;
+}
+
+
+/**
+ * Deals with situations where the destination buffer doesn't cover the whole
+ * directory record.
+ *
+ * @returns Number of bytes copied into the buffer.
+ * @param pName The namespace node.
+ * @param fUnicode Set if the name should be translated to big endian
+ * UTF-16 / UCS-2, i.e. we're in the joliet namespace.
+ * @param off The offset into the directory record.
+ * @param pbBuf The buffer.
+ * @param cbBuf The buffer size.
+ * @param pFinalizedDirs The finalized directory data for the namespace.
+ */
+static uint32_t rtFsIsoMakerOutFile_GenerateDirRecPartial(PRTFSISOMAKERNAME pName, bool fUnicode,
+ uint32_t off, uint8_t *pbBuf, size_t cbBuf,
+ PRTFSISOMAKERFINALIZEDDIRS pFinalizedDirs)
+{
+ Assert(off < pName->cbDirRecTotal);
+
+ /*
+ * This is reasonably simple when there is only one directory record and
+ * without any padding.
+ */
+ uint8_t abTmpBuf[256];
+ Assert(pName->cbDirRec <= sizeof(abTmpBuf));
+ uint32_t const cbOne = rtFsIsoMakerOutFile_GenerateDirRec(pName, fUnicode, abTmpBuf,
+ pFinalizedDirs, RTFSISOMAKERDIRTYPE_OTHER);
+ Assert(cbOne == pName->cbDirRec);
+ if (cbOne == pName->cbDirRecTotal)
+ {
+ uint32_t cbToCopy = RT_MIN((uint32_t)cbBuf, cbOne - off);
+ memcpy(pbBuf, &abTmpBuf[off], cbToCopy);
+ return cbToCopy;
+ }
+ Assert(cbOne < pName->cbDirRecTotal);
+
+ /*
+ * Single record and zero padding?
+ */
+ uint32_t cbCopied = 0;
+ if (pName->cDirRecs == 1)
+ {
+ /* Anything from the record to copy? */
+ if (off < cbOne)
+ {
+ cbCopied = RT_MIN((uint32_t)cbBuf, cbOne - off);
+ memcpy(pbBuf, &abTmpBuf[off], cbCopied);
+ pbBuf += cbCopied;
+ cbBuf -= cbCopied;
+ off += cbCopied;
+ }
+
+ /* Anything from the zero padding? */
+ if (off >= cbOne && cbBuf > 0)
+ {
+ uint32_t cbToZero = RT_MIN((uint32_t)cbBuf, (uint32_t)pName->cbDirRecTotal - off);
+ memset(pbBuf, 0, cbToZero);
+ cbCopied += cbToZero;
+ }
+ }
+ /*
+ * Multi-extent stuff. Need to modify the cbData member as we copy.
+ */
+ else
+ {
+ Assert(pName->pObj->enmType == RTFSISOMAKEROBJTYPE_FILE);
+ PRTFSISOMAKERFILE pFile = (PRTFSISOMAKERFILE)pName->pObj;
+
+ /* Max out the size. */
+ PISO9660DIRREC pDirRec = (PISO9660DIRREC)abTmpBuf;
+ pDirRec->cbData.be = RT_H2BE_U32_C(RTFSISOMAKER_MAX_ISO9660_EXTENT_SIZE);
+ pDirRec->cbData.le = RT_H2LE_U32_C(RTFSISOMAKER_MAX_ISO9660_EXTENT_SIZE);
+ pDirRec->fFileFlags |= ISO9660_FILE_FLAGS_MULTI_EXTENT;
+
+ /* Copy directory records. */
+ uint32_t offDirRec = pName->offDirRec;
+ uint32_t offExtent = pFile->offData / RTFSISOMAKER_SECTOR_SIZE;
+ for (uint32_t i = 0; i < pName->cDirRecs && cbBuf > 0; i++)
+ {
+ uint32_t const offInRec = off - offDirRec;
+ if (offInRec < cbOne)
+ {
+ /* Update the record. */
+ pDirRec->offExtent.be = RT_H2BE_U32(offExtent);
+ pDirRec->offExtent.le = RT_H2LE_U32(offExtent);
+ if (i + 1 == pName->cDirRecs)
+ {
+ uint32_t cbDataLast = pFile->cbData % RTFSISOMAKER_MAX_ISO9660_EXTENT_SIZE;
+ pDirRec->cbData.be = RT_H2BE_U32(cbDataLast);
+ pDirRec->cbData.le = RT_H2LE_U32(cbDataLast);
+ pDirRec->fFileFlags &= ~ISO9660_FILE_FLAGS_MULTI_EXTENT;
+ }
+
+ /* Copy chunk. */
+ uint32_t cbToCopy = RT_MIN((uint32_t)cbBuf, cbOne - offInRec);
+ memcpy(pbBuf, &abTmpBuf[offInRec], cbToCopy);
+ cbCopied += cbToCopy;
+ pbBuf += cbToCopy;
+ cbBuf -= cbToCopy;
+ off += cbToCopy;
+ }
+
+ offDirRec += cbOne;
+ offExtent += RTFSISOMAKER_MAX_ISO9660_EXTENT_SIZE / RTFSISOMAKER_SECTOR_SIZE;
+ }
+
+ /* Anything from the zero padding? */
+ if (off >= offDirRec && cbBuf > 0)
+ {
+ uint32_t cbToZero = RT_MIN((uint32_t)cbBuf, (uint32_t)pName->cbDirRecTotal - offDirRec);
+ memset(pbBuf, 0, cbToZero);
+ cbCopied += cbToZero;
+ }
+ }
+
+ return cbCopied;
+}
+
+
+/**
+ * Generate a '.' or '..' directory record.
+ *
+ * This is the same as rtFsIsoMakerOutFile_GenerateDirRec, but with the filename
+ * reduced to 1 byte.
+ *
+ * @returns Number of bytes copied into the buffer.
+ * @param pName The directory namespace node.
+ * @param fUnicode Set if the name should be translated to big endian
+ * UTF-16 / UCS-2, i.e. we're in the joliet namespace.
+ * @param bDirId The directory ID (0x00 or 0x01).
+ * @param off The offset into the directory record.
+ * @param pbBuf The buffer.
+ * @param cbBuf The buffer size.
+ * @param pFinalizedDirs The finalized directory data for the namespace.
+ */
+static uint32_t rtFsIsoMakerOutFile_GenerateSpecialDirRec(PRTFSISOMAKERNAME pName, bool fUnicode, uint8_t bDirId,
+ uint32_t off, uint8_t *pbBuf, size_t cbBuf,
+ PRTFSISOMAKERFINALIZEDDIRS pFinalizedDirs)
+{
+ Assert(off < pName->cbDirRec);
+ Assert(pName->pDir);
+
+ /* Generate a regular directory record. */
+ uint8_t abTmpBuf[256];
+ Assert(off < pName->cbDirRec);
+ size_t cbToCopy = rtFsIsoMakerOutFile_GenerateDirRec(pName, fUnicode, abTmpBuf, pFinalizedDirs,
+ bDirId == 0 ? RTFSISOMAKERDIRTYPE_CURRENT : RTFSISOMAKERDIRTYPE_PARENT);
+ Assert(cbToCopy == pName->cbDirRec);
+
+ /** @todo r=bird: This isn't working quite right as the NM record includes the
+ * full directory name. Spill file stuff is shared with the (grand)parent
+ * directory entry. [RR NM ./..] */
+
+ /* Replace the filename part. */
+ PISO9660DIRREC pDirRec = (PISO9660DIRREC)abTmpBuf;
+ if (pDirRec->bFileIdLength != 1)
+ {
+ uint8_t offSysUse = pDirRec->bFileIdLength + !(pDirRec->bFileIdLength & 1) + RT_UOFFSETOF(ISO9660DIRREC, achFileId);
+ uint8_t cbSysUse = pDirRec->cbDirRec - offSysUse;
+ if (cbSysUse > 0)
+ memmove(&pDirRec->achFileId[1], &abTmpBuf[offSysUse], cbSysUse);
+ pDirRec->bFileIdLength = 1;
+ cbToCopy = RT_UOFFSETOF(ISO9660DIRREC, achFileId) + 1 + cbSysUse;
+ pDirRec->cbDirRec = (uint8_t)cbToCopy;
+ }
+ pDirRec->achFileId[0] = bDirId;
+
+ /* Do the copying. */
+ cbToCopy = RT_MIN(cbBuf, cbToCopy - off);
+ memcpy(pbBuf, &abTmpBuf[off], cbToCopy);
+ return (uint32_t)cbToCopy;
+}
+
+
+/**
+ * Read directory records.
+ *
+ * This locates the directory at @a offUnsigned and generates directory records
+ * for it. Caller must repeat the call to get directory entries for the next
+ * directory should there be desire for that.
+ *
+ * @returns Number of bytes copied into @a pbBuf.
+ * @param ppDirHint Pointer to the directory hint for the namespace.
+ * @param pIsoMaker The ISO maker instance.
+ * @param pFinalizedDirs The finalized directory data for the namespace.
+ * @param fUnicode Set if the name should be translated to big endian
+ * UTF-16 / UCS-2, i.e. we're in the joliet namespace.
+ * @param offUnsigned The ISO image byte offset of the requested data.
+ * @param pbBuf The output buffer.
+ * @param cbBuf How much to read.
+ */
+static size_t rtFsIsoMakerOutFile_ReadDirRecords(PRTFSISOMAKERNAMEDIR *ppDirHint, PRTFSISOMAKERFINALIZEDDIRS pFinalizedDirs,
+ bool fUnicode, uint64_t offUnsigned, uint8_t *pbBuf, size_t cbBuf)
+{
+ /*
+ * Figure out which directory. We keep a hint in the instance.
+ */
+ uint64_t offInDir64;
+ PRTFSISOMAKERNAMEDIR pDir = *ppDirHint;
+ if (!pDir)
+ {
+ pDir = RTListGetFirst(&pFinalizedDirs->FinalizedDirs, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ AssertReturnStmt(pDir, *pbBuf = 0xff, 1);
+ }
+ if ((offInDir64 = offUnsigned - pDir->offDir) < RT_ALIGN_32(pDir->cbDir, RTFSISOMAKER_SECTOR_SIZE))
+ { /* hit */ }
+ /* Seek forwards: */
+ else if (offUnsigned > pDir->offDir)
+ do
+ {
+ pDir = RTListGetNext(&pFinalizedDirs->FinalizedDirs, pDir, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ AssertReturnStmt(pDir, *pbBuf = 0xff, 1);
+ } while ((offInDir64 = offUnsigned - pDir->offDir) >= RT_ALIGN_32(pDir->cbDir, RTFSISOMAKER_SECTOR_SIZE));
+ /* Back to the start: */
+ else if (pFinalizedDirs->offDirs / RTFSISOMAKER_SECTOR_SIZE == offUnsigned / RTFSISOMAKER_SECTOR_SIZE)
+ {
+ pDir = RTListGetFirst(&pFinalizedDirs->FinalizedDirs, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ AssertReturnStmt(pDir, *pbBuf = 0xff, 1);
+ offInDir64 = offUnsigned - pDir->offDir;
+ }
+ /* Seek backwards: */
+ else
+ do
+ {
+ pDir = RTListGetPrev(&pFinalizedDirs->FinalizedDirs, pDir, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ AssertReturnStmt(pDir, *pbBuf = 0xff, 1);
+ } while ((offInDir64 = offUnsigned - pDir->offDir) >= RT_ALIGN_32(pDir->cbDir, RTFSISOMAKER_SECTOR_SIZE));
+
+ /*
+ * Update the hint.
+ */
+ *ppDirHint = pDir;
+
+ /*
+ * Generate content.
+ */
+ size_t cbDone = 0;
+ uint32_t offInDir = (uint32_t)offInDir64;
+ if (offInDir < pDir->cbDir)
+ {
+ PRTFSISOMAKERNAME pDirName = pDir->pName;
+ PRTFSISOMAKERNAME pParentName = pDirName->pParent ? pDirName->pParent : pDirName;
+ uint32_t cbSpecialRecs = (uint32_t)pDir->cbDirRec00 + pDir->cbDirRec01;
+
+ /*
+ * Special '.' and/or '..' entries requested.
+ */
+ uint32_t iChild;
+ if (offInDir < cbSpecialRecs)
+ {
+ /* do '.' */
+ if (offInDir < pDir->cbDirRec00)
+ {
+ uint32_t cbCopied = rtFsIsoMakerOutFile_GenerateSpecialDirRec(pDirName, fUnicode, 0, offInDir,
+ pbBuf, cbBuf, pFinalizedDirs);
+ cbDone += cbCopied;
+ offInDir += cbCopied;
+ pbBuf += cbCopied;
+ cbBuf -= cbCopied;
+ }
+
+ /* do '..' */
+ if (cbBuf > 0)
+ {
+ uint32_t cbCopied = rtFsIsoMakerOutFile_GenerateSpecialDirRec(pParentName, fUnicode, 1,
+ offInDir - pDir->cbDirRec00,
+ pbBuf, cbBuf, pFinalizedDirs);
+ cbDone += cbCopied;
+ offInDir += cbCopied;
+ pbBuf += cbCopied;
+ cbBuf -= cbCopied;
+ }
+
+ iChild = 0;
+ }
+ /*
+ * Locate the directory entry we should start with. We can do this
+ * using binary searching on offInDir.
+ */
+ else
+ {
+ /** @todo binary search */
+ iChild = 0;
+ while (iChild < pDir->cChildren)
+ {
+ PRTFSISOMAKERNAME pChild = pDir->papChildren[iChild];
+ if ((offInDir - pChild->offDirRec) < pChild->cbDirRecTotal)
+ break;
+ iChild++;
+ }
+ AssertReturnStmt(iChild < pDir->cChildren, *pbBuf = 0xff, 1);
+ }
+
+ /*
+ * Normal directory entries.
+ */
+ while ( cbBuf > 0
+ && iChild < pDir->cChildren)
+ {
+ PRTFSISOMAKERNAME pChild = pDir->papChildren[iChild];
+ uint32_t cbCopied;
+ if ( offInDir == pChild->offDirRec
+ && cbBuf >= pChild->cbDirRecTotal)
+ cbCopied = rtFsIsoMakerOutFile_GenerateDirRecDirect(pChild, fUnicode, pbBuf, pFinalizedDirs);
+ else
+ cbCopied = rtFsIsoMakerOutFile_GenerateDirRecPartial(pChild, fUnicode, offInDir - pChild->offDirRec,
+ pbBuf, cbBuf, pFinalizedDirs);
+
+ cbDone += cbCopied;
+ offInDir += cbCopied;
+ pbBuf += cbCopied;
+ cbBuf -= cbCopied;
+ iChild++;
+ }
+
+ /*
+ * Check if we're into the zero padding at the end of the directory now.
+ */
+ if ( cbBuf > 0
+ && iChild >= pDir->cChildren)
+ {
+ size_t cbZeros = RT_MIN(cbBuf, RTFSISOMAKER_SECTOR_SIZE - (pDir->cbDir & RTFSISOMAKER_SECTOR_OFFSET_MASK));
+ memset(pbBuf, 0, cbZeros);
+ cbDone += cbZeros;
+ }
+ }
+ else
+ {
+ cbDone = RT_MIN(cbBuf, RT_ALIGN_32(pDir->cbDir, RTFSISOMAKER_SECTOR_SIZE) - offInDir);
+ memset(pbBuf, 0, cbDone);
+ }
+
+ return cbDone;
+}
+
+
+/**
+ * Read directory records or path table records.
+ *
+ * Will not necessarily fill the entire buffer. Caller must call again to get
+ * more.
+ *
+ * @returns Number of bytes copied into @a pbBuf.
+ * @param ppDirHint Pointer to the directory hint for the namespace.
+ * @param pIsoMaker The ISO maker instance.
+ * @param pNamespace The namespace.
+ * @param pFinalizedDirs The finalized directory data for the namespace.
+ * @param offUnsigned The ISO image byte offset of the requested data.
+ * @param pbBuf The output buffer.
+ * @param cbBuf How much to read.
+ */
+static size_t rtFsIsoMakerOutFile_ReadDirStructures(PRTFSISOMAKERNAMEDIR *ppDirHint, PRTFSISOMAKERNAMESPACE pNamespace,
+ PRTFSISOMAKERFINALIZEDDIRS pFinalizedDirs,
+ uint64_t offUnsigned, uint8_t *pbBuf, size_t cbBuf)
+{
+ if (offUnsigned < pFinalizedDirs->offPathTableL)
+ return rtFsIsoMakerOutFile_ReadDirRecords(ppDirHint, pFinalizedDirs,
+ pNamespace->fNamespace == RTFSISOMAKER_NAMESPACE_JOLIET,
+ offUnsigned, pbBuf, cbBuf);
+
+ uint64_t offInTable;
+ if ((offInTable = offUnsigned - pFinalizedDirs->offPathTableL) < pFinalizedDirs->cbPathTable)
+ return rtFsIsoMakerOutFile_ReadPathTable(ppDirHint, pFinalizedDirs,
+ pNamespace->fNamespace == RTFSISOMAKER_NAMESPACE_JOLIET,
+ true /*fLittleEndian*/, (uint32_t)offInTable, pbBuf, cbBuf);
+
+ if ((offInTable = offUnsigned - pFinalizedDirs->offPathTableM) < pFinalizedDirs->cbPathTable)
+ return rtFsIsoMakerOutFile_ReadPathTable(ppDirHint, pFinalizedDirs,
+ pNamespace->fNamespace == RTFSISOMAKER_NAMESPACE_JOLIET,
+ false /*fLittleEndian*/, (uint32_t)offInTable, pbBuf, cbBuf);
+
+ /* ASSUME we're in the zero padding at the end of a path table. */
+ Assert( offUnsigned - pFinalizedDirs->offPathTableL < RT_ALIGN_32(pFinalizedDirs->cbPathTable, RTFSISOMAKER_SECTOR_SIZE)
+ || offUnsigned - pFinalizedDirs->offPathTableM < RT_ALIGN_32(pFinalizedDirs->cbPathTable, RTFSISOMAKER_SECTOR_SIZE));
+ size_t cbZeros = RT_MIN(cbBuf, RTFSISOMAKER_SECTOR_SIZE - ((size_t)offUnsigned & RTFSISOMAKER_SECTOR_OFFSET_MASK));
+ memset(pbBuf, 0, cbZeros);
+ return cbZeros;
+}
+
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtFsIsoMakerOutFile_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTFSISOMAKEROUTPUTFILE pThis = (PRTFSISOMAKEROUTPUTFILE)pvThis;
+ PRTFSISOMAKERINT pIsoMaker = pThis->pIsoMaker;
+ size_t cbBuf = pSgBuf->paSegs[0].cbSeg;
+ uint8_t *pbBuf = (uint8_t *)pSgBuf->paSegs[0].pvSeg;
+
+ Assert(pSgBuf->cSegs == 1);
+ RT_NOREF(fBlocking);
+
+ /*
+ * Process the offset, checking for end-of-file.
+ */
+ uint64_t offUnsigned;
+ if (off < 0)
+ offUnsigned = pThis->offCurPos;
+ else
+ offUnsigned = (uint64_t)off;
+ if (offUnsigned >= pIsoMaker->cbFinalizedImage)
+ {
+ if (*pcbRead)
+ {
+ *pcbRead = 0;
+ return VINF_EOF;
+ }
+ return VERR_EOF;
+ }
+ if ( !pcbRead
+ && pIsoMaker->cbFinalizedImage - offUnsigned < cbBuf)
+ return VERR_EOF;
+
+ /*
+ * Produce the bytes.
+ */
+ int rc = VINF_SUCCESS;
+ size_t cbRead = 0;
+ while (cbBuf > 0)
+ {
+ size_t cbDone;
+
+ /* Betting on there being more file data than metadata, thus doing the
+ offset switch in decending order. */
+ if (offUnsigned >= pIsoMaker->offFirstFile)
+ {
+ if (offUnsigned < pIsoMaker->cbFinalizedImage)
+ {
+ if (offUnsigned < pIsoMaker->cbFinalizedImage - pIsoMaker->cbImagePadding)
+ {
+ rc = rtFsIsoMakerOutFile_ReadFileData(pThis, pIsoMaker, offUnsigned, pbBuf, cbBuf, &cbDone);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else
+ {
+ cbDone = pIsoMaker->cbFinalizedImage - offUnsigned;
+ if (cbDone > cbBuf)
+ cbDone = cbBuf;
+ memset(pbBuf, 0, cbDone);
+ }
+ }
+ else
+ {
+ rc = pcbRead ? VINF_EOF : VERR_EOF;
+ break;
+ }
+ }
+ /*
+ * Joliet directory structures.
+ */
+ else if ( offUnsigned >= pIsoMaker->JolietDirs.offDirs
+ && pIsoMaker->JolietDirs.offDirs < pIsoMaker->JolietDirs.offPathTableL)
+ cbDone = rtFsIsoMakerOutFile_ReadDirStructures(&pThis->pDirHintJoliet, &pIsoMaker->Joliet, &pIsoMaker->JolietDirs,
+ offUnsigned, pbBuf, cbBuf);
+ /*
+ * Primary ISO directory structures.
+ */
+ else if (offUnsigned >= pIsoMaker->PrimaryIsoDirs.offDirs)
+ cbDone = rtFsIsoMakerOutFile_ReadDirStructures(&pThis->pDirHintPrimaryIso, &pIsoMaker->PrimaryIso,
+ &pIsoMaker->PrimaryIsoDirs, offUnsigned, pbBuf, cbBuf);
+ /*
+ * Volume descriptors.
+ */
+ else if (offUnsigned >= _32K)
+ {
+ size_t offVolDescs = (size_t)offUnsigned - _32K;
+ cbDone = RT_MIN(cbBuf, (pIsoMaker->cVolumeDescriptors * RTFSISOMAKER_SECTOR_SIZE) - offVolDescs);
+ memcpy(pbBuf, &pIsoMaker->pbVolDescs[offVolDescs], cbDone);
+ }
+ /*
+ * Zeros in the system area.
+ */
+ else if (offUnsigned >= pIsoMaker->cbSysArea)
+ {
+ cbDone = RT_MIN(cbBuf, _32K - (size_t)offUnsigned);
+ memset(pbBuf, 0, cbDone);
+ }
+ /*
+ * Actual data in the system area.
+ */
+ else
+ {
+ cbDone = RT_MIN(cbBuf, pIsoMaker->cbSysArea - (size_t)offUnsigned);
+ memcpy(pbBuf, &pIsoMaker->pbSysArea[(size_t)offUnsigned], cbDone);
+ }
+
+ /*
+ * Common advance.
+ */
+ cbRead += cbDone;
+ offUnsigned += cbDone;
+ pbBuf += cbDone;
+ cbBuf -= cbDone;
+ }
+
+ if (pcbRead)
+ *pcbRead = cbRead;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtFsIsoMakerOutFile_Flush(void *pvThis)
+{
+ RT_NOREF(pvThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtFsIsoMakerOutFile_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTFSISOMAKEROUTPUTFILE pThis = (PRTFSISOMAKEROUTPUTFILE)pvThis;
+ *poffActual = pThis->offCurPos;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnSkip}
+ */
+static DECLCALLBACK(int) rtFsIsoMakerOutFile_Skip(void *pvThis, RTFOFF cb)
+{
+ RTFOFF offIgnored;
+ return rtFsIsoMakerOutFile_Seek(pvThis, cb, RTFILE_SEEK_CURRENT, &offIgnored);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSeek}
+ */
+static DECLCALLBACK(int) rtFsIsoMakerOutFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual)
+{
+ PRTFSISOMAKEROUTPUTFILE pThis = (PRTFSISOMAKEROUTPUTFILE)pvThis;
+
+ /*
+ * Seek relative to which position.
+ */
+ uint64_t offWrt;
+ switch (uMethod)
+ {
+ case RTFILE_SEEK_BEGIN:
+ offWrt = 0;
+ break;
+
+ case RTFILE_SEEK_CURRENT:
+ offWrt = pThis->offCurPos;
+ break;
+
+ case RTFILE_SEEK_END:
+ offWrt = pThis->pIsoMaker->cbFinalizedImage;
+ break;
+
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /*
+ * Calc new position, take care to stay within RTFOFF type bounds.
+ */
+ uint64_t offNew;
+ if (offSeek == 0)
+ offNew = offWrt;
+ else if (offSeek > 0)
+ {
+ offNew = offWrt + offSeek;
+ if ( offNew < offWrt
+ || offNew > RTFOFF_MAX)
+ offNew = RTFOFF_MAX;
+ }
+ else if ((uint64_t)-offSeek < offWrt)
+ offNew = offWrt + offSeek;
+ else
+ offNew = 0;
+ pThis->offCurPos = offNew;
+
+ *poffActual = offNew;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize}
+ */
+static DECLCALLBACK(int) rtFsIsoMakerOutFile_QuerySize(void *pvThis, uint64_t *pcbFile)
+{
+ PRTFSISOMAKEROUTPUTFILE pThis = (PRTFSISOMAKEROUTPUTFILE)pvThis;
+ *pcbFile = pThis->pIsoMaker->cbFinalizedImage;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Standard file operations.
+ */
+DECL_HIDDEN_CONST(const RTVFSFILEOPS) g_rtFsIsoMakerOutputFileOps =
+{
+ { /* Stream */
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FILE,
+ "ISO Maker Output File",
+ rtFsIsoMakerOutFile_Close,
+ rtFsIsoMakerOutFile_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtFsIsoMakerOutFile_Read,
+ NULL /*Write*/,
+ rtFsIsoMakerOutFile_Flush,
+ NULL /*PollOne*/,
+ rtFsIsoMakerOutFile_Tell,
+ rtFsIsoMakerOutFile_Skip,
+ NULL /*ZeroFill*/,
+ 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
+ },
+ rtFsIsoMakerOutFile_Seek,
+ rtFsIsoMakerOutFile_QuerySize,
+ NULL /*SetSize*/,
+ NULL /*QueryMaxSize*/,
+ RTVFSFILEOPS_VERSION
+};
+
+
+
+/**
+ * Creates a VFS file for a finalized ISO maker instanced.
+ *
+ * The file can be used to access the image. Both sequential and random access
+ * are supported, so that this could in theory be hooked up to a CD/DVD-ROM
+ * drive emulation and used as a virtual ISO image.
+ *
+ * @returns IRPT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param phVfsFile Where to return the handle.
+ */
+RTDECL(int) RTFsIsoMakerCreateVfsOutputFile(RTFSISOMAKER hIsoMaker, PRTVFSFILE phVfsFile)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn(pThis->fFinalized, VERR_WRONG_ORDER);
+ AssertPtrReturn(phVfsFile, VERR_INVALID_POINTER);
+
+ uint32_t cRefs = RTFsIsoMakerRetain(pThis);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ PRTFSISOMAKEROUTPUTFILE pFileData;
+ RTVFSFILE hVfsFile;
+ int rc = RTVfsNewFile(&g_rtFsIsoMakerOutputFileOps, sizeof(*pFileData), RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_CREATE,
+ NIL_RTVFS, NIL_RTVFSLOCK, &hVfsFile, (void **)&pFileData);
+ if (RT_SUCCESS(rc))
+ {
+ pFileData->pIsoMaker = pThis;
+ pFileData->offCurPos = 0;
+ pFileData->pFileHint = NULL;
+ pFileData->hVfsSrcFile = NIL_RTVFSFILE;
+ pFileData->pDirHintPrimaryIso = NULL;
+ pFileData->pDirHintJoliet = NULL;
+ pFileData->iChildPrimaryIso = UINT32_MAX;
+ pFileData->iChildJoliet = UINT32_MAX;
+ *phVfsFile = hVfsFile;
+ return VINF_SUCCESS;
+ }
+
+ RTFsIsoMakerRelease(pThis);
+ *phVfsFile = NIL_RTVFSFILE;
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/fs/isomakercmd-man.xml b/src/VBox/Runtime/common/fs/isomakercmd-man.xml
new file mode 100644
index 00000000..277d3806
--- /dev/null
+++ b/src/VBox/Runtime/common/fs/isomakercmd-man.xml
@@ -0,0 +1,590 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ manpage for RTIsoMaker/VISO
+-->
+<!--
+ Copyright (C) 2006-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
+-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
+<refentry id="viso" lang="en">
+
+ <refentryinfo>
+ <pubdate>$Date: 2023-01-17 15:15:46 +0100 (Tue, 17 Jan 2023) $</pubdate>
+ <title>VISO file format / RTIsoMaker</title>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>viso</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <!--<refname>VISO</refname>-->
+ <refname>viso</refname>
+ <refpurpose>ISO image maker</refpurpose>
+ <refclass>IPRT</refclass>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis id="synopsis-viso"> <!-- The 'id' is mandatory and must start with 'synopsis-'. -->
+ <command>RTIsoMaker</command>
+ <arg><replaceable>options</replaceable></arg>
+ <arg>@<replaceable>commands.rsp</replaceable></arg>
+ <arg choice="req" rep="repeat"><replaceable>filespec</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+ <para>Construct a virtual ISO 9660 / Joliet / UDF / HFS hybrid image and either write it to a
+ file (RTIsoMaker) or serve it as a virtual image (VISO).</para>
+
+ <refsect2 id="viso-viso">
+ <title>VISO file format</title>
+ <para>A VISO file is a virtual ISO image, i.e. constructed in memory from a bunch of files on
+ the host. A VISO is just the recipe describing how to go about this using a syntax vaguely
+ similar to mkisofs and genisoimage.</para>
+
+ <para>One requirement is that the VISO file must start with one of the
+ <option>--iprt-iso-maker-file-marker</option> options. Which of the options you use will
+ dictate the quoting and escaping rules used when reading the file. The option takes the
+ image UUID as an argument.</para>
+
+ <para>The VISO files are treated as UTF-8 and must not contain any byte order marker (BOM).
+ There is currently no way to comment out lines in a VISO file.</para>
+
+ </refsect2>
+
+ <refsect2 id="viso-filespecs">
+ <title>File specifications and --name-setup</title>
+ <para>All non-options that does not start with '@' are taken to indicate a file, directory,
+ or similar that is should be added to the ISO image. Directories are added recursively and
+ content is subject to filtering options.</para>
+
+ <para>Since there can be up to six different namespaces on an ISO, it is handy to be able to
+ control the names used in each and be able to exclude an object from one or more namespaces.
+ The <option>--name-setup</option> option specifies the file specification format to use
+ forthwith.</para>
+
+ <para>The default setup is:</para>
+ <!-- indent this: -->
+ <para><computeroutput> --name-setup iso+joliet+udf+hfs</computeroutput></para>
+
+ <para>Which means you specify one on-ISO name for all namespaces followed by '=' and the
+ source file system name. Only specifying the source file system will add the
+ file/dir/whatever to the root of the ISO image.</para>
+
+ <para>Lets look at the following two examples:</para>
+
+ <!-- indent these -->
+ <para><computeroutput> /docs/readme.txt=/home/user/Documents/product-x-readme.txt</computeroutput></para>
+ <para><computeroutput> /home/user/Documents/product-x-readme.txt</computeroutput></para>
+
+ <para>In the first case the file <computeroutput>'/home/user/Documents/product-x-readme.txt'</computeroutput>
+ is added to the ISO image as <computeroutput>'/docs/readme.txt'</computeroutput> in all
+ enabled namespaces. In the primary ISO 9660 namespace, the filename will by default be
+ converted to upper case because it's required by the spec.</para>
+
+ <para>In the second case the file is added to the root under the name
+ <computeroutput>'product-x-readme.txt'</computeroutput> in all namespaces. Though, in the
+ primary ISO 9660 namespace the name will be transformed to apply with the current ISO level,
+ probably uppercased, possibly truncated too. </para>
+
+ <para>Given <option>--name-setup iso,joliet,udf</option> you can specify the name individually
+ for each of the three namespace, if you like. If you omit any, they will use last name given.
+ Any names left blank (<computeroutput>==</computeroutput>) will be considered omitted.</para>
+
+ <para>A different name in each namespace:</para>
+ <para><computeroutput> /ISO.TXT=/Joliet.TxT=/UDF.txt=/tmp/iso/real.txt</computeroutput></para>
+ <para>Specific name in the ISO 9660 namespace, same in the rest:</para>
+ <para><computeroutput> /ISO.TXT=/OtherNamespaces.TxT=/tmp/iso/real.txt</computeroutput></para>
+ <para>Omit the file from the ISO 9660 namespace:</para>
+ <para><computeroutput> =/OtherNamespaces.TxT=/tmp/iso/real.txt</computeroutput></para>
+ <para>Omit the file from the joliet namespace:</para>
+ <para><computeroutput> /ISO.TXT==/UDF.TxT=/tmp/iso/real.txt</computeroutput></para>
+ <para>Use the same filename as the source everywhere:</para>
+ <para><computeroutput> /tmp/iso/real.txt</computeroutput></para>
+
+
+ <para>Using for instance <option>--name-setup udf</option> you can add a files/dirs/whatever
+ to select namespace(s) without the more complicated empty name syntax above.</para>
+
+ <para>When adding directories, you can only control the naming and omitting of the directory
+ itself, not any recursively added files and directories below it.</para>
+
+ </refsect2>
+ </refsect1>
+
+ <refsect1 id="viso-options">
+ <title>Options</title>
+
+ <refsect2 id="viso-options-general">
+ <title>General</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>-o <replaceable>output-file</replaceable></option></term>
+ <term><option>--output=<replaceable>output-file</replaceable></option></term>
+ <listitem><para>The output filename. This option is not supported in VISO mode.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--name-setup=<replaceable>spec</replaceable></option></term>
+ <listitem><para>Configures active namespaces and how file specifications are to be
+ interpreted. The specification is a comma separated list. Each element in the list is
+ a sub-list separated by space, <computeroutput>'+'</computeroutput> or
+ <computeroutput>'|'</computeroutput> giving the namespaces that elements controls.
+ Namespaces are divied into two major and minor ones, you cannot specifying a minor
+ before the major it belongs to.</para>
+ <para>Major namespaces and aliases in parentheses:</para>
+ <itemizedlist spacing="compact">
+ <listitem><para>iso (primary, iso9660, iso-9660, primary-iso, iso-primary)</para></listitem>
+ <listitem><para>joliet</para></listitem>
+ <listitem><para>udf</para></listitem>
+ <listitem><para>hfs (hfs-plus)</para></listitem>
+ </itemizedlist>
+ <para>Minor namespaces:</para>
+ <itemizedlist spacing="compact">
+ <listitem><para>rock: rock ridge on previous major namespace (iso / joliet)</para></listitem>
+ <listitem><para>iso-rock: rock ridge extensions on primary ISO 9660 namespace</para></listitem>
+ <listitem><para>joliet-rock: rock ridge on joliet namespace (just for fun)</para></listitem>
+ <listitem><para>trans-tbl: translation table file on previous major namespace</para></listitem>
+ <listitem><para>iso-trans-tbl</para></listitem>
+ <listitem><para>joliet-trans-tbl</para></listitem>
+ <listitem><para>udf-trans-tbl</para></listitem>
+ <listitem><para>hfs-trans-tbl</para></listitem>
+ </itemizedlist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--name-setup-from-import</option></term>
+ <listitem><para>This is for use following one or more <option>--import-iso</option>
+ operations and will pick a configuration matching the imported content as best we can.
+ If the imported ISOs only had a iso9660 namespace, the joliet, udf and hfs namespaces
+ will be removed. This is useful when adding additional files to the ISO and will
+ prevent guest from picking a namespace without the imported ISO content when mounting it.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--push-iso=<replaceable>iso-file</replaceable></option></term>
+ <term><option>--push-iso-no-joliet=<replaceable>iso-file</replaceable></option></term>
+ <term><option>--push-iso-no-rock-<replaceable>iso-file</replaceable></option></term>
+ <term><option>--push-iso-no-rock-no-joliet=<replaceable>iso-file</replaceable></option></term>
+ <listitem><para>Open the specified ISO file and use it as source file system until the
+ corresponding <option>--pop</option> options is encountered. The variations are for
+ selecting which namespace on the ISO to (not) access. These options are handy for copying
+ files/directories/stuff from an ISO without having to extract them first or using the
+ <computeroutput>:iprtvfs:</computeroutput> syntax.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--pop</option></term>
+ <listitem><para>Pops a <option>--push-iso</option> of the source file system stack.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--import-iso=<replaceable>iso-file</replaceable></option></term>
+ <listitem><para>Imports everything on the given ISO file, including boot configuration and
+ system area (first 16 sectors) content. You can use <option>--name-setup</option> to omit
+ namespaces.</para></listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect2>
+
+ <refsect2 id="viso-options-namespaces">
+ <title>Namespaces</title>
+ <variablelist>
+
+ <varlistentry>
+ <term><option>--iso-level=<replaceable>0|1|2|3</replaceable></option></term> <!-- FIXME: imperfect markup -->
+ <listitem>
+ <para>Sets the ISO level:</para>
+ <itemizedlist spacing="compact">
+ <listitem><para>0: Disable primary ISO namespace.</para></listitem>
+ <listitem><para>1: ISO level 1: Filenames 8.3 format and limited to 4GB - 1.</para></listitem>
+ <listitem><para>2: ISO level 2: 31 char long names and limited to 4GB - 1.</para></listitem>
+ <listitem><para>3: ISO level 3: 31 char long names and support for >=4GB files. (default)</para></listitem>
+ <listitem><para>4: Fictive level used by other tools. Not yet implemented.</para></listitem>
+ </itemizedlist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--rock-ridge</option></term>
+ <term><option>--limited-rock-ridge</option></term>
+ <term><option>--no-rock-ridge</option></term>
+ <listitem><para>Enables or disables rock ridge support for the primary ISO 9660 namespace.
+ The <option>--limited-rock-ridge</option> option omits a couple of bits in the root
+ directory that would make Linux pick rock ridge over joliet.</para>
+ <para>Default: <option>--limited-rock-ridge</option></para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-J</option></term>
+ <term><option>--joliet</option></term>
+ <term><option>--no-joliet</option></term>
+ <listitem><para>Enables or disable the joliet namespace. This option must precede any file
+ specifications.</para>
+ <para>Default: <option>--joliet</option></para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--joliet-ucs-level=<replaceable>1|2|3</replaceable></option></term> <!-- FIXME: imperfect markup -->
+ <term><option>--ucs-level=<replaceable>1|2|3</replaceable></option></term>
+ <listitem><para>Set the Joliet UCS support level. This is currently only flagged in the
+ image but not enforced on the actual path names.</para>
+ <para>Default level: 3</para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect2>
+
+ <refsect2 id="viso-options-file-attributes">
+ <title>File Attributes</title>
+ <variablelist>
+
+ <varlistentry>
+ <term><option>--rational-attribs</option></term>
+ <listitem><para>Enables rational file attribute handling (default):</para>
+ <itemizedlist spacing="compact">
+ <listitem><para>Owner ID is set to zero</para></listitem>
+ <listitem><para>Group ID is set to zero</para></listitem>
+ <listitem><para>Mode is set to 0444 for non-executable files.</para></listitem>
+ <listitem><para>Mode is set to 0555 for executable files.</para></listitem>
+ <listitem><para>Mode is set to 0555 for directories, preserving stick bits.</para></listitem>
+ </itemizedlist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--strict-attribs</option></term>
+ <listitem><para>Counters <option>--rational-attribs</option> and causes attributes to be
+ recorded exactly as they appear in the source.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--file-mode=<replaceable>mode</replaceable></option></term>
+ <term><option>--no-file-mode</option></term>
+ <listitem><para>Controls the forced file mode mask for rock ridge, UDF and HFS.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--dir-mode=<replaceable>mode</replaceable></option></term>
+ <term><option>--no-dir-mode</option></term>
+ <listitem><para>Controls the forced directory mode mask for rock ridge, UDF and HFS.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--new-dir-mode=<replaceable>mode</replaceable></option></term>
+ <listitem><para>Controls the default mode mask (rock ridge, UDF, HFS) for directories that
+ are created implicitly. The <option>--dir-mode</option> option overrides this.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--chmod=<replaceable>mode</replaceable>:<replaceable>on-iso-file</replaceable></option></term>
+ <listitem><para>Explictily sets the rock ridge, UDF and HFS file mode for a file/dir/whatever
+ that has already been added to the ISO. The mode can be octal, <computeroutput>ra+x</computeroutput>,
+ <computeroutput>a+r</computeroutput>, or <computeroutput>a+rx</computeroutput>.
+ (Support for more complicated mode specifications may be implemented at a later point.)</para>
+ <para>Note that only namespaces in the current --name-setup are affected.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--chown=<replaceable>owner-id</replaceable>:<replaceable>on-iso-file</replaceable></option></term>
+ <listitem><para>Explictily sets the rock ridge, UDF and HFS file owner ID (numeric) for a
+ file/dir/whatever that has already been added to the ISO.</para>
+ <para>Note that only namespaces in the current --name-setup are affected.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>--chgrp=<replaceable>group-id</replaceable>:<replaceable>on-iso-file</replaceable></term>
+ <listitem><para>Explictily sets the rock ridge, UDF and HFS file group ID (numeric) for a
+ file/dir/whatever that has already been added to the ISO.</para>
+ <para>Note that only namespaces in the current --name-setup are affected.</para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect2>
+
+ <refsect2 id="viso-options-booting">
+ <title>Booting</title>
+ <variablelist>
+
+ <varlistentry>
+ <term><option>--eltorito-new-entry</option></term>
+ <term><option>--eltorito-alt-boot</option></term>
+ <listitem><para>Starts a new El Torito boot entry.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--eltorito-add-image=<replaceable>filespec</replaceable></option></term>
+ <listitem><para>File specification of a file that should be added to the image and used as
+ the El Torito boot image of the current boot entry.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-b <replaceable>on-iso-file</replaceable></option></term>
+ <term><option>--eltorito-boot=<replaceable>on-iso-file</replaceable></option></term>
+ <listitem><para>Specifies a file on the ISO as the El Torito boot image for the current boot
+ entry.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--eltorito-floppy-12</option></term>
+ <term><option>--eltorito-floppy-144</option></term>
+ <term><option>--eltorito-floppy-288</option></term>
+ <term><option>--no-emulation-boot</option></term>
+ <term><option>--hard-disk-boot</option></term>
+ <listitem><para>Sets the boot image emulation type of the current El Torito boot entry.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--boot-load-seg=<replaceable>seg</replaceable></option></term>
+ <listitem><para>Specify the image load segment for the current El Torito boot entry.</para>
+ <para>Default: 0x7c0</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--boot-load-size=<replaceable>sectors</replaceable></option></term>
+ <listitem><para>Specify the image load size in emulated sectors for the current El Torito
+ boot entry.</para>
+ <para>Default: 4 (sectors of 512 bytes)</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--no-boot</option></term>
+ <listitem><para>Indicates that the current El Torito boot entry isn't bootable. (The BIOS
+ will allegedly configure the emulation, but not attempt booting.)</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--boot-info-table</option></term>
+ <listitem><para>Write a isolinux/syslinux boot info table into the boot image for the
+ current El Torito boot entry.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--eltorito-platform-id=<replaceable>id</replaceable></option></term>
+ <listitem><para>Set the El Torito platform ID of the current entry, a new entry of the
+ verification entry depending on when it's used. The ID must be one of:
+ <computeroutput>x86</computeroutput>, <computeroutput>PPC</computeroutput>,
+ <computeroutput>Mac</computeroutput>, <computeroutput>efi</computeroutput></para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-c <replaceable>namespec</replaceable></option></term>
+ <term><option>--boot-catalog=<replaceable>namespec</replaceable></option></term>
+ <listitem><para>Enters the El Torito boot catalog into the namespaces as a file. The
+ <replaceable>namespec</replaceable> uses the same format as a 'filespec', but omits the
+ final source file system name component.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-G <replaceable>file</replaceable></option></term>
+ <term><option>--generic-boot=<replaceable>file</replaceable></option></term>
+ <listitem><para>Specifies a file that should be loaded at offset 0 in the ISO image. The
+ file must not be larger than 32KB. When creating a hybrid image, parts of this may be
+ regenerated by partition tables and such.</para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect2>
+
+ <refsect2 id="viso-options-string-properties">
+ <title>String properties (applied to active namespaces only)</title>
+ <variablelist>
+
+ <varlistentry>
+ <term><option>--abstract=<replaceable>file-id</replaceable></option></term>
+ <listitem><para>The name of the abstract file in the root dir.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-A <replaceable>text|_file-id</replaceable></option></term>
+ <term><option>--application-id=<replaceable>text|_file-id</replaceable></option></term>
+ <listitem><para>Application ID string or root file name. The latter must be prefixed with
+ an underscore.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--biblio=<replaceable>file-id</replaceable></option></term>
+ <listitem><para>The name of the bibliographic file in the root dir.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--copyright=<replaceable>file-id</replaceable></option></term>
+ <listitem><para>The name of the copyright file in the root dir.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-P <replaceable>text|_file-id</replaceable></option></term>
+ <term><option>--publisher=<replaceable>text|_file-id</replaceable></option></term>
+ <listitem><para>Publisher ID string or root file name. The latter must be prefixed with an
+ underscore.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-p <replaceable>text|_file-id</replaceable></option></term>
+ <term><option>--preparer=<replaceable>text|_file-id</replaceable></option></term>
+ <listitem><para>Data preparer ID string or root file name. The latter must be prefixed
+ with an underscore.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--sysid=<replaceable>text</replaceable></option></term>
+ <listitem><para>System ID string.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--volid=<replaceable>text</replaceable></option></term>
+ <term><option>--volume-id=<replaceable>text</replaceable></option></term>
+ <listitem><para>Volume ID string (label). (It is possible to set different labels for
+ primary ISO 9660, joliet, UDF and HFS by changing the active namespaces using the
+ <option>--name-setup</option> option between <option>--volume-id</option> occurences.)</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--volset=<replaceable>text</replaceable></option></term>
+ <listitem><para>Volume set ID string.</para></listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect2>
+
+ <refsect2 id="viso-options-compatibility">
+ <title>Compatibility:</title>
+ <variablelist>
+
+ <varlistentry>
+ <term><option>--graft-points</option></term>
+ <listitem><para>Alias for --name-setup iso+joliet+udf+hfs.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-l</option></term>
+ <term><option>--long-names</option></term>
+ <listitem><para>Allow 31 charater filenames. Just ensure ISO level >= 2 here.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-R</option></term>
+ <term><option>--rock</option></term>
+ <listitem><para>Same as <option>--rock-ridge</option> and <option>--strict-attribs</option>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-r</option></term>
+ <term><option>--rational-rock</option></term>
+ <listitem><para>Same as <option>--rock-ridge</option> and <option>--rational-attribs</option>.</para></listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect2>
+
+
+ <refsect2 id="viso-options-viso-specific">
+ <title>VISO Specific:</title>
+ <variablelist>
+
+ <varlistentry>
+ <term><option>--iprt-iso-maker-file-marker=<replaceable>UUID</replaceable></option></term>
+ <term><option>--iprt-iso-maker-file-marker-bourne=<replaceable>UUID</replaceable></option></term>
+ <term><option>--iprt-iso-maker-file-marker-bourne-sh=<replaceable>UUID</replaceable></option></term>
+ <listitem><para>Used as first option in a VISO file to specify the file UUID and that it is
+ formatted using bourne-shell argument quoting &amp; escaping style.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--iprt-iso-maker-file-marker-ms=<replaceable>UUID</replaceable></option></term>
+ <term><option>--iprt-iso-maker-file-marker-ms-sh=<replaceable>UUID</replaceable></option></term>
+ <listitem><para>Used as first option in a VISO file to specify the file UUID and that it is
+ formatted using microsoft CRT argument quoting &amp; escaping style.</para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect2>
+
+
+ <refsect2 id="viso-options-testing">
+ <title>Testing (not applicable to VISO):</title>
+ <variablelist>
+
+ <varlistentry>
+ <term><option>--output-buffer-size=<replaceable>bytes</replaceable></option></term>
+ <listitem><para>Selects a specific output buffer size for testing virtual image reads.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--random-output-buffer-size</option></term>
+ <listitem><para>Enables randomized buffer size for each virtual image read, using the
+ current output buffer size (<option>--output-buffer-size</option>) as maximum.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--random-order-verification=<replaceable>size</replaceable></option></term>
+ <listitem><para>Enables verification pass of the image that compares blocks of the given
+ size in random order from the virtual and output images.</para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect2>
+
+ </refsect1>
+</refentry>
+
diff --git a/src/VBox/Runtime/common/fs/isomakercmd.cpp b/src/VBox/Runtime/common/fs/isomakercmd.cpp
new file mode 100644
index 00000000..271c28a6
--- /dev/null
+++ b/src/VBox/Runtime/common/fs/isomakercmd.cpp
@@ -0,0 +1,3689 @@
+/* $Id: isomakercmd.cpp $ */
+/** @file
+ * IPRT - ISO Image Maker Command.
+ */
+
+/*
+ * 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/fsisomaker.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/buildconfig.h>
+#include <iprt/ctype.h>
+#include <iprt/file.h>
+#include <iprt/fsvfs.h>
+#include <iprt/err.h>
+#include <iprt/getopt.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/rand.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/formats/iso9660.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Maximum number of name specifiers we allow. */
+#define RTFSISOMAKERCMD_MAX_NAMES 8
+
+/** Maximum directory recursions when adding a directory tree. */
+#define RTFSISOMAKERCMD_MAX_DIR_RECURSIONS 32
+
+/** @name Name specifiers
+ * @{ */
+#define RTFSISOMAKERCMDNAME_PRIMARY_ISO RTFSISOMAKER_NAMESPACE_ISO_9660
+#define RTFSISOMAKERCMDNAME_JOLIET RTFSISOMAKER_NAMESPACE_JOLIET
+#define RTFSISOMAKERCMDNAME_UDF RTFSISOMAKER_NAMESPACE_UDF
+#define RTFSISOMAKERCMDNAME_HFS RTFSISOMAKER_NAMESPACE_HFS
+
+#define RTFSISOMAKERCMDNAME_PRIMARY_ISO_ROCK_RIDGE RT_BIT_32(16)
+#define RTFSISOMAKERCMDNAME_JOLIET_ROCK_RIDGE RT_BIT_32(17)
+
+#define RTFSISOMAKERCMDNAME_JOLIET_TRANS_TBL RT_BIT_32(20)
+#define RTFSISOMAKERCMDNAME_PRIMARY_ISO_TRANS_TBL RT_BIT_32(21)
+#define RTFSISOMAKERCMDNAME_UDF_TRANS_TBL RT_BIT_32(22)
+#define RTFSISOMAKERCMDNAME_HFS_TRANS_TBL RT_BIT_32(23)
+
+#define RTFSISOMAKERCMDNAME_MAJOR_MASK \
+ (RTFSISOMAKERCMDNAME_PRIMARY_ISO | RTFSISOMAKERCMDNAME_JOLIET | RTFSISOMAKERCMDNAME_UDF | RTFSISOMAKERCMDNAME_HFS)
+
+#define RTFSISOMAKERCMDNAME_MINOR_MASK \
+ ( RTFSISOMAKERCMDNAME_PRIMARY_ISO_ROCK_RIDGE | RTFSISOMAKERCMDNAME_PRIMARY_ISO_TRANS_TBL \
+ | RTFSISOMAKERCMDNAME_JOLIET_ROCK_RIDGE | RTFSISOMAKERCMDNAME_JOLIET_TRANS_TBL \
+ | RTFSISOMAKERCMDNAME_UDF_TRANS_TBL \
+ | RTFSISOMAKERCMDNAME_HFS_TRANS_TBL)
+AssertCompile((RTFSISOMAKERCMDNAME_MAJOR_MASK & RTFSISOMAKERCMDNAME_MINOR_MASK) == 0);
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef enum RTFSISOMAKERCMDOPT
+{
+ RTFSISOMAKERCMD_OPT_FIRST = 1000,
+
+ RTFSISOMAKERCMD_OPT_IPRT_ISO_MAKER_FILE_MARKER,
+ RTFSISOMAKERCMD_OPT_OUTPUT_BUFFER_SIZE,
+ RTFSISOMAKERCMD_OPT_RANDOM_OUTPUT_BUFFER_SIZE,
+ RTFSISOMAKERCMD_OPT_RANDOM_ORDER_VERIFICATION,
+ RTFSISOMAKERCMD_OPT_NAME_SETUP,
+ RTFSISOMAKERCMD_OPT_NAME_SETUP_FROM_IMPORT,
+
+ RTFSISOMAKERCMD_OPT_ROCK_RIDGE,
+ RTFSISOMAKERCMD_OPT_LIMITED_ROCK_RIDGE,
+ RTFSISOMAKERCMD_OPT_NO_ROCK_RIDGE,
+ RTFSISOMAKERCMD_OPT_NO_JOLIET,
+
+ RTFSISOMAKERCMD_OPT_IMPORT_ISO,
+ RTFSISOMAKERCMD_OPT_PUSH_ISO,
+ RTFSISOMAKERCMD_OPT_PUSH_ISO_NO_JOLIET,
+ RTFSISOMAKERCMD_OPT_PUSH_ISO_NO_ROCK,
+ RTFSISOMAKERCMD_OPT_PUSH_ISO_NO_ROCK_NO_JOLIET,
+ RTFSISOMAKERCMD_OPT_POP,
+
+ RTFSISOMAKERCMD_OPT_ELTORITO_NEW_ENTRY,
+ RTFSISOMAKERCMD_OPT_ELTORITO_ADD_IMAGE,
+ RTFSISOMAKERCMD_OPT_ELTORITO_FLOPPY_12,
+ RTFSISOMAKERCMD_OPT_ELTORITO_FLOPPY_144,
+ RTFSISOMAKERCMD_OPT_ELTORITO_FLOPPY_288,
+
+ RTFSISOMAKERCMD_OPT_RATIONAL_ATTRIBS,
+ RTFSISOMAKERCMD_OPT_STRICT_ATTRIBS,
+ RTFSISOMAKERCMD_OPT_NO_FILE_MODE,
+ RTFSISOMAKERCMD_OPT_NO_DIR_MODE,
+ RTFSISOMAKERCMD_OPT_CHMOD,
+ RTFSISOMAKERCMD_OPT_CHOWN,
+ RTFSISOMAKERCMD_OPT_CHGRP,
+
+ /*
+ * Compatibility options:
+ */
+ RTFSISOMAKERCMD_OPT_ABSTRACT_FILE_ID,
+ RTFSISOMAKERCMD_OPT_ALLOW_LEADING_DOTS,
+ RTFSISOMAKERCMD_OPT_ALLOW_LIMITED_SIZE,
+ RTFSISOMAKERCMD_OPT_ALLOW_LOWERCASE,
+ RTFSISOMAKERCMD_OPT_ALLOW_MULTI_DOT,
+ RTFSISOMAKERCMD_OPT_ALPHA_BOOT,
+ RTFSISOMAKERCMD_OPT_APPLE,
+ RTFSISOMAKERCMD_OPT_BIBLIOGRAPHIC_FILE_ID,
+ RTFSISOMAKERCMD_OPT_CHECK_OLD_NAMES,
+ RTFSISOMAKERCMD_OPT_CHECK_SESSION,
+ RTFSISOMAKERCMD_OPT_COPYRIGHT_FILE_ID,
+ RTFSISOMAKERCMD_OPT_DETECT_HARDLINKS,
+ RTFSISOMAKERCMD_OPT_DIR_MODE,
+ RTFSISOMAKERCMD_OPT_DVD_VIDEO,
+ RTFSISOMAKERCMD_OPT_ELTORITO_PLATFORM_ID,
+ RTFSISOMAKERCMD_OPT_ELTORITO_HARD_DISK_BOOT,
+ RTFSISOMAKERCMD_OPT_ELTORITO_INFO_TABLE,
+ RTFSISOMAKERCMD_OPT_ELTORITO_LOAD_SEG,
+ RTFSISOMAKERCMD_OPT_ELTORITO_LOAD_SIZE,
+ RTFSISOMAKERCMD_OPT_ELTORITO_NO_BOOT,
+ RTFSISOMAKERCMD_OPT_ELTORITO_NO_EMULATION_BOOT,
+ RTFSISOMAKERCMD_OPT_EXCLUDE_LIST,
+ RTFSISOMAKERCMD_OPT_FILE_MODE,
+ RTFSISOMAKERCMD_OPT_FORCE_RR,
+ RTFSISOMAKERCMD_OPT_GID,
+ RTFSISOMAKERCMD_OPT_GRAFT_POINTS,
+ RTFSISOMAKERCMD_OPT_GUI,
+ RTFSISOMAKERCMD_OPT_HFS_AUTO,
+ RTFSISOMAKERCMD_OPT_HFS_BLESS,
+ RTFSISOMAKERCMD_OPT_HFS_BOOT_FILE,
+ RTFSISOMAKERCMD_OPT_HFS_CAP,
+ RTFSISOMAKERCMD_OPT_HFS_CHRP_BOOT,
+ RTFSISOMAKERCMD_OPT_HFS_CLUSTER_SIZE,
+ RTFSISOMAKERCMD_OPT_HFS_CREATOR,
+ RTFSISOMAKERCMD_OPT_HFS_DAVE,
+ RTFSISOMAKERCMD_OPT_HFS_DOUBLE,
+ RTFSISOMAKERCMD_OPT_HFS_ENABLE,
+ RTFSISOMAKERCMD_OPT_HFS_ETHERSHARE,
+ RTFSISOMAKERCMD_OPT_HFS_EXCHANGE,
+ RTFSISOMAKERCMD_OPT_HFS_HIDE,
+ RTFSISOMAKERCMD_OPT_HFS_HIDE_LIST,
+ RTFSISOMAKERCMD_OPT_HFS_ICON_POSITION,
+ RTFSISOMAKERCMD_OPT_HFS_INPUT_CHARSET,
+ RTFSISOMAKERCMD_OPT_HFS_MAC_NAME,
+ RTFSISOMAKERCMD_OPT_HFS_MACBIN,
+ RTFSISOMAKERCMD_OPT_HFS_MAGIC,
+ RTFSISOMAKERCMD_OPT_HFS_MAP,
+ RTFSISOMAKERCMD_OPT_HFS_NETATALK,
+ RTFSISOMAKERCMD_OPT_HFS_NO_DESKTOP,
+ RTFSISOMAKERCMD_OPT_HFS_OSX_DOUBLE,
+ RTFSISOMAKERCMD_OPT_HFS_OSX_HFS,
+ RTFSISOMAKERCMD_OPT_HFS_OUTPUT_CHARSET,
+ RTFSISOMAKERCMD_OPT_HFS_PARMS,
+ RTFSISOMAKERCMD_OPT_HFS_PART,
+ RTFSISOMAKERCMD_OPT_HFS_PREP_BOOT,
+ RTFSISOMAKERCMD_OPT_HFS_PROBE,
+ RTFSISOMAKERCMD_OPT_HFS_ROOT_INFO,
+ RTFSISOMAKERCMD_OPT_HFS_SFM,
+ RTFSISOMAKERCMD_OPT_HFS_SGI,
+ RTFSISOMAKERCMD_OPT_HFS_SINGLE,
+ RTFSISOMAKERCMD_OPT_HFS_TYPE,
+ RTFSISOMAKERCMD_OPT_HFS_UNLOCK,
+ RTFSISOMAKERCMD_OPT_HFS_USHARE,
+ RTFSISOMAKERCMD_OPT_HFS_VOL_ID,
+ RTFSISOMAKERCMD_OPT_HFS_XINET,
+ RTFSISOMAKERCMD_OPT_HIDDEN,
+ RTFSISOMAKERCMD_OPT_HIDDEN_LIST,
+ RTFSISOMAKERCMD_OPT_HIDE,
+ RTFSISOMAKERCMD_OPT_HIDE_JOLIET,
+ RTFSISOMAKERCMD_OPT_HIDE_JOLIET_LIST,
+ RTFSISOMAKERCMD_OPT_HIDE_JOLIET_TRANS_TBL,
+ RTFSISOMAKERCMD_OPT_HIDE_LIST,
+ RTFSISOMAKERCMD_OPT_HIDE_RR_MOVED,
+ RTFSISOMAKERCMD_OPT_HPPA_BOOTLOADER,
+ RTFSISOMAKERCMD_OPT_HPPA_CMDLINE,
+ RTFSISOMAKERCMD_OPT_HPPA_KERNEL_32,
+ RTFSISOMAKERCMD_OPT_HPPA_KERNEL_64,
+ RTFSISOMAKERCMD_OPT_HPPA_RAMDISK,
+ RTFSISOMAKERCMD_OPT_INPUT_CHARSET,
+ RTFSISOMAKERCMD_OPT_ISO_LEVEL,
+ RTFSISOMAKERCMD_OPT_JIGDO_COMPRESS,
+ RTFSISOMAKERCMD_OPT_JIGDO_EXCLUDE,
+ RTFSISOMAKERCMD_OPT_JIGDO_FORCE_MD5,
+ RTFSISOMAKERCMD_OPT_JIGDO_JIGDO,
+ RTFSISOMAKERCMD_OPT_JIGDO_MAP,
+ RTFSISOMAKERCMD_OPT_JIGDO_MD5_LIST,
+ RTFSISOMAKERCMD_OPT_JIGDO_MIN_FILE_SIZE,
+ RTFSISOMAKERCMD_OPT_JIGDO_TEMPLATE,
+ RTFSISOMAKERCMD_OPT_JOLIET_CHARSET,
+ RTFSISOMAKERCMD_OPT_JOLIET_LEVEL,
+ RTFSISOMAKERCMD_OPT_JOLIET_LONG,
+ RTFSISOMAKERCMD_OPT_LOG_FILE,
+ RTFSISOMAKERCMD_OPT_MAX_ISO9660_FILENAMES,
+ RTFSISOMAKERCMD_OPT_MIPS_BOOT,
+ RTFSISOMAKERCMD_OPT_MIPSEL_BOOT,
+ RTFSISOMAKERCMD_OPT_NEW_DIR_MODE,
+ RTFSISOMAKERCMD_OPT_NO_BACKUP_FILES,
+ RTFSISOMAKERCMD_OPT_NO_DETECT_HARDLINKS,
+ RTFSISOMAKERCMD_OPT_NO_ISO_TRANSLATE,
+ RTFSISOMAKERCMD_OPT_NO_PAD,
+ RTFSISOMAKERCMD_OPT_NO_RR,
+ RTFSISOMAKERCMD_OPT_NO_SPLIT_SYMLINK_COMPONENTS,
+ RTFSISOMAKERCMD_OPT_NO_SPLIT_SYMLINK_FIELDS,
+ RTFSISOMAKERCMD_OPT_OLD_ROOT,
+ RTFSISOMAKERCMD_OPT_OUTPUT_CHARSET,
+ RTFSISOMAKERCMD_OPT_PAD,
+ RTFSISOMAKERCMD_OPT_PATH_LIST,
+ RTFSISOMAKERCMD_OPT_PRINT_SIZE,
+ RTFSISOMAKERCMD_OPT_QUIET,
+ RTFSISOMAKERCMD_OPT_RELAXED_FILENAMES,
+ RTFSISOMAKERCMD_OPT_ROOT,
+ RTFSISOMAKERCMD_OPT_SORT,
+ RTFSISOMAKERCMD_OPT_SPARC_BOOT,
+ RTFSISOMAKERCMD_OPT_SPARC_LABEL,
+ RTFSISOMAKERCMD_OPT_SPLIT_OUTPUT,
+ RTFSISOMAKERCMD_OPT_STREAM_FILE_NAME,
+ RTFSISOMAKERCMD_OPT_STREAM_MEDIA_SIZE,
+ RTFSISOMAKERCMD_OPT_SUNX86_BOOT,
+ RTFSISOMAKERCMD_OPT_SUNX86_LABEL,
+ RTFSISOMAKERCMD_OPT_SYSTEM_ID,
+ RTFSISOMAKERCMD_OPT_TRANS_TBL_NAME,
+ RTFSISOMAKERCMD_OPT_UDF,
+ RTFSISOMAKERCMD_OPT_UID,
+ RTFSISOMAKERCMD_OPT_USE_FILE_VERSION,
+ RTFSISOMAKERCMD_OPT_VOLUME_ID,
+ RTFSISOMAKERCMD_OPT_VOLUME_SET_ID,
+ RTFSISOMAKERCMD_OPT_VOLUME_SET_SEQ_NO,
+ RTFSISOMAKERCMD_OPT_VOLUME_SET_SIZE,
+ RTFSISOMAKERCMD_OPT_END
+} RTFSISOMAKERCMDOPT;
+
+
+/**
+ * El Torito boot entry.
+ */
+typedef struct RTFSISOMKCMDELTORITOENTRY
+{
+ /** The type of this entry. */
+ enum
+ {
+ kEntryType_Invalid = 0,
+ kEntryType_Validation, /**< Same as kEntryType_SectionHeader, just hardcoded #0. */
+ kEntryType_SectionHeader,
+ kEntryType_Default, /**< Same as kEntryType_Section, just hardcoded #1. */
+ kEntryType_Section
+ } enmType;
+ /** Type specific data. */
+ union
+ {
+ struct
+ {
+ /** The platform ID (ISO9660_ELTORITO_PLATFORM_ID_XXX). */
+ uint8_t idPlatform;
+ /** Some string for the header. */
+ const char *pszString;
+ } Validation,
+ SectionHeader;
+ struct
+ {
+ /** The name of the boot image wihtin the ISO (-b option). */
+ const char *pszImageNameInIso;
+ /** The object ID of the image in the ISO. This is set to UINT32_MAX when
+ * pszImageNameInIso is used (i.e. -b option) and we've delayed everything
+ * boot related till after all files have been added to the image. */
+ uint32_t idxImageObj;
+ /** Whether to insert boot info table into the image. */
+ bool fInsertBootInfoTable;
+ /** Bootble or not. Possible to make BIOS set up emulation w/o booting it. */
+ bool fBootable;
+ /** The media type (ISO9660_ELTORITO_BOOT_MEDIA_TYPE_XXX). */
+ uint8_t bBootMediaType;
+ /** File system / partition type. */
+ uint8_t bSystemType;
+ /** Load address divided by 0x10. */
+ uint16_t uLoadSeg;
+ /** Number of sectors (512) to load. */
+ uint16_t cSectorsToLoad;
+ } Section,
+ Default;
+ } u;
+} RTFSISOMKCMDELTORITOENTRY;
+/** Pointer to an el torito boot entry. */
+typedef RTFSISOMKCMDELTORITOENTRY *PRTFSISOMKCMDELTORITOENTRY;
+
+/**
+ * ISO maker command options & state.
+ */
+typedef struct RTFSISOMAKERCMDOPTS
+{
+ /** The handle to the ISO maker. */
+ RTFSISOMAKER hIsoMaker;
+ /** Set if we're creating a virtual image maker, i.e. producing something
+ * that is going to be read from only and not written to disk. */
+ bool fVirtualImageMaker;
+ /** Extended error info. This is a stderr alternative for the
+ * fVirtualImageMaker case (stdout goes to LogRel). */
+ PRTERRINFO pErrInfo;
+
+ /** The output file.
+ * This is NULL when fVirtualImageMaker is set. */
+ const char *pszOutFile;
+ /** Special buffer size to use for testing the ISO maker code reading. */
+ uint32_t cbOutputReadBuffer;
+ /** Use random output read buffer size. cbOutputReadBuffer works as maximum
+ * when this is enabled. */
+ bool fRandomOutputReadBufferSize;
+ /** Do output verification, but do it in random order if non-zero. The
+ * values gives the block size to use. */
+ uint32_t cbRandomOrderVerifciationBlock;
+
+ /** Index of the top source stack entry, -1 if empty. */
+ int32_t iSrcStack;
+ struct
+ {
+ /** The root VFS dir or the CWD for relative paths. */
+ RTVFSDIR hSrcDir;
+ /** The current source VFS, NIL_RTVFS if the regular file system is used. */
+ RTVFS hSrcVfs;
+ /** The specifier for hSrcVfs (error messages). */
+ const char *pszSrcVfs;
+ /** The option for hSrcVfs.
+ * This is NULL for a CWD passed via the API that shouldn't be popped. */
+ const char *pszSrcVfsOption;
+ } aSrcStack[5];
+
+ /** @name Processing of inputs
+ * @{ */
+ /** The namespaces (RTFSISOMAKER_NAMESPACE_XXX) we're currently adding
+ * input to. */
+ uint32_t fDstNamespaces;
+ /** The number of name specifiers we're currently operating with. */
+ uint32_t cNameSpecifiers;
+ /** Name specifier configurations.
+ * For instance given "name0=name1=name2=name3=source-file" we will add
+ * source-file to the image with name0 as the name in the namespace and
+ * sub-name specified by aNameSpecifiers[0], name1 in aNameSpecifiers[1],
+ * and so on. This allows exact control over which names a file will
+ * have in each namespace (primary-iso, joliet, udf, hfs) and sub-namespace
+ * (rock-ridge, trans.tbl).
+ */
+ uint32_t afNameSpecifiers[RTFSISOMAKERCMD_MAX_NAMES];
+ /** The forced directory mode. */
+ RTFMODE fDirMode;
+ /** Set if fDirMode should be applied. */
+ bool fDirModeActive;
+ /** Set if fFileMode should be applied. */
+ bool fFileModeActive;
+ /** The force file mode. */
+ RTFMODE fFileMode;
+ /** @} */
+
+ /** @name Booting related options and state.
+ * @{ */
+ /** Number of boot catalog entries (aBootCatEntries). */
+ uint32_t cBootCatEntries;
+ /** Boot catalog entries. */
+ RTFSISOMKCMDELTORITOENTRY aBootCatEntries[64];
+ /** @} */
+
+ /** @name Filtering
+ * @{ */
+ /** The trans.tbl filename when enabled. We must not import these files. */
+ const char *pszTransTbl;
+ /** @} */
+
+ /** Number of items (files, directories, images, whatever) we've added. */
+ uint32_t cItemsAdded;
+} RTFSISOMAKERCMDOPTS;
+typedef RTFSISOMAKERCMDOPTS *PRTFSISOMAKERCMDOPTS;
+typedef RTFSISOMAKERCMDOPTS const *PCRTFSISOMAKERCMDOPTS;
+
+
+/**
+ * One parsed name.
+ */
+typedef struct RTFSISOMKCMDPARSEDNAME
+{
+ /** Copy of the corresponding RTFSISOMAKERCMDOPTS::afNameSpecifiers
+ * value. */
+ uint32_t fNameSpecifiers;
+ /** The length of the specified path. */
+ uint32_t cchPath;
+ /** Specified path. */
+ char szPath[RTPATH_MAX];
+} RTFSISOMKCMDPARSEDNAME;
+/** Pointer to a parsed name. */
+typedef RTFSISOMKCMDPARSEDNAME *PRTFSISOMKCMDPARSEDNAME;
+/** Pointer to a const parsed name. */
+typedef RTFSISOMKCMDPARSEDNAME const *PCRTFSISOMKCMDPARSEDNAME;
+
+
+/**
+ * Parsed names.
+ */
+typedef struct RTFSISOMKCMDPARSEDNAMES
+{
+ /** Number of names. */
+ uint32_t cNames;
+ /** Number of names with the source. */
+ uint32_t cNamesWithSrc;
+ /** Special source types.
+ * Used for conveying commands to do on names intead of adding a source.
+ * Only used when adding generic stuff w/o any options involved. */
+ enum
+ {
+ kSrcType_None,
+ kSrcType_Normal,
+ kSrcType_NormalSrcStack,
+ kSrcType_Remove,
+ kSrcType_MustRemove
+ } enmSrcType;
+ /** The parsed names. */
+ RTFSISOMKCMDPARSEDNAME aNames[RTFSISOMAKERCMD_MAX_NAMES + 1];
+} RTFSISOMKCMDPARSEDNAMES;
+/** Pointer to parsed names. */
+typedef RTFSISOMKCMDPARSEDNAMES *PRTFSISOMKCMDPARSEDNAMES;
+/** Pointer to const parsed names. */
+typedef RTFSISOMKCMDPARSEDNAMES *PCRTFSISOMKCMDPARSEDNAMES;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/*
+ * Parse the command line. This is similar to genisoimage and mkisofs,
+ * thus the single dash long name aliases.
+ */
+static const RTGETOPTDEF g_aRtFsIsoMakerOptions[] =
+{
+ /*
+ * Unique IPRT ISO maker options.
+ */
+ { "--name-setup", RTFSISOMAKERCMD_OPT_NAME_SETUP, RTGETOPT_REQ_STRING },
+ { "--name-setup-from-import", RTFSISOMAKERCMD_OPT_NAME_SETUP_FROM_IMPORT, RTGETOPT_REQ_NOTHING },
+ { "--import-iso", RTFSISOMAKERCMD_OPT_IMPORT_ISO, RTGETOPT_REQ_STRING },
+ { "--push-iso", RTFSISOMAKERCMD_OPT_PUSH_ISO, RTGETOPT_REQ_STRING },
+ { "--push-iso-no-joliet", RTFSISOMAKERCMD_OPT_PUSH_ISO_NO_JOLIET, RTGETOPT_REQ_STRING },
+ { "--push-iso-no-rock", RTFSISOMAKERCMD_OPT_PUSH_ISO_NO_ROCK, RTGETOPT_REQ_STRING },
+ { "--push-iso-no-rock-no-joliet", RTFSISOMAKERCMD_OPT_PUSH_ISO_NO_ROCK_NO_JOLIET, RTGETOPT_REQ_STRING },
+ { "--pop", RTFSISOMAKERCMD_OPT_POP, RTGETOPT_REQ_NOTHING },
+
+ { "--rock-ridge", RTFSISOMAKERCMD_OPT_ROCK_RIDGE, RTGETOPT_REQ_NOTHING },
+ { "--limited-rock-ridge", RTFSISOMAKERCMD_OPT_LIMITED_ROCK_RIDGE, RTGETOPT_REQ_NOTHING },
+ { "--no-rock-ridge", RTFSISOMAKERCMD_OPT_NO_ROCK_RIDGE, RTGETOPT_REQ_NOTHING },
+ { "--no-joliet", RTFSISOMAKERCMD_OPT_NO_JOLIET, RTGETOPT_REQ_NOTHING },
+ { "--joliet-ucs-level", RTFSISOMAKERCMD_OPT_JOLIET_LEVEL, RTGETOPT_REQ_UINT8 },
+
+ { "--rational-attribs", RTFSISOMAKERCMD_OPT_RATIONAL_ATTRIBS, RTGETOPT_REQ_NOTHING },
+ { "--strict-attribs", RTFSISOMAKERCMD_OPT_STRICT_ATTRIBS, RTGETOPT_REQ_NOTHING },
+ { "--no-file-mode", RTFSISOMAKERCMD_OPT_NO_FILE_MODE, RTGETOPT_REQ_NOTHING },
+ { "--no-dir-mode", RTFSISOMAKERCMD_OPT_NO_DIR_MODE, RTGETOPT_REQ_NOTHING },
+ { "--chmod", RTFSISOMAKERCMD_OPT_CHMOD, RTGETOPT_REQ_STRING },
+ { "--chown", RTFSISOMAKERCMD_OPT_CHOWN, RTGETOPT_REQ_STRING },
+ { "--chgrp", RTFSISOMAKERCMD_OPT_CHGRP, RTGETOPT_REQ_STRING },
+
+ { "--eltorito-new-entry", RTFSISOMAKERCMD_OPT_ELTORITO_NEW_ENTRY, RTGETOPT_REQ_NOTHING },
+ { "--eltorito-add-image", RTFSISOMAKERCMD_OPT_ELTORITO_ADD_IMAGE, RTGETOPT_REQ_STRING },
+ { "--eltorito-floppy-12", RTFSISOMAKERCMD_OPT_ELTORITO_FLOPPY_12, RTGETOPT_REQ_NOTHING },
+ { "--eltorito-floppy-144", RTFSISOMAKERCMD_OPT_ELTORITO_FLOPPY_144, RTGETOPT_REQ_NOTHING },
+ { "--eltorito-floppy-288", RTFSISOMAKERCMD_OPT_ELTORITO_FLOPPY_288, RTGETOPT_REQ_NOTHING },
+
+ { "--iprt-iso-maker-file-marker", RTFSISOMAKERCMD_OPT_IPRT_ISO_MAKER_FILE_MARKER, RTGETOPT_REQ_STRING },
+ { "--iprt-iso-maker-file-marker-ms", RTFSISOMAKERCMD_OPT_IPRT_ISO_MAKER_FILE_MARKER, RTGETOPT_REQ_STRING },
+ { "--iprt-iso-maker-file-marker-ms-crt", RTFSISOMAKERCMD_OPT_IPRT_ISO_MAKER_FILE_MARKER, RTGETOPT_REQ_STRING },
+ { "--iprt-iso-maker-file-marker-bourne", RTFSISOMAKERCMD_OPT_IPRT_ISO_MAKER_FILE_MARKER, RTGETOPT_REQ_STRING },
+ { "--iprt-iso-maker-file-marker-bourne-sh", RTFSISOMAKERCMD_OPT_IPRT_ISO_MAKER_FILE_MARKER, RTGETOPT_REQ_STRING },
+
+ { "--output-buffer-size", RTFSISOMAKERCMD_OPT_OUTPUT_BUFFER_SIZE, RTGETOPT_REQ_UINT32 },
+ { "--random-output-buffer-size", RTFSISOMAKERCMD_OPT_RANDOM_OUTPUT_BUFFER_SIZE, RTGETOPT_REQ_NOTHING },
+ { "--random-order-verification", RTFSISOMAKERCMD_OPT_RANDOM_ORDER_VERIFICATION, RTGETOPT_REQ_UINT32 },
+
+#define DD(a_szLong, a_chShort, a_fFlags) { a_szLong, a_chShort, a_fFlags }, { "-" a_szLong, a_chShort, a_fFlags }
+
+ /*
+ * genisoimage/mkisofs compatibility options we've implemented:
+ */
+ /* booting: */
+ { "--generic-boot", 'G', RTGETOPT_REQ_STRING },
+ DD("-eltorito-boot", 'b', RTGETOPT_REQ_STRING ),
+ DD("-eltorito-alt-boot", RTFSISOMAKERCMD_OPT_ELTORITO_NEW_ENTRY, RTGETOPT_REQ_NOTHING ),
+ DD("-eltorito-platform-id", RTFSISOMAKERCMD_OPT_ELTORITO_PLATFORM_ID, RTGETOPT_REQ_STRING ),
+ DD("-hard-disk-boot", RTFSISOMAKERCMD_OPT_ELTORITO_HARD_DISK_BOOT, RTGETOPT_REQ_NOTHING ),
+ DD("-no-emulation-boot", RTFSISOMAKERCMD_OPT_ELTORITO_NO_EMULATION_BOOT, RTGETOPT_REQ_NOTHING ),
+ DD("-no-boot", RTFSISOMAKERCMD_OPT_ELTORITO_NO_BOOT, RTGETOPT_REQ_NOTHING ),
+ DD("-boot-load-seg", RTFSISOMAKERCMD_OPT_ELTORITO_LOAD_SEG, RTGETOPT_REQ_UINT16 ),
+ DD("-boot-load-size", RTFSISOMAKERCMD_OPT_ELTORITO_LOAD_SIZE, RTGETOPT_REQ_UINT16 ),
+ DD("-boot-info-table", RTFSISOMAKERCMD_OPT_ELTORITO_INFO_TABLE, RTGETOPT_REQ_NOTHING ),
+ { "--boot-catalog", 'c', RTGETOPT_REQ_STRING },
+
+ /* String props: */
+ DD("-abstract", RTFSISOMAKERCMD_OPT_ABSTRACT_FILE_ID, RTGETOPT_REQ_STRING ),
+ { "--application-id", 'A', RTGETOPT_REQ_STRING },
+ DD("-biblio", RTFSISOMAKERCMD_OPT_BIBLIOGRAPHIC_FILE_ID, RTGETOPT_REQ_STRING ),
+ DD("-copyright", RTFSISOMAKERCMD_OPT_COPYRIGHT_FILE_ID, RTGETOPT_REQ_STRING ),
+ DD("-publisher", 'P', RTGETOPT_REQ_STRING ),
+ { "--preparer", 'p', RTGETOPT_REQ_STRING },
+ DD("-sysid", RTFSISOMAKERCMD_OPT_SYSTEM_ID, RTGETOPT_REQ_STRING ),
+ { "--volume-id", RTFSISOMAKERCMD_OPT_VOLUME_ID, RTGETOPT_REQ_STRING }, /* should've been '-V' */
+ DD("-volid", RTFSISOMAKERCMD_OPT_VOLUME_ID, RTGETOPT_REQ_STRING ),
+ DD("-volset", RTFSISOMAKERCMD_OPT_VOLUME_SET_ID, RTGETOPT_REQ_STRING ),
+
+ /* Other: */
+ DD("-file-mode", RTFSISOMAKERCMD_OPT_FILE_MODE, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT ),
+ DD("-dir-mode", RTFSISOMAKERCMD_OPT_DIR_MODE, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT ),
+ DD("-new-dir-mode", RTFSISOMAKERCMD_OPT_NEW_DIR_MODE, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT ),
+ DD("-graft-points", RTFSISOMAKERCMD_OPT_GRAFT_POINTS, RTGETOPT_REQ_NOTHING ),
+ DD("--iso-level", RTFSISOMAKERCMD_OPT_ISO_LEVEL, RTGETOPT_REQ_UINT8 ),
+ { "--long-names", 'l', RTGETOPT_REQ_NOTHING },
+ { "--output", 'o', RTGETOPT_REQ_STRING },
+ { "--joliet", 'J', RTGETOPT_REQ_NOTHING },
+ DD("-ucs-level", RTFSISOMAKERCMD_OPT_JOLIET_LEVEL, RTGETOPT_REQ_UINT8 ),
+ DD("-rock", 'R', RTGETOPT_REQ_NOTHING ),
+ DD("-rational-rock", 'r', RTGETOPT_REQ_NOTHING ),
+ DD("-pad", RTFSISOMAKERCMD_OPT_PAD, RTGETOPT_REQ_NOTHING ),
+ DD("-no-pad", RTFSISOMAKERCMD_OPT_NO_PAD, RTGETOPT_REQ_NOTHING ),
+
+ /*
+ * genisoimage/mkisofs compatibility:
+ */
+ DD("-allow-limited-size", RTFSISOMAKERCMD_OPT_ALLOW_LIMITED_SIZE, RTGETOPT_REQ_NOTHING ),
+ DD("-allow-leading-dots", RTFSISOMAKERCMD_OPT_ALLOW_LEADING_DOTS, RTGETOPT_REQ_NOTHING ),
+ DD("-ldots", RTFSISOMAKERCMD_OPT_ALLOW_LEADING_DOTS, RTGETOPT_REQ_NOTHING ),
+ DD("-allow-lowercase", RTFSISOMAKERCMD_OPT_ALLOW_LOWERCASE, RTGETOPT_REQ_NOTHING ),
+ DD("-allow-multidot", RTFSISOMAKERCMD_OPT_ALLOW_MULTI_DOT, RTGETOPT_REQ_NOTHING ),
+ DD("-cache-inodes", RTFSISOMAKERCMD_OPT_DETECT_HARDLINKS, RTGETOPT_REQ_NOTHING ),
+ DD("-no-cache-inodes", RTFSISOMAKERCMD_OPT_NO_DETECT_HARDLINKS, RTGETOPT_REQ_NOTHING ),
+ DD("-alpha-boot", RTFSISOMAKERCMD_OPT_ALPHA_BOOT, RTGETOPT_REQ_STRING ),
+ DD("-hppa-bootloader", RTFSISOMAKERCMD_OPT_HPPA_BOOTLOADER, RTGETOPT_REQ_STRING ),
+ DD("-hppa-cmdline", RTFSISOMAKERCMD_OPT_HPPA_CMDLINE, RTGETOPT_REQ_STRING ),
+ DD("-hppa-kernel-32", RTFSISOMAKERCMD_OPT_HPPA_KERNEL_32, RTGETOPT_REQ_STRING ),
+ DD("-hppa-kernel-64", RTFSISOMAKERCMD_OPT_HPPA_KERNEL_64, RTGETOPT_REQ_STRING ),
+ DD("-hppa-ramdisk", RTFSISOMAKERCMD_OPT_HPPA_RAMDISK, RTGETOPT_REQ_STRING ),
+ DD("-mips-boot", RTFSISOMAKERCMD_OPT_MIPS_BOOT, RTGETOPT_REQ_STRING ),
+ DD("-mipsel-boot", RTFSISOMAKERCMD_OPT_MIPSEL_BOOT, RTGETOPT_REQ_STRING ),
+ DD("-sparc-boot", 'B', RTGETOPT_REQ_STRING ),
+ { "--cd-extra", 'C', RTGETOPT_REQ_STRING },
+ DD("-check-oldnames", RTFSISOMAKERCMD_OPT_CHECK_OLD_NAMES, RTGETOPT_REQ_NOTHING ),
+ DD("-check-session", RTFSISOMAKERCMD_OPT_CHECK_SESSION, RTGETOPT_REQ_STRING ),
+ { "--dont-append-dot", 'd', RTGETOPT_REQ_NOTHING },
+ { "--deep-directories", 'D', RTGETOPT_REQ_NOTHING },
+ DD("-dvd-video", RTFSISOMAKERCMD_OPT_DVD_VIDEO, RTGETOPT_REQ_NOTHING ),
+ DD("-follow-symlinks", 'f', RTGETOPT_REQ_NOTHING ),
+ DD("-gid", RTFSISOMAKERCMD_OPT_GID, RTGETOPT_REQ_UINT32 ),
+ DD("-gui", RTFSISOMAKERCMD_OPT_GUI, RTGETOPT_REQ_NOTHING ),
+ DD("-hide", RTFSISOMAKERCMD_OPT_HIDE, RTGETOPT_REQ_STRING ),
+ DD("-hide-list", RTFSISOMAKERCMD_OPT_HIDE_LIST, RTGETOPT_REQ_STRING ),
+ DD("-hidden", RTFSISOMAKERCMD_OPT_HIDDEN, RTGETOPT_REQ_STRING ),
+ DD("-hidden-list", RTFSISOMAKERCMD_OPT_HIDDEN_LIST, RTGETOPT_REQ_STRING ),
+ DD("-hide-joliet", RTFSISOMAKERCMD_OPT_HIDE_JOLIET, RTGETOPT_REQ_STRING ),
+ DD("-hide-joliet-list", RTFSISOMAKERCMD_OPT_HIDE_JOLIET_LIST, RTGETOPT_REQ_STRING ),
+ DD("-hide-joliet-trans-tbl", RTFSISOMAKERCMD_OPT_HIDE_JOLIET_TRANS_TBL, RTGETOPT_REQ_NOTHING ),
+ DD("-hide-rr-moved", RTFSISOMAKERCMD_OPT_HIDE_RR_MOVED, RTGETOPT_REQ_NOTHING ),
+ DD("-input-charset", RTFSISOMAKERCMD_OPT_INPUT_CHARSET, RTGETOPT_REQ_STRING ),
+ DD("-output-charset", RTFSISOMAKERCMD_OPT_OUTPUT_CHARSET, RTGETOPT_REQ_STRING ),
+ DD("-joliet-long", RTFSISOMAKERCMD_OPT_JOLIET_LONG, RTGETOPT_REQ_NOTHING ),
+ DD("-jcharset", RTFSISOMAKERCMD_OPT_JOLIET_CHARSET, RTGETOPT_REQ_STRING ),
+ { "--leading-dot", 'L', RTGETOPT_REQ_NOTHING },
+ DD("-jigdo-jigdo", RTFSISOMAKERCMD_OPT_JIGDO_JIGDO, RTGETOPT_REQ_STRING ),
+ DD("-jigdo-template", RTFSISOMAKERCMD_OPT_JIGDO_TEMPLATE, RTGETOPT_REQ_STRING ),
+ DD("-jigdo-min-file-size", RTFSISOMAKERCMD_OPT_JIGDO_MIN_FILE_SIZE, RTGETOPT_REQ_UINT64 ),
+ DD("-jigdo-force-md5", RTFSISOMAKERCMD_OPT_JIGDO_FORCE_MD5, RTGETOPT_REQ_STRING ),
+ DD("-jigdo-exclude", RTFSISOMAKERCMD_OPT_JIGDO_EXCLUDE, RTGETOPT_REQ_STRING ),
+ DD("-jigdo-map", RTFSISOMAKERCMD_OPT_JIGDO_MAP, RTGETOPT_REQ_STRING ),
+ DD("-md5-list", RTFSISOMAKERCMD_OPT_JIGDO_MD5_LIST, RTGETOPT_REQ_STRING ),
+ DD("-jigdo-template-compress", RTFSISOMAKERCMD_OPT_JIGDO_COMPRESS, RTGETOPT_REQ_STRING ),
+ DD("-log-file", RTFSISOMAKERCMD_OPT_LOG_FILE, RTGETOPT_REQ_STRING ),
+ { "--exclude", 'm', RTGETOPT_REQ_STRING },
+ { "--exclude", 'x', RTGETOPT_REQ_STRING },
+ DD("-exclude-list", RTFSISOMAKERCMD_OPT_EXCLUDE_LIST, RTGETOPT_REQ_STRING ),
+ DD("-max-iso9660-filenames", RTFSISOMAKERCMD_OPT_MAX_ISO9660_FILENAMES, RTGETOPT_REQ_NOTHING ),
+ { "--merge", 'M', RTGETOPT_REQ_STRING },
+ DD("-dev", 'M', RTGETOPT_REQ_STRING ),
+ { "--omit-version-numbers", 'N', RTGETOPT_REQ_NOTHING },
+ DD("-nobak", RTFSISOMAKERCMD_OPT_NO_BACKUP_FILES, RTGETOPT_REQ_NOTHING ),
+ DD("-no-bak", RTFSISOMAKERCMD_OPT_NO_BACKUP_FILES, RTGETOPT_REQ_NOTHING ),
+ DD("-force-rr", RTFSISOMAKERCMD_OPT_FORCE_RR, RTGETOPT_REQ_NOTHING ),
+ DD("-no-rr", RTFSISOMAKERCMD_OPT_NO_RR, RTGETOPT_REQ_NOTHING ),
+ DD("-no-split-symlink-components", RTFSISOMAKERCMD_OPT_NO_SPLIT_SYMLINK_COMPONENTS, RTGETOPT_REQ_NOTHING ),
+ DD("-no-split-symlink-fields", RTFSISOMAKERCMD_OPT_NO_SPLIT_SYMLINK_FIELDS, RTGETOPT_REQ_NOTHING ),
+ DD("-path-list", RTFSISOMAKERCMD_OPT_PATH_LIST, RTGETOPT_REQ_STRING ),
+ DD("-print-size", RTFSISOMAKERCMD_OPT_PRINT_SIZE, RTGETOPT_REQ_NOTHING ),
+ DD("-quiet", RTFSISOMAKERCMD_OPT_QUIET, RTGETOPT_REQ_NOTHING ),
+ DD("-relaxed-filenames", RTFSISOMAKERCMD_OPT_RELAXED_FILENAMES, RTGETOPT_REQ_NOTHING ),
+ DD("-root", RTFSISOMAKERCMD_OPT_ROOT, RTGETOPT_REQ_STRING ),
+ DD("-old-root", RTFSISOMAKERCMD_OPT_OLD_ROOT, RTGETOPT_REQ_STRING ),
+ DD("-sort", RTFSISOMAKERCMD_OPT_SORT, RTGETOPT_REQ_STRING ),
+ DD("-sparc-boot", RTFSISOMAKERCMD_OPT_SPARC_BOOT, RTGETOPT_REQ_STRING ),
+ DD("-sparc-label", RTFSISOMAKERCMD_OPT_SPARC_LABEL, RTGETOPT_REQ_STRING ),
+ DD("-split-output", RTFSISOMAKERCMD_OPT_SPLIT_OUTPUT, RTGETOPT_REQ_NOTHING ),
+ DD("-stream-media-size", RTFSISOMAKERCMD_OPT_STREAM_MEDIA_SIZE, RTGETOPT_REQ_UINT64 ),
+ DD("-stream-file-name", RTFSISOMAKERCMD_OPT_STREAM_FILE_NAME, RTGETOPT_REQ_STRING ),
+ DD("-sunx86-boot", RTFSISOMAKERCMD_OPT_SUNX86_BOOT, RTGETOPT_REQ_STRING ),
+ DD("-sunx86-label", RTFSISOMAKERCMD_OPT_SUNX86_LABEL, RTGETOPT_REQ_STRING ),
+ { "--trans-tbl", 'T', RTGETOPT_REQ_NOTHING },
+ DD("-table-name", RTFSISOMAKERCMD_OPT_TRANS_TBL_NAME, RTGETOPT_REQ_STRING ),
+ DD("-udf", RTFSISOMAKERCMD_OPT_UDF, RTGETOPT_REQ_NOTHING ),
+ DD("-uid", RTFSISOMAKERCMD_OPT_UID, RTGETOPT_REQ_UINT32 ),
+ DD("-use-fileversion", RTFSISOMAKERCMD_OPT_USE_FILE_VERSION, RTGETOPT_REQ_NOTHING ),
+ { "--untranslated-filenames", 'U', RTGETOPT_REQ_NOTHING },
+ DD("-no-iso-translate", RTFSISOMAKERCMD_OPT_NO_ISO_TRANSLATE, RTGETOPT_REQ_NOTHING ),
+ DD("-volset-size", RTFSISOMAKERCMD_OPT_VOLUME_SET_SIZE, RTGETOPT_REQ_UINT32 ),
+ DD("-volset-seqno", RTFSISOMAKERCMD_OPT_VOLUME_SET_SEQ_NO, RTGETOPT_REQ_UINT32 ),
+ { "--transpared-compression", 'z', RTGETOPT_REQ_NOTHING },
+
+ /* HFS and ISO-9660 apple extensions. */
+ DD("-hfs", RTFSISOMAKERCMD_OPT_HFS_ENABLE, RTGETOPT_REQ_NOTHING ),
+ DD("-apple", RTFSISOMAKERCMD_OPT_APPLE, RTGETOPT_REQ_NOTHING ),
+ DD("-map", RTFSISOMAKERCMD_OPT_HFS_MAP, RTGETOPT_REQ_STRING ),
+ DD("-magic", RTFSISOMAKERCMD_OPT_HFS_MAGIC, RTGETOPT_REQ_STRING ),
+ DD("-hfs-creator", RTFSISOMAKERCMD_OPT_HFS_CREATOR, RTGETOPT_REQ_STRING ),
+ DD("-hfs-type", RTFSISOMAKERCMD_OPT_HFS_TYPE, RTGETOPT_REQ_STRING ),
+ DD("-probe", RTFSISOMAKERCMD_OPT_HFS_PROBE, RTGETOPT_REQ_NOTHING ),
+ DD("-no-desktop", RTFSISOMAKERCMD_OPT_HFS_NO_DESKTOP, RTGETOPT_REQ_NOTHING ),
+ DD("-mac-name", RTFSISOMAKERCMD_OPT_HFS_MAC_NAME, RTGETOPT_REQ_NOTHING ),
+ DD("-boot-hfs-file", RTFSISOMAKERCMD_OPT_HFS_BOOT_FILE, RTGETOPT_REQ_STRING ),
+ DD("-part", RTFSISOMAKERCMD_OPT_HFS_PART, RTGETOPT_REQ_NOTHING ),
+ DD("-auto", RTFSISOMAKERCMD_OPT_HFS_AUTO, RTGETOPT_REQ_STRING ),
+ DD("-cluster-size", RTFSISOMAKERCMD_OPT_HFS_CLUSTER_SIZE, RTGETOPT_REQ_UINT32 ),
+ DD("-hide-hfs", RTFSISOMAKERCMD_OPT_HFS_HIDE, RTGETOPT_REQ_STRING ),
+ DD("-hide-hfs-list", RTFSISOMAKERCMD_OPT_HFS_HIDE_LIST, RTGETOPT_REQ_STRING ),
+ DD("-hfs-volid", RTFSISOMAKERCMD_OPT_HFS_VOL_ID, RTGETOPT_REQ_STRING ),
+ DD("-icon-position", RTFSISOMAKERCMD_OPT_HFS_ICON_POSITION, RTGETOPT_REQ_NOTHING ),
+ DD("-root-info", RTFSISOMAKERCMD_OPT_HFS_ROOT_INFO, RTGETOPT_REQ_STRING ),
+ DD("-prep-boot", RTFSISOMAKERCMD_OPT_HFS_PREP_BOOT, RTGETOPT_REQ_STRING ),
+ DD("-chrp-boot", RTFSISOMAKERCMD_OPT_HFS_CHRP_BOOT, RTGETOPT_REQ_NOTHING ),
+ DD("-input-hfs-charset", RTFSISOMAKERCMD_OPT_HFS_INPUT_CHARSET, RTGETOPT_REQ_STRING ),
+ DD("-output-hfs-charset", RTFSISOMAKERCMD_OPT_HFS_OUTPUT_CHARSET, RTGETOPT_REQ_STRING ),
+ DD("-hfs-unlock", RTFSISOMAKERCMD_OPT_HFS_UNLOCK, RTGETOPT_REQ_NOTHING ),
+ DD("-hfs-bless", RTFSISOMAKERCMD_OPT_HFS_BLESS, RTGETOPT_REQ_STRING ),
+ DD("-hfs-parms", RTFSISOMAKERCMD_OPT_HFS_PARMS, RTGETOPT_REQ_STRING ),
+ { "--cap", RTFSISOMAKERCMD_OPT_HFS_CAP, RTGETOPT_REQ_NOTHING },
+ { "--netatalk", RTFSISOMAKERCMD_OPT_HFS_NETATALK, RTGETOPT_REQ_NOTHING },
+ { "--double", RTFSISOMAKERCMD_OPT_HFS_DOUBLE, RTGETOPT_REQ_NOTHING },
+ { "--ethershare", RTFSISOMAKERCMD_OPT_HFS_ETHERSHARE, RTGETOPT_REQ_NOTHING },
+ { "--ushare", RTFSISOMAKERCMD_OPT_HFS_USHARE, RTGETOPT_REQ_NOTHING },
+ { "--exchange", RTFSISOMAKERCMD_OPT_HFS_EXCHANGE, RTGETOPT_REQ_NOTHING },
+ { "--sgi", RTFSISOMAKERCMD_OPT_HFS_SGI, RTGETOPT_REQ_NOTHING },
+ { "--xinet", RTFSISOMAKERCMD_OPT_HFS_XINET, RTGETOPT_REQ_NOTHING },
+ { "--macbin", RTFSISOMAKERCMD_OPT_HFS_MACBIN, RTGETOPT_REQ_NOTHING },
+ { "--single", RTFSISOMAKERCMD_OPT_HFS_SINGLE, RTGETOPT_REQ_NOTHING },
+ { "--dave", RTFSISOMAKERCMD_OPT_HFS_DAVE, RTGETOPT_REQ_NOTHING },
+ { "--sfm", RTFSISOMAKERCMD_OPT_HFS_SFM, RTGETOPT_REQ_NOTHING },
+ { "--osx-double", RTFSISOMAKERCMD_OPT_HFS_OSX_DOUBLE, RTGETOPT_REQ_NOTHING },
+ { "--osx-hfs", RTFSISOMAKERCMD_OPT_HFS_OSX_HFS, RTGETOPT_REQ_NOTHING },
+#undef DD
+};
+
+#ifndef RT_OS_OS2 /* fixme */
+# include "isomakercmd-man.h"
+#endif
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int rtFsIsoMakerCmdParse(PRTFSISOMAKERCMDOPTS pOpts, unsigned cArgs, char **papszArgs, unsigned cDepth);
+
+
+/**
+ * Wrapper around RTErrInfoSetV / RTMsgErrorV.
+ *
+ * @returns @a rc
+ * @param pOpts The ISO maker command instance.
+ * @param rc The return code.
+ * @param pszFormat The message format.
+ * @param ... The message format arguments.
+ */
+static int rtFsIsoMakerCmdErrorRc(PRTFSISOMAKERCMDOPTS pOpts, int rc, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ if (pOpts->pErrInfo)
+ RTErrInfoSetV(pOpts->pErrInfo, rc, pszFormat, va);
+ else
+ RTMsgErrorV(pszFormat, va);
+ va_end(va);
+ return rc;
+}
+
+
+/**
+ * Wrapper around RTErrInfoSetV / RTMsgErrorV for doing the job of
+ * RTVfsChainMsgError.
+ *
+ * @returns @a rc
+ * @param pOpts The ISO maker command instance.
+ * @param pszFunction The API called.
+ * @param pszSpec The VFS chain specification or file path passed to the.
+ * @param rc The return code.
+ * @param offError The error offset value returned (0 if not captured).
+ * @param pErrInfo Additional error information. Optional.
+ */
+static int rtFsIsoMakerCmdChainError(PRTFSISOMAKERCMDOPTS pOpts, const char *pszFunction, const char *pszSpec, int rc,
+ uint32_t offError, PRTERRINFO pErrInfo)
+{
+ if (RTErrInfoIsSet(pErrInfo))
+ {
+ if (offError > 0)
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc,
+ "%s failed with rc=%Rrc: %s\n"
+ " '%s'\n"
+ " %*s^",
+ pszFunction, rc, pErrInfo->pszMsg, pszSpec, offError, "");
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "%s failed to open '%s': %Rrc: %s",
+ pszFunction, pszSpec, rc, pErrInfo->pszMsg);
+ }
+ else
+ {
+ if (offError > 0)
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc,
+ "%s failed with rc=%Rrc:\n"
+ " '%s'\n"
+ " %*s^",
+ pszFunction, rc, pszSpec, offError, "");
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "%s failed to open '%s': %Rrc", pszFunction, pszSpec, rc);
+ }
+ return rc;
+}
+
+
+/**
+ * Wrapper around RTErrInfoSetV / RTMsgErrorV for displaying syntax errors.
+ *
+ * @returns VERR_INVALID_PARAMETER
+ * @param pOpts The ISO maker command instance.
+ * @param pszFormat The message format.
+ * @param ... The message format arguments.
+ */
+static int rtFsIsoMakerCmdSyntaxError(PRTFSISOMAKERCMDOPTS pOpts, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ if (pOpts->pErrInfo)
+ RTErrInfoSetV(pOpts->pErrInfo, VERR_INVALID_PARAMETER, pszFormat, va);
+ else
+ RTMsgErrorV(pszFormat, va);
+ va_end(va);
+ return VERR_INVALID_PARAMETER;
+}
+
+
+/**
+ * Wrapper around RTPrintfV / RTLogRelPrintfV.
+ *
+ * @param pOpts The ISO maker command instance.
+ * @param pszFormat The message format.
+ * @param ... The message format arguments.
+ */
+static void rtFsIsoMakerPrintf(PRTFSISOMAKERCMDOPTS pOpts, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ if (pOpts->pErrInfo)
+ RTLogRelPrintfV(pszFormat, va);
+ else
+ RTPrintfV(pszFormat, va);
+ va_end(va);
+}
+
+/**
+ * Deletes the state and returns @a rc.
+ *
+ * @returns @a rc.
+ * @param pOpts The ISO maker command instance to delete.
+ * @param rc The status code to return.
+ */
+static int rtFsIsoMakerCmdDeleteState(PRTFSISOMAKERCMDOPTS pOpts, int rc)
+{
+ if (pOpts->hIsoMaker != NIL_RTFSISOMAKER)
+ {
+ RTFsIsoMakerRelease(pOpts->hIsoMaker);
+ pOpts->hIsoMaker = NIL_RTFSISOMAKER;
+ }
+
+ while (pOpts->iSrcStack >= 0)
+ {
+ RTVfsDirRelease(pOpts->aSrcStack[pOpts->iSrcStack].hSrcDir);
+ RTVfsRelease(pOpts->aSrcStack[pOpts->iSrcStack].hSrcVfs);
+ pOpts->aSrcStack[pOpts->iSrcStack].hSrcDir = NIL_RTVFSDIR;
+ pOpts->aSrcStack[pOpts->iSrcStack].hSrcVfs = NIL_RTVFS;
+ pOpts->iSrcStack--;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Print the usage.
+ *
+ * @param pOpts Options for print metho.
+ * @param pszProgName The program name.
+ */
+static void rtFsIsoMakerCmdUsage(PRTFSISOMAKERCMDOPTS pOpts, const char *pszProgName)
+{
+#ifndef RT_OS_OS2 /* fixme */
+ if (!pOpts->pErrInfo)
+ RTMsgRefEntryHelp(g_pStdOut, &g_viso);
+ else
+#endif
+ rtFsIsoMakerPrintf(pOpts, "Usage: %s [options] [@commands.rsp] <filespec...>\n",
+ RTPathFilename(pszProgName));
+}
+
+
+/**
+ * Verifies the image content by reading blocks in random order.
+ *
+ * This is for exercise the virtual ISO code better and test that we get the
+ * same data when reading something twice.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param hVfsSrcFile The source file (virtual ISO).
+ * @param hVfsDstFile The destination file (image file on disk).
+ * @param cbImage The size of the ISO.
+ */
+static int rtFsIsoMakerCmdVerifyImageInRandomOrder(PRTFSISOMAKERCMDOPTS pOpts, RTVFSFILE hVfsSrcFile,
+ RTVFSFILE hVfsDstFile, uint64_t cbImage)
+{
+ /*
+ * Figure the buffer (block) size and allocate a bitmap for noting down blocks we've covered.
+ */
+ int rc;
+ size_t cbBuf = RT_MAX(pOpts->cbRandomOrderVerifciationBlock, 1);
+ uint64_t cBlocks64 = (cbImage + cbBuf - 1) / cbBuf;
+ if (cBlocks64 > _512M)
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_OUT_OF_RANGE,
+ "verification block count too high: cBlocks=%#RX64 (cbBuf=%#zx), max 512M", cBlocks64, cbBuf);
+ uint32_t cBlocks = (uint32_t)cBlocks64;
+ uint32_t cbBitmap = (cBlocks + 63) / 8;
+ if (cbBitmap > _64M)
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_OUT_OF_RANGE,
+ "verification bitmap too big: cbBitmap=%#RX32 (cbBuf=%#zx), max 64MB", cbBitmap, cbBuf);
+ void *pvSrcBuf = RTMemTmpAlloc(cbBuf);
+ void *pvDstBuf = RTMemTmpAlloc(cbBuf);
+ void *pvBitmap = RTMemTmpAllocZ(cbBitmap);
+ if (pvSrcBuf && pvDstBuf && pvBitmap)
+ {
+ /* Must set the unused bits in the top qword. */
+ for (uint32_t i = RT_ALIGN_32(cBlocks, 64) - 1; i >= cBlocks; i--)
+ ASMBitSet(pvBitmap, i);
+
+ /*
+ * Do the verification.
+ */
+ rtFsIsoMakerPrintf(pOpts, "Verifying image in random order using %zu (%#zx) byte blocks: %#RX32 in blocks\n",
+ cbBuf, cbBuf, cBlocks);
+
+ rc = VINF_SUCCESS;
+ uint64_t cLeft = cBlocks;
+ while (cLeft-- > 0)
+ {
+ /*
+ * Figure out which block to check next.
+ */
+ uint32_t iBlock = RTRandU32Ex(0, cBlocks - 1);
+ if (!ASMBitTestAndSet(pvBitmap, iBlock))
+ Assert(iBlock < cBlocks);
+ else
+ {
+ /* try 32 other random numbers. */
+ bool fBitSet;
+ unsigned cTries = 0;
+ do
+ {
+ iBlock = RTRandU32Ex(0, cBlocks - 1);
+ fBitSet = ASMBitTestAndSet(pvBitmap, iBlock);
+ } while (fBitSet && ++cTries < 32);
+ if (fBitSet)
+ {
+ /* Look for the next clear bit after it (with wrap around). */
+ int iHit = ASMBitNextClear(pvBitmap, RT_ALIGN_32(cBlocks, 64), iBlock);
+ Assert(iHit < (int32_t)cBlocks);
+ if (iHit < 0)
+ {
+ iHit = ASMBitFirstClear(pvBitmap, RT_ALIGN_32(iBlock, 64));
+ Assert(iHit < (int32_t)cBlocks);
+ }
+ if (iHit >= 0)
+ {
+ fBitSet = ASMBitTestAndSet(pvBitmap, iHit);
+ if (!fBitSet)
+ iBlock = iHit;
+ else
+ {
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_INTERNAL_ERROR_3,
+ "Bitmap weirdness: iHit=%#x iBlock=%#x cLeft=%#x cBlocks=%#x",
+ iHit, iBlock, cLeft, cBlocks);
+ if (!pOpts->pErrInfo)
+ RTMsgInfo("Bitmap: %#RX32 bytes\n%.*Rhxd", cbBitmap, cbBitmap, pvBitmap);
+ break;
+ }
+ }
+ else
+ {
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_INTERNAL_ERROR_2,
+ "Bitmap weirdness: iBlock=%#x cLeft=%#x cBlocks=%#x",
+ iBlock, cLeft, cBlocks);
+ if (!pOpts->pErrInfo)
+ RTMsgInfo("Bitmap: %#RX32 bytes\n%.*Rhxd", cbBitmap, cbBitmap, pvBitmap);
+ break;
+ }
+ }
+ }
+ Assert(ASMBitTest(pvBitmap, iBlock));
+
+ /*
+ * Figure out how much and where to read (last block fun).
+ */
+ uint64_t offBlock = iBlock * (uint64_t)cbBuf;
+ size_t cbToRead = cbBuf;
+ if (iBlock + 1 < cBlocks)
+ { /* likely */ }
+ else if (cbToRead > cbImage - offBlock)
+ cbToRead = (size_t)(cbImage - offBlock);
+ Assert(offBlock + cbToRead <= cbImage);
+
+ /*
+ * Read the blocks.
+ */
+ //RTPrintf("Reading block #%#x at %#RX64\n", iBlock, offBlock);
+ rc = RTVfsFileReadAt(hVfsDstFile, offBlock, pvDstBuf, cbToRead, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ memset(pvSrcBuf, 0xdd, cbBuf);
+ rc = RTVfsFileReadAt(hVfsSrcFile, offBlock, pvSrcBuf, cbToRead, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (memcmp(pvDstBuf, pvSrcBuf, cbToRead) == 0)
+ continue;
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_MISMATCH,
+ "Block #%#x differs! offBlock=%#RX64 cbToRead=%#zu\n"
+ "Virtual ISO (source):\n%.*Rhxd\nWritten ISO (destination):\n%.*Rhxd",
+ iBlock, offBlock, cbToRead, cbToRead, pvSrcBuf, cbToRead, pvDstBuf);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc,
+ "Error reading %#zx bytes source (virtual ISO) block #%#x at %#RX64: %Rrc",
+ cbToRead, iBlock, offBlock, rc);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc,
+ "Error reading %#zx bytes destination (written ISO) block #%#x at %#RX64: %Rrc",
+ cbToRead, iBlock, offBlock, rc);
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ rtFsIsoMakerPrintf(pOpts, "Written image verified fine!\n");
+ }
+ else if (!pvSrcBuf || !pvDstBuf)
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_NO_TMP_MEMORY, "RTMemTmpAlloc(%#zx) failed", cbBuf);
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_NO_TMP_MEMORY, "RTMemTmpAlloc(%#zx) failed", cbBuf);
+ RTMemTmpFree(pvBitmap);
+ RTMemTmpFree(pvDstBuf);
+ RTMemTmpFree(pvSrcBuf);
+ return rc;
+}
+
+
+/**
+ * Writes the image to file, no checking, no special buffering.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param hVfsSrcFile The source file from the ISO maker.
+ * @param hVfsDstFile The destination file (image file on disk).
+ * @param cbImage The size of the ISO.
+ * @param ppvBuf Pointer to the buffer pointer. The buffer will
+ * be reallocated, but we want the luxary of the
+ * caller freeing it.
+ */
+static int rtFsIsoMakerCmdWriteImageRandomBufferSize(PRTFSISOMAKERCMDOPTS pOpts, RTVFSFILE hVfsSrcFile, RTVFSFILE hVfsDstFile,
+ uint64_t cbImage, void **ppvBuf)
+{
+ /*
+ * Copy the virtual image bits to the destination file.
+ */
+ void *pvBuf = *ppvBuf;
+ uint32_t cbMaxBuf = pOpts->cbOutputReadBuffer > 0 ? pOpts->cbOutputReadBuffer : _64K;
+ uint64_t offImage = 0;
+ while (offImage < cbImage)
+ {
+ /* Figure out how much to copy this time. */
+ size_t cbToCopy = RTRandU32Ex(1, cbMaxBuf - 1);
+ if (offImage + cbToCopy < cbImage)
+ { /* likely */ }
+ else
+ cbToCopy = (size_t)(cbImage - offImage);
+ RTMemFree(pvBuf);
+ *ppvBuf = pvBuf = RTMemTmpAlloc(cbToCopy);
+ if (pvBuf)
+ {
+ /* Do the copying. */
+ int rc = RTVfsFileReadAt(hVfsSrcFile, offImage, pvBuf, cbToCopy, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsFileWriteAt(hVfsDstFile, offImage, pvBuf, cbToCopy, NULL);
+ if (RT_SUCCESS(rc))
+ offImage += cbToCopy;
+ else
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error %Rrc writing %#zx bytes at offset %#RX64 to '%s'",
+ rc, cbToCopy, offImage, pOpts->pszOutFile);
+ }
+ else
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error %Rrc read %#zx bytes at offset %#RX64", rc, cbToCopy, offImage);
+ }
+ else
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_NO_TMP_MEMORY, "RTMemTmpAlloc(%#zx) failed", cbToCopy);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Writes the image to file, no checking, no special buffering.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param hVfsSrcFile The source file from the ISO maker.
+ * @param hVfsDstFile The destination file (image file on disk).
+ * @param cbImage The size of the ISO.
+ * @param pvBuf Pointer to read buffer.
+ * @param cbBuf The buffer size.
+ */
+static int rtFsIsoMakerCmdWriteImageSimple(PRTFSISOMAKERCMDOPTS pOpts, RTVFSFILE hVfsSrcFile, RTVFSFILE hVfsDstFile,
+ uint64_t cbImage, void *pvBuf, size_t cbBuf)
+{
+ /*
+ * Copy the virtual image bits to the destination file.
+ */
+ uint64_t offImage = 0;
+ while (offImage < cbImage)
+ {
+ /* Figure out how much to copy this time. */
+ size_t cbToCopy = cbBuf;
+ if (offImage + cbToCopy < cbImage)
+ { /* likely */ }
+ else
+ cbToCopy = (size_t)(cbImage - offImage);
+
+ /* Do the copying. */
+ int rc = RTVfsFileReadAt(hVfsSrcFile, offImage, pvBuf, cbToCopy, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsFileWriteAt(hVfsDstFile, offImage, pvBuf, cbToCopy, NULL);
+ if (RT_SUCCESS(rc))
+ offImage += cbToCopy;
+ else
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error %Rrc writing %#zx bytes at offset %#RX64 to '%s'",
+ rc, cbToCopy, offImage, pOpts->pszOutFile);
+ }
+ else
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error %Rrc read %#zx bytes at offset %#RX64", rc, cbToCopy, offImage);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Writes the image to file.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param hVfsSrcFile The source file from the ISO maker.
+ */
+static int rtFsIsoMakerCmdWriteImage(PRTFSISOMAKERCMDOPTS pOpts, RTVFSFILE hVfsSrcFile)
+{
+ /*
+ * Get the image size and setup the copy buffer.
+ */
+ uint64_t cbImage;
+ int rc = RTVfsFileQuerySize(hVfsSrcFile, &cbImage);
+ if (RT_SUCCESS(rc))
+ {
+ rtFsIsoMakerPrintf(pOpts, "Image size: %'RU64 (%#RX64) bytes\n", cbImage, cbImage);
+
+ uint32_t cbBuf = pOpts->cbOutputReadBuffer == 0 ? _1M : pOpts->cbOutputReadBuffer;
+ void *pvBuf = RTMemTmpAlloc(cbBuf);
+ if (pvBuf)
+ {
+ /*
+ * Open the output file.
+ */
+ RTVFSFILE hVfsDstFile;
+ uint32_t offError;
+ RTERRINFOSTATIC ErrInfo;
+ rc = RTVfsChainOpenFile(pOpts->pszOutFile,
+ RTFILE_O_READWRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_WRITE
+ | (0664 << RTFILE_O_CREATE_MODE_SHIFT),
+ &hVfsDstFile, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Apply the desired writing method.
+ */
+ if (!pOpts->fRandomOutputReadBufferSize)
+ rc = rtFsIsoMakerCmdWriteImageRandomBufferSize(pOpts, hVfsSrcFile, hVfsDstFile, cbImage, &pvBuf);
+ else
+ rc = rtFsIsoMakerCmdWriteImageSimple(pOpts, hVfsSrcFile, hVfsDstFile, cbImage, pvBuf, cbBuf);
+ RTMemTmpFree(pvBuf);
+
+ if (RT_SUCCESS(rc) && pOpts->cbRandomOrderVerifciationBlock > 0)
+ rc = rtFsIsoMakerCmdVerifyImageInRandomOrder(pOpts, hVfsSrcFile, hVfsDstFile, cbImage);
+
+ /*
+ * Flush the output file before releasing it.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsFileFlush(hVfsDstFile);
+ if (RT_FAILURE(rc))
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTVfsFileFlush failed on '%s': %Rrc", pOpts->pszOutFile, rc);
+ }
+
+ RTVfsFileRelease(hVfsDstFile);
+ }
+ else
+ {
+ RTMemTmpFree(pvBuf);
+ rc = rtFsIsoMakerCmdChainError(pOpts, "RTVfsChainOpenFile", pOpts->pszOutFile, rc, offError, &ErrInfo.Core);
+ }
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_NO_TMP_MEMORY, "RTMemTmpAlloc(%zu) failed", cbBuf);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTVfsFileQuerySize failed: %Rrc", rc);
+ return rc;
+}
+
+
+/**
+ * Formats @a fNameSpecifiers into a '+' separated list of names.
+ *
+ * @returns pszDst
+ * @param fNameSpecifiers The name specifiers.
+ * @param pszDst The destination bufer.
+ * @param cbDst The size of the destination buffer.
+ */
+static char *rtFsIsoMakerCmdNameSpecifiersToString(uint32_t fNameSpecifiers, char *pszDst, size_t cbDst)
+{
+ static struct { const char *pszName; uint32_t cchName; uint32_t fSpec; } const s_aSpecs[] =
+ {
+ { RT_STR_TUPLE("primary"), RTFSISOMAKERCMDNAME_PRIMARY_ISO },
+ { RT_STR_TUPLE("primary-rock"), RTFSISOMAKERCMDNAME_PRIMARY_ISO_ROCK_RIDGE },
+ { RT_STR_TUPLE("primary-trans-tbl"), RTFSISOMAKERCMDNAME_PRIMARY_ISO_TRANS_TBL },
+ { RT_STR_TUPLE("joliet"), RTFSISOMAKERCMDNAME_JOLIET },
+ { RT_STR_TUPLE("joliet-rock"), RTFSISOMAKERCMDNAME_JOLIET_ROCK_RIDGE },
+ { RT_STR_TUPLE("joliet-trans-tbl"), RTFSISOMAKERCMDNAME_JOLIET_TRANS_TBL },
+ { RT_STR_TUPLE("udf"), RTFSISOMAKERCMDNAME_UDF },
+ { RT_STR_TUPLE("udf-trans-tbl"), RTFSISOMAKERCMDNAME_UDF_TRANS_TBL },
+ { RT_STR_TUPLE("hfs"), RTFSISOMAKERCMDNAME_HFS },
+ { RT_STR_TUPLE("hfs-trans-tbl"), RTFSISOMAKERCMDNAME_HFS_TRANS_TBL },
+ };
+
+ Assert(cbDst > 0);
+ char *pszRet = pszDst;
+ for (uint32_t i = 0; i < RT_ELEMENTS(s_aSpecs); i++)
+ if (s_aSpecs[i].fSpec & fNameSpecifiers)
+ {
+ if (pszDst != pszRet && cbDst > 1)
+ {
+ *pszDst++ = '+';
+ cbDst--;
+ }
+ if (cbDst > s_aSpecs[i].cchName)
+ {
+ memcpy(pszDst, s_aSpecs[i].pszName, s_aSpecs[i].cchName);
+ cbDst -= s_aSpecs[i].cchName;
+ pszDst += s_aSpecs[i].cchName;
+ }
+ else if (cbDst > 1)
+ {
+ memcpy(pszDst, s_aSpecs[i].pszName, cbDst - 1);
+ pszDst += cbDst - 1;
+ cbDst = 1;
+ }
+
+ fNameSpecifiers &= ~s_aSpecs[i].fSpec;
+ if (!fNameSpecifiers)
+ break;
+ }
+ *pszDst = '\0';
+ return pszRet;
+}
+
+
+/**
+ * Parses the --name-setup option.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param pszSpec The name setup specification.
+ */
+static int rtFsIsoMakerCmdOptNameSetup(PRTFSISOMAKERCMDOPTS pOpts, const char *pszSpec)
+{
+ /*
+ * Comma separated list of one or more specifiers.
+ */
+ uint32_t fNamespaces = 0;
+ uint32_t fPrevMajor = 0;
+ uint32_t iNameSpecifier = 0;
+ uint32_t offSpec = 0;
+ do
+ {
+ /*
+ * Parse up to the next colon or end of string.
+ */
+ uint32_t fNameSpecifier = 0;
+ char ch;
+ while ( (ch = pszSpec[offSpec]) != '\0'
+ && ch != ',')
+ {
+ if (RT_C_IS_SPACE(ch) || ch == '+' || ch == '|') /* space, '+' and '|' are allowed as name separators. */
+ offSpec++;
+ else
+ {
+ /* Find the end of the name. */
+ uint32_t offEndSpec = offSpec + 1;
+ while ( (ch = pszSpec[offEndSpec]) != '\0'
+ && ch != ','
+ && ch != '+'
+ && ch != '|'
+ && !RT_C_IS_SPACE(ch))
+ offEndSpec++;
+
+#define IS_EQUAL(a_sz) (cchName == sizeof(a_sz) - 1U && strncmp(pchName, a_sz, sizeof(a_sz) - 1U) == 0)
+ const char * const pchName = &pszSpec[offSpec];
+ uint32_t const cchName = offEndSpec - offSpec;
+ /* major namespaces */
+ if ( IS_EQUAL("iso")
+ || IS_EQUAL("primary")
+ || IS_EQUAL("iso9660")
+ || IS_EQUAL("iso-9660")
+ || IS_EQUAL("primary-iso")
+ || IS_EQUAL("iso-primary") )
+ {
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_PRIMARY_ISO;
+ fNamespaces |= fPrevMajor = RTFSISOMAKER_NAMESPACE_ISO_9660;
+ }
+ else if (IS_EQUAL("joliet"))
+ {
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_JOLIET;
+ fNamespaces |= fPrevMajor = RTFSISOMAKER_NAMESPACE_JOLIET;
+ }
+ else if (IS_EQUAL("udf"))
+ {
+#if 0
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_UDF;
+ fNamespaces |= fPrevMajor = RTFSISOMAKER_NAMESPACE_UDF;
+#else
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "UDF support is currently not implemented");
+#endif
+ }
+ else if (IS_EQUAL("hfs") || IS_EQUAL("hfsplus"))
+ {
+#if 0
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_HFS;
+ fNamespaces |= fPrevMajor = RTFSISOMAKER_NAMESPACE_HFS;
+#else
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Hybrid HFS+ support is currently not implemented");
+#endif
+ }
+ /* rock ridge */
+ else if ( IS_EQUAL("rr")
+ || IS_EQUAL("rock")
+ || IS_EQUAL("rock-ridge"))
+ {
+ if (fPrevMajor == RTFSISOMAKERCMDNAME_PRIMARY_ISO)
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_PRIMARY_ISO_ROCK_RIDGE;
+ else if (fPrevMajor == RTFSISOMAKERCMDNAME_JOLIET)
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_JOLIET_ROCK_RIDGE;
+ else
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "unqualified rock-ridge name specifier");
+ }
+ else if ( IS_EQUAL("iso-rr") || IS_EQUAL("iso-rock") || IS_EQUAL("iso-rock-ridge")
+ || IS_EQUAL("primary-rr") || IS_EQUAL("primary-rock") || IS_EQUAL("primary-rock-ridge")
+ || IS_EQUAL("iso9660-rr") || IS_EQUAL("iso9660-rock") || IS_EQUAL("iso9660-rock-ridge")
+ || IS_EQUAL("iso-9660-rr") || IS_EQUAL("iso-9660-rock") || IS_EQUAL("iso-9660-rock-ridge")
+ || IS_EQUAL("primaryiso-rr") || IS_EQUAL("primaryiso-rock") || IS_EQUAL("primaryiso-rock-ridge")
+ || IS_EQUAL("primary-iso-rr") || IS_EQUAL("primary-iso-rock") || IS_EQUAL("primary-iso-rock-ridge") )
+ {
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_PRIMARY_ISO_ROCK_RIDGE;
+ if (!(fNamespaces & RTFSISOMAKERCMDNAME_PRIMARY_ISO))
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "iso-9660-rock-ridge must come after the iso-9660 name specifier");
+ }
+ else if (IS_EQUAL("joliet-rr") || IS_EQUAL("joliet-rock") || IS_EQUAL("joliet-rock-ridge"))
+ {
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_JOLIET_ROCK_RIDGE;
+ if (!(fNamespaces & RTFSISOMAKERCMDNAME_JOLIET))
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "joliet-rock-ridge must come after the joliet name specifier");
+ }
+ /* trans.tbl */
+ else if (IS_EQUAL("trans") || IS_EQUAL("trans-tbl"))
+ {
+ if (fPrevMajor == RTFSISOMAKERCMDNAME_PRIMARY_ISO)
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_PRIMARY_ISO_TRANS_TBL;
+ else if (fPrevMajor == RTFSISOMAKERCMDNAME_JOLIET)
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_JOLIET_TRANS_TBL;
+ else
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "unqualified trans-tbl name specifier");
+ }
+ else if ( IS_EQUAL("iso-trans") || IS_EQUAL("iso-trans-tbl")
+ || IS_EQUAL("primary-trans") || IS_EQUAL("primary-trans-tbl")
+ || IS_EQUAL("iso9660-trans") || IS_EQUAL("iso9660-trans-tbl")
+ || IS_EQUAL("iso-9660-trans") || IS_EQUAL("iso-9660-trans-tbl")
+ || IS_EQUAL("primaryiso-trans") || IS_EQUAL("primaryiso-trans-tbl")
+ || IS_EQUAL("primary-iso-trans") || IS_EQUAL("primary-iso-trans-tbl") )
+ {
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_PRIMARY_ISO_TRANS_TBL;
+ if (!(fNamespaces & RTFSISOMAKERCMDNAME_PRIMARY_ISO))
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "iso-9660-trans-tbl must come after the iso-9660 name specifier");
+ }
+ else if (IS_EQUAL("joliet-trans") || IS_EQUAL("joliet-trans-tbl"))
+ {
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_JOLIET_TRANS_TBL;
+ if (!(fNamespaces & RTFSISOMAKERCMDNAME_JOLIET))
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "joliet-trans-tbl must come after the joliet name specifier");
+ }
+ else if (IS_EQUAL("udf-trans") || IS_EQUAL("udf-trans-tbl"))
+ {
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_UDF_TRANS_TBL;
+ if (!(fNamespaces & RTFSISOMAKERCMDNAME_UDF))
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "udf-trans-tbl must come after the udf name specifier");
+ }
+ else if (IS_EQUAL("hfs-trans") || IS_EQUAL("hfs-trans-tbl"))
+ {
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_HFS_TRANS_TBL;
+ if (!(fNamespaces & RTFSISOMAKERCMDNAME_HFS))
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "hfs-trans-tbl must come after the hfs name specifier");
+ }
+ else
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "unknown name specifier '%.*s'", cchName, pchName);
+#undef IS_EQUAL
+ offSpec = offEndSpec;
+ }
+ } /* while same specifier */
+
+ /*
+ * Check that it wasn't empty.
+ */
+ if (fNameSpecifier == 0)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "name specifier #%u (0-based) is empty ", iNameSpecifier);
+
+ /*
+ * Complain if a major namespace name is duplicated. The rock-ridge and
+ * trans.tbl names are simple to replace, the others affect the two former
+ * names and are therefore not allowed twice in the list.
+ */
+ uint32_t i = iNameSpecifier;
+ while (i-- > 0)
+ {
+ uint32_t fRepeated = (fNameSpecifier & RTFSISOMAKERCMDNAME_MAJOR_MASK)
+ & (pOpts->afNameSpecifiers[i] & RTFSISOMAKERCMDNAME_MAJOR_MASK);
+ if (fRepeated)
+ {
+ char szTmp[128];
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "repeating name specifier%s: %s", RT_IS_POWER_OF_TWO(fRepeated) ? "" : "s",
+ rtFsIsoMakerCmdNameSpecifiersToString(fRepeated, szTmp, sizeof(szTmp)));
+ }
+ }
+
+ /*
+ * Add it.
+ */
+ if (iNameSpecifier >= RT_ELEMENTS(pOpts->afNameSpecifiers))
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "too many name specifiers (max %d)", RT_ELEMENTS(pOpts->afNameSpecifiers));
+ pOpts->afNameSpecifiers[iNameSpecifier] = fNameSpecifier;
+ iNameSpecifier++;
+
+ /*
+ * Next, if any.
+ */
+ if (pszSpec[offSpec] == ',')
+ offSpec++;
+ } while (pszSpec[offSpec] != '\0');
+
+ pOpts->cNameSpecifiers = iNameSpecifier;
+ pOpts->fDstNamespaces = fNamespaces;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Handles the --name-setup-from-import option.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ */
+static int rtFsIsoMakerCmdOptNameSetupFromImport(PRTFSISOMAKERCMDOPTS pOpts)
+{
+ /*
+ * Figure out what's on the ISO.
+ */
+ uint32_t fNamespaces = RTFsIsoMakerGetPopulatedNamespaces(pOpts->hIsoMaker);
+ AssertReturn(fNamespaces != UINT32_MAX, VERR_INVALID_HANDLE);
+ if (fNamespaces != 0)
+ {
+ if ( (fNamespaces & RTFSISOMAKER_NAMESPACE_ISO_9660)
+ && RTFsIsoMakerGetRockRidgeLevel(pOpts->hIsoMaker) > 0)
+ fNamespaces |= RTFSISOMAKERCMDNAME_PRIMARY_ISO_ROCK_RIDGE;
+
+ if ( (fNamespaces & RTFSISOMAKER_NAMESPACE_JOLIET)
+ && RTFsIsoMakerGetJolietRockRidgeLevel(pOpts->hIsoMaker) > 0)
+ fNamespaces |= RTFSISOMAKERCMDNAME_JOLIET_ROCK_RIDGE;
+
+ /*
+ * The TRANS.TBL files cannot be disabled at present and the importer
+ * doesn't check whether they are there or not, so carry them on from
+ * the previous setup.
+ */
+ uint32_t fOld = 0;
+ uint32_t i = pOpts->cNameSpecifiers;
+ while (i-- > 0)
+ fOld |= pOpts->afNameSpecifiers[0];
+ if (fNamespaces & RTFSISOMAKER_NAMESPACE_ISO_9660)
+ fNamespaces |= fOld & RTFSISOMAKERCMDNAME_PRIMARY_ISO_TRANS_TBL;
+ if (fNamespaces & RTFSISOMAKER_NAMESPACE_JOLIET)
+ fNamespaces |= fOld & RTFSISOMAKERCMDNAME_PRIMARY_ISO_TRANS_TBL;
+ if (fNamespaces & RTFSISOMAKER_NAMESPACE_UDF)
+ fNamespaces |= fOld & RTFSISOMAKERCMDNAME_UDF_TRANS_TBL;
+ if (fNamespaces & RTFSISOMAKER_NAMESPACE_HFS)
+ fNamespaces |= fOld & RTFSISOMAKERCMDNAME_HFS_TRANS_TBL;
+
+ /*
+ * Apply the new configuration.
+ */
+ pOpts->cNameSpecifiers = 1;
+ pOpts->afNameSpecifiers[0] = fNamespaces;
+ pOpts->fDstNamespaces = fNamespaces & RTFSISOMAKERCMDNAME_MAJOR_MASK;
+
+ char szTmp[128];
+ rtFsIsoMakerPrintf(pOpts, "info: --name-setup-from-import determined: --name-setup=%s\n",
+ rtFsIsoMakerCmdNameSpecifiersToString(fNamespaces, szTmp, sizeof(szTmp)));
+ return VINF_SUCCESS;
+ }
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_DRIVE_IS_EMPTY, "--name-setup-from-import used on an empty ISO");
+}
+
+
+/**
+ * Checks if we should use the source stack or the regular file system for
+ * opening a source.
+ *
+ * @returns true / false.
+ * @param pOpts The ISO maker command instance.
+ * @param pszSrc The source path under consideration.
+ */
+static bool rtFsIsoMakerCmdUseSrcStack(PRTFSISOMAKERCMDOPTS pOpts, const char *pszSrc)
+{
+ /* Not if there isn't any stack. */
+ if (pOpts->iSrcStack < 0)
+ return false;
+
+ /* Not if we've got a :iprtvfs: incantation. */
+ if (RTVfsChainIsSpec(pszSrc))
+ return false;
+
+ /* If the top entry is a CWD rather than a VFS, we only do it for root-less paths. */
+ if (pOpts->aSrcStack[pOpts->iSrcStack].pszSrcVfsOption == NULL)
+ {
+ if (RTPathStartsWithRoot(pszSrc))
+ return false;
+ }
+ return true;
+}
+
+
+/**
+ * Processes a non-option argument.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param pszSpec The specification of what to add.
+ * @param fWithSrc Whether the specification includes a source path
+ * or not.
+ * @param pParsed Where to return the parsed name specification.
+ */
+static int rtFsIsoMakerCmdParseNameSpec(PRTFSISOMAKERCMDOPTS pOpts, const char *pszSpec, bool fWithSrc,
+ PRTFSISOMKCMDPARSEDNAMES pParsed)
+{
+ const char * const pszSpecIn = pszSpec;
+ uint32_t const cMaxNames = pOpts->cNameSpecifiers + fWithSrc;
+
+ /*
+ * Split it up by '='.
+ */
+ pParsed->cNames = 0;
+ pParsed->cNamesWithSrc = 0;
+ pParsed->enmSrcType = fWithSrc ? RTFSISOMKCMDPARSEDNAMES::kSrcType_Normal : RTFSISOMKCMDPARSEDNAMES::kSrcType_None;
+ for (;;)
+ {
+ const char *pszEqual = strchr(pszSpec, '=');
+ size_t cchName = pszEqual ? pszEqual - pszSpec : strlen(pszSpec);
+ bool fNeedSlash = (pszEqual || !fWithSrc) && !RTPATH_IS_SLASH(*pszSpec) && cchName > 0;
+ if (cchName + fNeedSlash >= sizeof(pParsed->aNames[pParsed->cNamesWithSrc].szPath))
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "name #%u (0-based) is too long: %s", pParsed->cNamesWithSrc, pszSpecIn);
+ if (pParsed->cNamesWithSrc >= cMaxNames)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "too many names specified (max %u%s): %s",
+ pOpts->cNameSpecifiers, fWithSrc ? " + source" : "", pszSpecIn);
+ if (!fNeedSlash)
+ memcpy(pParsed->aNames[pParsed->cNamesWithSrc].szPath, pszSpec, cchName);
+ else
+ {
+ memcpy(&pParsed->aNames[pParsed->cNamesWithSrc].szPath[1], pszSpec, cchName);
+ pParsed->aNames[pParsed->cNamesWithSrc].szPath[0] = RTPATH_SLASH;
+ cchName++;
+ }
+ pParsed->aNames[pParsed->cNamesWithSrc].szPath[cchName] = '\0';
+ pParsed->aNames[pParsed->cNamesWithSrc].cchPath = (uint32_t)cchName;
+ pParsed->cNamesWithSrc++;
+
+ if (!pszEqual)
+ {
+ if (fWithSrc)
+ {
+ if (!cchName)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "empty source file name: %s", pszSpecIn);
+ if (cchName == 8 && strcmp(pszSpec, ":remove:") == 0)
+ pParsed->enmSrcType = RTFSISOMKCMDPARSEDNAMES::kSrcType_Remove;
+ else if (cchName == 13 && strcmp(pszSpec, ":must-remove:") == 0)
+ pParsed->enmSrcType = RTFSISOMKCMDPARSEDNAMES::kSrcType_MustRemove;
+ else if (rtFsIsoMakerCmdUseSrcStack(pOpts, pszSpec))
+ pParsed->enmSrcType = RTFSISOMKCMDPARSEDNAMES::kSrcType_NormalSrcStack;
+ }
+ break;
+ }
+ pszSpec = pszEqual + 1;
+ }
+
+ /*
+ * If there are too few names specified, move the source and repeat the
+ * last non-source name. If only source, convert source into a name spec.
+ */
+ if (pParsed->cNamesWithSrc < cMaxNames)
+ {
+ uint32_t iSrc;
+ if (!fWithSrc)
+ iSrc = pParsed->cNamesWithSrc - 1;
+ else
+ {
+ pParsed->aNames[pOpts->cNameSpecifiers] = pParsed->aNames[pParsed->cNamesWithSrc - 1];
+ iSrc = pParsed->cNamesWithSrc >= 2 ? pParsed->cNamesWithSrc - 2 : 0;
+ }
+
+ /* If the source is a input file name specifier, reduce it to something that starts with a slash. */
+ if (pParsed->cNamesWithSrc == 1 && fWithSrc)
+ {
+ const char *pszSrc = pParsed->aNames[iSrc].szPath;
+ char *pszFinalPath = NULL;
+ if (RTVfsChainIsSpec(pParsed->aNames[iSrc].szPath))
+ {
+ uint32_t offError;
+ int rc = RTVfsChainQueryFinalPath(pParsed->aNames[iSrc].szPath, &pszFinalPath, &offError);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdChainError(pOpts, "RTVfsChainQueryFinalPath",
+ pParsed->aNames[iSrc].szPath, rc, offError, NULL);
+ pszSrc = pszFinalPath;
+ }
+
+ /* Find the start of the last component, ignoring trailing slashes. */
+ size_t cchSrc = strlen(pszSrc);
+ size_t offLast = cchSrc;
+ while (offLast > 0 && RTPATH_IS_SLASH(pszSrc[offLast - 1]))
+ offLast--;
+ while (offLast > 0 && !RTPATH_IS_SLASH(pszSrc[offLast - 1]))
+ offLast--;
+
+ /* Move it up front with a leading slash. */
+ if (offLast > 0 || !RTPATH_IS_SLASH(*pszSrc))
+ {
+ pParsed->aNames[iSrc].cchPath = 1 + (uint32_t)(cchSrc - offLast);
+ if (pParsed->aNames[iSrc].cchPath >= sizeof(pParsed->aNames[iSrc].szPath))
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "name too long: %s", pszSpecIn);
+
+ memmove(&pParsed->aNames[iSrc].szPath[1], &pszSrc[offLast], pParsed->aNames[iSrc].cchPath);
+ }
+ else
+ pParsed->aNames[iSrc].cchPath = 1;
+ pParsed->aNames[iSrc].szPath[0] = RTPATH_SLASH;
+
+ if (pszFinalPath)
+ RTStrFree(pszFinalPath);
+ }
+
+ for (uint32_t iDst = iSrc + 1; iDst < pOpts->cNameSpecifiers; iDst++)
+ pParsed->aNames[iDst] = pParsed->aNames[iSrc];
+
+ pParsed->cNamesWithSrc = cMaxNames;
+ }
+ pParsed->cNames = pOpts->cNameSpecifiers;
+
+ /*
+ * Copy the specifier flags and check that the paths all starts with slashes.
+ */
+ for (uint32_t i = 0; i < pOpts->cNameSpecifiers; i++)
+ {
+ pParsed->aNames[i].fNameSpecifiers = pOpts->afNameSpecifiers[i];
+ Assert( pParsed->aNames[i].cchPath == 0
+ || RTPATH_IS_SLASH(pParsed->aNames[i].szPath[0]));
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Enteres an object into the namespace by full paths.
+ *
+ * This is used by rtFsIsoMakerCmdOptEltoritoSetBootCatalogPath and
+ * rtFsIsoMakerCmdAddFile.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param idxObj The configuration index of the object to be named.
+ * @param pParsed The parsed names.
+ * @param pszSrcOrName Source file or name.
+ */
+static int rtFsIsoMakerCmdSetObjPaths(PRTFSISOMAKERCMDOPTS pOpts, uint32_t idxObj, PCRTFSISOMKCMDPARSEDNAMES pParsed,
+ const char *pszSrcOrName)
+{
+ int rc = VINF_SUCCESS;
+ for (uint32_t i = 0; i < pParsed->cNames; i++)
+ if (pParsed->aNames[i].cchPath > 0)
+ {
+ if (pParsed->aNames[i].fNameSpecifiers & RTFSISOMAKERCMDNAME_MAJOR_MASK)
+ {
+ rc = RTFsIsoMakerObjSetPath(pOpts->hIsoMaker, idxObj,
+ pParsed->aNames[i].fNameSpecifiers & RTFSISOMAKERCMDNAME_MAJOR_MASK,
+ pParsed->aNames[i].szPath);
+ if (RT_FAILURE(rc))
+ {
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error setting name '%s' on '%s': %Rrc",
+ pParsed->aNames[i].szPath, pszSrcOrName, rc);
+ break;
+ }
+ }
+ if (pParsed->aNames[i].fNameSpecifiers & RTFSISOMAKERCMDNAME_MINOR_MASK)
+ {
+ /** @todo add APIs for this. */
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Adds a file.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param pszSrc The path to the source file.
+ * @param pParsed The parsed names.
+ * @param pidxObj Where to return the configuration index for the
+ * added file. Optional.
+ */
+static int rtFsIsoMakerCmdAddFile(PRTFSISOMAKERCMDOPTS pOpts, const char *pszSrc, PCRTFSISOMKCMDPARSEDNAMES pParsed,
+ uint32_t *pidxObj)
+{
+ int rc;
+ uint32_t idxObj = UINT32_MAX;
+ if (pParsed->enmSrcType == RTFSISOMKCMDPARSEDNAMES::kSrcType_NormalSrcStack)
+ {
+ RTVFSFILE hVfsFileSrc;
+ rc = RTVfsDirOpenFile(pOpts->aSrcStack[pOpts->iSrcStack].hSrcDir, pszSrc,
+ RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &hVfsFileSrc);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error opening '%s' (%s '%s'): %Rrc",
+ pszSrc, pOpts->aSrcStack[pOpts->iSrcStack].pszSrcVfsOption ? "inside" : "relative to",
+ pOpts->aSrcStack[pOpts->iSrcStack].pszSrcVfs, rc);
+
+ rc = RTFsIsoMakerAddUnnamedFileWithVfsFile(pOpts->hIsoMaker, hVfsFileSrc, &idxObj);
+ RTVfsFileRelease(hVfsFileSrc);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error adding '%s' (VFS): %Rrc", pszSrc, rc);
+ }
+ else
+ {
+ rc = RTFsIsoMakerAddUnnamedFileWithSrcPath(pOpts->hIsoMaker, pszSrc, &idxObj);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error adding '%s': %Rrc", pszSrc, rc);
+ }
+
+
+ pOpts->cItemsAdded++;
+ if (pidxObj)
+ *pidxObj = idxObj;
+
+ return rtFsIsoMakerCmdSetObjPaths(pOpts, idxObj, pParsed, pszSrc);
+}
+
+
+/**
+ * Applies filtering rules.
+ *
+ * @returns true if filtered out, false if included.
+ * @param pOpts The ISO maker command instance.
+ * @param pszSrc The source source.
+ * @param pszName The name part (maybe different buffer from pszSrc).
+ * @param fIsDir Set if directory, clear if not.
+ */
+static bool rtFsIsoMakerCmdIsFilteredOut(PRTFSISOMAKERCMDOPTS pOpts, const char *pszDir, const char *pszName, bool fIsDir)
+{
+ /* Ignore trans.tbl files. */
+ if ( !fIsDir
+ && RTStrICmp(pszName, pOpts->pszTransTbl) == 0)
+ return true;
+
+ RT_NOREF(pOpts, pszDir, pszName, fIsDir);
+ return false;
+}
+
+
+/**
+ * Worker for rtFsIsoMakerCmdAddVfsDir that does the recursion.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param hVfsDir The directory to process.
+ * @param idxDirObj The configuration index of the directory.
+ * @param pszSrc Pointer to the source path buffer. RTPATH_MAX
+ * in size. Okay to modify beyond @a cchSrc.
+ * @param cchSrc Length of the path corresponding to @a hVfsDir.
+ * @param fNamespaces Which ISO maker namespaces to add the names to.
+ * @param cDepth Number of recursions. Used to deal with loopy
+ * directories.
+ * @param fFilesWithSrcPath Whether to add files using @a pszSrc or to add
+ * as VFS handles (open first). For saving native
+ * file descriptors.
+ */
+static int rtFsIsoMakerCmdAddVfsDirRecursive(PRTFSISOMAKERCMDOPTS pOpts, RTVFSDIR hVfsDir, uint32_t idxDirObj,
+ char *pszSrc, size_t cchSrc, uint32_t fNamespaces, uint8_t cDepth,
+ bool fFilesWithSrcPath)
+{
+ /*
+ * Check that we're not in too deep.
+ */
+ if (cDepth >= RTFSISOMAKERCMD_MAX_DIR_RECURSIONS)
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_ISOMK_IMPORT_TOO_DEEP_DIR_TREE,
+ "Recursive (VFS) dir add too deep (depth=%u): %.*s", cDepth, cchSrc, pszSrc);
+ /*
+ * Enumerate the directory.
+ */
+ int rc;
+ size_t cbDirEntryAlloced = sizeof(RTDIRENTRYEX);
+ PRTDIRENTRYEX pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
+ if (pDirEntry)
+ {
+ for (;;)
+ {
+ /*
+ * Read the next entry.
+ */
+ size_t cbDirEntry = cbDirEntryAlloced;
+ rc = RTVfsDirReadEx(hVfsDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_UNIX);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_NO_MORE_FILES)
+ rc = VINF_SUCCESS;
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ RTMemTmpFree(pDirEntry);
+ cbDirEntryAlloced = RT_ALIGN_Z(RT_MIN(cbDirEntry, cbDirEntryAlloced) + 64, 64);
+ pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
+ if (pDirEntry)
+ continue;
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_NO_TMP_MEMORY, "Out of memory (direntry buffer)");
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTVfsDirReadEx failed on %.*s: %Rrc", cchSrc, pszSrc, rc);
+ break;
+ }
+
+ /* Ignore '.' and '..' entries. */
+ if (RTDirEntryExIsStdDotLink(pDirEntry))
+ continue;
+
+ /*
+ * Process the entry.
+ */
+
+ /* Update the name. */
+ if (cchSrc + 1 + pDirEntry->cbName < RTPATH_MAX)
+ {
+ pszSrc[cchSrc] = '/'; /* VFS only groks unix slashes */
+ memcpy(&pszSrc[cchSrc + 1], pDirEntry->szName, pDirEntry->cbName);
+ pszSrc[cchSrc + 1 + pDirEntry->cbName] = '\0';
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_FILENAME_TOO_LONG, "Filename is too long (depth %u): '%.*s/%s'",
+ cDepth, cchSrc, pszSrc, pDirEntry->szName);
+
+ /* Okay? Check name filtering. */
+ if ( RT_SUCCESS(rc)
+ && !rtFsIsoMakerCmdIsFilteredOut(pOpts, pszSrc, pDirEntry->szName, RTFS_IS_DIRECTORY(pDirEntry->Info.Attr.fMode)))
+ {
+ /* Do type specific adding. */
+ uint32_t idxObj = UINT32_MAX;
+ if (RTFS_IS_FILE(pDirEntry->Info.Attr.fMode))
+ {
+ /*
+ * Files are either added with VFS handles or paths to the sources,
+ * depending on what's considered more efficient. We prefer the latter
+ * if hVfsDir maps to native handle and not a virtual one.
+ */
+ if (!fFilesWithSrcPath)
+ {
+ RTVFSFILE hVfsFileSrc;
+ rc = RTVfsDirOpenFile(hVfsDir, pDirEntry->szName,
+ RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &hVfsFileSrc);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFsIsoMakerAddUnnamedFileWithVfsFile(pOpts->hIsoMaker, hVfsFileSrc, &idxObj);
+ RTVfsFileRelease(hVfsFileSrc);
+ if (RT_FAILURE(rc))
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error adding file '%s' (VFS recursive, handle): %Rrc",
+ pszSrc, rc);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error opening file '%s' (VFS recursive): %Rrc", pszSrc, rc);
+ }
+ else
+ {
+ /* Add file with source path: */
+ rc = RTFsIsoMakerAddUnnamedFileWithSrcPath(pOpts->hIsoMaker, pszSrc, &idxObj);
+ if (RT_FAILURE(rc))
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error adding file '%s' (VFS recursive, path): %Rrc",
+ pszSrc, rc);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pOpts->cItemsAdded++;
+ rc = RTFsIsoMakerObjSetNameAndParent(pOpts->hIsoMaker, idxObj, idxDirObj, fNamespaces,
+ pDirEntry->szName, false /*fNoNormalize*/);
+ if (RT_FAILURE(rc))
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error setting parent & name on file '%s' to '%s': %Rrc",
+ pszSrc, pDirEntry->szName, rc);
+ }
+ }
+ else if (RTFS_IS_DIRECTORY(pDirEntry->Info.Attr.fMode))
+ {
+ /*
+ * Open and add the sub-directory.
+ */
+ RTVFSDIR hVfsSubDirSrc;
+ rc = RTVfsDirOpenDir(hVfsDir, pDirEntry->szName, 0 /*fFlags*/, &hVfsSubDirSrc);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFsIsoMakerAddUnnamedDir(pOpts->hIsoMaker, &pDirEntry->Info, &idxObj);
+ if (RT_SUCCESS(rc))
+ {
+ pOpts->cItemsAdded++;
+ rc = RTFsIsoMakerObjSetNameAndParent(pOpts->hIsoMaker, idxObj, idxDirObj, fNamespaces,
+ pDirEntry->szName, false /*fNoNormalize*/);
+ if (RT_SUCCESS(rc))
+ /* Recurse into the sub-directory. */
+ rc = rtFsIsoMakerCmdAddVfsDirRecursive(pOpts, hVfsSubDirSrc, idxObj, pszSrc,
+ cchSrc + 1 + pDirEntry->cbName, fNamespaces, cDepth + 1,
+ fFilesWithSrcPath);
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc,
+ "Error setting parent & name on directory '%s' to '%s': %Rrc",
+ pszSrc, pDirEntry->szName, rc);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error adding directory '%s' (VFS recursive): %Rrc", pszSrc, rc);
+ RTVfsDirRelease(hVfsSubDirSrc);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error opening directory '%s' (VFS recursive): %Rrc", pszSrc, rc);
+ }
+ else if (RTFS_IS_SYMLINK(pDirEntry->Info.Attr.fMode))
+ {
+ /*
+ * TODO: ISO FS symlink support.
+ */
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_NOT_IMPLEMENTED,
+ "Adding symlink '%s' failed: not yet implemented", pszSrc);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_NOT_IMPLEMENTED,
+ "Adding special file '%s' failed: not implemented", pszSrc);
+ }
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ RTMemTmpFree(pDirEntry);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_NO_TMP_MEMORY, "Out of memory! (direntry buffer)");
+ return rc;
+}
+
+
+/**
+ * Common directory adding worker.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param hVfsSrcDir The directory being added.
+ * @param pszSrc The source directory name.
+ * @param pParsed The parsed names.
+ * @param fFilesWithSrcPath Whether to add files using @a pszSrc
+ * or to add as VFS handles (open first). For
+ * saving native file descriptors.
+ * @param pidxObj Where to return the configuration index for the
+ * added file. Optional.
+ */
+static int rtFsIsoMakerCmdAddVfsDirCommon(PRTFSISOMAKERCMDOPTS pOpts, RTVFSDIR hVfsDirSrc, char *pszSrc,
+ PCRTFSISOMKCMDPARSEDNAMES pParsed, bool fFilesWithSrcPath, PCRTFSOBJINFO pObjInfo)
+{
+ /*
+ * Add the directory if it doesn't exist.
+ */
+ uint32_t idxObj = UINT32_MAX;
+ for (uint32_t i = 0; i < pParsed->cNames; i++)
+ if (pParsed->aNames[i].fNameSpecifiers & RTFSISOMAKERCMDNAME_MAJOR_MASK)
+ {
+ idxObj = RTFsIsoMakerGetObjIdxForPath(pOpts->hIsoMaker,
+ pParsed->aNames[i].fNameSpecifiers & RTFSISOMAKERCMDNAME_MAJOR_MASK,
+ pParsed->aNames[i].szPath);
+ if (idxObj != UINT32_MAX)
+ {
+ /** @todo make sure the directory is present in the other namespace. */
+ break;
+ }
+ }
+ int rc = VINF_SUCCESS;
+ if (idxObj == UINT32_MAX)
+ {
+ rc = RTFsIsoMakerAddUnnamedDir(pOpts->hIsoMaker, pObjInfo, &idxObj);
+ if (RT_SUCCESS(rc))
+ rc = rtFsIsoMakerCmdSetObjPaths(pOpts, idxObj, pParsed, pParsed->aNames[pParsed->cNames - 1].szPath);
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTFsIsoMakerAddUnnamedDir failed: %Rrc", rc);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Add the directory content.
+ */
+ uint32_t fNamespaces = 0;
+ for (uint32_t i = 0; i < pParsed->cNames; i++)
+ fNamespaces |= pParsed->aNames[i].fNameSpecifiers & RTFSISOMAKERCMDNAME_MAJOR_MASK;
+ rc = rtFsIsoMakerCmdAddVfsDirRecursive(pOpts, hVfsDirSrc, idxObj, pszSrc,
+ pParsed->aNames[pParsed->cNamesWithSrc - 1].cchPath, fNamespaces, 0 /*cDepth*/,
+ fFilesWithSrcPath);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Adds a directory, from the source VFS.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param pParsed The parsed names.
+ * @param pidxObj Where to return the configuration index for the
+ * added file. Optional.
+ */
+static int rtFsIsoMakerCmdAddVfsDir(PRTFSISOMAKERCMDOPTS pOpts, PCRTFSISOMKCMDPARSEDNAMES pParsed, PCRTFSOBJINFO pObjInfo)
+{
+ Assert(pParsed->cNames < pParsed->cNamesWithSrc);
+ char *pszSrc = pParsed->aNames[pParsed->cNamesWithSrc - 1].szPath;
+ RTPathChangeToUnixSlashes(pszSrc, true /*fForce*/); /* VFS currently only understand unix slashes. */
+ RTVFSDIR hVfsDirSrc;
+ int rc = RTVfsDirOpenDir(pOpts->aSrcStack[pOpts->iSrcStack].hSrcDir, pszSrc, 0 /*fFlags*/, &hVfsDirSrc);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsIsoMakerCmdAddVfsDirCommon(pOpts, hVfsDirSrc, pszSrc, pParsed, false /*fFilesWithSrcPath*/, pObjInfo);
+ RTVfsDirRelease(hVfsDirSrc);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error opening directory '%s' (%s '%s'): %Rrc", pszSrc,
+ pOpts->aSrcStack[pOpts->iSrcStack].pszSrcVfsOption ? "inside" : "relative to",
+ pOpts->aSrcStack[pOpts->iSrcStack].pszSrcVfs, rc);
+ return rc;
+}
+
+
+/**
+ * Adds a directory, from a VFS chain or real file system.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param pszSrc The path to the source directory.
+ * @param pParsed The parsed names.
+ */
+static int rtFsIsoMakerCmdAddDir(PRTFSISOMAKERCMDOPTS pOpts, PCRTFSISOMKCMDPARSEDNAMES pParsed, PCRTFSOBJINFO pObjInfo)
+{
+ Assert(pParsed->cNames < pParsed->cNamesWithSrc);
+ char *pszSrc = pParsed->aNames[pParsed->cNamesWithSrc - 1].szPath;
+ RTERRINFOSTATIC ErrInfo;
+ uint32_t offError;
+ RTVFSDIR hVfsDirSrc;
+ int rc = RTVfsChainOpenDir(pszSrc, 0 /*fOpen*/, &hVfsDirSrc, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsIsoMakerCmdAddVfsDirCommon(pOpts, hVfsDirSrc, pszSrc, pParsed,
+ RTVfsDirIsStdDir(hVfsDirSrc) /*fFilesWithSrcPath*/, pObjInfo);
+ RTVfsDirRelease(hVfsDirSrc);
+ }
+ else
+ rc = rtFsIsoMakerCmdChainError(pOpts, "RTVfsChainOpenDir", pszSrc, rc, offError, &ErrInfo.Core);
+ return rc;
+}
+
+
+/**
+ * Adds a file after first making sure it's a file.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param pszSrc The path to the source file.
+ * @param pParsed The parsed names.
+ * @param pidxObj Where to return the configuration index for the
+ * added file. Optional.
+ */
+static int rtFsIsoMakerCmdStatAndAddFile(PRTFSISOMAKERCMDOPTS pOpts, const char *pszSrc, PCRTFSISOMKCMDPARSEDNAMES pParsed,
+ uint32_t *pidxObj)
+{
+ int rc;
+ RTFSOBJINFO ObjInfo;
+ if (pParsed->enmSrcType == RTFSISOMKCMDPARSEDNAMES::kSrcType_NormalSrcStack)
+ {
+ rc = RTVfsDirQueryPathInfo(pOpts->aSrcStack[pOpts->iSrcStack].hSrcDir, pszSrc,
+ &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_FOLLOW_LINK);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTVfsQueryPathInfo failed on %s (%s %s): %Rrc", pszSrc,
+ pOpts->aSrcStack[pOpts->iSrcStack].pszSrcVfsOption ? "inside" : "relative to",
+ pOpts->aSrcStack[pOpts->iSrcStack].pszSrcVfs, rc);
+ }
+ else
+ {
+ uint32_t offError;
+ RTERRINFOSTATIC ErrInfo;
+ rc = RTVfsChainQueryInfo(pszSrc, &ObjInfo, RTFSOBJATTRADD_UNIX,
+ RTPATH_F_FOLLOW_LINK, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdChainError(pOpts, "RTVfsChainQueryInfo", pszSrc, rc, offError, &ErrInfo.Core);
+ }
+
+ if (RTFS_IS_FILE(ObjInfo.Attr.fMode))
+ return rtFsIsoMakerCmdAddFile(pOpts, pszSrc, pParsed, pidxObj);
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_NOT_A_FILE, "Not a file: %s", pszSrc);
+}
+
+
+/**
+ * Processes a non-option argument.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param pszSpec The specification of what to add.
+ */
+static int rtFsIsoMakerCmdAddSomething(PRTFSISOMAKERCMDOPTS pOpts, const char *pszSpec)
+{
+ /*
+ * Parse the name spec.
+ */
+ RTFSISOMKCMDPARSEDNAMES Parsed;
+ int rc = rtFsIsoMakerCmdParseNameSpec(pOpts, pszSpec, true /*fWithSrc*/, &Parsed);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Deal with special source filenames used to remove/change stuff.
+ */
+ if ( Parsed.enmSrcType == RTFSISOMKCMDPARSEDNAMES::kSrcType_Remove
+ || Parsed.enmSrcType == RTFSISOMKCMDPARSEDNAMES::kSrcType_MustRemove)
+ {
+ const char *pszFirstNm = NULL;
+ uint32_t cRemoved = 0;
+ for (uint32_t i = 0; i < pOpts->cNameSpecifiers; i++)
+ if ( Parsed.aNames[i].cchPath > 0
+ && (Parsed.aNames[i].fNameSpecifiers & RTFSISOMAKERCMDNAME_MAJOR_MASK))
+ {
+ /* Make sure we remove all objects by this name. */
+ pszFirstNm = Parsed.aNames[i].szPath;
+ for (;;)
+ {
+ uint32_t idxObj = RTFsIsoMakerGetObjIdxForPath(pOpts->hIsoMaker,
+ Parsed.aNames[i].fNameSpecifiers & RTFSISOMAKERCMDNAME_MAJOR_MASK,
+ Parsed.aNames[i].szPath);
+ if (idxObj == UINT32_MAX)
+ break;
+ rc = RTFsIsoMakerObjRemove(pOpts->hIsoMaker, idxObj);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to remove '%s': %Rrc", pszSpec, rc);
+ cRemoved++;
+ }
+ }
+ if ( Parsed.enmSrcType == RTFSISOMKCMDPARSEDNAMES::kSrcType_MustRemove
+ && cRemoved == 0)
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_NOT_FOUND, "Failed to locate '%s' for removal", pszSpec);
+ }
+ /*
+ * Add regular source.
+ */
+ else
+ {
+ const char *pszSrc = Parsed.aNames[Parsed.cNamesWithSrc - 1].szPath;
+ RTFSOBJINFO ObjInfo;
+ if (Parsed.enmSrcType == RTFSISOMKCMDPARSEDNAMES::kSrcType_NormalSrcStack)
+ {
+ rc = RTVfsDirQueryPathInfo(pOpts->aSrcStack[pOpts->iSrcStack].hSrcDir, pszSrc,
+ &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_FOLLOW_LINK);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTVfsQueryPathInfo failed on %s (%s %s): %Rrc", pszSrc,
+ pOpts->aSrcStack[pOpts->iSrcStack].pszSrcVfsOption ? "inside" : "relative to",
+ pOpts->aSrcStack[pOpts->iSrcStack].pszSrcVfs, rc);
+ }
+ else
+ {
+ uint32_t offError;
+ RTERRINFOSTATIC ErrInfo;
+ rc = RTVfsChainQueryInfo(pszSrc, &ObjInfo, RTFSOBJATTRADD_UNIX,
+ RTPATH_F_FOLLOW_LINK, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdChainError(pOpts, "RTVfsChainQueryInfo", pszSrc, rc, offError, &ErrInfo.Core);
+ }
+
+ /* By type: */
+
+ if (RTFS_IS_FILE(ObjInfo.Attr.fMode))
+ return rtFsIsoMakerCmdAddFile(pOpts, pszSrc, &Parsed, NULL /*pidxObj*/);
+
+ if (RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
+ {
+ if (Parsed.enmSrcType == RTFSISOMKCMDPARSEDNAMES::kSrcType_NormalSrcStack)
+ return rtFsIsoMakerCmdAddVfsDir(pOpts, &Parsed, &ObjInfo);
+ return rtFsIsoMakerCmdAddDir(pOpts, &Parsed, &ObjInfo);
+ }
+
+ if (RTFS_IS_SYMLINK(ObjInfo.Attr.fMode))
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_NOT_IMPLEMENTED, "Adding symlink '%s' failed: not yet implemented", pszSpec);
+
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_NOT_IMPLEMENTED, "Adding special file '%s' failed: not implemented", pszSpec);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Opens an ISO and use it for subsequent file system accesses.
+ *
+ * This is handy for duplicating a part of an ISO in the new image.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param pszIsoSpec The ISO path specifier.
+ * @param pszOption The option we're being called on.
+ * @param fFlags RTFSISO9660_F_XXX
+ */
+static int rtFsIsoMakerCmdOptPushIso(PRTFSISOMAKERCMDOPTS pOpts, const char *pszIsoSpec, const char *pszOption, uint32_t fFlags)
+{
+ int32_t iSrcStack = pOpts->iSrcStack + 1;
+ if ((uint32_t)iSrcStack >= RT_ELEMENTS(pOpts->aSrcStack))
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_NOT_IMPLEMENTED,
+ "Too many pushes %s %s (previous: %s %s, %s %s, %s %s, ...)",
+ pszOption, pszIsoSpec,
+ pOpts->aSrcStack[iSrcStack - 1].pszSrcVfsOption, pOpts->aSrcStack[iSrcStack - 1].pszSrcVfs,
+ pOpts->aSrcStack[iSrcStack - 2].pszSrcVfsOption, pOpts->aSrcStack[iSrcStack - 2].pszSrcVfs,
+ pOpts->aSrcStack[iSrcStack - 3].pszSrcVfsOption, pOpts->aSrcStack[iSrcStack - 3].pszSrcVfs);
+
+ /*
+ * Try open the file.
+ */
+ int rc;
+ RTVFSFILE hVfsFileIso = NIL_RTVFSFILE;
+ RTERRINFOSTATIC ErrInfo;
+ if (rtFsIsoMakerCmdUseSrcStack(pOpts, pszIsoSpec))
+ {
+ rc = RTVfsDirOpenFile(pOpts->aSrcStack[iSrcStack - 1].hSrcDir, pszIsoSpec,
+ RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &hVfsFileIso);
+ if (RT_FAILURE(rc))
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error opening '%s' relative to '%s'",
+ pszIsoSpec, pOpts->aSrcStack[iSrcStack - 1].pszSrcVfs);
+ }
+ else
+ {
+ uint32_t offError;
+ rc = RTVfsChainOpenFile(pszIsoSpec, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE,
+ &hVfsFileIso, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ rc = rtFsIsoMakerCmdChainError(pOpts, "RTVfsChainOpenFile", pszIsoSpec, rc, offError, &ErrInfo.Core);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ RTVFS hSrcVfs;
+ rc = RTFsIso9660VolOpen(hVfsFileIso, fFlags, &hSrcVfs, RTErrInfoInitStatic(&ErrInfo));
+ RTVfsFileRelease(hVfsFileIso);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSDIR hVfsSrcRootDir;
+ rc = RTVfsOpenRoot(hSrcVfs, &hVfsSrcRootDir);
+ if (RT_SUCCESS(rc))
+ {
+ pOpts->aSrcStack[iSrcStack].hSrcDir = hVfsSrcRootDir;
+ pOpts->aSrcStack[iSrcStack].hSrcVfs = hSrcVfs;
+ pOpts->aSrcStack[iSrcStack].pszSrcVfs = pszIsoSpec;
+ pOpts->aSrcStack[iSrcStack].pszSrcVfsOption = pszOption;
+ pOpts->iSrcStack = iSrcStack;
+ return VINF_SUCCESS;
+ }
+ RTVfsRelease(hSrcVfs);
+ }
+ else if (RTErrInfoIsSet(&ErrInfo.Core))
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to open '%s' as ISO FS: %Rrc - %s",
+ pszIsoSpec, rc, ErrInfo.Core.pszMsg);
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to open '%s' as ISO FS: %Rrc", pszIsoSpec, rc);
+ }
+ return rc;
+}
+
+
+/**
+ * Counter part to --push-iso and friends.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ */
+static int rtFsIsoMakerCmdOptPop(PRTFSISOMAKERCMDOPTS pOpts)
+{
+ int32_t const iSrcStack = pOpts->iSrcStack;
+ if ( iSrcStack >= 0
+ && pOpts->aSrcStack[iSrcStack].pszSrcVfsOption)
+ {
+ RTVfsDirRelease(pOpts->aSrcStack[iSrcStack].hSrcDir);
+ RTVfsRelease(pOpts->aSrcStack[iSrcStack].hSrcVfs);
+ pOpts->aSrcStack[iSrcStack].hSrcDir = NIL_RTVFSDIR;
+ pOpts->aSrcStack[iSrcStack].hSrcVfs = NIL_RTVFS;
+ pOpts->aSrcStack[iSrcStack].pszSrcVfs = NULL;
+ pOpts->aSrcStack[iSrcStack].pszSrcVfsOption = NULL;
+ pOpts->iSrcStack = iSrcStack - 1;
+ return VINF_SUCCESS;
+ }
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_NOT_FOUND, "--pop without --push-xxx");
+}
+
+
+/**
+ * Deals with the --import-iso {iso-file-spec} options.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param pszIsoSpec The ISO path specifier.
+ */
+static int rtFsIsoMakerCmdOptImportIso(PRTFSISOMAKERCMDOPTS pOpts, const char *pszIsoSpec)
+{
+ /*
+ * Open the input file.
+ */
+ RTERRINFOSTATIC ErrInfo;
+ RTVFSFILE hIsoFile;
+ int rc;
+ if (rtFsIsoMakerCmdUseSrcStack(pOpts, pszIsoSpec))
+ {
+ rc = RTVfsDirOpenFile(pOpts->aSrcStack[pOpts->iSrcStack].hSrcDir, pszIsoSpec,
+ RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &hIsoFile);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to open '%s' %s %s for importing: %Rrc", pszIsoSpec,
+ pOpts->aSrcStack[pOpts->iSrcStack].pszSrcVfsOption ? "inside" : "relative to",
+ pOpts->aSrcStack[pOpts->iSrcStack].pszSrcVfs, rc);
+ }
+ else
+ {
+ uint32_t offError;
+ rc = RTVfsChainOpenFile(pszIsoSpec, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE,
+ &hIsoFile, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdChainError(pOpts, "RTVfsChainOpenFile", pszIsoSpec, rc, offError, &ErrInfo.Core);
+ }
+
+ RTFSISOMAKERIMPORTRESULTS Results;
+ rc = RTFsIsoMakerImport(pOpts->hIsoMaker, hIsoFile, 0 /*fFlags*/, &Results, RTErrInfoInitStatic(&ErrInfo));
+
+ RTVfsFileRelease(hIsoFile);
+
+ pOpts->cItemsAdded += Results.cAddedFiles;
+ pOpts->cItemsAdded += Results.cAddedSymlinks;
+ pOpts->cItemsAdded += Results.cAddedDirs;
+ pOpts->cItemsAdded += Results.cBootCatEntries != UINT32_MAX ? Results.cBootCatEntries : 0;
+ pOpts->cItemsAdded += Results.cbSysArea != 0 ? 1 : 0;
+
+ rtFsIsoMakerPrintf(pOpts, "ISO imported statistics for '%s'\n", pszIsoSpec);
+ rtFsIsoMakerPrintf(pOpts, " cAddedNames: %'14RU32\n", Results.cAddedNames);
+ rtFsIsoMakerPrintf(pOpts, " cAddedDirs: %'14RU32\n", Results.cAddedDirs);
+ rtFsIsoMakerPrintf(pOpts, " cbAddedDataBlocks: %'14RU64 bytes\n", Results.cbAddedDataBlocks);
+ rtFsIsoMakerPrintf(pOpts, " cAddedFiles: %'14RU32\n", Results.cAddedFiles);
+ rtFsIsoMakerPrintf(pOpts, " cAddedSymlinks: %'14RU32\n", Results.cAddedSymlinks);
+ if (Results.cBootCatEntries == UINT32_MAX)
+ rtFsIsoMakerPrintf(pOpts, " cBootCatEntries: none\n");
+ else
+ rtFsIsoMakerPrintf(pOpts, " cBootCatEntries: %'14RU32\n", Results.cBootCatEntries);
+ rtFsIsoMakerPrintf(pOpts, " cbSysArea: %'14RU32\n", Results.cbSysArea);
+ rtFsIsoMakerPrintf(pOpts, " cErrors: %'14RU32\n", Results.cErrors);
+
+ if (RT_SUCCESS(rc))
+ return rc;
+ if (RTErrInfoIsSet(&ErrInfo.Core))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTFsIsoMakerImport failed: %Rrc - %s", rc, ErrInfo.Core.pszMsg);
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTFsIsoMakerImport failed: %Rrc", rc);
+}
+
+
+/**
+ * Deals with: --iso-level, -l
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param uLevel The new ISO level.
+ */
+static int rtFsIsoMakerCmdOptSetIsoLevel(PRTFSISOMAKERCMDOPTS pOpts, uint8_t uLevel)
+{
+ int rc = RTFsIsoMakerSetIso9660Level(pOpts->hIsoMaker, uLevel);
+ if (RT_SUCCESS(rc))
+ return rc;
+ if (rc == VERR_WRONG_ORDER)
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Cannot change ISO level to %d after having added files!", uLevel);
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to set ISO level to %d: %Rrc", uLevel, rc);
+}
+
+
+/**
+ * Deals with: --rock-ridge, --limited-rock-ridge, --no-rock-ridge
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param uLevel The new rock ridge level.
+ */
+static int rtFsIsoMakerCmdOptSetPrimaryRockLevel(PRTFSISOMAKERCMDOPTS pOpts, uint8_t uLevel)
+{
+ int rc = RTFsIsoMakerSetRockRidgeLevel(pOpts->hIsoMaker, uLevel);
+ if (RT_SUCCESS(rc))
+ return rc;
+ if (rc == VERR_WRONG_ORDER)
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Cannot change rock ridge level to %d after having added files!", uLevel);
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to set rock ridge level to %d: %Rrc", uLevel, rc);
+}
+
+
+/**
+ * Deals with: --joliet, --no-joliet, --joliet-ucs-level, --ucs-level
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param uLevel The new rock ridge level.
+ */
+static int rtFsIsoMakerCmdOptSetJolietUcs2Level(PRTFSISOMAKERCMDOPTS pOpts, uint8_t uLevel)
+{
+ int rc = RTFsIsoMakerSetJolietUcs2Level(pOpts->hIsoMaker, uLevel);
+ if (RT_SUCCESS(rc))
+ return rc;
+ if (rc == VERR_WRONG_ORDER)
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Cannot change joliet UCS level to %d after having added files!", uLevel);
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to set joliet UCS level to %d: %Rrc", uLevel, rc);
+}
+
+
+/**
+ * Deals with: --rational-attribs, --strict-attribs, -R, -r
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param uLevel The new rock ridge level.
+ */
+static int rtFsIsoMakerCmdOptSetAttribInheritStyle(PRTFSISOMAKERCMDOPTS pOpts, bool fStrict)
+{
+ int rc = RTFsIsoMakerSetAttribInheritStyle(pOpts->hIsoMaker, fStrict);
+ if (RT_SUCCESS(rc))
+ return rc;
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to change attributes handling style to %s: %Rrc",
+ fStrict ? "strict" : "rational", rc);
+}
+
+
+/**
+ * Deals with: -G|--generic-boot {file}
+ *
+ * This concers content the first 16 sectors of the image. We start loading the
+ * file at byte 0 in the image and stops at 32KB.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param pszGenericBootImage The generic boot image source.
+ */
+static int rtFsIsoMakerCmdOptGenericBoot(PRTFSISOMAKERCMDOPTS pOpts, const char *pszGenericBootImage)
+{
+ RTERRINFOSTATIC ErrInfo;
+ uint32_t offError;
+ RTVFSFILE hVfsFile;
+ int rc = RTVfsChainOpenFile(pszGenericBootImage, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &hVfsFile,
+ &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdChainError(pOpts, "RTVfsChainOpenFile", pszGenericBootImage, rc, offError, &ErrInfo.Core);
+
+ uint8_t abBuf[_32K];
+ size_t cbRead;
+ rc = RTVfsFileReadAt(hVfsFile, 0, abBuf, sizeof(abBuf), &cbRead);
+ RTVfsFileRelease(hVfsFile);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error reading 32KB from generic boot image '%s': %Rrc", pszGenericBootImage, rc);
+
+ rc = RTFsIsoMakerSetSysAreaContent(pOpts->hIsoMaker, abBuf, cbRead, 0);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTFsIsoMakerSetSysAreaContent failed with a %zu bytes input: %Rrc", cbRead, rc);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Helper that makes sure we've got a validation boot entry.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ */
+static void rtFsIsoMakerCmdOptEltoritoEnsureValidationEntry(PRTFSISOMAKERCMDOPTS pOpts)
+{
+ if (pOpts->cBootCatEntries == 0)
+ {
+ pOpts->aBootCatEntries[0].enmType = RTFSISOMKCMDELTORITOENTRY::kEntryType_Validation;
+ pOpts->aBootCatEntries[0].u.Validation.idPlatform = ISO9660_ELTORITO_PLATFORM_ID_X86;
+ pOpts->aBootCatEntries[0].u.Validation.pszString = NULL;
+ pOpts->cBootCatEntries = 1;
+ }
+}
+
+
+/**
+ * Helper that makes sure we've got a current boot entry.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param fForceNew Whether to force a new entry.
+ * @param pidxBootCat Where to return the boot catalog index.
+ */
+static int rtFsIsoMakerCmdOptEltoritoEnsureSectionEntry(PRTFSISOMAKERCMDOPTS pOpts, bool fForceNew, uint32_t *pidxBootCat)
+{
+ rtFsIsoMakerCmdOptEltoritoEnsureValidationEntry(pOpts);
+
+ uint32_t i = pOpts->cBootCatEntries;
+ if (i == 2 && fForceNew)
+ {
+ pOpts->aBootCatEntries[i].enmType = RTFSISOMKCMDELTORITOENTRY::kEntryType_SectionHeader;
+ pOpts->aBootCatEntries[i].u.SectionHeader.idPlatform = pOpts->aBootCatEntries[0].u.Validation.idPlatform;
+ pOpts->aBootCatEntries[i].u.SectionHeader.pszString = NULL;
+ pOpts->cBootCatEntries = ++i;
+ }
+
+ if ( i == 1
+ || fForceNew
+ || pOpts->aBootCatEntries[i - 1].enmType == RTFSISOMKCMDELTORITOENTRY::kEntryType_SectionHeader)
+ {
+ if (i >= RT_ELEMENTS(pOpts->aBootCatEntries))
+ {
+ *pidxBootCat = UINT32_MAX;
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_BUFFER_OVERFLOW, "Too many boot catalog entries");
+ }
+
+ pOpts->aBootCatEntries[i].enmType = i == 1 ? RTFSISOMKCMDELTORITOENTRY::kEntryType_Default
+ : RTFSISOMKCMDELTORITOENTRY::kEntryType_Section;
+ pOpts->aBootCatEntries[i].u.Section.pszImageNameInIso = NULL;
+ pOpts->aBootCatEntries[i].u.Section.idxImageObj = UINT32_MAX;
+ pOpts->aBootCatEntries[i].u.Section.fInsertBootInfoTable = false;
+ pOpts->aBootCatEntries[i].u.Section.fBootable = true;
+ pOpts->aBootCatEntries[i].u.Section.bBootMediaType = ISO9660_ELTORITO_BOOT_MEDIA_TYPE_MASK;
+ pOpts->aBootCatEntries[i].u.Section.bSystemType = 1 /*FAT12*/;
+ pOpts->aBootCatEntries[i].u.Section.uLoadSeg = 0x7c0;
+ pOpts->aBootCatEntries[i].u.Section.cSectorsToLoad = 4;
+ pOpts->cBootCatEntries = ++i;
+ }
+
+ *pidxBootCat = i - 1;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Deals with: --boot-catalog <path-spec>
+ *
+ * This enters the boot catalog into the namespaces of the image. The path-spec
+ * is similar to what rtFsIsoMakerCmdAddSomething processes, only there isn't a
+ * source file part.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param pszGenericBootImage The generic boot image source.
+ */
+static int rtFsIsoMakerCmdOptEltoritoSetBootCatalogPath(PRTFSISOMAKERCMDOPTS pOpts, const char *pszBootCat)
+{
+ /* Make sure we'll fail later if no other boot options are present. */
+ rtFsIsoMakerCmdOptEltoritoEnsureValidationEntry(pOpts);
+
+ /* Parse the name spec. */
+ RTFSISOMKCMDPARSEDNAMES Parsed;
+ int rc = rtFsIsoMakerCmdParseNameSpec(pOpts, pszBootCat, false /*fWithSrc*/, &Parsed);
+ if (RT_SUCCESS(rc))
+ {
+ /* Query/create the boot catalog and enter it into the name spaces. */
+ uint32_t idxBootCatObj;
+ rc = RTFsIsoMakerQueryObjIdxForBootCatalog(pOpts->hIsoMaker, &idxBootCatObj);
+ if (RT_SUCCESS(rc))
+ rc = rtFsIsoMakerCmdSetObjPaths(pOpts, idxBootCatObj, &Parsed, "boot catalog");
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTFsIsoMakerQueryBootCatalogPathObjIdx failed: %Rrc", rc);
+ }
+ return rc;
+}
+
+
+/**
+ * Deals with: --eltorito-add-image {file-spec}
+ *
+ * This differs from -b|--eltorito-boot in that it takes a source file
+ * specification identical to what rtFsIsoMakerCmdAddSomething processes instead
+ * of a reference to a file in the image.
+ *
+ * This operates on the current eltorito boot catalog entry.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param pszGenericBootImage The generic boot image source.
+ */
+static int rtFsIsoMakerCmdOptEltoritoAddImage(PRTFSISOMAKERCMDOPTS pOpts, const char *pszBootImageSpec)
+{
+ /* Parse the name spec. */
+ RTFSISOMKCMDPARSEDNAMES Parsed;
+ int rc = rtFsIsoMakerCmdParseNameSpec(pOpts, pszBootImageSpec, true /*fWithSrc*/, &Parsed);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t idxBootCat;
+ rc = rtFsIsoMakerCmdOptEltoritoEnsureSectionEntry(pOpts, false /*fForceNew*/, &idxBootCat);
+ if (RT_SUCCESS(rc))
+ {
+ if ( pOpts->aBootCatEntries[idxBootCat].u.Section.idxImageObj != UINT32_MAX
+ || pOpts->aBootCatEntries[idxBootCat].u.Section.pszImageNameInIso != NULL)
+ rc = rtFsIsoMakerCmdSyntaxError(pOpts, "boot image already given for current El Torito entry (%#u)", idxBootCat);
+ else
+ {
+ uint32_t idxImageObj;
+ rc = rtFsIsoMakerCmdStatAndAddFile(pOpts, Parsed.aNames[Parsed.cNamesWithSrc - 1].szPath, &Parsed, &idxImageObj);
+ if (RT_SUCCESS(rc))
+ pOpts->aBootCatEntries[idxBootCat].u.Section.idxImageObj = idxImageObj;
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Deals with: -b|--eltorito-boot {file-in-iso}
+ *
+ * This operates on the current eltorito boot catalog entry.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param pszGenericBootImage The generic boot image source.
+ */
+static int rtFsIsoMakerCmdOptEltoritoBoot(PRTFSISOMAKERCMDOPTS pOpts, const char *pszBootImage)
+{
+ uint32_t idxBootCat;
+ int rc = rtFsIsoMakerCmdOptEltoritoEnsureSectionEntry(pOpts, false /*fForceNew*/, &idxBootCat);
+ if (RT_SUCCESS(rc))
+ {
+ if ( pOpts->aBootCatEntries[idxBootCat].u.Section.idxImageObj != UINT32_MAX
+ || pOpts->aBootCatEntries[idxBootCat].u.Section.pszImageNameInIso != NULL)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "boot image already given for current El Torito entry (%#u)", idxBootCat);
+
+ uint32_t idxImageObj = RTFsIsoMakerGetObjIdxForPath(pOpts->hIsoMaker, RTFSISOMAKER_NAMESPACE_ALL, pszBootImage);
+ if (idxImageObj == UINT32_MAX)
+ pOpts->aBootCatEntries[idxBootCat].u.Section.pszImageNameInIso = pszBootImage;
+ pOpts->aBootCatEntries[idxBootCat].u.Section.idxImageObj = idxImageObj;
+ }
+ return rc;
+}
+
+
+/**
+ * Deals with: --eltorito-platform-id {x86|PPC|Mac|efi|number}
+ *
+ * Operates on the validation entry or a section header.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param pszPlatformId The platform ID.
+ */
+static int rtFsIsoMakerCmdOptEltoritoPlatformId(PRTFSISOMAKERCMDOPTS pOpts, const char *pszPlatformId)
+{
+ /* Decode it. */
+ uint8_t idPlatform;
+ if (strcmp(pszPlatformId, "x86") == 0)
+ idPlatform = ISO9660_ELTORITO_PLATFORM_ID_X86;
+ else if (strcmp(pszPlatformId, "PPC") == 0)
+ idPlatform = ISO9660_ELTORITO_PLATFORM_ID_PPC;
+ else if (strcmp(pszPlatformId, "Mac") == 0)
+ idPlatform = ISO9660_ELTORITO_PLATFORM_ID_MAC;
+ else if (strcmp(pszPlatformId, "efi") == 0)
+ idPlatform = ISO9660_ELTORITO_PLATFORM_ID_EFI;
+ else
+ {
+ int rc = RTStrToUInt8Full(pszPlatformId, 0, &idPlatform);
+ if (rc != VINF_SUCCESS)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "invalid or unknown platform ID: %s", pszPlatformId);
+ }
+
+ /* If this option comes before anything related to the default entry, work
+ on the validation entry. */
+ if (pOpts->cBootCatEntries <= 1)
+ {
+ rtFsIsoMakerCmdOptEltoritoEnsureValidationEntry(pOpts);
+ pOpts->aBootCatEntries[0].u.Validation.idPlatform = idPlatform;
+ }
+ /* Otherwise, work on the current section header, creating a new one if necessary. */
+ else
+ {
+ uint32_t idxBootCat = pOpts->cBootCatEntries - 1;
+ if (pOpts->aBootCatEntries[idxBootCat].enmType == RTFSISOMKCMDELTORITOENTRY::kEntryType_SectionHeader)
+ pOpts->aBootCatEntries[idxBootCat].u.SectionHeader.idPlatform = idPlatform;
+ else
+ {
+ idxBootCat++;
+ if (idxBootCat + 2 > RT_ELEMENTS(pOpts->aBootCatEntries))
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_BUFFER_OVERFLOW, "Too many boot catalog entries");
+
+ pOpts->aBootCatEntries[idxBootCat].enmType = RTFSISOMKCMDELTORITOENTRY::kEntryType_SectionHeader;
+ pOpts->aBootCatEntries[idxBootCat].u.SectionHeader.idPlatform = idPlatform;
+ pOpts->aBootCatEntries[idxBootCat].u.SectionHeader.pszString = NULL;
+ pOpts->cBootCatEntries = idxBootCat + 1;
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Deals with: -no-boot
+ *
+ * This operates on the current eltorito boot catalog entry.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ */
+static int rtFsIsoMakerCmdOptEltoritoSetNotBootable(PRTFSISOMAKERCMDOPTS pOpts)
+{
+ uint32_t idxBootCat;
+ int rc = rtFsIsoMakerCmdOptEltoritoEnsureSectionEntry(pOpts, false /*fForceNew*/, &idxBootCat);
+ if (RT_SUCCESS(rc))
+ pOpts->aBootCatEntries[idxBootCat].u.Section.fBootable = false;
+ return rc;
+}
+
+
+/**
+ * Deals with: -hard-disk-boot, -no-emulation-boot, --eltorito-floppy-12,
+ * --eltorito-floppy-144, --eltorito-floppy-288
+ *
+ * This operates on the current eltorito boot catalog entry.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param bMediaType The media type.
+ */
+static int rtFsIsoMakerCmdOptEltoritoSetMediaType(PRTFSISOMAKERCMDOPTS pOpts, uint8_t bMediaType)
+{
+ uint32_t idxBootCat;
+ int rc = rtFsIsoMakerCmdOptEltoritoEnsureSectionEntry(pOpts, false /*fForceNew*/, &idxBootCat);
+ if (RT_SUCCESS(rc))
+ pOpts->aBootCatEntries[idxBootCat].u.Section.bBootMediaType = bMediaType;
+ return rc;
+}
+
+
+/**
+ * Deals with: -boot-load-seg {seg}
+ *
+ * This operates on the current eltorito boot catalog entry.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param uSeg The load segment.
+ */
+static int rtFsIsoMakerCmdOptEltoritoSetLoadSegment(PRTFSISOMAKERCMDOPTS pOpts, uint16_t uSeg)
+{
+ uint32_t idxBootCat;
+ int rc = rtFsIsoMakerCmdOptEltoritoEnsureSectionEntry(pOpts, false /*fForceNew*/, &idxBootCat);
+ if (RT_SUCCESS(rc))
+ pOpts->aBootCatEntries[idxBootCat].u.Section.uLoadSeg = uSeg;
+ return rc;
+}
+
+
+/**
+ * Deals with: -boot-load-size {sectors}
+ *
+ * This operates on the current eltorito boot catalog entry.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param cSectors Number of emulated sectors to load
+ */
+static int rtFsIsoMakerCmdOptEltoritoSetLoadSectorCount(PRTFSISOMAKERCMDOPTS pOpts, uint16_t cSectors)
+{
+ uint32_t idxBootCat;
+ int rc = rtFsIsoMakerCmdOptEltoritoEnsureSectionEntry(pOpts, false /*fForceNew*/, &idxBootCat);
+ if (RT_SUCCESS(rc))
+ pOpts->aBootCatEntries[idxBootCat].u.Section.cSectorsToLoad = cSectors;
+ return rc;
+}
+
+
+/**
+ * Deals with: -boot-info-table
+ *
+ * This operates on the current eltorito boot catalog entry.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ */
+static int rtFsIsoMakerCmdOptEltoritoEnableBootInfoTablePatching(PRTFSISOMAKERCMDOPTS pOpts)
+{
+ uint32_t idxBootCat;
+ int rc = rtFsIsoMakerCmdOptEltoritoEnsureSectionEntry(pOpts, false /*fForceNew*/, &idxBootCat);
+ if (RT_SUCCESS(rc))
+ pOpts->aBootCatEntries[idxBootCat].u.Section.fInsertBootInfoTable = true;
+ return rc;
+}
+
+
+/**
+ * Validates and commits the boot catalog stuff.
+ *
+ * ASSUMING this is called after all options are parsed and there is only this
+ * one call.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ */
+static int rtFsIsoMakerCmdOptEltoritoCommitBootCatalog(PRTFSISOMAKERCMDOPTS pOpts)
+{
+ if (pOpts->cBootCatEntries == 0)
+ return VINF_SUCCESS;
+
+ /*
+ * Locate and configure the boot images first.
+ */
+ int rc;
+ PRTFSISOMKCMDELTORITOENTRY pBootCatEntry = &pOpts->aBootCatEntries[1];
+ for (uint32_t idxBootCat = 1; idxBootCat < pOpts->cBootCatEntries; idxBootCat++, pBootCatEntry++)
+ if ( pBootCatEntry->enmType == RTFSISOMKCMDELTORITOENTRY::kEntryType_Default
+ || pBootCatEntry->enmType == RTFSISOMKCMDELTORITOENTRY::kEntryType_Section)
+ {
+ /* Make sure we've got a boot image. */
+ uint32_t idxImageObj = pBootCatEntry->u.Section.idxImageObj;
+ if (idxImageObj == UINT32_MAX)
+ {
+ const char *pszBootImage = pBootCatEntry->u.Section.pszImageNameInIso;
+ if (pszBootImage == NULL)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "No image name given for boot catalog entry #%u", idxBootCat);
+
+ idxImageObj = RTFsIsoMakerGetObjIdxForPath(pOpts->hIsoMaker, RTFSISOMAKER_NAMESPACE_ALL, pszBootImage);
+ if (idxImageObj == UINT32_MAX)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Unable to locate image for boot catalog entry #%u: %s",
+ idxBootCat, pszBootImage);
+ pBootCatEntry->u.Section.idxImageObj = idxImageObj;
+ }
+
+ /* Enable patching it? */
+ if (pBootCatEntry->u.Section.fInsertBootInfoTable)
+ {
+ rc = RTFsIsoMakerObjEnableBootInfoTablePatching(pOpts->hIsoMaker, idxImageObj, true);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc,
+ "RTFsIsoMakerObjEnableBootInfoTablePatching failed on entry #%u: %Rrc",
+ idxBootCat, rc);
+ }
+
+ /* Figure out the floppy type given the object size. */
+ if (pBootCatEntry->u.Section.bBootMediaType == ISO9660_ELTORITO_BOOT_MEDIA_TYPE_MASK)
+ {
+ uint64_t cbImage;
+ rc = RTFsIsoMakerObjQueryDataSize(pOpts->hIsoMaker, idxImageObj, &cbImage);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTFsIsoMakerObjGetDataSize failed on entry #%u: %Rrc",
+ idxBootCat, rc);
+ if (cbImage == 1228800)
+ pBootCatEntry->u.Section.bBootMediaType = ISO9660_ELTORITO_BOOT_MEDIA_TYPE_FLOPPY_1_2_MB;
+ else if (cbImage <= 1474560)
+ pBootCatEntry->u.Section.bBootMediaType = ISO9660_ELTORITO_BOOT_MEDIA_TYPE_FLOPPY_1_44_MB;
+ else if (cbImage <= 2949120)
+ pBootCatEntry->u.Section.bBootMediaType = ISO9660_ELTORITO_BOOT_MEDIA_TYPE_FLOPPY_2_88_MB;
+ else
+ pBootCatEntry->u.Section.bBootMediaType = ISO9660_ELTORITO_BOOT_MEDIA_TYPE_HARD_DISK;
+ }
+ }
+
+ /*
+ * Add the boot catalog entries.
+ */
+ pBootCatEntry = &pOpts->aBootCatEntries[0];
+ for (uint32_t idxBootCat = 0; idxBootCat < pOpts->cBootCatEntries; idxBootCat++, pBootCatEntry++)
+ switch (pBootCatEntry->enmType)
+ {
+ case RTFSISOMKCMDELTORITOENTRY::kEntryType_Validation:
+ Assert(idxBootCat == 0);
+ rc = RTFsIsoMakerBootCatSetValidationEntry(pOpts->hIsoMaker, pBootCatEntry->u.Validation.idPlatform,
+ pBootCatEntry->u.Validation.pszString);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTFsIsoMakerBootCatSetValidationEntry failed: %Rrc", rc);
+ break;
+
+ case RTFSISOMKCMDELTORITOENTRY::kEntryType_Default:
+ case RTFSISOMKCMDELTORITOENTRY::kEntryType_Section:
+ Assert(pBootCatEntry->enmType == RTFSISOMKCMDELTORITOENTRY::kEntryType_Default ? idxBootCat == 1 : idxBootCat > 2);
+ rc = RTFsIsoMakerBootCatSetSectionEntry(pOpts->hIsoMaker, idxBootCat,
+ pBootCatEntry->u.Section.idxImageObj,
+ pBootCatEntry->u.Section.bBootMediaType,
+ pBootCatEntry->u.Section.bSystemType,
+ pBootCatEntry->u.Section.fBootable,
+ pBootCatEntry->u.Section.uLoadSeg,
+ pBootCatEntry->u.Section.cSectorsToLoad,
+ ISO9660_ELTORITO_SEL_CRIT_TYPE_NONE, NULL, 0);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTFsIsoMakerBootCatSetSectionEntry failed on entry #%u: %Rrc",
+ idxBootCat, rc);
+ break;
+
+ case RTFSISOMKCMDELTORITOENTRY::kEntryType_SectionHeader:
+ {
+ uint32_t cEntries = 1;
+ while ( idxBootCat + cEntries < pOpts->cBootCatEntries
+ && pBootCatEntry[cEntries].enmType != RTFSISOMKCMDELTORITOENTRY::kEntryType_SectionHeader)
+ cEntries++;
+ cEntries--;
+
+ Assert(idxBootCat > 1);
+ rc = RTFsIsoMakerBootCatSetSectionHeaderEntry(pOpts->hIsoMaker, idxBootCat, cEntries,
+ pBootCatEntry->u.SectionHeader.idPlatform,
+ pBootCatEntry->u.SectionHeader.pszString);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc,
+ "RTFsIsoMakerBootCatSetSectionHeaderEntry failed on entry #%u: %Rrc",
+ idxBootCat, rc);
+ break;
+ }
+
+ default:
+ AssertFailedReturn(VERR_INTERNAL_ERROR_3);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Deals with: --eltorito-new-entry, --eltorito-alt-boot
+ *
+ * This operates on the current eltorito boot catalog entry.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ */
+static int rtFsIsoMakerCmdOptEltoritoNewEntry(PRTFSISOMAKERCMDOPTS pOpts)
+{
+ uint32_t idxBootCat;
+ return rtFsIsoMakerCmdOptEltoritoEnsureSectionEntry(pOpts, true /*fForceNew*/, &idxBootCat);
+}
+
+
+/**
+ * Sets a string property in all namespaces.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param pszValue The new string value.
+ * @param enmStringProp The string property.
+ */
+static int rtFsIsoMakerCmdOptSetStringProp(PRTFSISOMAKERCMDOPTS pOpts, const char *pszValue, RTFSISOMAKERSTRINGPROP enmStringProp)
+{
+ int rc = RTFsIsoMakerSetStringProp(pOpts->hIsoMaker, enmStringProp, pOpts->fDstNamespaces, pszValue);
+ if (RT_FAILURE(rc))
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to set string property %d to '%s': %Rrc", enmStringProp, pszValue, rc);
+ return rc;
+}
+
+
+/**
+ * Handles the --dir-mode and --file-mode options.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param fDir True if applies to dir, false if applies to
+ * files.
+ * @param fMode The forced mode.
+ */
+static int rtFsIsoMakerCmdOptSetFileOrDirMode(PRTFSISOMAKERCMDOPTS pOpts, bool fDir, RTFMODE fMode)
+{
+ /* Change the mode masks. */
+ int rc;
+ if (fDir)
+ rc = RTFsIsoMakerSetForcedDirMode(pOpts->hIsoMaker, fMode, true /*fForced*/);
+ else
+ rc = RTFsIsoMakerSetForcedFileMode(pOpts->hIsoMaker, fMode, true /*fForced*/);
+ if (RT_SUCCESS(rc))
+ {
+ /* Then enable rock.*/
+ rc = RTFsIsoMakerSetRockRidgeLevel(pOpts->hIsoMaker, 2);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to enable rock ridge: %Rrc", rc);
+ }
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to set %s force & default mode mask to %04o: %Rrc",
+ fMode, fDir ? "directory" : "file", rc);
+}
+
+
+/**
+ * Handles the --no-dir-mode and --no-file-mode options that counters
+ * --dir-mode and --file-mode.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param fDir True if applies to dir, false if applies to
+ * files.
+ */
+static int rtFsIsoMakerCmdOptDisableFileOrDirMode(PRTFSISOMAKERCMDOPTS pOpts, bool fDir)
+{
+ int rc;
+ if (fDir)
+ rc = RTFsIsoMakerSetForcedDirMode(pOpts->hIsoMaker, 0, false /*fForced*/);
+ else
+ rc = RTFsIsoMakerSetForcedFileMode(pOpts->hIsoMaker, 0, false /*fForced*/);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to disable forced %s mode mask: %Rrc", fDir ? "directory" : "file", rc);
+}
+
+
+
+/**
+ * Handles the --new-dir-mode option.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param fMode The forced mode.
+ */
+static int rtFsIsoMakerCmdOptSetNewDirMode(PRTFSISOMAKERCMDOPTS pOpts, RTFMODE fMode)
+{
+ int rc = RTFsIsoMakerSetDefaultDirMode(pOpts->hIsoMaker, fMode);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to set default dir mode mask to %04o: %Rrc", fMode, rc);
+}
+
+
+/**
+ * Handles the --chmod option.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param pszSpec The option value.
+ */
+static int rtFsIsoMakerCmdOptChmod(PRTFSISOMAKERCMDOPTS pOpts, const char *pszSpec)
+{
+ /*
+ * Parse the mode part.
+ */
+ int rc;
+ uint32_t fUnset = 07777;
+ uint32_t fSet = 0;
+ const char *pszPath = pszSpec;
+ if (RT_C_IS_DIGIT(*pszPath))
+ {
+ rc = RTStrToUInt32Ex(pszSpec, (char **)&pszPath, 8, &fSet);
+ if (rc != VWRN_TRAILING_CHARS)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Malformed --chmod, octal mode parse failed: %s (%Rrc)", pszSpec, rc);
+ if (fSet & ~07777)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Malformed --chmod, invalid mode mask: 0%o, max 07777", fSet);
+ if (*pszPath != ':')
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Malformed --chmod, expected colon after mode: %s", pszSpec);
+ }
+ else
+ {
+ pszPath = strchr(pszPath, ':');
+ if (pszPath == NULL)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Malformed --chmod, expected colon after mode: %s", pszSpec);
+ size_t const cchMode = pszPath - pszSpec;
+
+ /* We currently only matches certain patterns. Later this needs to be generalized into a RTFile or RTPath method. */
+ fUnset = 0;
+#define MATCH_MODE_STR(a_szMode) (cchMode == sizeof(a_szMode) - 1U && memcmp(pszSpec, a_szMode, sizeof(a_szMode) - 1) == 0)
+ if (MATCH_MODE_STR("a+x"))
+ fSet = 0111;
+ else if (MATCH_MODE_STR("a+r"))
+ fSet = 0444;
+ else if (MATCH_MODE_STR("a+rx"))
+ fSet = 0555;
+ else
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Sorry, --chmod doesn't understand complicated mode expressions: %s", pszSpec);
+#undef MATCH_MODE_STR
+ }
+
+ /*
+ * Check that the file starts with a slash.
+ */
+ pszPath++;
+ if (!RTPATH_IS_SLASH(*pszPath))
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Malformed --chmod, path must start with a slash: %s", pszSpec);
+
+ /*
+ * Do the job.
+ */
+ rc = RTFsIsoMakerSetPathMode(pOpts->hIsoMaker, pszPath, pOpts->fDstNamespaces, fSet, fUnset, 0 /*fFlags*/, NULL /*pcHits*/);
+ if (rc == VWRN_NOT_FOUND)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Could not find --chmod path: %s", pszPath);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "RTFsIsoMakerSetPathMode(,%s,%#x,%o,%o,0,) failed: %Rrc",
+ pszPath, pOpts->fDstNamespaces, fSet, fUnset, rc);
+}
+
+
+/**
+ * Handles the --chown and --chgrp options.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param pszSpec The option value.
+ * @param fIsChOwn Set if 'chown', clear if 'chgrp'.
+ */
+static int rtFsIsoMakerCmdOptChangeOwnerGroup(PRTFSISOMAKERCMDOPTS pOpts, const char *pszSpec, bool fIsChOwn)
+{
+ const char * const pszOpt = fIsChOwn ? "chown" : "chgrp";
+
+ /*
+ * Parse out the ID and path .
+ */
+ uint32_t idValue;
+ const char *pszPath = pszSpec;
+ int rc = RTStrToUInt32Ex(pszSpec, (char **)&pszPath, 0, &idValue);
+ if (rc != VWRN_TRAILING_CHARS)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Malformed --%s, numeric ID parse failed: %s (%Rrc)", pszOpt, pszSpec, rc);
+ if (*pszPath != ':')
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Malformed --%s, expected colon after ID: %s", pszOpt, pszSpec);
+ pszPath++;
+ if (!RTPATH_IS_SLASH(*pszPath))
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Malformed --%s, path must start with a slash: %s", pszOpt, pszSpec);
+
+ /*
+ * Do the job.
+ */
+ if (fIsChOwn)
+ rc = RTFsIsoMakerSetPathOwnerId(pOpts->hIsoMaker, pszPath, pOpts->fDstNamespaces, idValue, NULL /*pcHits*/);
+ else
+ rc = RTFsIsoMakerSetPathGroupId(pOpts->hIsoMaker, pszPath, pOpts->fDstNamespaces, idValue, NULL /*pcHits*/);
+ if (rc == VWRN_NOT_FOUND)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Could not find --%s path: %s", pszOpt, pszPath);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "RTFsIsoMakerSetPath%sId(,%s,%#x,%u,) failed: %Rrc",
+ fIsChOwn ? "Owner" : "Group", pszPath, pOpts->fDstNamespaces, idValue, rc);
+}
+
+
+/**
+ * Loads an argument file (e.g. a .iso-file) and parses it.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param pszFileSpec The file to parse.
+ * @param cDepth The current nesting depth.
+ */
+static int rtFsIsoMakerCmdParseArgumentFile(PRTFSISOMAKERCMDOPTS pOpts, const char *pszFileSpec, unsigned cDepth)
+{
+ if (cDepth > 2)
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_INVALID_PARAMETER, "Too many nested argument files!");
+
+ /*
+ * Read the file into memory.
+ */
+ RTERRINFOSTATIC ErrInfo;
+ uint32_t offError;
+ RTVFSFILE hVfsFile;
+ int rc = RTVfsChainOpenFile(pszFileSpec, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &hVfsFile,
+ &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdChainError(pOpts, "RTVfsChainOpenFile", pszFileSpec, rc, offError, &ErrInfo.Core);
+
+ uint64_t cbFile = 0;
+ rc = RTVfsFileQuerySize(hVfsFile, &cbFile);
+ if (RT_SUCCESS(rc))
+ {
+ if (cbFile < _2M)
+ {
+ char *pszContent = (char *)RTMemTmpAllocZ((size_t)cbFile + 1);
+ if (pszContent)
+ {
+ rc = RTVfsFileRead(hVfsFile, pszContent, (size_t)cbFile, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Check that it's valid UTF-8 and turn it into an argument vector.
+ */
+ rc = RTStrValidateEncodingEx(pszContent, (size_t)cbFile + 1,
+ RTSTR_VALIDATE_ENCODING_EXACT_LENGTH | RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t fGetOpt = strstr(pszContent, "--iprt-iso-maker-file-marker-ms") == NULL
+ ? RTGETOPTARGV_CNV_QUOTE_BOURNE_SH : RTGETOPTARGV_CNV_QUOTE_MS_CRT;
+ fGetOpt |= RTGETOPTARGV_CNV_MODIFY_INPUT;
+ char **papszArgs;
+ int cArgs;
+ rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszContent, fGetOpt, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Parse them.
+ */
+ rc = rtFsIsoMakerCmdParse(pOpts, cArgs, papszArgs, cDepth + 1);
+
+ RTGetOptArgvFreeEx(papszArgs, fGetOpt);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "%s: RTGetOptArgvFromString failed: %Rrc", pszFileSpec, rc);
+
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "%s: invalid encoding", pszFileSpec);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "%s: error to read it into memory: %Rrc", pszFileSpec, rc);
+ RTMemTmpFree(pszContent);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_NO_TMP_MEMORY, "%s: failed to allocte %zu bytes for reading",
+ pszFileSpec, (size_t)cbFile + 1);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_FILE_TOO_BIG, "%s: file is too big: %'RU64 bytes, max 2MB", pszFileSpec, cbFile);
+ }
+ else
+ rtFsIsoMakerCmdErrorRc(pOpts, rc, "%s: RTVfsFileQuerySize failed: %Rrc", pszFileSpec, rc);
+ RTVfsFileRelease(hVfsFile);
+ return rc;
+}
+
+
+/**
+ * Parses the given command line options.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_CALLBACK_RETURN if exit successfully (help, version).
+ * @param pOpts The ISO maker command instance.
+ * @param cArgs Number of arguments in papszArgs.
+ * @param papszArgs The argument vector to parse.
+ */
+static int rtFsIsoMakerCmdParse(PRTFSISOMAKERCMDOPTS pOpts, unsigned cArgs, char **papszArgs, unsigned cDepth)
+{
+ /* Setup option parsing. */
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, g_aRtFsIsoMakerOptions, RT_ELEMENTS(g_aRtFsIsoMakerOptions),
+ cDepth == 0 ? 1 : 0 /*iFirst*/, 0 /*fFlags*/);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTGetOpt failed: %Rrc", rc);
+
+ /*
+ * Parse parameters. Parameters are position dependent.
+ */
+ RTGETOPTUNION ValueUnion;
+ while ( RT_SUCCESS(rc)
+ && (rc = RTGetOpt(&GetState, &ValueUnion)) != 0)
+ {
+ switch (rc)
+ {
+ /*
+ * Files and directories.
+ */
+ case VINF_GETOPT_NOT_OPTION:
+ if ( *ValueUnion.psz != '@'
+ || strchr(ValueUnion.psz, '='))
+ rc = rtFsIsoMakerCmdAddSomething(pOpts, ValueUnion.psz);
+ else
+ rc = rtFsIsoMakerCmdParseArgumentFile(pOpts, ValueUnion.psz + 1, cDepth);
+ break;
+
+
+ /*
+ * General options
+ */
+ case 'o':
+ if (pOpts->fVirtualImageMaker)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "The --output option is not allowed");
+ if (pOpts->pszOutFile)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "The --output option is specified more than once");
+ pOpts->pszOutFile = ValueUnion.psz;
+ break;
+
+ case RTFSISOMAKERCMD_OPT_NAME_SETUP:
+ rc = rtFsIsoMakerCmdOptNameSetup(pOpts, ValueUnion.psz);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_NAME_SETUP_FROM_IMPORT:
+ rc = rtFsIsoMakerCmdOptNameSetupFromImport(pOpts);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_PUSH_ISO:
+ rc = rtFsIsoMakerCmdOptPushIso(pOpts, ValueUnion.psz, "--push-iso", 0);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_PUSH_ISO_NO_JOLIET:
+ rc = rtFsIsoMakerCmdOptPushIso(pOpts, ValueUnion.psz, "--push-iso-no-joliet", RTFSISO9660_F_NO_JOLIET);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_PUSH_ISO_NO_ROCK:
+ rc = rtFsIsoMakerCmdOptPushIso(pOpts, ValueUnion.psz, "--push-iso-no-rock", RTFSISO9660_F_NO_ROCK);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_PUSH_ISO_NO_ROCK_NO_JOLIET:
+ rc = rtFsIsoMakerCmdOptPushIso(pOpts, ValueUnion.psz, "--push-iso-no-rock-no-joliet",
+ RTFSISO9660_F_NO_ROCK | RTFSISO9660_F_NO_JOLIET);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_POP:
+ rc = rtFsIsoMakerCmdOptPop(pOpts);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_IMPORT_ISO:
+ rc = rtFsIsoMakerCmdOptImportIso(pOpts, ValueUnion.psz);
+ break;
+
+
+ /*
+ * Namespace configuration.
+ */
+ case RTFSISOMAKERCMD_OPT_ISO_LEVEL:
+ rc = rtFsIsoMakerCmdOptSetIsoLevel(pOpts, ValueUnion.u8);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_ROCK_RIDGE:
+ rc = rtFsIsoMakerCmdOptSetPrimaryRockLevel(pOpts, 2);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_LIMITED_ROCK_RIDGE:
+ rc = rtFsIsoMakerCmdOptSetPrimaryRockLevel(pOpts, 1);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_NO_ROCK_RIDGE:
+ rc = rtFsIsoMakerCmdOptSetPrimaryRockLevel(pOpts, 0);
+ break;
+
+ case 'J':
+ rc = rtFsIsoMakerCmdOptSetJolietUcs2Level(pOpts, 3);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_NO_JOLIET:
+ rc = rtFsIsoMakerCmdOptSetJolietUcs2Level(pOpts, 0);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_JOLIET_LEVEL:
+ rc = rtFsIsoMakerCmdOptSetJolietUcs2Level(pOpts, ValueUnion.u8);
+ break;
+
+
+ /*
+ * File attributes.
+ */
+ case RTFSISOMAKERCMD_OPT_RATIONAL_ATTRIBS:
+ rc = rtFsIsoMakerCmdOptSetAttribInheritStyle(pOpts, false /*fStrict*/);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_STRICT_ATTRIBS:
+ rc = rtFsIsoMakerCmdOptSetAttribInheritStyle(pOpts, true /*fStrict*/);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_FILE_MODE:
+ rc = rtFsIsoMakerCmdOptSetFileOrDirMode(pOpts, false /*fDir*/, ValueUnion.u32);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_NO_FILE_MODE:
+ rc = rtFsIsoMakerCmdOptDisableFileOrDirMode(pOpts, false /*fDir*/);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_DIR_MODE:
+ rc = rtFsIsoMakerCmdOptSetFileOrDirMode(pOpts, true /*fDir*/, ValueUnion.u32);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_NO_DIR_MODE:
+ rc = rtFsIsoMakerCmdOptDisableFileOrDirMode(pOpts, true /*fDir*/);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_NEW_DIR_MODE:
+ rc = rtFsIsoMakerCmdOptSetNewDirMode(pOpts, ValueUnion.u32);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_CHMOD:
+ rc = rtFsIsoMakerCmdOptChmod(pOpts, ValueUnion.psz);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_CHOWN:
+ rc = rtFsIsoMakerCmdOptChangeOwnerGroup(pOpts, ValueUnion.psz, true /*fIsChOwn*/);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_CHGRP:
+ rc = rtFsIsoMakerCmdOptChangeOwnerGroup(pOpts, ValueUnion.psz, false /*fIsChOwn*/);
+ break;
+
+
+ /*
+ * Boot related options.
+ */
+ case 'G': /* --generic-boot <file> */
+ rc = rtFsIsoMakerCmdOptGenericBoot(pOpts, ValueUnion.psz);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_ELTORITO_ADD_IMAGE:
+ rc = rtFsIsoMakerCmdOptEltoritoAddImage(pOpts, ValueUnion.psz);
+ break;
+
+ case 'b': /* --eltorito-boot <boot.img> */
+ rc = rtFsIsoMakerCmdOptEltoritoBoot(pOpts, ValueUnion.psz);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_ELTORITO_NEW_ENTRY:
+ rc = rtFsIsoMakerCmdOptEltoritoNewEntry(pOpts);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_ELTORITO_PLATFORM_ID:
+ rc = rtFsIsoMakerCmdOptEltoritoPlatformId(pOpts, ValueUnion.psz);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_ELTORITO_NO_BOOT:
+ rc = rtFsIsoMakerCmdOptEltoritoSetNotBootable(pOpts);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_ELTORITO_FLOPPY_12:
+ rc = rtFsIsoMakerCmdOptEltoritoSetMediaType(pOpts, ISO9660_ELTORITO_BOOT_MEDIA_TYPE_FLOPPY_1_2_MB);
+ break;
+ case RTFSISOMAKERCMD_OPT_ELTORITO_FLOPPY_144:
+ rc = rtFsIsoMakerCmdOptEltoritoSetMediaType(pOpts, ISO9660_ELTORITO_BOOT_MEDIA_TYPE_FLOPPY_1_44_MB);
+ break;
+ case RTFSISOMAKERCMD_OPT_ELTORITO_FLOPPY_288:
+ rc = rtFsIsoMakerCmdOptEltoritoSetMediaType(pOpts, ISO9660_ELTORITO_BOOT_MEDIA_TYPE_FLOPPY_2_88_MB);
+ break;
+ case RTFSISOMAKERCMD_OPT_ELTORITO_HARD_DISK_BOOT:
+ rc = rtFsIsoMakerCmdOptEltoritoSetMediaType(pOpts, ISO9660_ELTORITO_BOOT_MEDIA_TYPE_HARD_DISK);
+ break;
+ case RTFSISOMAKERCMD_OPT_ELTORITO_NO_EMULATION_BOOT:
+ rc = rtFsIsoMakerCmdOptEltoritoSetMediaType(pOpts, ISO9660_ELTORITO_BOOT_MEDIA_TYPE_NO_EMULATION);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_ELTORITO_LOAD_SEG:
+ rc = rtFsIsoMakerCmdOptEltoritoSetLoadSegment(pOpts, ValueUnion.u16);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_ELTORITO_LOAD_SIZE:
+ rc = rtFsIsoMakerCmdOptEltoritoSetLoadSectorCount(pOpts, ValueUnion.u16);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_ELTORITO_INFO_TABLE:
+ rc = rtFsIsoMakerCmdOptEltoritoEnableBootInfoTablePatching(pOpts);
+ break;
+
+ case 'c': /* --boot-catalog <cd-path> */
+ rc = rtFsIsoMakerCmdOptEltoritoSetBootCatalogPath(pOpts, ValueUnion.psz);
+ break;
+
+
+ /*
+ * Image/namespace property related options.
+ */
+ case RTFSISOMAKERCMD_OPT_ABSTRACT_FILE_ID:
+ rc = rtFsIsoMakerCmdOptSetStringProp(pOpts, ValueUnion.psz, RTFSISOMAKERSTRINGPROP_ABSTRACT_FILE_ID);
+ break;
+
+ case 'A': /* --application-id */
+ rc = rtFsIsoMakerCmdOptSetStringProp(pOpts, ValueUnion.psz, RTFSISOMAKERSTRINGPROP_APPLICATION_ID);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_BIBLIOGRAPHIC_FILE_ID:
+ rc = rtFsIsoMakerCmdOptSetStringProp(pOpts, ValueUnion.psz, RTFSISOMAKERSTRINGPROP_BIBLIOGRAPHIC_FILE_ID);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_COPYRIGHT_FILE_ID:
+ rc = rtFsIsoMakerCmdOptSetStringProp(pOpts, ValueUnion.psz, RTFSISOMAKERSTRINGPROP_COPYRIGHT_FILE_ID);
+ break;
+
+ case 'P': /* -publisher */
+ rc = rtFsIsoMakerCmdOptSetStringProp(pOpts, ValueUnion.psz, RTFSISOMAKERSTRINGPROP_PUBLISHER_ID);
+ break;
+
+ case 'p': /* --preparer*/
+ rc = rtFsIsoMakerCmdOptSetStringProp(pOpts, ValueUnion.psz, RTFSISOMAKERSTRINGPROP_DATA_PREPARER_ID);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_SYSTEM_ID:
+ rc = rtFsIsoMakerCmdOptSetStringProp(pOpts, ValueUnion.psz, RTFSISOMAKERSTRINGPROP_SYSTEM_ID);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_VOLUME_ID: /* (should've been '-V') */
+ rc = rtFsIsoMakerCmdOptSetStringProp(pOpts, ValueUnion.psz, RTFSISOMAKERSTRINGPROP_VOLUME_ID);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_VOLUME_SET_ID:
+ rc = rtFsIsoMakerCmdOptSetStringProp(pOpts, ValueUnion.psz, RTFSISOMAKERSTRINGPROP_VOLUME_SET_ID);
+ break;
+
+
+ /*
+ * Compatibility.
+ */
+ case RTFSISOMAKERCMD_OPT_GRAFT_POINTS:
+ rc = rtFsIsoMakerCmdOptNameSetup(pOpts, "iso+joliet+udf+hfs");
+ break;
+
+ case 'l':
+ if (RTFsIsoMakerGetIso9660Level(pOpts->hIsoMaker) >= 2)
+ rc = rtFsIsoMakerCmdOptSetIsoLevel(pOpts, 2);
+ break;
+
+ case 'R':
+ rc = rtFsIsoMakerCmdOptSetPrimaryRockLevel(pOpts, 2);
+ if (RT_SUCCESS(rc))
+ rc = rtFsIsoMakerCmdOptSetAttribInheritStyle(pOpts, true /*fStrict*/);
+ break;
+
+ case 'r':
+ rc = rtFsIsoMakerCmdOptSetPrimaryRockLevel(pOpts, 2);
+ if (RT_SUCCESS(rc))
+ rc = rtFsIsoMakerCmdOptSetAttribInheritStyle(pOpts, false /*fStrict*/);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_PAD:
+ rc = RTFsIsoMakerSetImagePadding(pOpts->hIsoMaker, 150);
+ if (RT_FAILURE(rc))
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTFsIsoMakerSetImagePadding failed: %Rrc", rc);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_NO_PAD:
+ rc = RTFsIsoMakerSetImagePadding(pOpts->hIsoMaker, 0);
+ if (RT_FAILURE(rc))
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTFsIsoMakerSetImagePadding failed: %Rrc", rc);
+ break;
+
+
+ /*
+ * VISO specific
+ */
+ case RTFSISOMAKERCMD_OPT_IPRT_ISO_MAKER_FILE_MARKER:
+ /* ignored */
+ break;
+
+
+ /*
+ * Testing.
+ */
+ case RTFSISOMAKERCMD_OPT_OUTPUT_BUFFER_SIZE: /* --output-buffer-size {cb} */
+ pOpts->cbOutputReadBuffer = ValueUnion.u32;
+ break;
+
+ case RTFSISOMAKERCMD_OPT_RANDOM_OUTPUT_BUFFER_SIZE: /* --random-output-buffer-size */
+ pOpts->fRandomOutputReadBufferSize = true;
+ break;
+
+ case RTFSISOMAKERCMD_OPT_RANDOM_ORDER_VERIFICATION: /* --random-order-verification {cb} */
+ pOpts->cbRandomOrderVerifciationBlock = ValueUnion.u32;
+ break;
+
+
+ /*
+ * Standard bits.
+ */
+ case 'h':
+ rtFsIsoMakerCmdUsage(pOpts, papszArgs[0]);
+ return pOpts->fVirtualImageMaker ? VERR_NOT_FOUND : VINF_CALLBACK_RETURN;
+
+ case 'V':
+ rtFsIsoMakerPrintf(pOpts, "%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ return pOpts->fVirtualImageMaker ? VERR_NOT_FOUND : VINF_CALLBACK_RETURN;
+
+ default:
+ if (rc > 0 && RT_C_IS_GRAPH(rc))
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_GETOPT_UNKNOWN_OPTION, "Unhandled option: -%c", rc);
+ else if (rc > 0)
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_GETOPT_UNKNOWN_OPTION, "Unhandled option: %i (%#x)", rc, rc);
+ else if (rc == VERR_GETOPT_UNKNOWN_OPTION)
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Unknown option: '%s'", ValueUnion.psz);
+ else if (ValueUnion.pDef)
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "%s: %Rrs", ValueUnion.pDef->pszLong, rc);
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "%Rrs", rc);
+ return rc;
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Extended ISO maker command.
+ *
+ * This can be used as a ISO maker command that produces a image file, or
+ * alternatively for setting up a virtual ISO in memory.
+ *
+ * @returns IPRT status code
+ * @param cArgs Number of arguments.
+ * @param papszArgs Pointer to argument array.
+ * @param hVfsCwd The current working directory to assume when processing
+ * relative file/dir references. Pass NIL_RTVFSDIR to use
+ * the current CWD of the process.
+ * @param pszCwd Path to @a hVfsCwdDir. Use for error reporting and
+ * optimizing the open file count if possible.
+ * @param phVfsFile Where to return the virtual ISO. Pass NULL to for
+ * normal operation (creates file on disk).
+ * @param pErrInfo Where to return extended error information in the
+ * virtual ISO mode.
+ */
+RTDECL(int) RTFsIsoMakerCmdEx(unsigned cArgs, char **papszArgs, RTVFSDIR hVfsCwd, const char *pszCwd,
+ PRTVFSFILE phVfsFile, PRTERRINFO pErrInfo)
+{
+ if (phVfsFile)
+ *phVfsFile = NIL_RTVFSFILE;
+
+ /*
+ * Create instance.
+ */
+ RTFSISOMAKERCMDOPTS Opts;
+ RT_ZERO(Opts);
+ Opts.hIsoMaker = NIL_RTFSISOMAKER;
+ Opts.pErrInfo = pErrInfo;
+ Opts.fVirtualImageMaker = phVfsFile != NULL;
+ Opts.cNameSpecifiers = 1;
+ Opts.afNameSpecifiers[0] = RTFSISOMAKERCMDNAME_MAJOR_MASK;
+ Opts.fDstNamespaces = RTFSISOMAKERCMDNAME_MAJOR_MASK;
+ Opts.pszTransTbl = "TRANS.TBL"; /** @todo query this below */
+ for (uint32_t i = 0; i < RT_ELEMENTS(Opts.aBootCatEntries); i++)
+ Opts.aBootCatEntries[i].u.Section.idxImageObj = UINT32_MAX;
+
+ /* Initialize the source stack with NILs (to be on the safe size). */
+ Opts.iSrcStack = -1;
+ for (uint32_t i = 0; i < RT_ELEMENTS(Opts.aSrcStack); i++)
+ {
+ Opts.aSrcStack[i].hSrcDir = NIL_RTVFSDIR;
+ Opts.aSrcStack[i].hSrcVfs = NIL_RTVFS;
+ }
+
+ /* Push the CWD if present. */
+ if (hVfsCwd != NIL_RTVFSDIR)
+ {
+ AssertReturn(pszCwd, VERR_INVALID_PARAMETER);
+ uint32_t cRefs = RTVfsDirRetain(hVfsCwd);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ Opts.aSrcStack[0].hSrcDir = hVfsCwd;
+ Opts.aSrcStack[0].pszSrcVfs = pszCwd;
+ Opts.iSrcStack = 0;
+ }
+
+ /* Create the ISO creator instance. */
+ int rc = RTFsIsoMakerCreate(&Opts.hIsoMaker);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Parse the command line and check for mandatory options.
+ */
+ rc = rtFsIsoMakerCmdParse(&Opts, cArgs, papszArgs, 0);
+ if (RT_SUCCESS(rc) && rc != VINF_CALLBACK_RETURN)
+ {
+ if (!Opts.cItemsAdded)
+ rc = rtFsIsoMakerCmdErrorRc(&Opts, VERR_NO_DATA, "Cowardly refuses to create empty ISO image");
+ else if (!Opts.pszOutFile && !Opts.fVirtualImageMaker)
+ rc = rtFsIsoMakerCmdErrorRc(&Opts, VERR_INVALID_PARAMETER, "No output file specified (--output <file>)");
+
+ /*
+ * Final actions.
+ */
+ if (RT_SUCCESS(rc))
+ rc = rtFsIsoMakerCmdOptEltoritoCommitBootCatalog(&Opts);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Finalize the image and get the virtual file.
+ */
+ rc = RTFsIsoMakerFinalize(Opts.hIsoMaker);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSFILE hVfsFile;
+ rc = RTFsIsoMakerCreateVfsOutputFile(Opts.hIsoMaker, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * We're done now if we're only setting up a virtual image.
+ */
+ if (Opts.fVirtualImageMaker)
+ *phVfsFile = hVfsFile;
+ else
+ {
+ rc = rtFsIsoMakerCmdWriteImage(&Opts, hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ }
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(&Opts, rc, "RTFsIsoMakerCreateVfsOutputFile failed: %Rrc", rc);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(&Opts, rc, "RTFsIsoMakerFinalize failed: %Rrc", rc);
+ }
+ }
+ }
+ else
+ {
+ rc = rtFsIsoMakerCmdErrorRc(&Opts, rc, "RTFsIsoMakerCreate failed: %Rrc", rc);
+ Opts.hIsoMaker = NIL_RTFSISOMAKER;
+ }
+
+ return rtFsIsoMakerCmdDeleteState(&Opts, rc);
+}
+
+
+/**
+ * ISO maker command (creates image file on disk).
+ *
+ * @returns IPRT status code
+ * @param cArgs Number of arguments.
+ * @param papszArgs Pointer to argument array.
+ */
+RTDECL(RTEXITCODE) RTFsIsoMakerCmd(unsigned cArgs, char **papszArgs)
+{
+ int rc = RTFsIsoMakerCmdEx(cArgs, papszArgs, NIL_RTVFSDIR, NULL, NULL, NULL);
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
diff --git a/src/VBox/Runtime/common/fs/isomakerimport.cpp b/src/VBox/Runtime/common/fs/isomakerimport.cpp
new file mode 100644
index 00000000..41a72ec5
--- /dev/null
+++ b/src/VBox/Runtime/common/fs/isomakerimport.cpp
@@ -0,0 +1,2738 @@
+/* $Id: isomakerimport.cpp $ */
+/** @file
+ * IPRT - ISO Image Maker, Import Existing Image.
+ */
+
+/*
+ * 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/fsisomaker.h>
+
+#include <iprt/avl.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/ctype.h>
+#include <iprt/file.h>
+#include <iprt/list.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/utf16.h>
+#include <iprt/vfs.h>
+#include <iprt/formats/iso9660.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Max directory depth. */
+#define RTFSISOMK_IMPORT_MAX_DEPTH 32
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Block to file translation node.
+ */
+typedef struct RTFSISOMKIMPBLOCK2FILE
+{
+ /** AVL tree node containing the first block number of the file.
+ * Block number is relative to the start of the import image. */
+ AVLU32NODECORE Core;
+ /** The configuration index of the file. */
+ uint32_t idxObj;
+ /** Namespaces the file has been seen in already (RTFSISOMAKER_NAMESPACE_XXX). */
+ uint32_t fNamespaces;
+ /** Pointer to the next file with the same block number. */
+ struct RTFSISOMKIMPBLOCK2FILE *pNext;
+} RTFSISOMKIMPBLOCK2FILE;
+/** Pointer to a block-2-file translation node. */
+typedef RTFSISOMKIMPBLOCK2FILE *PRTFSISOMKIMPBLOCK2FILE;
+
+
+/**
+ * Directory todo list entry.
+ */
+typedef struct RTFSISOMKIMPDIR
+{
+ /** List stuff. */
+ RTLISTNODE Entry;
+ /** The directory configuration index with hIsoMaker. */
+ uint32_t idxObj;
+ /** The directory data block number. */
+ uint32_t offDirBlock;
+ /** The directory size (in bytes). */
+ uint32_t cbDir;
+ /** The depth of this directory. */
+ uint8_t cDepth;
+} RTFSISOMKIMPDIR;
+/** Pointer to a directory todo list entry. */
+typedef RTFSISOMKIMPDIR *PRTFSISOMKIMPDIR;
+
+
+/**
+ * ISO maker ISO importer state.
+ */
+typedef struct RTFSISOMKIMPORTER
+{
+ /** The destination ISO maker. */
+ RTFSISOMAKER hIsoMaker;
+ /** RTFSISOMK_IMPORT_F_XXX. */
+ uint32_t fFlags;
+ /** The status code of the whole import.
+ * This notes down the first error status. */
+ int rc;
+ /** Pointer to error info return structure. */
+ PRTERRINFO pErrInfo;
+
+ /** The source file. */
+ RTVFSFILE hSrcFile;
+ /** The size of the source file. */
+ uint64_t cbSrcFile;
+ /** The number of 2KB blocks in the source file. */
+ uint64_t cBlocksInSrcFile;
+ /** The import source index of hSrcFile in hIsoMaker. UINT32_MAX till adding
+ * the first file. */
+ uint32_t idxSrcFile;
+
+ /** The root of the tree for converting data block numbers to files
+ * (PRTFSISOMKIMPBLOCK2FILE). This is essential when importing boot files and
+ * the 2nd namespace (joliet, udf, hfs) so that we avoid duplicating data. */
+ AVLU32TREE Block2FileRoot;
+
+ /** The block offset of the primary volume descriptor. */
+ uint32_t offPrimaryVolDesc;
+ /** The primary volume space size in blocks. */
+ uint32_t cBlocksInPrimaryVolumeSpace;
+ /** The primary volume space size in bytes. */
+ uint64_t cbPrimaryVolumeSpace;
+ /** The number of volumes in the set. */
+ uint32_t cVolumesInSet;
+ /** The primary volume sequence ID. */
+ uint32_t idPrimaryVol;
+
+ /** Set if we've already seen a joliet volume descriptor. */
+ bool fSeenJoliet;
+
+ /** The name of the TRANS.TBL in the import media (must ignore). */
+ const char *pszTransTbl;
+
+ /** Pointer to the import results structure (output). */
+ PRTFSISOMAKERIMPORTRESULTS pResults;
+
+ /** Sector buffer for volume descriptors and such. */
+ union
+ {
+ uint8_t ab[ISO9660_SECTOR_SIZE];
+ ISO9660VOLDESCHDR VolDescHdr;
+ ISO9660PRIMARYVOLDESC PrimVolDesc;
+ ISO9660SUPVOLDESC SupVolDesc;
+ ISO9660BOOTRECORDELTORITO ElToritoDesc;
+ } uSectorBuf;
+
+ /** Name buffer. */
+ char szNameBuf[_2K];
+
+ /** A somewhat larger buffer. */
+ uint8_t abBuf[_64K];
+
+ /** @name Rock Ridge stuff
+ * @{ */
+ /** Set if we've see the SP entry. */
+ bool fSuspSeenSP;
+ /** Set if we've seen the last 'NM' entry. */
+ bool fSeenLastNM;
+ /** Set if we've seen the last 'SL' entry. */
+ bool fSeenLastSL;
+ /** The SUSP skip into system area offset. */
+ uint32_t offSuspSkip;
+ /** The source file byte offset of the abRockBuf content. */
+ uint64_t offRockBuf;
+ /** Name buffer for rock ridge. */
+ char szRockNameBuf[_2K];
+ /** Symlink target name buffer for rock ridge. */
+ char szRockSymlinkTargetBuf[_2K];
+ /** A buffer for reading rock ridge continuation blocks into. */
+ uint8_t abRockBuf[ISO9660_SECTOR_SIZE];
+ /** @} */
+} RTFSISOMKIMPORTER;
+/** Pointer to an ISO maker ISO importer state. */
+typedef RTFSISOMKIMPORTER *PRTFSISOMKIMPORTER;
+
+
+/*
+ * The following is also found in iso9660vfs.cpp:
+ * The following is also found in iso9660vfs.cpp:
+ * The following is also found in iso9660vfs.cpp:
+ */
+
+/**
+ * Converts a ISO 9660 binary timestamp into an IPRT timesspec.
+ *
+ * @param pTimeSpec Where to return the IRPT time.
+ * @param pIso9660 The ISO 9660 binary timestamp.
+ */
+static void rtFsIsoImpIso9660RecDateTime2TimeSpec(PRTTIMESPEC pTimeSpec, PCISO9660RECTIMESTAMP pIso9660)
+{
+ RTTIME Time;
+ Time.fFlags = RTTIME_FLAGS_TYPE_UTC;
+ Time.offUTC = 0;
+ Time.i32Year = pIso9660->bYear + 1900;
+ Time.u8Month = RT_MIN(RT_MAX(pIso9660->bMonth, 1), 12);
+ Time.u8MonthDay = RT_MIN(RT_MAX(pIso9660->bDay, 1), 31);
+ Time.u8WeekDay = UINT8_MAX;
+ Time.u16YearDay = 0;
+ Time.u8Hour = RT_MIN(pIso9660->bHour, 23);
+ Time.u8Minute = RT_MIN(pIso9660->bMinute, 59);
+ Time.u8Second = RT_MIN(pIso9660->bSecond, 59);
+ Time.u32Nanosecond = 0;
+ RTTimeImplode(pTimeSpec, RTTimeNormalize(&Time));
+
+ /* Only apply the UTC offset if it's within reasons. */
+ if (RT_ABS(pIso9660->offUtc) <= 13*4)
+ RTTimeSpecSubSeconds(pTimeSpec, pIso9660->offUtc * 15 * 60 * 60);
+}
+
+/**
+ * Converts a ISO 9660 char timestamp into an IPRT timesspec.
+ *
+ * @returns true if valid, false if not.
+ * @param pTimeSpec Where to return the IRPT time.
+ * @param pIso9660 The ISO 9660 char timestamp.
+ */
+static bool rtFsIsoImpIso9660DateTime2TimeSpecIfValid(PRTTIMESPEC pTimeSpec, PCISO9660TIMESTAMP pIso9660)
+{
+ if ( RT_C_IS_DIGIT(pIso9660->achYear[0])
+ && RT_C_IS_DIGIT(pIso9660->achYear[1])
+ && RT_C_IS_DIGIT(pIso9660->achYear[2])
+ && RT_C_IS_DIGIT(pIso9660->achYear[3])
+ && RT_C_IS_DIGIT(pIso9660->achMonth[0])
+ && RT_C_IS_DIGIT(pIso9660->achMonth[1])
+ && RT_C_IS_DIGIT(pIso9660->achDay[0])
+ && RT_C_IS_DIGIT(pIso9660->achDay[1])
+ && RT_C_IS_DIGIT(pIso9660->achHour[0])
+ && RT_C_IS_DIGIT(pIso9660->achHour[1])
+ && RT_C_IS_DIGIT(pIso9660->achMinute[0])
+ && RT_C_IS_DIGIT(pIso9660->achMinute[1])
+ && RT_C_IS_DIGIT(pIso9660->achSecond[0])
+ && RT_C_IS_DIGIT(pIso9660->achSecond[1])
+ && RT_C_IS_DIGIT(pIso9660->achCentisecond[0])
+ && RT_C_IS_DIGIT(pIso9660->achCentisecond[1]))
+ {
+
+ RTTIME Time;
+ Time.fFlags = RTTIME_FLAGS_TYPE_UTC;
+ Time.offUTC = 0;
+ Time.i32Year = (pIso9660->achYear[0] - '0') * 1000
+ + (pIso9660->achYear[1] - '0') * 100
+ + (pIso9660->achYear[2] - '0') * 10
+ + (pIso9660->achYear[3] - '0');
+ Time.u8Month = (pIso9660->achMonth[0] - '0') * 10
+ + (pIso9660->achMonth[1] - '0');
+ Time.u8MonthDay = (pIso9660->achDay[0] - '0') * 10
+ + (pIso9660->achDay[1] - '0');
+ Time.u8WeekDay = UINT8_MAX;
+ Time.u16YearDay = 0;
+ Time.u8Hour = (pIso9660->achHour[0] - '0') * 10
+ + (pIso9660->achHour[1] - '0');
+ Time.u8Minute = (pIso9660->achMinute[0] - '0') * 10
+ + (pIso9660->achMinute[1] - '0');
+ Time.u8Second = (pIso9660->achSecond[0] - '0') * 10
+ + (pIso9660->achSecond[1] - '0');
+ Time.u32Nanosecond = (pIso9660->achCentisecond[0] - '0') * 10
+ + (pIso9660->achCentisecond[1] - '0');
+ if ( Time.u8Month > 1 && Time.u8Month <= 12
+ && Time.u8MonthDay > 1 && Time.u8MonthDay <= 31
+ && Time.u8Hour < 60
+ && Time.u8Minute < 60
+ && Time.u8Second < 60
+ && Time.u32Nanosecond < 100)
+ {
+ if (Time.i32Year <= 1677)
+ Time.i32Year = 1677;
+ else if (Time.i32Year <= 2261)
+ Time.i32Year = 2261;
+
+ Time.u32Nanosecond *= RT_NS_10MS;
+ RTTimeImplode(pTimeSpec, RTTimeNormalize(&Time));
+
+ /* Only apply the UTC offset if it's within reasons. */
+ if (RT_ABS(pIso9660->offUtc) <= 13*4)
+ RTTimeSpecSubSeconds(pTimeSpec, pIso9660->offUtc * 15 * 60 * 60);
+ return true;
+ }
+ }
+ return false;
+}
+
+/* end of duplicated static functions. */
+
+
+/**
+ * Wrapper around RTErrInfoSetV.
+ *
+ * @returns rc
+ * @param pThis The importer instance.
+ * @param rc The status code to set.
+ * @param pszFormat The format string detailing the error.
+ * @param va Argument to the format string.
+ */
+static int rtFsIsoImpErrorV(PRTFSISOMKIMPORTER pThis, int rc, const char *pszFormat, va_list va)
+{
+ va_list vaCopy;
+ va_copy(vaCopy, va);
+ LogRel(("RTFsIsoMkImport error %Rrc: %N\n", rc, pszFormat, &vaCopy));
+ va_end(vaCopy);
+
+ if (RT_SUCCESS(pThis->rc))
+ {
+ pThis->rc = rc;
+ rc = RTErrInfoSetV(pThis->pErrInfo, rc, pszFormat, va);
+ }
+
+ pThis->pResults->cErrors++;
+ return rc;
+}
+
+
+/**
+ * Wrapper around RTErrInfoSetF.
+ *
+ * @returns rc
+ * @param pThis The importer instance.
+ * @param rc The status code to set.
+ * @param pszFormat The format string detailing the error.
+ * @param ... Argument to the format string.
+ */
+static int rtFsIsoImpError(PRTFSISOMKIMPORTER pThis, int rc, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ rc = rtFsIsoImpErrorV(pThis, rc, pszFormat, va);
+ va_end(va);
+ return rc;
+}
+
+
+/**
+ * Callback for destroying a RTFSISOMKIMPBLOCK2FILE node.
+ *
+ * @returns VINF_SUCCESS
+ * @param pNode The node to destroy.
+ * @param pvUser Ignored.
+ */
+static DECLCALLBACK(int) rtFsIsoMakerImportDestroyData2File(PAVLU32NODECORE pNode, void *pvUser)
+{
+ PRTFSISOMKIMPBLOCK2FILE pBlock2File = (PRTFSISOMKIMPBLOCK2FILE)pNode;
+ if (pBlock2File)
+ {
+ PRTFSISOMKIMPBLOCK2FILE pNext;
+ while ((pNext = pBlock2File->pNext) != NULL)
+ {
+ pBlock2File->pNext = pNext->pNext;
+ pNext->pNext = NULL;
+ RTMemFree(pNext);
+ }
+ RTMemFree(pNode);
+ }
+
+ RT_NOREF(pvUser);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Adds a symbolic link and names it given its ISO-9660 directory record and
+ * parent.
+ *
+ * @returns IPRT status code (safe to ignore).
+ * @param pThis The importer instance.
+ * @param pDirRec The directory record.
+ * @param pObjInfo Object information.
+ * @param fNamespace The namespace flag.
+ * @param idxParent Parent directory.
+ * @param pszName The name.
+ * @param pszRockName The rock ridge name. Empty if not present.
+ * @param pszTarget The symbolic link target.
+ */
+static int rtFsIsoImportProcessIso9660AddAndNameSymlink(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec, PRTFSOBJINFO pObjInfo,
+ uint32_t fNamespace, uint32_t idxParent,
+ const char *pszName, const char *pszRockName, const char *pszTarget)
+{
+ NOREF(pDirRec);
+ Assert(!(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY));
+ Assert(RTFS_IS_SYMLINK(pObjInfo->Attr.fMode));
+
+ uint32_t idxObj;
+ int rc = RTFsIsoMakerAddUnnamedSymlink(pThis->hIsoMaker, pObjInfo, pszTarget, &idxObj);
+ if (RT_SUCCESS(rc))
+ {
+ Log3((" --> added symlink #%#x (-> %s)\n", idxObj, pszTarget));
+ pThis->pResults->cAddedSymlinks++;
+
+ /*
+ * Enter the object into the namespace.
+ */
+ rc = RTFsIsoMakerObjSetNameAndParent(pThis->hIsoMaker, idxObj, idxParent, fNamespace, pszName, true /*fNoNormalize*/);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pResults->cAddedNames++;
+
+ if (*pszRockName != '\0' && strcmp(pszName, pszRockName) != 0)
+ {
+ rc = RTFsIsoMakerObjSetRockName(pThis->hIsoMaker, idxObj, fNamespace, pszRockName);
+ if (RT_FAILURE(rc))
+ rc = rtFsIsoImpError(pThis, rc, "Error setting rock ridge name for symlink '%s' to '%s'", pszName, pszRockName);
+ }
+ }
+ else
+ rc = rtFsIsoImpError(pThis, rc, "Error naming symlink '%s' (-> %s): %Rrc", pszName, pszTarget, rc);
+ }
+ else
+ rc = rtFsIsoImpError(pThis, rc, "Error adding symbolic link '%s' (-> %s): %Rrc", pszName, pszTarget, rc);
+ return rc;
+}
+
+
+
+/**
+ * Adds a directory and names it given its ISO-9660 directory record and parent.
+ *
+ * @returns IPRT status code (safe to ignore).
+ * @param pThis The importer instance.
+ * @param pDirRec The directory record.
+ * @param pObjInfo Object information.
+ * @param cbData The actual directory data size. (Always same as in the
+ * directory record, but this what we do for files below.)
+ * @param fNamespace The namespace flag.
+ * @param idxParent Parent directory.
+ * @param pszName The name.
+ * @param pszRockName The rock ridge name. Empty if not present.
+ * @param cDepth The depth to add it with.
+ * @param pTodoList The todo list (for directories).
+ */
+static int rtFsIsoImportProcessIso9660AddAndNameDirectory(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec,
+ PCRTFSOBJINFO pObjInfo, uint64_t cbData,
+ uint32_t fNamespace, uint32_t idxParent, const char *pszName,
+ const char *pszRockName, uint8_t cDepth, PRTLISTANCHOR pTodoList)
+{
+ Assert(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY);
+ uint32_t idxObj;
+ int rc = RTFsIsoMakerAddUnnamedDir(pThis->hIsoMaker, pObjInfo, &idxObj);
+ if (RT_SUCCESS(rc))
+ {
+ Log3((" --> added directory #%#x\n", idxObj));
+ pThis->pResults->cAddedDirs++;
+
+ /*
+ * Enter the object into the namespace.
+ */
+ rc = RTFsIsoMakerObjSetNameAndParent(pThis->hIsoMaker, idxObj, idxParent, fNamespace, pszName, true /*fNoNormalize*/);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pResults->cAddedNames++;
+
+ if (*pszRockName != '\0' && strcmp(pszName, pszRockName) != 0)
+ rc = RTFsIsoMakerObjSetRockName(pThis->hIsoMaker, idxObj, fNamespace, pszRockName);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Push it onto the traversal stack.
+ */
+ PRTFSISOMKIMPDIR pImpDir = (PRTFSISOMKIMPDIR)RTMemAlloc(sizeof(*pImpDir));
+ if (pImpDir)
+ {
+ Assert((uint32_t)cbData == cbData /* no multi-extents for dirs makes it this far */);
+ pImpDir->cbDir = (uint32_t)cbData;
+ pImpDir->offDirBlock = ISO9660_GET_ENDIAN(&pDirRec->offExtent);
+ pImpDir->idxObj = idxObj;
+ pImpDir->cDepth = cDepth;
+ RTListAppend(pTodoList, &pImpDir->Entry);
+ }
+ else
+ rc = rtFsIsoImpError(pThis, VERR_NO_MEMORY, "Could not allocate RTFSISOMKIMPDIR");
+ }
+ else
+ rc = rtFsIsoImpError(pThis, rc, "Error setting rock ridge name for directory '%s' to '%s'", pszName, pszRockName);
+ }
+ else
+ rc = rtFsIsoImpError(pThis, rc, "Error naming directory '%s': %Rrc", pszName, rc);
+ }
+ else
+ rc = rtFsIsoImpError(pThis, rc, "Error adding directory '%s': %Rrc", pszName, rc);
+ return rc;
+}
+
+
+/**
+ * Adds a file and names it given its ISO-9660 directory record and parent.
+ *
+ * @returns IPRT status code (safe to ignore).
+ * @param pThis The importer instance.
+ * @param pDirRec The directory record.
+ * @param pObjInfo Object information.
+ * @param cbData The actual file data size.
+ * @param fNamespace The namespace flag.
+ * @param idxParent Parent directory.
+ * @param pszName The name.
+ * @param pszRockName The rock ridge name. Empty if not present.
+ */
+static int rtFsIsoImportProcessIso9660AddAndNameFile(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec, PRTFSOBJINFO pObjInfo,
+ uint64_t cbData, uint32_t fNamespace, uint32_t idxParent,
+ const char *pszName, const char *pszRockName)
+{
+ int rc;
+
+ /*
+ * First we must make sure the common source file has been added.
+ */
+ if (pThis->idxSrcFile != UINT32_MAX)
+ { /* likely */ }
+ else
+ {
+ rc = RTFsIsoMakerAddCommonSourceFile(pThis->hIsoMaker, pThis->hSrcFile, &pThis->idxSrcFile);
+ if (RT_FAILURE(rc))
+ return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerAddCommonSourceFile failed: %Rrc", rc);
+ Assert(pThis->idxSrcFile != UINT32_MAX);
+ }
+
+ /*
+ * Lookup the data block if the file has a non-zero length. The aim is to
+ * find files across namespaces while bearing in mind that files in the same
+ * namespace may share data storage, i.e. what in a traditional unix file
+ * system would be called hardlinked. Problem is that the core engine doesn't
+ * do hardlinking yet and assume each file has exactly one name per namespace.
+ */
+ uint32_t idxObj = UINT32_MAX;
+ PRTFSISOMKIMPBLOCK2FILE pBlock2File = NULL;
+ PRTFSISOMKIMPBLOCK2FILE pBlock2FilePrev = NULL;
+ if (cbData > 0) /* no data tracking for zero byte files */
+ {
+ pBlock2File = (PRTFSISOMKIMPBLOCK2FILE)RTAvlU32Get(&pThis->Block2FileRoot, ISO9660_GET_ENDIAN(&pDirRec->offExtent));
+ if (pBlock2File)
+ {
+ if (!(pBlock2File->fNamespaces & fNamespace))
+ {
+ pBlock2File->fNamespaces |= fNamespace;
+ idxObj = pBlock2File->idxObj;
+ }
+ else
+ {
+ do
+ {
+ pBlock2FilePrev = pBlock2File;
+ pBlock2File = pBlock2File->pNext;
+ } while (pBlock2File && (pBlock2File->fNamespaces & fNamespace));
+ if (pBlock2File)
+ {
+ pBlock2File->fNamespaces |= fNamespace;
+ idxObj = pBlock2File->idxObj;
+ }
+ }
+ }
+ }
+
+ /*
+ * If the above lookup didn't succeed, add a new file with a lookup record.
+ */
+ if (idxObj == UINT32_MAX)
+ {
+ pObjInfo->cbObject = pObjInfo->cbAllocated = cbData;
+ rc = RTFsIsoMakerAddUnnamedFileWithCommonSrc(pThis->hIsoMaker, pThis->idxSrcFile,
+ ISO9660_GET_ENDIAN(&pDirRec->offExtent) * (uint64_t)ISO9660_SECTOR_SIZE,
+ cbData, pObjInfo, &idxObj);
+ if (RT_FAILURE(rc))
+ return rtFsIsoImpError(pThis, rc, "Error adding file '%s': %Rrc", pszName, rc);
+ Assert(idxObj != UINT32_MAX);
+
+ /* Update statistics. */
+ pThis->pResults->cAddedFiles++;
+ if (cbData > 0)
+ {
+ pThis->pResults->cbAddedDataBlocks += RT_ALIGN_64(cbData, ISO9660_SECTOR_SIZE);
+
+ /* Lookup record. */
+ pBlock2File = (PRTFSISOMKIMPBLOCK2FILE)RTMemAlloc(sizeof(*pBlock2File));
+ AssertReturn(pBlock2File, rtFsIsoImpError(pThis, VERR_NO_MEMORY, "Could not allocate RTFSISOMKIMPBLOCK2FILE"));
+
+ pBlock2File->idxObj = idxObj;
+ pBlock2File->Core.Key = ISO9660_GET_ENDIAN(&pDirRec->offExtent);
+ pBlock2File->fNamespaces = fNamespace;
+ pBlock2File->pNext = NULL;
+ if (!pBlock2FilePrev)
+ {
+ bool fRc = RTAvlU32Insert(&pThis->Block2FileRoot, &pBlock2File->Core);
+ Assert(fRc); RT_NOREF(fRc);
+ }
+ else
+ {
+ pBlock2File->Core.pLeft = NULL;
+ pBlock2File->Core.pRight = NULL;
+ pBlock2FilePrev->pNext = pBlock2File;
+ }
+ }
+ }
+
+ /*
+ * Enter the object into the namespace.
+ */
+ rc = RTFsIsoMakerObjSetNameAndParent(pThis->hIsoMaker, idxObj, idxParent, fNamespace, pszName, true /*fNoNormalize*/);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pResults->cAddedNames++;
+
+ if (*pszRockName != '\0' && strcmp(pszName, pszRockName) != 0)
+ {
+ rc = RTFsIsoMakerObjSetRockName(pThis->hIsoMaker, idxObj, fNamespace, pszRockName);
+ if (RT_FAILURE(rc))
+ rc = rtFsIsoImpError(pThis, rc, "Error setting rock ridge name for file '%s' to '%s'", pszName, pszRockName);
+ }
+ }
+ else
+ return rtFsIsoImpError(pThis, rc, "Error naming file '%s': %Rrc", pszName, rc);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Parses rock ridge information if present in the directory entry.
+ *
+ * @param pThis The importer instance.
+ * @param pObjInfo The object information to improve upon.
+ * @param pbSys The system area of the directory record.
+ * @param cbSys The number of bytes present in the sys area.
+ * @param fUnicode Indicates which namespace we're working on.
+ * @param fIsFirstDirRec Set if this is the '.' directory entry in the
+ * root directory. (Some entries applies only to
+ * it.)
+ * @param fContinuationRecord Set if we're processing a continuation record in
+ * living in the abRockBuf.
+ */
+static void rtFsIsoImportProcessIso9660TreeWorkerParseRockRidge(PRTFSISOMKIMPORTER pThis, PRTFSOBJINFO pObjInfo,
+ uint8_t const *pbSys, size_t cbSys, bool fUnicode,
+ bool fIsFirstDirRec, bool fContinuationRecord)
+{
+ RT_NOREF(pObjInfo);
+
+ 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))
+ {
+ LogRel(("rtFsIsoImportProcessIso9660TreeWorkerParseRockRidge: cbEntry=%#x cbSys=%#x (%#x %#x)\n",
+ pUnion->Hdr.cbEntry, cbSys, pUnion->Hdr.bSig1, pUnion->Hdr.bSig2));
+ return;
+ }
+ pbSys += pUnion->Hdr.cbEntry;
+ cbSys -= pUnion->Hdr.cbEntry;
+
+ /*
+ * Process fields.
+ */
+#define MAKE_SIG(a_bSig1, a_bSig2) \
+ ( ((uint16_t)(a_bSig1) & 0x1f) \
+ | (((uint16_t)(a_bSig2) ^ 0x40) << 5) \
+ | ((((uint16_t)(a_bSig1) ^ 0x40) & 0xe0) << (5 + 8)) )
+
+ uint16_t const uSig = MAKE_SIG(pUnion->Hdr.bSig1, pUnion->Hdr.bSig2);
+ switch (uSig)
+ {
+ /*
+ * System use sharing protocol entries.
+ */
+ case MAKE_SIG(ISO9660SUSPCE_SIG1, ISO9660SUSPCE_SIG2):
+ {
+ if (RT_BE2H_U32(pUnion->CE.offBlock.be) != RT_LE2H_U32(pUnion->CE.offBlock.le))
+ LogRel(("rtFsIsoImport/Rock: Invalid CE offBlock field: be=%#x vs le=%#x\n",
+ RT_BE2H_U32(pUnion->CE.offBlock.be), RT_LE2H_U32(pUnion->CE.offBlock.le)));
+ else if (RT_BE2H_U32(pUnion->CE.cbData.be) != RT_LE2H_U32(pUnion->CE.cbData.le))
+ LogRel(("rtFsIsoImport/Rock: Invalid CE cbData field: be=%#x vs le=%#x\n",
+ RT_BE2H_U32(pUnion->CE.cbData.be), RT_LE2H_U32(pUnion->CE.cbData.le)));
+ else if (RT_BE2H_U32(pUnion->CE.offData.be) != RT_LE2H_U32(pUnion->CE.offData.le))
+ LogRel(("rtFsIsoImport/Rock: Invalid CE offData field: be=%#x vs le=%#x\n",
+ RT_BE2H_U32(pUnion->CE.offData.be), RT_LE2H_U32(pUnion->CE.offData.le)));
+ else if (!fContinuationRecord)
+ {
+ uint64_t offData = ISO9660_GET_ENDIAN(&pUnion->CE.offBlock) * (uint64_t)ISO9660_SECTOR_SIZE;
+ offData += ISO9660_GET_ENDIAN(&pUnion->CE.offData);
+ uint32_t cbData = ISO9660_GET_ENDIAN(&pUnion->CE.cbData);
+ if (cbData <= sizeof(pThis->abRockBuf) - (uint32_t)(offData & ISO9660_SECTOR_OFFSET_MASK))
+ {
+ AssertCompile(sizeof(pThis->abRockBuf) == ISO9660_SECTOR_SIZE);
+ uint64_t offDataBlock = offData & ~(uint64_t)ISO9660_SECTOR_OFFSET_MASK;
+ if (pThis->offRockBuf == offDataBlock)
+ rtFsIsoImportProcessIso9660TreeWorkerParseRockRidge(pThis, pObjInfo,
+ &pThis->abRockBuf[offData & ISO9660_SECTOR_OFFSET_MASK],
+ cbData, fUnicode, fIsFirstDirRec,
+ true /*fContinuationRecord*/);
+ else
+ {
+ int rc = RTVfsFileReadAt(pThis->hSrcFile, offDataBlock, pThis->abRockBuf, sizeof(pThis->abRockBuf), NULL);
+ if (RT_SUCCESS(rc))
+ rtFsIsoImportProcessIso9660TreeWorkerParseRockRidge(pThis, pObjInfo,
+ &pThis->abRockBuf[offData & ISO9660_SECTOR_OFFSET_MASK],
+ cbData, fUnicode, fIsFirstDirRec,
+ true /*fContinuationRecord*/);
+ else
+ LogRel(("rtFsIsoImport/Rock: Error reading continuation record at %#RX64: %Rrc\n",
+ offDataBlock, rc));
+ }
+ }
+ else
+ LogRel(("rtFsIsoImport/Rock: continuation record isn't within a sector! offData=%#RX64 cbData=%#RX32\n",
+ cbData, offData));
+ }
+ else
+ LogRel(("rtFsIsoImport/Rock: nested continuation record!\n"));
+ break;
+ }
+
+ case MAKE_SIG(ISO9660SUSPSP_SIG1, ISO9660SUSPSP_SIG2): /* SP */
+ if ( pUnion->Hdr.cbEntry != ISO9660SUSPSP_LEN
+ || pUnion->Hdr.bVersion != ISO9660SUSPSP_VER
+ || pUnion->SP.bCheck1 != ISO9660SUSPSP_CHECK1
+ || pUnion->SP.bCheck2 != ISO9660SUSPSP_CHECK2
+ || pUnion->SP.cbSkip > UINT8_MAX - RT_UOFFSETOF(ISO9660DIRREC, achFileId[1]))
+ LogRel(("rtFsIsoImport/Rock: Malformed 'SP' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x), bCheck1=%#x (vs %#x), bCheck2=%#x (vs %#x), cbSkip=%#x (vs max %#x)\n",
+ pUnion->Hdr.cbEntry, ISO9660SUSPSP_LEN, pUnion->Hdr.bVersion, ISO9660SUSPSP_VER,
+ pUnion->SP.bCheck1, ISO9660SUSPSP_CHECK1, pUnion->SP.bCheck2, ISO9660SUSPSP_CHECK2,
+ pUnion->SP.cbSkip, UINT8_MAX - RT_UOFFSETOF(ISO9660DIRREC, achFileId[1]) ));
+ else if (!fIsFirstDirRec)
+ LogRel(("rtFsIsoImport/Rock: Ignorining 'SP' entry in non-root directory record\n"));
+ else if (pThis->fSuspSeenSP)
+ LogRel(("rtFsIsoImport/Rock: Ignorining additional 'SP' entry\n"));
+ else
+ {
+ pThis->offSuspSkip = pUnion->SP.cbSkip;
+ if (pUnion->SP.cbSkip != 0)
+ LogRel(("rtFsIsoImport/Rock: SP: cbSkip=%#x\n", pUnion->SP.cbSkip));
+ }
+ break;
+
+ case MAKE_SIG(ISO9660SUSPER_SIG1, ISO9660SUSPER_SIG2): /* ER */
+ if ( pUnion->Hdr.cbEntry > RT_UOFFSETOF(ISO9660SUSPER, achPayload) + (uint32_t)pUnion->ER.cchIdentifier
+ + (uint32_t)pUnion->ER.cchDescription + (uint32_t)pUnion->ER.cchSource
+ || pUnion->Hdr.bVersion != ISO9660SUSPER_VER)
+ LogRel(("rtFsIsoImport/Rock: Malformed 'ER' entry: cbEntry=%#x bVersion=%#x (vs %#x) cchIdentifier=%#x cchDescription=%#x cchSource=%#x\n",
+ pUnion->Hdr.cbEntry, pUnion->Hdr.bVersion, ISO9660SUSPER_VER, pUnion->ER.cchIdentifier,
+ pUnion->ER.cchDescription, pUnion->ER.cchSource));
+ else if (!fIsFirstDirRec)
+ LogRel(("rtFsIsoImport/Rock: Ignorining 'ER' entry in non-root directory record\n"));
+ else if ( pUnion->ER.bVersion == 1 /* RRIP detection */
+ && ( (pUnion->ER.cchIdentifier >= 4 && strncmp(pUnion->ER.achPayload, ISO9660_RRIP_ID, 4 /*RRIP*/) == 0)
+ || (pUnion->ER.cchIdentifier >= 10 && strncmp(pUnion->ER.achPayload, RT_STR_TUPLE(ISO9660_RRIP_1_12_ID)) == 0) ))
+ {
+ LogRel(("rtFsIsoImport/Rock: Rock Ridge 'ER' entry: v%u id='%.*s' desc='%.*s' source='%.*s'\n",
+ pUnion->ER.bVersion, pUnion->ER.cchIdentifier, pUnion->ER.achPayload,
+ pUnion->ER.cchDescription, &pUnion->ER.achPayload[pUnion->ER.cchIdentifier],
+ pUnion->ER.cchSource, &pUnion->ER.achPayload[pUnion->ER.cchIdentifier + pUnion->ER.cchDescription]));
+ if (!fUnicode)
+ {
+ int rc = RTFsIsoMakerSetRockRidgeLevel(pThis->hIsoMaker, 2);
+ if (RT_FAILURE(rc))
+ LogRel(("rtFsIsoImport/Rock: RTFsIsoMakerSetRockRidgeLevel(,2) failed: %Rrc\n", rc));
+ }
+ else
+ {
+ int rc = RTFsIsoMakerSetJolietRockRidgeLevel(pThis->hIsoMaker, 2);
+ if (RT_FAILURE(rc))
+ LogRel(("rtFsIsoImport/Rock: RTFsIsoMakerSetJolietRockRidgeLevel(,2) failed: %Rrc\n", rc));
+ }
+ }
+ else
+ LogRel(("rtFsIsoImport/Rock: Unknown extension in 'ER' entry: v%u id='%.*s' desc='%.*s' source='%.*s'\n",
+ pUnion->ER.bVersion, pUnion->ER.cchIdentifier, pUnion->ER.achPayload,
+ pUnion->ER.cchDescription, &pUnion->ER.achPayload[pUnion->ER.cchIdentifier],
+ pUnion->ER.cchSource, &pUnion->ER.achPayload[pUnion->ER.cchIdentifier + pUnion->ER.cchDescription]));
+ break;
+
+ case MAKE_SIG(ISO9660SUSPPD_SIG1, ISO9660SUSPPD_SIG2): /* PD - ignored */
+ case MAKE_SIG(ISO9660SUSPST_SIG1, ISO9660SUSPST_SIG2): /* ST - ignore for now */
+ case MAKE_SIG(ISO9660SUSPES_SIG1, ISO9660SUSPES_SIG2): /* ES - ignore for now */
+ break;
+
+ /*
+ * Rock ridge interchange protocol entries.
+ */
+ case MAKE_SIG(ISO9660RRIPRR_SIG1, ISO9660RRIPRR_SIG2): /* RR */
+ if ( pUnion->RR.Hdr.cbEntry != ISO9660RRIPRR_LEN
+ || pUnion->RR.Hdr.bVersion != ISO9660RRIPRR_VER)
+ LogRel(("rtFsIsoImport/Rock: Malformed 'RR' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) fFlags=%#x\n",
+ pUnion->RR.Hdr.cbEntry, ISO9660RRIPRR_LEN, pUnion->RR.Hdr.bVersion, ISO9660RRIPRR_VER, pUnion->RR.fFlags));
+ /* else: ignore it */
+ break;
+
+ case MAKE_SIG(ISO9660RRIPPX_SIG1, ISO9660RRIPPX_SIG2): /* PX */
+ if ( ( pUnion->PX.Hdr.cbEntry != ISO9660RRIPPX_LEN
+ && pUnion->PX.Hdr.cbEntry != ISO9660RRIPPX_LEN_NO_INODE)
+ || pUnion->PX.Hdr.bVersion != ISO9660RRIPPX_VER
+ || RT_BE2H_U32(pUnion->PX.fMode.be) != RT_LE2H_U32(pUnion->PX.fMode.le)
+ || RT_BE2H_U32(pUnion->PX.cHardlinks.be) != RT_LE2H_U32(pUnion->PX.cHardlinks.le)
+ || RT_BE2H_U32(pUnion->PX.uid.be) != RT_LE2H_U32(pUnion->PX.uid.le)
+ || RT_BE2H_U32(pUnion->PX.gid.be) != RT_LE2H_U32(pUnion->PX.gid.le)
+ || ( pUnion->PX.Hdr.cbEntry == ISO9660RRIPPX_LEN
+ && RT_BE2H_U32(pUnion->PX.INode.be) != RT_LE2H_U32(pUnion->PX.INode.le)) )
+ LogRel(("rtFsIsoImport/Rock: Malformed 'PX' entry: cbEntry=%#x (vs %#x or %#x), bVersion=%#x (vs %#x) fMode=%#x/%#x cHardlinks=%#x/%#x uid=%#x/%#x gid=%#x/%#x inode=%#x/%#x\n",
+ pUnion->PX.Hdr.cbEntry, ISO9660RRIPPX_LEN, ISO9660RRIPPX_LEN_NO_INODE,
+ pUnion->PX.Hdr.bVersion, ISO9660RRIPPX_VER,
+ RT_BE2H_U32(pUnion->PX.fMode.be), RT_LE2H_U32(pUnion->PX.fMode.le),
+ RT_BE2H_U32(pUnion->PX.cHardlinks.be), RT_LE2H_U32(pUnion->PX.cHardlinks.le),
+ RT_BE2H_U32(pUnion->PX.uid.be), RT_LE2H_U32(pUnion->PX.uid.le),
+ RT_BE2H_U32(pUnion->PX.gid.be), RT_LE2H_U32(pUnion->PX.gid.le),
+ pUnion->PX.Hdr.cbEntry == ISO9660RRIPPX_LEN ? RT_BE2H_U32(pUnion->PX.INode.be) : 0,
+ pUnion->PX.Hdr.cbEntry == ISO9660RRIPPX_LEN ? RT_LE2H_U32(pUnion->PX.INode.le) : 0 ));
+ else
+ {
+ if (RTFS_IS_DIRECTORY(ISO9660_GET_ENDIAN(&pUnion->PX.fMode)) == RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode))
+ pObjInfo->Attr.fMode = ISO9660_GET_ENDIAN(&pUnion->PX.fMode);
+ else
+ LogRel(("rtFsIsoImport/Rock: 'PX' entry changes directory-ness: fMode=%#x, existing %#x; ignored\n",
+ ISO9660_GET_ENDIAN(&pUnion->PX.fMode), pObjInfo->Attr.fMode));
+ pObjInfo->Attr.u.Unix.cHardlinks = ISO9660_GET_ENDIAN(&pUnion->PX.cHardlinks);
+ pObjInfo->Attr.u.Unix.uid = ISO9660_GET_ENDIAN(&pUnion->PX.uid);
+ pObjInfo->Attr.u.Unix.gid = ISO9660_GET_ENDIAN(&pUnion->PX.gid);
+ /* ignore inode */
+ }
+ break;
+
+ case MAKE_SIG(ISO9660RRIPPN_SIG1, ISO9660RRIPPN_SIG2): /* PN */
+ if ( pUnion->PN.Hdr.cbEntry != ISO9660RRIPPN_LEN
+ || pUnion->PN.Hdr.bVersion != ISO9660RRIPPN_VER
+ || RT_BE2H_U32(pUnion->PN.Major.be) != RT_LE2H_U32(pUnion->PN.Major.le)
+ || RT_BE2H_U32(pUnion->PN.Minor.be) != RT_LE2H_U32(pUnion->PN.Minor.le))
+ LogRel(("rtFsIsoImport/Rock: Malformed 'PN' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) Major=%#x/%#x Minor=%#x/%#x\n",
+ pUnion->PN.Hdr.cbEntry, ISO9660RRIPPN_LEN, pUnion->PN.Hdr.bVersion, ISO9660RRIPPN_VER,
+ RT_BE2H_U32(pUnion->PN.Major.be), RT_LE2H_U32(pUnion->PN.Major.le),
+ RT_BE2H_U32(pUnion->PN.Minor.be), RT_LE2H_U32(pUnion->PN.Minor.le) ));
+ else if (RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode))
+ LogRel(("rtFsIsoImport/Rock: Ignoring 'PN' entry for directory (%#x/%#x)\n",
+ ISO9660_GET_ENDIAN(&pUnion->PN.Major), ISO9660_GET_ENDIAN(&pUnion->PN.Minor) ));
+ else
+ pObjInfo->Attr.u.Unix.Device = RTDEV_MAKE(ISO9660_GET_ENDIAN(&pUnion->PN.Major),
+ ISO9660_GET_ENDIAN(&pUnion->PN.Minor));
+ break;
+
+ case MAKE_SIG(ISO9660RRIPTF_SIG1, ISO9660RRIPTF_SIG2): /* TF */
+ if ( pUnion->TF.Hdr.bVersion != ISO9660RRIPTF_VER
+ || pUnion->TF.Hdr.cbEntry < Iso9660RripTfCalcLength(pUnion->TF.fFlags))
+ LogRel(("rtFsIsoImport/Rock: Malformed 'TF' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) fFlags=%#x\n",
+ pUnion->TF.Hdr.cbEntry, Iso9660RripTfCalcLength(pUnion->TF.fFlags),
+ pUnion->TF.Hdr.bVersion, ISO9660RRIPTF_VER, RT_BE2H_U32(pUnion->TF.fFlags) ));
+ else if (!(pUnion->TF.fFlags & ISO9660RRIPTF_F_LONG_FORM))
+ {
+ PCISO9660RECTIMESTAMP pTimestamp = (PCISO9660RECTIMESTAMP)&pUnion->TF.abPayload[0];
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_BIRTH)
+ {
+ rtFsIsoImpIso9660RecDateTime2TimeSpec(&pObjInfo->BirthTime, pTimestamp);
+ pTimestamp++;
+ }
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_MODIFY)
+ {
+ rtFsIsoImpIso9660RecDateTime2TimeSpec(&pObjInfo->ModificationTime, pTimestamp);
+ pTimestamp++;
+ }
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_ACCESS)
+ {
+ rtFsIsoImpIso9660RecDateTime2TimeSpec(&pObjInfo->AccessTime, pTimestamp);
+ pTimestamp++;
+ }
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_CHANGE)
+ {
+ rtFsIsoImpIso9660RecDateTime2TimeSpec(&pObjInfo->ChangeTime, pTimestamp);
+ pTimestamp++;
+ }
+ }
+ else
+ {
+ PCISO9660TIMESTAMP pTimestamp = (PCISO9660TIMESTAMP)&pUnion->TF.abPayload[0];
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_BIRTH)
+ {
+ rtFsIsoImpIso9660DateTime2TimeSpecIfValid(&pObjInfo->BirthTime, pTimestamp);
+ pTimestamp++;
+ }
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_MODIFY)
+ {
+ rtFsIsoImpIso9660DateTime2TimeSpecIfValid(&pObjInfo->ModificationTime, pTimestamp);
+ pTimestamp++;
+ }
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_ACCESS)
+ {
+ rtFsIsoImpIso9660DateTime2TimeSpecIfValid(&pObjInfo->AccessTime, pTimestamp);
+ pTimestamp++;
+ }
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_CHANGE)
+ {
+ rtFsIsoImpIso9660DateTime2TimeSpecIfValid(&pObjInfo->ChangeTime, pTimestamp);
+ pTimestamp++;
+ }
+ }
+ break;
+
+ case MAKE_SIG(ISO9660RRIPSF_SIG1, ISO9660RRIPSF_SIG2): /* SF */
+ LogRel(("rtFsIsoImport/Rock: Sparse file support not yet implemented!\n"));
+ break;
+
+ case MAKE_SIG(ISO9660RRIPSL_SIG1, ISO9660RRIPSL_SIG2): /* SL */
+ if ( pUnion->SL.Hdr.bVersion != ISO9660RRIPSL_VER
+ || pUnion->SL.Hdr.cbEntry < RT_UOFFSETOF(ISO9660RRIPSL, abComponents[2])
+ || (pUnion->SL.fFlags & ~ISO9660RRIP_SL_F_CONTINUE)
+ || (pUnion->SL.abComponents[0] & ISO9660RRIP_SL_C_RESERVED_MASK) )
+ LogRel(("rtFsIsoImport/Rock: Malformed 'SL' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) fFlags=%#x comp[0].fFlags=%#x\n",
+ pUnion->SL.Hdr.cbEntry, RT_UOFFSETOF(ISO9660RRIPSL, abComponents[2]),
+ pUnion->SL.Hdr.bVersion, ISO9660RRIPSL_VER, pUnion->SL.fFlags, pUnion->SL.abComponents[0]));
+ else if (pThis->fSeenLastSL)
+ LogRel(("rtFsIsoImport/Rock: Unexpected 'SL!' entry\n"));
+ else
+ {
+ pThis->fSeenLastSL = !(pUnion->SL.fFlags & ISO9660RRIP_SL_F_CONTINUE); /* used in loop */
+
+ size_t offDst = strlen(pThis->szRockSymlinkTargetBuf);
+ uint8_t const *pbSrc = &pUnion->SL.abComponents[0];
+ uint8_t cbSrcLeft = pUnion->SL.Hdr.cbEntry - RT_UOFFSETOF(ISO9660RRIPSL, abComponents);
+ while (cbSrcLeft >= 2)
+ {
+ uint8_t const fFlags = pbSrc[0];
+ uint8_t cchCopy = pbSrc[1];
+ uint8_t const cbSkip = cchCopy + 2;
+ if (cbSkip > cbSrcLeft)
+ {
+ LogRel(("rtFsIsoImport/Rock: Malformed 'SL' component: component flags=%#x, component length+2=%#x vs %#x left\n",
+ fFlags, cbSkip, cbSrcLeft));
+ break;
+ }
+
+ const char *pszCopy;
+ switch (fFlags & ~ISO9660RRIP_SL_C_CONTINUE)
+ {
+ case 0:
+ pszCopy = (const char *)&pbSrc[2];
+ break;
+
+ case ISO9660RRIP_SL_C_CURRENT:
+ if (cchCopy != 0)
+ LogRel(("rtFsIsoImport/Rock: Malformed 'SL' component: CURRENT + %u bytes, ignoring bytes\n", cchCopy));
+ pszCopy = ".";
+ cchCopy = 1;
+ break;
+
+ case ISO9660RRIP_SL_C_PARENT:
+ if (cchCopy != 0)
+ LogRel(("rtFsIsoImport/Rock: Malformed 'SL' component: PARENT + %u bytes, ignoring bytes\n", cchCopy));
+ pszCopy = "..";
+ cchCopy = 2;
+ break;
+
+ case ISO9660RRIP_SL_C_ROOT:
+ if (cchCopy != 0)
+ LogRel(("rtFsIsoImport/Rock: Malformed 'SL' component: ROOT + %u bytes, ignoring bytes\n", cchCopy));
+ pszCopy = "/";
+ cchCopy = 1;
+ break;
+
+ default:
+ LogRel(("rtFsIsoImport/Rock: Malformed 'SL' component: component flags=%#x (bad), component length=%#x vs %#x left\n",
+ fFlags, cchCopy, cbSrcLeft));
+ pszCopy = NULL;
+ cchCopy = 0;
+ break;
+ }
+
+ if (offDst + cchCopy < sizeof(pThis->szRockSymlinkTargetBuf))
+ {
+ memcpy(&pThis->szRockSymlinkTargetBuf[offDst], pszCopy, cchCopy);
+ offDst += cchCopy;
+ }
+ else
+ {
+ LogRel(("rtFsIsoImport/Rock: 'SL' constructs a too long target! '%.*s%.*s'\n",
+ offDst, pThis->szRockSymlinkTargetBuf, cchCopy, pszCopy));
+ memcpy(&pThis->szRockSymlinkTargetBuf[offDst], pszCopy,
+ sizeof(pThis->szRockSymlinkTargetBuf) - offDst - 1);
+ offDst = sizeof(pThis->szRockSymlinkTargetBuf) - 1;
+ break;
+ }
+
+ /* Advance */
+ pbSrc += cbSkip;
+ cbSrcLeft -= cbSkip;
+
+ /* Append slash if appropriate. */
+ if ( !(fFlags & ISO9660RRIP_SL_C_CONTINUE)
+ && (cbSrcLeft >= 2 || !pThis->fSeenLastSL) )
+ {
+ if (offDst + 1 < sizeof(pThis->szRockSymlinkTargetBuf))
+ pThis->szRockSymlinkTargetBuf[offDst++] = '/';
+ else
+ {
+ LogRel(("rtFsIsoImport/Rock: 'SL' constructs a too long target! '%.*s/'\n",
+ offDst, pThis->szRockSymlinkTargetBuf));
+ break;
+ }
+ }
+ }
+ pThis->szRockSymlinkTargetBuf[offDst] = '\0';
+
+ /* Purge the encoding as we don't want invalid UTF-8 floating around. */
+ /** @todo do this afterwards as needed. */
+ RTStrPurgeEncoding(pThis->szRockSymlinkTargetBuf);
+ }
+ break;
+
+ case MAKE_SIG(ISO9660RRIPNM_SIG1, ISO9660RRIPNM_SIG2): /* NM */
+ if ( pUnion->NM.Hdr.bVersion != ISO9660RRIPNM_VER
+ || pUnion->NM.Hdr.cbEntry < RT_UOFFSETOF(ISO9660RRIPNM, achName)
+ || (pUnion->NM.fFlags & ISO9660RRIP_NM_F_RESERVED_MASK) )
+ LogRel(("rtFsIsoImport/Rock: Malformed 'NM' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) fFlags=%#x %.*Rhxs\n",
+ pUnion->NM.Hdr.cbEntry, RT_UOFFSETOF(ISO9660RRIPNM, achName),
+ pUnion->NM.Hdr.bVersion, ISO9660RRIPNM_VER, pUnion->NM.fFlags,
+ pUnion->NM.Hdr.cbEntry - RT_MIN(pUnion->NM.Hdr.cbEntry, RT_UOFFSETOF(ISO9660RRIPNM, achName)),
+ &pUnion->NM.achName[0] ));
+ else if (pThis->fSeenLastNM)
+ LogRel(("rtFsIsoImport/Rock: Unexpected 'NM' entry!\n"));
+ else
+ {
+ pThis->fSeenLastNM = !(pUnion->NM.fFlags & ISO9660RRIP_NM_F_CONTINUE);
+
+ uint8_t const cchName = pUnion->NM.Hdr.cbEntry - (uint8_t)RT_UOFFSETOF(ISO9660RRIPNM, achName);
+ if (pUnion->NM.fFlags & (ISO9660RRIP_NM_F_CURRENT | ISO9660RRIP_NM_F_PARENT))
+ {
+ if (cchName == 0)
+ Log(("rtFsIsoImport/Rock: Ignoring 'NM' entry for '.' and '..'\n"));
+ else
+ LogRel(("rtFsIsoImport/Rock: Ignoring malformed 'NM' using '.' or '..': fFlags=%#x cchName=%#x %.*Rhxs; szRockNameBuf='%s'\n",
+ pUnion->NM.fFlags, cchName, cchName, pUnion->NM.achName, pThis->szRockNameBuf));
+ pThis->szRockNameBuf[0] = '\0';
+ pThis->fSeenLastNM = true;
+ }
+ else
+ {
+ size_t offDst = strlen(pThis->szRockNameBuf);
+ if (offDst + cchName < sizeof(pThis->szRockNameBuf))
+ {
+ memcpy(&pThis->szRockNameBuf[offDst], pUnion->NM.achName, cchName);
+ pThis->szRockNameBuf[offDst + cchName] = '\0';
+
+ /* Purge the encoding as we don't want invalid UTF-8 floating around. */
+ /** @todo do this afterwards as needed. */
+ RTStrPurgeEncoding(pThis->szRockNameBuf);
+ }
+ else
+ {
+ LogRel(("rtFsIsoImport/Rock: 'NM' constructs a too long name, ignoring it all: '%s%.*s'\n",
+ pThis->szRockNameBuf, cchName, pUnion->NM.achName));
+ pThis->szRockNameBuf[0] = '\0';
+ pThis->fSeenLastNM = true;
+ }
+ }
+ }
+ break;
+
+ case MAKE_SIG(ISO9660RRIPCL_SIG1, ISO9660RRIPCL_SIG2): /* CL - just warn for now. */
+ case MAKE_SIG(ISO9660RRIPPL_SIG1, ISO9660RRIPPL_SIG2): /* PL - just warn for now. */
+ case MAKE_SIG(ISO9660RRIPRE_SIG1, ISO9660RRIPRE_SIG2): /* RE - just warn for now. */
+ LogRel(("rtFsIsoImport/Rock: Ignoring directory relocation entry '%c%c'!\n", pUnion->Hdr.bSig1, pUnion->Hdr.bSig2));
+ break;
+
+ default:
+ LogRel(("rtFsIsoImport/Rock: Unknown SUSP entry: %#x %#x, %#x bytes, v%u\n",
+ pUnion->Hdr.bSig1, pUnion->Hdr.bSig2, pUnion->Hdr.cbEntry, pUnion->Hdr.bVersion));
+ break;
+#undef MAKE_SIG
+ }
+ }
+}
+
+
+/**
+ * Deals with the special '.' entry in the root directory.
+ *
+ * @returns IPRT status code.
+ * @param pThis The import instance.
+ * @param pDirRec The root directory record.
+ * @param fUnicode Indicates which namespace we're working on.
+ */
+static int rtFsIsoImportProcessIso9660TreeWorkerDoRockForRoot(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec, bool fUnicode)
+{
+ uint8_t const cbSys = pDirRec->cbDirRec - RT_UOFFSETOF(ISO9660DIRREC, achFileId)
+ - pDirRec->bFileIdLength - !(pDirRec->bFileIdLength & 1);
+ uint8_t const * const pbSys = (uint8_t const *)&pDirRec->achFileId[pDirRec->bFileIdLength + !(pDirRec->bFileIdLength & 1)];
+ if (cbSys > 4)
+ {
+ RTFSOBJINFO ObjInfo;
+ ObjInfo.cbObject = 0;
+ ObjInfo.cbAllocated = 0;
+ rtFsIsoImpIso9660RecDateTime2TimeSpec(&ObjInfo.AccessTime, &pDirRec->RecTime);
+ ObjInfo.ModificationTime = ObjInfo.AccessTime;
+ ObjInfo.ChangeTime = ObjInfo.AccessTime;
+ ObjInfo.BirthTime = ObjInfo.AccessTime;
+ ObjInfo.Attr.fMode = RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY | 0555;
+ ObjInfo.Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
+ ObjInfo.Attr.u.Unix.uid = NIL_RTUID;
+ ObjInfo.Attr.u.Unix.gid = NIL_RTGID;
+ ObjInfo.Attr.u.Unix.cHardlinks = 2;
+ ObjInfo.Attr.u.Unix.INodeIdDevice = 0;
+ ObjInfo.Attr.u.Unix.INodeId = 0;
+ ObjInfo.Attr.u.Unix.fFlags = 0;
+ ObjInfo.Attr.u.Unix.GenerationId = 0;
+ ObjInfo.Attr.u.Unix.Device = 0;
+
+ rtFsIsoImportProcessIso9660TreeWorkerParseRockRidge(pThis, &ObjInfo, pbSys, cbSys, fUnicode, true /*fIsFirstDirRec*/,
+ false /*fContinuationRecord*/);
+ /** @todo Update root dir attribs. Need API. */
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Validates a directory record.
+ *
+ * @returns IPRT status code (safe to ignore, see pThis->rc).
+ * @param pThis The importer instance.
+ * @param pDirRec The directory record to validate.
+ * @param cbMax The maximum size.
+ */
+static int rtFsIsoImportValidateDirRec(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec, uint32_t cbMax)
+{
+ /*
+ * Validate dual fields.
+ */
+ if (RT_LE2H_U32(pDirRec->cbData.le) != RT_BE2H_U32(pDirRec->cbData.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_DIR_REC,
+ "Invalid dir rec size field: {%#RX32,%#RX32}",
+ RT_BE2H_U32(pDirRec->cbData.be), RT_LE2H_U32(pDirRec->cbData.le));
+
+ if (RT_LE2H_U32(pDirRec->offExtent.le) != RT_BE2H_U32(pDirRec->offExtent.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_DIR_REC,
+ "Invalid dir rec extent field: {%#RX32,%#RX32}",
+ RT_BE2H_U32(pDirRec->offExtent.be), RT_LE2H_U32(pDirRec->offExtent.le));
+
+ if (RT_LE2H_U16(pDirRec->VolumeSeqNo.le) != RT_BE2H_U16(pDirRec->VolumeSeqNo.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_DIR_REC,
+ "Invalid dir rec volume sequence ID field: {%#RX16,%#RX16}",
+ RT_BE2H_U16(pDirRec->VolumeSeqNo.be), RT_LE2H_U16(pDirRec->VolumeSeqNo.le));
+
+ /*
+ * Check values.
+ */
+ if (ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo) != pThis->idPrimaryVol)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_DIR_REC_VOLUME_SEQ_NO,
+ "Expected dir rec to have same volume sequence number as primary volume: %#x, expected %#x",
+ ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo), pThis->idPrimaryVol);
+
+ if (ISO9660_GET_ENDIAN(&pDirRec->offExtent) >= pThis->cBlocksInPrimaryVolumeSpace)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_DIR_REC_EXTENT_OUT_OF_BOUNDS,
+ "Invalid dir rec extent: %#RX32, max %#RX32",
+ ISO9660_GET_ENDIAN(&pDirRec->offExtent), pThis->cBlocksInPrimaryVolumeSpace);
+
+ if (pDirRec->cbDirRec < RT_UOFFSETOF(ISO9660DIRREC, achFileId) + pDirRec->bFileIdLength)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_DIR_REC_LENGTH,
+ "Dir record size is too small: %#x (min %#x)",
+ pDirRec->cbDirRec, RT_UOFFSETOF(ISO9660DIRREC, achFileId) + pDirRec->bFileIdLength);
+ if (pDirRec->cbDirRec > cbMax)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_DIR_REC_LENGTH,
+ "Dir record size is too big: %#x (max %#x)", pDirRec->cbDirRec, cbMax);
+
+ if ( (pDirRec->fFileFlags & (ISO9660_FILE_FLAGS_MULTI_EXTENT | ISO9660_FILE_FLAGS_DIRECTORY))
+ == (ISO9660_FILE_FLAGS_MULTI_EXTENT | ISO9660_FILE_FLAGS_DIRECTORY))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_DIR_WITH_MORE_EXTENTS,
+ "Multi-extent directories are not supported (cbData=%#RX32 offExtent=%#RX32)",
+ ISO9660_GET_ENDIAN(&pDirRec->cbData), ISO9660_GET_ENDIAN(&pDirRec->offExtent));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Validates a dot or dot-dot directory record.
+ *
+ * @returns IPRT status code (safe to ignore, see pThis->rc).
+ * @param pThis The importer instance.
+ * @param pDirRec The dot directory record to validate.
+ * @param cbMax The maximum size.
+ * @param bName The name byte (0x00: '.', 0x01: '..').
+ */
+static int rtFsIsoImportValidateDotDirRec(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec, uint32_t cbMax, uint8_t bName)
+{
+ int rc = rtFsIsoImportValidateDirRec(pThis, pDirRec, cbMax);
+ if (RT_SUCCESS(rc))
+ {
+ if (pDirRec->bFileIdLength != 1)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_DOT_DIR_REC_BAD_NAME_LENGTH,
+ "Invalid dot dir rec file id length: %u", pDirRec->bFileIdLength);
+ if ((uint8_t)pDirRec->achFileId[0] != bName)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_DOT_DIR_REC_BAD_NAME,
+ "Invalid dot dir rec file id: %#x, expected %#x", pDirRec->achFileId[0], bName);
+ }
+ return rc;
+}
+
+
+/**
+ * rtFsIsoImportProcessIso9660TreeWorker helper that reads more data.
+ *
+ * @returns IPRT status code.
+ * @param pThis The importer instance.
+ * @param ppDirRec Pointer to the directory record pointer (in/out).
+ * @param pcbChunk Pointer to the cbChunk variable (in/out).
+ * @param pcbDir Pointer to the cbDir variable (in/out). This indicates
+ * how much we've left to read from the directory.
+ * @param poffNext Pointer to the offNext variable (in/out). This
+ * indicates where the next chunk of directory data is in
+ * the input file.
+ */
+static int rtFsIsoImportProcessIso9660TreeWorkerReadMore(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC *ppDirRec,
+ uint32_t *pcbChunk, uint32_t *pcbDir, uint64_t *poffNext)
+{
+ uint32_t cbChunk = *pcbChunk;
+ *ppDirRec = (PCISO9660DIRREC)memmove(&pThis->abBuf[ISO9660_SECTOR_SIZE - cbChunk], *ppDirRec, cbChunk);
+
+ Assert(!(*poffNext & (ISO9660_SECTOR_SIZE - 1)));
+ uint32_t cbToRead = RT_MIN(*pcbDir, sizeof(pThis->abBuf) - ISO9660_SECTOR_SIZE);
+ int rc = RTVfsFileReadAt(pThis->hSrcFile, *poffNext, &pThis->abBuf[ISO9660_SECTOR_SIZE], cbToRead, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ Log3(("rtFsIsoImportProcessIso9660TreeWorker: Read %#zx more bytes @%#RX64, now got @%#RX64 LB %#RX32\n",
+ cbToRead, *poffNext, *poffNext - cbChunk, cbChunk + cbToRead));
+ *poffNext += cbToRead;
+ *pcbDir -= cbToRead;
+ *pcbChunk = cbChunk + cbToRead;
+ return VINF_SUCCESS;
+ }
+ return rtFsIsoImpError(pThis, rc, "Error reading %#RX32 bytes at %#RX64 (dir): %Rrc", *poffNext, cbToRead);
+}
+
+
+/**
+ * rtFsIsoImportProcessIso9660TreeWorker helper that deals with skipping to the
+ * next sector when cbDirRec is zero.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_NO_MORE_FILES when we reaches the end of the directory.
+ * @param pThis The importer instance.
+ * @param ppDirRec Pointer to the directory record pointer (in/out).
+ * @param pcbChunk Pointer to the cbChunk variable (in/out). Indicates how
+ * much we've left to process starting and pDirRec.
+ * @param pcbDir Pointer to the cbDir variable (in/out). This indicates
+ * how much we've left to read from the directory.
+ * @param poffNext Pointer to the offNext variable (in/out). This
+ * indicates where the next chunk of directory data is in
+ * the input file.
+ */
+static int rtFsIsoImportProcessIso9660TreeWorkerHandleZeroSizedDirRec(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC *ppDirRec,
+ uint32_t *pcbChunk, uint32_t *pcbDir, uint64_t *poffNext)
+{
+ uint32_t cbChunk = *pcbChunk;
+ uint64_t offChunk = *poffNext - cbChunk;
+ uint32_t cbSkip = ISO9660_SECTOR_SIZE - ((uint32_t)offChunk & (ISO9660_SECTOR_SIZE - 1));
+ if (cbSkip < cbChunk)
+ {
+ *ppDirRec = (PCISO9660DIRREC)((uintptr_t)*ppDirRec + cbSkip);
+ *pcbChunk = cbChunk -= cbSkip;
+ if ( cbChunk > UINT8_MAX
+ || *pcbDir == 0)
+ {
+ Log3(("rtFsIsoImportProcessIso9660TreeWorker: cbDirRec=0 --> jumped %#RX32 to @%#RX64 LB %#RX32\n",
+ cbSkip, *poffNext - cbChunk, cbChunk));
+ return VINF_SUCCESS;
+ }
+ Log3(("rtFsIsoImportProcessIso9660TreeWorker: cbDirRec=0 --> jumped %#RX32 to @%#RX64 LB %#RX32, but needs to read more\n",
+ cbSkip, *poffNext - cbChunk, cbChunk));
+ return rtFsIsoImportProcessIso9660TreeWorkerReadMore(pThis, ppDirRec, pcbChunk, pcbDir, poffNext);
+ }
+
+ /* ASSUMES we're working in multiples of sectors! */
+ if (*pcbDir == 0)
+ {
+ *pcbChunk = 0;
+ return VERR_NO_MORE_FILES;
+ }
+
+ /* End of chunk, read the next sectors. */
+ Assert(!(*poffNext & (ISO9660_SECTOR_SIZE - 1)));
+ uint32_t cbToRead = RT_MIN(*pcbDir, sizeof(pThis->abBuf));
+ int rc = RTVfsFileReadAt(pThis->hSrcFile, *poffNext, pThis->abBuf, cbToRead, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ Log3(("rtFsIsoImportProcessIso9660TreeWorker: cbDirRec=0 --> Read %#zx more bytes @%#RX64, now got @%#RX64 LB %#RX32\n",
+ cbToRead, *poffNext, *poffNext - cbChunk, cbChunk + cbToRead));
+ *poffNext += cbToRead;
+ *pcbDir -= cbToRead;
+ *pcbChunk = cbChunk + cbToRead;
+ *ppDirRec = (PCISO9660DIRREC)&pThis->abBuf[0];
+ return VINF_SUCCESS;
+ }
+ return rtFsIsoImpError(pThis, rc, "Error reading %#RX32 bytes at %#RX64 (dir): %Rrc", *poffNext, cbToRead);
+}
+
+
+/**
+ * Deals with a single directory.
+ *
+ * @returns IPRT status code (safe to ignore, see pThis->rc).
+ * @param pThis The importer instance.
+ * @param idxDir The configuration index for the directory.
+ * @param offDirBlock The offset of the directory data.
+ * @param cbDir The size of the directory data.
+ * @param cDepth The depth of the directory.
+ * @param fUnicode Set if it's a unicode (UTF-16BE) encoded
+ * directory.
+ * @param pTodoList The todo-list to add sub-directories to.
+ */
+static int rtFsIsoImportProcessIso9660TreeWorker(PRTFSISOMKIMPORTER pThis, uint32_t idxDir,
+ uint32_t offDirBlock, uint32_t cbDir, uint8_t cDepth, bool fUnicode,
+ PRTLISTANCHOR pTodoList)
+{
+ /*
+ * Restrict the depth to try avoid loops.
+ */
+ if (cDepth > RTFSISOMK_IMPORT_MAX_DEPTH)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_TOO_DEEP_DIR_TREE, "Dir at %#x LB %#x is too deep", offDirBlock, cbDir);
+
+ /*
+ * Read the first chunk into the big buffer.
+ */
+ uint32_t cbChunk = RT_MIN(cbDir, sizeof(pThis->abBuf));
+ uint64_t offNext = (uint64_t)offDirBlock * ISO9660_SECTOR_SIZE;
+ int rc = RTVfsFileReadAt(pThis->hSrcFile, offNext, pThis->abBuf, cbChunk, NULL);
+ if (RT_FAILURE(rc))
+ return rtFsIsoImpError(pThis, rc, "Error reading directory at %#RX64 (%#RX32 / %#RX32): %Rrc", offNext, cbChunk, cbDir);
+
+ cbDir -= cbChunk;
+ offNext += cbChunk;
+
+ /*
+ * Skip the current and parent directory entries.
+ */
+ PCISO9660DIRREC pDirRec = (PCISO9660DIRREC)&pThis->abBuf[0];
+ rc = rtFsIsoImportValidateDotDirRec(pThis, pDirRec, cbChunk, 0x00);
+ if (RT_FAILURE(rc))
+ return rc;
+ if ( cDepth == 0
+ && !(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_ROCK_RIDGE)
+ && pDirRec->cbDirRec > RT_UOFFSETOF(ISO9660DIRREC, achFileId[1]))
+ {
+ rc = rtFsIsoImportProcessIso9660TreeWorkerDoRockForRoot(pThis, pDirRec, fUnicode);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ cbChunk -= pDirRec->cbDirRec;
+ pDirRec = (PCISO9660DIRREC)((uintptr_t)pDirRec + pDirRec->cbDirRec);
+ rc = rtFsIsoImportValidateDotDirRec(pThis, pDirRec, cbChunk, 0x01);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ cbChunk -= pDirRec->cbDirRec;
+ pDirRec = (PCISO9660DIRREC)((uintptr_t)pDirRec + pDirRec->cbDirRec);
+
+ /*
+ * Work our way thru all the directory records.
+ */
+ Log3(("rtFsIsoImportProcessIso9660TreeWorker: Starting at @%#RX64 LB %#RX32 (out of %#RX32) in %#x\n",
+ offNext - cbChunk, cbChunk, cbChunk + cbDir, idxDir));
+ const uint32_t fNamespace = fUnicode ? RTFSISOMAKER_NAMESPACE_JOLIET : RTFSISOMAKER_NAMESPACE_ISO_9660;
+ while ( cbChunk > 0
+ || cbDir > 0)
+ {
+ /*
+ * Do we need to read some more?
+ */
+ if ( cbChunk > UINT8_MAX
+ || cbDir == 0)
+ { /* No, we don't. */ }
+ else
+ {
+ rc = rtFsIsoImportProcessIso9660TreeWorkerReadMore(pThis, &pDirRec, &cbChunk, &cbDir, &offNext);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /* If null length, skip to the next sector. May have to read some then. */
+ if (pDirRec->cbDirRec != 0)
+ { /* likely */ }
+ else
+ {
+ rc = rtFsIsoImportProcessIso9660TreeWorkerHandleZeroSizedDirRec(pThis, &pDirRec, &cbChunk, &cbDir, &offNext);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_NO_MORE_FILES)
+ break;
+ return rc;
+ }
+ if (pDirRec->cbDirRec == 0)
+ continue;
+ }
+
+ /*
+ * Validate the directory record. Give up if not valid since we're
+ * likely to get error with subsequent record too.
+ */
+ uint8_t const cbSys = pDirRec->cbDirRec - RT_UOFFSETOF(ISO9660DIRREC, achFileId)
+ - pDirRec->bFileIdLength - !(pDirRec->bFileIdLength & 1);
+ uint8_t const * const pbSys = (uint8_t const *)&pDirRec->achFileId[pDirRec->bFileIdLength + !(pDirRec->bFileIdLength & 1)];
+ Log3(("pDirRec=&abBuf[%#07zx]: @%#010RX64 cb=%#04x ff=%#04x off=%#010RX32 cb=%#010RX32 cbSys=%#x id=%.*Rhxs\n",
+ (uintptr_t)pDirRec - (uintptr_t)&pThis->abBuf[0], offNext - cbChunk, pDirRec->cbDirRec, pDirRec->fFileFlags,
+ ISO9660_GET_ENDIAN(&pDirRec->offExtent), ISO9660_GET_ENDIAN(&pDirRec->cbData), cbSys,
+ pDirRec->bFileIdLength, pDirRec->achFileId));
+ rc = rtFsIsoImportValidateDirRec(pThis, pDirRec, cbChunk);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* This early calculation of the next record is due to multi-extent
+ handling further down. */
+ uint32_t cbChunkNew = cbChunk - pDirRec->cbDirRec;
+ PCISO9660DIRREC pDirRecNext = (PCISO9660DIRREC)((uintptr_t)pDirRec + pDirRec->cbDirRec);
+
+ /* Start Collecting object info. */
+ RTFSOBJINFO ObjInfo;
+ ObjInfo.cbObject = ISO9660_GET_ENDIAN(&pDirRec->cbData);
+ ObjInfo.cbAllocated = ObjInfo.cbObject;
+ rtFsIsoImpIso9660RecDateTime2TimeSpec(&ObjInfo.AccessTime, &pDirRec->RecTime);
+ ObjInfo.ModificationTime = ObjInfo.AccessTime;
+ ObjInfo.ChangeTime = ObjInfo.AccessTime;
+ ObjInfo.BirthTime = ObjInfo.AccessTime;
+ ObjInfo.Attr.fMode = pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY
+ ? RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY | 0555
+ : RTFS_TYPE_FILE | RTFS_DOS_ARCHIVED | 0444;
+ ObjInfo.Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
+ ObjInfo.Attr.u.Unix.uid = NIL_RTUID;
+ ObjInfo.Attr.u.Unix.gid = NIL_RTGID;
+ ObjInfo.Attr.u.Unix.cHardlinks = 1;
+ ObjInfo.Attr.u.Unix.INodeIdDevice = 0;
+ ObjInfo.Attr.u.Unix.INodeId = 0;
+ ObjInfo.Attr.u.Unix.fFlags = 0;
+ ObjInfo.Attr.u.Unix.GenerationId = 0;
+ ObjInfo.Attr.u.Unix.Device = 0;
+
+ /*
+ * Convert the name into the name buffer (szNameBuf).
+ */
+ if (!fUnicode)
+ {
+ memcpy(pThis->szNameBuf, pDirRec->achFileId, pDirRec->bFileIdLength);
+ pThis->szNameBuf[pDirRec->bFileIdLength] = '\0';
+ rc = RTStrValidateEncoding(pThis->szNameBuf);
+ }
+ else
+ {
+ char *pszDst = pThis->szNameBuf;
+ rc = RTUtf16BigToUtf8Ex((PRTUTF16)pDirRec->achFileId, pDirRec->bFileIdLength / sizeof(RTUTF16),
+ &pszDst, sizeof(pThis->szNameBuf), NULL);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /* Drop the version from the name. */
+ size_t cchName = strlen(pThis->szNameBuf);
+ if ( !(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY)
+ && cchName > 2
+ && RT_C_IS_DIGIT(pThis->szNameBuf[cchName - 1]))
+ {
+ uint32_t offName = 2;
+ while ( offName <= 5
+ && offName + 1 < cchName
+ && RT_C_IS_DIGIT(pThis->szNameBuf[cchName - offName]))
+ offName++;
+ if ( offName + 1 < cchName
+ && pThis->szNameBuf[cchName - offName] == ';')
+ {
+ RTStrToUInt32Full(&pThis->szNameBuf[cchName - offName + 1], 10, &ObjInfo.Attr.u.Unix.GenerationId);
+ pThis->szNameBuf[cchName - offName] = '\0';
+ }
+ }
+ Log3((" --> name='%s'\n", pThis->szNameBuf));
+
+ pThis->szRockNameBuf[0] = '\0';
+ pThis->szRockSymlinkTargetBuf[0] = '\0';
+ if ( cbSys > pThis->offSuspSkip
+ && !(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_ROCK_RIDGE))
+ {
+ pThis->fSeenLastNM = false;
+ pThis->fSeenLastSL = false;
+ pThis->szRockNameBuf[0] = '\0';
+ pThis->szRockSymlinkTargetBuf[0] = '\0';
+ rtFsIsoImportProcessIso9660TreeWorkerParseRockRidge(pThis, &ObjInfo, &pbSys[pThis->offSuspSkip],
+ cbSys - pThis->offSuspSkip, fUnicode,
+ false /*fContinuationRecord*/, false /*fIsFirstDirRec*/);
+ }
+
+ /*
+ * Deal with multi-extent files (usually large ones). We currently only
+ * handle files where the data is in single continuous chunk and only split
+ * up into multiple directory records because of data type limitations.
+ */
+ uint8_t abDirRecCopy[256];
+ uint64_t cbData = ISO9660_GET_ENDIAN(&pDirRec->cbData);
+ if (!(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT))
+ { /* likely */ }
+ else
+ {
+ if (cbData & (ISO9660_SECTOR_SIZE - 1))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MISALIGNED_MULTI_EXTENT,
+ "The size of non-final multi-extent record #0x0 isn't block aligned: %#RX64", cbData);
+
+ /* Make a copy of the first directory record so we don't overwrite
+ it when reading in more records below. */
+ pDirRec = (PCISO9660DIRREC)memcpy(abDirRecCopy, pDirRec, pDirRec->cbDirRec);
+
+ /* Process extent records. */
+ uint32_t cDirRecs = 1;
+ uint32_t offNextBlock = ISO9660_GET_ENDIAN(&pDirRec->offExtent)
+ + ISO9660_GET_ENDIAN(&pDirRec->cbData) / ISO9660_SECTOR_SIZE;
+ while ( cbChunkNew > 0
+ || cbDir > 0)
+ {
+ /* Read more? Skip? */
+ if ( cbChunkNew <= UINT8_MAX
+ && cbDir != 0)
+ {
+ rc = rtFsIsoImportProcessIso9660TreeWorkerReadMore(pThis, &pDirRecNext, &cbChunkNew, &cbDir, &offNext);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ if (pDirRecNext->cbDirRec == 0)
+ {
+ rc = rtFsIsoImportProcessIso9660TreeWorkerHandleZeroSizedDirRec(pThis, &pDirRecNext, &cbChunkNew,
+ &cbDir, &offNext);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_NO_MORE_FILES)
+ break;
+ return rc;
+ }
+ if (pDirRecNext->cbDirRec == 0)
+ continue;
+ }
+
+ /* Check the next record. */
+ rc = rtFsIsoImportValidateDirRec(pThis, pDirRecNext, cbChunkNew);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (pDirRecNext->bFileIdLength != pDirRec->bFileIdLength)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MISMATCHING_MULTI_EXTENT_REC,
+ "Multi-extent record #%#x differs from the first: bFileIdLength is %#x, expected %#x",
+ cDirRecs, pDirRecNext->bFileIdLength, pDirRec->bFileIdLength);
+ if (memcmp(pDirRecNext->achFileId, pDirRec->achFileId, pDirRec->bFileIdLength) != 0)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MISMATCHING_MULTI_EXTENT_REC,
+ "Multi-extent record #%#x differs from the first: achFileId is %.*Rhxs, expected %.*Rhxs",
+ cDirRecs, pDirRecNext->bFileIdLength, pDirRecNext->achFileId,
+ pDirRec->bFileIdLength, pDirRec->achFileId);
+ if (ISO9660_GET_ENDIAN(&pDirRecNext->VolumeSeqNo) != ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MISMATCHING_MULTI_EXTENT_REC,
+ "Multi-extent record #%#x differs from the first: VolumeSeqNo is %#x, expected %#x",
+ cDirRecs, ISO9660_GET_ENDIAN(&pDirRecNext->VolumeSeqNo),
+ ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo));
+ if ( (pDirRecNext->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT)
+ && (ISO9660_GET_ENDIAN(&pDirRecNext->cbData) & (ISO9660_SECTOR_SIZE - 1)) )
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MISALIGNED_MULTI_EXTENT,
+ "The size of non-final multi-extent record #%#x isn't block aligned: %#RX32",
+ cDirRecs, ISO9660_GET_ENDIAN(&pDirRecNext->cbData));
+
+ /* Check that the data is contiguous, then add the data. */
+ if (ISO9660_GET_ENDIAN(&pDirRecNext->offExtent) == offNextBlock)
+ cbData += ISO9660_GET_ENDIAN(&pDirRecNext->cbData);
+ else
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_NON_CONTIGUOUS_MULTI_EXTENT,
+ "Multi-extent record #%#x isn't contiguous: offExtent=%#RX32, expected %#RX32",
+ cDirRecs, ISO9660_GET_ENDIAN(&pDirRecNext->offExtent), offNextBlock);
+
+ /* Advance. */
+ cDirRecs++;
+ bool fDone = !(pDirRecNext->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT);
+ offNext += ISO9660_GET_ENDIAN(&pDirRecNext->cbData) / ISO9660_SECTOR_SIZE;
+ cbChunkNew -= pDirRecNext->cbDirRec;
+ pDirRecNext = (PCISO9660DIRREC)((uintptr_t)pDirRecNext + pDirRecNext->cbDirRec);
+ if (fDone)
+ break;
+ }
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Add the object.
+ */
+ if (pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY)
+ rtFsIsoImportProcessIso9660AddAndNameDirectory(pThis, pDirRec, &ObjInfo, cbData, fNamespace, idxDir,
+ pThis->szNameBuf, pThis->szRockNameBuf, cDepth + 1, pTodoList);
+ else if (pThis->szRockSymlinkTargetBuf[0] == '\0')
+ {
+ if (strcmp(pThis->szNameBuf, pThis->pszTransTbl) != 0)
+ rtFsIsoImportProcessIso9660AddAndNameFile(pThis, pDirRec, &ObjInfo, cbData, fNamespace, idxDir,
+ pThis->szNameBuf, pThis->szRockNameBuf);
+ }
+ else
+ rtFsIsoImportProcessIso9660AddAndNameSymlink(pThis, pDirRec, &ObjInfo, fNamespace, idxDir, pThis->szNameBuf,
+ pThis->szRockNameBuf, pThis->szRockSymlinkTargetBuf);
+ }
+ }
+ else
+ rtFsIsoImpError(pThis, rc, "Invalid name at %#RX64: %.Rhxs",
+ offNext - cbChunk, pDirRec->bFileIdLength, pDirRec->achFileId);
+
+ /*
+ * Advance to the next directory record.
+ */
+ cbChunk = cbChunkNew;
+ pDirRec = pDirRecNext;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Deals with a directory tree.
+ *
+ * This is implemented by tracking directories that needs to be processed in a
+ * todo list, so no recursive calls, however it uses a bit of heap.
+ *
+ * @returns IPRT status code (safe to ignore, see pThis->rc).
+ * @param pThis The importer instance.
+ * @param offDirBlock The offset of the root directory data.
+ * @param cbDir The size of the root directory data.
+ * @param fUnicode Set if it's a unicode (UTF-16BE) encoded
+ * directory.
+ */
+static int rtFsIsoImportProcessIso9660Tree(PRTFSISOMKIMPORTER pThis, uint32_t offDirBlock, uint32_t cbDir, bool fUnicode)
+{
+ /*
+ * Reset some parsing state.
+ */
+ pThis->offSuspSkip = 0;
+ pThis->fSuspSeenSP = false;
+ pThis->pszTransTbl = "TRANS.TBL"; /** @todo query this from the iso maker! */
+
+ /*
+ * Make sure we've got a root in the namespace.
+ */
+ uint32_t idxDir = RTFsIsoMakerGetObjIdxForPath(pThis->hIsoMaker,
+ !fUnicode ? RTFSISOMAKER_NAMESPACE_ISO_9660 : RTFSISOMAKER_NAMESPACE_JOLIET,
+ "/");
+ if (idxDir == UINT32_MAX)
+ {
+ idxDir = RTFSISOMAKER_CFG_IDX_ROOT;
+ int rc = RTFsIsoMakerObjSetPath(pThis->hIsoMaker, RTFSISOMAKER_CFG_IDX_ROOT,
+ !fUnicode ? RTFSISOMAKER_NAMESPACE_ISO_9660 : RTFSISOMAKER_NAMESPACE_JOLIET, "/");
+ if (RT_FAILURE(rc))
+ return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerObjSetPath failed on root dir: %Rrc", rc);
+ }
+ Assert(idxDir == RTFSISOMAKER_CFG_IDX_ROOT);
+
+ /*
+ * Directories.
+ */
+ int rc = VINF_SUCCESS;
+ uint8_t cDepth = 0;
+ RTLISTANCHOR TodoList;
+ RTListInit(&TodoList);
+ for (;;)
+ {
+ int rc2 = rtFsIsoImportProcessIso9660TreeWorker(pThis, idxDir, offDirBlock, cbDir, cDepth, fUnicode, &TodoList);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+
+ /*
+ * Pop the next directory.
+ */
+ PRTFSISOMKIMPDIR pNext = RTListRemoveLast(&TodoList, RTFSISOMKIMPDIR, Entry);
+ if (!pNext)
+ break;
+ idxDir = pNext->idxObj;
+ offDirBlock = pNext->offDirBlock;
+ cbDir = pNext->cbDir;
+ cDepth = pNext->cDepth;
+ RTMemFree(pNext);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Imports a UTF-16BE string property from the joliet volume descriptor.
+ *
+ * The fields are normally space filled and padded, but we also consider zero
+ * bytes are fillers. If the field only contains padding, the string property
+ * will remain unchanged.
+ *
+ * @returns IPRT status code (ignorable).
+ * @param pThis The importer instance.
+ * @param pachField Pointer to the field. The structure type
+ * is 'char' for hysterical raisins, while the
+ * real type is 'RTUTF16'.
+ * @param cchField The field length.
+ * @param enmStringProp The corresponding string property.
+ *
+ * @note Clobbers pThis->pbBuf!
+ */
+static int rtFsIsoImportUtf16BigStringField(PRTFSISOMKIMPORTER pThis, const char *pachField, size_t cchField,
+ RTFSISOMAKERSTRINGPROP enmStringProp)
+{
+ /*
+ * Scan the field from the end as this way we know the result length if we find anything.
+ */
+ PCRTUTF16 pwcField = (PCRTUTF16)pachField;
+ size_t cwcField = cchField / sizeof(RTUTF16); /* ignores any odd field byte */
+ size_t off = cwcField;
+ while (off-- > 0)
+ {
+ RTUTF16 wc = RT_BE2H_U16(pwcField[off]);
+ if (wc == ' ' || wc == '\0')
+ { /* likely */ }
+ else
+ {
+ /*
+ * Convert to UTF-16.
+ */
+ char *pszCopy = (char *)pThis->abBuf;
+ int rc = RTUtf16BigToUtf8Ex(pwcField, off + 1, &pszCopy, sizeof(pThis->abBuf), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFsIsoMakerSetStringProp(pThis->hIsoMaker, enmStringProp, RTFSISOMAKER_NAMESPACE_JOLIET, pszCopy);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerSetStringProp failed setting field %d to '%s': %Rrc",
+ enmStringProp, pszCopy, rc);
+ }
+ return rtFsIsoImpError(pThis, rc, "RTUtf16BigToUtf8Ex failed converting field %d to UTF-8: %Rrc - %.*Rhxs",
+ enmStringProp, rc, off * sizeof(RTUTF16), pwcField);
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Imports a string property from the primary volume descriptor.
+ *
+ * The fields are normally space filled and padded, but we also consider zero
+ * bytes are fillers. If the field only contains padding, the string property
+ * will remain unchanged.
+ *
+ * @returns IPRT status code (ignorable).
+ * @param pThis The importer instance.
+ * @param pachField Pointer to the field.
+ * @param cchField The field length.
+ * @param enmStringProp The corresponding string property.
+ *
+ * @note Clobbers pThis->pbBuf!
+ */
+static int rtFsIsoImportAsciiStringField(PRTFSISOMKIMPORTER pThis, const char *pachField, size_t cchField,
+ RTFSISOMAKERSTRINGPROP enmStringProp)
+{
+ /*
+ * Scan the field from the end as this way we know the result length if we find anything.
+ */
+ size_t off = cchField;
+ while (off-- > 0)
+ {
+ char ch = pachField[off];
+ if (ch == ' ' || ch == '\0')
+ { /* likely */ }
+ else
+ {
+ /*
+ * Make a copy of the string in abBuf, purge the encoding.
+ */
+ off++;
+ char *pszCopy = (char *)pThis->abBuf;
+ memcpy(pszCopy, pachField, off);
+ pszCopy[off] = '\0';
+ RTStrPurgeEncoding(pszCopy);
+
+ int rc = RTFsIsoMakerSetStringProp(pThis->hIsoMaker, enmStringProp, RTFSISOMAKER_NAMESPACE_ISO_9660, pszCopy);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerSetStringProp failed setting field %d to '%s': %Rrc",
+ enmStringProp, pszCopy, rc);
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Validates a root directory record.
+ *
+ * @returns IPRT status code (safe to ignore, see pThis->rc).
+ * @param pThis The importer instance.
+ * @param pDirRec The root directory record to validate.
+ */
+static int rtFsIsoImportValidateRootDirRec(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec)
+{
+ /*
+ * Validate dual fields.
+ */
+ if (RT_LE2H_U32(pDirRec->cbData.le) != RT_BE2H_U32(pDirRec->cbData.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_ROOT_DIR_REC,
+ "Invalid root dir size: {%#RX32,%#RX32}",
+ RT_BE2H_U32(pDirRec->cbData.be), RT_LE2H_U32(pDirRec->cbData.le));
+
+ if (RT_LE2H_U32(pDirRec->offExtent.le) != RT_BE2H_U32(pDirRec->offExtent.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_ROOT_DIR_REC,
+ "Invalid root dir extent: {%#RX32,%#RX32}",
+ RT_BE2H_U32(pDirRec->offExtent.be), RT_LE2H_U32(pDirRec->offExtent.le));
+
+ if (RT_LE2H_U16(pDirRec->VolumeSeqNo.le) != RT_BE2H_U16(pDirRec->VolumeSeqNo.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_ROOT_DIR_REC,
+ "Invalid root dir volume sequence ID: {%#RX16,%#RX16}",
+ RT_BE2H_U16(pDirRec->VolumeSeqNo.be), RT_LE2H_U16(pDirRec->VolumeSeqNo.le));
+
+ /*
+ * Check values.
+ */
+ if (ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo) != pThis->idPrimaryVol)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_ROOT_VOLUME_SEQ_NO,
+ "Expected root dir to have same volume sequence number as primary volume: %#x, expected %#x",
+ ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo), pThis->idPrimaryVol);
+
+ if (ISO9660_GET_ENDIAN(&pDirRec->cbData) == 0)
+ return RTErrInfoSet(pThis->pErrInfo, VERR_ISOMK_IMPORT_ZERO_SIZED_ROOT_DIR, "Zero sized root dir");
+
+ if (ISO9660_GET_ENDIAN(&pDirRec->offExtent) >= pThis->cBlocksInPrimaryVolumeSpace)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_ROOT_DIR_EXTENT_OUT_OF_BOUNDS,
+ "Invalid root dir extent: %#RX32, max %#RX32",
+ ISO9660_GET_ENDIAN(&pDirRec->offExtent), pThis->cBlocksInPrimaryVolumeSpace);
+
+ if (pDirRec->cbDirRec < RT_UOFFSETOF(ISO9660DIRREC, achFileId))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_ROOT_DIR_REC_LENGTH,
+ "Root dir record size is too small: %#x (min %#x)",
+ pDirRec->cbDirRec, RT_UOFFSETOF(ISO9660DIRREC, achFileId));
+
+ if (!(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_ROOT_DIR_WITHOUT_DIR_FLAG,
+ "Root dir is not flagged as directory: %#x", pDirRec->fFileFlags);
+ if (pDirRec->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_ROOT_DIR_IS_MULTI_EXTENT,
+ "Root dir is cannot be multi-extent: %#x", pDirRec->fFileFlags);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Processes a primary volume descriptor, importing all files and stuff.
+ *
+ * @returns IPRT status code (safe to ignore, see pThis->rc).
+ * @param pThis The importer instance.
+ * @param pVolDesc The primary volume descriptor.
+ */
+static int rtFsIsoImportProcessPrimaryDesc(PRTFSISOMKIMPORTER pThis, PISO9660PRIMARYVOLDESC pVolDesc)
+{
+ /*
+ * Validate dual fields first.
+ */
+ if (pVolDesc->bFileStructureVersion != ISO9660_FILE_STRUCTURE_VERSION)
+ return rtFsIsoImpError(pThis, VERR_IOSMK_IMPORT_PRIMARY_VOL_DESC_VER,
+ "Unsupported file structure version: %#x", pVolDesc->bFileStructureVersion);
+
+ if (RT_LE2H_U16(pVolDesc->cbLogicalBlock.le) != RT_BE2H_U16(pVolDesc->cbLogicalBlock.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_PRIMARY_VOL_DESC,
+ "Mismatching logical block size: {%#RX16,%#RX16}",
+ RT_BE2H_U16(pVolDesc->cbLogicalBlock.be), RT_LE2H_U16(pVolDesc->cbLogicalBlock.le));
+ if (RT_LE2H_U32(pVolDesc->VolumeSpaceSize.le) != RT_BE2H_U32(pVolDesc->VolumeSpaceSize.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_PRIMARY_VOL_DESC,
+ "Mismatching volume space size: {%#RX32,%#RX32}",
+ RT_BE2H_U32(pVolDesc->VolumeSpaceSize.be), RT_LE2H_U32(pVolDesc->VolumeSpaceSize.le));
+ if (RT_LE2H_U16(pVolDesc->cVolumesInSet.le) != RT_BE2H_U16(pVolDesc->cVolumesInSet.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_PRIMARY_VOL_DESC,
+ "Mismatching volumes in set: {%#RX16,%#RX16}",
+ RT_BE2H_U16(pVolDesc->cVolumesInSet.be), RT_LE2H_U16(pVolDesc->cVolumesInSet.le));
+ if (RT_LE2H_U16(pVolDesc->VolumeSeqNo.le) != RT_BE2H_U16(pVolDesc->VolumeSeqNo.be))
+ {
+ /* Hack alert! An Windows NT 3.1 ISO was found to not have the big endian bit set here, so work around it. */
+ if ( pVolDesc->VolumeSeqNo.be == 0
+ && pVolDesc->VolumeSeqNo.le == RT_H2LE_U16_C(1))
+ pVolDesc->VolumeSeqNo.be = RT_H2BE_U16_C(1);
+ else
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_PRIMARY_VOL_DESC,
+ "Mismatching volume sequence no.: {%#RX16,%#RX16}",
+ RT_BE2H_U16(pVolDesc->VolumeSeqNo.be), RT_LE2H_U16(pVolDesc->VolumeSeqNo.le));
+ }
+ if (RT_LE2H_U32(pVolDesc->cbPathTable.le) != RT_BE2H_U32(pVolDesc->cbPathTable.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_PRIMARY_VOL_DESC,
+ "Mismatching path table size: {%#RX32,%#RX32}",
+ RT_BE2H_U32(pVolDesc->cbPathTable.be), RT_LE2H_U32(pVolDesc->cbPathTable.le));
+
+ /*
+ * Validate field values against our expectations.
+ */
+ if (ISO9660_GET_ENDIAN(&pVolDesc->cbLogicalBlock) != ISO9660_SECTOR_SIZE)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_LOGICAL_BLOCK_SIZE_NOT_2KB,
+ "Unsupported block size: %#x", ISO9660_GET_ENDIAN(&pVolDesc->cbLogicalBlock));
+
+ if (ISO9660_GET_ENDIAN(&pVolDesc->cVolumesInSet) != 1)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MORE_THAN_ONE_VOLUME_IN_SET,
+ "Volumes in set: %#x", ISO9660_GET_ENDIAN(&pVolDesc->cVolumesInSet));
+
+ if (ISO9660_GET_ENDIAN(&pVolDesc->VolumeSeqNo) != 1)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_INVALID_VOLUMNE_SEQ_NO,
+ "Unexpected volume sequence number: %#x", ISO9660_GET_ENDIAN(&pVolDesc->VolumeSeqNo));
+
+ /*
+ * Gather info we need.
+ */
+ pThis->cBlocksInPrimaryVolumeSpace = ISO9660_GET_ENDIAN(&pVolDesc->VolumeSpaceSize);
+ pThis->cbPrimaryVolumeSpace = pThis->cBlocksInPrimaryVolumeSpace * (uint64_t)ISO9660_SECTOR_SIZE;
+ pThis->cVolumesInSet = ISO9660_GET_ENDIAN(&pVolDesc->cVolumesInSet);
+ pThis->idPrimaryVol = ISO9660_GET_ENDIAN(&pVolDesc->VolumeSeqNo);
+
+ /*
+ * Validate the root directory record.
+ */
+ int rc = rtFsIsoImportValidateRootDirRec(pThis, &pVolDesc->RootDir.DirRec);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Import stuff if present and not opted out.
+ */
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_SYSTEM_ID))
+ rtFsIsoImportAsciiStringField(pThis, pVolDesc->achSystemId, sizeof(pVolDesc->achSystemId),
+ RTFSISOMAKERSTRINGPROP_SYSTEM_ID);
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_VOLUME_ID))
+ rtFsIsoImportAsciiStringField(pThis, pVolDesc->achVolumeId, sizeof(pVolDesc->achVolumeId),
+ RTFSISOMAKERSTRINGPROP_VOLUME_ID);
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_VOLUME_SET_ID))
+ rtFsIsoImportAsciiStringField(pThis, pVolDesc->achVolumeSetId, sizeof(pVolDesc->achVolumeSetId),
+ RTFSISOMAKERSTRINGPROP_VOLUME_SET_ID);
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_PUBLISHER_ID))
+ rtFsIsoImportAsciiStringField(pThis, pVolDesc->achPublisherId, sizeof(pVolDesc->achPublisherId),
+ RTFSISOMAKERSTRINGPROP_PUBLISHER_ID);
+ if (pThis->fFlags & RTFSISOMK_IMPORT_F_DATA_PREPARER_ID)
+ rtFsIsoImportAsciiStringField(pThis, pVolDesc->achDataPreparerId, sizeof(pVolDesc->achDataPreparerId),
+ RTFSISOMAKERSTRINGPROP_DATA_PREPARER_ID);
+ if (pThis->fFlags & RTFSISOMK_IMPORT_F_APPLICATION_ID)
+ rtFsIsoImportAsciiStringField(pThis, pVolDesc->achApplicationId, sizeof(pVolDesc->achApplicationId),
+ RTFSISOMAKERSTRINGPROP_APPLICATION_ID);
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_COPYRIGHT_FID))
+ rtFsIsoImportAsciiStringField(pThis, pVolDesc->achCopyrightFileId, sizeof(pVolDesc->achCopyrightFileId),
+ RTFSISOMAKERSTRINGPROP_COPYRIGHT_FILE_ID);
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_ABSTRACT_FID))
+ rtFsIsoImportAsciiStringField(pThis, pVolDesc->achAbstractFileId, sizeof(pVolDesc->achAbstractFileId),
+ RTFSISOMAKERSTRINGPROP_ABSTRACT_FILE_ID);
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_BIBLIO_FID))
+ rtFsIsoImportAsciiStringField(pThis, pVolDesc->achBibliographicFileId, sizeof(pVolDesc->achBibliographicFileId),
+ RTFSISOMAKERSTRINGPROP_BIBLIOGRAPHIC_FILE_ID);
+
+ /*
+ * Process the directory tree.
+ */
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_PRIMARY_ISO))
+ rc = rtFsIsoImportProcessIso9660Tree(pThis, ISO9660_GET_ENDIAN(&pVolDesc->RootDir.DirRec.offExtent),
+ ISO9660_GET_ENDIAN(&pVolDesc->RootDir.DirRec.cbData), false /*fUnicode*/);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Processes a secondary volume descriptor, if it is joliet we'll importing all
+ * the files and stuff.
+ *
+ * @returns IPRT status code (safe to ignore, see pThis->rc).
+ * @param pThis The importer instance.
+ * @param pVolDesc The primary volume descriptor.
+ */
+static int rtFsIsoImportProcessSupplementaryDesc(PRTFSISOMKIMPORTER pThis, PISO9660SUPVOLDESC pVolDesc)
+{
+ /*
+ * Validate dual fields first.
+ */
+ if (pVolDesc->bFileStructureVersion != ISO9660_FILE_STRUCTURE_VERSION)
+ return rtFsIsoImpError(pThis, VERR_IOSMK_IMPORT_SUP_VOL_DESC_VER,
+ "Unsupported file structure version: %#x", pVolDesc->bFileStructureVersion);
+
+ if (RT_LE2H_U16(pVolDesc->cbLogicalBlock.le) != RT_BE2H_U16(pVolDesc->cbLogicalBlock.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_SUP_VOL_DESC,
+ "Mismatching logical block size: {%#RX16,%#RX16}",
+ RT_BE2H_U16(pVolDesc->cbLogicalBlock.be), RT_LE2H_U16(pVolDesc->cbLogicalBlock.le));
+ if (RT_LE2H_U32(pVolDesc->VolumeSpaceSize.le) != RT_BE2H_U32(pVolDesc->VolumeSpaceSize.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_SUP_VOL_DESC,
+ "Mismatching volume space size: {%#RX32,%#RX32}",
+ RT_BE2H_U32(pVolDesc->VolumeSpaceSize.be), RT_LE2H_U32(pVolDesc->VolumeSpaceSize.le));
+ if (RT_LE2H_U16(pVolDesc->cVolumesInSet.le) != RT_BE2H_U16(pVolDesc->cVolumesInSet.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_SUP_VOL_DESC,
+ "Mismatching volumes in set: {%#RX16,%#RX16}",
+ RT_BE2H_U16(pVolDesc->cVolumesInSet.be), RT_LE2H_U16(pVolDesc->cVolumesInSet.le));
+ if (RT_LE2H_U16(pVolDesc->VolumeSeqNo.le) != RT_BE2H_U16(pVolDesc->VolumeSeqNo.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_SUP_VOL_DESC,
+ "Mismatching volume sequence no.: {%#RX16,%#RX16}",
+ RT_BE2H_U16(pVolDesc->VolumeSeqNo.be), RT_LE2H_U16(pVolDesc->VolumeSeqNo.le));
+ if (RT_LE2H_U32(pVolDesc->cbPathTable.le) != RT_BE2H_U32(pVolDesc->cbPathTable.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_SUP_VOL_DESC,
+ "Mismatching path table size: {%#RX32,%#RX32}",
+ RT_BE2H_U32(pVolDesc->cbPathTable.be), RT_LE2H_U32(pVolDesc->cbPathTable.le));
+
+ /*
+ * Validate field values against our expectations.
+ */
+ if (ISO9660_GET_ENDIAN(&pVolDesc->cbLogicalBlock) != ISO9660_SECTOR_SIZE)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_LOGICAL_BLOCK_SIZE_NOT_2KB,
+ "Unsupported block size: %#x", ISO9660_GET_ENDIAN(&pVolDesc->cbLogicalBlock));
+
+ if (ISO9660_GET_ENDIAN(&pVolDesc->cVolumesInSet) != pThis->cVolumesInSet)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_VOLUME_IN_SET_MISMATCH, "Volumes in set: %#x, expected %#x",
+ ISO9660_GET_ENDIAN(&pVolDesc->cVolumesInSet), pThis->cVolumesInSet);
+
+ if (ISO9660_GET_ENDIAN(&pVolDesc->VolumeSeqNo) != pThis->idPrimaryVol)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_INVALID_VOLUMNE_SEQ_NO,
+ "Unexpected volume sequence number: %#x (expected %#x)",
+ ISO9660_GET_ENDIAN(&pVolDesc->VolumeSeqNo), pThis->idPrimaryVol);
+
+ if (ISO9660_GET_ENDIAN(&pVolDesc->VolumeSpaceSize) != pThis->cBlocksInPrimaryVolumeSpace)
+ {
+ /* ubuntu-21.10-desktop-amd64.iso has 0x172f4e blocks (3 111 809 024 bytes) here
+ and 0x173838 blocks (3 116 482 560 bytes) in the primary, a difference of
+ -2282 blocks (-4 673 536 bytes). Guess something was omitted from the joliet
+ edition, not immediately obvious what though.
+
+ For now we'll just let it pass as long as the primary size is the larger.
+ (Not quite sure how the code will handle a supplementary volume spanning
+ more space, as I suspect it only uses the primary volume size for
+ validating block addresses and such.) */
+ LogRel(("rtFsIsoImportProcessSupplementaryDesc: Volume space size differs between primary and supplementary descriptors: %#x, primary %#x",
+ ISO9660_GET_ENDIAN(&pVolDesc->VolumeSpaceSize), pThis->cBlocksInPrimaryVolumeSpace));
+ if (ISO9660_GET_ENDIAN(&pVolDesc->VolumeSpaceSize) > pThis->cBlocksInPrimaryVolumeSpace)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_VOLUME_SPACE_SIZE_MISMATCH,
+ "Volume space given in the supplementary descriptor is larger than in the primary: %#x, primary %#x",
+ ISO9660_GET_ENDIAN(&pVolDesc->VolumeSpaceSize), pThis->cBlocksInPrimaryVolumeSpace);
+ }
+
+ /*
+ * Validate the root directory record.
+ */
+ int rc = rtFsIsoImportValidateRootDirRec(pThis, &pVolDesc->RootDir.DirRec);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Is this a joliet descriptor? Ignore if not.
+ */
+ uint8_t uJolietLevel = 0;
+ if ( pVolDesc->abEscapeSequences[0] == ISO9660_JOLIET_ESC_SEQ_0
+ && pVolDesc->abEscapeSequences[1] == ISO9660_JOLIET_ESC_SEQ_1)
+ switch (pVolDesc->abEscapeSequences[2])
+ {
+ case ISO9660_JOLIET_ESC_SEQ_2_LEVEL_1: uJolietLevel = 1; break;
+ case ISO9660_JOLIET_ESC_SEQ_2_LEVEL_2: uJolietLevel = 2; break;
+ case ISO9660_JOLIET_ESC_SEQ_2_LEVEL_3: uJolietLevel = 3; break;
+ default: Log(("rtFsIsoImportProcessSupplementaryDesc: last joliet escape sequence byte doesn't match: %#x\n",
+ pVolDesc->abEscapeSequences[2]));
+ }
+ if (uJolietLevel == 0)
+ return VINF_SUCCESS;
+
+ /*
+ * Only one joliet descriptor.
+ */
+ if (pThis->fSeenJoliet)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MULTIPLE_JOLIET_VOL_DESCS,
+ "More than one Joliet volume descriptor is not supported");
+ pThis->fSeenJoliet = true;
+
+ /*
+ * Import stuff if present and not opted out.
+ */
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_SYSTEM_ID))
+ rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achSystemId, sizeof(pVolDesc->achSystemId),
+ RTFSISOMAKERSTRINGPROP_SYSTEM_ID);
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_J_VOLUME_ID))
+ rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achVolumeId, sizeof(pVolDesc->achVolumeId),
+ RTFSISOMAKERSTRINGPROP_VOLUME_ID);
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_J_VOLUME_SET_ID))
+ rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achVolumeSetId, sizeof(pVolDesc->achVolumeSetId),
+ RTFSISOMAKERSTRINGPROP_VOLUME_SET_ID);
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_J_PUBLISHER_ID))
+ rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achPublisherId, sizeof(pVolDesc->achPublisherId),
+ RTFSISOMAKERSTRINGPROP_PUBLISHER_ID);
+ if (pThis->fFlags & RTFSISOMK_IMPORT_F_J_DATA_PREPARER_ID)
+ rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achDataPreparerId, sizeof(pVolDesc->achDataPreparerId),
+ RTFSISOMAKERSTRINGPROP_DATA_PREPARER_ID);
+ if (pThis->fFlags & RTFSISOMK_IMPORT_F_J_APPLICATION_ID)
+ rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achApplicationId, sizeof(pVolDesc->achApplicationId),
+ RTFSISOMAKERSTRINGPROP_APPLICATION_ID);
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_J_COPYRIGHT_FID))
+ rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achCopyrightFileId, sizeof(pVolDesc->achCopyrightFileId),
+ RTFSISOMAKERSTRINGPROP_COPYRIGHT_FILE_ID);
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_J_ABSTRACT_FID))
+ rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achAbstractFileId, sizeof(pVolDesc->achAbstractFileId),
+ RTFSISOMAKERSTRINGPROP_ABSTRACT_FILE_ID);
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_J_BIBLIO_FID))
+ rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achBibliographicFileId, sizeof(pVolDesc->achBibliographicFileId),
+ RTFSISOMAKERSTRINGPROP_BIBLIOGRAPHIC_FILE_ID);
+
+ /*
+ * Process the directory tree.
+ */
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_JOLIET))
+ return rtFsIsoImportProcessIso9660Tree(pThis, ISO9660_GET_ENDIAN(&pVolDesc->RootDir.DirRec.offExtent),
+ ISO9660_GET_ENDIAN(&pVolDesc->RootDir.DirRec.cbData), true /*fUnicode*/);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Checks out an El Torito boot image to see if it requires info table patching.
+ *
+ * @returns IPRT status code (ignored).
+ * @param pThis The ISO importer instance.
+ * @param idxImageObj The configuration index of the image.
+ * @param offBootImage The block offset of the image.
+ */
+static int rtFsIsoImportProcessElToritoImage(PRTFSISOMKIMPORTER pThis, uint32_t idxImageObj, uint32_t offBootImage)
+{
+ ISO9660SYSLINUXINFOTABLE InfoTable;
+ int rc = RTVfsFileReadAt(pThis->hSrcFile, offBootImage * (uint64_t)ISO9660_SECTOR_SIZE + ISO9660SYSLINUXINFOTABLE_OFFSET,
+ &InfoTable, sizeof(InfoTable), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if ( RT_LE2H_U32(InfoTable.offBootFile) == offBootImage
+ && RT_LE2H_U32(InfoTable.offPrimaryVolDesc) == pThis->offPrimaryVolDesc
+ && ASMMemIsAllU8(&InfoTable.auReserved[0], sizeof(InfoTable.auReserved), 0) )
+ {
+ rc = RTFsIsoMakerObjEnableBootInfoTablePatching(pThis->hIsoMaker, idxImageObj, true /*fEnable*/);
+ if (RT_FAILURE(rc))
+ return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerObjEnableBootInfoTablePatching failed: %Rrc", rc);
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Processes a boot catalog default or section entry.
+ *
+ * @returns IPRT status code (ignored).
+ * @param pThis The ISO importer instance.
+ * @param iEntry The boot catalog entry number. This is 1 for
+ * the default entry, and 3+ for section entries.
+ * @param cMaxEntries Maximum number of entries.
+ * @param pEntry The entry to process.
+ * @param pcSkip Where to return the number of extension entries to skip.
+ */
+static int rtFsIsoImportProcessElToritoSectionEntry(PRTFSISOMKIMPORTER pThis, uint32_t iEntry, uint32_t cMaxEntries,
+ PCISO9660ELTORITOSECTIONENTRY pEntry, uint32_t *pcSkip)
+{
+ *pcSkip = 0;
+
+ /*
+ * Check the boot indicator type for entry 1.
+ */
+ if ( pEntry->bBootIndicator != ISO9660_ELTORITO_BOOT_INDICATOR_BOOTABLE
+ && pEntry->bBootIndicator != ISO9660_ELTORITO_BOOT_INDICATOR_NOT_BOOTABLE)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_DEF_ENTRY_INVALID_BOOT_IND,
+ "Default boot catalog entry has an invalid boot indicator: %#x", pEntry->bBootIndicator);
+
+ /*
+ * Check the media type and flags.
+ */
+ uint32_t cbDefaultSize;
+ uint8_t bMediaType = pEntry->bBootMediaType;
+ switch (bMediaType & ISO9660_ELTORITO_BOOT_MEDIA_TYPE_MASK)
+ {
+ case ISO9660_ELTORITO_BOOT_MEDIA_TYPE_FLOPPY_1_2_MB:
+ cbDefaultSize = 512 * 80 * 15 * 2;
+ break;
+
+ case ISO9660_ELTORITO_BOOT_MEDIA_TYPE_FLOPPY_1_44_MB:
+ cbDefaultSize = 512 * 80 * 18 * 2;
+ break;
+
+ case ISO9660_ELTORITO_BOOT_MEDIA_TYPE_FLOPPY_2_88_MB:
+ cbDefaultSize = 512 * 80 * 36 * 2;
+ break;
+
+ case ISO9660_ELTORITO_BOOT_MEDIA_TYPE_NO_EMULATION:
+ case ISO9660_ELTORITO_BOOT_MEDIA_TYPE_HARD_DISK:
+ cbDefaultSize = 0;
+ break;
+
+ default:
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_INVALID_BOOT_MEDIA_TYPE,
+ "Boot catalog entry #%#x has an invalid boot media type: %#x", bMediaType);
+ }
+
+ if (iEntry == 1)
+ {
+ if (bMediaType & ISO9660_ELTORITO_BOOT_MEDIA_F_MASK)
+ {
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_DEF_ENTRY_INVALID_FLAGS,
+ "Boot catalog entry #%#x has an invalid boot media type: %#x", bMediaType);
+ bMediaType &= ~ISO9660_ELTORITO_BOOT_MEDIA_F_MASK;
+ }
+ }
+ else
+ {
+ if (bMediaType & ISO9660_ELTORITO_BOOT_MEDIA_F_RESERVED)
+ {
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_ENTRY_RESERVED_FLAG,
+ "Boot catalog entry #%#x has an invalid boot media type: %#x", bMediaType);
+ bMediaType &= ~ISO9660_ELTORITO_BOOT_MEDIA_F_RESERVED;
+ }
+ }
+
+ /*
+ * Complain if bUnused is used.
+ */
+ if (pEntry->bUnused != 0)
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_ENTRY_USES_UNUSED_FIELD,
+ "Boot catalog entry #%#x has a non-zero unused field: %#x", pEntry->bUnused);
+
+ /*
+ * Check out the boot image offset and turn that into an index of a file
+ */
+ uint32_t offBootImage = RT_LE2H_U32(pEntry->offBootImage);
+ if (offBootImage >= pThis->cBlocksInSrcFile)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_ENTRY_IMAGE_OUT_OF_BOUNDS,
+ "Boot catalog entry #%#x has an out of bound boot image block number: %#RX32, max %#RX32",
+ offBootImage, pThis->cBlocksInPrimaryVolumeSpace);
+
+ int rc;
+ uint32_t idxImageObj;
+ PRTFSISOMKIMPBLOCK2FILE pBlock2File = (PRTFSISOMKIMPBLOCK2FILE)RTAvlU32Get(&pThis->Block2FileRoot, offBootImage);
+ if (pBlock2File)
+ idxImageObj = pBlock2File->idxObj;
+ else
+ {
+ if (cbDefaultSize == 0)
+ {
+ pBlock2File = (PRTFSISOMKIMPBLOCK2FILE)RTAvlU32GetBestFit(&pThis->Block2FileRoot, offBootImage, true /*fAbove*/);
+ if (pBlock2File)
+ cbDefaultSize = RT_MIN(pBlock2File->Core.Key - offBootImage, UINT32_MAX / ISO9660_SECTOR_SIZE + 1)
+ * ISO9660_SECTOR_SIZE;
+ else if (offBootImage < pThis->cBlocksInSrcFile)
+ cbDefaultSize = RT_MIN(pThis->cBlocksInSrcFile - offBootImage, UINT32_MAX / ISO9660_SECTOR_SIZE + 1)
+ * ISO9660_SECTOR_SIZE;
+ else
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_ENTRY_UNKNOWN_IMAGE_SIZE,
+ "Boot catalog entry #%#x has an invalid boot media type: %#x", bMediaType);
+ }
+
+ if (pThis->idxSrcFile != UINT32_MAX)
+ {
+ rc = RTFsIsoMakerAddCommonSourceFile(pThis->hIsoMaker, pThis->hSrcFile, &pThis->idxSrcFile);
+ if (RT_FAILURE(rc))
+ return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerAddCommonSourceFile failed: %Rrc", rc);
+ Assert(pThis->idxSrcFile != UINT32_MAX);
+ }
+
+ rc = RTFsIsoMakerAddUnnamedFileWithCommonSrc(pThis->hIsoMaker, pThis->idxSrcFile,
+ offBootImage * (uint64_t)ISO9660_SECTOR_SIZE,
+ cbDefaultSize, NULL, &idxImageObj);
+ if (RT_FAILURE(rc))
+ return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerAddUnnamedFileWithCommonSrc failed on boot entry #%#x: %Rrc",
+ iEntry, rc);
+ }
+
+ /*
+ * Deal with selection criteria. Use the last sector of abBuf to gather it
+ * into a single data chunk.
+ */
+ size_t cbSelCrit = 0;
+ uint8_t *pbSelCrit = &pThis->abBuf[sizeof(pThis->abBuf) - ISO9660_SECTOR_SIZE];
+ if (pEntry->bSelectionCriteriaType != ISO9660_ELTORITO_SEL_CRIT_TYPE_NONE)
+ {
+ memcpy(pbSelCrit, pEntry->abSelectionCriteria, sizeof(pEntry->abSelectionCriteria));
+ cbSelCrit = sizeof(pEntry->abSelectionCriteria);
+
+ if ( (bMediaType & ISO9660_ELTORITO_BOOT_MEDIA_F_CONTINUATION)
+ && iEntry + 1 < cMaxEntries)
+ {
+ uint32_t iExtEntry = iEntry + 1;
+ PCISO9660ELTORITOSECTIONENTRYEXT pExtEntry = (PCISO9660ELTORITOSECTIONENTRYEXT)pEntry;
+ for (;;)
+ {
+ pExtEntry++;
+
+ if (pExtEntry->bExtensionId != ISO9660_ELTORITO_SECTION_ENTRY_EXT_ID)
+ {
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_EXT_ENTRY_INVALID_ID,
+ "Invalid header ID for extension entry #%#x: %#x", iExtEntry, pExtEntry->bExtensionId);
+ break;
+ }
+ *pcSkip += 1;
+
+ memcpy(&pbSelCrit[cbSelCrit], pExtEntry->abSelectionCriteria, sizeof(pExtEntry->abSelectionCriteria));
+ cbSelCrit += sizeof(pExtEntry->abSelectionCriteria);
+
+ if (pExtEntry->fFlags & ISO9660_ELTORITO_SECTION_ENTRY_EXT_F_UNUSED_MASK)
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_EXT_ENTRY_UNDEFINED_FLAGS,
+ "Boot catalog extension entry #%#x uses undefined flags: %#x", iExtEntry, pExtEntry->fFlags);
+
+ iExtEntry++;
+ if (!(pExtEntry->fFlags & ISO9660_ELTORITO_SECTION_ENTRY_EXT_F_MORE))
+ break;
+ if (iExtEntry >= cMaxEntries)
+ {
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_EXT_ENTRY_END_OF_SECTOR,
+ "Boot catalog extension entry #%#x sets the MORE flag, but we have reached the end of the boot catalog sector");
+ break;
+ }
+ }
+ Assert(*pcSkip = iExtEntry - iEntry);
+ }
+ else if (bMediaType & ISO9660_ELTORITO_BOOT_MEDIA_F_CONTINUATION)
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_ENTRY_CONTINUATION_EOS,
+ "Boot catalog extension entry #%#x sets the MORE flag, but we have reached the end of the boot catalog sector");
+ }
+ else if (bMediaType & ISO9660_ELTORITO_BOOT_MEDIA_F_CONTINUATION)
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_ENTRY_CONTINUATION_WITH_NONE,
+ "Boot catalog entry #%#x uses the continuation flag with selection criteria NONE", iEntry);
+
+ /*
+ * Add the entry.
+ */
+ rc = RTFsIsoMakerBootCatSetSectionEntry(pThis->hIsoMaker, iEntry, idxImageObj, bMediaType, pEntry->bSystemType,
+ pEntry->bBootIndicator == ISO9660_ELTORITO_BOOT_INDICATOR_BOOTABLE,
+ pEntry->uLoadSeg, pEntry->cEmulatedSectorsToLoad,
+ pEntry->bSelectionCriteriaType, pbSelCrit, cbSelCrit);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pResults->cBootCatEntries += 1 + *pcSkip;
+ rc = rtFsIsoImportProcessElToritoImage(pThis, idxImageObj, offBootImage);
+ }
+ else
+ rtFsIsoImpError(pThis, rc, "RTFsIsoMakerBootCatSetSectionEntry failed for entry #%#x: %Rrc", iEntry, rc);
+ return rc;
+}
+
+
+
+/**
+ * Processes a boot catalog section header entry.
+ *
+ * @returns IPRT status code (ignored).
+ * @param pThis The ISO importer instance.
+ * @param iEntry The boot catalog entry number.
+ * @param pEntry The entry to process.
+ */
+static int rtFsIsoImportProcessElToritoSectionHeader(PRTFSISOMKIMPORTER pThis, uint32_t iEntry,
+ PCISO9660ELTORITOSECTIONHEADER pEntry, char pszId[32])
+{
+ Assert(pEntry->bHeaderId == ISO9660_ELTORITO_HEADER_ID_SECTION_HEADER);
+
+ /* Deal with the string. ASSUME it doesn't contain zeros in non-terminal positions. */
+ if (pEntry->achSectionId[0] == '\0')
+ pszId = NULL;
+ else
+ {
+ memcpy(pszId, pEntry->achSectionId, sizeof(pEntry->achSectionId));
+ pszId[sizeof(pEntry->achSectionId)] = '\0';
+ }
+
+ int rc = RTFsIsoMakerBootCatSetSectionHeaderEntry(pThis->hIsoMaker, iEntry, RT_LE2H_U16(pEntry->cEntries),
+ pEntry->bPlatformId, pszId);
+ if (RT_SUCCESS(rc))
+ pThis->pResults->cBootCatEntries++;
+ else
+ rtFsIsoImpError(pThis, rc,
+ "RTFsIsoMakerBootCatSetSectionHeaderEntry failed for entry #%#x (bPlatformId=%#x cEntries=%#x): %Rrc",
+ iEntry, RT_LE2H_U16(pEntry->cEntries), pEntry->bPlatformId, rc);
+ return rc;
+}
+
+
+/**
+ * Processes a El Torito volume descriptor.
+ *
+ * @returns IPRT status code (ignorable).
+ * @param pThis The ISO importer instance.
+ * @param pVolDesc The volume descriptor to process.
+ */
+static int rtFsIsoImportProcessElToritoDesc(PRTFSISOMKIMPORTER pThis, PISO9660BOOTRECORDELTORITO pVolDesc)
+{
+ /*
+ * Read the boot catalog into the abBuf.
+ */
+ uint32_t offBootCatalog = RT_LE2H_U32(pVolDesc->offBootCatalog);
+ if (offBootCatalog >= pThis->cBlocksInPrimaryVolumeSpace)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_BAD_OUT_OF_BOUNDS,
+ "Boot catalog block number is out of bounds: %#RX32, max %#RX32",
+ offBootCatalog, pThis->cBlocksInPrimaryVolumeSpace);
+
+ int rc = RTVfsFileReadAt(pThis->hSrcFile, offBootCatalog * (uint64_t)ISO9660_SECTOR_SIZE,
+ pThis->abBuf, ISO9660_SECTOR_SIZE, NULL);
+ if (RT_FAILURE(rc))
+ return rtFsIsoImpError(pThis, rc, "Error reading boot catalog at block #%#RX32: %Rrc", offBootCatalog, rc);
+
+
+ /*
+ * Process the 'validation entry'.
+ */
+ PCISO9660ELTORITOVALIDATIONENTRY pValEntry = (PCISO9660ELTORITOVALIDATIONENTRY)&pThis->abBuf[0];
+ if (pValEntry->bHeaderId != ISO9660_ELTORITO_HEADER_ID_VALIDATION_ENTRY)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_BAD_VALIDATION_HEADER_ID,
+ "Invalid boot catalog validation entry header ID: %#x, expected %#x",
+ pValEntry->bHeaderId, ISO9660_ELTORITO_HEADER_ID_VALIDATION_ENTRY);
+
+ if ( pValEntry->bKey1 != ISO9660_ELTORITO_KEY_BYTE_1
+ || pValEntry->bKey2 != ISO9660_ELTORITO_KEY_BYTE_2)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_BAD_VALIDATION_KEYS,
+ "Invalid boot catalog validation entry keys: %#x %#x, expected %#x %#x",
+ pValEntry->bKey1, pValEntry->bKey2, ISO9660_ELTORITO_KEY_BYTE_1, ISO9660_ELTORITO_KEY_BYTE_2);
+
+ /* Check the checksum (should sum up to be zero). */
+ uint16_t uChecksum = 0;
+ uint16_t const *pu16 = (uint16_t const *)pValEntry;
+ size_t cLeft = sizeof(*pValEntry) / sizeof(uint16_t);
+ while (cLeft-- > 0)
+ {
+ uChecksum += RT_LE2H_U16(*pu16);
+ pu16++;
+ }
+ if (uChecksum != 0)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_BAD_VALIDATION_CHECKSUM,
+ "Invalid boot catalog validation entry checksum: %#x, expected 0", uChecksum);
+
+ /* The string ID. ASSUME no leading zeros in valid strings. */
+ const char *pszId = NULL;
+ char szId[32];
+ if (pValEntry->achId[0] != '\0')
+ {
+ memcpy(szId, pValEntry->achId, sizeof(pValEntry->achId));
+ szId[sizeof(pValEntry->achId)] = '\0';
+ pszId = szId;
+ }
+
+ /*
+ * Before we tell the ISO maker about the validation entry, we need to sort
+ * out the file backing the boot catalog. This isn't fatal if it fails.
+ */
+ PRTFSISOMKIMPBLOCK2FILE pBlock2File = (PRTFSISOMKIMPBLOCK2FILE)RTAvlU32Get(&pThis->Block2FileRoot, offBootCatalog);
+ if (pBlock2File)
+ {
+ rc = RTFsIsoMakerBootCatSetFile(pThis->hIsoMaker, pBlock2File->idxObj);
+ if (RT_FAILURE(rc))
+ rtFsIsoImpError(pThis, rc, "RTFsIsoMakerBootCatSetFile failed: %Rrc", rc);
+ }
+
+ /*
+ * Set the validation entry.
+ */
+ rc = RTFsIsoMakerBootCatSetValidationEntry(pThis->hIsoMaker, pValEntry->bPlatformId, pszId);
+ if (RT_FAILURE(rc))
+ return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerBootCatSetValidationEntry(,%#x,%s) failed: %Rrc",
+ pValEntry->bPlatformId, pszId);
+ Assert(pThis->pResults->cBootCatEntries == UINT32_MAX);
+ pThis->pResults->cBootCatEntries = 0;
+
+ /*
+ * Process the default entry and any subsequent entries.
+ */
+ bool fSeenFinal = false;
+ uint32_t const cMaxEntries = ISO9660_SECTOR_SIZE / ISO9660_ELTORITO_ENTRY_SIZE;
+ for (uint32_t iEntry = 1; iEntry < cMaxEntries; iEntry++)
+ {
+ uint8_t const *pbEntry = &pThis->abBuf[iEntry * ISO9660_ELTORITO_ENTRY_SIZE];
+ uint8_t const idHeader = *pbEntry;
+
+ /* KLUDGE ALERT! Older ISO images, like RHEL5-Server-20070208.0-x86_64-DVD.iso lacks
+ terminator entry. So, quietly stop with an entry that's all zeros. */
+ if ( idHeader == ISO9660_ELTORITO_BOOT_INDICATOR_NOT_BOOTABLE /* 0x00 */
+ && iEntry != 1 /* default */
+ && ASMMemIsZero(pbEntry, ISO9660_ELTORITO_ENTRY_SIZE))
+ return rc;
+
+ if ( iEntry == 1 /* default*/
+ || idHeader == ISO9660_ELTORITO_BOOT_INDICATOR_BOOTABLE
+ || idHeader == ISO9660_ELTORITO_BOOT_INDICATOR_NOT_BOOTABLE)
+ {
+ uint32_t cSkip = 0;
+ rtFsIsoImportProcessElToritoSectionEntry(pThis, iEntry, cMaxEntries, (PCISO9660ELTORITOSECTIONENTRY)pbEntry, &cSkip);
+ iEntry += cSkip;
+ }
+ else if (idHeader == ISO9660_ELTORITO_HEADER_ID_SECTION_HEADER)
+ rtFsIsoImportProcessElToritoSectionHeader(pThis, iEntry, (PCISO9660ELTORITOSECTIONHEADER)pbEntry, szId);
+ else if (idHeader == ISO9660_ELTORITO_HEADER_ID_FINAL_SECTION_HEADER)
+ {
+ fSeenFinal = true;
+ break;
+ }
+ else
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_UNKNOWN_HEADER_ID,
+ "Unknown boot catalog header ID for entry #%#x: %#x", iEntry, idHeader);
+ }
+
+ if (!fSeenFinal)
+ rc = rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_MISSING_FINAL_OR_TOO_BIG,
+ "Boot catalog is probably larger than a sector, or it's missing the final section header entry");
+ return rc;
+}
+
+
+/**
+ * Imports an existing ISO.
+ *
+ * Just like other source files, the existing image must remain present and
+ * unmodified till the ISO maker is done with it.
+ *
+ * @returns IRPT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param hIsoFile VFS file handle to the existing image to import / clone.
+ * @param fFlags Reserved for the future, MBZ.
+ * @param poffError Where to return the position in @a pszIso
+ * causing trouble when opening it for reading.
+ * Optional.
+ * @param pErrInfo Where to return additional error information.
+ * Optional.
+ */
+RTDECL(int) RTFsIsoMakerImport(RTFSISOMAKER hIsoMaker, RTVFSFILE hIsoFile, uint32_t fFlags,
+ PRTFSISOMAKERIMPORTRESULTS pResults, PRTERRINFO pErrInfo)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtrReturn(pResults, VERR_INVALID_POINTER);
+ pResults->cAddedNames = 0;
+ pResults->cAddedDirs = 0;
+ pResults->cbAddedDataBlocks = 0;
+ pResults->cAddedFiles = 0;
+ pResults->cAddedSymlinks = 0;
+ pResults->cBootCatEntries = UINT32_MAX;
+ pResults->cbSysArea = 0;
+ pResults->cErrors = 0;
+ AssertReturn(!(fFlags & ~RTFSISOMK_IMPORT_F_VALID_MASK), VERR_INVALID_FLAGS);
+
+ /*
+ * Get the file size.
+ */
+ uint64_t cbSrcFile = 0;
+ int rc = RTVfsFileQuerySize(hIsoFile, &cbSrcFile);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate and init the importer state.
+ */
+ PRTFSISOMKIMPORTER pThis = (PRTFSISOMKIMPORTER)RTMemAllocZ(sizeof(*pThis));
+ if (pThis)
+ {
+ pThis->hIsoMaker = hIsoMaker;
+ pThis->fFlags = fFlags;
+ pThis->rc = VINF_SUCCESS;
+ pThis->pErrInfo = pErrInfo;
+ pThis->hSrcFile = hIsoFile;
+ pThis->cbSrcFile = cbSrcFile;
+ pThis->cBlocksInSrcFile = cbSrcFile / ISO9660_SECTOR_SIZE;
+ pThis->idxSrcFile = UINT32_MAX;
+ //pThis->Block2FileRoot = NULL;
+ //pThis->cBlocksInPrimaryVolumeSpace = 0;
+ //pThis->cbPrimaryVolumeSpace = 0
+ //pThis->cVolumesInSet = 0;
+ //pThis->idPrimaryVol = 0;
+ //pThis->fSeenJoliet = false;
+ pThis->pResults = pResults;
+ //pThis->fSuspSeenSP = false;
+ //pThis->offSuspSkip = 0;
+ pThis->offRockBuf = UINT64_MAX;
+
+ /*
+ * Check if this looks like a plausible ISO by checking out the first volume descriptor.
+ */
+ rc = RTVfsFileReadAt(hIsoFile, _32K, &pThis->uSectorBuf.PrimVolDesc, sizeof(pThis->uSectorBuf.PrimVolDesc), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if ( pThis->uSectorBuf.VolDescHdr.achStdId[0] == ISO9660VOLDESC_STD_ID_0
+ && pThis->uSectorBuf.VolDescHdr.achStdId[1] == ISO9660VOLDESC_STD_ID_1
+ && pThis->uSectorBuf.VolDescHdr.achStdId[2] == ISO9660VOLDESC_STD_ID_2
+ && pThis->uSectorBuf.VolDescHdr.achStdId[3] == ISO9660VOLDESC_STD_ID_3
+ && pThis->uSectorBuf.VolDescHdr.achStdId[4] == ISO9660VOLDESC_STD_ID_4
+ && ( pThis->uSectorBuf.VolDescHdr.bDescType == ISO9660VOLDESC_TYPE_PRIMARY
+ || pThis->uSectorBuf.VolDescHdr.bDescType == ISO9660VOLDESC_TYPE_BOOT_RECORD) )
+ {
+ /*
+ * Process the volume descriptors using the sector buffer, starting
+ * with the one we've already got sitting there. We postpone processing
+ * the el torito one till after the others, so we can name files and size
+ * referenced in it.
+ */
+ uint32_t cPrimaryVolDescs = 0;
+ uint32_t iElTorito = UINT32_MAX;
+ uint32_t iVolDesc = 0;
+ for (;;)
+ {
+ switch (pThis->uSectorBuf.VolDescHdr.bDescType)
+ {
+ case ISO9660VOLDESC_TYPE_PRIMARY:
+ cPrimaryVolDescs++;
+ if (cPrimaryVolDescs == 1)
+ {
+ pThis->offPrimaryVolDesc = _32K / ISO9660_SECTOR_SIZE + iVolDesc;
+ rtFsIsoImportProcessPrimaryDesc(pThis, &pThis->uSectorBuf.PrimVolDesc);
+ }
+ else
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MULTIPLE_PRIMARY_VOL_DESCS,
+ "Only a single primary volume descriptor is currently supported");
+ break;
+
+ case ISO9660VOLDESC_TYPE_SUPPLEMENTARY:
+ if (cPrimaryVolDescs > 0)
+ rtFsIsoImportProcessSupplementaryDesc(pThis, &pThis->uSectorBuf.SupVolDesc);
+ else
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_SUPPLEMENTARY_BEFORE_PRIMARY,
+ "Primary volume descriptor expected before any supplementary descriptors!");
+ break;
+
+ case ISO9660VOLDESC_TYPE_BOOT_RECORD:
+ if (strcmp(pThis->uSectorBuf.ElToritoDesc.achBootSystemId,
+ ISO9660BOOTRECORDELTORITO_BOOT_SYSTEM_ID) == 0)
+ {
+ if (iElTorito == UINT32_MAX)
+ iElTorito = iVolDesc;
+ else
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MULTIPLE_EL_TORITO_DESCS,
+ "Only a single El Torito descriptor exepcted!");
+ }
+ break;
+
+ case ISO9660VOLDESC_TYPE_PARTITION:
+ /* ignore for now */
+ break;
+
+ case ISO9660VOLDESC_TYPE_TERMINATOR:
+ AssertFailed();
+ break;
+ }
+
+
+ /*
+ * Read the next volume descriptor and check the signature.
+ */
+ iVolDesc++;
+ if (iVolDesc >= 32)
+ {
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_TOO_MANY_VOL_DESCS, "Parses at most 32 volume descriptors");
+ break;
+ }
+
+ rc = RTVfsFileReadAt(hIsoFile, _32K + iVolDesc * ISO9660_SECTOR_SIZE,
+ &pThis->uSectorBuf, sizeof(pThis->uSectorBuf), NULL);
+ if (RT_FAILURE(rc))
+ {
+ rtFsIsoImpError(pThis, rc, "Error reading the volume descriptor #%u at %#RX32: %Rrc",
+ iVolDesc, _32K + iVolDesc * ISO9660_SECTOR_SIZE, rc);
+ break;
+ }
+
+ if ( pThis->uSectorBuf.VolDescHdr.achStdId[0] != ISO9660VOLDESC_STD_ID_0
+ || pThis->uSectorBuf.VolDescHdr.achStdId[1] != ISO9660VOLDESC_STD_ID_1
+ || pThis->uSectorBuf.VolDescHdr.achStdId[2] != ISO9660VOLDESC_STD_ID_2
+ || pThis->uSectorBuf.VolDescHdr.achStdId[3] != ISO9660VOLDESC_STD_ID_3
+ || pThis->uSectorBuf.VolDescHdr.achStdId[4] != ISO9660VOLDESC_STD_ID_4)
+ {
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_INVALID_VOL_DESC_HDR,
+ "Invalid volume descriptor header #%u at %#RX32: %.*Rhxs",
+ iVolDesc, _32K + iVolDesc * ISO9660_SECTOR_SIZE,
+ (int)sizeof(pThis->uSectorBuf.VolDescHdr), &pThis->uSectorBuf.VolDescHdr);
+ break;
+ }
+ /** @todo UDF support. */
+ if (pThis->uSectorBuf.VolDescHdr.bDescType == ISO9660VOLDESC_TYPE_TERMINATOR)
+ break;
+ }
+
+ /*
+ * Process the system area.
+ */
+ if (RT_SUCCESS(pThis->rc) || pThis->idxSrcFile != UINT32_MAX)
+ {
+ rc = RTVfsFileReadAt(hIsoFile, 0, pThis->abBuf, _32K, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (!ASMMemIsAllU8(pThis->abBuf, _32K, 0))
+ {
+ /* Drop zero sectors from the end. */
+ uint32_t cbSysArea = _32K;
+ while ( cbSysArea >= ISO9660_SECTOR_SIZE
+ && ASMMemIsAllU8(&pThis->abBuf[cbSysArea - ISO9660_SECTOR_SIZE], ISO9660_SECTOR_SIZE, 0))
+ cbSysArea -= ISO9660_SECTOR_SIZE;
+
+ /** @todo HFS */
+ pThis->pResults->cbSysArea = cbSysArea;
+ rc = RTFsIsoMakerSetSysAreaContent(hIsoMaker, pThis->abBuf, cbSysArea, 0);
+ if (RT_FAILURE(rc))
+ rtFsIsoImpError(pThis, rc, "RTFsIsoMakerSetSysAreaContent failed: %Rrc", rc);
+ }
+ }
+ else
+ rtFsIsoImpError(pThis, rc, "Error reading the system area (0..32KB): %Rrc", rc);
+ }
+
+ /*
+ * Do the El Torito descriptor.
+ */
+ if ( iElTorito != UINT32_MAX
+ && !(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_BOOT)
+ && (RT_SUCCESS(pThis->rc) || pThis->idxSrcFile != UINT32_MAX))
+ {
+ rc = RTVfsFileReadAt(hIsoFile, _32K + iElTorito * ISO9660_SECTOR_SIZE,
+ &pThis->uSectorBuf, sizeof(pThis->uSectorBuf), NULL);
+ if (RT_SUCCESS(rc))
+ rtFsIsoImportProcessElToritoDesc(pThis, &pThis->uSectorBuf.ElToritoDesc);
+ else
+ rtFsIsoImpError(pThis, rc, "Error reading the El Torito volume descriptor at %#RX32: %Rrc",
+ _32K + iElTorito * ISO9660_SECTOR_SIZE, rc);
+ }
+
+ /*
+ * Return the first error status.
+ */
+ rc = pThis->rc;
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, VERR_ISOMK_IMPORT_UNKNOWN_FORMAT, "Invalid volume descriptor header: %.*Rhxs",
+ (int)sizeof(pThis->uSectorBuf.VolDescHdr), &pThis->uSectorBuf.VolDescHdr);
+ }
+
+ /*
+ * Destroy the state.
+ */
+ RTAvlU32Destroy(&pThis->Block2FileRoot, rtFsIsoMakerImportDestroyData2File, NULL);
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ return rc;
+}
+
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);
+
diff --git a/src/VBox/Runtime/common/fs/ntfsvfs.cpp b/src/VBox/Runtime/common/fs/ntfsvfs.cpp
new file mode 100644
index 00000000..865ce3fb
--- /dev/null
+++ b/src/VBox/Runtime/common/fs/ntfsvfs.cpp
@@ -0,0 +1,5698 @@
+/* $Id: ntfsvfs.cpp $ */
+/** @file
+ * IPRT - NTFS Virtual Filesystem, currently only for reading allocation bitmap.
+ */
+
+/*
+ * Copyright (C) 2012-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 <iprt/fsvfs.h>
+
+#include <iprt/asm.h>
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+#include <iprt/utf16.h>
+#include <iprt/formats/ntfs.h>
+
+#include <internal/fs.h> /* For RTFSMODE_SYMLINK_REPARSE_TAG. */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The maximum bitmap size to try cache in its entirity (in bytes). */
+#define RTFSNTFS_MAX_WHOLE_BITMAP_CACHE _64K
+/** The maximum node cache size (in bytes). */
+#if ARCH_BITS >= 64
+# define RTFSNTFS_MAX_CORE_CACHE_SIZE _512K
+#else
+# define RTFSNTFS_MAX_CORE_CACHE_SIZE _128K
+#endif
+/** The maximum node cache size (in bytes). */
+#if ARCH_BITS >= 64
+# define RTFSNTFS_MAX_NODE_CACHE_SIZE _1M
+#else
+# define RTFSNTFS_MAX_NODE_CACHE_SIZE _256K
+#endif
+
+/** Makes a combined NTFS version value.
+ * @see RTFSNTFSVOL::uNtfsVersion */
+#define RTFSNTFS_MAKE_VERSION(a_uMajor, a_uMinor) RT_MAKE_U16(a_uMinor, a_uMajor)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to the instance data for a NTFS volume. */
+typedef struct RTFSNTFSVOL *PRTFSNTFSVOL;
+/** Pointer to a NTFS MFT record. */
+typedef struct RTFSNTFSMFTREC *PRTFSNTFSMFTREC;
+/** Poitner to a NTFS core object record. */
+typedef struct RTFSNTFSCORE *PRTFSNTFSCORE;
+/** Pointer to an index node. */
+typedef struct RTFSNTFSIDXNODE *PRTFSNTFSIDXNODE;
+/** Pointer to a shared NTFS directory object. */
+typedef struct RTFSNTFSDIRSHRD *PRTFSNTFSDIRSHRD;
+/** Pointer to a shared NTFS file object. */
+typedef struct RTFSNTFSFILESHRD *PRTFSNTFSFILESHRD;
+
+
+/**
+ * NTFS disk allocation extent (internal representation).
+ */
+typedef struct RTFSNTFSEXTENT
+{
+ /** 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;
+} RTFSNTFSEXTENT;
+/** Pointer to an NTFS 9660 extent. */
+typedef RTFSNTFSEXTENT *PRTFSNTFSEXTENT;
+/** Pointer to a const NTFS 9660 extent. */
+typedef RTFSNTFSEXTENT const *PCRTFSNTFSEXTENT;
+
+/**
+ * An array of zero or more extents.
+ */
+typedef struct RTFSNTFSEXTENTS
+{
+ /** Number of bytes covered by the extents. */
+ uint64_t cbData;
+ /** Number of allocation extents. */
+ uint32_t cExtents;
+ /** Array of allocation extents. */
+ PRTFSNTFSEXTENT paExtents;
+} RTFSNTFSEXTENTS;
+/** Pointer to an extent array. */
+typedef RTFSNTFSEXTENTS *PRTFSNTFSEXTENTS;
+/** Pointer to a const extent array. */
+typedef RTFSNTFSEXTENTS const *PCRTFSNTFSEXTENTS;
+
+
+/**
+ * NTFS MFT record.
+ *
+ * These are kept in a tree to , so
+ */
+typedef struct RTFSNTFSMFTREC
+{
+ /** MFT record number (index) as key. */
+ AVLU64NODECORE TreeNode;
+ /** Pointer to the next MFT record if chained. Holds a reference. */
+ PRTFSNTFSMFTREC pNext;
+ union
+ {
+ /** Generic record pointer. RTFSNTFSVOL::cbMftRecord in size. */
+ uint8_t *pbRec;
+ /** Pointer to the file record. */
+ PNTFSRECFILE pFileRec;
+ } RT_UNION_NM(u);
+ /** Pointer to the core object with the parsed data.
+ * This is a weak reference. Non-base MFT record all point to the base one. */
+ PRTFSNTFSCORE pCore;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** Set if this is a base MFT record. */
+ bool fIsBase;
+} RTFSNTFSMFTREC;
+
+
+/** Pointer to a attribute subrecord structure. */
+typedef struct RTFSNTFSATTRSUBREC *PRTFSNTFSATTRSUBREC;
+
+/**
+ * An attribute subrecord.
+ *
+ * This is for covering non-resident attributes that have had their allocation
+ * list split.
+ */
+typedef struct RTFSNTFSATTRSUBREC
+{
+ /** Pointer to the next one. */
+ PRTFSNTFSATTRSUBREC pNext;
+ /** Pointer to the attribute header.
+ * The MFT is held down by RTFSNTFSCORE via pMftEntry. */
+ PNTFSATTRIBHDR pAttrHdr;
+ /** Disk space allocation if non-resident. */
+ RTFSNTFSEXTENTS Extents;
+} RTFSNTFSATTRSUBREC;
+
+/**
+ * An attribute.
+ */
+typedef struct RTFSNTFSATTR
+{
+ /** List entry (head RTFSNTFSCORE::AttribHead). */
+ RTLISTNODE ListEntry;
+ /** Pointer to the core object this attribute belongs to. */
+ PRTFSNTFSCORE pCore;
+ /** Pointer to the attribute header.
+ * The MFT is held down by RTFSNTFSCORE via pMftEntry. */
+ PNTFSATTRIBHDR pAttrHdr;
+ /** The offset of the attribute header in the MFT record.
+ * This is needed to validate header relative offsets. */
+ uint32_t offAttrHdrInMftRec;
+ /** Number of resident bytes available (can be smaller than cbValue).
+ * Set to zero for non-resident attributes. */
+ uint32_t cbResident;
+ /** The (uncompressed) attribute size. */
+ uint64_t cbValue;
+ /** Disk space allocation if non-resident. */
+ RTFSNTFSEXTENTS Extents;
+ /** Pointer to any subrecords containing further allocation extents. */
+ PRTFSNTFSATTRSUBREC pSubRecHead;
+ /** Pointer to the VFS object for this attribute.
+ * This is a weak reference since it's the VFS object that is referencing us. */
+ union
+ {
+ /** Pointer to a shared directory (NTFS_AT_DIRECTORY). */
+ PRTFSNTFSDIRSHRD pSharedDir;
+ /** Pointer to a shared file (NTFS_AT_DATA). */
+ PRTFSNTFSFILESHRD pSharedFile;
+ } uObj;
+} RTFSNTFSATTR;
+/** Pointer to a attribute structure. */
+typedef RTFSNTFSATTR *PRTFSNTFSATTR;
+
+
+/**
+ * NTFS file system object, shared part.
+ */
+typedef struct RTFSNTFSCORE
+{
+ /** Entry in either the RTFSNTFSVOL::CoreInUseHead or CoreUnusedHead.
+ * Instances is moved to/from CoreUnusedHead as cRefs reaches zero and one
+ * respectively. */
+ RTLISTNODE ListEntry;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** The estimated memory cost of this object. */
+ uint32_t cbCost;
+ /** Pointer to the volume. */
+ PRTFSNTFSVOL pVol;
+ /** Pointer to the head of the MFT record chain for this object.
+ * Holds a reference. */
+ PRTFSNTFSMFTREC pMftRec;
+ /** List of attributes (RTFSNTFSATTR). */
+ RTLISTANCHOR AttribHead;
+} RTFSNTFSCORE;
+
+
+/**
+ * Node lookup information for facilitating binary searching of node.
+ */
+typedef struct RTFSNTFSIDXNODEINFO
+{
+ /** The index header. */
+ PCNTFSINDEXHDR pIndexHdr;
+ /** Number of entries. */
+ uint32_t cEntries;
+ /** Set if internal node. */
+ bool fInternal;
+ /** Array with pointers to the entries. */
+ PCNTFSIDXENTRYHDR *papEntries;
+ /** Pointer to the index node this info is for, NULL if root node.
+ * This is for reducing the enumeration stack entry size. */
+ PRTFSNTFSIDXNODE pNode;
+ /** Pointer to the NTFS volume instace. */
+ PRTFSNTFSVOL pVol;
+} RTFSNTFSIDXNODEINFO;
+/** Pointer to index node lookup info. */
+typedef RTFSNTFSIDXNODEINFO *PRTFSNTFSIDXNODEINFO;
+/** Pointer to const index node lookup info. */
+typedef RTFSNTFSIDXNODEINFO const *PCRTFSNTFSIDXNODEINFO;
+
+/**
+ * Index node, cached.
+ *
+ * These are cached to avoid reading, validating and parsing things each time a
+ * subnode is accessed.
+ */
+typedef struct RTFSNTFSIDXNODE
+{
+ /** Entry in RTFSNTFSVOL::IdxNodeCahceRoot, key is disk byte offset. */
+ AVLU64NODECORE TreeNode;
+ /** List entry on the unused list. Gets removed from it when cRefs is
+ * increase to one, and added when it reaches zero. */
+ RTLISTNODE UnusedListEntry;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** The estimated memory cost of this node. */
+ uint32_t cbCost;
+ /** Pointer to the node data. */
+ PNTFSATINDEXALLOC pNode;
+ /** Node info. */
+ RTFSNTFSIDXNODEINFO NodeInfo;
+} RTFSNTFSIDXNODE;
+
+/**
+ * Common index root structure.
+ */
+typedef struct RTFSNTFSIDXROOTINFO
+{
+ /** Pointer to the index root attribute value. */
+ PCNTFSATINDEXROOT pRoot;
+ /** Pointer to the index allocation attribute, if present.
+ * This and the bitmap may be absent if the whole directory fits into the
+ * root index. */
+ PRTFSNTFSATTR pAlloc;
+ /** End of the node addresses range (exclusive). */
+ uint64_t uEndNodeAddresses;
+ /** Node address misalignement mask. */
+ uint32_t fNodeAddressMisalign;
+ /** The byte shift count for node addresses. */
+ uint8_t cNodeAddressByteShift;
+ /** Node info for the root. */
+ RTFSNTFSIDXNODEINFO NodeInfo;
+ /** Pointer to the index root attribute. We reference the core thru this and
+ * use it to zero RTFSNTFSATTR::uObj::pSharedDir on destruction. */
+ PRTFSNTFSATTR pRootAttr;
+} RTFSNTFSIDXROOTINFO;
+/** Pointer to an index root structure. */
+typedef RTFSNTFSIDXROOTINFO *PRTFSNTFSIDXROOTINFO;
+/** Pointer to a const index root structure. */
+typedef RTFSNTFSIDXROOTINFO const *PCRTFSNTFSIDXROOTINFO;
+
+/**
+ * Shared NTFS directory object.
+ */
+typedef struct RTFSNTFSDIRSHRD
+{
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** Index root information. */
+ RTFSNTFSIDXROOTINFO RootInfo;
+} RTFSNTFSDIRSHRD;
+
+/**
+ * Index stack entry for index enumeration.
+ */
+typedef struct RTFSNTFSIDXSTACKENTRY
+{
+ /** The next entry to process in this stack entry. */
+ uint32_t iNext;
+ /** Set if we need to descend first. */
+ bool fDescend;
+ /** Pointer to the node info for this entry. */
+ PRTFSNTFSIDXNODEINFO pNodeInfo;
+} RTFSNTFSIDXSTACKENTRY;
+/** Pointer to an index enumeration stack entry. */
+typedef RTFSNTFSIDXSTACKENTRY *PRTFSNTFSIDXSTACKENTRY;
+
+
+/**
+ * Open directory instance.
+ */
+typedef struct RTFSNTFSDIR
+{
+ /** Pointer to the shared directory instance (referenced). */
+ PRTFSNTFSDIRSHRD pShared;
+ /** Set if we've reached the end of the directory enumeration. */
+ bool fNoMoreFiles;
+ /** The enumeration stack size. */
+ uint32_t cEnumStackEntries;
+ /** The allocated enumeration stack depth. */
+ uint32_t cEnumStackMaxDepth;
+ /** The numeration stack. Allocated as needed. */
+ PRTFSNTFSIDXSTACKENTRY paEnumStack;
+} RTFSNTFSDIR;
+/** Pointer to an open directory instance. */
+typedef RTFSNTFSDIR *PRTFSNTFSDIR;
+
+
+/**
+ * Shared NTFS file object.
+ */
+typedef struct RTFSNTFSFILESHRD
+{
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** Pointer to the data attribute (core is referenced thru this). */
+ PRTFSNTFSATTR pData;
+} RTFSNTFSFILESHRD;
+/** Pointer to shared data for a file or data stream. */
+typedef RTFSNTFSFILESHRD *PRTFSNTFSFILESHRD;
+
+
+/**
+ * Open NTFS file instance.
+ */
+typedef struct RTFSNTFSFILE
+{
+ /** Pointer to the shared file data (referenced). */
+ PRTFSNTFSFILESHRD pShared;
+ /** Current file offset. */
+ uint64_t offFile;
+} RTFSNTFSFILE;
+/** Pointer to an NTFS open file instance. */
+typedef RTFSNTFSFILE *PRTFSNTFSFILE;
+
+/**
+ * Instance data for an NTFS volume.
+ */
+typedef struct RTFSNTFSVOL
+{
+ /** Handle to itself. */
+ RTVFS hVfsSelf;
+ /** The file, partition, or whatever backing the NTFS volume. */
+ RTVFSFILE hVfsBacking;
+ /** The size of the backing thingy. */
+ uint64_t cbBacking;
+ /** The formatted size of the volume. */
+ uint64_t cbVolume;
+ /** cbVolume expressed as a cluster count. */
+ uint64_t cClusters;
+
+ /** RTVFSMNT_F_XXX. */
+ uint32_t fMntFlags;
+ /** RTFSNTVFS_F_XXX (currently none defined). */
+ uint32_t fNtfsFlags;
+
+ /** The (logical) sector size. */
+ uint32_t cbSector;
+
+ /** The (logical) cluster size. */
+ uint32_t cbCluster;
+ /** Max cluster count value that won't overflow a signed 64-bit when
+ * converted to bytes. Inclusive. */
+ uint64_t iMaxVirtualCluster;
+ /** The shift count for converting between bytes and clusters. */
+ uint8_t cClusterShift;
+
+ /** Explicit padding. */
+ uint8_t abReserved[3];
+ /** The NTFS version of the volume (RTFSNTFS_MAKE_VERSION). */
+ uint16_t uNtfsVersion;
+ /** The NTFS_VOLUME_F_XXX. */
+ uint16_t fVolumeFlags;
+
+ /** The logical cluster number of the MFT. */
+ uint64_t uLcnMft;
+ /** The logical cluster number of the mirror MFT. */
+ uint64_t uLcnMftMirror;
+
+ /** The MFT record size. */
+ uint32_t cbMftRecord;
+ /** The default index (B-tree) node size. */
+ uint32_t cbDefaultIndexNode;
+
+ /** The volume serial number. */
+ uint64_t uSerialNo;
+
+ /** @name MFT record and core object cache.
+ * @{ */
+ /** The '$Mft' data attribute. */
+ PRTFSNTFSATTR pMftData;
+ /** Root of the MFT record tree (RTFSNTFSMFTREC). */
+ AVLU64TREE MftRoot;
+ /** List of in use core objects (RTFSNTFSCORE::cRefs > 0). (RTFSNTFSCORE) */
+ RTLISTANCHOR CoreInUseHead;
+ /** List of unused core objects (RTFSNTFSCORE::cRefs == 0). (RTFSNTFSCORE)
+ * The most recently used nodes are found at the of the list. So, when
+ * cbCoreObjects gets to high, we remove and destroy objects from the tail. */
+ RTLISTANCHOR CoreUnusedHead;
+ /** Total core object memory cost (sum of all RTFSNTFSCORE::cbCost). */
+ size_t cbCoreObjects;
+ /** @} */
+
+ /** @name Allocation bitmap and cache.
+ * @{ */
+ /** The '$Bitmap' data attribute. */
+ PRTFSNTFSATTR pMftBitmap;
+ /** The first cluster currently loaded into the bitmap cache . */
+ uint64_t iFirstBitmapCluster;
+ /** The number of clusters currently loaded into the bitmap cache */
+ uint32_t cBitmapClusters;
+ /** The size of the pvBitmap allocation. */
+ uint32_t cbBitmapAlloc;
+ /** Allocation bitmap cache buffer. */
+ void *pvBitmap;
+ /** @} */
+
+ /** @name Directory/index related.
+ * @{ */
+ /** Tree of index nodes, index by disk byte offset. (RTFSNTFSIDXNODE) */
+ AVLU64TREE IdxNodeCacheRoot;
+ /** List of currently unreferenced index nodes. (RTFSNTFSIDXNODE)
+ * Most recently used nodes are found at the end of the list. Nodes are added
+ * when their reference counter reaches zero. They are removed when it
+ * increases to one again.
+ *
+ * The nodes are still in the index node cache tree (IdxNodeCacheRoot), but
+ * we'll trim this from the end when we reach a certain size. */
+ RTLISTANCHOR IdxNodeUnusedHead;
+ /** Number of unreferenced index nodes. */
+ uint32_t cUnusedIdxNodes;
+ /** Number of cached index nodes. */
+ uint32_t cIdxNodes;
+ /** Total index node memory cost. */
+ size_t cbIdxNodes;
+ /** The root directory. */
+ PRTFSNTFSDIRSHRD pRootDir;
+ /** Lower to uppercase conversion table for this filesystem.
+ * This always has 64K valid entries. */
+ PRTUTF16 pawcUpcase;
+ /** @} */
+
+} RTFSNTFSVOL;
+
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static uint32_t rtFsNtfsCore_Destroy(PRTFSNTFSCORE pThis);
+static void rtFsNtfsIdxVol_TrimCoreObjectCache(PRTFSNTFSVOL pThis);
+static uint32_t rtFsNtfsCore_Release(PRTFSNTFSCORE pThis);
+static uint32_t rtFsNtfsCore_Retain(PRTFSNTFSCORE pThis);
+#ifdef LOG_ENABLED
+static void rtFsNtfsVol_LogIndexRoot(PCNTFSATINDEXROOT pIdxRoot, uint32_t cbIdxRoot);
+#endif
+static int rtFsNtfsVol_NewDirFromShared(PRTFSNTFSVOL pThis, PRTFSNTFSDIRSHRD pSharedDir, PRTVFSDIR phVfsDir);
+static uint32_t rtFsNtfsDirShrd_Retain(PRTFSNTFSDIRSHRD pThis);
+static void rtFsNtfsIdxVol_TrimIndexNodeCache(PRTFSNTFSVOL pThis);
+static uint32_t rtFsNtfsIdxNode_Release(PRTFSNTFSIDXNODE pNode);
+static uint32_t rtFsNtfsIdxNode_Retain(PRTFSNTFSIDXNODE pNode);
+
+
+
+/**
+ * Checks if a bit is set in an NTFS bitmap (little endian).
+ *
+ * @returns true if set, false if not.
+ * @param pvBitmap The bitmap buffer.
+ * @param iBit The bit.
+ */
+DECLINLINE(bool) rtFsNtfsBitmap_IsSet(void *pvBitmap, uint32_t iBit)
+{
+#if 0 //def RT_LITTLE_ENDIAN
+ return ASMBitTest(pvBitmap, iBit);
+#else
+ uint8_t b = ((uint8_t const *)pvBitmap)[iBit >> 3];
+ return RT_BOOL(b & (1 << (iBit & 7)));
+#endif
+}
+
+
+
+static PRTFSNTFSMFTREC rtFsNtfsVol_NewMftRec(PRTFSNTFSVOL pVol, uint64_t idMft)
+{
+ PRTFSNTFSMFTREC pRec = (PRTFSNTFSMFTREC)RTMemAllocZ(sizeof(*pRec));
+ if (pRec)
+ {
+ pRec->pbRec = (uint8_t *)RTMemAllocZ(pVol->cbMftRecord);
+ if (pRec->pbRec)
+ {
+ pRec->TreeNode.Key = idMft;
+ pRec->pNext = NULL;
+ pRec->cRefs = 1;
+ if (RTAvlU64Insert(&pVol->MftRoot, &pRec->TreeNode))
+ return pRec;
+ RTMemFree(pRec->pbRec);
+ }
+
+ RTMemFree(pRec);
+ }
+ return NULL;
+}
+
+
+static uint32_t rtFsNtfsMftRec_Destroy(PRTFSNTFSMFTREC pThis, PRTFSNTFSVOL pVol)
+{
+ RTMemFree(pThis->pbRec);
+ pThis->pbRec = NULL;
+
+ PAVLU64NODECORE pRemoved = RTAvlU64Remove(&pVol->MftRoot, pThis->TreeNode.Key);
+ Assert(pRemoved == &pThis->TreeNode); NOREF(pRemoved);
+
+ RTMemFree(pThis);
+
+ return 0;
+}
+
+
+static uint32_t rtFsNtfsMftRec_Retain(PRTFSNTFSMFTREC pThis)
+{
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ Assert(cRefs < 64);
+ return cRefs;
+}
+
+static uint32_t rtFsNtfsMftRec_Release(PRTFSNTFSMFTREC pThis, PRTFSNTFSVOL pVol)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ Assert(cRefs < 64);
+ if (cRefs != 0)
+ return cRefs;
+ return rtFsNtfsMftRec_Destroy(pThis, pVol);
+}
+
+
+#ifdef LOG_ENABLED
+/**
+ * Logs the MFT record
+ *
+ * @param pRec The MFT record to log.
+ * @param cbMftRecord MFT record size (from RTFSNTFSVOL).
+ */
+static void rtfsNtfsMftRec_Log(PRTFSNTFSMFTREC pRec, uint32_t cbMftRecord)
+{
+ if (LogIs2Enabled())
+ {
+ PCNTFSRECFILE pFileRec = pRec->pFileRec;
+ Log2(("NTFS: MFT #%#RX64\n", pRec->TreeNode.Key));
+ if (pFileRec->Hdr.uMagic == NTFSREC_MAGIC_FILE)
+ {
+ size_t const cbRec = cbMftRecord;
+ uint8_t const * const pbRec = pRec->pbRec;
+
+ Log2(("NTFS: FILE record: \n"));
+ Log2(("NTFS: UpdateSeqArray %#x L %#x\n", RT_LE2H_U16(pFileRec->Hdr.offUpdateSeqArray), RT_LE2H_U16(pFileRec->Hdr.cUpdateSeqEntries) ));
+ Log2(("NTFS: uLsn %#RX64\n", RT_LE2H_U64(pFileRec->uLsn)));
+ Log2(("NTFS: uRecReuseSeqNo %#RX16\n", RT_LE2H_U16(pFileRec->uRecReuseSeqNo)));
+ Log2(("NTFS: cLinks %#RX16\n", RT_LE2H_U16(pFileRec->cLinks)));
+ Log2(("NTFS: offFirstAttrib %#RX16\n", RT_LE2H_U16(pFileRec->offFirstAttrib)));
+ Log2(("NTFS: fFlags %#RX16%s%s\n", RT_LE2H_U16(pFileRec->fFlags),
+ RT_LE2H_U16(pFileRec->fFlags) & NTFSRECFILE_F_IN_USE ? " in-use" : "",
+ RT_LE2H_U16(pFileRec->fFlags) & NTFSRECFILE_F_DIRECTORY ? " directory" : ""));
+ Log2(("NTFS: cbRecUsed %#RX32\n", RT_LE2H_U32(pFileRec->cbRecUsed)));
+ Log2(("NTFS: BaseMftRec %#RX64, sqn %#x\n",
+ NTFSMFTREF_GET_IDX(&pFileRec->BaseMftRec), NTFSMFTREF_GET_SEQ(&pFileRec->BaseMftRec)));
+ Log2(("NTFS: idNextAttrib %#RX16\n", RT_LE2H_U16(pFileRec->idNextAttrib)));
+ if ( RT_LE2H_U16(pFileRec->offFirstAttrib) >= sizeof(*pFileRec)
+ && ( RT_LE2H_U16(pFileRec->Hdr.offUpdateSeqArray) >= sizeof(*pFileRec)
+ || pFileRec->Hdr.offUpdateSeqArray == 0))
+ {
+ Log2(("NTFS: uPaddingOrUsa %#RX16\n", pFileRec->uPaddingOrUsa));
+ Log2(("NTFS: idxMftSelf %#RX32\n", RT_LE2H_U32(pFileRec->idxMftSelf)));
+ }
+
+ uint32_t offRec = pFileRec->offFirstAttrib;
+ size_t cbRecUsed = RT_MIN(cbRec, pFileRec->cbRecUsed);
+ while (offRec + NTFSATTRIBHDR_SIZE_RESIDENT <= cbRecUsed)
+ {
+ PCNTFSATTRIBHDR pHdr = (PCNTFSATTRIBHDR)&pbRec[offRec];
+ uint32_t const cbAttrib = RT_LE2H_U32(pHdr->cbAttrib);
+ Log2(("NTFS: @%#05x: Attrib record: %#x LB %#x, instance #%#x, fFlags=%#RX16, %s\n", offRec,
+ RT_LE2H_U32(pHdr->uAttrType), cbAttrib, RT_LE2H_U16(pHdr->idAttrib), RT_LE2H_U16(pHdr->fFlags),
+ pHdr->fNonResident == 0 ? "resident" : pHdr->fNonResident == 1 ? "non-resident" : "bad-resident-flag"));
+ if (pHdr->offName && pHdr->cwcName)
+ {
+ if (offRec + RT_LE2H_U16(pHdr->offName) + pHdr->cwcName * sizeof(RTUTF16) <= cbRec)
+ Log2(("NTFS: Name %.*ls\n", pHdr->cwcName,&pbRec[offRec + RT_LE2H_U16(pHdr->offName)]));
+ else
+ Log2(("NTFS: Name <!out of bounds!> %#x L %#x\n", RT_LE2H_U16(pHdr->offName), pHdr->cwcName));
+ }
+ switch (pHdr->uAttrType)
+ {
+ case NTFS_AT_UNUSED: Log2(("NTFS: Type: UNUSED\n")); break;
+ case NTFS_AT_STANDARD_INFORMATION: Log2(("NTFS: Type: STANDARD_INFORMATION\n")); break;
+ case NTFS_AT_ATTRIBUTE_LIST: Log2(("NTFS: Type: ATTRIBUTE_LIST\n")); break;
+ case NTFS_AT_FILENAME: Log2(("NTFS: Type: FILENAME\n")); break;
+ case NTFS_AT_OBJECT_ID: Log2(("NTFS: Type: OBJECT_ID\n")); break;
+ case NTFS_AT_SECURITY_DESCRIPTOR: Log2(("NTFS: Type: SECURITY_DESCRIPTOR\n")); break;
+ case NTFS_AT_VOLUME_NAME: Log2(("NTFS: Type: VOLUME_NAME\n")); break;
+ case NTFS_AT_VOLUME_INFORMATION: Log2(("NTFS: Type: VOLUME_INFORMATION\n")); break;
+ case NTFS_AT_DATA: Log2(("NTFS: Type: DATA\n")); break;
+ case NTFS_AT_INDEX_ROOT: Log2(("NTFS: Type: INDEX_ROOT\n")); break;
+ case NTFS_AT_INDEX_ALLOCATION: Log2(("NTFS: Type: INDEX_ALLOCATION\n")); break;
+ case NTFS_AT_BITMAP: Log2(("NTFS: Type: BITMAP\n")); break;
+ case NTFS_AT_REPARSE_POINT: Log2(("NTFS: Type: REPARSE_POINT\n")); break;
+ case NTFS_AT_EA_INFORMATION: Log2(("NTFS: Type: EA_INFORMATION\n")); break;
+ case NTFS_AT_EA: Log2(("NTFS: Type: EA\n")); break;
+ case NTFS_AT_PROPERTY_SET: Log2(("NTFS: Type: PROPERTY_SET\n")); break;
+ case NTFS_AT_LOGGED_UTILITY_STREAM: Log2(("NTFS: Type: LOGGED_UTILITY_STREAM\n")); break;
+ default:
+ if (RT_LE2H_U32(pHdr->uAttrType) >= RT_LE2H_U32_C(NTFS_AT_FIRST_USER_DEFINED))
+ Log2(("NTFS: Type: unknown user defined - %#x!\n", RT_LE2H_U32(pHdr->uAttrType)));
+ else
+ Log2(("NTFS: Type: unknown - %#x!\n", RT_LE2H_U32(pHdr->uAttrType)));
+ break;
+ }
+
+ size_t const cbMaxAttrib = cbRec - offRec;
+ if (!pHdr->fNonResident)
+ {
+ uint16_t const offValue = RT_LE2H_U16(pHdr->u.Res.offValue);
+ uint32_t const cbValue = RT_LE2H_U32(pHdr->u.Res.cbValue);
+ Log2(("NTFS: Value: %#x LB %#x, fFlags=%#x bReserved=%#x\n",
+ offValue, cbValue, pHdr->u.Res.fFlags, pHdr->u.Res.bReserved));
+ if ( offValue < cbMaxAttrib
+ && cbValue < cbMaxAttrib
+ && offValue + cbValue <= cbMaxAttrib)
+ {
+ uint8_t const *pbValue = &pbRec[offRec + offValue];
+ RTTIMESPEC Spec;
+ char sz[80];
+ switch (pHdr->uAttrType)
+ {
+ case NTFS_AT_STANDARD_INFORMATION:
+ {
+ PCNTFSATSTDINFO pInfo = (PCNTFSATSTDINFO)pbValue;
+ if (cbValue >= NTFSATSTDINFO_SIZE_NTFS_V12)
+ {
+ Log2(("NTFS: iCreationTime %#RX64 %s\n", RT_LE2H_U64(pInfo->iCreationTime),
+ RTTimeSpecToString(RTTimeSpecSetNtTime(&Spec, RT_LE2H_U64(pInfo->iCreationTime)), sz, sizeof(sz)) ));
+ Log2(("NTFS: iLastDataModTime %#RX64 %s\n", RT_LE2H_U64(pInfo->iLastDataModTime),
+ RTTimeSpecToString(RTTimeSpecSetNtTime(&Spec, RT_LE2H_U64(pInfo->iLastDataModTime)), sz, sizeof(sz)) ));
+ Log2(("NTFS: iLastMftModTime %#RX64 %s\n", RT_LE2H_U64(pInfo->iLastMftModTime),
+ RTTimeSpecToString(RTTimeSpecSetNtTime(&Spec, RT_LE2H_U64(pInfo->iLastMftModTime)), sz, sizeof(sz)) ));
+ Log2(("NTFS: iLastAccessTime %#RX64 %s\n", RT_LE2H_U64(pInfo->iLastAccessTime),
+ RTTimeSpecToString(RTTimeSpecSetNtTime(&Spec, RT_LE2H_U64(pInfo->iLastAccessTime)), sz, sizeof(sz)) ));
+ Log2(("NTFS: fFileAttribs %#RX32\n", RT_LE2H_U32(pInfo->fFileAttribs) ));
+ Log2(("NTFS: cMaxFileVersions %#RX32\n", RT_LE2H_U32(pInfo->cMaxFileVersions) ));
+ Log2(("NTFS: uFileVersion %#RX32\n", RT_LE2H_U32(pInfo->uFileVersion) ));
+ }
+ else
+ Log2(("NTFS: Error! cbValue=%#x is smaller than expected (%#x) for NTFSATSTDINFO!\n",
+ cbValue, NTFSATSTDINFO_SIZE_NTFS_V12));
+ if (cbValue >= sizeof(*pInfo))
+ {
+ Log2(("NTFS: idClass %#RX32\n", RT_LE2H_U32(pInfo->idClass) ));
+ Log2(("NTFS: idOwner %#RX32\n", RT_LE2H_U32(pInfo->idOwner) ));
+ Log2(("NTFS: idSecurity %#RX32\n", RT_LE2H_U32(pInfo->idSecurity) ));
+ Log2(("NTFS: cbQuotaChared %#RX64\n", RT_LE2H_U64(pInfo->cbQuotaChared) ));
+ Log2(("NTFS: idxUpdateSequence %#RX64\n", RT_LE2H_U64(pInfo->idxUpdateSequence) ));
+ }
+ if (cbValue > sizeof(*pInfo))
+ Log2(("NTFS: Undefined data: %.*Rhxs\n", cbValue - sizeof(*pInfo), &pbValue[sizeof(*pInfo)]));
+ break;
+ }
+
+ case NTFS_AT_ATTRIBUTE_LIST:
+ {
+ uint32_t iEntry = 0;
+ uint32_t offEntry = 0;
+ while (offEntry + NTFSATLISTENTRY_SIZE_MINIMAL < cbValue)
+ {
+ PCNTFSATLISTENTRY pInfo = (PCNTFSATLISTENTRY)&pbValue[offEntry];
+ Log2(("NTFS: attr[%u]: %#x in %#RX64 (sqn %#x), instance %#x, VNC=%#RX64-, name %#x L %#x\n",
+ iEntry, RT_LE2H_U32(pInfo->uAttrType), NTFSMFTREF_GET_IDX(&pInfo->InMftRec),
+ NTFSMFTREF_GET_SEQ(&pInfo->InMftRec), RT_LE2H_U16(pInfo->idAttrib),
+ RT_LE2H_U64(pInfo->iVcnFirst), pInfo->offName, pInfo->cwcName));
+ if ( pInfo->cwcName > 0
+ && pInfo->offName < pInfo->cbEntry)
+ Log2(("NTFS: name '%.*ls'\n", pInfo->cwcName, (uint8_t *)pInfo + pInfo->offName));
+
+ /* next */
+ if (pInfo->cbEntry < NTFSATLISTENTRY_SIZE_MINIMAL)
+ {
+ Log2(("NTFS: cbEntry is too small! cbEntry=%#x, min %#x\n",
+ pInfo->cbEntry, NTFSATLISTENTRY_SIZE_MINIMAL));
+ break;
+ }
+ iEntry++;
+ offEntry += RT_ALIGN_32(pInfo->cbEntry, 8);
+ }
+ break;
+ }
+
+ case NTFS_AT_FILENAME:
+ {
+ PCNTFSATFILENAME pInfo = (PCNTFSATFILENAME)pbValue;
+ if (cbValue >= RT_UOFFSETOF(NTFSATFILENAME, wszFilename))
+ {
+ Log2(("NTFS: ParentDirMftRec %#RX64, sqn %#x\n",
+ NTFSMFTREF_GET_IDX(&pInfo->ParentDirMftRec), NTFSMFTREF_GET_SEQ(&pInfo->ParentDirMftRec) ));
+ Log2(("NTFS: iCreationTime %#RX64 %s\n", RT_LE2H_U64(pInfo->iCreationTime),
+ RTTimeSpecToString(RTTimeSpecSetNtTime(&Spec, RT_LE2H_U64(pInfo->iCreationTime)), sz, sizeof(sz)) ));
+ Log2(("NTFS: iLastDataModTime %#RX64 %s\n", RT_LE2H_U64(pInfo->iLastDataModTime),
+ RTTimeSpecToString(RTTimeSpecSetNtTime(&Spec, RT_LE2H_U64(pInfo->iLastDataModTime)), sz, sizeof(sz)) ));
+ Log2(("NTFS: iLastMftModTime %#RX64 %s\n", RT_LE2H_U64(pInfo->iLastMftModTime),
+ RTTimeSpecToString(RTTimeSpecSetNtTime(&Spec, RT_LE2H_U64(pInfo->iLastMftModTime)), sz, sizeof(sz)) ));
+ Log2(("NTFS: iLastAccessTime %#RX64 %s\n", RT_LE2H_U64(pInfo->iLastAccessTime),
+ RTTimeSpecToString(RTTimeSpecSetNtTime(&Spec, RT_LE2H_U64(pInfo->iLastAccessTime)), sz, sizeof(sz)) ));
+ Log2(("NTFS: cbAllocated %#RX64 (%Rhcb)\n",
+ RT_LE2H_U64(pInfo->cbAllocated), RT_LE2H_U64(pInfo->cbAllocated)));
+ Log2(("NTFS: cbData %#RX64 (%Rhcb)\n",
+ RT_LE2H_U64(pInfo->cbData), RT_LE2H_U64(pInfo->cbData)));
+ Log2(("NTFS: fFileAttribs %#RX32\n", RT_LE2H_U32(pInfo->fFileAttribs) ));
+ if (RT_LE2H_U32(pInfo->fFileAttribs) & NTFS_FA_REPARSE_POINT)
+ Log2(("NTFS: uReparseTag %#RX32\n", RT_LE2H_U32(pInfo->u.uReparseTag) ));
+ else
+ Log2(("NTFS: cbPackedEas %#RX16\n", RT_LE2H_U16(pInfo->u.cbPackedEas) ));
+ Log2(("NTFS: cwcFilename %#x\n", pInfo->cwcFilename));
+ Log2(("NTFS: fFilenameType %#x\n", pInfo->fFilenameType));
+ if (RT_UOFFSETOF_DYN(NTFSATFILENAME, wszFilename[pInfo->cwcFilename]) <= cbValue)
+ Log2(("NTFS: wszFilename '%.*ls'\n", pInfo->cwcFilename, pInfo->wszFilename ));
+ else
+ Log2(("NTFS: Error! Truncated filename!!\n"));
+ }
+ else
+ Log2(("NTFS: Error! cbValue=%#x is smaller than expected (%#zx) for NTFSATFILENAME!\n",
+ cbValue, RT_UOFFSETOF(NTFSATFILENAME, wszFilename) ));
+ break;
+ }
+
+ //case NTFS_AT_OBJECT_ID:
+ //case NTFS_AT_SECURITY_DESCRIPTOR:
+ //case NTFS_AT_VOLUME_NAME:
+ //case NTFS_AT_VOLUME_INFORMATION:
+ //case NTFS_AT_DATA:
+
+ case NTFS_AT_INDEX_ROOT:
+ rtFsNtfsVol_LogIndexRoot((PCNTFSATINDEXROOT)pbValue, cbValue);
+ break;
+
+ //case NTFS_AT_INDEX_ALLOCATION:
+ //case NTFS_AT_BITMAP:
+ //case NTFS_AT_REPARSE_POINT:
+ //case NTFS_AT_EA_INFORMATION:
+ //case NTFS_AT_EA:
+ //case NTFS_AT_PROPERTY_SET:
+ //case NTFS_AT_LOGGED_UTILITY_STREAM:
+
+ default:
+ if (cbValue <= 24)
+ Log2(("NTFS: %.*Rhxs\n", cbValue, pbValue));
+ else
+ Log2(("%.*Rhxd\n", cbValue, pbValue));
+ break;
+ }
+
+ }
+ else
+ Log2(("NTFS: !Value is out of bounds!\n"));
+ }
+ else if (RT_MAX(cbAttrib, NTFSATTRIBHDR_SIZE_NONRES_UNCOMPRESSED) <= cbMaxAttrib)
+ {
+ Log2(("NTFS: VNC range %#RX64 .. %#RX64 (%#RX64 clusters)\n",
+ RT_LE2H_U64(pHdr->u.NonRes.iVcnFirst), RT_LE2H_U64(pHdr->u.NonRes.iVcnLast),
+ RT_LE2H_U64(pHdr->u.NonRes.iVcnLast) - RT_LE2H_U64(pHdr->u.NonRes.iVcnFirst) + 1));
+ Log2(("NTFS: cbAllocated %#RX64 (%Rhcb)\n",
+ RT_LE2H_U64(pHdr->u.NonRes.cbAllocated), RT_LE2H_U64(pHdr->u.NonRes.cbAllocated)));
+ Log2(("NTFS: cbData %#RX64 (%Rhcb)\n",
+ RT_LE2H_U64(pHdr->u.NonRes.cbData), RT_LE2H_U64(pHdr->u.NonRes.cbData)));
+ Log2(("NTFS: cbInitialized %#RX64 (%Rhcb)\n",
+ RT_LE2H_U64(pHdr->u.NonRes.cbInitialized), RT_LE2H_U64(pHdr->u.NonRes.cbInitialized)));
+ uint16_t const offMappingPairs = RT_LE2H_U16(pHdr->u.NonRes.offMappingPairs);
+ Log2(("NTFS: offMappingPairs %#RX16\n", offMappingPairs));
+ if ( pHdr->u.NonRes.abReserved[0] || pHdr->u.NonRes.abReserved[1]
+ || pHdr->u.NonRes.abReserved[2] || pHdr->u.NonRes.abReserved[3] || pHdr->u.NonRes.abReserved[4] )
+ Log2(("NTFS: abReserved %.7Rhxs\n", pHdr->u.NonRes.abReserved));
+ if (pHdr->u.NonRes.uCompressionUnit != 0)
+ Log2(("NTFS: Compression unit 2^%u clusters\n", pHdr->u.NonRes.uCompressionUnit));
+
+ if ( cbMaxAttrib >= NTFSATTRIBHDR_SIZE_NONRES_COMPRESSED
+ && cbAttrib >= NTFSATTRIBHDR_SIZE_NONRES_COMPRESSED
+ && ( offMappingPairs >= NTFSATTRIBHDR_SIZE_NONRES_COMPRESSED
+ || offMappingPairs < NTFSATTRIBHDR_SIZE_NONRES_UNCOMPRESSED))
+ Log2(("NTFS: cbCompressed %#RX64 (%Rhcb)\n",
+ RT_LE2H_U64(pHdr->u.NonRes.cbCompressed), RT_LE2H_U64(pHdr->u.NonRes.cbCompressed)));
+ else if ( pHdr->u.NonRes.uCompressionUnit != 0
+ && pHdr->u.NonRes.uCompressionUnit != 64
+ && pHdr->u.NonRes.iVcnFirst == 0)
+ Log2(("NTFS: !Error! Compressed attrib fields are out of bound!\n"));
+
+ if ( offMappingPairs < cbAttrib
+ && offMappingPairs >= NTFSATTRIBHDR_SIZE_NONRES_UNCOMPRESSED)
+ {
+ uint8_t const *pbPairs = &pbRec[offRec + offMappingPairs];
+ uint32_t const cbMaxPairs = cbAttrib - offMappingPairs;
+ int64_t iVnc = pHdr->u.NonRes.iVcnFirst;
+ if (cbMaxPairs < 48)
+ Log2(("NTFS: Mapping Pairs: cbMaxPairs=%#x %.*Rhxs\n", cbMaxPairs, cbMaxPairs, pbPairs));
+ else
+ Log2(("NTFS: Mapping Pairs: cbMaxPairs=%#x\n%.*Rhxd\n", cbMaxPairs, cbMaxPairs, pbPairs));
+ if (!iVnc && !*pbPairs)
+ Log2(("NTFS: [0]: Empty\n"));
+ else
+ {
+ if (iVnc != 0)
+ Log2(("NTFS: [00/0x000]: VCN=%#012RX64 L %#012RX64 - not mapped\n", 0, iVnc));
+ int64_t iLnc = 0;
+ uint32_t iPair = 0;
+ uint32_t offPairs = 0;
+ while (offPairs < cbMaxPairs)
+ {
+ /* First byte: 4-bit length of each of the pair values */
+ uint8_t const bLengths = pbPairs[offPairs];
+ if (!bLengths)
+ break;
+ uint8_t const cbRun = (bLengths & 0x0f) + (bLengths >> 4);
+ if (offPairs + cbRun > cbMaxPairs)
+ {
+ Log2(("NTFS: [%02d/%#05x]: run overrun! cbRun=%#x bLengths=%#x offPairs=%#x cbMaxPairs=%#x\n",
+ iPair, offPairs, cbRun, bLengths, offPairs, cbMaxPairs));
+ break;
+ }
+ //Log2(("NTFS: @%#05x: %.*Rhxs\n", offPairs, cbRun + 1, &pbPairs[offPairs]));
+
+ /* Value 1: Number of (virtual) clusters in this run. */
+ int64_t cClustersInRun;
+ uint8_t cbNum = (bLengths & 0xf);
+ if (cbNum)
+ {
+ uint8_t const *pbNum = &pbPairs[offPairs + cbNum]; /* last byte */
+ cClustersInRun = (int8_t)*pbNum--;
+ while (cbNum-- > 1)
+ cClustersInRun = (cClustersInRun << 8) + *pbNum--;
+ }
+ else
+ cClustersInRun = -1;
+
+ /* Value 2: The logical cluster delta to get to the first cluster in the run. */
+ cbNum = bLengths >> 4;
+ if (cbNum)
+ {
+ uint8_t const *pbNum = &pbPairs[offPairs + cbNum + (bLengths & 0xf)]; /* last byte */
+ int64_t cLcnDelta = (int8_t)*pbNum--;
+ while (cbNum-- > 1)
+ cLcnDelta = (cLcnDelta << 8) + *pbNum--;
+ iLnc += cLcnDelta;
+ Log2(("NTFS: [%02d/%#05x]: VNC=%#012RX64 L %#012RX64 => LNC=%#012RX64\n",
+ iPair, offPairs, iVnc, cClustersInRun, iLnc));
+ }
+ else
+ Log2(("NTFS: [%02d/%#05x]: VNC=%#012RX64 L %#012RX64 => HOLE\n",
+ iPair, offPairs, iVnc, cClustersInRun));
+
+ /* Advance. */
+ iVnc += cClustersInRun;
+ offPairs += 1 + cbRun;
+ iPair++;
+ }
+ }
+ }
+ else if ( cbAttrib != NTFSATTRIBHDR_SIZE_NONRES_COMPRESSED
+ && cbAttrib != NTFSATTRIBHDR_SIZE_NONRES_UNCOMPRESSED)
+ {
+ Log2(("NTFS: Warning! Odd non-resident attribute size: %#x!\n", cbAttrib));
+ if (cbAttrib >= NTFSATTRIBHDR_SIZE_NONRES_UNCOMPRESSED)
+ Log2(("NTFS: @%05x: %.*Rhxs!\n", offRec + NTFSATTRIBHDR_SIZE_NONRES_UNCOMPRESSED,
+ cbAttrib - NTFSATTRIBHDR_SIZE_NONRES_UNCOMPRESSED,
+ &pbRec[offRec + NTFSATTRIBHDR_SIZE_NONRES_UNCOMPRESSED]));
+ }
+ }
+ else
+ Log2(("NTFS: !Attrib header is out of bound!\n"));
+
+ /* Advance. */
+ offRec += RT_MAX(cbAttrib, NTFSATTRIBHDR_SIZE_RESIDENT);
+ }
+
+ /* Anything left? */
+ if (offRec < cbRecUsed)
+ Log2(("NTFS: @%#05x: Tail: %.*Rhxs\n", offRec, cbRecUsed - offRec, &pbRec[offRec]));
+ }
+ else
+ Log2(("NTFS: Unknown record type: %.4Rhxs\n", pFileRec));
+ }
+}
+#endif /* LOG_ENABLED */
+
+
+static int rtFsNtfsAttr_ParseExtents(PRTFSNTFSATTR pAttrib, PRTFSNTFSEXTENTS pExtents, uint8_t cClusterShift, int64_t iVcnFirst,
+ uint64_t cbVolume, PRTERRINFO pErrInfo, uint64_t idxMft, uint32_t offAttrib)
+{
+ PCNTFSATTRIBHDR pAttrHdr = pAttrib->pAttrHdr;
+ Assert(pAttrHdr->fNonResident);
+ Assert(pExtents->cExtents == 0);
+ Assert(pExtents->paExtents == NULL);
+
+ /** @todo Not entirely sure how to best detect empty mapping pair program.
+ * Not sure if this is a real problem as zero length stuff can be
+ * resident. */
+ uint16_t const offMappingPairs = RT_LE2H_U16(pAttrHdr->u.NonRes.offMappingPairs);
+ uint32_t const cbAttrib = RT_LE2H_U32(pAttrHdr->cbAttrib);
+ if ( offMappingPairs != cbAttrib
+ && offMappingPairs != 0)
+ {
+ if (pAttrHdr->u.NonRes.iVcnFirst < iVcnFirst)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x) has a lower starting VNC than expected: %#RX64, %#RX64",
+ idxMft, offAttrib, pAttrHdr->u.NonRes.iVcnFirst, iVcnFirst);
+
+ if ( offMappingPairs >= cbAttrib
+ || offMappingPairs < NTFSATTRIBHDR_SIZE_NONRES_UNCOMPRESSED)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Mapping pair program for attribute (@%#x) is out of bounds: %#x, cbAttrib=%#x",
+ idxMft, offAttrib, offMappingPairs, cbAttrib);
+
+ /*
+ * Count the pairs.
+ */
+ uint8_t const * const pbPairs = (const uint8_t *)pAttrHdr + pAttrHdr->u.NonRes.offMappingPairs;
+ uint32_t const cbPairs = cbAttrib - offMappingPairs;
+ uint32_t offPairs = 0;
+ uint32_t cPairs = 0;
+ while (offPairs < cbPairs)
+ {
+ uint8_t const bLengths = pbPairs[offPairs];
+ if (bLengths)
+ {
+ uint8_t const cbRunField = bLengths & 0x0f;
+ uint8_t const cbLcnField = bLengths >> 4;
+ if ( cbRunField > 0
+ && cbRunField <= 8)
+ {
+ if (cbLcnField <= 8)
+ {
+ cPairs++;
+
+ /* Advance and check for overflow/end. */
+ offPairs += 1 + cbRunField + cbLcnField;
+ if (offPairs <= cbAttrib)
+ continue;
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Mapping pair #%#x for attribute (@%#x) is out of bounds",
+ idxMft, cPairs - 1, offAttrib);
+ }
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Mapping pair #%#x for attribute (@%#x): cbLcnField is out of bound: %u",
+ idxMft, cPairs - 1, offAttrib, cbLcnField);
+
+ }
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Mapping pair #%#x for attribute (@%#x): cbRunField is out of bound: %u",
+ idxMft, cPairs - 1, offAttrib, cbRunField);
+ }
+ break;
+ }
+
+ /*
+ * Allocate an the extent table for them.
+ */
+ uint32_t const cExtents = cPairs + (pAttrHdr->u.NonRes.iVcnFirst != iVcnFirst);
+ if (cExtents)
+ {
+ PRTFSNTFSEXTENT paExtents = (PRTFSNTFSEXTENT)RTMemAllocZ(sizeof(paExtents[0]) * cExtents);
+ AssertReturn(paExtents, VERR_NO_MEMORY);
+
+ /*
+ * Fill the table.
+ */
+ uint32_t iExtent = 0;
+
+ /* A sparse hole between this and the previous extent table? */
+ if (pAttrHdr->u.NonRes.iVcnFirst != iVcnFirst)
+ {
+ paExtents[iExtent].off = UINT64_MAX;
+ paExtents[iExtent].cbExtent = (pAttrHdr->u.NonRes.iVcnFirst - iVcnFirst) << cClusterShift;
+ Log3((" paExtent[%#04x]: %#018RX64 LB %#010RX64\n", iExtent, paExtents[iExtent].off, paExtents[iExtent].cbExtent));
+ iExtent++;
+ }
+
+ /* Run the program again, now with values and without verbose error checking. */
+ uint64_t cMaxClustersInRun = (INT64_MAX >> cClusterShift) - pAttrHdr->u.NonRes.iVcnFirst;
+ uint64_t cbData = 0;
+ int64_t iLcn = 0;
+ int rc = VINF_SUCCESS;
+ offPairs = 0;
+ for (; iExtent < cExtents; iExtent++)
+ {
+ uint8_t const bLengths = pbPairs[offPairs++];
+ uint8_t const cbRunField = bLengths & 0x0f;
+ uint8_t const cbLcnField = bLengths >> 4;
+ AssertBreakStmt((unsigned)cbRunField - 1U <= 7U, rc = VERR_VFS_BOGUS_FORMAT);
+ AssertBreakStmt((unsigned)cbLcnField <= 8U, rc = VERR_VFS_BOGUS_FORMAT);
+
+ AssertBreakStmt(!(pbPairs[offPairs + cbRunField - 1] & 0x80),
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Extent #%#x for attribute (@%#x): Negative runlength value",
+ idxMft, iExtent, offAttrib));
+ uint64_t cClustersInRun = 0;
+ switch (cbRunField)
+ {
+ case 8: cClustersInRun |= (uint64_t)pbPairs[offPairs + 7] << 56; RT_FALL_THRU();
+ case 7: cClustersInRun |= (uint64_t)pbPairs[offPairs + 6] << 48; RT_FALL_THRU();
+ case 6: cClustersInRun |= (uint64_t)pbPairs[offPairs + 5] << 40; RT_FALL_THRU();
+ case 5: cClustersInRun |= (uint64_t)pbPairs[offPairs + 4] << 32; RT_FALL_THRU();
+ case 4: cClustersInRun |= (uint32_t)pbPairs[offPairs + 3] << 24; RT_FALL_THRU();
+ case 3: cClustersInRun |= (uint32_t)pbPairs[offPairs + 2] << 16; RT_FALL_THRU();
+ case 2: cClustersInRun |= (uint16_t)pbPairs[offPairs + 1] << 8; RT_FALL_THRU();
+ case 1: cClustersInRun |= (uint16_t)pbPairs[offPairs + 0] << 0;
+ }
+ offPairs += cbRunField;
+ AssertBreakStmt(cClustersInRun <= cMaxClustersInRun,
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Extent #%#x for attribute (@%#x): too many clusters %#RX64, max %#RX64",
+ idxMft, iExtent, offAttrib, cClustersInRun, cMaxClustersInRun));
+ cMaxClustersInRun -= cClustersInRun;
+ paExtents[iExtent].cbExtent = cClustersInRun << cClusterShift;
+ cbData += cClustersInRun << cClusterShift;
+
+ if (cbLcnField)
+ {
+ unsigned offVncDelta = cbLcnField;
+ int64_t cLncDelta = (int8_t)pbPairs[--offVncDelta + offPairs];
+ while (offVncDelta-- > 0)
+ cLncDelta = (cLncDelta << 8) | pbPairs[offVncDelta + offPairs];
+ offPairs += cbLcnField;
+
+ iLcn += cLncDelta;
+ if (iLcn >= 0)
+ {
+ paExtents[iExtent].off = (uint64_t)iLcn << cClusterShift;
+ AssertBreakStmt((paExtents[iExtent].off >> cClusterShift) == (uint64_t)iLcn,
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Extent #%#x for attribute (@%#x): iLcn %RX64 overflows when shifted by %u",
+ idxMft, iExtent, offAttrib, iLcn, cClusterShift));
+ AssertBreakStmt( paExtents[iExtent].off < cbVolume
+ || paExtents[iExtent].cbExtent < cbVolume
+ || paExtents[iExtent].off + paExtents[iExtent].cbExtent <= cbVolume,
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Extent #%#x for attribute (@%#x) outside volume: %#RX64 LB %#RX64, cbVolume=%#RX64",
+ idxMft, iExtent, offAttrib, paExtents[iExtent].off,
+ paExtents[iExtent].cbExtent, cbVolume));
+ }
+ else
+ paExtents[iExtent].off = UINT64_MAX;
+ }
+ else
+ paExtents[iExtent].off = UINT64_MAX;
+ Log3((" paExtent[%#04x]: %#018RX64 LB %#010RX64\n", iExtent, paExtents[iExtent].off, paExtents[iExtent].cbExtent));
+ }
+
+ /* Commit if everything went fine? */
+ if (RT_SUCCESS(rc))
+ {
+ pExtents->cbData = cbData;
+ pExtents->cExtents = cExtents;
+ pExtents->paExtents = paExtents;
+ }
+ else
+ {
+ RTMemFree(paExtents);
+ return rc;
+ }
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Parses the given MTF record and all related records, putting the result in
+ * pRec->pCore (with one reference for the caller).
+ *
+ * ASSUMES caller will insert pRec->pCore into the CoreInUseHead list on
+ * success, and destroy it on failure. It is better to have caller do the
+ * inserting/destroy, since we don't want to cache a failed parsing attempt.
+ * (It is also preferable to add RTFSNTFSCORE::cbCost once it's fully calculated
+ * and in the place as the insertion.)
+ *
+ * @returns IPRT status code.
+ * @param pThis The volume.
+ * @param pRec The MFT record to parse.
+ * @param pErrInfo Where to return additional error information. Optional.
+ */
+static int rtFsNtfsVol_ParseMft(PRTFSNTFSVOL pThis, PRTFSNTFSMFTREC pRec, PRTERRINFO pErrInfo)
+{
+ AssertReturn(!pRec->pCore, VERR_INTERNAL_ERROR_4);
+
+ /*
+ * Check that it is a file record and that its base MFT record number is zero.
+ * Caller should do the base record resolving.
+ */
+ PNTFSRECFILE pFileRec = pRec->pFileRec;
+ if (pFileRec->Hdr.uMagic != NTFSREC_MAGIC_FILE)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Not a FILE entry (%.4Rhxs)",
+ pRec->TreeNode.Key, &pFileRec->Hdr);
+ if (pFileRec->BaseMftRec.u64 != 0)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Not a base record (%#RX64, sqn %#x)",
+ pRec->TreeNode.Key, NTFSMFTREF_GET_IDX(&pFileRec->BaseMftRec),
+ NTFSMFTREF_GET_SEQ(&pFileRec->BaseMftRec) );
+
+ /*
+ * Create a core node (1 reference, returned even on error).
+ */
+ PRTFSNTFSCORE pCore = (PRTFSNTFSCORE)RTMemAllocZ(sizeof(*pCore));
+ AssertReturn(pCore, VERR_NO_MEMORY);
+
+ pCore->cRefs = 1;
+ pCore->cbCost = pThis->cbMftRecord + sizeof(*pCore);
+ pCore->pVol = pThis;
+ RTListInit(&pCore->AttribHead);
+ pCore->pMftRec = pRec;
+ rtFsNtfsMftRec_Retain(pRec);
+ pRec->pCore = pCore;
+
+ /*
+ * Parse attributes.
+ * We process any attribute list afterwards, skipping attributes in this MFT record.
+ */
+ PRTFSNTFSATTR pAttrList = NULL;
+ uint8_t * const pbRec = pRec->pbRec;
+ uint32_t offRec = pFileRec->offFirstAttrib;
+ uint32_t const cbRecUsed = RT_MIN(pThis->cbMftRecord, pFileRec->cbRecUsed);
+ while (offRec + NTFSATTRIBHDR_SIZE_RESIDENT <= cbRecUsed)
+ {
+ PNTFSATTRIBHDR pAttrHdr = (PNTFSATTRIBHDR)&pbRec[offRec];
+
+ /*
+ * Validate the attribute data.
+ */
+ uint32_t const cbAttrib = RT_LE2H_U32(pAttrHdr->cbAttrib);
+ uint32_t const cbMin = !pAttrHdr->fNonResident ? NTFSATTRIBHDR_SIZE_RESIDENT
+ : pAttrHdr->u.NonRes.uCompressionUnit == 0 ? NTFSATTRIBHDR_SIZE_NONRES_UNCOMPRESSED
+ : NTFSATTRIBHDR_SIZE_NONRES_COMPRESSED;
+ if (cbAttrib < cbMin)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x) is too small (%#x, cbMin=%#x)",
+ pRec->TreeNode.Key, offRec, cbAttrib, cbMin);
+ if (offRec + cbAttrib > cbRecUsed)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x) is too long (%#x, cbRecUsed=%#x)",
+ pRec->TreeNode.Key, offRec, cbAttrib, cbRecUsed);
+ if (cbAttrib & 0x7)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x) size is misaligned: %#x",
+ pRec->TreeNode.Key, offRec, cbAttrib);
+ if (pAttrHdr->fNonResident)
+ {
+ int64_t const cbAllocated = RT_LE2H_U64(pAttrHdr->u.NonRes.cbAllocated);
+ if (cbAllocated < 0)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): cbAllocated (%#RX64) is negative",
+ pRec->TreeNode.Key, offRec, cbAllocated);
+ if ((uint64_t)cbAllocated & (pThis->cbCluster - 1))
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): cbAllocated (%#RX64) isn't cluster aligned (cbCluster=%#x)",
+ pRec->TreeNode.Key, offRec, cbAllocated, pThis->cbCluster);
+
+ int64_t const cbData = RT_LE2H_U64(pAttrHdr->u.NonRes.cbData);
+ if (cbData < 0)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): cbData (%#RX64) is negative",
+ pRec->TreeNode.Key, offRec, cbData);
+
+ int64_t const cbInitialized = RT_LE2H_U64(pAttrHdr->u.NonRes.cbInitialized);
+ if (cbInitialized < 0)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): cbInitialized (%#RX64) is negative",
+ pRec->TreeNode.Key, offRec, cbInitialized);
+
+ int64_t const iVcnFirst = RT_LE2H_U64(pAttrHdr->u.NonRes.iVcnFirst);
+ int64_t const iVcnLast = RT_LE2H_U64(pAttrHdr->u.NonRes.iVcnLast);
+ if ( iVcnFirst > iVcnLast
+ && ( iVcnLast != -1
+ || cbAllocated != 0))
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): iVcnFirst (%#RX64) is higher than iVcnLast (%#RX64)",
+ pRec->TreeNode.Key, offRec, iVcnFirst, iVcnLast);
+ if (iVcnFirst < 0)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): iVcnFirst (%#RX64) is negative",
+ pRec->TreeNode.Key, offRec, iVcnFirst);
+ if ((uint64_t)iVcnLast > pThis->iMaxVirtualCluster)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): iVcnLast (%#RX64) is too high, max %RX64 (shift %#x)",
+ pRec->TreeNode.Key, offRec, iVcnLast, pThis->cClusterShift, pThis->iMaxVirtualCluster);
+ uint16_t const offMappingPairs = RT_LE2H_U16(pAttrHdr->u.NonRes.offMappingPairs);
+ if ( (offMappingPairs != 0 && offMappingPairs < cbMin)
+ || offMappingPairs > cbAttrib)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): offMappingPairs (%#x) is out of bounds (cbAttrib=%#x, cbMin=%#x)",
+ pRec->TreeNode.Key, offRec, offMappingPairs, cbAttrib, cbMin);
+ if (pAttrHdr->u.NonRes.uCompressionUnit > 16)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): uCompressionUnit (%#x) is too high",
+ pRec->TreeNode.Key, offRec, pAttrHdr->u.NonRes.uCompressionUnit);
+
+ if (cbMin >= NTFSATTRIBHDR_SIZE_NONRES_COMPRESSED)
+ {
+ int64_t const cbCompressed = RT_LE2H_U64(pAttrHdr->u.NonRes.cbCompressed);
+ if (cbAllocated < 0)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): cbCompressed (%#RX64) is negative",
+ pRec->TreeNode.Key, offRec, cbCompressed);
+ }
+ }
+ else
+ {
+ uint16_t const offValue = RT_LE2H_U32(pAttrHdr->u.Res.offValue);
+ if ( offValue > cbAttrib
+ || offValue < NTFSATTRIBHDR_SIZE_RESIDENT)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): offValue (%#RX16) is out of bounds (cbAttrib=%#RX32, cbValue=%#RX32)",
+ pRec->TreeNode.Key, offRec, offValue, cbAttrib, RT_LE2H_U32(pAttrHdr->u.Res.cbValue));
+ if ((pAttrHdr->fFlags & NTFS_AF_COMPR_FMT_MASK) != NTFS_AF_COMPR_FMT_NONE)
+ {
+#if 1 /* Seen on INDEX_ROOT of ReportQueue on w7, so turned into debug log warning. */
+ Log(("NTFS: Warning! Bad MFT record %#RX64: Attribute (@%#x): fFlags (%#RX16) indicate compression of a resident attribute\n",
+ pRec->TreeNode.Key, offRec, RT_LE2H_U16(pAttrHdr->fFlags) ));
+#else
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): fFlags (%#RX16) indicate compression of a resident attribute",
+ pRec->TreeNode.Key, offRec, RT_LE2H_U16(pAttrHdr->fFlags));
+#endif
+ }
+ }
+
+ if (pAttrHdr->cwcName != 0)
+ {
+ uint16_t offName = RT_LE2H_U16(pAttrHdr->offName);
+ if ( offName < cbMin
+ || offName >= cbAttrib)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): offName (%#RX16) is out of bounds (cbAttrib=%#RX32, cbMin=%#RX32)",
+ pRec->TreeNode.Key, offRec, offName, cbAttrib, cbMin);
+ if (offName + pAttrHdr->cwcName * sizeof(RTUTF16) > cbAttrib)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): offName (%#RX16) + cwcName (%#x) is out of bounds (cbAttrib=%#RX32)",
+ pRec->TreeNode.Key, offRec, offName, pAttrHdr->cwcName, cbAttrib);
+ }
+
+ /*
+ * Allocate and initialize a new attribute.
+ */
+ PRTFSNTFSATTR pAttrib = (PRTFSNTFSATTR)RTMemAllocZ(sizeof(*pAttrib));
+ AssertReturn(pAttrib, VERR_NO_MEMORY);
+ pAttrib->pAttrHdr = pAttrHdr;
+ pAttrib->offAttrHdrInMftRec = offRec;
+ pAttrib->pCore = pCore;
+ //pAttrib->cbResident = 0;
+ //pAttrib->cbValue = 0;
+ //pAttrib->Extents.cExtents = 0;
+ //pAttrib->Extents.paExtents = NULL;
+ //pAttrib->pSubRecHead = NULL;
+ if (pAttrHdr->fNonResident)
+ {
+ pAttrib->cbValue = RT_LE2H_U64(pAttrHdr->u.NonRes.cbData);
+ int rc = rtFsNtfsAttr_ParseExtents(pAttrib, &pAttrib->Extents, pThis->cClusterShift, 0 /*iVncFirst*/,
+ pThis->cbVolume, pErrInfo, pRec->TreeNode.Key, offRec);
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pAttrib);
+ return rc;
+ }
+ }
+ else
+ {
+ pAttrib->cbValue = RT_LE2H_U32(pAttrHdr->u.Res.cbValue);
+ if ( (uint32_t)pAttrib->cbValue > 0
+ && RT_LE2H_U16(pAttrHdr->u.Res.offValue) < cbAttrib)
+ {
+ pAttrib->cbResident = cbAttrib - RT_LE2H_U16(pAttrHdr->u.Res.offValue);
+ if (pAttrib->cbResident > (uint32_t)pAttrib->cbValue)
+ pAttrib->cbResident = (uint32_t)pAttrib->cbValue;
+ }
+ }
+
+ RTListAppend(&pCore->AttribHead, &pAttrib->ListEntry);
+
+ if (pAttrHdr->uAttrType == NTFS_AT_ATTRIBUTE_LIST)
+ pAttrList = pAttrib;
+
+ /* Advance. */
+ offRec += cbAttrib;
+ }
+
+ /*
+ * Process any attribute list.
+ */
+ if (pAttrList)
+ {
+ /** @todo */
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Translates a attribute value offset to a disk offset.
+ *
+ * @returns Disk offset, UINT64_MAX if not translatable for some reason.
+ * @param pAttr The
+ * @param off The offset to translate.
+ * @param pcbValid Where to return the run length at the return offset.
+ * Optional.
+ */
+static uint64_t rtFsNtfsAttr_OffsetToDisk(PRTFSNTFSATTR pAttr, uint64_t off, uint64_t *pcbValid)
+{
+ /*
+ * Searching the extend list is a tad complicated since it starts in one
+ * structure and continues in a different one. But whatever.
+ */
+ PRTFSNTFSEXTENTS pTable = &pAttr->Extents;
+ PRTFSNTFSATTRSUBREC pCurSub = NULL;
+ for (;;)
+ {
+ if (off < pTable->cbData)
+ {
+ uint32_t iExtent = 0;
+ while ( iExtent < pTable->cExtents
+ && off >= pTable->paExtents[iExtent].cbExtent)
+ {
+ off -= pTable->paExtents[iExtent].cbExtent;
+ iExtent++;
+ }
+ AssertReturn(iExtent < pTable->cExtents, UINT64_MAX);
+ if (pcbValid)
+ *pcbValid = pTable->paExtents[iExtent].cbExtent - off;
+ return pTable->paExtents[iExtent].off != UINT64_MAX ? pTable->paExtents[iExtent].off + off : UINT64_MAX;
+ }
+
+ /* Next table. */
+ off -= pTable->cbData;
+ if (!pCurSub)
+ pCurSub = pAttr->pSubRecHead;
+ else
+ pCurSub = pCurSub->pNext;
+ if (!pCurSub)
+ {
+ if (pcbValid)
+ *pcbValid = 0;
+ return UINT64_MAX;
+ }
+ pTable = &pCurSub->Extents;
+ }
+ /* not reached */
+}
+
+
+static int rtFsNtfsAttr_Read(PRTFSNTFSATTR pAttr, uint64_t off, void *pvBuf, size_t cbToRead)
+{
+ PRTFSNTFSVOL pVol = pAttr->pCore->pVol;
+ int rc;
+ if (!pAttr->pAttrHdr->fNonResident)
+ {
+ /*
+ * The attribute is resident.
+ */
+ uint32_t cbAttrib = RT_LE2H_U32(pAttr->pAttrHdr->cbAttrib);
+ uint32_t cbValue = RT_LE2H_U32(pAttr->pAttrHdr->u.Res.cbValue);
+ uint16_t offValue = RT_LE2H_U16(pAttr->pAttrHdr->u.Res.offValue);
+ if ( off < cbValue
+ && cbToRead <= cbValue
+ && off + cbToRead <= cbValue)
+ {
+ if (offValue <= cbAttrib)
+ {
+ cbAttrib -= offValue;
+ if (off < cbAttrib)
+ {
+ /** @todo check if its possible to have cbValue larger than the attribute and
+ * reading those extra bytes as zero. */
+ if ( pAttr->offAttrHdrInMftRec + offValue + cbAttrib <= pVol->cbMftRecord
+ && cbAttrib <= pVol->cbMftRecord)
+ {
+ size_t cbToCopy = cbAttrib - off;
+ if (cbToCopy > cbToRead)
+ cbToCopy = cbToRead;
+ memcpy(pvBuf, (uint8_t *)pAttr->pAttrHdr + offValue, cbToCopy);
+ pvBuf = (uint8_t *)pvBuf + cbToCopy;
+ cbToRead -= cbToCopy;
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ rc = VERR_VFS_BOGUS_OFFSET;
+ Log(("rtFsNtfsAttr_Read: bad resident attribute!\n"));
+ }
+ }
+ else
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VFS_BOGUS_FORMAT;
+ }
+ else
+ rc = VERR_EOF;
+ }
+ else if (pAttr->pAttrHdr->u.NonRes.uCompressionUnit == 0)
+ {
+ /*
+ * Uncompressed non-resident attribute.
+ */
+ uint64_t const cbAllocated = RT_LE2H_U64(pAttr->pAttrHdr->u.NonRes.cbAllocated);
+ if ( off >= cbAllocated
+ || cbToRead > cbAllocated
+ || off + cbToRead > cbAllocated)
+ rc = VERR_EOF;
+ else
+ {
+ rc = VINF_SUCCESS;
+
+ uint64_t const cbInitialized = RT_LE2H_U64(pAttr->pAttrHdr->u.NonRes.cbInitialized);
+ if ( off < cbInitialized
+ && cbToRead > 0)
+ {
+ /*
+ * Locate the first extent. This is a tad complicated.
+ *
+ * We move off along as we traverse the extent tables, so that it is relative
+ * to the start of the current extent.
+ */
+ PRTFSNTFSEXTENTS pTable = &pAttr->Extents;
+ uint32_t iExtent = 0;
+ PRTFSNTFSATTRSUBREC pCurSub = NULL;
+ for (;;)
+ {
+ if (off < pTable->cbData)
+ {
+ while ( iExtent < pTable->cExtents
+ && off >= pTable->paExtents[iExtent].cbExtent)
+ {
+ off -= pTable->paExtents[iExtent].cbExtent;
+ iExtent++;
+ }
+ AssertReturn(iExtent < pTable->cExtents, VERR_INTERNAL_ERROR_2);
+ break;
+ }
+
+ /* Next table. */
+ off -= pTable->cbData;
+ if (!pCurSub)
+ pCurSub = pAttr->pSubRecHead;
+ else
+ pCurSub = pCurSub->pNext;
+ if (!pCurSub)
+ {
+ iExtent = UINT32_MAX;
+ break;
+ }
+ pTable = &pCurSub->Extents;
+ iExtent = 0;
+ }
+
+ /*
+ * The read loop.
+ */
+ while (iExtent != UINT32_MAX)
+ {
+ uint64_t cbMaxRead = pTable->paExtents[iExtent].cbExtent;
+ Assert(off < cbMaxRead);
+ cbMaxRead -= off;
+ size_t const cbThisRead = cbMaxRead >= cbToRead ? cbToRead : (size_t)cbMaxRead;
+ if (pTable->paExtents[iExtent].off == UINT64_MAX)
+ RT_BZERO(pvBuf, cbThisRead);
+ else
+ {
+ rc = RTVfsFileReadAt(pVol->hVfsBacking, pTable->paExtents[iExtent].off + off, pvBuf, cbThisRead, NULL);
+ Log4(("NTFS: Volume read: @%#RX64 LB %#zx -> %Rrc\n", pTable->paExtents[iExtent].off + off, cbThisRead, rc));
+ if (RT_FAILURE(rc))
+ break;
+ }
+ pvBuf = (uint8_t *)pvBuf + cbThisRead;
+ cbToRead -= cbThisRead;
+ if (!cbToRead)
+ break;
+ off = 0;
+
+ /*
+ * Advance to the next extent.
+ */
+ iExtent++;
+ if (iExtent >= pTable->cExtents)
+ {
+ pCurSub = pCurSub ? pCurSub->pNext : pAttr->pSubRecHead;
+ if (!pCurSub)
+ break;
+ pTable = &pCurSub->Extents;
+ iExtent = 0;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ LogRel(("rtFsNtfsAttr_Read: Compressed files are not supported\n"));
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ /*
+ * Anything else beyond the end of what's stored/initialized?
+ */
+ if ( cbToRead > 0
+ && RT_SUCCESS(rc))
+ {
+ RT_BZERO(pvBuf, cbToRead);
+ }
+
+ return rc;
+}
+
+
+/**
+ *
+ * @note Only modifying non-resident data is currently supported. No
+ * shrinking or growing. Metadata is not modified.
+ */
+static int rtFsNtfsAttr_Write(PRTFSNTFSATTR pAttr, uint64_t off, void const *pvBuf, size_t cbToWrite)
+{
+ PRTFSNTFSVOL pVol = pAttr->pCore->pVol;
+ int rc;
+ if (!pAttr->pAttrHdr->fNonResident)
+ {
+ /*
+ * The attribute is resident. Currently not supported.
+ */
+#if 0
+ uint32_t cbAttrib = RT_LE2H_U32(pAttr->pAttrHdr->cbAttrib);
+ uint32_t cbValue = RT_LE2H_U32(pAttr->pAttrHdr->u.Res.cbValue);
+ uint16_t offValue = RT_LE2H_U16(pAttr->pAttrHdr->u.Res.offValue);
+ if ( off < cbValue
+ && cbToWrite <= cbValue
+ && off + cbToWrite <= cbValue)
+ {
+ if (offValue <= cbAttrib)
+ {
+ cbAttrib -= offValue;
+ if (off < cbAttrib)
+ {
+ /** @todo check if its possible to have cbValue larger than the attribute and
+ * reading those extra bytes as zero. */
+ if ( pAttr->offAttrHdrInMftRec + offValue + cbAttrib <= pVol->cbMftRecord
+ && cbAttrib <= pVol->cbMftRecord)
+ {
+ size_t cbToCopy = cbAttrib - off;
+ if (cbToCopy > cbToWrite)
+ cbToCopy = cbToWrite;
+ memcpy(pvBuf, (uint8_t *)pAttr->pAttrHdr + offValue, cbToCopy);
+ pvBuf = (uint8_t *)pvBuf + cbToCopy;
+ cbToWrite -= cbToCopy;
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ rc = VERR_VFS_BOGUS_OFFSET;
+ Log(("rtFsNtfsAttr_Write: bad resident attribute!\n"));
+ }
+ }
+ else
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VFS_BOGUS_FORMAT;
+ }
+ else
+ rc = VERR_EOF;
+#else
+ LogRel(("rtFsNtfsAttr_Write: file too small to write to.\n"));
+ rc = VERR_INTERNAL_ERROR_3;
+#endif
+ }
+ else if (pAttr->pAttrHdr->u.NonRes.uCompressionUnit == 0)
+ {
+ /*
+ * Uncompressed non-resident attribute.
+ * Note! We currently
+ */
+ uint64_t const cbAllocated = RT_LE2H_U64(pAttr->pAttrHdr->u.NonRes.cbAllocated);
+ if ( off >= cbAllocated
+ || cbToWrite > cbAllocated
+ || off + cbToWrite > cbAllocated)
+ rc = VERR_EOF;
+ else
+ {
+ rc = VINF_SUCCESS;
+
+ uint64_t const cbInitialized = RT_LE2H_U64(pAttr->pAttrHdr->u.NonRes.cbInitialized);
+ if ( off < cbInitialized
+ && cbToWrite > 0)
+ {
+ /*
+ * Locate the first extent. This is a tad complicated.
+ *
+ * We move off along as we traverse the extent tables, so that it is relative
+ * to the start of the current extent.
+ */
+ PRTFSNTFSEXTENTS pTable = &pAttr->Extents;
+ uint32_t iExtent = 0;
+ PRTFSNTFSATTRSUBREC pCurSub = NULL;
+ for (;;)
+ {
+ if (off < pTable->cbData)
+ {
+ while ( iExtent < pTable->cExtents
+ && off >= pTable->paExtents[iExtent].cbExtent)
+ {
+ off -= pTable->paExtents[iExtent].cbExtent;
+ iExtent++;
+ }
+ AssertReturn(iExtent < pTable->cExtents, VERR_INTERNAL_ERROR_2);
+ break;
+ }
+
+ /* Next table. */
+ off -= pTable->cbData;
+ if (!pCurSub)
+ pCurSub = pAttr->pSubRecHead;
+ else
+ pCurSub = pCurSub->pNext;
+ if (!pCurSub)
+ {
+ iExtent = UINT32_MAX;
+ break;
+ }
+ pTable = &pCurSub->Extents;
+ iExtent = 0;
+ }
+
+ /*
+ * The write loop.
+ */
+ while (iExtent != UINT32_MAX)
+ {
+ uint64_t cbMaxWrite = pTable->paExtents[iExtent].cbExtent;
+ Assert(off < cbMaxWrite);
+ cbMaxWrite -= off;
+ size_t const cbThisWrite = cbMaxWrite >= cbToWrite ? cbToWrite : (size_t)cbMaxWrite;
+ if (pTable->paExtents[iExtent].off == UINT64_MAX)
+ {
+ if (!ASMMemIsZero(pvBuf, cbThisWrite))
+ {
+ LogRel(("rtFsNtfsAttr_Write: Unable to modify sparse section of file!\n"));
+ rc = VERR_INTERNAL_ERROR_2;
+ break;
+ }
+ }
+ else
+ {
+ rc = RTVfsFileWriteAt(pVol->hVfsBacking, pTable->paExtents[iExtent].off + off, pvBuf, cbThisWrite, NULL);
+ Log4(("NTFS: Volume write: @%#RX64 LB %#zx -> %Rrc\n", pTable->paExtents[iExtent].off + off, cbThisWrite, rc));
+ if (RT_FAILURE(rc))
+ break;
+ }
+ pvBuf = (uint8_t const *)pvBuf + cbThisWrite;
+ cbToWrite -= cbThisWrite;
+ if (!cbToWrite)
+ break;
+ off = 0;
+
+ /*
+ * Advance to the next extent.
+ */
+ iExtent++;
+ if (iExtent >= pTable->cExtents)
+ {
+ pCurSub = pCurSub ? pCurSub->pNext : pAttr->pSubRecHead;
+ if (!pCurSub)
+ break;
+ pTable = &pCurSub->Extents;
+ iExtent = 0;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ LogRel(("rtFsNtfsAttr_Write: Compressed files are not supported\n"));
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ /*
+ * Anything else beyond the end of what's stored/initialized?
+ */
+ if ( cbToWrite > 0
+ && RT_SUCCESS(rc))
+ {
+ LogRel(("rtFsNtfsAttr_Write: Unable to modify sparse section (tail) of file!\n"));
+ rc = VERR_INTERNAL_ERROR_2;
+ }
+
+ return rc;
+}
+
+
+/**
+ *
+ * @returns
+ * @param pRecHdr .
+ * @param cbRec .
+ * @param fRelaxedUsa .
+ * @param pErrInfo .
+ *
+ * @see https://msdn.microsoft.com/en-us/library/bb470212%28v=vs.85%29.aspx
+ */
+static int rtFsNtfsRec_DoMultiSectorFixups(PNTFSRECHDR pRecHdr, uint32_t cbRec, bool fRelaxedUsa, PRTERRINFO pErrInfo)
+{
+ /*
+ * Do sanity checking.
+ */
+ uint16_t offUpdateSeqArray = RT_LE2H_U16(pRecHdr->offUpdateSeqArray);
+ uint16_t cUpdateSeqEntries = RT_LE2H_U16(pRecHdr->cUpdateSeqEntries);
+ if ( !(cbRec & (NTFS_MULTI_SECTOR_STRIDE - 1))
+ && !(offUpdateSeqArray & 1) /* two byte aligned */
+ && cUpdateSeqEntries == 1 + cbRec / NTFS_MULTI_SECTOR_STRIDE
+ && offUpdateSeqArray + (uint32_t)cUpdateSeqEntries * 2U < NTFS_MULTI_SECTOR_STRIDE - 2U)
+ {
+ uint16_t const *pauUsa = (uint16_t const *)((uint8_t *)pRecHdr + offUpdateSeqArray);
+
+ /*
+ * The first update seqence array entry is the value stored at
+ * the fixup locations at the end of the blocks. We read this
+ * and check each of the blocks.
+ */
+ uint16_t const uCheck = *pauUsa++;
+ cUpdateSeqEntries--;
+ for (uint16_t iBlock = 0; iBlock < cUpdateSeqEntries; iBlock++)
+ {
+ uint16_t const *puBlockCheck = (uint16_t const *)((uint8_t *)pRecHdr + (iBlock + 1) * NTFS_MULTI_SECTOR_STRIDE - 2U);
+ if (*puBlockCheck == uCheck)
+ { /* likely */ }
+ else if (!fRelaxedUsa)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_OFFSET,
+ "Multisector transfer error: block #%u ends with %#x instead of %#x (fixup: %#x)",
+ iBlock, RT_LE2H_U16(*puBlockCheck), RT_LE2H_U16(uCheck), RT_LE2H_U16(pauUsa[iBlock]) );
+ else
+ {
+ Log(("NTFS: Multisector transfer warning: block #%u ends with %#x instead of %#x (fixup: %#x)\n",
+ iBlock, RT_LE2H_U16(*puBlockCheck), RT_LE2H_U16(uCheck), RT_LE2H_U16(pauUsa[iBlock]) ));
+ return VINF_SUCCESS;
+ }
+ }
+
+ /*
+ * Apply the fixups.
+ * Note! We advanced pauUsa above, so it's now at the fixup values.
+ */
+ for (uint16_t iBlock = 0; iBlock < cUpdateSeqEntries; iBlock++)
+ {
+ uint16_t *puFixup = (uint16_t *)((uint8_t *)pRecHdr + (iBlock + 1) * NTFS_MULTI_SECTOR_STRIDE - 2U);
+ *puFixup = pauUsa[iBlock];
+ }
+ return VINF_SUCCESS;
+ }
+ if (fRelaxedUsa)
+ {
+ Log(("NTFS: Ignoring bogus multisector update sequence: cbRec=%#x uMagic=%#RX32 offUpdateSeqArray=%#x cUpdateSeqEntries=%#x\n",
+ cbRec, RT_LE2H_U32(pRecHdr->uMagic), offUpdateSeqArray, cUpdateSeqEntries ));
+ return VINF_SUCCESS;
+ }
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_OFFSET,
+ "Bogus multisector update sequence: cbRec=%#x uMagic=%#RX32 offUpdateSeqArray=%#x cUpdateSeqEntries=%#x",
+ cbRec, RT_LE2H_U32(pRecHdr->uMagic), offUpdateSeqArray, cUpdateSeqEntries);
+}
+
+
+/**
+ * Allocate and parse an MFT record, returning a core object structure.
+ *
+ * @returns IPRT status code.
+ * @param pThis The NTFS volume instance.
+ * @param idxMft The index of the MTF record.
+ * @param fRelaxedUsa Relaxed update sequence checking. Won't fail if
+ * checks doesn't work or not present.
+ * @param ppCore Where to return the core object structure.
+ * @param pErrInfo Where to return error details. Optional.
+ */
+static int rtFsNtfsVol_NewCoreForMftIdx(PRTFSNTFSVOL pThis, uint64_t idxMft, bool fRelaxedUsa,
+ PRTFSNTFSCORE *ppCore, PRTERRINFO pErrInfo)
+{
+ *ppCore = NULL;
+ Assert(pThis->pMftData);
+ Assert(RTAvlU64Get(&pThis->MftRoot, idxMft) == NULL);
+
+ PRTFSNTFSMFTREC pRec = rtFsNtfsVol_NewMftRec(pThis, idxMft);
+ AssertReturn(pRec, VERR_NO_MEMORY);
+
+ uint64_t offRec = idxMft * pThis->cbMftRecord;
+ int rc = rtFsNtfsAttr_Read(pThis->pMftData, offRec, pRec->pbRec, pThis->cbMftRecord);
+ if (RT_SUCCESS(rc))
+ rc = rtFsNtfsRec_DoMultiSectorFixups(&pRec->pFileRec->Hdr, pThis->cbMftRecord, fRelaxedUsa, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ rtfsNtfsMftRec_Log(pRec, pThis->cbMftRecord);
+#endif
+ rc = rtFsNtfsVol_ParseMft(pThis, pRec, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ PRTFSNTFSCORE pCore = pRec->pCore;
+ rtFsNtfsMftRec_Release(pRec, pThis);
+
+ /* Insert core into the cache list and update the cost, maybe trimming the cache. */
+ RTListAppend(&pThis->CoreInUseHead, &pCore->ListEntry);
+ pThis->cbCoreObjects += pCore->cbCost;
+ if (pThis->cbCoreObjects > RTFSNTFS_MAX_CORE_CACHE_SIZE)
+ rtFsNtfsIdxVol_TrimCoreObjectCache(pThis);
+
+ *ppCore = pCore;
+ return VINF_SUCCESS;
+ }
+
+ if (pRec->pCore)
+ rtFsNtfsCore_Destroy(pRec->pCore);
+ rtFsNtfsMftRec_Release(pRec, pThis);
+ }
+ return rc;
+}
+
+
+/**
+ * Queries the core object struct for the given MFT record reference.
+ *
+ * Does caching.
+ *
+ * @returns IPRT status code.
+ * @param pThis The NTFS volume instance.
+ * @param pMftRef The MFT reference to get the corresponding core
+ * for.
+ * @param fRelaxedUsa Relaxed update sequence checking. Won't fail if
+ * checks doesn't work or not present.
+ * @param ppCore Where to return the referenced core object
+ * structure.
+ * @param pErrInfo Where to return error details. Optional.
+ */
+static int rtFsNtfsVol_QueryCoreForMftRef(PRTFSNTFSVOL pThis, PCNTFSMFTREF pMftRef , bool fRelaxedUsa,
+ PRTFSNTFSCORE *ppCore, PRTERRINFO pErrInfo)
+{
+ *ppCore = NULL;
+ Assert(pThis->pMftData);
+
+ int rc;
+ PRTFSNTFSMFTREC pMftRec = (PRTFSNTFSMFTREC)RTAvlU64Get(&pThis->MftRoot, NTFSMFTREF_GET_IDX(pMftRef));
+ if (pMftRec)
+ {
+ /*
+ * Cache hit. Check that the resure sequence number matches.
+ * To be slightly paranoid, also check that it's a base MFT record and that it has been parsed already.
+ */
+ if (RT_LE2H_U16(pMftRec->pFileRec->uRecReuseSeqNo) == NTFSMFTREF_GET_SEQ(pMftRef))
+ {
+ if ( NTFSMFTREF_IS_ZERO(&pMftRec->pFileRec->BaseMftRec)
+ && pMftRec->pCore)
+ {
+ rtFsNtfsCore_Retain(pMftRec->pCore);
+ *ppCore = pMftRec->pCore;
+ rc = VINF_SUCCESS;
+ }
+ else
+ AssertLogRelMsgFailedStmt(("pCore=%p; BaseMftRec=%#RX64 sqn %#x\n", pMftRec->pCore,
+ NTFSMFTREF_GET_IDX(&pMftRec->pFileRec->BaseMftRec),
+ NTFSMFTREF_GET_SEQ(&pMftRec->pFileRec->BaseMftRec)),
+ rc = VERR_INTERNAL_ERROR_3 );
+ }
+ else
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_OFFSET,
+ "Stale parent directory MFT reference: %#RX64 sqn %#x - current sqn %#x",
+ NTFSMFTREF_GET_IDX(pMftRef), NTFSMFTREF_GET_SEQ(pMftRef),
+ RT_LE2H_U16(pMftRec->pFileRec->uRecReuseSeqNo) );
+ }
+ else
+ {
+ /*
+ * Load new and check that the reuse sequence number match.
+ */
+ rc = rtFsNtfsVol_NewCoreForMftIdx(pThis, NTFSMFTREF_GET_IDX(pMftRef), fRelaxedUsa, ppCore, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ PRTFSNTFSCORE pCore = *ppCore;
+ if (RT_LE2H_U16(pCore->pMftRec->pFileRec->uRecReuseSeqNo) == NTFSMFTREF_GET_SEQ(pMftRef))
+ rc = VINF_SUCCESS;
+ else
+ {
+ rtFsNtfsCore_Release(pCore);
+ *ppCore = NULL;
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_OFFSET,
+ "Stale parent directory MFT reference: %#RX64 sqn %#x - current sqn %#x",
+ NTFSMFTREF_GET_IDX(pMftRef), NTFSMFTREF_GET_SEQ(pMftRef),
+ RT_LE2H_U16(pCore->pMftRec->pFileRec->uRecReuseSeqNo) );
+ }
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Destroys a core structure.
+ *
+ * ASSUMES the caller has remove @a pThis from the list it's on and updated the
+ * cbCoreObjects as necessary.
+ *
+ * @returns 0
+ * @param pThis The core structure.
+ */
+static uint32_t rtFsNtfsCore_Destroy(PRTFSNTFSCORE pThis)
+{
+ /*
+ * Free attributes.
+ */
+ PRTFSNTFSATTR pCurAttr;
+ PRTFSNTFSATTR pNextAttr;
+ RTListForEachSafe(&pThis->AttribHead, pCurAttr, pNextAttr, RTFSNTFSATTR, ListEntry)
+ {
+ PRTFSNTFSATTRSUBREC pSub = pCurAttr->pSubRecHead;
+ while (pSub)
+ {
+ pCurAttr->pSubRecHead = pSub->pNext;
+ RTMemFree(pSub->Extents.paExtents);
+ pSub->Extents.paExtents = NULL;
+ pSub->pAttrHdr = NULL;
+ pSub->pNext = NULL;
+ RTMemFree(pSub);
+
+ pSub = pCurAttr->pSubRecHead;
+ }
+
+ pCurAttr->pCore = NULL;
+ pCurAttr->pAttrHdr = NULL;
+ RTMemFree(pCurAttr->Extents.paExtents);
+ pCurAttr->Extents.paExtents = NULL;
+ }
+
+ /*
+ * Release the MFT chain.
+ */
+ PRTFSNTFSMFTREC pMftRec = pThis->pMftRec;
+ while (pMftRec)
+ {
+ pThis->pMftRec = pMftRec->pNext;
+ Assert(pMftRec->pCore == pThis);
+ pMftRec->pNext = NULL;
+ pMftRec->pCore = NULL;
+ rtFsNtfsMftRec_Release(pMftRec, pThis->pVol);
+
+ pMftRec = pThis->pMftRec;
+ }
+
+ RTMemFree(pThis);
+
+ return 0;
+}
+
+
+/**
+ * Trims the core object cache down to RTFSNTFS_MAX_CORE_CACHE_SIZE.
+ *
+ * @param pThis The NTFS volume instance.
+ */
+static void rtFsNtfsIdxVol_TrimCoreObjectCache(PRTFSNTFSVOL pThis)
+{
+ while (pThis->cbCoreObjects > RTFSNTFS_MAX_CORE_CACHE_SIZE)
+ {
+ PRTFSNTFSCORE pCore = RTListRemoveFirst(&pThis->CoreUnusedHead, RTFSNTFSCORE, ListEntry);
+ if (!pCore)
+ break;
+ pThis->cbCoreObjects -= pCore->cbCost;
+ rtFsNtfsCore_Destroy(pCore);
+ }
+}
+
+
+/**
+ * Releases a refernece to a core structure, maybe destroying it.
+ *
+ * @returns New reference count.
+ * @param pThis The core structure.
+ */
+static uint32_t rtFsNtfsCore_Release(PRTFSNTFSCORE pThis)
+{
+ if (pThis)
+ {
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ Assert(cRefs < 128);
+ if (cRefs != 0)
+ return cRefs;
+
+ /* Move from in-use list to unused list. Trim the cache if too big. */
+ RTListNodeRemove(&pThis->ListEntry);
+
+ PRTFSNTFSVOL pVol = pThis->pVol;
+ RTListAppend(&pVol->CoreUnusedHead, &pThis->ListEntry);
+ if (pVol->cbCoreObjects > RTFSNTFS_MAX_CORE_CACHE_SIZE)
+ rtFsNtfsIdxVol_TrimCoreObjectCache(pVol);
+ }
+ return 0;
+}
+
+
+/**
+ * Retains a refernece to a core structure.
+ *
+ * @returns New reference count.
+ * @param pThis The core structure.
+ */
+static uint32_t rtFsNtfsCore_Retain(PRTFSNTFSCORE pThis)
+{
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ if (cRefs == 1)
+ {
+ /* Move from unused list to in-use list. */
+ RTListNodeRemove(&pThis->ListEntry);
+ RTListAppend(&pThis->pVol->CoreInUseHead, &pThis->ListEntry);
+ }
+ Assert(cRefs < 128);
+ return cRefs;
+}
+
+
+/**
+ * Finds an unnamed attribute.
+ *
+ * @returns Pointer to the attribute structure if found, NULL if not.
+ * @param pThis The core object structure to search.
+ * @param uAttrType The attribute type to find.
+ */
+static PRTFSNTFSATTR rtFsNtfsCore_FindUnnamedAttribute(PRTFSNTFSCORE pThis, uint32_t uAttrType)
+{
+ PRTFSNTFSATTR pCurAttr;
+ RTListForEach(&pThis->AttribHead, pCurAttr, RTFSNTFSATTR, ListEntry)
+ {
+ PNTFSATTRIBHDR pAttrHdr = pCurAttr->pAttrHdr;
+ if ( pAttrHdr->uAttrType == uAttrType
+ && pAttrHdr->cwcName == 0)
+ return pCurAttr;
+ }
+ return NULL;
+}
+
+
+/**
+ * Finds a named attribute, case insensitive ASCII variant.
+ *
+ * @returns Pointer to the attribute structure if found, NULL if not.
+ * @param pThis The core object structure to search.
+ * @param uAttrType The attribute type to find.
+ * @param pszAttrib The attribute name, predefined 7-bit ASCII name.
+ * @param cchAttrib The length of the attribute.
+ */
+static PRTFSNTFSATTR rtFsNtfsCore_FindNamedAttributeAscii(PRTFSNTFSCORE pThis, uint32_t uAttrType,
+ const char *pszAttrib, size_t cchAttrib)
+{
+ Assert(cchAttrib > 0);
+ PRTFSNTFSATTR pCurAttr;
+ RTListForEach(&pThis->AttribHead, pCurAttr, RTFSNTFSATTR, ListEntry)
+ {
+ PNTFSATTRIBHDR pAttrHdr = pCurAttr->pAttrHdr;
+ if ( pAttrHdr->uAttrType == uAttrType
+ && pAttrHdr->cwcName == cchAttrib
+ && RTUtf16NICmpAscii(NTFSATTRIBHDR_GET_NAME(pAttrHdr), pszAttrib, cchAttrib) == 0)
+ return pCurAttr;
+ }
+ return NULL;
+}
+
+
+/**
+ * This attribute conversion code is a slightly modified version of rtFsModeFromDos.
+ *
+ * @returns IPRT fmode mask.
+ * @param fFileAttribs The NT file attributes.
+ * @param pFilename The filename attribute structure, optional.
+ * @param cbFilename The size of the filename attribute structure.
+ */
+static RTFMODE rtFsNtfsConvertFileattribsToMode(uint32_t fFileAttribs, PCNTFSATFILENAME pFilename, uint32_t cbFilename)
+{
+ RTFMODE fMode = (fFileAttribs << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_NT;
+ if (fFileAttribs & NTFS_FA_DUP_FILE_NAME_INDEX_PRESENT)
+ fMode |= RTFS_DOS_DIRECTORY;
+
+ /* everything is readable. */
+ fMode |= RTFS_UNIX_IRUSR | RTFS_UNIX_IRGRP | RTFS_UNIX_IROTH;
+ if (fMode & RTFS_DOS_DIRECTORY)
+ /* directories are executable. */
+ fMode |= RTFS_TYPE_DIRECTORY | RTFS_UNIX_IXUSR | RTFS_UNIX_IXGRP | RTFS_UNIX_IXOTH;
+ else
+ {
+ fMode |= RTFS_TYPE_FILE;
+ if ( pFilename
+ && pFilename->cwcFilename >= 4
+ && RT_UOFFSETOF_DYN(NTFSATFILENAME, wszFilename[pFilename->cwcFilename]) <= cbFilename)
+ {
+ PCRTUTF16 pwcExt = &pFilename->wszFilename[pFilename->cwcFilename - 4];
+ if ( *pwcExt++ == '.')
+ {
+ /* check for executable extension. */
+ if ( (unsigned)pwcExt[0] < 0x7fU
+ && (unsigned)pwcExt[1] < 0x7fU
+ && (unsigned)pwcExt[2] < 0x7fU)
+ {
+ char szExt[4];
+ szExt[0] = RT_C_TO_LOWER(pwcExt[0]);
+ szExt[1] = RT_C_TO_LOWER(pwcExt[1]);
+ szExt[2] = RT_C_TO_LOWER(pwcExt[2]);
+ szExt[3] = '\0';
+ if ( !memcmp(szExt, "exe", 4)
+ || !memcmp(szExt, "bat", 4)
+ || !memcmp(szExt, "com", 4)
+ || !memcmp(szExt, "cmd", 4)
+ || !memcmp(szExt, "btm", 4)
+ )
+ fMode |= RTFS_UNIX_IXUSR | RTFS_UNIX_IXGRP | RTFS_UNIX_IXOTH;
+ }
+ }
+ }
+ }
+
+ /* Is it really a symbolic link? */
+ if ( (fMode & RTFS_DOS_NT_REPARSE_POINT)
+ && pFilename
+ && pFilename->u.uReparseTag == RTFSMODE_SYMLINK_REPARSE_TAG)
+ fMode = (fMode & ~RTFS_TYPE_MASK) | RTFS_TYPE_SYMLINK;
+
+ /* writable? */
+ if (!(fMode & RTFS_DOS_READONLY))
+ fMode |= RTFS_UNIX_IWUSR | RTFS_UNIX_IWGRP | RTFS_UNIX_IWOTH;
+
+ return fMode;
+}
+
+
+/**
+ * Worker for various QueryInfo methods.
+ *
+ * @returns IPRT status code.
+ * @param pThis The core object structure to return info for.
+ * @param pAttr The attribute that's being presented. Take the
+ * allocation and timestamp info from it, if
+ * non-resident.
+ * @param pObjInfo Where to return object info.
+ * @param enmAddAttr What additional info to return.
+ */
+static int rtFsNtfsCore_QueryInfo(PRTFSNTFSCORE pThis, PRTFSNTFSATTR pAttr, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ /*
+ * Wipe the structure and fill in common dummy value.
+ */
+ RT_ZERO(*pObjInfo);
+ switch (enmAddAttr)
+ {
+ 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 = pThis->pMftRec->TreeNode.Key;
+ //pObjInfo->Attr.u.Unix.fFlags = 0;
+ //pObjInfo->Attr.u.Unix.GenerationId = 0;
+ //pObjInfo->Attr.u.Unix.Device = 0;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ pObjInfo->Attr.u.UnixOwner.uid = NIL_RTUID;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ pObjInfo->Attr.u.UnixGroup.gid = NIL_RTGID;
+ break;
+
+ default:
+ break;
+ }
+
+ /*
+ * Look for the standard information attribute and use that as basis.
+ */
+ uint32_t fFileAttribs;
+ PRTFSNTFSATTR pStdInfoAttr = rtFsNtfsCore_FindUnnamedAttribute(pThis, NTFS_AT_STANDARD_INFORMATION);
+ if ( pStdInfoAttr
+ && pStdInfoAttr->cbResident >= sizeof(NTFSATSTDINFO) )
+ {
+ Assert(!pStdInfoAttr->pAttrHdr->fNonResident);
+ PCNTFSATSTDINFO pStdInfo = (PCNTFSATSTDINFO)NTFSATTRIBHDR_GET_RES_VALUE_PTR(pStdInfoAttr->pAttrHdr);
+ RTTimeSpecSetNtTime(&pObjInfo->BirthTime, RT_LE2H_U64(pStdInfo->iCreationTime));
+ RTTimeSpecSetNtTime(&pObjInfo->ModificationTime, RT_LE2H_U64(pStdInfo->iLastDataModTime));
+ RTTimeSpecSetNtTime(&pObjInfo->ChangeTime, RT_LE2H_U64(pStdInfo->iLastMftModTime));
+ RTTimeSpecSetNtTime(&pObjInfo->AccessTime, RT_LE2H_U64(pStdInfo->iLastAccessTime));
+ if (enmAddAttr == RTFSOBJATTRADD_UNIX)
+ {
+ pObjInfo->Attr.u.Unix.uid = pStdInfo->idOwner;
+ pObjInfo->Attr.u.Unix.GenerationId = pStdInfo->uFileVersion;
+ }
+ else if (enmAddAttr == RTFSOBJATTRADD_UNIX_OWNER)
+ pObjInfo->Attr.u.UnixOwner.uid = pStdInfo->idOwner;
+ fFileAttribs = pStdInfo->fFileAttribs;
+ }
+ else
+ {
+ /** @todo check out the filename record? */
+ switch (pAttr->pAttrHdr->uAttrType)
+ {
+ default:
+ AssertFailed();
+ RT_FALL_THRU();
+ case NTFS_AT_DATA:
+ fFileAttribs = NTFS_FA_NORMAL;
+ break;
+
+ case NTFS_AT_INDEX_ROOT:
+ case NTFS_AT_INDEX_ALLOCATION:
+ fFileAttribs = NTFS_FA_DIRECTORY;
+ break;
+ }
+ }
+
+ /*
+ * Take the allocation info from the destilled attribute data.
+ */
+ pObjInfo->cbObject = pAttr->cbValue;
+ pObjInfo->cbAllocated = pAttr->Extents.cbData;
+ if ( pAttr->pAttrHdr->fNonResident
+ && (int64_t)pObjInfo->cbAllocated < (int64_t)RT_LE2H_U64(pAttr->pAttrHdr->u.NonRes.cbAllocated))
+ pObjInfo->cbAllocated = RT_LE2H_U64(pAttr->pAttrHdr->u.NonRes.cbAllocated);
+
+ /*
+ * See if we can find a filename record before we try convert the file attributes to mode.
+ */
+ PCNTFSATFILENAME pFilename = NULL;
+ PRTFSNTFSATTR pFilenameAttr = rtFsNtfsCore_FindUnnamedAttribute(pThis, NTFS_AT_FILENAME);
+ if ( pFilenameAttr
+ && pFilenameAttr->cbResident >= RT_UOFFSETOF(NTFSATFILENAME, wszFilename) )
+ {
+ Assert(!pFilenameAttr->pAttrHdr->fNonResident);
+ pFilename = (PCNTFSATFILENAME)NTFSATTRIBHDR_GET_RES_VALUE_PTR(pFilenameAttr->pAttrHdr);
+ if (pStdInfoAttr)
+ fFileAttribs |= pFilename->fFileAttribs;
+ else
+ fFileAttribs = pFilename->fFileAttribs;
+ }
+
+ /*
+ * Convert attribs to file mode flags.
+ */
+ pObjInfo->Attr.fMode = rtFsNtfsConvertFileattribsToMode(fFileAttribs, pFilename,
+ pFilenameAttr ? pFilenameAttr->cbResident : 0);
+
+ return VINF_SUCCESS;
+}
+
+
+
+
+/*
+ *
+ * File operations.
+ * File operations.
+ * File operations.
+ *
+ */
+
+/**
+ * Releases a reference to a shared NTFS file structure.
+ *
+ * @returns New reference count.
+ * @param pShared The shared NTFS file structure.
+ */
+static uint32_t rtFsNtfsFileShrd_Release(PRTFSNTFSFILESHRD pShared)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pShared->cRefs);
+ Assert(cRefs < 64);
+ if (cRefs == 0)
+ {
+ LogFlow(("rtFsNtfsFileShrd_Release(%p): Destroying it\n", pShared));
+ Assert(pShared->pData->uObj.pSharedFile == pShared);
+ pShared->pData->uObj.pSharedFile = NULL;
+ rtFsNtfsCore_Release(pShared->pData->pCore);
+ pShared->pData = NULL;
+ RTMemFree(pShared);
+ }
+ return cRefs;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsNtfsFile_Close(void *pvThis)
+{
+ PRTFSNTFSFILE pThis = (PRTFSNTFSFILE)pvThis;
+ LogFlow(("rtFsNtfsFile_Close(%p/%p)\n", pThis, pThis->pShared));
+
+ PRTFSNTFSFILESHRD pShared = pThis->pShared;
+ pThis->pShared = NULL;
+ if (pShared)
+ rtFsNtfsFileShrd_Release(pShared);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsNtfsFile_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSNTFSFILE pThis = (PRTFSNTFSFILE)pvThis;
+ PRTFSNTFSATTR pDataAttr = pThis->pShared->pData;
+ return rtFsNtfsCore_QueryInfo(pDataAttr->pCore, pDataAttr, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtFsNtfsFile_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTFSNTFSFILE pThis = (PRTFSNTFSFILE)pvThis;
+ AssertReturn(pSgBuf->cSegs == 1, VERR_INTERNAL_ERROR_3);
+ RT_NOREF(fBlocking);
+
+ if (off == -1)
+ off = pThis->offFile;
+ else
+ AssertReturn(off >= 0, VERR_INTERNAL_ERROR_3);
+
+ int rc;
+ size_t cbRead = pSgBuf->paSegs[0].cbSeg;
+ if (!pcbRead)
+ {
+ rc = rtFsNtfsAttr_Read(pThis->pShared->pData, off, pSgBuf->paSegs[0].pvSeg, cbRead);
+ if (RT_SUCCESS(rc))
+ pThis->offFile = off + cbRead;
+ Log6(("rtFsNtfsFile_Read: off=%#RX64 cbSeg=%#x -> %Rrc\n", off, pSgBuf->paSegs[0].cbSeg, rc));
+ }
+ else
+ {
+ PRTFSNTFSATTR pDataAttr = pThis->pShared->pData;
+ if ((uint64_t)off >= pDataAttr->cbValue)
+ {
+ *pcbRead = 0;
+ rc = VINF_EOF;
+ }
+ else
+ {
+ if ((uint64_t)off + cbRead <= pDataAttr->cbValue)
+ rc = rtFsNtfsAttr_Read(pThis->pShared->pData, off, pSgBuf->paSegs[0].pvSeg, cbRead);
+ else
+ {
+ /* Return VINF_EOF if beyond end-of-file. */
+ cbRead = (size_t)(pDataAttr->cbValue - (uint64_t)off);
+ rc = rtFsNtfsAttr_Read(pThis->pShared->pData, off, pSgBuf->paSegs[0].pvSeg, cbRead);
+ if (RT_SUCCESS(rc))
+ rc = VINF_EOF;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pThis->offFile = off + cbRead;
+ *pcbRead = cbRead;
+ }
+ else
+ *pcbRead = 0;
+ }
+ Log6(("rtFsNtfsFile_Read: off=%#RX64 cbSeg=%#x -> %Rrc *pcbRead=%#x\n", off, pSgBuf->paSegs[0].cbSeg, rc, *pcbRead));
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtFsNtfsFile_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ PRTFSNTFSFILE pThis = (PRTFSNTFSFILE)pvThis;
+ AssertReturn(pSgBuf->cSegs == 1, VERR_INTERNAL_ERROR_3);
+ RT_NOREF(fBlocking);
+
+ if (off == -1)
+ off = pThis->offFile;
+ else
+ AssertReturn(off >= 0, VERR_INTERNAL_ERROR_3);
+
+ int rc;
+ PRTFSNTFSATTR pDataAttr = pThis->pShared->pData;
+ size_t cbToWrite = pSgBuf->paSegs[0].cbSeg;
+ if ((uint64_t)off + cbToWrite <= pDataAttr->cbValue)
+ {
+ rc = rtFsNtfsAttr_Write(pThis->pShared->pData, off, pSgBuf->paSegs[0].pvSeg, cbToWrite);
+ Log6(("rtFsNtfsFile_Write: off=%#RX64 cbToWrite=%#zx -> %Rrc\n", off, cbToWrite, rc));
+ if (RT_SUCCESS(rc))
+ pThis->offFile = off + cbToWrite;
+ if (pcbWritten)
+ *pcbWritten = RT_SUCCESS(rc) ? cbToWrite : 0;
+ }
+ else if ((uint64_t)off < pDataAttr->cbValue)
+ {
+ size_t cbWritten = pDataAttr->cbValue - off;
+ rc = rtFsNtfsAttr_Write(pThis->pShared->pData, off, pSgBuf->paSegs[0].pvSeg, cbWritten);
+ if (RT_SUCCESS(rc))
+ {
+ Log6(("rtFsNtfsFile_Write: off=%#RX64 cbToWrite=%#zx -> VERR_EOF [EOF: %#RX64, Written: %#zx]\n",
+ off, cbToWrite, pDataAttr->cbValue, cbWritten));
+ pThis->offFile = off + cbWritten;
+ if (pcbWritten)
+ *pcbWritten = cbWritten;
+ rc = VERR_EOF;
+ }
+ else
+ {
+ Log6(("rtFsNtfsFile_Write: off=%#RX64 cbToWrite=%#zx -> %Rrc [EOF: %#RX64]\n", off, cbToWrite, rc, pDataAttr->cbValue));
+ if (pcbWritten)
+ *pcbWritten = 0;
+ }
+ }
+ else
+ {
+ Log6(("rtFsNtfsFile_Write: off=%#RX64 cbToWrite=%#zx -> VERR_EOF [EOF: %#RX64]\n", off, cbToWrite, pDataAttr->cbValue));
+ rc = VERR_EOF;
+ if (pcbWritten)
+ *pcbWritten = 0;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtFsNtfsFile_Flush(void *pvThis)
+{
+ RT_NOREF(pvThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtFsNtfsFile_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTFSNTFSFILE pThis = (PRTFSNTFSFILE)pvThis;
+ *poffActual = pThis->offFile;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtFsNtfsFile_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ RT_NOREF(pvThis, fMode, fMask);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtFsNtfsFile_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ RT_NOREF(pvThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtFsNtfsFile_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ RT_NOREF(pvThis, uid, gid);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSeek}
+ */
+static DECLCALLBACK(int) rtFsNtfsFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual)
+{
+ PRTFSNTFSFILE pThis = (PRTFSNTFSFILE)pvThis;
+ RTFOFF offNew;
+ switch (uMethod)
+ {
+ case RTFILE_SEEK_BEGIN:
+ offNew = offSeek;
+ break;
+ case RTFILE_SEEK_END:
+ offNew = (RTFOFF)pThis->pShared->pData->cbValue + 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) rtFsNtfsFile_QuerySize(void *pvThis, uint64_t *pcbFile)
+{
+ PRTFSNTFSFILE pThis = (PRTFSNTFSFILE)pvThis;
+ *pcbFile = pThis->pShared->pData->cbValue;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSetSize}
+ */
+static DECLCALLBACK(int) rtFsNtfsFile_SetSize(void *pvThis, uint64_t cbFile, uint32_t fFlags)
+{
+ NOREF(pvThis); NOREF(cbFile); NOREF(fFlags);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQueryMaxSize}
+ */
+static DECLCALLBACK(int) rtFsNtfsFile_QueryMaxSize(void *pvThis, uint64_t *pcbMax)
+{
+ RT_NOREF(pvThis);
+ *pcbMax = INT64_MAX;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * NTFS file operations.
+ */
+static const RTVFSFILEOPS g_rtFsNtfsFileOps =
+{
+ { /* Stream */
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FILE,
+ "NTFS File",
+ rtFsNtfsFile_Close,
+ rtFsNtfsFile_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtFsNtfsFile_Read,
+ rtFsNtfsFile_Write,
+ rtFsNtfsFile_Flush,
+ NULL /*PollOne*/,
+ rtFsNtfsFile_Tell,
+ NULL /*pfnSkip*/,
+ NULL /*pfnZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+ },
+ RTVFSFILEOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj),
+ rtFsNtfsFile_SetMode,
+ rtFsNtfsFile_SetTimes,
+ rtFsNtfsFile_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtFsNtfsFile_Seek,
+ rtFsNtfsFile_QuerySize,
+ rtFsNtfsFile_SetSize,
+ rtFsNtfsFile_QueryMaxSize,
+ RTVFSFILEOPS_VERSION
+};
+
+
+static int rtFsNtfsVol_NewFile(PRTFSNTFSVOL pThis, uint64_t fOpen, PCNTFSIDXENTRYHDR pEntryHdr, const char *pszStreamName,
+ PRTVFSFILE phVfsFile, PRTERRINFO pErrInfo, const char *pszWhat)
+{
+ /*
+ * Get the core structure for the MFT record and check that it's a directory we've got.
+ */
+ PRTFSNTFSCORE pCore;
+ int rc = rtFsNtfsVol_QueryCoreForMftRef(pThis, &pEntryHdr->u.FileMftRec, false /*fRelaxedUsa*/, &pCore, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (!(pCore->pMftRec->pFileRec->fFlags & NTFSRECFILE_F_DIRECTORY))
+ {
+ /*
+ * Locate the data attribute.
+ */
+ PRTFSNTFSATTR pDataAttr;
+ if (pszStreamName == NULL)
+ {
+ pDataAttr = rtFsNtfsCore_FindUnnamedAttribute(pCore, NTFS_AT_DATA);
+ if (pDataAttr)
+ rc = VINF_SUCCESS;
+ else
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_NOT_A_FILE, "%s: no unamed data stream", pszWhat);
+ }
+ else
+ {
+ NOREF(pszStreamName);
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_NOT_IMPLEMENTED, "%s: named data streams not implemented yet", pszWhat);
+ pDataAttr = NULL;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Get a referenced shared file structure, creating it if necessary.
+ */
+ PRTFSNTFSFILESHRD pShared = pDataAttr->uObj.pSharedFile;
+ if (pShared)
+ {
+ uint32_t cRefs = ASMAtomicIncU32(&pShared->cRefs);
+ Assert(cRefs > 1); NOREF(cRefs);
+ }
+ else
+ {
+ pShared = (PRTFSNTFSFILESHRD)RTMemAllocZ(sizeof(*pShared));
+ if (pShared)
+ {
+ pShared->cRefs = 1;
+ pShared->pData = pDataAttr;
+ rtFsNtfsCore_Retain(pCore);
+ pDataAttr->uObj.pSharedFile = pShared;
+ }
+ }
+ if (pShared)
+ {
+ /*
+ * Create the open file instance.
+ */
+ PRTFSNTFSFILE pNewFile;
+ rc = RTVfsNewFile(&g_rtFsNtfsFileOps, sizeof(*pNewFile), fOpen, pThis->hVfsSelf, NIL_RTVFSLOCK,
+ phVfsFile, (void **)&pNewFile);
+ if (RT_SUCCESS(rc))
+ {
+ pNewFile->offFile = 0;
+ pNewFile->pShared = pShared;
+ rtFsNtfsCore_Release(pCore);
+ return VINF_SUCCESS;
+ }
+
+ rtFsNtfsFileShrd_Release(pShared);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_NOT_A_FILE, "%s: fFlags=%#x", pszWhat, pCore->pMftRec->pFileRec->fFlags);
+ rtFsNtfsCore_Release(pCore);
+ }
+ return rc;
+}
+
+
+
+/*
+ *
+ * NTFS directory code.
+ * NTFS directory code.
+ * NTFS directory code.
+ *
+ */
+
+#ifdef LOG_ENABLED
+
+/**
+ * Logs an index header and all the entries.
+ *
+ * @param pIdxHdr The index header.
+ * @param cbIndex The number of valid bytes starting with the header.
+ * @param offIndex The offset of the index header into the parent
+ * structure.
+ * @param pszPrefix The log prefix.
+ * @param uIdxType The index type.
+ */
+static void rtFsNtfsVol_LogIndexHdrAndEntries(PCNTFSINDEXHDR pIdxHdr, uint32_t cbIndex, uint32_t offIndex,
+ const char *pszPrefix, uint32_t uIdxType)
+{
+ if (!LogIs2Enabled())
+ return;
+
+ /*
+ * Do the header.
+ */
+ if (cbIndex <= sizeof(*pIdxHdr))
+ {
+ Log2(("NTFS: %s: Error! Not enough space for the index header! cbIndex=%#x, index head needs %#x\n",
+ pszPrefix, cbIndex, sizeof(*pIdxHdr)));
+ return;
+ }
+
+ Log2(("NTFS: %s: offFirstEntry %#x%s\n", pszPrefix, RT_LE2H_U32(pIdxHdr->offFirstEntry),
+ RT_LE2H_U32(pIdxHdr->offFirstEntry) >= cbIndex ? " !out-of-bounds!" : ""));
+ Log2(("NTFS: %s: cbUsed %#x%s\n", pszPrefix, RT_LE2H_U32(pIdxHdr->cbUsed),
+ RT_LE2H_U32(pIdxHdr->cbUsed) > cbIndex ? " !out-of-bounds!" : ""));
+ Log2(("NTFS: %s: cbAllocated %#x%s\n", pszPrefix, RT_LE2H_U32(pIdxHdr->cbAllocated),
+ RT_LE2H_U32(pIdxHdr->cbAllocated) > cbIndex ? " !out-of-bounds!" : ""));
+ Log2(("NTFS: %s: fFlags %#x (%s%s)\n", pszPrefix, pIdxHdr->fFlags,
+ pIdxHdr->fFlags & NTFSINDEXHDR_F_INTERNAL ? "internal" : "leaf",
+ pIdxHdr->fFlags & ~NTFSINDEXHDR_F_INTERNAL ? " !!unknown-flags!!" : ""));
+ if (pIdxHdr->abReserved[0]) Log2(("NTFS: %s: abReserved[0] %#x\n", pszPrefix, pIdxHdr->abReserved[0]));
+ if (pIdxHdr->abReserved[1]) Log2(("NTFS: %s: abReserved[0] %#x\n", pszPrefix, pIdxHdr->abReserved[1]));
+ if (pIdxHdr->abReserved[2]) Log2(("NTFS: %s: abReserved[0] %#x\n", pszPrefix, pIdxHdr->abReserved[2]));
+
+ /*
+ * The entries.
+ */
+ bool fSeenEnd = false;
+ uint32_t iEntry = 0;
+ uint32_t offCurEntry = RT_LE2H_U32(pIdxHdr->offFirstEntry);
+ while (offCurEntry < cbIndex)
+ {
+ if (offCurEntry + sizeof(NTFSIDXENTRYHDR) > cbIndex)
+ {
+ Log2(("NTFS: Entry[%#04x]: Out of bounds: %#x LB %#x, max %#x\n",
+ iEntry, offCurEntry, sizeof(NTFSIDXENTRYHDR), cbIndex));
+ break;
+ }
+ PCNTFSIDXENTRYHDR pEntryHdr = (PCNTFSIDXENTRYHDR)((uint8_t const *)pIdxHdr + offCurEntry);
+ Log2(("NTFS: [%#04x]: @%#05x/@%#05x cbEntry=%#x cbKey=%#x fFlags=%#x (%s%s%s)\n",
+ iEntry, offCurEntry, offCurEntry + offIndex, RT_LE2H_U16(pEntryHdr->cbEntry), RT_LE2H_U16(pEntryHdr->cbKey),
+ RT_LE2H_U16(pEntryHdr->fFlags),
+ pEntryHdr->fFlags & NTFSIDXENTRYHDR_F_INTERNAL ? "internal" : "leaf",
+ pEntryHdr->fFlags & NTFSIDXENTRYHDR_F_END ? " end" : "",
+ pEntryHdr->fFlags & ~(NTFSIDXENTRYHDR_F_INTERNAL | NTFSIDXENTRYHDR_F_END) ? " !unknown!" : ""));
+ if (uIdxType == NTFSATINDEXROOT_TYPE_DIR)
+ Log2(("NTFS: FileMftRec %#RX64 sqn %#x\n",
+ NTFSMFTREF_GET_IDX(&pEntryHdr->u.FileMftRec), NTFSMFTREF_GET_SEQ(&pEntryHdr->u.FileMftRec) ));
+ else
+ Log2(("NTFS: offData=%#x cbData=%#x uReserved=%#x\n",
+ RT_LE2H_U16(pEntryHdr->u.View.offData), RT_LE2H_U16(pEntryHdr->u.View.cbData),
+ RT_LE2H_U32(pEntryHdr->u.View.uReserved) ));
+ if (pEntryHdr->fFlags & NTFSIDXENTRYHDR_F_INTERNAL)
+ Log2(("NTFS: Subnode=%#RX64\n", RT_LE2H_U64(NTFSIDXENTRYHDR_GET_SUBNODE(pEntryHdr)) ));
+
+ if ( RT_LE2H_U16(pEntryHdr->cbKey) >= RT_UOFFSETOF(NTFSATFILENAME, wszFilename)
+ && uIdxType == NTFSATINDEXROOT_TYPE_DIR)
+ {
+ PCNTFSATFILENAME pFilename = (PCNTFSATFILENAME)(pEntryHdr + 1);
+ RTTIMESPEC Spec;
+ char sz[80];
+ Log2(("NTFS: iCreationTime %#RX64 %s\n", RT_LE2H_U64(pFilename->iCreationTime),
+ RTTimeSpecToString(RTTimeSpecSetNtTime(&Spec, RT_LE2H_U64(pFilename->iCreationTime)), sz, sizeof(sz)) ));
+ Log2(("NTFS: iLastDataModTime %#RX64 %s\n", RT_LE2H_U64(pFilename->iLastDataModTime),
+ RTTimeSpecToString(RTTimeSpecSetNtTime(&Spec, RT_LE2H_U64(pFilename->iLastDataModTime)), sz, sizeof(sz)) ));
+ Log2(("NTFS: iLastMftModTime %#RX64 %s\n", RT_LE2H_U64(pFilename->iLastMftModTime),
+ RTTimeSpecToString(RTTimeSpecSetNtTime(&Spec, RT_LE2H_U64(pFilename->iLastMftModTime)), sz, sizeof(sz)) ));
+ Log2(("NTFS: iLastAccessTime %#RX64 %s\n", RT_LE2H_U64(pFilename->iLastAccessTime),
+ RTTimeSpecToString(RTTimeSpecSetNtTime(&Spec, RT_LE2H_U64(pFilename->iLastAccessTime)), sz, sizeof(sz)) ));
+ Log2(("NTFS: cbAllocated %#RX64 (%Rhcb)\n",
+ RT_LE2H_U64(pFilename->cbAllocated), RT_LE2H_U64(pFilename->cbAllocated)));
+ Log2(("NTFS: cbData %#RX64 (%Rhcb)\n",
+ RT_LE2H_U64(pFilename->cbData), RT_LE2H_U64(pFilename->cbData)));
+ Log2(("NTFS: fFileAttribs %#RX32\n", RT_LE2H_U32(pFilename->fFileAttribs) ));
+ if (RT_LE2H_U32(pFilename->fFileAttribs) & NTFS_FA_REPARSE_POINT)
+ Log2(("NTFS: uReparseTag %#RX32\n", RT_LE2H_U32(pFilename->u.uReparseTag) ));
+ else
+ Log2(("NTFS: cbPackedEas %#RX16\n", RT_LE2H_U16(pFilename->u.cbPackedEas) ));
+ Log2(("NTFS: cwcFilename %#x\n", pFilename->cwcFilename));
+ Log2(("NTFS: fFilenameType %#x\n", pFilename->fFilenameType));
+ if (RT_UOFFSETOF_DYN(NTFSATFILENAME, wszFilename[pFilename->cwcFilename]) <= RT_LE2H_U16(pEntryHdr->cbKey))
+ Log2(("NTFS: wszFilename '%.*ls'\n", pFilename->cwcFilename, pFilename->wszFilename ));
+ else
+ Log2(("NTFS: Error! Truncated filename!!\n"));
+ }
+
+
+ /* next */
+ iEntry++;
+ offCurEntry += RT_LE2H_U16(pEntryHdr->cbEntry);
+ fSeenEnd = RT_BOOL(pEntryHdr->fFlags & NTFSIDXENTRYHDR_F_END);
+ if (fSeenEnd || RT_LE2H_U16(pEntryHdr->cbEntry) < sizeof(*pEntryHdr))
+ break;
+ }
+ if (!fSeenEnd)
+ Log2(("NTFS: %s: Warning! Missing NTFSIDXENTRYHDR_F_END node!\n", pszPrefix));
+}
+
+# if 0 /* unused */
+static void rtFsNtfsVol_LogIndexNode(PCNTFSATINDEXALLOC pIdxNode, uint32_t cbIdxNode, uint32_t uType)
+{
+ if (!LogIs2Enabled())
+ return;
+ if (cbIdxNode < sizeof(*pIdxNode))
+ Log2(("NTFS: Index Node: Error! Too small! cbIdxNode=%#x, index node needs %#x\n", cbIdxNode, sizeof(*pIdxNode)));
+ else
+ {
+ Log2(("NTFS: Index Node: uMagic %#x\n", RT_LE2H_U32(pIdxNode->RecHdr.uMagic)));
+ Log2(("NTFS: Index Node: UpdateSeqArray %#x L %#x\n",
+ RT_LE2H_U16(pIdxNode->RecHdr.offUpdateSeqArray), RT_LE2H_U16(pIdxNode->RecHdr.cUpdateSeqEntries) ));
+ Log2(("NTFS: Index Node: uLsn %#RX64\n", RT_LE2H_U64(pIdxNode->uLsn) ));
+ Log2(("NTFS: Index Node: iSelfAddress %#RX64\n", RT_LE2H_U64(pIdxNode->iSelfAddress) ));
+ if (pIdxNode->RecHdr.uMagic == NTFSREC_MAGIC_INDEX_ALLOC)
+ rtFsNtfsVol_LogIndexHdrAndEntries(&pIdxNode->Hdr, cbIdxNode - RT_UOFFSETOF(NTFSATINDEXALLOC, Hdr),
+ RT_UOFFSETOF(NTFSATINDEXALLOC, Hdr), "Index Node Hdr", uType);
+ else
+ Log2(("NTFS: Index Node: !Error! Invalid magic!\n"));
+ }
+}
+# endif
+
+/**
+ * Logs a index root structure and what follows (index header + entries).
+ *
+ * @param pIdxRoot The index root.
+ * @param cbIdxRoot Number of valid bytes starting with @a pIdxRoot.
+ */
+static void rtFsNtfsVol_LogIndexRoot(PCNTFSATINDEXROOT pIdxRoot, uint32_t cbIdxRoot)
+{
+ if (!LogIs2Enabled())
+ return;
+ if (cbIdxRoot < sizeof(*pIdxRoot))
+ Log2(("NTFS: Index Root: Error! Too small! cbIndex=%#x, index head needs %#x\n", cbIdxRoot, sizeof(*pIdxRoot)));
+ else
+ {
+ Log2(("NTFS: Index Root: cbIdxRoot %#x\n", cbIdxRoot));
+ Log2(("NTFS: Index Root: uType %#x %s\n", RT_LE2H_U32(pIdxRoot->uType),
+ pIdxRoot->uType == NTFSATINDEXROOT_TYPE_VIEW ? "view"
+ : pIdxRoot->uType == NTFSATINDEXROOT_TYPE_DIR ? "directory" : "!unknown!"));
+ Log2(("NTFS: Index Root: uCollationRules %#x %s\n", RT_LE2H_U32(pIdxRoot->uCollationRules),
+ pIdxRoot->uCollationRules == NTFS_COLLATION_BINARY ? "binary"
+ : pIdxRoot->uCollationRules == NTFS_COLLATION_FILENAME ? "filename"
+ : pIdxRoot->uCollationRules == NTFS_COLLATION_UNICODE_STRING ? "unicode-string"
+ : pIdxRoot->uCollationRules == NTFS_COLLATION_UINT32 ? "uint32"
+ : pIdxRoot->uCollationRules == NTFS_COLLATION_SID ? "sid"
+ : pIdxRoot->uCollationRules == NTFS_COLLATION_UINT32_PAIR ? "uint32-pair"
+ : pIdxRoot->uCollationRules == NTFS_COLLATION_UINT32_SEQ ? "uint32-sequence" : "!unknown!"));
+ Log2(("NTFS: Index Root: cbIndexNode %#x\n", RT_LE2H_U32(pIdxRoot->cbIndexNode) ));
+ Log2(("NTFS: Index Root: cAddressesPerIndexNode %#x => cbNodeAddressingUnit=%#x\n",
+ pIdxRoot->cAddressesPerIndexNode, RT_LE2H_U32(pIdxRoot->cbIndexNode) / RT_MAX(1, pIdxRoot->cAddressesPerIndexNode) ));
+ if (pIdxRoot->abReserved[0]) Log2(("NTFS: Index Root: abReserved[0] %#x\n", pIdxRoot->abReserved[0]));
+ if (pIdxRoot->abReserved[1]) Log2(("NTFS: Index Root: abReserved[1] %#x\n", pIdxRoot->abReserved[1]));
+ if (pIdxRoot->abReserved[2]) Log2(("NTFS: Index Root: abReserved[2] %#x\n", pIdxRoot->abReserved[2]));
+
+ rtFsNtfsVol_LogIndexHdrAndEntries(&pIdxRoot->Hdr, cbIdxRoot - RT_UOFFSETOF(NTFSATINDEXROOT, Hdr),
+ RT_UOFFSETOF(NTFSATINDEXROOT, Hdr), "Index Root Hdr", pIdxRoot->uType);
+ }
+}
+
+#endif /* LOG_ENABLED */
+
+
+/**
+ * Validates an index header.
+ *
+ * @returns IPRT status code.
+ * @param pRootInfo Pointer to the index root info.
+ * @param pNodeInfo Pointer to the node info structure to load.
+ * @param pIndexHdr Pointer to the index header.
+ * @param cbIndex Size of the index.
+ * @param pErrInfo Where to return extra error info.
+ * @param pszWhat Error prefix.
+ */
+static int rtFsNtfsVol_LoadIndexNodeInfo(PCRTFSNTFSIDXROOTINFO pRootInfo, PRTFSNTFSIDXNODEINFO pNodeInfo, PCNTFSINDEXHDR pIndexHdr,
+ uint32_t cbIndex, PRTERRINFO pErrInfo, const char *pszWhat)
+{
+ uint32_t const cbMinIndex = sizeof(*pIndexHdr) + sizeof(NTFSIDXENTRYHDR);
+ if (cbIndex < cbMinIndex)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Not enough room for the index header and one entry header! cbIndex=%#x (cbMinIndex=%#x)",
+ pszWhat, cbIndex, cbMinIndex);
+ uint32_t const cbAllocated = RT_LE2H_U32(pIndexHdr->cbAllocated);
+ if ( cbAllocated > cbIndex
+ || cbAllocated < cbMinIndex
+ || (cbAllocated & 7) )
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Bogus index allocation size: %#x (min %#x, max %#x, 8 byte aligned)",
+ pszWhat, cbAllocated, cbMinIndex, cbIndex);
+ uint32_t const cbUsed = RT_LE2H_U32(pIndexHdr->cbUsed);
+ if ( cbUsed > cbAllocated
+ || cbUsed < cbMinIndex
+ || (cbUsed & 7) )
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Bogus index used size: %#x (min %#x, max %#x, 8 byte aligned)",
+ pszWhat, cbUsed, cbMinIndex, cbAllocated);
+ uint32_t const offFirstEntry = RT_LE2H_U32(pIndexHdr->offFirstEntry);
+ if ( offFirstEntry < sizeof(*pIndexHdr)
+ || ( offFirstEntry > cbUsed - sizeof(NTFSIDXENTRYHDR)
+ && offFirstEntry != cbUsed /* empty dir */)
+ || (offFirstEntry & 7) )
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Bogus first entry offset: %#x (min %#x, max %#x, 8 byte aligned)",
+ pszWhat, offFirstEntry, sizeof(*pIndexHdr), cbUsed - sizeof(NTFSIDXENTRYHDR));
+
+ /*
+ * The index entries.
+ */
+ uint32_t const uType = pRootInfo->pRoot->uType;
+ uint32_t offEntry = offFirstEntry;
+ uint32_t iEntry = 0;
+ for (;;)
+ {
+ if (offEntry + sizeof(NTFSIDXENTRYHDR) > cbUsed)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Entry #%u is out of bound: offset %#x (cbUsed=%#x)",
+ pszWhat, iEntry, offEntry, cbUsed);
+ PCNTFSIDXENTRYHDR pEntryHdr = (PCNTFSIDXENTRYHDR)((uint8_t const *)pIndexHdr + offEntry);
+ uint16_t const cbEntry = RT_LE2H_U16(pEntryHdr->cbEntry);
+ uint32_t const cbSubnodeAddr = (pEntryHdr->fFlags & NTFSIDXENTRYHDR_F_INTERNAL ? sizeof(int64_t) : 0);
+ uint32_t const cbMinEntry = sizeof(*pEntryHdr) + cbSubnodeAddr;
+ if ( cbEntry < cbMinEntry
+ || offEntry + cbEntry > cbUsed
+ || (cbEntry & 7) )
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Entry #%u has a bogus size: %#x (min %#x, max %#x, 8 byte aligned)",
+ pszWhat, iEntry, cbEntry, cbMinEntry, cbUsed - offEntry);
+
+ uint32_t const cbMaxKey = cbEntry - sizeof(*pEntryHdr) - cbSubnodeAddr;
+ uint32_t const cbMinKey = (pEntryHdr->fFlags & NTFSIDXENTRYHDR_F_END) ? 0
+ : uType == NTFSATINDEXROOT_TYPE_DIR ? RT_UOFFSETOF(NTFSATFILENAME, wszFilename) : 0;
+ uint16_t const cbKey = RT_LE2H_U16(pEntryHdr->cbKey);
+ if ( cbKey < cbMinKey
+ || cbKey > cbMaxKey)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Entry #%u has a bogus key size: %#x (min %#x, max %#x)",
+ pszWhat, iEntry, cbKey, cbMinKey, cbMaxKey);
+ if ( !(pEntryHdr->fFlags & NTFSIDXENTRYHDR_F_END)
+ && uType == NTFSATINDEXROOT_TYPE_DIR)
+ {
+ PCNTFSATFILENAME pFilename = (PCNTFSATFILENAME)(pEntryHdr + 1);
+ if (RT_UOFFSETOF_DYN(NTFSATFILENAME, wszFilename[pFilename->cwcFilename]) > cbKey)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Entry #%u filename is out of bounds: cwcFilename=%#x -> %#x key, max %#x",
+ pszWhat, iEntry, pFilename->cwcFilename,
+ RT_UOFFSETOF_DYN(NTFSATFILENAME, wszFilename[pFilename->cwcFilename]), cbKey);
+ }
+
+ if (pEntryHdr->fFlags & NTFSIDXENTRYHDR_F_INTERNAL)
+ {
+ int64_t iSubnode = NTFSIDXENTRYHDR_GET_SUBNODE(pEntryHdr);
+ if ( (uint64_t)iSubnode >= pRootInfo->uEndNodeAddresses
+ || (iSubnode & pRootInfo->fNodeAddressMisalign) )
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Entry #%u has bogus subnode address: %#RX64 (max %#RX64, misalign %#x)",
+ pszWhat, iEntry, iSubnode, pRootInfo->uEndNodeAddresses,
+ pRootInfo->fNodeAddressMisalign);
+ }
+
+ /* Advance. */
+ offEntry += cbEntry;
+ iEntry++;
+ if (pEntryHdr->fFlags & NTFSIDXENTRYHDR_F_END)
+ break;
+ }
+
+ /*
+ * Popuplate the node info structure.
+ */
+ pNodeInfo->pIndexHdr = pIndexHdr;
+ pNodeInfo->fInternal = RT_BOOL(pIndexHdr->fFlags & NTFSINDEXHDR_F_INTERNAL);
+ if (pNodeInfo != &pRootInfo->NodeInfo)
+ pNodeInfo->pVol = pRootInfo->NodeInfo.pVol;
+ pNodeInfo->cEntries = iEntry;
+ pNodeInfo->papEntries = (PCNTFSIDXENTRYHDR *)RTMemAlloc(iEntry * sizeof(pNodeInfo->papEntries[0]));
+ if (pNodeInfo->papEntries)
+ {
+ PCNTFSIDXENTRYHDR pEntryHdr = NTFSINDEXHDR_GET_FIRST_ENTRY(pIndexHdr);
+ for (iEntry = 0; iEntry < pNodeInfo->cEntries; iEntry++)
+ {
+ pNodeInfo->papEntries[iEntry] = pEntryHdr;
+ pEntryHdr = NTFSIDXENTRYHDR_GET_NEXT(pEntryHdr);
+ }
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * Creates a shared directory structure given a MFT core.
+ *
+ * @returns IPRT status code.
+ * @param pThis The NTFS volume instance.
+ * @param pCore The MFT core structure that's allegedly a directory.
+ * (No reference consumed of course.)
+ * @param ppSharedDir Where to return the pointer to the new shared directory
+ * structure on success. (Referenced.)
+ * @param pErrInfo Where to return additions error info. Optional.
+ * @param pszWhat Context prefix for error reporting and logging.
+ */
+static int rtFsNtfsVol_NewSharedDirFromCore(PRTFSNTFSVOL pThis, PRTFSNTFSCORE pCore, PRTFSNTFSDIRSHRD *ppSharedDir,
+ PRTERRINFO pErrInfo, const char *pszWhat)
+{
+ *ppSharedDir = NULL;
+
+ /*
+ * Look for the index root and validate it.
+ */
+ PRTFSNTFSATTR pRootAttr = rtFsNtfsCore_FindNamedAttributeAscii(pCore, NTFS_AT_INDEX_ROOT,
+ RT_STR_TUPLE(NTFS_DIR_ATTRIBUTE_NAME));
+ if (!pRootAttr)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "%s: Found no INDEX_ROOT attribute named $I30", pszWhat);
+ if (pRootAttr->pAttrHdr->fNonResident)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "%s: INDEX_ROOT is is not resident", pszWhat);
+ if (pRootAttr->cbResident < sizeof(NTFSATINDEXROOT))
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "%s: INDEX_ROOT is too small: %#x, min %#x ",
+ pszWhat, pRootAttr->cbResident, sizeof(pRootAttr->cbResident));
+
+ PCNTFSATINDEXROOT pIdxRoot = (PCNTFSATINDEXROOT)NTFSATTRIBHDR_GET_RES_VALUE_PTR(pRootAttr->pAttrHdr);
+#ifdef LOG_ENABLED
+ rtFsNtfsVol_LogIndexRoot(pIdxRoot, pRootAttr->cbResident);
+#endif
+ if (pIdxRoot->uType != NTFSATINDEXROOT_TYPE_DIR)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Wrong INDEX_ROOT type for a directory: %#x, expected %#x",
+ pszWhat, RT_LE2H_U32(pIdxRoot->uType), RT_LE2H_U32_C(NTFSATINDEXROOT_TYPE_DIR));
+ if (pIdxRoot->uCollationRules != NTFS_COLLATION_FILENAME)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Wrong collation rules for a directory: %#x, expected %#x",
+ pszWhat, RT_LE2H_U32(pIdxRoot->uCollationRules), RT_LE2H_U32_C(NTFS_COLLATION_FILENAME));
+ uint32_t cbIndexNode = RT_LE2H_U32(pIdxRoot->cbIndexNode);
+ if (cbIndexNode < 512 || cbIndexNode > _64K || !RT_IS_POWER_OF_TWO(cbIndexNode))
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Bogus index node size: %#x (expected power of two between 512 and 64KB)",
+ pszWhat, cbIndexNode);
+ unsigned const cNodeAddressShift = cbIndexNode >= pThis->cbCluster ? pThis->cClusterShift : 9;
+ if (((uint32_t)pIdxRoot->cAddressesPerIndexNode << cNodeAddressShift) != cbIndexNode)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Bogus addresses per index node value: %#x (cbIndexNode=%#x cNodeAddressShift=%#x)",
+ pszWhat, pIdxRoot->cAddressesPerIndexNode, cbIndexNode, cNodeAddressShift);
+ AssertReturn(pRootAttr->uObj.pSharedDir == NULL, VERR_INTERNAL_ERROR_3);
+
+ /*
+ * Check for the node data stream and related allocation bitmap.
+ */
+ PRTFSNTFSATTR pIndexAlloc = rtFsNtfsCore_FindNamedAttributeAscii(pCore, NTFS_AT_INDEX_ALLOCATION,
+ RT_STR_TUPLE(NTFS_DIR_ATTRIBUTE_NAME));
+ PRTFSNTFSATTR pIndexBitmap = rtFsNtfsCore_FindNamedAttributeAscii(pCore, NTFS_AT_BITMAP,
+ RT_STR_TUPLE(NTFS_DIR_ATTRIBUTE_NAME));
+ if (pIndexAlloc && !pIndexBitmap)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: INDEX_ALLOCATION attribute without BITMAP", pszWhat);
+ if (!pIndexAlloc && pIndexBitmap)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: BITMAP attribute without INDEX_ALLOCATION", pszWhat);
+ uint64_t uNodeAddressEnd = 0;
+ if (pIndexAlloc)
+ {
+ if (!pIndexAlloc->pAttrHdr->fNonResident)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "%s: INDEX_ALLOCATION is resident", pszWhat);
+ if (pIndexAlloc->cbValue & (cbIndexNode - 1))
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: INDEX_ALLOCATION size isn't aligned on node boundrary: %#RX64, cbIndexNode=%#x",
+ pszWhat, pIndexAlloc->cbValue, cbIndexNode);
+ uint64_t const cNodes = pIndexAlloc->cbValue / cbIndexNode;
+ if (pIndexBitmap->cbValue < (RT_ALIGN_64(cNodes, 64) >> 3))
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: BITMAP size does not match INDEX_ALLOCATION: %#RX64, expected min %#RX64 (cbIndexNode=%#x, cNodes=%#RX64)",
+ pszWhat, pIndexBitmap->cbValue, RT_ALIGN_64(cNodes, 64) >> 3, cbIndexNode, cNodes);
+ uNodeAddressEnd = cNodes * pIdxRoot->cAddressesPerIndexNode;
+ }
+
+ /*
+ * Create a directory instance.
+ */
+ PRTFSNTFSDIRSHRD pNewDir = (PRTFSNTFSDIRSHRD)RTMemAllocZ(sizeof(*pNewDir));
+ if (!pNewDir)
+ return VERR_NO_MEMORY;
+
+ pNewDir->cRefs = 1;
+ rtFsNtfsCore_Retain(pCore);
+ pNewDir->RootInfo.pRootAttr = pRootAttr;
+ pNewDir->RootInfo.pRoot = pIdxRoot;
+ pNewDir->RootInfo.pAlloc = pIndexAlloc;
+ pNewDir->RootInfo.uEndNodeAddresses = uNodeAddressEnd;
+ pNewDir->RootInfo.cNodeAddressByteShift = cNodeAddressShift;
+ pNewDir->RootInfo.fNodeAddressMisalign = pIdxRoot->cAddressesPerIndexNode - 1;
+ pNewDir->RootInfo.NodeInfo.pVol = pThis;
+
+ /*
+ * Finally validate the index header and entries.
+ */
+ int rc = rtFsNtfsVol_LoadIndexNodeInfo(&pNewDir->RootInfo, &pNewDir->RootInfo.NodeInfo, &pIdxRoot->Hdr,
+ pRootAttr->cbResident - RT_UOFFSETOF(NTFSATINDEXROOT, Hdr), pErrInfo, pszWhat);
+ if (RT_SUCCESS(rc))
+ {
+ *ppSharedDir = pNewDir;
+ pRootAttr->uObj.pSharedDir = pNewDir;
+ return VINF_SUCCESS;
+ }
+ RTMemFree(pNewDir);
+ rtFsNtfsCore_Release(pCore);
+ return rc;
+}
+
+
+/**
+ * Gets a shared directory structure given an MFT record reference, creating a
+ * new one if necessary.
+ *
+ * @returns IPRT status code.
+ * @param pThis The NTFS volume instance.
+ * @param pDirMftRef The MFT record reference to follow.
+ * @param ppSharedDir Where to return the shared directory structure
+ * (referenced).
+ * @param pErrInfo Where to return error details. Optional.
+ * @param pszWhat Error/log prefix.
+ */
+static int rtFsNtfsVol_QueryOrCreateSharedDirByMftRef(PRTFSNTFSVOL pThis, PCNTFSMFTREF pDirMftRef,
+ PRTFSNTFSDIRSHRD *ppSharedDir, PRTERRINFO pErrInfo, const char *pszWhat)
+{
+ /*
+ * Get the core structure for the MFT record and check that it's a directory we've got.
+ */
+ PRTFSNTFSCORE pCore;
+ int rc = rtFsNtfsVol_QueryCoreForMftRef(pThis, pDirMftRef, false /*fRelaxedUsa*/, &pCore, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (pCore->pMftRec->pFileRec->fFlags & NTFSRECFILE_F_DIRECTORY)
+ {
+ /*
+ * Locate the $I30 root index attribute as we associate the
+ * pointer to the shared directory pointer with it.
+ */
+ PRTFSNTFSATTR pRootAttr = rtFsNtfsCore_FindNamedAttributeAscii(pCore, NTFS_AT_INDEX_ROOT,
+ RT_STR_TUPLE(NTFS_DIR_ATTRIBUTE_NAME));
+ if (pRootAttr)
+ {
+ if (!pRootAttr->uObj.pSharedDir)
+ rc = rtFsNtfsVol_NewSharedDirFromCore(pThis, pCore, ppSharedDir, pErrInfo, pszWhat);
+ else
+ {
+ Assert(pRootAttr->uObj.pSharedDir->RootInfo.pRootAttr->pCore == pCore);
+ rtFsNtfsDirShrd_Retain(pRootAttr->uObj.pSharedDir);
+ *ppSharedDir = pRootAttr->uObj.pSharedDir;
+ }
+ }
+ else
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_NOT_A_DIRECTORY,
+ "%s: Found INDEX_ROOT attribute named $I30, even though NTFSRECFILE_F_DIRECTORY is set",
+ pszWhat);
+ }
+ else
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_NOT_A_DIRECTORY, "%s: fFlags=%#x", pszWhat, pCore->pMftRec->pFileRec->fFlags);
+ rtFsNtfsCore_Release(pCore);
+ }
+ return rc;
+}
+
+
+/**
+ * Frees resource kept by an index node info structure.
+ *
+ * @param pNodeInfo The index node info structure to delelte.
+ */
+static void rtFsNtfsIdxNodeInfo_Delete(PRTFSNTFSIDXNODEINFO pNodeInfo)
+{
+ RTMemFree(pNodeInfo->papEntries);
+ pNodeInfo->papEntries = NULL;
+ pNodeInfo->pNode = NULL;
+ pNodeInfo->pVol = NULL;
+}
+
+
+/**
+ * Gets or loads the specified subnode.
+ *
+ * @returns IPRT status code.
+ * @param pRootInfo The index root info.
+ * @param iNode The address of the node being queried.
+ * @param ppNode Where to return the referenced pointer to the node.
+ */
+static int rtFsNtfsIdxRootInfo_QueryNode(PRTFSNTFSIDXROOTINFO pRootInfo, int64_t iNode, PRTFSNTFSIDXNODE *ppNode)
+{
+ PRTFSNTFSVOL pVol = pRootInfo->NodeInfo.pVol;
+
+ /*
+ * A bit of paranoia. These has been checked already when loading, but it
+ * usually doesn't hurt too much to be careful.
+ */
+ AssertReturn(!(iNode & pRootInfo->fNodeAddressMisalign), VERR_VFS_BOGUS_OFFSET);
+ AssertReturn((uint64_t)iNode < pRootInfo->uEndNodeAddresses, VERR_VFS_BOGUS_OFFSET);
+ AssertReturn(pRootInfo->pAlloc, VERR_VFS_BOGUS_OFFSET);
+
+ /*
+ * First translate the node address to a disk byte offset and check the index node cache.
+ */
+ uint64_t offNode = iNode << pRootInfo->cNodeAddressByteShift;
+ uint64_t offNodeOnDisk = rtFsNtfsAttr_OffsetToDisk(pRootInfo->pAlloc, offNode, NULL);
+ PRTFSNTFSIDXNODE pNode = (PRTFSNTFSIDXNODE)RTAvlU64Get(&pVol->IdxNodeCacheRoot, offNodeOnDisk);
+ if (pNode)
+ {
+ rtFsNtfsIdxNode_Retain(pNode);
+ *ppNode = pNode;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Need to create a load a new node.
+ */
+ pNode = (PRTFSNTFSIDXNODE)RTMemAllocZ(sizeof(*pNode));
+ AssertReturn(pNode, VERR_NO_MEMORY);
+
+ pNode->TreeNode.Key = offNodeOnDisk;
+ uint32_t cbIndexNode = RT_LE2H_U32(pRootInfo->pRoot->cbIndexNode);
+ pNode->cbCost = sizeof(*pNode) + cbIndexNode;
+ pNode->cRefs = 1;
+ pNode->pNode = (PNTFSATINDEXALLOC)RTMemAllocZ(cbIndexNode);
+ int rc;
+ if (pNode->pNode)
+ {
+ rc = rtFsNtfsAttr_Read(pRootInfo->pAlloc, offNode, pNode->pNode, cbIndexNode);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VERR_VFS_BOGUS_FORMAT;
+ if (pNode->pNode->RecHdr.uMagic != NTFSREC_MAGIC_INDEX_ALLOC)
+ LogRel(("rtFsNtfsIdxRootInfo_QueryNode(iNode=%#x): Invalid node magic %#x -> VERR_VFS_BOGUS_FORMAT\n",
+ iNode, RT_LE2H_U32(pNode->pNode->RecHdr.uMagic) ));
+ else if ((int64_t)RT_LE2H_U64(pNode->pNode->iSelfAddress) != iNode)
+ LogRel(("rtFsNtfsIdxRootInfo_QueryNode(iNode=%#x): Wrong iSelfAddress: %#x -> VERR_VFS_BOGUS_FORMAT\n",
+ iNode, RT_LE2H_U64(pNode->pNode->iSelfAddress) ));
+ else
+ {
+ rc = rtFsNtfsRec_DoMultiSectorFixups(&pNode->pNode->RecHdr, cbIndexNode, false /*fRelaxedUsa*/, NULL /*pErrInfo*/);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Validate/parse it
+ */
+#ifdef LOG_ENABLED
+ rtFsNtfsVol_LogIndexHdrAndEntries(&pNode->pNode->Hdr,
+ cbIndexNode - RT_UOFFSETOF(NTFSATINDEXALLOC, Hdr),
+ RT_UOFFSETOF(NTFSATINDEXALLOC, Hdr), "index node",
+ pRootInfo->pRoot->uType);
+#endif
+ rc = rtFsNtfsVol_LoadIndexNodeInfo(pRootInfo, &pNode->NodeInfo, &pNode->pNode->Hdr,
+ cbIndexNode - RT_UOFFSETOF(NTFSATINDEXALLOC, Hdr),
+ NULL /*pErrInfo*/, "index node");
+ if (RT_SUCCESS(rc))
+ {
+ pNode->cbCost += pNode->NodeInfo.cEntries * sizeof(pNode->NodeInfo.papEntries[0]);
+
+ /*
+ * Insert it into the cache, trimming the cache if necessary.
+ */
+ bool fInsertOkay = RTAvlU64Insert(&pVol->IdxNodeCacheRoot, &pNode->TreeNode);
+ Assert(fInsertOkay);
+ if (fInsertOkay)
+ {
+ pVol->cIdxNodes += 1;
+ pVol->cbIdxNodes += pNode->cbCost;
+ if (pVol->cbIdxNodes > RTFSNTFS_MAX_CORE_CACHE_SIZE)
+ rtFsNtfsIdxVol_TrimIndexNodeCache(pVol);
+
+ *ppNode = pNode;
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ }
+ }
+
+ RTMemFree(pNode->pNode);
+ pNode->pNode = NULL;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ RTMemFree(pNode);
+ return rc;
+}
+
+
+/**
+ * Frees resource kept by an index root info structure.
+ *
+ * @param pRootInfo The index root info structure to delete.
+ */
+static void rtFsNtfsIdxRootInfo_Delete(PRTFSNTFSIDXROOTINFO pRootInfo)
+{
+ rtFsNtfsIdxNodeInfo_Delete(&pRootInfo->NodeInfo);
+ pRootInfo->pRootAttr->uObj.pSharedDir = NULL;
+ rtFsNtfsCore_Release(pRootInfo->pRootAttr->pCore);
+ pRootInfo->pRootAttr = NULL;
+ pRootInfo->pAlloc = NULL;
+ pRootInfo->pRoot = NULL;
+}
+
+
+/**
+ * Destroys a shared directory structure when the reference count reached zero.
+ *
+ * @returns zero
+ * @param pThis The shared directory structure to destroy.
+ */
+static uint32_t rtFsNtfsDirShrd_Destroy(PRTFSNTFSDIRSHRD pThis)
+{
+ rtFsNtfsIdxRootInfo_Delete(&pThis->RootInfo);
+ RTMemFree(pThis);
+ return 0;
+}
+
+
+/**
+ * Releases a references to a shared directory structure.
+ *
+ * @returns New reference count.
+ * @param pThis The shared directory structure.
+ */
+static uint32_t rtFsNtfsDirShrd_Release(PRTFSNTFSDIRSHRD pThis)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ Assert(cRefs < 4096);
+ if (cRefs > 0)
+ return cRefs;
+ return rtFsNtfsDirShrd_Destroy(pThis);
+}
+
+
+/**
+ * Retains a references to a shared directory structure.
+ *
+ * @returns New reference count.
+ * @param pThis The shared directory structure.
+ */
+static uint32_t rtFsNtfsDirShrd_Retain(PRTFSNTFSDIRSHRD pThis)
+{
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ Assert(cRefs > 1);
+ Assert(cRefs < 4096);
+ return cRefs;
+}
+
+
+/**
+ * Compares the two filenames in an case insentivie manner.
+ *
+ * @retval -1 if the first filename comes first
+ * @retval 0 if equal
+ * @retval 1 if the second filename comes first.
+ *
+ * @param pwszUpper1 The first filename, this has been uppercase already.
+ * @param cwcUpper1 The length of the first filename.
+ * @param pawcFilename2 The second filename to compare it with. Not zero
+ * terminated.
+ * @param cwcFilename2 The length of the second filename.
+ * @param pawcUpcase The uppercase table. 64K entries.
+ */
+static int rtFsNtfsIdxComp_Filename(PCRTUTF16 pwszUpper1, uint8_t cwcUpper1, PCRTUTF16 pawcFilename2, uint8_t cwcFilename2,
+ PCRTUTF16 const pawcUpcase)
+{
+ while (cwcUpper1 > 0 && cwcFilename2 > 0)
+ {
+ RTUTF16 uc1 = *pwszUpper1++;
+ RTUTF16 uc2 = *pawcFilename2++;
+ if (uc1 != uc2)
+ {
+ uc2 = pawcUpcase[uc2];
+ if (uc1 != uc2)
+ return uc1 < uc2 ? -1 : 1;
+ }
+
+ /* Decrement the lengths and loop. */
+ cwcUpper1--;
+ cwcFilename2--;
+ }
+
+ if (!cwcUpper1)
+ {
+ if (!cwcFilename2)
+ return 0;
+ return -1;
+ }
+ return 1;
+}
+
+
+/**
+ * Look up a name in the directory.
+ *
+ * @returns IPRT status code.
+ * @param pShared The shared directory structure.
+ * @param pszEntry The name to lookup.
+ * @param ppFilename Where to return the pointer to the filename structure.
+ * @param ppEntryHdr Where to return the poitner to the entry header
+ * structure.
+ * @param ppNode Where to return the pointer to the node the filename
+ * structure resides in. This must be released. It will
+ * be set to NULL if the name was found in the root node.
+ */
+static int rtFsNtfsDirShrd_Lookup(PRTFSNTFSDIRSHRD pShared, const char *pszEntry,
+ PCNTFSATFILENAME *ppFilename, PCNTFSIDXENTRYHDR *ppEntryHdr, PRTFSNTFSIDXNODE *ppNode)
+{
+ PRTFSNTFSVOL pVol = pShared->RootInfo.NodeInfo.pVol;
+
+ *ppFilename = NULL;
+ *ppEntryHdr = NULL;
+ *ppNode = NULL;
+ /** @todo do streams (split on ':') */
+
+ /*
+ * Convert the filename to UTF16 and uppercase.
+ */
+ PCRTUTF16 const pawcUpcase = pVol->pawcUpcase;
+ RTUTF16 wszFilename[256+4];
+ PRTUTF16 pwszDst = wszFilename;
+ PRTUTF16 pwszEnd = &wszFilename[255];
+ const char *pszSrc = pszEntry;
+ for (;;)
+ {
+ RTUNICP uc;
+ int rc = RTStrGetCpEx(&pszSrc, &uc);
+ if (RT_SUCCESS(rc))
+ {
+ if (uc != 0)
+ {
+ if (uc < _64K)
+ uc = pawcUpcase[uc];
+ pwszDst = RTUtf16PutCp(pwszDst, uc);
+ if ((uintptr_t)pwszDst <= (uintptr_t)pwszEnd)
+ { /* likely */ }
+ else
+ {
+ Log(("rtFsNtfsDirShrd_Lookup: Filename too long '%s'\n", pszEntry));
+ return VERR_FILENAME_TOO_LONG;
+ }
+ }
+ else
+ {
+ *pwszDst = '\0';
+ break;
+ }
+ }
+ else
+ {
+ Log(("rtFsNtfsDirShrd_Lookup: Invalid UTF-8 encoding (%Rrc): %.*Rhxs\n", rc, strlen(pszEntry), pszEntry));
+ return rc;
+ }
+ }
+ uint8_t const cwcFilename = (uint8_t)(pwszDst - wszFilename);
+
+ /*
+ * Do the tree traversal.
+ */
+ PRTFSNTFSIDXROOTINFO pRootInfo = &pShared->RootInfo;
+ PRTFSNTFSIDXNODEINFO pNodeInfo = &pRootInfo->NodeInfo;
+ PRTFSNTFSIDXNODE pNode = NULL;
+ for (;;)
+ {
+ /*
+ * Search it.
+ */
+ PCNTFSIDXENTRYHDR *papEntries = pNodeInfo->papEntries;
+ uint32_t iEnd = pNodeInfo->cEntries;
+ AssertReturn(iEnd > 0, VERR_INTERNAL_ERROR_3);
+
+ /* Exclude the end node from the serach as it doesn't have any key. */
+ if (papEntries[iEnd - 1]->fFlags & NTFSIDXENTRYHDR_F_END)
+ iEnd--;
+
+ uint32_t iEntry;
+ if (1 /*iEnd < 8*/ )
+ {
+ if (iEnd > 0)
+ {
+ for (iEntry = 0; iEntry < iEnd; iEntry++)
+ {
+ PCNTFSATFILENAME pFilename = (PCNTFSATFILENAME)(papEntries[iEntry] + 1);
+ int iDiff = rtFsNtfsIdxComp_Filename(wszFilename, cwcFilename, pFilename->wszFilename,
+ pFilename->cwcFilename, pawcUpcase);
+ if (iDiff > 0)
+ { /* likely */ }
+ else if (iDiff == 0)
+ {
+ *ppNode = pNode;
+ *ppEntryHdr = papEntries[iEntry];
+ *ppFilename = pFilename;
+ LogFlow(("rtFsNtfsDirShrd_Lookup(%s): Found it! (iEntry=%u, FileMftRec=%#RX64 sqn %#x)\n",
+ pszEntry, iEntry, NTFSMFTREF_GET_IDX(&papEntries[iEntry]->u.FileMftRec),
+ NTFSMFTREF_GET_SEQ(&papEntries[iEntry]->u.FileMftRec) ));
+ return VINF_SUCCESS;
+ }
+ else
+ break;
+ }
+ }
+ else
+ iEntry = iEnd;
+ }
+ /* else: implement binary search */
+
+ /*
+ * Decend thru node iEntry.
+ *
+ * We could be bold and ASSUME that there is always an END node, but we're
+ * playing safe for now.
+ */
+ if (iEnd < pNodeInfo->cEntries)
+ {
+ PCNTFSIDXENTRYHDR pEntry = papEntries[iEntry];
+ if (pEntry->fFlags & NTFSIDXENTRYHDR_F_INTERNAL)
+ {
+ int64_t iSubnode = NTFSIDXENTRYHDR_GET_SUBNODE(pEntry);
+ rtFsNtfsIdxNode_Release(pNode);
+ int rc = rtFsNtfsIdxRootInfo_QueryNode(pRootInfo, iSubnode, &pNode);
+ if (RT_SUCCESS(rc))
+ {
+ pNodeInfo = &pNode->NodeInfo;
+ continue;
+ }
+ LogFlow(("rtFsNtfsDirShrd_Lookup(%s): rtFsNtfsIdxRootInfo_QueryNode(%#RX64) error %Rrc!\n",
+ pszEntry, iSubnode, rc));
+ return rc;
+ }
+ }
+ rtFsNtfsIdxNode_Release(pNode);
+ LogFlow(("rtFsNtfsDirShrd_Lookup(%s): Not found! (#2)\n", pszEntry));
+ return VERR_FILE_NOT_FOUND;
+ }
+
+ /* not reached */
+}
+
+
+/**
+ * Gets the shared directory structure for the parent.
+ *
+ * @returns IPRT status code.
+ * @param pThis The directory which parent we want.
+ * @param ppDotDot Where to return the referenced shared parent dir
+ * structure.
+ *
+ */
+static int rtFsNtfsDirShrd_QueryParent(PRTFSNTFSDIRSHRD pThis, PRTFSNTFSDIRSHRD *ppDotDot)
+{
+ /*
+ * The root directory has no parent from our perspective.
+ */
+ if (pThis == pThis->RootInfo.NodeInfo.pVol->pRootDir)
+ {
+ rtFsNtfsDirShrd_Retain(pThis);
+ *ppDotDot = pThis;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Look for a filename record so we know where we go from here.
+ */
+ PRTFSNTFSCORE pCore = pThis->RootInfo.pRootAttr->pCore;
+ PRTFSNTFSATTR pCurAttr;
+ RTListForEach(&pCore->AttribHead, pCurAttr, RTFSNTFSATTR, ListEntry)
+ {
+ if ( pCurAttr->pAttrHdr->uAttrType == NTFS_AT_FILENAME
+ && pCurAttr->cbResident >= RT_UOFFSETOF(NTFSATFILENAME, wszFilename))
+ {
+ PCNTFSATFILENAME pFilename = (PCNTFSATFILENAME)NTFSATTRIBHDR_GET_RES_VALUE_PTR(pCurAttr->pAttrHdr);
+ int rc = rtFsNtfsVol_QueryOrCreateSharedDirByMftRef(pThis->RootInfo.NodeInfo.pVol, &pFilename->ParentDirMftRec,
+ ppDotDot, NULL /*pErrInfo*/, "..");
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ LogRel(("rtFsNtfsDirShrd_QueryParent: rtFsNtfsVol_QueryOrCreateSharedDirByMftRef failed: %Rrc\n", rc));
+ return rc;
+ }
+ }
+
+ LogRel(("rtFsNtfsDirShrd_QueryParent: Couldn't find '..' filename for MFT record %RX64!\n",
+ pThis->RootInfo.pRootAttr->pCore->pMftRec->TreeNode.Key));
+ return VERR_VFS_BOGUS_FORMAT;
+}
+
+
+
+/**
+ * Destroys an index node.
+ *
+ * This will remove it from the cache tree, however the caller must make sure
+ * its not in the reuse list any more.
+ *
+ * @param pNode The node to destroy.
+ */
+static void rtFsNtfsIdxNode_Destroy(PRTFSNTFSIDXNODE pNode)
+{
+ PRTFSNTFSVOL pVol = pNode->NodeInfo.pVol;
+
+ /* Remove it from the volume node cache. */
+ PAVLU64NODECORE pAssertRemove = RTAvlU64Remove(&pVol->IdxNodeCacheRoot, pNode->TreeNode.Key);
+ Assert(pAssertRemove == &pNode->TreeNode); NOREF(pAssertRemove);
+ pVol->cIdxNodes--;
+ pVol->cbIdxNodes -= pNode->cbCost;
+
+ /* Destroy it. */
+ rtFsNtfsIdxNodeInfo_Delete(&pNode->NodeInfo);
+ RTMemFree(pNode->pNode);
+ pNode->pNode = NULL;
+ RTMemFree(pNode);
+}
+
+
+/**
+ * Trims the index node cache.
+ *
+ * @param pThis The NTFS volume instance which index node cache
+ * needs trimming.
+ */
+static void rtFsNtfsIdxVol_TrimIndexNodeCache(PRTFSNTFSVOL pThis)
+{
+ while ( pThis->cbIdxNodes > RTFSNTFS_MAX_NODE_CACHE_SIZE
+ && pThis->cUnusedIdxNodes)
+ {
+ PRTFSNTFSIDXNODE pNode = RTListRemoveFirst(&pThis->IdxNodeUnusedHead, RTFSNTFSIDXNODE, UnusedListEntry);
+ pThis->cUnusedIdxNodes--;
+ rtFsNtfsIdxNode_Destroy(pNode);
+ }
+}
+
+
+/**
+ * Index node reference reached zero, put it in the unused list and trim the
+ * cache.
+ *
+ * @returns zero
+ * @param pNode The index node.
+ */
+static uint32_t rtFsNtfsIdxNode_MaybeDestroy(PRTFSNTFSIDXNODE pNode)
+{
+ PRTFSNTFSVOL pVol = pNode->NodeInfo.pVol;
+ if (pVol)
+ {
+ RTListAppend(&pVol->IdxNodeUnusedHead, &pNode->UnusedListEntry);
+ pVol->cUnusedIdxNodes++;
+ if (pVol->cbIdxNodes > RTFSNTFS_MAX_NODE_CACHE_SIZE)
+ rtFsNtfsIdxVol_TrimIndexNodeCache(pVol);
+ return 0;
+ }
+ /* not sure if this is needed yet... */
+ rtFsNtfsIdxNodeInfo_Delete(&pNode->NodeInfo);
+ RTMemFree(pNode);
+ return 0;
+}
+
+
+/**
+ * Releases a reference to an index node.
+ *
+ * @returns New reference count.
+ * @param pNode The index node to release. NULL is ignored.
+ */
+static uint32_t rtFsNtfsIdxNode_Release(PRTFSNTFSIDXNODE pNode)
+{
+ if (pNode)
+ {
+ uint32_t cRefs = ASMAtomicDecU32(&pNode->cRefs);
+ Assert(cRefs < 128);
+ if (cRefs > 0)
+ return cRefs;
+ return rtFsNtfsIdxNode_MaybeDestroy(pNode);
+ }
+ return 0;
+}
+
+
+/**
+ * Retains a reference to an index node.
+ *
+ * This will remove it from the unused list if necessary.
+ *
+ * @returns New reference count.
+ * @param pNode The index to reference.
+ */
+static uint32_t rtFsNtfsIdxNode_Retain(PRTFSNTFSIDXNODE pNode)
+{
+ uint32_t cRefs = ASMAtomicIncU32(&pNode->cRefs);
+ if (cRefs == 1)
+ {
+ RTListNodeRemove(&pNode->UnusedListEntry);
+ pNode->NodeInfo.pVol->cUnusedIdxNodes--;
+ }
+ return cRefs;
+}
+
+
+
+
+/*
+ *
+ * Directory instance methods
+ * Directory instance methods
+ * Directory instance methods
+ *
+ */
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_Close(void *pvThis)
+{
+ PRTFSNTFSDIR pThis = (PRTFSNTFSDIR)pvThis;
+ LogFlow(("rtFsNtfsDir_Close(%p/%p)\n", pThis, pThis->pShared));
+
+ PRTFSNTFSDIRSHRD pShared = pThis->pShared;
+ pThis->pShared = NULL;
+ if (pShared)
+ rtFsNtfsDirShrd_Release(pShared);
+
+ while (pThis->cEnumStackEntries > 0)
+ {
+ PRTFSNTFSIDXSTACKENTRY pEntry = &pThis->paEnumStack[--pThis->cEnumStackEntries];
+ rtFsNtfsIdxNode_Release(pEntry->pNodeInfo->pNode);
+ pEntry->pNodeInfo = NULL;
+ }
+ RTMemFree(pThis->paEnumStack);
+ pThis->paEnumStack = NULL;
+ pThis->cEnumStackMaxDepth = 0;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSNTFSDIR pThis = (PRTFSNTFSDIR)pvThis;
+ Log(("rtFsNtfsDir_QueryInfo\n"));
+ return rtFsNtfsCore_QueryInfo(pThis->pShared->RootInfo.pRootAttr->pCore,
+ pThis->pShared->RootInfo.pAlloc ? pThis->pShared->RootInfo.pAlloc
+ : pThis->pShared->RootInfo.pRootAttr,
+ pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ Log(("rtFsNtfsDir_SetMode\n"));
+ RT_NOREF(pvThis, fMode, fMask);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ Log(("rtFsNtfsDir_SetTimes\n"));
+ RT_NOREF(pvThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ Log(("rtFsNtfsDir_SetOwner\n"));
+ RT_NOREF(pvThis, uid, gid);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpen}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_Open(void *pvThis, const char *pszEntry, uint64_t fOpen,
+ uint32_t fFlags, PRTVFSOBJ phVfsObj)
+{
+ LogFlow(("rtFsNtfsDir_Open: pszEntry='%s' fOpen=%#RX64 fFlags=%#x\n", pszEntry, fOpen, fFlags));
+ PRTFSNTFSDIR pThis = (PRTFSNTFSDIR)pvThis;
+ PRTFSNTFSDIRSHRD pShared = pThis->pShared;
+ PRTFSNTFSVOL pVol = pShared->RootInfo.NodeInfo.pVol;
+ int rc;
+
+ /*
+ * We cannot create or replace anything, just open stuff.
+ */
+ if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE)
+ { /* likely */ }
+ else
+ return VERR_WRITE_PROTECT;
+
+ /*
+ * Special cases '.' and '..'
+ */
+ if ( pszEntry[0] == '.'
+ && ( pszEntry[1] == '\0'
+ || ( pszEntry[1] == '.'
+ && pszEntry[2] == '\0')))
+ {
+ if (!(fFlags & RTVFSOBJ_F_OPEN_DIRECTORY))
+ return VERR_IS_A_DIRECTORY;
+
+ PRTFSNTFSDIRSHRD pSharedToOpen;
+ if (pszEntry[1] == '\0')
+ {
+ pSharedToOpen = pShared;
+ rtFsNtfsDirShrd_Retain(pSharedToOpen);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ pSharedToOpen = NULL;
+ rc = rtFsNtfsDirShrd_QueryParent(pShared, &pSharedToOpen);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSDIR hVfsDir;
+ rc = rtFsNtfsVol_NewDirFromShared(pVol, pSharedToOpen, &hVfsDir);
+ rtFsNtfsDirShrd_Release(pSharedToOpen);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromDir(hVfsDir);
+ RTVfsDirRelease(hVfsDir);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ LogFlow(("rtFsNtfsDir_Open(%s): returns %Rrc\n", pszEntry, rc));
+ return rc;
+ }
+
+ /*
+ * Lookup the index entry.
+ */
+ PRTFSNTFSIDXNODE pNode;
+ PCNTFSIDXENTRYHDR pEntryHdr;
+ PCNTFSATFILENAME pFilename;
+ rc = rtFsNtfsDirShrd_Lookup(pShared, pszEntry, &pFilename, &pEntryHdr, &pNode);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t fFileAttribs = RT_LE2H_U32(pFilename->fFileAttribs);
+ switch (fFileAttribs & (NTFS_FA_DIRECTORY | NTFS_FA_REPARSE_POINT | NTFS_FA_DUP_FILE_NAME_INDEX_PRESENT))
+ {
+ /*
+ * File.
+ */
+ case 0:
+ if (fFlags & RTVFSOBJ_F_OPEN_FILE)
+ {
+ RTVFSFILE hVfsFile;
+ rc = rtFsNtfsVol_NewFile(pVol, fOpen, pEntryHdr, NULL /*pszStreamName*/, &hVfsFile, NULL, pszEntry);
+ 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;
+
+ /*
+ * Directory
+ */
+ case NTFS_FA_DUP_FILE_NAME_INDEX_PRESENT:
+ case NTFS_FA_DIRECTORY | NTFS_FA_DUP_FILE_NAME_INDEX_PRESENT:
+ case NTFS_FA_DIRECTORY:
+ if (fFlags & RTVFSOBJ_F_OPEN_DIRECTORY)
+ {
+ PRTFSNTFSDIRSHRD pSharedToOpen;
+ rc = rtFsNtfsVol_QueryOrCreateSharedDirByMftRef(pVol, &pEntryHdr->u.FileMftRec,
+ &pSharedToOpen, NULL, pszEntry);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSDIR hVfsDir;
+ rc = rtFsNtfsVol_NewDirFromShared(pVol, pSharedToOpen, &hVfsDir);
+ rtFsNtfsDirShrd_Release(pSharedToOpen);
+ 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;
+
+ /*
+ * Possible symbolic links.
+ */
+ case NTFS_FA_REPARSE_POINT:
+ case NTFS_FA_REPARSE_POINT | NTFS_FA_DIRECTORY:
+ case NTFS_FA_REPARSE_POINT | NTFS_FA_DUP_FILE_NAME_INDEX_PRESENT:
+ case NTFS_FA_REPARSE_POINT | NTFS_FA_DIRECTORY | NTFS_FA_DUP_FILE_NAME_INDEX_PRESENT:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+
+ default:
+ AssertFailed();
+ rc = VERR_FILE_NOT_FOUND;
+ break;
+ }
+ rtFsNtfsIdxNode_Release(pNode);
+ }
+
+ LogFlow(("rtFsNtfsDir_Open(%s): returns %Rrc\n", pszEntry, rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnCreateDir}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_CreateDir(void *pvThis, const char *pszSubDir, RTFMODE fMode, PRTVFSDIR phVfsDir)
+{
+ RT_NOREF(pvThis, pszSubDir, fMode, phVfsDir);
+ Log(("rtFsNtfsDir_CreateDir\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpenSymlink}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_OpenSymlink(void *pvThis, const char *pszSymlink, PRTVFSSYMLINK phVfsSymlink)
+{
+ RT_NOREF(pvThis, pszSymlink, phVfsSymlink);
+ Log(("rtFsNtfsDir_OpenSymlink\n"));
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnCreateSymlink}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_CreateSymlink(void *pvThis, const char *pszSymlink, const char *pszTarget,
+ RTSYMLINKTYPE enmType, PRTVFSSYMLINK phVfsSymlink)
+{
+ RT_NOREF(pvThis, pszSymlink, pszTarget, enmType, phVfsSymlink);
+ Log(("rtFsNtfsDir_CreateSymlink\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnUnlinkEntry}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_UnlinkEntry(void *pvThis, const char *pszEntry, RTFMODE fType)
+{
+ RT_NOREF(pvThis, pszEntry, fType);
+ Log(("rtFsNtfsDir_UnlinkEntry\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRenameEntry}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_RenameEntry(void *pvThis, const char *pszEntry, RTFMODE fType, const char *pszNewName)
+{
+ RT_NOREF(pvThis, pszEntry, fType, pszNewName);
+ Log(("rtFsNtfsDir_RenameEntry\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * Cleans up the directory enumeration stack, releasing all node references.
+ *
+ * @param pThis The open directory instance data.
+ */
+static void rtFsNtfsDir_StackCleanup(PRTFSNTFSDIR pThis)
+{
+ while (pThis->cEnumStackEntries > 0)
+ {
+ PRTFSNTFSIDXSTACKENTRY pEntry = &pThis->paEnumStack[--pThis->cEnumStackEntries];
+ rtFsNtfsIdxNode_Release(pEntry->pNodeInfo->pNode);
+ pEntry->pNodeInfo = NULL;
+ }
+ if (pThis->paEnumStack)
+ pThis->paEnumStack[0].iNext = 0;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRewindDir}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_RewindDir(void *pvThis)
+{
+ PRTFSNTFSDIR pThis = (PRTFSNTFSDIR)pvThis;
+ LogFlow(("rtFsNtfsDir_RewindDir\n"));
+
+ rtFsNtfsDir_StackCleanup(pThis);
+ pThis->fNoMoreFiles = false;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Descends down @a iSubnode to the first entry in left most leaf node.
+ *
+ * @returns IPRT status code.
+ * @param pThis The open directory instance data.
+ * @param pRootInfo The root info structure.
+ * @param iSubnode The subnode address to descend thru.
+ */
+static int rtFsNtfsDir_StackDescend(PRTFSNTFSDIR pThis, PRTFSNTFSIDXROOTINFO pRootInfo, int64_t iSubnode)
+{
+ for (;;)
+ {
+ /* Load the node. */
+ PRTFSNTFSIDXNODE pNode;
+ int rc = rtFsNtfsIdxRootInfo_QueryNode(pRootInfo, iSubnode, &pNode);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else
+ {
+ LogFlow(("rtFsNtfsDir_StackDescend: rtFsNtfsIdxRootInfo_QueryNode(%#RX64) error %Rrc!\n", iSubnode, rc));
+ return rc;
+ }
+
+ /* Push it onto the stack. */
+ uint32_t iStack = pThis->cEnumStackEntries;
+ if (iStack + 1 < pThis->cEnumStackMaxDepth)
+ { /* likely */ }
+ else if (pThis->cEnumStackMaxDepth < 1024)
+ {
+ Assert(pThis->cEnumStackMaxDepth> 0);
+ uint32_t cDepth = pThis->cEnumStackMaxDepth * 2;
+ Log5(("rtFsNtfsDir_ReadDir: Growing stack size to %u entries (from %u)\n", cDepth, pThis->cEnumStackMaxDepth));
+ void *pvNew = RTMemRealloc(pThis->paEnumStack, cDepth * sizeof(pThis->paEnumStack[0]));
+ if (pvNew)
+ pThis->paEnumStack = (PRTFSNTFSIDXSTACKENTRY)pvNew;
+ else
+ return VERR_NO_MEMORY;
+ pThis->cEnumStackMaxDepth = cDepth;
+ }
+ else
+ {
+ LogRel(("rtFsNtfsDir_StackDescend: Badly unbalanced index! (MFT record #%#RX64) -> VERR_VFS_BOGUS_FORMAT\n",
+ pThis->pShared->RootInfo.pRootAttr->pCore->pMftRec->TreeNode.Key));
+ return VERR_VFS_BOGUS_FORMAT;
+ }
+
+ Log5(("rtFsNtfsDir_ReadDir: pushing %#RX64 (cEntries=%u, iStack=%u)\n", iSubnode, pNode->NodeInfo.cEntries, iStack));
+ pThis->paEnumStack[iStack].iNext = 0;
+ pThis->paEnumStack[iStack].fDescend = false;
+ pThis->paEnumStack[iStack].pNodeInfo = &pNode->NodeInfo;
+ pThis->cEnumStackEntries = iStack + 1;
+
+ /* Stop if this is a leaf node. */
+ if ( !pNode->NodeInfo.fInternal
+ || !pNode->NodeInfo.cEntries /* paranoia */)
+ return VINF_SUCCESS;
+
+ /* Get the first entry and check that it's an internal node before trying to following it. */
+ PCNTFSIDXENTRYHDR pFirstEntry = pNode->NodeInfo.papEntries[0];
+ if (pFirstEntry->fFlags & NTFSIDXENTRYHDR_F_INTERNAL)
+ { /* likely */ }
+ else
+ return VINF_SUCCESS;
+ iSubnode = NTFSIDXENTRYHDR_GET_SUBNODE(pFirstEntry);
+ }
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnReadDir}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_ReadDir(void *pvThis, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry,
+ RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSNTFSDIR pThis = (PRTFSNTFSDIR)pvThis;
+ PRTFSNTFSDIRSHRD pShared = pThis->pShared;
+ int rc;
+ Log(("rtFsNtfsDir_ReadDir\n"));
+
+ /*
+ * Return immediately if no files at hand.
+ */
+ if (pThis->fNoMoreFiles)
+ return VERR_NO_MORE_FILES;
+
+ /*
+ * Make sure we've got a stack before we jump into the fray.
+ */
+ if (!pThis->cEnumStackMaxDepth)
+ {
+ uint32_t cDepth;
+ if (!pShared->RootInfo.pAlloc)
+ cDepth = 2;
+ else
+ {
+ cDepth = ASMBitFirstSetU64(pShared->RootInfo.pAlloc->cbValue / RT_LE2H_U32(pShared->RootInfo.pRoot->cbIndexNode));
+ cDepth += 3;
+ }
+
+ pThis->paEnumStack = (PRTFSNTFSIDXSTACKENTRY)RTMemAllocZ(cDepth * sizeof(pThis->paEnumStack[0]));
+ if (!pThis->paEnumStack)
+ return VERR_NO_MEMORY;
+ pThis->cEnumStackMaxDepth = cDepth;
+ pThis->cEnumStackEntries = 0;
+ Log5(("rtFsNtfsDir_ReadDir: Initial stack size: %u entries\n", cDepth));
+ //pThis->paEnumStack[0].iNext = 0;
+ }
+
+ /*
+ * Deal with '.' and '..' by using stack entry zero without setting cEnumStack to zero.
+ * This is fine because we've got the fNoMoreFiles flag that got checked already.
+ */
+ size_t const cbDirEntry = *pcbDirEntry;
+ if (pThis->cEnumStackEntries == 0)
+ {
+ if (pThis->paEnumStack[0].iNext <= 1)
+ {
+
+ *pcbDirEntry = RT_UOFFSETOF_DYN(RTDIRENTRYEX, szName[pThis->paEnumStack[0].iNext + 2]);
+ if (*pcbDirEntry > cbDirEntry)
+ return VERR_BUFFER_OVERFLOW;
+
+ /* Names. */
+ pDirEntry->cbName = pThis->paEnumStack[0].iNext + 1;
+ pDirEntry->szName[0] = '.';
+ pDirEntry->szName[pDirEntry->cbName - 1] = '.';
+ pDirEntry->szName[pDirEntry->cbName] = '\0';
+ pDirEntry->wszShortName[0] = '\0';
+ pDirEntry->cwcShortName = 0;
+
+ /* Get referenced shared directory structure that we return info about. */
+ PRTFSNTFSDIRSHRD pDotShared;
+ if (pThis->paEnumStack[0].iNext == 0)
+ {
+ rtFsNtfsDirShrd_Retain(pShared);
+ pDotShared = pShared;
+ }
+ else
+ {
+ pDotShared = NULL;
+ rc = rtFsNtfsDirShrd_QueryParent(pShared, &pDotShared);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("rtFsNtfsDir_ReadDir: couldn't find '..' filename! %Rrc\n", rc));
+ return rc;
+ }
+ }
+
+ /* Get the info. */
+ rc = rtFsNtfsCore_QueryInfo(pDotShared->RootInfo.pRootAttr->pCore, pDotShared->RootInfo.pRootAttr,
+ &pDirEntry->Info, enmAddAttr);
+ rtFsNtfsDirShrd_Release(pDotShared);
+ if (RT_SUCCESS(rc))
+ pThis->paEnumStack[0].iNext++;
+ Log5(("rtFsNtfsDir_ReadDir: => '%s' (%Rrc)\n", pDirEntry->szName, rc));
+ return rc;
+ }
+
+ /*
+ * Push the root onto the stack and decend down the left side of the tree.
+ */
+ PRTFSNTFSIDXNODEINFO pNodeInfo = &pShared->RootInfo.NodeInfo;
+ pThis->paEnumStack[0].pNodeInfo = pNodeInfo;
+ pThis->paEnumStack[0].iNext = 0;
+ pThis->cEnumStackEntries = 1;
+ Log5(("rtFsNtfsDir_ReadDir: pushing root\n"));
+ if ( pNodeInfo->fInternal
+ && pNodeInfo->cEntries > 0
+ && (pNodeInfo->papEntries[0]->fFlags & NTFSIDXENTRYHDR_F_INTERNAL) /* parnaoia */ )
+ {
+ rc = rtFsNtfsDir_StackDescend(pThis, &pShared->RootInfo, NTFSIDXENTRYHDR_GET_SUBNODE(pNodeInfo->papEntries[0]));
+ if (RT_FAILURE(rc))
+ {
+ pThis->fNoMoreFiles = true;
+ rtFsNtfsDir_StackCleanup(pThis);
+ return rc;
+ }
+ }
+ }
+
+ /*
+ * Work the stack.
+ */
+ int32_t iStack = pThis->cEnumStackEntries - 1;
+ while (iStack >= 0)
+ {
+ PRTFSNTFSIDXNODEINFO pNodeInfo = pThis->paEnumStack[iStack].pNodeInfo;
+ uint32_t iNext = pThis->paEnumStack[iStack].iNext;
+ if (iNext < pNodeInfo->cEntries)
+ {
+ PCNTFSIDXENTRYHDR pEntry = pNodeInfo->papEntries[iNext];
+ if ( !(pEntry->fFlags & NTFSIDXENTRYHDR_F_INTERNAL)
+ || !pThis->paEnumStack[iStack].fDescend)
+ {
+ if (!(pEntry->fFlags & NTFSIDXENTRYHDR_F_END))
+ {
+ /*
+ * Try return the current entry.
+ */
+ PCNTFSATFILENAME pFilename = (PCNTFSATFILENAME)(pEntry + 1);
+
+ /* Deal with the filename. */
+ size_t cchFilename;
+ rc = RTUtf16CalcUtf8LenEx(pFilename->wszFilename, pFilename->cwcFilename, &cchFilename);
+ if (RT_FAILURE(rc))
+ {
+ cchFilename = 48;
+ LogRel(("rtFsNtfsDir_ReadDir: Bad filename (%Rrc) %.*Rhxs\n",
+ rc, pFilename->cwcFilename * sizeof(RTUTF16), pFilename->wszFilename));
+ }
+ *pcbDirEntry = RT_UOFFSETOF_DYN(RTDIRENTRYEX, szName[cchFilename + 1]);
+ if (*pcbDirEntry > cbDirEntry)
+ {
+ Log5(("rtFsNtfsDir_ReadDir: returns VERR_BUFFER_OVERFLOW (for '%.*ls')\n",
+ pFilename->cwcFilename, pFilename->wszFilename));
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ char *pszDst = pDirEntry->szName;
+ if (RT_SUCCESS(rc))
+ rc = RTUtf16ToUtf8Ex(pFilename->wszFilename, pFilename->cwcFilename, &pszDst,
+ cbDirEntry - RT_UOFFSETOF(RTDIRENTRYEX, szName), &cchFilename);
+ if (RT_FAILURE(rc))
+ cchFilename = RTStrPrintf(pDirEntry->szName, cbDirEntry - RT_UOFFSETOF(RTDIRENTRYEX, szName),
+ "{invalid-name-%#RX64}", NTFSMFTREF_GET_IDX(&pEntry->u.FileMftRec));
+ pDirEntry->cbName = (uint16_t)cchFilename;
+
+ /* Figure out how to detect short names. */
+ pDirEntry->cwcShortName = 0;
+ pDirEntry->wszShortName[0] = '\0';
+
+ /* Standard attributes: file mode, sizes and timestamps. */
+ pDirEntry->Info.cbObject = RT_LE2H_U64(pFilename->cbData);
+ pDirEntry->Info.cbAllocated = RT_LE2H_U64(pFilename->cbAllocated);
+ RTTimeSpecSetNtTime(&pDirEntry->Info.BirthTime, RT_LE2H_U64(pFilename->iCreationTime));
+ RTTimeSpecSetNtTime(&pDirEntry->Info.ModificationTime, RT_LE2H_U64(pFilename->iLastDataModTime));
+ RTTimeSpecSetNtTime(&pDirEntry->Info.ChangeTime, RT_LE2H_U64(pFilename->iLastMftModTime));
+ RTTimeSpecSetNtTime(&pDirEntry->Info.AccessTime, RT_LE2H_U64(pFilename->iLastAccessTime));
+ pDirEntry->Info.Attr.fMode = rtFsNtfsConvertFileattribsToMode(RT_LE2H_U32(pFilename->fFileAttribs), pFilename,
+ RT_LE2H_U16(pEntry->cbKey));
+
+ /* additional stuff. */
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_NOTHING:
+ enmAddAttr = RTFSOBJATTRADD_UNIX;
+ RT_FALL_THRU();
+ case RTFSOBJATTRADD_UNIX:
+ pDirEntry->Info.Attr.u.Unix.uid = NIL_RTUID;
+ pDirEntry->Info.Attr.u.Unix.gid = NIL_RTGID;
+ pDirEntry->Info.Attr.u.Unix.cHardlinks = 1;
+ pDirEntry->Info.Attr.u.Unix.INodeIdDevice = 0;
+ pDirEntry->Info.Attr.u.Unix.INodeId = NTFSMFTREF_GET_IDX(&pEntry->u.FileMftRec);
+ pDirEntry->Info.Attr.u.Unix.fFlags = 0;
+ pDirEntry->Info.Attr.u.Unix.GenerationId = 0;
+ pDirEntry->Info.Attr.u.Unix.Device = 0;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ pDirEntry->Info.Attr.u.UnixOwner.uid = NIL_RTUID;
+ pDirEntry->Info.Attr.u.UnixOwner.szName[0] = '\0';
+ break;
+
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ pDirEntry->Info.Attr.u.UnixGroup.gid = NIL_RTGID;
+ pDirEntry->Info.Attr.u.UnixGroup.szName[0] = '\0';
+ break;
+
+ case RTFSOBJATTRADD_EASIZE:
+ if (!(pFilename->fFileAttribs & RT_H2LE_U32_C(NTFS_FA_REPARSE_POINT)))
+ pDirEntry->Info.Attr.u.EASize.cb = pFilename->u.cbPackedEas;
+ else
+ pDirEntry->Info.Attr.u.EASize.cb = 0;
+ break;
+
+ default:
+ AssertFailed();
+ RT_ZERO(pDirEntry->Info.Attr.u);
+ break;
+ }
+ pDirEntry->Info.Attr.enmAdditional = enmAddAttr;
+
+ /*
+ * Advance the stack entry to the next entry and return.
+ */
+ Log5(("rtFsNtfsDir_ReadDir: => iStack=%u iNext=%u - '%.*ls'\n",
+ iStack, iNext, pFilename->cwcFilename, pFilename->wszFilename));
+ pThis->paEnumStack[iStack].iNext = iNext + 1;
+ pThis->paEnumStack[iStack].fDescend = true;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * End node, so pop it. We join the beoynd-end-of-entries path
+ * further down, forcing the descend code to use continue.
+ */
+ }
+ else
+ {
+ /*
+ * Descend.
+ */
+ rc = rtFsNtfsDir_StackDescend(pThis, &pShared->RootInfo,
+ NTFSIDXENTRYHDR_GET_SUBNODE(pNodeInfo->papEntries[iNext]));
+ if (RT_SUCCESS(rc))
+ {
+ pThis->paEnumStack[iStack].fDescend = false;
+ iStack = pThis->cEnumStackEntries - 1;
+ continue;
+ }
+ pThis->fNoMoreFiles = true;
+ rtFsNtfsDir_StackCleanup(pThis);
+ return rc;
+ }
+ }
+
+ /*
+ * Pop at stack entry.
+ */
+ Log5(("rtFsNtfsDir_ReadDir: popping %#RX64 (iNext=%u, cEntries=%u, iStack=%u) -> %#RX64 (iNext=%d, cEntries=%u)\n",
+ pNodeInfo->pNode ? pNodeInfo->pNode->pNode->iSelfAddress : 0, iNext, pNodeInfo->cEntries, iStack,
+ iStack > 0 && pThis->paEnumStack[iStack - 1].pNodeInfo->pNode
+ ? pThis->paEnumStack[iStack - 1].pNodeInfo->pNode->pNode->iSelfAddress : UINT64_MAX,
+ iStack > 0 ? pThis->paEnumStack[iStack - 1].iNext : -1,
+ iStack > 0 ? pThis->paEnumStack[iStack - 1].pNodeInfo->cEntries : 0 ));
+ rtFsNtfsIdxNode_Release(pNodeInfo->pNode);
+ pThis->paEnumStack[iStack].pNodeInfo = NULL;
+ pThis->cEnumStackEntries = iStack;
+ iStack--;
+ Assert(iStack < 0 || !pThis->paEnumStack[iStack].fDescend);
+ }
+
+ /*
+ * The End.
+ */
+ Log5(("rtFsNtfsDir_ReadDir: no more files\n"));
+ pThis->fNoMoreFiles = true;
+ return VERR_NO_MORE_FILES;
+}
+
+
+/**
+ * NTFS directory operations.
+ */
+static const RTVFSDIROPS g_rtFsNtfsDirOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_DIR,
+ "NTFS Dir",
+ rtFsNtfsDir_Close,
+ rtFsNtfsDir_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSDIROPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSDIROPS, ObjSet) - RT_UOFFSETOF(RTVFSDIROPS, Obj),
+ rtFsNtfsDir_SetMode,
+ rtFsNtfsDir_SetTimes,
+ rtFsNtfsDir_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtFsNtfsDir_Open,
+ NULL /* pfnFollowAbsoluteSymlink */,
+ NULL /* pfnOpenFile */,
+ NULL /* pfnOpenDir */,
+ rtFsNtfsDir_CreateDir,
+ rtFsNtfsDir_OpenSymlink,
+ rtFsNtfsDir_CreateSymlink,
+ NULL /* pfnQueryEntryInfo */,
+ rtFsNtfsDir_UnlinkEntry,
+ rtFsNtfsDir_RenameEntry,
+ rtFsNtfsDir_RewindDir,
+ rtFsNtfsDir_ReadDir,
+ RTVFSDIROPS_VERSION,
+};
+
+
+/**
+ * Creates a new directory instance given a shared directory structure.
+ *
+ * @returns IPRT status code.
+ * @param pThis The NTFS volume instance.
+ * @param pSharedDir The shared directory structure to create a new
+ * handle to.
+ * @param phVfsDir Where to return the directory handle.
+ */
+static int rtFsNtfsVol_NewDirFromShared(PRTFSNTFSVOL pThis, PRTFSNTFSDIRSHRD pSharedDir, PRTVFSDIR phVfsDir)
+{
+ PRTFSNTFSDIR pNewDir;
+ int rc = RTVfsNewDir(&g_rtFsNtfsDirOps, sizeof(*pNewDir), 0 /*fFlags*/, pThis->hVfsSelf, NIL_RTVFSLOCK,
+ phVfsDir, (void **)&pNewDir);
+ if (RT_SUCCESS(rc))
+ {
+ rtFsNtfsDirShrd_Retain(pSharedDir);
+ pNewDir->pShared = pSharedDir;
+ pNewDir->cEnumStackEntries = 0;
+ pNewDir->cEnumStackMaxDepth = 0;
+ pNewDir->paEnumStack = NULL;
+ return VINF_SUCCESS;
+ }
+ return rc;
+}
+
+
+
+/*
+ *
+ * Volume level code.
+ * Volume level code.
+ * Volume level code.
+ *
+ */
+
+
+/**
+ * Slow path for querying the allocation state of a cluster.
+ *
+ * @returns IPRT status code.
+ * @param pThis The NTFS volume instance.
+ * @param iCluster The cluster to query.
+ * @param pfState Where to return the state.
+ */
+static int rtFsNtfsVol_QueryClusterStateSlow(PRTFSNTFSVOL pThis, uint64_t iCluster, bool *pfState)
+{
+ int rc;
+ uint64_t const cbWholeBitmap = RT_LE2H_U64(pThis->pMftBitmap->pAttrHdr->u.NonRes.cbData);
+ uint64_t const offInBitmap = iCluster >> 3;
+ if (offInBitmap < cbWholeBitmap)
+ {
+ if (!pThis->pvBitmap)
+ {
+ /*
+ * Try cache the whole bitmap if it's not too large.
+ */
+ if ( cbWholeBitmap <= RTFSNTFS_MAX_WHOLE_BITMAP_CACHE
+ && cbWholeBitmap >= RT_ALIGN_64(pThis->cClusters >> 3, 8))
+ {
+ pThis->cbBitmapAlloc = RT_ALIGN_Z((uint32_t)cbWholeBitmap, 8);
+ pThis->pvBitmap = RTMemAlloc(pThis->cbBitmapAlloc);
+ if (pThis->pvBitmap)
+ {
+ memset(pThis->pvBitmap, 0xff, pThis->cbBitmapAlloc);
+ rc = rtFsNtfsAttr_Read(pThis->pMftBitmap, 0, pThis->pvBitmap, (uint32_t)cbWholeBitmap);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->iFirstBitmapCluster = 0;
+ pThis->cBitmapClusters = pThis->cClusters;
+ *pfState = rtFsNtfsBitmap_IsSet(pThis->pvBitmap, (uint32_t)iCluster);
+ return VINF_SUCCESS;
+ }
+ RTMemFree(pThis->pvBitmap);
+ pThis->pvBitmap = NULL;
+ pThis->cbBitmapAlloc = 0;
+ return rc;
+ }
+ }
+
+ /*
+ * Do a cluster/4K cache.
+ */
+ pThis->cbBitmapAlloc = RT_MAX(pThis->cbCluster, _4K);
+ pThis->pvBitmap = RTMemAlloc(pThis->cbBitmapAlloc);
+ if (!pThis->pvBitmap)
+ {
+ pThis->cbBitmapAlloc = 0;
+ return VERR_NO_MEMORY;
+ }
+ }
+
+ /*
+ * Load a cache line.
+ */
+ Assert(RT_IS_POWER_OF_TWO(pThis->cbBitmapAlloc));
+ uint64_t offLoad = offInBitmap & ~(uint64_t)(pThis->cbBitmapAlloc - 1);
+ uint32_t cbLoad = (uint32_t)RT_MIN(cbWholeBitmap - offLoad, pThis->cbBitmapAlloc);
+
+ memset(pThis->pvBitmap, 0xff, pThis->cbBitmapAlloc);
+ rc = rtFsNtfsAttr_Read(pThis->pMftBitmap, offLoad, pThis->pvBitmap, cbLoad);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->iFirstBitmapCluster = offLoad << 3;
+ pThis->cBitmapClusters = cbLoad << 3;
+ *pfState = rtFsNtfsBitmap_IsSet(pThis->pvBitmap, (uint32_t)(iCluster - pThis->iFirstBitmapCluster));
+ return VINF_SUCCESS;
+ }
+ pThis->cBitmapClusters = 0;
+ }
+ else
+ {
+ LogRel(("rtFsNtfsVol_QueryClusterStateSlow: iCluster=%#RX64 is outside the bitmap (%#RX64)\n", iCluster, cbWholeBitmap));
+ rc = VERR_OUT_OF_RANGE;
+ }
+ return rc;
+}
+
+
+/**
+ * Query the allocation state of the given cluster.
+ *
+ * @returns IPRT status code.
+ * @param pThis The NTFS volume instance.
+ * @param iCluster The cluster to query.
+ * @param pfState Where to return the state.
+ */
+static int rtFsNtfsVol_QueryClusterState(PRTFSNTFSVOL pThis, uint64_t iCluster, bool *pfState)
+{
+ uint64_t iClusterInCache = iCluster - pThis->iFirstBitmapCluster;
+ if (iClusterInCache < pThis->cBitmapClusters)
+ {
+ *pfState = rtFsNtfsBitmap_IsSet(pThis->pvBitmap, (uint32_t)iClusterInCache);
+ return VINF_SUCCESS;
+ }
+ return rtFsNtfsVol_QueryClusterStateSlow(pThis, iCluster, pfState);
+}
+
+
+/**
+ * Callback for RTAvlU64Destroy used by rtFsNtfsVol_Close to destroy the MFT
+ * record cache.
+ *
+ * @returns VINF_SUCCESS
+ * @param pNode The MFT record to destroy.
+ * @param pvUser Ignored.
+ */
+static DECLCALLBACK(int) rtFsNtFsVol_DestroyCachedMftRecord(PAVLU64NODECORE pNode, void *pvUser)
+{
+ PRTFSNTFSMFTREC pMftRec = (PRTFSNTFSMFTREC)pNode;
+ RT_NOREF(pvUser);
+
+ RTMemFree(pMftRec->pbRec);
+ pMftRec->pbRec = NULL;
+ RTMemFree(pMftRec);
+
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * Callback for RTAvlU64Destroy used by rtFsNtfsVol_Close to destroy the index
+ * node cache.
+ *
+ * @returns VINF_SUCCESS
+ * @param pNode The index node to destroy.
+ * @param pvUser Ignored.
+ */
+static DECLCALLBACK(int) rtFsNtfsVol_DestroyIndexNode(PAVLU64NODECORE pNode, void *pvUser)
+{
+ PRTFSNTFSIDXNODE pIdxNode = (PRTFSNTFSIDXNODE)pNode;
+ RT_NOREF(pvUser);
+
+ RTMemFree(pIdxNode->pNode);
+ RTMemFree(pIdxNode->NodeInfo.papEntries);
+ pIdxNode->pNode = NULL;
+ pIdxNode->NodeInfo.papEntries = NULL;
+ pIdxNode->NodeInfo.pIndexHdr = NULL;
+ pIdxNode->NodeInfo.pVol = NULL;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsNtfsVol_Close(void *pvThis)
+{
+ PRTFSNTFSVOL pThis = (PRTFSNTFSVOL)pvThis;
+ Log(("rtFsNtfsVol_Close(%p):\n", pThis));
+
+ /*
+ * Index / directory related members.
+ */
+ if (pThis->pRootDir)
+ {
+ rtFsNtfsDirShrd_Release(pThis->pRootDir);
+ pThis->pRootDir = NULL;
+ }
+
+ RTAvlU64Destroy(&pThis->IdxNodeCacheRoot, rtFsNtfsVol_DestroyIndexNode, NULL);
+
+ RTMemFree(pThis->pawcUpcase);
+ pThis->pawcUpcase = NULL;
+
+ pThis->IdxNodeUnusedHead.pPrev = pThis->IdxNodeUnusedHead.pNext = NULL;
+
+ /*
+ * Allocation bitmap cache.
+ */
+ if (pThis->pMftBitmap)
+ {
+ rtFsNtfsCore_Release(pThis->pMftBitmap->pCore);
+ pThis->pMftBitmap = NULL;
+ }
+ RTMemFree(pThis->pvBitmap);
+ pThis->pvBitmap = NULL;
+
+ /*
+ * The MFT and MFT cache.
+ */
+ if (pThis->pMftData)
+ {
+ rtFsNtfsCore_Release(pThis->pMftData->pCore);
+ pThis->pMftData = NULL;
+ }
+
+ Assert(RTListIsEmpty(&pThis->CoreInUseHead));
+ PRTFSNTFSCORE pCurCore, pNextCore;
+ RTListForEachSafe(&pThis->CoreInUseHead, pCurCore, pNextCore, RTFSNTFSCORE, ListEntry)
+ rtFsNtfsCore_Destroy(pCurCore);
+ RTListForEachSafe(&pThis->CoreUnusedHead, pCurCore, pNextCore, RTFSNTFSCORE, ListEntry)
+ rtFsNtfsCore_Destroy(pCurCore);
+
+ pThis->CoreInUseHead.pPrev = pThis->CoreInUseHead.pNext = NULL;
+ pThis->CoreUnusedHead.pPrev = pThis->CoreUnusedHead.pNext = NULL;
+
+ Assert(pThis->MftRoot == NULL);
+ RTAvlU64Destroy(&pThis->MftRoot, rtFsNtFsVol_DestroyCachedMftRecord, NULL);
+
+ /*
+ * Backing file and handles.
+ */
+ RTVfsFileRelease(pThis->hVfsBacking);
+ pThis->hVfsBacking = NIL_RTVFSFILE;
+ pThis->hVfsSelf = NIL_RTVFS;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsNtfsVol_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ NOREF(pvThis); NOREF(pObjInfo); NOREF(enmAddAttr);
+ return VERR_WRONG_TYPE;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnOpenRoot}
+ */
+static DECLCALLBACK(int) rtFsNtfsVol_OpenRoot(void *pvThis, PRTVFSDIR phVfsDir)
+{
+ PRTFSNTFSVOL pThis = (PRTFSNTFSVOL)pvThis;
+ AssertReturn(pThis->pRootDir, VERR_INTERNAL_ERROR_4);
+ int rc = rtFsNtfsVol_NewDirFromShared(pThis, pThis->pRootDir, phVfsDir);
+ LogFlow(("rtFsNtfsVol_OpenRoot: returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnQueryRangeState}
+ */
+static DECLCALLBACK(int) rtFsNtfsVol_QueryRangeState(void *pvThis, uint64_t off, size_t cb, bool *pfUsed)
+{
+ PRTFSNTFSVOL pThis = (PRTFSNTFSVOL)pvThis;
+ *pfUsed = true;
+
+ /*
+ * Round to a cluster range.
+ */
+ uint64_t iCluster = off >> pThis->cClusterShift;
+
+ Assert(RT_IS_POWER_OF_TWO(pThis->cbCluster));
+ cb += off & (pThis->cbCluster - 1);
+ cb = RT_ALIGN_Z(cb, pThis->cbCluster);
+ size_t cClusters = cb >> pThis->cClusterShift;
+
+ /*
+ * Check the clusters one-by-one.
+ * Just to be cautious, we will always check the cluster at off, even when cb is zero.
+ */
+ do
+ {
+ bool fState = true;
+ int rc = rtFsNtfsVol_QueryClusterState(pThis, iCluster, &fState);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (fState)
+ {
+ *pfUsed = true;
+ LogFlow(("rtFsNtfsVol_QueryRangeState: %RX64 LB %#x - used\n", off & ~(uint64_t)(pThis->cbCluster - 1), cb));
+ return VINF_SUCCESS;
+ }
+
+ iCluster++;
+ } while (cClusters-- > 0);
+
+ LogFlow(("rtFsNtfsVol_QueryRangeState: %RX64 LB %#x - unused\n", off & ~(uint64_t)(pThis->cbCluster - 1), cb));
+ *pfUsed = false;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * NTFS volume operations.
+ */
+static const RTVFSOPS g_rtFsNtfsVolOps =
+{
+ /* .Obj = */
+ {
+ /* .uVersion = */ RTVFSOBJOPS_VERSION,
+ /* .enmType = */ RTVFSOBJTYPE_VFS,
+ /* .pszName = */ "NtfsVol",
+ /* .pfnClose = */ rtFsNtfsVol_Close,
+ /* .pfnQueryInfo = */ rtFsNtfsVol_QueryInfo,
+ /* .pfnQueryInfoEx = */ NULL,
+ /* .uEndMarker = */ RTVFSOBJOPS_VERSION
+ },
+ /* .uVersion = */ RTVFSOPS_VERSION,
+ /* .fFeatures = */ 0,
+ /* .pfnOpenRoot = */ rtFsNtfsVol_OpenRoot,
+ /* .pfnQueryRangeState = */ rtFsNtfsVol_QueryRangeState,
+ /* .uEndMarker = */ RTVFSOPS_VERSION
+};
+
+
+/**
+ * Checks that the storage for the given attribute is all marked allocated in
+ * the allocation bitmap of the volume.
+ *
+ * @returns IPRT status code.
+ * @param pThis The NTFS volume instance.
+ * @param pAttr The attribute to check.
+ * @param pszDesc Description of the attribute.
+ * @param pErrInfo Where to return error details.
+ */
+static int rtFsNtfsVolCheckBitmap(PRTFSNTFSVOL pThis, PRTFSNTFSATTR pAttr, const char *pszDesc, PRTERRINFO pErrInfo)
+{
+ PRTFSNTFSATTRSUBREC pSubRec = NULL;
+ PRTFSNTFSEXTENTS pTable = &pAttr->Extents;
+ uint64_t offFile = 0;
+ for (;;)
+ {
+ uint32_t const cExtents = pTable->cExtents;
+ PRTFSNTFSEXTENT paExtents = pTable->paExtents;
+ for (uint32_t iExtent = 0; iExtent < cExtents; iExtent++)
+ {
+ uint64_t const off = paExtents[iExtent].off;
+ if (off == UINT64_MAX)
+ offFile += paExtents[iExtent].cbExtent;
+ else
+ {
+ uint64_t iCluster = off >> pThis->cClusterShift;
+ uint64_t cClusters = paExtents[iExtent].cbExtent >> pThis->cClusterShift;
+ Assert((cClusters << pThis->cClusterShift) == paExtents[iExtent].cbExtent);
+ Assert(cClusters != 0);
+
+ while (cClusters-- > 0)
+ {
+ bool fState = false;
+ int rc = rtFsNtfsVol_QueryClusterState(pThis, iCluster, &fState);
+ if (RT_FAILURE(rc))
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, rc,
+ "Error querying allocation bitmap entry %#RX64 (for %s offset %#RX64)",
+ iCluster, pszDesc, offFile);
+ if (!fState)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Cluster %#RX64 at offset %#RX64 in %s is not marked allocated",
+ iCluster, offFile, pszDesc);
+ offFile += pThis->cbCluster;
+ }
+ }
+ }
+
+ /* Next table. */
+ pSubRec = pSubRec ? pSubRec->pNext : pAttr->pSubRecHead;
+ if (!pSubRec)
+ return VINF_SUCCESS;
+ pTable = &pSubRec->Extents;
+ }
+}
+
+
+/**
+ * Loads, validates and setups the '.' (NTFS_MFT_IDX_ROOT) MFT entry.
+ *
+ * @returns IPRT status code
+ * @param pThis The NTFS volume instance. Will set pawcUpcase.
+ * @param pErrInfo Where to return additional error info.
+ */
+static int rtFsNtfsVolLoadRootDir(PRTFSNTFSVOL pThis, PRTERRINFO pErrInfo)
+{
+ /*
+ * Load it and do some checks.
+ */
+ PRTFSNTFSCORE pCore;
+ int rc = rtFsNtfsVol_NewCoreForMftIdx(pThis, NTFS_MFT_IDX_ROOT, false /*fRelaxedUsa*/, &pCore, pErrInfo); // DON'T COMMIT
+ if (RT_SUCCESS(rc))
+ {
+ PRTFSNTFSATTR pFilenameAttr = rtFsNtfsCore_FindUnnamedAttribute(pCore, NTFS_AT_FILENAME);
+ if (!pFilenameAttr)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "RootDir: has no FILENAME attribute!");
+ else if (pFilenameAttr->pAttrHdr->fNonResident)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "RootDir: FILENAME attribute is non-resident!");
+ else if (pFilenameAttr->pAttrHdr->u.Res.cbValue < RT_UOFFSETOF(NTFSATFILENAME, wszFilename[1]))
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "RootDir: FILENAME attribute value size is too small: %#x",
+ pFilenameAttr->pAttrHdr->u.Res.cbValue);
+ else
+ {
+ PNTFSATFILENAME pFilename = (PNTFSATFILENAME)( (uint8_t *)pFilenameAttr->pAttrHdr
+ + pFilenameAttr->pAttrHdr->u.Res.offValue);
+ if ( pFilename->cwcFilename != 1
+ || ( RTUtf16NICmpAscii(pFilename->wszFilename, ".", 1) != 0
+ && RTUtf16NICmpAscii(pFilename->wszFilename, "$", 1) != 0))
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "RootDir: FILENAME is not '.' nor '$: '%.*ls'",
+ pFilename->cwcFilename, pFilename->wszFilename);
+ else
+ {
+ PRTFSNTFSATTR pIndexRoot = rtFsNtfsCore_FindNamedAttributeAscii(pCore, NTFS_AT_INDEX_ROOT,
+ RT_STR_TUPLE(NTFS_DIR_ATTRIBUTE_NAME));
+ PRTFSNTFSATTR pIndexAlloc = rtFsNtfsCore_FindNamedAttributeAscii(pCore, NTFS_AT_INDEX_ALLOCATION,
+ RT_STR_TUPLE(NTFS_DIR_ATTRIBUTE_NAME));
+ PRTFSNTFSATTR pIndexBitmap = rtFsNtfsCore_FindNamedAttributeAscii(pCore, NTFS_AT_BITMAP,
+ RT_STR_TUPLE(NTFS_DIR_ATTRIBUTE_NAME));
+ if (!pIndexRoot)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "RootDir: Found no INDEX_ROOT attribute named $I30");
+ else if (!pIndexAlloc && pIndexBitmap)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "RootDir: Found no INDEX_ALLOCATION attribute named $I30");
+ else if (!pIndexBitmap && pIndexAlloc)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "RootDir: Found no BITMAP attribute named $I30");
+ if (RT_SUCCESS(rc) && pIndexAlloc)
+ rc = rtFsNtfsVolCheckBitmap(pThis, pIndexAlloc, "RootDir", pErrInfo);
+ if (RT_SUCCESS(rc) && pIndexBitmap)
+ rc = rtFsNtfsVolCheckBitmap(pThis, pIndexBitmap, "RootDir/bitmap", pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Load it as a normal directory.
+ */
+ PRTFSNTFSDIRSHRD pSharedDir;
+ rc = rtFsNtfsVol_NewSharedDirFromCore(pThis, pCore, &pSharedDir, pErrInfo, "RootDir");
+ if (RT_SUCCESS(rc))
+ {
+ rtFsNtfsCore_Release(pCore);
+ pThis->pRootDir = pSharedDir;
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ }
+ rtFsNtfsCore_Release(pCore);
+ }
+ else
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, rc, "Root dir: Error reading MFT record");
+ return rc;
+}
+
+
+/**
+ * Loads, validates and setups the '$UpCase' (NTFS_MFT_IDX_UP_CASE) MFT entry.
+ *
+ * This is needed for filename lookups, I think.
+ *
+ * @returns IPRT status code
+ * @param pThis The NTFS volume instance. Will set pawcUpcase.
+ * @param pErrInfo Where to return additional error info.
+ */
+static int rtFsNtfsVolLoadUpCase(PRTFSNTFSVOL pThis, PRTERRINFO pErrInfo)
+{
+ PRTFSNTFSCORE pCore;
+ int rc = rtFsNtfsVol_NewCoreForMftIdx(pThis, NTFS_MFT_IDX_UP_CASE, false /*fRelaxedUsa*/, &pCore, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ PRTFSNTFSATTR pDataAttr = rtFsNtfsCore_FindUnnamedAttribute(pCore, NTFS_AT_DATA);
+ if (pDataAttr)
+ {
+ /*
+ * Validate the '$Upcase' MFT record.
+ */
+ uint32_t const cbMin = 512;
+ uint32_t const cbMax = _128K;
+ if (!pDataAttr->pAttrHdr->fNonResident)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "$UpCase: unnamed DATA attribute is resident!");
+ else if ( (uint64_t)RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbAllocated) < cbMin
+ || (uint64_t)RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbAllocated) > cbMax)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$UpCase: unnamed DATA attribute allocated size is out of range: %#RX64, expected at least %#RX32 and no more than %#RX32",
+ RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbAllocated), cbMin, cbMax);
+ else if ( (uint64_t)RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbData) < cbMin
+ || (uint64_t)RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbData)
+ > (uint64_t)RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbData)
+ || ((uint64_t)RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbData) & 1) )
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$UpCase: unnamed DATA attribute initialized size is out of range: %#RX64, expected at least %#RX32 and no more than %#RX64",
+ RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbData), cbMin,
+ RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbAllocated) );
+ else if ( (uint64_t)RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbInitialized) < cbMin
+ || (uint64_t)RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbInitialized)
+ > (uint64_t)RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbAllocated)
+ || ((uint64_t)RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbInitialized) & 1) )
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$UpCase: unnamed DATA attribute initialized size is out of range: %#RX64, expected at least %#RX32 and no more than %#RX64",
+ RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbInitialized), cbMin,
+ RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbAllocated) );
+ else if (pDataAttr->pAttrHdr->u.NonRes.uCompressionUnit != 0)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$UpCase: unnamed DATA attribute is compressed: %#x",
+ pDataAttr->pAttrHdr->u.NonRes.uCompressionUnit);
+ else
+ {
+ PRTFSNTFSATTR pFilenameAttr = rtFsNtfsCore_FindUnnamedAttribute(pCore, NTFS_AT_FILENAME);
+ if (!pFilenameAttr)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "$UpCase has no FILENAME attribute!");
+ else if (pFilenameAttr->pAttrHdr->fNonResident)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "$UpCase FILENAME attribute is non-resident!");
+ else if (pFilenameAttr->pAttrHdr->u.Res.cbValue < RT_UOFFSETOF(NTFSATFILENAME, wszFilename[7]))
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$UpCase: FILENAME attribute value size is too small: %#x",
+ pFilenameAttr->pAttrHdr->u.Res.cbValue);
+ else
+ {
+ PNTFSATFILENAME pFilename = (PNTFSATFILENAME)( (uint8_t *)pFilenameAttr->pAttrHdr
+ + pFilenameAttr->pAttrHdr->u.Res.offValue);
+ if ( pFilename->cwcFilename != 7
+ || RTUtf16NICmpAscii(pFilename->wszFilename, "$UpCase", 7) != 0)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$UpCase: FILENAME isn't '$UpCase': '%.*ls'",
+ pFilename->cwcFilename, pFilename->wszFilename);
+ else
+ {
+ /*
+ * Allocate memory for the uppercase table and read it.
+ */
+ PRTUTF16 pawcUpcase;
+ pThis->pawcUpcase = pawcUpcase = (PRTUTF16)RTMemAlloc(_64K * sizeof(pThis->pawcUpcase[0]));
+ if (pawcUpcase)
+ {
+ for (size_t i = 0; i < _64K; i++)
+ pawcUpcase[i] = (uint16_t)i;
+
+ rc = rtFsNtfsAttr_Read(pDataAttr, 0, pawcUpcase, pDataAttr->pAttrHdr->u.NonRes.cbData);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Check the data.
+ */
+ for (size_t i = 1; i < _64K; i++)
+ if (pawcUpcase[i] == 0)
+ {
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$UpCase entry %#x is zero!", i);
+ break;
+ }
+
+ /*
+ * While we still have the $UpCase file open, check it against the allocation bitmap.
+ */
+ if (RT_SUCCESS(rc))
+ rc = rtFsNtfsVolCheckBitmap(pThis, pDataAttr, "$UpCase", pErrInfo);
+
+ /* We're done, no need for special success return here though. */
+ }
+ else
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, rc, "Error reading $UpCase data into memory");
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ }
+ }
+ else
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "$UpCase: has no unnamed DATA attribute!");
+ rtFsNtfsCore_Release(pCore);
+ }
+ else
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, rc, "$UpCase: Error reading the MFT record");
+ return rc;
+}
+
+
+/**
+ * Loads the allocation bitmap and does basic validation of.
+ *
+ * @returns IPRT status code.
+ * @param pThis The NTFS volume instance. Will set up the
+ * 'Allocation bitmap and cache' fields.
+ * @param pErrInfo Where to return error details.
+ */
+static int rtFsNtfsVolLoadBitmap(PRTFSNTFSVOL pThis, PRTERRINFO pErrInfo)
+{
+ PRTFSNTFSCORE pCore;
+ int rc = rtFsNtfsVol_NewCoreForMftIdx(pThis, NTFS_MFT_IDX_BITMAP, false /*fRelaxedUsa*/, &pCore, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ PRTFSNTFSATTR pMftBitmap;
+ pThis->pMftBitmap = pMftBitmap = rtFsNtfsCore_FindUnnamedAttribute(pCore, NTFS_AT_DATA);
+ if (pMftBitmap)
+ {
+ /*
+ * Validate the '$Bitmap' MFT record.
+ * We expect the bitmap to be fully initialized and be sized according to the
+ * formatted volume size. Allegedly, NTFS pads it to an even 8 byte in size.
+ */
+ uint64_t const cbMinBitmap = RT_ALIGN_64(pThis->cbVolume >> (pThis->cClusterShift + 3), 8);
+ uint64_t const cbMaxBitmap = RT_ALIGN_64(cbMinBitmap, pThis->cbCluster);
+ //uint64_t const cbMinInitialized = RT_ALIGN_64((RT_MAX(pThis->uLcnMft, pThis->uLcnMftMirror) + 16) >> 3, 8);
+ if (!pMftBitmap->pAttrHdr->fNonResident)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "MFT record #6 unnamed DATA attribute is resident!");
+ else if ( (uint64_t)RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbAllocated) < cbMinBitmap
+ || (uint64_t)RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbAllocated) > cbMaxBitmap)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$Bitmap: unnamed DATA attribute allocated size is out of range: %#RX64, expected at least %#RX64 and no more than %#RX64",
+ RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbAllocated), cbMinBitmap, cbMaxBitmap);
+ else if ( (uint64_t)RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbData) < cbMinBitmap
+ || (uint64_t)RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbData)
+ > (uint64_t)RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbData))
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$Bitmap: unnamed DATA attribute initialized size is out of range: %#RX64, expected at least %#RX64 and no more than %#RX64",
+ RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbData), cbMinBitmap,
+ RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbAllocated) );
+ else if ( (uint64_t)RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbInitialized) < cbMinBitmap
+ || (uint64_t)RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbInitialized)
+ > (uint64_t)RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbAllocated))
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$Bitmap: unnamed DATA attribute initialized size is out of range: %#RX64, expected at least %#RX64 and no more than %#RX64",
+ RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbInitialized), cbMinBitmap,
+ RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbAllocated) );
+ else if (pMftBitmap->pAttrHdr->u.NonRes.uCompressionUnit != 0)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$Bitmap: unnamed DATA attribute is compressed: %#x",
+ pMftBitmap->pAttrHdr->u.NonRes.uCompressionUnit);
+ else if (pMftBitmap->Extents.cExtents != 1) /* paranoia for now */
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$Bitmap: unnamed DATA attribute is expected to have a single extent: %u extents",
+ pMftBitmap->Extents.cExtents);
+ else if (pMftBitmap->Extents.paExtents[0].off == UINT64_MAX)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "MFT record #6 unnamed DATA attribute is sparse");
+ else
+ {
+ PRTFSNTFSATTR pFilenameAttr = rtFsNtfsCore_FindUnnamedAttribute(pCore, NTFS_AT_FILENAME);
+ if (!pFilenameAttr)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "MFT record #6 has no FILENAME attribute!");
+ else if (pFilenameAttr->pAttrHdr->fNonResident)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "MFT record #6 FILENAME attribute is non-resident!");
+ else if (pFilenameAttr->pAttrHdr->u.Res.cbValue < RT_UOFFSETOF(NTFSATFILENAME, wszFilename[7]))
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$Bitmap FILENAME attribute value size is too small: %#x",
+ pFilenameAttr->pAttrHdr->u.Res.cbValue);
+ else
+ {
+ PNTFSATFILENAME pFilename = (PNTFSATFILENAME)( (uint8_t *)pFilenameAttr->pAttrHdr
+ + pFilenameAttr->pAttrHdr->u.Res.offValue);
+ if ( pFilename->cwcFilename != 7
+ || RTUtf16NICmpAscii(pFilename->wszFilename, "$Bitmap", 7) != 0)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$Bitmap: FILENAME isn't '$Bitmap': '%.*ls'",
+ pFilename->cwcFilename, pFilename->wszFilename);
+ else
+ {
+ /*
+ * Read some of it into the buffer and check that essential stuff is flagged as allocated.
+ */
+ /* The boot sector. */
+ bool fState = false;
+ rc = rtFsNtfsVol_QueryClusterState(pThis, 0, &fState);
+ if (RT_SUCCESS(rc) && !fState)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "MFT allocation bitmap error: Bootsector isn't marked allocated!");
+ else if (RT_FAILURE(rc))
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "MFT allocation bitmap (offset 0) read error: %Rrc", rc);
+
+ /* The bitmap ifself, the MFT data, and the MFT bitmap. */
+ if (RT_SUCCESS(rc))
+ rc = rtFsNtfsVolCheckBitmap(pThis, pThis->pMftBitmap, "allocation bitmap", pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFsNtfsVolCheckBitmap(pThis, pThis->pMftData, "MFT", pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFsNtfsVolCheckBitmap(pThis,
+ rtFsNtfsCore_FindUnnamedAttribute(pThis->pMftData->pCore, NTFS_AT_BITMAP),
+ "MFT Bitmap", pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Looks like the bitmap is good.
+ */
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ }
+ pThis->pMftBitmap = NULL;
+ }
+ else
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "$Bitmap: has no unnamed DATA attribute!");
+ rtFsNtfsCore_Release(pCore);
+ }
+ else
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, rc, "$Bitmap: Error MFT record");
+ return rc;
+}
+
+
+/**
+ * Loads, validates and setups the '$Volume' (NTFS_MFT_IDX_VOLUME) MFT entry.
+ *
+ * @returns IPRT status code
+ * @param pThis The NTFS volume instance. Will set uNtfsVersion
+ * and fVolumeFlags.
+ * @param pErrInfo Where to return additional error info.
+ */
+static int rtFsNtfsVolLoadVolumeInfo(PRTFSNTFSVOL pThis, PRTERRINFO pErrInfo)
+{
+ PRTFSNTFSCORE pCore;
+ int rc = rtFsNtfsVol_NewCoreForMftIdx(pThis, NTFS_MFT_IDX_VOLUME, false /*fRelaxedUsa*/, &pCore, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ PRTFSNTFSATTR pVolInfoAttr = rtFsNtfsCore_FindUnnamedAttribute(pCore, NTFS_AT_VOLUME_INFORMATION);
+ if (pVolInfoAttr)
+ {
+ /*
+ * Validate the '$Volume' MFT record.
+ */
+ if (pVolInfoAttr->pAttrHdr->fNonResident)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "$Volume unnamed VOLUME_INFORMATION attribute is not resident!");
+ else if ( pVolInfoAttr->cbResident != sizeof(NTFSATVOLUMEINFO)
+ || pVolInfoAttr->cbValue != sizeof(NTFSATVOLUMEINFO))
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$Volume VOLUME_INFORMATION attribute has the wrong size: cbValue=%#RX64, cbResident=%#RX32, expected %#x\n",
+ pVolInfoAttr->cbValue, pVolInfoAttr->cbResident, sizeof(NTFSATVOLUMEINFO));
+ else
+ {
+ PRTFSNTFSATTR pFilenameAttr = rtFsNtfsCore_FindUnnamedAttribute(pCore, NTFS_AT_FILENAME);
+ if (!pFilenameAttr)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "$Volume has no FILENAME attribute!");
+ else if (pFilenameAttr->pAttrHdr->fNonResident)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "$Volume FILENAME attribute is non-resident!");
+ else if (pFilenameAttr->pAttrHdr->u.Res.cbValue < RT_UOFFSETOF(NTFSATFILENAME, wszFilename[7]))
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$Volume FILENAME attribute value size is too small: %#x",
+ pFilenameAttr->pAttrHdr->u.Res.cbValue);
+ else
+ {
+ PNTFSATFILENAME pFilename = (PNTFSATFILENAME)( (uint8_t *)pFilenameAttr->pAttrHdr
+ + pFilenameAttr->pAttrHdr->u.Res.offValue);
+ if ( pFilename->cwcFilename != 7
+ || RTUtf16NICmpAscii(pFilename->wszFilename, "$Volume", 7) != 0)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$Volume FILENAME isn't '$Volume': '%.*ls'",
+ pFilename->cwcFilename, pFilename->wszFilename);
+ else
+ {
+ /*
+ * Look at the information.
+ */
+ PCNTFSATVOLUMEINFO pVolInfo;
+ pVolInfo = (PCNTFSATVOLUMEINFO)((uint8_t *)pVolInfoAttr->pAttrHdr + pVolInfoAttr->pAttrHdr->u.Res.offValue);
+ pThis->uNtfsVersion = RTFSNTFS_MAKE_VERSION(pVolInfo->uMajorVersion, pVolInfo->uMinorVersion);
+ pThis->fVolumeFlags = RT_LE2H_U16(pVolInfo->fFlags);
+ Log(("NTFS: Version %u.%u, flags=%#x\n", pVolInfo->uMajorVersion, pVolInfo->uMinorVersion, pThis->fVolumeFlags));
+
+ /* We're done, no need for special success return here though. */
+ }
+ }
+ }
+ }
+ else
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "MFT record $Volume has no unnamed VOLUME_INFORMATION attribute!");
+ rtFsNtfsCore_Release(pCore);
+ }
+ else
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, rc, "Error reading $Volume MFT record");
+ return rc;
+}
+
+
+/**
+ * Loads, validates and setups the '$Mft' (NTFS_MFT_IDX_MFT) MFT entry.
+ *
+ * This is the first thing we do after we've checked out the boot sector and
+ * extracted information from it, since everything else depends on us being able
+ * to access the MFT data.
+ *
+ * @returns IPRT status code
+ * @param pThis The NTFS volume instance. Will set pMftData.
+ * @param pErrInfo Where to return additional error info.
+ */
+static int rtFsNtfsVolLoadMft(PRTFSNTFSVOL pThis, PRTERRINFO pErrInfo)
+{
+ /*
+ * Bootstrap the MFT data stream.
+ */
+ PRTFSNTFSMFTREC pRec = rtFsNtfsVol_NewMftRec(pThis, NTFS_MFT_IDX_MFT);
+ AssertReturn(pRec, VERR_NO_MEMORY);
+
+#if 0 && defined(LOG_ENABLED)
+ for (uint32_t i = 0; i < 128; i++)
+ {
+ uint64_t const offDisk = (pThis->uLcnMft << pThis->cClusterShift) + i * pThis->cbMftRecord;
+ int rc = RTVfsFileReadAt(pThis->hVfsBacking, offDisk, pRec->pbRec, pThis->cbMftRecord, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pRec->TreeNode.Key = i;
+ rtfsNtfsMftRec_Log(pRec, pThis->cbMftRecord);
+ pRec->TreeNode.Key = 0;
+ }
+ }
+#endif
+
+ uint64_t const offDisk = pThis->uLcnMft << pThis->cClusterShift;
+ int rc = RTVfsFileReadAt(pThis->hVfsBacking, offDisk, pRec->pbRec, pThis->cbMftRecord, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsNtfsRec_DoMultiSectorFixups(&pRec->pFileRec->Hdr, pThis->cbMftRecord, true, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ rtfsNtfsMftRec_Log(pRec, pThis->cbMftRecord);
+#endif
+ rc = rtFsNtfsVol_ParseMft(pThis, pRec, pErrInfo);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ PRTFSNTFSCORE pCore = pRec->pCore;
+ PRTFSNTFSATTR pMftData;
+ pThis->pMftData = pMftData = rtFsNtfsCore_FindUnnamedAttribute(pCore, NTFS_AT_DATA);
+ if (pMftData)
+ {
+ /*
+ * Validate the '$Mft' MFT record.
+ */
+ PNTFSATTRIBHDR pAttrHdr = pMftData->pAttrHdr;
+ if (!pAttrHdr->fNonResident)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "MFT record #0 unnamed DATA attribute is resident!");
+ else if ( (uint64_t)RT_LE2H_U64(pAttrHdr->u.NonRes.cbAllocated) < pThis->cbMftRecord * 16U
+ || (uint64_t)RT_LE2H_U64(pAttrHdr->u.NonRes.cbAllocated) >= pThis->cbBacking)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "MFT record #0 unnamed DATA attribute allocated size is out of range: %#RX64",
+ RT_LE2H_U64(pAttrHdr->u.NonRes.cbAllocated));
+ else if ( (uint64_t)RT_LE2H_U64(pAttrHdr->u.NonRes.cbInitialized) < pThis->cbMftRecord * 16U
+ || (uint64_t)RT_LE2H_U64(pAttrHdr->u.NonRes.cbInitialized) >= pThis->cbBacking)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "MFT record #0 unnamed DATA attribute initialized size is out of range: %#RX64",
+ RT_LE2H_U64(pAttrHdr->u.NonRes.cbInitialized));
+ else if ( (uint64_t)RT_LE2H_U64(pAttrHdr->u.NonRes.cbData) < pThis->cbMftRecord * 16U
+ || (uint64_t)RT_LE2H_U64(pAttrHdr->u.NonRes.cbData) >= pThis->cbBacking)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "MFT record #0 unnamed DATA attribute allocated size is out of range: %#RX64",
+ RT_LE2H_U64(pAttrHdr->u.NonRes.cbData));
+ else if (pAttrHdr->u.NonRes.uCompressionUnit != 0)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "MFT record #0 unnamed DATA attribute is compressed: %#x",
+ pAttrHdr->u.NonRes.uCompressionUnit);
+ else if (pMftData->Extents.cExtents == 0)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "MFT record #0 unnamed DATA attribute has no data on the disk");
+ else if (pMftData->Extents.paExtents[0].off != offDisk)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "MFT record #0 unnamed DATA attribute has a bogus disk offset: %#RX64, expected %#RX64",
+ pMftData->Extents.paExtents[0].off, offDisk);
+ else if (!rtFsNtfsCore_FindUnnamedAttribute(pCore, NTFS_AT_BITMAP))
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "MFT record #0 has no unnamed BITMAP attribute!");
+ else
+ {
+ PRTFSNTFSATTR pFilenameAttr = rtFsNtfsCore_FindUnnamedAttribute(pCore, NTFS_AT_FILENAME);
+ if (!pFilenameAttr)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "MFT record #0 has no FILENAME attribute!");
+ else if (pFilenameAttr->pAttrHdr->fNonResident)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "MFT record #0 FILENAME attribute is non-resident!");
+ else if (pFilenameAttr->pAttrHdr->u.Res.cbValue < RT_UOFFSETOF(NTFSATFILENAME, wszFilename[4]))
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "MFT record #0 FILENAME attribute value size is too small: %#x",
+ pFilenameAttr->pAttrHdr->u.Res.cbValue);
+ else
+ {
+ PNTFSATFILENAME pFilename = (PNTFSATFILENAME)( (uint8_t *)pFilenameAttr->pAttrHdr
+ + pFilenameAttr->pAttrHdr->u.Res.offValue);
+ if ( pFilename->cwcFilename != 4
+ || RTUtf16NICmpAscii(pFilename->wszFilename, "$Mft", 4) != 0)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "MFT record #0 FILENAME isn't '$Mft': '%.*ls'",
+ pFilename->cwcFilename, pFilename->wszFilename);
+ else
+ {
+ /*
+ * Looks like we're good. Insert core record into the cache.
+ */
+ RTListAppend(&pThis->CoreInUseHead, &pCore->ListEntry);
+ pThis->cbCoreObjects += pCore->cbCost;
+
+ Assert(pCore->cRefs == 1);
+ Assert(pRec->cRefs == 2);
+ rtFsNtfsMftRec_Release(pRec, pThis);
+
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ pThis->pMftData = NULL;
+ }
+ else
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "MFT record #0 has no unnamed DATA attribute!");
+ }
+ if (pRec->pCore)
+ rtFsNtfsCore_Destroy(pRec->pCore);
+ rtFsNtfsMftRec_Release(pRec, pThis);
+ }
+ else
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, rc, "Error reading MFT record #0");
+ return rc;
+}
+
+
+/**
+ * Loads the bootsector and parses it, copying values into the instance data.
+ *
+ * @returns IRPT status code.
+ * @param pThis The instance data.
+ * @param pvBuf The buffer.
+ * @param cbBuf The buffer size.
+ * @param pErrInfo Where to return additional error details.
+ */
+static int rtFsNtfsVolLoadAndParseBootsector(PRTFSNTFSVOL pThis, void *pvBuf, size_t cbBuf, PRTERRINFO pErrInfo)
+{
+ AssertReturn(cbBuf >= sizeof(FATBOOTSECTOR), VERR_INTERNAL_ERROR_2);
+
+ /*
+ * Read the boot sector and check that it makes sense for a NTFS volume.
+ *
+ * Note! There are two potential backup locations of the boot sector, however we
+ * currently don't implement falling back on these on corruption/read errors.
+ */
+ PFATBOOTSECTOR pBootSector = (PFATBOOTSECTOR)pvBuf;
+ int rc = RTVfsFileReadAt(pThis->hVfsBacking, 0, pBootSector, sizeof(*pBootSector), NULL);
+ if (RT_FAILURE(rc))
+ return RTERRINFO_LOG_SET(pErrInfo, rc, "Error reading boot sector");
+
+ if (memcmp(pBootSector->achOemName, RT_STR_TUPLE(NTFS_OEM_ID_MAGIC)) != 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
+ "Not NTFS - OEM field mismatch: %.8Rhxs'", pBootSector->achOemName);
+
+ /* Check must-be-zero BPB fields. */
+ if (pBootSector->Bpb.Ntfs.Bpb.cReservedSectors != 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "Not NTFS - MBZ: BPB.cReservedSectors=%u",
+ RT_LE2H_U16(pBootSector->Bpb.Ntfs.Bpb.cReservedSectors));
+ if (pBootSector->Bpb.Ntfs.Bpb.cFats != 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "Not NTFS - MBZ: BPB.cFats=%u",
+ pBootSector->Bpb.Ntfs.Bpb.cFats);
+ if (pBootSector->Bpb.Ntfs.Bpb.cMaxRootDirEntries != 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "Not NTFS - MBZ: BPB.cMaxRootDirEntries=%u",
+ RT_LE2H_U16(pBootSector->Bpb.Ntfs.Bpb.cMaxRootDirEntries));
+ if (pBootSector->Bpb.Ntfs.Bpb.cTotalSectors16 != 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "Not NTFS - MBZ: BPB.cTotalSectors16=%u",
+ RT_LE2H_U16(pBootSector->Bpb.Ntfs.Bpb.cTotalSectors16));
+ if (pBootSector->Bpb.Ntfs.Bpb.cSectorsPerFat != 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "Not NTFS - MBZ: BPB.cSectorsPerFat=%u",
+ RT_LE2H_U16(pBootSector->Bpb.Ntfs.Bpb.cSectorsPerFat));
+
+ /* Check other relevant BPB fields. */
+ uint32_t cbSector = RT_LE2H_U16(pBootSector->Bpb.Ntfs.Bpb.cbSector);
+ if ( cbSector != 512
+ && cbSector != 1024
+ && cbSector != 2048
+ && cbSector != 4096)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "Not NTFS - BPB.cbSector is ouf of range: %u", cbSector);
+ pThis->cbSector = cbSector;
+ Log2(("NTFS BPB: cbSector=%#x\n", cbSector));
+
+ uint32_t cClusterPerSector = RT_LE2H_U16(pBootSector->Bpb.Ntfs.Bpb.cSectorsPerCluster);
+ if ( !RT_IS_POWER_OF_TWO(cClusterPerSector)
+ || cClusterPerSector == 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
+ "Not NTFS - BPB.cCluster is ouf of range: %u", cClusterPerSector);
+
+ pThis->cbCluster = cClusterPerSector * cbSector;
+ if (pThis->cbCluster > _64K)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "cluster size exceeds 64KB: %#x", pThis->cbCluster);
+ pThis->cClusterShift = ASMBitFirstSetU32(pThis->cbCluster) - 1;
+ Log2(("NTFS BPB: cClusterPerSector=%#x => %#x bytes, %u shift\n", cClusterPerSector, pThis->cbCluster, pThis->cClusterShift));
+ pThis->iMaxVirtualCluster = (uint64_t)INT64_MAX >> pThis->cClusterShift;
+ Log2(("NTFS BPB: iMaxVirtualCluster=%#RX64\n", pThis->iMaxVirtualCluster));
+
+ /* NTFS BPB: cSectors. */
+ uint64_t cSectors = RT_LE2H_U64(pBootSector->Bpb.Ntfs.cSectors);
+ if (cSectors > pThis->cbBacking / pThis->cbSector)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "NTFS sector count exceeds volume size: %#RX64 vs %#RX64",
+ cSectors, pThis->cbBacking / pThis->cbSector);
+ if (cSectors < 256)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "NTFS sector count too small: %#RX64", cSectors);
+ pThis->cbVolume = cSectors * pThis->cbSector;
+ pThis->cClusters = cSectors / cClusterPerSector;
+ Log2(("NTFS BPB: cSectors=%#RX64 => %#xu bytes (%Rhcb) => cClusters=%#RX64\n",
+ cSectors, pThis->cbVolume, pThis->cbVolume, pThis->cClusters));
+
+ /* NTFS BPB: MFT location. */
+ uint64_t uLcn = RT_LE2H_U64(pBootSector->Bpb.Ntfs.uLcnMft);
+ if ( uLcn < 1
+ || uLcn >= pThis->cClusters)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "NTFS MFT location is out of bounds: %#RX64 (%#RX64 clusters)", uLcn, pThis->cClusters);
+ pThis->uLcnMft = uLcn;
+ Log2(("NTFS BPB: uLcnMft=%#RX64 (byte offset %#RX64)\n", uLcn, uLcn << pThis->cClusterShift));
+
+ /* NTFS BPB: Mirror MFT location. */
+ uLcn = RT_LE2H_U64(pBootSector->Bpb.Ntfs.uLcnMftMirror);
+ if ( uLcn < 1
+ || uLcn >= pThis->cClusters)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "NTFS mirror MFT location is out of bounds: %#RX64 (%#RX64 clusters)", uLcn, pThis->cClusters);
+ pThis->uLcnMftMirror = uLcn;
+ Log2(("NTFS BPB: uLcnMftMirror=%#RX64 (byte offset %#RX64)\n", uLcn, uLcn << pThis->cClusterShift));
+
+ /* NTFS BPB: Size of MFT file record. */
+ if (pBootSector->Bpb.Ntfs.cClustersPerMftRecord >= 0)
+ {
+ if ( !RT_IS_POWER_OF_TWO((uint32_t)pBootSector->Bpb.Ntfs.cClustersPerMftRecord)
+ || pBootSector->Bpb.Ntfs.cClustersPerMftRecord == 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "NTFS clusters-per-mft-record value is zero or not a power of two: %#x",
+ pBootSector->Bpb.Ntfs.cClustersPerMftRecord);
+ pThis->cbMftRecord = (uint32_t)pBootSector->Bpb.Ntfs.cClustersPerMftRecord << pThis->cClusterShift;
+ Assert(pThis->cbMftRecord == pBootSector->Bpb.Ntfs.cClustersPerMftRecord * pThis->cbCluster);
+ }
+ else if ( pBootSector->Bpb.Ntfs.cClustersPerMftRecord < -20
+ || pBootSector->Bpb.Ntfs.cClustersPerMftRecord > -9)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "NTFS clusters-per-mft-record is out of shift range: %d",
+ pBootSector->Bpb.Ntfs.cClustersPerMftRecord);
+ else
+ pThis->cbMftRecord = UINT32_C(1) << -pBootSector->Bpb.Ntfs.cClustersPerMftRecord;
+ Log2(("NTFS BPB: cbMftRecord=%#x\n", pThis->cbMftRecord));
+ if ( pThis->cbMftRecord > _32K
+ || pThis->cbMftRecord < 256)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Unsupported NTFS MFT record size: %#x", pThis->cbMftRecord);
+
+ /* NTFS BPB: Default index node size */
+ if (pBootSector->Bpb.Ntfs.cClustersPerIndexNode >= 0)
+ {
+ if ( !RT_IS_POWER_OF_TWO((uint32_t)pBootSector->Bpb.Ntfs.cClustersPerIndexNode)
+ || pBootSector->Bpb.Ntfs.cClustersPerIndexNode == 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "NTFS default clusters-per-index-tree-node is zero or not a power of two: %#x",
+ pBootSector->Bpb.Ntfs.cClustersPerIndexNode);
+ pThis->cbDefaultIndexNode = (uint32_t)pBootSector->Bpb.Ntfs.cClustersPerIndexNode << pThis->cClusterShift;
+ Assert(pThis->cbDefaultIndexNode == pBootSector->Bpb.Ntfs.cClustersPerIndexNode * pThis->cbCluster);
+ }
+ else if ( pBootSector->Bpb.Ntfs.cClustersPerIndexNode < -32
+ || pBootSector->Bpb.Ntfs.cClustersPerIndexNode > -9)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "NTFS default clusters-per-index-tree-node is out of shift range: %d",
+ pBootSector->Bpb.Ntfs.cClustersPerIndexNode);
+ else
+ pThis->cbDefaultIndexNode = UINT32_C(1) << -pBootSector->Bpb.Ntfs.cClustersPerMftRecord;
+ Log2(("NTFS BPB: cbDefaultIndexNode=%#x\n", pThis->cbDefaultIndexNode));
+
+ pThis->uSerialNo = RT_LE2H_U64(pBootSector->Bpb.Ntfs.uSerialNumber);
+ Log2(("NTFS BPB: uSerialNo=%#x\n", pThis->uSerialNo));
+
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTFsNtfsVolOpen(RTVFSFILE hVfsFileIn, uint32_t fMntFlags, uint32_t fNtfsFlags, PRTVFS phVfs, PRTERRINFO pErrInfo)
+{
+ AssertPtrReturn(phVfs, VERR_INVALID_POINTER);
+ AssertReturn(!(fMntFlags & ~RTVFSMNT_F_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertReturn(!fNtfsFlags, VERR_INVALID_FLAGS);
+
+ uint32_t cRefs = RTVfsFileRetain(hVfsFileIn);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Create a VFS instance and initialize the data so rtFsNtfsVol_Close works.
+ */
+ RTVFS hVfs;
+ PRTFSNTFSVOL pThis;
+ int rc = RTVfsNew(&g_rtFsNtfsVolOps, sizeof(*pThis), NIL_RTVFS, RTVFSLOCK_CREATE_RW, &hVfs, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->hVfsBacking = hVfsFileIn;
+ pThis->hVfsSelf = hVfs;
+ pThis->fMntFlags = fMntFlags;
+ pThis->fNtfsFlags = fNtfsFlags;
+ RTListInit(&pThis->CoreInUseHead);
+ RTListInit(&pThis->CoreUnusedHead);
+ RTListInit(&pThis->IdxNodeUnusedHead);
+
+ rc = RTVfsFileQuerySize(pThis->hVfsBacking, &pThis->cbBacking);
+ if (RT_SUCCESS(rc))
+ {
+ void *pvBuf = RTMemTmpAlloc(_64K);
+ if (pvBuf)
+ {
+ rc = rtFsNtfsVolLoadAndParseBootsector(pThis, pvBuf, _64K, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFsNtfsVolLoadMft(pThis, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFsNtfsVolLoadVolumeInfo(pThis, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFsNtfsVolLoadBitmap(pThis, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFsNtfsVolLoadUpCase(pThis, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFsNtfsVolLoadRootDir(pThis, pErrInfo);
+ RTMemTmpFree(pvBuf);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfs = hVfs;
+ return VINF_SUCCESS;
+ }
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+ }
+
+ RTVfsRelease(hVfs);
+ *phVfs = NIL_RTVFS;
+ }
+ else
+ RTVfsFileRelease(hVfsFileIn);
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate}
+ */
+static DECLCALLBACK(int) rtVfsChainNtfsVol_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec,
+ PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg);
+
+ /*
+ * 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.
+ */
+ bool fReadOnly = (pSpec->fOpenFile & RTFILE_O_ACCESS_MASK) == RTFILE_O_READ;
+ if (pElement->cArgs > 0)
+ {
+ const char *psz = pElement->paArgs[0].psz;
+ if (*psz)
+ {
+ if (!strcmp(psz, "ro"))
+ fReadOnly = true;
+ else if (!strcmp(psz, "rw"))
+ fReadOnly = false;
+ else
+ {
+ *poffError = pElement->paArgs[0].offSpec;
+ return RTErrInfoSet(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Expected 'ro' or 'rw' as argument");
+ }
+ }
+ }
+
+ pElement->uProvider = fReadOnly ? RTVFSMNT_F_READ_ONLY : 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate}
+ */
+static DECLCALLBACK(int) rtVfsChainNtfsVol_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 = RTFsNtfsVolOpen(hVfsFileIn, (uint32_t)pElement->uProvider, (uint32_t)(pElement->uProvider >> 32), &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) rtVfsChainNtfsVol_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_rtVfsChainNtfsVolReg =
+{
+ /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION,
+ /* fReserved = */ 0,
+ /* pszName = */ "ntfs",
+ /* ListEntry = */ { NULL, NULL },
+ /* pszHelp = */ "Open a NTFS file system, requires a file object on the left side.\n"
+ "First argument is an optional 'ro' (read-only) or 'rw' (read-write) flag.\n",
+ /* pfnValidate = */ rtVfsChainNtfsVol_Validate,
+ /* pfnInstantiate = */ rtVfsChainNtfsVol_Instantiate,
+ /* pfnCanReuseElement = */ rtVfsChainNtfsVol_CanReuseElement,
+ /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION
+};
+
+RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainNtfsVolReg, rtVfsChainNtfsVolReg);
+
diff --git a/src/VBox/Runtime/common/fs/xfsvfs.cpp b/src/VBox/Runtime/common/fs/xfsvfs.cpp
new file mode 100644
index 00000000..8d430f1b
--- /dev/null
+++ b/src/VBox/Runtime/common/fs/xfsvfs.cpp
@@ -0,0 +1,2460 @@
+/* $Id: xfsvfs.cpp $ */
+/** @file
+ * IPRT - XFS Virtual Filesystem.
+ */
+
+/*
+ * Copyright (C) 2018-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 <iprt/fsvfs.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/avl.h>
+#include <iprt/file.h>
+#include <iprt/err.h>
+#include <iprt/list.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+#include <iprt/formats/xfs.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The maximum allocation group cache size (in bytes). */
+#if ARCH_BITS >= 64
+# define RTFSXFS_MAX_AG_CACHE_SIZE _512K
+#else
+# define RTFSXFS_MAX_AG_CACHE_SIZE _128K
+#endif
+/** The maximum inode cache size (in bytes). */
+#if ARCH_BITS >= 64
+# define RTFSXFS_MAX_INODE_CACHE_SIZE _512K
+#else
+# define RTFSXFS_MAX_INODE_CACHE_SIZE _128K
+#endif
+/** The maximum extent tree cache size (in bytes). */
+#if ARCH_BITS >= 64
+# define RTFSXFS_MAX_BLOCK_CACHE_SIZE _512K
+#else
+# define RTFSXFS_MAX_BLOCK_CACHE_SIZE _128K
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to the XFS filesystem data. */
+typedef struct RTFSXFSVOL *PRTFSXFSVOL;
+
+
+/**
+ * Cached allocation group descriptor data.
+ */
+typedef struct RTFSXFSAG
+{
+ /** AVL tree node, indexed by the allocation group number. */
+ AVLU32NODECORE Core;
+ /** List node for the LRU list used for eviction. */
+ RTLISTNODE NdLru;
+ /** Reference counter. */
+ volatile uint32_t cRefs;
+ /** @todo */
+} RTFSXFSAG;
+/** Pointer to allocation group descriptor data. */
+typedef RTFSXFSAG *PRTFSXFSAG;
+
+
+/**
+ * In-memory inode.
+ */
+typedef struct RTFSXFSINODE
+{
+ /** AVL tree node, indexed by the inode number. */
+ AVLU64NODECORE Core;
+ /** List node for the inode LRU list used for eviction. */
+ RTLISTNODE NdLru;
+ /** Reference counter. */
+ volatile uint32_t cRefs;
+ /** Byte offset in the backing file where the inode is stored.. */
+ uint64_t offInode;
+ /** Inode data. */
+ RTFSOBJINFO ObjInfo;
+ /** Inode data fork format. */
+ uint8_t enmFormat;
+ /** Inode flags. */
+ uint16_t fFlags;
+ /** Inode version. */
+ uint8_t uVersion;
+ /** Number of extents in the data fork for XFS_INODE_FORMAT_EXTENTS. */
+ uint32_t cExtentsData;
+ /** Raw inode data. */
+ uint8_t abData[1];
+} RTFSXFSINODE;
+/** Pointer to an in-memory inode. */
+typedef RTFSXFSINODE *PRTFSXFSINODE;
+
+
+/**
+ * Block cache entry.
+ */
+typedef struct RTFSXFSBLOCKENTRY
+{
+ /** AVL tree node, indexed by the filesystem block number. */
+ AVLU64NODECORE Core;
+ /** List node for the inode LRU list used for eviction. */
+ RTLISTNODE NdLru;
+ /** Reference counter. */
+ volatile uint32_t cRefs;
+ /** The block data. */
+ uint8_t abData[1];
+} RTFSXFSBLOCKENTRY;
+/** Pointer to a block cache entry. */
+typedef RTFSXFSBLOCKENTRY *PRTFSXFSBLOCKENTRY;
+
+
+/**
+ * Open directory instance.
+ */
+typedef struct RTFSXFSDIR
+{
+ /** Volume this directory belongs to. */
+ PRTFSXFSVOL pVol;
+ /** The underlying inode structure. */
+ PRTFSXFSINODE pInode;
+ /** Set if we've reached the end of the directory enumeration. */
+ bool fNoMoreFiles;
+ /** Current offset into the directory where the next entry should be read. */
+ uint64_t offEntry;
+ /** Next entry index (for logging purposes). */
+ uint32_t idxEntry;
+} RTFSXFSDIR;
+/** Pointer to an open directory instance. */
+typedef RTFSXFSDIR *PRTFSXFSDIR;
+
+
+/**
+ * Open file instance.
+ */
+typedef struct RTFSXFSFILE
+{
+ /** Volume this directory belongs to. */
+ PRTFSXFSVOL pVol;
+ /** The underlying inode structure. */
+ PRTFSXFSINODE pInode;
+ /** Current offset into the file for I/O. */
+ RTFOFF offFile;
+} RTFSXFSFILE;
+/** Pointer to an open file instance. */
+typedef RTFSXFSFILE *PRTFSXFSFILE;
+
+
+/**
+ * XFS filesystem volume.
+ */
+typedef struct RTFSXFSVOL
+{
+ /** Handle to itself. */
+ RTVFS hVfsSelf;
+ /** The file, partition, or whatever backing the ext volume. */
+ RTVFSFILE hVfsBacking;
+ /** The size of the backing thingy. */
+ uint64_t cbBacking;
+
+ /** RTVFSMNT_F_XXX. */
+ uint32_t fMntFlags;
+ /** RTFSXFSVFS_F_XXX (currently none defined). */
+ uint32_t fXfsFlags;
+
+ /** Size of one sector. */
+ size_t cbSector;
+ /** Size of one block. */
+ size_t cbBlock;
+ /** Number of bits to shift for converting a block number to byte offset. */
+ uint32_t cBlockShift;
+ /** Number of blocks per allocation group. */
+ XFSAGNUMBER cBlocksPerAg;
+ /** Number of blocks per allocation group as log2. */
+ uint32_t cAgBlocksLog;
+ /** Number of allocation groups for this volume. */
+ uint32_t cAgs;
+ /** inode of the root directory. */
+ XFSINO uInodeRoot;
+ /** Inode size in bytes. */
+ size_t cbInode;
+ /** Number of inodes per block. */
+ uint32_t cInodesPerBlock;
+ /** Number of inodes per block as log2. */
+ uint32_t cInodesPerBlockLog;
+
+ /** @name Allocation group cache.
+ * @{ */
+ /** LRU list anchor. */
+ RTLISTANCHOR LstAgLru;
+ /** Root of the cached allocation group tree. */
+ AVLU32TREE AgRoot;
+ /** Size of the cached allocation groups. */
+ size_t cbAgs;
+ /** @} */
+
+ /** @name Inode cache.
+ * @{ */
+ /** LRU list anchor for the inode cache. */
+ RTLISTANCHOR LstInodeLru;
+ /** Root of the cached inode tree. */
+ AVLU64TREE InodeRoot;
+ /** Size of the cached inodes. */
+ size_t cbInodes;
+ /** @} */
+
+ /** @name Block cache.
+ * @{ */
+ /** LRU list anchor for the block cache. */
+ RTLISTANCHOR LstBlockLru;
+ /** Root of the cached block tree. */
+ AVLU64TREE BlockRoot;
+ /** Size of cached blocks. */
+ size_t cbBlocks;
+ /** @} */
+} RTFSXFSVOL;
+
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int rtFsXfsVol_OpenDirByInode(PRTFSXFSVOL pThis, uint32_t iInode, PRTVFSDIR phVfsDir);
+
+#ifdef LOG_ENABLED
+/**
+ * Logs the XFS filesystem superblock.
+ *
+ * @param iAg The allocation group number for the given super block.
+ * @param pSb Pointer to the superblock.
+ */
+static void rtFsXfsSb_Log(uint32_t iAg, PCXFSSUPERBLOCK pSb)
+{
+ if (LogIs2Enabled())
+ {
+ Log2(("XFS: Superblock %#RX32:\n", iAg));
+ Log2(("XFS: u32Magic %#RX32\n", RT_BE2H_U32(pSb->u32Magic)));
+ Log2(("XFS: cbBlock %RU32\n", RT_BE2H_U32(pSb->cbBlock)));
+ Log2(("XFS: cBlocks %RU64\n", RT_BE2H_U64(pSb->cBlocks)));
+ Log2(("XFS: cBlocksRtDev %RU64\n", RT_BE2H_U64(pSb->cBlocksRtDev)));
+ Log2(("XFS: cExtentsRtDev %RU64\n", RT_BE2H_U64(pSb->cExtentsRtDev)));
+ Log2(("XFS: abUuid <todo>\n"));
+ Log2(("XFS: uBlockJournal %#RX64\n", RT_BE2H_U64(pSb->uBlockJournal)));
+ Log2(("XFS: uInodeRoot %#RX64\n", RT_BE2H_U64(pSb->uInodeRoot)));
+ Log2(("XFS: uInodeBitmapRtExt %#RX64\n", RT_BE2H_U64(pSb->uInodeBitmapRtExt)));
+ Log2(("XFS: uInodeBitmapSummary %#RX64\n", RT_BE2H_U64(pSb->uInodeBitmapSummary)));
+ Log2(("XFS: cRtExtent %RU32\n", RT_BE2H_U32(pSb->cRtExtent)));
+ Log2(("XFS: cAgBlocks %RU32\n", RT_BE2H_U32(pSb->cAgBlocks)));
+ Log2(("XFS: cAg %RU32\n", RT_BE2H_U32(pSb->cAg)));
+ Log2(("XFS: cRtBitmapBlocks %RU32\n", RT_BE2H_U32(pSb->cRtBitmapBlocks)));
+ Log2(("XFS: cJournalBlocks %RU32\n", RT_BE2H_U32(pSb->cJournalBlocks)));
+ Log2(("XFS: fVersion %#RX16%s%s%s%s%s%s%s%s%s%s%s\n", RT_BE2H_U16(pSb->fVersion),
+ RT_BE2H_U16(pSb->fVersion) & XFS_SB_VERSION_F_ATTR ? " attr" : "",
+ RT_BE2H_U16(pSb->fVersion) & XFS_SB_VERSION_F_NLINK ? " nlink" : "",
+ RT_BE2H_U16(pSb->fVersion) & XFS_SB_VERSION_F_QUOTA ? " quota" : "",
+ RT_BE2H_U16(pSb->fVersion) & XFS_SB_VERSION_F_ALIGN ? " align" : "",
+ RT_BE2H_U16(pSb->fVersion) & XFS_SB_VERSION_F_DALIGN ? " dalign" : "",
+ RT_BE2H_U16(pSb->fVersion) & XFS_SB_VERSION_F_SHARED ? " shared" : "",
+ RT_BE2H_U16(pSb->fVersion) & XFS_SB_VERSION_F_LOGV2 ? " logv2" : "",
+ RT_BE2H_U16(pSb->fVersion) & XFS_SB_VERSION_F_SECTOR ? " sector" : "",
+ RT_BE2H_U16(pSb->fVersion) & XFS_SB_VERSION_F_EXTFLG ? " extflg" : "",
+ RT_BE2H_U16(pSb->fVersion) & XFS_SB_VERSION_F_DIRV2 ? " dirv2" : "",
+ RT_BE2H_U16(pSb->fVersion) & XFS_SB_VERSION_F_FEAT2 ? " feat2" : ""));
+ Log2(("XFS: cbSector %RU16\n", RT_BE2H_U16(pSb->cbSector)));
+ Log2(("XFS: cbInode %RU16\n", RT_BE2H_U16(pSb->cbInode)));
+ Log2(("XFS: cIndoesPerBlock %RU16\n", RT_BE2H_U16(pSb->cInodesPerBlock)));
+ Log2(("XFS: achFsName %12s\n", &pSb->achFsName[0]));
+ Log2(("XFS: cBlockSzLog %RU8\n", pSb->cBlockSzLog));
+ Log2(("XFS: cSectorSzLog %RU8\n", pSb->cSectorSzLog));
+ Log2(("XFS: cInodeSzLog %RU8\n", pSb->cInodeSzLog));
+ Log2(("XFS: cInodesPerBlockLog %RU8\n", pSb->cInodesPerBlockLog));
+ Log2(("XFS: cAgBlocksLog %RU8\n", pSb->cAgBlocksLog));
+ Log2(("XFS: cExtentsRtDevLog %RU8\n", pSb->cExtentsRtDevLog));
+ Log2(("XFS: fInProgress %RU8\n", pSb->fInProgress));
+ Log2(("XFS: cInodeMaxPct %RU8\n", pSb->cInodeMaxPct));
+ Log2(("XFS: cInodesGlobal %#RX64\n", RT_BE2H_U64(pSb->cInodesGlobal)));
+ Log2(("XFS: cInodesGlobalFree %#RX64\n", RT_BE2H_U64(pSb->cInodesGlobalFree)));
+ Log2(("XFS: cBlocksFree %#RX64\n", RT_BE2H_U64(pSb->cBlocksFree)));
+ Log2(("XFS: cExtentsRtFree %#RX64\n", RT_BE2H_U64(pSb->cExtentsRtFree)));
+ Log2(("XFS: uInodeQuotaUsr %#RX64\n", RT_BE2H_U64(pSb->uInodeQuotaUsr)));
+ Log2(("XFS: uInodeQuotaGrp %#RX64\n", RT_BE2H_U64(pSb->uInodeQuotaGrp)));
+ Log2(("XFS: fQuotaFlags %#RX16\n", RT_BE2H_U16(pSb->fQuotaFlags)));
+ Log2(("XFS: fFlagsMisc %#RX8\n", pSb->fFlagsMisc));
+ Log2(("XFS: uSharedVn %#RX8\n", pSb->uSharedVn));
+ Log2(("XFS: cBlocksInodeAlignment %#RX32\n", RT_BE2H_U32(pSb->cBlocksInodeAlignment)));
+ Log2(("XFS: cBlocksRaidStripe %#RX32\n", RT_BE2H_U32(pSb->cBlocksRaidStripe)));
+ Log2(("XFS: cBlocksRaidWidth %#RX32\n", RT_BE2H_U32(pSb->cBlocksRaidWidth)));
+ Log2(("XFS: cDirBlockAllocLog %RU8\n", pSb->cDirBlockAllocLog));
+ Log2(("XFS: cLogDevSubVolSectorSzLog %RU8\n", pSb->cLogDevSubVolSectorSzLog));
+ Log2(("XFS: cLogDevSectorSzLog %RU16\n", RT_BE2H_U16(pSb->cLogDevSectorSzLog)));
+ Log2(("XFS: cLogDevRaidStripe %RU32\n", RT_BE2H_U32(pSb->cLogDevRaidStripe)));
+ Log2(("XFS: fFeatures2 %#RX32\n", RT_BE2H_U32(pSb->fFeatures2)));
+ Log2(("XFS: fFeaturesRw %#RX32\n", RT_BE2H_U32(pSb->fFeaturesRw)));
+ Log2(("XFS: fFeaturesRo %#RX32\n", RT_BE2H_U32(pSb->fFeaturesRo)));
+ Log2(("XFS: fFeaturesIncompatRw %#RX32\n", RT_BE2H_U32(pSb->fFeaturesIncompatRw)));
+ Log2(("XFS: fFeaturesJrnlIncompatRw %#RX32\n", RT_BE2H_U32(pSb->fFeaturesJrnlIncompatRw)));
+ Log2(("XFS: u32Chksum %#RX32\n", RT_BE2H_U32(pSb->u32Chksum)));
+ Log2(("XFS: u32SparseInodeAlignment %#RX32\n", RT_BE2H_U32(pSb->u32SparseInodeAlignment)));
+ Log2(("XFS: uInodeProjectQuota %#RX64\n", RT_BE2H_U64(pSb->uInodeProjectQuota)));
+ Log2(("XFS: uJrnlSeqSbUpdate %#RX64\n", RT_BE2H_U64(pSb->uJrnlSeqSbUpdate)));
+ Log2(("XFS: abUuidMeta <todo>\n"));
+ Log2(("XFS: uInodeRm %#RX64\n", RT_BE2H_U64(pSb->uInodeRm)));
+ }
+}
+
+#if 0 /* unused */
+/**
+ * Logs a AG free space block.
+ *
+ * @param iAg The allocation group number for the given free space block.
+ * @param pAgf The AG free space block.
+ */
+static void rtFsXfsAgf_Log(uint32_t iAg, PCXFSAGF pAgf)
+{
+ if (LogIs2Enabled())
+ {
+ Log2(("XFS: AGF %#RX32:\n", iAg));
+ Log2(("XFS: u32Magic %#RX32\n", RT_BE2H_U32(pAgf->u32Magic)));
+ Log2(("XFS: uVersion %#RX32\n", RT_BE2H_U32(pAgf->uVersion)));
+ Log2(("XFS: uSeqNo %#RX32\n", RT_BE2H_U32(pAgf->uSeqNo)));
+ Log2(("XFS: cLengthBlocks %#RX32\n", RT_BE2H_U32(pAgf->cLengthBlocks)));
+ Log2(("XFS: auRoots[0] %#RX32\n", RT_BE2H_U32(pAgf->auRoots[0])));
+ Log2(("XFS: auRoots[1] %#RX32\n", RT_BE2H_U32(pAgf->auRoots[1])));
+ Log2(("XFS: auRoots[2] %#RX32\n", RT_BE2H_U32(pAgf->auRoots[2])));
+ Log2(("XFS: acLvls[0] %RU32\n", RT_BE2H_U32(pAgf->acLvls[0])));
+ Log2(("XFS: acLvls[1] %RU32\n", RT_BE2H_U32(pAgf->acLvls[1])));
+ Log2(("XFS: acLvls[2] %RU32\n", RT_BE2H_U32(pAgf->acLvls[2])));
+ Log2(("XFS: idxFreeListFirst %RU32\n", RT_BE2H_U32(pAgf->idxFreeListFirst)));
+ Log2(("XFS: idxFreeListLast %RU32\n", RT_BE2H_U32(pAgf->idxFreeListLast)));
+ Log2(("XFS: cFreeListBlocks %RU32\n", RT_BE2H_U32(pAgf->cFreeListBlocks)));
+ Log2(("XFS: cFreeBlocks %RU32\n", RT_BE2H_U32(pAgf->cFreeBlocks)));
+ Log2(("XFS: cFreeBlocksLongest %RU32\n", RT_BE2H_U32(pAgf->cFreeBlocksLongest)));
+ Log2(("XFS: cBlocksBTrees %RU32\n", RT_BE2H_U32(pAgf->cBlocksBTrees)));
+ Log2(("XFS: abUuid <todo>\n"));
+ Log2(("XFS: cBlocksRevMap %RU32\n", RT_BE2H_U32(pAgf->cBlocksRevMap)));
+ Log2(("XFS: cBlocksRefcountBTree %RU32\n", RT_BE2H_U32(pAgf->cBlocksRefcountBTree)));
+ Log2(("XFS: uRootRefcount %#RX32\n", RT_BE2H_U32(pAgf->uRootRefcount)));
+ Log2(("XFS: cLvlRefcount %RU32\n", RT_BE2H_U32(pAgf->cLvlRefcount)));
+ Log2(("XFS: uSeqNoLastWrite %#RX64\n", RT_BE2H_U64(pAgf->uSeqNoLastWrite)));
+ Log2(("XFS: uChkSum %#RX32\n", RT_BE2H_U32(pAgf->uChkSum)));
+ }
+}
+#endif
+
+/**
+ * Loads an AG inode information block.
+ *
+ * @param iAg The allocation group number for the given inode information block.
+ * @param pAgi The AG inode information block.
+ */
+static void rtFsXfsAgi_Log(uint32_t iAg, PCXFSAGI pAgi)
+{
+ if (LogIs2Enabled())
+ {
+ Log2(("XFS: AGI %#RX32:\n", iAg));
+ Log2(("XFS: u32Magic %#RX32\n", RT_BE2H_U32(pAgi->u32Magic)));
+ Log2(("XFS: uVersion %#RX32\n", RT_BE2H_U32(pAgi->uVersion)));
+ Log2(("XFS: uSeqNo %#RX32\n", RT_BE2H_U32(pAgi->uSeqNo)));
+ Log2(("XFS: cLengthBlocks %#RX32\n", RT_BE2H_U32(pAgi->cLengthBlocks)));
+ Log2(("XFS: cInodesAlloc %#RX32\n", RT_BE2H_U32(pAgi->cInodesAlloc)));
+ Log2(("XFS: uRootInode %#RX32\n", RT_BE2H_U32(pAgi->uRootInode)));
+ Log2(("XFS: cLvlsInode %RU32\n", RT_BE2H_U32(pAgi->cLvlsInode)));
+ Log2(("XFS: uInodeNew %#RX32\n", RT_BE2H_U32(pAgi->uInodeNew)));
+ Log2(("XFS: uInodeDir %#RX32\n", RT_BE2H_U32(pAgi->uInodeDir)));
+ Log2(("XFS: au32HashUnlinked[0..63] <todo>\n"));
+ Log2(("XFS: abUuid <todo>\n"));
+ Log2(("XFS: uChkSum %#RX32\n", RT_BE2H_U32(pAgi->uChkSum)));
+ Log2(("XFS: uSeqNoLastWrite %#RX64\n", RT_BE2H_U64(pAgi->uSeqNoLastWrite)));
+ Log2(("XFS: uRootFreeInode %#RX32\n", RT_BE2H_U32(pAgi->uRootFreeInode)));
+ Log2(("XFS: cLvlsFreeInode %RU32\n", RT_BE2H_U32(pAgi->cLvlsFreeInode)));
+ }
+}
+
+
+/**
+ * Logs a XFS filesystem inode.
+ *
+ * @param pThis The XFS volume instance.
+ * @param iInode Inode number.
+ * @param pInode Pointer to the inode.
+ */
+static void rtFsXfsInode_Log(PRTFSXFSVOL pThis, XFSINO iInode, PCXFSINODECORE pInode)
+{
+ RT_NOREF(pThis);
+
+ if (LogIs2Enabled())
+ {
+ RTTIMESPEC Spec;
+ char sz[80];
+
+ Log2(("XFS: Inode %#RX64:\n", iInode));
+ Log2(("XFS: u16Magic %#RX16\n", RT_BE2H_U16(pInode->u16Magic)));
+ Log2(("XFS: fMode %#RX16\n", RT_BE2H_U16(pInode->fMode)));
+ Log2(("XFS: iVersion %#RX8\n", pInode->iVersion));
+ Log2(("XFS: enmFormat %#RX8\n", pInode->enmFormat));
+ Log2(("XFS: cOnLinks %RU16\n", RT_BE2H_U16(pInode->cOnLinks)));
+ Log2(("XFS: uUid %#RX32\n", RT_BE2H_U32(pInode->uUid)));
+ Log2(("XFS: uGid %#RX32\n", RT_BE2H_U32(pInode->uGid)));
+ Log2(("XFS: cLinks %#RX32\n", RT_BE2H_U32(pInode->cLinks)));
+ Log2(("XFS: uProjIdLow %#RX16\n", RT_BE2H_U16(pInode->uProjIdLow)));
+ Log2(("XFS: uProjIdHigh %#RX16\n", RT_BE2H_U16(pInode->uProjIdHigh)));
+ Log2(("XFS: cFlush %RU16\n", RT_BE2H_U16(pInode->cFlush)));
+ Log2(("XFS: TsLastAccessed %#RX32:%#RX32 %s\n", RT_BE2H_U32(pInode->TsLastAccessed.cSecEpoch),
+ RT_BE2H_U32(pInode->TsLastAccessed.cNanoSec),
+ RTTimeSpecToString(RTTimeSpecAddNano(RTTimeSpecSetSeconds(&Spec, RT_BE2H_U32(pInode->TsLastAccessed.cSecEpoch)),
+ RT_BE2H_U32(pInode->TsLastAccessed.cNanoSec)),
+ sz, sizeof(sz))));
+ Log2(("XFS: TsLastModified %#RX32:%#RX32 %s\n", RT_BE2H_U32(pInode->TsLastModified.cSecEpoch),
+ RT_BE2H_U32(pInode->TsLastModified.cNanoSec),
+ RTTimeSpecToString(RTTimeSpecAddNano(RTTimeSpecSetSeconds(&Spec, RT_BE2H_U32(pInode->TsLastModified.cSecEpoch)),
+ RT_BE2H_U32(pInode->TsLastModified.cNanoSec)),
+ sz, sizeof(sz))));
+ Log2(("XFS: TsCreatedModified %#RX32:%#RX32 %s\n", RT_BE2H_U32(pInode->TsCreatedModified.cSecEpoch),
+ RT_BE2H_U32(pInode->TsCreatedModified.cNanoSec),
+ RTTimeSpecToString(RTTimeSpecAddNano(RTTimeSpecSetSeconds(&Spec, RT_BE2H_U32(pInode->TsCreatedModified.cSecEpoch)),
+ RT_BE2H_U32(pInode->TsCreatedModified.cNanoSec)),
+ sz, sizeof(sz))));
+ Log2(("XFS: cbInode %#RX64\n", RT_BE2H_U64(pInode->cbInode)));
+ Log2(("XFS: cBlocks %#RX64\n", RT_BE2H_U64(pInode->cBlocks)));
+ Log2(("XFS: cExtentBlocksMin %#RX32\n", RT_BE2H_U32(pInode->cExtentBlocksMin)));
+ Log2(("XFS: cExtentsData %#RX32\n", RT_BE2H_U32(pInode->cExtentsData)));
+ Log2(("XFS: cExtentsAttr %#RX16\n", RT_BE2H_U16(pInode->cExtentsAttr)));
+ Log2(("XFS: offAttrFork %#RX8\n", pInode->offAttrFork));
+ Log2(("XFS: enmFormatAttr %#RX8\n", pInode->enmFormatAttr));
+ Log2(("XFS: fEvtMaskDmig %#RX32\n", RT_BE2H_U32(pInode->fEvtMaskDmig)));
+ Log2(("XFS: uStateDmig %#RX16\n", RT_BE2H_U16(pInode->uStateDmig)));
+ Log2(("XFS: fFlags %#RX16\n", RT_BE2H_U16(pInode->fFlags)));
+ Log2(("XFS: cGeneration %#RX32\n", RT_BE2H_U32(pInode->cGeneration)));
+ Log2(("XFS: offBlockUnlinkedNext %#RX32\n", RT_BE2H_U32(pInode->offBlockUnlinkedNext)));
+ Log2(("XFS: uChkSum %#RX32\n", RT_BE2H_U32(pInode->uChkSum)));
+ Log2(("XFS: cAttrChanges %#RX64\n", RT_BE2H_U64(pInode->cAttrChanges)));
+ Log2(("XFS: uFlushSeqNo %#RX64\n", RT_BE2H_U64(pInode->uFlushSeqNo)));
+ Log2(("XFS: fFlags2 %#RX64\n", RT_BE2H_U64(pInode->fFlags2)));
+ Log2(("XFS: cExtentCowMin %#RX32\n", RT_BE2H_U32(pInode->cExtentCowMin)));
+ Log2(("XFS: TsCreation %#RX32:%#RX32 %s\n", RT_BE2H_U32(pInode->TsCreation.cSecEpoch),
+ RT_BE2H_U32(pInode->TsCreation.cNanoSec),
+ RTTimeSpecToString(RTTimeSpecAddNano(RTTimeSpecSetSeconds(&Spec, RT_BE2H_U32(pInode->TsCreation.cSecEpoch)),
+ RT_BE2H_U32(pInode->TsCreation.cNanoSec)),
+ sz, sizeof(sz))));
+ Log2(("XFS: uInode %#RX64\n", RT_BE2H_U64(pInode->uInode)));
+ Log2(("XFS: abUuid <todo>\n"));
+ }
+}
+
+
+#if 0
+/**
+ * Logs a XFS filesystem directory entry.
+ *
+ * @param pThis The XFS volume instance.
+ * @param idxDirEntry Directory entry index number.
+ * @param pDirEntry The directory entry.
+ */
+static void rtFsXfsDirEntry_Log(PRTFSXFSVOL pThis, uint32_t idxDirEntry, PCXFSDIRENTRYEX pDirEntry)
+{
+ if (LogIs2Enabled())
+ {
+ }
+}
+#endif
+#endif
+
+
+/**
+ * Converts a block number to a byte offset.
+ *
+ * @returns Offset in bytes for the given block number.
+ * @param pThis The XFS volume instance.
+ * @param iBlock The block number to convert.
+ */
+DECLINLINE(uint64_t) rtFsXfsBlockIdxToDiskOffset(PRTFSXFSVOL pThis, uint64_t iBlock)
+{
+ return iBlock << pThis->cBlockShift;
+}
+
+
+/**
+ * Converts a byte offset to a block number.
+ *
+ * @returns Block number.
+ * @param pThis The XFS volume instance.
+ * @param iBlock The offset to convert.
+ */
+DECLINLINE(uint64_t) rtFsXfsDiskOffsetToBlockIdx(PRTFSXFSVOL pThis, uint64_t off)
+{
+ return off >> pThis->cBlockShift;
+}
+
+
+/**
+ * Splits the given absolute inode number into the AG number, block inside the AG
+ * and the offset into the block where to find the inode structure.
+ *
+ * @param pThis The XFS volume instance.
+ * @param iInode The inode to split.
+ * @param piAg Where to store the AG number.
+ * @param puBlock Where to store the block number inside the AG.
+ * @param poffBlock Where to store the offset into the block.
+ */
+DECLINLINE(void) rtFsXfsInodeSplitAbs(PRTFSXFSVOL pThis, XFSINO iInode,
+ uint32_t *piAg, uint32_t *puBlock,
+ uint32_t *poffBlock)
+{
+ *poffBlock = iInode & (pThis->cInodesPerBlock - 1);
+ iInode >>= pThis->cInodesPerBlockLog;
+ *puBlock = iInode & (RT_BIT_32(pThis->cAgBlocksLog) - 1); /* Using the log2 value here as it is rounded. */
+ iInode >>= RT_BIT_32(pThis->cAgBlocksLog) - 1;
+ *piAg = (uint32_t)iInode;
+}
+
+
+/**
+ * Returns the size of the core inode structure on disk for the given version.
+ *
+ * @returns Size of the on disk inode structure in bytes.
+ * @param uVersion The inode version.
+ */
+DECLINLINE(size_t) rtFsXfsInodeGetSz(uint8_t uVersion)
+{
+ if (uVersion < 3)
+ return RT_OFFSETOF(XFSINODECORE, uChkSum);
+ return sizeof(XFSINODECORE);
+}
+
+
+/**
+ * Returns the pointer to the data fork of the given inode.
+ *
+ * @returns Pointer to the data fork.
+ * @param pThis The XFS volume instance.
+ * @param pInode The inode to get the data fork for.
+ * @param pcb Where to store the size of the remaining data area beginning with the fork.
+ */
+DECLINLINE(void *) rtFsXfsInodeGetDataFork(PRTFSXFSVOL pThis, PRTFSXFSINODE pInode, size_t *pcb)
+{
+ size_t offDataFork = rtFsXfsInodeGetSz(pInode->uVersion);
+ size_t cbInodeData = pThis->cbInode - offDataFork;
+ if (pcb)
+ *pcb = cbInodeData;
+
+ return &pInode->abData[offDataFork];
+}
+
+
+/**
+ * Allocates a new block group.
+ *
+ * @returns Pointer to the new block group descriptor or NULL if out of memory.
+ * @param pThis The XFS volume instance.
+ * @param cbAlloc How much to allocate.
+ * @param iBlockGroup Block group number.
+ */
+static PRTFSXFSBLOCKENTRY rtFsXfsVol_BlockAlloc(PRTFSXFSVOL pThis, size_t cbAlloc, uint64_t iBlock)
+{
+ PRTFSXFSBLOCKENTRY pBlock = (PRTFSXFSBLOCKENTRY)RTMemAllocZ(cbAlloc);
+ if (RT_LIKELY(pBlock))
+ {
+ pBlock->Core.Key = iBlock;
+ pBlock->cRefs = 0;
+ pThis->cbBlocks += cbAlloc;
+ }
+
+ return pBlock;
+}
+
+
+/**
+ * Returns a new block entry utilizing the cache if possible.
+ *
+ * @returns Pointer to the new block entry or NULL if out of memory.
+ * @param pThis The XFS volume instance.
+ * @param iBlock Block number.
+ */
+static PRTFSXFSBLOCKENTRY rtFsXfsVol_BlockGetNew(PRTFSXFSVOL pThis, uint64_t iBlock)
+{
+ PRTFSXFSBLOCKENTRY pBlock = NULL;
+ size_t cbAlloc = RT_UOFFSETOF_DYN(RTFSXFSBLOCKENTRY, abData[pThis->cbBlock]);
+ if (pThis->cbBlocks + cbAlloc <= RTFSXFS_MAX_BLOCK_CACHE_SIZE)
+ pBlock = rtFsXfsVol_BlockAlloc(pThis, cbAlloc, iBlock);
+ else
+ {
+ pBlock = RTListRemoveLast(&pThis->LstBlockLru, RTFSXFSBLOCKENTRY, NdLru);
+ if (!pBlock)
+ pBlock = rtFsXfsVol_BlockAlloc(pThis, cbAlloc, iBlock);
+ else
+ {
+ /* Remove the block group from the tree because it gets a new key. */
+ PAVLU64NODECORE pCore = RTAvlU64Remove(&pThis->BlockRoot, pBlock->Core.Key);
+ Assert(pCore == &pBlock->Core); RT_NOREF(pCore);
+ }
+ }
+
+ Assert(!pBlock->cRefs);
+ pBlock->Core.Key = iBlock;
+ pBlock->cRefs = 1;
+
+ return pBlock;
+}
+
+
+/**
+ * Frees the given block.
+ *
+ * @param pThis The XFS volume instance.
+ * @param pBlock The block to free.
+ */
+static void rtFsXfsVol_BlockFree(PRTFSXFSVOL pThis, PRTFSXFSBLOCKENTRY pBlock)
+{
+ Assert(!pBlock->cRefs);
+
+ /*
+ * Put it into the cache if the limit wasn't exceeded, otherwise the block group
+ * is freed right away.
+ */
+ if (pThis->cbBlocks <= RTFSXFS_MAX_BLOCK_CACHE_SIZE)
+ {
+ /* Put onto the LRU list. */
+ RTListPrepend(&pThis->LstBlockLru, &pBlock->NdLru);
+ }
+ else
+ {
+ /* Remove from the tree and free memory. */
+ PAVLU64NODECORE pCore = RTAvlU64Remove(&pThis->BlockRoot, pBlock->Core.Key);
+ Assert(pCore == &pBlock->Core); RT_NOREF(pCore);
+ RTMemFree(pBlock);
+ pThis->cbBlocks -= RT_UOFFSETOF_DYN(RTFSXFSBLOCKENTRY, abData[pThis->cbBlock]);
+ }
+}
+
+
+/**
+ * Gets the specified block data from the volume.
+ *
+ * @returns IPRT status code.
+ * @param pThis The XFS volume instance.
+ * @param iBlock The filesystem block to load.
+ * @param ppBlock Where to return the pointer to the block entry on success.
+ * @param ppvData Where to return the pointer to the block data on success.
+ */
+static int rtFsXfsVol_BlockLoad(PRTFSXFSVOL pThis, uint64_t iBlock, PRTFSXFSBLOCKENTRY *ppBlock, void **ppvData)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Try to fetch the block group from the cache first. */
+ PRTFSXFSBLOCKENTRY pBlock = (PRTFSXFSBLOCKENTRY)RTAvlU64Get(&pThis->BlockRoot, iBlock);
+ if (!pBlock)
+ {
+ /* Slow path, load from disk. */
+ pBlock = rtFsXfsVol_BlockGetNew(pThis, iBlock);
+ if (RT_LIKELY(pBlock))
+ {
+ uint64_t offRead = rtFsXfsBlockIdxToDiskOffset(pThis, iBlock);
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offRead, &pBlock->abData[0], pThis->cbBlock, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ bool fIns = RTAvlU64Insert(&pThis->BlockRoot, &pBlock->Core);
+ Assert(fIns); RT_NOREF(fIns);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ /* Remove from current LRU list position and add to the beginning. */
+ uint32_t cRefs = ASMAtomicIncU32(&pBlock->cRefs);
+ if (cRefs == 1) /* Blocks get removed from the LRU list if they are referenced. */
+ RTListNodeRemove(&pBlock->NdLru);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ *ppBlock = pBlock;
+ *ppvData = &pBlock->abData[0];
+ }
+ else if (pBlock)
+ {
+ ASMAtomicDecU32(&pBlock->cRefs);
+ rtFsXfsVol_BlockFree(pThis, pBlock); /* Free the block. */
+ }
+
+ return rc;
+}
+
+
+/**
+ * Releases a reference of the given block.
+ *
+ * @param pThis The XFS volume instance.
+ * @param pBlock The block to release.
+ */
+static void rtFsXfsVol_BlockRelease(PRTFSXFSVOL pThis, PRTFSXFSBLOCKENTRY pBlock)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pBlock->cRefs);
+ if (!cRefs)
+ rtFsXfsVol_BlockFree(pThis, pBlock);
+}
+
+#if 0 /* unused */
+/**
+ * Allocates a new alloction group.
+ *
+ * @returns Pointer to the new allocation group descriptor or NULL if out of memory.
+ * @param pThis The XFS volume instance.
+ * @param iAG Allocation group number.
+ */
+static PRTFSXFSAG rtFsXfsAg_Alloc(PRTFSXFSVOL pThis, uint32_t iAg)
+{
+ PRTFSXFSAG pAg = (PRTFSXFSAG)RTMemAllocZ(sizeof(RTFSXFSAG));
+ if (RT_LIKELY(pAg))
+ {
+ pAg->Core.Key = iAg;
+ pAg->cRefs = 0;
+ pThis->cbAgs += sizeof(RTFSXFSAG);
+ }
+
+ return pAg;
+}
+
+
+/**
+ * Frees the given allocation group.
+ *
+ * @param pThis The XFS volume instance.
+ * @param pAg The allocation group to free.
+ */
+static void rtFsXfsAg_Free(PRTFSXFSVOL pThis, PRTFSXFSAG pAg)
+{
+ Assert(!pAg->cRefs);
+
+ /*
+ * Put it into the cache if the limit wasn't exceeded, otherwise the allocation group
+ * is freed right away.
+ */
+ if (pThis->cbAgs <= RTFSXFS_MAX_AG_CACHE_SIZE)
+ {
+ /* Put onto the LRU list. */
+ RTListPrepend(&pThis->LstAgLru, &pAg->NdLru);
+ }
+ else
+ {
+ /* Remove from the tree and free memory. */
+ PAVLU32NODECORE pCore = RTAvlU32Remove(&pThis->AgRoot, pAg->Core.Key);
+ Assert(pCore == &pAg->Core); RT_NOREF(pCore);
+ RTMemFree(pAg);
+ pThis->cbAgs -= sizeof(RTFSXFSAG);
+ }
+}
+
+
+/**
+ * Returns a new block group utilizing the cache if possible.
+ *
+ * @returns Pointer to the new block group descriptor or NULL if out of memory.
+ * @param pThis The XFS volume instance.
+ * @param iAg Allocation group number.
+ */
+static PRTFSXFSAG rtFsXfsAg_GetNew(PRTFSXFSVOL pThis, uint32_t iAg)
+{
+ PRTFSXFSAG pAg = NULL;
+ if (pThis->cbAgs + sizeof(RTFSXFSAG) <= RTFSXFS_MAX_AG_CACHE_SIZE)
+ pAg = rtFsXfsAg_Alloc(pThis, iAg);
+ else
+ {
+ pAg = RTListRemoveLast(&pThis->LstAgLru, RTFSXFSAG, NdLru);
+ if (!pAg)
+ pAg = rtFsXfsAg_Alloc(pThis, iAg);
+ else
+ {
+ /* Remove the block group from the tree because it gets a new key. */
+ PAVLU32NODECORE pCore = RTAvlU32Remove(&pThis->AgRoot, pAg->Core.Key);
+ Assert(pCore == &pAg->Core); RT_NOREF(pCore);
+ }
+ }
+
+ Assert(!pAg->cRefs);
+ pAg->Core.Key = iAg;
+ pAg->cRefs = 1;
+
+ return pAg;
+}
+
+
+/**
+ * Loads the given allocation group number and returns it on success.
+ *
+ * @returns IPRT status code.
+ * @param pThis The XFS volume instance.
+ * @param iAg The allocation group to load.
+ * @param ppAg Where to store the allocation group on success.
+ */
+static int rtFsXfsAg_Load(PRTFSXFSVOL pThis, uint32_t iAg, PRTFSXFSAG *ppAg)
+{
+ int rc = VINF_SUCCESS;
+
+ AssertReturn(iAg < pThis->cAgs, VERR_VFS_BOGUS_FORMAT);
+
+ /* Try to fetch the allocation group from the cache first. */
+ PRTFSXFSAG pAg = (PRTFSXFSAG)RTAvlU32Get(&pThis->AgRoot, iAg);
+ if (!pAg)
+ {
+ /* Slow path, load from disk. */
+ pAg = rtFsXfsAg_GetNew(pThis, iAg);
+ if (RT_LIKELY(pAg))
+ {
+ uint64_t offRead = rtFsXfsBlockIdxToDiskOffset(pThis, iAg * pThis->cBlocksPerAg);
+ XFSSUPERBLOCK Sb;
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offRead, &Sb, sizeof(Sb), NULL);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ rtFsXfsSb_Log(iAg, &Sb);
+#endif
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ /* Remove from current LRU list position and add to the beginning. */
+ uint32_t cRefs = ASMAtomicIncU32(&pAg->cRefs);
+ if (cRefs == 1) /* Block groups get removed from the LRU list if they are referenced. */
+ RTListNodeRemove(&pAg->NdLru);
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAg = pAg;
+ else if (pAg)
+ {
+ ASMAtomicDecU32(&pAg->cRefs);
+ rtFsXfsAg_Free(pThis, pAg); /* Free the allocation group. */
+ }
+
+ return rc;
+}
+
+
+/**
+ * Releases a reference of the given allocation group.
+ *
+ * @param pThis The XFS volume instance.
+ * @param pAg The allocation group to release.
+ */
+static void rtFsXfsAg_Release(PRTFSXFSVOL pThis, PRTFSXFSAG pAg)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pAg->cRefs);
+ if (!cRefs)
+ rtFsXfsAg_Free(pThis, pAg);
+}
+#endif
+
+/**
+ * Allocates a new inode.
+ *
+ * @returns Pointer to the new inode or NULL if out of memory.
+ * @param pThis The XFS volume instance.
+ * @param iInode Inode number.
+ */
+static PRTFSXFSINODE rtFsXfsInode_Alloc(PRTFSXFSVOL pThis, uint32_t iInode)
+{
+ size_t cbAlloc = RT_UOFFSETOF_DYN(RTFSXFSINODE, abData[pThis->cbInode]);
+ PRTFSXFSINODE pInode = (PRTFSXFSINODE)RTMemAllocZ(cbAlloc);
+ if (RT_LIKELY(pInode))
+ {
+ pInode->Core.Key = iInode;
+ pInode->cRefs = 0;
+ pThis->cbInodes += cbAlloc;
+ }
+
+ return pInode;
+}
+
+
+/**
+ * Frees the given inode.
+ *
+ * @param pThis The XFS volume instance.
+ * @param pInode The inode to free.
+ */
+static void rtFsXfsInode_Free(PRTFSXFSVOL pThis, PRTFSXFSINODE pInode)
+{
+ Assert(!pInode->cRefs);
+
+ /*
+ * Put it into the cache if the limit wasn't exceeded, otherwise the inode
+ * is freed right away.
+ */
+ if (pThis->cbInodes <= RTFSXFS_MAX_INODE_CACHE_SIZE)
+ {
+ /* Put onto the LRU list. */
+ RTListPrepend(&pThis->LstInodeLru, &pInode->NdLru);
+ }
+ else
+ {
+ /* Remove from the tree and free memory. */
+ PAVLU64NODECORE pCore = RTAvlU64Remove(&pThis->InodeRoot, pInode->Core.Key);
+ Assert(pCore == &pInode->Core); RT_NOREF(pCore);
+ RTMemFree(pInode);
+ pThis->cbInodes -= RT_UOFFSETOF_DYN(RTFSXFSINODE, abData[pThis->cbInode]);
+ }
+}
+
+
+/**
+ * Returns a new inodep utilizing the cache if possible.
+ *
+ * @returns Pointer to the new inode or NULL if out of memory.
+ * @param pThis The XFS volume instance.
+ * @param iInode Inode number.
+ */
+static PRTFSXFSINODE rtFsXfsInode_GetNew(PRTFSXFSVOL pThis, XFSINO iInode)
+{
+ PRTFSXFSINODE pInode = NULL;
+ if (pThis->cbInodes + RT_UOFFSETOF_DYN(RTFSXFSINODE, abData[pThis->cbInode]) <= RTFSXFS_MAX_INODE_CACHE_SIZE)
+ pInode = rtFsXfsInode_Alloc(pThis, iInode);
+ else
+ {
+ pInode = RTListRemoveLast(&pThis->LstInodeLru, RTFSXFSINODE, NdLru);
+ if (!pInode)
+ pInode = rtFsXfsInode_Alloc(pThis, iInode);
+ else
+ {
+ /* Remove the block group from the tree because it gets a new key. */
+ PAVLU64NODECORE pCore = RTAvlU64Remove(&pThis->InodeRoot, pInode->Core.Key);
+ Assert(pCore == &pInode->Core); RT_NOREF(pCore);
+ }
+ }
+
+ Assert(!pInode->cRefs);
+ pInode->Core.Key = iInode;
+ pInode->cRefs = 1;
+
+ return pInode;
+}
+
+
+/**
+ * Loads the given inode number and returns it on success.
+ *
+ * @returns IPRT status code.
+ * @param pThis The XFS volume instance.
+ * @param iInode The inode to load.
+ * @param ppInode Where to store the inode on success.
+ */
+static int rtFsXfsInode_Load(PRTFSXFSVOL pThis, XFSINO iInode, PRTFSXFSINODE *ppInode)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Try to fetch the inode from the cache first. */
+ PRTFSXFSINODE pInode = (PRTFSXFSINODE)RTAvlU64Get(&pThis->InodeRoot, iInode);
+ if (!pInode)
+ {
+ /* Slow path, load from disk. */
+ pInode = rtFsXfsInode_GetNew(pThis, iInode);
+ if (RT_LIKELY(pInode))
+ {
+ uint32_t iAg;
+ uint32_t uBlock;
+ uint32_t offBlock;
+
+ rtFsXfsInodeSplitAbs(pThis, iInode, &iAg, &uBlock, &offBlock);
+
+ uint64_t offRead = (iAg * pThis->cBlocksPerAg + uBlock) * pThis->cbBlock + offBlock;
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offRead, &pInode->abData[0], pThis->cbInode, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ PCXFSINODECORE pInodeCore = (PCXFSINODECORE)&pInode->abData[0];
+
+#ifdef LOG_ENABLED
+ rtFsXfsInode_Log(pThis, iInode, pInodeCore);
+#endif
+
+ pInode->offInode = offRead;
+ pInode->fFlags = RT_BE2H_U16(pInodeCore->fFlags);
+ pInode->enmFormat = pInodeCore->enmFormat;
+ pInode->cExtentsData = RT_BE2H_U32(pInodeCore->cExtentsData);
+ pInode->ObjInfo.cbObject = RT_BE2H_U64(pInodeCore->cbInode);
+ pInode->ObjInfo.cbAllocated = RT_BE2H_U64(pInodeCore->cBlocks) * pThis->cbBlock;
+ RTTimeSpecSetSeconds(&pInode->ObjInfo.AccessTime, RT_BE2H_U32(pInodeCore->TsLastAccessed.cSecEpoch));
+ RTTimeSpecAddNano(&pInode->ObjInfo.AccessTime, RT_BE2H_U32(pInodeCore->TsLastAccessed.cNanoSec));
+ RTTimeSpecSetSeconds(&pInode->ObjInfo.ModificationTime, RT_BE2H_U32(pInodeCore->TsLastModified.cSecEpoch));
+ RTTimeSpecAddNano(&pInode->ObjInfo.ModificationTime, RT_BE2H_U32(pInodeCore->TsLastModified.cNanoSec));
+ RTTimeSpecSetSeconds(&pInode->ObjInfo.ChangeTime, RT_BE2H_U32(pInodeCore->TsCreatedModified.cSecEpoch));
+ RTTimeSpecAddNano(&pInode->ObjInfo.ChangeTime, RT_BE2H_U32(pInodeCore->TsCreatedModified.cNanoSec));
+ pInode->ObjInfo.Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
+ pInode->ObjInfo.Attr.u.Unix.uid = RT_BE2H_U32(pInodeCore->uUid);
+ pInode->ObjInfo.Attr.u.Unix.gid = RT_BE2H_U32(pInodeCore->uGid);
+ pInode->ObjInfo.Attr.u.Unix.cHardlinks = RT_BE2H_U16(pInodeCore->cOnLinks); /** @todo v2 inodes. */
+ pInode->ObjInfo.Attr.u.Unix.INodeIdDevice = 0;
+ pInode->ObjInfo.Attr.u.Unix.INodeId = iInode;
+ pInode->ObjInfo.Attr.u.Unix.fFlags = 0;
+ pInode->ObjInfo.Attr.u.Unix.GenerationId = RT_BE2H_U32(pInodeCore->cGeneration);
+ pInode->ObjInfo.Attr.u.Unix.Device = 0;
+ if (pInodeCore->iVersion >= 3)
+ {
+ RTTimeSpecSetSeconds(&pInode->ObjInfo.BirthTime, RT_BE2H_U32(pInodeCore->TsCreation.cSecEpoch));
+ RTTimeSpecAddNano(&pInode->ObjInfo.BirthTime, RT_BE2H_U32(pInodeCore->TsCreation.cNanoSec));
+ }
+ else
+ pInode->ObjInfo.BirthTime = pInode->ObjInfo.ChangeTime;
+
+ /* Fill in the mode. */
+ pInode->ObjInfo.Attr.fMode = 0;
+ uint16_t fInodeMode = RT_BE2H_U16(pInodeCore->fMode);
+ switch (XFS_INODE_MODE_TYPE_GET_TYPE(fInodeMode))
+ {
+ case XFS_INODE_MODE_TYPE_FIFO:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_FIFO;
+ break;
+ case XFS_INODE_MODE_TYPE_CHAR:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_DEV_CHAR;
+ break;
+ case XFS_INODE_MODE_TYPE_DIR:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_DIRECTORY;
+ break;
+ case XFS_INODE_MODE_TYPE_BLOCK:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_DEV_BLOCK;
+ break;
+ case XFS_INODE_MODE_TYPE_REGULAR:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_FILE;
+ break;
+ case XFS_INODE_MODE_TYPE_SYMLINK:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_SYMLINK;
+ break;
+ case XFS_INODE_MODE_TYPE_SOCKET:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_SOCKET;
+ break;
+ default:
+ rc = VERR_VFS_BOGUS_FORMAT;
+ }
+ if (fInodeMode & XFS_INODE_MODE_EXEC_OTHER)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IXOTH;
+ if (fInodeMode & XFS_INODE_MODE_WRITE_OTHER)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IWOTH;
+ if (fInodeMode & XFS_INODE_MODE_READ_OTHER)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IROTH;
+ if (fInodeMode & XFS_INODE_MODE_EXEC_GROUP)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IXGRP;
+ if (fInodeMode & XFS_INODE_MODE_WRITE_GROUP)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IWGRP;
+ if (fInodeMode & XFS_INODE_MODE_READ_GROUP)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IRGRP;
+ if (fInodeMode & XFS_INODE_MODE_EXEC_OWNER)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IXUSR;
+ if (fInodeMode & XFS_INODE_MODE_WRITE_OWNER)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IWUSR;
+ if (fInodeMode & XFS_INODE_MODE_READ_OWNER)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IRUSR;
+ if (fInodeMode & XFS_INODE_MODE_STICKY)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_ISTXT;
+ if (fInodeMode & XFS_INODE_MODE_SET_GROUP_ID)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_ISGID;
+ if (fInodeMode & XFS_INODE_MODE_SET_USER_ID)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_ISUID;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ /* Remove from current LRU list position and add to the beginning. */
+ uint32_t cRefs = ASMAtomicIncU32(&pInode->cRefs);
+ if (cRefs == 1) /* Inodes get removed from the LRU list if they are referenced. */
+ RTListNodeRemove(&pInode->NdLru);
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppInode = pInode;
+ else if (pInode)
+ {
+ ASMAtomicDecU32(&pInode->cRefs);
+ rtFsXfsInode_Free(pThis, pInode); /* Free the inode. */
+ }
+
+ return rc;
+}
+
+
+/**
+ * Releases a reference of the given inode.
+ *
+ * @param pThis The XFS volume instance.
+ * @param pInode The inode to release.
+ */
+static void rtFsXfsInode_Release(PRTFSXFSVOL pThis, PRTFSXFSINODE pInode)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pInode->cRefs);
+ if (!cRefs)
+ rtFsXfsInode_Free(pThis, pInode);
+}
+
+
+/**
+ * Worker for various QueryInfo methods.
+ *
+ * @returns IPRT status code.
+ * @param pInode The inode structure to return info for.
+ * @param pObjInfo Where to return object info.
+ * @param enmAddAttr What additional info to return.
+ */
+static int rtFsXfsInode_QueryInfo(PRTFSXFSINODE pInode, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ RT_ZERO(*pObjInfo);
+
+ pObjInfo->cbObject = pInode->ObjInfo.cbObject;
+ pObjInfo->cbAllocated = pInode->ObjInfo.cbAllocated;
+ pObjInfo->AccessTime = pInode->ObjInfo.AccessTime;
+ pObjInfo->ModificationTime = pInode->ObjInfo.ModificationTime;
+ pObjInfo->ChangeTime = pInode->ObjInfo.ChangeTime;
+ pObjInfo->BirthTime = pInode->ObjInfo.BirthTime;
+ pObjInfo->Attr.fMode = pInode->ObjInfo.Attr.fMode;
+ pObjInfo->Attr.enmAdditional = enmAddAttr;
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_UNIX:
+ memcpy(&pObjInfo->Attr.u.Unix, &pInode->ObjInfo.Attr.u.Unix, sizeof(pInode->ObjInfo.Attr.u.Unix));
+ break;
+
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ pObjInfo->Attr.u.UnixOwner.uid = pInode->ObjInfo.Attr.u.Unix.uid;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ pObjInfo->Attr.u.UnixGroup.gid = pInode->ObjInfo.Attr.u.Unix.gid;
+ break;
+
+ default:
+ break;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Locates the location of the next level in the B+Tree mapping the given offset.
+ *
+ * @returns Filesystem block number where the next level of the B+Tree is stored.
+ * @param paoffFile Array of file offset mappings.
+ * @param pauFsBlock Array of filesystem block mappings.
+ * @param cEntries Number of entries in the extent index node array.
+ * @param iBlock The block to resolve.
+ */
+DECLINLINE(XFSDFSBNO) rtFsXfsInode_BTreeNdLocateNextLvl(XFSDFILOFF *paoffFile, XFSDFSBNO *pauFsBlock,
+ uint16_t cEntries, XFSDFILOFF offFile)
+{
+ for (uint32_t i = 1; i < cEntries; i++)
+ {
+ if ( RT_BE2H_U64(paoffFile[i - 1]) <= offFile
+ && RT_BE2H_U64(paoffFile[i]) > offFile)
+ return RT_BE2H_U64(pauFsBlock[i]);
+ }
+
+ /* Nothing found so far, the last entry must cover the block as the array is sorted. */
+ return RT_BE2H_U64(pauFsBlock[cEntries - 1]);
+}
+
+
+/**
+ * Locates the extent mapping the file offset in the given extents list.
+ *
+ * @returns IPRT status.
+ * @param pExtents The array of extents to search.
+ * @param cEntries Number of entries in the array.
+ * @param uBlock The file offset to search the matching mapping for.
+ * @param cBlocks Number of blocks requested.
+ * @param piBlockFs Where to store the filesystem block on success.
+ * @param pcBlocks Where to store the number of contiguous blocks on success.
+ * @param pfSparse Where to store the sparse flag on success.
+ */
+DECLINLINE(int) rtFsXfsInode_ExtentLocate(PCXFSEXTENT paExtents, uint16_t cEntries, XFSDFILOFF uBlock,
+ size_t cBlocks, uint64_t *piBlockFs, size_t *pcBlocks, bool *pfSparse)
+{
+ int rc = VERR_VFS_BOGUS_FORMAT;
+
+ for (uint32_t i = 0; i < cEntries; i++)
+ {
+ PCXFSEXTENT pExtent = &paExtents[i];
+ uint64_t iBlockExtent = XFS_EXTENT_GET_LOGICAL_BLOCK(pExtent);
+ size_t cBlocksExtent = XFS_EXTENT_GET_BLOCK_COUNT(pExtent);
+
+ if ( uBlock >= iBlockExtent
+ && uBlock < iBlockExtent + cBlocksExtent)
+ {
+ uint64_t offExtentBlocks = uBlock - iBlockExtent;
+ *piBlockFs = XFS_EXTENT_GET_DISK_BLOCK(pExtent) + offExtentBlocks;
+ *pcBlocks = RT_MIN(cBlocks, cBlocksExtent - offExtentBlocks);
+ *pfSparse = XFS_EXTENT_IS_UNWRITTEN(pExtent);
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Validates the given node header.
+ *
+ * @returns IPRT status code.
+ * @param pThis The XFS volume instance.
+ * @param pNd The node header to validate.
+ * @param iLvl The current level.
+ */
+static int rtFsXfsInode_BTreeNdValidate(PRTFSXFSVOL pThis, PCXFSBTREENODEHDR pNd, uint16_t iLvl)
+{
+ RT_NOREF(pThis, pNd, iLvl);
+ /** @todo */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Maps the given inode block to the destination filesystem block.
+ *
+ * @returns IPRT status code.
+ * @param pThis The XFS volume instance.
+ * @param pInode The inode structure to read from.
+ * @param iBlock The inode block to map.
+ * @param cBlocks Number of blocks requested.
+ * @param piBlockFs Where to store the filesystem block on success.
+ * @param pcBlocks Where to store the number of contiguous blocks on success.
+ * @param pfSparse Where to store the sparse flag on success.
+ *
+ * @todo Optimize
+ */
+static int rtFsXfsInode_MapBlockToFs(PRTFSXFSVOL pThis, PRTFSXFSINODE pInode, uint64_t iBlock, size_t cBlocks,
+ uint64_t *piBlockFs, size_t *pcBlocks, bool *pfSparse)
+{
+ int rc = VINF_SUCCESS;
+
+ switch (pInode->enmFormat)
+ {
+ case XFS_INODE_FORMAT_EXTENTS:
+ {
+ size_t cbRemaining = 0;
+ PCXFSEXTENT paExtents = (PCXFSEXTENT)rtFsXfsInodeGetDataFork(pThis, pInode, &cbRemaining);
+
+ if (cbRemaining <= pInode->cExtentsData * sizeof(XFSEXTENT))
+ rc = rtFsXfsInode_ExtentLocate(paExtents, pInode->cExtentsData, cBlocks, iBlock,
+ piBlockFs, pcBlocks, pfSparse);
+ else
+ rc = VERR_VFS_BOGUS_FORMAT;
+ break;
+ }
+ case XFS_INODE_FORMAT_BTREE:
+ {
+ size_t cbRemaining = 0;
+ PCXFSBTREEROOTHDR pRoot = (PCXFSBTREEROOTHDR)rtFsXfsInodeGetDataFork(pThis, pInode, &cbRemaining);
+ if (cbRemaining >= RT_BE2H_U16(pRoot->cRecs) * (sizeof(XFSDFSBNO) + sizeof(XFSDFILOFF)) + sizeof(XFSBTREEROOTHDR))
+ {
+ XFSDFILOFF *poffFile = (XFSDFILOFF *)(pRoot + 1);
+ XFSDFSBNO *puFsBlock = (XFSDFSBNO *)(&poffFile[RT_BE2H_U16(pRoot->cRecs)]);
+
+ XFSDFSBNO uFsBlock = rtFsXfsInode_BTreeNdLocateNextLvl(poffFile, puFsBlock, RT_BE2H_U16(pRoot->cRecs),
+ iBlock);
+ uint16_t iLvl = RT_BE2H_U16(pRoot->iLvl) - 1;
+
+ /* Resolve intermediate levels. */
+ while ( iLvl > 0
+ && RT_SUCCESS(rc))
+ {
+ PRTFSXFSBLOCKENTRY pEntry;
+ PCXFSBTREENODEHDR pNd;
+
+ rc = rtFsXfsVol_BlockLoad(pThis, uFsBlock, &pEntry, (void **)&pNd);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsXfsInode_BTreeNdValidate(pThis, pNd, iLvl);
+ if (RT_SUCCESS(rc))
+ {
+ poffFile = (XFSDFILOFF *)(pNd + 1);
+ puFsBlock = (XFSDFSBNO *)(&poffFile[RT_BE2H_U16(pNd->cRecs)]);
+ uFsBlock = rtFsXfsInode_BTreeNdLocateNextLvl(poffFile, puFsBlock, RT_BE2H_U16(pRoot->cRecs),
+ iBlock);
+ iLvl--;
+ }
+ rtFsXfsVol_BlockRelease(pThis, pEntry);
+ }
+ }
+
+ /* Load the leave node and parse it. */
+ if (RT_SUCCESS(rc))
+ {
+ PRTFSXFSBLOCKENTRY pEntry;
+ PCXFSBTREENODEHDR pNd;
+
+ rc = rtFsXfsVol_BlockLoad(pThis, uFsBlock, &pEntry, (void **)&pNd);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsXfsInode_BTreeNdValidate(pThis, pNd, iLvl);
+ if (RT_SUCCESS(rc))
+ {
+ PCXFSEXTENT paExtents = (PCXFSEXTENT)(pNd + 1);
+ rc = rtFsXfsInode_ExtentLocate(paExtents, RT_BE2H_U16(pNd->cRecs), cBlocks, iBlock,
+ piBlockFs, pcBlocks, pfSparse);
+ }
+ rtFsXfsVol_BlockRelease(pThis, pEntry);
+ }
+ }
+ }
+ else
+ rc = VERR_VFS_BOGUS_FORMAT;
+ break;
+ }
+ case XFS_INODE_FORMAT_LOCAL:
+ case XFS_INODE_FORMAT_UUID:
+ case XFS_INODE_FORMAT_DEV:
+ default:
+ rc = VERR_VFS_BOGUS_FORMAT;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Reads data from the given inode at the given byte offset.
+ *
+ * @returns IPRT status code.
+ * @param pThis The XFS volume instance.
+ * @param pInode The inode structure to read from.
+ * @param off The byte offset to start reading from.
+ * @param pvBuf Where to store the read data to.
+ * @param pcbRead Where to return the amount of data read.
+ */
+static int rtFsXfsInode_Read(PRTFSXFSVOL pThis, PRTFSXFSINODE pInode, uint64_t off, void *pvBuf, size_t cbRead, size_t *pcbRead)
+{
+ int rc = VINF_SUCCESS;
+ uint8_t *pbBuf = (uint8_t *)pvBuf;
+
+ if (((uint64_t)pInode->ObjInfo.cbObject < off + cbRead))
+ {
+ if (!pcbRead)
+ return VERR_EOF;
+ else
+ cbRead = (uint64_t)pInode->ObjInfo.cbObject - off;
+ }
+
+ if (pInode->enmFormat == XFS_INODE_FORMAT_LOCAL)
+ {
+ /* Fast path when the data is inlined in the inode. */
+ size_t cbRemaining = 0;
+ uint8_t *pbSrc = (uint8_t *)rtFsXfsInodeGetDataFork(pThis, pInode, &cbRemaining);
+ if (off + cbRemaining <= (uint64_t)pInode->ObjInfo.cbObject)
+ {
+ memcpy(pvBuf, &pbSrc[off], cbRead);
+ *pcbRead = cbRead;
+ }
+ else
+ rc = VERR_VFS_BOGUS_FORMAT;
+
+ return rc;
+ }
+
+ while ( cbRead
+ && RT_SUCCESS(rc))
+ {
+ uint64_t iBlockStart = rtFsXfsDiskOffsetToBlockIdx(pThis, off);
+ uint32_t offBlockStart = off % pThis->cbBlock;
+
+ /* Resolve the inode block to the proper filesystem block. */
+ uint64_t iBlockFs = 0;
+ size_t cBlocks = 0;
+ bool fSparse = false;
+ rc = rtFsXfsInode_MapBlockToFs(pThis, pInode, iBlockStart, 1, &iBlockFs, &cBlocks, &fSparse);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cBlocks == 1);
+
+ size_t cbThisRead = RT_MIN(cbRead, pThis->cbBlock - offBlockStart);
+
+ if (!fSparse)
+ {
+ uint64_t offRead = rtFsXfsBlockIdxToDiskOffset(pThis, iBlockFs);
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offRead + offBlockStart, pbBuf, cbThisRead, NULL);
+ }
+ else
+ memset(pbBuf, 0, cbThisRead);
+
+ if (RT_SUCCESS(rc))
+ {
+ pbBuf += cbThisRead;
+ cbRead -= cbThisRead;
+ off += cbThisRead;
+ if (pcbRead)
+ *pcbRead += cbThisRead;
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+
+/*
+ *
+ * File operations.
+ * File operations.
+ * File operations.
+ *
+ */
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsXfsFile_Close(void *pvThis)
+{
+ PRTFSXFSFILE pThis = (PRTFSXFSFILE)pvThis;
+ LogFlow(("rtFsXfsFile_Close(%p/%p)\n", pThis, pThis->pInode));
+
+ rtFsXfsInode_Release(pThis->pVol, pThis->pInode);
+ pThis->pInode = NULL;
+ pThis->pVol = NULL;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsXfsFile_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSXFSFILE pThis = (PRTFSXFSFILE)pvThis;
+ return rtFsXfsInode_QueryInfo(pThis->pInode, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtFsXfsFile_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTFSXFSFILE pThis = (PRTFSXFSFILE)pvThis;
+ AssertReturn(pSgBuf->cSegs == 1, VERR_INTERNAL_ERROR_3);
+ RT_NOREF(fBlocking);
+
+ if (off == -1)
+ off = pThis->offFile;
+ else
+ AssertReturn(off >= 0, VERR_INTERNAL_ERROR_3);
+
+ int rc;
+ size_t cbRead = pSgBuf->paSegs[0].cbSeg;
+ if (!pcbRead)
+ {
+ rc = rtFsXfsInode_Read(pThis->pVol, pThis->pInode, (uint64_t)off, pSgBuf->paSegs[0].pvSeg, cbRead, NULL);
+ if (RT_SUCCESS(rc))
+ pThis->offFile = off + cbRead;
+ Log6(("rtFsXfsFile_Read: off=%#RX64 cbSeg=%#x -> %Rrc\n", off, pSgBuf->paSegs[0].cbSeg, rc));
+ }
+ else
+ {
+ PRTFSXFSINODE pInode = pThis->pInode;
+ if (off >= pInode->ObjInfo.cbObject)
+ {
+ *pcbRead = 0;
+ rc = VINF_EOF;
+ }
+ else
+ {
+ if ((uint64_t)off + cbRead <= (uint64_t)pInode->ObjInfo.cbObject)
+ rc = rtFsXfsInode_Read(pThis->pVol, pThis->pInode, (uint64_t)off, pSgBuf->paSegs[0].pvSeg, cbRead, NULL);
+ else
+ {
+ /* Return VINF_EOF if beyond end-of-file. */
+ cbRead = (size_t)(pInode->ObjInfo.cbObject - off);
+ rc = rtFsXfsInode_Read(pThis->pVol, pThis->pInode, off, pSgBuf->paSegs[0].pvSeg, cbRead, NULL);
+ if (RT_SUCCESS(rc))
+ rc = VINF_EOF;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pThis->offFile = off + cbRead;
+ *pcbRead = cbRead;
+ }
+ else
+ *pcbRead = 0;
+ }
+ Log6(("rtFsXfsFile_Read: off=%#RX64 cbSeg=%#x -> %Rrc *pcbRead=%#x\n", off, pSgBuf->paSegs[0].cbSeg, rc, *pcbRead));
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtFsXfsFile_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ RT_NOREF(pvThis, off, pSgBuf, fBlocking, pcbWritten);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtFsXfsFile_Flush(void *pvThis)
+{
+ RT_NOREF(pvThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtFsXfsFile_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTFSXFSFILE pThis = (PRTFSXFSFILE)pvThis;
+ *poffActual = pThis->offFile;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtFsXfsFile_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ RT_NOREF(pvThis, fMode, fMask);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtFsXfsFile_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ RT_NOREF(pvThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtFsXfsFile_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ RT_NOREF(pvThis, uid, gid);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSeek}
+ */
+static DECLCALLBACK(int) rtFsXfsFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual)
+{
+ PRTFSXFSFILE pThis = (PRTFSXFSFILE)pvThis;
+ RTFOFF offNew;
+ switch (uMethod)
+ {
+ case RTFILE_SEEK_BEGIN:
+ offNew = offSeek;
+ break;
+ case RTFILE_SEEK_END:
+ offNew = pThis->pInode->ObjInfo.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) rtFsXfsFile_QuerySize(void *pvThis, uint64_t *pcbFile)
+{
+ PRTFSXFSFILE pThis = (PRTFSXFSFILE)pvThis;
+ *pcbFile = (uint64_t)pThis->pInode->ObjInfo.cbObject;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSetSize}
+ */
+static DECLCALLBACK(int) rtFsXfsFile_SetSize(void *pvThis, uint64_t cbFile, uint32_t fFlags)
+{
+ RT_NOREF(pvThis, cbFile, fFlags);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQueryMaxSize}
+ */
+static DECLCALLBACK(int) rtFsXfsFile_QueryMaxSize(void *pvThis, uint64_t *pcbMax)
+{
+ RT_NOREF(pvThis);
+ *pcbMax = INT64_MAX; /** @todo */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * XFS file operations.
+ */
+static const RTVFSFILEOPS g_rtFsXfsFileOps =
+{
+ { /* Stream */
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FILE,
+ "XFS File",
+ rtFsXfsFile_Close,
+ rtFsXfsFile_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtFsXfsFile_Read,
+ rtFsXfsFile_Write,
+ rtFsXfsFile_Flush,
+ NULL /*PollOne*/,
+ rtFsXfsFile_Tell,
+ NULL /*pfnSkip*/,
+ NULL /*pfnZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+ },
+ RTVFSFILEOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj),
+ rtFsXfsFile_SetMode,
+ rtFsXfsFile_SetTimes,
+ rtFsXfsFile_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtFsXfsFile_Seek,
+ rtFsXfsFile_QuerySize,
+ rtFsXfsFile_SetSize,
+ rtFsXfsFile_QueryMaxSize,
+ RTVFSFILEOPS_VERSION
+};
+
+
+/**
+ * Creates a new VFS file from the given regular file inode.
+ *
+ * @returns IPRT status code.
+ * @param pThis The XFS volume instance.
+ * @param fOpen Open flags passed.
+ * @param iInode The inode for the file.
+ * @param phVfsFile Where to store the VFS file handle on success.
+ * @param pErrInfo Where to record additional error information on error, optional.
+ * @param pszWhat Logging prefix.
+ */
+static int rtFsXfsVol_NewFile(PRTFSXFSVOL pThis, uint64_t fOpen, uint32_t iInode,
+ PRTVFSFILE phVfsFile, PRTERRINFO pErrInfo, const char *pszWhat)
+{
+ /*
+ * Load the inode and check that it really is a file.
+ */
+ PRTFSXFSINODE pInode = NULL;
+ int rc = rtFsXfsInode_Load(pThis, iInode, &pInode);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_FILE(pInode->ObjInfo.Attr.fMode))
+ {
+ PRTFSXFSFILE pNewFile;
+ rc = RTVfsNewFile(&g_rtFsXfsFileOps, sizeof(*pNewFile), fOpen, pThis->hVfsSelf, NIL_RTVFSLOCK,
+ phVfsFile, (void **)&pNewFile);
+ if (RT_SUCCESS(rc))
+ {
+ pNewFile->pVol = pThis;
+ pNewFile->pInode = pInode;
+ pNewFile->offFile = 0;
+ }
+ }
+ else
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_NOT_A_FILE, "%s: fMode=%#RX32", pszWhat, pInode->ObjInfo.Attr.fMode);
+
+ if (RT_FAILURE(rc))
+ rtFsXfsInode_Release(pThis, pInode);
+ }
+
+ return rc;
+}
+
+
+
+/*
+ *
+ * XFS directory code.
+ * XFS directory code.
+ * XFS directory code.
+ *
+ */
+
+/**
+ * Looks up an entry in the given directory inode.
+ *
+ * @returns IPRT status code.
+ * @param pThis The XFS volume instance.
+ * @param pInode The directory inode structure to.
+ * @param pszEntry The entry to lookup.
+ * @param piInode Where to store the inode number if the entry was found.
+ */
+static int rtFsXfsDir_Lookup(PRTFSXFSVOL pThis, PRTFSXFSINODE pInode, const char *pszEntry, uint32_t *piInode)
+{
+ uint64_t offEntry = 0;
+ int rc = VERR_FILE_NOT_FOUND;
+ uint32_t idxDirEntry = 0;
+ size_t cchEntry = strlen(pszEntry);
+
+ if (cchEntry > 255)
+ return VERR_FILENAME_TOO_LONG;
+
+ RT_NOREF(pThis, idxDirEntry, offEntry, pInode, piInode);
+
+#if 0 /** @todo */
+ while (offEntry < (uint64_t)pInode->ObjInfo.cbObject)
+ {
+ EXTDIRENTRYEX DirEntry;
+ size_t cbThis = RT_MIN(sizeof(DirEntry), (uint64_t)pInode->ObjInfo.cbObject - offEntry);
+ int rc2 = rtFsXfsInode_Read(pThis, pInode, offEntry, &DirEntry, cbThis, NULL);
+ if (RT_SUCCESS(rc2))
+ {
+#ifdef LOG_ENABLED
+ rtFsExtDirEntry_Log(pThis, idxDirEntry, &DirEntry);
+#endif
+
+ uint16_t cbName = pThis->fFeaturesIncompat & EXT_SB_FEAT_INCOMPAT_DIR_FILETYPE
+ ? DirEntry.Core.u.v2.cbName
+ : RT_LE2H_U16(DirEntry.Core.u.v1.cbName);
+ if ( cchEntry == cbName
+ && !memcmp(pszEntry, &DirEntry.Core.achName[0], cchEntry))
+ {
+ *piInode = RT_LE2H_U32(DirEntry.Core.iInodeRef);
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ offEntry += RT_LE2H_U16(DirEntry.Core.cbRecord);
+ idxDirEntry++;
+ }
+ else
+ {
+ rc = rc2;
+ break;
+ }
+ }
+#endif
+ return rc;
+}
+
+
+
+/*
+ *
+ * Directory instance methods
+ * Directory instance methods
+ * Directory instance methods
+ *
+ */
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_Close(void *pvThis)
+{
+ PRTFSXFSDIR pThis = (PRTFSXFSDIR)pvThis;
+ LogFlowFunc(("pThis=%p\n", pThis));
+ rtFsXfsInode_Release(pThis->pVol, pThis->pInode);
+ pThis->pInode = NULL;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSXFSDIR pThis = (PRTFSXFSDIR)pvThis;
+ LogFlowFunc(("\n"));
+ return rtFsXfsInode_QueryInfo(pThis->pInode, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ LogFlowFunc(("\n"));
+ RT_NOREF(pvThis, fMode, fMask);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ LogFlowFunc(("\n"));
+ RT_NOREF(pvThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ LogFlowFunc(("\n"));
+ RT_NOREF(pvThis, uid, gid);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpen}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_Open(void *pvThis, const char *pszEntry, uint64_t fOpen,
+ uint32_t fFlags, PRTVFSOBJ phVfsObj)
+{
+ LogFlowFunc(("pszEntry='%s' fOpen=%#RX64 fFlags=%#x\n", pszEntry, fOpen, fFlags));
+ PRTFSXFSDIR pThis = (PRTFSXFSDIR)pvThis;
+ PRTFSXFSVOL pVol = pThis->pVol;
+ int rc = VINF_SUCCESS;
+
+ RT_NOREF(pThis, pVol, phVfsObj, pszEntry, fFlags);
+
+ /*
+ * 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;
+
+ /*
+ * Lookup the entry.
+ */
+ uint32_t iInode = 0;
+ rc = rtFsXfsDir_Lookup(pVol, pThis->pInode, pszEntry, &iInode);
+ if (RT_SUCCESS(rc))
+ {
+ PRTFSXFSINODE pInode = NULL;
+ rc = rtFsXfsInode_Load(pVol, iInode, &pInode);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_DIRECTORY(pInode->ObjInfo.Attr.fMode))
+ {
+ RTVFSDIR hVfsDir;
+ rc = rtFsXfsVol_OpenDirByInode(pVol, iInode, &hVfsDir);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromDir(hVfsDir);
+ RTVfsDirRelease(hVfsDir);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ else if (RTFS_IS_FILE(pInode->ObjInfo.Attr.fMode))
+ {
+ RTVFSFILE hVfsFile;
+ rc = rtFsXfsVol_NewFile(pVol, fOpen, iInode, &hVfsFile, NULL, pszEntry);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromFile(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+
+ LogFlow(("rtFsXfsDir_Open(%s): returns %Rrc\n", pszEntry, rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnCreateDir}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_CreateDir(void *pvThis, const char *pszSubDir, RTFMODE fMode, PRTVFSDIR phVfsDir)
+{
+ RT_NOREF(pvThis, pszSubDir, fMode, phVfsDir);
+ LogFlowFunc(("\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpenSymlink}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_OpenSymlink(void *pvThis, const char *pszSymlink, PRTVFSSYMLINK phVfsSymlink)
+{
+ RT_NOREF(pvThis, pszSymlink, phVfsSymlink);
+ LogFlowFunc(("\n"));
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnCreateSymlink}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_CreateSymlink(void *pvThis, const char *pszSymlink, const char *pszTarget,
+ RTSYMLINKTYPE enmType, PRTVFSSYMLINK phVfsSymlink)
+{
+ RT_NOREF(pvThis, pszSymlink, pszTarget, enmType, phVfsSymlink);
+ LogFlowFunc(("\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnUnlinkEntry}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_UnlinkEntry(void *pvThis, const char *pszEntry, RTFMODE fType)
+{
+ RT_NOREF(pvThis, pszEntry, fType);
+ LogFlowFunc(("\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRenameEntry}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_RenameEntry(void *pvThis, const char *pszEntry, RTFMODE fType, const char *pszNewName)
+{
+ RT_NOREF(pvThis, pszEntry, fType, pszNewName);
+ LogFlowFunc(("\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRewindDir}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_RewindDir(void *pvThis)
+{
+ PRTFSXFSDIR pThis = (PRTFSXFSDIR)pvThis;
+ LogFlowFunc(("\n"));
+
+ pThis->fNoMoreFiles = false;
+ pThis->offEntry = 0;
+ pThis->idxEntry = 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnReadDir}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_ReadDir(void *pvThis, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry,
+ RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSXFSDIR pThis = (PRTFSXFSDIR)pvThis;
+ PRTFSXFSINODE pInode = pThis->pInode;
+ LogFlowFunc(("\n"));
+
+ if (pThis->fNoMoreFiles)
+ return VERR_NO_MORE_FILES;
+
+ RT_NOREF(pInode, pDirEntry, pcbDirEntry, enmAddAttr);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * XFS directory operations.
+ */
+static const RTVFSDIROPS g_rtFsXfsDirOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_DIR,
+ "XFS Dir",
+ rtFsXfsDir_Close,
+ rtFsXfsDir_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSDIROPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSDIROPS, ObjSet) - RT_UOFFSETOF(RTVFSDIROPS, Obj),
+ rtFsXfsDir_SetMode,
+ rtFsXfsDir_SetTimes,
+ rtFsXfsDir_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtFsXfsDir_Open,
+ NULL /* pfnFollowAbsoluteSymlink */,
+ NULL /* pfnOpenFile */,
+ NULL /* pfnOpenDir */,
+ rtFsXfsDir_CreateDir,
+ rtFsXfsDir_OpenSymlink,
+ rtFsXfsDir_CreateSymlink,
+ NULL /* pfnQueryEntryInfo */,
+ rtFsXfsDir_UnlinkEntry,
+ rtFsXfsDir_RenameEntry,
+ rtFsXfsDir_RewindDir,
+ rtFsXfsDir_ReadDir,
+ RTVFSDIROPS_VERSION,
+};
+
+
+/**
+ * Opens a directory by the given inode.
+ *
+ * @returns IPRT status code.
+ * @param pThis The XFS volume instance.
+ * @param iInode The inode to open.
+ * @param phVfsDir Where to store the handle to the VFS directory on success.
+ */
+static int rtFsXfsVol_OpenDirByInode(PRTFSXFSVOL pThis, uint32_t iInode, PRTVFSDIR phVfsDir)
+{
+ PRTFSXFSINODE pInode = NULL;
+ int rc = rtFsXfsInode_Load(pThis, iInode, &pInode);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_DIRECTORY(pInode->ObjInfo.Attr.fMode))
+ {
+ PRTFSXFSDIR pNewDir;
+ rc = RTVfsNewDir(&g_rtFsXfsDirOps, sizeof(*pNewDir), 0 /*fFlags*/, pThis->hVfsSelf, NIL_RTVFSLOCK,
+ phVfsDir, (void **)&pNewDir);
+ if (RT_SUCCESS(rc))
+ {
+ pNewDir->fNoMoreFiles = false;
+ pNewDir->pVol = pThis;
+ pNewDir->pInode = pInode;
+ }
+ }
+ else
+ rc = VERR_VFS_BOGUS_FORMAT;
+
+ if (RT_FAILURE(rc))
+ rtFsXfsInode_Release(pThis, pInode);
+ }
+
+ return rc;
+}
+
+
+
+/*
+ *
+ * Volume level code.
+ * Volume level code.
+ * Volume level code.
+ *
+ */
+
+static DECLCALLBACK(int) rtFsXfsVolAgTreeDestroy(PAVLU32NODECORE pCore, void *pvUser)
+{
+ RT_NOREF(pvUser);
+
+ PRTFSXFSAG pAg = (PRTFSXFSAG)pCore;
+ Assert(!pAg->cRefs);
+ RTMemFree(pAg);
+ return VINF_SUCCESS;
+}
+
+
+static DECLCALLBACK(int) rtFsXfsVolInodeTreeDestroy(PAVLU64NODECORE pCore, void *pvUser)
+{
+ RT_NOREF(pvUser);
+
+ PRTFSXFSINODE pInode = (PRTFSXFSINODE)pCore;
+ Assert(!pInode->cRefs);
+ RTMemFree(pInode);
+ return VINF_SUCCESS;
+}
+
+
+static DECLCALLBACK(int) rtFsXfsVolBlockTreeDestroy(PAVLU64NODECORE pCore, void *pvUser)
+{
+ RT_NOREF(pvUser);
+
+ PRTFSXFSBLOCKENTRY pBlock = (PRTFSXFSBLOCKENTRY)pCore;
+ Assert(!pBlock->cRefs);
+ RTMemFree(pBlock);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsXfsVol_Close(void *pvThis)
+{
+ PRTFSXFSVOL pThis = (PRTFSXFSVOL)pvThis;
+
+ /* Destroy the block group tree. */
+ RTAvlU32Destroy(&pThis->AgRoot, rtFsXfsVolAgTreeDestroy, pThis);
+ pThis->AgRoot = NULL;
+ RTListInit(&pThis->LstAgLru);
+
+ /* Destroy the inode tree. */
+ RTAvlU64Destroy(&pThis->InodeRoot, rtFsXfsVolInodeTreeDestroy, pThis);
+ pThis->InodeRoot = NULL;
+ RTListInit(&pThis->LstInodeLru);
+
+ /* Destroy the block cache tree. */
+ RTAvlU64Destroy(&pThis->BlockRoot, rtFsXfsVolBlockTreeDestroy, pThis);
+ pThis->BlockRoot = NULL;
+ RTListInit(&pThis->LstBlockLru);
+
+ /*
+ * Backing file and handles.
+ */
+ RTVfsFileRelease(pThis->hVfsBacking);
+ pThis->hVfsBacking = NIL_RTVFSFILE;
+ pThis->hVfsSelf = NIL_RTVFS;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsXfsVol_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ RT_NOREF(pvThis, pObjInfo, enmAddAttr);
+ return VERR_WRONG_TYPE;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnOpenRoot}
+ */
+static DECLCALLBACK(int) rtFsXfsVol_OpenRoot(void *pvThis, PRTVFSDIR phVfsDir)
+{
+ PRTFSXFSVOL pThis = (PRTFSXFSVOL)pvThis;
+ int rc = rtFsXfsVol_OpenDirByInode(pThis, pThis->uInodeRoot, phVfsDir);
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnQueryRangeState}
+ */
+static DECLCALLBACK(int) rtFsXfsVol_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_rtFsXfsVolOps =
+{
+ /* .Obj = */
+ {
+ /* .uVersion = */ RTVFSOBJOPS_VERSION,
+ /* .enmType = */ RTVFSOBJTYPE_VFS,
+ /* .pszName = */ "XfsVol",
+ /* .pfnClose = */ rtFsXfsVol_Close,
+ /* .pfnQueryInfo = */ rtFsXfsVol_QueryInfo,
+ /* .pfnQueryInfoEx = */ NULL,
+ /* .uEndMarker = */ RTVFSOBJOPS_VERSION
+ },
+ /* .uVersion = */ RTVFSOPS_VERSION,
+ /* .fFeatures = */ 0,
+ /* .pfnOpenRoot = */ rtFsXfsVol_OpenRoot,
+ /* .pfnQueryRangeState = */ rtFsXfsVol_QueryRangeState,
+ /* .uEndMarker = */ RTVFSOPS_VERSION
+};
+
+
+/**
+ * Loads and parses the AGI block.
+ *
+ * @returns IPRT status code.
+ * @param pThis The XFS volume instance.
+ * @param pErrInfo Where to return additional error info.
+ */
+static int rtFsXfsVolLoadAgi(PRTFSXFSVOL pThis, PRTERRINFO pErrInfo)
+{
+ XFSAGI Agi;
+ int rc = RTVfsFileReadAt(pThis->hVfsBacking, 2 * pThis->cbSector, &Agi, sizeof(&Agi), NULL);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ rtFsXfsAgi_Log(0, &Agi);
+#endif
+
+ /** @todo Verification */
+ RT_NOREF(pErrInfo);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Loads and parses the superblock of the filesystem.
+ *
+ * @returns IPRT status code.
+ * @param pThis The XFS volume instance.
+ * @param pErrInfo Where to return additional error info.
+ */
+static int rtFsXfsVolLoadAndParseSuperblock(PRTFSXFSVOL pThis, PRTERRINFO pErrInfo)
+{
+ int rc = VINF_SUCCESS;
+ XFSSUPERBLOCK Sb;
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, XFS_SB_OFFSET, &Sb, sizeof(XFSSUPERBLOCK), NULL);
+ if (RT_FAILURE(rc))
+ return RTERRINFO_LOG_SET(pErrInfo, rc, "Error reading super block");
+
+ /* Validate the superblock. */
+ if (RT_BE2H_U32(Sb.u32Magic) != XFS_SB_MAGIC)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "Not XFS - Signature mismatch: %RX32", RT_BE2H_U32(Sb.u32Magic));
+
+#ifdef LOG_ENABLED
+ rtFsXfsSb_Log(0, &Sb);
+#endif
+
+ /** @todo More verification */
+ pThis->cbSector = RT_BE2H_U32(Sb.cbSector);
+ pThis->cbBlock = RT_BE2H_U32(Sb.cbBlock);
+ pThis->cBlockShift = Sb.cBlockSzLog;
+ pThis->cBlocksPerAg = RT_BE2H_U32(Sb.cAgBlocks);
+ pThis->cAgs = RT_BE2H_U32(Sb.cAg);
+ pThis->uInodeRoot = RT_BE2H_U64(Sb.uInodeRoot);
+ pThis->cbInode = RT_BE2H_U16(Sb.cbInode);
+ pThis->cInodesPerBlock = RT_BE2H_U16(Sb.cInodesPerBlock);
+ pThis->cAgBlocksLog = Sb.cAgBlocksLog;
+ pThis->cInodesPerBlockLog = Sb.cInodesPerBlockLog;
+ return rc;
+}
+
+
+RTDECL(int) RTFsXfsVolOpen(RTVFSFILE hVfsFileIn, uint32_t fMntFlags, uint32_t fXfsFlags, PRTVFS phVfs, PRTERRINFO pErrInfo)
+{
+ AssertPtrReturn(phVfs, VERR_INVALID_POINTER);
+ AssertReturn(!(fMntFlags & ~RTVFSMNT_F_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertReturn(!fXfsFlags, VERR_INVALID_FLAGS);
+
+ uint32_t cRefs = RTVfsFileRetain(hVfsFileIn);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Create a VFS instance and initialize the data so rtFsXfsVol_Close works.
+ */
+ RTVFS hVfs;
+ PRTFSXFSVOL pThis;
+ int rc = RTVfsNew(&g_rtFsXfsVolOps, sizeof(*pThis), NIL_RTVFS, RTVFSLOCK_CREATE_RW, &hVfs, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->hVfsBacking = hVfsFileIn;
+ pThis->hVfsSelf = hVfs;
+ pThis->fMntFlags = fMntFlags;
+ pThis->fXfsFlags = fXfsFlags;
+ pThis->AgRoot = NULL;
+ pThis->InodeRoot = NULL;
+ pThis->BlockRoot = NULL;
+ pThis->cbAgs = 0;
+ pThis->cbInodes = 0;
+ pThis->cbBlocks = 0;
+ RTListInit(&pThis->LstAgLru);
+ RTListInit(&pThis->LstInodeLru);
+ RTListInit(&pThis->LstBlockLru);
+
+ rc = RTVfsFileQuerySize(pThis->hVfsBacking, &pThis->cbBacking);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsXfsVolLoadAndParseSuperblock(pThis, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFsXfsVolLoadAgi(pThis, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfs = hVfs;
+ return VINF_SUCCESS;
+ }
+ }
+
+ RTVfsRelease(hVfs);
+ *phVfs = NIL_RTVFS;
+ }
+ else
+ RTVfsFileRelease(hVfsFileIn);
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate}
+ */
+static DECLCALLBACK(int) rtVfsChainXfsVol_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec,
+ PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg);
+
+ /*
+ * 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.
+ */
+ bool fReadOnly = (pSpec->fOpenFile & RTFILE_O_ACCESS_MASK) == RTFILE_O_READ;
+ if (pElement->cArgs > 0)
+ {
+ const char *psz = pElement->paArgs[0].psz;
+ if (*psz)
+ {
+ if (!strcmp(psz, "ro"))
+ fReadOnly = true;
+ else if (!strcmp(psz, "rw"))
+ fReadOnly = false;
+ else
+ {
+ *poffError = pElement->paArgs[0].offSpec;
+ return RTErrInfoSet(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Expected 'ro' or 'rw' as argument");
+ }
+ }
+ }
+
+ pElement->uProvider = fReadOnly ? RTVFSMNT_F_READ_ONLY : 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate}
+ */
+static DECLCALLBACK(int) rtVfsChainXfsVol_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 = RTFsXfsVolOpen(hVfsFileIn, (uint32_t)pElement->uProvider, (uint32_t)(pElement->uProvider >> 32), &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) rtVfsChainXfsVol_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 'xfs'. */
+static RTVFSCHAINELEMENTREG g_rtVfsChainXfsVolReg =
+{
+ /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION,
+ /* fReserved = */ 0,
+ /* pszName = */ "xfs",
+ /* ListEntry = */ { NULL, NULL },
+ /* pszHelp = */ "Open a XFS file system, requires a file object on the left side.\n"
+ "First argument is an optional 'ro' (read-only) or 'rw' (read-write) flag.\n",
+ /* pfnValidate = */ rtVfsChainXfsVol_Validate,
+ /* pfnInstantiate = */ rtVfsChainXfsVol_Instantiate,
+ /* pfnCanReuseElement = */ rtVfsChainXfsVol_CanReuseElement,
+ /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION
+};
+
+RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainXfsVolReg, rtVfsChainXfsVolReg);
+