hfs.c 33.7 KB
Newer Older
1 2 3
/* hfs.c - HFS.  */
/*
 *  GRUB  --  GRand Unified Bootloader
4
 *  Copyright (C) 2004,2005,2006,2007,2008,2009  Free Software Foundation, Inc.
5
 *
6
 *  GRUB is free software: you can redistribute it and/or modify
7
 *  it under the terms of the GNU General Public License as published by
8
 *  the Free Software Foundation, either version 3 of the License, or
9 10
 *  (at your option) any later version.
 *
11
 *  GRUB is distributed in the hope that it will be useful,
12 13 14 15 16
 *  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
17
 *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
18 19
 */

20 21 22
/* HFS is documented at
   http://developer.apple.com/documentation/mac/Files/Files-2.html */

23 24 25 26 27 28 29
#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>
30
#include <grub/hfs.h>
31
#include <grub/i18n.h>
32
#include <grub/fshelp.h>
33

34 35
GRUB_MOD_LICENSE ("GPLv3+");

36
#define	GRUB_HFS_SBLOCK		2
37
#define GRUB_HFS_EMBED_HFSPLUS_SIG 0x482B
38 39 40 41 42 43 44 45 46 47 48 49

#define GRUB_HFS_BLKS		(data->blksz >> 9)

#define GRUB_HFS_NODE_LEAF	0xFF

/* The two supported filesystems a record can have.  */
enum
  {
    GRUB_HFS_FILETYPE_DIR = 1,
    GRUB_HFS_FILETYPE_FILE = 2
  };

50 51 52 53 54 55 56 57 58 59
/* Catalog node ID (CNID).  */
enum grub_hfs_cnid_type
  {
    GRUB_HFS_CNID_ROOT_PARENT = 1,
    GRUB_HFS_CNID_ROOT = 2,
    GRUB_HFS_CNID_EXT = 3,
    GRUB_HFS_CNID_CAT = 4,
    GRUB_HFS_CNID_BAD = 5
  };

60
/* A node descriptor.  This is the header of every node.  */
61 62 63 64 65 66 67 68
struct grub_hfs_node
{
  grub_uint32_t next;
  grub_uint32_t prev;
  grub_uint8_t type;
  grub_uint8_t level;
  grub_uint16_t reccnt;
  grub_uint16_t unused;
69
} GRUB_PACKED;
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84

/* The head of the B*-Tree.  */
struct grub_hfs_treeheader
{
  grub_uint16_t tree_depth;
  /* The number of the first node.  */
  grub_uint32_t root_node;
  grub_uint32_t leaves;
  grub_uint32_t first_leaf;
  grub_uint32_t last_leaf;
  grub_uint16_t node_size;
  grub_uint16_t key_size;
  grub_uint32_t nodes;
  grub_uint32_t free_nodes;
  grub_uint8_t unused[76];
85
} GRUB_PACKED;
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109

/* The state of a mounted HFS filesystem.  */
struct grub_hfs_data
{
  struct grub_hfs_sblock sblock;
  grub_disk_t disk;
  grub_hfs_datarecord_t extents;
  int fileid;
  int size;
  int ext_root;
  int ext_size;
  int cat_root;
  int cat_size;
  int blksz;
  int log2_blksz;
  int rootdir;
};

/* The key as used on disk in a catalog tree.  This is used to lookup
   file/directory nodes by parent directory ID and filename.  */
struct grub_hfs_catalog_key
{
  grub_uint8_t unused;
  grub_uint32_t parent_dir;
110

111 112 113 114 115
  /* Filename length.  */
  grub_uint8_t strlen;

  /* Filename.  */
  grub_uint8_t str[31];
116
} GRUB_PACKED;
117 118 119 120 121 122 123 124 125 126 127 128

/* The key as used on disk in a extent overflow tree.  Using this key
   the extents can be looked up using a fileid and logical start block
   as index.  */
struct grub_hfs_extent_key
{
  /* The kind of fork.  This is used to store meta information like
     icons, attributes, etc.  We will only use the datafork, which is
     0.  */
  grub_uint8_t forktype;
  grub_uint32_t fileid;
  grub_uint16_t first_block;
129
} GRUB_PACKED;
130

131
/* A directory record.  This is used to find out the directory ID.  */
132 133 134 135 136 137
struct grub_hfs_dirrec
{
  /* For a directory, type == 1.  */
  grub_uint8_t type;
  grub_uint8_t unused[5];
  grub_uint32_t dirid;
138 139
  grub_uint32_t ctime;
  grub_uint32_t mtime;
140
} GRUB_PACKED;
141 142 143 144 145 146 147 148 149 150

/* Information about a file.  */
struct grub_hfs_filerec
{
  /* For a file, type == 2.  */
  grub_uint8_t type;
  grub_uint8_t unused[19];
  grub_uint32_t fileid;
  grub_uint8_t unused2[2];
  grub_uint32_t size;
151 152 153
  grub_uint8_t unused3[18];
  grub_uint32_t mtime;
  grub_uint8_t unused4[22];
154 155 156 157

  /* The first 3 extents of the file.  The other extents can be found
     in the extent overflow file.  */
  grub_hfs_datarecord_t extents;
158
} GRUB_PACKED;
159 160 161 162 163 164

/* A record descriptor, both key and data, used to pass to call back
   functions.  */
struct grub_hfs_record
{
  void *key;
165
  grub_size_t keylen;
166
  void *data;
167
  grub_size_t datalen;
168 169 170 171 172
};

static grub_dl_t my_mod;

static int grub_hfs_find_node (struct grub_hfs_data *, char *,
173
			       grub_uint32_t, int, char *, grub_size_t);
174 175 176 177 178 179 180 181 182 183 184

/* Find block BLOCK of the file FILE in the mounted UFS filesystem
   DATA.  The first 3 extents are described by DAT.  If cache is set,
   using caching to improve non-random reads.  */
