static int unionfs_create(struct inode *dir, struct dentry *dentry, umode_t mode, bool want_excl) { int err = 0; struct dentry *lower_dentry = NULL; struct dentry *lower_parent_dentry = NULL; struct dentry *parent; int valid = 0; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); valid = __unionfs_d_revalidate(dentry, parent, false, 0); if (unlikely(!valid)) { err = -ESTALE; /* same as what real_lookup does */ goto out; } lower_dentry = find_writeable_branch(dir, dentry); if (IS_ERR(lower_dentry)) { err = PTR_ERR(lower_dentry); goto out; } lower_parent_dentry = lock_parent(lower_dentry); if (IS_ERR(lower_parent_dentry)) { err = PTR_ERR(lower_parent_dentry); goto out_unlock; } err = vfs_create(lower_parent_dentry->d_inode, lower_dentry, mode, want_excl); if (!err) { err = PTR_ERR(unionfs_interpose(dentry, dir->i_sb, 0)); if (!err) { unionfs_copy_attr_times(dir); fsstack_copy_inode_size(dir, lower_parent_dentry->d_inode); /* update no. of links on parent directory */ set_nlink(dir, unionfs_get_nlinks(dir)); } } out_unlock: unlock_dir(lower_parent_dentry); out: if (!err) { unionfs_postcopyup_setmnt(dentry); unionfs_check_inode(dir); unionfs_check_dentry(dentry); } unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
static int unionfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) { int err = 0; struct dentry *lower_dentry = NULL; struct dentry *lower_parent_dentry = NULL; struct dentry *parent; int bindex = 0, bstart; char *name = NULL; int valid; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); valid = __unionfs_d_revalidate(dentry, parent, false, 0); if (unlikely(!valid)) { err = -ESTALE; /* same as what real_lookup does */ goto out; } bstart = dbstart(dentry); lower_dentry = unionfs_lower_dentry(dentry); /* check for a whiteout in new dentry branch, and delete it */ err = check_unlink_whiteout(dentry, lower_dentry, bstart); if (err > 0) /* whiteout found and removed successfully */ err = 0; if (err) { /* exit if the error returned was NOT -EROFS */ if (!IS_COPYUP_ERR(err)) goto out; bstart--; } /* check if copyup's needed, and mkdir */ for (bindex = bstart; bindex >= 0; bindex--) { int i; int bend = dbend(dentry); if (is_robranch_super(dentry->d_sb, bindex)) continue; lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); if (!lower_dentry) { lower_dentry = create_parents(dir, dentry, dentry->d_name.name, bindex); if (!lower_dentry || IS_ERR(lower_dentry)) { printk(KERN_ERR "unionfs: lower dentry " " NULL for bindex = %d\n", bindex); continue; } } lower_parent_dentry = lock_parent(lower_dentry); if (IS_ERR(lower_parent_dentry)) { err = PTR_ERR(lower_parent_dentry); goto out; } err = vfs_mkdir(lower_parent_dentry->d_inode, lower_dentry, mode); unlock_dir(lower_parent_dentry); /* did the mkdir succeed? */ if (err) break; for (i = bindex + 1; i <= bend; i++) { /* XXX: use path_put_lowers? */ if (unionfs_lower_dentry_idx(dentry, i)) { dput(unionfs_lower_dentry_idx(dentry, i)); unionfs_set_lower_dentry_idx(dentry, i, NULL); } } dbend(dentry) = bindex; /* * Only INTERPOSE_LOOKUP can return a value other than 0 on * err. */ err = PTR_ERR(unionfs_interpose(dentry, dir->i_sb, 0)); if (!err) { unionfs_copy_attr_times(dir); fsstack_copy_inode_size(dir, lower_parent_dentry->d_inode); /* update number of links on parent directory */ set_nlink(dir, unionfs_get_nlinks(dir)); } err = make_dir_opaque(dentry, dbstart(dentry)); if (err) { printk(KERN_ERR "unionfs: mkdir: error creating " ".wh.__dir_opaque: %d\n", err); goto out; } /* we are done! */ break; } out: if (!dentry->d_inode) d_drop(dentry); kfree(name); if (!err) { unionfs_copy_attr_times(dentry->d_inode); unionfs_postcopyup_setmnt(dentry); } unionfs_check_inode(dir); unionfs_check_dentry(dentry); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
static int unionfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev) { int err = 0; struct dentry *lower_dentry = NULL; struct dentry *wh_dentry = NULL; struct dentry *lower_parent_dentry = NULL; struct dentry *parent; char *name = NULL; int valid = 0; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); valid = __unionfs_d_revalidate(dentry, parent, false, 0); if (unlikely(!valid)) { err = -ESTALE; goto out; } /* * It's only a bug if this dentry was not negative and couldn't be * revalidated (shouldn't happen). */ BUG_ON(!valid && dentry->d_inode); lower_dentry = find_writeable_branch(dir, dentry); if (IS_ERR(lower_dentry)) { err = PTR_ERR(lower_dentry); goto out; } lower_parent_dentry = lock_parent(lower_dentry); if (IS_ERR(lower_parent_dentry)) { err = PTR_ERR(lower_parent_dentry); goto out_unlock; } err = vfs_mknod(lower_parent_dentry->d_inode, lower_dentry, mode, dev); if (!err) { err = PTR_ERR(unionfs_interpose(dentry, dir->i_sb, 0)); if (!err) { unionfs_copy_attr_times(dir); fsstack_copy_inode_size(dir, lower_parent_dentry->d_inode); /* update no. of links on parent directory */ set_nlink(dir, unionfs_get_nlinks(dir)); } } out_unlock: unlock_dir(lower_parent_dentry); out: dput(wh_dentry); kfree(name); if (!err) { unionfs_postcopyup_setmnt(dentry); unionfs_check_inode(dir); unionfs_check_dentry(dentry); } unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
struct dentry *unionfs_lookup_backend(struct dentry *dentry, struct nameidata *nd, int lookupmode) { int err = 0; struct dentry *hidden_dentry = NULL; struct dentry *wh_hidden_dentry = NULL; struct dentry *hidden_dir_dentry = NULL; struct dentry *parent_dentry = NULL; int bindex, bstart, bend, bopaque; int dentry_count = 0; /* Number of positive dentries. */ int first_dentry_offset = -1; struct dentry *first_hidden_dentry = NULL; struct vfsmount *first_hidden_mnt = NULL; int locked_parent = 0; int locked_child = 0; int opaque; char *whname = NULL; const char *name; int namelen; /* We should already have a lock on this dentry in the case of a * partial lookup, or a revalidation. Otherwise it is returned from * new_dentry_private_data already locked. */ if (lookupmode == INTERPOSE_PARTIAL || lookupmode == INTERPOSE_REVAL || lookupmode == INTERPOSE_REVAL_NEG) verify_locked(dentry); else { BUG_ON(UNIONFS_D(dentry) != NULL); locked_child = 1; } if (lookupmode != INTERPOSE_PARTIAL) if ((err = new_dentry_private_data(dentry))) goto out; /* must initialize dentry operations */ dentry->d_op = &unionfs_dops; parent_dentry = dget_parent(dentry); /* We never partial lookup the root directory. */ if (parent_dentry != dentry) { unionfs_lock_dentry(parent_dentry); locked_parent = 1; } else { dput(parent_dentry); parent_dentry = NULL; goto out; } name = dentry->d_name.name; namelen = dentry->d_name.len; /* No dentries should get created for possible whiteout names. */ if (!is_validname(name)) { err = -EPERM; goto out_free; } /* Now start the actual lookup procedure. */ bstart = dbstart(parent_dentry); bend = dbend(parent_dentry); bopaque = dbopaque(parent_dentry); BUG_ON(bstart < 0); /* It would be ideal if we could convert partial lookups to only have * to do this work when they really need to. It could probably improve * performance quite a bit, and maybe simplify the rest of the code. */ if (lookupmode == INTERPOSE_PARTIAL) { bstart++; if ((bopaque != -1) && (bopaque < bend)) bend = bopaque; } for (bindex = bstart; bindex <= bend; bindex++) { hidden_dentry = unionfs_lower_dentry_idx(dentry, bindex); if (lookupmode == INTERPOSE_PARTIAL && hidden_dentry) continue; BUG_ON(hidden_dentry != NULL); hidden_dir_dentry = unionfs_lower_dentry_idx(parent_dentry, bindex); /* if the parent hidden dentry does not exist skip this */ if (!(hidden_dir_dentry && hidden_dir_dentry->d_inode)) continue; /* also skip it if the parent isn't a directory. */ if (!S_ISDIR(hidden_dir_dentry->d_inode->i_mode)) continue; /* Reuse the whiteout name because its value doesn't change. */ if (!whname) { whname = alloc_whname(name, namelen); if (IS_ERR(whname)) { err = PTR_ERR(whname); goto out_free; } } /* check if whiteout exists in this branch: lookup .wh.foo */ wh_hidden_dentry = lookup_one_len(whname, hidden_dir_dentry, namelen + UNIONFS_WHLEN); if (IS_ERR(wh_hidden_dentry)) { dput(first_hidden_dentry); mntput(first_hidden_mnt); err = PTR_ERR(wh_hidden_dentry); goto out_free; } if (wh_hidden_dentry->d_inode) { /* We found a whiteout so lets give up. */ if (S_ISREG(wh_hidden_dentry->d_inode->i_mode)) { set_dbend(dentry, bindex); set_dbopaque(dentry, bindex); dput(wh_hidden_dentry); break; } err = -EIO; printk(KERN_NOTICE "EIO: Invalid whiteout entry type" " %d.\n", wh_hidden_dentry->d_inode->i_mode); dput(wh_hidden_dentry); dput(first_hidden_dentry); mntput(first_hidden_mnt); goto out_free; } dput(wh_hidden_dentry); wh_hidden_dentry = NULL; /* Now do regular lookup; lookup foo */ nd->dentry = unionfs_lower_dentry_idx(dentry, bindex); /* FIXME: fix following line for mount point crossing */ nd->mnt = unionfs_lower_mnt_idx(parent_dentry, bindex); hidden_dentry = lookup_one_len_nd(name, hidden_dir_dentry, namelen, nd); if (IS_ERR(hidden_dentry)) { dput(first_hidden_dentry); mntput(first_hidden_mnt); err = PTR_ERR(hidden_dentry); goto out_free; } /* Store the first negative dentry specially, because if they * are all negative we need this for future creates. */ if (!hidden_dentry->d_inode) { if (!first_hidden_dentry && (dbstart(dentry) == -1)) { first_hidden_dentry = hidden_dentry; /* FIXME: following line needs to be changed * to allow mountpoint crossing */ first_hidden_mnt = mntget( unionfs_lower_mnt_idx(parent_dentry, bindex)); first_dentry_offset = bindex; } else dput(hidden_dentry); continue; } /* number of positive dentries */ dentry_count++; /* store underlying dentry */ if (dbstart(dentry) == -1) set_dbstart(dentry, bindex); unionfs_set_lower_dentry_idx(dentry, bindex, hidden_dentry); /* FIXME: the following line needs to get fixed to allow * mountpoint crossing */ unionfs_set_lower_mnt_idx(dentry, bindex, mntget(unionfs_lower_mnt_idx(parent_dentry, bindex))); set_dbend(dentry, bindex); /* update parent directory's atime with the bindex */ fsstack_copy_attr_atime(parent_dentry->d_inode, hidden_dir_dentry->d_inode); /* We terminate file lookups here. */ if (!S_ISDIR(hidden_dentry->d_inode->i_mode)) { if (lookupmode == INTERPOSE_PARTIAL) continue; if (dentry_count == 1) goto out_positive; /* This can only happen with mixed D-*-F-* */ BUG_ON(!S_ISDIR(unionfs_lower_dentry(dentry)->d_inode->i_mode)); continue; } opaque = is_opaque_dir(dentry, bindex); if (opaque < 0) { dput(first_hidden_dentry); mntput(first_hidden_mnt); err = opaque; goto out_free; } else if (opaque) { set_dbend(dentry, bindex); set_dbopaque(dentry, bindex); break; } } if (dentry_count) goto out_positive; else goto out_negative; out_negative: if (lookupmode == INTERPOSE_PARTIAL) goto out; /* If we've only got negative dentries, then use the leftmost one. */ if (lookupmode == INTERPOSE_REVAL) { if (dentry->d_inode) UNIONFS_I(dentry->d_inode)->stale = 1; goto out; } /* This should only happen if we found a whiteout. */ if (first_dentry_offset == -1) { nd->dentry = dentry; /* FIXME: fix following line for mount point crossing */ nd->mnt = unionfs_lower_mnt_idx(parent_dentry, bindex); first_hidden_dentry = lookup_one_len_nd(name, hidden_dir_dentry, namelen, nd); first_dentry_offset = bindex; if (IS_ERR(first_hidden_dentry)) { err = PTR_ERR(first_hidden_dentry); goto out; } /* FIXME: the following line needs to be changed to allow * mountpoint crossing */ first_hidden_mnt = mntget(unionfs_lower_mnt_idx(dentry, bindex)); } unionfs_set_lower_dentry_idx(dentry, first_dentry_offset, first_hidden_dentry); unionfs_set_lower_mnt_idx(dentry, first_dentry_offset, first_hidden_mnt); set_dbstart(dentry, first_dentry_offset); set_dbend(dentry, first_dentry_offset); if (lookupmode == INTERPOSE_REVAL_NEG) BUG_ON(dentry->d_inode != NULL); else d_add(dentry, NULL); goto out; /* This part of the code is for positive dentries. */ out_positive: BUG_ON(dentry_count <= 0); /* If we're holding onto the first negative dentry & corresponding * vfsmount - throw it out. */ dput(first_hidden_dentry); mntput(first_hidden_mnt); /* Partial lookups need to reinterpose, or throw away older negs. */ if (lookupmode == INTERPOSE_PARTIAL) { if (dentry->d_inode) { unionfs_reinterpose(dentry); goto out; } /* This somehow turned positive, so it is as if we had a * negative revalidation. */ lookupmode = INTERPOSE_REVAL_NEG; update_bstart(dentry); bstart = dbstart(dentry); bend = dbend(dentry); } err = unionfs_interpose(dentry, dentry->d_sb, lookupmode); if (err) goto out_drop; goto out; out_drop: d_drop(dentry); out_free: /* should dput all the underlying dentries on error condition */ bstart = dbstart(dentry); if (bstart >= 0) { bend = dbend(dentry); for (bindex = bstart; bindex <= bend; bindex++) { dput(unionfs_lower_dentry_idx(dentry, bindex)); mntput(unionfs_lower_mnt_idx(dentry, bindex)); } } kfree(UNIONFS_D(dentry)->lower_paths); UNIONFS_D(dentry)->lower_paths = NULL; set_dbstart(dentry, -1); set_dbend(dentry, -1); out: if (!err && UNIONFS_D(dentry)) { BUG_ON(dbend(dentry) > UNIONFS_D(dentry)->bcount); BUG_ON(dbend(dentry) > sbmax(dentry->d_sb)); BUG_ON(dbstart(dentry) < 0); } kfree(whname); if (locked_parent) unionfs_unlock_dentry(parent_dentry); dput(parent_dentry); if (locked_child) unionfs_unlock_dentry(dentry); return ERR_PTR(err); }
/* * Main (and complex) driver function for Unionfs's lookup * * Returns: NULL (ok), ERR_PTR if an error occurred, or a non-null non-error * PTR if d_splice returned a different dentry. * * If lookupmode is INTERPOSE_PARTIAL/REVAL/REVAL_NEG, the passed dentry's * inode info must be locked. If lookupmode is INTERPOSE_LOOKUP (i.e., a * newly looked-up dentry), then unionfs_lookup_backend will return a locked * dentry's info, which the caller must unlock. */ struct dentry *unionfs_lookup_full(struct dentry *dentry, struct dentry *parent, int lookupmode) { int err = 0; struct dentry *lower_dentry = NULL; struct vfsmount *lower_mnt; struct vfsmount *lower_dir_mnt; struct dentry *wh_lower_dentry = NULL; struct dentry *lower_dir_dentry = NULL; struct dentry *d_interposed = NULL; int bindex, bstart, bend, bopaque; int opaque, num_positive = 0; const char *name; int namelen; int pos_start, pos_end; /* * 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. */ verify_locked(dentry); verify_locked(parent); /* must initialize dentry operations */ dentry->d_op = &unionfs_dops; /* We never partial lookup the root directory. */ if (IS_ROOT(dentry)) goto out; name = dentry->d_name.name; namelen = dentry->d_name.len; /* No dentries should get created for possible whiteout names. */ if (!is_validname(name)) { err = -EPERM; goto out_free; } /* Now start the actual lookup procedure. */ bstart = dbstart(parent); bend = dbend(parent); bopaque = dbopaque(parent); BUG_ON(bstart < 0); /* adjust bend to bopaque if needed */ if ((bopaque >= 0) && (bopaque < bend)) bend = bopaque; /* lookup all possible dentries */ for (bindex = bstart; bindex <= bend; bindex++) { lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); lower_mnt = unionfs_lower_mnt_idx(dentry, bindex); /* skip if we already have a positive lower dentry */ if (lower_dentry) { if (dbstart(dentry) < 0) dbstart(dentry) = bindex; if (bindex > dbend(dentry)) dbend(dentry) = bindex; if (lower_dentry->d_inode) num_positive++; continue; } lower_dir_dentry = unionfs_lower_dentry_idx(parent, bindex); /* if the lower dentry's parent does not exist, skip this */ if (!lower_dir_dentry || !lower_dir_dentry->d_inode) continue; /* also skip it if the parent isn't a directory. */ if (!S_ISDIR(lower_dir_dentry->d_inode->i_mode)) continue; /* XXX: should be BUG_ON */ /* check for whiteouts: stop lookup if found */ wh_lower_dentry = lookup_whiteout(name, lower_dir_dentry); if (IS_ERR(wh_lower_dentry)) { err = PTR_ERR(wh_lower_dentry); goto out_free; } if (wh_lower_dentry->d_inode) { dbend(dentry) = dbopaque(dentry) = bindex; if (dbstart(dentry) < 0) dbstart(dentry) = bindex; dput(wh_lower_dentry); break; } dput(wh_lower_dentry); /* Now do regular lookup; lookup @name */ lower_dir_mnt = unionfs_lower_mnt_idx(parent, bindex); lower_mnt = NULL; /* XXX: needed? */ lower_dentry = __lookup_one(lower_dir_dentry, lower_dir_mnt, name, &lower_mnt); if (IS_ERR(lower_dentry)) { err = PTR_ERR(lower_dentry); goto out_free; } unionfs_set_lower_dentry_idx(dentry, bindex, lower_dentry); if (!lower_mnt) lower_mnt = unionfs_mntget(dentry->d_sb->s_root, bindex); unionfs_set_lower_mnt_idx(dentry, bindex, lower_mnt); /* adjust dbstart/end */ if (dbstart(dentry) < 0) dbstart(dentry) = bindex; if (bindex > dbend(dentry)) dbend(dentry) = bindex; /* * We always store the lower dentries above, and update * dbstart/dbend, even if the whole unionfs dentry is * negative (i.e., no lower inodes). */ if (!lower_dentry->d_inode) continue; num_positive++; /* * check if we just found an opaque directory, if so, stop * lookups here. */ if (!S_ISDIR(lower_dentry->d_inode->i_mode)) continue; opaque = is_opaque_dir(dentry, bindex); if (opaque < 0) { err = opaque; goto out_free; } else if (opaque) { dbend(dentry) = dbopaque(dentry) = bindex; break; } dbend(dentry) = bindex; /* update parent directory's atime with the bindex */ fsstack_copy_attr_atime(parent->d_inode, lower_dir_dentry->d_inode); } /* sanity checks, then decide if to process a negative dentry */ BUG_ON(dbstart(dentry) < 0 && dbend(dentry) >= 0); BUG_ON(dbstart(dentry) >= 0 && dbend(dentry) < 0); if (num_positive > 0) goto out_positive; /*** handle NEGATIVE dentries ***/ /* * If negative, keep only first lower negative dentry, to save on * memory. */ if (dbstart(dentry) < dbend(dentry)) { path_put_lowers(dentry, dbstart(dentry) + 1, dbend(dentry), false); dbend(dentry) = dbstart(dentry); } if (lookupmode == INTERPOSE_PARTIAL) goto out; if (lookupmode == INTERPOSE_LOOKUP) { /* * If all we found was a whiteout in the first available * branch, then create a negative dentry for a possibly new * file to be created. */ if (dbopaque(dentry) < 0) goto out; /* XXX: need to get mnt here */ bindex = dbstart(dentry); if (unionfs_lower_dentry_idx(dentry, bindex)) goto out; lower_dir_dentry = unionfs_lower_dentry_idx(parent, bindex); if (!lower_dir_dentry || !lower_dir_dentry->d_inode) goto out; if (!S_ISDIR(lower_dir_dentry->d_inode->i_mode)) goto out; /* XXX: should be BUG_ON */ /* XXX: do we need to cross bind mounts here? */ lower_dentry = lookup_one_len(name, lower_dir_dentry, namelen); if (IS_ERR(lower_dentry)) { err = PTR_ERR(lower_dentry); goto out; } /* XXX: need to mntget/mntput as needed too! */ unionfs_set_lower_dentry_idx(dentry, bindex, lower_dentry); /* XXX: wrong mnt for crossing bind mounts! */ lower_mnt = unionfs_mntget(dentry->d_sb->s_root, bindex); unionfs_set_lower_mnt_idx(dentry, bindex, lower_mnt); goto out; } /* if we're revalidating a positive dentry, don't make it negative */ if (lookupmode != INTERPOSE_REVAL) d_add(dentry, NULL); goto out; out_positive: /*** handle POSITIVE dentries ***/ /* * This unionfs dentry is positive (at least one lower inode * exists), so scan entire dentry from beginning to end, and remove * any negative lower dentries, if any. Then, update dbstart/dbend * to reflect the start/end of positive dentries. */ pos_start = pos_end = -1; for (bindex = bstart; bindex <= bend; bindex++) { lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); if (lower_dentry && lower_dentry->d_inode) { if (pos_start < 0) pos_start = bindex; if (bindex > pos_end) pos_end = bindex; continue; } path_put_lowers(dentry, bindex, bindex, false); } if (pos_start >= 0) dbstart(dentry) = pos_start; if (pos_end >= 0) dbend(dentry) = pos_end; /* Partial lookups need to re-interpose, or throw away older negs. */ if (lookupmode == INTERPOSE_PARTIAL) { if (dentry->d_inode) { unionfs_reinterpose(dentry); goto out; } /* * This dentry was positive, so it is as if we had a * negative revalidation. */ lookupmode = INTERPOSE_REVAL_NEG; update_bstart(dentry); } /* * Interpose can return a dentry if d_splice returned a different * dentry. */ d_interposed = unionfs_interpose(dentry, dentry->d_sb, lookupmode); if (IS_ERR(d_interposed)) err = PTR_ERR(d_interposed); else if (d_interposed) dentry = d_interposed; if (!err) goto out; d_drop(dentry); out_free: /* should dput/mntput all the underlying dentries on error condition */ if (dbstart(dentry) >= 0) path_put_lowers_all(dentry, false); /* free lower_paths unconditionally */ kfree(UNIONFS_D(dentry)->lower_paths); UNIONFS_D(dentry)->lower_paths = NULL; out: if (dentry && UNIONFS_D(dentry)) { BUG_ON(dbstart(dentry) < 0 && dbend(dentry) >= 0); BUG_ON(dbstart(dentry) >= 0 && dbend(dentry) < 0); } if (d_interposed && UNIONFS_D(d_interposed)) { BUG_ON(dbstart(d_interposed) < 0 && dbend(d_interposed) >= 0); BUG_ON(dbstart(d_interposed) >= 0 && dbend(d_interposed) < 0); } if (!err && d_interposed) return d_interposed; return ERR_PTR(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); }
/* * our custom d_alloc_root work-alike * * 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 */ 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 (likely(ret)) { ret->d_op = &unionfs_dops; ret->d_sb = sb; ret->d_parent = ret; } } return ret; } /* * There is no need to lock the unionfs_super_info's rwsem as there is no * way anyone can have a reference to the superblock at this point in time. */ static int unionfs_read_super(struct super_block *sb, void *raw_data, int silent) { int err = 0; struct unionfs_dentry_info *lower_root_info = NULL; int bindex, bstart, bend; if (!raw_data) { printk(KERN_ERR "unionfs: read_super: missing data argument\n"); err = -EINVAL; goto out; } /* Allocate superblock private data */ sb->s_fs_info = kzalloc(sizeof(struct unionfs_sb_info), GFP_KERNEL); if (unlikely(!UNIONFS_SB(sb))) { printk(KERN_CRIT "unionfs: read_super: out of memory\n"); err = -ENOMEM; goto out; } UNIONFS_SB(sb)->bend = -1; atomic_set(&UNIONFS_SB(sb)->generation, 1); init_rwsem(&UNIONFS_SB(sb)->rwsem); UNIONFS_SB(sb)->high_branch_id = -1; /* -1 == invalid branch ID */ lower_root_info = unionfs_parse_options(sb, raw_data); if (IS_ERR(lower_root_info)) { printk(KERN_ERR "unionfs: read_super: error while parsing options " "(err = %ld)\n", PTR_ERR(lower_root_info)); err = PTR_ERR(lower_root_info); lower_root_info = NULL; goto out_free; } if (lower_root_info->bstart == -1) { err = -ENOENT; goto out_free; } /* set the lower superblock field of upper superblock */ bstart = lower_root_info->bstart; BUG_ON(bstart != 0); sbend(sb) = bend = lower_root_info->bend; for (bindex = bstart; bindex <= bend; bindex++) { struct dentry *d = lower_root_info->lower_paths[bindex].dentry; atomic_inc(&d->d_sb->s_active); unionfs_set_lower_super_idx(sb, bindex, d->d_sb); } /* max Bytes is the maximum bytes from highest priority branch */ sb->s_maxbytes = unionfs_lower_super_idx(sb, 0)->s_maxbytes; /* * Our c/m/atime granularity is 1 ns because we may stack on file * systems whose granularity is as good. This is important for our * time-based cache coherency. */ sb->s_time_gran = 1; sb->s_op = &unionfs_sops; /* See comment next to the definition of unionfs_d_alloc_root */ sb->s_root = unionfs_d_alloc_root(sb); if (unlikely(!sb->s_root)) { err = -ENOMEM; goto out_dput; } /* link the upper and lower dentries */ sb->s_root->d_fsdata = NULL; err = new_dentry_private_data(sb->s_root, UNIONFS_DMUTEX_ROOT); if (unlikely(err)) goto out_freedpd; /* Set the lower dentries for s_root */ for (bindex = bstart; bindex <= bend; bindex++) { struct dentry *d; struct vfsmount *m; d = lower_root_info->lower_paths[bindex].dentry; m = lower_root_info->lower_paths[bindex].mnt; unionfs_set_lower_dentry_idx(sb->s_root, bindex, d); unionfs_set_lower_mnt_idx(sb->s_root, bindex, m); } dbstart(sb->s_root) = bstart; dbend(sb->s_root) = bend; /* Set the generation number to one, since this is for the mount. */ atomic_set(&UNIONFS_D(sb->s_root)->generation, 1); /* * Call interpose to create the upper level inode. Only * INTERPOSE_LOOKUP can return a value other than 0 on err. */ err = PTR_ERR(unionfs_interpose(sb->s_root, sb, 0)); unionfs_unlock_dentry(sb->s_root); if (!err) goto out; /* else fall through */ out_freedpd: if (UNIONFS_D(sb->s_root)) { kfree(UNIONFS_D(sb->s_root)->lower_paths); free_dentry_private_data(sb->s_root); } dput(sb->s_root); out_dput: if (lower_root_info && !IS_ERR(lower_root_info)) { for (bindex = lower_root_info->bstart; bindex <= lower_root_info->bend; bindex++) { struct dentry *d; struct vfsmount *m; d = lower_root_info->lower_paths[bindex].dentry; m = lower_root_info->lower_paths[bindex].mnt; dput(d); /* initializing: can't use unionfs_mntput here */ mntput(m); /* drop refs we took earlier */ atomic_dec(&d->d_sb->s_active); } kfree(lower_root_info->lower_paths); kfree(lower_root_info); lower_root_info = NULL; } out_free: kfree(UNIONFS_SB(sb)->data); kfree(UNIONFS_SB(sb)); sb->s_fs_info = NULL; out: if (lower_root_info && !IS_ERR(lower_root_info)) { kfree(lower_root_info->lower_paths); kfree(lower_root_info); } return 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; }
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; }
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_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_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; }