/* 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; }
/* * Post-copyup helper to ensure we have valid mnts: set lower mnt of * dentry+parents to the first parent node that has an mnt. */ void unionfs_postcopyup_setmnt(struct dentry *dentry) { struct dentry *parent, *hasone; int bindex = dbstart(dentry); if (unionfs_lower_mnt_idx(dentry, bindex)) return; hasone = dentry->d_parent; /* this loop should stop at root dentry */ while (!unionfs_lower_mnt_idx(hasone, bindex)) hasone = hasone->d_parent; parent = dentry; while (!unionfs_lower_mnt_idx(parent, bindex)) { unionfs_set_lower_mnt_idx(parent, bindex, unionfs_mntget(hasone, bindex)); parent = parent->d_parent; } }
/* * 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; }
/* 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; }
/* * 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_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); } } }
static int __copyup_reg_data(struct dentry *dentry, struct dentry *new_lower_dentry, int new_bindex, struct dentry *old_lower_dentry, int old_bindex, struct file **copyup_file, loff_t len) { struct super_block *sb = dentry->d_sb; struct file *input_file; struct file *output_file; struct vfsmount *output_mnt; mm_segment_t old_fs; char *buf = NULL; ssize_t read_bytes, write_bytes; loff_t size; int err = 0; /* open old file */ unionfs_mntget(dentry, old_bindex); branchget(sb, old_bindex); /* dentry_open calls dput and mntput if it returns an error */ input_file = dentry_open(old_lower_dentry, unionfs_lower_mnt_idx(dentry, old_bindex), O_RDONLY | O_LARGEFILE); if (IS_ERR(input_file)) { dput(old_lower_dentry); err = PTR_ERR(input_file); goto out; } if (unlikely(!input_file->f_op || !input_file->f_op->read)) { err = -EINVAL; goto out_close_in; } /* open new file */ dget(new_lower_dentry); output_mnt = unionfs_mntget(sb->s_root, new_bindex); branchget(sb, new_bindex); output_file = dentry_open(new_lower_dentry, output_mnt, O_RDWR | O_LARGEFILE); if (IS_ERR(output_file)) { err = PTR_ERR(output_file); goto out_close_in2; } if (unlikely(!output_file->f_op || !output_file->f_op->write)) { err = -EINVAL; goto out_close_out; } /* allocating a buffer */ buf = kmalloc(PAGE_SIZE, GFP_KERNEL); if (unlikely(!buf)) { err = -ENOMEM; goto out_close_out; } input_file->f_pos = 0; output_file->f_pos = 0; old_fs = get_fs(); set_fs(KERNEL_DS); size = len; err = 0; 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, (char __user *)buf, size, &input_file->f_pos); if (read_bytes <= 0) { err = read_bytes; break; } /* see Documentation/filesystems/unionfs/issues.txt */ lockdep_off(); write_bytes = output_file->f_op->write(output_file, (char __user *)buf, read_bytes, &output_file->f_pos); lockdep_on(); if ((write_bytes < 0) || (write_bytes < read_bytes)) { err = write_bytes; break; } } while ((read_bytes > 0) && (len > 0)); set_fs(old_fs); kfree(buf); if (!err) err = output_file->f_op->fsync(output_file, new_lower_dentry, 0); if (err) goto out_close_out; if (copyup_file) { *copyup_file = output_file; goto out_close_in; } out_close_out: fput(output_file); out_close_in2: branchput(sb, new_bindex); out_close_in: fput(input_file); out: branchput(sb, old_bindex); return err; }
struct dentry *unionfs_lookup_backend(struct dentry *dentry, struct nameidata *nd, int lookupmode) { int err = 0; struct dentry *hidden_dentry = NULL; struct dentry *wh_hidden_dentry = NULL; struct dentry *hidden_dir_dentry = NULL; struct dentry *parent_dentry = NULL; int bindex, bstart, bend, bopaque; int dentry_count = 0; /* Number of positive dentries. */ int first_dentry_offset = -1; struct dentry *first_hidden_dentry = NULL; struct vfsmount *first_hidden_mnt = NULL; int locked_parent = 0; int locked_child = 0; int opaque; char *whname = NULL; const char *name; int namelen; /* We should already have a lock on this dentry in the case of a * partial lookup, or a revalidation. Otherwise it is returned from * new_dentry_private_data already locked. */ if (lookupmode == INTERPOSE_PARTIAL || lookupmode == INTERPOSE_REVAL || lookupmode == INTERPOSE_REVAL_NEG) verify_locked(dentry); else { BUG_ON(UNIONFS_D(dentry) != NULL); locked_child = 1; } if (lookupmode != INTERPOSE_PARTIAL) if ((err = new_dentry_private_data(dentry))) goto out; /* must initialize dentry operations */ dentry->d_op = &unionfs_dops; parent_dentry = dget_parent(dentry); /* We never partial lookup the root directory. */ if (parent_dentry != dentry) { unionfs_lock_dentry(parent_dentry); locked_parent = 1; } else { dput(parent_dentry); parent_dentry = NULL; goto out; } name = dentry->d_name.name; namelen = dentry->d_name.len; /* No dentries should get created for possible whiteout names. */ if (!is_validname(name)) { err = -EPERM; goto out_free; } /* Now start the actual lookup procedure. */ bstart = dbstart(parent_dentry); bend = dbend(parent_dentry); bopaque = dbopaque(parent_dentry); BUG_ON(bstart < 0); /* It would be ideal if we could convert partial lookups to only have * to do this work when they really need to. It could probably improve * performance quite a bit, and maybe simplify the rest of the code. */ if (lookupmode == INTERPOSE_PARTIAL) { bstart++; if ((bopaque != -1) && (bopaque < bend)) bend = bopaque; } for (bindex = bstart; bindex <= bend; bindex++) { hidden_dentry = unionfs_lower_dentry_idx(dentry, bindex); if (lookupmode == INTERPOSE_PARTIAL && hidden_dentry) continue; BUG_ON(hidden_dentry != NULL); hidden_dir_dentry = unionfs_lower_dentry_idx(parent_dentry, bindex); /* if the parent hidden dentry does not exist skip this */ if (!(hidden_dir_dentry && hidden_dir_dentry->d_inode)) continue; /* also skip it if the parent isn't a directory. */ if (!S_ISDIR(hidden_dir_dentry->d_inode->i_mode)) continue; /* Reuse the whiteout name because its value doesn't change. */ if (!whname) { whname = alloc_whname(name, namelen); if (IS_ERR(whname)) { err = PTR_ERR(whname); goto out_free; } } /* check if whiteout exists in this branch: lookup .wh.foo */ wh_hidden_dentry = lookup_one_len(whname, hidden_dir_dentry, namelen + UNIONFS_WHLEN); if (IS_ERR(wh_hidden_dentry)) { dput(first_hidden_dentry); mntput(first_hidden_mnt); err = PTR_ERR(wh_hidden_dentry); goto out_free; } if (wh_hidden_dentry->d_inode) { /* We found a whiteout so lets give up. */ if (S_ISREG(wh_hidden_dentry->d_inode->i_mode)) { set_dbend(dentry, bindex); set_dbopaque(dentry, bindex); dput(wh_hidden_dentry); break; } err = -EIO; printk(KERN_NOTICE "EIO: Invalid whiteout entry type" " %d.\n", wh_hidden_dentry->d_inode->i_mode); dput(wh_hidden_dentry); dput(first_hidden_dentry); mntput(first_hidden_mnt); goto out_free; } dput(wh_hidden_dentry); wh_hidden_dentry = NULL; /* Now do regular lookup; lookup foo */ nd->dentry = unionfs_lower_dentry_idx(dentry, bindex); /* FIXME: fix following line for mount point crossing */ nd->mnt = unionfs_lower_mnt_idx(parent_dentry, bindex); hidden_dentry = lookup_one_len_nd(name, hidden_dir_dentry, namelen, nd); if (IS_ERR(hidden_dentry)) { dput(first_hidden_dentry); mntput(first_hidden_mnt); err = PTR_ERR(hidden_dentry); goto out_free; } /* Store the first negative dentry specially, because if they * are all negative we need this for future creates. */ if (!hidden_dentry->d_inode) { if (!first_hidden_dentry && (dbstart(dentry) == -1)) { first_hidden_dentry = hidden_dentry; /* FIXME: following line needs to be changed * to allow mountpoint crossing */ first_hidden_mnt = mntget( unionfs_lower_mnt_idx(parent_dentry, bindex)); first_dentry_offset = bindex; } else dput(hidden_dentry); continue; } /* number of positive dentries */ dentry_count++; /* store underlying dentry */ if (dbstart(dentry) == -1) set_dbstart(dentry, bindex); unionfs_set_lower_dentry_idx(dentry, bindex, hidden_dentry); /* FIXME: the following line needs to get fixed to allow * mountpoint crossing */ unionfs_set_lower_mnt_idx(dentry, bindex, mntget(unionfs_lower_mnt_idx(parent_dentry, bindex))); set_dbend(dentry, bindex); /* update parent directory's atime with the bindex */ fsstack_copy_attr_atime(parent_dentry->d_inode, hidden_dir_dentry->d_inode); /* We terminate file lookups here. */ if (!S_ISDIR(hidden_dentry->d_inode->i_mode)) { if (lookupmode == INTERPOSE_PARTIAL) continue; if (dentry_count == 1) goto out_positive; /* This can only happen with mixed D-*-F-* */ BUG_ON(!S_ISDIR(unionfs_lower_dentry(dentry)->d_inode->i_mode)); continue; } opaque = is_opaque_dir(dentry, bindex); if (opaque < 0) { dput(first_hidden_dentry); mntput(first_hidden_mnt); err = opaque; goto out_free; } else if (opaque) { set_dbend(dentry, bindex); set_dbopaque(dentry, bindex); break; } } if (dentry_count) goto out_positive; else goto out_negative; out_negative: if (lookupmode == INTERPOSE_PARTIAL) goto out; /* If we've only got negative dentries, then use the leftmost one. */ if (lookupmode == INTERPOSE_REVAL) { if (dentry->d_inode) UNIONFS_I(dentry->d_inode)->stale = 1; goto out; } /* This should only happen if we found a whiteout. */ if (first_dentry_offset == -1) { nd->dentry = dentry; /* FIXME: fix following line for mount point crossing */ nd->mnt = unionfs_lower_mnt_idx(parent_dentry, bindex); first_hidden_dentry = lookup_one_len_nd(name, hidden_dir_dentry, namelen, nd); first_dentry_offset = bindex; if (IS_ERR(first_hidden_dentry)) { err = PTR_ERR(first_hidden_dentry); goto out; } /* FIXME: the following line needs to be changed to allow * mountpoint crossing */ first_hidden_mnt = mntget(unionfs_lower_mnt_idx(dentry, bindex)); } unionfs_set_lower_dentry_idx(dentry, first_dentry_offset, first_hidden_dentry); unionfs_set_lower_mnt_idx(dentry, first_dentry_offset, first_hidden_mnt); set_dbstart(dentry, first_dentry_offset); set_dbend(dentry, first_dentry_offset); if (lookupmode == INTERPOSE_REVAL_NEG) BUG_ON(dentry->d_inode != NULL); else d_add(dentry, NULL); goto out; /* This part of the code is for positive dentries. */ out_positive: BUG_ON(dentry_count <= 0); /* If we're holding onto the first negative dentry & corresponding * vfsmount - throw it out. */ dput(first_hidden_dentry); mntput(first_hidden_mnt); /* Partial lookups need to reinterpose, or throw away older negs. */ if (lookupmode == INTERPOSE_PARTIAL) { if (dentry->d_inode) { unionfs_reinterpose(dentry); goto out; } /* This somehow turned positive, so it is as if we had a * negative revalidation. */ lookupmode = INTERPOSE_REVAL_NEG; update_bstart(dentry); bstart = dbstart(dentry); bend = dbend(dentry); } err = unionfs_interpose(dentry, dentry->d_sb, lookupmode); if (err) goto out_drop; goto out; out_drop: d_drop(dentry); out_free: /* should dput all the underlying dentries on error condition */ bstart = dbstart(dentry); if (bstart >= 0) { bend = dbend(dentry); for (bindex = bstart; bindex <= bend; bindex++) { dput(unionfs_lower_dentry_idx(dentry, bindex)); mntput(unionfs_lower_mnt_idx(dentry, bindex)); } } kfree(UNIONFS_D(dentry)->lower_paths); UNIONFS_D(dentry)->lower_paths = NULL; set_dbstart(dentry, -1); set_dbend(dentry, -1); out: if (!err && UNIONFS_D(dentry)) { BUG_ON(dbend(dentry) > UNIONFS_D(dentry)->bcount); BUG_ON(dbend(dentry) > sbmax(dentry->d_sb)); BUG_ON(dbstart(dentry) < 0); } kfree(whname); if (locked_parent) unionfs_unlock_dentry(parent_dentry); dput(parent_dentry); if (locked_child) unionfs_unlock_dentry(dentry); return ERR_PTR(err); }
static int __copyup_reg_data(struct dentry *dentry, struct dentry *new_hidden_dentry, int new_bindex, struct dentry *old_hidden_dentry, int old_bindex, struct file **copyup_file, loff_t len) { struct super_block *sb = dentry->d_sb; struct file *input_file; struct file *output_file; mm_segment_t old_fs; char *buf = NULL; ssize_t read_bytes, write_bytes; loff_t size; int err = 0; /* open old file */ mntget(unionfs_lower_mnt_idx(dentry, old_bindex)); branchget(sb, old_bindex); input_file = dentry_open(old_hidden_dentry, unionfs_lower_mnt_idx(dentry, old_bindex), O_RDONLY | O_LARGEFILE); if (IS_ERR(input_file)) { dput(old_hidden_dentry); err = PTR_ERR(input_file); goto out; } if (!input_file->f_op || !input_file->f_op->read) { err = -EINVAL; goto out_close_in; } /* open new file */ dget(new_hidden_dentry); mntget(unionfs_lower_mnt_idx(dentry, new_bindex)); branchget(sb, new_bindex); output_file = dentry_open(new_hidden_dentry, unionfs_lower_mnt_idx(dentry, new_bindex), O_WRONLY | O_LARGEFILE); if (IS_ERR(output_file)) { err = PTR_ERR(output_file); goto out_close_in2; } if (!output_file->f_op || !output_file->f_op->write) { err = -EINVAL; goto out_close_out; } /* allocating a buffer */ buf = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!buf) { err = -ENOMEM; goto out_close_out; } input_file->f_pos = 0; output_file->f_pos = 0; old_fs = get_fs(); set_fs(KERNEL_DS); size = len; err = 0; 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, (char __user *)buf, size, &input_file->f_pos); if (read_bytes <= 0) { err = read_bytes; break; } write_bytes = output_file->f_op->write(output_file, (char __user *)buf, read_bytes, &output_file->f_pos); if ((write_bytes < 0) || (write_bytes < read_bytes)) { err = write_bytes; break; } } while ((read_bytes > 0) && (len > 0)); set_fs(old_fs); kfree(buf); if (err) goto out_close_out; if (copyup_file) { *copyup_file = output_file; goto out_close_in; } out_close_out: fput(output_file); out_close_in2: branchput(sb, new_bindex); out_close_in: fput(input_file); out: branchput(sb, old_bindex); return err; }
/* unionfs_open helper function: open a file */ static int __open_file(struct inode *inode, struct file *file, struct dentry *parent) { struct dentry *lower_dentry; struct file *lower_file; int lower_flags; int bindex, bstart, bend; lower_dentry = unionfs_lower_dentry(file->f_path.dentry); lower_flags = file->f_flags; bstart = fbstart(file) = dbstart(file->f_path.dentry); bend = fbend(file) = dbend(file->f_path.dentry); /* * check for the permission for lower file. If the error is * COPYUP_ERR, copyup the file. */ if (lower_dentry->d_inode && is_robranch(file->f_path.dentry)) { /* * if the open will change the file, copy it up otherwise * defer it. */ if (lower_flags & O_TRUNC) { int size = 0; int err = -EROFS; /* copyup the file */ for (bindex = bstart - 1; bindex >= 0; bindex--) { err = copyup_file(parent->d_inode, file, bstart, bindex, size); if (!err) { /* only one regular file open */ fbend(file) = fbstart(file); break; } } return err; } else { /* * turn off writeable flags, to force delayed copyup * by caller. */ lower_flags &= ~(OPEN_WRITE_FLAGS); } } dget(lower_dentry); /* * dentry_open will decrement mnt refcnt if err. * otherwise fput() will do an mntput() for us upon file close. */ unionfs_mntget(file->f_path.dentry, bstart); lower_file = dentry_open(lower_dentry, unionfs_lower_mnt_idx(file->f_path.dentry, bstart), lower_flags, current_cred()); if (IS_ERR(lower_file)) return PTR_ERR(lower_file); unionfs_set_lower_file(file, lower_file); branchget(inode->i_sb, bstart); return 0; }