/* SPDX-License-Identifier: GPL-3.0-or-later */ #include "meta_reader.h" #include #include #include #define SWAB16(x) x = le16toh(x) #define SWAB32(x) x = le32toh(x) #define SWAB64(x) x = le64toh(x) static int check_mode(sqfs_inode_t *inode) { switch (inode->mode & S_IFMT) { case S_IFSOCK: if (inode->type != SQFS_INODE_SOCKET && inode->type != SQFS_INODE_EXT_SOCKET) { goto fail_mismatch; } break; case S_IFLNK: if (inode->type != SQFS_INODE_SLINK && inode->type != SQFS_INODE_EXT_SLINK) { goto fail_mismatch; } break; case S_IFREG: if (inode->type != SQFS_INODE_FILE && inode->type != SQFS_INODE_EXT_FILE) { goto fail_mismatch; } break; case S_IFBLK: if (inode->type != SQFS_INODE_BDEV && inode->type != SQFS_INODE_EXT_BDEV) { goto fail_mismatch; } break; case S_IFDIR: if (inode->type != SQFS_INODE_DIR && inode->type != SQFS_INODE_EXT_DIR) { goto fail_mismatch; } break; case S_IFCHR: if (inode->type != SQFS_INODE_CDEV && inode->type != SQFS_INODE_EXT_CDEV) { goto fail_mismatch; } break; case S_IFIFO: if (inode->type != SQFS_INODE_FIFO && inode->type != SQFS_INODE_EXT_FIFO) { goto fail_mismatch; } break; default: fputs("Found inode with unknown file mode\n", stderr); return -1; } return 0; fail_mismatch: fputs("Found inode where type does not match mode\n", stderr); return -1; } static sqfs_inode_generic_t *read_inode_file(meta_reader_t *ir, sqfs_inode_t *base, size_t block_size) { sqfs_inode_generic_t *out; sqfs_inode_file_t file; size_t i, count; if (meta_reader_read(ir, &file, sizeof(file))) return NULL; SWAB32(file.blocks_start); SWAB32(file.fragment_index); SWAB32(file.fragment_offset); SWAB32(file.file_size); count = file.file_size / block_size; out = calloc(1, sizeof(*out) + count * sizeof(uint32_t)); if (out == NULL) { perror("reading extended file inode"); return NULL; } out->base = *base; out->data.file = file; out->block_sizes = (uint32_t *)out->extra; if (meta_reader_read(ir, out->block_sizes, count * sizeof(uint32_t))) { free(out); return NULL; } for (i = 0; i < count; ++i) SWAB32(out->block_sizes[i]); return out; } static sqfs_inode_generic_t *read_inode_file_ext(meta_reader_t *ir, sqfs_inode_t *base, size_t block_size) { sqfs_inode_file_ext_t file; sqfs_inode_generic_t *out; size_t i, count; if (meta_reader_read(ir, &file, sizeof(file))) return NULL; SWAB64(file.blocks_start); SWAB64(file.file_size); SWAB64(file.sparse); SWAB32(file.nlink); SWAB32(file.fragment_idx); SWAB32(file.fragment_offset); SWAB32(file.xattr_idx); count = file.file_size / block_size; out = calloc(1, sizeof(*out) + count * sizeof(uint32_t)); if (out == NULL) { perror("reading extended file inode"); return NULL; } out->base = *base; out->data.file_ext = file; out->block_sizes = (uint32_t *)out->extra; if (meta_reader_read(ir, out->block_sizes, count * sizeof(uint32_t))) { free(out); return NULL; } for (i = 0; i < count; ++i) SWAB32(out->block_sizes[i]); return out; } static sqfs_inode_generic_t *read_inode_slink(meta_reader_t *ir, sqfs_inode_t *base) { sqfs_inode_generic_t *out; sqfs_inode_slink_t slink; if (meta_reader_read(ir, &slink, sizeof(slink))) return NULL; SWAB32(slink.nlink); SWAB32(slink.target_size); out = calloc(1, sizeof(*out) + slink.target_size + 1); if (out == NULL) { perror("reading symlink inode"); return NULL; } out->slink_target = (char *)out->extra; out->base = *base; out->data.slink = slink; if (meta_reader_read(ir, out->slink_target, slink.target_size)) { free(out); return NULL; } return out; } static sqfs_inode_generic_t *read_inode_slink_ext(meta_reader_t *ir, sqfs_inode_t *base) { sqfs_inode_generic_t *out = read_inode_slink(ir, base); uint32_t xattr; if (out != NULL) { if (meta_reader_read(ir, &xattr, sizeof(xattr))) { free(out); return NULL; } out->data.slink_ext.xattr_idx = le32toh(xattr); } return out; } sqfs_inode_generic_t *meta_reader_read_inode(meta_reader_t *ir, sqfs_super_t *super, uint64_t block_start, size_t offset) { sqfs_inode_generic_t *out; sqfs_inode_t inode; /* read base inode */ block_start += super->inode_table_start; if (meta_reader_seek(ir, block_start, offset)) return NULL; if (meta_reader_read(ir, &inode, sizeof(inode))) return NULL; SWAB16(inode.type); SWAB16(inode.mode); SWAB16(inode.uid_idx); SWAB16(inode.gid_idx); SWAB32(inode.mod_time); SWAB32(inode.inode_number); if (check_mode(&inode)) return NULL; /* inode types where the size is variable */ switch (inode.type) { case SQFS_INODE_FILE: return read_inode_file(ir, &inode, super->block_size); case SQFS_INODE_SLINK: return read_inode_slink(ir, &inode); case SQFS_INODE_EXT_FILE: return read_inode_file_ext(ir, &inode, super->block_size); case SQFS_INODE_EXT_SLINK: return read_inode_slink_ext(ir, &inode); default: break; } /* everything else */ out = calloc(1, sizeof(*out)); if (out == NULL) { perror("reading symlink inode"); return NULL; } out->base = inode; switch (inode.type) { case SQFS_INODE_DIR: if (meta_reader_read(ir, &out->data.dir, sizeof(out->data.dir))) { goto fail_free; } SWAB32(out->data.dir.start_block); SWAB32(out->data.dir.nlink); SWAB16(out->data.dir.size); SWAB16(out->data.dir.offset); SWAB32(out->data.dir.parent_inode); break; case SQFS_INODE_BDEV: case SQFS_INODE_CDEV: if (meta_reader_read(ir, &out->data.dev, sizeof(out->data.dev))) { goto fail_free; } SWAB32(out->data.dev.nlink); SWAB32(out->data.dev.devno); break; case SQFS_INODE_FIFO: case SQFS_INODE_SOCKET: if (meta_reader_read(ir, &out->data.ipc, sizeof(out->data.ipc))) { goto fail_free; } SWAB32(out->data.ipc.nlink); break; case SQFS_INODE_EXT_DIR: if (meta_reader_read(ir, &out->data.dir_ext, sizeof(out->data.dir_ext))) { goto fail_free; } SWAB32(out->data.dir_ext.nlink); SWAB32(out->data.dir_ext.size); SWAB32(out->data.dir_ext.start_block); SWAB32(out->data.dir_ext.parent_inode); SWAB16(out->data.dir_ext.inodex_count); SWAB16(out->data.dir_ext.offset); SWAB32(out->data.dir_ext.xattr_idx); break; case SQFS_INODE_EXT_BDEV: case SQFS_INODE_EXT_CDEV: if (meta_reader_read(ir, &out->data.dev_ext, sizeof(out->data.dev_ext))) { goto fail_free; } SWAB32(out->data.dev_ext.nlink); SWAB32(out->data.dev_ext.devno); SWAB32(out->data.dev_ext.xattr_idx); break; case SQFS_INODE_EXT_FIFO: case SQFS_INODE_EXT_SOCKET: if (meta_reader_read(ir, &out->data.ipc_ext, sizeof(out->data.ipc_ext))) { goto fail_free; } SWAB32(out->data.ipc_ext.nlink); SWAB32(out->data.ipc_ext.xattr_idx); break; default: fputs("Unknown inode type found\n", stderr); goto fail_free; } return out; fail_free: free(out); return NULL; }