mirror of
https://github.com/jamesstringer90/appsandbox.git
synced 2026-10-22 11:03:15 -05:00
143 lines
5.3 KiB
C
143 lines
5.3 KiB
C
/* squashfs.h - read-only squashfs walker.
|
|
*
|
|
* Spec reference:
|
|
* https://dr-emann.github.io/squashfs/squashfs.html
|
|
*
|
|
* Format summary (all little-endian):
|
|
*
|
|
* superblock (96 bytes at offset 0)
|
|
* - magic 0x73717368 ("hsqs")
|
|
* - compressor: 1=GZIP 2=LZMA 3=LZO 4=XZ 5=LZ4 6=ZSTD
|
|
* - block_size (data block size, e.g. 128 KiB)
|
|
* - inode_table_start, directory_table_start
|
|
* - fragment_table_start, lookup_table_start
|
|
* - id_table_start, xattr_id_table_start
|
|
*
|
|
* tables are stored as a chain of "metadata blocks":
|
|
* - 2-byte header: bit15 = is_uncompressed, bits 14..0 = on-disk length
|
|
* - up to 8 KiB of (possibly compressed) data which decompresses to <= 8 KiB
|
|
*
|
|
* inodes are variable-size records inside the inode metadata stream.
|
|
* The first inode is at the offset specified by sb.root_inode_ref:
|
|
* high 48 bits = block start (offset relative to inode_table_start)
|
|
* low 16 bits = byte offset within the *decompressed* block
|
|
*
|
|
* directories are entries inside the directory metadata stream, grouped
|
|
* by header (shared inode_block, inode_base, type).
|
|
*
|
|
* data blocks are stored contiguously, indexed by per-file block lists
|
|
* in the inode. The last partial block may be a "fragment" packed with
|
|
* others' tails into a fragment block.
|
|
*/
|
|
#ifndef POC_SQUASHFS_H
|
|
#define POC_SQUASHFS_H
|
|
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <windows.h>
|
|
|
|
#define SQFS_MAGIC 0x73717368u
|
|
#define SQFS_METADATA_SIZE 8192u
|
|
|
|
enum sqfs_compressor {
|
|
SQFS_COMP_GZIP = 1,
|
|
SQFS_COMP_LZMA = 2,
|
|
SQFS_COMP_LZO = 3,
|
|
SQFS_COMP_XZ = 4,
|
|
SQFS_COMP_LZ4 = 5,
|
|
SQFS_COMP_ZSTD = 6
|
|
};
|
|
|
|
#pragma pack(push, 1)
|
|
typedef struct {
|
|
uint32_t magic;
|
|
uint32_t inode_count;
|
|
uint32_t mod_time;
|
|
uint32_t block_size;
|
|
uint32_t fragment_entry_count;
|
|
uint16_t compression_id;
|
|
uint16_t block_log;
|
|
uint16_t flags;
|
|
uint16_t id_count;
|
|
uint16_t version_major;
|
|
uint16_t version_minor;
|
|
uint64_t root_inode_ref;
|
|
uint64_t bytes_used;
|
|
uint64_t id_table_start;
|
|
uint64_t xattr_id_table_start;
|
|
uint64_t inode_table_start;
|
|
uint64_t directory_table_start;
|
|
uint64_t fragment_table_start;
|
|
uint64_t lookup_table_start;
|
|
} sqfs_superblock_t;
|
|
#pragma pack(pop)
|
|
|
|
/* Open a squashfs file. Caller passes a wide path. Returns 0 on success
|
|
* and fills *out_sb. The file handle stays inside the opaque context. */
|
|
typedef struct sqfs_ctx sqfs_ctx_t;
|
|
|
|
sqfs_ctx_t *sqfs_open(const wchar_t *path);
|
|
void sqfs_close(sqfs_ctx_t *ctx);
|
|
|
|
/* Accessors. */
|
|
const sqfs_superblock_t *sqfs_sb(const sqfs_ctx_t *ctx);
|
|
const char *sqfs_compressor_name(uint16_t id);
|
|
|
|
/* Read raw bytes from the underlying file. Returns 0 on success. */
|
|
int sqfs_read_raw(sqfs_ctx_t *ctx, uint64_t off, void *buf, size_t len);
|
|
|
|
/* Decompress one squashfs metadata block starting at file offset `off`.
|
|
* Writes up to SQFS_METADATA_SIZE bytes into `out`, returns the actual
|
|
* decompressed length via *out_len, advances *off past the block.
|
|
* Returns 0 on success. */
|
|
int sqfs_read_metadata_block(sqfs_ctx_t *ctx, uint64_t *off,
|
|
void *out, size_t *out_len);
|
|
|
|
/* Inode types from the squashfs spec. */
|
|
enum sqfs_inode_type {
|
|
SQFS_DIR_TYPE = 1, SQFS_REG_TYPE = 2, SQFS_SYM_TYPE = 3,
|
|
SQFS_BLK_TYPE = 4, SQFS_CHR_TYPE = 5, SQFS_FIFO_TYPE = 6,
|
|
SQFS_SOCK_TYPE = 7,
|
|
SQFS_EDIR_TYPE = 8, SQFS_EREG_TYPE = 9, SQFS_ESYM_TYPE = 10,
|
|
SQFS_EBLK_TYPE = 11, SQFS_ECHR_TYPE = 12, SQFS_EFIFO_TYPE = 13,
|
|
SQFS_ESOCK_TYPE = 14
|
|
};
|
|
|
|
/* What gets handed to the walker callback. */
|
|
typedef struct {
|
|
const char *path; /* canonical absolute path, e.g. "/etc/hostname" */
|
|
uint16_t type; /* sqfs_inode_type */
|
|
uint16_t mode; /* unix mode bits (perms only, no type) */
|
|
uint32_t uid;
|
|
uint32_t gid;
|
|
uint32_t mtime;
|
|
uint64_t size; /* file size (regular), symlink target len, etc. */
|
|
uint32_t rdev; /* major/minor encoded for device nodes */
|
|
const char *symlink_target;/* non-NULL for symlinks; not null-terminated */
|
|
uint32_t symlink_target_size;
|
|
/* For regular files, set by the walker, used by the callback to fetch
|
|
* the bytes (deferred so the callback can stream / skip as needed). */
|
|
uint64_t _file_inode_off; /* internal: offset of file's inode in table */
|
|
uint32_t _xattr_idx;
|
|
} sqfs_entry_t;
|
|
|
|
/* Callback signature. Return 0 to continue, non-zero to abort traversal. */
|
|
typedef int (*sqfs_walk_cb_t)(const sqfs_entry_t *entry, void *user);
|
|
|
|
/* Walk the entire tree starting at root_inode_ref. Callback fires per
|
|
* entry in pre-order (parent before children). Returns 0 on success. */
|
|
int sqfs_walk(sqfs_ctx_t *ctx, sqfs_walk_cb_t cb, void *user);
|
|
|
|
/* Read the data blocks of a regular file given an entry from the walker.
|
|
* Allocates *out_data with malloc, sets *out_size. Caller frees.
|
|
* Designed for Phase 4 ingestion - never holds the whole file in memory
|
|
* for very large files... TODO streaming variant if Ubuntu has multi-GB
|
|
* files we can't fit. For now, simple bulk read. */
|
|
int sqfs_read_file(sqfs_ctx_t *ctx, const sqfs_entry_t *entry,
|
|
void **out_data, size_t *out_size);
|
|
|
|
int sqfs_read_capability(sqfs_ctx_t *ctx, const sqfs_entry_t *entry,
|
|
void **out_data, size_t *out_size);
|
|
|
|
#endif
|