487 lines
21 KiB
C
487 lines
21 KiB
C
|
|
/*
|
||
|
|
* string.h wrappers -- TODO.md section 3.1.
|
||
|
|
*
|
||
|
|
* This is the section akbasic needed most and could not have: across its source
|
||
|
|
* it calls strlen 37 times, strcmp 16, strncpy 15 and strstr once, every one of
|
||
|
|
* them raw because there was nothing here to call instead. Ten of those sites
|
||
|
|
* are the same idiom written out by hand -- a length check, then strncpy, then
|
||
|
|
* an explicit NUL -- which is exactly the "truncation reported as an error
|
||
|
|
* rather than silently accepted" that TODO.md 3.1 asks for.
|
||
|
|
*
|
||
|
|
* Two conventions run through the whole file.
|
||
|
|
*
|
||
|
|
* The copying functions take the size of the destination, and the ones named
|
||
|
|
* after libc functions that do not take one take it anyway. strcpy(3) and
|
||
|
|
* strcat(3) cannot be called safely without knowing how much room the
|
||
|
|
* destination has, and a wrapper that accepts the same arguments as strcpy
|
||
|
|
* would be an error-handling wrapper around a buffer overflow. Truncation is
|
||
|
|
* AKERR_OUTOFBOUNDS: it is the failure these functions exist to report, and
|
||
|
|
* nothing is written to the destination when it happens, so a caller who
|
||
|
|
* ignores the status does not get a half-copied string either. aksl_strncpy
|
||
|
|
* also always terminates, which strncpy(3) famously does not.
|
||
|
|
*
|
||
|
|
* The searching functions answer through an out-param and treat "not found" as
|
||
|
|
* a successful answer of NULL rather than as an error -- absent is an ordinary
|
||
|
|
* answer to "where is this", and a library that raised on it would have every
|
||
|
|
* caller handling a non-error as an error.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <akstdlib.h>
|
||
|
|
|
||
|
|
#include <ctype.h>
|
||
|
|
#include <errno.h>
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <string.h>
|
||
|
|
#include <strings.h>
|
||
|
|
|
||
|
|
#include "aksl_internal.h"
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
/* Length */
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
|
||
|
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_strlen(const char *s, size_t *dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(e);
|
||
|
|
FAIL_ZERO_RETURN(e, s, AKERR_NULLPOINTER, "s=%p, dest=%p", (void *)s, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "s=%p, dest=%p", (void *)s, (void *)dest);
|
||
|
|
*dest = strlen(s);
|
||
|
|
SUCCEED_RETURN(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* strnlen(3) stops at maxlen whether or not it found a terminator, so a result
|
||
|
|
* equal to maxlen means "at least this long" rather than "this long". That
|
||
|
|
* ambiguity is the reason to use it -- it is how you measure a buffer that may
|
||
|
|
* not be terminated -- so it is reported rather than hidden: *dest is the
|
||
|
|
* length and the call succeeds either way.
|
||
|
|
*/
|
||
|
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_strnlen(const char *s, size_t maxlen, size_t *dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(e);
|
||
|
|
FAIL_ZERO_RETURN(e, s, AKERR_NULLPOINTER, "s=%p, dest=%p", (void *)s, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "s=%p, dest=%p", (void *)s, (void *)dest);
|
||
|
|
*dest = strnlen(s, maxlen);
|
||
|
|
SUCCEED_RETURN(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
/* Copying and concatenation */
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Bounded copy. dstsize is the whole destination buffer, terminator included,
|
||
|
|
* so `char buf[64]` pairs with `sizeof(buf)`. A source that does not fit is
|
||
|
|
* AKERR_OUTOFBOUNDS and the destination is left as an empty string rather than
|
||
|
|
* as a truncated one -- a caller who ignores the error gets nothing, which is
|
||
|
|
* far easier to notice than a plausible-looking prefix.
|
||
|
|
*/
|
||
|
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_strcpy(char *dst, size_t dstsize, const char *src)
|
||
|
|
{
|
||
|
|
size_t len = 0;
|
||
|
|
PREPARE_ERROR(e);
|
||
|
|
FAIL_ZERO_RETURN(e, dst, AKERR_NULLPOINTER, "dst=%p, src=%p", (void *)dst, (void *)src);
|
||
|
|
FAIL_ZERO_RETURN(e, src, AKERR_NULLPOINTER, "dst=%p, src=%p", (void *)dst, (void *)src);
|
||
|
|
FAIL_ZERO_RETURN(e, dstsize, AKERR_VALUE, "dstsize=0 leaves no room for a terminator");
|
||
|
|
dst[0] = '\0';
|
||
|
|
len = strlen(src);
|
||
|
|
FAIL_NONZERO_RETURN(e, (len >= dstsize), AKERR_OUTOFBOUNDS,
|
||
|
|
"source is %zu bytes and the destination holds %zu including the terminator",
|
||
|
|
len, dstsize);
|
||
|
|
memcpy(dst, src, len + 1);
|
||
|
|
SUCCEED_RETURN(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* At most n bytes of src, and always terminated.
|
||
|
|
*
|
||
|
|
* strncpy(3) does two surprising things that this does not: it leaves the
|
||
|
|
* destination unterminated when the source is at least n bytes long, and it
|
||
|
|
* pads the remainder with NULs when the source is shorter, which turns a short
|
||
|
|
* copy into a full-length write. Here n bounds how much of the source is
|
||
|
|
* considered, dstsize bounds the write, and the result is always a C string.
|
||
|
|
*/
|
||
|
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_strncpy(char *dst, size_t dstsize, const char *src, size_t n)
|
||
|
|
{
|
||
|
|
size_t len = 0;
|
||
|
|
PREPARE_ERROR(e);
|
||
|
|
FAIL_ZERO_RETURN(e, dst, AKERR_NULLPOINTER, "dst=%p, src=%p", (void *)dst, (void *)src);
|
||
|
|
FAIL_ZERO_RETURN(e, src, AKERR_NULLPOINTER, "dst=%p, src=%p", (void *)dst, (void *)src);
|
||
|
|
FAIL_ZERO_RETURN(e, dstsize, AKERR_VALUE, "dstsize=0 leaves no room for a terminator");
|
||
|
|
dst[0] = '\0';
|
||
|
|
len = strnlen(src, n);
|
||
|
|
FAIL_NONZERO_RETURN(e, (len >= dstsize), AKERR_OUTOFBOUNDS,
|
||
|
|
"%zu bytes to copy and the destination holds %zu including the terminator",
|
||
|
|
len, dstsize);
|
||
|
|
memcpy(dst, src, len);
|
||
|
|
dst[len] = '\0';
|
||
|
|
SUCCEED_RETURN(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Bounded append. dstsize is again the whole buffer, so the room actually
|
||
|
|
* available is dstsize minus what is already in there. The destination must
|
||
|
|
* already be a terminated string within dstsize; if it is not, that is
|
||
|
|
* AKERR_VALUE rather than a walk off the end looking for a NUL that is not
|
||
|
|
* there.
|
||
|
|
*/
|
||
|
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_strcat(char *dst, size_t dstsize, const char *src)
|
||
|
|
{
|
||
|
|
size_t used = 0;
|
||
|
|
size_t len = 0;
|
||
|
|
PREPARE_ERROR(e);
|
||
|
|
FAIL_ZERO_RETURN(e, dst, AKERR_NULLPOINTER, "dst=%p, src=%p", (void *)dst, (void *)src);
|
||
|
|
FAIL_ZERO_RETURN(e, src, AKERR_NULLPOINTER, "dst=%p, src=%p", (void *)dst, (void *)src);
|
||
|
|
FAIL_ZERO_RETURN(e, dstsize, AKERR_VALUE, "dstsize=0 leaves no room for a terminator");
|
||
|
|
used = strnlen(dst, dstsize);
|
||
|
|
FAIL_NONZERO_RETURN(e, (used == dstsize), AKERR_VALUE,
|
||
|
|
"destination is not terminated within its %zu bytes", dstsize);
|
||
|
|
len = strlen(src);
|
||
|
|
FAIL_NONZERO_RETURN(e, (used + len >= dstsize), AKERR_OUTOFBOUNDS,
|
||
|
|
"%zu bytes in use plus %zu to append exceeds the %zu-byte destination",
|
||
|
|
used, len, dstsize);
|
||
|
|
memcpy(dst + used, src, len + 1);
|
||
|
|
SUCCEED_RETURN(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_strncat(char *dst, size_t dstsize, const char *src, size_t n)
|
||
|
|
{
|
||
|
|
size_t used = 0;
|
||
|
|
size_t len = 0;
|
||
|
|
PREPARE_ERROR(e);
|
||
|
|
FAIL_ZERO_RETURN(e, dst, AKERR_NULLPOINTER, "dst=%p, src=%p", (void *)dst, (void *)src);
|
||
|
|
FAIL_ZERO_RETURN(e, src, AKERR_NULLPOINTER, "dst=%p, src=%p", (void *)dst, (void *)src);
|
||
|
|
FAIL_ZERO_RETURN(e, dstsize, AKERR_VALUE, "dstsize=0 leaves no room for a terminator");
|
||
|
|
used = strnlen(dst, dstsize);
|
||
|
|
FAIL_NONZERO_RETURN(e, (used == dstsize), AKERR_VALUE,
|
||
|
|
"destination is not terminated within its %zu bytes", dstsize);
|
||
|
|
len = strnlen(src, n);
|
||
|
|
FAIL_NONZERO_RETURN(e, (used + len >= dstsize), AKERR_OUTOFBOUNDS,
|
||
|
|
"%zu bytes in use plus %zu to append exceeds the %zu-byte destination",
|
||
|
|
used, len, dstsize);
|
||
|
|
memcpy(dst + used, src, len);
|
||
|
|
dst[used + len] = '\0';
|
||
|
|
SUCCEED_RETURN(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
/* Duplication */
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
|
||
|
|
/* *dest is the caller's, to release with aksl_free or aksl_freep. */
|
||
|
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_strdup(const char *s, char **dest)
|
||
|
|
{
|
||
|
|
char *copy = NULL;
|
||
|
|
PREPARE_ERROR(e);
|
||
|
|
FAIL_ZERO_RETURN(e, s, AKERR_NULLPOINTER, "s=%p, dest=%p", (void *)s, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "s=%p, dest=%p", (void *)s, (void *)dest);
|
||
|
|
*dest = NULL;
|
||
|
|
errno = 0;
|
||
|
|
copy = strdup(s);
|
||
|
|
FAIL_ZERO_RETURN(e, copy, AKSL_ERRNO_OR(ENOMEM), "%zu bytes", strlen(s) + 1);
|
||
|
|
*dest = copy;
|
||
|
|
SUCCEED_RETURN(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_strndup(const char *s, size_t n, char **dest)
|
||
|
|
{
|
||
|
|
char *copy = NULL;
|
||
|
|
PREPARE_ERROR(e);
|
||
|
|
FAIL_ZERO_RETURN(e, s, AKERR_NULLPOINTER, "s=%p, dest=%p", (void *)s, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "s=%p, dest=%p", (void *)s, (void *)dest);
|
||
|
|
*dest = NULL;
|
||
|
|
errno = 0;
|
||
|
|
copy = strndup(s, n);
|
||
|
|
FAIL_ZERO_RETURN(e, copy, AKSL_ERRNO_OR(ENOMEM), "%zu bytes", n + 1);
|
||
|
|
*dest = copy;
|
||
|
|
SUCCEED_RETURN(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
/* Comparison */
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
|
||
|
|
/*
|
||
|
|
* The comparison result goes through *dest because the return value is spoken
|
||
|
|
* for by the error context. The sign is the usual one: negative, zero or
|
||
|
|
* positive as a sorts before, equal to, or after b.
|
||
|
|
*/
|
||
|
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_strcmp(const char *a, const char *b, int *dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(e);
|
||
|
|
FAIL_ZERO_RETURN(e, a, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, b, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||
|
|
*dest = strcmp(a, b);
|
||
|
|
SUCCEED_RETURN(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_strncmp(const char *a, const char *b, size_t n, int *dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(e);
|
||
|
|
FAIL_ZERO_RETURN(e, a, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, b, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||
|
|
*dest = strncmp(a, b, n);
|
||
|
|
SUCCEED_RETURN(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Case-insensitive comparison. akbasic folds case by hand at three sites --
|
||
|
|
* twice with an open-coded loop and once through <ctype.h> -- because BASIC
|
||
|
|
* verb and function names are case-insensitive while variable names are not.
|
||
|
|
* All three of those are this call.
|
||
|
|
*/
|
||
|
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_strcasecmp(const char *a, const char *b, int *dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(e);
|
||
|
|
FAIL_ZERO_RETURN(e, a, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, b, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||
|
|
*dest = strcasecmp(a, b);
|
||
|
|
SUCCEED_RETURN(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_strncasecmp(const char *a, const char *b, size_t n, int *dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(e);
|
||
|
|
FAIL_ZERO_RETURN(e, a, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, b, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||
|
|
*dest = strncasecmp(a, b, n);
|
||
|
|
SUCCEED_RETURN(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Locale-aware collation. strcoll(3) has no error return of its own, so a
|
||
|
|
* malformed multibyte sequence in the current locale shows up only as errno
|
||
|
|
* being set -- which is why errno is cleared first and consulted after.
|
||
|
|
*/
|
||
|
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_strcoll(const char *a, const char *b, int *dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(e);
|
||
|
|
FAIL_ZERO_RETURN(e, a, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, b, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "a=%p, b=%p, dest=%p", (void *)a, (void *)b, (void *)dest);
|
||
|
|
errno = 0;
|
||
|
|
*dest = strcoll(a, b);
|
||
|
|
FAIL_NONZERO_RETURN(e, errno, errno, "strcoll failed in the current locale");
|
||
|
|
SUCCEED_RETURN(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
/* Searching */
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Every function in this section writes NULL to *dest and succeeds when there
|
||
|
|
* is nothing to find. "Absent" is an answer, not a failure; raising on it would
|
||
|
|
* make every caller handle a non-error as an error, and the pool slot that
|
||
|
|
* error consumed would be pure waste.
|
||
|
|
*
|
||
|
|
* *dest points into the caller's own string, so it lives exactly as long as the
|
||
|
|
* string does and must not be freed.
|
||
|
|
*/
|
||
|
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_strchr(const char *s, int c, char **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(e);
|
||
|
|
FAIL_ZERO_RETURN(e, s, AKERR_NULLPOINTER, "s=%p, dest=%p", (void *)s, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "s=%p, dest=%p", (void *)s, (void *)dest);
|
||
|
|
*dest = strchr(s, c);
|
||
|
|
SUCCEED_RETURN(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_strrchr(const char *s, int c, char **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(e);
|
||
|
|
FAIL_ZERO_RETURN(e, s, AKERR_NULLPOINTER, "s=%p, dest=%p", (void *)s, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "s=%p, dest=%p", (void *)s, (void *)dest);
|
||
|
|
*dest = strrchr(s, c);
|
||
|
|
SUCCEED_RETURN(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_strstr(const char *haystack, const char *needle, char **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(e);
|
||
|
|
FAIL_ZERO_RETURN(e, haystack, AKERR_NULLPOINTER, "haystack=%p, needle=%p, dest=%p",
|
||
|
|
(void *)haystack, (void *)needle, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, needle, AKERR_NULLPOINTER, "haystack=%p, needle=%p, dest=%p",
|
||
|
|
(void *)haystack, (void *)needle, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "haystack=%p, needle=%p, dest=%p",
|
||
|
|
(void *)haystack, (void *)needle, (void *)dest);
|
||
|
|
*dest = strstr(haystack, needle);
|
||
|
|
SUCCEED_RETURN(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Case-insensitive search. strcasestr(3) is a GNU extension that also exists on
|
||
|
|
* the BSDs and musl, but it is not in any standard, so it is open-coded here
|
||
|
|
* rather than depending on _GNU_SOURCE reaching every consumer's build. The
|
||
|
|
* naive scan is the same complexity as glibc's fallback and this is not the hot
|
||
|
|
* path in anything.
|
||
|
|
*/
|
||
|
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_strcasestr(const char *haystack, const char *needle, char **dest)
|
||
|
|
{
|
||
|
|
size_t hlen = 0;
|
||
|
|
size_t nlen = 0;
|
||
|
|
size_t i = 0;
|
||
|
|
PREPARE_ERROR(e);
|
||
|
|
FAIL_ZERO_RETURN(e, haystack, AKERR_NULLPOINTER, "haystack=%p, needle=%p, dest=%p",
|
||
|
|
(void *)haystack, (void *)needle, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, needle, AKERR_NULLPOINTER, "haystack=%p, needle=%p, dest=%p",
|
||
|
|
(void *)haystack, (void *)needle, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "haystack=%p, needle=%p, dest=%p",
|
||
|
|
(void *)haystack, (void *)needle, (void *)dest);
|
||
|
|
*dest = NULL;
|
||
|
|
nlen = strlen(needle);
|
||
|
|
/* An empty needle matches at the start, as strstr(3) has it. */
|
||
|
|
if ( nlen == 0 ) {
|
||
|
|
*dest = (char *)haystack;
|
||
|
|
SUCCEED_RETURN(e);
|
||
|
|
}
|
||
|
|
hlen = strlen(haystack);
|
||
|
|
if ( nlen <= hlen ) {
|
||
|
|
for ( i = 0; i <= hlen - nlen; i++ ) {
|
||
|
|
if ( strncasecmp(haystack + i, needle, nlen) == 0 ) {
|
||
|
|
*dest = (char *)(haystack + i);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
SUCCEED_RETURN(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_strpbrk(const char *s, const char *accept, char **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(e);
|
||
|
|
FAIL_ZERO_RETURN(e, s, AKERR_NULLPOINTER, "s=%p, accept=%p, dest=%p",
|
||
|
|
(void *)s, (void *)accept, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, accept, AKERR_NULLPOINTER, "s=%p, accept=%p, dest=%p",
|
||
|
|
(void *)s, (void *)accept, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "s=%p, accept=%p, dest=%p",
|
||
|
|
(void *)s, (void *)accept, (void *)dest);
|
||
|
|
*dest = strpbrk(s, accept);
|
||
|
|
SUCCEED_RETURN(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_strspn(const char *s, const char *accept, size_t *dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(e);
|
||
|
|
FAIL_ZERO_RETURN(e, s, AKERR_NULLPOINTER, "s=%p, accept=%p, dest=%p",
|
||
|
|
(void *)s, (void *)accept, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, accept, AKERR_NULLPOINTER, "s=%p, accept=%p, dest=%p",
|
||
|
|
(void *)s, (void *)accept, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "s=%p, accept=%p, dest=%p",
|
||
|
|
(void *)s, (void *)accept, (void *)dest);
|
||
|
|
*dest = strspn(s, accept);
|
||
|
|
SUCCEED_RETURN(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_strcspn(const char *s, const char *reject, size_t *dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(e);
|
||
|
|
FAIL_ZERO_RETURN(e, s, AKERR_NULLPOINTER, "s=%p, reject=%p, dest=%p",
|
||
|
|
(void *)s, (void *)reject, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, reject, AKERR_NULLPOINTER, "s=%p, reject=%p, dest=%p",
|
||
|
|
(void *)s, (void *)reject, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "s=%p, reject=%p, dest=%p",
|
||
|
|
(void *)s, (void *)reject, (void *)dest);
|
||
|
|
*dest = strcspn(s, reject);
|
||
|
|
SUCCEED_RETURN(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
/* Tokenising */
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
|
||
|
|
/*
|
||
|
|
* The reentrant tokeniser only. strtok(3) keeps its state in a hidden static,
|
||
|
|
* which makes two interleaved tokenisations silently corrupt each other and
|
||
|
|
* makes any use from a thread a bug, so it is not wrapped here at all -- see
|
||
|
|
* the note on threads in README.md.
|
||
|
|
*
|
||
|
|
* Running out of tokens is success with *dest NULL, for the same reason a failed
|
||
|
|
* search is: it is how the loop ends, not a fault.
|
||
|
|
*/
|
||
|
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_strtok_r(char *str, const char *delim, char **saveptr, char **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(e);
|
||
|
|
FAIL_ZERO_RETURN(e, delim, AKERR_NULLPOINTER, "delim=%p, saveptr=%p, dest=%p",
|
||
|
|
(void *)delim, (void *)saveptr, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, saveptr, AKERR_NULLPOINTER, "delim=%p, saveptr=%p, dest=%p",
|
||
|
|
(void *)delim, (void *)saveptr, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "delim=%p, saveptr=%p, dest=%p",
|
||
|
|
(void *)delim, (void *)saveptr, (void *)dest);
|
||
|
|
*dest = strtok_r(str, delim, saveptr);
|
||
|
|
SUCCEED_RETURN(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* strsep(3) differs from strtok_r in returning empty tokens between adjacent
|
||
|
|
* delimiters, which is what you want for parsing "a::b" as three fields rather
|
||
|
|
* than two. It advances *stringp itself and sets it to NULL when done.
|
||
|
|
*/
|
||
|
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_strsep(char **stringp, const char *delim, char **dest)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(e);
|
||
|
|
FAIL_ZERO_RETURN(e, stringp, AKERR_NULLPOINTER, "stringp=%p, delim=%p, dest=%p",
|
||
|
|
(void *)stringp, (void *)delim, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, delim, AKERR_NULLPOINTER, "stringp=%p, delim=%p, dest=%p",
|
||
|
|
(void *)stringp, (void *)delim, (void *)dest);
|
||
|
|
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "stringp=%p, delim=%p, dest=%p",
|
||
|
|
(void *)stringp, (void *)delim, (void *)dest);
|
||
|
|
*dest = strsep(stringp, delim);
|
||
|
|
SUCCEED_RETURN(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
/* errno messages */
|
||
|
|
/* ---------------------------------------------------------------------- */
|
||
|
|
|
||
|
|
/*
|
||
|
|
* The message for a status, into the caller's own buffer.
|
||
|
|
*
|
||
|
|
* Deliberately not built on strerror_r(3). There are two incompatible functions
|
||
|
|
* by that name -- the XSI one returns int, the GNU one returns char * and may
|
||
|
|
* not touch the buffer at all -- and which one a translation unit gets depends
|
||
|
|
* on feature-test macros that a consumer of this library cannot influence from
|
||
|
|
* in here. strerror(3) itself is not thread-safe, so that is no way out either.
|
||
|
|
*
|
||
|
|
* libakerror already carries the full errno name table (its generrno.sh builds
|
||
|
|
* one at configure time), and its registry also knows the names of this
|
||
|
|
* library's own non-errno statuses -- AKERR_NULLPOINTER, AKERR_ITERATOR_BREAK
|
||
|
|
* and the rest -- which strerror_r could never name. So the lookup goes there,
|
||
|
|
* and a status neither table recognises comes back as its own number rather
|
||
|
|
* than as the bare string "Unknown Error", which tells the reader nothing.
|
||
|
|
*
|
||
|
|
* The message is truncated to nothing rather than to a prefix if it does not
|
||
|
|
* fit, on the same principle as aksl_strcpy above.
|
||
|
|
*/
|
||
|
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_strerror(int status, char *buf, size_t buflen)
|
||
|
|
{
|
||
|
|
const char *name = NULL;
|
||
|
|
int needed = 0;
|
||
|
|
PREPARE_ERROR(e);
|
||
|
|
FAIL_ZERO_RETURN(e, buf, AKERR_NULLPOINTER, "buf=%p", (void *)buf);
|
||
|
|
FAIL_ZERO_RETURN(e, buflen, AKERR_VALUE, "buflen=0 leaves no room for a terminator");
|
||
|
|
buf[0] = '\0';
|
||
|
|
name = akerr_name_for_status(status, NULL);
|
||
|
|
/*
|
||
|
|
* akerr_name_for_status never returns NULL; it returns this exact string
|
||
|
|
* for anything it does not have a name for.
|
||
|
|
*/
|
||
|
|
if ( name == NULL || strcmp(name, "Unknown Error") == 0 ) {
|
||
|
|
needed = snprintf(buf, buflen, "Unknown status %d", status);
|
||
|
|
} else {
|
||
|
|
needed = snprintf(buf, buflen, "%s", name);
|
||
|
|
}
|
||
|
|
FAIL_NONZERO_RETURN(e, (needed < 0), AKERR_IO, "could not format status %d", status);
|
||
|
|
if ( (size_t)needed >= buflen ) {
|
||
|
|
buf[0] = '\0';
|
||
|
|
FAIL_RETURN(e, AKERR_OUTOFBOUNDS,
|
||
|
|
"the message for status %d needs %d bytes and the buffer holds %zu",
|
||
|
|
status, needed + 1, buflen);
|
||
|
|
}
|
||
|
|
SUCCEED_RETURN(e);
|
||
|
|
}
|