diff --git a/grub-core/fs/xfs.c b/grub-core/fs/xfs.c index 26d8147a9..f00e43e7d 100644 --- a/grub-core/fs/xfs.c +++ b/grub-core/fs/xfs.c @@ -34,6 +34,50 @@ GRUB_MOD_LICENSE ("GPLv3+"); #define XFS_INODE_FORMAT_EXT 2 #define XFS_INODE_FORMAT_BTREE 3 +/* 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) struct grub_xfs_sblock { @@ -45,7 +89,9 @@ struct grub_xfs_sblock grub_uint64_t rootino; grub_uint8_t unused3[20]; grub_uint32_t agsize; - grub_uint8_t unused4[20]; + grub_uint8_t unused4[12]; + grub_uint16_t version; + grub_uint8_t unused5[6]; grub_uint8_t label[12]; grub_uint8_t log2_bsize; grub_uint8_t log2_sect; @@ -54,12 +100,19 @@ struct grub_xfs_sblock grub_uint8_t log2_agblk; grub_uint8_t unused6[67]; grub_uint8_t log2_dirblk; + 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; } GRUB_PACKED; struct grub_xfs_dir_header { grub_uint8_t count; - grub_uint8_t smallino; + grub_uint8_t largeino; union { grub_uint32_t i4; @@ -67,14 +120,16 @@ struct grub_xfs_dir_header } GRUB_PACKED parent; } GRUB_PACKED; +/* Structure for directory entry inlined in the inode */ struct grub_xfs_dir_entry { grub_uint8_t len; grub_uint16_t offset; char name[1]; - /* Inode number follows, 32 bits. */ + /* Inode number follows, 32 / 64 bits. */ } GRUB_PACKED; +/* Structure for directory entry in a block */ struct grub_xfs_dir2_entry { grub_uint64_t inode; @@ -90,7 +145,8 @@ struct grub_xfs_btree_node grub_uint16_t numrecs; grub_uint64_t left; grub_uint64_t right; - grub_uint64_t keys[1]; + /* In V5 here follow crc, uuid, etc. */ + /* Then follow keys and block pointers */ } GRUB_PACKED; struct grub_xfs_btree_root @@ -123,19 +179,11 @@ struct grub_xfs_inode grub_uint16_t unused3; grub_uint8_t fork_offset; grub_uint8_t unused4[17]; - union - { - char raw[156]; - struct dir - { - struct grub_xfs_dir_header dirhead; - struct grub_xfs_dir_entry direntry[1]; - } dir; - grub_xfs_extent extents[XFS_INODE_EXTENTS]; - struct grub_xfs_btree_root btree; - } GRUB_PACKED data; } GRUB_PACKED; +#define XFS_V2_INODE_SIZE sizeof(struct grub_xfs_inode) +#define XFS_V3_INODE_SIZE (XFS_V2_INODE_SIZE + 76) + struct grub_xfs_dirblock_tail { grub_uint32_t leaf_count; @@ -157,6 +205,8 @@ struct grub_xfs_data int pos; int bsize; grub_uint32_t agsize; + unsigned int hasftype:1; + unsigned int hascrc:1; struct grub_fshelp_node diropen; }; @@ -164,6 +214,71 @@ static grub_dl_t my_mod; +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; +} + /* Filetype information as used in inodes. */ #define FILETYPE_INO_MASK 0170000 #define FILETYPE_INO_REG 0100000 @@ -219,18 +334,6 @@ GRUB_XFS_EXTENT_SIZE (grub_xfs_extent *exts, int ex) return (grub_be_to_cpu32 (exts[ex][3]) & ((1 << 21) - 1)); } -static inline int -GRUB_XFS_ROUND_TO_DIRENT (int pos) -{ - return ((((pos) + 8 - 1) / 8) * 8); -} - -static inline int -GRUB_XFS_NEXT_DIRENT (int pos, int len) -{ - return (pos) + GRUB_XFS_ROUND_TO_DIRENT (8 + 1 + len + 2); -} - static inline grub_uint64_t grub_xfs_inode_block (struct grub_xfs_data *data, @@ -274,6 +377,85 @@ grub_xfs_fshelp_size(struct grub_xfs_data *data) + grub_xfs_inode_size(data); } +static void * +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) +{ + /* + * With small inode numbers the header is 4 bytes smaller because of + * smaller parent pointer + */ + return (void *)(((char *)head) + sizeof(struct grub_xfs_dir_header) - + (head->largeino ? 0 : sizeof(grub_uint32_t))); +} + +static grub_uint8_t * +grub_xfs_inline_de_inopos(struct grub_xfs_data *data, + struct grub_xfs_dir_entry *de) +{ + return ((grub_uint8_t *)(de + 1)) + de->len - 1 + + (data->hasftype ? 1 : 0); +} + +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)); +} + +static grub_uint64_t * +grub_xfs_btree_keys(struct grub_xfs_data *data, + struct grub_xfs_btree_node *leaf) +{ + grub_uint64_t *keys = (grub_uint64_t *)(leaf + 1); + + if (data->hascrc) + keys += 6; /* skip crc, uuid, ... */ + return keys; +} + static grub_err_t grub_xfs_read_inode (struct grub_xfs_data *data, grub_uint64_t ino, struct grub_xfs_inode *inode) @@ -281,6 +463,8 @@ grub_xfs_read_inode (struct grub_xfs_data *data, grub_uint64_t ino, grub_uint64_t block = grub_xfs_inode_block (data, ino); int offset = grub_xfs_inode_offset (data, ino); + grub_dprintf("xfs", "Reading inode (%"PRIuGRUB_UINT64_T") - %"PRIuGRUB_UINT64_T", %d\n", + ino, block, offset); /* Read the inode. */ if (grub_disk_read (data->disk, block, offset, grub_xfs_inode_size(data), inode)) @@ -303,6 +487,7 @@ grub_xfs_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock) if (node->inode.format == XFS_INODE_FORMAT_BTREE) { + struct grub_xfs_btree_root *root; const grub_uint64_t *keys; int recoffset; @@ -310,15 +495,15 @@ grub_xfs_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock) if (leaf == 0) return 0; - nrec = grub_be_to_cpu16 (node->inode.data.btree.numrecs); - keys = &node->inode.data.btree.keys[0]; + root = grub_xfs_inode_data(&node->inode); + nrec = grub_be_to_cpu16 (root->numrecs); + keys = &root->keys[0]; if (node->inode.fork_offset) recoffset = (node->inode.fork_offset - 1) / 2; else recoffset = (grub_xfs_inode_size(node->data) - - ((char *) &node->inode.data.btree.keys - - (char *) &node->inode)) - / (2 * sizeof (grub_uint64_t)); + - ((char *) keys - (char *) &node->inode)) + / (2 * sizeof (grub_uint64_t)); do { int i; @@ -340,7 +525,10 @@ grub_xfs_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock) 0, node->data->bsize, leaf)) return 0; - if (grub_strncmp ((char *) leaf->magic, "BMAP", 4)) + if ((!node->data->hascrc && + grub_strncmp ((char *) leaf->magic, "BMAP", 4)) || + (node->data->hascrc && + grub_strncmp ((char *) leaf->magic, "BMA3", 4))) { grub_free (leaf); grub_error (GRUB_ERR_BAD_FS, "not a correct XFS BMAP node"); @@ -348,8 +536,8 @@ grub_xfs_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock) } nrec = grub_be_to_cpu16 (leaf->numrecs); - keys = &leaf->keys[0]; - recoffset = ((node->data->bsize - ((char *) &leaf->keys + keys = grub_xfs_btree_keys(node->data, leaf); + recoffset = ((node->data->bsize - ((char *) keys - (char *) leaf)) / (2 * sizeof (grub_uint64_t))); } @@ -359,7 +547,7 @@ grub_xfs_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock) else if (node->inode.format == XFS_INODE_FORMAT_EXT) { nrec = grub_be_to_cpu32 (node->inode.nextents); - exts = &node->inode.data.extents[0]; + exts = grub_xfs_inode_data(&node->inode); } else { @@ -417,7 +605,7 @@ grub_xfs_read_symlink (grub_fshelp_node_t node) switch (node->inode.format) { case XFS_INODE_FORMAT_INO: - return grub_strndup (node->inode.data.raw, size); + return grub_strndup (grub_xfs_inode_data(&node->inode), size); case XFS_INODE_FORMAT_EXT: { @@ -512,23 +700,18 @@ grub_xfs_iterate_dir (grub_fshelp_node_t dir, { case XFS_INODE_FORMAT_INO: { - struct grub_xfs_dir_entry *de = &diro->inode.data.dir.direntry[0]; - int smallino = !diro->inode.data.dir.dirhead.smallino; + struct grub_xfs_dir_header *head = grub_xfs_inode_data(&diro->inode); + struct grub_xfs_dir_entry *de = grub_xfs_inline_de(head); + int smallino = !head->largeino; 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) - { - parent = grub_be_to_cpu32 (diro->inode.data.dir.dirhead.parent.i4); - /* The header is a bit smaller than usual. */ - de = (struct grub_xfs_dir_entry *) ((char *) de - 4); - } + parent = grub_be_to_cpu32 (head->parent.i4); else - { - parent = grub_be_to_cpu64(diro->inode.data.dir.dirhead.parent.i8); - } + parent = grub_be_to_cpu64 (head->parent.i8); /* Synthesize the direntries for `.' and `..'. */ if (iterate_dir_call_hook (diro->ino, ".", &ctx)) @@ -537,12 +720,10 @@ grub_xfs_iterate_dir (grub_fshelp_node_t dir, if (iterate_dir_call_hook (parent, "..", &ctx)) return 1; - for (i = 0; i < diro->inode.data.dir.dirhead.count; i++) + for (i = 0; i < head->count; i++) { grub_uint64_t ino; - grub_uint8_t *inopos = (((grub_uint8_t *) de) - + sizeof (struct grub_xfs_dir_entry) - + de->len - 1); + grub_uint8_t *inopos = grub_xfs_inline_de_inopos(dir->data, de); grub_uint8_t c; /* inopos might be unaligned. */ @@ -567,10 +748,7 @@ grub_xfs_iterate_dir (grub_fshelp_node_t dir, return 1; de->name[de->len] = c; - de = ((struct grub_xfs_dir_entry *) - (((char *) de)+ sizeof (struct grub_xfs_dir_entry) + de->len - + ((smallino ? sizeof (grub_uint32_t) - : sizeof (grub_uint64_t))) - 1)); + de = grub_xfs_inline_next_de(dir->data, head, de); } break; } @@ -597,15 +775,11 @@ grub_xfs_iterate_dir (grub_fshelp_node_t dir, >> dirblk_log2); blk++) { - /* The header is skipped, the first direntry is stored - from byte 16. */ - int pos = 16; + struct grub_xfs_dir2_entry *direntry = + grub_xfs_first_de(dir->data, dirblock); int entries; - int tail_start = (dirblk_size - - sizeof (struct grub_xfs_dirblock_tail)); - - struct grub_xfs_dirblock_tail *tail; - tail = (struct grub_xfs_dirblock_tail *) &dirblock[tail_start]; + struct grub_xfs_dirblock_tail *tail = + grub_xfs_dir_tail(dir->data, dirblock); numread = grub_xfs_read_file (dir, 0, 0, blk << dirblk_log2, @@ -617,13 +791,11 @@ grub_xfs_iterate_dir (grub_fshelp_node_t dir, - grub_be_to_cpu32 (tail->leaf_stale)); /* Iterate over all entries within this block. */ - while (pos < tail_start) + while ((char *)direntry < (char *)tail) { - struct grub_xfs_dir2_entry *direntry; grub_uint8_t *freetag; char *filename; - direntry = (struct grub_xfs_dir2_entry *) &dirblock[pos]; freetag = (grub_uint8_t *) direntry; if (grub_get_unaligned16 (freetag) == 0XFFFF) @@ -631,14 +803,16 @@ grub_xfs_iterate_dir (grub_fshelp_node_t dir, grub_uint8_t *skip = (freetag + sizeof (grub_uint16_t)); /* This entry is not used, go to the next one. */ - pos += grub_be_to_cpu16 (grub_get_unaligned16 (skip)); + direntry = (struct grub_xfs_dir2_entry *) + (((char *)direntry) + + grub_be_to_cpu16 (grub_get_unaligned16 (skip))); continue; } - filename = &dirblock[pos + sizeof (*direntry)]; - /* The byte after the filename is for the tag, which - is not used by GRUB. So it can be overwritten. */ + 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. */ filename[direntry->len] = '\0'; if (iterate_dir_call_hook (grub_be_to_cpu64(direntry->inode), @@ -655,8 +829,7 @@ grub_xfs_iterate_dir (grub_fshelp_node_t dir, break; /* Select the next directory entry. */ - pos = GRUB_XFS_NEXT_DIRENT (pos, direntry->len); - pos = GRUB_XFS_ROUND_TO_DIRENT (pos); + direntry = grub_xfs_next_de(dir->data, direntry); } } grub_free (dirblock); @@ -681,19 +854,14 @@ grub_xfs_mount (grub_disk_t disk) if (!data) return 0; + grub_dprintf("xfs", "Reading sb\n"); /* Read the superblock. */ if (grub_disk_read (disk, 0, 0, sizeof (struct grub_xfs_sblock), &data->sblock)) goto fail; - 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"); - goto fail; - } + if (!grub_xfs_sb_valid(data)) + goto fail; data = grub_realloc (data, sizeof (struct grub_xfs_data) @@ -708,9 +876,13 @@ grub_xfs_mount (grub_disk_t disk) data->diropen.inode_read = 1; data->bsize = grub_be_to_cpu32 (data->sblock.bsize); data->agsize = grub_be_to_cpu32 (data->sblock.agsize); + data->hasftype = grub_xfs_sb_hasftype(data); + data->hascrc = grub_xfs_sb_hascrc(data); data->disk = disk; data->pos = 0; + grub_dprintf("xfs", "Reading root ino %"PRIuGRUB_UINT64_T"\n", + grub_cpu_to_be64(data->sblock.rootino)); grub_xfs_read_inode (data, data->diropen.ino, &data->diropen.inode);