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

#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>
29
#include <grub/charset.h>
30
#include <grub/datetime.h>
31

32 33
GRUB_MOD_LICENSE ("GPLv3+");

34 35 36 37 38 39 40 41 42 43 44
#define GRUB_ISO9660_FSTYPE_DIR		0040000
#define GRUB_ISO9660_FSTYPE_REG		0100000
#define GRUB_ISO9660_FSTYPE_SYMLINK	0120000
#define GRUB_ISO9660_FSTYPE_MASK	0170000

#define GRUB_ISO9660_LOG2_BLKSZ		2
#define GRUB_ISO9660_BLKSZ		2048

#define GRUB_ISO9660_RR_DOT		2
#define GRUB_ISO9660_RR_DOTDOT		4

45 46 47 48 49 50
#define GRUB_ISO9660_VOLDESC_BOOT	0
#define GRUB_ISO9660_VOLDESC_PRIMARY	1
#define GRUB_ISO9660_VOLDESC_SUPP	2
#define GRUB_ISO9660_VOLDESC_PART	3
#define GRUB_ISO9660_VOLDESC_END	255

51 52 53 54 55 56
/* The head of a volume descriptor.  */
struct grub_iso9660_voldesc
{
  grub_uint8_t type;
  grub_uint8_t magic[5];
  grub_uint8_t version;
57
} GRUB_PACKED;
58

59 60 61 62 63 64 65 66 67
struct grub_iso9660_date2
{
  grub_uint8_t year;
  grub_uint8_t month;
  grub_uint8_t day;
  grub_uint8_t hour;
  grub_uint8_t minute;
  grub_uint8_t second;
  grub_uint8_t offset;
68
} GRUB_PACKED;
69

70 71 72 73 74 75 76 77 78
/* A directory entry.  */
struct grub_iso9660_dir
{
  grub_uint8_t len;
  grub_uint8_t ext_sectors;
  grub_uint32_t first_sector;
  grub_uint32_t first_sector_be;
  grub_uint32_t size;
  grub_uint32_t size_be;
79
  struct grub_iso9660_date2 mtime;
80 81
  grub_uint8_t flags;
  grub_uint8_t unused2[6];
82
#define MAX_NAMELEN 255
83
  grub_uint8_t namelen;
84
} GRUB_PACKED;
85

86 87 88 89 90 91 92 93 94 95
struct grub_iso9660_date
{
  grub_uint8_t year[4];
  grub_uint8_t month[2];
  grub_uint8_t day[2];
  grub_uint8_t hour[2];
  grub_uint8_t minute[2];
  grub_uint8_t second[2];
  grub_uint8_t hundredth[2];
  grub_uint8_t offset;
96
} GRUB_PACKED;
97

98 99 100 101 102 103
/* The primary volume descriptor.  Only little endian is used.  */
struct grub_iso9660_primary_voldesc
{
  struct grub_iso9660_voldesc voldesc;
  grub_uint8_t unused1[33];
  grub_uint8_t volname[32];
104 105 106
  grub_uint8_t unused2[16];
  grub_uint8_t escape[32];
  grub_uint8_t unused3[12];
107
  grub_uint32_t path_table_size;
108
  grub_uint8_t unused4[4];
109
  grub_uint32_t path_table;
110
  grub_uint8_t unused5[12];
111
  struct grub_iso9660_dir rootdir;
112
  grub_uint8_t unused6[624];
113
  struct grub_iso9660_date created;
114
  struct grub_iso9660_date modified;
115
} GRUB_PACKED;
116 117 118 119 120 121 122 123 124

/* A single entry in the path table.  */
struct grub_iso9660_path
{
  grub_uint8_t len;
  grub_uint8_t sectors;
  grub_uint32_t first_sector;
  grub_uint16_t parentdir;
  grub_uint8_t name[0];
125
} GRUB_PACKED;
126 127 128 129 130 131 132 133

/* An entry in the System Usage area of the directory entry.  */
struct grub_iso9660_susp_entry
{
  grub_uint8_t sig[2];
  grub_uint8_t len;
  grub_uint8_t version;
  grub_uint8_t data[0];
134
} GRUB_PACKED;
135 136 137 138 139 140 141 142 143 144 145 146

/* The CE entry.  This is used to describe the next block where data
   can be found.  */
struct grub_iso9660_susp_ce
{
  struct grub_iso9660_susp_entry entry;
  grub_uint32_t blk;
  grub_uint32_t blk_be;
  grub_uint32_t off;
  grub_uint32_t off_be;
  grub_uint32_t len;
  grub_uint32_t len_be;
147
} GRUB_PACKED;
148 149 150 151 152 153 154

struct grub_iso9660_data
{
  struct grub_iso9660_primary_voldesc voldesc;
  grub_disk_t disk;
  int rockridge;
  int susp_skip;
155
  int joliet;
156
  struct grub_fshelp_node *node;
157 158 159 160 161
};

