affs.c 17.2 KB
Newer Older
1 2 3
/* affs.c - Amiga Fast FileSystem.  */
/*
 *  GRUB  --  GRand Unified Bootloader
4
 *  Copyright (C) 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 23 24 25 26 27
 */

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

30 31
GRUB_MOD_LICENSE ("GPLv3+");

32 33 34 35
/* The affs bootblock.  */
struct grub_affs_bblock
{
  grub_uint8_t type[3];
36
  grub_uint8_t flags;
37 38
  grub_uint32_t checksum;
  grub_uint32_t rootblock;
39
} GRUB_PACKED;
40

41 42 43 44
/* Set if the filesystem is a AFFS filesystem.  Otherwise this is an
   OFS filesystem.  */
#define GRUB_AFFS_FLAG_FFS	1

45 46 47
/* The affs rootblock.  */
struct grub_affs_rblock
{
48
  grub_uint32_t type;
49 50 51 52 53
  grub_uint8_t unused1[8];
  grub_uint32_t htsize;
  grub_uint32_t unused2;
  grub_uint32_t checksum;
  grub_uint32_t hashtable[1];
54
} GRUB_PACKED;
55

56 57 58 59
struct grub_affs_time
{
  grub_int32_t day;
  grub_uint32_t min;
60
  grub_uint32_t hz;
61
} GRUB_PACKED;
62

63 64 65 66 67
/* The second part of a file header block.  */
struct grub_affs_file
{
  grub_uint8_t unused1[12];
  grub_uint32_t size;
68 69
  grub_uint8_t unused2[92];
  struct grub_affs_time mtime;
70 71
  grub_uint8_t namelen;
  grub_uint8_t name[30];
72 73 74
  grub_uint8_t unused3[5];
  grub_uint32_t hardlink;
  grub_uint32_t unused4[6];
75 76 77
  grub_uint32_t next;
  grub_uint32_t parent;
  grub_uint32_t extension;
78
  grub_uint32_t type;
79
} GRUB_PACKED;
80 81 82 83 84 85 86 87 88 89 90

/* The location of `struct grub_affs_file' relative to the end of a
   file header block.  */
#define	GRUB_AFFS_FILE_LOCATION		200

/* The offset in both the rootblock and the file header block for the
   hashtable, symlink and block pointers (all synonyms).  */
#define GRUB_AFFS_HASHTABLE_OFFSET	24
#define GRUB_AFFS_BLOCKPTR_OFFSET	24
#define GRUB_AFFS_SYMLINK_OFFSET	24

91 92 93 94 95 96 97
enum
  {
    GRUB_AFFS_FILETYPE_DIR = 2,
    GRUB_AFFS_FILETYPE_SYMLINK = 3,
    GRUB_AFFS_FILETYPE_HARDLINK = 0xfffffffc,
    GRUB_AFFS_FILETYPE_REG = 0xfffffffd
  };
98 99

#define AFFS_MAX_LOG_BLOCK_SIZE 4
100
#define AFFS_MAX_SUPERBLOCK 1
101

102 103 104 105 106


struct grub_fshelp_node
{
  struct grub_affs_data *data;
107
  grub_uint32_t block;
108 109
  struct grub_fshelp_node *parent;
  struct grub_affs_file di;
110 111
  grub_uint32_t *block_cache;
  grub_uint32_t last_block_cache;
112 113 114 115 116 117 118 119 120
};

/* Information about a "mounted" affs filesystem.  */
struct grub_affs_data
{
  struct grub_affs_bblock bblock;
  struct grub_fshelp_node diropen;
  grub_disk_t disk;

121 122
  /* Log blocksize in sectors.  */
  int log_blocksize;
123 124

  /* The number of entries in the hashtable.  */
125
  unsigned int htsize;
126 127 128 129 130
};

static grub_dl_t my_mod;


