venus: Remove dedebug
[openafs.git] / src / afs / LINUX / osi_vnodeops.c
index ee8b414..511b083 100644 (file)
 #include "afsincludes.h"
 #include "afs/afs_stats.h"
 #include <linux/mm.h>
+#include <linux/buffer_head.h>
 #ifdef HAVE_MM_INLINE_H
 #include <linux/mm_inline.h>
 #endif
 #include <linux/pagemap.h>
 #include <linux/writeback.h>
-#include <linux/pagevec.h>
+#if defined(HAVE_LINUX_FOLIO_ADD_LRU) || defined(HAVE_LINUX_LRU_CACHE_ADD_FILE)
+# include <linux/swap.h>
+#else
+# include <linux/pagevec.h>
+#endif
 #include <linux/aio.h>
 #include "afs/lock.h"
 #include "afs/afs_bypasscache.h"
 #include "osi_compat.h"
 #include "osi_pagecopy.h"
 
-#ifndef HAVE_LINUX_PAGEVEC_LRU_ADD_FILE
-#define __pagevec_lru_add_file __pagevec_lru_add
-#endif
-
 #ifndef MAX_ERRNO
 #define MAX_ERRNO 1000L
 #endif
 # define D_SPLICE_ALIAS_RACE
 #endif
 
+#if defined(STRUCT_FILE_OPERATIONS_HAS_ITERATE_SHARED)
+# define USE_FOP_ITERATE 1
+#elif defined(STRUCT_FILE_OPERATIONS_HAS_ITERATE) && !defined(FMODE_KABI_ITERATE)
 /* Workaround for RH 7.5 which introduced file operation iterate() but requires
  * each file->f_mode to be marked with FMODE_KABI_ITERATE.  Instead OpenAFS will
  * continue to use file opearation readdir() in this case.
  */
-#if defined(STRUCT_FILE_OPERATIONS_HAS_ITERATE) && !defined(FMODE_KABI_ITERATE)
-#define USE_FOP_ITERATE 1
+# define USE_FOP_ITERATE 1
 #else
-#undef USE_FOP_ITERATE
+# undef USE_FOP_ITERATE
+#endif
+
+/* Kernels from before 2.6.19 may not be able to return errors from
+ * d_revalidate. */
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,19)
+# define ERRORS_FROM_D_REVALIDATE
 #endif
 
 int cachefs_noreadpage = 0;
@@ -69,6 +78,99 @@ extern struct backing_dev_info *afs_backing_dev_info;
 
 extern struct vcache *afs_globalVp;
 
+/* Handle interfacing with Linux's pagevec/lru facilities */
+
+#if defined(HAVE_LINUX_FOLIO_ADD_LRU) || \
+    defined(HAVE_LINUX_LRU_CACHE_ADD_FILE) || defined(HAVE_LINUX_LRU_CACHE_ADD)
+
+/*
+ * Linux's lru_cache_add_file provides a simplified LRU interface without
+ * needing a pagevec
+ */
+struct afs_lru_pages {
+    char unused;
+};
+
+static inline void
+afs_lru_cache_init(struct afs_lru_pages *alrupages)
+{
+    return;
+}
+
+static inline void
+afs_lru_cache_add(struct afs_lru_pages *alrupages, struct page *page)
+{
+# if defined(HAVE_LINUX_FOLIO_ADD_LRU)
+    struct folio *folio = page_folio(page);
+    folio_add_lru(folio);
+# elif defined(HAVE_LINUX_LRU_CACHE_ADD)
+    lru_cache_add(page);
+# elif defined(HAVE_LINUX_LRU_CACHE_ADD_FILE)
+    lru_cache_add_file(page);
+# else
+#  error need a kernel function to add a page to the kernel lru cache
+# endif
+}
+
+static inline void
+afs_lru_cache_finalize(struct afs_lru_pages *alrupages)
+{
+    return;
+}
+#else
+
+/* Linux's pagevec/lru interfaces require a pagevec */
+struct afs_lru_pages {
+    struct pagevec lrupv;
+};
+
+static inline void
+afs_lru_cache_init(struct afs_lru_pages *alrupages)
+{
+# if defined(PAGEVEC_INIT_COLD_ARG)
+    pagevec_init(&alrupages->lrupv, 0);
+# else
+    pagevec_init(&alrupages->lrupv);
+# endif
+}
+
+# ifndef HAVE_LINUX_PAGEVEC_LRU_ADD_FILE
+#  define __pagevec_lru_add_file __pagevec_lru_add
+# endif
+
+static inline void
+afs_lru_cache_add(struct afs_lru_pages *alrupages, struct page *page)
+{
+    get_page(page);
+    if (!pagevec_add(&alrupages->lrupv, page))
+       __pagevec_lru_add_file(&alrupages->lrupv);
+}
+
+static inline void
+afs_lru_cache_finalize(struct afs_lru_pages *alrupages)
+{
+    if (pagevec_count(&alrupages->lrupv))
+       __pagevec_lru_add_file(&alrupages->lrupv);
+}
+#endif /* !HAVE_LINUX_LRU_ADD_FILE */
+
+static inline int
+afs_add_to_page_cache_lru(struct afs_lru_pages *alrupages, struct page *page,
+                         struct address_space *mapping,
+                         pgoff_t index, gfp_t gfp)
+{
+#if defined(HAVE_LINUX_ADD_TO_PAGE_CACHE_LRU)
+    return add_to_page_cache_lru(page, mapping, index, gfp);
+#else
+    int code;
+    code = add_to_page_cache(page, mapping, index, gfp);
+    if (code == 0) {
+       afs_lru_cache_add(alrupages, page);
+    }
+    return code;
+#endif
+}
+
 /* This function converts a positive error code from AFS into a negative
  * code suitable for passing into the Linux VFS layer. It checks that the
  * error code is within the permissable bounds for the ERR_PTR mechanism.
@@ -105,7 +207,7 @@ afs_linux_VerifyVCache(struct vcache *avc, cred_t **retcred) {
 
     code = afs_CreateReq(&treq, credp);
     if (code == 0) {
-        code = afs_VerifyVCache2(avc, treq);
+       code = afs_VerifyVCache(avc, treq);
        afs_DestroyReq(treq);
     }
 
@@ -305,8 +407,6 @@ afs_linux_write(struct file *fp, const char *buf, size_t count, loff_t * offp)
 }
 #endif
 
-extern int BlobScan(struct dcache * afile, afs_int32 ablob, afs_int32 *ablobOut);
-
 /* This is a complete rewrite of afs_readdir, since we can make use of
  * filldir instead of afs_readdir_move. Note that changes to vcache/dcache
  * handling and use of bulkstats will need to be reflected here as well.
@@ -324,7 +424,7 @@ afs_linux_readdir(struct file *fp, void *dirbuf, filldir_t filldir)
     int code;
     int offset;
     afs_int32 dirpos;
-    struct DirEntry *de;
+    struct DirEntryFlex *de;
     struct DirBuffer entry;
     ino_t ino;
     int len;
@@ -347,7 +447,7 @@ afs_linux_readdir(struct file *fp, void *dirbuf, filldir_t filldir)
 
     /* update the cache entry */
   tagain:
-    code = afs_convert_code(afs_VerifyVCache2(avc, treq));
+    code = afs_convert_code(afs_VerifyVCache(avc, treq));
     if (code)
        goto out;
 
@@ -368,7 +468,7 @@ afs_linux_readdir(struct file *fp, void *dirbuf, filldir_t filldir)
      */
     while ((avc->f.states & CStatd)
           && (tdc->dflags & DFFetching)
-          && hsame(avc->f.m.DataVersion, tdc->f.versionNo)) {
+          && afs_IsDCacheFresh(tdc, avc)) {
        ReleaseReadLock(&tdc->lock);
        ReleaseWriteLock(&avc->lock);
        afs_osi_Sleep(&tdc->validPos);
@@ -376,7 +476,7 @@ afs_linux_readdir(struct file *fp, void *dirbuf, filldir_t filldir)
        ObtainReadLock(&tdc->lock);
     }
     if (!(avc->f.states & CStatd)
-       || !hsame(avc->f.m.DataVersion, tdc->f.versionNo)) {
+       || !afs_IsDCacheFresh(tdc, avc)) {
        ReleaseReadLock(&tdc->lock);
        ReleaseWriteLock(&avc->lock);
        afs_PutDCache(tdc);
@@ -429,7 +529,7 @@ afs_linux_readdir(struct file *fp, void *dirbuf, filldir_t filldir)
            goto unlock_out;
         }
 