struct grub_fshelp_node
{
  struct grub_iso9660_data *data;
162
  grub_size_t have_dirents, alloc_dirents;
163
  int have_symlink;
164
  struct grub_iso9660_dir dirents[8];
165
  char symlink[0];
166 167
};

168 169 170 171 172 173 174 175
enum
  {
    FLAG_TYPE_PLAIN = 0,
    FLAG_TYPE_DIR = 2,
    FLAG_TYPE = 3,
    FLAG_MORE_EXTENTS = 0x80
  };

176 177 178
static grub_dl_t my_mod;


179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
static grub_err_t
iso9660_to_unixtime (const struct grub_iso9660_date *i, grub_int32_t *nix)
{
  struct grub_datetime datetime;
  
  if (! i->year[0] && ! i->year[1]
      && ! i->year[2] && ! i->year[3]
      && ! i->month[0] && ! i->month[1]
      && ! i->day[0] && ! i->day[1]
      && ! i->hour[0] && ! i->hour[1]
      && ! i->minute[0] && ! i->minute[1]
      && ! i->second[0] && ! i->second[1]
      && ! i->hundredth[0] && ! i->hundredth[1])
    return grub_error (GRUB_ERR_BAD_NUMBER, "empty date");
  datetime.year = (i->year[0] - '0') * 1000 + (i->year[1] - '0') * 100
    + (i->year[2] - '0') * 10 + (i->year[3] - '0');
  datetime.month = (i->month[0] - '0') * 10 + (i->month[1] - '0');
  datetime.day = (i->day[0] - '0') * 10 + (i->day[1] - '0');
  datetime.hour = (i->hour[0] - '0') * 10 + (i->hour[1] - '0');
  datetime.minute = (i->minute[0] - '0') * 10 + (i->minute[1] - '0');
  datetime.second = (i->second[0] - '0') * 10 + (i->second[1] - '0');
  
201 202
  if (!grub_datetime2unixtime (&datetime, nix))
    return grub_error (GRUB_ERR_BAD_NUMBER, "incorrect date");
203
  *nix -= i->offset * 60 * 15;
204
  return GRUB_ERR_NONE;
205 206
}

207
static int
208 209 210
iso9660_to_unixtime2 (const struct grub_iso9660_date2 *i, grub_int32_t *nix)
{
  struct grub_datetime datetime;
211

212 213 214 215 216 217 218
  datetime.year = i->year + 1900;
  datetime.month = i->month;
  datetime.day = i->day;
  datetime.hour = i->hour;
  datetime.minute = i->minute;
  datetime.second = i->second;
  
219 220
  if (!grub_datetime2unixtime (&datetime, nix))
    return 0;
221
  *nix -= i->offset * 60 * 15;
222
  return 1;
223 224
}

225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
static grub_err_t
read_node (grub_fshelp_node_t node, grub_off_t off, grub_size_t len, char *buf)
{
  grub_size_t i = 0;

  while (len > 0)
    {
      grub_size_t toread;
      grub_err_t err;
      while (i < node->have_dirents
	     && off >= grub_le_to_cpu32 (node->dirents[i].size))
	{
	  off -= grub_le_to_cpu32 (node->dirents[i].size);
	  i++;
	}
      if (i == node->have_dirents)
	return grub_error (GRUB_ERR_OUT_OF_RANGE, "read out of range");
      toread = grub_le_to_cpu32 (node->dirents[i].size);
      if (toread > len)
	toread = len;
      err = grub_disk_read (node->data->disk,
			    ((grub_disk_addr_t) grub_le_to_cpu32 (node->dirents[i].first_sector)) << GRUB_ISO9660_LOG2_BLKSZ,
			    off, toread, buf);
      if (err)
	return err;
      len -= toread;
      off += toread;
      buf += toread;
    }
  return GRUB_ERR_NONE;
}

257 258 259 260
/* Iterate over the susp entries, starting with block SUA_BLOCK on the
   offset SUA_POS with a size of SUA_SIZE bytes.  Hook is called for
   every entry.  */
static grub_err_t
261
grub_iso9660_susp_iterate (grub_fshelp_node_t node, grub_off_t off,
262
			   grub_ssize_t sua_size,
263
			   grub_err_t (*hook)
264 265
			   (struct grub_iso9660_susp_entry *entry, void *hook_arg),
			   void *hook_arg)
