/* 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 #include #include "xz.h" #include "xz_stream.h" GRUB_MOD_LICENSE ("GPLv3+"); /* object format Pointed by superblock RAW Fixed offset (0) data RAW ? Fixed offset (60) inode table Chunk superblock dir table Chunk superblock fragment table 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 fragment table 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 block_size; grub_uint32_t dummy2; grub_uint16_t compression; grub_uint16_t dummy3; grub_uint64_t dummy4; grub_uint16_t root_ino_offset; grub_uint32_t root_ino_chunk; grub_uint16_t dummy5; grub_uint64_t total_size; grub_uint64_t exttbloffset; grub_uint64_t dummy6; grub_uint64_t inodeoffset; grub_uint64_t diroffset; grub_uint64_t unk1offset; grub_uint64_t unk2offset; } GRUB_PACKED; /* Chunk-based */ struct grub_squash_inode { /* Same values as direlem types. */ grub_uint16_t type; grub_uint16_t dummy[3]; grub_uint32_t mtime; grub_uint32_t dummy2; union { struct { grub_uint32_t chunk; grub_uint32_t fragment; grub_uint32_t offset; grub_uint32_t size; grub_uint32_t block_size[0]; } GRUB_PACKED file; struct { grub_uint64_t chunk; grub_uint64_t size; grub_uint32_t dummy1[3]; grub_uint32_t fragment; grub_uint32_t offset; grub_uint32_t dummy3; grub_uint32_t block_size[0]; } GRUB_PACKED long_file; struct { grub_uint32_t chunk; grub_uint32_t dummy; grub_uint16_t size; grub_uint16_t offset; } GRUB_PACKED dir; struct { grub_uint32_t dummy1; grub_uint32_t size; grub_uint32_t chunk; grub_uint32_t dummy2; grub_uint16_t dummy3; grub_uint16_t offset; } GRUB_PACKED long_dir; struct { grub_uint32_t dummy; grub_uint32_t namelen; char name[0]; } GRUB_PACKED symlink; } GRUB_PACKED; } GRUB_PACKED; struct grub_squash_cache_inode { struct grub_squash_inode ino; grub_disk_addr_t ino_chunk; grub_uint16_t ino_offset; grub_uint32_t *block_sizes; grub_disk_addr_t *cumulated_block_sizes; }; /* Chunk-based. */ struct grub_squash_dirent_header { /* Actually the value is the number of elements - 1. */ grub_uint32_t nelems; grub_uint32_t ino_chunk; grub_uint32_t dummy; } GRUB_PACKED; struct grub_squash_dirent { grub_uint16_t ino_offset; grub_uint16_t dummy; grub_uint16_t type; /* Actually the value is the length of name - 1. */ grub_uint16_t namelen; char name[0]; } GRUB_PACKED; enum { SQUASH_TYPE_DIR = 1, SQUASH_TYPE_REGULAR = 2, SQUASH_TYPE_SYMLINK = 3, SQUASH_TYPE_LONG_DIR = 8, SQUASH_TYPE_LONG_REGULAR = 9, }; struct grub_squash_frag_desc { grub_uint64_t offset; grub_uint32_t size; grub_uint32_t dummy; } GRUB_PACKED; enum { SQUASH_CHUNK_FLAGS = 0x8000, SQUASH_CHUNK_UNCOMPRESSED = 0x8000 }; enum { SQUASH_BLOCK_FLAGS = 0x1000000, SQUASH_BLOCK_UNCOMPRESSED = 0x1000000 }; enum { COMPRESSION_ZLIB = 1, COMPRESSION_LZO = 3, COMPRESSION_XZ = 4, }; #define SQUASH_CHUNK_SIZE 0x2000 #define XZBUFSIZ 0x2000 struct grub_squash_data { grub_disk_t disk; struct grub_squash_super sb; struct grub_squash_cache_inode ino; grub_uint64_t fragments; int log2_blksz; grub_size_t blksz; grub_ssize_t (*decompress) (char *inbuf, grub_size_t insize, grub_off_t off, char *outbuf, grub_size_t outsize, struct grub_squash_data *data); struct xz_dec *xzdec; char *xzbuf; }; struct grub_fshelp_node { struct grub_squash_data *data; struct grub_squash_inode ino; grub_size_t stsize; struct { grub_disk_addr_t ino_chunk; grub_uint16_t ino_offset; } stack[1]; }; static grub_err_t read_chunk (struct grub_squash_data *data, void *buf, grub_size_t len, grub_uint64_t chunk_start, grub_off_t offset) { while (len > 0) { grub_uint64_t csize; grub_uint16_t d; grub_err_t err; while (1) { err = grub_disk_read (data->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 (data->disk, (a >> GRUB_DISK_SECTOR_BITS), a & (GRUB_DISK_SECTOR_SIZE - 1), csize, buf); if (err) return err; } else { char *tmp; grub_size_t bsize = grub_le_to_cpu16 (d) & ~SQUASH_CHUNK_FLAGS; grub_disk_addr_t a = chunk_start + 2; tmp = grub_malloc (bsize); if (!tmp) return grub_errno; /* FIXME: buffer uncompressed data. */ err = grub_disk_read (data->disk, (a >> GRUB_DISK_SECTOR_BITS), a & (GRUB_DISK_SECTOR_SIZE - 1), bsize, tmp); if (err) { grub_free (tmp); return err; } if (data->decompress (tmp, bsize, offset, buf, csize, data) < 0) { grub_free (tmp); return grub_errno; } grub_free (tmp); } len -= csize; offset += csize; buf = (char *) buf + csize; } return GRUB_ERR_NONE; } static grub_ssize_t zlib_decompress (char *inbuf, grub_size_t insize, grub_off_t off, char *outbuf, grub_size_t outsize, struct grub_squash_data *data __attribute__ ((unused))) { return grub_zlib_decompress (inbuf, insize, off, outbuf, outsize); } static grub_ssize_t lzo_decompress (char *inbuf, grub_size_t insize, grub_off_t off, char *outbuf, grub_size_t len, struct grub_squash_data *data) { lzo_uint usize = data->blksz; grub_uint8_t *udata; if (usize < 8192) usize = 8192; udata = grub_malloc (usize); if (!udata) return -1; if (lzo1x_decompress_safe ((grub_uint8_t *) inbuf, insize, udata, &usize, NULL) != LZO_E_OK) { grub_error (GRUB_ERR_BAD_FS, "incorrect compressed chunk"); grub_free (udata); return -1; } grub_memcpy (outbuf, udata + off, len); grub_free (udata); return len; } static grub_ssize_t xz_decompress (char *inbuf, grub_size_t insize, grub_off_t off, char *outbuf, grub_size_t len, struct grub_squash_data *data) { grub_size_t ret = 0; grub_off_t pos = 0; struct xz_buf buf; xz_dec_reset (data->xzdec); buf.in = (grub_uint8_t *) inbuf; buf.in_pos = 0; buf.in_size = insize; buf.out = (grub_uint8_t *) data->xzbuf; buf.out_pos = 0; buf.out_size = XZBUFSIZ; while (len) { enum xz_ret xzret; buf.out_pos = 0; xzret = xz_dec_run (data->xzdec, &buf); if (xzret != XZ_OK && xzret != XZ_STREAM_END) { grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, "invalid xz chunk"); return -1; } if (pos + buf.out_pos >= off) { grub_ssize_t outoff = pos - off; grub_size_t l; if (outoff >= 0) { l = buf.out_pos; if (l > len) l = len; grub_memcpy (outbuf + outoff, buf.out, l); } else { outoff = -outoff; l = buf.out_pos - outoff; if (l > len) l = len; grub_memcpy (outbuf, buf.out + outoff, l); } ret += l; len -= l; } pos += buf.out_pos; if (xzret == XZ_STREAM_END) break; } return ret; } 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; grub_uint64_t frag; 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 (sb.magic != grub_cpu_to_le32_compile_time (SQUASH_MAGIC) || sb.block_size == 0 || ((sb.block_size - 1) & sb.block_size)) { grub_error (GRUB_ERR_BAD_FS, "not squash4"); return NULL; } err = grub_disk_read (disk, grub_le_to_cpu64 (sb.unk1offset) >> GRUB_DISK_SECTOR_BITS, grub_le_to_cpu64 (sb.unk1offset) & (GRUB_DISK_SECTOR_SIZE - 1), sizeof (frag), &frag); if (grub_errno == GRUB_ERR_OUT_OF_RANGE) grub_error (GRUB_ERR_BAD_FS, "not a squash4"); if (err) return NULL; data = grub_zalloc (sizeof (*data)); if (!data) return NULL; data->sb = sb; data->disk = disk; data->fragments = grub_le_to_cpu64 (frag); switch (sb.compression) { case grub_cpu_to_le16_compile_time (COMPRESSION_ZLIB): data->decompress = zlib_decompress; break; case grub_cpu_to_le16_compile_time (COMPRESSION_LZO): data->decompress = lzo_decompress; break; case grub_cpu_to_le16_compile_time (COMPRESSION_XZ): data->decompress = xz_decompress; data->xzbuf = grub_malloc (XZBUFSIZ); if (!data->xzbuf) { grub_free (data); return NULL; } data->xzdec = xz_dec_init (1 << 16); if (!data->xzdec) { grub_free (data->xzbuf); grub_free (data); return NULL; } break; default: grub_free (data); grub_error (GRUB_ERR_BAD_FS, "unsupported compression %d", grub_le_to_cpu16 (sb.compression)); return NULL; } data->blksz = grub_le_to_cpu32 (data->sb.block_size); for (data->log2_blksz = 0; (1U << data->log2_blksz) < data->blksz; data->log2_blksz++); return data; } static char * grub_squash_read_symlink (grub_fshelp_node_t node) { char *ret; grub_err_t err; ret = grub_malloc (grub_le_to_cpu32 (node->ino.symlink.namelen) + 1); err = read_chunk (node->data, ret, grub_le_to_cpu32 (node->ino.symlink.namelen), grub_le_to_cpu64 (node->data->sb.inodeoffset) + node->stack[node->stsize - 1].ino_chunk, node->stack[node->stsize - 1].ino_offset + (node->ino.symlink.name - (char *) &node->ino)); if (err) { grub_free (ret); return NULL; } ret[grub_le_to_cpu32 (node->ino.symlink.namelen)] = 0; return ret; } static int grub_squash_iterate_dir (grub_fshelp_node_t dir, grub_fshelp_iterate_dir_hook_t hook, void *hook_data) { grub_uint32_t off; grub_uint32_t endoff; grub_uint64_t chunk; unsigned i; /* FIXME: why - 3 ? */ switch (dir->ino.type) { case grub_cpu_to_le16_compile_time (SQUASH_TYPE_DIR): off = grub_le_to_cpu16 (dir->ino.dir.offset); endoff = grub_le_to_cpu16 (dir->ino.dir.size) + off - 3; chunk = grub_le_to_cpu32 (dir->ino.dir.chunk); break; case grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_DIR): off = grub_le_to_cpu16 (dir->ino.long_dir.offset); endoff = grub_le_to_cpu16 (dir->ino.long_dir.size) + off - 3; chunk = grub_le_to_cpu32 (dir->ino.long_dir.chunk); break; default: grub_error (GRUB_ERR_BAD_FS, "unexpected ino type 0x%x", grub_le_to_cpu16 (dir->ino.type)); return 0; } { grub_fshelp_node_t node; node = grub_malloc (sizeof (*node) + dir->stsize * sizeof (dir->stack[0])); if (!node) return 0; grub_memcpy (node, dir, sizeof (*node) + dir->stsize * sizeof (dir->stack[0])); if (hook (".", GRUB_FSHELP_DIR, node, hook_data)) return 1; if (dir->stsize != 1) { grub_err_t err; node = grub_malloc (sizeof (*node) + dir->stsize * sizeof (dir->stack[0])); if (!node) return 0; grub_memcpy (node, dir, sizeof (*node) + dir->stsize * sizeof (dir->stack[0])); node->stsize--; err = read_chunk (dir->data, &node->ino, sizeof (node->ino), grub_le_to_cpu64 (dir->data->sb.inodeoffset) + node->stack[node->stsize - 1].ino_chunk, node->stack[node->stsize - 1].ino_offset); if (err) return 0; if (hook ("..", GRUB_FSHELP_DIR, node, hook_data)) return 1; } } while (off < endoff) { struct grub_squash_dirent_header dh; grub_err_t err; err = read_chunk (dir->data, &dh, sizeof (dh), grub_le_to_cpu64 (dir->data->sb.diroffset) + chunk, off); if (err) return 0; off += sizeof (dh); for (i = 0; i < (unsigned) grub_le_to_cpu32 (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, &di, sizeof (di), grub_le_to_cpu64 (dir->data->sb.diroffset) + chunk, off); if (err) return 0; off += sizeof (di); err = read_chunk (dir->data, &ino, sizeof (ino), grub_le_to_cpu64 (dir->data->sb.inodeoffset) + grub_le_to_cpu32 (dh.ino_chunk), grub_cpu_to_le16 (di.ino_offset)); if (err) return 0; buf = grub_malloc (grub_le_to_cpu16 (di.namelen) + 2); if (!buf) return 0; err = read_chunk (dir->data, buf, grub_le_to_cpu16 (di.namelen) + 1, grub_le_to_cpu64 (dir->data->sb.diroffset) + chunk, 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 (di.type) == SQUASH_TYPE_DIR) filetype = GRUB_FSHELP_DIR; if (grub_le_to_cpu16 (di.type) == SQUASH_TYPE_SYMLINK) filetype = GRUB_FSHELP_SYMLINK; node = grub_malloc (sizeof (*node) + (dir->stsize + 1) * sizeof (dir->stack[0])); if (! node) return 0; grub_memcpy (node, dir, sizeof (*node) + dir->stsize * sizeof (dir->stack[0])); node->ino = ino; node->stack[node->stsize].ino_chunk = grub_le_to_cpu32 (dh.ino_chunk); node->stack[node->stsize].ino_offset = grub_le_to_cpu16 (di.ino_offset); node->stsize++; r = hook (buf, filetype, node, hook_data); 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) { grub_memset (root, 0, sizeof (*root)); root->data = data; root->stsize = 1; root->stack[0].ino_chunk = grub_le_to_cpu32 (data->sb.root_ino_chunk); root->stack[0].ino_offset = grub_cpu_to_le16 (data->sb.root_ino_offset); return read_chunk (data, &root->ino, sizeof (root->ino), grub_le_to_cpu64 (data->sb.inodeoffset) + root->stack[0].ino_chunk, root->stack[0].ino_offset); } static void squash_unmount (struct grub_squash_data *data) { if (data->xzdec) xz_dec_end (data->xzdec); grub_free (data->xzbuf); grub_free (data->ino.cumulated_block_sizes); grub_free (data->ino.block_sizes); grub_free (data); } /* Context for grub_squash_dir. */ struct grub_squash_dir_ctx { grub_fs_dir_hook_t hook; void *hook_data; }; /* Helper for grub_squash_dir. */ static int grub_squash_dir_iter (const char *filename, enum grub_fshelp_filetype filetype, grub_fshelp_node_t node, void *data) { struct grub_squash_dir_ctx *ctx = data; struct grub_dirhook_info info; grub_memset (&info, 0, sizeof (info)); info.dir = ((filetype & GRUB_FSHELP_TYPE_MASK) == GRUB_FSHELP_DIR); info.mtimeset = 1; info.mtime = grub_le_to_cpu32 (node->ino.mtime); grub_free (node); return ctx->hook (filename, &info, ctx->hook_data); } static grub_err_t grub_squash_dir (grub_device_t device, const char *path, grub_fs_dir_hook_t hook, void *hook_data) { struct grub_squash_dir_ctx ctx = { hook, hook_data }; 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, grub_squash_read_symlink, GRUB_FSHELP_DIR); if (!grub_errno) grub_squash_iterate_dir (fdiro, grub_squash_dir_iter, &ctx); squash_unmount (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, grub_squash_read_symlink, GRUB_FSHELP_REG); if (grub_errno) { squash_unmount (data); return grub_errno; } file->data = data; data->ino.ino = fdiro->ino; data->ino.block_sizes = NULL; data->ino.cumulated_block_sizes = NULL; data->ino.ino_chunk = fdiro->stack[fdiro->stsize - 1].ino_chunk; data->ino.ino_offset = fdiro->stack[fdiro->stsize - 1].ino_offset; switch (fdiro->ino.type) { case grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_REGULAR): file->size = grub_le_to_cpu64 (fdiro->ino.long_file.size); break; case grub_cpu_to_le16_compile_time (SQUASH_TYPE_REGULAR): file->size = grub_le_to_cpu32 (fdiro->ino.file.size); break; default: { grub_uint16_t type = grub_le_to_cpu16 (fdiro->ino.type); grub_free (fdiro); squash_unmount (data); return grub_error (GRUB_ERR_BAD_FS, "unexpected ino type 0x%x", type); } } grub_free (fdiro); return GRUB_ERR_NONE; } static grub_ssize_t direct_read (struct grub_squash_data *data, struct grub_squash_cache_inode *ino, grub_off_t off, char *buf, grub_size_t len) { grub_err_t err; grub_off_t cumulated_uncompressed_size = 0; grub_uint64_t a = 0; grub_size_t i; grub_size_t origlen = len; switch (ino->ino.type) { case grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_REGULAR): a = grub_le_to_cpu64 (ino->ino.long_file.chunk); break; case grub_cpu_to_le16_compile_time (SQUASH_TYPE_REGULAR): a = grub_le_to_cpu32 (ino->ino.file.chunk); break; } if (!ino->block_sizes) { grub_off_t total_size = 0; grub_size_t total_blocks; grub_size_t block_offset = 0; switch (ino->ino.type) { case grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_REGULAR): total_size = grub_le_to_cpu64 (ino->ino.long_file.size); block_offset = ((char *) &ino->ino.long_file.block_size - (char *) &ino->ino); break; case grub_cpu_to_le16_compile_time (SQUASH_TYPE_REGULAR): total_size = grub_le_to_cpu32 (ino->ino.file.size); block_offset = ((char *) &ino->ino.file.block_size - (char *) &ino->ino); break; } total_blocks = ((total_size + data->blksz - 1) >> data->log2_blksz); ino->block_sizes = grub_malloc (total_blocks * sizeof (ino->block_sizes[0])); ino->cumulated_block_sizes = grub_malloc (total_blocks * sizeof (ino->cumulated_block_sizes[0])); if (!ino->block_sizes || !ino->cumulated_block_sizes) { grub_free (ino->block_sizes); grub_free (ino->cumulated_block_sizes); ino->block_sizes = 0; ino->cumulated_block_sizes = 0; return -1; } err = read_chunk (data, ino->block_sizes, total_blocks * sizeof (ino->block_sizes[0]), grub_le_to_cpu64 (data->sb.inodeoffset) + ino->ino_chunk, ino->ino_offset + block_offset); if (err) { grub_free (ino->block_sizes); grub_free (ino->cumulated_block_sizes); ino->block_sizes = 0; ino->cumulated_block_sizes = 0; return -1; } ino->cumulated_block_sizes[0] = 0; for (i = 1; i < total_blocks; i++) ino->cumulated_block_sizes[i] = ino->cumulated_block_sizes[i - 1] + (grub_le_to_cpu32 (ino->block_sizes[i - 1]) & ~SQUASH_BLOCK_FLAGS); } if (a == 0) a = sizeof (struct grub_squash_super); i = off >> data->log2_blksz; cumulated_uncompressed_size = data->blksz * (grub_disk_addr_t) i; while (cumulated_uncompressed_size < off + len) { grub_size_t boff, curread; boff = off - cumulated_uncompressed_size; curread = data->blksz - boff; if (curread > len) curread = len; if (!ino->block_sizes[i]) { /* Sparse block */ grub_memset (buf, '\0', curread); } else if (!(ino->block_sizes[i] & grub_cpu_to_le32_compile_time (SQUASH_BLOCK_UNCOMPRESSED))) { char *block; grub_size_t csize; csize = grub_le_to_cpu32 (ino->block_sizes[i]) & ~SQUASH_BLOCK_FLAGS; block = grub_malloc (csize); if (!block) return -1; err = grub_disk_read (data->disk, (ino->cumulated_block_sizes[i] + a) >> GRUB_DISK_SECTOR_BITS, (ino->cumulated_block_sizes[i] + a) & (GRUB_DISK_SECTOR_SIZE - 1), csize, block); if (err) { grub_free (block); return -1; } if (data->decompress (block, csize, boff, buf, curread, data) != (grub_ssize_t) curread) { grub_free (block); if (!grub_errno) grub_error (GRUB_ERR_BAD_FS, "incorrect compressed chunk"); return -1; } grub_free (block); } else err = grub_disk_read (data->disk, (ino->cumulated_block_sizes[i] + a + boff) >> GRUB_DISK_SECTOR_BITS, (ino->cumulated_block_sizes[i] + a + boff) & (GRUB_DISK_SECTOR_SIZE - 1), curread, buf); if (err) return -1; off += curread; len -= curread; buf += curread; cumulated_uncompressed_size += grub_le_to_cpu32 (data->sb.block_size); i++; } return origlen; } static grub_ssize_t grub_squash_read (grub_file_t file, char *buf, grub_size_t len) { struct grub_squash_data *data = file->data; struct grub_squash_cache_inode *ino = &data->ino; grub_off_t off = file->offset; grub_err_t err; grub_uint64_t a, b; grub_uint32_t fragment = 0; int compressed = 0; struct grub_squash_frag_desc frag; grub_off_t direct_len; grub_uint64_t mask = grub_le_to_cpu32 (data->sb.block_size) - 1; grub_size_t orig_len = len; switch (ino->ino.type) { case grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_REGULAR): fragment = grub_le_to_cpu32 (ino->ino.long_file.fragment); break; case grub_cpu_to_le16_compile_time (SQUASH_TYPE_REGULAR): fragment = grub_le_to_cpu32 (ino->ino.file.fragment); break; } /* Squash may pack file tail as fragment. So read initial part directly and get tail from fragments */ direct_len = fragment == 0xffffffff ? file->size : file->size & ~mask; if (off < direct_len) { grub_size_t read_len = direct_len - off; grub_ssize_t res; if (read_len > len) read_len = len; res = direct_read (data, ino, off, buf, read_len); if ((grub_size_t) res != read_len) return -1; /* FIXME: is short read possible here? */ len -= read_len; if (!len) return read_len; buf += read_len; off = 0; } else off -= direct_len; err = read_chunk (data, &frag, sizeof (frag), data->fragments, sizeof (frag) * fragment); if (err) return -1; a = grub_le_to_cpu64 (frag.offset); compressed = !(frag.size & grub_cpu_to_le32_compile_time (SQUASH_BLOCK_UNCOMPRESSED)); if (ino->ino.type == grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_REGULAR)) b = grub_le_to_cpu32 (ino->ino.long_file.offset) + off; else b = grub_le_to_cpu32 (ino->ino.file.offset) + off; /* FIXME: cache uncompressed chunks. */ if (compressed) { char *block; block = grub_malloc (grub_le_to_cpu32 (frag.size)); if (!block) return -1; err = grub_disk_read (data->disk, a >> GRUB_DISK_SECTOR_BITS, a & (GRUB_DISK_SECTOR_SIZE - 1), grub_le_to_cpu32 (frag.size), block); if (err) { grub_free (block); return -1; } if (data->decompress (block, grub_le_to_cpu32 (frag.size), b, buf, len, data) != (grub_ssize_t) len) { grub_free (block); if (!grub_errno) grub_error (GRUB_ERR_BAD_FS, "incorrect compressed chunk"); return -1; } grub_free (block); } else { err = grub_disk_read (data->disk, (a + b) >> GRUB_DISK_SECTOR_BITS, (a + b) & (GRUB_DISK_SECTOR_SIZE - 1), len, buf); if (err) return -1; } return orig_len; } static grub_err_t grub_squash_close (grub_file_t file) { squash_unmount (file->data); return GRUB_ERR_NONE; } static grub_err_t grub_squash_mtime (grub_device_t dev, grub_int32_t *tm) { struct grub_squash_data *data = 0; data = squash_mount (dev->disk); if (! data) return grub_errno; *tm = grub_le_to_cpu32 (data->sb.creation_time); squash_unmount (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, .mtime = grub_squash_mtime, #ifdef GRUB_UTIL .reserved_first_sector = 0, .blocklist_install = 0, #endif .next = 0 }; GRUB_MOD_INIT(squash4) { grub_fs_register (&grub_squash_fs); } GRUB_MOD_FINI(squash4) { grub_fs_unregister (&grub_squash_fs); }