/* * Copyright (C) 2013 Raphael S. Carvalho <raphael.scarv@gmail.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; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will 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 to the * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <dprintf.h> #include <stdio.h> #include <string.h> #include <sys/dirent.h> #include <cache.h> #include <disk.h> #include <fs.h> #include <minmax.h> #include "core.h" #include "ufs.h" /* * Read the super block and check magic fields based on * passed paramaters. */ static bool do_checksb(struct ufs_super_block *sb, struct disk *disk, const uint32_t sblock_off, const uint32_t ufs_smagic) { uint32_t lba; static uint32_t count; /* How many sectors are needed to fill sb struct */ if (!count) count = sizeof *sb >> disk->sector_shift; /* Get lba address based on sector size of disk */ lba = sblock_off >> (disk->sector_shift); /* Read super block */ disk->rdwr_sectors(disk, sb, lba, count, 0); if (sb->magic == ufs_smagic) return true; return false; } /* * Go through all possible ufs superblock offsets. * TODO: Add UFS support to removable media (sb offset: 0). */ static int ufs_checksb(struct ufs_super_block *sb, struct disk *disk) { /* Check for UFS1 sb */ if (do_checksb(sb, disk, UFS1_SBLOCK_OFFSET, UFS1_SUPER_MAGIC)) return UFS1; /* Check for UFS2 sb */ if (do_checksb(sb, disk, UFS2_SBLOCK_OFFSET, UFS2_SUPER_MAGIC)) return UFS2; /* UFS2 may also exist in 256k-, but this isn't the default */ if (do_checksb(sb, disk, UFS2_SBLOCK2_OFFSET, UFS2_SUPER_MAGIC)) return UFS2_PIGGY; return NONE; } /* * lblock stands for linear block address, * whereas pblock is the actual blk ptr to get data from. * * UFS1/2 use frag addrs rather than blk ones, then * the offset into the block must be calculated. */ static const void * ufs_get_cache(struct inode *inode, block_t lblock) { const void *data; struct fs_info *fs = inode->fs; struct ufs_sb_info *sb = UFS_SB(inode->fs); uint64_t frag_addr, frag_offset; uint32_t frag_shift; block_t pblock; frag_addr = ufs_bmap(inode, lblock, NULL); if (!frag_addr) return NULL; frag_shift = fs->block_shift - sb->c_blk_frag_shift; /* Get fragment byte address */ frag_offset = frag_addr << frag_shift; /* Convert frag addr to blk addr */ pblock = frag_to_blk(fs, frag_addr); /* Read the blk */ data = get_cache(fs->fs_dev, pblock); /* Return offset into block */ return data + (frag_offset & (fs->block_size - 1)); } /* * Based on fs/ext2/ext2.c * find a dir entry, return it if found, or return NULL. */ static const struct ufs_dir_entry * ufs_find_entry(struct fs_info *fs, struct inode *inode, const char *dname) { const struct ufs_dir_entry *dir; const char *data; int32_t i, offset, maxoffset; block_t index = 0; ufs_debug("ufs_find_entry: dname: %s ", dname); for (i = 0; i < inode->size; i += fs->block_size) { data = ufs_get_cache(inode, index++); offset = 0; maxoffset = min(inode->size-i, fs->block_size); /* The smallest possible size is 9 bytes */ while (offset < maxoffset-8) { dir = (const struct ufs_dir_entry *)(data + offset); if (dir->dir_entry_len > maxoffset - offset) break; /* * Name fields are variable-length and null terminated, * then it's possible to use strcmp directly. */ if (dir->inode_value && !strcmp(dname, (const char *)dir->name)) { ufs_debug("(found)\n"); return dir; } offset += dir->dir_entry_len; } } ufs_debug("(not found)\n"); return NULL; } /* * Get either UFS1/2 inode structures. */ static const void * ufs_get_inode(struct fs_info *fs, int inr) { const char *data; uint32_t group, inode_offset, inode_table; uint32_t block_num, block_off; /* Get cylinder group nr. */ group = inr / UFS_SB(fs)->inodes_per_cg; /* * Ensuring group will not exceed the range 0:groups_count-1. * By the way, this should *never* happen. * Unless the (on-disk) fs structure is corrupted! */ if (group >= UFS_SB(fs)->groups_count) { printf("ufs_get_inode: " "group(%d) exceeded the avail. range (0:%d)\n", group, UFS_SB(fs)->groups_count - 1); return NULL; } /* Offset into inode table of the cylinder group */ inode_offset = inr % UFS_SB(fs)->inodes_per_cg; /* Get inode table blk addr respective to cylinder group */ inode_table = (group * UFS_SB(fs)->blocks_per_cg) + UFS_SB(fs)->off_inode_tbl; /* Calculating staggering offset (UFS1 only!) */ if (UFS_SB(fs)->fs_type == UFS1) inode_table += UFS_SB(fs)->ufs1.delta_value * (group & UFS_SB(fs)->ufs1.cycle_mask); /* Get blk nr and offset into the blk */ block_num = inode_table + inode_offset / UFS_SB(fs)->inodes_per_block; block_off = inode_offset % UFS_SB(fs)->inodes_per_block; /* * Read the blk from the blk addr previously computed; * Calc the inode struct offset into the read block. */ data = get_cache(fs->fs_dev, block_num); return data + block_off * UFS_SB(fs)->inode_size; } static struct inode * ufs1_iget_by_inr(struct fs_info *fs, uint32_t inr) { const struct ufs1_inode *ufs_inode; struct inode *inode; uint64_t *dest; uint32_t *source; int i; ufs_inode = (struct ufs1_inode *) ufs_get_inode(fs, inr); if (!ufs_inode) return NULL; if (!(inode = alloc_inode(fs, inr, sizeof(struct ufs_inode_pvt)))) return NULL; /* UFS1 doesn't support neither creation nor deletion times */ inode->refcnt = ufs_inode->link_count; inode->mode = IFTODT(ufs_inode->file_mode); inode->size = ufs_inode->size; inode->atime = ufs_inode->a_time; inode->mtime = ufs_inode->m_time; inode->blocks = ufs_inode->blocks_held; inode->flags = ufs_inode->flags; /* * Copy and extend blk pointers to 64 bits, so avoid * having two structures for inode private. */ dest = (uint64_t *) inode->pvt; source = (uint32_t *) ufs_inode->direct_blk_ptr; for (i = 0; i < UFS_NBLOCKS; i++) dest[i] = ((uint64_t) source[i]) & 0xFFFFFFFF; return inode; } static struct inode * ufs2_iget_by_inr(struct fs_info *fs, uint32_t inr) { const struct ufs2_inode *ufs_inode; struct inode *inode; ufs_inode = (struct ufs2_inode *) ufs_get_inode(fs, inr); if (!ufs_inode) return NULL; if (!(inode = alloc_inode(fs, inr, sizeof(struct ufs_inode_pvt)))) return NULL; /* UFS2 doesn't support deletion time */ inode->refcnt = ufs_inode->link_count; inode->mode = IFTODT(ufs_inode->file_mode); inode->size = ufs_inode->size; inode->atime = ufs_inode->a_time; inode->ctime = ufs_inode->creat_time; inode->mtime = ufs_inode->m_time; inode->blocks = ufs_inode->bytes_held >> fs->block_shift; inode->flags = ufs_inode->flags; memcpy(inode->pvt, ufs_inode->direct_blk_ptr, sizeof(uint64_t) * UFS_NBLOCKS); return inode; } /* * Both ufs_iget_root and ufs_iget callback based on ufs type. */ static struct inode * ufs_iget_root(struct fs_info *fs) { return UFS_SB(fs)->ufs_iget_by_inr(fs, UFS_ROOT_INODE); } static struct inode * ufs_iget(const char *dname, struct inode *parent) { const struct ufs_dir_entry *dir; struct fs_info *fs = parent->fs; dir = ufs_find_entry(fs, parent, dname); if (!dir) return NULL; return UFS_SB(fs)->ufs_iget_by_inr(fs, dir->inode_value); } static void ufs1_read_blkaddrs(struct inode *inode, char *buf) { uint32_t dest[UFS_NBLOCKS]; const uint64_t *source = (uint64_t *) (inode->pvt); int i; /* Convert ufs_inode_pvt uint64_t fields into uint32_t * Upper-half part of ufs1 private blk addrs are always supposed to be * zero (it's previosuly extended by us), thus data isn't being lost. */ for (i = 0; i < UFS_NBLOCKS; i++) { if ((source[i] >> 32) != 0) { /* This should never happen, but will not prevent anything * from working. */ ufs_debug("ufs1: inode->pvt[%d]: warning!\n", i); } dest[i] = (uint32_t)(source[i] & 0xFFFFFFFF); } memcpy(buf, (const char *) dest, inode->size); } static void ufs2_read_blkaddrs(struct inode *inode, char *buf) { memcpy(buf, (const char *) (inode->pvt), inode->size); } /* * Taken from ext2/ext2.c. * Read the entire contents of an inode into a memory buffer */ static int cache_get_file(struct inode *inode, void *buf, size_t bytes) { struct fs_info *fs = inode->fs; size_t block_size = BLOCK_SIZE(fs); uint32_t index = 0; /* Logical block number */ size_t chunk; const char *data; char *p = buf; if (inode->size > bytes) bytes = inode->size; while (bytes) { chunk = min(bytes, block_size); data = ufs_get_cache(inode, index++); memcpy(p, data, chunk); bytes -= chunk; p += chunk; } return 0; } static int ufs_readlink(struct inode *inode, char *buf) { struct fs_info *fs = inode->fs; uint32_t i_symlink_limit; if (inode->size > BLOCK_SIZE(fs)) return -1; /* Error! */ // TODO: use UFS_SB(fs)->maxlen_isymlink instead. i_symlink_limit = ((UFS_SB(fs)->fs_type == UFS1) ? sizeof(uint32_t) : sizeof(uint64_t)) * UFS_NBLOCKS; ufs_debug("UFS_SB(fs)->maxlen_isymlink=%d", UFS_SB(fs)->maxlen_isymlink); if (inode->size <= i_symlink_limit) UFS_SB(fs)->ufs_read_blkaddrs(inode, buf); else cache_get_file(inode, buf, inode->size); return inode->size; } static inline enum dir_type_flags get_inode_mode(uint8_t type) { switch(type) { case UFS_DTYPE_FIFO: return DT_FIFO; case UFS_DTYPE_CHARDEV: return DT_CHR; case UFS_DTYPE_DIR: return DT_DIR; case UFS_DTYPE_BLOCK: return DT_BLK; case UFS_DTYPE_RFILE: return DT_REG; case UFS_DTYPE_SYMLINK: return DT_LNK; case UFS_DTYPE_SOCKET: return DT_SOCK; case UFS_DTYPE_WHITEOUT: return DT_WHT; default: return DT_UNKNOWN; } } /* * Read one directory entry at a time */ static int ufs_readdir(struct file *file, struct dirent *dirent) { struct fs_info *fs = file->fs; struct inode *inode = file->inode; const struct ufs_dir_entry *dir; const char *data; block_t index = file->offset >> fs->block_shift; if (file->offset >= inode->size) return -1; /* End of file */ data = ufs_get_cache(inode, index); dir = (const struct ufs_dir_entry *) (data + (file->offset & (BLOCK_SIZE(fs) - 1))); dirent->d_ino = dir->inode_value; dirent->d_off = file->offset; dirent->d_reclen = offsetof(struct dirent, d_name) + dir->name_length + 1; dirent->d_type = get_inode_mode(dir->file_type & 0x0F); memcpy(dirent->d_name, dir->name, dir->name_length); dirent->d_name[dir->name_length] = '\0'; file->offset += dir->dir_entry_len; /* Update for next reading */ return 0; } static inline struct ufs_sb_info * set_ufs_info(struct ufs_super_block *sb, int ufs_type) { struct ufs_sb_info *sbi; sbi = malloc(sizeof *sbi); if (!sbi) malloc_error("ufs_sb_info structure"); /* Setting up UFS-dependent info */ if (ufs_type == UFS1) { sbi->inode_size = sizeof (struct ufs1_inode); sbi->groups_count = sb->ufs1.nr_frags / sb->frags_per_cg; sbi->ufs1.delta_value = sb->ufs1.delta_value; sbi->ufs1.cycle_mask = sb->ufs1.cycle_mask; sbi->ufs_iget_by_inr = ufs1_iget_by_inr; sbi->ufs_read_blkaddrs = ufs1_read_blkaddrs; sbi->addr_shift = UFS1_ADDR_SHIFT; } else { // UFS2 or UFS2_PIGGY sbi->inode_size = sizeof (struct ufs2_inode); sbi->groups_count = sb->ufs2.nr_frags / sb->frags_per_cg; sbi->ufs_iget_by_inr = ufs2_iget_by_inr; sbi->ufs_read_blkaddrs = ufs2_read_blkaddrs; sbi->addr_shift = UFS2_ADDR_SHIFT; } sbi->inodes_per_block = sb->block_size / sbi->inode_size; sbi->inodes_per_cg = sb->inodes_per_cg; sbi->blocks_per_cg = sb->frags_per_cg >> sb->c_blk_frag_shift; sbi->off_inode_tbl = sb->off_inode_tbl >> sb->c_blk_frag_shift; sbi->c_blk_frag_shift = sb->c_blk_frag_shift; sbi->maxlen_isymlink = sb->maxlen_isymlink; sbi->fs_type = ufs_type; return sbi; } /* * Init the fs metadata and return block size */ static int ufs_fs_init(struct fs_info *fs) { struct disk *disk = fs->fs_dev->disk; struct ufs_super_block sb; struct cache *cs; int ufs_type = ufs_checksb(&sb, disk); if (ufs_type == NONE) return -1; ufs_debug("%s SB FOUND!\n", ufs_type == UFS1 ? "UFS1" : "UFS2"); ufs_debug("Block size: %u\n", sb.block_size); fs->fs_info = (struct ufs_sb_info *) set_ufs_info(&sb, ufs_type); fs->sector_shift = disk->sector_shift; fs->sector_size = disk->sector_size; fs->block_shift = sb.block_shift; fs->block_size = sb.block_size; /* Initialize the cache, and force a clean on block zero */ cache_init(fs->fs_dev, sb.block_shift); cs = _get_cache_block(fs->fs_dev, 0); memset(cs->data, 0, fs->block_size); cache_lock_block(cs); /* For debug purposes */ //ufs_checking(fs); //return -1; return fs->block_shift; } const struct fs_ops ufs_fs_ops = { .fs_name = "ufs", .fs_flags = FS_USEMEM | FS_THISIND, .fs_init = ufs_fs_init, .searchdir = NULL, .getfssec = generic_getfssec, .close_file = generic_close_file, .mangle_name = generic_mangle_name, .open_config = generic_open_config, .readlink = ufs_readlink, .readdir = ufs_readdir, .iget_root = ufs_iget_root, .iget = ufs_iget, .next_extent = ufs_next_extent, };