static unsigned int
grub_hfs_block (struct grub_hfs_data *data, grub_hfs_datarecord_t dat,
		int file, int block, int cache)
{
  grub_hfs_datarecord_t dr;
  int pos = 0;
  struct grub_hfs_extent_key key;
185

186 187
  int tree = 0;
  static int cache_file = 0;
188
  static int cache_pos = 0;
189
  static grub_hfs_datarecord_t cache_dr;
190

191
  grub_memcpy (dr, dat, sizeof (dr));
192

193 194
  key.forktype = 0;
  key.fileid = grub_cpu_to_be32 (file);
195

196 197 198 199 200 201
  if (cache && cache_file == file  && block > cache_pos)
    {
      pos = cache_pos;
      key.first_block = grub_cpu_to_be16 (pos);
      grub_memcpy (dr, cache_dr, sizeof (cache_dr));
    }
202

203 204 205
  for (;;)
    {
      int i;
206

207 208 209 210 211 212 213
      /* Try all 3 extents.  */
      for (i = 0; i < 3; i++)
	{
	  /* Check if the block is stored in this extent.  */
	  if (grub_be_to_cpu16 (dr[i].count) + pos > block)
	    {
	      int first = grub_be_to_cpu16 (dr[i].first_block);
214

215 216 217 218 219 220 221 222
	      /* If the cache is enabled, store the current position
		 in the tree.  */
	      if (tree && cache)
		{
		  cache_file = file;
		  cache_pos = pos;
		  grub_memcpy (cache_dr, dr, sizeof (cache_dr));
		}
223

224 225 226
	      return (grub_be_to_cpu16 (data->sblock.first_block)
		      + (first + block - pos) * GRUB_HFS_BLKS);
	    }
227

228 229 230
	  /* Try the next extent.  */
	  pos += grub_be_to_cpu16 (dr[i].count);
	}
231

232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
      /* Lookup the block in the extent overflow file.  */
      key.first_block = grub_cpu_to_be16 (pos);
      tree = 1;
      grub_hfs_find_node (data, (char *) &key, data->ext_root,
			  1, (char *) &dr, sizeof (dr));
      if (grub_errno)
	return 0;
    }
}


/* Read LEN bytes from the file described by DATA starting with byte
   POS.  Return the amount of read bytes in READ.  */
static grub_ssize_t
grub_hfs_read_file (struct grub_hfs_data *data,
247
		    grub_disk_read_hook_t read_hook, void *read_hook_data,
248
		    grub_uint32_t pos, grub_size_t len, char *buf)
249
{
250 251
  grub_off_t i;
  grub_off_t blockcnt;
252

253 254 255
  /* Files are at most 2G/4G - 1 bytes on hfs. Avoid 64-bit division.
     Moreover len > 0 as checked in upper layer.  */
  blockcnt = (len + pos - 1) / data->blksz + 1;
256

257
  for (i = pos / data->blksz; i < blockcnt; i++)
258
    {
259 260 261
      grub_disk_addr_t blknr;
      grub_off_t blockoff;
      grub_off_t blockend = data->blksz;
262 263

      int skipfirst = 0;
264

265
      blockoff = pos % data->blksz;
266

267 268 269
      blknr = grub_hfs_block (data, data->extents, data->fileid, i, 1);
      if (grub_errno)
	return -1;
270

271 272 273
      /* Last block.  */
      if (i == blockcnt - 1)
	{
274
	  blockend = (len + pos) % data->blksz;
275

276
	  /* The last portion is exactly EXT2_BLOCK_SIZE (data).  */
277
	  if (! blockend)
278 279 280 281
	    blockend = data->blksz;
	}

      /* First block.  */
282
      if (i == pos / data->blksz)
283 284 285 286 287 288 289 290 291
	{
	  skipfirst = blockoff;
	  blockend -= skipfirst;
	}

      /* If the block number is 0 this block is not stored on disk but
	 is zero filled instead.  */
      if (blknr)
	{
292
	  data->disk->read_hook = read_hook;
293
	  data->disk->read_hook_data = read_hook_data;
294 295 296 297 298 299
	  grub_disk_read (data->disk, blknr, skipfirst,
			  blockend, buf);
	  data->disk->read_hook = 0;
	  if (grub_errno)
	    return -1;
	}
300

301 302
      buf += data->blksz - skipfirst;
    }
303

304 305 306 307 308 309 310 311 312 313 314 315
  return len;
}


/* Mount the filesystem on the disk DISK.  */
static struct grub_hfs_data *
grub_hfs_mount (grub_disk_t disk)
{
  struct grub_hfs_data *data;
  struct grub_hfs_catalog_key key;
  struct grub_hfs_dirrec dir;
  int first_block;
316

317 318 319 320 321
  struct
  {
    struct grub_hfs_node node;
    struct grub_hfs_treeheader head;
  } treehead;
322

323 324 325 326 327 328
  data = grub_malloc (sizeof (struct grub_hfs_data));
  if (!data)
    return 0;

  /* Read the superblock.  */
  if (grub_disk_read (disk, GRUB_HFS_SBLOCK, 0,
329
		      sizeof (struct grub_hfs_sblock), &data->sblock))
330
    goto fail;
331

332
  /* Check if this is a HFS filesystem.  */
333
  if (grub_be_to_cpu16 (data->sblock.magic) != GRUB_HFS_MAGIC
334
      || data->sblock.blksz == 0
335
      || (data->sblock.blksz & grub_cpu_to_be32_compile_time (0xc00001ff)))
336
    {
337 338 339 340 341 342 343 344
      grub_error (GRUB_ERR_BAD_FS, "not an HFS filesystem");
      goto fail;
    }

  /* Check if this is an embedded HFS+ filesystem.  */
  if (grub_be_to_cpu16 (data->sblock.embed_sig) == GRUB_HFS_EMBED_HFSPLUS_SIG)
    {
      grub_error (GRUB_ERR_BAD_FS, "embedded HFS+ filesystem");
345 346
      goto fail;
    }
347

348 349
  data->blksz = grub_be_to_cpu32 (data->sblock.blksz);
  data->disk = disk;
350

351
  /* Lookup the root node of the extent overflow tree.  */
352
  first_block = ((grub_be_to_cpu16 (data->sblock.extent_recs[0].first_block)
353 354
		  * GRUB_HFS_BLKS)
		 + grub_be_to_cpu16 (data->sblock.first_block));
355

356
  if (grub_disk_read (data->disk, first_block, 0,
357
		      sizeof (treehead), &treehead))
358 359 360
    goto fail;
  data->ext_root = grub_be_to_cpu32 (treehead.head.root_node);
  data->ext_size = grub_be_to_cpu16 (treehead.head.node_size);
361

362
  /* Lookup the root node of the catalog tree.  */
363
  first_block = ((grub_be_to_cpu16 (data->sblock.catalog_recs[0].first_block)
364 365 366
		  * GRUB_HFS_BLKS)
		 + grub_be_to_cpu16 (data->sblock.first_block));
  if (grub_disk_read (data->disk, first_block, 0,
367
		      sizeof (treehead), &treehead))
368 369 370
    goto fail;
  data->cat_root = grub_be_to_cpu32 (treehead.head.root_node);
  data->cat_size = grub_be_to_cpu16 (treehead.head.node_size);
371

372 373 374 375 376
  if (data->cat_size == 0
      || data->blksz < data->cat_size
      || data->blksz < data->ext_size)
    goto fail;

377 378
  /* Lookup the root directory node in the catalog tree using the
     volume name.  */
379
  key.parent_dir = grub_cpu_to_be32_compile_time (1);
380
  key.strlen = data->sblock.volname[0];
381
  grub_strcpy ((char *) key.str, (char *) (data->sblock.volname + 1));
382

383 384 385
  if (grub_hfs_find_node (data, (char *) &key, data->cat_root,
			  0, (char *) &dir, sizeof (dir)) == 0)
    {
386
      grub_error (GRUB_ERR_BAD_FS, "cannot find the HFS root directory");
387 388
      goto fail;
    }
389

390 391 392 393
  if (grub_errno)
    goto fail;

  data->rootdir = grub_be_to_cpu32 (dir.dirid);
394

395
  return data;
396 397
 fail:
  grub_free (data);
398

399
  if (grub_errno == GRUB_ERR_OUT_OF_RANGE)
400
    grub_error (GRUB_ERR_BAD_FS, "not a HFS filesystem");
401

402
  return 0;
403 404
}

