xfs.c 29.3 KB
Newer Older
1 2 3
/* xfs.c - XFS.  */
/*
 *  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 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 30
GRUB_MOD_LICENSE ("GPLv3+");

31 32 33 34 35 36
#define	XFS_INODE_EXTENTS	9

#define XFS_INODE_FORMAT_INO	1
#define XFS_INODE_FORMAT_EXT	2
#define XFS_INODE_FORMAT_BTREE	3

37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
/* Superblock version field flags */
#define XFS_SB_VERSION_NUMBITS		0x000f
#define	XFS_SB_VERSION_ATTRBIT		0x0010
#define	XFS_SB_VERSION_NLINKBIT		0x0020
#define	XFS_SB_VERSION_QUOTABIT		0x0040
#define	XFS_SB_VERSION_ALIGNBIT		0x0080
#define	XFS_SB_VERSION_DALIGNBIT	0x0100
#define	XFS_SB_VERSION_LOGV2BIT		0x0400
#define	XFS_SB_VERSION_SECTORBIT	0x0800
#define	XFS_SB_VERSION_EXTFLGBIT	0x1000
#define	XFS_SB_VERSION_DIRV2BIT		0x2000
#define XFS_SB_VERSION_MOREBITSBIT	0x8000
#define XFS_SB_VERSION_BITS_SUPPORTED \
	(XFS_SB_VERSION_NUMBITS | \
	 XFS_SB_VERSION_ATTRBIT | \
	 XFS_SB_VERSION_NLINKBIT | \
	 XFS_SB_VERSION_QUOTABIT | \
	 XFS_SB_VERSION_ALIGNBIT | \
	 XFS_SB_VERSION_DALIGNBIT | \
	 XFS_SB_VERSION_LOGV2BIT | \
	 XFS_SB_VERSION_SECTORBIT | \
	 XFS_SB_VERSION_EXTFLGBIT | \
	 XFS_SB_VERSION_DIRV2BIT | \
	 XFS_SB_VERSION_MOREBITSBIT)

/* Recognized xfs format versions */
#define XFS_SB_VERSION_4		4	/* Good old XFS filesystem */
#define XFS_SB_VERSION_5		5	/* CRC enabled filesystem */

/* features2 field flags */
#define XFS_SB_VERSION2_LAZYSBCOUNTBIT	0x00000002	/* Superblk counters */
#define XFS_SB_VERSION2_ATTR2BIT	0x00000008	/* Inline attr rework */
#define XFS_SB_VERSION2_PROJID32BIT	0x00000080	/* 32-bit project ids */
#define XFS_SB_VERSION2_FTYPE		0x00000200	/* inode type in dir */
#define XFS_SB_VERSION2_BITS_SUPPORTED \
	(XFS_SB_VERSION2_LAZYSBCOUNTBIT | \
	 XFS_SB_VERSION2_ATTR2BIT | \
	 XFS_SB_VERSION2_PROJID32BIT | \
	 XFS_SB_VERSION2_FTYPE)

/* incompat feature flags */
#define XFS_SB_FEAT_INCOMPAT_FTYPE      (1 << 0)        /* filetype in dirent */
#define XFS_SB_FEAT_INCOMPAT_SUPPORTED \
	(XFS_SB_FEAT_INCOMPAT_FTYPE)
81 82 83 84 85

struct grub_xfs_sblock
{
  grub_uint8_t magic[4];
  grub_uint32_t bsize;
86 87 88
  grub_uint8_t unused1[24];
  grub_uint16_t uuid[8];
  grub_uint8_t unused2[8];
89 90
  grub_uint64_t rootino;
  grub_uint8_t unused3[20];
91
  grub_uint32_t agsize;
92 93 94
  grub_uint8_t unused4[12];
  grub_uint16_t version;
  grub_uint8_t unused5[6];
95 96
  grub_uint8_t label[12];
  grub_uint8_t log2_bsize;
97 98
  grub_uint8_t log2_sect;
  grub_uint8_t log2_inode;
99 100
  grub_uint8_t log2_inop;
  grub_uint8_t log2_agblk;
101
  grub_uint8_t unused6[67];
102
  grub_uint8_t log2_dirblk;
103 104 105 106 107 108 109
  grub_uint8_t unused7[7];
  grub_uint32_t features2;
  grub_uint8_t unused8[4];
  grub_uint32_t sb_features_compat;
  grub_uint32_t sb_features_ro_compat;
  grub_uint32_t sb_features_incompat;
  grub_uint32_t sb_features_log_incompat;
110
} GRUB_PACKED;
111 112 113

struct grub_xfs_dir_header
{
114
  grub_uint8_t count;
115
  grub_uint8_t largeino;
116 117 118 119
  union
  {
    grub_uint32_t i4;
    grub_uint64_t i8;
120 121
  } GRUB_PACKED parent;
} GRUB_PACKED;
122

123
/* Structure for directory entry inlined in the inode */
124 125 126 127 128
struct grub_xfs_dir_entry
{
  grub_uint8_t len;
  grub_uint16_t offset;
  char name[1];
129
  /* Inode number follows, 32 / 64 bits.  */
130
} GRUB_PACKED;
131

132
/* Structure for directory entry in a block */
133 134 135 136
struct grub_xfs_dir2_entry
{
  grub_uint64_t inode;
  grub_uint8_t len;
137
} GRUB_PACKED;
138

