]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
9p: Enable symlink caching in page cache
authorRemi Pommarel <repk@triplefau.lt>
Thu, 21 May 2026 09:40:32 +0000 (11:40 +0200)
committerDominique Martinet <asmadeus@codewreck.org>
Sun, 21 Jun 2026 05:22:57 +0000 (05:22 +0000)
Currently, when cache=loose is enabled, file reads are cached in the
page cache, but symlink reads are not. This patch allows the results
of p9_client_readlink() to be stored in the page cache, eliminating
the need for repeated 9P transactions on subsequent symlink accesses.

This change improves performance for workloads that involve frequent
symlink resolution.

Signed-off-by: Remi Pommarel <repk@triplefau.lt>
Message-ID: <982462d17c0c0d2856763266a25eb04d080c1dbb.1779355927.git.repk@triplefau.lt>
Signed-off-by: Dominique Martinet <asmadeus@codewreck.org>
fs/9p/vfs_addr.c
fs/9p/vfs_inode.c
fs/9p/vfs_inode_dotl.c

index 862164181baca1e88ecf8859c55868bb01525ce1..2b6ca573f955a33507c73f05948583b64b185143 100644 (file)
@@ -70,11 +70,34 @@ static void v9fs_issue_read(struct netfs_io_subrequest *subreq)
 {
        struct netfs_io_request *rreq = subreq->rreq;
        struct p9_fid *fid = rreq->netfs_priv;
+       char *target;
        unsigned long long pos = subreq->start + subreq->transferred;
-       int total, err;
-
-       total = p9_client_read(fid, pos, &subreq->io_iter, &err);
+       int total = 0, err, len, n;
+
+       if (S_ISLNK(rreq->inode->i_mode)) {
+               /* p9_client_readlink() must not be called for legacy protocols
+                * 9p2000 or 9p2000.u.
+                */
+               BUG_ON(!p9_is_proto_dotl(fid->clnt));
+               if (WARN_ON_ONCE(pos)) {
+                       /* reading a link at a non null offset should
+                        * not happen
+                        */
+                       err = -EIO;
+                       goto fill_subreq;
+               }
+               err = p9_client_readlink(fid, &target);
+               if (err != 0)
+                       goto fill_subreq;
+               len = strlen(target);
+               n = copy_to_iter(target, len, &subreq->io_iter);
+               kfree(target);
+               total = n;
+       } else {
+               total = p9_client_read(fid, pos, &subreq->io_iter, &err);
+       }
 
+fill_subreq:
        /* if we just extended the file size, any portion not in
         * cache won't be on server and is zeroes */
        if (subreq->rreq->origin != NETFS_UNBUFFERED_READ &&
@@ -99,6 +122,7 @@ static void v9fs_issue_read(struct netfs_io_subrequest *subreq)
 static int v9fs_init_request(struct netfs_io_request *rreq, struct file *file)
 {
        struct p9_fid *fid;
+       struct dentry *dentry;
        bool writing = (rreq->origin == NETFS_READ_FOR_WRITE ||
                        rreq->origin == NETFS_WRITETHROUGH ||
                        rreq->origin == NETFS_UNBUFFERED_WRITE ||
@@ -115,6 +139,14 @@ static int v9fs_init_request(struct netfs_io_request *rreq, struct file *file)
                if (!fid)
                        goto no_fid;
                p9_fid_get(fid);
+       } else if (S_ISLNK(rreq->inode->i_mode)) {
+               dentry = d_find_any_alias(rreq->inode);
+               if (!dentry)
+                       goto no_fid;
+               fid = v9fs_fid_lookup(dentry);
+               dput(dentry);
+               if (IS_ERR(fid))
+                       goto no_fid;
        } else {
                fid = v9fs_fid_find_inode(rreq->inode, writing, INVALID_UID, true);
                if (!fid)
index a178e8cb2c82bab43e00ad74c153571b14817bf2..cdaa5034cbef6d71d067651a85d2db022003d163 100644 (file)
@@ -302,10 +302,12 @@ int v9fs_init_inode(struct v9fs_session_info *v9ses,
                        goto error;
                }
 
-               if (v9fs_proto_dotl(v9ses))
+               if (v9fs_proto_dotl(v9ses)) {
                        inode->i_op = &v9fs_symlink_inode_operations_dotl;
-               else
+                       inode_nohighmem(inode);
+               } else {
                        inode->i_op = &v9fs_symlink_inode_operations;
+               }
 
                break;
        case S_IFDIR:
index fae324681ff35d8f7bb186a143f001a863dbcf6e..8b056c334c2b0b7c2fcf4b09014a0bbb0e6681d7 100644 (file)
@@ -686,9 +686,11 @@ v9fs_vfs_symlink_dotl(struct mnt_idmap *idmap, struct inode *dir,
        int err;
        kgid_t gid;
        const unsigned char *name;
+       struct v9fs_session_info *v9ses;
        struct p9_qid qid;
        struct p9_fid *dfid;
        struct p9_fid *fid = NULL;
+       struct inode *inode;
 
        name = dentry->d_name.name;
        p9_debug(P9_DEBUG_VFS, "%lu,%s,%s\n", dir->i_ino, name, symname);
@@ -712,6 +714,26 @@ v9fs_vfs_symlink_dotl(struct mnt_idmap *idmap, struct inode *dir,
 
        v9fs_invalidate_inode_attr(dir);
 
+       /* instantiate inode and assign the unopened fid to the dentry */
+       fid = p9_client_walk(dfid, 1, &name, 1);
+       if (IS_ERR(fid)) {
+               err = PTR_ERR(fid);
+               p9_debug(P9_DEBUG_VFS, "p9_client_walk failed %d\n",
+                        err);
+               goto error;
+       }
+
+       v9ses = v9fs_inode2v9ses(dir);
+       inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb);
+       if (IS_ERR(inode)) {
+               err = PTR_ERR(inode);
+               p9_debug(P9_DEBUG_VFS, "inode creation failed %d\n",
+                        err);
+               goto error;
+       }
+       v9fs_fid_add(dentry, &fid);
+       d_instantiate(dentry, inode);
+       err = 0;
 error:
        p9_fid_put(fid);
        p9_fid_put(dfid);
@@ -853,16 +875,18 @@ error:
 }
 
 /**
- * v9fs_vfs_get_link_dotl - follow a symlink path
+ * v9fs_vfs_get_link_nocache_dotl - Resolve a symlink directly.
+ *
+ * To be used when symlink caching is not enabled.
+ *
  * @dentry: dentry for symlink
  * @inode: inode for symlink
  * @done: destructor for return value
  */
-
 static const char *
-v9fs_vfs_get_link_dotl(struct dentry *dentry,
-                      struct inode *inode,
-                      struct delayed_call *done)
+v9fs_vfs_get_link_nocache_dotl(struct dentry *dentry,
+                              struct inode *inode,
+                              struct delayed_call *done)
 {
        struct p9_fid *fid;
        char *target;
@@ -884,6 +908,26 @@ v9fs_vfs_get_link_dotl(struct dentry *dentry,
        return target;
 }
 
+/**
+ * v9fs_vfs_get_link_dotl - follow a symlink path
+ * @dentry: dentry for symlink
+ * @inode: inode for symlink
+ * @done: destructor for return value
+ */
+static const char *
+v9fs_vfs_get_link_dotl(struct dentry *dentry,
+                      struct inode *inode,
+                      struct delayed_call *done)
+{
+       struct v9fs_session_info *v9ses;
+
+       v9ses = v9fs_inode2v9ses(inode);
+       if (v9ses->cache & (CACHE_META|CACHE_LOOSE))
+               return page_get_link(dentry, inode, done);
+
+       return v9fs_vfs_get_link_nocache_dotl(dentry, inode, done);
+}
+
 int v9fs_refresh_inode_dotl(struct p9_fid *fid, struct inode *inode)
 {
        struct p9_stat_dotl *st;