/* 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); }
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(); }
/* set lower dentry ptr and update bstart & bend if necessary */ static void __set_dentry(struct dentry *upper, struct dentry *lower, int bindex) { unionfs_set_lower_dentry_idx(upper, bindex, lower); if (likely(dbstart(upper) > bindex)) dbstart(upper) = bindex; if (likely(dbend(upper) < bindex)) dbend(upper) = bindex; }
/* * return to user-space the branch indices containing the file in question * * We use fd_set and therefore we are limited to the number of the branches * to FD_SETSIZE, which is currently 1024 - plenty for most people */ static int unionfs_ioctl_queryfile(struct file *file, struct dentry *parent, unsigned int cmd, unsigned long arg) { int err = 0; fd_set branchlist; int bstart = 0, bend = 0, bindex = 0; int orig_bstart, orig_bend; struct dentry *dentry, *lower_dentry; struct vfsmount *mnt; dentry = file->f_path.dentry; orig_bstart = dbstart(dentry); orig_bend = dbend(dentry); err = unionfs_partial_lookup(dentry, parent); if (err) goto out; bstart = dbstart(dentry); bend = dbend(dentry); FD_ZERO(&branchlist); for (bindex = bstart; bindex <= bend; bindex++) { lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); if (!lower_dentry) continue; if (likely(lower_dentry->d_inode)) FD_SET(bindex, &branchlist); /* purge any lower objects after partial_lookup */ if (bindex < orig_bstart || bindex > orig_bend) { dput(lower_dentry); unionfs_set_lower_dentry_idx(dentry, bindex, NULL); iput(unionfs_lower_inode_idx(dentry->d_inode, bindex)); unionfs_set_lower_inode_idx(dentry->d_inode, bindex, NULL); mnt = unionfs_lower_mnt_idx(dentry, bindex); if (!mnt) continue; unionfs_mntput(dentry, bindex); unionfs_set_lower_mnt_idx(dentry, bindex, NULL); } } /* restore original dentry's offsets */ dbstart(dentry) = orig_bstart; dbend(dentry) = orig_bend; ibstart(dentry->d_inode) = orig_bstart; ibend(dentry->d_inode) = orig_bend; err = copy_to_user((void __user *)arg, &branchlist, sizeof(fd_set)); if (unlikely(err)) err = -EFAULT; out: return err < 0 ? err : bend; }
static void unionfs_fill_inode(struct dentry *dentry, struct inode *inode) { struct inode *lower_inode; struct dentry *lower_dentry; int bindex, bstart, bend; bstart = dbstart(dentry); bend = dbend(dentry); for (bindex = bstart; bindex <= bend; bindex++) { lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); if (!lower_dentry) { unionfs_set_lower_inode_idx(inode, bindex, NULL); continue; } /* Initialize the lower inode to the new lower inode. */ if (!lower_dentry->d_inode) continue; unionfs_set_lower_inode_idx(inode, bindex, igrab(lower_dentry->d_inode)); } ibstart(inode) = dbstart(dentry); ibend(inode) = dbend(dentry); /* Use attributes from the first branch. */ lower_inode = unionfs_lower_inode(inode); /* Use different set of inode ops for symlinks & directories */ if (S_ISLNK(lower_inode->i_mode)) inode->i_op = &unionfs_symlink_iops; else if (S_ISDIR(lower_inode->i_mode)) inode->i_op = &unionfs_dir_iops; /* Use different set of file ops for directories */ if (S_ISDIR(lower_inode->i_mode)) inode->i_fop = &unionfs_dir_fops; /* properly initialize special inodes */ if (S_ISBLK(lower_inode->i_mode) || S_ISCHR(lower_inode->i_mode) || S_ISFIFO(lower_inode->i_mode) || S_ISSOCK(lower_inode->i_mode)) init_special_inode(inode, lower_inode->i_mode, lower_inode->i_rdev); /* all well, copy inode attributes */ unionfs_copy_attr_all(inode, lower_inode); fsstack_copy_inode_size(inode, lower_inode); }
/* * Post-copyup helper to release all non-directory source objects of a * copied-up file. Regular files should have only one lower object. */ void unionfs_postcopyup_release(struct dentry *dentry) { int bstart, bend; BUG_ON(S_ISDIR(dentry->d_inode->i_mode)); bstart = dbstart(dentry); bend = dbend(dentry); path_put_lowers(dentry, bstart + 1, bend, false); iput_lowers(dentry->d_inode, bstart + 1, bend, false); dbend(dentry) = bstart; ibend(dentry->d_inode) = ibstart(dentry->d_inode) = bstart; }
static struct dentry *lookup_whiteout(struct dentry *dentry) { char *whname; int bindex = -1, bstart = -1, bend = -1; struct dentry *parent, *hidden_parent, *wh_dentry; whname = alloc_whname(dentry->d_name.name, dentry->d_name.len); if (IS_ERR(whname)) return (void *)whname; parent = GET_PARENT(dentry); lock_dentry(parent); bstart = dbstart(parent); bend = dbend(parent); wh_dentry = ERR_PTR(-ENOENT); for (bindex = bstart; bindex <= bend; bindex++) { hidden_parent = dtohd_index(parent, bindex); if (!hidden_parent) continue; wh_dentry = LOOKUP_ONE_LEN(whname, hidden_parent, dentry->d_name.len + WHLEN); if (IS_ERR(wh_dentry)) continue; if (wh_dentry->d_inode) break; DPUT(wh_dentry); wh_dentry = ERR_PTR(-ENOENT); } unlock_dentry(parent); DPUT(parent); KFREE(whname); return wh_dentry; }
/* unionfs_open helper function: open a directory */ static int __open_dir(struct inode *inode, struct file *file) { struct dentry *lower_dentry; struct file *lower_file; int bindex, bstart, bend; struct vfsmount *mnt; bstart = fbstart(file) = dbstart(file->f_path.dentry); bend = fbend(file) = dbend(file->f_path.dentry); for (bindex = bstart; bindex <= bend; bindex++) { lower_dentry = unionfs_lower_dentry_idx(file->f_path.dentry, bindex); if (!lower_dentry) continue; dget(lower_dentry); unionfs_mntget(file->f_path.dentry, bindex); mnt = unionfs_lower_mnt_idx(file->f_path.dentry, bindex); lower_file = dentry_open(lower_dentry, mnt, file->f_flags, current_cred()); if (IS_ERR(lower_file)) return PTR_ERR(lower_file); unionfs_set_lower_file_idx(file, bindex, lower_file); /* * The branchget goes after the open, because otherwise * we would miss the reference on release. */ branchget(inode->i_sb, bindex); } return 0; }
/* open all lower files for a given file */ static int open_all_files(struct file *file) { int bindex, bstart, bend, err = 0; struct file *lower_file; struct dentry *lower_dentry; struct dentry *dentry = file->f_path.dentry; struct super_block *sb = dentry->d_sb; bstart = dbstart(dentry); bend = dbend(dentry); for (bindex = bstart; bindex <= bend; bindex++) { lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); if (!lower_dentry) continue; dget(lower_dentry); unionfs_mntget(dentry, bindex); branchget(sb, bindex); lower_file = dentry_open(lower_dentry, unionfs_lower_mnt_idx(dentry, bindex), file->f_flags, current_cred()); if (IS_ERR(lower_file)) { branchput(sb, bindex); err = PTR_ERR(lower_file); goto out; } else { unionfs_set_lower_file_idx(file, bindex, lower_file); } } out: return err; }
static struct dentry *lookup_whiteout(struct dentry *dentry) { char *whname; int bindex = -1, bstart = -1, bend = -1; struct dentry *parent, *hidden_parent, *wh_dentry; whname = alloc_whname(dentry->d_name.name, dentry->d_name.len); if (IS_ERR(whname)) return (void *)whname; parent = dget_parent(dentry); unionfs_lock_dentry(parent); bstart = dbstart(parent); bend = dbend(parent); wh_dentry = ERR_PTR(-ENOENT); for (bindex = bstart; bindex <= bend; bindex++) { hidden_parent = unionfs_lower_dentry_idx(parent, bindex); if (!hidden_parent) continue; wh_dentry = lookup_one_len(whname, hidden_parent, dentry->d_name.len + UNIONFS_WHLEN); if (IS_ERR(wh_dentry)) continue; if (wh_dentry->d_inode) break; dput(wh_dentry); wh_dentry = ERR_PTR(-ENOENT); } unionfs_unlock_dentry(parent); dput(parent); kfree(whname); return wh_dentry; }
/* * dput the lower references for old and new dentry & clear a lower dentry * pointer */ static void __clear(struct dentry *dentry, struct dentry *old_lower_dentry, int old_bstart, int old_bend, struct dentry *new_lower_dentry, int new_bindex) { /* get rid of the lower dentry and all its traces */ unionfs_set_lower_dentry_idx(dentry, new_bindex, NULL); dbstart(dentry) = old_bstart; dbend(dentry) = old_bend; dput(new_lower_dentry); dput(old_lower_dentry); }
/* 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; }
/* purge a dentry's lower-branch states (dput/mntput, etc.) */ static void __cleanup_dentry(struct dentry *dentry, int bindex, int old_bstart, int old_bend) { int loop_start; int loop_end; int new_bstart = -1; int new_bend = -1; int i; loop_start = min(old_bstart, bindex); loop_end = max(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 */ for (i = loop_start; i <= loop_end; i++) { if (!unionfs_lower_dentry_idx(dentry, i)) continue; if (i == bindex) { new_bend = i; if (new_bstart < 0) new_bstart = i; continue; } if (!unionfs_lower_dentry_idx(dentry, i)->d_inode) { dput(unionfs_lower_dentry_idx(dentry, i)); unionfs_set_lower_dentry_idx(dentry, i, NULL); unionfs_mntput(dentry, i); unionfs_set_lower_mnt_idx(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; dbstart(dentry) = new_bstart; dbend(dentry) = new_bend; }
void unionfs_d_release(struct dentry *dentry) { struct dentry *hidden_dentry; int bindex, bstart, bend; print_entry_location(); /* There is no reason to lock the dentry, because we have the only * reference, but the printing functions verify that we have a lock * on the dentry before calling dbstart, etc. */ lock_dentry(dentry); __fist_print_dentry("unionfs_d_release IN dentry", dentry, 0); /* this could be a negative dentry, so check first */ if (!dtopd(dentry)) { fist_dprint(6, "dentry without private data: %*s", dentry->d_name.len, dentry->d_name.name); goto out; } else if (dbstart(dentry) < 0) { /* this is due to a failed lookup */ /* the failed lookup has a dtohd_ptr set to null, but this is a better check */ fist_dprint(6, "dentry without hidden dentries : %*s", dentry->d_name.len, dentry->d_name.name); goto out_free; } /* Release all the hidden dentries */ bstart = dbstart(dentry); bend = dbend(dentry); for (bindex = bstart; bindex <= bend; bindex++) { hidden_dentry = dtohd_index(dentry, bindex); DPUT(hidden_dentry); set_dtohd_index(dentry, bindex, NULL); } /* free private data (unionfs_dentry_info) here */ KFREE(dtohd_ptr(dentry)); dtohd_ptr(dentry) = NULL; out_free: /* No need to unlock it, because it is disappeared. */ #ifdef TRACKLOCK printk("DESTROYLOCK:%p\n", dentry); #endif free_dentry_private_data(dtopd(dentry)); dtopd_lhs(dentry) = NULL; /* just to be safe */ out: print_exit_location(); }
/* open the highest priority file for a given upper file */ static int open_highest_file(struct file *file, bool willwrite) { int bindex, bstart, bend, err = 0; struct file *lower_file; struct dentry *lower_dentry; struct dentry *dentry = file->f_path.dentry; struct dentry *parent = dget_parent(dentry); struct inode *parent_inode = parent->d_inode; struct super_block *sb = dentry->d_sb; bstart = dbstart(dentry); bend = dbend(dentry); lower_dentry = unionfs_lower_dentry(dentry); if (willwrite && IS_WRITE_FLAG(file->f_flags) && is_robranch(dentry)) { for (bindex = bstart - 1; bindex >= 0; bindex--) { err = copyup_file(parent_inode, file, bstart, bindex, i_size_read(dentry->d_inode)); if (!err) break; } atomic_set(&UNIONFS_F(file)->generation, atomic_read(&UNIONFS_I(dentry->d_inode)-> generation)); goto out; } dget(lower_dentry); unionfs_mntget(dentry, bstart); lower_file = dentry_open(lower_dentry, unionfs_lower_mnt_idx(dentry, bstart), file->f_flags, current_cred()); if (IS_ERR(lower_file)) { err = PTR_ERR(lower_file); goto out; } branchget(sb, bstart); unionfs_set_lower_file(file, lower_file); /* Fix up the position. */ lower_file->f_pos = file->f_pos; memcpy(&lower_file->f_ra, &file->f_ra, sizeof(struct file_ra_state)); out: dput(parent); return err; }
static void unionfs_d_release(struct dentry *dentry) { int bindex, bstart, bend; /* There is no reason to lock the dentry, because we have the only * reference, but the printing functions verify that we have a lock * on the dentry before calling dbstart, etc. */ unionfs_lock_dentry(dentry); /* this could be a negative dentry, so check first */ if (!UNIONFS_D(dentry)) { printk(KERN_DEBUG "dentry without private data: %.*s", dentry->d_name.len, dentry->d_name.name); goto out; } else if (dbstart(dentry) < 0) { /* this is due to a failed lookup */ printk(KERN_DEBUG "dentry without hidden dentries : %.*s", dentry->d_name.len, dentry->d_name.name); goto out_free; } /* Release all the hidden dentries */ bstart = dbstart(dentry); bend = dbend(dentry); for (bindex = bstart; bindex <= bend; bindex++) { dput(unionfs_lower_dentry_idx(dentry, bindex)); mntput(unionfs_lower_mnt_idx(dentry, bindex)); unionfs_set_lower_dentry_idx(dentry, bindex, NULL); unionfs_set_lower_mnt_idx(dentry, bindex, NULL); } /* free private data (unionfs_dentry_info) here */ kfree(UNIONFS_D(dentry)->lower_paths); UNIONFS_D(dentry)->lower_paths = NULL; out_free: /* No need to unlock it, because it is disappeared. */ free_dentry_private_data(UNIONFS_D(dentry)); dentry->d_fsdata = NULL; /* just to be safe */ out: return; }
void update_bstart(struct dentry *dentry) { int bindex; int bstart = dbstart(dentry); int bend = dbend(dentry); struct dentry *hidden_dentry; for (bindex = bstart; bindex <= bend; bindex++) { hidden_dentry = dtohd_index(dentry, bindex); if (!hidden_dentry) continue; if (hidden_dentry->d_inode) { set_dbstart(dentry, bindex); break; } DPUT(hidden_dentry); set_dtohd_index(dentry, bindex, NULL); } }
/* * scan through the lower dentry objects, and set bstart to reflect the * starting branch */ void update_bstart(struct dentry *dentry) { int bindex; int bstart = dbstart(dentry); int bend = dbend(dentry); struct dentry *lower_dentry; for (bindex = bstart; bindex <= bend; bindex++) { lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); if (!lower_dentry) continue; if (lower_dentry->d_inode) { dbstart(dentry) = bindex; break; } dput(lower_dentry); unionfs_set_lower_dentry_idx(dentry, bindex, NULL); } }
int unionfs_ioctl_queryfile(struct file *file, unsigned int cmd, unsigned long arg) { int err = 0; fd_set branchlist; int bstart = 0, bend = 0, bindex = 0; struct dentry *dentry, *hidden_dentry; print_entry_location(); dentry = file->f_dentry; lock_dentry(dentry); if ((err = unionfs_partial_lookup(dentry))) goto out; bstart = dbstart(dentry); bend = dbend(dentry); FD_ZERO(&branchlist); for (bindex = bstart; bindex <= bend; bindex++) { hidden_dentry = dtohd_index(dentry, bindex); if (!hidden_dentry) continue; if (hidden_dentry->d_inode) FD_SET(bindex, &branchlist); } err = copy_to_user((void *)arg, &branchlist, sizeof(fd_set)); if (err) { err = -EFAULT; goto out; } out: unlock_dentry(dentry); err = err < 0 ? err : bend; print_exit_status(err); return (err); }
/* We can't copyup a directory, because it may involve huge * numbers of children, etc. Doing that in the kernel would * be bad, so instead we let the userspace recurse and ask us * to copy up each file separately */ static int may_rename_dir(struct dentry *dentry) { int err, bstart; err = check_empty(dentry, NULL); if (err == -ENOTEMPTY) { if (is_robranch(dentry)) return -EXDEV; } else if (err) return err; bstart = dbstart(dentry); if (dbend(dentry) == bstart || dbopaque(dentry) == bstart) return 0; set_dbstart(dentry, bstart + 1); err = check_empty(dentry, NULL); set_dbstart(dentry, bstart); if (err == -ENOTEMPTY) err = -EXDEV; return err; }
static int unionfs_setattr(struct dentry *dentry, struct iattr *ia) { int err = 0; struct dentry *lower_dentry; struct dentry *parent; struct inode *inode; struct inode *lower_inode; int bstart, bend, bindex; loff_t size; struct iattr lower_ia; /* check if user has permission to change inode */ err = inode_change_ok(dentry->d_inode, ia); if (err) goto out_err; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); if (unlikely(!__unionfs_d_revalidate(dentry, parent, false, 0))) { err = -ESTALE; goto out; } bstart = dbstart(dentry); bend = dbend(dentry); inode = dentry->d_inode; /* * mode change is for clearing setuid/setgid. Allow lower filesystem * to reinterpret it in its own way. */ if (ia->ia_valid & (ATTR_KILL_SUID | ATTR_KILL_SGID)) ia->ia_valid &= ~ATTR_MODE; lower_dentry = unionfs_lower_dentry(dentry); if (!lower_dentry) { /* should never happen after above revalidate */ err = -EINVAL; goto out; } /* * Get the lower inode directly from lower dentry, in case ibstart * is -1 (which happens when the file is open but unlinked. */ lower_inode = lower_dentry->d_inode; /* check if user has permission to change lower inode */ err = inode_change_ok(lower_inode, ia); if (err) goto out; /* copyup if the file is on a read only branch */ if (is_robranch_super(dentry->d_sb, bstart) || __is_rdonly(lower_inode)) { /* check if we have a branch to copy up to */ if (bstart <= 0) { err = -EACCES; goto out; } if (ia->ia_valid & ATTR_SIZE) size = ia->ia_size; else size = i_size_read(inode); /* copyup to next available branch */ for (bindex = bstart - 1; bindex >= 0; bindex--) { err = copyup_dentry(parent->d_inode, dentry, bstart, bindex, dentry->d_name.name, dentry->d_name.len, NULL, size); if (!err) break; } if (err) goto out; /* get updated lower_dentry/inode after copyup */ lower_dentry = unionfs_lower_dentry(dentry); lower_inode = unionfs_lower_inode(inode); /* * check for whiteouts in writeable branch, and remove them * if necessary. */ if (lower_dentry) { err = check_unlink_whiteout(dentry, lower_dentry, bindex); if (err > 0) /* ignore if whiteout found and removed */ err = 0; } } /* * If shrinking, first truncate upper level to cancel writing dirty * pages beyond the new eof; and also if its' maxbytes is more * limiting (fail with -EFBIG before making any change to the lower * level). There is no need to vmtruncate the upper level * afterwards in the other cases: we fsstack_copy_inode_size from * the lower level. */ if (ia->ia_valid & ATTR_SIZE) { err = inode_newsize_ok(inode, ia->ia_size); if (err) goto out; truncate_setsize(inode, ia->ia_size); } /* notify the (possibly copied-up) lower inode */ /* * Note: we use lower_dentry->d_inode, because lower_inode may be * unlinked (no inode->i_sb and i_ino==0. This happens if someone * tries to open(), unlink(), then ftruncate() a file. */ /* prepare our own lower struct iattr (with our own lower file) */ memcpy(&lower_ia, ia, sizeof(lower_ia)); if (ia->ia_valid & ATTR_FILE) { lower_ia.ia_file = unionfs_lower_file(ia->ia_file); BUG_ON(!lower_ia.ia_file); // XXX? } mutex_lock(&lower_dentry->d_inode->i_mutex); err = notify_change(lower_dentry, &lower_ia); mutex_unlock(&lower_dentry->d_inode->i_mutex); if (err) goto out; /* get attributes from the first lower inode */ if (ibstart(inode) >= 0) unionfs_copy_attr_all(inode, lower_inode); /* * unionfs_copy_attr_all will copy the lower times to our inode if * the lower ones are newer (useful for cache coherency). However, * ->setattr is the only place in which we may have to copy the * lower inode times absolutely, to support utimes(2). */ if (ia->ia_valid & ATTR_MTIME_SET) inode->i_mtime = lower_inode->i_mtime; if (ia->ia_valid & ATTR_CTIME) inode->i_ctime = lower_inode->i_ctime; if (ia->ia_valid & ATTR_ATIME_SET) inode->i_atime = lower_inode->i_atime; fsstack_copy_inode_size(inode, lower_inode); out: if (!err) unionfs_check_dentry(dentry); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); out_err: return err; }
static int unionfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) { int err = 0; struct dentry *lower_dentry = NULL; struct dentry *lower_parent_dentry = NULL; struct dentry *parent; int bindex = 0, bstart; char *name = NULL; int valid; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); valid = __unionfs_d_revalidate(dentry, parent, false, 0); if (unlikely(!valid)) { err = -ESTALE; /* same as what real_lookup does */ goto out; } bstart = dbstart(dentry); lower_dentry = unionfs_lower_dentry(dentry); /* check for a whiteout in new dentry branch, and delete it */ err = check_unlink_whiteout(dentry, lower_dentry, bstart); if (err > 0) /* whiteout found and removed successfully */ err = 0; if (err) { /* exit if the error returned was NOT -EROFS */ if (!IS_COPYUP_ERR(err)) goto out; bstart--; } /* check if copyup's needed, and mkdir */ for (bindex = bstart; bindex >= 0; bindex--) { int i; int bend = dbend(dentry); if (is_robranch_super(dentry->d_sb, bindex)) continue; lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); if (!lower_dentry) { lower_dentry = create_parents(dir, dentry, dentry->d_name.name, bindex); if (!lower_dentry || IS_ERR(lower_dentry)) { printk(KERN_ERR "unionfs: lower dentry " " NULL for bindex = %d\n", bindex); continue; } } lower_parent_dentry = lock_parent(lower_dentry); if (IS_ERR(lower_parent_dentry)) { err = PTR_ERR(lower_parent_dentry); goto out; } err = vfs_mkdir(lower_parent_dentry->d_inode, lower_dentry, mode); unlock_dir(lower_parent_dentry); /* did the mkdir succeed? */ if (err) break; for (i = bindex + 1; i <= bend; i++) { /* XXX: use path_put_lowers? */ if (unionfs_lower_dentry_idx(dentry, i)) { dput(unionfs_lower_dentry_idx(dentry, i)); unionfs_set_lower_dentry_idx(dentry, i, NULL); } } dbend(dentry) = bindex; /* * Only INTERPOSE_LOOKUP can return a value other than 0 on * err. */ err = PTR_ERR(unionfs_interpose(dentry, dir->i_sb, 0)); if (!err) { unionfs_copy_attr_times(dir); fsstack_copy_inode_size(dir, lower_parent_dentry->d_inode); /* update number of links on parent directory */ set_nlink(dir, unionfs_get_nlinks(dir)); } err = make_dir_opaque(dentry, dbstart(dentry)); if (err) { printk(KERN_ERR "unionfs: mkdir: error creating " ".wh.__dir_opaque: %d\n", err); goto out; } /* we are done! */ break; } out: if (!dentry->d_inode) d_drop(dentry); kfree(name); if (!err) { unionfs_copy_attr_times(dentry->d_inode); unionfs_postcopyup_setmnt(dentry); } unionfs_check_inode(dir); unionfs_check_dentry(dentry); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
/* 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; }
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; }
/* * 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); }
void __unionfs_check_file(const struct file *file, const char *fname, const char *fxn, int line) { int bindex; int dstart, dend, fstart, fend; struct dentry *dentry; struct file *lower_file; struct inode *inode; struct super_block *sb; int printed_caller = 0; BUG_ON(!file); dentry = file->f_path.dentry; sb = dentry->d_sb; dstart = dbstart(dentry); dend = dbend(dentry); BUG_ON(dstart > dend); fstart = fbstart(file); fend = fbend(file); BUG_ON(fstart > fend); if (unlikely((fstart == -1 && fend != -1) || (fstart != -1 && fend == -1))) { PRINT_CALLER(fname, fxn, line); pr_debug(" CF0: file/dentry=%p:%p fstart/end=%d:%d\n", file, dentry, fstart, fend); } if (unlikely(fstart != dstart)) { PRINT_CALLER(fname, fxn, line); pr_debug(" CF1: file/dentry=%p:%p fstart=%d dstart=%d\n", file, dentry, fstart, dstart); } if (unlikely(fend != dend)) { PRINT_CALLER(fname, fxn, line); pr_debug(" CF2: file/dentry=%p:%p fend=%d dend=%d\n", file, dentry, fend, dend); } inode = dentry->d_inode; if (!S_ISDIR(inode->i_mode)) { if (unlikely(fend != fstart)) { PRINT_CALLER(fname, fxn, line); pr_debug(" CF3: file/inode=%p:%p fstart=%d fend=%d\n", file, inode, fstart, fend); } if (unlikely(dend != dstart)) { PRINT_CALLER(fname, fxn, line); pr_debug(" CF4: file/dentry=%p:%p dstart=%d dend=%d\n", file, 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_file = unionfs_lower_file_idx(file, bindex); if (lower_file) { if (unlikely(bindex < fstart || bindex > fend)) { PRINT_CALLER(fname, fxn, line); pr_debug(" CF5: file/lower=%p:%p bindex=%d " "fstart/end=%d:%d\n", file, lower_file, bindex, fstart, fend); } } else { /* lower_file == NULL */ if (bindex >= fstart && bindex <= fend) { /* * 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 > fstart && bindex < fend))) { PRINT_CALLER(fname, fxn, line); pr_debug(" CF6: file/lower=%p:%p " "bindex=%d fstart/end=%d:%d\n", file, lower_file, bindex, fstart, fend); } } } } __unionfs_check_dentry(dentry, fname, fxn, line); }
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); } } }
/* This must be called with the super block already locked. */ int unionfs_ioctl_delbranch(struct super_block *sb, unsigned long arg) { struct dentry *hidden_dentry; struct inode *hidden_inode; struct super_block *hidden_sb; struct vfsmount *hidden_mnt; struct dentry *root_dentry; struct inode *root_inode; int err = 0; int pmindex, i, gen; print_entry("branch = %lu ", arg); lock_dentry(sb->s_root); err = -EBUSY; if (sbmax(sb) == 1) goto out; err = -EINVAL; if (arg < 0 || arg > stopd(sb)->b_end) goto out; err = -EBUSY; if (branch_count(sb, arg)) goto out; if ((err = newputmap(sb))) goto out; pmindex = stopd(sb)->usi_lastputmap; pmindex -= stopd(sb)->usi_firstputmap; atomic_inc(&stopd(sb)->usi_generation); gen = atomic_read(&stopd(sb)->usi_generation); root_dentry = sb->s_root; root_inode = sb->s_root->d_inode; hidden_dentry = dtohd_index(root_dentry, arg); hidden_mnt = stohiddenmnt_index(sb, arg); hidden_inode = itohi_index(root_inode, arg); hidden_sb = stohs_index(sb, arg); DPUT(hidden_dentry); iput(hidden_inode); mntput(hidden_mnt); for (i = arg; i <= (sbend(sb) - 1); i++) { set_branch_count(sb, i, branch_count(sb, i + 1)); set_stohiddenmnt_index(sb, i, stohiddenmnt_index(sb, i + 1)); set_stohs_index(sb, i, stohs_index(sb, i + 1)); set_branchperms(sb, i, branchperms(sb, i + 1)); set_dtohd_index(root_dentry, i, dtohd_index(root_dentry, i + 1)); set_itohi_index(root_inode, i, itohi_index(root_inode, i + 1)); stopd(sb)->usi_putmaps[pmindex]->map[i + 1] = i; } set_dtohd_index(root_dentry, sbend(sb), NULL); set_itohi_index(root_inode, sbend(sb), NULL); set_stohiddenmnt_index(sb, sbend(sb), NULL); set_stohs_index(sb, sbend(sb), NULL); stopd(sb)->b_end--; set_dbend(root_dentry, dbend(root_dentry) - 1); dtopd(root_dentry)->udi_bcount--; itopd(root_inode)->b_end--; atomic_set(&dtopd(root_dentry)->udi_generation, gen); atomic_set(&itopd(root_inode)->uii_generation, gen); fixputmaps(sb); /* This doesn't open a file, so we might have to free the map here. */ if (atomic_read(&stopd(sb)->usi_putmaps[pmindex]->count) == 0) { KFREE(stopd(sb)->usi_putmaps[pmindex]); stopd(sb)->usi_putmaps[pmindex] = NULL; } out: unlock_dentry(sb->s_root); print_exit_status(err); return err; }
int unionfs_ioctl_addbranch(struct inode *inode, unsigned int cmd, unsigned long arg) { int err; struct unionfs_addbranch_args *addargs = NULL; struct nameidata nd; char *path = NULL; int gen; int i; int count; int pobjects; struct vfsmount **new_hidden_mnt = NULL; struct inode **new_uii_inode = NULL; struct dentry **new_udi_dentry = NULL; struct super_block **new_usi_sb = NULL; int *new_branchperms = NULL; atomic_t *new_counts = NULL; print_entry_location(); err = -ENOMEM; addargs = KMALLOC(sizeof(struct unionfs_addbranch_args), GFP_UNIONFS); if (!addargs) goto out; err = -EFAULT; if (copy_from_user (addargs, (void *)arg, sizeof(struct unionfs_addbranch_args))) goto out; err = -EINVAL; if (addargs->ab_perms & ~(MAY_READ | MAY_WRITE)) goto out; if (!(addargs->ab_perms & MAY_READ)) goto out; err = -E2BIG; if (sbend(inode->i_sb) > FD_SETSIZE) goto out; err = -ENOMEM; if (!(path = getname(addargs->ab_path))) goto out; err = path_lookup(path, LOOKUP_FOLLOW, &nd); RECORD_PATH_LOOKUP(&nd); if (err) goto out; if ((err = check_branch(&nd))) { path_release(&nd); RECORD_PATH_RELEASE(&nd); goto out; } unionfs_write_lock(inode->i_sb); lock_dentry(inode->i_sb->s_root); err = -EINVAL; if (addargs->ab_branch < 0 || (addargs->ab_branch > (sbend(inode->i_sb) + 1))) goto out; if ((err = newputmap(inode->i_sb))) goto out; stopd(inode->i_sb)->b_end++; dtopd(inode->i_sb->s_root)->udi_bcount++; set_dbend(inode->i_sb->s_root, dbend(inode->i_sb->s_root) + 1); itopd(inode->i_sb->s_root->d_inode)->b_end++; atomic_inc(&stopd(inode->i_sb)->usi_generation); gen = atomic_read(&stopd(inode->i_sb)->usi_generation); pobjects = (sbend(inode->i_sb) + 1) - UNIONFS_INLINE_OBJECTS; if (pobjects > 0) { /* Reallocate the dynamic structures. */ new_hidden_mnt = KMALLOC(sizeof(struct vfsmount *) * pobjects, GFP_UNIONFS); new_udi_dentry = KMALLOC(sizeof(struct dentry *) * pobjects, GFP_UNIONFS); new_uii_inode = KMALLOC(sizeof(struct inode *) * pobjects, GFP_UNIONFS); new_usi_sb = KMALLOC(sizeof(struct super_block *) * pobjects, GFP_UNIONFS); new_counts = KMALLOC(sizeof(atomic_t) * pobjects, GFP_UNIONFS); new_branchperms = KMALLOC(sizeof(int) * pobjects, GFP_UNIONFS); if (!new_hidden_mnt || !new_udi_dentry || !new_uii_inode || !new_counts || !new_usi_sb || !new_branchperms) { err = -ENOMEM; goto out; } memset(new_hidden_mnt, 0, sizeof(struct vfsmount *) * pobjects); memset(new_udi_dentry, 0, sizeof(struct dentry *) * pobjects); memset(new_uii_inode, 0, sizeof(struct inode *) * pobjects); memset(new_usi_sb, 0, sizeof(struct super_block *) * pobjects); memset(new_branchperms, 0, sizeof(int) * pobjects); } /* Copy the in-place values to our new structure. */ for (i = UNIONFS_INLINE_OBJECTS; i < addargs->ab_branch; i++) { int j = i - UNIONFS_INLINE_OBJECTS; count = branch_count(inode->i_sb, i); atomic_set(&(new_counts[j]), count); new_branchperms[j] = branchperms(inode->i_sb, i); new_hidden_mnt[j] = stohiddenmnt_index(inode->i_sb, i); new_usi_sb[j] = stohs_index(inode->i_sb, i); new_udi_dentry[j] = dtohd_index(inode->i_sb->s_root, i); new_uii_inode[j] = itohi_index(inode->i_sb->s_root->d_inode, i); } /* Shift the ends to the right (only handle reallocated bits). */ for (i = sbend(inode->i_sb) - 1; i >= (int)addargs->ab_branch; i--) { int j = i + 1; int perms; struct vfsmount *hm; struct super_block *hs; struct dentry *hd; struct inode *hi; int pmindex; count = branch_count(inode->i_sb, i); perms = branchperms(inode->i_sb, i); hm = stohiddenmnt_index(inode->i_sb, i); hs = stohs_index(inode->i_sb, i); hd = dtohd_index(inode->i_sb->s_root, i); hi = itohi_index(inode->i_sb->s_root->d_inode, i); /* Update the newest putmap, so it is correct for later. */ pmindex = stopd(inode->i_sb)->usi_lastputmap; pmindex -= stopd(inode->i_sb)->usi_firstputmap; stopd(inode->i_sb)->usi_putmaps[pmindex]->map[i] = j; if (j >= UNIONFS_INLINE_OBJECTS) { j -= UNIONFS_INLINE_OBJECTS; atomic_set(&(new_counts[j]), count); new_branchperms[j] = perms; new_hidden_mnt[j] = hm; new_usi_sb[j] = hs; new_udi_dentry[j] = hd; new_uii_inode[j] = hi; } else { set_branch_count(inode->i_sb, j, count); set_branchperms(inode->i_sb, j, perms); set_stohiddenmnt_index(inode->i_sb, j, hm); set_stohs_index(inode->i_sb, j, hs); set_dtohd_index(inode->i_sb->s_root, j, hd); set_itohi_index(inode->i_sb->s_root->d_inode, j, hi); } } /* Now we can free the old ones. */ KFREE(dtopd(inode->i_sb->s_root)->udi_dentry_p); KFREE(itopd(inode->i_sb->s_root->d_inode)->uii_inode_p); KFREE(stopd(inode->i_sb)->usi_hidden_mnt_p); KFREE(stopd(inode->i_sb)->usi_sb_p); KFREE(stopd(inode->i_sb)->usi_sbcount_p); KFREE(stopd(inode->i_sb)->usi_branchperms_p); /* Update the real pointers. */ dtohd_ptr(inode->i_sb->s_root) = new_udi_dentry; itohi_ptr(inode->i_sb->s_root->d_inode) = new_uii_inode; stohiddenmnt_ptr(inode->i_sb) = new_hidden_mnt; stohs_ptr(inode->i_sb) = new_usi_sb; stopd(inode->i_sb)->usi_sbcount_p = new_counts; stopd(inode->i_sb)->usi_branchperms_p = new_branchperms; /* Re-NULL the new ones so we don't try to free them. */ new_hidden_mnt = NULL; new_udi_dentry = NULL; new_usi_sb = NULL; new_uii_inode = NULL; new_counts = NULL; new_branchperms = NULL; /* Put the new dentry information into it's slot. */ set_dtohd_index(inode->i_sb->s_root, addargs->ab_branch, nd.dentry); set_itohi_index(inode->i_sb->s_root->d_inode, addargs->ab_branch, igrab(nd.dentry->d_inode)); set_branchperms(inode->i_sb, addargs->ab_branch, addargs->ab_perms); set_branch_count(inode->i_sb, addargs->ab_branch, 0); set_stohiddenmnt_index(inode->i_sb, addargs->ab_branch, nd.mnt); set_stohs_index(inode->i_sb, addargs->ab_branch, nd.dentry->d_sb); atomic_set(&dtopd(inode->i_sb->s_root)->udi_generation, gen); atomic_set(&itopd(inode->i_sb->s_root->d_inode)->uii_generation, gen); fixputmaps(inode->i_sb); out: unlock_dentry(inode->i_sb->s_root); unionfs_write_unlock(inode->i_sb); KFREE(new_hidden_mnt); KFREE(new_udi_dentry); KFREE(new_uii_inode); KFREE(new_usi_sb); KFREE(new_counts); KFREE(new_branchperms); KFREE(addargs); if (path) putname(path); print_exit_status(err); return err; }
/* Is a directory logically empty? */ int check_empty(struct dentry *dentry, struct dentry *parent, struct unionfs_dir_state **namelist) { int err = 0; struct dentry *lower_dentry = NULL; struct vfsmount *mnt; struct super_block *sb; struct file *lower_file; struct unionfs_rdutil_callback *buf = NULL; int bindex, bstart, bend, bopaque; sb = dentry->d_sb; BUG_ON(!S_ISDIR(dentry->d_inode->i_mode)); err = unionfs_partial_lookup(dentry, parent); if (err) goto out; bstart = dbstart(dentry); bend = dbend(dentry); bopaque = dbopaque(dentry); if (0 <= bopaque && bopaque < bend) bend = bopaque; buf = kmalloc(sizeof(struct unionfs_rdutil_callback), GFP_KERNEL); if (unlikely(!buf)) { err = -ENOMEM; goto out; } buf->err = 0; buf->mode = RD_CHECK_EMPTY; buf->rdstate = alloc_rdstate(dentry->d_inode, bstart); if (unlikely(!buf->rdstate)) { err = -ENOMEM; goto out; } /* Process the lower directories with rdutil_callback as a filldir. */ for (bindex = bstart; bindex <= bend; bindex++) { lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); if (!lower_dentry) continue; if (!lower_dentry->d_inode) continue; if (!S_ISDIR(lower_dentry->d_inode->i_mode)) continue; dget(lower_dentry); mnt = unionfs_mntget(dentry, bindex); branchget(sb, bindex); lower_file = dentry_open(lower_dentry, mnt, O_RDONLY); if (IS_ERR(lower_file)) { err = PTR_ERR(lower_file); branchput(sb, bindex); goto out; } do { buf->filldir_called = 0; buf->rdstate->bindex = bindex; err = vfs_readdir(lower_file, readdir_util_callback, buf); if (buf->err) err = buf->err; } while ((err >= 0) && buf->filldir_called); /* fput calls dput for lower_dentry */ fput(lower_file); branchput(sb, bindex); if (err < 0) goto out; } out: if (buf) { if (namelist && !err) *namelist = buf->rdstate; else if (buf->rdstate) free_rdstate(buf->rdstate); kfree(buf); } return err; }