131 132
static grub_disk_addr_t
grub_affs_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock)
133
{
134
  grub_uint32_t target, curblock;
135 136 137
  grub_uint32_t pos;
  struct grub_affs_file file;
  struct grub_affs_data *data = node->data;
138
  grub_uint64_t mod;
139

140 141
  if (!node->block_cache)
    {
142
      node->block_cache = grub_malloc (((grub_be_to_cpu32 (node->di.size)
143 144
					 >> (9 + node->data->log_blocksize))
					/ data->htsize + 2)
145 146 147 148 149 150 151 152 153 154
				       * sizeof (node->block_cache[0]));
      if (!node->block_cache)
	return -1;
      node->last_block_cache = 0;
      node->block_cache[0] = node->block;
    }

  /* Files are at most 2G on AFFS, so no need for 64-bit division.  */
  target = (grub_uint32_t) fileblock / data->htsize;
  mod = (grub_uint32_t) fileblock % data->htsize;
155 156
  /* Find the block that points to the fileblock we are looking up by
     following the chain until the right table is reached.  */
157
  for (curblock = node->last_block_cache + 1; curblock < target + 1; curblock++)
158
    {
159
      grub_disk_read (data->disk,
160 161 162
		      (((grub_uint64_t) node->block_cache[curblock - 1] + 1)
		       << data->log_blocksize) - 1,
		      GRUB_DISK_SECTOR_SIZE - GRUB_AFFS_FILE_LOCATION,
163
		      sizeof (file), &file);
164 165
      if (grub_errno)
	return 0;
166

167 168
      node->block_cache[curblock] = grub_be_to_cpu32 (file.extension);
      node->last_block_cache = curblock;
169 170 171
    }

  /* Translate the fileblock to the block within the right table.  */
172 173
  grub_disk_read (data->disk, (grub_uint64_t) node->block_cache[target]
		  << data->log_blocksize,
174
		  GRUB_AFFS_BLOCKPTR_OFFSET
175
		  + (data->htsize - mod - 1) * sizeof (pos),
176
		  sizeof (pos), &pos);
177 178
  if (grub_errno)
    return 0;
179

180 181 182 183 184 185 186 187
  return grub_be_to_cpu32 (pos);
}

static struct grub_affs_data *
grub_affs_mount (grub_disk_t disk)
{
  struct grub_affs_data *data;
  grub_uint32_t *rootblock = 0;
188
  struct grub_affs_rblock *rblock = 0;
189
  int log_blocksize = 0;
190
  int bsnum = 0;
191

192
  data = grub_zalloc (sizeof (struct grub_affs_data));
193 194 195
  if (!data)
    return 0;

196
  for (bsnum = 0; bsnum < AFFS_MAX_SUPERBLOCK + 1; bsnum++)
197
    {
198 199 200
      /* Read the bootblock.  */
      grub_disk_read (disk, bsnum, 0, sizeof (struct grub_affs_bblock),
		      &data->bblock);
201 202
      if (grub_errno)
	goto fail;
203

204 205 206 207
      /* Make sure this is an affs filesystem.  */
      if (grub_strncmp ((char *) (data->bblock.type), "DOS", 3) != 0
	  /* Test if the filesystem is a OFS filesystem.  */
	  || !(data->bblock.flags & GRUB_AFFS_FLAG_FFS))
208 209
	continue;

210 211 212 213 214 215 216
      /* No sane person uses more than 8KB for a block.  At least I hope
	 for that person because in that case this won't work.  */
      if (!rootblock)
	rootblock = grub_malloc (GRUB_DISK_SECTOR_SIZE
				 << AFFS_MAX_LOG_BLOCK_SIZE);
      if (!rootblock)
	goto fail;
217

218 219 220 221 222 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 257 258 259 260 261 262 263 264 265 266 267 268 269
      rblock = (struct grub_affs_rblock *) rootblock;

      /* The filesystem blocksize is not stored anywhere in the filesystem
	 itself.  One way to determine it is try reading blocks for the
	 rootblock until the checksum is correct.  */
      for (log_blocksize = 0; log_blocksize <= AFFS_MAX_LOG_BLOCK_SIZE;
	   log_blocksize++)
	{
	  grub_uint32_t *currblock = rootblock;
	  unsigned int i;
	  grub_uint32_t checksum = 0;

	  /* Read the rootblock.  */
	  grub_disk_read (disk,
			  (grub_uint64_t) grub_be_to_cpu32 (data->bblock.rootblock)
			  << log_blocksize, 0,
			  GRUB_DISK_SECTOR_SIZE << log_blocksize, rootblock);
	  if (grub_errno == GRUB_ERR_OUT_OF_RANGE)
	    {
	      grub_errno = 0;
	      break;
	    }
	  if (grub_errno)
	    goto fail;

	  if (rblock->type != grub_cpu_to_be32_compile_time (2)
	      || rblock->htsize == 0
	      || currblock[(GRUB_DISK_SECTOR_SIZE << log_blocksize)
			   / sizeof (*currblock) - 1]
	      != grub_cpu_to_be32_compile_time (1))
	    continue;

	  for (i = 0; i < (GRUB_DISK_SECTOR_SIZE << log_blocksize)
		 / sizeof (*currblock);
	       i++)
	    checksum += grub_be_to_cpu32 (currblock[i]);

	  if (checksum == 0)
	    {
	      data->log_blocksize = log_blocksize;
	      data->disk = disk;
	      data->htsize = grub_be_to_cpu32 (rblock->htsize);
	      data->diropen.data = data;
	      data->diropen.block = grub_be_to_cpu32 (data->bblock.rootblock);
	      data->diropen.parent = NULL;
	      grub_memcpy (&data->diropen.di, rootblock,
			   sizeof (data->diropen.di));
	      grub_free (rootblock);

	      return data;
	    }
	}
270 271 272
    }

 fail:
273
  if (grub_errno == GRUB_ERR_NONE || grub_errno == GRUB_ERR_OUT_OF_RANGE)
274
    grub_error (GRUB_ERR_BAD_FS, "not an AFFS filesystem");
275

276 277 278 279 280 281 282 283 284 285
  grub_free (data);
  grub_free (rootblock);
  return 0;
}