139 140 141 142 143 144 145
struct grub_xfs_extent
{
  /* This should be a bitfield but bietfields are unportable, so just have
     a raw array and functions extracting useful info from it.
   */
  grub_uint32_t raw[4];
} GRUB_PACKED;
146

147 148 149 150 151 152 153
struct grub_xfs_btree_node
{
  grub_uint8_t magic[4];
  grub_uint16_t level;
  grub_uint16_t numrecs;
  grub_uint64_t left;
  grub_uint64_t right;
154 155
  /* In V5 here follow crc, uuid, etc. */
  /* Then follow keys and block pointers */
156
} GRUB_PACKED;
157 158 159 160 161 162

struct grub_xfs_btree_root
{
  grub_uint16_t level;
  grub_uint16_t numrecs;
  grub_uint64_t keys[1];
163
} GRUB_PACKED;
164

165 166 167 168
struct grub_xfs_time
{
  grub_uint32_t sec;
  grub_uint32_t nanosec;
169
} GRUB_PACKED;
170

171 172 173 174 175 176
struct grub_xfs_inode
{
  grub_uint8_t magic[2];
  grub_uint16_t mode;
  grub_uint8_t version;
  grub_uint8_t format;
177 178 179 180
  grub_uint8_t unused2[26];
  struct grub_xfs_time atime;
  struct grub_xfs_time mtime;
  struct grub_xfs_time ctime;
181
  grub_uint64_t size;
182 183 184
  grub_uint64_t nblocks;
  grub_uint32_t extsize;
  grub_uint32_t nextents;
185 186 187
  grub_uint16_t unused3;
  grub_uint8_t fork_offset;
  grub_uint8_t unused4[17];
188
} GRUB_PACKED;
189

190 191 192
#define XFS_V2_INODE_SIZE sizeof(struct grub_xfs_inode)
#define XFS_V3_INODE_SIZE (XFS_V2_INODE_SIZE + 76)

193 194 195 196
struct grub_xfs_dirblock_tail
{
  grub_uint32_t leaf_count;
  grub_uint32_t leaf_stale;
197
} GRUB_PACKED;
198 199 200 201 202 203

struct grub_fshelp_node
{
  struct grub_xfs_data *data;
  grub_uint64_t ino;
  int inode_read;
204
  struct grub_xfs_inode inode;
205 206 207 208 209 210 211 212
};

struct grub_xfs_data
{
  struct grub_xfs_sblock sblock;
  grub_disk_t disk;
  int pos;
  int bsize;
213
  grub_uint32_t agsize;
214 215
  unsigned int hasftype:1;
  unsigned int hascrc:1;
216 217
  struct grub_fshelp_node diropen;
};
218 219 220

static grub_dl_t my_mod;

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 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
static int grub_xfs_sb_hascrc(struct grub_xfs_data *data)
{
  return (data->sblock.version & grub_cpu_to_be16_compile_time(XFS_SB_VERSION_NUMBITS)) ==
	  grub_cpu_to_be16_compile_time(XFS_SB_VERSION_5);
}

static int grub_xfs_sb_hasftype(struct grub_xfs_data *data)
{
  if ((data->sblock.version & grub_cpu_to_be16_compile_time(XFS_SB_VERSION_NUMBITS)) ==
	grub_cpu_to_be16_compile_time(XFS_SB_VERSION_5) &&
      data->sblock.sb_features_incompat & grub_cpu_to_be32_compile_time(XFS_SB_FEAT_INCOMPAT_FTYPE))
    return 1;
  if (data->sblock.version & grub_cpu_to_be16_compile_time(XFS_SB_VERSION_MOREBITSBIT) &&
      data->sblock.features2 & grub_cpu_to_be32_compile_time(XFS_SB_VERSION2_FTYPE))
    return 1;
  return 0;
}

static int grub_xfs_sb_valid(struct grub_xfs_data *data)
{
  grub_dprintf("xfs", "Validating superblock\n");
  if (grub_strncmp ((char *) (data->sblock.magic), "XFSB", 4)
      || data->sblock.log2_bsize < GRUB_DISK_SECTOR_BITS
      || ((int) data->sblock.log2_bsize
	  + (int) data->sblock.log2_dirblk) >= 27)
    {
      grub_error (GRUB_ERR_BAD_FS, "not a XFS filesystem");
      return 0;
    }
  if ((data->sblock.version & grub_cpu_to_be16_compile_time(XFS_SB_VERSION_NUMBITS)) ==
       grub_cpu_to_be16_compile_time(XFS_SB_VERSION_5))
    {
      grub_dprintf("xfs", "XFS v5 superblock detected\n");
      if (data->sblock.sb_features_incompat &
          grub_cpu_to_be32_compile_time(~XFS_SB_FEAT_INCOMPAT_SUPPORTED))
        {
	  grub_error (GRUB_ERR_BAD_FS, "XFS filesystem has unsupported "
		      "incompatible features");
	  return 0;
        }
      return 1;
    }
  else if ((data->sblock.version & grub_cpu_to_be16_compile_time(XFS_SB_VERSION_NUMBITS)) ==
	   grub_cpu_to_be16_compile_time(XFS_SB_VERSION_4))
    {
      grub_dprintf("xfs", "XFS v4 superblock detected\n");
      if (!(data->sblock.version & grub_cpu_to_be16_compile_time(XFS_SB_VERSION_DIRV2BIT)))
	{
	  grub_error (GRUB_ERR_BAD_FS, "XFS filesystem without V2 directories "
		      "is unsupported");
	  return 0;
	}
      if (data->sblock.version & grub_cpu_to_be16_compile_time(~XFS_SB_VERSION_BITS_SUPPORTED) ||
	  (data->sblock.version & grub_cpu_to_be16_compile_time(XFS_SB_VERSION_MOREBITSBIT) &&
	   data->sblock.features2 & grub_cpu_to_be16_compile_time(~XFS_SB_VERSION2_BITS_SUPPORTED)))
	{
	  grub_error (GRUB_ERR_BAD_FS, "XFS filesystem has unsupported version "
		      "bits");
	  return 0;
	}
      return 1;
    }
  return 0;
}

