552 lines
22 KiB
C
552 lines
22 KiB
C
|
|
/*
|
||
|
|
* 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);
|
||
|
|
}
|