#include #include #include #include #include #include #include #include /* EXT2 Filesystem States */ #define EXT2_FS_UNRECOVERABLE_ERRORS 3 /* Ext2 incompatible features */ #define EXT2_IF_COMPRESSION 0x01 #define EXT2_IF_EXTENTS 0x40 #define EXT2_IF_64BIT 0x80 #define EXT2_IF_INLINE_DATA 0x8000 #define EXT2_IF_ENCRYPT 0x10000 #define EXT2_FEATURE_INCOMPAT_META_BG 0x0010 /* Ext4 flags */ #define EXT4_EXTENTS_FLAG 0x80000 #define EXT2_S_MAGIC 0xEF53 /* Superblock Fields */ struct ext2_superblock { uint32_t s_inodes_count; uint32_t s_blocks_count; uint32_t s_r_blocks_count; uint32_t s_free_blocks_count; uint32_t s_free_inodes_count; uint32_t s_first_data_block; uint32_t s_log_block_size; uint32_t s_log_frag_size; uint32_t s_blocks_per_group; uint32_t s_frags_per_group; uint32_t s_inodes_per_group; uint32_t s_mtime; uint32_t s_wtime; uint16_t s_mnt_count; uint16_t s_max_mnt_count; uint16_t s_magic; uint16_t s_state; uint16_t s_errors; uint16_t s_minor_rev_level; uint32_t s_lastcheck; uint32_t s_checkinterval; uint32_t s_creator_os; uint32_t s_rev_level; uint16_t s_def_resuid; uint16_t s_def_gid; // if version number >= 1, we have to use the ext2 extended superblock as well /* Extended Superblock */ uint32_t s_first_ino; uint16_t s_inode_size; uint16_t s_block_group_nr; uint32_t s_feature_compat; uint32_t s_feature_incompat; uint32_t s_feature_ro_compat; uint64_t s_uuid[2]; uint64_t s_volume_name[2]; uint64_t s_last_mounted[8]; uint32_t compression_info; uint8_t prealloc_blocks; uint8_t prealloc_dir_blocks; uint16_t reserved_gdt_blocks; uint8_t journal_uuid[16]; uint32_t journal_inum; uint32_t journal_dev; uint32_t last_orphan; uint32_t hash_seed[4]; uint8_t def_hash_version; uint8_t jnl_backup_type; uint16_t group_desc_size; uint32_t default_mount_opts; uint32_t first_meta_bg; uint32_t mkfs_time; uint32_t jnl_blocks[17]; } __attribute__((packed)); /* EXT2 Block Group Descriptor */ struct ext2_bgd { uint32_t bg_block_bitmap; uint32_t bg_inode_bitmap; uint32_t bg_inode_table; uint16_t bg_free_blocks_count; uint16_t bg_free_inodes_count; uint16_t bg_dirs_count; uint16_t reserved[7]; } __attribute__((packed)); struct ext4_bgd { uint32_t bg_block_bitmap; uint32_t bg_inode_bitmap; uint32_t bg_inode_table; uint16_t bg_free_blocks_count; uint16_t bg_free_inodes_count; uint16_t bg_dirs_count; uint16_t pad; uint32_t reserved[3]; uint32_t block_id_hi; uint32_t inode_id_hi; uint32_t inode_table_id_hi; uint16_t free_blocks_hi; uint16_t free_inodes_hi; uint16_t used_dirs_hi; uint16_t pad2; uint32_t reserved2[3]; } __attribute__((packed)); /* EXT2 Inode Types */ #define EXT2_INO_DIRECTORY 0x4000 /* EXT2 Directory Entry */ struct ext2_dir_entry { uint32_t inode; uint16_t rec_len; uint8_t name_len; uint8_t type; } __attribute__((packed)); struct ext4_extent_header { uint16_t magic; uint16_t entries; uint16_t max; uint16_t depth; uint16_t generation; } __attribute__((packed)); struct ext4_extent { uint32_t block; uint16_t len; uint16_t start_hi; uint32_t start; } __attribute__((packed)); struct ext4_extent_idx { uint32_t block; uint32_t leaf; uint16_t leaf_hi; uint16_t empty; } __attribute__((packed)); static int inode_read(void *buf, uint64_t loc, uint64_t count, uint64_t block_size, struct ext2_inode *inode, uint64_t drive, struct part *part); // parse an inode given the partition base and inode number static int ext2_get_inode(struct ext2_inode *ret, uint64_t drive, struct part *part, uint64_t inode, struct ext2_superblock *sb) { if (inode == 0) return -1; //determine if we need to use 64 bit inode ids bool bit64 = false; if (sb->s_rev_level != 0 && (sb->s_feature_incompat & (EXT2_IF_64BIT)) && sb->group_desc_size != 0 && ((sb->group_desc_size & (sb->group_desc_size - 1)) == 0)) { if(sb->group_desc_size > 32) { bit64 = true; } } const uint64_t ino_blk_grp = (inode - 1) / sb->s_inodes_per_group; const uint64_t ino_tbl_idx = (inode - 1) % sb->s_inodes_per_group; const uint64_t block_size = ((uint64_t)1024 << sb->s_log_block_size); uint64_t ino_offset; const uint64_t bgd_start_offset = block_size >= 2048 ? block_size : block_size * 2; const uint64_t ino_size = sb->s_rev_level == 0 ? sizeof(struct ext2_inode) : sb->s_inode_size; if (!bit64) { struct ext2_bgd target_descriptor; const uint64_t bgd_offset = bgd_start_offset + (sizeof(struct ext2_bgd) * ino_blk_grp); read_partition(drive, part, &target_descriptor, bgd_offset, sizeof(struct ext2_bgd)); ino_offset = ((target_descriptor.bg_inode_table) * block_size) + (ino_size * ino_tbl_idx); } else { struct ext4_bgd target_descriptor; const uint64_t bgd_offset = bgd_start_offset + (sizeof(struct ext4_bgd) * ino_blk_grp); read_partition(drive, part, &target_descriptor, bgd_offset, sizeof(struct ext4_bgd)); ino_offset = ((target_descriptor.bg_inode_table | (bit64 ? ((uint64_t)target_descriptor.inode_id_hi << 32) : 0)) * block_size) + (ino_size * ino_tbl_idx); } read_partition(drive, part, ret, ino_offset, sizeof(struct ext2_inode)); return 0; } static int ext2_parse_dirent(struct ext2_dir_entry *dir, struct ext2_file_handle *fd, struct ext2_superblock *sb, const char *path) { if (*path == '/') path++; struct ext2_inode current_inode = fd->root_inode; bool escape = false; char token[256] = {0}; next: for (size_t i = 0; *path != '/' && *path != '\0'; i++, path++) token[i] = *path; if (*path == '\0') escape = true; else path++; for (uint32_t i = 0; i < current_inode.i_size; ) { // preliminary read inode_read(dir, i, sizeof(struct ext2_dir_entry), fd->block_size, ¤t_inode, fd->drive, &fd->part); // name read char name[dir->name_len + 1]; memset(name, 0, dir->name_len + 1); inode_read(name, i + sizeof(struct ext2_dir_entry), dir->name_len, fd->block_size, ¤t_inode, fd->drive, &fd->part); int r = strcmp(token, name); if (!r) { if (escape) { return 0; } else { // update the current inode ext2_get_inode(¤t_inode, fd->drive, &fd->part, dir->inode, sb); goto next; } } i += dir->rec_len; } return -1; } int ext2_open(struct ext2_file_handle *ret, int drive, int partition, const char *path) { if (get_part(&ret->part, drive, partition)) panic("Invalid partition"); ret->drive = drive; struct ext2_superblock sb; read_partition(drive, &ret->part, &sb, 1024, sizeof(struct ext2_superblock)); if (sb.s_state == EXT2_FS_UNRECOVERABLE_ERRORS) panic("EXT2: unrecoverable errors found\n"); ret->block_size = ((uint64_t)1024 << sb.s_log_block_size); ext2_get_inode(&ret->root_inode, drive, &ret->part, 2, &sb); struct ext2_dir_entry entry; int r = ext2_parse_dirent(&entry, ret, &sb, path); if (r) return r; ext2_get_inode(&ret->inode, drive, &ret->part, entry.inode, &sb); ret->size = ret->inode.i_size; if ((ret->inode.i_mode & 0xf000) == EXT2_INO_DIRECTORY) panic("ext2: Requested file \"%s\" is a directory!", path); return 0; } int ext2_read(struct ext2_file_handle *file, void *buf, uint64_t loc, uint64_t count) { return inode_read(buf, loc, count, file->block_size, &file->inode, file->drive, &file->part); } static struct ext4_extent_header* ext4_find_leaf(struct ext4_extent_header* ext_block, uint32_t read_block, uint64_t block_size, uint64_t drive, struct part *part) { struct ext4_extent_idx* index; void* buf = NULL; while (1) { index = (struct ext4_extent_idx*)((size_t)ext_block + 12); #define EXT4_EXT_MAGIC 0xf30a if (ext_block->magic != EXT4_EXT_MAGIC) panic("invalid extent magic"); if (ext_block->depth == 0) { return ext_block; } int i; for (i = 0; i < ext_block->entries; i++) { if(read_block < index[i].block) break; } if (--i < 0) panic("extent not found"); uint64_t block = ((uint64_t)index[i].leaf_hi << 32) | index[i].leaf; if(!buf) buf = conv_mem_alloc(block_size); read_partition(drive, part, buf, (block * block_size), block_size); ext_block = buf; } } static int inode_read(void *buf, uint64_t loc, uint64_t count, uint64_t block_size, struct ext2_inode *inode, uint64_t drive, struct part *part) { for (uint64_t progress = 0; progress < count;) { uint64_t block = (loc + progress) / block_size; uint64_t chunk = count - progress; uint64_t offset = (loc + progress) % block_size; if (chunk > block_size - offset) chunk = block_size - offset; uint32_t block_index; if (inode->i_flags & EXT4_EXTENTS_FLAG) { struct ext4_extent_header *leaf; struct ext4_extent *ext; int i; leaf = ext4_find_leaf((struct ext4_extent_header*)inode->i_blocks, block, block_size, drive, part); if (!leaf) panic("invalid extent"); ext = (struct ext4_extent*)((size_t)leaf + 12); for (i = 0; i < leaf->entries; i++) { if (block < ext[i].block) { break; } } if (--i >= 0) { block -= ext[i].block; if (block >= ext[i].len) { panic("block longer than extent"); } else { uint64_t start = ((uint64_t)ext[i].start_hi << 32) + ext[i].start; block_index = start + block; } } else { panic("extent for block not found"); } } else if (block < 12) { // Direct block block_index = inode->i_blocks[block]; } else { // Indirect block block -= 12; if (block * sizeof(uint32_t) >= block_size) { // Double indirect block block -= block_size / sizeof(uint32_t); uint32_t index = block / (block_size / sizeof(uint32_t)); if (index * sizeof(uint32_t) >= block_size) { // Triple indirect block panic("ext2: triply indirect blocks unsupported"); } uint32_t offset = block % (block_size / sizeof(uint32_t)); uint32_t indirect_block; read_partition( drive, part, &indirect_block, inode->i_blocks[13] * block_size + index * sizeof(uint32_t), sizeof(uint32_t) ); read_partition( drive, part, &block_index, indirect_block * block_size + offset * sizeof(uint32_t), sizeof(uint32_t) ); } else { read_partition( drive, part, &block_index, inode->i_blocks[12] * block_size + block * sizeof(uint32_t), sizeof(uint32_t) ); } } read_partition(drive, part, buf + progress, (block_index * block_size) + offset, chunk); progress += chunk; } return 0; } // attempts to initialize the ext2 filesystem // and checks if all features are supported int ext2_check_signature(int drive, int partition) { struct part part; if (get_part(&part, drive, partition)) panic("Invalid partition"); struct ext2_superblock sb; read_partition(drive, &part, &sb, 1024, sizeof(struct ext2_superblock)); if (sb.s_magic != EXT2_S_MAGIC) return 0; // If the revision level is 0, we can't test for features. if (sb.s_rev_level == 0) return 1; if (sb.s_feature_incompat & EXT2_IF_COMPRESSION || sb.s_feature_incompat & EXT2_IF_INLINE_DATA || sb.s_feature_incompat & EXT2_FEATURE_INCOMPAT_META_BG || sb.s_feature_incompat & EXT2_IF_ENCRYPT) panic("EXT2: filesystem has unsupported features %x", sb.s_feature_incompat); return 1; }