/* 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 int unionfs_readdir(struct file *file, void *dirent, filldir_t filldir) { int err = 0; struct file *lower_file = NULL; struct dentry *dentry = file->f_path.dentry; struct dentry *parent; struct inode *inode = NULL; struct unionfs_getdents_callback buf; struct unionfs_dir_state *uds; int bend; loff_t offset; unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); err = unionfs_file_revalidate(file, parent, false); if (unlikely(err)) goto out; inode = dentry->d_inode; uds = UNIONFS_F(file)->rdstate; if (!uds) { if (file->f_pos == DIREOF) { goto out; } else if (file->f_pos > 0) { uds = find_rdstate(inode, file->f_pos); if (unlikely(!uds)) { err = -ESTALE; goto out; } UNIONFS_F(file)->rdstate = uds; } else { init_rdstate(file); uds = UNIONFS_F(file)->rdstate; } } bend = fbend(file); while (uds->bindex <= bend) { lower_file = unionfs_lower_file_idx(file, uds->bindex); if (!lower_file) { uds->bindex++; uds->dirpos = 0; continue; } /* prepare callback buffer */ buf.filldir_called = 0; buf.filldir_error = 0; buf.entries_written = 0; buf.dirent = dirent; buf.filldir = filldir; buf.rdstate = uds; buf.sb = inode->i_sb; /* Read starting from where we last left off. */ offset = vfs_llseek(lower_file, uds->dirpos, SEEK_SET); if (offset < 0) { err = offset; goto out; } err = vfs_readdir(lower_file, unionfs_filldir, &buf); /* Save the position for when we continue. */ offset = vfs_llseek(lower_file, 0, SEEK_CUR); if (offset < 0) { err = offset; goto out; } uds->dirpos = offset; /* Copy the atime. */ fsstack_copy_attr_atime(inode, lower_file->f_path.dentry->d_inode); if (err < 0) goto out; if (buf.filldir_error) break; if (!buf.entries_written) { uds->bindex++; uds->dirpos = 0; } } if (!buf.filldir_error && uds->bindex >= bend) { /* Save the number of hash entries for next time. */ UNIONFS_I(inode)->hashsize = uds->hashentries; free_rdstate(uds); UNIONFS_F(file)->rdstate = NULL; file->f_pos = DIREOF; } else { file->f_pos = rdstate2offset(uds); } out: if (!err) unionfs_check_file(file); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
/* * Don't grab the superblock read-lock in unionfs_permission, which prevents * a deadlock with the branch-management "add branch" code (which grabbed * the write lock). It is safe to not grab the read lock here, because even * with branch management taking place, there is no chance that * unionfs_permission, or anything it calls, will use stale branch * information. */ static int unionfs_permission(struct inode *inode, int mask) { struct inode *lower_inode = NULL; int err = 0; int bindex, bstart, bend; int is_file; const int write_mask = (mask & MAY_WRITE) && !(mask & MAY_READ); struct inode *inode_grabbed; inode_grabbed = igrab(inode); is_file = !S_ISDIR(inode->i_mode); if (!UNIONFS_I(inode)->lower_inodes) { if (is_file) /* dirs can be unlinked but chdir'ed to */ err = -ESTALE; /* force revalidate */ goto out; } bstart = ibstart(inode); bend = ibend(inode); if (unlikely(bstart < 0 || bend < 0)) { /* * With branch-management, we can get a stale inode here. * If so, we return ESTALE back to link_path_walk, which * would discard the dcache entry and re-lookup the * dentry+inode. This should be equivalent to issuing * __unionfs_d_revalidate_chain on nd.dentry here. */ if (is_file) /* dirs can be unlinked but chdir'ed to */ err = -ESTALE; /* force revalidate */ goto out; } for (bindex = bstart; bindex <= bend; bindex++) { lower_inode = unionfs_lower_inode_idx(inode, bindex); if (!lower_inode) continue; /* * check the condition for D-F-D underlying files/directories, * we don't have to check for files, if we are checking for * directories. */ if (!is_file && !S_ISDIR(lower_inode->i_mode)) continue; /* * We check basic permissions, but we ignore any conditions * such as readonly file systems or branches marked as * readonly, because those conditions should lead to a * copyup taking place later on. However, if user never had * access to the file, then no copyup could ever take place. */ err = __inode_permission(lower_inode, mask); if (err && err != -EACCES && err != EPERM && bindex > 0) { umode_t mode = lower_inode->i_mode; if ((is_robranch_super(inode->i_sb, bindex) || __is_rdonly(lower_inode)) && (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))) err = 0; if (IS_COPYUP_ERR(err)) err = 0; } /* * NFS HACK: NFSv2/3 return EACCES on readonly-exported, * locally readonly-mounted file systems, instead of EROFS * like other file systems do. So we have no choice here * but to intercept this and ignore it for NFS branches * marked readonly. Specifically, we avoid using NFS's own * "broken" ->permission method, and rely on * generic_permission() to do basic checking for us. */ if (err && err == -EACCES && is_robranch_super(inode->i_sb, bindex) && lower_inode->i_sb->s_magic == NFS_SUPER_MAGIC) err = generic_permission(lower_inode, mask); /* * The permissions are an intersection of the overall directory * permissions, so we fail if one fails. */ if (err) goto out; /* only the leftmost file matters. */ if (is_file || write_mask) { if (is_file && write_mask) { err = get_write_access(lower_inode); if (!err) put_write_access(lower_inode); } break; } } /* sync times which may have changed (asynchronously) below */ unionfs_copy_attr_times(inode); out: unionfs_check_inode(inode); iput(inode_grabbed); return err; }
/* * __unionfs_check_{inode,dentry,file} perform exhaustive sanity checking on * the fan-out of various Unionfs objects. We check that no lower objects * exist outside the start/end branch range; that all objects within are * non-NULL (with some allowed exceptions); that for every lower file * there's a lower dentry+inode; that the start/end ranges match for all * corresponding lower objects; that open files/symlinks have only one lower * objects, but directories can have several; and more. */ void __unionfs_check_inode(const struct inode *inode, const char *fname, const char *fxn, int line) { int bindex; int istart, iend; struct inode *lower_inode; struct super_block *sb; int printed_caller = 0; void *poison_ptr; /* for inodes now */ BUG_ON(!inode); sb = inode->i_sb; istart = ibstart(inode); iend = ibend(inode); /* don't check inode if no lower branches */ if (istart < 0 && iend < 0) return; if (unlikely(istart > iend)) { PRINT_CALLER(fname, fxn, line); pr_debug(" Ci0: inode=%p istart/end=%d:%d\n", inode, istart, iend); } if (unlikely((istart == -1 && iend != -1) || (istart != -1 && iend == -1))) { PRINT_CALLER(fname, fxn, line); pr_debug(" Ci1: inode=%p istart/end=%d:%d\n", inode, istart, iend); } if (!S_ISDIR(inode->i_mode)) { if (unlikely(iend != istart)) { PRINT_CALLER(fname, fxn, line); pr_debug(" Ci2: inode=%p istart=%d iend=%d\n", inode, istart, iend); } } for (bindex = sbstart(sb); bindex < sbmax(sb); bindex++) { if (unlikely(!UNIONFS_I(inode))) { PRINT_CALLER(fname, fxn, line); pr_debug(" Ci3: no inode_info %p\n", inode); return; } if (unlikely(!UNIONFS_I(inode)->lower_inodes)) { PRINT_CALLER(fname, fxn, line); pr_debug(" Ci4: no lower_inodes %p\n", inode); return; } 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: inode/linode=%p:%p bindex=%d " "istart/end=%d:%d\n", inode, lower_inode, bindex, istart, iend); } else if (unlikely(lower_inode == poison_ptr)) { /* freed inode! */ PRINT_CALLER(fname, fxn, line); pr_debug(" Ci6: inode/linode=%p:%p bindex=%d " "istart/end=%d:%d\n", inode, 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: inode/linode=%p:%p " "bindex=%d istart/end=%d:%d\n", inode, lower_inode, bindex, istart, iend); } }
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); }
/* * Connect a unionfs inode dentry/inode with several lower ones. This is * the classic stackable file system "vnode interposition" action. * * @sb: unionfs's super_block */ struct dentry *unionfs_interpose(struct dentry *dentry, struct super_block *sb, int flag) { int err = 0; struct inode *inode; int need_fill_inode = 1; struct dentry *spliced = NULL; verify_locked(dentry); /* * We allocate our new inode below, by calling iget. * iget will call our read_inode which will initialize some * of the new inode's fields */ /* * On revalidate we've already got our own inode and just need * to fix it up. */ if (flag == INTERPOSE_REVAL) { inode = dentry->d_inode; UNIONFS_I(inode)->bstart = -1; UNIONFS_I(inode)->bend = -1; atomic_set(&UNIONFS_I(inode)->generation, atomic_read(&UNIONFS_SB(sb)->generation)); UNIONFS_I(inode)->lower_inodes = kcalloc(sbmax(sb), sizeof(struct inode *), GFP_KERNEL); if (unlikely(!UNIONFS_I(inode)->lower_inodes)) { err = -ENOMEM; goto out; } } else { /* get unique inode number for unionfs */ inode = iget(sb, iunique(sb, UNIONFS_ROOT_INO)); if (!inode) { err = -EACCES; goto out; } if (atomic_read(&inode->i_count) > 1) goto skip; } need_fill_inode = 0; unionfs_fill_inode(dentry, inode); skip: /* only (our) lookup wants to do a d_add */ switch (flag) { case INTERPOSE_DEFAULT: /* for operations which create new inodes */ d_add(dentry, inode); break; case INTERPOSE_REVAL_NEG: d_instantiate(dentry, inode); break; case INTERPOSE_LOOKUP: spliced = d_splice_alias(inode, dentry); if (spliced && spliced != dentry) { /* * d_splice can return a dentry if it was * disconnected and had to be moved. We must ensure * that the private data of the new dentry is * correct and that the inode info was filled * properly. Finally we must return this new * dentry. */ spliced->d_op = &unionfs_dops; spliced->d_fsdata = dentry->d_fsdata; dentry->d_fsdata = NULL; dentry = spliced; if (need_fill_inode) { need_fill_inode = 0; unionfs_fill_inode(dentry, inode); } goto out_spliced; } else if (!spliced) { if (need_fill_inode) { need_fill_inode = 0; unionfs_fill_inode(dentry, inode); goto out_spliced; } } break; case INTERPOSE_REVAL: /* Do nothing. */ break; default: printk(KERN_CRIT "unionfs: invalid interpose flag passed!\n"); BUG(); } goto out; out_spliced: if (!err) return spliced; out: return ERR_PTR(err); }
/* * release all lower object references & free the file info structure * * No need to grab sb info's rwsem. */ int unionfs_file_release(struct inode *inode, struct file *file) { struct file *lower_file = NULL; struct unionfs_file_info *fileinfo; struct unionfs_inode_info *inodeinfo; struct super_block *sb = inode->i_sb; struct dentry *dentry = file->f_path.dentry; struct dentry *parent; int bindex, bstart, bend; int fgen, err = 0; /* * Since mm/memory.c:might_fault() (under PROVE_LOCKING) was * modified in 2.6.29-rc1 to call might_lock_read on mmap_sem, this * has been causing false positives in file system stacking layers. * In particular, our ->mmap is called after sys_mmap2 already holds * mmap_sem, then we lock our own mutexes; but earlier, it's * possible for lockdep to have locked our mutexes first, and then * we call a lower ->readdir which could call might_fault. The * different ordering of the locks is what lockdep complains about * -- unnecessarily. Therefore, we have no choice but to tell * lockdep to temporarily turn off lockdep here. Note: the comments * inside might_sleep also suggest that it would have been * nicer to only annotate paths that needs that might_lock_read. */ lockdep_off(); unionfs_read_lock(sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); /* * We try to revalidate, but the VFS ignores return return values * from file->release, so we must always try to succeed here, * including to do the kfree and dput below. So if revalidation * failed, all we can do is print some message and keep going. */ err = unionfs_file_revalidate(file, parent, UNIONFS_F(file)->wrote_to_file); if (!err) unionfs_check_file(file); fileinfo = UNIONFS_F(file); BUG_ON(file->f_path.dentry->d_inode != inode); inodeinfo = UNIONFS_I(inode); /* fput all the lower files */ fgen = atomic_read(&fileinfo->generation); bstart = fbstart(file); bend = fbend(file); for (bindex = bstart; bindex <= bend; bindex++) { lower_file = unionfs_lower_file_idx(file, bindex); if (lower_file) { unionfs_set_lower_file_idx(file, bindex, NULL); fput(lower_file); branchput(sb, bindex); } /* if there are no more refs to the dentry, dput it */ if (d_deleted(dentry)) { dput(unionfs_lower_dentry_idx(dentry, bindex)); unionfs_set_lower_dentry_idx(dentry, bindex, NULL); } } kfree(fileinfo->lower_files); kfree(fileinfo->saved_branch_ids); if (fileinfo->rdstate) { fileinfo->rdstate->access = jiffies; spin_lock(&inodeinfo->rdlock); inodeinfo->rdcount++; list_add_tail(&fileinfo->rdstate->cache, &inodeinfo->readdircache); mark_inode_dirty(inode); spin_unlock(&inodeinfo->rdlock); fileinfo->rdstate = NULL; } kfree(fileinfo); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(sb); lockdep_on(); return err; }
int unionfs_open(struct inode *inode, struct file *file) { int err = 0; struct file *lower_file = NULL; struct dentry *dentry = file->f_path.dentry; struct dentry *parent; int bindex = 0, bstart = 0, bend = 0; int size; int valid = 0; unionfs_read_lock(inode->i_sb, UNIONFS_SMUTEX_PARENT); parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT); unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD); /* don't open unhashed/deleted files */ if (d_deleted(dentry)) { err = -ENOENT; goto out_nofree; } /* XXX: should I change 'false' below to the 'willwrite' flag? */ valid = __unionfs_d_revalidate(dentry, parent, false); if (unlikely(!valid)) { err = -ESTALE; goto out_nofree; } file->private_data = kzalloc(sizeof(struct unionfs_file_info), GFP_KERNEL); if (unlikely(!UNIONFS_F(file))) { err = -ENOMEM; goto out_nofree; } fbstart(file) = -1; fbend(file) = -1; atomic_set(&UNIONFS_F(file)->generation, atomic_read(&UNIONFS_I(inode)->generation)); size = sizeof(struct file *) * sbmax(inode->i_sb); UNIONFS_F(file)->lower_files = kzalloc(size, GFP_KERNEL); if (unlikely(!UNIONFS_F(file)->lower_files)) { err = -ENOMEM; goto out; } size = sizeof(int) * sbmax(inode->i_sb); UNIONFS_F(file)->saved_branch_ids = kzalloc(size, GFP_KERNEL); if (unlikely(!UNIONFS_F(file)->saved_branch_ids)) { err = -ENOMEM; goto out; } bstart = fbstart(file) = dbstart(dentry); bend = fbend(file) = dbend(dentry); /* * open all directories and make the unionfs file struct point to * these lower file structs */ if (S_ISDIR(inode->i_mode)) err = __open_dir(inode, file); /* open a dir */ else err = __open_file(inode, file, parent); /* open a file */ /* freeing the allocated resources, and fput the opened files */ if (err) { for (bindex = bstart; bindex <= bend; bindex++) { lower_file = unionfs_lower_file_idx(file, bindex); if (!lower_file) continue; branchput(dentry->d_sb, bindex); /* fput calls dput for lower_dentry */ fput(lower_file); } } out: if (err) { kfree(UNIONFS_F(file)->lower_files); kfree(UNIONFS_F(file)->saved_branch_ids); kfree(UNIONFS_F(file)); } out_nofree: if (!err) { unionfs_postcopyup_setmnt(dentry); unionfs_copy_attr_times(inode); unionfs_check_file(file); unionfs_check_inode(inode); } unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(inode->i_sb); return err; }
/* * Helper function for unionfs_file_revalidate/locked. * Expects dentry/parent to be locked already, and revalidated. */ static int __unionfs_file_revalidate(struct file *file, struct dentry *dentry, struct dentry *parent, struct super_block *sb, int sbgen, int dgen, bool willwrite) { int fgen; int bstart, bend, orig_brid; int size; int err = 0; fgen = atomic_read(&UNIONFS_F(file)->generation); /* * There are two cases we are interested in. The first is if the * generation is lower than the super-block. The second is if * someone has copied up this file from underneath us, we also need * to refresh things. */ if (d_deleted(dentry) || (sbgen <= fgen && dbstart(dentry) == fbstart(file) && unionfs_lower_file(file))) goto out_may_copyup; /* save orig branch ID */ orig_brid = UNIONFS_F(file)->saved_branch_ids[fbstart(file)]; /* First we throw out the existing files. */ cleanup_file(file); /* Now we reopen the file(s) as in unionfs_open. */ bstart = fbstart(file) = dbstart(dentry); bend = fbend(file) = dbend(dentry); size = sizeof(struct file *) * sbmax(sb); UNIONFS_F(file)->lower_files = kzalloc(size, GFP_KERNEL); if (unlikely(!UNIONFS_F(file)->lower_files)) { err = -ENOMEM; goto out; } size = sizeof(int) * sbmax(sb); UNIONFS_F(file)->saved_branch_ids = kzalloc(size, GFP_KERNEL); if (unlikely(!UNIONFS_F(file)->saved_branch_ids)) { err = -ENOMEM; goto out; } if (S_ISDIR(dentry->d_inode->i_mode)) { /* We need to open all the files. */ err = open_all_files(file); if (err) goto out; } else { int new_brid; /* We only open the highest priority branch. */ err = open_highest_file(file, willwrite); if (err) goto out; new_brid = UNIONFS_F(file)->saved_branch_ids[fbstart(file)]; if (unlikely(new_brid != orig_brid && sbgen > fgen)) { /* * If we re-opened the file on a different branch * than the original one, and this was due to a new * branch inserted, then update the mnt counts of * the old and new branches accordingly. */ unionfs_mntget(dentry, bstart); unionfs_mntput(sb->s_root, branch_id_to_idx(sb, orig_brid)); } /* regular files have only one open lower file */ fbend(file) = fbstart(file); } atomic_set(&UNIONFS_F(file)->generation, atomic_read(&UNIONFS_I(dentry->d_inode)->generation)); out_may_copyup: /* Copyup on the first write to a file on a readonly branch. */ if (willwrite && IS_WRITE_FLAG(file->f_flags) && !IS_WRITE_FLAG(unionfs_lower_file(file)->f_flags) && is_robranch(dentry)) { pr_debug("unionfs: do delay copyup of \"%s\"\n", dentry->d_name.name); err = do_delayed_copyup(file, parent); /* regular files have only one open lower file */ if (!err && !S_ISDIR(dentry->d_inode->i_mode)) fbend(file) = fbstart(file); } out: if (err) { kfree(UNIONFS_F(file)->lower_files); kfree(UNIONFS_F(file)->saved_branch_ids); } return err; }
/* * returns 1 if valid, 0 otherwise. */ int unionfs_d_revalidate(struct dentry *dentry, struct nameidata *nd) { int valid = 1; /* default is valid (1); invalid is 0. */ struct dentry *hidden_dentry; int bindex, bstart, bend; int sbgen, dgen; int positive = 0; int locked = 0; int restart = 0; int interpose_flag; struct nameidata lowernd; /* TODO: be gentler to the stack */ if (nd) memcpy(&lowernd, nd, sizeof(struct nameidata)); else memset(&lowernd, 0, sizeof(struct nameidata)); restart: verify_locked(dentry); /* if the dentry is unhashed, do NOT revalidate */ if (d_deleted(dentry)) { printk(KERN_DEBUG "unhashed dentry being revalidated: %*s\n", dentry->d_name.len, dentry->d_name.name); goto out; } BUG_ON(dbstart(dentry) == -1); if (dentry->d_inode) positive = 1; dgen = atomic_read(&UNIONFS_D(dentry)->generation); sbgen = atomic_read(&UNIONFS_SB(dentry->d_sb)->generation); /* If we are working on an unconnected dentry, then there is no * revalidation to be done, because this file does not exist within the * namespace, and Unionfs operates on the namespace, not data. */ if (sbgen != dgen) { struct dentry *result; int pdgen; unionfs_read_lock(dentry->d_sb); locked = 1; /* The root entry should always be valid */ BUG_ON(IS_ROOT(dentry)); /* We can't work correctly if our parent isn't valid. */ pdgen = atomic_read(&UNIONFS_D(dentry->d_parent)->generation); if (!restart && (pdgen != sbgen)) { unionfs_read_unlock(dentry->d_sb); locked = 0; /* We must be locked before our parent. */ if (! (dentry->d_parent->d_op-> d_revalidate(dentry->d_parent, nd))) { valid = 0; goto out; } restart = 1; goto restart; } BUG_ON(pdgen != sbgen); /* Free the pointers for our inodes and this dentry. */ bstart = dbstart(dentry); bend = dbend(dentry); if (bstart >= 0) { struct dentry *hidden_dentry; for (bindex = bstart; bindex <= bend; bindex++) { hidden_dentry = unionfs_lower_dentry_idx(dentry, bindex); dput(hidden_dentry); } } set_dbstart(dentry, -1); set_dbend(dentry, -1); interpose_flag = INTERPOSE_REVAL_NEG; if (positive) { interpose_flag = INTERPOSE_REVAL; mutex_lock(&dentry->d_inode->i_mutex); bstart = ibstart(dentry->d_inode); bend = ibend(dentry->d_inode); if (bstart >= 0) { struct inode *hidden_inode; for (bindex = bstart; bindex <= bend; bindex++) { hidden_inode = unionfs_lower_inode_idx(dentry->d_inode, bindex); iput(hidden_inode); } } kfree(UNIONFS_I(dentry->d_inode)->lower_inodes); UNIONFS_I(dentry->d_inode)->lower_inodes = NULL; ibstart(dentry->d_inode) = -1; ibend(dentry->d_inode) = -1; mutex_unlock(&dentry->d_inode->i_mutex); } result = unionfs_lookup_backend(dentry, &lowernd, interpose_flag); if (result) { if (IS_ERR(result)) { valid = 0; goto out; } /* current unionfs_lookup_backend() doesn't return * a valid dentry */ dput(dentry); dentry = result; } if (positive && UNIONFS_I(dentry->d_inode)->stale) { make_bad_inode(dentry->d_inode); d_drop(dentry); valid = 0; goto out; } goto out; } /* The revalidation must occur across all branches */ bstart = dbstart(dentry); bend = dbend(dentry); BUG_ON(bstart == -1); for (bindex = bstart; bindex <= bend; bindex++) { hidden_dentry = unionfs_lower_dentry_idx(dentry, bindex); if (!hidden_dentry || !hidden_dentry->d_op || !hidden_dentry->d_op->d_revalidate) continue; if (!hidden_dentry->d_op->d_revalidate(hidden_dentry, nd)) valid = 0; } if (!dentry->d_inode) valid = 0; if (valid) { fsstack_copy_attr_all(dentry->d_inode, unionfs_lower_inode(dentry->d_inode), unionfs_get_nlinks); fsstack_copy_inode_size(dentry->d_inode, unionfs_lower_inode(dentry->d_inode)); } out: if (locked) unionfs_read_unlock(dentry->d_sb); return valid; }