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