# Upgrading ## 0.1.0 → 0.2.0 This is an ABI break and a source break. Pre-1.0 the soname is `MAJOR.MINOR`, so `libakstdlib.so.0.1` and `libakstdlib.so.0.2` are different libraries as far as the loader is concerned and a consumer built against 0.1 will not silently pick this up. Rebuild against the new header. `AKSL_VERSION_CHECK()` catches the pairing at runtime if a stale `.so` ever does end up on the path. Everything below comes out of `TODO.md` sections 2.1 and 2.2 — the confirmed defects, all of which were reproduced against the 0.1.0 library before being fixed. ### Behaviour changes with the same signature **`aksl_atoi`, `aksl_atol`, `aksl_atoll`, `aksl_atof` report bad conversions.** This is the one to look at first, because the compiler will not tell you about it. They used to return success for anything: `aksl_atoi("not a number", &n)` gave you `0`, and `aksl_atoi("99999999999999999999", &n)` gave you a wrapped value. Now: | input | before | after | |---|---|---| | `"42"` | success, 42 | success, 42 | | `"not a number"` | **success, 0** | `AKERR_VALUE` | | `""` or `" "` | **success, 0** | `AKERR_VALUE` | | `"12abc"` | **success, 12** | `AKERR_VALUE` | | `"0x10"` | **success, 0** | `AKERR_VALUE` (base 10 stops at the `x`) | | `"99999999999999999999"` | **success, garbage** | `ERANGE` | `*dest` is `0` on every failure path. If you were relying on the old silence — treating an unparseable string as zero on purpose — that is now an error you have to handle or deliberately ignore. For `"0x10"` and friends, use `aksl_strtol(nptr, NULL, 0, &dest)`, which honours the `0x` and `0` prefixes. The whole `aksl_strto*` family is new and is what the `ato*` forms are built on. **Errors no longer carry status `0`.** Any wrapper that reported `errno` could previously raise an error whose status was whatever `errno` happened to hold, including `0` — which every `DETECT` and `CATCH` downstream reads as *success* while the context still holds a pool slot. `errno` is now cleared before each wrapped call and read back through a fallback. If you were matching on a specific status, check it is still the one you get; several calls that used to report a flat `AKERR_IO` now report the real `errno` (a read from a write-only stream is `EBADF`, not `AKERR_IO`). **`aksl_malloc(0, &p)` is `AKERR_VALUE`.** It used to depend on whether the platform's `malloc(0)` returned NULL. **`aksl_memcpy` refuses overlapping ranges** with `AKERR_VALUE`. Use `aksl_memmove`. **`aksl_list_append` refuses a node already in the list** with `AKERR_VALUE`. It used to relink it and orphan everything between its old position and the tail. **`aksl_list_append` and `aksl_list_iterate` are correct now.** If you worked around either defect — appending one node at a time and fixing up the links by hand, or starting your own iteration from the head because the callback never saw it — that workaround is now wrong. `append` truncated any list of two or more nodes; `iterate` skipped everything before the list midpoint. **`AKERR_ITERATOR_BREAK` stops a tree traversal.** It previously did not: the walk ran to completion regardless. Code that raised it and relied on the traversal continuing anyway (unlikely, but it was the behaviour) will now stop. **Tree traversal is bounded.** A tree deeper than `AKSL_TREE_MAX_DEPTH` (256) is `AKERR_OUTOFBOUNDS`, and a depth-first walk over a tree whose child points back at an ancestor is `AKERR_CIRCULAR_REFERENCE`. Both used to run until the process died. **An unrecognised `searchmode` is `AKERR_VALUE`.** It used to return success having visited nothing. ### Signature changes **`aksl_sprintf` is gone.** It wrapped `vsprintf`, which cannot be bounded. ```c /* before */ aksl_sprintf(&count, buf, "%s=%d", key, value); /* after */ aksl_snprintf(&count, buf, sizeof(buf), "%s=%d", key, value); ``` Truncation is `AKERR_OUTOFBOUNDS` rather than a short success, and `*count` is `0` on any failure rather than `vsprintf`'s `-1`. **`aksl_realpath` takes the destination's length.** ```c /* before */ char resolved[PATH_MAX]; aksl_realpath(path, resolved); /* after */ char resolved[PATH_MAX]; aksl_realpath(path, resolved, sizeof(resolved)); ``` `buflen` must be at least `PATH_MAX`; `realpath(3)` cannot be bounded below it, so a shorter buffer is `AKERR_OUTOFBOUNDS` rather than an overflow. If you have no `PATH_MAX`-sized buffer to hand, `aksl_realpath_alloc(path, &dest)` allocates one and hands it over — release it with `aksl_free`. **`aksl_fread` and `aksl_fwrite` take a transferred-count out-param.** ```c /* before */ aksl_fread(buf, 1, sizeof(buf), fp); /* after */ size_t got = 0; aksl_fread(buf, 1, sizeof(buf), fp, &got); ``` It is required, not optional, because it is the only way a caller who gets `AKERR_EOF` can find out how much data arrived. It is written on every path including the failure ones. A short transfer with neither EOF nor a stream error set is now `AKERR_IO` rather than success. **`aksl_list_pop` takes the head by reference.** ```c /* before */ aksl_list_pop(node); /* after */ aksl_list_pop(&head, node); /* head is updated when node was the head */ ``` Popping the head used to leave the caller's own head pointer aimed at a now-detached node with no way to learn the new one. **`aksl_tree_iterate` lost its `queue` parameter.** ```c /* before */ aksl_tree_iterate(root, iter, NULL, NULL, mode, data, NULL); /* after */ aksl_tree_iterate(root, iter, NULL, NULL, mode, data); ``` The doc comment told callers to pass `NULL`, which is a sign the queue belonged in an internal helper. `lalloc` and `lfree` stay, and they are actually used now — the breadth-first modes are implemented, where before they raised `AKERR_NOT_IMPLEMENTED`. **`aksl_strhash_djb2` takes `const char *`** and reads bytes as unsigned. Callers no longer have to cast away constness. The hash value **changes** for any input containing a byte ≥ 0x80: it now matches canonical djb2 instead of depending on whether plain `char` is signed on the target. 7-bit ASCII keys are unaffected. If you have persisted djb2 values across a restart, they will not match. **`aksl_fopen` takes `const char *`** for both `pathname` and `mode`, and checks them — `fopen(NULL, ...)` is undefined behaviour and used to go straight through. **`aksl_memcpy` takes `const void *` for its source.** ### Additions Nothing here breaks anything; see `README.md` for the full surface. - `aksl_calloc`, `aksl_realloc`, `aksl_freep`, `aksl_memmove`, `aksl_memcmp`, `aksl_memchr` - `aksl_snprintf`, `aksl_vprintf`, `aksl_vfprintf`, `aksl_vsnprintf` - `aksl_strtol`, `aksl_strtoll`, `aksl_strtoul`, `aksl_strtoull`, `aksl_strtod`, `aksl_strtof`, `aksl_strtold` - the whole of `src/string.c` and `src/stream.c` - `aksl_list_prepend` / `insert_after` / `insert_before` / `length` / `find` / `reverse` / `concat` / `free_all` / `iterate_reverse`, and the `aksl_List` container so append is O(1) - `aksl_tree_insert` / `find` / `remove` / `height` / `count` / `free_all` - `aksl_hashmap_*`, `aksl_strbuf_*`, `aksl_strhash_fnv1a` - `aksl_list_node_init` and `aksl_tree_node_init`, so a caller no longer has to remember to `memset` a node before its first use - the header is `extern "C"`-guarded and pulls in four standard headers rather than six