diff --git a/acinclude.m4 b/acinclude.m4 index 84dd9b09d7..96259e9444 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -740,6 +740,7 @@ case $AFS_SYSNAME in *_linux* | *_umlinux*) LINUX_KBUILD_USES_EXTRA_CFLAGS LINUX_KERNEL_COMPILE_WORKS LINUX_EXPORTS_FIND_TASK_BY_PID + AC_CHECK_LINUX_STRUCT([dentry_operations], [d_automount], [dcache.h]) LINUX_EXPORTS_PROC_ROOT_FS LINUX_HAVE_CURRENT_KERNEL_TIME LINUX_HAVE_WRITE_BEGIN_AOP diff --git a/src/afs/LINUX/osi_vnodeops.c b/src/afs/LINUX/osi_vnodeops.c index 8f9abee61b..c7fc2aeb0d 100644 --- a/src/afs/LINUX/osi_vnodeops.c +++ b/src/afs/LINUX/osi_vnodeops.c @@ -734,6 +734,61 @@ struct file_operations afs_file_fops = { .llseek = default_llseek, }; +static struct dentry * +canonical_dentry(struct inode *ip) +{ + struct vcache *vcp = VTOAFS(ip); + struct dentry *first = NULL, *ret = NULL, *cur; + + /* general strategy: + * if vcp->target_link is set, and can be found in ip->i_dentry, use that. + * otherwise, use the first dentry in ip->i_dentry. + * if ip->i_dentry is empty, use the 'dentry' argument we were given. + */ + /* note that vcp->target_link specifies which dentry to use, but we have + * no reference held on that dentry. so, we cannot use or dereference + * vcp->target_link itself, since it may have been freed. instead, we only + * use it to compare to pointers in the ip->i_dentry list. */ + + d_prune_aliases(ip); + +# ifdef HAVE_DCACHE_LOCK + spin_lock(&dcache_lock); +# else + spin_lock(&ip->i_lock); +# endif + + list_for_each_entry_reverse(cur, &ip->i_dentry, d_alias) { + + if (!vcp->target_link || cur == vcp->target_link) { + ret = cur; + break; + } + + if (!first) { + first = cur; + } + } + if (!ret && first) { + ret = first; + } + + vcp->target_link = ret; + +# ifdef HAVE_DCACHE_LOCK + if (ret) { + dget_locked(ret); + } + spin_unlock(&dcache_lock); +# else + if (ret) { + dget(ret); + } + spin_unlock(&ip->i_lock); +# endif + + return ret; +} /********************************************************************** * AFS Linux dentry operations @@ -1021,10 +1076,40 @@ afs_dentry_delete(struct dentry *dp) return 0; } +#ifdef STRUCT_DENTRY_OPERATIONS_HAS_D_AUTOMOUNT +static struct vfsmount * +afs_dentry_automount(struct path *path) +{ + struct dentry *target; + + target = canonical_dentry(path->dentry->d_inode); + + if (target == path->dentry) { + dput(target); + target = NULL; + } + + if (target) { + dput(path->dentry); + path->dentry = target; + + } else { + spin_lock(&path->dentry->d_lock); + path->dentry->d_flags &= ~DCACHE_NEED_AUTOMOUNT; + spin_unlock(&path->dentry->d_lock); + } + + return NULL; +} +#endif /* STRUCT_DENTRY_OPERATIONS_HAS_D_AUTOMOUNT */ + struct dentry_operations afs_dentry_operations = { .d_revalidate = afs_linux_dentry_revalidate, .d_delete = afs_dentry_delete, .d_iput = afs_dentry_iput, +#ifdef STRUCT_DENTRY_OPERATIONS_HAS_D_AUTOMOUNT + .d_automount = afs_dentry_automount, +#endif /* STRUCT_DENTRY_OPERATIONS_HAS_D_AUTOMOUNT */ }; /********************************************************************** @@ -1136,21 +1221,27 @@ afs_linux_lookup(struct inode *dip, struct dentry *dp) #if defined(AFS_LINUX24_ENV) if (ip && S_ISDIR(ip->i_mode)) { + int retry = 1; struct dentry *alias; - alias = d_find_alias(ip); - if (alias) { - if (d_invalidate(alias) == 0) { - dput(alias); - } else { - iput(ip); -#if defined(AFS_LINUX26_ENV) - unlock_kernel(); -#endif - crfree(credp); - return alias; + while (retry) { + retry = 0; + + /* Try to invalidate an existing alias in favor of our new one */ + alias = d_find_alias(ip); + if (alias) { + if (d_invalidate(alias) == 0) { + /* there may be more aliases; try again until we run out */ + retry = 1; + } } + + dput(alias); } + +#ifdef STRUCT_DENTRY_OPERATIONS_HAS_D_AUTOMOUNT + ip->i_flags |= S_AUTOMOUNT; +#endif } #endif d_add(dp, ip); @@ -1906,6 +1997,33 @@ afs_linux_write_begin(struct file *file, struct address_space *mapping, } #endif +#ifndef STRUCT_DENTRY_OPERATIONS_HAS_D_AUTOMOUNT +static void * +afs_linux_dir_follow_link(struct dentry *dentry, struct nameidata *nd) +{ + struct dentry **dpp; + struct dentry *target; + + target = canonical_dentry(dentry->d_inode); + +# ifdef STRUCT_NAMEIDATA_HAS_PATH + dpp = &nd->path.dentry; +# else + dpp = &nd->dentry; +# endif + + dput(*dpp); + + if (target) { + *dpp = target; + } else { + *dpp = dget(dentry); + } + + return NULL; +} +#endif /* !STRUCT_DENTRY_OPERATIONS_HAS_D_AUTOMOUNT */ + static struct inode_operations afs_file_iops = { #if defined(AFS_LINUX26_ENV) .permission = afs_linux_permission, @@ -1962,6 +2080,9 @@ static struct inode_operations afs_dir_iops = { .revalidate = afs_linux_revalidate, #endif .permission = afs_linux_permission, +#ifndef STRUCT_DENTRY_OPERATIONS_HAS_D_AUTOMOUNT + .follow_link = afs_linux_dir_follow_link, +#endif }; /* We really need a separate symlink set of ops, since do_follow_link() diff --git a/src/afs/afs.h b/src/afs/afs.h index c99e141ebd..5789f10c48 100644 --- a/src/afs/afs.h +++ b/src/afs/afs.h @@ -751,6 +751,13 @@ struct vcache { struct bhv_desc vc_bhv_desc; /* vnode's behavior data. */ #endif #endif /* AFS_SGI_ENV */ +#ifdef AFS_LINUX24_ENV + struct dentry *target_link; /* dentry we prefer, when we are redirecting + * all requests due to duplicate dentry aliases. + * See LINUX/osi_vnodeops.c. Note that this is + * NOT an actual reference to a dentry, so this + * pointer MUST NOT be dereferenced on its own. */ +#endif afs_int32 vc_error; /* stash write error for this vnode. */ int xlatordv; /* Used by nfs xlator */ struct AFS_UCRED *uncred;