summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.clang-format32
-rw-r--r--.clang-tidy5
-rw-r--r--.git-blame-ignore-revs5
-rw-r--r--.gitignore15
-rw-r--r--.gitlab-ci.yml28
-rw-r--r--.reuse/dep52
-rw-r--r--LICENSES/0BSD.txt2
-rw-r--r--[l---------]LICENSES/ISC.txt14
-rw-r--r--NEWS30
-rw-r--r--README.md10
-rw-r--r--benchmark/.clang-tidy5
-rw-r--r--benchmark/bench.h16
-rw-r--r--benchmark/dict_bench.c62
-rw-r--r--benchmark/meson.build11
-rw-r--r--benchmark/tree_bench.c122
-rw-r--r--benchmark/warnings.h9
-rw-r--r--doc/Doxyfile.in12
-rw-r--r--doc/conf.py.in4
-rw-r--r--doc/html/meson.build15
-rw-r--r--doc/meson.build3
-rw-r--r--doc/overview_code.c21
-rw-r--r--doc/singlehtml/meson.build15
-rw-r--r--doc/xml/meson.build8
-rw-r--r--include/zix/allocator.h19
-rw-r--r--include/zix/attributes.h74
-rw-r--r--include/zix/btree.h63
-rw-r--r--include/zix/bump_allocator.h7
-rw-r--r--include/zix/digest.h20
-rw-r--r--include/zix/environment.h39
-rw-r--r--include/zix/filesystem.h83
-rw-r--r--include/zix/hash.h71
-rw-r--r--include/zix/path.h90
-rw-r--r--include/zix/ring.h128
-rw-r--r--include/zix/sem.h30
-rw-r--r--include/zix/status.h7
-rw-r--r--include/zix/string_view.h22
-rw-r--r--include/zix/thread.h10
-rw-r--r--include/zix/tree.h62
-rw-r--r--include/zix/warnings.h41
-rw-r--r--include/zix/zix.h39
-rw-r--r--meson.build137
-rw-r--r--meson_options.txt29
-rwxr-xr-xscripts/benchmark.py6
-rw-r--r--src/.clang-tidy7
-rw-r--r--src/allocator.c16
-rw-r--r--src/btree.c108
-rw-r--r--src/bump_allocator.c21
-rw-r--r--src/darwin/sem_darwin.c4
-rw-r--r--src/digest.c31
-rw-r--r--src/errno_status.c74
-rw-r--r--src/errno_status.h12
-rw-r--r--src/filesystem.c119
-rw-r--r--src/hash.c28
-rw-r--r--src/path.c126
-rw-r--r--src/path_iter.h8
-rw-r--r--src/posix/environment_posix.c134
-rw-r--r--src/posix/filesystem_posix.c84
-rw-r--r--src/posix/sem_posix.c15
-rw-r--r--src/posix/system_posix.c43
-rw-r--r--src/posix/thread_posix.c4
-rw-r--r--src/qualifiers.h15
-rw-r--r--src/ring.c38
-rw-r--r--src/status.c7
-rw-r--r--src/string_view.c10
-rw-r--r--src/system.c55
-rw-r--r--src/system.h49
-rw-r--r--src/tree.c64
-rw-r--r--src/tree_debug.h139
-rw-r--r--src/win32/environment_win32.c37
-rw-r--r--src/win32/filesystem_win32.c323
-rw-r--r--src/win32/sem_win32.c4
-rw-r--r--src/win32/system_win32.c49
-rw-r--r--src/win32/thread_win32.c4
-rw-r--r--src/win32/win32_util.c73
-rw-r--r--src/win32/win32_util.h31
-rw-r--r--src/zix_config.h81
-rw-r--r--subprojects/sphinxygen.wrap12
-rw-r--r--test/.clang-tidy10
-rw-r--r--test/cpp/.clang-tidy8
-rw-r--r--test/cpp/test_headers_cpp.cpp31
-rw-r--r--test/cpp/test_path_std.cpp14
-rw-r--r--test/failing_allocator.c30
-rw-r--r--test/failing_allocator.h15
-rw-r--r--test/headers/.clang-tidy4
-rw-r--r--test/headers/test_headers.c31
-rw-r--r--test/lint/meson.build43
-rw-r--r--test/meson.build122
-rw-r--r--test/test_allocator.c6
-rw-r--r--test/test_btree.c45
-rw-r--r--test/test_digest.c7
-rw-r--r--test/test_environment.c143
-rw-r--r--test/test_filesystem.c273
-rw-r--r--test/test_hash.c43
-rw-r--r--test/test_path.c294
-rw-r--r--test/test_ring.c15
-rw-r--r--test/test_sem.c8
-rw-r--r--test/test_status.c2
-rw-r--r--test/test_string_view.c7
-rw-r--r--test/test_thread.c4
-rw-r--r--test/test_tree.c12
100 files changed, 2709 insertions, 1706 deletions
diff --git a/.clang-format b/.clang-format
index 6245b22..ffff065 100644
--- a/.clang-format
+++ b/.clang-format
@@ -1,10 +1,25 @@
-# Copyright 2020-2022 David Robillard <d@drobilla.net>
+# Copyright 2020-2025 David Robillard <d@drobilla.net>
# SPDX-License-Identifier: 0BSD OR ISC
---
AlignConsecutiveAssignments: true
AlignConsecutiveDeclarations: true
-AlignEscapedNewlinesLeft: true
+AlignEscapedNewlines: Left
+AttributeMacros:
+ - ZIX_ALLOC_COUNT_SIZE
+ - ZIX_ALLOC_SIZE
+ - ZIX_ALWAYS_INLINE_FUNC
+ - ZIX_API
+ - ZIX_CONST_API
+ - ZIX_CONST_FUNC
+ - ZIX_MALLOC_API
+ - ZIX_MALLOC_FUNC
+ - ZIX_NODISCARD
+ - ZIX_NONBLOCKING
+ - ZIX_PURE_API
+ - ZIX_PURE_FUNC
+ - ZIX_PURE_WIN_API
+ - ZIX_REALTIME
BasedOnStyle: Mozilla
BraceWrapping:
AfterEnum: false
@@ -20,17 +35,10 @@ KeepEmptyLinesAtTheStartOfBlocks: false
SpacesInContainerLiterals: false
StatementMacros:
- FALLTHROUGH
- - ZIX_ALWAYS_INLINE_FUNC
- - ZIX_API
- ZIX_BEGIN_DECLS
- - ZIX_CONST_API
- - ZIX_CONST_FUNC
+ - ZIX_DISABLE_EFFECT_WARNINGS
+ - ZIX_DISABLE_GLIB_WARNINGS
- ZIX_END_DECLS
- - ZIX_MALLOC_API
- - ZIX_MALLOC_FUNC
- - ZIX_NODISCARD
- - ZIX_PURE_API
- - ZIX_PURE_FUNC
- - ZIX_PURE_WIN_API
+ - ZIX_RESTORE_WARNINGS
- _Pragma
...
diff --git a/.clang-tidy b/.clang-tidy
index 1c5a762..04367ff 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -1,15 +1,18 @@
-# Copyright 2020-2022 David Robillard <d@drobilla.net>
+# Copyright 2020-2025 David Robillard <d@drobilla.net>
# SPDX-License-Identifier: 0BSD OR ISC
Checks: >
*,
+ -*-use-nullptr,
-altera-*,
-bugprone-assignment-in-if-condition,
-bugprone-switch-missing-default-case,
-hicpp-multiway-paths-covered,
-llvmlibc-*,
-misc-include-cleaner,
+ -readability-avoid-nested-conditional-operator,
-readability-identifier-length,
+ -readability-implicit-bool-conversion,
WarningsAsErrors: '*'
HeaderFilterRegex: '.*'
FormatStyle: file
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
new file mode 100644
index 0000000..7b4b16b
--- /dev/null
+++ b/.git-blame-ignore-revs
@@ -0,0 +1,5 @@
+# Copyright 2025 David Robillard <d@drobilla.net>
+# SPDX-License-Identifier: 0BSD OR ISC
+
+# Format all code with clang-format
+741c3349b09c8774fcd013e3bdd7d9e7f6b470ce
diff --git a/.gitignore b/.gitignore
index c330864..53c3c1d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,11 @@
-# Copyright 2021-2022 David Robillard <d@drobilla.net>
+# Copyright 2021-2025 David Robillard <d@drobilla.net>
# SPDX-License-Identifier: 0BSD OR ISC
+/.meson-subproject-wrap-hash.txt
+/build/
+/subprojects/packagecache/
+/subprojects/sphinxygen-1.0.10/
+/subprojects/sphinxygen/
+
*.pyc
-.meson-subproject-wrap-hash.txt
-__pycache__
-build/**
-subprojects/packagecache/
-subprojects/sphinxygen-1.0.4/
-subprojects/sphinxygen/
+__pycache__/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ebfdd1a..c2b2715 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-# Copyright 2019-2022 David Robillard <d@drobilla.net>
+# Copyright 2019-2025 David Robillard <d@drobilla.net>
# SPDX-License-Identifier: 0BSD OR ISC
stages: [build, deploy]
@@ -40,7 +40,7 @@ sanitize:
- meson setup build -Db_lundef=false -Dbuildtype=plain -Dwarning_level=3 -Dwerror=true -Dc_args="-fno-sanitize-recover=all -fsanitize=address -fsanitize=undefined -fsanitize=float-divide-by-zero -fsanitize=implicit-conversion -fsanitize=local-bounds -fsanitize=nullability" -Dc_link_args="-fno-sanitize-recover=all -fsanitize=address -fsanitize=undefined -fsanitize=float-divide-by-zero -fsanitize=implicit-conversion -fsanitize=local-bounds -fsanitize=nullability" -Dcpp_args="-fno-sanitize-recover=all -fsanitize=address -fsanitize=undefined -fsanitize=float-divide-by-zero" -Dcpp_link_args="-fno-sanitize-recover=all -fsanitize=address -fsanitize=undefined -fsanitize=float-divide-by-zero" -Ddocs=disabled
- ninja -C build test
- meson configure build -Dbuildtype=debugoptimized -Dc_args="" -Dc_link_args="" -Dcpp_args="" -Dcpp_link_args=""
- - meson configure build -Db_sanitize=thread
+ - meson configure build -Dc_std=c17 -Db_sanitize=thread
- ninja -C build test
- meson configure build -Db_sanitize=memory -Dtests_cpp=disabled
- ninja -C build test
@@ -95,6 +95,14 @@ mingw32:
- ninja -C build test
- meson configure -Dbuildtype=release build
- ninja -C build test
+ - meson configure -Dwin_ver=vista build
+ - ninja -C build test
+ - meson configure -Dwin_ver=winxp build
+ - ninja -C build test
+ - meson configure -Dwin_ver=nt4 build
+ - ninja -C build test
+ - meson configure -Dwin_wchar=disabled build
+ - ninja -C build test
variables:
WINEPATH: "Z:\\usr\\lib\\gcc\\i686-w64-mingw32\\10-win32"
@@ -106,6 +114,12 @@ mingw64:
- ninja -C build test
- meson configure -Dbuildtype=release build
- ninja -C build test
+ - meson configure -Dwin_ver=vista build
+ - ninja -C build test
+ - meson configure -Dwin_ver=winxp build
+ - ninja -C build test
+ - meson configure -Dwin_wchar=disabled build
+ - ninja -C build test
variables:
WINEPATH: "Z:\\usr\\lib\\gcc\\x86_64-w64-mingw32\\10-win32"
@@ -146,6 +160,16 @@ win:
- ninja -C build test
- meson configure -Dbuildtype=release build
- ninja -C build test
+ - meson configure -Dwin_ver=vista build
+ - ninja -C build test
+ - meson configure -Dwin_ver=winxp build
+ - ninja -C build test
+ - meson configure -Dwin_ver=nt4 build
+ - ninja -C build test
+ - meson configure -Dwin_wchar=disabled build
+ - ninja -C build test
+ - meson configure -Dwin_wchar=enabled -Dwin_ver=win8 -Dc_args="/DWINAPI_FAMILY=WINAPI_FAMILY_PC_APP" -Dc_link_args="/APPCONTAINER" build
+ - ninja -C build test
# Documentation
diff --git a/.reuse/dep5 b/.reuse/dep5
index ab2d669..a13171a 100644
--- a/.reuse/dep5
+++ b/.reuse/dep5
@@ -4,5 +4,5 @@ Upstream-Contact: David Robillard <d@drobilla.net>
Source: https://gitlab.com/drobilla/zix
Files: *.md NEWS
-Copyright: 2021 David Robillard <d@drobilla.net>
+Copyright: 2021-2025 David Robillard <d@drobilla.net>
License: 0BSD OR ISC
diff --git a/LICENSES/0BSD.txt b/LICENSES/0BSD.txt
index b3e8cfd..f33ca7b 100644
--- a/LICENSES/0BSD.txt
+++ b/LICENSES/0BSD.txt
@@ -1,4 +1,4 @@
-Copyright 2011-2022 David Robillard <d@drobilla.net>
+Copyright 2011-2025 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
diff --git a/LICENSES/ISC.txt b/LICENSES/ISC.txt
index 012065c..5533913 120000..100644
--- a/LICENSES/ISC.txt
+++ b/LICENSES/ISC.txt
@@ -1 +1,13 @@
-../COPYING \ No newline at end of file
+Copyright 2011-2025 David Robillard <d@drobilla.net>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
diff --git a/NEWS b/NEWS
index eaf8c05..dff7613 100644
--- a/NEWS
+++ b/NEWS
@@ -1,14 +1,40 @@
-zix (0.5.0) unstable; urgency=medium
+zix (0.7.1) unstable; urgency=medium
+
+ * Add ZIX_REALTIME and ZIX_NONBLOCKING attributes
+ * Add warning suppression macros
+ * Annotate count and size parameters of allocator API
+ * Avoid "deprecated" POSIX functions on Windows
+ * Avoid over-use of yielding meson options
+ * Clean up attribute documentation
+ * Gracefully handle failed allocation in path and filesystem functions
+ * Reduce empty BTree memory requirements
+ * Strengthen zix_file_equals()
+ * Use getenv() instead of environ to avoid issues on FreeBSD
+
+ -- David Robillard <d@drobilla.net> Mon, 08 Sep 2025 04:44:48 +0000
+
+zix (0.6.2) stable; urgency=medium
+
+ * Fix documentation build with sphinxygen fallback wrap
+
+ -- David Robillard <d@drobilla.net> Sat, 18 Jan 2025 23:53:31 +0000
+
+zix (0.6.0) stable; urgency=medium
* Add ZIX_NODISCARD attribute
+ * Add option to build for older Windows versions
+ * Add zix_expand_environment_strings()
* Add zix_string_view_equals()
* Avoid fdatasync() on Darwin
* Fix build on POSIX systems without PATH_MAX defined
* Fix library current_version on MacOS
* Fix nullability annotations for zix_canonical_path() and friends
* Fix ring unit test when mlock() is unavailable
+ * Improve documentation
+ * Support building for UWP
+ * Support building for Windows with or without UNICODE
- -- David Robillard <d@drobilla.net> Wed, 26 Jun 2024 22:21:42 +0000
+ -- David Robillard <d@drobilla.net> Sat, 18 Jan 2025 21:49:37 +0000
zix (0.4.2) stable; urgency=medium
diff --git a/README.md b/README.md
index 654c3d4..0335acd 100644
--- a/README.md
+++ b/README.md
@@ -32,17 +32,21 @@ Components
* `zix/filesystem.h`: Functions for working with filesystems.
* `zix/path.h`: Functions for working with filesystem paths lexically.
+* Environment
+
+ * `zix/environment.h`: Function to expand shell-style variables in a string.
+
Platforms
---------
Zix is continually tested on:
- * Debian GNU/Linux 11 (x86, x64, arm32, and arm64)
+ * Debian 11 (x86, x64, arm32, and arm64)
* Fedora 36 (x64)
- * FreeBSD 13 (x64)
+ * FreeBSD 14 (x64)
* MacOS 14 (M2)
* Node 12 (as wasm via emscripten 2.0.12)
- * Windows 10 (x86)
+ * Windows 10 (x64)
Dependencies
------------
diff --git a/benchmark/.clang-tidy b/benchmark/.clang-tidy
index b89626f..eed9c22 100644
--- a/benchmark/.clang-tidy
+++ b/benchmark/.clang-tidy
@@ -1,4 +1,4 @@
-# Copyright 2020-2022 David Robillard <d@drobilla.net>
+# Copyright 2020-2025 David Robillard <d@drobilla.net>
# SPDX-License-Identifier: 0BSD OR ISC
Checks: >
@@ -9,4 +9,7 @@ Checks: >
-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,
-llvm-header-guard,
-performance-no-int-to-ptr,
+CheckOptions:
+ - key: readability-function-cognitive-complexity.Threshold
+ value: '26'
InheritParentConfig: true
diff --git a/benchmark/bench.h b/benchmark/bench.h
index bcfe077..2a25b11 100644
--- a/benchmark/bench.h
+++ b/benchmark/bench.h
@@ -1,33 +1,29 @@
-// Copyright 2011-2020 David Robillard <d@drobilla.net>
+// Copyright 2011-2024 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#ifndef BENCH_H
#define BENCH_H
-#include <time.h>
+#include <glib.h>
-typedef struct timespec BenchmarkTime;
+typedef gint64 BenchmarkTime;
static inline double
bench_elapsed_s(const BenchmarkTime* start, const BenchmarkTime* end)
{
- return ((double)(end->tv_sec - start->tv_sec) +
- ((double)(end->tv_nsec - start->tv_nsec) * 0.000000001));
+ return (double)(*end - *start) * 0.000001;
}
static inline BenchmarkTime
bench_start(void)
{
- BenchmarkTime start_t;
- clock_gettime(CLOCK_REALTIME, &start_t);
- return start_t;
+ return g_get_monotonic_time();
}
static inline double
bench_end(const BenchmarkTime* start_t)
{
- BenchmarkTime end_t;
- clock_gettime(CLOCK_REALTIME, &end_t);
+ const BenchmarkTime end_t = g_get_monotonic_time();
return bench_elapsed_s(start_t, &end_t);
}
diff --git a/benchmark/dict_bench.c b/benchmark/dict_bench.c
index 279e6ad..3ecd0b2 100644
--- a/benchmark/dict_bench.c
+++ b/benchmark/dict_bench.c
@@ -1,4 +1,4 @@
-// Copyright 2011-2023 David Robillard <d@drobilla.net>
+// Copyright 2011-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#include "bench.h"
@@ -12,10 +12,11 @@ typedef struct {
#define ZIX_HASH_KEY_TYPE ZixChunk
#define ZIX_HASH_RECORD_TYPE ZixChunk
-#include "zix/attributes.h"
-#include "zix/digest.h"
-#include "zix/hash.h"
-#include "zix/status.h"
+#include <zix/attributes.h>
+#include <zix/digest.h>
+#include <zix/hash.h>
+#include <zix/status.h>
+#include <zix/warnings.h>
ZIX_DISABLE_GLIB_WARNINGS
#include <glib.h>
@@ -27,7 +28,6 @@ ZIX_RESTORE_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <time.h>
typedef struct {
ZixChunk* chunks;
@@ -45,8 +45,7 @@ lcg64(const uint64_t i)
return (a * i) + c;
}
-ZIX_PURE_FUNC
-static const ZixChunk*
+ZIX_PURE_FUNC static const ZixChunk*
identity(const ZixChunk* record)
{
return record;
@@ -66,6 +65,17 @@ zix_chunk_equal(const ZixChunk* a, const ZixChunk* b)
static const unsigned seed = 1;
+static void
+free_inputs(const Inputs* const inputs)
+{
+ for (size_t i = 0; i < inputs->n_chunks; ++i) {
+ free(inputs->chunks[i].buf);
+ }
+
+ free(inputs->chunks);
+ free(inputs->buf);
+}
+
static Inputs
read_inputs(FILE* const fd)
{
@@ -85,14 +95,14 @@ read_inputs(FILE* const fd)
inputs.chunks, (inputs.n_chunks + 1) * sizeof(ZixChunk));
if (!new_chunks) {
- free(inputs.chunks);
- free(inputs.buf);
+ free_inputs(&inputs);
return no_inputs;
}
inputs.chunks = new_chunks;
inputs.chunks[inputs.n_chunks].buf = (char*)malloc(buf_len);
inputs.chunks[inputs.n_chunks].len = this_str_len;
+ assert(inputs.chunks[inputs.n_chunks].buf);
memcpy(inputs.chunks[inputs.n_chunks].buf, inputs.buf, buf_len);
this_str_len = 0;
if (++inputs.n_chunks == max_n_strings) {
@@ -105,8 +115,7 @@ read_inputs(FILE* const fd)
char* const new_buf = (char*)realloc(inputs.buf, buf_len);
if (!new_buf) {
- free(inputs.chunks);
- free(inputs.buf);
+ free_inputs(&inputs);
return no_inputs;
}
@@ -128,7 +137,22 @@ run(FILE* const fd)
fclose(fd);
FILE* insert_dat = fopen("dict_insert.txt", "w");
+ if (!insert_dat) {
+ fprintf(stderr, "error: Failed to open dict_insert.txt\n");
+ free_inputs(&inputs);
+ return 1;
+ }
+
FILE* search_dat = fopen("dict_search.txt", "w");
+ if (!search_dat) {
+ fclose(insert_dat);
+ free_inputs(&inputs);
+ fprintf(stderr, "error: Failed to open dict_search.txt\n");
+ return 1;
+ }
+
+ assert(insert_dat);
+ assert(search_dat);
fprintf(insert_dat, "# n\tGHashTable\tZixHash\n");
fprintf(search_dat, "# n\tGHashTable\tZixHash\n");
@@ -147,7 +171,7 @@ run(FILE* const fd)
// Benchmark insertion
// GHashTable
- struct timespec insert_start = bench_start();
+ BenchmarkTime insert_start = bench_start();
for (size_t i = 0; i < n; ++i) {
g_hash_table_insert(hash, inputs.chunks[i].buf, inputs.chunks[i].buf);
}
@@ -165,7 +189,7 @@ run(FILE* const fd)
// Benchmark search
// GHashTable
- struct timespec search_start = bench_start();
+ BenchmarkTime search_start = bench_start();
for (size_t i = 0; i < n; ++i) {
const size_t index = (size_t)(lcg64(seed + i) % n);
char* volatile match =
@@ -201,15 +225,9 @@ run(FILE* const fd)
g_hash_table_unref(hash);
}
- fclose(insert_dat);
fclose(search_dat);
-
- for (size_t i = 0; i < inputs.n_chunks; ++i) {
- free(inputs.chunks[i].buf);
- }
-
- free(inputs.chunks);
- free(inputs.buf);
+ fclose(insert_dat);
+ free_inputs(&inputs);
fprintf(stderr, "Wrote dict_insert.txt dict_search.txt\n");
return 0;
diff --git a/benchmark/meson.build b/benchmark/meson.build
index d3b04ea..54cac3d 100644
--- a/benchmark/meson.build
+++ b/benchmark/meson.build
@@ -15,15 +15,19 @@ glib_dep = dependency(
if glib_dep.found()
build_benchmarks = true
- benchmark_c_args = platform_c_args
+ benchmark_c_args = extra_c_args
+ benchmark_c_suppressions = []
if cc.get_id() in ['clang', 'emscripten']
benchmark_c_suppressions = [
+ '-Wno-bad-function-cast',
+ '-Wno-c11-extensions', # Glib
'-Wno-reserved-identifier',
]
-
- benchmark_c_args += cc.get_supported_arguments(benchmark_c_suppressions)
+ elif cc.get_id() == 'gcc'
+ benchmark_c_suppressions = ['-Wno-bad-function-cast']
endif
+ benchmark_c_args += cc.get_supported_arguments(benchmark_c_suppressions)
foreach benchmark : benchmarks
benchmark(
@@ -33,6 +37,7 @@ if glib_dep.found()
files('@0@.c'.format(benchmark)),
c_args: c_suppressions + benchmark_c_args,
dependencies: [zix_dep, glib_dep],
+ implicit_include_directories: false,
include_directories: include_dirs,
),
)
diff --git a/benchmark/tree_bench.c b/benchmark/tree_bench.c
index b9e230e..afb2111 100644
--- a/benchmark/tree_bench.c
+++ b/benchmark/tree_bench.c
@@ -1,26 +1,25 @@
-// Copyright 2011-2020 David Robillard <d@drobilla.net>
+// Copyright 2011-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
+#include "../test/test_data.h"
#include "bench.h"
#include "warnings.h"
-#include "../test/test_data.h"
-
-#include "zix/attributes.h"
-#include "zix/btree.h"
-#include "zix/status.h"
-#include "zix/tree.h"
+#include <zix/attributes.h>
+#include <zix/btree.h>
+#include <zix/status.h>
+#include <zix/tree.h>
+#include <zix/warnings.h>
ZIX_DISABLE_GLIB_WARNINGS
#include <glib.h>
ZIX_RESTORE_WARNINGS
+#include <assert.h>
#include <inttypes.h>
-#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
-#include <time.h>
#ifndef MIN
# define MIN(a, b) (((a) < (b)) ? (a) : (b))
@@ -55,15 +54,11 @@ g_int_cmp(const void* a, const void* b, void* user_data)
return int_cmp(a, b, user_data);
}
-ZIX_LOG_FUNC(1, 2)
static int
-test_fail(const char* fmt, ...)
+test_fail(const char* const prefix, const uintptr_t value)
{
- va_list args;
- va_start(args, fmt);
fprintf(stderr, "error: ");
- vfprintf(stderr, fmt, args);
- va_end(args);
+ fprintf(stderr, "%s %" PRIuPTR "\n", prefix, value);
return EXIT_FAILURE;
}
@@ -87,32 +82,32 @@ bench_zix_tree(size_t n_elems,
ZixTree* t = zix_tree_new(NULL, false, int_cmp, NULL, NULL, NULL);
// Insert n_elems elements
- struct timespec insert_start = bench_start();
+ BenchmarkTime insert_start = bench_start();
for (size_t i = 0; i < n_elems; i++) {
r = unique_rand(i);
ZixStatus status = zix_tree_insert(t, (void*)r, &ti);
if (status) {
- return test_fail("Failed to insert %" PRIuPTR "\n", r);
+ return test_fail("Failed to insert", r);
}
}
fprintf(insert_dat, "\t%lf", bench_end(&insert_start));
// Search for all elements
- struct timespec search_start = bench_start();
+ BenchmarkTime search_start = bench_start();
for (size_t i = 0; i < n_elems; i++) {
r = unique_rand(i);
if (zix_tree_find(t, (void*)r, &ti)) {
- return test_fail("Failed to find %" PRIuPTR "\n", r);
+ return test_fail("Failed to find", r);
}
if ((uintptr_t)zix_tree_get(ti) != r) {
- return test_fail("Failed to get %" PRIuPTR "\n", r);
+ return test_fail("Failed to get", r);
}
}
fprintf(search_dat, "\t%lf", bench_end(&search_start));
// Iterate over all elements
- struct timespec iter_start = bench_start();
+ BenchmarkTime iter_start = bench_start();
for (ZixTreeIter* iter = zix_tree_begin(t); !zix_tree_iter_is_end(iter);
iter = zix_tree_iter_next(iter)) {
volatile void* const value = zix_tree_get(iter);
@@ -121,16 +116,16 @@ bench_zix_tree(size_t n_elems,
fprintf(iter_dat, "\t%lf", bench_end(&iter_start));
// Delete all elements
- struct timespec del_start = bench_start();
+ BenchmarkTime del_start = bench_start();
for (size_t i = 0; i < n_elems; i++) {
r = unique_rand(i);
ZixTreeIter* item = NULL;
if (zix_tree_find(t, (void*)r, &item)) {
- return test_fail("Failed to find %" PRIuPTR " to delete\n", r);
+ return test_fail("Failed on delete to find", r);
}
if (zix_tree_remove(t, item)) {
- return test_fail("Failed to remove %" PRIuPTR "\n", r);
+ return test_fail("Failed to remove", r);
}
}
fprintf(del_dat, "\t%lf", bench_end(&del_start));
@@ -154,33 +149,33 @@ bench_zix_btree(size_t n_elems,
ZixBTree* t = zix_btree_new(NULL, int_cmp, NULL);
// Insert n_elems elements
- struct timespec insert_start = bench_start();
+ BenchmarkTime insert_start = bench_start();
for (size_t i = 0; i < n_elems; i++) {
r = unique_rand(i);
ZixStatus status = zix_btree_insert(t, (void*)r);
if (status) {
- return test_fail("Failed to insert %" PRIuPTR "\n", r);
+ return test_fail("Failed to insert", r);
}
}
fprintf(insert_dat, "\t%lf", bench_end(&insert_start));
// Search for all elements
- struct timespec search_start = bench_start();
+ BenchmarkTime search_start = bench_start();
for (size_t i = 0; i < n_elems; i++) {
r = unique_rand(i);
if (zix_btree_find(t, (void*)r, &ti)) {
- return test_fail("Failed to find %" PRIuPTR "\n", r);
+ return test_fail("Failed to find", r);
}
if ((uintptr_t)zix_btree_get(ti) != r) {
- return test_fail("Failed to get %" PRIuPTR "\n", r);
+ return test_fail("Failed to get", r);
}
}
fprintf(search_dat, "\t%lf", bench_end(&search_start));
// Iterate over all elements
- struct timespec iter_start = bench_start();
- ZixBTreeIter iter = zix_btree_begin(t);
+ BenchmarkTime iter_start = bench_start();
+ ZixBTreeIter iter = zix_btree_begin(t);
for (; !zix_btree_iter_is_end(iter); zix_btree_iter_increment(&iter)) {
volatile void* const value = zix_btree_get(iter);
(void)value;
@@ -188,14 +183,14 @@ bench_zix_btree(size_t n_elems,
fprintf(iter_dat, "\t%lf", bench_end(&iter_start));
// Delete all elements
- struct timespec del_start = bench_start();
+ BenchmarkTime del_start = bench_start();
for (size_t i = 0; i < n_elems; i++) {
r = unique_rand(i);
void* removed = NULL;
ZixBTreeIter next = zix_btree_end(t);
if (zix_btree_remove(t, (void*)r, &removed, &next)) {
- return test_fail("Failed to remove %" PRIuPTR "\n", r);
+ return test_fail("Failed to remove", r);
}
}
fprintf(del_dat, "\t%lf", bench_end(&del_start));
@@ -218,31 +213,31 @@ bench_glib(size_t n_elems,
GSequence* t = g_sequence_new(NULL);
// Insert n_elems elements
- struct timespec insert_start = bench_start();
+ BenchmarkTime insert_start = bench_start();
for (size_t i = 0; i < n_elems; ++i) {
r = unique_rand(i);
GSequenceIter* iter =
g_sequence_insert_sorted(t, (void*)r, g_int_cmp, NULL);
if (!iter || g_sequence_iter_is_end(iter)) {
- return test_fail("Failed to insert %" PRIuPTR "\n", r);
+ return test_fail("Failed to insert", r);
}
}
fprintf(insert_dat, "\t%lf", bench_end(&insert_start));
// Search for all elements
- struct timespec search_start = bench_start();
+ BenchmarkTime search_start = bench_start();
for (size_t i = 0; i < n_elems; ++i) {
r = unique_rand(i);
GSequenceIter* iter = g_sequence_lookup(t, (void*)r, g_int_cmp, NULL);
if (!iter || g_sequence_iter_is_end(iter)) {
- return test_fail("Failed to find %" PRIuPTR "\n", r);
+ return test_fail("Failed to find", r);
}
}
fprintf(search_dat, "\t%lf", bench_end(&search_start));
// Iterate over all elements
- struct timespec iter_start = bench_start();
+ BenchmarkTime iter_start = bench_start();
for (GSequenceIter* iter = g_sequence_get_begin_iter(t);
!g_sequence_iter_is_end(iter);
iter = g_sequence_iter_next(iter)) {
@@ -251,12 +246,12 @@ bench_glib(size_t n_elems,
fprintf(iter_dat, "\t%lf", bench_end(&iter_start));
// Delete all elements
- struct timespec del_start = bench_start();
+ BenchmarkTime del_start = bench_start();
for (size_t i = 0; i < n_elems; ++i) {
r = unique_rand(i);
GSequenceIter* iter = g_sequence_lookup(t, (void*)r, g_int_cmp, NULL);
if (!iter || g_sequence_iter_is_end(iter)) {
- return test_fail("Failed to remove %" PRIuPTR "\n", r);
+ return test_fail("Failed to remove", r);
}
g_sequence_remove(iter);
}
@@ -267,6 +262,24 @@ bench_glib(size_t n_elems,
return EXIT_SUCCESS;
}
+static FILE*
+open_output(const char* const path)
+{
+ FILE* const f = fopen(path, "w");
+ if (!f) {
+ fprintf(stderr, "error: Failed to open %s\n", path);
+ }
+ return f;
+}
+
+static void
+close_output(FILE* const f)
+{
+ if (f) {
+ fclose(f);
+ }
+}
+
int
main(int argc, char** argv)
{
@@ -282,10 +295,23 @@ main(int argc, char** argv)
#define HEADER "# n\tZixTree\tZixBTree\tGSequence\n"
- FILE* insert_dat = fopen("tree_insert.txt", "w");
- FILE* search_dat = fopen("tree_search.txt", "w");
- FILE* iter_dat = fopen("tree_iterate.txt", "w");
- FILE* del_dat = fopen("tree_delete.txt", "w");
+ FILE* const insert_dat = open_output("tree_insert.txt");
+ FILE* const search_dat = open_output("tree_search.txt");
+ FILE* const iter_dat = open_output("tree_iterate.txt");
+ FILE* const del_dat = open_output("tree_delete.txt");
+ if (!insert_dat || !search_dat || !iter_dat || !del_dat) {
+ close_output(del_dat);
+ close_output(iter_dat);
+ close_output(search_dat);
+ close_output(insert_dat);
+ return EXIT_FAILURE;
+ }
+
+ assert(insert_dat);
+ assert(search_dat);
+ assert(iter_dat);
+ assert(del_dat);
+
fprintf(insert_dat, HEADER);
fprintf(search_dat, HEADER);
fprintf(iter_dat, HEADER);
@@ -304,10 +330,10 @@ main(int argc, char** argv)
fprintf(iter_dat, "\n");
fprintf(del_dat, "\n");
}
- fclose(insert_dat);
- fclose(search_dat);
- fclose(iter_dat);
- fclose(del_dat);
+ close_output(del_dat);
+ close_output(iter_dat);
+ close_output(search_dat);
+ close_output(insert_dat);
fprintf(
stderr,
diff --git a/benchmark/warnings.h b/benchmark/warnings.h
index 43e740e..d87b2b6 100644
--- a/benchmark/warnings.h
+++ b/benchmark/warnings.h
@@ -1,9 +1,11 @@
-// Copyright 2020 David Robillard <d@drobilla.net>
+// Copyright 2020-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#ifndef WARNINGS_H
#define WARNINGS_H
+#include <zix/warnings.h>
+
#if defined(__clang__)
# define ZIX_DISABLE_GLIB_WARNINGS \
@@ -12,18 +14,13 @@
_Pragma("clang diagnostic ignored \"-Wdocumentation\"") \
_Pragma("clang diagnostic ignored \"-Wreserved-id-macro\"")
-# define ZIX_RESTORE_WARNINGS _Pragma("clang diagnostic pop")
-
#elif defined(__GNUC__)
# define ZIX_DISABLE_GLIB_WARNINGS _Pragma("GCC diagnostic push")
-# define ZIX_RESTORE_WARNINGS _Pragma("GCC diagnostic pop")
-
#else
# define ZIX_DISABLE_GLIB_WARNINGS
-# define ZIX_RESTORE_WARNINGS
#endif
diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in
index 741fe1a..78a7bc9 100644
--- a/doc/Doxyfile.in
+++ b/doc/Doxyfile.in
@@ -1,4 +1,4 @@
-# Copyright 2021-2022 David Robillard <d@drobilla.net>
+# Copyright 2021-2025 David Robillard <d@drobilla.net>
# SPDX-License-Identifier: 0BSD OR ISC
PROJECT_NAME = Zix
@@ -6,7 +6,7 @@ PROJECT_BRIEF = "A lightweight C library of portability wrappers and da
QUIET = YES
WARN_AS_ERROR = YES
-WARN_IF_UNDOCUMENTED = YES
+WARN_IF_UNDOCUMENTED = NO
WARN_NO_PARAMDOC = NO
RECURSIVE=YES
@@ -29,22 +29,28 @@ EXPAND_ONLY_PREDEF = YES
MACRO_EXPANSION = YES
SKIP_FUNCTION_MACROS = NO
PREDEFINED = ZIX_ALLOCATED= \
+ ZIX_ALLOC_COUNT_SIZE(n,s)= \
+ ZIX_ALLOC_SIZE(s)= \
ZIX_ALWAYS_INLINE_FUNC= \
ZIX_API= \
ZIX_BEGIN_DECLS= \
ZIX_CONST_API= \
ZIX_CONST_FUNC= \
ZIX_END_DECLS= \
+ ZIX_LOG_FUNC(f,a)= \
ZIX_MALLOC_API= \
ZIX_MALLOC_FUNC= \
ZIX_NODISCARD= \
+ ZIX_NONBLOCKING= \
ZIX_NONNULL= \
ZIX_NULLABLE= \
ZIX_PURE_API= \
ZIX_PURE_FUNC= \
ZIX_PURE_WIN_API= \
+ ZIX_REALTIME= \
ZIX_THREAD_FUNC= \
ZIX_UNSPECIFIED= \
+ ZIX_UNUSED(n)= \
STRIP_FROM_PATH = @ZIX_SRCDIR@
INPUT = @ZIX_SRCDIR@/include/zix/zix.h \
@@ -68,5 +74,7 @@ INPUT = @ZIX_SRCDIR@/include/zix/zix.h \
\
@ZIX_SRCDIR@/include/zix/filesystem.h \
@ZIX_SRCDIR@/include/zix/path.h \
+ \
+ @ZIX_SRCDIR@/include/zix/environment.h \
OUTPUT_DIRECTORY = @DOX_OUTPUT@
diff --git a/doc/conf.py.in b/doc/conf.py.in
index 6b3c415..a7c254d 100644
--- a/doc/conf.py.in
+++ b/doc/conf.py.in
@@ -1,10 +1,10 @@
-# Copyright 2021-2023 David Robillard <d@drobilla.net>
+# Copyright 2021-2025 David Robillard <d@drobilla.net>
# SPDX-License-Identifier: 0BSD OR ISC
# Project information
project = "Zix"
-copyright = "2011-2023, David Robillard"
+copyright = "2011-2025, David Robillard"
author = "David Robillard"
release = "@ZIX_VERSION@"
version = "@ZIX_VERSION@"
diff --git a/doc/html/meson.build b/doc/html/meson.build
index 9c0da44..45374d3 100644
--- a/doc/html/meson.build
+++ b/doc/html/meson.build
@@ -6,12 +6,15 @@ html_dir = docdir / versioned_name / 'html'
custom_target(
'html',
build_by_default: true,
- command: sphinx_build_command + [
- '-b', 'html',
- '-t', 'html',
- sphinx_in_dir,
- '@OUTDIR@',
- ],
+ command: (
+ sphinx_build_command
+ + [
+ '-b', 'html',
+ '-t', 'html',
+ sphinx_in_dir,
+ '@OUTDIR@',
+ ]
+ ),
input: [api_zix_rst, conf_py, sphinx_input],
install: true,
install_dir: html_dir,
diff --git a/doc/meson.build b/doc/meson.build
index 6d60bbb..d48f120 100644
--- a/doc/meson.build
+++ b/doc/meson.build
@@ -37,8 +37,9 @@ if build_docs
executable(
'overview_code',
files('overview_code.c'),
- dependencies: [zix_dep],
c_args: c_suppressions,
+ dependencies: [zix_dep],
+ implicit_include_directories: false,
),
suite: 'doc',
)
diff --git a/doc/overview_code.c b/doc/overview_code.c
index 2f6a099..063f829 100644
--- a/doc/overview_code.c
+++ b/doc/overview_code.c
@@ -1,4 +1,4 @@
-// Copyright 2021-2023 David Robillard <d@drobilla.net>
+// Copyright 2021-2024 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
/*
@@ -8,13 +8,8 @@
written such that it at least compiles and will run without crashing.
*/
-#include "zix/attributes.h"
-#include "zix/string_view.h"
-
-#if defined(__GNUC__)
-# pragma GCC diagnostic push
-# pragma GCC diagnostic ignored "-Wunused-variable"
-#endif
+#include <zix/attributes.h>
+#include <zix/string_view.h>
static void
string_views(void)
@@ -24,6 +19,7 @@ string_views(void)
// begin make-empty-string
ZixStringView empty = zix_empty_string();
// end make-empty-string
+ (void)empty;
// begin make-static-string
static const ZixStringView hello = ZIX_STATIC_STRING("hello");
@@ -33,20 +29,17 @@ string_views(void)
// begin measure-string
ZixStringView view = zix_string(string_pointer);
// end measure-string
+ (void)view;
// begin make-string-view
ZixStringView slice = zix_substring(string_pointer, 4);
// end make-string-view
+ (void)slice;
}
-ZIX_CONST_FUNC
-int
+ZIX_CONST_FUNC int
main(void)
{
string_views();
return 0;
}
-
-#if defined(__GNUC__)
-# pragma GCC diagnostic pop
-#endif
diff --git a/doc/singlehtml/meson.build b/doc/singlehtml/meson.build
index 33bff61..32a8201 100644
--- a/doc/singlehtml/meson.build
+++ b/doc/singlehtml/meson.build
@@ -6,12 +6,15 @@ singlehtml_dir = docdir / versioned_name / 'singlehtml'
custom_target(
'singlehtml',
build_by_default: true,
- command: sphinx_build_command + [
- '-b', 'singlehtml',
- '-t', 'singlehtml',
- sphinx_in_dir,
- '@OUTDIR@',
- ],
+ command: (
+ sphinx_build_command
+ + [
+ '-b', 'singlehtml',
+ '-t', 'singlehtml',
+ sphinx_in_dir,
+ '@OUTDIR@',
+ ]
+ ),
input: [api_zix_rst, conf_py, sphinx_input],
install: true,
install_dir: singlehtml_dir,
diff --git a/doc/xml/meson.build b/doc/xml/meson.build
index b8863f3..f5a6b1d 100644
--- a/doc/xml/meson.build
+++ b/doc/xml/meson.build
@@ -23,6 +23,7 @@ doxygen_xml = custom_target(
'btree_8h.xml',
'bump__allocator_8h.xml',
'digest_8h.xml',
+ 'environment_8h.xml',
'filesystem_8h.xml',
'group__bump__allocator.xml',
'group__zix.xml',
@@ -35,8 +36,11 @@ doxygen_xml = custom_target(
'group__zix__btree__modification.xml',
'group__zix__btree__searching.xml',
'group__zix__btree__setup.xml',
+ 'group__zix__btree__types.xml',
'group__zix__data__structures.xml',
'group__zix__digest.xml',
+ 'group__zix__environment.xml',
+ 'group__zix__expand.xml',
'group__zix__file__system.xml',
'group__zix__fs__access.xml',
'group__zix__fs__creation.xml',
@@ -46,11 +50,11 @@ doxygen_xml = custom_target(
'group__zix__fs__queries.xml',
'group__zix__fs__resolution.xml',
'group__zix__hash.xml',
- 'group__zix__hash__datatypes.xml',
'group__zix__hash__iteration.xml',
'group__zix__hash__modification.xml',
'group__zix__hash__searching.xml',
'group__zix__hash__setup.xml',
+ 'group__zix__hash__types.xml',
'group__zix__path.xml',
'group__zix__path__concatenation.xml',
'group__zix__path__decomposition.xml',
@@ -59,6 +63,7 @@ doxygen_xml = custom_target(
'group__zix__ring.xml',
'group__zix__ring__read.xml',
'group__zix__ring__setup.xml',
+ 'group__zix__ring__types.xml',
'group__zix__ring__write.xml',
'group__zix__sem.xml',
'group__zix__status.xml',
@@ -70,6 +75,7 @@ doxygen_xml = custom_target(
'group__zix__tree__modification.xml',
'group__zix__tree__searching.xml',
'group__zix__tree__setup.xml',
+ 'group__zix__tree__types.xml',
'group__zix__utilities.xml',
'hash_8h.xml',
'path_8h.xml',
diff --git a/include/zix/allocator.h b/include/zix/allocator.h
index bace039..77be11b 100644
--- a/include/zix/allocator.h
+++ b/include/zix/allocator.h
@@ -1,10 +1,10 @@
-// Copyright 2021-2022 David Robillard <d@drobilla.net>
+// Copyright 2021-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#ifndef ZIX_ALLOCATOR_H
#define ZIX_ALLOCATOR_H
-#include "zix/attributes.h"
+#include <zix/attributes.h>
#include <stddef.h>
@@ -103,13 +103,11 @@ struct ZixAllocatorImpl {
};
/// Return the default allocator which simply uses the system allocator
-ZIX_CONST_API
-ZixAllocator* ZIX_NONNULL
+ZIX_CONST_API ZixAllocator* ZIX_NONNULL
zix_default_allocator(void);
/// Convenience wrapper that defers to malloc() if allocator is null
-ZIX_MALLOC_FUNC
-static inline void* ZIX_ALLOCATED
+ZIX_MALLOC_FUNC ZIX_ALLOC_SIZE(2) static inline void* ZIX_ALLOCATED
zix_malloc(ZixAllocator* const ZIX_NULLABLE allocator, const size_t size)
{
ZixAllocator* const actual = allocator ? allocator : zix_default_allocator();
@@ -118,8 +116,7 @@ zix_malloc(ZixAllocator* const ZIX_NULLABLE allocator, const size_t size)
}
/// Convenience wrapper that defers to calloc() if allocator is null
-ZIX_MALLOC_FUNC
-static inline void* ZIX_ALLOCATED
+ZIX_MALLOC_FUNC ZIX_ALLOC_COUNT_SIZE(2, 3) static inline void* ZIX_ALLOCATED
zix_calloc(ZixAllocator* const ZIX_NULLABLE allocator,
const size_t nmemb,
const size_t size)
@@ -130,8 +127,7 @@ zix_calloc(ZixAllocator* const ZIX_NULLABLE allocator,
}
/// Convenience wrapper that defers to realloc() if allocator is null
-ZIX_NODISCARD
-static inline void* ZIX_ALLOCATED
+ZIX_NODISCARD ZIX_ALLOC_SIZE(3) static inline void* ZIX_ALLOCATED
zix_realloc(ZixAllocator* const ZIX_NULLABLE allocator,
void* const ZIX_NULLABLE ptr,
const size_t size)
@@ -152,8 +148,7 @@ zix_free(ZixAllocator* const ZIX_NULLABLE allocator,
}
/// Convenience wrapper that defers to the system allocator if allocator is null
-ZIX_MALLOC_FUNC
-static inline void* ZIX_ALLOCATED
+ZIX_MALLOC_FUNC ZIX_ALLOC_SIZE(3) static inline void* ZIX_ALLOCATED
zix_aligned_alloc(ZixAllocator* const ZIX_NULLABLE allocator,
const size_t alignment,
const size_t size)
diff --git a/include/zix/attributes.h b/include/zix/attributes.h
index 249e6a6..2fac163 100644
--- a/include/zix/attributes.h
+++ b/include/zix/attributes.h
@@ -1,4 +1,4 @@
-// Copyright 2021-2022 David Robillard <d@drobilla.net>
+// Copyright 2021-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#ifndef ZIX_ATTRIBUTES_H
@@ -15,8 +15,8 @@
# define ZIX_BEGIN_DECLS extern "C" {
# define ZIX_END_DECLS }
#else
-# define ZIX_BEGIN_DECLS ///< Begin public API definitions
-# define ZIX_END_DECLS ///< End public API definitions
+# define ZIX_BEGIN_DECLS // Begin public API definitions
+# define ZIX_END_DECLS // End public API definitions
#endif
// ZIX_API must be used to decorate things in the public API
@@ -40,36 +40,50 @@
# define ZIX_MALLOC_FUNC __attribute__((malloc))
# define ZIX_NODISCARD __attribute__((warn_unused_result))
#else
-# define ZIX_ALWAYS_INLINE_FUNC ///< Should absolutely always be inlined
-# define ZIX_PURE_FUNC ///< Only reads memory
-# define ZIX_CONST_FUNC ///< Only reads its parameters
-# define ZIX_MALLOC_FUNC ///< Allocates memory with no pointers in it
-# define ZIX_NODISCARD ///< Returns a value that must be used
+# define ZIX_ALWAYS_INLINE_FUNC ///< Function should always be inlined
+# define ZIX_PURE_FUNC ///< Function only reads memory
+# define ZIX_CONST_FUNC ///< Function only reads its parameters
+# define ZIX_MALLOC_FUNC ///< Function allocates pointer-free memory
+# define ZIX_NODISCARD ///< Function return value must be used
#endif
-/// A pure function in the public API that only reads memory
-#define ZIX_PURE_API \
- ZIX_API \
- ZIX_PURE_FUNC \
- ZIX_NODISCARD
+// Clang nonallocating/nonblocking function attributes
+#if defined(__clang__) && __clang_major__ >= 20
+# define ZIX_NONBLOCKING __attribute__((nonblocking))
+# define ZIX_REALTIME ZIX_NONBLOCKING
+#else
+# define ZIX_NONBLOCKING ///< Function doesn't allocate or block
+# define ZIX_REALTIME ///< Function is nonblocking and constant time
+#endif
-/// A const function in the public API that is pure and only reads parameters
-#define ZIX_CONST_API \
- ZIX_API \
- ZIX_CONST_FUNC \
- ZIX_NODISCARD
+#define ZIX_PURE_API ZIX_API ZIX_PURE_FUNC ZIX_NODISCARD
+#define ZIX_CONST_API ZIX_API ZIX_CONST_FUNC ZIX_NODISCARD
+#define ZIX_MALLOC_API ZIX_API ZIX_MALLOC_FUNC ZIX_NODISCARD
-/// A malloc function in the public API that returns allocated memory
-#define ZIX_MALLOC_API \
- ZIX_API \
- ZIX_MALLOC_FUNC \
- ZIX_NODISCARD
+// Malloc and calloc-like functions with count/size arguments
+#if (defined(__GNUC__) && __GNUC__ >= 11) || \
+ (defined(__clang__) && __clang_major__ >= 4)
+# define ZIX_ALLOC_SIZE(s) __attribute__((alloc_size(s)))
+# define ZIX_ALLOC_COUNT_SIZE(n, s) __attribute__((alloc_size(n, s)))
+#else
+/// Function with malloc-like parameters
+/// @param s 1-based index of size parameter
+# define ZIX_ALLOC_SIZE(s)
+
+/// Function with calloc-like parameters
+/// @param n 1-based index of number of elements parameter
+/// @param s 1-based index of element size parameter
+# define ZIX_ALLOC_COUNT_SIZE(n, s)
+#endif
-// Printf-like format functions
+// Printf-like function with format arguments
#ifdef __GNUC__
# define ZIX_LOG_FUNC(fmt, arg1) __attribute__((format(printf, fmt, arg1)))
#else
-# define ZIX_LOG_FUNC(fmt, arg1) ///< A function with printf-like parameters
+/// Function with printf-like parameters
+/// @param fmt 1-based index of format string parameter
+/// @param arg1 1-based index of first format argument parameter
+# define ZIX_LOG_FUNC(fmt, arg1)
#endif
// Unused parameter macro to suppresses warnings and make it impossible to use
@@ -80,7 +94,7 @@
#elif defined(_MSC_VER)
# define ZIX_UNUSED(name) __pragma(warning(suppress : 4100)) name
#else
-# define ZIX_UNUSED(name) name ///< An unused parameter
+# define ZIX_UNUSED(name) name ///< Unused parameter
#endif
// Clang nullability annotations
@@ -90,10 +104,10 @@
# define ZIX_ALLOCATED _Null_unspecified
# define ZIX_UNSPECIFIED _Null_unspecified
#else
-# define ZIX_NONNULL ///< A non-null pointer
-# define ZIX_NULLABLE ///< A nullable pointer
-# define ZIX_ALLOCATED ///< An allocated (possibly null) pointer
-# define ZIX_UNSPECIFIED ///< A pointer with unspecified nullability
+# define ZIX_NONNULL ///< Non-null pointer
+# define ZIX_NULLABLE ///< Nullable pointer
+# define ZIX_ALLOCATED ///< Allocated pointer
+# define ZIX_UNSPECIFIED ///< Pointer with unspecified nullability
#endif
/**
diff --git a/include/zix/btree.h b/include/zix/btree.h
index ddade81..35f0c24 100644
--- a/include/zix/btree.h
+++ b/include/zix/btree.h
@@ -1,12 +1,12 @@
-// Copyright 2011-2022 David Robillard <d@drobilla.net>
+// Copyright 2011-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#ifndef ZIX_BTREE_H
#define ZIX_BTREE_H
-#include "zix/allocator.h"
-#include "zix/attributes.h"
-#include "zix/status.h"
+#include <zix/allocator.h>
+#include <zix/attributes.h>
+#include <zix/status.h>
#include <stdbool.h>
#include <stddef.h>
@@ -21,7 +21,7 @@ ZIX_BEGIN_DECLS
*/
/**
- @defgroup zix_btree_setup Setup
+ @defgroup zix_btree_types Types
@{
*/
@@ -51,6 +51,12 @@ typedef void (*ZixBTreeDestroyFunc)(void* ZIX_UNSPECIFIED ptr,
const void* ZIX_UNSPECIFIED user_data);
/**
+ @}
+ @defgroup zix_btree_setup Setup
+ @{
+*/
+
+/**
Create a new (empty) B-Tree.
The given comparator must be a total ordering and is used to internally
@@ -59,9 +65,7 @@ typedef void (*ZixBTreeDestroyFunc)(void* ZIX_UNSPECIFIED ptr,
Searching can be done with a custom comparator that supports wildcards, see
zix_btree_lower_bound() for details.
*/
-ZIX_API
-ZIX_NODISCARD
-ZixBTree* ZIX_ALLOCATED
+ZIX_API ZIX_NODISCARD ZixBTree* ZIX_ALLOCATED
zix_btree_new(ZixAllocator* ZIX_NULLABLE allocator,
ZixBTreeCompareFunc ZIX_NONNULL cmp,
const void* ZIX_UNSPECIFIED cmp_data);
@@ -76,8 +80,7 @@ zix_btree_new(ZixAllocator* ZIX_NULLABLE allocator,
@param destroy_data Opaque user data pointer to pass to `destroy`.
*/
-ZIX_API
-void
+ZIX_API void
zix_btree_free(ZixBTree* ZIX_NULLABLE t,
ZixBTreeDestroyFunc ZIX_NULLABLE destroy,
const void* ZIX_NULLABLE destroy_data);
@@ -92,15 +95,13 @@ zix_btree_free(ZixBTree* ZIX_NULLABLE t,
@param destroy_data Opaque user data pointer to pass to `destroy`.
*/
-ZIX_API
-void
+ZIX_API void
zix_btree_clear(ZixBTree* ZIX_NONNULL t,
ZixBTreeDestroyFunc ZIX_NULLABLE destroy,
const void* ZIX_NULLABLE destroy_data);
/// Return the number of elements in `t`
-ZIX_PURE_API
-size_t
+ZIX_PURE_API ZIX_REALTIME size_t
zix_btree_size(const ZixBTree* ZIX_NONNULL t);
/**
@@ -135,42 +136,34 @@ static const ZixBTreeIter zix_btree_end_iter = {
};
/// Return the data at the given position in the tree
-ZIX_PURE_API
-void* ZIX_UNSPECIFIED
+ZIX_PURE_API ZIX_REALTIME void* ZIX_UNSPECIFIED
zix_btree_get(ZixBTreeIter ti);
/// Return an iterator to the first (smallest) element in `t`
-ZIX_PURE_API
-ZixBTreeIter
+ZIX_PURE_API ZIX_NONBLOCKING ZixBTreeIter
zix_btree_begin(const ZixBTree* ZIX_NONNULL t);
/// Return an iterator to the end of `t` (one past the last element)
-ZIX_CONST_API
-ZixBTreeIter
+ZIX_CONST_API ZIX_REALTIME ZixBTreeIter
zix_btree_end(const ZixBTree* ZIX_NULLABLE t);
/// Return true iff `lhs` is equal to `rhs`
-ZIX_CONST_API
-bool
+ZIX_CONST_API ZIX_REALTIME bool
zix_btree_iter_equals(ZixBTreeIter lhs, ZixBTreeIter rhs);
/// Return true iff `i` is an iterator at the end of a tree
-ZIX_NODISCARD
-static inline bool
+ZIX_NODISCARD static inline bool
zix_btree_iter_is_end(const ZixBTreeIter i)
{
return i.level == 0 && !i.nodes[0];
}
/// Increment `i` to point to the next element in the tree
-ZIX_API
-ZixStatus
+ZIX_API ZIX_NONBLOCKING ZixStatus
zix_btree_iter_increment(ZixBTreeIter* ZIX_NONNULL i);
/// Return an iterator one past `iter`
-ZIX_API
-ZIX_NODISCARD
-ZixBTreeIter
+ZIX_API ZIX_NONBLOCKING ZIX_NODISCARD ZixBTreeIter
zix_btree_iter_next(ZixBTreeIter iter);
/**
@@ -185,8 +178,7 @@ zix_btree_iter_next(ZixBTreeIter iter);
@return #ZIX_STATUS_SUCCESS on success, #ZIX_STATUS_EXISTS, or
#ZIX_STATUS_NO_MEM.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_btree_insert(ZixBTree* ZIX_NONNULL t, void* ZIX_UNSPECIFIED e);
/**
@@ -203,8 +195,7 @@ zix_btree_insert(ZixBTree* ZIX_NONNULL t, void* ZIX_UNSPECIFIED e);
@return #ZIX_STATUS_SUCCESS on success, or #ZIX_STATUS_NOT_FOUND.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_btree_remove(ZixBTree* ZIX_NONNULL t,
const void* ZIX_UNSPECIFIED e,
void* ZIX_UNSPECIFIED* ZIX_NONNULL out,
@@ -223,8 +214,7 @@ zix_btree_remove(ZixBTree* ZIX_NONNULL t,
@return #ZIX_STATUS_SUCCESS on success, or #ZIX_STATUS_NOT_FOUND.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_btree_find(const ZixBTree* ZIX_NONNULL t,
const void* ZIX_UNSPECIFIED e,
ZixBTreeIter* ZIX_NONNULL ti);
@@ -245,8 +235,7 @@ zix_btree_find(const ZixBTree* ZIX_NONNULL t,
@return #ZIX_STATUS_SUCCESS.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_btree_lower_bound(const ZixBTree* ZIX_NONNULL t,
ZixBTreeCompareFunc ZIX_NULLABLE compare_key,
const void* ZIX_NULLABLE compare_key_data,
diff --git a/include/zix/bump_allocator.h b/include/zix/bump_allocator.h
index c69de92..1d5a661 100644
--- a/include/zix/bump_allocator.h
+++ b/include/zix/bump_allocator.h
@@ -4,8 +4,8 @@
#ifndef ZIX_BUMP_ALLOCATOR_H
#define ZIX_BUMP_ALLOCATOR_H
-#include "zix/allocator.h"
-#include "zix/attributes.h"
+#include <zix/allocator.h>
+#include <zix/attributes.h>
#include <stddef.h>
@@ -52,8 +52,7 @@ typedef struct {
} ZixBumpAllocator;
/// Return a bump allocator that works within a provided buffer
-ZIX_API
-ZixBumpAllocator
+ZIX_API ZIX_REALTIME ZixBumpAllocator
zix_bump_allocator(size_t capacity, void* ZIX_NONNULL buffer);
/**
diff --git a/include/zix/digest.h b/include/zix/digest.h
index deffaf0..bd86bfa 100644
--- a/include/zix/digest.h
+++ b/include/zix/digest.h
@@ -4,7 +4,7 @@
#ifndef ZIX_DIGEST_H
#define ZIX_DIGEST_H
-#include "zix/attributes.h"
+#include <zix/attributes.h>
#include <stddef.h>
#include <stdint.h>
@@ -29,8 +29,7 @@ ZIX_BEGIN_DECLS
This can be used for any size or alignment.
*/
-ZIX_PURE_API
-uint32_t
+ZIX_PURE_API ZIX_NONBLOCKING uint32_t
zix_digest32(uint32_t seed, const void* ZIX_NONNULL buf, size_t len);
/**
@@ -39,8 +38,7 @@ zix_digest32(uint32_t seed, const void* ZIX_NONNULL buf, size_t len);
Both the buffer and size must be aligned to 32 bits. For data that fits
these requirements, this is equivalent to, but faster than, zix_digest32().
*/
-ZIX_PURE_API
-uint32_t
+ZIX_PURE_API ZIX_NONBLOCKING uint32_t
zix_digest32_aligned(uint32_t seed, const void* ZIX_NONNULL buf, size_t len);
/**
@@ -48,8 +46,7 @@ zix_digest32_aligned(uint32_t seed, const void* ZIX_NONNULL buf, size_t len);
This can be used for any size or alignment.
*/
-ZIX_PURE_API
-uint64_t
+ZIX_PURE_API ZIX_NONBLOCKING uint64_t
zix_digest64(uint64_t seed, const void* ZIX_NONNULL buf, size_t len);
/**
@@ -58,8 +55,7 @@ zix_digest64(uint64_t seed, const void* ZIX_NONNULL buf, size_t len);
Both the buffer and size must be aligned to 64 bits. For data that fits
these requirements, this is equivalent to, but faster than, zix_digest64().
*/
-ZIX_PURE_API
-uint64_t
+ZIX_PURE_API ZIX_NONBLOCKING uint64_t
zix_digest64_aligned(uint64_t seed, const void* ZIX_NONNULL buf, size_t len);
/**
@@ -70,8 +66,7 @@ zix_digest64_aligned(uint64_t seed, const void* ZIX_NONNULL buf, size_t len);
Internally, this simply dispatches to zix_digest32() or zix_digest64() as
appropriate.
*/
-ZIX_PURE_API
-size_t
+ZIX_PURE_API ZIX_NONBLOCKING size_t
zix_digest(size_t seed, const void* ZIX_NONNULL buf, size_t len);
/**
@@ -84,8 +79,7 @@ zix_digest(size_t seed, const void* ZIX_NONNULL buf, size_t len);
Internally, this simply dispatches to zix_digest32_aligned() or
zix_digest64_aligned() as appropriate.
*/
-ZIX_PURE_API
-size_t
+ZIX_PURE_API ZIX_NONBLOCKING size_t
zix_digest_aligned(size_t seed, const void* ZIX_NONNULL buf, size_t len);
/**
diff --git a/include/zix/environment.h b/include/zix/environment.h
new file mode 100644
index 0000000..095d421
--- /dev/null
+++ b/include/zix/environment.h
@@ -0,0 +1,39 @@
+// Copyright 2024 David Robillard <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#ifndef ZIX_ENVIRONMENT_H
+#define ZIX_ENVIRONMENT_H
+
+#include <zix/allocator.h>
+#include <zix/attributes.h>
+
+ZIX_BEGIN_DECLS
+
+/**
+ @defgroup zix_expand Variable Expansion
+ @ingroup zix_environment
+ @{
+*/
+
+/**
+ Expand shell-style variables in a string.
+
+ On Windows, this expands environment variable references like
+ "%USERPROFILE%". On POSIX systems, it expands environment variable
+ references like "$HOME", and the special path component "~".
+
+ @param allocator Allocator used for returned string.
+ @param string Input string to expand.
+ @return A newly allocated copy of `string` with variables expanded, or null.
+*/
+ZIX_MALLOC_API char* ZIX_ALLOCATED
+zix_expand_environment_strings(ZixAllocator* ZIX_NULLABLE allocator,
+ const char* ZIX_NONNULL string);
+
+/**
+ @}
+*/
+
+ZIX_END_DECLS
+
+#endif /* ZIX_ENVIRONMENT_H */
diff --git a/include/zix/filesystem.h b/include/zix/filesystem.h
index 221e165..dff4145 100644
--- a/include/zix/filesystem.h
+++ b/include/zix/filesystem.h
@@ -4,9 +4,9 @@
#ifndef ZIX_FILESYSTEM_H
#define ZIX_FILESYSTEM_H
-#include "zix/allocator.h"
-#include "zix/attributes.h"
-#include "zix/status.h"
+#include <zix/allocator.h>
+#include <zix/attributes.h>
+#include <zix/status.h>
#if !(defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64)
# include <stddef.h>
@@ -51,8 +51,7 @@ typedef uint32_t ZixCopyOptions;
@param options Options to control the kind of copy and error conditions.
@return #ZIX_STATUS_SUCCESS if `dst` was successfully created, or an error.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_copy_file(ZixAllocator* ZIX_NULLABLE allocator,
const char* ZIX_NONNULL src,
const char* ZIX_NONNULL dst,
@@ -64,8 +63,7 @@ zix_copy_file(ZixAllocator* ZIX_NULLABLE allocator,
@return #ZIX_STATUS_SUCCESS if `dir_path` was successfully created, or an
error.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_create_directory(const char* ZIX_NONNULL dir_path);
/**
@@ -77,8 +75,7 @@ zix_create_directory(const char* ZIX_NONNULL dir_path);
@return #ZIX_STATUS_SUCCESS if `dir_path` was successfully created, or an
error.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_create_directory_like(const char* ZIX_NONNULL dir_path,
const char* ZIX_NONNULL existing_path);
@@ -92,8 +89,7 @@ zix_create_directory_like(const char* ZIX_NONNULL dir_path,
@return #ZIX_STATUS_SUCCESS if all directories in `dir_path` were
successfully created (or already existed), or an error.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_create_directories(ZixAllocator* ZIX_NULLABLE allocator,
const char* ZIX_NONNULL dir_path);
@@ -102,8 +98,7 @@ zix_create_directories(ZixAllocator* ZIX_NULLABLE allocator,
@return #ZIX_STATUS_SUCCESS, or an error.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_create_hard_link(const char* ZIX_NONNULL target_path,
const char* ZIX_NONNULL link_path);
@@ -116,8 +111,7 @@ zix_create_hard_link(const char* ZIX_NONNULL target_path,
@return #ZIX_STATUS_SUCCESS, or an error.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_create_symlink(const char* ZIX_NONNULL target_path,
const char* ZIX_NONNULL link_path);
@@ -129,8 +123,7 @@ zix_create_symlink(const char* ZIX_NONNULL target_path,
@return #ZIX_STATUS_SUCCESS, or an error.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_create_directory_symlink(const char* ZIX_NONNULL target_path,
const char* ZIX_NONNULL link_path);
@@ -146,14 +139,12 @@ zix_create_directory_symlink(const char* ZIX_NONNULL target_path,
@return The path of the created directory, or null.
*/
-ZIX_MALLOC_API
-char* ZIX_NULLABLE
+ZIX_MALLOC_API char* ZIX_NULLABLE
zix_create_temporary_directory(ZixAllocator* ZIX_NULLABLE allocator,
const char* ZIX_NONNULL path_pattern);
/// Remove the file or empty directory at `path`
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_remove(const char* ZIX_NONNULL path);
/**
@@ -163,6 +154,17 @@ zix_remove(const char* ZIX_NONNULL path);
*/
/**
+ Function for reading input bytes from a stream.
+
+ @param path Path to the directory being visited.
+ @param name Name of the directory entry.
+ @param data Opaque user data.
+*/
+typedef void (*ZixDirEntryVisitFunc)(const char* ZIX_NONNULL path,
+ const char* ZIX_NONNULL name,
+ void* ZIX_NONNULL data);
+
+/**
Visit every file in the directory at `path`.
@param path A path to a directory.
@@ -173,13 +175,10 @@ zix_remove(const char* ZIX_NONNULL path);
parameter is always the directory path passed to this function, the `name`
parameter is the name of the directory entry (not its full path).
*/
-ZIX_API
-void
-zix_dir_for_each(const char* ZIX_NONNULL path,
- void* ZIX_NULLABLE data,
- void (*ZIX_NONNULL f)(const char* ZIX_NONNULL path,
- const char* ZIX_NONNULL name,
- void* ZIX_NONNULL data));
+ZIX_API void
+zix_dir_for_each(const char* ZIX_NONNULL path,
+ void* ZIX_NULLABLE data,
+ ZixDirEntryVisitFunc ZIX_NONNULL f);
/**
Return whether the given paths point to files with identical contents.
@@ -193,9 +192,7 @@ zix_dir_for_each(const char* ZIX_NONNULL path,
@return True if the two files have byte-for-byte identical contents.
*/
-ZIX_API
-ZIX_NODISCARD
-bool
+ZIX_API ZIX_NODISCARD bool
zix_file_equals(ZixAllocator* ZIX_NULLABLE allocator,
const char* ZIX_NONNULL a_path,
const char* ZIX_NONNULL b_path);
@@ -228,8 +225,7 @@ zix_file_equals(ZixAllocator* ZIX_NULLABLE allocator,
@return A new canonical version of `path`, or null if it doesn't exist.
*/
-ZIX_MALLOC_API
-char* ZIX_NULLABLE
+ZIX_MALLOC_API char* ZIX_NULLABLE
zix_canonical_path(ZixAllocator* ZIX_NULLABLE allocator,
const char* ZIX_NULLABLE path);
@@ -256,8 +252,7 @@ typedef enum {
@param mode Lock mode.
@return #ZIX_STATUS_SUCCESS if the file was locked, or an error.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_file_lock(FILE* ZIX_NONNULL file, ZixFileLockMode mode);
/**
@@ -267,8 +262,7 @@ zix_file_lock(FILE* ZIX_NONNULL file, ZixFileLockMode mode);
@param mode Lock mode.
@return #ZIX_STATUS_SUCCESS if the file was unlocked, or an error.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_file_unlock(FILE* ZIX_NONNULL file, ZixFileLockMode mode);
/**
@@ -309,8 +303,7 @@ typedef enum {
/**
Return the type of a file or directory, resolving symlinks.
*/
-ZIX_API
-ZixFileType
+ZIX_API ZixFileType
zix_file_type(const char* ZIX_NONNULL path);
/**
@@ -319,8 +312,7 @@ zix_file_type(const char* ZIX_NONNULL path);
On Windows, a directory symlink (actually a "reparse point") always appears
as a directory.
*/
-ZIX_API
-ZixFileType
+ZIX_API ZixFileType
zix_symlink_type(const char* ZIX_NONNULL path);
/**
@@ -332,8 +324,7 @@ zix_symlink_type(const char* ZIX_NONNULL path);
@return A non-negative size in bytes, or -1 on error.
*/
-ZIX_API
-ZixFileOffset
+ZIX_API ZixFileOffset
zix_file_size(const char* ZIX_NONNULL path);
/**
@@ -347,8 +338,7 @@ zix_file_size(const char* ZIX_NONNULL path);
@param allocator Allocator used for the returned path.
*/
-ZIX_MALLOC_API
-char* ZIX_ALLOCATED
+ZIX_MALLOC_API char* ZIX_ALLOCATED
zix_current_path(ZixAllocator* ZIX_NULLABLE allocator);
/**
@@ -358,8 +348,7 @@ zix_current_path(ZixAllocator* ZIX_NULLABLE allocator);
@return A new path to a temporary directory, or null on error.
*/
-ZIX_MALLOC_API
-char* ZIX_ALLOCATED
+ZIX_MALLOC_API char* ZIX_ALLOCATED
zix_temp_directory_path(ZixAllocator* ZIX_NULLABLE allocator);
/**
diff --git a/include/zix/hash.h b/include/zix/hash.h
index 6cd2271..6f1f4c3 100644
--- a/include/zix/hash.h
+++ b/include/zix/hash.h
@@ -1,12 +1,12 @@
-// Copyright 2011-2022 David Robillard <d@drobilla.net>
+// Copyright 2011-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#ifndef ZIX_HASH_H
#define ZIX_HASH_H
-#include "zix/allocator.h"
-#include "zix/attributes.h"
-#include "zix/status.h"
+#include <zix/allocator.h>
+#include <zix/attributes.h>
+#include <zix/status.h>
#include <stdbool.h>
#include <stddef.h>
@@ -20,7 +20,7 @@ ZIX_BEGIN_DECLS
*/
/**
- @defgroup zix_hash_datatypes Datatypes
+ @defgroup zix_hash_types Types
@{
*/
@@ -77,12 +77,6 @@ typedef struct ZixHashImpl ZixHash;
/// A full hash code for a key which is not folded down to the table size
typedef size_t ZixHashCode;
-/**
- @}
- @defgroup zix_hash_setup Setup
- @{
-*/
-
/// User function for getting the key of a record
typedef const ZixHashKey* ZIX_NONNULL (*ZixKeyFunc)(
const ZixHashRecord* ZIX_NONNULL record);
@@ -95,6 +89,12 @@ typedef bool (*ZixKeyEqualFunc)(const ZixHashKey* ZIX_NONNULL a,
const ZixHashKey* ZIX_NONNULL b);
/**
+ @}
+ @defgroup zix_hash_setup Setup
+ @{
+*/
+
+/**
Create a new hash table.
@param allocator Allocator used for the internal array.
@@ -102,22 +102,18 @@ typedef bool (*ZixKeyEqualFunc)(const ZixHashKey* ZIX_NONNULL a,
@param hash_func The key hashing function.
@param equal_func A function to test keys for equality.
*/
-ZIX_API
-ZIX_NODISCARD
-ZixHash* ZIX_ALLOCATED
+ZIX_API ZIX_NODISCARD ZixHash* ZIX_ALLOCATED
zix_hash_new(ZixAllocator* ZIX_NULLABLE allocator,
ZixKeyFunc ZIX_NONNULL key_func,
ZixHashFunc ZIX_NONNULL hash_func,
ZixKeyEqualFunc ZIX_NONNULL equal_func);
/// Free `hash`
-ZIX_API
-void
+ZIX_API void
zix_hash_free(ZixHash* ZIX_NULLABLE hash);
/// Return the number of elements in the hash
-ZIX_PURE_API
-size_t
+ZIX_PURE_API ZIX_REALTIME size_t
zix_hash_size(const ZixHash* ZIX_NONNULL hash);
/**
@@ -135,23 +131,19 @@ zix_hash_size(const ZixHash* ZIX_NONNULL hash);
typedef size_t ZixHashIter;
/// Return an iterator to the first record in a hash, or the end if it is empty
-ZIX_PURE_API
-ZixHashIter
+ZIX_PURE_API ZIX_NONBLOCKING ZixHashIter
zix_hash_begin(const ZixHash* ZIX_NONNULL hash);
/// Return an iterator one past the last possible record in a hash
-ZIX_PURE_API
-ZixHashIter
+ZIX_PURE_API ZIX_REALTIME ZixHashIter
zix_hash_end(const ZixHash* ZIX_NONNULL hash);
/// Return the record pointed to by an iterator
-ZIX_PURE_API
-ZixHashRecord* ZIX_NULLABLE
+ZIX_PURE_API ZIX_REALTIME ZixHashRecord* ZIX_NULLABLE
zix_hash_get(const ZixHash* ZIX_NONNULL hash, ZixHashIter i);
/// Return an iterator that has been advanced to the next record in a hash
-ZIX_PURE_API
-ZixHashIter
+ZIX_PURE_API ZIX_NONBLOCKING ZixHashIter
zix_hash_next(const ZixHash* ZIX_NONNULL hash, ZixHashIter i);
/**
@@ -189,8 +181,7 @@ typedef struct {
record with this key using zix_hash_insert_at() until the hash table is
modified (which invalidates the position).
*/
-ZIX_API
-ZixHashInsertPlan
+ZIX_API ZixHashInsertPlan
zix_hash_plan_insert(const ZixHash* ZIX_NONNULL hash,
const ZixHashKey* ZIX_NONNULL key);
@@ -214,8 +205,7 @@ zix_hash_plan_insert(const ZixHash* ZIX_NONNULL hash,
be inserted, and the predicate must return true only if the key it is called
with (the first argument) matches the key to be inserted.
*/
-ZIX_API
-ZixHashInsertPlan
+ZIX_API ZixHashInsertPlan
zix_hash_plan_insert_prehashed(const ZixHash* ZIX_NONNULL hash,
ZixHashCode code,
ZixKeyMatchFunc ZIX_NONNULL predicate,
@@ -228,8 +218,7 @@ zix_hash_plan_insert_prehashed(const ZixHash* ZIX_NONNULL hash,
can be used to insert a new record, or to access the existing matching
record.
*/
-ZIX_PURE_API
-ZixHashRecord* ZIX_NULLABLE
+ZIX_PURE_API ZIX_REALTIME ZixHashRecord* ZIX_NULLABLE
zix_hash_record_at(const ZixHash* ZIX_NONNULL hash, ZixHashInsertPlan position);
/**
@@ -249,8 +238,7 @@ zix_hash_record_at(const ZixHash* ZIX_NONNULL hash, ZixHashInsertPlan position);
@return ZIX_STATUS_SUCCESS, ZIX_STATUS_EXISTS if a record already exists at
this position, or ZIX_STATUS_NO_MEM if growing the hash table failed.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_hash_insert_at(ZixHash* ZIX_NONNULL hash,
ZixHashInsertPlan position,
ZixHashRecord* ZIX_NONNULL record);
@@ -269,8 +257,7 @@ zix_hash_insert_at(ZixHash* ZIX_NONNULL hash,
@return ZIX_STATUS_SUCCESS, ZIX_STATUS_EXISTS, or ZIX_STATUS_NO_MEM.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_hash_insert(ZixHash* ZIX_NONNULL hash, ZixHashRecord* ZIX_NONNULL record);
/**
@@ -287,8 +274,7 @@ zix_hash_insert(ZixHash* ZIX_NONNULL hash, ZixHashRecord* ZIX_NONNULL record);
@return ZIX_STATUS_SUCCES or ZIX_STATUS_BAD_ARG if `i` does not point at a
removable record.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_hash_erase(ZixHash* ZIX_NONNULL hash,
ZixHashIter i,
ZixHashRecord* ZIX_NULLABLE* ZIX_NONNULL removed);
@@ -301,8 +287,7 @@ zix_hash_erase(ZixHash* ZIX_NONNULL hash,
@param removed Set to the removed record, or null.
@return ZIX_STATUS_SUCCES or ZIX_STATUS_NOT_FOUND.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_hash_remove(ZixHash* ZIX_NONNULL hash,
const ZixHashKey* ZIX_NONNULL key,
ZixHashRecord* ZIX_NULLABLE* ZIX_NONNULL removed);
@@ -323,8 +308,7 @@ zix_hash_remove(ZixHash* ZIX_NONNULL hash,
@return An iterator to the matching record, or the end iterator if no such
record exists.
*/
-ZIX_API
-ZixHashIter
+ZIX_API ZixHashIter
zix_hash_find(const ZixHash* ZIX_NONNULL hash,
const ZixHashKey* ZIX_NONNULL key);
@@ -340,8 +324,7 @@ zix_hash_find(const ZixHash* ZIX_NONNULL hash,
@return A pointer to the matching record, of null if no such record exists.
*/
-ZIX_API
-ZixHashRecord* ZIX_NULLABLE
+ZIX_API ZixHashRecord* ZIX_NULLABLE
zix_hash_find_record(const ZixHash* ZIX_NONNULL hash,
const ZixHashKey* ZIX_NONNULL key);
diff --git a/include/zix/path.h b/include/zix/path.h
index 22c8202..74c5ddd 100644
--- a/include/zix/path.h
+++ b/include/zix/path.h
@@ -1,12 +1,12 @@
-// Copyright 2007-2022 David Robillard <d@drobilla.net>
+// Copyright 2007-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#ifndef ZIX_PATH_H
#define ZIX_PATH_H
-#include "zix/allocator.h"
-#include "zix/attributes.h"
-#include "zix/string_view.h"
+#include <zix/allocator.h>
+#include <zix/attributes.h>
+#include <zix/string_view.h>
#include <stdbool.h>
@@ -35,9 +35,7 @@ ZIX_BEGIN_DECLS
*/
/// Join path `a` and path `b` with a single directory separator between them
-ZIX_API
-ZIX_NODISCARD
-char* ZIX_ALLOCATED
+ZIX_MALLOC_API char* ZIX_ALLOCATED
zix_path_join(ZixAllocator* ZIX_NULLABLE allocator,
const char* ZIX_NULLABLE a,
const char* ZIX_NULLABLE b);
@@ -55,9 +53,7 @@ zix_path_join(ZixAllocator* ZIX_NULLABLE allocator,
converted to the preferred separator (backslash on Windows, slash everywhere
else).
*/
-ZIX_API
-ZIX_NODISCARD
-char* ZIX_ALLOCATED
+ZIX_MALLOC_API char* ZIX_ALLOCATED
zix_path_preferred(ZixAllocator* ZIX_NULLABLE allocator,
const char* ZIX_NONNULL path);
@@ -66,15 +62,13 @@ zix_path_preferred(ZixAllocator* ZIX_NULLABLE allocator,
Paths in normal form have all dot segments removed and use only a single
preferred separator for all separators (that is, any number of separators is
- replaced with a single "\" on Windows, and a single "/" everwhere else).
+ replaced with a single "\" on Windows, and a single "/" everywhere else).
Note that this function doesn't access the filesystem, so won't do anything
like case normalization or symbolic link dereferencing. For that, use
zix_canonical_path().
*/
-ZIX_API
-ZIX_NODISCARD
-char* ZIX_ALLOCATED
+ZIX_MALLOC_API char* ZIX_ALLOCATED
zix_path_lexically_normal(ZixAllocator* ZIX_NULLABLE allocator,
const char* ZIX_NONNULL path);
@@ -85,9 +79,7 @@ zix_path_lexically_normal(ZixAllocator* ZIX_NULLABLE allocator,
equivalent path relative to `base` is returned (which may contain
up-references).
*/
-ZIX_API
-ZIX_NODISCARD
-char* ZIX_ALLOCATED
+ZIX_MALLOC_API char* ZIX_ALLOCATED
zix_path_lexically_relative(ZixAllocator* ZIX_NULLABLE allocator,
const char* ZIX_NONNULL path,
const char* ZIX_NONNULL base);
@@ -99,13 +91,11 @@ zix_path_lexically_relative(ZixAllocator* ZIX_NULLABLE allocator,
*/
/// Return the root name of `path` like "C:", or null
-ZIX_PURE_WIN_API
-ZixStringView
+ZIX_PURE_WIN_API ZIX_NONBLOCKING ZixStringView
zix_path_root_name(const char* ZIX_NONNULL path);
/// Return the root directory of `path` like "/" or "\", or null
-ZIX_PURE_API
-ZixStringView
+ZIX_PURE_API ZIX_NONBLOCKING ZixStringView
zix_path_root_directory(const char* ZIX_NONNULL path);
/**
@@ -116,13 +106,12 @@ zix_path_root_directory(const char* ZIX_NONNULL path);
normalized. For example, "/" is the root of "/", "//", "/.", and "/..".
On Windows, the root may additionally be an absolute drive root like "C:\",
- a relative drive root like "C:", or a network root like "//Host/".
+ a relative drive root like "C:", or a network root like "\\HOST\".
- @return The newly allocated root path of `path`, or null if it has no root
- or allocation failed.
+ @return A view of the root path within `path`, or an empty string if it has
+ no root.
*/
-ZIX_PURE_API
-ZixStringView
+ZIX_PURE_API ZIX_NONBLOCKING ZixStringView
zix_path_root_path(const char* ZIX_NONNULL path);
/**
@@ -131,8 +120,7 @@ zix_path_root_path(const char* ZIX_NONNULL path);
If the path has no relative path (because it is empty or a root path), this
returns null.
*/
-ZIX_PURE_API
-ZixStringView
+ZIX_PURE_API ZIX_NONBLOCKING ZixStringView
zix_path_relative_path(const char* ZIX_NONNULL path);
/**
@@ -148,11 +136,10 @@ zix_path_relative_path(const char* ZIX_NONNULL path);
If `path` is relative, then this returns either a relative path to the
parent if possible, or null. For example, the parent of "a/b" is "a".
- @return The newly allocated path to the parent of `path`, or null if it has
- no parent or allocation failed.
+ @return A view of the parent path within `path`, or the empty string if it
+ has no parent.
*/
-ZIX_PURE_API
-ZixStringView
+ZIX_PURE_API ZIX_NONBLOCKING ZixStringView
zix_path_parent_path(const char* ZIX_NONNULL path);
/**
@@ -161,8 +148,7 @@ zix_path_parent_path(const char* ZIX_NONNULL path);
The filename is the name after the last directory separator. If the path
has no filename, this returns null.
*/
-ZIX_PURE_API
-ZixStringView
+ZIX_PURE_API ZIX_NONBLOCKING ZixStringView
zix_path_filename(const char* ZIX_NONNULL path);
/**
@@ -171,8 +157,7 @@ zix_path_filename(const char* ZIX_NONNULL path);
The "stem" is the filename without the extension, that is, everything up to
the last "." if "." is not the first character.
*/
-ZIX_PURE_API
-ZixStringView
+ZIX_PURE_API ZIX_NONBLOCKING ZixStringView
zix_path_stem(const char* ZIX_NONNULL path);
/**
@@ -181,8 +166,7 @@ zix_path_stem(const char* ZIX_NONNULL path);
The "extension" is everything past the last "." in the filename, if "." is
not the first character.
*/
-ZIX_PURE_API
-ZixStringView
+ZIX_PURE_API ZIX_NONBLOCKING ZixStringView
zix_path_extension(const char* ZIX_NONNULL path);
/**
@@ -192,53 +176,43 @@ zix_path_extension(const char* ZIX_NONNULL path);
*/
/// Return true if `path` has a root path like "/" or "C:\"
-ZIX_PURE_API
-bool
+ZIX_PURE_API ZIX_NONBLOCKING bool
zix_path_has_root_path(const char* ZIX_NULLABLE path);
/// Return true if `path` has a root name like "C:"
-ZIX_PURE_WIN_API
-bool
+ZIX_PURE_WIN_API ZIX_NONBLOCKING bool
zix_path_has_root_name(const char* ZIX_NULLABLE path);
/// Return true if `path` has a root directory like "/" or "\"
-ZIX_PURE_API
-bool
+ZIX_PURE_API ZIX_NONBLOCKING bool
zix_path_has_root_directory(const char* ZIX_NULLABLE path);
/// Return true if `path` has a relative path "dir/file.txt"
-ZIX_PURE_API
-bool
+ZIX_PURE_API ZIX_NONBLOCKING bool
zix_path_has_relative_path(const char* ZIX_NULLABLE path);
/// Return true if `path` has a parent path like "dir/"
-ZIX_PURE_API
-bool
+ZIX_PURE_API ZIX_NONBLOCKING bool
zix_path_has_parent_path(const char* ZIX_NULLABLE path);
/// Return true if `path` has a filename like "file.txt"
-ZIX_PURE_API
-bool
+ZIX_PURE_API ZIX_NONBLOCKING bool
zix_path_has_filename(const char* ZIX_NULLABLE path);
/// Return true if `path` has a stem like "file"
-ZIX_PURE_API
-bool
+ZIX_PURE_API ZIX_NONBLOCKING bool
zix_path_has_stem(const char* ZIX_NULLABLE path);
/// Return true if `path` has an extension like ".txt"
-ZIX_PURE_API
-bool
+ZIX_PURE_API ZIX_NONBLOCKING bool
zix_path_has_extension(const char* ZIX_NULLABLE path);
/// Return true if `path` is an absolute path
-ZIX_PURE_API
-bool
+ZIX_PURE_API ZIX_NONBLOCKING bool
zix_path_is_absolute(const char* ZIX_NULLABLE path);
/// Return true if `path` is a relative path
-ZIX_PURE_API
-bool
+ZIX_PURE_API ZIX_NONBLOCKING bool
zix_path_is_relative(const char* ZIX_NULLABLE path);
/**
diff --git a/include/zix/ring.h b/include/zix/ring.h
index db72f41..325e728 100644
--- a/include/zix/ring.h
+++ b/include/zix/ring.h
@@ -1,12 +1,12 @@
-// Copyright 2011-2022 David Robillard <d@drobilla.net>
+// Copyright 2011-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#ifndef ZIX_RING_H
#define ZIX_RING_H
-#include "zix/allocator.h"
-#include "zix/attributes.h"
-#include "zix/status.h"
+#include <zix/allocator.h>
+#include <zix/attributes.h>
+#include <zix/status.h>
#include <stdint.h>
@@ -19,7 +19,7 @@ ZIX_BEGIN_DECLS
*/
/**
- @defgroup zix_ring_setup Setup
+ @defgroup zix_ring_types Types
@{
*/
@@ -32,22 +32,30 @@ ZIX_BEGIN_DECLS
typedef struct ZixRingImpl ZixRing;
/**
+ @}
+ @defgroup zix_ring_setup Setup
+ @{
+*/
+
+/**
Create a new ring.
@param allocator Allocator for the ring object and its array.
- @param size Size of the ring in bytes (note this may be rounded up).
+ @param size Minimum size of the ring in bytes (rounded up to a power of 2).
- At most `size` - 1 bytes may be stored in the ring at once.
+ Note that one byte of the ring is reserved, so in order to be able to write
+ `n` bytes to the ring at once, `size` must be `n + 1`.
*/
-ZIX_API
-ZIX_NODISCARD
-ZixRing* ZIX_ALLOCATED
+ZIX_API ZIX_NODISCARD ZixRing* ZIX_ALLOCATED
zix_ring_new(ZixAllocator* ZIX_NULLABLE allocator, uint32_t size);
-/// Destroy a ring
-ZIX_API
-void
+/**
+ Destroy a ring.
+
+ This frees the ring structure and its buffer, discarding its contents.
+*/
+ZIX_API void
zix_ring_free(ZixRing* ZIX_NULLABLE ring);
/**
@@ -57,8 +65,7 @@ zix_ring_free(ZixRing* ZIX_NULLABLE ring);
after zix_ring_new() to lock all ring memory to avoid page faults while
using the ring.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_ring_mlock(ZixRing* ZIX_NONNULL ring);
/**
@@ -67,8 +74,7 @@ zix_ring_mlock(ZixRing* ZIX_NONNULL ring);
This function is NOT thread-safe, it may only be called when there is no
reader or writer.
*/
-ZIX_API
-void
+ZIX_API ZIX_REALTIME void
zix_ring_reset(ZixRing* ZIX_NONNULL ring);
/**
@@ -77,8 +83,7 @@ zix_ring_reset(ZixRing* ZIX_NONNULL ring);
This function returns a constant for any given ring, and may (but usually
shouldn't) be called anywhere.
*/
-ZIX_PURE_API
-uint32_t
+ZIX_PURE_API ZIX_REALTIME uint32_t
zix_ring_capacity(const ZixRing* ZIX_NONNULL ring);
/**
@@ -88,24 +93,47 @@ zix_ring_capacity(const ZixRing* ZIX_NONNULL ring);
@{
*/
-/// Return the number of bytes available for reading
-ZIX_PURE_API
-uint32_t
+/**
+ Return the number of bytes available for reading.
+
+ This function returns at most one less than the ring's buffer size.
+*/
+ZIX_PURE_API ZIX_REALTIME uint32_t
zix_ring_read_space(const ZixRing* ZIX_NONNULL ring);
-/// Read from the ring without advancing the read head
-ZIX_API
-uint32_t
+/**
+ Read from the ring without advancing the read head.
+
+ @param ring The ring to read data from.
+ @param dst The buffer to write data to.
+ @param size The number of bytes to read from `ring` and write to `dst`.
+
+ @return The number of bytes read, which is either `size` on success, or zero
+ on failure.
+*/
+ZIX_API ZIX_NONBLOCKING uint32_t
zix_ring_peek(ZixRing* ZIX_NONNULL ring, void* ZIX_NONNULL dst, uint32_t size);
-/// Read from the ring and advance the read head
-ZIX_API
-uint32_t
+/**
+ Read from the ring and advance the read head.
+
+ @param ring The ring to read data from.
+ @param dst The buffer to write data to.
+ @param size The number of bytes to read from `ring` and write to `dst`.
+
+ @return The number of bytes read, which is either `size` on success, or zero
+ on failure.
+*/
+ZIX_API ZIX_NONBLOCKING uint32_t
zix_ring_read(ZixRing* ZIX_NONNULL ring, void* ZIX_NONNULL dst, uint32_t size);
-/// Advance the read head, ignoring any data
-ZIX_API
-uint32_t
+/**
+ Advance the read head, ignoring any data.
+
+ @return Either `size` on success, or zero if there aren't enough bytes to
+ skip.
+*/
+ZIX_API ZIX_REALTIME uint32_t
zix_ring_skip(ZixRing* ZIX_NONNULL ring, uint32_t size);
/**
@@ -123,20 +151,36 @@ zix_ring_skip(ZixRing* ZIX_NONNULL ring, uint32_t size);
written in several chunks before being "committed" and becoming readable.
This can be useful for things like prefixing messages with a header without
needing an allocated buffer to construct the "packet".
+
+ The contents of this structure are an implementation detail and must not be
+ manipulated by the user.
*/
typedef struct {
uint32_t read_head; ///< Read head at the start of the transaction
uint32_t write_head; ///< Write head if the transaction were committed
} ZixRingTransaction;
-/// Return the number of bytes available for writing
-ZIX_PURE_API
-uint32_t
+/**
+ Return the number of bytes available for writing.
+
+ This function returns at most one less than the ring's buffer size.
+*/
+ZIX_PURE_API ZIX_REALTIME uint32_t
zix_ring_write_space(const ZixRing* ZIX_NONNULL ring);
-/// Write data to the ring
-ZIX_API
-uint32_t
+/**
+ Write data to the ring.
+
+ This writes a contiguous input buffer of bytes to the ring.
+
+ @param ring The ring to write data to.
+ @param src The buffer to read data from.
+ @param size The number of bytes to read from `src` and write to `ring`.
+
+ @return The number of bytes written, which is either `size` on success, or
+ zero on failure.
+*/
+ZIX_API ZIX_NONBLOCKING uint32_t
zix_ring_write(ZixRing* ZIX_NONNULL ring,
const void* ZIX_NONNULL src,
uint32_t size);
@@ -155,9 +199,7 @@ zix_ring_write(ZixRing* ZIX_NONNULL ring,
@param ring The ring to write data to.
@return A new empty transaction.
*/
-ZIX_API
-ZIX_NODISCARD
-ZixRingTransaction
+ZIX_API ZIX_REALTIME ZIX_NODISCARD ZixRingTransaction
zix_ring_begin_write(ZixRing* ZIX_NONNULL ring);
/**
@@ -178,8 +220,7 @@ zix_ring_begin_write(ZixRing* ZIX_NONNULL ring);
@param size Length of data to write in bytes.
@return #ZIX_STATUS_NO_MEM or #ZIX_STATUS_SUCCESS.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZIX_NONBLOCKING ZixStatus
zix_ring_amend_write(ZixRing* ZIX_NONNULL ring,
ZixRingTransaction* ZIX_NONNULL tx,
const void* ZIX_NONNULL src,
@@ -198,8 +239,7 @@ zix_ring_amend_write(ZixRing* ZIX_NONNULL ring,
@param tx The active transaction, from zix_ring_begin_write().
@return #ZIX_STATUS_SUCCESS.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZIX_REALTIME ZixStatus
zix_ring_commit_write(ZixRing* ZIX_NONNULL ring,
const ZixRingTransaction* ZIX_NONNULL tx);
diff --git a/include/zix/sem.h b/include/zix/sem.h
index 42dd6c3..0a92152 100644
--- a/include/zix/sem.h
+++ b/include/zix/sem.h
@@ -1,11 +1,11 @@
-// Copyright 2012-2022 David Robillard <d@drobilla.net>
+// Copyright 2012-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#ifndef ZIX_SEM_H
#define ZIX_SEM_H
-#include "zix/attributes.h"
-#include "zix/status.h"
+#include <zix/attributes.h>
+#include <zix/status.h>
#ifdef __APPLE__
# include <mach/mach.h>
@@ -15,10 +15,10 @@
# include <semaphore.h>
#endif
-ZIX_BEGIN_DECLS
-
#include <stdint.h>
+ZIX_BEGIN_DECLS
+
/**
@defgroup zix_sem Semaphore
@ingroup zix_threading
@@ -37,7 +37,7 @@ ZIX_BEGIN_DECLS
be 0 so the semaphore can be used as a simple signal where each post
corresponds to one wait.
- Semaphores are very efficient (much moreso than a mutex/cond pair). In
+ Semaphores are very efficient (compared to a mutex/cond pair). In
particular, at least on Linux, post is async-signal-safe, which means it
does not block and will not be interrupted. If you need to signal from
a realtime thread, this is the most appropriate primitive to use.
@@ -49,8 +49,7 @@ typedef struct ZixSemImpl ZixSem;
@return #ZIX_STATUS_SUCCESS, or an unlikely error.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_sem_init(ZixSem* ZIX_NONNULL sem, unsigned initial);
/**
@@ -58,8 +57,7 @@ zix_sem_init(ZixSem* ZIX_NONNULL sem, unsigned initial);
@return #ZIX_STATUS_SUCCESS, or an error.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_sem_destroy(ZixSem* ZIX_NONNULL sem);
/**
@@ -71,8 +69,7 @@ zix_sem_destroy(ZixSem* ZIX_NONNULL sem);
if the maximum possible value would have been exceeded, or
#ZIX_STATUS_BAD_ARG if `sem` is invalid.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZIX_REALTIME ZixStatus
zix_sem_post(ZixSem* ZIX_NONNULL sem);
/**
@@ -83,8 +80,7 @@ zix_sem_post(ZixSem* ZIX_NONNULL sem);
@return #ZIX_STATUS_SUCCESS if `sem` was decremented, or #ZIX_STATUS_BAD_ARG
if `sem` is invalid.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_sem_wait(ZixSem* ZIX_NONNULL sem);
/**
@@ -94,8 +90,7 @@ zix_sem_wait(ZixSem* ZIX_NONNULL sem);
#ZIX_STATUS_UNAVAILABLE if it was already zero, or #ZIX_STATUS_BAD_ARG if
`sem` is invalid.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_sem_try_wait(ZixSem* ZIX_NONNULL sem);
/**
@@ -108,8 +103,7 @@ zix_sem_try_wait(ZixSem* ZIX_NONNULL sem);
the system does not support timed waits, or #ZIX_STATUS_BAD_ARG if `sem` is
invalid.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_sem_timed_wait(ZixSem* ZIX_NONNULL sem,
uint32_t seconds,
uint32_t nanoseconds);
diff --git a/include/zix/status.h b/include/zix/status.h
index 47aede8..9b68f06 100644
--- a/include/zix/status.h
+++ b/include/zix/status.h
@@ -1,10 +1,10 @@
-// Copyright 2016-2022 David Robillard <d@drobilla.net>
+// Copyright 2016-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#ifndef ZIX_STATUS_H
#define ZIX_STATUS_H
-#include "zix/attributes.h"
+#include <zix/attributes.h>
ZIX_BEGIN_DECLS
@@ -38,8 +38,7 @@ typedef enum {
The returned string is always one sentence, with an uppercase first
character, and no trailing period.
*/
-ZIX_CONST_API
-const char*
+ZIX_CONST_API ZIX_REALTIME const char*
zix_strerror(ZixStatus status);
/**
diff --git a/include/zix/string_view.h b/include/zix/string_view.h
index cfd478d..82e1c03 100644
--- a/include/zix/string_view.h
+++ b/include/zix/string_view.h
@@ -4,8 +4,8 @@
#ifndef ZIX_STRING_VIEW_H
#define ZIX_STRING_VIEW_H
-#include "zix/allocator.h"
-#include "zix/attributes.h"
+#include <zix/allocator.h>
+#include <zix/attributes.h>
#include <stdbool.h>
#include <stddef.h>
@@ -40,9 +40,7 @@ typedef struct {
// clang-format on
/// Return a view of an empty string
-ZIX_ALWAYS_INLINE_FUNC
-ZIX_CONST_FUNC
-static inline ZixStringView
+ZIX_ALWAYS_INLINE_FUNC ZIX_CONST_FUNC static inline ZixStringView
zix_empty_string(void)
{
const ZixStringView view = {"", 0U};
@@ -62,9 +60,7 @@ zix_empty_string(void)
@param len Length of the substring in bytes, not including the trailing null
terminator if present.
*/
-ZIX_ALWAYS_INLINE_FUNC
-ZIX_CONST_FUNC
-static inline ZixStringView
+ZIX_ALWAYS_INLINE_FUNC ZIX_CONST_FUNC static inline ZixStringView
zix_substring(const char* const ZIX_NONNULL str, const size_t len)
{
const ZixStringView view = {str, len};
@@ -78,9 +74,7 @@ zix_substring(const char* const ZIX_NONNULL str, const size_t len)
@param str Pointer to the start of a null-terminated C string, or null.
*/
-ZIX_ALWAYS_INLINE_FUNC
-ZIX_PURE_FUNC
-static inline ZixStringView
+ZIX_ALWAYS_INLINE_FUNC ZIX_PURE_FUNC static inline ZixStringView
// NOLINTNEXTLINE(clang-diagnostic-unused-function)
zix_string(const char* const ZIX_NULLABLE str)
{
@@ -90,8 +84,7 @@ zix_string(const char* const ZIX_NULLABLE str)
/**
Copy a string view into a newly allocated null-terminated string.
*/
-ZIX_MALLOC_API
-char* ZIX_ALLOCATED
+ZIX_MALLOC_API char* ZIX_ALLOCATED
zix_string_view_copy(ZixAllocator* ZIX_NULLABLE allocator, ZixStringView view);
/**
@@ -101,8 +94,7 @@ zix_string_view_copy(ZixAllocator* ZIX_NULLABLE allocator, ZixStringView view);
has fast paths for when the operands have different lengths, or point to the
same string data.
*/
-ZIX_PURE_API
-bool
+ZIX_PURE_API ZIX_NONBLOCKING bool
zix_string_view_equals(ZixStringView lhs, ZixStringView rhs);
/**
diff --git a/include/zix/thread.h b/include/zix/thread.h
index 2493ed3..77a6bf5 100644
--- a/include/zix/thread.h
+++ b/include/zix/thread.h
@@ -4,8 +4,8 @@
#ifndef ZIX_THREAD_H
#define ZIX_THREAD_H
-#include "zix/attributes.h"
-#include "zix/status.h"
+#include <zix/attributes.h>
+#include <zix/status.h>
#ifdef _WIN32
# include <windows.h>
@@ -62,8 +62,7 @@ typedef ZixThreadResult(ZIX_THREAD_FUNC* ZixThreadFunc)(void*);
@return #ZIX_STATUS_SUCCESS on success, or #ZIX_STATUS_ERROR.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_thread_create(ZixThread* thread,
size_t stack_size,
ZixThreadFunc function,
@@ -74,8 +73,7 @@ zix_thread_create(ZixThread* thread,
@return #ZIX_STATUS_SUCCESS on success, or #ZIX_STATUS_ERROR.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_thread_join(ZixThread thread);
/**
diff --git a/include/zix/tree.h b/include/zix/tree.h
index b51fa72..981ebc4 100644
--- a/include/zix/tree.h
+++ b/include/zix/tree.h
@@ -1,12 +1,12 @@
-// Copyright 2011-2022 David Robillard <d@drobilla.net>
+// Copyright 2011-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#ifndef ZIX_TREE_H
#define ZIX_TREE_H
-#include "zix/allocator.h"
-#include "zix/attributes.h"
-#include "zix/status.h"
+#include <zix/allocator.h>
+#include <zix/attributes.h>
+#include <zix/status.h>
#include <stdbool.h>
#include <stddef.h>
@@ -20,7 +20,7 @@ ZIX_BEGIN_DECLS
*/
/**
- @defgroup zix_tree_setup Setup
+ @defgroup zix_tree_types Types
@{
*/
@@ -36,10 +36,14 @@ typedef int (*ZixTreeCompareFunc)(const void* ZIX_UNSPECIFIED a,
typedef void (*ZixTreeDestroyFunc)(void* ZIX_UNSPECIFIED ptr,
const void* ZIX_UNSPECIFIED user_data);
+/**
+ @}
+ @defgroup zix_tree_setup Setup
+ @{
+*/
+
/// Create a new (empty) tree
-ZIX_API
-ZIX_NODISCARD
-ZixTree* ZIX_ALLOCATED
+ZIX_API ZIX_NODISCARD ZixTree* ZIX_ALLOCATED
zix_tree_new(ZixAllocator* ZIX_NULLABLE allocator,
bool allow_duplicates,
ZixTreeCompareFunc ZIX_NONNULL cmp,
@@ -48,13 +52,11 @@ zix_tree_new(ZixAllocator* ZIX_NULLABLE allocator,
const void* ZIX_NULLABLE destroy_user_data);
/// Free `t`
-ZIX_API
-void
+ZIX_API void
zix_tree_free(ZixTree* ZIX_NULLABLE t);
/// Return the number of elements in `t`
-ZIX_PURE_API
-size_t
+ZIX_PURE_API ZIX_REALTIME size_t
zix_tree_size(const ZixTree* ZIX_NONNULL t);
/**
@@ -67,48 +69,39 @@ zix_tree_size(const ZixTree* ZIX_NONNULL t);
typedef struct ZixTreeNodeImpl ZixTreeIter;
/// Return the data associated with the given tree item
-ZIX_PURE_API
-void* ZIX_UNSPECIFIED
+ZIX_PURE_API ZIX_REALTIME void* ZIX_UNSPECIFIED
zix_tree_get(const ZixTreeIter* ZIX_NULLABLE ti);
/// Return an iterator to the first (smallest) element in `t`
-ZIX_PURE_API
-ZixTreeIter* ZIX_NULLABLE
+ZIX_PURE_API ZIX_NONBLOCKING ZixTreeIter* ZIX_NULLABLE
zix_tree_begin(ZixTree* ZIX_NONNULL t);
/// Return an iterator the the element one past the last element in `t`
-ZIX_CONST_API
-ZixTreeIter* ZIX_NULLABLE
+ZIX_CONST_API ZIX_REALTIME ZixTreeIter* ZIX_NULLABLE
zix_tree_end(ZixTree* ZIX_NONNULL t);
/// Return true iff `i` is an iterator to the end of its tree
-ZIX_CONST_API
-bool
+ZIX_CONST_API ZIX_REALTIME bool
zix_tree_iter_is_end(const ZixTreeIter* ZIX_NULLABLE i);
/// Return an iterator to the last (largest) element in `t`
-ZIX_PURE_API
-ZixTreeIter* ZIX_NULLABLE
+ZIX_PURE_API ZIX_NONBLOCKING ZixTreeIter* ZIX_NULLABLE
zix_tree_rbegin(ZixTree* ZIX_NONNULL t);
/// Return an iterator the the element one before the first element in `t`
-ZIX_CONST_API
-ZixTreeIter* ZIX_NULLABLE
+ZIX_CONST_API ZIX_REALTIME ZixTreeIter* ZIX_NULLABLE
zix_tree_rend(ZixTree* ZIX_NONNULL t);
/// Return true iff `i` is an iterator to the reverse end of its tree
-ZIX_CONST_API
-bool
+ZIX_CONST_API ZIX_REALTIME bool
zix_tree_iter_is_rend(const ZixTreeIter* ZIX_NULLABLE i);
/// Return an iterator that points to the element one past `i`
-ZIX_PURE_API
-ZixTreeIter* ZIX_NULLABLE
+ZIX_PURE_API ZIX_NONBLOCKING ZixTreeIter* ZIX_NULLABLE
zix_tree_iter_next(ZixTreeIter* ZIX_NULLABLE i);
/// Return an iterator that points to the element one before `i`
-ZIX_PURE_API
-ZixTreeIter* ZIX_NULLABLE
+ZIX_PURE_API ZIX_NONBLOCKING ZixTreeIter* ZIX_NULLABLE
zix_tree_iter_prev(ZixTreeIter* ZIX_NULLABLE i);
/**
@@ -118,15 +111,13 @@ zix_tree_iter_prev(ZixTreeIter* ZIX_NULLABLE i);
*/
/// Insert the element `e` into `t` and point `ti` at the new element
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_tree_insert(ZixTree* ZIX_NONNULL t,
void* ZIX_UNSPECIFIED e,
ZixTreeIter* ZIX_NULLABLE* ZIX_NULLABLE ti);
/// Remove the item pointed at by `ti` from `t`
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_tree_remove(ZixTree* ZIX_NONNULL t, ZixTreeIter* ZIX_NONNULL ti);
/**
@@ -140,8 +131,7 @@ zix_tree_remove(ZixTree* ZIX_NONNULL t, ZixTreeIter* ZIX_NONNULL ti);
If no such item exists, `ti` is set to NULL.
*/
-ZIX_API
-ZixStatus
+ZIX_API ZixStatus
zix_tree_find(const ZixTree* ZIX_NONNULL t,
const void* ZIX_UNSPECIFIED e,
ZixTreeIter* ZIX_NULLABLE* ZIX_NONNULL ti);
diff --git a/include/zix/warnings.h b/include/zix/warnings.h
new file mode 100644
index 0000000..4c4c5a3
--- /dev/null
+++ b/include/zix/warnings.h
@@ -0,0 +1,41 @@
+// Copyright 2025 David Robillard <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#ifndef ZIX_WARNINGS_H
+#define ZIX_WARNINGS_H
+
+/**
+ @defgroup zix_attributes Attributes
+ @ingroup zix_utilities
+ @{
+*/
+
+#if defined(__clang__)
+
+# if __clang_major__ >= 20
+# define ZIX_DISABLE_EFFECT_WARNINGS \
+ _Pragma("clang diagnostic push") \
+ _Pragma("clang diagnostic ignored \"-Wfunction-effects\"")
+# else
+# define ZIX_DISABLE_EFFECT_WARNINGS _Pragma("clang diagnostic push")
+# endif
+
+# define ZIX_RESTORE_WARNINGS _Pragma("clang diagnostic pop")
+
+#elif defined(__GNUC__)
+
+# define ZIX_DISABLE_EFFECT_WARNINGS _Pragma("GCC diagnostic push")
+# define ZIX_RESTORE_WARNINGS _Pragma("GCC diagnostic pop")
+
+#else
+
+# define ZIX_DISABLE_EFFECT_WARNINGS ///< Disable function effect warnings
+# define ZIX_RESTORE_WARNINGS ///< Restore disabled warnings
+
+#endif
+
+/**
+ @}
+*/
+
+#endif /* ZIX_WARNINGS_H */
diff --git a/include/zix/zix.h b/include/zix/zix.h
index ca72d35..d24fa4d 100644
--- a/include/zix/zix.h
+++ b/include/zix/zix.h
@@ -1,4 +1,4 @@
-// Copyright 2016-2022 David Robillard <d@drobilla.net>
+// Copyright 2016-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#ifndef ZIX_ZIX_H
@@ -16,9 +16,10 @@
@{
*/
-#include "zix/attributes.h"
-#include "zix/status.h"
-#include "zix/string_view.h"
+#include <zix/attributes.h>
+#include <zix/status.h>
+#include <zix/string_view.h>
+#include <zix/warnings.h>
/**
@}
@@ -26,8 +27,8 @@
@{
*/
-#include "zix/allocator.h"
-#include "zix/bump_allocator.h"
+#include <zix/allocator.h>
+#include <zix/bump_allocator.h>
/**
@}
@@ -35,7 +36,7 @@
@{
*/
-#include "zix/digest.h"
+#include <zix/digest.h>
/**
@}
@@ -43,10 +44,10 @@
@{
*/
-#include "zix/btree.h"
-#include "zix/hash.h"
-#include "zix/ring.h"
-#include "zix/tree.h"
+#include <zix/btree.h>
+#include <zix/hash.h>
+#include <zix/ring.h>
+#include <zix/tree.h>
/**
@}
@@ -54,8 +55,8 @@
@{
*/
-#include "zix/sem.h"
-#include "zix/thread.h"
+#include <zix/sem.h>
+#include <zix/thread.h>
/**
@}
@@ -63,8 +64,16 @@
@{
*/
-#include "zix/filesystem.h"
-#include "zix/path.h"
+#include <zix/filesystem.h>
+#include <zix/path.h>
+
+/**
+ @}
+ @defgroup zix_environment Environment
+ @{
+*/
+
+#include <zix/environment.h>
/**
@}
diff --git a/meson.build b/meson.build
index f84642b..b07dc99 100644
--- a/meson.build
+++ b/meson.build
@@ -1,4 +1,4 @@
-# Copyright 2020-2023 David Robillard <d@drobilla.net>
+# Copyright 2020-2025 David Robillard <d@drobilla.net>
# SPDX-License-Identifier: 0BSD OR ISC
project(
@@ -8,11 +8,13 @@ project(
'b_ndebug=if-release',
'buildtype=release',
'c_std=c99',
+ 'c_winlibs=',
'cpp_std=c++17',
+ 'cpp_winlibs=',
],
license: 'ISC',
meson_version: '>= 0.56.0',
- version: '0.5.0',
+ version: '0.7.1',
)
zix_src_root = meson.current_source_dir()
@@ -29,33 +31,25 @@ versioned_name = 'zix' + version_suffix
pkg = import('pkgconfig')
cc = meson.get_compiler('c')
-# Restrict Windows API usage to Vista and earlier
-if host_machine.system() == 'windows'
- if cc.get_id() == 'msvc'
- add_project_arguments('/D_WIN32_WINNT=0x0600', language: ['c', 'cpp'])
- else
- add_project_arguments('-D_WIN32_WINNT=0x0600', language: ['c', 'cpp'])
- endif
-endif
-
# Set global warning suppressions
warning_level = get_option('warning_level')
c_suppressions = []
if cc.get_id() in ['clang', 'emscripten']
if warning_level == 'everything'
c_suppressions += [
- '-Wno-bad-function-cast',
- '-Wno-c11-extensions', # Glib
+ '-Wno-c++98-compat',
'-Wno-declaration-after-statement',
'-Wno-implicit-fallthrough', # Really for clang < 12
'-Wno-padded',
+ '-Wno-pre-c11-compat',
+ '-Wno-pre-c23-compat',
+ '-Wno-switch-default',
'-Wno-unsafe-buffer-usage',
+ '-Wno-unsafe-buffer-usage-in-libc-call',
]
if not meson.is_cross_build()
- c_suppressions += [
- '-Wno-poison-system-directories',
- ]
+ c_suppressions += ['-Wno-poison-system-directories']
endif
if host_machine.system() == 'windows'
@@ -67,22 +61,17 @@ if cc.get_id() in ['clang', 'emscripten']
endif
if warning_level in ['everything', '3']
- c_suppressions += [
- '-Wno-nullability-extension',
- ]
+ c_suppressions += ['-Wno-nullability-extension']
endif
if cc.get_id() == 'emscripten'
- c_suppressions += [
- '-Wno-format',
- ]
+ c_suppressions += ['-Wno-format']
endif
elif cc.get_id() == 'gcc'
if warning_level == 'everything'
c_suppressions += [
- '-Wno-bad-function-cast',
- '-Wno-cast-function-type',
+ '-Wno-analyzer-too-complex',
'-Wno-inline',
'-Wno-padded',
'-Wno-strict-overflow',
@@ -92,7 +81,6 @@ elif cc.get_id() == 'gcc'
if host_machine.system() == 'windows'
c_suppressions += [
- '-Wno-format',
'-Wno-suggest-attribute=const',
'-Wno-suggest-attribute=format',
'-Wno-suggest-attribute=pure',
@@ -101,9 +89,7 @@ elif cc.get_id() == 'gcc'
endif
if warning_level in ['everything', '3']
- c_suppressions += [
- '-Wno-pedantic', # C11
- ]
+ c_suppressions += ['-Wno-pedantic'] # C11
endif
elif cc.get_id() == 'msvc'
@@ -131,16 +117,8 @@ elif cc.get_id() == 'msvc'
]
endif
- if warning_level in ['everything', '3', '2']
- c_suppressions += [
- '/wd4996', # POSIX name for this item is deprecated
- ]
- endif
-
- if warning_level in ['everything', '3', '2', '1']
- c_suppressions += [
- '/wd4114', # same type qualifier used more than once
- ]
+ if get_option('win_ver') in ['nt4', 'xp']
+ c_suppressions += ['/D_CRT_SECURE_NO_WARNINGS']
endif
endif
@@ -179,7 +157,22 @@ elif host_machine.system() in ['gnu', 'linux']
]
elif host_machine.system() in ['dragonfly', 'freebsd', 'netbsd', 'openbsd']
system_c_args += [
- '-D_BSD_SOURCE',
+ '-D_BSD_SOURCE'
+ ]
+elif host_machine.system() == 'windows'
+ if not get_option('win_wchar').disabled()
+ system_c_args += ['-DUNICODE', '-D_UNICODE']
+ endif
+
+ winvers = {
+ 'nt4': '0x0400',
+ 'winxp': '0x0501',
+ 'vista': '0x0600',
+ 'win8': '0x0602',
+ }
+ system_c_args += [
+ '-DWIN32_LEAN_AND_MEAN',
+ '-D_WIN32_WINNT=' + winvers[get_option('win_ver')],
]
else
system_c_args += [
@@ -258,16 +251,41 @@ else
}
windows_checks = {
+ 'sopen_s': template.format(
+ 'io.h',
+ 'int fd; return (int)_sopen_s(&fd, "/", 0, 0, 0644);',
+ ),
+ 'CreateFile2': template.format(
+ 'windows.h',
+ 'CREATEFILE2_EXTENDED_PARAMETERS p = {0};\n'
+ + 'return CreateFile2(NULL, FILE_READ_ATTRIBUTES, 0U, OPEN_EXISTING, &p);',
+ ),
+ 'CreateHardLink': template.format(
+ 'windows.h',
+ 'return CreateHardLink(TEXT("l"), TEXT("t"), NULL);',
+ ),
'CreateSymbolicLink': template.format(
'windows.h',
- 'return CreateSymbolicLink(' + '"l", "t", SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE);',
+ 'return CreateSymbolicLink(TEXT("l"), TEXT("t"), SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE);',
+ ),
+ 'GetFinalPathNameByHandle': template.format(
+ 'windows.h',
+ 'return GetFinalPathNameByHandle(NULL, NULL, 0U, 0U);',
+ ),
+ 'VirtualLock': template.format(
+ 'windows.h',
+ 'return VirtualLock(NULL, sizeof(void*));',
),
}
+ check_c_args = system_c_args
if host_machine.system() == 'darwin'
checks = posix_checks + mac_checks
elif host_machine.system() == 'windows'
checks = windows_checks
+ if cc.get_id() == 'msvc'
+ check_c_args += ['/Wall', '/WX']
+ endif
else
checks = posix_checks
@@ -276,7 +294,7 @@ else
'''#include <semaphore.h>
#include <time.h>
int main(void) { sem_t s; struct timespec t; return sem_timedwait(&s, &t); }''',
- args: system_c_args,
+ args: check_c_args,
dependencies: thread_dep,
name: 'sem_timedwait',
)
@@ -286,7 +304,18 @@ int main(void) { sem_t s; struct timespec t; return sem_timedwait(&s, &t); }''',
endif
foreach name, check_code : checks
- if cc.links(check_code, args: system_c_args, name: name)
+ is_mingw = host_machine.system() == 'windows' and cc.get_id() == 'gcc'
+ if is_mingw and name in ['CreateFile2', 'CreateSymbolicLink']
+ message('Ignoring MinGW stub @0@'.format(name))
+ continue
+ endif
+
+ if cc.get_id() == 'emscripten' and name in ['flock', 'fileno']
+ message('Ignoring emscripten stub @0@'.format(name))
+ continue
+ endif
+
+ if cc.links(check_code, args: check_c_args, name: name)
platform_c_args += ['-DHAVE_@0@'.format(name.to_upper())]
endif
endforeach
@@ -301,9 +330,11 @@ include_dirs = include_directories(['include'])
c_headers = files(
'include/zix/allocator.h',
'include/zix/attributes.h',
+ 'include/zix/warnings.h',
'include/zix/btree.h',
'include/zix/bump_allocator.h',
'include/zix/digest.h',
+ 'include/zix/environment.h',
'include/zix/filesystem.h',
'include/zix/hash.h',
'include/zix/path.h',
@@ -332,18 +363,16 @@ sources = files(
'src/tree.c',
)
-if host_machine.system() == 'darwin'
- sources += files(
- 'src/posix/filesystem_posix.c',
- 'src/posix/system_posix.c',
- )
-elif host_machine.system() == 'windows'
+if host_machine.system() == 'windows'
sources += files(
+ 'src/win32/environment_win32.c',
'src/win32/filesystem_win32.c',
'src/win32/system_win32.c',
+ 'src/win32/win32_util.c',
)
else
sources += files(
+ 'src/posix/environment_posix.c',
'src/posix/filesystem_posix.c',
'src/posix/system_posix.c',
)
@@ -378,7 +407,7 @@ endif
# Set any additional arguments required for building libraries or programs
library_c_args = platform_c_args + extra_c_args + ['-DZIX_INTERNAL']
library_link_args = []
-program_c_args = platform_c_args + extra_c_args
+program_c_args = extra_c_args
program_link_args = []
if cc.get_id() == 'emscripten'
wasm_c_args = [
@@ -387,10 +416,7 @@ if cc.get_id() == 'emscripten'
'-pthread',
]
- wasm_link_args = [
- '-matomics',
- '-mbulk-memory',
- '-pthread',
+ wasm_link_args = wasm_c_args + [
['-s', 'ENVIRONMENT=node,worker'],
['-s', 'INITIAL_MEMORY=33554432'],
]
@@ -410,9 +436,10 @@ libzix = library(
versioned_name,
sources,
c_args: c_suppressions + library_c_args,
- darwin_versions: ['0.5.0', meson.project_version()],
+ darwin_versions: ['0.7.0', meson.project_version()],
dependencies: dependencies,
gnu_symbol_visibility: 'hidden',
+ implicit_include_directories: false,
include_directories: include_dirs,
install: true,
link_args: library_link_args,
@@ -427,7 +454,7 @@ zix_dep = declare_dependency(
link_with: libzix,
)
-# Generage pkg-config file for external dependants
+# Generate pkg-config file for external dependants
pkg.generate(
libzix,
description: 'Lightweight C library of portability wrappers and data structures',
diff --git a/meson_options.txt b/meson_options.txt
index 3eac738..2d3c16f 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -1,35 +1,42 @@
-# Copyright 2020-2023 David Robillard <d@drobilla.net>
+# Copyright 2020-2025 David Robillard <d@drobilla.net>
# SPDX-License-Identifier: 0BSD OR ISC
-option('benchmarks', type: 'feature', value: 'auto', yield: true,
+option('benchmarks', type: 'feature',
description: 'Build benchmarks')
-option('checks', type: 'feature', value: 'enabled', yield: true,
+option('checks', type: 'feature', value: 'enabled',
description: 'Check for platform-specific features')
-option('docs', type: 'feature', value: 'auto', yield: true,
+option('docs', type: 'feature',
description: 'Build documentation')
-option('html', type: 'feature', value: 'auto', yield: true,
+option('html', type: 'feature',
description: 'Build paginated HTML documentation')
-option('lint', type: 'boolean', value: false, yield: true,
+option('lint', type: 'boolean', value: false,
description: 'Run code quality checks')
-option('posix', type: 'feature', value: 'auto', yield: true,
+option('posix', type: 'feature',
description: 'Use POSIX system facilities')
-option('singlehtml', type: 'feature', value: 'auto', yield: true,
+option('singlehtml', type: 'feature',
description: 'Build single-page HTML documentation')
-option('threads', type: 'feature', value: 'auto', yield: true,
+option('threads', type: 'feature',
description: 'Enable thread support')
-option('tests', type: 'feature', value: 'auto', yield: true,
+option('tests', type: 'feature',
description: 'Build tests')
-option('tests_cpp', type: 'feature', value: 'auto', yield: true,
+option('tests_cpp', type: 'feature',
description: 'Build C++ standard library comparison tests')
option('title', type: 'string', value: 'Zix',
description: 'Project title')
+
+option('win_ver', type: 'combo', value: 'win8',
+ choices: ['nt4', 'winxp', 'vista', 'win8'],
+ description: 'Latest Windows API version to use')
+
+option('win_wchar', type: 'feature',
+ description: 'Use UTF-16 wchar_t and UNICODE with Windows API')
diff --git a/scripts/benchmark.py b/scripts/benchmark.py
index c160445..6721309 100755
--- a/scripts/benchmark.py
+++ b/scripts/benchmark.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
-# Copyright 2011-2022 David Robillard <d@drobilla.net>
+# Copyright 2011-2024 David Robillard <d@drobilla.net>
# SPDX-License-Identifier: ISC
"""
@@ -14,7 +14,7 @@ import subprocess
# Benchmark trees
-subprocess.call(["./tree_bench", "40000", "640000"])
+subprocess.call(["benchmark/tree_bench", "40000", "640000"])
subprocess.call(
[
"../scripts/plot.py",
@@ -64,7 +64,7 @@ if not os.path.exists(FILENAME):
out.write(random_word())
out.write("\n")
-subprocess.call(["./dict_bench", "gibberish.txt"])
+subprocess.call(["benchmark/dict_bench", "gibberish.txt"])
subprocess.call(
[
"../scripts/plot.py",
diff --git a/src/.clang-tidy b/src/.clang-tidy
index 5647a8f..dfa5bf0 100644
--- a/src/.clang-tidy
+++ b/src/.clang-tidy
@@ -1,11 +1,14 @@
-# Copyright 2021-2022 David Robillard <d@drobilla.net>
+# Copyright 2021-2025 David Robillard <d@drobilla.net>
# SPDX-License-Identifier: 0BSD OR ISC
Checks: >
-*-magic-numbers,
-bugprone-easily-swappable-parameters,
+ -bugprone-multi-level-implicit-pointer-conversion,
-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,
-llvm-header-guard,
-misc-no-recursion,
- -readability-function-cognitive-complexity,
+CheckOptions:
+ - key: readability-function-cognitive-complexity.Threshold
+ value: '85'
InheritParentConfig: true
diff --git a/src/allocator.c b/src/allocator.c
index 6c18804..b1453b4 100644
--- a/src/allocator.c
+++ b/src/allocator.c
@@ -1,30 +1,27 @@
// Copyright 2011-2021 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
-#include "zix/allocator.h"
-
-#include "zix/attributes.h"
+#include <zix/allocator.h>
#include "zix_config.h"
+#include <zix/attributes.h>
+
#ifdef _WIN32
-# define WIN32_LEAN_AND_MEAN
# include <malloc.h>
# include <windows.h>
#endif
#include <stdlib.h>
-ZIX_MALLOC_FUNC
-static void*
+ZIX_MALLOC_FUNC static void*
zix_default_malloc(ZixAllocator* const allocator, const size_t size)
{
(void)allocator;
return malloc(size);
}
-ZIX_MALLOC_FUNC
-static void*
+ZIX_MALLOC_FUNC static void*
zix_default_calloc(ZixAllocator* const allocator,
const size_t nmemb,
const size_t size)
@@ -49,8 +46,7 @@ zix_default_free(ZixAllocator* const allocator, void* const ptr)
free(ptr);
}
-ZIX_MALLOC_FUNC
-static void*
+ZIX_MALLOC_FUNC static void*
zix_default_aligned_alloc(ZixAllocator* const allocator,
const size_t alignment,
const size_t size)
diff --git a/src/btree.c b/src/btree.c
index 7970796..56f6341 100644
--- a/src/btree.c
+++ b/src/btree.c
@@ -1,7 +1,11 @@
-// Copyright 2011-2021 David Robillard <d@drobilla.net>
+// Copyright 2011-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
-#include "zix/btree.h"
+#include <zix/btree.h>
+
+#include <zix/allocator.h>
+#include <zix/attributes.h>
+#include <zix/status.h>
#include <assert.h>
#include <stdint.h>
@@ -10,9 +14,9 @@
// #define ZIX_BTREE_SORTED_CHECK 1
// Define ZixShort as an integer type half the size of a pointer
-#if UINTPTR_MAX >= UINT32_MAX
+#if UINTPTR_MAX > UINT32_MAX // 64-bit
typedef uint32_t ZixShort;
-#else
+#else // 32-bit
typedef uint16_t ZixShort;
#endif
@@ -78,8 +82,7 @@ zix_btree_node_new(ZixAllocator* const allocator, const bool leaf)
return node;
}
-ZIX_PURE_FUNC
-static ZixBTreeNode*
+ZIX_PURE_FUNC static ZixBTreeNode*
zix_btree_child(const ZixBTreeNode* const node, const unsigned i)
{
assert(!node->is_leaf);
@@ -99,23 +102,15 @@ zix_btree_new(ZixAllocator* const allocator,
assert(cmp);
- ZixBTree* const t = (ZixBTree*)zix_aligned_alloc(
- allocator, ZIX_BTREE_PAGE_SIZE, ZIX_BTREE_PAGE_SIZE);
-
- if (!t) {
- return NULL;
- }
-
- if (!(t->root = zix_btree_node_new(allocator, true))) {
- zix_aligned_free(allocator, t);
- return NULL;
+ ZixBTree* const t = (ZixBTree*)zix_malloc(allocator, sizeof(ZixBTree));
+ if (t) {
+ t->allocator = allocator;
+ t->root = NULL;
+ t->cmp = cmp;
+ t->cmp_data = cmp_data;
+ t->size = 0U;
}
- t->allocator = allocator;
- t->cmp = cmp;
- t->cmp_data = cmp_data;
- t->size = 0U;
-
return t;
}
@@ -154,23 +149,25 @@ zix_btree_free(ZixBTree* const t,
if (t) {
zix_btree_clear(t, destroy, destroy_user_data);
zix_aligned_free(t->allocator, t->root);
- zix_aligned_free(t->allocator, t);
+ zix_free(t->allocator, t);
}
}
void
-zix_btree_clear(ZixBTree* const t,
- ZixBTreeDestroyFunc destroy,
- const void* destroy_user_data)
+zix_btree_clear(ZixBTree* const t,
+ const ZixBTreeDestroyFunc destroy,
+ const void* const destroy_user_data)
{
- zix_btree_free_children(t, t->root, destroy, destroy_user_data);
+ if (t->root) {
+ zix_btree_free_children(t, t->root, destroy, destroy_user_data);
+ zix_aligned_free(t->allocator, t->root);
+ t->root = NULL;
+ }
- memset(t->root, 0U, sizeof(ZixBTreeNode));
- t->root->is_leaf = true;
- t->size = 0U;
+ t->size = 0U;
}
-size_t
+ZIX_REALTIME size_t
zix_btree_size(const ZixBTree* const t)
{
assert(t);
@@ -196,7 +193,7 @@ zix_btree_ainsert(void** const array,
const unsigned i,
void* const e)
{
- memmove(array + i + 1U, array + i, (n - i) * sizeof(e));
+ memmove(array + i + 1U, array + i, ((size_t)n - i) * sizeof(e));
array[i] = e;
}
@@ -205,7 +202,7 @@ static void*
zix_btree_aerase(void** const array, const unsigned n, const unsigned i)
{
void* const ret = array[i];
- memmove(array + i, array + i + 1U, (n - i) * sizeof(ret));
+ memmove(array + i, array + i + 1U, ((size_t)n - i) * sizeof(ret));
return ret;
}
@@ -221,7 +218,7 @@ zix_btree_split_child(ZixAllocator* const allocator,
assert(i < n->n_vals + 1U);
assert(zix_btree_child(n, i) == lhs);
- const ZixShort max_n_vals = zix_btree_max_vals(lhs);
+ const unsigned max_n_vals = zix_btree_max_vals(lhs);
ZixBTreeNode* rhs = zix_btree_node_new(allocator, lhs->is_leaf);
if (!rhs) {
return NULL;
@@ -247,7 +244,7 @@ zix_btree_split_child(ZixAllocator* const allocator,
rhs->n_vals * sizeof(void*));
memcpy(rhs->data.inode.children,
lhs->data.inode.children + lhs->n_vals + 1U,
- (rhs->n_vals + 1U) * sizeof(ZixBTreeNode*));
+ ((size_t)rhs->n_vals + 1U) * sizeof(ZixBTreeNode*));
// Move middle value up to parent
zix_btree_ainsert(
@@ -394,14 +391,14 @@ zix_btree_leaf_find(const ZixBTree* const t,
t->cmp, t->cmp_data, n->data.leaf.vals, n->n_vals, e, equal);
}
-static inline bool
+ZIX_PURE_FUNC static inline bool
zix_btree_can_remove_from(const ZixBTreeNode* const n)
{
assert(n->n_vals >= zix_btree_min_vals(n));
return n->n_vals > zix_btree_min_vals(n);
}
-static inline bool
+ZIX_PURE_FUNC static inline bool
zix_btree_is_full(const ZixBTreeNode* const n)
{
assert(n->n_vals <= zix_btree_max_vals(n));
@@ -433,8 +430,13 @@ zix_btree_insert(ZixBTree* const t, void* const e)
ZixStatus st = ZIX_STATUS_SUCCESS;
- // Grow up if necessary to ensure the root is not full
- if (zix_btree_is_full(t->root)) {
+ if (!t->root) {
+ // Empty tree, create a new leaf root
+ if (!(t->root = zix_btree_node_new(t->allocator, true))) {
+ return ZIX_STATUS_NO_MEM;
+ }
+ } else if (zix_btree_is_full(t->root)) {
+ // Grow up if necessary to ensure the root is not full
if ((st = zix_btree_grow_up(t))) {
return st;
}
@@ -490,8 +492,9 @@ zix_btree_insert(ZixBTree* const t, void* const e)
static void
zix_btree_iter_set_frame(ZixBTreeIter* const ti,
ZixBTreeNode* const n,
- const ZixShort i)
+ const unsigned i)
{
+ assert(i <= UINT16_MAX);
ti->nodes[ti->level] = n;
ti->indexes[ti->level] = (uint16_t)i;
}
@@ -617,7 +620,7 @@ zix_btree_merge(ZixBTree* const t, ZixBTreeNode* const n, const unsigned i)
rhs->n_vals * sizeof(void*));
memcpy(lhs->data.inode.children + lhs->n_vals,
rhs->data.inode.children,
- (rhs->n_vals + 1U) * sizeof(void*));
+ ((size_t)rhs->n_vals + 1U) * sizeof(void*));
}
lhs->n_vals += rhs->n_vals;
@@ -677,6 +680,7 @@ zix_btree_fatten_child(ZixBTree* const t, ZixBTreeIter* const iter)
assert(n);
assert(!n->is_leaf);
+ assert(n->n_vals);
ZixBTreeNode* const* const children = n->data.inode.children;
if (i > 0U && zix_btree_can_remove_from(children[i - 1U])) {
@@ -736,6 +740,10 @@ zix_btree_remove(ZixBTree* const t,
assert(t);
assert(out);
+ if (!t->root) {
+ return ZIX_STATUS_NOT_FOUND;
+ }
+
ZixBTreeNode* n = t->root;
ZixBTreeIter* ti = next;
ZixStatus st = ZIX_STATUS_SUCCESS;
@@ -825,6 +833,9 @@ zix_btree_find(const ZixBTree* const t,
ZixBTreeNode* n = t->root;
*ti = zix_btree_end_iter;
+ if (!n) {
+ return ZIX_STATUS_NOT_FOUND;
+ }
while (!n->is_leaf) {
bool equal = false;
@@ -866,6 +877,9 @@ zix_btree_lower_bound(const ZixBTree* const t,
ZixBTreeNode* n = t->root; // Current node
uint16_t found_level = 0U; // Lowest level a match was found at
bool found = false; // True if a match was ever found
+ if (!n) {
+ return ZIX_STATUS_SUCCESS;
+ }
// Search down until we reach a leaf
while (!n->is_leaf) {
@@ -915,7 +929,7 @@ zix_btree_lower_bound(const ZixBTree* const t,
return ZIX_STATUS_SUCCESS;
}
-void*
+ZIX_REALTIME void*
zix_btree_get(const ZixBTreeIter ti)
{
const ZixBTreeNode* const node = ti.nodes[ti.level];
@@ -928,7 +942,7 @@ zix_btree_get(const ZixBTreeIter ti)
: node->data.inode.vals[index];
}
-ZixBTreeIter
+ZIX_NONBLOCKING ZixBTreeIter
zix_btree_begin(const ZixBTree* const t)
{
assert(t);
@@ -948,7 +962,7 @@ zix_btree_begin(const ZixBTree* const t)
return iter;
}
-ZixBTreeIter
+ZIX_REALTIME ZixBTreeIter
zix_btree_end(const ZixBTree* const t)
{
(void)t;
@@ -956,16 +970,16 @@ zix_btree_end(const ZixBTree* const t)
return zix_btree_end_iter;
}
-bool
+ZIX_REALTIME bool
zix_btree_iter_equals(const ZixBTreeIter lhs, const ZixBTreeIter rhs)
{
- const size_t indexes_size = (lhs.level + 1U) * sizeof(uint16_t);
+ const size_t indexes_size = ((size_t)lhs.level + 1U) * sizeof(uint16_t);
return (lhs.level == rhs.level) && (lhs.nodes[0U] == rhs.nodes[0U]) &&
(!lhs.nodes[0U] || !memcmp(lhs.indexes, rhs.indexes, indexes_size));
}
-ZixStatus
+ZIX_NONBLOCKING ZixStatus
zix_btree_iter_increment(ZixBTreeIter* const i)
{
assert(i);
@@ -1003,7 +1017,7 @@ zix_btree_iter_increment(ZixBTreeIter* const i)
return ZIX_STATUS_SUCCESS;
}
-ZixBTreeIter
+ZIX_NONBLOCKING ZixBTreeIter
zix_btree_iter_next(const ZixBTreeIter iter)
{
ZixBTreeIter next = iter;
diff --git a/src/bump_allocator.c b/src/bump_allocator.c
index 924a1c8..e5cb23d 100644
--- a/src/bump_allocator.c
+++ b/src/bump_allocator.c
@@ -1,10 +1,10 @@
// Copyright 2021 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
-#include "zix/bump_allocator.h"
+#include <zix/bump_allocator.h>
-#include "zix/allocator.h"
-#include "zix/attributes.h"
+#include <zix/allocator.h>
+#include <zix/attributes.h>
#include <assert.h>
#include <stddef.h>
@@ -13,8 +13,7 @@
static const size_t min_alignment = sizeof(uintmax_t);
-ZIX_PURE_FUNC
-static size_t
+ZIX_PURE_FUNC static size_t
round_up_multiple(const size_t number, const size_t factor)
{
assert(factor); // Factor must be non-zero
@@ -23,8 +22,7 @@ round_up_multiple(const size_t number, const size_t factor)
return (number + factor - 1U) & ~(factor - 1U);
}
-ZIX_MALLOC_FUNC
-static void*
+ZIX_MALLOC_FUNC static void*
zix_bump_malloc(ZixAllocator* const allocator, const size_t size)
{
ZixBumpAllocator* const state = (ZixBumpAllocator*)allocator;
@@ -46,8 +44,7 @@ zix_bump_malloc(ZixAllocator* const allocator, const size_t size)
return (void*)((char*)state->buffer + state->last);
}
-ZIX_MALLOC_FUNC
-static void*
+ZIX_MALLOC_FUNC static void*
zix_bump_calloc(ZixAllocator* const allocator,
const size_t nmemb,
const size_t size)
@@ -91,8 +88,7 @@ zix_bump_free(ZixAllocator* const allocator, void* const ptr)
}
}
-ZIX_MALLOC_FUNC
-static void*
+ZIX_MALLOC_FUNC static void*
zix_bump_aligned_alloc(ZixAllocator* const allocator,
const size_t alignment,
const size_t size)
@@ -138,8 +134,7 @@ zix_bump_aligned_free(ZixAllocator* const allocator, void* const ptr)
zix_bump_free(allocator, ptr);
}
-ZIX_CONST_FUNC
-ZixBumpAllocator
+ZIX_CONST_FUNC ZIX_REALTIME ZixBumpAllocator
zix_bump_allocator(const size_t capacity, void* buffer)
{
const size_t aligned_top = (uintptr_t)buffer % min_alignment;
diff --git a/src/darwin/sem_darwin.c b/src/darwin/sem_darwin.c
index 0ace564..dd5750b 100644
--- a/src/darwin/sem_darwin.c
+++ b/src/darwin/sem_darwin.c
@@ -1,9 +1,9 @@
// Copyright 2012-2022 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
-#include "zix/sem.h"
+#include <zix/sem.h>
-#include "zix/status.h"
+#include <zix/status.h>
#include <mach/mach.h>
diff --git a/src/digest.c b/src/digest.c
index bc65f90..a7482b3 100644
--- a/src/digest.c
+++ b/src/digest.c
@@ -1,7 +1,10 @@
-// Copyright 2012-2021 David Robillard <d@drobilla.net>
+// Copyright 2012-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
-#include "zix/digest.h"
+#include <zix/attributes.h>
+#include <zix/digest.h>
+
+#include "qualifiers.h"
#include <assert.h>
#include <stdint.h>
@@ -29,10 +32,10 @@ mix64(uint64_t h)
return h;
}
-uint64_t
+ZIX_NONBLOCKING uint64_t
zix_digest64(const uint64_t seed, const void* const buf, const size_t len)
{
- static const uint64_t m = 0x880355F21E6D1965ULL;
+ ZIX_CONSTEXPR uint64_t m = 0x880355F21E6D1965ULL;
// Process as many 64-bit blocks as possible
const size_t n_blocks = len / sizeof(uint64_t);
@@ -79,10 +82,10 @@ zix_digest64(const uint64_t seed, const void* const buf, const size_t len)
return mix64(h);
}
-uint64_t
+ZIX_NONBLOCKING uint64_t
zix_digest64_aligned(const uint64_t seed, const void* const buf, size_t len)
{
- static const uint64_t m = 0x880355F21E6D1965ULL;
+ ZIX_CONSTEXPR uint64_t m = 0x880355F21E6D1965ULL;
assert((uintptr_t)buf % sizeof(uint64_t) == 0U);
assert(len % sizeof(uint64_t) == 0U);
@@ -127,11 +130,11 @@ mix32(uint32_t h)
return h;
}
-uint32_t
+ZIX_NONBLOCKING uint32_t
zix_digest32(const uint32_t seed, const void* const buf, const size_t len)
{
- static const uint32_t c1 = 0xCC9E2D51U;
- static const uint32_t c2 = 0x1B873593U;
+ ZIX_CONSTEXPR uint32_t c1 = 0xCC9E2D51U;
+ ZIX_CONSTEXPR uint32_t c2 = 0x1B873593U;
// Process as many 32-bit blocks as possible
const size_t n_blocks = len / sizeof(uint32_t);
@@ -172,13 +175,13 @@ zix_digest32(const uint32_t seed, const void* const buf, const size_t len)
return mix32(h ^ (uint32_t)len);
}
-uint32_t
+ZIX_NONBLOCKING uint32_t
zix_digest32_aligned(const uint32_t seed,
const void* const buf,
const size_t len)
{
- static const uint32_t c1 = 0xCC9E2D51U;
- static const uint32_t c2 = 0x1B873593U;
+ ZIX_CONSTEXPR uint32_t c1 = 0xCC9E2D51U;
+ ZIX_CONSTEXPR uint32_t c2 = 0x1B873593U;
assert((uintptr_t)buf % sizeof(uint32_t) == 0U);
assert(len % sizeof(uint32_t) == 0U);
@@ -203,7 +206,7 @@ zix_digest32_aligned(const uint32_t seed,
// Native word size wrapper
-size_t
+ZIX_NONBLOCKING size_t
zix_digest(const size_t seed, const void* const buf, const size_t len)
{
#if UINTPTR_MAX >= UINT64_MAX
@@ -213,7 +216,7 @@ zix_digest(const size_t seed, const void* const buf, const size_t len)
#endif
}
-size_t
+ZIX_NONBLOCKING size_t
zix_digest_aligned(const size_t seed, const void* const buf, const size_t len)
{
#if UINTPTR_MAX >= UINT64_MAX
diff --git a/src/errno_status.c b/src/errno_status.c
index 887158f..e2e0071 100644
--- a/src/errno_status.c
+++ b/src/errno_status.c
@@ -1,53 +1,63 @@
-// Copyright 2014-2022 David Robillard <d@drobilla.net>
+// Copyright 2014-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#include "errno_status.h"
-#include "zix/status.h"
+#include <zix/attributes.h>
+#include <zix/status.h>
+#include <zix/warnings.h>
#include <errno.h>
#include <stddef.h>
-ZixStatus
+#ifdef ENOTSUP
+# define N_ERRNO_MAPPINGS 14U
+#else
+# define N_ERRNO_MAPPINGS 13U
+#endif
+
+typedef struct {
+ int code;
+ ZixStatus status;
+} ErrnoMapping;
+
+static const ErrnoMapping errno_map[N_ERRNO_MAPPINGS] = {
+ {0, ZIX_STATUS_SUCCESS},
+ {EACCES, ZIX_STATUS_BAD_PERMS},
+ {EAGAIN, ZIX_STATUS_UNAVAILABLE},
+ {EEXIST, ZIX_STATUS_EXISTS},
+ {EINVAL, ZIX_STATUS_BAD_ARG},
+ {EMLINK, ZIX_STATUS_MAX_LINKS},
+ {ENOENT, ZIX_STATUS_NOT_FOUND},
+ {ENOMEM, ZIX_STATUS_NO_MEM},
+ {ENOSPC, ZIX_STATUS_NO_SPACE},
+ {ENOSYS, ZIX_STATUS_NOT_SUPPORTED},
+ {EPERM, ZIX_STATUS_BAD_PERMS},
+ {ETIMEDOUT, ZIX_STATUS_TIMEOUT},
+#ifdef ENOTSUP
+ {ENOTSUP, ZIX_STATUS_NOT_SUPPORTED},
+#endif
+ {0, ZIX_STATUS_ERROR}, // Fallback mapping
+};
+
+ZIX_REALTIME ZixStatus
zix_errno_status_if(const int r)
{
+ ZIX_DISABLE_EFFECT_WARNINGS // errno is sometimes a sneaky function call
+
return r ? zix_errno_status(errno) : ZIX_STATUS_SUCCESS;
+
+ ZIX_RESTORE_WARNINGS
}
-ZixStatus
+ZIX_REALTIME ZixStatus
zix_errno_status(const int e)
{
- typedef struct {
- int code;
- ZixStatus status;
- } Mapping;
-
- static const Mapping map[] = {
- {0, ZIX_STATUS_SUCCESS},
- {EACCES, ZIX_STATUS_BAD_PERMS},
- {EAGAIN, ZIX_STATUS_UNAVAILABLE},
- {EEXIST, ZIX_STATUS_EXISTS},
- {EINVAL, ZIX_STATUS_BAD_ARG},
- {EMLINK, ZIX_STATUS_MAX_LINKS},
- {ENOENT, ZIX_STATUS_NOT_FOUND},
- {ENOMEM, ZIX_STATUS_NO_MEM},
- {ENOSPC, ZIX_STATUS_NO_SPACE},
- {ENOSYS, ZIX_STATUS_NOT_SUPPORTED},
- {EPERM, ZIX_STATUS_BAD_PERMS},
- {ETIMEDOUT, ZIX_STATUS_TIMEOUT},
-#ifdef ENOTSUP
- {ENOTSUP, ZIX_STATUS_NOT_SUPPORTED},
-#endif
- {0, ZIX_STATUS_ERROR}, // Fallback mapping
- };
-
- static const size_t n_mappings = sizeof(map) / sizeof(Mapping) - 1U;
-
// Find the index of the matching mapping (or leave it at the fallback entry)
size_t m = 0;
- while (m < n_mappings && map[m].code != e) {
+ while (m < N_ERRNO_MAPPINGS && errno_map[m].code != e) {
++m;
}
- return map[m].status;
+ return errno_map[m].status;
}
diff --git a/src/errno_status.h b/src/errno_status.h
index 2f81e4e..ad599c3 100644
--- a/src/errno_status.h
+++ b/src/errno_status.h
@@ -1,20 +1,18 @@
-// Copyright 2022 David Robillard <d@drobilla.net>
+// Copyright 2022-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#ifndef ZIX_ERRNO_STATUS_H
#define ZIX_ERRNO_STATUS_H
-#include "zix/attributes.h"
-#include "zix/status.h"
+#include <zix/attributes.h>
+#include <zix/status.h>
/// Return an errno value converted to a status code
-ZIX_CONST_FUNC
-ZixStatus
+ZIX_CONST_FUNC ZIX_REALTIME ZixStatus
zix_errno_status(int e);
/// Return success if `r` is non-zero, or `errno` as a status code otherwise
-ZIX_PURE_FUNC
-ZixStatus
+ZIX_PURE_FUNC ZIX_REALTIME ZixStatus
zix_errno_status_if(int r);
#endif // ZIX_ERRNO_STATUS_H
diff --git a/src/filesystem.c b/src/filesystem.c
index 61487e9..2889039 100644
--- a/src/filesystem.c
+++ b/src/filesystem.c
@@ -1,28 +1,20 @@
-// Copyright 2007-2022 David Robillard <d@drobilla.net>
+// Copyright 2007-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
-#include "zix/filesystem.h"
+#include <zix/filesystem.h>
#include "path_iter.h"
#include "system.h"
-#include "zix/allocator.h"
-#include "zix/status.h"
-
-#ifdef _WIN32
-# include <direct.h>
-# include <io.h>
-#else
-# include <unistd.h>
-#endif
+#include <zix/allocator.h>
+#include <zix/status.h>
#include <fcntl.h>
#include <sys/stat.h>
+#include <sys/types.h>
#include <errno.h>
#include <stdbool.h>
-#include <stdint.h>
-#include <stdio.h>
#include <string.h>
ZixStatus
@@ -36,36 +28,37 @@ zix_create_directories(ZixAllocator* const allocator,
// Allocate a working copy of the path to chop along the way
const size_t path_len = strlen(dir_path);
char* const path = (char*)zix_malloc(allocator, path_len + 1U);
- ZixStatus st = path ? ZIX_STATUS_SUCCESS : ZIX_STATUS_NO_MEM;
- if (path) {
- // Copy directory path as prefix
- memcpy(path, dir_path, path_len + 1U);
-
- // Start at the root directory (past any name)
- ZixPathIter p = zix_path_begin(path);
- while (p.state < ZIX_PATH_FILE_NAME) {
- p = zix_path_next(path, p);
- }
+ if (!path) {
+ return ZIX_STATUS_NO_MEM;
+ }
- // Create each directory down the path
- while (p.state != ZIX_PATH_END) {
- char* const end = &path[p.range.end];
- const char old_last = *end;
+ // Copy directory path as prefix
+ memcpy(path, dir_path, path_len + 1U);
- *end = '\0';
- if (zix_file_type(path) != ZIX_FILE_TYPE_DIRECTORY) {
- if ((st = zix_create_directory(path))) {
- break;
- }
- }
+ // Start at the root directory (past any name)
+ ZixPathIter p = zix_path_begin(path);
+ while (p.state < ZIX_PATH_FILE_NAME) {
+ p = zix_path_next(path, p);
+ }
+
+ // Create each directory down the path
+ ZixStatus st = ZIX_STATUS_SUCCESS;
+ while (p.state != ZIX_PATH_END) {
+ char* const end = &path[p.range.end];
+ const char old_last = *end;
- *end = old_last;
- p = zix_path_next(path, p);
+ *end = '\0';
+ if (zix_file_type(path) != ZIX_FILE_TYPE_DIRECTORY) {
+ if ((st = zix_create_directory(path))) {
+ break;
+ }
}
- zix_free(allocator, path);
+ *end = old_last;
+ p = zix_path_next(path, p);
}
+ zix_free(allocator, path);
return st;
}
@@ -76,6 +69,21 @@ zix_file_size(const char* const path)
return stat(path, &sb) ? (off_t)0 : sb.st_size;
}
+// Wrapper for read() that transparently deals with short reads
+static ssize_t
+full_read(const int fd, void* const buf, const size_t count)
+{
+ size_t n = 0;
+ ssize_t r = 0;
+
+ while (n < count &&
+ (r = zix_system_read(fd, (char*)buf + n, count - n)) > 0) {
+ n += (size_t)r;
+ }
+
+ return (ssize_t)n;
+}
+
bool
zix_file_equals(ZixAllocator* const allocator,
const char* const path_a,
@@ -88,8 +96,8 @@ zix_file_equals(ZixAllocator* const allocator,
errno = 0;
// Open files and get file information
- const int fd_a = zix_system_open_fd(path_a, O_RDONLY, 0);
- const int fd_b = zix_system_open_fd(path_b, O_RDONLY, 0);
+ const int fd_a = zix_system_open(path_a, O_RDONLY, 0);
+ const int fd_b = zix_system_open(path_b, O_RDONLY, 0);
struct stat stat_a;
struct stat stat_b;
if (fd_a < 0 || fd_b < 0 || fstat(fd_a, &stat_a) || fstat(fd_b, &stat_b)) {
@@ -98,28 +106,33 @@ zix_file_equals(ZixAllocator* const allocator,
}
bool match = false;
- if (stat_a.st_dev == stat_b.st_dev && stat_a.st_ino && stat_b.st_ino &&
+ if (stat_a.st_dev == stat_b.st_dev && stat_a.st_ino &&
stat_a.st_ino == stat_b.st_ino) {
match = true; // Fast path: paths refer to the same file
} else if (stat_a.st_size == stat_b.st_size) {
// Slow path: files have equal size, compare contents
- const uint32_t size = zix_system_page_size();
- void* const page_a = zix_aligned_alloc(allocator, size, size);
- void* const page_b = zix_aligned_alloc(allocator, size, size);
-
- if (page_a && page_b) {
- match = true;
- for (ZixSystemCountReturn n = 0; (n = read(fd_a, page_a, size)) > 0;) {
- if (read(fd_b, page_b, size) != n ||
- !!memcmp(page_a, page_b, (size_t)n)) {
- match = false;
- break;
- }
+
+ // Allocate two blocks in a single buffer (to simplify error handling)
+ const uint32_t align = zix_system_page_size();
+ const uint32_t size = zix_system_max_block_size(&stat_a, &stat_b, align);
+ BlockBuffer blocks = zix_system_new_block(allocator, align, 2U * size);
+ void* const data = blocks.buffer ? blocks.buffer : blocks.fallback;
+
+ // Compare files a block at a time
+ const uint32_t block_size = blocks.size / 2U;
+ void* const block_a = data;
+ void* const block_b = (void*)((char*)data + block_size);
+ match = true;
+ for (ssize_t n = 0; n < stat_a.st_size && match;) {
+ const ssize_t r = zix_system_read(fd_a, block_a, block_size);
+ if (r <= 0 || full_read(fd_b, block_b, (uint32_t)r) != r ||
+ !!memcmp(block_a, block_b, (size_t)r)) {
+ match = false;
}
+ n += r;
}
- zix_aligned_free(allocator, page_b);
- zix_aligned_free(allocator, page_a);
+ zix_system_free_block(allocator, blocks);
}
return !zix_system_close_fds(fd_b, fd_a) && match;
diff --git a/src/hash.c b/src/hash.c
index 7576eba..ed5b0a4 100644
--- a/src/hash.c
+++ b/src/hash.c
@@ -1,7 +1,13 @@
-// Copyright 2011-2021 David Robillard <d@drobilla.net>
+// Copyright 2011-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
-#include "zix/hash.h"
+#include <zix/hash.h>
+
+#include "qualifiers.h"
+
+#include <zix/allocator.h>
+#include <zix/attributes.h>
+#include <zix/status.h>
#include <assert.h>
#include <stdbool.h>
@@ -22,8 +28,8 @@ struct ZixHashImpl {
ZixHashEntry* entries; ///< Pointer to dynamically allocated table
};
-static const size_t min_n_entries = 4U;
-static const size_t tombstone = 0xDEADU;
+static ZIX_CONSTEXPR size_t min_n_entries = 4U;
+static ZIX_CONSTEXPR size_t tombstone = 0xDEADU;
ZixHash*
zix_hash_new(ZixAllocator* const allocator,
@@ -68,21 +74,21 @@ zix_hash_free(ZixHash* const hash)
}
}
-ZixHashIter
+ZIX_NONBLOCKING ZixHashIter
zix_hash_begin(const ZixHash* const hash)
{
assert(hash);
return hash->entries[0U].value ? 0U : zix_hash_next(hash, 0U);
}
-ZixHashIter
+ZIX_REALTIME ZixHashIter
zix_hash_end(const ZixHash* const hash)
{
assert(hash);
return hash->n_entries;
}
-ZixHashRecord*
+ZIX_REALTIME ZixHashRecord*
zix_hash_get(const ZixHash* hash, const ZixHashIter i)
{
assert(hash);
@@ -91,7 +97,7 @@ zix_hash_get(const ZixHash* hash, const ZixHashIter i)
return hash->entries[i].value;
}
-ZixHashIter
+ZIX_NONBLOCKING ZixHashIter
zix_hash_next(const ZixHash* const hash, ZixHashIter i)
{
assert(hash);
@@ -102,7 +108,7 @@ zix_hash_next(const ZixHash* const hash, ZixHashIter i)
return i;
}
-size_t
+ZIX_REALTIME size_t
zix_hash_size(const ZixHash* const hash)
{
assert(hash);
@@ -300,7 +306,7 @@ zix_hash_plan_insert(const ZixHash* const hash, const ZixHashKey* const key)
hash, hash->hash_func(key), hash->equal_func, key);
}
-ZixHashRecord*
+ZIX_REALTIME ZixHashRecord*
zix_hash_record_at(const ZixHash* const hash, const ZixHashInsertPlan position)
{
assert(hash);
@@ -327,7 +333,7 @@ zix_hash_insert_at(ZixHash* const hash,
entry->value = record;
// Update size and rehash if we exceeded the maximum load
- const size_t max_load = hash->n_entries / 2U + hash->n_entries / 8U;
+ const size_t max_load = (hash->n_entries / 2U) + (hash->n_entries / 8U);
const size_t new_count = hash->count + 1U;
if (new_count >= max_load) {
const ZixStatus st = grow(hash);
diff --git a/src/path.c b/src/path.c
index 404557a..8abaac6 100644
--- a/src/path.c
+++ b/src/path.c
@@ -1,19 +1,22 @@
-// Copyright 2007-2022 David Robillard <d@drobilla.net>
+// Copyright 2007-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
-#include "zix/path.h"
+#include <zix/path.h>
#include "index_range.h"
#include "path_iter.h"
+#include "qualifiers.h"
-#include "zix/string_view.h"
+#include <zix/allocator.h>
+#include <zix/attributes.h>
+#include <zix/string_view.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
-static const ZixIndexRange two_range = {0U, 2U};
-static const ZixIndexRange one_range = {0U, 1U};
+static ZIX_CONSTEXPR ZixIndexRange two_range = {0U, 2U};
+static ZIX_CONSTEXPR ZixIndexRange one_range = {0U, 1U};
#ifdef _WIN32
@@ -31,7 +34,7 @@ is_any_sep(const char c)
return c == '/' || c == ':' || c == '\\';
}
-static bool
+static inline bool
is_root_name_char(const char c)
{
return c && !is_dir_sep(c);
@@ -87,13 +90,25 @@ zix_path_root_name_range(const char* const path)
#endif
+static inline char
+normal_path_char(const char c)
+{
+ return (char)(is_dir_sep(c) ? ZIX_DIR_SEP : c);
+}
+
+static ZixStringView
+string_view(const char* const path)
+{
+ const ZixStringView view = {path, strlen(path)};
+ return view;
+}
+
typedef struct {
ZixIndexRange name;
ZixIndexRange dir;
} ZixRootSlices;
-ZIX_PURE_FUNC
-static ZixRootSlices
+ZIX_PURE_FUNC static ZixRootSlices
zix_path_root_slices(const char* const path)
{
// A root name not trailed by a separator has no root directory
@@ -128,8 +143,7 @@ zix_string_ranges_equal(const char* const lhs,
!strncmp(lhs + lhs_range.begin, rhs + rhs_range.begin, lhs_len));
}
-ZIX_PURE_FUNC
-static ZixIndexRange
+ZIX_PURE_FUNC static ZixIndexRange
zix_path_root_path_range(const char* const path)
{
const ZixRootSlices root = zix_path_root_slices(path);
@@ -140,8 +154,7 @@ zix_path_root_path_range(const char* const path)
: zix_make_range(root.name.begin, root.name.end + dir_len);
}
-ZIX_PURE_FUNC
-static ZixIndexRange
+ZIX_PURE_FUNC static ZixIndexRange
zix_path_parent_path_range(const ZixStringView path)
{
if (!path.length) {
@@ -178,8 +191,7 @@ zix_path_parent_path_range(const ZixStringView path)
return zix_make_range(root.begin, root.begin + l + 1U - p);
}
-ZIX_PURE_FUNC
-static ZixIndexRange
+ZIX_PURE_FUNC static ZixIndexRange
zix_path_filename_range(const ZixStringView path)
{
if (!path.length) {
@@ -201,8 +213,7 @@ zix_path_filename_range(const ZixStringView path)
return zix_make_range(f, path.length);
}
-ZIX_PURE_FUNC
-static ZixIndexRange
+ZIX_PURE_FUNC static ZixIndexRange
zix_path_stem_range(const ZixStringView path)
{
const ZixIndexRange name = zix_path_filename_range(path);
@@ -221,8 +232,7 @@ zix_path_stem_range(const ZixStringView path)
return zix_is_empty_range(stem) ? name : stem;
}
-ZIX_PURE_FUNC
-static ZixIndexRange
+ZIX_PURE_FUNC static ZixIndexRange
zix_path_extension_range(const ZixStringView path)
{
const ZixIndexRange stem = zix_path_stem_range(path);
@@ -288,12 +298,12 @@ zix_path_join(ZixAllocator* const allocator,
char*
zix_path_preferred(ZixAllocator* const allocator, const char* const path)
{
- const ZixStringView path_view = zix_string(path);
+ const ZixStringView path_view = string_view(path);
char* const result = (char*)zix_calloc(allocator, path_view.length + 1U, 1U);
if (result) {
for (size_t i = 0U; i < path_view.length; ++i) {
- result[i] = (char)(is_dir_sep(path[i]) ? ZIX_DIR_SEP : path[i]);
+ result[i] = normal_path_char(path[i]);
}
}
@@ -303,7 +313,7 @@ zix_path_preferred(ZixAllocator* const allocator, const char* const path)
char*
zix_path_lexically_normal(ZixAllocator* const allocator, const char* const path)
{
- static const char sep = ZIX_DIR_SEP;
+ ZIX_CONSTEXPR char sep = ZIX_DIR_SEP;
/* Loosely following the normalization algorithm from
<https://en.cppreference.com/w/cpp/filesystem/path>, but in such a way
@@ -316,24 +326,27 @@ zix_path_lexically_normal(ZixAllocator* const allocator, const char* const path)
}
// Allocate a result buffer, with space for one additional character
- const ZixStringView path_view = zix_string(path);
+ const ZixStringView path_view = string_view(path);
const size_t path_len = path_view.length;
char* const result = (char*)zix_calloc(allocator, path_len + 2U, 1);
- size_t r = 0U;
+ if (!result) {
+ return NULL;
+ }
// Copy root, normalizing separators as we go
const ZixIndexRange root = zix_path_root_path_range(path);
const size_t root_len = root.end - root.begin;
+ size_t r = 0U;
for (size_t i = 0; i < root_len; ++i) {
- result[r++] = (char)(is_dir_sep(path[i]) ? sep : path[i]);
+ result[r++] = normal_path_char(path[i]);
}
// Copy path, removing dot entries and collapsing separators as we go
for (size_t i = root.end; i < path_len; ++i) {
if (is_dir_sep(path[i])) {
- if ((i >= root.end) && ((r == root.end + 1U && result[r - 1] == '.') ||
- (r >= root.end + 2U && result[r - 2] == sep &&
- result[r - 1] == '.'))) {
+ if (((r == root.end + 1U && result[r - 1] == '.') ||
+ (r >= root.end + 2U && result[r - 2] == sep &&
+ result[r - 1] == '.'))) {
// Remove dot entry and any immediately following separators
result[--r] = '\0';
@@ -509,8 +522,7 @@ zix_path_lexically_relative(ZixAllocator* const allocator,
// Find the first mismatching element in the paths (or the end)
ZixPathIter a = zix_path_begin(path);
ZixPathIter b = zix_path_begin(base);
- while (a.state != ZIX_PATH_END && b.state != ZIX_PATH_END &&
- a.state == b.state &&
+ while (a.state != ZIX_PATH_END && a.state == b.state &&
zix_string_ranges_equal(path, a.range, base, b.range)) {
a = zix_path_next(path, a);
b = zix_path_next(base, b);
@@ -552,6 +564,9 @@ zix_path_lexically_relative(ZixAllocator* const allocator,
const size_t path_len = strlen(path);
const size_t rel_len = (n_up * 3U) + path_len - a.range.begin;
char* const rel = (char*)zix_calloc(allocator, rel_len + 1U, 1U);
+ if (!rel) {
+ return NULL;
+ }
// Write leading up-references
size_t offset = 0U;
@@ -583,7 +598,7 @@ range_string_view(const char* const path, const ZixIndexRange range)
#ifdef _WIN32
-ZixStringView
+ZIX_NONBLOCKING ZixStringView
zix_path_root_name(const char* const path)
{
return range_string_view(path, zix_path_root_name_range(path));
@@ -591,7 +606,7 @@ zix_path_root_name(const char* const path)
#else
-ZixStringView
+ZIX_NONBLOCKING ZixStringView
zix_path_root_name(const char* const path)
{
(void)path;
@@ -600,103 +615,102 @@ zix_path_root_name(const char* const path)
#endif
-ZixStringView
+ZIX_NONBLOCKING ZixStringView
zix_path_root_directory(const char* const path)
{
return range_string_view(path, zix_path_root_slices(path).dir);
}
-ZixStringView
+ZIX_NONBLOCKING ZixStringView
zix_path_root_path(const char* const path)
{
return range_string_view(path, zix_path_root_path_range(path));
}
-ZixStringView
+ZIX_NONBLOCKING ZixStringView
zix_path_relative_path(const char* const path)
{
- const ZixStringView path_view = zix_string(path);
- const size_t path_len = path_view.length;
- const ZixIndexRange root = zix_path_root_path_range(path);
+ const ZixStringView view = string_view(path);
+ const ZixIndexRange root = zix_path_root_path_range(path);
- return range_string_view(path, zix_make_range(root.end, path_len));
+ return range_string_view(path, zix_make_range(root.end, view.length));
}
-ZixStringView
+ZIX_NONBLOCKING ZixStringView
zix_path_parent_path(const char* const path)
{
- return range_string_view(path, zix_path_parent_path_range(zix_string(path)));
+ return range_string_view(path, zix_path_parent_path_range(string_view(path)));
}
-ZixStringView
+ZIX_NONBLOCKING ZixStringView
zix_path_filename(const char* const path)
{
- return range_string_view(path, zix_path_filename_range(zix_string(path)));
+ return range_string_view(path, zix_path_filename_range(string_view(path)));
}
-ZixStringView
+ZIX_NONBLOCKING ZixStringView
zix_path_stem(const char* const path)
{
- return range_string_view(path, zix_path_stem_range(zix_string(path)));
+ return range_string_view(path, zix_path_stem_range(string_view(path)));
}
-ZixStringView
+ZIX_NONBLOCKING ZixStringView
zix_path_extension(const char* const path)
{
- return range_string_view(path, zix_path_extension_range(zix_string(path)));
+ return range_string_view(path, zix_path_extension_range(string_view(path)));
}
// Queries
-bool
+ZIX_NONBLOCKING bool
zix_path_has_root_path(const char* const path)
{
return !zix_is_empty_range(zix_path_root_path_range(path));
}
-bool
+ZIX_NONBLOCKING bool
zix_path_has_root_name(const char* const path)
{
return !zix_is_empty_range(zix_path_root_name_range(path));
}
-bool
+ZIX_NONBLOCKING bool
zix_path_has_root_directory(const char* const path)
{
return !zix_is_empty_range(zix_path_root_slices(path).dir);
}
-bool
+ZIX_NONBLOCKING bool
zix_path_has_relative_path(const char* const path)
{
return path && path[zix_path_root_path_range(path).end];
}
-bool
+ZIX_NONBLOCKING bool
zix_path_has_parent_path(const char* const path)
{
return !zix_is_empty_range(zix_path_parent_path_range(zix_string(path)));
}
-bool
+ZIX_NONBLOCKING bool
zix_path_has_filename(const char* const path)
{
return !zix_is_empty_range(zix_path_filename_range(zix_string(path)));
}
-bool
+ZIX_NONBLOCKING bool
zix_path_has_stem(const char* const path)
{
return !zix_is_empty_range(zix_path_stem_range(zix_string(path)));
}
-bool
+ZIX_NONBLOCKING bool
zix_path_has_extension(const char* const path)
{
return !zix_is_empty_range(zix_path_extension_range(zix_string(path)));
}
-bool
+ZIX_NONBLOCKING bool
zix_path_is_absolute(const char* const path)
{
#ifdef _WIN32
@@ -710,7 +724,7 @@ zix_path_is_absolute(const char* const path)
#endif
}
-bool
+ZIX_NONBLOCKING bool
zix_path_is_relative(const char* const path)
{
return !zix_path_is_absolute(path);
diff --git a/src/path_iter.h b/src/path_iter.h
index b3b23e8..43eb8c9 100644
--- a/src/path_iter.h
+++ b/src/path_iter.h
@@ -6,7 +6,7 @@
#include "index_range.h"
-#include "zix/attributes.h"
+#include <zix/attributes.h>
typedef enum {
ZIX_PATH_ROOT_NAME,
@@ -20,12 +20,10 @@ typedef struct {
ZixPathIterState state;
} ZixPathIter;
-ZIX_PURE_FUNC
-ZixPathIter
+ZIX_PURE_FUNC ZixPathIter
zix_path_begin(const char* ZIX_NULLABLE path);
-ZIX_PURE_FUNC
-ZixPathIter
+ZIX_PURE_FUNC ZixPathIter
zix_path_next(const char* ZIX_NONNULL path, ZixPathIter iter);
#endif // ZIX_PATH_ITER_H
diff --git a/src/posix/environment_posix.c b/src/posix/environment_posix.c
new file mode 100644
index 0000000..8447753
--- /dev/null
+++ b/src/posix/environment_posix.c
@@ -0,0 +1,134 @@
+// Copyright 2012-2024 David Robillard <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#include <zix/environment.h>
+
+#include <zix/allocator.h>
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+static bool
+is_path_delim(const char c)
+{
+ return c == '/' || c == ':' || c == '\0';
+}
+
+static bool
+is_var_name_char(const char c)
+{
+ return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c == '_');
+}
+
+// Append suffix to dst, update dst_len, and return the realloc'd result
+static char*
+append_str(ZixAllocator* const allocator,
+ size_t* const dst_len,
+ char* const dst,
+ const size_t suffix_len,
+ const char* const suffix)
+{
+ const size_t out_len = *dst_len + suffix_len;
+ char* const out = (char*)zix_realloc(allocator, dst, out_len + 1U);
+ if (!out) {
+ zix_free(allocator, dst);
+ return NULL;
+ }
+
+ memcpy(out + *dst_len, suffix, suffix_len);
+ out[(*dst_len += suffix_len)] = '\0';
+ return out;
+}
+
+// Append the value of an environment variable to dst if it's set
+static char*
+append_var(ZixAllocator* const allocator,
+ size_t* const dst_len,
+ char* const dst,
+ const size_t ref_len,
+ const char* const ref)
+{
+ // Get value from environment
+ const char* const val = getenv(ref + 1U); // NOLINT(concurrency-mt-unsafe)
+ if (val) {
+ return append_str(allocator, dst_len, dst, strlen(val), val);
+ }
+
+ // No value found, append variable reference as-is
+ return append_str(allocator, dst_len, dst, ref_len, ref);
+}
+
+// Copy a variable reference string like $NAME to a null-terminated string
+static char*
+set_ref(ZixAllocator* const allocator,
+ char** const buffer,
+ const size_t ref_len,
+ const char* const ref)
+{
+ char* const out = (char*)zix_realloc(allocator, *buffer, ref_len + 1U);
+
+ if (out) {
+ memcpy(out, ref, ref_len);
+ out[ref_len] = '\0';
+ } else {
+ zix_free(allocator, *buffer);
+ }
+
+ return out;
+}
+
+char*
+zix_expand_environment_strings(ZixAllocator* const allocator,
+ const char* const string)
+{
+ char* ref = NULL; // Reference string like $NAME
+ char* out = NULL; // Expanded result
+ size_t len = 0U; // Length of expanded result
+
+ size_t start = 0U; // Start of current chunk to copy
+ for (size_t s = 0U; string[s];) {
+ const char c = string[s];
+ if (c == '$' && is_var_name_char(string[s + 1U])) {
+ // Hit $ (variable reference like $VAR_NAME)
+ for (size_t t = 1U;; ++t) {
+ if (!is_var_name_char(string[s + t])) {
+ const char* const prefix = string + start;
+ const size_t prefix_len = s - start;
+ if ((prefix_len &&
+ !(out = append_str(allocator, &len, out, prefix_len, prefix))) ||
+ !(ref = set_ref(allocator, &ref, t, string + s)) ||
+ !(out = append_var(allocator, &len, out, t, ref))) {
+ zix_free(allocator, ref);
+ zix_free(allocator, out);
+ return NULL;
+ }
+ start = s = t;
+ break;
+ }
+ }
+ } else if (c == '~' && is_path_delim(string[s + 1U])) {
+ // Hit ~ before delimiter or end of string (home directory reference)
+ const char* const prefix = string + start;
+ const size_t prefix_len = s - start;
+ if ((prefix_len &&
+ !(out = append_str(allocator, &len, out, prefix_len, prefix))) ||
+ !(out = append_var(allocator, &len, out, 5U, "$HOME"))) {
+ zix_free(allocator, ref);
+ return NULL;
+ }
+ start = ++s;
+ } else {
+ ++s;
+ }
+ }
+
+ if (string[start]) {
+ const char* const tail = string + start;
+ const size_t tail_len = strlen(tail);
+ out = append_str(allocator, &len, out, tail_len, tail);
+ }
+
+ zix_free(allocator, ref);
+ return out;
+}
diff --git a/src/posix/filesystem_posix.c b/src/posix/filesystem_posix.c
index be13995..4b4f0c5 100644
--- a/src/posix/filesystem_posix.c
+++ b/src/posix/filesystem_posix.c
@@ -1,15 +1,16 @@
-// Copyright 2007-2022 David Robillard <d@drobilla.net>
+// Copyright 2007-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
-#include "zix/filesystem.h"
+#include <zix/filesystem.h>
#include "../errno_status.h"
+#include "../qualifiers.h"
#include "../system.h"
#include "../zix_config.h"
-#include "zix/allocator.h"
-#include "zix/attributes.h"
-#include "zix/status.h"
+#include <zix/allocator.h>
+#include <zix/attributes.h>
+#include <zix/status.h>
#if USE_FLOCK && USE_FILENO
# include <sys/file.h>
@@ -26,36 +27,23 @@
#include <dirent.h>
#include <fcntl.h>
+#include <stdint.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <stdbool.h>
-#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#ifndef MAX
-# define MAX(a, b) (((a) > (b)) ? (a) : (b))
-#endif
-
static inline ZixStatus
zix_posix_status(const int rc)
{
return rc ? zix_errno_status(errno) : ZIX_STATUS_SUCCESS;
}
-static uint32_t
-zix_get_block_size(const struct stat* const s1, const struct stat* const s2)
-{
- const blksize_t b1 = s1->st_blksize;
- const blksize_t b2 = s2->st_blksize;
-
- return (b1 > 0 && b2 > 0) ? (uint32_t)MAX(b1, b2) : 4096U;
-}
-
static ZixStatus
finish_copy(const int dst_fd, const int src_fd, const ZixStatus status)
{
@@ -158,14 +146,16 @@ copy_blocks(const int src_fd,
void* const block,
const size_t block_size)
{
- ssize_t n_read = 0;
- while ((n_read = read(src_fd, block, block_size)) > 0) {
+ ZixStatus st = ZIX_STATUS_SUCCESS;
+ ssize_t n_read = 0;
+
+ while (!st && (n_read = read(src_fd, block, block_size)) > 0) {
if (write(dst_fd, block, (size_t)n_read) != n_read) {
- return zix_errno_status(errno);
+ st = zix_errno_status(errno);
}
}
- return ZIX_STATUS_SUCCESS;
+ return st;
}
ZixStatus
@@ -175,7 +165,8 @@ zix_copy_file(ZixAllocator* const allocator,
const ZixCopyOptions options)
{
ZixStatus st = ZIX_STATUS_SUCCESS;
- (void)st;
+
+ errno = 0;
#if USE_CLONEFILE
// Try to copy via the kernel on MacOS to take advantage of CoW
@@ -186,7 +177,7 @@ zix_copy_file(ZixAllocator* const allocator,
#endif
// Open source file and get its status
- const int src_fd = zix_system_open_fd(src, O_RDONLY, 0);
+ const int src_fd = zix_system_open(src, O_RDONLY, 0);
struct stat src_stat;
if (src_fd < 0 || fstat(src_fd, &src_stat)) {
return finish_copy(-1, src_fd, zix_errno_status(errno));
@@ -200,7 +191,7 @@ zix_copy_file(ZixAllocator* const allocator,
// Open a new destination file
const bool overwrite = (options == ZIX_COPY_OPTION_OVERWRITE_EXISTING);
const int dst_flags = O_WRONLY | O_CREAT | (overwrite ? O_TRUNC : O_EXCL);
- const int dst_fd = zix_system_open_fd(dst, dst_flags, 0644);
+ const int dst_fd = zix_system_open(dst, dst_flags, 0644);
struct stat dst_stat;
if (dst_fd < 0 || fstat(dst_fd, &dst_stat)) {
return finish_copy(dst_fd, src_fd, zix_errno_status(errno));
@@ -222,20 +213,14 @@ zix_copy_file(ZixAllocator* const allocator,
errno = 0;
- // Allocate a block for copying
- const size_t align = zix_system_page_size();
- const uint32_t block_size = zix_get_block_size(&src_stat, &dst_stat);
- void* const block = zix_aligned_alloc(allocator, align, block_size);
-
- // Fall back to using a small stack buffer if allocation is unavailable
- char stack_buf[512];
- void* const buffer = block ? block : stack_buf;
- const size_t buffer_size = block ? block_size : sizeof(stack_buf);
-
- // Copy file content one buffer at a time
- st = copy_blocks(src_fd, dst_fd, buffer, buffer_size);
+ // Allocate a block and copy file content one block at a time
+ const uint32_t align = zix_system_page_size();
+ const uint32_t size = zix_system_max_block_size(&src_stat, &dst_stat, align);
+ BlockBuffer block = zix_system_new_block(allocator, align, size);
+ void* const data = block.buffer ? block.buffer : block.fallback;
+ st = copy_blocks(src_fd, dst_fd, data, block.size);
- zix_aligned_free(NULL, block);
+ zix_system_free_block(allocator, block);
return finish_copy(dst_fd, src_fd, st);
}
@@ -300,11 +285,9 @@ zix_remove(const char* const path)
}
void
-zix_dir_for_each(const char* const path,
- void* const data,
- void (*const f)(const char* path,
- const char* name,
- void* data))
+zix_dir_for_each(const char* const path,
+ void* const data,
+ const ZixDirEntryVisitFunc f)
{
DIR* dir = opendir(path);
if (dir) {
@@ -343,6 +326,9 @@ zix_canonical_path(ZixAllocator* const allocator, const char* const path)
}
zix_free(allocator, buffer);
+
+#else
+ (void)allocator;
#endif
return NULL;
@@ -351,7 +337,7 @@ zix_canonical_path(ZixAllocator* const allocator, const char* const path)
ZixStatus
zix_file_lock(FILE* const file, const ZixFileLockMode mode)
{
-#if !defined(__EMSCRIPTEN__) && USE_FLOCK && USE_FILENO
+#if USE_FLOCK && USE_FILENO
return zix_posix_status(
flock(fileno(file),
(mode == ZIX_FILE_LOCK_BLOCK) ? LOCK_EX : (LOCK_EX | LOCK_NB)));
@@ -366,7 +352,7 @@ zix_file_lock(FILE* const file, const ZixFileLockMode mode)
ZixStatus
zix_file_unlock(FILE* const file, const ZixFileLockMode mode)
{
-#if !defined(__EMSCRIPTEN__) && USE_FLOCK && USE_FILENO
+#if USE_FLOCK && USE_FILENO
return zix_posix_status(
flock(fileno(file),
(mode == ZIX_FILE_LOCK_BLOCK) ? LOCK_UN : (LOCK_UN | LOCK_NB)));
@@ -378,8 +364,7 @@ zix_file_unlock(FILE* const file, const ZixFileLockMode mode)
#endif
}
-ZIX_CONST_FUNC
-static ZixFileType
+ZIX_CONST_FUNC static ZixFileType
stat_file_type(const struct stat* sb)
{
typedef struct {
@@ -387,7 +372,7 @@ stat_file_type(const struct stat* sb)
ZixFileType type;
} Mapping;
- static const Mapping map[] = {
+ ZIX_CONSTEXPR Mapping map[] = {
{S_IFREG, ZIX_FILE_TYPE_REGULAR},
{S_IFDIR, ZIX_FILE_TYPE_DIRECTORY},
{S_IFLNK, ZIX_FILE_TYPE_SYMLINK},
@@ -452,6 +437,7 @@ zix_current_path(ZixAllocator* const allocator)
return current;
#else
+ (void)allocator;
return NULL;
#endif
diff --git a/src/posix/sem_posix.c b/src/posix/sem_posix.c
index 452830d..0ed733e 100644
--- a/src/posix/sem_posix.c
+++ b/src/posix/sem_posix.c
@@ -1,12 +1,14 @@
-// Copyright 2012-2022 David Robillard <d@drobilla.net>
+// Copyright 2012-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
-#include "zix/sem.h"
+#include <zix/sem.h>
#include "../errno_status.h"
#include "../zix_config.h"
-#include "zix/status.h"
+#include <zix/attributes.h>
+#include <zix/status.h>
+#include <zix/warnings.h>
#include <semaphore.h>
@@ -26,10 +28,12 @@ zix_sem_destroy(ZixSem* sem)
return zix_errno_status_if(sem_destroy(&sem->sem));
}
-ZixStatus
+ZIX_REALTIME ZixStatus
zix_sem_post(ZixSem* sem)
{
+ ZIX_DISABLE_EFFECT_WARNINGS
return zix_errno_status_if(sem_post(&sem->sem));
+ ZIX_RESTORE_WARNINGS
}
ZixStatus
@@ -61,6 +65,9 @@ zix_sem_timed_wait(ZixSem* sem,
{
#if !USE_CLOCK_GETTIME || !USE_SEM_TIMEDWAIT
+ (void)sem;
+ (void)seconds;
+ (void)nanoseconds;
return ZIX_STATUS_NOT_SUPPORTED;
#else
diff --git a/src/posix/system_posix.c b/src/posix/system_posix.c
index 4c37315..6857588 100644
--- a/src/posix/system_posix.c
+++ b/src/posix/system_posix.c
@@ -1,12 +1,19 @@
-// Copyright 2007-2022 David Robillard <d@drobilla.net>
+// Copyright 2007-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#include "../system.h"
#include "../zix_config.h"
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <unistd.h>
-#include <stdint.h>
+#ifndef MAX
+# define MAX(a, b) (((a) > (b)) ? (a) : (b))
+#endif
#if defined(PAGE_SIZE)
# define ZIX_DEFAULT_PAGE_SIZE PAGE_SIZE
@@ -24,3 +31,35 @@ zix_system_page_size(void)
return (uint32_t)ZIX_DEFAULT_PAGE_SIZE;
#endif
}
+
+uint32_t
+zix_system_max_block_size(const struct stat* const s1,
+ const struct stat* const s2,
+ const uint32_t fallback)
+{
+ const blksize_t size = MAX(s1->st_blksize, s2->st_blksize);
+
+ return (size > 0) ? (uint32_t)size : fallback;
+}
+
+int
+zix_system_open(const char* const path, const int flags, const mode_t mode)
+{
+#if defined(O_CLOEXEC)
+ return open(path, flags | O_CLOEXEC, mode); // NOLINT(hicpp-signed-bitwise)
+#else
+ return open(path, flags, mode);
+#endif
+}
+
+int
+zix_system_close(const int fd)
+{
+ return close(fd);
+}
+
+ssize_t
+zix_system_read(const int fd, void* const buf, const size_t count)
+{
+ return read(fd, buf, count);
+}
diff --git a/src/posix/thread_posix.c b/src/posix/thread_posix.c
index 2073448..468fe38 100644
--- a/src/posix/thread_posix.c
+++ b/src/posix/thread_posix.c
@@ -1,11 +1,11 @@
// Copyright 2012-2020 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
-#include "zix/thread.h"
+#include <zix/thread.h>
#include "../errno_status.h"
-#include "zix/status.h"
+#include <zix/status.h>
#include <pthread.h>
diff --git a/src/qualifiers.h b/src/qualifiers.h
new file mode 100644
index 0000000..3b96dd7
--- /dev/null
+++ b/src/qualifiers.h
@@ -0,0 +1,15 @@
+// Copyright 2025 David Robillard <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#ifndef ZIX_QUALIFIERS_H
+#define ZIX_QUALIFIERS_H
+
+/// A static constant expression usable at compile time
+#if ((defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) || \
+ (defined(__cplusplus) && __cplusplus >= 201103L))
+# define ZIX_CONSTEXPR constexpr
+#else
+# define ZIX_CONSTEXPR const
+#endif
+
+#endif // ZIX_QUALIFIERS_H
diff --git a/src/ring.c b/src/ring.c
index ad00f75..88ceaa6 100644
--- a/src/ring.c
+++ b/src/ring.c
@@ -1,15 +1,16 @@
-// Copyright 2011-2022 David Robillard <d@drobilla.net>
+// Copyright 2011-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
-#include "zix/ring.h"
+#include <zix/ring.h>
#include "errno_status.h"
#include "zix_config.h"
-#include "zix/allocator.h"
-#include "zix/status.h"
+#include <zix/allocator.h>
+#include <zix/attributes.h>
+#include <zix/status.h>
-#if defined(_WIN32)
+#if USE_VIRTUALLOCK
# include <windows.h>
#elif USE_MLOCK
# include <sys/mman.h>
@@ -108,7 +109,7 @@ zix_ring_free(ZixRing* const ring)
ZixStatus
zix_ring_mlock(ZixRing* const ring)
{
-#if defined(_WIN32)
+#if USE_VIRTUALLOCK
return (VirtualLock(ring, sizeof(ZixRing)) &&
VirtualLock(ring->buf, ring->size))
? ZIX_STATUS_SUCCESS
@@ -119,11 +120,12 @@ zix_ring_mlock(ZixRing* const ring)
mlock(ring->buf, ring->size));
#else
+ (void)ring;
return ZIX_STATUS_NOT_SUPPORTED;
#endif
}
-void
+ZIX_REALTIME void
zix_ring_reset(ZixRing* const ring)
{
ring->write_head = 0;
@@ -144,7 +146,7 @@ read_space_internal(const ZixRing* const ring,
return (w - r) & ring->size_mask;
}
-uint32_t
+ZIX_REALTIME uint32_t
zix_ring_read_space(const ZixRing* const ring)
{
const uint32_t w = zix_atomic_load(&ring->write_head);
@@ -160,7 +162,7 @@ write_space_internal(const ZixRing* const ring,
return (r - w - 1U) & ring->size_mask;
}
-uint32_t
+ZIX_REALTIME uint32_t
zix_ring_write_space(const ZixRing* const ring)
{
const uint32_t r = zix_atomic_load(&ring->read_head);
@@ -168,7 +170,7 @@ zix_ring_write_space(const ZixRing* const ring)
return write_space_internal(ring, r, ring->write_head);
}
-uint32_t
+ZIX_REALTIME uint32_t
zix_ring_capacity(const ZixRing* const ring)
{
return ring->size - 1U;
@@ -190,13 +192,13 @@ peek_internal(const ZixRing* const ring,
} else {
const uint32_t first_size = ring->size - r;
memcpy(dst, &ring->buf[r], first_size);
- memcpy((char*)dst + first_size, &ring->buf[0], size - first_size);
+ memcpy((char*)dst + first_size, &ring->buf[0], (size_t)size - first_size);
}
return size;
}
-uint32_t
+ZIX_REALTIME uint32_t
zix_ring_peek(ZixRing* const ring, void* const dst, const uint32_t size)
{
const uint32_t w = zix_atomic_load(&ring->write_head);
@@ -204,7 +206,7 @@ zix_ring_peek(ZixRing* const ring, void* const dst, const uint32_t size)
return peek_internal(ring, ring->read_head, w, size, dst);
}
-uint32_t
+ZIX_REALTIME uint32_t
zix_ring_read(ZixRing* const ring, void* const dst, const uint32_t size)
{
const uint32_t w = zix_atomic_load(&ring->write_head);
@@ -217,7 +219,7 @@ zix_ring_read(ZixRing* const ring, void* const dst, const uint32_t size)
return size;
}
-uint32_t
+ZIX_REALTIME uint32_t
zix_ring_skip(ZixRing* const ring, const uint32_t size)
{
const uint32_t w = zix_atomic_load(&ring->write_head);
@@ -230,7 +232,7 @@ zix_ring_skip(ZixRing* const ring, const uint32_t size)
return size;
}
-ZixRingTransaction
+ZIX_REALTIME ZixRingTransaction
zix_ring_begin_write(ZixRing* const ring)
{
const uint32_t r = zix_atomic_load(&ring->read_head);
@@ -240,7 +242,7 @@ zix_ring_begin_write(ZixRing* const ring)
return tx;
}
-ZixStatus
+ZIX_REALTIME ZixStatus
zix_ring_amend_write(ZixRing* const ring,
ZixRingTransaction* const tx,
const void* const src,
@@ -267,14 +269,14 @@ zix_ring_amend_write(ZixRing* const ring,
return ZIX_STATUS_SUCCESS;
}
-ZixStatus
+ZIX_REALTIME ZixStatus
zix_ring_commit_write(ZixRing* const ring, const ZixRingTransaction* const tx)
{
zix_atomic_store(&ring->write_head, tx->write_head);
return ZIX_STATUS_SUCCESS;
}
-uint32_t
+ZIX_REALTIME uint32_t
zix_ring_write(ZixRing* const ring, const void* src, const uint32_t size)
{
ZixRingTransaction tx = zix_ring_begin_write(ring);
diff --git a/src/status.c b/src/status.c
index e3d8b99..cea8f37 100644
--- a/src/status.c
+++ b/src/status.c
@@ -1,9 +1,10 @@
-// Copyright 2014-2022 David Robillard <d@drobilla.net>
+// Copyright 2014-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
-#include "zix/status.h"
+#include <zix/attributes.h>
+#include <zix/status.h>
-const char*
+ZIX_REALTIME const char*
zix_strerror(const ZixStatus status)
{
switch (status) {
diff --git a/src/string_view.c b/src/string_view.c
index 192f918..9838718 100644
--- a/src/string_view.c
+++ b/src/string_view.c
@@ -1,8 +1,10 @@
-// Copyright 2007-2024 David Robillard <d@drobilla.net>
+// Copyright 2007-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
-#include "zix/string_view.h"
-#include "zix/allocator.h"
+#include <zix/string_view.h>
+
+#include <zix/allocator.h>
+#include <zix/attributes.h>
#include <stdbool.h>
#include <string.h>
@@ -18,7 +20,7 @@ zix_string_view_copy(ZixAllocator* const allocator, const ZixStringView view)
return copy;
}
-bool
+ZIX_NONBLOCKING bool
zix_string_view_equals(const ZixStringView lhs, const ZixStringView rhs)
{
if (lhs.length != rhs.length) {
diff --git a/src/system.c b/src/system.c
index eab568b..c41bb0c 100644
--- a/src/system.c
+++ b/src/system.c
@@ -1,41 +1,44 @@
-// Copyright 2007-2022 David Robillard <d@drobilla.net>
+// Copyright 2007-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#include "system.h"
#include "errno_status.h"
-#include "zix/status.h"
+#include <zix/allocator.h>
+#include <zix/status.h>
-#ifdef _WIN32
-# include <io.h>
-#else
-# include <unistd.h>
-#endif
-
-#include <errno.h>
-#include <fcntl.h>
-
-int
-zix_system_open_fd(const char* const path, const int flags, const mode_t mode)
-{
-#ifdef O_CLOEXEC
- return open(path, flags | O_CLOEXEC, mode); // NOLINT(hicpp-signed-bitwise)
-#else
- return open(path, flags, mode);
-#endif
-}
+#include <stddef.h>
ZixStatus
zix_system_close_fds(const int fd1, const int fd2)
{
// Careful: we need to always close both files, but catch errno at any point
- const ZixStatus st0 = zix_errno_status(errno);
- const int r1 = fd1 >= 0 ? close(fd1) : 0;
- const ZixStatus st1 = r1 ? ZIX_STATUS_SUCCESS : zix_errno_status(errno);
- const int r2 = fd2 >= 0 ? close(fd2) : 0;
- const ZixStatus st2 = r2 ? ZIX_STATUS_SUCCESS : zix_errno_status(errno);
+ const int r1 = fd1 >= 0 ? zix_system_close(fd1) : 0;
+ const ZixStatus st1 = zix_errno_status_if(r1);
+ const int r2 = fd2 >= 0 ? zix_system_close(fd2) : 0;
+ const ZixStatus st2 = zix_errno_status_if(r2);
+
+ return st1 ? st1 : st2;
+}
+
+BlockBuffer
+zix_system_new_block(ZixAllocator* const allocator,
+ const uint32_t align,
+ const uint32_t size)
+{
+ BlockBuffer block = {size, NULL, {'\0'}};
+
+ if (!(block.buffer = zix_aligned_alloc(allocator, align, size))) {
+ block.size = ZIX_STACK_BLOCK_SIZE;
+ }
- return st0 ? st0 : st1 ? st1 : st2;
+ return block;
+}
+
+void
+zix_system_free_block(ZixAllocator* const allocator, const BlockBuffer block)
+{
+ zix_aligned_free(allocator, block.buffer);
}
diff --git a/src/system.h b/src/system.h
index ca55161..ca0ce37 100644
--- a/src/system.h
+++ b/src/system.h
@@ -1,30 +1,59 @@
-// Copyright 2022 David Robillard <d@drobilla.net>
+// Copyright 2022-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#ifndef ZIX_SYSTEM_H
#define ZIX_SYSTEM_H
-#include "zix/status.h"
+#include <zix/allocator.h>
+#include <zix/attributes.h>
+#include <zix/status.h>
+#include <stddef.h>
#include <stdint.h>
+#include <sys/stat.h> // IWYU pragma: keep
#include <sys/types.h>
-#ifdef _WIN32
-typedef int ZixSystemCountReturn;
-# ifndef __GNUC__
-typedef int mode_t;
-# endif
-#else
-typedef ssize_t ZixSystemCountReturn;
+#ifndef ZIX_STACK_BLOCK_SIZE
+# define ZIX_STACK_BLOCK_SIZE 512U
#endif
+#if defined(_WIN32) && !defined(__GNUC__)
+typedef long ssize_t;
+typedef int mode_t;
+#endif
+
+typedef struct {
+ uint32_t size; ///< Buffer size
+ void* ZIX_NULLABLE buffer; ///< Allocated buffer
+ char fallback[ZIX_STACK_BLOCK_SIZE]; ///< Stack fallback
+} BlockBuffer;
+
uint32_t
zix_system_page_size(void);
int
-zix_system_open_fd(const char* path, int flags, mode_t mode);
+zix_system_open(const char* ZIX_NONNULL path, int flags, mode_t mode);
+
+int
+zix_system_close(int fd);
ZixStatus
zix_system_close_fds(int fd1, int fd2);
+ssize_t
+zix_system_read(int fd, void* ZIX_NONNULL buf, size_t count);
+
+ZIX_PURE_FUNC uint32_t
+zix_system_max_block_size(const struct stat* ZIX_NONNULL s1,
+ const struct stat* ZIX_NONNULL s2,
+ uint32_t fallback);
+
+BlockBuffer
+zix_system_new_block(ZixAllocator* ZIX_NULLABLE allocator,
+ uint32_t align,
+ uint32_t size);
+
+void
+zix_system_free_block(ZixAllocator* ZIX_NULLABLE allocator, BlockBuffer block);
+
#endif // ZIX_SYSTEM_H
diff --git a/src/tree.c b/src/tree.c
index 5e3aa61..a05d11a 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -1,9 +1,11 @@
-// Copyright 2011-2020 David Robillard <d@drobilla.net>
+// Copyright 2011-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
-#include "zix/tree.h"
+#include <zix/tree.h>
-#include "zix/status.h"
+#include <zix/allocator.h>
+#include <zix/attributes.h>
+#include <zix/status.h>
#include <assert.h>
@@ -31,13 +33,12 @@ struct ZixTreeNodeImpl {
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
-// Uncomment these for debugging features
-// #define ZIX_TREE_VERIFY 1
-// #define ZIX_TREE_HYPER_VERIFY 1
-
-#if defined(ZIX_TREE_VERIFY) || defined(ZIX_TREE_HYPER_VERIFY)
-# include "tree_debug.h"
-# define ASSERT_BALANCE(n) assert(verify_balance(n))
+#ifndef NDEBUG
+# define ASSERT_BALANCE(n) \
+ assert(!(((n)->balance < -2 || (n)->balance > 2) || \
+ ((n)->balance < 0 && !(n)->left) || \
+ ((n)->balance > 0 && !(n)->right) || \
+ ((n)->balance != 0 && !(n)->left && !(n)->right)))
#else
# define ASSERT_BALANCE(n)
#endif
@@ -94,7 +95,7 @@ zix_tree_free(ZixTree* t)
}
}
-size_t
+ZIX_REALTIME size_t
zix_tree_size(const ZixTree* t)
{
return t->size;
@@ -282,10 +283,6 @@ rotate_right_left(ZixTreeNode* p, int* height_change)
static ZixTreeNode*
zix_tree_rebalance(ZixTree* t, ZixTreeNode* node, int* height_change)
{
-#ifdef ZIX_TREE_HYPER_VERIFY
- const size_t old_height = height(node);
-#endif
-
*height_change = 0;
const bool is_root = !node->parent;
@@ -294,6 +291,7 @@ zix_tree_rebalance(ZixTree* t, ZixTreeNode* node, int* height_change)
ZixTreeNode* replacement = node;
if (node->balance == -2) {
assert(node->left);
+ // NOLINTNEXTLINE(clang-analyzer-core.NullDereference)
if (node->left->balance == 1) {
replacement = rotate_left_right(node, height_change);
} else {
@@ -301,6 +299,7 @@ zix_tree_rebalance(ZixTree* t, ZixTreeNode* node, int* height_change)
}
} else if (node->balance == 2) {
assert(node->right);
+ // NOLINTNEXTLINE(clang-analyzer-core.NullDereference)
if (node->right->balance == -1) {
replacement = rotate_right_left(node, height_change);
} else {
@@ -312,10 +311,6 @@ zix_tree_rebalance(ZixTree* t, ZixTreeNode* node, int* height_change)
t->root = replacement;
}
-#ifdef ZIX_TREE_HYPER_VERIFY
- assert(old_height + *height_change == height(replacement));
-#endif
-
return replacement;
}
@@ -395,12 +390,6 @@ zix_tree_insert(ZixTree* t, void* e, ZixTreeIter** ti)
++t->size;
-#ifdef ZIX_TREE_VERIFY
- if (!verify(t, t->root)) {
- return ZIX_STATUS_ERROR;
- }
-#endif
-
return ZIX_STATUS_SUCCESS;
}
@@ -548,13 +537,6 @@ zix_tree_remove(ZixTree* t, ZixTreeIter* ti)
zix_free(t->allocator, n);
--t->size;
-
-#ifdef ZIX_TREE_VERIFY
- if (!verify(t, t->root)) {
- return ZIX_STATUS_ERROR;
- }
-#endif
-
return ZIX_STATUS_SUCCESS;
}
@@ -579,13 +561,13 @@ zix_tree_find(const ZixTree* t, const void* e, ZixTreeIter** ti)
return (n) ? ZIX_STATUS_SUCCESS : ZIX_STATUS_NOT_FOUND;
}
-void*
+ZIX_REALTIME void*
zix_tree_get(const ZixTreeIter* ti)
{
return ti ? ti->data : NULL;
}
-ZixTreeIter*
+ZIX_NONBLOCKING ZixTreeIter*
zix_tree_begin(ZixTree* t)
{
if (!t->root) {
@@ -600,13 +582,13 @@ zix_tree_begin(ZixTree* t)
return n;
}
-ZixTreeIter*
+ZIX_REALTIME ZixTreeIter*
zix_tree_end(ZixTree* ZIX_UNUSED(t))
{
return NULL;
}
-ZixTreeIter*
+ZIX_NONBLOCKING ZixTreeIter*
zix_tree_rbegin(ZixTree* t)
{
if (!t->root) {
@@ -621,25 +603,25 @@ zix_tree_rbegin(ZixTree* t)
return n;
}
-ZixTreeIter*
+ZIX_REALTIME ZixTreeIter*
zix_tree_rend(ZixTree* ZIX_UNUSED(t))
{
return NULL;
}
-bool
+ZIX_REALTIME bool
zix_tree_iter_is_end(const ZixTreeIter* i)
{
return !i;
}
-bool
+ZIX_REALTIME bool
zix_tree_iter_is_rend(const ZixTreeIter* i)
{
return !i;
}
-ZixTreeIter*
+ZIX_NONBLOCKING ZixTreeIter*
zix_tree_iter_next(ZixTreeIter* i)
{
if (!i) {
@@ -662,7 +644,7 @@ zix_tree_iter_next(ZixTreeIter* i)
return i;
}
-ZixTreeIter*
+ZIX_NONBLOCKING ZixTreeIter*
zix_tree_iter_prev(ZixTreeIter* i)
{
if (!i) {
diff --git a/src/tree_debug.h b/src/tree_debug.h
deleted file mode 100644
index b1a2d2e..0000000
--- a/src/tree_debug.h
+++ /dev/null
@@ -1,139 +0,0 @@
-// Copyright 2011-2020 David Robillard <d@drobilla.net>
-// SPDX-License-Identifier: ISC
-
-#ifndef ZIX_TREE_DEBUG_H
-#define ZIX_TREE_DEBUG_H
-
-#include <inttypes.h>
-
-#ifdef ZIX_TREE_HYPER_VERIFY
-static size_t
-height(ZixTreeNode* n)
-{
- if (!n) {
- return 0;
- } else {
- return 1 + MAX(height(n->left), height(n->right));
- }
-}
-#endif
-
-#if defined(ZIX_TREE_VERIFY) || !defined(NDEBUG)
-static bool
-verify_balance(ZixTreeNode* n)
-{
- if (!n) {
- return true;
- }
-
- if (n->balance < -2 || n->balance > 2) {
- fprintf(stderr,
- "Balance out of range : %ld (balance %d)\n",
- (intptr_t)n->data,
- n->balance);
- return false;
- }
-
- if (n->balance < 0 && !n->left) {
- fprintf(stderr,
- "Bad balance : %ld (balance %d) has no left child\n",
- (intptr_t)n->data,
- n->balance);
- return false;
- }
-
- if (n->balance > 0 && !n->right) {
- fprintf(stderr,
- "Bad balance : %ld (balance %d) has no right child\n",
- (intptr_t)n->data,
- n->balance);
- return false;
- }
-
- if (n->balance != 0 && !n->left && !n->right) {
- fprintf(stderr,
- "Bad balance : %ld (balance %d) has no children\n",
- (intptr_t)n->data,
- n->balance);
- return false;
- }
-
-# ifdef ZIX_TREE_HYPER_VERIFY
- const intptr_t left_height = (intptr_t)height(n->left);
- const intptr_t right_height = (intptr_t)height(n->right);
- if (n->balance != right_height - left_height) {
- fprintf(stderr,
- "Bad balance at %ld: h_r (%" PRIdPTR ")"
- "- l_h (%" PRIdPTR ") != %d\n",
- (intptr_t)n->data,
- right_height,
- left_height,
- n->balance);
- assert(false);
- return false;
- }
-# endif
-
- return true;
-}
-#endif
-
-#ifdef ZIX_TREE_VERIFY
-static bool
-verify(ZixTree* t, ZixTreeNode* n)
-{
- if (!n) {
- return true;
- }
-
- if (n->parent) {
- if ((n->parent->left != n) && (n->parent->right != n)) {
- fprintf(stderr, "Corrupt child/parent pointers\n");
- return false;
- }
- }
-
- if (n->left) {
- if (t->cmp(n->left->data, n->data, t->cmp_data) > 0) {
- fprintf(
- stderr, "Bad order: %" PRIdPTR " with left child\n", (intptr_t)n->data);
- fprintf(stderr, "%p ? %p\n", (void*)n, (void*)n->left);
- fprintf(stderr,
- "%" PRIdPTR " ? %" PRIdPTR "\n",
- (intptr_t)n->data,
- (intptr_t)n->left->data);
- return false;
- }
-
- if (!verify(t, n->left)) {
- return false;
- }
- }
-
- if (n->right) {
- if (t->cmp(n->right->data, n->data, t->cmp_data) < 0) {
- fprintf(stderr,
- "Bad order: %" PRIdPTR " with right child\n",
- (intptr_t)n->data);
- return false;
- }
-
- if (!verify(t, n->right)) {
- return false;
- }
- }
-
- if (!verify_balance(n)) {
- return false;
- }
-
- if (n->balance <= -2 || n->balance >= 2) {
- fprintf(stderr, "Imbalance: %p (balance %d)\n", (void*)n, n->balance);
- return false;
- }
-
- return true;
-}
-#endif
-
-#endif // ZIX_TREE_DEBUG_H
diff --git a/src/win32/environment_win32.c b/src/win32/environment_win32.c
new file mode 100644
index 0000000..ce5f00e
--- /dev/null
+++ b/src/win32/environment_win32.c
@@ -0,0 +1,37 @@
+// Copyright 2012-2024 David Robillard <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#include "win32_util.h"
+
+#include <zix/environment.h>
+
+#include <windows.h>
+
+char*
+zix_expand_environment_strings(ZixAllocator* const allocator,
+ const char* const string)
+{
+ ArgPathChar* const wstring = arg_path_new(allocator, string);
+
+ const DWORD size = ExpandEnvironmentStrings(wstring, NULL, 0U);
+ if (!size) {
+ arg_path_free(allocator, wstring);
+ return NULL;
+ }
+
+ TCHAR* const out =
+ (TCHAR*)zix_calloc(allocator, (size_t)size + 1U, sizeof(TCHAR));
+ if (out) {
+ ExpandEnvironmentStrings(wstring, out, size + 1U);
+ }
+
+ arg_path_free(allocator, wstring);
+
+#ifdef UNICODE
+ char* const result = zix_wchar_to_utf8(allocator, out);
+ zix_free(allocator, out);
+ return result;
+#else
+ return out;
+#endif
+}
diff --git a/src/win32/filesystem_win32.c b/src/win32/filesystem_win32.c
index 5dc36a8..0a0bb36 100644
--- a/src/win32/filesystem_win32.c
+++ b/src/win32/filesystem_win32.c
@@ -1,21 +1,23 @@
-// Copyright 2007-2022 David Robillard <d@drobilla.net>
+// Copyright 2007-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
-#include "zix/bump_allocator.h"
-#include "zix/filesystem.h"
+#include <zix/filesystem.h>
#include "../errno_status.h"
+#include "../qualifiers.h"
#include "../zix_config.h"
+#include "win32_util.h"
-#include "zix/allocator.h"
-#include "zix/path.h"
-#include "zix/status.h"
+#include <zix/allocator.h>
+#include <zix/path.h>
+#include <zix/status.h>
#include <direct.h>
#include <fcntl.h>
#include <io.h>
#include <limits.h>
#include <sys/stat.h>
+#include <tchar.h>
#include <windows.h>
#include <errno.h>
@@ -25,6 +27,34 @@
#include <stdlib.h>
#include <string.h>
+#ifdef UNICODE
+
+static char*
+path_result(ZixAllocator* const allocator, wchar_t* const path)
+{
+ if (!path) {
+ return NULL;
+ }
+
+ ZIX_CONSTEXPR wchar_t* const long_prefix = L"\\\\?\\";
+
+ const size_t p = !wcsncmp(path, long_prefix, 4U) ? 4U : 0U;
+ char* const result = zix_wchar_to_utf8(allocator, path + p);
+ zix_free(allocator, path);
+ return result;
+}
+
+#else // !defined(UNICODE)
+
+static char*
+path_result(ZixAllocator* const allocator, char* const path)
+{
+ (void)allocator;
+ return path;
+}
+
+#endif
+
static inline ZixStatus
zix_winerror_status(const DWORD e)
{
@@ -65,18 +95,23 @@ zix_copy_file(ZixAllocator* const allocator,
const char* const dst,
const ZixCopyOptions options)
{
- (void)allocator;
+ ArgPathChar* const wsrc = arg_path_new(allocator, src);
+ ArgPathChar* const wdst = arg_path_new(allocator, dst);
- return zix_windows_status(
- CopyFile(src, dst, !(options & ZIX_COPY_OPTION_OVERWRITE_EXISTING)));
+ const BOOL ret =
+ CopyFile(wsrc, wdst, !(options & ZIX_COPY_OPTION_OVERWRITE_EXISTING));
+
+ arg_path_free(allocator, wdst);
+ arg_path_free(allocator, wsrc);
+ return zix_windows_status(ret);
}
/// Linear Congruential Generator for making random 32-bit integers
static inline uint32_t
lcg32(const uint32_t i)
{
- static const uint32_t a = 134775813U;
- static const uint32_t c = 1U;
+ ZIX_CONSTEXPR uint32_t a = 134775813U;
+ ZIX_CONSTEXPR uint32_t c = 1U;
return (a * i) + c;
}
@@ -85,8 +120,8 @@ char*
zix_create_temporary_directory(ZixAllocator* const allocator,
const char* const path_pattern)
{
- static const char chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
- static const size_t n_chars = sizeof(chars) - 1;
+ ZIX_CONSTEXPR char chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ ZIX_CONSTEXPR size_t n_chars = sizeof(chars) - 1;
// Ensure that the pattern ends with "XXXXXX"
const size_t length = strlen(path_pattern);
@@ -111,7 +146,7 @@ zix_create_temporary_directory(ZixAllocator* const allocator,
suffix[i] = chars[seed % n_chars];
}
- if (!_mkdir(result)) {
+ if (!zix_create_directory(result)) {
return result;
}
}
@@ -123,22 +158,44 @@ zix_create_temporary_directory(ZixAllocator* const allocator,
ZixStatus
zix_remove(const char* const path)
{
- return ((zix_file_type(path) == ZIX_FILE_TYPE_DIRECTORY)
- ? zix_windows_status(RemoveDirectory(path))
- : remove(path) ? zix_errno_status(errno)
- : ZIX_STATUS_SUCCESS);
+ ArgPathChar* const wpath = arg_path_new(NULL, path);
+ const DWORD attrs = GetFileAttributes(wpath);
+
+ const BOOL success =
+ ((attrs & FILE_ATTRIBUTE_DIRECTORY) ? RemoveDirectory(wpath)
+ : DeleteFile(wpath));
+
+ arg_path_free(NULL, wpath);
+ return zix_windows_status(success);
}
void
-zix_dir_for_each(const char* const path,
- void* const data,
- void (*const f)(const char* path,
- const char* name,
- void* data))
+zix_dir_for_each(const char* const path,
+ void* const data,
+ const ZixDirEntryVisitFunc f)
{
+ ZIX_CONSTEXPR TCHAR* const dot = TEXT(".");
+ ZIX_CONSTEXPR TCHAR* const dotdot = TEXT("..");
+
+#ifdef UNICODE
+ const int path_size = MultiByteToWideChar(CP_UTF8, 0, path, -1, NULL, 0);
+ if (path_size < 1) {
+ return;
+ }
+
+ const size_t path_len = (size_t)path_size - 1U;
+ TCHAR* const pat = (TCHAR*)zix_calloc(NULL, path_len + 4U, sizeof(TCHAR));
+ if (!pat) {
+ return;
+ }
+
+ MultiByteToWideChar(CP_UTF8, 0, path, -1, pat, path_size);
+#else
const size_t path_len = strlen(path);
- char pat[MAX_PATH + 2U];
+ TCHAR* const pat = (TCHAR*)zix_calloc(NULL, path_len + 2U, sizeof(TCHAR));
memcpy(pat, path, path_len + 1U);
+#endif
+
pat[path_len] = '\\';
pat[path_len + 1U] = '*';
pat[path_len + 2U] = '\0';
@@ -147,8 +204,14 @@ zix_dir_for_each(const char* const path,
HANDLE fh = FindFirstFile(pat, &fd);
if (fh != INVALID_HANDLE_VALUE) {
do {
- if (!!strcmp(fd.cFileName, ".") && !!strcmp(fd.cFileName, "..")) {
+ if (!!_tcscmp(fd.cFileName, dot) && !!_tcscmp(fd.cFileName, dotdot)) {
+#ifdef UNICODE
+ char* const name = zix_wchar_to_utf8(NULL, fd.cFileName);
+ f(path, name, data);
+ zix_free(NULL, name);
+#else
f(path, fd.cFileName, data);
+#endif
}
} while (FindNextFile(fh, &fd));
}
@@ -158,67 +221,127 @@ zix_dir_for_each(const char* const path,
ZixStatus
zix_file_lock(FILE* const file, const ZixFileLockMode mode)
{
- HANDLE handle = (HANDLE)_get_osfhandle(fileno(file));
- OVERLAPPED overlapped = {0};
+#ifdef __clang__
+ (void)file;
+ (void)mode;
+ return ZIX_STATUS_NOT_SUPPORTED;
+#else
+
+ const intptr_t handle = _get_osfhandle(fileno(file));
+ OVERLAPPED overlapped = {0};
const DWORD flags =
(LOCKFILE_EXCLUSIVE_LOCK |
(mode == ZIX_FILE_LOCK_TRY ? LOCKFILE_FAIL_IMMEDIATELY : 0));
return zix_windows_status(
- LockFileEx(handle, flags, 0, UINT32_MAX, UINT32_MAX, &overlapped));
+ LockFileEx((HANDLE)handle, flags, 0, UINT32_MAX, UINT32_MAX, &overlapped));
+#endif
}
ZixStatus
zix_file_unlock(FILE* const file, const ZixFileLockMode mode)
{
(void)mode;
+#ifdef __clang__
+ (void)file;
+ return ZIX_STATUS_NOT_SUPPORTED;
+#else
- HANDLE handle = (HANDLE)_get_osfhandle(fileno(file));
- OVERLAPPED overlapped = {0};
+ const intptr_t handle = _get_osfhandle(fileno(file));
+ OVERLAPPED overlapped = {0};
return zix_windows_status(
- UnlockFileEx(handle, 0, UINT32_MAX, UINT32_MAX, &overlapped));
+ UnlockFileEx((HANDLE)handle, 0, UINT32_MAX, UINT32_MAX, &overlapped));
+#endif
}
+#if USE_GETFINALPATHNAMEBYHANDLE && USE_CREATEFILE2
+
+static HANDLE
+open_attribute_handle(const char* const path)
+{
+ wchar_t* const wpath = zix_utf8_to_wchar(NULL, path);
+
+ CREATEFILE2_EXTENDED_PARAMETERS params = {
+ sizeof(CREATEFILE2_EXTENDED_PARAMETERS),
+ 0U,
+ FILE_FLAG_BACKUP_SEMANTICS,
+ 0U,
+ NULL,
+ NULL};
+
+ const HANDLE handle =
+ CreateFile2(wpath, FILE_READ_ATTRIBUTES, 0U, OPEN_EXISTING, &params);
+
+ zix_free(NULL, wpath);
+ return handle;
+}
+
+#elif USE_GETFINALPATHNAMEBYHANDLE
+
+static HANDLE
+open_attribute_handle(const char* const path)
+{
+ ArgPathChar* const wpath = arg_path_new(NULL, path);
+
+ const HANDLE handle = CreateFile(wpath,
+ FILE_READ_ATTRIBUTES,
+ 0U,
+ NULL,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS,
+ NULL);
+
+ arg_path_free(NULL, wpath);
+ return handle;
+}
+
+#endif
+
char*
zix_canonical_path(ZixAllocator* const allocator, const char* const path)
{
- char full[MAX_PATH] = {0};
- if (!path || !GetFullPathName(path, MAX_PATH, full, NULL)) {
+ if (!path) {
return NULL;
}
- const HANDLE h =
- CreateFile(full,
- FILE_READ_ATTRIBUTES,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
- NULL,
- OPEN_EXISTING,
- FILE_FLAG_BACKUP_SEMANTICS,
- NULL);
-
- const DWORD flags = FILE_NAME_NORMALIZED | VOLUME_NAME_DOS;
- const DWORD final_size = GetFinalPathNameByHandle(h, NULL, 0U, flags);
- if (!final_size) {
- CloseHandle(h);
- return NULL;
+#if USE_GETFINALPATHNAMEBYHANDLE // Vista+
+
+ const HANDLE h = open_attribute_handle(path);
+ TCHAR* final = NULL;
+ if (h != INVALID_HANDLE_VALUE) {
+ const DWORD flags = FILE_NAME_NORMALIZED | VOLUME_NAME_DOS;
+ const DWORD length = GetFinalPathNameByHandle(h, NULL, 0U, flags);
+ if (length) {
+ final = (TCHAR*)zix_calloc(allocator, (size_t)length + 1U, sizeof(TCHAR));
+ if (final) {
+ GetFinalPathNameByHandle(h, final, length + 1U, flags);
+ }
+ }
}
- char* const final = (char*)zix_calloc(allocator, final_size + 1U, 1U);
- if (!final || !GetFinalPathNameByHandle(h, final, final_size + 1U, flags)) {
- zix_free(allocator, final);
- CloseHandle(h);
- return NULL;
+ CloseHandle(h);
+ return path_result(allocator, final);
+
+#else // Fall back to "full path iff it exists" for older Windows
+
+ ArgPathChar* const wpath = arg_path_new(allocator, path);
+ TCHAR* full = NULL;
+ if (GetFileAttributes(wpath) != INVALID_FILE_ATTRIBUTES) {
+ const DWORD length = GetFullPathName(wpath, 0U, NULL, NULL);
+ if (length) {
+ full = (TCHAR*)zix_calloc(allocator, (size_t)length + 1U, sizeof(TCHAR));
+ if (full) {
+ GetFullPathName(wpath, length + 1U, full, NULL);
+ }
+ }
}
- if (final_size > 4U && !strncmp(final, "\\\\?\\", 4)) {
- memmove(final, final + 4U, final_size - 4U);
- final[final_size - 4U] = '\0';
- }
+ arg_path_free(allocator, wpath);
+ return path_result(allocator, full);
- CloseHandle(h);
- return final;
+#endif
}
static ZixFileType
@@ -246,30 +369,44 @@ attrs_file_type(const DWORD attrs)
ZixFileType
zix_file_type(const char* const path)
{
- const ZixFileType type = attrs_file_type(GetFileAttributes(path));
+ const ZixFileType type = zix_symlink_type(path);
if (type != ZIX_FILE_TYPE_SYMLINK) {
return type;
}
// Resolve symlink to find the canonical type
- char buf[MAX_PATH];
- ZixBumpAllocator allocator = zix_bump_allocator(sizeof(buf), buf);
- char* const canonical = zix_canonical_path(&allocator.base, path);
- return zix_file_type(canonical);
+ char* const canonical = zix_canonical_path(NULL, path);
+ const ZixFileType real_type =
+ canonical ? zix_symlink_type(canonical) : ZIX_FILE_TYPE_NONE;
+
+ zix_free(NULL, canonical);
+ return real_type;
}
ZixFileType
zix_symlink_type(const char* const path)
{
- return attrs_file_type(GetFileAttributes(path));
+ ArgPathChar* const wpath = arg_path_new(NULL, path);
+ if (!wpath) {
+ return ZIX_FILE_TYPE_NONE;
+ }
+
+ const ZixFileType type = attrs_file_type(GetFileAttributes(wpath));
+ arg_path_free(NULL, wpath);
+ return type;
}
ZixStatus
zix_create_directory(const char* const dir_path)
{
- return (!dir_path[0]) ? ZIX_STATUS_BAD_ARG
- : _mkdir(dir_path) ? zix_errno_status(errno)
- : ZIX_STATUS_SUCCESS;
+ if (!dir_path[0]) {
+ return ZIX_STATUS_BAD_ARG;
+ }
+
+ ArgPathChar* const wpath = arg_path_new(NULL, dir_path);
+ const ZixStatus st = zix_windows_status(CreateDirectory(wpath, NULL));
+ arg_path_free(NULL, wpath);
+ return st;
}
ZixStatus
@@ -285,9 +422,17 @@ ZixStatus
zix_create_symlink(const char* const target_path, const char* const link_path)
{
#if USE_CREATESYMBOLICLINK
- static const DWORD flags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
+ ZIX_CONSTEXPR DWORD flags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
+
+ ArgPathChar* const wtarget = arg_path_new(NULL, target_path);
+ ArgPathChar* const wlink = arg_path_new(NULL, link_path);
+
+ const BOOL success = CreateSymbolicLink(wlink, wtarget, flags);
+
+ arg_path_free(NULL, wlink);
+ arg_path_free(NULL, wtarget);
+ return zix_windows_status(success);
- return zix_windows_status(CreateSymbolicLink(link_path, target_path, flags));
#else
(void)target_path;
(void)link_path;
@@ -300,10 +445,18 @@ zix_create_directory_symlink(const char* const target_path,
const char* const link_path)
{
#if USE_CREATESYMBOLICLINK
- static const DWORD flags =
+ ZIX_CONSTEXPR DWORD flags =
SYMBOLIC_LINK_FLAG_DIRECTORY | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
- return zix_windows_status(CreateSymbolicLink(link_path, target_path, flags));
+ ArgPathChar* const wtarget = arg_path_new(NULL, target_path);
+ ArgPathChar* const wlink = arg_path_new(NULL, link_path);
+
+ const BOOL success = CreateSymbolicLink(wlink, wtarget, flags);
+
+ arg_path_free(NULL, wlink);
+ arg_path_free(NULL, wtarget);
+ return zix_windows_status(success);
+
#else
(void)target_path;
(void)link_path;
@@ -314,31 +467,45 @@ zix_create_directory_symlink(const char* const target_path,
ZixStatus
zix_create_hard_link(const char* const target_path, const char* const link_path)
{
- return zix_windows_status(CreateHardLink(link_path, target_path, NULL));
+#if USE_CREATEHARDLINK
+ ArgPathChar* const wtarget = arg_path_new(NULL, target_path);
+ ArgPathChar* const wlink = arg_path_new(NULL, link_path);
+
+ const BOOL success = CreateHardLink(wlink, wtarget, NULL);
+
+ arg_path_free(NULL, wlink);
+ arg_path_free(NULL, wtarget);
+ return zix_windows_status(success);
+
+#else
+ (void)target_path;
+ (void)link_path;
+ return ZIX_STATUS_NOT_SUPPORTED;
+#endif
}
char*
zix_temp_directory_path(ZixAllocator* const allocator)
{
- const DWORD size = GetTempPath(0U, NULL);
- char* const buf = (char*)zix_calloc(allocator, size, 1);
+ const DWORD size = GetTempPath(0U, NULL);
+ TCHAR* const buf = (TCHAR*)zix_calloc(allocator, size, sizeof(TCHAR));
if (buf && (GetTempPath(size, buf) != size - 1U)) {
zix_free(allocator, buf);
return NULL;
}
- return buf;
+ return path_result(allocator, buf);
}
char*
zix_current_path(ZixAllocator* const allocator)
{
- const DWORD size = GetCurrentDirectory(0U, NULL);
- char* const buf = (char*)zix_calloc(allocator, size, 1);
+ const DWORD size = GetCurrentDirectory(0U, NULL);
+ TCHAR* const buf = (TCHAR*)zix_calloc(allocator, size, sizeof(TCHAR));
if (buf && (GetCurrentDirectory(size, buf) != size - 1U)) {
zix_free(allocator, buf);
return NULL;
}
- return buf;
+ return path_result(allocator, buf);
}
diff --git a/src/win32/sem_win32.c b/src/win32/sem_win32.c
index 979390e..d28f39f 100644
--- a/src/win32/sem_win32.c
+++ b/src/win32/sem_win32.c
@@ -1,9 +1,9 @@
// Copyright 2012-2022 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
-#include "zix/sem.h"
+#include <zix/sem.h>
-#include "zix/status.h"
+#include <zix/status.h>
#include <windows.h>
diff --git a/src/win32/system_win32.c b/src/win32/system_win32.c
index a2e68bf..eaed6e1 100644
--- a/src/win32/system_win32.c
+++ b/src/win32/system_win32.c
@@ -1,8 +1,12 @@
-// Copyright 2007-2022 David Robillard <d@drobilla.net>
+// Copyright 2007-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#include "../system.h"
+#include "../zix_config.h"
+#include <fcntl.h>
+#include <io.h>
+#include <share.h>
#include <windows.h>
#include <limits.h>
@@ -18,3 +22,46 @@ zix_system_page_size(void)
? (uint32_t)info.dwPageSize
: 512U;
}
+
+uint32_t
+zix_system_max_block_size(const struct stat* const s1,
+ const struct stat* const s2,
+ const uint32_t fallback)
+{
+ (void)s1;
+ (void)s2;
+
+ /* Windows doesn't provide st_blksize, so to implement this properly, we'd
+ need to do something like get the corresponding device handle from the
+ files and use DeviceIoControl to query it. This is pretty tedious, and
+ shouldn't affect performance very much (if at all) in most cases, so just
+ use the fallback for now. */
+
+ return fallback;
+}
+
+int
+zix_system_open(const char* const path, const int flags, const mode_t mode)
+{
+ const int oflag = flags | O_BINARY; // NOLINT(hicpp-signed-bitwise)
+
+#if USE_SOPEN_S
+ int fd = 0;
+ const errno_t err = _sopen_s(&fd, path, oflag, _SH_DENYNO, mode);
+ return err ? -1 : fd;
+#else
+ return _open(path, oflag, mode);
+#endif
+}
+
+int
+zix_system_close(const int fd)
+{
+ return _close(fd);
+}
+
+ssize_t
+zix_system_read(const int fd, void* const buf, const size_t count)
+{
+ return _read(fd, buf, (unsigned)count);
+}
diff --git a/src/win32/thread_win32.c b/src/win32/thread_win32.c
index e4411b1..dbfe026 100644
--- a/src/win32/thread_win32.c
+++ b/src/win32/thread_win32.c
@@ -1,9 +1,9 @@
// Copyright 2012-2022 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
-#include "zix/thread.h"
+#include <zix/thread.h>
-#include "zix/status.h"
+#include <zix/status.h>
#include <windows.h>
diff --git a/src/win32/win32_util.c b/src/win32/win32_util.c
new file mode 100644
index 0000000..959867c
--- /dev/null
+++ b/src/win32/win32_util.c
@@ -0,0 +1,73 @@
+// Copyright 2019-2024 David Robillard <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#include "win32_util.h"
+
+#include <limits.h>
+#include <windows.h>
+
+#ifdef UNICODE
+
+ArgPathChar*
+arg_path_new(ZixAllocator* const allocator, const char* const path)
+{
+ return zix_utf8_to_wchar(allocator, path);
+}
+
+void
+arg_path_free(ZixAllocator* const allocator, ArgPathChar* const path)
+{
+ zix_free(allocator, path);
+}
+
+#else // !defined(UNICODE)
+
+ArgPathChar*
+arg_path_new(ZixAllocator* const allocator, const char* const path)
+{
+ (void)allocator;
+ return path;
+}
+
+void
+arg_path_free(ZixAllocator* const allocator, ArgPathChar* const path)
+{
+ (void)allocator;
+ (void)path;
+}
+
+#endif
+
+wchar_t*
+zix_utf8_to_wchar(ZixAllocator* const allocator, const char* const utf8)
+{
+ const int rc = utf8 ? MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0) : 0;
+ if (rc <= 0 || rc == INT_MAX) {
+ return NULL;
+ }
+
+ wchar_t* const result =
+ (wchar_t*)zix_calloc(allocator, (size_t)rc, sizeof(wchar_t));
+ if (result) {
+ MultiByteToWideChar(CP_UTF8, 0, utf8, -1, result, rc);
+ }
+
+ return result;
+}
+
+char*
+zix_wchar_to_utf8(ZixAllocator* const allocator, const wchar_t* const wstr)
+{
+ const int rc =
+ wstr ? WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL) : 0;
+ if (rc <= 0 || rc == INT_MAX) {
+ return NULL;
+ }
+
+ char* const result = (char*)zix_calloc(allocator, (size_t)rc, sizeof(char));
+ if (result) {
+ WideCharToMultiByte(CP_UTF8, 0, wstr, -1, result, rc, NULL, NULL);
+ }
+
+ return result;
+}
diff --git a/src/win32/win32_util.h b/src/win32/win32_util.h
new file mode 100644
index 0000000..3ae2e2b
--- /dev/null
+++ b/src/win32/win32_util.h
@@ -0,0 +1,31 @@
+// Copyright 2019-2024 David Robillard <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#ifndef ZIX_WIN32_UTIL_H
+#define ZIX_WIN32_UTIL_H
+
+#include <zix/allocator.h>
+
+#ifdef UNICODE
+typedef wchar_t ArgPathChar;
+#else
+typedef const char ArgPathChar;
+#endif
+
+/// Copy and convert a path argument if necessary
+ArgPathChar*
+arg_path_new(ZixAllocator* allocator, const char* path);
+
+/// Free a path from arg_path_new() if necessary
+void
+arg_path_free(ZixAllocator* allocator, ArgPathChar* path);
+
+/// Convert from (user) UTF-8 to (Windows) UTF-16
+wchar_t*
+zix_utf8_to_wchar(ZixAllocator* allocator, const char* utf8);
+
+/// Convert from (Windows) UTF-16 to (user) UTF-8
+char*
+zix_wchar_to_utf8(ZixAllocator* allocator, const wchar_t* wstr);
+
+#endif // ZIX_WIN32_UTIL_H
diff --git a/src/zix_config.h b/src/zix_config.h
index 0aab586..ca8ab2a 100644
--- a/src/zix_config.h
+++ b/src/zix_config.h
@@ -1,4 +1,4 @@
-// Copyright 2021-2023 David Robillard <d@drobilla.net>
+// Copyright 2021-2024 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
/*
@@ -55,6 +55,13 @@
# define ZIX_POSIX_VERSION 0
# endif
+// Define ZIX_WINAPI_UWP to 1 (if this is a sandboxed UWP app) or 0
+# if defined(WINAPI_FAMILY) && WINAPI_FAMILY < 10
+# define ZIX_WINAPI_UWP 1
+# else
+# define ZIX_WINAPI_UWP 0
+# endif
+
// POSIX.1-2001: clock_gettime()
# ifndef HAVE_CLOCK_GETTIME
# if ZIX_POSIX_VERSION >= 200112L
@@ -78,9 +85,24 @@
# endif
# endif
-// Windows: CreateSymbolicLink()
+// Windows 8 (Desktop, UWP): CreateFile2()
+# ifndef HAVE_CREATEFILE2
+# if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0602 && !defined(__MINGW32__)
+# define HAVE_CREATEFILE2 1
+# endif
+# endif
+
+// Windows XP (Desktop): CreateHardLink()
+# ifndef HAVE_CREATEHARDLINK
+# if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0501 && !ZIX_WINAPI_UWP
+# define HAVE_CREATEHARDLINK 1
+# endif
+# endif
+
+// Windows Vista (Desktop): CreateSymbolicLink()
# ifndef HAVE_CREATESYMBOLICLINK
-# if defined(_MSC_VER) && _MSC_VER >= 1910
+# if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0600 && \
+ !defined(__MINGW32__) && !ZIX_WINAPI_UWP
# define HAVE_CREATESYMBOLICLINK 1
# endif
# endif
@@ -94,11 +116,18 @@
// Classic UNIX: flock()
# ifndef HAVE_FLOCK
-# if defined(__APPLE__) || defined(__unix__)
+# if (defined(__APPLE__) || defined(__unix__)) && !defined(__EMSCRIPTEN__)
# define HAVE_FLOCK 1
# endif
# endif
+// Windows Vista (Desktop, UWP): GetFinalPathNameByHandle()
+# ifndef HAVE_GETFINALPATHNAMEBYHANDLE
+# if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0600
+# define HAVE_GETFINALPATHNAMEBYHANDLE 1
+# endif
+# endif
+
// POSIX.1-2001: mlock()
# ifndef HAVE_MLOCK
# if ZIX_POSIX_VERSION >= 200112L
@@ -141,6 +170,13 @@
# endif
# endif
+// Windows Vista (Desktop, UWP): _sopen_s()
+# ifndef HAVE_SOPEN_S
+# if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0600
+# define HAVE_SOPEN_S 1
+# endif
+# endif
+
// POSIX.1-2001: sysconf()
# ifndef HAVE_SYSCONF
# if ZIX_POSIX_VERSION >= 200112L
@@ -148,6 +184,13 @@
# endif
# endif
+// Windows XP (Desktop): VirtualLock
+# ifndef HAVE_VIRTUALLOCK
+# if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0501 && !ZIX_WINAPI_UWP
+# define HAVE_VIRTUALLOCK 1
+# endif
+# endif
+
#endif // !defined(ZIX_NO_DEFAULT_CONFIG)
/*
@@ -176,6 +219,18 @@
# define USE_COPY_FILE_RANGE 0
#endif
+#if defined(HAVE_CREATEFILE2) && HAVE_CREATEFILE2
+# define USE_CREATEFILE2 1
+#else
+# define USE_CREATEFILE2 0
+#endif
+
+#if defined(HAVE_CREATEHARDLINK) && HAVE_CREATEHARDLINK
+# define USE_CREATEHARDLINK 1
+#else
+# define USE_CREATEHARDLINK 0
+#endif
+
#if defined(HAVE_CREATESYMBOLICLINK) && HAVE_CREATESYMBOLICLINK
# define USE_CREATESYMBOLICLINK 1
#else
@@ -194,6 +249,12 @@
# define USE_FLOCK 0
#endif
+#if defined(HAVE_GETFINALPATHNAMEBYHANDLE) && HAVE_GETFINALPATHNAMEBYHANDLE
+# define USE_GETFINALPATHNAMEBYHANDLE 1
+#else
+# define USE_GETFINALPATHNAMEBYHANDLE 0
+#endif
+
#if defined(HAVE_MLOCK) && HAVE_MLOCK
# define USE_MLOCK 1
#else
@@ -230,10 +291,22 @@
# define USE_SEM_TIMEDWAIT 0
#endif
+#if defined(HAVE_SOPEN_S) && HAVE_SOPEN_S
+# define USE_SOPEN_S 1
+#else
+# define USE_SOPEN_S 0
+#endif
+
#if defined(HAVE_SYSCONF) && HAVE_SYSCONF
# define USE_SYSCONF 1
#else
# define USE_SYSCONF 0
#endif
+#if defined(HAVE_VIRTUALLOCK) && HAVE_VIRTUALLOCK
+# define USE_VIRTUALLOCK 1
+#else
+# define USE_VIRTUALLOCK 0
+#endif
+
#endif // ZIX_CONFIG_H
diff --git a/subprojects/sphinxygen.wrap b/subprojects/sphinxygen.wrap
index 9707d1a..898ee52 100644
--- a/subprojects/sphinxygen.wrap
+++ b/subprojects/sphinxygen.wrap
@@ -1,14 +1,14 @@
-# Copyright 2022-2023 David Robillard <d@drobilla.net>
+# Copyright 2022-2025 David Robillard <d@drobilla.net>
# SPDX-License-Identifier: 0BSD OR ISC
[wrap-file]
-directory = sphinxygen-1.0.4
-source_url = https://download.drobilla.net/sphinxygen-1.0.4.tar.gz
-source_filename = sphinxygen-1.0.4.tar.gz
-source_hash = 12fa9f18ed9fca608f272520072257ba61fd9eff25613f86d83d4fce14fc01f5
+directory = sphinxygen-1.0.10
+source_url = https://download.drobilla.net/sphinxygen-1.0.10.tar.gz
+source_filename = sphinxygen-1.0.10.tar.gz
+source_hash = 4b5eeb1ff47d43ee1ddae9327c2b49bab1e49363538c569bc544705284f3695d
# [wrap-git]
# url = https://gitlab.com/drobilla/sphinxygen.git
# push-url = ssh://git@gitlab.com:drobilla/sphinxygen.git
-# revision = v1.0.4
+# revision = v1.0.10
# depth = 1
diff --git a/test/.clang-tidy b/test/.clang-tidy
index 2d684a6..b3e4b96 100644
--- a/test/.clang-tidy
+++ b/test/.clang-tidy
@@ -1,16 +1,20 @@
-# Copyright 2020-2022 David Robillard <d@drobilla.net>
+# Copyright 2020-2025 David Robillard <d@drobilla.net>
# SPDX-License-Identifier: 0BSD OR ISC
Checks: >
+ -*-macro-to-enum,
-*-magic-numbers,
-android-cloexec-fopen,
-bugprone-easily-swappable-parameters,
+ -bugprone-multi-level-implicit-pointer-conversion,
-cert-err33-c,
-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,
+ -concurrency-mt-unsafe,
-cppcoreguidelines-avoid-non-const-global-variables,
-google-readability-casting,
-llvm-header-guard,
- -modernize-macro-to-enum,
-performance-no-int-to-ptr,
- -readability-function-cognitive-complexity,
+CheckOptions:
+ - key: readability-function-cognitive-complexity.Threshold
+ value: '194'
InheritParentConfig: true
diff --git a/test/cpp/.clang-tidy b/test/cpp/.clang-tidy
index 9a3cd8c..8d2b128 100644
--- a/test/cpp/.clang-tidy
+++ b/test/cpp/.clang-tidy
@@ -1,13 +1,17 @@
-# Copyright 2020-2022 David Robillard <d@drobilla.net>
+# Copyright 2020-2025 David Robillard <d@drobilla.net>
# SPDX-License-Identifier: 0BSD OR ISC
Checks: >
-*-avoid-c-arrays,
-*-no-malloc,
-android-cloexec-fopen,
+ -bugprone-exception-escape,
+ -cppcoreguidelines-macro-usage,
-cppcoreguidelines-owning-memory,
-fuchsia-default-arguments-calls,
-modernize-raw-string-literal,
+ -modernize-redundant-void-arg,
-modernize-use-trailing-return-type,
- -readability-implicit-bool-conversion,
+ -modernize-use-using,
+ -performance-enum-size,
InheritParentConfig: true
diff --git a/test/cpp/test_headers_cpp.cpp b/test/cpp/test_headers_cpp.cpp
index f8038c1..fe367ad 100644
--- a/test/cpp/test_headers_cpp.cpp
+++ b/test/cpp/test_headers_cpp.cpp
@@ -5,21 +5,22 @@
# define WIN32_LEAN_AND_MEAN
#endif
-#include "zix/allocator.h" // IWYU pragma: keep
-#include "zix/attributes.h" // IWYU pragma: keep
-#include "zix/btree.h" // IWYU pragma: keep
-#include "zix/bump_allocator.h" // IWYU pragma: keep
-#include "zix/digest.h" // IWYU pragma: keep
-#include "zix/filesystem.h" // IWYU pragma: keep
-#include "zix/hash.h" // IWYU pragma: keep
-#include "zix/path.h" // IWYU pragma: keep
-#include "zix/ring.h" // IWYU pragma: keep
-#include "zix/sem.h" // IWYU pragma: keep
-#include "zix/status.h" // IWYU pragma: keep
-#include "zix/string_view.h" // IWYU pragma: keep
-#include "zix/thread.h" // IWYU pragma: keep
-#include "zix/tree.h" // IWYU pragma: keep
-#include "zix/zix.h" // IWYU pragma: keep
+#include <zix/allocator.h> // IWYU pragma: keep
+#include <zix/attributes.h> // IWYU pragma: keep
+#include <zix/btree.h> // IWYU pragma: keep
+#include <zix/bump_allocator.h> // IWYU pragma: keep
+#include <zix/digest.h> // IWYU pragma: keep
+#include <zix/environment.h> // IWYU pragma: keep
+#include <zix/filesystem.h> // IWYU pragma: keep
+#include <zix/hash.h> // IWYU pragma: keep
+#include <zix/path.h> // IWYU pragma: keep
+#include <zix/ring.h> // IWYU pragma: keep
+#include <zix/sem.h> // IWYU pragma: keep
+#include <zix/status.h> // IWYU pragma: keep
+#include <zix/string_view.h> // IWYU pragma: keep
+#include <zix/thread.h> // IWYU pragma: keep
+#include <zix/tree.h> // IWYU pragma: keep
+#include <zix/zix.h> // IWYU pragma: keep
#if defined(__GNUC__)
__attribute__((const))
diff --git a/test/cpp/test_path_std.cpp b/test/cpp/test_path_std.cpp
index cabf3da..b0850a9 100644
--- a/test/cpp/test_path_std.cpp
+++ b/test/cpp/test_path_std.cpp
@@ -10,15 +10,16 @@
#undef NDEBUG
-#include "zix/path.h"
-#include "zix/string_view.h"
+#include <zix/path.h>
+#include <zix/string_view.h>
#include <cassert>
#include <cstdlib>
#include <filesystem>
-#include <sstream>
#include <string>
+// IWYU pragma: no_include <version>
+
namespace {
struct BinaryCase {
@@ -483,8 +484,11 @@ run()
}
for (const auto& relatives : lexical_relatives) {
- const Path l = relatives.lhs ? Path{relatives.lhs} : Path{};
- const Path r = relatives.rhs ? Path{relatives.rhs} : Path{};
+ assert(relatives.lhs);
+ assert(relatives.rhs);
+
+ const Path l = Path{relatives.lhs};
+ const Path r = Path{relatives.rhs};
assert(match(
l.lexically_relative(r),
diff --git a/test/failing_allocator.c b/test/failing_allocator.c
index 684a8ec..6cffc7b 100644
--- a/test/failing_allocator.c
+++ b/test/failing_allocator.c
@@ -1,10 +1,10 @@
-// Copyright 2021 David Robillard <d@drobilla.net>
+// Copyright 2021-2024 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#include "failing_allocator.h"
-#include "zix/allocator.h"
-#include "zix/attributes.h"
+#include <zix/allocator.h>
+#include <zix/attributes.h>
#include <stdbool.h>
#include <stddef.h>
@@ -23,8 +23,7 @@ attempt(ZixFailingAllocator* const allocator)
return true;
}
-ZIX_MALLOC_FUNC
-static void*
+ZIX_MALLOC_FUNC static void*
zix_failing_malloc(ZixAllocator* const allocator, const size_t size)
{
ZixFailingAllocator* const state = (ZixFailingAllocator*)allocator;
@@ -33,8 +32,7 @@ zix_failing_malloc(ZixAllocator* const allocator, const size_t size)
return attempt(state) ? base->malloc(base, size) : NULL;
}
-ZIX_MALLOC_FUNC
-static void*
+ZIX_MALLOC_FUNC static void*
zix_failing_calloc(ZixAllocator* const allocator,
const size_t nmemb,
const size_t size)
@@ -66,8 +64,7 @@ zix_failing_free(ZixAllocator* const allocator, void* const ptr)
base->free(base, ptr);
}
-ZIX_MALLOC_FUNC
-static void*
+ZIX_MALLOC_FUNC static void*
zix_failing_aligned_alloc(ZixAllocator* const allocator,
const size_t alignment,
const size_t size)
@@ -88,8 +85,7 @@ zix_failing_aligned_free(ZixAllocator* const allocator, void* const ptr)
base->aligned_free(base, ptr);
}
-ZIX_CONST_FUNC
-ZixFailingAllocator
+ZIX_CONST_FUNC ZixFailingAllocator
zix_failing_allocator(void)
{
ZixFailingAllocator failing_allocator = {
@@ -107,3 +103,15 @@ zix_failing_allocator(void)
return failing_allocator;
}
+
+size_t
+zix_failing_allocator_reset(ZixFailingAllocator* const allocator,
+ const size_t n_allowed)
+{
+ const size_t n_allocations = allocator->n_allocations;
+
+ allocator->n_allocations = 0U;
+ allocator->n_remaining = n_allowed;
+
+ return n_allocations;
+}
diff --git a/test/failing_allocator.h b/test/failing_allocator.h
index 982874d..215f2b6 100644
--- a/test/failing_allocator.h
+++ b/test/failing_allocator.h
@@ -1,10 +1,10 @@
-// Copyright 2021 David Robillard <d@drobilla.net>
+// Copyright 2021-2024 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
-#ifndef ZIX_FAILING_ALLOCATOR_H
-#define ZIX_FAILING_ALLOCATOR_H
+#ifndef ZIX_TEST_FAILING_ALLOCATOR_H
+#define ZIX_TEST_FAILING_ALLOCATOR_H
-#include "zix/allocator.h"
+#include <zix/allocator.h>
#include <stddef.h>
@@ -15,7 +15,12 @@ typedef struct {
size_t n_remaining; ///< Number of remaining successful allocations
} ZixFailingAllocator;
+/// Return an allocator configured by default to succeed
ZixFailingAllocator
zix_failing_allocator(void);
-#endif // ZIX_FAILING_ALLOCATOR_H
+/// Reset an allocator to fail after some number of "allowed" allocations
+size_t
+zix_failing_allocator_reset(ZixFailingAllocator* allocator, size_t n_allowed);
+
+#endif // ZIX_TEST_FAILING_ALLOCATOR_H
diff --git a/test/headers/.clang-tidy b/test/headers/.clang-tidy
index 799689a..cd60371 100644
--- a/test/headers/.clang-tidy
+++ b/test/headers/.clang-tidy
@@ -1,11 +1,13 @@
-# Copyright 2022 David Robillard <d@drobilla.net>
+# Copyright 2022-2025 David Robillard <d@drobilla.net>
# SPDX-License-Identifier: 0BSD OR ISC
Checks: >
*,
+ -*-use-nullptr,
-altera-*,
-llvmlibc-*,
-readability-identifier-length,
+ -readability-implicit-bool-conversion,
WarningsAsErrors: '*'
HeaderFilterRegex: '.*'
FormatStyle: file
diff --git a/test/headers/test_headers.c b/test/headers/test_headers.c
index a610600..f10d05f 100644
--- a/test/headers/test_headers.c
+++ b/test/headers/test_headers.c
@@ -1,21 +1,22 @@
// Copyright 2022 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
-#include "zix/allocator.h" // IWYU pragma: keep
-#include "zix/attributes.h" // IWYU pragma: keep
-#include "zix/btree.h" // IWYU pragma: keep
-#include "zix/bump_allocator.h" // IWYU pragma: keep
-#include "zix/digest.h" // IWYU pragma: keep
-#include "zix/filesystem.h" // IWYU pragma: keep
-#include "zix/hash.h" // IWYU pragma: keep
-#include "zix/path.h" // IWYU pragma: keep
-#include "zix/ring.h" // IWYU pragma: keep
-#include "zix/sem.h" // IWYU pragma: keep
-#include "zix/status.h" // IWYU pragma: keep
-#include "zix/string_view.h" // IWYU pragma: keep
-#include "zix/thread.h" // IWYU pragma: keep
-#include "zix/tree.h" // IWYU pragma: keep
-#include "zix/zix.h" // IWYU pragma: keep
+#include <zix/allocator.h> // IWYU pragma: keep
+#include <zix/attributes.h> // IWYU pragma: keep
+#include <zix/btree.h> // IWYU pragma: keep
+#include <zix/bump_allocator.h> // IWYU pragma: keep
+#include <zix/digest.h> // IWYU pragma: keep
+#include <zix/environment.h> // IWYU pragma: keep
+#include <zix/filesystem.h> // IWYU pragma: keep
+#include <zix/hash.h> // IWYU pragma: keep
+#include <zix/path.h> // IWYU pragma: keep
+#include <zix/ring.h> // IWYU pragma: keep
+#include <zix/sem.h> // IWYU pragma: keep
+#include <zix/status.h> // IWYU pragma: keep
+#include <zix/string_view.h> // IWYU pragma: keep
+#include <zix/thread.h> // IWYU pragma: keep
+#include <zix/tree.h> // IWYU pragma: keep
+#include <zix/zix.h> // IWYU pragma: keep
#if defined(__GNUC__)
__attribute__((const))
diff --git a/test/lint/meson.build b/test/lint/meson.build
new file mode 100644
index 0000000..c703509
--- /dev/null
+++ b/test/lint/meson.build
@@ -0,0 +1,43 @@
+# Copyright 2024 David Robillard <d@drobilla.net>
+# SPDX-License-Identifier: 0BSD OR ISC
+
+if not meson.is_subproject()
+ # Check release metadata
+ autoship = find_program('autoship', required: get_option('tests'))
+ if autoship.found()
+ test('autoship', autoship, args: ['test', zix_src_root], suite: 'data')
+ endif
+
+ # Check code with cppcheck
+ cppcheck = find_program('cppcheck', required: false)
+ if cppcheck.found()
+ compdb_path = join_paths(zix_build_root, 'compile_commands.json')
+ cppcheck_args = [
+ '--cppcheck-build-dir=' + meson.current_build_dir(),
+ '--enable=warning,style,performance,portability',
+ '--error-exitcode=1',
+ '--project=' + compdb_path,
+ '--suppress=arrayIndexOutOfBoundsCond',
+ '--suppress=constParameterCallback',
+ '--suppress=constParameterPointer',
+ '--suppress=normalCheckLevelMaxBranches',
+ '--suppress=unreadVariable',
+ '-q',
+ ]
+ test('cppcheck', cppcheck, args: cppcheck_args, suite: 'code')
+ endif
+endif
+
+# Check licensing metadata
+reuse = find_program('reuse', required: get_option('tests'))
+if reuse.found()
+ reuse_args = ['--root', zix_src_root, 'lint']
+ test('REUSE', reuse, args: reuse_args, suite: 'data')
+endif
+
+# Check code formatting
+clang_format = find_program('clang-format', required: false)
+if clang_format.found()
+ clang_format_args = ['--Werror', '--dry-run'] + c_headers + sources
+ test('format', clang_format, args: clang_format_args, suite: 'code')
+endif
diff --git a/test/meson.build b/test/meson.build
index 96fe455..ea5e9be 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -1,72 +1,37 @@
-# Copyright 2020-2024 David Robillard <d@drobilla.net>
+# Copyright 2020-2025 David Robillard <d@drobilla.net>
# SPDX-License-Identifier: 0BSD OR ISC
-if not meson.is_subproject() and get_option('lint')
- # Check release metadata
- autoship = find_program('autoship', required: get_option('tests'))
- if autoship.found()
- test(
- 'autoship',
- autoship,
- args: ['test', zix_src_root],
- suite: 'data',
- )
- endif
+########
+# Lint #
+########
- # Check licensing metadata
- reuse = find_program('reuse', required: get_option('tests'))
- if reuse.found()
- test(
- 'REUSE',
- reuse,
- args: ['--root', zix_src_root, 'lint'],
- suite: 'data',
- )
- endif
+if get_option('lint')
+ subdir('lint')
+endif
- # Check code formatting
- clang_format = find_program('clang-format', required: false)
- if clang_format.found()
- test(
- 'format',
- clang_format,
- args: ['--Werror', '--dry-run'] + c_headers + sources,
- suite: 'code',
- )
- endif
+##############
+# Unit Tests #
+##############
- # Check code with cppcheck
- if not meson.is_subproject()
- cppcheck = find_program('cppcheck', required: false)
- if cppcheck.found()
- compdb_path = join_paths(zix_build_root, 'compile_commands.json')
- test(
- 'cppcheck',
- cppcheck,
- args: [
- '--check-level=exhaustive',
- '--enable=warning,style,performance,portability',
- '--error-exitcode=1',
- '--project=' + compdb_path,
- '--suppress=constParameterCallback',
- '--suppress=constParameterPointer',
- '--suppress=unreadVariable',
- '-q',
- ],
- suite: 'code',
- )
+# Set warning suppression flags specific to tests
+test_suppressions = []
+if cc.get_id() in ['clang', 'emscripten']
+ if warning_level == 'everything'
+ test_suppressions += ['-Wno-bad-function-cast']
+
+ if host_machine.system() == 'windows'
+ if cc.get_id() in ['clang', 'emscripten']
+ test_suppressions += ['-Wno-format-nonliteral']
+ endif
endif
endif
-endif
+elif cc.get_id() == 'gcc'
+ test_suppressions += [
+ '-Wno-analyzer-malloc-leak',
+ '-Wno-bad-function-cast',
+ ]
-# Set warning suppression flags specific to tests
-test_suppressions = []
-if host_machine.system() == 'windows'
- if cc.get_id() in ['clang', 'emscripten']
- test_suppressions += [
- '-Wno-format-nonliteral',
- ]
- elif cc.get_id() == 'gcc'
+ if host_machine.system() == 'windows'
test_suppressions += ['-Wno-format']
endif
endif
@@ -80,8 +45,9 @@ sequential_tests = {
'': [],
'_small': ['4'],
},
- 'filesystem': {'': files('../README.md')},
'digest': {'': []},
+ 'environment': {'': []},
+ 'filesystem': {'': files('../README.md')},
'hash': {'': []},
'path': {'': []},
'status': {'': []},
@@ -128,6 +94,7 @@ foreach test, cases : sequential_tests
sources,
c_args: c_suppressions + program_c_args + test_suppressions,
dependencies: [zix_dep],
+ implicit_include_directories: false,
include_directories: include_dirs,
link_args: program_link_args,
)
@@ -147,6 +114,7 @@ if thread_dep.found()
sources,
c_args: c_suppressions + program_c_args + test_suppressions,
dependencies: [zix_dep, thread_dep],
+ implicit_include_directories: false,
include_directories: include_dirs,
link_args: program_link_args,
)
@@ -171,26 +139,23 @@ foreach test, cases : bad_tests
endforeach
endforeach
-# Test that headers have no warnings (ignoring the usual suppressions)
+# Test that headers have (almost) no warnings
if cc.get_id() != 'emscripten'
header_suppressions = []
- if cc.get_id() in ['clang', 'emscripten']
+ if cc.get_id() == 'clang'
header_suppressions += [
'-Wno-declaration-after-statement',
'-Wno-nullability-extension',
'-Wno-padded',
+ '-Wno-pre-c23-compat',
]
if not meson.is_cross_build()
- header_suppressions += [
- '-Wno-poison-system-directories',
- ]
+ header_suppressions += ['-Wno-poison-system-directories']
endif
if host_machine.system() == 'windows'
- header_suppressions += [
- '-Wno-nonportable-system-include-path',
- ]
+ header_suppressions += ['-Wno-nonportable-system-include-path']
endif
elif cc.get_id() == 'gcc'
@@ -209,6 +174,8 @@ if cc.get_id() != 'emscripten'
]
endif
+ header_suppressions = cc.get_supported_arguments(header_suppressions)
+
test(
'headers',
executable(
@@ -216,6 +183,7 @@ if cc.get_id() != 'emscripten'
files('headers/test_headers.c'),
c_args: header_suppressions + program_c_args,
dependencies: zix_dep,
+ implicit_include_directories: false,
include_directories: include_dirs,
),
suite: 'build',
@@ -233,19 +201,16 @@ if add_languages(['cpp'], native: false, required: get_option('tests_cpp'))
'-Wno-c++98-compat-pedantic',
'-Wno-nullability-extension',
'-Wno-padded',
+ '-Wno-unsafe-buffer-usage-in-libc-call',
'-Wno-zero-as-null-pointer-constant',
]
if not meson.is_cross_build()
- cpp_test_args += [
- '-Wno-poison-system-directories',
- ]
+ cpp_test_args += ['-Wno-poison-system-directories']
endif
if host_machine.system() == 'windows'
- cpp_test_args += [
- '-Wno-nonportable-system-include-path',
- ]
+ cpp_test_args += ['-Wno-nonportable-system-include-path']
endif
elif cpp.get_id() == 'gcc'
@@ -267,11 +232,14 @@ if add_languages(['cpp'], native: false, required: get_option('tests_cpp'))
'/wd4711', # function selected for automatic inline expansion
'/wd4820', # padding added after construct
'/wd5039', # throwing function passed to C (winbase.h)
+ '/wd5045', # will insert Spectre mitigation for memory load
'/wd5262', # implicit fall-through
'/wd5264', # const variable is not used
]
endif
+ cpp_test_args = cpp.get_supported_arguments(cpp_test_args)
+
test(
'headers_cpp',
executable(
@@ -279,6 +247,7 @@ if add_languages(['cpp'], native: false, required: get_option('tests_cpp'))
files('cpp/test_headers_cpp.cpp'),
cpp_args: cpp_test_args + program_c_args,
dependencies: [zix_dep],
+ implicit_include_directories: false,
include_directories: include_dirs,
link_args: program_link_args,
),
@@ -296,6 +265,7 @@ int main(void) { return 0; }'''
files('cpp/test_path_std.cpp'),
cpp_args: cpp_test_args + program_c_args,
dependencies: [zix_dep],
+ implicit_include_directories: false,
include_directories: include_dirs,
link_args: program_link_args,
),
diff --git a/test/test_allocator.c b/test/test_allocator.c
index 9ecbfa0..e9be7ea 100644
--- a/test/test_allocator.c
+++ b/test/test_allocator.c
@@ -5,8 +5,8 @@
#include "failing_allocator.h"
-#include "zix/allocator.h"
-#include "zix/bump_allocator.h"
+#include <zix/allocator.h>
+#include <zix/bump_allocator.h>
#include <assert.h>
#include <stddef.h>
@@ -122,7 +122,7 @@ static void
test_failing_allocator(void)
{
ZixFailingAllocator allocator = zix_failing_allocator();
- allocator.n_remaining = 0;
+ zix_failing_allocator_reset(&allocator, 0);
assert(!zix_malloc(&allocator.base, 16U));
assert(!zix_calloc(&allocator.base, 16U, 1U));
diff --git a/test/test_btree.c b/test/test_btree.c
index e918799..5e41399 100644
--- a/test/test_btree.c
+++ b/test/test_btree.c
@@ -3,16 +3,15 @@
#undef NDEBUG
-#include "zix/btree.h"
-
#include "ensure.h"
#include "failing_allocator.h"
#include "test_args.h"
#include "test_data.h"
-#include "zix/allocator.h"
-#include "zix/attributes.h"
-#include "zix/status.h"
+#include <zix/allocator.h>
+#include <zix/attributes.h>
+#include <zix/btree.h>
+#include <zix/status.h>
#include <assert.h>
#include <inttypes.h>
@@ -20,8 +19,7 @@
#include <stdio.h>
#include <stdlib.h>
-ZIX_PURE_FUNC
-static int
+ZIX_PURE_FUNC static int
int_cmp(const void* a, const void* b, const void* ZIX_UNUSED(user_data))
{
const uintptr_t ia = (uintptr_t)a;
@@ -103,6 +101,23 @@ destroy(void* const ptr, const void* const user_data)
}
static void
+test_empty(void)
+{
+ ZixBTree* const t = zix_btree_new(NULL, int_cmp, NULL);
+ assert(t);
+
+ // Check that reading functions work properly with an empty (rootless) tree
+ const int e = 42;
+ ZixBTreeIter ti = zix_btree_end(t);
+ zix_btree_clear(t, NULL, NULL);
+ assert(!zix_btree_size(t));
+ assert(zix_btree_find(t, &e, &ti) == ZIX_STATUS_NOT_FOUND);
+ assert(!zix_btree_lower_bound(t, int_cmp, NULL, &e, &ti));
+
+ zix_btree_free(t, destroy, NULL);
+}
+
+static void
test_clear(void)
{
ZixBTree* t = zix_btree_new(NULL, int_cmp, NULL);
@@ -213,6 +228,15 @@ test_remove_cases(void)
ZixBTree* const t = zix_btree_new(NULL, int_cmp, NULL);
+ {
+ // Try to remove from an emptry tree
+ ZixBTreeIter next = zix_btree_end(t);
+ const uintptr_t value = 42U;
+ void* out = NULL;
+ assert(zix_btree_remove(t, (void*)value, &out, &next) ==
+ ZIX_STATUS_NOT_FOUND);
+ }
+
// Insert in s1-sized chunks
for (uintptr_t phase = 0U; phase < s1; ++phase) {
for (uintptr_t r = 0U; r < n_insertions / s1; ++r) {
@@ -419,7 +443,7 @@ stress(ZixAllocator* const allocator,
uintptr_t removed = 0;
ENSUREV(t,
zix_btree_remove(t, (void*)r, (void**)&removed, &next),
- "Removal of non-existant %" PRIuPTR " succeeded\n",
+ "Removal of non-existent %" PRIuPTR " succeeded\n",
r);
}
@@ -563,9 +587,9 @@ test_failed_alloc(void)
assert(!stress(&allocator.base, 0, 4096));
// Test that each allocation failing is handled gracefully
- const size_t n_new_allocs = allocator.n_allocations;
+ const size_t n_new_allocs = zix_failing_allocator_reset(&allocator, 0);
for (size_t i = 0U; i < n_new_allocs; ++i) {
- allocator.n_remaining = i;
+ zix_failing_allocator_reset(&allocator, i);
assert(stress(&allocator.base, 0, 4096));
}
}
@@ -578,6 +602,7 @@ main(int argc, char** argv)
return EXIT_FAILURE;
}
+ test_empty();
test_clear();
test_free();
test_iter_comparison();
diff --git a/test/test_digest.c b/test/test_digest.c
index 7228f86..65a0e41 100644
--- a/test/test_digest.c
+++ b/test/test_digest.c
@@ -3,8 +3,8 @@
#undef NDEBUG
-#include "zix/attributes.h"
-#include "zix/digest.h"
+#include <zix/attributes.h>
+#include <zix/digest.h>
#include <assert.h>
#include <stddef.h>
@@ -116,8 +116,7 @@ test_digest_aligned(void)
}
}
-ZIX_PURE_FUNC
-int
+ZIX_PURE_FUNC int
main(void)
{
test_digest32();
diff --git a/test/test_environment.c b/test/test_environment.c
new file mode 100644
index 0000000..8756441
--- /dev/null
+++ b/test/test_environment.c
@@ -0,0 +1,143 @@
+// Copyright 2012-2024 David Robillard <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#undef NDEBUG
+
+#include "failing_allocator.h"
+
+#include <zix/allocator.h>
+#include <zix/environment.h>
+#include <zix/path.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef _WIN32
+
+# define HOME_NAME "USERPROFILE"
+# define HOME_VAR "%USERPROFILE%"
+
+#else
+
+# define HOME_NAME "HOME"
+# define HOME_VAR "$HOME"
+
+static char*
+concat(ZixAllocator* const allocator,
+ const char* const prefix,
+ const char* const suffix)
+{
+ const size_t prefix_len = strlen(prefix);
+ const size_t suffix_len = strlen(suffix);
+ const size_t result_len = prefix_len + suffix_len;
+
+ char* const result = (char*)zix_calloc(allocator, 1U, result_len + 1U);
+ assert(result);
+ memcpy(result, prefix, prefix_len + 1U);
+ memcpy(result + prefix_len, suffix, suffix_len + 1U);
+ return result;
+}
+
+#endif
+
+static void
+check_expansion(const char* const path, const char* const expected)
+{
+ char* const expanded = zix_expand_environment_strings(NULL, path);
+ assert(expanded);
+ assert(!strcmp(expanded, expected));
+ zix_free(NULL, expanded);
+}
+
+static void
+test_expansion(void)
+{
+ // Check non-expansion of hopefully unset variables
+ check_expansion("$ZIX_UNSET0", "$ZIX_UNSET0");
+ check_expansion("$ZIX_unset0", "$ZIX_unset0");
+ check_expansion("%ZIX_UNSET0%", "%ZIX_UNSET0%");
+ check_expansion("%ZIX_unset0%", "%ZIX_unset0%");
+
+ // Check non-expansion of invalid variable names
+ check_expansion("$%INVALID", "$%INVALID");
+ check_expansion("$<INVALID>", "$<INVALID>");
+ check_expansion("$[INVALID]", "$[INVALID]");
+ check_expansion("$invalid", "$invalid");
+ check_expansion("${INVALID}", "${INVALID}");
+
+ const char* const home = getenv(HOME_NAME);
+ if (home) {
+ char* const var_foo = zix_path_join(NULL, HOME_VAR, "foo");
+ char* const home_foo = zix_path_join(NULL, home, "foo");
+
+ check_expansion(var_foo, home_foo);
+
+#ifndef _WIN32
+ char* const tilde_foo = zix_path_join(NULL, "~", "foo");
+ char* const home_and_other = concat(NULL, home, ":/other");
+ char* const other_and_home = concat(NULL, "/other:", home);
+ check_expansion("~other", "~other");
+ check_expansion("~", home);
+ check_expansion("~/foo", home_foo);
+ check_expansion("~:/other", home_and_other);
+ check_expansion("/other:~", other_and_home);
+ check_expansion("$HO", "$HO");
+ check_expansion("$HOMEZIX", "$HOMEZIX");
+ zix_free(NULL, other_and_home);
+ zix_free(NULL, home_and_other);
+ zix_free(NULL, tilde_foo);
+#endif
+
+ zix_free(NULL, home_foo);
+ zix_free(NULL, var_foo);
+ }
+}
+
+static void
+test_failed_alloc(void)
+{
+ ZixFailingAllocator allocator = zix_failing_allocator();
+
+ zix_failing_allocator_reset(&allocator, 0U);
+ assert(!zix_expand_environment_strings(&allocator.base, "/one:~"));
+ assert(!zix_expand_environment_strings(&allocator.base, "/only"));
+ assert(!zix_expand_environment_strings(&allocator.base, "~"));
+
+#ifndef _WIN32
+ zix_failing_allocator_reset(&allocator, 1U);
+ assert(!zix_expand_environment_strings(&allocator.base, "/one:~"));
+ assert(!zix_expand_environment_strings(&allocator.base, "/one:$HOME/two"));
+
+ zix_failing_allocator_reset(&allocator, 2U);
+ assert(!zix_expand_environment_strings(&allocator.base, "/one:$HOME/two"));
+
+ zix_failing_allocator_reset(&allocator, 1U);
+ assert(!zix_expand_environment_strings(&allocator.base, "/one:$UNSET/two"));
+#endif
+}
+
+#ifndef _WIN32
+
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+extern char** environ;
+
+static void
+test_null_environ(void)
+{
+ environ = NULL;
+ check_expansion(HOME_VAR, HOME_VAR);
+}
+
+#endif
+
+int
+main(void)
+{
+ test_expansion();
+ test_failed_alloc();
+#ifndef _WIN32
+ test_null_environ();
+#endif
+ return 0;
+}
diff --git a/test/test_filesystem.c b/test/test_filesystem.c
index 87a3d15..7c4dc67 100644
--- a/test/test_filesystem.c
+++ b/test/test_filesystem.c
@@ -1,13 +1,15 @@
-// Copyright 2020-2023 David Robillard <d@drobilla.net>
+// Copyright 2020-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#undef NDEBUG
-#include "zix/allocator.h"
-#include "zix/filesystem.h"
-#include "zix/path.h"
-#include "zix/status.h"
-#include "zix/string_view.h"
+#include "failing_allocator.h"
+
+#include <zix/allocator.h>
+#include <zix/filesystem.h>
+#include <zix/path.h>
+#include <zix/status.h>
+#include <zix/string_view.h>
#ifndef _WIN32
# include <unistd.h>
@@ -21,6 +23,7 @@
#include <assert.h>
#include <errno.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -48,31 +51,44 @@ test_current_path(void)
static char*
create_temp_dir(const char* const name_pattern)
{
- char* const temp = zix_temp_directory_path(NULL);
+ char* const temp = zix_temp_directory_path(NULL);
+ assert(temp);
+
char* const path_pattern = zix_path_join(NULL, temp, name_pattern);
char* const result = zix_create_temporary_directory(NULL, path_pattern);
free(path_pattern);
zix_free(NULL, temp);
+ assert(result);
return result;
}
+static int
+write_to_path(const char* const path, const char* const contents)
+{
+ int ret = -1;
+ FILE* const f = fopen(path, "wb");
+ if (f) {
+ fwrite(contents, 1, strlen(contents), f);
+ ret = fflush(f) ? errno : ferror(f) ? EBADF : 0;
+ ret = (fclose(f) && !ret) ? errno : ret;
+ }
+
+ return ret;
+}
+
static void
test_canonical_path(void)
{
assert(!zix_canonical_path(NULL, NULL));
char* const temp_dir = create_temp_dir("zixXXXXXX");
- assert(temp_dir);
+ char* const sub_dir = zix_path_join(NULL, temp_dir, "sub");
+ assert(!zix_create_directory(sub_dir));
+ assert(zix_file_type(temp_dir) == ZIX_FILE_TYPE_DIRECTORY);
char* const file_path = zix_path_join(NULL, temp_dir, "zix_test_file");
assert(file_path);
-
- {
- FILE* const f = fopen(file_path, "w");
- assert(f);
- fprintf(f, "test\n");
- fclose(f);
- }
+ assert(!write_to_path(file_path, "test\n"));
#ifndef _WIN32
// Test symlink resolution
@@ -96,10 +112,10 @@ test_canonical_path(void)
// Test dot segment resolution
- char* const parent_dir_1 = zix_path_join(NULL, temp_dir, "..");
+ char* const parent_dir_1 = zix_path_join(NULL, sub_dir, "..");
assert(parent_dir_1);
- const ZixStringView parent_view = zix_path_parent_path(temp_dir);
+ const ZixStringView parent_view = zix_path_parent_path(sub_dir);
char* const parent_dir_2 = zix_string_view_copy(NULL, parent_view);
assert(parent_dir_2);
assert(parent_dir_2[0]);
@@ -120,6 +136,7 @@ test_canonical_path(void)
// Clean everything up
assert(!zix_remove(file_path));
+ assert(!zix_remove(sub_dir));
assert(!zix_remove(temp_dir));
free(real_parent_dir_2);
@@ -127,24 +144,23 @@ test_canonical_path(void)
free(parent_dir_2);
free(parent_dir_1);
free(file_path);
+ free(sub_dir);
free(temp_dir);
}
static void
test_file_type(void)
{
- char* const temp_dir = create_temp_dir("zixXXXXXX");
- assert(temp_dir);
-
+ char* const temp_dir = create_temp_dir("zixXXXXXX");
char* const file_path = zix_path_join(NULL, temp_dir, "zix_test_file");
assert(file_path);
assert(zix_file_type(file_path) == ZIX_FILE_TYPE_NONE);
+ // Directory
+ assert(zix_file_type(temp_dir) == ZIX_FILE_TYPE_DIRECTORY);
+
// Regular file
- FILE* f = fopen(file_path, "w");
- assert(f);
- fprintf(f, "test\n");
- fclose(f);
+ assert(!write_to_path(file_path, "test\n"));
assert(zix_file_type(file_path) == ZIX_FILE_TYPE_REGULAR);
assert(!zix_remove(file_path));
@@ -161,6 +177,7 @@ test_file_type(void)
if (sock >= 0) {
const socklen_t addr_len = sizeof(struct sockaddr_un);
struct sockaddr_un* const addr = (struct sockaddr_un*)calloc(1, addr_len);
+ assert(addr);
if (strlen(file_path) < sizeof(addr->sun_path)) {
addr->sun_family = AF_UNIX;
@@ -172,9 +189,7 @@ test_file_type(void)
assert(!zix_remove(file_path));
close(fd);
}
- } else {
- fprintf(stderr, "warning: Skipped socket test with oddly long TMPDIR\n");
- }
+ } // otherwise, TMPDIR is oddly long, skip test
close(sock);
free(addr);
@@ -188,85 +203,19 @@ test_file_type(void)
}
static void
-test_path_exists(void)
-{
- char* const temp_dir = create_temp_dir("zixXXXXXX");
- char* const file_path = zix_path_join(NULL, temp_dir, "zix_test_file");
- assert(temp_dir);
- assert(file_path);
-
- assert(zix_file_type(file_path) == ZIX_FILE_TYPE_NONE);
-
- FILE* f = fopen(file_path, "w");
- assert(f);
- fprintf(f, "test\n");
- fclose(f);
-
- assert(zix_file_type(file_path) == ZIX_FILE_TYPE_REGULAR);
-
- assert(!zix_remove(file_path));
- assert(!zix_remove(temp_dir));
-
- free(file_path);
- free(temp_dir);
-}
-
-static void
-test_is_directory(void)
-{
- char* const temp_dir = create_temp_dir("zixXXXXXX");
- char* const file_path = zix_path_join(NULL, temp_dir, "zix_test_file");
- assert(temp_dir);
- assert(file_path);
-
- assert(zix_file_type(temp_dir) == ZIX_FILE_TYPE_DIRECTORY);
- assert(zix_file_type(file_path) == ZIX_FILE_TYPE_NONE);
-
- FILE* f = fopen(file_path, "w");
- assert(f);
- fprintf(f, "test\n");
- fclose(f);
-
- assert(zix_file_type(file_path) == ZIX_FILE_TYPE_REGULAR);
-
- assert(!zix_remove(file_path));
- assert(!zix_remove(temp_dir));
-
- free(file_path);
- free(temp_dir);
-}
-
-static int
-write_to_path(const char* const path, const char* const contents)
-{
- int ret = -1;
- FILE* const f = fopen(path, "w");
- if (f) {
- const size_t len = strlen(contents);
- fwrite(contents, 1, len, f);
-
- ret = fflush(f) ? errno : ferror(f) ? EBADF : fclose(f) ? errno : 0;
- }
-
- return ret;
-}
-
-static void
test_copy_file(const char* data_file_path)
{
char* const temp_dir = create_temp_dir("zixXXXXXX");
char* const tmp_file_path = zix_path_join(NULL, temp_dir, "zix_test_file");
char* const copy_path = zix_path_join(NULL, temp_dir, "zix_test_copy");
- assert(temp_dir);
assert(tmp_file_path);
assert(copy_path);
- if (!data_file_path) {
- data_file_path = tmp_file_path;
- }
+ data_file_path = data_file_path ? data_file_path : tmp_file_path;
assert(!write_to_path(tmp_file_path, "test\n"));
+ // Fail to copy to/from a nonexistent file
assert(zix_copy_file(NULL, tmp_file_path, "/does/not/exist", 0U));
assert(zix_copy_file(NULL, "/does/not/exist", copy_path, 0U));
assert(zix_file_type(copy_path) == ZIX_FILE_TYPE_NONE);
@@ -329,7 +278,7 @@ test_copy_file(const char* data_file_path)
NULL, tmp_file_path, "/dev/full", ZIX_COPY_OPTION_OVERWRITE_EXISTING));
// Copy long file (error during writing)
- FILE* const f = fopen(tmp_file_path, "w");
+ FILE* const f = fopen(tmp_file_path, "wb");
assert(f);
for (size_t i = 0; i < 4096; ++i) {
fprintf(f, "test\n");
@@ -352,11 +301,10 @@ test_flock(void)
{
char* const temp_dir = create_temp_dir("zixXXXXXX");
char* const file_path = zix_path_join(NULL, temp_dir, "zix_test_file");
- assert(temp_dir);
assert(file_path);
- FILE* const f1 = fopen(file_path, "w");
- FILE* const f2 = fopen(file_path, "a+");
+ FILE* const f1 = fopen(file_path, "wb");
+ FILE* const f2 = fopen(file_path, "a+b");
assert(f1);
assert(f2);
@@ -395,6 +343,7 @@ visit(const char* const path, const char* const name, void* const data)
if (new_names) {
char* const name_copy = (char*)calloc(name_len + 1, 1);
+ assert(name_copy);
memcpy(name_copy, name, name_len + 1);
file_list->names = new_names;
@@ -408,18 +357,11 @@ test_dir_for_each(void)
char* const temp_dir = create_temp_dir("zixXXXXXX");
char* const path1 = zix_path_join(NULL, temp_dir, "zix_test_1");
char* const path2 = zix_path_join(NULL, temp_dir, "zix_test_2");
- assert(temp_dir);
assert(path1);
assert(path2);
- FILE* const f1 = fopen(path1, "w");
- FILE* const f2 = fopen(path2, "w");
- assert(f1);
- assert(f2);
- fprintf(f1, "test\n");
- fprintf(f2, "test\n");
- fclose(f2);
- fclose(f1);
+ assert(!write_to_path(path1, "test\n"));
+ assert(!write_to_path(path2, "test\n"));
FileList file_list = {0, NULL};
zix_dir_for_each(temp_dir, &file_list, visit);
@@ -448,15 +390,12 @@ test_create_temporary_directory(void)
assert(!zix_create_temporary_directory(NULL, ""));
char* const path1 = create_temp_dir("zixXXXXXX");
-
assert(path1);
assert(zix_file_type(path1) == ZIX_FILE_TYPE_DIRECTORY);
char* const path2 = create_temp_dir("zixXXXXXX");
-
assert(path2);
assert(strcmp(path1, path2));
- assert(zix_file_type(path1) == ZIX_FILE_TYPE_DIRECTORY);
assert(zix_file_type(path2) == ZIX_FILE_TYPE_DIRECTORY);
assert(!zix_remove(path2));
@@ -469,7 +408,6 @@ static void
test_create_directory_like(void)
{
char* const temp_dir = create_temp_dir("zixXXXXXX");
- assert(temp_dir);
assert(zix_file_type(temp_dir) == ZIX_FILE_TYPE_DIRECTORY);
char* const sub_dir = zix_path_join(NULL, temp_dir, "sub");
@@ -487,24 +425,27 @@ static void
test_create_directories(void)
{
char* const temp_dir = create_temp_dir("zixXXXXXX");
-
assert(temp_dir);
assert(zix_file_type(temp_dir) == ZIX_FILE_TYPE_DIRECTORY);
+
assert(zix_create_directories(NULL, "") == ZIX_STATUS_BAD_ARG);
char* const child_dir = zix_path_join(NULL, temp_dir, "child");
char* const grandchild_dir = zix_path_join(NULL, child_dir, "grandchild");
+ {
+ ZixFailingAllocator bad_allocator = zix_failing_allocator();
+ zix_failing_allocator_reset(&bad_allocator, 0U);
+ assert(zix_create_directories(&bad_allocator.base, grandchild_dir) ==
+ ZIX_STATUS_NO_MEM);
+ }
+
assert(!zix_create_directories(NULL, grandchild_dir));
assert(zix_file_type(grandchild_dir) == ZIX_FILE_TYPE_DIRECTORY);
assert(zix_file_type(child_dir) == ZIX_FILE_TYPE_DIRECTORY);
char* const file_path = zix_path_join(NULL, temp_dir, "zix_test_file");
- FILE* const f = fopen(file_path, "w");
-
- assert(f);
- fprintf(f, "test\n");
- fclose(f);
+ assert(!write_to_path(file_path, "test\n"));
assert(zix_create_directories(NULL, file_path) == ZIX_STATUS_EXISTS);
@@ -518,37 +459,61 @@ test_create_directories(void)
free(temp_dir);
}
+static bool
+check_file_equals(const char* const path1, const char* const path2)
+{
+ const bool r1 = zix_file_equals(NULL, path1, path2);
+ const bool r2 = zix_file_equals(NULL, path2, path1);
+ assert(r2 == r1);
+
+ ZixFailingAllocator bad_allocator = zix_failing_allocator();
+ zix_failing_allocator_reset(&bad_allocator, 0U);
+
+ const bool r3 = zix_file_equals(&bad_allocator.base, path1, path2);
+ const bool r4 = zix_file_equals(&bad_allocator.base, path2, path1);
+ assert(r3 == r1);
+ assert(r4 == r1);
+
+ return r1;
+}
+
static void
-test_file_equals(void)
+test_file_equals(const char* const data_file_path)
{
char* const temp_dir = create_temp_dir("zixXXXXXX");
char* const path1 = zix_path_join(NULL, temp_dir, "zix1");
char* const path2 = zix_path_join(NULL, temp_dir, "zix2");
- assert(temp_dir);
// Equal: test, test
assert(!write_to_path(path1, "test"));
assert(!write_to_path(path2, "test"));
- assert(zix_file_equals(NULL, path1, path2));
+ assert(check_file_equals(path1, path2));
- // Missing files
- assert(!zix_file_equals(NULL, path1, "/does/not/exist"));
- assert(!zix_file_equals(NULL, "/does/not/exist", path2));
+ // Missing file
+ assert(!check_file_equals(path1, "/does/not/exist"));
// Longer RHS: test, testdiff
assert(!write_to_path(path2, "diff"));
- assert(!zix_file_equals(NULL, path1, path2));
+ assert(!check_file_equals(path1, path2));
// Longer LHS: testdifflong, testdiff
assert(!write_to_path(path1, "difflong"));
- assert(!zix_file_equals(NULL, path1, path2));
+ assert(!check_file_equals(path1, path2));
// Equal sizes but different content: testdifflong, testdifflang
assert(!write_to_path(path2, "difflang"));
- assert(!zix_file_equals(NULL, path1, path2));
+ assert(!check_file_equals(path1, path2));
- assert(zix_file_equals(NULL, path1, path1));
- assert(zix_file_equals(NULL, path2, path2));
+ // Same path
+ assert(check_file_equals(path1, path1));
+
+ // Different devices
+ if (data_file_path) {
+ assert(!check_file_equals(data_file_path, path2));
+ assert(!zix_remove(path2));
+ assert(!zix_copy_file(NULL, data_file_path, path2, 0U));
+ assert(check_file_equals(data_file_path, path2));
+ }
assert(!zix_remove(path2));
assert(!zix_remove(path1));
@@ -562,17 +527,22 @@ test_file_equals(void)
static void
test_file_size(void)
{
- static const char* const contents = "file size test";
+ static const ZixStringView contents = ZIX_STATIC_STRING("file size test");
char* const temp_dir = create_temp_dir("zixXXXXXX");
char* const path = zix_path_join(NULL, temp_dir, "zix_test");
- assert(temp_dir);
- assert(!write_to_path(path, contents));
+ // Nonexistent files
+ assert(!zix_file_size(path));
+ assert(!zix_file_size("/does/not/exist"));
- const ZixFileOffset size = zix_file_size(path);
- assert(size > 0);
- assert((size_t)size == strlen(contents));
+ // Empty file
+ assert(!write_to_path(path, ""));
+ assert(!zix_file_size(path));
+
+ // Non-empty file
+ assert(!write_to_path(path, contents.data));
+ assert((size_t)zix_file_size(path) == contents.length);
assert(!zix_remove(path));
assert(!zix_remove(temp_dir));
@@ -587,16 +557,10 @@ test_create_symlink(void)
static const char* const contents = "zixtest";
char* const temp_dir = create_temp_dir("zixXXXXXX");
- assert(temp_dir);
// Write contents to original file
char* const file_path = zix_path_join(NULL, temp_dir, "zix_test_file");
- {
- FILE* const f = fopen(file_path, "w");
- assert(f);
- fprintf(f, "%s", contents);
- fclose(f);
- }
+ assert(!write_to_path(file_path, contents));
// Ensure original file exists and is a regular file
assert(zix_symlink_type(file_path) == ZIX_FILE_TYPE_REGULAR);
@@ -626,9 +590,7 @@ test_create_symlink(void)
static void
test_create_directory_symlink(void)
{
- char* const temp_dir = create_temp_dir("zixXXXXXX");
- assert(temp_dir);
-
+ char* const temp_dir = create_temp_dir("zixXXXXXX");
char* const link_path = zix_path_join(NULL, temp_dir, "zix_test_link");
const ZixStatus st = zix_create_directory_symlink(temp_dir, link_path);
@@ -656,16 +618,10 @@ test_create_hard_link(void)
static const char* const contents = "zixtest";
char* const temp_dir = create_temp_dir("zixXXXXXX");
- assert(temp_dir);
// Write contents to original file
char* const file_path = zix_path_join(NULL, temp_dir, "zix_test_file");
- {
- FILE* const f = fopen(file_path, "w");
- assert(f);
- fprintf(f, "%s", contents);
- fclose(f);
- }
+ assert(!write_to_path(file_path, contents));
// Ensure original file exists and is a regular file
assert(zix_symlink_type(file_path) == ZIX_FILE_TYPE_REGULAR);
@@ -695,24 +651,23 @@ int
main(const int argc, char** const argv)
{
// Try to find some existing data file that's ideally not on a tmpfs
- const char* data_file_path = (argc > 1) ? argv[1] : "build.ninja";
- if (zix_file_type(data_file_path) != ZIX_FILE_TYPE_REGULAR) {
- data_file_path = NULL;
- }
+ const char* const default_file_path = (argc > 1) ? argv[1] : "build.ninja";
+ const char* const data_file_path =
+ (zix_file_type(default_file_path) == ZIX_FILE_TYPE_REGULAR)
+ ? default_file_path
+ : NULL;
test_temp_directory_path();
test_current_path();
test_canonical_path();
test_file_type();
- test_path_exists();
- test_is_directory();
test_copy_file(data_file_path);
test_flock();
test_dir_for_each();
test_create_temporary_directory();
test_create_directory_like();
test_create_directories();
- test_file_equals();
+ test_file_equals(data_file_path);
test_file_size();
test_create_symlink();
test_create_directory_symlink();
diff --git a/test/test_hash.c b/test/test_hash.c
index 3d3ca95..2ff286c 100644
--- a/test/test_hash.c
+++ b/test/test_hash.c
@@ -10,11 +10,11 @@
#include "failing_allocator.h"
#include "test_data.h"
-#include "zix/allocator.h"
-#include "zix/attributes.h"
-#include "zix/digest.h"
-#include "zix/hash.h"
-#include "zix/status.h"
+#include <zix/allocator.h>
+#include <zix/attributes.h>
+#include <zix/digest.h>
+#include <zix/hash.h>
+#include <zix/status.h>
#include <assert.h>
#include <inttypes.h>
@@ -47,24 +47,21 @@ test_fail(TestState* const state, const char* fmt, ...)
return EXIT_FAILURE;
}
-ZIX_PURE_FUNC
-static const char*
+ZIX_PURE_FUNC static const char*
identity(const char* record)
{
return record;
}
/// Decent hash function using zix_digest (murmur2)
-ZIX_PURE_FUNC
-static size_t
+ZIX_PURE_FUNC static size_t
decent_string_hash(const char* const str)
{
return zix_digest(0U, str, strlen(str));
}
/// Terrible hash function from K&R first edition
-ZIX_PURE_FUNC
-static size_t
+ZIX_PURE_FUNC static size_t
terrible_string_hash(const char* str)
{
size_t hash = 0U;
@@ -77,8 +74,7 @@ terrible_string_hash(const char* str)
return hash;
}
-ZIX_PURE_FUNC
-static size_t
+ZIX_PURE_FUNC static size_t
string_hash_aligned(const char* const str)
{
size_t length = strlen(str);
@@ -87,22 +83,19 @@ string_hash_aligned(const char* const str)
return zix_digest_aligned(0U, str, length);
}
-ZIX_PURE_FUNC
-static size_t
+ZIX_PURE_FUNC static size_t
string_hash32(const char* const str)
{
return (size_t)zix_digest32(0U, str, strlen(str));
}
-ZIX_PURE_FUNC
-static size_t
+ZIX_PURE_FUNC static size_t
string_hash64(const char* const str)
{
return (size_t)zix_digest64(0U, str, strlen(str));
}
-ZIX_PURE_FUNC
-static size_t
+ZIX_PURE_FUNC static size_t
string_hash32_aligned(const char* const str)
{
size_t length = strlen(str);
@@ -113,8 +106,7 @@ string_hash32_aligned(const char* const str)
#if UINTPTR_MAX >= UINT64_MAX
-ZIX_PURE_FUNC
-static size_t
+ZIX_PURE_FUNC static size_t
string_hash64_aligned(const char* const str)
{
size_t length = strlen(str);
@@ -201,8 +193,8 @@ stress_with(ZixAllocator* const allocator,
if (not_indexed) {
memcpy(not_indexed, not_indexed_string, strlen(not_indexed_string) + 1);
const char* match = (const char*)zix_hash_find_record(hash, not_indexed);
- ENSUREV(&state, !match, "Unexpectedly found `%s'\n", not_indexed);
free(not_indexed);
+ ENSUREV(&state, !match, "Unexpectedly found `%s'\n", not_indexed_string);
}
// Remove strings
@@ -298,8 +290,7 @@ stress(ZixAllocator* const allocator, const size_t n_elems)
}
/// Identity hash function for numeric strings for explicitly hitting cases
-ZIX_PURE_FUNC
-static size_t
+ZIX_PURE_FUNC static size_t
identity_index_hash(const char* const str)
{
return strtoul(str, NULL, 10);
@@ -366,9 +357,9 @@ test_failed_alloc(void)
assert(!stress(&allocator.base, 16));
// Test that each allocation failing is handled gracefully
- const size_t n_new_allocs = allocator.n_allocations;
+ const size_t n_new_allocs = zix_failing_allocator_reset(&allocator, 0);
for (size_t i = 0U; i < n_new_allocs; ++i) {
- allocator.n_remaining = i;
+ zix_failing_allocator_reset(&allocator, i);
assert(stress(&allocator.base, 16));
}
}
diff --git a/test/test_path.c b/test/test_path.c
index 5c98c32..f44c804 100644
--- a/test/test_path.c
+++ b/test/test_path.c
@@ -1,17 +1,20 @@
-// Copyright 2020-2022 David Robillard <d@drobilla.net>
+// Copyright 2020-2025 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#undef NDEBUG
-#include "zix/path.h"
-#include "zix/string_view.h"
+#include "failing_allocator.h"
+
+#include <zix/allocator.h>
+#include <zix/path.h>
+#include <zix/string_view.h>
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
-/// Abort if `string` doesn't equal `expected`
+/// Return whether `string` equals `expected`
static bool
equal(char* const string, const char* const expected)
{
@@ -22,7 +25,7 @@ equal(char* const string, const char* const expected)
return result;
}
-/// Abort if `string` doesn't equal `expected` with preferred separators
+/// Return whether `string` matches `expected` with preferred separators
static bool
match(char* const string, const char* const expected)
{
@@ -37,7 +40,7 @@ match(char* const string, const char* const expected)
return result;
}
-/// Abort if `view` doesn't equal `expected`
+/// Return whether `view` equals `expected`
static bool
view_equal(const ZixStringView view, const char* const expected)
{
@@ -338,105 +341,96 @@ test_path_lexically_normal(void)
#endif
}
+static char*
+lexically_relative(const char* const path, const char* const base)
+{
+ return zix_path_lexically_relative(NULL, path, base);
+}
+
static void
test_path_lexically_relative(void)
{
- assert(equal(zix_path_lexically_relative(NULL, "", ""), "."));
- assert(equal(zix_path_lexically_relative(NULL, "", "."), "."));
- assert(equal(zix_path_lexically_relative(NULL, ".", ""), "."));
- assert(equal(zix_path_lexically_relative(NULL, ".", "."), "."));
- assert(equal(zix_path_lexically_relative(NULL, "//base", "a"), NULL));
- assert(equal(zix_path_lexically_relative(NULL, "//host", "//host"), "."));
- assert(equal(zix_path_lexically_relative(NULL, "//host", "a"), NULL));
- assert(equal(zix_path_lexically_relative(NULL, "//host/", "//host/"), "."));
- assert(equal(zix_path_lexically_relative(NULL, "//host/", "a"), NULL));
- assert(
- equal(zix_path_lexically_relative(NULL, "//host/a/b", "//host/a/b"), "."));
- assert(equal(zix_path_lexically_relative(NULL, "/a/b", "/a/"), "b"));
- assert(equal(zix_path_lexically_relative(NULL, "C:/a/b", "C:/a/"), "b"));
- assert(equal(zix_path_lexically_relative(NULL, "a", "/"), NULL));
- assert(equal(zix_path_lexically_relative(NULL, "a", "//host"), NULL));
- assert(equal(zix_path_lexically_relative(NULL, "a", "//host/"), NULL));
- assert(equal(zix_path_lexically_relative(NULL, "a", "a"), "."));
- assert(equal(zix_path_lexically_relative(NULL, "a/b", "/a/b"), NULL));
- assert(equal(zix_path_lexically_relative(NULL, "a/b", "a/b"), "."));
- assert(equal(zix_path_lexically_relative(NULL, "a/b/c", "a"), "b/c"));
- assert(equal(zix_path_lexically_relative(NULL, "a/b/c", "a/b/c"), "."));
- assert(equal(zix_path_lexically_relative(NULL, "a/b/c/", "a/b/c/"), "."));
- assert(match(zix_path_lexically_relative(NULL, "../", "../"), "."));
- assert(match(zix_path_lexically_relative(NULL, "../", "./"), "../"));
- assert(match(zix_path_lexically_relative(NULL, "../", "a"), "../../"));
- assert(
- match(zix_path_lexically_relative(NULL, "../../a", "../b"), "../../a"));
- assert(match(zix_path_lexically_relative(NULL, "../a", "../b"), "../a"));
- assert(match(zix_path_lexically_relative(NULL, "/a", "/b/c/"), "../../a"));
- assert(match(zix_path_lexically_relative(NULL, "/a/b/c", "/a/b/d/"), "../c"));
- assert(
- match(zix_path_lexically_relative(NULL, "/a/b/c", "/a/b/d/e/"), "../../c"));
- assert(match(zix_path_lexically_relative(NULL, "/a/b/c", "/a/d"), "../b/c"));
- assert(match(zix_path_lexically_relative(NULL, "/a/d", "/a/b/c"), "../../d"));
- assert(
- match(zix_path_lexically_relative(NULL, "C:/D/", "C:/D/F.txt"), "../"));
- assert(match(zix_path_lexically_relative(NULL, "C:/D/F", "C:/D/S/"), "../F"));
- assert(match(zix_path_lexically_relative(NULL, "C:/D/F", "C:/G"), "../D/F"));
- assert(
- match(zix_path_lexically_relative(NULL, "C:/D/F.txt", "C:/D/"), "F.txt"));
- assert(match(zix_path_lexically_relative(NULL, "C:/E", "C:/D/G"), "../../E"));
- assert(
- match(zix_path_lexically_relative(NULL, "C:/a", "C:/b/c/"), "../../a"));
- assert(
- match(zix_path_lexically_relative(NULL, "C:/a/b/c", "C:/a/b/d/"), "../c"));
- assert(match(zix_path_lexically_relative(NULL, "a/b", "c/d"), "../../a/b"));
- assert(match(zix_path_lexically_relative(NULL, "a/b/c", "../"), NULL));
- assert(match(zix_path_lexically_relative(NULL, "a/b/c", "../../"), NULL));
- assert(
- match(zix_path_lexically_relative(NULL, "a/b/c", "a/b/c/x/y"), "../.."));
+ assert(equal(lexically_relative("", ""), "."));
+ assert(equal(lexically_relative("", "."), "."));
+ assert(equal(lexically_relative(".", ""), "."));
+ assert(equal(lexically_relative(".", "."), "."));
+ assert(equal(lexically_relative("//base", "a"), NULL));
+ assert(equal(lexically_relative("//host", "//host"), "."));
+ assert(equal(lexically_relative("//host", "a"), NULL));
+ assert(equal(lexically_relative("//host/", "//host/"), "."));
+ assert(equal(lexically_relative("//host/", "a"), NULL));
+ assert(equal(lexically_relative("//host/a/b", "//host/a/b"), "."));
+ assert(equal(lexically_relative("/a/b", "/a/"), "b"));
+ assert(equal(lexically_relative("C:/a/b", "C:/a/"), "b"));
+ assert(equal(lexically_relative("a", "/"), NULL));
+ assert(equal(lexically_relative("a", "//host"), NULL));
+ assert(equal(lexically_relative("a", "//host/"), NULL));
+ assert(equal(lexically_relative("a", "a"), "."));
+ assert(equal(lexically_relative("a/b", "/a/b"), NULL));
+ assert(equal(lexically_relative("a/b", "a/b"), "."));
+ assert(equal(lexically_relative("a/b/c", "a"), "b/c"));
+ assert(equal(lexically_relative("a/b/c", "a/b/c"), "."));
+ assert(equal(lexically_relative("a/b/c/", "a/b/c/"), "."));
+ assert(match(lexically_relative("../", "../"), "."));
+ assert(match(lexically_relative("../", "./"), "../"));
+ assert(match(lexically_relative("../", "a"), "../../"));
+ assert(match(lexically_relative("../../a", "../b"), "../../a"));
+ assert(match(lexically_relative("../a", "../b"), "../a"));
+ assert(match(lexically_relative("/a", "/b/c/"), "../../a"));
+ assert(match(lexically_relative("/a/b/c", "/a/b/d/"), "../c"));
+ assert(match(lexically_relative("/a/b/c", "/a/b/d/e/"), "../../c"));
+ assert(match(lexically_relative("/a/b/c", "/a/d"), "../b/c"));
+ assert(match(lexically_relative("/a/d", "/a/b/c"), "../../d"));
+ assert(match(lexically_relative("C:/D/", "C:/D/F.txt"), "../"));
+ assert(match(lexically_relative("C:/D/F", "C:/D/S/"), "../F"));
+ assert(match(lexically_relative("C:/D/F", "C:/G"), "../D/F"));
+ assert(match(lexically_relative("C:/D/F.txt", "C:/D/"), "F.txt"));
+ assert(match(lexically_relative("C:/E", "C:/D/G"), "../../E"));
+ assert(match(lexically_relative("C:/a", "C:/b/c/"), "../../a"));
+ assert(match(lexically_relative("C:/a/b/c", "C:/a/b/d/"), "../c"));
+ assert(match(lexically_relative("a/b", "c/d"), "../../a/b"));
+ assert(match(lexically_relative("a/b/c", "../"), NULL));
+ assert(match(lexically_relative("a/b/c", "../../"), NULL));
+ assert(match(lexically_relative("a/b/c", "a/b/c/x/y"), "../.."));
#ifdef _WIN32
- assert(equal(zix_path_lexically_relative(NULL, "/", "a"), "/"));
- assert(equal(zix_path_lexically_relative(NULL, "//host", "//host/"), NULL));
- assert(equal(zix_path_lexically_relative(NULL, "//host/", "//host"), "/"));
- assert(equal(zix_path_lexically_relative(NULL, "//host/", "/a"), NULL));
- assert(equal(zix_path_lexically_relative(NULL, "/a", "//host"), NULL));
- assert(equal(zix_path_lexically_relative(NULL, "/a", "//host/"), NULL));
- assert(equal(zix_path_lexically_relative(NULL, "C:/D/", "C:F.txt"), NULL));
- assert(equal(zix_path_lexically_relative(NULL, "C:/D/S/", "F.txt"), NULL));
- assert(equal(zix_path_lexically_relative(NULL, "C:F", "C:/D/"), NULL));
- assert(equal(zix_path_lexically_relative(NULL, "C:F", "D:G"), NULL));
- assert(equal(zix_path_lexically_relative(NULL, "F", "C:/D/S/"), NULL));
- assert(match(zix_path_lexically_relative(NULL, "C:../", "C:../"), "."));
- assert(match(zix_path_lexically_relative(NULL, "C:../", "C:./"), "../"));
- assert(match(zix_path_lexically_relative(NULL, "C:../", "C:a"), "../../"));
- assert(
- match(zix_path_lexically_relative(NULL, "C:../../a", "C:../b"), "../../a"));
- assert(match(zix_path_lexically_relative(NULL, "C:../a", "C:../b"), "../a"));
- assert(match(zix_path_lexically_relative(NULL, "C:a/b/c", "C:../"), NULL));
- assert(match(zix_path_lexically_relative(NULL, "C:a/b/c", "C:../../"), NULL));
+ assert(equal(lexically_relative("/", "a"), "/"));
+ assert(equal(lexically_relative("//host", "//host/"), NULL));
+ assert(equal(lexically_relative("//host/", "//host"), "/"));
+ assert(equal(lexically_relative("//host/", "/a"), NULL));
+ assert(equal(lexically_relative("/a", "//host"), NULL));
+ assert(equal(lexically_relative("/a", "//host/"), NULL));
+ assert(equal(lexically_relative("C:/D/", "C:F.txt"), NULL));
+ assert(equal(lexically_relative("C:/D/S/", "F.txt"), NULL));
+ assert(equal(lexically_relative("C:F", "C:/D/"), NULL));
+ assert(equal(lexically_relative("C:F", "D:G"), NULL));
+ assert(equal(lexically_relative("F", "C:/D/S/"), NULL));
+ assert(match(lexically_relative("C:../", "C:../"), "."));
+ assert(match(lexically_relative("C:../", "C:./"), "../"));
+ assert(match(lexically_relative("C:../", "C:a"), "../../"));
+ assert(match(lexically_relative("C:../../a", "C:../b"), "../../a"));
+ assert(match(lexically_relative("C:../a", "C:../b"), "../a"));
+ assert(match(lexically_relative("C:a/b/c", "C:../"), NULL));
+ assert(match(lexically_relative("C:a/b/c", "C:../../"), NULL));
#else
- assert(equal(zix_path_lexically_relative(NULL, "/", "a"), NULL));
- assert(equal(zix_path_lexically_relative(NULL, "//host", "//host/"), "."));
- assert(equal(zix_path_lexically_relative(NULL, "//host/", "//host"), "."));
- assert(equal(zix_path_lexically_relative(NULL, "//host/", "/a"), "../host/"));
- assert(equal(zix_path_lexically_relative(NULL, "/a", "//host"), "../a"));
- assert(equal(zix_path_lexically_relative(NULL, "/a", "//host/"), "../a"));
- assert(
- equal(zix_path_lexically_relative(NULL, "C:/D/", "C:F.txt"), "../C:/D/"));
- assert(
- equal(zix_path_lexically_relative(NULL, "C:/D/S/", "F.txt"), "../C:/D/S/"));
- assert(equal(zix_path_lexically_relative(NULL, "C:F", "C:/D/"), "../../C:F"));
- assert(equal(zix_path_lexically_relative(NULL, "C:F", "D:G"), "../C:F"));
- assert(
- equal(zix_path_lexically_relative(NULL, "F", "C:/D/S/"), "../../../F"));
- assert(match(zix_path_lexically_relative(NULL, "C:../", "C:../"), "."));
- assert(match(zix_path_lexically_relative(NULL, "C:../", "C:./"), "../C:../"));
- assert(match(zix_path_lexically_relative(NULL, "C:../", "C:a"), "../C:../"));
- assert(
- match(zix_path_lexically_relative(NULL, "C:../../a", "C:../b"), "../../a"));
- assert(match(zix_path_lexically_relative(NULL, "C:../a", "C:../b"), "../a"));
- assert(
- match(zix_path_lexically_relative(NULL, "C:a/b/c", "C:../"), "../C:a/b/c"));
- assert(
- match(zix_path_lexically_relative(NULL, "C:a/b/c", "C:../../"), "C:a/b/c"));
+ assert(equal(lexically_relative("/", "a"), NULL));
+ assert(equal(lexically_relative("//host", "//host/"), "."));
+ assert(equal(lexically_relative("//host/", "//host"), "."));
+ assert(equal(lexically_relative("//host/", "/a"), "../host/"));
+ assert(equal(lexically_relative("/a", "//host"), "../a"));
+ assert(equal(lexically_relative("/a", "//host/"), "../a"));
+ assert(equal(lexically_relative("C:/D/", "C:F.txt"), "../C:/D/"));
+ assert(equal(lexically_relative("C:/D/S/", "F.txt"), "../C:/D/S/"));
+ assert(equal(lexically_relative("C:F", "C:/D/"), "../../C:F"));
+ assert(equal(lexically_relative("C:F", "D:G"), "../C:F"));
+ assert(equal(lexically_relative("F", "C:/D/S/"), "../../../F"));
+ assert(match(lexically_relative("C:../", "C:../"), "."));
+ assert(match(lexically_relative("C:../", "C:./"), "../C:../"));
+ assert(match(lexically_relative("C:../", "C:a"), "../C:../"));
+ assert(match(lexically_relative("C:../../a", "C:../b"), "../../a"));
+ assert(match(lexically_relative("C:../a", "C:../b"), "../a"));
+ assert(match(lexically_relative("C:a/b/c", "C:../"), "../C:a/b/c"));
+ assert(match(lexically_relative("C:a/b/c", "C:../../"), "C:a/b/c"));
#endif
}
@@ -924,10 +918,107 @@ test_path_is_absolute(void)
assert(zix_path_is_relative("C:\\a\\b"));
assert(zix_path_is_relative("D:/a/b"));
assert(zix_path_is_relative("D:\\a\\b"));
-
#endif
}
+static void
+test_null_queries(void)
+{
+ assert(!zix_path_has_root_path(NULL));
+ assert(!zix_path_has_root_name(NULL));
+ assert(!zix_path_has_root_directory(NULL));
+ assert(!zix_path_has_relative_path(NULL));
+ assert(!zix_path_has_parent_path(NULL));
+ assert(!zix_path_has_filename(NULL));
+ assert(!zix_path_has_stem(NULL));
+ assert(!zix_path_has_extension(NULL));
+ assert(!zix_path_is_absolute(NULL));
+ assert(zix_path_is_relative(NULL));
+}
+
+typedef char* (*AllocCheckFunc)(ZixAllocator*);
+
+static char*
+check_join_alloc(ZixAllocator* const allocator)
+{
+ return zix_path_join(allocator, "a", "b");
+}
+
+static char*
+check_join_empty_alloc(ZixAllocator* const allocator)
+{
+ return zix_path_join(allocator, "", "b");
+}
+
+static char*
+check_preferred_alloc(ZixAllocator* const allocator)
+{
+ return zix_path_preferred(allocator, "/a/b");
+}
+
+static char*
+check_lexically_normal_alloc(ZixAllocator* const allocator)
+{
+ return zix_path_lexically_normal(allocator, "./a/././b/./");
+}
+
+static char*
+check_lexically_normal_empty_alloc(ZixAllocator* const allocator)
+{
+ return zix_path_lexically_normal(allocator, "");
+}
+
+static char*
+check_lexically_relative_alloc(ZixAllocator* const allocator)
+{
+ return zix_path_lexically_relative(allocator, "../../a", "../b");
+}
+
+static char*
+check_lexically_relative_equal_alloc(ZixAllocator* const allocator)
+{
+ return zix_path_lexically_relative(allocator, "a", "a");
+}
+
+static char*
+check_lexically_relative_dot_alloc(ZixAllocator* const allocator)
+{
+ return zix_path_lexically_relative(allocator, "", ".");
+}
+
+static void
+check_failed_alloc(const AllocCheckFunc check)
+{
+ ZixFailingAllocator allocator = zix_failing_allocator();
+
+ // Successfully perform check to count the number of allocations
+ {
+ char* const result = check(&allocator.base);
+ assert(result);
+ zix_free(&allocator.base, result);
+ }
+
+ // Test that each allocation failing is handled gracefully
+ const size_t n_new_allocs = zix_failing_allocator_reset(&allocator, 0);
+ for (size_t i = 0U; i < n_new_allocs; ++i) {
+ zix_failing_allocator_reset(&allocator, i);
+ assert(!check(&allocator.base));
+ }
+}
+
+static void
+test_failed_alloc(void)
+{
+ check_failed_alloc(check_join_alloc);
+ check_failed_alloc(check_join_empty_alloc);
+ check_failed_alloc(check_preferred_alloc);
+ check_failed_alloc(check_lexically_normal_alloc);
+ check_failed_alloc(check_lexically_normal_empty_alloc);
+ check_failed_alloc(check_lexically_relative_alloc);
+ check_failed_alloc(check_lexically_relative_equal_alloc);
+ check_failed_alloc(check_lexically_relative_dot_alloc);
+}
+
int
main(void)
{
@@ -938,11 +1029,14 @@ main(void)
test_path_stem();
test_path_extension();
test_path_is_absolute();
+ test_null_queries();
test_path_join();
test_path_preferred();
test_path_lexically_normal();
test_path_lexically_relative();
+ test_failed_alloc();
+
return 0;
}
diff --git a/test/test_ring.c b/test/test_ring.c
index b1845ce..06ac183 100644
--- a/test/test_ring.c
+++ b/test/test_ring.c
@@ -6,10 +6,10 @@
#include "failing_allocator.h"
#include "test_args.h"
-#include "zix/attributes.h"
-#include "zix/ring.h"
-#include "zix/status.h"
-#include "zix/thread.h"
+#include <zix/attributes.h>
+#include <zix/ring.h>
+#include <zix/status.h>
+#include <zix/thread.h>
#include <assert.h>
#include <limits.h>
@@ -34,8 +34,7 @@ gen_msg(int* const msg, int start)
return start;
}
-ZIX_PURE_FUNC
-static int
+ZIX_PURE_FUNC static int
cmp_msg(const int* const msg1, const int* const msg2)
{
for (unsigned i = 0U; i < MSG_SIZE; ++i) {
@@ -170,9 +169,9 @@ test_failed_alloc(void)
assert(ring);
// Test that each allocation failing is handled gracefully
- const size_t n_new_allocs = allocator.n_allocations;
+ const size_t n_new_allocs = zix_failing_allocator_reset(&allocator, 0);
for (size_t i = 0U; i < n_new_allocs; ++i) {
- allocator.n_remaining = i;
+ zix_failing_allocator_reset(&allocator, i);
assert(!zix_ring_new(&allocator.base, 512));
}
diff --git a/test/test_sem.c b/test/test_sem.c
index 0118f50..ad2ea6d 100644
--- a/test/test_sem.c
+++ b/test/test_sem.c
@@ -3,10 +3,10 @@
#undef NDEBUG
-#include "zix/attributes.h"
-#include "zix/sem.h"
-#include "zix/status.h"
-#include "zix/thread.h"
+#include <zix/attributes.h>
+#include <zix/sem.h>
+#include <zix/status.h>
+#include <zix/thread.h>
#include <assert.h>
#include <stdio.h>
diff --git a/test/test_status.c b/test/test_status.c
index 6857928..b047fe8 100644
--- a/test/test_status.c
+++ b/test/test_status.c
@@ -3,7 +3,7 @@
#undef NDEBUG
-#include "zix/status.h"
+#include <zix/status.h>
#include <assert.h>
#include <stdio.h>
diff --git a/test/test_string_view.c b/test/test_string_view.c
index 52b824d..9aeb826 100644
--- a/test/test_string_view.c
+++ b/test/test_string_view.c
@@ -5,7 +5,8 @@
#include "failing_allocator.h"
-#include "zix/string_view.h"
+#include <zix/allocator.h>
+#include <zix/string_view.h>
#include <assert.h>
#include <string.h>
@@ -94,7 +95,7 @@ test_copy(void)
ZixFailingAllocator allocator = zix_failing_allocator();
// Copying a string takes exactly one allocation
- allocator.n_remaining = 1U;
+ zix_failing_allocator_reset(&allocator, 1U);
char* const copy = zix_string_view_copy(&allocator.base, orig);
assert(copy);
@@ -102,7 +103,7 @@ test_copy(void)
zix_free(&allocator.base, copy);
// Check that allocation failure is handled gracefully
- allocator.n_remaining = 0U;
+ zix_failing_allocator_reset(&allocator, 0U);
assert(!zix_string_view_copy(&allocator.base, orig));
}
diff --git a/test/test_thread.c b/test/test_thread.c
index 09edde6..ede0f31 100644
--- a/test/test_thread.c
+++ b/test/test_thread.c
@@ -3,8 +3,8 @@
#undef NDEBUG
-#include "zix/status.h"
-#include "zix/thread.h"
+#include <zix/status.h>
+#include <zix/thread.h>
#include <assert.h>
#include <string.h>
diff --git a/test/test_tree.c b/test/test_tree.c
index fae6d85..61be9be 100644
--- a/test/test_tree.c
+++ b/test/test_tree.c
@@ -8,10 +8,10 @@
#include "test_args.h"
#include "test_data.h"
-#include "zix/allocator.h"
-#include "zix/attributes.h"
-#include "zix/status.h"
-#include "zix/tree.h"
+#include <zix/allocator.h>
+#include <zix/attributes.h>
+#include <zix/status.h>
+#include <zix/tree.h>
#include <assert.h>
#include <inttypes.h>
@@ -211,9 +211,9 @@ test_failed_alloc(void)
assert(!stress(&allocator.base, 0, 16));
// Test that each allocation failing is handled gracefully
- const size_t n_new_allocs = allocator.n_allocations;
+ const size_t n_new_allocs = zix_failing_allocator_reset(&allocator, 0);
for (size_t i = 0U; i < n_new_allocs; ++i) {
- allocator.n_remaining = i;
+ zix_failing_allocator_reset(&allocator, i);
assert(stress(&allocator.base, 0, 16));
}
}