void unionfs_reinterpose(struct dentry *dentry) { struct dentry *hidden_dentry; struct inode *inode; int bindex, bstart, bend; print_entry_location(); verify_locked(dentry); fist_print_dentry("IN: unionfs_reinterpose: ", dentry); /* This is pre-allocated inode */ inode = dentry->d_inode; bstart = dbstart(dentry); bend = dbend(dentry); for (bindex = bstart; bindex <= bend; bindex++) { hidden_dentry = dtohd_index(dentry, bindex); if (!hidden_dentry) continue; if (!hidden_dentry->d_inode) continue; if (itohi_index(inode, bindex)) continue; set_itohi_index(inode, bindex, IGRAB(hidden_dentry->d_inode)); } ibstart(inode) = dbstart(dentry); ibend(inode) = dbend(dentry); fist_print_dentry("OUT: unionfs_reinterpose: ", dentry); fist_print_inode("OUT: unionfs_reinterpose: ", inode); print_exit_location(); }
/* like interpose above, but for an already existing dentry */ void unionfs_reinterpose(struct dentry *dentry) { struct dentry *lower_dentry; struct inode *inode; int bindex, bstart, bend; verify_locked(dentry); /* This is pre-allocated inode */ inode = dentry->d_inode; bstart = dbstart(dentry); bend = dbend(dentry); for (bindex = bstart; bindex <= bend; bindex++) { lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); if (!lower_dentry) continue; if (!lower_dentry->d_inode) continue; if (unionfs_lower_inode_idx(inode, bindex)) continue; unionfs_set_lower_inode_idx(inode, bindex, igrab(lower_dentry->d_inode)); } ibstart(inode) = dbstart(dentry); ibend(inode) = dbend(dentry); }
/* * Revalidate the struct file * @file: file to revalidate * @parent: parent dentry (locked by caller) * @willwrite: true if caller may cause changes to the file; false otherwise. * Caller must lock/unlock dentry's branch configuration. */ int unionfs_file_revalidate(struct file *file, struct dentry *parent, bool willwrite) { struct super_block *sb; struct dentry *dentry; int sbgen, dgen; int err = 0; dentry = file->f_path.dentry; sb = dentry->d_sb; verify_locked(dentry); verify_locked(parent); /* * First revalidate the dentry inside struct file, * but not unhashed dentries. */ if (!d_deleted(dentry) && !__unionfs_d_revalidate(dentry, parent, willwrite)) { err = -ESTALE; goto out; } sbgen = atomic_read(&UNIONFS_SB(sb)->generation); dgen = atomic_read(&UNIONFS_D(dentry)->generation); if (unlikely(sbgen > dgen)) { /* XXX: should never happen */ pr_debug("unionfs: failed to revalidate dentry (%s)\n", dentry->d_name.name); err = -ESTALE; goto out; } err = __unionfs_file_revalidate(file, dentry, parent, sb, sbgen, dgen, willwrite); out: return err; }
/* * This is a helper function for rename, used when rename ends up with hosed * over dentries and we need to revert. */ static int unionfs_refresh_lower_dentry(struct dentry *dentry, struct dentry *parent, int bindex) { struct dentry *lower_dentry; struct dentry *lower_parent; int err = 0; verify_locked(dentry); lower_parent = unionfs_lower_dentry_idx(parent, bindex); BUG_ON(!S_ISDIR(lower_parent->d_inode->i_mode)); lower_dentry = lookup_one_len(dentry->d_name.name, lower_parent, dentry->d_name.len); if (IS_ERR(lower_dentry)) { err = PTR_ERR(lower_dentry); goto out; } dput(unionfs_lower_dentry_idx(dentry, bindex)); iput(unionfs_lower_inode_idx(dentry->d_inode, bindex)); unionfs_set_lower_inode_idx(dentry->d_inode, bindex, NULL); if (!lower_dentry->d_inode) { dput(lower_dentry); unionfs_set_lower_dentry_idx(dentry, bindex, NULL); } else { unionfs_set_lower_dentry_idx(dentry, bindex, lower_dentry); unionfs_set_lower_inode_idx(dentry->d_inode, bindex, igrab(lower_dentry->d_inode)); } out: return 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); }
/* * This function replicates the directory structure up-to given dentry * in the bindex branch. */ struct dentry *create_parents(struct inode *dir, struct dentry *dentry, const char *name, int bindex) { int err; struct dentry *child_dentry; struct dentry *parent_dentry; struct dentry *lower_parent_dentry = NULL; struct dentry *lower_dentry = NULL; const char *childname; unsigned int childnamelen; int nr_dentry; int count = 0; int old_bstart; int old_bend; struct dentry **path = NULL; struct super_block *sb; verify_locked(dentry); err = is_robranch_super(dir->i_sb, bindex); if (err) { lower_dentry = ERR_PTR(err); goto out; } old_bstart = dbstart(dentry); old_bend = dbend(dentry); lower_dentry = ERR_PTR(-ENOMEM); /* There is no sense allocating any less than the minimum. */ nr_dentry = 1; path = kmalloc(nr_dentry * sizeof(struct dentry *), GFP_KERNEL); if (unlikely(!path)) goto out; /* assume the negative dentry of unionfs as the parent dentry */ parent_dentry = dentry; /* * 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 = dget_parent(child_dentry); /* find out the lower_parent_dentry in the given branch */ lower_parent_dentry = unionfs_lower_dentry_idx(parent_dentry, bindex); /* grow path table */ if (count == nr_dentry) { void *p; nr_dentry *= 2; p = krealloc(path, nr_dentry * sizeof(struct dentry *), GFP_KERNEL); if (unlikely(!p)) { lower_dentry = ERR_PTR(-ENOMEM); goto out; } path = p; } /* store the child dentry */ path[count++] = child_dentry; } while (!lower_parent_dentry); count--; sb = dentry->d_sb; /* * This code goes between the begin/end labels and basically * emulates a while(child_dentry != dentry), only cleaner and * shorter than what would be a much longer while loop. */ begin: /* get lower parent dir in the current branch */ lower_parent_dentry = unionfs_lower_dentry_idx(parent_dentry, bindex); dput(parent_dentry); /* init the values to lookup */ childname = child_dentry->d_name.name; childnamelen = child_dentry->d_name.len; if (child_dentry != dentry) { /* lookup child in the underlying file system */ lower_dentry = lookup_one_len(childname, lower_parent_dentry, childnamelen); if (IS_ERR(lower_dentry)) goto out; } else { /* * Is the name a whiteout of the child name ? lookup the * whiteout child in the underlying file system */ lower_dentry = lookup_one_len(name, lower_parent_dentry, strlen(name)); if (IS_ERR(lower_dentry)) goto out; /* Replace the current dentry (if any) with the new one */ dput(unionfs_lower_dentry_idx(dentry, bindex)); unionfs_set_lower_dentry_idx(dentry, bindex, lower_dentry); __cleanup_dentry(dentry, bindex, old_bstart, old_bend); goto out; } if (lower_dentry->d_inode) { /* * since this already exists we dput to avoid * multiple references on the same dentry */ dput(lower_dentry); } else { struct sioq_args args; /* it's a negative dentry, create a new dir */ lower_parent_dentry = lock_parent(lower_dentry); args.mkdir.parent = lower_parent_dentry->d_inode; args.mkdir.dentry = lower_dentry; args.mkdir.mode = child_dentry->d_inode->i_mode; run_sioq(__unionfs_mkdir, &args); err = args.err; if (!err) err = copyup_permissions(dir->i_sb, child_dentry, lower_dentry); unlock_dir(lower_parent_dentry); if (err) { dput(lower_dentry); lower_dentry = ERR_PTR(err); goto out; } } __set_inode(child_dentry, lower_dentry, bindex); __set_dentry(child_dentry, lower_dentry, bindex); /* * update times of this dentry, but also the parent, because if * we changed, the parent may have changed too. */ fsstack_copy_attr_times(parent_dentry->d_inode, lower_parent_dentry->d_inode); unionfs_copy_attr_times(child_dentry->d_inode); parent_dentry = child_dentry; child_dentry = path[--count]; goto begin; out: /* cleanup any leftover locks from the do/while loop above */ if (IS_ERR(lower_dentry)) while (count) dput(path[count--]); kfree(path); return lower_dentry; }
/* * Copy up a dentry to a file of specified name. * * @dir: used to pull the ->i_sb to access other branches * @dentry: the non-negative dentry whose lower_inode we should copy * @bstart: the branch of the lower_inode to copy from * @new_bindex: the branch to create the new file in * @name: the name of the file to create * @namelen: length of @name * @copyup_file: the "struct file" to return (optional) * @len: how many bytes to copy-up? */ int copyup_dentry(struct inode *dir, struct dentry *dentry, int bstart, int new_bindex, const char *name, int namelen, struct file **copyup_file, loff_t len) { struct dentry *new_lower_dentry; struct dentry *old_lower_dentry = NULL; struct super_block *sb; int err = 0; int old_bindex; int old_bstart; int old_bend; struct dentry *new_lower_parent_dentry = NULL; mm_segment_t oldfs; char *symbuf = NULL; verify_locked(dentry); old_bindex = bstart; old_bstart = dbstart(dentry); old_bend = dbend(dentry); BUG_ON(new_bindex < 0); BUG_ON(new_bindex >= old_bindex); sb = dir->i_sb; err = is_robranch_super(sb, new_bindex); if (err) goto out; /* Create the directory structure above this dentry. */ new_lower_dentry = create_parents(dir, dentry, name, new_bindex); if (IS_ERR(new_lower_dentry)) { err = PTR_ERR(new_lower_dentry); goto out; } old_lower_dentry = unionfs_lower_dentry_idx(dentry, old_bindex); /* we conditionally dput this old_lower_dentry at end of function */ dget(old_lower_dentry); /* For symlinks, we must read the link before we lock the directory. */ if (S_ISLNK(old_lower_dentry->d_inode->i_mode)) { symbuf = kmalloc(PATH_MAX, GFP_KERNEL); if (unlikely(!symbuf)) { __clear(dentry, old_lower_dentry, old_bstart, old_bend, new_lower_dentry, new_bindex); err = -ENOMEM; goto out_free; } oldfs = get_fs(); set_fs(KERNEL_DS); err = old_lower_dentry->d_inode->i_op->readlink( old_lower_dentry, (char __user *)symbuf, PATH_MAX); set_fs(oldfs); if (err < 0) { __clear(dentry, old_lower_dentry, old_bstart, old_bend, new_lower_dentry, new_bindex); goto out_free; } symbuf[err] = '\0'; } /* Now we lock the parent, and create the object in the new branch. */ new_lower_parent_dentry = lock_parent(new_lower_dentry); /* create the new inode */ err = __copyup_ndentry(old_lower_dentry, new_lower_dentry, new_lower_parent_dentry, symbuf); if (err) { __clear(dentry, old_lower_dentry, old_bstart, old_bend, new_lower_dentry, new_bindex); goto out_unlock; } /* We actually copyup the file here. */ if (S_ISREG(old_lower_dentry->d_inode->i_mode)) err = __copyup_reg_data(dentry, new_lower_dentry, new_bindex, old_lower_dentry, old_bindex, copyup_file, len); if (err) goto out_unlink; /* Set permissions. */ err = copyup_permissions(sb, old_lower_dentry, new_lower_dentry); if (err) goto out_unlink; #ifdef CONFIG_UNION_FS_XATTR /* Selinux uses extended attributes for permissions. */ err = copyup_xattrs(old_lower_dentry, new_lower_dentry); if (err) goto out_unlink; #endif /* CONFIG_UNION_FS_XATTR */ /* do not allow files getting deleted to be re-interposed */ if (!d_deleted(dentry)) unionfs_reinterpose(dentry); goto out_unlock; out_unlink: /* * copyup failed, because we possibly ran out of space or * quota, or something else happened so let's unlink; we don't * really care about the return value of vfs_unlink */ vfs_unlink(new_lower_parent_dentry->d_inode, new_lower_dentry); if (copyup_file) { /* need to close the file */ fput(*copyup_file); branchput(sb, new_bindex); } /* * TODO: should we reset the error to something like -EIO? * * If we don't reset, the user may get some nonsensical errors, but * on the other hand, if we reset to EIO, we guarantee that the user * will get a "confusing" error message. */ out_unlock: unlock_dir(new_lower_parent_dentry); out_free: /* * If old_lower_dentry was not a file, then we need to dput it. If * it was a file, then it was already dput indirectly by other * functions we call above which operate on regular files. */ if (old_lower_dentry && old_lower_dentry->d_inode && !S_ISREG(old_lower_dentry->d_inode->i_mode)) dput(old_lower_dentry); kfree(symbuf); if (err) { /* * if directory creation succeeded, but inode copyup failed, * then purge new dentries. */ if (dbstart(dentry) < old_bstart && ibstart(dentry->d_inode) > dbstart(dentry)) __clear(dentry, NULL, old_bstart, old_bend, unionfs_lower_dentry(dentry), dbstart(dentry)); goto out; } if (!S_ISDIR(dentry->d_inode->i_mode)) { unionfs_postcopyup_release(dentry); if (!unionfs_lower_inode(dentry->d_inode)) { /* * If we got here, then we copied up to an * unlinked-open file, whose name is .unionfsXXXXX. */ struct inode *inode = new_lower_dentry->d_inode; atomic_inc(&inode->i_count); unionfs_set_lower_inode_idx(dentry->d_inode, ibstart(dentry->d_inode), inode); } } unionfs_postcopyup_setmnt(dentry); /* sync inode times from copied-up inode to our inode */ unionfs_copy_attr_times(dentry->d_inode); unionfs_check_inode(dir); unionfs_check_dentry(dentry); out: 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; }
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); }
/* * Connect a unionfs inode dentry/inode with several lower ones. This is * the classic stackable file system "vnode interposition" action. * * @sb: unionfs's super_block */ struct dentry *unionfs_interpose(struct dentry *dentry, struct super_block *sb, int flag) { int err = 0; struct inode *inode; int need_fill_inode = 1; struct dentry *spliced = NULL; verify_locked(dentry); /* * We allocate our new inode below, by calling iget. * iget will call our read_inode which will initialize some * of the new inode's fields */ /* * On revalidate we've already got our own inode and just need * to fix it up. */ if (flag == INTERPOSE_REVAL) { inode = dentry->d_inode; UNIONFS_I(inode)->bstart = -1; UNIONFS_I(inode)->bend = -1; atomic_set(&UNIONFS_I(inode)->generation, atomic_read(&UNIONFS_SB(sb)->generation)); UNIONFS_I(inode)->lower_inodes = kcalloc(sbmax(sb), sizeof(struct inode *), GFP_KERNEL); if (unlikely(!UNIONFS_I(inode)->lower_inodes)) { err = -ENOMEM; goto out; } } else { /* get unique inode number for unionfs */ inode = iget(sb, iunique(sb, UNIONFS_ROOT_INO)); if (!inode) { err = -EACCES; goto out; } if (atomic_read(&inode->i_count) > 1) goto skip; } need_fill_inode = 0; unionfs_fill_inode(dentry, inode); skip: /* only (our) lookup wants to do a d_add */ switch (flag) { case INTERPOSE_DEFAULT: /* for operations which create new inodes */ d_add(dentry, inode); break; case INTERPOSE_REVAL_NEG: d_instantiate(dentry, inode); break; case INTERPOSE_LOOKUP: spliced = d_splice_alias(inode, dentry); if (spliced && spliced != dentry) { /* * d_splice can return a dentry if it was * disconnected and had to be moved. We must ensure * that the private data of the new dentry is * correct and that the inode info was filled * properly. Finally we must return this new * dentry. */ spliced->d_op = &unionfs_dops; spliced->d_fsdata = dentry->d_fsdata; dentry->d_fsdata = NULL; dentry = spliced; if (need_fill_inode) { need_fill_inode = 0; unionfs_fill_inode(dentry, inode); } goto out_spliced; } else if (!spliced) { if (need_fill_inode) { need_fill_inode = 0; unionfs_fill_inode(dentry, inode); goto out_spliced; } } break; case INTERPOSE_REVAL: /* Do nothing. */ break; default: printk(KERN_CRIT "unionfs: invalid interpose flag passed!\n"); BUG(); } goto out; out_spliced: if (!err) return spliced; out: return ERR_PTR(err); }
/* This function replicates the directory structure upto given dentry * in the bindex branch. */ static struct dentry *create_parents_named(struct inode *dir, struct dentry *dentry, const char *name, int bindex) { int err; struct dentry *child_dentry; struct dentry *parent_dentry; struct dentry *hidden_parent_dentry = NULL; struct dentry *hidden_dentry = NULL; const char *childname; unsigned int childnamelen; int old_kmalloc_size; int kmalloc_size; int num_dentry; int count; int old_bstart; int old_bend; struct dentry **path = NULL; struct dentry **tmp_path; struct super_block *sb; verify_locked(dentry); /* There is no sense allocating any less than the minimum. */ kmalloc_size = malloc_sizes[0].cs_size; num_dentry = kmalloc_size / sizeof(struct dentry *); if ((err = is_robranch_super(dir->i_sb, bindex))) { hidden_dentry = ERR_PTR(err); goto out; } old_bstart = dbstart(dentry); old_bend = dbend(dentry); hidden_dentry = ERR_PTR(-ENOMEM); path = kzalloc(kmalloc_size, GFP_KERNEL); if (!path) goto out; /* assume the negative dentry of unionfs as the parent dentry */ parent_dentry = dentry; count = 0; /* This loop finds the first parent that exists in the given branch. * We start building the directory structure from there. At the end * of the loop, the following should hold: * - child_dentry is the first nonexistent child * - parent_dentry is the first existent parent * - path[0] is the = deepest child * - path[count] is the first child to create */ do { child_dentry = parent_dentry; /* find the parent directory dentry in unionfs */ parent_dentry = child_dentry->d_parent; unionfs_lock_dentry(parent_dentry); /* find out the hidden_parent_dentry in the given branch */ hidden_parent_dentry = unionfs_lower_dentry_idx(parent_dentry, bindex); /* store the child dentry */ path[count++] = child_dentry; /* grow path table */ if (count == num_dentry) { old_kmalloc_size = kmalloc_size; kmalloc_size *= 2; num_dentry = kmalloc_size / sizeof(struct dentry *); tmp_path = kzalloc(kmalloc_size, GFP_KERNEL); if (!tmp_path) { hidden_dentry = ERR_PTR(-ENOMEM); goto out; } memcpy(tmp_path, path, old_kmalloc_size); kfree(path); path = tmp_path; tmp_path = NULL; } } while (!hidden_parent_dentry); count--; sb = dentry->d_sb; /* This is basically while(child_dentry != dentry). This loop is * horrible to follow and should be replaced with cleaner code. */ while (1) { /* get hidden parent dir in the current branch */ hidden_parent_dentry = unionfs_lower_dentry_idx(parent_dentry, bindex); unionfs_unlock_dentry(parent_dentry); /* init the values to lookup */ childname = child_dentry->d_name.name; childnamelen = child_dentry->d_name.len; if (child_dentry != dentry) { /* lookup child in the underlying file system */ hidden_dentry = lookup_one_len(childname, hidden_parent_dentry, childnamelen); if (IS_ERR(hidden_dentry)) goto out; } else { /* is the name a whiteout of the childname ? * lookup the whiteout child in the underlying file system */ hidden_dentry = lookup_one_len(name, hidden_parent_dentry, strlen(name)); if (IS_ERR(hidden_dentry)) goto out; /* Replace the current dentry (if any) with the new one. */ dput(unionfs_lower_dentry_idx(dentry, bindex)); unionfs_set_lower_dentry_idx(dentry, bindex, hidden_dentry); __cleanup_dentry(dentry, bindex, old_bstart, old_bend); break; } if (hidden_dentry->d_inode) { /* since this already exists we dput to avoid * multiple references on the same dentry */ dput(hidden_dentry); } else { struct sioq_args args; /* its a negative dentry, create a new dir */ hidden_parent_dentry = lock_parent(hidden_dentry); args.mkdir.parent = hidden_parent_dentry->d_inode; args.mkdir.dentry = hidden_dentry; args.mkdir.mode = child_dentry->d_inode->i_mode; run_sioq(__unionfs_mkdir, &args); err = args.err; if (!err) err = copyup_permissions(dir->i_sb, child_dentry, hidden_dentry); unlock_dir(hidden_parent_dentry); if (err) { dput(hidden_dentry); hidden_dentry = ERR_PTR(err); goto out; } } __set_inode(child_dentry, hidden_dentry, bindex); __set_dentry(child_dentry, hidden_dentry, bindex); parent_dentry = child_dentry; child_dentry = path[--count]; } out: kfree(path); return hidden_dentry; }
/* copy up a dentry to a file of specified name */ static int copyup_named_dentry(struct inode *dir, struct dentry *dentry, int bstart, int new_bindex, const char *name, int namelen, struct file **copyup_file, loff_t len) { struct dentry *new_hidden_dentry; struct dentry *old_hidden_dentry = NULL; struct super_block *sb; int err = 0; int old_bindex; int old_bstart; int old_bend; struct dentry *new_hidden_parent_dentry = NULL; mm_segment_t oldfs; char *symbuf = NULL; verify_locked(dentry); old_bindex = bstart; old_bstart = dbstart(dentry); old_bend = dbend(dentry); BUG_ON(new_bindex < 0); BUG_ON(new_bindex >= old_bindex); sb = dir->i_sb; unionfs_read_lock(sb); if ((err = is_robranch_super(sb, new_bindex))) { dput(old_hidden_dentry); goto out; } /* Create the directory structure above this dentry. */ new_hidden_dentry = create_parents_named(dir, dentry, name, new_bindex); if (IS_ERR(new_hidden_dentry)) { dput(old_hidden_dentry); err = PTR_ERR(new_hidden_dentry); goto out; } old_hidden_dentry = unionfs_lower_dentry_idx(dentry, old_bindex); 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)) { symbuf = kmalloc(PATH_MAX, GFP_KERNEL); if (!symbuf) { __clear(dentry, old_hidden_dentry, old_bstart, old_bend, new_hidden_dentry, new_bindex); err = -ENOMEM; goto out_free; } oldfs = get_fs(); set_fs(KERNEL_DS); err = old_hidden_dentry->d_inode->i_op->readlink( old_hidden_dentry, (char __user *)symbuf, PATH_MAX); set_fs(oldfs); if (err) { __clear(dentry, old_hidden_dentry, old_bstart, old_bend, new_hidden_dentry, new_bindex); goto out_free; } 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); /* create the new inode */ err = __copyup_ndentry(old_hidden_dentry, new_hidden_dentry, new_hidden_parent_dentry, symbuf); if (err) { __clear(dentry, old_hidden_dentry, old_bstart, old_bend, new_hidden_dentry, new_bindex); goto out_unlock; } /* We actually copyup the file here. */ if (S_ISREG(old_hidden_dentry->d_inode->i_mode)) err = __copyup_reg_data(dentry, new_hidden_dentry, new_bindex, old_hidden_dentry, old_bindex, copyup_file, len); if (err) goto out_unlink; /* Set permissions. */ if ((err = copyup_permissions(sb, old_hidden_dentry, new_hidden_dentry))) goto out_unlink; #ifdef CONFIG_UNION_FS_XATTR /* Selinux uses extended attributes for permissions. */ if ((err = copyup_xattrs(old_hidden_dentry, new_hidden_dentry))) goto out_unlink; #endif /* do not allow files getting deleted to be reinterposed */ if (!d_deleted(dentry)) unionfs_reinterpose(dentry); goto out_unlock; /****/ out_unlink: /* copyup failed, because we possibly ran out of space or * quota, or something else happened so let's unlink; we don't * really care about the return value of vfs_unlink */ vfs_unlink(new_hidden_parent_dentry->d_inode, new_hidden_dentry); if (copyup_file) { /* need to close the file */ fput(*copyup_file); branchput(sb, new_bindex); } /* * TODO: should we reset the error to something like -EIO? * * If we don't reset, the user may get some non-sensical errors, but * on the other hand, if we reset to EIO, we guarantee that the user * will get a "confusing" error message. */ out_unlock: unlock_dir(new_hidden_parent_dentry); out_free: kfree(symbuf); out: unionfs_read_unlock(sb); 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); }
/* sb we pass is unionfs's super_block */ int unionfs_interpose(struct dentry *dentry, struct super_block *sb, int flag) { struct inode *hidden_inode; struct dentry *hidden_dentry; int err = 0; struct inode *inode; int is_negative_dentry = 1; int bindex, bstart, bend; print_entry("flag = %d", flag); verify_locked(dentry); fist_print_dentry("In unionfs_interpose", dentry); bstart = dbstart(dentry); bend = dbend(dentry); /* Make sure that we didn't get a negative dentry. */ for (bindex = bstart; bindex <= bend; bindex++) { if (dtohd_index(dentry, bindex) && dtohd_index(dentry, bindex)->d_inode) { is_negative_dentry = 0; break; } } BUG_ON(is_negative_dentry); /* We allocate our new inode below, by calling iget. * iget will call our read_inode which will initialize some * of the new inode's fields */ /* On revalidate we've already got our own inode and just need * to fix it up. */ if (flag == INTERPOSE_REVAL) { inode = dentry->d_inode; itopd(inode)->b_start = -1; itopd(inode)->b_end = -1; atomic_set(&itopd(inode)->uii_generation, atomic_read(&stopd(sb)->usi_generation)); itohi_ptr(inode) = KZALLOC(sbmax(sb) * sizeof(struct inode *), GFP_KERNEL); if (!itohi_ptr(inode)) { err = -ENOMEM; goto out; } } else { ino_t ino; /* get unique inode number for unionfs */ #ifdef UNIONFS_IMAP if (stopd(sb)->usi_persistent) { err = read_uin(sb, bindex, dtohd_index(dentry, bindex)->d_inode->i_ino, O_CREAT, &ino); if (err) goto out; } else #endif ino = iunique(sb, UNIONFS_ROOT_INO); inode = IGET(sb, ino); if (!inode) { err = -EACCES; /* should be impossible??? */ goto out; } } down(&inode->i_sem); if (atomic_read(&inode->i_count) > 1) goto skip; for (bindex = bstart; bindex <= bend; bindex++) { hidden_dentry = dtohd_index(dentry, bindex); if (!hidden_dentry) { set_itohi_index(inode, bindex, NULL); continue; } /* Initialize the hidden inode to the new hidden inode. */ if (!hidden_dentry->d_inode) continue; set_itohi_index(inode, bindex, IGRAB(hidden_dentry->d_inode)); } ibstart(inode) = dbstart(dentry); ibend(inode) = dbend(dentry); /* Use attributes from the first branch. */ hidden_inode = itohi(inode); /* Use different set of inode ops for symlinks & directories */ if (S_ISLNK(hidden_inode->i_mode)) inode->i_op = &unionfs_symlink_iops; else if (S_ISDIR(hidden_inode->i_mode)) inode->i_op = &unionfs_dir_iops; /* Use different set of file ops for directories */ if (S_ISDIR(hidden_inode->i_mode)) inode->i_fop = &unionfs_dir_fops; /* properly initialize special inodes */ if (S_ISBLK(hidden_inode->i_mode) || S_ISCHR(hidden_inode->i_mode) || S_ISFIFO(hidden_inode->i_mode) || S_ISSOCK(hidden_inode->i_mode)) init_special_inode(inode, hidden_inode->i_mode, hidden_inode->i_rdev); /* Fix our inode's address operations to that of the lower inode (Unionfs is FiST-Lite) */ if (inode->i_mapping->a_ops != hidden_inode->i_mapping->a_ops) { fist_dprint(7, "fixing inode 0x%p a_ops (0x%p -> 0x%p)\n", inode, inode->i_mapping->a_ops, hidden_inode->i_mapping->a_ops); inode->i_mapping->a_ops = hidden_inode->i_mapping->a_ops; } /* all well, copy inode attributes */ fist_copy_attr_all(inode, hidden_inode); skip: /* only (our) lookup wants to do a d_add */ switch (flag) { case INTERPOSE_DEFAULT: case INTERPOSE_REVAL_NEG: d_instantiate(dentry, inode); break; case INTERPOSE_LOOKUP: err = PTR_ERR(d_splice_alias(inode, dentry)); break; case INTERPOSE_REVAL: /* Do nothing. */ break; default: printk(KERN_ERR "Invalid interpose flag passed!"); BUG(); } fist_print_dentry("Leaving unionfs_interpose", dentry); fist_print_inode("Leaving unionfs_interpose", inode); up(&inode->i_sem); out: 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; }
/* 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; }
/* * 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; struct nameidata lowernd; /* TODO: be gentler to the stack */ if (nd) memcpy(&lowernd, nd, sizeof(struct nameidata)); else memset(&lowernd, 0, sizeof(struct nameidata)); restart: verify_locked(dentry); /* if the dentry is unhashed, do NOT revalidate */ if (d_deleted(dentry)) { printk(KERN_DEBUG "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(&UNIONFS_D(dentry)->generation); sbgen = atomic_read(&UNIONFS_SB(dentry->d_sb)->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(&UNIONFS_D(dentry->d_parent)->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 = unionfs_lower_dentry_idx(dentry, bindex); dput(hidden_dentry); } } set_dbstart(dentry, -1); set_dbend(dentry, -1); interpose_flag = INTERPOSE_REVAL_NEG; if (positive) { interpose_flag = INTERPOSE_REVAL; mutex_lock(&dentry->d_inode->i_mutex); 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 = unionfs_lower_inode_idx(dentry->d_inode, bindex); iput(hidden_inode); } } kfree(UNIONFS_I(dentry->d_inode)->lower_inodes); UNIONFS_I(dentry->d_inode)->lower_inodes = NULL; ibstart(dentry->d_inode) = -1; ibend(dentry->d_inode) = -1; mutex_unlock(&dentry->d_inode->i_mutex); } result = unionfs_lookup_backend(dentry, &lowernd, 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 && UNIONFS_I(dentry->d_inode)->stale) { make_bad_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 = unionfs_lower_dentry_idx(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) { fsstack_copy_attr_all(dentry->d_inode, unionfs_lower_inode(dentry->d_inode), unionfs_get_nlinks); fsstack_copy_inode_size(dentry->d_inode, unionfs_lower_inode(dentry->d_inode)); } out: if (locked) unionfs_read_unlock(dentry->d_sb); return valid; }