static int unionfs_unlink_whiteout(struct inode *dir, struct dentry *dentry) { struct dentry *hidden_dentry; struct dentry *hidden_dir_dentry; int bindex; int err = 0; print_entry_location(); if ((err = unionfs_partial_lookup(dentry))) goto out; bindex = dbstart(dentry); hidden_dentry = dtohd_index(dentry, bindex); if (!hidden_dentry) goto out; hidden_dir_dentry = lock_parent(hidden_dentry); /* avoid destroying the hidden inode if the file is in use */ DGET(hidden_dentry); if (!(err = is_robranch_super(dentry->d_sb, bindex))) err = vfs_unlink(hidden_dir_dentry->d_inode, hidden_dentry); DPUT(hidden_dentry); fist_copy_attr_times(dir, hidden_dir_dentry->d_inode); unlock_dir(hidden_dir_dentry); if (err) { if (!IS_COPYUP_ERR(err)) goto out; } if (err) { if (dbstart(dentry) == 0) { goto out; } err = create_whiteout(dentry, dbstart(dentry) - 1); } else if (dbopaque(dentry) != -1) { /* There is a hidden lower-priority file with the same name. */ err = create_whiteout(dentry, dbopaque(dentry)); } else { err = create_whiteout(dentry, dbstart(dentry)); } out: if (!err) dentry->d_inode->i_nlink--; /* We don't want to leave negative leftover dentries for revalidate. */ if (!err && (dbopaque(dentry) != -1)) update_bstart(dentry); print_exit_status(err); return err; }
/* * return to user-space the branch indices containing the file in question * * We use fd_set and therefore we are limited to the number of the branches * to FD_SETSIZE, which is currently 1024 - plenty for most people */ static int unionfs_ioctl_queryfile(struct file *file, struct dentry *parent, unsigned int cmd, unsigned long arg) { int err = 0; fd_set branchlist; int bstart = 0, bend = 0, bindex = 0; int orig_bstart, orig_bend; struct dentry *dentry, *lower_dentry; struct vfsmount *mnt; dentry = file->f_path.dentry; orig_bstart = dbstart(dentry); orig_bend = dbend(dentry); err = unionfs_partial_lookup(dentry, parent); if (err) goto out; bstart = dbstart(dentry); bend = dbend(dentry); FD_ZERO(&branchlist); for (bindex = bstart; bindex <= bend; bindex++) { lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); if (!lower_dentry) continue; if (likely(lower_dentry->d_inode)) FD_SET(bindex, &branchlist); /* purge any lower objects after partial_lookup */ if (bindex < orig_bstart || bindex > orig_bend) { dput(lower_dentry); unionfs_set_lower_dentry_idx(dentry, bindex, NULL); iput(unionfs_lower_inode_idx(dentry->d_inode, bindex)); unionfs_set_lower_inode_idx(dentry->d_inode, bindex, NULL); mnt = unionfs_lower_mnt_idx(dentry, bindex); if (!mnt) continue; unionfs_mntput(dentry, bindex); unionfs_set_lower_mnt_idx(dentry, bindex, NULL); } } /* restore original dentry's offsets */ dbstart(dentry) = orig_bstart; dbend(dentry) = orig_bend; ibstart(dentry->d_inode) = orig_bstart; ibend(dentry->d_inode) = orig_bend; err = copy_to_user((void __user *)arg, &branchlist, sizeof(fd_set)); if (unlikely(err)) err = -EFAULT; out: return err < 0 ? err : bend; }
int unionfs_ioctl_queryfile(struct file *file, unsigned int cmd, unsigned long arg) { int err = 0; fd_set branchlist; int bstart = 0, bend = 0, bindex = 0; struct dentry *dentry, *hidden_dentry; print_entry_location(); dentry = file->f_dentry; lock_dentry(dentry); if ((err = unionfs_partial_lookup(dentry))) goto out; bstart = dbstart(dentry); bend = dbend(dentry); FD_ZERO(&branchlist); for (bindex = bstart; bindex <= bend; bindex++) { hidden_dentry = dtohd_index(dentry, bindex); if (!hidden_dentry) continue; if (hidden_dentry->d_inode) FD_SET(bindex, &branchlist); } err = copy_to_user((void *)arg, &branchlist, sizeof(fd_set)); if (err) { err = -EFAULT; goto out; } out: unlock_dentry(dentry); err = err < 0 ? err : bend; print_exit_status(err); return (err); }
/* Is a directory logically empty? */ int check_empty(struct dentry *dentry, struct dentry *parent, struct unionfs_dir_state **namelist) { int err = 0; struct dentry *lower_dentry = NULL; struct vfsmount *mnt; struct super_block *sb; struct file *lower_file; struct unionfs_rdutil_callback *buf = NULL; int bindex, bstart, bend, bopaque; sb = dentry->d_sb; BUG_ON(!S_ISDIR(dentry->d_inode->i_mode)); err = unionfs_partial_lookup(dentry, parent); if (err) goto out; bstart = dbstart(dentry); bend = dbend(dentry); bopaque = dbopaque(dentry); if (0 <= bopaque && bopaque < bend) bend = bopaque; buf = kmalloc(sizeof(struct unionfs_rdutil_callback), GFP_KERNEL); if (unlikely(!buf)) { err = -ENOMEM; goto out; } buf->err = 0; buf->mode = RD_CHECK_EMPTY; buf->rdstate = alloc_rdstate(dentry->d_inode, bstart); if (unlikely(!buf->rdstate)) { err = -ENOMEM; goto out; } /* Process the lower directories with rdutil_callback as a filldir. */ for (bindex = bstart; bindex <= bend; bindex++) { lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); if (!lower_dentry) continue; if (!lower_dentry->d_inode) continue; if (!S_ISDIR(lower_dentry->d_inode->i_mode)) continue; dget(lower_dentry); mnt = unionfs_mntget(dentry, bindex); branchget(sb, bindex); lower_file = dentry_open(lower_dentry, mnt, O_RDONLY); if (IS_ERR(lower_file)) { err = PTR_ERR(lower_file); branchput(sb, bindex); goto out; } do { buf->filldir_called = 0; buf->rdstate->bindex = bindex; err = vfs_readdir(lower_file, readdir_util_callback, buf); if (buf->err) err = buf->err; } while ((err >= 0) && buf->filldir_called); /* fput calls dput for lower_dentry */ fput(lower_file); branchput(sb, bindex); if (err < 0) goto out; } out: if (buf) { if (namelist && !err) *namelist = buf->rdstate; else if (buf->rdstate) free_rdstate(buf->rdstate); kfree(buf); } return err; }
static int unionfs_unlink_all(struct inode *dir, struct dentry *dentry) { struct dentry *hidden_dentry; struct dentry *hidden_dir_dentry; int bstart, bend, bindex; int err = 0; int global_err = 0; print_entry_location(); if ((err = unionfs_partial_lookup(dentry))) goto out; bstart = dbstart(dentry); bend = dbend(dentry); for (bindex = bend; bindex >= bstart; bindex--) { hidden_dentry = dtohd_index(dentry, bindex); if (!hidden_dentry) continue; hidden_dir_dentry = lock_parent(hidden_dentry); /* avoid destroying the hidden inode if the file is in use */ DGET(hidden_dentry); if (!(err = is_robranch_super(dentry->d_sb, bindex))) err = vfs_unlink(hidden_dir_dentry->d_inode, hidden_dentry); DPUT(hidden_dentry); fist_copy_attr_times(dir, hidden_dir_dentry->d_inode); unlock_dir(hidden_dir_dentry); if (err) { /* passup the last error we got */ if (!IS_COPYUP_ERR(err)) goto out; global_err = err; } } /* check if encountered error in the above loop */ if (global_err) { /* If we failed in the leftmost branch, then err will be set * and we should move one over to create the whiteout. * Otherwise, we should try in the leftmost branch. */ if (err) { if (dbstart(dentry) == 0) { goto out; } err = create_whiteout(dentry, dbstart(dentry) - 1); } else { err = create_whiteout(dentry, dbstart(dentry)); } } else if (dbopaque(dentry) != -1) { /* There is a hidden lower-priority file with the same name. */ err = create_whiteout(dentry, dbopaque(dentry)); } out: /* propagate number of hard-links */ if (dentry->d_inode->i_nlink != 0) { dentry->d_inode->i_nlink = get_nlinks(dentry->d_inode); if (!err && global_err) dentry->d_inode->i_nlink--; } /* We don't want to leave negative leftover dentries for revalidate. */ if (!err && (global_err || dbopaque(dentry) != -1)) update_bstart(dentry); print_exit_status(err); return err; }
static int unionfs_rmdir_all(struct inode *dir, struct dentry *dentry, struct unionfs_dir_state *namelist) { struct dentry *hidden_dentry; struct dentry *hidden_dir_dentry; int bstart, bend, bindex; int err = 0; int global_err = 0; print_entry_location(); fist_print_dentry("IN unionfs_rmdir_all: ", dentry); err = unionfs_partial_lookup(dentry); if (err) { fist_dprint(8, "Error in partial lookup\n"); goto out; } bstart = dbstart(dentry); bend = dbend(dentry); for (bindex = bend; bindex >= bstart; bindex--) { hidden_dentry = dtohd_index(dentry, bindex); if (!hidden_dentry) continue; hidden_dir_dentry = lock_parent(hidden_dentry); if (S_ISDIR(hidden_dentry->d_inode->i_mode)) { delete_whiteouts(dentry, bindex, namelist); if (!(err = is_robranch_super(dentry->d_sb, bindex))) { err = vfs_rmdir(hidden_dir_dentry->d_inode, hidden_dentry); } } else { err = -EISDIR; } fist_copy_attr_times(dir, hidden_dir_dentry->d_inode); unlock_dir(hidden_dir_dentry); if (err) { int local_err = unionfs_refresh_hidden_dentry(dentry, bindex); if (local_err) { err = local_err; goto out; } if (!IS_COPYUP_ERR(err) && err != -ENOTEMPTY && err != -EISDIR) goto out; global_err = err; } } /* check if encountered error in the above loop */ if (global_err) { /* If we failed in the leftmost branch, then err will be set and we should * move one over to create the whiteout. Otherwise, we should try in the * leftmost branch. */ if (err) { if (dbstart(dentry) == 0) { goto out; } err = create_whiteout(dentry, dbstart(dentry) - 1); } else { err = create_whiteout(dentry, dbstart(dentry)); } } out: /* propagate number of hard-links */ dentry->d_inode->i_nlink = get_nlinks(dentry->d_inode); fist_print_dentry("OUT unionfs_rmdir_all: ", dentry); print_exit_status(err); return err; }
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; print_entry_location(); double_lock_dentry(old_dentry, new_dentry); fist_checkinode(old_dir, "unionfs_rename-old_dir"); fist_checkinode(new_dir, "unionfs_rename-new_dir"); fist_print_dentry("IN: unionfs_rename, old_dentry", old_dentry); fist_print_dentry("IN: unionfs_rename, new_dentry", new_dentry); if (!S_ISDIR(old_dentry->d_inode->i_mode)) err = unionfs_partial_lookup(old_dentry); else err = may_rename_dir(old_dentry); if (err) goto out; err = unionfs_partial_lookup(new_dentry); if (err) goto out; /* * if new_dentry is already hidden because of whiteout, * simply override it even if the whiteouted dir is not empty. */ wh_dentry = lookup_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(old_dentry->d_inode->i_mode)) { /* check if this unionfs directory is empty or not */ err = check_empty(new_dentry, NULL); if (err) goto out; /* Handle the case where we are overwriting directories * that are not really empty because of whiteout or * non-whiteout entries. */ } } #ifdef UNIONFS_DELETE_ALL if (IS_SET(old_dir->i_sb, DELETE_ALL)) err = unionfs_rename_all(old_dir, old_dentry, new_dir, new_dentry); else #endif err = unionfs_rename_whiteout(old_dir, old_dentry, new_dir, new_dentry); out: fist_checkinode(new_dir, "post unionfs_rename-new_dir"); fist_print_dentry("OUT: unionfs_rename, old_dentry", old_dentry); if (err) { /* clear the new_dentry stuff created */ d_drop(new_dentry); } else { /* force re-lookup since the dir on ro branch is not renamed, and hidden dentries still indicate the un-renamed ones. */ if (S_ISDIR(old_dentry->d_inode->i_mode)) atomic_dec(&dtopd(old_dentry)->udi_generation); fist_print_dentry("OUT: unionfs_rename, new_dentry", new_dentry); } unlock_dentry(new_dentry); unlock_dentry(old_dentry); print_exit_status(err); return err; }
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; double_lock_dentry(old_dentry, new_dentry); if (!S_ISDIR(old_dentry->d_inode->i_mode)) err = unionfs_partial_lookup(old_dentry); else err = may_rename_dir(old_dentry); if (err) goto out; err = unionfs_partial_lookup(new_dentry); if (err) goto out; /* * if new_dentry is already hidden because of whiteout, * simply override it even if the whiteouted dir is not empty. */ wh_dentry = lookup_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; /* check if this unionfs directory is empty or not */ err = check_empty(new_dentry, &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, new_dir, new_dentry); out: if (err) /* clear the new_dentry stuff created */ d_drop(new_dentry); else /* force re-lookup since the dir on ro branch is not renamed, and hidden dentries still indicate the un-renamed ones. */ if (S_ISDIR(old_dentry->d_inode->i_mode)) atomic_dec(&UNIONFS_D(old_dentry)->generation); unionfs_unlock_dentry(new_dentry); unionfs_unlock_dentry(old_dentry); return err; }
/* Is a directory logically empty? */ int check_empty(struct dentry *dentry, struct dentry *parent, struct unionfs_dir_state **namelist) { int err = 0; struct dentry *lower_dentry = NULL; struct vfsmount *mnt; struct super_block *sb; struct file *lower_file; struct unionfs_rdutil_callback buf = { .ctx.actor = readdir_util_callback, }; int bindex, bstart, bend, bopaque; struct path path; sb = dentry->d_sb; BUG_ON(!S_ISDIR(dentry->d_inode->i_mode)); err = unionfs_partial_lookup(dentry, parent); if (err) goto out; bstart = dbstart(dentry); bend = dbend(dentry); bopaque = dbopaque(dentry); if (0 <= bopaque && bopaque < bend) bend = bopaque; buf.err = 0; buf.filldir_called = 0; buf.mode = RD_CHECK_EMPTY; buf.ctx.pos = 0; /* XXX: needed?! */ buf.rdstate = alloc_rdstate(dentry->d_inode, bstart); if (unlikely(!buf.rdstate)) { err = -ENOMEM; goto out; } /* Process the lower directories with rdutil_callback as a filldir. */ for (bindex = bstart; bindex <= bend; bindex++) { lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); if (!lower_dentry) continue; if (!lower_dentry->d_inode) continue; if (!S_ISDIR(lower_dentry->d_inode->i_mode)) continue; dget(lower_dentry); mnt = unionfs_mntget(dentry, bindex); branchget(sb, bindex); path.dentry = lower_dentry; path.mnt = mnt; lower_file = dentry_open(&path, O_RDONLY, current_cred()); path_put(&path); if (IS_ERR(lower_file)) { err = PTR_ERR(lower_file); branchput(sb, bindex); goto out; } do { buf.filldir_called = 0; buf.rdstate->bindex = bindex; err = iterate_dir(lower_file, &buf.ctx); if (buf.err) err = buf.err; } while ((err >= 0) && buf.filldir_called); /* fput calls dput for lower_dentry */ fput(lower_file); branchput(sb, bindex); if (err < 0) goto out; } out: if (namelist && !err) *namelist = buf.rdstate; else if (buf.rdstate) free_rdstate(buf.rdstate); 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; }