/* perform a delayed copyup of a read-write file on a read-only branch */ static int do_delayed_copyup(struct file *file, struct dentry *parent) { int bindex, bstart, bend, err = 0; struct dentry *dentry = file->f_path.dentry; struct inode *parent_inode = parent->d_inode; bstart = fbstart(file); bend = fbend(file); BUG_ON(!S_ISREG(dentry->d_inode->i_mode)); unionfs_check_file(file); for (bindex = bstart - 1; bindex >= 0; bindex--) { if (!d_deleted(dentry)) err = copyup_file(parent_inode, file, bstart, bindex, i_size_read(dentry->d_inode)); else err = copyup_deleted_file(file, dentry, parent, bstart, bindex); /* if succeeded, set lower open-file flags and break */ if (!err) { struct file *lower_file; lower_file = unionfs_lower_file_idx(file, bindex); lower_file->f_flags = file->f_flags; break; } } if (err || (bstart <= fbstart(file))) goto out; bend = fbend(file); for (bindex = bstart; bindex <= bend; bindex++) { if (unionfs_lower_file_idx(file, bindex)) { branchput(dentry->d_sb, bindex); fput(unionfs_lower_file_idx(file, bindex)); unionfs_set_lower_file_idx(file, bindex, NULL); } } path_put_lowers(dentry, bstart, bend, false); iput_lowers(dentry->d_inode, bstart, bend, false); /* for reg file, we only open it "once" */ fbend(file) = fbstart(file); dbend(dentry) = dbstart(dentry); ibend(dentry->d_inode) = ibstart(dentry->d_inode); out: unionfs_check_file(file); return err; }
/* * 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; }
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; }
void __unionfs_check_dentry(const struct dentry *dentry, const char *fname, const char *fxn, int line) { int bindex; int dstart, dend, istart, iend; struct dentry *lower_dentry; struct inode *inode, *lower_inode; struct super_block *sb; struct vfsmount *lower_mnt; int printed_caller = 0; void *poison_ptr; BUG_ON(!dentry); sb = dentry->d_sb; inode = dentry->d_inode; dstart = dbstart(dentry); dend = dbend(dentry); /* don't check dentry/mnt if no lower branches */ if (dstart < 0 && dend < 0) goto check_inode; BUG_ON(dstart > dend); if (unlikely((dstart == -1 && dend != -1) || (dstart != -1 && dend == -1))) { PRINT_CALLER(fname, fxn, line); pr_debug(" CD0: dentry=%p dstart/end=%d:%d\n", dentry, dstart, dend); } /* * check for NULL dentries inside the start/end range, or * non-NULL dentries outside the start/end range. */ for (bindex = sbstart(sb); bindex < sbmax(sb); bindex++) { lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); if (lower_dentry) { if (unlikely(bindex < dstart || bindex > dend)) { PRINT_CALLER(fname, fxn, line); pr_debug(" CD1: dentry/lower=%p:%p(%p) " "bindex=%d dstart/end=%d:%d\n", dentry, lower_dentry, (lower_dentry ? lower_dentry->d_inode : (void *) -1L), bindex, dstart, dend); } } else { /* lower_dentry == NULL */ if (bindex < dstart || bindex > dend) continue; /* * Directories can have NULL lower inodes in b/t * start/end, but NOT if at the start/end range. * Ignore this rule, however, if this is a NULL * dentry or a deleted dentry. */ if (unlikely(!d_deleted((struct dentry *) dentry) && inode && !(inode && S_ISDIR(inode->i_mode) && bindex > dstart && bindex < dend))) { PRINT_CALLER(fname, fxn, line); pr_debug(" CD2: dentry/lower=%p:%p(%p) " "bindex=%d dstart/end=%d:%d\n", dentry, lower_dentry, (lower_dentry ? lower_dentry->d_inode : (void *) -1L), bindex, dstart, dend); } } } /* check for vfsmounts same as for dentries */ for (bindex = sbstart(sb); bindex < sbmax(sb); bindex++) { lower_mnt = unionfs_lower_mnt_idx(dentry, bindex); if (lower_mnt) { if (unlikely(bindex < dstart || bindex > dend)) { PRINT_CALLER(fname, fxn, line); pr_debug(" CM0: dentry/lmnt=%p:%p bindex=%d " "dstart/end=%d:%d\n", dentry, lower_mnt, bindex, dstart, dend); } } else { /* lower_mnt == NULL */ if (bindex < dstart || bindex > dend) continue; /* * Directories can have NULL lower inodes in b/t * start/end, but NOT if at the start/end range. * Ignore this rule, however, if this is a NULL * dentry. */ if (unlikely(inode && !(inode && S_ISDIR(inode->i_mode) && bindex > dstart && bindex < dend))) { PRINT_CALLER(fname, fxn, line); pr_debug(" CM1: dentry/lmnt=%p:%p " "bindex=%d dstart/end=%d:%d\n", dentry, lower_mnt, bindex, dstart, dend); } } } check_inode: /* for inodes now */ if (!inode) return; istart = ibstart(inode); iend = ibend(inode); /* don't check inode if no lower branches */ if (istart < 0 && iend < 0) return; BUG_ON(istart > iend); if (unlikely((istart == -1 && iend != -1) || (istart != -1 && iend == -1))) { PRINT_CALLER(fname, fxn, line); pr_debug(" CI0: dentry/inode=%p:%p istart/end=%d:%d\n", dentry, inode, istart, iend); } if (unlikely(istart != dstart)) { PRINT_CALLER(fname, fxn, line); pr_debug(" CI1: dentry/inode=%p:%p istart=%d dstart=%d\n", dentry, inode, istart, dstart); } if (unlikely(iend != dend)) { PRINT_CALLER(fname, fxn, line); pr_debug(" CI2: dentry/inode=%p:%p iend=%d dend=%d\n", dentry, inode, iend, dend); } if (!S_ISDIR(inode->i_mode)) { if (unlikely(dend != dstart)) { PRINT_CALLER(fname, fxn, line); pr_debug(" CI3: dentry/inode=%p:%p dstart=%d dend=%d\n", dentry, inode, dstart, dend); } if (unlikely(iend != istart)) { PRINT_CALLER(fname, fxn, line); pr_debug(" CI4: dentry/inode=%p:%p istart=%d iend=%d\n", dentry, inode, istart, iend); } } for (bindex = sbstart(sb); bindex < sbmax(sb); bindex++) { lower_inode = unionfs_lower_inode_idx(inode, bindex); if (lower_inode) { memset(&poison_ptr, POISON_INUSE, sizeof(void *)); if (unlikely(bindex < istart || bindex > iend)) { PRINT_CALLER(fname, fxn, line); pr_debug(" CI5: dentry/linode=%p:%p bindex=%d " "istart/end=%d:%d\n", dentry, lower_inode, bindex, istart, iend); } else if (unlikely(lower_inode == poison_ptr)) { /* freed inode! */ PRINT_CALLER(fname, fxn, line); pr_debug(" CI6: dentry/linode=%p:%p bindex=%d " "istart/end=%d:%d\n", dentry, lower_inode, bindex, istart, iend); } continue; } /* if we get here, then lower_inode == NULL */ if (bindex < istart || bindex > iend) continue; /* * directories can have NULL lower inodes in b/t start/end, * but NOT if at the start/end range. */ if (unlikely(S_ISDIR(inode->i_mode) && bindex > istart && bindex < iend)) continue; PRINT_CALLER(fname, fxn, line); pr_debug(" CI7: dentry/linode=%p:%p " "bindex=%d istart/end=%d:%d\n", dentry, lower_inode, bindex, istart, iend); } /* * If it's a directory, then intermediate objects b/t start/end can * be NULL. But, check that all three are NULL: lower dentry, mnt, * and inode. */ if (dstart >= 0 && dend >= 0 && S_ISDIR(inode->i_mode)) for (bindex = dstart+1; bindex < dend; bindex++) { lower_inode = unionfs_lower_inode_idx(inode, bindex); lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); lower_mnt = unionfs_lower_mnt_idx(dentry, bindex); if (unlikely(!((lower_inode && lower_dentry && lower_mnt) || (!lower_inode && !lower_dentry && !lower_mnt)))) { PRINT_CALLER(fname, fxn, line); pr_debug(" Cx: lmnt/ldentry/linode=%p:%p:%p " "bindex=%d dstart/end=%d:%d\n", lower_mnt, lower_dentry, lower_inode, bindex, dstart, dend); } } /* check if lower inode is newer than upper one (it shouldn't) */ if (unlikely(is_newer_lower(dentry) && !is_negative_lower(dentry))) { PRINT_CALLER(fname, fxn, line); for (bindex = ibstart(inode); bindex <= ibend(inode); bindex++) { lower_inode = unionfs_lower_inode_idx(inode, bindex); if (unlikely(!lower_inode)) continue; pr_debug(" CI8: bindex=%d mtime/lmtime=%lu.%lu/%lu.%lu " "ctime/lctime=%lu.%lu/%lu.%lu\n", bindex, inode->i_mtime.tv_sec, inode->i_mtime.tv_nsec, lower_inode->i_mtime.tv_sec, lower_inode->i_mtime.tv_nsec, inode->i_ctime.tv_sec, inode->i_ctime.tv_nsec, lower_inode->i_ctime.tv_sec, lower_inode->i_ctime.tv_nsec); } } }
/* * 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; }
/* 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; }
/* * release all lower object references & free the file info structure * * No need to grab sb info's rwsem. */ int unionfs_file_release(struct inode *inode, struct file *file) { struct file *lower_file = NULL; struct unionfs_file_info *fileinfo; struct unionfs_inode_info *inodeinfo; struct super_block *sb = inode->i_sb; struct dentry *dentry = file->f_path.dentry; struct dentry *parent; int bindex, bstart, bend; int fgen, err = 0; /* * Since mm/memory.c:might_fault() (under PROVE_LOCKING) was * modified in 2.6.29-rc1 to call might_lock_read on mmap_sem, this * has been causing false positives in file system stacking layers. * In particular, our ->mmap is called after sys_mmap2 already holds * mmap_sem, then we lock our own mutexes; but earlier, it's * possible for lockdep to have locked our mutexes first, and then * we call a lower ->readdir which could call might_fault. The * different ordering of the locks is what lockdep complains about * -- unnecessarily. Therefore, we have no choice but to tell * lockdep to temporarily turn off lockdep here. Note: the comments * inside might_sleep also suggest that it would have been * nicer to only annotate paths that needs that might_lock_read. */ lockdep_off(); unionfs_read_lock(sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); /* * We try to revalidate, but the VFS ignores return return values * from file->release, so we must always try to succeed here, * including to do the kfree and dput below. So if revalidation * failed, all we can do is print some message and keep going. */ err = unionfs_file_revalidate(file, parent, UNIONFS_F(file)->wrote_to_file); if (!err) unionfs_check_file(file); fileinfo = UNIONFS_F(file); BUG_ON(file->f_path.dentry->d_inode != inode); inodeinfo = UNIONFS_I(inode); /* fput all the lower files */ fgen = atomic_read(&fileinfo->generation); bstart = fbstart(file); bend = fbend(file); for (bindex = bstart; bindex <= bend; bindex++) { lower_file = unionfs_lower_file_idx(file, bindex); if (lower_file) { unionfs_set_lower_file_idx(file, bindex, NULL); fput(lower_file); branchput(sb, bindex); } /* if there are no more refs to the dentry, dput it */ if (d_deleted(dentry)) { dput(unionfs_lower_dentry_idx(dentry, bindex)); unionfs_set_lower_dentry_idx(dentry, bindex, NULL); } } kfree(fileinfo->lower_files); kfree(fileinfo->saved_branch_ids); if (fileinfo->rdstate) { fileinfo->rdstate->access = jiffies; spin_lock(&inodeinfo->rdlock); inodeinfo->rdcount++; list_add_tail(&fileinfo->rdstate->cache, &inodeinfo->readdircache); mark_inode_dirty(inode); spin_unlock(&inodeinfo->rdlock); fileinfo->rdstate = NULL; } kfree(fileinfo); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(sb); lockdep_on(); return err; }
int unionfs_open(struct inode *inode, struct file *file) { int err = 0; struct file *lower_file = NULL; struct dentry *dentry = file->f_path.dentry; struct dentry *parent; int bindex = 0, bstart = 0, bend = 0; int size; int valid = 0; unionfs_read_lock(inode->i_sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); /* don't open unhashed/deleted files */ if (d_deleted(dentry)) { err = -ENOENT; goto out_nofree; } /* XXX: should I change 'false' below to the 'willwrite' flag? */ valid = __unionfs_d_revalidate(dentry, parent, false); if (unlikely(!valid)) { err = -ESTALE; goto out_nofree; } file->private_data = kzalloc(sizeof(struct unionfs_file_info), GFP_KERNEL); if (unlikely(!UNIONFS_F(file))) { err = -ENOMEM; goto out_nofree; } fbstart(file) = -1; fbend(file) = -1; atomic_set(&UNIONFS_F(file)->generation, atomic_read(&UNIONFS_I(inode)->generation)); size = sizeof(struct file *) * sbmax(inode->i_sb); UNIONFS_F(file)->lower_files = kzalloc(size, GFP_KERNEL); if (unlikely(!UNIONFS_F(file)->lower_files)) { err = -ENOMEM; goto out; } size = sizeof(int) * sbmax(inode->i_sb); UNIONFS_F(file)->saved_branch_ids = kzalloc(size, GFP_KERNEL); if (unlikely(!UNIONFS_F(file)->saved_branch_ids)) { err = -ENOMEM; goto out; } bstart = fbstart(file) = dbstart(dentry); bend = fbend(file) = dbend(dentry); /* * open all directories and make the unionfs file struct point to * these lower file structs */ if (S_ISDIR(inode->i_mode)) err = __open_dir(inode, file); /* open a dir */ else err = __open_file(inode, file, parent); /* open a file */ /* freeing the allocated resources, and fput the opened files */ if (err) { for (bindex = bstart; bindex <= bend; bindex++) { lower_file = unionfs_lower_file_idx(file, bindex); if (!lower_file) continue; branchput(dentry->d_sb, bindex); /* fput calls dput for lower_dentry */ fput(lower_file); } } out: if (err) { kfree(UNIONFS_F(file)->lower_files); kfree(UNIONFS_F(file)->saved_branch_ids); kfree(UNIONFS_F(file)); } out_nofree: if (!err) { unionfs_postcopyup_setmnt(dentry); unionfs_copy_attr_times(inode); unionfs_check_file(file); unionfs_check_inode(inode); } unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(inode->i_sb); return err; }
/* * Helper function for unionfs_file_revalidate/locked. * Expects dentry/parent to be locked already, and revalidated. */ static int __unionfs_file_revalidate(struct file *file, struct dentry *dentry, struct dentry *parent, struct super_block *sb, int sbgen, int dgen, bool willwrite) { int fgen; int bstart, bend, orig_brid; int size; int err = 0; fgen = atomic_read(&UNIONFS_F(file)->generation); /* * There are two cases we are interested in. The first is if the * generation is lower than the super-block. The second is if * someone has copied up this file from underneath us, we also need * to refresh things. */ if (d_deleted(dentry) || (sbgen <= fgen && dbstart(dentry) == fbstart(file) && unionfs_lower_file(file))) goto out_may_copyup; /* save orig branch ID */ orig_brid = UNIONFS_F(file)->saved_branch_ids[fbstart(file)]; /* First we throw out the existing files. */ cleanup_file(file); /* Now we reopen the file(s) as in unionfs_open. */ bstart = fbstart(file) = dbstart(dentry); bend = fbend(file) = dbend(dentry); size = sizeof(struct file *) * sbmax(sb); UNIONFS_F(file)->lower_files = kzalloc(size, GFP_KERNEL); if (unlikely(!UNIONFS_F(file)->lower_files)) { err = -ENOMEM; goto out; } size = sizeof(int) * sbmax(sb); UNIONFS_F(file)->saved_branch_ids = kzalloc(size, GFP_KERNEL); if (unlikely(!UNIONFS_F(file)->saved_branch_ids)) { err = -ENOMEM; goto out; } if (S_ISDIR(dentry->d_inode->i_mode)) { /* We need to open all the files. */ err = open_all_files(file); if (err) goto out; } else { int new_brid; /* We only open the highest priority branch. */ err = open_highest_file(file, willwrite); if (err) goto out; new_brid = UNIONFS_F(file)->saved_branch_ids[fbstart(file)]; if (unlikely(new_brid != orig_brid && sbgen > fgen)) { /* * If we re-opened the file on a different branch * than the original one, and this was due to a new * branch inserted, then update the mnt counts of * the old and new branches accordingly. */ unionfs_mntget(dentry, bstart); unionfs_mntput(sb->s_root, branch_id_to_idx(sb, orig_brid)); } /* regular files have only one open lower file */ fbend(file) = fbstart(file); } atomic_set(&UNIONFS_F(file)->generation, atomic_read(&UNIONFS_I(dentry->d_inode)->generation)); out_may_copyup: /* Copyup on the first write to a file on a readonly branch. */ if (willwrite && IS_WRITE_FLAG(file->f_flags) && !IS_WRITE_FLAG(unionfs_lower_file(file)->f_flags) && is_robranch(dentry)) { pr_debug("unionfs: do delay copyup of \"%s\"\n", dentry->d_name.name); err = do_delayed_copyup(file, parent); /* regular files have only one open lower file */ if (!err && !S_ISDIR(dentry->d_inode->i_mode)) fbend(file) = fbstart(file); } out: if (err) { kfree(UNIONFS_F(file)->lower_files); kfree(UNIONFS_F(file)->saved_branch_ids); } return err; }
/* * The locking rules in unionfs_rename are complex. We could use a simpler * superblock-level name-space lock for renames and copy-ups. */ int unionfs_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry) { int err = 0; struct dentry *wh_dentry; struct dentry *old_parent, *new_parent; int valid = true; unionfs_read_lock(old_dentry->d_sb, UNIONFS_SMUTEX_CHILD); old_parent = dget_parent(old_dentry); new_parent = dget_parent(new_dentry); /* un/lock parent dentries only if they differ from old/new_dentry */ if (old_parent != old_dentry && old_parent != new_dentry) unionfs_lock_dentry(old_parent, UNIONFS_DMUTEX_REVAL_PARENT); if (new_parent != old_dentry && new_parent != new_dentry && new_parent != old_parent) unionfs_lock_dentry(new_parent, UNIONFS_DMUTEX_REVAL_CHILD); unionfs_double_lock_dentry(old_dentry, new_dentry); valid = __unionfs_d_revalidate(old_dentry, old_parent, false); if (!valid) { err = -ESTALE; goto out; } if (!d_deleted(new_dentry) && new_dentry->d_inode) { valid = __unionfs_d_revalidate(new_dentry, new_parent, false); if (!valid) { err = -ESTALE; goto out; } } if (!S_ISDIR(old_dentry->d_inode->i_mode)) err = unionfs_partial_lookup(old_dentry, old_parent); else err = may_rename_dir(old_dentry, old_parent); if (err) goto out; err = unionfs_partial_lookup(new_dentry, new_parent); if (err) goto out; /* * if new_dentry is already lower because of whiteout, * simply override it even if the whited-out dir is not empty. */ wh_dentry = find_first_whiteout(new_dentry); if (!IS_ERR(wh_dentry)) { dput(wh_dentry); } else if (new_dentry->d_inode) { if (S_ISDIR(old_dentry->d_inode->i_mode) != S_ISDIR(new_dentry->d_inode->i_mode)) { err = S_ISDIR(old_dentry->d_inode->i_mode) ? -ENOTDIR : -EISDIR; goto out; } if (S_ISDIR(new_dentry->d_inode->i_mode)) { struct unionfs_dir_state *namelist = NULL; /* check if this unionfs directory is empty or not */ err = check_empty(new_dentry, new_parent, &namelist); if (err) goto out; if (!is_robranch(new_dentry)) err = delete_whiteouts(new_dentry, dbstart(new_dentry), namelist); free_rdstate(namelist); if (err) goto out; } } err = do_unionfs_rename(old_dir, old_dentry, old_parent, new_dir, new_dentry, new_parent); if (err) goto out; /* * force re-lookup since the dir on ro branch is not renamed, and * lower dentries still indicate the un-renamed ones. */ if (S_ISDIR(old_dentry->d_inode->i_mode)) atomic_dec(&UNIONFS_D(old_dentry)->generation); else unionfs_postcopyup_release(old_dentry); if (new_dentry->d_inode && !S_ISDIR(new_dentry->d_inode->i_mode)) { unionfs_postcopyup_release(new_dentry); unionfs_postcopyup_setmnt(new_dentry); if (!unionfs_lower_inode(new_dentry->d_inode)) { /* * If we get here, it means that no copyup was * needed, and that a file by the old name already * existing on the destination branch; that file got * renamed earlier in this function, so all we need * to do here is set the lower inode. */ struct inode *inode; inode = unionfs_lower_inode(old_dentry->d_inode); igrab(inode); unionfs_set_lower_inode_idx(new_dentry->d_inode, dbstart(new_dentry), inode); } } /* if all of this renaming succeeded, update our times */ unionfs_copy_attr_times(old_dentry->d_inode); unionfs_copy_attr_times(new_dentry->d_inode); unionfs_check_inode(old_dir); unionfs_check_inode(new_dir); unionfs_check_dentry(old_dentry); unionfs_check_dentry(new_dentry); out: if (err) /* clear the new_dentry stuff created */ d_drop(new_dentry); unionfs_double_unlock_dentry(old_dentry, new_dentry); if (new_parent != old_dentry && new_parent != new_dentry && new_parent != old_parent) unionfs_unlock_dentry(new_parent); if (old_parent != old_dentry && old_parent != new_dentry) unionfs_unlock_dentry(old_parent); dput(new_parent); dput(old_parent); unionfs_read_unlock(old_dentry->d_sb); return err; }
/* * 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; }