diff --git a/Makefile.util.def b/Makefile.util.def index f3cd1d234..8eec8d0f7 100644 --- a/Makefile.util.def +++ b/Makefile.util.def @@ -65,6 +65,7 @@ library = { common = grub-core/fs/ntfscomp.c; common = grub-core/fs/reiserfs.c; common = grub-core/fs/sfs.c; + common = grub-core/fs/squash4.c; common = grub-core/fs/tar.c; common = grub-core/fs/udf.c; common = grub-core/fs/ufs2.c; diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def index c62d7d12f..583218372 100644 --- a/grub-core/Makefile.core.def +++ b/grub-core/Makefile.core.def @@ -983,6 +983,11 @@ module = { common = fs/sfs.c; }; +module = { + name = squash4; + common = fs/squash4.c; +}; + module = { name = tar; common = fs/tar.c; diff --git a/grub-core/fs/squash4.c b/grub-core/fs/squash4.c new file mode 100644 index 000000000..889f8b94c --- /dev/null +++ b/grub-core/fs/squash4.c @@ -0,0 +1,384 @@ +/* squash4.c - SquashFS */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2010 Free Software Foundation, Inc. + * + * GRUB 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. + * + * GRUB 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 GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + object format Pointed by + superblock RAW Fixed offset (0) + data RAW ? Fixed offset (60) + inode table Chunk superblock + dir table Chunk superblock + unk3 Chunk unk1 + unk1 RAW, Chunk superblock + unk2 RAW superblock + UID/GID Chunk exttblptr + exttblptr RAW superblock + + UID/GID table is the array ot uint32_t + unk1 contains pointer to unk3 followed by some chunk. + unk2 containts one uint64_t +*/ + +struct grub_squash_super +{ + grub_uint32_t magic; +#define SQUASH_MAGIC 0x73717368 + grub_uint32_t dummy1; + grub_uint32_t creation_time; + grub_uint32_t dummy2; + grub_uint32_t dummy3[4]; + grub_uint32_t dummy4[2]; + grub_uint64_t total_size; + grub_uint64_t exttbloffset; + grub_uint32_t dummy5[2]; + grub_uint64_t inodeoffset; + grub_uint64_t diroffset; + grub_uint64_t unk1offset; + grub_uint64_t unk2offset; +}; + + +/* Chunk-based */ +struct grub_squash_inode +{ + /* Same values as direlem types. */ + grub_uint16_t type; + grub_uint16_t dummy1[3]; + grub_uint32_t mtime; + grub_uint16_t dummy2[6]; + grub_uint32_t offset; + grub_uint16_t size; + grub_uint16_t dummy3; +} __attribute__ ((packed)); + +/* Chunk-based. */ +struct grub_squash_dirent_header +{ + /* Actually the value is the number of elements - 1. */ + grub_uint16_t nelems; + grub_uint16_t dummy[5]; +}; + +struct grub_squash_dirent +{ + grub_uint16_t ino; + grub_uint16_t dummy; + grub_uint16_t type; +#define SQUASH_TYPE_DIR 1 +#define SQUASH_TYPE_REGULAR 2 + /* Actually the value is the length of name - 1. */ + grub_uint16_t namelen; + char name[0]; +}; + +#define SQUASH_CHUNK_SIZE 0x2000 +#define SQUASH_CHUNK_FLAGS 0x8000 +#define SQUASH_CHUNK_UNCOMPRESSED 0x8000 + +static grub_err_t +read_chunk (grub_disk_t disk, void *buf, grub_size_t len, + grub_uint64_t chunk_start, grub_uint64_t offset) +{ + while (len > 0) + { + grub_uint64_t csize; + grub_uint16_t d; + grub_err_t err; + while (1) + { + err = grub_disk_read (disk, chunk_start >> GRUB_DISK_SECTOR_BITS, + chunk_start & (GRUB_DISK_SECTOR_SIZE - 1), + sizeof (d), &d); + if (err) + return err; + if (offset < SQUASH_CHUNK_SIZE) + break; + offset -= SQUASH_CHUNK_SIZE; + chunk_start += 2 + (grub_le_to_cpu16 (d) & ~SQUASH_CHUNK_FLAGS); + } + + csize = SQUASH_CHUNK_SIZE - offset; + if (csize > len) + csize = len; + + if (grub_le_to_cpu16 (d) & SQUASH_CHUNK_UNCOMPRESSED) + { + grub_disk_addr_t a = chunk_start + 2 + offset; + err = grub_disk_read (disk, (a >> GRUB_DISK_SECTOR_BITS), + a & (GRUB_DISK_SECTOR_SIZE - 1), + csize, buf); + if (err) + return err; + } + else + { + return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "compression not implemented yet"); + } + len -= csize; + offset += csize; + buf = (char *) buf + csize; + } + return GRUB_ERR_NONE; +} + +struct grub_squash_data +{ + grub_disk_t disk; + struct grub_squash_super sb; + struct grub_squash_inode ino; +}; + +struct grub_fshelp_node +{ + struct grub_squash_data *data; + struct grub_squash_inode ino; +}; + +static struct grub_squash_data * +squash_mount (grub_disk_t disk) +{ + struct grub_squash_super sb; + grub_err_t err; + struct grub_squash_data *data; + + err = grub_disk_read (disk, 0, 0, sizeof (sb), &sb); + if (grub_errno == GRUB_ERR_OUT_OF_RANGE) + grub_error (GRUB_ERR_BAD_FS, "not a squash4"); + if (err) + return NULL; + if (grub_le_to_cpu32 (sb.magic) != SQUASH_MAGIC) + { + grub_error (GRUB_ERR_BAD_FS, "not squash4"); + return NULL; + } + data = grub_malloc (sizeof (*data)); + if (!data) + return NULL; + data->sb = sb; + data->disk = disk; + + return data; +} + +static int +grub_squash_iterate_dir (grub_fshelp_node_t dir, + int NESTED_FUNC_ATTR + (*hook) (const char *filename, + enum grub_fshelp_filetype filetype, + grub_fshelp_node_t node)) +{ + grub_uint32_t off = grub_le_to_cpu32 (dir->ino.offset) >> 16; + /* FIXME: determine this. */ + unsigned numheaders = 1; + unsigned i, j; + for (j = 0; j < numheaders; j++) + { + struct grub_squash_dirent_header dh; + grub_err_t err; + + err = read_chunk (dir->data->disk, &dh, sizeof (dh), + grub_le_to_cpu64 (dir->data->sb.diroffset), off); + if (err) + return 0; + off += sizeof (dh); + for (i = 0; i < (unsigned) grub_le_to_cpu16 (dh.nelems) + 1; i++) + { + char *buf; + int r; + struct grub_fshelp_node *node; + enum grub_fshelp_filetype filetype = GRUB_FSHELP_REG; + struct grub_squash_dirent di; + struct grub_squash_inode ino; + + err = read_chunk (dir->data->disk, &di, sizeof (di), + grub_le_to_cpu64 (dir->data->sb.diroffset), off); + if (err) + return 0; + off += sizeof (di); + + err = read_chunk (dir->data->disk, &ino, sizeof (ino), + grub_le_to_cpu64 (dir->data->sb.inodeoffset), + grub_le_to_cpu16 (di.ino)); + if (err) + return 0; + + buf = grub_malloc (grub_le_to_cpu16 (di.namelen) + 2); + if (!buf) + return 0; + err = read_chunk (dir->data->disk, buf, + grub_le_to_cpu16 (di.namelen) + 1, + grub_le_to_cpu64 (dir->data->sb.diroffset), off); + if (err) + return 0; + + off += grub_le_to_cpu16 (di.namelen) + 1; + buf[grub_le_to_cpu16 (di.namelen) + 1] = 0; + if (grub_le_to_cpu16 (ino.type) == SQUASH_TYPE_DIR) + filetype = GRUB_FSHELP_DIR; + node = grub_malloc (sizeof (*node)); + if (! node) + return 0; + *node = *dir; + node->ino = ino; + r = hook (buf, filetype, node); + + grub_free (buf); + if (r) + return r; + } + } + return 0; +} + +static grub_err_t +make_root_node (struct grub_squash_data *data, struct grub_fshelp_node *root) +{ + /* FIXME */ + grub_memset (root, 0, sizeof (*root)); + root->data = data; + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_squash_dir (grub_device_t device, const char *path, + int (*hook) (const char *filename, + const struct grub_dirhook_info *info)) +{ + auto int NESTED_FUNC_ATTR iterate (const char *filename, + enum grub_fshelp_filetype filetype, + grub_fshelp_node_t node); + + int NESTED_FUNC_ATTR iterate (const char *filename, + enum grub_fshelp_filetype filetype, + grub_fshelp_node_t node __attribute__ ((unused))) + { + struct grub_dirhook_info info; + grub_memset (&info, 0, sizeof (info)); + info.dir = ((filetype & GRUB_FSHELP_TYPE_MASK) == GRUB_FSHELP_DIR); + return hook (filename, &info); + } + + struct grub_squash_data *data = 0; + struct grub_fshelp_node *fdiro = 0; + struct grub_fshelp_node root; + grub_err_t err; + + data = squash_mount (device->disk); + if (! data) + return grub_errno; + + err = make_root_node (data, &root); + if (err) + return err; + + grub_fshelp_find_file (path, &root, &fdiro, grub_squash_iterate_dir, + NULL, GRUB_FSHELP_DIR); + if (!grub_errno) + grub_squash_iterate_dir (fdiro, iterate); + + grub_free (data); + + return grub_errno; +} + +static grub_err_t +grub_squash_open (struct grub_file *file, const char *name) +{ + struct grub_squash_data *data = 0; + struct grub_fshelp_node *fdiro = 0; + struct grub_fshelp_node root; + grub_err_t err; + + data = squash_mount (file->device->disk); + if (! data) + return grub_errno; + + err = make_root_node (data, &root); + if (err) + return err; + + grub_fshelp_find_file (name, &root, &fdiro, grub_squash_iterate_dir, + NULL, GRUB_FSHELP_REG); + if (grub_errno) + { + grub_free (data); + return grub_errno; + } + file->data = data; + data->ino = fdiro->ino; + file->size = grub_le_to_cpu16 (fdiro->ino.size); + return GRUB_ERR_NONE; +} + +static grub_ssize_t +grub_squash_read (grub_file_t file, char *buf, grub_size_t len) +{ + grub_err_t err; + grub_uint64_t a; + struct grub_squash_data *data = file->data; + + a = sizeof (struct grub_squash_super) + grub_le_to_cpu32 (data->ino.offset); + + err = grub_disk_read (file->device->disk, a >> GRUB_DISK_SECTOR_BITS, + a & (GRUB_DISK_SECTOR_SIZE - 1), len, buf); + if (err) + return -1; + return len; +} + +static grub_err_t +grub_squash_close (grub_file_t file) +{ + grub_free (file->data); + return GRUB_ERR_NONE; +} + +static struct grub_fs grub_squash_fs = + { + .name = "squash4", + .dir = grub_squash_dir, + .open = grub_squash_open, + .read = grub_squash_read, + .close = grub_squash_close, +#ifdef GRUB_UTIL + .reserved_first_sector = 0, +#endif + .next = 0 + }; + +GRUB_MOD_INIT(squash4) +{ + grub_fs_register (&grub_squash_fs); +} + +GRUB_MOD_FINI(squash4) +{ + grub_fs_unregister (&grub_squash_fs); +} +