266 267 268
{
  char *sua;
  struct grub_iso9660_susp_entry *entry;
269
  grub_err_t err;
270

271 272 273
  if (sua_size <= 0)
    return GRUB_ERR_NONE;

274 275
  sua = grub_malloc (sua_size);
  if (!sua)
276
    return grub_errno;
277

278 279 280 281 282 283
  /* Load a part of the System Usage Area.  */
  err = read_node (node, off, sua_size, sua);
  if (err)
    return err;

  for (entry = (struct grub_iso9660_susp_entry *) sua; (char *) entry < (char *) sua + sua_size - 1 && entry->len > 0;
284 285 286 287
       entry = (struct grub_iso9660_susp_entry *)
	 ((char *) entry + entry->len))
    {
      /* The last entry.  */
288
      if (grub_strncmp ((char *) entry->sig, "ST", 2) == 0)
289
	break;
290

291
      /* Additional entries are stored elsewhere.  */
292
      if (grub_strncmp ((char *) entry->sig, "CE", 2) == 0)
293 294
	{
	  struct grub_iso9660_susp_ce *ce;
295
	  grub_disk_addr_t ce_block;
296

297 298
	  ce = (struct grub_iso9660_susp_ce *) entry;
	  sua_size = grub_le_to_cpu32 (ce->len);
299 300
	  off = grub_le_to_cpu32 (ce->off);
	  ce_block = grub_le_to_cpu32 (ce->blk) << GRUB_ISO9660_LOG2_BLKSZ;
301

302
	  grub_free (sua);
303 304
	  sua = grub_malloc (sua_size);
	  if (!sua)
305
	    return grub_errno;
306 307 308 309 310 311 312 313

	  /* Load a part of the System Usage Area.  */
	  err = grub_disk_read (node->data->disk, ce_block, off,
				sua_size, sua);
	  if (err)
	    return err;

	  entry = (struct grub_iso9660_susp_entry *) sua;
314
	}
315

316
      if (hook (entry, hook_arg))
317 318 319 320 321
	{
	  grub_free (sua);
	  return 0;
	}
    }
322

323 324 325 326
  grub_free (sua);
  return 0;
}

327
static char *
328
grub_iso9660_convert_string (grub_uint8_t *us, int len)
329 330 331
{
  char *p;
  int i;
332
  grub_uint16_t t[MAX_NAMELEN / 2 + 1];
333

334
  p = grub_malloc (len * GRUB_MAX_UTF8_PER_UTF16 + 1);
335
  if (! p)
336
    return NULL;
337 338

  for (i=0; i<len; i++)
339
    t[i] = grub_be_to_cpu16 (grub_get_unaligned16 (us + 2 * i));
340

341
  *grub_utf16_to_utf8 ((grub_uint8_t *) p, t, len) = '\0';
342 343 344

  return p;
}
345

346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
static grub_err_t
susp_iterate_set_rockridge (struct grub_iso9660_susp_entry *susp_entry,
			    void *_data)
{
  struct grub_iso9660_data *data = _data;
  /* The "ER" entry is used to detect extensions.  The
     `IEEE_P1285' extension means Rock ridge.  */
  if (grub_strncmp ((char *) susp_entry->sig, "ER", 2) == 0)
    {
      data->rockridge = 1;
      return 1;
    }
  return 0;
}

361 362
static grub_err_t
set_rockridge (struct grub_iso9660_data *data)
363 364 365 366
{
  int sua_pos;
  int sua_size;
  char *sua;
367
  struct grub_iso9660_dir rootdir;
368
  struct grub_iso9660_susp_entry *entry;
369

370
  data->rockridge = 0;
371

372 373
  /* Read the system use area and test it to see if SUSP is
     supported.  */
374 375 376
  if (grub_disk_read (data->disk,
		      (grub_le_to_cpu32 (data->voldesc.rootdir.first_sector)
		       << GRUB_ISO9660_LOG2_BLKSZ), 0,
377
		      sizeof (rootdir), (char *) &rootdir))
378
    return grub_error (GRUB_ERR_BAD_FS, "not a ISO9660 filesystem");
379

380 381 382 383
  sua_pos = (sizeof (rootdir) + rootdir.namelen
	     + (rootdir.namelen % 2) - 1);
  sua_size = rootdir.len - sua_pos;

384
  if (!sua_size)
385
    return GRUB_ERR_NONE;
386

387
  sua = grub_malloc (sua_size);
388
  if (! sua)
389
    return grub_errno;
390

391 392 393
  if (grub_disk_read (data->disk,
		      (grub_le_to_cpu32 (data->voldesc.rootdir.first_sector)
		       << GRUB_ISO9660_LOG2_BLKSZ), sua_pos,
394
		      sua_size, sua))
395 396 397 398
    {
      grub_free (sua);
      return grub_error (GRUB_ERR_BAD_FS, "not a ISO9660 filesystem");
    }
399

400
  entry = (struct grub_iso9660_susp_entry *) sua;
401

402
  /* Test if the SUSP protocol is used on this filesystem.  */
403
  if (grub_strncmp ((char *) entry->sig, "SP", 2) == 0)
404
    {
405 406 407
      struct grub_fshelp_node rootnode;

      rootnode.data = data;
408
      rootnode.alloc_dirents = ARRAY_SIZE (rootnode.dirents);
409
      rootnode.have_dirents = 1;
410
      rootnode.have_symlink = 0;
411 412
      rootnode.dirents[0] = data->voldesc.rootdir;

413
      /* The 2nd data byte stored how many bytes are skipped every time
414 415 416
	 to get to the SUA (System Usage Area).  */
      data->susp_skip = entry->data[2];
      entry = (struct grub_iso9660_susp_entry *) ((char *) entry + entry->len);
417

418
      /* Iterate over the entries in the SUA area to detect
419
	 extensions.  */
420
      if (grub_iso9660_susp_iterate (&rootnode,
421 422
				     sua_pos, sua_size, susp_iterate_set_rockridge,
				     data))
423 424 425 426
	{
	  grub_free (sua);
	  return grub_errno;
	}
427
    }
428
  grub_free (sua);
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
  return GRUB_ERR_NONE;
}