static char *
grub_affs_read_symlink (grub_fshelp_node_t node)
{
  struct grub_affs_data *data = node->data;
286
  grub_uint8_t *latin1, *utf8;
287
  const grub_size_t symlink_size = ((GRUB_DISK_SECTOR_SIZE
288
				     << data->log_blocksize) - GRUB_AFFS_SYMLINK_OFFSET);
289

290
  latin1 = grub_malloc (symlink_size + 1);
291
  if (!latin1)
292 293
    return 0;

294 295 296
  grub_disk_read (data->disk,
		  (grub_uint64_t) node->block << data->log_blocksize,
		  GRUB_AFFS_SYMLINK_OFFSET,
297
		  symlink_size, latin1);
298 299
  if (grub_errno)
    {
300
      grub_free (latin1);
301 302
      return 0;
    }
303
  latin1[symlink_size] = 0;
304 305 306 307 308 309 310 311 312
  utf8 = grub_malloc (symlink_size * GRUB_MAX_UTF8_PER_LATIN1 + 1);
  if (!utf8)
    {
      grub_free (latin1);
      return 0;
    }
  *grub_latin1_to_utf8 (utf8, latin1, symlink_size) = '\0';
  grub_dprintf ("affs", "Symlink: `%s'\n", utf8);
  grub_free (latin1);
313 314
  if (utf8[0] == ':')
    utf8[0] = '/';
315
  return (char *) utf8;
316 317 318
}


319
/* Helper for grub_affs_iterate_dir.  */
320
static int
321 322 323 324 325
grub_affs_create_node (grub_fshelp_node_t dir,
		       grub_fshelp_iterate_dir_hook_t hook, void *hook_data,
		       struct grub_fshelp_node **node,
		       grub_uint32_t **hashtable,
		       grub_uint32_t block, const struct grub_affs_file *fil)
