Skip to content

proven_c_lib-v0.6.0

Latest

Choose a tag to compare

@rubidus-api rubidus-api released this 01 Oct 10:21

A MINOR release: nothing public removed. It carries the whole-library review (RFC-0009): defects
fixed, four security changes, faster CRC-32, SHA-256, directory listing and stream readers, map
iteration, in-place array editing, job groups, and a build that links its tests in parallel.
Behaviour changes, each where the old behaviour was wrong or unsafe: an exclusive create over an
existing name is PROVEN_ERR_EXISTS instead of PROVEN_ERR_IO; environment values and directory
entry names that are not valid text are PROVEN_ERR_INVALID_ENCODING on POSIX too (they were
returned as raw bytes; Windows substituted U+FFFD); proven_map_hash values for default
integer-key maps change and now differ per process; and proven_reader_buffered_t gains a field.

Added

  • PROVEN_ERR_EXISTS: an exclusive create found the name already there (RFC-0009 X-009).
    proven_fs_open with PROVEN_FS_CREATE_NEW over an existing name returned PROVEN_ERR_IO,
    the same code as every unclassified failure, so a caller could not tell "choose another name"
    from "the disk failed". It now returns the new code, on POSIX (EEXIST) and Windows
    (ERROR_FILE_EXISTS). PROVEN_ERR_LAST moves to it; alias XCV_ERR_EXISTS. Code that
    compared that case against PROVEN_ERR_IO must compare against PROVEN_ERR_EXISTS.
  • proven_fs_is_staging_name(name): true for a name with the shape of the temp file an
    atomic or durable write leaves behind when killed mid-write (today's and the old one), for a
    cleanup job. The library itself never removes one it did not just create (RFC-0009 D-001).
  • proven_random_u64_checked(&out): one strong word, and false when there is none
    (RFC-0009 S-002). proven_random_u64 returns 0 when the entropy source fails - on a bare-metal
    target with no source that is every call - and its documentation offered it for tokens. A
    token of 0 is a predictable secret nothing reports. The checked form is for secrets; the
    unchecked one stays, now documented as not for them. Alias xcv_random_u64_checked.
  • proven_fs_read_all_bounded(alloc, path, max_bytes): a whole-file read with a ceiling
    (RFC-0009 S-003). proven_fs_read_all reads until the source ends, so a path naming
    /dev/zero, an endless FIFO, or a far larger file than expected grows the buffer until the
    allocator refuses. The bounded form returns PROVEN_ERR_OUT_OF_BOUNDS and no buffer past
    max_bytes: before allocating when the reported size says so, and as the extra byte arrives
    when the size says nothing. The unbounded functions now say they are for trusted paths. Alias
    xcv_fs_read_all_bounded.
  • A map can be walked: proven_map_iter_init / proven_map_iter_next, and proven_map_len
    (RFC-0009 X-001). Before, a program could not list what it stored, serialise a map, or release
    what its values own without keeping a second list of keys. The walk is in bucket order; removing
    entries (including the current one) and updating values are allowed during it; a new key or a
    reserve that rehashes the map makes the next step PROVEN_ERR_INVALID_STATE instead of skipping
    or repeating entries. Aliases xcv_map_iter_t, xcv_map_iter_init, xcv_map_iter_next,
    xcv_map_len.
  • Arrays can be edited in place: proven_array_clear, _truncate, _insert, _remove_at,
    _swap_remove and _extend
    (RFC-0009 X-002) - the operations callers wrote by hand on top
    of get_mut and len. Each changes nothing when it fails; insert and extend accept
    elements taken from the array itself (a source that only partly overlaps it is refused), and
    extend reallocates at most once. Aliases xcv_array_*.
  • Jobs: proven_job_submit_ex says why a submit was refused, and job groups can be waited on
    (RFC-0009 X-003). proven_job_submit returns false both for a full queue and for a closed
    system, which call for opposite responses; _ex returns PROVEN_ERR_AGAIN or
    PROVEN_ERR_INVALID_STATE. proven_job_group_t counts submitted jobs
    (proven_job_group_init, _submit, _pending); proven_job_group_wait returns when all have
    run, running queued jobs itself while it waits, with what they wrote visible afterwards.
    Aliases xcv_job_submit_ex, xcv_job_group_*.

Changed

  • SHA-256 is about 25% faster (RFC-0009 P-107): proven_sha256_update compresses whole
    64-byte blocks straight from the input instead of copying every byte into its block buffer
    first. Measured on 4 MiB: 4.7 ns per byte, from 6.2. Digests are unchanged.

  • ./nob build -keep-going runs every test and lists the failures (RFC-0009 X-006, first part).
    A run stopped at the first failing test, so one failure hid every other. With the flag, each
    test still runs once and the run ends with a summary and exit status 1.

  • ./nob <mode> -jobs N runs tests in parallel on POSIX (RFC-0009 X-006). Tests created
    fixtures under fixed names in the working directory, so they could only run one at a time. With
    the flag each runs in a directory of its own that links to the repository's top level, its output
    is printed whole in registry order, and stress and benchmark tests still run alone. On the
    16-CPU host a cached ./nob asan takes 8.9 s instead of 16.9, a cached ./nob build 7.1 s
    instead of 10.1. The default stays one test at a time.

  • A clean ./nob build takes 14 s instead of 36 on a 16-CPU host (RFC-0009 P-108). The test
    executables were compiled and linked one at a time - 20 of the 31 seconds a clean build spent
    outside the tests themselves. They are now linked in parallel, up to one per CPU, and then run
    one at a time in registry order as before, with a link failure still reported under the test's
    name. Running the tests in parallel needs per-test scratch files first and is not done.

  • The seven *_internal / *_impl functions in public headers are marked MACRO SUPPORT and are
    not a stable interface
    (RFC-0009 X-004): proven_u8str_fmt_internal, proven_scan_fmt_internal,
    proven_scan_fmt_internal_view, proven_fmt_to_writer_impl, proven_sysio_scanner_scan_impl,
    proven_sysio_print_impl, proven_sysio_scan_chunk_impl. Their signatures may change in a MINOR
    release; call the macros. A source-contract test keeps the list closed.

  • proven_u16_reader_read with a small destination no longer revalidates everything it holds
    (RFC-0009 P-103). Each call decoded and validated the whole staged area - up to 512 units - to
    hand out cap of them: 261 ns per unit at cap 2. It now examines cap + 1 units: 12 ns per
    unit at cap 2, 2.3 at 64 (from 9.6). The text delivered and the error that ends it are the
    same for every cap.

  • proven_reader_read_line searches each byte once (RFC-0009 P-104). It searched from the
    start of the pending line, one byte at a time, after every refill, so a long line arriving in
    small pieces cost time quadratic in its length: 4.8 us per byte for a 16,000-byte line read a
    byte at a time. It now remembers how far it has searched and uses the platform memchr: 21-25 ns
    per byte for that case, and 0.27 ns per byte from 1.33 for 80-byte lines read from a file.
    proven_reader_buffered_t gains a field, scanned, set by proven_reader_buffered.

  • Listing a directory on POSIX costs one metadata call per entry, or none (RFC-0009 P-102).
    proven_fs_dir_next (and so proven_fs_list and the walk) asked for every entry's type twice,
    following and not following symlinks. readdir's d_type already says what most entries are:
    a directory or a special file now needs no call, a regular file one (for its size), and only a
    symlink or a filesystem that leaves d_type unknown takes both. Measured over 50,000 files:
    3.5 us per entry, from 5.9. What is reported is unchanged.

  • CRC-32 is about 6x faster on long inputs (RFC-0009 P-101): slicing-by-8, eight bytes per
    step with independent table lookups. Measured on 1 MiB: 0.36 ns per byte, from 2.30. Output is
    unchanged (checked against the single-table path at every length and offset, and against
    zlib). It costs 7 KiB more read-only data; a build with -DPROVEN_CRC32_SMALL=1 keeps the
    single 1 KiB table.

  • Integer map keys are hashed with the per-process secret in a default map (RFC-0009 S-001).
    They used a public bit-mix finaliser whatever the map was created with, and its inverse is
    public, so anyone choosing the keys - user ids, record numbers from a request - could put any
    number of them in one bucket and make the map quadratic. A default (proven_map_create)
    integer-key map now uses SipHash-1-3 of the key under the same secret as string keys;
    proven_map_create_trusted keeps the finaliser. Measured cost on a default map: about 7 ns
    more per lookup on a 1K-key table, 15-20 ns per insert. proven_map_hash values for default
    integer-key maps change (and differ per process); trusted maps are unchanged.

  • Text from the platform is strict, like all other text (RFC-0009 D-003). An environment value
    or a directory entry name that is not valid text - bytes that are not UTF-8 on POSIX, a lone
    surrogate on Windows - is now PROVEN_ERR_INVALID_ENCODING. Before, POSIX returned the raw
    bytes as PROVEN_OK and Windows silently substituted U+FFFD, which in a listing named a
    different file, or none. proven_fs_dir_next and proven_fs_walk_next report such an entry
    once (empty name, other fields filled in) and go on; proven_fs_list refuses the whole
    listing rather than leave a file out.

  • The library's source list is one manifest, build_sources.inc, with a freestanding attribute
    per source
    (RFC-0009 X-005). It was a literal array in nob.c, and the hosted-only sources
    were listed twice more - for the native freestanding build and for the cross matrix - with
    nothing checking the copies agreed. Both now read the manifest; a source-contract test refuses a
    hand-kept list in nob.c.

Fixed

  • ./nob build failed in an unpacked release archive. It runs scripts/project-check.sh,
    whose checks read the git repository, and the archive has none - so a build from any release
    ZIP stopped at that step. Outside a git checkout the step is now skipped with a
    [PROVEN][PROJECT_CHECK][SKIP] reason=not-a-git-checkout line. The archive also gains
    build_sources.inc, which nob.c now includes.
  • ./nob build could run a test linked against a library object from the previous build.
    A test relinked when a library object was newer than the executable, compared by whole-second
    modification time; an object rebuilt in the same second as the previous link compared equal,
    and the old binary ran. The objects' contents now go into each test link's state hash (and
    into the hash recorded after the link, so a cached build still relinks nothing).
  • The view-taking macros say how to pass a compound literal (RFC-0009 X-008).
    PROVEN_ARG((proven_u8str_view_t){ p, n }) and the same in proven_scan_fmt, the print and
    append_fmt macros and the PROVEN_MAP_*_U8_* wrappers do not compile - the preprocessor splits
    the literal at its comma - and the error names an internal function. The headers and manual
    chapter 1 (EN/KO) now say to pass a variable, a PROVEN_LIT, or the literal in parentheses; a
    test pins the parenthesised form.
  • proven_random_set_source is no longer a data race when called while other threads draw
    (RFC-0009 S-004). The hook was two plain globals, so a late install was undefined behaviour and
    a draw could pair one source's function with the other's context. The pair is now published
    under a sequence count; a draw always sees a function with its own context. Installing once at
    startup is still the advice.
  • Eight leftover staging files no longer block every later atomic write of a path
    (RFC-0009 D-001). proven_fs_write_file_atomic and _durable staged into the fixed names
    <path>.pvtmp00 .. 07 and gave up after them, so eight writers killed mid-write - or anyone
    who could write the directory - made the path unwritable through the library for good, with
    PROVEN_ERR_IO. The suffix is now 13 random characters (64 bits from the entropy source), only
    a name collision is retried, any other failure is returned at once with its own code, and
    sixteen collisions in a row are PROVEN_ERR_EXISTS. Confidentiality was never affected: the
    staging file is created exclusively, so a planted name was refused, not written through.
  • An environment variable set to the empty string read as PROVEN_ERR_IO on Windows
    (RFC-0009 D-002). GetEnvironmentVariableW returns 0 both for failure and for "stored 0
    characters"; GetLastError now tells them apart, so the value is an empty string as on POSIX.
    A value that grows between the sizing call and the read is re-sized instead of failing.
  • fs.h: proven_fs_copy states its contract, and proven_fs_write_file's [[nodiscard]]
    sits on its declaration again
    (RFC-0009 X-007). The copy's whole documentation was one line;
    it now says the copy is not staged (a failure leaves dest partly written), that symbolic
    links are followed at both ends, that dest takes the source's permission bits, and that the
    read-only rule covers it. The attribute had been separated from its declaration by a long
    block comment.