This gives me about 1.5% speed up when issuing readlink on /initrd.img on ext4. Signed-off-by: Mateusz Guzik <mjguzik@xxxxxxxxx> --- 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