Compare commits
27 Commits
d5e5e95c61
...
todo-to-is
| Author | SHA1 | Date | |
|---|---|---|---|
|
51144038ce
|
|||
|
91a8896b18
|
|||
|
dbe1ccaf57
|
|||
|
669b2b395f
|
|||
|
f8425b8729
|
|||
|
cc5e7899bb
|
|||
|
3ad3994762
|
|||
|
7940276f87
|
|||
|
b104a07eb4
|
|||
|
125eeb2109
|
|||
|
98a0a8562d
|
|||
|
55eb0334c4
|
|||
|
fd71bcc67b
|
|||
|
95e5002512
|
|||
|
07c448508b
|
|||
|
a37ba3fb89
|
|||
|
437da2960b
|
|||
|
82c47ed773
|
|||
|
a87cbfb26d
|
|||
|
566004afd6
|
|||
|
11c04923f8
|
|||
|
01734f511b
|
|||
| 8003239116 | |||
|
80205d5c4f
|
|||
|
54fb44cd2b
|
|||
|
68009ea0e3
|
|||
|
3e296c3bff
|
@@ -21,7 +21,7 @@ jobs:
|
||||
# different libakerrors depending on where you built: the install went to
|
||||
# /usr/local, while the build below is top-level and so compiles
|
||||
# deps/libakerror at the pinned commit via add_subdirectory -- the
|
||||
# installed one was never actually linked against.
|
||||
# installed one was never actually linked against. TODO.md 2.3.
|
||||
#
|
||||
# The submodule wins. It is the version this repository pins, tests and
|
||||
# ships against, and a CI that tests a different one is testing something
|
||||
@@ -155,7 +155,7 @@ jobs:
|
||||
# new surface being argument validation whose mutants are frequently
|
||||
# equivalent. `errno = 0` deleted from a wrapper whose libc call always
|
||||
# sets errno cannot be distinguished by any test that could be written.
|
||||
# The survivors worth acting on are named in issue #7; raise the gate as
|
||||
# The survivors worth acting on are named in TODO.md; raise the gate as
|
||||
# they become assertions.
|
||||
- name: mutation testing
|
||||
run: |
|
||||
|
||||
10
AGENTS.md
10
AGENTS.md
@@ -14,7 +14,6 @@ reasoning. The implementation is split by domain:
|
||||
| `src/stdlib.c` | memory, formatted output, string-to-number, realpath, djb2, and the list/tree traversal entry points |
|
||||
| `src/string.c` | the `string.h` surface |
|
||||
| `src/stream.c` | `stdio.h` beyond open/read/write/close |
|
||||
| `src/dir.c` | directory stream open/read/rewind/close |
|
||||
| `src/collections.c` | list and tree operations, hash map, string buffer, FNV-1a |
|
||||
| `src/aksl_internal.h` | shared internals; not installed, not public |
|
||||
|
||||
@@ -77,10 +76,6 @@ return convention and the `PREPARE_ERROR` / `FAIL_*` / `SUCCEED_RETURN` pattern.
|
||||
Four conventions hold across the whole library, and a new wrapper that breaks one
|
||||
of them is wrong even if it compiles and passes:
|
||||
|
||||
- **Preserve the libc contract unless there is a compelling, documented reason
|
||||
not to.** libc behaviour is the standard to meet. This library changes only
|
||||
the error transport (to `akerror`) and, where libc returns a value, the result
|
||||
shape (through a caller-provided destination pointer).
|
||||
- **A NULL out-param is a caller error**, not "don't care".
|
||||
- **Finding nothing is success** -- searching functions write NULL or zero and
|
||||
return NULL.
|
||||
@@ -98,11 +93,6 @@ The build is `-Wall -Wextra` and CI adds `-Werror`. `-Wpedantic` is deliberately
|
||||
off: libakerror's `FAIL_*` macros trip "ISO C99 requires at least one argument
|
||||
for the ..." on their own expansion, not on anything at the call site.
|
||||
|
||||
**Do not wrap a libc function that cannot fail and provides no failure or
|
||||
operation-status result.** There is no `akerror` context to carry. A value such
|
||||
as `umask()`'s previous mask is not an operation-status result, so `umask()` is
|
||||
not a wrapper candidate.
|
||||
|
||||
## Testing Guidelines
|
||||
|
||||
Add a new test by creating `tests/test_mything.c` and adding `mything` to the
|
||||
|
||||
@@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.10)
|
||||
# akstdlibConfigVersion.cmake. Nothing else should spell a version number.
|
||||
#
|
||||
# 0.2.0, and the minor bump is an ABI break on purpose. Fixing the confirmed
|
||||
# defects listed in UPGRADING.md changed documented behaviour and, in five places,
|
||||
# defects in TODO.md 2.1 changed documented behaviour and, in five places,
|
||||
# signatures:
|
||||
#
|
||||
# aksl_realpath takes the destination's length; aksl_realpath_alloc is new
|
||||
@@ -54,7 +54,7 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
endif()
|
||||
|
||||
# Warnings. The only warning in the tree when these went on was the unused
|
||||
# `queue` parameter of aksl_tree_iterate, which UPGRADING.md had already
|
||||
# `queue` parameter of aksl_tree_iterate, which TODO.md 2.2.8 had already
|
||||
# recorded as a dead parameter -- so the cost of turning them on was one
|
||||
# already-known defect, and the cost of leaving them off was every future one.
|
||||
#
|
||||
@@ -63,7 +63,7 @@ endif()
|
||||
# requires at least one argument for the ...", which is a complaint about the
|
||||
# macro's shape rather than about anything at this call site. Every FAIL_* in
|
||||
# src/stdlib.c passes at least one argument regardless -- see the note in
|
||||
# libakerror issue #15 -- so the code is pedantic-clean; it is the expansion that is not.
|
||||
# TODO.md 2.3 -- so the code is pedantic-clean; it is the expansion that is not.
|
||||
if(CMAKE_C_COMPILER_ID MATCHES "^(GNU|Clang|AppleClang)$")
|
||||
add_compile_options(-Wall -Wextra)
|
||||
# CI turns this on. Locally it is off, because a warning that stops the build
|
||||
@@ -173,7 +173,7 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
||||
# configure at all. Rename the dependency's on the way past rather than
|
||||
# dropping it: its coverage script drives its own instrumented build tree, so
|
||||
# `cmake --build build-coverage --target akerror_coverage` still does the right
|
||||
# thing. Remove this once the dependency namespaces it upstream -- libakerror issue #15.
|
||||
# thing. Remove this once the dependency namespaces it upstream -- see TODO.md.
|
||||
function(add_custom_target _name)
|
||||
if(AKSL_SUPPRESS_ADD_TEST AND _name STREQUAL "coverage")
|
||||
_add_custom_target(akerror_coverage ${ARGN})
|
||||
@@ -207,8 +207,6 @@ add_library(akstdlib SHARED
|
||||
src/stdlib.c
|
||||
src/string.c
|
||||
src/stream.c
|
||||
src/stat.c
|
||||
src/dir.c
|
||||
src/collections.c
|
||||
)
|
||||
|
||||
@@ -309,7 +307,6 @@ install(FILES
|
||||
set(AKSL_TESTS
|
||||
collections
|
||||
convert
|
||||
dir
|
||||
format
|
||||
hashmap
|
||||
linkedlist
|
||||
@@ -320,7 +317,6 @@ set(AKSL_TESTS
|
||||
strbuf
|
||||
stream
|
||||
streamio
|
||||
stat
|
||||
strhash
|
||||
string
|
||||
strto
|
||||
@@ -333,7 +329,7 @@ set(AKSL_WILL_FAIL_TESTS
|
||||
|
||||
# Empty, and that is the news. It held four entries -- convert_strict,
|
||||
# list_append_chain, list_iterate_head and tree_iterate_break -- one for each
|
||||
# confirmed defect listed in UPGRADING.md. All four are fixed, so each of those files
|
||||
# confirmed defect in TODO.md 2.1. All four are fixed, so each of those files
|
||||
# was folded back into the test for the thing it was testing (tests/
|
||||
# test_convert.c, tests/test_linkedlist.c and tests/test_tree.c) where it now
|
||||
# has to keep passing rather than merely keep failing visibly.
|
||||
@@ -354,7 +350,7 @@ foreach(_test IN LISTS AKSL_TESTS AKSL_WILL_FAIL_TESTS AKSL_KNOWN_FAILING_TESTS)
|
||||
list(APPEND AKSL_TEST_TARGETS test_${_test})
|
||||
endforeach()
|
||||
|
||||
# Negative compile tests: the format attributes and AKERR_NOIGNORE.
|
||||
# Negative compile tests -- TODO.md 1.3 and 1.9.
|
||||
#
|
||||
# Two properties of this library are enforced by the compiler and by nothing
|
||||
# else: AKERR_NOIGNORE makes discarding a returned error context an error, and
|
||||
|
||||
@@ -8,8 +8,7 @@ through [libakerror](https://source.starfort.tech/andrew/libakerror)'s
|
||||
and `errno`. It also provides data structures built on the same convention.
|
||||
|
||||
Every entry point returns `akerr_ErrorContext *` and is marked `AKERR_NOIGNORE`.
|
||||
Outstanding work is in [the issue tracker](https://source.starfort.tech/andrew/libakstdlib/issues).
|
||||
See `TODO.md` for where the library stands and what is deliberately not wrapped, and `UPGRADING.md` if you are
|
||||
See `TODO.md` for the current state of the library and `UPGRADING.md` if you are
|
||||
coming from 0.1.0, which this release breaks.
|
||||
|
||||
## What it wraps
|
||||
|
||||
@@ -26,8 +26,7 @@
|
||||
* takes a slot from it on any failure path. See README.md.
|
||||
*
|
||||
* @see README.md for the deviations from libc semantics, UPGRADING.md if you are
|
||||
* coming from 0.1.0. Outstanding work is in the issue tracker;
|
||||
* TODO.md is the record of where the library stands.
|
||||
* coming from 0.1.0, and TODO.md for what is still open.
|
||||
*/
|
||||
|
||||
#ifndef _AKSTDLIB_H_
|
||||
@@ -63,7 +62,6 @@
|
||||
|
||||
/*
|
||||
* What this header needs in its own declarations, and no more:
|
||||
* dirent.h DIR, struct dirent
|
||||
* stdio.h FILE
|
||||
* stddef.h size_t
|
||||
* stdint.h uint32_t
|
||||
@@ -71,16 +69,12 @@
|
||||
*
|
||||
* It used to pull in stdlib.h and string.h as well, which nothing here needs and
|
||||
* which every consumer then got whether it wanted them or not. stddef.h in place
|
||||
* of stdlib.h is the same size_t at a fraction of the namespace.
|
||||
* of stdlib.h is the same size_t at a fraction of the namespace. TODO.md 2.2.16.
|
||||
*/
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/statvfs.h>
|
||||
/* off_t, for the aksl_fseeko/aksl_ftello pair. POSIX, like aksl_realpath. */
|
||||
#include <sys/types.h>
|
||||
|
||||
@@ -91,7 +85,7 @@ extern "C" {
|
||||
/*
|
||||
* Restore the compile-time format/argument checking that callers otherwise lose
|
||||
* by going through a variadic wrapper: without it, printf("%d", "str") is caught
|
||||
* and aksl_printf(&n, "%d", "str") is not.
|
||||
* and aksl_printf(&n, "%d", "str") is not. TODO.md 2.2.5.
|
||||
*
|
||||
* tests/negative/format_mismatch.c is a compile that must fail, which is what
|
||||
* proves these are still attached.
|
||||
@@ -813,151 +807,6 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_djb2_str(const char *str, uint32
|
||||
|
||||
/** @} */
|
||||
|
||||
/* ====================================================================== */
|
||||
/** @name File and filesystem metadata
|
||||
*
|
||||
* The stat family reports into caller-owned POSIX structs rather than a smaller
|
||||
* library-defined copy: the platform owns the fields, and a wrapper should not
|
||||
* discard a field merely because this library does not currently use it. libc
|
||||
* failures retain their errno value as the status, so callers can distinguish
|
||||
* absent paths from inaccessible ones without parsing an error message.
|
||||
* @{
|
||||
*/
|
||||
/* ====================================================================== */
|
||||
|
||||
/**
|
||||
* @brief stat(2).
|
||||
* @param[in] pathname Path to inspect. Required.
|
||||
* @param[out] dest File metadata. Required.
|
||||
* @throws AKERR_NULLPOINTER If pathname or dest is NULL.
|
||||
* @throws AKERR_IO If stat(2) failed and left errno at 0.
|
||||
* @throws (errno) The errno stat(2) set, reported directly as the status.
|
||||
* @return NULL on success, an error context otherwise.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_stat(const char *pathname, struct stat *dest);
|
||||
|
||||
/**
|
||||
* @brief lstat(2), inspecting a symbolic link itself.
|
||||
* @param[in] pathname Path to inspect. Required.
|
||||
* @param[out] dest File metadata. Required.
|
||||
* @throws AKERR_NULLPOINTER If pathname or dest is NULL.
|
||||
* @throws AKERR_IO If lstat(2) failed and left errno at 0.
|
||||
* @throws (errno) The errno lstat(2) set, reported directly as the status.
|
||||
* @return NULL on success, an error context otherwise.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_lstat(const char *pathname, struct stat *dest);
|
||||
|
||||
/**
|
||||
* @brief fstat(2).
|
||||
* @param[in] fd Open file descriptor.
|
||||
* @param[out] dest File metadata. Required.
|
||||
* @throws AKERR_NULLPOINTER If dest is NULL.
|
||||
* @throws AKERR_IO If fstat(2) failed and left errno at 0.
|
||||
* @throws (errno) The errno fstat(2) set, reported directly as the status.
|
||||
* @return NULL on success, an error context otherwise.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_fstat(int fd, struct stat *dest);
|
||||
|
||||
/**
|
||||
* @brief fstatat(2).
|
||||
* @param[in] dirfd Directory descriptor, or AT_FDCWD.
|
||||
* @param[in] pathname Path to inspect. Required.
|
||||
* @param[out] dest File metadata. Required.
|
||||
* @param[in] flags libc fstatat flags, passed unchanged.
|
||||
* @throws AKERR_NULLPOINTER If pathname or dest is NULL.
|
||||
* @throws AKERR_IO If fstatat(2) failed and left errno at 0.
|
||||
* @throws (errno) The errno fstatat(2) set, reported directly as the status.
|
||||
* @return NULL on success, an error context otherwise.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_fstatat(int dirfd, const char *pathname, struct stat *dest, int flags);
|
||||
|
||||
/**
|
||||
* @brief statvfs(3).
|
||||
* @param[in] path Path on the filesystem. Required.
|
||||
* @param[out] dest Filesystem metadata. Required.
|
||||
* @throws AKERR_NULLPOINTER If path or dest is NULL.
|
||||
* @throws AKERR_IO If statvfs(3) failed and left errno at 0.
|
||||
* @throws (errno) The errno statvfs(3) set, reported directly as the status.
|
||||
* @return NULL on success, an error context otherwise.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_statvfs(const char *path, struct statvfs *dest);
|
||||
|
||||
/**
|
||||
* @brief fstatvfs(3).
|
||||
* @param[in] fd Open file descriptor.
|
||||
* @param[out] dest Filesystem metadata. Required.
|
||||
* @throws AKERR_NULLPOINTER If dest is NULL.
|
||||
* @throws AKERR_IO If fstatvfs(3) failed and left errno at 0.
|
||||
* @throws (errno) The errno fstatvfs(3) set, reported directly as the status.
|
||||
* @return NULL on success, an error context otherwise.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_fstatvfs(int fd, struct statvfs *dest);
|
||||
|
||||
|
||||
/** @} */
|
||||
/* ====================================================================== */
|
||||
/** @name Directories
|
||||
*
|
||||
* Directory entries are copied into caller-owned storage. `d_type` may be
|
||||
* `DT_UNKNOWN`; callers that require a type must fall back to aksl_stat or
|
||||
* aksl_fstatat.
|
||||
* @{
|
||||
*/
|
||||
/* ====================================================================== */
|
||||
|
||||
/**
|
||||
* @brief Open a directory stream by path.
|
||||
* @param[in] pathname Directory path. Required.
|
||||
* @param[out] dest Open directory stream, or NULL on failure. Required.
|
||||
* @throws AKERR_NULLPOINTER If pathname or dest is NULL.
|
||||
* @throws AKERR_IO If opendir(3) fails without setting errno.
|
||||
* @throws (errno) The errno opendir(3) set, reported directly as the status.
|
||||
* @return NULL on success, an error context otherwise.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_opendir(const char *pathname, DIR **dest);
|
||||
|
||||
/**
|
||||
* @brief Open a directory stream from a file descriptor.
|
||||
* @param[in] fd Open directory descriptor. Ownership transfers on success.
|
||||
* @param[out] dest Open directory stream, or NULL on failure. Required.
|
||||
* @throws AKERR_NULLPOINTER If dest is NULL.
|
||||
* @throws AKERR_IO If fdopendir(3) fails without setting errno.
|
||||
* @throws (errno) The errno fdopendir(3) set, reported directly as the status.
|
||||
* @return NULL on success, an error context otherwise.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_fdopendir(int fd, DIR **dest);
|
||||
|
||||
/**
|
||||
* @brief Read and copy the next directory entry.
|
||||
* @param[in] dirp Open directory stream. Required.
|
||||
* @param[out] dest Caller-owned storage for the copied entry. Required.
|
||||
* @throws AKERR_NULLPOINTER If dirp or dest is NULL.
|
||||
* @throws AKERR_EOF At the end of the directory stream.
|
||||
* @throws AKERR_IO If readdir(3) fails without setting errno.
|
||||
* @throws (errno) The errno readdir(3) set, reported directly as the status.
|
||||
* @return NULL on success, an error context otherwise.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_readdir(DIR *dirp, struct dirent *dest);
|
||||
|
||||
/**
|
||||
* @brief Close a directory stream.
|
||||
* @param[in] dirp Open directory stream. Required.
|
||||
* @throws AKERR_NULLPOINTER If dirp is NULL.
|
||||
* @throws AKERR_IO If closedir(3) fails without setting errno.
|
||||
* @throws (errno) The errno closedir(3) set, reported directly as the status.
|
||||
* @return NULL on success, an error context otherwise.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_closedir(DIR *dirp);
|
||||
|
||||
/**
|
||||
* @brief Reset a directory stream to its beginning.
|
||||
* @param[in] dirp Open directory stream. Required.
|
||||
* @throws AKERR_NULLPOINTER If dirp is NULL.
|
||||
* @return NULL on success, an error context otherwise.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_rewinddir(DIR *dirp);
|
||||
|
||||
/** @} */
|
||||
/* ====================================================================== */
|
||||
/** @name Streams: open, read, write, close
|
||||
*
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
* the context still holds a pool slot: an error that is invisible and leaks at
|
||||
* the same time. Every errno-sourced status in this library goes through here,
|
||||
* and every wrapped call clears errno first so the value read back is its own.
|
||||
* See UPGRADING.md.
|
||||
* TODO.md 2.2.1.
|
||||
*/
|
||||
#define AKSL_ERRNO_OR(__fallback) (errno != 0 ? errno : (__fallback))
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Data structures.
|
||||
* Data structures -- TODO.md section 3.6.
|
||||
*
|
||||
* Not libc wrappers. These are the parts of the list and tree API that were
|
||||
* visibly missing, plus the two structures the first real consumer had to write
|
||||
@@ -328,7 +328,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_iterate_reverse(aksl_ListNode *tail
|
||||
* aksl_list_append has to walk the whole list to find the tail, so building a
|
||||
* list of n nodes with it is O(n^2). That is fine for the handful of nodes the
|
||||
* bare-node API was written for and wrong for anything larger, which is what
|
||||
* the collections plan meant by "a head/tail-tracking container type so append is O(1)".
|
||||
* TODO.md 3.6 means by "a head/tail-tracking container type so append is O(1)".
|
||||
*
|
||||
* The container holds the length as well, so aksl_list_length stops being a
|
||||
* walk. It owns no memory -- the nodes are still the caller's -- so there is no
|
||||
@@ -440,7 +440,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_clear(aksl_List *list, aksl_FreeFun
|
||||
* through an out-param and may raise, like every other callback here.
|
||||
*
|
||||
* These are the functions that set and read aksl_TreeNode.parent, which was
|
||||
* declared and then never touched by anything in the library.
|
||||
* declared and then never touched by anything in the library (TODO.md 2.2.15).
|
||||
* aksl_tree_remove needs it: relinking a node's replacement means telling that
|
||||
* node's parent about it, and finding the parent by walking from the root again
|
||||
* would turn a removal into a second search.
|
||||
@@ -691,7 +691,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_free_all(aksl_TreeNode **root, aksl
|
||||
/* ====================================================================== */
|
||||
|
||||
/*
|
||||
* FNV-1a, the other half of the collections plan's hash request. It differs from djb2 in
|
||||
* FNV-1a, the other half of TODO.md 3.6's hash request. It differs from djb2 in
|
||||
* XOR-then-multiply rather than multiply-then-add, which mixes the low bits
|
||||
* rather better -- worth having when the keys are short and share a prefix,
|
||||
* which is exactly what identifiers in a symbol table look like.
|
||||
@@ -730,7 +730,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_fnv1a_str(const char *str, uint3
|
||||
/*
|
||||
* Fixed-capacity, open-addressed, linear-probing, string-keyed.
|
||||
*
|
||||
* The shape is akbasic's src/symtab.c, which the collections plan called "worth lifting
|
||||
* The shape is akbasic's src/symtab.c, which TODO.md 3.6 says is "worth lifting
|
||||
* more or less verbatim": the caller supplies the slot array, the map refuses
|
||||
* rather than resizes when full, and the keys are copied into fixed-size slots
|
||||
* so the map owns them and a caller cannot outlive its own key strings.
|
||||
@@ -939,7 +939,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_hashmap_iterate(aksl_HashMap *map,
|
||||
*
|
||||
* The bounded formatting wrappers are the right answer when the destination is
|
||||
* a fixed buffer, and no answer at all when the output length is not known in
|
||||
* advance -- which is why the collections plan asked for this to "make the snprintf and
|
||||
* advance -- which is why TODO.md 3.6 asks for this to "make the snprintf and
|
||||
* strcat wrappers pleasant to use". Building a diagnostic, a serialised record
|
||||
* or a generated line means appending to something that grows.
|
||||
*
|
||||
|
||||
69
src/dir.c
69
src/dir.c
@@ -1,69 +0,0 @@
|
||||
/* POSIX directory-stream wrappers. */
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include "aksl_internal.h"
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_opendir(const char *pathname, DIR **dest)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "pathname=%p, dest=%p",
|
||||
(void *)pathname, (void *)dest);
|
||||
*dest = NULL;
|
||||
FAIL_ZERO_RETURN(e, pathname, AKERR_NULLPOINTER, "pathname=%p, dest=%p",
|
||||
(void *)pathname, (void *)dest);
|
||||
errno = 0;
|
||||
*dest = opendir(pathname);
|
||||
FAIL_ZERO_RETURN(e, *dest, AKSL_ERRNO_OR(AKERR_IO), "pathname=%s", pathname);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_fdopendir(int fd, DIR **dest)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "fd=%d, dest=%p", fd, (void *)dest);
|
||||
*dest = NULL;
|
||||
errno = 0;
|
||||
*dest = fdopendir(fd);
|
||||
FAIL_ZERO_RETURN(e, *dest, AKSL_ERRNO_OR(AKERR_IO), "fd=%d", fd);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_readdir(DIR *dirp, struct dirent *dest)
|
||||
{
|
||||
struct dirent *entry = NULL;
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, dirp, AKERR_NULLPOINTER, "dirp=%p, dest=%p",
|
||||
(void *)dirp, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "dirp=%p, dest=%p",
|
||||
(void *)dirp, (void *)dest);
|
||||
|
||||
/* readdir uses errno to distinguish failure from end-of-directory. */
|
||||
errno = 0;
|
||||
entry = readdir(dirp);
|
||||
if ( entry == NULL ) {
|
||||
FAIL_NONZERO_RETURN(e, errno, AKSL_ERRNO_OR(AKERR_IO), "readdir failed");
|
||||
FAIL_RETURN(e, AKERR_EOF, "end of directory");
|
||||
}
|
||||
*dest = *entry;
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_closedir(DIR *dirp)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, dirp, AKERR_NULLPOINTER, "dirp=%p", (void *)dirp);
|
||||
errno = 0;
|
||||
FAIL_NONZERO_RETURN(e, closedir(dirp), AKSL_ERRNO_OR(AKERR_IO),
|
||||
"closedir failed");
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_rewinddir(DIR *dirp)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, dirp, AKERR_NULLPOINTER, "dirp=%p", (void *)dirp);
|
||||
rewinddir(dirp);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
108
src/stat.c
108
src/stat.c
@@ -1,108 +0,0 @@
|
||||
/*
|
||||
* sys/stat.h and sys/statvfs.h metadata wrappers.
|
||||
*
|
||||
* These calls make information about a file or filesystem available only by a
|
||||
* return value that must be checked alongside a caller-owned POSIX struct. The
|
||||
* wrappers put failure in the return value, where AKERR_NOIGNORE prevents it
|
||||
* from being silently dropped, while preserving the errno that distinguishes
|
||||
* missing paths, inaccessible paths, and invalid descriptors. errno is cleared
|
||||
* immediately before each libc call, so a broken libc that reports failure
|
||||
* without setting errno is still an AKERR_IO failure rather than status 0.
|
||||
*/
|
||||
#include <akstdlib.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/statvfs.h>
|
||||
|
||||
#include "aksl_internal.h"
|
||||
|
||||
/*
|
||||
* stat(2) follows pathname through any symbolic links and writes the target's
|
||||
* metadata into the caller-owned struct stat. A file that is gone, cannot be
|
||||
* searched, or lives below a non-directory component is reported as the errno
|
||||
* from libc rather than as a library-specific status.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_stat(const char *pathname, struct stat *dest)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, pathname, AKERR_NULLPOINTER, "pathname=%p, dest=%p", (void *)pathname, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "pathname=%p, dest=%p", (void *)pathname, (void *)dest);
|
||||
errno = 0;
|
||||
FAIL_NONZERO_RETURN(e, stat(pathname, dest), AKSL_ERRNO_OR(AKERR_IO), "pathname=%s", pathname);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/*
|
||||
* lstat(2) is stat(2) without the final symbolic-link traversal. That is the
|
||||
* difference a caller needs when it is deciding whether a path is a link or
|
||||
* when the link's ownership and mode are the metadata of interest.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_lstat(const char *pathname, struct stat *dest)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, pathname, AKERR_NULLPOINTER, "pathname=%p, dest=%p", (void *)pathname, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "pathname=%p, dest=%p", (void *)pathname, (void *)dest);
|
||||
errno = 0;
|
||||
FAIL_NONZERO_RETURN(e, lstat(pathname, dest), AKSL_ERRNO_OR(AKERR_IO), "pathname=%s", pathname);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/*
|
||||
* fstat(2) gets metadata from an already-open descriptor, so it has no path
|
||||
* lookup race and remains useful after the file has been renamed or unlinked.
|
||||
* A closed or otherwise invalid descriptor reports EBADF from libc.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_fstat(int fd, struct stat *dest)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "fd=%d, dest=%p", fd, (void *)dest);
|
||||
errno = 0;
|
||||
FAIL_NONZERO_RETURN(e, fstat(fd, dest), AKSL_ERRNO_OR(AKERR_IO), "fd=%d", fd);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/*
|
||||
* fstatat(2) is the directory-descriptor form of stat(2). pathname is resolved
|
||||
* relative to dirfd unless it is absolute, and flags retain the libc choices
|
||||
* such as inspecting a link itself. Keeping those flags unchanged prevents this
|
||||
* wrapper from inventing a smaller policy than the POSIX call already exposes.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_fstatat(int dirfd, const char *pathname, struct stat *dest, int flags)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, pathname, AKERR_NULLPOINTER, "dirfd=%d, pathname=%p, dest=%p", dirfd, (void *)pathname, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "dirfd=%d, pathname=%p, dest=%p", dirfd, (void *)pathname, (void *)dest);
|
||||
errno = 0;
|
||||
FAIL_NONZERO_RETURN(e, fstatat(dirfd, pathname, dest, flags), AKSL_ERRNO_OR(AKERR_IO), "dirfd=%d, pathname=%s, flags=0x%x", dirfd, pathname, flags);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/*
|
||||
* statvfs(3) writes information about the mounted filesystem containing path,
|
||||
* not merely the named file. The result includes the filesystem block sizes and
|
||||
* available space the caller needs before it decides whether an operation fits.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_statvfs(const char *path, struct statvfs *dest)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, path, AKERR_NULLPOINTER, "path=%p, dest=%p", (void *)path, (void *)dest);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "path=%p, dest=%p", (void *)path, (void *)dest);
|
||||
errno = 0;
|
||||
FAIL_NONZERO_RETURN(e, statvfs(path, dest), AKSL_ERRNO_OR(AKERR_IO), "path=%s", path);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/*
|
||||
* fstatvfs(3) is the descriptor form of statvfs(3). It asks the filesystem that
|
||||
* owns fd for the same capacity and flag information without resolving a path
|
||||
* again, and reports a bad descriptor through the errno libc supplies.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_fstatvfs(int fd, struct statvfs *dest)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "fd=%d, dest=%p", fd, (void *)dest);
|
||||
errno = 0;
|
||||
FAIL_NONZERO_RETURN(e, fstatvfs(fd, dest), AKSL_ERRNO_OR(AKERR_IO), "fd=%d", fd);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
44
src/stdlib.c
44
src/stdlib.c
@@ -99,7 +99,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_calloc(size_t nmemb, size_t size, void *
|
||||
* block valid*, so the near-universal `p = realloc(p, n)` leaks the original
|
||||
* every time it fails. Here the old pointer goes in and out through the same
|
||||
* out-param, and is left untouched -- still valid, still the caller's to free --
|
||||
* whenever an error is raised.
|
||||
* whenever an error is raised. TODO.md 3.1.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_realloc(void **ptr, size_t size)
|
||||
{
|
||||
@@ -196,7 +196,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_free(void *ptr)
|
||||
|
||||
/*
|
||||
* Frees and clears in one step, so the pointer cannot be used or freed twice.
|
||||
* aksl_free leaves the caller holding a dangling pointer, and
|
||||
* TODO.md 2.2.13 -- aksl_free leaves the caller holding a dangling pointer, and
|
||||
* "remember to NULL it afterwards" is exactly the discipline this library is
|
||||
* supposed to make unnecessary rather than merely possible.
|
||||
*/
|
||||
@@ -214,7 +214,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_freep(void **ptr)
|
||||
* memset(3) and memcpy(3) cannot fail. They return their destination pointer
|
||||
* unconditionally, so the old FAIL_ZERO_RETURN(e, memset(...), ...) and
|
||||
* (memcpy(...) == d) checks were dead code that read as though there were a
|
||||
* failure mode to catch. What is worth checking is the
|
||||
* failure mode to catch -- TODO.md 2.2.11. What is worth checking is the
|
||||
* arguments, which is all that is checked now.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_memset(void *s, int c, size_t n)
|
||||
@@ -289,7 +289,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_memchr(const void *s, int c, size_t n, v
|
||||
* pathname and mode are checked. fopen(NULL, ...) is undefined behaviour, and
|
||||
* this wrapper used to hand both straight through unexamined -- reachable from
|
||||
* user input in practice, which is why akbasic validates the filename itself
|
||||
* before calling DLOAD/DSAVE with a comment pointing at.
|
||||
* before calling DLOAD/DSAVE with a comment pointing at TODO.md 2.2.2.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_fopen(
|
||||
const char *pathname,
|
||||
@@ -312,7 +312,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_fopen(
|
||||
|
||||
/*
|
||||
* fread and fwrite both report how much they actually transferred, through a
|
||||
* required out-param. Three things were wrong with the old pair,
|
||||
* required out-param. Three things were wrong with the old pair (TODO.md 2.2.3),
|
||||
* and the count is the fix for the worst of them: a caller who got AKERR_EOF had
|
||||
* no way to find out how much data had arrived before the stream ran out, which
|
||||
* makes the EOF status almost useless for the partial-read case it exists to
|
||||
@@ -405,7 +405,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_fclose(FILE *stream)
|
||||
* Formatted output.
|
||||
*
|
||||
* The va_list forms below do the work and the variadic forms are thin wrappers
|
||||
* over them, which is both what the wrapper contract asked for -- so consumers can
|
||||
* over them, which is both what §3.1 of TODO.md asked for -- so consumers can
|
||||
* build their own variadic wrappers -- and what makes the va_end rule below
|
||||
* checkable in one place instead of three.
|
||||
*
|
||||
@@ -415,7 +415,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_fclose(FILE *stream)
|
||||
* one. Omitting it is undefined behaviour per the standard and leaks
|
||||
* register-save state on some ABIs. akbasic's text sink ran this UB on every
|
||||
* line of program output without anything visibly misbehaving, which is
|
||||
* exactly what made it worth fixing before something did.
|
||||
* exactly what made it worth fixing before something did. TODO.md 2.1.4.
|
||||
* - *count is written on every path. It used to be left holding vprintf's -1
|
||||
* after a failure, so a caller who read the length rather than the status got
|
||||
* a negative byte count out of a function that had already failed. It is now
|
||||
@@ -426,7 +426,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_fclose(FILE *stream)
|
||||
* aksl_sprintf is gone. It wrapped vsprintf, which cannot be bounded, and an
|
||||
* error-handling wrapper around an unbounded write is precisely the sharp edge
|
||||
* this library exists to remove. aksl_snprintf replaces it, and treats
|
||||
* truncation as the failure it is rather than as a short success.
|
||||
* truncation as the failure it is rather than as a short success. TODO.md 2.2.4.
|
||||
*/
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_vprintf(int *count, const char *restrict format, va_list args)
|
||||
@@ -561,7 +561,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_vasprintf(int *count, char **dest, const
|
||||
* same arguments a few lines up, so the write cannot truncate. Guarding it
|
||||
* anyway would mean a failure branch nothing can reach and a free() beneath
|
||||
* it that nothing can execute -- dead code that reads as though there were a
|
||||
* failure mode to catch, which is exactly what was recorded
|
||||
* failure mode to catch, which is exactly what TODO.md 2.2.11 recorded
|
||||
* against the old aksl_memset and aksl_memcpy and what removing those was
|
||||
* for. The invariant is stated here instead, where it can be read.
|
||||
*/
|
||||
@@ -590,10 +590,10 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_asprintf(int *count, char **dest, const
|
||||
* atoi("99999999999999999999") handed back success and a wrapped value, because
|
||||
* atoi(3) has no error channel at all. A library whose entire value proposition
|
||||
* is turning silent libc failures into error contexts cannot ship that.
|
||||
* See UPGRADING.md.
|
||||
* TODO.md 2.1.5.
|
||||
*
|
||||
* The strto* wrappers below are the real implementation and the ato* wrappers
|
||||
* are three-line calls into them, which is what the wrapper contract asked for on its own
|
||||
* are three-line calls into them, which is what TODO.md 3.1 asked for on its own
|
||||
* account -- akbasic had to hand-write ~60 lines of exactly this (its
|
||||
* src/convert.c) because the library would not do it.
|
||||
*
|
||||
@@ -846,7 +846,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_atof(const char *nptr, double *dest)
|
||||
* realpath(3), with the destination buffer made expressible.
|
||||
*
|
||||
* The old two-argument form had three separate problems, all of them reachable
|
||||
* from ordinary use: resolved_path was never NULL-checked, so
|
||||
* from ordinary use (TODO.md 2.1.6): resolved_path was never NULL-checked, so
|
||||
* realpath(path, NULL) allocated a buffer the wrapper then discarded and leaked;
|
||||
* there was no way for a caller to say how big the buffer was, so everyone had
|
||||
* to know to supply PATH_MAX bytes; and the failure path formatted resolved_path
|
||||
@@ -904,7 +904,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_realpath_alloc(const char *restrict path
|
||||
* negative value: the hash differed from canonical djb2, and -- worse -- differed
|
||||
* between platforms depending on the signedness of char. Benign for the 7-bit
|
||||
* ASCII identifiers akbasic hashes, and quietly wrong for the first caller to
|
||||
* key a table on a filename or a UTF-8 string literal.
|
||||
* key a table on a filename or a UTF-8 string literal. TODO.md 2.2.6.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_djb2(const char *str, size_t len, uint32_t *hashval)
|
||||
{
|
||||
@@ -921,7 +921,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_djb2(const char *str, size_t len
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/* The NUL-terminated convenience form the collections plan asked for. */
|
||||
/* The NUL-terminated convenience form TODO.md 3.6 asked for. */
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_djb2_str(const char *str, uint32_t *hashval)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
@@ -941,7 +941,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_append(aksl_ListNode *list, aksl_Li
|
||||
* Two separate walks, deliberately. The Floyd pass answers "is this list
|
||||
* finite"; it says nothing about where the tail is, because `slow` stops at
|
||||
* the midpoint. Conflating the two is what made this function truncate every
|
||||
* list of two or more nodes.
|
||||
* list of two or more nodes -- see TODO.md 2.1.1.
|
||||
*/
|
||||
while ( fast != NULL && fast->next != NULL ) {
|
||||
slow = slow->next;
|
||||
@@ -973,7 +973,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_append(aksl_ListNode *list, aksl_Li
|
||||
/*
|
||||
* `head` is required, not optional. Popping the head node used to leave the
|
||||
* caller's own head pointer aimed at a node that is no longer in the list, and
|
||||
* there was no way for the caller to learn the new one. Taking
|
||||
* there was no way for the caller to learn the new one -- TODO.md 2.2.12. Taking
|
||||
* the head by reference makes the correct call the only call that compiles.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_pop(aksl_ListNode **head, aksl_ListNode *node)
|
||||
@@ -998,7 +998,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_pop(aksl_ListNode **head, aksl_List
|
||||
/*
|
||||
* Zeroing initialisers. Every caller previously had to remember to memset a node
|
||||
* before its first use, because append and iterate both read next/prev -- and a
|
||||
* stack-allocated node that skipped it walked into garbage.
|
||||
* stack-allocated node that skipped it walked into garbage. TODO.md 2.2.14.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_node_init(aksl_ListNode *node, void *data)
|
||||
{
|
||||
@@ -1027,7 +1027,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_node_init(aksl_TreeNode *node, void
|
||||
* aksl_ListNode: the walk needs to carry each node's depth alongside it, which is
|
||||
* how a cyclic tree is stopped without a visited-set. The caller's lalloc/lfree
|
||||
* allocate and release these -- which is what they were always documented to do
|
||||
* and never actually did.
|
||||
* and never actually did (TODO.md 2.2.8).
|
||||
*/
|
||||
typedef struct TreeQueueEntry {
|
||||
struct TreeQueueEntry *next;
|
||||
@@ -1165,12 +1165,12 @@ static akerr_ErrorContext AKERR_NOIGNORE *tree_bfs_walk(
|
||||
* unwinds every frame up to the public entry point, which swallows it exactly
|
||||
* once. In the old single-function form the frame that raised the break handled
|
||||
* it and returned success, the parent's PASS therefore saw nothing wrong, and
|
||||
* the walk carried on into the sibling subtree.
|
||||
* the walk carried on into the sibling subtree -- TODO.md 2.1.3.
|
||||
*
|
||||
* `path` is the chain of ancestors of `root` and `depth` its length. Checking
|
||||
* each node against its own ancestry turns a tree that loops back on itself from
|
||||
* infinite recursion into AKERR_CIRCULAR_REFERENCE, and the depth cap catches the
|
||||
* degenerate chain that is merely too deep to recurse over.
|
||||
* degenerate chain that is merely too deep to recurse over (TODO.md 2.2.7).
|
||||
*/
|
||||
static akerr_ErrorContext AKERR_NOIGNORE *tree_dfs_walk(
|
||||
aksl_TreeNode *root,
|
||||
@@ -1266,7 +1266,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_iterate(
|
||||
* assume it. The switch used to have no default at all, so searchmode 99 --
|
||||
* and AKSL_TREE_SEARCH_VISIT, which the header documented but nothing
|
||||
* implemented -- fell straight through to success having visited nothing
|
||||
* See UPGRADING.md.
|
||||
* (TODO.md 2.2.9).
|
||||
*/
|
||||
switch ( searchmode ) {
|
||||
case AKSL_TREE_SEARCH_DFS_PREORDER:
|
||||
@@ -1328,7 +1328,7 @@ akerr_ErrorContext AKERR_NOIGNORE *aksl_list_iterate(aksl_ListNode *list, aksl_L
|
||||
/*
|
||||
* From `list`, not from `slow`. The Floyd pass above leaves `slow` at the
|
||||
* midpoint, and starting the visit there skipped the whole first half of the
|
||||
* list including the head.
|
||||
* list including the head -- see TODO.md 2.1.2.
|
||||
*/
|
||||
while ( node != NULL ) {
|
||||
ATTEMPT {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* stdio.h wrappers beyond fopen/fread/fwrite/fclose.
|
||||
* 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.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/*
|
||||
* string.h wrappers.
|
||||
* string.h wrappers -- TODO.md section 3.1.
|
||||
*
|
||||
* This is the section akbasic needed most and could not have: across its source
|
||||
* it calls strlen 37 times, strcmp 16, strncpy 15 and strstr once, every one of
|
||||
* them raw because there was nothing here to call instead. Ten of those sites
|
||||
* are the same idiom written out by hand -- a length check, then strncpy, then
|
||||
* an explicit NUL -- which is exactly the "truncation reported as an error
|
||||
* rather than silently accepted" that the wrapper contract asks for.
|
||||
* rather than silently accepted" that TODO.md 3.1 asks for.
|
||||
*
|
||||
* Two conventions run through the whole file.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* NEGATIVE COMPILE TEST.
|
||||
* NEGATIVE COMPILE TEST -- TODO.md sections 1.3 and 2.2.5.
|
||||
*
|
||||
* This file must NOT compile. It is built by the CTest entry
|
||||
* `negative_format_mismatch` with -Werror, and that test is marked WILL_FAIL.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* NEGATIVE COMPILE TEST.
|
||||
* NEGATIVE COMPILE TEST -- TODO.md section 1.9.
|
||||
*
|
||||
* This file must NOT compile. It is built by the CTest entry `negative_noignore`
|
||||
* with -Werror, and that test is marked WILL_FAIL, so a successful build is a
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* List and tree additions -- src/collections.c.
|
||||
* List and tree additions -- src/collections.c, TODO.md section 3.6.
|
||||
*
|
||||
* The bare-node list functions, the tracked aksl_List container, and the binary
|
||||
* search tree. The hash map and string buffer have their own files.
|
||||
@@ -407,7 +407,7 @@ static akerr_ErrorContext AKERR_NOIGNORE *free_that_fails_once(void *ptr)
|
||||
* single walk. Only the first is kept and handed back; the rest have to be
|
||||
* released, or a walk over n nodes with a broken free would consume n pool slots
|
||||
* and exhaust the pool -- the failure mode the whole pool-accounting section of
|
||||
* the cross-cutting wrapper contract exists to catch.
|
||||
* TODO.md 1.9 exists to catch.
|
||||
*/
|
||||
static akerr_ErrorContext AKERR_NOIGNORE *free_that_always_fails(void *ptr)
|
||||
{
|
||||
@@ -800,7 +800,7 @@ static int test_tree_insert_orders_the_leaves(void)
|
||||
}
|
||||
|
||||
/*
|
||||
* aksl_TreeNode.parent was declared and never touched by
|
||||
* TODO.md 2.2.15: aksl_TreeNode.parent was declared and never touched by
|
||||
* anything in the library. These are the functions that set it, and
|
||||
* aksl_tree_remove is the one that needs it.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* String -> number wrappers: aksl_atoi / atol / atoll / atof.
|
||||
*
|
||||
* Numeric conversion, complete. The cases that used to live in
|
||||
* TODO.md section 1.4, complete. The cases that used to live in
|
||||
* tests/test_convert_strict.c -- registered as a known failure because the ato*
|
||||
* family had no error channel at all (2.1.5) -- are folded back in here now that
|
||||
* they pass: non-numeric input, empty input, trailing junk and overflow are all
|
||||
@@ -95,7 +95,7 @@ static int test_trailing_junk_is_a_value_error(void)
|
||||
|
||||
/*
|
||||
* "0x10" through the ato* forms is base 10, so parsing stops at the 'x' and the
|
||||
* rest is trailing junk. The wrapper plan left this open; the answer is that the
|
||||
* rest is trailing junk. TODO.md 1.4 left this open; the answer is that the
|
||||
* prefix-honouring parse is aksl_strtol(nptr, NULL, 0, &dest), and tests/
|
||||
* test_strto.c holds that half.
|
||||
*/
|
||||
@@ -149,7 +149,7 @@ static int test_atoi_narrows_to_int_range(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The type boundaries round-trip exactly rather than nearly. */
|
||||
/* TODO.md 1.4: the type boundaries round-trip exactly rather than nearly. */
|
||||
static int test_type_boundaries_round_trip(void)
|
||||
{
|
||||
char buf[64];
|
||||
@@ -229,7 +229,7 @@ static int test_atof_converts_and_rejects_null(void)
|
||||
}
|
||||
|
||||
/*
|
||||
* The wrapper plan left "inf" and "nan" open. They are accepted, because strtod(3)
|
||||
* TODO.md 1.4 left "inf" and "nan" open. They are accepted, because strtod(3)
|
||||
* accepts them and because they are exact round-trips rather than approximations
|
||||
* of something else -- a caller who did not want them has a domain check to make
|
||||
* that this library cannot make for it.
|
||||
|
||||
147
tests/test_dir.c
147
tests/test_dir.c
@@ -1,147 +0,0 @@
|
||||
#include "aksl_capture.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
static int make_directory(char *path, size_t size)
|
||||
{
|
||||
const char *tmp = getenv("TMPDIR");
|
||||
if ( tmp == NULL || tmp[0] == '\0' ) {
|
||||
tmp = "/tmp";
|
||||
}
|
||||
if ( (size_t)snprintf(path, size, "%s/aksl_dir_XXXXXX", tmp) >= size ) {
|
||||
return 1;
|
||||
}
|
||||
return mkdtemp(path) == NULL;
|
||||
}
|
||||
|
||||
static int test_open_errors_and_nulls(void)
|
||||
{
|
||||
char file[AKSL_TMP_MAX];
|
||||
DIR *dirp = (DIR *)1;
|
||||
struct dirent entry;
|
||||
|
||||
AKSL_CHECK_STATUS(aksl_opendir("/nonexistent/aksl/dir", &dirp), ENOENT);
|
||||
AKSL_CHECK(dirp == NULL);
|
||||
AKSL_CHECK(aksl_temp_file(file, sizeof(file)) == 0);
|
||||
AKSL_CHECK_STATUS(aksl_opendir(file, &dirp), ENOTDIR);
|
||||
AKSL_CHECK_STATUS(aksl_opendir(NULL, &dirp), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_opendir(".", NULL), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_fdopendir(-1, &dirp), EBADF);
|
||||
AKSL_CHECK_STATUS(aksl_fdopendir(0, NULL), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_readdir(NULL, &entry), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_OK(aksl_opendir(".", &dirp));
|
||||
AKSL_CHECK_STATUS(aksl_readdir(dirp, NULL), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_OK(aksl_closedir(dirp));
|
||||
AKSL_CHECK_STATUS(aksl_closedir(NULL), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_rewinddir(NULL), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK(unlink(file) == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_copy_eof_rewind_and_fdopendir(void)
|
||||
{
|
||||
char path[AKSL_TMP_MAX], first_path[AKSL_TMP_MAX], second_path[AKSL_TMP_MAX];
|
||||
struct dirent entry, saved;
|
||||
char first_read[sizeof(entry.d_name)];
|
||||
DIR *dirp = NULL;
|
||||
int fd = -1, seen_first = 0, seen_second = 0;
|
||||
|
||||
AKSL_CHECK(make_directory(path, sizeof(path)) == 0);
|
||||
AKSL_CHECK(snprintf(first_path, sizeof(first_path), "%s/first", path) < (int)sizeof(first_path));
|
||||
AKSL_CHECK(snprintf(second_path, sizeof(second_path), "%s/second", path) < (int)sizeof(second_path));
|
||||
fd = open(first_path, O_CREAT | O_WRONLY, 0600);
|
||||
AKSL_CHECK(fd >= 0);
|
||||
AKSL_CHECK(close(fd) == 0);
|
||||
fd = open(second_path, O_CREAT | O_WRONLY, 0600);
|
||||
AKSL_CHECK(fd >= 0);
|
||||
AKSL_CHECK(close(fd) == 0);
|
||||
|
||||
AKSL_CHECK_OK(aksl_opendir(path, &dirp));
|
||||
do {
|
||||
akerr_ErrorContext *error = aksl_readdir(dirp, &entry);
|
||||
if ( error != NULL ) {
|
||||
int status = error->status;
|
||||
RELEASE_ERROR(error);
|
||||
AKSL_CHECK(status == AKERR_EOF);
|
||||
break;
|
||||
}
|
||||
if ( strcmp(entry.d_name, "first") == 0 ) { saved = entry; seen_first++; }
|
||||
if ( strcmp(entry.d_name, "second") == 0 ) seen_second++;
|
||||
} while ( 1 );
|
||||
AKSL_CHECK(seen_first == 1 && seen_second == 1);
|
||||
AKSL_CHECK(strcmp(saved.d_name, "first") == 0);
|
||||
AKSL_CHECK_OK(aksl_rewinddir(dirp));
|
||||
AKSL_CHECK_OK(aksl_readdir(dirp, &entry));
|
||||
AKSL_CHECK(snprintf(first_read, sizeof(first_read), "%s", entry.d_name) < (int)sizeof(first_read));
|
||||
AKSL_CHECK_OK(aksl_readdir(dirp, &entry));
|
||||
AKSL_CHECK_OK(aksl_rewinddir(dirp));
|
||||
AKSL_CHECK_OK(aksl_readdir(dirp, &entry));
|
||||
AKSL_CHECK(strcmp(entry.d_name, first_read) == 0);
|
||||
AKSL_CHECK_OK(aksl_closedir(dirp));
|
||||
|
||||
fd = open(path, O_RDONLY | O_DIRECTORY);
|
||||
AKSL_CHECK(fd >= 0);
|
||||
AKSL_CHECK_OK(aksl_fdopendir(fd, &dirp));
|
||||
AKSL_CHECK_OK(aksl_readdir(dirp, &entry));
|
||||
AKSL_CHECK_OK(aksl_closedir(dirp));
|
||||
AKSL_CHECK(unlink(first_path) == 0);
|
||||
AKSL_CHECK(unlink(second_path) == 0);
|
||||
AKSL_CHECK(rmdir(path) == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_empty_directory_reaches_eof_after_dot_entries(void)
|
||||
{
|
||||
char path[AKSL_TMP_MAX];
|
||||
struct dirent entry;
|
||||
DIR *dirp = NULL;
|
||||
int count = 0, saw_dot = 0, saw_dotdot = 0;
|
||||
|
||||
AKSL_CHECK(make_directory(path, sizeof(path)) == 0);
|
||||
AKSL_CHECK_OK(aksl_opendir(path, &dirp));
|
||||
for ( ;; ) {
|
||||
akerr_ErrorContext *error = aksl_readdir(dirp, &entry);
|
||||
if ( error != NULL ) {
|
||||
int status = error->status;
|
||||
RELEASE_ERROR(error);
|
||||
AKSL_CHECK(status == AKERR_EOF);
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
if ( strcmp(entry.d_name, ".") == 0 ) saw_dot++;
|
||||
if ( strcmp(entry.d_name, "..") == 0 ) saw_dotdot++;
|
||||
}
|
||||
AKSL_CHECK(count == 2 && saw_dot == 1 && saw_dotdot == 1);
|
||||
AKSL_CHECK_OK(aksl_closedir(dirp));
|
||||
AKSL_CHECK(rmdir(path) == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_permission_denied(void)
|
||||
{
|
||||
char path[AKSL_TMP_MAX];
|
||||
DIR *dirp = NULL;
|
||||
AKSL_CHECK(make_directory(path, sizeof(path)) == 0);
|
||||
AKSL_CHECK(chmod(path, 0000) == 0);
|
||||
if ( geteuid() == 0 ) {
|
||||
fprintf(stderr, " (skipped: running as root, chmod 000 denies nothing)\n");
|
||||
} else {
|
||||
AKSL_CHECK_STATUS(aksl_opendir(path, &dirp), EACCES);
|
||||
}
|
||||
AKSL_CHECK(chmod(path, 0700) == 0);
|
||||
AKSL_CHECK(rmdir(path) == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int failures = 0;
|
||||
AKSL_RUN(failures, test_open_errors_and_nulls);
|
||||
AKSL_RUN(failures, test_copy_eof_rewind_and_fdopendir);
|
||||
AKSL_RUN(failures, test_empty_directory_reaches_eof_after_dot_entries);
|
||||
AKSL_RUN(failures, test_permission_denied);
|
||||
AKSL_REPORT(failures);
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
* Formatted-output wrappers: aksl_printf / aksl_fprintf / aksl_snprintf and
|
||||
* their va_list forms.
|
||||
*
|
||||
* Formatted output, complete. Each happy path asserts both halves of the
|
||||
* TODO.md section 1.3, now complete. Each happy path asserts both halves of the
|
||||
* contract -- the byte count handed back through *count and the text that
|
||||
* actually landed somewhere -- and every pointer argument is checked for its
|
||||
* NULL guard.
|
||||
@@ -11,7 +11,7 @@
|
||||
* points at the formatted-output wrapper under test and not at the stream
|
||||
* wrappers, which tests/test_stream.c covers.
|
||||
*
|
||||
* aksl_sprintf is gone and aksl_snprintf takes its place, so the
|
||||
* aksl_sprintf is gone (TODO.md 2.2.4) and aksl_snprintf takes its place, so the
|
||||
* destination-overflow case that could not previously be written is here: it is
|
||||
* AKERR_OUTOFBOUNDS, not the short success snprintf(3) would have reported.
|
||||
*/
|
||||
@@ -139,7 +139,7 @@ static int test_fprintf_writes_to_stream(void)
|
||||
/*
|
||||
* vfprintf on a stream opened "r" fails outright, so the wrapper reports the
|
||||
* errno it saw (EBADF on glibc). *count is 0 afterwards, not vfprintf's -1:
|
||||
* The wrapper plan recorded the negative count as a contract gap, and this is the
|
||||
* TODO.md 1.3 recorded the negative count as a contract gap, and this is the
|
||||
* assertion that closes it.
|
||||
*/
|
||||
static int test_fprintf_to_read_only_stream_reports_errno(void)
|
||||
@@ -270,7 +270,7 @@ static int test_asprintf_allocates_to_fit(void)
|
||||
}
|
||||
|
||||
/*
|
||||
* The va_list forms are what the variadic ones are built on, and the wrapper contract
|
||||
* The va_list forms are what the variadic ones are built on, and TODO.md 3.1
|
||||
* wanted them exposed so consumers can write their own variadic wrappers. This
|
||||
* is a consumer doing exactly that.
|
||||
*/
|
||||
@@ -304,7 +304,7 @@ static int test_va_list_forms_are_usable_from_outside(void)
|
||||
}
|
||||
|
||||
/*
|
||||
* Regression cover for the missing va_end. Nothing here can
|
||||
* Regression cover for the missing va_end (TODO.md 2.1.4). Nothing here can
|
||||
* assert on register-save state directly; the point is to run the variadic
|
||||
* wrappers enough times, with enough arguments, that the sanitizer build has
|
||||
* something to trip over.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* The fixed-capacity hash map and FNV-1a -- src/collections.c.
|
||||
* The fixed-capacity hash map and FNV-1a -- src/collections.c, TODO.md 3.6.
|
||||
*
|
||||
* "The single most obviously-missing data structure in the library", by the
|
||||
* TODO's own account: akbasic needed one three times over -- variables,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Linked list.
|
||||
* Linked list -- TODO.md section 1.7, complete.
|
||||
*
|
||||
* The two confirmed list defects are fixed, so the tests that used to live in
|
||||
* tests/test_list_append_chain.c and tests/test_list_iterate_head.c are folded
|
||||
@@ -59,7 +59,7 @@ static akerr_ErrorContext AKERR_NOIGNORE *record_visit(aksl_ListNode *node, void
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* every caller used to have to remember to memset a node before
|
||||
* TODO.md 2.2.14: every caller used to have to remember to memset a node before
|
||||
* its first use, and a stack node that skipped it walked straight into garbage.
|
||||
*/
|
||||
static int test_node_init_zeroes_the_links(void)
|
||||
@@ -103,7 +103,7 @@ static int test_append_single_node(void)
|
||||
* The defect that made this library's list unusable: `tail` was assigned from
|
||||
* Floyd's `slow` cursor *before* slow advanced, so it tracked the node behind
|
||||
* the midpoint rather than the last node. Appending n1..n4 to n0 produced the
|
||||
* chain "n0 -> n4" and silently dropped n1, n2 and n3.
|
||||
* chain "n0 -> n4" and silently dropped n1, n2 and n3. TODO.md 2.1.1.
|
||||
*/
|
||||
static int test_append_builds_the_whole_chain(void)
|
||||
{
|
||||
@@ -218,7 +218,7 @@ static int test_append_detects_cycle_below_the_head(void)
|
||||
}
|
||||
|
||||
/*
|
||||
* The wrapper plan asked for the aliasing contract to be defined. It is refusal:
|
||||
* TODO.md 1.7 asked for the aliasing contract to be defined. It is refusal:
|
||||
* relinking a node that is already in the list would orphan everything between
|
||||
* its old position and the tail, so the tail walk -- which happens anyway --
|
||||
* doubles as the check.
|
||||
@@ -281,7 +281,7 @@ static int test_iterate_single_node(void)
|
||||
* Every node, exactly once, in order, starting at the head. The cycle check
|
||||
* leaves Floyd's `slow` cursor at the list midpoint, and the visiting loop used
|
||||
* to start from there -- so the whole first half of the list, head included, was
|
||||
* never passed to the callback at all.
|
||||
* never passed to the callback at all. TODO.md 2.1.2.
|
||||
*/
|
||||
static int test_iterate_visits_every_node_from_the_head(void)
|
||||
{
|
||||
@@ -470,7 +470,7 @@ static int test_pop_middle_node(void)
|
||||
}
|
||||
|
||||
/*
|
||||
* popping the head used to leave the caller's own head pointer
|
||||
* TODO.md 2.2.12: popping the head used to leave the caller's own head pointer
|
||||
* aimed at a node that was no longer in the list, with no way to learn the new
|
||||
* one. That is what the head out-param is for, and this is the assertion.
|
||||
*/
|
||||
@@ -572,7 +572,7 @@ static int test_pop_then_iterate(void)
|
||||
}
|
||||
|
||||
/*
|
||||
* The wrapper plan's pool-accounting case. AKSL_RUN already asserts that each test
|
||||
* TODO.md 1.7's pool-accounting case. AKSL_RUN already asserts that each test
|
||||
* leaves the pool as it found it; this one drives enough failures in a row to
|
||||
* exhaust the pool several times over, which is where a wrapper that raises an
|
||||
* error and forgets to release it shows up as an outright exhaustion rather than
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* Memory wrappers, complete, plus the additions from
|
||||
* Memory wrappers -- TODO.md section 1.1, now complete, plus the additions from
|
||||
* section 3.1.
|
||||
*
|
||||
* The three cases the wrapper plan left open are all pinned here: malloc(0) is AKERR_VALUE
|
||||
* The three cases 1.1 left open are all pinned here: malloc(0) is AKERR_VALUE
|
||||
* rather than whatever errno happened to hold when the platform's malloc(0)
|
||||
* returned NULL; an allocation the system cannot satisfy reports ENOMEM and
|
||||
* leaves *dst NULL rather than garbage; and overlapping memcpy is refused with
|
||||
@@ -32,7 +32,7 @@ static int test_malloc_rejects_null_destination(void)
|
||||
}
|
||||
|
||||
/*
|
||||
* The wrapper plan asked for this contract to be pinned down. malloc(0) is allowed to
|
||||
* TODO.md 1.1 asked for this contract to be pinned down. malloc(0) is allowed to
|
||||
* return either a unique pointer or NULL, and a NULL there is not a failure and
|
||||
* need not set errno -- so the old wrapper could raise an error whose status was
|
||||
* 0, which every DETECT downstream reads as success while the context holds a
|
||||
@@ -296,7 +296,7 @@ static int test_memcpy_zero_length_is_noop(void)
|
||||
}
|
||||
|
||||
/*
|
||||
* The wrapper plan's open question, decided: overlap is AKERR_VALUE. memcpy(3) calls
|
||||
* TODO.md 1.1's open question, decided: overlap is AKERR_VALUE. memcpy(3) calls
|
||||
* it undefined behaviour, which in practice means "works until the day a
|
||||
* compiler version or a length changes and it does not". Callers who mean to
|
||||
* overlap want aksl_memmove, and the message says so.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/*
|
||||
* aksl_realpath and aksl_realpath_alloc.
|
||||
* aksl_realpath and aksl_realpath_alloc -- TODO.md section 1.5, now complete.
|
||||
*
|
||||
* The happy paths compare against realpath(3) itself rather than against a
|
||||
* hard-coded string, because $TMPDIR may itself be a symlink (/tmp -> /private/tmp
|
||||
* and friends) and the resolved answer is what the platform says it is.
|
||||
*
|
||||
* The failure cases now pass an *uninitialised* resolved_path on purpose. That
|
||||
* used to be the crash case: the wrapper's own error path
|
||||
* used to be the crash case (TODO.md 2.1.6): the wrapper's own error path
|
||||
* formatted the buffer with %s while realpath(3) leaves its contents
|
||||
* unspecified on failure, so the library read uninitialised memory while
|
||||
* reporting an error. The message names only the input path now, and this test
|
||||
@@ -122,7 +122,7 @@ static int test_rejects_null_arguments(void)
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_realpath(NULL, resolved, sizeof(resolved)),
|
||||
AKERR_NULLPOINTER, "path=");
|
||||
/*
|
||||
* this used to be unchecked, and realpath(path, NULL)
|
||||
* TODO.md 2.1.6: this used to be unchecked, and realpath(path, NULL)
|
||||
* allocated a buffer that the wrapper then discarded and leaked.
|
||||
*/
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_realpath("/tmp", NULL, PATH_MAX),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Cross-cutting properties every wrapper has to have.
|
||||
* Cross-cutting properties every wrapper has to have -- TODO.md section 1.9.
|
||||
*
|
||||
* Two of them, and neither is about what any individual function computes:
|
||||
*
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
/* File metadata wrapper tests. */
|
||||
#include "aksl_capture.h"
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* A bit libc has never assigned in the AT_ space. fstatat(2) must reject it
|
||||
* with EINVAL rather than ignoring it, which is what proves flags reach libc
|
||||
* unmasked. Re-check this constant if AT_ ever grows into 0x40000000.
|
||||
*/
|
||||
#define AKSL_TEST_AT_INVALID 0x40000000
|
||||
|
||||
static int test_stat_success_and_fstat(void)
|
||||
{
|
||||
char path[AKSL_TMP_MAX];
|
||||
int fd = -1;
|
||||
struct stat path_dest;
|
||||
struct stat fd_dest;
|
||||
struct statvfs vfs_dest;
|
||||
/* A real file makes the mode and four-byte size observable to stat(2). */
|
||||
AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0);
|
||||
fd = open(path, O_RDWR);
|
||||
AKSL_CHECK(fd >= 0);
|
||||
AKSL_CHECK(write(fd, "stat", 4) == 4);
|
||||
AKSL_CHECK_OK(aksl_stat(path, &path_dest));
|
||||
AKSL_CHECK(S_ISREG(path_dest.st_mode));
|
||||
AKSL_CHECK(path_dest.st_size == 4);
|
||||
|
||||
/* fstat(2) addresses the same opened inode, not a second pathname lookup. */
|
||||
AKSL_CHECK_OK(aksl_fstat(fd, &fd_dest));
|
||||
AKSL_CHECK(fd_dest.st_ino == path_dest.st_ino);
|
||||
|
||||
/* fstatvfs(3) describes the backing filesystem and has a usable block size. */
|
||||
AKSL_CHECK_OK(aksl_fstatvfs(fd, &vfs_dest));
|
||||
AKSL_CHECK(vfs_dest.f_frsize != 0);
|
||||
AKSL_CHECK(close(fd) == 0);
|
||||
|
||||
/* A descriptor that was just closed must surface libc's EBADF. */
|
||||
AKSL_CHECK_STATUS(aksl_fstat(fd, &fd_dest), EBADF);
|
||||
AKSL_CHECK_STATUS(aksl_fstatvfs(fd, &vfs_dest), EBADF);
|
||||
AKSL_CHECK(unlink(path) == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_stat_paths_and_fstatat(void)
|
||||
{
|
||||
char path[AKSL_TMP_MAX];
|
||||
char child[AKSL_TMP_MAX * 2];
|
||||
char directory[AKSL_TMP_MAX];
|
||||
char linkpath[AKSL_TMP_MAX * 2];
|
||||
char *basename = NULL;
|
||||
int dirfd = -1;
|
||||
struct stat dest;
|
||||
struct stat ldest;
|
||||
struct stat path_dest;
|
||||
struct statvfs vdest;
|
||||
/* Missing paths and a regular file used as a directory preserve errno. */
|
||||
AKSL_CHECK_STATUS(aksl_stat("/nonexistent/aksl/stat", &dest), ENOENT);
|
||||
AKSL_CHECK_STATUS(aksl_lstat("/nonexistent/aksl/stat", &ldest), ENOENT);
|
||||
/* The temporary path is a regular file, so adding a child tests ENOTDIR. */
|
||||
AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0);
|
||||
AKSL_CHECK(snprintf(child, sizeof(child), "%s/child", path) < (int)sizeof(child));
|
||||
AKSL_CHECK_OK(aksl_stat(path, &path_dest));
|
||||
AKSL_CHECK_STATUS(aksl_stat(child, &dest), ENOTDIR);
|
||||
AKSL_CHECK(snprintf(linkpath, sizeof(linkpath), "%s.link", path) < (int)sizeof(linkpath));
|
||||
AKSL_CHECK(symlink(path, linkpath) == 0);
|
||||
|
||||
/* stat follows the link while lstat reports the link object itself. */
|
||||
AKSL_CHECK_OK(aksl_stat(linkpath, &dest));
|
||||
AKSL_CHECK_OK(aksl_lstat(linkpath, &ldest));
|
||||
AKSL_CHECK(S_ISREG(dest.st_mode));
|
||||
AKSL_CHECK(S_ISLNK(ldest.st_mode));
|
||||
|
||||
/* A real dirfd must resolve a relative name against that directory. */
|
||||
AKSL_CHECK(snprintf(directory, sizeof(directory), "%s", path) < (int)sizeof(directory));
|
||||
basename = strrchr(directory, '/');
|
||||
AKSL_CHECK(basename != NULL);
|
||||
*basename = '\0';
|
||||
dirfd = open(directory, O_RDONLY | O_DIRECTORY);
|
||||
AKSL_CHECK(dirfd >= 0);
|
||||
AKSL_CHECK_OK(aksl_fstatat(dirfd, strrchr(path, '/') + 1, &dest, 0));
|
||||
AKSL_CHECK(dest.st_ino == path_dest.st_ino);
|
||||
AKSL_CHECK(close(dirfd) == 0);
|
||||
|
||||
/* A valid non-zero flag must reach libc, making this call an lstat. */
|
||||
AKSL_CHECK_OK(aksl_fstatat(AT_FDCWD, linkpath, &dest, AT_SYMLINK_NOFOLLOW));
|
||||
AKSL_CHECK(S_ISLNK(dest.st_mode));
|
||||
|
||||
/* Invalid flags must replace stale errno with the EINVAL libc reports. */
|
||||
errno = E2BIG;
|
||||
AKSL_CHECK_STATUS(aksl_fstatat(AT_FDCWD, path, &dest, AKSL_TEST_AT_INVALID), EINVAL);
|
||||
|
||||
/* statvfs reports ENOENT before it can describe a missing filesystem path. */
|
||||
AKSL_CHECK_STATUS(aksl_statvfs("/nonexistent/aksl/stat", &vdest), ENOENT);
|
||||
|
||||
/* statvfs reports the filesystem containing the current directory. */
|
||||
AKSL_CHECK_OK(aksl_statvfs(".", &vdest));
|
||||
AKSL_CHECK(vdest.f_frsize != 0);
|
||||
AKSL_CHECK(unlink(linkpath) == 0);
|
||||
AKSL_CHECK(unlink(path) == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_stat_null_arguments(void)
|
||||
{
|
||||
char path[AKSL_TMP_MAX];
|
||||
struct stat dest;
|
||||
struct statvfs vdest;
|
||||
/* Create a valid path so each failure below isolates a NULL argument. */
|
||||
AKSL_CHECK(aksl_temp_file(path, sizeof(path)) == 0);
|
||||
/* Every wrapper refuses either missing caller-owned input or output storage. */
|
||||
AKSL_CHECK_STATUS(aksl_stat(NULL, &dest), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_stat(path, NULL), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_lstat(NULL, &dest), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_lstat(path, NULL), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_fstat(0, NULL), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_fstatat(AT_FDCWD, NULL, &dest, 0), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_fstatat(AT_FDCWD, path, NULL, 0), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_statvfs(NULL, &vdest), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_statvfs(path, NULL), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK_STATUS(aksl_fstatvfs(0, NULL), AKERR_NULLPOINTER);
|
||||
AKSL_CHECK(unlink(path) == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int failures = 0;
|
||||
AKSL_RUN(failures, test_stat_success_and_fstat);
|
||||
AKSL_RUN(failures, test_stat_paths_and_fstatat);
|
||||
AKSL_RUN(failures, test_stat_null_arguments);
|
||||
AKSL_REPORT(failures);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* The growable string buffer -- src/collections.c.
|
||||
* The growable string buffer -- src/collections.c, TODO.md 3.6.
|
||||
*
|
||||
* The bounded formatting wrappers are the right answer when the destination is
|
||||
* a fixed buffer and no answer at all when the length is not known in advance.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Stream wrappers: aksl_fopen / aksl_fread / aksl_fwrite / aksl_fclose.
|
||||
*
|
||||
* Stream wrappers, complete. The happy paths, the round trip, every
|
||||
* TODO.md section 1.2, now complete. The happy paths, the round trip, every
|
||||
* NULL guard, both stream-error statuses, the transferred-member count that
|
||||
* aksl_fread and aksl_fwrite report through nmemb_out, and the three cases that
|
||||
* needed a hostile file to produce: a mode-denied path (EACCES), a full device
|
||||
@@ -81,7 +81,7 @@ static int test_fopen_rejects_null_arguments(void)
|
||||
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fopen(path, "r", NULL),
|
||||
AKERR_NULLPOINTER, "fp=");
|
||||
/* both of these used to go straight through to fopen(3). */
|
||||
/* TODO.md 2.2.2: both of these used to go straight through to fopen(3). */
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fopen(NULL, "r", &fp),
|
||||
AKERR_NULLPOINTER, "pathname=");
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fopen(path, NULL, &fp),
|
||||
@@ -144,7 +144,7 @@ static int test_fread_short_read_is_eof_and_reports_the_count(void)
|
||||
AKSL_CHECK_OK(aksl_fopen(path, "r", &fp));
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(buf, 1, sizeof(buf), fp, &moved),
|
||||
AKERR_EOF, "EOF");
|
||||
/* this is what the caller could not previously find out. */
|
||||
/* TODO.md 2.2.3: this is what the caller could not previously find out. */
|
||||
AKSL_CHECK(moved == 4);
|
||||
AKSL_CHECK_OK(aksl_fclose(fp));
|
||||
|
||||
@@ -160,7 +160,7 @@ static int test_fread_short_read_is_eof_and_reports_the_count(void)
|
||||
*
|
||||
* That is a change from the old wrapper, which read ferror() and reported
|
||||
* AKERR_IO without ever consulting errno. Routing it through AKSL_ERRNO_OR
|
||||
* keeps AKERR_IO as the fallback for the case where the stream
|
||||
* (TODO.md 2.2.1) keeps AKERR_IO as the fallback for the case where the stream
|
||||
* is in error and errno says nothing, and hands back the real reason otherwise.
|
||||
*/
|
||||
static int test_fread_from_write_only_stream_reports_errno(void)
|
||||
@@ -194,7 +194,7 @@ static int test_fread_rejects_null_arguments(void)
|
||||
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(buf, 1, sizeof(buf), NULL, &moved),
|
||||
AKERR_NULLPOINTER, "fp=");
|
||||
/* ptr was never checked in either direction. */
|
||||
/* TODO.md 2.2.3: ptr was never checked in either direction. */
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(NULL, 1, sizeof(buf), fp, &moved),
|
||||
AKERR_NULLPOINTER, "ptr=");
|
||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(buf, 1, sizeof(buf), fp, NULL),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Stream wrappers beyond open/read/write/close -- src/stream.c.
|
||||
* Stream wrappers beyond open/read/write/close -- src/stream.c, TODO.md 3.1.
|
||||
*
|
||||
* tests/test_stream.c covers fopen/fread/fwrite/fclose. This one covers
|
||||
* positioning, flushing, character and line I/O, stream state, and formatted
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* aksl_strhash_djb2.
|
||||
* aksl_strhash_djb2 -- TODO.md section 1.6.
|
||||
*
|
||||
* The expected values are the canonical djb2 ones: h = 5381, then
|
||||
* h = h * 33 + byte for each of len bytes, truncated to 32 bits. They were
|
||||
@@ -7,7 +7,7 @@
|
||||
*
|
||||
* Most vectors here are 7-bit ASCII, where signed and unsigned char agree and
|
||||
* the test therefore says nothing either way about byte signedness. The high-bit
|
||||
* vector is the one that pins the sign-extension defect down.
|
||||
* vector is the one that pins TODO.md 2.2.6 down.
|
||||
*/
|
||||
|
||||
#include "aksl_capture.h"
|
||||
@@ -47,7 +47,7 @@ static int test_known_answer_vectors(void)
|
||||
}
|
||||
|
||||
/*
|
||||
* Pinned. The cursor is an unsigned char * now, so bytes at or
|
||||
* TODO.md 2.2.6, pinned. The cursor is an unsigned char * now, so bytes at or
|
||||
* above 0x80 contribute their unsigned value. Iterating a plain char * on x86 or
|
||||
* ARM Linux made them negative, giving 5859874 here instead of 5868578 -- a hash
|
||||
* that disagreed with canonical djb2 and, worse, disagreed with itself across
|
||||
@@ -68,7 +68,7 @@ static int test_high_bit_bytes_are_unsigned(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The NUL-terminated convenience form agrees with the length one. */
|
||||
/* The NUL-terminated convenience form (TODO.md 3.6) agrees with the length one. */
|
||||
static int test_str_form_matches_the_length_form(void)
|
||||
{
|
||||
const char *s = "libakstdlib";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* String wrappers -- src/string.c.
|
||||
* String wrappers -- src/string.c, TODO.md section 3.1.
|
||||
*
|
||||
* The two contracts worth testing hardest are the ones that differ from libc:
|
||||
* every copying function takes the destination size and treats truncation as an
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* The strto* family.
|
||||
* The strto* family -- TODO.md section 3.1.
|
||||
*
|
||||
* These are the real implementation behind the ato* wrappers and the thing
|
||||
* akbasic had to hand-write for itself (its src/convert.c, ~60 lines) because
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Tree traversal.
|
||||
* Tree traversal -- TODO.md section 1.8, complete.
|
||||
*
|
||||
* The old version of this file counted steps, which cannot tell the three
|
||||
* depth-first orders apart because all three visit all seven nodes -- and could
|
||||
@@ -183,7 +183,7 @@ static int test_dfs_is_an_alias_for_preorder(void)
|
||||
|
||||
/*
|
||||
* BFS was AKERR_NOT_IMPLEMENTED, and the lalloc/lfree parameters that existed to
|
||||
* serve it were defaulted and then never called. See UPGRADING.md.
|
||||
* serve it were defaulted and then never called -- TODO.md 2.2.8 and 2.2.10.
|
||||
* Both modes work now, and the allocator test below proves the queue is real.
|
||||
*/
|
||||
static int test_bfs_visits_level_by_level(void)
|
||||
@@ -249,7 +249,7 @@ static akerr_ErrorContext AKERR_NOIGNORE *counting_free(void *ptr)
|
||||
}
|
||||
|
||||
/*
|
||||
* The wrapper plan asked that "custom lalloc/lfree are actually invoked -- currently they are
|
||||
* TODO.md 1.8: "Custom lalloc/lfree are actually invoked -- currently they are
|
||||
* stored and never called". They are called now, once per node enqueued, and
|
||||
* every allocation is released. The depth-first modes allocate nothing at all,
|
||||
* which is the other half of the contract.
|
||||
@@ -382,7 +382,7 @@ static int test_degenerate_chains(void)
|
||||
}
|
||||
|
||||
/*
|
||||
* A chain deeper than the recursion can take. It used to
|
||||
* TODO.md 1.8 / 2.2.7: a chain deeper than the recursion can take. It used to
|
||||
* overflow the stack; it is AKERR_OUTOFBOUNDS now, and the message names the
|
||||
* documented limit. Built one node past the cap so the failure is the cap itself
|
||||
* and not some incidental shortfall. `static` because AKSL_TREE_MAX_DEPTH nodes
|
||||
@@ -488,7 +488,7 @@ static int test_cyclic_tree_is_caught(void)
|
||||
* that raised the break handled it in its own PROCESS/HANDLE block and returned
|
||||
* success, so the parent frame's PASS saw nothing wrong and carried straight on
|
||||
* into the sibling subtree. All seven nodes were visited no matter where the
|
||||
* break was raised.
|
||||
* break was raised. TODO.md 2.1.3.
|
||||
*
|
||||
* One case per order, each breaking on a node that is *not* last in that order --
|
||||
* which is precisely what the old test could not do, because it hid its target
|
||||
@@ -579,7 +579,7 @@ static int test_null_arguments(void)
|
||||
}
|
||||
|
||||
/*
|
||||
* the switch had no default, so an unrecognised mode -- and
|
||||
* TODO.md 2.2.9: the switch had no default, so an unrecognised mode -- and
|
||||
* AKSL_TREE_SEARCH_VISIT, which the header documented but nothing implemented --
|
||||
* fell straight through to SUCCEED_RETURN having visited nothing at all. A
|
||||
* traversal that silently did not happen, reported as success.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Version reporting.
|
||||
* Version reporting -- TODO.md section 2.2.16.
|
||||
*
|
||||
* There are two versions in play and the whole point of this API is that they
|
||||
* are allowed to differ:
|
||||
|
||||
Reference in New Issue
Block a user