2N/A/* squash4.c - SquashFS */
2N/A/*
2N/A * GRUB -- GRand Unified Bootloader
2N/A * Copyright (C) 2010 Free Software Foundation, Inc.
2N/A *
2N/A * GRUB is free software: you can redistribute it and/or modify
2N/A * it under the terms of the GNU General Public License as published by
2N/A * the Free Software Foundation, either version 3 of the License, or
2N/A * (at your option) any later version.
2N/A *
2N/A * GRUB is distributed in the hope that it will be useful,
2N/A * but WITHOUT ANY WARRANTY; without even the implied warranty of
2N/A * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2N/A * GNU General Public License for more details.
2N/A *
2N/A * You should have received a copy of the GNU General Public License
2N/A * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
2N/A */
2N/A
2N/A#include <grub/err.h>
2N/A#include <grub/file.h>
2N/A#include <grub/mm.h>
2N/A#include <grub/misc.h>
2N/A#include <grub/disk.h>
2N/A#include <grub/dl.h>
2N/A#include <grub/types.h>
2N/A#include <grub/fshelp.h>
2N/A#include <grub/deflate.h>
2N/A#include <minilzo.h>
2N/A
2N/A#include "xz.h"
2N/A#include "xz_stream.h"
2N/A
2N/AGRUB_MOD_LICENSE ("GPLv3+");
2N/A
2N/A/*
2N/A object format Pointed by
2N/A superblock RAW Fixed offset (0)
2N/A data RAW ? Fixed offset (60)
2N/A inode table Chunk superblock
2N/A dir table Chunk superblock
2N/A fragment table Chunk unk1
2N/A unk1 RAW, Chunk superblock
2N/A unk2 RAW superblock
2N/A UID/GID Chunk exttblptr
2N/A exttblptr RAW superblock
2N/A
2N/A UID/GID table is the array ot uint32_t
2N/A unk1 contains pointer to fragment table followed by some chunk.
2N/A unk2 containts one uint64_t
2N/A*/
2N/A
2N/Astruct grub_squash_super
2N/A{
2N/A grub_uint32_t magic;
2N/A#define SQUASH_MAGIC 0x73717368
2N/A grub_uint32_t dummy1;
2N/A grub_uint32_t creation_time;
2N/A grub_uint32_t block_size;
2N/A grub_uint32_t dummy2;
2N/A grub_uint16_t compression;
2N/A grub_uint16_t dummy3;
2N/A grub_uint64_t dummy4;
2N/A grub_uint16_t root_ino_offset;
2N/A grub_uint32_t root_ino_chunk;
2N/A grub_uint16_t dummy5;
2N/A grub_uint64_t total_size;
2N/A grub_uint64_t exttbloffset;
2N/A grub_uint64_t dummy6;
2N/A grub_uint64_t inodeoffset;
2N/A grub_uint64_t diroffset;
2N/A grub_uint64_t unk1offset;
2N/A grub_uint64_t unk2offset;
2N/A} __attribute__ ((packed));
2N/A
2N/A/* Chunk-based */
2N/Astruct grub_squash_inode
2N/A{
2N/A /* Same values as direlem types. */
2N/A grub_uint16_t type;
2N/A grub_uint16_t dummy[3];
2N/A grub_uint32_t mtime;
2N/A grub_uint32_t dummy2;
2N/A union
2N/A {
2N/A struct {
2N/A grub_uint32_t chunk;
2N/A grub_uint32_t fragment;
2N/A grub_uint16_t offset;
2N/A grub_uint16_t dummy;
2N/A grub_uint32_t size;
2N/A grub_uint32_t block_size[0];
2N/A } __attribute__ ((packed)) file;
2N/A struct {
2N/A grub_uint64_t chunk;
2N/A grub_uint64_t size;
2N/A grub_uint32_t dummy1[3];
2N/A grub_uint32_t fragment;
2N/A grub_uint16_t offset;
2N/A grub_uint16_t dummy2;
2N/A grub_uint32_t dummy3;
2N/A grub_uint32_t block_size[0];
2N/A } __attribute__ ((packed)) long_file;
2N/A struct {
2N/A grub_uint32_t chunk;
2N/A grub_uint32_t dummy;
2N/A grub_uint16_t size;
2N/A grub_uint16_t offset;
2N/A } __attribute__ ((packed)) dir;
2N/A struct {
2N/A grub_uint32_t dummy1;
2N/A grub_uint32_t size;
2N/A grub_uint32_t chunk;
2N/A grub_uint32_t dummy2;
2N/A grub_uint16_t dummy3;
2N/A grub_uint16_t offset;
2N/A } __attribute__ ((packed)) long_dir;
2N/A struct {
2N/A grub_uint32_t dummy;
2N/A grub_uint32_t namelen;
2N/A char name[0];
2N/A } __attribute__ ((packed)) symlink;
2N/A } __attribute__ ((packed));
2N/A} __attribute__ ((packed));
2N/A
2N/Astruct grub_squash_cache_inode
2N/A{
2N/A struct grub_squash_inode ino;
2N/A grub_disk_addr_t ino_chunk;
2N/A grub_uint16_t ino_offset;
2N/A grub_uint32_t *block_sizes;
2N/A grub_disk_addr_t *cumulated_block_sizes;
2N/A};
2N/A
2N/A/* Chunk-based. */
2N/Astruct grub_squash_dirent_header
2N/A{
2N/A /* Actually the value is the number of elements - 1. */
2N/A grub_uint32_t nelems;
2N/A grub_uint32_t ino_chunk;
2N/A grub_uint32_t dummy;
2N/A} __attribute__ ((packed));
2N/A
2N/Astruct grub_squash_dirent
2N/A{
2N/A grub_uint16_t ino_offset;
2N/A grub_uint16_t dummy;
2N/A grub_uint16_t type;
2N/A /* Actually the value is the length of name - 1. */
2N/A grub_uint16_t namelen;
2N/A char name[0];
2N/A} __attribute__ ((packed));
2N/A
2N/Aenum
2N/A {
2N/A SQUASH_TYPE_DIR = 1,
2N/A SQUASH_TYPE_REGULAR = 2,
2N/A SQUASH_TYPE_SYMLINK = 3,
2N/A SQUASH_TYPE_LONG_DIR = 8,
2N/A SQUASH_TYPE_LONG_REGULAR = 9,
2N/A };
2N/A
2N/A
2N/Astruct grub_squash_frag_desc
2N/A{
2N/A grub_uint64_t offset;
2N/A grub_uint32_t size;
2N/A grub_uint32_t dummy;
2N/A} __attribute__ ((packed));
2N/A
2N/Aenum
2N/A {
2N/A SQUASH_CHUNK_FLAGS = 0x8000,
2N/A SQUASH_CHUNK_UNCOMPRESSED = 0x8000
2N/A };
2N/A
2N/Aenum
2N/A {
2N/A SQUASH_BLOCK_FLAGS = 0x1000000,
2N/A SQUASH_BLOCK_UNCOMPRESSED = 0x1000000
2N/A };
2N/A
2N/Aenum
2N/A {
2N/A COMPRESSION_ZLIB = 1,
2N/A COMPRESSION_LZO = 3,
2N/A COMPRESSION_XZ = 4,
2N/A };
2N/A
2N/A
2N/A#define SQUASH_CHUNK_SIZE 0x2000
2N/A#define XZBUFSIZ 0x2000
2N/A
2N/Astruct grub_squash_data
2N/A{
2N/A grub_disk_t disk;
2N/A struct grub_squash_super sb;
2N/A struct grub_squash_cache_inode ino;
2N/A grub_uint64_t fragments;
2N/A int log2_blksz;
2N/A grub_size_t blksz;
2N/A grub_ssize_t (*decompress) (char *inbuf, grub_size_t insize, grub_off_t off,
2N/A char *outbuf, grub_size_t outsize,
2N/A struct grub_squash_data *data);
2N/A struct xz_dec *xzdec;
2N/A char *xzbuf;
2N/A};
2N/A
2N/Astruct grub_fshelp_node
2N/A{
2N/A struct grub_squash_data *data;
2N/A struct grub_squash_inode ino;
2N/A grub_disk_addr_t ino_chunk;
2N/A grub_uint16_t ino_offset;
2N/A};
2N/A
2N/Astatic grub_err_t
2N/Aread_chunk (struct grub_squash_data *data, void *buf, grub_size_t len,
2N/A grub_uint64_t chunk_start, grub_off_t offset)
2N/A{
2N/A while (len > 0)
2N/A {
2N/A grub_uint64_t csize;
2N/A grub_uint16_t d;
2N/A grub_err_t err;
2N/A while (1)
2N/A {
2N/A err = grub_disk_read (data->disk,
2N/A chunk_start >> GRUB_DISK_SECTOR_BITS,
2N/A chunk_start & (GRUB_DISK_SECTOR_SIZE - 1),
2N/A sizeof (d), &d);
2N/A if (err)
2N/A return err;
2N/A if (offset < SQUASH_CHUNK_SIZE)
2N/A break;
2N/A offset -= SQUASH_CHUNK_SIZE;
2N/A chunk_start += 2 + (grub_le_to_cpu16 (d) & ~SQUASH_CHUNK_FLAGS);
2N/A }
2N/A
2N/A csize = SQUASH_CHUNK_SIZE - offset;
2N/A if (csize > len)
2N/A csize = len;
2N/A
2N/A if (grub_le_to_cpu16 (d) & SQUASH_CHUNK_UNCOMPRESSED)
2N/A {
2N/A grub_disk_addr_t a = chunk_start + 2 + offset;
2N/A err = grub_disk_read (data->disk, (a >> GRUB_DISK_SECTOR_BITS),
2N/A a & (GRUB_DISK_SECTOR_SIZE - 1),
2N/A csize, buf);
2N/A if (err)
2N/A return err;
2N/A }
2N/A else
2N/A {
2N/A char *tmp;
2N/A grub_size_t bsize = grub_le_to_cpu16 (d) & ~SQUASH_CHUNK_FLAGS;
2N/A grub_disk_addr_t a = chunk_start + 2;
2N/A tmp = grub_malloc (bsize);
2N/A if (!tmp)
2N/A return grub_errno;
2N/A /* FIXME: buffer uncompressed data. */
2N/A err = grub_disk_read (data->disk, (a >> GRUB_DISK_SECTOR_BITS),
2N/A a & (GRUB_DISK_SECTOR_SIZE - 1),
2N/A bsize, tmp);
2N/A if (err)
2N/A {
2N/A grub_free (tmp);
2N/A return err;
2N/A }
2N/A
2N/A if (data->decompress (tmp, bsize, offset,
2N/A buf, csize, data) < 0)
2N/A {
2N/A grub_free (tmp);
2N/A return grub_errno;
2N/A }
2N/A grub_free (tmp);
2N/A }
2N/A len -= csize;
2N/A offset += csize;
2N/A buf = (char *) buf + csize;
2N/A }
2N/A return GRUB_ERR_NONE;
2N/A}
2N/A
2N/Astatic grub_ssize_t
2N/Azlib_decompress (char *inbuf, grub_size_t insize, grub_off_t off,
2N/A char *outbuf, grub_size_t outsize,
2N/A struct grub_squash_data *data __attribute__ ((unused)))
2N/A{
2N/A return grub_zlib_decompress (inbuf, insize, off, outbuf, outsize);
2N/A}
2N/A
2N/Astatic grub_ssize_t
2N/Alzo_decompress (char *inbuf, grub_size_t insize, grub_off_t off,
2N/A char *outbuf, grub_size_t len, struct grub_squash_data *data)
2N/A{
2N/A lzo_uint usize = data->blksz;
2N/A grub_uint8_t *udata;
2N/A
2N/A udata = grub_malloc (data->blksz);
2N/A if (!udata)
2N/A return -1;
2N/A
2N/A if (lzo1x_decompress_safe ((grub_uint8_t *) inbuf,
2N/A insize, udata, &usize, NULL) != LZO_E_OK)
2N/A {
2N/A grub_free (udata);
2N/A return -1;
2N/A }
2N/A grub_memcpy (outbuf, udata + off, len);
2N/A grub_free (udata);
2N/A return len;
2N/A}
2N/A
2N/Astatic grub_ssize_t
2N/Axz_decompress (char *inbuf, grub_size_t insize, grub_off_t off,
2N/A char *outbuf, grub_size_t len, struct grub_squash_data *data)
2N/A{
2N/A grub_size_t ret;
2N/A grub_off_t pos = 0;
2N/A struct xz_buf buf;
2N/A
2N/A xz_dec_reset (data->xzdec);
2N/A buf.in = (grub_uint8_t *) inbuf;
2N/A buf.in_pos = 0;
2N/A buf.in_size = insize;
2N/A buf.out = (grub_uint8_t *) data->xzbuf;
2N/A buf.out_pos = 0;
2N/A buf.out_size = XZBUFSIZ;
2N/A
2N/A while (len)
2N/A {
2N/A enum xz_ret xzret;
2N/A
2N/A buf.out_pos = 0;
2N/A
2N/A xzret = xz_dec_run (data->xzdec, &buf);
2N/A
2N/A if (xzret != XZ_OK && xzret != XZ_STREAM_END)
2N/A {
2N/A grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, "invalid xz chunk");
2N/A return -1;
2N/A }
2N/A if (pos + buf.out_pos >= off)
2N/A {
2N/A grub_ssize_t outoff = pos - off;
2N/A grub_size_t l;
2N/A if (outoff >= 0)
2N/A {
2N/A l = buf.out_pos;
2N/A if (l > len)
2N/A l = len;
2N/A grub_memcpy (outbuf + outoff, buf.out, l);
2N/A }
2N/A else
2N/A {
2N/A outoff = -outoff;
2N/A l = buf.out_pos - outoff;
2N/A if (l > len)
2N/A l = len;
2N/A grub_memcpy (outbuf, buf.out + outoff, l);
2N/A }
2N/A ret += l;
2N/A len -= l;
2N/A }
2N/A pos += buf.out_pos;
2N/A if (xzret == XZ_STREAM_END)
2N/A break;
2N/A }
2N/A return ret;
2N/A}
2N/A
2N/Astatic struct grub_squash_data *
2N/Asquash_mount (grub_disk_t disk)
2N/A{
2N/A struct grub_squash_super sb;
2N/A grub_err_t err;
2N/A struct grub_squash_data *data;
2N/A grub_uint64_t frag;
2N/A
2N/A err = grub_disk_read (disk, 0, 0, sizeof (sb), &sb);
2N/A if (grub_errno == GRUB_ERR_OUT_OF_RANGE)
2N/A grub_error (GRUB_ERR_BAD_FS, "not a squash4");
2N/A if (err)
2N/A return NULL;
2N/A if (sb.magic != grub_cpu_to_le32_compile_time (SQUASH_MAGIC)
2N/A || sb.block_size == 0
2N/A || ((sb.block_size - 1) & sb.block_size))
2N/A {
2N/A grub_error (GRUB_ERR_BAD_FS, "not squash4");
2N/A return NULL;
2N/A }
2N/A
2N/A err = grub_disk_read (disk,
2N/A grub_le_to_cpu64 (sb.unk1offset)
2N/A >> GRUB_DISK_SECTOR_BITS,
2N/A grub_le_to_cpu64 (sb.unk1offset)
2N/A & (GRUB_DISK_SECTOR_SIZE - 1), sizeof (frag), &frag);
2N/A if (grub_errno == GRUB_ERR_OUT_OF_RANGE)
2N/A grub_error (GRUB_ERR_BAD_FS, "not a squash4");
2N/A if (err)
2N/A return NULL;
2N/A
2N/A data = grub_zalloc (sizeof (*data));
2N/A if (!data)
2N/A return NULL;
2N/A data->sb = sb;
2N/A data->disk = disk;
2N/A data->fragments = grub_le_to_cpu64 (frag);
2N/A
2N/A switch (sb.compression)
2N/A {
2N/A case grub_cpu_to_le16_compile_time (COMPRESSION_ZLIB):
2N/A data->decompress = zlib_decompress;
2N/A break;
2N/A case grub_cpu_to_le16_compile_time (COMPRESSION_LZO):
2N/A data->decompress = lzo_decompress;
2N/A break;
2N/A case grub_cpu_to_le16_compile_time (COMPRESSION_XZ):
2N/A data->decompress = xz_decompress;
2N/A data->xzbuf = grub_malloc (XZBUFSIZ);
2N/A if (!data->xzbuf)
2N/A {
2N/A grub_free (data);
2N/A return NULL;
2N/A }
2N/A data->xzdec = xz_dec_init (1 << 16);
2N/A if (!data->xzdec)
2N/A {
2N/A grub_free (data->xzbuf);
2N/A grub_free (data);
2N/A return NULL;
2N/A }
2N/A break;
2N/A default:
2N/A grub_free (data);
2N/A grub_error (GRUB_ERR_BAD_FS, "unsupported compression %d",
2N/A grub_le_to_cpu16 (sb.compression));
2N/A return NULL;
2N/A }
2N/A
2N/A data->blksz = grub_le_to_cpu32 (data->sb.block_size);
2N/A for (data->log2_blksz = 0;
2N/A (1U << data->log2_blksz) < data->blksz;
2N/A data->log2_blksz++);
2N/A
2N/A return data;
2N/A}
2N/A
2N/Astatic char *
2N/Agrub_squash_read_symlink (grub_fshelp_node_t node)
2N/A{
2N/A char *ret;
2N/A grub_err_t err;
2N/A ret = grub_malloc (grub_le_to_cpu32 (node->ino.symlink.namelen) + 1);
2N/A
2N/A err = read_chunk (node->data, ret,
2N/A grub_le_to_cpu32 (node->ino.symlink.namelen),
2N/A grub_le_to_cpu64 (node->data->sb.inodeoffset)
2N/A + node->ino_chunk,
2N/A node->ino_offset + (node->ino.symlink.name
2N/A - (char *) &node->ino));
2N/A if (err)
2N/A {
2N/A grub_free (ret);
2N/A return NULL;
2N/A }
2N/A ret[grub_le_to_cpu32 (node->ino.symlink.namelen)] = 0;
2N/A return ret;
2N/A}
2N/A
2N/Astatic int
2N/Agrub_squash_iterate_dir (grub_fshelp_node_t dir,
2N/A int NESTED_FUNC_ATTR
2N/A (*hook) (const char *filename,
2N/A enum grub_fshelp_filetype filetype,
2N/A grub_fshelp_node_t node))
2N/A{
2N/A grub_uint32_t off;
2N/A grub_uint32_t endoff;
2N/A grub_uint64_t chunk;
2N/A unsigned i;
2N/A
2N/A /* FIXME: why - 3 ? */
2N/A switch (dir->ino.type)
2N/A {
2N/A case grub_cpu_to_le16_compile_time (SQUASH_TYPE_DIR):
2N/A off = grub_le_to_cpu16 (dir->ino.dir.offset);
2N/A endoff = grub_le_to_cpu16 (dir->ino.dir.size) + off - 3;
2N/A chunk = grub_le_to_cpu32 (dir->ino.dir.chunk);
2N/A break;
2N/A case grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_DIR):
2N/A off = grub_le_to_cpu16 (dir->ino.long_dir.offset);
2N/A endoff = grub_le_to_cpu16 (dir->ino.long_dir.size) + off - 3;
2N/A chunk = grub_le_to_cpu32 (dir->ino.long_dir.chunk);
2N/A break;
2N/A default:
2N/A grub_error (GRUB_ERR_BAD_FS, "unexpected ino type 0x%x",
2N/A grub_le_to_cpu16 (dir->ino.type));
2N/A return 0;
2N/A }
2N/A
2N/A while (off < endoff)
2N/A {
2N/A struct grub_squash_dirent_header dh;
2N/A grub_err_t err;
2N/A
2N/A err = read_chunk (dir->data, &dh, sizeof (dh),
2N/A grub_le_to_cpu64 (dir->data->sb.diroffset)
2N/A + chunk, off);
2N/A if (err)
2N/A return 0;
2N/A off += sizeof (dh);
2N/A for (i = 0; i < (unsigned) grub_le_to_cpu32 (dh.nelems) + 1; i++)
2N/A {
2N/A char *buf;
2N/A int r;
2N/A struct grub_fshelp_node *node;
2N/A enum grub_fshelp_filetype filetype = GRUB_FSHELP_REG;
2N/A struct grub_squash_dirent di;
2N/A struct grub_squash_inode ino;
2N/A
2N/A err = read_chunk (dir->data, &di, sizeof (di),
2N/A grub_le_to_cpu64 (dir->data->sb.diroffset)
2N/A + chunk, off);
2N/A if (err)
2N/A return 0;
2N/A off += sizeof (di);
2N/A
2N/A err = read_chunk (dir->data, &ino, sizeof (ino),
2N/A grub_le_to_cpu64 (dir->data->sb.inodeoffset)
2N/A + grub_le_to_cpu32 (dh.ino_chunk),
2N/A grub_cpu_to_le16 (di.ino_offset));
2N/A if (err)
2N/A return 0;
2N/A
2N/A buf = grub_malloc (grub_le_to_cpu16 (di.namelen) + 2);
2N/A if (!buf)
2N/A return 0;
2N/A err = read_chunk (dir->data, buf,
2N/A grub_le_to_cpu16 (di.namelen) + 1,
2N/A grub_le_to_cpu64 (dir->data->sb.diroffset)
2N/A + chunk, off);
2N/A if (err)
2N/A return 0;
2N/A
2N/A off += grub_le_to_cpu16 (di.namelen) + 1;
2N/A buf[grub_le_to_cpu16 (di.namelen) + 1] = 0;
2N/A if (grub_le_to_cpu16 (di.type) == SQUASH_TYPE_DIR)
2N/A filetype = GRUB_FSHELP_DIR;
2N/A if (grub_le_to_cpu16 (di.type) == SQUASH_TYPE_SYMLINK)
2N/A filetype = GRUB_FSHELP_SYMLINK;
2N/A
2N/A node = grub_malloc (sizeof (*node));
2N/A if (! node)
2N/A return 0;
2N/A *node = *dir;
2N/A node->ino = ino;
2N/A node->ino_chunk = grub_le_to_cpu32 (dh.ino_chunk);
2N/A node->ino_offset = grub_le_to_cpu16 (di.ino_offset);
2N/A
2N/A r = hook (buf, filetype, node);
2N/A
2N/A grub_free (buf);
2N/A if (r)
2N/A return r;
2N/A }
2N/A }
2N/A return 0;
2N/A}
2N/A
2N/Astatic grub_err_t
2N/Amake_root_node (struct grub_squash_data *data, struct grub_fshelp_node *root)
2N/A{
2N/A grub_memset (root, 0, sizeof (*root));
2N/A root->data = data;
2N/A
2N/A return read_chunk (data, &root->ino, sizeof (root->ino),
2N/A grub_le_to_cpu64 (data->sb.inodeoffset)
2N/A + grub_le_to_cpu32 (data->sb.root_ino_chunk),
2N/A grub_cpu_to_le16 (data->sb.root_ino_offset));
2N/A}
2N/A
2N/Astatic void
2N/Asquash_unmount (struct grub_squash_data *data)
2N/A{
2N/A if (data->xzdec)
2N/A xz_dec_end (data->xzdec);
2N/A grub_free (data->xzbuf);
2N/A grub_free (data->ino.cumulated_block_sizes);
2N/A grub_free (data->ino.block_sizes);
2N/A grub_free (data);
2N/A}
2N/A
2N/A
2N/Astatic grub_err_t
2N/Agrub_squash_dir (grub_device_t device, const char *path,
2N/A int (*hook) (const char *filename,
2N/A const struct grub_dirhook_info *info))
2N/A{
2N/A auto int NESTED_FUNC_ATTR iterate (const char *filename,
2N/A enum grub_fshelp_filetype filetype,
2N/A grub_fshelp_node_t node);
2N/A
2N/A int NESTED_FUNC_ATTR iterate (const char *filename,
2N/A enum grub_fshelp_filetype filetype,
2N/A grub_fshelp_node_t node)
2N/A {
2N/A struct grub_dirhook_info info;
2N/A grub_memset (&info, 0, sizeof (info));
2N/A info.dir = ((filetype & GRUB_FSHELP_TYPE_MASK) == GRUB_FSHELP_DIR);
2N/A info.mtimeset = 1;
2N/A info.mtime = grub_le_to_cpu32 (node->ino.mtime);
2N/A grub_free (node);
2N/A return hook (filename, &info);
2N/A }
2N/A
2N/A struct grub_squash_data *data = 0;
2N/A struct grub_fshelp_node *fdiro = 0;
2N/A struct grub_fshelp_node root;
2N/A grub_err_t err;
2N/A
2N/A data = squash_mount (device->disk);
2N/A if (! data)
2N/A return grub_errno;
2N/A
2N/A err = make_root_node (data, &root);
2N/A if (err)
2N/A return err;
2N/A
2N/A grub_fshelp_find_file (path, &root, &fdiro, grub_squash_iterate_dir,
2N/A grub_squash_read_symlink, GRUB_FSHELP_DIR);
2N/A if (!grub_errno)
2N/A grub_squash_iterate_dir (fdiro, iterate);
2N/A
2N/A squash_unmount (data);
2N/A
2N/A return grub_errno;
2N/A}
2N/A
2N/Astatic grub_err_t
2N/Agrub_squash_open (struct grub_file *file, const char *name)
2N/A{
2N/A struct grub_squash_data *data = 0;
2N/A struct grub_fshelp_node *fdiro = 0;
2N/A struct grub_fshelp_node root;
2N/A grub_err_t err;
2N/A
2N/A data = squash_mount (file->device->disk);
2N/A if (! data)
2N/A return grub_errno;
2N/A
2N/A err = make_root_node (data, &root);
2N/A if (err)
2N/A return err;
2N/A
2N/A grub_fshelp_find_file (name, &root, &fdiro, grub_squash_iterate_dir,
2N/A grub_squash_read_symlink, GRUB_FSHELP_REG);
2N/A if (grub_errno)
2N/A {
2N/A squash_unmount (data);
2N/A return grub_errno;
2N/A }
2N/A
2N/A file->data = data;
2N/A data->ino.ino = fdiro->ino;
2N/A data->ino.block_sizes = NULL;
2N/A data->ino.cumulated_block_sizes = NULL;
2N/A data->ino.ino_chunk = fdiro->ino_chunk;
2N/A data->ino.ino_offset = fdiro->ino_offset;
2N/A
2N/A switch (fdiro->ino.type)
2N/A {
2N/A case grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_REGULAR):
2N/A file->size = grub_le_to_cpu64 (fdiro->ino.long_file.size);
2N/A break;
2N/A case grub_cpu_to_le16_compile_time (SQUASH_TYPE_REGULAR):
2N/A file->size = grub_le_to_cpu32 (fdiro->ino.file.size);
2N/A break;
2N/A default:
2N/A {
2N/A grub_uint16_t type = grub_le_to_cpu16 (fdiro->ino.type);
2N/A grub_free (fdiro);
2N/A squash_unmount (data);
2N/A return grub_error (GRUB_ERR_BAD_FS, "unexpected ino type 0x%x", type);
2N/A }
2N/A }
2N/A
2N/A grub_free (fdiro);
2N/A
2N/A return GRUB_ERR_NONE;
2N/A}
2N/A
2N/Astatic grub_ssize_t
2N/Adirect_read (struct grub_squash_data *data,
2N/A struct grub_squash_cache_inode *ino,
2N/A grub_off_t off, char *buf, grub_size_t len)
2N/A{
2N/A grub_err_t err;
2N/A grub_off_t cumulated_uncompressed_size = 0;
2N/A grub_uint64_t a = 0;
2N/A grub_size_t i;
2N/A grub_size_t origlen = len;
2N/A
2N/A switch (ino->ino.type)
2N/A {
2N/A case grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_REGULAR):
2N/A a = grub_le_to_cpu64 (ino->ino.long_file.chunk);
2N/A break;
2N/A case grub_cpu_to_le16_compile_time (SQUASH_TYPE_REGULAR):
2N/A a = grub_le_to_cpu32 (ino->ino.file.chunk);
2N/A break;
2N/A }
2N/A
2N/A if (!ino->block_sizes)
2N/A {
2N/A grub_off_t total_size = 0;
2N/A grub_size_t total_blocks;
2N/A grub_size_t block_offset = 0;
2N/A switch (ino->ino.type)
2N/A {
2N/A case grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_REGULAR):
2N/A total_size = grub_le_to_cpu64 (ino->ino.long_file.size);
2N/A block_offset = ((char *) &ino->ino.long_file.block_size
2N/A - (char *) &ino->ino);
2N/A break;
2N/A case grub_cpu_to_le16_compile_time (SQUASH_TYPE_REGULAR):
2N/A total_size = grub_le_to_cpu32 (ino->ino.file.size);
2N/A block_offset = ((char *) &ino->ino.file.block_size
2N/A - (char *) &ino->ino);
2N/A break;
2N/A }
2N/A total_blocks = ((total_size + data->blksz - 1) >> data->log2_blksz);
2N/A ino->block_sizes = grub_malloc (total_blocks
2N/A * sizeof (ino->block_sizes[0]));
2N/A ino->cumulated_block_sizes = grub_malloc (total_blocks
2N/A * sizeof (ino->cumulated_block_sizes[0]));
2N/A if (!ino->block_sizes || !ino->cumulated_block_sizes)
2N/A {
2N/A grub_free (ino->block_sizes);
2N/A grub_free (ino->cumulated_block_sizes);
2N/A ino->block_sizes = 0;
2N/A ino->cumulated_block_sizes = 0;
2N/A return -1;
2N/A }
2N/A err = read_chunk (data, ino->block_sizes,
2N/A total_blocks * sizeof (ino->block_sizes[0]),
2N/A grub_le_to_cpu64 (data->sb.inodeoffset)
2N/A + ino->ino_chunk,
2N/A ino->ino_offset + block_offset);
2N/A if (err)
2N/A {
2N/A grub_free (ino->block_sizes);
2N/A grub_free (ino->cumulated_block_sizes);
2N/A ino->block_sizes = 0;
2N/A ino->cumulated_block_sizes = 0;
2N/A return -1;
2N/A }
2N/A ino->cumulated_block_sizes[0] = 0;
2N/A for (i = 1; i < total_blocks; i++)
2N/A ino->cumulated_block_sizes[i] = ino->cumulated_block_sizes[i - 1]
2N/A + (grub_le_to_cpu32 (ino->block_sizes[i - 1]) & ~SQUASH_BLOCK_FLAGS);
2N/A }
2N/A
2N/A if (a == 0)
2N/A a = sizeof (struct grub_squash_super);
2N/A i = off >> data->log2_blksz;
2N/A cumulated_uncompressed_size = data->blksz * (grub_disk_addr_t) i;
2N/A while (cumulated_uncompressed_size < off + len)
2N/A {
2N/A grub_size_t boff, read;
2N/A boff = off - cumulated_uncompressed_size;
2N/A read = data->blksz - boff;
2N/A if (read > len)
2N/A read = len;
2N/A if (!(ino->block_sizes[i]
2N/A & grub_cpu_to_le32_compile_time (SQUASH_BLOCK_UNCOMPRESSED)))
2N/A {
2N/A char *block;
2N/A block = grub_malloc (data->blksz);
2N/A if (!block)
2N/A return -1;
2N/A err = grub_disk_read (data->disk,
2N/A (ino->cumulated_block_sizes[i] + a)
2N/A >> GRUB_DISK_SECTOR_BITS,
2N/A (ino->cumulated_block_sizes[i] + a)
2N/A & (GRUB_DISK_SECTOR_SIZE - 1),
2N/A data->blksz, block);
2N/A if (err)
2N/A {
2N/A grub_free (block);
2N/A return -1;
2N/A }
2N/A if (data->decompress (block, data->blksz, boff, buf, read, data)
2N/A != (grub_ssize_t) read)
2N/A {
2N/A grub_free (block);
2N/A if (!grub_errno)
2N/A grub_error (GRUB_ERR_BAD_FS, "incorrect compressed chunk");
2N/A return -1;
2N/A }
2N/A grub_free (block);
2N/A }
2N/A else
2N/A err = grub_disk_read (data->disk,
2N/A (ino->cumulated_block_sizes[i] + a + boff)
2N/A >> GRUB_DISK_SECTOR_BITS,
2N/A (ino->cumulated_block_sizes[i] + a + boff)
2N/A & (GRUB_DISK_SECTOR_SIZE - 1),
2N/A read, buf);
2N/A if (err)
2N/A return -1;
2N/A off += read;
2N/A len -= read;
2N/A buf += read;
2N/A cumulated_uncompressed_size += grub_le_to_cpu32 (data->sb.block_size);
2N/A i++;
2N/A }
2N/A return origlen;
2N/A}
2N/A
2N/A
2N/Astatic grub_ssize_t
2N/Agrub_squash_read_data (struct grub_squash_data *data,
2N/A struct grub_squash_cache_inode *ino,
2N/A grub_off_t off, char *buf, grub_size_t len)
2N/A{
2N/A grub_err_t err;
2N/A grub_uint64_t a = 0, b;
2N/A grub_uint32_t fragment = 0;
2N/A int compressed = 0;
2N/A struct grub_squash_frag_desc frag;
2N/A
2N/A switch (ino->ino.type)
2N/A {
2N/A case grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_REGULAR):
2N/A a = grub_le_to_cpu64 (ino->ino.long_file.chunk);
2N/A fragment = grub_le_to_cpu32 (ino->ino.long_file.fragment);
2N/A break;
2N/A case grub_cpu_to_le16_compile_time (SQUASH_TYPE_REGULAR):
2N/A a = grub_le_to_cpu32 (ino->ino.file.chunk);
2N/A fragment = grub_le_to_cpu32 (ino->ino.file.fragment);
2N/A break;
2N/A }
2N/A
2N/A if (fragment == 0xffffffff)
2N/A return direct_read (data, ino, off, buf, len);
2N/A
2N/A err = read_chunk (data, &frag, sizeof (frag),
2N/A data->fragments, sizeof (frag) * fragment);
2N/A if (err)
2N/A return -1;
2N/A a += grub_le_to_cpu64 (frag.offset);
2N/A compressed = !(frag.size & SQUASH_BLOCK_UNCOMPRESSED);
2N/A if (ino->ino.type == grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_REGULAR))
2N/A b = grub_le_to_cpu64 (ino->ino.long_file.offset) + off;
2N/A else
2N/A b = grub_le_to_cpu32 (ino->ino.file.offset) + off;
2N/A
2N/A /* FIXME: cache uncompressed chunks. */
2N/A if (compressed)
2N/A {
2N/A char *block;
2N/A block = grub_malloc (data->blksz);
2N/A if (!block)
2N/A return -1;
2N/A err = grub_disk_read (data->disk,
2N/A a >> GRUB_DISK_SECTOR_BITS,
2N/A a & (GRUB_DISK_SECTOR_SIZE - 1),
2N/A data->blksz, block);
2N/A if (err)
2N/A {
2N/A grub_free (block);
2N/A return -1;
2N/A }
2N/A if (data->decompress (block, data->blksz, b, buf, len, data)
2N/A != (grub_ssize_t) len)
2N/A {
2N/A grub_free (block);
2N/A if (!grub_errno)
2N/A grub_error (GRUB_ERR_BAD_FS, "incorrect compressed chunk");
2N/A return -1;
2N/A }
2N/A grub_free (block);
2N/A }
2N/A else
2N/A {
2N/A err = grub_disk_read (data->disk, (a + b) >> GRUB_DISK_SECTOR_BITS,
2N/A (a + b) & (GRUB_DISK_SECTOR_SIZE - 1), len, buf);
2N/A if (err)
2N/A return -1;
2N/A }
2N/A return len;
2N/A}
2N/A
2N/Astatic grub_ssize_t
2N/Agrub_squash_read (grub_file_t file, char *buf, grub_size_t len)
2N/A{
2N/A struct grub_squash_data *data = file->data;
2N/A
2N/A return grub_squash_read_data (data, &data->ino,
2N/A file->offset, buf, len);
2N/A}
2N/A
2N/Astatic grub_err_t
2N/Agrub_squash_close (grub_file_t file)
2N/A{
2N/A squash_unmount (file->data);
2N/A return GRUB_ERR_NONE;
2N/A}
2N/A
2N/Astatic grub_err_t
2N/Agrub_squash_mtime (grub_device_t dev, grub_int32_t *tm)
2N/A{
2N/A struct grub_squash_data *data = 0;
2N/A
2N/A data = squash_mount (dev->disk);
2N/A if (! data)
2N/A return grub_errno;
2N/A *tm = grub_le_to_cpu32 (data->sb.creation_time);
2N/A squash_unmount (data);
2N/A return GRUB_ERR_NONE;
2N/A}
2N/A
2N/Astatic struct grub_fs grub_squash_fs =
2N/A {
2N/A .name = "squash4",
2N/A .dir = grub_squash_dir,
2N/A .open = grub_squash_open,
2N/A .read = grub_squash_read,
2N/A .close = grub_squash_close,
2N/A .mtime = grub_squash_mtime,
2N/A#ifdef GRUB_UTIL
2N/A .reserved_first_sector = 0,
2N/A#endif
2N/A .next = 0
2N/A };
2N/A
2N/AGRUB_MOD_INIT(squash4)
2N/A{
2N/A grub_fs_register (&grub_squash_fs);
2N/A}
2N/A
2N/AGRUB_MOD_FINI(squash4)
2N/A{
2N/A grub_fs_unregister (&grub_squash_fs);
2N/A}
2N/A