static struct grub_iso9660_data *
grub_iso9660_mount (grub_disk_t disk)
{
  struct grub_iso9660_data *data = 0;
  struct grub_iso9660_primary_voldesc voldesc;
  int block;

  data = grub_zalloc (sizeof (struct grub_iso9660_data));
  if (! data)
    return 0;

  data->disk = disk;

  block = 16;
  do
    {
      int copy_voldesc = 0;

      /* Read the superblock.  */
      if (grub_disk_read (disk, block << GRUB_ISO9660_LOG2_BLKSZ, 0,
			  sizeof (struct grub_iso9660_primary_voldesc),
			  (char *) &voldesc))
        {
          grub_error (GRUB_ERR_BAD_FS, "not a ISO9660 filesystem");
          goto fail;
        }

      if (grub_strncmp ((char *) voldesc.voldesc.magic, "CD001", 5) != 0)
        {
          grub_error (GRUB_ERR_BAD_FS, "not a ISO9660 filesystem");
          goto fail;
        }

      if (voldesc.voldesc.type == GRUB_ISO9660_VOLDESC_PRIMARY)
	copy_voldesc = 1;
      else if (!data->rockridge
	       && (voldesc.voldesc.type == GRUB_ISO9660_VOLDESC_SUPP)
	       && (voldesc.escape[0] == 0x25) && (voldesc.escape[1] == 0x2f)
	       &&
               ((voldesc.escape[2] == 0x40) ||	/* UCS-2 Level 1.  */
                (voldesc.escape[2] == 0x43) ||  /* UCS-2 Level 2.  */
                (voldesc.escape[2] == 0x45)))	/* UCS-2 Level 3.  */
        {
          copy_voldesc = 1;
          data->joliet = 1;
        }

      if (copy_voldesc)
	{
	  grub_memcpy((char *) &data->voldesc, (char *) &voldesc,
		      sizeof (struct grub_iso9660_primary_voldesc));
	  if (set_rockridge (data))
	    goto fail;
	}

      block++;
    } while (voldesc.voldesc.type != GRUB_ISO9660_VOLDESC_END);
489

490
  return data;
491

492 493 494 495 496 497 498 499 500
 fail:
  grub_free (data);
  return 0;
}


static char *
grub_iso9660_read_symlink (grub_fshelp_node_t node)
{
501 502 503 504
  return node->have_symlink 
    ? grub_strdup (node->symlink
		   + (node->have_dirents) * sizeof (node->dirents[0])
		   - sizeof (node->dirents)) : grub_strdup ("");
505 506
}

507 508 509 510 511 512 513 514 515 516
static grub_off_t
get_node_size (grub_fshelp_node_t node)
{
  grub_off_t ret = 0;
  grub_size_t i;

  for (i = 0; i < node->have_dirents; i++)
    ret += grub_le_to_cpu32 (node->dirents[i].size);
  return ret;
}
517

518
struct iterate_dir_ctx
519
{
520 521
  char *filename;
  int filename_alloc;
522
  enum grub_fshelp_filetype type;
523 524 525
  char *symlink;
  int was_continue;
};
526 527

  /* Extend the symlink.  */
528
static void
529 530 531 532 533
add_part (struct iterate_dir_ctx *ctx,
	  const char *part,
	  int len2)
{
  int size = ctx->symlink ? grub_strlen (ctx->symlink) : 0;
534

535 536 537
  ctx->symlink = grub_realloc (ctx->symlink, size + len2 + 1);
  if (! ctx->symlink)
    return;
538

539 540
  grub_memcpy (ctx->symlink + size, part, len2);
  ctx->symlink[size + len2] = 0;  
541
}
542