288 289 290 291 292 293
/* Filetype information as used in inodes.  */
#define FILETYPE_INO_MASK	0170000
#define FILETYPE_INO_REG	0100000
#define FILETYPE_INO_DIRECTORY	0040000
#define FILETYPE_INO_SYMLINK	0120000

294 295 296 297 298 299 300 301 302 303
static inline int
GRUB_XFS_INO_AGBITS(struct grub_xfs_data *data)
{
  return ((data)->sblock.log2_agblk + (data)->sblock.log2_inop);
}

static inline grub_uint64_t
GRUB_XFS_INO_INOINAG (struct grub_xfs_data *data,
		      grub_uint64_t ino)
{
304
  return (ino & ((1LL << GRUB_XFS_INO_AGBITS (data)) - 1));
305 306 307 308 309 310
}

static inline grub_uint64_t
GRUB_XFS_INO_AG (struct grub_xfs_data *data,
		 grub_uint64_t ino)
{
311
  return (ino >> GRUB_XFS_INO_AGBITS (data));
312 313 314 315 316 317 318 319 320 321
}

static inline grub_disk_addr_t
GRUB_XFS_FSB_TO_BLOCK (struct grub_xfs_data *data, grub_disk_addr_t fsb)
{
  return ((fsb >> data->sblock.log2_agblk) * data->agsize
	  + (fsb & ((1LL << data->sblock.log2_agblk) - 1)));
}

static inline grub_uint64_t
322
GRUB_XFS_EXTENT_OFFSET (struct grub_xfs_extent *exts, int ex)
323
{
324 325
  return ((grub_be_to_cpu32 (exts[ex].raw[0]) & ~(1 << 31)) << 23
	  | grub_be_to_cpu32 (exts[ex].raw[1]) >> 9);
326 327 328
}

static inline grub_uint64_t
329
GRUB_XFS_EXTENT_BLOCK (struct grub_xfs_extent *exts, int ex)
330
{
331
  return ((grub_uint64_t) (grub_be_to_cpu32 (exts[ex].raw[1])
332
			   & (0x1ff)) << 43
333 334
	  | (grub_uint64_t) grub_be_to_cpu32 (exts[ex].raw[2]) << 11
	  | grub_be_to_cpu32 (exts[ex].raw[3]) >> 21);
335 336 337
}

static inline grub_uint64_t
338
GRUB_XFS_EXTENT_SIZE (struct grub_xfs_extent *exts, int ex)
339
{
340
  return (grub_be_to_cpu32 (exts[ex].raw[3]) & ((1 << 21) - 1));
341 342
}

343

344
static inline grub_uint64_t
345 346
grub_xfs_inode_block (struct grub_xfs_data *data,
		      grub_uint64_t ino)
347 348 349 350 351
{
  long long int inoinag = GRUB_XFS_INO_INOINAG (data, ino);
  long long ag = GRUB_XFS_INO_AG (data, ino);
  long long block;

352
  block = (inoinag >> data->sblock.log2_inop) + ag * data->agsize;
353 354 355 356 357
  block <<= (data->sblock.log2_bsize - GRUB_DISK_SECTOR_BITS);
  return block;
}


358 359 360
static inline int
grub_xfs_inode_offset (struct grub_xfs_data *data,
		       grub_uint64_t ino)
361 362
{
  int inoag = GRUB_XFS_INO_INOINAG (data, ino);
363 364
  return ((inoag & ((1 << data->sblock.log2_inop) - 1)) <<
	  data->sblock.log2_inode);
365 366
}

367 368 369
static inline grub_size_t
grub_xfs_inode_size(struct grub_xfs_data *data)
{
370
  return (grub_size_t)1 << data->sblock.log2_inode;
371 372 373 374 375 376 377 378 379 380 381 382 383 384
}

/*
 * Returns size occupied by XFS inode stored in memory - we store struct
 * grub_fshelp_node there but on disk inode size may be actually larger than
 * struct grub_xfs_inode so we need to account for that so that we can read
 * from disk directly into in-memory structure.
 */
static inline grub_size_t
grub_xfs_fshelp_size(struct grub_xfs_data *data)
{
  return sizeof (struct grub_fshelp_node) - sizeof (struct grub_xfs_inode)
	       + grub_xfs_inode_size(data);
}
385

386 387 388 389
/* This should return void * but XFS code is error-prone with alignment, so
   return char to retain cast-align.
 */
static char *
390 391 392 393 394 395 396 397 398 399
grub_xfs_inode_data(struct grub_xfs_inode *inode)
{
	if (inode->version <= 2)
		return ((char *)inode) + XFS_V2_INODE_SIZE;
	return ((char *)inode) + XFS_V3_INODE_SIZE;
}

