/* * 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; }
/* 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; }
/* * 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; }