squash4.c 25.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
/* 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>
29 30 31 32
#include <minilzo.h>

#include "xz.h"
#include "xz_stream.h"
33

34 35
GRUB_MOD_LICENSE ("GPLv3+");

36
/*
37 38 39 40 41 42 43 44 45 46
  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
47 48

  UID/GID table is the array ot uint32_t
49
  unk1 contains pointer to fragment table followed by some chunk.
50 51 52 53 54 55 56 57 58
  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;
59
  grub_uint32_t block_size;
60 61 62
  grub_uint32_t dummy2;
  grub_uint16_t compression;
  grub_uint16_t dummy3;
63
  grub_uint64_t dummy4;
64
  grub_uint16_t root_ino_offset;
65 66
  grub_uint32_t root_ino_chunk;
  grub_uint16_t dummy5;
67 68
  grub_uint64_t total_size;
  grub_uint64_t exttbloffset;
69
  grub_uint64_t dummy6;
70 71 72 73
  grub_uint64_t inodeoffset;
  grub_uint64_t diroffset;
  grub_uint64_t unk1offset;
  grub_uint64_t unk2offset;
74
} GRUB_PACKED;
75 76 77 78 79 80

/* Chunk-based */
struct grub_squash_inode
{
  /* Same values as direlem types. */
  grub_uint16_t type;
81
  grub_uint16_t dummy[3];
82
  grub_uint32_t mtime;
83
  grub_uint32_t dummy2;
84 85 86 87 88
  union
  {
    struct {
      grub_uint32_t chunk;
      grub_uint32_t fragment;
89
      grub_uint32_t offset;
90
      grub_uint32_t size;
91
      grub_uint32_t block_size[0];
92
    }  GRUB_PACKED file;
93 94 95
    struct {
      grub_uint64_t chunk;
      grub_uint64_t size;
96
      grub_uint32_t dummy1[3];
97
      grub_uint32_t fragment;
98
      grub_uint32_t offset;
99
      grub_uint32_t dummy3;
100
      grub_uint32_t block_size[0];
101
    }  GRUB_PACKED long_file;
102 103
    struct {
      grub_uint32_t chunk;
104
      grub_uint32_t dummy;
105
      grub_uint16_t size;
106
      grub_uint16_t offset;
107
    } GRUB_PACKED dir;
108
    struct {
109 110 111 112 113 114
      grub_uint32_t dummy1;
      grub_uint32_t size;
      grub_uint32_t chunk;
      grub_uint32_t dummy2;
      grub_uint16_t dummy3;
      grub_uint16_t offset;
115
    } GRUB_PACKED long_dir;
116 117
    struct {
      grub_uint32_t dummy;
118 119
      grub_uint32_t namelen;
      char name[0];
120 121 122
    } GRUB_PACKED symlink;
  }  GRUB_PACKED;
} GRUB_PACKED;
123

124 125 126 127 128 129 130 131 132
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;
};

133 134 135 136
/* Chunk-based.  */
struct grub_squash_dirent_header
{
  /* Actually the value is the number of elements - 1.  */
137
  grub_uint32_t nelems;
138 139
  grub_uint32_t ino_chunk;
  grub_uint32_t dummy;
140
} GRUB_PACKED;
141 142 143

struct grub_squash_dirent
{
144
  grub_uint16_t ino_offset;
145 146 147 148 149
  grub_uint16_t dummy;
  grub_uint16_t type;
  /* Actually the value is the length of name - 1.  */
  grub_uint16_t namelen;
  char name[0];
150
} GRUB_PACKED;
151

152 153 154 155 156
enum
  {
    SQUASH_TYPE_DIR = 1,
    SQUASH_TYPE_REGULAR = 2,
    SQUASH_TYPE_SYMLINK = 3,
157
    SQUASH_TYPE_LONG_DIR = 8,
158 159 160 161
    SQUASH_TYPE_LONG_REGULAR = 9,
  };


162 163 164
struct grub_squash_frag_desc
{
  grub_uint64_t offset;
165 166
  grub_uint32_t size;
  grub_uint32_t dummy;
167
} GRUB_PACKED;
168

169 170 171 172 173 174 175 176 177 178 179 180
enum
  {
    SQUASH_CHUNK_FLAGS = 0x8000,
    SQUASH_CHUNK_UNCOMPRESSED = 0x8000
  };

enum
  {
    SQUASH_BLOCK_FLAGS = 0x1000000,
    SQUASH_BLOCK_UNCOMPRESSED = 0x1000000
  };