405
/* Compare the K1 and K2 catalog file keys using HFS character ordering.  */
406
static int
407 408
grub_hfs_cmp_catkeys (const struct grub_hfs_catalog_key *k1,
		      const struct grub_hfs_catalog_key *k2)
409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628
{
  /* Taken from hfsutils 3.2.6 and converted to a readable form */
  static const unsigned char hfs_charorder[256] = {
    [0x00] = 0,
    [0x01] = 1,
    [0x02] = 2,
    [0x03] = 3,
    [0x04] = 4,
    [0x05] = 5,
    [0x06] = 6,
    [0x07] = 7,
    [0x08] = 8,
    [0x09] = 9,
    [0x0A] = 10,
    [0x0B] = 11,
    [0x0C] = 12,
    [0x0D] = 13,
    [0x0E] = 14,
    [0x0F] = 15,
    [0x10] = 16,
    [0x11] = 17,
    [0x12] = 18,
    [0x13] = 19,
    [0x14] = 20,
    [0x15] = 21,
    [0x16] = 22,
    [0x17] = 23,
    [0x18] = 24,
    [0x19] = 25,
    [0x1A] = 26,
    [0x1B] = 27,
    [0x1C] = 28,
    [0x1D] = 29,
    [0x1E] = 30,
    [0x1F] = 31,
    [' '] = 32,		[0xCA] = 32,
    ['!'] = 33,
    ['"'] = 34,
    [0xD2] = 35,
    [0xD3] = 36,
    [0xC7] = 37,
    [0xC8] = 38,
    ['#'] = 39,
    ['$'] = 40,
    ['%'] = 41,
    ['&'] = 42,
    ['\''] = 43,
    [0xD4] = 44,
    [0xD5] = 45,
    ['('] = 46,
    [')'] = 47,
    ['*'] = 48,
    ['+'] = 49,
    [','] = 50,
    ['-'] = 51,
    ['.'] = 52,
    ['/'] = 53,
    ['0'] = 54,
    ['1'] = 55,
    ['2'] = 56,
    ['3'] = 57,
    ['4'] = 58,
    ['5'] = 59,
    ['6'] = 60,
    ['7'] = 61,
    ['8'] = 62,
    ['9'] = 63,
    [':'] = 64,
    [';'] = 65,
    ['<'] = 66,
    ['='] = 67,
    ['>'] = 68,
    ['?'] = 69,
    ['@'] = 70,
    ['A'] = 71,		['a'] = 71,
    [0x88] = 72,	[0xCB] = 72,
    [0x80] = 73,	[0x8A] = 73,
    [0x8B] = 74,	[0xCC] = 74,
    [0x81] = 75,	[0x8C] = 75,
    [0xAE] = 76,	[0xBE] = 76,
    ['`'] = 77,
    [0x87] = 78,
    [0x89] = 79,
    [0xBB] = 80,
    ['B'] = 81,		['b'] = 81,
    ['C'] = 82,		['c'] = 82,
    [0x82] = 83,	[0x8D] = 83,
    ['D'] = 84,		['d'] = 84,
    ['E'] = 85,		['e'] = 85,
    [0x83] = 86,	[0x8E] = 86,
    [0x8F] = 87,
    [0x90] = 88,
    [0x91] = 89,
    ['F'] = 90,		['f'] = 90,
    ['G'] = 91,		['g'] = 91,
    ['H'] = 92,		['h'] = 92,
    ['I'] = 93,		['i'] = 93,
    [0x92] = 94,
    [0x93] = 95,
    [0x94] = 96,
    [0x95] = 97,
    ['J'] = 98,		['j'] = 98,
    ['K'] = 99,		['k'] = 99,
    ['L'] = 100,	['l'] = 100,
    ['M'] = 101,	['m'] = 101,
    ['N'] = 102,	['n'] = 102,
    [0x84] = 103,	[0x96] = 103,
    ['O'] = 104,	['o'] = 104,
    [0x85] = 105,	[0x9A] = 105,
    [0x9B] = 106,	[0xCD] = 106,
    [0xAF] = 107,	[0xBF] = 107,
    [0xCE] = 108,	[0xCF] = 108,
    [0x97] = 109,
    [0x98] = 110,
    [0x99] = 111,
    [0xBC] = 112,
    ['P'] = 113,	['p'] = 113,
    ['Q'] = 114,	['q'] = 114,
    ['R'] = 115,	['r'] = 115,
    ['S'] = 116,	['s'] = 116,
    [0xA7] = 117,
    ['T'] = 118,	['t'] = 118,
    ['U'] = 119,	['u'] = 119,
    [0x86] = 120,	[0x9F] = 120,
    [0x9C] = 121,
    [0x9D] = 122,
    [0x9E] = 123,
    ['V'] = 124,	['v'] = 124,
    ['W'] = 125,	['w'] = 125,
    ['X'] = 126,	['x'] = 126,
    ['Y'] = 127,	['y'] = 127,
    [0xD8] = 128,
    ['Z'] = 129,	['z'] = 129,
    ['['] = 130,
    ['\\'] = 131,
    [']'] = 132,
    ['^'] = 133,
    ['_'] = 134,
    ['{'] = 135,
    ['|'] = 136,
    ['}'] = 137,
    ['~'] = 138,
    [0x7F] = 139,
    [0xA0] = 140,
    [0xA1] = 141,
    [0xA2] = 142,
    [0xA3] = 143,
    [0xA4] = 144,
    [0xA5] = 145,
    [0xA6] = 146,
    [0xA8] = 147,
    [0xA9] = 148,
    [0xAA] = 149,
    [0xAB] = 150,
    [0xAC] = 151,
    [0xAD] = 152,
    [0xB0] = 153,
    [0xB1] = 154,
    [0xB2] = 155,
    [0xB3] = 156,
    [0xB4] = 157,
    [0xB5] = 158,
    [0xB6] = 159,
    [0xB7] = 160,
    [0xB8] = 161,
    [0xB9] = 162,
    [0xBA] = 163,
    [0xBD] = 164,
    [0xC0] = 165,
    [0xC1] = 166,
    [0xC2] = 167,
    [0xC3] = 168,
    [0xC4] = 169,
    [0xC5] = 170,
    [0xC6] = 171,
    [0xC9] = 172,
    [0xD0] = 173,
    [0xD1] = 174,
    [0xD6] = 175,
    [0xD7] = 176,
    [0xD9] = 177,
    [0xDA] = 178,
    [0xDB] = 179,
    [0xDC] = 180,
    [0xDD] = 181,
    [0xDE] = 182,
    [0xDF] = 183,
    [0xE0] = 184,
    [0xE1] = 185,
    [0xE2] = 186,
    [0xE3] = 187,
    [0xE4] = 188,
    [0xE5] = 189,
    [0xE6] = 190,
    [0xE7] = 191,
    [0xE8] = 192,
    [0xE9] = 193,
    [0xEA] = 194,
    [0xEB] = 195,
    [0xEC] = 196,
    [0xED] = 197,
    [0xEE] = 198,
    [0xEF] = 199,
    [0xF0] = 200,
    [0xF1] = 201,
    [0xF2] = 202,
    [0xF3] = 203,
    [0xF4] = 204,
    [0xF5] = 205,
    [0xF6] = 206,
    [0xF7] = 207,
    [0xF8] = 208,
    [0xF9] = 209,
    [0xFA] = 210,
    [0xFB] = 211,
    [0xFC] = 212,
    [0xFD] = 213,
    [0xFE] = 214,
    [0xFF] = 215,
  };
629 630 631
  int i;
  int cmp;
  int minlen = (k1->strlen < k2->strlen) ? k1->strlen : k2->strlen;
632

633 634 635
  cmp = (grub_be_to_cpu32 (k1->parent_dir) - grub_be_to_cpu32 (k2->parent_dir));
  if (cmp != 0)
    return cmp;
636

637
  for (i = 0; i < minlen; i++)
638
    {
639 640 641
      cmp = (hfs_charorder[k1->str[i]] - hfs_charorder[k2->str[i]]);
      if (cmp != 0)
	return cmp;
642 643
    }

644 645
  /* Shorter strings precede long ones.  */
  return (k1->strlen - k2->strlen);
646 647 648 649 650
}