326 327
{
  struct grub_affs_data *data = dir->data;
328
  int type = GRUB_FSHELP_REG;
329 330 331
  grub_uint8_t name_u8[sizeof (fil->name) * GRUB_MAX_UTF8_PER_LATIN1 + 1];
  grub_size_t len;
  unsigned int nest;
332

333 334
  *node = grub_zalloc (sizeof (**node));
  if (!*node)
335
    {
336 337 338
      grub_free (*hashtable);
      return 1;
    }
339

340 341 342 343 344 345 346 347 348 349 350 351 352
  (*node)->data = data;
  (*node)->block = block;
  (*node)->parent = dir;

  len = fil->namelen;
  if (len > sizeof (fil->name))
    len = sizeof (fil->name);
  *grub_latin1_to_utf8 (name_u8, fil->name, len) = '\0';
  
  (*node)->di = *fil;
  for (nest = 0; nest < 8; nest++)
    {
      switch ((*node)->di.type)
353
	{
354 355 356 357 358 359 360 361
	case grub_cpu_to_be32_compile_time (GRUB_AFFS_FILETYPE_REG):
	  type = GRUB_FSHELP_REG;
	  break;
	case grub_cpu_to_be32_compile_time (GRUB_AFFS_FILETYPE_DIR):
	  type = GRUB_FSHELP_DIR;
	  break;
	case grub_cpu_to_be32_compile_time (GRUB_AFFS_FILETYPE_SYMLINK):
	  type = GRUB_FSHELP_SYMLINK;
362
	  break;
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377
	case grub_cpu_to_be32_compile_time (GRUB_AFFS_FILETYPE_HARDLINK):
	  {
	    grub_err_t err;
	    (*node)->block = grub_be_to_cpu32 ((*node)->di.hardlink);
	    err = grub_disk_read (data->disk,
				  (((grub_uint64_t) (*node)->block + 1) << data->log_blocksize)
				  - 1,
				  GRUB_DISK_SECTOR_SIZE - GRUB_AFFS_FILE_LOCATION,
				  sizeof ((*node)->di), (char *) &(*node)->di);
	    if (err)
	      return 1;
	    continue;
	  }
	default:
	  return 0;
378
	}
379 380
      break;
    }
381

382 383
  if (nest == 8)
    return 0;
384

385
  type |= GRUB_FSHELP_CASE_INSENSITIVE;
386

387 388 389 390 391
  if (hook ((char *) name_u8, type, *node, hook_data))
    {
      grub_free (*hashtable);
      *node = 0;
      return 1;
392
    }
393 394 395 396 397 398 399 400 401 402 403 404 405
  *node = 0;
  return 0;
}

static int
grub_affs_iterate_dir (grub_fshelp_node_t dir,
		       grub_fshelp_iterate_dir_hook_t hook, void *hook_data)
{
  unsigned int i;
  struct grub_affs_file file;
  struct grub_fshelp_node *node = 0;
  struct grub_affs_data *data = dir->data;
  grub_uint32_t *hashtable;
406

407
  /* Create the directory entries for `.' and `..'.  */
408
  node = grub_zalloc (sizeof (*node));
409 410 411 412
  if (!node)
    return 1;
    
  *node = *dir;
413
  if (hook (".", GRUB_FSHELP_DIR, node, hook_data))
414 415 416
    return 1;
  if (dir->parent)
    {
417
      node = grub_zalloc (sizeof (*node));
418 419 420
      if (!node)
	return 1;
      *node = *dir->parent;
421
      if (hook ("..", GRUB_FSHELP_DIR, node, hook_data))
422 423 424
	return 1;
    }

425
  hashtable = grub_zalloc (data->htsize * sizeof (*hashtable));
426 427 428
  if (!hashtable)
    return 1;

429 430 431
  grub_disk_read (data->disk,
		  (grub_uint64_t) dir->block << data->log_blocksize,
		  GRUB_AFFS_HASHTABLE_OFFSET,
432 433 434 435 436 437
		  data->htsize * sizeof (*hashtable), (char *) hashtable);
  if (grub_errno)
    goto fail;

  for (i = 0; i < data->htsize; i++)
    {
438
      grub_uint32_t next;
439 440 441 442 443 444 445 446 447 448

      if (!hashtable[i])
	continue;

      /* Every entry in the hashtable can be chained.  Read the entire
	 chain.  */
      next = grub_be_to_cpu32 (hashtable[i]);

      while (next)
	{
449 450 451 452
	  grub_disk_read (data->disk,
			  (((grub_uint64_t) next + 1) << data->log_blocksize)
			  - 1,
			  GRUB_DISK_SECTOR_SIZE - GRUB_AFFS_FILE_LOCATION,
453 454 455
			  sizeof (file), (char *) &file);
	  if (grub_errno)
	    goto fail;
456

457 458
	  if (grub_affs_create_node (dir, hook, hook_data, &node, &hashtable,
				     next, &file))
459 460 461
	    return 1;

	  next = grub_be_to_cpu32 (file.next);
462
	}
463 464 465 466 467 468 469 470
    }

  grub_free (hashtable);
  return 0;

 fail:
  grub_free (node);
  grub_free (hashtable);
471
  return 0;
472 473 474 475 476 477 478 479 480
}


