Files
libakstdlib/src/stream.c

552 lines
22 KiB
C
Raw Normal View History

Wrap the strings, streams and collections the wishlist asked for TODO.md section 3.1's high-priority list and section 3.6's data structures. This is the surface akbasic went without: it makes 10 calls into this library and 116 to raw libc, and 69 of those 116 are strlen, strcmp, strncpy and strstr. Three new translation units, because src/stdlib.c covering four times what it did would stop being readable: src/string.c lengths, bounded copy and concatenation, duplication, comparison including the case-insensitive forms, searching, the reentrant tokenisers, and a status message that knows this library's own statuses as well as errno's. src/stream.c positioning, flushing and buffering, character and line I/O, stream state, freopen/fdopen/tmpfile, formatted input, and the file operations. src/collections.c the list functions that were missing, a head/tail container so append is O(1), a binary search tree, FNV-1a, a fixed-capacity hash map and a growable string buffer. Two conventions run through all of it. The copying functions take the destination size even where the libc function they are named for does not, because strcpy(3) cannot be called safely without it, and truncation is an error that writes nothing rather than a plausible prefix -- this is the idiom akbasic writes out by hand at ten sites. The searching functions treat "not found" as a successful answer of NULL, because absent is an answer and raising on it would make every caller handle a non-error. The hash map is akbasic's src/symtab.c generalised: open-addressed with linear probing over a caller-supplied slot array, keys copied into fixed slots so the map owns them, tombstones on delete so a removal cannot cut a probe chain, and a refusal rather than a resize when full. Tests: 16 binaries, green under the normal and sanitizer builds. The scanf wrappers take the number of conversions the caller expects, since comparing scanf(3)'s return against that by hand is the check everyone eventually forgets. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:32:35 -04:00
/*
* stdio.h wrappers beyond fopen/fread/fwrite/fclose -- TODO.md section 3.1.
*
* Positioning, flushing, character and line I/O, stream state, formatted input,
* and the file-level operations that go with them.
*
* The recurring theme, and the reason most of these are worth wrapping at all,
* is that stdio reports failure through a return value that is easy to mistake
* for data. ftell(3) returns -1L, fgetc(3) returns EOF, fgets(3) returns NULL,
* and sscanf(3) returns a count that a caller has to compare against the number
* of conversions it wrote out by hand. Every one of those is a silent failure
* waiting for someone to forget the check once.
*
* Where a function has a genuine "nothing more to read" outcome -- fgets at the
* end of a file, getline at EOF -- that is AKERR_EOF rather than AKERR_IO, so a
* read loop can tell the end of its input from the failure of its input.
*/
#include <akstdlib.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include "aksl_internal.h"
/* ---------------------------------------------------------------------- */
/* Positioning */
/* ---------------------------------------------------------------------- */
akerr_ErrorContext AKERR_NOIGNORE *aksl_fseek(FILE *stream, long offset, int whence)
{
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, stream, AKERR_NULLPOINTER, "stream=%p", (void *)stream);
errno = 0;
FAIL_NONZERO_RETURN(e, fseek(stream, offset, whence), AKSL_ERRNO_OR(AKERR_IO),
"fseek to offset %ld whence %d", offset, whence);
SUCCEED_RETURN(e);
}
/*
* ftell(3) reports failure as -1L, which is also a perfectly ordinary thing for
* an arithmetic type to hold. Out through *dest, with the failure as a status.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_ftell(FILE *stream, long *dest)
{
long pos = 0;
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, stream, AKERR_NULLPOINTER, "stream=%p, dest=%p", (void *)stream, (void *)dest);
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "stream=%p, dest=%p", (void *)stream, (void *)dest);
*dest = 0;
errno = 0;
pos = ftell(stream);
FAIL_NONZERO_RETURN(e, (pos == -1L), AKSL_ERRNO_OR(AKERR_IO), "ftell failed");
*dest = pos;
SUCCEED_RETURN(e);
}
/*
* rewind(3) is the one positioning call with no error return at all: it is
* fseek(stream, 0, SEEK_SET) with the result thrown away, and it clears the
* error indicator on the way past so even that evidence is gone. This is the
* fseek, so a rewind that cannot happen is reported rather than assumed.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_rewind(FILE *stream)
{
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, stream, AKERR_NULLPOINTER, "stream=%p", (void *)stream);
errno = 0;
FAIL_NONZERO_RETURN(e, fseek(stream, 0L, SEEK_SET), AKSL_ERRNO_OR(AKERR_IO),
"rewind failed");
clearerr(stream);
SUCCEED_RETURN(e);
}
akerr_ErrorContext AKERR_NOIGNORE *aksl_fseeko(FILE *stream, off_t offset, int whence)
{
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, stream, AKERR_NULLPOINTER, "stream=%p", (void *)stream);
errno = 0;
FAIL_NONZERO_RETURN(e, fseeko(stream, offset, whence), AKSL_ERRNO_OR(AKERR_IO),
"fseeko to offset %lld whence %d", (long long)offset, whence);
SUCCEED_RETURN(e);
}
akerr_ErrorContext AKERR_NOIGNORE *aksl_ftello(FILE *stream, off_t *dest)
{
off_t pos = 0;
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, stream, AKERR_NULLPOINTER, "stream=%p, dest=%p", (void *)stream, (void *)dest);
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "stream=%p, dest=%p", (void *)stream, (void *)dest);
*dest = 0;
errno = 0;
pos = ftello(stream);
FAIL_NONZERO_RETURN(e, (pos == (off_t)-1), AKSL_ERRNO_OR(AKERR_IO), "ftello failed");
*dest = pos;
SUCCEED_RETURN(e);
}
/*
* fgetpos/fsetpos carry an opaque fpos_t rather than a byte offset, which is
* what makes them the right pair for a stream in a multibyte locale where a
* byte offset is not enough to restore the conversion state.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_fgetpos(FILE *stream, fpos_t *pos)
{
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, stream, AKERR_NULLPOINTER, "stream=%p, pos=%p", (void *)stream, (void *)pos);
FAIL_ZERO_RETURN(e, pos, AKERR_NULLPOINTER, "stream=%p, pos=%p", (void *)stream, (void *)pos);
errno = 0;
FAIL_NONZERO_RETURN(e, fgetpos(stream, pos), AKSL_ERRNO_OR(AKERR_IO), "fgetpos failed");
SUCCEED_RETURN(e);
}
akerr_ErrorContext AKERR_NOIGNORE *aksl_fsetpos(FILE *stream, const fpos_t *pos)
{
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, stream, AKERR_NULLPOINTER, "stream=%p, pos=%p", (void *)stream, (void *)pos);
FAIL_ZERO_RETURN(e, pos, AKERR_NULLPOINTER, "stream=%p, pos=%p", (void *)stream, (void *)pos);
errno = 0;
FAIL_NONZERO_RETURN(e, fsetpos(stream, pos), AKSL_ERRNO_OR(AKERR_IO), "fsetpos failed");
SUCCEED_RETURN(e);
}
/* ---------------------------------------------------------------------- */
/* Flushing and buffering */
/* ---------------------------------------------------------------------- */
/*
* NULL is legal here and means "every output stream", which is fflush(3)'s own
* documented behaviour and genuinely useful before a fork or an abort -- so
* unlike almost everywhere else in this library, a NULL argument is not an
* error. It is also the only way to find out that buffered data could not be
* written before the process goes away.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_fflush(FILE *stream)
{
PREPARE_ERROR(e);
errno = 0;
FAIL_NONZERO_RETURN(e, fflush(stream), AKSL_ERRNO_OR(AKERR_IO),
"fflush failed and the buffered data is lost");
SUCCEED_RETURN(e);
}
akerr_ErrorContext AKERR_NOIGNORE *aksl_setvbuf(FILE *stream, char *buf, int mode, size_t size)
{
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, stream, AKERR_NULLPOINTER, "stream=%p", (void *)stream);
errno = 0;
FAIL_NONZERO_RETURN(e, setvbuf(stream, buf, mode, size), AKSL_ERRNO_OR(AKERR_VALUE),
"setvbuf mode %d size %zu", mode, size);
SUCCEED_RETURN(e);
}
/* ---------------------------------------------------------------------- */
/* Character and line I/O */
/* ---------------------------------------------------------------------- */
/*
* fgetc(3) folds three outcomes into one int: a byte, the end of the file, and
* a read error, the last two both spelled EOF. Split apart here -- *dest is the
* byte, AKERR_EOF is the end, and anything else is the error -- so a read loop
* can be written without the ferror/feof dance at the bottom of it.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_fgetc(FILE *stream, int *dest)
{
int c = 0;
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, stream, AKERR_NULLPOINTER, "stream=%p, dest=%p", (void *)stream, (void *)dest);
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "stream=%p, dest=%p", (void *)stream, (void *)dest);
*dest = 0;
errno = 0;
c = fgetc(stream);
if ( c == EOF ) {
FAIL_NONZERO_RETURN(e, ferror(stream), AKSL_ERRNO_OR(AKERR_IO), "fgetc failed");
FAIL_RETURN(e, AKERR_EOF, "end of stream");
}
*dest = c;
SUCCEED_RETURN(e);
}
akerr_ErrorContext AKERR_NOIGNORE *aksl_fputc(int c, FILE *stream)
{
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, stream, AKERR_NULLPOINTER, "stream=%p", (void *)stream);
errno = 0;
FAIL_NONZERO_RETURN(e, (fputc(c, stream) == EOF), AKSL_ERRNO_OR(AKERR_IO),
"fputc of 0x%02x failed", (unsigned)c & 0xffu);
SUCCEED_RETURN(e);
}
/* Pushes one byte back so the next read returns it. One byte is all that is
* guaranteed; a second ungetc without a read in between may well fail. */
akerr_ErrorContext AKERR_NOIGNORE *aksl_ungetc(int c, FILE *stream)
{
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, stream, AKERR_NULLPOINTER, "stream=%p", (void *)stream);
errno = 0;
FAIL_NONZERO_RETURN(e, (ungetc(c, stream) == EOF), AKSL_ERRNO_OR(AKERR_IO),
"ungetc of 0x%02x failed", (unsigned)c & 0xffu);
SUCCEED_RETURN(e);
}
/*
* fgets(3) into a bounded buffer, with the length read reported and the three
* outcomes separated. A line too long for the buffer is *not* an error -- it is
* a short read that leaves the rest of the line in the stream, which is how
* fgets works and how a caller reading fixed-size chunks wants it -- but
* *len_out lets the caller notice, because a full buffer with no trailing
* newline is exactly that case.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_fgets(char *s, size_t size, FILE *stream, size_t *len_out)
{
char *result = NULL;
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, len_out, AKERR_NULLPOINTER, "s=%p, stream=%p, len_out=%p",
(void *)s, (void *)stream, (void *)len_out);
*len_out = 0;
FAIL_ZERO_RETURN(e, s, AKERR_NULLPOINTER, "s=%p, stream=%p, len_out=%p",
(void *)s, (void *)stream, (void *)len_out);
FAIL_ZERO_RETURN(e, stream, AKERR_NULLPOINTER, "s=%p, stream=%p, len_out=%p",
(void *)s, (void *)stream, (void *)len_out);
/* fgets takes an int, and a size that does not fit one is a caller error. */
FAIL_NONZERO_RETURN(e, (size == 0 || size > (size_t)INT_MAX), AKERR_VALUE,
"size %zu is outside the range fgets accepts", size);
s[0] = '\0';
errno = 0;
result = fgets(s, (int)size, stream);
if ( result == NULL ) {
FAIL_NONZERO_RETURN(e, ferror(stream), AKSL_ERRNO_OR(AKERR_IO), "fgets failed");
FAIL_RETURN(e, AKERR_EOF, "end of stream");
}
*len_out = strlen(s);
SUCCEED_RETURN(e);
}
akerr_ErrorContext AKERR_NOIGNORE *aksl_fputs(const char *s, FILE *stream)
{
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, s, AKERR_NULLPOINTER, "s=%p, stream=%p", (void *)s, (void *)stream);
FAIL_ZERO_RETURN(e, stream, AKERR_NULLPOINTER, "s=%p, stream=%p", (void *)s, (void *)stream);
errno = 0;
FAIL_NONZERO_RETURN(e, (fputs(s, stream) == EOF), AKSL_ERRNO_OR(AKERR_IO), "fputs failed");
SUCCEED_RETURN(e);
}
/*
* getline(3) grows *lineptr as needed, so the caller starts with
* `char *line = NULL; size_t cap = 0;` and releases the buffer with aksl_free
* once the loop is done -- not once per line. *len_out is the length read,
* which is what tells an embedded NUL from the end of the line; strlen cannot.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_getline(char **lineptr, size_t *n, FILE *stream, size_t *len_out)
{
ssize_t got = 0;
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, len_out, AKERR_NULLPOINTER, "lineptr=%p, n=%p, stream=%p, len_out=%p",
(void *)lineptr, (void *)n, (void *)stream, (void *)len_out);
*len_out = 0;
FAIL_ZERO_RETURN(e, lineptr, AKERR_NULLPOINTER, "lineptr=%p, n=%p, stream=%p, len_out=%p",
(void *)lineptr, (void *)n, (void *)stream, (void *)len_out);
FAIL_ZERO_RETURN(e, n, AKERR_NULLPOINTER, "lineptr=%p, n=%p, stream=%p, len_out=%p",
(void *)lineptr, (void *)n, (void *)stream, (void *)len_out);
FAIL_ZERO_RETURN(e, stream, AKERR_NULLPOINTER, "lineptr=%p, n=%p, stream=%p, len_out=%p",
(void *)lineptr, (void *)n, (void *)stream, (void *)len_out);
errno = 0;
got = getline(lineptr, n, stream);
if ( got < 0 ) {
FAIL_NONZERO_RETURN(e, ferror(stream), AKSL_ERRNO_OR(AKERR_IO), "getline failed");
FAIL_RETURN(e, AKERR_EOF, "end of stream");
}
*len_out = (size_t)got;
SUCCEED_RETURN(e);
}
akerr_ErrorContext AKERR_NOIGNORE *aksl_getdelim(char **lineptr, size_t *n, int delim,
FILE *stream, size_t *len_out)
{
ssize_t got = 0;
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, len_out, AKERR_NULLPOINTER, "lineptr=%p, n=%p, stream=%p, len_out=%p",
(void *)lineptr, (void *)n, (void *)stream, (void *)len_out);
*len_out = 0;
FAIL_ZERO_RETURN(e, lineptr, AKERR_NULLPOINTER, "lineptr=%p, n=%p, stream=%p, len_out=%p",
(void *)lineptr, (void *)n, (void *)stream, (void *)len_out);
FAIL_ZERO_RETURN(e, n, AKERR_NULLPOINTER, "lineptr=%p, n=%p, stream=%p, len_out=%p",
(void *)lineptr, (void *)n, (void *)stream, (void *)len_out);
FAIL_ZERO_RETURN(e, stream, AKERR_NULLPOINTER, "lineptr=%p, n=%p, stream=%p, len_out=%p",
(void *)lineptr, (void *)n, (void *)stream, (void *)len_out);
errno = 0;
got = getdelim(lineptr, n, delim, stream);
if ( got < 0 ) {
FAIL_NONZERO_RETURN(e, ferror(stream), AKSL_ERRNO_OR(AKERR_IO), "getdelim failed");
FAIL_RETURN(e, AKERR_EOF, "end of stream");
}
*len_out = (size_t)got;
SUCCEED_RETURN(e);
}
/* ---------------------------------------------------------------------- */
/* Stream state */
/* ---------------------------------------------------------------------- */
/*
* feof and ferror answer through an out-param, exposed so that a caller never
* has to reach into a FILE * itself. They are thin, and that is the point: the
* NULL check is the whole value.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_feof(FILE *stream, int *dest)
{
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, stream, AKERR_NULLPOINTER, "stream=%p, dest=%p", (void *)stream, (void *)dest);
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "stream=%p, dest=%p", (void *)stream, (void *)dest);
*dest = feof(stream);
SUCCEED_RETURN(e);
}
akerr_ErrorContext AKERR_NOIGNORE *aksl_ferror(FILE *stream, int *dest)
{
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, stream, AKERR_NULLPOINTER, "stream=%p, dest=%p", (void *)stream, (void *)dest);
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "stream=%p, dest=%p", (void *)stream, (void *)dest);
*dest = ferror(stream);
SUCCEED_RETURN(e);
}
akerr_ErrorContext AKERR_NOIGNORE *aksl_clearerr(FILE *stream)
{
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, stream, AKERR_NULLPOINTER, "stream=%p", (void *)stream);
clearerr(stream);
SUCCEED_RETURN(e);
}
akerr_ErrorContext AKERR_NOIGNORE *aksl_fileno(FILE *stream, int *dest)
{
int fd = -1;
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, stream, AKERR_NULLPOINTER, "stream=%p, dest=%p", (void *)stream, (void *)dest);
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "stream=%p, dest=%p", (void *)stream, (void *)dest);
*dest = -1;
errno = 0;
fd = fileno(stream);
FAIL_NONZERO_RETURN(e, (fd < 0), AKSL_ERRNO_OR(EBADF), "stream has no descriptor");
*dest = fd;
SUCCEED_RETURN(e);
}
/* ---------------------------------------------------------------------- */
/* Opening by other means */
/* ---------------------------------------------------------------------- */
akerr_ErrorContext AKERR_NOIGNORE *aksl_freopen(const char *pathname, const char *mode,
FILE *stream, FILE **fp)
{
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, fp, AKERR_NULLPOINTER, "pathname=%p, mode=%p, stream=%p, fp=%p",
(void *)pathname, (void *)mode, (void *)stream, (void *)fp);
*fp = NULL;
FAIL_ZERO_RETURN(e, mode, AKERR_NULLPOINTER, "pathname=%p, mode=%p, stream=%p, fp=%p",
(void *)pathname, (void *)mode, (void *)stream, (void *)fp);
FAIL_ZERO_RETURN(e, stream, AKERR_NULLPOINTER, "pathname=%p, mode=%p, stream=%p, fp=%p",
(void *)pathname, (void *)mode, (void *)stream, (void *)fp);
/*
* pathname NULL is legal and means "reopen the same file with a new mode",
* which is the one thing freopen can do that fopen cannot -- so it is not
* checked, unlike aksl_fopen's.
*/
errno = 0;
*fp = freopen(pathname, mode, stream);
FAIL_ZERO_RETURN(e, *fp, AKSL_ERRNO_OR(AKERR_IO), "%s", pathname != NULL ? pathname : "(same file)");
SUCCEED_RETURN(e);
}
akerr_ErrorContext AKERR_NOIGNORE *aksl_fdopen(int fd, const char *mode, FILE **fp)
{
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, fp, AKERR_NULLPOINTER, "mode=%p, fp=%p", (void *)mode, (void *)fp);
*fp = NULL;
FAIL_ZERO_RETURN(e, mode, AKERR_NULLPOINTER, "mode=%p, fp=%p", (void *)mode, (void *)fp);
FAIL_NONZERO_RETURN(e, (fd < 0), AKERR_VALUE, "fd=%d is not a descriptor", fd);
errno = 0;
*fp = fdopen(fd, mode);
FAIL_ZERO_RETURN(e, *fp, AKSL_ERRNO_OR(AKERR_IO), "fd=%d mode=%s", fd, mode);
SUCCEED_RETURN(e);
}
akerr_ErrorContext AKERR_NOIGNORE *aksl_tmpfile(FILE **fp)
{
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, fp, AKERR_NULLPOINTER, "fp=%p", (void *)fp);
*fp = NULL;
errno = 0;
*fp = tmpfile();
FAIL_ZERO_RETURN(e, *fp, AKSL_ERRNO_OR(AKERR_IO), "could not create a temporary file");
SUCCEED_RETURN(e);
}
/* ---------------------------------------------------------------------- */
/* Formatted input */
/* ---------------------------------------------------------------------- */
/*
* The scanf family's return value is the number of conversions that succeeded,
* which the caller is expected to compare against the number it wrote in the
* format string -- by hand, from memory, at every call site. Getting that wrong
* leaves the unassigned arguments holding whatever they held before, which for
* the usual uninitialised local is anything at all.
*
* So `expected` is an argument: say how many conversions must succeed, and
* anything less is AKERR_VALUE with both counts in the message. Pass 0 to opt
* out and read the count from *assigned yourself.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_vsscanf(const char *str, const char *format,
int expected, int *assigned, va_list args)
{
int got = 0;
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, assigned, AKERR_NULLPOINTER, "str=%p, format=%p, assigned=%p",
(void *)str, (void *)format, (void *)assigned);
*assigned = 0;
FAIL_ZERO_RETURN(e, str, AKERR_NULLPOINTER, "str=%p, format=%p, assigned=%p",
(void *)str, (void *)format, (void *)assigned);
FAIL_ZERO_RETURN(e, format, AKERR_NULLPOINTER, "str=%p, format=%p, assigned=%p",
(void *)str, (void *)format, (void *)assigned);
errno = 0;
got = vsscanf(str, format, args);
FAIL_NONZERO_RETURN(e, (got == EOF), AKSL_ERRNO_OR(AKERR_VALUE),
"no conversions were possible on \"%s\"", str);
*assigned = got;
FAIL_NONZERO_RETURN(e, (expected > 0 && got < expected), AKERR_VALUE,
"%d of %d conversions succeeded on \"%s\"", got, expected, str);
SUCCEED_RETURN(e);
}
akerr_ErrorContext AKERR_NOIGNORE *aksl_sscanf(const char *str, const char *format,
int expected, int *assigned, ...)
{
va_list args;
akerr_ErrorContext *raised = NULL;
va_start(args, assigned);
raised = aksl_vsscanf(str, format, expected, assigned, args);
va_end(args);
return raised;
}
akerr_ErrorContext AKERR_NOIGNORE *aksl_vfscanf(FILE *stream, const char *format,
int expected, int *assigned, va_list args)
{
int got = 0;
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, assigned, AKERR_NULLPOINTER, "stream=%p, format=%p, assigned=%p",
(void *)stream, (void *)format, (void *)assigned);
*assigned = 0;
FAIL_ZERO_RETURN(e, stream, AKERR_NULLPOINTER, "stream=%p, format=%p, assigned=%p",
(void *)stream, (void *)format, (void *)assigned);
FAIL_ZERO_RETURN(e, format, AKERR_NULLPOINTER, "stream=%p, format=%p, assigned=%p",
(void *)stream, (void *)format, (void *)assigned);
errno = 0;
got = vfscanf(stream, format, args);
if ( got == EOF ) {
FAIL_NONZERO_RETURN(e, ferror(stream), AKSL_ERRNO_OR(AKERR_IO), "fscanf failed");
FAIL_RETURN(e, AKERR_EOF, "end of stream before any conversion");
}
*assigned = got;
FAIL_NONZERO_RETURN(e, (expected > 0 && got < expected), AKERR_VALUE,
"%d of %d conversions succeeded", got, expected);
SUCCEED_RETURN(e);
}
akerr_ErrorContext AKERR_NOIGNORE *aksl_fscanf(FILE *stream, const char *format,
int expected, int *assigned, ...)
{
va_list args;
akerr_ErrorContext *raised = NULL;
va_start(args, assigned);
raised = aksl_vfscanf(stream, format, expected, assigned, args);
va_end(args);
return raised;
}
/* ---------------------------------------------------------------------- */
/* Files */
/* ---------------------------------------------------------------------- */
akerr_ErrorContext AKERR_NOIGNORE *aksl_remove(const char *pathname)
{
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, pathname, AKERR_NULLPOINTER, "pathname=%p", (void *)pathname);
errno = 0;
FAIL_NONZERO_RETURN(e, remove(pathname), AKSL_ERRNO_OR(AKERR_IO), "%s", pathname);
SUCCEED_RETURN(e);
}
akerr_ErrorContext AKERR_NOIGNORE *aksl_rename(const char *oldpath, const char *newpath)
{
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, oldpath, AKERR_NULLPOINTER, "oldpath=%p, newpath=%p",
(void *)oldpath, (void *)newpath);
FAIL_ZERO_RETURN(e, newpath, AKERR_NULLPOINTER, "oldpath=%p, newpath=%p",
(void *)oldpath, (void *)newpath);
errno = 0;
FAIL_NONZERO_RETURN(e, rename(oldpath, newpath), AKSL_ERRNO_OR(AKERR_IO),
"%s -> %s", oldpath, newpath);
SUCCEED_RETURN(e);
}
/*
* mkstemp and mkdtemp both rewrite the template in place, so the template must
* be a writable buffer ending in exactly six X characters -- a string literal
* is a segfault, and that is checked here rather than left to the kernel. The
* caller owns the resulting file or directory, including removing it.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_mkstemp(char *template_, int *fd)
{
size_t len = 0;
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, fd, AKERR_NULLPOINTER, "template=%p, fd=%p", (void *)template_, (void *)fd);
*fd = -1;
FAIL_ZERO_RETURN(e, template_, AKERR_NULLPOINTER, "template=%p, fd=%p",
(void *)template_, (void *)fd);
len = strlen(template_);
FAIL_NONZERO_RETURN(e, (len < 6 || strcmp(template_ + len - 6, "XXXXXX") != 0),
AKERR_VALUE,
"template \"%s\" must end in six literal X characters", template_);
errno = 0;
*fd = mkstemp(template_);
FAIL_NONZERO_RETURN(e, (*fd < 0), AKSL_ERRNO_OR(AKERR_IO), "template \"%s\"", template_);
SUCCEED_RETURN(e);
}
akerr_ErrorContext AKERR_NOIGNORE *aksl_mkdtemp(char *template_)
{
size_t len = 0;
PREPARE_ERROR(e);
FAIL_ZERO_RETURN(e, template_, AKERR_NULLPOINTER, "template=%p", (void *)template_);
len = strlen(template_);
FAIL_NONZERO_RETURN(e, (len < 6 || strcmp(template_ + len - 6, "XXXXXX") != 0),
AKERR_VALUE,
"template \"%s\" must end in six literal X characters", template_);
errno = 0;
FAIL_ZERO_RETURN(e, mkdtemp(template_), AKSL_ERRNO_OR(AKERR_IO),
"template \"%s\"", template_);
SUCCEED_RETURN(e);
}