/* * Copyright (c) 2012-2015 Paulo Alcantara * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation. * * This program is distributed in the hope that it would be useful, * 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 * along with this program; if not, write the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include "codepage.h" #include "xfs_types.h" #include "xfs_sb.h" #include "xfs_ag.h" #include "misc.h" #include "xfs.h" #include "xfs_dinode.h" #include "xfs_dir2.h" #include "xfs_readdir.h" static inline int xfs_fmt_local_readdir(struct file *file, struct dirent *dirent, xfs_dinode_t *core) { return xfs_readdir_dir2_local(file, dirent, core); } static inline int xfs_fmt_extents_readdir(struct file *file, struct dirent *dirent, xfs_dinode_t *core) { if (be32_to_cpu(core->di_nextents) <= 1) { /* Single-block Directories */ return xfs_readdir_dir2_block(file, dirent, core); } else if (xfs_dir2_isleaf(file->fs, core)) { /* Leaf Directory */ return xfs_readdir_dir2_leaf(file, dirent, core); } else { /* Node Directory */ return xfs_readdir_dir2_node(file, dirent, core); } } static int xfs_readdir(struct file *file, struct dirent *dirent) { struct fs_info *fs = file->fs; xfs_dinode_t *core; struct inode *inode = file->inode; xfs_debug("file %p dirent %p"); core = xfs_dinode_get_core(fs, inode->ino); if (!core) { xfs_error("Failed to get dinode from disk (ino %llx)", inode->ino); return -1; } switch (core->di_format) { case XFS_DINODE_FMT_LOCAL: return xfs_fmt_local_readdir(file, dirent, core); case XFS_DINODE_FMT_EXTENTS: case XFS_DINODE_FMT_BTREE: return xfs_fmt_extents_readdir(file, dirent, core); } return -1; } static uint32_t xfs_getfssec(struct file *file, char *buf, int sectors, bool *have_more) { return generic_getfssec(file, buf, sectors, have_more); } static int xfs_next_extent(struct inode *inode, uint32_t lstart) { struct fs_info *fs = inode->fs; xfs_dinode_t *core = NULL; xfs_bmbt_irec_t rec; block_t bno; xfs_bmdr_block_t *rblock; int fsize; xfs_bmbt_ptr_t *pp; xfs_btree_block_t *blk; uint16_t nextents; block_t nextbno; uint32_t index; (void)lstart; xfs_debug("inode %p lstart %lu", inode, lstart); core = xfs_dinode_get_core(fs, inode->ino); if (!core) { xfs_error("Failed to get dinode from disk (ino %llx)", inode->ino); goto out; } /* The data fork contains the file's data extents */ if (XFS_PVT(inode)->i_cur_extent == be32_to_cpu(core->di_nextents)) goto out; if (core->di_format == XFS_DINODE_FMT_EXTENTS) { bmbt_irec_get(&rec, (xfs_bmbt_rec_t *)XFS_DFORK_PTR(core, XFS_DATA_FORK) + XFS_PVT(inode)->i_cur_extent); XFS_PVT(inode)->i_cur_extent++; bno = fsblock_to_bytes(fs, rec.br_startblock) >> BLOCK_SHIFT(fs); XFS_PVT(inode)->i_offset = rec.br_startoff; inode->next_extent.pstart = bno << BLOCK_SHIFT(fs) >> SECTOR_SHIFT(fs); inode->next_extent.len = ((rec.br_blockcount << BLOCK_SHIFT(fs)) + SECTOR_SIZE(fs) - 1) >> SECTOR_SHIFT(fs); } else if (core->di_format == XFS_DINODE_FMT_BTREE) { xfs_debug("XFS_DINODE_FMT_BTREE"); index = XFS_PVT(inode)->i_cur_extent++; rblock = XFS_DFORK_PTR(core, XFS_DATA_FORK); fsize = XFS_DFORK_SIZE(core, fs, XFS_DATA_FORK); pp = XFS_BMDR_PTR_ADDR(rblock, 1, xfs_bmdr_maxrecs(fsize, 0)); bno = fsblock_to_bytes(fs, be64_to_cpu(pp[0])) >> BLOCK_SHIFT(fs); /* Find the leaf */ for (;;) { blk = (xfs_btree_block_t *)get_cache(fs->fs_dev, bno); if (be16_to_cpu(blk->bb_level) == 0) break; pp = XFS_BMBT_PTR_ADDR(fs, blk, 1, xfs_bmdr_maxrecs(XFS_INFO(fs)->blocksize, 0)); bno = fsblock_to_bytes(fs, be64_to_cpu(pp[0])) >> BLOCK_SHIFT(fs); } /* Find the right extent among threaded leaves */ for (;;) { nextbno = be64_to_cpu(blk->bb_u.l.bb_rightsib); nextents = be16_to_cpu(blk->bb_numrecs); if (nextents - index > 0) { bmbt_irec_get(&rec, XFS_BMDR_REC_ADDR(blk, index + 1)); bno = fsblock_to_bytes(fs, rec.br_startblock) >> BLOCK_SHIFT(fs); XFS_PVT(inode)->i_offset = rec.br_startoff; inode->next_extent.pstart = bno << BLOCK_SHIFT(fs) >> SECTOR_SHIFT(fs); inode->next_extent.len = ((rec.br_blockcount << BLOCK_SHIFT(fs)) + SECTOR_SIZE(fs) - 1) >> SECTOR_SHIFT(fs); break; } index -= nextents; bno = fsblock_to_bytes(fs, nextbno) >> BLOCK_SHIFT(fs); blk = (xfs_btree_block_t *)get_cache(fs->fs_dev, bno); } } return 0; out: return -1; } static inline struct inode *xfs_fmt_local_find_entry(const char *dname, struct inode *parent, xfs_dinode_t *core) { return xfs_dir2_local_find_entry(dname, parent, core); } static inline struct inode *xfs_fmt_extents_find_entry(const char *dname, struct inode *parent, xfs_dinode_t *core) { if (be32_to_cpu(core->di_nextents) <= 1) { /* Single-block Directories */ return xfs_dir2_block_find_entry(dname, parent, core); } else if (xfs_dir2_isleaf(parent->fs, core)) { /* Leaf Directory */ return xfs_dir2_leaf_find_entry(dname, parent, core); } else { /* Node Directory */ return xfs_dir2_node_find_entry(dname, parent, core); } } static inline struct inode *xfs_fmt_btree_find_entry(const char *dname, struct inode *parent, xfs_dinode_t *core) { return xfs_dir2_node_find_entry(dname, parent, core); } static struct inode *xfs_iget(const char *dname, struct inode *parent) { struct fs_info *fs = parent->fs; xfs_dinode_t *core = NULL; struct inode *inode = NULL; xfs_debug("dname %s parent %p parent ino %lu", dname, parent, parent->ino); core = xfs_dinode_get_core(fs, parent->ino); if (!core) { xfs_error("Failed to get dinode from disk (ino 0x%llx)", parent->ino); goto out; } if (core->di_format == XFS_DINODE_FMT_LOCAL) { inode = xfs_fmt_local_find_entry(dname, parent, core); } else if (core->di_format == XFS_DINODE_FMT_EXTENTS) { inode = xfs_fmt_extents_find_entry(dname, parent, core); } else if (core->di_format == XFS_DINODE_FMT_BTREE) { inode = xfs_fmt_btree_find_entry(dname, parent, core); } if (!inode) { xfs_debug("Entry not found!"); goto out; } if (inode->mode == DT_REG) { XFS_PVT(inode)->i_offset = 0; XFS_PVT(inode)->i_cur_extent = 0; } else if (inode->mode == DT_DIR) { XFS_PVT(inode)->i_btree_offset = 0; XFS_PVT(inode)->i_leaf_ent_offset = 0; } return inode; out: return NULL; } static int xfs_readlink(struct inode *inode, char *buf) { struct fs_info *fs = inode->fs; xfs_dinode_t *core; int pathlen = -1; xfs_bmbt_irec_t rec; block_t db; const char *dir_buf; xfs_debug("inode %p buf %p", inode, buf); core = xfs_dinode_get_core(fs, inode->ino); if (!core) { xfs_error("Failed to get dinode from disk (ino 0x%llx)", inode->ino); goto out; } pathlen = be64_to_cpu(core->di_size); if (!pathlen) goto out; if (pathlen < 0 || pathlen > MAXPATHLEN) { xfs_error("inode (%llu) bad symlink length (%d)", inode->ino, pathlen); goto out; } if (core->di_format == XFS_DINODE_FMT_LOCAL) { memcpy(buf, XFS_DFORK_PTR(core, XFS_DATA_FORK), pathlen); } else if (core->di_format == XFS_DINODE_FMT_EXTENTS) { bmbt_irec_get(&rec, XFS_DFORK_PTR(core, XFS_DATA_FORK)); db = fsblock_to_bytes(fs, rec.br_startblock) >> BLOCK_SHIFT(fs); dir_buf = xfs_dir2_dirblks_get_cached(fs, db, rec.br_blockcount); /* * Syslinux only supports filesystem block size larger than or equal to * 4 KiB. Thus, one directory block is far enough to hold the maximum * symbolic link file content, which is only 1024 bytes long. */ memcpy(buf, dir_buf, pathlen); } out: return pathlen; } static struct inode *xfs_iget_root(struct fs_info *fs) { xfs_dinode_t *core = NULL; struct inode *inode = xfs_new_inode(fs); xfs_debug("Looking for the root inode..."); core = xfs_dinode_get_core(fs, XFS_INFO(fs)->rootino); if (!core) { xfs_error("Inode core's magic number does not match!"); xfs_debug("magic number 0x%04x", be16_to_cpu(core->di_magic)); goto out; } fill_xfs_inode_pvt(fs, inode, XFS_INFO(fs)->rootino); xfs_debug("Root inode has been found!"); if ((be16_to_cpu(core->di_mode) & S_IFMT) != S_IFDIR) { xfs_error("root inode is not a directory ?! No makes sense..."); goto out; } inode->ino = XFS_INFO(fs)->rootino; inode->mode = DT_DIR; inode->size = be64_to_cpu(core->di_size); return inode; out: free(inode); return NULL; } static inline int xfs_read_sb(struct fs_info *fs, xfs_sb_t *sb) { struct disk *disk = fs->fs_dev->disk; if (!disk->rdwr_sectors(disk, sb, XFS_SB_DADDR, 1, false)) return -1; return 0; } static struct xfs_fs_info *xfs_new_sb_info(xfs_sb_t *sb) { struct xfs_fs_info *info; info = malloc(sizeof *info); if (!info) malloc_error("xfs_fs_info structure"); info->blocksize = be32_to_cpu(sb->sb_blocksize); info->block_shift = sb->sb_blocklog; info->dirblksize = 1 << (sb->sb_blocklog + sb->sb_dirblklog); info->dirblklog = sb->sb_dirblklog; info->inopb_shift = sb->sb_inopblog; info->agblk_shift = sb->sb_agblklog; info->rootino = be64_to_cpu(sb->sb_rootino); info->agblocks = be32_to_cpu(sb->sb_agblocks); info->agblocks_shift = sb->sb_agblklog; info->agcount = be32_to_cpu(sb->sb_agcount); info->inodesize = be16_to_cpu(sb->sb_inodesize); info->inode_shift = sb->sb_inodelog; return info; } static int xfs_fs_init(struct fs_info *fs) { struct disk *disk = fs->fs_dev->disk; xfs_sb_t sb; struct xfs_fs_info *info; xfs_debug("fs %p", fs); SECTOR_SHIFT(fs) = disk->sector_shift; SECTOR_SIZE(fs) = 1 << SECTOR_SHIFT(fs); if (xfs_read_sb(fs, &sb)) { xfs_error("Superblock read failed"); goto out; } if (!xfs_is_valid_sb(&sb)) { xfs_error("Invalid superblock"); goto out; } xfs_debug("sb magic: 0x%lX", be32_to_cpu(sb.sb_magicnum)); info = xfs_new_sb_info(&sb); if (!info) { xfs_error("Failed to fill in filesystem-specific info structure"); goto out; } fs->fs_info = info; xfs_debug("block_shift %u blocksize 0x%lX (%lu)", info->block_shift, info->blocksize, info->blocksize); xfs_debug("rootino 0x%llX (%llu)", info->rootino, info->rootino); BLOCK_SHIFT(fs) = info->block_shift; BLOCK_SIZE(fs) = info->blocksize; cache_init(fs->fs_dev, BLOCK_SHIFT(fs)); XFS_INFO(fs)->dirleafblk = xfs_dir2_db_to_da(fs, XFS_DIR2_LEAF_FIRSTDB(fs)); return BLOCK_SHIFT(fs); out: return -1; } const struct fs_ops xfs_fs_ops = { .fs_name = "xfs", .fs_flags = FS_USEMEM | FS_THISIND, .fs_init = xfs_fs_init, .iget_root = xfs_iget_root, .searchdir = NULL, .getfssec = xfs_getfssec, .open_config = generic_open_config, .close_file = generic_close_file, .mangle_name = generic_mangle_name, .readdir = xfs_readdir, .iget = xfs_iget, .next_extent = xfs_next_extent, .readlink = xfs_readlink, .fs_uuid = NULL, };