linux: Fix leaked dentry reference in the revalidate op
[openafs.git] / src / afs / LINUX / osi_vnodeops.c
index 48e2614..ca7fe85 100644 (file)
@@ -97,8 +97,15 @@ afs_linux_VerifyVCache(struct vcache *avc, cred_t **retcred) {
 }
 
 #ifdef HAVE_LINUX_GENERIC_FILE_AIO_READ
+# ifdef LINUX_HAS_NONVECTOR_AIO
 static ssize_t
-afs_linux_aio_read(struct kiocb *iocb, const struct iovec *iov, unsigned long segs, loff_t pos)
+afs_linux_aio_read(struct kiocb *iocb, char __user *buf, size_t bufsize,
+                   loff_t pos)
+# else
+static ssize_t
+afs_linux_aio_read(struct kiocb *iocb, const struct iovec *buf,
+                   unsigned long bufsize, loff_t pos)
+# endif
 {
     struct file *fp = iocb->ki_filp;
     ssize_t code = 0;
@@ -106,8 +113,8 @@ afs_linux_aio_read(struct kiocb *iocb, const struct iovec *iov, unsigned long se
 
     AFS_GLOCK();
     afs_Trace4(afs_iclSetp, CM_TRACE_AIOREADOP, ICL_TYPE_POINTER, vcp,
-              ICL_TYPE_OFFSET, ICL_HANDLE_OFFSET(pos), ICL_TYPE_INT32, segs, ICL_TYPE_INT32,
-              99999);
+              ICL_TYPE_OFFSET, ICL_HANDLE_OFFSET(pos), ICL_TYPE_INT32,
+               (afs_int32)bufsize, ICL_TYPE_INT32, 99999);
     code = afs_linux_VerifyVCache(vcp, NULL);
 
     if (code == 0) {
@@ -115,13 +122,13 @@ afs_linux_aio_read(struct kiocb *iocb, const struct iovec *iov, unsigned long se
         * so we optimise by not using it */
        osi_FlushPages(vcp, NULL);      /* ensure stale pages are gone */
        AFS_GUNLOCK();
-       code = generic_file_aio_read(iocb, iov, segs, pos);
+       code = generic_file_aio_read(iocb, buf, bufsize, pos);
        AFS_GLOCK();
     }
 
     afs_Trace4(afs_iclSetp, CM_TRACE_AIOREADOP, ICL_TYPE_POINTER, vcp,
-              ICL_TYPE_OFFSET, ICL_HANDLE_OFFSET(pos), ICL_TYPE_INT32, segs, ICL_TYPE_INT32,
-              code);
+              ICL_TYPE_OFFSET, ICL_HANDLE_OFFSET(pos), ICL_TYPE_INT32,
+               (afs_int32)bufsize, ICL_TYPE_INT32, code);
     AFS_GUNLOCK();
     return code;
 }
@@ -161,8 +168,15 @@ afs_linux_read(struct file *fp, char *buf, size_t count, loff_t * offp)
  * mode. Call fake open/close to ensure we do writes of core dumps.
  */
 #ifdef HAVE_LINUX_GENERIC_FILE_AIO_READ
+# ifdef LINUX_HAS_NONVECTOR_AIO
 static ssize_t