-       de = (struct DirEntry *)entry.data;
+       de = entry.data;
        ino = afs_calc_inum (avc->f.fid.Cell, avc->f.fid.Fid.Volume,
                             ntohl(de->fid.vnode));
        len = strlen(de->name);
@@ -446,7 +546,7 @@ afs_linux_readdir(struct file *fp, void *dirbuf, filldir_t filldir)
            afid.Fid.Unique = ntohl(de->fid.vunique);
            if ((avc->f.states & CForeign) == 0 && (ntohl(de->fid.vnode) & 1)) {
                type = DT_DIR;
-           } else if ((tvc = afs_FindVCache(&afid, 0, 0))) {
+           } else if ((tvc = afs_FindVCache(&afid, 0))) {
                if (tvc->mvstat != AFS_MVSTAT_FILE) {
                    type = DT_DIR;
                } else if (((tvc->f.states) & (CStatd | CTruth))) {
@@ -513,17 +613,11 @@ out1:
 }
 
 
-/* in afs_pioctl.c */
-extern int afs_xioctl(struct inode *ip, struct file *fp, unsigned int com,
-                     unsigned long arg);
-
-#if defined(HAVE_UNLOCKED_IOCTL) || defined(HAVE_COMPAT_IOCTL)
 static long afs_unlocked_xioctl(struct file *fp, unsigned int com,
                                unsigned long arg) {
     return afs_xioctl(FILE_INODE(fp), fp, com, arg);
 
 }
-#endif
 
 
 static int
@@ -533,9 +627,9 @@ afs_linux_mmap(struct file *fp, struct vm_area_struct *vmap)
     int code;
 
     AFS_GLOCK();
-    afs_Trace3(afs_iclSetp, CM_TRACE_GMAP, ICL_TYPE_POINTER, vcp,
-              ICL_TYPE_POINTER, vmap->vm_start, ICL_TYPE_INT32,
-              vmap->vm_end - vmap->vm_start);
+    afs_Trace4(afs_iclSetp, CM_TRACE_GMAP, ICL_TYPE_POINTER, vcp,
+              ICL_TYPE_POINTER, vmap->vm_start, ICL_TYPE_LONG,
+              vmap->vm_end - vmap->vm_start, ICL_TYPE_LONG, 0);
 
     /* get a validated vcache entry */
     code = afs_linux_VerifyVCache(vcp, NULL);
@@ -618,6 +712,61 @@ afs_linux_fsync(struct file *fp, int datasync)
 
 }
 
+/* Handle getting/setting file_lock type */
+
+static inline unsigned char
+afs_get_flock_type(struct file_lock *flp)
+{
+#if defined(HAVE_FILE_LOCK_CORE)
+    return flp->c.flc_type;
+#else
+    return flp->fl_type;
+#endif
+}
+
+static inline void
+afs_set_flock_type(struct file_lock *flp, unsigned char type)
+{
+#if defined(HAVE_FILE_LOCK_CORE)
+    flp->c.flc_type = type;
+#else
+    flp->fl_type = type;
+#endif
+}
+
+/* Handle getting/setting file_lock pid */
+
+static inline pid_t
+afs_get_flock_pid(struct file_lock *flp)
+{
+#if defined(HAVE_FILE_LOCK_CORE)
+    return flp->c.flc_pid;
+#else
+    return flp->fl_pid;
+#endif
+}
+
+static inline void
+afs_set_flock_pid(struct file_lock *flp, pid_t pid)
+{
+#if defined(HAVE_FILE_LOCK_CORE)
+    flp->c.flc_pid = pid;
+#else
+    flp->fl_pid = pid;
+#endif
+}
+
+/* Handle clearing file_lock sleep */
+
+static inline void
+afs_clear_flock_sleep(struct file_lock *flp)
+{
+#if defined(HAVE_FILE_LOCK_CORE)
+       flp->c.flc_flags &= ~FL_SLEEP;
+#else
+       flp->fl_flags &= ~FL_SLEEP;
+#endif
+}
 
 static int
 afs_linux_lock(struct file *fp, int cmd, struct file_lock *flp)
@@ -629,8 +778,8 @@ afs_linux_lock(struct file *fp, int cmd, struct file_lock *flp)
 
     /* Convert to a lock format afs_lockctl understands. */
     memset(&flock, 0, sizeof(flock));
-    flock.l_type = flp->fl_type;
-    flock.l_pid = flp->fl_pid;
+    flock.l_type = afs_get_flock_type(flp);
+    flock.l_pid = afs_get_flock_pid(flp);
     flock.l_whence = 0;
     flock.l_start = flp->fl_start;
     if (flp->fl_end == OFFSET_MAX)
@@ -652,10 +801,10 @@ afs_linux_lock(struct file *fp, int cmd, struct file_lock *flp)
     code = afs_convert_code(afs_lockctl(vcp, &flock, cmd, credp));
     AFS_GUNLOCK();
 
