summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml2
-rw-r--r--NEWS5
-rw-r--r--doc/Doxyfile.in2
-rw-r--r--include/zix/environment.h39
-rw-r--r--include/zix/zix.h10
-rw-r--r--meson.build5
-rw-r--r--src/posix/environment_posix.c134
-rw-r--r--src/win32/environment_win32.c22
-rw-r--r--test/.clang-tidy1
-rw-r--r--test/meson.build1
-rw-r--r--test/test_environment.c140
11 files changed, 356 insertions, 5 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ebfdd1a..081c7b6 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -124,7 +124,7 @@ freebsd:
stage: build
tags: [freebsd,meson]
script:
- - meson setup build -Dbuildtype=debug -Dwarning_level=everything -Dwerror=true -Ddocs=disabled
+ - meson setup build -Dbuildtype=debug -Dwarning_level=everything -Dwerror=true -Ddocs=disabled -Db_lundef=false
- ninja -C build test
- meson configure -Dbuildtype=release build
- ninja -C build test
diff --git a/NEWS b/NEWS
index 2e17f76..36cb979 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,7 @@
-zix (0.5.0) unstable; urgency=medium
+zix (0.5.1) unstable; urgency=medium
* Add ZIX_NODISCARD attribute
+ * Add zix_expand_environment_strings()
* Add zix_string_view_equals()
* Avoid fdatasync() on Darwin
* Fix build on POSIX systems without PATH_MAX defined
@@ -9,7 +10,7 @@ zix (0.5.0) unstable; urgency=medium
* Fix ring unit test when mlock() is unavailable
* Improve documentation
- -- David Robillard <d@drobilla.net> Fri, 15 Nov 2024 19:18:43 +0000
+ -- David Robillard <d@drobilla.net> Sun, 24 Nov 2024 15:48:27 +0000
zix (0.4.2) stable; urgency=medium
diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in
index 741fe1a..0c60cab 100644
--- a/doc/Doxyfile.in
+++ b/doc/Doxyfile.in
@@ -68,5 +68,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/include/zix/environment.h b/include/zix/environment.h
new file mode 100644
index 0000000..fbbcd0d
--- /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_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/zix.h b/include/zix/zix.h
index 4fcf238..ceaadc0 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-2024 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
#ifndef ZIX_ZIX_H
@@ -68,6 +68,14 @@
/**
@}
+ @defgroup zix_environment Environment
+ @{
+*/
+
+#include <zix/environment.h>
+
+/**
+ @}
@}
*/
diff --git a/meson.build b/meson.build
index 55ed750..ad31a87 100644
--- a/meson.build
+++ b/meson.build
@@ -12,7 +12,7 @@ project(
],
license: 'ISC',
meson_version: '>= 0.56.0',
- version: '0.5.0',
+ version: '0.5.1',
)
zix_src_root = meson.current_source_dir()
@@ -305,6 +305,7 @@ c_headers = files(
'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',
@@ -335,11 +336,13 @@ sources = files(
if host_machine.system() == 'windows'
sources += files(
+ 'src/win32/environment_win32.c',
'src/win32/filesystem_win32.c',
'src/win32/system_win32.c',
)
else
sources += files(
+ 'src/posix/environment_posix.c',
'src/posix/filesystem_posix.c',
'src/posix/system_posix.c',
)
diff --git a/src/posix/environment_posix.c b/src/posix/environment_posix.c
new file mode 100644
index 0000000..58b8fd2
--- /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 <zix/string_view.h>
+
+#include <stdbool.h>
+#include <string.h>
+
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+extern char** environ;
+
+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 == '_');
+}
+
+static const char*
+find_env(const ZixStringView name)
+{
+ if (environ) {
+ for (size_t i = 0U; environ[i]; ++i) {
+ // Find the first character in this entry that doesn't match the name
+ const char* const entry = environ[i];
+ size_t j = 0U;
+ while (j < name.length && entry[j] == name.data[j]) {
+ ++j;
+ }
+
+ if (j == name.length && entry[j] == '=') {
+ return entry + j + 1U;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+// 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 the environment variable var to dst if one is 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 ZixStringView var = zix_substring(ref + 1U, ref_len - 1U);
+ const char* val = find_env(var);
+ 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);
+}
+
+char*
+zix_expand_environment_strings(ZixAllocator* const allocator,
+ const char* const string)
+{
+ char* out = NULL;
+ size_t len = 0U;
+
+ 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))) ||
+ !(out = append_var(allocator, &len, out, t, string + s))) {
+ 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"))) {
+ 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);
+ }
+
+ return out;
+}
diff --git a/src/win32/environment_win32.c b/src/win32/environment_win32.c
new file mode 100644
index 0000000..e7a0ee3
--- /dev/null
+++ b/src/win32/environment_win32.c
@@ -0,0 +1,22 @@
+// Copyright 2012-2024 David Robillard <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#include <zix/environment.h>
+
+#include <windows.h>
+
+char*
+zix_expand_environment_strings(ZixAllocator* const allocator,
+ const char* const string)
+{
+ const DWORD size = ExpandEnvironmentStrings(string, NULL, 0U);
+ if (!size) {
+ return NULL;
+ }
+
+ char* const out = (char*)zix_calloc(allocator, 1U, size + 1U);
+ if (out) {
+ ExpandEnvironmentStrings(string, out, size + 1U);
+ }
+ return out;
+}
diff --git a/test/.clang-tidy b/test/.clang-tidy
index f869db0..401b1b0 100644
--- a/test/.clang-tidy
+++ b/test/.clang-tidy
@@ -9,6 +9,7 @@ Checks: >
-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,
diff --git a/test/meson.build b/test/meson.build
index 6aa4e95..309551f 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -73,6 +73,7 @@ sequential_tests = {
},
'filesystem': {'': files('../README.md')},
'digest': {'': []},
+ 'environment': {'': []},
'hash': {'': []},
'path': {'': []},
'status': {'': []},
diff --git a/test/test_environment.c b/test/test_environment.c
new file mode 100644
index 0000000..dba7b90
--- /dev/null
+++ b/test/test_environment.c
@@ -0,0 +1,140 @@
+// 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"));
+#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;
+}