181 182 183 184 185 186 187 188
enum
  {
    COMPRESSION_ZLIB = 1,
    COMPRESSION_LZO = 3,
    COMPRESSION_XZ = 4,
  };


189
#define SQUASH_CHUNK_SIZE 0x2000
190
#define XZBUFSIZ 0x2000
191

192 193 194 195
struct grub_squash_data
{
  grub_disk_t disk;
  struct grub_squash_super sb;
196
  struct grub_squash_cache_inode ino;
197
  grub_uint64_t fragments;
198
  int log2_blksz;
199 200 201 202 203 204
  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;
205 206 207 208 209 210
};

struct grub_fshelp_node
{
  struct grub_squash_data *data;
  struct grub_squash_inode ino;
211 212 213 214 215 216
  grub_size_t stsize;
  struct 
  {
    grub_disk_addr_t ino_chunk;
    grub_uint16_t ino_offset;
  } stack[1];
217 218
};

219
static grub_err_t
220
read_chunk (struct grub_squash_data *data, void *buf, grub_size_t len,
221
	    grub_uint64_t chunk_start, grub_off_t offset)
222 223 224 225 226 227 228 229
{
  while (len > 0)
    {
      grub_uint64_t csize;
      grub_uint16_t d;
      grub_err_t err;
      while (1)
	{
230 231
	  err = grub_disk_read (data->disk,
				chunk_start >> GRUB_DISK_SECTOR_BITS,
232 233 234 235
				chunk_start & (GRUB_DISK_SECTOR_SIZE - 1),
				sizeof (d), &d);
	  if (err)
	    return err;
236
	  if (offset < SQUASH_CHUNK_SIZE)
237
	    break;
238
	  offset -= SQUASH_CHUNK_SIZE;
239 240 241 242 243 244 245 246 247 248
	  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;
249
	  err = grub_disk_read (data->disk, (a >> GRUB_DISK_SECTOR_BITS),
250 251 252 253 254 255 256
				a & (GRUB_DISK_SECTOR_SIZE - 1),
				csize, buf);
	  if (err)
	    return err;
	}
      else
	{
257 258 259 260 261 262 263
	  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.  */
264
	  err = grub_disk_read (data->disk, (a >> GRUB_DISK_SECTOR_BITS),
265 266 267 268 269 270 271 272
				a & (GRUB_DISK_SECTOR_SIZE - 1),
				bsize, tmp);
	  if (err)
	    {
	      grub_free (tmp);
	      return err;
	    }

273 274
	  if (data->decompress (tmp, bsize, offset,
				buf, csize, data) < 0)
275 276 277 278 279
	    {
	      grub_free (tmp);
	      return grub_errno;
	    }
	  grub_free (tmp);
280 281 282 283 284 285 286 287
	}
      len -= csize;
      offset += csize;
      buf = (char *) buf + csize;
    }
  return GRUB_ERR_NONE;
}

288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
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;

303 304 305 306
  if (usize < 8192)
    usize = 8192;

  udata = grub_malloc (usize);
307 308 309 310 311 312
  if (!udata)
    return -1;

  if (lzo1x_decompress_safe ((grub_uint8_t *) inbuf,
			     insize, udata, &usize, NULL) != LZO_E_OK)
    {
313
      grub_error (GRUB_ERR_BAD_FS, "incorrect compressed chunk");
314 315 316 317 318 319 320 321 322 323 324 325
      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)
{
326
  grub_size_t ret = 0;
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
  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;
}

380 381 382 383 384 385
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;
386
  grub_uint64_t frag;
387 388 389 390 391 392

  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;
393 394 395
  if (sb.magic != grub_cpu_to_le32_compile_time (SQUASH_MAGIC)
      || sb.block_size == 0
      || ((sb.block_size - 1) & sb.block_size))
396 397 398 399
    {
      grub_error (GRUB_ERR_BAD_FS, "not squash4");
      return NULL;
    }
400

401 402
  err = grub_disk_read (disk, 
			grub_le_to_cpu64 (sb.unk1offset)
403
			>> GRUB_DISK_SECTOR_BITS, 
404
			grub_le_to_cpu64 (sb.unk1offset)
405 406 407 408 409 410
			& (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;

411
  data = grub_zalloc (sizeof (*data));
412 413 414 415
  if (!data)
    return NULL;
  data->sb = sb;
  data->disk = disk;
416
  data->fragments = grub_le_to_cpu64 (frag);
417

418 419
  switch (sb.compression)
    {
420
    case grub_cpu_to_le16_compile_time (COMPRESSION_ZLIB):
421 422
      data->decompress = zlib_decompress;
      break;
423
    case grub_cpu_to_le16_compile_time (COMPRESSION_LZO):
424 425
      data->decompress = lzo_decompress;
      break;
426
    case grub_cpu_to_le16_compile_time (COMPRESSION_XZ):
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449
      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);
450
  for (data->log2_blksz = 0; 
451
       (1U << data->log2_blksz) < data->blksz;
452 453
       data->log2_blksz++);

454 455 456
  return data;
}

457 458 459 460 461 462 463
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);