static struct grub_xfs_dir_entry *
grub_xfs_inline_de(struct grub_xfs_dir_header *head)
{
400 401 402 403 404 405 406
  /*
    With small inode numbers the header is 4 bytes smaller because of
    smaller parent pointer
  */
  return (struct grub_xfs_dir_entry *)
    (((char *) head) + sizeof(struct grub_xfs_dir_header) -
     (head->largeino ? 0 : sizeof(grub_uint32_t)));
407 408 409 410 411 412
}

static grub_uint8_t *
grub_xfs_inline_de_inopos(struct grub_xfs_data *data,
			  struct grub_xfs_dir_entry *de)
{
413
  return ((grub_uint8_t *)(de + 1)) + de->len - 1 + (data->hasftype ? 1 : 0);
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
}

static struct grub_xfs_dir_entry *
grub_xfs_inline_next_de(struct grub_xfs_data *data,
			struct grub_xfs_dir_header *head,
			struct grub_xfs_dir_entry *de)
{
  char *p = (char *)de + sizeof(struct grub_xfs_dir_entry) - 1 + de->len;

  p += head->largeino ? sizeof(grub_uint64_t) : sizeof(grub_uint32_t);
  if (data->hasftype)
    p++;

  return (struct grub_xfs_dir_entry *)p;
}

static struct grub_xfs_dirblock_tail *
grub_xfs_dir_tail(struct grub_xfs_data *data, void *dirblock)
{
  int dirblksize = 1 << (data->sblock.log2_bsize + data->sblock.log2_dirblk);

  return (struct grub_xfs_dirblock_tail *)
    ((char *)dirblock + dirblksize - sizeof (struct grub_xfs_dirblock_tail));
}

static struct grub_xfs_dir2_entry *
grub_xfs_first_de(struct grub_xfs_data *data, void *dirblock)
{
  if (data->hascrc)
    return (struct grub_xfs_dir2_entry *)((char *)dirblock + 64);
  return (struct grub_xfs_dir2_entry *)((char *)dirblock + 16);
}

static struct grub_xfs_dir2_entry *
grub_xfs_next_de(struct grub_xfs_data *data, struct grub_xfs_dir2_entry *de)
{
  int size = sizeof (struct grub_xfs_dir2_entry) + de->len + 2 /* Tag */;

  if (data->hasftype)
    size++;		/* File type */
  return (struct grub_xfs_dir2_entry *)(((char *)de) + ALIGN_UP(size, 8));
}

457 458 459 460
/* This should return void * but XFS code is error-prone with alignment, so
   return char to retain cast-align.
 */
static char *
461 462 463
grub_xfs_btree_keys(struct grub_xfs_data *data,
		    struct grub_xfs_btree_node *leaf)
{
464
  char *keys = (char *)(leaf + 1);
465 466

  if (data->hascrc)
467
    keys += 48;	/* skip crc, uuid, ... */
468 469 470
  return keys;
}

471 472 473 474
static grub_err_t
grub_xfs_read_inode (struct grub_xfs_data *data, grub_uint64_t ino,
		     struct grub_xfs_inode *inode)
{
475
  grub_uint64_t block = grub_xfs_inode_block (data, ino);
476 477
  int offset = grub_xfs_inode_offset (data, ino);

478 479
  grub_dprintf("xfs", "Reading inode (%"PRIuGRUB_UINT64_T") - %"PRIuGRUB_UINT64_T", %d\n",
	       ino, block, offset);
480
  /* Read the inode.  */
481 482
  if (grub_disk_read (data->disk, block, offset, grub_xfs_inode_size(data),
		      inode))
483 484
    return grub_errno;

485
  if (grub_strncmp ((char *) inode->magic, "IN", 2))
486
    return grub_error (GRUB_ERR_BAD_FS, "not a correct XFS inode");
487 488 489 490

  return 0;
}

491 492 493 494 495 496
static grub_uint64_t
get_fsb (const void *keys, int idx)
{
  const char *p = (const char *) keys + sizeof(grub_uint64_t) * idx;
  return grub_be_to_cpu64 (grub_get_unaligned64 (p));
}
497

