vaseboot/VasEBoot-core/fs/xfs.c

1151 lines
32 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* xfs.c - XFS. */
/*
* VasEBoot -- GRand Unified Bootloader
* Copyright (C) 2005,2006,2007,2008,2009 Free Software Foundation, Inc.
*
* VasEBoot is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VasEBoot is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VasEBoot. If not, see <http://www.gnu.org/licenses/>.
*/
#include <VasEBoot/err.h>
#include <VasEBoot/file.h>
#include <VasEBoot/mm.h>
#include <VasEBoot/misc.h>
#include <VasEBoot/disk.h>
#include <VasEBoot/dl.h>
#include <VasEBoot/types.h>
#include <VasEBoot/fshelp.h>
VasEBoot_MOD_LICENSE ("GPLv3+");
#define XFS_INODE_EXTENTS 9
#define XFS_INODE_FORMAT_INO 1
#define XFS_INODE_FORMAT_EXT 2
#define XFS_INODE_FORMAT_BTREE 3
/* Superblock version field flags */
#define XFS_SB_VERSION_NUMBITS 0x000f
#define XFS_SB_VERSION_ATTRBIT 0x0010
#define XFS_SB_VERSION_NLINKBIT 0x0020
#define XFS_SB_VERSION_QUOTABIT 0x0040
#define XFS_SB_VERSION_ALIGNBIT 0x0080
#define XFS_SB_VERSION_DALIGNBIT 0x0100
#define XFS_SB_VERSION_LOGV2BIT 0x0400
#define XFS_SB_VERSION_SECTORBIT 0x0800
#define XFS_SB_VERSION_EXTFLGBIT 0x1000
#define XFS_SB_VERSION_DIRV2BIT 0x2000
#define XFS_SB_VERSION_MOREBITSBIT 0x8000
#define XFS_SB_VERSION_BITS_SUPPORTED \
(XFS_SB_VERSION_NUMBITS | \
XFS_SB_VERSION_ATTRBIT | \
XFS_SB_VERSION_NLINKBIT | \
XFS_SB_VERSION_QUOTABIT | \
XFS_SB_VERSION_ALIGNBIT | \
XFS_SB_VERSION_DALIGNBIT | \
XFS_SB_VERSION_LOGV2BIT | \
XFS_SB_VERSION_SECTORBIT | \
XFS_SB_VERSION_EXTFLGBIT | \
XFS_SB_VERSION_DIRV2BIT | \
XFS_SB_VERSION_MOREBITSBIT)
/* Recognized xfs format versions */
#define XFS_SB_VERSION_4 4 /* Good old XFS filesystem */
#define XFS_SB_VERSION_5 5 /* CRC enabled filesystem */
/* features2 field flags */
#define XFS_SB_VERSION2_LAZYSBCOUNTBIT 0x00000002 /* Superblk counters */
#define XFS_SB_VERSION2_ATTR2BIT 0x00000008 /* Inline attr rework */
#define XFS_SB_VERSION2_PROJID32BIT 0x00000080 /* 32-bit project ids */
#define XFS_SB_VERSION2_FTYPE 0x00000200 /* inode type in dir */
#define XFS_SB_VERSION2_BITS_SUPPORTED \
(XFS_SB_VERSION2_LAZYSBCOUNTBIT | \
XFS_SB_VERSION2_ATTR2BIT | \
XFS_SB_VERSION2_PROJID32BIT | \
XFS_SB_VERSION2_FTYPE)
/* incompat feature flags */
#define XFS_SB_FEAT_INCOMPAT_FTYPE (1 << 0) /* filetype in dirent */
#define XFS_SB_FEAT_INCOMPAT_SPINODES (1 << 1) /* sparse inode chunks */
#define XFS_SB_FEAT_INCOMPAT_META_UUID (1 << 2) /* metadata UUID */
/* We do not currently verify metadata UUID so it is safe to read such filesystem */
#define XFS_SB_FEAT_INCOMPAT_SUPPORTED \
(XFS_SB_FEAT_INCOMPAT_FTYPE | \
XFS_SB_FEAT_INCOMPAT_META_UUID)
struct VasEBoot_xfs_sblock
{
VasEBoot_uint8_t magic[4];
VasEBoot_uint32_t bsize;
VasEBoot_uint8_t unused1[24];
VasEBoot_uint16_t uuid[8];
VasEBoot_uint8_t unused2[8];
VasEBoot_uint64_t rootino;
VasEBoot_uint8_t unused3[20];
VasEBoot_uint32_t agsize;
VasEBoot_uint8_t unused4[12];
VasEBoot_uint16_t version;
VasEBoot_uint8_t unused5[6];
VasEBoot_uint8_t label[12];
VasEBoot_uint8_t log2_bsize;
VasEBoot_uint8_t log2_sect;
VasEBoot_uint8_t log2_inode;
VasEBoot_uint8_t log2_inop;
VasEBoot_uint8_t log2_agblk;
VasEBoot_uint8_t unused6[67];
VasEBoot_uint8_t log2_dirblk;
VasEBoot_uint8_t unused7[7];
VasEBoot_uint32_t features2;
VasEBoot_uint8_t unused8[4];
VasEBoot_uint32_t sb_features_compat;
VasEBoot_uint32_t sb_features_ro_compat;
VasEBoot_uint32_t sb_features_incompat;
VasEBoot_uint32_t sb_features_log_incompat;
} VasEBoot_PACKED;
struct VasEBoot_xfs_dir_header
{
VasEBoot_uint8_t count;
VasEBoot_uint8_t largeino;
union
{
VasEBoot_uint32_t i4;
VasEBoot_uint64_t i8;
} VasEBoot_PACKED parent;
} VasEBoot_PACKED;
/* Structure for directory entry inlined in the inode */
struct VasEBoot_xfs_dir_entry
{
VasEBoot_uint8_t len;
VasEBoot_uint16_t offset;
char name[1];
/* Inode number follows, 32 / 64 bits. */
} VasEBoot_PACKED;
/* Structure for directory entry in a block */
struct VasEBoot_xfs_dir2_entry
{
VasEBoot_uint64_t inode;
VasEBoot_uint8_t len;
} VasEBoot_PACKED;
struct VasEBoot_xfs_extent
{
/* This should be a bitfield but bietfields are unportable, so just have
a raw array and functions extracting useful info from it.
*/
VasEBoot_uint32_t raw[4];
} VasEBoot_PACKED;
struct VasEBoot_xfs_btree_node
{
VasEBoot_uint8_t magic[4];
VasEBoot_uint16_t level;
VasEBoot_uint16_t numrecs;
VasEBoot_uint64_t left;
VasEBoot_uint64_t right;
/* In V5 here follow crc, uuid, etc. */
/* Then follow keys and block pointers */
} VasEBoot_PACKED;
struct VasEBoot_xfs_btree_root
{
VasEBoot_uint16_t level;
VasEBoot_uint16_t numrecs;
VasEBoot_uint64_t keys[1];
} VasEBoot_PACKED;
struct VasEBoot_xfs_time
{
VasEBoot_uint32_t sec;
VasEBoot_uint32_t nanosec;
} VasEBoot_PACKED;
struct VasEBoot_xfs_inode
{
VasEBoot_uint8_t magic[2];
VasEBoot_uint16_t mode;
VasEBoot_uint8_t version;
VasEBoot_uint8_t format;
VasEBoot_uint8_t unused2[26];
struct VasEBoot_xfs_time atime;
struct VasEBoot_xfs_time mtime;
struct VasEBoot_xfs_time ctime;
VasEBoot_uint64_t size;
VasEBoot_uint64_t nblocks;
VasEBoot_uint32_t extsize;
VasEBoot_uint32_t nextents;
VasEBoot_uint16_t unused3;
VasEBoot_uint8_t fork_offset;
VasEBoot_uint8_t unused4[17];
} VasEBoot_PACKED;
#define XFS_V2_INODE_SIZE sizeof(struct VasEBoot_xfs_inode)
#define XFS_V3_INODE_SIZE (XFS_V2_INODE_SIZE + 76)
struct VasEBoot_xfs_dirblock_tail
{
VasEBoot_uint32_t leaf_count;
VasEBoot_uint32_t leaf_stale;
} VasEBoot_PACKED;
struct VasEBoot_fshelp_node
{
struct VasEBoot_xfs_data *data;
VasEBoot_uint64_t ino;
int inode_read;
struct VasEBoot_xfs_inode inode;
};
struct VasEBoot_xfs_data
{
struct VasEBoot_xfs_sblock sblock;
VasEBoot_disk_t disk;
int pos;
int bsize;
VasEBoot_uint32_t agsize;
unsigned int hasftype:1;
unsigned int hascrc:1;
struct VasEBoot_fshelp_node diropen;
};
static VasEBoot_dl_t my_mod;
static int VasEBoot_xfs_sb_hascrc(struct VasEBoot_xfs_data *data)
{
return (data->sblock.version & VasEBoot_cpu_to_be16_compile_time(XFS_SB_VERSION_NUMBITS)) ==
VasEBoot_cpu_to_be16_compile_time(XFS_SB_VERSION_5);
}
static int VasEBoot_xfs_sb_hasftype(struct VasEBoot_xfs_data *data)
{
if ((data->sblock.version & VasEBoot_cpu_to_be16_compile_time(XFS_SB_VERSION_NUMBITS)) ==
VasEBoot_cpu_to_be16_compile_time(XFS_SB_VERSION_5) &&
data->sblock.sb_features_incompat & VasEBoot_cpu_to_be32_compile_time(XFS_SB_FEAT_INCOMPAT_FTYPE))
return 1;
if (data->sblock.version & VasEBoot_cpu_to_be16_compile_time(XFS_SB_VERSION_MOREBITSBIT) &&
data->sblock.features2 & VasEBoot_cpu_to_be32_compile_time(XFS_SB_VERSION2_FTYPE))
return 1;
return 0;
}
static int VasEBoot_xfs_sb_valid(struct VasEBoot_xfs_data *data)
{
VasEBoot_dprintf("xfs", "Validating superblock\n");
if (VasEBoot_strncmp ((char *) (data->sblock.magic), "XFSB", 4)
|| data->sblock.log2_bsize < VasEBoot_DISK_SECTOR_BITS
|| ((int) data->sblock.log2_bsize
+ (int) data->sblock.log2_dirblk) >= 27)
{
VasEBoot_error (VasEBoot_ERR_BAD_FS, "not a XFS filesystem");
return 0;
}
if ((data->sblock.version & VasEBoot_cpu_to_be16_compile_time(XFS_SB_VERSION_NUMBITS)) ==
VasEBoot_cpu_to_be16_compile_time(XFS_SB_VERSION_5))
{
VasEBoot_dprintf("xfs", "XFS v5 superblock detected\n");
if (data->sblock.sb_features_incompat &
VasEBoot_cpu_to_be32_compile_time(~XFS_SB_FEAT_INCOMPAT_SUPPORTED))
{
VasEBoot_error (VasEBoot_ERR_BAD_FS, "XFS filesystem has unsupported "
"incompatible features");
return 0;
}
return 1;
}
else if ((data->sblock.version & VasEBoot_cpu_to_be16_compile_time(XFS_SB_VERSION_NUMBITS)) ==
VasEBoot_cpu_to_be16_compile_time(XFS_SB_VERSION_4))
{
VasEBoot_dprintf("xfs", "XFS v4 superblock detected\n");
if (!(data->sblock.version & VasEBoot_cpu_to_be16_compile_time(XFS_SB_VERSION_DIRV2BIT)))
{
VasEBoot_error (VasEBoot_ERR_BAD_FS, "XFS filesystem without V2 directories "
"is unsupported");
return 0;
}
if (data->sblock.version & VasEBoot_cpu_to_be16_compile_time(~XFS_SB_VERSION_BITS_SUPPORTED) ||
(data->sblock.version & VasEBoot_cpu_to_be16_compile_time(XFS_SB_VERSION_MOREBITSBIT) &&
data->sblock.features2 & VasEBoot_cpu_to_be16_compile_time(~XFS_SB_VERSION2_BITS_SUPPORTED)))
{
VasEBoot_error (VasEBoot_ERR_BAD_FS, "XFS filesystem has unsupported version "
"bits");
return 0;
}
return 1;
}
return 0;
}
/* Filetype information as used in inodes. */
#define FILETYPE_INO_MASK 0170000
#define FILETYPE_INO_REG 0100000
#define FILETYPE_INO_DIRECTORY 0040000
#define FILETYPE_INO_SYMLINK 0120000
static inline int
VasEBoot_XFS_INO_AGBITS(struct VasEBoot_xfs_data *data)
{
return ((data)->sblock.log2_agblk + (data)->sblock.log2_inop);
}
static inline VasEBoot_uint64_t
VasEBoot_XFS_INO_INOINAG (struct VasEBoot_xfs_data *data,
VasEBoot_uint64_t ino)
{
return (ino & ((1LL << VasEBoot_XFS_INO_AGBITS (data)) - 1));
}
static inline VasEBoot_uint64_t
VasEBoot_XFS_INO_AG (struct VasEBoot_xfs_data *data,
VasEBoot_uint64_t ino)
{
return (ino >> VasEBoot_XFS_INO_AGBITS (data));
}
static inline VasEBoot_disk_addr_t
VasEBoot_XFS_FSB_TO_BLOCK (struct VasEBoot_xfs_data *data, VasEBoot_disk_addr_t fsb)
{
return ((fsb >> data->sblock.log2_agblk) * data->agsize
+ (fsb & ((1LL << data->sblock.log2_agblk) - 1)));
}
static inline VasEBoot_uint64_t
VasEBoot_XFS_EXTENT_OFFSET (struct VasEBoot_xfs_extent *exts, int ex)
{
return ((VasEBoot_be_to_cpu32 (exts[ex].raw[0]) & ~(1 << 31)) << 23
| VasEBoot_be_to_cpu32 (exts[ex].raw[1]) >> 9);
}
static inline VasEBoot_uint64_t
VasEBoot_XFS_EXTENT_BLOCK (struct VasEBoot_xfs_extent *exts, int ex)
{
return ((VasEBoot_uint64_t) (VasEBoot_be_to_cpu32 (exts[ex].raw[1])
& (0x1ff)) << 43
| (VasEBoot_uint64_t) VasEBoot_be_to_cpu32 (exts[ex].raw[2]) << 11
| VasEBoot_be_to_cpu32 (exts[ex].raw[3]) >> 21);
}
static inline VasEBoot_uint64_t
VasEBoot_XFS_EXTENT_SIZE (struct VasEBoot_xfs_extent *exts, int ex)
{
return (VasEBoot_be_to_cpu32 (exts[ex].raw[3]) & ((1 << 21) - 1));
}
static inline VasEBoot_uint64_t
VasEBoot_xfs_inode_block (struct VasEBoot_xfs_data *data,
VasEBoot_uint64_t ino)
{
long long int inoinag = VasEBoot_XFS_INO_INOINAG (data, ino);
long long ag = VasEBoot_XFS_INO_AG (data, ino);
long long block;
block = (inoinag >> data->sblock.log2_inop) + ag * data->agsize;
block <<= (data->sblock.log2_bsize - VasEBoot_DISK_SECTOR_BITS);
return block;
}
static inline int
VasEBoot_xfs_inode_offset (struct VasEBoot_xfs_data *data,
VasEBoot_uint64_t ino)
{
int inoag = VasEBoot_XFS_INO_INOINAG (data, ino);
return ((inoag & ((1 << data->sblock.log2_inop) - 1)) <<
data->sblock.log2_inode);
}
static inline VasEBoot_size_t
VasEBoot_xfs_inode_size(struct VasEBoot_xfs_data *data)
{
return (VasEBoot_size_t)1 << data->sblock.log2_inode;
}
/*
* Returns size occupied by XFS inode stored in memory - we store struct
* VasEBoot_fshelp_node there but on disk inode size may be actually larger than
* struct VasEBoot_xfs_inode so we need to account for that so that we can read
* from disk directly into in-memory structure.
*/
static inline VasEBoot_size_t
VasEBoot_xfs_fshelp_size(struct VasEBoot_xfs_data *data)
{
return sizeof (struct VasEBoot_fshelp_node) - sizeof (struct VasEBoot_xfs_inode)
+ VasEBoot_xfs_inode_size(data);
}
/* This should return void * but XFS code is error-prone with alignment, so
return char to retain cast-align.
*/
static char *
VasEBoot_xfs_inode_data(struct VasEBoot_xfs_inode *inode)
{
if (inode->version <= 2)
return ((char *)inode) + XFS_V2_INODE_SIZE;
return ((char *)inode) + XFS_V3_INODE_SIZE;
}
static struct VasEBoot_xfs_dir_entry *
VasEBoot_xfs_inline_de(struct VasEBoot_xfs_dir_header *head)
{
/*
With small inode numbers the header is 4 bytes smaller because of
smaller parent pointer
*/
return (struct VasEBoot_xfs_dir_entry *)
(((char *) head) + sizeof(struct VasEBoot_xfs_dir_header) -
(head->largeino ? 0 : sizeof(VasEBoot_uint32_t)));
}
static VasEBoot_uint8_t *
VasEBoot_xfs_inline_de_inopos(struct VasEBoot_xfs_data *data,
struct VasEBoot_xfs_dir_entry *de)
{
return ((VasEBoot_uint8_t *)(de + 1)) + de->len - 1 + (data->hasftype ? 1 : 0);
}
static struct VasEBoot_xfs_dir_entry *
VasEBoot_xfs_inline_next_de(struct VasEBoot_xfs_data *data,
struct VasEBoot_xfs_dir_header *head,
struct VasEBoot_xfs_dir_entry *de)
{
char *p = (char *)de + sizeof(struct VasEBoot_xfs_dir_entry) - 1 + de->len;
p += head->largeino ? sizeof(VasEBoot_uint64_t) : sizeof(VasEBoot_uint32_t);
if (data->hasftype)
p++;
return (struct VasEBoot_xfs_dir_entry *)p;
}
static struct VasEBoot_xfs_dirblock_tail *
VasEBoot_xfs_dir_tail(struct VasEBoot_xfs_data *data, void *dirblock)
{
int dirblksize = 1 << (data->sblock.log2_bsize + data->sblock.log2_dirblk);
return (struct VasEBoot_xfs_dirblock_tail *)
((char *)dirblock + dirblksize - sizeof (struct VasEBoot_xfs_dirblock_tail));
}
static struct VasEBoot_xfs_dir2_entry *
VasEBoot_xfs_first_de(struct VasEBoot_xfs_data *data, void *dirblock)
{
if (data->hascrc)
return (struct VasEBoot_xfs_dir2_entry *)((char *)dirblock + 64);
return (struct VasEBoot_xfs_dir2_entry *)((char *)dirblock + 16);
}
static struct VasEBoot_xfs_dir2_entry *
VasEBoot_xfs_next_de(struct VasEBoot_xfs_data *data, struct VasEBoot_xfs_dir2_entry *de)
{
int size = sizeof (struct VasEBoot_xfs_dir2_entry) + de->len + 2 /* Tag */;
if (data->hasftype)
size++; /* File type */
return (struct VasEBoot_xfs_dir2_entry *)(((char *)de) + ALIGN_UP(size, 8));
}
/* This should return void * but XFS code is error-prone with alignment, so
return char to retain cast-align.
*/
static char *
VasEBoot_xfs_btree_keys(struct VasEBoot_xfs_data *data,
struct VasEBoot_xfs_btree_node *leaf)
{
char *keys = (char *)(leaf + 1);
if (data->hascrc)
keys += 48; /* skip crc, uuid, ... */
return keys;
}
static VasEBoot_err_t
VasEBoot_xfs_read_inode (struct VasEBoot_xfs_data *data, VasEBoot_uint64_t ino,
struct VasEBoot_xfs_inode *inode)
{
VasEBoot_uint64_t block = VasEBoot_xfs_inode_block (data, ino);
int offset = VasEBoot_xfs_inode_offset (data, ino);
VasEBoot_dprintf("xfs", "Reading inode (%"PRIuVasEBoot_UINT64_T") - %"PRIuVasEBoot_UINT64_T", %d\n",
ino, block, offset);
/* Read the inode. */
if (VasEBoot_disk_read (data->disk, block, offset, VasEBoot_xfs_inode_size(data),
inode))
return VasEBoot_errno;
if (VasEBoot_strncmp ((char *) inode->magic, "IN", 2))
return VasEBoot_error (VasEBoot_ERR_BAD_FS, "not a correct XFS inode");
return 0;
}
static VasEBoot_uint64_t
get_fsb (const void *keys, int idx)
{
const char *p = (const char *) keys + sizeof(VasEBoot_uint64_t) * idx;
return VasEBoot_be_to_cpu64 (VasEBoot_get_unaligned64 (p));
}
static VasEBoot_disk_addr_t
VasEBoot_xfs_read_block (VasEBoot_fshelp_node_t node, VasEBoot_disk_addr_t fileblock)
{
struct VasEBoot_xfs_btree_node *leaf = 0;
int ex, nrec;
struct VasEBoot_xfs_extent *exts;
VasEBoot_uint64_t ret = 0;
if (node->inode.format == XFS_INODE_FORMAT_BTREE)
{
struct VasEBoot_xfs_btree_root *root;
const char *keys;
int recoffset;
leaf = VasEBoot_malloc (node->data->bsize);
if (leaf == 0)
return 0;
root = (struct VasEBoot_xfs_btree_root *) VasEBoot_xfs_inode_data(&node->inode);
nrec = VasEBoot_be_to_cpu16 (root->numrecs);
keys = (char *) &root->keys[0];
if (node->inode.fork_offset)
recoffset = (node->inode.fork_offset - 1) / 2;
else
recoffset = (VasEBoot_xfs_inode_size(node->data)
- ((char *) keys - (char *) &node->inode))
/ (2 * sizeof (VasEBoot_uint64_t));
do
{
int i;
for (i = 0; i < nrec; i++)
{
if (fileblock < get_fsb(keys, i))
break;
}
/* Sparse block. */
if (i == 0)
{
VasEBoot_free (leaf);
return 0;
}
if (VasEBoot_disk_read (node->data->disk,
VasEBoot_XFS_FSB_TO_BLOCK (node->data, get_fsb (keys, i - 1 + recoffset)) << (node->data->sblock.log2_bsize - VasEBoot_DISK_SECTOR_BITS),
0, node->data->bsize, leaf))
return 0;
if ((!node->data->hascrc &&
VasEBoot_strncmp ((char *) leaf->magic, "BMAP", 4)) ||
(node->data->hascrc &&
VasEBoot_strncmp ((char *) leaf->magic, "BMA3", 4)))
{
VasEBoot_free (leaf);
VasEBoot_error (VasEBoot_ERR_BAD_FS, "not a correct XFS BMAP node");
return 0;
}
nrec = VasEBoot_be_to_cpu16 (leaf->numrecs);
keys = VasEBoot_xfs_btree_keys(node->data, leaf);
recoffset = ((node->data->bsize - ((char *) keys
- (char *) leaf))
/ (2 * sizeof (VasEBoot_uint64_t)));
}
while (leaf->level);
exts = (struct VasEBoot_xfs_extent *) keys;
}
else if (node->inode.format == XFS_INODE_FORMAT_EXT)
{
nrec = VasEBoot_be_to_cpu32 (node->inode.nextents);
exts = (struct VasEBoot_xfs_extent *) VasEBoot_xfs_inode_data(&node->inode);
}
else
{
VasEBoot_error (VasEBoot_ERR_NOT_IMPLEMENTED_YET,
"XFS does not support inode format %d yet",
node->inode.format);
return 0;
}
/* Iterate over each extent to figure out which extent has
the block we are looking for. */
for (ex = 0; ex < nrec; ex++)
{
VasEBoot_uint64_t start = VasEBoot_XFS_EXTENT_BLOCK (exts, ex);
VasEBoot_uint64_t offset = VasEBoot_XFS_EXTENT_OFFSET (exts, ex);
VasEBoot_uint64_t size = VasEBoot_XFS_EXTENT_SIZE (exts, ex);
/* Sparse block. */
if (fileblock < offset)
break;
else if (fileblock < offset + size)
{
ret = (fileblock - offset + start);
break;
}
}
VasEBoot_free (leaf);
return VasEBoot_XFS_FSB_TO_BLOCK(node->data, ret);
}
/* Read LEN bytes from the file described by DATA starting with byte
POS. Return the amount of read bytes in READ. */
static VasEBoot_ssize_t
VasEBoot_xfs_read_file (VasEBoot_fshelp_node_t node,
VasEBoot_disk_read_hook_t read_hook, void *read_hook_data,
VasEBoot_off_t pos, VasEBoot_size_t len, char *buf, VasEBoot_uint32_t header_size)
{
return VasEBoot_fshelp_read_file (node->data->disk, node,
read_hook, read_hook_data,
pos, len, buf, VasEBoot_xfs_read_block,
VasEBoot_be_to_cpu64 (node->inode.size) + header_size,
node->data->sblock.log2_bsize
- VasEBoot_DISK_SECTOR_BITS, 0);
}
static char *
VasEBoot_xfs_read_symlink (VasEBoot_fshelp_node_t node)
{
VasEBoot_ssize_t size = VasEBoot_be_to_cpu64 (node->inode.size);
if (size < 0)
{
VasEBoot_error (VasEBoot_ERR_BAD_FS, "invalid symlink");
return 0;
}
switch (node->inode.format)
{
case XFS_INODE_FORMAT_INO:
return VasEBoot_strndup (VasEBoot_xfs_inode_data(&node->inode), size);
case XFS_INODE_FORMAT_EXT:
{
char *symlink;
VasEBoot_ssize_t numread;
int off = 0;
if (node->data->hascrc)
off = 56;
symlink = VasEBoot_malloc (size + 1);
if (!symlink)
return 0;
node->inode.size = VasEBoot_be_to_cpu64 (size + off);
numread = VasEBoot_xfs_read_file (node, 0, 0, off, size, symlink, off);
if (numread != size)
{
VasEBoot_free (symlink);
return 0;
}
symlink[size] = '\0';
return symlink;
}
}
return 0;
}
static enum VasEBoot_fshelp_filetype
VasEBoot_xfs_mode_to_filetype (VasEBoot_uint16_t mode)
{
if ((VasEBoot_be_to_cpu16 (mode)
& FILETYPE_INO_MASK) == FILETYPE_INO_DIRECTORY)
return VasEBoot_FSHELP_DIR;
else if ((VasEBoot_be_to_cpu16 (mode)
& FILETYPE_INO_MASK) == FILETYPE_INO_SYMLINK)
return VasEBoot_FSHELP_SYMLINK;
else if ((VasEBoot_be_to_cpu16 (mode)
& FILETYPE_INO_MASK) == FILETYPE_INO_REG)
return VasEBoot_FSHELP_REG;
return VasEBoot_FSHELP_UNKNOWN;
}
/* Context for VasEBoot_xfs_iterate_dir. */
struct VasEBoot_xfs_iterate_dir_ctx
{
VasEBoot_fshelp_iterate_dir_hook_t hook;
void *hook_data;
struct VasEBoot_fshelp_node *diro;
};
/* Helper for VasEBoot_xfs_iterate_dir. */
static int iterate_dir_call_hook (VasEBoot_uint64_t ino, const char *filename,
struct VasEBoot_xfs_iterate_dir_ctx *ctx)
{
struct VasEBoot_fshelp_node *fdiro;
VasEBoot_err_t err;
fdiro = VasEBoot_malloc (VasEBoot_xfs_fshelp_size(ctx->diro->data) + 1);
if (!fdiro)
{
VasEBoot_print_error ();
return 0;
}
/* The inode should be read, otherwise the filetype can
not be determined. */
fdiro->ino = ino;
fdiro->inode_read = 1;
fdiro->data = ctx->diro->data;
err = VasEBoot_xfs_read_inode (ctx->diro->data, ino, &fdiro->inode);
if (err)
{
VasEBoot_print_error ();
return 0;
}
return ctx->hook (filename, VasEBoot_xfs_mode_to_filetype (fdiro->inode.mode),
fdiro, ctx->hook_data);
}
static int
VasEBoot_xfs_iterate_dir (VasEBoot_fshelp_node_t dir,
VasEBoot_fshelp_iterate_dir_hook_t hook, void *hook_data)
{
struct VasEBoot_fshelp_node *diro = (struct VasEBoot_fshelp_node *) dir;
struct VasEBoot_xfs_iterate_dir_ctx ctx = {
.hook = hook,
.hook_data = hook_data,
.diro = diro
};
switch (diro->inode.format)
{
case XFS_INODE_FORMAT_INO:
{
struct VasEBoot_xfs_dir_header *head = (struct VasEBoot_xfs_dir_header *) VasEBoot_xfs_inode_data(&diro->inode);
struct VasEBoot_xfs_dir_entry *de = VasEBoot_xfs_inline_de(head);
int smallino = !head->largeino;
int i;
VasEBoot_uint64_t parent;
/* If small inode numbers are used to pack the direntry, the
parent inode number is small too. */
if (smallino)
parent = VasEBoot_be_to_cpu32 (head->parent.i4);
else
parent = VasEBoot_be_to_cpu64 (head->parent.i8);
/* Synthesize the direntries for `.' and `..'. */
if (iterate_dir_call_hook (diro->ino, ".", &ctx))
return 1;
if (iterate_dir_call_hook (parent, "..", &ctx))
return 1;
for (i = 0; i < head->count; i++)
{
VasEBoot_uint64_t ino;
VasEBoot_uint8_t *inopos = VasEBoot_xfs_inline_de_inopos(dir->data, de);
VasEBoot_uint8_t c;
/* inopos might be unaligned. */
if (smallino)
ino = (((VasEBoot_uint32_t) inopos[0]) << 24)
| (((VasEBoot_uint32_t) inopos[1]) << 16)
| (((VasEBoot_uint32_t) inopos[2]) << 8)
| (((VasEBoot_uint32_t) inopos[3]) << 0);
else
ino = (((VasEBoot_uint64_t) inopos[0]) << 56)
| (((VasEBoot_uint64_t) inopos[1]) << 48)
| (((VasEBoot_uint64_t) inopos[2]) << 40)
| (((VasEBoot_uint64_t) inopos[3]) << 32)
| (((VasEBoot_uint64_t) inopos[4]) << 24)
| (((VasEBoot_uint64_t) inopos[5]) << 16)
| (((VasEBoot_uint64_t) inopos[6]) << 8)
| (((VasEBoot_uint64_t) inopos[7]) << 0);
c = de->name[de->len];
de->name[de->len] = '\0';
if (iterate_dir_call_hook (ino, de->name, &ctx))
{
de->name[de->len] = c;
return 1;
}
de->name[de->len] = c;
de = VasEBoot_xfs_inline_next_de(dir->data, head, de);
}
break;
}
case XFS_INODE_FORMAT_BTREE:
case XFS_INODE_FORMAT_EXT:
{
VasEBoot_ssize_t numread;
char *dirblock;
VasEBoot_uint64_t blk;
int dirblk_size, dirblk_log2;
dirblk_log2 = (dir->data->sblock.log2_bsize
+ dir->data->sblock.log2_dirblk);
dirblk_size = 1 << dirblk_log2;
dirblock = VasEBoot_malloc (dirblk_size);
if (! dirblock)
return 0;
/* Iterate over every block the directory has. */
for (blk = 0;
blk < (VasEBoot_be_to_cpu64 (dir->inode.size)
>> dirblk_log2);
blk++)
{
struct VasEBoot_xfs_dir2_entry *direntry =
VasEBoot_xfs_first_de(dir->data, dirblock);
int entries;
struct VasEBoot_xfs_dirblock_tail *tail =
VasEBoot_xfs_dir_tail(dir->data, dirblock);
numread = VasEBoot_xfs_read_file (dir, 0, 0,
blk << dirblk_log2,
dirblk_size, dirblock, 0);
if (numread != dirblk_size)
return 0;
entries = (VasEBoot_be_to_cpu32 (tail->leaf_count)
- VasEBoot_be_to_cpu32 (tail->leaf_stale));
/* Iterate over all entries within this block. */
while ((char *)direntry < (char *)tail)
{
VasEBoot_uint8_t *freetag;
char *filename;
freetag = (VasEBoot_uint8_t *) direntry;
if (VasEBoot_get_unaligned16 (freetag) == 0XFFFF)
{
VasEBoot_uint8_t *skip = (freetag + sizeof (VasEBoot_uint16_t));
/* This entry is not used, go to the next one. */
direntry = (struct VasEBoot_xfs_dir2_entry *)
(((char *)direntry) +
VasEBoot_be_to_cpu16 (VasEBoot_get_unaligned16 (skip)));
continue;
}
filename = (char *)(direntry + 1);
/* The byte after the filename is for the filetype, padding, or
tag, which is not used by VasEBoot. So it can be overwritten. */
filename[direntry->len] = '\0';
if (iterate_dir_call_hook (VasEBoot_be_to_cpu64(direntry->inode),
filename, &ctx))
{
VasEBoot_free (dirblock);
return 1;
}
/* Check if last direntry in this block is
reached. */
entries--;
if (!entries)
break;
/* Select the next directory entry. */
direntry = VasEBoot_xfs_next_de(dir->data, direntry);
}
}
VasEBoot_free (dirblock);
break;
}
default:
VasEBoot_error (VasEBoot_ERR_NOT_IMPLEMENTED_YET,
"XFS does not support inode format %d yet",
diro->inode.format);
}
return 0;
}
static struct VasEBoot_xfs_data *
VasEBoot_xfs_mount (VasEBoot_disk_t disk)
{
struct VasEBoot_xfs_data *data = 0;
data = VasEBoot_zalloc (sizeof (struct VasEBoot_xfs_data));
if (!data)
return 0;
VasEBoot_dprintf("xfs", "Reading sb\n");
/* Read the superblock. */
if (VasEBoot_disk_read (disk, 0, 0,
sizeof (struct VasEBoot_xfs_sblock), &data->sblock))
goto fail;
if (!VasEBoot_xfs_sb_valid(data))
goto fail;
data = VasEBoot_realloc (data,
sizeof (struct VasEBoot_xfs_data)
- sizeof (struct VasEBoot_xfs_inode)
+ VasEBoot_xfs_inode_size(data) + 1);
if (! data)
goto fail;
data->diropen.data = data;
data->diropen.ino = VasEBoot_be_to_cpu64(data->sblock.rootino);
data->diropen.inode_read = 1;
data->bsize = VasEBoot_be_to_cpu32 (data->sblock.bsize);
data->agsize = VasEBoot_be_to_cpu32 (data->sblock.agsize);
data->hasftype = VasEBoot_xfs_sb_hasftype(data);
data->hascrc = VasEBoot_xfs_sb_hascrc(data);
data->disk = disk;
data->pos = 0;
VasEBoot_dprintf("xfs", "Reading root ino %"PRIuVasEBoot_UINT64_T"\n",
VasEBoot_cpu_to_be64(data->sblock.rootino));
VasEBoot_xfs_read_inode (data, data->diropen.ino, &data->diropen.inode);
return data;
fail:
if (VasEBoot_errno == VasEBoot_ERR_OUT_OF_RANGE)
VasEBoot_error (VasEBoot_ERR_BAD_FS, "not an XFS filesystem");
VasEBoot_free (data);
return 0;
}
/* Context for VasEBoot_xfs_dir. */
struct VasEBoot_xfs_dir_ctx
{
VasEBoot_fs_dir_hook_t hook;
void *hook_data;
};
/* Helper for VasEBoot_xfs_dir. */
static int
VasEBoot_xfs_dir_iter (const char *filename, enum VasEBoot_fshelp_filetype filetype,
VasEBoot_fshelp_node_t node, void *data)
{
struct VasEBoot_xfs_dir_ctx *ctx = data;
struct VasEBoot_dirhook_info info;
VasEBoot_memset (&info, 0, sizeof (info));
if (node->inode_read)
{
info.mtimeset = 1;
info.mtime = VasEBoot_be_to_cpu32 (node->inode.mtime.sec);
}
info.dir = ((filetype & VasEBoot_FSHELP_TYPE_MASK) == VasEBoot_FSHELP_DIR);
VasEBoot_free (node);
return ctx->hook (filename, &info, ctx->hook_data);
}
static VasEBoot_err_t
VasEBoot_xfs_dir (VasEBoot_device_t device, const char *path,
VasEBoot_fs_dir_hook_t hook, void *hook_data)
{
struct VasEBoot_xfs_dir_ctx ctx = { hook, hook_data };
struct VasEBoot_xfs_data *data = 0;
struct VasEBoot_fshelp_node *fdiro = 0;
VasEBoot_dl_ref (my_mod);
data = VasEBoot_xfs_mount (device->disk);
if (!data)
goto mount_fail;
VasEBoot_fshelp_find_file (path, &data->diropen, &fdiro, VasEBoot_xfs_iterate_dir,
VasEBoot_xfs_read_symlink, VasEBoot_FSHELP_DIR);
if (VasEBoot_errno)
goto fail;
VasEBoot_xfs_iterate_dir (fdiro, VasEBoot_xfs_dir_iter, &ctx);
fail:
if (fdiro != &data->diropen)
VasEBoot_free (fdiro);
VasEBoot_free (data);
mount_fail:
VasEBoot_dl_unref (my_mod);
return VasEBoot_errno;
}
/* Open a file named NAME and initialize FILE. */
static VasEBoot_err_t
VasEBoot_xfs_open (struct VasEBoot_file *file, const char *name)
{
struct VasEBoot_xfs_data *data;
struct VasEBoot_fshelp_node *fdiro = 0;
VasEBoot_dl_ref (my_mod);
data = VasEBoot_xfs_mount (file->device->disk);
if (!data)
goto mount_fail;
VasEBoot_fshelp_find_file (name, &data->diropen, &fdiro, VasEBoot_xfs_iterate_dir,
VasEBoot_xfs_read_symlink, VasEBoot_FSHELP_REG);
if (VasEBoot_errno)
goto fail;
if (!fdiro->inode_read)
{
VasEBoot_xfs_read_inode (data, fdiro->ino, &fdiro->inode);
if (VasEBoot_errno)
goto fail;
}
if (fdiro != &data->diropen)
{
VasEBoot_memcpy (&data->diropen, fdiro, VasEBoot_xfs_fshelp_size(data));
VasEBoot_free (fdiro);
}
file->size = VasEBoot_be_to_cpu64 (data->diropen.inode.size);
file->data = data;
file->offset = 0;
return 0;
fail:
if (fdiro != &data->diropen)
VasEBoot_free (fdiro);
VasEBoot_free (data);
mount_fail:
VasEBoot_dl_unref (my_mod);
return VasEBoot_errno;
}
static VasEBoot_ssize_t
VasEBoot_xfs_read (VasEBoot_file_t file, char *buf, VasEBoot_size_t len)
{
struct VasEBoot_xfs_data *data =
(struct VasEBoot_xfs_data *) file->data;
return VasEBoot_xfs_read_file (&data->diropen,
file->read_hook, file->read_hook_data,
file->offset, len, buf, 0);
}
static VasEBoot_err_t
VasEBoot_xfs_close (VasEBoot_file_t file)
{
VasEBoot_free (file->data);
VasEBoot_dl_unref (my_mod);
return VasEBoot_ERR_NONE;
}
static VasEBoot_err_t
VasEBoot_xfs_label (VasEBoot_device_t device, char **label)
{
struct VasEBoot_xfs_data *data;
VasEBoot_disk_t disk = device->disk;
VasEBoot_dl_ref (my_mod);
data = VasEBoot_xfs_mount (disk);
if (data)
*label = VasEBoot_strndup ((char *) (data->sblock.label), 12);
else
*label = 0;
VasEBoot_dl_unref (my_mod);
VasEBoot_free (data);
return VasEBoot_errno;
}
static VasEBoot_err_t
VasEBoot_xfs_uuid (VasEBoot_device_t device, char **uuid)
{
struct VasEBoot_xfs_data *data;
VasEBoot_disk_t disk = device->disk;
VasEBoot_dl_ref (my_mod);
data = VasEBoot_xfs_mount (disk);
if (data)
{
*uuid = VasEBoot_xasprintf ("%04x%04x-%04x-%04x-%04x-%04x%04x%04x",
VasEBoot_be_to_cpu16 (data->sblock.uuid[0]),
VasEBoot_be_to_cpu16 (data->sblock.uuid[1]),
VasEBoot_be_to_cpu16 (data->sblock.uuid[2]),
VasEBoot_be_to_cpu16 (data->sblock.uuid[3]),
VasEBoot_be_to_cpu16 (data->sblock.uuid[4]),
VasEBoot_be_to_cpu16 (data->sblock.uuid[5]),
VasEBoot_be_to_cpu16 (data->sblock.uuid[6]),
VasEBoot_be_to_cpu16 (data->sblock.uuid[7]));
}
else
*uuid = NULL;
VasEBoot_dl_unref (my_mod);
VasEBoot_free (data);
return VasEBoot_errno;
}
static struct VasEBoot_fs VasEBoot_xfs_fs =
{
.name = "xfs",
.dir = VasEBoot_xfs_dir,
.open = VasEBoot_xfs_open,
.read = VasEBoot_xfs_read,
.close = VasEBoot_xfs_close,
.label = VasEBoot_xfs_label,
.uuid = VasEBoot_xfs_uuid,
#ifdef VasEBoot_UTIL
.reserved_first_sector = 0,
.blocklist_install = 1,
#endif
.next = 0
};
VasEBoot_MOD_INIT(xfs)
{
VasEBoot_fs_register (&VasEBoot_xfs_fs);
my_mod = mod;
}
VasEBoot_MOD_FINI(xfs)
{
VasEBoot_fs_unregister (&VasEBoot_xfs_fs);
}