| /* |
| * Copyright (C) 2013 Raphael S. Carvalho <[email protected]> |
| * |
| * 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, |
| }; |