-    if ((code == 0 || flp->fl_type == F_UNLCK) &&
-        (cmd == F_SETLK || cmd == F_SETLKW)) {
+    if ((code == 0 || afs_get_flock_type(flp) == F_UNLCK) &&
+       (cmd == F_SETLK || cmd == F_SETLKW)) {
        code = afs_posix_lock_file(fp, flp);
-       if (code && flp->fl_type != F_UNLCK) {
+       if (code && afs_get_flock_type(flp) != F_UNLCK) {
            struct AFS_FLOCK flock2;
            flock2 = flock;
            flock2.l_type = F_UNLCK;
@@ -668,17 +817,17 @@ afs_linux_lock(struct file *fp, int cmd, struct file_lock *flp)
      * kernel, as lockctl knows nothing about byte range locks
      */
     if (code == 0 && cmd == F_GETLK && flock.l_type == F_UNLCK) {
-        afs_posix_test_lock(fp, flp);
-        /* If we found a lock in the kernel's structure, return it */
-        if (flp->fl_type != F_UNLCK) {
-            crfree(credp);
-            return 0;
-        }
+       afs_posix_test_lock(fp, flp);
+       /* If we found a lock in the kernel's structure, return it */
+       if (afs_get_flock_type(flp) != F_UNLCK) {
+           crfree(credp);
+           return 0;
+       }
     }
 
     /* Convert flock back to Linux's file_lock */
-    flp->fl_type = flock.l_type;
-    flp->fl_pid = flock.l_pid;
+    afs_set_flock_type(flp, flock.l_type);
+    afs_set_flock_pid(flp, flock.l_pid);
     flp->fl_start = flock.l_start;
     if (flock.l_len == 0)
        flp->fl_end = OFFSET_MAX; /* Lock to end of file */
@@ -698,8 +847,8 @@ afs_linux_flock(struct file *fp, int cmd, struct file_lock *flp) {
     struct AFS_FLOCK flock;
     /* Convert to a lock format afs_lockctl understands. */
     memset(&flock, 0, sizeof(flock));
-    flock.l_type = flp->fl_type;
-    flock.l_pid = flp->fl_pid;
+    flock.l_type = afs_get_flock_type(flp);
+    flock.l_pid = afs_get_flock_pid(flp);
     flock.l_whence = 0;
     flock.l_start = 0;
     flock.l_len = 0;
@@ -718,11 +867,11 @@ afs_linux_flock(struct file *fp, int cmd, struct file_lock *flp) {
     code = afs_convert_code(afs_lockctl(vcp, &flock, cmd, credp));
     AFS_GUNLOCK();
 
-    if ((code == 0 || flp->fl_type == F_UNLCK) &&
-        (cmd == F_SETLK || cmd == F_SETLKW)) {
-       flp->fl_flags &=~ FL_SLEEP;
+    if ((code == 0 || afs_get_flock_type(flp) == F_UNLCK) &&
+       (cmd == F_SETLK || cmd == F_SETLKW)) {
+       afs_clear_flock_sleep(flp);
        code = flock_lock_file_wait(fp, flp);
-       if (code && flp->fl_type != F_UNLCK) {
+       if (code && afs_get_flock_type(flp) != F_UNLCK) {
            struct AFS_FLOCK flock2;
            flock2 = flock;
            flock2.l_type = F_UNLCK;
@@ -732,8 +881,8 @@ afs_linux_flock(struct file *fp, int cmd, struct file_lock *flp) {
        }
     }
     /* Convert flock back to Linux's file_lock */
-    flp->fl_type = flock.l_type;
-    flp->fl_pid = flock.l_pid;
+    afs_set_flock_type(flp, flock.l_type);
+    afs_set_flock_pid(flp, flock.l_pid);
 
     crfree(credp);
     return code;
@@ -814,19 +963,15 @@ out:
 
 struct file_operations afs_dir_fops = {
   .read =      generic_read_dir,
-#if defined(USE_FOP_ITERATE)
+#if defined(STRUCT_FILE_OPERATIONS_HAS_ITERATE_SHARED)
+  .iterate_shared = afs_linux_readdir,
+#elif defined(USE_FOP_ITERATE)
   .iterate =   afs_linux_readdir,
 #else
   .readdir =   afs_linux_readdir,
 #endif
-#ifdef HAVE_UNLOCKED_IOCTL
   .unlocked_ioctl = afs_unlocked_xioctl,
-#else
-  .ioctl =     afs_xioctl,
-#endif
-#ifdef HAVE_COMPAT_IOCTL
   .compat_ioctl = afs_unlocked_xioctl,
-#endif
   .open =      afs_linux_open,
   .release =   afs_linux_release,
   .llseek =    default_llseek,
@@ -854,14 +999,8 @@ struct file_operations afs_file_fops = {
   .read =      afs_linux_read,
   .write =     afs_linux_write,
 #endif
-#ifdef HAVE_UNLOCKED_IOCTL
   .unlocked_ioctl = afs_unlocked_xioctl,
-#else
-  .ioctl =     afs_xioctl,
-#endif
-#ifdef HAVE_COMPAT_IOCTL
   .compat_ioctl = afs_unlocked_xioctl,
-#endif
   .mmap =      afs_linux_mmap,
   .open =      afs_linux_open,
   .flush =     afs_linux_flush,
@@ -874,7 +1013,11 @@ struct file_operations afs_file_fops = {
 # else
   .splice_write = generic_file_splice_write,
 # endif
+# if LINUX_VERSION_CODE >= KERNEL_VERSION(6,5,0)
+  .splice_read = filemap_splice_read,
+# else
   .splice_read = generic_file_splice_read,
+# endif
 #endif
   .release =   afs_linux_release,
   .fsync =     afs_linux_fsync,
@@ -908,16 +1051,7 @@ canonical_dentry(struct inode *ip)
 
     afs_d_alias_lock(ip);
 
-#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
-
+    afs_d_alias_foreach_reverse(cur, ip, p) {
        if (!vcp->target_link || cur == vcp->target_link) {
            ret = cur;
            break;
@@ -1008,15 +1142,15 @@ iattr2vattr(struct vattr *vattrp, struct iattr *iattrp)
        vattrp->va_size = iattrp->ia_size;
     if (iattrp->ia_valid & ATTR_ATIME) {
        vattrp->va_atime.tv_sec = iattrp->ia_atime.tv_sec;
-       vattrp->va_atime.tv_usec = 0;
+       vattrp->va_atime.tv_nsec = 0;
     }
     if (iattrp->ia_valid & ATTR_MTIME) {
        vattrp->va_mtime.tv_sec = iattrp->ia_mtime.tv_sec;
-       vattrp->va_mtime.tv_usec = 0;
+       vattrp->va_mtime.tv_nsec = 0;
     }
     if (iattrp->ia_valid & ATTR_CTIME) {
        vattrp->va_ctime.tv_sec = iattrp->ia_ctime.tv_sec;
-       vattrp->va_ctime.tv_usec = 0;
+       vattrp->va_ctime.tv_nsec = 0;
     }
 }
 
@@ -1044,24 +1178,29 @@ vattr2inode(struct inode *ip, struct vattr *vp)
     ip->i_uid = afs_make_kuid(vp->va_uid);
     ip->i_gid = afs_make_kgid(vp->va_gid);
     i_size_write(ip, vp->va_size);
-    ip->i_atime.tv_sec = vp->va_atime.tv_sec;
-    ip->i_atime.tv_nsec = 0;
-    ip->i_mtime.tv_sec = vp->va_mtime.tv_sec;
+    afs_inode_set_atime(ip, vp->va_atime.tv_sec, 0);
     /* Set the mtime nanoseconds to the sysname generation number.
      * This convinces NFS clients that all directories have changed
      * any time the sysname list changes.
      */
-    ip->i_mtime.tv_nsec = afs_sysnamegen;
-    ip->i_ctime.tv_sec = vp->va_ctime.tv_sec;
-    ip->i_ctime.tv_nsec = 0;
+    afs_inode_set_mtime(ip, vp->va_mtime.tv_sec, afs_sysnamegen);
+    afs_inode_set_ctime(ip, vp->va_ctime.tv_sec, 0);
 }
 
 /* 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.
  */
+#if defined(IOP_TAKES_MNT_IDMAP)
+static int
+afs_notify_change(struct mnt_idmap *idmap, struct dentry *dp, struct iattr *iattrp)
+#elif defined(IOP_TAKES_USER_NAMESPACE)
+static int
+afs_notify_change(struct user_namespace *mnt_userns, struct dentry *dp, struct iattr *iattrp)
+#else
 static int
 afs_notify_change(struct dentry *dp, struct iattr *iattrp)
+#endif
 {
     struct vattr *vattr = NULL;
     cred_t *credp = crref();
@@ -1089,7 +1228,33 @@ out:
     return afs_convert_code(code);
 }
 
-#if defined(IOP_GETATTR_TAKES_PATH_STRUCT)
+#if defined(IOP_TAKES_MNT_IDMAP)
+static int
+afs_linux_getattr(struct mnt_idmap *idmap, const struct path *path, struct kstat *stat,
+                 u32 request_mask, unsigned int sync_mode)
+{
+       int err = afs_linux_revalidate(path->dentry);
+       if (!err) {
+# if defined(GENERIC_FILLATTR_TAKES_REQUEST_MASK)
+               generic_fillattr(afs_mnt_idmap, request_mask, path->dentry->d_inode, stat);
+# else
+               generic_fillattr(afs_mnt_idmap, path->dentry->d_inode, stat);
+# endif
+       }
+       return err;
+}
+#elif defined(IOP_TAKES_USER_NAMESPACE)
+static int
+afs_linux_getattr(struct user_namespace *mnt_userns, const struct path *path, struct kstat *stat,
+                 u32 request_mask, unsigned int sync_mode)
+{
+       int err = afs_linux_revalidate(path->dentry);
+       if (!err) {
+               generic_fillattr(afs_ns, path->dentry->d_inode, stat);
+       }
+       return err;
+}
+#elif defined(IOP_GETATTR_TAKES_PATH_STRUCT)
 static int
 afs_linux_getattr(const struct path *path, struct kstat *stat, u32 request_mask, unsigned int sync_mode)
 {
@@ -1141,6 +1306,19 @@ parent_vcache_dv(struct inode *inode, cred_t *credp)
     return hgetlo(pvcp->f.m.DataVersion);
 }
 
+static inline int
+filter_enoent(int code)
+{
+#ifdef HAVE_LINUX_FATAL_SIGNAL_PENDING
+    if (code == ENOENT && fatal_signal_pending(current)) {
+        return EINTR;
+    }
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25)
+# error fatal_signal_pending not available, but it should be
+#endif
+    return code;
+}
+
 #ifndef D_SPLICE_ALIAS_RACE
 
 static inline void dentry_race_lock(void) {}
@@ -1148,11 +1326,7 @@ static inline void dentry_race_unlock(void) {}
 
 #else
 
-# if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16)
 static DEFINE_MUTEX(dentry_race_sem);
-# else
-static DECLARE_MUTEX(dentry_race_sem);
-# endif
 
 static inline void
 dentry_race_lock(void)
@@ -1222,6 +1396,7 @@ afs_linux_dentry_revalidate(struct dentry *dp, int flags)
     struct afs_fakestat_state fakestate;
     int force_drop = 0;
     afs_uint32 parent_dv;
+    int code = 0;
 
 #ifdef LOOKUP_RCU
     /* We don't support RCU path walking */
@@ -1252,14 +1427,13 @@ afs_linux_dentry_revalidate(struct dentry *dp, int flags)
        if (vcp->mvstat == AFS_MVSTAT_MTPT) {
            if (vcp->mvid.target_root && (vcp->f.states & CMValid)) {
                int tryEvalOnly = 0;
-               int code = 0;
                struct vrequest *treq = NULL;
 
                credp = crref();
 
                code = afs_CreateReq(&treq, credp);
                if (code) {
-                   goto bad_dentry;
+                   goto error;
                }
                if ((strcmp(dp->d_name.name, ".directory") == 0)) {
                    tryEvalOnly = 1;
@@ -1269,7 +1443,10 @@ afs_linux_dentry_revalidate(struct dentry *dp, int flags)
                else
                    code = afs_EvalFakeStat(&vcp, &fakestate, treq);
                afs_DestroyReq(treq);
-               if ((tryEvalOnly && vcp->mvstat == AFS_MVSTAT_MTPT) || code) {
+               if (code != 0) {
+                   goto error;
+               }
+               if (tryEvalOnly && vcp->mvstat == AFS_MVSTAT_MTPT) {
                    /* a mount point, not yet replaced by its directory */
                    goto bad_dentry;
                }
@@ -1278,20 +1455,6 @@ afs_linux_dentry_revalidate(struct dentry *dp, int flags)
            osi_Assert(vcp->mvid.parent != NULL);
        }
 
-#ifdef notdef
-       /* If the last looker changes, we should make sure the current
-        * looker still has permission to examine this file.  This would
-        * 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)) {
-               goto bad_dentry;
-           }
-
-           vcp->last_looker = treq.uid;
-       }
-#endif
-
        parent = dget_parent(dp);
        pvcp = VTOAFS(parent->d_inode);
        parent_dv = parent_vcache_dv(parent->d_inode, credp);
@@ -1303,21 +1466,27 @@ afs_linux_dentry_revalidate(struct dentry *dp, int flags)
 
        if (parent_dv > dp->d_time || !(vcp->f.states & CStatd)) {
            struct vattr *vattr = NULL;
-           int code;
-           int lookup_good;
 
            if (credp == NULL) {
                credp = crref();
            }
            code = afs_lookup(pvcp, (char *)dp->d_name.name, &tvc, credp);
+            code = filter_enoent(code);
+           if (code == ENOENT) {
+               /* ENOENT is not an error here. */
+               code = 0;
+               osi_Assert(tvc == NULL);
+           }
 
            if (code) {
-               /* We couldn't perform the lookup, so we're not okay. */
-               lookup_good = 0;
+               /* We couldn't perform the lookup, so we don't know if the
+                * dentry is valid or not. */
+               dput(parent);
+               goto error;
+           }
 
-           } else if (tvc == vcp) {
+           if (tvc == vcp) {
                /* We got back the same vcache, so we're good. */
-               lookup_good = 1;
 
            } else if (tvc == VTOAFS(dp->d_inode)) {
                /* We got back the same vcache, so we're good. This is
@@ -1328,37 +1497,29 @@ afs_linux_dentry_revalidate(struct dentry *dp, int flags)
                 * versa, so the previous case will not succeed. But this is
                 * still 'correct', so make sure not to mark the dentry as
                 * invalid; it still points to the same thing! */
-               lookup_good = 1;
 
            } else {
-               /* We got back a different file, so we're definitely not
-                * okay. */
-               lookup_good = 0;
-           }
-
-           if (!lookup_good) {
+               /*
+                * We got back a different file, so we know this dentry is
+                * _not_ okay. Force it to be unhashed, since the given name
+                * doesn't point to this file anymore.
+                */
                dput(parent);
-               /* Force unhash; the name doesn't point to this file
-                * anymore. */
                force_drop = 1;
-               if (code && code != ENOENT) {
-                   /* ...except if we couldn't perform the actual lookup,
-                    * we don't know if the name points to this file or not. */
-                   force_drop = 0;
-               }
                goto bad_dentry;
            }
 
            code = afs_CreateAttr(&vattr);
            if (code) {
                dput(parent);
-               goto bad_dentry;
+               goto error;
            }
 
            if (afs_getattr(vcp, vattr, credp)) {
                dput(parent);
                afs_DestroyAttr(vattr);
-               goto bad_dentry;
+               code = EIO;
+               goto error;
            }
 
            vattr2inode(AFSTOV(vcp), vattr);
@@ -1390,10 +1551,12 @@ afs_linux_dentry_revalidate(struct dentry *dp, int flags)
     }
 
   good_dentry:
+    code = 0;
     valid = 1;
     goto done;
 
   bad_dentry:
+    code = 0;
     valid = 0;
 #ifndef D_INVALIDATE_IS_VOID
     /* When (v3.18) d_invalidate was converted to void, it also started
@@ -1419,6 +1582,18 @@ afs_linux_dentry_revalidate(struct dentry *dp, int flags)
     if (credp)
        crfree(credp);
 
+#ifdef ERRORS_FROM_D_REVALIDATE
+    if (code != 0) {
+       /*
+        * If code is nonzero, we don't know whether this dentry is valid or
+        * not; we couldn't successfully perform the relevant lookup in order
+        * to tell. So we must not return 'valid' (1) or 'not valid' (0); we
+        * need to return an error (e.g. -EIO).
+        */
+       return -code;
+    }
+#endif
+
 #ifndef D_INVALIDATE_IS_VOID
     if (!valid) {
        /*
@@ -1435,18 +1610,37 @@ afs_linux_dentry_revalidate(struct dentry *dp, int flags)
 #endif
     return valid;
 
+ error:
+    if (code <= 0) {
+       code = EIO;
+    }
+#ifdef ERRORS_FROM_D_REVALIDATE
+    valid = 0;
+    goto done;
+#else
+    /* We can't return an error, so default to saying the dentry is invalid. */
+    goto bad_dentry;
+#endif
 }
 
 static void
 afs_dentry_iput(struct dentry *dp, struct inode *ip)
 {
     struct vcache *vcp = VTOAFS(ip);
+    int haveGlock = ISAFS_GLOCK();
+
+    if (!haveGlock) {
+        AFS_GLOCK();
+    }
 
-    AFS_GLOCK();
     if (!AFS_IS_DISCONNECTED || (vcp->f.states & CUnlinked)) {
        (void) afs_InactiveVCache(vcp, NULL);
     }
-    AFS_GUNLOCK();
+
+    if (!haveGlock) {
+        AFS_GUNLOCK();
+    }
+
     afs_linux_clear_nfsfs_renamed(dp);
 
     iput(ip);
@@ -1525,17 +1719,29 @@ struct dentry_operations afs_dentry_operations = {
  *
  * name is in kernel space at this point.
  */
+
+#if defined(IOP_TAKES_MNT_IDMAP)
+static int
+afs_linux_create(struct mnt_idmap *idmap, struct inode *dip,
+                struct dentry *dp, umode_t mode, bool excl)
+#elif defined(IOP_TAKES_USER_NAMESPACE)
+static int
+afs_linux_create(struct user_namespace *mnt_userns, struct inode *dip,
+                struct dentry *dp, umode_t mode, bool excl)
+#elif defined(IOP_CREATE_TAKES_BOOL)
 static int
-#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)
+static int
 afs_linux_create(struct inode *dip, struct dentry *dp, umode_t mode,
                 struct nameidata *nd)
 #elif defined(IOP_CREATE_TAKES_NAMEIDATA)
+static int
 afs_linux_create(struct inode *dip, struct dentry *dp, int mode,
                 struct nameidata *nd)
 #else
+static int
 afs_linux_create(struct inode *dip, struct dentry *dp, int mode)
 #endif
 {
@@ -1601,6 +1807,7 @@ afs_linux_lookup(struct inode *dip, struct dentry *dp)
     AFS_GLOCK();
 
     code = afs_lookup(VTOAFS(dip), (char *)comp, &vcp, credp);
+    code = filter_enoent(code);
     if (code == ENOENT) {
         /* It's ok for the file to not be found. That's noted by the caller by
          * seeing that the dp->d_inode field is NULL (set by d_splice_alias or
@@ -1809,8 +2016,18 @@ afs_linux_unlink(struct inode *dip, struct dentry *dp)
 }
 
 
+#if defined(IOP_TAKES_MNT_IDMAP)
+static int
+afs_linux_symlink(struct mnt_idmap *idmap, struct inode *dip,
+                 struct dentry *dp, const char *target)
+#elif defined(IOP_TAKES_USER_NAMESPACE)
+static int
+afs_linux_symlink(struct user_namespace *mnt_userns, struct inode *dip,
+                 struct dentry *dp, const char *target)
+#else
 static int
 afs_linux_symlink(struct inode *dip, struct dentry *dp, const char *target)
+#endif
 {
     int code;
     cred_t *credp = crref();
@@ -1838,10 +2055,19 @@ out:
     return afs_convert_code(code);
 }
 
+#if defined(IOP_TAKES_MNT_IDMAP)
+static int
+afs_linux_mkdir(struct mnt_idmap *idmap, struct inode *dip,
+               struct dentry *dp, umode_t mode)
+#elif defined(IOP_TAKES_USER_NAMESPACE)
+static int
+afs_linux_mkdir(struct user_namespace *mnt_userns, struct inode *dip,
+               struct dentry *dp, umode_t mode)
+#elif defined(IOP_MKDIR_TAKES_UMODE_T)
 static int
-#if defined(IOP_MKDIR_TAKES_UMODE_T)
 afs_linux_mkdir(struct inode *dip, struct dentry *dp, umode_t mode)
 #else
+static int
 afs_linux_mkdir(struct inode *dip, struct dentry *dp, int mode)
 #endif
 {
@@ -1913,13 +2139,28 @@ afs_linux_rmdir(struct inode *dip, struct dentry *dp)
 }
 
 
+#if defined(IOP_TAKES_MNT_IDMAP)
+static int
+afs_linux_rename(struct mnt_idmap *idmap,
+                struct inode *oldip, struct dentry *olddp,
+                struct inode *newip, struct dentry *newdp,
+                unsigned int flags)
+#elif defined(IOP_TAKES_USER_NAMESPACE)
+static int
+afs_linux_rename(struct user_namespace *mnt_userns,
+                struct inode *oldip, struct dentry *olddp,
+                struct inode *newip, struct dentry *newdp,
+                unsigned int flags)
+#elif defined(HAVE_LINUX_INODE_OPERATIONS_RENAME_TAKES_FLAGS)
+static int
+afs_linux_rename(struct inode *oldip, struct dentry *olddp,
+                struct inode *newip, struct dentry *newdp,
+                unsigned int flags)
+#else
 static int
 afs_linux_rename(struct inode *oldip, struct dentry *olddp,
-                struct inode *newip, struct dentry *newdp
-#ifdef HAVE_LINUX_INODE_OPERATIONS_RENAME_TAKES_FLAGS
-                , unsigned int flags
+                struct inode *newip, struct dentry *newdp)
 #endif
-               )
 {
     int code;
     cred_t *credp = crref();
@@ -1927,7 +2168,8 @@ afs_linux_rename(struct inode *oldip, struct dentry *olddp,
     const char *newname = newdp->d_name.name;
     struct dentry *rehash = NULL;
 
-#ifdef HAVE_LINUX_INODE_OPERATIONS_RENAME_TAKES_FLAGS
+#if defined(HAVE_LINUX_INODE_OPERATIONS_RENAME_TAKES_FLAGS) || \
+    defined(IOP_TAKES_MNT_IDMAP) || defined(IOP_TAKES_USER_NAMESPACE)
     if (flags)
        return -EINVAL;         /* no support for new flags yet */
 #endif
@@ -2062,16 +2304,33 @@ afs_linux_put_link(struct dentry *dentry, struct nameidata *nd)
 
 #endif /* USABLE_KERNEL_PAGE_SYMLINK_CACHE */
 
+/*
+ * Call the mapping function that reads data for a given page.
+ * Note: When we return, it is expected that the page is unlocked.  It is the
+ * responsibility of the called function (e.g. ->readpage) to unlock the given
+ * page, even when an error occurs.
+ */
+static int
+mapping_read_page(struct address_space *mapping, struct page *page)
+{
+#if defined(STRUCT_ADDRESS_SPACE_OPERATIONS_HAS_READ_FOLIO)
+    return mapping->a_ops->read_folio(NULL, page_folio(page));
+#else
+    return mapping->a_ops->readpage(NULL, page);
+#endif
+}
+
 /* Populate a page by filling it from the cache file pointed at by cachefp
  * (which contains indicated chunk)
  * If task is NULL, the page copy occurs syncronously, and the routine
  * returns with page still locked. If task is non-NULL, then page copies
  * may occur in the background, and the page will be unlocked when it is
- * ready for use.
+ * ready for use. Note that if task is non-NULL and we encounter an error
+ * before we start the background copy, we MUST unlock 'page' before we return.
  */
 static int
 afs_linux_read_cache(struct file *cachefp, struct page *page,
-                    int chunk, struct pagevec *lrupv,
+                    int chunk, struct afs_lru_pages *alrupages,
                     struct afs_pagecopy_task *task) {
     loff_t offset = page_offset(page);
     struct inode *cacheinode = cachefp->f_dentry->d_inode;
@@ -2108,16 +2367,11 @@ afs_linux_read_cache(struct file *cachefp, struct page *page,
                goto out;
            }
 
-           code = add_to_page_cache(newpage, cachemapping,
-                                    pageindex, GFP_KERNEL);
+           code = afs_add_to_page_cache_lru(alrupages, newpage, cachemapping,
+                                            pageindex, GFP_KERNEL);
            if (code == 0) {
                cachepage = newpage;
                newpage = NULL;
-
-               get_page(cachepage);
-                if (!pagevec_add(lrupv, cachepage))
-                    __pagevec_lru_add_file(lrupv);
-
            } else {
                put_page(newpage);
                newpage = NULL;
@@ -2131,7 +2385,9 @@ afs_linux_read_cache(struct file *cachefp, struct page *page,
 
     if (!PageUptodate(cachepage)) {
        ClearPageError(cachepage);
-        code = cachemapping->a_ops->readpage(NULL, cachepage);
+       /* Note that mapping_read_page always handles unlocking the given page,
+        * even when an error is returned. */
+       code = mapping_read_page(cachemapping, cachepage);
        if (!code && !task) {
            wait_on_page_locked(cachepage);
        }
@@ -2154,17 +2410,33 @@ afs_linux_read_cache(struct file *cachefp, struct page *page,
        }
     }
 
+ out:
     if (code && task) {
         unlock_page(page);
     }
 
-out:
     if (cachepage)
        put_page(cachepage);
 
     return code;
 }
 
+/*
+ * Return true if the file has a mapping that can read pages
+ */
+static int inline
+file_can_read_pages(struct file *fp)
+{
+#if defined(STRUCT_ADDRESS_SPACE_OPERATIONS_HAS_READ_FOLIO)
+    if (fp->f_dentry->d_inode->i_mapping->a_ops->read_folio != NULL)
+       return 1;
+#else
+    if (fp->f_dentry->d_inode->i_mapping->a_ops->readpage != NULL)
+       return 1;
+#endif
+    return 0;
+}
+
 static int inline
 afs_linux_readpage_fastpath(struct file *fp, struct page *pp, int *codep)
 {
@@ -2175,7 +2447,7 @@ afs_linux_readpage_fastpath(struct file *fp, struct page *pp, int *codep)
     struct file *cacheFp = NULL;
     int code;
     int dcLocked = 0;
-    struct pagevec lrupv;
+    struct afs_lru_pages lrupages;
 
     /* Not a UFS cache, don't do anything */
     if (cacheDiskType != AFS_FCACHE_TYPE_UFS)
@@ -2243,7 +2515,7 @@ afs_linux_readpage_fastpath(struct file *fp, struct page *pp, int *codep)
        ObtainReadLock(&tdc->lock);
 
     /* Is the dcache we've been given currently up to date */
-    if (!hsame(avc->f.m.DataVersion, tdc->f.versionNo) ||
+    if (!afs_IsDCacheFresh(tdc, avc) ||
        (tdc->dflags & DFFetching))
        goto out;
 
@@ -2255,22 +2527,23 @@ afs_linux_readpage_fastpath(struct file *fp, struct page *pp, int *codep)
     /* XXX - I suspect we should be locking the inodes before we use them! */
     AFS_GUNLOCK();
     cacheFp = afs_linux_raw_open(&tdc->f.inode);
-    osi_Assert(cacheFp);
-    if (!cacheFp->f_dentry->d_inode->i_mapping->a_ops->readpage) {
+    if (cacheFp == NULL) {
+       /* Problem getting the inode */
+       AFS_GLOCK();
+       goto out;
+    }
+
+    if (!file_can_read_pages(cacheFp)) {
        cachefs_noreadpage = 1;
        AFS_GLOCK();
        goto out;
     }
-#if defined(PAGEVEC_INIT_COLD_ARG)
-    pagevec_init(&lrupv, 0);
-#else
-    pagevec_init(&lrupv);
-#endif
 
-    code = afs_linux_read_cache(cacheFp, pp, tdc->f.chunk, &lrupv, NULL);
+    afs_lru_cache_init(&lrupages);
+
+    code = afs_linux_read_cache(cacheFp, pp, tdc->f.chunk, &lrupages, NULL);
 
-    if (pagevec_count(&lrupv))
-       __pagevec_lru_add_file(&lrupv);
+    afs_lru_cache_finalize(&lrupages);
 
     filp_close(cacheFp, NULL);
     AFS_GLOCK();
@@ -2283,6 +2556,9 @@ afs_linux_readpage_fastpath(struct file *fp, struct page *pp, int *codep)
     return 1;
 
 out:
+    if (cacheFp != NULL) {
+       filp_close(cacheFp, NULL);
+    }
     ReleaseWriteLock(&avc->lock);
     ReleaseReadLock(&tdc->lock);
     afs_PutDCache(tdc);
@@ -2390,17 +2666,103 @@ afs_linux_prefetch(struct file *fp, struct page *pp)
 
 }
 
+#if defined(STRUCT_ADDRESS_SPACE_OPERATIONS_HAS_READAHEAD)
+/*
+ * Bypass the cache while performing a readahead.
+ * See the comments for afs_linux_readahead for the semantics
+ * for 'rac'.
+ */
+static void
+afs_linux_bypass_readahead(struct readahead_control *rac)
+{
+    struct file *fp = rac->file;
+    unsigned num_pages = readahead_count(rac);
+    afs_int32 page_ix;
+    afs_offs_t offset;
+    struct iovec* iovecp;
+    struct nocache_read_request *ancr;
+    struct page *pp;
+    afs_int32 code = 0;
+
+    cred_t *credp;
+    struct inode *ip = FILE_INODE(fp);
+    struct vcache *avc = VTOAFS(ip);
+    afs_int32 base_index = 0;
+    afs_int32 page_count = 0;
+    afs_int32 isize;
+
+    ancr = afs_alloc_ncr(num_pages);
+    if (ancr == NULL)
+       goto done;
+
+    iovecp = ancr->auio->uio_iov;
+
+    for (page_ix = 0; page_ix < num_pages; ++page_ix) {
+       pp = readahead_page(rac);
+       if (pp == NULL)
+           break;
+
+       isize = (i_size_read(fp->f_mapping->host) - 1) >> PAGE_SHIFT;
+       if (pp->index > isize) {
+           if (PageLocked(pp))
+               unlock_page(pp);
+           put_page(pp);
+           continue;
+       }
+
+       if (page_ix == 0) {
+           offset = page_offset(pp);
+           ancr->offset = ancr->auio->uio_offset = offset;
+           base_index = pp->index;
+       }
+       iovecp[page_ix].iov_len = PAGE_SIZE;
+       if (base_index != pp->index) {
+           if (PageLocked(pp))
+                unlock_page(pp);
+           put_page(pp);
+           iovecp[page_ix].iov_base = NULL;
+           base_index++;
+           ancr->length -= PAGE_SIZE;
+           continue;
+       }
+       base_index++;
+       page_count++;
+       /* save the page for background map */
+       iovecp[page_ix].iov_base = pp;
+    }
+
+    /* If there were useful pages in the page list, schedule
+     * the read */
+    if (page_count > 0) {
+       credp = crref();
+       /* The background thread frees the ancr */
+       code = afs_ReadNoCache(avc, ancr, credp);
+       crfree(credp);
+    } else {
+       /* If there is nothing for the background thread to handle,
+        * it won't be freeing the things that we never gave it */
+       afs_free_ncr(&ancr);
+    }
+    /* we do not flush, release, or unmap pages--that will be
+     * done for us by the background thread as each page comes in
+     * from the fileserver */
+
+ done:
+    /* The vfs layer will unlock/put any of the pages in the rac that were not
+     * processed */
+    return;
+}
+#else /* STRUCT_ADDRESS_SPACE_OPERATIONS_HAS_READAHEAD */
 static int
 afs_linux_bypass_readpages(struct file *fp, struct address_space *mapping,
                           struct list_head *page_list, unsigned num_pages)
 {
     afs_int32 page_ix;
-    struct uio *auio;
     afs_offs_t offset;
     struct iovec* iovecp;
     struct nocache_read_request *ancr;
     struct page *pp;
-    struct pagevec lrupv;
+    struct afs_lru_pages lrupages;
     afs_int32 code = 0;
 
     cred_t *credp;
@@ -2410,26 +2772,12 @@ afs_linux_bypass_readpages(struct file *fp, struct address_space *mapping,
     afs_int32 page_count = 0;
     afs_int32 isize;
 
-    /* background thread must free: iovecp, auio, ancr */
-    iovecp = osi_Alloc(num_pages * sizeof(struct iovec));
+    ancr = afs_alloc_ncr(num_pages);
+    if (ancr == NULL)
+       return afs_convert_code(ENOMEM);
+    iovecp = ancr->auio->uio_iov;
 
-    auio = osi_Alloc(sizeof(struct uio));
-    auio->uio_iov = iovecp;
-    auio->uio_iovcnt = num_pages;
-    auio->uio_flag = UIO_READ;
-    auio->uio_seg = AFS_UIOSYS;
-    auio->uio_resid = num_pages * PAGE_SIZE;
-
-    ancr = osi_Alloc(sizeof(struct nocache_read_request));
-    ancr->auio = auio;
-    ancr->offset = auio->uio_offset;
-    ancr->length = auio->uio_resid;
-
-#if defined(PAGEVEC_INIT_COLD_ARG)
-    pagevec_init(&lrupv, 0);
-#else
-    pagevec_init(&lrupv);
-#endif
+    afs_lru_cache_init(&lrupages);
 
     for(page_ix = 0; page_ix < num_pages; ++page_ix) {
 
@@ -2444,12 +2792,13 @@ afs_linux_bypass_readpages(struct file *fp, struct address_space *mapping,
        if(pp->index > isize) {
            if(PageLocked(pp))
                unlock_page(pp);
+           put_page(pp);
            continue;
        }
 
        if(page_ix == 0) {
            offset = page_offset(pp);
-           ancr->offset = auio->uio_offset = offset;
+           ancr->offset = ancr->auio->uio_offset = offset;
            base_index = pp->index;
        }
         iovecp[page_ix].iov_len = PAGE_SIZE;
@@ -2475,43 +2824,33 @@ afs_linux_bypass_readpages(struct file *fp, struct address_space *mapping,
                lock_page(pp);
            }
 
-            /* increment page refcount--our original design assumed
-             * that locking it would effectively pin it;  protect
-             * ourselves from the possiblity that this assumption is
-             * is faulty, at low cost (provided we do not fail to
-             * do the corresponding decref on the other side) */
-            get_page(pp);
-
            /* save the page for background map */
             iovecp[page_ix].iov_base = (void*) pp;
 
            /* and put it on the LRU cache */
-           if (!pagevec_add(&lrupv, pp))
-               __pagevec_lru_add_file(&lrupv);
+           afs_lru_cache_add(&lrupages, pp);
         }
     }
 
     /* If there were useful pages in the page list, make sure all pages
      * are in the LRU cache, then schedule the read */
     if(page_count) {
-       if (pagevec_count(&lrupv))
-           __pagevec_lru_add_file(&lrupv);
+       afs_lru_cache_finalize(&lrupages);
        credp = crref();
+       /* background thread frees the ancr */
         code = afs_ReadNoCache(avc, ancr, credp);
        crfree(credp);
     } else {
         /* If there is nothing for the background thread to handle,
          * it won't be freeing the things that we never gave it */
-        osi_Free(iovecp, num_pages * sizeof(struct iovec));
-        osi_Free(auio, sizeof(struct uio));
-        osi_Free(ancr, sizeof(struct nocache_read_request));
+       afs_free_ncr(&ancr);
     }
     /* we do not flush, release, or unmap pages--that will be
      * done for us by the background thread as each page comes in
      * from the fileserver */
     return afs_convert_code(code);
 }
-
+#endif /* STRUCT_ADDRESS_SPACE_OPERATIONS_HAS_READAHEAD */
 
 static int
 afs_linux_bypass_readpage(struct file *fp, struct page *pp)
@@ -2536,8 +2875,17 @@ afs_linux_bypass_readpage(struct file *fp, struct page *pp)
     ClearPageError(pp);
 
     /* receiver frees */
-    auio = osi_Alloc(sizeof(struct uio));
-    iovecp = osi_Alloc(sizeof(struct iovec));
+    ancr = afs_alloc_ncr(1);
+    if (ancr == NULL) {
+       SetPageError(pp);
+       return afs_convert_code(ENOMEM);
+    }
+    /*
+     * afs_alloc_ncr has already set the auio->uio_iov, make sure setup_uio
+     * uses the existing value when it sets auio->uio_iov.
+     */
+    auio = ancr->auio;
+    iovecp = auio->uio_iov;
 
     /* address can be NULL, because we overwrite it with 'pp', below */
     setup_uio(auio, iovecp, NULL, page_offset(pp),
@@ -2547,8 +2895,6 @@ afs_linux_bypass_readpage(struct file *fp, struct page *pp)
     get_page(pp); /* see above */
     auio->uio_iov->iov_base = (void*) pp;
     /* the background thread will free this */
-    ancr = osi_Alloc(sizeof(struct nocache_read_request));
-    ancr->auio = auio;
     ancr->offset = page_offset(pp);
     ancr->length = PAGE_SIZE;
 
@@ -2570,6 +2916,7 @@ afs_linux_can_bypass(struct inode *ip) {
        case LARGE_FILES_BYPASS_CACHE:
            if (i_size_read(ip) > cache_bypass_threshold)
                return 1;
+           AFS_FALLTHROUGH;
        default:
            return 0;
      }
@@ -2609,11 +2956,189 @@ afs_linux_readpage(struct file *fp, struct page *pp)
     return code;
 }
 
+#if defined(STRUCT_ADDRESS_SPACE_OPERATIONS_HAS_READ_FOLIO)
+static int
+afs_linux_read_folio(struct file *fp, struct folio *folio)
+{
+    struct page *pp = &folio->page;
+
+    return afs_linux_readpage(fp, pp);
+}
+#endif
+
+/*
+ * Updates the adc and acacheFp parameters
+ * Returns:
+ *    0 - success
+ *   -1 - problem getting inode or no mapping function
+ */
+static int
+get_dcache_readahead(struct dcache **adc, struct file **acacheFp,
+                    struct vcache *avc, loff_t offset)
+{
+    struct dcache *tdc = *adc;
+    struct file *cacheFp = *acacheFp;
+    int code;
+
+    if (tdc != NULL && tdc->f.chunk != AFS_CHUNK(offset)) {
+       AFS_GLOCK();
+       ReleaseReadLock(&tdc->lock);
+       afs_PutDCache(tdc);
+       AFS_GUNLOCK();
+       tdc = NULL;
+       if (cacheFp != NULL) {
+           filp_close(cacheFp, NULL);
+           cacheFp = NULL;
+       }
+    }
+
+    if (tdc == NULL) {
+       AFS_GLOCK();
+       tdc = afs_FindDCache(avc, offset);
+       if (tdc != NULL) {
+           ObtainReadLock(&tdc->lock);
+           if (!afs_IsDCacheFresh(tdc, avc) ||
+               (tdc->dflags & DFFetching) != 0) {
+               ReleaseReadLock(&tdc->lock);
+               afs_PutDCache(tdc);
+               tdc = NULL;
+           }
+       }
+       AFS_GUNLOCK();
+       if (tdc != NULL) {
+           cacheFp = afs_linux_raw_open(&tdc->f.inode);
+           if (cacheFp == NULL) {
+               /* Problem getting the inode */
+               code = -1;
+               goto out;
+           }
+           if (!file_can_read_pages(cacheFp)) {
+               cachefs_noreadpage = 1;
+               /* No mapping function */
+               code = -1;
+               goto out;
+           }
+       }
+    }
+    code = 0;
+
+ out:
+    if (code != 0) {
+       if (cacheFp != NULL) {
+           filp_close(cacheFp, NULL);
+           cacheFp = NULL;
+       }
+       if (tdc != NULL) {
+           AFS_GLOCK();
+           ReleaseReadLock(&tdc->lock);
+           afs_PutDCache(tdc);
+           AFS_GUNLOCK();
+           tdc = NULL;
+       }
+    }
+    *adc = tdc;
+    *acacheFp = cacheFp;
+    return code;
+}
+
+#if defined(STRUCT_ADDRESS_SPACE_OPERATIONS_HAS_READAHEAD)
+/*
+ * Readahead reads a number of pages for a particular file. We use
+ * this to optimise the reading, by limiting the number of times upon which
+ * we have to lookup, lock and open vcaches and dcaches.
+ *
+ * Upon return, the vfs layer handles unlocking and putting any pages in the
+ * rac that we did not process here.
+ *
+ * Note: any errors detected during readahead are ignored at this stage by the
+ * vfs. We just need to unlock/put the page and return.  Errors will be detected
+ * later in the vfs processing.
+ */
+static void
+afs_linux_readahead(struct readahead_control *rac)
+{
+    struct page *page;
+    struct address_space *mapping = rac->mapping;
+    struct inode *inode = mapping->host;
+    struct vcache *avc = VTOAFS(inode);
+    struct dcache *tdc;
+    struct file *cacheFp = NULL;
+    int code;
+    loff_t offset;
+    struct afs_lru_pages lrupages;
+    struct afs_pagecopy_task *task;
+
+    if (afs_linux_bypass_check(inode)) {
+       afs_linux_bypass_readahead(rac);
+       return;
+    }
+    if (cacheDiskType == AFS_FCACHE_TYPE_MEM)
+       return;
+
+    /* No readpage (ex: tmpfs) , skip */
+    if (cachefs_noreadpage)
+       return;
+
+    AFS_GLOCK();
+    code = afs_linux_VerifyVCache(avc, NULL);
+    if (code != 0) {
+       AFS_GUNLOCK();
+       return;
+    }
+
+    ObtainWriteLock(&avc->lock, 912);
+    AFS_GUNLOCK();
+
+    task = afs_pagecopy_init_task();
+
+    tdc = NULL;
+
+    afs_lru_cache_init(&lrupages);
+
+    while ((page = readahead_page(rac)) != NULL) {
+       offset = page_offset(page);
+
+       code = get_dcache_readahead(&tdc, &cacheFp, avc, offset);
+       if (code != 0) {
+           if (PageLocked(page)) {
+               unlock_page(page);
+           }
+           put_page(page);
+           goto done;
+       }
+
+       if (tdc != NULL) {
+           /* afs_linux_read_cache will unlock the page */
+           afs_linux_read_cache(cacheFp, page, tdc->f.chunk, &lrupages, task);
+       } else if (PageLocked(page)) {
+           unlock_page(page);
+       }
+       put_page(page);
+    }
+
+ done:
+    afs_lru_cache_finalize(&lrupages);
+
+    if (cacheFp != NULL)
+       filp_close(cacheFp, NULL);
+
+    afs_pagecopy_put_task(task);
+
+    AFS_GLOCK();
+    if (tdc != NULL) {
+       ReleaseReadLock(&tdc->lock);
+       afs_PutDCache(tdc);
+    }
+
+    ReleaseWriteLock(&avc->lock);
+    AFS_GUNLOCK();
+    return;
+}
+#else /* STRUCT_ADDRESS_SPACE_OPERATIONS_HAS_READAHEAD */
 /* Readpages reads a number of pages for a particular file. We use
  * this to optimise the reading, by limiting the number of times upon which
  * we have to lookup, lock and open vcaches and dcaches
  */
-
 static int
 afs_linux_readpages(struct file *fp, struct address_space *mapping,
                    struct list_head *page_list, unsigned int num_pages)
@@ -2625,7 +3150,7 @@ afs_linux_readpages(struct file *fp, struct address_space *mapping,
     int code;
     unsigned int page_idx;
     loff_t offset;
-    struct pagevec lrupv;
+    struct afs_lru_pages lrupages;
     struct afs_pagecopy_task *task;
 
     if (afs_linux_bypass_check(inode))
@@ -2650,63 +3175,32 @@ afs_linux_readpages(struct file *fp, struct address_space *mapping,
     task = afs_pagecopy_init_task();
 
     tdc = NULL;
-#if defined(PAGEVEC_INIT_COLD_ARG)
-    pagevec_init(&lrupv, 0);
-#else
-    pagevec_init(&lrupv);
-#endif
+
+    afs_lru_cache_init(&lrupages);
+
     for (page_idx = 0; page_idx < num_pages; page_idx++) {
        struct page *page = list_entry(page_list->prev, struct page, lru);
        list_del(&page->lru);
        offset = page_offset(page);
 
-       if (tdc && tdc->f.chunk != AFS_CHUNK(offset)) {
-           AFS_GLOCK();
-           ReleaseReadLock(&tdc->lock);
-           afs_PutDCache(tdc);
-           AFS_GUNLOCK();
-           tdc = NULL;
-           if (cacheFp)
-               filp_close(cacheFp, NULL);
-       }
-
-       if (!tdc) {
-           AFS_GLOCK();
-           if ((tdc = afs_FindDCache(avc, offset))) {
-               ObtainReadLock(&tdc->lock);
-               if (!hsame(avc->f.m.DataVersion, tdc->f.versionNo) ||
-                   (tdc->dflags & DFFetching)) {
-                   ReleaseReadLock(&tdc->lock);
-                   afs_PutDCache(tdc);
-                   tdc = NULL;
-               }
-           }
-           AFS_GUNLOCK();
-           if (tdc) {
-               cacheFp = afs_linux_raw_open(&tdc->f.inode);
-                osi_Assert(cacheFp);
-               if (!cacheFp->f_dentry->d_inode->i_mapping->a_ops->readpage) {
-                   cachefs_noreadpage = 1;
-                   goto out;
-               }
-           }
+       code = get_dcache_readahead(&tdc, &cacheFp, avc, offset);
+       if (code != 0) {
+           put_page(page);
+           goto out;
        }
 
-       if (tdc && !add_to_page_cache(page, mapping, page->index,
-                                     GFP_KERNEL)) {
-           get_page(page);
-           if (!pagevec_add(&lrupv, page))
-               __pagevec_lru_add_file(&lrupv);
-
-           afs_linux_read_cache(cacheFp, page, tdc->f.chunk, &lrupv, task);
+       if (tdc && !afs_add_to_page_cache_lru(&lrupages, page, mapping, page->index,
+                                             GFP_KERNEL)) {
+           /* Note that afs_add_to_page_cache_lru() locks the 'page'.
+            * afs_linux_read_cache() is guaranteed to handle unlocking it. */
+           afs_linux_read_cache(cacheFp, page, tdc->f.chunk, &lrupages, task);
        }
        put_page(page);
     }
-    if (pagevec_count(&lrupv))
-       __pagevec_lru_add_file(&lrupv);
+    afs_lru_cache_finalize(&lrupages);
 
 out:
-    if (tdc)
+    if (cacheFp)
        filp_close(cacheFp, NULL);
 
     afs_pagecopy_put_task(task);
@@ -2721,6 +3215,7 @@ out:
     AFS_GUNLOCK();
     return 0;
 }
+#endif /* STRUCT_ADDRESS_SPACE_OPERATIONS_HAS_READAHEAD */
 
 /* Prepare an AFS vcache for writeback. Should be called with the vcache
  * locked */
@@ -2969,12 +3464,21 @@ done:
 /* afs_linux_permission
  * Check access rights - returns error if can't check or permission denied.
  */
+
+#if defined(IOP_TAKES_MNT_IDMAP)
+static int
+afs_linux_permission(struct mnt_idmap *idmap, struct inode *ip, int mode)
+#elif defined(IOP_TAKES_USER_NAMESPACE)
+static int
+afs_linux_permission(struct user_namespace *mnt_userns, struct inode *ip, int mode)
+#elif defined(IOP_PERMISSION_TAKES_FLAGS)
 static int
-#if defined(IOP_PERMISSION_TAKES_FLAGS)
 afs_linux_permission(struct inode *ip, int mode, unsigned int flags)
 #elif defined(IOP_PERMISSION_TAKES_NAMEIDATA)
+static int
 afs_linux_permission(struct inode *ip, int mode, struct nameidata *nd)
 #else
+static int
 afs_linux_permission(struct inode *ip, int mode)
 #endif
 {
@@ -3032,9 +3536,11 @@ afs_linux_prepare_write(struct file *file, struct page *page, unsigned from,
                        unsigned to)
 {
 
-    /* http://kerneltrap.org/node/4941 details the expected behaviour of
-     * prepare_write. Essentially, if the page exists within the file,
-     * and is not being fully written, then we should populate it.
+    /*
+     * Linux's Documentation/filesystems/vfs.txt (.rst) details the expected
+     * behaviour of prepare_write (prior to 2.6.28) and write_begin (2.6.28).
+     * Essentially, if the page exists within the file, and is not being fully
+     * written, then we should populate it.
      */
 
     if (!PageUptodate(page)) {
@@ -3051,7 +3557,7 @@ afs_linux_prepare_write(struct file *file, struct page *page, unsigned from,
            SetPageChecked(page);
        /* Is the page readable, if it's wronly, we don't care, because we're
         * not actually going to read from it ... */
-       } else if ((file->f_flags && O_ACCMODE) != O_WRONLY) {
+       } else if ((file->f_flags & O_ACCMODE) != O_WRONLY) {
            /* We don't care if fillpage fails, because if it does the page
             * won't be marked as up to date
             */
@@ -3077,6 +3583,33 @@ afs_linux_write_end(struct file *file, struct address_space *mapping,
     return code;
 }
 
+# if defined(HAVE_LINUX_GRAB_CACHE_PAGE_WRITE_BEGIN_NOFLAGS)
+static int
+afs_linux_write_begin(struct file *file, struct address_space *mapping,
+                     loff_t pos, unsigned len,
+                     struct page **pagep, void **fsdata)
+{
+    struct page *page;
+    pgoff_t index = pos >> PAGE_SHIFT;
+    unsigned int from = pos & (PAGE_SIZE - 1);
+    int code;
+
+    page = grab_cache_page_write_begin(mapping, index);
+    if (!page) {
+       return -ENOMEM;
+    }
+
+    *pagep = page;
+
+    code = afs_linux_prepare_write(file, page, from, from + len);
+    if (code) {
+       unlock_page(page);
+       put_page(page);
+    }
+
+    return code;
+}
+# else
 static int
 afs_linux_write_begin(struct file *file, struct address_space *mapping,
                                 loff_t pos, unsigned len, unsigned flags,
@@ -3102,7 +3635,8 @@ afs_linux_write_begin(struct file *file, struct address_space *mapping,
 
     return code;
 }
-#endif
+# endif /* HAVE_LINUX_GRAB_CACHE_PAGE_WRITE_BEGIN_NOFLAGS */
+#endif /* STRUCT_ADDRESS_SPACE_OPERATIONS_HAS_WRITE_BEGIN */
 
 #ifndef STRUCT_DENTRY_OPERATIONS_HAS_D_AUTOMOUNT
 static void *
@@ -3152,9 +3686,23 @@ static struct inode_operations afs_file_iops = {
 };
 
 static struct address_space_operations afs_file_aops = {
+#if defined(STRUCT_ADDRESS_SPACE_OPERATIONS_HAS_READ_FOLIO)
+  .read_folio =                afs_linux_read_folio,
+#else
   .readpage =          afs_linux_readpage,
+#endif
+#if defined(STRUCT_ADDRESS_SPACE_OPERATIONS_HAS_READAHEAD)
+  .readahead =         afs_linux_readahead,
+#else
   .readpages =                 afs_linux_readpages,
+#endif
   .writepage =         afs_linux_writepage,
+#if defined(STRUCT_ADDRESS_SPACE_OPERATIONS_HAS_DIRTY_FOLIO) && \
+    defined(HAVE_LINUX_BLOCK_DIRTY_FOLIO)
+  .dirty_folio =       block_dirty_folio,
+#else
+  .set_page_dirty =    __set_page_dirty_buffers,
+#endif
 #if defined (STRUCT_ADDRESS_SPACE_OPERATIONS_HAS_WRITE_BEGIN)
   .write_begin =        afs_linux_write_begin,
   .write_end =          afs_linux_write_end,
@@ -3216,9 +3764,22 @@ afs_symlink_filler(struct file *file, struct page *page)
     unlock_page(page);
     return code;
 }
+#if defined(STRUCT_ADDRESS_SPACE_OPERATIONS_HAS_READ_FOLIO)
+static int
+afs_symlink_filler_folio(struct file *file, struct folio *folio)
+{
+    struct page *page = &folio->page;
+    return afs_symlink_filler(file, page);
+}
+#endif
+
 
 static struct address_space_operations afs_symlink_aops = {
+#if defined(STRUCT_ADDRESS_SPACE_OPERATIONS_HAS_READ_FOLIO)
+  .read_folio =        afs_symlink_filler_folio
+#else
   .readpage =  afs_symlink_filler
+#endif
 };
 #endif /* USABLE_KERNEL_PAGE_SYMLINK_CACHE */
 
@@ -3239,6 +3800,7 @@ static struct inode_operations afs_symlink_iops = {
   .put_link =          afs_linux_put_link,
 #endif /* USABLE_KERNEL_PAGE_SYMLINK_CACHE */
   .setattr =           afs_notify_change,
+  .getattr =           afs_linux_getattr,
 };
 
 void