vaseboot/VasEBoot-core/fs/ext2.c

1156 lines
34 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.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ext2.c - Second Extended filesystem */
/*
* VAS_EBOOT -- GRand Unified Bootloader
* Copyright (C) 2003,2004,2005,2007,2008,2009 Free Software Foundation, Inc.
*
* VAS_EBOOT 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.
*
* VAS_EBOOT 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 VAS_EBOOT. If not, see <http://www.gnu.org/licenses/>.
*/
/* Magic value used to identify an ext2 filesystem. */
#define EXT2_MAGIC 0xEF53
/* Amount of indirect blocks in an inode. */
#define INDIRECT_BLOCKS 12
/* The good old revision and the default inode size. */
#define EXT2_GOOD_OLD_REVISION 0
#define EXT2_GOOD_OLD_INODE_SIZE 128
/* Filetype used in directory entry. */
#define FILETYPE_UNKNOWN 0
#define FILETYPE_REG 1
#define FILETYPE_DIRECTORY 2
#define FILETYPE_SYMLINK 7
/* 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
#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>
#include <VasEBoot/safemath.h>
VAS_EBOOT_MOD_LICENSE ("GPLv3+");
/* Log2 size of ext2 block in 512 blocks. */
#define LOG2_EXT2_BLOCK_SIZE(data) \
(VasEBoot_le_to_cpu32 (data->sblock.log2_block_size) + 1)
/* Log2 size of ext2 block in bytes. */
#define LOG2_BLOCK_SIZE(data) \
(VasEBoot_le_to_cpu32 (data->sblock.log2_block_size) + 10)
/* The size of an ext2 block in bytes. */
#define EXT2_BLOCK_SIZE(data) (1U << LOG2_BLOCK_SIZE (data))
/* The revision level. */
#define EXT2_REVISION(data) VasEBoot_le_to_cpu32 (data->sblock.revision_level)
/* The inode size. */
#define EXT2_INODE_SIZE(data) \
(data->sblock.revision_level \
== VasEBoot_cpu_to_le32_compile_time (EXT2_GOOD_OLD_REVISION) \
? EXT2_GOOD_OLD_INODE_SIZE \
: VasEBoot_le_to_cpu16 (data->sblock.inode_size))
/* Superblock filesystem feature flags (RW compatible)
* A filesystem with any of these enabled can be read and written by a driver
* that does not understand them without causing metadata/data corruption. */
#define EXT2_FEATURE_COMPAT_DIR_PREALLOC 0x0001
#define EXT2_FEATURE_COMPAT_IMAGIC_INODES 0x0002
#define EXT3_FEATURE_COMPAT_HAS_JOURNAL 0x0004
#define EXT2_FEATURE_COMPAT_EXT_ATTR 0x0008
#define EXT2_FEATURE_COMPAT_RESIZE_INODE 0x0010
#define EXT2_FEATURE_COMPAT_DIR_INDEX 0x0020
/* Superblock filesystem feature flags (RO compatible)
* A filesystem with any of these enabled can be safely read by a driver that
* does not understand them, but should not be written to, usually because
* additional metadata is required. */
#define EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER 0x0001
#define EXT2_FEATURE_RO_COMPAT_LARGE_FILE 0x0002
#define EXT2_FEATURE_RO_COMPAT_BTREE_DIR 0x0004
#define EXT4_FEATURE_RO_COMPAT_GDT_CSUM 0x0010
#define EXT4_FEATURE_RO_COMPAT_DIR_NLINK 0x0020
#define EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE 0x0040
/* Superblock filesystem feature flags (back-incompatible)
* A filesystem with any of these enabled should not be attempted to be read
* by a driver that does not understand them, since they usually indicate
* metadata format changes that might confuse the reader. */
#define EXT2_FEATURE_INCOMPAT_COMPRESSION 0x0001
#define EXT2_FEATURE_INCOMPAT_FILETYPE 0x0002
#define EXT3_FEATURE_INCOMPAT_RECOVER 0x0004 /* Needs recovery */
#define EXT3_FEATURE_INCOMPAT_JOURNAL_DEV 0x0008 /* Volume is journal device */
#define EXT2_FEATURE_INCOMPAT_META_BG 0x0010
#define EXT4_FEATURE_INCOMPAT_EXTENTS 0x0040 /* Extents used */
#define EXT4_FEATURE_INCOMPAT_64BIT 0x0080
#define EXT4_FEATURE_INCOMPAT_MMP 0x0100
#define EXT4_FEATURE_INCOMPAT_FLEX_BG 0x0200
#define EXT4_FEATURE_INCOMPAT_CSUM_SEED 0x2000
#define EXT4_FEATURE_INCOMPAT_LARGEDIR 0x4000 /* >2GB or 3 level htree */
#define EXT4_FEATURE_INCOMPAT_ENCRYPT 0x10000
/* The set of back-incompatible features this driver DOES support. Add (OR)
* flags here as the related features are implemented into the driver. */
#define EXT2_DRIVER_SUPPORTED_INCOMPAT ( EXT2_FEATURE_INCOMPAT_FILETYPE \
| EXT4_FEATURE_INCOMPAT_EXTENTS \
| EXT4_FEATURE_INCOMPAT_FLEX_BG \
| EXT2_FEATURE_INCOMPAT_META_BG \
| EXT4_FEATURE_INCOMPAT_64BIT \
| EXT4_FEATURE_INCOMPAT_ENCRYPT)
/* List of rationales for the ignored "incompatible" features:
* needs_recovery: Not really back-incompatible - was added as such to forbid
* ext2 drivers from mounting an ext3 volume with a dirty
* journal because they will ignore the journal, but the next
* ext3 driver to mount the volume will find the journal and
* replay it, potentially corrupting the metadata written by
* the ext2 drivers. Safe to ignore for this RO driver.
* mmp: Not really back-incompatible - was added as such to
* avoid multiple read-write mounts. Safe to ignore for this
* RO driver.
* checksum seed: Not really back-incompatible - was added to allow tools
* such as tune2fs to change the UUID on a mounted metadata
* checksummed filesystem. Safe to ignore for now since the
* driver doesn't support checksum verification. However, it
* has to be removed from this list if the support is added later.
* large_dir: Not back-incompatible given that the VAS_EBOOT ext2 driver does
* not implement EXT2_FEATURE_COMPAT_DIR_INDEX. If the VAS_EBOOT
* eventually supports the htree feature (aka dir_index)
* it should support 3 level htrees and then move
* EXT4_FEATURE_INCOMPAT_LARGEDIR to
* EXT2_DRIVER_SUPPORTED_INCOMPAT.
*/
#define EXT2_DRIVER_IGNORED_INCOMPAT ( EXT3_FEATURE_INCOMPAT_RECOVER \
| EXT4_FEATURE_INCOMPAT_MMP \
| EXT4_FEATURE_INCOMPAT_CSUM_SEED \
| EXT4_FEATURE_INCOMPAT_LARGEDIR)
#define EXT3_JOURNAL_MAGIC_NUMBER 0xc03b3998U
#define EXT3_JOURNAL_DESCRIPTOR_BLOCK 1
#define EXT3_JOURNAL_COMMIT_BLOCK 2
#define EXT3_JOURNAL_SUPERBLOCK_V1 3
#define EXT3_JOURNAL_SUPERBLOCK_V2 4
#define EXT3_JOURNAL_REVOKE_BLOCK 5
#define EXT3_JOURNAL_FLAG_ESCAPE 1
#define EXT3_JOURNAL_FLAG_SAME_UUID 2
#define EXT3_JOURNAL_FLAG_DELETED 4
#define EXT3_JOURNAL_FLAG_LAST_TAG 8
#define EXT4_ENCRYPT_FLAG 0x800
#define EXT4_EXTENTS_FLAG 0x80000
/* The ext2 superblock. */
struct VasEBoot_ext2_sblock
{
VasEBoot_uint32_t total_inodes;
VasEBoot_uint32_t total_blocks;
VasEBoot_uint32_t reserved_blocks;
VasEBoot_uint32_t free_blocks;
VasEBoot_uint32_t free_inodes;
VasEBoot_uint32_t first_data_block;
VasEBoot_uint32_t log2_block_size;
VasEBoot_uint32_t log2_fragment_size;
VasEBoot_uint32_t blocks_per_group;
VasEBoot_uint32_t fragments_per_group;
VasEBoot_uint32_t inodes_per_group;
VasEBoot_uint32_t mtime;
VasEBoot_uint32_t utime;
VasEBoot_uint16_t mnt_count;
VasEBoot_uint16_t max_mnt_count;
VasEBoot_uint16_t magic;
VasEBoot_uint16_t fs_state;
VasEBoot_uint16_t error_handling;
VasEBoot_uint16_t minor_revision_level;
VasEBoot_uint32_t lastcheck;
VasEBoot_uint32_t checkinterval;
VasEBoot_uint32_t creator_os;
VasEBoot_uint32_t revision_level;
VasEBoot_uint16_t uid_reserved;
VasEBoot_uint16_t gid_reserved;
VasEBoot_uint32_t first_inode;
VasEBoot_uint16_t inode_size;
VasEBoot_uint16_t block_group_number;
VasEBoot_uint32_t feature_compatibility;
VasEBoot_uint32_t feature_incompat;
VasEBoot_uint32_t feature_ro_compat;
VasEBoot_uint16_t uuid[8];
char volume_name[16];
char last_mounted_on[64];
VasEBoot_uint32_t compression_info;
VasEBoot_uint8_t prealloc_blocks;
VasEBoot_uint8_t prealloc_dir_blocks;
VasEBoot_uint16_t reserved_gdt_blocks;
VasEBoot_uint8_t journal_uuid[16];
VasEBoot_uint32_t journal_inum;
VasEBoot_uint32_t journal_dev;
VasEBoot_uint32_t last_orphan;
VasEBoot_uint32_t hash_seed[4];
VasEBoot_uint8_t def_hash_version;
VasEBoot_uint8_t jnl_backup_type;
VasEBoot_uint16_t group_desc_size;
VasEBoot_uint32_t default_mount_opts;
VasEBoot_uint32_t first_meta_bg;
VasEBoot_uint32_t mkfs_time;
VasEBoot_uint32_t jnl_blocks[17];
};
/* The ext2 blockgroup. */
struct VasEBoot_ext2_block_group
{
VasEBoot_uint32_t block_id;
VasEBoot_uint32_t inode_id;
VasEBoot_uint32_t inode_table_id;
VasEBoot_uint16_t free_blocks;
VasEBoot_uint16_t free_inodes;
VasEBoot_uint16_t used_dirs;
VasEBoot_uint16_t pad;
VasEBoot_uint32_t reserved[3];
VasEBoot_uint32_t block_id_hi;
VasEBoot_uint32_t inode_id_hi;
VasEBoot_uint32_t inode_table_id_hi;
VasEBoot_uint16_t free_blocks_hi;
VasEBoot_uint16_t free_inodes_hi;
VasEBoot_uint16_t used_dirs_hi;
VasEBoot_uint16_t pad2;
VasEBoot_uint32_t reserved2[3];
};
/* The ext2 inode. */
struct VasEBoot_ext2_inode
{
VasEBoot_uint16_t mode;
VasEBoot_uint16_t uid;
VasEBoot_uint32_t size;
VasEBoot_uint32_t atime;
VasEBoot_uint32_t ctime;
VasEBoot_uint32_t mtime;
VasEBoot_uint32_t dtime;
VasEBoot_uint16_t gid;
VasEBoot_uint16_t nlinks;
VasEBoot_uint32_t blockcnt; /* Blocks of 512 bytes!! */
VasEBoot_uint32_t flags;
VasEBoot_uint32_t osd1;
union
{
struct datablocks
{
VasEBoot_uint32_t dir_blocks[INDIRECT_BLOCKS];
VasEBoot_uint32_t indir_block;
VasEBoot_uint32_t double_indir_block;
VasEBoot_uint32_t triple_indir_block;
} blocks;
char symlink[60];
};
VasEBoot_uint32_t version;
VasEBoot_uint32_t acl;
VasEBoot_uint32_t size_high;
VasEBoot_uint32_t fragment_addr;
VasEBoot_uint32_t osd2[3];
};
/* The header of an ext2 directory entry. */
struct ext2_dirent
{
VasEBoot_uint32_t inode;
VasEBoot_uint16_t direntlen;
#define MAX_NAMELEN 255
VasEBoot_uint8_t namelen;
VasEBoot_uint8_t filetype;
};
struct VasEBoot_ext3_journal_header
{
VasEBoot_uint32_t magic;
VasEBoot_uint32_t block_type;
VasEBoot_uint32_t sequence;
};
struct VasEBoot_ext3_journal_revoke_header
{
struct VasEBoot_ext3_journal_header header;
VasEBoot_uint32_t count;
VasEBoot_uint32_t data[0];
};
struct VasEBoot_ext3_journal_block_tag
{
VasEBoot_uint32_t block;
VasEBoot_uint32_t flags;
};
struct VasEBoot_ext3_journal_sblock
{
struct VasEBoot_ext3_journal_header header;
VasEBoot_uint32_t block_size;
VasEBoot_uint32_t maxlen;
VasEBoot_uint32_t first;
VasEBoot_uint32_t sequence;
VasEBoot_uint32_t start;
};
#define EXT4_EXT_MAGIC 0xf30a
struct VasEBoot_ext4_extent_header
{
VasEBoot_uint16_t magic;
VasEBoot_uint16_t entries;
VasEBoot_uint16_t max;
VasEBoot_uint16_t depth;
VasEBoot_uint32_t generation;
};
struct VasEBoot_ext4_extent
{
VasEBoot_uint32_t block;
VasEBoot_uint16_t len;
VasEBoot_uint16_t start_hi;
VasEBoot_uint32_t start;
};
struct VasEBoot_ext4_extent_idx
{
VasEBoot_uint32_t block;
VasEBoot_uint32_t leaf;
VasEBoot_uint16_t leaf_hi;
VasEBoot_uint16_t unused;
};
struct VasEBoot_fshelp_node
{
struct VasEBoot_ext2_data *data;
struct VasEBoot_ext2_inode inode;
int ino;
int inode_read;
};
/* Information about a "mounted" ext2 filesystem. */
struct VasEBoot_ext2_data
{
struct VasEBoot_ext2_sblock sblock;
int log_group_desc_size;
VasEBoot_disk_t disk;
struct VasEBoot_ext2_inode *inode;
struct VasEBoot_fshelp_node diropen;
};
static VasEBoot_dl_t my_mod;
/* Check is a = b^x for some x. */
static inline int
is_power_of (VasEBoot_uint64_t a, VasEBoot_uint32_t b)
{
VasEBoot_uint64_t c;
/* Prevent overflow assuming b < 8. */
if (a >= (1LL << 60))
return 0;
for (c = 1; c <= a; c *= b);
return (c == a);
}
static inline int
group_has_super_block (struct VasEBoot_ext2_data *data, VasEBoot_uint64_t group)
{
if (!(data->sblock.feature_ro_compat
& VasEBoot_cpu_to_le32_compile_time(EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER)))
return 1;
/* Algorithm looked up in Linux source. */
if (group <= 1)
return 1;
/* Even number is never a power of odd number. */
if (!(group & 1))
return 0;
return (is_power_of(group, 7) || is_power_of(group, 5) ||
is_power_of(group, 3));
}
/* Read into BLKGRP the blockgroup descriptor of blockgroup GROUP of
the mounted filesystem DATA. */
inline static VasEBoot_err_t
VasEBoot_ext2_blockgroup (struct VasEBoot_ext2_data *data, VasEBoot_uint64_t group,
struct VasEBoot_ext2_block_group *blkgrp)
{
VasEBoot_uint64_t full_offset = (group << data->log_group_desc_size);
VasEBoot_uint64_t block, offset;
block = (full_offset >> LOG2_BLOCK_SIZE (data));
offset = (full_offset & ((1 << LOG2_BLOCK_SIZE (data)) - 1));
if ((data->sblock.feature_incompat
& VasEBoot_cpu_to_le32_compile_time (EXT2_FEATURE_INCOMPAT_META_BG))
&& block >= VasEBoot_le_to_cpu32(data->sblock.first_meta_bg))
{
VasEBoot_uint64_t first_block_group;
/* Find the first block group for which a descriptor
is stored in given block. */
first_block_group = (block << (LOG2_BLOCK_SIZE (data)
- data->log_group_desc_size));
block = (first_block_group
* VasEBoot_le_to_cpu32(data->sblock.blocks_per_group));
if (group_has_super_block (data, first_block_group))
block++;
}
else
/* Superblock. */
block++;
return VasEBoot_disk_read (data->disk,
((VasEBoot_le_to_cpu32 (data->sblock.first_data_block)
+ block)
<< LOG2_EXT2_BLOCK_SIZE (data)), offset,
sizeof (struct VasEBoot_ext2_block_group), blkgrp);
}
static VasEBoot_err_t
VasEBoot_ext4_find_leaf (struct VasEBoot_ext2_data *data,
struct VasEBoot_ext4_extent_header *ext_block,
VasEBoot_uint32_t fileblock,
struct VasEBoot_ext4_extent_header **leaf)
{
struct VasEBoot_ext4_extent_idx *index;
void *buf = NULL;
*leaf = NULL;
while (1)
{
int i;
VasEBoot_disk_addr_t block;
index = (struct VasEBoot_ext4_extent_idx *) (ext_block + 1);
if (ext_block->magic != VasEBoot_cpu_to_le16_compile_time (EXT4_EXT_MAGIC))
goto fail;
if (ext_block->depth == 0)
{
*leaf = ext_block;
return VAS_EBOOT_ERR_NONE;
}
for (i = 0; i < VasEBoot_le_to_cpu16 (ext_block->entries); i++)
{
if (fileblock < VasEBoot_le_to_cpu32(index[i].block))
break;
}
if (--i < 0)
{
VasEBoot_free (buf);
return VAS_EBOOT_ERR_NONE;
}
block = VasEBoot_le_to_cpu16 (index[i].leaf_hi);
block = (block << 32) | VasEBoot_le_to_cpu32 (index[i].leaf);
if (!buf)
buf = VasEBoot_malloc (EXT2_BLOCK_SIZE(data));
if (!buf)
goto fail;
if (VasEBoot_disk_read (data->disk,
block << LOG2_EXT2_BLOCK_SIZE (data),
0, EXT2_BLOCK_SIZE(data), buf))
goto fail;
ext_block = buf;
}
fail:
VasEBoot_free (buf);
return VAS_EBOOT_ERR_BAD_FS;
}
static VasEBoot_disk_addr_t
VasEBoot_ext2_read_block (VasEBoot_fshelp_node_t node, VasEBoot_disk_addr_t fileblock)
{
struct VasEBoot_ext2_data *data = node->data;
struct VasEBoot_ext2_inode *inode = &node->inode;
unsigned int blksz = EXT2_BLOCK_SIZE (data);
VasEBoot_disk_addr_t blksz_quarter = blksz / 4;
int log2_blksz = LOG2_EXT2_BLOCK_SIZE (data);
int log_perblock = log2_blksz + 9 - 2;
VasEBoot_uint32_t indir;
int shift;
if (inode->flags & VasEBoot_cpu_to_le32_compile_time (EXT4_EXTENTS_FLAG))
{
struct VasEBoot_ext4_extent_header *leaf;
struct VasEBoot_ext4_extent *ext;
int i;
VasEBoot_disk_addr_t ret;
VasEBoot_uint16_t nent;
/* Maximum number of extent entries in the inode's inline extent area. */
const VasEBoot_uint16_t max_inline_ext = sizeof (inode->blocks) / sizeof (*ext) - 1; /* Minus 1 extent header. */
/* Maximum number of extent entries in the external extent block. */
const VasEBoot_uint16_t max_external_ext = EXT2_BLOCK_SIZE (data) / sizeof (*ext) - 1; /* Minus 1 extent header. */
if (VasEBoot_ext4_find_leaf (data, (struct VasEBoot_ext4_extent_header *) inode->blocks.dir_blocks,
fileblock, &leaf) != VAS_EBOOT_ERR_NONE)
{
VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "invalid extent");
return -1;
}
if (leaf == NULL)
/* Leaf for the given block is absent (i.e. sparse) */
return 0;
ext = (struct VasEBoot_ext4_extent *) (leaf + 1);
nent = VasEBoot_le_to_cpu16 (leaf->entries);
/*
* Determine the effective number of extent entries (nent) to process.
* If the extent header (leaf) is stored inline in the inodes block
* area, i.e. at inode->blocks.dir_blocks, then only max_inline_ext
* entries can fit. Otherwise, if the header was read from an external
* extent block use the larger limit, max_external_ext, based on the
* full block size.
*/
if (leaf == (struct VasEBoot_ext4_extent_header *) inode->blocks.dir_blocks)
nent = VasEBoot_min (nent, max_inline_ext);
else
nent = VasEBoot_min (nent, max_external_ext);
for (i = 0; i < nent; i++)
{
if (fileblock < VasEBoot_le_to_cpu32 (ext[i].block))
break;
}
if (--i >= 0)
{
fileblock -= VasEBoot_le_to_cpu32 (ext[i].block);
if (fileblock >= VasEBoot_le_to_cpu16 (ext[i].len))
ret = 0;
else
{
VasEBoot_disk_addr_t start;
start = VasEBoot_le_to_cpu16 (ext[i].start_hi);
start = (start << 32) + VasEBoot_le_to_cpu32 (ext[i].start);
ret = fileblock + start;
}
}
else
{
VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "something wrong with extent");
ret = -1;
}
if (leaf != (struct VasEBoot_ext4_extent_header *) inode->blocks.dir_blocks)
VasEBoot_free (leaf);
return ret;
}
/* Direct blocks. */
if (fileblock < INDIRECT_BLOCKS)
return VasEBoot_le_to_cpu32 (inode->blocks.dir_blocks[fileblock]);
fileblock -= INDIRECT_BLOCKS;
/* Indirect. */
if (fileblock < blksz_quarter)
{
indir = inode->blocks.indir_block;
shift = 0;
goto indirect;
}
fileblock -= blksz_quarter;
/* Double indirect. */
if (fileblock < blksz_quarter * blksz_quarter)
{
indir = inode->blocks.double_indir_block;
shift = 1;
goto indirect;
}
fileblock -= blksz_quarter * blksz_quarter;
/* Triple indirect. */
if (fileblock < blksz_quarter * blksz_quarter * (blksz_quarter + 1))
{
indir = inode->blocks.triple_indir_block;
shift = 2;
goto indirect;
}
VasEBoot_error (VAS_EBOOT_ERR_BAD_FS,
"ext2fs doesn't support quadruple indirect blocks");
return -1;
indirect:
do {
/* If the indirect block is zero, all child blocks are absent
(i.e. filled with zeros.) */
if (indir == 0)
return 0;
if (VasEBoot_disk_read (data->disk,
((VasEBoot_disk_addr_t) VasEBoot_le_to_cpu32 (indir))
<< log2_blksz,
((fileblock >> (log_perblock * shift))
& ((1 << log_perblock) - 1))
* sizeof (indir),
sizeof (indir), &indir))
return -1;
} while (shift--);
return VasEBoot_le_to_cpu32 (indir);
}
/* 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_ext2_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)
{
return VasEBoot_fshelp_read_file (node->data->disk, node,
read_hook, read_hook_data,
pos, len, buf, VasEBoot_ext2_read_block,
VasEBoot_cpu_to_le32 (node->inode.size)
| (((VasEBoot_off_t) VasEBoot_cpu_to_le32 (node->inode.size_high)) << 32),
LOG2_EXT2_BLOCK_SIZE (node->data), 0);
}
/* Read the inode INO for the file described by DATA into INODE. */
static VasEBoot_err_t
VasEBoot_ext2_read_inode (struct VasEBoot_ext2_data *data,
int ino, struct VasEBoot_ext2_inode *inode)
{
struct VasEBoot_ext2_block_group blkgrp;
struct VasEBoot_ext2_sblock *sblock = &data->sblock;
int inodes_per_block;
unsigned int blkno;
unsigned int blkoff;
VasEBoot_disk_addr_t base;
/* It is easier to calculate if the first inode is 0. */
ino--;
VasEBoot_ext2_blockgroup (data,
ino / VasEBoot_le_to_cpu32 (sblock->inodes_per_group),
&blkgrp);
if (VasEBoot_errno)
return VasEBoot_errno;
inodes_per_block = EXT2_BLOCK_SIZE (data) / EXT2_INODE_SIZE (data);
blkno = (ino % VasEBoot_le_to_cpu32 (sblock->inodes_per_group))
/ inodes_per_block;
blkoff = (ino % VasEBoot_le_to_cpu32 (sblock->inodes_per_group))
% inodes_per_block;
base = VasEBoot_le_to_cpu32 (blkgrp.inode_table_id);
if (data->log_group_desc_size >= 6)
base |= (((VasEBoot_disk_addr_t) VasEBoot_le_to_cpu32 (blkgrp.inode_table_id_hi))
<< 32);
/* Read the inode. */
if (VasEBoot_disk_read (data->disk,
((base + blkno) << LOG2_EXT2_BLOCK_SIZE (data)),
EXT2_INODE_SIZE (data) * blkoff,
sizeof (struct VasEBoot_ext2_inode), inode))
return VasEBoot_errno;
return 0;
}
static struct VasEBoot_ext2_data *
VasEBoot_ext2_mount (VasEBoot_disk_t disk)
{
struct VasEBoot_ext2_data *data;
data = VasEBoot_malloc (sizeof (struct VasEBoot_ext2_data));
if (!data)
return 0;
/* Read the superblock. */
VasEBoot_disk_read (disk, 1 * 2, 0, sizeof (struct VasEBoot_ext2_sblock),
&data->sblock);
if (VasEBoot_errno)
goto fail;
/* Make sure this is an ext2 filesystem. */
if (data->sblock.magic != VasEBoot_cpu_to_le16_compile_time (EXT2_MAGIC)
|| VasEBoot_le_to_cpu32 (data->sblock.log2_block_size) >= 16
|| data->sblock.inodes_per_group == 0
/* 20 already means 1GiB blocks. We don't want to deal with blocks overflowing int32. */
|| VasEBoot_le_to_cpu32 (data->sblock.log2_block_size) > 20
|| EXT2_INODE_SIZE (data) == 0
|| EXT2_BLOCK_SIZE (data) / EXT2_INODE_SIZE (data) == 0)
{
VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "not an ext2 filesystem");
goto fail;
}
/* Check the FS doesn't have feature bits enabled that we don't support. */
if (data->sblock.revision_level != VasEBoot_cpu_to_le32_compile_time (EXT2_GOOD_OLD_REVISION)
&& (data->sblock.feature_incompat
& VasEBoot_cpu_to_le32_compile_time (~(EXT2_DRIVER_SUPPORTED_INCOMPAT
| EXT2_DRIVER_IGNORED_INCOMPAT))))
{
VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "filesystem has unsupported incompatible features");
goto fail;
}
if (data->sblock.revision_level != VasEBoot_cpu_to_le32_compile_time (EXT2_GOOD_OLD_REVISION)
&& (data->sblock.feature_incompat
& VasEBoot_cpu_to_le32_compile_time (EXT4_FEATURE_INCOMPAT_64BIT))
&& data->sblock.group_desc_size != 0
&& ((data->sblock.group_desc_size & (data->sblock.group_desc_size - 1))
== 0)
&& (data->sblock.group_desc_size & VasEBoot_cpu_to_le16_compile_time (0x1fe0)))
{
VasEBoot_uint16_t b = VasEBoot_le_to_cpu16 (data->sblock.group_desc_size);
for (data->log_group_desc_size = 0; b != (1 << data->log_group_desc_size);
data->log_group_desc_size++);
}
else
data->log_group_desc_size = 5;
data->disk = disk;
data->diropen.data = data;
data->diropen.ino = 2;
data->diropen.inode_read = 1;
data->inode = &data->diropen.inode;
VasEBoot_ext2_read_inode (data, 2, data->inode);
if (VasEBoot_errno)
goto fail;
return data;
fail:
if (VasEBoot_errno == VAS_EBOOT_ERR_OUT_OF_RANGE)
VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "not an ext2 filesystem");
VasEBoot_free (data);
return 0;
}
static char *
VasEBoot_ext2_read_symlink (VasEBoot_fshelp_node_t node)
{
char *symlink;
struct VasEBoot_fshelp_node *diro = node;
VasEBoot_size_t sz;
if (! diro->inode_read)
{
VasEBoot_ext2_read_inode (diro->data, diro->ino, &diro->inode);
if (VasEBoot_errno)
return 0;
if (diro->inode.flags & VasEBoot_cpu_to_le32_compile_time (EXT4_ENCRYPT_FLAG))
{
VasEBoot_error (VAS_EBOOT_ERR_NOT_IMPLEMENTED_YET, "symlink is encrypted");
return 0;
}
}
if (VasEBoot_add (VasEBoot_le_to_cpu32 (diro->inode.size), 1, &sz))
{
VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, N_("overflow is detected"));
return NULL;
}
symlink = VasEBoot_malloc (sz);
if (! symlink)
return 0;
/*
* If the filesize of the symlink is equal to or bigger than 60 the symlink
* is stored in a separate block, otherwise it is stored in the inode.
*/
if (VasEBoot_le_to_cpu32 (diro->inode.size) < sizeof (diro->inode.symlink))
VasEBoot_memcpy (symlink,
diro->inode.symlink,
VasEBoot_le_to_cpu32 (diro->inode.size));
else
{
VasEBoot_ext2_read_file (diro, 0, 0, 0,
VasEBoot_le_to_cpu32 (diro->inode.size),
symlink);
if (VasEBoot_errno)
{
VasEBoot_free (symlink);
return 0;
}
}
symlink[VasEBoot_le_to_cpu32 (diro->inode.size)] = '\0';
return symlink;
}
static int
VasEBoot_ext2_iterate_dir (VasEBoot_fshelp_node_t dir,
VasEBoot_fshelp_iterate_dir_hook_t hook, void *hook_data)
{
unsigned int fpos = 0;
struct VasEBoot_fshelp_node *diro = (struct VasEBoot_fshelp_node *) dir;
if (! diro->inode_read)
{
VasEBoot_ext2_read_inode (diro->data, diro->ino, &diro->inode);
if (VasEBoot_errno)
return 0;
}
if (diro->inode.flags & VasEBoot_cpu_to_le32_compile_time (EXT4_ENCRYPT_FLAG))
{
VasEBoot_error (VAS_EBOOT_ERR_NOT_IMPLEMENTED_YET, "directory is encrypted");
return 0;
}
/* Search the file. */
while (fpos < VasEBoot_le_to_cpu32 (diro->inode.size))
{
struct ext2_dirent dirent;
VasEBoot_ext2_read_file (diro, 0, 0, fpos, sizeof (struct ext2_dirent),
(char *) &dirent);
if (VasEBoot_errno)
return 0;
if (dirent.direntlen == 0)
return 0;
if (dirent.inode != 0 && dirent.namelen != 0)
{
char filename[MAX_NAMELEN + 1];
struct VasEBoot_fshelp_node *fdiro;
enum VasEBoot_fshelp_filetype type = VAS_EBOOT_FSHELP_UNKNOWN;
VasEBoot_ext2_read_file (diro, 0, 0, fpos + sizeof (struct ext2_dirent),
dirent.namelen, filename);
if (VasEBoot_errno)
return 0;
fdiro = VasEBoot_malloc (sizeof (struct VasEBoot_fshelp_node));
if (! fdiro)
return 0;
fdiro->data = diro->data;
fdiro->ino = VasEBoot_le_to_cpu32 (dirent.inode);
filename[dirent.namelen] = '\0';
if (dirent.filetype != FILETYPE_UNKNOWN)
{
fdiro->inode_read = 0;
if (dirent.filetype == FILETYPE_DIRECTORY)
type = VAS_EBOOT_FSHELP_DIR;
else if (dirent.filetype == FILETYPE_SYMLINK)
type = VAS_EBOOT_FSHELP_SYMLINK;
else if (dirent.filetype == FILETYPE_REG)
type = VAS_EBOOT_FSHELP_REG;
}
else
{
/* The filetype can not be read from the dirent, read
the inode to get more information. */
VasEBoot_ext2_read_inode (diro->data,
VasEBoot_le_to_cpu32 (dirent.inode),
&fdiro->inode);
if (VasEBoot_errno)
{
VasEBoot_free (fdiro);
return 0;
}
fdiro->inode_read = 1;
if ((VasEBoot_le_to_cpu16 (fdiro->inode.mode)
& FILETYPE_INO_MASK) == FILETYPE_INO_DIRECTORY)
type = VAS_EBOOT_FSHELP_DIR;
else if ((VasEBoot_le_to_cpu16 (fdiro->inode.mode)
& FILETYPE_INO_MASK) == FILETYPE_INO_SYMLINK)
type = VAS_EBOOT_FSHELP_SYMLINK;
else if ((VasEBoot_le_to_cpu16 (fdiro->inode.mode)
& FILETYPE_INO_MASK) == FILETYPE_INO_REG)
type = VAS_EBOOT_FSHELP_REG;
}
if (hook (filename, type, fdiro, hook_data))
return 1;
}
fpos += VasEBoot_le_to_cpu16 (dirent.direntlen);
}
return 0;
}
/* Open a file named NAME and initialize FILE. */
static VasEBoot_err_t
VasEBoot_ext2_open (struct VasEBoot_file *file, const char *name)
{
struct VasEBoot_ext2_data *data;
struct VasEBoot_fshelp_node *fdiro = 0;
VasEBoot_err_t err;
VasEBoot_dl_ref (my_mod);
data = VasEBoot_ext2_mount (file->device->disk);
if (! data)
{
err = VasEBoot_errno;
goto fail;
}
err = VasEBoot_fshelp_find_file (name, &data->diropen, &fdiro,
VasEBoot_ext2_iterate_dir,
VasEBoot_ext2_read_symlink, VAS_EBOOT_FSHELP_REG);
if (err)
goto fail;
if (! fdiro->inode_read)
{
err = VasEBoot_ext2_read_inode (data, fdiro->ino, &fdiro->inode);
if (err)
goto fail;
}
if (fdiro->inode.flags & VasEBoot_cpu_to_le32_compile_time (EXT4_ENCRYPT_FLAG))
{
err = VasEBoot_error (VAS_EBOOT_ERR_NOT_IMPLEMENTED_YET, "file is encrypted");
goto fail;
}
VasEBoot_memcpy (data->inode, &fdiro->inode, sizeof (struct VasEBoot_ext2_inode));
VasEBoot_free (fdiro);
file->size = VasEBoot_le_to_cpu32 (data->inode->size);
file->size |= ((VasEBoot_off_t) VasEBoot_le_to_cpu32 (data->inode->size_high)) << 32;
file->data = data;
file->offset = 0;
return 0;
fail:
if (fdiro != &data->diropen)
VasEBoot_free (fdiro);
VasEBoot_free (data);
VasEBoot_dl_unref (my_mod);
return err;
}
static VasEBoot_err_t
VasEBoot_ext2_close (VasEBoot_file_t file)
{
VasEBoot_free (file->data);
VasEBoot_dl_unref (my_mod);
return VAS_EBOOT_ERR_NONE;
}
/* Read LEN bytes data from FILE into BUF. */
static VasEBoot_ssize_t
VasEBoot_ext2_read (VasEBoot_file_t file, char *buf, VasEBoot_size_t len)
{
struct VasEBoot_ext2_data *data = (struct VasEBoot_ext2_data *) file->data;
return VasEBoot_ext2_read_file (&data->diropen,
file->read_hook, file->read_hook_data,
file->offset, len, buf);
}
/* Context for VasEBoot_ext2_dir. */
struct VasEBoot_ext2_dir_ctx
{
VasEBoot_fs_dir_hook_t hook;
void *hook_data;
struct VasEBoot_ext2_data *data;
};
/* Helper for VasEBoot_ext2_dir. */
static int
VasEBoot_ext2_dir_iter (const char *filename, enum VasEBoot_fshelp_filetype filetype,
VasEBoot_fshelp_node_t node, void *data)
{
struct VasEBoot_ext2_dir_ctx *ctx = data;
struct VasEBoot_dirhook_info info;
VasEBoot_memset (&info, 0, sizeof (info));
if (! node->inode_read)
{
VasEBoot_ext2_read_inode (ctx->data, node->ino, &node->inode);
if (!VasEBoot_errno)
node->inode_read = 1;
VasEBoot_errno = VAS_EBOOT_ERR_NONE;
}
if (node->inode_read)
{
info.mtimeset = 1;
info.mtime = VasEBoot_le_to_cpu32 (node->inode.mtime);
}
info.dir = ((filetype & VAS_EBOOT_FSHELP_TYPE_MASK) == VAS_EBOOT_FSHELP_DIR);
VasEBoot_free (node);
return ctx->hook (filename, &info, ctx->hook_data);
}
static VasEBoot_err_t
VasEBoot_ext2_dir (VasEBoot_device_t device, const char *path, VasEBoot_fs_dir_hook_t hook,
void *hook_data)
{
struct VasEBoot_ext2_dir_ctx ctx = {
.hook = hook,
.hook_data = hook_data
};
struct VasEBoot_fshelp_node *fdiro = 0;
VasEBoot_dl_ref (my_mod);
ctx.data = VasEBoot_ext2_mount (device->disk);
if (! ctx.data)
goto fail;
VasEBoot_fshelp_find_file (path, &ctx.data->diropen, &fdiro,
VasEBoot_ext2_iterate_dir, VasEBoot_ext2_read_symlink,
VAS_EBOOT_FSHELP_DIR);
if (VasEBoot_errno)
goto fail;
VasEBoot_ext2_iterate_dir (fdiro, VasEBoot_ext2_dir_iter, &ctx);
fail:
if (fdiro != &ctx.data->diropen)
VasEBoot_free (fdiro);
VasEBoot_free (ctx.data);
VasEBoot_dl_unref (my_mod);
return VasEBoot_errno;
}
static VasEBoot_err_t
VasEBoot_ext2_label (VasEBoot_device_t device, char **label)
{
struct VasEBoot_ext2_data *data;
VasEBoot_disk_t disk = device->disk;
VasEBoot_dl_ref (my_mod);
data = VasEBoot_ext2_mount (disk);
if (data)
*label = VasEBoot_strndup (data->sblock.volume_name,
sizeof (data->sblock.volume_name));
else
*label = NULL;
VasEBoot_dl_unref (my_mod);
VasEBoot_free (data);
return VasEBoot_errno;
}
static VasEBoot_err_t
VasEBoot_ext2_uuid (VasEBoot_device_t device, char **uuid)
{
struct VasEBoot_ext2_data *data;
VasEBoot_disk_t disk = device->disk;
VasEBoot_dl_ref (my_mod);
data = VasEBoot_ext2_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;
}
/* Get mtime. */
static VasEBoot_err_t
VasEBoot_ext2_mtime (VasEBoot_device_t device, VasEBoot_int64_t *tm)
{
struct VasEBoot_ext2_data *data;
VasEBoot_disk_t disk = device->disk;
VasEBoot_dl_ref (my_mod);
data = VasEBoot_ext2_mount (disk);
if (!data)
*tm = 0;
else
*tm = VasEBoot_le_to_cpu32 (data->sblock.utime);
VasEBoot_dl_unref (my_mod);
VasEBoot_free (data);
return VasEBoot_errno;
}
static struct VasEBoot_fs VasEBoot_ext2_fs =
{
.name = "ext2",
.fs_dir = VasEBoot_ext2_dir,
.fs_open = VasEBoot_ext2_open,
.fs_read = VasEBoot_ext2_read,
.fs_close = VasEBoot_ext2_close,
.fs_label = VasEBoot_ext2_label,
.fs_uuid = VasEBoot_ext2_uuid,
.fs_mtime = VasEBoot_ext2_mtime,
#ifdef VAS_EBOOT_UTIL
.reserved_first_sector = 1,
.blocklist_install = 1,
#endif
.next = 0
};
VAS_EBOOT_MOD_INIT(ext2)
{
VasEBoot_ext2_fs.mod = mod;
VasEBoot_fs_register (&VasEBoot_ext2_fs);
my_mod = mod;
}
VAS_EBOOT_MOD_FINI(ext2)
{
VasEBoot_fs_unregister (&VasEBoot_ext2_fs);
}