blob: a7ef28f32a5a2986c40849ee215d9c6a3297a4ba [file] [log] [blame]
/*
* 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,
};