LINUX: Dir follow_link should set LAST_BIND
[openafs.git] / src / afs / LINUX24 / osi_vnodeops.c
index f86b0d2..f307990 100644 (file)
@@ -47,7 +47,6 @@
 #endif
 
 extern struct vcache *afs_globalVp;
-extern int afs_notify_change(struct dentry *dp, struct iattr *iattrp);
 #if defined(AFS_LINUX24_ENV)
 /* Some uses of BKL are perhaps not needed for bypass or memcache--
  * why don't we try it out? */
@@ -224,6 +223,7 @@ afs_linux_readdir(struct file *fp, void *dirbuf, filldir_t filldir)
     int offset;
     int dirpos;
     struct DirEntry *de;
+    struct DirBuffer entry;
     ino_t ino;
     int len;
     afs_size_t origOffset, tlen;
@@ -305,14 +305,15 @@ afs_linux_readdir(struct file *fp, void *dirbuf, filldir_t filldir)
            break;
        de = (struct DirEntry *)entry.data;
 
-       ino = afs_calc_inum (avc->f.fid.Fid.Volume, ntohl(de->fid.vnode));
+       ino = afs_calc_inum(avc->f.fid.Cell, avc->f.fid.Fid.Volume,
+                           ntohl(de->fid.vnode));
 
        if (de->name)
            len = strlen(de->name);
        else {
            printf("afs_linux_readdir: afs_dir_GetBlob failed, null name (inode %lx, dirpos %d)\n", 
                   (unsigned long)&tdc->f.inode, dirpos);
-           DRelease(de, 0);
+           DRelease(&entry, 0);
            ReleaseSharedLock(&avc->lock);
            afs_PutDCache(tdc);
            code = -ENOENT;
@@ -363,7 +364,7 @@ afs_linux_readdir(struct file *fp, void *dirbuf, filldir_t filldir)
 #else
        code = (*filldir) (dirbuf, de->name, len, offset, ino);
 #endif
-       DRelease(de, 0);
+       DRelease(&entry, 0);
        if (code)
            break;
        offset = dirpos + 1 + ((len + 16) >> 5);
@@ -782,6 +783,57 @@ struct file_operations afs_file_fops = {
 #endif
 };
 
+static struct dentry *
+canonical_dentry(struct inode *ip)
+{
+    struct vcache *vcp = VTOAFS(ip);
+    struct dentry *first = NULL, *ret = NULL, *cur;
+    struct list_head *head, *prev, *tmp;
+
+    /* 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);
+
+    spin_lock(&dcache_lock);
+
+    head = &ip->i_dentry;
+    prev = ip->i_dentry.prev;
+
+    while (prev != head) {
+       tmp = prev;
+       prev = tmp->prev;
+       cur = list_entry(tmp, struct 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;
+
+    if (ret) {
+       dget_locked(ret);
+    }
+    spin_unlock(&dcache_lock);
+
+    return ret;
+}
 
 /**********************************************************************
  * AFS Linux dentry operations
@@ -887,6 +939,86 @@ afs_linux_revalidate(struct dentry *dp)
     return afs_convert_code(code);
 }
 
+/* vattr_setattr
+ * Set iattr data into vattr. Assume vattr cleared before call.
+ */
+static void
+iattr2vattr(struct vattr *vattrp, struct iattr *iattrp)
+{
+    vattrp->va_mask = iattrp->ia_valid;
+    if (iattrp->ia_valid & ATTR_MODE)
+       vattrp->va_mode = iattrp->ia_mode;
+    if (iattrp->ia_valid & ATTR_UID)
+       vattrp->va_uid = iattrp->ia_uid;
+    if (iattrp->ia_valid & ATTR_GID)
+       vattrp->va_gid = iattrp->ia_gid;
+    if (iattrp->ia_valid & ATTR_SIZE)
+       vattrp->va_size = iattrp->ia_size;
+    if (iattrp->ia_valid & ATTR_ATIME) {
+       vattrp->va_atime.tv_sec = iattrp->ia_atime;
+       vattrp->va_atime.tv_usec = 0;
+    }
+    if (iattrp->ia_valid & ATTR_MTIME) {
+       vattrp->va_mtime.tv_sec = iattrp->ia_mtime;
+       vattrp->va_mtime.tv_usec = 0;
+    }
+    if (iattrp->ia_valid & ATTR_CTIME) {
+       vattrp->va_ctime.tv_sec = iattrp->ia_ctime;
+       vattrp->va_ctime.tv_usec = 0;
+    }
+}
+
+/* vattr2inode
+ * Rewrite the inode cache from the attr. Assumes all vattr fields are valid.
+ */
+void
+vattr2inode(struct inode *ip, struct vattr *vp)
+{
+    ip->i_ino = vp->va_nodeid;
+    ip->i_nlink = vp->va_nlink;
+    ip->i_blocks = vp->va_blocks;
+#ifdef STRUCT_INODE_HAS_I_BLKBITS
+    ip->i_blkbits = AFS_BLKBITS;
+#endif
+#ifdef STRUCT_INODE_HAS_I_BLKSIZE
+    ip->i_blksize = vp->va_blocksize;
+#endif
+    ip->i_rdev = vp->va_rdev;
+    ip->i_mode = vp->va_mode;
+    ip->i_uid = vp->va_uid;
+    ip->i_gid = vp->va_gid;
+    i_size_write(ip, vp->va_size);
+    ip->i_atime = vp->va_atime.tv_sec;
+    ip->i_mtime = vp->va_mtime.tv_sec;
+    ip->i_ctime = vp->va_ctime.tv_sec;
+}
+
+/* afs_notify_change
+ * Linux version of setattr call. What to change is in the iattr struct.
+ * We need to set bits in both the Linux inode as well as the vcache.
+ */
+int
+afs_notify_change(struct dentry *dp, struct iattr *iattrp)
+{
+    struct vattr vattr;
+    cred_t *credp = crref();
+    struct inode *ip = dp->d_inode;
+    int code;
+
+    VATTR_NULL(&vattr);
+    iattr2vattr(&vattr, iattrp);       /* Convert for AFS vnodeops call. */
+
+    AFS_GLOCK();
+    code = afs_setattr(VTOAFS(ip), &vattr, credp);
+    if (!code) {
+       afs_getattr(VTOAFS(ip), &vattr, credp);
+       vattr2inode(ip, &vattr);
+    }
+    AFS_GUNLOCK();
+    crfree(credp);
+    return afs_convert_code(code);
+}
+
 /* Validate a dentry. Return 1 if unchanged, 0 if VFS layer should re-evaluate.
  * In kernels 2.2.10 and above, we are passed an additional flags var which
  * may have either the LOOKUP_FOLLOW OR LOOKUP_DIRECTORY set in which case
@@ -1141,6 +1273,18 @@ afs_linux_lookup(struct inode *dip, struct dentry *dp)
     
     if (vcp) {
        struct vattr vattr;
+       struct vcache *parent_vc = VTOAFS(dip);
+
+       if (parent_vc == vcp) {
+           /* This is possible if the parent dir is a mountpoint to a volume,
+            * and the dir entry we looked up is a mountpoint to the same
+            * volume. Linux cannot cope with this, so return an error instead
+            * of risking a deadlock or panic. */
+           afs_PutVCache(vcp);
+           code = EDEADLK;
+           AFS_GUNLOCK();
+           goto done;
+       }
 
        ip = AFSTOV(vcp);
        afs_getattr(vcp, &vattr, credp);
@@ -1160,23 +1304,29 @@ 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;
 
-        /* Try to invalidate an existing alias in favor of our new one */
-       alias = d_find_alias(ip);
-       if (alias) {
-           if (d_invalidate(alias) == 0) {
-               dput(alias);
-           } else {
-               iput(ip);
-               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);
+           /* But not if it's disconnected; then we want d_splice_alias below */
+           if (alias) {
+               if (d_invalidate(alias) == 0) {
+                   /* there may be more aliases; try again until we run out */
+                   retry = 1;
+               }
            }
+
+           dput(alias);
        }
     }
 #endif
     d_add(dp, ip);
 
+ done:
     crfree(credp);
 
     /* It's ok for the file to not be found. That's noted by the caller by
@@ -1917,6 +2067,28 @@ afs_linux_write_begin(struct file *file, struct address_space *mapping,
 }
 #endif
 
+static int
+afs_linux_dir_follow_link(struct dentry *dentry, struct nameidata *nd)
+{
+    struct dentry **dpp;
+    struct dentry *target;
+
+    target = canonical_dentry(dentry->d_inode);
+
+    dpp = &nd->dentry;
+
+    dput(*dpp);
+
+    if (target) {
+       *dpp = target;
+    } else {
+       *dpp = dget(dentry);
+    }
+
+    nd->last_type = LAST_BIND;
+
+    return 0;
+}
 
 static struct inode_operations afs_file_iops = {
 #if defined(AFS_LINUX24_ENV)
@@ -1966,6 +2138,7 @@ static struct inode_operations afs_dir_iops = {
   .rename =            afs_linux_rename,
   .revalidate =                afs_linux_revalidate,
   .permission =                afs_linux_permission,
+  .follow_link =       afs_linux_dir_follow_link,
 };
 
 /* We really need a separate symlink set of ops, since do_follow_link()