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 = GET_PARENT(dentry); lock_dentry(parent); bstart = dbstart(parent); bend = dbend(parent); wh_dentry = ERR_PTR(-ENOENT); for (bindex = bstart; bindex <= bend; bindex++) { hidden_parent = dtohd_index(parent, bindex); if (!hidden_parent) continue; wh_dentry = LOOKUP_ONE_LEN(whname, hidden_parent, dentry->d_name.len + WHLEN); if (IS_ERR(wh_dentry)) continue; if (wh_dentry->d_inode) break; DPUT(wh_dentry); wh_dentry = ERR_PTR(-ENOENT); } unlock_dentry(parent); DPUT(parent); KFREE(whname); return wh_dentry; }
/* The rest of these are utility functions for lookup. */ static int is_opaque_dir(struct dentry *dentry, int bindex) { int err = 0; struct dentry *hidden_dentry; struct dentry *wh_hidden_dentry; struct inode *hidden_inode; uid_t saved_uid = current->fsuid; gid_t saved_gid = current->fsgid; print_entry_location(); hidden_dentry = dtohd_index(dentry, bindex); hidden_inode = hidden_dentry->d_inode; BUG_ON(!S_ISDIR(hidden_inode->i_mode)); current->fsuid = hidden_inode->i_uid; current->fsgid = hidden_inode->i_gid; wh_hidden_dentry = LOOKUP_ONE_LEN(UNIONFS_DIR_OPAQUE, hidden_dentry, sizeof(UNIONFS_DIR_OPAQUE) - 1); current->fsuid = saved_uid; current->fsgid = saved_gid; if (IS_ERR(wh_hidden_dentry)) { err = PTR_ERR(wh_hidden_dentry); fist_dprint(1, "LOOKUP_ONE_LEN returned: %d\n", err); goto out; } if (wh_hidden_dentry->d_inode) err = 1; DPUT(wh_hidden_dentry); out: print_exit_status(err); return err; }
/* * Rename all occurences of source except for the leftmost destination */ static int __rename_all(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, fd_set * success_mask, struct rename_info *info) { int bindex; int err = 0; print_entry_location(); /* Loop through all the branches from right to left and rename all * instances of source to destination, except the leftmost destination */ for (bindex = info->old_bend; bindex >= info->old_bstart; bindex--) { /* We don't rename if there is no source. */ if (dtohd_index(old_dentry, bindex) == NULL) continue; /* we rename the bstart of destination only at the last of * all operations, so that we don't lose it on error */ if (info->clobber && (bindex == info->new_bstart)) continue; DPUT(info->wh_old); info->bwh_old = bindex; /* We shouldn't have a handle on this if there is no inode. */ err = do_rename(old_dir, old_dentry, new_dir, new_dentry, bindex, &info->wh_old); if (!err) { /* For reverting. */ FD_SET(bindex, success_mask); /* So we know not to copyup on failures the right */ info->rename_ok = bindex; } else if (IS_COPYUP_ERR(err)) { if (info->isdir) { err = -EXDEV; break; } /* we need a whiteout... */ info->do_whiteout = bindex - 1; if (bindex == info->old_bstart) /* ...and a copyup */ info->do_copyup = bindex - 1; err = 0; /* reset error */ } else break; /* error is set by do_rename */ } print_exit_status(err); return err; }
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; }
/* * Unlink all destinations (if they exist) to the left of the left-most * source */ static int __rename_all_unlink(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, struct rename_info *info) { int bindex; struct dentry *unlink_dentry; struct dentry *unlink_dir_dentry; int err = 0; print_entry_location(); for (bindex = info->old_bstart - 1; bindex > info->new_bstart; bindex--) { unlink_dentry = dtohd_index(new_dentry, bindex); if (!unlink_dentry) continue; /* lock, unlink if possible, copyup times, unlock */ unlink_dir_dentry = lock_parent(unlink_dentry); if (!(err = is_robranch_super(old_dir->i_sb, bindex))) err = vfs_unlink(unlink_dir_dentry->d_inode, unlink_dentry); fist_copy_attr_times(new_dentry->d_parent->d_inode, unlink_dir_dentry->d_inode); new_dentry->d_parent->d_inode->i_nlink = get_nlinks(new_dentry->d_parent->d_inode); unlock_dir(unlink_dir_dentry); if (!err) { if (bindex != info->new_bstart) { DPUT(unlink_dentry); set_dtohd_index(new_dentry, bindex, NULL); } } else if (IS_COPYUP_ERR(err)) { if (info->isdir) { err = -EXDEV; break; } info->do_copyup = bindex - 1; err = 0; /* reset error */ } else break; /* err is set by is_ro_branch_super or vfs_unlink */ } print_exit_status(err); return err; }
void unionfs_d_release(struct dentry *dentry) { struct dentry *hidden_dentry; int bindex, bstart, bend; print_entry_location(); /* 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. */ lock_dentry(dentry); __fist_print_dentry("unionfs_d_release IN dentry", dentry, 0); /* this could be a negative dentry, so check first */ if (!dtopd(dentry)) { fist_dprint(6, "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 */ /* the failed lookup has a dtohd_ptr set to null, but this is a better check */ fist_dprint(6, "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++) { hidden_dentry = dtohd_index(dentry, bindex); DPUT(hidden_dentry); set_dtohd_index(dentry, bindex, NULL); } /* free private data (unionfs_dentry_info) here */ KFREE(dtohd_ptr(dentry)); dtohd_ptr(dentry) = NULL; out_free: /* No need to unlock it, because it is disappeared. */ #ifdef TRACKLOCK printk("DESTROYLOCK:%p\n", dentry); #endif free_dentry_private_data(dtopd(dentry)); dtopd_lhs(dentry) = NULL; /* just to be safe */ out: print_exit_location(); }
void update_bstart(struct dentry *dentry) { int bindex; int bstart = dbstart(dentry); int bend = dbend(dentry); struct dentry *hidden_dentry; for (bindex = bstart; bindex <= bend; bindex++) { hidden_dentry = dtohd_index(dentry, bindex); if (!hidden_dentry) continue; if (hidden_dentry->d_inode) { set_dbstart(dentry, bindex); break; } DPUT(hidden_dentry); set_dtohd_index(dentry, bindex, NULL); } }
static int unionfs_rmdir_first(struct inode *dir, struct dentry *dentry, struct unionfs_dir_state *namelist) { int err; struct dentry *hidden_dentry; struct dentry *hidden_dir_dentry = NULL; print_entry_location(); fist_print_dentry("IN unionfs_rmdir_first: ", dentry); /* Here we need to remove whiteout entries. */ err = delete_whiteouts(dentry, dbstart(dentry), namelist); if (err) { goto out; } hidden_dentry = dtohd(dentry); PASSERT(hidden_dentry); 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(dentry))) { err = vfs_rmdir(hidden_dir_dentry->d_inode, hidden_dentry); } DPUT(hidden_dentry); fist_copy_attr_times(dir, hidden_dir_dentry->d_inode); /* propagate number of hard-links */ dentry->d_inode->i_nlink = get_nlinks(dentry->d_inode); out: if (hidden_dir_dentry) { unlock_dir(hidden_dir_dentry); } fist_print_dentry("OUT unionfs_rmdir_first: ", dentry); print_exit_status(err); return err; }
/* * The function is nasty, nasty, nasty, but so is rename. :( */ static int unionfs_rename_all(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry) { struct dentry *parent_dentry = NULL; int err = 0; int eio; /* These variables control error handling. */ fd_set success_mask; char *name = NULL; /* unfortunately, we have to resort to this, because dbstart/dbend would return different things in different place of the rename code */ struct rename_info info; info.rename_ok = FD_SETSIZE; /* The last rename that is ok. */ info.do_copyup = -1; /* Where we should start copyup. */ info.do_whiteout = -1; /* Where we should start whiteouts of the source. */ info.wh_old = NULL; info.bwh_old = -1; print_entry_location(); parent_dentry = old_dentry->d_parent; name = KMALLOC(old_dentry->d_name.len + 1, GFP_KERNEL); if (!name) { err = -ENOMEM; goto out; } strncpy(name, old_dentry->d_name.name, old_dentry->d_name.len + 1); info.new_bstart = dbstart(new_dentry); info.new_bend = dbend(new_dentry); info.old_bstart = dbstart(old_dentry); info.old_bend = dbend(old_dentry); BUG_ON(info.new_bstart < 0); BUG_ON(info.old_bstart < 0); /* The failure mask only can deal with FD_SETSIZE entries. */ BUG_ON(info.old_bend > FD_SETSIZE); BUG_ON(info.new_bend > FD_SETSIZE); FD_ZERO(&success_mask); /* Life is simpler if the dentry doesn't exist. */ info.clobber = (dtohd_index(new_dentry, info.new_bstart)->d_inode) ? 1 : 0; info.isdir = S_ISDIR(old_dentry->d_inode->i_mode); /* rename everything we can */ err = __rename_all(old_dir, old_dentry, new_dir, new_dentry, &success_mask, &info); if (err) goto revert; /* unlink destinations even further left */ err = __rename_all_unlink(old_dir, old_dentry, new_dir, new_dentry, &info); if (err) goto revert; if (info.clobber) { /* Now we need to handle the leftmost of the destination. */ err = __rename_all_clobber(old_dir, old_dentry, new_dir, new_dentry, &info); if (err) goto revert; } /* Copy up if necessary */ if (info.do_copyup != -1) { int bindex; for (bindex = info.do_copyup; bindex >= 0; bindex--) { err = copyup_dentry(old_dentry->d_parent->d_inode, old_dentry, info.old_bstart, bindex, NULL, old_dentry->d_inode->i_size); if (!err) { DPUT(info.wh_old); info.bwh_old = bindex; err = do_rename(old_dir, old_dentry, new_dir, new_dentry, bindex, &info.wh_old); break; } } } /* Create a whiteout for the source. */ if (info.do_whiteout != -1) { struct dentry *hidden_parent; BUG_ON(info.do_whiteout < 0 || !info.wh_old || IS_ERR(info.wh_old) || info.wh_old->d_inode || info.bwh_old < 0); hidden_parent = lock_parent(info.wh_old); err = vfs_create(hidden_parent->d_inode, info.wh_old, S_IRUGO, NULL); unlock_dir(hidden_parent); if (!err) set_dbopaque(old_dentry, info.bwh_old); else { /* We can't fix anything now, so we -EIO. */ printk(KERN_WARNING "We can't create a whiteout for the" "source in rename!\n"); err = -EIO; goto out; } } /* We are at the point where reverting doesn't happen. */ goto out; revert: /* something bad happened, try to revert */ eio = __rename_all_revert(old_dir, old_dentry, new_dir, new_dentry, &success_mask, &info); if (eio) err = eio; out: DPUT(info.wh_old); KFREE(name); print_exit_status(err); return err; }
/* * Finish off the rename, by either over writing the last destination or * unlinking the last destination to the left of us */ static int __rename_all_clobber(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, struct rename_info *info) { int err = 0; print_entry_location(); if (dtohd_index(old_dentry, info->new_bstart)) { /* rename the last source, knowing we're overwriting something */ DPUT(info->wh_old); info->bwh_old = info->new_bstart; err = do_rename(old_dir, old_dentry, new_dir, new_dentry, info->new_bstart, &info->wh_old); if (IS_COPYUP_ERR(err)) { if (info->isdir) { err = -EXDEV; goto out; } if (info->rename_ok > info->new_bstart) { if ((info->do_copyup == -1) || (info->new_bstart - 1 < info->do_copyup)) info->do_copyup = info->new_bstart - 1; } if ((info->do_whiteout == -1) || (info->new_bstart - 1 < info->do_whiteout)) { info->do_whiteout = info->new_bstart - 1; } err = 0; // reset error } } else if (info->new_bstart < info->old_bstart) { /* the newly renamed file would get hidden, let's unlink the * file to the left of it */ struct dentry *unlink_dentry; struct dentry *unlink_dir_dentry; unlink_dentry = dtohd_index(new_dentry, info->new_bstart); unlink_dir_dentry = lock_parent(unlink_dentry); if (!(err = is_robranch_super(old_dir->i_sb, info->new_bstart))) err = vfs_unlink(unlink_dir_dentry->d_inode, unlink_dentry); fist_copy_attr_times(new_dentry->d_parent->d_inode, unlink_dir_dentry->d_inode); new_dentry->d_parent->d_inode->i_nlink = get_nlinks(new_dentry->d_parent->d_inode); unlock_dir(unlink_dir_dentry); if (IS_COPYUP_ERR(err)) { if (info->isdir) { err = -EXDEV; goto out; } if ((info->do_copyup == -1) || (info->new_bstart - 1 < info->do_copyup)) info->do_copyup = info->new_bstart - 1; err = 0; // reset error } } out: print_exit_status(err); return err; }
static int unionfs_create(struct inode *parent, struct dentry *dentry, int mode, struct nameidata *nd) { int err = 0; struct dentry *hidden_dentry = NULL; struct dentry *whiteout_dentry = NULL; struct dentry *new_hidden_dentry; struct dentry *hidden_parent_dentry = NULL; int bindex = 0, bstart; char *name = NULL; print_entry_location(); lock_dentry(dentry); fist_print_dentry("IN unionfs_create", dentry); /* We start out in the leftmost branch. */ bstart = dbstart(dentry); hidden_dentry = dtohd(dentry); /* check if whiteout exists in this branch, i.e. lookup .wh.foo first */ name = alloc_whname(dentry->d_name.name, dentry->d_name.len); if (IS_ERR(name)) { err = PTR_ERR(name); goto out; } whiteout_dentry = LOOKUP_ONE_LEN(name, hidden_dentry->d_parent, dentry->d_name.len + WHLEN); if (IS_ERR(whiteout_dentry)) { err = PTR_ERR(whiteout_dentry); whiteout_dentry = NULL; goto out; } if (whiteout_dentry->d_inode) { /* .wh.foo has been found. */ /* First truncate it and then rename it to foo (hence having * the same overall effect as a normal create. * * XXX: This is not strictly correct. If we have unlinked the * file and it still has a reference count, then we should * actually unlink the whiteout so that user's data isn't * hosed over. */ struct dentry *hidden_dir_dentry; struct iattr newattrs; down(&whiteout_dentry->d_inode->i_sem); newattrs.ia_valid = ATTR_CTIME | ATTR_MODE | ATTR_ATIME | ATTR_MTIME | ATTR_UID | ATTR_GID | ATTR_FORCE | ATTR_KILL_SUID | ATTR_KILL_SGID; newattrs.ia_mode = mode & ~current->fs->umask; newattrs.ia_uid = current->fsuid; newattrs.ia_gid = current->fsgid; if (whiteout_dentry->d_inode->i_size != 0) { newattrs.ia_valid |= ATTR_SIZE; newattrs.ia_size = 0; } err = notify_change(whiteout_dentry, &newattrs); up(&whiteout_dentry->d_inode->i_sem); if (err) printk(KERN_WARNING "unionfs: %s:%d: notify_change failed: %d, ignoring..\n", __FILE__, __LINE__, err); new_hidden_dentry = dtohd(dentry); DGET(new_hidden_dentry); hidden_dir_dentry = GET_PARENT(whiteout_dentry); lock_rename(hidden_dir_dentry, hidden_dir_dentry); if (!(err = is_robranch_super(dentry->d_sb, bstart))) { err = vfs_rename(hidden_dir_dentry->d_inode, whiteout_dentry, hidden_dir_dentry->d_inode, new_hidden_dentry); } if (!err) { fist_copy_attr_timesizes(parent, new_hidden_dentry->d_parent-> d_inode); parent->i_nlink = get_nlinks(parent); } unlock_rename(hidden_dir_dentry, hidden_dir_dentry); DPUT(hidden_dir_dentry); DPUT(new_hidden_dentry); if (err) { /* exit if the error returned was NOT -EROFS */ if (!IS_COPYUP_ERR(err)) goto out; /* We were not able to create the file in this branch, * so, we try to create it in one branch to left */ bstart--; } else { /* reset the unionfs dentry to point to the .wh.foo entry. */ /* Discard any old reference. */ DPUT(dtohd(dentry)); /* Trade one reference to another. */ set_dtohd_index(dentry, bstart, whiteout_dentry); whiteout_dentry = NULL; err = unionfs_interpose(dentry, parent->i_sb, 0); goto out; } } for (bindex = bstart; bindex >= 0; bindex--) { hidden_dentry = dtohd_index(dentry, bindex); if (!hidden_dentry) { /* if hidden_dentry is NULL, create the entire * dentry directory structure in branch 'bindex'. * hidden_dentry will NOT be null when bindex == bstart * because lookup passed as a negative unionfs dentry * pointing to a lone negative underlying dentry */ hidden_dentry = create_parents(parent, dentry, bindex); if (!hidden_dentry || IS_ERR(hidden_dentry)) { if (IS_ERR(hidden_dentry)) err = PTR_ERR(hidden_dentry); continue; } } fist_checkinode(parent, "unionfs_create"); hidden_parent_dentry = lock_parent(hidden_dentry); if (IS_ERR(hidden_parent_dentry)) { err = PTR_ERR(hidden_parent_dentry); goto out; } /* We shouldn't create things in a read-only branch. */ if (!(err = is_robranch_super(dentry->d_sb, bindex))) { //DQ: vfs_create has a different prototype in 2.6 err = vfs_create(hidden_parent_dentry->d_inode, hidden_dentry, mode, nd); } if (err || !hidden_dentry->d_inode) { unlock_dir(hidden_parent_dentry); /* break out of for loop if the error wasn't -EROFS */ if (!IS_COPYUP_ERR(err)) break; } else { err = unionfs_interpose(dentry, parent->i_sb, 0); if (!err) { fist_copy_attr_timesizes(parent, hidden_parent_dentry-> d_inode); /* update number of links on parent directory */ parent->i_nlink = get_nlinks(parent); } unlock_dir(hidden_parent_dentry); break; } } out: DPUT(whiteout_dentry); KFREE(name); fist_print_dentry("OUT unionfs_create :", dentry); unlock_dentry(dentry); print_exit_status(err); return err; }
static int unionfs_mkdir(struct inode *parent, struct dentry *dentry, int mode) { int err = 0; struct dentry *hidden_dentry = NULL, *whiteout_dentry = NULL; struct dentry *hidden_parent_dentry = NULL; int bindex = 0, bstart; char *name = NULL; int whiteout_unlinked = 0; uid_t saved_uid = current->fsuid; gid_t saved_gid = current->fsgid; print_entry_location(); lock_dentry(dentry); fist_print_dentry("IN unionfs_mkdir", dentry); bstart = dbstart(dentry); hidden_dentry = dtohd(dentry); // check if whiteout exists in this branch, i.e. lookup .wh.foo first name = alloc_whname(dentry->d_name.name, dentry->d_name.len); if (IS_ERR(name)) { err = PTR_ERR(name); goto out; } whiteout_dentry = LOOKUP_ONE_LEN(name, hidden_dentry->d_parent, dentry->d_name.len + WHLEN); if (IS_ERR(whiteout_dentry)) { err = PTR_ERR(whiteout_dentry); goto out; } if (!whiteout_dentry->d_inode) { DPUT(whiteout_dentry); whiteout_dentry = NULL; } else { hidden_parent_dentry = lock_parent(whiteout_dentry); /* Set the uid and gid to trick the fs into allowing us to create * the file */ current->fsuid = hidden_parent_dentry->d_inode->i_uid; current->fsgid = hidden_parent_dentry->d_inode->i_gid; //found a.wh.foo entry, remove it then do vfs_mkdir if (!(err = is_robranch_super(dentry->d_sb, bstart))) { err = vfs_unlink(hidden_parent_dentry->d_inode, whiteout_dentry); } DPUT(whiteout_dentry); current->fsuid = saved_uid; current->fsgid = saved_gid; unlock_dir(hidden_parent_dentry); if (err) { /* exit if the error returned was NOT -EROFS */ if (!IS_COPYUP_ERR(err)) goto out; bstart--; } else { whiteout_unlinked = 1; } } for (bindex = bstart; bindex >= 0; bindex--) { hidden_dentry = dtohd_index(dentry, bindex); if (!hidden_dentry) { hidden_dentry = create_parents(parent, dentry, bindex); if (!hidden_dentry || IS_ERR(hidden_dentry)) { fist_dprint(8, "hidden dentry NULL for bindex = %d\n", bindex); continue; } } hidden_parent_dentry = lock_parent(hidden_dentry); if (IS_ERR(hidden_parent_dentry)) { err = PTR_ERR(hidden_parent_dentry); goto out; } if (!(err = is_robranch_super(dentry->d_sb, bindex))) { err = vfs_mkdir(hidden_parent_dentry->d_inode, hidden_dentry, mode); } unlock_dir(hidden_parent_dentry); /* XXX this could potentially return a negative hidden_dentry! */ if (err || !hidden_dentry->d_inode) { /* break out of for loop if error returned was NOT -EROFS */ if (!IS_COPYUP_ERR(err)) break; } else { int i; int bend = dbend(dentry); for (i = bindex + 1; i < bend; i++) { if (dtohd_index(dentry, i)) { DPUT(dtohd_index(dentry, i)); set_dtohd_index(dentry, i, NULL); } } bend = bindex; set_dbend(dentry, bend); err = unionfs_interpose(dentry, parent->i_sb, 0); if (!err) { fist_copy_attr_timesizes(parent, hidden_parent_dentry-> d_inode); /* update number of links on parent directory */ parent->i_nlink = get_nlinks(parent); } whiteout_dentry = LOOKUP_ONE_LEN(UNIONFS_DIR_OPAQUE, hidden_dentry, sizeof (UNIONFS_DIR_OPAQUE) - 1); if (IS_ERR(whiteout_dentry)) { err = PTR_ERR(whiteout_dentry); goto out; } down(&hidden_dentry->d_inode->i_sem); err = vfs_create(hidden_dentry->d_inode, whiteout_dentry, 0600, NULL); up(&hidden_dentry->d_inode->i_sem); DPUT(whiteout_dentry); if (err) { fist_dprint(8, "mkdir: error creating directory override entry: %d\n", err); goto out; } break; } } out: if (!dentry->d_inode) d_drop(dentry); KFREE(name); fist_print_dentry("OUT unionfs_mkdir :", dentry); unlock_dentry(dentry); print_exit_status(err); return err; }
static int unionfs_symlink(struct inode *dir, struct dentry *dentry, const char *symname) { int err = 0; struct dentry *hidden_dentry = NULL; struct dentry *whiteout_dentry = NULL; struct dentry *hidden_dir_dentry = NULL; umode_t mode; int bindex = 0, bstart; char *name = NULL; print_entry_location(); lock_dentry(dentry); fist_print_dentry("IN unionfs_symlink", dentry); /* We start out in the leftmost branch. */ bstart = dbstart(dentry); hidden_dentry = dtohd(dentry); /* check if whiteout exists in this branch, i.e. lookup .wh.foo first. If present, delete it */ name = alloc_whname(dentry->d_name.name, dentry->d_name.len); if (IS_ERR(name)) { err = PTR_ERR(name); goto out; } whiteout_dentry = LOOKUP_ONE_LEN(name, hidden_dentry->d_parent, dentry->d_name.len + WHLEN); if (IS_ERR(whiteout_dentry)) { err = PTR_ERR(whiteout_dentry); goto out; } if (!whiteout_dentry->d_inode) { DPUT(whiteout_dentry); whiteout_dentry = NULL; } else { /* found a .wh.foo entry, unlink it and then call vfs_symlink() */ hidden_dir_dentry = lock_parent(whiteout_dentry); fist_print_generic_dentry("HDD", hidden_dir_dentry); fist_print_generic_dentry("WD", whiteout_dentry); if (!(err = is_robranch_super(dentry->d_sb, bstart))) { err = vfs_unlink(hidden_dir_dentry->d_inode, whiteout_dentry); } DPUT(whiteout_dentry); fist_copy_attr_times(dir, hidden_dir_dentry->d_inode); /* propagate number of hard-links */ dir->i_nlink = get_nlinks(dir); unlock_dir(hidden_dir_dentry); if (err) { /* exit if the error returned was NOT -EROFS */ if (!IS_COPYUP_ERR(err)) goto out; /* should now try to create symlink in the another branch */ bstart--; } } /* deleted whiteout if it was present, now do a normal vfs_symlink() with possible recursive directory creation */ for (bindex = bstart; bindex >= 0; bindex--) { hidden_dentry = dtohd_index(dentry, bindex); if (!hidden_dentry) { /* if hidden_dentry is NULL, create the entire * dentry directory structure in branch 'bindex'. hidden_dentry will NOT be null when * bindex == bstart because lookup passed as a negative unionfs dentry pointing to a * lone negative underlying dentry */ hidden_dentry = create_parents(dir, dentry, bindex); if (!hidden_dentry || IS_ERR(hidden_dentry)) { if (IS_ERR(hidden_dentry)) { err = PTR_ERR(hidden_dentry); } fist_dprint(8, "hidden dentry NULL (or error) for bindex = %d\n", bindex); continue; } } hidden_dir_dentry = lock_parent(hidden_dentry); if (!(err = is_robranch_super(dentry->d_sb, bindex))) { mode = S_IALLUGO; err = vfs_symlink(hidden_dir_dentry->d_inode, hidden_dentry, symname, mode); } unlock_dir(hidden_dir_dentry); if (err || !hidden_dentry->d_inode) { /* break out of for loop if error returned was NOT -EROFS */ if (!IS_COPYUP_ERR(err)) break; } else { err = unionfs_interpose(dentry, dir->i_sb, 0); if (!err) { fist_copy_attr_timesizes(dir, hidden_dir_dentry-> d_inode); /* update number of links on parent directory */ dir->i_nlink = get_nlinks(dir); } break; } } out: if (!dentry->d_inode) d_drop(dentry); KFREE(name); fist_print_dentry("OUT unionfs_symlink :", dentry); unlock_dentry(dentry); print_exit_status(err); return err; }
int copyup_named_dentry(struct inode *dir, struct dentry *dentry, int bstart, int new_bindex, char *name, int namelen, struct file **copyup_file, int len) { struct dentry *new_hidden_dentry; struct dentry *old_hidden_dentry = NULL; struct super_block *sb; struct file *input_file = NULL; struct file *output_file = NULL; ssize_t read_bytes, write_bytes; mm_segment_t old_fs; int err = 0; char *buf; int old_bindex; int got_branch_input = -1; int got_branch_output = -1; int old_bstart; int old_bend; int size = len; struct dentry *new_hidden_parent_dentry; mm_segment_t oldfs; char *symbuf = NULL; uid_t saved_uid = current->fsuid; gid_t saved_gid = current->fsgid; print_entry_location(); verify_locked(dentry); fist_print_dentry("IN: copyup_named_dentry", dentry); old_bindex = bstart; old_bstart = dbstart(dentry); old_bend = dbend(dentry); ASSERT(new_bindex >= 0); ASSERT(new_bindex < old_bindex); PASSERT(dir); PASSERT(dentry); sb = dir->i_sb; if ((err = is_robranch_super(sb, new_bindex))) goto out; /* Create the directory structure above this dentry. */ new_hidden_dentry = create_parents_named(dir, dentry, name, new_bindex); PASSERT(new_hidden_dentry); if (IS_ERR(new_hidden_dentry)) { err = PTR_ERR(new_hidden_dentry); goto out; } fist_print_generic_dentry("Copyup Object", new_hidden_dentry); /* Now we actually create the object. */ old_hidden_dentry = dtohd_index(dentry, old_bindex); PASSERT(old_hidden_dentry); PASSERT(old_hidden_dentry->d_inode); DGET(old_hidden_dentry); /* For symlinks, we must read the link before we lock the directory. */ if (S_ISLNK(old_hidden_dentry->d_inode->i_mode)) { PASSERT(old_hidden_dentry->d_inode->i_op); PASSERT(old_hidden_dentry->d_inode->i_op->readlink); symbuf = KMALLOC(PATH_MAX, GFP_UNIONFS); if (!symbuf) { err = -ENOMEM; goto copyup_readlink_err; } oldfs = get_fs(); set_fs(KERNEL_DS); err = old_hidden_dentry->d_inode->i_op-> readlink(old_hidden_dentry, symbuf, PATH_MAX); set_fs(oldfs); if (err < 0) goto copyup_readlink_err; symbuf[err] = '\0'; } /* Now we lock the parent, and create the object in the new branch. */ new_hidden_parent_dentry = lock_parent(new_hidden_dentry); current->fsuid = new_hidden_parent_dentry->d_inode->i_uid; current->fsgid = new_hidden_parent_dentry->d_inode->i_gid; if (S_ISDIR(old_hidden_dentry->d_inode->i_mode)) { err = vfs_mkdir(new_hidden_parent_dentry->d_inode, new_hidden_dentry, S_IRWXU); } else if (S_ISLNK(old_hidden_dentry->d_inode->i_mode)) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) err = vfs_symlink(new_hidden_parent_dentry->d_inode, new_hidden_dentry, symbuf); #else err = vfs_symlink(new_hidden_parent_dentry->d_inode, new_hidden_dentry, symbuf, S_IRWXU); #endif } else if (S_ISBLK(old_hidden_dentry->d_inode->i_mode) || S_ISCHR(old_hidden_dentry->d_inode->i_mode) || S_ISFIFO(old_hidden_dentry->d_inode->i_mode) || S_ISSOCK(old_hidden_dentry->d_inode->i_mode)) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) err = vfs_mknod(new_hidden_parent_dentry->d_inode, new_hidden_dentry, old_hidden_dentry->d_inode->i_mode, kdev_t_to_nr(old_hidden_dentry->d_inode-> i_rdev)); #else err = vfs_mknod(new_hidden_parent_dentry->d_inode, new_hidden_dentry, old_hidden_dentry->d_inode->i_mode, old_hidden_dentry->d_inode->i_rdev); #endif } else if (S_ISREG(old_hidden_dentry->d_inode->i_mode)) { #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) err = vfs_create(new_hidden_parent_dentry->d_inode, new_hidden_dentry, S_IRWXU); #else err = vfs_create(new_hidden_parent_dentry->d_inode, new_hidden_dentry, S_IRWXU, NULL); #endif } else { char diemsg[100]; snprintf(diemsg, sizeof(diemsg), "Unknown inode type %d\n", old_hidden_dentry->d_inode->i_mode); FISTBUG(diemsg); } current->fsuid = saved_uid; current->fsgid = saved_gid; unlock_dir(new_hidden_parent_dentry); copyup_readlink_err: KFREE(symbuf); if (err) { /* get rid of the hidden dentry and all its traces */ DPUT(new_hidden_dentry); set_dtohd_index(dentry, new_bindex, NULL); set_dbstart(dentry, old_bstart); set_dbend(dentry, old_bend); goto out; } /* We actually copyup the file here. */ if (S_ISREG(old_hidden_dentry->d_inode->i_mode)) { mntget(stohiddenmnt_index(sb, old_bindex)); branchget(sb, old_bindex); got_branch_input = old_bindex; input_file = DENTRY_OPEN(old_hidden_dentry, stohiddenmnt_index(sb, old_bindex), O_RDONLY); if (IS_ERR(input_file)) { err = PTR_ERR(input_file); goto out; } if (!input_file->f_op || !input_file->f_op->read) { err = -EINVAL; goto out; } /* copy the new file */ DGET(new_hidden_dentry); mntget(stohiddenmnt_index(sb, new_bindex)); branchget(sb, new_bindex); got_branch_output = new_bindex; output_file = DENTRY_OPEN(new_hidden_dentry, stohiddenmnt_index(sb, new_bindex), O_WRONLY); if (IS_ERR(output_file)) { err = PTR_ERR(output_file); goto out; } if (!output_file->f_op || !output_file->f_op->write) { err = -EINVAL; goto out; } /* allocating a buffer */ buf = (char *)KMALLOC(PAGE_SIZE, GFP_UNIONFS); if (!buf) { err = -ENOMEM; goto out; } /* now read PAGE_SIZE bytes from offset 0 in a loop */ old_fs = get_fs(); input_file->f_pos = 0; output_file->f_pos = 0; set_fs(KERNEL_DS); do { if (len >= PAGE_SIZE) size = PAGE_SIZE; else if ((len < PAGE_SIZE) && (len > 0)) size = len; len -= PAGE_SIZE; read_bytes = input_file->f_op->read(input_file, buf, size, &input_file->f_pos); if (read_bytes <= 0) { err = read_bytes; break; } write_bytes = output_file->f_op->write(output_file, buf, read_bytes, &output_file->f_pos); if (write_bytes < 0 || (write_bytes < read_bytes)) { err = -EIO; break; } } while ((read_bytes > 0) && (len > 0)); set_fs(old_fs); KFREE(buf); } /* Set permissions. */ if ((err = copyup_permissions(sb, old_hidden_dentry, new_hidden_dentry))) goto out; /* Selinux uses extended attributes for permissions. */ #if defined(UNIONFS_XATTR) && (LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,20)) if ((err = copyup_xattrs(old_hidden_dentry, new_hidden_dentry))) goto out; #endif /* do not allow files getting deleted to be reinterposed */ if (!d_deleted(dentry)) unionfs_reinterpose(dentry); out: if (input_file && !IS_ERR(input_file)) { fput(input_file); } else { /* since input file was not opened, we need to explicitly * dput the old_hidden_dentry */ DPUT(old_hidden_dentry); } /* in any case, we have to branchput */ if (got_branch_input >= 0) branchput(sb, got_branch_input); if (output_file) { if (copyup_file && !err) { *copyup_file = output_file; } else { fput(output_file); branchput(sb, got_branch_output); } } fist_print_dentry("OUT: copyup_dentry", dentry); fist_print_inode("OUT: copyup_dentry", dentry->d_inode); print_exit_status(err); return err; }
static int unionfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev) { int err = 0; struct dentry *hidden_dentry = NULL, *whiteout_dentry = NULL; struct dentry *hidden_parent_dentry = NULL; int bindex = 0, bstart; char *name = NULL; int whiteout_unlinked = 0; print_entry_location(); lock_dentry(dentry); fist_print_dentry("IN unionfs_mknod", dentry); bstart = dbstart(dentry); hidden_dentry = dtohd(dentry); // check if whiteout exists in this branch, i.e. lookup .wh.foo first name = alloc_whname(dentry->d_name.name, dentry->d_name.len); if (IS_ERR(name)) { err = PTR_ERR(name); goto out; } whiteout_dentry = LOOKUP_ONE_LEN(name, hidden_dentry->d_parent, dentry->d_name.len + WHLEN); if (IS_ERR(whiteout_dentry)) { err = PTR_ERR(whiteout_dentry); goto out; } if (!whiteout_dentry->d_inode) { DPUT(whiteout_dentry); whiteout_dentry = NULL; } else { /* found .wh.foo, unlink it */ hidden_parent_dentry = lock_parent(whiteout_dentry); //found a.wh.foo entry, remove it then do vfs_mkdir if (!(err = is_robranch_super(dentry->d_sb, bstart))) err = vfs_unlink(hidden_parent_dentry->d_inode, whiteout_dentry); DPUT(whiteout_dentry); unlock_dir(hidden_parent_dentry); if (err) { if (!IS_COPYUP_ERR(err)) goto out; bstart--; } else { whiteout_unlinked = 1; } } for (bindex = bstart; bindex >= 0; bindex--) { hidden_dentry = dtohd_index(dentry, bindex); if (!hidden_dentry) { hidden_dentry = create_parents(dir, dentry, bindex); if (!hidden_dentry || IS_ERR(hidden_dentry)) { fist_dprint(8, "hidden dentry NULL for bindex = %d\n", bindex); continue; } } hidden_parent_dentry = lock_parent(hidden_dentry); if (IS_ERR(hidden_parent_dentry)) { err = PTR_ERR(hidden_parent_dentry); goto out; } if (!(err = is_robranch_super(dentry->d_sb, bindex))) { err = vfs_mknod(hidden_parent_dentry->d_inode, hidden_dentry, mode, dev); } /* XXX this could potentially return a negative hidden_dentry! */ if (err || !hidden_dentry->d_inode) { unlock_dir(hidden_parent_dentry); /* break out of for, if error was NOT -EROFS */ if (!IS_COPYUP_ERR(err)) break; } else { err = unionfs_interpose(dentry, dir->i_sb, 0); if (!err) { fist_copy_attr_timesizes(dir, hidden_parent_dentry-> d_inode); /* update number of links on parent directory */ dir->i_nlink = get_nlinks(dir); } unlock_dir(hidden_parent_dentry); break; } } out: if (!dentry->d_inode) d_drop(dentry); if (name) { KFREE(name); } fist_print_dentry("OUT unionfs_mknod :", dentry); 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; 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; }
static int parse_dirs_option(struct super_block *sb, struct unionfs_dentry_info *hidden_root_info, char *options) { struct nameidata nd; char *name; int err = 0; int branches = 1; int bindex = 0; int i = 0; int j = 0; struct dentry *dent1 = NULL; struct dentry *dent2 = NULL; if (options[0] == '\0') { printk(KERN_WARNING "unionfs: no branches specified\n"); err = -EINVAL; goto out; } /* Each colon means we have a separator, this is really just a rough * guess, since strsep will handle empty fields for us. */ for (i = 0; options[i]; i++) { if (options[i] == ':') branches++; } /* allocate space for underlying pointers to hidden dentry */ if (!(stopd(sb)->usi_data = alloc_new_data(branches))) { err = -ENOMEM; goto out; } if (!(hidden_root_info->udi_dentry = alloc_new_dentries(branches))) { err = -ENOMEM; goto out; } /* now parsing the string b1:b2=rw:b3=ro:b4 */ branches = 0; while ((name = strsep(&options, ":")) != NULL) { int perms; if (!*name) continue; branches++; /* strip off =rw or =ro if it is specified. */ perms = parse_branch_mode(name); if (!bindex && !(perms & MAY_WRITE)) { err = -EINVAL; goto out; } fist_dprint(4, "unionfs: using directory: %s (%c%c%c)\n", name, perms & MAY_READ ? 'r' : '-', perms & MAY_WRITE ? 'w' : '-', perms & MAY_NFSRO ? 'n' : '-'); err = path_lookup(name, LOOKUP_FOLLOW, &nd); RECORD_PATH_LOOKUP(&nd); if (err) { printk(KERN_WARNING "unionfs: error accessing " "hidden directory '%s' (error %d)\n", name, err); goto out; } if ((err = check_branch(&nd))) { printk(KERN_WARNING "unionfs: hidden directory " "'%s' is not a valid branch\n", name); path_release(&nd); RECORD_PATH_RELEASE(&nd); goto out; } hidden_root_info->udi_dentry[bindex] = nd.dentry; set_stohiddenmnt_index(sb, bindex, nd.mnt); set_branchperms(sb, bindex, perms); set_branch_count(sb, bindex, 0); if (hidden_root_info->udi_bstart < 0) hidden_root_info->udi_bstart = bindex; hidden_root_info->udi_bend = bindex; bindex++; } if (branches == 0) { printk(KERN_WARNING "unionfs: no branches specified\n"); err = -EINVAL; goto out; } BUG_ON(branches != (hidden_root_info->udi_bend + 1)); /* ensure that no overlaps exist in the branches */ for (i = 0; i < branches; i++) { for (j = i + 1; j < branches; j++) { dent1 = hidden_root_info->udi_dentry[i]; dent2 = hidden_root_info->udi_dentry[j]; if (is_branch_overlap(dent1, dent2)) { goto out_overlap; } } } out_overlap: if (i != branches) { printk(KERN_WARNING "unionfs: branches %d and %d overlap\n", i, j); err = -EINVAL; goto out; } out: if (err) { for (i = 0; i < branches; i++) { if (hidden_root_info->udi_dentry[i]) DPUT(hidden_root_info->udi_dentry[i]); } KFREE(hidden_root_info->udi_dentry); KFREE(stopd(sb)->usi_data); /* MUST clear the pointers to prevent potential double free if * the caller dies later on */ hidden_root_info->udi_dentry = NULL; stopd(sb)->usi_data = NULL; } return err; }
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); }
static struct dentry *unionfs_d_alloc_root(struct super_block *sb) { struct dentry *ret = NULL; if (sb) { static const struct qstr name = {.name = "/",.len = 1 }; ret = d_alloc(NULL, &name); if (ret) { ret->d_op = &unionfs_dops; ret->d_sb = sb; ret->d_parent = ret; } } return ret; } static int unionfs_read_super(struct super_block *sb, void *raw_data, int silent) { int err = 0; struct unionfs_dentry_info *hidden_root_info = NULL; int bindex, bstart, bend; unsigned long long maxbytes; print_entry_location(); if (!raw_data) { printk(KERN_WARNING "unionfs_read_super: missing data argument\n"); err = -EINVAL; goto out; } /* * Allocate superblock private data */ stopd_lhs(sb) = KZALLOC(sizeof(struct unionfs_sb_info), GFP_KERNEL); if (!stopd(sb)) { printk(KERN_WARNING "%s: out of memory\n", __FUNCTION__); err = -ENOMEM; goto out; } stopd(sb)->b_end = -1; atomic_set(&stopd(sb)->usi_generation, 1); init_rwsem(&stopd(sb)->usi_rwsem); hidden_root_info = unionfs_parse_options(sb, raw_data); if (IS_ERR(hidden_root_info)) { printk(KERN_WARNING "unionfs_read_super: error while parsing options (err = %ld)\n", PTR_ERR(hidden_root_info)); err = PTR_ERR(hidden_root_info); hidden_root_info = NULL; goto out_free; } if (hidden_root_info->udi_bstart == -1) { err = -ENOENT; goto out_free; } /* set the hidden superblock field of upper superblock */ bstart = hidden_root_info->udi_bstart; BUG_ON(bstart != 0); sbend(sb) = bend = hidden_root_info->udi_bend; for (bindex = bstart; bindex <= bend; bindex++) { struct dentry *d; d = hidden_root_info->udi_dentry[bindex]; set_stohs_index(sb, bindex, d->d_sb); } /* Unionfs: Max Bytes is the maximum bytes from among all the branches */ maxbytes = -1; for (bindex = bstart; bindex <= bend; bindex++) if (maxbytes < stohs_index(sb, bindex)->s_maxbytes) maxbytes = stohs_index(sb, bindex)->s_maxbytes; sb->s_maxbytes = maxbytes; sb->s_op = &unionfs_sops; sb->s_export_op = &unionfs_export_ops; /* * we can't use d_alloc_root if we want to use * our own interpose function unchanged, * so we simply call our own "fake" d_alloc_root */ sb->s_root = unionfs_d_alloc_root(sb); if (!sb->s_root) { err = -ENOMEM; goto out_dput; } /* link the upper and lower dentries */ dtopd_lhs(sb->s_root) = NULL; if ((err = new_dentry_private_data(sb->s_root))) goto out_freedpd; /* Set the hidden dentries for s_root */ for (bindex = bstart; bindex <= bend; bindex++) { struct dentry *d; d = hidden_root_info->udi_dentry[bindex]; set_dtohd_index(sb->s_root, bindex, d); } set_dbstart(sb->s_root, bstart); set_dbend(sb->s_root, bend); /* Set the generation number to one, since this is for the mount. */ atomic_set(&dtopd(sb->s_root)->udi_generation, 1); /* call interpose to create the upper level inode */ if ((err = unionfs_interpose(sb->s_root, sb, 0))) goto out_freedpd; unlock_dentry(sb->s_root); goto out; out_freedpd: if (dtopd(sb->s_root)) { KFREE(dtohd_ptr(sb->s_root)); free_dentry_private_data(dtopd(sb->s_root)); } DPUT(sb->s_root); out_dput: if (hidden_root_info && !IS_ERR(hidden_root_info)) { for (bindex = hidden_root_info->udi_bstart; bindex <= hidden_root_info->udi_bend; bindex++) { struct dentry *d; d = hidden_root_info->udi_dentry[bindex]; if (d) DPUT(d); if (stopd(sb) && stohiddenmnt_index(sb, bindex)) mntput(stohiddenmnt_index(sb, bindex)); } KFREE(hidden_root_info->udi_dentry); KFREE(hidden_root_info); hidden_root_info = NULL; } out_free: KFREE(stopd(sb)->usi_data); KFREE(stopd(sb)); stopd_lhs(sb) = NULL; out: if (hidden_root_info && !IS_ERR(hidden_root_info)) { KFREE(hidden_root_info->udi_dentry); KFREE(hidden_root_info); } print_exit_status(err); return err; }
/* * Parse mount options. See the manual page for usage instructions. * * Returns the dentry object of the lower-level (hidden) directory; * We want to mount our stackable file system on top of that hidden directory. * * Sets default debugging level to N, if any. */ static struct unionfs_dentry_info *unionfs_parse_options(struct super_block *sb, char *options) { struct unionfs_dentry_info *hidden_root_info; char *optname; int err = 0; int bindex; int dirsfound = 0; #ifdef UNIONFS_IMAP int imapfound = 0; #endif print_entry_location(); /* allocate private data area */ err = -ENOMEM; hidden_root_info = KZALLOC(sizeof(struct unionfs_dentry_info), GFP_KERNEL); if (!hidden_root_info) goto out_error; hidden_root_info->udi_bstart = -1; hidden_root_info->udi_bend = -1; hidden_root_info->udi_bopaque = -1; while ((optname = strsep(&options, ",")) != NULL) { char *optarg; char *endptr; int intval; if (!*optname) { continue; } optarg = strchr(optname, '='); if (optarg) { *optarg++ = '\0'; } /* All of our options take an argument now. Insert ones that * don't, above this check. */ if (!optarg) { printk("unionfs: %s requires an argument.\n", optname); err = -EINVAL; goto out_error; } if (!strcmp("dirs", optname)) { if (++dirsfound > 1) { printk(KERN_WARNING "unionfs: multiple dirs specified\n"); err = -EINVAL; goto out_error; } err = parse_dirs_option(sb, hidden_root_info, optarg); if (err) goto out_error; continue; } #ifdef UNIONFS_IMAP if (!strcmp("imap", optname)) { if (++imapfound > 1) { printk(KERN_WARNING "unionfs: multiple imap specified\n"); err = -EINVAL; goto out_error; } err = parse_imap_option(sb, hidden_root_info, optarg); if (err) goto out_error; continue; } #endif if (!strcmp("delete", optname)) { if (!strcmp("whiteout", optarg)) { /* default */ #ifdef UNIONFS_DELETE_ALL } else if (!strcmp("all", optarg)) { MOUNT_FLAG(sb) |= DELETE_ALL; #endif } else { printk(KERN_WARNING "unionfs: invalid delete option '%s'\n", optarg); err = -EINVAL; goto out_error; } continue; } if (!strcmp("copyup", optname)) { if (!strcmp("preserve", optarg)) { /* default */ } else if (!strcmp("currentuser", optarg)) { MOUNT_FLAG(sb) |= COPYUP_CURRENT_USER; } else { printk(KERN_WARNING "unionfs: could not parse copyup option value '%s'\n", optarg); err = -EINVAL; goto out_error; } continue; } /* All of these options require an integer argument. */ intval = simple_strtoul(optarg, &endptr, 0); if (*endptr) { printk(KERN_WARNING "unionfs: invalid %s option '%s'\n", optname, optarg); err = -EINVAL; goto out_error; } if (!strcmp("debug", optname)) { fist_set_debug_value(intval); continue; } err = -EINVAL; printk(KERN_WARNING "unionfs: unrecognized option '%s'\n", optname); goto out_error; } if (dirsfound != 1) { printk(KERN_WARNING "unionfs: dirs option required\n"); err = -EINVAL; goto out_error; } goto out; out_error: if (hidden_root_info && hidden_root_info->udi_dentry) { for (bindex = hidden_root_info->udi_bstart; bindex >= 0 && bindex <= hidden_root_info->udi_bend; bindex++) { struct dentry *d; d = hidden_root_info->udi_dentry[bindex]; DPUT(d); if (stohiddenmnt_index(sb, bindex)) mntput(stohiddenmnt_index(sb, bindex)); } } KFREE(hidden_root_info->udi_dentry); KFREE(hidden_root_info); KFREE(stopd(sb)->usi_data); stopd(sb)->usi_data = NULL; hidden_root_info = ERR_PTR(err); out: print_exit_location(); return hidden_root_info; }
static int unionfs_unlink_whiteout(struct inode *dir, struct dentry *dentry) { int err = 0; struct dentry *hidden_old_dentry; struct dentry *hidden_wh_dentry = NULL; struct dentry *hidden_old_dir_dentry, *hidden_new_dir_dentry; char *name = NULL; struct iattr newattrs; print_entry_location(); fist_print_dentry("IN unionfs_unlink_whiteout: ", dentry); /* create whiteout, get the leftmost underlying dentry and rename it */ hidden_old_dentry = dtohd(dentry); /* lookup .wh.foo first, MUST NOT EXIST */ name = KMALLOC(dentry->d_name.len + sizeof(".wh."), GFP_UNIONFS); if (!name) { err = -ENOMEM; goto out; } strcpy(name, ".wh."); strncat(name, dentry->d_name.name, dentry->d_name.len); name[4 + dentry->d_name.len] = '\0'; hidden_wh_dentry = LOOKUP_ONE_LEN(name, hidden_old_dentry->d_parent, dentry->d_name.len + 4); if (IS_ERR(hidden_wh_dentry)) { err = PTR_ERR(hidden_wh_dentry); goto out; } ASSERT(hidden_wh_dentry->d_inode == NULL); DGET(hidden_old_dentry); hidden_old_dir_dentry = GET_PARENT(hidden_old_dentry); hidden_new_dir_dentry = GET_PARENT(hidden_wh_dentry); double_lock(hidden_old_dir_dentry, hidden_new_dir_dentry); if (!(err = is_robranch(dentry))) { if (S_ISREG(hidden_old_dentry->d_inode->i_mode)) { err = vfs_rename(hidden_old_dir_dentry->d_inode, hidden_old_dentry, hidden_new_dir_dentry->d_inode, hidden_wh_dentry); } else { err = vfs_unlink(hidden_old_dir_dentry->d_inode, hidden_old_dentry); if (!err) #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) err = vfs_create(hidden_new_dir_dentry->d_inode, hidden_wh_dentry, 0666, NULL); #else err = vfs_create(hidden_new_dir_dentry->d_inode, hidden_wh_dentry, 0666); #endif } } double_unlock(hidden_old_dir_dentry, hidden_new_dir_dentry); DPUT(hidden_old_dentry); DPUT(hidden_wh_dentry); if (!err) { hidden_wh_dentry = LOOKUP_ONE_LEN(name, hidden_old_dentry->d_parent, dentry->d_name.len + 4); if (IS_ERR(hidden_wh_dentry)) { err = PTR_ERR(hidden_wh_dentry); goto out; } PASSERT(hidden_wh_dentry->d_inode); down(&hidden_wh_dentry->d_inode->i_sem); newattrs.ia_valid = ATTR_CTIME; if (hidden_wh_dentry->d_inode->i_size != 0) { newattrs.ia_valid |= ATTR_SIZE; newattrs.ia_size = 0; } /* We discard this error, because the entry is whited out * even if we fail here. */ notify_change(hidden_wh_dentry, &newattrs); up(&hidden_wh_dentry->d_inode->i_sem); DPUT(hidden_wh_dentry); } if (err) { if (dbstart(dentry) == 0) goto out; /* exit if the error returned was NOT -EROFS */ if (!IS_COPYUP_ERR(err)) goto out; err = create_whiteout(dentry, dbstart(dentry) - 1); } else { fist_copy_attr_all(dir, hidden_new_dir_dentry->d_inode); } out: KFREE(name); print_exit_status(err); 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; }
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_rename_whiteout(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry) { int err = 0; int bindex, bwh_old; int old_bstart, old_bend; int new_bstart, new_bend; int do_copyup = -1; struct dentry *parent_dentry; int local_err = 0; int eio = 0; int revert = 0; struct dentry *wh_old = NULL; print_entry_location(); old_bstart = dbstart(old_dentry); bwh_old = old_bstart; old_bend = dbend(old_dentry); parent_dentry = old_dentry->d_parent; new_bstart = dbstart(new_dentry); new_bend = dbend(new_dentry); /* Rename source to destination. */ err = do_rename(old_dir, old_dentry, new_dir, new_dentry, old_bstart, &wh_old); if (err) { if (!IS_COPYUP_ERR(err)) { goto out; } do_copyup = old_bstart - 1; } else { revert = 1; } /* Unlink all instances of destination that exist to the left of * bstart of source. On error, revert back, goto out. */ for (bindex = old_bstart - 1; bindex >= new_bstart; bindex--) { struct dentry *unlink_dentry; struct dentry *unlink_dir_dentry; unlink_dentry = dtohd_index(new_dentry, bindex); if (!unlink_dentry) { continue; } unlink_dir_dentry = lock_parent(unlink_dentry); if (!(err = is_robranch_super(old_dir->i_sb, bindex))) { err = vfs_unlink(unlink_dir_dentry->d_inode, unlink_dentry); } fist_copy_attr_times(new_dentry->d_parent->d_inode, unlink_dir_dentry->d_inode); /* propagate number of hard-links */ new_dentry->d_parent->d_inode->i_nlink = get_nlinks(new_dentry->d_parent->d_inode); unlock_dir(unlink_dir_dentry); if (!err) { if (bindex != new_bstart) { DPUT(unlink_dentry); set_dtohd_index(new_dentry, bindex, NULL); } } else if (IS_COPYUP_ERR(err)) { do_copyup = bindex - 1; } else if (revert) { DPUT(wh_old); goto revert; } } if (do_copyup != -1) { for (bindex = do_copyup; bindex >= 0; bindex--) { /* copyup the file into some left directory, so that you can rename it */ err = copyup_dentry(old_dentry->d_parent->d_inode, old_dentry, old_bstart, bindex, NULL, old_dentry->d_inode->i_size); if (!err) { DPUT(wh_old); bwh_old = bindex; err = do_rename(old_dir, old_dentry, new_dir, new_dentry, bindex, &wh_old); break; } } } /* Create whiteout for source, only if: * (1) There is more than one underlying instance of source. * (2) We did a copy_up */ if ((old_bstart != old_bend) || (do_copyup != -1)) { struct dentry *hidden_parent; BUG_ON(!wh_old || IS_ERR(wh_old) || wh_old->d_inode || bwh_old < 0); hidden_parent = lock_parent(wh_old); local_err = vfs_create(hidden_parent->d_inode, wh_old, S_IRUGO, NULL); unlock_dir(hidden_parent); if (!local_err) set_dbopaque(old_dentry, bwh_old); else { /* We can't fix anything now, so we cop-out and use -EIO. */ printk ("<0>We can't create a whiteout for the source in rename!\n"); err = -EIO; } } out: DPUT(wh_old); print_exit_status(err); return err; revert: /* Do revert here. */ local_err = unionfs_refresh_hidden_dentry(new_dentry, old_bstart); if (local_err) { printk(KERN_WARNING "Revert failed in rename: the new refresh failed.\n"); eio = -EIO; } local_err = unionfs_refresh_hidden_dentry(old_dentry, old_bstart); if (local_err) { printk(KERN_WARNING "Revert failed in rename: the old refresh failed.\n"); eio = -EIO; goto revert_out; } if (!dtohd_index(new_dentry, bindex) || !dtohd_index(new_dentry, bindex)->d_inode) { printk(KERN_WARNING "Revert failed in rename: the object disappeared from under us!\n"); eio = -EIO; goto revert_out; } if (dtohd_index(old_dentry, bindex) && dtohd_index(old_dentry, bindex)->d_inode) { printk(KERN_WARNING "Revert failed in rename: the object was created underneath us!\n"); eio = -EIO; goto revert_out; } local_err = do_rename(new_dir, new_dentry, old_dir, old_dentry, old_bstart, NULL); /* If we can't fix it, then we cop-out with -EIO. */ if (local_err) { printk(KERN_WARNING "Revert failed in rename!\n"); eio = -EIO; } local_err = unionfs_refresh_hidden_dentry(new_dentry, bindex); if (local_err) eio = -EIO; local_err = unionfs_refresh_hidden_dentry(old_dentry, bindex); if (local_err) eio = -EIO; revert_out: if (eio) err = eio; 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; }
static int do_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, int bindex, struct dentry **wh_old) { int err = 0; struct dentry *hidden_old_dentry; struct dentry *hidden_new_dentry; struct dentry *hidden_old_dir_dentry; struct dentry *hidden_new_dir_dentry; struct dentry *hidden_wh_dentry; struct dentry *hidden_wh_dir_dentry; char *wh_name = NULL; print_entry(" bindex=%d", bindex); fist_print_dentry("IN: do_rename, old_dentry", old_dentry); fist_print_dentry("IN: do_rename, new_dentry", new_dentry); fist_dprint(7, "do_rename for bindex = %d\n", bindex); hidden_new_dentry = dtohd_index(new_dentry, bindex); hidden_old_dentry = dtohd_index(old_dentry, bindex); if (!hidden_new_dentry) { hidden_new_dentry = create_parents(new_dentry->d_parent->d_inode, new_dentry, bindex); if (IS_ERR(hidden_new_dentry)) { fist_dprint(7, "error creating directory tree for rename, bindex = %d\n", bindex); err = PTR_ERR(hidden_new_dentry); goto out; } } wh_name = alloc_whname(new_dentry->d_name.name, new_dentry->d_name.len); if (IS_ERR(wh_name)) { err = PTR_ERR(wh_name); goto out; } hidden_wh_dentry = LOOKUP_ONE_LEN(wh_name, hidden_new_dentry->d_parent, new_dentry->d_name.len + WHLEN); if (IS_ERR(hidden_wh_dentry)) { err = PTR_ERR(hidden_wh_dentry); goto out; } if (hidden_wh_dentry->d_inode) { /* get rid of the whiteout that is existing */ if (hidden_new_dentry->d_inode) { printk(KERN_WARNING "Both a whiteout and a dentry exist when doing a rename!\n"); err = -EIO; DPUT(hidden_wh_dentry); goto out; } hidden_wh_dir_dentry = lock_parent(hidden_wh_dentry); if (!(err = is_robranch_super(old_dentry->d_sb, bindex))) { err = vfs_unlink(hidden_wh_dir_dentry->d_inode, hidden_wh_dentry); } DPUT(hidden_wh_dentry); unlock_dir(hidden_wh_dir_dentry); if (err) goto out; } else DPUT(hidden_wh_dentry); DGET(hidden_old_dentry); hidden_old_dir_dentry = GET_PARENT(hidden_old_dentry); hidden_new_dir_dentry = GET_PARENT(hidden_new_dentry); lock_rename(hidden_old_dir_dentry, hidden_new_dir_dentry); err = is_robranch_super(old_dentry->d_sb, bindex); if (err) goto out_unlock; /* ready to whiteout for old_dentry. caller will create the actual whiteout, and must dput(*wh_old) */ if (wh_old) { char *whname; whname = alloc_whname(old_dentry->d_name.name, old_dentry->d_name.len); err = PTR_ERR(whname); if (IS_ERR(whname)) goto out_unlock; *wh_old = LOOKUP_ONE_LEN(whname, hidden_old_dir_dentry, old_dentry->d_name.len + WHLEN); KFREE(whname); err = PTR_ERR(*wh_old); if (IS_ERR(*wh_old)) { *wh_old = NULL; goto out_unlock; } } fist_print_dentry("NEWBEF", new_dentry); fist_print_dentry("OLDBEF", old_dentry); err = vfs_rename(hidden_old_dir_dentry->d_inode, hidden_old_dentry, hidden_new_dir_dentry->d_inode, hidden_new_dentry); fist_print_dentry("NEWAFT", new_dentry); fist_print_dentry("OLDAFT", old_dentry); out_unlock: unlock_rename(hidden_old_dir_dentry, hidden_new_dir_dentry); DPUT(hidden_old_dir_dentry); DPUT(hidden_new_dir_dentry); DPUT(hidden_old_dentry); out: if (!err) { /* Fixup the newdentry. */ if (bindex < dbstart(new_dentry)) set_dbstart(new_dentry, bindex); else if (bindex > dbend(new_dentry)) set_dbend(new_dentry, bindex); } KFREE(wh_name); fist_print_dentry("OUT: do_rename, old_dentry", old_dentry); fist_print_dentry("OUT: do_rename, new_dentry", new_dentry); print_exit_status(err); return err; }
/* This function replicates the directory structure upto given dentry * in the bindex branch. */ 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; print_entry_location(); verify_locked(dentry); /* There is no sense allocating any less than the minimum. */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) kmalloc_size = malloc_sizes[0].cs_size; #else kmalloc_size = 32; #endif num_dentry = kmalloc_size / sizeof(struct dentry *); if ((err = is_robranch_super(dir->i_sb, bindex))) { hidden_dentry = ERR_PTR(err); goto out; } fist_print_dentry("IN: create_parents_named", dentry); fist_dprint(8, "name = %s\n", name); old_bstart = dbstart(dentry); old_bend = dbend(dentry); path = (struct dentry **)KMALLOC(kmalloc_size, GFP_KERNEL); memset(path, 0, kmalloc_size); /* 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; lock_dentry(parent_dentry); /* find out the hidden_parent_dentry in the given branch */ hidden_parent_dentry = dtohd_index(parent_dentry, bindex); /* store the child dentry */ path[count++] = child_dentry; if (count == num_dentry) { old_kmalloc_size = kmalloc_size; kmalloc_size *= 2; num_dentry = kmalloc_size / sizeof(struct dentry *); tmp_path = (struct dentry **)KMALLOC(kmalloc_size, GFP_KERNEL); if (!tmp_path) { hidden_dentry = ERR_PTR(-ENOMEM); goto out; } memset(tmp_path, 0, kmalloc_size); memcpy(tmp_path, path, old_kmalloc_size); KFREE(path); path = tmp_path; tmp_path = NULL; } } while (!hidden_parent_dentry); count--; /* This is basically while(child_dentry != dentry). This loop is * horrible to follow and should be replaced with cleaner code. */ while (1) { PASSERT(child_dentry); PASSERT(parent_dentry); PASSERT(parent_dentry->d_inode); // get hidden parent dir in the current branch hidden_parent_dentry = dtohd_index(parent_dentry, bindex); unlock_dentry(parent_dentry); PASSERT(hidden_parent_dentry); PASSERT(hidden_parent_dentry->d_inode); // 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 { int loop_start; int loop_end; int new_bstart = -1; int new_bend = -1; int i; /* 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(dtohd_index(dentry, bindex)); set_dtohd_index(dentry, bindex, hidden_dentry); loop_start = (old_bstart < bindex) ? old_bstart : bindex; loop_end = (old_bend > bindex) ? old_bend : bindex; /* This loop sets the bstart and bend for the new * dentry by traversing from left to right. * It also dputs all negative dentries except * bindex (the newly looked dentry */ for (i = loop_start; i <= loop_end; i++) { if (!dtohd_index(dentry, i)) continue; if (i == bindex) { new_bend = i; if (new_bstart < 0) new_bstart = i; continue; } if (!dtohd_index(dentry, i)->d_inode) { DPUT(dtohd_index(dentry, i)); set_dtohd_index(dentry, i, NULL); } else { if (new_bstart < 0) new_bstart = i; new_bend = i; } } if (new_bstart < 0) new_bstart = bindex; if (new_bend < 0) new_bend = bindex; set_dbstart(dentry, new_bstart); set_dbend(dentry, new_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 { uid_t saved_uid = current->fsuid; gid_t saved_gid = current->fsgid; /* its a negative dentry, create a new dir */ hidden_parent_dentry = lock_parent(hidden_dentry); current->fsuid = hidden_parent_dentry->d_inode->i_uid; current->fsgid = hidden_parent_dentry->d_inode->i_gid; err = vfs_mkdir(hidden_parent_dentry->d_inode, hidden_dentry, S_IRWXUGO); current->fsuid = saved_uid; current->fsgid = saved_gid; unlock_dir(hidden_parent_dentry); if (err || !hidden_dentry->d_inode) { DPUT(hidden_dentry); hidden_dentry = ERR_PTR(err); goto out; } err = copyup_permissions(dir->i_sb, child_dentry, hidden_dentry); if (err) { DPUT(hidden_dentry); hidden_dentry = ERR_PTR(err); goto out; } set_itohi_index(child_dentry->d_inode, bindex, igrab(hidden_dentry->d_inode)); if (ibstart(child_dentry->d_inode) > bindex) ibstart(child_dentry->d_inode) = bindex; if (ibend(child_dentry->d_inode) < bindex) ibend(child_dentry->d_inode) = bindex; set_dtohd_index(child_dentry, bindex, hidden_dentry); if (dbstart(child_dentry) > bindex) set_dbstart(child_dentry, bindex); if (dbend(child_dentry) < bindex) set_dbend(child_dentry, bindex); } parent_dentry = child_dentry; child_dentry = path[--count]; } out: KFREE(path); fist_print_dentry("OUT: create_parents_named", dentry); print_exit_pointer(hidden_dentry); return hidden_dentry; }
static int unionfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry) { int err = 0; struct dentry *hidden_old_dentry = NULL; struct dentry *hidden_new_dentry = NULL; struct dentry *hidden_dir_dentry = NULL; struct dentry *whiteout_dentry; char *name = NULL; print_entry_location(); double_lock_dentry(new_dentry, old_dentry); hidden_new_dentry = dtohd(new_dentry); /* check if whiteout exists in the branch of new dentry, i.e. lookup * .wh.foo first. If present, delete it */ name = alloc_whname(new_dentry->d_name.name, new_dentry->d_name.len); if (IS_ERR(name)) { err = PTR_ERR(name); goto out; } whiteout_dentry = LOOKUP_ONE_LEN(name, hidden_new_dentry->d_parent, new_dentry->d_name.len + WHLEN); if (IS_ERR(whiteout_dentry)) { err = PTR_ERR(whiteout_dentry); goto out; } if (!whiteout_dentry->d_inode) { DPUT(whiteout_dentry); whiteout_dentry = NULL; } else { /* found a .wh.foo entry, unlink it and then call vfs_link() */ hidden_dir_dentry = lock_parent(whiteout_dentry); if (! (err = is_robranch_super(new_dentry->d_sb, dbstart(new_dentry)))) { err = vfs_unlink(hidden_dir_dentry->d_inode, whiteout_dentry); } fist_copy_attr_times(dir, hidden_dir_dentry->d_inode); dir->i_nlink = get_nlinks(dir); unlock_dir(hidden_dir_dentry); hidden_dir_dentry = NULL; DPUT(whiteout_dentry); if (err) goto out; } if (dbstart(old_dentry) != dbstart(new_dentry)) { hidden_new_dentry = create_parents(dir, new_dentry, dbstart(old_dentry)); err = PTR_ERR(hidden_new_dentry); if (IS_COPYUP_ERR(err)) goto docopyup; if (!hidden_new_dentry || IS_ERR(hidden_new_dentry)) goto out; } hidden_new_dentry = dtohd(new_dentry); hidden_old_dentry = dtohd(old_dentry); BUG_ON(dbstart(old_dentry) != dbstart(new_dentry)); hidden_dir_dentry = lock_parent(hidden_new_dentry); if (!(err = is_robranch(old_dentry))) err = vfs_link(hidden_old_dentry, hidden_dir_dentry->d_inode, hidden_new_dentry); unlock_dir(hidden_dir_dentry); docopyup: if (IS_COPYUP_ERR(err)) { int old_bstart = dbstart(old_dentry); int bindex; for (bindex = old_bstart - 1; bindex >= 0; bindex--) { err = copyup_dentry(old_dentry->d_parent-> d_inode, old_dentry, old_bstart, bindex, NULL, old_dentry->d_inode->i_size); if (!err) { hidden_new_dentry = create_parents(dir, new_dentry, bindex); hidden_old_dentry = dtohd(old_dentry); hidden_dir_dentry = lock_parent(hidden_new_dentry); /* do vfs_link */ err = vfs_link(hidden_old_dentry, hidden_dir_dentry->d_inode, hidden_new_dentry); unlock_dir(hidden_dir_dentry); goto check_link; } } goto out; } check_link: if (err || !hidden_new_dentry->d_inode) goto out; /* Its a hard link, so use the same inode */ new_dentry->d_inode = IGRAB(old_dentry->d_inode); d_instantiate(new_dentry, new_dentry->d_inode); fist_copy_attr_all(dir, hidden_new_dentry->d_parent->d_inode); /* propagate number of hard-links */ old_dentry->d_inode->i_nlink = get_nlinks(old_dentry->d_inode); out: if (!new_dentry->d_inode) d_drop(new_dentry); KFREE(name); unlock_dentry(new_dentry); unlock_dentry(old_dentry); print_exit_status(err); return err; }