/* erofs.c - Enhanced Read-Only File System */ /* * VAS_EBOOT -- GRand Unified Bootloader * Copyright (C) 2024 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 . */ #include #include #include #include #include #include #include #include #include #include VAS_EBOOT_MOD_LICENSE ("GPLv3+"); #define EROFS_SUPER_OFFSET 1024 #define EROFS_MAGIC 0xE0F5E1E2 #define EROFS_ISLOTBITS 5 #define EROFS_FEATURE_INCOMPAT_CHUNKED_FILE 0x00000004 #define EROFS_ALL_FEATURE_INCOMPAT EROFS_FEATURE_INCOMPAT_CHUNKED_FILE struct VasEBoot_erofs_super { VasEBoot_uint32_t magic; VasEBoot_uint32_t checksum; VasEBoot_uint32_t feature_compat; VasEBoot_uint8_t log2_blksz; VasEBoot_uint8_t sb_extslots; VasEBoot_uint16_t root_nid; VasEBoot_uint64_t inos; VasEBoot_uint64_t build_time; VasEBoot_uint32_t build_time_nsec; VasEBoot_uint32_t blocks; VasEBoot_uint32_t meta_blkaddr; VasEBoot_uint32_t xattr_blkaddr; VasEBoot_packed_guid_t uuid; VasEBoot_uint8_t volume_name[16]; VasEBoot_uint32_t feature_incompat; union { VasEBoot_uint16_t available_compr_algs; VasEBoot_uint16_t lz4_max_distance; } VAS_EBOOT_PACKED u1; VasEBoot_uint16_t extra_devices; VasEBoot_uint16_t devt_slotoff; VasEBoot_uint8_t log2_dirblksz; VasEBoot_uint8_t xattr_prefix_count; VasEBoot_uint32_t xattr_prefix_start; VasEBoot_uint64_t packed_nid; VasEBoot_uint8_t reserved2[24]; } VAS_EBOOT_PACKED; #define EROFS_INODE_LAYOUT_COMPACT 0 #define EROFS_INODE_LAYOUT_EXTENDED 1 #define EROFS_INODE_FLAT_PLAIN 0 #define EROFS_INODE_COMPRESSED_FULL 1 #define EROFS_INODE_FLAT_INLINE 2 #define EROFS_INODE_COMPRESSED_COMPACT 3 #define EROFS_INODE_CHUNK_BASED 4 #define EROFS_I_VERSION_MASKS 0x01 #define EROFS_I_DATALAYOUT_MASKS 0x07 #define EROFS_I_VERSION_BIT 0 #define EROFS_I_DATALAYOUT_BIT 1 struct VasEBoot_erofs_inode_chunk_info { VasEBoot_uint16_t format; VasEBoot_uint16_t reserved; } VAS_EBOOT_PACKED; #define EROFS_CHUNK_FORMAT_BLKBITS_MASK 0x001F #define EROFS_CHUNK_FORMAT_INDEXES 0x0020 #define EROFS_BLOCK_MAP_ENTRY_SIZE 4 #define EROFS_MAP_MAPPED 0x02 #define EROFS_NULL_ADDR 1 #define EROFS_NAME_LEN 255 #define EROFS_PATH_LEN 4096 #define EROFS_MIN_LOG2_BLOCK_SIZE 9 #define EROFS_MAX_LOG2_BLOCK_SIZE 16 struct VasEBoot_erofs_inode_chunk_index { VasEBoot_uint16_t advise; VasEBoot_uint16_t device_id; VasEBoot_uint32_t blkaddr; }; union VasEBoot_erofs_inode_i_u { VasEBoot_uint32_t compressed_blocks; VasEBoot_uint32_t raw_blkaddr; VasEBoot_uint32_t rdev; struct VasEBoot_erofs_inode_chunk_info c; }; struct VasEBoot_erofs_inode_compact { VasEBoot_uint16_t i_format; VasEBoot_uint16_t i_xattr_icount; VasEBoot_uint16_t i_mode; VasEBoot_uint16_t i_nlink; VasEBoot_uint32_t i_size; VasEBoot_uint32_t i_reserved; union VasEBoot_erofs_inode_i_u i_u; VasEBoot_uint32_t i_ino; VasEBoot_uint16_t i_uid; VasEBoot_uint16_t i_gid; VasEBoot_uint32_t i_reserved2; } VAS_EBOOT_PACKED; struct VasEBoot_erofs_inode_extended { VasEBoot_uint16_t i_format; VasEBoot_uint16_t i_xattr_icount; VasEBoot_uint16_t i_mode; VasEBoot_uint16_t i_reserved; VasEBoot_uint64_t i_size; union VasEBoot_erofs_inode_i_u i_u; VasEBoot_uint32_t i_ino; VasEBoot_uint32_t i_uid; VasEBoot_uint32_t i_gid; VasEBoot_uint64_t i_mtime; VasEBoot_uint32_t i_mtime_nsec; VasEBoot_uint32_t i_nlink; VasEBoot_uint8_t i_reserved2[16]; } VAS_EBOOT_PACKED; union VasEBoot_erofs_inode { struct VasEBoot_erofs_inode_compact c; struct VasEBoot_erofs_inode_extended e; } VAS_EBOOT_PACKED; #define EROFS_FT_UNKNOWN 0 #define EROFS_FT_REG_FILE 1 #define EROFS_FT_DIR 2 #define EROFS_FT_CHRDEV 3 #define EROFS_FT_BLKDEV 4 #define EROFS_FT_FIFO 5 #define EROFS_FT_SOCK 6 #define EROFS_FT_SYMLINK 7 struct VasEBoot_erofs_dirent { VasEBoot_uint64_t nid; VasEBoot_uint16_t nameoff; VasEBoot_uint8_t file_type; VasEBoot_uint8_t reserved; } VAS_EBOOT_PACKED; struct VasEBoot_erofs_map_blocks { VasEBoot_uint64_t m_pa; /* physical address */ VasEBoot_uint64_t m_la; /* logical address */ VasEBoot_uint64_t m_plen; /* physical length */ VasEBoot_uint64_t m_llen; /* logical length */ VasEBoot_uint32_t m_flags; }; struct VasEBoot_erofs_xattr_ibody_header { VasEBoot_uint32_t h_reserved; VasEBoot_uint8_t h_shared_count; VasEBoot_uint8_t h_reserved2[7]; VasEBoot_uint32_t h_shared_xattrs[0]; }; struct VasEBoot_fshelp_node { struct VasEBoot_erofs_data *data; union VasEBoot_erofs_inode inode; VasEBoot_uint64_t ino; VasEBoot_uint8_t inode_type; VasEBoot_uint8_t inode_datalayout; /* If the inode has been read into memory? */ bool inode_loaded; }; struct VasEBoot_erofs_data { VasEBoot_disk_t disk; struct VasEBoot_erofs_super sb; struct VasEBoot_fshelp_node inode; }; #define erofs_blocksz(data) (((VasEBoot_uint32_t) 1) << data->sb.log2_blksz) static VasEBoot_size_t VasEBoot_erofs_strnlen (const char *s, VasEBoot_size_t n) { const char *p = s; if (n == 0) return 0; while (n-- && *p) p++; return p - s; } static VasEBoot_uint64_t erofs_iloc (VasEBoot_fshelp_node_t node) { struct VasEBoot_erofs_super *sb = &node->data->sb; return ((VasEBoot_uint64_t) VasEBoot_le_to_cpu32 (sb->meta_blkaddr) << sb->log2_blksz) + (node->ino << EROFS_ISLOTBITS); } static VasEBoot_err_t erofs_read_inode (struct VasEBoot_erofs_data *data, VasEBoot_fshelp_node_t node) { union VasEBoot_erofs_inode *di; VasEBoot_err_t err; VasEBoot_uint16_t i_format; VasEBoot_uint64_t addr = erofs_iloc (node); di = (union VasEBoot_erofs_inode *) &node->inode; err = VasEBoot_disk_read (data->disk, addr >> VAS_EBOOT_DISK_SECTOR_BITS, addr & (VAS_EBOOT_DISK_SECTOR_SIZE - 1), sizeof (struct VasEBoot_erofs_inode_compact), &di->c); if (err != VAS_EBOOT_ERR_NONE) return err; i_format = VasEBoot_le_to_cpu16 (di->c.i_format); node->inode_type = (i_format >> EROFS_I_VERSION_BIT) & EROFS_I_VERSION_MASKS; node->inode_datalayout = (i_format >> EROFS_I_DATALAYOUT_BIT) & EROFS_I_DATALAYOUT_MASKS; switch (node->inode_type) { case EROFS_INODE_LAYOUT_EXTENDED: addr += sizeof (struct VasEBoot_erofs_inode_compact); err = VasEBoot_disk_read (data->disk, addr >> VAS_EBOOT_DISK_SECTOR_BITS, addr & (VAS_EBOOT_DISK_SECTOR_SIZE - 1), sizeof (struct VasEBoot_erofs_inode_extended) - sizeof (struct VasEBoot_erofs_inode_compact), (VasEBoot_uint8_t *) di + sizeof (struct VasEBoot_erofs_inode_compact)); if (err != VAS_EBOOT_ERR_NONE) return err; break; case EROFS_INODE_LAYOUT_COMPACT: break; default: return VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "invalid type %u @ inode %" PRIuVAS_EBOOT_UINT64_T, node->inode_type, node->ino); } node->inode_loaded = true; return 0; } static VasEBoot_uint64_t erofs_inode_size (VasEBoot_fshelp_node_t node) { return node->inode_type == EROFS_INODE_LAYOUT_COMPACT ? sizeof (struct VasEBoot_erofs_inode_compact) : sizeof (struct VasEBoot_erofs_inode_extended); } static VasEBoot_uint64_t erofs_inode_file_size (VasEBoot_fshelp_node_t node) { union VasEBoot_erofs_inode *di = (union VasEBoot_erofs_inode *) &node->inode; return node->inode_type == EROFS_INODE_LAYOUT_COMPACT ? VasEBoot_le_to_cpu32 (di->c.i_size) : VasEBoot_le_to_cpu64 (di->e.i_size); } static VasEBoot_uint32_t erofs_inode_xattr_ibody_size (VasEBoot_fshelp_node_t node) { VasEBoot_uint16_t cnt = VasEBoot_le_to_cpu16 (node->inode.e.i_xattr_icount); if (cnt == 0) return 0; return sizeof (struct VasEBoot_erofs_xattr_ibody_header) + ((cnt - 1) * sizeof (VasEBoot_uint32_t)); } static VasEBoot_uint64_t erofs_inode_mtime (VasEBoot_fshelp_node_t node) { return node->inode_type == EROFS_INODE_LAYOUT_COMPACT ? VasEBoot_le_to_cpu64 (node->data->sb.build_time) : VasEBoot_le_to_cpu64 (node->inode.e.i_mtime); } static VasEBoot_err_t erofs_map_blocks_flatmode (VasEBoot_fshelp_node_t node, struct VasEBoot_erofs_map_blocks *map) { VasEBoot_uint64_t nblocks, lastblk, file_size; bool tailendpacking = (node->inode_datalayout == EROFS_INODE_FLAT_INLINE); VasEBoot_uint64_t blocksz = erofs_blocksz (node->data); /* `file_size` is checked by caller and cannot be zero, hence nblocks > 0. */ file_size = erofs_inode_file_size (node); if (VasEBoot_add (file_size, blocksz - 1, &nblocks)) return VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, "nblocks overflow"); nblocks >>= node->data->sb.log2_blksz; lastblk = nblocks - tailendpacking; map->m_flags = EROFS_MAP_MAPPED; /* No overflow as (lastblk <= nblocks) && (nblocks * blocksz <= UINT64_MAX - blocksz + 1). */ if (map->m_la < (lastblk * blocksz)) { if (VasEBoot_mul ((VasEBoot_uint64_t) VasEBoot_le_to_cpu32 (node->inode.e.i_u.raw_blkaddr), blocksz, &map->m_pa) || VasEBoot_add (map->m_pa, map->m_la, &map->m_pa)) return VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, "m_pa overflow"); if (VasEBoot_sub (lastblk * blocksz, map->m_la, &map->m_plen)) return VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, "m_plen underflow"); } else if (tailendpacking) { if (VasEBoot_add (erofs_iloc (node), erofs_inode_size (node), &map->m_pa) || VasEBoot_add (map->m_pa, erofs_inode_xattr_ibody_size (node), &map->m_pa) || VasEBoot_add (map->m_pa, map->m_la & (blocksz - 1), &map->m_pa)) return VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, "m_pa overflow when handling tailpacking"); if (VasEBoot_sub (file_size, map->m_la, &map->m_plen)) return VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, "m_plen overflow when handling tailpacking"); /* No overflow as map->m_plen <= UINT64_MAX - blocksz + 1. */ if (((map->m_pa & (blocksz - 1)) + map->m_plen) > blocksz) return VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "inline data cross block boundary @ inode %" PRIuVAS_EBOOT_UINT64_T, node->ino); } else return VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "invalid map->m_la=%" PRIuVAS_EBOOT_UINT64_T " @ inode %" PRIuVAS_EBOOT_UINT64_T, map->m_la, node->ino); map->m_llen = map->m_plen; return VAS_EBOOT_ERR_NONE; } static VasEBoot_err_t erofs_map_blocks_chunkmode (VasEBoot_fshelp_node_t node, struct VasEBoot_erofs_map_blocks *map) { VasEBoot_uint16_t chunk_format = VasEBoot_le_to_cpu16 (node->inode.e.i_u.c.format); VasEBoot_uint64_t unit, pos, chunknr, blkaddr; VasEBoot_uint8_t chunkbits; VasEBoot_err_t err; if (chunk_format & EROFS_CHUNK_FORMAT_INDEXES) unit = sizeof (struct VasEBoot_erofs_inode_chunk_index); else unit = EROFS_BLOCK_MAP_ENTRY_SIZE; chunkbits = node->data->sb.log2_blksz + (chunk_format & EROFS_CHUNK_FORMAT_BLKBITS_MASK); if (chunkbits > 63) return VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "invalid chunkbits %u @ inode %" PRIuVAS_EBOOT_UINT64_T, chunkbits, node->ino); chunknr = map->m_la >> chunkbits; if (VasEBoot_add (erofs_iloc (node), erofs_inode_size (node), &pos)) return VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, "chunkmap position overflow when adding inode size"); if (VasEBoot_add (pos, erofs_inode_xattr_ibody_size (node), &pos)) return VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, "chunkmap position overflow when adding xattr size"); if (ALIGN_UP_OVF (pos, unit, &pos)) return VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, "position overflow when seeking at the start of chunkmap"); /* No overflow for multiplication as chunkbits >= 9 and sizeof(unit) <= 8. */ if (VasEBoot_add (pos, chunknr * unit, &pos)) return VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, "chunkmap position overflow when finding the specific chunk"); map->m_la = chunknr << chunkbits; if (VasEBoot_sub (erofs_inode_file_size (node), map->m_la, &map->m_plen)) return VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, "m_plen underflow"); map->m_plen = VasEBoot_min (((VasEBoot_uint64_t) 1) << chunkbits, ALIGN_UP (map->m_plen, erofs_blocksz (node->data))); if (chunk_format & EROFS_CHUNK_FORMAT_INDEXES) { struct VasEBoot_erofs_inode_chunk_index idx; err = VasEBoot_disk_read (node->data->disk, pos >> VAS_EBOOT_DISK_SECTOR_BITS, pos & (VAS_EBOOT_DISK_SECTOR_SIZE - 1), unit, &idx); if (err != VAS_EBOOT_ERR_NONE) return err; blkaddr = VasEBoot_le_to_cpu32 (idx.blkaddr); } else { VasEBoot_uint32_t blkaddr_le; err = VasEBoot_disk_read (node->data->disk, pos >> VAS_EBOOT_DISK_SECTOR_BITS, pos & (VAS_EBOOT_DISK_SECTOR_SIZE - 1), unit, &blkaddr_le); if (err != VAS_EBOOT_ERR_NONE) return err; blkaddr = VasEBoot_le_to_cpu32 (blkaddr_le); } if (blkaddr == EROFS_NULL_ADDR) { map->m_pa = 0; map->m_flags = 0; } else { map->m_pa = blkaddr << node->data->sb.log2_blksz; map->m_flags = EROFS_MAP_MAPPED; } map->m_llen = map->m_plen; return VAS_EBOOT_ERR_NONE; } static VasEBoot_err_t erofs_map_blocks (VasEBoot_fshelp_node_t node, struct VasEBoot_erofs_map_blocks *map) { if (map->m_la >= erofs_inode_file_size (node)) { map->m_llen = map->m_plen = 0; map->m_pa = 0; map->m_flags = 0; return VAS_EBOOT_ERR_NONE; } if (node->inode_datalayout != EROFS_INODE_CHUNK_BASED) return erofs_map_blocks_flatmode (node, map); else return erofs_map_blocks_chunkmode (node, map); } static VasEBoot_err_t erofs_read_raw_data (VasEBoot_fshelp_node_t node, VasEBoot_uint8_t *buf, VasEBoot_uint64_t size, VasEBoot_uint64_t offset, VasEBoot_uint64_t *bytes) { struct VasEBoot_erofs_map_blocks map = {0}; VasEBoot_uint64_t cur; VasEBoot_err_t err; if (bytes) *bytes = 0; if (node->inode_loaded == false) { err = erofs_read_inode (node->data, node); if (err != VAS_EBOOT_ERR_NONE) return err; } cur = offset; while (cur < offset + size) { VasEBoot_uint8_t *const estart = buf + cur - offset; VasEBoot_uint64_t eend, moff = 0; map.m_la = cur; err = erofs_map_blocks (node, &map); if (err != VAS_EBOOT_ERR_NONE) return err; if (VasEBoot_add(map.m_la, map.m_llen, &eend)) return VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, "eend overflow"); eend = VasEBoot_min (eend, offset + size); if (!(map.m_flags & EROFS_MAP_MAPPED)) { if (!map.m_llen) { /* Reached EOF. */ VasEBoot_memset (estart, 0, offset + size - cur); cur = offset + size; continue; } /* It's a hole. */ VasEBoot_memset (estart, 0, eend - cur); if (bytes) *bytes += eend - cur; cur = eend; continue; } if (cur > map.m_la) { moff = cur - map.m_la; map.m_la = cur; } err = VasEBoot_disk_read (node->data->disk, (map.m_pa + moff) >> VAS_EBOOT_DISK_SECTOR_BITS, (map.m_pa + moff) & (VAS_EBOOT_DISK_SECTOR_SIZE - 1), eend - map.m_la, estart); if (err != VAS_EBOOT_ERR_NONE) return err; if (bytes) *bytes += eend - map.m_la; cur = eend; } return VAS_EBOOT_ERR_NONE; } static int erofs_iterate_dir (VasEBoot_fshelp_node_t dir, VasEBoot_fshelp_iterate_dir_hook_t hook, void *hook_data) { VasEBoot_uint64_t offset = 0, file_size; VasEBoot_uint32_t blocksz = erofs_blocksz (dir->data); VasEBoot_uint8_t *buf; VasEBoot_err_t err; if (dir->inode_loaded == false) { err = erofs_read_inode (dir->data, dir); if (err != VAS_EBOOT_ERR_NONE) return 0; } file_size = erofs_inode_file_size (dir); buf = VasEBoot_malloc (blocksz); if (buf == NULL) return 0; while (offset < file_size) { VasEBoot_uint64_t maxsize = VasEBoot_min (blocksz, file_size - offset); struct VasEBoot_erofs_dirent *de = (void *) buf, *end; VasEBoot_uint16_t nameoff; err = erofs_read_raw_data (dir, buf, maxsize, offset, NULL); if (err != VAS_EBOOT_ERR_NONE) goto not_found; nameoff = VasEBoot_le_to_cpu16 (de->nameoff); if (nameoff < sizeof (struct VasEBoot_erofs_dirent) || nameoff >= maxsize) { VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "invalid nameoff %u @ inode %" PRIuVAS_EBOOT_UINT64_T, nameoff, dir->ino); goto not_found; } end = (struct VasEBoot_erofs_dirent *) ((VasEBoot_uint8_t *) de + nameoff); while (de < end) { struct VasEBoot_fshelp_node *fdiro; enum VasEBoot_fshelp_filetype type; char filename[EROFS_NAME_LEN + 1]; VasEBoot_size_t de_namelen; const char *de_name; fdiro = VasEBoot_malloc (sizeof (struct VasEBoot_fshelp_node)); if (fdiro == NULL) goto not_found; fdiro->data = dir->data; fdiro->ino = VasEBoot_le_to_cpu64 (de->nid); fdiro->inode_loaded = false; nameoff = VasEBoot_le_to_cpu16 (de->nameoff); if (nameoff < sizeof (struct VasEBoot_erofs_dirent) || nameoff >= maxsize) { VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "invalid nameoff %u @ inode %" PRIuVAS_EBOOT_UINT64_T, nameoff, dir->ino); VasEBoot_free (fdiro); goto not_found; } de_name = (char *) buf + nameoff; if (de + 1 >= end) de_namelen = VasEBoot_erofs_strnlen (de_name, maxsize - nameoff); else { if (VasEBoot_sub (VasEBoot_le_to_cpu16 (de[1].nameoff), nameoff, &de_namelen)) { VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, "de_namelen underflow"); VasEBoot_free (fdiro); goto not_found; } } if (nameoff + de_namelen > maxsize || de_namelen > EROFS_NAME_LEN) { VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "invalid de_namelen %" PRIuVAS_EBOOT_SIZE " @ inode %" PRIuVAS_EBOOT_UINT64_T, de_namelen, dir->ino); VasEBoot_free (fdiro); goto not_found; } VasEBoot_memcpy (filename, de_name, de_namelen); filename[de_namelen] = '\0'; switch (VasEBoot_le_to_cpu16 (de->file_type)) { case EROFS_FT_REG_FILE: case EROFS_FT_BLKDEV: case EROFS_FT_CHRDEV: case EROFS_FT_FIFO: case EROFS_FT_SOCK: type = VAS_EBOOT_FSHELP_REG; break; case EROFS_FT_DIR: type = VAS_EBOOT_FSHELP_DIR; break; case EROFS_FT_SYMLINK: type = VAS_EBOOT_FSHELP_SYMLINK; break; case EROFS_FT_UNKNOWN: default: type = VAS_EBOOT_FSHELP_UNKNOWN; } if (hook (filename, type, fdiro, hook_data)) { VasEBoot_free (buf); return 1; } ++de; } offset += maxsize; } not_found: VasEBoot_free (buf); return 0; } static char * erofs_read_symlink (VasEBoot_fshelp_node_t node) { char *symlink; VasEBoot_size_t sz, lsz; VasEBoot_err_t err; if (node->inode_loaded == false) { err = erofs_read_inode (node->data, node); if (err != VAS_EBOOT_ERR_NONE) return NULL; } sz = erofs_inode_file_size (node); if (sz >= EROFS_PATH_LEN) { VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "symlink too long @ inode %" PRIuVAS_EBOOT_UINT64_T, node->ino); return NULL; } if (VasEBoot_add (sz, 1, &lsz)) { VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, N_("symlink size overflow")); return NULL; } symlink = VasEBoot_malloc (lsz); if (symlink == NULL) return NULL; err = erofs_read_raw_data (node, (VasEBoot_uint8_t *) symlink, sz, 0, NULL); if (err != VAS_EBOOT_ERR_NONE) { VasEBoot_free (symlink); return NULL; } symlink[sz] = '\0'; return symlink; } static struct VasEBoot_erofs_data * erofs_mount (VasEBoot_disk_t disk, bool read_root) { struct VasEBoot_erofs_super sb; VasEBoot_err_t err; struct VasEBoot_erofs_data *data; VasEBoot_uint32_t feature; err = VasEBoot_disk_read (disk, EROFS_SUPER_OFFSET >> VAS_EBOOT_DISK_SECTOR_BITS, 0, sizeof (sb), &sb); if (err != VAS_EBOOT_ERR_NONE) return NULL; if (sb.magic != VasEBoot_cpu_to_le32_compile_time (EROFS_MAGIC) || VasEBoot_le_to_cpu32 (sb.log2_blksz) < EROFS_MIN_LOG2_BLOCK_SIZE || VasEBoot_le_to_cpu32 (sb.log2_blksz) > EROFS_MAX_LOG2_BLOCK_SIZE) { VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "not a valid erofs filesystem"); return NULL; } feature = VasEBoot_le_to_cpu32 (sb.feature_incompat); if (feature & ~EROFS_ALL_FEATURE_INCOMPAT) { VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "unsupported features: 0x%x", feature & ~EROFS_ALL_FEATURE_INCOMPAT); return NULL; } data = VasEBoot_malloc (sizeof (*data)); if (data == NULL) return NULL; data->disk = disk; data->sb = sb; if (read_root) { data->inode.data = data; data->inode.ino = VasEBoot_le_to_cpu16 (sb.root_nid); err = erofs_read_inode (data, &data->inode); if (err != VAS_EBOOT_ERR_NONE) { VasEBoot_free (data); return NULL; } } return data; } /* Context for VasEBoot_erofs_dir. */ struct VasEBoot_erofs_dir_ctx { VasEBoot_fs_dir_hook_t hook; void *hook_data; struct VasEBoot_erofs_data *data; }; /* Helper for VasEBoot_erofs_dir. */ static int erofs_dir_iter (const char *filename, enum VasEBoot_fshelp_filetype filetype, VasEBoot_fshelp_node_t node, void *data) { struct VasEBoot_erofs_dir_ctx *ctx = data; struct VasEBoot_dirhook_info info = {0}; VasEBoot_err_t err; if (node->inode_loaded == false) { err = erofs_read_inode (ctx->data, node); if (err != VAS_EBOOT_ERR_NONE) return 0; } if (node->inode_loaded == true) { info.mtimeset = 1; info.mtime = erofs_inode_mtime (node); } 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_erofs_dir (VasEBoot_device_t device, const char *path, VasEBoot_fs_dir_hook_t hook, void *hook_data) { VasEBoot_fshelp_node_t fdiro = NULL; VasEBoot_err_t err; struct VasEBoot_erofs_dir_ctx ctx = { .hook = hook, .hook_data = hook_data }; ctx.data = erofs_mount (device->disk, true); if (ctx.data == NULL) goto fail; err = VasEBoot_fshelp_find_file (path, &ctx.data->inode, &fdiro, erofs_iterate_dir, erofs_read_symlink, VAS_EBOOT_FSHELP_DIR); if (err != VAS_EBOOT_ERR_NONE) goto fail; erofs_iterate_dir (fdiro, erofs_dir_iter, &ctx); fail: if (fdiro != &ctx.data->inode) VasEBoot_free (fdiro); VasEBoot_free (ctx.data); return VasEBoot_errno; } static VasEBoot_err_t VasEBoot_erofs_open (VasEBoot_file_t file, const char *name) { struct VasEBoot_erofs_data *data; struct VasEBoot_fshelp_node *fdiro = NULL; VasEBoot_err_t err; data = erofs_mount (file->device->disk, true); if (data == NULL) { err = VasEBoot_errno; goto fail; } err = VasEBoot_fshelp_find_file (name, &data->inode, &fdiro, erofs_iterate_dir, erofs_read_symlink, VAS_EBOOT_FSHELP_REG); if (err != VAS_EBOOT_ERR_NONE) goto fail; if (fdiro->inode_loaded == false) { err = erofs_read_inode (data, fdiro); if (err != VAS_EBOOT_ERR_NONE) goto fail; } VasEBoot_memcpy (&data->inode, fdiro, sizeof (*fdiro)); VasEBoot_free (fdiro); file->data = data; file->size = erofs_inode_file_size (&data->inode); return VAS_EBOOT_ERR_NONE; fail: if (fdiro != &data->inode) VasEBoot_free (fdiro); VasEBoot_free (data); return err; } static VasEBoot_ssize_t VasEBoot_erofs_read (VasEBoot_file_t file, char *buf, VasEBoot_size_t len) { struct VasEBoot_erofs_data *data = file->data; struct VasEBoot_fshelp_node *inode = &data->inode; VasEBoot_off_t off = file->offset; VasEBoot_uint64_t ret = 0, file_size; VasEBoot_err_t err; if (inode->inode_loaded == false) { err = erofs_read_inode (data, inode); if (err != VAS_EBOOT_ERR_NONE) return -1; } file_size = erofs_inode_file_size (inode); if (off > file_size) { VasEBoot_error (VAS_EBOOT_ERR_IO, "read past EOF @ inode %" PRIuVAS_EBOOT_UINT64_T, inode->ino); return -1; } if (off == file_size) return 0; if (off + len > file_size) len = file_size - off; err = erofs_read_raw_data (inode, (VasEBoot_uint8_t *) buf, len, off, &ret); if (err != VAS_EBOOT_ERR_NONE) return -1; return ret; } static VasEBoot_err_t VasEBoot_erofs_close (VasEBoot_file_t file) { VasEBoot_free (file->data); return VAS_EBOOT_ERR_NONE; } static VasEBoot_err_t VasEBoot_erofs_uuid (VasEBoot_device_t device, char **uuid) { struct VasEBoot_erofs_data *data; data = erofs_mount (device->disk, false); if (data == NULL) { *uuid = NULL; return VasEBoot_errno; } *uuid = VasEBoot_xasprintf ("%pG", &data->sb.uuid); VasEBoot_free (data); return VAS_EBOOT_ERR_NONE; } static VasEBoot_err_t VasEBoot_erofs_label (VasEBoot_device_t device, char **label) { struct VasEBoot_erofs_data *data; data = erofs_mount (device->disk, false); if (data == NULL) { *label = NULL; return VasEBoot_errno; } *label = VasEBoot_strndup ((char *) data->sb.volume_name, sizeof (data->sb.volume_name)); VasEBoot_free (data); if (*label == NULL) return VasEBoot_errno; return VAS_EBOOT_ERR_NONE; } static VasEBoot_err_t VasEBoot_erofs_mtime (VasEBoot_device_t device, VasEBoot_int64_t *tm) { struct VasEBoot_erofs_data *data; data = erofs_mount (device->disk, false); if (data == NULL) { *tm = 0; return VasEBoot_errno; } *tm = VasEBoot_le_to_cpu64 (data->sb.build_time); VasEBoot_free (data); return VAS_EBOOT_ERR_NONE; } static struct VasEBoot_fs VasEBoot_erofs_fs = { .name = "erofs", .fs_dir = VasEBoot_erofs_dir, .fs_open = VasEBoot_erofs_open, .fs_read = VasEBoot_erofs_read, .fs_close = VasEBoot_erofs_close, .fs_uuid = VasEBoot_erofs_uuid, .fs_label = VasEBoot_erofs_label, .fs_mtime = VasEBoot_erofs_mtime, #ifdef VAS_EBOOT_UTIL .reserved_first_sector = 1, .blocklist_install = 0, #endif .next = 0, }; VAS_EBOOT_MOD_INIT (erofs) { VasEBoot_erofs_fs.mod = mod; VasEBoot_fs_register (&VasEBoot_erofs_fs); } VAS_EBOOT_MOD_FINI (erofs) { VasEBoot_fs_unregister (&VasEBoot_erofs_fs); }