464
  err = read_chunk (node->data, ret,
465 466
		    grub_le_to_cpu32 (node->ino.symlink.namelen),
		    grub_le_to_cpu64 (node->data->sb.inodeoffset)
467 468 469
		    + node->stack[node->stsize - 1].ino_chunk,
		    node->stack[node->stsize - 1].ino_offset
		    + (node->ino.symlink.name - (char *) &node->ino));
470 471 472 473 474 475 476 477 478
  if (err)
    {
      grub_free (ret);
      return NULL;
    }
  ret[grub_le_to_cpu32 (node->ino.symlink.namelen)] = 0;
  return ret;
}

479 480
static int
grub_squash_iterate_dir (grub_fshelp_node_t dir,
481
			 grub_fshelp_iterate_dir_hook_t hook, void *hook_data)
482
{
483
  grub_uint32_t off;
484
  grub_uint32_t endoff;
485
  grub_uint64_t chunk;
486
  unsigned i;
487

488
  /* FIXME: why - 3 ? */
489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505
  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;
    }
506

507 508 509 510 511 512 513
  {
    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]));
514
    if (hook (".", GRUB_FSHELP_DIR, node, hook_data))
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535
      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;

536
	if (hook ("..", GRUB_FSHELP_DIR, node, hook_data))
537 538 539 540
	  return 1;
      }
  }

541
  while (off < endoff)
542 543 544 545
    {
      struct grub_squash_dirent_header dh;
      grub_err_t err;

546
      err = read_chunk (dir->data, &dh, sizeof (dh),
547
			grub_le_to_cpu64 (dir->data->sb.diroffset)
548
			+ chunk, off);
549 550 551
      if (err)
	return 0;
      off += sizeof (dh);
552
      for (i = 0; i < (unsigned) grub_le_to_cpu32 (dh.nelems) + 1; i++)
553 554 555 556 557 558 559 560
	{
	  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;

561
	  err = read_chunk (dir->data, &di, sizeof (di),
562
			    grub_le_to_cpu64 (dir->data->sb.diroffset)
563
			    + chunk, off);
564 565 566 567
	  if (err)
	    return 0;
	  off += sizeof (di);

568
	  err = read_chunk (dir->data, &ino, sizeof (ino),
569 570 571
			    grub_le_to_cpu64 (dir->data->sb.inodeoffset)
			    + grub_le_to_cpu32 (dh.ino_chunk),
			    grub_cpu_to_le16 (di.ino_offset));
572 573 574 575 576 577
	  if (err)
	    return 0;

	  buf = grub_malloc (grub_le_to_cpu16 (di.namelen) + 2);
	  if (!buf)
	    return 0;
578
	  err = read_chunk (dir->data, buf,
579
			    grub_le_to_cpu16 (di.namelen) + 1,
580
			    grub_le_to_cpu64 (dir->data->sb.diroffset)
581
			    + chunk, off);
582 583 584 585 586
	  if (err)
	    return 0;

	  off += grub_le_to_cpu16 (di.namelen) + 1;
	  buf[grub_le_to_cpu16 (di.namelen) + 1] = 0;
587
	  if (grub_le_to_cpu16 (di.type) == SQUASH_TYPE_DIR)
588
	    filetype = GRUB_FSHELP_DIR;
589 590 591
	  if (grub_le_to_cpu16 (di.type) == SQUASH_TYPE_SYMLINK)
	    filetype = GRUB_FSHELP_SYMLINK;

592 593
	  node = grub_malloc (sizeof (*node)
			      + (dir->stsize + 1) * sizeof (dir->stack[0]));
594 595
	  if (! node)
	    return 0;
596

597 598 599 600 601 602 603
	  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++;
604
	  r = hook (buf, filetype, node, hook_data);
605 606 607 608 609 610 611 612 613 614 615 616 617 618

	  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;
619 620 621
  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);
622
 return read_chunk (data, &root->ino, sizeof (root->ino),
623
		    grub_le_to_cpu64 (data->sb.inodeoffset) 
624 625
		    + root->stack[0].ino_chunk,
		    root->stack[0].ino_offset);
626 627
}

