Files
libakstdlib/src/stream.c
Tachikoma 55d986c631
All checks were successful
libakstdlib CI Build / cmake_build (push) Successful in 5m53s
libakstdlib CI Build / sanitizers (push) Successful in 2m55s
libakstdlib CI Build / coverage (push) Successful in 2m48s
libakstdlib CI Build / mutation_test (push) Successful in 12m39s
Drop the TODO.md section numbers from 26 files
Eighty-five comments cited section numbers -- 1.1, 2.2.6, 3.6 -- from a
numbering the file had already abandoned before the move to the tracker. They
label completed work, so the pointer was the only wrong part.

The citation is removed and the sentence kept, which is what issue #27
recommended: these are labels, not references, and a label carrying a
version-dependent pointer goes stale again at the next reorganisation. Where a
pointer earns its place it names what actually holds the content now --
UPGRADING.md for the confirmed defects, libakerror #15 for the target
namespacing, issue #7 for the mutation survivors.

README.md and akstdlib.h sent readers to TODO.md for 'what is still open';
they name the tracker.

Verified: cmake --build build && ctest --test-dir build, 19/19.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-08-02 22:01:27 -04:00

571 lines
23 KiB
C

/*
* stdio.h wrappers beyond fopen/fread/fwrite/fclose.
*
* 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;
}
/* stdin, for symmetry with aksl_printf writing to stdout. */
akerr_ErrorContext AKERR_NOIGNORE *aksl_scanf(const char *format,
int expected, int *assigned, ...)
{
va_list args;
akerr_ErrorContext *raised = NULL;
va_start(args, assigned);
raised = aksl_vfscanf(stdin, format, expected, assigned, args);
va_end(args);
return raised;
}
akerr_ErrorContext AKERR_NOIGNORE *aksl_vscanf(const char *format, int expected,
int *assigned, va_list args)
{
return aksl_vfscanf(stdin, format, expected, assigned, args);
}
/* ---------------------------------------------------------------------- */
/* 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);
}