summaryrefslogtreecommitdiffstats
path: root/src/VBox/Runtime/common/fs/extvfs.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Runtime/common/fs/extvfs.cpp')
-rw-r--r--src/VBox/Runtime/common/fs/extvfs.cpp2853
1 files changed, 2853 insertions, 0 deletions
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);
+