| /* SPDX-License-Identifier: LGPL-2.1 OR MIT */ | |
| /* | |
| * string function definitions for NOLIBC | |
| * Copyright (C) 2017-2021 Willy Tarreau <w@1wt.eu> | |
| */ | |
| /* make sure to include all global symbols */ | |
| #include "nolibc.h" | |
| #ifndef _NOLIBC_STRING_H | |
| #define _NOLIBC_STRING_H | |
| #include "arch.h" | |
| #include "std.h" | |
| static void *malloc(size_t len); | |
| /* | |
| * As much as possible, please keep functions alphabetically sorted. | |
| */ | |
| static __attribute__((unused)) | |
| int memcmp(const void *s1, const void *s2, size_t n) | |
| { | |
| size_t ofs = 0; | |
| int c1 = 0; | |
| while (ofs < n && !(c1 = ((unsigned char *)s1)[ofs] - ((unsigned char *)s2)[ofs])) { | |
| ofs++; | |
| } | |
| return c1; | |
| } | |
| #ifndef NOLIBC_ARCH_HAS_MEMMOVE | |
| /* might be ignored by the compiler without -ffreestanding, then found as | |
| * missing. | |
| */ | |
| void *memmove(void *dst, const void *src, size_t len); | |
| __attribute__((weak,unused,section(".text.nolibc_memmove"))) | |
| void *memmove(void *dst, const void *src, size_t len) | |
| { | |
| size_t dir, pos; | |
| pos = len; | |
| dir = -1; | |
| if (dst < src) { | |
| pos = -1; | |
| dir = 1; | |
| } | |
| while (len) { | |
| pos += dir; | |
| ((char *)dst)[pos] = ((const char *)src)[pos]; | |
| len--; | |
| } | |
| return dst; | |
| } | |
| #endif /* #ifndef NOLIBC_ARCH_HAS_MEMMOVE */ | |
| #ifndef NOLIBC_ARCH_HAS_MEMCPY | |
| /* must be exported, as it's used by libgcc on ARM */ | |
| void *memcpy(void *dst, const void *src, size_t len); | |
| __attribute__((weak,unused,section(".text.nolibc_memcpy"))) | |
| void *memcpy(void *dst, const void *src, size_t len) | |
| { | |
| size_t pos = 0; | |
| while (pos < len) { | |
| ((char *)dst)[pos] = ((const char *)src)[pos]; | |
| pos++; | |
| } | |
| return dst; | |
| } | |
| #endif /* #ifndef NOLIBC_ARCH_HAS_MEMCPY */ | |
| #ifndef NOLIBC_ARCH_HAS_MEMSET | |
| /* might be ignored by the compiler without -ffreestanding, then found as | |
| * missing. | |
| */ | |
| void *memset(void *dst, int b, size_t len); | |
| __attribute__((weak,unused,section(".text.nolibc_memset"))) | |
| void *memset(void *dst, int b, size_t len) | |
| { | |
| char *p = dst; | |
| while (len--) { | |
| /* prevent gcc from recognizing memset() here */ | |
| __asm__ volatile(""); | |
| *(p++) = b; | |
| } | |
| return dst; | |
| } | |
| #endif /* #ifndef NOLIBC_ARCH_HAS_MEMSET */ | |
| #ifndef NOLIBC_ARCH_HAS_MEMCHR | |
| static __attribute__((unused)) | |
| void *memchr(const void *s, int c, size_t len) | |
| { | |
| char *p = (char *)s; | |
| while (len--) { | |
| if (*p == (char)c) | |
| return p; | |
| p++; | |
| } | |
| return NULL; | |
| } | |
| #endif /* #ifndef NOLIBC_ARCH_HAS_MEMCHR */ | |
| static __attribute__((unused)) | |
| char *strchr(const char *s, int c) | |
| { | |
| while (*s) { | |
| if (*s == (char)c) | |
| return (char *)s; | |
| s++; | |
| } | |
| return NULL; | |
| } | |
| static __attribute__((unused)) | |
| int strcmp(const char *a, const char *b) | |
| { | |
| unsigned int c; | |
| int diff; | |
| while (!(diff = (unsigned char)*a++ - (c = (unsigned char)*b++)) && c) | |
| ; | |
| return diff; | |
| } | |
| static __attribute__((unused)) | |
| char *strcpy(char *dst, const char *src) | |
| { | |
| char *ret = dst; | |
| while ((*dst++ = *src++)); | |
| return ret; | |
| } | |
| /* this function is only used with arguments that are not constants or when | |
| * it's not known because optimizations are disabled. Note that gcc 12 | |
| * recognizes an strlen() pattern and replaces it with a jump to strlen(), | |
| * thus itself, hence the asm() statement below that's meant to disable this | |
| * confusing practice. | |
| */ | |
| size_t strlen(const char *str); | |
| __attribute__((weak,unused,section(".text.nolibc_strlen"))) | |
| size_t strlen(const char *str) | |
| { | |
| size_t len; | |
| for (len = 0; str[len]; len++) | |
| __asm__(""); | |
| return len; | |
| } | |
| /* do not trust __builtin_constant_p() at -O0, as clang will emit a test and | |
| * the two branches, then will rely on an external definition of strlen(). | |
| */ | |
| #if defined(__OPTIMIZE__) | |
| #define nolibc_strlen(x) strlen(x) | |
| #define strlen(str) ({ \ | |
| __builtin_constant_p((str)) ? \ | |
| __builtin_strlen((str)) : \ | |
| nolibc_strlen((str)); \ | |
| }) | |
| #endif | |
| static __attribute__((unused)) | |
| size_t strnlen(const char *str, size_t maxlen) | |
| { | |
| size_t len; | |
| for (len = 0; (len < maxlen) && str[len]; len++); | |
| return len; | |
| } | |
| static __attribute__((unused)) | |
| char *strdup(const char *str) | |
| { | |
| size_t len; | |
| char *ret; | |
| len = strlen(str); | |
| ret = malloc(len + 1); | |
| if (__builtin_expect(ret != NULL, 1)) | |
| memcpy(ret, str, len + 1); | |
| return ret; | |
| } | |
| static __attribute__((unused)) | |
| char *strndup(const char *str, size_t maxlen) | |
| { | |
| size_t len; | |
| char *ret; | |
| len = strnlen(str, maxlen); | |
| ret = malloc(len + 1); | |
| if (__builtin_expect(ret != NULL, 1)) { | |
| memcpy(ret, str, len); | |
| ret[len] = '\0'; | |
| } | |
| return ret; | |
| } | |
| static __attribute__((unused)) | |
| size_t strlcat(char *dst, const char *src, size_t size) | |
| { | |
| size_t len = strnlen(dst, size); | |
| /* | |
| * We want len < size-1. But as size is unsigned and can wrap | |
| * around, we use len + 1 instead. | |
| */ | |
| while (len + 1 < size) { | |
| dst[len] = *src; | |
| if (*src == '\0') | |
| break; | |
| len++; | |
| src++; | |
| } | |
| if (len < size) | |
| dst[len] = '\0'; | |
| while (*src++) | |
| len++; | |
| return len; | |
| } | |
| static __attribute__((unused)) | |
| size_t strlcpy(char *dst, const char *src, size_t size) | |
| { | |
| size_t len; | |
| for (len = 0; len < size; len++) { | |
| dst[len] = src[len]; | |
| if (!dst[len]) | |
| return len; | |
| } | |
| if (size) | |
| dst[size-1] = '\0'; | |
| while (src[len]) | |
| len++; | |
| return len; | |
| } | |
| static __attribute__((unused)) | |
| char *strncat(char *dst, const char *src, size_t size) | |
| { | |
| char *orig = dst; | |
| while (*dst) | |
| dst++; | |
| while (size && (*dst = *src)) { | |
| src++; | |
| dst++; | |
| size--; | |
| } | |
| *dst = 0; | |
| return orig; | |
| } | |
| static __attribute__((unused)) | |
| int strncmp(const char *a, const char *b, size_t size) | |
| { | |
| unsigned int c; | |
| int diff = 0; | |
| while (size-- && | |
| !(diff = (unsigned char)*a++ - (c = (unsigned char)*b++)) && c) | |
| ; | |
| return diff; | |
| } | |
| static __attribute__((unused)) | |
| char *strncpy(char *dst, const char *src, size_t size) | |
| { | |
| size_t len; | |
| for (len = 0; len < size; len++) | |
| if ((dst[len] = *src)) | |
| src++; | |
| return dst; | |
| } | |
| static __attribute__((unused)) | |
| char *strrchr(const char *s, int c) | |
| { | |
| const char *ret = NULL; | |
| while (*s) { | |
| if (*s == (char)c) | |
| ret = s; | |
| s++; | |
| } | |
| return (char *)ret; | |
| } | |
| static __attribute__((unused)) | |
| char *strstr(const char *haystack, const char *needle) | |
| { | |
| size_t len_haystack, len_needle; | |
| len_needle = strlen(needle); | |
| if (!len_needle) | |
| return NULL; | |
| len_haystack = strlen(haystack); | |
| while (len_haystack >= len_needle) { | |
| if (!memcmp(haystack, needle, len_needle)) | |
| return (char *)haystack; | |
| haystack++; | |
| len_haystack--; | |
| } | |
| return NULL; | |
| } | |
| static __attribute__((unused)) | |
| int tolower(int c) | |
| { | |
| if (c >= 'A' && c <= 'Z') | |
| return c - 'A' + 'a'; | |
| return c; | |
| } | |
| static __attribute__((unused)) | |
| int toupper(int c) | |
| { | |
| if (c >= 'a' && c <= 'z') | |
| return c - 'a' + 'A'; | |
| return c; | |
| } | |
| #endif /* _NOLIBC_STRING_H */ |