/* $Id$ */
/** @file
* IPRT Filesystem API (FileSys) - ext2/3 format.
*/
/*
* Copyright (C) 2012 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 LOG_GROUP_DEFAULT
#include <iprt/types.h>
#include <iprt/assert.h>
#include <iprt/mem.h>
#include <iprt/filesystem.h>
#include <iprt/string.h>
#include <iprt/vfs.h>
#include "internal/filesystem.h"
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/*
* The filesystem structures are from http://wiki.osdev.org/Ext2 and
* http://www.nongnu.org/ext2-doc/ext2.html
*/
/**
* Ext superblock.
*
* Everything is stored little endian on the disk.
*/
#pragma pack(1)
typedef struct ExtSuperBlock
{
/** Total number of inodes in the filesystem. */
uint32_t cInodesTotal;
/** Total number of blocks in the filesystem. */
uint32_t cBlocksTotal;
/** Number of blocks reserved for the super user. */
uint32_t cBlocksRsvdForSuperUser;
/** Total number of unallocated blocks. */
uint32_t cBlocksUnallocated;
/** Total number of unallocated inodes. */
uint32_t cInodesUnallocated;
/** Block number of block containing the superblock. */
uint32_t iBlockOfSuperblock;
/** Number of bits to shift 1024 to the left to get the block size */
uint32_t cBitsShiftLeftBlockSize;
/** Number of bits to shift 1024 to the left to get the fragment size */
uint32_t cBitsShiftLeftFragmentSize;
/** Number of blocks in each block group. */
uint32_t cBlocksPerGroup;
/** Number of fragments in each block group. */
uint32_t cFragmentsPerBlockGroup;
/** Number of inodes in each block group. */
uint32_t cInodesPerBlockGroup;
/** Last mount time. */
uint32_t u32LastMountTime;
/** Last written time. */
uint32_t u32LastWrittenTime;
/** Number of times the volume was mounted since the last check. */
uint16_t cMountsSinceLastCheck;
/** Number of mounts allowed before a consistency check. */
uint16_t cMaxMountsUntilCheck;
/** Signature to identify a ext2 volume. */
uint16_t u16Signature;
/** State of the filesystem (clean/errors) */
uint16_t u16FilesystemState;
/** What to do on an error. */
uint16_t u16ActionOnError;
/** Minor version field. */
uint16_t u16VersionMinor;
/** Time of last check. */
uint32_t u32LastCheckTime;
/** Interval between consistency checks. */
uint32_t u32CheckInterval;
/** Operating system ID of the filesystem creator. */
uint32_t u32OsIdCreator;
/** Major version field. */
uint32_t u32VersionMajor;
/** User ID that is allowed to use reserved blocks. */
uint16_t u16UidReservedBlocks;
/** Group ID that is allowed to use reserved blocks. */
uint16_t u16GidReservedBlocks;
/** Reserved fields. */
uint8_t abReserved[940];
} ExtSuperBlock;
#pragma pack()
AssertCompileSize(ExtSuperBlock, 1024);
/** Pointer to an ext super block. */
typedef ExtSuperBlock *PExtSuperBlock;
/** Ext2 signature. */
#define RTFILESYSTEM_EXT2_SIGNATURE (0xef53)
/** Clean filesystem state. */
#define RTFILESYSTEM_EXT2_STATE_CLEAN (0x0001)
/** Error filesystem state. */
#define RTFILESYSTEM_EXT2_STATE_ERRORS (0x0002)
/**
* Block group descriptor.
*/
#pragma pack(1)
typedef struct BlockGroupDesc
{
/** Block address of the block bitmap. */
uint32_t offBlockBitmap;
/** Block address of the inode bitmap. */
uint32_t offInodeBitmap;
/** Start block address of the inode table. */
uint32_t offInodeTable;
/** Number of unallocated blocks in group. */
uint16_t cBlocksUnallocated;
/** Number of unallocated inodes in group. */
uint16_t cInodesUnallocated;
/** Number of directories in the group. */
uint16_t cDirectories;
/** Padding. */
uint16_t u16Padding;
/** Reserved. */
uint8_t abReserved[12];
} BlockGroupDesc;
#pragma pack()
AssertCompileSize(BlockGroupDesc, 32);
/** Pointer to an ext block group descriptor. */
typedef BlockGroupDesc *PBlockGroupDesc;
/**
* Cached block group descriptor data.
*/
typedef struct RTFILESYSTEMEXTBLKGRP
{
/** Start offset (in bytes and from the start of the disk). */
uint64_t offStart;
/** Last offset in the block group (inclusive). */
uint64_t offLast;
/** Block bitmap - variable in size (depends on the block size
* and number of blocks per group). */
uint8_t abBlockBitmap[1];
} RTFILESYSTEMEXTBLKGRP;
/** Pointer to block group descriptor data. */
typedef RTFILESYSTEMEXTBLKGRP *PRTFILESYSTEMEXTBLKGRP;
/**
* Ext2/3 filesystem data.
*/
typedef struct RTFILESYSTEMEXT
{
/** VFS file handle. */
RTVFSFILE hVfsFile;
/** Block number of the superblock. */
uint32_t iSbBlock;
/** Size of one block. */
size_t cbBlock;
/** Number of blocks in one group. */
unsigned cBlocksPerGroup;
/** Number of blocks groups in the volume. */
unsigned cBlockGroups;
/** Cached block group descriptor data. */
PRTFILESYSTEMEXTBLKGRP pBlkGrpDesc;
} RTFILESYSTEMEXT;
/** Pointer to the ext filesystem data. */
typedef RTFILESYSTEMEXT *PRTFILESYSTEMEXT;
/*******************************************************************************
* Methods *
*******************************************************************************/
/**
* Loads the block descriptor of the given block group from the medium.
*
* @returns IPRT status code.
* @param pThis EXT filesystem instance data.
* @param iBlkGrp Block group number to load.
*/
static int rtFsExtLoadBlkGrpDesc(PRTFILESYSTEMEXT pThis, uint32_t iBlkGrp)
{
int rc = VINF_SUCCESS;
PRTFILESYSTEMEXTBLKGRP pBlkGrpDesc = pThis->pBlkGrpDesc;
uint64_t offRead = (pThis->iSbBlock + 1) * pThis->cbBlock;
BlockGroupDesc BlkDesc;
size_t cbBlockBitmap;
cbBlockBitmap = pThis->cBlocksPerGroup / 8;
if (pThis->cBlocksPerGroup % 8)
cbBlockBitmap++;
if (!pBlkGrpDesc)
{
size_t cbBlkDesc = RT_OFFSETOF(RTFILESYSTEMEXTBLKGRP, abBlockBitmap[cbBlockBitmap]);
pBlkGrpDesc = (PRTFILESYSTEMEXTBLKGRP)RTMemAllocZ(cbBlkDesc);
if (!pBlkGrpDesc)
return VERR_NO_MEMORY;
}
rc = RTVfsFileReadAt(pThis->hVfsFile, offRead, &BlkDesc, sizeof(BlkDesc), NULL);
if (RT_SUCCESS(rc))
{
pBlkGrpDesc->offStart = pThis->iSbBlock + (uint64_t)iBlkGrp * pThis->cBlocksPerGroup * pThis->cbBlock;
pBlkGrpDesc->offLast = pBlkGrpDesc->offStart + pThis->cBlocksPerGroup * pThis->cbBlock;
rc = RTVfsFileReadAt(pThis->hVfsFile, BlkDesc.offBlockBitmap * pThis->cbBlock,
&pBlkGrpDesc->abBlockBitmap[0], cbBlockBitmap, NULL);
}
pThis->pBlkGrpDesc = pBlkGrpDesc;
return rc;
}
static bool rtFsExtIsBlockRangeInUse(PRTFILESYSTEMEXTBLKGRP pBlkGrpDesc,
uint32_t offBlockStart, size_t cBlocks)
{
bool fUsed = false;
while (cBlocks)
{
uint32_t idxByte = offBlockStart / 8;
uint32_t iBit = offBlockStart % 8;
if (pBlkGrpDesc->abBlockBitmap[idxByte] & RT_BIT(iBit))
{
fUsed = true;
break;
}
cBlocks--;
offBlockStart++;
}
return fUsed;
}
static DECLCALLBACK(int) rtFsExtProbe(RTVFSFILE hVfsFile, uint32_t *puScore)
{
int rc = VINF_SUCCESS;
uint64_t cbMedium = 0;
*puScore = RTFILESYSTEM_MATCH_SCORE_UNSUPPORTED;
rc = RTVfsFileGetSize(hVfsFile, &cbMedium);
if (RT_SUCCESS(rc))
{
if (cbMedium >= 2*sizeof(ExtSuperBlock))
{
ExtSuperBlock SuperBlock;
rc = RTVfsFileReadAt(hVfsFile, 1024, &SuperBlock, sizeof(ExtSuperBlock), NULL);
if (RT_SUCCESS(rc))
{
#if defined(RT_BIGENDIAN)
/** @todo: Convert to host endianess. */
#endif
if (SuperBlock.u16Signature == RTFILESYSTEM_EXT2_SIGNATURE)
*puScore = RTFILESYSTEM_MATCH_SCORE_SUPPORTED;
}
}
}
return rc;
}
static DECLCALLBACK(int) rtFsExtInit(void *pvThis, RTVFSFILE hVfsFile)
{
int rc = VINF_SUCCESS;
PRTFILESYSTEMEXT pThis = (PRTFILESYSTEMEXT)pvThis;
ExtSuperBlock SuperBlock;
pThis->hVfsFile = hVfsFile;
pThis->pBlkGrpDesc = NULL;
rc = RTVfsFileReadAt(hVfsFile, 1024, &SuperBlock, sizeof(ExtSuperBlock), NULL);
if (RT_SUCCESS(rc))
{
#if defined(RT_BIGENDIAN)
/** @todo: Convert to host endianess. */
#endif
if (SuperBlock.u16FilesystemState == RTFILESYSTEM_EXT2_STATE_ERRORS)
rc = VERR_FILESYSTEM_CORRUPT;
else
{
pThis->iSbBlock = SuperBlock.iBlockOfSuperblock;
pThis->cbBlock = 1024 << SuperBlock.cBitsShiftLeftBlockSize;
pThis->cBlocksPerGroup = SuperBlock.cBlocksPerGroup;
pThis->cBlockGroups = SuperBlock.cBlocksTotal / pThis->cBlocksPerGroup;
/* Load first block group descriptor. */
rc = rtFsExtLoadBlkGrpDesc(pThis, 0);
}
}
return rc;
}
static DECLCALLBACK(void) rtFsExtDestroy(void *pvThis)
{
PRTFILESYSTEMEXT pThis = (PRTFILESYSTEMEXT)pvThis;
if (pThis->pBlkGrpDesc)
RTMemFree(pThis->pBlkGrpDesc);
}
static DECLCALLBACK(int) rtFsExtOpenRoot(void *pvThis, PRTVFSDIR phVfsDir)
{
NOREF(pvThis);
NOREF(phVfsDir);
return VERR_NOT_IMPLEMENTED;
}
static DECLCALLBACK(int) rtFsExtIsRangeInUse(void *pvThis, RTFOFF off, size_t cb,
bool *pfUsed)
{
int rc = VINF_SUCCESS;
uint64_t offStart = (uint64_t)off;
PRTFILESYSTEMEXT pThis = (PRTFILESYSTEMEXT)pvThis;
*pfUsed = false;
while (cb > 0)
{
bool fUsed;
uint32_t offBlockStart = (uint32_t)(offStart / pThis->cbBlock);
uint32_t iBlockGroup = (offBlockStart - pThis->iSbBlock) / pThis->cBlocksPerGroup;
uint32_t offBlockRelStart = offBlockStart - iBlockGroup * pThis->cBlocksPerGroup;
size_t cbThis = 0;
if ( offStart < pThis->pBlkGrpDesc->offStart
|| offStart > pThis->pBlkGrpDesc->offLast)
{
/* Load new block descriptor. */
rc = rtFsExtLoadBlkGrpDesc(pThis, iBlockGroup);
if (RT_FAILURE(rc))
break;
}
cbThis = RT_MIN(cb, pThis->pBlkGrpDesc->offLast - offStart + 1);
fUsed = rtFsExtIsBlockRangeInUse(pThis->pBlkGrpDesc, offBlockRelStart,
cbThis / pThis->cbBlock +
(cbThis % pThis->cbBlock ? 1 : 0));
if (fUsed)
{
*pfUsed = true;
break;
}
cb -= cbThis;
offStart += cbThis;
}
return rc;
}
DECL_HIDDEN_CONST(RTFILESYSTEMDESC) const g_rtFsExt =
{
/** cbFs */
sizeof(RTFILESYSTEMEXT),
/** VfsOps */
{
/** uVersion. */
RTVFSOPS_VERSION,
/** fFeatures */
0,
/** pszName */
"ExtVfsOps",
/** pfnDestroy */
rtFsExtDestroy,
/** pfnOpenRoot */
rtFsExtOpenRoot,
/** pfnIsRangeInUse */
rtFsExtIsRangeInUse,
/** uEndMarker */
RTVFSOPS_VERSION
},
/** pfnProbe */
rtFsExtProbe,
/** pfnInit */
rtFsExtInit
};