498 499
static grub_disk_addr_t
grub_xfs_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock)
500
{
501 502
  struct grub_xfs_btree_node *leaf = 0;
  int ex, nrec;
503
  struct grub_xfs_extent *exts;
504
  grub_uint64_t ret = 0;
505

506 507
  if (node->inode.format == XFS_INODE_FORMAT_BTREE)
    {
508
      struct grub_xfs_btree_root *root;
509
      const char *keys;
510
      int recoffset;
511

512
      leaf = grub_malloc (node->data->bsize);
513 514 515
      if (leaf == 0)
        return 0;

516
      root = (struct grub_xfs_btree_root *) grub_xfs_inode_data(&node->inode);
517
      nrec = grub_be_to_cpu16 (root->numrecs);
518
      keys = (char *) &root->keys[0];
519
      if (node->inode.fork_offset)
520
	recoffset = (node->inode.fork_offset - 1) / 2;
521
      else
522
	recoffset = (grub_xfs_inode_size(node->data)
523 524
		     - ((char *) keys - (char *) &node->inode))
				/ (2 * sizeof (grub_uint64_t));
525 526 527 528 529 530
      do
        {
          int i;

          for (i = 0; i < nrec; i++)
            {
531
              if (fileblock < get_fsb(keys, i))
532 533 534 535 536 537 538 539 540
                break;
            }

          /* Sparse block.  */
          if (i == 0)
            {
              grub_free (leaf);
              return 0;
            }
541

542
          if (grub_disk_read (node->data->disk,
543
                              GRUB_XFS_FSB_TO_BLOCK (node->data, get_fsb (keys, i - 1 + recoffset)) << (node->data->sblock.log2_bsize - GRUB_DISK_SECTOR_BITS),
544
                              0, node->data->bsize, leaf))
545 546
            return 0;

547 548 549 550
	  if ((!node->data->hascrc &&
	       grub_strncmp ((char *) leaf->magic, "BMAP", 4)) ||
	      (node->data->hascrc &&
	       grub_strncmp ((char *) leaf->magic, "BMA3", 4)))
551 552
            {
              grub_free (leaf);
553
              grub_error (GRUB_ERR_BAD_FS, "not a correct XFS BMAP node");
554 555 556 557
              return 0;
            }

          nrec = grub_be_to_cpu16 (leaf->numrecs);
558 559
          keys = grub_xfs_btree_keys(node->data, leaf);
	  recoffset = ((node->data->bsize - ((char *) keys
560 561 562 563
					     - (char *) leaf))
		       / (2 * sizeof (grub_uint64_t)));
	}
      while (leaf->level);
564
      exts = (struct grub_xfs_extent *) keys;
565 566 567 568
    }
  else if (node->inode.format == XFS_INODE_FORMAT_EXT)
    {
      nrec = grub_be_to_cpu32 (node->inode.nextents);
569
      exts = (struct grub_xfs_extent *) grub_xfs_inode_data(&node->inode);
570 571
    }
  else
572 573
    {
      grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
574
		  "XFS does not support inode format %d yet",
575 576 577 578 579 580
		  node->inode.format);
      return 0;
    }

  /* Iterate over each extent to figure out which extent has
     the block we are looking for.  */
581
  for (ex = 0; ex < nrec; ex++)
582
    {
583
      grub_uint64_t start = GRUB_XFS_EXTENT_BLOCK (exts, ex);
584 585
      grub_uint64_t offset = GRUB_XFS_EXTENT_OFFSET (exts, ex);
      grub_uint64_t size = GRUB_XFS_EXTENT_SIZE (exts, ex);
586 587 588 589 590 591 592 593 594

      /* Sparse block.  */
      if (fileblock < offset)
        break;
      else if (fileblock < offset + size)
        {
          ret = (fileblock - offset + start);
          break;
        }
595 596
    }

597
  grub_free (leaf);
598

599
  return GRUB_XFS_FSB_TO_BLOCK(node->data, ret);
600 601 602 603 604 605 606
}


/* 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_xfs_read_file (grub_fshelp_node_t node,
607 608
		    grub_disk_read_hook_t read_hook, void *read_hook_data,
		    grub_off_t pos, grub_size_t len, char *buf, grub_uint32_t header_size)
609
{
610 611
  return grub_fshelp_read_file (node->data->disk, node,
				read_hook, read_hook_data,
612
				pos, len, buf, grub_xfs_read_block,
613
				grub_be_to_cpu64 (node->inode.size) + header_size,
614
				node->data->sblock.log2_bsize
615
				- GRUB_DISK_SECTOR_BITS, 0);
616 617 618 619 620 621
}


static char *
grub_xfs_read_symlink (grub_fshelp_node_t node)
{
622 623 624 625 626 627 628
  grub_ssize_t size = grub_be_to_cpu64 (node->inode.size);

  if (size < 0)
    {
      grub_error (GRUB_ERR_BAD_FS, "invalid symlink");
      return 0;
    }
629 630 631 632

  switch (node->inode.format)
    {
    case XFS_INODE_FORMAT_INO:
633
      return grub_strndup (grub_xfs_inode_data(&node->inode), size);
634 635 636 637 638

    case XFS_INODE_FORMAT_EXT:
      {
	char *symlink;
	grub_ssize_t numread;
639 640 641 642
	int off = 0;

	if (node->data->hascrc)
	  off = 56;
643 644 645 646 647

	symlink = grub_malloc (size + 1);
	if (!symlink)
	  return 0;

648 649
	node->inode.size = grub_be_to_cpu64 (size + off);
	numread = grub_xfs_read_file (node, 0, 0, off, size, symlink, off);
650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679
	if (numread != size)
	  {
	    grub_free (symlink);
	    return 0;
	  }
	symlink[size] = '\0';
	return symlink;
      }
    }

  return 0;
}


static enum grub_fshelp_filetype
grub_xfs_mode_to_filetype (grub_uint16_t mode)
{
  if ((grub_be_to_cpu16 (mode)
       & FILETYPE_INO_MASK) == FILETYPE_INO_DIRECTORY)
    return GRUB_FSHELP_DIR;
  else if ((grub_be_to_cpu16 (mode)
	    & FILETYPE_INO_MASK) == FILETYPE_INO_SYMLINK)
    return GRUB_FSHELP_SYMLINK;
  else if ((grub_be_to_cpu16 (mode)
	    & FILETYPE_INO_MASK) == FILETYPE_INO_REG)
    return GRUB_FSHELP_REG;
  return GRUB_FSHELP_UNKNOWN;
}


680 681
/* Context for grub_xfs_iterate_dir.  */
struct grub_xfs_iterate_dir_ctx
682
{
683 684 685 686
  grub_fshelp_iterate_dir_hook_t hook;
  void *hook_data;
  struct grub_fshelp_node *diro;
};
687