543 544 545 546 547
static grub_err_t
susp_iterate_dir (struct grub_iso9660_susp_entry *entry,
		  void *_ctx)
{
  struct iterate_dir_ctx *ctx = _ctx;
548

549 550
  /* The filename in the rock ridge entry.  */
  if (grub_strncmp ("NM", (char *) entry->sig, 2) == 0)
551
    {
552 553 554 555 556 557 558 559
      /* The flags are stored at the data position 0, here the
	 filename type is stored.  */
      /* FIXME: Fix this slightly improper cast.  */
      if (entry->data[0] & GRUB_ISO9660_RR_DOT)
	ctx->filename = (char *) ".";
      else if (entry->data[0] & GRUB_ISO9660_RR_DOTDOT)
	ctx->filename = (char *) "..";
      else if (entry->len >= 5)
560
	{
561
	  grub_size_t off = 0, csize = 1;
562
	  char *old;
563
	  csize = entry->len - 5;
564 565 566
	  old = ctx->filename;
	  if (ctx->filename_alloc)
	    {
567 568
	      off = grub_strlen (ctx->filename);
	      ctx->filename = grub_realloc (ctx->filename, csize + off + 1);
569 570
	    }
	  else
571
	    {
572 573
	      off = 0;
	      ctx->filename = grub_zalloc (csize + 1);
574
	    }
575 576 577 578 579 580
	  if (!ctx->filename)
	    {
	      ctx->filename = old;
	      return grub_errno;
	    }
	  ctx->filename_alloc = 1;
581 582
	  grub_memcpy (ctx->filename + off, (char *) &entry->data[1], csize);
	  ctx->filename[off + csize] = '\0';
583
	}
584 585 586 587 588 589 590 591 592 593
    }
  /* The mode information (st_mode).  */
  else if (grub_strncmp ((char *) entry->sig, "PX", 2) == 0)
    {
      /* At position 0 of the PX record the st_mode information is
	 stored (little-endian).  */
      grub_uint32_t mode = ((entry->data[0] + (entry->data[1] << 8))
			    & GRUB_ISO9660_FSTYPE_MASK);

      switch (mode)
594
	{
595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610
	case GRUB_ISO9660_FSTYPE_DIR:
	  ctx->type = GRUB_FSHELP_DIR;
	  break;
	case GRUB_ISO9660_FSTYPE_REG:
	  ctx->type = GRUB_FSHELP_REG;
	  break;
	case GRUB_ISO9660_FSTYPE_SYMLINK:
	  ctx->type = GRUB_FSHELP_SYMLINK;
	  break;
	default:
	  ctx->type = GRUB_FSHELP_UNKNOWN;
	}
    }
  else if (grub_strncmp ("SL", (char *) entry->sig, 2) == 0)
    {
      unsigned int pos = 1;
611

612 613 614 615 616
      /* The symlink is not stored as a POSIX symlink, translate it.  */
      while (pos + sizeof (*entry) < entry->len)
	{
	  /* The current position is the `Component Flag'.  */
	  switch (entry->data[pos] & 30)
617
	    {
618 619 620 621 622 623 624 625 626 627 628 629 630 631 632
	    case 0:
	      {
		/* The data on pos + 2 is the actual data, pos + 1
		   is the length.  Both are part of the `Component
		   Record'.  */
		if (ctx->symlink && !ctx->was_continue)
		  add_part (ctx, "/", 1);
		add_part (ctx, (char *) &entry->data[pos + 2],
			  entry->data[pos + 1]);
		ctx->was_continue = (entry->data[pos] & 1);
		break;
	      }

	    case 2:
	      add_part (ctx, "./", 2);
633
	      break;
634 635 636

	    case 4:
	      add_part (ctx, "../", 3);
637
	      break;
638 639 640

	    case 8:
	      add_part (ctx, "/", 1);
641 642
	      break;
	    }
643 644 645
	  /* In pos + 1 the length of the `Component Record' is
	     stored.  */
	  pos += entry->data[pos + 1] + 2;
646
	}
647

648 649 650 651
      /* Check if `grub_realloc' failed.  */
      if (grub_errno)
	return grub_errno;
    }
652

653 654
  return 0;
}
655

