int unionfs_rmdir(struct inode *dir, struct dentry *dentry) { int err = 0; struct unionfs_dir_state *namelist = NULL; print_entry_location(); lock_dentry(dentry); fist_print_dentry("IN unionfs_rmdir: ", dentry); /* check if this unionfs directory is empty or not */ err = check_empty(dentry, &namelist); if (err) { goto out; } if (IS_SET(dir->i_sb, DELETE_WHITEOUT)) { /* Delete the first directory. */ 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; } } else { /* delete all. */ err = unionfs_rmdir_all(dir, dentry, namelist); } out: /* call d_drop so the system "forgets" about us */ if (!err) d_drop(dentry); if (namelist) free_rdstate(namelist); unlock_dentry(dentry); print_exit_status(err); return err; }
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; }
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; }
/* * 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; }
/* 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; }
int unionfs_rmdir(struct inode *dir, struct dentry *dentry) { int err = 0; struct unionfs_dir_state *namelist = NULL; print_entry_location(); lock_dentry(dentry); fist_print_dentry("IN unionfs_rmdir: ", dentry); /* check if this unionfs directory is empty or not */ err = check_empty(dentry, &namelist); if (err) { #if 0 /* vfs_rmdir(our caller) unhashed the dentry. This will recover * the Unionfs inode number for the directory itself, but the * children are already lost. It seems that tmpfs manages its * way around this by upping the refcount on everything. * * Even if we do this, we still lose the inode numbers of the * children. The best way to fix this is to fix the VFS (or * use persistent inode maps). */ if (d_unhashed(dentry)) d_rehash(dentry); #endif goto out; } #ifdef UNIONFS_DELETE_ALL if (IS_SET(dir->i_sb, DELETE_ALL)) { /* delete all. */ err = unionfs_rmdir_all(dir, dentry, namelist); } else { /* Delete the first directory. */ #endif 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; } #ifdef UNIONFS_DELETE_ALL } #endif out: /* call d_drop so the system "forgets" about us */ if (!err) d_drop(dentry); if (namelist) free_rdstate(namelist); unlock_dentry(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; }