/* ext2meta.c: ext2 metadata filesystem */ /* Copyright (c) 2001 Jeff Garzik. All rights reserved. With much inspiration from Al Viro, Andrew Morton, ramfs, and ext2. This software may be freely redistributed under the terms of the GNU General Public License. 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., 675 Mass Ave, Cambridge, MA 02139, USA. TODO, in rough priority order: Header from linux/fs/ext2/inode.c, from which this file lifted code: * linux/fs/ext2/inode.c * * Copyright (C) 1992, 1993, 1994, 1995 * Remy Card (card@masi.ibp.fr) * Laboratoire MASI - Institut Blaise Pascal * Universite Pierre et Marie Curie (Paris VI) * * from * * linux/fs/minix/inode.c * * Copyright (C) 1991, 1992 Linus Torvalds * * Goal-directed block allocation by Stephen Tweedie * (sct@dcs.ed.ac.uk), 1993, 1998 * Big-endian to little-endian byte-swapping/bitmaps by * David S. Miller (davem@caip.rutgers.edu), 1995 * 64-bit file support on 64-bit platforms by Jakub Jelinek * (jj@sunsite.ms.mff.cuni.cz) * * Assorted race fixes, rewrite of ext2_get_block() by Al Viro, 2000 */ #define EM_VERSION "0.0.2" #include #include #include #include #include #include #include #include #include #include #include #include #include enum { EXT2META_MAGIC = EXT2_SUPER_MAGIC << 16 | EXT2_SUPER_MAGIC, EM_INODE_MAX_LEN = 40, }; struct em_mnt_info { /* handles to ext2 superblock */ struct vfsmount *mnt; struct super_block *sb; struct ext2_super_block *s_es; }; static struct super_operations em_ops; static struct address_space_operations em_aops; static struct file_operations em_dir_operations; static struct file_operations em_file_operations; static struct inode_operations em_dir_inode_operations; static struct file_operations em_inodes_dir_operations; static struct inode_operations em_inodes_dir_inode_operations; static struct file_operations em_ext2_inode_file_operations; typedef struct { u32 *p; u32 key; struct buffer_head *bh; } Indirect; static int ext2_block_to_path(struct inode *inode, long i_block, int offsets[4]); static Indirect *ext2_get_branch(struct inode *inode, int depth, int *offsets, Indirect chain[4], int *err); extern int ext2_new_block(struct inode *inode, unsigned long goal, u32 * prealloc_count, u32 * prealloc_block, int *err); extern void ext2_free_blocks(struct inode *inode, unsigned long block, unsigned long count); ssize_t em_inode_write(struct file *file, const char *buf, size_t count, loff_t * ppos); static unsigned int em_is_swapfile(struct inode *inode) { struct swap_info_struct *ptr = swap_info; unsigned int i, rc = 0; for (i = 0; i < nr_swapfiles; i++, ptr++) { if ((ptr->flags & SWP_USED) && ptr->swap_map && !ptr->swap_device && (inode == ptr->swap_file->d_inode)) { rc = 1; goto out; } } out: return rc; } static int em_statfs(struct super_block *sb, struct statfs *buf) { buf->f_type = EXT2META_MAGIC; buf->f_bsize = PAGE_CACHE_SIZE; buf->f_namelen = 255; return 0; } /* * Lookup the data. This is trivial - if the dentry didn't already * exist, we know it is negative. */ static struct dentry * em_lookup(struct inode *dir, struct dentry *dentry) { d_add(dentry, NULL); return NULL; } /* * Read a page. Again trivial. If it didn't already exist * in the page cache, it is zero-filled. */ static int em_readpage(struct file *file, struct page *page) { if (!Page_Uptodate(page)) { memset(kmap(page), 0, PAGE_CACHE_SIZE); kunmap(page); flush_dcache_page(page); SetPageUptodate(page); } UnlockPage(page); return 0; } static int em_prepare_write(struct file *file, struct page *page, unsigned offset, unsigned to) { void *addr = kmap(page); if (!Page_Uptodate(page)) { memset(addr, 0, PAGE_CACHE_SIZE); flush_dcache_page(page); SetPageUptodate(page); } SetPageDirty(page); return 0; } static int em_commit_write(struct file *file, struct page *page, unsigned offset, unsigned to) { struct inode *inode = page->mapping->host; loff_t pos = ((loff_t) page->index << PAGE_CACHE_SHIFT) + to; kunmap(page); if (pos > inode->i_size) inode->i_size = pos; return 0; } struct inode * em_get_inode(struct super_block *sb, int mode, int dev) { struct inode *inode = new_inode(sb); if (inode) { inode->i_mode = mode; inode->i_uid = current->fsuid; inode->i_gid = current->fsgid; inode->i_blksize = PAGE_CACHE_SIZE; inode->i_blocks = 0; inode->i_rdev = NODEV; inode->i_mapping->a_ops = &em_aops; inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; switch (mode & S_IFMT) { default: init_special_inode(inode, mode, dev); break; case S_IFREG: inode->i_fop = &em_file_operations; break; case S_IFDIR: inode->i_op = &em_dir_inode_operations; inode->i_fop = &em_dir_operations; break; case S_IFLNK: inode->i_op = &page_symlink_inode_operations; break; } } return inode; } static int em_sync_file(struct file *file, struct dentry *dentry, int datasync) { return 0; } static loff_t em_inode_llseek(struct file *file, loff_t offset, int origin) { if (offset & 3) /* require alignment on block boundary */ return -EINVAL; return default_llseek(file, offset, origin); } static ssize_t em_inode_read(struct file *file, char *buf, size_t count, loff_t * ppos) { struct inode *e2inode; ssize_t ret = 0; struct inode *inode = file->f_dentry->d_inode; struct em_mnt_info *emi = inode->i_sb->u.generic_sbp; loff_t blk = (*ppos) >> 2; /* ie. / sizeof(u32) */ int err = -EIO, depth, offsets[4]; Indirect chain[4]; Indirect *partial; if (*ppos & 3) /* require alignment on block boundary */ return -EINVAL; if (count & 3) /* require alignment on block boundary */ return -EINVAL; count >>= 2; /* change units from bytes to blocks */ /* get ref to ext2 inode we are peeking into */ e2inode = iget(emi->sb, inode->i_ino); if (!e2inode) return -EIO; if (is_bad_inode(e2inode)) { iput(e2inode); return -ENOENT; } lock_kernel(); down(&e2inode->i_sem); #if 0 /* update our inode size, only for cosmetic reasons */ inode->i_size = e2inode->i_blocks * sizeof (u32); #endif /* block beyond end of logical block list */ if (blk > (e2inode->i_size >> inode->i_sb->s_blocksize_bits)) { ret = -ENXIO; goto out; } /* handle end-of-file */ if (blk == (e2inode->i_size >> inode->i_sb->s_blocksize_bits)) { ret = 0; *ppos = e2inode->i_size; goto out; } /* block in direct list */ if (blk < EXT2_NDIR_BLOCKS) { size_t bytes; count = min_t(size_t, count, e2inode->i_blocks - blk); count = min_t(size_t, count, EXT2_NDIR_BLOCKS - blk); bytes = count * sizeof (u32); if (copy_to_user(buf, &e2inode->u.ext2_i.i_data[blk], bytes)) { ret = -EFAULT; goto out; } *ppos = (blk + count) * sizeof (u32); ret = bytes; } /* * handle t/d/indirect blocks */ depth = ext2_block_to_path(inode, blk, offsets); if (depth == 0) { ret = -ENXIO; goto out; } partial = ext2_get_branch(inode, depth, offsets, chain, &err); if (partial) { if (err) ret = err; else ret = -ENXIO; goto cleanup; } else if (err) { partial = chain + depth - 1; /* the whole chain */ ret = err; goto cleanup; } else { unsigned int pos, total; partial = chain + depth - 1; /* the whole chain */ pos = (((char *) partial->p) - partial->bh->b_data) >> 2; total = e2inode->i_sb->s_blocksize >> 2; if (count > (total - pos)) count = total - pos; if (copy_to_user(buf, partial->p, count * sizeof (u32))) ret = -EFAULT; else { ret = count * sizeof (u32); *ppos = (blk + count) * sizeof (u32); } } cleanup: while (partial > chain) { brelse(partial->bh); partial--; } out: up(&e2inode->i_sem); unlock_kernel(); iput(e2inode); return ret; } static int em_inodes_dir_readdir(struct file *filp, void *dirent, filldir_t filldir) { unsigned int i; struct dentry *dentry = filp->f_dentry; struct inode *inode = dentry->d_inode; struct em_mnt_info *emi = inode->i_sb->u.generic_sbp; i = filp->f_pos; switch (i) { case 0: if (filldir(dirent, ".", 1, i, inode->i_ino, DT_DIR) < 0) break; i++; filp->f_pos++; /* fallthrough */ case 1: if (filldir (dirent, "..", 2, i, dentry->d_parent->d_inode->i_ino, DT_DIR) < 0) break; i++; filp->f_pos++; /* fallthrough */ default:{ char iname[EM_INODE_MAX_LEN + 1]; i -= 2; sprintf(iname, "%u", i); while (filldir (dirent, iname, strlen(iname), i + 2, i, DT_REG)) { i++; filp->f_pos++; if (i >= le32_to_cpu(emi->s_es->s_inodes_count)) return 0; sprintf(iname, "%u", i); } } } return 0; } static struct dentry * em_inodes_dir_lookup(struct inode *dir, struct dentry *dentry) { struct inode *inode; char istr[EM_INODE_MAX_LEN + 1], *endp = NULL; unsigned long l; struct super_block *sb = dir->i_sb; struct em_mnt_info *emi = sb->u.generic_sbp; if (dentry->d_name.len > EM_INODE_MAX_LEN) return ERR_PTR(-ENAMETOOLONG); /* convert dentry string to ext2 fs inode number */ memcpy(&istr, dentry->d_name.name, dentry->d_name.len); istr[dentry->d_name.len] = 0; l = simple_strtoul(istr, &endp, 10); if (l < EXT2_BAD_INO || l > le32_to_cpu(emi->s_es->s_inodes_count)) { d_add(dentry, NULL); return NULL; } /* create inode for /mnt/inodes/1234 */ inode = em_get_inode(sb, S_IFREG | 0600, 0); if (!inode) return ERR_PTR(-EACCES); inode->i_ino = (ino_t) l; inode->i_fop = &em_ext2_inode_file_operations; inode->i_size = 0; d_add(dentry, inode); return NULL; } static int em_inode_open(struct inode *inode, struct file *file) { struct super_block *sb = inode->i_sb; struct em_mnt_info *emi = sb->u.generic_sbp; struct inode *e2inode; unsigned int rc; /* get ref to ext2 inode we are peeking into */ e2inode = iget(emi->sb, inode->i_ino); if (!e2inode) return -EIO; if (is_bad_inode(e2inode)) { iput(e2inode); return -ENOENT; } lock_kernel(); /* FIXME: insufficient, other side is unprotected */ rc = em_is_swapfile(e2inode); unlock_kernel(); /* we pay a slight penalty by doing this (not storing a * reference to the underlying ext2 inode) but this * allows us to be fully async for read(2) or write(2) */ iput(e2inode); return rc ? -ENXIO : 0; } static struct file_operations em_ext2_inode_file_operations = { owner:THIS_MODULE, llseek:em_inode_llseek, read:em_inode_read, write:em_inode_write, open:em_inode_open, fsync:em_sync_file, }; static struct address_space_operations em_aops = { readpage:em_readpage, writepage:fail_writepage, prepare_write:em_prepare_write, commit_write:em_commit_write }; static struct file_operations em_file_operations = { owner:THIS_MODULE, read:generic_file_read, write:generic_file_write, mmap:generic_file_mmap, fsync:em_sync_file, }; static struct file_operations em_dir_operations = { owner:THIS_MODULE, read:generic_read_dir, readdir:dcache_readdir, fsync:em_sync_file, }; static struct inode_operations em_dir_inode_operations = { lookup:em_lookup, }; static struct super_operations em_ops = { statfs:em_statfs, put_inode:force_delete, }; static struct file_operations em_inodes_dir_operations = { owner:THIS_MODULE, read:generic_read_dir, readdir:em_inodes_dir_readdir, fsync:em_sync_file, }; static struct inode_operations em_inodes_dir_inode_operations = { lookup:em_inodes_dir_lookup, }; static int parse_options(char *options, struct vfsmount **mnt) { char *this_char; char *value; if (!options) return 1; for (this_char = strtok(options, ","); this_char != NULL; this_char = strtok(NULL, ",")) { if ((value = strchr(this_char, '=')) != NULL) *value++ = 0; if (!strcmp(this_char, "path")) { struct file *file; if (!value || !*value) { printk("ext2meta: the path option requires " "an argument\n"); return 0; } file = filp_open(value, O_RDONLY, 0); if (IS_ERR(file)) { printk("ext2meta: cannot open path: %s\n", value); return 0; } *mnt = mntget(file->f_vfsmnt); /* get ref to ext2 sb */ if (filp_close(file, NULL)) { mntput(*mnt); printk("ext2meta: error closing path: %s\n", value); return 0; } if (!*mnt) { printk("ext2meta: error obtaining path: %s\n", value); return 0; } } else if (!strcmp(this_char, "grpquota") || !strcmp(this_char, "noquota") || !strcmp(this_char, "quota") || !strcmp(this_char, "usrquota")) /* Don't do anything ;-) */ ; else { printk("ext2meta: Unrecognized mount option %s\n", this_char); return 0; } } return 1; } static struct super_block * em_read_super(struct super_block *sb, void *data, int silent) { struct inode *root_ino, *idir_ino; struct dentry *root_de, *idir_de; struct em_mnt_info *emi; /* alloc private per-superblock info */ emi = kmalloc(sizeof (*emi), GFP_NOFS); if (!emi) return NULL; memset(emi, 0, sizeof (*emi)); /* parse mount options */ if (!parse_options((char *) data, &emi->mnt)) goto err_out_free; /* initialize filesystem */ emi->sb = emi->mnt->mnt_sb; emi->s_es = emi->sb->u.ext2_sb.s_es; sb->s_blocksize = PAGE_CACHE_SIZE; sb->s_blocksize_bits = PAGE_CACHE_SHIFT; sb->s_magic = EXT2META_MAGIC; sb->s_op = &em_ops; /* set up root dir */ root_ino = em_get_inode(sb, S_IFDIR | 0755, 0); if (!root_ino) goto err_out_mntput; root_de = d_alloc_root(root_ino); if (!root_de) goto err_out_iput; /* set up 'blocks' sub-dir */ idir_de = d_alloc(root_de, &(const struct qstr) { "blocks", 6, 0}); if (!idir_de) goto err_out_root; idir_ino = em_get_inode(sb, S_IFDIR | 0700, 0); if (!idir_ino) goto err_out_idir_de; idir_ino->i_op = &em_inodes_dir_inode_operations; idir_ino->i_fop = &em_inodes_dir_operations; d_instantiate(idir_de, idir_ino); dget(idir_de); /* Extra count - pin the dentry in core */ /* finish initializing filesystem */ sb->s_root = root_de; sb->u.generic_sbp = emi; return sb; err_out_idir_de: dput(idir_de); err_out_root: dput(root_de); err_out_iput: iput(root_ino); err_out_mntput: mntput(emi->mnt); err_out_free: kfree(emi); return NULL; } static DECLARE_FSTYPE(em_fs_type, "ext2meta", em_read_super, FS_LITTER); static int __init init_em_fs(void) { return register_filesystem(&em_fs_type); } static void __exit exit_em_fs(void) { unregister_filesystem(&em_fs_type); } module_init(init_em_fs); module_exit(exit_em_fs); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Jeff Garzik"); MODULE_DESCRIPTION("Ext2 metadata filesystem"); /* ugly!! we copy static functions from ext2 */ static inline void add_chain(Indirect * p, struct buffer_head *bh, u32 * v) { p->key = *(p->p = v); p->bh = bh; } static inline int verify_chain(Indirect * from, Indirect * to) { while (from <= to && from->key == *from->p) from++; return (from > to); } static int ext2_block_to_path(struct inode *inode, long i_block, int offsets[4]) { int ptrs = EXT2_ADDR_PER_BLOCK(inode->i_sb); int ptrs_bits = EXT2_ADDR_PER_BLOCK_BITS(inode->i_sb); const long direct_blocks = EXT2_NDIR_BLOCKS, indirect_blocks = ptrs, double_blocks = (1 << (ptrs_bits * 2)); int n = 0; if (i_block < 0) { ext2_warning(inode->i_sb, "ext2_block_to_path", "block < 0"); } else if (i_block < direct_blocks) { offsets[n++] = i_block; } else if ((i_block -= direct_blocks) < indirect_blocks) { offsets[n++] = EXT2_IND_BLOCK; offsets[n++] = i_block; } else if ((i_block -= indirect_blocks) < double_blocks) { offsets[n++] = EXT2_DIND_BLOCK; offsets[n++] = i_block >> ptrs_bits; offsets[n++] = i_block & (ptrs - 1); } else if (((i_block -= double_blocks) >> (ptrs_bits * 2)) < ptrs) { offsets[n++] = EXT2_TIND_BLOCK; offsets[n++] = i_block >> (ptrs_bits * 2); offsets[n++] = (i_block >> ptrs_bits) & (ptrs - 1); offsets[n++] = i_block & (ptrs - 1); } else { ext2_warning(inode->i_sb, "ext2_block_to_path", "block > big"); } return n; } static Indirect * ext2_get_branch(struct inode *inode, int depth, int *offsets, Indirect chain[4], int *err) { kdev_t dev = inode->i_dev; int size = inode->i_sb->s_blocksize; Indirect *p = chain; struct buffer_head *bh; *err = 0; /* i_data is not going away, no lock needed */ add_chain(chain, NULL, inode->u.ext2_i.i_data + *offsets); if (!p->key) goto no_block; while (--depth) { bh = bread(dev, le32_to_cpu(p->key), size); if (!bh) goto failure; /* Reader: pointers */ if (!verify_chain(chain, p)) goto changed; add_chain(++p, bh, (u32 *) bh->b_data + *++offsets); /* Reader: end */ if (!p->key) goto no_block; } return NULL; changed: *err = -EAGAIN; goto no_block; failure: *err = -EIO; no_block: return p; } /* free a run of blocks. fully smp threaded. */ static void em_unreserve_blocks(struct inode *inode, u32 blk, unsigned int n) { lock_kernel(); ext2_free_blocks(inode, blk, n); unlock_kernel(); } /* reserve a single block, with proper locking. fail if requested * block is not available. fully smp threaded. */ static int em_reserve_block(struct inode *inode, u32 blk) { u32 tmp; int err = -ENOSPC; lock_kernel(); tmp = ext2_new_block(inode, blk, NULL, NULL, &err); if (!tmp) goto out; if (tmp != blk) { ext2_free_blocks(inode, tmp, 1); err = -ENOENT; goto out; } err = 0; out: unlock_kernel(); return err; } /* move a page's worth of blocks to a new location, or just one block. * fully smp threaded. * axioms: inode blocks are not sparse, no t/d/indirect blocks, * page 100% full of data */ static int em_move_page(struct inode *inode, u32 virt, u32 phys, unsigned int just_one) { struct super_block *sb = inode->i_sb; struct address_space *mapping = inode->i_mapping; const unsigned int blks_per_page = PAGE_CACHE_SIZE >> sb->s_blocksize_bits; unsigned int i, target_blk, page_idx; int err = -EIO; struct buffer_head *head, *bh; struct page *page; u32 freed_blocks[blks_per_page]; target_blk = virt & (blks_per_page - 1); /* reserve requested blocks in block bitmap; abort if even one fails */ if (just_one) { err = em_reserve_block(inode, phys); if (err) return err; } else { for (i = 0; i < blks_per_page; i++) { err = em_reserve_block(inode, phys + i); if (err) { if (i > 0) em_unreserve_blocks(inode, phys, i); goto err_out_nores; } } } /* read page and map it to physical memory */ page_idx = virt / blks_per_page; page = read_cache_page(mapping, page_idx, (filler_t *) mapping->a_ops->readpage, NULL); if (IS_ERR(page)) { err = PTR_ERR(page); goto err_out; } kmap(page); lock_page(page); if (!Page_Uptodate(page) || PageError(page)) { err = -EIO; ClearPageUptodate(page); goto err_out_unlock; } /* buffers might have disappeared before lock_page completes */ if (!page->buffers) { err = -EAGAIN; goto err_out_unlock; } head = page->buffers; /* remap buffer(s) to new location(s) */ bh = head; i = 0; do { if (!just_one || (i == target_blk)) { /* lock the buffer, mark it clean */ lock_buffer(bh); set_buffer_async_io(bh); set_bit(BH_Uptodate, &bh->b_state); clear_bit(BH_Dirty, &bh->b_state); /* remap buffer to new location on disk */ bh->b_blocknr = phys + i; set_bit(BH_Mapped, &bh->b_state); mark_buffer_dirty(bh); } /* next buffer */ i++; bh = bh->b_this_page; } while (bh != head); /* submit the IO */ /* implicit: bh = head; */ i = 0; do { if (!just_one || (i == target_blk)) submit_bh(WRITE, bh); /* next buffer */ i++; bh = bh->b_this_page; } while (bh != head); /* point inode blocks to new blocks */ /* post-BKL, get private inode spinlock for updating i_data */ /* implicit: bh = head; */ lock_kernel(); for (i = 0; i < blks_per_page; i++) { if (!just_one || (i == target_blk)) { freed_blocks[i] = le32_to_cpu(inode->u.ext2_i.i_data[virt + i]); inode->u.ext2_i.i_data[virt + i] = cpu_to_le32(phys + i); } } unlock_kernel(); mark_inode_dirty(inode); /* release our ref to the page. end_buffer_io_async will unlock */ kunmap(page); page_cache_release(page); /* synchronous: wait for end_buffer_io_async to unlock page */ wait_on_page(page); /* FIXME: check PageError? if yes... how to recover? */ /* release blocks previously allocated to inode */ lock_kernel(); for (i = 0; i < blks_per_page; i++) if (!just_one || (i == target_blk)) ext2_free_blocks(inode, freed_blocks[i], 1); unlock_kernel(); return 0; err_out_unlock: UnlockPage(page); kunmap(page); page_cache_release(page); err_out: if (just_one) em_unreserve_blocks(inode, phys, 1); else em_unreserve_blocks(inode, phys, blks_per_page); err_out_nores: return err; } /* move a run of blocks. fully smp threaded */ static int em_move_blocks(struct inode *inode, u32 virt, u32 phys, unsigned int n_blocks) { struct super_block *sb = inode->i_sb; const unsigned int blks_per_page = PAGE_CACHE_SIZE >> sb->s_blocksize_bits; u32 n, last_blk; int err = 0; if (blks_per_page == 0) return -EINVAL; if (n_blocks == 0) return -EINVAL; /* for now... slackness! we fail on inodes with t/d/indirect blocks */ if (n_blocks > EXT2_NDIR_BLOCKS) return -EINVAL; if ((virt + n_blocks - 1) >= EXT2_NDIR_BLOCKS) return -EINVAL; /* start and end offsets, in blocks */ n = virt; last_blk = virt + n_blocks - 1; /* loop through the blocks */ while (n < last_blk) { /* * block number is beginning of page and we have a full * page of blocks to move. The "blks_per_page==1" test * is a bit redundant, but remains because it is IMHO * a fast path case, since PAGE_CACHE_SIZE and an * ext2 fs's block size are often the same in many cases. */ if (likely(blks_per_page == 1) || (((n & (blks_per_page - 1)) == 0) && ((last_blk - n + 1) >= blks_per_page))) { err = em_move_page(inode, n, phys + (n - virt), 0); if (err) return err; n += blks_per_page; } /* otherwise move a single block within a page */ else { err = em_move_page(inode, n, phys + (n - virt), 1); if (err) return err; n++; } /* be nice to the system */ if (current->need_resched) schedule(); } return 0; } /* fully smp threaded */ ssize_t em_inode_write(struct file * file, const char *buf, size_t count, loff_t * ppos) { struct inode *e2inode; ssize_t ret = 0; struct inode *inode = file->f_dentry->d_inode; struct em_mnt_info *emi = inode->i_sb->u.generic_sbp; loff_t blk = (*ppos) >> 2; /* ie. / sizeof(u32) */ int err = -EIO, depth, offsets[4]; Indirect chain[4]; Indirect *partial; if (*ppos & 3) /* require alignment on block boundary */ return -EINVAL; if (count & 3) /* require alignment on block boundary */ return -EINVAL; count >>= 2; /* change units from bytes to blocks */ /* get ref to ext2 inode we are peeking into */ e2inode = iget(emi->sb, inode->i_ino); if (!e2inode) return -EIO; if (is_bad_inode(e2inode)) { iput(e2inode); return -ENOENT; } lock_kernel(); down(&e2inode->i_sem); #if 0 /* update our inode size, only for cosmetic reasons */ inode->i_size = e2inode->i_blocks * sizeof (u32); #endif /* block beyond end of logical block list */ /* TODO: support extending inode */ if (blk >= (e2inode->i_size >> inode->i_sb->s_blocksize_bits)) { ret = -EFBIG; goto out; } /* block in direct list */ if (blk < EXT2_NDIR_BLOCKS) { size_t bytes; u32 i_data[15], i; count = min_t(size_t, count, e2inode->i_blocks - blk); count = min_t(size_t, count, EXT2_NDIR_BLOCKS - blk); bytes = count * sizeof (u32); if (!count) { ret = 0; goto out; } if (copy_from_user(&i_data[blk], buf, bytes)) { ret = -EFAULT; goto out; } /* TODO: cluster data passed to us from userspace */ for (i = blk; i < (blk + count); i++) em_move_blocks(e2inode, blk + i, le32_to_cpu(i_data[blk + i]), 1); *ppos = (blk + count) * sizeof (u32); ret = bytes; goto out; } /* * handle t/d/indirect blocks */ depth = ext2_block_to_path(inode, blk, offsets); if (depth == 0) { ret = -ENXIO; goto out; } partial = ext2_get_branch(inode, depth, offsets, chain, &err); if (partial) { if (err) ret = err; else ret = -ENXIO; goto cleanup; } else if (err) { partial = chain + depth - 1; /* the whole chain */ ret = err; goto cleanup; } else { unsigned int pos, total; partial = chain + depth - 1; /* the whole chain */ pos = (((char *) partial->p) - partial->bh->b_data) >> 2; total = e2inode->i_sb->s_blocksize >> 2; if (count > (total - pos)) count = total - pos; if (copy_from_user(partial->p, buf, count * sizeof (u32))) ret = -EFAULT; else { ret = count * sizeof (u32); *ppos = (blk + count) * sizeof (u32); } } cleanup: while (partial > chain) { brelse(partial->bh); partial--; } out: up(&e2inode->i_sem); unlock_kernel(); return ret; }