688 689 690 691 692 693
/* Helper for grub_xfs_iterate_dir.  */
static int iterate_dir_call_hook (grub_uint64_t ino, const char *filename,
				  struct grub_xfs_iterate_dir_ctx *ctx)
{
  struct grub_fshelp_node *fdiro;
  grub_err_t err;
694

695
  fdiro = grub_malloc (grub_xfs_fshelp_size(ctx->diro->data) + 1);
696 697 698 699 700
  if (!fdiro)
    {
      grub_print_error ();
      return 0;
    }
701

702 703 704 705 706 707 708 709 710 711
  /* The inode should be read, otherwise the filetype can
     not be determined.  */
  fdiro->ino = ino;
  fdiro->inode_read = 1;
  fdiro->data = ctx->diro->data;
  err = grub_xfs_read_inode (ctx->diro->data, ino, &fdiro->inode);
  if (err)
    {
      grub_print_error ();
      return 0;
712
    }
713

714 715 716 717 718 719 720 721 722 723 724 725 726 727 728
  return ctx->hook (filename, grub_xfs_mode_to_filetype (fdiro->inode.mode),
		    fdiro, ctx->hook_data);
}

static int
grub_xfs_iterate_dir (grub_fshelp_node_t dir,
		      grub_fshelp_iterate_dir_hook_t hook, void *hook_data)
{
  struct grub_fshelp_node *diro = (struct grub_fshelp_node *) dir;
  struct grub_xfs_iterate_dir_ctx ctx = {
    .hook = hook,
    .hook_data = hook_data,
    .diro = diro
  };

729 730 731 732
  switch (diro->inode.format)
    {
    case XFS_INODE_FORMAT_INO:
      {
733
	struct grub_xfs_dir_header *head = (struct grub_xfs_dir_header *) grub_xfs_inode_data(&diro->inode);
734 735
	struct grub_xfs_dir_entry *de = grub_xfs_inline_de(head);
	int smallino = !head->largeino;
736 737 738 739 740 741
	int i;
	grub_uint64_t parent;

	/* If small inode numbers are used to pack the direntry, the
	   parent inode number is small too.  */
	if (smallino)
742
	  parent = grub_be_to_cpu32 (head->parent.i4);
743
	else
744
	  parent = grub_be_to_cpu64 (head->parent.i8);
745 746

	/* Synthesize the direntries for `.' and `..'.  */
747
	if (iterate_dir_call_hook (diro->ino, ".", &ctx))
748 749
	  return 1;

750
	if (iterate_dir_call_hook (parent, "..", &ctx))
751 752
	  return 1;

753
	for (i = 0; i < head->count; i++)
754 755
	  {
	    grub_uint64_t ino;
756
	    grub_uint8_t *inopos = grub_xfs_inline_de_inopos(dir->data, de);
757
	    grub_uint8_t c;
758

759
	    /* inopos might be unaligned.  */
760
	    if (smallino)
761 762 763 764
	      ino = (((grub_uint32_t) inopos[0]) << 24)
		| (((grub_uint32_t) inopos[1]) << 16)
		| (((grub_uint32_t) inopos[2]) << 8)
		| (((grub_uint32_t) inopos[3]) << 0);
765
	    else
766 767 768 769 770 771 772 773
	      ino = (((grub_uint64_t) inopos[0]) << 56)
		| (((grub_uint64_t) inopos[1]) << 48)
		| (((grub_uint64_t) inopos[2]) << 40)
		| (((grub_uint64_t) inopos[3]) << 32)
		| (((grub_uint64_t) inopos[4]) << 24)
		| (((grub_uint64_t) inopos[5]) << 16)
		| (((grub_uint64_t) inopos[6]) << 8)
		| (((grub_uint64_t) inopos[7]) << 0);
774

775 776 777
	    c = de->name[de->len];
	    de->name[de->len] = '\0';
	    if (iterate_dir_call_hook (ino, de->name, &ctx))
778 779 780 781
	      {
		de->name[de->len] = c;
		return 1;
	      }
782
	    de->name[de->len] = c;
783

784
	    de = grub_xfs_inline_next_de(dir->data, head, de);
785 786 787 788 789 790 791 792 793
	  }
	break;
      }

    case XFS_INODE_FORMAT_BTREE:
    case XFS_INODE_FORMAT_EXT:
      {
	grub_ssize_t numread;
	char *dirblock;
794
	grub_uint64_t blk;
795
        int dirblk_size, dirblk_log2;
796

797 798 799 800 801
        dirblk_log2 = (dir->data->sblock.log2_bsize
                       + dir->data->sblock.log2_dirblk);
        dirblk_size = 1 << dirblk_log2;

	dirblock = grub_malloc (dirblk_size);
802
	if (! dirblock)
803 804 805 806
	  return 0;

	/* Iterate over every block the directory has.  */
	for (blk = 0;
807
	     blk < (grub_be_to_cpu64 (dir->inode.size)
808
		    >> dirblk_log2);
809 810
	     blk++)
	  {
811 812
	    struct grub_xfs_dir2_entry *direntry =
					grub_xfs_first_de(dir->data, dirblock);
813
	    int entries;
814 815
	    struct grub_xfs_dirblock_tail *tail =
					grub_xfs_dir_tail(dir->data, dirblock);
816

817
	    numread = grub_xfs_read_file (dir, 0, 0,
818
					  blk << dirblk_log2,
819
					  dirblk_size, dirblock, 0);
820
	    if (numread != dirblk_size)
821
	      return 0;
822 823 824 825 826

	    entries = (grub_be_to_cpu32 (tail->leaf_count)
		       - grub_be_to_cpu32 (tail->leaf_stale));

	    /* Iterate over all entries within this block.  */
827
	    while ((char *)direntry < (char *)tail)
828
	      {
829
		grub_uint8_t *freetag;
830 831
		char *filename;

832
		freetag = (grub_uint8_t *) direntry;
833

834
		if (grub_get_unaligned16 (freetag) == 0XFFFF)
835
		  {
836
		    grub_uint8_t *skip = (freetag + sizeof (grub_uint16_t));
837 838

		    /* This entry is not used, go to the next one.  */
839 840 841
		    direntry = (struct grub_xfs_dir2_entry *)
				(((char *)direntry) +
				grub_be_to_cpu16 (grub_get_unaligned16 (skip)));
842 843 844 845

		    continue;
		  }

846 847 848
		filename = (char *)(direntry + 1);
		/* The byte after the filename is for the filetype, padding, or
		   tag, which is not used by GRUB.  So it can be overwritten. */
849 850
		filename[direntry->len] = '\0';

851 852
		if (iterate_dir_call_hook (grub_be_to_cpu64(direntry->inode), 
					   filename, &ctx))
853 854 855 856 857 858 859 860 861 862 863 864
		  {
		    grub_free (dirblock);
		    return 1;
		  }

		/* Check if last direntry in this block is
		   reached.  */
		entries--;
		if (!entries)
		  break;

		/* Select the next directory entry.  */
865
		direntry = grub_xfs_next_de(dir->data, direntry);
866 867 868 869 870 871 872 873
	      }
	  }
	grub_free (dirblock);
	break;
      }

    default:
      grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
874
		  "XFS does not support inode format %d yet",
875 876 877 878 879 880 881 882 883 884 885
		  diro->inode.format);
    }
  return 0;
}