-afs_linux_aio_write(struct kiocb *iocb, const struct iovec *iov, unsigned long segs, loff_t pos)
+afs_linux_aio_write(struct kiocb *iocb, const char __user *buf, size_t bufsize,
+                    loff_t pos)
+# else
+static ssize_t
+afs_linux_aio_write(struct kiocb *iocb, const struct iovec *buf,
+                    unsigned long bufsize, loff_t pos)
+# endif
 {
     ssize_t code = 0;
     struct vcache *vcp = VTOAFS(iocb->ki_filp->f_dentry->d_inode);
@@ -171,7 +185,8 @@ afs_linux_aio_write(struct kiocb *iocb, const struct iovec *iov, unsigned long s
     AFS_GLOCK();
 
     afs_Trace4(afs_iclSetp, CM_TRACE_AIOWRITEOP, ICL_TYPE_POINTER, vcp,
-              ICL_TYPE_OFFSET, ICL_HANDLE_OFFSET(pos), ICL_TYPE_INT32, segs, ICL_TYPE_INT32,
+              ICL_TYPE_OFFSET, ICL_HANDLE_OFFSET(pos), ICL_TYPE_INT32,
+               (afs_int32)bufsize, ICL_TYPE_INT32,
               (iocb->ki_filp->f_flags & O_APPEND) ? 99998 : 99999);
 
     code = afs_linux_VerifyVCache(vcp, &credp);
@@ -181,7 +196,7 @@ afs_linux_aio_write(struct kiocb *iocb, const struct iovec *iov, unsigned long s
     ReleaseWriteLock(&vcp->lock);
     if (code == 0) {
            AFS_GUNLOCK();
-           code = generic_file_aio_write(iocb, iov, segs, pos);
+           code = generic_file_aio_write(iocb, buf, bufsize, pos);
            AFS_GLOCK();
     }
 
@@ -194,8 +209,8 @@ afs_linux_aio_write(struct kiocb *iocb, const struct iovec *iov, unsigned long s
     ReleaseWriteLock(&vcp->lock);
 
     afs_Trace4(afs_iclSetp, CM_TRACE_AIOWRITEOP, ICL_TYPE_POINTER, vcp,
-              ICL_TYPE_OFFSET, ICL_HANDLE_OFFSET(pos), ICL_TYPE_INT32, segs, ICL_TYPE_INT32,
-              code);
+              ICL_TYPE_OFFSET, ICL_HANDLE_OFFSET(pos), ICL_TYPE_INT32,
+               (afs_int32)bufsize, ICL_TYPE_INT32, code);
 
     if (credp)
       crfree(credp);
@@ -556,6 +571,16 @@ afs_linux_lock(struct file *fp, int cmd, struct file_lock *flp)
 #endif /* F_GETLK64 && F_GETLK != F_GETLK64 */
 
     AFS_GLOCK();
+    if ((vcp->f.states & CRO)) {
+       if (flp->fl_type == F_WRLCK) {
+           code = EBADF;
+       } else {
+           code = 0;
+       }
+       AFS_GUNLOCK();
+       crfree(credp);
+       return code;
+    }
     code = afs_convert_code(afs_lockctl(vcp, &flock, cmd, credp));
     AFS_GUNLOCK();
 
@@ -779,7 +804,7 @@ canonical_dentry(struct inode *ip)
 {
     struct vcache *vcp = VTOAFS(ip);
     struct dentry *first = NULL, *ret = NULL, *cur;
-#if defined(D_ALIAS_IS_HLIST)
+#if defined(D_ALIAS_IS_HLIST) && !defined(HLIST_ITERATOR_NO_NODE)
     struct hlist_node *p;
 #endif
 
@@ -802,7 +827,11 @@ canonical_dentry(struct inode *ip)
 # endif
 
 #if defined(D_ALIAS_IS_HLIST)
+# if defined(HLIST_ITERATOR_NO_NODE)
+    hlist_for_each_entry(cur, &ip->i_dentry, d_alias) {
+# else
     hlist_for_each_entry(cur, p, &ip->i_dentry, d_alias) {
+# endif
 #else
     list_for_each_entry_reverse(cur, &ip->i_dentry, d_alias) {
 #endif
@@ -1045,7 +1074,9 @@ afs_linux_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *sta
  * The code here assumes that on entry the global lock is not held
  */
 static int
-#ifdef DOP_REVALIDATE_TAKES_NAMEIDATA
+#if defined(DOP_REVALIDATE_TAKES_UNSIGNED)
+afs_linux_dentry_revalidate(struct dentry *dp, unsigned int flags)
+#elif defined(DOP_REVALIDATE_TAKES_NAMEIDATA)
 afs_linux_dentry_revalidate(struct dentry *dp, struct nameidata *nd)
 #else
 afs_linux_dentry_revalidate(struct dentry *dp, int flags)
@@ -1061,7 +1092,11 @@ afs_linux_dentry_revalidate(struct dentry *dp, int flags)
 
 #ifdef LOOKUP_RCU
     /* We don't support RCU path walking */
+# if defined(DOP_REVALIDATE_TAKES_UNSIGNED)
+    if (flags & LOOKUP_RCU)
+# else
     if (nd->flags & LOOKUP_RCU)
+# endif
        return -ECHILD;
 #endif
 
@@ -1099,6 +1134,7 @@ afs_linux_dentry_revalidate(struct dentry *dp, int flags)
                    code = afs_EvalFakeStat(&vcp, &fakestate, &treq);
                if ((tryEvalOnly && vcp->mvstat == 1) || code) {
                    /* a mount point, not yet replaced by its directory */
+                   dput(parent);
                    goto bad_dentry;
                }
            }
@@ -1115,8 +1151,10 @@ afs_linux_dentry_revalidate(struct dentry *dp, int flags)
         * always require a crref() which would be "slow".
         */
        if (vcp->last_looker != treq.uid) {
-           if (!afs_AccessOK(vcp, (vType(vcp) == VREG) ? PRSFS_READ : PRSFS_LOOKUP, &treq, CHECK_MODE_BITS))
+           if (!afs_AccessOK(vcp, (vType(vcp) == VREG) ? PRSFS_READ : PRSFS_LOOKUP, &treq, CHECK_MODE_BITS)) {
+               dput(parent);
                goto bad_dentry;
+           }
 
            vcp->last_looker = treq.uid;
        }
@@ -1232,10 +1270,14 @@ afs_dentry_delete(struct dentry *dp)
 
 #ifdef STRUCT_DENTRY_OPERATIONS_HAS_D_AUTOMOUNT
 static struct vfsmount *
-afs_dentry_automount(struct path *path)
+afs_dentry_automount(afs_linux_path_t *path)
 {
     struct dentry *target;
 
+    /* avoid symlink resolution limits when resolving; we cannot contribute to
+     * an infinite symlink loop */
+    current->total_link_count--;
+
     target = canonical_dentry(path->dentry->d_inode);
 
     if (target == path->dentry) {
@@ -1279,17 +1321,18 @@ struct dentry_operations afs_dentry_operations = {
  * name is in kernel space at this point.
  */
 static int
-#if defined(IOP_MKDIR_TAKES_UMODE_T)
+#if defined(IOP_CREATE_TAKES_BOOL)
+afs_linux_create(struct inode *dip, struct dentry *dp, umode_t mode,
+                bool excl)
+#elif defined(IOP_CREATE_TAKES_UMODE_T)
 afs_linux_create(struct inode *dip, struct dentry *dp, umode_t mode,
                 struct nameidata *nd)
-#else
-#ifdef IOP_CREATE_TAKES_NAMEIDATA
+#elif defined(IOP_CREATE_TAKES_NAMEIDATA)
 afs_linux_create(struct inode *dip, struct dentry *dp, int mode,
                 struct nameidata *nd)
 #else
 afs_linux_create(struct inode *dip, struct dentry *dp, int mode)
 #endif
-#endif
 {
     struct vattr vattr;
     cred_t *credp = crref();
@@ -1325,7 +1368,10 @@ afs_linux_create(struct inode *dip, struct dentry *dp, int mode)
 
 /* afs_linux_lookup */
 static struct dentry *
-#ifdef IOP_LOOKUP_TAKES_NAMEIDATA
+#if defined(IOP_LOOKUP_TAKES_UNSIGNED)
+afs_linux_lookup(struct inode *dip, struct dentry *dp,
+                unsigned flags)
+#elif defined(IOP_LOOKUP_TAKES_NAMEIDATA)
 afs_linux_lookup(struct inode *dip, struct dentry *dp,
                 struct nameidata *nd)
 #else
@@ -1370,24 +1416,7 @@ afs_linux_lookup(struct inode *dip, struct dentry *dp)
     AFS_GUNLOCK();
 
     if (ip && S_ISDIR(ip->i_mode)) {
-       int retry = 1;
-       struct dentry *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 && !(alias->d_flags & DCACHE_DISCONNECTED)) {
-               if (d_invalidate(alias) == 0) {
-                   /* there may be more aliases; try again until we run out */
-                   retry = 1;
-               }
-           }
-
-           dput(alias);
-       }
+       d_prune_aliases(ip);
 
 #ifdef STRUCT_DENTRY_OPERATIONS_HAS_D_AUTOMOUNT
        ip->i_flags |= S_AUTOMOUNT;
@@ -2090,7 +2119,7 @@ afs_linux_bypass_readpages(struct file *fp, struct address_space *mapping,
 
        if(page_ix == 0) {
            offset = page_offset(pp);
-           auio->uio_offset = offset;
+           ancr->offset = auio->uio_offset = offset;
            base_index = pp->index;
        }
         iovecp[page_ix].iov_len = PAGE_SIZE;
@@ -2202,13 +2231,14 @@ afs_linux_bypass_readpage(struct file *fp, struct page *pp)
 
 static inline int
 afs_linux_can_bypass(struct inode *ip) {
+
     switch(cache_bypass_strategy) {
        case NEVER_BYPASS_CACHE:
            return 0;
        case ALWAYS_BYPASS_CACHE:
            return 1;
        case LARGE_FILES_BYPASS_CACHE:
-           if(i_size_read(ip) > cache_bypass_threshold)
+           if (i_size_read(ip) > cache_bypass_threshold)
                return 1;
        default:
            return 0;
@@ -2696,6 +2726,17 @@ afs_linux_dir_follow_link(struct dentry *dentry, struct nameidata *nd)
     struct dentry **dpp;
     struct dentry *target;
 
+    if (current->total_link_count > 0) {
+       /* avoid symlink resolution limits when resolving; we cannot contribute to
+        * an infinite symlink loop */
+       /* only do this for follow_link when total_link_count is positive to be
+        * on the safe side; there is at least one code path in the Linux
+        * kernel where it seems like it may be possible to get here without
+        * total_link_count getting incremented. it is not clear on how that
+        * path is actually reached, but guard against it just to be safe */
+       current->total_link_count--;
+    }
+
     target = canonical_dentry(dentry->d_inode);
 
 # ifdef STRUCT_NAMEIDATA_HAS_PATH
@@ -2712,6 +2753,8 @@ afs_linux_dir_follow_link(struct dentry *dentry, struct nameidata *nd)
        *dpp = dget(dentry);
     }
 
+    nd->last_type = LAST_BIND;
+
     return NULL;
 }
 #endif /* !STRUCT_DENTRY_OPERATIONS_HAS_D_AUTOMOUNT */