628 629 630
static void
squash_unmount (struct grub_squash_data *data)
{
631 632 633
  if (data->xzdec)
    xz_dec_end (data->xzdec);
  grub_free (data->xzbuf);
634 635 636 637 638 639
  grub_free (data->ino.cumulated_block_sizes);
  grub_free (data->ino.block_sizes);
  grub_free (data);
}


640 641
/* Context for grub_squash_dir.  */
struct grub_squash_dir_ctx
642
{
643 644 645
  grub_fs_dir_hook_t hook;
  void *hook_data;
};
646

647 648 649 650 651 652 653 654 655 656 657 658 659 660 661
/* 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);
}
662

663 664 665 666 667
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 };
668 669 670 671 672 673 674 675 676 677 678 679 680 681
  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,
682
			 grub_squash_read_symlink, GRUB_FSHELP_DIR);
683
  if (!grub_errno)
684
    grub_squash_iterate_dir (fdiro, grub_squash_dir_iter, &ctx);
685

686
  squash_unmount (data);
687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707

  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,
708
			 grub_squash_read_symlink, GRUB_FSHELP_REG);
709 710
  if (grub_errno)
    {
711
      squash_unmount (data);
712 713
      return grub_errno;
    }
714

715
  file->data = data;
716 717 718
  data->ino.ino = fdiro->ino;
  data->ino.block_sizes = NULL;
  data->ino.cumulated_block_sizes = NULL;
719 720
  data->ino.ino_chunk = fdiro->stack[fdiro->stsize - 1].ino_chunk;
  data->ino.ino_offset = fdiro->stack[fdiro->stsize - 1].ino_offset;
721

722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737
  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);
      }
    }
738

739 740
  grub_free (fdiro);

741 742 743
  return GRUB_ERR_NONE;
}

744 745 746 747 748 749 750
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;
751
  grub_uint64_t a = 0;
752 753 754
  grub_size_t i;
  grub_size_t origlen = len;

755 756 757 758 759 760 761 762 763
  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;
    }
764 765 766

  if (!ino->block_sizes)
    {
767
      grub_off_t total_size = 0;
768
      grub_size_t total_blocks;
769
      grub_size_t block_offset = 0;
770
      switch (ino->ino.type)
771
	{
772
	case grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_REGULAR):
773 774 775
	  total_size = grub_le_to_cpu64 (ino->ino.long_file.size);
	  block_offset = ((char *) &ino->ino.long_file.block_size
			  - (char *) &ino->ino);
776 777
	  break;
	case grub_cpu_to_le16_compile_time (SQUASH_TYPE_REGULAR):
778 779 780
	  total_size = grub_le_to_cpu32 (ino->ino.file.size);
	  block_offset = ((char *) &ino->ino.file.block_size
			  - (char *) &ino->ino);
781
	  break;
782
	}
783
      total_blocks = ((total_size + data->blksz - 1) >> data->log2_blksz);
784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816
      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);
817
  i = off >> data->log2_blksz;
818
  cumulated_uncompressed_size = data->blksz * (grub_disk_addr_t) i;
819 820
  while (cumulated_uncompressed_size < off + len)
    {
821
      grub_size_t boff, curread;
822
      boff = off - cumulated_uncompressed_size;
823 824 825
      curread = data->blksz - boff;
      if (curread > len)
	curread = len;
826 827
      if (!(ino->block_sizes[i]
	    & grub_cpu_to_le32_compile_time (SQUASH_BLOCK_UNCOMPRESSED)))
828 829
	{
	  char *block;
830 831 832
	  grub_size_t csize;
	  csize = grub_le_to_cpu32 (ino->block_sizes[i]) & ~SQUASH_BLOCK_FLAGS;
	  block = grub_malloc (csize);
833 834 835 836 837 838 839
	  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),
840
				csize, block);
841 842 843 844 845
	  if (err)
	    {
	      grub_free (block);
	      return -1;
	    }
846 847
	  if (data->decompress (block, csize, boff, buf, curread, data)
	      != (grub_ssize_t) curread)
848 849 850 851 852 853 854 855
	    {
	      grub_free (block);
	      if (!grub_errno)
		grub_error (GRUB_ERR_BAD_FS, "incorrect compressed chunk");
	      return -1;
	    }
	  grub_free (block);
	}
856 857 858 859 860 861
      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),
862
			      curread, buf);
863 864
      if (err)
	return -1;
865 866 867
      off += curread;
      len -= curread;
      buf += curread;
868 869 870 871 872 873 874
      cumulated_uncompressed_size += grub_le_to_cpu32 (data->sb.block_size);
      i++;
    }
  return origlen;
}


875
static grub_ssize_t
876
grub_squash_read_data (struct grub_squash_data *data, 
877
		       struct grub_squash_cache_inode *ino,
878
		       grub_off_t off, char *buf, grub_size_t len)
879 880
{
  grub_err_t err;
881 882
  grub_uint64_t a = 0, b;
  grub_uint32_t fragment = 0;
883
  int compressed = 0;
884
  struct grub_squash_frag_desc frag;
885

886
  switch (ino->ino.type)
887
    {
888
    case grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_REGULAR):
889 890
      a = grub_le_to_cpu64 (ino->ino.long_file.chunk);
      fragment = grub_le_to_cpu32 (ino->ino.long_file.fragment);
891 892
      break;
    case grub_cpu_to_le16_compile_time (SQUASH_TYPE_REGULAR):
893 894
      a = grub_le_to_cpu32 (ino->ino.file.chunk);
      fragment = grub_le_to_cpu32 (ino->ino.file.fragment);
895
      break;
896
    }
897

898 899 900 901 902 903 904 905
  if (fragment == 0xffffffff)
    return direct_read (data, ino, off, buf, len);
 
  err = read_chunk (data, &frag, sizeof (frag),
		    data->fragments, sizeof (frag) * fragment);
  if (err)
    return -1;
  a += grub_le_to_cpu64 (frag.offset);
906
  compressed = !(frag.size & grub_cpu_to_le32_compile_time (SQUASH_BLOCK_UNCOMPRESSED));
907
  if (ino->ino.type == grub_cpu_to_le16_compile_time (SQUASH_TYPE_LONG_REGULAR))
908
    b = grub_le_to_cpu32 (ino->ino.long_file.offset) + off;
909 910 911
  else
    b = grub_le_to_cpu32 (ino->ino.file.offset) + off;
  
912 913
  /* FIXME: cache uncompressed chunks.  */
  if (compressed)