/* Compare the K1 and K2 extent overflow file keys.  */
static int
651 652
grub_hfs_cmp_extkeys (const struct grub_hfs_extent_key *k1,
		      const struct grub_hfs_extent_key *k2)
653 654 655 656 657
{
  int cmp = k1->forktype - k2->forktype;
  if (cmp == 0)
    cmp = grub_be_to_cpu32 (k1->fileid) - grub_be_to_cpu32 (k2->fileid);
  if (cmp == 0)
658
    cmp = (grub_be_to_cpu16 (k1->first_block)
659 660 661 662 663 664 665 666 667 668 669 670
	   - grub_be_to_cpu16 (k2->first_block));
  return cmp;
}


/* Iterate the records in the node with index IDX in the mounted HFS
   filesystem DATA.  This node holds data of the type TYPE (0 =
   catalog node, 1 = extent overflow node).  If this is set, continue
   iterating to the next node.  For every records, call NODE_HOOK.  */
static grub_err_t
grub_hfs_iterate_records (struct grub_hfs_data *data, int type, int idx,
			  int this, int (*node_hook) (struct grub_hfs_node *hnd,
671 672 673
						      struct grub_hfs_record *,
						      void *hook_arg),
			  void *hook_arg)
674
{
675
  grub_size_t nodesize = type == 0 ? data->cat_size : data->ext_size;
676

677
  union node_union
678 679
  {
    struct grub_hfs_node node;
680 681 682 683 684 685 686 687 688 689
    char rawnode[0];
    grub_uint16_t offsets[0];
  } *node;

  if (nodesize < sizeof (struct grub_hfs_node))
    nodesize = sizeof (struct grub_hfs_node);

  node = grub_malloc (nodesize);
  if (!node)
    return grub_errno;
690

691 692 693 694 695
  do
    {
      int i;
      struct grub_hfs_extent *dat;
      int blk;
696
      grub_uint16_t reccnt;
697 698

      dat = (struct grub_hfs_extent *) (type == 0
699 700
					? (&data->sblock.catalog_recs)
					: (&data->sblock.extent_recs));
701

702 703
      /* Read the node into memory.  */
      blk = grub_hfs_block (data, dat,
704 705
                            (type == 0) ? GRUB_HFS_CNID_CAT : GRUB_HFS_CNID_EXT,
			    idx / (data->blksz / nodesize), 0);
706
      blk += (idx % (data->blksz / nodesize));
707

708 709 710 711 712 713
      if (grub_errno || grub_disk_read (data->disk, blk, 0,
					nodesize, node))
	{
	  grub_free (node);
	  return grub_errno;
	}
714

715 716 717 718
      reccnt = grub_be_to_cpu16 (node->node.reccnt);
      if (reccnt > (nodesize >> 1))
	reccnt = (nodesize >> 1);

719
      /* Iterate over all records in this node.  */
720
      for (i = 0; i < reccnt; i++)
721 722 723 724 725 726
	{
	  int pos = (nodesize >> 1) - 1 - i;
 	  struct pointer
	  {
	    grub_uint8_t keylen;
	    grub_uint8_t key;
727
	  } GRUB_PACKED *pnt;
728 729 730 731 732 733
	  grub_uint16_t off = grub_be_to_cpu16 (node->offsets[pos]);
	  if (off > nodesize - sizeof(*pnt))
	    continue;
	  pnt = (struct pointer *) (off + node->rawnode);
	  if (nodesize < (grub_size_t) off + pnt->keylen + 1)
	    continue;
734 735

	  struct grub_hfs_record rec =
736 737 738 739
	    {
	      &pnt->key,
	      pnt->keylen,
	      &pnt->key + pnt->keylen +(pnt->keylen + 1) % 2,
740
	      nodesize - off - pnt->keylen - 1
741
	    };
742

743 744 745 746 747
	  if (node_hook (&node->node, &rec, hook_arg))
	    {
	      grub_free (node);
	      return 0;
	    }
748
	}
749

750
      idx = grub_be_to_cpu32 (node->node.next);
751
    } while (idx && this);
752
  grub_free (node);
753 754 755
  return 0;
}

