12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055 |
- /* 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 <http://www.gnu.org/licenses/>.
- */
- #include <grub/err.h>
- #include <grub/file.h>
- #include <grub/mm.h>
- #include <grub/misc.h>
- #include <grub/disk.h>
- #include <grub/dl.h>
- #include <grub/types.h>
- #include <grub/fshelp.h>
- #include <grub/deflate.h>
- #include <grub/safemath.h>
- #include <minilzo.h>
- #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;
- grub_size_t sz;
- if (grub_add (grub_le_to_cpu32 (node->ino.symlink.namelen), 1, &sz))
- {
- grub_error (GRUB_ERR_OUT_OF_RANGE, N_("overflow is detected"));
- return NULL;
- }
- ret = grub_malloc (sz);
- if (!ret)
- return NULL;
- 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;
- grub_size_t sz;
- if (grub_mul (dir->stsize, sizeof (dir->stack[0]), &sz) ||
- grub_add (sz, sizeof (*node), &sz))
- return 0;
- node = grub_malloc (sz);
- if (!node)
- return 0;
- grub_memcpy (node, dir, sz);
- if (hook (".", GRUB_FSHELP_DIR, node, hook_data))
- return 1;
- if (dir->stsize != 1)
- {
- grub_err_t err;
- if (grub_mul (dir->stsize, sizeof (dir->stack[0]), &sz) ||
- grub_add (sz, sizeof (*node), &sz))
- return 0;
- node = grub_malloc (sz);
- if (!node)
- return 0;
- grub_memcpy (node, dir, sz);
- 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)
- {
- grub_free (node);
- 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;
- grub_size_t sz;
- 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)
- {
- grub_free (buf);
- 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;
- if (grub_add (dir->stsize, 1, &sz) ||
- grub_mul (sz, sizeof (dir->stack[0]), &sz) ||
- grub_add (sz, sizeof (*node), &sz))
- {
- grub_free (buf);
- return 0;
- }
- node = grub_malloc (sz);
- if (! node)
- {
- grub_free (buf);
- return 0;
- }
- grub_memcpy (node, dir, sz - 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_ERR_NONE;
- 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_int64_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",
- .fs_dir = grub_squash_dir,
- .fs_open = grub_squash_open,
- .fs_read = grub_squash_read,
- .fs_close = grub_squash_close,
- .fs_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);
- }
|