From 7950e3852ab86826b7349a535d2e8b0000340d7f Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 10 Oct 2012 16:43:13 -0400 Subject: vfs: embed struct filename inside of names_cache allocation if possible In the common case where a name is much smaller than PATH_MAX, an extra allocation for struct filename is unnecessary. Before allocating a separate one, try to embed the struct filename inside the buffer first. If it turns out that that's not long enough, then fall back to allocating a separate struct filename and redoing the copy. Signed-off-by: Jeff Layton Signed-off-by: Al Viro --- fs/namei.c | 69 ++++++++++++++++++++++++++++++++++++++---------------- include/linux/fs.h | 1 + 2 files changed, 50 insertions(+), 20 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index 80b162b142f9..d1895f308156 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -119,40 +119,69 @@ */ void final_putname(struct filename *name) { - __putname(name->name); - kfree(name); + if (name->separate) { + __putname(name->name); + kfree(name); + } else { + __putname(name); + } } +#define EMBEDDED_NAME_MAX (PATH_MAX - sizeof(struct filename)) + static struct filename * getname_flags(const char __user *filename, int flags, int *empty) { struct filename *result, *err; - char *kname; int len; + long max; + char *kname; result = audit_reusename(filename); if (result) return result; - /* FIXME: create dedicated slabcache? */ - result = kzalloc(sizeof(*result), GFP_KERNEL); + result = __getname(); if (unlikely(!result)) return ERR_PTR(-ENOMEM); - kname = __getname(); - if (unlikely(!kname)) { - err = ERR_PTR(-ENOMEM); - goto error_free_name; - } - + /* + * First, try to embed the struct filename inside the names_cache + * allocation + */ + kname = (char *)result + sizeof(*result); result->name = kname; - result->uptr = filename; - len = strncpy_from_user(kname, filename, PATH_MAX); + result->separate = false; + max = EMBEDDED_NAME_MAX; + +recopy: + len = strncpy_from_user(kname, filename, max); if (unlikely(len < 0)) { err = ERR_PTR(len); goto error; } + /* + * Uh-oh. We have a name that's approaching PATH_MAX. Allocate a + * separate struct filename so we can dedicate the entire + * names_cache allocation for the pathname, and re-do the copy from + * userland. + */ + if (len == EMBEDDED_NAME_MAX && max == EMBEDDED_NAME_MAX) { + kname = (char *)result; + + result = kzalloc(sizeof(*result), GFP_KERNEL); + if (!result) { + err = ERR_PTR(-ENOMEM); + result = (struct filename *)kname; + goto error; + } + result->name = kname; + result->separate = true; + max = PATH_MAX; + goto recopy; + } + /* The empty path is special. */ if (unlikely(!len)) { if (empty) @@ -163,15 +192,15 @@ getname_flags(const char __user *filename, int flags, int *empty) } err = ERR_PTR(-ENAMETOOLONG); - if (likely(len < PATH_MAX)) { - audit_getname(result); - return result; - } + if (unlikely(len >= PATH_MAX)) + goto error; + + result->uptr = filename; + audit_getname(result); + return result; error: - __putname(kname); -error_free_name: - kfree(result); + final_putname(result); return err; } diff --git a/include/linux/fs.h b/include/linux/fs.h index 4aa7160a51ce..65fbf571023f 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2201,6 +2201,7 @@ struct filename { const char *name; /* pointer to actual string */ const __user char *uptr; /* original userland pointer */ struct audit_names *aname; + bool separate; /* should "name" be freed? */ }; extern int do_truncate(struct dentry *, loff_t start, unsigned int time_attrs, -- cgit v1.2.3-58-ga151