756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805
struct grub_hfs_find_node_node_found_ctx
{
  int found;
  int isleaf;
  int done;
  int type;
  const char *key;
  char *datar;
  grub_size_t datalen;
};

static int
grub_hfs_find_node_node_found (struct grub_hfs_node *hnd, struct grub_hfs_record *rec,
			       void *hook_arg)
{
  struct grub_hfs_find_node_node_found_ctx *ctx = hook_arg;
  int cmp = 1;

  if (ctx->type == 0)
    cmp = grub_hfs_cmp_catkeys (rec->key, (const void *) ctx->key);
  else
    cmp = grub_hfs_cmp_extkeys (rec->key, (const void *) ctx->key);

  /* If the key is smaller or equal to the current node, mark the
     entry.  In case of a non-leaf mode it will be used to lookup
     the rest of the tree.  */
  if (cmp <= 0)
    ctx->found = grub_be_to_cpu32 (grub_get_unaligned32 (rec->data));
  else /* The key can not be found in the tree. */
    return 1;

  /* Check if this node is a leaf node.  */
  if (hnd->type == GRUB_HFS_NODE_LEAF)
    {
      ctx->isleaf = 1;

      /* Found it!!!!  */
      if (cmp == 0)
	{
	  ctx->done = 1;

	  grub_memcpy (ctx->datar, rec->data,
		       rec->datalen < ctx->datalen ? rec->datalen : ctx->datalen);
	  return 1;
	}
    }

  return 0;
}

806 807 808 809 810 811 812

/* Lookup a record in the mounted filesystem DATA using the key KEY.
   The index of the node on top of the tree is IDX.  The tree is of
   the type TYPE (0 = catalog node, 1 = extent overflow node).  Return
   the data in DATAR with a maximum length of DATALEN.  */
static int
grub_hfs_find_node (struct grub_hfs_data *data, char *key,
813
		    grub_uint32_t idx, int type, char *datar, grub_size_t datalen)
814
{
815
  struct grub_hfs_find_node_node_found_ctx ctx =
816
    {
817 818 819 820 821 822 823 824
      .found = -1,
      .isleaf = 0,
      .done = 0,
      .type = type,
      .key = key,
      .datar = datar,
      .datalen = datalen
    };
825

826 827
  do
    {
828
      ctx.found = -1;
829

830
      if (grub_hfs_iterate_records (data, type, idx, 0, grub_hfs_find_node_node_found, &ctx))
831
        return 0;
832

833
      if (ctx.found == -1)
834
        return 0;
835

836 837
      idx = ctx.found;
    } while (! ctx.isleaf);
838

839
  return ctx.done;
840 841
}

842 843 844 845 846 847 848 849 850
struct grub_hfs_iterate_dir_node_found_ctx
{
  grub_uint32_t dir_be;
  int found;
  int isleaf;
  grub_uint32_t next;
  int (*hook) (struct grub_hfs_record *, void *hook_arg);
  void *hook_arg;
};
851

852 853 854
static int
grub_hfs_iterate_dir_node_found (struct grub_hfs_node *hnd, struct grub_hfs_record *rec,
				 void *hook_arg)
855
{
856 857
  struct grub_hfs_iterate_dir_node_found_ctx *ctx = hook_arg;
  struct grub_hfs_catalog_key *ckey = rec->key;
858

859
  /* The lowest key possible with DIR as root directory.  */
860
  const struct grub_hfs_catalog_key key = {0, ctx->dir_be, 0, ""};
861

862 863
  if (grub_hfs_cmp_catkeys (rec->key, &key) <= 0)
    ctx->found = grub_be_to_cpu32 (grub_get_unaligned32 (rec->data));
864

865
  if (hnd->type == 0xFF && ckey->strlen > 0)
866
    {
867 868
      ctx->isleaf = 1;
      ctx->next = grub_be_to_cpu32 (hnd->next);
869

870 871 872 873
      /* An entry was found.  */
      if (ckey->parent_dir == ctx->dir_be)
	return ctx->hook (rec, ctx->hook_arg);
    }
874

875 876
  return 0;
}
877

878 879 880 881 882 883 884 885 886 887 888
static int
grub_hfs_iterate_dir_it_dir (struct grub_hfs_node *hnd __attribute ((unused)),
			     struct grub_hfs_record *rec,
			     void *hook_arg)
{
  struct grub_hfs_catalog_key *ckey = rec->key;
  struct grub_hfs_iterate_dir_node_found_ctx *ctx = hook_arg;
  
  /* Stop when the entries do not match anymore.  */
  if (ckey->parent_dir != ctx->dir_be)
    return 1;
889

890 891
  return ctx->hook (rec, ctx->hook_arg);
}
892 893


894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910
/* Iterate over the directory with the id DIR.  The tree is searched
   starting with the node ROOT_IDX.  For every entry in this directory
   call HOOK.  */
static grub_err_t
grub_hfs_iterate_dir (struct grub_hfs_data *data, grub_uint32_t root_idx,
		      grub_uint32_t dir, int (*hook) (struct grub_hfs_record *, void *hook_arg),
		      void *hook_arg)
{
  struct grub_hfs_iterate_dir_node_found_ctx ctx =
  {
    .dir_be = grub_cpu_to_be32 (dir),
    .found = -1,
    .isleaf = 0,
    .next = 0,
    .hook = hook,
    .hook_arg = hook_arg
  };
911

912 913
  do
    {
914
      ctx.found = -1;
915

916
      if (grub_hfs_iterate_records (data, 0, root_idx, 0, grub_hfs_iterate_dir_node_found, &ctx))
917
        return grub_errno;
918

919
      if (ctx.found == -1)
920
        return 0;
921

922 923
      root_idx = ctx.found;
    } while (! ctx.isleaf);
924

925 926
  /* If there was a matching record in this leaf node, continue the
     iteration until the last record was found.  */
927
  grub_hfs_iterate_records (data, 0, ctx.next, 1, grub_hfs_iterate_dir_it_dir, &ctx);
928
  return grub_errno;
929 930
}