914 915
    {
      char *block;
916
      block = grub_malloc (grub_le_to_cpu32 (frag.size));
917 918 919 920 921
      if (!block)
	return -1;
      err = grub_disk_read (data->disk,
			    a >> GRUB_DISK_SECTOR_BITS,
			    a & (GRUB_DISK_SECTOR_SIZE - 1),
922
			    grub_le_to_cpu32 (frag.size), block);
923 924 925 926 927
      if (err)
	{
	  grub_free (block);
	  return -1;
	}
928 929
      if (data->decompress (block, grub_le_to_cpu32 (frag.size),
			    b, buf, len, data)
930 931 932 933 934 935 936 937 938
	  != (grub_ssize_t) len)
	{
	  grub_free (block);
	  if (!grub_errno)
	    grub_error (GRUB_ERR_BAD_FS, "incorrect compressed chunk");
	  return -1;
	}
      grub_free (block);
    }
939
  else
940 941
    {
      err = grub_disk_read (data->disk, (a + b) >> GRUB_DISK_SECTOR_BITS,
942
			  (a + b) & (GRUB_DISK_SECTOR_SIZE - 1), len, buf);
943 944 945
      if (err)
	return -1;
    }
946 947 948
  return len;
}

949 950 951 952 953
static grub_ssize_t
grub_squash_read (grub_file_t file, char *buf, grub_size_t len)
{
  struct grub_squash_data *data = file->data;

954
  return grub_squash_read_data (data, &data->ino,
955 956 957
				file->offset, buf, len);
}

958 959 960
static grub_err_t
grub_squash_close (grub_file_t file)
{
961
  squash_unmount (file->data);
962 963 964
  return GRUB_ERR_NONE;
}

965 966 967 968 969 970 971 972 973
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);
974
  squash_unmount (data);
975 976 977
  return GRUB_ERR_NONE;
} 

978 979 980 981 982 983 984
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,
985
    .mtime = grub_squash_mtime,
986 987
#ifdef GRUB_UTIL
    .reserved_first_sector = 0,
988
    .blocklist_install = 0,
989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002
#endif
    .next = 0
  };

GRUB_MOD_INIT(squash4)
{
  grub_fs_register (&grub_squash_fs);
}

GRUB_MOD_FINI(squash4)
{
  grub_fs_unregister (&grub_squash_fs);
}