/* 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); }
/* copy a/m/ctime from the lower branch with the newest times */ void unionfs_copy_attr_times(struct inode *upper) { int bindex; struct inode *lower; if (!upper) return; if (ibstart(upper) < 0) { #ifdef CONFIG_UNION_FS_DEBUG WARN_ON(ibstart(upper) < 0); #endif /* CONFIG_UNION_FS_DEBUG */ return; } for (bindex = ibstart(upper); bindex <= ibend(upper); bindex++) { lower = unionfs_lower_inode_idx(upper, bindex); if (!lower) continue; /* not all lower dir objects may exist */ if (unlikely(timespec_compare(&upper->i_mtime, &lower->i_mtime) < 0)) upper->i_mtime = lower->i_mtime; if (unlikely(timespec_compare(&upper->i_ctime, &lower->i_ctime) < 0)) upper->i_ctime = lower->i_ctime; if (unlikely(timespec_compare(&upper->i_atime, &lower->i_atime) < 0)) upper->i_atime = lower->i_atime; } }
/* * 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; }
int unionfs_fsync(struct file *file, struct dentry *dentry, int datasync) { int bindex, bstart, bend; struct file *lower_file; struct dentry *lower_dentry; struct dentry *parent; struct inode *lower_inode, *inode; int err = -EINVAL; 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, true); if (unlikely(err)) goto out; unionfs_check_file(file); bstart = fbstart(file); bend = fbend(file); if (bstart < 0 || bend < 0) goto out; inode = dentry->d_inode; if (unlikely(!inode)) { printk(KERN_ERR "unionfs: null lower inode in unionfs_fsync\n"); goto out; } for (bindex = bstart; bindex <= bend; bindex++) { lower_inode = unionfs_lower_inode_idx(inode, bindex); if (!lower_inode || !lower_inode->i_fop->fsync) continue; lower_file = unionfs_lower_file_idx(file, bindex); lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); mutex_lock(&lower_inode->i_mutex); err = lower_inode->i_fop->fsync(lower_file, lower_dentry, datasync); if (!err && bindex == bstart) fsstack_copy_attr_times(inode, lower_inode); mutex_unlock(&lower_inode->i_mutex); if (err) goto out; } out: if (!err) unionfs_check_file(file); unionfs_unlock_dentry(dentry); unionfs_unlock_parent(dentry, parent); unionfs_read_unlock(dentry->d_sb); return err; }
void __show_inode_counts(const struct inode *inode, const char *file, const char *fxn, int line) { struct inode *lower_inode; int bindex; if (unlikely(!inode)) { pr_debug("SiC: Null inode\n"); return; } for (bindex = sbstart(inode->i_sb); bindex <= sbend(inode->i_sb); bindex++) { lower_inode = unionfs_lower_inode_idx(inode, bindex); if (unlikely(!lower_inode)) continue; pr_debug("SIC(%lu:%d:%d): lc=%d %s:%s:%d\n", inode->i_ino, bindex, atomic_read(&(inode)->i_count), atomic_read(&(lower_inode)->i_count), file, fxn, line); } }
void __show_inode_times(const struct inode *inode, const char *file, const char *fxn, int line) { struct inode *lower_inode; int bindex; for (bindex = ibstart(inode); bindex <= ibend(inode); bindex++) { lower_inode = unionfs_lower_inode_idx(inode, bindex); if (unlikely(!lower_inode)) continue; pr_debug("IT(%lu:%d): %s:%s:%d " "um=%lu/%lu lm=%lu/%lu uc=%lu/%lu lc=%lu/%lu\n", inode->i_ino, bindex, file, fxn, line, 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 is a helper function for rename, used when rename ends up with hosed * over dentries and we need to revert. */ static int unionfs_refresh_lower_dentry(struct dentry *dentry, struct dentry *parent, int bindex) { struct dentry *lower_dentry; struct dentry *lower_parent; int err = 0; verify_locked(dentry); lower_parent = unionfs_lower_dentry_idx(parent, bindex); BUG_ON(!S_ISDIR(lower_parent->d_inode->i_mode)); lower_dentry = lookup_one_len(dentry->d_name.name, lower_parent, dentry->d_name.len); if (IS_ERR(lower_dentry)) { err = PTR_ERR(lower_dentry); goto out; } dput(unionfs_lower_dentry_idx(dentry, bindex)); iput(unionfs_lower_inode_idx(dentry->d_inode, bindex)); unionfs_set_lower_inode_idx(dentry->d_inode, bindex, NULL); if (!lower_dentry->d_inode) { dput(lower_dentry); unionfs_set_lower_dentry_idx(dentry, bindex, NULL); } else { unionfs_set_lower_dentry_idx(dentry, bindex, lower_dentry); unionfs_set_lower_inode_idx(dentry->d_inode, bindex, igrab(lower_dentry->d_inode)); } out: 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); } }
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); } } }
/* * 1) Copyup the file * 2) Rename the file to '.unionfs<original inode#><counter>' - obviously * stolen from NFS's silly rename */ static int copyup_deleted_file(struct file *file, struct dentry *dentry, struct dentry *parent, int bstart, int bindex) { static unsigned int counter; const int i_inosize = sizeof(dentry->d_inode->i_ino) * 2; const int countersize = sizeof(counter) * 2; const int nlen = sizeof(".unionfs") + i_inosize + countersize - 1; char name[nlen + 1]; int err; struct dentry *tmp_dentry = NULL; struct dentry *lower_dentry; struct dentry *lower_dir_dentry = NULL; lower_dentry = unionfs_lower_dentry_idx(dentry, bstart); sprintf(name, ".unionfs%*.*lx", i_inosize, i_inosize, lower_dentry->d_inode->i_ino); /* * Loop, looking for an unused temp name to copyup to. * * It's somewhat silly that we look for a free temp tmp name in the * source branch (bstart) instead of the dest branch (bindex), where * the final name will be created. We _will_ catch it if somehow * the name exists in the dest branch, but it'd be nice to catch it * sooner than later. */ retry: tmp_dentry = NULL; do { char *suffix = name + nlen - countersize; dput(tmp_dentry); counter++; sprintf(suffix, "%*.*x", countersize, countersize, counter); pr_debug("unionfs: trying to rename %s to %s\n", dentry->d_name.name, name); tmp_dentry = lookup_lck_len(name, lower_dentry->d_parent, nlen); if (IS_ERR(tmp_dentry)) { err = PTR_ERR(tmp_dentry); goto out; } } while (tmp_dentry->d_inode != NULL); /* need negative dentry */ dput(tmp_dentry); err = copyup_named_file(parent->d_inode, file, name, bstart, bindex, i_size_read(file->f_path.dentry->d_inode)); if (err) { if (unlikely(err == -EEXIST)) goto retry; goto out; } /* bring it to the same state as an unlinked file */ lower_dentry = unionfs_lower_dentry_idx(dentry, dbstart(dentry)); if (!unionfs_lower_inode_idx(dentry->d_inode, bindex)) { atomic_inc(&lower_dentry->d_inode->i_count); unionfs_set_lower_inode_idx(dentry->d_inode, bindex, lower_dentry->d_inode); } lower_dir_dentry = lock_parent(lower_dentry); err = vfs_unlink(lower_dir_dentry->d_inode, lower_dentry); unlock_dir(lower_dir_dentry); out: if (!err) unionfs_check_dentry(dentry); 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; }