931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065
#define MAX_UTF8_PER_MAC_ROMAN 3

static const char macroman[0x80][MAX_UTF8_PER_MAC_ROMAN + 1] =
  {
    /* 80 */ "\xc3\x84",
    /* 81 */ "\xc3\x85",
    /* 82 */ "\xc3\x87",
    /* 83 */ "\xc3\x89",
    /* 84 */ "\xc3\x91",
    /* 85 */ "\xc3\x96",
    /* 86 */ "\xc3\x9c",
    /* 87 */ "\xc3\xa1",
    /* 88 */ "\xc3\xa0",
    /* 89 */ "\xc3\xa2",
    /* 8A */ "\xc3\xa4",
    /* 8B */ "\xc3\xa3",
    /* 8C */ "\xc3\xa5",
    /* 8D */ "\xc3\xa7",
    /* 8E */ "\xc3\xa9",
    /* 8F */ "\xc3\xa8",
    /* 90 */ "\xc3\xaa",
    /* 91 */ "\xc3\xab",
    /* 92 */ "\xc3\xad",
    /* 93 */ "\xc3\xac",
    /* 94 */ "\xc3\xae",
    /* 95 */ "\xc3\xaf",
    /* 96 */ "\xc3\xb1",
    /* 97 */ "\xc3\xb3",
    /* 98 */ "\xc3\xb2",
    /* 99 */ "\xc3\xb4",
    /* 9A */ "\xc3\xb6",
    /* 9B */ "\xc3\xb5",
    /* 9C */ "\xc3\xba",
    /* 9D */ "\xc3\xb9",
    /* 9E */ "\xc3\xbb",
    /* 9F */ "\xc3\xbc",
    /* A0 */ "\xe2\x80\xa0",
    /* A1 */ "\xc2\xb0",
    /* A2 */ "\xc2\xa2",
    /* A3 */ "\xc2\xa3",
    /* A4 */ "\xc2\xa7",
    /* A5 */ "\xe2\x80\xa2",
    /* A6 */ "\xc2\xb6",
    /* A7 */ "\xc3\x9f",
    /* A8 */ "\xc2\xae",
    /* A9 */ "\xc2\xa9",
    /* AA */ "\xe2\x84\xa2",
    /* AB */ "\xc2\xb4",
    /* AC */ "\xc2\xa8",
    /* AD */ "\xe2\x89\xa0",
    /* AE */ "\xc3\x86",
    /* AF */ "\xc3\x98",
    /* B0 */ "\xe2\x88\x9e",
    /* B1 */ "\xc2\xb1",
    /* B2 */ "\xe2\x89\xa4",
    /* B3 */ "\xe2\x89\xa5",
    /* B4 */ "\xc2\xa5",
    /* B5 */ "\xc2\xb5",
    /* B6 */ "\xe2\x88\x82",
    /* B7 */ "\xe2\x88\x91",
    /* B8 */ "\xe2\x88\x8f",
    /* B9 */ "\xcf\x80",
    /* BA */ "\xe2\x88\xab",
    /* BB */ "\xc2\xaa",
    /* BC */ "\xc2\xba",
    /* BD */ "\xce\xa9",
    /* BE */ "\xc3\xa6",
    /* BF */ "\xc3\xb8",
    /* C0 */ "\xc2\xbf",
    /* C1 */ "\xc2\xa1",
    /* C2 */ "\xc2\xac",
    /* C3 */ "\xe2\x88\x9a",
    /* C4 */ "\xc6\x92",
    /* C5 */ "\xe2\x89\x88",
    /* C6 */ "\xe2\x88\x86",
    /* C7 */ "\xc2\xab",
    /* C8 */ "\xc2\xbb",
    /* C9 */ "\xe2\x80\xa6",
    /* CA */ "\xc2\xa0",
    /* CB */ "\xc3\x80",
    /* CC */ "\xc3\x83",
    /* CD */ "\xc3\x95",
    /* CE */ "\xc5\x92",
    /* CF */ "\xc5\x93",
    /* D0 */ "\xe2\x80\x93",
    /* D1 */ "\xe2\x80\x94",
    /* D2 */ "\xe2\x80\x9c",
    /* D3 */ "\xe2\x80\x9d",
    /* D4 */ "\xe2\x80\x98",
    /* D5 */ "\xe2\x80\x99",
    /* D6 */ "\xc3\xb7",
    /* D7 */ "\xe2\x97\x8a",
    /* D8 */ "\xc3\xbf",
    /* D9 */ "\xc5\xb8",
    /* DA */ "\xe2\x81\x84",
    /* DB */ "\xe2\x82\xac",
    /* DC */ "\xe2\x80\xb9",
    /* DD */ "\xe2\x80\xba",
    /* DE */ "\xef\xac\x81",
    /* DF */ "\xef\xac\x82",
    /* E0 */ "\xe2\x80\xa1",
    /* E1 */ "\xc2\xb7",
    /* E2 */ "\xe2\x80\x9a",
    /* E3 */ "\xe2\x80\x9e",
    /* E4 */ "\xe2\x80\xb0",
    /* E5 */ "\xc3\x82",
    /* E6 */ "\xc3\x8a",
    /* E7 */ "\xc3\x81",
    /* E8 */ "\xc3\x8b",
    /* E9 */ "\xc3\x88",
    /* EA */ "\xc3\x8d",
    /* EB */ "\xc3\x8e",
    /* EC */ "\xc3\x8f",
    /* ED */ "\xc3\x8c",
    /* EE */ "\xc3\x93",
    /* EF */ "\xc3\x94",
    /* F0 */ "\xef\xa3\xbf",
    /* F1 */ "\xc3\x92",
    /* F2 */ "\xc3\x9a",
    /* F3 */ "\xc3\x9b",
    /* F4 */ "\xc3\x99",
    /* F5 */ "\xc4\xb1",
    /* F6 */ "\xcb\x86",
    /* F7 */ "\xcb\x9c",
    /* F8 */ "\xc2\xaf",
    /* F9 */ "\xcb\x98",
    /* FA */ "\xcb\x99",
    /* FB */ "\xcb\x9a",
    /* FC */ "\xc2\xb8",
    /* FD */ "\xcb\x9d",
    /* FE */ "\xcb\x9b",
    /* FF */ "\xcb\x87",
  };