/* Open a file named NAME and initialize FILE.  */
static grub_err_t
grub_affs_open (struct grub_file *file, const char *name)
{
  struct grub_affs_data *data;
  struct grub_fshelp_node *fdiro = 0;
481

482
  grub_dl_ref (my_mod);
483

484 485 486
  data = grub_affs_mount (file->device->disk);
  if (!data)
    goto fail;
487

488 489 490 491
  grub_fshelp_find_file (name, &data->diropen, &fdiro, grub_affs_iterate_dir,
			 grub_affs_read_symlink, GRUB_FSHELP_REG);
  if (grub_errno)
    goto fail;
492

493
  file->size = grub_be_to_cpu32 (fdiro->di.size);
494 495 496 497 498 499 500 501 502 503 504 505
  data->diropen = *fdiro;
  grub_free (fdiro);

  file->data = data;
  file->offset = 0;

  return 0;

 fail:
  if (data && fdiro != &data->diropen)
    grub_free (fdiro);
  grub_free (data);
506

507 508 509 510 511 512 513 514
  grub_dl_unref (my_mod);

  return grub_errno;
}

static grub_err_t
grub_affs_close (grub_file_t file)
{
515 516 517 518
  struct grub_affs_data *data =
    (struct grub_affs_data *) file->data;

  grub_free (data->diropen.block_cache);
519 520 521 522 523 524 525 526 527
  grub_free (file->data);

  grub_dl_unref (my_mod);

  return GRUB_ERR_NONE;
}

/* Read LEN bytes data from FILE into BUF.  */
static grub_ssize_t
528
grub_affs_read (grub_file_t file, char *buf, grub_size_t len)
529
{
530
  struct grub_affs_data *data =
531 532
    (struct grub_affs_data *) file->data;

533
  return grub_fshelp_read_file (data->diropen.data->disk, &data->diropen,
534
				file->read_hook, file->read_hook_data,
535
				file->offset, len, buf, grub_affs_read_block,
536
				grub_be_to_cpu32 (data->diropen.di.size),
537
				data->log_blocksize, 0);
538 539
}

540 541 542 543 544 545 546 547 548
static grub_int32_t
aftime2ctime (const struct grub_affs_time *t)
{
  return grub_be_to_cpu32 (t->day) * 86400
    + grub_be_to_cpu32 (t->min) * 60
    + grub_be_to_cpu32 (t->hz) / 50
    + 8 * 365 * 86400 + 86400 * 2;
}

549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571
/* Context for grub_affs_dir.  */
struct grub_affs_dir_ctx
{
  grub_fs_dir_hook_t hook;
  void *hook_data;
};

/* Helper for grub_affs_dir.  */
static int
grub_affs_dir_iter (const char *filename, enum grub_fshelp_filetype filetype,
		    grub_fshelp_node_t node, void *data)
{
  struct grub_affs_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 = aftime2ctime (&node->di.mtime);
  grub_free (node);
  return ctx->hook (filename, &info, ctx->hook_data);
}

572
static grub_err_t
573
grub_affs_dir (grub_device_t device, const char *path,
574
	       grub_fs_dir_hook_t hook, void *hook_data)