656 657 658 659 660 661 662 663
static int
grub_iso9660_iterate_dir (grub_fshelp_node_t dir,
			  grub_fshelp_iterate_dir_hook_t hook, void *hook_data)
{
  struct grub_iso9660_dir dirent;
  grub_off_t offset = 0;
  grub_off_t len;
  struct iterate_dir_ctx ctx;
664

665 666 667
  len = get_node_size (dir);

  for (; offset < len; offset += dirent.len)
668
    {
669 670
      ctx.symlink = 0;
      ctx.was_continue = 0;
671

672
      if (read_node (dir, offset, sizeof (dirent), (char *) &dirent))
673
	return 0;
674

675 676 677 678 679 680
      /* The end of the block, skip to the next one.  */
      if (!dirent.len)
	{
	  offset = (offset / GRUB_ISO9660_BLKSZ + 1) * GRUB_ISO9660_BLKSZ;
	  continue;
	}
681

682
      {
683
	char name[MAX_NAMELEN + 1];
684 685 686
	int nameoffset = offset + sizeof (dirent);
	struct grub_fshelp_node *node;
	int sua_off = (sizeof (dirent) + dirent.namelen + 1
687
		       - (dirent.namelen % 2));
688
	int sua_size = dirent.len - sua_off;
689

690
	sua_off += offset + dir->data->susp_skip;
691

692 693 694
	ctx.filename = 0;
	ctx.filename_alloc = 0;
	ctx.type = GRUB_FSHELP_UNKNOWN;
695 696

	if (dir->data->rockridge
697
	    && grub_iso9660_susp_iterate (dir, sua_off, sua_size,
698
					  susp_iterate_dir, &ctx))
699
	  return 0;
700

701
	/* Read the name.  */
702
	if (read_node (dir, nameoffset, dirent.namelen, (char *) name))
703
	  return 0;
704

705 706 707
	node = grub_malloc (sizeof (struct grub_fshelp_node));
	if (!node)
	  return 0;
708

709 710 711
	node->alloc_dirents = ARRAY_SIZE (node->dirents);
	node->have_dirents = 1;

712 713
	/* Setup a new node.  */
	node->data = dir->data;
714
	node->have_symlink = 0;
715

716 717
	/* If the filetype was not stored using rockridge, use
	   whatever is stored in the iso9660 filesystem.  */
718
	if (ctx.type == GRUB_FSHELP_UNKNOWN)
719
	  {
720
	    if ((dirent.flags & FLAG_TYPE) == FLAG_TYPE_DIR)
721
	      ctx.type = GRUB_FSHELP_DIR;
722
	    else
723
	      ctx.type = GRUB_FSHELP_REG;
724
	  }
725

726
	/* . and .. */
727 728
	if (!ctx.filename && dirent.namelen == 1 && name[0] == 0)
	  ctx.filename = (char *) ".";
729

730 731
	if (!ctx.filename && dirent.namelen == 1 && name[0] == 1)
	  ctx.filename = (char *) "..";
732

733 734
	/* The filename was not stored in a rock ridge entry.  Read it
	   from the iso9660 filesystem.  */
735
	if (!dir->data->joliet && !ctx.filename)
736
	  {
737
	    char *ptr;
738
	    name[dirent.namelen] = '\0';
739 740 741
	    ctx.filename = grub_strrchr (name, ';');
	    if (ctx.filename)
	      *ctx.filename = '\0';
742
	    /* ISO9660 names are not case-preserving.  */
743
	    ctx.type |= GRUB_FSHELP_CASE_INSENSITIVE;
744 745 746 747
	    for (ptr = name; *ptr; ptr++)
	      *ptr = grub_tolower (*ptr);
	    if (ptr != name && *(ptr - 1) == '.')
	      *(ptr - 1) = 0;
748
	    ctx.filename = name;
749
	  }
750

751
        if (dir->data->joliet && !ctx.filename)
752
          {
753
            char *semicolon;
754

755
            ctx.filename = grub_iso9660_convert_string
756
                  ((grub_uint8_t *) name, dirent.namelen >> 1);
757

758
	    semicolon = grub_strrchr (ctx.filename, ';');
759 760 761
	    if (semicolon)
	      *semicolon = '\0';

762
            ctx.filename_alloc = 1;
763 764
          }

765 766 767 768 769 770
	node->dirents[0] = dirent;
	while (dirent.flags & FLAG_MORE_EXTENTS)
	  {
	    offset += dirent.len;
	    if (read_node (dir, offset, sizeof (dirent), (char *) &dirent))
	      {
771 772
		if (ctx.filename_alloc)
		  grub_free (ctx.filename);
773 774 775 776 777 778 779
		grub_free (node);
		return 0;
	      }
	    if (node->have_dirents >= node->alloc_dirents)
	      {
		struct grub_fshelp_node *new_node;
		node->alloc_dirents *= 2;
780 781 782 783 784
		new_node = grub_realloc (node, 
					 sizeof (struct grub_fshelp_node)
					 + ((node->alloc_dirents
					     - ARRAY_SIZE (node->dirents))
					    * sizeof (node->dirents[0])));
785 786
		if (!new_node)
		  {
787 788
		    if (ctx.filename_alloc)
		      grub_free (ctx.filename);
789 790 791
		    grub_free (node);
		    return 0;
		  }
792
		node = new_node;
793 794 795
	      }
	    node->dirents[node->have_dirents++] = dirent;
	  }
796
	if (ctx.symlink)
797 798
	  {
	    if ((node->alloc_dirents - node->have_dirents)
799
		* sizeof (node->dirents[0]) < grub_strlen (ctx.symlink) + 1)
800 801 802 803 804 805 806
	      {
		struct grub_fshelp_node *new_node;
		new_node = grub_realloc (node,
					 sizeof (struct grub_fshelp_node)
					 + ((node->alloc_dirents
					     - ARRAY_SIZE (node->dirents))
					    * sizeof (node->dirents[0]))
807
					 + grub_strlen (ctx.symlink) + 1);
808 809
		if (!new_node)
		  {
810 811
		    if (ctx.filename_alloc)
		      grub_free (ctx.filename);
812 813 814 815 816 817 818 819
		    grub_free (node);
		    return 0;
		  }
		node = new_node;
	      }
	    node->have_symlink = 1;
	    grub_strcpy (node->symlink
			 + node->have_dirents * sizeof (node->dirents[0])
820 821 822 823
			 - sizeof (node->dirents), ctx.symlink);
	    grub_free (ctx.symlink);
	    ctx.symlink = 0;
	    ctx.was_continue = 0;
824
	  }
825
	if (hook (ctx.filename, ctx.type, node, hook_data))
826
	  {
827 828
	    if (ctx.filename_alloc)
	      grub_free (ctx.filename);
829 830
	    return 1;
	  }
831 832
	if (ctx.filename_alloc)
	  grub_free (ctx.filename);
833 834
      }
    }
835

836 837 838 839 840
  return 0;
}