static void
1066 1067
macroman_to_utf8 (char *to, const grub_uint8_t *from, grub_size_t len,
		  int translate_slash)
1068 1069 1070 1071 1072 1073
{
  char *optr = to;
  const grub_uint8_t *iptr;

  for (iptr = from; iptr < from + len && *iptr; iptr++)
    {
1074
      /* Translate '/' to ':' as per HFS spec.  */
1075
      if (*iptr == '/' && translate_slash)
1076 1077 1078 1079
	{
	  *optr++ = ':';
	  continue;
	}	
1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099
      if (!(*iptr & 0x80))
	{
	  *optr++ = *iptr;
	  continue;
	}
      optr = grub_stpcpy (optr, macroman[*iptr & 0x7f]);
    }
  *optr = 0;
}

static grub_ssize_t
utf8_to_macroman (grub_uint8_t *to, const char *from)
{
  grub_uint8_t *end = to + 31;
  grub_uint8_t *optr = to;
  const char *iptr = from;
  
  while (*iptr && optr < end)
    {
      int i, clen;
1100 1101 1102 1103 1104 1105 1106
      /* Translate ':' to '/' as per HFS spec.  */
      if (*iptr == ':')
	{
	  *optr++ = '/';
	  iptr++;
	  continue;
	}	
1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128
      if (!(*iptr & 0x80))
	{
	  *optr++ = *iptr++;
	  continue;
	}
      clen = 2;
      if ((*iptr & 0xf0) == 0xe0)
	clen++;
      for (i = 0; i < 0x80; i++)
	if (grub_memcmp (macroman[i], iptr, clen) == 0)
	  break;
      if (i == 0x80)
	break;
      *optr++ = i | 0x80;
      iptr += clen;
    }
  /* Too long or not encodable.  */
  if (*iptr)
    return -1;
  return optr - to;
}

1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173
union grub_hfs_anyrec {
  struct grub_hfs_filerec frec;
  struct grub_hfs_dirrec dir;
};

struct grub_fshelp_node
{
  struct grub_hfs_data *data;
  union grub_hfs_anyrec fdrec;
  grub_uint32_t inode;
};

static grub_err_t
lookup_file (grub_fshelp_node_t dir,
	     const char *name,
	     grub_fshelp_node_t *foundnode,
	     enum grub_fshelp_filetype *foundtype)
{
  struct grub_hfs_catalog_key key;
  grub_ssize_t slen;
  union grub_hfs_anyrec fdrec;

  key.parent_dir = grub_cpu_to_be32 (dir->inode);
  slen = utf8_to_macroman (key.str, name);
  if (slen < 0)
    /* Not found */
    return GRUB_ERR_NONE;
  key.strlen = slen;

  /* Lookup this node.  */
  if (! grub_hfs_find_node (dir->data, (char *) &key, dir->data->cat_root,
			    0, (char *) &fdrec.frec, sizeof (fdrec.frec)))
    /* Not found */
    return GRUB_ERR_NONE;

  *foundnode = grub_malloc (sizeof (struct grub_fshelp_node));
  if (!*foundnode)
    return grub_errno;
  
  (*foundnode)->inode = grub_be_to_cpu32 (fdrec.dir.dirid);
  (*foundnode)->fdrec = fdrec;
  (*foundnode)->data = dir->data;
  *foundtype = (fdrec.frec.type == GRUB_HFS_FILETYPE_DIR) ? GRUB_FSHELP_DIR : GRUB_FSHELP_REG;
  return GRUB_ERR_NONE;
}
1174 1175 1176 1177 1178 1179

/* Find a file or directory with the pathname PATH in the filesystem
   DATA.  Return the file record in RETDATA when it is non-zero.
   Return the directory number in RETINODE when it is non-zero.  */
static grub_err_t
grub_hfs_find_dir (struct grub_hfs_data *data, const char *path,
1180 1181
		   grub_fshelp_node_t *found,
		   enum grub_fshelp_filetype exptype)
1182
{
1183 1184 1185 1186 1187 1188 1189
  struct grub_fshelp_node root = {
    .data = data,
    .inode = data->rootdir,
    .fdrec = {
      .frec = {
	.type = GRUB_HFS_FILETYPE_DIR
      }
1190
    }
1191 1192
  };
  grub_err_t err;
1193

1194
  err = grub_fshelp_find_file_lookup (path, &root, found, lookup_file, NULL, exptype);
1195

1196
  if (&root == *found)
1197
    {
1198 1199 1200 1201
      *found = grub_malloc (sizeof (root));
      if (!*found)
	return grub_errno;
      grub_memcpy (*found, &root, sizeof (root));
1202
    }
1203
  return err;
1204 1205
}

1206
struct grub_hfs_dir_hook_ctx
1207
{
1208 1209 1210
  grub_fs_dir_hook_t hook;
  void *hook_data;
};
1211

1212 1213 1214 1215 1216 1217 1218 1219 1220 1221
static int
grub_hfs_dir_hook (struct grub_hfs_record *rec, void *hook_arg)
{
  struct grub_hfs_dir_hook_ctx *ctx = hook_arg;
  struct grub_hfs_dirrec *drec = rec->data;
  struct grub_hfs_filerec *frec = rec->data;
  struct grub_hfs_catalog_key *ckey = rec->key;
  char fname[sizeof (ckey->str) * MAX_UTF8_PER_MAC_ROMAN + 1];
  struct grub_dirhook_info info;
  grub_size_t len;
1222

1223
  grub_memset (fname, 0, sizeof (fname));
1224

1225
  grub_memset (&info, 0, sizeof (info));
1226

1227 1228 1229 1230
  len = ckey->strlen;
  if (len > sizeof (ckey->str))
    len = sizeof (ckey->str);
  macroman_to_utf8 (fname, ckey->str, len, 1);
1231

1232
  info.case_insensitive = 1;
1233

1234 1235 1236 1237
  if (drec->type == GRUB_HFS_FILETYPE_DIR)
    {
      info.dir = 1;
      info.mtimeset = 1;
1238
      info.inodeset = 1;
1239
      info.mtime = grub_be_to_cpu32 (drec->mtime) - 2082844800;
1240
      info.inode = grub_be_to_cpu32 (drec->dirid);
1241 1242 1243 1244 1245 1246
      return ctx->hook (fname, &info, ctx->hook_data);
    }
  if (frec->type == GRUB_HFS_FILETYPE_FILE)
    {
      info.dir = 0;
      info.mtimeset = 1;
1247
      info.inodeset = 1;
1248
      info.mtime = grub_be_to_cpu32 (frec->mtime) - 2082844800;
1249
      info.inode = grub_be_to_cpu32 (frec->fileid);
1250 1251
      return ctx->hook (fname, &info, ctx->hook_data);
    }
1252

1253 1254
  return 0;
}
1255

