Compare commits
36 Commits
todo-to-is
...
10
| Author | SHA1 | Date | |
|---|---|---|---|
|
d5e5e95c61
|
|||
|
2b79aca103
|
|||
|
15e9104d9e
|
|||
|
01034fc668
|
|||
|
8c94231167
|
|||
|
be725f8cf2
|
|||
|
58f426abce
|
|||
|
55d986c631
|
|||
| d6a1cd8ca8 | |||
|
c95f8e9770
|
|||
|
22208f0aa0
|
|||
|
8a76b88648
|
|||
|
c0c18f5a6a
|
|||
|
c0a1c18c87
|
|||
|
602759e491
|
|||
|
90134ca0f6
|
|||
|
8c1746aba6
|
|||
|
b22de3e34c
|
|||
|
a2ec96e88f
|
|||
|
c61e59b9a5
|
|||
|
a5b19f1d8d
|
|||
|
ac9f383a91
|
|||
|
024fe6cde2
|
|||
|
f60787a852
|
|||
|
681ca1d3c7
|
|||
|
be6d2f6d7c
|
|||
|
a776c5a568
|
|||
|
b63d1a0503
|
|||
|
c5386492b4
|
|||
|
bd401b2452
|
|||
|
ac570890f9
|
|||
|
54165a615b
|
|||
|
13efe5e91b
|
|||
|
7ca7d08d7e
|
|||
|
824272a4d7
|
|||
|
fe0d1145a1
|
@@ -21,7 +21,7 @@ jobs:
|
|||||||
# different libakerrors depending on where you built: the install went to
|
# different libakerrors depending on where you built: the install went to
|
||||||
# /usr/local, while the build below is top-level and so compiles
|
# /usr/local, while the build below is top-level and so compiles
|
||||||
# deps/libakerror at the pinned commit via add_subdirectory -- the
|
# deps/libakerror at the pinned commit via add_subdirectory -- the
|
||||||
# installed one was never actually linked against. TODO.md 2.3.
|
# installed one was never actually linked against.
|
||||||
#
|
#
|
||||||
# The submodule wins. It is the version this repository pins, tests and
|
# 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
|
# 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
|
# new surface being argument validation whose mutants are frequently
|
||||||
# equivalent. `errno = 0` deleted from a wrapper whose libc call always
|
# equivalent. `errno = 0` deleted from a wrapper whose libc call always
|
||||||
# sets errno cannot be distinguished by any test that could be written.
|
# sets errno cannot be distinguished by any test that could be written.
|
||||||
# The survivors worth acting on are named in TODO.md; raise the gate as
|
# The survivors worth acting on are named in issue #7; raise the gate as
|
||||||
# they become assertions.
|
# they become assertions.
|
||||||
- name: mutation testing
|
- name: mutation testing
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
10
AGENTS.md
10
AGENTS.md
@@ -14,6 +14,7 @@ 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/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/string.c` | the `string.h` surface |
|
||||||
| `src/stream.c` | `stdio.h` beyond open/read/write/close |
|
| `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/collections.c` | list and tree operations, hash map, string buffer, FNV-1a |
|
||||||
| `src/aksl_internal.h` | shared internals; not installed, not public |
|
| `src/aksl_internal.h` | shared internals; not installed, not public |
|
||||||
|
|
||||||
@@ -76,6 +77,10 @@ return convention and the `PREPARE_ERROR` / `FAIL_*` / `SUCCEED_RETURN` pattern.
|
|||||||
Four conventions hold across the whole library, and a new wrapper that breaks one
|
Four conventions hold across the whole library, and a new wrapper that breaks one
|
||||||
of them is wrong even if it compiles and passes:
|
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".
|
- **A NULL out-param is a caller error**, not "don't care".
|
||||||
- **Finding nothing is success** -- searching functions write NULL or zero and
|
- **Finding nothing is success** -- searching functions write NULL or zero and
|
||||||
return NULL.
|
return NULL.
|
||||||
@@ -93,6 +98,11 @@ 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
|
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.
|
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
|
## Testing Guidelines
|
||||||
|
|
||||||
Add a new test by creating `tests/test_mything.c` and adding `mything` to the
|
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.
|
# 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
|
# 0.2.0, and the minor bump is an ABI break on purpose. Fixing the confirmed
|
||||||
# defects in TODO.md 2.1 changed documented behaviour and, in five places,
|
# defects listed in UPGRADING.md changed documented behaviour and, in five places,
|
||||||
# signatures:
|
# signatures:
|
||||||
#
|
#
|
||||||
# aksl_realpath takes the destination's length; aksl_realpath_alloc is new
|
# 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()
|
endif()
|
||||||
|
|
||||||
# Warnings. The only warning in the tree when these went on was the unused
|
# Warnings. The only warning in the tree when these went on was the unused
|
||||||
# `queue` parameter of aksl_tree_iterate, which TODO.md 2.2.8 had already
|
# `queue` parameter of aksl_tree_iterate, which UPGRADING.md had already
|
||||||
# recorded as a dead parameter -- so the cost of turning them on was one
|
# 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.
|
# 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
|
# 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
|
# 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
|
# src/stdlib.c passes at least one argument regardless -- see the note in
|
||||||
# TODO.md 2.3 -- so the code is pedantic-clean; it is the expansion that is not.
|
# libakerror issue #15 -- so the code is pedantic-clean; it is the expansion that is not.
|
||||||
if(CMAKE_C_COMPILER_ID MATCHES "^(GNU|Clang|AppleClang)$")
|
if(CMAKE_C_COMPILER_ID MATCHES "^(GNU|Clang|AppleClang)$")
|
||||||
add_compile_options(-Wall -Wextra)
|
add_compile_options(-Wall -Wextra)
|
||||||
# CI turns this on. Locally it is off, because a warning that stops the build
|
# 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
|
# 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
|
# dropping it: its coverage script drives its own instrumented build tree, so
|
||||||
# `cmake --build build-coverage --target akerror_coverage` still does the right
|
# `cmake --build build-coverage --target akerror_coverage` still does the right
|
||||||
# thing. Remove this once the dependency namespaces it upstream -- see TODO.md.
|
# thing. Remove this once the dependency namespaces it upstream -- libakerror issue #15.
|
||||||
function(add_custom_target _name)
|
function(add_custom_target _name)
|
||||||
if(AKSL_SUPPRESS_ADD_TEST AND _name STREQUAL "coverage")
|
if(AKSL_SUPPRESS_ADD_TEST AND _name STREQUAL "coverage")
|
||||||
_add_custom_target(akerror_coverage ${ARGN})
|
_add_custom_target(akerror_coverage ${ARGN})
|
||||||
@@ -207,6 +207,8 @@ add_library(akstdlib SHARED
|
|||||||
src/stdlib.c
|
src/stdlib.c
|
||||||
src/string.c
|
src/string.c
|
||||||
src/stream.c
|
src/stream.c
|
||||||
|
src/stat.c
|
||||||
|
src/dir.c
|
||||||
src/collections.c
|
src/collections.c
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -307,6 +309,7 @@ install(FILES
|
|||||||
set(AKSL_TESTS
|
set(AKSL_TESTS
|
||||||
collections
|
collections
|
||||||
convert
|
convert
|
||||||
|
dir
|
||||||
format
|
format
|
||||||
hashmap
|
hashmap
|
||||||
linkedlist
|
linkedlist
|
||||||
@@ -317,6 +320,7 @@ set(AKSL_TESTS
|
|||||||
strbuf
|
strbuf
|
||||||
stream
|
stream
|
||||||
streamio
|
streamio
|
||||||
|
stat
|
||||||
strhash
|
strhash
|
||||||
string
|
string
|
||||||
strto
|
strto
|
||||||
@@ -329,7 +333,7 @@ set(AKSL_WILL_FAIL_TESTS
|
|||||||
|
|
||||||
# Empty, and that is the news. It held four entries -- convert_strict,
|
# 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
|
# list_append_chain, list_iterate_head and tree_iterate_break -- one for each
|
||||||
# confirmed defect in TODO.md 2.1. All four are fixed, so each of those files
|
# confirmed defect listed in UPGRADING.md. All four are fixed, so each of those files
|
||||||
# was folded back into the test for the thing it was testing (tests/
|
# 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
|
# 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.
|
# has to keep passing rather than merely keep failing visibly.
|
||||||
@@ -350,7 +354,7 @@ foreach(_test IN LISTS AKSL_TESTS AKSL_WILL_FAIL_TESTS AKSL_KNOWN_FAILING_TESTS)
|
|||||||
list(APPEND AKSL_TEST_TARGETS test_${_test})
|
list(APPEND AKSL_TEST_TARGETS test_${_test})
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
# Negative compile tests -- TODO.md 1.3 and 1.9.
|
# Negative compile tests: the format attributes and AKERR_NOIGNORE.
|
||||||
#
|
#
|
||||||
# Two properties of this library are enforced by the compiler and by nothing
|
# 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
|
# else: AKERR_NOIGNORE makes discarding a returned error context an error, and
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ through [libakerror](https://source.starfort.tech/andrew/libakerror)'s
|
|||||||
and `errno`. It also provides data structures built on the same convention.
|
and `errno`. It also provides data structures built on the same convention.
|
||||||
|
|
||||||
Every entry point returns `akerr_ErrorContext *` and is marked `AKERR_NOIGNORE`.
|
Every entry point returns `akerr_ErrorContext *` and is marked `AKERR_NOIGNORE`.
|
||||||
See `TODO.md` for the current state of the library and `UPGRADING.md` if you are
|
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
|
||||||
coming from 0.1.0, which this release breaks.
|
coming from 0.1.0, which this release breaks.
|
||||||
|
|
||||||
## What it wraps
|
## What it wraps
|
||||||
|
|||||||
@@ -26,7 +26,8 @@
|
|||||||
* takes a slot from it on any failure path. See README.md.
|
* 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
|
* @see README.md for the deviations from libc semantics, UPGRADING.md if you are
|
||||||
* coming from 0.1.0, and TODO.md for what is still open.
|
* coming from 0.1.0. Outstanding work is in the issue tracker;
|
||||||
|
* TODO.md is the record of where the library stands.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _AKSTDLIB_H_
|
#ifndef _AKSTDLIB_H_
|
||||||
@@ -62,6 +63,7 @@
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* What this header needs in its own declarations, and no more:
|
* What this header needs in its own declarations, and no more:
|
||||||
|
* dirent.h DIR, struct dirent
|
||||||
* stdio.h FILE
|
* stdio.h FILE
|
||||||
* stddef.h size_t
|
* stddef.h size_t
|
||||||
* stdint.h uint32_t
|
* stdint.h uint32_t
|
||||||
@@ -69,12 +71,16 @@
|
|||||||
*
|
*
|
||||||
* It used to pull in stdlib.h and string.h as well, which nothing here needs and
|
* 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
|
* 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. TODO.md 2.2.16.
|
* of stdlib.h is the same size_t at a fraction of the namespace.
|
||||||
*/
|
*/
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <fcntl.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.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. */
|
/* off_t, for the aksl_fseeko/aksl_ftello pair. POSIX, like aksl_realpath. */
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
@@ -85,7 +91,7 @@ extern "C" {
|
|||||||
/*
|
/*
|
||||||
* Restore the compile-time format/argument checking that callers otherwise lose
|
* Restore the compile-time format/argument checking that callers otherwise lose
|
||||||
* by going through a variadic wrapper: without it, printf("%d", "str") is caught
|
* by going through a variadic wrapper: without it, printf("%d", "str") is caught
|
||||||
* and aksl_printf(&n, "%d", "str") is not. TODO.md 2.2.5.
|
* and aksl_printf(&n, "%d", "str") is not.
|
||||||
*
|
*
|
||||||
* tests/negative/format_mismatch.c is a compile that must fail, which is what
|
* tests/negative/format_mismatch.c is a compile that must fail, which is what
|
||||||
* proves these are still attached.
|
* proves these are still attached.
|
||||||
@@ -807,6 +813,151 @@ 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
|
/** @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 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,
|
* 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.
|
* and every wrapped call clears errno first so the value read back is its own.
|
||||||
* TODO.md 2.2.1.
|
* See UPGRADING.md.
|
||||||
*/
|
*/
|
||||||
#define AKSL_ERRNO_OR(__fallback) (errno != 0 ? errno : (__fallback))
|
#define AKSL_ERRNO_OR(__fallback) (errno != 0 ? errno : (__fallback))
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Data structures -- TODO.md section 3.6.
|
* Data structures.
|
||||||
*
|
*
|
||||||
* Not libc wrappers. These are the parts of the list and tree API that were
|
* 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
|
* 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
|
* 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
|
* 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
|
* bare-node API was written for and wrong for anything larger, which is what
|
||||||
* TODO.md 3.6 means by "a head/tail-tracking container type so append is O(1)".
|
* the collections plan meant 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
|
* 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
|
* 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.
|
* 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
|
* These are the functions that set and read aksl_TreeNode.parent, which was
|
||||||
* declared and then never touched by anything in the library (TODO.md 2.2.15).
|
* declared and then never touched by anything in the library.
|
||||||
* aksl_tree_remove needs it: relinking a node's replacement means telling that
|
* 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
|
* node's parent about it, and finding the parent by walking from the root again
|
||||||
* would turn a removal into a second search.
|
* 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 TODO.md 3.6's hash request. It differs from djb2 in
|
* FNV-1a, the other half of the collections plan's hash request. It differs from djb2 in
|
||||||
* XOR-then-multiply rather than multiply-then-add, which mixes the low bits
|
* 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,
|
* rather better -- worth having when the keys are short and share a prefix,
|
||||||
* which is exactly what identifiers in a symbol table look like.
|
* 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.
|
* Fixed-capacity, open-addressed, linear-probing, string-keyed.
|
||||||
*
|
*
|
||||||
* The shape is akbasic's src/symtab.c, which TODO.md 3.6 says is "worth lifting
|
* The shape is akbasic's src/symtab.c, which the collections plan called "worth lifting
|
||||||
* more or less verbatim": the caller supplies the slot array, the map refuses
|
* 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
|
* 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.
|
* 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
|
* 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
|
* a fixed buffer, and no answer at all when the output length is not known in
|
||||||
* advance -- which is why TODO.md 3.6 asks for this to "make the snprintf and
|
* advance -- which is why the collections plan asked for this to "make the snprintf and
|
||||||
* strcat wrappers pleasant to use". Building a diagnostic, a serialised record
|
* strcat wrappers pleasant to use". Building a diagnostic, a serialised record
|
||||||
* or a generated line means appending to something that grows.
|
* or a generated line means appending to something that grows.
|
||||||
*
|
*
|
||||||
|
|||||||
69
src/dir.c
Normal file
69
src/dir.c
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/* 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
Normal file
108
src/stat.c
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
* 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
|
* 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 --
|
* out-param, and is left untouched -- still valid, still the caller's to free --
|
||||||
* whenever an error is raised. TODO.md 3.1.
|
* whenever an error is raised.
|
||||||
*/
|
*/
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_realloc(void **ptr, size_t size)
|
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.
|
* Frees and clears in one step, so the pointer cannot be used or freed twice.
|
||||||
* TODO.md 2.2.13 -- aksl_free leaves the caller holding a dangling pointer, and
|
* aksl_free leaves the caller holding a dangling pointer, and
|
||||||
* "remember to NULL it afterwards" is exactly the discipline this library is
|
* "remember to NULL it afterwards" is exactly the discipline this library is
|
||||||
* supposed to make unnecessary rather than merely possible.
|
* 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
|
* memset(3) and memcpy(3) cannot fail. They return their destination pointer
|
||||||
* unconditionally, so the old FAIL_ZERO_RETURN(e, memset(...), ...) and
|
* unconditionally, so the old FAIL_ZERO_RETURN(e, memset(...), ...) and
|
||||||
* (memcpy(...) == d) checks were dead code that read as though there were a
|
* (memcpy(...) == d) checks were dead code that read as though there were a
|
||||||
* failure mode to catch -- TODO.md 2.2.11. What is worth checking is the
|
* failure mode to catch. What is worth checking is the
|
||||||
* arguments, which is all that is checked now.
|
* arguments, which is all that is checked now.
|
||||||
*/
|
*/
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_memset(void *s, int c, size_t n)
|
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
|
* pathname and mode are checked. fopen(NULL, ...) is undefined behaviour, and
|
||||||
* this wrapper used to hand both straight through unexamined -- reachable from
|
* this wrapper used to hand both straight through unexamined -- reachable from
|
||||||
* user input in practice, which is why akbasic validates the filename itself
|
* user input in practice, which is why akbasic validates the filename itself
|
||||||
* before calling DLOAD/DSAVE with a comment pointing at TODO.md 2.2.2.
|
* before calling DLOAD/DSAVE with a comment pointing at.
|
||||||
*/
|
*/
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_fopen(
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_fopen(
|
||||||
const char *pathname,
|
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
|
* fread and fwrite both report how much they actually transferred, through a
|
||||||
* required out-param. Three things were wrong with the old pair (TODO.md 2.2.3),
|
* required out-param. Three things were wrong with the old pair,
|
||||||
* and the count is the fix for the worst of them: a caller who got AKERR_EOF had
|
* 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
|
* 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
|
* 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.
|
* Formatted output.
|
||||||
*
|
*
|
||||||
* The va_list forms below do the work and the variadic forms are thin wrappers
|
* The va_list forms below do the work and the variadic forms are thin wrappers
|
||||||
* over them, which is both what §3.1 of TODO.md asked for -- so consumers can
|
* over them, which is both what the wrapper contract asked for -- so consumers can
|
||||||
* build their own variadic wrappers -- and what makes the va_end rule below
|
* build their own variadic wrappers -- and what makes the va_end rule below
|
||||||
* checkable in one place instead of three.
|
* 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
|
* 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
|
* 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
|
* line of program output without anything visibly misbehaving, which is
|
||||||
* exactly what made it worth fixing before something did. TODO.md 2.1.4.
|
* exactly what made it worth fixing before something did.
|
||||||
* - *count is written on every path. It used to be left holding vprintf's -1
|
* - *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
|
* 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
|
* 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
|
* 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
|
* error-handling wrapper around an unbounded write is precisely the sharp edge
|
||||||
* this library exists to remove. aksl_snprintf replaces it, and treats
|
* this library exists to remove. aksl_snprintf replaces it, and treats
|
||||||
* truncation as the failure it is rather than as a short success. TODO.md 2.2.4.
|
* truncation as the failure it is rather than as a short success.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_vprintf(int *count, const char *restrict format, va_list args)
|
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
|
* 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
|
* 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
|
* it that nothing can execute -- dead code that reads as though there were a
|
||||||
* failure mode to catch, which is exactly what TODO.md 2.2.11 recorded
|
* failure mode to catch, which is exactly what was recorded
|
||||||
* against the old aksl_memset and aksl_memcpy and what removing those was
|
* 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.
|
* 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("99999999999999999999") handed back success and a wrapped value, because
|
||||||
* atoi(3) has no error channel at all. A library whose entire value proposition
|
* 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.
|
* is turning silent libc failures into error contexts cannot ship that.
|
||||||
* TODO.md 2.1.5.
|
* See UPGRADING.md.
|
||||||
*
|
*
|
||||||
* The strto* wrappers below are the real implementation and the ato* wrappers
|
* The strto* wrappers below are the real implementation and the ato* wrappers
|
||||||
* are three-line calls into them, which is what TODO.md 3.1 asked for on its own
|
* are three-line calls into them, which is what the wrapper contract asked for on its own
|
||||||
* account -- akbasic had to hand-write ~60 lines of exactly this (its
|
* account -- akbasic had to hand-write ~60 lines of exactly this (its
|
||||||
* src/convert.c) because the library would not do it.
|
* 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.
|
* realpath(3), with the destination buffer made expressible.
|
||||||
*
|
*
|
||||||
* The old two-argument form had three separate problems, all of them reachable
|
* The old two-argument form had three separate problems, all of them reachable
|
||||||
* from ordinary use (TODO.md 2.1.6): resolved_path was never NULL-checked, so
|
* from ordinary use: resolved_path was never NULL-checked, so
|
||||||
* realpath(path, NULL) allocated a buffer the wrapper then discarded and leaked;
|
* 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
|
* 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
|
* 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
|
* negative value: the hash differed from canonical djb2, and -- worse -- differed
|
||||||
* between platforms depending on the signedness of char. Benign for the 7-bit
|
* 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
|
* ASCII identifiers akbasic hashes, and quietly wrong for the first caller to
|
||||||
* key a table on a filename or a UTF-8 string literal. TODO.md 2.2.6.
|
* key a table on a filename or a UTF-8 string literal.
|
||||||
*/
|
*/
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_djb2(const char *str, size_t len, uint32_t *hashval)
|
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);
|
SUCCEED_RETURN(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The NUL-terminated convenience form TODO.md 3.6 asked for. */
|
/* The NUL-terminated convenience form the collections plan asked for. */
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_djb2_str(const char *str, uint32_t *hashval)
|
akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_djb2_str(const char *str, uint32_t *hashval)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(e);
|
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
|
* Two separate walks, deliberately. The Floyd pass answers "is this list
|
||||||
* finite"; it says nothing about where the tail is, because `slow` stops at
|
* 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
|
* the midpoint. Conflating the two is what made this function truncate every
|
||||||
* list of two or more nodes -- see TODO.md 2.1.1.
|
* list of two or more nodes.
|
||||||
*/
|
*/
|
||||||
while ( fast != NULL && fast->next != NULL ) {
|
while ( fast != NULL && fast->next != NULL ) {
|
||||||
slow = slow->next;
|
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
|
* `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
|
* 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 -- TODO.md 2.2.12. Taking
|
* there was no way for the caller to learn the new one. Taking
|
||||||
* the head by reference makes the correct call the only call that compiles.
|
* 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)
|
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
|
* 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
|
* before its first use, because append and iterate both read next/prev -- and a
|
||||||
* stack-allocated node that skipped it walked into garbage. TODO.md 2.2.14.
|
* stack-allocated node that skipped it walked into garbage.
|
||||||
*/
|
*/
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_node_init(aksl_ListNode *node, void *data)
|
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
|
* 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
|
* 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
|
* allocate and release these -- which is what they were always documented to do
|
||||||
* and never actually did (TODO.md 2.2.8).
|
* and never actually did.
|
||||||
*/
|
*/
|
||||||
typedef struct TreeQueueEntry {
|
typedef struct TreeQueueEntry {
|
||||||
struct TreeQueueEntry *next;
|
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
|
* 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
|
* 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
|
* it and returned success, the parent's PASS therefore saw nothing wrong, and
|
||||||
* the walk carried on into the sibling subtree -- TODO.md 2.1.3.
|
* the walk carried on into the sibling subtree.
|
||||||
*
|
*
|
||||||
* `path` is the chain of ancestors of `root` and `depth` its length. Checking
|
* `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
|
* 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
|
* infinite recursion into AKERR_CIRCULAR_REFERENCE, and the depth cap catches the
|
||||||
* degenerate chain that is merely too deep to recurse over (TODO.md 2.2.7).
|
* degenerate chain that is merely too deep to recurse over.
|
||||||
*/
|
*/
|
||||||
static akerr_ErrorContext AKERR_NOIGNORE *tree_dfs_walk(
|
static akerr_ErrorContext AKERR_NOIGNORE *tree_dfs_walk(
|
||||||
aksl_TreeNode *root,
|
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 --
|
* 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
|
* and AKSL_TREE_SEARCH_VISIT, which the header documented but nothing
|
||||||
* implemented -- fell straight through to success having visited nothing
|
* implemented -- fell straight through to success having visited nothing
|
||||||
* (TODO.md 2.2.9).
|
* See UPGRADING.md.
|
||||||
*/
|
*/
|
||||||
switch ( searchmode ) {
|
switch ( searchmode ) {
|
||||||
case AKSL_TREE_SEARCH_DFS_PREORDER:
|
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
|
* 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
|
* midpoint, and starting the visit there skipped the whole first half of the
|
||||||
* list including the head -- see TODO.md 2.1.2.
|
* list including the head.
|
||||||
*/
|
*/
|
||||||
while ( node != NULL ) {
|
while ( node != NULL ) {
|
||||||
ATTEMPT {
|
ATTEMPT {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* stdio.h wrappers beyond fopen/fread/fwrite/fclose -- TODO.md section 3.1.
|
* stdio.h wrappers beyond fopen/fread/fwrite/fclose.
|
||||||
*
|
*
|
||||||
* Positioning, flushing, character and line I/O, stream state, formatted input,
|
* Positioning, flushing, character and line I/O, stream state, formatted input,
|
||||||
* and the file-level operations that go with them.
|
* and the file-level operations that go with them.
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* string.h wrappers -- TODO.md section 3.1.
|
* string.h wrappers.
|
||||||
*
|
*
|
||||||
* This is the section akbasic needed most and could not have: across its source
|
* 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
|
* 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
|
* 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
|
* 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
|
* an explicit NUL -- which is exactly the "truncation reported as an error
|
||||||
* rather than silently accepted" that TODO.md 3.1 asks for.
|
* rather than silently accepted" that the wrapper contract asks for.
|
||||||
*
|
*
|
||||||
* Two conventions run through the whole file.
|
* Two conventions run through the whole file.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* NEGATIVE COMPILE TEST -- TODO.md sections 1.3 and 2.2.5.
|
* NEGATIVE COMPILE TEST.
|
||||||
*
|
*
|
||||||
* This file must NOT compile. It is built by the CTest entry
|
* This file must NOT compile. It is built by the CTest entry
|
||||||
* `negative_format_mismatch` with -Werror, and that test is marked WILL_FAIL.
|
* `negative_format_mismatch` with -Werror, and that test is marked WILL_FAIL.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* NEGATIVE COMPILE TEST -- TODO.md section 1.9.
|
* NEGATIVE COMPILE TEST.
|
||||||
*
|
*
|
||||||
* This file must NOT compile. It is built by the CTest entry `negative_noignore`
|
* 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
|
* 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, TODO.md section 3.6.
|
* List and tree additions -- src/collections.c.
|
||||||
*
|
*
|
||||||
* The bare-node list functions, the tracked aksl_List container, and the binary
|
* 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.
|
* 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
|
* 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
|
* 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
|
* and exhaust the pool -- the failure mode the whole pool-accounting section of
|
||||||
* TODO.md 1.9 exists to catch.
|
* the cross-cutting wrapper contract exists to catch.
|
||||||
*/
|
*/
|
||||||
static akerr_ErrorContext AKERR_NOIGNORE *free_that_always_fails(void *ptr)
|
static akerr_ErrorContext AKERR_NOIGNORE *free_that_always_fails(void *ptr)
|
||||||
{
|
{
|
||||||
@@ -800,7 +800,7 @@ static int test_tree_insert_orders_the_leaves(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO.md 2.2.15: aksl_TreeNode.parent was declared and never touched by
|
* aksl_TreeNode.parent was declared and never touched by
|
||||||
* anything in the library. These are the functions that set it, and
|
* anything in the library. These are the functions that set it, and
|
||||||
* aksl_tree_remove is the one that needs it.
|
* aksl_tree_remove is the one that needs it.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* String -> number wrappers: aksl_atoi / atol / atoll / atof.
|
* String -> number wrappers: aksl_atoi / atol / atoll / atof.
|
||||||
*
|
*
|
||||||
* TODO.md section 1.4, complete. The cases that used to live in
|
* Numeric conversion, complete. The cases that used to live in
|
||||||
* tests/test_convert_strict.c -- registered as a known failure because the ato*
|
* 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
|
* 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
|
* 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
|
* "0x10" through the ato* forms is base 10, so parsing stops at the 'x' and the
|
||||||
* rest is trailing junk. TODO.md 1.4 left this open; the answer is that the
|
* rest is trailing junk. The wrapper plan left this open; the answer is that the
|
||||||
* prefix-honouring parse is aksl_strtol(nptr, NULL, 0, &dest), and tests/
|
* prefix-honouring parse is aksl_strtol(nptr, NULL, 0, &dest), and tests/
|
||||||
* test_strto.c holds that half.
|
* test_strto.c holds that half.
|
||||||
*/
|
*/
|
||||||
@@ -149,7 +149,7 @@ static int test_atoi_narrows_to_int_range(void)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO.md 1.4: the type boundaries round-trip exactly rather than nearly. */
|
/* The type boundaries round-trip exactly rather than nearly. */
|
||||||
static int test_type_boundaries_round_trip(void)
|
static int test_type_boundaries_round_trip(void)
|
||||||
{
|
{
|
||||||
char buf[64];
|
char buf[64];
|
||||||
@@ -229,7 +229,7 @@ static int test_atof_converts_and_rejects_null(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO.md 1.4 left "inf" and "nan" open. They are accepted, because strtod(3)
|
* The wrapper plan left "inf" and "nan" open. They are accepted, because strtod(3)
|
||||||
* accepts them and because they are exact round-trips rather than approximations
|
* 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
|
* of something else -- a caller who did not want them has a domain check to make
|
||||||
* that this library cannot make for it.
|
* that this library cannot make for it.
|
||||||
|
|||||||
147
tests/test_dir.c
Normal file
147
tests/test_dir.c
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
#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
|
* Formatted-output wrappers: aksl_printf / aksl_fprintf / aksl_snprintf and
|
||||||
* their va_list forms.
|
* their va_list forms.
|
||||||
*
|
*
|
||||||
* TODO.md section 1.3, now complete. Each happy path asserts both halves of the
|
* Formatted output, complete. Each happy path asserts both halves of the
|
||||||
* contract -- the byte count handed back through *count and the text that
|
* contract -- the byte count handed back through *count and the text that
|
||||||
* actually landed somewhere -- and every pointer argument is checked for its
|
* actually landed somewhere -- and every pointer argument is checked for its
|
||||||
* NULL guard.
|
* NULL guard.
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
* points at the formatted-output wrapper under test and not at the stream
|
* points at the formatted-output wrapper under test and not at the stream
|
||||||
* wrappers, which tests/test_stream.c covers.
|
* wrappers, which tests/test_stream.c covers.
|
||||||
*
|
*
|
||||||
* aksl_sprintf is gone (TODO.md 2.2.4) and aksl_snprintf takes its place, so the
|
* aksl_sprintf is gone and aksl_snprintf takes its place, so the
|
||||||
* destination-overflow case that could not previously be written is here: it is
|
* destination-overflow case that could not previously be written is here: it is
|
||||||
* AKERR_OUTOFBOUNDS, not the short success snprintf(3) would have reported.
|
* 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
|
* 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:
|
* errno it saw (EBADF on glibc). *count is 0 afterwards, not vfprintf's -1:
|
||||||
* TODO.md 1.3 recorded the negative count as a contract gap, and this is the
|
* The wrapper plan recorded the negative count as a contract gap, and this is the
|
||||||
* assertion that closes it.
|
* assertion that closes it.
|
||||||
*/
|
*/
|
||||||
static int test_fprintf_to_read_only_stream_reports_errno(void)
|
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 TODO.md 3.1
|
* The va_list forms are what the variadic ones are built on, and the wrapper contract
|
||||||
* wanted them exposed so consumers can write their own variadic wrappers. This
|
* wanted them exposed so consumers can write their own variadic wrappers. This
|
||||||
* is a consumer doing exactly that.
|
* 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 (TODO.md 2.1.4). Nothing here can
|
* Regression cover for the missing va_end. Nothing here can
|
||||||
* assert on register-save state directly; the point is to run the variadic
|
* assert on register-save state directly; the point is to run the variadic
|
||||||
* wrappers enough times, with enough arguments, that the sanitizer build has
|
* wrappers enough times, with enough arguments, that the sanitizer build has
|
||||||
* something to trip over.
|
* something to trip over.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* The fixed-capacity hash map and FNV-1a -- src/collections.c, TODO.md 3.6.
|
* The fixed-capacity hash map and FNV-1a -- src/collections.c.
|
||||||
*
|
*
|
||||||
* "The single most obviously-missing data structure in the library", by the
|
* "The single most obviously-missing data structure in the library", by the
|
||||||
* TODO's own account: akbasic needed one three times over -- variables,
|
* TODO's own account: akbasic needed one three times over -- variables,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Linked list -- TODO.md section 1.7, complete.
|
* Linked list.
|
||||||
*
|
*
|
||||||
* The two confirmed list defects are fixed, so the tests that used to live in
|
* 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
|
* 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
|
|||||||
/* ---------------------------------------------------------------------- */
|
/* ---------------------------------------------------------------------- */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO.md 2.2.14: every caller used to have to remember to memset a node before
|
* 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.
|
* its first use, and a stack node that skipped it walked straight into garbage.
|
||||||
*/
|
*/
|
||||||
static int test_node_init_zeroes_the_links(void)
|
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
|
* 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
|
* 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
|
* the midpoint rather than the last node. Appending n1..n4 to n0 produced the
|
||||||
* chain "n0 -> n4" and silently dropped n1, n2 and n3. TODO.md 2.1.1.
|
* chain "n0 -> n4" and silently dropped n1, n2 and n3.
|
||||||
*/
|
*/
|
||||||
static int test_append_builds_the_whole_chain(void)
|
static int test_append_builds_the_whole_chain(void)
|
||||||
{
|
{
|
||||||
@@ -218,7 +218,7 @@ static int test_append_detects_cycle_below_the_head(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO.md 1.7 asked for the aliasing contract to be defined. It is refusal:
|
* The wrapper plan asked for the aliasing contract to be defined. It is refusal:
|
||||||
* relinking a node that is already in the list would orphan everything between
|
* 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 --
|
* its old position and the tail, so the tail walk -- which happens anyway --
|
||||||
* doubles as the check.
|
* 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
|
* 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
|
* 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
|
* to start from there -- so the whole first half of the list, head included, was
|
||||||
* never passed to the callback at all. TODO.md 2.1.2.
|
* never passed to the callback at all.
|
||||||
*/
|
*/
|
||||||
static int test_iterate_visits_every_node_from_the_head(void)
|
static int test_iterate_visits_every_node_from_the_head(void)
|
||||||
{
|
{
|
||||||
@@ -470,7 +470,7 @@ static int test_pop_middle_node(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO.md 2.2.12: popping the head used to leave the caller's own head pointer
|
* 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
|
* 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.
|
* 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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO.md 1.7's pool-accounting case. AKSL_RUN already asserts that each test
|
* The wrapper plan'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
|
* 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
|
* 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
|
* error and forgets to release it shows up as an outright exhaustion rather than
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* Memory wrappers -- TODO.md section 1.1, now complete, plus the additions from
|
* Memory wrappers, complete, plus the additions from
|
||||||
* section 3.1.
|
* section 3.1.
|
||||||
*
|
*
|
||||||
* The three cases 1.1 left open are all pinned here: malloc(0) is AKERR_VALUE
|
* The three cases the wrapper plan left open are all pinned here: malloc(0) is AKERR_VALUE
|
||||||
* rather than whatever errno happened to hold when the platform's malloc(0)
|
* rather than whatever errno happened to hold when the platform's malloc(0)
|
||||||
* returned NULL; an allocation the system cannot satisfy reports ENOMEM and
|
* returned NULL; an allocation the system cannot satisfy reports ENOMEM and
|
||||||
* leaves *dst NULL rather than garbage; and overlapping memcpy is refused with
|
* leaves *dst NULL rather than garbage; and overlapping memcpy is refused with
|
||||||
@@ -32,7 +32,7 @@ static int test_malloc_rejects_null_destination(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO.md 1.1 asked for this contract to be pinned down. malloc(0) is allowed to
|
* The wrapper plan 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
|
* 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
|
* 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
|
* 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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO.md 1.1's open question, decided: overlap is AKERR_VALUE. memcpy(3) calls
|
* The wrapper plan's open question, decided: overlap is AKERR_VALUE. memcpy(3) calls
|
||||||
* it undefined behaviour, which in practice means "works until the day a
|
* 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
|
* compiler version or a length changes and it does not". Callers who mean to
|
||||||
* overlap want aksl_memmove, and the message says so.
|
* overlap want aksl_memmove, and the message says so.
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* aksl_realpath and aksl_realpath_alloc -- TODO.md section 1.5, now complete.
|
* aksl_realpath and aksl_realpath_alloc.
|
||||||
*
|
*
|
||||||
* The happy paths compare against realpath(3) itself rather than against a
|
* 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
|
* 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.
|
* 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
|
* The failure cases now pass an *uninitialised* resolved_path on purpose. That
|
||||||
* used to be the crash case (TODO.md 2.1.6): the wrapper's own error path
|
* used to be the crash case: the wrapper's own error path
|
||||||
* formatted the buffer with %s while realpath(3) leaves its contents
|
* formatted the buffer with %s while realpath(3) leaves its contents
|
||||||
* unspecified on failure, so the library read uninitialised memory while
|
* unspecified on failure, so the library read uninitialised memory while
|
||||||
* reporting an error. The message names only the input path now, and this test
|
* 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)),
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_realpath(NULL, resolved, sizeof(resolved)),
|
||||||
AKERR_NULLPOINTER, "path=");
|
AKERR_NULLPOINTER, "path=");
|
||||||
/*
|
/*
|
||||||
* TODO.md 2.1.6: this used to be unchecked, and realpath(path, NULL)
|
* this used to be unchecked, and realpath(path, NULL)
|
||||||
* allocated a buffer that the wrapper then discarded and leaked.
|
* allocated a buffer that the wrapper then discarded and leaked.
|
||||||
*/
|
*/
|
||||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_realpath("/tmp", NULL, PATH_MAX),
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_realpath("/tmp", NULL, PATH_MAX),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Cross-cutting properties every wrapper has to have -- TODO.md section 1.9.
|
* Cross-cutting properties every wrapper has to have.
|
||||||
*
|
*
|
||||||
* Two of them, and neither is about what any individual function computes:
|
* Two of them, and neither is about what any individual function computes:
|
||||||
*
|
*
|
||||||
|
|||||||
134
tests/test_stat.c
Normal file
134
tests/test_stat.c
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
/* 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, TODO.md 3.6.
|
* The growable string buffer -- src/collections.c.
|
||||||
*
|
*
|
||||||
* The bounded formatting wrappers are the right answer when the destination is
|
* 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.
|
* 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: aksl_fopen / aksl_fread / aksl_fwrite / aksl_fclose.
|
||||||
*
|
*
|
||||||
* TODO.md section 1.2, now complete. The happy paths, the round trip, every
|
* Stream wrappers, complete. The happy paths, the round trip, every
|
||||||
* NULL guard, both stream-error statuses, the transferred-member count that
|
* 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
|
* 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
|
* 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),
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fopen(path, "r", NULL),
|
||||||
AKERR_NULLPOINTER, "fp=");
|
AKERR_NULLPOINTER, "fp=");
|
||||||
/* TODO.md 2.2.2: both of these used to go straight through to fopen(3). */
|
/* both of these used to go straight through to fopen(3). */
|
||||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fopen(NULL, "r", &fp),
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fopen(NULL, "r", &fp),
|
||||||
AKERR_NULLPOINTER, "pathname=");
|
AKERR_NULLPOINTER, "pathname=");
|
||||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fopen(path, NULL, &fp),
|
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_OK(aksl_fopen(path, "r", &fp));
|
||||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(buf, 1, sizeof(buf), fp, &moved),
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(buf, 1, sizeof(buf), fp, &moved),
|
||||||
AKERR_EOF, "EOF");
|
AKERR_EOF, "EOF");
|
||||||
/* TODO.md 2.2.3: this is what the caller could not previously find out. */
|
/* this is what the caller could not previously find out. */
|
||||||
AKSL_CHECK(moved == 4);
|
AKSL_CHECK(moved == 4);
|
||||||
AKSL_CHECK_OK(aksl_fclose(fp));
|
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
|
* 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
|
* AKERR_IO without ever consulting errno. Routing it through AKSL_ERRNO_OR
|
||||||
* (TODO.md 2.2.1) keeps AKERR_IO as the fallback for the case where the stream
|
* 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.
|
* 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)
|
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),
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(buf, 1, sizeof(buf), NULL, &moved),
|
||||||
AKERR_NULLPOINTER, "fp=");
|
AKERR_NULLPOINTER, "fp=");
|
||||||
/* TODO.md 2.2.3: ptr was never checked in either direction. */
|
/* ptr was never checked in either direction. */
|
||||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(NULL, 1, sizeof(buf), fp, &moved),
|
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(NULL, 1, sizeof(buf), fp, &moved),
|
||||||
AKERR_NULLPOINTER, "ptr=");
|
AKERR_NULLPOINTER, "ptr=");
|
||||||
AKSL_CHECK_STATUS_MSG_CONTAINS(aksl_fread(buf, 1, sizeof(buf), fp, NULL),
|
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, TODO.md 3.1.
|
* Stream wrappers beyond open/read/write/close -- src/stream.c.
|
||||||
*
|
*
|
||||||
* tests/test_stream.c covers fopen/fread/fwrite/fclose. This one covers
|
* tests/test_stream.c covers fopen/fread/fwrite/fclose. This one covers
|
||||||
* positioning, flushing, character and line I/O, stream state, and formatted
|
* positioning, flushing, character and line I/O, stream state, and formatted
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* aksl_strhash_djb2 -- TODO.md section 1.6.
|
* aksl_strhash_djb2.
|
||||||
*
|
*
|
||||||
* The expected values are the canonical djb2 ones: h = 5381, then
|
* 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
|
* 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
|
* 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
|
* the test therefore says nothing either way about byte signedness. The high-bit
|
||||||
* vector is the one that pins TODO.md 2.2.6 down.
|
* vector is the one that pins the sign-extension defect down.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "aksl_capture.h"
|
#include "aksl_capture.h"
|
||||||
@@ -47,7 +47,7 @@ static int test_known_answer_vectors(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO.md 2.2.6, pinned. The cursor is an unsigned char * now, so bytes at or
|
* 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
|
* 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
|
* ARM Linux made them negative, giving 5859874 here instead of 5868578 -- a hash
|
||||||
* that disagreed with canonical djb2 and, worse, disagreed with itself across
|
* 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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The NUL-terminated convenience form (TODO.md 3.6) agrees with the length one. */
|
/* The NUL-terminated convenience form agrees with the length one. */
|
||||||
static int test_str_form_matches_the_length_form(void)
|
static int test_str_form_matches_the_length_form(void)
|
||||||
{
|
{
|
||||||
const char *s = "libakstdlib";
|
const char *s = "libakstdlib";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* String wrappers -- src/string.c, TODO.md section 3.1.
|
* String wrappers -- src/string.c.
|
||||||
*
|
*
|
||||||
* The two contracts worth testing hardest are the ones that differ from libc:
|
* 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
|
* every copying function takes the destination size and treats truncation as an
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* The strto* family -- TODO.md section 3.1.
|
* The strto* family.
|
||||||
*
|
*
|
||||||
* These are the real implementation behind the ato* wrappers and the thing
|
* 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
|
* akbasic had to hand-write for itself (its src/convert.c, ~60 lines) because
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Tree traversal -- TODO.md section 1.8, complete.
|
* Tree traversal.
|
||||||
*
|
*
|
||||||
* The old version of this file counted steps, which cannot tell the three
|
* 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
|
* 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
|
* BFS was AKERR_NOT_IMPLEMENTED, and the lalloc/lfree parameters that existed to
|
||||||
* serve it were defaulted and then never called -- TODO.md 2.2.8 and 2.2.10.
|
* serve it were defaulted and then never called. See UPGRADING.md.
|
||||||
* Both modes work now, and the allocator test below proves the queue is real.
|
* Both modes work now, and the allocator test below proves the queue is real.
|
||||||
*/
|
*/
|
||||||
static int test_bfs_visits_level_by_level(void)
|
static int test_bfs_visits_level_by_level(void)
|
||||||
@@ -249,7 +249,7 @@ static akerr_ErrorContext AKERR_NOIGNORE *counting_free(void *ptr)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO.md 1.8: "Custom lalloc/lfree are actually invoked -- currently they are
|
* The wrapper plan asked that "custom lalloc/lfree are actually invoked -- currently they are
|
||||||
* stored and never called". They are called now, once per node enqueued, and
|
* 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,
|
* every allocation is released. The depth-first modes allocate nothing at all,
|
||||||
* which is the other half of the contract.
|
* which is the other half of the contract.
|
||||||
@@ -382,7 +382,7 @@ static int test_degenerate_chains(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO.md 1.8 / 2.2.7: a chain deeper than the recursion can take. It used to
|
* A chain deeper than the recursion can take. It used to
|
||||||
* overflow the stack; it is AKERR_OUTOFBOUNDS now, and the message names the
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* into the sibling subtree. All seven nodes were visited no matter where the
|
||||||
* break was raised. TODO.md 2.1.3.
|
* break was raised.
|
||||||
*
|
*
|
||||||
* One case per order, each breaking on a node that is *not* last in that order --
|
* 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
|
* 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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO.md 2.2.9: the switch had no default, so an unrecognised mode -- and
|
* the switch had no default, so an unrecognised mode -- and
|
||||||
* AKSL_TREE_SEARCH_VISIT, which the header documented but nothing implemented --
|
* AKSL_TREE_SEARCH_VISIT, which the header documented but nothing implemented --
|
||||||
* fell straight through to SUCCEED_RETURN having visited nothing at all. A
|
* fell straight through to SUCCEED_RETURN having visited nothing at all. A
|
||||||
* traversal that silently did not happen, reported as success.
|
* traversal that silently did not happen, reported as success.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Version reporting -- TODO.md section 2.2.16.
|
* Version reporting.
|
||||||
*
|
*
|
||||||
* There are two versions in play and the whole point of this API is that they
|
* There are two versions in play and the whole point of this API is that they
|
||||||
* are allowed to differ:
|
* are allowed to differ:
|
||||||
|
|||||||
Reference in New Issue
Block a user