841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864
/* Context for grub_iso9660_dir.  */
struct grub_iso9660_dir_ctx
{
  grub_fs_dir_hook_t hook;
  void *hook_data;
};

/* Helper for grub_iso9660_dir.  */
static int
grub_iso9660_dir_iter (const char *filename,
		       enum grub_fshelp_filetype filetype,
		       grub_fshelp_node_t node, void *data)
{
  struct grub_iso9660_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 = !!iso9660_to_unixtime2 (&node->dirents[0].mtime, &info.mtime);

  grub_free (node);
  return ctx->hook (filename, &info, ctx->hook_data);
}

865
static grub_err_t
866
grub_iso9660_dir (grub_device_t device, const char *path,
867
		  grub_fs_dir_hook_t hook, void *hook_data)
868
{
869
  struct grub_iso9660_dir_ctx ctx = { hook, hook_data };
870 871 872
  struct grub_iso9660_data *data = 0;
  struct grub_fshelp_node rootnode;
  struct grub_fshelp_node *foundnode;
873

874 875 876
  grub_dl_ref (my_mod);

  data = grub_iso9660_mount (device->disk);
877
  if (! data)
878
    goto fail;
879

880
  rootnode.data = data;
881 882
  rootnode.alloc_dirents = 0;
  rootnode.have_dirents = 1;
883
  rootnode.have_symlink = 0;
884
  rootnode.dirents[0] = data->voldesc.rootdir;
885

886 887 888 889 890 891 892
  /* Use the fshelp function to traverse the path.  */
  if (grub_fshelp_find_file (path, &rootnode,
			     &foundnode,
			     grub_iso9660_iterate_dir,
			     grub_iso9660_read_symlink,
			     GRUB_FSHELP_DIR))
    goto fail;
893

894
  /* List the files in the directory.  */
895
  grub_iso9660_iterate_dir (foundnode, grub_iso9660_dir_iter, &ctx);
896

897 898
  if (foundnode != &rootnode)
    grub_free (foundnode);
899

900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915
 fail:
  grub_free (data);

  grub_dl_unref (my_mod);

  return grub_errno;
}


/* Open a file named NAME and initialize FILE.  */
static grub_err_t
grub_iso9660_open (struct grub_file *file, const char *name)
{
  struct grub_iso9660_data *data;
  struct grub_fshelp_node rootnode;
  struct grub_fshelp_node *foundnode;
916

917 918 919 920 921
  grub_dl_ref (my_mod);

  data = grub_iso9660_mount (file->device->disk);
  if (!data)
    goto fail;
922

923
  rootnode.data = data;
924 925
  rootnode.alloc_dirents = 0;
  rootnode.have_dirents = 1;
926
  rootnode.have_symlink = 0;
927
  rootnode.dirents[0] = data->voldesc.rootdir;
928

929 930 931 932 933 934 935
  /* Use the fshelp function to traverse the path.  */
  if (grub_fshelp_find_file (name, &rootnode,
			     &foundnode,
			     grub_iso9660_iterate_dir,
			     grub_iso9660_read_symlink,
			     GRUB_FSHELP_REG))
    goto fail;
936

937
  data->node = foundnode;
938
  file->data = data;
939
  file->size = get_node_size (foundnode);
940
  file->offset = 0;
941

942
  return 0;
943

944 945
 fail:
  grub_dl_unref (my_mod);
946

947
  grub_free (data);
948

949
  return grub_errno;
950 951 952 953
}


static grub_ssize_t
954
grub_iso9660_read (grub_file_t file, char *buf, grub_size_t len)
955
{
956
  struct grub_iso9660_data *data =
957
    (struct grub_iso9660_data *) file->data;
958
  grub_err_t err;
959

960
  /* XXX: The file is stored in as a single extent.  */
961
  data->disk->read_hook = file->read_hook;
962
  data->disk->read_hook_data = file->read_hook_data;
963
  err = read_node (data->node, file->offset, len, buf);
964 965
  data->disk->read_hook = NULL;

966
  if (err || grub_errno)
967
    return -1;
968

969 970 971 972 973 974 975
  return len;
}


static grub_err_t
grub_iso9660_close (grub_file_t file)
{
976 977 978 979
  struct grub_iso9660_data *data =
    (struct grub_iso9660_data *) file->data;
  grub_free (data->node);
  grub_free (data);
980

981
  grub_dl_unref (my_mod);
982

983 984 985 986 987 988 989 990 991
  return GRUB_ERR_NONE;
}