575
{
576
  struct grub_affs_dir_ctx ctx = { hook, hook_data };
577 578
  struct grub_affs_data *data = 0;
  struct grub_fshelp_node *fdiro = 0;
579

580
  grub_dl_ref (my_mod);
581

582 583 584
  data = grub_affs_mount (device->disk);
  if (!data)
    goto fail;
585

586 587 588 589 590
  grub_fshelp_find_file (path, &data->diropen, &fdiro, grub_affs_iterate_dir,
			 grub_affs_read_symlink, GRUB_FSHELP_DIR);
  if (grub_errno)
    goto fail;

591
  grub_affs_iterate_dir (fdiro, grub_affs_dir_iter, &ctx);
592

593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615
 fail:
  if (data && fdiro != &data->diropen)
    grub_free (fdiro);
  grub_free (data);

  grub_dl_unref (my_mod);

  return grub_errno;
}


static grub_err_t
grub_affs_label (grub_device_t device, char **label)
{
  struct grub_affs_data *data;
  struct grub_affs_file file;
  grub_disk_t disk = device->disk;

  grub_dl_ref (my_mod);

  data = grub_affs_mount (disk);
  if (data)
    {
616
      grub_size_t len;
617 618
      /* The rootblock maps quite well on a file header block, it's
	 something we can use here.  */
619 620 621 622 623
      grub_disk_read (data->disk,
		      (((grub_uint64_t)
			grub_be_to_cpu32 (data->bblock.rootblock) + 1)
		       << data->log_blocksize) - 1,
		      GRUB_DISK_SECTOR_SIZE - GRUB_AFFS_FILE_LOCATION,
624
		      sizeof (file), &file);
625
      if (grub_errno)
626
	return grub_errno;
627

628 629 630
      len = file.namelen;
      if (len > sizeof (file.name))
	len = sizeof (file.name);
631 632 633
      *label = grub_malloc (len * GRUB_MAX_UTF8_PER_LATIN1 + 1);
      if (*label)
	*grub_latin1_to_utf8 ((grub_uint8_t *) *label, file.name, len) = '\0';
634 635 636 637 638 639 640 641 642 643 644
    }
  else
    *label = 0;

  grub_dl_unref (my_mod);

  grub_free (data);

  return grub_errno;
}

645 646 647 648 649 650 651 652 653
static grub_err_t
grub_affs_mtime (grub_device_t device, grub_int32_t *t)
{
  struct grub_affs_data *data;
  grub_disk_t disk = device->disk;
  struct grub_affs_time af_time;

  *t = 0;

654 655
  grub_dl_ref (my_mod);

656 657 658 659 660 661 662
  data = grub_affs_mount (disk);
  if (!data)
    {
      grub_dl_unref (my_mod);
      return grub_errno;
    }

663 664 665 666 667
  grub_disk_read (data->disk,
		  (((grub_uint64_t)
		    grub_be_to_cpu32 (data->bblock.rootblock) + 1)
		   << data->log_blocksize) - 1,
		  GRUB_DISK_SECTOR_SIZE - 40,
668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683
		  sizeof (af_time), &af_time);
  if (grub_errno)
    {
      grub_dl_unref (my_mod);
      grub_free (data);
      return grub_errno;
    }

  *t = aftime2ctime (&af_time);
  grub_dl_unref (my_mod);

  grub_free (data);

  return GRUB_ERR_NONE;
}

684 685 686 687 688 689 690 691 692

static struct grub_fs grub_affs_fs =
  {
    .name = "affs",
    .dir = grub_affs_dir,
    .open = grub_affs_open,
    .read = grub_affs_read,
    .close = grub_affs_close,
    .label = grub_affs_label,
693 694
    .mtime = grub_affs_mtime,

695 696
#ifdef GRUB_UTIL
    .reserved_first_sector = 0,
697
    .blocklist_install = 1,
698
#endif
699 700 701
    .next = 0
  };

702
GRUB_MOD_INIT(affs)
703 704 705 706 707
{
  grub_fs_register (&grub_affs_fs);
  my_mod = mod;
}

708
GRUB_MOD_FINI(affs)
709 710 711
{
  grub_fs_unregister (&grub_affs_fs);
}