static struct grub_xfs_data *
grub_xfs_mount (grub_disk_t disk)
{
  struct grub_xfs_data *data = 0;

886
  data = grub_zalloc (sizeof (struct grub_xfs_data));
887 888 889
  if (!data)
    return 0;

890
  grub_dprintf("xfs", "Reading sb\n");
891 892
  /* Read the superblock.  */
  if (grub_disk_read (disk, 0, 0,
893
		      sizeof (struct grub_xfs_sblock), &data->sblock))
894
    goto fail;
895

896 897
  if (!grub_xfs_sb_valid(data))
    goto fail;
898

899 900 901
  data = grub_realloc (data,
		       sizeof (struct grub_xfs_data)
		       - sizeof (struct grub_xfs_inode)
902
		       + grub_xfs_inode_size(data) + 1);
903 904 905 906

  if (! data)
    goto fail;

907
  data->diropen.data = data;
908
  data->diropen.ino = grub_be_to_cpu64(data->sblock.rootino);
909
  data->diropen.inode_read = 1;
910 911
  data->bsize = grub_be_to_cpu32 (data->sblock.bsize);
  data->agsize = grub_be_to_cpu32 (data->sblock.agsize);
912 913
  data->hasftype = grub_xfs_sb_hasftype(data);
  data->hascrc = grub_xfs_sb_hascrc(data);
914 915 916

  data->disk = disk;
  data->pos = 0;
917 918
  grub_dprintf("xfs", "Reading root ino %"PRIuGRUB_UINT64_T"\n",
	       grub_cpu_to_be64(data->sblock.rootino));
919

920
  grub_xfs_read_inode (data, data->diropen.ino, &data->diropen.inode);
921 922 923

  return data;
 fail:
924

925
  if (grub_errno == GRUB_ERR_OUT_OF_RANGE)
926
    grub_error (GRUB_ERR_BAD_FS, "not an XFS filesystem");
927

928
  grub_free (data);
929

930 931 932 933
  return 0;
}


934 935
/* Context for grub_xfs_dir.  */
struct grub_xfs_dir_ctx
936
{
937 938 939
  grub_fs_dir_hook_t hook;
  void *hook_data;
};
940

941 942 943 944 945 946 947
/* Helper for grub_xfs_dir.  */
static int
grub_xfs_dir_iter (const char *filename, enum grub_fshelp_filetype filetype,
		   grub_fshelp_node_t node, void *data)
{
  struct grub_xfs_dir_ctx *ctx = data;
  struct grub_dirhook_info info;
948

949 950
  grub_memset (&info, 0, sizeof (info));
  if (node->inode_read)
951
    {
952 953
      info.mtimeset = 1;
      info.mtime = grub_be_to_cpu32 (node->inode.mtime.sec);
954
    }
955 956 957 958 959 960 961 962 963 964 965 966
  info.dir = ((filetype & GRUB_FSHELP_TYPE_MASK) == GRUB_FSHELP_DIR);
  grub_free (node);
  return ctx->hook (filename, &info, ctx->hook_data);
}

