/* * 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 #include #include #include #include #include #include #include #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); }