433 lines
12 KiB
C
433 lines
12 KiB
C
/*
|
|
* Copyright (c) 2012-2013 Paulo Alcantara <pcacjr@zytor.com>
|
|
*
|
|
* 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 <dprintf.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/dirent.h>
|
|
#include <cache.h>
|
|
#include <core.h>
|
|
#include <disk.h>
|
|
#include <fs.h>
|
|
#include <ilog2.h>
|
|
#include <klibc/compiler.h>
|
|
#include <ctype.h>
|
|
|
|
#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;
|
|
}
|
|
|
|
if (core->di_format == XFS_DINODE_FMT_LOCAL)
|
|
return xfs_fmt_local_readdir(file, dirent, core);
|
|
else if (core->di_format == XFS_DINODE_FMT_EXTENTS)
|
|
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 *)&core->di_literal_area[0] +
|
|
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_bmdr_block_t *)&core->di_literal_area[0];
|
|
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, (char *)&core->di_literal_area[0], pathlen);
|
|
} else if (core->di_format == XFS_DINODE_FMT_EXTENTS) {
|
|
bmbt_irec_get(&rec, (xfs_bmbt_rec_t *)&core->di_literal_area[0]);
|
|
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_superblock(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_superblock(fs, &sb)) {
|
|
xfs_error("Superblock read failed");
|
|
goto out;
|
|
}
|
|
|
|
if (!xfs_is_valid_magicnum(&sb)) {
|
|
xfs_error("Invalid superblock");
|
|
goto out;
|
|
}
|
|
|
|
xfs_debug("magicnum 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,
|
|
};
|