static grub_err_t
grub_xfs_dir (grub_device_t device, const char *path,
	      grub_fs_dir_hook_t hook, void *hook_data)
{
  struct grub_xfs_dir_ctx ctx = { hook, hook_data };
  struct grub_xfs_data *data = 0;
  struct grub_fshelp_node *fdiro = 0;
967 968

  grub_dl_ref (my_mod);
969

970 971
  data = grub_xfs_mount (device->disk);
  if (!data)
972
    goto mount_fail;
973

974 975 976 977 978
  grub_fshelp_find_file (path, &data->diropen, &fdiro, grub_xfs_iterate_dir,
			 grub_xfs_read_symlink, GRUB_FSHELP_DIR);
  if (grub_errno)
    goto fail;

979
  grub_xfs_iterate_dir (fdiro, grub_xfs_dir_iter, &ctx);
980

981 982 983 984 985
 fail:
  if (fdiro != &data->diropen)
    grub_free (fdiro);
  grub_free (data);

986 987
 mount_fail:

988 989 990 991 992 993 994 995 996 997 998 999
  grub_dl_unref (my_mod);

  return grub_errno;
}


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

1001
  grub_dl_ref (my_mod);
1002

1003 1004
  data = grub_xfs_mount (file->device->disk);
  if (!data)
1005
    goto mount_fail;
1006

1007 1008 1009 1010
  grub_fshelp_find_file (name, &data->diropen, &fdiro, grub_xfs_iterate_dir,
			 grub_xfs_read_symlink, GRUB_FSHELP_REG);
  if (grub_errno)
    goto fail;
1011

1012 1013 1014 1015 1016 1017
  if (!fdiro->inode_read)
    {
      grub_xfs_read_inode (data, fdiro->ino, &fdiro->inode);
      if (grub_errno)
	goto fail;
    }
1018

1019
  if (fdiro != &data->diropen)
1020
    {
1021
      grub_memcpy (&data->diropen, fdiro, grub_xfs_fshelp_size(data));
1022 1023
      grub_free (fdiro);
    }
1024

1025
  file->size = grub_be_to_cpu64 (data->diropen.inode.size);
1026 1027 1028 1029 1030 1031 1032 1033 1034
  file->data = data;
  file->offset = 0;

  return 0;

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

1036
 mount_fail:
1037 1038 1039 1040 1041 1042 1043
  grub_dl_unref (my_mod);

  return grub_errno;
}


static grub_ssize_t
1044
grub_xfs_read (grub_file_t file, char *buf, grub_size_t len)
1045
{
1046
  struct grub_xfs_data *data =
1047 1048
    (struct grub_xfs_data *) file->data;

1049 1050
  return grub_xfs_read_file (&data->diropen,
			     file->read_hook, file->read_hook_data,
1051
			     file->offset, len, buf, 0);
1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075
}


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

  grub_dl_unref (my_mod);

  return GRUB_ERR_NONE;
}


static grub_err_t
grub_xfs_label (grub_device_t device, char **label)
{
  struct grub_xfs_data *data;
  grub_disk_t disk = device->disk;

  grub_dl_ref (my_mod);

  data = grub_xfs_mount (disk);
  if (data)
1076
    *label = grub_strndup ((char *) (data->sblock.label), 12);
1077 1078 1079 1080 1081 1082 1083 1084 1085 1086
  else
    *label = 0;

  grub_dl_unref (my_mod);

  grub_free (data);

  return grub_errno;
}

1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097
static grub_err_t
grub_xfs_uuid (grub_device_t device, char **uuid)
{
  struct grub_xfs_data *data;
  grub_disk_t disk = device->disk;

  grub_dl_ref (my_mod);

  data = grub_xfs_mount (disk);
  if (data)
    {
1098
      *uuid = grub_xasprintf ("%04x%04x-%04x-%04x-%04x-%04x%04x%04x",
1099 1100 1101 1102 1103 1104 1105 1106
			     grub_be_to_cpu16 (data->sblock.uuid[0]),
			     grub_be_to_cpu16 (data->sblock.uuid[1]),
			     grub_be_to_cpu16 (data->sblock.uuid[2]),
			     grub_be_to_cpu16 (data->sblock.uuid[3]),
			     grub_be_to_cpu16 (data->sblock.uuid[4]),
			     grub_be_to_cpu16 (data->sblock.uuid[5]),
			     grub_be_to_cpu16 (data->sblock.uuid[6]),
			     grub_be_to_cpu16 (data->sblock.uuid[7]));
1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117
    }
  else
    *uuid = NULL;

  grub_dl_unref (my_mod);

  grub_free (data);

  return grub_errno;
}

1118 1119 1120 1121 1122 1123 1124 1125 1126 1127


static struct grub_fs grub_xfs_fs =
  {
    .name = "xfs",
    .dir = grub_xfs_dir,
    .open = grub_xfs_open,
    .read = grub_xfs_read,
    .close = grub_xfs_close,
    .label = grub_xfs_label,
1128
    .uuid = grub_xfs_uuid,
1129 1130
#ifdef GRUB_UTIL
    .reserved_first_sector = 0,
1131
    .blocklist_install = 1,
1132
#endif
1133 1134 1135
    .next = 0
  };

1136
GRUB_MOD_INIT(xfs)
1137 1138 1139 1140 1141
{
  grub_fs_register (&grub_xfs_fs);
  my_mod = mod;
}

1142
GRUB_MOD_FINI(xfs)
1143 1144 1145
{
  grub_fs_unregister (&grub_xfs_fs);
}