feat(strings): strcasecmp/ffs/bcopy/bzero

This commit is contained in:
2026-09-03 20:54:23 -04:00
parent b2fbde0fc2
commit ce92aa2b6d
3 changed files with 632 additions and 0 deletions
+202
View File
@@ -0,0 +1,202 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <strings.h>
#if VLIBC_LEVEL_GE(2)
/*
* vlibc — <strings.h> legacy functions (todo 10).
*
* The whole file self-gates at VLIBC_LEVEL >= 2 (the build wiring pass may
* compile it unconditionally): at level 1 this translation unit produces
* nothing, because every function here is XSI/BSD legacy and none is
* POSIX.1-2008 base.
*
* bcopy/bzero/bcmp delegate to the project's own memmove/memset/memcmp
* (satisfied from the archive at link time) — bcopy MUST NOT be a bare
* memcpy alias, because its contract includes overlapping regions.
* index/rindex are written as direct byte scans (own code, not strchr
* wrappers) so their documented unsigned char conversion is explicit.
*/
/*
* Fold an ASCII uppercase letter to lowercase; every other byte passes
* through unchanged. Kept local and locale-free: the BSD comparisons must
* never depend on ctype tables or the active locale.
*/
static inline unsigned char
fold_ascii_byte(unsigned char c)
{
if (c >= 'A' && c <= 'Z')
{
return (unsigned char)(c + ('a' - 'A'));
}
return c;
}
/*
* Compare s1 and s2 byte-wise ignoring case. Equal-folded runs are skipped
* with a raw-byte fast path; the first differing byte pair is compared
* folded, so "A" and "a" order as equal and the returned sign is that of
* the folded difference. The scan stops at the first NUL in either string.
*/
int
strcasecmp(const char *s1, const char *s2)
{
const unsigned char *l = (const unsigned char *)s1;
const unsigned char *r = (const unsigned char *)s2;
for (; *l == *r; l++, r++)
{
if (*l == '\0')
{
return 0;
}
}
return (int)fold_ascii_byte(*l) - (int)fold_ascii_byte(*r);
}
/*
* Compare at most n bytes of s1 and s2 as strcasecmp does, stopping early
* at the first folded difference or the first NUL. When the comparison
* ends at a NUL, the shorter (equal-prefix) string sorts first.
*/
int
strncasecmp(const char *s1, const char *s2, size_t n)
{
const unsigned char *l = (const unsigned char *)s1;
const unsigned char *r = (const unsigned char *)s2;
while (n != 0 && *l != '\0' && *r != '\0')
{
const unsigned char fl = fold_ascii_byte(*l);
const unsigned char fr = fold_ascii_byte(*r);
if (fl != fr)
{
return (int)fl - (int)fr;
}
l++;
r++;
n--;
}
/* n bytes compared: equal so far. */
if (n == 0)
{
return 0;
}
/* A NUL ended the scan: the one that is NUL here is the shorter. */
return (int)*l - (int)*r;
}
/*
* Return the 1-based index of the least significant set bit of i, or 0
* when i is 0. The zero case is guarded because __builtin_ctz is
* undefined for a zero argument.
*/
int
ffs(int i)
{
return i == 0 ? 0 : __builtin_ctz((unsigned int)i) + 1;
}
int
ffsl(long i)
{
return i == 0 ? 0 : __builtin_ctzl((unsigned long)i) + 1;
}
int
ffsll(long long i)
{
return i == 0 ? 0 : __builtin_ctzll((unsigned long long)i) + 1;
}
/*
* Copy n bytes from src to dst with memmove semantics: the regions may
* overlap and the copy behaves as if through a temporary. The argument
* order (src, dst) is the reverse of memmove's — kept as such because it
* is the BSD contract.
*/
void
bcopy(const void *src, void *dst, size_t n)
{
(void)memmove(dst, src, n);
}
/*
* Fill n bytes at s with zero.
*/
void
bzero(void *s, size_t n)
{
(void)memset(s, 0, n);
}
/*
* Compare the first n bytes of s1 and s2 as unsigned char, like memcmp;
* return 0 when equal, nonzero otherwise. A NUL byte does not end the
* comparison.
*/
int
bcmp(const void *s1, const void *s2, size_t n)
{
return memcmp(s1, s2, n);
}
/*
* Return a pointer to the first occurrence of c (converted to unsigned
* char) in s, or NULL when absent. The terminating NUL is part of the
* string, so index(s, '\0') returns a pointer to it. Legacy name for
* strchr.
*/
char *
index(const char *s, int c)
{
const unsigned char cc = (unsigned char)c;
for (;; s++)
{
if ((unsigned char)*s == cc)
{
return (char *)s;
}
if (*s == '\0')
{
return NULL;
}
}
}
/*
* Return a pointer to the last occurrence of c (converted to unsigned
* char) in s, or NULL when absent. The terminating NUL is part of the
* string, so rindex(s, '\0') returns a pointer to it. Legacy name for
* strrchr.
*/
char *
rindex(const char *s, int c)
{
const unsigned char cc = (unsigned char)c;
const char *last = NULL;
for (;; s++)
{
if ((unsigned char)*s == cc)
{
last = s;
}
if (*s == '\0')
{
return (char *)last;
}
}
}
#endif /* VLIBC_LEVEL_GE(2) */