static grub_err_t
grub_iso9660_label (grub_device_t device, char **label)
{
  struct grub_iso9660_data *data;
  data = grub_iso9660_mount (device->disk);
992

993 994
  if (data)
    {
995
      if (data->joliet)
996
        *label = grub_iso9660_convert_string (data->voldesc.volname, 16);
997 998
      else
        *label = grub_strndup ((char *) data->voldesc.volname, 32);
999 1000 1001 1002 1003 1004 1005 1006 1007
      if (*label)
	{
	  char *ptr;
	  for (ptr = *label; *ptr;ptr++);
	  ptr--;
	  while (ptr >= *label && *ptr == ' ')
	    *ptr-- = 0;
	}

1008 1009 1010 1011 1012 1013 1014 1015
      grub_free (data);
    }
  else
    *label = 0;

  return grub_errno;
}

1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027

static grub_err_t
grub_iso9660_uuid (grub_device_t device, char **uuid)
{
  struct grub_iso9660_data *data;
  grub_disk_t disk = device->disk;

  grub_dl_ref (my_mod);

  data = grub_iso9660_mount (disk);
  if (data)
    {
1028 1029 1030 1031 1032 1033 1034 1035
      if (! data->voldesc.modified.year[0] && ! data->voldesc.modified.year[1]
	  && ! data->voldesc.modified.year[2] && ! data->voldesc.modified.year[3]
	  && ! data->voldesc.modified.month[0] && ! data->voldesc.modified.month[1]
	  && ! data->voldesc.modified.day[0] && ! data->voldesc.modified.day[1]
	  && ! data->voldesc.modified.hour[0] && ! data->voldesc.modified.hour[1]
	  && ! data->voldesc.modified.minute[0] && ! data->voldesc.modified.minute[1]
	  && ! data->voldesc.modified.second[0] && ! data->voldesc.modified.second[1]
	  && ! data->voldesc.modified.hundredth[0] && ! data->voldesc.modified.hundredth[1])
1036
	{
1037
	  grub_error (GRUB_ERR_BAD_NUMBER, "no creation date in filesystem to generate UUID");
1038 1039
	  *uuid = NULL;
	}
1040
      else
1041
	{
1042
	  *uuid = grub_xasprintf ("%c%c%c%c-%c%c-%c%c-%c%c-%c%c-%c%c-%c%c",
1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058
				 data->voldesc.modified.year[0],
				 data->voldesc.modified.year[1],
				 data->voldesc.modified.year[2],
				 data->voldesc.modified.year[3],
				 data->voldesc.modified.month[0],
				 data->voldesc.modified.month[1],
				 data->voldesc.modified.day[0],
				 data->voldesc.modified.day[1],
				 data->voldesc.modified.hour[0],
				 data->voldesc.modified.hour[1],
				 data->voldesc.modified.minute[0],
				 data->voldesc.modified.minute[1],
				 data->voldesc.modified.second[0],
				 data->voldesc.modified.second[1],
				 data->voldesc.modified.hundredth[0],
				 data->voldesc.modified.hundredth[1]);
1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070
	}
    }
  else
    *uuid = NULL;

	grub_dl_unref (my_mod);

  grub_free (data);

  return grub_errno;
}

1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096
/* Get writing time of filesystem. */
static grub_err_t 
grub_iso9660_mtime (grub_device_t device, grub_int32_t *timebuf)
{
  struct grub_iso9660_data *data;
  grub_disk_t disk = device->disk;
  grub_err_t err;

  grub_dl_ref (my_mod);

  data = grub_iso9660_mount (disk);
  if (!data)
    {
      grub_dl_unref (my_mod);
      return grub_errno;
    }
  err = iso9660_to_unixtime (&data->voldesc.modified, timebuf);

  grub_dl_unref (my_mod);

  grub_free (data);

  return err;
}


1097 1098 1099 1100 1101 1102 1103 1104 1105 1106


static struct grub_fs grub_iso9660_fs =
  {
    .name = "iso9660",
    .dir = grub_iso9660_dir,
    .open = grub_iso9660_open,
    .read = grub_iso9660_read,
    .close = grub_iso9660_close,
    .label = grub_iso9660_label,
1107
    .uuid = grub_iso9660_uuid,
1108
    .mtime = grub_iso9660_mtime,
1109 1110 1111 1112
#ifdef GRUB_UTIL
    .reserved_first_sector = 1,
    .blocklist_install = 1,
#endif
1113 1114 1115
    .next = 0
  };

1116
GRUB_MOD_INIT(iso9660)
1117 1118 1119 1120 1121
{
  grub_fs_register (&grub_iso9660_fs);
  my_mod = mod;
}

1122
GRUB_MOD_FINI(iso9660)
1123 1124 1125
{
  grub_fs_unregister (&grub_iso9660_fs);
}