static struct dentry *lookup_whiteout(struct dentry *dentry) { char *whname; int bindex = -1, bstart = -1, bend = -1; struct dentry *parent, *hidden_parent, *wh_dentry; whname = alloc_whname(dentry->d_name.name, dentry->d_name.len); if (IS_ERR(whname)) return (void *)whname; parent = dget_parent(dentry); unionfs_lock_dentry(parent); bstart = dbstart(parent); bend = dbend(parent); wh_dentry = ERR_PTR(-ENOENT); for (bindex = bstart; bindex <= bend; bindex++) { hidden_parent = unionfs_lower_dentry_idx(parent, bindex); if (!hidden_parent) continue; wh_dentry = lookup_one_len(whname, hidden_parent, dentry->d_name.len + UNIONFS_WHLEN); if (IS_ERR(wh_dentry)) continue; if (wh_dentry->d_inode) break; dput(wh_dentry); wh_dentry = ERR_PTR(-ENOENT); } unionfs_unlock_dentry(parent); dput(parent); kfree(whname); return wh_dentry; }
static ssize_t unionfs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { int err = 0; struct file *lower_file; struct dentry *dentry = file->f_path.dentry; struct dentry *parent; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); err = unionfs_file_revalidate(file, parent, true); if (unlikely(err)) goto out; lower_file = unionfs_lower_file(file); err = vfs_write(lower_file, buf, count, ppos); /* update our inode times+sizes upon a successful lower write */ if (err >= 0) { fsstack_copy_inode_size(dentry->d_inode, lower_file->f_path.dentry->d_inode); fsstack_copy_attr_times(dentry->d_inode, lower_file->f_path.dentry->d_inode); UNIONFS_F(file)->wrote_to_file = true; /* for delayed copyup */ unionfs_check_file(file); } out: unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
/* * BKL held by caller. * dentry->d_inode->i_mutex locked */ ssize_t unionfs_listxattr(struct dentry *dentry, char *list, size_t size) { struct dentry *lower_dentry = NULL; struct dentry *parent; int err = -EOPNOTSUPP; char *encoded_list = NULL; bool valid; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); valid = __unionfs_d_revalidate(dentry, parent, false); if (unlikely(!valid)) { err = -ESTALE; goto out; } lower_dentry = unionfs_lower_dentry(dentry); encoded_list = list; err = vfs_listxattr(lower_dentry, encoded_list, size); out: unionfs_check_dentry(dentry); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
static ssize_t unionfs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { int err; struct file *lower_file; struct dentry *dentry = file->f_path.dentry; struct dentry *parent; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); err = unionfs_file_revalidate(file, parent, false); if (unlikely(err)) goto out; lower_file = unionfs_lower_file(file); err = vfs_read(lower_file, buf, count, ppos); /* update our inode atime upon a successful lower read */ if (err >= 0) { fsstack_copy_attr_atime(dentry->d_inode, lower_file->f_path.dentry->d_inode); unionfs_check_file(file); } out: unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
static ssize_t unionfs_splice_write(struct pipe_inode_info *pipe, struct file *file, loff_t *ppos, size_t len, unsigned int flags) { ssize_t err = 0; struct file *lower_file; struct dentry *dentry = file->f_path.dentry; struct dentry *parent; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); err = unionfs_file_revalidate(file, parent, true); if (unlikely(err)) goto out; lower_file = unionfs_lower_file(file); err = vfs_splice_from(pipe, lower_file, ppos, len, flags); /* update our inode times+sizes upon a successful lower write */ if (err >= 0) { fsstack_copy_inode_size(dentry->d_inode, lower_file->f_path.dentry->d_inode); fsstack_copy_attr_times(dentry->d_inode, lower_file->f_path.dentry->d_inode); unionfs_check_file(file); } out: unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
/* * BKL held by caller. * dentry->d_inode->i_mutex locked */ int unionfs_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags) { struct dentry *lower_dentry = NULL; struct dentry *parent; int err = -EOPNOTSUPP; bool valid; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); valid = __unionfs_d_revalidate(dentry, parent, false); if (unlikely(!valid)) { err = -ESTALE; goto out; } lower_dentry = unionfs_lower_dentry(dentry); err = vfs_setxattr(lower_dentry, (char *) name, (void *) value, size, flags); out: unionfs_check_dentry(dentry); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
static int unionfs_readlink(struct dentry *dentry, char __user *buf, int bufsiz) { int err; struct dentry *parent; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); if (unlikely(!__unionfs_d_revalidate(dentry, parent, false, 0))) { err = -ESTALE; goto out; } err = __unionfs_readlink(dentry, buf, bufsiz); out: unionfs_check_dentry(dentry); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
/* this @nd *IS* still used */ static void unionfs_put_link(struct dentry *dentry, struct nameidata *nd, void *cookie) { struct dentry *parent; char *buf; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); if (unlikely(!__unionfs_d_revalidate(dentry, parent, false))) printk(KERN_ERR "unionfs: put_link failed to revalidate dentry\n"); unionfs_check_dentry(dentry); #if 0 /* XXX: can't run this check b/c this fxn can receive a poisoned 'nd' PTR */ unionfs_check_nd(nd); #endif buf = nd_get_link(nd); if (!IS_ERR(buf)) kfree(buf); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); }
static int unionfs_create(struct inode *dir, struct dentry *dentry, umode_t mode, bool want_excl) { int err = 0; struct dentry *lower_dentry = NULL; struct dentry *lower_parent_dentry = NULL; struct dentry *parent; int valid = 0; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); valid = __unionfs_d_revalidate(dentry, parent, false, 0); if (unlikely(!valid)) { err = -ESTALE; /* same as what real_lookup does */ goto out; } lower_dentry = find_writeable_branch(dir, dentry); if (IS_ERR(lower_dentry)) { err = PTR_ERR(lower_dentry); goto out; } lower_parent_dentry = lock_parent(lower_dentry); if (IS_ERR(lower_parent_dentry)) { err = PTR_ERR(lower_parent_dentry); goto out_unlock; } err = vfs_create(lower_parent_dentry->d_inode, lower_dentry, mode, want_excl); if (!err) { err = PTR_ERR(unionfs_interpose(dentry, dir->i_sb, 0)); if (!err) { unionfs_copy_attr_times(dir); fsstack_copy_inode_size(dir, lower_parent_dentry->d_inode); /* update no. of links on parent directory */ set_nlink(dir, unionfs_get_nlinks(dir)); } } out_unlock: unlock_dir(lower_parent_dentry); out: if (!err) { unionfs_postcopyup_setmnt(dentry); unionfs_check_inode(dir); unionfs_check_dentry(dentry); } unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
int unionfs_fsync(struct file *file, struct dentry *dentry, int datasync) { int bindex, bstart, bend; struct file *lower_file; struct dentry *lower_dentry; struct dentry *parent; struct inode *lower_inode, *inode; int err = -EINVAL; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); err = unionfs_file_revalidate(file, parent, true); if (unlikely(err)) goto out; unionfs_check_file(file); bstart = fbstart(file); bend = fbend(file); if (bstart < 0 || bend < 0) goto out; inode = dentry->d_inode; if (unlikely(!inode)) { printk(KERN_ERR "unionfs: null lower inode in unionfs_fsync\n"); goto out; } for (bindex = bstart; bindex <= bend; bindex++) { lower_inode = unionfs_lower_inode_idx(inode, bindex); if (!lower_inode || !lower_inode->i_fop->fsync) continue; lower_file = unionfs_lower_file_idx(file, bindex); lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); mutex_lock(&lower_inode->i_mutex); err = lower_inode->i_fop->fsync(lower_file, lower_dentry, datasync); if (!err && bindex == bstart) fsstack_copy_attr_times(inode, lower_inode); mutex_unlock(&lower_inode->i_mutex); if (err) goto out; } out: if (!err) unionfs_check_file(file); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
static int unionfs_d_revalidate_wrap(struct dentry *dentry, struct nameidata *nd) { int err; unionfs_lock_dentry(dentry); err = unionfs_d_revalidate(dentry, nd); unionfs_unlock_dentry(dentry); return err; }
int unionfs_unlink(struct inode *dir, struct dentry *dentry) { int err = 0; unionfs_lock_dentry(dentry); err = unionfs_unlink_whiteout(dir, dentry); /* call d_drop so the system "forgets" about us */ if (!err) d_drop(dentry); unionfs_unlock_dentry(dentry); return err; }
static void *unionfs_follow_link(struct dentry *dentry, struct nameidata *nd) { char *buf; int len = PAGE_SIZE, err; mm_segment_t old_fs; struct dentry *parent; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); /* This is freed by the put_link method assuming a successful call. */ buf = kmalloc(len, GFP_KERNEL); if (unlikely(!buf)) { err = -ENOMEM; goto out; } /* read the symlink, and then we will follow it */ old_fs = get_fs(); set_fs(KERNEL_DS); err = __unionfs_readlink(dentry, buf, len); set_fs(old_fs); if (err < 0) { kfree(buf); buf = NULL; goto out; } buf[err] = 0; nd_set_link(nd, buf); err = 0; out: if (err >= 0) { unionfs_check_nd(nd); unionfs_check_dentry(dentry); } unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return ERR_PTR(err); }
static void unionfs_d_release(struct dentry *dentry) { int bindex, bstart, bend; /* There is no reason to lock the dentry, because we have the only * reference, but the printing functions verify that we have a lock * on the dentry before calling dbstart, etc. */ unionfs_lock_dentry(dentry); /* this could be a negative dentry, so check first */ if (!UNIONFS_D(dentry)) { printk(KERN_DEBUG "dentry without private data: %.*s", dentry->d_name.len, dentry->d_name.name); goto out; } else if (dbstart(dentry) < 0) { /* this is due to a failed lookup */ printk(KERN_DEBUG "dentry without hidden dentries : %.*s", dentry->d_name.len, dentry->d_name.name); goto out_free; } /* Release all the hidden dentries */ bstart = dbstart(dentry); bend = dbend(dentry); for (bindex = bstart; bindex <= bend; bindex++) { dput(unionfs_lower_dentry_idx(dentry, bindex)); mntput(unionfs_lower_mnt_idx(dentry, bindex)); unionfs_set_lower_dentry_idx(dentry, bindex, NULL); unionfs_set_lower_mnt_idx(dentry, bindex, NULL); } /* free private data (unionfs_dentry_info) here */ kfree(UNIONFS_D(dentry)->lower_paths); UNIONFS_D(dentry)->lower_paths = NULL; out_free: /* No need to unlock it, because it is disappeared. */ free_dentry_private_data(UNIONFS_D(dentry)); dentry->d_fsdata = NULL; /* just to be safe */ out: return; }
int unionfs_rmdir(struct inode *dir, struct dentry *dentry) { int err = 0; struct unionfs_dir_state *namelist = NULL; unionfs_lock_dentry(dentry); /* check if this unionfs directory is empty or not */ err = check_empty(dentry, &namelist); if (err) goto out; err = unionfs_rmdir_first(dir, dentry, namelist); /* create whiteout */ if (!err) err = create_whiteout(dentry, dbstart(dentry)); else { int new_err; if (dbstart(dentry) == 0) goto out; /* exit if the error returned was NOT -EROFS */ if (!IS_COPYUP_ERR(err)) goto out; new_err = create_whiteout(dentry, dbstart(dentry) - 1); if (new_err != -EEXIST) err = new_err; } out: /* call d_drop so the system "forgets" about us */ if (!err) d_drop(dentry); if (namelist) free_rdstate(namelist); unionfs_unlock_dentry(dentry); return err; }
/* this @nd *IS* still used */ static void unionfs_put_link(struct dentry *dentry, struct nameidata *nd, void *cookie) { struct dentry *parent; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); if (unlikely(!__unionfs_d_revalidate(dentry, parent, false))) printk(KERN_ERR "unionfs: put_link failed to revalidate dentry\n"); unionfs_check_dentry(dentry); unionfs_check_nd(nd); kfree(nd_get_link(nd)); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); }
long unionfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { long err; struct dentry *dentry = file->f_path.dentry; struct dentry *parent; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); err = unionfs_file_revalidate(file, parent, true); if (unlikely(err)) goto out; /* check if asked for local commands */ switch (cmd) { case UNIONFS_IOCTL_INCGEN: /* Increment the superblock generation count */ pr_info("unionfs: incgen ioctl deprecated; " "use \"-o remount,incgen\"\n"); err = -ENOSYS; break; case UNIONFS_IOCTL_QUERYFILE: /* Return list of branches containing the given file */ err = unionfs_ioctl_queryfile(file, parent, cmd, arg); break; default: /* pass the ioctl down */ err = do_ioctl(file, cmd, arg); break; } out: unionfs_check_file(file); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
int unionfs_flush(struct file *file, fl_owner_t id) { int err = 0; struct file *lower_file = NULL; struct dentry *dentry = file->f_path.dentry; struct dentry *parent; int bindex, bstart, bend; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); err = unionfs_file_revalidate(file, parent, UNIONFS_F(file)->wrote_to_file); if (unlikely(err)) goto out; unionfs_check_file(file); bstart = fbstart(file); bend = fbend(file); for (bindex = bstart; bindex <= bend; bindex++) { lower_file = unionfs_lower_file_idx(file, bindex); if (lower_file && lower_file->f_op && lower_file->f_op->flush) { err = lower_file->f_op->flush(lower_file, id); if (err) goto out; } } out: if (!err) unionfs_check_file(file); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
static int unionfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev) { int err = 0; struct dentry *lower_dentry = NULL; struct dentry *wh_dentry = NULL; struct dentry *lower_parent_dentry = NULL; struct dentry *parent; char *name = NULL; int valid = 0; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); valid = __unionfs_d_revalidate(dentry, parent, false, 0); if (unlikely(!valid)) { err = -ESTALE; goto out; } /* * It's only a bug if this dentry was not negative and couldn't be * revalidated (shouldn't happen). */ BUG_ON(!valid && dentry->d_inode); lower_dentry = find_writeable_branch(dir, dentry); if (IS_ERR(lower_dentry)) { err = PTR_ERR(lower_dentry); goto out; } lower_parent_dentry = lock_parent(lower_dentry); if (IS_ERR(lower_parent_dentry)) { err = PTR_ERR(lower_parent_dentry); goto out_unlock; } err = vfs_mknod(lower_parent_dentry->d_inode, lower_dentry, mode, dev); if (!err) { err = PTR_ERR(unionfs_interpose(dentry, dir->i_sb, 0)); if (!err) { unionfs_copy_attr_times(dir); fsstack_copy_inode_size(dir, lower_parent_dentry->d_inode); /* update no. of links on parent directory */ set_nlink(dir, unionfs_get_nlinks(dir)); } } out_unlock: unlock_dir(lower_parent_dentry); out: dput(wh_dentry); kfree(name); if (!err) { unionfs_postcopyup_setmnt(dentry); unionfs_check_inode(dir); unionfs_check_dentry(dentry); } unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
static int unionfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) { int err = 0; struct dentry *lower_dentry = NULL; struct dentry *lower_parent_dentry = NULL; struct dentry *parent; int bindex = 0, bstart; char *name = NULL; int valid; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); valid = __unionfs_d_revalidate(dentry, parent, false, 0); if (unlikely(!valid)) { err = -ESTALE; /* same as what real_lookup does */ goto out; } bstart = dbstart(dentry); lower_dentry = unionfs_lower_dentry(dentry); /* check for a whiteout in new dentry branch, and delete it */ err = check_unlink_whiteout(dentry, lower_dentry, bstart); if (err > 0) /* whiteout found and removed successfully */ err = 0; if (err) { /* exit if the error returned was NOT -EROFS */ if (!IS_COPYUP_ERR(err)) goto out; bstart--; } /* check if copyup's needed, and mkdir */ for (bindex = bstart; bindex >= 0; bindex--) { int i; int bend = dbend(dentry); if (is_robranch_super(dentry->d_sb, bindex)) continue; lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); if (!lower_dentry) { lower_dentry = create_parents(dir, dentry, dentry->d_name.name, bindex); if (!lower_dentry || IS_ERR(lower_dentry)) { printk(KERN_ERR "unionfs: lower dentry " " NULL for bindex = %d\n", bindex); continue; } } lower_parent_dentry = lock_parent(lower_dentry); if (IS_ERR(lower_parent_dentry)) { err = PTR_ERR(lower_parent_dentry); goto out; } err = vfs_mkdir(lower_parent_dentry->d_inode, lower_dentry, mode); unlock_dir(lower_parent_dentry); /* did the mkdir succeed? */ if (err) break; for (i = bindex + 1; i <= bend; i++) { /* XXX: use path_put_lowers? */ if (unionfs_lower_dentry_idx(dentry, i)) { dput(unionfs_lower_dentry_idx(dentry, i)); unionfs_set_lower_dentry_idx(dentry, i, NULL); } } dbend(dentry) = bindex; /* * Only INTERPOSE_LOOKUP can return a value other than 0 on * err. */ err = PTR_ERR(unionfs_interpose(dentry, dir->i_sb, 0)); if (!err) { unionfs_copy_attr_times(dir); fsstack_copy_inode_size(dir, lower_parent_dentry->d_inode); /* update number of links on parent directory */ set_nlink(dir, unionfs_get_nlinks(dir)); } err = make_dir_opaque(dentry, dbstart(dentry)); if (err) { printk(KERN_ERR "unionfs: mkdir: error creating " ".wh.__dir_opaque: %d\n", err); goto out; } /* we are done! */ break; } out: if (!dentry->d_inode) d_drop(dentry); kfree(name); if (!err) { unionfs_copy_attr_times(dentry->d_inode); unionfs_postcopyup_setmnt(dentry); } unionfs_check_inode(dir); unionfs_check_dentry(dentry); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
/* * The locking rules in unionfs_rename are complex. We could use a simpler * superblock-level name-space lock for renames and copy-ups. */ int unionfs_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry) { int err = 0; struct dentry *wh_dentry; struct dentry *old_parent, *new_parent; int valid = true; unionfs_read_lock(old_dentry->d_sb, UNIONFS_SMUTEX_CHILD); old_parent = dget_parent(old_dentry); new_parent = dget_parent(new_dentry); /* un/lock parent dentries only if they differ from old/new_dentry */ if (old_parent != old_dentry && old_parent != new_dentry) unionfs_lock_dentry(old_parent, UNIONFS_DMUTEX_REVAL_PARENT); if (new_parent != old_dentry && new_parent != new_dentry && new_parent != old_parent) unionfs_lock_dentry(new_parent, UNIONFS_DMUTEX_REVAL_CHILD); unionfs_double_lock_dentry(old_dentry, new_dentry); valid = __unionfs_d_revalidate(old_dentry, old_parent, false); if (!valid) { err = -ESTALE; goto out; } if (!d_deleted(new_dentry) && new_dentry->d_inode) { valid = __unionfs_d_revalidate(new_dentry, new_parent, false); if (!valid) { err = -ESTALE; goto out; } } if (!S_ISDIR(old_dentry->d_inode->i_mode)) err = unionfs_partial_lookup(old_dentry, old_parent); else err = may_rename_dir(old_dentry, old_parent); if (err) goto out; err = unionfs_partial_lookup(new_dentry, new_parent); if (err) goto out; /* * if new_dentry is already lower because of whiteout, * simply override it even if the whited-out dir is not empty. */ wh_dentry = find_first_whiteout(new_dentry); if (!IS_ERR(wh_dentry)) { dput(wh_dentry); } else if (new_dentry->d_inode) { if (S_ISDIR(old_dentry->d_inode->i_mode) != S_ISDIR(new_dentry->d_inode->i_mode)) { err = S_ISDIR(old_dentry->d_inode->i_mode) ? -ENOTDIR : -EISDIR; goto out; } if (S_ISDIR(new_dentry->d_inode->i_mode)) { struct unionfs_dir_state *namelist = NULL; /* check if this unionfs directory is empty or not */ err = check_empty(new_dentry, new_parent, &namelist); if (err) goto out; if (!is_robranch(new_dentry)) err = delete_whiteouts(new_dentry, dbstart(new_dentry), namelist); free_rdstate(namelist); if (err) goto out; } } err = do_unionfs_rename(old_dir, old_dentry, old_parent, new_dir, new_dentry, new_parent); if (err) goto out; /* * force re-lookup since the dir on ro branch is not renamed, and * lower dentries still indicate the un-renamed ones. */ if (S_ISDIR(old_dentry->d_inode->i_mode)) atomic_dec(&UNIONFS_D(old_dentry)->generation); else unionfs_postcopyup_release(old_dentry); if (new_dentry->d_inode && !S_ISDIR(new_dentry->d_inode->i_mode)) { unionfs_postcopyup_release(new_dentry); unionfs_postcopyup_setmnt(new_dentry); if (!unionfs_lower_inode(new_dentry->d_inode)) { /* * If we get here, it means that no copyup was * needed, and that a file by the old name already * existing on the destination branch; that file got * renamed earlier in this function, so all we need * to do here is set the lower inode. */ struct inode *inode; inode = unionfs_lower_inode(old_dentry->d_inode); igrab(inode); unionfs_set_lower_inode_idx(new_dentry->d_inode, dbstart(new_dentry), inode); } } /* if all of this renaming succeeded, update our times */ unionfs_copy_attr_times(old_dentry->d_inode); unionfs_copy_attr_times(new_dentry->d_inode); unionfs_check_inode(old_dir); unionfs_check_inode(new_dir); unionfs_check_dentry(old_dentry); unionfs_check_dentry(new_dentry); out: if (err) /* clear the new_dentry stuff created */ d_drop(new_dentry); unionfs_double_unlock_dentry(old_dentry, new_dentry); if (new_parent != old_dentry && new_parent != new_dentry && new_parent != old_parent) unionfs_unlock_dentry(new_parent); if (old_parent != old_dentry && old_parent != new_dentry) unionfs_unlock_dentry(old_parent); dput(new_parent); dput(old_parent); unionfs_read_unlock(old_dentry->d_sb); return err; }
static int unionfs_mmap(struct file *file, struct vm_area_struct *vma) { int err = 0; bool willwrite; struct file *lower_file; struct dentry *dentry = file->f_path.dentry; struct dentry *parent; struct vm_operations_struct *saved_vm_ops = NULL; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); /* This might be deferred to mmap's writepage */ willwrite = ((vma->vm_flags | VM_SHARED | VM_WRITE) == vma->vm_flags); err = unionfs_file_revalidate(file, parent, willwrite); if (unlikely(err)) goto out; unionfs_check_file(file); /* * File systems which do not implement ->writepage may use * generic_file_readonly_mmap as their ->mmap op. If you call * generic_file_readonly_mmap with VM_WRITE, you'd get an -EINVAL. * But we cannot call the lower ->mmap op, so we can't tell that * writeable mappings won't work. Therefore, our only choice is to * check if the lower file system supports the ->writepage, and if * not, return EINVAL (the same error that * generic_file_readonly_mmap returns in that case). */ lower_file = unionfs_lower_file(file); if (willwrite && !lower_file->f_mapping->a_ops->writepage) { err = -EINVAL; printk(KERN_ERR "unionfs: branch %d file system does not " "support writeable mmap\n", fbstart(file)); goto out; } /* * find and save lower vm_ops. * * XXX: the VFS should have a cleaner way of finding the lower vm_ops */ if (!UNIONFS_F(file)->lower_vm_ops) { err = lower_file->f_op->mmap(lower_file, vma); if (err) { printk(KERN_ERR "unionfs: lower mmap failed %d\n", err); goto out; } saved_vm_ops = vma->vm_ops; err = do_munmap(current->mm, vma->vm_start, vma->vm_end - vma->vm_start); if (err) { printk(KERN_ERR "unionfs: do_munmap failed %d\n", err); goto out; } } file->f_mapping->a_ops = &unionfs_dummy_aops; err = generic_file_mmap(file, vma); file->f_mapping->a_ops = &unionfs_aops; if (err) { printk(KERN_ERR "unionfs: generic_file_mmap failed %d\n", err); goto out; } vma->vm_ops = &unionfs_vm_ops; if (!UNIONFS_F(file)->lower_vm_ops) UNIONFS_F(file)->lower_vm_ops = saved_vm_ops; out: if (!err) { /* copyup could cause parent dir times to change */ unionfs_copy_attr_times(parent->d_inode); unionfs_check_file(file); } unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
/* * Don't grab the superblock read-lock in unionfs_permission, which prevents * a deadlock with the branch-management "add branch" code (which grabbed * the write lock). It is safe to not grab the read lock here, because even * with branch management taking place, there is no chance that * unionfs_permission, or anything it calls, will use stale branch * information. */ static int unionfs_permission(struct inode *inode, int mask) { struct inode *lower_inode = NULL; int err = 0; int bindex, bstart, bend; const int is_file = !S_ISDIR(inode->i_mode); const int write_mask = (mask & MAY_WRITE) && !(mask & MAY_READ); struct inode *inode_grabbed = igrab(inode); struct dentry *dentry = d_find_alias(inode); if (dentry) unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); if (!UNIONFS_I(inode)->lower_inodes) { if (is_file) /* dirs can be unlinked but chdir'ed to */ err = -ESTALE; /* force revalidate */ goto out; } bstart = ibstart(inode); bend = ibend(inode); if (unlikely(bstart < 0 || bend < 0)) { /* * With branch-management, we can get a stale inode here. * If so, we return ESTALE back to link_path_walk, which * would discard the dcache entry and re-lookup the * dentry+inode. This should be equivalent to issuing * __unionfs_d_revalidate_chain on nd.dentry here. */ if (is_file) /* dirs can be unlinked but chdir'ed to */ err = -ESTALE; /* force revalidate */ goto out; } for (bindex = bstart; bindex <= bend; bindex++) { lower_inode = unionfs_lower_inode_idx(inode, bindex); if (!lower_inode) continue; /* * check the condition for D-F-D underlying files/directories, * we don't have to check for files, if we are checking for * directories. */ if (!is_file && !S_ISDIR(lower_inode->i_mode)) continue; /* * We check basic permissions, but we ignore any conditions * such as readonly file systems or branches marked as * readonly, because those conditions should lead to a * copyup taking place later on. However, if user never had * access to the file, then no copyup could ever take place. */ err = __inode_permission(lower_inode, mask); if (err && err != -EACCES && err != EPERM && bindex > 0) { umode_t mode = lower_inode->i_mode; if ((is_robranch_super(inode->i_sb, bindex) || __is_rdonly(lower_inode)) && (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))) err = 0; if (IS_COPYUP_ERR(err)) err = 0; } /* * NFS HACK: NFSv2/3 return EACCES on readonly-exported, * locally readonly-mounted file systems, instead of EROFS * like other file systems do. So we have no choice here * but to intercept this and ignore it for NFS branches * marked readonly. Specifically, we avoid using NFS's own * "broken" ->permission method, and rely on * generic_permission() to do basic checking for us. */ if (err && err == -EACCES && is_robranch_super(inode->i_sb, bindex) && lower_inode->i_sb->s_magic == NFS_SUPER_MAGIC) err = generic_permission(lower_inode, mask, NULL); /* * The permissions are an intersection of the overall directory * permissions, so we fail if one fails. */ if (err) goto out; /* only the leftmost file matters. */ if (is_file || write_mask) { if (is_file && write_mask) { err = get_write_access(lower_inode); if (!err) put_write_access(lower_inode); } break; } } /* sync times which may have changed (asynchronously) below */ unionfs_copy_attr_times(inode); out: unionfs_check_inode(inode); if (dentry) { unionfs_unlock_dentry(dentry); dput(dentry); } iput(inode_grabbed); return err; }
/* * This is not meant to be a generic repositioning function. If you do * things that aren't supported, then we return EINVAL. * * What is allowed: * (1) seeking to the same position that you are currently at * This really has no effect, but returns where you are. * (2) seeking to the beginning of the file * This throws out all state, and lets you begin again. */ static loff_t unionfs_dir_llseek(struct file *file, loff_t offset, int origin) { struct unionfs_dir_state *rdstate; struct dentry *dentry = file->f_path.dentry; struct dentry *parent; loff_t err; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); err = unionfs_file_revalidate(file, parent, false); if (unlikely(err)) goto out; rdstate = UNIONFS_F(file)->rdstate; /* * we let users seek to their current position, but not anywhere * else. */ if (!offset) { switch (origin) { case SEEK_SET: if (rdstate) { free_rdstate(rdstate); UNIONFS_F(file)->rdstate = NULL; } init_rdstate(file); err = 0; break; case SEEK_CUR: err = file->f_pos; break; case SEEK_END: /* Unsupported, because we would break everything. */ err = -EINVAL; break; } } else { switch (origin) { case SEEK_SET: if (rdstate) { if (offset == rdstate2offset(rdstate)) err = offset; else if (file->f_pos == DIREOF) err = DIREOF; else err = -EINVAL; } else { struct inode *inode; inode = dentry->d_inode; rdstate = find_rdstate(inode, offset); if (rdstate) { UNIONFS_F(file)->rdstate = rdstate; err = rdstate->offset; } else { err = -EINVAL; } } break; case SEEK_CUR: case SEEK_END: /* Unsupported, because we would break everything. */ err = -EINVAL; break; } } out: if (!err) unionfs_check_file(file); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
static int unionfs_setattr(struct dentry *dentry, struct iattr *ia) { int err = 0; struct dentry *lower_dentry; struct dentry *parent; struct inode *inode; struct inode *lower_inode; int bstart, bend, bindex; loff_t size; struct iattr lower_ia; /* check if user has permission to change inode */ err = inode_change_ok(dentry->d_inode, ia); if (err) goto out_err; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); if (unlikely(!__unionfs_d_revalidate(dentry, parent, false, 0))) { err = -ESTALE; goto out; } bstart = dbstart(dentry); bend = dbend(dentry); inode = dentry->d_inode; /* * mode change is for clearing setuid/setgid. Allow lower filesystem * to reinterpret it in its own way. */ if (ia->ia_valid & (ATTR_KILL_SUID | ATTR_KILL_SGID)) ia->ia_valid &= ~ATTR_MODE; lower_dentry = unionfs_lower_dentry(dentry); if (!lower_dentry) { /* should never happen after above revalidate */ err = -EINVAL; goto out; } /* * Get the lower inode directly from lower dentry, in case ibstart * is -1 (which happens when the file is open but unlinked. */ lower_inode = lower_dentry->d_inode; /* check if user has permission to change lower inode */ err = inode_change_ok(lower_inode, ia); if (err) goto out; /* copyup if the file is on a read only branch */ if (is_robranch_super(dentry->d_sb, bstart) || __is_rdonly(lower_inode)) { /* check if we have a branch to copy up to */ if (bstart <= 0) { err = -EACCES; goto out; } if (ia->ia_valid & ATTR_SIZE) size = ia->ia_size; else size = i_size_read(inode); /* copyup to next available branch */ for (bindex = bstart - 1; bindex >= 0; bindex--) { err = copyup_dentry(parent->d_inode, dentry, bstart, bindex, dentry->d_name.name, dentry->d_name.len, NULL, size); if (!err) break; } if (err) goto out; /* get updated lower_dentry/inode after copyup */ lower_dentry = unionfs_lower_dentry(dentry); lower_inode = unionfs_lower_inode(inode); /* * check for whiteouts in writeable branch, and remove them * if necessary. */ if (lower_dentry) { err = check_unlink_whiteout(dentry, lower_dentry, bindex); if (err > 0) /* ignore if whiteout found and removed */ err = 0; } } /* * If shrinking, first truncate upper level to cancel writing dirty * pages beyond the new eof; and also if its' maxbytes is more * limiting (fail with -EFBIG before making any change to the lower * level). There is no need to vmtruncate the upper level * afterwards in the other cases: we fsstack_copy_inode_size from * the lower level. */ if (ia->ia_valid & ATTR_SIZE) { err = inode_newsize_ok(inode, ia->ia_size); if (err) goto out; truncate_setsize(inode, ia->ia_size); } /* notify the (possibly copied-up) lower inode */ /* * Note: we use lower_dentry->d_inode, because lower_inode may be * unlinked (no inode->i_sb and i_ino==0. This happens if someone * tries to open(), unlink(), then ftruncate() a file. */ /* prepare our own lower struct iattr (with our own lower file) */ memcpy(&lower_ia, ia, sizeof(lower_ia)); if (ia->ia_valid & ATTR_FILE) { lower_ia.ia_file = unionfs_lower_file(ia->ia_file); BUG_ON(!lower_ia.ia_file); // XXX? } mutex_lock(&lower_dentry->d_inode->i_mutex); err = notify_change(lower_dentry, &lower_ia); mutex_unlock(&lower_dentry->d_inode->i_mutex); if (err) goto out; /* get attributes from the first lower inode */ if (ibstart(inode) >= 0) unionfs_copy_attr_all(inode, lower_inode); /* * unionfs_copy_attr_all will copy the lower times to our inode if * the lower ones are newer (useful for cache coherency). However, * ->setattr is the only place in which we may have to copy the * lower inode times absolutely, to support utimes(2). */ if (ia->ia_valid & ATTR_MTIME_SET) inode->i_mtime = lower_inode->i_mtime; if (ia->ia_valid & ATTR_CTIME) inode->i_ctime = lower_inode->i_ctime; if (ia->ia_valid & ATTR_ATIME_SET) inode->i_atime = lower_inode->i_atime; fsstack_copy_inode_size(inode, lower_inode); out: if (!err) unionfs_check_dentry(dentry); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); out_err: return err; }
int unionfs_open(struct inode *inode, struct file *file) { int err = 0; struct file *lower_file = NULL; struct dentry *dentry = file->f_path.dentry; struct dentry *parent; int bindex = 0, bstart = 0, bend = 0; int size; int valid = 0; unionfs_read_lock(inode->i_sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); /* don't open unhashed/deleted files */ if (d_deleted(dentry)) { err = -ENOENT; goto out_nofree; } /* XXX: should I change 'false' below to the 'willwrite' flag? */ valid = __unionfs_d_revalidate(dentry, parent, false); if (unlikely(!valid)) { err = -ESTALE; goto out_nofree; } file->private_data = kzalloc(sizeof(struct unionfs_file_info), GFP_KERNEL); if (unlikely(!UNIONFS_F(file))) { err = -ENOMEM; goto out_nofree; } fbstart(file) = -1; fbend(file) = -1; atomic_set(&UNIONFS_F(file)->generation, atomic_read(&UNIONFS_I(inode)->generation)); size = sizeof(struct file *) * sbmax(inode->i_sb); UNIONFS_F(file)->lower_files = kzalloc(size, GFP_KERNEL); if (unlikely(!UNIONFS_F(file)->lower_files)) { err = -ENOMEM; goto out; } size = sizeof(int) * sbmax(inode->i_sb); UNIONFS_F(file)->saved_branch_ids = kzalloc(size, GFP_KERNEL); if (unlikely(!UNIONFS_F(file)->saved_branch_ids)) { err = -ENOMEM; goto out; } bstart = fbstart(file) = dbstart(dentry); bend = fbend(file) = dbend(dentry); /* * open all directories and make the unionfs file struct point to * these lower file structs */ if (S_ISDIR(inode->i_mode)) err = __open_dir(inode, file); /* open a dir */ else err = __open_file(inode, file, parent); /* open a file */ /* freeing the allocated resources, and fput the opened files */ if (err) { for (bindex = bstart; bindex <= bend; bindex++) { lower_file = unionfs_lower_file_idx(file, bindex); if (!lower_file) continue; branchput(dentry->d_sb, bindex); /* fput calls dput for lower_dentry */ fput(lower_file); } } out: if (err) { kfree(UNIONFS_F(file)->lower_files); kfree(UNIONFS_F(file)->saved_branch_ids); kfree(UNIONFS_F(file)); } out_nofree: if (!err) { unionfs_postcopyup_setmnt(dentry); unionfs_copy_attr_times(inode); unionfs_check_file(file); unionfs_check_inode(inode); } unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(inode->i_sb); return err; }
/* This function replicates the directory structure upto given dentry * in the bindex branch. */ static struct dentry *create_parents_named(struct inode *dir, struct dentry *dentry, const char *name, int bindex) { int err; struct dentry *child_dentry; struct dentry *parent_dentry; struct dentry *hidden_parent_dentry = NULL; struct dentry *hidden_dentry = NULL; const char *childname; unsigned int childnamelen; int old_kmalloc_size; int kmalloc_size; int num_dentry; int count; int old_bstart; int old_bend; struct dentry **path = NULL; struct dentry **tmp_path; struct super_block *sb; verify_locked(dentry); /* There is no sense allocating any less than the minimum. */ kmalloc_size = malloc_sizes[0].cs_size; num_dentry = kmalloc_size / sizeof(struct dentry *); if ((err = is_robranch_super(dir->i_sb, bindex))) { hidden_dentry = ERR_PTR(err); goto out; } old_bstart = dbstart(dentry); old_bend = dbend(dentry); hidden_dentry = ERR_PTR(-ENOMEM); path = kzalloc(kmalloc_size, GFP_KERNEL); if (!path) goto out; /* assume the negative dentry of unionfs as the parent dentry */ parent_dentry = dentry; count = 0; /* This loop finds the first parent that exists in the given branch. * We start building the directory structure from there. At the end * of the loop, the following should hold: * - child_dentry is the first nonexistent child * - parent_dentry is the first existent parent * - path[0] is the = deepest child * - path[count] is the first child to create */ do { child_dentry = parent_dentry; /* find the parent directory dentry in unionfs */ parent_dentry = child_dentry->d_parent; unionfs_lock_dentry(parent_dentry); /* find out the hidden_parent_dentry in the given branch */ hidden_parent_dentry = unionfs_lower_dentry_idx(parent_dentry, bindex); /* store the child dentry */ path[count++] = child_dentry; /* grow path table */ if (count == num_dentry) { old_kmalloc_size = kmalloc_size; kmalloc_size *= 2; num_dentry = kmalloc_size / sizeof(struct dentry *); tmp_path = kzalloc(kmalloc_size, GFP_KERNEL); if (!tmp_path) { hidden_dentry = ERR_PTR(-ENOMEM); goto out; } memcpy(tmp_path, path, old_kmalloc_size); kfree(path); path = tmp_path; tmp_path = NULL; } } while (!hidden_parent_dentry); count--; sb = dentry->d_sb; /* This is basically while(child_dentry != dentry). This loop is * horrible to follow and should be replaced with cleaner code. */ while (1) { /* get hidden parent dir in the current branch */ hidden_parent_dentry = unionfs_lower_dentry_idx(parent_dentry, bindex); unionfs_unlock_dentry(parent_dentry); /* init the values to lookup */ childname = child_dentry->d_name.name; childnamelen = child_dentry->d_name.len; if (child_dentry != dentry) { /* lookup child in the underlying file system */ hidden_dentry = lookup_one_len(childname, hidden_parent_dentry, childnamelen); if (IS_ERR(hidden_dentry)) goto out; } else { /* is the name a whiteout of the childname ? * lookup the whiteout child in the underlying file system */ hidden_dentry = lookup_one_len(name, hidden_parent_dentry, strlen(name)); if (IS_ERR(hidden_dentry)) goto out; /* Replace the current dentry (if any) with the new one. */ dput(unionfs_lower_dentry_idx(dentry, bindex)); unionfs_set_lower_dentry_idx(dentry, bindex, hidden_dentry); __cleanup_dentry(dentry, bindex, old_bstart, old_bend); break; } if (hidden_dentry->d_inode) { /* since this already exists we dput to avoid * multiple references on the same dentry */ dput(hidden_dentry); } else { struct sioq_args args; /* its a negative dentry, create a new dir */ hidden_parent_dentry = lock_parent(hidden_dentry); args.mkdir.parent = hidden_parent_dentry->d_inode; args.mkdir.dentry = hidden_dentry; args.mkdir.mode = child_dentry->d_inode->i_mode; run_sioq(__unionfs_mkdir, &args); err = args.err; if (!err) err = copyup_permissions(dir->i_sb, child_dentry, hidden_dentry); unlock_dir(hidden_parent_dentry); if (err) { dput(hidden_dentry); hidden_dentry = ERR_PTR(err); goto out; } } __set_inode(child_dentry, hidden_dentry, bindex); __set_dentry(child_dentry, hidden_dentry, bindex); parent_dentry = child_dentry; child_dentry = path[--count]; } out: kfree(path); return hidden_dentry; }
/* * release all lower object references & free the file info structure * * No need to grab sb info's rwsem. */ int unionfs_file_release(struct inode *inode, struct file *file) { struct file *lower_file = NULL; struct unionfs_file_info *fileinfo; struct unionfs_inode_info *inodeinfo; struct super_block *sb = inode->i_sb; struct dentry *dentry = file->f_path.dentry; struct dentry *parent; int bindex, bstart, bend; int fgen, err = 0; /* * Since mm/memory.c:might_fault() (under PROVE_LOCKING) was * modified in 2.6.29-rc1 to call might_lock_read on mmap_sem, this * has been causing false positives in file system stacking layers. * In particular, our ->mmap is called after sys_mmap2 already holds * mmap_sem, then we lock our own mutexes; but earlier, it's * possible for lockdep to have locked our mutexes first, and then * we call a lower ->readdir which could call might_fault. The * different ordering of the locks is what lockdep complains about * -- unnecessarily. Therefore, we have no choice but to tell * lockdep to temporarily turn off lockdep here. Note: the comments * inside might_sleep also suggest that it would have been * nicer to only annotate paths that needs that might_lock_read. */ lockdep_off(); unionfs_read_lock(sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); /* * We try to revalidate, but the VFS ignores return return values * from file->release, so we must always try to succeed here, * including to do the kfree and dput below. So if revalidation * failed, all we can do is print some message and keep going. */ err = unionfs_file_revalidate(file, parent, UNIONFS_F(file)->wrote_to_file); if (!err) unionfs_check_file(file); fileinfo = UNIONFS_F(file); BUG_ON(file->f_path.dentry->d_inode != inode); inodeinfo = UNIONFS_I(inode); /* fput all the lower files */ fgen = atomic_read(&fileinfo->generation); bstart = fbstart(file); bend = fbend(file); for (bindex = bstart; bindex <= bend; bindex++) { lower_file = unionfs_lower_file_idx(file, bindex); if (lower_file) { unionfs_set_lower_file_idx(file, bindex, NULL); fput(lower_file); branchput(sb, bindex); } /* if there are no more refs to the dentry, dput it */ if (d_deleted(dentry)) { dput(unionfs_lower_dentry_idx(dentry, bindex)); unionfs_set_lower_dentry_idx(dentry, bindex, NULL); } } kfree(fileinfo->lower_files); kfree(fileinfo->saved_branch_ids); if (fileinfo->rdstate) { fileinfo->rdstate->access = jiffies; spin_lock(&inodeinfo->rdlock); inodeinfo->rdcount++; list_add_tail(&fileinfo->rdstate->cache, &inodeinfo->readdircache); mark_inode_dirty(inode); spin_unlock(&inodeinfo->rdlock); fileinfo->rdstate = NULL; } kfree(fileinfo); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(sb); lockdep_on(); return err; }
static int unionfs_readdir(struct file *file, void *dirent, filldir_t filldir) { int err = 0; struct file *lower_file = NULL; struct dentry *dentry = file->f_path.dentry; struct dentry *parent; struct inode *inode = NULL; struct unionfs_getdents_callback buf; struct unionfs_dir_state *uds; int bend; loff_t offset; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); err = unionfs_file_revalidate(file, parent, false); if (unlikely(err)) goto out; inode = dentry->d_inode; uds = UNIONFS_F(file)->rdstate; if (!uds) { if (file->f_pos == DIREOF) { goto out; } else if (file->f_pos > 0) { uds = find_rdstate(inode, file->f_pos); if (unlikely(!uds)) { err = -ESTALE; goto out; } UNIONFS_F(file)->rdstate = uds; } else { init_rdstate(file); uds = UNIONFS_F(file)->rdstate; } } bend = fbend(file); while (uds->bindex <= bend) { lower_file = unionfs_lower_file_idx(file, uds->bindex); if (!lower_file) { uds->bindex++; uds->dirpos = 0; continue; } /* prepare callback buffer */ buf.filldir_called = 0; buf.filldir_error = 0; buf.entries_written = 0; buf.dirent = dirent; buf.filldir = filldir; buf.rdstate = uds; buf.sb = inode->i_sb; /* Read starting from where we last left off. */ offset = vfs_llseek(lower_file, uds->dirpos, SEEK_SET); if (offset < 0) { err = offset; goto out; } err = vfs_readdir(lower_file, unionfs_filldir, &buf); /* Save the position for when we continue. */ offset = vfs_llseek(lower_file, 0, SEEK_CUR); if (offset < 0) { err = offset; goto out; } uds->dirpos = offset; /* Copy the atime. */ fsstack_copy_attr_atime(inode, lower_file->f_path.dentry->d_inode); if (err < 0) goto out; if (buf.filldir_error) break; if (!buf.entries_written) { uds->bindex++; uds->dirpos = 0; } } if (!buf.filldir_error && uds->bindex >= bend) { /* Save the number of hash entries for next time. */ UNIONFS_I(inode)->hashsize = uds->hashentries; free_rdstate(uds); UNIONFS_F(file)->rdstate = NULL; file->f_pos = DIREOF; } else { file->f_pos = rdstate2offset(uds); } out: if (!err) unionfs_check_file(file); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
struct dentry *unionfs_lookup_backend(struct dentry *dentry, struct nameidata *nd, 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; struct vfsmount *first_hidden_mnt = NULL; int locked_parent = 0; int locked_child = 0; int opaque; char *whname = NULL; const char *name; int namelen; /* 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(UNIONFS_D(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 = dget_parent(dentry); /* We never partial lookup the root directory. */ if (parent_dentry != dentry) { unionfs_lock_dentry(parent_dentry); locked_parent = 1; } else { dput(parent_dentry); parent_dentry = NULL; goto out; } 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; } for (bindex = bstart; bindex <= bend; bindex++) { hidden_dentry = unionfs_lower_dentry_idx(dentry, bindex); if (lookupmode == INTERPOSE_PARTIAL && hidden_dentry) continue; BUG_ON(hidden_dentry != NULL); hidden_dir_dentry = unionfs_lower_dentry_idx(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 + UNIONFS_WHLEN); if (IS_ERR(wh_hidden_dentry)) { dput(first_hidden_dentry); mntput(first_hidden_mnt); err = PTR_ERR(wh_hidden_dentry); goto out_free; } if (wh_hidden_dentry->d_inode) { /* We found a whiteout so lets give up. */ 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); mntput(first_hidden_mnt); goto out_free; } dput(wh_hidden_dentry); wh_hidden_dentry = NULL; /* Now do regular lookup; lookup foo */ nd->dentry = unionfs_lower_dentry_idx(dentry, bindex); /* FIXME: fix following line for mount point crossing */ nd->mnt = unionfs_lower_mnt_idx(parent_dentry, bindex); hidden_dentry = lookup_one_len_nd(name, hidden_dir_dentry, namelen, nd); if (IS_ERR(hidden_dentry)) { dput(first_hidden_dentry); mntput(first_hidden_mnt); 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; /* FIXME: following line needs to be changed * to allow mountpoint crossing */ first_hidden_mnt = mntget( unionfs_lower_mnt_idx(parent_dentry, bindex)); 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); unionfs_set_lower_dentry_idx(dentry, bindex, hidden_dentry); /* FIXME: the following line needs to get fixed to allow * mountpoint crossing */ unionfs_set_lower_mnt_idx(dentry, bindex, mntget(unionfs_lower_mnt_idx(parent_dentry, bindex))); set_dbend(dentry, bindex); /* update parent directory's atime with the bindex */ fsstack_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(unionfs_lower_dentry(dentry)->d_inode->i_mode)); continue; } opaque = is_opaque_dir(dentry, bindex); if (opaque < 0) { dput(first_hidden_dentry); mntput(first_hidden_mnt); err = opaque; goto out_free; } else 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) UNIONFS_I(dentry->d_inode)->stale = 1; goto out; } /* This should only happen if we found a whiteout. */ if (first_dentry_offset == -1) { nd->dentry = dentry; /* FIXME: fix following line for mount point crossing */ nd->mnt = unionfs_lower_mnt_idx(parent_dentry, bindex); first_hidden_dentry = lookup_one_len_nd(name, hidden_dir_dentry, namelen, nd); first_dentry_offset = bindex; if (IS_ERR(first_hidden_dentry)) { err = PTR_ERR(first_hidden_dentry); goto out; } /* FIXME: the following line needs to be changed to allow * mountpoint crossing */ first_hidden_mnt = mntget(unionfs_lower_mnt_idx(dentry, bindex)); } unionfs_set_lower_dentry_idx(dentry, first_dentry_offset, first_hidden_dentry); unionfs_set_lower_mnt_idx(dentry, first_dentry_offset, first_hidden_mnt); 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 & corresponding * vfsmount - throw it out. */ dput(first_hidden_dentry); mntput(first_hidden_mnt); /* 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; 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(unionfs_lower_dentry_idx(dentry, bindex)); mntput(unionfs_lower_mnt_idx(dentry, bindex)); } } kfree(UNIONFS_D(dentry)->lower_paths); UNIONFS_D(dentry)->lower_paths = NULL; set_dbstart(dentry, -1); set_dbend(dentry, -1); out: if (!err && UNIONFS_D(dentry)) { BUG_ON(dbend(dentry) > UNIONFS_D(dentry)->bcount); BUG_ON(dbend(dentry) > sbmax(dentry->d_sb)); BUG_ON(dbstart(dentry) < 0); } kfree(whname); if (locked_parent) unionfs_unlock_dentry(parent_dentry); dput(parent_dentry); if (locked_child) unionfs_unlock_dentry(dentry); return ERR_PTR(err); }