[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <20241118085357.494178-1-mjguzik@gmail.com>
Date: Mon, 18 Nov 2024 09:53:57 +0100
From: Mateusz Guzik <mjguzik@...il.com>
To: brauner@...nel.org
Cc: viro@...iv.linux.org.uk,
jack@...e.cz,
linux-kernel@...r.kernel.org,
linux-fsdevel@...r.kernel.org,
Mateusz Guzik <mjguzik@...il.com>
Subject: [RFC PATCH] vfs: dodge strlen() in vfs_readlink() when ->i_link is populated
This gives me about 1.5% speed up when issuing readlink on /initrd.img
on ext4.
Signed-off-by: Mateusz Guzik <mjguzik@...il.com>
---
I had this running with the following debug:
if (strlen(link) != inode->i_size)
printk(KERN_CRIT "mismatch [%s] %l %l\n", link,
strlen(link), inode->i_size);
nothing popped up
I would leave something of that sort in if it was not defeating the
point of the change.
However, I'm a little worried some crap fs *does not* fill this in
despite populating i_link.
Perhaps it would make sense to keep the above with the patch hanging out
in next and remove later?
Anyhow, worst case, should it turn out i_size does not work there are at
least two 4-byte holes which can be used to store the length (and
chances are some existing field can be converted into a union instead).
Bench:
$ cat tests/readlink1.c
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <string.h>
char *testcase_description = "readlink /initrd.img";
void testcase(unsigned long long *iterations, unsigned long nr)
{
char *tmplink = "/initrd.img";
char buf[1024];
while (1) {
int error = readlink(tmplink, buf, sizeof(buf));
assert(error > 0);
(*iterations)++;
}
}
fs/namei.c | 43 ++++++++++++++++++----------------
fs/proc/namespaces.c | 2 +-
include/linux/fs.h | 2 +-
security/apparmor/apparmorfs.c | 2 +-
4 files changed, 26 insertions(+), 23 deletions(-)
diff --git a/fs/namei.c b/fs/namei.c
index 9d30c7aa9aa6..7aced8aca0f6 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -5272,19 +5272,16 @@ SYSCALL_DEFINE2(rename, const char __user *, oldname, const char __user *, newna
getname(newname), 0);
}
-int readlink_copy(char __user *buffer, int buflen, const char *link)
+int readlink_copy(char __user *buffer, int buflen, const char *link, int linklen)
{
- int len = PTR_ERR(link);
- if (IS_ERR(link))
- goto out;
+ int copylen;
- len = strlen(link);
- if (len > (unsigned) buflen)
- len = buflen;
- if (copy_to_user(buffer, link, len))
- len = -EFAULT;
-out:
- return len;
+ copylen = linklen;
+ if (unlikely(copylen > (unsigned) buflen))
+ copylen = buflen;
+ if (copy_to_user(buffer, link, copylen))
+ copylen = -EFAULT;
+ return copylen;
}
/**
@@ -5317,13 +5314,15 @@ int vfs_readlink(struct dentry *dentry, char __user *buffer, int buflen)
}
link = READ_ONCE(inode->i_link);
- if (!link) {
- link = inode->i_op->get_link(dentry, inode, &done);
- if (IS_ERR(link))
- return PTR_ERR(link);
+ if (link)
+ return readlink_copy(buffer, buflen, link, inode->i_size);
+
+ link = inode->i_op->get_link(dentry, inode, &done);
+ res = PTR_ERR(link);
+ if (!IS_ERR(link)) {
+ res = readlink_copy(buffer, buflen, link, strlen(link));
+ do_delayed_call(&done);
}
- res = readlink_copy(buffer, buflen, link);
- do_delayed_call(&done);
return res;
}
EXPORT_SYMBOL(vfs_readlink);
@@ -5391,10 +5390,14 @@ EXPORT_SYMBOL(page_put_link);
int page_readlink(struct dentry *dentry, char __user *buffer, int buflen)
{
+ const char *link;
+ int res;
+
DEFINE_DELAYED_CALL(done);
- int res = readlink_copy(buffer, buflen,
- page_get_link(dentry, d_inode(dentry),
- &done));
+ link = page_get_link(dentry, d_inode(dentry), &done);
+ res = PTR_ERR(link);
+ if (!IS_ERR(link))
+ res = readlink_copy(buffer, buflen, link, strlen(link));
do_delayed_call(&done);
return res;
}
diff --git a/fs/proc/namespaces.c b/fs/proc/namespaces.c
index 8e159fc78c0a..c610224faf10 100644
--- a/fs/proc/namespaces.c
+++ b/fs/proc/namespaces.c
@@ -83,7 +83,7 @@ static int proc_ns_readlink(struct dentry *dentry, char __user *buffer, int bufl
if (ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) {
res = ns_get_name(name, sizeof(name), task, ns_ops);
if (res >= 0)
- res = readlink_copy(buffer, buflen, name);
+ res = readlink_copy(buffer, buflen, name, strlen(name));
}
put_task_struct(task);
return res;
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 972147da71f9..7d456db6a381 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -3351,7 +3351,7 @@ extern const struct file_operations generic_ro_fops;
#define special_file(m) (S_ISCHR(m)||S_ISBLK(m)||S_ISFIFO(m)||S_ISSOCK(m))
-extern int readlink_copy(char __user *, int, const char *);
+extern int readlink_copy(char __user *, int, const char *, int);
extern int page_readlink(struct dentry *, char __user *, int);
extern const char *page_get_link(struct dentry *, struct inode *,
struct delayed_call *);
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c
index 01b923d97a44..60959cfba672 100644
--- a/security/apparmor/apparmorfs.c
+++ b/security/apparmor/apparmorfs.c
@@ -2611,7 +2611,7 @@ static int policy_readlink(struct dentry *dentry, char __user *buffer,
res = snprintf(name, sizeof(name), "%s:[%lu]", AAFS_NAME,
d_inode(dentry)->i_ino);
if (res > 0 && res < sizeof(name))
- res = readlink_copy(buffer, buflen, name);
+ res = readlink_copy(buffer, buflen, name, strlen(name));
else
res = -ENOENT;
--
2.43.0
Powered by blists - more mailing lists