/* * Copyright (c) 1997-2003 Erez Zadok * Copyright (c) 2001-2003 Stony Brook University * * For specific licensing information, see the COPYING file distributed with * this package, or get one from ftp://ftp.filesystems.org/pub/fist/COPYING. * * This Copyright notice must be kept intact and distributed with all * fistgen sources INCLUDING sources generated by fistgen. */ /* * Copyright (C) 2004, 2005 Markus Klotzbuecher * * 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. */ /* * $Id$ */ #ifdef HAVE_CONFIG_H # include #endif #include "fist.h" #include "mini_fo.h" STATIC int #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) mini_fo_create(inode_t *dir, dentry_t *dentry, int mode, struct nameidata *nd) #else mini_fo_create(inode_t *dir, dentry_t *dentry, int mode) #endif { int err = 0; check_mini_fo_dentry(dentry); #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) err = create_sto_reg_file(dentry, mode, nd); #else err = create_sto_reg_file(dentry, mode); #endif check_mini_fo_dentry(dentry); return err; } STATIC dentry_t * #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) mini_fo_lookup(inode_t *dir, dentry_t *dentry, struct nameidata* nd) #else mini_fo_lookup(inode_t *dir, dentry_t *dentry) #endif { int err = 0; dentry_t *hidden_dir_dentry; dentry_t *hidden_dentry = NULL; dentry_t *hidden_sto_dir_dentry; dentry_t *hidden_sto_dentry = NULL; /* whiteout flag */ int del_flag = 0; char *bpath = NULL; const char *name; unsigned int namelen; /* Don't allow lookups of META-files */ namelen = strlen(META_FILENAME); if(namelen == dentry->d_name.len) { if(!strncmp(dentry->d_name.name, META_FILENAME, namelen)) { err = -ENOENT; goto out; } } hidden_dir_dentry = dtohd(dentry->d_parent); hidden_sto_dir_dentry = dtohd2(dentry->d_parent); name = dentry->d_name.name; namelen = dentry->d_name.len; /* must initialize dentry operations */ dentry->d_op = &mini_fo_dops; /* setup the del_flag */ del_flag = __meta_check_d_entry(dir, name, namelen); bpath = __meta_check_r_entry(dir, name, namelen); /* perform the lookups of base and storage files: * * This caused some serious trouble, as a lookup_one_len passing * a negative dentry oopses. Solution is to only do the lookup * if the dentry is positive, else we set it to NULL * More trouble, who said a *_dir_dentry can't be NULL? */ if(bpath) { /* Cross-Interposing (C), yeah! */ hidden_dentry = bpath_walk(dir->i_sb, bpath); if(!hidden_dentry || !hidden_dentry->d_inode) { printk(KERN_CRIT "mini_fo_lookup: bpath_walk failed.\n"); err= -EINVAL; goto out; } /* this can be set up safely without fear of spaghetti * interposing as it is only used for copying times */ hidden_dir_dentry = hidden_dentry->d_parent; kfree(bpath); } else if(hidden_dir_dentry && hidden_dir_dentry->d_inode) { mutex_lock(&hidden_dir_dentry->d_inode->i_mutex); hidden_dentry = lookup_one_len(name, hidden_dir_dentry, namelen); mutex_unlock(&hidden_dir_dentry->d_inode->i_mutex); } else { hidden_dentry = NULL; } if(hidden_sto_dir_dentry && hidden_sto_dir_dentry->d_inode) { mutex_lock(&hidden_sto_dir_dentry->d_inode->i_mutex); hidden_sto_dentry = lookup_one_len(name, hidden_sto_dir_dentry, namelen); mutex_unlock(&hidden_sto_dir_dentry->d_inode->i_mutex); } else { hidden_sto_dentry = NULL; } /* catch error in lookup */ if (IS_ERR(hidden_dentry) || IS_ERR(hidden_sto_dentry)) { /* mk: we need to call dput on the dentry, whose * lookup_one_len operation failed, in order to avoid * unmount trouble. */ if(IS_ERR(hidden_dentry)) { printk(KERN_CRIT "mini_fo_lookup: ERR from base dentry, lookup failed.\n"); err = PTR_ERR(hidden_dentry); } else { dput(hidden_dentry); } if(IS_ERR(hidden_sto_dentry)) { printk(KERN_CRIT "mini_fo_lookup: ERR from storage dentry, lookup failed.\n"); err = PTR_ERR(hidden_sto_dentry); } else { dput(hidden_sto_dentry); } goto out; } /* allocate dentry private data */ __dtopd(dentry) = (struct mini_fo_dentry_info *) kmalloc(sizeof(struct mini_fo_dentry_info), GFP_KERNEL); if (!dtopd(dentry)) { err = -ENOMEM; goto out_dput; } /* check for different states of the mini_fo file to be looked up. */ /* state 1, file has been modified */ if(hidden_dentry && hidden_sto_dentry && hidden_dentry->d_inode && hidden_sto_dentry->d_inode && !del_flag) { /* update parent directory's atime */ fist_copy_attr_atime(dir, hidden_sto_dir_dentry->d_inode); dtopd(dentry)->state = MODIFIED; dtohd(dentry) = hidden_dentry; dtohd2(dentry) = hidden_sto_dentry; err = mini_fo_tri_interpose(hidden_dentry, hidden_sto_dentry, dentry, dir->i_sb, 1); if (err) { printk(KERN_CRIT "mini_fo_lookup: error interposing (state1).\n"); goto out_free; } goto out; } /* state 2, file is unmodified */ if(hidden_dentry && hidden_dentry->d_inode && !del_flag) { fist_copy_attr_atime(dir, hidden_dir_dentry->d_inode); dtopd(dentry)->state = UNMODIFIED; dtohd(dentry) = hidden_dentry; dtohd2(dentry) = hidden_sto_dentry; /* could be negative */ err = mini_fo_tri_interpose(hidden_dentry, hidden_sto_dentry, dentry, dir->i_sb, 1); if (err) { printk(KERN_CRIT "mini_fo_lookup: error interposing (state2).\n"); goto out_free; } goto out; } /* state 3, file has been newly created */ if(hidden_sto_dentry && hidden_sto_dentry->d_inode && !del_flag) { fist_copy_attr_atime(dir, hidden_sto_dir_dentry->d_inode); dtopd(dentry)->state = CREATED; dtohd(dentry) = hidden_dentry; /* could be negative */ dtohd2(dentry) = hidden_sto_dentry; err = mini_fo_tri_interpose(hidden_dentry, hidden_sto_dentry, dentry, dir->i_sb, 1); if (err) { printk(KERN_CRIT "mini_fo_lookup: error interposing (state3).\n"); goto out_free; } goto out; } /* state 4, file has deleted and created again. */ if(hidden_dentry && hidden_sto_dentry && hidden_dentry->d_inode && hidden_sto_dentry->d_inode && del_flag) { fist_copy_attr_atime(dir, hidden_sto_dir_dentry->d_inode); dtopd(dentry)->state = DEL_REWRITTEN; dtohd(dentry) = NULL; dtohd2(dentry) = hidden_sto_dentry; err = mini_fo_tri_interpose(NULL, hidden_sto_dentry, dentry, dir->i_sb, 1); if (err) { printk(KERN_CRIT "mini_fo_lookup: error interposing (state4).\n"); goto out_free; } /* We will never need this dentry again, as the file has been * deleted from base */ dput(hidden_dentry); goto out; } /* state 5, file has been deleted in base */ if(hidden_dentry && hidden_sto_dentry && hidden_dentry->d_inode && !hidden_sto_dentry->d_inode && del_flag) { /* check which parents atime we need for updating */ if(hidden_sto_dir_dentry->d_inode) fist_copy_attr_atime(dir, hidden_sto_dir_dentry->d_inode); else fist_copy_attr_atime(dir, hidden_dir_dentry->d_inode); dtopd(dentry)->state = DELETED; dtohd(dentry) = NULL; dtohd2(dentry) = hidden_sto_dentry; /* add negative dentry to dcache to speed up lookups */ d_add(dentry, NULL); dput(hidden_dentry); goto out; } /* state 6, file does not exist */ if(((hidden_dentry && !hidden_dentry->d_inode) || (hidden_sto_dentry && !hidden_sto_dentry->d_inode)) && !del_flag) { /* check which parents atime we need for updating */ if(hidden_sto_dir_dentry && hidden_sto_dir_dentry->d_inode) fist_copy_attr_atime(dir, hidden_sto_dir_dentry->d_inode); else fist_copy_attr_atime(dir, hidden_dir_dentry->d_inode); dtopd(dentry)->state = NON_EXISTANT; dtohd(dentry) = hidden_dentry; dtohd2(dentry) = hidden_sto_dentry; d_add(dentry, NULL); goto out; } /* if we get to here, were in an invalid state. bad. */ printk(KERN_CRIT "mini_fo_lookup: ERROR, meta data corruption detected.\n"); /* end state checking */ out_free: d_drop(dentry); /* so that our bad dentry will get destroyed */ kfree(dtopd(dentry)); __dtopd(dentry) = NULL; /* be safe */ out_dput: if(hidden_dentry) dput(hidden_dentry); if(hidden_sto_dentry) dput(hidden_sto_dentry); /* drops usage count and marks for release */ out: /* initalize wol if file exists and is directory */ if(dentry->d_inode) { if(S_ISDIR(dentry->d_inode->i_mode)) { itopd(dentry->d_inode)->deleted_list_size = -1; itopd(dentry->d_inode)->renamed_list_size = -1; meta_build_lists(dentry); } } return ERR_PTR(err); } STATIC int mini_fo_link(dentry_t *old_dentry, inode_t *dir, dentry_t *new_dentry) { int err; dentry_t *hidden_old_dentry; dentry_t *hidden_new_dentry; dentry_t *hidden_dir_dentry; check_mini_fo_dentry(old_dentry); check_mini_fo_dentry(new_dentry); check_mini_fo_inode(dir); /* no links to directorys and existing targets target allowed */ if(S_ISDIR(old_dentry->d_inode->i_mode) || is_mini_fo_existant(new_dentry)) { err = -EPERM; goto out; } /* bring it directly from unmod to del_rew */ if(dtost(old_dentry) == UNMODIFIED) { err = nondir_unmod_to_mod(old_dentry, 1); if(err) { err = -EINVAL; goto out; } err = meta_add_d_entry(old_dentry->d_parent, old_dentry->d_name.name, old_dentry->d_name.len); if(err) { err = -EINVAL; goto out; } dput(dtohd(old_dentry)); dtohd(old_dentry) = NULL; dtost(old_dentry) = DEL_REWRITTEN; } err = get_neg_sto_dentry(new_dentry); if(err) { err = -EINVAL; goto out; } hidden_old_dentry = dtohd2(old_dentry); hidden_new_dentry = dtohd2(new_dentry); dget(hidden_old_dentry); dget(hidden_new_dentry); /* was: hidden_dir_dentry = lock_parent(hidden_new_dentry); */ hidden_dir_dentry = dget(hidden_new_dentry->d_parent); #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) mutex_lock(&hidden_dir_dentry->d_inode->i_mutex); #else down(&hidden_dir_dentry->d_inode->i_sem); #endif err = vfs_link(hidden_old_dentry, hidden_dir_dentry->d_inode, hidden_new_dentry); if (err || !hidden_new_dentry->d_inode) goto out_lock; dtost(new_dentry) = CREATED; err = mini_fo_tri_interpose(NULL, hidden_new_dentry, new_dentry, dir->i_sb, 0); if (err) goto out_lock; fist_copy_attr_timesizes(dir, hidden_new_dentry->d_inode); /* propagate number of hard-links */ old_dentry->d_inode->i_nlink = itohi2(old_dentry->d_inode)->i_nlink; out_lock: /* was: unlock_dir(hidden_dir_dentry); */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) mutex_unlock(&hidden_dir_dentry->d_inode->i_mutex); #else up(&hidden_dir_dentry->d_inode->i_sem); #endif dput(hidden_dir_dentry); dput(hidden_new_dentry); dput(hidden_old_dentry); if (!new_dentry->d_inode) d_drop(new_dentry); out: return err; } STATIC int mini_fo_unlink(inode_t *dir, dentry_t *dentry) { int err = 0; dget(dentry); if(dtopd(dentry)->state == MODIFIED) { err = nondir_mod_to_del(dentry); goto out; } else if(dtopd(dentry)->state == UNMODIFIED) { err = nondir_unmod_to_del(dentry); goto out; } else if(dtopd(dentry)->state == CREATED) { err = nondir_creat_to_del(dentry); goto out; } else if(dtopd(dentry)->state == DEL_REWRITTEN) { err = nondir_del_rew_to_del(dentry); goto out; } printk(KERN_CRIT "mini_fo_unlink: ERROR, invalid state detected.\n"); out: fist_copy_attr_times(dir, itohi2(dentry->d_parent->d_inode)); if(!err) { /* is this causing my pain? d_delete(dentry); */ d_drop(dentry); } dput(dentry); return err; } STATIC int mini_fo_symlink(inode_t *dir, dentry_t *dentry, const char *symname) { int err=0; dentry_t *hidden_sto_dentry; dentry_t *hidden_sto_dir_dentry; #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)) umode_t mode; #endif /* Fail if the symlink file exists */ if(!(dtost(dentry) == DELETED || dtost(dentry) == NON_EXISTANT)) { err = -EEXIST; goto out; } err = get_neg_sto_dentry(dentry); if(err) { err = -EINVAL; goto out; } hidden_sto_dentry = dtohd2(dentry); dget(hidden_sto_dentry); /* was: hidden_sto_dir_dentry = lock_parent(hidden_sto_dentry); */ hidden_sto_dir_dentry = dget(hidden_sto_dentry->d_parent); #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) mutex_lock(&hidden_sto_dir_dentry->d_inode->i_mutex); #else down(&hidden_sto_dir_dentry->d_inode->i_sem); #endif #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)) mode = S_IALLUGO; err = vfs_symlink(hidden_sto_dir_dentry->d_inode, hidden_sto_dentry, symname, mode); #else err = vfs_symlink(hidden_sto_dir_dentry->d_inode, hidden_sto_dentry, symname); #endif if (err || !hidden_sto_dentry->d_inode) goto out_lock; if(dtost(dentry) == DELETED) { dtost(dentry) = DEL_REWRITTEN; err = mini_fo_tri_interpose(NULL, hidden_sto_dentry, dentry, dir->i_sb, 0); if(err) goto out_lock; } else if(dtost(dentry) == NON_EXISTANT) { dtost(dentry) = CREATED; err = mini_fo_tri_interpose(dtohd(dentry), hidden_sto_dentry, dentry, dir->i_sb, 0); if(err) goto out_lock; } fist_copy_attr_timesizes(dir, hidden_sto_dir_dentry->d_inode); out_lock: /* was: unlock_dir(hidden_sto_dir_dentry); */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) mutex_unlock(&hidden_sto_dir_dentry->d_inode->i_mutex); #else up(&hidden_sto_dir_dentry->d_inode->i_sem); #endif dput(hidden_sto_dir_dentry); dput(hidden_sto_dentry); if (!dentry->d_inode) d_drop(dentry); out: return err; } STATIC int mini_fo_mkdir(inode_t *dir, dentry_t *dentry, int mode) { int err; err = create_sto_dir(dentry, mode); check_mini_fo_dentry(dentry); return err; } STATIC int mini_fo_rmdir(inode_t *dir, dentry_t *dentry) { int err = 0; dentry_t *hidden_sto_dentry; dentry_t *hidden_sto_dir_dentry; dentry_t *meta_dentry; inode_t *hidden_sto_dir = NULL; check_mini_fo_dentry(dentry); check_mini_fo_inode(dir); dget(dentry); if(dtopd(dentry)->state == MODIFIED) { /* XXX: disabled, because it does not bother to check files on * the original filesystem - just a hack, but better than simply * removing it without testing */ err = -EINVAL; goto out; hidden_sto_dir = itohi2(dir); hidden_sto_dentry = dtohd2(dentry); /* was:hidden_sto_dir_dentry = lock_parent(hidden_sto_dentry); */ hidden_sto_dir_dentry = dget(hidden_sto_dentry->d_parent); #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) mutex_lock(&hidden_sto_dir_dentry->d_inode->i_mutex); #else down(&hidden_sto_dir_dentry->d_inode->i_sem); #endif /* Delete an old WOL file contained in the storage dir */ mutex_lock(&hidden_sto_dentry->d_inode->i_mutex); meta_dentry = lookup_one_len(META_FILENAME, hidden_sto_dentry, strlen(META_FILENAME)); mutex_unlock(&hidden_sto_dentry->d_inode->i_mutex); if(meta_dentry->d_inode) { err = vfs_unlink(hidden_sto_dentry->d_inode, meta_dentry); dput(meta_dentry); if(!err) d_delete(meta_dentry); } err = vfs_rmdir(hidden_sto_dir, hidden_sto_dentry); dput(hidden_sto_dentry); if(!err) d_delete(hidden_sto_dentry); /* propagate number of hard-links */ dentry->d_inode->i_nlink = itohi2(dentry->d_inode)->i_nlink; dput(dtohd(dentry)); dtohd(dentry) = NULL; dtopd(dentry)->state = DELETED; /* carefull with R files */ if( __meta_is_r_entry(dir, dentry->d_name.name, dentry->d_name.len) == 1) { err = meta_remove_r_entry(dentry->d_parent, dentry->d_name.name, dentry->d_name.len); if(err) { printk(KERN_CRIT "mini_fo: rmdir: meta_remove_r_entry failed.\n"); goto out; } } else { /* ok, add deleted file to META */ meta_add_d_entry(dentry->d_parent, dentry->d_name.name, dentry->d_name.len); } /* was: unlock_dir(hidden_sto_dir_dentry); */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) mutex_unlock(&hidden_sto_dir_dentry->d_inode->i_mutex); #else up(&hidden_sto_dir_dentry->d_inode->i_sem); #endif dput(hidden_sto_dir_dentry); goto out; } else if(dtopd(dentry)->state == UNMODIFIED) { /* XXX: simply adding it to the delete list here is fscking dangerous! * as a temporary hack, i will disable rmdir on unmodified directories * for now. */ err = -EINVAL; goto out; err = get_neg_sto_dentry(dentry); if(err) { err = -EINVAL; goto out; } /* dput base dentry, this will relase the inode and free the * dentry, as we will never need it again. */ dput(dtohd(dentry)); dtohd(dentry) = NULL; dtopd(dentry)->state = DELETED; /* add deleted file to META-file */ meta_add_d_entry(dentry->d_parent, dentry->d_name.name, dentry->d_name.len); goto out; } else if(dtopd(dentry)->state == CREATED) { hidden_sto_dir = itohi2(dir); hidden_sto_dentry = dtohd2(dentry); /* was: hidden_sto_dir_dentry = lock_parent(hidden_sto_dentry);*/ hidden_sto_dir_dentry = dget(hidden_sto_dentry->d_parent); #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) mutex_lock(&hidden_sto_dir_dentry->d_inode->i_mutex); #else down(&hidden_sto_dir_dentry->d_inode->i_sem); #endif /* Delete an old WOL file contained in the storage dir */ mutex_lock(&hidden_sto_dentry->d_inode->i_mutex); meta_dentry = lookup_one_len(META_FILENAME, hidden_sto_dentry, strlen(META_FILENAME)); mutex_unlock(&hidden_sto_dentry->d_inode->i_mutex); if(meta_dentry->d_inode) { /* is this necessary? dget(meta_dentry); */ err = vfs_unlink(hidden_sto_dentry->d_inode, meta_dentry); dput(meta_dentry); if(!err) d_delete(meta_dentry); } err = vfs_rmdir(hidden_sto_dir, hidden_sto_dentry); dput(hidden_sto_dentry); if(!err) d_delete(hidden_sto_dentry); /* propagate number of hard-links */ dentry->d_inode->i_nlink = itohi2(dentry->d_inode)->i_nlink; dtopd(dentry)->state = NON_EXISTANT; /* was: unlock_dir(hidden_sto_dir_dentry); */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) mutex_unlock(&hidden_sto_dir_dentry->d_inode->i_mutex); #else up(&hidden_sto_dir_dentry->d_inode->i_sem); #endif dput(hidden_sto_dir_dentry); goto out; } else if(dtopd(dentry)->state == DEL_REWRITTEN) { hidden_sto_dir = itohi2(dir); hidden_sto_dentry = dtohd2(dentry); /* was: hidden_sto_dir_dentry = lock_parent(hidden_sto_dentry);*/ hidden_sto_dir_dentry = dget(hidden_sto_dentry->d_parent); #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) mutex_lock(&hidden_sto_dir_dentry->d_inode->i_mutex); #else down(&hidden_sto_dir_dentry->d_inode->i_sem); #endif /* Delete an old WOL file contained in the storage dir */ mutex_lock(&hidden_sto_dentry->d_inode->i_mutex); meta_dentry = lookup_one_len(META_FILENAME, hidden_sto_dentry, strlen(META_FILENAME)); mutex_unlock(&hidden_sto_dentry->d_inode->i_mutex); if(meta_dentry->d_inode) { /* is this necessary? dget(meta_dentry); */ err = vfs_unlink(hidden_sto_dentry->d_inode, meta_dentry); dput(meta_dentry); if(!err) d_delete(meta_dentry); } err = vfs_rmdir(hidden_sto_dir, hidden_sto_dentry); dput(hidden_sto_dentry); if(!err) d_delete(hidden_sto_dentry); /* propagate number of hard-links */ dentry->d_inode->i_nlink = itohi2(dentry->d_inode)->i_nlink; dtopd(dentry)->state = DELETED; /* was: unlock_dir(hidden_sto_dir_dentry); */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) mutex_unlock(&hidden_sto_dir_dentry->d_inode->i_mutex); #else up(&hidden_sto_dir_dentry->d_inode->i_sem); #endif dput(hidden_sto_dir_dentry); goto out; } printk(KERN_CRIT "mini_fo_rmdir: ERROR, invalid state detected.\n"); out: if(!err) { d_drop(dentry); } fist_copy_attr_times(dir, itohi2(dentry->d_parent->d_inode)); dput(dentry); return err; } STATIC int #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) mini_fo_mknod(inode_t *dir, dentry_t *dentry, int mode, dev_t dev) #else mini_fo_mknod(inode_t *dir, dentry_t *dentry, int mode, int dev) #endif { int err = 0; check_mini_fo_dentry(dentry); err = create_sto_nod(dentry, mode, dev); if(err) { printk(KERN_CRIT "mini_fo_mknod: creating sto nod failed.\n"); err = -EINVAL; } check_mini_fo_dentry(dentry); return err; } STATIC int mini_fo_rename(inode_t *old_dir, dentry_t *old_dentry, inode_t *new_dir, dentry_t *new_dentry) { /* dispatch */ if(S_ISDIR(old_dentry->d_inode->i_mode)) return rename_directory(old_dir, old_dentry, new_dir, new_dentry); return rename_nondir(old_dir, old_dentry, new_dir, new_dentry); } int rename_directory(inode_t *old_dir, dentry_t *old_dentry, inode_t *new_dir, dentry_t *new_dentry) { int err, bpath_len; char *bpath; dentry_t *hidden_old_dentry; dentry_t *hidden_new_dentry; dentry_t *hidden_old_dir_dentry; dentry_t *hidden_new_dir_dentry; err = 0; bpath = NULL; bpath_len = 0; /* this is a test, chuck out if it works */ if(!(dtopd(new_dentry)->state == DELETED || dtopd(new_dentry)->state == NON_EXISTANT)) { printk(KERN_CRIT "mini_fo: rename_directory: \ uh, ah, new_dentry not negative.\n"); /* return -1; */ } /* state = UNMODIFIED */ if(dtopd(old_dentry)->state == UNMODIFIED) { err = dir_unmod_to_mod(old_dentry); if (err) goto out; } /* state = MODIFIED */ if(dtopd(old_dentry)->state == MODIFIED) { bpath = meta_check_r_entry(old_dentry->d_parent, old_dentry->d_name.name, old_dentry->d_name.len); if(bpath) { err = meta_remove_r_entry(old_dentry->d_parent, old_dentry->d_name.name, old_dentry->d_name.len); if(err) { printk(KERN_CRIT "mini_fo: rename_directory:\ meta_remove_r_entry \ failed.\n"); goto out; } err = meta_add_r_entry(new_dentry->d_parent, bpath, strlen(bpath), new_dentry->d_name.name, new_dentry->d_name.len); kfree(bpath); } else {/* wol it */ err = meta_add_d_entry(old_dentry->d_parent, old_dentry->d_name.name, old_dentry->d_name.len); if (err) goto out; /* put it on rename list */ err = get_mini_fo_bpath(old_dentry, &bpath, &bpath_len); if (err) goto out; err = meta_add_r_entry(new_dentry->d_parent, bpath, bpath_len, new_dentry->d_name.name, new_dentry->d_name.len); if (err) goto out; } /* no state change, MODIFIED stays MODIFIED */ } /* state = CREATED */ if(dtopd(old_dentry)->state == CREATED || dtopd(old_dentry)->state == DEL_REWRITTEN) { if(dtohd(old_dentry)) dput(dtohd(old_dentry)); if(dtopd(new_dentry)->state == DELETED) { dtopd(old_dentry)->state = DEL_REWRITTEN; dtohd(old_dentry) = NULL; } else if(dtopd(new_dentry)->state == NON_EXISTANT) { dtopd(old_dentry)->state = CREATED; /* steal new dentry's neg. base dentry */ dtohd(old_dentry) = dtohd(new_dentry); dtohd(new_dentry) = NULL; } } if(dtopd(new_dentry)->state == UNMODIFIED || dtopd(new_dentry)->state == NON_EXISTANT) { err = get_neg_sto_dentry(new_dentry); if(err) goto out; } /* now move sto file */ hidden_old_dentry = dtohd2(old_dentry); hidden_new_dentry = dtohd2(new_dentry); dget(hidden_old_dentry); dget(hidden_new_dentry); hidden_old_dir_dentry = dget(hidden_old_dentry->d_parent); hidden_new_dir_dentry = dget(hidden_new_dentry->d_parent); double_lock(hidden_old_dir_dentry, hidden_new_dir_dentry); err = vfs_rename(hidden_old_dir_dentry->d_inode, hidden_old_dentry, hidden_new_dir_dentry->d_inode, hidden_new_dentry); if(err) goto out_lock; fist_copy_attr_all(new_dir, hidden_new_dir_dentry->d_inode); if (new_dir != old_dir) fist_copy_attr_all(old_dir, hidden_old_dir_dentry->d_inode); out_lock: /* double_unlock will dput the new/old parent dentries * whose refcnts were incremented via get_parent above. */ double_unlock(hidden_old_dir_dentry, hidden_new_dir_dentry); dput(hidden_new_dentry); dput(hidden_old_dentry); out: return err; } int rename_nondir(inode_t *old_dir, dentry_t *old_dentry, inode_t *new_dir, dentry_t *new_dentry) { int err=0; check_mini_fo_dentry(old_dentry); check_mini_fo_dentry(new_dentry); check_mini_fo_inode(old_dir); check_mini_fo_inode(new_dir); /* state: UNMODIFIED */ if(dtost(old_dentry) == UNMODIFIED) { err = nondir_unmod_to_mod(old_dentry, 1); if(err) { err = -EINVAL; goto out; } } /* the easy states */ if(exists_in_storage(old_dentry)) { dentry_t *hidden_old_dentry; dentry_t *hidden_new_dentry; dentry_t *hidden_old_dir_dentry; dentry_t *hidden_new_dir_dentry; /* if old file is MODIFIED, add it to the deleted_list */ if(dtopd(old_dentry)->state == MODIFIED) { meta_add_d_entry(old_dentry->d_parent, old_dentry->d_name.name, old_dentry->d_name.len); dput(dtohd(old_dentry)); } /* if old file is CREATED, we only release the base dentry */ if(dtopd(old_dentry)->state == CREATED) { if(dtohd(old_dentry)) dput(dtohd(old_dentry)); } /* now setup the new states (depends on new_dentry state) */ /* new dentry state = MODIFIED */ if(dtopd(new_dentry)->state == MODIFIED) { meta_add_d_entry(new_dentry->d_parent, new_dentry->d_name.name, new_dentry->d_name.len); /* new dentry will be d_put'ed later by the vfs * so don't do it here * dput(dtohd(new_dentry)); */ dtohd(old_dentry) = NULL; dtopd(old_dentry)->state = DEL_REWRITTEN; } /* new dentry state = UNMODIFIED */ else if(dtopd(new_dentry)->state == UNMODIFIED) { if(get_neg_sto_dentry(new_dentry)) return -EINVAL; meta_add_d_entry(new_dentry->d_parent, new_dentry->d_name.name, new_dentry->d_name.len); /* is this right??? */ /*dput(dtohd(new_dentry));*/ dtohd(old_dentry) = NULL; dtopd(old_dentry)->state = DEL_REWRITTEN; } /* new dentry state = CREATED */ else if(dtopd(new_dentry)->state == CREATED) { /* we keep the neg. base dentry (if exists) */ dtohd(old_dentry) = dtohd(new_dentry); /* ...and set it to Null, or we'll get * dcache.c:345 if it gets dput twice... */ dtohd(new_dentry) = NULL; dtopd(old_dentry)->state = CREATED; } /* new dentry state = NON_EXISTANT */ else if(dtopd(new_dentry)->state == NON_EXISTANT) { if(get_neg_sto_dentry(new_dentry)) return -EINVAL; /* we keep the neg. base dentry (if exists) */ dtohd(old_dentry) = dtohd(new_dentry); /* ...and set it to Null, or we'll get * Dr. dcache.c:345 if it gets dput twice... */ dtohd(new_dentry) = NULL; dtopd(old_dentry)->state = CREATED; } /* new dentry state = DEL_REWRITTEN or DELETED */ else if(dtopd(new_dentry)->state == DEL_REWRITTEN || dtopd(new_dentry)->state == DELETED) { dtohd(old_dentry) = NULL; dtopd(old_dentry)->state = DEL_REWRITTEN; } else { /* not possible, uhh, ahh */ printk(KERN_CRIT "mini_fo: rename_reg_file: invalid state detected [1].\n"); return -1; } /* now we definitely have a sto file */ hidden_old_dentry = dtohd2(old_dentry); hidden_new_dentry = dtohd2(new_dentry); dget(hidden_old_dentry); dget(hidden_new_dentry); hidden_old_dir_dentry = dget(hidden_old_dentry->d_parent); hidden_new_dir_dentry = dget(hidden_new_dentry->d_parent); double_lock(hidden_old_dir_dentry, hidden_new_dir_dentry); err = vfs_rename(hidden_old_dir_dentry->d_inode, hidden_old_dentry, hidden_new_dir_dentry->d_inode, hidden_new_dentry); if(err) goto out_lock; fist_copy_attr_all(new_dir, hidden_new_dir_dentry->d_inode); if (new_dir != old_dir) fist_copy_attr_all(old_dir, hidden_old_dir_dentry->d_inode); out_lock: /* double_unlock will dput the new/old parent dentries * whose refcnts were incremented via get_parent above. */ double_unlock(hidden_old_dir_dentry, hidden_new_dir_dentry); dput(hidden_new_dentry); dput(hidden_old_dentry); out: return err; } else { /* invalid state */ printk(KERN_CRIT "mini_fo: rename_reg_file: ERROR: invalid state detected [2].\n"); return -1; } } STATIC int mini_fo_readlink(dentry_t *dentry, char *buf, int bufsiz) { int err=0; dentry_t *hidden_dentry = NULL; if(dtohd2(dentry) && dtohd2(dentry)->d_inode) { hidden_dentry = dtohd2(dentry); } else if(dtohd(dentry) && dtohd(dentry)->d_inode) { hidden_dentry = dtohd(dentry); } else { goto out; } if (!hidden_dentry->d_inode->i_op || !hidden_dentry->d_inode->i_op->readlink) { err = -EINVAL; goto out; } err = hidden_dentry->d_inode->i_op->readlink(hidden_dentry, buf, bufsiz); if (err > 0) fist_copy_attr_atime(dentry->d_inode, hidden_dentry->d_inode); out: return err; } #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13) static int mini_fo_follow_link(dentry_t *dentry, struct nameidata *nd) #else static void* mini_fo_follow_link(dentry_t *dentry, struct nameidata *nd) #endif { char *buf; int len = PAGE_SIZE, err; mm_segment_t old_fs; /* in 2.6 this is freed by mini_fo_put_link called by __do_follow_link */ buf = kmalloc(len, GFP_KERNEL); if (!buf) { err = -ENOMEM; goto out; } /* read the symlink, and then we will follow it */ old_fs = get_fs(); set_fs(KERNEL_DS); err = dentry->d_inode->i_op->readlink(dentry, buf, len); set_fs(old_fs); if (err < 0) { kfree(buf); buf = NULL; goto out; } buf[err] = 0; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) nd_set_link(nd, buf); err = 0; #else err = vfs_follow_link(nd, buf); #endif out: #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) kfree(buf); #endif #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13) return err; #else return ERR_PTR(err); #endif } STATIC #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13) void mini_fo_put_link(struct dentry *dentry, struct nameidata *nd) #else void mini_fo_put_link(struct dentry *dentry, struct nameidata *nd, void *cookie) #endif { char *link; link = nd_get_link(nd); kfree(link); } #endif STATIC int #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)) mini_fo_permission(inode_t *inode, int mask, struct nameidata *nd) #else mini_fo_permission(inode_t *inode, int mask) #endif { inode_t *hidden_inode; int mode; int err; if(itohi2(inode)) { hidden_inode = itohi2(inode); } else { hidden_inode = itohi(inode); } mode = inode->i_mode; /* not really needed, as permission handles everything: * err = vfs_permission(inode, mask); * if (err) * goto out; */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27) err = inode_permission(hidden_inode, mask); #elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) err = permission(hidden_inode, mask, nd); #else err = permission(hidden_inode, mask); #endif /* out: */ return err; } #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) STATIC int mini_fo_inode_revalidate(dentry_t *dentry) { int err = 0; dentry_t *hidden_dentry; inode_t *hidden_inode; ASSERT(dentry->d_inode); ASSERT(itopd(dentry->d_inode)); if(itohi2(dentry->d_inode)) { hidden_dentry = dtohd2(dentry); hidden_inode = hidden_dentry->d_inode; } else if(itohi(dentry->d_inode)) { hidden_dentry = dtohd(dentry); hidden_inode = hidden_dentry->d_inode; } else { printk(KERN_CRIT "mini_fo_inode_revalidate: ERROR, invalid state detected.\n"); err = -ENOENT; goto out; } if (hidden_inode && hidden_inode->i_op && hidden_inode->i_op->revalidate){ err = hidden_inode->i_op->revalidate(hidden_dentry); if (err) goto out; } fist_copy_attr_all(dentry->d_inode, hidden_inode); out: return err; } #endif STATIC int mini_fo_setattr(dentry_t *dentry, struct iattr *ia) { int err = 0; check_mini_fo_dentry(dentry); if(!is_mini_fo_existant(dentry)) { printk(KERN_CRIT "mini_fo_setattr: ERROR, invalid state detected [1].\n"); goto out; } if(dtost(dentry) == UNMODIFIED) { if(!IS_COPY_FLAG(ia->ia_valid)) goto out; /* we ignore these changes to base */ if(S_ISDIR(dentry->d_inode->i_mode)) { err = dir_unmod_to_mod(dentry); } else { /* we copy contents if file is not beeing truncated */ if(S_ISREG(dentry->d_inode->i_mode) && !(ia->ia_size == 0 && (ia->ia_valid & ATTR_SIZE))) { err = nondir_unmod_to_mod(dentry, 1); } else err = nondir_unmod_to_mod(dentry, 0); } if(err) { err = -EINVAL; printk(KERN_CRIT "mini_fo_setattr: ERROR changing states.\n"); goto out; } } if(!exists_in_storage(dentry)) { printk(KERN_CRIT "mini_fo_setattr: ERROR, invalid state detected [2].\n"); err = -EINVAL; goto out; } ASSERT(dentry->d_inode); ASSERT(dtohd2(dentry)); ASSERT(itopd(dentry->d_inode)); ASSERT(itohi2(dentry->d_inode)); err = notify_change(dtohd2(dentry), ia); fist_copy_attr_all(dentry->d_inode, itohi2(dentry->d_inode)); out: return err; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) STATIC int mini_fo_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat) { int err = 0; dentry_t *hidden_dentry; ASSERT(dentry->d_inode); ASSERT(itopd(dentry->d_inode)); if(itohi2(dentry->d_inode)) { hidden_dentry = dtohd2(dentry); } else if(itohi(dentry->d_inode)) { hidden_dentry = dtohd(dentry); } else { printk(KERN_CRIT "mini_fo_getattr: ERROR, invalid state detected.\n"); err = -ENOENT; goto out; } fist_copy_attr_all(dentry->d_inode, hidden_dentry->d_inode); ASSERT(hidden_dentry); ASSERT(hidden_dentry->d_inode); ASSERT(hidden_dentry->d_inode->i_op); generic_fillattr(dentry->d_inode, stat); if (!stat->blksize) { struct super_block *s = hidden_dentry->d_inode->i_sb; unsigned blocks; blocks = (stat->size+s->s_blocksize-1) >> s->s_blocksize_bits; stat->blocks = (s->s_blocksize / 512) * blocks; stat->blksize = s->s_blocksize; } out: return err; } #endif #if defined(XATTR) && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,20)) #if 0 /* no xattr_alloc() and xattr_free() */ /* This is lifted from fs/xattr.c */ static void * xattr_alloc(size_t size, size_t limit) { void *ptr; if (size > limit) return ERR_PTR(-E2BIG); if (!size) /* size request, no buffer is needed */ return NULL; else if (size <= PAGE_SIZE) ptr = kmalloc((unsigned long) size, GFP_KERNEL); else ptr = vmalloc((unsigned long) size); if (!ptr) return ERR_PTR(-ENOMEM); return ptr; } static void xattr_free(void *ptr, size_t size) { if (!size) /* size request, no buffer was needed */ return; else if (size <= PAGE_SIZE) kfree(ptr); else vfree(ptr); } #endif /* no xattr_alloc() and xattr_free() */ /* BKL held by caller. * dentry->d_inode->i_sem down */ STATIC int mini_fo_getxattr(struct dentry *dentry, const char *name, void *value, size_t size) { struct dentry *hidden_dentry = NULL; int err = -EOPNOTSUPP; /* Define these anyway so we don't need as much ifdef'ed code. */ char *encoded_name = NULL; char *encoded_value = NULL; check_mini_fo_dentry(dentry); if(exists_in_storage(dentry)) hidden_dentry = dtohd2(dentry); else hidden_dentry = dtohd(dentry); ASSERT(hidden_dentry); ASSERT(hidden_dentry->d_inode); ASSERT(hidden_dentry->d_inode->i_op); if (hidden_dentry->d_inode->i_op->getxattr) { encoded_name = (char *)name; encoded_value = (char *)value; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) mutex_lock(&hidden_dentry->d_inode->i_mutex); #else down(&hidden_dentry->d_inode->i_sem); #endif /* lock_kernel() already done by caller. */ err = hidden_dentry->d_inode->i_op->getxattr(hidden_dentry, encoded_name, encoded_value, size); /* unlock_kernel() will be done by caller. */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) mutex_lock(&hidden_dentry->d_inode->i_mutex); #else up(&hidden_dentry->d_inode->i_sem); #endif } return err; } /* BKL held by caller. * dentry->d_inode->i_sem down */ STATIC int #if ((LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,21) \ && LINUX_VERSION_CODE <= KERNEL_VERSION(2,4,23)) \ || LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) mini_fo_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags) #else mini_fo_setxattr(struct dentry *dentry, const char *name, void *value, size_t size, int flags) #endif { struct dentry *hidden_dentry = NULL; int err = -EOPNOTSUPP; /* Define these anyway, so we don't have as much ifdef'ed code. */ char *encoded_value = NULL; char *encoded_name = NULL; check_mini_fo_dentry(dentry); if(exists_in_storage(dentry)) hidden_dentry = dtohd2(dentry); else hidden_dentry = dtohd(dentry); ASSERT(hidden_dentry); ASSERT(hidden_dentry->d_inode); ASSERT(hidden_dentry->d_inode->i_op); if (hidden_dentry->d_inode->i_op->setxattr) { encoded_name = (char *)name; encoded_value = (char *)value; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) mutex_lock(&hidden_dentry->d_inode->i_mutex); #else down(&hidden_dentry->d_inode->i_sem); #endif /* lock_kernel() already done by caller. */ err = hidden_dentry->d_inode->i_op->setxattr(hidden_dentry, encoded_name, encoded_value, size, flags); /* unlock_kernel() will be done by caller. */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) mutex_unlock(&hidden_dentry->d_inode->i_mutex); #else up(&hidden_dentry->d_inode->i_sem); #endif } return err; } /* BKL held by caller. * dentry->d_inode->i_sem down */ STATIC int mini_fo_removexattr(struct dentry *dentry, const char *name) { struct dentry *hidden_dentry = NULL; int err = -EOPNOTSUPP; char *encoded_name; check_mini_fo_dentry(dentry); if(exists_in_storage(dentry)) hidden_dentry = dtohd2(dentry); else hidden_dentry = dtohd(dentry); ASSERT(hidden_dentry); ASSERT(hidden_dentry->d_inode); ASSERT(hidden_dentry->d_inode->i_op); if (hidden_dentry->d_inode->i_op->removexattr) { encoded_name = (char *)name; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) mutex_lock(&hidden_dentry->d_inode->i_mutex); #else down(&hidden_dentry->d_inode->i_sem); #endif /* lock_kernel() already done by caller. */ err = hidden_dentry->d_inode->i_op->removexattr(hidden_dentry, encoded_name); /* unlock_kernel() will be done by caller. */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) mutex_unlock(&hidden_dentry->d_inode->i_mutex); #else up(&hidden_dentry->d_inode->i_sem); #endif } return err; } /* BKL held by caller. * dentry->d_inode->i_sem down */ STATIC int mini_fo_listxattr(struct dentry *dentry, char *list, size_t size) { struct dentry *hidden_dentry = NULL; int err = -EOPNOTSUPP; char *encoded_list = NULL; check_mini_fo_dentry(dentry); if(exists_in_storage(dentry)) hidden_dentry = dtohd2(dentry); else hidden_dentry = dtohd(dentry); ASSERT(hidden_dentry); ASSERT(hidden_dentry->d_inode); ASSERT(hidden_dentry->d_inode->i_op); if (hidden_dentry->d_inode->i_op->listxattr) { encoded_list = list; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) mutex_lock(&hidden_dentry->d_inode->i_mutex); #else down(&hidden_dentry->d_inode->i_sem); #endif /* lock_kernel() already done by caller. */ err = hidden_dentry->d_inode->i_op->listxattr(hidden_dentry, encoded_list, size); /* unlock_kernel() will be done by caller. */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) mutex_unlock(&hidden_dentry->d_inode->i_mutex); #else up(&hidden_dentry->d_inode->i_sem); #endif } return err; } # endif /* defined(XATTR) && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,20)) */ struct inode_operations mini_fo_symlink_iops = { readlink: mini_fo_readlink, follow_link: mini_fo_follow_link, /* mk: permission: mini_fo_permission, */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) revalidate: mini_fo_inode_revalidate, #endif setattr: mini_fo_setattr, #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) getattr: mini_fo_getattr, put_link: mini_fo_put_link, #endif #if defined(XATTR) && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,20)) setxattr: mini_fo_setxattr, getxattr: mini_fo_getxattr, listxattr: mini_fo_listxattr, removexattr: mini_fo_removexattr # endif /* defined(XATTR) && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,20)) */ }; struct inode_operations mini_fo_dir_iops = { create: mini_fo_create, lookup: mini_fo_lookup, link: mini_fo_link, unlink: mini_fo_unlink, symlink: mini_fo_symlink, mkdir: mini_fo_mkdir, rmdir: mini_fo_rmdir, mknod: mini_fo_mknod, rename: mini_fo_rename, /* no readlink/follow_link for non-symlinks */ // off because we have setattr // truncate: mini_fo_truncate, /* mk:permission: mini_fo_permission, */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) revalidate: mini_fo_inode_revalidate, #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) getattr: mini_fo_getattr, #endif setattr: mini_fo_setattr, #if defined(XATTR) && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,20)) setxattr: mini_fo_setxattr, getxattr: mini_fo_getxattr, listxattr: mini_fo_listxattr, removexattr: mini_fo_removexattr # endif /* XATTR && LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,20) */ }; struct inode_operations mini_fo_main_iops = { /* permission: mini_fo_permission, */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) revalidate: mini_fo_inode_revalidate, #endif setattr: mini_fo_setattr, #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) getattr: mini_fo_getattr, #endif #if defined(XATTR) && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,20)) setxattr: mini_fo_setxattr, getxattr: mini_fo_getxattr, listxattr: mini_fo_listxattr, removexattr: mini_fo_removexattr # endif /* XATTR && LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,20) */ };