diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 03:01:46 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 03:01:46 +0000 |
commit | f8fe689a81f906d1b91bb3220acde2a4ecb14c5b (patch) | |
tree | 26484e9d7e2c67806c2d1760196ff01aaa858e8c /src/VBox/Runtime/common/fs | |
parent | Initial commit. (diff) | |
download | virtualbox-f8fe689a81f906d1b91bb3220acde2a4ecb14c5b.tar.xz virtualbox-f8fe689a81f906d1b91bb3220acde2a4ecb14c5b.zip |
Adding upstream version 6.0.4-dfsg.upstream/6.0.4-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Runtime/common/fs')
-rw-r--r-- | src/VBox/Runtime/common/fs/Makefile.kup | 0 | ||||
-rw-r--r-- | src/VBox/Runtime/common/fs/RTFsCmdLs.cpp | 1836 | ||||
-rw-r--r-- | src/VBox/Runtime/common/fs/extvfs.cpp | 2853 | ||||
-rw-r--r-- | src/VBox/Runtime/common/fs/fatvfs.cpp | 6345 | ||||
-rw-r--r-- | src/VBox/Runtime/common/fs/isomaker.cpp | 7438 | ||||
-rw-r--r-- | src/VBox/Runtime/common/fs/isomakercmd-man.xml | 569 | ||||
-rw-r--r-- | src/VBox/Runtime/common/fs/isomakercmd.cpp | 3594 | ||||
-rw-r--r-- | src/VBox/Runtime/common/fs/isomakerimport.cpp | 2720 | ||||
-rw-r--r-- | src/VBox/Runtime/common/fs/isovfs.cpp | 5992 | ||||
-rw-r--r-- | src/VBox/Runtime/common/fs/ntfsvfs.cpp | 5683 | ||||
-rw-r--r-- | src/VBox/Runtime/common/fs/xfsvfs.cpp | 2240 |
11 files changed, 39270 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..1eb8e122 --- /dev/null +++ b/src/VBox/Runtime/common/fs/RTFsCmdLs.cpp @@ -0,0 +1,1836 @@ +/* $Id: RTFsCmdLs.cpp $ */ +/** @file + * IPRT - /bin/ls like utility for testing the VFS code. + */ + +/* + * Copyright (C) 2017-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#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. */ + 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). */ + 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\nOpts.on dump:\n"); + for (unsigned i = 0; i < RT_ELEMENTS(s_aOptions); i++) + RTPrintf(" -%c,%s\n", s_aOptions[i].iShort, s_aOptions[i].pszLong); + 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..5159db55 --- /dev/null +++ b/src/VBox/Runtime/common/fs/extvfs.cpp @@ -0,0 +1,2853 @@ +/* $Id: extvfs.cpp $ */ +/** @file + * IPRT - Ext2/3/4 Virtual Filesystem. + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FS +#include <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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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 + + +/** + * 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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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; + } + } + } + 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. + * + * @returns nothing. + * @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, + 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(pInode, &pDirEntry->Info, enmAddAttr); + if (RT_SUCCESS(rc)) + { + pThis->offEntry += RT_LE2H_U16(DirEntry.Core.cbRecord); + pThis->idxEntry++; + rtFsExtInodeRelease(pThis->pVol, pInode); + return VINF_SUCCESS; + } + rtFsExtInodeRelease(pThis->pVol, pInode); + } + } + 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, + 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, + /* .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 = RTVfsFileGetSize(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..bb08ae0c --- /dev/null +++ b/src/VBox/Runtime/common/fs/fatvfs.cpp @@ -0,0 +1,6345 @@ +/* $Id: fatvfs.cpp $ */ +/** @file + * IPRT - FAT Virtual Filesystem. + */ + +/* + * Copyright (C) 2017-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FS +#include "internal/iprt.h" +#include <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. */ + 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]; + 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 ( idxCluster >= pFatCache->cClusters + || idxCluster < FAT_FIRST_DATA_CLUSTER) + { + if (idxCluster >= FAT_FIRST_FAT12_EOC) + return VINF_SUCCESS; + 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 ( idxCluster >= pFatCache->cClusters + || idxCluster < FAT_FIRST_DATA_CLUSTER) + { + if (idxCluster >= FAT_FIRST_FAT16_EOC) + return VINF_SUCCESS; + 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 ( idxCluster >= pFatCache->cClusters + || idxCluster < FAT_FIRST_DATA_CLUSTER) + { + if (idxCluster >= FAT_FIRST_FAT32_EOC) + return VINF_SUCCESS; + 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); + 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, + 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 directory. + * + * @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, + 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, + 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 = RTVfsFileGetSize(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 = RTVfsFileGetSize(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 19h */ + pbBuf[pBootSector->abJmp[1] + 2 + 1] = 0x19; + 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*/); +} + + +/** + * @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..8c75381d --- /dev/null +++ b/src/VBox/Runtime/common/fs/isomaker.cpp @@ -0,0 +1,7438 @@ +/* $Id: isomaker.cpp $ */ +/** @file + * IPRT - ISO Image Maker. + */ + +/* + * Copyright (C) 2017-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FS +#include "internal/iprt.h" +#include <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. */ + 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. */ + 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. */ + 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; + + + +/********************************************************************************************************************************* +* 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, 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; +} + + +/** + * 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++; + AssertReturn(*pszPath, VERR_ISOMK_IPE_EMPTY_PATH); + + 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 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 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 = pThis->PrimaryIso.uLevel; + size_t cchDst; + size_t offDstDot; + if (fIsDir) + 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 (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 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 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, 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), &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 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, 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, + 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, &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, &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, &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, 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; +} + + + + + + +/* + * + * 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; + 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. + */ +RTDECL(int) RTFsIsoMakerObjSetNameAndParent(RTFSISOMAKER hIsoMaker, uint32_t idxObj, uint32_t idxParentObj, + uint32_t fNamespaces, const char *pszName) +{ + /* + * 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, 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; + pObj->fMode = pObjInfo->Attr.fMode; + 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)) + 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) ) + { + 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). */ + 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); + AssertReturn(pThis->PrimaryIso.pRoot || pThis->PrimaryIso.uLevel == 0, VERR_NO_DATA); + AssertReturn(pThis->Joliet.pRoot || pThis->Joliet.uLevel == 0, VERR_NO_DATA); + + 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). + */ +static void rtFsIosMakerOutFile_GenerateRockRidge(PRTFSISOMAKERNAME pName, uint8_t *pbSys, size_t cbSys, bool fInSpill) +{ + /* + * 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; + 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); + rtFsIosMakerOutFile_GenerateRockRidge(pDir->pName, pbBuf, cbToRead, true /*fInSpill*/); + 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 = RTVfsFileGetSize(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. + */ +static uint32_t rtFsIsoMakerOutFile_GenerateDirRec(PRTFSISOMAKERNAME pName, bool fUnicode, uint8_t *pbBuf, + PRTFSISOMAKERFINALIZEDDIRS pFinalizedDirs) +{ + /* + * 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*/); + 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); + 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); + 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); + Assert(cbToCopy == pName->cbDirRec); + + /* 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, + 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..b119feac --- /dev/null +++ b/src/VBox/Runtime/common/fs/isomakercmd-man.xml @@ -0,0 +1,569 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + manpage for RTIsoMaker/VISO + + Copyright (C) 2006-2017 Oracle Corporation + + This file is part of VirtualBox Open Source Edition (OSE), as + available from http://www.virtualbox.org. This file is free software; + you can redistribute it and/or modify it under the terms of the GNU + General Public License (GPL) as published by the Free Software + Foundation, in version 2 as it comes in the "COPYING" file of the + VirtualBox OSE distribution. VirtualBox OSE is distributed in the + hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + + The contents of this file may alternatively be used under the terms + of the Common Development and Distribution License Version 1.0 + (CDDL) only, as it comes in the "COPYING.CDDL" file of the + VirtualBox OSE distribution, in which case the provisions of the + CDDL are applicable instead of those of the GPL. + + You may elect to license modified versions of this file under the + terms and conditions of either the GPL or the CDDL or both. + --> +<!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: 2018-07-20 19:42:10 +0200 (Fri, 20 Jul 2018) $</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>--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 & 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 & 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..adebd11b --- /dev/null +++ b/src/VBox/Runtime/common/fs/isomakercmd.cpp @@ -0,0 +1,3594 @@ +/* $Id: isomakercmd.cpp $ */ +/** @file + * IPRT - ISO Image Maker Command. + */ + +/* + * Copyright (C) 2017-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FS +#include "internal/iprt.h" +#include <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_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 }, + { "--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, cBlocks, iBlock); + Assert(iHit < (int32_t)cBlocks); + if (iHit < 0) + { + iHit = ASMBitFirstClear(pvBitmap, iBlock); + 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 = RTVfsFileGetSize(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, "RTVfsFileGetSize 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; +} + + +/** + * 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. + */ +static int rtFsIsoMakerCmdAddVfsDirRecursive(PRTFSISOMAKERCMDOPTS pOpts, RTVFSDIR hVfsDir, uint32_t idxDirObj, + char *pszSrc, size_t cchSrc, uint32_t fNamespaces, uint8_t cDepth) +{ + /* + * 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 added with VFS file sources. + * The ASSUMPTION is that we're working with a virtual file system + * here and won't be wasting native file descriptors. + */ + 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_SUCCESS(rc)) + { + pOpts->cItemsAdded++; + rc = RTFsIsoMakerObjSetNameAndParent(pOpts->hIsoMaker, idxObj, idxDirObj, fNamespaces, + pDirEntry->szName); + if (RT_FAILURE(rc)) + rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error setting parent & name on file '%s' to '%s': %Rrc", + pszSrc, pDirEntry->szName, rc); + } + else + rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error adding file '%s' (VFS recursive): %Rrc", pszSrc, rc); + } + else + rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error opening file '%s' (VFS recursive): %Rrc", pszSrc, 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); + if (RT_SUCCESS(rc)) + /* Recurse into the sub-directory. */ + rc = rtFsIsoMakerCmdAddVfsDirRecursive(pOpts, hVfsSubDirSrc, idxObj, pszSrc, + cchSrc + 1 + pDirEntry->cbName, fNamespaces, cDepth + 1); + 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 pidxObj Where to return the configuration index for the + * added file. Optional. + */ +static int rtFsIsoMakerCmdAddVfsDirCommon(PRTFSISOMAKERCMDOPTS pOpts, RTVFSDIR hVfsDirSrc, char *pszSrc, + PCRTFSISOMKCMDPARSEDNAMES pParsed, 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*/); + } + + 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, 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, 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, true /*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 = RTVfsFileGetSize(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: RTVfsFileGetSize 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_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..6d598e56 --- /dev/null +++ b/src/VBox/Runtime/common/fs/isomakerimport.cpp @@ -0,0 +1,2720 @@ +/* $Id: isomakerimport.cpp $ */ +/** @file + * IPRT - ISO Image Maker, Import Existing Image. + */ + +/* + * Copyright (C) 2017-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FS +#include "internal/iprt.h" +#include <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); + if (RT_SUCCESS(rc)) + { + pThis->pResults->cAddedNames++; + + if (*pszRockName != '\0' && strcmp(pszName, pszRockName) != 0) + { + rc = RTFsIsoMakerObjSetRockName(pThis->hIsoMaker, idxObj, fNamespace, pszRockName); + if (RT_FAILURE(rc)) + rc = rtFsIsoImpError(pThis, rc, "Error setting rock ridge name for symlink '%s' to '%s'", pszName, pszRockName); + } + } + else + rc = rtFsIsoImpError(pThis, rc, "Error naming symlink '%s' (-> %s): %Rrc", pszName, pszTarget, rc); + } + else + rc = rtFsIsoImpError(pThis, rc, "Error adding symbolic link '%s' (-> %s): %Rrc", pszName, pszTarget, rc); + return rc; +} + + + +/** + * Adds a directory and names it given its ISO-9660 directory record and parent. + * + * @returns IPRT status code (safe to ignore). + * @param pThis The importer instance. + * @param pDirRec The directory record. + * @param pObjInfo Object information. + * @param cbData The actual directory data size. (Always same as in the + * directory record, but this what we do for files below.) + * @param fNamespace The namespace flag. + * @param idxParent Parent directory. + * @param pszName The name. + * @param pszRockName The rock ridge name. Empty if not present. + * @param cDepth The depth to add it with. + * @param pTodoList The todo list (for directories). + */ +static int rtFsIsoImportProcessIso9660AddAndNameDirectory(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec, + PCRTFSOBJINFO pObjInfo, uint64_t cbData, + uint32_t fNamespace, uint32_t idxParent, const char *pszName, + const char *pszRockName, uint8_t cDepth, PRTLISTANCHOR pTodoList) +{ + Assert(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY); + uint32_t idxObj; + int rc = RTFsIsoMakerAddUnnamedDir(pThis->hIsoMaker, pObjInfo, &idxObj); + if (RT_SUCCESS(rc)) + { + Log3((" --> added directory #%#x\n", idxObj)); + pThis->pResults->cAddedDirs++; + + /* + * Enter the object into the namespace. + */ + rc = RTFsIsoMakerObjSetNameAndParent(pThis->hIsoMaker, idxObj, idxParent, fNamespace, pszName); + if (RT_SUCCESS(rc)) + { + pThis->pResults->cAddedNames++; + + if (*pszRockName != '\0' && strcmp(pszName, pszRockName) != 0) + rc = RTFsIsoMakerObjSetRockName(pThis->hIsoMaker, idxObj, fNamespace, pszRockName); + if (RT_SUCCESS(rc)) + { + /* + * Push it onto the traversal stack. + */ + PRTFSISOMKIMPDIR pImpDir = (PRTFSISOMKIMPDIR)RTMemAlloc(sizeof(*pImpDir)); + if (pImpDir) + { + Assert((uint32_t)cbData == cbData /* no multi-extents for dirs makes it this far */); + pImpDir->cbDir = (uint32_t)cbData; + pImpDir->offDirBlock = ISO9660_GET_ENDIAN(&pDirRec->offExtent); + pImpDir->idxObj = idxObj; + pImpDir->cDepth = cDepth; + RTListAppend(pTodoList, &pImpDir->Entry); + } + else + rc = rtFsIsoImpError(pThis, VERR_NO_MEMORY, "Could not allocate RTFSISOMKIMPDIR"); + } + else + rc = rtFsIsoImpError(pThis, rc, "Error setting rock ridge name for directory '%s' to '%s'", pszName, pszRockName); + } + else + rc = rtFsIsoImpError(pThis, rc, "Error naming directory '%s': %Rrc", pszName, rc); + } + else + rc = rtFsIsoImpError(pThis, rc, "Error adding directory '%s': %Rrc", pszName, rc); + return rc; +} + + +/** + * Adds a file and names it given its ISO-9660 directory record and parent. + * + * @returns IPRT status code (safe to ignore). + * @param pThis The importer instance. + * @param pDirRec The directory record. + * @param pObjInfo Object information. + * @param cbData The actual file data size. + * @param fNamespace The namespace flag. + * @param idxParent Parent directory. + * @param pszName The name. + * @param pszRockName The rock ridge name. Empty if not present. + */ +static int rtFsIsoImportProcessIso9660AddAndNameFile(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec, PRTFSOBJINFO pObjInfo, + uint64_t cbData, uint32_t fNamespace, uint32_t idxParent, + const char *pszName, const char *pszRockName) +{ + int rc; + + /* + * First we must make sure the common source file has been added. + */ + if (pThis->idxSrcFile != UINT32_MAX) + { /* likely */ } + else + { + rc = RTFsIsoMakerAddCommonSourceFile(pThis->hIsoMaker, pThis->hSrcFile, &pThis->idxSrcFile); + if (RT_FAILURE(rc)) + return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerAddCommonSourceFile failed: %Rrc", rc); + Assert(pThis->idxSrcFile != UINT32_MAX); + } + + /* + * Lookup the data block if the file has a non-zero length. The aim is to + * find files across namespaces while bearing in mind that files in the same + * namespace may share data storage, i.e. what in a traditional unix file + * system would be called hardlinked. Problem is that the core engine doesn't + * do hardlinking yet and assume each file has exactly one name per namespace. + */ + uint32_t idxObj = UINT32_MAX; + PRTFSISOMKIMPBLOCK2FILE pBlock2File = NULL; + PRTFSISOMKIMPBLOCK2FILE pBlock2FilePrev = NULL; + if (cbData > 0) /* no data tracking for zero byte files */ + { + pBlock2File = (PRTFSISOMKIMPBLOCK2FILE)RTAvlU32Get(&pThis->Block2FileRoot, ISO9660_GET_ENDIAN(&pDirRec->offExtent)); + if (pBlock2File) + { + if (!(pBlock2File->fNamespaces & fNamespace)) + { + pBlock2File->fNamespaces |= fNamespace; + idxObj = pBlock2File->idxObj; + } + else + { + do + { + pBlock2FilePrev = pBlock2File; + pBlock2File = pBlock2File->pNext; + } while (pBlock2File && (pBlock2File->fNamespaces & fNamespace)); + if (pBlock2File) + { + pBlock2File->fNamespaces |= fNamespace; + idxObj = pBlock2File->idxObj; + } + } + } + } + + /* + * If the above lookup didn't succeed, add a new file with a lookup record. + */ + if (idxObj == UINT32_MAX) + { + pObjInfo->cbObject = pObjInfo->cbAllocated = cbData; + rc = RTFsIsoMakerAddUnnamedFileWithCommonSrc(pThis->hIsoMaker, pThis->idxSrcFile, + ISO9660_GET_ENDIAN(&pDirRec->offExtent) * (uint64_t)ISO9660_SECTOR_SIZE, + cbData, pObjInfo, &idxObj); + if (RT_FAILURE(rc)) + return rtFsIsoImpError(pThis, rc, "Error adding file '%s': %Rrc", pszName, rc); + Assert(idxObj != UINT32_MAX); + + /* Update statistics. */ + pThis->pResults->cAddedFiles++; + if (cbData > 0) + { + pThis->pResults->cbAddedDataBlocks += RT_ALIGN_64(cbData, ISO9660_SECTOR_SIZE); + + /* Lookup record. */ + pBlock2File = (PRTFSISOMKIMPBLOCK2FILE)RTMemAlloc(sizeof(*pBlock2File)); + AssertReturn(pBlock2File, rtFsIsoImpError(pThis, VERR_NO_MEMORY, "Could not allocate RTFSISOMKIMPBLOCK2FILE")); + + pBlock2File->idxObj = idxObj; + pBlock2File->Core.Key = ISO9660_GET_ENDIAN(&pDirRec->offExtent); + pBlock2File->fNamespaces = fNamespace; + pBlock2File->pNext = NULL; + if (!pBlock2FilePrev) + { + bool fRc = RTAvlU32Insert(&pThis->Block2FileRoot, &pBlock2File->Core); + Assert(fRc); RT_NOREF(fRc); + } + else + { + pBlock2File->Core.pLeft = NULL; + pBlock2File->Core.pRight = NULL; + pBlock2FilePrev->pNext = pBlock2File; + } + } + } + + /* + * Enter the object into the namespace. + */ + rc = RTFsIsoMakerObjSetNameAndParent(pThis->hIsoMaker, idxObj, idxParent, fNamespace, pszName); + if (RT_SUCCESS(rc)) + { + pThis->pResults->cAddedNames++; + + if (*pszRockName != '\0' && strcmp(pszName, pszRockName) != 0) + { + rc = RTFsIsoMakerObjSetRockName(pThis->hIsoMaker, idxObj, fNamespace, pszRockName); + if (RT_FAILURE(rc)) + rc = rtFsIsoImpError(pThis, rc, "Error setting rock ridge name for file '%s' to '%s'", pszName, pszRockName); + } + } + else + return rtFsIsoImpError(pThis, rc, "Error naming file '%s': %Rrc", pszName, rc); + return VINF_SUCCESS; +} + + +/** + * Parses rock ridge information if present in the directory entry. + * + * @param pThis The importer instance. + * @param pObjInfo The object information to improve upon. + * @param pbSys The system area of the directory record. + * @param cbSys The number of bytes present in the sys area. + * @param fUnicode Indicates which namespace we're working on. + * @param fIsFirstDirRec Set if this is the '.' directory entry in the + * root directory. (Some entries applies only to + * it.) + * @param fContinuationRecord Set if we're processing a continuation record in + * living in the abRockBuf. + */ +static void rtFsIsoImportProcessIso9660TreeWorkerParseRockRidge(PRTFSISOMKIMPORTER pThis, PRTFSOBJINFO pObjInfo, + uint8_t const *pbSys, size_t cbSys, bool fUnicode, + bool fIsFirstDirRec, bool fContinuationRecord) +{ + RT_NOREF(pObjInfo); + + /* + * Do skipping if specified. + */ + if (pThis->offSuspSkip) + { + if (cbSys <= pThis->offSuspSkip) + return; + pbSys += pThis->offSuspSkip; + cbSys -= pThis->offSuspSkip; + } + + while (cbSys >= 4) + { + /* + * Check header length and advance the sys variables. + */ + PCISO9660SUSPUNION pUnion = (PCISO9660SUSPUNION)pbSys; + if (pUnion->Hdr.cbEntry > cbSys) + { + LogRel(("rtFsIsoImportProcessIso9660TreeWorkerParseRockRidge: cbEntry=%#x cbSys=%#x (%#x %#x)\n", + pUnion->Hdr.cbEntry, cbSys, pUnion->Hdr.bSig1, pUnion->Hdr.bSig2)); + return; + } + pbSys += pUnion->Hdr.cbEntry; + cbSys -= pUnion->Hdr.cbEntry; + + /* + * Process fields. + */ +#define MAKE_SIG(a_bSig1, a_bSig2) \ + ( ((uint16_t)(a_bSig1) & 0x1f) \ + | (((uint16_t)(a_bSig2) ^ 0x40) << 5) \ + | ((((uint16_t)(a_bSig1) ^ 0x40) & 0xe0) << (5 + 8)) ) + + uint16_t const uSig = MAKE_SIG(pUnion->Hdr.bSig1, pUnion->Hdr.bSig2); + switch (uSig) + { + /* + * System use sharing protocol entries. + */ + case MAKE_SIG(ISO9660SUSPCE_SIG1, ISO9660SUSPCE_SIG2): + { + if (RT_BE2H_U32(pUnion->CE.offBlock.be) != RT_LE2H_U32(pUnion->CE.offBlock.le)) + LogRel(("rtFsIsoImport/Rock: Invalid CE offBlock field: be=%#x vs le=%#x\n", + RT_BE2H_U32(pUnion->CE.offBlock.be), RT_LE2H_U32(pUnion->CE.offBlock.le))); + else if (RT_BE2H_U32(pUnion->CE.cbData.be) != RT_LE2H_U32(pUnion->CE.cbData.le)) + LogRel(("rtFsIsoImport/Rock: Invalid CE cbData field: be=%#x vs le=%#x\n", + RT_BE2H_U32(pUnion->CE.cbData.be), RT_LE2H_U32(pUnion->CE.cbData.le))); + else if (RT_BE2H_U32(pUnion->CE.offData.be) != RT_LE2H_U32(pUnion->CE.offData.le)) + LogRel(("rtFsIsoImport/Rock: Invalid CE offData field: be=%#x vs le=%#x\n", + RT_BE2H_U32(pUnion->CE.offData.be), RT_LE2H_U32(pUnion->CE.offData.le))); + else if (!fContinuationRecord) + { + uint64_t offData = ISO9660_GET_ENDIAN(&pUnion->CE.offBlock) * (uint64_t)ISO9660_SECTOR_SIZE; + offData += ISO9660_GET_ENDIAN(&pUnion->CE.offData); + uint32_t cbData = ISO9660_GET_ENDIAN(&pUnion->CE.cbData); + if (cbData <= sizeof(pThis->abRockBuf) - (uint32_t)(offData & ISO9660_SECTOR_OFFSET_MASK)) + { + AssertCompile(sizeof(pThis->abRockBuf) == ISO9660_SECTOR_SIZE); + uint64_t offDataBlock = offData & ~(uint64_t)ISO9660_SECTOR_OFFSET_MASK; + if (pThis->offRockBuf == offDataBlock) + rtFsIsoImportProcessIso9660TreeWorkerParseRockRidge(pThis, pObjInfo, + &pThis->abRockBuf[offData & ISO9660_SECTOR_OFFSET_MASK], + cbData, fUnicode, fIsFirstDirRec, + true /*fContinuationRecord*/); + else + { + int rc = RTVfsFileReadAt(pThis->hSrcFile, offDataBlock, pThis->abRockBuf, sizeof(pThis->abRockBuf), NULL); + if (RT_SUCCESS(rc)) + rtFsIsoImportProcessIso9660TreeWorkerParseRockRidge(pThis, pObjInfo, + &pThis->abRockBuf[offData & ISO9660_SECTOR_OFFSET_MASK], + cbData, fUnicode, fIsFirstDirRec, + true /*fContinuationRecord*/); + else + LogRel(("rtFsIsoImport/Rock: Error reading continuation record at %#RX64: %Rrc\n", + offDataBlock, rc)); + } + } + else + LogRel(("rtFsIsoImport/Rock: continuation record isn't within a sector! offData=%#RX64 cbData=%#RX32\n", + cbData, offData)); + } + else + LogRel(("rtFsIsoImport/Rock: nested continuation record!\n")); + break; + } + + case MAKE_SIG(ISO9660SUSPSP_SIG1, ISO9660SUSPSP_SIG2): /* SP */ + if ( pUnion->Hdr.cbEntry != ISO9660SUSPSP_LEN + || pUnion->Hdr.bVersion != ISO9660SUSPSP_VER + || pUnion->SP.bCheck1 != ISO9660SUSPSP_CHECK1 + || pUnion->SP.bCheck2 != ISO9660SUSPSP_CHECK2 + || pUnion->SP.cbSkip > UINT8_MAX - RT_UOFFSETOF(ISO9660DIRREC, achFileId[1])) + LogRel(("rtFsIsoImport/Rock: Malformed 'SP' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x), bCheck1=%#x (vs %#x), bCheck2=%#x (vs %#x), cbSkip=%#x (vs max %#x)\n", + pUnion->Hdr.cbEntry, ISO9660SUSPSP_LEN, pUnion->Hdr.bVersion, ISO9660SUSPSP_VER, + pUnion->SP.bCheck1, ISO9660SUSPSP_CHECK1, pUnion->SP.bCheck2, ISO9660SUSPSP_CHECK2, + pUnion->SP.cbSkip, UINT8_MAX - RT_UOFFSETOF(ISO9660DIRREC, achFileId[1]) )); + else if (!fIsFirstDirRec) + LogRel(("rtFsIsoImport/Rock: Ignorining 'SP' entry in non-root directory record\n")); + else if (pThis->fSuspSeenSP) + LogRel(("rtFsIsoImport/Rock: Ignorining additional 'SP' entry\n")); + else + { + pThis->offSuspSkip = pUnion->SP.cbSkip; + if (pUnion->SP.cbSkip != 0) + LogRel(("rtFsIsoImport/Rock: SP: cbSkip=%#x\n", pUnion->SP.cbSkip)); + } + break; + + case MAKE_SIG(ISO9660SUSPER_SIG1, ISO9660SUSPER_SIG2): /* ER */ + if ( pUnion->Hdr.cbEntry > RT_UOFFSETOF(ISO9660SUSPER, achPayload) + (uint32_t)pUnion->ER.cchIdentifier + + (uint32_t)pUnion->ER.cchDescription + (uint32_t)pUnion->ER.cchSource + || pUnion->Hdr.bVersion != ISO9660SUSPER_VER) + LogRel(("rtFsIsoImport/Rock: Malformed 'ER' entry: cbEntry=%#x bVersion=%#x (vs %#x) cchIdentifier=%#x cchDescription=%#x cchSource=%#x\n", + pUnion->Hdr.cbEntry, pUnion->Hdr.bVersion, ISO9660SUSPER_VER, pUnion->ER.cchIdentifier, + pUnion->ER.cchDescription, pUnion->ER.cchSource)); + else if (!fIsFirstDirRec) + LogRel(("rtFsIsoImport/Rock: Ignorining 'ER' entry in non-root directory record\n")); + else if ( pUnion->ER.bVersion == 1 /* RRIP detection */ + && ( (pUnion->ER.cchIdentifier >= 4 && strncmp(pUnion->ER.achPayload, ISO9660_RRIP_ID, 4 /*RRIP*/) == 0) + || (pUnion->ER.cchIdentifier >= 10 && strncmp(pUnion->ER.achPayload, RT_STR_TUPLE(ISO9660_RRIP_1_12_ID)) == 0) )) + { + LogRel(("rtFsIsoImport/Rock: Rock Ridge 'ER' entry: v%u id='%.*s' desc='%.*s' source='%.*s'\n", + pUnion->ER.bVersion, pUnion->ER.cchIdentifier, pUnion->ER.achPayload, + pUnion->ER.cchDescription, &pUnion->ER.achPayload[pUnion->ER.cchIdentifier], + pUnion->ER.cchSource, &pUnion->ER.achPayload[pUnion->ER.cchIdentifier + pUnion->ER.cchDescription])); + if (!fUnicode) + { + int rc = RTFsIsoMakerSetRockRidgeLevel(pThis->hIsoMaker, 2); + if (RT_FAILURE(rc)) + LogRel(("rtFsIsoImport/Rock: RTFsIsoMakerSetRockRidgeLevel(,2) failed: %Rrc\n", rc)); + } + else + { + int rc = RTFsIsoMakerSetJolietRockRidgeLevel(pThis->hIsoMaker, 2); + if (RT_FAILURE(rc)) + LogRel(("rtFsIsoImport/Rock: RTFsIsoMakerSetJolietRockRidgeLevel(,2) failed: %Rrc\n", rc)); + } + } + else + LogRel(("rtFsIsoImport/Rock: Unknown extension in 'ER' entry: v%u id='%.*s' desc='%.*s' source='%.*s'\n", + pUnion->ER.bVersion, pUnion->ER.cchIdentifier, pUnion->ER.achPayload, + pUnion->ER.cchDescription, &pUnion->ER.achPayload[pUnion->ER.cchIdentifier], + pUnion->ER.cchSource, &pUnion->ER.achPayload[pUnion->ER.cchIdentifier + pUnion->ER.cchDescription])); + break; + + case MAKE_SIG(ISO9660SUSPPD_SIG1, ISO9660SUSPPD_SIG2): /* PD - ignored */ + case MAKE_SIG(ISO9660SUSPST_SIG1, ISO9660SUSPST_SIG2): /* ST - ignore for now */ + case MAKE_SIG(ISO9660SUSPES_SIG1, ISO9660SUSPES_SIG2): /* ES - ignore for now */ + break; + + /* + * Rock ridge interchange protocol entries. + */ + case MAKE_SIG(ISO9660RRIPRR_SIG1, ISO9660RRIPRR_SIG2): /* RR */ + if ( pUnion->RR.Hdr.cbEntry != ISO9660RRIPRR_LEN + || pUnion->RR.Hdr.bVersion != ISO9660RRIPRR_VER) + LogRel(("rtFsIsoImport/Rock: Malformed 'RR' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) fFlags=%#x\n", + pUnion->RR.Hdr.cbEntry, ISO9660RRIPRR_LEN, pUnion->RR.Hdr.bVersion, ISO9660RRIPRR_VER, pUnion->RR.fFlags)); + /* else: ignore it */ + break; + + case MAKE_SIG(ISO9660RRIPPX_SIG1, ISO9660RRIPPX_SIG2): /* PX */ + if ( ( pUnion->PX.Hdr.cbEntry != ISO9660RRIPPX_LEN + && pUnion->PX.Hdr.cbEntry != ISO9660RRIPPX_LEN_NO_INODE) + || pUnion->PX.Hdr.bVersion != ISO9660RRIPPX_VER + || RT_BE2H_U32(pUnion->PX.fMode.be) != RT_LE2H_U32(pUnion->PX.fMode.le) + || RT_BE2H_U32(pUnion->PX.cHardlinks.be) != RT_LE2H_U32(pUnion->PX.cHardlinks.le) + || RT_BE2H_U32(pUnion->PX.uid.be) != RT_LE2H_U32(pUnion->PX.uid.le) + || RT_BE2H_U32(pUnion->PX.gid.be) != RT_LE2H_U32(pUnion->PX.gid.le) + || ( pUnion->PX.Hdr.cbEntry == ISO9660RRIPPX_LEN + && RT_BE2H_U32(pUnion->PX.INode.be) != RT_LE2H_U32(pUnion->PX.INode.le)) ) + LogRel(("rtFsIsoImport/Rock: Malformed 'PX' entry: cbEntry=%#x (vs %#x or %#x), bVersion=%#x (vs %#x) fMode=%#x/%#x cHardlinks=%#x/%#x uid=%#x/%#x gid=%#x/%#x inode=%#x/%#x\n", + pUnion->PX.Hdr.cbEntry, ISO9660RRIPPX_LEN, ISO9660RRIPPX_LEN_NO_INODE, + pUnion->PX.Hdr.bVersion, ISO9660RRIPPX_VER, + RT_BE2H_U32(pUnion->PX.fMode.be), RT_LE2H_U32(pUnion->PX.fMode.le), + RT_BE2H_U32(pUnion->PX.cHardlinks.be), RT_LE2H_U32(pUnion->PX.cHardlinks.le), + RT_BE2H_U32(pUnion->PX.uid.be), RT_LE2H_U32(pUnion->PX.uid.le), + RT_BE2H_U32(pUnion->PX.gid.be), RT_LE2H_U32(pUnion->PX.gid.le), + pUnion->PX.Hdr.cbEntry == ISO9660RRIPPX_LEN ? RT_BE2H_U32(pUnion->PX.INode.be) : 0, + pUnion->PX.Hdr.cbEntry == ISO9660RRIPPX_LEN ? RT_LE2H_U32(pUnion->PX.INode.le) : 0 )); + else + { + if (RTFS_IS_DIRECTORY(ISO9660_GET_ENDIAN(&pUnion->PX.fMode)) == RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode)) + pObjInfo->Attr.fMode = ISO9660_GET_ENDIAN(&pUnion->PX.fMode); + else + LogRel(("rtFsIsoImport/Rock: 'PX' entry changes directory-ness: fMode=%#x, existing %#x; ignored\n", + ISO9660_GET_ENDIAN(&pUnion->PX.fMode), pObjInfo->Attr.fMode)); + pObjInfo->Attr.u.Unix.cHardlinks = ISO9660_GET_ENDIAN(&pUnion->PX.cHardlinks); + pObjInfo->Attr.u.Unix.uid = ISO9660_GET_ENDIAN(&pUnion->PX.uid); + pObjInfo->Attr.u.Unix.gid = ISO9660_GET_ENDIAN(&pUnion->PX.gid); + /* ignore inode */ + } + break; + + case MAKE_SIG(ISO9660RRIPPN_SIG1, ISO9660RRIPPN_SIG2): /* PN */ + if ( pUnion->PN.Hdr.cbEntry != ISO9660RRIPPN_LEN + || pUnion->PN.Hdr.bVersion != ISO9660RRIPPN_VER + || RT_BE2H_U32(pUnion->PN.Major.be) != RT_LE2H_U32(pUnion->PN.Major.le) + || RT_BE2H_U32(pUnion->PN.Minor.be) != RT_LE2H_U32(pUnion->PN.Minor.le)) + LogRel(("rtFsIsoImport/Rock: Malformed 'PN' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) Major=%#x/%#x Minor=%#x/%#x\n", + pUnion->PN.Hdr.cbEntry, ISO9660RRIPPN_LEN, pUnion->PN.Hdr.bVersion, ISO9660RRIPPN_VER, + RT_BE2H_U32(pUnion->PN.Major.be), RT_LE2H_U32(pUnion->PN.Major.le), + RT_BE2H_U32(pUnion->PN.Minor.be), RT_LE2H_U32(pUnion->PN.Minor.le) )); + else if (RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode)) + LogRel(("rtFsIsoImport/Rock: Ignorning 'PN' entry for directory (%#x/%#x)\n", + ISO9660_GET_ENDIAN(&pUnion->PN.Major), ISO9660_GET_ENDIAN(&pUnion->PN.Minor) )); + else + pObjInfo->Attr.u.Unix.Device = RTDEV_MAKE(ISO9660_GET_ENDIAN(&pUnion->PN.Major), + ISO9660_GET_ENDIAN(&pUnion->PN.Minor)); + break; + + case MAKE_SIG(ISO9660RRIPTF_SIG1, ISO9660RRIPTF_SIG2): /* TF */ + if ( pUnion->TF.Hdr.bVersion != ISO9660RRIPTF_VER + || pUnion->TF.Hdr.cbEntry < Iso9660RripTfCalcLength(pUnion->TF.fFlags)) + LogRel(("rtFsIsoImport/Rock: Malformed 'TF' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) fFlags=%#x\n", + pUnion->TF.Hdr.cbEntry, Iso9660RripTfCalcLength(pUnion->TF.fFlags), + pUnion->TF.Hdr.bVersion, ISO9660RRIPTF_VER, RT_BE2H_U32(pUnion->TF.fFlags) )); + else if (!(pUnion->TF.fFlags & ISO9660RRIPTF_F_LONG_FORM)) + { + PCISO9660RECTIMESTAMP pTimestamp = (PCISO9660RECTIMESTAMP)&pUnion->TF.abPayload[0]; + if (pUnion->TF.fFlags & ISO9660RRIPTF_F_BIRTH) + { + rtFsIsoImpIso9660RecDateTime2TimeSpec(&pObjInfo->BirthTime, pTimestamp); + pTimestamp++; + } + if (pUnion->TF.fFlags & ISO9660RRIPTF_F_MODIFY) + { + rtFsIsoImpIso9660RecDateTime2TimeSpec(&pObjInfo->ModificationTime, pTimestamp); + pTimestamp++; + } + if (pUnion->TF.fFlags & ISO9660RRIPTF_F_ACCESS) + { + rtFsIsoImpIso9660RecDateTime2TimeSpec(&pObjInfo->AccessTime, pTimestamp); + pTimestamp++; + } + if (pUnion->TF.fFlags & ISO9660RRIPTF_F_CHANGE) + { + rtFsIsoImpIso9660RecDateTime2TimeSpec(&pObjInfo->ChangeTime, pTimestamp); + pTimestamp++; + } + } + else + { + PCISO9660TIMESTAMP pTimestamp = (PCISO9660TIMESTAMP)&pUnion->TF.abPayload[0]; + if (pUnion->TF.fFlags & ISO9660RRIPTF_F_BIRTH) + { + rtFsIsoImpIso9660DateTime2TimeSpecIfValid(&pObjInfo->BirthTime, pTimestamp); + pTimestamp++; + } + if (pUnion->TF.fFlags & ISO9660RRIPTF_F_MODIFY) + { + rtFsIsoImpIso9660DateTime2TimeSpecIfValid(&pObjInfo->ModificationTime, pTimestamp); + pTimestamp++; + } + if (pUnion->TF.fFlags & ISO9660RRIPTF_F_ACCESS) + { + rtFsIsoImpIso9660DateTime2TimeSpecIfValid(&pObjInfo->AccessTime, pTimestamp); + pTimestamp++; + } + if (pUnion->TF.fFlags & ISO9660RRIPTF_F_CHANGE) + { + rtFsIsoImpIso9660DateTime2TimeSpecIfValid(&pObjInfo->ChangeTime, pTimestamp); + pTimestamp++; + } + } + break; + + case MAKE_SIG(ISO9660RRIPSF_SIG1, ISO9660RRIPSF_SIG2): /* SF */ + LogRel(("rtFsIsoImport/Rock: Sparse file support not yet implemented!\n")); + break; + + case MAKE_SIG(ISO9660RRIPSL_SIG1, ISO9660RRIPSL_SIG2): /* SL */ + if ( pUnion->SL.Hdr.bVersion != ISO9660RRIPSL_VER + || pUnion->SL.Hdr.cbEntry < RT_UOFFSETOF(ISO9660RRIPSL, abComponents[2]) + || (pUnion->SL.fFlags & ~ISO9660RRIP_SL_F_CONTINUE) + || (pUnion->SL.abComponents[0] & ISO9660RRIP_SL_C_RESERVED_MASK) ) + LogRel(("rtFsIsoImport/Rock: Malformed 'SL' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) fFlags=%#x comp[0].fFlags=%#x\n", + pUnion->SL.Hdr.cbEntry, RT_UOFFSETOF(ISO9660RRIPSL, abComponents[2]), + pUnion->SL.Hdr.bVersion, ISO9660RRIPSL_VER, pUnion->SL.fFlags, pUnion->SL.abComponents[0])); + else if (pThis->fSeenLastSL) + LogRel(("rtFsIsoImport/Rock: Unexpected 'SL!' entry\n")); + else + { + pThis->fSeenLastSL = !(pUnion->SL.fFlags & ISO9660RRIP_SL_F_CONTINUE); /* used in loop */ + + size_t offDst = strlen(pThis->szRockSymlinkTargetBuf); + uint8_t const *pbSrc = &pUnion->SL.abComponents[0]; + uint8_t cbSrcLeft = pUnion->SL.Hdr.cbEntry - RT_UOFFSETOF(ISO9660RRIPSL, abComponents); + while (cbSrcLeft >= 2) + { + uint8_t const fFlags = pbSrc[0]; + uint8_t cchCopy = pbSrc[1]; + uint8_t const cbSkip = cchCopy + 2; + if (cbSkip > cbSrcLeft) + { + LogRel(("rtFsIsoImport/Rock: Malformed 'SL' component: component flags=%#x, component length+2=%#x vs %#x left\n", + fFlags, cbSkip, cbSrcLeft)); + break; + } + + const char *pszCopy; + switch (fFlags & ~ISO9660RRIP_SL_C_CONTINUE) + { + case 0: + pszCopy = (const char *)&pbSrc[2]; + break; + + case ISO9660RRIP_SL_C_CURRENT: + if (cchCopy != 0) + LogRel(("rtFsIsoImport/Rock: Malformed 'SL' component: CURRENT + %u bytes, ignoring bytes\n", cchCopy)); + pszCopy = "."; + cchCopy = 1; + break; + + case ISO9660RRIP_SL_C_PARENT: + if (cchCopy != 0) + LogRel(("rtFsIsoImport/Rock: Malformed 'SL' component: PARENT + %u bytes, ignoring bytes\n", cchCopy)); + pszCopy = ".."; + cchCopy = 2; + break; + + case ISO9660RRIP_SL_C_ROOT: + if (cchCopy != 0) + LogRel(("rtFsIsoImport/Rock: Malformed 'SL' component: ROOT + %u bytes, ignoring bytes\n", cchCopy)); + pszCopy = "/"; + cchCopy = 1; + break; + + default: + LogRel(("rtFsIsoImport/Rock: Malformed 'SL' component: component flags=%#x (bad), component length=%#x vs %#x left\n", + fFlags, cchCopy, cbSrcLeft)); + pszCopy = NULL; + cchCopy = 0; + break; + } + + if (offDst + cchCopy < sizeof(pThis->szRockSymlinkTargetBuf)) + { + memcpy(&pThis->szRockSymlinkTargetBuf[offDst], pszCopy, cchCopy); + offDst += cchCopy; + } + else + { + LogRel(("rtFsIsoImport/Rock: 'SL' constructs a too long target! '%.*s%.*s'\n", + offDst, pThis->szRockSymlinkTargetBuf, cchCopy, pszCopy)); + memcpy(&pThis->szRockSymlinkTargetBuf[offDst], pszCopy, + sizeof(pThis->szRockSymlinkTargetBuf) - offDst - 1); + offDst = sizeof(pThis->szRockSymlinkTargetBuf) - 1; + break; + } + + /* Advance */ + pbSrc += cbSkip; + cbSrcLeft -= cbSkip; + + /* Append slash if appropriate. */ + if ( !(fFlags & ISO9660RRIP_SL_C_CONTINUE) + && (cbSrcLeft >= 2 || !pThis->fSeenLastSL) ) + { + if (offDst + 1 < sizeof(pThis->szRockSymlinkTargetBuf)) + pThis->szRockSymlinkTargetBuf[offDst++] = '/'; + else + { + LogRel(("rtFsIsoImport/Rock: 'SL' constructs a too long target! '%.*s/'\n", + offDst, pThis->szRockSymlinkTargetBuf)); + break; + } + } + } + pThis->szRockSymlinkTargetBuf[offDst] = '\0'; + + /* Purge the encoding as we don't want invalid UTF-8 floating around. */ + RTStrPurgeEncoding(pThis->szRockSymlinkTargetBuf); + } + break; + + case MAKE_SIG(ISO9660RRIPNM_SIG1, ISO9660RRIPNM_SIG2): /* NM */ + if ( pUnion->NM.Hdr.bVersion != ISO9660RRIPNM_VER + || pUnion->NM.Hdr.cbEntry < RT_UOFFSETOF(ISO9660RRIPNM, achName) + || (pUnion->NM.fFlags & ISO9660RRIP_NM_F_RESERVED_MASK) ) + LogRel(("rtFsIsoImport/Rock: Malformed 'NM' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) fFlags=%#x %.*Rhxs\n", + pUnion->NM.Hdr.cbEntry, RT_UOFFSETOF(ISO9660RRIPNM, achName), + pUnion->NM.Hdr.bVersion, ISO9660RRIPNM_VER, pUnion->NM.fFlags, + pUnion->NM.Hdr.cbEntry - RT_MIN(pUnion->NM.Hdr.cbEntry, RT_UOFFSETOF(ISO9660RRIPNM, achName)), + &pUnion->NM.achName[0] )); + else if (pThis->fSeenLastNM) + LogRel(("rtFsIsoImport/Rock: Unexpected 'NM' entry!\n")); + else + { + pThis->fSeenLastNM = !(pUnion->NM.fFlags & ISO9660RRIP_NM_F_CONTINUE); + + uint8_t const cchName = pUnion->NM.Hdr.cbEntry - (uint8_t)RT_UOFFSETOF(ISO9660RRIPNM, achName); + if (pUnion->NM.fFlags & (ISO9660RRIP_NM_F_CURRENT | ISO9660RRIP_NM_F_PARENT)) + { + if (cchName == 0 && pThis->szRockNameBuf[0] == '\0') + Log(("rtFsIsoImport/Rock: Ignoring 'NM' entry for '.' and '..'\n")); + else + LogRel(("rtFsIsoImport/Rock: Ignoring malformed 'NM' using '.' or '..': fFlags=%#x cchName=%#x %.*Rhxs; szRockNameBuf='%s'\n", + pUnion->NM.fFlags, cchName, cchName, pUnion->NM.achName, pThis->szRockNameBuf)); + pThis->szRockNameBuf[0] = '\0'; + pThis->fSeenLastNM = true; + } + else + { + size_t offDst = strlen(pThis->szRockNameBuf); + if (offDst + cchName < sizeof(pThis->szRockNameBuf)) + { + memcpy(&pThis->szRockNameBuf[offDst], pUnion->NM.achName, cchName); + pThis->szRockNameBuf[offDst + cchName] = '\0'; + + /* Purge the encoding as we don't want invalid UTF-8 floating around. */ + RTStrPurgeEncoding(pThis->szRockSymlinkTargetBuf); + } + else + { + LogRel(("rtFsIsoImport/Rock: 'NM' constructs a too long name, ignoring it all: '%s%.*s'\n", + pThis->szRockNameBuf, cchName, pUnion->NM.achName)); + pThis->szRockNameBuf[0] = '\0'; + pThis->fSeenLastNM = true; + } + } + } + break; + + case MAKE_SIG(ISO9660RRIPCL_SIG1, ISO9660RRIPCL_SIG2): /* CL - just warn for now. */ + case MAKE_SIG(ISO9660RRIPPL_SIG1, ISO9660RRIPPL_SIG2): /* PL - just warn for now. */ + case MAKE_SIG(ISO9660RRIPRE_SIG1, ISO9660RRIPRE_SIG2): /* RE - just warn for now. */ + LogRel(("rtFsIsoImport/Rock: Ignorning directory relocation entry '%c%c'!\n", pUnion->Hdr.bSig1, pUnion->Hdr.bSig2)); + break; + + default: + LogRel(("rtFsIsoImport/Rock: Unknown SUSP entry: %#x %#x, %#x bytes, v%u\n", + pUnion->Hdr.bSig1, pUnion->Hdr.bSig2, pUnion->Hdr.cbEntry, pUnion->Hdr.bVersion)); + break; +#undef MAKE_SIG + } + } +} + + +/** + * Deals with the special '.' entry in the root directory. + * + * @returns IPRT status code. + * @param pThis The import instance. + * @param pDirRec The root directory record. + * @param fUnicode Indicates which namespace we're working on. + */ +static int rtFsIsoImportProcessIso9660TreeWorkerDoRockForRoot(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec, bool fUnicode) +{ + uint8_t const cbSys = pDirRec->cbDirRec - RT_UOFFSETOF(ISO9660DIRREC, achFileId) + - pDirRec->bFileIdLength - !(pDirRec->bFileIdLength & 1); + uint8_t const * const pbSys = (uint8_t const *)&pDirRec->achFileId[pDirRec->bFileIdLength + !(pDirRec->bFileIdLength & 1)]; + if (cbSys > 4) + { + RTFSOBJINFO ObjInfo; + ObjInfo.cbObject = 0; + ObjInfo.cbAllocated = 0; + rtFsIsoImpIso9660RecDateTime2TimeSpec(&ObjInfo.AccessTime, &pDirRec->RecTime); + ObjInfo.ModificationTime = ObjInfo.AccessTime; + ObjInfo.ChangeTime = ObjInfo.AccessTime; + ObjInfo.BirthTime = ObjInfo.AccessTime; + ObjInfo.Attr.fMode = RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY | 0555; + ObjInfo.Attr.enmAdditional = RTFSOBJATTRADD_UNIX; + ObjInfo.Attr.u.Unix.uid = NIL_RTUID; + ObjInfo.Attr.u.Unix.gid = NIL_RTGID; + ObjInfo.Attr.u.Unix.cHardlinks = 2; + ObjInfo.Attr.u.Unix.INodeIdDevice = 0; + ObjInfo.Attr.u.Unix.INodeId = 0; + ObjInfo.Attr.u.Unix.fFlags = 0; + ObjInfo.Attr.u.Unix.GenerationId = 0; + ObjInfo.Attr.u.Unix.Device = 0; + + rtFsIsoImportProcessIso9660TreeWorkerParseRockRidge(pThis, &ObjInfo, pbSys, cbSys, fUnicode, true /*fIsFirstDirRec*/, + false /*fContinuationRecord*/); + /** @todo Update root dir attribs. Need API. */ + } + return VINF_SUCCESS; +} + + +/** + * Validates a directory record. + * + * @returns IPRT status code (safe to ignore, see pThis->rc). + * @param pThis The importer instance. + * @param pDirRec The directory record to validate. + * @param cbMax The maximum size. + */ +static int rtFsIsoImportValidateDirRec(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec, uint32_t cbMax) +{ + /* + * Validate dual fields. + */ + if (RT_LE2H_U32(pDirRec->cbData.le) != RT_BE2H_U32(pDirRec->cbData.be)) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_DIR_REC, + "Invalid dir rec size field: {%#RX32,%#RX32}", + RT_BE2H_U32(pDirRec->cbData.be), RT_LE2H_U32(pDirRec->cbData.le)); + + if (RT_LE2H_U32(pDirRec->offExtent.le) != RT_BE2H_U32(pDirRec->offExtent.be)) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_DIR_REC, + "Invalid dir rec extent field: {%#RX32,%#RX32}", + RT_BE2H_U32(pDirRec->offExtent.be), RT_LE2H_U32(pDirRec->offExtent.le)); + + if (RT_LE2H_U16(pDirRec->VolumeSeqNo.le) != RT_BE2H_U16(pDirRec->VolumeSeqNo.be)) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_DIR_REC, + "Invalid dir rec volume sequence ID field: {%#RX16,%#RX16}", + RT_BE2H_U16(pDirRec->VolumeSeqNo.be), RT_LE2H_U16(pDirRec->VolumeSeqNo.le)); + + /* + * Check values. + */ + if (ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo) != pThis->idPrimaryVol) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_DIR_REC_VOLUME_SEQ_NO, + "Expected dir rec to have same volume sequence number as primary volume: %#x, expected %#x", + ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo), pThis->idPrimaryVol); + + if (ISO9660_GET_ENDIAN(&pDirRec->offExtent) >= pThis->cBlocksInPrimaryVolumeSpace) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_DIR_REC_EXTENT_OUT_OF_BOUNDS, + "Invalid dir rec extent: %#RX32, max %#RX32", + ISO9660_GET_ENDIAN(&pDirRec->offExtent), pThis->cBlocksInPrimaryVolumeSpace); + + if (pDirRec->cbDirRec < RT_UOFFSETOF(ISO9660DIRREC, achFileId) + pDirRec->bFileIdLength) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_DIR_REC_LENGTH, + "Dir record size is too small: %#x (min %#x)", + pDirRec->cbDirRec, RT_UOFFSETOF(ISO9660DIRREC, achFileId) + pDirRec->bFileIdLength); + if (pDirRec->cbDirRec > cbMax) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_DIR_REC_LENGTH, + "Dir record size is too big: %#x (max %#x)", pDirRec->cbDirRec, cbMax); + + if ( (pDirRec->fFileFlags & (ISO9660_FILE_FLAGS_MULTI_EXTENT | ISO9660_FILE_FLAGS_DIRECTORY)) + == (ISO9660_FILE_FLAGS_MULTI_EXTENT | ISO9660_FILE_FLAGS_DIRECTORY)) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_DIR_WITH_MORE_EXTENTS, + "Multi-extent directories are not supported (cbData=%#RX32 offExtent=%#RX32)", + ISO9660_GET_ENDIAN(&pDirRec->cbData), ISO9660_GET_ENDIAN(&pDirRec->offExtent)); + + return VINF_SUCCESS; +} + + +/** + * Validates a dot or dot-dot directory record. + * + * @returns IPRT status code (safe to ignore, see pThis->rc). + * @param pThis The importer instance. + * @param pDirRec The dot directory record to validate. + * @param cbMax The maximum size. + * @param bName The name byte (0x00: '.', 0x01: '..'). + */ +static int rtFsIsoImportValidateDotDirRec(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec, uint32_t cbMax, uint8_t bName) +{ + int rc = rtFsIsoImportValidateDirRec(pThis, pDirRec, cbMax); + if (RT_SUCCESS(rc)) + { + if (pDirRec->bFileIdLength != 1) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_DOT_DIR_REC_BAD_NAME_LENGTH, + "Invalid dot dir rec file id length: %u", pDirRec->bFileIdLength); + if ((uint8_t)pDirRec->achFileId[0] != bName) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_DOT_DIR_REC_BAD_NAME, + "Invalid dot dir rec file id: %#x, expected %#x", pDirRec->achFileId[0], bName); + } + return rc; +} + + +/** + * rtFsIsoImportProcessIso9660TreeWorker helper that reads more data. + * + * @returns IPRT status code. + * @param pThis The importer instance. + * @param ppDirRec Pointer to the directory record pointer (in/out). + * @param pcbChunk Pointer to the cbChunk variable (in/out). + * @param pcbDir Pointer to the cbDir variable (in/out). This indicates + * how much we've left to read from the directory. + * @param poffNext Pointer to the offNext variable (in/out). This + * indicates where the next chunk of directory data is in + * the input file. + */ +static int rtFsIsoImportProcessIso9660TreeWorkerReadMore(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC *ppDirRec, + uint32_t *pcbChunk, uint32_t *pcbDir, uint64_t *poffNext) +{ + uint32_t cbChunk = *pcbChunk; + *ppDirRec = (PCISO9660DIRREC)memmove(&pThis->abBuf[ISO9660_SECTOR_SIZE - cbChunk], *ppDirRec, cbChunk); + + Assert(!(*poffNext & (ISO9660_SECTOR_SIZE - 1))); + uint32_t cbToRead = RT_MIN(*pcbDir, sizeof(pThis->abBuf) - ISO9660_SECTOR_SIZE); + int rc = RTVfsFileReadAt(pThis->hSrcFile, *poffNext, &pThis->abBuf[ISO9660_SECTOR_SIZE], cbToRead, NULL); + if (RT_SUCCESS(rc)) + { + Log3(("rtFsIsoImportProcessIso9660TreeWorker: Read %#zx more bytes @%#RX64, now got @%#RX64 LB %#RX32\n", + cbToRead, *poffNext, *poffNext - cbChunk, cbChunk + cbToRead)); + *poffNext += cbToRead; + *pcbDir -= cbToRead; + *pcbChunk = cbChunk + cbToRead; + return VINF_SUCCESS; + } + return rtFsIsoImpError(pThis, rc, "Error reading %#RX32 bytes at %#RX64 (dir): %Rrc", *poffNext, cbToRead); +} + + +/** + * rtFsIsoImportProcessIso9660TreeWorker helper that deals with skipping to the + * next sector when cbDirRec is zero. + * + * @returns IPRT status code. + * @retval VERR_NO_MORE_FILES when we reaches the end of the directory. + * @param pThis The importer instance. + * @param ppDirRec Pointer to the directory record pointer (in/out). + * @param pcbChunk Pointer to the cbChunk variable (in/out). Indicates how + * much we've left to process starting and pDirRec. + * @param pcbDir Pointer to the cbDir variable (in/out). This indicates + * how much we've left to read from the directory. + * @param poffNext Pointer to the offNext variable (in/out). This + * indicates where the next chunk of directory data is in + * the input file. + */ +static int rtFsIsoImportProcessIso9660TreeWorkerHandleZeroSizedDirRec(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC *ppDirRec, + uint32_t *pcbChunk, uint32_t *pcbDir, uint64_t *poffNext) +{ + uint32_t cbChunk = *pcbChunk; + uint64_t offChunk = *poffNext - cbChunk; + uint32_t cbSkip = ISO9660_SECTOR_SIZE - ((uint32_t)offChunk & (ISO9660_SECTOR_SIZE - 1)); + if (cbSkip < cbChunk) + { + *ppDirRec = (PCISO9660DIRREC)((uintptr_t)*ppDirRec + cbSkip); + *pcbChunk = cbChunk -= cbSkip; + if ( cbChunk > UINT8_MAX + || *pcbDir == 0) + { + Log3(("rtFsIsoImportProcessIso9660TreeWorker: cbDirRec=0 --> jumped %#RX32 to @%#RX64 LB %#RX32\n", + cbSkip, *poffNext - cbChunk, cbChunk)); + return VINF_SUCCESS; + } + Log3(("rtFsIsoImportProcessIso9660TreeWorker: cbDirRec=0 --> jumped %#RX32 to @%#RX64 LB %#RX32, but needs to read more\n", + cbSkip, *poffNext - cbChunk, cbChunk)); + return rtFsIsoImportProcessIso9660TreeWorkerReadMore(pThis, ppDirRec, pcbChunk, pcbDir, poffNext); + } + + /* ASSUMES we're working in multiples of sectors! */ + if (*pcbDir == 0) + { + *pcbChunk = 0; + return VERR_NO_MORE_FILES; + } + + /* End of chunk, read the next sectors. */ + Assert(!(*poffNext & (ISO9660_SECTOR_SIZE - 1))); + uint32_t cbToRead = RT_MIN(*pcbDir, sizeof(pThis->abBuf)); + int rc = RTVfsFileReadAt(pThis->hSrcFile, *poffNext, pThis->abBuf, cbToRead, NULL); + if (RT_SUCCESS(rc)) + { + Log3(("rtFsIsoImportProcessIso9660TreeWorker: cbDirRec=0 --> Read %#zx more bytes @%#RX64, now got @%#RX64 LB %#RX32\n", + cbToRead, *poffNext, *poffNext - cbChunk, cbChunk + cbToRead)); + *poffNext += cbToRead; + *pcbDir -= cbToRead; + *pcbChunk = cbChunk + cbToRead; + *ppDirRec = (PCISO9660DIRREC)&pThis->abBuf[0]; + return VINF_SUCCESS; + } + return rtFsIsoImpError(pThis, rc, "Error reading %#RX32 bytes at %#RX64 (dir): %Rrc", *poffNext, cbToRead); +} + + +/** + * Deals with a single directory. + * + * @returns IPRT status code (safe to ignore, see pThis->rc). + * @param pThis The importer instance. + * @param idxDir The configuration index for the directory. + * @param offDirBlock The offset of the directory data. + * @param cbDir The size of the directory data. + * @param cDepth The depth of the directory. + * @param fUnicode Set if it's a unicode (UTF-16BE) encoded + * directory. + * @param pTodoList The todo-list to add sub-directories to. + */ +static int rtFsIsoImportProcessIso9660TreeWorker(PRTFSISOMKIMPORTER pThis, uint32_t idxDir, + uint32_t offDirBlock, uint32_t cbDir, uint8_t cDepth, bool fUnicode, + PRTLISTANCHOR pTodoList) +{ + /* + * Restrict the depth to try avoid loops. + */ + if (cDepth > RTFSISOMK_IMPORT_MAX_DEPTH) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_TOO_DEEP_DIR_TREE, "Dir at %#x LB %#x is too deep", offDirBlock, cbDir); + + /* + * Read the first chunk into the big buffer. + */ + uint32_t cbChunk = RT_MIN(cbDir, sizeof(pThis->abBuf)); + uint64_t offNext = (uint64_t)offDirBlock * ISO9660_SECTOR_SIZE; + int rc = RTVfsFileReadAt(pThis->hSrcFile, offNext, pThis->abBuf, cbChunk, NULL); + if (RT_FAILURE(rc)) + return rtFsIsoImpError(pThis, rc, "Error reading directory at %#RX64 (%#RX32 / %#RX32): %Rrc", offNext, cbChunk, cbDir); + + cbDir -= cbChunk; + offNext += cbChunk; + + /* + * Skip the current and parent directory entries. + */ + PCISO9660DIRREC pDirRec = (PCISO9660DIRREC)&pThis->abBuf[0]; + rc = rtFsIsoImportValidateDotDirRec(pThis, pDirRec, cbChunk, 0x00); + if (RT_FAILURE(rc)) + return rc; + if ( cDepth == 0 + && !(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_ROCK_RIDGE) + && pDirRec->cbDirRec > RT_UOFFSETOF(ISO9660DIRREC, achFileId[1])) + { + rc = rtFsIsoImportProcessIso9660TreeWorkerDoRockForRoot(pThis, pDirRec, fUnicode); + if (RT_FAILURE(rc)) + return rc; + } + + cbChunk -= pDirRec->cbDirRec; + pDirRec = (PCISO9660DIRREC)((uintptr_t)pDirRec + pDirRec->cbDirRec); + rc = rtFsIsoImportValidateDotDirRec(pThis, pDirRec, cbChunk, 0x01); + if (RT_FAILURE(rc)) + return rc; + + cbChunk -= pDirRec->cbDirRec; + pDirRec = (PCISO9660DIRREC)((uintptr_t)pDirRec + pDirRec->cbDirRec); + + /* + * Work our way thru all the directory records. + */ + Log3(("rtFsIsoImportProcessIso9660TreeWorker: Starting at @%#RX64 LB %#RX32 (out of %#RX32) in %#x\n", + offNext - cbChunk, cbChunk, cbChunk + cbDir, idxDir)); + const uint32_t fNamespace = fUnicode ? RTFSISOMAKER_NAMESPACE_JOLIET : RTFSISOMAKER_NAMESPACE_ISO_9660; + while ( cbChunk > 0 + || cbDir > 0) + { + /* + * Do we need to read some more? + */ + if ( cbChunk > UINT8_MAX + || cbDir == 0) + { /* No, we don't. */ } + else + { + rc = rtFsIsoImportProcessIso9660TreeWorkerReadMore(pThis, &pDirRec, &cbChunk, &cbDir, &offNext); + if (RT_FAILURE(rc)) + return rc; + } + + /* If null length, skip to the next sector. May have to read some then. */ + if (pDirRec->cbDirRec != 0) + { /* likely */ } + else + { + rc = rtFsIsoImportProcessIso9660TreeWorkerHandleZeroSizedDirRec(pThis, &pDirRec, &cbChunk, &cbDir, &offNext); + if (RT_FAILURE(rc)) + { + if (rc == VERR_NO_MORE_FILES) + break; + return rc; + } + if (pDirRec->cbDirRec == 0) + continue; + } + + /* + * Validate the directory record. Give up if not valid since we're + * likely to get error with subsequent record too. + */ + uint8_t const cbSys = pDirRec->cbDirRec - RT_UOFFSETOF(ISO9660DIRREC, achFileId) + - pDirRec->bFileIdLength - !(pDirRec->bFileIdLength & 1); + uint8_t const * const pbSys = (uint8_t const *)&pDirRec->achFileId[pDirRec->bFileIdLength + !(pDirRec->bFileIdLength & 1)]; + Log3(("pDirRec=&abBuf[%#07zx]: @%#010RX64 cb=%#04x ff=%#04x off=%#010RX32 cb=%#010RX32 cbSys=%#x id=%.*Rhxs\n", + (uintptr_t)pDirRec - (uintptr_t)&pThis->abBuf[0], offNext - cbChunk, pDirRec->cbDirRec, pDirRec->fFileFlags, + ISO9660_GET_ENDIAN(&pDirRec->offExtent), ISO9660_GET_ENDIAN(&pDirRec->cbData), cbSys, + pDirRec->bFileIdLength, pDirRec->achFileId)); + rc = rtFsIsoImportValidateDirRec(pThis, pDirRec, cbChunk); + if (RT_FAILURE(rc)) + return rc; + + /* This early calculation of the next record is due to multi-extent + handling further down. */ + uint32_t cbChunkNew = cbChunk - pDirRec->cbDirRec; + PCISO9660DIRREC pDirRecNext = (PCISO9660DIRREC)((uintptr_t)pDirRec + pDirRec->cbDirRec); + + /* Start Collecting object info. */ + RTFSOBJINFO ObjInfo; + ObjInfo.cbObject = ISO9660_GET_ENDIAN(&pDirRec->cbData); + ObjInfo.cbAllocated = ObjInfo.cbObject; + rtFsIsoImpIso9660RecDateTime2TimeSpec(&ObjInfo.AccessTime, &pDirRec->RecTime); + ObjInfo.ModificationTime = ObjInfo.AccessTime; + ObjInfo.ChangeTime = ObjInfo.AccessTime; + ObjInfo.BirthTime = ObjInfo.AccessTime; + ObjInfo.Attr.fMode = pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY + ? RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY | 0555 + : RTFS_TYPE_FILE | RTFS_DOS_ARCHIVED | 0444; + ObjInfo.Attr.enmAdditional = RTFSOBJATTRADD_UNIX; + ObjInfo.Attr.u.Unix.uid = NIL_RTUID; + ObjInfo.Attr.u.Unix.gid = NIL_RTGID; + ObjInfo.Attr.u.Unix.cHardlinks = 1; + ObjInfo.Attr.u.Unix.INodeIdDevice = 0; + ObjInfo.Attr.u.Unix.INodeId = 0; + ObjInfo.Attr.u.Unix.fFlags = 0; + ObjInfo.Attr.u.Unix.GenerationId = 0; + ObjInfo.Attr.u.Unix.Device = 0; + + /* + * Convert the name into the name buffer (szNameBuf). + */ + if (!fUnicode) + { + memcpy(pThis->szNameBuf, pDirRec->achFileId, pDirRec->bFileIdLength); + pThis->szNameBuf[pDirRec->bFileIdLength] = '\0'; + rc = RTStrValidateEncoding(pThis->szNameBuf); + } + else + { + char *pszDst = pThis->szNameBuf; + rc = RTUtf16BigToUtf8Ex((PRTUTF16)pDirRec->achFileId, pDirRec->bFileIdLength / sizeof(RTUTF16), + &pszDst, sizeof(pThis->szNameBuf), NULL); + } + if (RT_SUCCESS(rc)) + { + /* Drop the version from the name. */ + size_t cchName = strlen(pThis->szNameBuf); + if ( !(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY) + && cchName > 2 + && RT_C_IS_DIGIT(pThis->szNameBuf[cchName - 1])) + { + uint32_t offName = 2; + while ( offName <= 5 + && offName + 1 < cchName + && RT_C_IS_DIGIT(pThis->szNameBuf[cchName - offName])) + offName++; + if ( offName + 1 < cchName + && pThis->szNameBuf[cchName - offName] == ';') + { + RTStrToUInt32Full(&pThis->szNameBuf[cchName - offName + 1], 10, &ObjInfo.Attr.u.Unix.GenerationId); + pThis->szNameBuf[cchName - offName] = '\0'; + } + } + Log3((" --> name='%s'\n", pThis->szNameBuf)); + + pThis->szRockNameBuf[0] = '\0'; + pThis->szRockSymlinkTargetBuf[0] = '\0'; + if (cbSys > 0 && !(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_ROCK_RIDGE)) + { + pThis->fSeenLastNM = false; + pThis->fSeenLastSL = false; + pThis->szRockNameBuf[0] = '\0'; + pThis->szRockSymlinkTargetBuf[0] = '\0'; + rtFsIsoImportProcessIso9660TreeWorkerParseRockRidge(pThis, &ObjInfo, pbSys, cbSys, fUnicode, + false /*fContinuationRecord*/, false /*fIsFirstDirRec*/); + } + + /* + * Deal with multi-extent files (usually large ones). We currently only + * handle files where the data is in single continuous chunk and only split + * up into multiple directory records because of data type limitations. + */ + uint8_t abDirRecCopy[256]; + uint64_t cbData = ISO9660_GET_ENDIAN(&pDirRec->cbData); + if (!(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT)) + { /* likely */ } + else + { + if (cbData & (ISO9660_SECTOR_SIZE - 1)) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MISALIGNED_MULTI_EXTENT, + "The size of non-final multi-extent record #0x0 isn't block aligned: %#RX64", cbData); + + /* Make a copy of the first directory record so we don't overwrite + it when reading in more records below. */ + pDirRec = (PCISO9660DIRREC)memcpy(abDirRecCopy, pDirRec, pDirRec->cbDirRec); + + /* Process extent records. */ + uint32_t cDirRecs = 1; + uint32_t offNextBlock = ISO9660_GET_ENDIAN(&pDirRec->offExtent) + + ISO9660_GET_ENDIAN(&pDirRec->cbData) / ISO9660_SECTOR_SIZE; + while ( cbChunkNew > 0 + || cbDir > 0) + { + /* Read more? Skip? */ + if ( cbChunkNew <= UINT8_MAX + && cbDir != 0) + { + rc = rtFsIsoImportProcessIso9660TreeWorkerReadMore(pThis, &pDirRecNext, &cbChunkNew, &cbDir, &offNext); + if (RT_FAILURE(rc)) + return rc; + } + if (pDirRecNext->cbDirRec == 0) + { + rc = rtFsIsoImportProcessIso9660TreeWorkerHandleZeroSizedDirRec(pThis, &pDirRecNext, &cbChunkNew, + &cbDir, &offNext); + if (RT_FAILURE(rc)) + { + if (rc == VERR_NO_MORE_FILES) + break; + return rc; + } + if (pDirRecNext->cbDirRec == 0) + continue; + } + + /* Check the next record. */ + rc = rtFsIsoImportValidateDirRec(pThis, pDirRecNext, cbChunkNew); + if (RT_FAILURE(rc)) + return rc; + if (pDirRecNext->bFileIdLength != pDirRec->bFileIdLength) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MISMATCHING_MULTI_EXTENT_REC, + "Multi-extent record #%#x differs from the first: bFileIdLength is %#x, expected %#x", + cDirRecs, pDirRecNext->bFileIdLength, pDirRec->bFileIdLength); + if (memcmp(pDirRecNext->achFileId, pDirRec->achFileId, pDirRec->bFileIdLength) != 0) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MISMATCHING_MULTI_EXTENT_REC, + "Multi-extent record #%#x differs from the first: achFileId is %.*Rhxs, expected %.*Rhxs", + cDirRecs, pDirRecNext->bFileIdLength, pDirRecNext->achFileId, + pDirRec->bFileIdLength, pDirRec->achFileId); + if (ISO9660_GET_ENDIAN(&pDirRecNext->VolumeSeqNo) != ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo)) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MISMATCHING_MULTI_EXTENT_REC, + "Multi-extent record #%#x differs from the first: VolumeSeqNo is %#x, expected %#x", + cDirRecs, ISO9660_GET_ENDIAN(&pDirRecNext->VolumeSeqNo), + ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo)); + if ( (pDirRecNext->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT) + && (ISO9660_GET_ENDIAN(&pDirRecNext->cbData) & (ISO9660_SECTOR_SIZE - 1)) ) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MISALIGNED_MULTI_EXTENT, + "The size of non-final multi-extent record #%#x isn't block aligned: %#RX32", + cDirRecs, ISO9660_GET_ENDIAN(&pDirRecNext->cbData)); + + /* Check that the data is contiguous, then add the data. */ + if (ISO9660_GET_ENDIAN(&pDirRecNext->offExtent) == offNextBlock) + cbData += ISO9660_GET_ENDIAN(&pDirRecNext->cbData); + else + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_NON_CONTIGUOUS_MULTI_EXTENT, + "Multi-extent record #%#x isn't contiguous: offExtent=%#RX32, expected %#RX32", + cDirRecs, ISO9660_GET_ENDIAN(&pDirRecNext->offExtent), offNextBlock); + + /* Advance. */ + cDirRecs++; + bool fDone = !(pDirRecNext->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT); + offNext += ISO9660_GET_ENDIAN(&pDirRecNext->cbData) / ISO9660_SECTOR_SIZE; + cbChunkNew -= pDirRecNext->cbDirRec; + pDirRecNext = (PCISO9660DIRREC)((uintptr_t)pDirRecNext + pDirRecNext->cbDirRec); + if (fDone) + break; + } + } + if (RT_SUCCESS(rc)) + { + /* + * Add the object. + */ + if (pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY) + rtFsIsoImportProcessIso9660AddAndNameDirectory(pThis, pDirRec, &ObjInfo, cbData, fNamespace, idxDir, + pThis->szNameBuf, pThis->szRockNameBuf, cDepth + 1, pTodoList); + else if (pThis->szRockSymlinkTargetBuf[0] == '\0') + { + if (strcmp(pThis->szNameBuf, pThis->pszTransTbl) != 0) + rtFsIsoImportProcessIso9660AddAndNameFile(pThis, pDirRec, &ObjInfo, cbData, fNamespace, idxDir, + pThis->szNameBuf, pThis->szRockNameBuf); + } + else + rtFsIsoImportProcessIso9660AddAndNameSymlink(pThis, pDirRec, &ObjInfo, fNamespace, idxDir, pThis->szNameBuf, + pThis->szRockNameBuf, pThis->szRockSymlinkTargetBuf); + } + } + else + rtFsIsoImpError(pThis, rc, "Invalid name at %#RX64: %.Rhxs", + offNext - cbChunk, pDirRec->bFileIdLength, pDirRec->achFileId); + + /* + * Advance to the next directory record. + */ + cbChunk = cbChunkNew; + pDirRec = pDirRecNext; + } + + return VINF_SUCCESS; +} + + +/** + * Deals with a directory tree. + * + * This is implemented by tracking directories that needs to be processed in a + * todo list, so no recursive calls, however it uses a bit of heap. + * + * @returns IPRT status code (safe to ignore, see pThis->rc). + * @param pThis The importer instance. + * @param offDirBlock The offset of the root directory data. + * @param cbDir The size of the root directory data. + * @param fUnicode Set if it's a unicode (UTF-16BE) encoded + * directory. + */ +static int rtFsIsoImportProcessIso9660Tree(PRTFSISOMKIMPORTER pThis, uint32_t offDirBlock, uint32_t cbDir, bool fUnicode) +{ + /* + * Reset some parsing state. + */ + pThis->offSuspSkip = 0; + pThis->fSuspSeenSP = false; + pThis->pszTransTbl = "TRANS.TBL"; /** @todo query this from the iso maker! */ + + /* + * Make sure we've got a root in the namespace. + */ + uint32_t idxDir = RTFsIsoMakerGetObjIdxForPath(pThis->hIsoMaker, + !fUnicode ? RTFSISOMAKER_NAMESPACE_ISO_9660 : RTFSISOMAKER_NAMESPACE_JOLIET, + "/"); + if (idxDir == UINT32_MAX) + { + idxDir = RTFSISOMAKER_CFG_IDX_ROOT; + int rc = RTFsIsoMakerObjSetPath(pThis->hIsoMaker, RTFSISOMAKER_CFG_IDX_ROOT, + !fUnicode ? RTFSISOMAKER_NAMESPACE_ISO_9660 : RTFSISOMAKER_NAMESPACE_JOLIET, "/"); + if (RT_FAILURE(rc)) + return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerObjSetPath failed on root dir: %Rrc", rc); + } + Assert(idxDir == RTFSISOMAKER_CFG_IDX_ROOT); + + /* + * Directories. + */ + int rc = VINF_SUCCESS; + uint8_t cDepth = 0; + RTLISTANCHOR TodoList; + RTListInit(&TodoList); + for (;;) + { + int rc2 = rtFsIsoImportProcessIso9660TreeWorker(pThis, idxDir, offDirBlock, cbDir, cDepth, fUnicode, &TodoList); + if (RT_FAILURE(rc2) && RT_SUCCESS(rc)) + rc = rc2; + + /* + * Pop the next directory. + */ + PRTFSISOMKIMPDIR pNext = RTListRemoveLast(&TodoList, RTFSISOMKIMPDIR, Entry); + if (!pNext) + break; + idxDir = pNext->idxObj; + offDirBlock = pNext->offDirBlock; + cbDir = pNext->cbDir; + cDepth = pNext->cDepth; + RTMemFree(pNext); + } + + return rc; +} + + +/** + * Imports a UTF-16BE string property from the joliet volume descriptor. + * + * The fields are normally space filled and padded, but we also consider zero + * bytes are fillers. If the field only contains padding, the string property + * will remain unchanged. + * + * @returns IPRT status code (ignorable). + * @param pThis The importer instance. + * @param pachField Pointer to the field. The structure type + * is 'char' for hysterical raisins, while the + * real type is 'RTUTF16'. + * @param cchField The field length. + * @param enmStringProp The corresponding string property. + * + * @note Clobbers pThis->pbBuf! + */ +static int rtFsIsoImportUtf16BigStringField(PRTFSISOMKIMPORTER pThis, const char *pachField, size_t cchField, + RTFSISOMAKERSTRINGPROP enmStringProp) +{ + /* + * Scan the field from the end as this way we know the result length if we find anything. + */ + PCRTUTF16 pwcField = (PCRTUTF16)pachField; + size_t cwcField = cchField / sizeof(RTUTF16); /* ignores any odd field byte */ + size_t off = cwcField; + while (off-- > 0) + { + RTUTF16 wc = RT_BE2H_U16(pwcField[off]); + if (wc == ' ' || wc == '\0') + { /* likely */ } + else + { + /* + * Convert to UTF-16. + */ + char *pszCopy = (char *)pThis->abBuf; + int rc = RTUtf16BigToUtf8Ex(pwcField, off + 1, &pszCopy, sizeof(pThis->abBuf), NULL); + if (RT_SUCCESS(rc)) + { + rc = RTFsIsoMakerSetStringProp(pThis->hIsoMaker, enmStringProp, RTFSISOMAKER_NAMESPACE_JOLIET, pszCopy); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerSetStringProp failed setting field %d to '%s': %Rrc", + enmStringProp, pszCopy, rc); + } + return rtFsIsoImpError(pThis, rc, "RTUtf16BigToUtf8Ex failed converting field %d to UTF-8: %Rrc - %.*Rhxs", + enmStringProp, rc, off * sizeof(RTUTF16), pwcField); + } + } + return VINF_SUCCESS; +} + + +/** + * Imports a string property from the primary volume descriptor. + * + * The fields are normally space filled and padded, but we also consider zero + * bytes are fillers. If the field only contains padding, the string property + * will remain unchanged. + * + * @returns IPRT status code (ignorable). + * @param pThis The importer instance. + * @param pachField Pointer to the field. + * @param cchField The field length. + * @param enmStringProp The corresponding string property. + * + * @note Clobbers pThis->pbBuf! + */ +static int rtFsIsoImportAsciiStringField(PRTFSISOMKIMPORTER pThis, const char *pachField, size_t cchField, + RTFSISOMAKERSTRINGPROP enmStringProp) +{ + /* + * Scan the field from the end as this way we know the result length if we find anything. + */ + size_t off = cchField; + while (off-- > 0) + { + char ch = pachField[off]; + if (ch == ' ' || ch == '\0') + { /* likely */ } + else + { + /* + * Make a copy of the string in abBuf, purge the encoding. + */ + off++; + char *pszCopy = (char *)pThis->abBuf; + memcpy(pszCopy, pachField, off); + pszCopy[off] = '\0'; + RTStrPurgeEncoding(pszCopy); + + int rc = RTFsIsoMakerSetStringProp(pThis->hIsoMaker, enmStringProp, RTFSISOMAKER_NAMESPACE_ISO_9660, pszCopy); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerSetStringProp failed setting field %d to '%s': %Rrc", + enmStringProp, pszCopy, rc); + } + } + return VINF_SUCCESS; +} + + +/** + * Validates a root directory record. + * + * @returns IPRT status code (safe to ignore, see pThis->rc). + * @param pThis The importer instance. + * @param pDirRec The root directory record to validate. + */ +static int rtFsIsoImportValidateRootDirRec(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec) +{ + /* + * Validate dual fields. + */ + if (RT_LE2H_U32(pDirRec->cbData.le) != RT_BE2H_U32(pDirRec->cbData.be)) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_ROOT_DIR_REC, + "Invalid root dir size: {%#RX32,%#RX32}", + RT_BE2H_U32(pDirRec->cbData.be), RT_LE2H_U32(pDirRec->cbData.le)); + + if (RT_LE2H_U32(pDirRec->offExtent.le) != RT_BE2H_U32(pDirRec->offExtent.be)) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_ROOT_DIR_REC, + "Invalid root dir extent: {%#RX32,%#RX32}", + RT_BE2H_U32(pDirRec->offExtent.be), RT_LE2H_U32(pDirRec->offExtent.le)); + + if (RT_LE2H_U16(pDirRec->VolumeSeqNo.le) != RT_BE2H_U16(pDirRec->VolumeSeqNo.be)) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_ROOT_DIR_REC, + "Invalid root dir volume sequence ID: {%#RX16,%#RX16}", + RT_BE2H_U16(pDirRec->VolumeSeqNo.be), RT_LE2H_U16(pDirRec->VolumeSeqNo.le)); + + /* + * Check values. + */ + if (ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo) != pThis->idPrimaryVol) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_ROOT_VOLUME_SEQ_NO, + "Expected root dir to have same volume sequence number as primary volume: %#x, expected %#x", + ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo), pThis->idPrimaryVol); + + if (ISO9660_GET_ENDIAN(&pDirRec->cbData) == 0) + return RTErrInfoSet(pThis->pErrInfo, VERR_ISOMK_IMPORT_ZERO_SIZED_ROOT_DIR, "Zero sized root dir"); + + if (ISO9660_GET_ENDIAN(&pDirRec->offExtent) >= pThis->cBlocksInPrimaryVolumeSpace) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_ROOT_DIR_EXTENT_OUT_OF_BOUNDS, + "Invalid root dir extent: %#RX32, max %#RX32", + ISO9660_GET_ENDIAN(&pDirRec->offExtent), pThis->cBlocksInPrimaryVolumeSpace); + + if (pDirRec->cbDirRec < RT_UOFFSETOF(ISO9660DIRREC, achFileId)) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_ROOT_DIR_REC_LENGTH, + "Root dir record size is too small: %#x (min %#x)", + pDirRec->cbDirRec, RT_UOFFSETOF(ISO9660DIRREC, achFileId)); + + if (!(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY)) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_ROOT_DIR_WITHOUT_DIR_FLAG, + "Root dir is not flagged as directory: %#x", pDirRec->fFileFlags); + if (pDirRec->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_ROOT_DIR_IS_MULTI_EXTENT, + "Root dir is cannot be multi-extent: %#x", pDirRec->fFileFlags); + + return VINF_SUCCESS; +} + + +/** + * Processes a primary volume descriptor, importing all files and stuff. + * + * @returns IPRT status code (safe to ignore, see pThis->rc). + * @param pThis The importer instance. + * @param pVolDesc The primary volume descriptor. + */ +static int rtFsIsoImportProcessPrimaryDesc(PRTFSISOMKIMPORTER pThis, PISO9660PRIMARYVOLDESC pVolDesc) +{ + /* + * Validate dual fields first. + */ + if (pVolDesc->bFileStructureVersion != ISO9660_FILE_STRUCTURE_VERSION) + return rtFsIsoImpError(pThis, VERR_IOSMK_IMPORT_PRIMARY_VOL_DESC_VER, + "Unsupported file structure version: %#x", pVolDesc->bFileStructureVersion); + + if (RT_LE2H_U16(pVolDesc->cbLogicalBlock.le) != RT_BE2H_U16(pVolDesc->cbLogicalBlock.be)) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_PRIMARY_VOL_DESC, + "Mismatching logical block size: {%#RX16,%#RX16}", + RT_BE2H_U16(pVolDesc->cbLogicalBlock.be), RT_LE2H_U16(pVolDesc->cbLogicalBlock.le)); + if (RT_LE2H_U32(pVolDesc->VolumeSpaceSize.le) != RT_BE2H_U32(pVolDesc->VolumeSpaceSize.be)) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_PRIMARY_VOL_DESC, + "Mismatching volume space size: {%#RX32,%#RX32}", + RT_BE2H_U32(pVolDesc->VolumeSpaceSize.be), RT_LE2H_U32(pVolDesc->VolumeSpaceSize.le)); + if (RT_LE2H_U16(pVolDesc->cVolumesInSet.le) != RT_BE2H_U16(pVolDesc->cVolumesInSet.be)) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_PRIMARY_VOL_DESC, + "Mismatching volumes in set: {%#RX16,%#RX16}", + RT_BE2H_U16(pVolDesc->cVolumesInSet.be), RT_LE2H_U16(pVolDesc->cVolumesInSet.le)); + if (RT_LE2H_U16(pVolDesc->VolumeSeqNo.le) != RT_BE2H_U16(pVolDesc->VolumeSeqNo.be)) + { + /* Hack alert! An Windows NT 3.1 ISO was found to not have the big endian bit set here, so work around it. */ + if ( pVolDesc->VolumeSeqNo.be == 0 + && pVolDesc->VolumeSeqNo.le == RT_H2LE_U16_C(1)) + pVolDesc->VolumeSeqNo.be = RT_H2BE_U16_C(1); + else + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_PRIMARY_VOL_DESC, + "Mismatching volume sequence no.: {%#RX16,%#RX16}", + RT_BE2H_U16(pVolDesc->VolumeSeqNo.be), RT_LE2H_U16(pVolDesc->VolumeSeqNo.le)); + } + if (RT_LE2H_U32(pVolDesc->cbPathTable.le) != RT_BE2H_U32(pVolDesc->cbPathTable.be)) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_PRIMARY_VOL_DESC, + "Mismatching path table size: {%#RX32,%#RX32}", + RT_BE2H_U32(pVolDesc->cbPathTable.be), RT_LE2H_U32(pVolDesc->cbPathTable.le)); + + /* + * Validate field values against our expectations. + */ + if (ISO9660_GET_ENDIAN(&pVolDesc->cbLogicalBlock) != ISO9660_SECTOR_SIZE) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_LOGICAL_BLOCK_SIZE_NOT_2KB, + "Unsupported block size: %#x", ISO9660_GET_ENDIAN(&pVolDesc->cbLogicalBlock)); + + if (ISO9660_GET_ENDIAN(&pVolDesc->cVolumesInSet) != 1) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MORE_THAN_ONE_VOLUME_IN_SET, + "Volumes in set: %#x", ISO9660_GET_ENDIAN(&pVolDesc->cVolumesInSet)); + + if (ISO9660_GET_ENDIAN(&pVolDesc->VolumeSeqNo) != 1) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_INVALID_VOLUMNE_SEQ_NO, + "Unexpected volume sequence number: %#x", ISO9660_GET_ENDIAN(&pVolDesc->VolumeSeqNo)); + + /* + * Gather info we need. + */ + pThis->cBlocksInPrimaryVolumeSpace = ISO9660_GET_ENDIAN(&pVolDesc->VolumeSpaceSize); + pThis->cbPrimaryVolumeSpace = pThis->cBlocksInPrimaryVolumeSpace * (uint64_t)ISO9660_SECTOR_SIZE; + pThis->cVolumesInSet = ISO9660_GET_ENDIAN(&pVolDesc->cVolumesInSet); + pThis->idPrimaryVol = ISO9660_GET_ENDIAN(&pVolDesc->VolumeSeqNo); + + /* + * Validate the root directory record. + */ + int rc = rtFsIsoImportValidateRootDirRec(pThis, &pVolDesc->RootDir.DirRec); + if (RT_SUCCESS(rc)) + { + /* + * Import stuff if present and not opted out. + */ + if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_SYSTEM_ID)) + rtFsIsoImportAsciiStringField(pThis, pVolDesc->achSystemId, sizeof(pVolDesc->achSystemId), + RTFSISOMAKERSTRINGPROP_SYSTEM_ID); + if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_VOLUME_ID)) + rtFsIsoImportAsciiStringField(pThis, pVolDesc->achVolumeId, sizeof(pVolDesc->achVolumeId), + RTFSISOMAKERSTRINGPROP_VOLUME_ID); + if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_VOLUME_SET_ID)) + rtFsIsoImportAsciiStringField(pThis, pVolDesc->achVolumeSetId, sizeof(pVolDesc->achVolumeSetId), + RTFSISOMAKERSTRINGPROP_VOLUME_SET_ID); + if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_PUBLISHER_ID)) + rtFsIsoImportAsciiStringField(pThis, pVolDesc->achPublisherId, sizeof(pVolDesc->achPublisherId), + RTFSISOMAKERSTRINGPROP_PUBLISHER_ID); + if (pThis->fFlags & RTFSISOMK_IMPORT_F_DATA_PREPARER_ID) + rtFsIsoImportAsciiStringField(pThis, pVolDesc->achDataPreparerId, sizeof(pVolDesc->achDataPreparerId), + RTFSISOMAKERSTRINGPROP_DATA_PREPARER_ID); + if (pThis->fFlags & RTFSISOMK_IMPORT_F_APPLICATION_ID) + rtFsIsoImportAsciiStringField(pThis, pVolDesc->achApplicationId, sizeof(pVolDesc->achApplicationId), + RTFSISOMAKERSTRINGPROP_APPLICATION_ID); + if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_COPYRIGHT_FID)) + rtFsIsoImportAsciiStringField(pThis, pVolDesc->achCopyrightFileId, sizeof(pVolDesc->achCopyrightFileId), + RTFSISOMAKERSTRINGPROP_COPYRIGHT_FILE_ID); + if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_ABSTRACT_FID)) + rtFsIsoImportAsciiStringField(pThis, pVolDesc->achAbstractFileId, sizeof(pVolDesc->achAbstractFileId), + RTFSISOMAKERSTRINGPROP_ABSTRACT_FILE_ID); + if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_BIBLIO_FID)) + rtFsIsoImportAsciiStringField(pThis, pVolDesc->achBibliographicFileId, sizeof(pVolDesc->achBibliographicFileId), + RTFSISOMAKERSTRINGPROP_BIBLIOGRAPHIC_FILE_ID); + + /* + * Process the directory tree. + */ + if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_PRIMARY_ISO)) + rc = rtFsIsoImportProcessIso9660Tree(pThis, ISO9660_GET_ENDIAN(&pVolDesc->RootDir.DirRec.offExtent), + ISO9660_GET_ENDIAN(&pVolDesc->RootDir.DirRec.cbData), false /*fUnicode*/); + } + + return rc; +} + + +/** + * Processes a secondary volume descriptor, if it is joliet we'll importing all + * the files and stuff. + * + * @returns IPRT status code (safe to ignore, see pThis->rc). + * @param pThis The importer instance. + * @param pVolDesc The primary volume descriptor. + */ +static int rtFsIsoImportProcessSupplementaryDesc(PRTFSISOMKIMPORTER pThis, PISO9660SUPVOLDESC pVolDesc) +{ + /* + * Validate dual fields first. + */ + if (pVolDesc->bFileStructureVersion != ISO9660_FILE_STRUCTURE_VERSION) + return rtFsIsoImpError(pThis, VERR_IOSMK_IMPORT_SUP_VOL_DESC_VER, + "Unsupported file structure version: %#x", pVolDesc->bFileStructureVersion); + + if (RT_LE2H_U16(pVolDesc->cbLogicalBlock.le) != RT_BE2H_U16(pVolDesc->cbLogicalBlock.be)) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_SUP_VOL_DESC, + "Mismatching logical block size: {%#RX16,%#RX16}", + RT_BE2H_U16(pVolDesc->cbLogicalBlock.be), RT_LE2H_U16(pVolDesc->cbLogicalBlock.le)); + if (RT_LE2H_U32(pVolDesc->VolumeSpaceSize.le) != RT_BE2H_U32(pVolDesc->VolumeSpaceSize.be)) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_SUP_VOL_DESC, + "Mismatching volume space size: {%#RX32,%#RX32}", + RT_BE2H_U32(pVolDesc->VolumeSpaceSize.be), RT_LE2H_U32(pVolDesc->VolumeSpaceSize.le)); + if (RT_LE2H_U16(pVolDesc->cVolumesInSet.le) != RT_BE2H_U16(pVolDesc->cVolumesInSet.be)) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_SUP_VOL_DESC, + "Mismatching volumes in set: {%#RX16,%#RX16}", + RT_BE2H_U16(pVolDesc->cVolumesInSet.be), RT_LE2H_U16(pVolDesc->cVolumesInSet.le)); + if (RT_LE2H_U16(pVolDesc->VolumeSeqNo.le) != RT_BE2H_U16(pVolDesc->VolumeSeqNo.be)) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_SUP_VOL_DESC, + "Mismatching volume sequence no.: {%#RX16,%#RX16}", + RT_BE2H_U16(pVolDesc->VolumeSeqNo.be), RT_LE2H_U16(pVolDesc->VolumeSeqNo.le)); + if (RT_LE2H_U32(pVolDesc->cbPathTable.le) != RT_BE2H_U32(pVolDesc->cbPathTable.be)) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_SUP_VOL_DESC, + "Mismatching path table size: {%#RX32,%#RX32}", + RT_BE2H_U32(pVolDesc->cbPathTable.be), RT_LE2H_U32(pVolDesc->cbPathTable.le)); + + /* + * Validate field values against our expectations. + */ + if (ISO9660_GET_ENDIAN(&pVolDesc->cbLogicalBlock) != ISO9660_SECTOR_SIZE) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_LOGICAL_BLOCK_SIZE_NOT_2KB, + "Unsupported block size: %#x", ISO9660_GET_ENDIAN(&pVolDesc->cbLogicalBlock)); + + if (ISO9660_GET_ENDIAN(&pVolDesc->cVolumesInSet) != pThis->cVolumesInSet) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_VOLUME_IN_SET_MISMATCH, "Volumes in set: %#x, expected %#x", + ISO9660_GET_ENDIAN(&pVolDesc->cVolumesInSet), pThis->cVolumesInSet); + + if (ISO9660_GET_ENDIAN(&pVolDesc->VolumeSeqNo) != pThis->idPrimaryVol) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_INVALID_VOLUMNE_SEQ_NO, + "Unexpected volume sequence number: %#x (expected %#x)", + ISO9660_GET_ENDIAN(&pVolDesc->VolumeSeqNo), pThis->idPrimaryVol); + + if (ISO9660_GET_ENDIAN(&pVolDesc->VolumeSpaceSize) != pThis->cBlocksInPrimaryVolumeSpace) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_INVALID_VOLUMNE_SEQ_NO, + "Volume space size differs between primary and supplementary descriptors: %#x, primary %#x", + ISO9660_GET_ENDIAN(&pVolDesc->VolumeSpaceSize), pThis->cBlocksInPrimaryVolumeSpace); + + /* + * Validate the root directory record. + */ + int rc = rtFsIsoImportValidateRootDirRec(pThis, &pVolDesc->RootDir.DirRec); + if (RT_FAILURE(rc)) + return rc; + + /* + * Is this a joliet descriptor? Ignore if not. + */ + uint8_t uJolietLevel = 0; + if ( pVolDesc->abEscapeSequences[0] == ISO9660_JOLIET_ESC_SEQ_0 + && pVolDesc->abEscapeSequences[1] == ISO9660_JOLIET_ESC_SEQ_1) + switch (pVolDesc->abEscapeSequences[2]) + { + case ISO9660_JOLIET_ESC_SEQ_2_LEVEL_1: uJolietLevel = 1; break; + case ISO9660_JOLIET_ESC_SEQ_2_LEVEL_2: uJolietLevel = 2; break; + case ISO9660_JOLIET_ESC_SEQ_2_LEVEL_3: uJolietLevel = 3; break; + default: Log(("rtFsIsoImportProcessSupplementaryDesc: last joliet escape sequence byte doesn't match: %#x\n", + pVolDesc->abEscapeSequences[2])); + } + if (uJolietLevel == 0) + return VINF_SUCCESS; + + /* + * Only one joliet descriptor. + */ + if (pThis->fSeenJoliet) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MULTIPLE_JOLIET_VOL_DESCS, + "More than one Joliet volume descriptor is not supported"); + pThis->fSeenJoliet = true; + + /* + * Import stuff if present and not opted out. + */ + if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_SYSTEM_ID)) + rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achSystemId, sizeof(pVolDesc->achSystemId), + RTFSISOMAKERSTRINGPROP_SYSTEM_ID); + if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_J_VOLUME_ID)) + rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achVolumeId, sizeof(pVolDesc->achVolumeId), + RTFSISOMAKERSTRINGPROP_VOLUME_ID); + if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_J_VOLUME_SET_ID)) + rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achVolumeSetId, sizeof(pVolDesc->achVolumeSetId), + RTFSISOMAKERSTRINGPROP_VOLUME_SET_ID); + if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_J_PUBLISHER_ID)) + rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achPublisherId, sizeof(pVolDesc->achPublisherId), + RTFSISOMAKERSTRINGPROP_PUBLISHER_ID); + if (pThis->fFlags & RTFSISOMK_IMPORT_F_J_DATA_PREPARER_ID) + rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achDataPreparerId, sizeof(pVolDesc->achDataPreparerId), + RTFSISOMAKERSTRINGPROP_DATA_PREPARER_ID); + if (pThis->fFlags & RTFSISOMK_IMPORT_F_J_APPLICATION_ID) + rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achApplicationId, sizeof(pVolDesc->achApplicationId), + RTFSISOMAKERSTRINGPROP_APPLICATION_ID); + if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_J_COPYRIGHT_FID)) + rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achCopyrightFileId, sizeof(pVolDesc->achCopyrightFileId), + RTFSISOMAKERSTRINGPROP_COPYRIGHT_FILE_ID); + if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_J_ABSTRACT_FID)) + rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achAbstractFileId, sizeof(pVolDesc->achAbstractFileId), + RTFSISOMAKERSTRINGPROP_ABSTRACT_FILE_ID); + if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_J_BIBLIO_FID)) + rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achBibliographicFileId, sizeof(pVolDesc->achBibliographicFileId), + RTFSISOMAKERSTRINGPROP_BIBLIOGRAPHIC_FILE_ID); + + /* + * Process the directory tree. + */ + if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_JOLIET)) + return rtFsIsoImportProcessIso9660Tree(pThis, ISO9660_GET_ENDIAN(&pVolDesc->RootDir.DirRec.offExtent), + ISO9660_GET_ENDIAN(&pVolDesc->RootDir.DirRec.cbData), true /*fUnicode*/); + return VINF_SUCCESS; +} + + +/** + * Checks out an El Torito boot image to see if it requires info table patching. + * + * @returns IPRT status code (ignored). + * @param pThis The ISO importer instance. + * @param idxImageObj The configuration index of the image. + * @param offBootImage The block offset of the image. + */ +static int rtFsIsoImportProcessElToritoImage(PRTFSISOMKIMPORTER pThis, uint32_t idxImageObj, uint32_t offBootImage) +{ + ISO9660SYSLINUXINFOTABLE InfoTable; + int rc = RTVfsFileReadAt(pThis->hSrcFile, offBootImage * (uint64_t)ISO9660_SECTOR_SIZE + ISO9660SYSLINUXINFOTABLE_OFFSET, + &InfoTable, sizeof(InfoTable), NULL); + if (RT_SUCCESS(rc)) + { + if ( RT_LE2H_U32(InfoTable.offBootFile) == offBootImage + && RT_LE2H_U32(InfoTable.offPrimaryVolDesc) == pThis->offPrimaryVolDesc + && ASMMemIsAllU8(&InfoTable.auReserved[0], sizeof(InfoTable.auReserved), 0) ) + { + rc = RTFsIsoMakerObjEnableBootInfoTablePatching(pThis->hIsoMaker, idxImageObj, true /*fEnable*/); + if (RT_FAILURE(rc)) + return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerObjEnableBootInfoTablePatching failed: %Rrc", rc); + } + } + return VINF_SUCCESS; +} + + +/** + * Processes a boot catalog default or section entry. + * + * @returns IPRT status code (ignored). + * @param pThis The ISO importer instance. + * @param iEntry The boot catalog entry number. This is 1 for + * the default entry, and 3+ for section entries. + * @param cMaxEntries Maximum number of entries. + * @param pEntry The entry to process. + * @param pcSkip Where to return the number of extension entries to skip. + */ +static int rtFsIsoImportProcessElToritoSectionEntry(PRTFSISOMKIMPORTER pThis, uint32_t iEntry, uint32_t cMaxEntries, + PCISO9660ELTORITOSECTIONENTRY pEntry, uint32_t *pcSkip) +{ + *pcSkip = 0; + + /* + * Check the boot indicator type for entry 1. + */ + if ( pEntry->bBootIndicator != ISO9660_ELTORITO_BOOT_INDICATOR_BOOTABLE + && pEntry->bBootIndicator != ISO9660_ELTORITO_BOOT_INDICATOR_NOT_BOOTABLE) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_DEF_ENTRY_INVALID_BOOT_IND, + "Default boot catalog entry has an invalid boot indicator: %#x", pEntry->bBootIndicator); + + /* + * Check the media type and flags. + */ + uint32_t cbDefaultSize; + uint8_t bMediaType = pEntry->bBootMediaType; + switch (bMediaType & ISO9660_ELTORITO_BOOT_MEDIA_TYPE_MASK) + { + case ISO9660_ELTORITO_BOOT_MEDIA_TYPE_FLOPPY_1_2_MB: + cbDefaultSize = 512 * 80 * 15 * 2; + break; + + case ISO9660_ELTORITO_BOOT_MEDIA_TYPE_FLOPPY_1_44_MB: + cbDefaultSize = 512 * 80 * 18 * 2; + break; + + case ISO9660_ELTORITO_BOOT_MEDIA_TYPE_FLOPPY_2_88_MB: + cbDefaultSize = 512 * 80 * 36 * 2; + break; + + case ISO9660_ELTORITO_BOOT_MEDIA_TYPE_NO_EMULATION: + case ISO9660_ELTORITO_BOOT_MEDIA_TYPE_HARD_DISK: + cbDefaultSize = 0; + break; + + default: + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_INVALID_BOOT_MEDIA_TYPE, + "Boot catalog entry #%#x has an invalid boot media type: %#x", bMediaType); + } + + if (iEntry == 1) + { + if (bMediaType & ISO9660_ELTORITO_BOOT_MEDIA_F_MASK) + { + rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_DEF_ENTRY_INVALID_FLAGS, + "Boot catalog entry #%#x has an invalid boot media type: %#x", bMediaType); + bMediaType &= ~ISO9660_ELTORITO_BOOT_MEDIA_F_MASK; + } + } + else + { + if (bMediaType & ISO9660_ELTORITO_BOOT_MEDIA_F_RESERVED) + { + rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_ENTRY_RESERVED_FLAG, + "Boot catalog entry #%#x has an invalid boot media type: %#x", bMediaType); + bMediaType &= ~ISO9660_ELTORITO_BOOT_MEDIA_F_RESERVED; + } + } + + /* + * Complain if bUnused is used. + */ + if (pEntry->bUnused != 0) + rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_ENTRY_USES_UNUSED_FIELD, + "Boot catalog entry #%#x has a non-zero unused field: %#x", pEntry->bUnused); + + /* + * Check out the boot image offset and turn that into an index of a file + */ + uint32_t offBootImage = RT_LE2H_U32(pEntry->offBootImage); + if (offBootImage >= pThis->cBlocksInSrcFile) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_ENTRY_IMAGE_OUT_OF_BOUNDS, + "Boot catalog entry #%#x has an out of bound boot image block number: %#RX32, max %#RX32", + offBootImage, pThis->cBlocksInPrimaryVolumeSpace); + + int rc; + uint32_t idxImageObj; + PRTFSISOMKIMPBLOCK2FILE pBlock2File = (PRTFSISOMKIMPBLOCK2FILE)RTAvlU32Get(&pThis->Block2FileRoot, offBootImage); + if (pBlock2File) + idxImageObj = pBlock2File->idxObj; + else + { + if (cbDefaultSize == 0) + { + pBlock2File = (PRTFSISOMKIMPBLOCK2FILE)RTAvlU32GetBestFit(&pThis->Block2FileRoot, offBootImage, true /*fAbove*/); + if (pBlock2File) + cbDefaultSize = RT_MIN(pBlock2File->Core.Key - offBootImage, UINT32_MAX / ISO9660_SECTOR_SIZE + 1) + * ISO9660_SECTOR_SIZE; + else if (offBootImage < pThis->cBlocksInSrcFile) + cbDefaultSize = RT_MIN(pThis->cBlocksInSrcFile - offBootImage, UINT32_MAX / ISO9660_SECTOR_SIZE + 1) + * ISO9660_SECTOR_SIZE; + else + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_ENTRY_UNKNOWN_IMAGE_SIZE, + "Boot catalog entry #%#x has an invalid boot media type: %#x", bMediaType); + } + + if (pThis->idxSrcFile != UINT32_MAX) + { + rc = RTFsIsoMakerAddCommonSourceFile(pThis->hIsoMaker, pThis->hSrcFile, &pThis->idxSrcFile); + if (RT_FAILURE(rc)) + return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerAddCommonSourceFile failed: %Rrc", rc); + Assert(pThis->idxSrcFile != UINT32_MAX); + } + + rc = RTFsIsoMakerAddUnnamedFileWithCommonSrc(pThis->hIsoMaker, pThis->idxSrcFile, + offBootImage * (uint64_t)ISO9660_SECTOR_SIZE, + cbDefaultSize, NULL, &idxImageObj); + if (RT_FAILURE(rc)) + return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerAddUnnamedFileWithCommonSrc failed on boot entry #%#x: %Rrc", + iEntry, rc); + } + + /* + * Deal with selection criteria. Use the last sector of abBuf to gather it + * into a single data chunk. + */ + size_t cbSelCrit = 0; + uint8_t *pbSelCrit = &pThis->abBuf[sizeof(pThis->abBuf) - ISO9660_SECTOR_SIZE]; + if (pEntry->bSelectionCriteriaType != ISO9660_ELTORITO_SEL_CRIT_TYPE_NONE) + { + memcpy(pbSelCrit, pEntry->abSelectionCriteria, sizeof(pEntry->abSelectionCriteria)); + cbSelCrit = sizeof(pEntry->abSelectionCriteria); + + if ( (bMediaType & ISO9660_ELTORITO_BOOT_MEDIA_F_CONTINUATION) + && iEntry + 1 < cMaxEntries) + { + uint32_t iExtEntry = iEntry + 1; + PCISO9660ELTORITOSECTIONENTRYEXT pExtEntry = (PCISO9660ELTORITOSECTIONENTRYEXT)pEntry; + for (;;) + { + pExtEntry++; + + if (pExtEntry->bExtensionId != ISO9660_ELTORITO_SECTION_ENTRY_EXT_ID) + { + rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_EXT_ENTRY_INVALID_ID, + "Invalid header ID for extension entry #%#x: %#x", iExtEntry, pExtEntry->bExtensionId); + break; + } + *pcSkip += 1; + + memcpy(&pbSelCrit[cbSelCrit], pExtEntry->abSelectionCriteria, sizeof(pExtEntry->abSelectionCriteria)); + cbSelCrit += sizeof(pExtEntry->abSelectionCriteria); + + if (pExtEntry->fFlags & ISO9660_ELTORITO_SECTION_ENTRY_EXT_F_UNUSED_MASK) + rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_EXT_ENTRY_UNDEFINED_FLAGS, + "Boot catalog extension entry #%#x uses undefined flags: %#x", iExtEntry, pExtEntry->fFlags); + + iExtEntry++; + if (!(pExtEntry->fFlags & ISO9660_ELTORITO_SECTION_ENTRY_EXT_F_MORE)) + break; + if (iExtEntry >= cMaxEntries) + { + rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_EXT_ENTRY_END_OF_SECTOR, + "Boot catalog extension entry #%#x sets the MORE flag, but we have reached the end of the boot catalog sector"); + break; + } + } + Assert(*pcSkip = iExtEntry - iEntry); + } + else if (bMediaType & ISO9660_ELTORITO_BOOT_MEDIA_F_CONTINUATION) + rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_ENTRY_CONTINUATION_EOS, + "Boot catalog extension entry #%#x sets the MORE flag, but we have reached the end of the boot catalog sector"); + } + else if (bMediaType & ISO9660_ELTORITO_BOOT_MEDIA_F_CONTINUATION) + rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_ENTRY_CONTINUATION_WITH_NONE, + "Boot catalog entry #%#x uses the continuation flag with selection criteria NONE", iEntry); + + /* + * Add the entry. + */ + rc = RTFsIsoMakerBootCatSetSectionEntry(pThis->hIsoMaker, iEntry, idxImageObj, bMediaType, pEntry->bSystemType, + pEntry->bBootIndicator == ISO9660_ELTORITO_BOOT_INDICATOR_BOOTABLE, + pEntry->uLoadSeg, pEntry->cEmulatedSectorsToLoad, + pEntry->bSelectionCriteriaType, pbSelCrit, cbSelCrit); + if (RT_SUCCESS(rc)) + { + pThis->pResults->cBootCatEntries += 1 + *pcSkip; + rc = rtFsIsoImportProcessElToritoImage(pThis, idxImageObj, offBootImage); + } + else + rtFsIsoImpError(pThis, rc, "RTFsIsoMakerBootCatSetSectionEntry failed for entry #%#x: %Rrc", iEntry, rc); + return rc; +} + + + +/** + * Processes a boot catalog section header entry. + * + * @returns IPRT status code (ignored). + * @param pThis The ISO importer instance. + * @param iEntry The boot catalog entry number. + * @param pEntry The entry to process. + */ +static int rtFsIsoImportProcessElToritoSectionHeader(PRTFSISOMKIMPORTER pThis, uint32_t iEntry, + PCISO9660ELTORITOSECTIONHEADER pEntry, char pszId[32]) +{ + Assert(pEntry->bHeaderId == ISO9660_ELTORITO_HEADER_ID_SECTION_HEADER); + + /* Deal with the string. ASSUME it doesn't contain zeros in non-terminal positions. */ + if (pEntry->achSectionId[0] == '\0') + pszId = NULL; + else + { + memcpy(pszId, pEntry->achSectionId, sizeof(pEntry->achSectionId)); + pszId[sizeof(pEntry->achSectionId)] = '\0'; + } + + int rc = RTFsIsoMakerBootCatSetSectionHeaderEntry(pThis->hIsoMaker, iEntry, RT_LE2H_U16(pEntry->cEntries), + pEntry->bPlatformId, pszId); + if (RT_SUCCESS(rc)) + pThis->pResults->cBootCatEntries++; + else + rtFsIsoImpError(pThis, rc, + "RTFsIsoMakerBootCatSetSectionHeaderEntry failed for entry #%#x (bPlatformId=%#x cEntries=%#x): %Rrc", + iEntry, RT_LE2H_U16(pEntry->cEntries), pEntry->bPlatformId, rc); + return rc; +} + + +/** + * Processes a El Torito volume descriptor. + * + * @returns IPRT status code (ignorable). + * @param pThis The ISO importer instance. + * @param pVolDesc The volume descriptor to process. + */ +static int rtFsIsoImportProcessElToritoDesc(PRTFSISOMKIMPORTER pThis, PISO9660BOOTRECORDELTORITO pVolDesc) +{ + /* + * Read the boot catalog into the abBuf. + */ + uint32_t offBootCatalog = RT_LE2H_U32(pVolDesc->offBootCatalog); + if (offBootCatalog >= pThis->cBlocksInPrimaryVolumeSpace) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_BAD_OUT_OF_BOUNDS, + "Boot catalog block number is out of bounds: %#RX32, max %#RX32", + offBootCatalog, pThis->cBlocksInPrimaryVolumeSpace); + + int rc = RTVfsFileReadAt(pThis->hSrcFile, offBootCatalog * (uint64_t)ISO9660_SECTOR_SIZE, + pThis->abBuf, ISO9660_SECTOR_SIZE, NULL); + if (RT_FAILURE(rc)) + return rtFsIsoImpError(pThis, rc, "Error reading boot catalog at block #%#RX32: %Rrc", offBootCatalog, rc); + + + /* + * Process the 'validation entry'. + */ + PCISO9660ELTORITOVALIDATIONENTRY pValEntry = (PCISO9660ELTORITOVALIDATIONENTRY)&pThis->abBuf[0]; + if (pValEntry->bHeaderId != ISO9660_ELTORITO_HEADER_ID_VALIDATION_ENTRY) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_BAD_VALIDATION_HEADER_ID, + "Invalid boot catalog validation entry header ID: %#x, expected %#x", + pValEntry->bHeaderId, ISO9660_ELTORITO_HEADER_ID_VALIDATION_ENTRY); + + if ( pValEntry->bKey1 != ISO9660_ELTORITO_KEY_BYTE_1 + || pValEntry->bKey2 != ISO9660_ELTORITO_KEY_BYTE_2) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_BAD_VALIDATION_KEYS, + "Invalid boot catalog validation entry keys: %#x %#x, expected %#x %#x", + pValEntry->bKey1, pValEntry->bKey2, ISO9660_ELTORITO_KEY_BYTE_1, ISO9660_ELTORITO_KEY_BYTE_2); + + /* Check the checksum (should sum up to be zero). */ + uint16_t uChecksum = 0; + uint16_t const *pu16 = (uint16_t const *)pValEntry; + size_t cLeft = sizeof(*pValEntry) / sizeof(uint16_t); + while (cLeft-- > 0) + { + uChecksum += RT_LE2H_U16(*pu16); + pu16++; + } + if (uChecksum != 0) + return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_BAD_VALIDATION_CHECKSUM, + "Invalid boot catalog validation entry checksum: %#x, expected 0", uChecksum); + + /* The string ID. ASSUME no leading zeros in valid strings. */ + const char *pszId = NULL; + char szId[32]; + if (pValEntry->achId[0] != '\0') + { + memcpy(szId, pValEntry->achId, sizeof(pValEntry->achId)); + szId[sizeof(pValEntry->achId)] = '\0'; + pszId = szId; + } + + /* + * Before we tell the ISO maker about the validation entry, we need to sort + * out the file backing the boot catalog. This isn't fatal if it fails. + */ + PRTFSISOMKIMPBLOCK2FILE pBlock2File = (PRTFSISOMKIMPBLOCK2FILE)RTAvlU32Get(&pThis->Block2FileRoot, offBootCatalog); + if (pBlock2File) + { + rc = RTFsIsoMakerBootCatSetFile(pThis->hIsoMaker, pBlock2File->idxObj); + if (RT_FAILURE(rc)) + rtFsIsoImpError(pThis, rc, "RTFsIsoMakerBootCatSetFile failed: %Rrc", rc); + } + + /* + * Set the validation entry. + */ + rc = RTFsIsoMakerBootCatSetValidationEntry(pThis->hIsoMaker, pValEntry->bPlatformId, pszId); + if (RT_FAILURE(rc)) + return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerBootCatSetValidationEntry(,%#x,%s) failed: %Rrc", + pValEntry->bPlatformId, pszId); + Assert(pThis->pResults->cBootCatEntries == UINT32_MAX); + pThis->pResults->cBootCatEntries = 0; + + /* + * Process the default entry and any subsequent entries. + */ + bool fSeenFinal = false; + uint32_t const cMaxEntries = ISO9660_SECTOR_SIZE / ISO9660_ELTORITO_ENTRY_SIZE; + for (uint32_t iEntry = 1; iEntry < cMaxEntries; iEntry++) + { + uint8_t const *pbEntry = &pThis->abBuf[iEntry * ISO9660_ELTORITO_ENTRY_SIZE]; + uint8_t const idHeader = *pbEntry; + + /* KLUDGE ALERT! Older ISO images, like RHEL5-Server-20070208.0-x86_64-DVD.iso lacks + terminator entry. So, quietly stop with an entry that's all zeros. */ + if ( idHeader == ISO9660_ELTORITO_BOOT_INDICATOR_NOT_BOOTABLE /* 0x00 */ + && iEntry != 1 /* default */ + && ASMMemIsZero(pbEntry, ISO9660_ELTORITO_ENTRY_SIZE)) + return rc; + + if ( iEntry == 1 /* default*/ + || idHeader == ISO9660_ELTORITO_BOOT_INDICATOR_BOOTABLE + || idHeader == ISO9660_ELTORITO_BOOT_INDICATOR_NOT_BOOTABLE) + { + uint32_t cSkip = 0; + rtFsIsoImportProcessElToritoSectionEntry(pThis, iEntry, cMaxEntries, (PCISO9660ELTORITOSECTIONENTRY)pbEntry, &cSkip); + iEntry += cSkip; + } + else if (idHeader == ISO9660_ELTORITO_HEADER_ID_SECTION_HEADER) + rtFsIsoImportProcessElToritoSectionHeader(pThis, iEntry, (PCISO9660ELTORITOSECTIONHEADER)pbEntry, szId); + else if (idHeader == ISO9660_ELTORITO_HEADER_ID_FINAL_SECTION_HEADER) + { + fSeenFinal = true; + break; + } + else + rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_UNKNOWN_HEADER_ID, + "Unknown boot catalog header ID for entry #%#x: %#x", iEntry, idHeader); + } + + if (!fSeenFinal) + rc = rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_MISSING_FINAL_OR_TOO_BIG, + "Boot catalog is probably larger than a sector, or it's missing the final section header entry"); + return rc; +} + + +/** + * Imports an existing ISO. + * + * Just like other source files, the existing image must remain present and + * unmodified till the ISO maker is done with it. + * + * @returns IRPT status code. + * @param hIsoMaker The ISO maker handle. + * @param hIsoFile VFS file handle to the existing image to import / clone. + * @param fFlags Reserved for the future, MBZ. + * @param poffError Where to return the position in @a pszIso + * causing trouble when opening it for reading. + * Optional. + * @param pErrInfo Where to return additional error information. + * Optional. + */ +RTDECL(int) RTFsIsoMakerImport(RTFSISOMAKER hIsoMaker, RTVFSFILE hIsoFile, uint32_t fFlags, + PRTFSISOMAKERIMPORTRESULTS pResults, PRTERRINFO pErrInfo) +{ + /* + * Validate input. + */ + AssertPtrReturn(pResults, VERR_INVALID_POINTER); + pResults->cAddedNames = 0; + pResults->cAddedDirs = 0; + pResults->cbAddedDataBlocks = 0; + pResults->cAddedFiles = 0; + pResults->cAddedSymlinks = 0; + pResults->cBootCatEntries = UINT32_MAX; + pResults->cbSysArea = 0; + pResults->cErrors = 0; + AssertReturn(!(fFlags & ~RTFSISOMK_IMPORT_F_VALID_MASK), VERR_INVALID_FLAGS); + + /* + * Get the file size. + */ + uint64_t cbSrcFile = 0; + int rc = RTVfsFileGetSize(hIsoFile, &cbSrcFile); + if (RT_SUCCESS(rc)) + { + /* + * Allocate and init the importer state. + */ + PRTFSISOMKIMPORTER pThis = (PRTFSISOMKIMPORTER)RTMemAllocZ(sizeof(*pThis)); + if (pThis) + { + pThis->hIsoMaker = hIsoMaker; + pThis->fFlags = fFlags; + pThis->rc = VINF_SUCCESS; + pThis->pErrInfo = pErrInfo; + pThis->hSrcFile = hIsoFile; + pThis->cbSrcFile = cbSrcFile; + pThis->cBlocksInSrcFile = cbSrcFile / ISO9660_SECTOR_SIZE; + pThis->idxSrcFile = UINT32_MAX; + //pThis->Block2FileRoot = NULL; + //pThis->cBlocksInPrimaryVolumeSpace = 0; + //pThis->cbPrimaryVolumeSpace = 0 + //pThis->cVolumesInSet = 0; + //pThis->idPrimaryVol = 0; + //pThis->fSeenJoliet = false; + pThis->pResults = pResults; + //pThis->fSuspSeenSP = false; + //pThis->offSuspSkip = 0; + pThis->offRockBuf = UINT64_MAX; + + /* + * Check if this looks like a plausible ISO by checking out the first volume descriptor. + */ + rc = RTVfsFileReadAt(hIsoFile, _32K, &pThis->uSectorBuf.PrimVolDesc, sizeof(pThis->uSectorBuf.PrimVolDesc), NULL); + if (RT_SUCCESS(rc)) + { + if ( pThis->uSectorBuf.VolDescHdr.achStdId[0] == ISO9660VOLDESC_STD_ID_0 + && pThis->uSectorBuf.VolDescHdr.achStdId[1] == ISO9660VOLDESC_STD_ID_1 + && pThis->uSectorBuf.VolDescHdr.achStdId[2] == ISO9660VOLDESC_STD_ID_2 + && pThis->uSectorBuf.VolDescHdr.achStdId[3] == ISO9660VOLDESC_STD_ID_3 + && pThis->uSectorBuf.VolDescHdr.achStdId[4] == ISO9660VOLDESC_STD_ID_4 + && ( pThis->uSectorBuf.VolDescHdr.bDescType == ISO9660VOLDESC_TYPE_PRIMARY + || pThis->uSectorBuf.VolDescHdr.bDescType == ISO9660VOLDESC_TYPE_BOOT_RECORD) ) + { + /* + * Process the volume descriptors using the sector buffer, starting + * with the one we've already got sitting there. We postpone processing + * the el torito one till after the others, so we can name files and size + * referenced in it. + */ + uint32_t cPrimaryVolDescs = 0; + uint32_t iElTorito = UINT32_MAX; + uint32_t iVolDesc = 0; + for (;;) + { + switch (pThis->uSectorBuf.VolDescHdr.bDescType) + { + case ISO9660VOLDESC_TYPE_PRIMARY: + cPrimaryVolDescs++; + if (cPrimaryVolDescs == 1) + { + pThis->offPrimaryVolDesc = _32K / ISO9660_SECTOR_SIZE + iVolDesc; + rtFsIsoImportProcessPrimaryDesc(pThis, &pThis->uSectorBuf.PrimVolDesc); + } + else + rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MULTIPLE_PRIMARY_VOL_DESCS, + "Only a single primary volume descriptor is currently supported"); + break; + + case ISO9660VOLDESC_TYPE_SUPPLEMENTARY: + if (cPrimaryVolDescs > 0) + rtFsIsoImportProcessSupplementaryDesc(pThis, &pThis->uSectorBuf.SupVolDesc); + else + rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_SUPPLEMENTARY_BEFORE_PRIMARY, + "Primary volume descriptor expected before any supplementary descriptors!"); + break; + + case ISO9660VOLDESC_TYPE_BOOT_RECORD: + if (strcmp(pThis->uSectorBuf.ElToritoDesc.achBootSystemId, + ISO9660BOOTRECORDELTORITO_BOOT_SYSTEM_ID) == 0) + { + if (iElTorito == UINT32_MAX) + iElTorito = iVolDesc; + else + rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MULTIPLE_EL_TORITO_DESCS, + "Only a single El Torito descriptor exepcted!"); + } + break; + + case ISO9660VOLDESC_TYPE_PARTITION: + /* ignore for now */ + break; + + case ISO9660VOLDESC_TYPE_TERMINATOR: + AssertFailed(); + break; + } + + + /* + * Read the next volume descriptor and check the signature. + */ + iVolDesc++; + if (iVolDesc >= 32) + { + rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_TOO_MANY_VOL_DESCS, "Parses at most 32 volume descriptors"); + break; + } + + rc = RTVfsFileReadAt(hIsoFile, _32K + iVolDesc * ISO9660_SECTOR_SIZE, + &pThis->uSectorBuf, sizeof(pThis->uSectorBuf), NULL); + if (RT_FAILURE(rc)) + { + rtFsIsoImpError(pThis, rc, "Error reading the volume descriptor #%u at %#RX32: %Rrc", + iVolDesc, _32K + iVolDesc * ISO9660_SECTOR_SIZE, rc); + break; + } + + if ( pThis->uSectorBuf.VolDescHdr.achStdId[0] != ISO9660VOLDESC_STD_ID_0 + || pThis->uSectorBuf.VolDescHdr.achStdId[1] != ISO9660VOLDESC_STD_ID_1 + || pThis->uSectorBuf.VolDescHdr.achStdId[2] != ISO9660VOLDESC_STD_ID_2 + || pThis->uSectorBuf.VolDescHdr.achStdId[3] != ISO9660VOLDESC_STD_ID_3 + || pThis->uSectorBuf.VolDescHdr.achStdId[4] != ISO9660VOLDESC_STD_ID_4) + { + rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_INVALID_VOL_DESC_HDR, + "Invalid volume descriptor header #%u at %#RX32: %.*Rhxs", + iVolDesc, _32K + iVolDesc * ISO9660_SECTOR_SIZE, + (int)sizeof(pThis->uSectorBuf.VolDescHdr), &pThis->uSectorBuf.VolDescHdr); + break; + } + /** @todo UDF support. */ + if (pThis->uSectorBuf.VolDescHdr.bDescType == ISO9660VOLDESC_TYPE_TERMINATOR) + break; + } + + /* + * Process the system area. + */ + if (RT_SUCCESS(pThis->rc) || pThis->idxSrcFile != UINT32_MAX) + { + rc = RTVfsFileReadAt(hIsoFile, 0, pThis->abBuf, _32K, NULL); + if (RT_SUCCESS(rc)) + { + if (!ASMMemIsAllU8(pThis->abBuf, _32K, 0)) + { + /* Drop zero sectors from the end. */ + uint32_t cbSysArea = _32K; + while ( cbSysArea >= ISO9660_SECTOR_SIZE + && ASMMemIsAllU8(&pThis->abBuf[cbSysArea - ISO9660_SECTOR_SIZE], ISO9660_SECTOR_SIZE, 0)) + cbSysArea -= ISO9660_SECTOR_SIZE; + + /** @todo HFS */ + pThis->pResults->cbSysArea = cbSysArea; + rc = RTFsIsoMakerSetSysAreaContent(hIsoMaker, pThis->abBuf, cbSysArea, 0); + if (RT_FAILURE(rc)) + rtFsIsoImpError(pThis, rc, "RTFsIsoMakerSetSysAreaContent failed: %Rrc", rc); + } + } + else + rtFsIsoImpError(pThis, rc, "Error reading the system area (0..32KB): %Rrc", rc); + } + + /* + * Do the El Torito descriptor. + */ + if ( iElTorito != UINT32_MAX + && !(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_BOOT) + && (RT_SUCCESS(pThis->rc) || pThis->idxSrcFile != UINT32_MAX)) + { + rc = RTVfsFileReadAt(hIsoFile, _32K + iElTorito * ISO9660_SECTOR_SIZE, + &pThis->uSectorBuf, sizeof(pThis->uSectorBuf), NULL); + if (RT_SUCCESS(rc)) + rtFsIsoImportProcessElToritoDesc(pThis, &pThis->uSectorBuf.ElToritoDesc); + else + rtFsIsoImpError(pThis, rc, "Error reading the El Torito volume descriptor at %#RX32: %Rrc", + _32K + iElTorito * ISO9660_SECTOR_SIZE, rc); + } + + /* + * Return the first error status. + */ + rc = pThis->rc; + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_ISOMK_IMPORT_UNKNOWN_FORMAT, "Invalid volume descriptor header: %.*Rhxs", + (int)sizeof(pThis->uSectorBuf.VolDescHdr), &pThis->uSectorBuf.VolDescHdr); + } + + /* + * Destroy the state. + */ + RTAvlU32Destroy(&pThis->Block2FileRoot, rtFsIsoMakerImportDestroyData2File, NULL); + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + } + return rc; +} + diff --git a/src/VBox/Runtime/common/fs/isovfs.cpp b/src/VBox/Runtime/common/fs/isovfs.cpp new file mode 100644 index 00000000..a06664a3 --- /dev/null +++ b/src/VBox/Runtime/common/fs/isovfs.cpp @@ -0,0 +1,5992 @@ +/* $Id: isovfs.cpp $ */ +/** @file + * IPRT - ISO 9660 and UDF Virtual Filesystem (read only). + */ + +/* + * Copyright (C) 2017-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FS +#include "internal/iprt.h" +#include <iprt/fsvfs.h> + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/crc.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 /*??*/ )); \ + } \ + 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) +/** @} */ + + + +/********************************************************************************************************************************* +* 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; + + + +/** + * 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; + /** 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 +{ + /** Accessing the primary ISO-9660 volume. */ + RTFSISOVOLTYPE_ISO9960 = 0, + /** 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; + /** 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; +} 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; + + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + + +/********************************************************************************************************************************* +* 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, 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 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 pVol The volume. + */ +static int rtFsIsoCore_InitFrom9660DirRec(PRTFSISOCORE pCore, PCISO9660DIRREC pDirRec, uint32_t cDirRecs, + uint64_t offDirRec, uint32_t uVersion, PRTFSISOVOL pVol) +{ + RTListInit(&pCore->Entry); + pCore->cRefs = 1; + pCore->pParentDir = NULL; + pCore->pVol = pVol; + pCore->offDirRec = offDirRec; + pCore->idINode = offDirRec; + 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; + + 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 + { + size_t cbSkip = (offDirRec + pVol->cbSector) & ~(pVol->cbSector - 1U); + offDirRec += cbSkip; + pDirRec = (PCISO9660DIRREC)((uintptr_t)pDirRec + 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; + } + 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) + { + 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) 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, + 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 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, 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, 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 */ + + +/** + * 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. + */ +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(memcmp(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(memcmp(pDirRec->achFileId, pszEntry, cchEntry) != 0)) + return false; + *puVersion = 1; + } + else + { + if (RT_LIKELY(pDirRec->achFileId[cchEntry] != ';')) + return false; + if (RT_LIKELY(memcmp(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. + */ +static int rtFsIsoDir_FindEntry9660(PRTFSISODIRSHRD pThis, const char *pszEntry, uint64_t *poffDirRec, + PCISO9660DIRREC *ppDirRec, uint32_t *pcDirRecs, PRTFMODE pfMode, uint32_t *puVersion) +{ + Assert(pThis->Core.pVol->enmType != RTFSISOVOLTYPE_UDF); + + /* Set return values. */ + *poffDirRec = UINT64_MAX; + *ppDirRec = NULL; + *pcDirRecs = 1; + *pfMode = UINT32_MAX; + *puVersion = 0; + + /* + * 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); + } + + /* + * 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. */ + if (fIsUtf16) + { + if (RT_LIKELY(!rtFsIsoDir_IsEntryEqualUtf16Big(pDirRec, uBuf.wszEntry, cbEntry, cwcEntry, puVersion))) + { + /* Advance */ + offEntryInDir += pDirRec->cbDirRec; + continue; + } + } + else + { + if (RT_LIKELY(!rtFsIsoDir_IsEntryEqualAscii(pDirRec, uBuf.s.szUpper, cchUpper, puVersion))) + { + /** @todo check rock. */ + if (1) + { + /* Advance */ + offEntryInDir += pDirRec->cbDirRec; + continue; + } + } + } + + *poffDirRec = pThis->Core.FirstExtent.off + offEntryInDir; + *ppDirRec = pDirRec; + *pfMode = pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY + ? 0755 | RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY + : 0644 | RTFS_TYPE_FILE; + + /* + * 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; + rc = rtFsIsoDir_FindEntry9660(pShared, pszEntry, &offDirRec, &pDirRec, &cDirRecs, &fMode, &uVersion); + 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, &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, &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) +{ + 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); + + /** @todo check for rock ridge names here. */ + } + 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, pShared->Core.pVol); + int rc = rtFsIsoCore_QueryInfo(&TmpObj, &pDirEntry->Info, enmAddAttr); + + /* + * 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, + 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 ppShared Where to return the shared directory structure. + */ +static int rtFsIsoDirShrd_New9660(PRTFSISOVOL pThis, PRTFSISODIRSHRD pParentDir, PCISO9660DIRREC pDirRec, + uint32_t cDirRecs, uint64_t offDirRec, 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*/, 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 + + /* + * 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 phVfsDir Where to return the directory handle. + */ +static int rtFsIsoDir_New9660(PRTFSISOVOL pThis, PRTFSISODIRSHRD pParentDir, PCISO9660DIRREC pDirRec, + uint32_t cDirRecs, uint64_t offDirRec, 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, &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; + + 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; +} + + +/** + * @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, + 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 = { 0 }; + 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); + + /* + * 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 (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); + 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)); + } + 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_Destroy won't trip up. + */ + pThis->hVfsSelf = hVfsSelf; + pThis->hVfsBacking = hVfsBacking; /* Caller referenced it for us, we consume it; rtFsIsoVol_Destroy 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; + + /* + * Get stuff that may fail. + */ + int rc = RTVfsFileGetSize(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) + return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "More than one primary volume descriptor"); + rc = rtFsIsoVolHandlePrimaryVolDesc(pThis, &Buf.PrimaryVolDesc, offVolDesc, &RootDir, &offRootDirRec, pErrInfo); + } + 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) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, + "Missing ISO 9660 terminator volume descriptor? (Found %.5Rhxs)", Buf.VolDescHdr.achStdId); + 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 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, &pThis->pRootDir); + } + pThis->enmType = RTFSISOVOLTYPE_ISO9960; + return rtFsIsoDirShrd_New9660(pThis, NULL, &RootDir, 1, offRootDirRec, &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; + void *pvThis = NULL; + int rc = RTVfsNew(&g_rtFsIsoVolOps, sizeof(RTFSISOVOL), NIL_RTVFS, RTVFSLOCK_CREATE_RW, &hVfs, &pvThis); + if (RT_SUCCESS(rc)) + { + rc = rtFsIsoVolTryInit((PRTFSISOVOL)pvThis, 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..8af82602 --- /dev/null +++ b/src/VBox/Runtime/common/fs/ntfsvfs.cpp @@ -0,0 +1,5683 @@ +/* $Id: ntfsvfs.cpp $ */ +/** @file + * IPRT - NTFS Virtual Filesystem, currently only for reading allocation bitmap. + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FS +#include <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); + } + } + 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; RT_FALL_THRU(); + } + 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, + 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, + 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 & ~(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, + /* .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 = RTVfsFileGetSize(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..84432e35 --- /dev/null +++ b/src/VBox/Runtime/common/fs/xfsvfs.cpp @@ -0,0 +1,2240 @@ +/* $Id: xfsvfs.cpp $ */ +/** @file + * IPRT - XFS Virtual Filesystem. + */ + +/* + * Copyright (C) 2018-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_FS +#include <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; +} 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. + * + * @returns nothing. + * @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))); + } +} + + +/** + * Logs a AG free space block. + * + * @returns nothing. + * @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))); + } +} + + +/** + * Loads an AG inode information block. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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; +} + + +/** + * 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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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); +} + + +/** + * 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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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); +} + + +/** + * 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) +{ + PRTFSXFSINODE pInode = (PRTFSXFSINODE)RTMemAllocZ(sizeof(RTFSXFSINODE)); + if (RT_LIKELY(pInode)) + { + pInode->Core.Key = iInode; + pInode->cRefs = 0; + pThis->cbInodes += sizeof(RTFSXFSINODE); + } + + return pInode; +} + + +/** + * Frees the given inode. + * + * @returns nothing. + * @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 -= sizeof(RTFSXFSINODE); + } +} + + +/** + * 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 + sizeof(RTFSXFSINODE) <= 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; + XFSINODECORE Inode; + rc = RTVfsFileReadAt(pThis->hVfsBacking, offRead, &Inode, RT_MIN(sizeof(Inode), pThis->cbInode), NULL); + if (RT_SUCCESS(rc)) + { +#ifdef LOG_ENABLED + rtFsXfsInode_Log(pThis, iInode, &Inode); +#endif + + pInode->offInode = offRead; + pInode->fFlags = RT_BE2H_U16(Inode.fFlags); + pInode->enmFormat = Inode.enmFormat; + pInode->ObjInfo.cbObject = RT_BE2H_U64(Inode.cbInode); + pInode->ObjInfo.cbAllocated = RT_BE2H_U64(Inode.cBlocks) * pThis->cbBlock; + RTTimeSpecSetSeconds(&pInode->ObjInfo.AccessTime, RT_BE2H_U32(Inode.TsLastAccessed.cSecEpoch)); + RTTimeSpecAddNano(&pInode->ObjInfo.AccessTime, RT_BE2H_U32(Inode.TsLastAccessed.cNanoSec)); + RTTimeSpecSetSeconds(&pInode->ObjInfo.ModificationTime, RT_BE2H_U32(Inode.TsLastModified.cSecEpoch)); + RTTimeSpecAddNano(&pInode->ObjInfo.ModificationTime, RT_BE2H_U32(Inode.TsLastModified.cNanoSec)); + RTTimeSpecSetSeconds(&pInode->ObjInfo.ChangeTime, RT_BE2H_U32(Inode.TsCreatedModified.cSecEpoch)); + RTTimeSpecAddNano(&pInode->ObjInfo.ChangeTime, RT_BE2H_U32(Inode.TsCreatedModified.cNanoSec)); + pInode->ObjInfo.Attr.enmAdditional = RTFSOBJATTRADD_UNIX; + pInode->ObjInfo.Attr.u.Unix.uid = RT_BE2H_U32(Inode.uUid); + pInode->ObjInfo.Attr.u.Unix.gid = RT_BE2H_U32(Inode.uGid); + pInode->ObjInfo.Attr.u.Unix.cHardlinks = RT_BE2H_U16(Inode.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(Inode.cGeneration); + pInode->ObjInfo.Attr.u.Unix.Device = 0; + if (Inode.iVersion >= 3) + { + RTTimeSpecSetSeconds(&pInode->ObjInfo.BirthTime, RT_BE2H_U32(Inode.TsCreation.cSecEpoch)); + RTTimeSpecAddNano(&pInode->ObjInfo.BirthTime, RT_BE2H_U32(Inode.TsCreation.cNanoSec)); + } + else + pInode->ObjInfo.BirthTime = pInode->ObjInfo.ChangeTime; + + /* Fill in the mode. */ + pInode->ObjInfo.Attr.fMode = 0; + uint16_t fInodeMode = RT_BE2H_U16(Inode.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. + * + * @returns nothing. + * @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; +} + + +/** + * 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) +{ + RT_NOREF(pThis, pInode, iBlock, cBlocks, piBlockFs, pcBlocks, pfSparse); + return VERR_NOT_IMPLEMENTED; +} + + +/** + * 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; + } + + 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, + 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, + 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, + /* .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 = RTVfsFileGetSize(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); + |