/* * Called by iput() when the inode reference count reached zero * and the inode is not hashed anywhere. Used to clear anything * that needs to be, before the inode is completely destroyed and put * on the inode free list. */ STATIC void mini_fo_clear_inode(inode_t *inode) { /* * Decrement a reference to a hidden_inode, which was incremented * by our read_inode when it was created initially. */ /* release the wol_list */ if(S_ISDIR(inode->i_mode)) { __meta_put_lists(inode); } /* mk: fan out fun */ if(itohi(inode)) iput(itohi(inode)); if(itohi2(inode)) iput(itohi2(inode)); // XXX: why this assertion fails? // because it doesn't like us // ASSERT((inode->i_state & I_DIRTY) == 0); kfree(itopd(inode)); __itopd(inode) = NULL; }
static void unionfs_read_inode(struct inode *inode) { static struct address_space_operations unionfs_empty_aops; print_entry_location(); if (!itopd(inode)) { FISTBUG ("No kernel memory when allocating inode private data!\n"); } PASSERT(inode->i_sb); memset(itopd(inode), 0, sizeof(struct unionfs_inode_info)); itopd(inode)->b_start = -1; itopd(inode)->b_end = -1; atomic_set(&itopd(inode)->uii_generation, atomic_read(&stopd(inode->i_sb)->usi_generation)); itopd(inode)->uii_rdlock = SPIN_LOCK_UNLOCKED; itopd(inode)->uii_rdcount = 1; itopd(inode)->uii_hashsize = -1; INIT_LIST_HEAD(&itopd(inode)->uii_readdircache); if (sbmax(inode->i_sb) > UNIONFS_INLINE_OBJECTS) { int size = (sbmax(inode->i_sb) - UNIONFS_INLINE_OBJECTS) * sizeof(struct inode *); itohi_ptr(inode) = KMALLOC(size, GFP_UNIONFS); if (!itohi_ptr(inode)) { FISTBUG ("No kernel memory when allocating lower-pointer array!\n"); } memset(itohi_ptr(inode), 0, size); } memset(itohi_inline(inode), 0, UNIONFS_INLINE_OBJECTS * sizeof(struct inode *)); inode->i_version++; inode->i_op = &unionfs_main_iops; inode->i_fop = &unionfs_main_fops; /* I don't think ->a_ops is ever allowed to be NULL */ inode->i_mapping->a_ops = &unionfs_empty_aops; fist_dprint(7, "setting inode 0x%p a_ops to empty (0x%p)\n", inode, inode->i_mapping->a_ops); print_exit_location(); }
int unionfs_ioctl_rdwrbranch(struct inode *inode, unsigned int cmd, unsigned long arg) { int err; struct unionfs_rdwrbranch_args *rdwrargs = NULL; int gen; print_entry_location(); unionfs_write_lock(inode->i_sb); lock_dentry(inode->i_sb->s_root); if ((err = newputmap(inode->i_sb))) goto out; err = -ENOMEM; rdwrargs = KMALLOC(sizeof(struct unionfs_rdwrbranch_args), GFP_KERNEL); if (!rdwrargs) goto out; err = -EFAULT; if (copy_from_user (rdwrargs, (const void __user *)arg, sizeof(struct unionfs_rdwrbranch_args))) goto out; err = -EINVAL; if (rdwrargs->rwb_branch < 0 || (rdwrargs->rwb_branch > (sbend(inode->i_sb) + 1))) goto out; if (rdwrargs->rwb_perms & ~(MAY_READ | MAY_WRITE | MAY_NFSRO)) goto out; if (!(rdwrargs->rwb_perms & MAY_READ)) goto out; set_branchperms(inode->i_sb, rdwrargs->rwb_branch, rdwrargs->rwb_perms); atomic_inc(&stopd(inode->i_sb)->usi_generation); gen = atomic_read(&stopd(inode->i_sb)->usi_generation); atomic_set(&dtopd(inode->i_sb->s_root)->udi_generation, gen); atomic_set(&itopd(inode->i_sb->s_root->d_inode)->uii_generation, gen); err = 0; out: unlock_dentry(inode->i_sb->s_root); unionfs_write_unlock(inode->i_sb); KFREE(rdwrargs); print_exit_status(err); return err; }
STATIC void mini_fo_read_inode(inode_t *inode) { static struct address_space_operations mini_fo_empty_aops; __itopd(inode) = kmalloc(sizeof(struct mini_fo_inode_info), GFP_KERNEL); if (!itopd(inode)) { printk("<0>%s:%s:%d: No kernel memory!\n", __FILE__, __FUNCTION__, __LINE__); ASSERT(NULL); } itohi(inode) = NULL; itohi2(inode) = NULL; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) inode->i_version++; #else inode->i_version = ++event; /* increment inode version */ #endif inode->i_op = &mini_fo_main_iops; inode->i_fop = &mini_fo_main_fops; #if 0 /* * XXX: To export a file system via NFS, it has to have the * FS_REQUIRES_DEV flag, so turn it on. But should we inherit it from * the lower file system, or can we allow our file system to be exported * even if the lower one cannot be natively exported. */ inode->i_sb->s_type->fs_flags |= FS_REQUIRES_DEV; /* * OK, the above was a hack, which is now turned off because it may * cause a panic/oops on some systems. The correct way to export a * "nodev" filesystem is via using nfs-utils > 1.0 and the "fsid=" export * parameter, which requires 2.4.20 or later. */ #endif /* I don't think ->a_ops is ever allowed to be NULL */ inode->i_mapping->a_ops = &mini_fo_empty_aops; }
int unionfs_ioctl_incgen(struct file *file, unsigned int cmd, unsigned long arg) { int err = 0; struct super_block *sb; print_entry_location(); sb = file->f_dentry->d_sb; unionfs_write_lock(sb); if ((err = newputmap(sb))) goto out; atomic_inc(&stopd(sb)->usi_generation); err = atomic_read(&stopd(sb)->usi_generation); atomic_set(&dtopd(sb->s_root)->udi_generation, err); atomic_set(&itopd(sb->s_root->d_inode)->uii_generation, err); out: unionfs_write_unlock(sb); print_exit_status(err); return err; }
/* This must be called with the super block already locked. */ int unionfs_ioctl_delbranch(struct super_block *sb, unsigned long arg) { struct dentry *hidden_dentry; struct inode *hidden_inode; struct super_block *hidden_sb; struct vfsmount *hidden_mnt; struct dentry *root_dentry; struct inode *root_inode; int err = 0; int pmindex, i, gen; print_entry("branch = %lu ", arg); lock_dentry(sb->s_root); err = -EBUSY; if (sbmax(sb) == 1) goto out; err = -EINVAL; if (arg < 0 || arg > stopd(sb)->b_end) goto out; err = -EBUSY; if (branch_count(sb, arg)) goto out; if ((err = newputmap(sb))) goto out; pmindex = stopd(sb)->usi_lastputmap; pmindex -= stopd(sb)->usi_firstputmap; atomic_inc(&stopd(sb)->usi_generation); gen = atomic_read(&stopd(sb)->usi_generation); root_dentry = sb->s_root; root_inode = sb->s_root->d_inode; hidden_dentry = dtohd_index(root_dentry, arg); hidden_mnt = stohiddenmnt_index(sb, arg); hidden_inode = itohi_index(root_inode, arg); hidden_sb = stohs_index(sb, arg); DPUT(hidden_dentry); iput(hidden_inode); mntput(hidden_mnt); for (i = arg; i <= (sbend(sb) - 1); i++) { set_branch_count(sb, i, branch_count(sb, i + 1)); set_stohiddenmnt_index(sb, i, stohiddenmnt_index(sb, i + 1)); set_stohs_index(sb, i, stohs_index(sb, i + 1)); set_branchperms(sb, i, branchperms(sb, i + 1)); set_dtohd_index(root_dentry, i, dtohd_index(root_dentry, i + 1)); set_itohi_index(root_inode, i, itohi_index(root_inode, i + 1)); stopd(sb)->usi_putmaps[pmindex]->map[i + 1] = i; } set_dtohd_index(root_dentry, sbend(sb), NULL); set_itohi_index(root_inode, sbend(sb), NULL); set_stohiddenmnt_index(sb, sbend(sb), NULL); set_stohs_index(sb, sbend(sb), NULL); stopd(sb)->b_end--; set_dbend(root_dentry, dbend(root_dentry) - 1); dtopd(root_dentry)->udi_bcount--; itopd(root_inode)->b_end--; atomic_set(&dtopd(root_dentry)->udi_generation, gen); atomic_set(&itopd(root_inode)->uii_generation, gen); fixputmaps(sb); /* This doesn't open a file, so we might have to free the map here. */ if (atomic_read(&stopd(sb)->usi_putmaps[pmindex]->count) == 0) { KFREE(stopd(sb)->usi_putmaps[pmindex]); stopd(sb)->usi_putmaps[pmindex] = NULL; } out: unlock_dentry(sb->s_root); print_exit_status(err); return err; }
int unionfs_ioctl_addbranch(struct inode *inode, unsigned int cmd, unsigned long arg) { int err; struct unionfs_addbranch_args *addargs = NULL; struct nameidata nd; char *path = NULL; int gen; int i; int count; int pobjects; struct vfsmount **new_hidden_mnt = NULL; struct inode **new_uii_inode = NULL; struct dentry **new_udi_dentry = NULL; struct super_block **new_usi_sb = NULL; int *new_branchperms = NULL; atomic_t *new_counts = NULL; print_entry_location(); err = -ENOMEM; addargs = KMALLOC(sizeof(struct unionfs_addbranch_args), GFP_UNIONFS); if (!addargs) goto out; err = -EFAULT; if (copy_from_user (addargs, (void *)arg, sizeof(struct unionfs_addbranch_args))) goto out; err = -EINVAL; if (addargs->ab_perms & ~(MAY_READ | MAY_WRITE)) goto out; if (!(addargs->ab_perms & MAY_READ)) goto out; err = -E2BIG; if (sbend(inode->i_sb) > FD_SETSIZE) goto out; err = -ENOMEM; if (!(path = getname(addargs->ab_path))) goto out; err = path_lookup(path, LOOKUP_FOLLOW, &nd); RECORD_PATH_LOOKUP(&nd); if (err) goto out; if ((err = check_branch(&nd))) { path_release(&nd); RECORD_PATH_RELEASE(&nd); goto out; } unionfs_write_lock(inode->i_sb); lock_dentry(inode->i_sb->s_root); err = -EINVAL; if (addargs->ab_branch < 0 || (addargs->ab_branch > (sbend(inode->i_sb) + 1))) goto out; if ((err = newputmap(inode->i_sb))) goto out; stopd(inode->i_sb)->b_end++; dtopd(inode->i_sb->s_root)->udi_bcount++; set_dbend(inode->i_sb->s_root, dbend(inode->i_sb->s_root) + 1); itopd(inode->i_sb->s_root->d_inode)->b_end++; atomic_inc(&stopd(inode->i_sb)->usi_generation); gen = atomic_read(&stopd(inode->i_sb)->usi_generation); pobjects = (sbend(inode->i_sb) + 1) - UNIONFS_INLINE_OBJECTS; if (pobjects > 0) { /* Reallocate the dynamic structures. */ new_hidden_mnt = KMALLOC(sizeof(struct vfsmount *) * pobjects, GFP_UNIONFS); new_udi_dentry = KMALLOC(sizeof(struct dentry *) * pobjects, GFP_UNIONFS); new_uii_inode = KMALLOC(sizeof(struct inode *) * pobjects, GFP_UNIONFS); new_usi_sb = KMALLOC(sizeof(struct super_block *) * pobjects, GFP_UNIONFS); new_counts = KMALLOC(sizeof(atomic_t) * pobjects, GFP_UNIONFS); new_branchperms = KMALLOC(sizeof(int) * pobjects, GFP_UNIONFS); if (!new_hidden_mnt || !new_udi_dentry || !new_uii_inode || !new_counts || !new_usi_sb || !new_branchperms) { err = -ENOMEM; goto out; } memset(new_hidden_mnt, 0, sizeof(struct vfsmount *) * pobjects); memset(new_udi_dentry, 0, sizeof(struct dentry *) * pobjects); memset(new_uii_inode, 0, sizeof(struct inode *) * pobjects); memset(new_usi_sb, 0, sizeof(struct super_block *) * pobjects); memset(new_branchperms, 0, sizeof(int) * pobjects); } /* Copy the in-place values to our new structure. */ for (i = UNIONFS_INLINE_OBJECTS; i < addargs->ab_branch; i++) { int j = i - UNIONFS_INLINE_OBJECTS; count = branch_count(inode->i_sb, i); atomic_set(&(new_counts[j]), count); new_branchperms[j] = branchperms(inode->i_sb, i); new_hidden_mnt[j] = stohiddenmnt_index(inode->i_sb, i); new_usi_sb[j] = stohs_index(inode->i_sb, i); new_udi_dentry[j] = dtohd_index(inode->i_sb->s_root, i); new_uii_inode[j] = itohi_index(inode->i_sb->s_root->d_inode, i); } /* Shift the ends to the right (only handle reallocated bits). */ for (i = sbend(inode->i_sb) - 1; i >= (int)addargs->ab_branch; i--) { int j = i + 1; int perms; struct vfsmount *hm; struct super_block *hs; struct dentry *hd; struct inode *hi; int pmindex; count = branch_count(inode->i_sb, i); perms = branchperms(inode->i_sb, i); hm = stohiddenmnt_index(inode->i_sb, i); hs = stohs_index(inode->i_sb, i); hd = dtohd_index(inode->i_sb->s_root, i); hi = itohi_index(inode->i_sb->s_root->d_inode, i); /* Update the newest putmap, so it is correct for later. */ pmindex = stopd(inode->i_sb)->usi_lastputmap; pmindex -= stopd(inode->i_sb)->usi_firstputmap; stopd(inode->i_sb)->usi_putmaps[pmindex]->map[i] = j; if (j >= UNIONFS_INLINE_OBJECTS) { j -= UNIONFS_INLINE_OBJECTS; atomic_set(&(new_counts[j]), count); new_branchperms[j] = perms; new_hidden_mnt[j] = hm; new_usi_sb[j] = hs; new_udi_dentry[j] = hd; new_uii_inode[j] = hi; } else { set_branch_count(inode->i_sb, j, count); set_branchperms(inode->i_sb, j, perms); set_stohiddenmnt_index(inode->i_sb, j, hm); set_stohs_index(inode->i_sb, j, hs); set_dtohd_index(inode->i_sb->s_root, j, hd); set_itohi_index(inode->i_sb->s_root->d_inode, j, hi); } } /* Now we can free the old ones. */ KFREE(dtopd(inode->i_sb->s_root)->udi_dentry_p); KFREE(itopd(inode->i_sb->s_root->d_inode)->uii_inode_p); KFREE(stopd(inode->i_sb)->usi_hidden_mnt_p); KFREE(stopd(inode->i_sb)->usi_sb_p); KFREE(stopd(inode->i_sb)->usi_sbcount_p); KFREE(stopd(inode->i_sb)->usi_branchperms_p); /* Update the real pointers. */ dtohd_ptr(inode->i_sb->s_root) = new_udi_dentry; itohi_ptr(inode->i_sb->s_root->d_inode) = new_uii_inode; stohiddenmnt_ptr(inode->i_sb) = new_hidden_mnt; stohs_ptr(inode->i_sb) = new_usi_sb; stopd(inode->i_sb)->usi_sbcount_p = new_counts; stopd(inode->i_sb)->usi_branchperms_p = new_branchperms; /* Re-NULL the new ones so we don't try to free them. */ new_hidden_mnt = NULL; new_udi_dentry = NULL; new_usi_sb = NULL; new_uii_inode = NULL; new_counts = NULL; new_branchperms = NULL; /* Put the new dentry information into it's slot. */ set_dtohd_index(inode->i_sb->s_root, addargs->ab_branch, nd.dentry); set_itohi_index(inode->i_sb->s_root->d_inode, addargs->ab_branch, igrab(nd.dentry->d_inode)); set_branchperms(inode->i_sb, addargs->ab_branch, addargs->ab_perms); set_branch_count(inode->i_sb, addargs->ab_branch, 0); set_stohiddenmnt_index(inode->i_sb, addargs->ab_branch, nd.mnt); set_stohs_index(inode->i_sb, addargs->ab_branch, nd.dentry->d_sb); atomic_set(&dtopd(inode->i_sb->s_root)->udi_generation, gen); atomic_set(&itopd(inode->i_sb->s_root->d_inode)->uii_generation, gen); fixputmaps(inode->i_sb); out: unlock_dentry(inode->i_sb->s_root); unionfs_write_unlock(inode->i_sb); KFREE(new_hidden_mnt); KFREE(new_udi_dentry); KFREE(new_uii_inode); KFREE(new_usi_sb); KFREE(new_counts); KFREE(new_branchperms); KFREE(addargs); if (path) putname(path); print_exit_status(err); return err; }
/* create the sto dir, setup states */ int create_sto_dir(dentry_t *dentry, int mode) { int err = 0; inode_t *dir; dentry_t *hidden_sto_dentry; dentry_t *hidden_sto_dir_dentry; /* had to take the "!S_ISDIR(mode))" check out, because it failed */ if(exists_in_storage(dentry)) { printk(KERN_CRIT "mini_fo: create_sto_dir: wrong type or state.\\ n"); err = -EINVAL; goto out; } err = get_neg_sto_dentry(dentry); if(err) { err = -EINVAL; goto out; } dir = dentry->d_parent->d_inode; 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); err = PTR_ERR(hidden_sto_dir_dentry); if (IS_ERR(hidden_sto_dir_dentry)) goto out; err = vfs_mkdir(hidden_sto_dir_dentry->d_inode, hidden_sto_dentry, mode); if(err) { printk(KERN_CRIT "mini_fo: create_sto_dir: ERROR creating sto dir.\n"); goto out_lock; } if(!dtohd2(dentry)->d_inode) { printk(KERN_CRIT "mini_fo: create_sto_dir: ERROR creating sto dir [2].\n"); err = -EINVAL; goto out_lock; } /* interpose the new inode */ 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(dtopd(dentry)->state == NON_EXISTANT) { dtopd(dentry)->state = CREATED; err = mini_fo_tri_interpose(dtohd(dentry), hidden_sto_dentry, dentry, dir->i_sb, 0); if(err) goto out_lock; } else if(dtopd(dentry)->state == UNMODIFIED) { dtopd(dentry)->state = MODIFIED; /* interpose on new inode */ if(itohi2(dentry->d_inode) != NULL) { printk(KERN_CRIT "mini_fo: create_sto_dir: ERROR, invalid inode detected.\n"); err = -EINVAL; goto out_lock; } itohi2(dentry->d_inode) = igrab(dtohd2(dentry)->d_inode); } fist_copy_attr_timesizes(dir, hidden_sto_dir_dentry->d_inode); /* initalize the wol list */ itopd(dentry->d_inode)->deleted_list_size = -1; itopd(dentry->d_inode)->renamed_list_size = -1; meta_build_lists(dentry); out_lock: /* was: unlock_dir(hidden_sto_dir_dentry); */ dput(hidden_sto_dir_dentry); out: return err; }
/* * THIS IS A BOOLEAN FUNCTION: returns 1 if valid, 0 otherwise. */ int unionfs_d_revalidate(struct dentry *dentry, struct nameidata *nd) { int valid = 1; /* default is valid (1); invalid is 0. */ struct dentry *hidden_dentry; int bindex, bstart, bend; int sbgen, dgen; int positive = 0; int locked = 0; int restart = 0; int interpose_flag; print_util_entry_location(); restart: verify_locked(dentry); /* if the dentry is unhashed, do NOT revalidate */ if (d_deleted(dentry)) { fist_dprint(6, "unhashed dentry being revalidated: %*s\n", dentry->d_name.len, dentry->d_name.name); goto out; } BUG_ON(dbstart(dentry) == -1); if (dentry->d_inode) positive = 1; dgen = atomic_read(&dtopd(dentry)->udi_generation); sbgen = atomic_read(&stopd(dentry->d_sb)->usi_generation); /* If we are working on an unconnected dentry, then there is no * revalidation to be done, because this file does not exist within the * namespace, and Unionfs operates on the namespace, not data. */ if (sbgen != dgen) { struct dentry *result; int pdgen; unionfs_read_lock(dentry->d_sb); locked = 1; /* The root entry should always be valid */ BUG_ON(IS_ROOT(dentry)); /* We can't work correctly if our parent isn't valid. */ pdgen = atomic_read(&dtopd(dentry->d_parent)->udi_generation); if (!restart && (pdgen != sbgen)) { unionfs_read_unlock(dentry->d_sb); locked = 0; /* We must be locked before our parent. */ if (! (dentry->d_parent->d_op-> d_revalidate(dentry->d_parent, nd))) { valid = 0; goto out; } restart = 1; goto restart; } BUG_ON(pdgen != sbgen); /* Free the pointers for our inodes and this dentry. */ bstart = dbstart(dentry); bend = dbend(dentry); if (bstart >= 0) { struct dentry *hidden_dentry; for (bindex = bstart; bindex <= bend; bindex++) { hidden_dentry = dtohd_index_nocheck(dentry, bindex); if (!hidden_dentry) continue; DPUT(hidden_dentry); } } set_dbstart(dentry, -1); set_dbend(dentry, -1); interpose_flag = INTERPOSE_REVAL_NEG; if (positive) { interpose_flag = INTERPOSE_REVAL; down(&dentry->d_inode->i_sem); bstart = ibstart(dentry->d_inode); bend = ibend(dentry->d_inode); if (bstart >= 0) { struct inode *hidden_inode; for (bindex = bstart; bindex <= bend; bindex++) { hidden_inode = itohi_index(dentry->d_inode, bindex); if (!hidden_inode) continue; IPUT(hidden_inode); } } KFREE(itohi_ptr(dentry->d_inode)); itohi_ptr(dentry->d_inode) = NULL; ibstart(dentry->d_inode) = -1; ibend(dentry->d_inode) = -1; up(&dentry->d_inode->i_sem); } result = unionfs_lookup_backend(dentry, interpose_flag); if (result) { if (IS_ERR(result)) { valid = 0; goto out; } /* current unionfs_lookup_backend() doesn't return a valid dentry */ DPUT(dentry); dentry = result; } if (positive && itopd(dentry->d_inode)->uii_stale) { make_stale_inode(dentry->d_inode); d_drop(dentry); valid = 0; goto out; } goto out; } /* The revalidation must occur across all branches */ bstart = dbstart(dentry); bend = dbend(dentry); BUG_ON(bstart == -1); for (bindex = bstart; bindex <= bend; bindex++) { hidden_dentry = dtohd_index(dentry, bindex); if (!hidden_dentry || !hidden_dentry->d_op || !hidden_dentry->d_op->d_revalidate) continue; if (!hidden_dentry->d_op->d_revalidate(hidden_dentry, nd)) valid = 0; } if (!dentry->d_inode) valid = 0; if (valid) fist_copy_attr_all(dentry->d_inode, itohi(dentry->d_inode)); out: if (locked) unionfs_read_unlock(dentry->d_sb); fist_print_dentry("revalidate out", dentry); print_util_exit_status(valid); return valid; }
struct dentry *unionfs_lookup_backend(struct dentry *dentry, int lookupmode) { int err = 0; struct dentry *hidden_dentry = NULL; struct dentry *wh_hidden_dentry = NULL; struct dentry *hidden_dir_dentry = NULL; struct dentry *parent_dentry = NULL; int bindex, bstart, bend, bopaque; int dentry_count = 0; /* Number of positive dentries. */ int first_dentry_offset = -1; struct dentry *first_hidden_dentry = NULL; int locked_parent = 0; int locked_child = 0; int opaque; char *whname = NULL; const char *name; int namelen; print_entry("mode = %d", lookupmode); /* We should already have a lock on this dentry in the case of a * partial lookup, or a revalidation. Otherwise it is returned from * new_dentry_private_data already locked. */ if (lookupmode == INTERPOSE_PARTIAL || lookupmode == INTERPOSE_REVAL || lookupmode == INTERPOSE_REVAL_NEG) { verify_locked(dentry); } else { BUG_ON(dtopd_nocheck(dentry) != NULL); locked_child = 1; } if (lookupmode != INTERPOSE_PARTIAL) if ((err = new_dentry_private_data(dentry))) goto out; /* must initialize dentry operations */ dentry->d_op = &unionfs_dops; parent_dentry = GET_PARENT(dentry); /* We never partial lookup the root directory. */ if (parent_dentry != dentry) { lock_dentry(parent_dentry); locked_parent = 1; } else { DPUT(parent_dentry); parent_dentry = NULL; goto out; } fist_print_dentry("IN unionfs_lookup (parent)", parent_dentry); fist_print_dentry("IN unionfs_lookup (child)", dentry); name = dentry->d_name.name; namelen = dentry->d_name.len; /* No dentries should get created for possible whiteout names. */ if (!is_validname(name)) { err = -EPERM; goto out_free; } /* Now start the actual lookup procedure. */ bstart = dbstart(parent_dentry); bend = dbend(parent_dentry); bopaque = dbopaque(parent_dentry); BUG_ON(bstart < 0); /* It would be ideal if we could convert partial lookups to only have * to do this work when they really need to. It could probably improve * performance quite a bit, and maybe simplify the rest of the code. */ if (lookupmode == INTERPOSE_PARTIAL) { bstart++; if ((bopaque != -1) && (bopaque < bend)) bend = bopaque; } fist_dprint(8, "bstart = %d, bend = %d\n", bstart, bend); for (bindex = bstart; bindex <= bend; bindex++) { hidden_dentry = dtohd_index(dentry, bindex); if (lookupmode == INTERPOSE_PARTIAL && hidden_dentry) continue; BUG_ON(hidden_dentry != NULL); hidden_dir_dentry = dtohd_index(parent_dentry, bindex); /* if the parent hidden dentry does not exist skip this */ if (!(hidden_dir_dentry && hidden_dir_dentry->d_inode)) continue; /* also skip it if the parent isn't a directory. */ if (!S_ISDIR(hidden_dir_dentry->d_inode->i_mode)) continue; /* Reuse the whiteout name because its value doesn't change. */ if (!whname) { whname = alloc_whname(name, namelen); if (IS_ERR(whname)) { err = PTR_ERR(whname); goto out_free; } } /* check if whiteout exists in this branch: lookup .wh.foo */ wh_hidden_dentry = LOOKUP_ONE_LEN(whname, hidden_dir_dentry, namelen + WHLEN); if (IS_ERR(wh_hidden_dentry)) { DPUT(first_hidden_dentry); err = PTR_ERR(wh_hidden_dentry); goto out_free; } if (wh_hidden_dentry->d_inode) { /* We found a whiteout so lets give up. */ fist_dprint(8, "whiteout found in %d\n", bindex); if (S_ISREG(wh_hidden_dentry->d_inode->i_mode)) { set_dbend(dentry, bindex); set_dbopaque(dentry, bindex); DPUT(wh_hidden_dentry); break; } err = -EIO; printk(KERN_NOTICE "EIO: Invalid whiteout entry type" " %d.\n", wh_hidden_dentry->d_inode->i_mode); DPUT(wh_hidden_dentry); DPUT(first_hidden_dentry); goto out_free; } DPUT(wh_hidden_dentry); wh_hidden_dentry = NULL; /* Now do regular lookup; lookup foo */ hidden_dentry = LOOKUP_ONE_LEN(name, hidden_dir_dentry, namelen); fist_print_generic_dentry("hidden result", hidden_dentry); if (IS_ERR(hidden_dentry)) { DPUT(first_hidden_dentry); err = PTR_ERR(hidden_dentry); goto out_free; } /* Store the first negative dentry specially, because if they * are all negative we need this for future creates. */ if (!hidden_dentry->d_inode) { if (!first_hidden_dentry && (dbstart(dentry) == -1)) { first_hidden_dentry = hidden_dentry; first_dentry_offset = bindex; } else { DPUT(hidden_dentry); } continue; } /* number of positive dentries */ dentry_count++; /* store underlying dentry */ if (dbstart(dentry) == -1) set_dbstart(dentry, bindex); set_dtohd_index(dentry, bindex, hidden_dentry); set_dbend(dentry, bindex); /* update parent directory's atime with the bindex */ fist_copy_attr_atime(parent_dentry->d_inode, hidden_dir_dentry->d_inode); /* We terminate file lookups here. */ if (!S_ISDIR(hidden_dentry->d_inode->i_mode)) { if (lookupmode == INTERPOSE_PARTIAL) continue; if (dentry_count == 1) goto out_positive; /* This can only happen with mixed D-*-F-* */ BUG_ON(!S_ISDIR(dtohd(dentry)->d_inode->i_mode)); continue; } opaque = is_opaque_dir(dentry, bindex); if (opaque < 0) { DPUT(first_hidden_dentry); err = opaque; goto out_free; } if (opaque) { set_dbend(dentry, bindex); set_dbopaque(dentry, bindex); break; } } if (dentry_count) goto out_positive; else goto out_negative; out_negative: if (lookupmode == INTERPOSE_PARTIAL) goto out; /* If we've only got negative dentries, then use the leftmost one. */ if (lookupmode == INTERPOSE_REVAL) { if (dentry->d_inode) { itopd(dentry->d_inode)->uii_stale = 1; } goto out; } /* This should only happen if we found a whiteout. */ if (first_dentry_offset == -1) { first_hidden_dentry = LOOKUP_ONE_LEN(name, hidden_dir_dentry, namelen); first_dentry_offset = bindex; if (IS_ERR(first_hidden_dentry)) { err = PTR_ERR(first_hidden_dentry); goto out; } } set_dtohd_index(dentry, first_dentry_offset, first_hidden_dentry); set_dbstart(dentry, first_dentry_offset); set_dbend(dentry, first_dentry_offset); if (lookupmode == INTERPOSE_REVAL_NEG) BUG_ON(dentry->d_inode != NULL); else d_add(dentry, NULL); goto out; /* This part of the code is for positive dentries. */ out_positive: BUG_ON(dentry_count <= 0); /* If we're holding onto the first negative dentry throw it out. */ DPUT(first_hidden_dentry); /* Partial lookups need to reinterpose, or throw away older negs. */ if (lookupmode == INTERPOSE_PARTIAL) { if (dentry->d_inode) { unionfs_reinterpose(dentry); goto out; } /* This somehow turned positive, so it is as if we had a * negative revalidation. */ lookupmode = INTERPOSE_REVAL_NEG; update_bstart(dentry); bstart = dbstart(dentry); bend = dbend(dentry); } err = unionfs_interpose(dentry, dentry->d_sb, lookupmode); if (err) goto out_drop; fist_checkinode(dentry->d_inode, "unionfs_lookup OUT: child"); fist_checkinode(parent_dentry->d_inode, "unionfs_lookup OUT: dir"); goto out; out_drop: d_drop(dentry); out_free: /* should dput all the underlying dentries on error condition */ bstart = dbstart(dentry); if (bstart >= 0) { bend = dbend(dentry); for (bindex = bstart; bindex <= bend; bindex++) DPUT(dtohd_index(dentry, bindex)); } KFREE(dtohd_ptr(dentry)); dtohd_ptr(dentry) = NULL; set_dbstart(dentry, -1); set_dbend(dentry, -1); out: if (!err && dtopd(dentry)) { BUG_ON(dbend(dentry) > dtopd(dentry)->udi_bcount); BUG_ON(dbend(dentry) > sbmax(dentry->d_sb)); BUG_ON(dbstart(dentry) < 0); } KFREE(whname); fist_print_dentry("OUT unionfs_lookup (parent)", parent_dentry); fist_print_dentry("OUT unionfs_lookup (child)", dentry); if (locked_parent) unlock_dentry(parent_dentry); DPUT(parent_dentry); if (locked_child) unlock_dentry(dentry); print_exit_status(err); return ERR_PTR(err); }
int unionfs_ioctl_addbranch(struct inode *inode, unsigned int cmd, unsigned long arg) { int err; struct unionfs_addbranch_args *addargs = NULL; struct nameidata nd; char *path = NULL; int gen; int i; int pobjects; struct unionfs_usi_data *new_data = NULL; struct dentry **new_udi_dentry = NULL; struct inode **new_uii_inode = NULL; struct dentry *root = NULL; struct dentry *hidden_root = NULL; print_entry_location(); err = -ENOMEM; addargs = KMALLOC(sizeof(struct unionfs_addbranch_args), GFP_KERNEL); if (!addargs) goto out; err = -EFAULT; if (copy_from_user (addargs, (const void __user *)arg, sizeof(struct unionfs_addbranch_args))) goto out; err = -EINVAL; if (addargs->ab_perms & ~(MAY_READ | MAY_WRITE | MAY_NFSRO)) goto out; if (!(addargs->ab_perms & MAY_READ)) goto out; err = -E2BIG; if (sbend(inode->i_sb) > FD_SETSIZE) goto out; err = -ENOMEM; if (!(path = getname((const char __user *)addargs->ab_path))) goto out; err = path_lookup(path, LOOKUP_FOLLOW, &nd); RECORD_PATH_LOOKUP(&nd); if (err) goto out; if ((err = check_branch(&nd))) { path_release(&nd); RECORD_PATH_RELEASE(&nd); goto out; } unionfs_write_lock(inode->i_sb); lock_dentry(inode->i_sb->s_root); root = inode->i_sb->s_root; for (i = dbstart(inode->i_sb->s_root); i <= dbend(inode->i_sb->s_root); i++) { hidden_root = dtohd_index(root, i); if (is_branch_overlap(hidden_root, nd.dentry)) { err = -EINVAL; goto out; } } err = -EINVAL; if (addargs->ab_branch < 0 || (addargs->ab_branch > (sbend(inode->i_sb) + 1))) goto out; if ((err = newputmap(inode->i_sb))) goto out; stopd(inode->i_sb)->b_end++; dtopd(inode->i_sb->s_root)->udi_bcount++; set_dbend(inode->i_sb->s_root, dbend(inode->i_sb->s_root) + 1); itopd(inode->i_sb->s_root->d_inode)->b_end++; atomic_inc(&stopd(inode->i_sb)->usi_generation); gen = atomic_read(&stopd(inode->i_sb)->usi_generation); pobjects = sbend(inode->i_sb) + 1; /* Reallocate the dynamic structures. */ new_data = alloc_new_data(pobjects); new_udi_dentry = alloc_new_dentries(pobjects); new_uii_inode = KZALLOC(sizeof(struct inode *) * pobjects, GFP_KERNEL); if (!new_udi_dentry || !new_uii_inode || !new_data) { err = -ENOMEM; goto out; } /* Copy the in-place values to our new structure. */ for (i = 0; i < addargs->ab_branch; i++) { atomic_set(&(new_data[i].sbcount), branch_count(inode->i_sb, i)); new_data[i].branchperms = branchperms(inode->i_sb, i); new_data[i].hidden_mnt = stohiddenmnt_index(inode->i_sb, i); new_data[i].sb = stohs_index(inode->i_sb, i); new_udi_dentry[i] = dtohd_index(inode->i_sb->s_root, i); new_uii_inode[i] = itohi_index(inode->i_sb->s_root->d_inode, i); } /* Shift the ends to the right (only handle reallocated bits). */ for (i = sbend(inode->i_sb) - 1; i >= (int)addargs->ab_branch; i--) { int j = i + 1; int pmindex; atomic_set(&new_data[j].sbcount, branch_count(inode->i_sb, i)); new_data[j].branchperms = branchperms(inode->i_sb, i); new_data[j].hidden_mnt = stohiddenmnt_index(inode->i_sb, i); new_data[j].sb = stohs_index(inode->i_sb, i); new_udi_dentry[j] = dtohd_index(inode->i_sb->s_root, i); new_uii_inode[j] = itohi_index(inode->i_sb->s_root->d_inode, i); /* Update the newest putmap, so it is correct for later. */ pmindex = stopd(inode->i_sb)->usi_lastputmap; pmindex -= stopd(inode->i_sb)->usi_firstputmap; stopd(inode->i_sb)->usi_putmaps[pmindex]->map[i] = j; } /* Now we can free the old ones. */ KFREE(dtopd(inode->i_sb->s_root)->udi_dentry); KFREE(itopd(inode->i_sb->s_root->d_inode)->uii_inode); KFREE(stopd(inode->i_sb)->usi_data); /* Update the real pointers. */ dtohd_ptr(inode->i_sb->s_root) = new_udi_dentry; itohi_ptr(inode->i_sb->s_root->d_inode) = new_uii_inode; stopd(inode->i_sb)->usi_data = new_data; /* Re-NULL the new ones so we don't try to free them. */ new_data = NULL; new_udi_dentry = NULL; new_uii_inode = NULL; /* Put the new dentry information into it's slot. */ set_dtohd_index(inode->i_sb->s_root, addargs->ab_branch, nd.dentry); set_itohi_index(inode->i_sb->s_root->d_inode, addargs->ab_branch, IGRAB(nd.dentry->d_inode)); set_branchperms(inode->i_sb, addargs->ab_branch, addargs->ab_perms); set_branch_count(inode->i_sb, addargs->ab_branch, 0); set_stohiddenmnt_index(inode->i_sb, addargs->ab_branch, nd.mnt); set_stohs_index(inode->i_sb, addargs->ab_branch, nd.dentry->d_sb); atomic_set(&dtopd(inode->i_sb->s_root)->udi_generation, gen); atomic_set(&itopd(inode->i_sb->s_root->d_inode)->uii_generation, gen); fixputmaps(inode->i_sb); out: unlock_dentry(inode->i_sb->s_root); unionfs_write_unlock(inode->i_sb); KFREE(new_udi_dentry); KFREE(new_uii_inode); KFREE(new_data); KFREE(addargs); if (path) putname(path); print_exit_status(err); return err; }
/* sb we pass is unionfs's super_block */ int unionfs_interpose(struct dentry *dentry, struct super_block *sb, int flag) { struct inode *hidden_inode; struct dentry *hidden_dentry; int err = 0; struct inode *inode; int is_negative_dentry = 1; int bindex, bstart, bend; print_entry("flag = %d", flag); verify_locked(dentry); fist_print_dentry("In unionfs_interpose", dentry); bstart = dbstart(dentry); bend = dbend(dentry); /* Make sure that we didn't get a negative dentry. */ for (bindex = bstart; bindex <= bend; bindex++) { if (dtohd_index(dentry, bindex) && dtohd_index(dentry, bindex)->d_inode) { is_negative_dentry = 0; break; } } BUG_ON(is_negative_dentry); /* We allocate our new inode below, by calling iget. * iget will call our read_inode which will initialize some * of the new inode's fields */ /* On revalidate we've already got our own inode and just need * to fix it up. */ if (flag == INTERPOSE_REVAL) { inode = dentry->d_inode; itopd(inode)->b_start = -1; itopd(inode)->b_end = -1; atomic_set(&itopd(inode)->uii_generation, atomic_read(&stopd(sb)->usi_generation)); itohi_ptr(inode) = KZALLOC(sbmax(sb) * sizeof(struct inode *), GFP_KERNEL); if (!itohi_ptr(inode)) { err = -ENOMEM; goto out; } } else { ino_t ino; /* get unique inode number for unionfs */ #ifdef UNIONFS_IMAP if (stopd(sb)->usi_persistent) { err = read_uin(sb, bindex, dtohd_index(dentry, bindex)->d_inode->i_ino, O_CREAT, &ino); if (err) goto out; } else #endif ino = iunique(sb, UNIONFS_ROOT_INO); inode = IGET(sb, ino); if (!inode) { err = -EACCES; /* should be impossible??? */ goto out; } } down(&inode->i_sem); if (atomic_read(&inode->i_count) > 1) goto skip; for (bindex = bstart; bindex <= bend; bindex++) { hidden_dentry = dtohd_index(dentry, bindex); if (!hidden_dentry) { set_itohi_index(inode, bindex, NULL); continue; } /* Initialize the hidden inode to the new hidden inode. */ if (!hidden_dentry->d_inode) continue; set_itohi_index(inode, bindex, IGRAB(hidden_dentry->d_inode)); } ibstart(inode) = dbstart(dentry); ibend(inode) = dbend(dentry); /* Use attributes from the first branch. */ hidden_inode = itohi(inode); /* Use different set of inode ops for symlinks & directories */ if (S_ISLNK(hidden_inode->i_mode)) inode->i_op = &unionfs_symlink_iops; else if (S_ISDIR(hidden_inode->i_mode)) inode->i_op = &unionfs_dir_iops; /* Use different set of file ops for directories */ if (S_ISDIR(hidden_inode->i_mode)) inode->i_fop = &unionfs_dir_fops; /* properly initialize special inodes */ if (S_ISBLK(hidden_inode->i_mode) || S_ISCHR(hidden_inode->i_mode) || S_ISFIFO(hidden_inode->i_mode) || S_ISSOCK(hidden_inode->i_mode)) init_special_inode(inode, hidden_inode->i_mode, hidden_inode->i_rdev); /* Fix our inode's address operations to that of the lower inode (Unionfs is FiST-Lite) */ if (inode->i_mapping->a_ops != hidden_inode->i_mapping->a_ops) { fist_dprint(7, "fixing inode 0x%p a_ops (0x%p -> 0x%p)\n", inode, inode->i_mapping->a_ops, hidden_inode->i_mapping->a_ops); inode->i_mapping->a_ops = hidden_inode->i_mapping->a_ops; } /* all well, copy inode attributes */ fist_copy_attr_all(inode, hidden_inode); skip: /* only (our) lookup wants to do a d_add */ switch (flag) { case INTERPOSE_DEFAULT: case INTERPOSE_REVAL_NEG: d_instantiate(dentry, inode); break; case INTERPOSE_LOOKUP: err = PTR_ERR(d_splice_alias(inode, dentry)); break; case INTERPOSE_REVAL: /* Do nothing. */ break; default: printk(KERN_ERR "Invalid interpose flag passed!"); BUG(); } fist_print_dentry("Leaving unionfs_interpose", dentry); fist_print_inode("Leaving unionfs_interpose", inode); up(&inode->i_sem); out: print_exit_status(err); return err; }