1256 1257 1258 1259 1260

static grub_err_t
grub_hfs_dir (grub_device_t device, const char *path, grub_fs_dir_hook_t hook,
	      void *hook_data)
{
1261
  struct grub_hfs_data *data;
1262 1263 1264 1265 1266
  struct grub_hfs_dir_hook_ctx ctx =
    {
      .hook = hook,
      .hook_data = hook_data
    };
1267 1268
  grub_fshelp_node_t found = NULL;
  
1269
  grub_dl_ref (my_mod);
1270

1271 1272 1273
  data = grub_hfs_mount (device->disk);
  if (!data)
    goto fail;
1274

1275
  /* First the directory ID for the directory.  */
1276
  if (grub_hfs_find_dir (data, path, &found, GRUB_FSHELP_DIR))
1277 1278
    goto fail;

1279
  grub_hfs_iterate_dir (data, data->cat_root, found->inode, grub_hfs_dir_hook, &ctx);
1280

1281
 fail:
1282
  grub_free (found);
1283 1284 1285
  grub_free (data);

  grub_dl_unref (my_mod);
1286

1287 1288 1289 1290 1291 1292 1293 1294 1295
  return grub_errno;
}


/* Open a file named NAME and initialize FILE.  */
static grub_err_t
grub_hfs_open (struct grub_file *file, const char *name)
{
  struct grub_hfs_data *data;
1296 1297
  grub_fshelp_node_t found = NULL;
  
1298 1299 1300
  grub_dl_ref (my_mod);

  data = grub_hfs_mount (file->device->disk);
1301

1302 1303 1304 1305 1306 1307
  if (!data)
    {
      grub_dl_unref (my_mod);
      return grub_errno;
    }

1308
  if (grub_hfs_find_dir (data, name, &found, GRUB_FSHELP_REG))
1309 1310
    {
      grub_free (data);
Andrei Borzenkov's avatar
Andrei Borzenkov committed
1311
      grub_free (found);
1312
      grub_dl_unref (my_mod);
1313 1314
      return grub_errno;
    }
1315

1316 1317 1318 1319
  grub_memcpy (data->extents, found->fdrec.frec.extents, sizeof (grub_hfs_datarecord_t));
  file->size = grub_be_to_cpu32 (found->fdrec.frec.size);
  data->size = grub_be_to_cpu32 (found->fdrec.frec.size);
  data->fileid = grub_be_to_cpu32 (found->fdrec.frec.fileid);
1320 1321 1322
  file->offset = 0;

  file->data = data;
1323

1324 1325
  grub_free (found);

1326 1327 1328 1329
  return 0;
}

static grub_ssize_t
1330
grub_hfs_read (grub_file_t file, char *buf, grub_size_t len)
1331
{
1332
  struct grub_hfs_data *data =
1333
    (struct grub_hfs_data *) file->data;
1334

1335 1336
  return grub_hfs_read_file (data, file->read_hook, file->read_hook_data,
			     file->offset, len, buf);
1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356
}


static grub_err_t
grub_hfs_close (grub_file_t file)
{
  grub_free (file->data);

  grub_dl_unref (my_mod);

  return 0;
}


static grub_err_t
grub_hfs_label (grub_device_t device, char **label)
{
  struct grub_hfs_data *data;

  data = grub_hfs_mount (device->disk);
1357

1358
  if (data)
1359
    {
1360 1361 1362
      grub_size_t len = data->sblock.volname[0];
      if (len > sizeof (data->sblock.volname) - 1)
	len = sizeof (data->sblock.volname) - 1;
1363 1364 1365
      *label = grub_malloc (len * MAX_UTF8_PER_MAC_ROMAN + 1);
      if (*label)
	macroman_to_utf8 (*label, data->sblock.volname + 1,
1366
			  len + 1, 0);
1367
    }
1368 1369 1370 1371 1372 1373 1374
  else
    *label = 0;

  grub_free (data);
  return grub_errno;
}

1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390
static grub_err_t
grub_hfs_mtime (grub_device_t device, grub_int32_t *tm)
{
  struct grub_hfs_data *data;

  data = grub_hfs_mount (device->disk);

  if (data)
    *tm = grub_be_to_cpu32 (data->sblock.mtime) - 2082844800;
  else
    *tm = 0;

  grub_free (data);
  return grub_errno;
}

1391 1392 1393 1394 1395 1396 1397 1398 1399 1400
static grub_err_t
grub_hfs_uuid (grub_device_t device, char **uuid)
{
  struct grub_hfs_data *data;

  grub_dl_ref (my_mod);

  data = grub_hfs_mount (device->disk);
  if (data && data->sblock.num_serial != 0)
    {
1401
      *uuid = grub_xasprintf ("%016llx",
1402 1403
			     (unsigned long long)
			     grub_be_to_cpu64 (data->sblock.num_serial));
1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414
    }
  else
    *uuid = NULL;

  grub_dl_unref (my_mod);

  grub_free (data);

  return grub_errno;
}

1415 1416 1417 1418 1419 1420 1421 1422 1423 1424


static struct grub_fs grub_hfs_fs =
  {
    .name = "hfs",
    .dir = grub_hfs_dir,
    .open = grub_hfs_open,
    .read = grub_hfs_read,
    .close = grub_hfs_close,
    .label = grub_hfs_label,
1425
    .uuid = grub_hfs_uuid,
1426
    .mtime = grub_hfs_mtime,
1427 1428 1429 1430
#ifdef GRUB_UTIL
    .reserved_first_sector = 1,
    .blocklist_install = 1,
#endif
1431 1432 1433
    .next = 0
  };

1434
GRUB_MOD_INIT(hfs)
1435 1436 1437 1438 1439
{
  grub_fs_register (&grub_hfs_fs);
  my_mod = mod;
}

1440
GRUB_MOD_FINI(hfs)
1441 1442 1443
{
  grub_fs_unregister (&grub_hfs_fs);
}