| From: Chris Metcalf <cmetcalf@ezchip.com> |
| Date: Wed, 29 Apr 2015 12:52:04 -0400 |
| Subject: string: provide strscpy() |
| |
| commit 30035e45753b708e7d47a98398500ca005e02b86 upstream. |
| |
| The strscpy() API is intended to be used instead of strlcpy(), |
| and instead of most uses of strncpy(). |
| |
| - Unlike strlcpy(), it doesn't read from memory beyond (src + size). |
| |
| - Unlike strlcpy() or strncpy(), the API provides an easy way to check |
| for destination buffer overflow: an -E2BIG error return value. |
| |
| - The provided implementation is robust in the face of the source |
| buffer being asynchronously changed during the copy, unlike the |
| current implementation of strlcpy(). |
| |
| - Unlike strncpy(), the destination buffer will be NUL-terminated |
| if the string in the source buffer is too long. |
| |
| - Also unlike strncpy(), the destination buffer will not be updated |
| beyond the NUL termination, avoiding strncpy's behavior of zeroing |
| the entire tail end of the destination buffer. (A memset() after |
| the strscpy() can be used if this behavior is desired.) |
| |
| - The implementation should be reasonably performant on all |
| platforms since it uses the asm/word-at-a-time.h API rather than |
| simple byte copy. Kernel-to-kernel string copy is not considered |
| to be performance critical in any case. |
| |
| Signed-off-by: Chris Metcalf <cmetcalf@ezchip.com> |
| [bwh: Backported to 3.16: adjust context] |
| Signed-off-by: Ben Hutchings <ben@decadent.org.uk> |
| --- |
| include/linux/string.h | 3 ++ |
| lib/string.c | 88 ++++++++++++++++++++++++++++++++++++++++++ |
| 2 files changed, 91 insertions(+) |
| |
| --- a/include/linux/string.h |
| +++ b/include/linux/string.h |
| @@ -25,6 +25,9 @@ extern char * strncpy(char *,const char |
| #ifndef __HAVE_ARCH_STRLCPY |
| size_t strlcpy(char *, const char *, size_t); |
| #endif |
| +#ifndef __HAVE_ARCH_STRSCPY |
| +ssize_t __must_check strscpy(char *, const char *, size_t); |
| +#endif |
| #ifndef __HAVE_ARCH_STRCAT |
| extern char * strcat(char *, const char *); |
| #endif |
| --- a/lib/string.c |
| +++ b/lib/string.c |
| @@ -27,6 +27,10 @@ |
| #include <linux/bug.h> |
| #include <linux/errno.h> |
| |
| +#include <asm/byteorder.h> |
| +#include <asm/word-at-a-time.h> |
| +#include <asm/page.h> |
| + |
| #ifndef __HAVE_ARCH_STRNICMP |
| /** |
| * strnicmp - Case insensitive, length-limited string comparison |
| @@ -160,6 +164,90 @@ size_t strlcpy(char *dest, const char *s |
| EXPORT_SYMBOL(strlcpy); |
| #endif |
| |
| +#ifndef __HAVE_ARCH_STRSCPY |
| +/** |
| + * strscpy - Copy a C-string into a sized buffer |
| + * @dest: Where to copy the string to |
| + * @src: Where to copy the string from |
| + * @count: Size of destination buffer |
| + * |
| + * Copy the string, or as much of it as fits, into the dest buffer. |
| + * The routine returns the number of characters copied (not including |
| + * the trailing NUL) or -E2BIG if the destination buffer wasn't big enough. |
| + * The behavior is undefined if the string buffers overlap. |
| + * The destination buffer is always NUL terminated, unless it's zero-sized. |
| + * |
| + * Preferred to strlcpy() since the API doesn't require reading memory |
| + * from the src string beyond the specified "count" bytes, and since |
| + * the return value is easier to error-check than strlcpy()'s. |
| + * In addition, the implementation is robust to the string changing out |
| + * from underneath it, unlike the current strlcpy() implementation. |
| + * |
| + * Preferred to strncpy() since it always returns a valid string, and |
| + * doesn't unnecessarily force the tail of the destination buffer to be |
| + * zeroed. If the zeroing is desired, it's likely cleaner to use strscpy() |
| + * with an overflow test, then just memset() the tail of the dest buffer. |
| + */ |
| +ssize_t strscpy(char *dest, const char *src, size_t count) |
| +{ |
| + const struct word_at_a_time constants = WORD_AT_A_TIME_CONSTANTS; |
| + size_t max = count; |
| + long res = 0; |
| + |
| + if (count == 0) |
| + return -E2BIG; |
| + |
| +#ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS |
| + /* |
| + * If src is unaligned, don't cross a page boundary, |
| + * since we don't know if the next page is mapped. |
| + */ |
| + if ((long)src & (sizeof(long) - 1)) { |
| + size_t limit = PAGE_SIZE - ((long)src & (PAGE_SIZE - 1)); |
| + if (limit < max) |
| + max = limit; |
| + } |
| +#else |
| + /* If src or dest is unaligned, don't do word-at-a-time. */ |
| + if (((long) dest | (long) src) & (sizeof(long) - 1)) |
| + max = 0; |
| +#endif |
| + |
| + while (max >= sizeof(unsigned long)) { |
| + unsigned long c, data; |
| + |
| + c = *(unsigned long *)(src+res); |
| + *(unsigned long *)(dest+res) = c; |
| + if (has_zero(c, &data, &constants)) { |
| + data = prep_zero_mask(c, data, &constants); |
| + data = create_zero_mask(data); |
| + return res + find_zero(data); |
| + } |
| + res += sizeof(unsigned long); |
| + count -= sizeof(unsigned long); |
| + max -= sizeof(unsigned long); |
| + } |
| + |
| + while (count) { |
| + char c; |
| + |
| + c = src[res]; |
| + dest[res] = c; |
| + if (!c) |
| + return res; |
| + res++; |
| + count--; |
| + } |
| + |
| + /* Hit buffer length without finding a NUL; force NUL-termination. */ |
| + if (res) |
| + dest[res-1] = '\0'; |
| + |
| + return -E2BIG; |
| +} |
| +EXPORT_SYMBOL(strscpy); |
| +#endif |
| + |
| #ifndef __HAVE_ARCH_STRCAT |
| /** |
| * strcat - Append one %NUL-terminated string to another |