diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:49:52 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:49:52 +0000 |
commit | 55944e5e40b1be2afc4855d8d2baf4b73d1876b5 (patch) | |
tree | 33f869f55a1b149e9b7c2b7e201867ca5dd52992 /src/test | |
parent | Initial commit. (diff) | |
download | systemd-55944e5e40b1be2afc4855d8d2baf4b73d1876b5.tar.xz systemd-55944e5e40b1be2afc4855d8d2baf4b73d1876b5.zip |
Adding upstream version 255.4.upstream/255.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
218 files changed, 54611 insertions, 0 deletions
diff --git a/src/test/generate-sym-test.py b/src/test/generate-sym-test.py new file mode 100755 index 0000000..028d108 --- /dev/null +++ b/src/test/generate-sym-test.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: LGPL-2.1-or-later + +import os +import re +import sys + +def process_sym_file(file): + for line in file: + m = re.search(r'^ +([a-zA-Z0-9_]+);', line) + if m: + if m[1] == 'sd_bus_object_vtable_format': + print(' {{"{0}", &{0}}},'.format(m[1])) + else: + print(' {{"{0}", {0}}},'.format(m[1])) + +def process_source_file(file): + for line in file: + # Functions + m = re.search(r'^_public_\s+(\S+\s+)+\**(\w+)\s*\(', line) + if m: + print(' {{ "{0}", {0} }},'.format(m[2])) + # Variables + m = re.search(r'^_public_\s+(\S+\s+)+\**(\w+)\s*=', line) + if m: + print(' {{ "{0}", &{0} }},'.format(m[2])) + # Functions defined through a macro + m = re.search(r'^DEFINE_PUBLIC_TRIVIAL_REF_FUNC\([^,]+,\s*(\w+)\s*\)', line) + if m: + print(' {{ "{0}_ref", {0}_ref }},'.format(m[1])) + m = re.search(r'^DEFINE_PUBLIC_TRIVIAL_UNREF_FUNC\([^,]+,\s*(\w+)\s*,', line) + if m: + print(' {{ "{0}_unref", {0}_unref }},'.format(m[1])) + m = re.search(r"^DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC\([^,]+,\s*(\w+)\s*,", line) + if m: + print(' {{ "{0}_ref", {0}_ref }},'.format(m[1])) + print(' {{ "{0}_unref", {0}_unref }},'.format(m[1])) + +print('''/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +''') + +for header in sys.argv[3:]: + print('#include "{}"'.format(header.split('/')[-1])) + +print(''' +/* We want to check deprecated symbols too, without complaining */ +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +''') + +print(''' +struct symbol { + const char *name; + const void *symbol; +}; +static struct symbol symbols_from_sym[] = {''') + +with open(sys.argv[1], "r") as f: + process_sym_file(f) + +print(''' {} +}, symbols_from_source[] = {''') + +for dirpath, _, filenames in sorted(os.walk(sys.argv[2])): + for filename in sorted(filenames): + if not filename.endswith(".c") and not filename.endswith(".h"): + continue + with open(os.path.join(dirpath, filename), "r") as f: + process_source_file(f) + +print(''' {} +}; + +static int sort_callback(const void *a, const void *b) { + const struct symbol *x = a, *y = b; + return strcmp(x->name, y->name); +} + +int main(void) { + size_t i, j; + + qsort(symbols_from_sym, sizeof(symbols_from_sym)/sizeof(symbols_from_sym[0])-1, sizeof(symbols_from_sym[0]), sort_callback); + qsort(symbols_from_source, sizeof(symbols_from_source)/sizeof(symbols_from_source[0])-1, sizeof(symbols_from_source[0]), sort_callback); + + puts("From symbol file:"); + for (i = 0; symbols_from_sym[i].name; i++) + printf("%p: %s\\n", symbols_from_sym[i].symbol, symbols_from_sym[i].name); + + puts("\\nFrom source files:"); + for (j = 0; symbols_from_source[j].name; j++) + printf("%p: %s\\n", symbols_from_source[j].symbol, symbols_from_source[j].name); + + puts(""); + printf("Found %zu symbols from symbol file.\\n", i); + printf("Found %zu symbols from source files.\\n", j); + + for (i = 0; symbols_from_sym[i].name; i++) { + struct symbol*n = bsearch(symbols_from_sym+i, symbols_from_source, sizeof(symbols_from_source)/sizeof(symbols_from_source[0])-1, sizeof(symbols_from_source[0]), sort_callback); + if (!n) + printf("Found in symbol file, but not in sources: %s\\n", symbols_from_sym[i].name); + } + + for (j = 0; symbols_from_source[j].name; j++) { + struct symbol*n = bsearch(symbols_from_source+j, symbols_from_source, sizeof(symbols_from_sym)/sizeof(symbols_from_sym[0])-1, sizeof(symbols_from_sym[0]), sort_callback); + if (!n) + printf("Found in sources, but not in symbol file: %s\\n", symbols_from_source[i].name); + } + + return i == j ? EXIT_SUCCESS : EXIT_FAILURE; +}''') diff --git a/src/test/meson.build b/src/test/meson.build new file mode 100644 index 0000000..cce90d7 --- /dev/null +++ b/src/test/meson.build @@ -0,0 +1,597 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +awkscript = 'test-hashmap-ordered.awk' +test_hashmap_ordered_c = custom_target( + 'test-hashmap-ordered.c', + input : [awkscript, 'test-hashmap-plain.c'], + output : 'test-hashmap-ordered.c', + command : [awk, '-f', '@INPUT0@', '@INPUT1@'], + capture : true, + build_by_default : want_tests != 'false') + +path = run_command(sh, '-c', 'echo "$PATH"', check: true).stdout().strip() +test_env = environment() +test_env.set('SYSTEMD_LANGUAGE_FALLBACK_MAP', language_fallback_map) +test_env.set('PATH', project_build_root + ':' + path) +test_env.set('PROJECT_BUILD_ROOT', project_build_root) +test_env.set('SYSTEMD_SLOW_TESTS', slow_tests ? '1' : '0') + +if efi_addon != '' + test_env.set('EFI_ADDON', efi_addon) +endif + +############################################################ + +generate_sym_test_py = find_program('generate-sym-test.py') + +test_libsystemd_sym_c = custom_target( + 'test-libsystemd-sym.c', + input : [libsystemd_sym_path] + systemd_headers + libsystemd_sources, + output : 'test-libsystemd-sym.c', + command : [generate_sym_test_py, libsystemd_sym_path, libsystemd_dir_path] + systemd_headers, + capture : true, + build_by_default : want_tests != 'false') + +test_libudev_sym_c = custom_target( + 'test-libudev-sym.c', + input : [libudev_sym_path, libudev_h_path] + libudev_sources, + output : 'test-libudev-sym.c', + command : [generate_sym_test_py, libudev_sym_path, libudev_dir_path, libudev_h_path], + capture : true, + build_by_default : want_tests != 'false') + +############################################################ + +simple_tests += files( + 'test-alloc-util.c', + 'test-architecture.c', + 'test-argv-util.c', + 'test-barrier.c', + 'test-bitfield.c', + 'test-bitmap.c', + 'test-blockdev-util.c', + 'test-bootspec.c', + 'test-bus-util.c', + 'test-calendarspec.c', + 'test-cgroup-setup.c', + 'test-cgroup-util.c', + 'test-cgroup.c', + 'test-chase.c', + 'test-clock.c', + 'test-compare-operator.c', + 'test-condition.c', + 'test-conf-files.c', + 'test-conf-parser.c', + 'test-copy.c', + 'test-coredump-util.c', + 'test-cpu-set-util.c', + 'test-creds.c', + 'test-daemon.c', + 'test-data-fd-util.c', + 'test-date.c', + 'test-dev-setup.c', + 'test-device-nodes.c', + 'test-devnum-util.c', + 'test-dns-domain.c', + 'test-ellipsize.c', + 'test-env-file.c', + 'test-env-util.c', + 'test-errno-util.c', + 'test-escape.c', + 'test-ether-addr-util.c', + 'test-exec-util.c', + 'test-execve.c', + 'test-exit-status.c', + 'test-extract-word.c', + 'test-fdset.c', + 'test-fiemap.c', + 'test-fileio.c', + 'test-firewall-util.c', + 'test-format-table.c', + 'test-format-util.c', + 'test-fs-util.c', + 'test-fstab-util.c', + 'test-glob-util.c', + 'test-gpt.c', + 'test-gunicode.c', + 'test-hash-funcs.c', + 'test-hexdecoct.c', + 'test-hmac.c', + 'test-hostname-setup.c', + 'test-hostname-util.c', + 'test-id128.c', + 'test-image-policy.c', + 'test-import-util.c', + 'test-in-addr-prefix-util.c', + 'test-in-addr-util.c', + 'test-install-file.c', + 'test-install-root.c', + 'test-io-util.c', + 'test-journal-importer.c', + 'test-kbd-util.c', + 'test-limits-util.c', + 'test-list.c', + 'test-local-addresses.c', + 'test-locale-util.c', + 'test-lock-util.c', + 'test-log.c', + 'test-logarithm.c', + 'test-macro.c', + 'test-memfd-util.c', + 'test-memory-util.c', + 'test-mempool.c', + 'test-memstream-util.c', + 'test-mkdir.c', + 'test-modhex.c', + 'test-mountpoint-util.c', + 'test-net-naming-scheme.c', + 'test-nulstr-util.c', + 'test-open-file.c', + 'test-ordered-set.c', + 'test-os-util.c', + 'test-parse-argument.c', + 'test-parse-helpers.c', + 'test-path-lookup.c', + 'test-path-util.c', + 'test-percent-util.c', + 'test-pretty-print.c', + 'test-prioq.c', + 'test-proc-cmdline.c', + 'test-procfs-util.c', + 'test-psi-util.c', + 'test-ratelimit.c', + 'test-raw-clone.c', + 'test-recurse-dir.c', + 'test-replace-var.c', + 'test-rlimit-util.c', + 'test-rm-rf.c', + 'test-sd-hwdb.c', + 'test-sd-path.c', + 'test-secure-bits.c', + 'test-selinux.c', + 'test-serialize.c', + 'test-set.c', + 'test-sha256.c', + 'test-sigbus.c', + 'test-signal-util.c', + 'test-siphash24.c', + 'test-sleep-config.c', + 'test-socket-netlink.c', + 'test-socket-util.c', + 'test-specifier.c', + 'test-stat-util.c', + 'test-static-destruct.c', + 'test-strbuf.c', + 'test-string-util.c', + 'test-strip-tab-ansi.c', + 'test-strv.c', + 'test-strxcpyx.c', + 'test-sysctl-util.c', + 'test-terminal-util.c', + 'test-tmpfile-util.c', + 'test-udev-util.c', + 'test-uid-alloc-range.c', + 'test-uid-range.c', + 'test-umask-util.c', + 'test-unaligned.c', + 'test-unit-file.c', + 'test-user-util.c', + 'test-utf8.c', + 'test-verbs.c', + 'test-web-util.c', + 'test-xattr-util.c', + 'test-xml.c', +) + +############################################################ + +common_test_dependencies = [ + libblkid, + libmount, + librt, + libseccomp, + libselinux, + threads, +] + +executables += [ + test_template + { + 'sources' : files('test-acl-util.c'), + 'conditions' : ['HAVE_ACL'], + }, + test_template + { + 'sources' : files('test-af-list.c') + + generated_gperf_headers, + }, + test_template + { + 'sources' : files('test-arphrd-util.c') + + generated_gperf_headers, + }, + test_template + { + 'sources' : files('test-ask-password-api.c'), + 'type' : 'manual', + }, + test_template + { + 'sources' : files('test-async.c'), + 'timeout' : 120, + }, + test_template + { + 'sources' : files('test-boot-timestamps.c'), + 'conditions' : ['ENABLE_EFI'], + }, + test_template + { + 'sources' : files('test-btrfs.c'), + 'type' : 'manual', + }, + test_template + { + 'sources' : files('test-btrfs-physical-offset.c'), + 'type' : 'manual', + }, + test_template + { + 'sources' : files('test-cap-list.c') + + generated_gperf_headers, + 'dependencies' : libcap, + }, + test_template + { + 'sources' : files('test-capability.c'), + 'dependencies' : libcap, + }, + test_template + { + 'sources' : files('test-chase-manual.c'), + 'type' : 'manual', + }, + test_template + { + 'sources' : files('test-compress-benchmark.c'), + 'link_with' : [ + libbasic_compress, + libshared, + ], + 'timeout' : 90, + }, + test_template + { + 'sources' : files('test-compress.c'), + 'link_with' : [ + libbasic_compress, + libshared, + ], + }, + test_template + { + 'sources' : files('test-cryptolib.c'), + 'dependencies' : lib_openssl_or_gcrypt, + 'conditions' : ['HAVE_OPENSSL_OR_GCRYPT'], + }, + test_template + { + 'sources' : files('test-dlopen-so.c'), + 'dependencies' : libp11kit_cflags + }, + test_template + { + # only static linking apart from libdl, to make sure that the + # module is linked to all libraries that it uses. + 'sources' : files('test-dlopen.c'), + 'link_with' : libbasic, + 'dependencies' : libdl, + 'install' : false, + 'type' : 'manual', + }, + test_template + { + 'sources' : files('test-errno-list.c') + + generated_gperf_headers, + }, + test_template + { + 'sources' : files('test-fd-util.c'), + 'dependencies' : libseccomp, + }, + test_template + { + 'sources' : files( + 'test-hashmap.c', + 'test-hashmap-plain.c', + ) + [ + test_hashmap_ordered_c, + ], + 'timeout' : 180, + }, + test_template + { + 'sources' : files('test-ip-protocol-list.c') + + shared_generated_gperf_headers, + }, + test_template + { + 'sources' : files('test-ipcrm.c'), + 'type' : 'unsafe', + }, + test_template + { + 'sources' : files('test-json.c'), + 'dependencies' : libm, + }, + test_template + { + 'sources' : files('test-libcrypt-util.c'), + 'dependencies' : libcrypt, + 'timeout' : 120, + }, + test_template + { + 'sources' : files('test-libmount.c'), + 'dependencies' : [ + libmount, + threads, + ], + }, + test_template + { + 'sources' : files('test-loopback.c'), + 'dependencies' : common_test_dependencies, + }, + test_template + { + 'sources' : files('test-math-util.c'), + 'dependencies' : libm, + }, + test_template + { + 'sources' : files('test-mempress.c'), + 'dependencies' : threads, + }, + test_template + { + 'sources' : files('test-mount-util.c'), + 'dependencies' : libmount, + }, + test_template + { + 'sources' : files('test-netlink-manual.c'), + 'dependencies' : libkmod, + 'conditions' : ['HAVE_KMOD'], + 'type' : 'manual', + }, + test_template + { + 'sources' : files('test-nft-set.c'), + 'type' : 'manual', + }, + test_template + { + 'sources' : files('test-nscd-flush.c'), + 'conditions' : ['ENABLE_NSCD'], + 'type' : 'manual', + }, + test_template + { + 'sources' : files( + 'test-nss-hosts.c', + 'nss-test-util.c', + ), + 'dependencies' : libdl, + 'conditions' : ['ENABLE_NSS'], + 'timeout' : 120, + }, + test_template + { + 'sources' : files( + 'test-nss-users.c', + 'nss-test-util.c', + ), + 'dependencies' : libdl, + 'conditions' : ['ENABLE_NSS'], + }, + test_template + { + 'sources' : files('test-openssl.c'), + 'dependencies' : libopenssl, + 'conditions' : ['HAVE_OPENSSL'], + }, + test_template + { + 'sources' : files('test-parse-util.c'), + 'dependencies' : libm, + }, + test_template + { + 'sources' : files('test-process-util.c'), + 'dependencies' : threads, + }, + test_template + { + 'sources' : files('test-qrcode-util.c'), + 'dependencies' : libdl, + }, + test_template + { + 'sources' : files('test-random-util.c'), + 'dependencies' : libm, + 'timeout' : 120, + }, + test_template + { + 'sources' : files('test-sbat.c'), + 'conditions' : ['ENABLE_BOOTLOADER'], + 'c_args' : '-I@0@'.format(efi_config_h_dir), + }, + test_template + { + 'sources' : files('test-seccomp.c'), + 'dependencies' : libseccomp, + 'conditions' : ['HAVE_SECCOMP'], + }, + test_template + { + 'sources' : files('test-set-disable-mempool.c'), + 'dependencies' : threads, + }, + test_template + { + 'sources' : files('test-sizeof.c'), + 'link_with' : libbasic, + }, + test_template + { + 'sources' : files('test-time-util.c'), + 'timeout' : 120, + }, + test_template + { + 'sources' : files('test-tpm2.c'), + 'dependencies' : libopenssl, + 'timeout' : 120, + }, + test_template + { + 'sources' : files('test-utmp.c'), + 'conditions' : ['ENABLE_UTMP'], + }, + test_template + { + 'sources' : files('test-varlink.c'), + 'dependencies' : threads, + }, + test_template + { + 'sources' : files('test-varlink-idl.c'), + 'dependencies' : threads, + }, + test_template + { + 'sources' : files('test-watchdog.c'), + 'type' : 'unsafe', + }, + + # Symbol tests + test_template + { + 'name' : 'test-libsystemd-sym', + 'sources' : test_libsystemd_sym_c, + 'link_with' : libsystemd, + 'suite' : 'libsystemd', + }, + test_template + { + 'name' : 'test-libsystemd-static-sym', + 'sources' : test_libsystemd_sym_c, + 'link_with' : install_libsystemd_static, + 'build_by_default' : want_tests != 'false' and static_libsystemd != 'false', + 'install' : install_tests and static_libsystemd != 'false', + 'suite' : 'libsystemd', + }, + test_template + { + 'name' : 'test-libudev-sym', + 'sources' : test_libudev_sym_c, + 'include_directories' : libudev_includes, + 'c_args' : ['-Wno-deprecated-declarations'] + test_cflags, + 'link_with' : libudev, + 'suite' : 'libudev', + }, + test_template + { + 'name' : 'test-libudev-static-sym', + 'sources' : test_libudev_sym_c, + 'include_directories' : libudev_includes, + 'c_args' : ['-Wno-deprecated-declarations'] + test_cflags, + 'link_with' : install_libudev_static, + 'build_by_default' : want_tests != 'false' and static_libudev != 'false', + 'install' : install_tests and static_libudev != 'false', + 'suite' : 'libudev', + }, + + # Tests that link to libcore, i.e. tests for pid1 code. + core_test_template + { + 'sources' : files('test-bpf-devices.c'), + 'dependencies' : common_test_dependencies, + }, + core_test_template + { + 'sources' : files('test-bpf-firewall.c'), + 'dependencies' : common_test_dependencies, + }, + core_test_template + { + 'sources' : files('test-bpf-foreign-programs.c'), + }, + core_test_template + { + 'sources' : files('test-bpf-lsm.c'), + 'dependencies' : common_test_dependencies, + }, + core_test_template + { + 'sources' : files('test-cgroup-cpu.c'), + }, + core_test_template + { + 'sources' : files('test-cgroup-mask.c'), + 'dependencies' : common_test_dependencies, + }, + core_test_template + { + 'sources' : files('test-cgroup-unit-default.c'), + }, + core_test_template + { + 'sources' : files('test-chown-rec.c'), + }, + core_test_template + { + 'sources' : files('test-core-unit.c'), + 'dependencies' : common_test_dependencies, + }, + core_test_template + { + 'sources' : files('test-emergency-action.c'), + }, + core_test_template + { + 'sources' : files('test-engine.c'), + 'dependencies' : common_test_dependencies, + }, + core_test_template + { + 'sources' : files('test-execute.c'), + 'dependencies' : common_test_dependencies, + 'timeout' : 360, + }, + core_test_template + { + 'sources' : files('test-install.c'), + 'type' : 'manual', + }, + core_test_template + { + 'sources' : files('test-job-type.c'), + 'dependencies' : common_test_dependencies, + }, + core_test_template + { + 'sources' : files('test-load-fragment.c'), + 'dependencies' : common_test_dependencies, + }, + core_test_template + { + 'sources' : files('test-loop-block.c'), + 'dependencies' : [threads, libblkid], + 'parallel' : false, + }, + core_test_template + { + 'sources' : files('test-manager.c'), + }, + core_test_template + { + 'sources' : files('test-namespace.c'), + 'dependencies' : [ + libblkid, + threads, + ], + }, + core_test_template + { + 'sources' : files('test-ns.c'), + 'dependencies' : common_test_dependencies, + 'type' : 'manual', + }, + core_test_template + { + 'sources' : files('test-path.c'), + 'dependencies' : common_test_dependencies, + 'timeout' : 120, + }, + core_test_template + { + 'sources' : files('test-sched-prio.c'), + 'dependencies' : common_test_dependencies, + }, + core_test_template + { + 'sources' : files('test-socket-bind.c'), + 'dependencies' : libdl, + 'conditions' : ['BPF_FRAMEWORK'], + }, + core_test_template + { + 'sources' : files('test-tables.c'), + }, + core_test_template + { + 'sources' : files('test-unit-name.c'), + 'dependencies' : common_test_dependencies, + }, + core_test_template + { + 'sources' : files('test-unit-serialize.c'), + 'dependencies' : common_test_dependencies, + }, + core_test_template + { + 'sources' : files('test-watch-pid.c'), + 'dependencies' : common_test_dependencies, + }, + + # Tests from other directories that have link_with deps that were not defined earlier + test_template + { + 'sources' : files('../libsystemd/sd-bus/test-bus-error.c'), + 'link_with' : [ + libshared_static, + libsystemd_static, + ], + }, + test_template + { + 'sources' : files('../libsystemd/sd-device/test-sd-device-thread.c'), + 'link_with' : libsystemd, + 'dependencies' : threads, + }, + test_template + { + 'sources' : files('../libudev/test-udev-device-thread.c'), + 'link_with' : libudev, + 'dependencies' : threads, + }, + test_template + { + 'sources' : files('../libudev/test-libudev.c'), + 'link_with' : [ + libshared, + libudev_basic, + ], + }, +] diff --git a/src/test/nss-test-util.c b/src/test/nss-test-util.c new file mode 100644 index 0000000..20643f8 --- /dev/null +++ b/src/test/nss-test-util.c @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <dlfcn.h> +#include <stdio.h> +#include <unistd.h> + +#include "nss-test-util.h" +#include "string-util.h" + +const char* nss_status_to_string(enum nss_status status, char *buf, size_t buf_len) { + switch (status) { + case NSS_STATUS_TRYAGAIN: + return "NSS_STATUS_TRYAGAIN"; + case NSS_STATUS_UNAVAIL: + return "NSS_STATUS_UNAVAIL"; + case NSS_STATUS_NOTFOUND: + return "NSS_STATUS_NOTFOUND"; + case NSS_STATUS_SUCCESS: + return "NSS_STATUS_SUCCESS"; + case NSS_STATUS_RETURN: + return "NSS_STATUS_RETURN"; + default: + (void) snprintf(buf, buf_len, "%i", status); + return buf; + } +}; + +void* nss_open_handle(const char *dir, const char *module, int flags) { + const char *path = NULL; + void *handle; + + if (dir) + path = strjoina(dir, "/libnss_", module, ".so.2"); + if (!path || access(path, F_OK) < 0) + path = strjoina("libnss_", module, ".so.2"); + + log_debug("Using %s", path); + handle = dlopen(path, flags); + if (!handle) + log_error("Failed to load module %s: %s", module, dlerror()); + return handle; +} diff --git a/src/test/nss-test-util.h b/src/test/nss-test-util.h new file mode 100644 index 0000000..f081e64 --- /dev/null +++ b/src/test/nss-test-util.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <nss.h> +#include <stdint.h> + +const char* nss_status_to_string(enum nss_status status, char *buf, size_t buf_len); +void* nss_open_handle(const char *dir, const char *module, int flags); diff --git a/src/test/test-acl-util.c b/src/test/test-acl-util.c new file mode 100644 index 0000000..eb9678a --- /dev/null +++ b/src/test/test-acl-util.c @@ -0,0 +1,130 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <fcntl.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "acl-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "format-util.h" +#include "fs-util.h" +#include "string-util.h" +#include "tests.h" +#include "tmpfile-util.h" +#include "user-util.h" + +TEST_RET(add_acls_for_user) { + _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-empty.XXXXXX"; + _cleanup_close_ int fd = -EBADF; + char *cmd; + uid_t uid; + int r; + + fd = mkostemp_safe(fn); + assert_se(fd >= 0); + + /* Use the mode that user journal files use */ + assert_se(fchmod(fd, 0640) == 0); + + cmd = strjoina("ls -l ", fn); + assert_se(system(cmd) == 0); + + cmd = strjoina("getfacl -p ", fn); + assert_se(system(cmd) == 0); + + if (getuid() == 0) { + const char *nobody = NOBODY_USER_NAME; + r = get_user_creds(&nobody, &uid, NULL, NULL, NULL, 0); + if (r < 0) + uid = 0; + } else + uid = getuid(); + + r = fd_add_uid_acl_permission(fd, uid, ACL_READ); + if (ERRNO_IS_NOT_SUPPORTED(r)) + return log_tests_skipped("no ACL support on /tmp"); + + log_info_errno(r, "fd_add_uid_acl_permission(%i, "UID_FMT", ACL_READ): %m", fd, uid); + assert_se(r >= 0); + + cmd = strjoina("ls -l ", fn); + assert_se(system(cmd) == 0); + + cmd = strjoina("getfacl -p ", fn); + assert_se(system(cmd) == 0); + + /* set the acls again */ + + r = fd_add_uid_acl_permission(fd, uid, ACL_READ); + assert_se(r >= 0); + + cmd = strjoina("ls -l ", fn); + assert_se(system(cmd) == 0); + + cmd = strjoina("getfacl -p ", fn); + assert_se(system(cmd) == 0); + + return 0; +} + +TEST(fd_acl_make_read_only) { + _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-empty.XXXXXX"; + _cleanup_close_ int fd = -EBADF; + const char *cmd; + struct stat st; + + fd = mkostemp_safe(fn); + assert_se(fd >= 0); + + /* make it more exciting */ + (void) fd_add_uid_acl_permission(fd, 1, ACL_READ|ACL_WRITE|ACL_EXECUTE); + + assert_se(fstat(fd, &st) >= 0); + assert_se((st.st_mode & 0200) == 0200); + + cmd = strjoina("getfacl -p ", fn); + assert_se(system(cmd) == 0); + + cmd = strjoina("stat ", fn); + assert_se(system(cmd) == 0); + + log_info("read-only"); + assert_se(fd_acl_make_read_only(fd)); + + assert_se(fstat(fd, &st) >= 0); + assert_se((st.st_mode & 0222) == 0000); + + cmd = strjoina("getfacl -p ", fn); + assert_se(system(cmd) == 0); + + cmd = strjoina("stat ", fn); + assert_se(system(cmd) == 0); + + log_info("writable"); + assert_se(fd_acl_make_writable(fd)); + + assert_se(fstat(fd, &st) >= 0); + assert_se((st.st_mode & 0222) == 0200); + + cmd = strjoina("getfacl -p ", fn); + assert_se(system(cmd) == 0); + + cmd = strjoina("stat ", fn); + assert_se(system(cmd) == 0); + + log_info("read-only"); + assert_se(fd_acl_make_read_only(fd)); + + assert_se(fstat(fd, &st) >= 0); + assert_se((st.st_mode & 0222) == 0000); + + cmd = strjoina("getfacl -p ", fn); + assert_se(system(cmd) == 0); + + cmd = strjoina("stat ", fn); + assert_se(system(cmd) == 0); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-af-list.c b/src/test/test-af-list.c new file mode 100644 index 0000000..45655d7 --- /dev/null +++ b/src/test/test-af-list.c @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <sys/socket.h> + +#include "macro.h" +#include "string-util.h" +#include "tests.h" + +_unused_ +static const struct af_name* lookup_af(register const char *str, register GPERF_LEN_TYPE len); + +#include "af-from-name.h" +#include "af-list.h" +#include "af-to-name.h" + +TEST(af_list) { + for (unsigned i = 0; i < ELEMENTSOF(af_names); i++) { + if (af_names[i]) { + assert_se(streq(af_to_name(i), af_names[i])); + assert_se(af_from_name(af_names[i]) == (int) i); + } + } + + assert_se(af_to_name(af_max()) == NULL); + assert_se(af_to_name(-1) == NULL); + assert_se(af_from_name("huddlduddl") == -EINVAL); + assert_se(af_from_name("") == -EINVAL); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-alloc-util.c b/src/test/test-alloc-util.c new file mode 100644 index 0000000..24cb5f7 --- /dev/null +++ b/src/test/test-alloc-util.c @@ -0,0 +1,233 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <malloc.h> +#include <stdint.h> + +#include "alloc-util.h" +#include "macro.h" +#include "memory-util.h" +#include "random-util.h" +#include "tests.h" + +TEST(alloca) { + static const uint8_t zero[997] = { }; + char *t; + + t = alloca_align(17, 512); + assert_se(!((uintptr_t)t & 0xff)); + memzero(t, 17); + + t = alloca0_align(997, 1024); + assert_se(!((uintptr_t)t & 0x1ff)); + assert_se(!memcmp(t, zero, 997)); +} + +TEST(GREEDY_REALLOC) { + _cleanup_free_ int *a = NULL, *b = NULL, *c = NULL; + size_t i, j, n_c = 0; + + /* Give valgrind a chance to verify our realloc() operations */ + + for (i = 0; i < 20480; i++) { + assert_se(GREEDY_REALLOC(a, i + 1)); + assert_se(MALLOC_ELEMENTSOF(a) >= i + 1); + assert_se(MALLOC_SIZEOF_SAFE(a) >= (i + 1) * sizeof(int)); + a[i] = (int) i; + assert_se(GREEDY_REALLOC(a, i / 2)); + assert_se(MALLOC_ELEMENTSOF(a) >= i / 2); + assert_se(MALLOC_SIZEOF_SAFE(a) >= (i / 2) * sizeof(int)); + } + + for (j = 0; j < i / 2; j++) + assert_se(a[j] == (int) j); + + for (i = 30; i < 20480; i += 7) { + assert_se(GREEDY_REALLOC(b, i + 1)); + assert_se(MALLOC_ELEMENTSOF(b) >= i + 1); + assert_se(MALLOC_SIZEOF_SAFE(b) >= (i + 1) * sizeof(int)); + b[i] = (int) i; + assert_se(GREEDY_REALLOC(b, i / 2)); + assert_se(MALLOC_ELEMENTSOF(b) >= i / 2); + assert_se(MALLOC_SIZEOF_SAFE(b) >= (i / 2) * sizeof(int)); + } + + for (j = 30; j < i / 2; j += 7) + assert_se(b[j] == (int) j); + + size_t n_from = 10; + int from[n_from]; + for (i = 0; i < 2048; i++) { + for (j = 0; j < n_from; j++) + from[j] = n_from * i + j; + + _cleanup_free_ int *before = NULL; + size_t n_before = 0; + assert_se(GREEDY_REALLOC_APPEND(before, n_before, c, n_c)); + assert_se(before); + assert_se(n_before == n_c); + assert_se(memcmp_safe(c, before, n_c) == 0); + + assert_se(GREEDY_REALLOC_APPEND(c, n_c, from, n_from)); + assert_se(n_c == n_before + n_from); + assert_se(MALLOC_ELEMENTSOF(c) >= n_c); + assert_se(MALLOC_SIZEOF_SAFE(c) >= n_c * sizeof(int)); + assert_se(memcmp_safe(c, before, n_before) == 0); + assert_se(memcmp_safe(&c[n_before], from, n_from) == 0); + + before = mfree(before); + assert_se(!before); + n_before = 0; + assert_se(GREEDY_REALLOC_APPEND(before, n_before, c, n_c)); + assert_se(before); + assert_se(n_before == n_c); + assert_se(memcmp_safe(c, before, n_c) == 0); + + assert_se(GREEDY_REALLOC_APPEND(c, n_c, NULL, 0)); + assert_se(c); + assert_se(n_c == n_before); + assert_se(MALLOC_ELEMENTSOF(c) >= n_c); + assert_se(MALLOC_SIZEOF_SAFE(c) >= n_c * sizeof(int)); + assert_se(memcmp_safe(c, before, n_c) == 0); + } + + for (j = 0; j < i * n_from; j++) + assert_se(c[j] == (int) j); +} + +TEST(memdup_multiply_and_greedy_realloc) { + static const int org[] = { 1, 2, 3 }; + _cleanup_free_ int *dup; + size_t i; + int *p; + + dup = memdup_suffix0_multiply(org, 3, sizeof(int)); + assert_se(dup); + assert_se(dup[0] == 1); + assert_se(dup[1] == 2); + assert_se(dup[2] == 3); + assert_se(((uint8_t*) dup)[sizeof(int) * 3] == 0); + free(dup); + + dup = memdup_multiply(org, 3, sizeof(int)); + assert_se(dup); + assert_se(dup[0] == 1); + assert_se(dup[1] == 2); + assert_se(dup[2] == 3); + + memzero(dup + 3, malloc_usable_size(dup) - sizeof(int) * 3); + + p = dup; + assert_se(GREEDY_REALLOC0(dup, 2) == p); + + p = GREEDY_REALLOC0(dup, 10); + assert_se(p == dup); + assert_se(MALLOC_ELEMENTSOF(p) >= 10); + assert_se(p[0] == 1); + assert_se(p[1] == 2); + assert_se(p[2] == 3); + for (i = 3; i < MALLOC_ELEMENTSOF(p); i++) + assert_se(p[i] == 0); +} + +TEST(bool_assign) { + bool b, c, *cp = &c, d, e, f, g, h; + + b = 123; + *cp = -11; + d = 0xF & 0xFF; + e = b & d; + f = 0x0; + g = cp; /* cast from pointer */ + h = NULL; /* cast from pointer */ + + assert_se(b); + assert_se(c); + assert_se(d); + assert_se(e); + assert_se(!f); + assert_se(g); + assert_se(!h); +} + +static int cleanup_counter = 0; + +static void cleanup1(void *a) { + log_info("%s(%p)", __func__, a); + assert_se(++cleanup_counter == *(int*) a); +} +static void cleanup2(void *a) { + log_info("%s(%p)", __func__, a); + assert_se(++cleanup_counter == *(int*) a); +} +static void cleanup3(void *a) { + log_info("%s(%p)", __func__, a); + assert_se(++cleanup_counter == *(int*) a); +} + +TEST(cleanup_order) { + _cleanup_(cleanup1) int x1 = 4, x2 = 3; + _cleanup_(cleanup3) int z = 2; + _cleanup_(cleanup2) int y = 1; + log_debug("x1: %p", &x1); + log_debug("x2: %p", &x2); + log_debug("y: %p", &y); + log_debug("z: %p", &z); +} + +TEST(auto_erase_memory) { + _cleanup_(erase_and_freep) uint8_t *p1, *p2; + + /* print address of p2, else e.g. clang-11 will optimize it out */ + log_debug("p1: %p p2: %p", &p1, &p2); + + assert_se(p1 = new(uint8_t, 4703)); /* use prime size, to ensure that there will be free space at the + * end of the allocation, since malloc() enforces alignment */ + assert_se(p2 = new(uint8_t, 4703)); + + assert_se(crypto_random_bytes(p1, 4703) == 0); + + /* before we exit the scope, do something with this data, so that the compiler won't optimize this away */ + memcpy(p2, p1, 4703); + for (size_t i = 0; i < 4703; i++) + assert_se(p1[i] == p2[i]); +} + +#define TEST_SIZES(f, n) \ + do { \ + log_debug("requested=%zu vs. malloc_size=%zu vs. gcc_size=%zu", \ + n * sizeof(*f), \ + malloc_usable_size(f), \ + __builtin_object_size(f, 0)); \ + assert_se(MALLOC_ELEMENTSOF(f) >= n); \ + assert_se(MALLOC_SIZEOF_SAFE(f) >= sizeof(*f) * n); \ + assert_se(malloc_usable_size(f) >= sizeof(*f) * n); \ + assert_se(__builtin_object_size(f, 0) >= sizeof(*f) * n); \ + } while (false) + +TEST(malloc_size_safe) { + _cleanup_free_ uint32_t *f = NULL; + size_t n = 4711; + + /* Let's check the macros and built-ins work on NULL and return the expected values */ + assert_se(MALLOC_ELEMENTSOF((float*) NULL) == 0); + assert_se(MALLOC_SIZEOF_SAFE((float*) NULL) == 0); + assert_se(malloc_usable_size(NULL) == 0); /* as per man page, this is safe and defined */ + assert_se(__builtin_object_size(NULL, 0) == SIZE_MAX); /* as per docs SIZE_MAX is returned for pointers where the size isn't known */ + + /* Then, let's try these macros once with constant size values, so that __builtin_object_size() + * definitely can work (as long as -O2 is used when compiling) */ + assert_se(f = new(uint32_t, n)); + TEST_SIZES(f, n); + + /* Finally, let's use some dynamically sized allocations, to make sure this doesn't deteriorate */ + for (unsigned i = 0; i < 50; i++) { + _cleanup_free_ uint64_t *g = NULL; + size_t m; + + m = random_u64_range(16*1024); + assert_se(g = new(uint64_t, m)); + TEST_SIZES(g, m); + } +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-architecture.c b/src/test/test-architecture.c new file mode 100644 index 0000000..8731e1c --- /dev/null +++ b/src/test/test-architecture.c @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "architecture.h" +#include "errno-util.h" +#include "log.h" +#include "tests.h" +#include "virt.h" + +int main(int argc, char *argv[]) { + Virtualization v; + Architecture a; + const char *p; + + test_setup_logging(LOG_INFO); + + assert_se(architecture_from_string("") < 0); + assert_se(architecture_from_string(NULL) < 0); + assert_se(architecture_from_string("hoge") < 0); + assert_se(architecture_to_string(-1) == NULL); + assert_se(architecture_from_string(architecture_to_string(0)) == 0); + assert_se(architecture_from_string(architecture_to_string(1)) == 1); + + v = detect_virtualization(); + if (ERRNO_IS_NEG_PRIVILEGE(v)) + return log_tests_skipped("Cannot detect virtualization"); + + assert_se(v >= 0); + + log_info("virtualization=%s id=%s", + VIRTUALIZATION_IS_CONTAINER(v) ? "container" : + VIRTUALIZATION_IS_VM(v) ? "vm" : "n/a", + virtualization_to_string(v)); + + a = uname_architecture(); + assert_se(a >= 0); + + p = architecture_to_string(a); + assert_se(p); + log_info("uname architecture=%s", p); + assert_se(architecture_from_string(p) == a); + + a = native_architecture(); + assert_se(a >= 0); + + p = architecture_to_string(a); + assert_se(p); + log_info("native architecture=%s", p); + assert_se(architecture_from_string(p) == a); + + log_info("primary library architecture=" LIB_ARCH_TUPLE); + + return 0; +} diff --git a/src/test/test-argv-util.c b/src/test/test-argv-util.c new file mode 100644 index 0000000..5bf2903 --- /dev/null +++ b/src/test/test-argv-util.c @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <sched.h> +#include <unistd.h> + +#if HAVE_VALGRIND_VALGRIND_H +# include <valgrind/valgrind.h> +#endif + +#include "argv-util.h" +#include "missing_sched.h" +#include "process-util.h" +#include "tests.h" +#include "virt.h" + +static void test_rename_process_now(const char *p, int ret) { + _cleanup_free_ char *comm = NULL, *cmdline = NULL; + int r; + + log_info("/* %s(%s) */", __func__, p); + + r = rename_process(p); + assert_se(r == ret || + (ret == 0 && r >= 0) || + (ret > 0 && r > 0)); + + log_debug_errno(r, "rename_process(%s): %m", p); + + if (r < 0) + return; + +#if HAVE_VALGRIND_VALGRIND_H + /* see above, valgrind is weird, we can't verify what we are doing here */ + if (RUNNING_ON_VALGRIND) + return; +#endif + + assert_se(pid_get_comm(0, &comm) >= 0); + log_debug("comm = <%s>", comm); + assert_se(strneq(comm, p, TASK_COMM_LEN-1)); + /* We expect comm to be at most 16 bytes (TASK_COMM_LEN). The kernel may raise this limit in the + * future. We'd only check the initial part, at least until we recompile, but this will still pass. */ + + r = pid_get_cmdline(0, SIZE_MAX, 0, &cmdline); + assert_se(r >= 0); + /* we cannot expect cmdline to be renamed properly without privileges */ + if (geteuid() == 0) { + if (r == 0 && detect_container() > 0) + log_info("cmdline = <%s> (not verified, Running in unprivileged container?)", cmdline); + else { + log_info("cmdline = <%s> (expected <%.*s>)", cmdline, (int) strlen("test-process-util"), p); + + bool skip = cmdline[0] == '"'; /* A shortcut to check if the string is quoted */ + + assert_se(strneq(cmdline + skip, p, strlen("test-process-util"))); + assert_se(startswith(cmdline + skip, p)); + } + } else + log_info("cmdline = <%s> (not verified)", cmdline); +} + +static void test_rename_process_one(const char *p, int ret) { + siginfo_t si; + pid_t pid; + + log_info("/* %s(%s) */", __func__, p); + + pid = fork(); + assert_se(pid >= 0); + + if (pid == 0) { + /* child */ + test_rename_process_now(p, ret); + _exit(EXIT_SUCCESS); + } + + assert_se(wait_for_terminate(pid, &si) >= 0); + assert_se(si.si_code == CLD_EXITED); + assert_se(si.si_status == EXIT_SUCCESS); +} + +TEST(rename_process_invalid) { + assert_se(rename_process(NULL) == -EINVAL); + assert_se(rename_process("") == -EINVAL); +} + +TEST(rename_process_multi) { + pid_t pid; + + pid = fork(); + assert_se(pid >= 0); + + if (pid > 0) { + siginfo_t si; + + assert_se(wait_for_terminate(pid, &si) >= 0); + assert_se(si.si_code == CLD_EXITED); + assert_se(si.si_status == EXIT_SUCCESS); + + return; + } + + /* child */ + test_rename_process_now("one", 1); + test_rename_process_now("more", 0); /* longer than "one", hence truncated */ + (void) setresuid(99, 99, 99); /* change uid when running privileged */ + test_rename_process_now("time!", 0); + test_rename_process_now("0", 1); /* shorter than "one", should fit */ + _exit(EXIT_SUCCESS); +} + +TEST(rename_process) { + test_rename_process_one("foo", 1); /* should always fit */ + test_rename_process_one("this is a really really long process name, followed by some more words", 0); /* unlikely to fit */ + test_rename_process_one("1234567", 1); /* should always fit */ +} + +TEST(argv_help) { + assert_se(argv_looks_like_help(1, STRV_MAKE("program"))); + assert_se(argv_looks_like_help(2, STRV_MAKE("program", "help"))); + assert_se(argv_looks_like_help(3, STRV_MAKE("program", "arg1", "--help"))); + assert_se(argv_looks_like_help(4, STRV_MAKE("program", "arg1", "arg2", "-h"))); + assert_se(!argv_looks_like_help(2, STRV_MAKE("program", "arg1"))); + assert_se(!argv_looks_like_help(4, STRV_MAKE("program", "arg1", "arg2", "--h"))); +} + +static int intro(void) { + log_show_color(true); + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/test/test-arphrd-util.c b/src/test/test-arphrd-util.c new file mode 100644 index 0000000..d8dd464 --- /dev/null +++ b/src/test/test-arphrd-util.c @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <linux/if_arp.h> + +#include "arphrd-util.h" +#include "string-util.h" +#include "tests.h" + +TEST(arphrd) { + for (int i = 0; i <= ARPHRD_VOID + 1; i++) { + const char *name; + + name = arphrd_to_name(i); + if (name) { + log_info("%i: %s", i, name); + + assert_se(arphrd_from_name(name) == i); + } + } + + assert_se(arphrd_to_name(ARPHRD_VOID + 1) == NULL); + assert_se(arphrd_from_name("huddlduddl") == -EINVAL); + assert_se(arphrd_from_name("") == -EINVAL); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-ask-password-api.c b/src/test/test-ask-password-api.c new file mode 100644 index 0000000..b24159e --- /dev/null +++ b/src/test/test-ask-password-api.c @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "ask-password-api.h" +#include "strv.h" +#include "tests.h" + +TEST(ask_password) { + int r; + _cleanup_strv_free_ char **ret = NULL; + + r = ask_password_tty(-1, "hello?", "da key", 0, ASK_PASSWORD_CONSOLE_COLOR, NULL, &ret); + if (r == -ECANCELED) + assert_se(ret == NULL); + else { + assert_se(r >= 0); + assert_se(strv_length(ret) == 1); + log_info("Got \"%s\"", *ret); + } +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-async.c b/src/test/test-async.c new file mode 100644 index 0000000..75bc4d8 --- /dev/null +++ b/src/test/test-async.c @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <fcntl.h> +#include <sys/prctl.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "async.h" +#include "fs-util.h" +#include "path-util.h" +#include "process-util.h" +#include "signal-util.h" +#include "tests.h" +#include "tmpfile-util.h" + +TEST(asynchronous_sync) { + assert_se(asynchronous_sync(NULL) >= 0); +} + +TEST(asynchronous_close) { + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-asynchronous_close.XXXXXX"; + int fd, r; + + fd = mkostemp_safe(name); + assert_se(fd >= 0); + asynchronous_close(fd); + + sleep(1); + + assert_se(fcntl(fd, F_GETFD) == -1); + assert_se(errno == EBADF); + + r = safe_fork("(subreaper)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_LOG|FORK_WAIT, NULL); + assert(r >= 0); + + if (r == 0) { + /* child */ + + assert(make_reaper_process(true) >= 0); + + fd = open("/dev/null", O_RDONLY|O_CLOEXEC); + assert_se(fd >= 0); + asynchronous_close(fd); + + sleep(1); + + assert_se(fcntl(fd, F_GETFD) == -1); + assert_se(errno == EBADF); + + _exit(EXIT_SUCCESS); + } +} + +TEST(asynchronous_rm_rf) { + _cleanup_free_ char *t = NULL, *k = NULL; + int r; + + assert_se(mkdtemp_malloc(NULL, &t) >= 0); + assert_se(k = path_join(t, "somefile")); + assert_se(touch(k) >= 0); + assert_se(asynchronous_rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + + /* Do this once more, form a subreaper. Which is nice, because we can watch the async child even + * though detached */ + + r = safe_fork("(subreaper)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL); + assert_se(r >= 0); + + if (r == 0) { + _cleanup_free_ char *tt = NULL, *kk = NULL; + + /* child */ + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0); + assert_se(make_reaper_process(true) >= 0); + + assert_se(mkdtemp_malloc(NULL, &tt) >= 0); + assert_se(kk = path_join(tt, "somefile")); + assert_se(touch(kk) >= 0); + assert_se(asynchronous_rm_rf(tt, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + + for (;;) { + siginfo_t si = {}; + + assert_se(waitid(P_ALL, 0, &si, WEXITED) >= 0); + + if (access(tt, F_OK) < 0) { + assert_se(errno == ENOENT); + break; + } + + /* wasn't the rm_rf() call. let's wait longer */ + } + + _exit(EXIT_SUCCESS); + } +} + + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-barrier.c b/src/test/test-barrier.c new file mode 100644 index 0000000..7e8bfc0 --- /dev/null +++ b/src/test/test-barrier.c @@ -0,0 +1,441 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* + * IPC barrier tests + * These tests verify the correct behavior of the IPC Barrier implementation. + * Note that the tests use alarm-timers to verify dead-locks and timeouts. These + * might not work on slow machines where 20ms are too short to perform specific + * operations (though, very unlikely). In case that turns out true, we have to + * increase it at the slightly cost of lengthen test-duration on other machines. + */ + +#include <stdio.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "barrier.h" +#include "errno-util.h" +#include "tests.h" +#include "time-util.h" +#include "virt.h" + +/* 20ms to test deadlocks; All timings use multiples of this constant as + * alarm/sleep timers. If this timeout is too small for slow machines to perform + * the requested operations, we have to increase it. On an i7 this works fine + * with 1ms base-time, so 20ms should be just fine for everyone. */ +#define BASE_TIME (20 * USEC_PER_MSEC) + +static void set_alarm(usec_t usecs) { + struct itimerval v = { }; + + timeval_store(&v.it_value, usecs); + assert_se(setitimer(ITIMER_REAL, &v, NULL) >= 0); +} + +#define TEST_BARRIER(_FUNCTION, _CHILD_CODE, _WAIT_CHILD, _PARENT_CODE, _WAIT_PARENT) \ + TEST(_FUNCTION) { \ + Barrier b = BARRIER_NULL; \ + pid_t pid1, pid2; \ + \ + assert_se(barrier_create(&b) >= 0); \ + assert_se(b.me > 0); \ + assert_se(b.them > 0); \ + assert_se(b.pipe[0] > 0); \ + assert_se(b.pipe[1] > 0); \ + \ + pid1 = fork(); \ + assert_se(pid1 >= 0); \ + if (pid1 == 0) { \ + barrier_set_role(&b, BARRIER_CHILD); \ + { _CHILD_CODE; } \ + exit(42); \ + } \ + \ + pid2 = fork(); \ + assert_se(pid2 >= 0); \ + if (pid2 == 0) { \ + barrier_set_role(&b, BARRIER_PARENT); \ + { _PARENT_CODE; } \ + exit(42); \ + } \ + \ + barrier_destroy(&b); \ + set_alarm(999999); \ + { _WAIT_CHILD; } \ + { _WAIT_PARENT; } \ + set_alarm(0); \ + } + +#define TEST_BARRIER_WAIT_SUCCESS(_pid) \ + ({ \ + int pidr, status; \ + pidr = waitpid(_pid, &status, 0); \ + assert_se(pidr == _pid); \ + assert_se(WIFEXITED(status)); \ + assert_se(WEXITSTATUS(status) == 42); \ + }) + +#define TEST_BARRIER_WAIT_ALARM(_pid) \ + ({ \ + int pidr, status; \ + pidr = waitpid(_pid, &status, 0); \ + assert_se(pidr == _pid); \ + assert_se(WIFSIGNALED(status)); \ + assert_se(WTERMSIG(status) == SIGALRM); \ + }) + +/* + * Test basic sync points + * This places a barrier in both processes and waits synchronously for them. + * The timeout makes sure the sync works as expected. The usleep_safe() on one side + * makes sure the exit of the parent does not overwrite previous barriers. Due + * to the usleep_safe(), we know that the parent already exited, thus there's a + * pending HUP on the pipe. However, the barrier_sync() prefers reads on the + * eventfd, thus we can safely wait on the barrier. + */ +TEST_BARRIER(barrier_sync, + ({ + set_alarm(BASE_TIME * 10); + assert_se(barrier_place(&b)); + usleep_safe(BASE_TIME * 2); + assert_se(barrier_sync(&b)); + }), + TEST_BARRIER_WAIT_SUCCESS(pid1), + ({ + set_alarm(BASE_TIME * 10); + assert_se(barrier_place(&b)); + assert_se(barrier_sync(&b)); + }), + TEST_BARRIER_WAIT_SUCCESS(pid2)); + +/* + * Test wait_next() + * This places a barrier in the parent and syncs on it. The child sleeps while + * the parent places the barrier and then waits for a barrier. The wait will + * succeed as the child hasn't read the parent's barrier, yet. The following + * barrier and sync synchronize the exit. + */ +TEST_BARRIER(barrier_wait_next, + ({ + usleep_safe(BASE_TIME); + set_alarm(BASE_TIME * 10); + assert_se(barrier_wait_next(&b)); + assert_se(barrier_place(&b)); + assert_se(barrier_sync(&b)); + }), + TEST_BARRIER_WAIT_SUCCESS(pid1), + ({ + set_alarm(BASE_TIME * 4); + assert_se(barrier_place(&b)); + assert_se(barrier_sync(&b)); + }), + TEST_BARRIER_WAIT_SUCCESS(pid2)); + +/* + * Test wait_next() multiple times + * This places two barriers in the parent and waits for the child to exit. The + * child sleeps 20ms so both barriers _should_ be in place. It then waits for + * the parent to place the next barrier twice. The first call will fetch both + * barriers and return. However, the second call will stall as the parent does + * not place a 3rd barrier (the sleep caught two barriers). wait_next() is does + * not look at barrier-links so this stall is expected. Thus this test times + * out. + */ +TEST_BARRIER(barrier_wait_next_twice, + ({ + usleep_safe(BASE_TIME); + set_alarm(BASE_TIME); + assert_se(barrier_wait_next(&b)); + assert_se(barrier_wait_next(&b)); + assert_se(0); + }), + TEST_BARRIER_WAIT_ALARM(pid1), + ({ + set_alarm(BASE_TIME * 10); + assert_se(barrier_place(&b)); + assert_se(barrier_place(&b)); + usleep_safe(BASE_TIME * 4); + }), + TEST_BARRIER_WAIT_SUCCESS(pid2)); + +/* + * Test wait_next() with local barriers + * This is the same as test_barrier_wait_next_twice, but places local barriers + * between both waits. This does not have any effect on the wait so it times out + * like the other test. + */ +TEST_BARRIER(barrier_wait_next_twice_local, + ({ + usleep_safe(BASE_TIME); + set_alarm(BASE_TIME); + assert_se(barrier_wait_next(&b)); + assert_se(barrier_place(&b)); + assert_se(barrier_place(&b)); + assert_se(barrier_wait_next(&b)); + assert_se(0); + }), + TEST_BARRIER_WAIT_ALARM(pid1), + ({ + set_alarm(BASE_TIME * 10); + assert_se(barrier_place(&b)); + assert_se(barrier_place(&b)); + usleep_safe(BASE_TIME * 4); + }), + TEST_BARRIER_WAIT_SUCCESS(pid2)); + +/* + * Test wait_next() with sync_next() + * This is again the same as test_barrier_wait_next_twice but uses a + * synced wait as the second wait. This works just fine because the local state + * has no barriers placed, therefore, the remote is always in sync. + */ +TEST_BARRIER(barrier_wait_next_twice_sync, + ({ + usleep_safe(BASE_TIME); + set_alarm(BASE_TIME); + assert_se(barrier_wait_next(&b)); + assert_se(barrier_sync_next(&b)); + }), + TEST_BARRIER_WAIT_SUCCESS(pid1), + ({ + set_alarm(BASE_TIME * 10); + assert_se(barrier_place(&b)); + assert_se(barrier_place(&b)); + }), + TEST_BARRIER_WAIT_SUCCESS(pid2)); + +/* + * Test wait_next() with sync_next() and local barriers + * This is again the same as test_barrier_wait_next_twice_local but uses a + * synced wait as the second wait. This works just fine because the local state + * is in sync with the remote. + */ +TEST_BARRIER(barrier_wait_next_twice_local_sync, + ({ + usleep_safe(BASE_TIME); + set_alarm(BASE_TIME); + assert_se(barrier_wait_next(&b)); + assert_se(barrier_place(&b)); + assert_se(barrier_place(&b)); + assert_se(barrier_sync_next(&b)); + }), + TEST_BARRIER_WAIT_SUCCESS(pid1), + ({ + set_alarm(BASE_TIME * 10); + assert_se(barrier_place(&b)); + assert_se(barrier_place(&b)); + }), + TEST_BARRIER_WAIT_SUCCESS(pid2)); + +/* + * Test sync_next() and sync() + * This tests sync_*() synchronizations and makes sure they work fine if the + * local state is behind the remote state. + */ +TEST_BARRIER(barrier_sync_next, + ({ + set_alarm(BASE_TIME * 10); + assert_se(barrier_sync_next(&b)); + assert_se(barrier_sync(&b)); + assert_se(barrier_place(&b)); + assert_se(barrier_place(&b)); + assert_se(barrier_sync_next(&b)); + assert_se(barrier_sync_next(&b)); + assert_se(barrier_sync(&b)); + }), + TEST_BARRIER_WAIT_SUCCESS(pid1), + ({ + set_alarm(BASE_TIME * 10); + usleep_safe(BASE_TIME); + assert_se(barrier_place(&b)); + assert_se(barrier_place(&b)); + assert_se(barrier_sync(&b)); + }), + TEST_BARRIER_WAIT_SUCCESS(pid2)); + +/* + * Test sync_next() and sync() with local barriers + * This tests timeouts if sync_*() is used if local barriers are placed but the + * remote didn't place any. + */ +TEST_BARRIER(barrier_sync_next_local, + ({ + set_alarm(BASE_TIME); + assert_se(barrier_place(&b)); + assert_se(barrier_sync_next(&b)); + assert_se(0); + }), + TEST_BARRIER_WAIT_ALARM(pid1), + ({ + usleep_safe(BASE_TIME * 2); + }), + TEST_BARRIER_WAIT_SUCCESS(pid2)); + +/* + * Test sync_next() and sync() with local barriers and abortion + * This is the same as test_barrier_sync_next_local but aborts the sync in the + * parent. Therefore, the sync_next() succeeds just fine due to the abortion. + */ +TEST_BARRIER(barrier_sync_next_local_abort, + ({ + set_alarm(BASE_TIME * 10); + assert_se(barrier_place(&b)); + assert_se(!barrier_sync_next(&b)); + }), + TEST_BARRIER_WAIT_SUCCESS(pid1), + ({ + assert_se(barrier_abort(&b)); + }), + TEST_BARRIER_WAIT_SUCCESS(pid2)); + +/* + * Test matched wait_abortion() + * This runs wait_abortion() with remote abortion. + */ +TEST_BARRIER(barrier_wait_abortion, + ({ + set_alarm(BASE_TIME * 10); + assert_se(barrier_wait_abortion(&b)); + }), + TEST_BARRIER_WAIT_SUCCESS(pid1), + ({ + assert_se(barrier_abort(&b)); + }), + TEST_BARRIER_WAIT_SUCCESS(pid2)); + +/* + * Test unmatched wait_abortion() + * This runs wait_abortion() without any remote abortion going on. It thus must + * timeout. + */ +TEST_BARRIER(barrier_wait_abortion_unmatched, + ({ + set_alarm(BASE_TIME); + assert_se(barrier_wait_abortion(&b)); + assert_se(0); + }), + TEST_BARRIER_WAIT_ALARM(pid1), + ({ + usleep_safe(BASE_TIME * 2); + }), + TEST_BARRIER_WAIT_SUCCESS(pid2)); + +/* + * Test matched wait_abortion() with local abortion + * This runs wait_abortion() with local and remote abortion. + */ +TEST_BARRIER(barrier_wait_abortion_local, + ({ + set_alarm(BASE_TIME * 10); + assert_se(barrier_abort(&b)); + assert_se(!barrier_wait_abortion(&b)); + }), + TEST_BARRIER_WAIT_SUCCESS(pid1), + ({ + assert_se(barrier_abort(&b)); + }), + TEST_BARRIER_WAIT_SUCCESS(pid2)); + +/* + * Test unmatched wait_abortion() with local abortion + * This runs wait_abortion() with only local abortion. This must time out. + */ +TEST_BARRIER(barrier_wait_abortion_local_unmatched, + ({ + set_alarm(BASE_TIME); + assert_se(barrier_abort(&b)); + assert_se(!barrier_wait_abortion(&b)); + assert_se(0); + }), + TEST_BARRIER_WAIT_ALARM(pid1), + ({ + usleep_safe(BASE_TIME * 2); + }), + TEST_BARRIER_WAIT_SUCCESS(pid2)); + +/* + * Test child exit + * Place barrier and sync with the child. The child only exits()s, which should + * cause an implicit abortion and wake the parent. + */ +TEST_BARRIER(barrier_exit, + ({ + }), + TEST_BARRIER_WAIT_SUCCESS(pid1), + ({ + set_alarm(BASE_TIME * 10); + assert_se(barrier_place(&b)); + assert_se(!barrier_sync(&b)); + }), + TEST_BARRIER_WAIT_SUCCESS(pid2)); + +/* + * Test child exit with sleep + * Same as test_barrier_exit but verifies the test really works due to the + * child-exit. We add a usleep_safe() which triggers the alarm in the parent and + * causes the test to time out. + */ +TEST_BARRIER(barrier_no_exit, + ({ + usleep_safe(BASE_TIME * 2); + }), + TEST_BARRIER_WAIT_SUCCESS(pid1), + ({ + set_alarm(BASE_TIME); + assert_se(barrier_place(&b)); + assert_se(!barrier_sync(&b)); + }), + TEST_BARRIER_WAIT_ALARM(pid2)); + +/* + * Test pending exit against sync + * The parent places a barrier *and* exits. The 20ms wait in the child + * guarantees both are pending. However, our logic prefers pending barriers over + * pending exit-abortions (unlike normal abortions), thus the wait_next() must + * succeed, same for the sync_next() as our local barrier-count is smaller than + * the remote. Once we place a barrier our count is equal, so the sync still + * succeeds. Only if we place one more barrier, we're ahead of the remote, thus + * we will fail due to HUP on the pipe. + */ +TEST_BARRIER(barrier_pending_exit, + ({ + set_alarm(BASE_TIME * 4); + usleep_safe(BASE_TIME * 2); + assert_se(barrier_wait_next(&b)); + assert_se(barrier_sync_next(&b)); + assert_se(barrier_place(&b)); + assert_se(barrier_sync_next(&b)); + assert_se(barrier_place(&b)); + assert_se(!barrier_sync_next(&b)); + }), + TEST_BARRIER_WAIT_SUCCESS(pid1), + ({ + assert_se(barrier_place(&b)); + }), + TEST_BARRIER_WAIT_SUCCESS(pid2)); + + +static int intro(void) { + if (!slow_tests_enabled()) + return log_tests_skipped("slow tests are disabled"); + + /* + * This test uses real-time alarms and sleeps to test for CPU races explicitly. This is highly + * fragile if your system is under load. We already increased the BASE_TIME value to make the tests + * more robust, but that just makes the test take significantly longer. Given the recent issues when + * running the test in a virtualized environments, limit it to bare metal machines only, to minimize + * false-positives in CIs. + */ + + Virtualization v = detect_virtualization(); + if (ERRNO_IS_NEG_PRIVILEGE(v)) + return log_tests_skipped("Cannot detect virtualization"); + + if (v != VIRTUALIZATION_NONE) + return log_tests_skipped("This test requires a baremetal machine"); + + return EXIT_SUCCESS; + } + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/test/test-bitfield.c b/src/test/test-bitfield.c new file mode 100644 index 0000000..f26b423 --- /dev/null +++ b/src/test/test-bitfield.c @@ -0,0 +1,230 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stddef.h> + +#include "bitfield.h" +#include "log.h" +#include "tests.h" + +#define TEST_BITS(bits, v, ...) \ + ({ \ + assert_se((!!BITS_SET(bits, ##__VA_ARGS__)) == v); \ + assert_se((!!BITS_SET(~(bits), ##__VA_ARGS__)) == !v); \ + }) +#define TEST_BIT(bits, v, i) \ + ({ \ + assert_se((!!BIT_SET(bits, i)) == v); \ + assert_se((!!BIT_SET(~(bits), i)) == !v); \ + TEST_BITS(bits, v, i); \ + }) + +#define TEST_BIT_SET(bits, i) TEST_BIT(bits, 1, i) +#define TEST_BIT_CLEAR(bits, i) TEST_BIT(bits, 0, i) + +#define TEST_BITS_SET(bits, ...) TEST_BITS(bits, 1, ##__VA_ARGS__) +#define TEST_BITS_CLEAR(bits, ...) TEST_BITS(bits, 0, ##__VA_ARGS__) + +TEST(bits) { + int count; + + /* Test uint8_t */ + TEST_BIT_SET(0x81, 0); + TEST_BIT_SET(0x81, 7); + TEST_BITS_SET(0x81, 0, 7); + TEST_BIT_CLEAR(0x81, 4); + TEST_BIT_CLEAR(0x81, 6); + TEST_BITS_CLEAR(0x81, 1, 2, 3, 4, 5, 6); + uint8_t expected8 = 0; + BIT_FOREACH(i, 0x81) + expected8 |= UINT8_C(1) << i; + assert_se(expected8 == 0x81); + uint8_t u8 = 0x91; + TEST_BIT_SET(u8, 4); + TEST_BITS_SET(u8, 0, 4, 7); + TEST_BIT_CLEAR(u8, 2); + TEST_BITS_CLEAR(u8, 1, 2, 3, 5, 6); + SET_BIT(u8, 1); + TEST_BITS_SET(u8, 0, 1, 4, 7); + TEST_BITS_CLEAR(u8, 2, 3, 5, 6); + SET_BITS(u8, 3, 5); + TEST_BITS_SET(u8, 0, 1, 3, 4, 5, 7); + TEST_BITS_CLEAR(u8, 2, 6); + CLEAR_BIT(u8, 4); + TEST_BITS_SET(u8, 0, 1, 3, 5, 7); + TEST_BITS_CLEAR(u8, 2, 4, 6); + CLEAR_BITS(u8, 1); + CLEAR_BITS(u8, 0, 7); + TEST_BITS_SET(u8, 3, 5); + TEST_BITS_CLEAR(u8, 0, 1, 2, 4, 6, 7); + expected8 = 0; + BIT_FOREACH(i, u8) + expected8 |= UINT8_C(1) << i; + assert_se(expected8 == u8); + u8 = 0; + TEST_BITS_CLEAR(u8, 0, 1, 2, 3, 4, 5, 6, 7); + BIT_FOREACH(i, u8) + assert_se(0); + u8 = ~u8; + TEST_BITS_SET(u8, 0, 1, 2, 3, 4, 5, 6, 7); + count = 0; + BIT_FOREACH(i, u8) + count++; + assert_se(count == 8); + uint8_t _u8 = u8; + SET_BITS(u8); + assert_se(_u8 == u8); + CLEAR_BITS(u8); + assert_se(_u8 == u8); + + /* Test uint16_t */ + TEST_BIT_SET(0x1f81, 10); + TEST_BITS_SET(0x1f81, 0, 7, 8, 9, 10, 11, 12); + TEST_BIT_CLEAR(0x1f81, 13); + TEST_BITS_CLEAR(0x1f81, 1, 2, 3, 4, 5, 6, 13, 14, 15); + uint16_t expected16 = 0; + BIT_FOREACH(i, 0x1f81) + expected16 |= UINT16_C(1) << i; + assert_se(expected16 == 0x1f81); + uint16_t u16 = 0xf060; + TEST_BIT_SET(u16, 12); + TEST_BITS_SET(u16, 5, 6, 12, 13, 14, 15); + TEST_BIT_CLEAR(u16, 9); + TEST_BITS_CLEAR(u16, 0, 1, 2, 3, 4, 7, 8, 9, 10, 11); + SET_BITS(u16, 1, 8); + TEST_BITS_SET(u16, 1, 5, 6, 8, 12, 13, 14, 15); + TEST_BITS_CLEAR(u16, 0, 2, 3, 4, 7, 9, 10, 11); + CLEAR_BITS(u16, 13, 14); + TEST_BITS_SET(u16, 1, 5, 6, 8, 12, 15); + TEST_BITS_CLEAR(u16, 0, 2, 3, 4, 7, 9, 10, 11, 13, 14); + expected16 = 0; + BIT_FOREACH(i, u16) + expected16 |= UINT16_C(1) << i; + assert_se(expected16 == u16); + u16 = 0; + TEST_BITS_CLEAR(u16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + BIT_FOREACH(i, u16) + assert_se(0); + u16 = ~u16; + TEST_BITS_SET(u16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + count = 0; + BIT_FOREACH(i, u16) + count++; + assert_se(count == 16); + uint16_t _u16 = u16; + SET_BITS(u16); + assert_se(_u16 == u16); + CLEAR_BITS(u16); + assert_se(_u16 == u16); + + /* Test uint32_t */ + TEST_BIT_SET(0x80224f10, 11); + TEST_BITS_SET(0x80224f10, 4, 8, 9, 10, 11, 14, 17, 21, 31); + TEST_BIT_CLEAR(0x80224f10, 28); + TEST_BITS_CLEAR(0x80224f10, 0, 1, 2, 3, 5, 6, 7, 12, 13, 15, 16, 18, 19, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30); + uint32_t expected32 = 0; + BIT_FOREACH(i, 0x80224f10) + expected32 |= UINT32_C(1) << i; + assert_se(expected32 == 0x80224f10); + uint32_t u32 = 0x605e0388; + TEST_BIT_SET(u32, 3); + TEST_BIT_SET(u32, 30); + TEST_BITS_SET(u32, 3, 7, 8, 9, 17, 18, 19, 20, 22, 29, 30); + TEST_BIT_CLEAR(u32, 0); + TEST_BIT_CLEAR(u32, 31); + TEST_BITS_CLEAR(u32, 0, 1, 2, 4, 5, 6, 10, 11, 12, 13, 14, 15, 16, 21, 23, 24, 25, 26, 27, 28, 31); + SET_BITS(u32, 1, 25, 26); + TEST_BITS_SET(u32, 1, 3, 7, 8, 9, 17, 18, 19, 20, 22, 25, 26, 29, 30); + TEST_BITS_CLEAR(u32, 0, 2, 4, 5, 6, 10, 11, 12, 13, 14, 15, 16, 21, 23, 24, 27, 28, 31); + CLEAR_BITS(u32, 29, 17, 1); + TEST_BITS_SET(u32, 3, 7, 8, 9, 18, 19, 20, 22, 25, 26, 30); + TEST_BITS_CLEAR(u32, 0, 1, 2, 4, 5, 6, 10, 11, 12, 13, 14, 15, 16, 17, 21, 23, 24, 27, 28, 29, 31); + expected32 = 0; + BIT_FOREACH(i, u32) + expected32 |= UINT32_C(1) << i; + assert_se(expected32 == u32); + u32 = 0; + TEST_BITS_CLEAR(u32, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31); + BIT_FOREACH(i, u32) + assert_se(0); + u32 = ~u32; + TEST_BITS_SET(u32, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31); + count = 0; + BIT_FOREACH(i, u32) + count++; + assert_se(count == 32); + uint32_t _u32 = u32; + SET_BITS(u32); + assert_se(_u32 == u32); + CLEAR_BITS(u32); + assert_se(_u32 == u32); + + /* Test uint64_t */ + TEST_BIT_SET(0x18ba1400f4857460, 60); + TEST_BITS_SET(0x18ba1400f4857460, 5, 6, 10, 12, 13, 14, 16, 18, 23, 26, 28, 29, 30, 31, 42, 44, 49, 51, 52, 53, 55, 59, 60); + TEST_BIT_CLEAR(UINT64_C(0x18ba1400f4857460), 0); + TEST_BIT_CLEAR(UINT64_C(0x18ba1400f4857460), 63); + TEST_BITS_CLEAR(UINT64_C(0x18ba1400f4857460), 0, 1, 2, 3, 4, 7, 8, 9, 11, 15, 17, 19, 20, 21, 22, 24, 25, 27, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 43, 45, 46, 47, 48, 50, 54, 56, 57, 58, 61, 62, 63); + uint64_t expected64 = 0; + BIT_FOREACH(i, 0x18ba1400f4857460) + expected64 |= UINT64_C(1) << i; + assert_se(expected64 == 0x18ba1400f4857460); + uint64_t u64 = 0xa90e2d8507a65739; + TEST_BIT_SET(u64, 0); + TEST_BIT_SET(u64, 63); + TEST_BITS_SET(u64, 0, 3, 4, 5, 8, 9, 10, 12, 14, 17, 18, 21, 23, 24, 25, 26, 32, 34, 39, 40, 42, 43, 45, 49, 50, 51, 56, 59, 61, 63); + TEST_BIT_CLEAR(u64, 1); + TEST_BITS_CLEAR(u64, 1, 2, 6, 7, 11, 13, 15, 16, 19, 20, 22, 27, 28, 29, 30, 31, 33, 35, 36, 37, 38, 41, 44, 46, 47, 48, 52, 53, 54, 55, 57, 58, 60, 62); + SET_BIT(u64, 1); + TEST_BITS_SET(u64, 0, 1, 3, 4, 5, 8, 9, 10, 12, 14, 17, 18, 21, 23, 24, 25, 26, 32, 34, 39, 40, 42, 43, 45, 49, 50, 51, 56, 59, 61, 63); + TEST_BITS_CLEAR(u64, 2, 6, 7, 11, 13, 15, 16, 19, 20, 22, 27, 28, 29, 30, 31, 33, 35, 36, 37, 38, 41, 44, 46, 47, 48, 52, 53, 54, 55, 57, 58, 60, 62); + CLEAR_BIT(u64, 63); + TEST_BITS_SET(u64, 0, 1, 3, 4, 5, 8, 9, 10, 12, 14, 17, 18, 21, 23, 24, 25, 26, 32, 34, 39, 40, 42, 43, 45, 49, 50, 51, 56, 59, 61); + TEST_BITS_CLEAR(u64, 2, 6, 7, 11, 13, 15, 16, 19, 20, 22, 27, 28, 29, 30, 31, 33, 35, 36, 37, 38, 41, 44, 46, 47, 48, 52, 53, 54, 55, 57, 58, 60, 62, 63); + SET_BIT(u64, 62); + TEST_BITS_SET(u64, 0, 1, 3, 4, 5, 8, 9, 10, 12, 14, 17, 18, 21, 23, 24, 25, 26, 32, 34, 39, 40, 42, 43, 45, 49, 50, 51, 56, 59, 61, 62); + TEST_BITS_CLEAR(u64, 2, 6, 7, 11, 13, 15, 16, 19, 20, 22, 27, 28, 29, 30, 31, 33, 35, 36, 37, 38, 41, 44, 46, 47, 48, 52, 53, 54, 55, 57, 58, 60, 63); + SET_BITS(u64, 63, 62, 7, 13, 38, 40); + TEST_BITS_SET(u64, 0, 1, 3, 4, 5, 7, 8, 9, 10, 12, 13, 14, 17, 18, 21, 23, 24, 25, 26, 32, 34, 38, 39, 40, 42, 43, 45, 49, 50, 51, 56, 59, 61, 62, 63); + TEST_BITS_CLEAR(u64, 2, 6, 11, 15, 16, 19, 20, 22, 27, 28, 29, 30, 31, 33, 35, 36, 37, 41, 44, 46, 47, 48, 52, 53, 54, 55, 57, 58, 60); + CLEAR_BIT(u64, 32); + TEST_BITS_SET(u64, 0, 1, 3, 4, 5, 7, 8, 9, 10, 12, 13, 14, 17, 18, 21, 23, 24, 25, 26, 34, 38, 39, 40, 42, 43, 45, 49, 50, 51, 56, 59, 61, 62, 63); + TEST_BITS_CLEAR(u64, 2, 6, 11, 15, 16, 19, 20, 22, 27, 28, 29, 30, 31, 32, 33, 35, 36, 37, 41, 44, 46, 47, 48, 52, 53, 54, 55, 57, 58, 60); + CLEAR_BITS(u64, 0, 2, 11, 63, 32, 58); + TEST_BITS_SET(u64, 1, 3, 4, 5, 7, 8, 9, 10, 12, 13, 14, 17, 18, 21, 23, 24, 25, 26, 34, 38, 39, 40, 42, 43, 45, 49, 50, 51, 56, 59, 61, 62); + TEST_BITS_CLEAR(u64, 0, 2, 6, 11, 15, 16, 19, 20, 22, 27, 28, 29, 30, 31, 32, 33, 35, 36, 37, 41, 44, 46, 47, 48, 52, 53, 54, 55, 57, 58, 60, 63); + expected64 = 0; + BIT_FOREACH(i, u64) + expected64 |= UINT64_C(1) << i; + assert_se(expected64 == u64); + u64 = 0; + TEST_BITS_CLEAR(u64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63); + BIT_FOREACH(i, u64) + assert_se(0); + u64 = ~u64; + TEST_BITS_SET(u64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63); + count = 0; + BIT_FOREACH(i, u64) + count++; + assert_se(count == 64); + uint64_t _u64 = u64; + SET_BITS(u64); + assert_se(_u64 == u64); + CLEAR_BITS(u64); + assert_se(_u64 == u64); + + /* Verify these use cases are constant-folded. */ +#if !defined(__clang__) || (__clang_major__ >= 13) + /* Clang 11 and 12 (and possibly older) do not grok those; skip them. */ + assert_cc(__builtin_constant_p(INDEX_TO_MASK(uint8_t, 1))); + assert_cc(__builtin_constant_p(INDEX_TO_MASK(uint16_t, 1))); + assert_cc(__builtin_constant_p(INDEX_TO_MASK(uint32_t, 1))); + assert_cc(__builtin_constant_p(INDEX_TO_MASK(uint64_t, 1))); + + assert_cc(__builtin_constant_p(BIT_SET((uint8_t)2, 1))); + assert_cc(__builtin_constant_p(BIT_SET((uint16_t)2, 1))); + assert_cc(__builtin_constant_p(BIT_SET((uint32_t)2, 1))); + assert_cc(__builtin_constant_p(BIT_SET((uint64_t)2, 1))); +#endif +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-bitmap.c b/src/test/test-bitmap.c new file mode 100644 index 0000000..8acf833 --- /dev/null +++ b/src/test/test-bitmap.c @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "bitmap.h" +#include "tests.h" + +int main(int argc, const char *argv[]) { + _cleanup_bitmap_free_ Bitmap *b = NULL, *b2 = NULL; + unsigned n = UINT_MAX, i = 0; + + test_setup_logging(LOG_DEBUG); + + b = bitmap_new(); + assert_se(b); + + assert_se(bitmap_ensure_allocated(&b) == 0); + b = bitmap_free(b); + assert_se(bitmap_ensure_allocated(&b) == 0); + + assert_se(bitmap_isset(b, 0) == false); + assert_se(bitmap_isset(b, 1) == false); + assert_se(bitmap_isset(b, 256) == false); + assert_se(bitmap_isclear(b) == true); + + assert_se(bitmap_set(b, 0) == 0); + assert_se(bitmap_isset(b, 0) == true); + assert_se(bitmap_isclear(b) == false); + bitmap_unset(b, 0); + assert_se(bitmap_isset(b, 0) == false); + assert_se(bitmap_isclear(b) == true); + + assert_se(bitmap_set(b, 1) == 0); + assert_se(bitmap_isset(b, 1) == true); + assert_se(bitmap_isclear(b) == false); + bitmap_unset(b, 1); + assert_se(bitmap_isset(b, 1) == false); + assert_se(bitmap_isclear(b) == true); + + assert_se(bitmap_set(b, 256) == 0); + assert_se(bitmap_isset(b, 256) == true); + assert_se(bitmap_isclear(b) == false); + bitmap_unset(b, 256); + assert_se(bitmap_isset(b, 256) == false); + assert_se(bitmap_isclear(b) == true); + + assert_se(bitmap_set(b, 32) == 0); + bitmap_unset(b, 0); + assert_se(bitmap_isset(b, 32) == true); + bitmap_unset(b, 32); + + BITMAP_FOREACH(n, NULL) + assert_not_reached(); + + assert_se(bitmap_set(b, 0) == 0); + assert_se(bitmap_set(b, 1) == 0); + assert_se(bitmap_set(b, 256) == 0); + + BITMAP_FOREACH(n, b) { + assert_se(n == i); + if (i == 0) + i = 1; + else if (i == 1) + i = 256; + else if (i == 256) + i = UINT_MAX; + } + + assert_se(i == UINT_MAX); + + i = 0; + + BITMAP_FOREACH(n, b) { + assert_se(n == i); + if (i == 0) + i = 1; + else if (i == 1) + i = 256; + else if (i == 256) + i = UINT_MAX; + } + + assert_se(i == UINT_MAX); + + b2 = bitmap_copy(b); + assert_se(b2); + assert_se(bitmap_equal(b, b2) == true); + assert_se(bitmap_equal(b, b) == true); + assert_se(bitmap_equal(b, NULL) == false); + assert_se(bitmap_equal(NULL, b) == false); + assert_se(bitmap_equal(NULL, NULL) == true); + + bitmap_clear(b); + assert_se(bitmap_isclear(b) == true); + assert_se(bitmap_equal(b, b2) == false); + b2 = bitmap_free(b2); + + assert_se(bitmap_set(b, UINT_MAX) == -ERANGE); + + b = bitmap_free(b); + assert_se(bitmap_ensure_allocated(&b) == 0); + assert_se(bitmap_ensure_allocated(&b2) == 0); + + assert_se(bitmap_equal(b, b2)); + assert_se(bitmap_set(b, 0) == 0); + bitmap_unset(b, 0); + assert_se(bitmap_equal(b, b2)); + + assert_se(bitmap_set(b, 1) == 0); + bitmap_clear(b); + assert_se(bitmap_equal(b, b2)); + + assert_se(bitmap_set(b, 0) == 0); + assert_se(bitmap_set(b2, 0) == 0); + assert_se(bitmap_equal(b, b2)); + + return 0; +} diff --git a/src/test/test-blockdev-util.c b/src/test/test-blockdev-util.c new file mode 100644 index 0000000..134386c --- /dev/null +++ b/src/test/test-blockdev-util.c @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "blockdev-util.h" +#include "errno-util.h" +#include "tests.h" + +static void test_path_is_encrypted_one(const char *p, int expect) { + int r; + + r = path_is_encrypted(p); + if (IN_SET(r, -ENOENT, -ELOOP) || ERRNO_IS_NEG_PRIVILEGE(r)) + /* This might fail, if btrfs is used and we run in a container. In that case we cannot + * resolve the device node paths that BTRFS_IOC_DEV_INFO returns, because the device nodes + * are unlikely to exist in the container. But if we can't stat() them we cannot determine + * the dev_t of them, and thus cannot figure out if they are encrypted. Hence let's just + * ignore ENOENT here. Also skip the test if we lack privileges. + * ELOOP might happen if the mount point is a symlink, as seen with under + * some rpm-ostree distros */ + return; + assert_se(r >= 0); + + log_info("%s encrypted: %s", p, yes_no(r)); + + assert_se(expect < 0 || ((r > 0) == (expect > 0))); +} + +TEST(path_is_encrypted) { + int booted = sd_booted(); /* If this is run in build environments such as koji, /dev/ might be a + * regular fs. Don't assume too much if not running under systemd. */ + + log_info("/* %s (sd_booted=%d) */", __func__, booted); + + test_path_is_encrypted_one("/home", -1); + test_path_is_encrypted_one("/var", -1); + test_path_is_encrypted_one("/", -1); + test_path_is_encrypted_one("/proc", false); + test_path_is_encrypted_one("/sys", false); + test_path_is_encrypted_one("/dev", booted > 0 ? false : -1); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-boot-timestamps.c b/src/test/test-boot-timestamps.c new file mode 100644 index 0000000..c3e4876 --- /dev/null +++ b/src/test/test-boot-timestamps.c @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <sys/types.h> +#include <unistd.h> + +#include "acpi-fpdt.h" +#include "boot-timestamps.h" +#include "efi-loader.h" +#include "errno-util.h" +#include "log.h" +#include "tests.h" + +static int test_acpi_fpdt(void) { + usec_t loader_start, loader_exit; + int r; + + r = acpi_get_boot_usec(&loader_start, &loader_exit); + if (r < 0) { + bool ok = IN_SET(r, -ENOENT, -ENODATA, -ERANGE) || ERRNO_IS_PRIVILEGE(r); + + log_full_errno(ok ? LOG_DEBUG : LOG_ERR, r, "Failed to read ACPI FPDT: %m"); + return ok ? 0 : r; + } + + log_info("ACPI FPDT: loader start=%s exit=%s duration=%s", + FORMAT_TIMESPAN(loader_start, USEC_PER_MSEC), + FORMAT_TIMESPAN(loader_exit, USEC_PER_MSEC), + FORMAT_TIMESPAN(loader_exit - loader_start, USEC_PER_MSEC)); + return 1; +} + +static int test_efi_loader(void) { + usec_t loader_start, loader_exit; + int r; + + r = efi_loader_get_boot_usec(&loader_start, &loader_exit); + if (r < 0) { + bool ok = IN_SET(r, -ENOENT, -EOPNOTSUPP) || ERRNO_IS_PRIVILEGE(r); + + log_full_errno(ok ? LOG_DEBUG : LOG_ERR, r, "Failed to read EFI loader data: %m"); + return ok ? 0 : r; + } + + log_info("EFI Loader: start=%s exit=%s duration=%s", + FORMAT_TIMESPAN(loader_start, USEC_PER_MSEC), + FORMAT_TIMESPAN(loader_exit, USEC_PER_MSEC), + FORMAT_TIMESPAN(loader_exit - loader_start, USEC_PER_MSEC)); + return 1; +} + +static int test_boot_timestamps(void) { + dual_timestamp fw, l, k; + int r; + + dual_timestamp_from_monotonic(&k, 0); + + r = boot_timestamps(NULL, &fw, &l); + if (r < 0) { + bool ok = IN_SET(r, -ENOENT, -EOPNOTSUPP) || ERRNO_IS_PRIVILEGE(r); + + log_full_errno(ok ? LOG_DEBUG : LOG_ERR, r, "Failed to read variables: %m"); + return ok ? 0 : r; + } + + log_info("Firmware began %s before kernel.", FORMAT_TIMESPAN(fw.monotonic, 0)); + log_info("Loader began %s before kernel.", FORMAT_TIMESPAN(l.monotonic, 0)); + log_info("Firmware began %s.", FORMAT_TIMESTAMP(fw.realtime)); + log_info("Loader began %s.", FORMAT_TIMESTAMP(l.realtime)); + log_info("Kernel began %s.", FORMAT_TIMESTAMP(k.realtime)); + return 1; +} + +int main(int argc, char* argv[]) { + int p, q, r; + + test_setup_logging(LOG_DEBUG); + + p = test_acpi_fpdt(); + assert_se(p >= 0); + q = test_efi_loader(); + assert_se(q >= 0); + r = test_boot_timestamps(); + assert_se(r >= 0); + + if (p == 0 && q == 0 && r == 0) + return log_tests_skipped("access to firmware variables not possible"); + + return EXIT_SUCCESS; +} diff --git a/src/test/test-bootspec.c b/src/test/test-bootspec.c new file mode 100644 index 0000000..18611fc --- /dev/null +++ b/src/test/test-bootspec.c @@ -0,0 +1,211 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "bootspec.h" +#include "fileio.h" +#include "path-util.h" +#include "rm-rf.h" +#include "tests.h" +#include "tmpfile-util.h" + +TEST_RET(bootspec_sort) { + + static const struct { + const char *fname; + const char *contents; + } entries[] = { + { + .fname = "a-10.conf", + .contents = + "title A\n" + "version 10\n" + "machine-id dd235d00696545768f6f693bfd23b15f\n", + }, + { + .fname = "a-5.conf", + .contents = + "title A\n" + "version 5\n" + "machine-id dd235d00696545768f6f693bfd23b15f\n", + }, + { + .fname = "b.conf", + .contents = + "title B\n" + "version 3\n" + "machine-id b75451ad92f94feeab50b0b442768dbd\n", + }, + { + .fname = "c.conf", + .contents = + "title C\n" + "sort-key xxxx\n" + "version 5\n" + "machine-id 309de666fd5044268a9a26541ac93176\n", + }, + { + .fname = "cx.conf", + .contents = + "title C\n" + "sort-key xxxx\n" + "version 10\n" + "machine-id 309de666fd5044268a9a26541ac93176\n", + }, + { + .fname = "d.conf", + .contents = + "title D\n" + "sort-key kkkk\n" + "version 100\n" + "machine-id 81c6e3147cf544c19006af023e22b292\n", + }, + }; + + _cleanup_(rm_rf_physical_and_freep) char *d = NULL; + _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; + + assert_se(mkdtemp_malloc("/tmp/bootspec-testXXXXXX", &d) >= 0); + + for (size_t i = 0; i < ELEMENTSOF(entries); i++) { + _cleanup_free_ char *j = NULL; + + j = path_join(d, "/loader/entries/", entries[i].fname); + assert_se(j); + + assert_se(write_string_file(j, entries[i].contents, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755) >= 0); + } + + assert_se(boot_config_load(&config, d, NULL) >= 0); + + assert_se(config.n_entries == 6); + + /* First, because has sort key, and its the lowest one */ + assert_se(streq(config.entries[0].id, "d.conf")); + + /* These two have a sort key, and newest must be first */ + assert_se(streq(config.entries[1].id, "cx.conf")); + assert_se(streq(config.entries[2].id, "c.conf")); + + /* The following ones have no sort key, hence order by version compared ids, lowest first */ + assert_se(streq(config.entries[3].id, "b.conf")); + assert_se(streq(config.entries[4].id, "a-10.conf")); + assert_se(streq(config.entries[5].id, "a-5.conf")); + + return 0; +} + +static void test_extract_tries_one(const char *fname, int ret, const char *stripped, unsigned tries_left, unsigned tries_done) { + _cleanup_free_ char *p = NULL; + unsigned l, d; + + assert_se(boot_filename_extract_tries(fname, &p, &l, &d) == ret); + if (ret < 0) + return; + + assert_se(streq_ptr(p, stripped)); + assert_se(l == tries_left); + assert_se(d == tries_done); +} + +TEST_RET(bootspec_extract_tries) { + test_extract_tries_one("foo.conf", 0, "foo.conf", UINT_MAX, UINT_MAX); + + test_extract_tries_one("foo+0.conf", 0, "foo.conf", 0, UINT_MAX); + test_extract_tries_one("foo+1.conf", 0, "foo.conf", 1, UINT_MAX); + test_extract_tries_one("foo+2.conf", 0, "foo.conf", 2, UINT_MAX); + test_extract_tries_one("foo+33.conf", 0, "foo.conf", 33, UINT_MAX); + + assert_cc(INT_MAX == INT32_MAX); + test_extract_tries_one("foo+2147483647.conf", 0, "foo.conf", 2147483647, UINT_MAX); + test_extract_tries_one("foo+2147483648.conf", -ERANGE, NULL, UINT_MAX, UINT_MAX); + + test_extract_tries_one("foo+33-0.conf", 0, "foo.conf", 33, 0); + test_extract_tries_one("foo+33-1.conf", 0, "foo.conf", 33, 1); + test_extract_tries_one("foo+33-107.conf", 0, "foo.conf", 33, 107); + test_extract_tries_one("foo+33-107.efi", 0, "foo.efi", 33, 107); + test_extract_tries_one("foo+33-2147483647.conf", 0, "foo.conf", 33, 2147483647); + test_extract_tries_one("foo+33-2147483648.conf", -ERANGE, NULL, UINT_MAX, UINT_MAX); + + test_extract_tries_one("foo+007-000008.conf", 0, "foo.conf", 7, 8); + + test_extract_tries_one("foo-1.conf", 0, "foo-1.conf", UINT_MAX, UINT_MAX); + test_extract_tries_one("foo-999.conf", 0, "foo-999.conf", UINT_MAX, UINT_MAX); + test_extract_tries_one("foo-.conf", 0, "foo-.conf", UINT_MAX, UINT_MAX); + + test_extract_tries_one("foo+.conf", 0, "foo+.conf", UINT_MAX, UINT_MAX); + test_extract_tries_one("+.conf", 0, "+.conf", UINT_MAX, UINT_MAX); + test_extract_tries_one("-.conf", 0, "-.conf", UINT_MAX, UINT_MAX); + test_extract_tries_one("", 0, "", UINT_MAX, UINT_MAX); + + test_extract_tries_one("+1.", 0, ".", 1, UINT_MAX); + test_extract_tries_one("+1-7.", 0, ".", 1, 7); + + test_extract_tries_one("some+name+24324-22.efi", 0, "some+name.efi", 24324, 22); + test_extract_tries_one("sels+2-3+7-6.", 0, "sels+2-3.", 7, 6); + test_extract_tries_one("a+1-2..", 0, "a+1-2..", UINT_MAX, UINT_MAX); + test_extract_tries_one("ses.sgesge.+4-1.efi", 0, "ses.sgesge..efi", 4, 1); + test_extract_tries_one("abc+0x4.conf", 0, "abc+0x4.conf", UINT_MAX, UINT_MAX); + test_extract_tries_one("def+1-0x3.conf", 0, "def+1-0x3.conf", UINT_MAX, UINT_MAX); + + return 0; +} + +TEST_RET(bootspec_boot_config_find_entry) { + + static const struct { + const char *fname; + const char *contents; + } entries[] = { + { + .fname = "a-10.conf", + .contents = + "title A\n" + "version 10\n" + "machine-id dd235d00696545768f6f693bfd23b15f\n", + }, + { + .fname = "a-05.conf", + .contents = + "title A\n" + "version 10\n" + "machine-id dd235d00696545768f6f693bfd23b15f\n", + }, + }; + + _cleanup_(rm_rf_physical_and_freep) char *d = NULL; + _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; + + assert_se(mkdtemp_malloc("/tmp/bootspec-testXXXXXX", &d) >= 0); + + for (size_t i = 0; i < ELEMENTSOF(entries); i++) { + _cleanup_free_ char *j = NULL; + + j = path_join(d, "/loader/entries/", entries[i].fname); + assert_se(j); + + assert_se(write_string_file(j, entries[i].contents, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755) >= 0); + } + + assert_se(boot_config_load(&config, d, NULL) >= 0); + assert_se(config.n_entries == 2); + + // Test finding the first entry + BootEntry *entry = boot_config_find_entry(&config, "a-10.conf"); + assert_se(entry && streq(entry->id, "a-10.conf")); + + // Test finding the second entry + entry = boot_config_find_entry(&config, "a-05.conf"); + assert_se(entry && streq(entry->id, "a-05.conf")); + + // Test finding a non-existent entry + entry = boot_config_find_entry(&config, "nonexistent.conf"); + assert_se(entry == NULL); + + // Test case-insensitivity + entry = boot_config_find_entry(&config, "A-10.CONF"); + assert_se(entry && streq(entry->id, "a-10.conf")); + + + return 0; +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-bpf-devices.c b/src/test/test-bpf-devices.c new file mode 100644 index 0000000..4bd606e --- /dev/null +++ b/src/test/test-bpf-devices.c @@ -0,0 +1,307 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <sys/resource.h> +#include <sys/time.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "bpf-devices.h" +#include "bpf-program.h" +#include "cgroup-setup.h" +#include "errno-list.h" +#include "fd-util.h" +#include "fs-util.h" +#include "path-util.h" +#include "tests.h" + +static void test_policy_closed(const char *cgroup_path, BPFProgram **installed_prog) { + _cleanup_(bpf_program_freep) BPFProgram *prog = NULL; + unsigned wrong = 0; + int r; + + log_info("/* %s */", __func__); + + r = bpf_devices_cgroup_init(&prog, CGROUP_DEVICE_POLICY_CLOSED, true); + assert_se(r >= 0); + + r = bpf_devices_allow_list_static(prog, cgroup_path); + assert_se(r >= 0); + + r = bpf_devices_apply_policy(&prog, CGROUP_DEVICE_POLICY_CLOSED, true, cgroup_path, installed_prog); + assert_se(r >= 0); + + FOREACH_STRING(s, "/dev/null", + "/dev/zero", + "/dev/full", + "/dev/random", + "/dev/urandom", + "/dev/tty", + "/dev/ptmx") { + _cleanup_close_ int fd = -EBADF, fd2 = -EBADF; + + fd = open(s, O_CLOEXEC|O_RDONLY|O_NOCTTY); + log_debug("open(%s, \"r\") = %d/%s", s, fd, fd < 0 ? errno_to_name(errno) : "-"); + wrong += fd < 0 && errno == EPERM; + /* We ignore errors other than EPERM, e.g. ENOENT or ENXIO */ + + fd2 = open(s, O_CLOEXEC|O_WRONLY|O_NOCTTY); + log_debug("open(%s, \"w\") = %d/%s", s, fd2, fd2 < 0 ? errno_to_name(errno) : "-"); + wrong += fd2 < 0 && errno == EPERM; + } + assert_se(wrong == 0); +} + +static void test_policy_strict(const char *cgroup_path, BPFProgram **installed_prog) { + _cleanup_(bpf_program_freep) BPFProgram *prog = NULL; + unsigned wrong = 0; + int r; + + log_info("/* %s */", __func__); + + r = bpf_devices_cgroup_init(&prog, CGROUP_DEVICE_POLICY_STRICT, true); + assert_se(r >= 0); + + r = bpf_devices_allow_list_device(prog, cgroup_path, "/dev/null", CGROUP_DEVICE_READ|CGROUP_DEVICE_WRITE); + assert_se(r >= 0); + + r = bpf_devices_allow_list_device(prog, cgroup_path, "/dev/random", CGROUP_DEVICE_READ); + assert_se(r >= 0); + + r = bpf_devices_allow_list_device(prog, cgroup_path, "/dev/zero", CGROUP_DEVICE_WRITE); + assert_se(r >= 0); + + r = bpf_devices_apply_policy(&prog, CGROUP_DEVICE_POLICY_STRICT, true, cgroup_path, installed_prog); + assert_se(r >= 0); + + { + _cleanup_close_ int fd = -EBADF, fd2 = -EBADF; + const char *s = "/dev/null"; + + fd = open(s, O_CLOEXEC|O_RDONLY|O_NOCTTY); + log_debug("open(%s, \"r\") = %d/%s", s, fd, fd < 0 ? errno_to_name(errno) : "-"); + wrong += fd < 0; + + fd2 = open(s, O_CLOEXEC|O_WRONLY|O_NOCTTY); + log_debug("open(%s, \"w\") = %d/%s", s, fd2, fd2 < 0 ? errno_to_name(errno) : "-"); + wrong += fd2 < 0; + } + + { + _cleanup_close_ int fd = -EBADF, fd2 = -EBADF; + const char *s = "/dev/random"; + + fd = open(s, O_CLOEXEC|O_RDONLY|O_NOCTTY); + log_debug("open(%s, \"r\") = %d/%s", s, fd, fd < 0 ? errno_to_name(errno) : "-"); + wrong += fd < 0; + + fd2 = open(s, O_CLOEXEC|O_WRONLY|O_NOCTTY); + log_debug("open(%s, \"w\") = %d/%s", s, fd2, fd2 < 0 ? errno_to_name(errno) : "-"); + wrong += fd2 >= 0; + } + + { + _cleanup_close_ int fd = -EBADF, fd2 = -EBADF; + const char *s = "/dev/zero"; + + fd = open(s, O_CLOEXEC|O_RDONLY|O_NOCTTY); + log_debug("open(%s, \"r\") = %d/%s", s, fd, fd < 0 ? errno_to_name(errno) : "-"); + wrong += fd >= 0; + + fd2 = open(s, O_CLOEXEC|O_WRONLY|O_NOCTTY); + log_debug("open(%s, \"w\") = %d/%s", s, fd2, fd2 < 0 ? errno_to_name(errno) : "-"); + wrong += fd2 < 0; + } + + { + _cleanup_close_ int fd = -EBADF, fd2 = -EBADF; + const char *s = "/dev/full"; + + fd = open(s, O_CLOEXEC|O_RDONLY|O_NOCTTY); + log_debug("open(%s, \"r\") = %d/%s", s, fd, fd < 0 ? errno_to_name(errno) : "-"); + wrong += fd >= 0; + + fd2 = open(s, O_CLOEXEC|O_WRONLY|O_NOCTTY); + log_debug("open(%s, \"w\") = %d/%s", s, fd2, fd2 < 0 ? errno_to_name(errno) : "-"); + wrong += fd2 >= 0; + } + + assert_se(wrong == 0); +} + +static void test_policy_allow_list_major(const char *pattern, const char *cgroup_path, BPFProgram **installed_prog) { + _cleanup_(bpf_program_freep) BPFProgram *prog = NULL; + unsigned wrong = 0; + int r; + + log_info("/* %s(%s) */", __func__, pattern); + + r = bpf_devices_cgroup_init(&prog, CGROUP_DEVICE_POLICY_STRICT, true); + assert_se(r >= 0); + + r = bpf_devices_allow_list_major(prog, cgroup_path, pattern, 'c', CGROUP_DEVICE_READ|CGROUP_DEVICE_WRITE); + assert_se(r >= 0); + + r = bpf_devices_apply_policy(&prog, CGROUP_DEVICE_POLICY_STRICT, true, cgroup_path, installed_prog); + assert_se(r >= 0); + + /* /dev/null, /dev/full have major==1, /dev/tty has major==5 */ + { + _cleanup_close_ int fd = -EBADF, fd2 = -EBADF; + const char *s = "/dev/null"; + + fd = open(s, O_CLOEXEC|O_RDONLY|O_NOCTTY); + log_debug("open(%s, \"r\") = %d/%s", s, fd, fd < 0 ? errno_to_name(errno) : "-"); + wrong += fd < 0; + + fd2 = open(s, O_CLOEXEC|O_WRONLY|O_NOCTTY); + log_debug("open(%s, \"w\") = %d/%s", s, fd2, fd2 < 0 ? errno_to_name(errno) : "-"); + wrong += fd2 < 0; + } + + { + _cleanup_close_ int fd = -EBADF, fd2 = -EBADF; + const char *s = "/dev/full"; + + fd = open(s, O_CLOEXEC|O_RDONLY|O_NOCTTY); + log_debug("open(%s, \"r\") = %d/%s", s, fd, fd < 0 ? errno_to_name(errno) : "-"); + wrong += fd < 0; + + fd2 = open(s, O_CLOEXEC|O_WRONLY|O_NOCTTY); + log_debug("open(%s, \"w\") = %d/%s", s, fd2, fd2 < 0 ? errno_to_name(errno) : "-"); + wrong += fd2 < 0; + } + + { + _cleanup_close_ int fd = -EBADF, fd2 = -EBADF; + const char *s = "/dev/tty"; + + fd = open(s, O_CLOEXEC|O_RDONLY|O_NOCTTY); + log_debug("open(%s, \"r\") = %d/%s", s, fd, fd < 0 ? errno_to_name(errno) : "-"); + wrong += fd >= 0; + + fd2 = open(s, O_CLOEXEC|O_WRONLY|O_NOCTTY); + log_debug("open(%s, \"w\") = %d/%s", s, fd2, fd2 < 0 ? errno_to_name(errno) : "-"); + wrong += fd2 >= 0; + } + + assert_se(wrong == 0); +} + +static void test_policy_allow_list_major_star(char type, const char *cgroup_path, BPFProgram **installed_prog) { + _cleanup_(bpf_program_freep) BPFProgram *prog = NULL; + unsigned wrong = 0; + int r; + + log_info("/* %s(type=%c) */", __func__, type); + + r = bpf_devices_cgroup_init(&prog, CGROUP_DEVICE_POLICY_STRICT, true); + assert_se(r >= 0); + + r = bpf_devices_allow_list_major(prog, cgroup_path, "*", type, CGROUP_DEVICE_READ|CGROUP_DEVICE_WRITE); + assert_se(r >= 0); + + r = bpf_devices_apply_policy(&prog, CGROUP_DEVICE_POLICY_STRICT, true, cgroup_path, installed_prog); + assert_se(r >= 0); + + { + _cleanup_close_ int fd = -EBADF; + const char *s = "/dev/null"; + + fd = open(s, O_CLOEXEC|O_RDWR|O_NOCTTY); + log_debug("open(%s, \"r\") = %d/%s", s, fd, fd < 0 ? errno_to_name(errno) : "-"); + if (type == 'c') + wrong += fd < 0; + else + wrong += fd >= 0; + } + + assert_se(wrong == 0); +} + +static void test_policy_empty(bool add_mismatched, const char *cgroup_path, BPFProgram **installed_prog) { + _cleanup_(bpf_program_freep) BPFProgram *prog = NULL; + unsigned wrong = 0; + int r; + + log_info("/* %s(add_mismatched=%s) */", __func__, yes_no(add_mismatched)); + + r = bpf_devices_cgroup_init(&prog, CGROUP_DEVICE_POLICY_STRICT, add_mismatched); + assert_se(r >= 0); + + if (add_mismatched) { + r = bpf_devices_allow_list_major(prog, cgroup_path, "foobarxxx", 'c', CGROUP_DEVICE_READ|CGROUP_DEVICE_WRITE); + assert_se(r < 0); + } + + r = bpf_devices_apply_policy(&prog, CGROUP_DEVICE_POLICY_STRICT, false, cgroup_path, installed_prog); + assert_se(r >= 0); + + { + _cleanup_close_ int fd = -EBADF; + const char *s = "/dev/null"; + + fd = open(s, O_CLOEXEC|O_RDWR|O_NOCTTY); + log_debug("open(%s, \"r\") = %d/%s", s, fd, fd < 0 ? errno_to_name(errno) : "-"); + wrong += fd >= 0; + } + + assert_se(wrong == 0); +} + + +int main(int argc, char *argv[]) { + _cleanup_free_ char *cgroup = NULL, *parent = NULL; + _cleanup_(rmdir_and_freep) char *controller_path = NULL; + CGroupMask supported; + struct rlimit rl; + int r; + + test_setup_logging(LOG_DEBUG); + + assert_se(getrlimit(RLIMIT_MEMLOCK, &rl) >= 0); + rl.rlim_cur = rl.rlim_max = MAX(rl.rlim_max, CAN_MEMLOCK_SIZE); + (void) setrlimit(RLIMIT_MEMLOCK, &rl); + + r = cg_all_unified(); + if (r <= 0) + return log_tests_skipped("We don't seem to be running with unified cgroup hierarchy"); + + if (!can_memlock()) + return log_tests_skipped("Can't use mlock()"); + + r = enter_cgroup_subroot(&cgroup); + if (r == -ENOMEDIUM) + return log_tests_skipped("cgroupfs not available"); + if (r < 0) + return log_tests_skipped_errno(r, "Failed to prepare cgroup subtree"); + + r = bpf_devices_supported(); + if (r == 0) + return log_tests_skipped("BPF device filter not supported"); + assert_se(r == 1); + + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, cgroup, NULL, &controller_path); + assert_se(r >= 0); + + _cleanup_(bpf_program_freep) BPFProgram *prog = NULL; + + test_policy_closed(cgroup, &prog); + test_policy_strict(cgroup, &prog); + + test_policy_allow_list_major("mem", cgroup, &prog); + test_policy_allow_list_major("1", cgroup, &prog); + + test_policy_allow_list_major_star('c', cgroup, &prog); + test_policy_allow_list_major_star('b', cgroup, &prog); + + test_policy_empty(false, cgroup, &prog); + test_policy_empty(true, cgroup, &prog); + + assert_se(path_extract_directory(cgroup, &parent) >= 0); + + assert_se(cg_mask_supported(&supported) >= 0); + r = cg_attach_everywhere(supported, parent, 0, NULL, NULL); + assert_se(r >= 0); + + return 0; +} diff --git a/src/test/test-bpf-firewall.c b/src/test/test-bpf-firewall.c new file mode 100644 index 0000000..c4175bc --- /dev/null +++ b/src/test/test-bpf-firewall.c @@ -0,0 +1,217 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <linux/bpf_insn.h> +#include <string.h> +#include <unistd.h> + +#include "bpf-firewall.h" +#include "bpf-program.h" +#include "in-addr-prefix-util.h" +#include "load-fragment.h" +#include "manager.h" +#include "memory-util.h" +#include "rm-rf.h" +#include "service.h" +#include "tests.h" +#include "unit-serialize.h" +#include "virt.h" + +int main(int argc, char *argv[]) { + const struct bpf_insn exit_insn[] = { + BPF_MOV64_IMM(BPF_REG_0, 0), /* drop */ + BPF_EXIT_INSN() + }; + + _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; + CGroupContext *cc = NULL; + _cleanup_(bpf_program_freep) BPFProgram *p = NULL; + _cleanup_(manager_freep) Manager *m = NULL; + Unit *u; + char log_buf[65535]; + struct rlimit rl; + int r; + union bpf_attr attr; + bool test_custom_filter = false; + const char *test_prog = "/sys/fs/bpf/test-dropper"; + + test_setup_logging(LOG_DEBUG); + + if (detect_container() > 0) + return log_tests_skipped("test-bpf-firewall fails inside LXC and Docker containers: https://github.com/systemd/systemd/issues/9666"); + + assert_se(getrlimit(RLIMIT_MEMLOCK, &rl) >= 0); + rl.rlim_cur = rl.rlim_max = MAX(rl.rlim_max, CAN_MEMLOCK_SIZE); + (void) setrlimit(RLIMIT_MEMLOCK, &rl); + + if (!can_memlock()) + return log_tests_skipped("Can't use mlock()"); + + r = enter_cgroup_subroot(NULL); + if (r == -ENOMEDIUM) + return log_tests_skipped("cgroupfs not available"); + + _cleanup_free_ char *unit_dir = NULL; + assert_se(get_testdata_dir("units", &unit_dir) >= 0); + assert_se(set_unit_path(unit_dir) >= 0); + assert_se(runtime_dir = setup_fake_runtime_dir()); + + r = bpf_program_new(BPF_PROG_TYPE_CGROUP_SKB, "sd_trivial", &p); + assert_se(r == 0); + + r = bpf_program_add_instructions(p, exit_insn, ELEMENTSOF(exit_insn)); + assert_se(r == 0); + + r = bpf_firewall_supported(); + if (r == BPF_FIREWALL_UNSUPPORTED) + return log_tests_skipped("BPF firewalling not supported"); + assert_se(r > 0); + + if (r == BPF_FIREWALL_SUPPORTED_WITH_MULTI) { + log_notice("BPF firewalling with BPF_F_ALLOW_MULTI supported. Yay!"); + test_custom_filter = true; + } else + log_notice("BPF firewalling (though without BPF_F_ALLOW_MULTI) supported. Good."); + + r = bpf_program_load_kernel(p, log_buf, ELEMENTSOF(log_buf)); + assert_se(r >= 0); + + if (test_custom_filter) { + zero(attr); + attr.pathname = PTR_TO_UINT64(test_prog); + attr.bpf_fd = p->kernel_fd; + attr.file_flags = 0; + + (void) unlink(test_prog); + + r = bpf(BPF_OBJ_PIN, &attr, sizeof(attr)); + if (r < 0) { + log_warning_errno(errno, "BPF object pinning failed, will not run custom filter test: %m"); + test_custom_filter = false; + } + } + + p = bpf_program_free(p); + + /* The simple tests succeeded. Now let's try full unit-based use-case. */ + + assert_se(manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &m) >= 0); + assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + + assert_se(u = unit_new(m, sizeof(Service))); + assert_se(unit_add_name(u, "foo.service") == 0); + assert_se(cc = unit_get_cgroup_context(u)); + u->perpetual = true; + + cc->ip_accounting = true; + + assert_se(config_parse_in_addr_prefixes(u->id, "filename", 1, "Service", 1, "IPAddressAllow", 0, "10.0.1.0/24", &cc->ip_address_allow, NULL) == 0); + assert_se(config_parse_in_addr_prefixes(u->id, "filename", 1, "Service", 1, "IPAddressAllow", 0, "127.0.0.2", &cc->ip_address_allow, NULL) == 0); + assert_se(config_parse_in_addr_prefixes(u->id, "filename", 1, "Service", 1, "IPAddressDeny", 0, "127.0.0.3", &cc->ip_address_deny, NULL) == 0); + assert_se(config_parse_in_addr_prefixes(u->id, "filename", 1, "Service", 1, "IPAddressDeny", 0, "10.0.3.2/24", &cc->ip_address_deny, NULL) == 0); + assert_se(config_parse_in_addr_prefixes(u->id, "filename", 1, "Service", 1, "IPAddressDeny", 0, "127.0.0.1/25", &cc->ip_address_deny, NULL) == 0); + assert_se(config_parse_in_addr_prefixes(u->id, "filename", 1, "Service", 1, "IPAddressDeny", 0, "127.0.0.4", &cc->ip_address_deny, NULL) == 0); + + assert_se(set_size(cc->ip_address_allow) == 2); + assert_se(set_size(cc->ip_address_deny) == 4); + + /* The deny list is defined redundantly, let's ensure it will be properly reduced */ + assert_se(in_addr_prefixes_reduce(cc->ip_address_allow) >= 0); + assert_se(in_addr_prefixes_reduce(cc->ip_address_deny) >= 0); + + assert_se(set_size(cc->ip_address_allow) == 2); + assert_se(set_size(cc->ip_address_deny) == 2); + + assert_se(set_contains(cc->ip_address_allow, &(struct in_addr_prefix) { + .family = AF_INET, + .address.in.s_addr = htobe32((UINT32_C(10) << 24) | (UINT32_C(1) << 8)), + .prefixlen = 24 })); + assert_se(set_contains(cc->ip_address_allow, &(struct in_addr_prefix) { + .family = AF_INET, + .address.in.s_addr = htobe32(0x7f000002), + .prefixlen = 32 })); + assert_se(set_contains(cc->ip_address_deny, &(struct in_addr_prefix) { + .family = AF_INET, + .address.in.s_addr = htobe32(0x7f000000), + .prefixlen = 25 })); + assert_se(set_contains(cc->ip_address_deny, &(struct in_addr_prefix) { + .family = AF_INET, + .address.in.s_addr = htobe32((UINT32_C(10) << 24) | (UINT32_C(3) << 8)), + .prefixlen = 24 })); + + assert_se(config_parse_exec(u->id, "filename", 1, "Service", 1, "ExecStart", SERVICE_EXEC_START, "/bin/ping -c 1 127.0.0.2 -W 5", SERVICE(u)->exec_command, u) == 0); + assert_se(config_parse_exec(u->id, "filename", 1, "Service", 1, "ExecStart", SERVICE_EXEC_START, "/bin/ping -c 1 127.0.0.3 -W 5", SERVICE(u)->exec_command, u) == 0); + + assert_se(SERVICE(u)->exec_command[SERVICE_EXEC_START]); + assert_se(SERVICE(u)->exec_command[SERVICE_EXEC_START]->command_next); + assert_se(!SERVICE(u)->exec_command[SERVICE_EXEC_START]->command_next->command_next); + + SERVICE(u)->type = SERVICE_ONESHOT; + u->load_state = UNIT_LOADED; + + unit_dump(u, stdout, NULL); + + r = bpf_firewall_compile(u); + if (IN_SET(r, -ENOTTY, -ENOSYS, -EPERM)) + return log_tests_skipped("Kernel doesn't support the necessary bpf bits (masked out via seccomp?)"); + assert_se(r >= 0); + + assert_se(u->ip_bpf_ingress); + assert_se(u->ip_bpf_egress); + + r = bpf_program_load_kernel(u->ip_bpf_ingress, log_buf, ELEMENTSOF(log_buf)); + + log_notice("log:"); + log_notice("-------"); + log_notice("%s", log_buf); + log_notice("-------"); + + assert_se(r >= 0); + + r = bpf_program_load_kernel(u->ip_bpf_egress, log_buf, ELEMENTSOF(log_buf)); + + log_notice("log:"); + log_notice("-------"); + log_notice("%s", log_buf); + log_notice("-------"); + + assert_se(r >= 0); + + assert_se(unit_start(u, NULL) >= 0); + + while (!IN_SET(SERVICE(u)->state, SERVICE_DEAD, SERVICE_FAILED)) + assert_se(sd_event_run(m->event, UINT64_MAX) >= 0); + + assert_se(SERVICE(u)->exec_command[SERVICE_EXEC_START]->exec_status.code == CLD_EXITED && + SERVICE(u)->exec_command[SERVICE_EXEC_START]->exec_status.status == EXIT_SUCCESS); + + assert_se(SERVICE(u)->exec_command[SERVICE_EXEC_START]->command_next->exec_status.code != CLD_EXITED || + SERVICE(u)->exec_command[SERVICE_EXEC_START]->command_next->exec_status.status != EXIT_SUCCESS); + + if (test_custom_filter) { + assert_se(u = unit_new(m, sizeof(Service))); + assert_se(unit_add_name(u, "custom-filter.service") == 0); + assert_se(cc = unit_get_cgroup_context(u)); + u->perpetual = true; + + cc->ip_accounting = true; + + assert_se(config_parse_ip_filter_bpf_progs(u->id, "filename", 1, "Service", 1, "IPIngressFilterPath", 0, test_prog, &cc->ip_filters_ingress, u) == 0); + assert_se(config_parse_exec(u->id, "filename", 1, "Service", 1, "ExecStart", SERVICE_EXEC_START, "-/bin/ping -c 1 127.0.0.1 -W 5", SERVICE(u)->exec_command, u) == 0); + + SERVICE(u)->type = SERVICE_ONESHOT; + u->load_state = UNIT_LOADED; + + assert_se(unit_start(u, NULL) >= 0); + + while (!IN_SET(SERVICE(u)->state, SERVICE_DEAD, SERVICE_FAILED)) + assert_se(sd_event_run(m->event, UINT64_MAX) >= 0); + + assert_se(SERVICE(u)->exec_command[SERVICE_EXEC_START]->exec_status.code != CLD_EXITED || + SERVICE(u)->exec_command[SERVICE_EXEC_START]->exec_status.status != EXIT_SUCCESS); + + (void) unlink(test_prog); + assert_se(SERVICE(u)->state == SERVICE_DEAD); + } + + return 0; +} diff --git a/src/test/test-bpf-foreign-programs.c b/src/test/test-bpf-foreign-programs.c new file mode 100644 index 0000000..35c7e0d --- /dev/null +++ b/src/test/test-bpf-foreign-programs.c @@ -0,0 +1,330 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <fcntl.h> +#include <linux/bpf_insn.h> +#include <string.h> +#include <sys/mman.h> +#include <unistd.h> + +#include "bpf-foreign.h" +#include "load-fragment.h" +#include "manager.h" +#include "process-util.h" +#include "rlimit-util.h" +#include "rm-rf.h" +#include "service.h" +#include "tests.h" +#include "unit.h" +#include "virt.h" + +struct Test { + const char *option_name; + enum bpf_prog_type prog_type; + enum bpf_attach_type attach_type; + const char *bpffs_path; +}; + +typedef struct Test Test; + +#define BPFFS_PATH(prog_suffix) ("/sys/fs/bpf/test-bpf-foreing-" # prog_suffix) +static const Test single_prog[] = { + { + .option_name = "BPFProgram", + .prog_type = BPF_PROG_TYPE_CGROUP_SKB, + .attach_type = BPF_CGROUP_INET_INGRESS, + .bpffs_path = BPFFS_PATH("trivial-skb"), + }, +}; +static const Test path_split_test[] = { + { + .option_name = "BPFProgram", + .prog_type = BPF_PROG_TYPE_CGROUP_SKB, + .attach_type = BPF_CGROUP_INET_INGRESS, + .bpffs_path = BPFFS_PATH("path:split:test"), + }, +}; + +static const Test same_prog_same_hook[] = { + { + .option_name = "BPFProgram", + .prog_type = BPF_PROG_TYPE_CGROUP_SOCK, + .attach_type = BPF_CGROUP_INET_SOCK_CREATE, + .bpffs_path = BPFFS_PATH("trivial-sock"), + }, + { + .option_name = "BPFProgram", + .prog_type = BPF_PROG_TYPE_CGROUP_SOCK, + .attach_type = BPF_CGROUP_INET_SOCK_CREATE, + .bpffs_path = BPFFS_PATH("trivial-sock"), + } +}; + +static const Test multi_prog_same_hook[] = { + { + .option_name = "BPFProgram", + .prog_type = BPF_PROG_TYPE_CGROUP_SOCK, + .attach_type = BPF_CGROUP_INET_SOCK_CREATE, + .bpffs_path = BPFFS_PATH("trivial-sock-0"), + }, + { + .option_name = "BPFProgram", + .prog_type = BPF_PROG_TYPE_CGROUP_SOCK, + .attach_type = BPF_CGROUP_INET_SOCK_CREATE, + .bpffs_path = BPFFS_PATH("trivial-sock-1"), + } +}; + +static const Test same_prog_multi_hook[] = { + { + .option_name = "BPFProgram", + .prog_type = BPF_PROG_TYPE_CGROUP_SKB, + .attach_type = BPF_CGROUP_INET_INGRESS, + .bpffs_path = BPFFS_PATH("trivial-skb"), + }, + { + .option_name = "BPFProgram", + .prog_type = BPF_PROG_TYPE_CGROUP_SKB, + .attach_type = BPF_CGROUP_INET_EGRESS, + .bpffs_path = BPFFS_PATH("trivial-skb"), + } +}; + +static const Test same_prog_multi_option_0[] = { + { + .option_name = "BPFProgram", + .prog_type = BPF_PROG_TYPE_CGROUP_SKB, + .attach_type = BPF_CGROUP_INET_INGRESS, + .bpffs_path = BPFFS_PATH("trivial-skb"), + }, + { + .option_name = "IPIngressFilterPath", + .prog_type = BPF_PROG_TYPE_CGROUP_SKB, + .attach_type = BPF_CGROUP_INET_INGRESS, + .bpffs_path = BPFFS_PATH("trivial-skb"), + } +}; + +static const Test same_prog_multi_option_1[] = { + { + .option_name = "IPEgressFilterPath", + .prog_type = BPF_PROG_TYPE_CGROUP_SKB, + .attach_type = BPF_CGROUP_INET_EGRESS, + .bpffs_path = BPFFS_PATH("trivial-skb"), + }, + { + .option_name = "BPFProgram", + .prog_type = BPF_PROG_TYPE_CGROUP_SKB, + .attach_type = BPF_CGROUP_INET_EGRESS, + .bpffs_path = BPFFS_PATH("trivial-skb"), + } +}; +#undef BPFFS_PATH + +static int bpf_foreign_test_to_string(enum bpf_attach_type attach_type, const char *bpffs_path, char **ret_str) { + const char *s = NULL; + + assert_se(bpffs_path); + assert_se(ret_str); + + assert_se(s = bpf_cgroup_attach_type_to_string(attach_type)); + assert_se(*ret_str = strjoin(s, ":", bpffs_path)); + + return 0; +} + +static char **unlink_paths_and_free(char **paths) { + STRV_FOREACH(i, paths) + (void) unlink(*i); + + return strv_free(paths); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(char **, unlink_paths_and_free); + +static int pin_programs(Unit *u, CGroupContext *cc, const Test *test_suite, size_t test_suite_size, char ***paths_ret) { + _cleanup_(unlink_paths_and_freep) char **bpffs_paths = NULL; + static const struct bpf_insn trivial[] = { + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN() + }; + char log_buf[0xffff]; + int r; + + assert_se(paths_ret); + + for (size_t i = 0; i < test_suite_size; i++) { + _cleanup_(bpf_program_freep) BPFProgram *prog = NULL; + _cleanup_free_ char *str = NULL; + + r = bpf_foreign_test_to_string(test_suite[i].attach_type, test_suite[i].bpffs_path, &str); + if (r < 0) + return log_error_errno(r, "Failed to convert program to string"); + + r = bpf_program_new(test_suite[i].prog_type, "sd_trivial", &prog); + if (r < 0) + return log_error_errno(r, "Failed to create program '%s'", str); + + r = bpf_program_add_instructions(prog, trivial, ELEMENTSOF(trivial)); + if (r < 0) + return log_error_errno(r, "Failed to add trivial instructions for '%s'", str); + + r = bpf_program_load_kernel(prog, log_buf, ELEMENTSOF(log_buf)); + if (r < 0) + return log_error_errno(r, "Failed to load BPF program '%s'", str); + + if (strv_contains(bpffs_paths, test_suite[i].bpffs_path)) + continue; + + r = strv_extend(&bpffs_paths, test_suite[i].bpffs_path); + if (r < 0) + return log_error_errno(r, "Failed to put path into a vector: %m"); + + r = bpf_program_pin(prog->kernel_fd, test_suite[i].bpffs_path); + if (r < 0) + return log_error_errno(r, "Failed to pin BPF program '%s'", str); + } + + *paths_ret = TAKE_PTR(bpffs_paths); + return 0; +} + +static int test_bpf_cgroup_programs(Manager *m, const char *unit_name, const Test *test_suite, size_t test_suite_size) { + _cleanup_(unlink_paths_and_freep) char **bpffs_paths = NULL; + _cleanup_(unit_freep) Unit *u = NULL; + CGroupContext *cc = NULL; + int cld_code, r; + + assert_se(u = unit_new(m, sizeof(Service))); + assert_se(unit_add_name(u, unit_name) == 0); + assert_se(cc = unit_get_cgroup_context(u)); + + r = pin_programs(u, cc, test_suite, test_suite_size, &bpffs_paths); + if (r < 0) + return log_error_errno(r, "Failed to pin programs: %m"); + + for (size_t i = 0; i < test_suite_size; i++) { + if (streq(test_suite[i].option_name, "BPFProgram")) { + _cleanup_free_ char *option = NULL; + r = bpf_foreign_test_to_string(test_suite[i].attach_type, test_suite[i].bpffs_path, &option); + if (r < 0) + return log_error_errno(r, "Failed to compose option string: %m"); + r = config_parse_bpf_foreign_program( + u->id, "filename", 1, "Service", 1, test_suite[i].option_name, 0, option, cc, u); + + if (r < 0) + return log_error_errno(r, "Failed to parse option string '%s': %m", option); + } else if (STR_IN_SET(test_suite[i].option_name, "IPIngressFilterPath", "IPEgressFilterPath")) { + const char *option = test_suite[i].bpffs_path; + void *paths = NULL; + + if (streq(test_suite[i].option_name, "IPIngressFilterPath")) + paths = &cc->ip_filters_ingress; + else + paths = &cc->ip_filters_egress; + + r = config_parse_ip_filter_bpf_progs( + u->id, "filename", 1, "Service", 1, test_suite[i].option_name, 0, option, paths, u); + if (r < 0) + return log_error_errno(r, "Failed to parse option string '%s': %m", option); + } + } + + r = config_parse_exec( + u->id, + "filename", + 1, + "Service", + 1, + "ExecStart", + SERVICE_EXEC_START, + "-/bin/ping -c 5 127.0.0.1 -W 1", + SERVICE(u)->exec_command, + u); + if (r < 0) + return log_error_errno(r, "Failed to parse ExecStart"); + + SERVICE(u)->type = SERVICE_ONESHOT; + u->load_state = UNIT_LOADED; + + r = unit_start(u, NULL); + if (r < 0) + return log_error_errno(r, "Unit start failed %m"); + + while (!IN_SET(SERVICE(u)->state, SERVICE_DEAD, SERVICE_FAILED)) { + r = sd_event_run(m->event, UINT64_MAX); + if (r < 0) + return log_error_errno(errno, "Event run failed %m"); + } + + cld_code = SERVICE(u)->exec_command[SERVICE_EXEC_START]->exec_status.code; + if (cld_code != CLD_EXITED) + return log_error_errno(SYNTHETIC_ERRNO(EBUSY), + "Child didn't exit normally, code='%s'", sigchld_code_to_string(cld_code)); + + if (SERVICE(u)->state != SERVICE_DEAD) + return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Service is not dead"); + + return r; +} + +int main(int argc, char *argv[]) { + _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; + _cleanup_(manager_freep) Manager *m = NULL; + _cleanup_free_ char *unit_dir = NULL; + struct rlimit rl; + int r; + + test_setup_logging(LOG_DEBUG); + + if (detect_container() > 0) + return log_tests_skipped("test-bpf fails inside LXC and Docker containers: https://github.com/systemd/systemd/issues/9666"); + + if (getuid() != 0) + return log_tests_skipped("not running as root"); + + assert_se(getrlimit(RLIMIT_MEMLOCK, &rl) >= 0); + rl.rlim_cur = rl.rlim_max = MAX(rl.rlim_max, CAN_MEMLOCK_SIZE); + (void) setrlimit_closest(RLIMIT_MEMLOCK, &rl); + + if (!can_memlock()) + return log_tests_skipped("Can't use mlock()"); + + r = cg_all_unified(); + if (r <= 0) + return log_tests_skipped("Unified hierarchy is required"); + + r = enter_cgroup_subroot(NULL); + if (r == -ENOMEDIUM) + return log_tests_skipped("cgroupfs not available"); + + assert_se(get_testdata_dir("units", &unit_dir) >= 0); + assert_se(set_unit_path(unit_dir) >= 0); + assert_se(runtime_dir = setup_fake_runtime_dir()); + + assert_se(manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &m) >= 0); + assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + + assert_se(test_bpf_cgroup_programs(m, + "single_prog.service", single_prog, ELEMENTSOF(single_prog)) >= 0); + assert_se(test_bpf_cgroup_programs(m, + "multi_prog_same_hook.service", + multi_prog_same_hook, ELEMENTSOF(multi_prog_same_hook)) >= 0); + assert_se(test_bpf_cgroup_programs(m, + "same_prog_multi_hook.service", + same_prog_multi_hook, ELEMENTSOF(same_prog_multi_hook)) >= 0); + assert_se(test_bpf_cgroup_programs(m, + "same_prog_multi_option_0.service", + same_prog_multi_option_0, ELEMENTSOF(same_prog_multi_option_0)) >= 0); + assert_se(test_bpf_cgroup_programs(m, + "same_prog_multi_option_1.service", + same_prog_multi_option_1, ELEMENTSOF(same_prog_multi_option_1)) >= 0); + assert_se(test_bpf_cgroup_programs(m, + "same_prog_same_hook.service", + same_prog_same_hook, + ELEMENTSOF(same_prog_same_hook)) >= 0); + assert_se(test_bpf_cgroup_programs(m, + "path_split_test.service", + path_split_test, + ELEMENTSOF(path_split_test)) >= 0); + return 0; +} diff --git a/src/test/test-bpf-lsm.c b/src/test/test-bpf-lsm.c new file mode 100644 index 0000000..42ea64c --- /dev/null +++ b/src/test/test-bpf-lsm.c @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "bpf-lsm.h" +#include "load-fragment.h" +#include "manager.h" +#include "process-util.h" +#include "rlimit-util.h" +#include "rm-rf.h" +#include "service.h" +#include "strv.h" +#include "tests.h" +#include "unit.h" +#include "virt.h" + +static int test_restrict_filesystems(Manager *m, const char *unit_name, const char *file_path, char **allowed_filesystems) { + _cleanup_free_ char *exec_start = NULL; + _cleanup_(unit_freep) Unit *u = NULL; + ExecContext *ec = NULL; + int cld_code, r; + + assert_se(u = unit_new(m, sizeof(Service))); + assert_se(unit_add_name(u, unit_name) == 0); + assert_se(ec = unit_get_exec_context(u)); + + STRV_FOREACH(allow_filesystem, allowed_filesystems) { + r = config_parse_restrict_filesystems( + u->id, "filename", 1, "Service", 1, "RestrictFileSystems", 0, + *allow_filesystem, ec, u); + if (r < 0) + return log_unit_error_errno(u, r, "Failed to parse RestrictFileSystems: %m"); + } + + assert_se(exec_start = strjoin("cat ", file_path)); + r = config_parse_exec(u->id, "filename", 1, "Service", 1, "ExecStart", + SERVICE_EXEC_START, exec_start, SERVICE(u)->exec_command, u); + if (r < 0) + return log_error_errno(r, "Failed to parse ExecStart"); + + SERVICE(u)->type = SERVICE_ONESHOT; + u->load_state = UNIT_LOADED; + + r = unit_start(u, NULL); + if (r < 0) + return log_error_errno(r, "Unit start failed %m"); + + while (!IN_SET(SERVICE(u)->state, SERVICE_DEAD, SERVICE_FAILED)) { + r = sd_event_run(m->event, UINT64_MAX); + if (r < 0) + return log_error_errno(errno, "Event run failed %m"); + } + + cld_code = SERVICE(u)->exec_command[SERVICE_EXEC_START]->exec_status.code; + if (cld_code != CLD_EXITED) + return log_error_errno(-SYNTHETIC_ERRNO(EBUSY), "ExecStart didn't exited, code='%s'", sigchld_code_to_string(cld_code)); + + if (SERVICE(u)->state != SERVICE_DEAD) + return log_error_errno(-SYNTHETIC_ERRNO(EBUSY), "Service is not dead"); + + return 0; +} + +int main(int argc, char *argv[]) { + _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; + _cleanup_(manager_freep) Manager *m = NULL; + _cleanup_free_ char *unit_dir = NULL; + struct rlimit rl; + int r; + + test_setup_logging(LOG_DEBUG); + + assert_se(getrlimit(RLIMIT_MEMLOCK, &rl) >= 0); + rl.rlim_cur = rl.rlim_max = MAX(rl.rlim_max, CAN_MEMLOCK_SIZE); + (void) setrlimit_closest(RLIMIT_MEMLOCK, &rl); + + if (!can_memlock()) + return log_tests_skipped("Can't use mlock()"); + + if (!lsm_bpf_supported(/* initialize = */ true)) + return log_tests_skipped("LSM BPF hooks are not supported"); + + r = enter_cgroup_subroot(NULL); + if (r == -ENOMEDIUM) + return log_tests_skipped("cgroupfs not available"); + + assert_se(get_testdata_dir("units", &unit_dir) >= 0); + assert_se(set_unit_path(unit_dir) >= 0); + assert_se(runtime_dir = setup_fake_runtime_dir()); + + assert_se(manager_new(RUNTIME_SCOPE_SYSTEM, MANAGER_TEST_RUN_BASIC, &m) >= 0); + assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + + /* We need to enable access to the filesystem where the binary is so we + * add @common-block */ + assert_se(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/tracing/printk_formats", STRV_MAKE("@common-block")) < 0); + assert_se(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/tracing/printk_formats", STRV_MAKE("tracefs", "@common-block")) >= 0); + assert_se(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/tracing/printk_formats", STRV_MAKE("tracefs", "@common-block", "~tracefs")) < 0); + assert_se(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/debug/sleep_time", STRV_MAKE("@common-block")) < 0); + assert_se(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/debug/sleep_time", STRV_MAKE("debugfs", "@common-block")) >= 0); + assert_se(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/debug/sleep_time", STRV_MAKE("~debugfs")) < 0); + + return 0; +} diff --git a/src/test/test-btrfs-physical-offset.c b/src/test/test-btrfs-physical-offset.c new file mode 100644 index 0000000..221c08e --- /dev/null +++ b/src/test/test-btrfs-physical-offset.c @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> +#include <fcntl.h> + +#include "btrfs-util.h" +#include "fd-util.h" +#include "format-util.h" +#include "log.h" +#include "memory-util.h" +#include "tests.h" + +int main(int argc, char *argv[]) { + _cleanup_close_ int fd = -EBADF; + uint64_t offset; + int r; + + assert(argc == 2); + assert(!isempty(argv[1])); + + test_setup_logging(LOG_DEBUG); + + fd = open(argv[1], O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) { + log_error_errno(errno, "Failed to open '%s': %m", argv[1]); + return EXIT_FAILURE; + } + + r = btrfs_get_file_physical_offset_fd(fd, &offset); + if (r < 0) { + log_error_errno(r, "Failed to get physical offset of '%s': %m", argv[1]); + return EXIT_FAILURE; + } + + printf("%" PRIu64 "\n", offset / page_size()); + return EXIT_SUCCESS; +} diff --git a/src/test/test-btrfs.c b/src/test/test-btrfs.c new file mode 100644 index 0000000..205142e --- /dev/null +++ b/src/test/test-btrfs.c @@ -0,0 +1,200 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <fcntl.h> +#include <sys/file.h> + +#include "btrfs-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "fileio.h" +#include "format-util.h" +#include "log.h" +#include "string-util.h" +#include "tests.h" + +int main(int argc, char *argv[]) { + BtrfsQuotaInfo quota; + int r, fd; + + test_setup_logging(LOG_DEBUG); + + fd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) + log_error_errno(errno, "Failed to open root directory: %m"); + else { + BtrfsSubvolInfo info; + + r = btrfs_subvol_get_info_fd(fd, 0, &info); + if (r < 0) + log_error_errno(r, "Failed to get subvolume info: %m"); + else { + log_info("otime: %s", FORMAT_TIMESTAMP(info.otime)); + log_info("read-only (search): %s", yes_no(info.read_only)); + } + + r = btrfs_qgroup_get_quota_fd(fd, 0, "a); + if (r < 0) + log_error_errno(r, "Failed to get quota info: %m"); + else { + log_info("referenced: %s", strna(FORMAT_BYTES(quota.referenced))); + log_info("exclusive: %s", strna(FORMAT_BYTES(quota.exclusive))); + log_info("referenced_max: %s", strna(FORMAT_BYTES(quota.referenced_max))); + log_info("exclusive_max: %s", strna(FORMAT_BYTES(quota.exclusive_max))); + } + + r = btrfs_subvol_get_read_only_fd(fd); + if (r < 0) + log_error_errno(r, "Failed to get read only flag: %m"); + else + log_info("read-only (ioctl): %s", yes_no(r)); + + safe_close(fd); + } + + r = btrfs_subvol_make(AT_FDCWD, "/xxxtest"); + if (r < 0) + log_error_errno(r, "Failed to make subvolume: %m"); + + r = write_string_file("/xxxtest/file", "ljsadhfljasdkfhlkjdsfha", WRITE_STRING_FILE_CREATE); + if (r < 0) + log_error_errno(r, "Failed to write file: %m"); + + r = btrfs_subvol_snapshot_at(AT_FDCWD, "/xxxtest", AT_FDCWD, "/xxxtest2", 0); + if (r < 0) + log_error_errno(r, "Failed to make snapshot: %m"); + + r = btrfs_subvol_snapshot_at(AT_FDCWD, "/xxxtest", AT_FDCWD, "/xxxtest3", BTRFS_SNAPSHOT_READ_ONLY); + if (r < 0) + log_error_errno(r, "Failed to make snapshot: %m"); + + r = btrfs_subvol_snapshot_at(AT_FDCWD, "/xxxtest", AT_FDCWD, "/xxxtest4", BTRFS_SNAPSHOT_LOCK_BSD); + if (r < 0) + log_error_errno(r, "Failed to make snapshot: %m"); + if (r >= 0) + assert_se(xopenat_lock(AT_FDCWD, "/xxxtest4", 0, 0, 0, LOCK_BSD, LOCK_EX|LOCK_NB) == -EAGAIN); + + safe_close(r); + + r = btrfs_subvol_remove("/xxxtest", BTRFS_REMOVE_QUOTA); + if (r < 0) + log_error_errno(r, "Failed to remove subvolume: %m"); + + r = btrfs_subvol_remove("/xxxtest2", BTRFS_REMOVE_QUOTA); + if (r < 0) + log_error_errno(r, "Failed to remove subvolume: %m"); + + r = btrfs_subvol_remove("/xxxtest3", BTRFS_REMOVE_QUOTA); + if (r < 0) + log_error_errno(r, "Failed to remove subvolume: %m"); + + r = btrfs_subvol_remove("/xxxtest4", BTRFS_REMOVE_QUOTA); + if (r < 0) + log_error_errno(r, "Failed to remove subvolume: %m"); + + r = btrfs_subvol_snapshot_at(AT_FDCWD, "/etc", AT_FDCWD, "/etc2", + BTRFS_SNAPSHOT_READ_ONLY|BTRFS_SNAPSHOT_FALLBACK_COPY); + if (r < 0) + log_error_errno(r, "Failed to make snapshot: %m"); + + r = btrfs_subvol_remove("/etc2", BTRFS_REMOVE_QUOTA); + if (r < 0) + log_error_errno(r, "Failed to remove subvolume: %m"); + + r = btrfs_subvol_make(AT_FDCWD, "/xxxrectest"); + if (r < 0) + log_error_errno(r, "Failed to make subvolume: %m"); + + r = btrfs_subvol_make(AT_FDCWD, "/xxxrectest/xxxrectest2"); + if (r < 0) + log_error_errno(r, "Failed to make subvolume: %m"); + + r = btrfs_subvol_make(AT_FDCWD, "/xxxrectest/xxxrectest3"); + if (r < 0) + log_error_errno(r, "Failed to make subvolume: %m"); + + r = btrfs_subvol_make(AT_FDCWD, "/xxxrectest/xxxrectest3/sub"); + if (r < 0) + log_error_errno(r, "Failed to make subvolume: %m"); + + if (mkdir("/xxxrectest/dir", 0755) < 0) + log_error_errno(errno, "Failed to make directory: %m"); + + r = btrfs_subvol_make(AT_FDCWD, "/xxxrectest/dir/xxxrectest4"); + if (r < 0) + log_error_errno(r, "Failed to make subvolume: %m"); + + if (mkdir("/xxxrectest/dir/xxxrectest4/dir", 0755) < 0) + log_error_errno(errno, "Failed to make directory: %m"); + + r = btrfs_subvol_make(AT_FDCWD, "/xxxrectest/dir/xxxrectest4/dir/xxxrectest5"); + if (r < 0) + log_error_errno(r, "Failed to make subvolume: %m"); + + if (mkdir("/xxxrectest/mnt", 0755) < 0) + log_error_errno(errno, "Failed to make directory: %m"); + + r = btrfs_subvol_snapshot_at(AT_FDCWD, "/xxxrectest", AT_FDCWD, "/xxxrectest2", BTRFS_SNAPSHOT_RECURSIVE); + if (r < 0) + log_error_errno(r, "Failed to snapshot subvolume: %m"); + + r = btrfs_subvol_remove("/xxxrectest", BTRFS_REMOVE_QUOTA|BTRFS_REMOVE_RECURSIVE); + if (r < 0) + log_error_errno(r, "Failed to recursively remove subvolume: %m"); + + r = btrfs_subvol_remove("/xxxrectest2", BTRFS_REMOVE_QUOTA|BTRFS_REMOVE_RECURSIVE); + if (r < 0) + log_error_errno(r, "Failed to recursively remove subvolume: %m"); + + r = btrfs_subvol_make(AT_FDCWD, "/xxxquotatest"); + if (r < 0) + log_error_errno(r, "Failed to make subvolume: %m"); + + r = btrfs_subvol_auto_qgroup("/xxxquotatest", 0, true); + if (r < 0) + log_error_errno(r, "Failed to set up auto qgroup: %m"); + + r = btrfs_subvol_make(AT_FDCWD, "/xxxquotatest/beneath"); + if (r < 0) + log_error_errno(r, "Failed to make subvolume: %m"); + + r = btrfs_subvol_auto_qgroup("/xxxquotatest/beneath", 0, false); + if (r < 0) + log_error_errno(r, "Failed to set up auto qgroup: %m"); + + r = btrfs_qgroup_set_limit("/xxxquotatest/beneath", 0, 4ULL * 1024 * 1024 * 1024); + if (r < 0) + log_error_errno(r, "Failed to set up quota limit: %m"); + + r = btrfs_subvol_set_subtree_quota_limit("/xxxquotatest", 0, 5ULL * 1024 * 1024 * 1024); + if (r < 0) + log_error_errno(r, "Failed to set up quota limit: %m"); + + r = btrfs_subvol_snapshot_at(AT_FDCWD, "/xxxquotatest", AT_FDCWD, "/xxxquotatest2", + BTRFS_SNAPSHOT_RECURSIVE|BTRFS_SNAPSHOT_QUOTA); + if (r < 0) + log_error_errno(r, "Failed to set up snapshot: %m"); + + r = btrfs_qgroup_get_quota("/xxxquotatest2/beneath", 0, "a); + if (r < 0) + log_error_errno(r, "Failed to query quota: %m"); + + if (r >= 0) + assert_se(quota.referenced_max == 4ULL * 1024 * 1024 * 1024); + + r = btrfs_subvol_get_subtree_quota("/xxxquotatest2", 0, "a); + if (r < 0) + log_error_errno(r, "Failed to query quota: %m"); + + if (r >= 0) + assert_se(quota.referenced_max == 5ULL * 1024 * 1024 * 1024); + + r = btrfs_subvol_remove("/xxxquotatest", BTRFS_REMOVE_QUOTA|BTRFS_REMOVE_RECURSIVE); + if (r < 0) + log_error_errno(r, "Failed remove subvolume: %m"); + + r = btrfs_subvol_remove("/xxxquotatest2", BTRFS_REMOVE_QUOTA|BTRFS_REMOVE_RECURSIVE); + if (r < 0) + log_error_errno(r, "Failed remove subvolume: %m"); + + return 0; +} diff --git a/src/test/test-bus-util.c b/src/test/test-bus-util.c new file mode 100644 index 0000000..2f52bca --- /dev/null +++ b/src/test/test-bus-util.c @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "bus-util.h" +#include "log.h" +#include "tests.h" + +static int callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + return 1; +} + +static void destroy_callback(void *userdata) { + int *n_called = userdata; + + (*n_called) ++; +} + +TEST(destroy_callback) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + sd_bus_slot *slot = NULL; + sd_bus_destroy_t t; + + int r, n_called = 0; + + r = bus_open_system_watch_bind_with_description(&bus, "test-bus"); + if (r < 0) { + log_error_errno(r, "Failed to connect to bus: %m"); + return; + } + + r = sd_bus_request_name_async(bus, &slot, "org.freedesktop.systemd.test-bus-util", 0, callback, &n_called); + assert_se(r == 1); + + assert_se(sd_bus_slot_get_destroy_callback(slot, NULL) == 0); + assert_se(sd_bus_slot_get_destroy_callback(slot, &t) == 0); + + assert_se(sd_bus_slot_set_destroy_callback(slot, destroy_callback) == 0); + assert_se(sd_bus_slot_get_destroy_callback(slot, NULL) == 1); + assert_se(sd_bus_slot_get_destroy_callback(slot, &t) == 1); + assert_se(t == destroy_callback); + + /* Force cleanup so we can look at n_called */ + assert_se(n_called == 0); + sd_bus_slot_unref(slot); + assert_se(n_called == 1); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-calendarspec.c b/src/test/test-calendarspec.c new file mode 100644 index 0000000..18a0f8f --- /dev/null +++ b/src/test/test-calendarspec.c @@ -0,0 +1,264 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "calendarspec.h" +#include "env-util.h" +#include "errno-util.h" +#include "string-util.h" +#include "tests.h" + +static void _test_one(int line, const char *input, const char *output) { + _cleanup_(calendar_spec_freep) CalendarSpec *c = NULL; + _cleanup_free_ char *p = NULL, *q = NULL; + usec_t u; + int r; + + r = calendar_spec_from_string(input, &c); + if (r < 0) + log_error_errno(r, "Failed to parse \"%s\": %m", input); + assert_se(r >= 0); + + assert_se(calendar_spec_to_string(c, &p) >= 0); + log_info("line %d: \"%s\" → \"%s\"%s%s", line, input, p, + !streq(p, output) ? " expected:" : "", + !streq(p, output) ? output : ""); + + assert_se(streq(p, output)); + + u = now(CLOCK_REALTIME); + r = calendar_spec_next_usec(c, u, &u); + log_info("Next: %s", r < 0 ? STRERROR(r) : FORMAT_TIMESTAMP(u)); + c = calendar_spec_free(c); + + assert_se(calendar_spec_from_string(p, &c) >= 0); + assert_se(calendar_spec_to_string(c, &q) >= 0); + + assert_se(streq(q, p)); +} +#define test_one(input, output) _test_one(__LINE__, input, output) + +static void _test_next(int line, const char *input, const char *new_tz, usec_t after, usec_t expect) { + _cleanup_(calendar_spec_freep) CalendarSpec *c = NULL; + usec_t u; + char *old_tz; + int r; + + old_tz = getenv("TZ"); + if (old_tz) + old_tz = strdupa_safe(old_tz); + + if (!isempty(new_tz)) + new_tz = strjoina(":", new_tz); + + assert_se(set_unset_env("TZ", new_tz, true) == 0); + tzset(); + + assert_se(calendar_spec_from_string(input, &c) >= 0); + + log_info("line %d: \"%s\" new_tz=%s", line, input, strnull(new_tz)); + + u = after; + r = calendar_spec_next_usec(c, after, &u); + log_info("At: %s", r < 0 ? STRERROR(r) : FORMAT_TIMESTAMP_STYLE(u, TIMESTAMP_US)); + if (expect != USEC_INFINITY) + assert_se(r >= 0 && u == expect); + else + assert_se(r == -ENOENT); + + assert_se(set_unset_env("TZ", old_tz, true) == 0); + tzset(); +} +#define test_next(input, new_tz, after, expect) _test_next(__LINE__, input,new_tz,after,expect) + +TEST(timestamp) { + char buf[FORMAT_TIMESTAMP_MAX]; + _cleanup_free_ char *t = NULL; + _cleanup_(calendar_spec_freep) CalendarSpec *c = NULL; + usec_t x, y; + + /* Ensure that a timestamp is also a valid calendar specification. Convert forth and back */ + + x = now(CLOCK_REALTIME); + + assert_se(format_timestamp_style(buf, sizeof buf, x, TIMESTAMP_US)); + log_info("%s", buf); + assert_se(calendar_spec_from_string(buf, &c) >= 0); + assert_se(calendar_spec_to_string(c, &t) >= 0); + log_info("%s", t); + + assert_se(parse_timestamp(t, &y) >= 0); + assert_se(y == x); +} + +TEST(hourly_bug_4031) { + _cleanup_(calendar_spec_freep) CalendarSpec *c = NULL; + usec_t n, u, w; + int r; + + assert_se(calendar_spec_from_string("hourly", &c) >= 0); + n = now(CLOCK_REALTIME); + assert_se((r = calendar_spec_next_usec(c, n, &u)) >= 0); + + log_info("Now: %s (%"PRIu64")", FORMAT_TIMESTAMP_STYLE(n, TIMESTAMP_US), n); + log_info("Next hourly: %s (%"PRIu64")", r < 0 ? STRERROR(r) : FORMAT_TIMESTAMP_STYLE(u, TIMESTAMP_US), u); + + assert_se((r = calendar_spec_next_usec(c, u, &w)) >= 0); + log_info("Next hourly: %s (%"PRIu64")", r < 0 ? STRERROR(r) : FORMAT_TIMESTAMP_STYLE(w, TIMESTAMP_US), w); + + assert_se(n < u); + assert_se(u <= n + USEC_PER_HOUR); + assert_se(u < w); + assert_se(w <= u + USEC_PER_HOUR); +} + +TEST(calendar_spec_one) { + test_one("Sat,Thu,Mon-Wed,Sat-Sun", "Mon..Thu,Sat,Sun *-*-* 00:00:00"); + test_one("Sat,Thu,Mon..Wed,Sat..Sun", "Mon..Thu,Sat,Sun *-*-* 00:00:00"); + test_one("Mon,Sun 12-*-* 2,1:23", "Mon,Sun 2012-*-* 01,02:23:00"); + test_one("Wed *-1", "Wed *-*-01 00:00:00"); + test_one("Wed-Wed,Wed *-1", "Wed *-*-01 00:00:00"); + test_one("Wed..Wed,Wed *-1", "Wed *-*-01 00:00:00"); + test_one("Wed, 17:48", "Wed *-*-* 17:48:00"); + test_one("Wednesday,", "Wed *-*-* 00:00:00"); + test_one("Wed-Sat,Tue 12-10-15 1:2:3", "Tue..Sat 2012-10-15 01:02:03"); + test_one("Wed..Sat,Tue 12-10-15 1:2:3", "Tue..Sat 2012-10-15 01:02:03"); + test_one("*-*-7 0:0:0", "*-*-07 00:00:00"); + test_one("10-15", "*-10-15 00:00:00"); + test_one("monday *-12-* 17:00", "Mon *-12-* 17:00:00"); + test_one("Mon,Fri *-*-3,1,2 *:30:45", "Mon,Fri *-*-01,02,03 *:30:45"); + test_one("12,14,13,12:20,10,30", "*-*-* 12,13,14:10,20,30:00"); + test_one("mon,fri *-1/2-1,3 *:30:45", "Mon,Fri *-01/2-01,03 *:30:45"); + test_one("03-05 08:05:40", "*-03-05 08:05:40"); + test_one("08:05:40", "*-*-* 08:05:40"); + test_one("05:40", "*-*-* 05:40:00"); + test_one("Sat,Sun 12-05 08:05:40", "Sat,Sun *-12-05 08:05:40"); + test_one("Sat,Sun 08:05:40", "Sat,Sun *-*-* 08:05:40"); + test_one("2003-03-05 05:40", "2003-03-05 05:40:00"); + test_one("2003-03-05", "2003-03-05 00:00:00"); + test_one("03-05", "*-03-05 00:00:00"); + test_one("hourly", "*-*-* *:00:00"); + test_one("daily", "*-*-* 00:00:00"); + test_one("monthly", "*-*-01 00:00:00"); + test_one("weekly", "Mon *-*-* 00:00:00"); + test_one("minutely", "*-*-* *:*:00"); + test_one("quarterly", "*-01,04,07,10-01 00:00:00"); + test_one("semi-annually", "*-01,07-01 00:00:00"); + test_one("annually", "*-01-01 00:00:00"); + test_one("*:2/3", "*-*-* *:02/3:00"); + test_one("2015-10-25 01:00:00 uTc", "2015-10-25 01:00:00 UTC"); + test_one("2015-10-25 01:00:00 Asia/Vladivostok", "2015-10-25 01:00:00 Asia/Vladivostok"); + test_one("weekly Pacific/Auckland", "Mon *-*-* 00:00:00 Pacific/Auckland"); + test_one("2016-03-27 03:17:00.4200005", "2016-03-27 03:17:00.420001"); + test_one("2016-03-27 03:17:00/0.42", "2016-03-27 03:17:00/0.420000"); + test_one("9..11,13:00,30", "*-*-* 09..11,13:00,30:00"); + test_one("1..3-1..3 1..3:1..3", "*-01..03-01..03 01..03:01..03:00"); + test_one("00:00:1.125..2.125", "*-*-* 00:00:01.125000..02.125000"); + test_one("00:00:1.0..3.8", "*-*-* 00:00:01..03"); + test_one("00:00:01..03", "*-*-* 00:00:01..03"); + test_one("00:00:01/2,02..03", "*-*-* 00:00:01/2,02..03"); + test_one("*:4,30:0..3", "*-*-* *:04,30:00..03"); + test_one("*:4,30:0/1", "*-*-* *:04,30:*"); + test_one("*:4,30:0/1,3,5", "*-*-* *:04,30:*"); + test_one("*-*~1 Utc", "*-*~01 00:00:00 UTC"); + test_one("*-*~05,3 ", "*-*~03,05 00:00:00"); + test_one("*-*~* 00:00:00", "*-*-* 00:00:00"); + test_one("Monday", "Mon *-*-* 00:00:00"); + test_one("Monday *-*-*", "Mon *-*-* 00:00:00"); + test_one("*-*-*", "*-*-* 00:00:00"); + test_one("*:*:*", "*-*-* *:*:*"); + test_one("*:*", "*-*-* *:*:00"); + test_one("12:*", "*-*-* 12:*:00"); + test_one("*:30", "*-*-* *:30:00"); + test_one("93..00-*-*", "1993..2000-*-* 00:00:00"); + test_one("00..07-*-*", "2000..2007-*-* 00:00:00"); + test_one("*:20..39/5", "*-*-* *:20..35/5:00"); + test_one("00:00:20..40/1", "*-*-* 00:00:20..40"); + test_one("*~03/1,03..05", "*-*~03/1,03..05 00:00:00"); + /* UNIX timestamps are always UTC */ + test_one("@1493187147", "2017-04-26 06:12:27 UTC"); + test_one("@1493187147 UTC", "2017-04-26 06:12:27 UTC"); + test_one("@0", "1970-01-01 00:00:00 UTC"); + test_one("@0 UTC", "1970-01-01 00:00:00 UTC"); + test_one("*:05..05", "*-*-* *:05:00"); + test_one("*:05..10/6", "*-*-* *:05:00"); +} + +TEST(calendar_spec_next) { + test_next("2016-03-27 03:17:00", "", 12345, 1459048620000000); + test_next("2016-03-27 03:17:00", "CET", 12345, 1459041420000000); + test_next("2016-03-27 03:17:00", "EET", 12345, -1); + test_next("2016-03-27 03:17:00 UTC", NULL, 12345, 1459048620000000); + test_next("2016-03-27 03:17:00 UTC", "", 12345, 1459048620000000); + test_next("2016-03-27 03:17:00 UTC", "CET", 12345, 1459048620000000); + test_next("2016-03-27 03:17:00 UTC", "EET", 12345, 1459048620000000); + test_next("2016-03-27 03:17:00.420000001 UTC", "EET", 12345, 1459048620420000); + test_next("2016-03-27 03:17:00.4200005 UTC", "EET", 12345, 1459048620420001); + test_next("2015-11-13 09:11:23.42", "EET", 12345, 1447398683420000); + test_next("2015-11-13 09:11:23.42/1.77", "EET", 1447398683420000, 1447398685190000); + test_next("2015-11-13 09:11:23.42/1.77", "EET", 1447398683419999, 1447398683420000); + test_next("Sun 16:00:00", "CET", 1456041600123456, 1456066800000000); + test_next("*-04-31", "", 12345, -1); + test_next("2016-02~01 UTC", "", 12345, 1456704000000000); + test_next("Mon 2017-05~01..07 UTC", "", 12345, 1496016000000000); + test_next("Mon 2017-05~07/1 UTC", "", 12345, 1496016000000000); + test_next("*-*-01/5 04:00:00 UTC", "", 1646010000000000, 1646107200000000); + test_next("*-01/7-01 04:00:00 UTC", "", 1664607600000000, 1672545600000000); + test_next("2017-08-06 9,11,13,15,17:00 UTC", "", 1502029800000000, 1502031600000000); + test_next("2017-08-06 9..17/2:00 UTC", "", 1502029800000000, 1502031600000000); + test_next("2016-12-* 3..21/6:00 UTC", "", 1482613200000001, 1482634800000000); + test_next("2017-09-24 03:30:00 Pacific/Auckland", "", 12345, 1506177000000000); + /* Due to daylight saving time - 2017-09-24 02:30:00 does not exist */ + test_next("2017-09-24 02:30:00 Pacific/Auckland", "", 12345, -1); + test_next("2017-04-02 02:30:00 Pacific/Auckland", "", 12345, 1491053400000000); + /* Confirm that even though it's a time change here (backward) 02:30 happens only once */ + test_next("2017-04-02 02:30:00 Pacific/Auckland", "", 1491053400000000, -1); + test_next("2017-04-02 03:30:00 Pacific/Auckland", "", 12345, 1491060600000000); + /* Confirm that timezones in the Spec work regardless of current timezone */ + test_next("2017-09-09 20:42:00 Pacific/Auckland", "", 12345, 1504946520000000); + test_next("2017-09-09 20:42:00 Pacific/Auckland", "EET", 12345, 1504946520000000); + /* Check that we don't start looping if mktime() moves us backwards */ + test_next("Sun *-*-* 01:00:00 Europe/Dublin", "", 1616412478000000, 1617494400000000); + test_next("Sun *-*-* 01:00:00 Europe/Dublin", "IST", 1616412478000000, 1617494400000000); +} + +TEST(calendar_spec_from_string) { + CalendarSpec *c; + + assert_se(calendar_spec_from_string("test", &c) == -EINVAL); + assert_se(calendar_spec_from_string(" utc", &c) == -EINVAL); + assert_se(calendar_spec_from_string(" ", &c) == -EINVAL); + assert_se(calendar_spec_from_string("", &c) == -EINVAL); + assert_se(calendar_spec_from_string("7", &c) == -EINVAL); + assert_se(calendar_spec_from_string("121212:1:2", &c) == -EINVAL); + assert_se(calendar_spec_from_string("2000-03-05.23 00:00:00", &c) == -EINVAL); + assert_se(calendar_spec_from_string("2000-03-05 00:00.1:00", &c) == -EINVAL); + assert_se(calendar_spec_from_string("00:00:00/0.00000001", &c) == -ERANGE); + assert_se(calendar_spec_from_string("00:00:00.0..00.9", &c) == -EINVAL); + assert_se(calendar_spec_from_string("2016~11-22", &c) == -EINVAL); + assert_se(calendar_spec_from_string("*-*~5/5", &c) == -EINVAL); + assert_se(calendar_spec_from_string("Monday.. 12:00", &c) == -EINVAL); + assert_se(calendar_spec_from_string("Monday..", &c) == -EINVAL); + assert_se(calendar_spec_from_string("-00:+00/-5", &c) == -EINVAL); + assert_se(calendar_spec_from_string("00:+00/-5", &c) == -EINVAL); + assert_se(calendar_spec_from_string("2016- 11- 24 12: 30: 00", &c) == -EINVAL); + assert_se(calendar_spec_from_string("*~29", &c) == -EINVAL); + assert_se(calendar_spec_from_string("*~16..31", &c) == -EINVAL); + assert_se(calendar_spec_from_string("12..1/2-*", &c) == -EINVAL); + assert_se(calendar_spec_from_string("20/4:00", &c) == -EINVAL); + assert_se(calendar_spec_from_string("00:00/60", &c) == -EINVAL); + assert_se(calendar_spec_from_string("00:00:2300", &c) == -ERANGE); + assert_se(calendar_spec_from_string("00:00:18446744073709551615", &c) == -ERANGE); + assert_se(calendar_spec_from_string("@88588582097858858", &c) == -ERANGE); + assert_se(calendar_spec_from_string("*:4,30:*,5", &c) == -EINVAL); + assert_se(calendar_spec_from_string("*:4,30:5,*", &c) == -EINVAL); + assert_se(calendar_spec_from_string("*:4,30:*\n", &c) == -EINVAL); +} + +static int intro(void) { + /* Tests have hard-coded results that do not expect a specific timezone to be set by the caller */ + assert_se(unsetenv("TZ") >= 0); + + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/test/test-cap-list.c b/src/test/test-cap-list.c new file mode 100644 index 0000000..a9cbf69 --- /dev/null +++ b/src/test/test-cap-list.c @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdio.h> +#include <sys/prctl.h> + +#include "alloc-util.h" +#include "cap-list.h" +#include "capability-util.h" +#include "parse-util.h" +#include "random-util.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" + +/* verify the capability parser */ +TEST(cap_list) { + assert_se(!capability_to_name(-1)); + assert_se(!capability_to_name(capability_list_length())); + assert_se(!capability_to_name(63)); + assert_se(!capability_to_name(64)); + + assert_se(!CAPABILITY_TO_STRING(-1)); + if (capability_list_length() <= 62) + assert_se(streq(CAPABILITY_TO_STRING(62), "0x3e")); + assert_se(!CAPABILITY_TO_STRING(64)); + + for (int i = 0; i < capability_list_length(); i++) { + const char *n; + + assert_se(n = capability_to_name(i)); + assert_se(capability_from_name(n) == i); + printf("%s = %i\n", n, i); + + assert_se(streq(CAPABILITY_TO_STRING(i), n)); + } + + assert_se(capability_from_name("asdfbsd") == -EINVAL); + assert_se(capability_from_name("CAP_AUDIT_READ") == CAP_AUDIT_READ); + assert_se(capability_from_name("cap_audit_read") == CAP_AUDIT_READ); + assert_se(capability_from_name("cAp_aUdIt_rEAd") == CAP_AUDIT_READ); + assert_se(capability_from_name("0") == 0); + assert_se(capability_from_name("15") == 15); + assert_se(capability_from_name("62") == 62); + assert_se(capability_from_name("63") == -EINVAL); + assert_se(capability_from_name("64") == -EINVAL); + assert_se(capability_from_name("-1") == -EINVAL); + + for (int i = 0; i < capability_list_length(); i++) { + _cleanup_cap_free_charp_ char *a = NULL; + const char *b; + unsigned u; + + assert_se(a = cap_to_name(i)); + + /* quit the loop as soon as libcap starts returning + * numeric ids, formatted as strings */ + if (safe_atou(a, &u) >= 0) + break; + + assert_se(b = capability_to_name(i)); + + printf("%s vs. %s\n", a, b); + + assert_se(strcasecmp(a, b) == 0); + } +} + +static void test_capability_set_one(uint64_t c, const char *t) { + _cleanup_free_ char *t1 = NULL; + uint64_t c1, c_masked = c & all_capabilities(); + + assert_se(capability_set_to_string(c, &t1) == 0); + assert_se(streq(t1, t)); + + assert_se(capability_set_from_string(t1, &c1) > 0); + assert_se(c1 == c_masked); + + free(t1); + assert_se(t1 = strjoin("'cap_chown cap_dac_override' \"cap_setgid cap_setuid\"", t, + " hogehoge foobar 18446744073709551616 3.14 -3 ", t)); + assert_se(capability_set_from_string(t1, &c1) == 0); + assert_se(c1 == c_masked); +} + +TEST(capability_set_from_string) { + uint64_t c; + + assert_se(capability_set_from_string(NULL, &c) > 0); + assert_se(c == 0); + + assert_se(capability_set_from_string("", &c) > 0); + assert_se(c == 0); + + assert_se(capability_set_from_string("0", &c) > 0); + assert_se(c == UINT64_C(1)); + + assert_se(capability_set_from_string("1", &c) > 0); + assert_se(c == UINT64_C(1) << 1); + + assert_se(capability_set_from_string("0 1 2 3", &c) > 0); + assert_se(c == (UINT64_C(1) << 4) - 1); +} + +static void test_capability_set_to_strv_one(uint64_t m, char **l) { + _cleanup_strv_free_ char **b = NULL; + + assert_se(capability_set_to_strv(m, &b) >= 0); + assert_se(strv_equal(l, b)); +} + +TEST(capability_set_to_strv) { + test_capability_set_to_strv_one(0, STRV_MAKE(NULL)); + test_capability_set_to_strv_one(UINT64_C(1) << CAP_MKNOD, STRV_MAKE("cap_mknod")); + test_capability_set_to_strv_one((UINT64_C(1) << CAP_MKNOD) | + (UINT64_C(1) << CAP_NET_BIND_SERVICE), STRV_MAKE("cap_net_bind_service", "cap_mknod")); + test_capability_set_to_strv_one((UINT64_C(1) << CAP_MKNOD) | + (UINT64_C(1) << CAP_NET_BIND_SERVICE) | + (UINT64_C(1) << CAP_IPC_OWNER), STRV_MAKE("cap_net_bind_service", "cap_ipc_owner", "cap_mknod")); +} + +static void test_capability_set_to_string_invalid(uint64_t invalid_cap_set) { + uint64_t c; + + test_capability_set_one(invalid_cap_set, ""); + + c = (UINT64_C(1) << CAP_DAC_OVERRIDE | invalid_cap_set); + test_capability_set_one(c, "cap_dac_override"); + + c = (UINT64_C(1) << CAP_CHOWN | + UINT64_C(1) << CAP_DAC_OVERRIDE | + UINT64_C(1) << CAP_DAC_READ_SEARCH | + UINT64_C(1) << CAP_FOWNER | + UINT64_C(1) << CAP_SETGID | + UINT64_C(1) << CAP_SETUID | + UINT64_C(1) << CAP_SYS_PTRACE | + UINT64_C(1) << CAP_SYS_ADMIN | + UINT64_C(1) << CAP_AUDIT_CONTROL | + UINT64_C(1) << CAP_MAC_OVERRIDE | + UINT64_C(1) << CAP_SYSLOG | + invalid_cap_set); + test_capability_set_one(c, ("cap_chown cap_dac_override cap_dac_read_search cap_fowner " + "cap_setgid cap_setuid cap_sys_ptrace cap_sys_admin " + "cap_audit_control cap_mac_override cap_syslog")); +} + +TEST(capability_set_to_string) { + test_capability_set_to_string_invalid(0); + + /* once the kernel supports 62 caps, there are no 'invalid' numbers + * for us to test with */ + if (cap_last_cap() < 62) + test_capability_set_to_string_invalid(all_capabilities() + 1); +} + +TEST(capability_set_to_string_negative) { + + for (unsigned i = 0; i < 150; i++) { + _cleanup_free_ char *a = NULL, *b = NULL; + + uint64_t m = + random_u64() % (UINT64_C(1) << (cap_last_cap() + 1)); + + assert_se(capability_set_to_string(m, &a) >= 0); + assert_se(capability_set_to_string_negative(m, &b) >= 0); + + printf("%s (%zu) → ", a, strlen(a)); + + if (streq(a, b)) + printf("same\n"); + else + printf("%s (%zu)\n", b, strlen(b)); + + assert_se(strlen(b) <= strlen(a)); + } +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-capability.c b/src/test/test-capability.c new file mode 100644 index 0000000..e8a0569 --- /dev/null +++ b/src/test/test-capability.c @@ -0,0 +1,332 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <netinet/in.h> +#include <pwd.h> +#include <sys/prctl.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <unistd.h> + +#define TEST_CAPABILITY_C + +#include "alloc-util.h" +#include "capability-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "macro.h" +#include "missing_prctl.h" +#include "parse-util.h" +#include "process-util.h" +#include "string-util.h" +#include "tests.h" + +static uid_t test_uid = -1; +static gid_t test_gid = -1; + +#if HAS_FEATURE_ADDRESS_SANITIZER +/* Keep CAP_SYS_PTRACE when running under Address Sanitizer */ +static const uint64_t test_flags = UINT64_C(1) << CAP_SYS_PTRACE; +#else +/* We keep CAP_DAC_OVERRIDE to avoid errors with gcov when doing test coverage */ +static const uint64_t test_flags = UINT64_C(1) << CAP_DAC_OVERRIDE; +#endif + +/* verify cap_last_cap() against /proc/sys/kernel/cap_last_cap */ +static void test_last_cap_file(void) { + _cleanup_free_ char *content = NULL; + unsigned long val = 0; + int r; + + r = read_one_line_file("/proc/sys/kernel/cap_last_cap", &content); + if (r == -ENOENT || ERRNO_IS_NEG_PRIVILEGE(r)) /* kernel pre 3.2 or no access */ + return; + assert_se(r >= 0); + + r = safe_atolu(content, &val); + assert_se(r >= 0); + assert_se(val != 0); + assert_se(val == cap_last_cap()); +} + +/* verify cap_last_cap() against syscall probing */ +static void test_last_cap_probe(void) { + unsigned long p = (unsigned long)CAP_LAST_CAP; + + if (prctl(PR_CAPBSET_READ, p) < 0) { + for (p--; p > 0; p --) + if (prctl(PR_CAPBSET_READ, p) >= 0) + break; + } else { + for (;; p++) + if (prctl(PR_CAPBSET_READ, p+1) < 0) + break; + } + + assert_se(p != 0); + assert_se(p == cap_last_cap()); +} + +static void fork_test(void (*test_func)(void)) { + pid_t pid = 0; + + pid = fork(); + assert_se(pid >= 0); + if (pid == 0) { + test_func(); + exit(EXIT_SUCCESS); + } else if (pid > 0) { + int status; + + assert_se(waitpid(pid, &status, 0) > 0); + assert_se(WIFEXITED(status) && WEXITSTATUS(status) == 0); + } +} + +static void show_capabilities(void) { + cap_t caps; + char *text; + + caps = cap_get_proc(); + assert_se(caps); + + text = cap_to_text(caps, NULL); + assert_se(text); + + log_info("Capabilities:%s", text); + cap_free(caps); + cap_free(text); +} + +static int setup_tests(bool *run_ambient) { + struct passwd *nobody; + int r; + + nobody = getpwnam(NOBODY_USER_NAME); + if (!nobody) + return log_warning_errno(SYNTHETIC_ERRNO(ENOENT), "Couldn't find 'nobody' user: %m"); + + test_uid = nobody->pw_uid; + test_gid = nobody->pw_gid; + + r = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0); + /* There's support for PR_CAP_AMBIENT if the prctl() call succeeded or error code was something else + * than EINVAL. The EINVAL check should be good enough to rule out false positives. */ + *run_ambient = r >= 0 || errno != EINVAL; + + return 0; +} + +static void test_drop_privileges_keep_net_raw(void) { + int sock; + + sock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP); + assert_se(sock >= 0); + safe_close(sock); + + assert_se(drop_privileges(test_uid, test_gid, test_flags | (1ULL << CAP_NET_RAW)) >= 0); + assert_se(getuid() == test_uid); + assert_se(getgid() == test_gid); + show_capabilities(); + + sock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP); + assert_se(sock >= 0); + safe_close(sock); +} + +static void test_drop_privileges_dontkeep_net_raw(void) { + int sock; + + sock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP); + assert_se(sock >= 0); + safe_close(sock); + + assert_se(drop_privileges(test_uid, test_gid, test_flags) >= 0); + assert_se(getuid() == test_uid); + assert_se(getgid() == test_gid); + show_capabilities(); + + sock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP); + assert_se(sock < 0); +} + +static void test_drop_privileges_fail(void) { + assert_se(drop_privileges(test_uid, test_gid, test_flags) >= 0); + assert_se(getuid() == test_uid); + assert_se(getgid() == test_gid); + + assert_se(drop_privileges(test_uid, test_gid, test_flags) < 0); + assert_se(drop_privileges(0, 0, test_flags) < 0); +} + +static void test_drop_privileges(void) { + fork_test(test_drop_privileges_fail); + + if (have_effective_cap(CAP_NET_RAW) <= 0) /* The remaining two tests only work if we have CAP_NET_RAW + * in the first place. If we are run in some restricted + * container environment we might not. */ + return; + + fork_test(test_drop_privileges_keep_net_raw); + fork_test(test_drop_privileges_dontkeep_net_raw); +} + +static void test_have_effective_cap(void) { + assert_se(have_effective_cap(CAP_KILL) > 0); + assert_se(have_effective_cap(CAP_CHOWN) > 0); + + assert_se(drop_privileges(test_uid, test_gid, test_flags | (1ULL << CAP_KILL)) >= 0); + assert_se(getuid() == test_uid); + assert_se(getgid() == test_gid); + + assert_se(have_effective_cap(CAP_KILL) > 0); + assert_se(have_effective_cap(CAP_CHOWN) == 0); +} + +static void test_update_inherited_set(void) { + cap_t caps; + uint64_t set = 0; + cap_flag_value_t fv; + + caps = cap_get_proc(); + assert_se(caps); + + set = (UINT64_C(1) << CAP_CHOWN); + + assert_se(!capability_update_inherited_set(caps, set)); + assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv)); + assert_se(fv == CAP_SET); + + cap_free(caps); +} + +static void test_apply_ambient_caps(void) { + cap_t caps; + uint64_t set = 0; + cap_flag_value_t fv; + + assert_se(prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_CHOWN, 0, 0) == 0); + + set = (UINT64_C(1) << CAP_CHOWN); + + assert_se(!capability_ambient_set_apply(set, true)); + + caps = cap_get_proc(); + assert_se(caps); + assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv)); + assert_se(fv == CAP_SET); + cap_free(caps); + + assert_se(prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_CHOWN, 0, 0) == 1); + + assert_se(!capability_ambient_set_apply(0, true)); + caps = cap_get_proc(); + assert_se(caps); + assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv)); + assert_se(fv == CAP_CLEAR); + cap_free(caps); + + assert_se(prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_CHOWN, 0, 0) == 0); +} + +static void test_ensure_cap_64_bit(void) { + _cleanup_free_ char *content = NULL; + unsigned long p = 0; + int r; + + r = read_one_line_file("/proc/sys/kernel/cap_last_cap", &content); + if (r == -ENOENT || ERRNO_IS_NEG_PRIVILEGE(r)) /* kernel pre 3.2 or no access */ + return; + assert_se(r >= 0); + + assert_se(safe_atolu(content, &p) >= 0); + + /* If caps don't fit into 64-bit anymore, we have a problem, fail the test. */ + assert_se(p <= 63); + + /* Also check for the header definition */ + assert_cc(CAP_LAST_CAP <= 63); +} + +static void test_capability_get_ambient(void) { + uint64_t c; + int r; + + assert_se(capability_get_ambient(&c) >= 0); + + r = safe_fork("(getambient)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_WAIT|FORK_LOG, NULL); + assert_se(r >= 0); + + if (r == 0) { + int x, y; + /* child */ + assert_se(capability_get_ambient(&c) >= 0); + + x = capability_ambient_set_apply( + (UINT64_C(1) << CAP_MKNOD)| + (UINT64_C(1) << CAP_LINUX_IMMUTABLE), + /* also_inherit= */ true); + assert_se(x >= 0 || ERRNO_IS_PRIVILEGE(x)); + + assert_se(capability_get_ambient(&c) >= 0); + assert_se(x < 0 || FLAGS_SET(c, UINT64_C(1) << CAP_MKNOD)); + assert_se(x < 0 || FLAGS_SET(c, UINT64_C(1) << CAP_LINUX_IMMUTABLE)); + assert_se(x < 0 || !FLAGS_SET(c, UINT64_C(1) << CAP_SETPCAP)); + + y = capability_bounding_set_drop( + ((UINT64_C(1) << CAP_LINUX_IMMUTABLE)| + (UINT64_C(1) << CAP_SETPCAP)), + /* right_now= */ true); + assert_se(y >= 0 || ERRNO_IS_PRIVILEGE(y)); + + assert_se(capability_get_ambient(&c) >= 0); + assert_se(x < 0 || y < 0 || !FLAGS_SET(c, UINT64_C(1) << CAP_MKNOD)); + assert_se(x < 0 || y < 0 || FLAGS_SET(c, UINT64_C(1) << CAP_LINUX_IMMUTABLE)); + assert_se(x < 0 || y < 0 || !FLAGS_SET(c, UINT64_C(1) << CAP_SETPCAP)); + + y = capability_bounding_set_drop( + (UINT64_C(1) << CAP_SETPCAP), + /* right_now= */ true); + assert_se(y >= 0 || ERRNO_IS_PRIVILEGE(y)); + + assert_se(capability_get_ambient(&c) >= 0); + assert_se(x < 0 || y < 0 || !FLAGS_SET(c, UINT64_C(1) << CAP_MKNOD)); + assert_se(x < 0 || y < 0 || !FLAGS_SET(c, UINT64_C(1) << CAP_LINUX_IMMUTABLE)); + assert_se(x < 0 || y < 0 || !FLAGS_SET(c, UINT64_C(1) << CAP_SETPCAP)); + + _exit(EXIT_SUCCESS); + } +} + +int main(int argc, char *argv[]) { + bool run_ambient; + + test_setup_logging(LOG_DEBUG); + + test_ensure_cap_64_bit(); + + test_last_cap_file(); + test_last_cap_probe(); + + log_info("have ambient caps: %s", yes_no(ambient_capabilities_supported())); + + if (getuid() != 0) + return log_tests_skipped("not running as root"); + + if (setup_tests(&run_ambient) < 0) + return log_tests_skipped("setup failed"); + + show_capabilities(); + + test_drop_privileges(); + test_update_inherited_set(); + + fork_test(test_have_effective_cap); + + if (run_ambient) + fork_test(test_apply_ambient_caps); + + test_capability_get_ambient(); + + return 0; +} diff --git a/src/test/test-cgroup-cpu.c b/src/test/test-cgroup-cpu.c new file mode 100644 index 0000000..fcf84d3 --- /dev/null +++ b/src/test/test-cgroup-cpu.c @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "cgroup.h" +#include "log.h" +#include "tests.h" + +TEST(group_cpu_adjust_period) { + /* Period 1ms, quota 40% -> Period 2.5ms */ + assert_se(2500 == cgroup_cpu_adjust_period(USEC_PER_MSEC, 400 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC)); + /* Period 10ms, quota 10% -> keep. */ + assert_se(10 * USEC_PER_MSEC == cgroup_cpu_adjust_period(10 * USEC_PER_MSEC, 100 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC)); + /* Period 1ms, quota 1000% -> keep. */ + assert_se(USEC_PER_MSEC == cgroup_cpu_adjust_period(USEC_PER_MSEC, 10000 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC)); + /* Period 100ms, quota 30% -> keep. */ + assert_se(100 * USEC_PER_MSEC == cgroup_cpu_adjust_period(100 * USEC_PER_MSEC, 300 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC)); + /* Period 5s, quota 40% -> adjust to 1s. */ + assert_se(USEC_PER_SEC == cgroup_cpu_adjust_period(5 * USEC_PER_SEC, 400 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC)); + /* Period 2s, quota 250% -> adjust to 1s. */ + assert_se(USEC_PER_SEC == cgroup_cpu_adjust_period(2 * USEC_PER_SEC, 2500 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC)); + /* Period 10us, quota 5,000,000% -> adjust to 1ms. */ + assert_se(USEC_PER_MSEC == cgroup_cpu_adjust_period(10, 50000000 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC)); + /* Period 10ms, quota 50,000% -> keep. */ + assert_se(10 * USEC_PER_MSEC == cgroup_cpu_adjust_period(10 * USEC_PER_MSEC, 500000 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC)); + /* Period 10ms, quota 1% -> adjust to 100ms. */ + assert_se(100 * USEC_PER_MSEC == cgroup_cpu_adjust_period(10 * USEC_PER_MSEC, 10 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC)); + /* Period 10ms, quota .001% -> adjust to 1s. */ + assert_se(1 * USEC_PER_SEC == cgroup_cpu_adjust_period(10 * USEC_PER_MSEC, 10, USEC_PER_MSEC, USEC_PER_SEC)); + /* Period 0ms, quota 200% -> adjust to 1ms. */ + assert_se(1 * USEC_PER_MSEC == cgroup_cpu_adjust_period(0, 2 * USEC_PER_SEC, USEC_PER_MSEC, USEC_PER_SEC)); + /* Period 0ms, quota 40% -> adjust to 2.5ms. */ + assert_se(2500 == cgroup_cpu_adjust_period(0, 400 * USEC_PER_MSEC, USEC_PER_MSEC, USEC_PER_SEC)); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-cgroup-mask.c b/src/test/test-cgroup-mask.c new file mode 100644 index 0000000..bfc8fac --- /dev/null +++ b/src/test/test-cgroup-mask.c @@ -0,0 +1,184 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdio.h> + +#include "cgroup.h" +#include "cgroup-util.h" +#include "macro.h" +#include "manager.h" +#include "rm-rf.h" +#include "string-util.h" +#include "tests.h" +#include "unit.h" + +#define ASSERT_CGROUP_MASK(got, expected) \ + log_cgroup_mask(got, expected); \ + assert_se(got == expected) + +#define ASSERT_CGROUP_MASK_JOINED(got, expected) ASSERT_CGROUP_MASK(got, CGROUP_MASK_EXTEND_JOINED(expected)) + +static void log_cgroup_mask(CGroupMask got, CGroupMask expected) { + _cleanup_free_ char *e_store = NULL, *g_store = NULL; + + assert_se(cg_mask_to_string(expected, &e_store) >= 0); + log_info("Expected mask: %s", e_store); + assert_se(cg_mask_to_string(got, &g_store) >= 0); + log_info("Got mask: %s", g_store); +} + +TEST_RET(cgroup_mask, .sd_booted = true) { + _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; + _cleanup_(manager_freep) Manager *m = NULL; + Unit *son, *daughter, *parent, *root, *grandchild, *parent_deep, *nomem_parent, *nomem_leaf; + int r; + CGroupMask cpu_accounting_mask = get_cpu_accounting_mask(); + + r = enter_cgroup_subroot(NULL); + if (r == -ENOMEDIUM) + return log_tests_skipped("cgroupfs not available"); + + /* Prepare the manager. */ + _cleanup_free_ char *unit_dir = NULL; + assert_se(get_testdata_dir("units", &unit_dir) >= 0); + assert_se(set_unit_path(unit_dir) >= 0); + assert_se(runtime_dir = setup_fake_runtime_dir()); + r = manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &m); + if (IN_SET(r, -EPERM, -EACCES)) { + log_error_errno(r, "manager_new: %m"); + return log_tests_skipped("cannot create manager"); + } + + assert_se(r >= 0); + + /* Turn off all kinds of default accounting, so that we can + * verify the masks resulting of our configuration and nothing + * else. */ + m->defaults.cpu_accounting = + m->defaults.memory_accounting = + m->defaults.blockio_accounting = + m->defaults.io_accounting = + m->defaults.tasks_accounting = false; + m->defaults.tasks_max = CGROUP_TASKS_MAX_UNSET; + + assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + + /* Load units and verify hierarchy. */ + assert_se(manager_load_startable_unit_or_warn(m, "parent.slice", NULL, &parent) >= 0); + assert_se(manager_load_startable_unit_or_warn(m, "son.service", NULL, &son) >= 0); + assert_se(manager_load_startable_unit_or_warn(m, "daughter.service", NULL, &daughter) >= 0); + assert_se(manager_load_startable_unit_or_warn(m, "grandchild.service", NULL, &grandchild) >= 0); + assert_se(manager_load_startable_unit_or_warn(m, "parent-deep.slice", NULL, &parent_deep) >= 0); + assert_se(manager_load_startable_unit_or_warn(m, "nomem.slice", NULL, &nomem_parent) >= 0); + assert_se(manager_load_startable_unit_or_warn(m, "nomemleaf.service", NULL, &nomem_leaf) >= 0); + assert_se(UNIT_GET_SLICE(son) == parent); + assert_se(UNIT_GET_SLICE(daughter) == parent); + assert_se(UNIT_GET_SLICE(parent_deep) == parent); + assert_se(UNIT_GET_SLICE(grandchild) == parent_deep); + assert_se(UNIT_GET_SLICE(nomem_leaf) == nomem_parent); + root = UNIT_GET_SLICE(parent); + assert_se(UNIT_GET_SLICE(nomem_parent) == root); + + /* Verify per-unit cgroups settings. */ + ASSERT_CGROUP_MASK_JOINED(unit_get_own_mask(son), CGROUP_MASK_CPU); + ASSERT_CGROUP_MASK_JOINED(unit_get_own_mask(daughter), cpu_accounting_mask); + ASSERT_CGROUP_MASK_JOINED(unit_get_own_mask(grandchild), 0); + ASSERT_CGROUP_MASK_JOINED(unit_get_own_mask(parent_deep), CGROUP_MASK_MEMORY); + ASSERT_CGROUP_MASK_JOINED(unit_get_own_mask(parent), (CGROUP_MASK_IO | CGROUP_MASK_BLKIO)); + ASSERT_CGROUP_MASK_JOINED(unit_get_own_mask(nomem_parent), 0); + ASSERT_CGROUP_MASK_JOINED(unit_get_own_mask(nomem_leaf), (CGROUP_MASK_IO | CGROUP_MASK_BLKIO | CGROUP_MASK_MEMORY)); + ASSERT_CGROUP_MASK_JOINED(unit_get_own_mask(root), 0); + + /* Verify aggregation of member masks */ + ASSERT_CGROUP_MASK_JOINED(unit_get_members_mask(son), 0); + ASSERT_CGROUP_MASK_JOINED(unit_get_members_mask(daughter), 0); + ASSERT_CGROUP_MASK_JOINED(unit_get_members_mask(grandchild), 0); + ASSERT_CGROUP_MASK_JOINED(unit_get_members_mask(parent_deep), 0); + ASSERT_CGROUP_MASK_JOINED(unit_get_members_mask(parent), (CGROUP_MASK_CPU | cpu_accounting_mask | CGROUP_MASK_MEMORY)); + ASSERT_CGROUP_MASK_JOINED(unit_get_members_mask(nomem_parent), (CGROUP_MASK_IO | CGROUP_MASK_BLKIO | CGROUP_MASK_MEMORY)); + ASSERT_CGROUP_MASK_JOINED(unit_get_members_mask(nomem_leaf), 0); + ASSERT_CGROUP_MASK_JOINED(unit_get_members_mask(root), (CGROUP_MASK_CPU | cpu_accounting_mask | CGROUP_MASK_IO | CGROUP_MASK_BLKIO | CGROUP_MASK_MEMORY)); + + /* Verify aggregation of sibling masks. */ + ASSERT_CGROUP_MASK_JOINED(unit_get_siblings_mask(son), (CGROUP_MASK_CPU | cpu_accounting_mask | CGROUP_MASK_MEMORY)); + ASSERT_CGROUP_MASK_JOINED(unit_get_siblings_mask(daughter), (CGROUP_MASK_CPU | cpu_accounting_mask | CGROUP_MASK_MEMORY)); + ASSERT_CGROUP_MASK_JOINED(unit_get_siblings_mask(grandchild), 0); + ASSERT_CGROUP_MASK_JOINED(unit_get_siblings_mask(parent_deep), (CGROUP_MASK_CPU | cpu_accounting_mask | CGROUP_MASK_MEMORY)); + ASSERT_CGROUP_MASK_JOINED(unit_get_siblings_mask(parent), (CGROUP_MASK_CPU | cpu_accounting_mask | CGROUP_MASK_IO | CGROUP_MASK_BLKIO | CGROUP_MASK_MEMORY)); + ASSERT_CGROUP_MASK_JOINED(unit_get_siblings_mask(nomem_parent), (CGROUP_MASK_CPU | CGROUP_MASK_CPUACCT | CGROUP_MASK_IO | CGROUP_MASK_BLKIO | CGROUP_MASK_MEMORY)); + ASSERT_CGROUP_MASK_JOINED(unit_get_siblings_mask(nomem_leaf), (CGROUP_MASK_IO | CGROUP_MASK_BLKIO | CGROUP_MASK_MEMORY)); + ASSERT_CGROUP_MASK_JOINED(unit_get_siblings_mask(root), (CGROUP_MASK_CPU | cpu_accounting_mask | CGROUP_MASK_IO | CGROUP_MASK_BLKIO | CGROUP_MASK_MEMORY)); + + /* Verify aggregation of target masks. */ + ASSERT_CGROUP_MASK(unit_get_target_mask(son), (CGROUP_MASK_EXTEND_JOINED(CGROUP_MASK_CPU | cpu_accounting_mask | CGROUP_MASK_MEMORY) & m->cgroup_supported)); + ASSERT_CGROUP_MASK(unit_get_target_mask(daughter), (CGROUP_MASK_EXTEND_JOINED(CGROUP_MASK_CPU | cpu_accounting_mask | CGROUP_MASK_MEMORY) & m->cgroup_supported)); + ASSERT_CGROUP_MASK(unit_get_target_mask(grandchild), 0); + ASSERT_CGROUP_MASK(unit_get_target_mask(parent_deep), (CGROUP_MASK_EXTEND_JOINED(CGROUP_MASK_CPU | cpu_accounting_mask | CGROUP_MASK_MEMORY) & m->cgroup_supported)); + ASSERT_CGROUP_MASK(unit_get_target_mask(parent), (CGROUP_MASK_EXTEND_JOINED(CGROUP_MASK_CPU | cpu_accounting_mask | CGROUP_MASK_IO | CGROUP_MASK_BLKIO | CGROUP_MASK_MEMORY) & m->cgroup_supported)); + ASSERT_CGROUP_MASK(unit_get_target_mask(nomem_parent), (CGROUP_MASK_EXTEND_JOINED(CGROUP_MASK_CPU | CGROUP_MASK_CPUACCT | CGROUP_MASK_IO | CGROUP_MASK_BLKIO) & m->cgroup_supported)); + ASSERT_CGROUP_MASK(unit_get_target_mask(nomem_leaf), (CGROUP_MASK_EXTEND_JOINED(CGROUP_MASK_IO | CGROUP_MASK_BLKIO) & m->cgroup_supported)); + ASSERT_CGROUP_MASK(unit_get_target_mask(root), (CGROUP_MASK_EXTEND_JOINED(CGROUP_MASK_CPU | cpu_accounting_mask | CGROUP_MASK_IO | CGROUP_MASK_BLKIO | CGROUP_MASK_MEMORY) & m->cgroup_supported)); + + /* Verify aggregation of enable masks. */ + ASSERT_CGROUP_MASK(unit_get_enable_mask(son), 0); + ASSERT_CGROUP_MASK(unit_get_enable_mask(daughter), 0); + ASSERT_CGROUP_MASK(unit_get_enable_mask(grandchild), 0); + ASSERT_CGROUP_MASK(unit_get_enable_mask(parent_deep), 0); + ASSERT_CGROUP_MASK(unit_get_enable_mask(parent), (CGROUP_MASK_EXTEND_JOINED(CGROUP_MASK_CPU | cpu_accounting_mask | CGROUP_MASK_MEMORY) & m->cgroup_supported)); + ASSERT_CGROUP_MASK(unit_get_enable_mask(nomem_parent), (CGROUP_MASK_EXTEND_JOINED(CGROUP_MASK_IO | CGROUP_MASK_BLKIO) & m->cgroup_supported)); + ASSERT_CGROUP_MASK(unit_get_enable_mask(nomem_leaf), 0); + ASSERT_CGROUP_MASK(unit_get_enable_mask(root), (CGROUP_MASK_EXTEND_JOINED(CGROUP_MASK_CPU | cpu_accounting_mask | CGROUP_MASK_IO | CGROUP_MASK_BLKIO | CGROUP_MASK_MEMORY) & m->cgroup_supported)); + + return 0; +} + +static void test_cg_mask_to_string_one(CGroupMask mask, const char *t) { + _cleanup_free_ char *b = NULL; + + assert_se(cg_mask_to_string(mask, &b) >= 0); + assert_se(streq_ptr(b, t)); +} + +TEST(cg_mask_to_string) { + test_cg_mask_to_string_one(0, NULL); + test_cg_mask_to_string_one(_CGROUP_MASK_ALL, "cpu cpuacct cpuset io blkio memory devices pids bpf-firewall bpf-devices bpf-foreign bpf-socket-bind bpf-restrict-network-interfaces"); + test_cg_mask_to_string_one(CGROUP_MASK_CPU, "cpu"); + test_cg_mask_to_string_one(CGROUP_MASK_CPUACCT, "cpuacct"); + test_cg_mask_to_string_one(CGROUP_MASK_CPUSET, "cpuset"); + test_cg_mask_to_string_one(CGROUP_MASK_IO, "io"); + test_cg_mask_to_string_one(CGROUP_MASK_BLKIO, "blkio"); + test_cg_mask_to_string_one(CGROUP_MASK_MEMORY, "memory"); + test_cg_mask_to_string_one(CGROUP_MASK_DEVICES, "devices"); + test_cg_mask_to_string_one(CGROUP_MASK_PIDS, "pids"); + test_cg_mask_to_string_one(CGROUP_MASK_CPU|CGROUP_MASK_CPUACCT, "cpu cpuacct"); + test_cg_mask_to_string_one(CGROUP_MASK_CPU|CGROUP_MASK_PIDS, "cpu pids"); + test_cg_mask_to_string_one(CGROUP_MASK_CPUACCT|CGROUP_MASK_PIDS, "cpuacct pids"); + test_cg_mask_to_string_one(CGROUP_MASK_DEVICES|CGROUP_MASK_PIDS, "devices pids"); + test_cg_mask_to_string_one(CGROUP_MASK_IO|CGROUP_MASK_BLKIO, "io blkio"); +} + +static void cgroup_device_permissions_test_normalize(const char *a, const char *b) { + assert_se(streq_ptr(cgroup_device_permissions_to_string(cgroup_device_permissions_from_string(a)), b)); +} + +TEST(cgroup_device_permissions) { + for (CGroupDevicePermissions p = 0; p < _CGROUP_DEVICE_PERMISSIONS_MAX; p++) { + const char *s; + + assert_se(s = cgroup_device_permissions_to_string(p)); + assert_se(cgroup_device_permissions_from_string(s) == p); + } + + cgroup_device_permissions_test_normalize("", ""); + cgroup_device_permissions_test_normalize("rw", "rw"); + cgroup_device_permissions_test_normalize("wr", "rw"); + cgroup_device_permissions_test_normalize("wwrr", "rw"); + cgroup_device_permissions_test_normalize("mmmmmmmmmmmmmm", "m"); + cgroup_device_permissions_test_normalize("mmmmrrrrmmmwwmwmwmwmwmrmrmr", "rwm"); + + assert_se(cgroup_device_permissions_from_string(NULL) == -EINVAL); + assert_se(cgroup_device_permissions_from_string("rwq") == -EINVAL); + assert_se(cgroup_device_permissions_from_string("RW") == -EINVAL); + assert_se(cgroup_device_permissions_from_string("") == 0); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-cgroup-setup.c b/src/test/test-cgroup-setup.c new file mode 100644 index 0000000..e669e9b --- /dev/null +++ b/src/test/test-cgroup-setup.c @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <unistd.h> + +#include "alloc-util.h" +#include "cgroup-setup.h" +#include "errno-util.h" +#include "log.h" +#include "proc-cmdline.h" +#include "string-util.h" +#include "tests.h" + +static void test_is_wanted_print_one(bool header) { + _cleanup_free_ char *cmdline = NULL; + + log_info("-- %s --", __func__); + assert_se(proc_cmdline(&cmdline) >= 0); + log_info("cmdline: %s", cmdline); + if (header) { + log_info("default-hierarchy=" DEFAULT_HIERARCHY_NAME); + (void) system("findmnt -n /sys/fs/cgroup"); + } + + log_info("is_unified_wanted() → %s", yes_no(cg_is_unified_wanted())); + log_info("is_hybrid_wanted() → %s", yes_no(cg_is_hybrid_wanted())); + log_info("is_legacy_wanted() → %s", yes_no(cg_is_legacy_wanted())); + log_info(" "); +} + +TEST(is_wanted_print) { + test_is_wanted_print_one(true); + test_is_wanted_print_one(false); /* run twice to test caching */ +} + +TEST(is_wanted) { + assert_se(setenv("SYSTEMD_PROC_CMDLINE", + "systemd.unified_cgroup_hierarchy", 1) >= 0); + test_is_wanted_print_one(false); + + assert_se(setenv("SYSTEMD_PROC_CMDLINE", + "systemd.unified_cgroup_hierarchy=0", 1) >= 0); + test_is_wanted_print_one(false); + + assert_se(setenv("SYSTEMD_PROC_CMDLINE", + "systemd.unified_cgroup_hierarchy=0 " + "systemd.legacy_systemd_cgroup_controller", 1) >= 0); + test_is_wanted_print_one(false); + + assert_se(setenv("SYSTEMD_PROC_CMDLINE", + "systemd.unified_cgroup_hierarchy=0 " + "systemd.legacy_systemd_cgroup_controller=0", 1) >= 0); + test_is_wanted_print_one(false); + + /* cgroup_no_v1=all implies unified cgroup hierarchy, unless otherwise + * explicitly specified. */ + assert_se(setenv("SYSTEMD_PROC_CMDLINE", + "cgroup_no_v1=all", 1) >= 0); + test_is_wanted_print_one(false); + + assert_se(setenv("SYSTEMD_PROC_CMDLINE", + "cgroup_no_v1=all " + "systemd.unified_cgroup_hierarchy=0", 1) >= 0); + test_is_wanted_print_one(false); +} + +static int intro(void) { + if (access("/proc/cmdline", R_OK) < 0 && ERRNO_IS_PRIVILEGE(errno)) + return log_tests_skipped("can't read /proc/cmdline"); + + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/src/test/test-cgroup-unit-default.c b/src/test/test-cgroup-unit-default.c new file mode 100644 index 0000000..62618ce --- /dev/null +++ b/src/test/test-cgroup-unit-default.c @@ -0,0 +1,138 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdio.h> + +#include "cgroup.h" +#include "manager.h" +#include "rm-rf.h" +#include "tests.h" +#include "unit.h" + +TEST_RET(default_memory_low, .sd_booted = true) { + _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; + _cleanup_(manager_freep) Manager *m = NULL; + Unit *root, *dml, + *dml_passthrough, *dml_passthrough_empty, *dml_passthrough_set_dml, *dml_passthrough_set_ml, + *dml_override, *dml_override_empty, + *dml_discard, *dml_discard_empty, *dml_discard_set_ml; + uint64_t dml_tree_default; + int r; + + r = enter_cgroup_subroot(NULL); + if (r == -ENOMEDIUM) + return log_tests_skipped("cgroupfs not available"); + + _cleanup_free_ char *unit_dir = NULL; + assert_se(get_testdata_dir("units", &unit_dir) >= 0); + assert_se(set_unit_path(unit_dir) >= 0); + assert_se(runtime_dir = setup_fake_runtime_dir()); + r = manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &m); + if (IN_SET(r, -EPERM, -EACCES)) { + log_error_errno(r, "manager_new: %m"); + return log_tests_skipped("cannot create manager"); + } + + assert_se(r >= 0); + assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + + /* dml.slice has DefaultMemoryLow=50. Beyond that, individual subhierarchies look like this: + * + * 1. dml-passthrough.slice sets MemoryLow=100. This should not affect its children, as only + * DefaultMemoryLow is propagated, not MemoryLow. As such, all leaf services should end up with + * memory.low as 50, inherited from dml.slice, *except* for dml-passthrough-set-ml.service, which + * should have the value of 0, as it has MemoryLow explicitly set. + * + * ┌───────────┐ + * │ dml.slice │ + * └─────┬─────┘ + * MemoryLow=100 + * ┌───────────┴───────────┐ + * │ dml-passthrough.slice │ + * └───────────┬───────────┘ + * ┌───────────────────────────────────┼───────────────────────────────────┐ + * no new settings DefaultMemoryLow=15 MemoryLow=0 + * ┌───────────────┴───────────────┐ ┌────────────────┴────────────────┐ ┌───────────────┴────────────────┐ + * │ dml-passthrough-empty.service │ │ dml-passthrough-set-dml.service │ │ dml-passthrough-set-ml.service │ + * └───────────────────────────────┘ └─────────────────────────────────┘ └────────────────────────────────┘ + * + * 2. dml-override.slice sets DefaultMemoryLow=10. As such, dml-override-empty.service should also + * end up with a memory.low of 10. dml-override.slice should still have a memory.low of 50. + * + * ┌───────────┐ + * │ dml.slice │ + * └─────┬─────┘ + * DefaultMemoryLow=10 + * ┌─────────┴──────────┐ + * │ dml-override.slice │ + * └─────────┬──────────┘ + * no new settings + * ┌─────────────┴──────────────┐ + * │ dml-override-empty.service │ + * └────────────────────────────┘ + * + * 3. dml-discard.slice sets DefaultMemoryLow= with no rvalue. As such, + * dml-discard-empty.service should end up with a value of 0. + * dml-discard-set-ml.service sets MemoryLow=15, and as such should have that override the + * reset DefaultMemoryLow value. dml-discard.slice should still have an eventual memory.low of 50. + * + * ┌───────────┐ + * │ dml.slice │ + * └─────┬─────┘ + * DefaultMemoryLow= + * ┌─────────┴─────────┐ + * │ dml-discard.slice │ + * └─────────┬─────────┘ + * ┌──────────────┴───────────────┐ + * no new settings MemoryLow=15 + * ┌─────────────┴─────────────┐ ┌─────────────┴──────────────┐ + * │ dml-discard-empty.service │ │ dml-discard-set-ml.service │ + * └───────────────────────────┘ └────────────────────────────┘ + */ + assert_se(manager_load_startable_unit_or_warn(m, "dml.slice", NULL, &dml) >= 0); + + assert_se(manager_load_startable_unit_or_warn(m, "dml-passthrough.slice", NULL, &dml_passthrough) >= 0); + assert_se(UNIT_GET_SLICE(dml_passthrough) == dml); + assert_se(manager_load_startable_unit_or_warn(m, "dml-passthrough-empty.service", NULL, &dml_passthrough_empty) >= 0); + assert_se(UNIT_GET_SLICE(dml_passthrough_empty) == dml_passthrough); + assert_se(manager_load_startable_unit_or_warn(m, "dml-passthrough-set-dml.service", NULL, &dml_passthrough_set_dml) >= 0); + assert_se(UNIT_GET_SLICE(dml_passthrough_set_dml) == dml_passthrough); + assert_se(manager_load_startable_unit_or_warn(m, "dml-passthrough-set-ml.service", NULL, &dml_passthrough_set_ml) >= 0); + assert_se(UNIT_GET_SLICE(dml_passthrough_set_ml) == dml_passthrough); + + assert_se(manager_load_startable_unit_or_warn(m, "dml-override.slice", NULL, &dml_override) >= 0); + assert_se(UNIT_GET_SLICE(dml_override) == dml); + assert_se(manager_load_startable_unit_or_warn(m, "dml-override-empty.service", NULL, &dml_override_empty) >= 0); + assert_se(UNIT_GET_SLICE(dml_override_empty) == dml_override); + + assert_se(manager_load_startable_unit_or_warn(m, "dml-discard.slice", NULL, &dml_discard) >= 0); + assert_se(UNIT_GET_SLICE(dml_discard) == dml); + assert_se(manager_load_startable_unit_or_warn(m, "dml-discard-empty.service", NULL, &dml_discard_empty) >= 0); + assert_se(UNIT_GET_SLICE(dml_discard_empty) == dml_discard); + assert_se(manager_load_startable_unit_or_warn(m, "dml-discard-set-ml.service", NULL, &dml_discard_set_ml) >= 0); + assert_se(UNIT_GET_SLICE(dml_discard_set_ml) == dml_discard); + + assert_se(root = UNIT_GET_SLICE(dml)); + assert_se(!UNIT_GET_SLICE(root)); + + assert_se(unit_get_ancestor_memory_low(root) == CGROUP_LIMIT_MIN); + + assert_se(unit_get_ancestor_memory_low(dml) == CGROUP_LIMIT_MIN); + dml_tree_default = unit_get_cgroup_context(dml)->default_memory_low; + assert_se(dml_tree_default == 50); + + assert_se(unit_get_ancestor_memory_low(dml_passthrough) == 100); + assert_se(unit_get_ancestor_memory_low(dml_passthrough_empty) == dml_tree_default); + assert_se(unit_get_ancestor_memory_low(dml_passthrough_set_dml) == 50); + assert_se(unit_get_ancestor_memory_low(dml_passthrough_set_ml) == 0); + + assert_se(unit_get_ancestor_memory_low(dml_override) == dml_tree_default); + assert_se(unit_get_ancestor_memory_low(dml_override_empty) == 10); + + assert_se(unit_get_ancestor_memory_low(dml_discard) == dml_tree_default); + assert_se(unit_get_ancestor_memory_low(dml_discard_empty) == CGROUP_LIMIT_MIN); + assert_se(unit_get_ancestor_memory_low(dml_discard_set_ml) == 15); + + return 0; +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-cgroup-util.c b/src/test/test-cgroup-util.c new file mode 100644 index 0000000..51f52d9 --- /dev/null +++ b/src/test/test-cgroup-util.c @@ -0,0 +1,466 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "cgroup-util.h" +#include "dirent-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "format-util.h" +#include "parse-util.h" +#include "proc-cmdline.h" +#include "process-util.h" +#include "special.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" +#include "user-util.h" + +static void check_p_d_u(const char *path, int code, const char *result) { + _cleanup_free_ char *unit = NULL; + int r; + + r = cg_path_decode_unit(path, &unit); + printf("%s: %s → %s %d expected %s %d\n", __func__, path, unit, r, strnull(result), code); + assert_se(r == code); + assert_se(streq_ptr(unit, result)); +} + +TEST(path_decode_unit) { + check_p_d_u("getty@tty2.service", 0, "getty@tty2.service"); + check_p_d_u("getty@tty2.service/", 0, "getty@tty2.service"); + check_p_d_u("getty@tty2.service/xxx", 0, "getty@tty2.service"); + check_p_d_u("getty@.service/", -ENXIO, NULL); + check_p_d_u("getty@.service", -ENXIO, NULL); + check_p_d_u("getty.service", 0, "getty.service"); + check_p_d_u("getty", -ENXIO, NULL); + check_p_d_u("getty/waldo", -ENXIO, NULL); + check_p_d_u("_cpu.service", 0, "cpu.service"); +} + +static void check_p_g_u(const char *path, int code, const char *result) { + _cleanup_free_ char *unit = NULL; + int r; + + r = cg_path_get_unit(path, &unit); + printf("%s: %s → %s %d expected %s %d\n", __func__, path, unit, r, strnull(result), code); + assert_se(r == code); + assert_se(streq_ptr(unit, result)); +} + +TEST(path_get_unit) { + check_p_g_u("/system.slice/foobar.service/sdfdsaf", 0, "foobar.service"); + check_p_g_u("/system.slice/getty@tty5.service", 0, "getty@tty5.service"); + check_p_g_u("/system.slice/getty@tty5.service/aaa/bbb", 0, "getty@tty5.service"); + check_p_g_u("/system.slice/getty@tty5.service/", 0, "getty@tty5.service"); + check_p_g_u("/system.slice/getty@tty6.service/tty5", 0, "getty@tty6.service"); + check_p_g_u("sadfdsafsda", -ENXIO, NULL); + check_p_g_u("/system.slice/getty####@tty6.service/xxx", -ENXIO, NULL); + check_p_g_u("/system.slice/system-waldo.slice/foobar.service/sdfdsaf", 0, "foobar.service"); + check_p_g_u("/system.slice/system-waldo.slice/_cpu.service/sdfdsaf", 0, "cpu.service"); + check_p_g_u("/user.slice/user-1000.slice/user@1000.service/server.service", 0, "user@1000.service"); + check_p_g_u("/user.slice/user-1000.slice/user@.service/server.service", -ENXIO, NULL); +} + +static void check_p_g_u_p(const char *path, int code, const char *result) { + _cleanup_free_ char *unit_path = NULL; + int r; + + r = cg_path_get_unit_path(path, &unit_path); + printf("%s: %s → %s %d expected %s %d\n", __func__, path, unit_path, r, strnull(result), code); + assert_se(r == code); + assert_se(streq_ptr(unit_path, result)); +} + +TEST(path_get_unit_path) { + check_p_g_u_p("/system.slice/foobar.service/sdfdsaf", 0, "/system.slice/foobar.service"); + check_p_g_u_p("/system.slice/getty@tty5.service", 0, "/system.slice/getty@tty5.service"); + check_p_g_u_p("/system.slice/getty@tty5.service/aaa/bbb", 0, "/system.slice/getty@tty5.service"); + check_p_g_u_p("/system.slice/getty@tty5.service/", 0, "/system.slice/getty@tty5.service"); + check_p_g_u_p("/system.slice/getty@tty6.service/tty5", 0, "/system.slice/getty@tty6.service"); + check_p_g_u_p("sadfdsafsda", -ENXIO, NULL); + check_p_g_u_p("/system.slice/getty####@tty6.service/xxx", -ENXIO, NULL); + check_p_g_u_p("/system.slice/system-waldo.slice/foobar.service/sdfdsaf", 0, "/system.slice/system-waldo.slice/foobar.service"); + check_p_g_u_p("/system.slice/system-waldo.slice/_cpu.service/sdfdsaf", 0, "/system.slice/system-waldo.slice/_cpu.service"); + check_p_g_u_p("/system.slice/system-waldo.slice/_cpu.service", 0, "/system.slice/system-waldo.slice/_cpu.service"); + check_p_g_u_p("/user.slice/user-1000.slice/user@1000.service/server.service", 0, "/user.slice/user-1000.slice/user@1000.service"); + check_p_g_u_p("/user.slice/user-1000.slice/user@.service/server.service", -ENXIO, NULL); + check_p_g_u_p("/user.slice/_user-1000.slice/user@1000.service/foobar.slice/foobar@pie.service", 0, "/user.slice/_user-1000.slice/user@1000.service"); + check_p_g_u_p("/_session-2.scope/_foobar@pie.service/pa/po", 0, "/_session-2.scope"); +} + +static void check_p_g_u_u(const char *path, int code, const char *result) { + _cleanup_free_ char *unit = NULL; + int r; + + r = cg_path_get_user_unit(path, &unit); + printf("%s: %s → %s %d expected %s %d\n", __func__, path, unit, r, strnull(result), code); + assert_se(r == code); + assert_se(streq_ptr(unit, result)); +} + +TEST(path_get_user_unit) { + check_p_g_u_u("/user.slice/user-1000.slice/session-2.scope/foobar.service", 0, "foobar.service"); + check_p_g_u_u("/user.slice/user-1000.slice/session-2.scope/waldo.slice/foobar.service", 0, "foobar.service"); + check_p_g_u_u("/user.slice/user-1002.slice/session-2.scope/foobar.service/waldo", 0, "foobar.service"); + check_p_g_u_u("/user.slice/user-1000.slice/session-2.scope/foobar.service/waldo/uuuux", 0, "foobar.service"); + check_p_g_u_u("/user.slice/user-1000.slice/session-2.scope/waldo/waldo/uuuux", -ENXIO, NULL); + check_p_g_u_u("/user.slice/user-1000.slice/session-2.scope/foobar@pie.service/pa/po", 0, "foobar@pie.service"); + check_p_g_u_u("/session-2.scope/foobar@pie.service/pa/po", 0, "foobar@pie.service"); + check_p_g_u_u("/xyz.slice/xyz-waldo.slice/session-77.scope/foobar@pie.service/pa/po", 0, "foobar@pie.service"); + check_p_g_u_u("/meh.service", -ENXIO, NULL); + check_p_g_u_u("/session-3.scope/_cpu.service", 0, "cpu.service"); + check_p_g_u_u("/user.slice/user-1000.slice/user@1000.service/server.service", 0, "server.service"); + check_p_g_u_u("/user.slice/user-1000.slice/user@1000.service/foobar.slice/foobar@pie.service", 0, "foobar@pie.service"); + check_p_g_u_u("/user.slice/user-1000.slice/user@.service/server.service", -ENXIO, NULL); +} + +static void check_p_g_s(const char *path, int code, const char *result) { + _cleanup_free_ char *s = NULL; + + assert_se(cg_path_get_session(path, &s) == code); + assert_se(streq_ptr(s, result)); +} + +TEST(path_get_session) { + check_p_g_s("/user.slice/user-1000.slice/session-2.scope/foobar.service", 0, "2"); + check_p_g_s("/session-3.scope", 0, "3"); + check_p_g_s("/session-.scope", -ENXIO, NULL); + check_p_g_s("", -ENXIO, NULL); +} + +static void check_p_g_o_u(const char *path, int code, uid_t result) { + uid_t uid = 0; + + assert_se(cg_path_get_owner_uid(path, &uid) == code); + assert_se(uid == result); +} + +TEST(path_get_owner_uid) { + check_p_g_o_u("/user.slice/user-1000.slice/session-2.scope/foobar.service", 0, 1000); + check_p_g_o_u("/user.slice/user-1006.slice", 0, 1006); + check_p_g_o_u("", -ENXIO, 0); +} + +static void check_p_g_slice(const char *path, int code, const char *result) { + _cleanup_free_ char *s = NULL; + + assert_se(cg_path_get_slice(path, &s) == code); + assert_se(streq_ptr(s, result)); +} + +TEST(path_get_slice) { + check_p_g_slice("/user.slice", 0, "user.slice"); + check_p_g_slice("/foobar", 0, SPECIAL_ROOT_SLICE); + check_p_g_slice("/user.slice/user-waldo.slice", 0, "user-waldo.slice"); + check_p_g_slice("", 0, SPECIAL_ROOT_SLICE); + check_p_g_slice("foobar", 0, SPECIAL_ROOT_SLICE); + check_p_g_slice("foobar.slice", 0, "foobar.slice"); + check_p_g_slice("foo.slice/foo-bar.slice/waldo.service", 0, "foo-bar.slice"); +} + +static void check_p_g_u_slice(const char *path, int code, const char *result) { + _cleanup_free_ char *s = NULL; + + assert_se(cg_path_get_user_slice(path, &s) == code); + assert_se(streq_ptr(s, result)); +} + +TEST(path_get_user_slice) { + check_p_g_u_slice("/user.slice", -ENXIO, NULL); + check_p_g_u_slice("/foobar", -ENXIO, NULL); + check_p_g_u_slice("/user.slice/user-waldo.slice", -ENXIO, NULL); + check_p_g_u_slice("", -ENXIO, NULL); + check_p_g_u_slice("foobar", -ENXIO, NULL); + check_p_g_u_slice("foobar.slice", -ENXIO, NULL); + check_p_g_u_slice("foo.slice/foo-bar.slice/waldo.service", -ENXIO, NULL); + + check_p_g_u_slice("foo.slice/foo-bar.slice/user@1000.service", 0, SPECIAL_ROOT_SLICE); + check_p_g_u_slice("foo.slice/foo-bar.slice/user@1000.service/", 0, SPECIAL_ROOT_SLICE); + check_p_g_u_slice("foo.slice/foo-bar.slice/user@1000.service///", 0, SPECIAL_ROOT_SLICE); + check_p_g_u_slice("foo.slice/foo-bar.slice/user@1000.service/waldo.service", 0, SPECIAL_ROOT_SLICE); + check_p_g_u_slice("foo.slice/foo-bar.slice/user@1000.service/piep.slice/foo.service", 0, "piep.slice"); + check_p_g_u_slice("/foo.slice//foo-bar.slice/user@1000.service/piep.slice//piep-pap.slice//foo.service", 0, "piep-pap.slice"); +} + +TEST(get_paths, .sd_booted = true) { + _cleanup_free_ char *a = NULL; + + assert_se(cg_get_root_path(&a) >= 0); + log_info("Root = %s", a); +} + +TEST(proc) { + _cleanup_closedir_ DIR *d = NULL; + int r; + + assert_se(proc_dir_open(&d) >= 0); + + for (;;) { + _cleanup_free_ char *path = NULL, *path_shifted = NULL, *session = NULL, *unit = NULL, *user_unit = NULL, *machine = NULL, *slice = NULL; + _cleanup_(pidref_done) PidRef pid = PIDREF_NULL; + uid_t uid = UID_INVALID; + + r = proc_dir_read_pidref(d, &pid); + assert_se(r >= 0); + + if (r == 0) + break; + + if (pidref_is_kernel_thread(&pid) != 0) + continue; + + cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid.pid, &path); + cg_pid_get_path_shifted(pid.pid, NULL, &path_shifted); + cg_pid_get_owner_uid(pid.pid, &uid); + cg_pid_get_session(pid.pid, &session); + cg_pid_get_unit(pid.pid, &unit); + cg_pid_get_user_unit(pid.pid, &user_unit); + cg_pid_get_machine_name(pid.pid, &machine); + cg_pid_get_slice(pid.pid, &slice); + + printf(PID_FMT"\t%s\t%s\t"UID_FMT"\t%s\t%s\t%s\t%s\t%s\n", + pid.pid, + path, + path_shifted, + uid, + session, + unit, + user_unit, + machine, + slice); + } +} + +static void test_escape_one(const char *s, const char *expected) { + _cleanup_free_ char *b = NULL; + + assert_se(s); + assert_se(expected); + + assert_se(cg_escape(s, &b) >= 0); + assert_se(streq(b, expected)); + + assert_se(streq(cg_unescape(b), s)); + + assert_se(filename_is_valid(b)); + assert_se(!cg_needs_escape(s) || b[0] == '_'); +} + +TEST(escape, .sd_booted = true) { + test_escape_one("foobar", "foobar"); + test_escape_one(".foobar", "_.foobar"); + test_escape_one("foobar.service", "foobar.service"); + test_escape_one("cgroup.service", "_cgroup.service"); + test_escape_one("tasks", "_tasks"); + if (access("/sys/fs/cgroup/cpu", F_OK) == 0) + test_escape_one("cpu.service", "_cpu.service"); + test_escape_one("_foobar", "__foobar"); + test_escape_one("", "_"); + test_escape_one("_", "__"); + test_escape_one(".", "_."); +} + +TEST(controller_is_valid) { + assert_se(cg_controller_is_valid("foobar")); + assert_se(cg_controller_is_valid("foo_bar")); + assert_se(cg_controller_is_valid("name=foo")); + assert_se(!cg_controller_is_valid("")); + assert_se(!cg_controller_is_valid("name=")); + assert_se(!cg_controller_is_valid("=")); + assert_se(!cg_controller_is_valid("cpu,cpuacct")); + assert_se(!cg_controller_is_valid("_")); + assert_se(!cg_controller_is_valid("_foobar")); + assert_se(!cg_controller_is_valid("tatü")); +} + +static void test_slice_to_path_one(const char *unit, const char *path, int error) { + _cleanup_free_ char *ret = NULL; + int r; + + log_info("unit: %s", unit); + + r = cg_slice_to_path(unit, &ret); + log_info("actual: %s / %d", strnull(ret), r); + log_info("expect: %s / %d", strnull(path), error); + assert_se(r == error); + assert_se(streq_ptr(ret, path)); +} + +TEST(slice_to_path) { + test_slice_to_path_one("foobar.slice", "foobar.slice", 0); + test_slice_to_path_one("foobar-waldo.slice", "foobar.slice/foobar-waldo.slice", 0); + test_slice_to_path_one("foobar-waldo.service", NULL, -EINVAL); + test_slice_to_path_one(SPECIAL_ROOT_SLICE, "", 0); + test_slice_to_path_one("--.slice", NULL, -EINVAL); + test_slice_to_path_one("-", NULL, -EINVAL); + test_slice_to_path_one("-foo-.slice", NULL, -EINVAL); + test_slice_to_path_one("-foo.slice", NULL, -EINVAL); + test_slice_to_path_one("foo-.slice", NULL, -EINVAL); + test_slice_to_path_one("foo--bar.slice", NULL, -EINVAL); + test_slice_to_path_one("foo.slice/foo--bar.slice", NULL, -EINVAL); + test_slice_to_path_one("a-b.slice", "a.slice/a-b.slice", 0); + test_slice_to_path_one("a-b-c-d-e.slice", "a.slice/a-b.slice/a-b-c.slice/a-b-c-d.slice/a-b-c-d-e.slice", 0); + + test_slice_to_path_one("foobar@.slice", NULL, -EINVAL); + test_slice_to_path_one("foobar@waldo.slice", NULL, -EINVAL); + test_slice_to_path_one("foobar@waldo.service", NULL, -EINVAL); + test_slice_to_path_one("-foo@-.slice", NULL, -EINVAL); + test_slice_to_path_one("-foo@.slice", NULL, -EINVAL); + test_slice_to_path_one("foo@-.slice", NULL, -EINVAL); + test_slice_to_path_one("foo@@bar.slice", NULL, -EINVAL); + test_slice_to_path_one("foo.slice/foo@@bar.slice", NULL, -EINVAL); +} + +static void test_shift_path_one(const char *raw, const char *root, const char *shifted) { + const char *s = NULL; + + assert_se(cg_shift_path(raw, root, &s) >= 0); + assert_se(streq(s, shifted)); +} + +TEST(shift_path) { + test_shift_path_one("/foobar/waldo", "/", "/foobar/waldo"); + test_shift_path_one("/foobar/waldo", "", "/foobar/waldo"); + test_shift_path_one("/foobar/waldo", "/foobar", "/waldo"); + test_shift_path_one("/foobar/waldo", "/hogehoge", "/foobar/waldo"); +} + +TEST(mask_supported, .sd_booted = true) { + CGroupMask m; + CGroupController c; + + assert_se(cg_mask_supported(&m) >= 0); + + for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) + printf("'%s' is supported: %s\n", cgroup_controller_to_string(c), yes_no(m & CGROUP_CONTROLLER_TO_MASK(c))); +} + +TEST(is_cgroup_fs, .sd_booted = true) { + struct statfs sfs; + assert_se(statfs("/sys/fs/cgroup", &sfs) == 0); + if (is_temporary_fs(&sfs)) + assert_se(statfs("/sys/fs/cgroup/systemd", &sfs) == 0); + assert_se(is_cgroup_fs(&sfs)); +} + +TEST(fd_is_cgroup_fs, .sd_booted = true) { + int fd; + + fd = open("/sys/fs/cgroup", O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + assert_se(fd >= 0); + if (fd_is_temporary_fs(fd)) { + fd = safe_close(fd); + fd = open("/sys/fs/cgroup/systemd", O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + assert_se(fd >= 0); + } + assert_se(fd_is_cgroup_fs(fd)); + fd = safe_close(fd); +} + +TEST(cg_tests) { + int all, hybrid, systemd, r; + + r = cg_unified(); + if (r == -ENOMEDIUM) { + log_tests_skipped("cgroup not mounted"); + return; + } + assert_se(r >= 0); + + all = cg_all_unified(); + assert_se(IN_SET(all, 0, 1)); + + hybrid = cg_hybrid_unified(); + assert_se(IN_SET(hybrid, 0, 1)); + + systemd = cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER); + assert_se(IN_SET(systemd, 0, 1)); + + if (all) { + assert_se(systemd); + assert_se(!hybrid); + + } else if (hybrid) { + assert_se(systemd); + assert_se(!all); + + } else + assert_se(!systemd); +} + +TEST(cg_get_keyed_attribute) { + _cleanup_free_ char *val = NULL; + char *vals3[3] = {}, *vals3a[3] = {}; + int i, r; + + r = cg_get_keyed_attribute("cpu", "/init.scope", "no_such_file", STRV_MAKE("no_such_attr"), &val); + if (r == -ENOMEDIUM || ERRNO_IS_PRIVILEGE(r)) { + log_info_errno(r, "Skipping most of %s, /sys/fs/cgroup not accessible: %m", __func__); + return; + } + + assert_se(r == -ENOENT); + assert_se(val == NULL); + + if (access("/sys/fs/cgroup/init.scope/cpu.stat", R_OK) < 0) { + log_info_errno(errno, "Skipping most of %s, /init.scope/cpu.stat not accessible: %m", __func__); + return; + } + + assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", STRV_MAKE("no_such_attr"), &val) == -ENXIO); + assert_se(cg_get_keyed_attribute_graceful("cpu", "/init.scope", "cpu.stat", STRV_MAKE("no_such_attr"), &val) == 0); + assert_se(val == NULL); + + assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", STRV_MAKE("usage_usec"), &val) == 0); + val = mfree(val); + + assert_se(cg_get_keyed_attribute_graceful("cpu", "/init.scope", "cpu.stat", STRV_MAKE("usage_usec"), &val) == 1); + log_info("cpu /init.scope cpu.stat [usage_usec] → \"%s\"", val); + + assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", STRV_MAKE("usage_usec", "no_such_attr"), vals3) == -ENXIO); + assert_se(cg_get_keyed_attribute_graceful("cpu", "/init.scope", "cpu.stat", STRV_MAKE("usage_usec", "no_such_attr"), vals3) == 1); + assert_se(vals3[0] && !vals3[1]); + free(vals3[0]); + + assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", STRV_MAKE("usage_usec", "usage_usec"), vals3) == -ENXIO); + assert_se(cg_get_keyed_attribute_graceful("cpu", "/init.scope", "cpu.stat", STRV_MAKE("usage_usec", "usage_usec"), vals3) == 1); + assert_se(vals3[0] && !vals3[1]); + free(vals3[0]); + + assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", + STRV_MAKE("usage_usec", "user_usec", "system_usec"), vals3) == 0); + for (i = 0; i < 3; i++) + free(vals3[i]); + + assert_se(cg_get_keyed_attribute_graceful("cpu", "/init.scope", "cpu.stat", + STRV_MAKE("usage_usec", "user_usec", "system_usec"), vals3) == 3); + log_info("cpu /init.scope cpu.stat [usage_usec user_usec system_usec] → \"%s\", \"%s\", \"%s\"", + vals3[0], vals3[1], vals3[2]); + + assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", + STRV_MAKE("system_usec", "user_usec", "usage_usec"), vals3a) == 0); + for (i = 0; i < 3; i++) + free(vals3a[i]); + + assert_se(cg_get_keyed_attribute_graceful("cpu", "/init.scope", "cpu.stat", + STRV_MAKE("system_usec", "user_usec", "usage_usec"), vals3a) == 3); + log_info("cpu /init.scope cpu.stat [system_usec user_usec usage_usec] → \"%s\", \"%s\", \"%s\"", + vals3a[0], vals3a[1], vals3a[2]); + + for (i = 0; i < 3; i++) { + free(vals3[i]); + free(vals3a[i]); + } +} + +TEST(bfq_weight_conversion) { + assert_se(BFQ_WEIGHT(1) == 1); + assert_se(BFQ_WEIGHT(50) == 50); + assert_se(BFQ_WEIGHT(100) == 100); + assert_se(BFQ_WEIGHT(500) == 136); + assert_se(BFQ_WEIGHT(5000) == 545); + assert_se(BFQ_WEIGHT(10000) == 1000); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-cgroup.c b/src/test/test-cgroup.c new file mode 100644 index 0000000..0fbd635 --- /dev/null +++ b/src/test/test-cgroup.c @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <unistd.h> + +#include "cgroup-setup.h" +#include "cgroup-util.h" +#include "errno-util.h" +#include "path-util.h" +#include "process-util.h" +#include "string-util.h" +#include "tests.h" + +TEST(cg_split_spec) { + char *c, *p; + + assert_se(cg_split_spec("foobar:/", &c, &p) == 0); + assert_se(streq(c, "foobar")); + assert_se(streq(p, "/")); + c = mfree(c); + p = mfree(p); + + assert_se(cg_split_spec("foobar:", &c, &p) == 0); + c = mfree(c); + p = mfree(p); + + assert_se(cg_split_spec("foobar:asdfd", &c, &p) < 0); + assert_se(cg_split_spec(":///", &c, &p) < 0); + assert_se(cg_split_spec(":", &c, &p) < 0); + assert_se(cg_split_spec("", &c, &p) < 0); + assert_se(cg_split_spec("fo/obar:/", &c, &p) < 0); + + assert_se(cg_split_spec("/", &c, &p) >= 0); + assert_se(c == NULL); + assert_se(streq(p, "/")); + p = mfree(p); + + assert_se(cg_split_spec("foo", &c, &p) >= 0); + assert_se(streq(c, "foo")); + assert_se(p == NULL); + c = mfree(c); +} + +TEST(cg_create) { + int r; + + r = cg_unified_cached(false); + if (r == -ENOMEDIUM) { + log_tests_skipped("cgroup not mounted"); + return; + } + assert_se(r >= 0); + + _cleanup_free_ char *here = NULL; + assert_se(cg_pid_get_path_shifted(0, NULL, &here) >= 0); + + const char *test_a = prefix_roota(here, "/test-a"), + *test_b = prefix_roota(here, "/test-b"), + *test_c = prefix_roota(here, "/test-b/test-c"), + *test_d = prefix_roota(here, "/test-b/test-d"); + char *path; + + log_info("Paths for test:\n%s\n%s", test_a, test_b); + + /* Possibly clean up left-overs from aboted previous runs */ + (void) cg_trim(SYSTEMD_CGROUP_CONTROLLER, test_a, /* delete_root= */ true); + (void) cg_trim(SYSTEMD_CGROUP_CONTROLLER, test_b, /* delete_root= */ true); + + r = cg_create(SYSTEMD_CGROUP_CONTROLLER, test_a); + if (IN_SET(r, -EPERM, -EACCES, -EROFS)) { + log_info_errno(r, "Skipping %s: %m", __func__); + return; + } + + assert_se(r == 1); + assert_se(cg_create(SYSTEMD_CGROUP_CONTROLLER, test_a) == 0); + assert_se(cg_create(SYSTEMD_CGROUP_CONTROLLER, test_b) == 1); + assert_se(cg_create(SYSTEMD_CGROUP_CONTROLLER, test_c) == 1); + assert_se(cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, test_b, 0) == 0); + + assert_se(cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, getpid_cached(), &path) == 0); + assert_se(streq(path, test_b)); + free(path); + + assert_se(cg_attach(SYSTEMD_CGROUP_CONTROLLER, test_a, 0) == 0); + + assert_se(cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, getpid_cached(), &path) == 0); + assert_se(path_equal(path, test_a)); + free(path); + + assert_se(cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, test_d, 0) == 1); + + assert_se(cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, getpid_cached(), &path) == 0); + assert_se(path_equal(path, test_d)); + free(path); + + assert_se(cg_get_path(SYSTEMD_CGROUP_CONTROLLER, test_d, NULL, &path) == 0); + log_debug("test_d: %s", path); + const char *full_d; + if (cg_all_unified()) + full_d = strjoina("/sys/fs/cgroup", test_d); + else if (cg_hybrid_unified()) + full_d = strjoina("/sys/fs/cgroup/unified", test_d); + else + full_d = strjoina("/sys/fs/cgroup/systemd", test_d); + assert_se(path_equal(path, full_d)); + free(path); + + assert_se(cg_is_empty(SYSTEMD_CGROUP_CONTROLLER, test_a) > 0); + assert_se(cg_is_empty(SYSTEMD_CGROUP_CONTROLLER, test_b) > 0); + assert_se(cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, test_a) > 0); + assert_se(cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, test_b) == 0); + + assert_se(cg_kill_recursive(test_a, 0, 0, NULL, NULL, NULL) == 0); + assert_se(cg_kill_recursive(test_b, 0, 0, NULL, NULL, NULL) > 0); + + assert_se(cg_migrate_recursive(SYSTEMD_CGROUP_CONTROLLER, test_b, SYSTEMD_CGROUP_CONTROLLER, test_a, 0) > 0); + + assert_se(cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, test_a) == 0); + assert_se(cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, test_b) > 0); + + assert_se(cg_kill_recursive(test_a, 0, 0, NULL, NULL, NULL) > 0); + assert_se(cg_kill_recursive(test_b, 0, 0, NULL, NULL, NULL) == 0); + + (void) cg_trim(SYSTEMD_CGROUP_CONTROLLER, test_b, false); + + assert_se(cg_rmdir(SYSTEMD_CGROUP_CONTROLLER, test_b) == 0); + assert_se(cg_rmdir(SYSTEMD_CGROUP_CONTROLLER, test_a) < 0); + assert_se(cg_migrate_recursive(SYSTEMD_CGROUP_CONTROLLER, test_a, SYSTEMD_CGROUP_CONTROLLER, here, 0) > 0); + assert_se(cg_rmdir(SYSTEMD_CGROUP_CONTROLLER, test_a) == 0); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-chase-manual.c b/src/test/test-chase-manual.c new file mode 100644 index 0000000..475f089 --- /dev/null +++ b/src/test/test-chase-manual.c @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include <getopt.h> + +#include "chase.h" +#include "fd-util.h" +#include "log.h" +#include "main-func.h" +#include "tests.h" + +static char *arg_root = NULL; +static int arg_flags = 0; +static bool arg_open = false; + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_ROOT = 0x1000, + ARG_OPEN, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "root", required_argument, NULL, ARG_ROOT }, + { "open", no_argument, NULL, ARG_OPEN }, + + { "prefix-root", no_argument, NULL, CHASE_PREFIX_ROOT }, + { "nonexistent", no_argument, NULL, CHASE_NONEXISTENT }, + { "no_autofs", no_argument, NULL, CHASE_NO_AUTOFS }, + { "safe", no_argument, NULL, CHASE_SAFE }, + { "trail-slash", no_argument, NULL, CHASE_TRAIL_SLASH }, + { "step", no_argument, NULL, CHASE_STEP }, + { "nofollow", no_argument, NULL, CHASE_NOFOLLOW }, + { "warn", no_argument, NULL, CHASE_WARN }, + {} + }; + + int c; + + assert_se(argc >= 0); + assert_se(argv); + + while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0) + switch (c) { + + case 'h': + printf("Syntax:\n" + " %s [OPTION...] path...\n" + "Options:\n" + , argv[0]); + for (size_t i = 0; i < ELEMENTSOF(options) - 1; i++) + printf(" --%s\n", options[i].name); + return 0; + + case ARG_ROOT: + arg_root = optarg; + break; + + case ARG_OPEN: + arg_open = true; + break; + + case CHASE_PREFIX_ROOT: + case CHASE_NONEXISTENT: + case CHASE_NO_AUTOFS: + case CHASE_SAFE: + case CHASE_TRAIL_SLASH: + case CHASE_STEP: + case CHASE_NOFOLLOW: + case CHASE_WARN: + arg_flags |= c; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + + if (optind == argc) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "At least one argument is required."); + + return 1; +} + +static int run(int argc, char **argv) { + int r; + + test_setup_logging(LOG_DEBUG); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + for (int i = optind; i < argc; i++) { + _cleanup_free_ char *p = NULL; + _cleanup_close_ int fd = -EBADF; + + printf("%s ", argv[i]); + fflush(stdout); + + r = chase(argv[i], arg_root, arg_flags, &p, arg_open ? &fd : NULL); + if (r < 0) + log_error_errno(r, "failed: %m"); + else { + log_info("→ %s", p); + if (arg_open) + assert_se(fd >= 0); + else + assert_se(fd == -EBADF); + } + } + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/test/test-chase.c b/src/test/test-chase.c new file mode 100644 index 0000000..dbbc99b --- /dev/null +++ b/src/test/test-chase.c @@ -0,0 +1,756 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <unistd.h> + +#include "alloc-util.h" +#include "chase.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "id128-util.h" +#include "mkdir.h" +#include "path-util.h" +#include "rm-rf.h" +#include "string-util.h" +#include "tests.h" +#include "tmpfile-util.h" + +static const char *arg_test_dir = NULL; + +static void test_chase_extract_filename_one(const char *path, const char *root, const char *expected) { + _cleanup_free_ char *ret1 = NULL, *ret2 = NULL, *fname = NULL; + + log_debug("/* %s(path=%s, root=%s) */", __func__, path, strnull(root)); + + assert_se(chase(path, root, CHASE_EXTRACT_FILENAME, &ret1, NULL) > 0); + assert_se(streq(ret1, expected)); + + assert_se(chase(path, root, 0, &ret2, NULL) > 0); + assert_se(chase_extract_filename(ret2, root, &fname) >= 0); + assert_se(streq(fname, expected)); +} + +TEST(chase) { + _cleanup_free_ char *result = NULL, *pwd = NULL; + _cleanup_close_ int pfd = -EBADF; + char *temp; + const char *top, *p, *pslash, *q, *qslash; + struct stat st; + int r; + + temp = strjoina(arg_test_dir ?: "/tmp", "/test-chase.XXXXXX"); + assert_se(mkdtemp(temp)); + + top = strjoina(temp, "/top"); + assert_se(mkdir(top, 0700) >= 0); + + p = strjoina(top, "/dot"); + if (symlink(".", p) < 0) { + assert_se(IN_SET(errno, EINVAL, ENOSYS, ENOTTY, EPERM)); + log_tests_skipped_errno(errno, "symlink() not possible"); + goto cleanup; + }; + + p = strjoina(top, "/dotdot"); + assert_se(symlink("..", p) >= 0); + + p = strjoina(top, "/dotdota"); + assert_se(symlink("../a", p) >= 0); + + p = strjoina(temp, "/a"); + assert_se(symlink("b", p) >= 0); + + p = strjoina(temp, "/b"); + assert_se(symlink("/usr", p) >= 0); + + p = strjoina(temp, "/start"); + assert_se(symlink("top/dot/dotdota", p) >= 0); + + /* Paths that use symlinks underneath the "root" */ + + r = chase(p, NULL, 0, &result, NULL); + assert_se(r > 0); + assert_se(path_equal(result, "/usr")); + result = mfree(result); + + r = chase(p, "/.//../../../", 0, &result, NULL); + assert_se(r > 0); + assert_se(path_equal(result, "/usr")); + result = mfree(result); + + pslash = strjoina(p, "/"); + r = chase(pslash, NULL, 0, &result, NULL); + assert_se(r > 0); + assert_se(path_equal(result, "/usr/")); + result = mfree(result); + + r = chase(p, temp, 0, &result, NULL); + assert_se(r == -ENOENT); + + r = chase(pslash, temp, 0, &result, NULL); + assert_se(r == -ENOENT); + + q = strjoina(temp, "/usr"); + + r = chase(p, temp, CHASE_NONEXISTENT, &result, NULL); + assert_se(r == 0); + assert_se(path_equal(result, q)); + result = mfree(result); + + qslash = strjoina(q, "/"); + + r = chase(pslash, temp, CHASE_NONEXISTENT, &result, NULL); + assert_se(r == 0); + assert_se(path_equal(result, qslash)); + result = mfree(result); + + assert_se(mkdir(q, 0700) >= 0); + + r = chase(p, temp, 0, &result, NULL); + assert_se(r > 0); + assert_se(path_equal(result, q)); + result = mfree(result); + + r = chase(pslash, temp, 0, &result, NULL); + assert_se(r > 0); + assert_se(path_equal(result, qslash)); + result = mfree(result); + + p = strjoina(temp, "/slash"); + assert_se(symlink("/", p) >= 0); + + r = chase(p, NULL, 0, &result, NULL); + assert_se(r > 0); + assert_se(path_equal(result, "/")); + result = mfree(result); + + r = chase(p, temp, 0, &result, NULL); + assert_se(r > 0); + assert_se(path_equal(result, temp)); + result = mfree(result); + + /* Tests for CHASE_EXTRACT_FILENAME and chase_extract_filename() */ + + p = strjoina(temp, "/start"); + pslash = strjoina(p, "/"); + test_chase_extract_filename_one(p, NULL, "usr"); + test_chase_extract_filename_one(pslash, NULL, "usr"); + test_chase_extract_filename_one(p, temp, "usr"); + test_chase_extract_filename_one(pslash, temp, "usr"); + + p = strjoina(temp, "/slash"); + test_chase_extract_filename_one(p, NULL, "."); + test_chase_extract_filename_one(p, temp, "."); + + /* Paths that would "escape" outside of the "root" */ + + p = strjoina(temp, "/6dots"); + assert_se(symlink("../../..", p) >= 0); + + r = chase(p, temp, 0, &result, NULL); + assert_se(r > 0 && path_equal(result, temp)); + result = mfree(result); + + p = strjoina(temp, "/6dotsusr"); + assert_se(symlink("../../../usr", p) >= 0); + + r = chase(p, temp, 0, &result, NULL); + assert_se(r > 0 && path_equal(result, q)); + result = mfree(result); + + p = strjoina(temp, "/top/8dotsusr"); + assert_se(symlink("../../../../usr", p) >= 0); + + r = chase(p, temp, 0, &result, NULL); + assert_se(r > 0 && path_equal(result, q)); + result = mfree(result); + + /* Paths that contain repeated slashes */ + + p = strjoina(temp, "/slashslash"); + assert_se(symlink("///usr///", p) >= 0); + + r = chase(p, NULL, 0, &result, NULL); + assert_se(r > 0); + assert_se(path_equal(result, "/usr")); + assert_se(streq(result, "/usr")); /* we guarantee that we drop redundant slashes */ + result = mfree(result); + + r = chase(p, temp, 0, &result, NULL); + assert_se(r > 0); + assert_se(path_equal(result, q)); + result = mfree(result); + + /* Paths underneath the "root" with different UIDs while using CHASE_SAFE */ + + if (geteuid() == 0) { + p = strjoina(temp, "/user"); + assert_se(mkdir(p, 0755) >= 0); + assert_se(chown(p, UID_NOBODY, GID_NOBODY) >= 0); + + q = strjoina(temp, "/user/root"); + assert_se(mkdir(q, 0755) >= 0); + + p = strjoina(q, "/link"); + assert_se(symlink("/", p) >= 0); + + /* Fail when user-owned directories contain root-owned subdirectories. */ + r = chase(p, temp, CHASE_SAFE, &result, NULL); + assert_se(r == -ENOLINK); + result = mfree(result); + + /* Allow this when the user-owned directories are all in the "root". */ + r = chase(p, q, CHASE_SAFE, &result, NULL); + assert_se(r > 0); + result = mfree(result); + } + + /* Paths using . */ + + r = chase("/etc/./.././", NULL, 0, &result, NULL); + assert_se(r > 0); + assert_se(path_equal(result, "/")); + result = mfree(result); + + r = chase("/etc/./.././", "/etc", 0, &result, NULL); + assert_se(r > 0 && path_equal(result, "/etc")); + result = mfree(result); + + r = chase("/../.././//../../etc", NULL, 0, &result, NULL); + assert_se(r > 0); + assert_se(streq(result, "/etc")); + result = mfree(result); + + r = chase("/../.././//../../test-chase.fsldajfl", NULL, CHASE_NONEXISTENT, &result, NULL); + assert_se(r == 0); + assert_se(streq(result, "/test-chase.fsldajfl")); + result = mfree(result); + + r = chase("/../.././//../../etc", "/", CHASE_PREFIX_ROOT, &result, NULL); + assert_se(r > 0); + assert_se(streq(result, "/etc")); + result = mfree(result); + + r = chase("/../.././//../../test-chase.fsldajfl", "/", CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &result, NULL); + assert_se(r == 0); + assert_se(streq(result, "/test-chase.fsldajfl")); + result = mfree(result); + + r = chase("/etc/machine-id/foo", NULL, 0, &result, NULL); + assert_se(IN_SET(r, -ENOTDIR, -ENOENT)); + result = mfree(result); + + /* Path that loops back to self */ + + p = strjoina(temp, "/recursive-symlink"); + assert_se(symlink("recursive-symlink", p) >= 0); + r = chase(p, NULL, 0, &result, NULL); + assert_se(r == -ELOOP); + + /* Path which doesn't exist */ + + p = strjoina(temp, "/idontexist"); + r = chase(p, NULL, 0, &result, NULL); + assert_se(r == -ENOENT); + + r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL); + assert_se(r == 0); + assert_se(path_equal(result, p)); + result = mfree(result); + + p = strjoina(temp, "/idontexist/meneither"); + r = chase(p, NULL, 0, &result, NULL); + assert_se(r == -ENOENT); + + r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL); + assert_se(r == 0); + assert_se(path_equal(result, p)); + result = mfree(result); + + /* Relative paths */ + + assert_se(safe_getcwd(&pwd) >= 0); + + assert_se(chdir(temp) >= 0); + + p = "this/is/a/relative/path"; + r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL); + assert_se(r == 0); + + p = strjoina(temp, "/", p); + assert_se(path_equal(result, p)); + result = mfree(result); + + p = "this/is/a/relative/path"; + r = chase(p, temp, CHASE_NONEXISTENT, &result, NULL); + assert_se(r == 0); + + p = strjoina(temp, "/", p); + assert_se(path_equal(result, p)); + result = mfree(result); + + assert_se(chdir(pwd) >= 0); + + /* Path which doesn't exist, but contains weird stuff */ + + p = strjoina(temp, "/idontexist/.."); + r = chase(p, NULL, 0, &result, NULL); + assert_se(r == -ENOENT); + + r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL); + assert_se(r == -ENOENT); + + p = strjoina(temp, "/target"); + q = strjoina(temp, "/top"); + assert_se(symlink(q, p) >= 0); + p = strjoina(temp, "/target/idontexist"); + r = chase(p, NULL, 0, &result, NULL); + assert_se(r == -ENOENT); + + if (geteuid() == 0) { + p = strjoina(temp, "/priv1"); + assert_se(mkdir(p, 0755) >= 0); + + q = strjoina(p, "/priv2"); + assert_se(mkdir(q, 0755) >= 0); + + assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0); + + assert_se(chown(q, UID_NOBODY, GID_NOBODY) >= 0); + assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0); + + assert_se(chown(p, UID_NOBODY, GID_NOBODY) >= 0); + assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0); + + assert_se(chown(q, 0, 0) >= 0); + assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) == -ENOLINK); + + assert_se(rmdir(q) >= 0); + assert_se(symlink("/etc/passwd", q) >= 0); + assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) == -ENOLINK); + + assert_se(chown(p, 0, 0) >= 0); + assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0); + } + + p = strjoina(temp, "/machine-id-test"); + assert_se(symlink("/usr/../etc/./machine-id", p) >= 0); + + r = chase(p, NULL, 0, NULL, &pfd); + if (r != -ENOENT && sd_id128_get_machine(NULL) >= 0) { + _cleanup_close_ int fd = -EBADF; + sd_id128_t a, b; + + assert_se(pfd >= 0); + + fd = fd_reopen(pfd, O_RDONLY|O_CLOEXEC); + assert_se(fd >= 0); + safe_close(pfd); + + assert_se(id128_read_fd(fd, ID128_FORMAT_PLAIN, &a) >= 0); + assert_se(sd_id128_get_machine(&b) >= 0); + assert_se(sd_id128_equal(a, b)); + } + + assert_se(lstat(p, &st) >= 0); + r = chase_and_unlink(p, NULL, 0, 0, &result); + assert_se(r == 0); + assert_se(path_equal(result, p)); + result = mfree(result); + assert_se(lstat(p, &st) == -1 && errno == ENOENT); + + /* Test CHASE_NOFOLLOW */ + + p = strjoina(temp, "/target"); + q = strjoina(temp, "/symlink"); + assert_se(symlink(p, q) >= 0); + r = chase(q, NULL, CHASE_NOFOLLOW, &result, &pfd); + assert_se(r >= 0); + assert_se(pfd >= 0); + assert_se(path_equal(result, q)); + assert_se(fstat(pfd, &st) >= 0); + assert_se(S_ISLNK(st.st_mode)); + result = mfree(result); + pfd = safe_close(pfd); + + /* s1 -> s2 -> nonexistent */ + q = strjoina(temp, "/s1"); + assert_se(symlink("s2", q) >= 0); + p = strjoina(temp, "/s2"); + assert_se(symlink("nonexistent", p) >= 0); + r = chase(q, NULL, CHASE_NOFOLLOW, &result, &pfd); + assert_se(r >= 0); + assert_se(pfd >= 0); + assert_se(path_equal(result, q)); + assert_se(fstat(pfd, &st) >= 0); + assert_se(S_ISLNK(st.st_mode)); + result = mfree(result); + pfd = safe_close(pfd); + + /* Test CHASE_STEP */ + + p = strjoina(temp, "/start"); + r = chase(p, NULL, CHASE_STEP, &result, NULL); + assert_se(r == 0); + p = strjoina(temp, "/top/dot/dotdota"); + assert_se(streq(p, result)); + result = mfree(result); + + r = chase(p, NULL, CHASE_STEP, &result, NULL); + assert_se(r == 0); + p = strjoina(temp, "/top/dotdota"); + assert_se(streq(p, result)); + result = mfree(result); + + r = chase(p, NULL, CHASE_STEP, &result, NULL); + assert_se(r == 0); + p = strjoina(temp, "/top/../a"); + assert_se(streq(p, result)); + result = mfree(result); + + r = chase(p, NULL, CHASE_STEP, &result, NULL); + assert_se(r == 0); + p = strjoina(temp, "/a"); + assert_se(streq(p, result)); + result = mfree(result); + + r = chase(p, NULL, CHASE_STEP, &result, NULL); + assert_se(r == 0); + p = strjoina(temp, "/b"); + assert_se(streq(p, result)); + result = mfree(result); + + r = chase(p, NULL, CHASE_STEP, &result, NULL); + assert_se(r == 0); + assert_se(streq("/usr", result)); + result = mfree(result); + + r = chase("/usr", NULL, CHASE_STEP, &result, NULL); + assert_se(r > 0); + assert_se(streq("/usr", result)); + result = mfree(result); + + /* Make sure that symlinks in the "root" path are not resolved, but those below are */ + p = strjoina("/etc/..", temp, "/self"); + assert_se(symlink(".", p) >= 0); + q = strjoina(p, "/top/dot/dotdota"); + r = chase(q, p, 0, &result, NULL); + assert_se(r > 0); + assert_se(path_equal(path_startswith(result, p), "usr")); + result = mfree(result); + + /* Test CHASE_PROHIBIT_SYMLINKS */ + + assert_se(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG); + assert_se(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG); + assert_se(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG); + assert_se(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG); + assert_se(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG); + assert_se(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG); + + cleanup: + assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); +} + +TEST(chaseat) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int tfd = -EBADF, fd = -EBADF; + _cleanup_free_ char *result = NULL; + _cleanup_closedir_ DIR *dir = NULL; + _cleanup_fclose_ FILE *f = NULL; + struct stat st; + const char *p; + + assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0); + + /* Test that AT_FDCWD with CHASE_AT_RESOLVE_IN_ROOT resolves against / and not the current working + * directory. */ + + assert_se(symlinkat("/usr", tfd, "abc") >= 0); + + p = strjoina(t, "/abc"); + assert_se(chaseat(AT_FDCWD, p, CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0); + assert_se(streq(result, "/usr")); + result = mfree(result); + + /* If the file descriptor points to the root directory, the result will be absolute. */ + + fd = open("/", O_CLOEXEC | O_DIRECTORY | O_PATH); + assert_se(fd >= 0); + + assert_se(chaseat(fd, p, 0, &result, NULL) >= 0); + assert_se(streq(result, "/usr")); + result = mfree(result); + + assert_se(chaseat(fd, p, CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0); + assert_se(streq(result, "/usr")); + result = mfree(result); + + fd = safe_close(fd); + + /* If the file descriptor does not point to the root directory, the result will be relative + * unless the result is outside of the specified file descriptor. */ + + assert_se(chaseat(tfd, "abc", 0, &result, NULL) >= 0); + assert_se(streq(result, "/usr")); + result = mfree(result); + + assert_se(chaseat(tfd, "/abc", 0, &result, NULL) >= 0); + assert_se(streq(result, "/usr")); + result = mfree(result); + + assert_se(chaseat(tfd, "abc", CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL) == -ENOENT); + assert_se(chaseat(tfd, "/abc", CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL) == -ENOENT); + + assert_se(chaseat(tfd, "abc", CHASE_AT_RESOLVE_IN_ROOT | CHASE_NONEXISTENT, &result, NULL) >= 0); + assert_se(streq(result, "usr")); + result = mfree(result); + + assert_se(chaseat(tfd, "/abc", CHASE_AT_RESOLVE_IN_ROOT | CHASE_NONEXISTENT, &result, NULL) >= 0); + assert_se(streq(result, "usr")); + result = mfree(result); + + /* Test that absolute path or not are the same when resolving relative to a directory file + * descriptor and that we always get a relative path back. */ + + assert_se(fd = openat(tfd, "def", O_CREAT|O_CLOEXEC, 0700) >= 0); + fd = safe_close(fd); + assert_se(symlinkat("/def", tfd, "qed") >= 0); + assert_se(chaseat(tfd, "qed", CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0); + assert_se(streq(result, "def")); + result = mfree(result); + assert_se(chaseat(tfd, "/qed", CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0); + assert_se(streq(result, "def")); + result = mfree(result); + + /* Valid directory file descriptor without CHASE_AT_RESOLVE_IN_ROOT should resolve symlinks against + * host's root. */ + assert_se(chaseat(tfd, "/qed", 0, NULL, NULL) == -ENOENT); + + /* Test CHASE_PARENT */ + + assert_se((fd = open_mkdir_at(tfd, "chase", O_CLOEXEC, 0755)) >= 0); + assert_se(symlinkat("/def", fd, "parent") >= 0); + fd = safe_close(fd); + + /* Make sure that when we chase a symlink parent directory, that we chase the parent directory of the + * symlink target and not the symlink itself. But if we add CHASE_NOFOLLOW, we get the parent + * directory of the symlink itself. */ + + assert_se(chaseat(tfd, "chase/parent", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd) >= 0); + assert_se(faccessat(fd, "def", F_OK, 0) >= 0); + assert_se(streq(result, "def")); + fd = safe_close(fd); + result = mfree(result); + + assert_se(chaseat(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW, &result, &fd) >= 0); + assert_se(faccessat(fd, "parent", F_OK, AT_SYMLINK_NOFOLLOW) >= 0); + assert_se(streq(result, "chase/parent")); + fd = safe_close(fd); + result = mfree(result); + + assert_se(chaseat(tfd, "chase", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd) >= 0); + assert_se(faccessat(fd, "chase", F_OK, 0) >= 0); + assert_se(streq(result, "chase")); + fd = safe_close(fd); + result = mfree(result); + + assert_se(chaseat(tfd, "/", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0); + assert_se(streq(result, ".")); + result = mfree(result); + + assert_se(chaseat(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0); + assert_se(streq(result, ".")); + result = mfree(result); + + /* Test CHASE_MKDIR_0755 */ + + assert_se(chaseat(tfd, "m/k/d/i/r", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL) >= 0); + assert_se(faccessat(tfd, "m/k/d/i", F_OK, 0) >= 0); + assert_se(RET_NERRNO(faccessat(tfd, "m/k/d/i/r", F_OK, 0)) == -ENOENT); + assert_se(streq(result, "m/k/d/i/r")); + result = mfree(result); + + assert_se(chaseat(tfd, "m/../q", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL) >= 0); + assert_se(faccessat(tfd, "m", F_OK, 0) >= 0); + assert_se(RET_NERRNO(faccessat(tfd, "q", F_OK, 0)) == -ENOENT); + assert_se(streq(result, "q")); + result = mfree(result); + + assert_se(chaseat(tfd, "i/../p", CHASE_MKDIR_0755|CHASE_NONEXISTENT, NULL, NULL) == -ENOENT); + + /* Test CHASE_EXTRACT_FILENAME */ + + assert_se(chaseat(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME, &result, &fd) >= 0); + assert_se(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW) >= 0); + assert_se(streq(result, "parent")); + fd = safe_close(fd); + result = mfree(result); + + assert_se(chaseat(tfd, "chase", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, &fd) >= 0); + assert_se(faccessat(fd, result, F_OK, 0) >= 0); + assert_se(streq(result, "chase")); + fd = safe_close(fd); + result = mfree(result); + + assert_se(chaseat(tfd, "/", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL) >= 0); + assert_se(streq(result, ".")); + result = mfree(result); + + assert_se(chaseat(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL) >= 0); + assert_se(streq(result, ".")); + result = mfree(result); + + assert_se(chaseat(tfd, NULL, CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL) >= 0); + assert_se(streq(result, ".")); + result = mfree(result); + + /* Test chase_and_openat() */ + + fd = chase_and_openat(tfd, "o/p/e/n/f/i/l/e", CHASE_MKDIR_0755, O_CREAT|O_EXCL|O_CLOEXEC, NULL); + assert_se(fd >= 0); + assert_se(fd_verify_regular(fd) >= 0); + fd = safe_close(fd); + + fd = chase_and_openat(tfd, "o/p/e/n/d/i/r", CHASE_MKDIR_0755, O_DIRECTORY|O_CREAT|O_EXCL|O_CLOEXEC, NULL); + assert_se(fd >= 0); + assert_se(fd_verify_directory(fd) >= 0); + fd = safe_close(fd); + + fd = chase_and_openat(tfd, NULL, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result); + assert_se(fd >= 0); + assert_se(streq(result, ".")); + fd = safe_close(fd); + result = mfree(result); + + /* Test chase_and_openatdir() */ + + assert_se(chase_and_opendirat(tfd, "o/p/e/n/d/i", 0, &result, &dir) >= 0); + FOREACH_DIRENT(de, dir, assert_not_reached()) + assert_se(streq(de->d_name, "r")); + assert_se(streq(result, "o/p/e/n/d/i")); + result = mfree(result); + + /* Test chase_and_statat() */ + + assert_se(chase_and_statat(tfd, "o/p", 0, &result, &st) >= 0); + assert_se(stat_verify_directory(&st) >= 0); + assert_se(streq(result, "o/p")); + result = mfree(result); + + /* Test chase_and_accessat() */ + + assert_se(chase_and_accessat(tfd, "o/p/e", 0, F_OK, &result) >= 0); + assert_se(streq(result, "o/p/e")); + result = mfree(result); + + /* Test chase_and_fopenat_unlocked() */ + + assert_se(chase_and_fopenat_unlocked(tfd, "o/p/e/n/f/i/l/e", 0, "re", &result, &f) >= 0); + assert_se(fread(&(char[1]) {}, 1, 1, f) == 0); + assert_se(feof(f)); + f = safe_fclose(f); + assert_se(streq(result, "o/p/e/n/f/i/l/e")); + result = mfree(result); + + /* Test chase_and_unlinkat() */ + + assert_se(chase_and_unlinkat(tfd, "o/p/e/n/f/i/l/e", 0, 0, &result) >= 0); + assert_se(streq(result, "o/p/e/n/f/i/l/e")); + result = mfree(result); + + /* Test chase_and_open_parent_at() */ + + assert_se((fd = chase_and_open_parent_at(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_NOFOLLOW, &result)) >= 0); + assert_se(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW) >= 0); + assert_se(streq(result, "parent")); + fd = safe_close(fd); + result = mfree(result); + + assert_se((fd = chase_and_open_parent_at(tfd, "chase", CHASE_AT_RESOLVE_IN_ROOT, &result)) >= 0); + assert_se(faccessat(fd, result, F_OK, 0) >= 0); + assert_se(streq(result, "chase")); + fd = safe_close(fd); + result = mfree(result); + + assert_se((fd = chase_and_open_parent_at(tfd, "/", CHASE_AT_RESOLVE_IN_ROOT, &result)) >= 0); + assert_se(streq(result, ".")); + fd = safe_close(fd); + result = mfree(result); + + assert_se((fd = chase_and_open_parent_at(tfd, ".", CHASE_AT_RESOLVE_IN_ROOT, &result)) >= 0); + assert_se(streq(result, ".")); + fd = safe_close(fd); + result = mfree(result); +} + +TEST(chaseat_prefix_root) { + _cleanup_free_ char *cwd = NULL, *ret = NULL, *expected = NULL; + + assert_se(safe_getcwd(&cwd) >= 0); + + assert_se(chaseat_prefix_root("/hoge", NULL, &ret) >= 0); + assert_se(streq(ret, "/hoge")); + + ret = mfree(ret); + + assert_se(chaseat_prefix_root("/hoge", "a/b/c", &ret) >= 0); + assert_se(streq(ret, "/hoge")); + + ret = mfree(ret); + + assert_se(chaseat_prefix_root("hoge", "/a/b//./c///", &ret) >= 0); + assert_se(streq(ret, "/a/b/c/hoge")); + + ret = mfree(ret); + + assert_se(chaseat_prefix_root("hoge", "a/b//./c///", &ret) >= 0); + assert_se(expected = path_join(cwd, "a/b/c/hoge")); + assert_se(streq(ret, expected)); + + ret = mfree(ret); + expected = mfree(expected); + + assert_se(chaseat_prefix_root("./hoge/aaa/../././b", "/a/b//./c///", &ret) >= 0); + assert_se(streq(ret, "/a/b/c/hoge/aaa/../././b")); + + ret = mfree(ret); + + assert_se(chaseat_prefix_root("./hoge/aaa/../././b", "a/b//./c///", &ret) >= 0); + assert_se(expected = path_join(cwd, "a/b/c/hoge/aaa/../././b")); + assert_se(streq(ret, expected)); +} + +TEST(trailing_dot_dot) { + _cleanup_free_ char *path = NULL, *fdpath = NULL; + _cleanup_close_ int fd = -EBADF; + + assert_se(chase("/usr/..", NULL, CHASE_PARENT, &path, &fd) >= 0); + assert_se(path_equal(path, "/")); + assert_se(fd_get_path(fd, &fdpath) >= 0); + assert_se(path_equal(fdpath, "/")); + + path = mfree(path); + fdpath = mfree(fdpath); + fd = safe_close(fd); + + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + assert_se(mkdtemp_malloc(NULL, &t) >= 0); + _cleanup_free_ char *sub = ASSERT_PTR(path_join(t, "a/b/c/d")); + assert_se(mkdir_p(sub, 0700) >= 0); + _cleanup_free_ char *suffixed = ASSERT_PTR(path_join(sub, "..")); + assert_se(chase(suffixed, NULL, CHASE_PARENT, &path, &fd) >= 0); + _cleanup_free_ char *expected1 = ASSERT_PTR(path_join(t, "a/b/c")); + _cleanup_free_ char *expected2 = ASSERT_PTR(path_join(t, "a/b")); + + assert_se(path_equal(path, expected1)); + assert_se(fd_get_path(fd, &fdpath) >= 0); + assert_se(path_equal(fdpath, expected2)); +} + +static int intro(void) { + arg_test_dir = saved_argv[1]; + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/test/test-chown-rec.c b/src/test/test-chown-rec.c new file mode 100644 index 0000000..5d83f59 --- /dev/null +++ b/src/test/test-chown-rec.c @@ -0,0 +1,162 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <sys/xattr.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "chown-recursive.h" +#include "log.h" +#include "rm-rf.h" +#include "string-util.h" +#include "tests.h" +#include "tmpfile-util.h" + +static const uint8_t acl[] = { + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, + 0xff, 0xff, 0xff, 0xff, 0x02, 0x00, 0x07, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x07, 0x00, + 0xff, 0xff, 0xff, 0xff, 0x10, 0x00, 0x07, 0x00, + 0xff, 0xff, 0xff, 0xff, 0x20, 0x00, 0x05, 0x00, + 0xff, 0xff, 0xff, 0xff, +}; + +static const uint8_t default_acl[] = { + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, + 0xff, 0xff, 0xff, 0xff, 0x04, 0x00, 0x07, 0x00, + 0xff, 0xff, 0xff, 0xff, 0x08, 0x00, 0x07, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x07, 0x00, + 0xff, 0xff, 0xff, 0xff, 0x20, 0x00, 0x05, 0x00, + 0xff, 0xff, 0xff, 0xff, +}; + +static bool has_xattr(const char *p) { + char buffer[sizeof(acl) * 4]; + + if (lgetxattr(p, "system.posix_acl_access", buffer, sizeof(buffer)) < 0) + return !ERRNO_IS_XATTR_ABSENT(errno); + + return true; +} + +TEST(chown_recursive) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + struct stat st; + const char *p; + const uid_t uid = getuid(); + const gid_t gid = getgid(); + int r; + + umask(022); + assert_se(mkdtemp_malloc(NULL, &t) >= 0); + + p = strjoina(t, "/dir"); + assert_se(mkdir(p, 0777) >= 0); + assert_se(lstat(p, &st) >= 0); + assert_se(S_ISDIR(st.st_mode)); + assert_se((st.st_mode & 07777) == 0755); + assert_se(st.st_uid == uid); + assert_se(st.st_gid == gid); + assert_se(!has_xattr(p)); + + p = strjoina(t, "/dir/symlink"); + assert_se(symlink("../../", p) >= 0); + assert_se(lstat(p, &st) >= 0); + assert_se(S_ISLNK(st.st_mode)); + assert_se((st.st_mode & 07777) == 0777); + assert_se(st.st_uid == uid); + assert_se(st.st_gid == gid); + assert_se(!has_xattr(p)); + + p = strjoina(t, "/dir/reg"); + assert_se(mknod(p, S_IFREG|0777, 0) >= 0); + assert_se(lstat(p, &st) >= 0); + assert_se(S_ISREG(st.st_mode)); + assert_se((st.st_mode & 07777) == 0755); + assert_se(st.st_uid == uid); + assert_se(st.st_gid == gid); + assert_se(!has_xattr(p)); + + p = strjoina(t, "/dir/sock"); + assert_se(mknod(p, S_IFSOCK|0777, 0) >= 0); + assert_se(lstat(p, &st) >= 0); + assert_se(S_ISSOCK(st.st_mode)); + assert_se((st.st_mode & 07777) == 0755); + assert_se(st.st_uid == uid); + assert_se(st.st_gid == gid); + assert_se(!has_xattr(p)); + + p = strjoina(t, "/dir/fifo"); + assert_se(mknod(p, S_IFIFO|0777, 0) >= 0); + assert_se(lstat(p, &st) >= 0); + assert_se(S_ISFIFO(st.st_mode)); + assert_se((st.st_mode & 07777) == 0755); + assert_se(st.st_uid == uid); + assert_se(st.st_gid == gid); + assert_se(!has_xattr(p)); + + /* We now apply an xattr to the dir, and check it again */ + p = strjoina(t, "/dir"); + r = RET_NERRNO(setxattr(p, "system.posix_acl_access", acl, sizeof(acl), 0)); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return (void) log_tests_skipped_errno(r, "no acl supported on /tmp"); + + assert_se(r >= 0); + assert_se(setxattr(p, "system.posix_acl_default", default_acl, sizeof(default_acl), 0) >= 0); + assert_se(lstat(p, &st) >= 0); + assert_se(S_ISDIR(st.st_mode)); + assert_se((st.st_mode & 07777) == 0775); /* acl change changed the mode too */ + assert_se(st.st_uid == uid); + assert_se(st.st_gid == gid); + assert_se(has_xattr(p)); + + assert_se(path_chown_recursive(t, 1, 2, 07777, 0) >= 0); + + p = strjoina(t, "/dir"); + assert_se(lstat(p, &st) >= 0); + assert_se(S_ISDIR(st.st_mode)); + assert_se((st.st_mode & 07777) == 0775); + assert_se(st.st_uid == 1); + assert_se(st.st_gid == 2); + assert_se(!has_xattr(p)); + + p = strjoina(t, "/dir/symlink"); + assert_se(lstat(p, &st) >= 0); + assert_se(S_ISLNK(st.st_mode)); + assert_se((st.st_mode & 07777) == 0777); + assert_se(st.st_uid == 1); + assert_se(st.st_gid == 2); + assert_se(!has_xattr(p)); + + p = strjoina(t, "/dir/reg"); + assert_se(lstat(p, &st) >= 0); + assert_se(S_ISREG(st.st_mode)); + assert_se((st.st_mode & 07777) == 0755); + assert_se(st.st_uid == 1); + assert_se(st.st_gid == 2); + assert_se(!has_xattr(p)); + + p = strjoina(t, "/dir/sock"); + assert_se(lstat(p, &st) >= 0); + assert_se(S_ISSOCK(st.st_mode)); + assert_se((st.st_mode & 07777) == 0755); + assert_se(st.st_uid == 1); + assert_se(st.st_gid == 2); + assert_se(!has_xattr(p)); + + p = strjoina(t, "/dir/fifo"); + assert_se(lstat(p, &st) >= 0); + assert_se(S_ISFIFO(st.st_mode)); + assert_se((st.st_mode & 07777) == 0755); + assert_se(st.st_uid == 1); + assert_se(st.st_gid == 2); + assert_se(!has_xattr(p)); +} + +static int intro(void) { + if (geteuid() != 0) + return log_tests_skipped("not running as root"); + + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/src/test/test-clock.c b/src/test/test-clock.c new file mode 100644 index 0000000..123831a --- /dev/null +++ b/src/test/test-clock.c @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2016 Canonical Ltd. +***/ + +#include <unistd.h> +#include <fcntl.h> + +#include "clock-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "log.h" +#include "macro.h" +#include "tests.h" +#include "tmpfile-util.h" + +TEST(clock_is_localtime) { + _cleanup_(unlink_tempfilep) char adjtime[] = "/tmp/test-adjtime.XXXXXX"; + _cleanup_fclose_ FILE* f = NULL; + + static const struct scenario { + const char* contents; + int expected_result; + } scenarios[] = { + /* adjtime configures UTC */ + {"0.0 0 0\n0\nUTC\n", 0}, + /* adjtime configures local time */ + {"0.0 0 0\n0\nLOCAL\n", 1}, + /* no final EOL */ + {"0.0 0 0\n0\nUTC", 0}, + {"0.0 0 0\n0\nLOCAL", 1}, + /* empty value -> defaults to UTC */ + {"0.0 0 0\n0\n", 0}, + /* unknown value -> defaults to UTC */ + {"0.0 0 0\n0\nFOO\n", 0}, + /* no third line */ + {"0.0 0 0", 0}, + {"0.0 0 0\n", 0}, + {"0.0 0 0\n0", 0}, + }; + + /* without an adjtime file we default to UTC */ + assert_se(clock_is_localtime("/nonexisting/adjtime") == 0); + + assert_se(fmkostemp_safe(adjtime, "w", &f) == 0); + log_info("adjtime test file: %s", adjtime); + + for (size_t i = 0; i < ELEMENTSOF(scenarios); ++i) { + log_info("scenario #%zu:, expected result %i", i, scenarios[i].expected_result); + log_info("%s", scenarios[i].contents); + rewind(f); + assert_se(ftruncate(fileno(f), 0) == 0); + assert_se(write_string_stream(f, scenarios[i].contents, WRITE_STRING_FILE_AVOID_NEWLINE) == 0); + assert_se(clock_is_localtime(adjtime) == scenarios[i].expected_result); + } +} + +/* Test with the real /etc/adjtime */ +TEST(clock_is_localtime_system) { + int r; + r = clock_is_localtime(NULL); + + if (access("/etc/adjtime", R_OK) == 0) { + log_info("/etc/adjtime is readable, clock_is_localtime() == %i", r); + /* if /etc/adjtime exists we expect some answer, no error or + * crash */ + assert_se(IN_SET(r, 0, 1)); + } else + /* default is UTC if there is no /etc/adjtime */ + assert_se(r == 0 || ERRNO_IS_PRIVILEGE(r)); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-compare-operator.c b/src/test/test-compare-operator.c new file mode 100644 index 0000000..3d8f46f --- /dev/null +++ b/src/test/test-compare-operator.c @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "compare-operator.h" +#include "tests.h" + +TEST(parse_compare_operator) { + const char *str_a = "<>version"; + assert_se(parse_compare_operator(&str_a, 0) == COMPARE_UNEQUAL); + const char *str_b = "$=version"; + assert_se(parse_compare_operator(&str_b, 0) == _COMPARE_OPERATOR_INVALID); + assert_se(parse_compare_operator(&str_b, COMPARE_ALLOW_FNMATCH) == COMPARE_FNMATCH_EQUAL); + const char *str_c = "eq oranges"; + assert_se(parse_compare_operator(&str_c, 0) == _COMPARE_OPERATOR_INVALID); + assert_se(parse_compare_operator(&str_c, COMPARE_ALLOW_TEXTUAL) == COMPARE_EQUAL); + const char *str_d = ""; + assert_se(parse_compare_operator(&str_d, 0) == _COMPARE_OPERATOR_INVALID); + const char *str_e = "!=!="; /* parse_compare_operator() moves the pointer */ + assert_se(parse_compare_operator(&str_e, COMPARE_EQUAL_BY_STRING) == COMPARE_STRING_UNEQUAL); + assert_se(parse_compare_operator(&str_e, 0) == COMPARE_UNEQUAL); + assert_se(parse_compare_operator(&str_e, 0) == _COMPARE_OPERATOR_INVALID); +} + +TEST(test_order) { + assert_se(!test_order(5, COMPARE_LOWER)); + assert_se(!test_order(5, COMPARE_LOWER_OR_EQUAL)); + assert_se(!test_order(5, COMPARE_EQUAL)); + assert_se(test_order(5, COMPARE_UNEQUAL)); + assert_se(test_order(5, COMPARE_GREATER_OR_EQUAL)); + assert_se(test_order(5, COMPARE_GREATER)); + assert_se(test_order(5, COMPARE_STRING_EQUAL) == -EINVAL); +} + +TEST(version_or_fnmatch_compare) { + assert_se(version_or_fnmatch_compare(COMPARE_STRING_EQUAL, "locale", "locale")); + assert_se(version_or_fnmatch_compare(COMPARE_STRING_UNEQUAL, "locale", "LOCALE")); + assert_se(version_or_fnmatch_compare(COMPARE_FNMATCH_EQUAL, "locaale", "loc*le")); + assert_se(version_or_fnmatch_compare(COMPARE_FNMATCH_UNEQUAL, "locaale", "loc?le")); + assert_se(version_or_fnmatch_compare(COMPARE_GREATER, "local512", "local256")); + assert_se(version_or_fnmatch_compare(COMPARE_LOWER, "local52", "local256")); + assert_se(version_or_fnmatch_compare(_COMPARE_OPERATOR_MAX, "local512", "local256") == -EINVAL); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-compress-benchmark.c b/src/test/test-compress-benchmark.c new file mode 100644 index 0000000..1727db8 --- /dev/null +++ b/src/test/test-compress-benchmark.c @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "compress.h" +#include "env-util.h" +#include "macro.h" +#include "memory-util.h" +#include "nulstr-util.h" +#include "parse-util.h" +#include "process-util.h" +#include "random-util.h" +#include "string-util.h" +#include "tests.h" + +typedef int (compress_t)(const void *src, uint64_t src_size, void *dst, + size_t dst_alloc_size, size_t *dst_size); +typedef int (decompress_t)(const void *src, uint64_t src_size, + void **dst, size_t* dst_size, size_t dst_max); + +#if HAVE_COMPRESSION + +static usec_t arg_duration; +static size_t arg_start; + +#define MAX_SIZE (1024*1024LU) +#define PRIME 1048571 /* A prime close enough to one megabyte that mod 4 == 3 */ + +static size_t _permute(size_t x) { + size_t residue; + + if (x >= PRIME) + return x; + + residue = x*x % PRIME; + if (x <= PRIME / 2) + return residue; + else + return PRIME - residue; +} + +static size_t permute(size_t x) { + return _permute((_permute(x) + arg_start) % MAX_SIZE ^ 0xFF345); +} + +static char* make_buf(size_t count, const char *type) { + char *buf; + size_t i; + + buf = malloc(count); + assert_se(buf); + + if (streq(type, "zeros")) + memzero(buf, count); + else if (streq(type, "simple")) + for (i = 0; i < count; i++) + buf[i] = 'a' + i % ('z' - 'a' + 1); + else if (streq(type, "random")) { + size_t step = count / 10; + + random_bytes(buf, step); + memzero(buf + 1*step, step); + random_bytes(buf + 2*step, step); + memzero(buf + 3*step, step); + random_bytes(buf + 4*step, step); + memzero(buf + 5*step, step); + random_bytes(buf + 6*step, step); + memzero(buf + 7*step, step); + random_bytes(buf + 8*step, step); + memzero(buf + 9*step, step); + } else + assert_not_reached(); + + return buf; +} + +static void test_compress_decompress(const char* label, const char* type, + compress_t compress, decompress_t decompress) { + usec_t n, n2 = 0; + float dt; + + _cleanup_free_ char *text = NULL, *buf = NULL; + _cleanup_free_ void *buf2 = NULL; + size_t skipped = 0, compressed = 0, total = 0; + + text = make_buf(MAX_SIZE, type); + buf = calloc(MAX_SIZE + 1, 1); + assert_se(text && buf); + + n = now(CLOCK_MONOTONIC); + + for (size_t i = 0; i <= MAX_SIZE; i++) { + size_t j = 0, k = 0, size; + int r; + + size = permute(i); + if (size == 0) + continue; + + log_debug("%s %zu %zu", type, i, size); + + memzero(buf, MIN(size + 1000, MAX_SIZE)); + + r = compress(text, size, buf, size, &j); + /* assume compression must be successful except for small or random inputs */ + assert_se(r >= 0 || (size < 2048 && r == -ENOBUFS) || streq(type, "random")); + + /* check for overwrites */ + assert_se(buf[size] == 0); + if (r < 0) { + skipped += size; + continue; + } + + assert_se(j > 0); + if (j >= size) + log_error("%s \"compressed\" %zu -> %zu", label, size, j); + + r = decompress(buf, j, &buf2, &k, 0); + assert_se(r == 0); + assert_se(k == size); + + assert_se(memcmp(text, buf2, size) == 0); + + total += size; + compressed += j; + + n2 = now(CLOCK_MONOTONIC); + if (n2 - n > arg_duration) + break; + } + + dt = (n2-n) / 1e6; + + log_info("%s/%s: compressed & decompressed %zu bytes in %.2fs (%.2fMiB/s), " + "mean compression %.2f%%, skipped %zu bytes", + label, type, total, dt, + total / 1024. / 1024 / dt, + 100 - compressed * 100. / total, + skipped); +} +#endif + +int main(int argc, char *argv[]) { +#if HAVE_COMPRESSION + test_setup_logging(LOG_INFO); + + if (argc >= 2) { + unsigned x; + + assert_se(safe_atou(argv[1], &x) >= 0); + arg_duration = x * USEC_PER_SEC; + } else + arg_duration = slow_tests_enabled() ? + 2 * USEC_PER_SEC : USEC_PER_SEC / 50; + + if (argc == 3) + (void) safe_atozu(argv[2], &arg_start); + else + arg_start = getpid_cached(); + + NULSTR_FOREACH(i, "zeros\0simple\0random\0") { +#if HAVE_XZ + test_compress_decompress("XZ", i, compress_blob_xz, decompress_blob_xz); +#endif +#if HAVE_LZ4 + test_compress_decompress("LZ4", i, compress_blob_lz4, decompress_blob_lz4); +#endif +#if HAVE_ZSTD + test_compress_decompress("ZSTD", i, compress_blob_zstd, decompress_blob_zstd); +#endif + } + return 0; +#else + return log_tests_skipped("No compression feature is enabled"); +#endif +} diff --git a/src/test/test-compress.c b/src/test/test-compress.c new file mode 100644 index 0000000..2f20d00 --- /dev/null +++ b/src/test/test-compress.c @@ -0,0 +1,373 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <sys/stat.h> + +#if HAVE_LZ4 +#include <lz4.h> +#endif + +#include "alloc-util.h" +#include "compress.h" +#include "fd-util.h" +#include "fs-util.h" +#include "macro.h" +#include "memory-util.h" +#include "path-util.h" +#include "random-util.h" +#include "tests.h" +#include "tmpfile-util.h" + +#if HAVE_XZ +# define XZ_OK 0 +#else +# define XZ_OK -EPROTONOSUPPORT +#endif + +#if HAVE_LZ4 +# define LZ4_OK 0 +#else +# define LZ4_OK -EPROTONOSUPPORT +#endif + +#define HUGE_SIZE (4096*1024) + +typedef int (compress_blob_t)(const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size); +typedef int (decompress_blob_t)(const void *src, uint64_t src_size, + void **dst, + size_t* dst_size, size_t dst_max); +typedef int (decompress_sw_t)(const void *src, uint64_t src_size, + void **buffer, + const void *prefix, size_t prefix_len, + uint8_t extra); + +typedef int (compress_stream_t)(int fdf, int fdt, uint64_t max_bytes, uint64_t *uncompressed_size); +typedef int (decompress_stream_t)(int fdf, int fdt, uint64_t max_size); + +#if HAVE_COMPRESSION +_unused_ static void test_compress_decompress( + const char *compression, + compress_blob_t compress, + decompress_blob_t decompress, + const char *data, + size_t data_len, + bool may_fail) { + + char compressed[512]; + size_t csize; + _cleanup_free_ char *decompressed = NULL; + int r; + + log_info("/* testing %s %s blob compression/decompression */", + compression, data); + + r = compress(data, data_len, compressed, sizeof(compressed), &csize); + if (r == -ENOBUFS) { + log_info_errno(r, "compression failed: %m"); + assert_se(may_fail); + } else { + assert_se(r >= 0); + r = decompress(compressed, csize, + (void **) &decompressed, &csize, 0); + assert_se(r == 0); + assert_se(decompressed); + assert_se(memcmp(decompressed, data, data_len) == 0); + } + + r = decompress("garbage", 7, + (void **) &decompressed, &csize, 0); + assert_se(r < 0); + + /* make sure to have the minimal lz4 compressed size */ + r = decompress("00000000\1g", 9, + (void **) &decompressed, &csize, 0); + assert_se(r < 0); + + r = decompress("\100000000g", 9, + (void **) &decompressed, &csize, 0); + assert_se(r < 0); + + explicit_bzero_safe(decompressed, MALLOC_SIZEOF_SAFE(decompressed)); +} + +_unused_ static void test_decompress_startswith(const char *compression, + compress_blob_t compress, + decompress_sw_t decompress_sw, + const char *data, + size_t data_len, + bool may_fail) { + + char *compressed; + _cleanup_free_ char *compressed1 = NULL, *compressed2 = NULL, *decompressed = NULL; + size_t csize, len; + int r; + + log_info("/* testing decompress_startswith with %s on %.20s text */", + compression, data); + +#define BUFSIZE_1 512 +#define BUFSIZE_2 20000 + + compressed = compressed1 = malloc(BUFSIZE_1); + assert_se(compressed1); + r = compress(data, data_len, compressed, BUFSIZE_1, &csize); + if (r == -ENOBUFS) { + log_info_errno(r, "compression failed: %m"); + assert_se(may_fail); + + compressed = compressed2 = malloc(BUFSIZE_2); + assert_se(compressed2); + r = compress(data, data_len, compressed, BUFSIZE_2, &csize); + } + assert_se(r >= 0); + + len = strlen(data); + + r = decompress_sw(compressed, csize, (void **) &decompressed, data, len, '\0'); + assert_se(r > 0); + r = decompress_sw(compressed, csize, (void **) &decompressed, data, len, 'w'); + assert_se(r == 0); + r = decompress_sw(compressed, csize, (void **) &decompressed, "barbarbar", 9, ' '); + assert_se(r == 0); + r = decompress_sw(compressed, csize, (void **) &decompressed, data, len - 1, data[len-1]); + assert_se(r > 0); + r = decompress_sw(compressed, csize, (void **) &decompressed, data, len - 1, 'w'); + assert_se(r == 0); + r = decompress_sw(compressed, csize, (void **) &decompressed, data, len, '\0'); + assert_se(r > 0); +} + +_unused_ static void test_decompress_startswith_short(const char *compression, + compress_blob_t compress, + decompress_sw_t decompress_sw) { + +#define TEXT "HUGE=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + + char buf[1024]; + size_t csize; + int r; + + log_info("/* %s with %s */", __func__, compression); + + r = compress(TEXT, sizeof TEXT, buf, sizeof buf, &csize); + assert_se(r >= 0); + + for (size_t i = 1; i < strlen(TEXT); i++) { + _cleanup_free_ void *buf2 = NULL; + + assert_se(buf2 = malloc(i)); + + assert_se(decompress_sw(buf, csize, &buf2, TEXT, i, TEXT[i]) == 1); + assert_se(decompress_sw(buf, csize, &buf2, TEXT, i, 'y') == 0); + } +} + +_unused_ static void test_compress_stream(const char *compression, + const char *cat, + compress_stream_t compress, + decompress_stream_t decompress, + const char *srcfile) { + + _cleanup_close_ int src = -EBADF, dst = -EBADF, dst2 = -EBADF; + _cleanup_(unlink_tempfilep) char + pattern[] = "/tmp/systemd-test.compressed.XXXXXX", + pattern2[] = "/tmp/systemd-test.compressed.XXXXXX"; + int r; + _cleanup_free_ char *cmd = NULL, *cmd2 = NULL; + struct stat st = {}; + uint64_t uncompressed_size; + + r = find_executable(cat, NULL); + if (r < 0) { + log_error_errno(r, "Skipping %s, could not find %s binary: %m", __func__, cat); + return; + } + + log_debug("/* testing %s compression */", compression); + + log_debug("/* create source from %s */", srcfile); + + assert_se((src = open(srcfile, O_RDONLY|O_CLOEXEC)) >= 0); + + log_debug("/* test compression */"); + + assert_se((dst = mkostemp_safe(pattern)) >= 0); + + assert_se(compress(src, dst, -1, &uncompressed_size) >= 0); + + if (cat) { + assert_se(asprintf(&cmd, "%s %s | diff %s -", cat, pattern, srcfile) > 0); + assert_se(system(cmd) == 0); + } + + log_debug("/* test decompression */"); + + assert_se((dst2 = mkostemp_safe(pattern2)) >= 0); + + assert_se(stat(srcfile, &st) == 0); + assert_se((uint64_t)st.st_size == uncompressed_size); + + assert_se(lseek(dst, 0, SEEK_SET) == 0); + r = decompress(dst, dst2, st.st_size); + assert_se(r == 0); + + assert_se(asprintf(&cmd2, "diff %s %s", srcfile, pattern2) > 0); + assert_se(system(cmd2) == 0); + + log_debug("/* test faulty decompression */"); + + assert_se(lseek(dst, 1, SEEK_SET) == 1); + r = decompress(dst, dst2, st.st_size); + assert_se(IN_SET(r, 0, -EBADMSG)); + + assert_se(lseek(dst, 0, SEEK_SET) == 0); + assert_se(lseek(dst2, 0, SEEK_SET) == 0); + r = decompress(dst, dst2, st.st_size - 1); + assert_se(r == -EFBIG); +} +#endif + +#if HAVE_LZ4 +static void test_lz4_decompress_partial(void) { + char buf[20000], buf2[100]; + size_t buf_size = sizeof(buf), compressed; + int r; + _cleanup_free_ char *huge = NULL; + + log_debug("/* %s */", __func__); + + assert_se(huge = malloc(HUGE_SIZE)); + memcpy(huge, "HUGE=", STRLEN("HUGE=")); + memset(&huge[STRLEN("HUGE=")], 'x', HUGE_SIZE - STRLEN("HUGE=") - 1); + huge[HUGE_SIZE - 1] = '\0'; + + r = LZ4_compress_default(huge, buf, HUGE_SIZE, buf_size); + assert_se(r >= 0); + compressed = r; + log_info("Compressed %i → %zu", HUGE_SIZE, compressed); + + r = LZ4_decompress_safe(buf, huge, r, HUGE_SIZE); + assert_se(r >= 0); + log_info("Decompressed → %i", r); + + r = LZ4_decompress_safe_partial(buf, huge, + compressed, + 12, HUGE_SIZE); + assert_se(r >= 0); + log_info("Decompressed partial %i/%i → %i", 12, HUGE_SIZE, r); + + for (size_t size = 1; size < sizeof(buf2); size++) { + /* This failed in older lz4s but works in newer ones. */ + r = LZ4_decompress_safe_partial(buf, buf2, compressed, size, size); + log_info("Decompressed partial %zu/%zu → %i (%s)", size, size, r, + r < 0 ? "bad" : "good"); + if (r >= 0 && LZ4_versionNumber() >= 10803) + /* lz4 <= 1.8.2 should fail that test, let's only check for newer ones */ + assert_se(memcmp(buf2, huge, r) == 0); + } +} +#endif + +int main(int argc, char *argv[]) { +#if HAVE_COMPRESSION + _unused_ const char text[] = + "text\0foofoofoofoo AAAA aaaaaaaaa ghost busters barbarbar FFF" + "foofoofoofoo AAAA aaaaaaaaa ghost busters barbarbar FFF"; + + /* The file to test compression on can be specified as the first argument */ + const char *srcfile = argc > 1 ? argv[1] : argv[0]; + + char data[512] = "random\0"; + + _cleanup_free_ char *huge = NULL; + + assert_se(huge = malloc(HUGE_SIZE)); + memcpy(huge, "HUGE=", STRLEN("HUGE=")); + memset(&huge[STRLEN("HUGE=")], 'x', HUGE_SIZE - STRLEN("HUGE=") - 1); + huge[HUGE_SIZE - 1] = '\0'; + + test_setup_logging(LOG_DEBUG); + + random_bytes(data + 7, sizeof(data) - 7); + +#if HAVE_XZ + test_compress_decompress("XZ", compress_blob_xz, decompress_blob_xz, + text, sizeof(text), false); + test_compress_decompress("XZ", compress_blob_xz, decompress_blob_xz, + data, sizeof(data), true); + + test_decompress_startswith("XZ", + compress_blob_xz, decompress_startswith_xz, + text, sizeof(text), false); + test_decompress_startswith("XZ", + compress_blob_xz, decompress_startswith_xz, + data, sizeof(data), true); + test_decompress_startswith("XZ", + compress_blob_xz, decompress_startswith_xz, + huge, HUGE_SIZE, true); + + test_compress_stream("XZ", "xzcat", + compress_stream_xz, decompress_stream_xz, srcfile); + + test_decompress_startswith_short("XZ", compress_blob_xz, decompress_startswith_xz); + +#else + log_info("/* XZ test skipped */"); +#endif + +#if HAVE_LZ4 + test_compress_decompress("LZ4", compress_blob_lz4, decompress_blob_lz4, + text, sizeof(text), false); + test_compress_decompress("LZ4", compress_blob_lz4, decompress_blob_lz4, + data, sizeof(data), true); + + test_decompress_startswith("LZ4", + compress_blob_lz4, decompress_startswith_lz4, + text, sizeof(text), false); + test_decompress_startswith("LZ4", + compress_blob_lz4, decompress_startswith_lz4, + data, sizeof(data), true); + test_decompress_startswith("LZ4", + compress_blob_lz4, decompress_startswith_lz4, + huge, HUGE_SIZE, true); + + test_compress_stream("LZ4", "lz4cat", + compress_stream_lz4, decompress_stream_lz4, srcfile); + + test_lz4_decompress_partial(); + + test_decompress_startswith_short("LZ4", compress_blob_lz4, decompress_startswith_lz4); + +#else + log_info("/* LZ4 test skipped */"); +#endif + +#if HAVE_ZSTD + test_compress_decompress("ZSTD", compress_blob_zstd, decompress_blob_zstd, + text, sizeof(text), false); + test_compress_decompress("ZSTD", compress_blob_zstd, decompress_blob_zstd, + data, sizeof(data), true); + + test_decompress_startswith("ZSTD", + compress_blob_zstd, decompress_startswith_zstd, + text, sizeof(text), false); + test_decompress_startswith("ZSTD", + compress_blob_zstd, decompress_startswith_zstd, + data, sizeof(data), true); + test_decompress_startswith("ZSTD", + compress_blob_zstd, decompress_startswith_zstd, + huge, HUGE_SIZE, true); + + test_compress_stream("ZSTD", "zstdcat", + compress_stream_zstd, decompress_stream_zstd, srcfile); + + test_decompress_startswith_short("ZSTD", compress_blob_zstd, decompress_startswith_zstd); +#else + log_info("/* ZSTD test skipped */"); +#endif + + return 0; +#else + return log_tests_skipped("no compression algorithm supported"); +#endif +} diff --git a/src/test/test-condition.c b/src/test/test-condition.c new file mode 100644 index 0000000..bb98761 --- /dev/null +++ b/src/test/test-condition.c @@ -0,0 +1,1496 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdio.h> +#include <sys/types.h> +#include <sys/utsname.h> +#include <unistd.h> + +#include "sd-id128.h" + +#include "alloc-util.h" +#include "apparmor-util.h" +#include "architecture.h" +#include "audit-util.h" +#include "battery-util.h" +#include "cgroup-util.h" +#include "condition.h" +#include "confidential-virt.h" +#include "cpu-set-util.h" +#include "efi-loader.h" +#include "env-util.h" +#include "errno-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "hostname-util.h" +#include "id128-util.h" +#include "ima-util.h" +#include "limits-util.h" +#include "log.h" +#include "macro.h" +#include "nulstr-util.h" +#include "os-util.h" +#include "path-util.h" +#include "process-util.h" +#include "psi-util.h" +#include "rm-rf.h" +#include "selinux-util.h" +#include "set.h" +#include "smack-util.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" +#include "tmpfile-util.h" +#include "tomoyo-util.h" +#include "uid-alloc-range.h" +#include "user-util.h" +#include "virt.h" + +TEST(condition_test_path) { + Condition *condition; + + condition = condition_new(CONDITION_PATH_EXISTS, "/bin/sh", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + condition = condition_new(CONDITION_PATH_EXISTS, "/bin/s?", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + condition = condition_new(CONDITION_PATH_EXISTS_GLOB, "/bin/s?", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + condition = condition_new(CONDITION_PATH_EXISTS_GLOB, "/bin/s?", false, true); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + condition = condition_new(CONDITION_PATH_EXISTS, "/thiscertainlywontexist", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + condition = condition_new(CONDITION_PATH_EXISTS, "/thiscertainlywontexist", false, true); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + condition = condition_new(CONDITION_PATH_IS_DIRECTORY, "/bin", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + condition = condition_new(CONDITION_DIRECTORY_NOT_EMPTY, "/bin", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + condition = condition_new(CONDITION_FILE_NOT_EMPTY, "/bin/sh", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + condition = condition_new(CONDITION_FILE_IS_EXECUTABLE, "/bin/sh", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + condition = condition_new(CONDITION_FILE_IS_EXECUTABLE, "/etc/passwd", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + condition = condition_new(CONDITION_PATH_IS_MOUNT_POINT, "/proc", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + condition = condition_new(CONDITION_PATH_IS_MOUNT_POINT, "/", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + condition = condition_new(CONDITION_PATH_IS_MOUNT_POINT, "/bin", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + condition = condition_new(CONDITION_PATH_IS_READ_WRITE, "/tmp", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + condition = condition_new(CONDITION_PATH_IS_ENCRYPTED, "/sys", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + condition = condition_new(CONDITION_PATH_IS_SYMBOLIC_LINK, "/dev/stdout", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); +} + +TEST(condition_test_control_group_hierarchy) { + Condition *condition; + int r; + + r = cg_unified(); + if (r == -ENOMEDIUM) { + log_tests_skipped("cgroup not mounted"); + return; + } + assert_se(r >= 0); + + condition = condition_new(CONDITION_CONTROL_GROUP_CONTROLLER, "v1", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == (r < CGROUP_UNIFIED_ALL)); + condition_free(condition); + + condition = condition_new(CONDITION_CONTROL_GROUP_CONTROLLER, "v2", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == (r >= CGROUP_UNIFIED_ALL)); + condition_free(condition); +} + +TEST(condition_test_control_group_controller) { + Condition *condition; + CGroupMask system_mask; + _cleanup_free_ char *controller_name = NULL; + int r; + + r = cg_unified(); + if (r == -ENOMEDIUM) { + log_tests_skipped("cgroup not mounted"); + return; + } + assert_se(r >= 0); + + /* Invalid controllers are ignored */ + condition = condition_new(CONDITION_CONTROL_GROUP_CONTROLLER, "thisisnotarealcontroller", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + condition = condition_new(CONDITION_CONTROL_GROUP_CONTROLLER, "thisisnotarealcontroller", false, true); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + assert_se(cg_mask_supported(&system_mask) >= 0); + + /* Individual valid controllers one by one */ + for (CGroupController controller = 0; controller < _CGROUP_CONTROLLER_MAX; controller++) { + const char *local_controller_name = cgroup_controller_to_string(controller); + log_info("chosen controller is '%s'", local_controller_name); + if (system_mask & CGROUP_CONTROLLER_TO_MASK(controller)) { + log_info("this controller is available"); + condition = condition_new(CONDITION_CONTROL_GROUP_CONTROLLER, local_controller_name, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + condition = condition_new(CONDITION_CONTROL_GROUP_CONTROLLER, local_controller_name, false, true); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + } else { + log_info("this controller is unavailable"); + condition = condition_new(CONDITION_CONTROL_GROUP_CONTROLLER, local_controller_name, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + condition = condition_new(CONDITION_CONTROL_GROUP_CONTROLLER, local_controller_name, false, true); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + } + } + + /* Multiple valid controllers at the same time */ + assert_se(cg_mask_to_string(system_mask, &controller_name) >= 0); + + condition = condition_new(CONDITION_CONTROL_GROUP_CONTROLLER, strempty(controller_name), false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + condition = condition_new(CONDITION_CONTROL_GROUP_CONTROLLER, strempty(controller_name), false, true); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); +} + +TEST(condition_test_ac_power) { + Condition *condition; + + condition = condition_new(CONDITION_AC_POWER, "true", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == on_ac_power()); + condition_free(condition); + + condition = condition_new(CONDITION_AC_POWER, "false", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) != on_ac_power()); + condition_free(condition); + + condition = condition_new(CONDITION_AC_POWER, "false", false, true); + assert_se(condition); + assert_se(condition_test(condition, environ) == on_ac_power()); + condition_free(condition); +} + +TEST(condition_test_host) { + _cleanup_free_ char *hostname = NULL; + Condition *condition; + sd_id128_t id; + int r; + + r = sd_id128_get_machine(&id); + if (ERRNO_IS_NEG_MACHINE_ID_UNSET(r)) + return (void) log_tests_skipped("/etc/machine-id missing"); + assert_se(r >= 0); + + condition = condition_new(CONDITION_HOST, SD_ID128_TO_STRING(id), false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + condition = condition_new(CONDITION_HOST, "garbage value jjjjjjjjjjjjjj", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + condition = condition_new(CONDITION_HOST, SD_ID128_TO_STRING(id), false, true); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + hostname = gethostname_malloc(); + assert_se(hostname); + + /* if hostname looks like an id128 then skip testing it */ + if (id128_is_valid(hostname)) + log_notice("hostname is an id128, skipping test"); + else { + condition = condition_new(CONDITION_HOST, hostname, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + } +} + +TEST(condition_test_architecture) { + Condition *condition; + const char *sa; + Architecture a; + + a = uname_architecture(); + assert_se(a >= 0); + + sa = architecture_to_string(a); + assert_se(sa); + + condition = condition_new(CONDITION_ARCHITECTURE, sa, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + condition = condition_new(CONDITION_ARCHITECTURE, "garbage value", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + condition = condition_new(CONDITION_ARCHITECTURE, sa, false, true); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); +} + +TEST(condition_test_firmware) { + Condition *condition; + + /* Empty parameter */ + condition = condition_new(CONDITION_FIRMWARE, "", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + /* uefi parameter */ + condition = condition_new(CONDITION_FIRMWARE, "uefi", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == is_efi_boot()); + condition_free(condition); +} + +TEST(condition_test_firmware_device_tree) { + Condition *condition; + bool is_device_tree_system; + + /* device-tree parameter */ + is_device_tree_system = (access("/sys/firmware/devicetree/", F_OK) == 0); + + condition = condition_new(CONDITION_FIRMWARE, "device-tree", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == is_device_tree_system); + condition_free(condition); + + /* device-tree-compatible parameter */ + if (!is_device_tree_system) { + condition = condition_new(CONDITION_FIRMWARE, "device-tree-compatible()", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + } else { + _cleanup_free_ char *dtcompat = NULL; + _cleanup_strv_free_ char **dtcompatlist = NULL; + size_t dtcompat_size; + int r; + + r = read_full_virtual_file("/proc/device-tree/compatible", &dtcompat, &dtcompat_size); + if (r < 0) { + condition = condition_new(CONDITION_FIRMWARE, "device-tree-compatible()", false, false); + assert_se(condition); + if (r == -ENOENT) + assert_se(condition_test(condition, environ) == 0); + else + assert_se(condition_test(condition, environ) < 0); + condition_free(condition); + return; + } + + dtcompatlist = strv_parse_nulstr(dtcompat, dtcompat_size); + + STRV_FOREACH(c, dtcompatlist) { + _cleanup_free_ char *expression = NULL; + + assert_se(expression = strjoin("device-tree-compatible(", *c, ")")); + condition = condition_new(CONDITION_FIRMWARE, expression, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + } + } +} + +TEST(condition_test_firmware_smbios) { + Condition *condition; + _cleanup_free_ char *bios_vendor = NULL, *bios_version = NULL; + const char *expression; + + /* smbios-field parameter */ + /* Test some malformed smbios-field arguments */ + condition = condition_new(CONDITION_FIRMWARE, "smbios-field()", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == -EINVAL); + condition_free(condition); + + condition = condition_new(CONDITION_FIRMWARE, "smbios-field(malformed)", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == -EINVAL); + condition_free(condition); + + condition = condition_new(CONDITION_FIRMWARE, "smbios-field(malformed", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == -EINVAL); + condition_free(condition); + + condition = condition_new(CONDITION_FIRMWARE, "smbios-field(malformed=)", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == -EINVAL); + condition_free(condition); + + condition = condition_new(CONDITION_FIRMWARE, "smbios-field(malformed=)", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == -EINVAL); + condition_free(condition); + + condition = condition_new(CONDITION_FIRMWARE, "smbios-field(not_existing=nothing garbage)", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == -EINVAL); + condition_free(condition); + + /* Test not existing SMBIOS field */ + condition = condition_new(CONDITION_FIRMWARE, "smbios-field(not_existing=nothing)", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + /* Test with bios_vendor, if available */ + if (read_virtual_file("/sys/class/dmi/id/bios_vendor", SIZE_MAX, &bios_vendor, NULL) <= 0) + return; + + /* remove trailing newline */ + strstrip(bios_vendor); + + /* Check if the bios_vendor contains any spaces we should quote */ + const char *quote = strchr(bios_vendor, ' ') ? "\"" : ""; + + /* Test equality / inequality using fnmatch() */ + expression = strjoina("smbios-field(bios_vendor $= ", quote, bios_vendor, quote, ")"); + condition = condition_new(CONDITION_FIRMWARE, expression, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + expression = strjoina("smbios-field(bios_vendor$=", quote, bios_vendor, quote, ")"); + condition = condition_new(CONDITION_FIRMWARE, expression, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + expression = strjoina("smbios-field(bios_vendor !$= ", quote, bios_vendor, quote, ")"); + condition = condition_new(CONDITION_FIRMWARE, expression, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + expression = strjoina("smbios-field(bios_vendor!$=", quote, bios_vendor, quote, ")"); + condition = condition_new(CONDITION_FIRMWARE, expression, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + expression = strjoina("smbios-field(bios_vendor $= ", quote, bios_vendor, "*", quote, ")"); + condition = condition_new(CONDITION_FIRMWARE, expression, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + /* Test version comparison with bios_version, if available */ + if (read_virtual_file("/sys/class/dmi/id/bios_version", SIZE_MAX, &bios_version, NULL) <= 0) + return; + + /* remove trailing newline */ + strstrip(bios_version); + + /* Check if the bios_version contains any spaces we should quote */ + quote = strchr(bios_version, ' ') ? "\"" : ""; + + expression = strjoina("smbios-field(bios_version = ", quote, bios_version, quote, ")"); + condition = condition_new(CONDITION_FIRMWARE, expression, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + expression = strjoina("smbios-field(bios_version != ", quote, bios_version, quote, ")"); + condition = condition_new(CONDITION_FIRMWARE, expression, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + expression = strjoina("smbios-field(bios_version <= ", quote, bios_version, quote, ")"); + condition = condition_new(CONDITION_FIRMWARE, expression, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + expression = strjoina("smbios-field(bios_version >= ", quote, bios_version, quote, ")"); + condition = condition_new(CONDITION_FIRMWARE, expression, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + expression = strjoina("smbios-field(bios_version < ", quote, bios_version, ".1", quote, ")"); + condition = condition_new(CONDITION_FIRMWARE, expression, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + expression = strjoina("smbios-field(bios_version > ", quote, bios_version, ".1", quote, ")"); + condition = condition_new(CONDITION_FIRMWARE, expression, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); +} + +TEST(condition_test_kernel_command_line) { + Condition *condition; + int r; + + condition = condition_new(CONDITION_KERNEL_COMMAND_LINE, "thisreallyshouldntbeonthekernelcommandline", false, false); + assert_se(condition); + r = condition_test(condition, environ); + if (ERRNO_IS_PRIVILEGE(r)) + return; + assert_se(r == 0); + condition_free(condition); + + condition = condition_new(CONDITION_KERNEL_COMMAND_LINE, "andthis=neither", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); +} + +TEST(condition_test_kernel_version) { + Condition *condition; + struct utsname u; + const char *v; + + condition = condition_new(CONDITION_KERNEL_VERSION, "*thisreallyshouldntbeinthekernelversion*", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + condition = condition_new(CONDITION_KERNEL_VERSION, "*", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + /* An artificially empty condition. It evaluates to true, but normally + * such condition cannot be created, because the condition list is reset instead. */ + condition = condition_new(CONDITION_KERNEL_VERSION, "", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + assert_se(uname(&u) >= 0); + + condition = condition_new(CONDITION_KERNEL_VERSION, u.release, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + strshorten(u.release, 4); + strcpy(strchr(u.release, 0), "*"); + + condition = condition_new(CONDITION_KERNEL_VERSION, u.release, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + /* 0.1.2 would be a very very very old kernel */ + condition = condition_new(CONDITION_KERNEL_VERSION, "> 0.1.2", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + condition = condition_new(CONDITION_KERNEL_VERSION, ">0.1.2", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + condition = condition_new(CONDITION_KERNEL_VERSION, "'>0.1.2' '<9.0.0'", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + condition = condition_new(CONDITION_KERNEL_VERSION, "> 0.1.2 < 9.0.0", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == -EINVAL); + condition_free(condition); + + condition = condition_new(CONDITION_KERNEL_VERSION, ">", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == -EINVAL); + condition_free(condition); + + condition = condition_new(CONDITION_KERNEL_VERSION, ">= 0.1.2", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + condition = condition_new(CONDITION_KERNEL_VERSION, "< 0.1.2", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + condition = condition_new(CONDITION_KERNEL_VERSION, "<= 0.1.2", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + condition = condition_new(CONDITION_KERNEL_VERSION, "= 0.1.2", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + /* 4711.8.15 is a very very very future kernel */ + condition = condition_new(CONDITION_KERNEL_VERSION, "< 4711.8.15", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + condition = condition_new(CONDITION_KERNEL_VERSION, "<= 4711.8.15", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + condition = condition_new(CONDITION_KERNEL_VERSION, "= 4711.8.15", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + condition = condition_new(CONDITION_KERNEL_VERSION, "> 4711.8.15", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + condition = condition_new(CONDITION_KERNEL_VERSION, " >= 4711.8.15", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + assert_se(uname(&u) >= 0); + + v = strjoina(">=", u.release); + condition = condition_new(CONDITION_KERNEL_VERSION, v, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + v = strjoina("= ", u.release); + condition = condition_new(CONDITION_KERNEL_VERSION, v, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + v = strjoina("<=", u.release); + condition = condition_new(CONDITION_KERNEL_VERSION, v, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + v = strjoina("> ", u.release); + condition = condition_new(CONDITION_KERNEL_VERSION, v, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + v = strjoina("< ", u.release); + condition = condition_new(CONDITION_KERNEL_VERSION, v, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); +} + +TEST(condition_test_credential) { + _cleanup_(rm_rf_physical_and_freep) char *n1 = NULL, *n2 = NULL; + _cleanup_free_ char *d1 = NULL, *d2 = NULL, *j = NULL; + Condition *condition; + + assert_se(free_and_strdup(&d1, getenv("CREDENTIALS_DIRECTORY")) >= 0); + assert_se(free_and_strdup(&d2, getenv("ENCRYPTED_CREDENTIALS_DIRECTORY")) >= 0); + + assert_se(unsetenv("CREDENTIALS_DIRECTORY") >= 0); + assert_se(unsetenv("ENCRYPTED_CREDENTIALS_DIRECTORY") >= 0); + + condition = condition_new(CONDITION_CREDENTIAL, "definitelymissing", /* trigger= */ false, /* negate= */ false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + /* invalid */ + condition = condition_new(CONDITION_CREDENTIAL, "..", /* trigger= */ false, /* negate= */ false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + assert_se(mkdtemp_malloc(NULL, &n1) >= 0); + assert_se(mkdtemp_malloc(NULL, &n2) >= 0); + + assert_se(setenv("CREDENTIALS_DIRECTORY", n1, /* overwrite= */ true) >= 0); + assert_se(setenv("ENCRYPTED_CREDENTIALS_DIRECTORY", n2, /* overwrite= */ true) >= 0); + + condition = condition_new(CONDITION_CREDENTIAL, "stillmissing", /* trigger= */ false, /* negate= */ false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + assert_se(j = path_join(n1, "existing")); + assert_se(touch(j) >= 0); + assert_se(j); + condition = condition_new(CONDITION_CREDENTIAL, "existing", /* trigger= */ false, /* negate= */ false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + free(j); + + assert_se(j = path_join(n2, "existing-encrypted")); + assert_se(touch(j) >= 0); + assert_se(j); + condition = condition_new(CONDITION_CREDENTIAL, "existing-encrypted", /* trigger= */ false, /* negate= */ false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + assert_se(set_unset_env("CREDENTIALS_DIRECTORY", d1, /* overwrite= */ true) >= 0); + assert_se(set_unset_env("ENCRYPTED_CREDENTIALS_DIRECTORY", d2, /* overwrite= */ true) >= 0); +} + +#if defined(__i386__) || defined(__x86_64__) +TEST(condition_test_cpufeature) { + Condition *condition; + + condition = condition_new(CONDITION_CPU_FEATURE, "fpu", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + condition = condition_new(CONDITION_CPU_FEATURE, "somecpufeaturethatreallydoesntmakesense", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + condition = condition_new(CONDITION_CPU_FEATURE, "a", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); +} +#endif + +TEST(condition_test_security) { + Condition *condition; + + condition = condition_new(CONDITION_SECURITY, "garbage oifdsjfoidsjoj", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + condition = condition_new(CONDITION_SECURITY, "selinux", false, true); + assert_se(condition); + assert_se(condition_test(condition, environ) != mac_selinux_use()); + condition_free(condition); + + condition = condition_new(CONDITION_SECURITY, "apparmor", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == mac_apparmor_use()); + condition_free(condition); + + condition = condition_new(CONDITION_SECURITY, "tomoyo", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == mac_tomoyo_use()); + condition_free(condition); + + condition = condition_new(CONDITION_SECURITY, "ima", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == use_ima()); + condition_free(condition); + + condition = condition_new(CONDITION_SECURITY, "smack", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == mac_smack_use()); + condition_free(condition); + + condition = condition_new(CONDITION_SECURITY, "audit", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == use_audit()); + condition_free(condition); + + condition = condition_new(CONDITION_SECURITY, "uefi-secureboot", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == is_efi_secure_boot()); + condition_free(condition); + + condition = condition_new(CONDITION_SECURITY, "cvm", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == + (detect_confidential_virtualization() != CONFIDENTIAL_VIRTUALIZATION_NONE)); + condition_free(condition); +} + +TEST(print_securities) { + log_info("------ enabled security technologies ------"); + log_info("SELinux: %s", yes_no(mac_selinux_use())); + log_info("AppArmor: %s", yes_no(mac_apparmor_use())); + log_info("Tomoyo: %s", yes_no(mac_tomoyo_use())); + log_info("IMA: %s", yes_no(use_ima())); + log_info("SMACK: %s", yes_no(mac_smack_use())); + log_info("Audit: %s", yes_no(use_audit())); + log_info("UEFI secure boot: %s", yes_no(is_efi_secure_boot())); + log_info("Confidential VM: %s", yes_no + (detect_confidential_virtualization() != CONFIDENTIAL_VIRTUALIZATION_NONE)); + log_info("-------------------------------------------"); +} + +TEST(condition_test_virtualization) { + Condition *condition; + int r; + + condition = condition_new(CONDITION_VIRTUALIZATION, "garbage oifdsjfoidsjoj", false, false); + assert_se(condition); + r = condition_test(condition, environ); + if (ERRNO_IS_PRIVILEGE(r)) + return; + log_info("ConditionVirtualization=garbage → %i", r); + assert_se(r == 0); + condition_free(condition); + + condition = condition_new(CONDITION_VIRTUALIZATION, "container", false, false); + assert_se(condition); + r = condition_test(condition, environ); + log_info("ConditionVirtualization=container → %i", r); + assert_se(r == !!detect_container()); + condition_free(condition); + + condition = condition_new(CONDITION_VIRTUALIZATION, "vm", false, false); + assert_se(condition); + r = condition_test(condition, environ); + log_info("ConditionVirtualization=vm → %i", r); + assert_se(r == (detect_vm() && !detect_container())); + condition_free(condition); + + condition = condition_new(CONDITION_VIRTUALIZATION, "private-users", false, false); + assert_se(condition); + r = condition_test(condition, environ); + log_info("ConditionVirtualization=private-users → %i", r); + assert_se(r == !!running_in_userns()); + condition_free(condition); + + NULSTR_FOREACH(virt, + "kvm\0" + "amazon\0" + "qemu\0" + "bochs\0" + "xen\0" + "uml\0" + "vmware\0" + "oracle\0" + "microsoft\0" + "zvm\0" + "parallels\0" + "bhyve\0" + "vm_other\0") { + + condition = condition_new(CONDITION_VIRTUALIZATION, virt, false, false); + assert_se(condition); + r = condition_test(condition, environ); + log_info("ConditionVirtualization=%s → %i", virt, r); + assert_se(r >= 0); + condition_free(condition); + } +} + +TEST(condition_test_user) { + Condition *condition; + char* uid; + char* username; + int r; + + condition = condition_new(CONDITION_USER, "garbage oifdsjfoidsjoj", false, false); + assert_se(condition); + r = condition_test(condition, environ); + log_info("ConditionUser=garbage → %i", r); + assert_se(r == 0); + condition_free(condition); + + assert_se(asprintf(&uid, "%"PRIu32, UINT32_C(0xFFFF)) > 0); + condition = condition_new(CONDITION_USER, uid, false, false); + assert_se(condition); + r = condition_test(condition, environ); + log_info("ConditionUser=%s → %i", uid, r); + assert_se(r == 0); + condition_free(condition); + free(uid); + + assert_se(asprintf(&uid, "%u", (unsigned)getuid()) > 0); + condition = condition_new(CONDITION_USER, uid, false, false); + assert_se(condition); + r = condition_test(condition, environ); + log_info("ConditionUser=%s → %i", uid, r); + assert_se(r > 0); + condition_free(condition); + free(uid); + + assert_se(asprintf(&uid, "%u", (unsigned)getuid()+1) > 0); + condition = condition_new(CONDITION_USER, uid, false, false); + assert_se(condition); + r = condition_test(condition, environ); + log_info("ConditionUser=%s → %i", uid, r); + assert_se(r == 0); + condition_free(condition); + free(uid); + + username = getusername_malloc(); + assert_se(username); + condition = condition_new(CONDITION_USER, username, false, false); + assert_se(condition); + r = condition_test(condition, environ); + log_info("ConditionUser=%s → %i", username, r); + assert_se(r > 0); + condition_free(condition); + free(username); + + username = (char*)(geteuid() == 0 ? NOBODY_USER_NAME : "root"); + condition = condition_new(CONDITION_USER, username, false, false); + assert_se(condition); + r = condition_test(condition, environ); + log_info("ConditionUser=%s → %i", username, r); + assert_se(r == 0); + condition_free(condition); + + condition = condition_new(CONDITION_USER, "@system", false, false); + assert_se(condition); + r = condition_test(condition, environ); + log_info("ConditionUser=@system → %i", r); + if (uid_is_system(getuid()) || uid_is_system(geteuid())) + assert_se(r > 0); + else + assert_se(r == 0); + condition_free(condition); +} + +TEST(condition_test_group) { + Condition *condition; + char* gid; + char* groupname; + gid_t *gids, max_gid; + int ngroups_max, ngroups, r, i; + + assert_se(0 < asprintf(&gid, "%u", UINT32_C(0xFFFF))); + condition = condition_new(CONDITION_GROUP, gid, false, false); + assert_se(condition); + r = condition_test(condition, environ); + log_info("ConditionGroup=%s → %i", gid, r); + assert_se(r == 0); + condition_free(condition); + free(gid); + + assert_se(0 < asprintf(&gid, "%u", getgid())); + condition = condition_new(CONDITION_GROUP, gid, false, false); + assert_se(condition); + r = condition_test(condition, environ); + log_info("ConditionGroup=%s → %i", gid, r); + assert_se(r > 0); + condition_free(condition); + free(gid); + + ngroups_max = sysconf(_SC_NGROUPS_MAX); + assert_se(ngroups_max > 0); + + gids = newa(gid_t, ngroups_max); + + ngroups = getgroups(ngroups_max, gids); + assert_se(ngroups >= 0); + + max_gid = getgid(); + for (i = 0; i < ngroups; i++) { + _cleanup_free_ char *name = NULL; + + assert_se(0 < asprintf(&gid, "%u", gids[i])); + condition = condition_new(CONDITION_GROUP, gid, false, false); + assert_se(condition); + r = condition_test(condition, environ); + log_info("ConditionGroup=%s → %i", gid, r); + assert_se(r > 0); + condition_free(condition); + free(gid); + max_gid = gids[i] > max_gid ? gids[i] : max_gid; + + name = gid_to_name(gids[i]); + assert_se(name); + if (STR_IN_SET(name, "sbuild", "buildd")) + return; /* Debian package build in chroot, groupnames won't match, skip */ + condition = condition_new(CONDITION_GROUP, name, false, false); + assert_se(condition); + r = condition_test(condition, environ); + log_info("ConditionGroup=%s → %i", name, r); + assert_se(r > 0); + condition_free(condition); + max_gid = gids[i] > max_gid ? gids[i] : max_gid; + } + + assert_se(0 < asprintf(&gid, "%u", max_gid+1)); + condition = condition_new(CONDITION_GROUP, gid, false, false); + assert_se(condition); + r = condition_test(condition, environ); + log_info("ConditionGroup=%s → %i", gid, r); + assert_se(r == 0); + condition_free(condition); + free(gid); + + groupname = (char*)(getegid() == 0 ? NOBODY_GROUP_NAME : "root"); + condition = condition_new(CONDITION_GROUP, groupname, false, false); + assert_se(condition); + r = condition_test(condition, environ); + log_info("ConditionGroup=%s → %i", groupname, r); + assert_se(r == 0); + condition_free(condition); +} + +static void test_condition_test_cpus_one(const char *s, bool result) { + Condition *condition; + int r; + + log_debug("%s=%s", condition_type_to_string(CONDITION_CPUS), s); + + condition = condition_new(CONDITION_CPUS, s, false, false); + assert_se(condition); + + r = condition_test(condition, environ); + assert_se(r >= 0); + assert_se(r == result); + condition_free(condition); +} + +TEST(condition_test_cpus) { + _cleanup_free_ char *t = NULL; + int cpus; + + cpus = cpus_in_affinity_mask(); + assert_se(cpus >= 0); + + test_condition_test_cpus_one("> 0", true); + test_condition_test_cpus_one(">= 0", true); + test_condition_test_cpus_one("!= 0", true); + test_condition_test_cpus_one("<= 0", false); + test_condition_test_cpus_one("< 0", false); + test_condition_test_cpus_one("= 0", false); + + test_condition_test_cpus_one("> 100000", false); + test_condition_test_cpus_one("= 100000", false); + test_condition_test_cpus_one(">= 100000", false); + test_condition_test_cpus_one("< 100000", true); + test_condition_test_cpus_one("!= 100000", true); + test_condition_test_cpus_one("<= 100000", true); + + assert_se(asprintf(&t, "= %i", cpus) >= 0); + test_condition_test_cpus_one(t, true); + t = mfree(t); + + assert_se(asprintf(&t, "<= %i", cpus) >= 0); + test_condition_test_cpus_one(t, true); + t = mfree(t); + + assert_se(asprintf(&t, ">= %i", cpus) >= 0); + test_condition_test_cpus_one(t, true); + t = mfree(t); + + assert_se(asprintf(&t, "!= %i", cpus) >= 0); + test_condition_test_cpus_one(t, false); + t = mfree(t); + + assert_se(asprintf(&t, "< %i", cpus) >= 0); + test_condition_test_cpus_one(t, false); + t = mfree(t); + + assert_se(asprintf(&t, "> %i", cpus) >= 0); + test_condition_test_cpus_one(t, false); + t = mfree(t); +} + +static void test_condition_test_memory_one(const char *s, bool result) { + Condition *condition; + int r; + + log_debug("%s=%s", condition_type_to_string(CONDITION_MEMORY), s); + + condition = condition_new(CONDITION_MEMORY, s, false, false); + assert_se(condition); + + r = condition_test(condition, environ); + assert_se(r >= 0); + assert_se(r == result); + condition_free(condition); +} + +TEST(condition_test_memory) { + _cleanup_free_ char *t = NULL; + uint64_t memory; + + memory = physical_memory(); + + test_condition_test_memory_one("> 0", true); + test_condition_test_memory_one(">= 0", true); + test_condition_test_memory_one("!= 0", true); + test_condition_test_memory_one("<= 0", false); + test_condition_test_memory_one("< 0", false); + test_condition_test_memory_one("= 0", false); + + test_condition_test_memory_one("> 18446744073709547520", false); + test_condition_test_memory_one("= 18446744073709547520", false); + test_condition_test_memory_one(">= 18446744073709547520", false); + test_condition_test_memory_one("< 18446744073709547520", true); + test_condition_test_memory_one("!= 18446744073709547520", true); + test_condition_test_memory_one("<= 18446744073709547520", true); + + test_condition_test_memory_one("> 100T", false); + test_condition_test_memory_one("= 100T", false); + test_condition_test_memory_one(">= 100T", false); + test_condition_test_memory_one("< 100T", true); + test_condition_test_memory_one("!= 100T", true); + test_condition_test_memory_one("<= 100T", true); + + test_condition_test_memory_one("> 100 T", false); + test_condition_test_memory_one("= 100 T", false); + test_condition_test_memory_one(">= 100 T", false); + test_condition_test_memory_one("< 100 T", true); + test_condition_test_memory_one("!= 100 T", true); + test_condition_test_memory_one("<= 100 T", true); + + test_condition_test_memory_one("> 100 T 1 G", false); + test_condition_test_memory_one("= 100 T 1 G", false); + test_condition_test_memory_one(">= 100 T 1 G", false); + test_condition_test_memory_one("< 100 T 1 G", true); + test_condition_test_memory_one("!= 100 T 1 G", true); + test_condition_test_memory_one("<= 100 T 1 G", true); + + assert_se(asprintf(&t, "= %" PRIu64, memory) >= 0); + test_condition_test_memory_one(t, true); + t = mfree(t); + + assert_se(asprintf(&t, "<= %" PRIu64, memory) >= 0); + test_condition_test_memory_one(t, true); + t = mfree(t); + + assert_se(asprintf(&t, ">= %" PRIu64, memory) >= 0); + test_condition_test_memory_one(t, true); + t = mfree(t); + + assert_se(asprintf(&t, "!= %" PRIu64, memory) >= 0); + test_condition_test_memory_one(t, false); + t = mfree(t); + + assert_se(asprintf(&t, "< %" PRIu64, memory) >= 0); + test_condition_test_memory_one(t, false); + t = mfree(t); + + assert_se(asprintf(&t, "> %" PRIu64, memory) >= 0); + test_condition_test_memory_one(t, false); + t = mfree(t); +} + +static void test_condition_test_environment_one(const char *s, bool result) { + Condition *condition; + int r; + + log_debug("%s=%s", condition_type_to_string(CONDITION_ENVIRONMENT), s); + + condition = condition_new(CONDITION_ENVIRONMENT, s, false, false); + assert_se(condition); + + r = condition_test(condition, environ); + assert_se(r >= 0); + assert_se(r == result); + condition_free(condition); +} + +TEST(condition_test_environment) { + assert_se(setenv("EXISTINGENVVAR", "foo", false) >= 0); + + test_condition_test_environment_one("MISSINGENVVAR", false); + test_condition_test_environment_one("MISSINGENVVAR=foo", false); + test_condition_test_environment_one("MISSINGENVVAR=", false); + + test_condition_test_environment_one("EXISTINGENVVAR", true); + test_condition_test_environment_one("EXISTINGENVVAR=foo", true); + test_condition_test_environment_one("EXISTINGENVVAR=bar", false); + test_condition_test_environment_one("EXISTINGENVVAR=", false); +} + +TEST(condition_test_os_release) { + _cleanup_strv_free_ char **os_release_pairs = NULL; + _cleanup_free_ char *version_id = NULL; + const char *key_value_pair; + Condition *condition; + + /* Should not happen, but it's a test so we don't know the environment. */ + if (load_os_release_pairs(NULL, &os_release_pairs) < 0) + return; + if (strv_length(os_release_pairs) < 2) + return; + + condition = condition_new(CONDITION_OS_RELEASE, "_THISHOPEFULLYWONTEXIST=01234 56789", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + condition = condition_new(CONDITION_OS_RELEASE, "WRONG FORMAT", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == -EINVAL); + condition_free(condition); + + condition = condition_new(CONDITION_OS_RELEASE, "WRONG!<>=FORMAT", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == -EINVAL); + condition_free(condition); + + condition = condition_new(CONDITION_OS_RELEASE, "WRONG FORMAT=", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == -EINVAL); + condition_free(condition); + + condition = condition_new(CONDITION_OS_RELEASE, "WRONG =FORMAT", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == -EINVAL); + condition_free(condition); + + condition = condition_new(CONDITION_OS_RELEASE, "WRONG = FORMAT", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == -EINVAL); + condition_free(condition); + + condition = condition_new(CONDITION_OS_RELEASE, "WRONGFORMAT= ", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == -EINVAL); + condition_free(condition); + + condition = condition_new(CONDITION_OS_RELEASE, "WRO NG=FORMAT", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == -EINVAL); + condition_free(condition); + + condition = condition_new(CONDITION_OS_RELEASE, "", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + /* load_os_release_pairs() removes quotes, we have to add them back, + * otherwise we get a string: "PRETTY_NAME=Debian GNU/Linux 10 (buster)" + * which is wrong, as the value is not quoted anymore. */ + const char *quote = strchr(os_release_pairs[1], ' ') ? "\"" : ""; + key_value_pair = strjoina(os_release_pairs[0], "=", quote, os_release_pairs[1], quote); + condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + key_value_pair = strjoina(os_release_pairs[0], "!=", quote, os_release_pairs[1], quote); + condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + /* Test fnmatch() operators */ + key_value_pair = strjoina(os_release_pairs[0], "$=", quote, os_release_pairs[1], quote); + condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + key_value_pair = strjoina(os_release_pairs[0], "!$=", quote, os_release_pairs[1], quote); + condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + /* Some distros (eg: Arch) do not set VERSION_ID */ + if (parse_os_release(NULL, "VERSION_ID", &version_id) <= 0) + return; + + key_value_pair = strjoina("VERSION_ID", "=", version_id); + condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + key_value_pair = strjoina("VERSION_ID", "!=", version_id); + condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + key_value_pair = strjoina("VERSION_ID", "<=", version_id); + condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + key_value_pair = strjoina("VERSION_ID", ">=", version_id); + condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + key_value_pair = strjoina("VERSION_ID", "<", version_id, ".1"); + condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + key_value_pair = strjoina("VERSION_ID", ">", version_id, ".1"); + condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + key_value_pair = strjoina("VERSION_ID", "=", version_id, " ", os_release_pairs[0], "=", quote, os_release_pairs[1], quote); + condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + key_value_pair = strjoina("VERSION_ID", "!=", version_id, " ", os_release_pairs[0], "=", quote, os_release_pairs[1], quote); + condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + key_value_pair = strjoina("VERSION_ID", "=", version_id, " ", os_release_pairs[0], "!=", quote, os_release_pairs[1], quote); + condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + key_value_pair = strjoina("VERSION_ID", "!=", version_id, " ", os_release_pairs[0], "!=", quote, os_release_pairs[1], quote); + condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) == 0); + condition_free(condition); + + key_value_pair = strjoina("VERSION_ID", "<", version_id, ".1", " ", os_release_pairs[0], "=", quote, os_release_pairs[1], quote); + condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); +} + +TEST(condition_test_psi) { + Condition *condition; + CGroupMask mask; + int r; + + if (!is_pressure_supported()) + return (void) log_notice("Pressure Stall Information (PSI) is not supported, skipping %s", __func__); + + condition = condition_new(CONDITION_MEMORY_PRESSURE, "", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) < 0); + condition_free(condition); + + condition = condition_new(CONDITION_CPU_PRESSURE, "sbarabau", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) < 0); + condition_free(condition); + + condition = condition_new(CONDITION_MEMORY_PRESSURE, "10%sbarabau", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) < 0); + condition_free(condition); + + condition = condition_new(CONDITION_CPU_PRESSURE, "10% sbarabau", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) < 0); + condition_free(condition); + + condition = condition_new(CONDITION_CPU_PRESSURE, "-10", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) < 0); + condition_free(condition); + + condition = condition_new(CONDITION_CPU_PRESSURE, "10%/10min", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) < 0); + condition_free(condition); + + condition = condition_new(CONDITION_CPU_PRESSURE, "10min/10%", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) < 0); + condition_free(condition); + + condition = condition_new(CONDITION_CPU_PRESSURE, "10% 5min", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) < 0); + condition_free(condition); + + condition = condition_new(CONDITION_CPU_PRESSURE, "/5min", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) < 0); + condition_free(condition); + + condition = condition_new(CONDITION_IO_PRESSURE, "10s / ", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) < 0); + condition_free(condition); + + condition = condition_new(CONDITION_MEMORY_PRESSURE, "100%", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) >= 0); + condition_free(condition); + + condition = condition_new(CONDITION_MEMORY_PRESSURE, "0%", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) >= 0); + condition_free(condition); + + condition = condition_new(CONDITION_MEMORY_PRESSURE, "0.0%", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) >= 0); + condition_free(condition); + + condition = condition_new(CONDITION_CPU_PRESSURE, "100%", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) >= 0); + condition_free(condition); + + condition = condition_new(CONDITION_CPU_PRESSURE, "0%", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) >= 0); + condition_free(condition); + + condition = condition_new(CONDITION_CPU_PRESSURE, "0.0%", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) >= 0); + condition_free(condition); + + condition = condition_new(CONDITION_CPU_PRESSURE, "0.01%", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) >= 0); + condition_free(condition); + + condition = condition_new(CONDITION_CPU_PRESSURE, "0.0%/10sec", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) >= 0); + condition_free(condition); + + condition = condition_new(CONDITION_CPU_PRESSURE, "100.0% / 1min", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) >= 0); + condition_free(condition); + + condition = condition_new(CONDITION_IO_PRESSURE, "50.0% / 1min", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) >= 0); + condition_free(condition); + + r = cg_all_unified(); + if (r < 0) + return (void) log_notice("Failed to determine whether the unified cgroups hierarchy is used, skipping %s", __func__); + if (r == 0) + return (void) log_notice("Requires the unified cgroups hierarchy, skipping %s", __func__); + + if (cg_mask_supported(&mask) < 0) + return (void) log_notice("Failed to get supported cgroup controllers, skipping %s", __func__); + + if (!FLAGS_SET(mask, CGROUP_MASK_MEMORY)) + return (void) log_notice("Requires the cgroup memory controller, skipping %s", __func__); + + if (!FLAGS_SET(mask, CGROUP_MASK_CPU)) + return (void) log_notice("Requires the cgroup CPU controller, skipping %s", __func__); + + condition = condition_new(CONDITION_MEMORY_PRESSURE, " : / ", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) < 0); + condition_free(condition); + + condition = condition_new(CONDITION_CPU_PRESSURE, "hopefullythisisnotarealone.slice:100% / 10sec", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) > 0); + condition_free(condition); + + condition = condition_new(CONDITION_CPU_PRESSURE, "-.slice:100.0% / 1min", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) >= 0); + condition_free(condition); + + condition = condition_new(CONDITION_MEMORY_PRESSURE, "-.slice:0.0%/5min", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) >= 0); + condition_free(condition); + + condition = condition_new(CONDITION_MEMORY_PRESSURE, "-.slice:100.0%", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) >= 0); + condition_free(condition); + + condition = condition_new(CONDITION_IO_PRESSURE, "-.slice:0.0%", false, false); + assert_se(condition); + assert_se(condition_test(condition, environ) >= 0); + condition_free(condition); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-conf-files.c b/src/test/test-conf-files.c new file mode 100644 index 0000000..4253490 --- /dev/null +++ b/src/test/test-conf-files.c @@ -0,0 +1,218 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2014 Michael Marineau +***/ + +#include <stdarg.h> +#include <stdio.h> + +#include "alloc-util.h" +#include "conf-files.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "macro.h" +#include "mkdir.h" +#include "path-util.h" +#include "rm-rf.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" +#include "tmpfile-util.h" + +TEST(conf_files_list) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int tfd = -EBADF; + _cleanup_strv_free_ char **result = NULL; + const char *search1, *search2, *search1_a, *search1_b, *search1_c, *search2_aa; + + tfd = mkdtemp_open("/tmp/test-conf-files-XXXXXX", O_PATH, &t); + assert(tfd >= 0); + + assert_se(mkdirat(tfd, "dir1", 0755) >= 0); + assert_se(mkdirat(tfd, "dir2", 0755) >= 0); + + search1 = strjoina(t, "/dir1/"); + search2 = strjoina(t, "/dir2/"); + + FOREACH_STRING(p, "a.conf", "b.conf", "c.foo") { + _cleanup_free_ char *path = NULL; + + assert_se(path = path_join(search1, p)); + assert_se(write_string_file(path, "foobar", WRITE_STRING_FILE_CREATE) >= 0); + } + + assert_se(symlinkat("/dev/null", tfd, "dir1/m.conf") >= 0); + + FOREACH_STRING(p, "a.conf", "aa.conf", "m.conf") { + _cleanup_free_ char *path = NULL; + + assert_se(path = path_join(search2, p)); + assert_se(write_string_file(path, "hogehoge", WRITE_STRING_FILE_CREATE) >= 0); + } + + search1_a = strjoina(search1, "a.conf"); + search1_b = strjoina(search1, "b.conf"); + search1_c = strjoina(search1, "c.foo"); + search2_aa = strjoina(search2, "aa.conf"); + + /* search dir1 without suffix */ + assert_se(conf_files_list(&result, NULL, NULL, CONF_FILES_FILTER_MASKED, search1) >= 0); + strv_print(result); + assert_se(strv_equal(result, STRV_MAKE(search1_a, search1_b, search1_c))); + + result = strv_free(result); + + assert_se(conf_files_list(&result, NULL, t, CONF_FILES_FILTER_MASKED, "/dir1/") >= 0); + strv_print(result); + assert_se(strv_equal(result, STRV_MAKE(search1_a, search1_b, search1_c))); + + result = strv_free(result); + + assert_se(conf_files_list_at(&result, NULL, AT_FDCWD, CONF_FILES_FILTER_MASKED, search1) >= 0); + strv_print(result); + assert_se(strv_equal(result, STRV_MAKE(search1_a, search1_b, search1_c))); + + result = strv_free(result); + + assert_se(conf_files_list_at(&result, NULL, tfd, CONF_FILES_FILTER_MASKED, "/dir1/") >= 0); + strv_print(result); + assert_se(strv_equal(result, STRV_MAKE("dir1/a.conf", "dir1/b.conf", "dir1/c.foo"))); + + result = strv_free(result); + + /* search dir1 with suffix */ + assert_se(conf_files_list(&result, ".conf", NULL, CONF_FILES_FILTER_MASKED, search1) >= 0); + strv_print(result); + assert_se(strv_equal(result, STRV_MAKE(search1_a, search1_b))); + + result = strv_free(result); + + assert_se(conf_files_list(&result, ".conf", t, CONF_FILES_FILTER_MASKED, "/dir1/") >= 0); + strv_print(result); + assert_se(strv_equal(result, STRV_MAKE(search1_a, search1_b))); + + result = strv_free(result); + + assert_se(conf_files_list_at(&result, ".conf", AT_FDCWD, CONF_FILES_FILTER_MASKED, search1) >= 0); + strv_print(result); + assert_se(strv_equal(result, STRV_MAKE(search1_a, search1_b))); + + result = strv_free(result); + + assert_se(conf_files_list_at(&result, ".conf", tfd, CONF_FILES_FILTER_MASKED, "/dir1/") >= 0); + strv_print(result); + assert_se(strv_equal(result, STRV_MAKE("dir1/a.conf", "dir1/b.conf"))); + + result = strv_free(result); + + /* search two dirs */ + assert_se(conf_files_list_strv(&result, ".conf", NULL, CONF_FILES_FILTER_MASKED, STRV_MAKE_CONST(search1, search2)) >= 0); + strv_print(result); + assert_se(strv_equal(result, STRV_MAKE(search1_a, search2_aa, search1_b))); + + result = strv_free(result); + + assert_se(conf_files_list_strv(&result, ".conf", t, CONF_FILES_FILTER_MASKED, STRV_MAKE_CONST("/dir1/", "/dir2/")) >= 0); + strv_print(result); + assert_se(strv_equal(result, STRV_MAKE(search1_a, search2_aa, search1_b))); + + result = strv_free(result); + + assert_se(conf_files_list_strv_at(&result, ".conf", AT_FDCWD, CONF_FILES_FILTER_MASKED, STRV_MAKE_CONST(search1, search2)) >= 0); + strv_print(result); + assert_se(strv_equal(result, STRV_MAKE(search1_a, search2_aa, search1_b))); + + result = strv_free(result); + + assert_se(conf_files_list_strv_at(&result, ".conf", tfd, CONF_FILES_FILTER_MASKED, STRV_MAKE_CONST("/dir1/", "/dir2/")) >= 0); + strv_print(result); + assert_se(strv_equal(result, STRV_MAKE("dir1/a.conf", "dir2/aa.conf", "dir1/b.conf"))); + + result = strv_free(result); + + /* filename only */ + assert_se(conf_files_list_strv(&result, ".conf", NULL, CONF_FILES_FILTER_MASKED | CONF_FILES_BASENAME, STRV_MAKE_CONST(search1, search2)) >= 0); + strv_print(result); + assert_se(strv_equal(result, STRV_MAKE("a.conf", "aa.conf", "b.conf"))); + + result = strv_free(result); + + assert_se(conf_files_list_strv(&result, ".conf", t, CONF_FILES_FILTER_MASKED | CONF_FILES_BASENAME, STRV_MAKE_CONST("/dir1/", "/dir2/")) >= 0); + strv_print(result); + assert_se(strv_equal(result, STRV_MAKE("a.conf", "aa.conf", "b.conf"))); + + result = strv_free(result); + + assert_se(conf_files_list_strv_at(&result, ".conf", AT_FDCWD, CONF_FILES_FILTER_MASKED | CONF_FILES_BASENAME, STRV_MAKE_CONST(search1, search2)) >= 0); + strv_print(result); + assert_se(strv_equal(result, STRV_MAKE("a.conf", "aa.conf", "b.conf"))); + + result = strv_free(result); + + assert_se(conf_files_list_strv_at(&result, ".conf", tfd, CONF_FILES_FILTER_MASKED | CONF_FILES_BASENAME, STRV_MAKE_CONST("/dir1/", "/dir2/")) >= 0); + strv_print(result); + assert_se(strv_equal(result, STRV_MAKE("a.conf", "aa.conf", "b.conf"))); +} + +static void test_conf_files_insert_one(const char *root) { + _cleanup_strv_free_ char **s = NULL; + + log_info("/* %s root=%s */", __func__, strempty(root)); + + char **dirs = STRV_MAKE("/dir1", "/dir2", "/dir3"); + + _cleanup_free_ const char + *foo1 = path_join(root, "/dir1/foo.conf"), + *foo2 = path_join(root, "/dir2/foo.conf"), + *bar2 = path_join(root, "/dir2/bar.conf"), + *zzz3 = path_join(root, "/dir3/zzz.conf"), + *whatever = path_join(root, "/whatever.conf"); + + assert_se(conf_files_insert(&s, root, dirs, "/dir2/foo.conf") == 0); + assert_se(strv_equal(s, STRV_MAKE(foo2))); + + /* The same file again, https://github.com/systemd/systemd/issues/11124 */ + assert_se(conf_files_insert(&s, root, dirs, "/dir2/foo.conf") == 0); + assert_se(strv_equal(s, STRV_MAKE(foo2))); + + /* Lower priority → new entry is ignored */ + assert_se(conf_files_insert(&s, root, dirs, "/dir3/foo.conf") == 0); + assert_se(strv_equal(s, STRV_MAKE(foo2))); + + /* Higher priority → new entry replaces */ + assert_se(conf_files_insert(&s, root, dirs, "/dir1/foo.conf") == 0); + assert_se(strv_equal(s, STRV_MAKE(foo1))); + + /* Earlier basename */ + assert_se(conf_files_insert(&s, root, dirs, "/dir2/bar.conf") == 0); + assert_se(strv_equal(s, STRV_MAKE(bar2, foo1))); + + /* Later basename */ + assert_se(conf_files_insert(&s, root, dirs, "/dir3/zzz.conf") == 0); + assert_se(strv_equal(s, STRV_MAKE(bar2, foo1, zzz3))); + + /* All lower priority → all ignored */ + assert_se(conf_files_insert(&s, root, dirs, "/dir3/zzz.conf") == 0); + assert_se(conf_files_insert(&s, root, dirs, "/dir2/bar.conf") == 0); + assert_se(conf_files_insert(&s, root, dirs, "/dir3/bar.conf") == 0); + assert_se(conf_files_insert(&s, root, dirs, "/dir2/foo.conf") == 0); + assert_se(strv_equal(s, STRV_MAKE(bar2, foo1, zzz3))); + + /* Two entries that don't match any of the directories, but match basename */ + assert_se(conf_files_insert(&s, root, dirs, "/dir4/zzz.conf") == 0); + assert_se(conf_files_insert(&s, root, dirs, "/zzz.conf") == 0); + assert_se(strv_equal(s, STRV_MAKE(bar2, foo1, zzz3))); + + /* An entry that doesn't match any of the directories, no match at all */ + assert_se(conf_files_insert(&s, root, dirs, "/whatever.conf") == 0); + assert_se(strv_equal(s, STRV_MAKE(bar2, foo1, whatever, zzz3))); +} + +TEST(conf_files_insert) { + test_conf_files_insert_one(NULL); + test_conf_files_insert_one("/root"); + test_conf_files_insert_one("/root/"); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-conf-parser.c b/src/test/test-conf-parser.c new file mode 100644 index 0000000..0acb413 --- /dev/null +++ b/src/test/test-conf-parser.c @@ -0,0 +1,393 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "conf-parser.h" +#include "fd-util.h" +#include "fs-util.h" +#include "log.h" +#include "macro.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" +#include "tmpfile-util.h" + +static void test_config_parse_path_one(const char *rvalue, const char *expected) { + _cleanup_free_ char *path = NULL; + + assert_se(config_parse_path("unit", "filename", 1, "section", 1, "lvalue", 0, rvalue, &path, NULL) >= 0); + assert_se(streq_ptr(expected, path)); +} + +static void test_config_parse_log_level_one(const char *rvalue, int expected) { + int log_level = 0; + + assert_se(config_parse_log_level("unit", "filename", 1, "section", 1, "lvalue", 0, rvalue, &log_level, NULL) >= 0); + assert_se(expected == log_level); +} + +static void test_config_parse_log_facility_one(const char *rvalue, int expected) { + int log_facility = 0; + + assert_se(config_parse_log_facility("unit", "filename", 1, "section", 1, "lvalue", 0, rvalue, &log_facility, NULL) >= 0); + assert_se(expected == log_facility); +} + +static void test_config_parse_iec_size_one(const char *rvalue, size_t expected) { + size_t iec_size = 0; + + assert_se(config_parse_iec_size("unit", "filename", 1, "section", 1, "lvalue", 0, rvalue, &iec_size, NULL) >= 0); + assert_se(expected == iec_size); +} + +static void test_config_parse_si_uint64_one(const char *rvalue, uint64_t expected) { + uint64_t si_uint64 = 0; + + assert_se(config_parse_si_uint64("unit", "filename", 1, "section", 1, "lvalue", 0, rvalue, &si_uint64, NULL) >= 0); + assert_se(expected == si_uint64); +} + +static void test_config_parse_int_one(const char *rvalue, int expected) { + int v = -1; + + assert_se(config_parse_int("unit", "filename", 1, "section", 1, "lvalue", 0, rvalue, &v, NULL) >= 0); + assert_se(expected == v); +} + +static void test_config_parse_unsigned_one(const char *rvalue, unsigned expected) { + unsigned v = 0; + + assert_se(config_parse_unsigned("unit", "filename", 1, "section", 1, "lvalue", 0, rvalue, &v, NULL) >= 0); + assert_se(expected == v); +} + +static void test_config_parse_strv_one(const char *rvalue, char **expected) { + _cleanup_strv_free_ char **strv = NULL; + + assert_se(config_parse_strv("unit", "filename", 1, "section", 1, "lvalue", 0, rvalue, &strv, NULL) >= 0); + assert_se(strv_equal(expected, strv)); +} + +static void test_config_parse_mode_one(const char *rvalue, mode_t expected) { + mode_t v = 0; + + assert_se(config_parse_mode("unit", "filename", 1, "section", 1, "lvalue", 0, rvalue, &v, NULL) >= 0); + assert_se(expected == v); +} + +static void test_config_parse_sec_one(const char *rvalue, usec_t expected) { + usec_t v = 0; + + assert_se(config_parse_sec("unit", "filename", 1, "section", 1, "lvalue", 0, rvalue, &v, NULL) >= 0); + assert_se(expected == v); +} + +static void test_config_parse_nsec_one(const char *rvalue, nsec_t expected) { + nsec_t v = 0; + + assert_se(config_parse_nsec("unit", "filename", 1, "nsection", 1, "lvalue", 0, rvalue, &v, NULL) >= 0); + assert_se(expected == v); +} + +TEST(config_parse_path) { + test_config_parse_path_one("/path", "/path"); + test_config_parse_path_one("/path//////////", "/path"); + test_config_parse_path_one("///path/foo///bar////bar//", "/path/foo/bar/bar"); + test_config_parse_path_one("/path//./////hogehoge///.", "/path/hogehoge"); + test_config_parse_path_one("/path/\xc3\x80", "/path/\xc3\x80"); + + test_config_parse_path_one("not_absolute/path", NULL); + test_config_parse_path_one("/path/\xc3\x7f", NULL); +} + +TEST(config_parse_log_level) { + test_config_parse_log_level_one("debug", LOG_DEBUG); + test_config_parse_log_level_one("info", LOG_INFO); + + test_config_parse_log_level_one("garbage", 0); +} + +TEST(config_parse_log_facility) { + test_config_parse_log_facility_one("mail", LOG_MAIL); + test_config_parse_log_facility_one("user", LOG_USER); + + test_config_parse_log_facility_one("garbage", 0); +} + +TEST(config_parse_iec_size) { + test_config_parse_iec_size_one("1024", 1024); + test_config_parse_iec_size_one("2K", 2048); + test_config_parse_iec_size_one("10M", 10 * 1024 * 1024); + test_config_parse_iec_size_one("1G", 1 * 1024 * 1024 * 1024); + test_config_parse_iec_size_one("0G", 0); + test_config_parse_iec_size_one("0", 0); + + test_config_parse_iec_size_one("-982", 0); + test_config_parse_iec_size_one("49874444198739873000000G", 0); + test_config_parse_iec_size_one("garbage", 0); +} + +TEST(config_parse_si_uint64) { + test_config_parse_si_uint64_one("1024", 1024); + test_config_parse_si_uint64_one("2K", 2000); + test_config_parse_si_uint64_one("10M", 10 * 1000 * 1000); + test_config_parse_si_uint64_one("1G", 1 * 1000 * 1000 * 1000); + test_config_parse_si_uint64_one("0G", 0); + test_config_parse_si_uint64_one("0", 0); + + test_config_parse_si_uint64_one("-982", 0); + test_config_parse_si_uint64_one("49874444198739873000000G", 0); + test_config_parse_si_uint64_one("garbage", 0); +} + +TEST(config_parse_int) { + test_config_parse_int_one("1024", 1024); + test_config_parse_int_one("-1024", -1024); + test_config_parse_int_one("0", 0); + + test_config_parse_int_one("99999999999999999999999999999999999999999999999999999999", -1); + test_config_parse_int_one("-99999999999999999999999999999999999999999999999999999999", -1); + test_config_parse_int_one("1G", -1); + test_config_parse_int_one("garbage", -1); +} + +TEST(config_parse_unsigned) { + test_config_parse_unsigned_one("10241024", 10241024); + test_config_parse_unsigned_one("1024", 1024); + test_config_parse_unsigned_one("0", 0); + + test_config_parse_unsigned_one("99999999999999999999999999999999999999999999999999999999", 0); + test_config_parse_unsigned_one("1G", 0); + test_config_parse_unsigned_one("garbage", 0); + test_config_parse_unsigned_one("1000garbage", 0); +} + +TEST(config_parse_strv) { + test_config_parse_strv_one("", STRV_MAKE_EMPTY); + test_config_parse_strv_one("foo", STRV_MAKE("foo")); + test_config_parse_strv_one("foo bar foo", STRV_MAKE("foo", "bar", "foo")); + test_config_parse_strv_one("\"foo bar\" foo", STRV_MAKE("foo bar", "foo")); + test_config_parse_strv_one("\xc3\x80", STRV_MAKE("\xc3\x80")); + test_config_parse_strv_one("\xc3\x7f", STRV_MAKE("\xc3\x7f")); +} + +TEST(config_parse_mode) { + test_config_parse_mode_one("777", 0777); + test_config_parse_mode_one("644", 0644); + + test_config_parse_mode_one("-777", 0); + test_config_parse_mode_one("999", 0); + test_config_parse_mode_one("garbage", 0); + test_config_parse_mode_one("777garbage", 0); + test_config_parse_mode_one("777 garbage", 0); +} + +TEST(config_parse_sec) { + test_config_parse_sec_one("1", 1 * USEC_PER_SEC); + test_config_parse_sec_one("1s", 1 * USEC_PER_SEC); + test_config_parse_sec_one("100ms", 100 * USEC_PER_MSEC); + test_config_parse_sec_one("5min 20s", 5 * 60 * USEC_PER_SEC + 20 * USEC_PER_SEC); + + test_config_parse_sec_one("-1", 0); + test_config_parse_sec_one("10foo", 0); + test_config_parse_sec_one("garbage", 0); +} + +TEST(config_parse_nsec) { + test_config_parse_nsec_one("1", 1); + test_config_parse_nsec_one("1s", 1 * NSEC_PER_SEC); + test_config_parse_nsec_one("100ms", 100 * NSEC_PER_MSEC); + test_config_parse_nsec_one("5min 20s", 5 * 60 * NSEC_PER_SEC + 20 * NSEC_PER_SEC); + + test_config_parse_nsec_one("-1", 0); + test_config_parse_nsec_one("10foo", 0); + test_config_parse_nsec_one("garbage", 0); +} + +TEST(config_parse_iec_uint64) { + uint64_t offset = 0; + assert_se(config_parse_iec_uint64(NULL, "/this/file", 11, "Section", 22, "Size", 0, "4M", &offset, NULL) == 0); + assert_se(offset == 4 * 1024 * 1024); + + assert_se(config_parse_iec_uint64(NULL, "/this/file", 11, "Section", 22, "Size", 0, "4.5M", &offset, NULL) == 0); +} + +#define x10(x) x x x x x x x x x x +#define x100(x) x10(x10(x)) +#define x1000(x) x10(x100(x)) + +static const char* const config_file[] = { + "[Section]\n" + "setting1=1\n", + + "[Section]\n" + "setting1=1", /* no terminating newline */ + + "\n\n\n\n[Section]\n\n\n" + "setting1=1", /* some whitespace, no terminating newline */ + + "[Section]\n" + "[Section]\n" + "setting1=1\n" + "setting1= 2 \t\n" + "setting1= 1\n", /* repeated settings */ + + "[Section]\n" + "[Section]\n" + "setting1=1\n" + "setting1=2\\\n" + " \n" /* empty line breaks continuation */ + "setting1=1\n", /* repeated settings */ + + "[Section]\n" + "setting1=1\\\n" /* normal continuation */ + "2\\\n" + "3\n", + + "[Section]\n" + "#hogehoge\\\n" /* continuation is ignored in comment */ + "setting1=1\\\n" /* normal continuation */ + "2\\\n" + "3\n", + + "[Section]\n" + "setting1=1\\\n" /* normal continuation */ + "#hogehoge\\\n" /* commented out line in continuation is ignored */ + "2\\\n" + "3\n", + + "[Section]\n" + " #hogehoge\\\n" /* whitespaces before comments */ + " setting1=1\\\n" /* whitespaces before key */ + "2\\\n" + "3\n", + + "[Section]\n" + " setting1=1\\\n" /* whitespaces before key */ + " #hogehoge\\\n" /* commented out line prefixed with whitespaces in continuation */ + "2\\\n" + "3\n", + + "[Section]\n" + "setting1=1\\\n" /* continuation with extra trailing backslash at the end */ + "2\\\n" + "3\\\n", + + "[Section]\n" + "setting1=1\\\\\\\n" /* continuation with trailing escape symbols */ + "\\\\2\n", /* note that C requires one level of escaping, so the + * parser gets "…1 BS BS BS NL BS BS 2 NL", which + * it translates into "…1 BS BS SP BS BS 2" */ + + "\n[Section]\n\n" + "setting1=" /* a line above LINE_MAX length */ + x1000("ABCD") + "\n", + + "[Section]\n" + "setting1=" /* a line above LINE_MAX length, with continuation */ + x1000("ABCD") "\\\n" + "foobar", + + "[Section]\n" + "setting1=" /* a line above LINE_MAX length, with continuation */ + x1000("ABCD") "\\\n" /* and an extra trailing backslash */ + "foobar\\\n", + + "[Section]\n" + "setting1=" /* a line above the allowed limit: 9 + 1050000 + 1 */ + x1000(x1000("x") x10("abcde")) "\n", + + "[Section]\n" + "setting1=" /* many continuation lines, together above the limit */ + x1000(x1000("x") x10("abcde") "\\\n") "xxx", + + "[Section]\n" + "setting1=2\n" + "[NoWarnSection]\n" + "setting1=3\n" + "[WarnSection]\n" + "setting1=3\n" + "[X-Section]\n" + "setting1=3\n", +}; + +static void test_config_parse_one(unsigned i, const char *s) { + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-conf-parser.XXXXXX"; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *setting1 = NULL; + int r; + + const ConfigTableItem items[] = { + { "Section", "setting1", config_parse_string, 0, &setting1}, + {} + }; + + log_info("== %s[%u] ==", __func__, i); + + assert_se(fmkostemp_safe(name, "r+", &f) == 0); + assert_se(fwrite(s, strlen(s), 1, f) == 1); + rewind(f); + + /* + int config_parse(const char *unit, + const char *filename, + FILE *f, + const char *sections, + ConfigItemLookup lookup, + const void *table, + ConfigParseFlags flags, + void *userdata, + struct stat *ret_stat); + */ + + r = config_parse(NULL, name, f, + "Section\0" + "-NoWarnSection\0", + config_item_table_lookup, items, + CONFIG_PARSE_WARN, + NULL, + NULL); + + switch (i) { + case 0 ... 4: + assert_se(r == 1); + assert_se(streq(setting1, "1")); + break; + + case 5 ... 10: + assert_se(r == 1); + assert_se(streq(setting1, "1 2 3")); + break; + + case 11: + assert_se(r == 1); + assert_se(streq(setting1, "1\\\\ \\\\2")); + break; + + case 12: + assert_se(r == 1); + assert_se(streq(setting1, x1000("ABCD"))); + break; + + case 13 ... 14: + assert_se(r == 1); + assert_se(streq(setting1, x1000("ABCD") " foobar")); + break; + + case 15 ... 16: + assert_se(r == -ENOBUFS); + assert_se(setting1 == NULL); + break; + + case 17: + assert_se(r == 1); + assert_se(streq(setting1, "2")); + break; + } +} + +TEST(config_parse) { + for (unsigned i = 0; i < ELEMENTSOF(config_file); i++) + test_config_parse_one(i, config_file[i]); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-copy.c b/src/test/test-copy.c new file mode 100644 index 0000000..f3144f0 --- /dev/null +++ b/src/test/test-copy.c @@ -0,0 +1,532 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <sys/file.h> +#include <sys/xattr.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "chase.h" +#include "copy.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "hexdecoct.h" +#include "io-util.h" +#include "log.h" +#include "macro.h" +#include "mkdir.h" +#include "path-util.h" +#include "random-util.h" +#include "rm-rf.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" +#include "tmpfile-util.h" +#include "user-util.h" +#include "xattr-util.h" + +TEST(copy_file) { + _cleanup_free_ char *buf = NULL; + _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-copy_file.XXXXXX"; + _cleanup_(unlink_tempfilep) char fn_copy[] = "/tmp/test-copy_file.XXXXXX"; + size_t sz = 0; + int fd; + + fd = mkostemp_safe(fn); + assert_se(fd >= 0); + close(fd); + + fd = mkostemp_safe(fn_copy); + assert_se(fd >= 0); + close(fd); + + assert_se(write_string_file(fn, "foo bar bar bar foo", WRITE_STRING_FILE_CREATE) == 0); + + assert_se(copy_file(fn, fn_copy, 0, 0644, COPY_REFLINK) == 0); + + assert_se(read_full_file(fn_copy, &buf, &sz) == 0); + assert_se(streq(buf, "foo bar bar bar foo\n")); + assert_se(sz == 20); +} + +static bool read_file_at_and_streq(int dir_fd, const char *path, const char *expected) { + _cleanup_free_ char *buf = NULL; + + assert_se(read_full_file_at(dir_fd, path, &buf, NULL) == 0); + return streq(buf, expected); +} + +TEST(copy_tree_replace_file) { + _cleanup_free_ char *src = NULL, *dst = NULL; + + assert_se(tempfn_random("/tmp/test-copy_file.XXXXXX", NULL, &src) >= 0); + assert_se(tempfn_random("/tmp/test-copy_file.XXXXXX", NULL, &dst) >= 0); + + assert_se(write_string_file(src, "bar bar", WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file(dst, "foo foo foo", WRITE_STRING_FILE_CREATE) == 0); + + /* The file exists- now overwrite original contents, and test the COPY_REPLACE flag. */ + + assert_se(copy_tree(src, dst, UID_INVALID, GID_INVALID, COPY_REFLINK, NULL, NULL) == -EEXIST); + + assert_se(read_file_at_and_streq(AT_FDCWD, dst, "foo foo foo\n")); + + assert_se(copy_tree(src, dst, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE, NULL, NULL) == 0); + + assert_se(read_file_at_and_streq(AT_FDCWD, dst, "bar bar\n")); +} + +TEST(copy_tree_replace_dirs) { + _cleanup_(rm_rf_physical_and_freep) char *srcp = NULL, *dstp = NULL; + _cleanup_close_ int src = -EBADF, dst = -EBADF; + + /* Create the random source/destination directories */ + assert_se((src = mkdtemp_open(NULL, 0, &srcp)) >= 0); + assert_se((dst = mkdtemp_open(NULL, 0, &dstp)) >= 0); + + /* Populate some data to differentiate the files. */ + assert_se(write_string_file_at(src, "foo", "src file 1", WRITE_STRING_FILE_CREATE) >= 0); + assert_se(write_string_file_at(src, "bar", "src file 2", WRITE_STRING_FILE_CREATE) == 0); + + assert_se(write_string_file_at(dst, "foo", "dest file 1", WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file_at(dst, "bar", "dest file 2", WRITE_STRING_FILE_CREATE) == 0); + + /* Copying without COPY_REPLACE should fail because the destination file already exists. */ + assert_se(copy_tree_at(src, ".", dst, ".", UID_INVALID, GID_INVALID, COPY_REFLINK, NULL, NULL) == -EEXIST); + + assert_se(read_file_at_and_streq(src, "foo", "src file 1\n")); + assert_se(read_file_at_and_streq(src, "bar", "src file 2\n")); + assert_se(read_file_at_and_streq(dst, "foo", "dest file 1\n")); + assert_se(read_file_at_and_streq(dst, "bar", "dest file 2\n")); + + assert_se(copy_tree_at(src, ".", dst, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_MERGE, NULL, NULL) == 0); + + assert_se(read_file_at_and_streq(src, "foo", "src file 1\n")); + assert_se(read_file_at_and_streq(src, "bar", "src file 2\n")); + assert_se(read_file_at_and_streq(dst, "foo", "src file 1\n")); + assert_se(read_file_at_and_streq(dst, "bar", "src file 2\n")); +} + +TEST(copy_file_fd) { + _cleanup_(unlink_tempfilep) char in_fn[] = "/tmp/test-copy-file-fd-XXXXXX"; + _cleanup_(unlink_tempfilep) char out_fn[] = "/tmp/test-copy-file-fd-XXXXXX"; + _cleanup_close_ int in_fd = -EBADF, out_fd = -EBADF; + const char *text = "boohoo\nfoo\n\tbar\n"; + char buf[64] = {}; + + in_fd = mkostemp_safe(in_fn); + assert_se(in_fd >= 0); + out_fd = mkostemp_safe(out_fn); + assert_se(out_fd >= 0); + + assert_se(write_string_file(in_fn, text, WRITE_STRING_FILE_CREATE) == 0); + assert_se(copy_file_fd("/a/file/which/does/not/exist/i/guess", out_fd, COPY_REFLINK) < 0); + assert_se(copy_file_fd(in_fn, out_fd, COPY_REFLINK) >= 0); + assert_se(lseek(out_fd, SEEK_SET, 0) == 0); + + assert_se(read(out_fd, buf, sizeof buf) == (ssize_t) strlen(text)); + assert_se(streq(buf, text)); +} + +TEST(copy_tree) { + _cleanup_hashmap_free_ Hashmap *denylist = NULL; + _cleanup_free_ char *cp = NULL; + char original_dir[] = "/tmp/test-copy_tree/"; + char copy_dir[] = "/tmp/test-copy_tree-copy/"; + char **files = STRV_MAKE("file", "dir1/file", "dir1/dir2/file", "dir1/dir2/dir3/dir4/dir5/file"); + char **symlinks = STRV_MAKE("link", "file", + "link2", "dir1/file"); + char **hardlinks = STRV_MAKE("hlink", "file", + "hlink2", "dir1/file"); + const char *unixsockp, *ignorep; + struct stat st; + int xattr_worked = -1; /* xattr support is optional in temporary directories, hence use it if we can, + * but don't fail if we can't */ + + (void) rm_rf(copy_dir, REMOVE_ROOT|REMOVE_PHYSICAL); + (void) rm_rf(original_dir, REMOVE_ROOT|REMOVE_PHYSICAL); + + STRV_FOREACH(p, files) { + _cleanup_free_ char *f = NULL, *c = NULL; + int k; + + assert_se(f = path_join(original_dir, *p)); + + assert_se(write_string_file(f, "file", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755) == 0); + + assert_se(base64mem(*p, strlen(*p), &c) >= 0); + + k = setxattr(f, "user.testxattr", c, strlen(c), 0); + assert_se(xattr_worked < 0 || ((k >= 0) == !!xattr_worked)); + xattr_worked = k >= 0; + } + + STRV_FOREACH_PAIR(ll, p, symlinks) { + _cleanup_free_ char *f = NULL, *l = NULL; + + assert_se(f = path_join(original_dir, *p)); + assert_se(l = path_join(original_dir, *ll)); + + assert_se(mkdir_parents(l, 0755) >= 0); + assert_se(symlink(f, l) == 0); + } + + STRV_FOREACH_PAIR(ll, p, hardlinks) { + _cleanup_free_ char *f = NULL, *l = NULL; + + assert_se(f = path_join(original_dir, *p)); + assert_se(l = path_join(original_dir, *ll)); + + assert_se(mkdir_parents(l, 0755) >= 0); + assert_se(link(f, l) == 0); + } + + unixsockp = strjoina(original_dir, "unixsock"); + assert_se(mknod(unixsockp, S_IFSOCK|0644, 0) >= 0); + + ignorep = strjoina(original_dir, "ignore/file"); + assert_se(write_string_file(ignorep, "ignore", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755) == 0); + assert_se(RET_NERRNO(stat(ignorep, &st)) >= 0); + assert_se(cp = memdup(&st, sizeof(st))); + assert_se(hashmap_ensure_put(&denylist, &inode_hash_ops, cp, INT_TO_PTR(DENY_INODE)) >= 0); + TAKE_PTR(cp); + + assert_se(copy_tree(original_dir, copy_dir, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_MERGE|COPY_HARDLINKS, denylist, NULL) == 0); + + STRV_FOREACH(p, files) { + _cleanup_free_ char *buf = NULL, *f = NULL, *c = NULL; + size_t sz; + int k; + + assert_se(f = path_join(copy_dir, *p)); + + assert_se(access(f, F_OK) == 0); + assert_se(read_full_file(f, &buf, &sz) == 0); + assert_se(streq(buf, "file\n")); + + k = lgetxattr_malloc(f, "user.testxattr", &c); + assert_se(xattr_worked < 0 || ((k >= 0) == !!xattr_worked)); + + if (k >= 0) { + _cleanup_free_ char *d = NULL; + + assert_se(base64mem(*p, strlen(*p), &d) >= 0); + assert_se(streq(d, c)); + } + } + + STRV_FOREACH_PAIR(ll, p, symlinks) { + _cleanup_free_ char *target = NULL, *f = NULL, *l = NULL; + + assert_se(f = strjoin(original_dir, *p)); + assert_se(l = strjoin(copy_dir, *ll)); + + assert_se(chase(l, NULL, 0, &target, NULL) == 1); + assert_se(path_equal(f, target)); + } + + STRV_FOREACH_PAIR(ll, p, hardlinks) { + _cleanup_free_ char *f = NULL, *l = NULL; + struct stat a, b; + + assert_se(f = strjoin(copy_dir, *p)); + assert_se(l = strjoin(copy_dir, *ll)); + + assert_se(lstat(f, &a) >= 0); + assert_se(lstat(l, &b) >= 0); + + assert_se(a.st_ino == b.st_ino); + assert_se(a.st_dev == b.st_dev); + } + + unixsockp = strjoina(copy_dir, "unixsock"); + assert_se(stat(unixsockp, &st) >= 0); + assert_se(S_ISSOCK(st.st_mode)); + + assert_se(copy_tree(original_dir, copy_dir, UID_INVALID, GID_INVALID, COPY_REFLINK, denylist, NULL) < 0); + assert_se(copy_tree("/tmp/inexistent/foo/bar/fsdoi", copy_dir, UID_INVALID, GID_INVALID, COPY_REFLINK, denylist, NULL) < 0); + + ignorep = strjoina(copy_dir, "ignore/file"); + assert_se(RET_NERRNO(access(ignorep, F_OK)) == -ENOENT); + + (void) rm_rf(copy_dir, REMOVE_ROOT|REMOVE_PHYSICAL); + (void) rm_rf(original_dir, REMOVE_ROOT|REMOVE_PHYSICAL); +} + +TEST(copy_bytes) { + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + _cleanup_close_ int infd = -EBADF; + int r, r2; + char buf[1024], buf2[1024]; + + infd = open("/usr/lib/os-release", O_RDONLY|O_CLOEXEC); + if (infd < 0) + infd = open("/etc/os-release", O_RDONLY|O_CLOEXEC); + assert_se(infd >= 0); + + assert_se(pipe2(pipefd, O_CLOEXEC) == 0); + + r = copy_bytes(infd, pipefd[1], UINT64_MAX, 0); + assert_se(r == 0); + + r = read(pipefd[0], buf, sizeof(buf)); + assert_se(r >= 0); + + assert_se(lseek(infd, 0, SEEK_SET) == 0); + r2 = read(infd, buf2, sizeof(buf2)); + assert_se(r == r2); + + assert_se(strneq(buf, buf2, r)); + + /* test copy_bytes with invalid descriptors */ + r = copy_bytes(pipefd[0], pipefd[0], 1, 0); + assert_se(r == -EBADF); + + r = copy_bytes(pipefd[1], pipefd[1], 1, 0); + assert_se(r == -EBADF); + + r = copy_bytes(pipefd[1], infd, 1, 0); + assert_se(r == -EBADF); +} + +static void test_copy_bytes_regular_file_one(const char *src, bool try_reflink, uint64_t max_bytes) { + _cleanup_(unlink_tempfilep) char fn2[] = "/tmp/test-copy-file-XXXXXX"; + _cleanup_(unlink_tempfilep) char fn3[] = "/tmp/test-copy-file-XXXXXX"; + _cleanup_close_ int fd = -EBADF, fd2 = -EBADF, fd3 = -EBADF; + int r; + struct stat buf, buf2, buf3; + + log_info("%s try_reflink=%s max_bytes=%" PRIu64, __func__, yes_no(try_reflink), max_bytes); + + fd = open(src, O_CLOEXEC | O_PATH); + assert_se(fd >= 0); + + fd2 = mkostemp_safe(fn2); + assert_se(fd2 >= 0); + + fd3 = mkostemp_safe(fn3); + assert_se(fd3 >= 0); + + r = copy_bytes(fd, fd2, max_bytes, try_reflink ? COPY_REFLINK : 0); + if (max_bytes == UINT64_MAX) + assert_se(r == 0); + else + assert_se(IN_SET(r, 0, 1)); + + assert_se(fstat(fd, &buf) == 0); + assert_se(fstat(fd2, &buf2) == 0); + assert_se((uint64_t) buf2.st_size == MIN((uint64_t) buf.st_size, max_bytes)); + + if (max_bytes < UINT64_MAX) + /* Make sure the file is now higher than max_bytes */ + assert_se(ftruncate(fd2, max_bytes + 1) == 0); + + assert_se(lseek(fd2, 0, SEEK_SET) == 0); + + r = copy_bytes(fd2, fd3, max_bytes, try_reflink ? COPY_REFLINK : 0); + if (max_bytes == UINT64_MAX) + assert_se(r == 0); + else + /* We cannot distinguish between the input being exactly max_bytes + * or longer than max_bytes (without trying to read one more byte, + * or calling stat, or FION_READ, etc, and we don't want to do any + * of that). So we expect "truncation" since we know that file we + * are copying is exactly max_bytes bytes. */ + assert_se(r == 1); + + assert_se(fstat(fd3, &buf3) == 0); + + if (max_bytes == UINT64_MAX) + assert_se(buf3.st_size == buf2.st_size); + else + assert_se((uint64_t) buf3.st_size == max_bytes); +} + +TEST(copy_bytes_regular_file) { + test_copy_bytes_regular_file_one(saved_argv[0], false, UINT64_MAX); + test_copy_bytes_regular_file_one(saved_argv[0], true, UINT64_MAX); + test_copy_bytes_regular_file_one(saved_argv[0], false, 1000); /* smaller than copy buffer size */ + test_copy_bytes_regular_file_one(saved_argv[0], true, 1000); + test_copy_bytes_regular_file_one(saved_argv[0], false, 32000); /* larger than copy buffer size */ + test_copy_bytes_regular_file_one(saved_argv[0], true, 32000); +} + +TEST(copy_atomic) { + _cleanup_(rm_rf_physical_and_freep) char *p = NULL; + const char *q; + int r; + + assert_se(mkdtemp_malloc(NULL, &p) >= 0); + + q = strjoina(p, "/fstab"); + + r = copy_file_atomic("/etc/fstab", q, 0644, COPY_REFLINK); + if (r == -ENOENT || ERRNO_IS_PRIVILEGE(r)) + return; + + assert_se(copy_file_atomic("/etc/fstab", q, 0644, COPY_REFLINK) == -EEXIST); + + assert_se(copy_file_atomic("/etc/fstab", q, 0644, COPY_REPLACE) >= 0); +} + +TEST(copy_proc) { + _cleanup_(rm_rf_physical_and_freep) char *p = NULL; + _cleanup_free_ char *f = NULL, *a = NULL, *b = NULL; + + /* Check if copying data from /proc/ works correctly, i.e. let's see if https://lwn.net/Articles/846403/ is a problem for us */ + + assert_se(mkdtemp_malloc(NULL, &p) >= 0); + assert_se(f = path_join(p, "version")); + assert_se(copy_file("/proc/version", f, 0, MODE_INVALID, 0) >= 0); + + assert_se(read_one_line_file("/proc/version", &a) >= 0); + assert_se(read_one_line_file(f, &b) >= 0); + assert_se(streq(a, b)); + assert_se(!isempty(a)); +} + +TEST_RET(copy_holes) { + _cleanup_(unlink_tempfilep) char fn[] = "/var/tmp/test-copy-hole-fd-XXXXXX"; + _cleanup_(unlink_tempfilep) char fn_copy[] = "/var/tmp/test-copy-hole-fd-XXXXXX"; + struct stat stat; + off_t blksz; + int r, fd, fd_copy; + char *buf; + + fd = mkostemp_safe(fn); + assert_se(fd >= 0); + + fd_copy = mkostemp_safe(fn_copy); + assert_se(fd_copy >= 0); + + r = RET_NERRNO(fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 0, 1)); + if (ERRNO_IS_NOT_SUPPORTED(r)) + return log_tests_skipped("Filesystem doesn't support hole punching"); + assert_se(r >= 0); + + assert_se(fstat(fd, &stat) >= 0); + blksz = stat.st_blksize; + buf = alloca_safe(blksz); + memset(buf, 1, blksz); + + /* We need to make sure to create hole in multiples of the block size, otherwise filesystems (btrfs) + * might silently truncate/extend the holes. */ + + assert_se(lseek(fd, blksz, SEEK_CUR) >= 0); + assert_se(write(fd, buf, blksz) >= 0); + assert_se(lseek(fd, 0, SEEK_END) == 2 * blksz); + /* Only ftruncate() can create holes at the end of a file. */ + assert_se(ftruncate(fd, 3 * blksz) >= 0); + assert_se(lseek(fd, 0, SEEK_SET) >= 0); + + assert_se(copy_bytes(fd, fd_copy, UINT64_MAX, COPY_HOLES) >= 0); + + /* Test that the hole starts at the beginning of the file. */ + assert_se(lseek(fd_copy, 0, SEEK_HOLE) == 0); + /* Test that the hole has the expected size. */ + assert_se(lseek(fd_copy, 0, SEEK_DATA) == blksz); + assert_se(lseek(fd_copy, blksz, SEEK_HOLE) == 2 * blksz); + assert_se(lseek(fd_copy, 2 * blksz, SEEK_DATA) < 0 && errno == ENXIO); + + /* Test that the copied file has the correct size. */ + assert_se(fstat(fd_copy, &stat) >= 0); + assert_se(stat.st_size == 3 * blksz); + + close(fd); + close(fd_copy); + + return 0; +} + +TEST_RET(copy_holes_with_gaps) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int tfd = -EBADF, fd = -EBADF, fd_copy = -EBADF; + struct stat st; + off_t blksz; + char *buf; + int r; + + assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0); + assert_se((fd = openat(tfd, "src", O_CREAT | O_RDWR, 0600)) >= 0); + assert_se((fd_copy = openat(tfd, "dst", O_CREAT | O_WRONLY, 0600)) >= 0); + + assert_se(fstat(fd, &st) >= 0); + blksz = st.st_blksize; + buf = alloca_safe(blksz); + memset(buf, 1, blksz); + + /* Create a file with: + * - hole of 1 block + * - data of 2 block + * - hole of 2 blocks + * - data of 1 block + * + * Since sparse files are based on blocks and not bytes, we need to make + * sure that the holes are aligned to the block size. + */ + + r = RET_NERRNO(fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 0, blksz)); + if (ERRNO_IS_NOT_SUPPORTED(r)) + return log_tests_skipped("Filesystem doesn't support hole punching"); + + assert_se(lseek(fd, blksz, SEEK_CUR) >= 0); + assert_se(loop_write(fd, buf, blksz) >= 0); + assert_se(loop_write(fd, buf, blksz) >= 0); + assert_se(lseek(fd, 2 * blksz, SEEK_CUR) >= 0); + assert_se(loop_write(fd, buf, blksz) >= 0); + assert_se(lseek(fd, 0, SEEK_SET) >= 0); + assert_se(fsync(fd) >= 0); + + /* Copy to the start of the second hole */ + assert_se(copy_bytes(fd, fd_copy, 3 * blksz, COPY_HOLES) >= 0); + assert_se(fstat(fd_copy, &st) >= 0); + assert_se(st.st_size == 3 * blksz); + + /* Copy to the middle of the second hole */ + assert_se(lseek(fd, 0, SEEK_SET) >= 0); + assert_se(lseek(fd_copy, 0, SEEK_SET) >= 0); + assert_se(ftruncate(fd_copy, 0) >= 0); + assert_se(copy_bytes(fd, fd_copy, 4 * blksz, COPY_HOLES) >= 0); + assert_se(fstat(fd_copy, &st) >= 0); + assert_se(st.st_size == 4 * blksz); + + /* Copy to the end of the second hole */ + assert_se(lseek(fd, 0, SEEK_SET) >= 0); + assert_se(lseek(fd_copy, 0, SEEK_SET) >= 0); + assert_se(ftruncate(fd_copy, 0) >= 0); + assert_se(copy_bytes(fd, fd_copy, 5 * blksz, COPY_HOLES) >= 0); + assert_se(fstat(fd_copy, &st) >= 0); + assert_se(st.st_size == 5 * blksz); + + /* Copy everything */ + assert_se(lseek(fd, 0, SEEK_SET) >= 0); + assert_se(lseek(fd_copy, 0, SEEK_SET) >= 0); + assert_se(ftruncate(fd_copy, 0) >= 0); + assert_se(copy_bytes(fd, fd_copy, UINT64_MAX, COPY_HOLES) >= 0); + assert_se(fstat(fd_copy, &st) >= 0); + assert_se(st.st_size == 6 * blksz); + + return 0; +} + +TEST(copy_lock) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int tfd = -EBADF, fd = -EBADF; + + assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0); + assert_se(mkdirat(tfd, "abc", 0755) >= 0); + assert_se(write_string_file_at(tfd, "abc/def", "abc", WRITE_STRING_FILE_CREATE) >= 0); + + assert_se((fd = copy_directory_at(tfd, "abc", tfd, "qed", COPY_LOCK_BSD)) >= 0); + assert_se(faccessat(tfd, "qed", F_OK, 0) >= 0); + assert_se(faccessat(tfd, "qed/def", F_OK, 0) >= 0); + assert_se(xopenat_lock(tfd, "qed", 0, 0, 0, LOCK_BSD, LOCK_EX|LOCK_NB) == -EAGAIN); + fd = safe_close(fd); + + assert_se((fd = copy_file_at(tfd, "abc/def", tfd, "poi", 0, 0644, COPY_LOCK_BSD))); + assert_se(read_file_at_and_streq(tfd, "poi", "abc\n")); + assert_se(xopenat_lock(tfd, "poi", 0, 0, 0, LOCK_BSD, LOCK_EX|LOCK_NB) == -EAGAIN); + fd = safe_close(fd); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-core-unit.c b/src/test/test-core-unit.c new file mode 100644 index 0000000..dc108cc --- /dev/null +++ b/src/test/test-core-unit.c @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "escape.h" +#include "tests.h" +#include "unit.h" + +static void test_unit_escape_setting_one( + const char *s, + const char *expected_exec_env, + const char *expected_exec, + const char *expected_c) { + + _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL, + *s_esc = NULL, *a_esc = NULL, *b_esc = NULL, *c_esc = NULL, *d_esc = NULL; + const char *t; + + if (!expected_exec_env) + expected_exec_env = s; + if (!expected_exec) + expected_exec = expected_exec_env; + if (!expected_c) + expected_c = expected_exec; + assert_se(s_esc = cescape(s)); + + assert_se(t = unit_escape_setting(s, 0, &a)); + assert_se(a_esc = cescape(t)); + log_debug("%s: [%s] → [%s]", __func__, s_esc, a_esc); + assert_se(a == NULL); + assert_se(t == s); + + assert_se(t = unit_escape_setting(s, UNIT_ESCAPE_EXEC_SYNTAX_ENV, &b)); + assert_se(b_esc = cescape(t)); + log_debug("%s: [%s] → [%s]", __func__, s_esc, b_esc); + assert_se(b == NULL || streq(b, t)); + assert_se(streq(t, expected_exec_env)); + + assert_se(t = unit_escape_setting(s, UNIT_ESCAPE_EXEC_SYNTAX, &c)); + assert_se(c_esc = cescape(t)); + log_debug("%s: [%s] → [%s]", __func__, s_esc, c_esc); + assert_se(c == NULL || streq(c, t)); + assert_se(streq(t, expected_exec)); + + assert_se(t = unit_escape_setting(s, UNIT_ESCAPE_C, &d)); + assert_se(d_esc = cescape(t)); + log_debug("%s: [%s] → [%s]", __func__, s_esc, d_esc); + assert_se(d == NULL || streq(d, t)); + assert_se(streq(t, expected_c)); +} + +TEST(unit_escape_setting) { + test_unit_escape_setting_one("/sbin/sbash", NULL, NULL, NULL); + test_unit_escape_setting_one("$", "$$", "$", "$"); + test_unit_escape_setting_one("$$", "$$$$", "$$", "$$"); + test_unit_escape_setting_one("'", "'", NULL, "\\'"); + test_unit_escape_setting_one("\"", "\\\"", NULL, NULL); + test_unit_escape_setting_one("\t", "\\t", NULL, NULL); + test_unit_escape_setting_one(" ", NULL, NULL, NULL); + test_unit_escape_setting_one("$;'\"\t\n", "$$;'\\\"\\t\\n", "$;'\\\"\\t\\n", "$;\\'\\\"\\t\\n"); +} + +static void test_unit_concat_strv_one( + char **s, + const char *expected_none, + const char *expected_exec_env, + const char *expected_exec, + const char *expected_c) { + + _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL, + *s_ser = NULL, *s_esc = NULL, *a_esc = NULL, *b_esc = NULL, *c_esc = NULL, *d_esc = NULL; + + assert_se(s_ser = strv_join(s, "_")); + assert_se(s_esc = cescape(s_ser)); + if (!expected_exec_env) + expected_exec_env = expected_none; + if (!expected_exec) + expected_exec = expected_none; + if (!expected_c) + expected_c = expected_none; + + assert_se(a = unit_concat_strv(s, 0)); + assert_se(a_esc = cescape(a)); + log_debug("%s: [%s] → [%s]", __func__, s_esc, a_esc); + assert_se(streq(a, expected_none)); + + assert_se(b = unit_concat_strv(s, UNIT_ESCAPE_EXEC_SYNTAX_ENV)); + assert_se(b_esc = cescape(b)); + log_debug("%s: [%s] → [%s]", __func__, s_esc, b_esc); + assert_se(streq(b, expected_exec_env)); + + assert_se(c = unit_concat_strv(s, UNIT_ESCAPE_EXEC_SYNTAX)); + assert_se(c_esc = cescape(c)); + log_debug("%s: [%s] → [%s]", __func__, s_esc, c_esc); + assert_se(streq(c, expected_exec)); + + assert_se(d = unit_concat_strv(s, UNIT_ESCAPE_C)); + assert_se(d_esc = cescape(d)); + log_debug("%s: [%s] → [%s]", __func__, s_esc, d_esc); + assert_se(streq(d, expected_c)); +} + +TEST(unit_concat_strv) { + test_unit_concat_strv_one(STRV_MAKE("a", "b", "c"), + "\"a\" \"b\" \"c\"", + NULL, + NULL, + NULL); + test_unit_concat_strv_one(STRV_MAKE("a", " ", "$", "$$", ""), + "\"a\" \" \" \"$\" \"$$\" \"\"", + "\"a\" \" \" \"$$\" \"$$$$\" \"\"", + NULL, + NULL); + test_unit_concat_strv_one(STRV_MAKE("\n", " ", "\t"), + "\"\n\" \" \" \"\t\"", + "\"\\n\" \" \" \"\\t\"", + "\"\\n\" \" \" \"\\t\"", + "\"\\n\" \" \" \"\\t\""); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-coredump-util.c b/src/test/test-coredump-util.c new file mode 100644 index 0000000..4e7f3b4 --- /dev/null +++ b/src/test/test-coredump-util.c @@ -0,0 +1,161 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <elf.h> + +#include "alloc-util.h" +#include "coredump-util.h" +#include "fileio.h" +#include "fd-util.h" +#include "format-util.h" +#include "macro.h" +#include "tests.h" + +TEST(coredump_filter_to_from_string) { + for (CoredumpFilter i = 0; i < _COREDUMP_FILTER_MAX; i++) { + const char *n; + + assert_se(n = coredump_filter_to_string(i)); + log_info("0x%x\t%s", 1u << i, n); + assert_se(coredump_filter_from_string(n) == i); + + uint64_t f; + assert_se(coredump_filter_mask_from_string(n, &f) == 0); + assert_se(f == 1u << i); + } +} + +TEST(coredump_filter_mask_from_string) { + uint64_t f; + assert_se(coredump_filter_mask_from_string("default", &f) == 0); + assert_se(f == COREDUMP_FILTER_MASK_DEFAULT); + assert_se(coredump_filter_mask_from_string("all", &f) == 0); + assert_se(f == COREDUMP_FILTER_MASK_ALL); + + assert_se(coredump_filter_mask_from_string(" default\tdefault\tdefault ", &f) == 0); + assert_se(f == COREDUMP_FILTER_MASK_DEFAULT); + + assert_se(coredump_filter_mask_from_string("defaulta", &f) < 0); + assert_se(coredump_filter_mask_from_string("default defaulta default", &f) < 0); + assert_se(coredump_filter_mask_from_string("default default defaulta", &f) < 0); + + assert_se(coredump_filter_mask_from_string("private-anonymous default", &f) == 0); + assert_se(f == COREDUMP_FILTER_MASK_DEFAULT); + + assert_se(coredump_filter_mask_from_string("shared-file-backed shared-dax", &f) == 0); + assert_se(f == (1 << COREDUMP_FILTER_SHARED_FILE_BACKED | + 1 << COREDUMP_FILTER_SHARED_DAX)); + + assert_se(coredump_filter_mask_from_string("private-file-backed private-dax 0xF", &f) == 0); + assert_se(f == (1 << COREDUMP_FILTER_PRIVATE_FILE_BACKED | + 1 << COREDUMP_FILTER_PRIVATE_DAX | + 0xF)); + + assert_se(coredump_filter_mask_from_string("11", &f) == 0); + assert_se(f == 0x11); + + assert_se(coredump_filter_mask_from_string("0x1101", &f) == 0); + assert_se(f == 0x1101); + + assert_se(coredump_filter_mask_from_string("0", &f) == 0); + assert_se(f == 0); + + assert_se(coredump_filter_mask_from_string("all", &f) == 0); + assert_se(FLAGS_SET(f, (1 << COREDUMP_FILTER_PRIVATE_ANONYMOUS | + 1 << COREDUMP_FILTER_SHARED_ANONYMOUS | + 1 << COREDUMP_FILTER_PRIVATE_FILE_BACKED | + 1 << COREDUMP_FILTER_SHARED_FILE_BACKED | + 1 << COREDUMP_FILTER_ELF_HEADERS | + 1 << COREDUMP_FILTER_PRIVATE_HUGE | + 1 << COREDUMP_FILTER_SHARED_HUGE | + 1 << COREDUMP_FILTER_PRIVATE_DAX | + 1 << COREDUMP_FILTER_SHARED_DAX))); +} + +static void test_parse_auxv_two( + uint8_t elf_class, + size_t offset, + const char *data, + size_t data_size, + int expect_at_secure, + uid_t expect_uid, + uid_t expect_euid, + gid_t expect_gid, + gid_t expect_egid) { + + int at_secure; + uid_t uid, euid; + gid_t gid, egid; + assert_se(parse_auxv(LOG_ERR, elf_class, data, data_size, + &at_secure, &uid, &euid, &gid, &egid) == 0); + + log_debug("[offset=%zu] at_secure=%d, uid="UID_FMT", euid="UID_FMT", gid="GID_FMT", egid="GID_FMT, + offset, + at_secure, uid, euid, gid, egid); + + assert_se(uid == expect_uid); + assert_se(euid == expect_euid); + assert_se(gid == expect_gid); + assert_se(egid == expect_egid); +} + +static void test_parse_auxv_one( + uint8_t elf_class, + int dir_fd, + const char *filename, + int expect_at_secure, + uid_t expect_uid, + uid_t expect_euid, + gid_t expect_gid, + gid_t expect_egid) { + + _cleanup_free_ char *buf; + const char *data; + size_t data_size; + log_info("Parsing %s…", filename); + assert_se(read_full_file_at(dir_fd, filename, &buf, &data_size) >= 0); + + for (size_t offset = 0; offset < 8; offset++) { + _cleanup_free_ char *buf2 = NULL; + + if (offset == 0) + data = buf; + else { + assert_se(buf2 = malloc(offset + data_size)); + memcpy(buf2 + offset, buf, data_size); + data = buf2 + offset; + } + + test_parse_auxv_two(elf_class, offset, data, data_size, + expect_at_secure, expect_uid, expect_euid, expect_gid, expect_egid); + } +} + +TEST(parse_auxv) { + _cleanup_free_ char *dir = NULL; + _cleanup_close_ int dir_fd = -EBADF; + + assert_se(get_testdata_dir("auxv", &dir) >= 0); + dir_fd = open(dir, O_RDONLY | O_CLOEXEC | O_DIRECTORY | O_PATH); + assert_se(dir_fd >= 0); + + if (__BYTE_ORDER == __LITTLE_ENDIAN) { + test_parse_auxv_one(ELFCLASS32, dir_fd, "resolved.arm32", 0, 193, 193, 193, 193); + test_parse_auxv_one(ELFCLASS64, dir_fd, "bash.riscv64", 0, 1001, 1001, 1001, 1001); + test_parse_auxv_one(ELFCLASS32, dir_fd, "sleep.i686", 0, 1000, 1000, 1000, 1000); + /* after chgrp and chmod g+s */ + test_parse_auxv_one(ELFCLASS32, dir_fd, "sleep32.i686", 1, 1000, 1000, 1000, 10); + test_parse_auxv_one(ELFCLASS64, dir_fd, "sleep64.amd64", 1, 1000, 1000, 1000, 10); + + test_parse_auxv_one(ELFCLASS64, dir_fd, "sudo.aarch64", 1, 1494200408, 0, 1494200408, 1494200408); + test_parse_auxv_one(ELFCLASS64, dir_fd, "sudo.amd64", 1, 1000, 0, 1000, 1000); + + /* Those run unprivileged, but start as root. */ + test_parse_auxv_one(ELFCLASS64, dir_fd, "dbus-broker-launch.amd64", 0, 0, 0, 0, 0); + test_parse_auxv_one(ELFCLASS64, dir_fd, "dbus-broker-launch.aarch64", 0, 0, 0, 0, 0); + test_parse_auxv_one(ELFCLASS64, dir_fd, "polkitd.aarch64", 0, 0, 0, 0, 0); + } else { + test_parse_auxv_one(ELFCLASS64, dir_fd, "cat.s390x", 0, 3481, 3481, 3481, 3481); + } +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-cpu-set-util.c b/src/test/test-cpu-set-util.c new file mode 100644 index 0000000..a0660f5 --- /dev/null +++ b/src/test/test-cpu-set-util.c @@ -0,0 +1,280 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "cpu-set-util.h" +#include "string-util.h" +#include "tests.h" +#include "macro.h" + +TEST(parse_cpu_set) { + CPUSet c = {}; + _cleanup_free_ char *str = NULL; + int cpu; + + /* Single value */ + assert_se(parse_cpu_set_full("0", &c, true, NULL, "fake", 1, "CPUAffinity") >= 0); + assert_se(c.set); + assert_se(c.allocated >= DIV_ROUND_UP(sizeof(__cpu_mask), 8)); + assert_se(CPU_ISSET_S(0, c.allocated, c.set)); + assert_se(CPU_COUNT_S(c.allocated, c.set) == 1); + + assert_se(str = cpu_set_to_string(&c)); + log_info("cpu_set_to_string: %s", str); + str = mfree(str); + assert_se(str = cpu_set_to_range_string(&c)); + log_info("cpu_set_to_range_string: %s", str); + assert_se(streq(str, "0")); + str = mfree(str); + cpu_set_reset(&c); + + /* Simple range (from CPUAffinity example) */ + assert_se(parse_cpu_set_full("1 2 4", &c, true, NULL, "fake", 1, "CPUAffinity") >= 0); + assert_se(c.set); + assert_se(c.allocated >= DIV_ROUND_UP(sizeof(__cpu_mask), 8)); + assert_se(CPU_ISSET_S(1, c.allocated, c.set)); + assert_se(CPU_ISSET_S(2, c.allocated, c.set)); + assert_se(CPU_ISSET_S(4, c.allocated, c.set)); + assert_se(CPU_COUNT_S(c.allocated, c.set) == 3); + + assert_se(str = cpu_set_to_string(&c)); + log_info("cpu_set_to_string: %s", str); + str = mfree(str); + assert_se(str = cpu_set_to_range_string(&c)); + log_info("cpu_set_to_range_string: %s", str); + assert_se(streq(str, "1-2 4")); + str = mfree(str); + cpu_set_reset(&c); + + /* A more interesting range */ + assert_se(parse_cpu_set_full("0 1 2 3 8 9 10 11", &c, true, NULL, "fake", 1, "CPUAffinity") >= 0); + assert_se(c.allocated >= DIV_ROUND_UP(sizeof(__cpu_mask), 8)); + assert_se(CPU_COUNT_S(c.allocated, c.set) == 8); + for (cpu = 0; cpu < 4; cpu++) + assert_se(CPU_ISSET_S(cpu, c.allocated, c.set)); + for (cpu = 8; cpu < 12; cpu++) + assert_se(CPU_ISSET_S(cpu, c.allocated, c.set)); + + assert_se(str = cpu_set_to_string(&c)); + log_info("cpu_set_to_string: %s", str); + str = mfree(str); + assert_se(str = cpu_set_to_range_string(&c)); + log_info("cpu_set_to_range_string: %s", str); + assert_se(streq(str, "0-3 8-11")); + str = mfree(str); + cpu_set_reset(&c); + + /* Quoted strings */ + assert_se(parse_cpu_set_full("8 '9' 10 \"11\"", &c, true, NULL, "fake", 1, "CPUAffinity") >= 0); + assert_se(c.allocated >= DIV_ROUND_UP(sizeof(__cpu_mask), 8)); + assert_se(CPU_COUNT_S(c.allocated, c.set) == 4); + for (cpu = 8; cpu < 12; cpu++) + assert_se(CPU_ISSET_S(cpu, c.allocated, c.set)); + assert_se(str = cpu_set_to_string(&c)); + log_info("cpu_set_to_string: %s", str); + str = mfree(str); + assert_se(str = cpu_set_to_range_string(&c)); + log_info("cpu_set_to_range_string: %s", str); + assert_se(streq(str, "8-11")); + str = mfree(str); + cpu_set_reset(&c); + + /* Use commas as separators */ + assert_se(parse_cpu_set_full("0,1,2,3 8,9,10,11", &c, true, NULL, "fake", 1, "CPUAffinity") >= 0); + assert_se(c.allocated >= DIV_ROUND_UP(sizeof(__cpu_mask), 8)); + assert_se(CPU_COUNT_S(c.allocated, c.set) == 8); + for (cpu = 0; cpu < 4; cpu++) + assert_se(CPU_ISSET_S(cpu, c.allocated, c.set)); + for (cpu = 8; cpu < 12; cpu++) + assert_se(CPU_ISSET_S(cpu, c.allocated, c.set)); + assert_se(str = cpu_set_to_string(&c)); + log_info("cpu_set_to_string: %s", str); + str = mfree(str); + cpu_set_reset(&c); + + /* Commas with spaces (and trailing comma, space) */ + assert_se(parse_cpu_set_full("0, 1, 2, 3, 4, 5, 6, 7, 63, ", &c, true, NULL, "fake", 1, "CPUAffinity") >= 0); + assert_se(c.allocated >= DIV_ROUND_UP(sizeof(__cpu_mask), 8)); + assert_se(CPU_COUNT_S(c.allocated, c.set) == 9); + for (cpu = 0; cpu < 8; cpu++) + assert_se(CPU_ISSET_S(cpu, c.allocated, c.set)); + + assert_se(CPU_ISSET_S(63, c.allocated, c.set)); + assert_se(str = cpu_set_to_string(&c)); + log_info("cpu_set_to_string: %s", str); + str = mfree(str); + assert_se(str = cpu_set_to_range_string(&c)); + log_info("cpu_set_to_range_string: %s", str); + assert_se(streq(str, "0-7 63")); + str = mfree(str); + cpu_set_reset(&c); + + /* Ranges */ + assert_se(parse_cpu_set_full("0-3,8-11", &c, true, NULL, "fake", 1, "CPUAffinity") >= 0); + assert_se(c.allocated >= DIV_ROUND_UP(sizeof(__cpu_mask), 8)); + assert_se(CPU_COUNT_S(c.allocated, c.set) == 8); + for (cpu = 0; cpu < 4; cpu++) + assert_se(CPU_ISSET_S(cpu, c.allocated, c.set)); + for (cpu = 8; cpu < 12; cpu++) + assert_se(CPU_ISSET_S(cpu, c.allocated, c.set)); + assert_se(str = cpu_set_to_string(&c)); + log_info("cpu_set_to_string: %s", str); + str = mfree(str); + cpu_set_reset(&c); + + /* Ranges with trailing comma, space */ + assert_se(parse_cpu_set_full("0-3 8-11, ", &c, true, NULL, "fake", 1, "CPUAffinity") >= 0); + assert_se(c.allocated >= DIV_ROUND_UP(sizeof(__cpu_mask), 8)); + assert_se(CPU_COUNT_S(c.allocated, c.set) == 8); + for (cpu = 0; cpu < 4; cpu++) + assert_se(CPU_ISSET_S(cpu, c.allocated, c.set)); + for (cpu = 8; cpu < 12; cpu++) + assert_se(CPU_ISSET_S(cpu, c.allocated, c.set)); + assert_se(str = cpu_set_to_string(&c)); + log_info("cpu_set_to_string: %s", str); + str = mfree(str); + assert_se(str = cpu_set_to_range_string(&c)); + log_info("cpu_set_to_range_string: %s", str); + assert_se(streq(str, "0-3 8-11")); + str = mfree(str); + cpu_set_reset(&c); + + /* Negative range (returns empty cpu_set) */ + assert_se(parse_cpu_set_full("3-0", &c, true, NULL, "fake", 1, "CPUAffinity") >= 0); + assert_se(c.allocated >= DIV_ROUND_UP(sizeof(__cpu_mask), 8)); + assert_se(CPU_COUNT_S(c.allocated, c.set) == 0); + cpu_set_reset(&c); + + /* Overlapping ranges */ + assert_se(parse_cpu_set_full("0-7 4-11", &c, true, NULL, "fake", 1, "CPUAffinity") >= 0); + assert_se(c.allocated >= DIV_ROUND_UP(sizeof(__cpu_mask), 8)); + assert_se(CPU_COUNT_S(c.allocated, c.set) == 12); + for (cpu = 0; cpu < 12; cpu++) + assert_se(CPU_ISSET_S(cpu, c.allocated, c.set)); + assert_se(str = cpu_set_to_string(&c)); + log_info("cpu_set_to_string: %s", str); + str = mfree(str); + assert_se(str = cpu_set_to_range_string(&c)); + log_info("cpu_set_to_range_string: %s", str); + assert_se(streq(str, "0-11")); + str = mfree(str); + cpu_set_reset(&c); + + /* Mix ranges and individual CPUs */ + assert_se(parse_cpu_set_full("0,2 4-11", &c, true, NULL, "fake", 1, "CPUAffinity") >= 0); + assert_se(c.allocated >= DIV_ROUND_UP(sizeof(__cpu_mask), 8)); + assert_se(CPU_COUNT_S(c.allocated, c.set) == 10); + assert_se(CPU_ISSET_S(0, c.allocated, c.set)); + assert_se(CPU_ISSET_S(2, c.allocated, c.set)); + for (cpu = 4; cpu < 12; cpu++) + assert_se(CPU_ISSET_S(cpu, c.allocated, c.set)); + assert_se(str = cpu_set_to_string(&c)); + log_info("cpu_set_to_string: %s", str); + str = mfree(str); + assert_se(str = cpu_set_to_range_string(&c)); + log_info("cpu_set_to_range_string: %s", str); + assert_se(streq(str, "0 2 4-11")); + str = mfree(str); + cpu_set_reset(&c); + + /* Garbage */ + assert_se(parse_cpu_set_full("0 1 2 3 garbage", &c, true, NULL, "fake", 1, "CPUAffinity") == -EINVAL); + assert_se(!c.set); + assert_se(c.allocated == 0); + + /* Range with garbage */ + assert_se(parse_cpu_set_full("0-3 8-garbage", &c, true, NULL, "fake", 1, "CPUAffinity") == -EINVAL); + assert_se(!c.set); + assert_se(c.allocated == 0); + + /* Empty string */ + assert_se(parse_cpu_set_full("", &c, true, NULL, "fake", 1, "CPUAffinity") == 0); + assert_se(!c.set); /* empty string returns NULL */ + assert_se(c.allocated == 0); + + /* Runaway quoted string */ + assert_se(parse_cpu_set_full("0 1 2 3 \"4 5 6 7 ", &c, true, NULL, "fake", 1, "CPUAffinity") == -EINVAL); + assert_se(!c.set); + assert_se(c.allocated == 0); + + /* Maximum allocation */ + assert_se(parse_cpu_set_full("8000-8191", &c, true, NULL, "fake", 1, "CPUAffinity") == 0); + assert_se(CPU_COUNT_S(c.allocated, c.set) == 192); + assert_se(str = cpu_set_to_string(&c)); + log_info("cpu_set_to_string: %s", str); + str = mfree(str); + assert_se(str = cpu_set_to_range_string(&c)); + log_info("cpu_set_to_range_string: %s", str); + assert_se(streq(str, "8000-8191")); + str = mfree(str); + cpu_set_reset(&c); +} + +TEST(parse_cpu_set_extend) { + CPUSet c = {}; + _cleanup_free_ char *s1 = NULL, *s2 = NULL; + + assert_se(parse_cpu_set_extend("1 3", &c, true, NULL, "fake", 1, "CPUAffinity") == 1); + assert_se(CPU_COUNT_S(c.allocated, c.set) == 2); + assert_se(s1 = cpu_set_to_string(&c)); + log_info("cpu_set_to_string: %s", s1); + + assert_se(parse_cpu_set_extend("4", &c, true, NULL, "fake", 1, "CPUAffinity") == 1); + assert_se(CPU_COUNT_S(c.allocated, c.set) == 3); + assert_se(s2 = cpu_set_to_string(&c)); + log_info("cpu_set_to_string: %s", s2); + + assert_se(parse_cpu_set_extend("", &c, true, NULL, "fake", 1, "CPUAffinity") == 0); + assert_se(!c.set); + assert_se(c.allocated == 0); + log_info("cpu_set_to_string: (null)"); +} + +TEST(cpu_set_to_from_dbus) { + _cleanup_(cpu_set_reset) CPUSet c = {}, c2 = {}; + _cleanup_free_ char *s = NULL; + + assert_se(parse_cpu_set_extend("1 3 8 100-200", &c, true, NULL, "fake", 1, "CPUAffinity") == 1); + assert_se(s = cpu_set_to_string(&c)); + log_info("cpu_set_to_string: %s", s); + assert_se(CPU_COUNT_S(c.allocated, c.set) == 104); + + _cleanup_free_ uint8_t *array = NULL; + size_t allocated; + static const char expected[32] = + "\x0A\x01\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\xF0\xFF\xFF\xFF\xFF\xFF\xFF\xFF" + "\xFF\xFF\xFF\xFF\xFF\x01"; + + assert_se(cpu_set_to_dbus(&c, &array, &allocated) == 0); + assert_se(array); + assert_se(allocated == c.allocated); + + assert_se(allocated <= sizeof expected); + assert_se(allocated >= DIV_ROUND_UP(201u, 8u)); /* We need at least 201 bits for our mask */ + assert_se(memcmp(array, expected, allocated) == 0); + + assert_se(cpu_set_from_dbus(array, allocated, &c2) == 0); + assert_se(c2.set); + assert_se(c2.allocated == c.allocated); + assert_se(memcmp(c.set, c2.set, c.allocated) == 0); +} + +TEST(cpus_in_affinity_mask) { + int r; + + r = cpus_in_affinity_mask(); + assert_se(r > 0); + log_info("cpus_in_affinity_mask: %d", r); +} + +TEST(print_cpu_alloc_size) { + log_info("CPU_ALLOC_SIZE(1) = %zu", CPU_ALLOC_SIZE(1)); + log_info("CPU_ALLOC_SIZE(9) = %zu", CPU_ALLOC_SIZE(9)); + log_info("CPU_ALLOC_SIZE(64) = %zu", CPU_ALLOC_SIZE(64)); + log_info("CPU_ALLOC_SIZE(65) = %zu", CPU_ALLOC_SIZE(65)); + log_info("CPU_ALLOC_SIZE(1024) = %zu", CPU_ALLOC_SIZE(1024)); + log_info("CPU_ALLOC_SIZE(1025) = %zu", CPU_ALLOC_SIZE(1025)); + log_info("CPU_ALLOC_SIZE(8191) = %zu", CPU_ALLOC_SIZE(8191)); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-creds.c b/src/test/test-creds.c new file mode 100644 index 0000000..acb198c --- /dev/null +++ b/src/test/test-creds.c @@ -0,0 +1,121 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "creds-util.h" +#include "fileio.h" +#include "path-util.h" +#include "rm-rf.h" +#include "tests.h" +#include "tmpfile-util.h" + +TEST(read_credential_strings) { + _cleanup_free_ char *x = NULL, *y = NULL, *saved = NULL, *p = NULL; + _cleanup_(rm_rf_physical_and_freep) char *tmp = NULL; + _cleanup_fclose_ FILE *f = NULL; + + const char *e = getenv("CREDENTIALS_DIRECTORY"); + if (e) + assert_se(saved = strdup(e)); + + assert_se(read_credential_strings_many("foo", &x, "bar", &y) == 0); + assert_se(x == NULL); + assert_se(y == NULL); + + assert_se(mkdtemp_malloc(NULL, &tmp) >= 0); + + assert_se(setenv("CREDENTIALS_DIRECTORY", tmp, /* override= */ true) >= 0); + + assert_se(read_credential_strings_many("foo", &x, "bar", &y) == 0); + assert_se(x == NULL); + assert_se(y == NULL); + + assert_se(p = path_join(tmp, "bar")); + assert_se(write_string_file(p, "piff", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE) >= 0); + + assert_se(read_credential_strings_many("foo", &x, "bar", &y) == 0); + assert_se(x == NULL); + assert_se(streq(y, "piff")); + + assert_se(write_string_file(p, "paff", WRITE_STRING_FILE_TRUNCATE|WRITE_STRING_FILE_AVOID_NEWLINE) >= 0); + + assert_se(read_credential_strings_many("foo", &x, "bar", &y) == 0); + assert_se(x == NULL); + assert_se(streq(y, "piff")); + + p = mfree(p); + assert_se(p = path_join(tmp, "foo")); + assert_se(write_string_file(p, "knurz", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE) >= 0); + + assert_se(read_credential_strings_many("foo", &x, "bar", &y) >= 0); + assert_se(streq(x, "knurz")); + assert_se(streq(y, "piff")); + + y = mfree(y); + + assert_se(read_credential_strings_many("foo", &x, "bar", &y) >= 0); + assert_se(streq(x, "knurz")); + assert_se(streq(y, "paff")); + + p = mfree(p); + assert_se(p = path_join(tmp, "bazz")); + assert_se(f = fopen(p, "w")); + assert_se(fwrite("x\0y", 1, 3, f) == 3); /* embedded NUL byte should result in EBADMSG when reading back with read_credential_strings_many() */ + f = safe_fclose(f); + + assert_se(read_credential_strings_many("bazz", &x, "foo", &y) == -EBADMSG); + assert_se(streq(x, "knurz")); + assert_se(streq(y, "paff")); + + if (saved) + assert_se(setenv("CREDENTIALS_DIRECTORY", saved, /* override= */ 1) >= 0); + else + assert_se(unsetenv("CREDENTIALS_DIRECTORY") >= 0); +} + +TEST(credential_name_valid) { + char buf[NAME_MAX+2]; + + assert_se(!credential_name_valid(NULL)); + assert_se(!credential_name_valid("")); + assert_se(!credential_name_valid(".")); + assert_se(!credential_name_valid("..")); + assert_se(!credential_name_valid("foo/bar")); + assert_se(credential_name_valid("foo")); + + memset(buf, 'x', sizeof(buf)-1); + buf[sizeof(buf)-1] = 0; + assert_se(!credential_name_valid(buf)); + + buf[sizeof(buf)-2] = 0; + assert_se(credential_name_valid(buf)); +} + +TEST(credential_glob_valid) { + char buf[NAME_MAX+2]; + + assert_se(!credential_glob_valid(NULL)); + assert_se(!credential_glob_valid("")); + assert_se(!credential_glob_valid(".")); + assert_se(!credential_glob_valid("..")); + assert_se(!credential_glob_valid("foo/bar")); + assert_se(credential_glob_valid("foo")); + assert_se(credential_glob_valid("foo*")); + assert_se(credential_glob_valid("x*")); + assert_se(credential_glob_valid("*")); + assert_se(!credential_glob_valid("?")); + assert_se(!credential_glob_valid("*a")); + assert_se(!credential_glob_valid("a?")); + assert_se(!credential_glob_valid("a[abc]")); + assert_se(!credential_glob_valid("a[abc]")); + + memset(buf, 'x', sizeof(buf)-1); + buf[sizeof(buf)-1] = 0; + assert_se(!credential_glob_valid(buf)); + + buf[sizeof(buf)-2] = 0; + assert_se(credential_glob_valid(buf)); + + buf[sizeof(buf)-2] = '*'; + assert_se(credential_glob_valid(buf)); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-cryptolib.c b/src/test/test-cryptolib.c new file mode 100644 index 0000000..6202a5d --- /dev/null +++ b/src/test/test-cryptolib.c @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "gcrypt-util.h" +#include "macro.h" +#include "openssl-util.h" +#include "string-util.h" +#include "tests.h" + +TEST(string_hashsum) { + _cleanup_free_ char *out1 = NULL, *out2 = NULL, *out3 = NULL, *out4 = NULL; + + assert_se(string_hashsum("asdf", 4, + OPENSSL_OR_GCRYPT("SHA224", GCRY_MD_SHA224), + &out1) == 0); + /* echo -n 'asdf' | sha224sum - */ + assert_se(streq(out1, "7872a74bcbf298a1e77d507cd95d4f8d96131cbbd4cdfc571e776c8a")); + + assert_se(string_hashsum("asdf", 4, + OPENSSL_OR_GCRYPT("SHA256", GCRY_MD_SHA256), + &out2) == 0); + /* echo -n 'asdf' | sha256sum - */ + assert_se(streq(out2, "f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b")); + + assert_se(string_hashsum("", 0, + OPENSSL_OR_GCRYPT("SHA224", GCRY_MD_SHA224), + &out3) == 0); + /* echo -n '' | sha224sum - */ + assert_se(streq(out3, "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f")); + + assert_se(string_hashsum("", 0, + OPENSSL_OR_GCRYPT("SHA256", GCRY_MD_SHA256), + &out4) == 0); + /* echo -n '' | sha256sum - */ + assert_se(streq(out4, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-daemon.c b/src/test/test-daemon.c new file mode 100644 index 0000000..b880521 --- /dev/null +++ b/src/test/test-daemon.c @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <unistd.h> + +#include "sd-daemon.h" + +#include "parse-util.h" +#include "strv.h" +#include "time-util.h" +#include "tests.h" + +int main(int argc, char *argv[]) { + _cleanup_strv_free_ char **l = NULL; + int n, i; + usec_t duration = USEC_PER_SEC / 10; + + test_setup_logging(LOG_DEBUG); + + if (argc >= 2) { + unsigned x; + + assert_se(safe_atou(argv[1], &x) >= 0); + duration = x * USEC_PER_SEC; + } + + n = sd_listen_fds_with_names(false, &l); + if (n < 0) { + log_error_errno(n, "Failed to get listening fds: %m"); + return EXIT_FAILURE; + } + + for (i = 0; i < n; i++) + log_info("fd=%i name=%s", SD_LISTEN_FDS_START + i, l[i]); + + sd_notify(0, + "STATUS=Starting up"); + usleep_safe(duration); + + sd_notify(0, + "STATUS=Running\n" + "READY=1"); + usleep_safe(duration); + + sd_notify(0, + "STATUS=Reloading\n" + "RELOADING=1"); + usleep_safe(duration); + + sd_notify(0, + "STATUS=Running\n" + "READY=1"); + usleep_safe(duration); + + sd_notify(0, + "STATUS=Quitting\n" + "STOPPING=1"); + usleep_safe(duration); + + return EXIT_SUCCESS; +} diff --git a/src/test/test-data-fd-util.c b/src/test/test-data-fd-util.c new file mode 100644 index 0000000..aa68132 --- /dev/null +++ b/src/test/test-data-fd-util.c @@ -0,0 +1,148 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#include "data-fd-util.h" +#include "fd-util.h" +#include "memory-util.h" +#include "process-util.h" +#include "tests.h" +#include "random-util.h" + +static void test_acquire_data_fd_one(unsigned flags) { + char wbuffer[196*1024 - 7]; + char rbuffer[sizeof(wbuffer)]; + int fd; + + fd = acquire_data_fd("foo", 3, flags); + assert_se(fd >= 0); + + zero(rbuffer); + assert_se(read(fd, rbuffer, sizeof(rbuffer)) == 3); + assert_se(streq(rbuffer, "foo")); + + fd = safe_close(fd); + + fd = acquire_data_fd("", 0, flags); + assert_se(fd >= 0); + + zero(rbuffer); + assert_se(read(fd, rbuffer, sizeof(rbuffer)) == 0); + assert_se(streq(rbuffer, "")); + + fd = safe_close(fd); + + random_bytes(wbuffer, sizeof(wbuffer)); + + fd = acquire_data_fd(wbuffer, sizeof(wbuffer), flags); + assert_se(fd >= 0); + + zero(rbuffer); + assert_se(read(fd, rbuffer, sizeof(rbuffer)) == sizeof(rbuffer)); + assert_se(memcmp(rbuffer, wbuffer, sizeof(rbuffer)) == 0); + + fd = safe_close(fd); +} + +TEST(acquire_data_fd) { + test_acquire_data_fd_one(0); + test_acquire_data_fd_one(ACQUIRE_NO_DEV_NULL); + test_acquire_data_fd_one(ACQUIRE_NO_MEMFD); + test_acquire_data_fd_one(ACQUIRE_NO_DEV_NULL|ACQUIRE_NO_MEMFD); + test_acquire_data_fd_one(ACQUIRE_NO_PIPE); + test_acquire_data_fd_one(ACQUIRE_NO_DEV_NULL|ACQUIRE_NO_PIPE); + test_acquire_data_fd_one(ACQUIRE_NO_MEMFD|ACQUIRE_NO_PIPE); + test_acquire_data_fd_one(ACQUIRE_NO_DEV_NULL|ACQUIRE_NO_MEMFD|ACQUIRE_NO_PIPE); + test_acquire_data_fd_one(ACQUIRE_NO_DEV_NULL|ACQUIRE_NO_MEMFD|ACQUIRE_NO_PIPE|ACQUIRE_NO_TMPFILE); +} + +static void assert_equal_fd(int fd1, int fd2) { + for (;;) { + uint8_t a[4096], b[4096]; + ssize_t x, y; + + x = read(fd1, a, sizeof(a)); + assert_se(x >= 0); + + y = read(fd2, b, sizeof(b)); + assert_se(y >= 0); + + assert_se(x == y); + + if (x == 0) + break; + + assert_se(memcmp(a, b, x) == 0); + } +} + +TEST(copy_data_fd) { + _cleanup_close_ int fd1 = -EBADF, fd2 = -EBADF; + _cleanup_close_pair_ int sfd[2] = EBADF_PAIR; + _cleanup_(sigkill_waitp) pid_t pid = -1; + int r; + + fd1 = open("/etc/fstab", O_RDONLY|O_CLOEXEC); + if (fd1 >= 0) { + + fd2 = copy_data_fd(fd1); + assert_se(fd2 >= 0); + + assert_se(lseek(fd1, 0, SEEK_SET) == 0); + assert_equal_fd(fd1, fd2); + } + + fd1 = safe_close(fd1); + fd2 = safe_close(fd2); + + fd1 = acquire_data_fd("hallo", 6, 0); + assert_se(fd1 >= 0); + + fd2 = copy_data_fd(fd1); + assert_se(fd2 >= 0); + + safe_close(fd1); + fd1 = acquire_data_fd("hallo", 6, 0); + assert_se(fd1 >= 0); + + assert_equal_fd(fd1, fd2); + + fd1 = safe_close(fd1); + fd2 = safe_close(fd2); + + assert_se(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, sfd) >= 0); + + r = safe_fork("(sd-pipe)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG, &pid); + assert_se(r >= 0); + + if (r == 0) { + /* child */ + + sfd[0] = safe_close(sfd[0]); + + for (uint64_t i = 0; i < 1536*1024 / sizeof(uint64_t); i++) + assert_se(write(sfd[1], &i, sizeof(i)) == sizeof(i)); + + sfd[1] = safe_close(sfd[1]); + + _exit(EXIT_SUCCESS); + } + + sfd[1] = safe_close(sfd[1]); + + fd2 = copy_data_fd(sfd[0]); + assert_se(fd2 >= 0); + + uint64_t j; + for (uint64_t i = 0; i < 1536*1024 / sizeof(uint64_t); i++) { + assert_se(read(fd2, &j, sizeof(j)) == sizeof(j)); + assert_se(i == j); + } + + assert_se(read(fd2, &j, sizeof(j)) == 0); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-date.c b/src/test/test-date.c new file mode 100644 index 0000000..162ac34 --- /dev/null +++ b/src/test/test-date.c @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "string-util.h" +#include "tests.h" +#include "time-util.h" + +static void test_should_pass(const char *p) { + usec_t t, q; + char buf[FORMAT_TIMESTAMP_MAX], buf_relative[FORMAT_TIMESTAMP_RELATIVE_MAX]; + + log_info("Test: %s", p); + assert_se(parse_timestamp(p, &t) >= 0); + assert_se(format_timestamp_style(buf, sizeof(buf), t, TIMESTAMP_US)); + log_info("\"%s\" → \"%s\"", p, buf); + + assert_se(parse_timestamp(buf, &q) >= 0); + if (q != t) + log_error("round-trip failed: \"%s\" → \"%s\"", + buf, FORMAT_TIMESTAMP_STYLE(q, TIMESTAMP_US)); + assert_se(q == t); + + assert_se(format_timestamp_relative(buf_relative, sizeof(buf_relative), t)); + log_info("%s", strna(buf_relative)); +} + +static void test_should_parse(const char *p) { + usec_t t; + + log_info("Test: %s", p); + assert_se(parse_timestamp(p, &t) >= 0); + log_info("\"%s\" → \"@%" PRI_USEC "\"", p, t); +} + +static void test_should_fail(const char *p) { + usec_t t; + int r; + + log_info("Test: %s", p); + r = parse_timestamp(p, &t); + if (r >= 0) + log_info("\"%s\" → \"@%" PRI_USEC "\" (unexpected)", p, t); + else + log_info("parse_timestamp() returns %d (expected)", r); + assert_se(r < 0); +} + +static void test_one(const char *p) { + _cleanup_free_ char *with_utc = NULL; + + with_utc = strjoin(p, " UTC"); + test_should_pass(p); + test_should_pass(with_utc); +} + +static void test_one_noutc(const char *p) { + _cleanup_free_ char *with_utc = NULL; + + with_utc = strjoin(p, " UTC"); + test_should_pass(p); + test_should_fail(with_utc); +} + +int main(int argc, char *argv[]) { + /* Tests have hard-coded results that do not expect a specific timezone to be set by the caller */ + assert_se(unsetenv("TZ") >= 0); + + test_setup_logging(LOG_DEBUG); + + test_one("17:41"); + test_one("18:42:44"); + test_one("18:42:44.0"); + test_one("18:42:44.999999999999"); + test_one("12-10-02 12:13:14"); + test_one("12-10-2 12:13:14"); + test_one("12-10-03 12:13"); + test_one("2012-12-30 18:42"); + test_one("2012-10-02"); + test_one("Mar 12 12:01:01"); + test_one("Mar 12 12:01:01.687197"); + test_one("Tue 2012-10-02"); + test_one("yesterday"); + test_one("today"); + test_one("tomorrow"); + test_one_noutc("16:20 UTC"); + test_one_noutc("16:20 Asia/Seoul"); + test_one_noutc("tomorrow Asia/Seoul"); + test_one_noutc("2012-12-30 18:42 Asia/Seoul"); + test_one_noutc("now"); + test_one_noutc("+2d"); + test_one_noutc("+2y 4d"); + test_one_noutc("5months ago"); + test_one_noutc("@1395716396"); + test_should_parse("1970-1-1 UTC"); + test_should_pass("1970-1-1 00:00:01 UTC"); + test_should_fail("1969-12-31 UTC"); + test_should_fail("-1000y"); + test_should_fail("today UTC UTC"); + test_should_fail("now Asia/Seoul"); + test_should_fail("+2d Asia/Seoul"); + test_should_fail("@1395716396 Asia/Seoul"); +#if SIZEOF_TIME_T == 8 + test_should_pass("9999-12-30 23:59:59 UTC"); + test_should_fail("9999-12-31 00:00:00 UTC"); + test_should_fail("10000-01-01 00:00:00 UTC"); +#elif SIZEOF_TIME_T == 4 + test_should_pass("2038-01-18 03:14:07 UTC"); + test_should_fail("2038-01-18 03:14:08 UTC"); +#endif + + return 0; +} diff --git a/src/test/test-dev-setup.c b/src/test/test-dev-setup.c new file mode 100644 index 0000000..b75576a --- /dev/null +++ b/src/test/test-dev-setup.c @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "capability-util.h" +#include "dev-setup.h" +#include "fs-util.h" +#include "mkdir.h" +#include "path-util.h" +#include "rm-rf.h" +#include "tests.h" +#include "tmpfile-util.h" + +int main(int argc, char *argv[]) { + _cleanup_(rm_rf_physical_and_freep) char *p = NULL; + const char *f; + struct stat st; + + test_setup_logging(LOG_DEBUG); + + if (have_effective_cap(CAP_DAC_OVERRIDE) <= 0) + return log_tests_skipped("missing capability (CAP_DAC_OVERRIDE)"); + + assert_se(mkdtemp_malloc("/tmp/test-dev-setupXXXXXX", &p) >= 0); + + f = prefix_roota(p, "/run/systemd"); + assert_se(mkdir_p(f, 0755) >= 0); + + assert_se(make_inaccessible_nodes(f, 1, 1) >= 0); + + f = prefix_roota(p, "/run/systemd/inaccessible/reg"); + assert_se(stat(f, &st) >= 0); + assert_se(S_ISREG(st.st_mode)); + assert_se((st.st_mode & 07777) == 0000); + + f = prefix_roota(p, "/run/systemd/inaccessible/dir"); + assert_se(stat(f, &st) >= 0); + assert_se(S_ISDIR(st.st_mode)); + assert_se((st.st_mode & 07777) == 0000); + + f = prefix_roota(p, "/run/systemd/inaccessible/fifo"); + assert_se(stat(f, &st) >= 0); + assert_se(S_ISFIFO(st.st_mode)); + assert_se((st.st_mode & 07777) == 0000); + + f = prefix_roota(p, "/run/systemd/inaccessible/sock"); + assert_se(stat(f, &st) >= 0); + assert_se(S_ISSOCK(st.st_mode)); + assert_se((st.st_mode & 07777) == 0000); + + f = prefix_roota(p, "/run/systemd/inaccessible/chr"); + if (stat(f, &st) < 0) + assert_se(errno == ENOENT); + else { + assert_se(S_ISCHR(st.st_mode)); + assert_se((st.st_mode & 07777) == 0000); + } + + f = prefix_roota(p, "/run/systemd/inaccessible/blk"); + if (stat(f, &st) < 0) + assert_se(errno == ENOENT); + else { + assert_se(S_ISBLK(st.st_mode)); + assert_se((st.st_mode & 07777) == 0000); + } + + return EXIT_SUCCESS; +} diff --git a/src/test/test-device-nodes.c b/src/test/test-device-nodes.c new file mode 100644 index 0000000..36fa2ce --- /dev/null +++ b/src/test/test-device-nodes.c @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdio.h> +#include <sys/types.h> + +#include "alloc-util.h" +#include "device-nodes.h" +#include "string-util.h" +#include "tests.h" + +/* helpers for test_encode_devnode_name */ +static char *do_encode_string(const char *in) { + size_t out_len = strlen(in) * 4 + 1; + char *out = malloc(out_len); + + assert_se(out); + assert_se(encode_devnode_name(in, out, out_len) >= 0); + puts(out); + + return out; +} + +static bool expect_encoded_as(const char *in, const char *expected) { + _cleanup_free_ char *encoded = do_encode_string(in); + return streq(encoded, expected); +} + +TEST(encode_devnode_name) { + assert_se(expect_encoded_as("systemd sucks", "systemd\\x20sucks")); + assert_se(expect_encoded_as("pinkiepie", "pinkiepie")); + assert_se(expect_encoded_as("valíd\\ųtf8", "valíd\\x5cųtf8")); + assert_se(expect_encoded_as("s/ash/ng", "s\\x2fash\\x2fng")); + assert_se(expect_encoded_as("/", "\\x2f")); + assert_se(expect_encoded_as("!", "\\x21")); + assert_se(expect_encoded_as("QEMU ", "QEMU\\x20\\x20\\x20\\x20")); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-devnum-util.c b/src/test/test-devnum-util.c new file mode 100644 index 0000000..2068e35 --- /dev/null +++ b/src/test/test-devnum-util.c @@ -0,0 +1,124 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <sys/stat.h> + +#include "devnum-util.h" +#include "path-util.h" +#include "stat-util.h" +#include "tests.h" + +TEST(parse_devnum) { + dev_t dev; + + assert_se(parse_devnum("", &dev) == -EINVAL); + assert_se(parse_devnum("junk", &dev) == -EINVAL); + assert_se(parse_devnum("0", &dev) == -EINVAL); + assert_se(parse_devnum("5", &dev) == -EINVAL); + assert_se(parse_devnum("5:", &dev) == -EINVAL); + assert_se(parse_devnum(":5", &dev) == -EINVAL); + assert_se(parse_devnum("-1:-1", &dev) == -EINVAL); +#if SIZEOF_DEV_T < 8 + assert_se(parse_devnum("4294967295:4294967295", &dev) == -EINVAL); +#endif + assert_se(parse_devnum("8:11", &dev) >= 0 && major(dev) == 8 && minor(dev) == 11); + assert_se(parse_devnum("0:0", &dev) >= 0 && major(dev) == 0 && minor(dev) == 0); +} + +TEST(device_major_minor_valid) { + /* on glibc dev_t is 64-bit, even though in the kernel it is only 32-bit */ + assert_cc(sizeof(dev_t) == sizeof(uint64_t)); + + assert_se(DEVICE_MAJOR_VALID(0U)); + assert_se(DEVICE_MINOR_VALID(0U)); + + assert_se(DEVICE_MAJOR_VALID(1U)); + assert_se(DEVICE_MINOR_VALID(1U)); + + assert_se(!DEVICE_MAJOR_VALID(-1U)); + assert_se(!DEVICE_MINOR_VALID(-1U)); + + assert_se(DEVICE_MAJOR_VALID(1U << 10)); + assert_se(DEVICE_MINOR_VALID(1U << 10)); + + assert_se(DEVICE_MAJOR_VALID((1U << 12) - 1)); + assert_se(DEVICE_MINOR_VALID((1U << 20) - 1)); + + assert_se(!DEVICE_MAJOR_VALID((1U << 12))); + assert_se(!DEVICE_MINOR_VALID((1U << 20))); + + assert_se(!DEVICE_MAJOR_VALID(1U << 25)); + assert_se(!DEVICE_MINOR_VALID(1U << 25)); + + assert_se(!DEVICE_MAJOR_VALID(UINT32_MAX)); + assert_se(!DEVICE_MINOR_VALID(UINT32_MAX)); + + assert_se(!DEVICE_MAJOR_VALID(UINT64_MAX)); + assert_se(!DEVICE_MINOR_VALID(UINT64_MAX)); + + assert_se(DEVICE_MAJOR_VALID(major(0))); + assert_se(DEVICE_MINOR_VALID(minor(0))); +} + +static void test_device_path_make_canonical_one(const char *path) { + _cleanup_free_ char *resolved = NULL, *raw = NULL; + struct stat st; + dev_t devno; + mode_t mode; + int r; + + log_debug("> %s", path); + + if (stat(path, &st) < 0) { + assert_se(errno == ENOENT); + log_notice("Path %s not found, skipping test", path); + return; + } + + r = device_path_make_canonical(st.st_mode, st.st_rdev, &resolved); + if (r == -ENOENT) { + /* maybe /dev/char/x:y and /dev/block/x:y are missing in this test environment, because we + * run in a container or so? */ + log_notice("Device %s cannot be resolved, skipping test", path); + return; + } + + assert_se(r >= 0); + assert_se(path_equal(path, resolved)); + + assert_se(device_path_make_major_minor(st.st_mode, st.st_rdev, &raw) >= 0); + assert_se(device_path_parse_major_minor(raw, &mode, &devno) >= 0); + + assert_se(st.st_rdev == devno); + assert_se((st.st_mode & S_IFMT) == (mode & S_IFMT)); +} + +TEST(device_path_make_canonical) { + test_device_path_make_canonical_one("/dev/null"); + test_device_path_make_canonical_one("/dev/zero"); + test_device_path_make_canonical_one("/dev/full"); + test_device_path_make_canonical_one("/dev/random"); + test_device_path_make_canonical_one("/dev/urandom"); + test_device_path_make_canonical_one("/dev/tty"); + + if (is_device_node("/run/systemd/inaccessible/blk") > 0) { + test_device_path_make_canonical_one("/run/systemd/inaccessible/chr"); + test_device_path_make_canonical_one("/run/systemd/inaccessible/blk"); + } +} + +static void test_devnum_format_str_one(dev_t devnum, const char *s) { + dev_t x; + + assert_se(streq(FORMAT_DEVNUM(devnum), s)); + assert_se(parse_devnum(s, &x) >= 0); + assert_se(x == devnum); +} + +TEST(devnum_format_str) { + test_devnum_format_str_one(makedev(0, 0), "0:0"); + test_devnum_format_str_one(makedev(1, 2), "1:2"); + test_devnum_format_str_one(makedev(99, 100), "99:100"); + test_devnum_format_str_one(makedev(4095, 1048575), "4095:1048575"); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-dlopen-so.c b/src/test/test-dlopen-so.c new file mode 100644 index 0000000..e98b8da --- /dev/null +++ b/src/test/test-dlopen-so.c @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <dlfcn.h> +#include <stdlib.h> + +#include "bpf-dlopen.h" +#include "cryptsetup-util.h" +#include "elf-util.h" +#include "idn-util.h" +#include "libfido2-util.h" +#include "macro.h" +#include "main-func.h" +#include "password-quality-util-passwdqc.h" +#include "password-quality-util-pwquality.h" +#include "pcre2-util.h" +#include "pkcs11-util.h" +#include "qrcode-util.h" +#include "tests.h" +#include "tpm2-util.h" + +static int run(int argc, char **argv) { + test_setup_logging(LOG_DEBUG); + + /* Try to load each of our weak library dependencies once. This is supposed to help finding cases + * where .so versions change and distributions update, but systemd doesn't have the new so names + * around yet. */ + +#if HAVE_LIBIDN2 || HAVE_LIBIDN + assert_se(dlopen_idn() >= 0); +#endif + +#if HAVE_LIBCRYPTSETUP + assert_se(dlopen_cryptsetup() >= 0); +#endif + +#if HAVE_PASSWDQC + assert_se(dlopen_passwdqc() >= 0); +#endif + +#if HAVE_PWQUALITY + assert_se(dlopen_pwquality() >= 0); +#endif + +#if HAVE_QRENCODE + assert_se(dlopen_qrencode() >= 0); +#endif + +#if HAVE_TPM2 + assert_se(dlopen_tpm2() >= 0); +#endif + +#if HAVE_LIBFIDO2 + assert_se(dlopen_libfido2() >= 0); +#endif + +#if HAVE_LIBBPF + assert_se(dlopen_bpf() >= 0); +#endif + +#if HAVE_ELFUTILS + assert_se(dlopen_dw() >= 0); + assert_se(dlopen_elf() >= 0); +#endif + +#if HAVE_PCRE2 + assert_se(dlopen_pcre2() >= 0); +#endif + +#if HAVE_P11KIT + assert_se(dlopen_p11kit() >= 0); +#endif + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/test/test-dlopen.c b/src/test/test-dlopen.c new file mode 100644 index 0000000..9c31537 --- /dev/null +++ b/src/test/test-dlopen.c @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <dlfcn.h> +#include <stdlib.h> + +#include "macro.h" + +int main(int argc, char **argv) { + void *handles[argc - 1]; + int i; + + for (i = 0; i < argc - 1; i++) + assert_se(handles[i] = dlopen(argv[i + 1], RTLD_NOW)); + + for (i--; i >= 0; i--) + assert_se(dlclose(handles[i]) == 0); + + return EXIT_SUCCESS; +} diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c new file mode 100644 index 0000000..6c107e2 --- /dev/null +++ b/src/test/test-dns-domain.c @@ -0,0 +1,753 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "dns-domain.h" +#include "macro.h" +#include "string-util.h" +#include "tests.h" + +static void test_dns_label_unescape_one(const char *what, const char *expect, size_t buffer_sz, int ret, int ret_ldh) { + char buffer[buffer_sz]; + int r; + const char *w = what; + + log_info("%s, %s, %zu, →%d/%d", what, expect, buffer_sz, ret, ret_ldh); + + r = dns_label_unescape(&w, buffer, buffer_sz, 0); + assert_se(r == ret); + if (r >= 0) + assert_se(streq(buffer, expect)); + + w = what; + r = dns_label_unescape(&w, buffer, buffer_sz, DNS_LABEL_LDH); + assert_se(r == ret_ldh); + if (r >= 0) + assert_se(streq(buffer, expect)); + + w = what; + r = dns_label_unescape(&w, buffer, buffer_sz, DNS_LABEL_NO_ESCAPES); + const int ret_noe = strchr(what, '\\') ? -EINVAL : ret; + assert_se(r == ret_noe); + if (r >= 0) + assert_se(streq(buffer, expect)); +} + +TEST(dns_label_unescape) { + test_dns_label_unescape_one("hallo", "hallo", 6, 5, 5); + test_dns_label_unescape_one("hallo", "hallo", 4, -ENOBUFS, -ENOBUFS); + test_dns_label_unescape_one("", "", 10, 0, 0); + test_dns_label_unescape_one("hallo\\.foobar", "hallo.foobar", 20, 12, -EINVAL); + test_dns_label_unescape_one("hallo.foobar", "hallo", 10, 5, 5); + test_dns_label_unescape_one("hallo\n.foobar", "hallo", 20, -EINVAL, -EINVAL); + test_dns_label_unescape_one("hallo\\", "hallo", 20, -EINVAL, -EINVAL); + test_dns_label_unescape_one("hallo\\032 ", "hallo ", 20, 7, -EINVAL); + test_dns_label_unescape_one(".", "", 20, 0, 0); + test_dns_label_unescape_one("..", "", 20, -EINVAL, -EINVAL); + test_dns_label_unescape_one(".foobar", "", 20, -EINVAL, -EINVAL); + test_dns_label_unescape_one("foobar.", "foobar", 20, 6, 6); + test_dns_label_unescape_one("foobar..", "foobar", 20, -EINVAL, -EINVAL); + test_dns_label_unescape_one("foo-bar", "foo-bar", 20, 7, 7); + test_dns_label_unescape_one("foo-", "foo-", 20, 4, -EINVAL); + test_dns_label_unescape_one("-foo", "-foo", 20, 4, -EINVAL); + test_dns_label_unescape_one("-foo-", "-foo-", 20, 5, -EINVAL); + test_dns_label_unescape_one("foo-.", "foo-", 20, 4, -EINVAL); + test_dns_label_unescape_one("foo.-", "foo", 20, 3, 3); + test_dns_label_unescape_one("foo\\032", "foo ", 20, 4, -EINVAL); + test_dns_label_unescape_one("foo\\045", "foo-", 20, 4, -EINVAL); + test_dns_label_unescape_one("głąb", "głąb", 20, 6, -EINVAL); +} + +static void test_dns_name_to_wire_format_one(const char *what, const char *expect, size_t buffer_sz, int ret) { + uint8_t buffer[buffer_sz]; + int r; + + log_info("%s, %s, %zu, →%d", what, strnull(expect), buffer_sz, ret); + + r = dns_name_to_wire_format(what, buffer, buffer_sz, false); + assert_se(r == ret); + + if (r >= 0) { + assert(expect); /* for gcc */ + assert_se(memcmp(buffer, expect, r) == 0); + } +} + +TEST(dns_name_to_wire_format) { + static const char out0[] = { 0 }; + static const char out1[] = { 3, 'f', 'o', 'o', 0 }; + static const char out2[] = { 5, 'h', 'a', 'l', 'l', 'o', 3, 'f', 'o', 'o', 3, 'b', 'a', 'r', 0 }; + static const char out3[] = { 4, ' ', 'f', 'o', 'o', 3, 'b', 'a', 'r', 0 }; + static const char out4[] = { 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8', + 3, 'a', '1', '2', 0 }; + + test_dns_name_to_wire_format_one("", out0, sizeof(out0), sizeof(out0)); + + test_dns_name_to_wire_format_one("foo", out1, sizeof(out1), sizeof(out1)); + test_dns_name_to_wire_format_one("foo", out1, sizeof(out1) + 1, sizeof(out1)); + test_dns_name_to_wire_format_one("foo", out1, sizeof(out1) - 1, -ENOBUFS); + + test_dns_name_to_wire_format_one("hallo.foo.bar", out2, sizeof(out2), sizeof(out2)); + test_dns_name_to_wire_format_one("hallo.foo..bar", NULL, 32, -EINVAL); + + test_dns_name_to_wire_format_one("\\032foo.bar", out3, sizeof(out3), sizeof(out3)); + + test_dns_name_to_wire_format_one("a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a123", NULL, 500, -EINVAL); + test_dns_name_to_wire_format_one("a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12", out4, sizeof(out4), sizeof(out4)); +} + +static void test_dns_label_unescape_suffix_one(const char *what, const char *expect1, const char *expect2, size_t buffer_sz, int ret1, int ret2) { + char buffer[buffer_sz]; + const char *label; + int r; + + log_info("%s, %s, %s, %zu, %d, %d", what, expect1, expect2, buffer_sz, ret1, ret2); + + label = what + strlen(what); + + r = dns_label_unescape_suffix(what, &label, buffer, buffer_sz); + assert_se(r == ret1); + if (r >= 0) + assert_se(streq(buffer, expect1)); + + r = dns_label_unescape_suffix(what, &label, buffer, buffer_sz); + assert_se(r == ret2); + if (r >= 0) + assert_se(streq(buffer, expect2)); +} + +TEST(dns_label_unescape_suffix) { + test_dns_label_unescape_suffix_one("hallo", "hallo", "", 6, 5, 0); + test_dns_label_unescape_suffix_one("hallo", "hallo", "", 4, -ENOBUFS, -ENOBUFS); + test_dns_label_unescape_suffix_one("", "", "", 10, 0, 0); + test_dns_label_unescape_suffix_one("hallo\\.foobar", "hallo.foobar", "", 20, 12, 0); + test_dns_label_unescape_suffix_one("hallo.foobar", "foobar", "hallo", 10, 6, 5); + test_dns_label_unescape_suffix_one("hallo.foobar\n", "foobar", "foobar", 20, -EINVAL, -EINVAL); + test_dns_label_unescape_suffix_one("hallo\\", "hallo", "hallo", 20, -EINVAL, -EINVAL); + test_dns_label_unescape_suffix_one("hallo\\032 ", "hallo ", "", 20, 7, 0); + test_dns_label_unescape_suffix_one(".", "", "", 20, 0, 0); + test_dns_label_unescape_suffix_one("..", "", "", 20, 0, -EINVAL); + test_dns_label_unescape_suffix_one(".foobar", "foobar", "", 20, 6, -EINVAL); + test_dns_label_unescape_suffix_one("foobar.", "foobar", "", 20, 6, 0); + test_dns_label_unescape_suffix_one("foo\\\\bar", "foo\\bar", "", 20, 7, 0); + test_dns_label_unescape_suffix_one("foo.bar", "bar", "foo", 20, 3, 3); + test_dns_label_unescape_suffix_one("foo..bar", "bar", "", 20, 3, -EINVAL); + test_dns_label_unescape_suffix_one("foo...bar", "bar", "", 20, 3, -EINVAL); + test_dns_label_unescape_suffix_one("foo\\.bar", "foo.bar", "", 20, 7, 0); + test_dns_label_unescape_suffix_one("foo\\\\.bar", "bar", "foo\\", 20, 3, 4); + test_dns_label_unescape_suffix_one("foo\\\\\\.bar", "foo\\.bar", "", 20, 8, 0); +} + +static void test_dns_label_escape_one(const char *what, size_t l, const char *expect, int ret) { + _cleanup_free_ char *t = NULL; + int r; + + log_info("%s, %zu, %s, →%d", what, l, strnull(expect), ret); + + r = dns_label_escape_new(what, l, &t); + assert_se(r == ret); + + if (r < 0) + return; + + assert_se(streq_ptr(expect, t)); +} + +TEST(dns_label_escape) { + test_dns_label_escape_one("", 0, NULL, -EINVAL); + test_dns_label_escape_one("hallo", 5, "hallo", 5); + test_dns_label_escape_one("hallo", 6, "hallo\\000", 9); + test_dns_label_escape_one("hallo hallo.foobar,waldi", 24, "hallo\\032hallo\\.foobar\\044waldi", 31); +} + +static void test_dns_name_normalize_one(const char *what, const char *expect, int ret) { + _cleanup_free_ char *t = NULL; + int r; + + r = dns_name_normalize(what, 0, &t); + assert_se(r == ret); + + if (r < 0) + return; + + assert_se(streq_ptr(expect, t)); +} + +TEST(dns_name_normalize) { + test_dns_name_normalize_one("", ".", 0); + test_dns_name_normalize_one("f", "f", 0); + test_dns_name_normalize_one("f.waldi", "f.waldi", 0); + test_dns_name_normalize_one("f \\032.waldi", "f\\032\\032.waldi", 0); + test_dns_name_normalize_one("\\000", "\\000", 0); + test_dns_name_normalize_one("..", NULL, -EINVAL); + test_dns_name_normalize_one(".foobar", NULL, -EINVAL); + test_dns_name_normalize_one("foobar.", "foobar", 0); + test_dns_name_normalize_one(".", ".", 0); +} + +static void test_dns_name_equal_one(const char *a, const char *b, int ret) { + int r; + + r = dns_name_equal(a, b); + assert_se(r == ret); + + r = dns_name_equal(b, a); + assert_se(r == ret); +} + +TEST(dns_name_equal) { + test_dns_name_equal_one("", "", true); + test_dns_name_equal_one("x", "x", true); + test_dns_name_equal_one("x", "x.", true); + test_dns_name_equal_one("abc.def", "abc.def", true); + test_dns_name_equal_one("abc.def", "ABC.def", true); + test_dns_name_equal_one("abc.def", "CBA.def", false); + test_dns_name_equal_one("", "xxx", false); + test_dns_name_equal_one("ab", "a", false); + test_dns_name_equal_one("\\000", "\\000", true); + test_dns_name_equal_one(".", "", true); + test_dns_name_equal_one(".", ".", true); + test_dns_name_equal_one("..", "..", -EINVAL); +} + +static void test_dns_name_between_one(const char *a, const char *b, const char *c, int ret) { + int r; + + r = dns_name_between(a, b, c); + assert_se(r == ret); + + r = dns_name_between(c, b, a); + if (ret >= 0) + assert_se(r == 0 || dns_name_equal(a, c) > 0); + else + assert_se(r == ret); +} + +TEST(dns_name_between) { + /* see https://tools.ietf.org/html/rfc4034#section-6.1 + Note that we use "\033.z.example" in stead of "\001.z.example" as we + consider the latter invalid */ + test_dns_name_between_one("example", "a.example", "yljkjljk.a.example", true); + test_dns_name_between_one("a.example", "yljkjljk.a.example", "Z.a.example", true); + test_dns_name_between_one("yljkjljk.a.example", "Z.a.example", "zABC.a.EXAMPLE", true); + test_dns_name_between_one("Z.a.example", "zABC.a.EXAMPLE", "z.example", true); + test_dns_name_between_one("zABC.a.EXAMPLE", "z.example", "\\033.z.example", true); + test_dns_name_between_one("z.example", "\\033.z.example", "*.z.example", true); + test_dns_name_between_one("\\033.z.example", "*.z.example", "\\200.z.example", true); + test_dns_name_between_one("*.z.example", "\\200.z.example", "example", true); + test_dns_name_between_one("\\200.z.example", "example", "a.example", true); + + test_dns_name_between_one("example", "a.example", "example", true); + test_dns_name_between_one("example", "example", "example", false); + test_dns_name_between_one("example", "example", "yljkjljk.a.example", false); + test_dns_name_between_one("example", "yljkjljk.a.example", "yljkjljk.a.example", false); + test_dns_name_between_one("hkps.pool.sks-keyservers.net", "_pgpkey-https._tcp.hkps.pool.sks-keyservers.net", "ipv4.pool.sks-keyservers.net", true); +} + +static void test_dns_name_endswith_one(const char *a, const char *b, int ret) { + assert_se(dns_name_endswith(a, b) == ret); +} + +TEST(dns_name_endswith) { + test_dns_name_endswith_one("", "", true); + test_dns_name_endswith_one("", "xxx", false); + test_dns_name_endswith_one("xxx", "", true); + test_dns_name_endswith_one("x", "x", true); + test_dns_name_endswith_one("x", "y", false); + test_dns_name_endswith_one("x.y", "y", true); + test_dns_name_endswith_one("x.y", "Y", true); + test_dns_name_endswith_one("x.y", "x", false); + test_dns_name_endswith_one("x.y.z", "Z", true); + test_dns_name_endswith_one("x.y.z", "y.Z", true); + test_dns_name_endswith_one("x.y.z", "x.y.Z", true); + test_dns_name_endswith_one("x.y.z", "waldo", false); + test_dns_name_endswith_one("x.y.z.u.v.w", "y.z", false); + test_dns_name_endswith_one("x.y.z.u.v.w", "u.v.w", true); + test_dns_name_endswith_one("x.y\001.z", "waldo", -EINVAL); +} + +static void test_dns_name_startswith_one(const char *a, const char *b, int ret) { + assert_se(dns_name_startswith(a, b) == ret); +} + +TEST(dns_name_startswith) { + test_dns_name_startswith_one("", "", true); + test_dns_name_startswith_one("", "xxx", false); + test_dns_name_startswith_one("xxx", "", true); + test_dns_name_startswith_one("x", "x", true); + test_dns_name_startswith_one("x", "y", false); + test_dns_name_startswith_one("x.y", "x.y", true); + test_dns_name_startswith_one("x.y", "y.x", false); + test_dns_name_startswith_one("x.y", "x", true); + test_dns_name_startswith_one("x.y", "X", true); + test_dns_name_startswith_one("x.y", "y", false); + test_dns_name_startswith_one("x.y", "", true); + test_dns_name_startswith_one("x.y", "X", true); +} + +TEST(dns_name_is_root) { + assert_se(dns_name_is_root("")); + assert_se(dns_name_is_root(".")); + assert_se(!dns_name_is_root("xxx")); + assert_se(!dns_name_is_root("xxx.")); + assert_se(!dns_name_is_root("..")); +} + +TEST(dns_name_is_single_label) { + assert_se(!dns_name_is_single_label("")); + assert_se(!dns_name_is_single_label(".")); + assert_se(!dns_name_is_single_label("..")); + assert_se(dns_name_is_single_label("x")); + assert_se(dns_name_is_single_label("x.")); + assert_se(!dns_name_is_single_label("xx.yy")); +} + +static void test_dns_name_reverse_one(const char *address, const char *name) { + _cleanup_free_ char *p = NULL; + union in_addr_union a, b = {}; + int familya, familyb; + + assert_se(in_addr_from_string_auto(address, &familya, &a) >= 0); + assert_se(dns_name_reverse(familya, &a, &p) >= 0); + assert_se(streq(p, name)); + assert_se(dns_name_address(p, &familyb, &b) > 0); + assert_se(familya == familyb); + assert_se(in_addr_equal(familya, &a, &b)); +} + +TEST(dns_name_reverse) { + test_dns_name_reverse_one("47.11.8.15", "15.8.11.47.in-addr.arpa"); + test_dns_name_reverse_one("fe80::47", "7.4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.e.f.ip6.arpa"); + test_dns_name_reverse_one("127.0.0.1", "1.0.0.127.in-addr.arpa"); + test_dns_name_reverse_one("::1", "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa"); +} + +static void test_dns_name_concat_one(const char *a, const char *b, int r, const char *result) { + _cleanup_free_ char *p = NULL; + + assert_se(dns_name_concat(a, b, 0, &p) == r); + assert_se(streq_ptr(p, result)); +} + +TEST(dns_name_concat) { + test_dns_name_concat_one("", "", 0, "."); + test_dns_name_concat_one(".", "", 0, "."); + test_dns_name_concat_one("", ".", 0, "."); + test_dns_name_concat_one(".", ".", 0, "."); + test_dns_name_concat_one("foo", "bar", 0, "foo.bar"); + test_dns_name_concat_one("foo.foo", "bar.bar", 0, "foo.foo.bar.bar"); + test_dns_name_concat_one("foo", NULL, 0, "foo"); + test_dns_name_concat_one("foo", ".", 0, "foo"); + test_dns_name_concat_one("foo.", "bar.", 0, "foo.bar"); + test_dns_name_concat_one(NULL, NULL, 0, "."); + test_dns_name_concat_one(NULL, ".", 0, "."); + test_dns_name_concat_one(NULL, "foo", 0, "foo"); +} + +static void test_dns_name_is_valid_one(const char *s, int ret, int ret_ldh) { + log_info("%s, →%d", s, ret); + + assert_se(dns_name_is_valid(s) == ret); + assert_se(dns_name_is_valid_ldh(s) == ret_ldh); +} + +TEST(dns_name_is_valid) { + test_dns_name_is_valid_one("foo", 1, 1); + test_dns_name_is_valid_one("foo.", 1, 1); + test_dns_name_is_valid_one("foo..", 0, 0); + test_dns_name_is_valid_one("Foo", 1, 1); + test_dns_name_is_valid_one("foo.bar", 1, 1); + test_dns_name_is_valid_one("foo.bar.baz", 1, 1); + test_dns_name_is_valid_one("", 1, 1); + test_dns_name_is_valid_one("foo..bar", 0, 0); + test_dns_name_is_valid_one(".foo.bar", 0, 0); + test_dns_name_is_valid_one("foo.bar.", 1, 1); + test_dns_name_is_valid_one("foo.bar..", 0, 0); + test_dns_name_is_valid_one("\\zbar", 0, 0); + test_dns_name_is_valid_one("ä", 1, 0); + test_dns_name_is_valid_one("\n", 0, 0); + + test_dns_name_is_valid_one("dash-", 1, 0); + test_dns_name_is_valid_one("-dash", 1, 0); + test_dns_name_is_valid_one("dash-dash", 1, 1); + test_dns_name_is_valid_one("foo.dash-", 1, 0); + test_dns_name_is_valid_one("foo.-dash", 1, 0); + test_dns_name_is_valid_one("foo.dash-dash", 1, 1); + test_dns_name_is_valid_one("foo.dash-.bar", 1, 0); + test_dns_name_is_valid_one("foo.-dash.bar", 1, 0); + test_dns_name_is_valid_one("foo.dash-dash.bar", 1, 1); + test_dns_name_is_valid_one("dash-.bar", 1, 0); + test_dns_name_is_valid_one("-dash.bar", 1, 0); + test_dns_name_is_valid_one("dash-dash.bar", 1, 1); + test_dns_name_is_valid_one("-.bar", 1, 0); + test_dns_name_is_valid_one("foo.-", 1, 0); + + /* 256 characters */ + test_dns_name_is_valid_one("a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345", 0, 0); + + /* 255 characters */ + test_dns_name_is_valid_one("a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a1234", 0, 0); + + /* 254 characters */ + test_dns_name_is_valid_one("a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a123", 0, 0); + + /* 253 characters */ + test_dns_name_is_valid_one("a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12", 1, 1); + + /* label of 64 chars length */ + test_dns_name_is_valid_one("a123456789a123456789a123456789a123456789a123456789a123456789a123", 0, 0); + + /* label of 63 chars length */ + test_dns_name_is_valid_one("a123456789a123456789a123456789a123456789a123456789a123456789a12", 1, 1); +} + +TEST(dns_service_name_is_valid) { + assert_se(dns_service_name_is_valid("Lennart's Compüter")); + assert_se(dns_service_name_is_valid("piff.paff")); + + assert_se(!dns_service_name_is_valid(NULL)); + assert_se(!dns_service_name_is_valid("")); + assert_se(!dns_service_name_is_valid("foo\nbar")); + assert_se(!dns_service_name_is_valid("foo\201bar")); + assert_se(!dns_service_name_is_valid("this is an overly long string that is certainly longer than 63 characters")); +} + +TEST(dns_srv_type_is_valid) { + assert_se(dns_srv_type_is_valid("_http._tcp")); + assert_se(dns_srv_type_is_valid("_foo-bar._tcp")); + assert_se(dns_srv_type_is_valid("_w._udp")); + assert_se(dns_srv_type_is_valid("_a800._tcp")); + assert_se(dns_srv_type_is_valid("_a-800._tcp")); + + assert_se(!dns_srv_type_is_valid(NULL)); + assert_se(!dns_srv_type_is_valid("")); + assert_se(!dns_srv_type_is_valid("x")); + assert_se(!dns_srv_type_is_valid("_foo")); + assert_se(!dns_srv_type_is_valid("_tcp")); + assert_se(!dns_srv_type_is_valid("_")); + assert_se(!dns_srv_type_is_valid("_foo.")); + assert_se(!dns_srv_type_is_valid("_föo._tcp")); + assert_se(!dns_srv_type_is_valid("_f\no._tcp")); + assert_se(!dns_srv_type_is_valid("_800._tcp")); + assert_se(!dns_srv_type_is_valid("_-800._tcp")); + assert_se(!dns_srv_type_is_valid("_-foo._tcp")); + assert_se(!dns_srv_type_is_valid("_piep._foo._udp")); +} + +TEST(dnssd_srv_type_is_valid) { + assert_se(dnssd_srv_type_is_valid("_http._tcp")); + assert_se(dnssd_srv_type_is_valid("_foo-bar._tcp")); + assert_se(dnssd_srv_type_is_valid("_w._udp")); + assert_se(dnssd_srv_type_is_valid("_a800._tcp")); + assert_se(dnssd_srv_type_is_valid("_a-800._tcp")); + + assert_se(!dnssd_srv_type_is_valid(NULL)); + assert_se(!dnssd_srv_type_is_valid("")); + assert_se(!dnssd_srv_type_is_valid("x")); + assert_se(!dnssd_srv_type_is_valid("_foo")); + assert_se(!dnssd_srv_type_is_valid("_tcp")); + assert_se(!dnssd_srv_type_is_valid("_")); + assert_se(!dnssd_srv_type_is_valid("_foo.")); + assert_se(!dnssd_srv_type_is_valid("_föo._tcp")); + assert_se(!dnssd_srv_type_is_valid("_f\no._tcp")); + assert_se(!dnssd_srv_type_is_valid("_800._tcp")); + assert_se(!dnssd_srv_type_is_valid("_-800._tcp")); + assert_se(!dnssd_srv_type_is_valid("_-foo._tcp")); + assert_se(!dnssd_srv_type_is_valid("_piep._foo._udp")); + assert_se(!dnssd_srv_type_is_valid("_foo._unknown")); +} + +static void test_dns_service_join_one(const char *a, const char *b, const char *c, int r, const char *d) { + _cleanup_free_ char *x = NULL, *y = NULL, *z = NULL, *t = NULL; + + log_info("%s, %s, %s, →%d, %s", strnull(a), strnull(b), strnull(c), r, strnull(d)); + + assert_se(dns_service_join(a, b, c, &t) == r); + assert_se(streq_ptr(t, d)); + + if (r < 0) + return; + + assert_se(dns_service_split(t, &x, &y, &z) >= 0); + assert_se(streq_ptr(a, x)); + assert_se(streq_ptr(b, y)); + assert_se(dns_name_equal(c, z) > 0); +} + +TEST(dns_service_join) { + test_dns_service_join_one("", "", "", -EINVAL, NULL); + test_dns_service_join_one("", "_http._tcp", "", -EINVAL, NULL); + test_dns_service_join_one("", "_http._tcp", "foo", -EINVAL, NULL); + test_dns_service_join_one("foo", "", "foo", -EINVAL, NULL); + test_dns_service_join_one("foo", "foo", "foo", -EINVAL, NULL); + + test_dns_service_join_one("foo", "_http._tcp", "", 0, "foo._http._tcp"); + test_dns_service_join_one(NULL, "_http._tcp", "", 0, "_http._tcp"); + test_dns_service_join_one("foo", "_http._tcp", "foo", 0, "foo._http._tcp.foo"); + test_dns_service_join_one(NULL, "_http._tcp", "foo", 0, "_http._tcp.foo"); + test_dns_service_join_one("Lennart's PC", "_pc._tcp", "foo.bar.com", 0, "Lennart\\039s\\032PC._pc._tcp.foo.bar.com"); + test_dns_service_join_one(NULL, "_pc._tcp", "foo.bar.com", 0, "_pc._tcp.foo.bar.com"); +} + +static void test_dns_service_split_one(const char *joined, const char *a, const char *b, const char *c, int r) { + _cleanup_free_ char *x = NULL, *y = NULL, *z = NULL, *t = NULL; + + log_info("%s, %s, %s, %s, →%d", joined, strnull(a), strnull(b), strnull(c), r); + + assert_se(dns_service_split(joined, &x, &y, &z) == r); + assert_se(streq_ptr(x, a)); + assert_se(streq_ptr(y, b)); + assert_se(streq_ptr(z, c)); + + if (r < 0) + return; + + if (y) { + assert_se(dns_service_join(x, y, z, &t) == 0); + assert_se(dns_name_equal(joined, t) > 0); + } else + assert_se(!x && dns_name_equal(z, joined) > 0); +} + +TEST(dns_service_split) { + test_dns_service_split_one("", NULL, NULL, ".", 0); + test_dns_service_split_one("foo", NULL, NULL, "foo", 0); + test_dns_service_split_one("foo.bar", NULL, NULL, "foo.bar", 0); + test_dns_service_split_one("_foo.bar", NULL, NULL, "_foo.bar", 0); + test_dns_service_split_one("_foo._bar", NULL, "_foo._bar", ".", 0); + test_dns_service_split_one("_meh._foo._bar", "_meh", "_foo._bar", ".", 0); + test_dns_service_split_one("Wuff\\032Wuff._foo._bar.waldo.com", "Wuff Wuff", "_foo._bar", "waldo.com", 0); + test_dns_service_split_one("_Q._Q-------------------------------------------------------------", NULL, "_Q._Q-------------------------------------------------------------", ".", 0); +} + +static void test_dns_name_change_suffix_one(const char *name, const char *old_suffix, const char *new_suffix, int r, const char *result) { + _cleanup_free_ char *s = NULL; + + log_info("%s, %s, %s, →%s", name, old_suffix, new_suffix, strnull(result)); + + assert_se(dns_name_change_suffix(name, old_suffix, new_suffix, &s) == r); + assert_se(streq_ptr(s, result)); +} + +TEST(dns_name_change_suffix) { + test_dns_name_change_suffix_one("foo.bar", "bar", "waldo", 1, "foo.waldo"); + test_dns_name_change_suffix_one("foo.bar.waldi.quux", "foo.bar.waldi.quux", "piff.paff", 1, "piff.paff"); + test_dns_name_change_suffix_one("foo.bar.waldi.quux", "bar.waldi.quux", "piff.paff", 1, "foo.piff.paff"); + test_dns_name_change_suffix_one("foo.bar.waldi.quux", "waldi.quux", "piff.paff", 1, "foo.bar.piff.paff"); + test_dns_name_change_suffix_one("foo.bar.waldi.quux", "quux", "piff.paff", 1, "foo.bar.waldi.piff.paff"); + test_dns_name_change_suffix_one("foo.bar.waldi.quux", "", "piff.paff", 1, "foo.bar.waldi.quux.piff.paff"); + test_dns_name_change_suffix_one("", "", "piff.paff", 1, "piff.paff"); + test_dns_name_change_suffix_one("", "", "", 1, "."); + test_dns_name_change_suffix_one("a", "b", "c", 0, NULL); +} + +static void test_dns_name_suffix_one(const char *name, unsigned n_labels, const char *result, int ret) { + const char *p = NULL; + + log_info("%s, %u, → %s, %d", name, n_labels, strnull(result), ret); + + assert_se(ret == dns_name_suffix(name, n_labels, &p)); + assert_se(streq_ptr(p, result)); +} + +TEST(dns_name_suffix) { + test_dns_name_suffix_one("foo.bar", 2, "foo.bar", 0); + test_dns_name_suffix_one("foo.bar", 1, "bar", 1); + test_dns_name_suffix_one("foo.bar", 0, "", 2); + test_dns_name_suffix_one("foo.bar", 3, NULL, -EINVAL); + test_dns_name_suffix_one("foo.bar", 4, NULL, -EINVAL); + + test_dns_name_suffix_one("bar", 1, "bar", 0); + test_dns_name_suffix_one("bar", 0, "", 1); + test_dns_name_suffix_one("bar", 2, NULL, -EINVAL); + test_dns_name_suffix_one("bar", 3, NULL, -EINVAL); + + test_dns_name_suffix_one("", 0, "", 0); + test_dns_name_suffix_one("", 1, NULL, -EINVAL); + test_dns_name_suffix_one("", 2, NULL, -EINVAL); +} + +static void test_dns_name_count_labels_one(const char *name, int n) { + log_info("%s, →%d", name, n); + + assert_se(dns_name_count_labels(name) == n); +} + +TEST(dns_name_count_labels) { + test_dns_name_count_labels_one("foo.bar.quux.", 3); + test_dns_name_count_labels_one("foo.bar.quux", 3); + test_dns_name_count_labels_one("foo.bar.", 2); + test_dns_name_count_labels_one("foo.bar", 2); + test_dns_name_count_labels_one("foo.", 1); + test_dns_name_count_labels_one("foo", 1); + test_dns_name_count_labels_one("", 0); + test_dns_name_count_labels_one(".", 0); + test_dns_name_count_labels_one("..", -EINVAL); +} + +static void test_dns_name_equal_skip_one(const char *a, unsigned n_labels, const char *b, int ret) { + log_info("%s, %u, %s, →%d", a, n_labels, b, ret); + + assert_se(dns_name_equal_skip(a, n_labels, b) == ret); +} + +TEST(dns_name_equal_skip) { + test_dns_name_equal_skip_one("foo", 0, "bar", 0); + test_dns_name_equal_skip_one("foo", 0, "foo", 1); + test_dns_name_equal_skip_one("foo", 1, "foo", 0); + test_dns_name_equal_skip_one("foo", 2, "foo", 0); + + test_dns_name_equal_skip_one("foo.bar", 0, "foo.bar", 1); + test_dns_name_equal_skip_one("foo.bar", 1, "foo.bar", 0); + test_dns_name_equal_skip_one("foo.bar", 2, "foo.bar", 0); + test_dns_name_equal_skip_one("foo.bar", 3, "foo.bar", 0); + + test_dns_name_equal_skip_one("foo.bar", 0, "bar", 0); + test_dns_name_equal_skip_one("foo.bar", 1, "bar", 1); + test_dns_name_equal_skip_one("foo.bar", 2, "bar", 0); + test_dns_name_equal_skip_one("foo.bar", 3, "bar", 0); + + test_dns_name_equal_skip_one("foo.bar", 0, "", 0); + test_dns_name_equal_skip_one("foo.bar", 1, "", 0); + test_dns_name_equal_skip_one("foo.bar", 2, "", 1); + test_dns_name_equal_skip_one("foo.bar", 3, "", 0); + + test_dns_name_equal_skip_one("", 0, "", 1); + test_dns_name_equal_skip_one("", 1, "", 0); + test_dns_name_equal_skip_one("", 1, "foo", 0); + test_dns_name_equal_skip_one("", 2, "foo", 0); +} + +TEST(dns_name_compare_func) { + assert_se(dns_name_compare_func("", "") == 0); + assert_se(dns_name_compare_func("", ".") == 0); + assert_se(dns_name_compare_func(".", "") == 0); + assert_se(dns_name_compare_func("foo", "foo.") == 0); + assert_se(dns_name_compare_func("foo.", "foo") == 0); + assert_se(dns_name_compare_func("foo", "foo") == 0); + assert_se(dns_name_compare_func("foo.", "foo.") == 0); + assert_se(dns_name_compare_func("heise.de", "HEISE.DE.") == 0); + + assert_se(dns_name_compare_func("de.", "heise.de") != 0); +} + +static void test_dns_name_common_suffix_one(const char *a, const char *b, const char *result) { + const char *c; + + log_info("%s, %s, →%s", a, b, result); + + assert_se(dns_name_common_suffix(a, b, &c) >= 0); + assert_se(streq(c, result)); +} + +TEST(dns_name_common_suffix) { + test_dns_name_common_suffix_one("", "", ""); + test_dns_name_common_suffix_one("foo", "", ""); + test_dns_name_common_suffix_one("", "foo", ""); + test_dns_name_common_suffix_one("foo", "bar", ""); + test_dns_name_common_suffix_one("bar", "foo", ""); + test_dns_name_common_suffix_one("foo", "foo", "foo"); + test_dns_name_common_suffix_one("quux.foo", "foo", "foo"); + test_dns_name_common_suffix_one("foo", "quux.foo", "foo"); + test_dns_name_common_suffix_one("this.is.a.short.sentence", "this.is.another.short.sentence", "short.sentence"); + test_dns_name_common_suffix_one("FOO.BAR", "tEST.bAR", "BAR"); +} + +static void test_dns_name_apply_idna_one(const char *s, int expected, const char *result) { + _cleanup_free_ char *buf = NULL; + int r; + + r = dns_name_apply_idna(s, &buf); + log_debug("dns_name_apply_idna: \"%s\" → %d/\"%s\" (expected %d/\"%s\")", + s, r, strnull(buf), expected, strnull(result)); + + /* Different libidn2 versions are more and less accepting + * of underscore-prefixed names. So let's list the lowest + * expected return value. */ + assert_se(r >= expected); + if (expected == 1) + assert_se(dns_name_equal(buf, result) == 1); +} + +TEST(dns_name_apply_idna) { + const int ret = HAVE_LIBIDN2 | HAVE_LIBIDN; + + /* IDNA2008 forbids names with hyphens in third and fourth positions + * (https://tools.ietf.org/html/rfc5891#section-4.2.3.1). + * IDNA2003 does not have this restriction + * (https://tools.ietf.org/html/rfc3490#section-5). + * This means that when using libidn we will transform and test more + * labels. If registrars follow IDNA2008 we'll just be performing a + * useless lookup. + */ + const int ret2 = HAVE_LIBIDN; + + test_dns_name_apply_idna_one("", ret, ""); + test_dns_name_apply_idna_one("foo", ret, "foo"); + test_dns_name_apply_idna_one("foo.", ret, "foo"); + test_dns_name_apply_idna_one("foo.bar", ret, "foo.bar"); + test_dns_name_apply_idna_one("foo.bar.", ret, "foo.bar"); + test_dns_name_apply_idna_one("föö", ret, "xn--f-1gaa"); + test_dns_name_apply_idna_one("föö.", ret, "xn--f-1gaa"); + test_dns_name_apply_idna_one("föö.bär", ret, "xn--f-1gaa.xn--br-via"); + test_dns_name_apply_idna_one("föö.bär.", ret, "xn--f-1gaa.xn--br-via"); + test_dns_name_apply_idna_one("xn--f-1gaa.xn--br-via", ret, "xn--f-1gaa.xn--br-via"); + + test_dns_name_apply_idna_one("_443._tcp.fedoraproject.org", ret2, + "_443._tcp.fedoraproject.org"); + test_dns_name_apply_idna_one("_443", ret2, "_443"); + test_dns_name_apply_idna_one("gateway", ret, "gateway"); + test_dns_name_apply_idna_one("_gateway", ret2, "_gateway"); + + test_dns_name_apply_idna_one("r3---sn-ab5l6ne7.googlevideo.com", ret2, + ret2 ? "r3---sn-ab5l6ne7.googlevideo.com" : ""); +} + +TEST(dns_name_is_valid_or_address) { + assert_se(dns_name_is_valid_or_address(NULL) == 0); + assert_se(dns_name_is_valid_or_address("") == 0); + assert_se(dns_name_is_valid_or_address("foobar") > 0); + assert_se(dns_name_is_valid_or_address("foobar.com") > 0); + assert_se(dns_name_is_valid_or_address("foobar..com") == 0); + assert_se(dns_name_is_valid_or_address("foobar.com.") > 0); + assert_se(dns_name_is_valid_or_address("127.0.0.1") > 0); + assert_se(dns_name_is_valid_or_address("::") > 0); + assert_se(dns_name_is_valid_or_address("::1") > 0); +} + +TEST(dns_name_dot_suffixed) { + assert_se(dns_name_dot_suffixed("") == 0); + assert_se(dns_name_dot_suffixed(".") > 0); + assert_se(dns_name_dot_suffixed("foo") == 0); + assert_se(dns_name_dot_suffixed("foo.") > 0); + assert_se(dns_name_dot_suffixed("foo\\..") > 0); + assert_se(dns_name_dot_suffixed("foo\\.") == 0); + assert_se(dns_name_dot_suffixed("foo.bar.") > 0); + assert_se(dns_name_dot_suffixed("foo.bar\\.\\.\\..") > 0); + assert_se(dns_name_dot_suffixed("foo.bar\\.\\.\\.\\.") == 0); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-ellipsize.c b/src/test/test-ellipsize.c new file mode 100644 index 0000000..c272c56 --- /dev/null +++ b/src/test/test-ellipsize.c @@ -0,0 +1,159 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdio.h> + +#include "alloc-util.h" +#include "constants.h" +#include "escape.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "tests.h" +#include "utf8.h" + +static void test_ellipsize_mem_one(const char *s, size_t old_length, size_t new_length) { + _cleanup_free_ char *n = NULL; + _cleanup_free_ char *t1 = NULL, *t2 = NULL, *t3 = NULL; + char buf[LINE_MAX]; + bool has_wide_chars; + size_t max_width; + + n = memdup_suffix0(s, old_length); + + if (!utf8_is_valid(n)) + /* We don't support invalid sequences… */ + return; + + /* Report out inputs. We duplicate the data so that cellescape + * can properly report truncated multibyte sequences. */ + log_info("%s \"%s\" old_length=%zu/%zu new_length=%zu", __func__, + cellescape(buf, sizeof buf, n), + old_length, utf8_console_width(n), + new_length); + + /* To keep this test simple, any case with wide chars starts with this glyph */ + has_wide_chars = startswith(s, "你"); + max_width = MIN(utf8_console_width(n), new_length); + + t1 = ellipsize_mem(n, old_length, new_length, 30); + log_info("30%% → %s utf8_console_width=%zu", t1, utf8_console_width(t1)); + if (!has_wide_chars) + assert_se(utf8_console_width(t1) == max_width); + else + assert_se(utf8_console_width(t1) <= max_width); + + t2 = ellipsize_mem(n, old_length, new_length, 90); + log_info("90%% → %s utf8_console_width=%zu", t2, utf8_console_width(t2)); + if (!has_wide_chars) + assert_se(utf8_console_width(t2) == max_width); + else + assert_se(utf8_console_width(t2) <= max_width); + + t3 = ellipsize_mem(n, old_length, new_length, 100); + log_info("100%% → %s utf8_console_width=%zu", t3, utf8_console_width(t3)); + if (!has_wide_chars) + assert_se(utf8_console_width(t3) == max_width); + else + assert_se(utf8_console_width(t3) <= max_width); + + if (new_length >= old_length) { + assert_se(streq(t1, n)); + assert_se(streq(t2, n)); + assert_se(streq(t3, n)); + } +} + +TEST(ellipsize_mem) { + FOREACH_STRING(s, + "_XXXXXXXXXXX_", /* ASCII */ + "_aąęółśćńżźć_", /* two-byte utf-8 */ + "გამარჯობა", /* multi-byte utf-8 */ + "你好世界", /* wide characters */ + "你გą世óoó界") /* a mix */ + for (ssize_t l = strlen(s); l >= 0; l--) + for (ssize_t k = strlen(s) + 1; k >= 0; k--) + test_ellipsize_mem_one(s, l, k); +} + +static void test_ellipsize_one(const char *p) { + _cleanup_free_ char *t = NULL; + t = ellipsize(p, columns(), 70); + puts(t); + free(t); + t = ellipsize(p, columns(), 0); + puts(t); + free(t); + t = ellipsize(p, columns(), 100); + puts(t); + free(t); + t = ellipsize(p, 0, 50); + puts(t); + free(t); + t = ellipsize(p, 1, 50); + puts(t); + free(t); + t = ellipsize(p, 2, 50); + puts(t); + free(t); + t = ellipsize(p, 3, 50); + puts(t); + free(t); + t = ellipsize(p, 4, 50); + puts(t); + free(t); + t = ellipsize(p, 5, 50); + puts(t); +} + +TEST(ellipsize) { + test_ellipsize_one(DIGITS LETTERS DIGITS LETTERS); + test_ellipsize_one("한국어한국어한국어한국어한국어한국어한국어한국어한국어한국어한국어한국어한국어한국어한국어한국어한국어한국어"); + test_ellipsize_one("-日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国日本国"); + test_ellipsize_one("中国中国中国中国中国中国中国中国中国中国中国中国中国中国中国中国中国中国中国中国中国中国-中国中国中国中国中国中国中国中国中国中国中国中国中国"); + test_ellipsize_one("sÿstëmd sÿstëmd sÿstëmd sÿstëmd sÿstëmd sÿstëmd sÿstëmd sÿstëmd sÿstëmd sÿstëmd sÿstëmd sÿstëmd sÿstëmd"); + test_ellipsize_one("🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮🐮"); + test_ellipsize_one("Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."); + test_ellipsize_one("shórt"); +} + +TEST(ellipsize_ansi) { + const char *s = ANSI_HIGHLIGHT_YELLOW_UNDERLINE "yęllow" + ANSI_HIGHLIGHT_GREY_UNDERLINE "grěy" + ANSI_HIGHLIGHT_BLUE_UNDERLINE "blue" + ANSI_NORMAL "nórmął"; + size_t len = strlen(s); + + for (unsigned percent = 0; percent <= 100; percent += 15) + for (ssize_t x = 21; x >= 0; x--) { + _cleanup_free_ char *t = ellipsize_mem(s, len, x, percent); + printf("%02zd: \"%s\"\n", x, t); + assert_se(utf8_is_valid(t)); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *e = cescape(t); + printf(" : \"%s\"\n", e); + } + } +} + +TEST(ellipsize_ansi_cats) { + _cleanup_free_ char *e = NULL, *f = NULL, *g = NULL, *h = NULL; + + /* Make sure we don't cut off in the middle of an ANSI escape sequence. */ + + e = ellipsize("01" ANSI_NORMAL "23", 4, 0); + puts(e); + assert_se(streq(e, "01" ANSI_NORMAL "23")); + f = ellipsize("ab" ANSI_NORMAL "cd", 4, 90); + puts(f); + assert_se(streq(f, "ab" ANSI_NORMAL "cd")); + + g = ellipsize("🐱🐱" ANSI_NORMAL "🐱🐱" ANSI_NORMAL, 5, 0); + puts(g); + assert_se(streq(g, "…" ANSI_NORMAL "🐱🐱" ANSI_NORMAL)); + h = ellipsize("🐱🐱" ANSI_NORMAL "🐱🐱" ANSI_NORMAL, 5, 90); + puts(h); + assert_se(streq(h, "🐱…" ANSI_NORMAL "🐱" ANSI_NORMAL)); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-emergency-action.c b/src/test/test-emergency-action.c new file mode 100644 index 0000000..5c0ce7f --- /dev/null +++ b/src/test/test-emergency-action.c @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "emergency-action.h" +#include "tests.h" + +TEST(parse_emergency_action) { + EmergencyAction x; + + assert_se(parse_emergency_action("none", RUNTIME_SCOPE_USER, &x) == 0); + assert_se(x == EMERGENCY_ACTION_NONE); + assert_se(parse_emergency_action("reboot", RUNTIME_SCOPE_USER, &x) == -EOPNOTSUPP); + assert_se(parse_emergency_action("reboot-force", RUNTIME_SCOPE_USER, &x) == -EOPNOTSUPP); + assert_se(parse_emergency_action("reboot-immediate", RUNTIME_SCOPE_USER, &x) == -EOPNOTSUPP); + assert_se(parse_emergency_action("poweroff", RUNTIME_SCOPE_USER, &x) == -EOPNOTSUPP); + assert_se(parse_emergency_action("poweroff-force", RUNTIME_SCOPE_USER, &x) == -EOPNOTSUPP); + assert_se(parse_emergency_action("poweroff-immediate", RUNTIME_SCOPE_USER, &x) == -EOPNOTSUPP); + assert_se(x == EMERGENCY_ACTION_NONE); + assert_se(parse_emergency_action("exit", RUNTIME_SCOPE_USER, &x) == 0); + assert_se(x == EMERGENCY_ACTION_EXIT); + assert_se(parse_emergency_action("exit-force", RUNTIME_SCOPE_USER, &x) == 0); + assert_se(x == EMERGENCY_ACTION_EXIT_FORCE); + assert_se(parse_emergency_action("exit-forcee", RUNTIME_SCOPE_USER, &x) == -EINVAL); + + assert_se(parse_emergency_action("none", RUNTIME_SCOPE_SYSTEM, &x) == 0); + assert_se(x == EMERGENCY_ACTION_NONE); + assert_se(parse_emergency_action("reboot", RUNTIME_SCOPE_SYSTEM, &x) == 0); + assert_se(x == EMERGENCY_ACTION_REBOOT); + assert_se(parse_emergency_action("reboot-force", RUNTIME_SCOPE_SYSTEM, &x) == 0); + assert_se(x == EMERGENCY_ACTION_REBOOT_FORCE); + assert_se(parse_emergency_action("reboot-immediate", RUNTIME_SCOPE_SYSTEM, &x) == 0); + assert_se(x == EMERGENCY_ACTION_REBOOT_IMMEDIATE); + assert_se(parse_emergency_action("poweroff", RUNTIME_SCOPE_SYSTEM, &x) == 0); + assert_se(x == EMERGENCY_ACTION_POWEROFF); + assert_se(parse_emergency_action("poweroff-force", RUNTIME_SCOPE_SYSTEM, &x) == 0); + assert_se(x == EMERGENCY_ACTION_POWEROFF_FORCE); + assert_se(parse_emergency_action("poweroff-immediate", RUNTIME_SCOPE_SYSTEM, &x) == 0); + assert_se(parse_emergency_action("exit", RUNTIME_SCOPE_SYSTEM, &x) == 0); + assert_se(parse_emergency_action("exit-force", RUNTIME_SCOPE_SYSTEM, &x) == 0); + assert_se(parse_emergency_action("exit-forcee", RUNTIME_SCOPE_SYSTEM, &x) == -EINVAL); + assert_se(x == EMERGENCY_ACTION_EXIT_FORCE); + assert_se(parse_emergency_action("kexec", RUNTIME_SCOPE_SYSTEM, &x) == 0); + assert_se(parse_emergency_action("kexec-force", RUNTIME_SCOPE_SYSTEM, &x) == 0); + assert_se(parse_emergency_action("kexec-forcee", RUNTIME_SCOPE_SYSTEM, &x) == -EINVAL); + assert_se(x == EMERGENCY_ACTION_KEXEC_FORCE); + assert_se(parse_emergency_action("halt", RUNTIME_SCOPE_SYSTEM, &x) == 0); + assert_se(parse_emergency_action("halt-force", RUNTIME_SCOPE_SYSTEM, &x) == 0); + assert_se(parse_emergency_action("halt-forcee", RUNTIME_SCOPE_SYSTEM, &x) == -EINVAL); + assert_se(x == EMERGENCY_ACTION_HALT_FORCE); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-engine.c b/src/test/test-engine.c new file mode 100644 index 0000000..cf77e7c --- /dev/null +++ b/src/test/test-engine.c @@ -0,0 +1,300 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> +#include <stdio.h> + +#include "bus-util.h" +#include "manager.h" +#include "manager-dump.h" +#include "rm-rf.h" +#include "service.h" +#include "slice.h" +#include "special.h" +#include "strv.h" +#include "tests.h" +#include "unit-serialize.h" + +static void verify_dependency_atoms(void) { + UnitDependencyAtom combined = 0, multi_use_atoms = 0; + + /* Let's guarantee that our dependency type/atom translation tables are fully correct */ + + for (UnitDependency d = 0; d < _UNIT_DEPENDENCY_MAX; d++) { + UnitDependencyAtom a; + UnitDependency reverse; + bool has_superset = false; + + assert_se((a = unit_dependency_to_atom(d)) >= 0); + + for (UnitDependency t = 0; t < _UNIT_DEPENDENCY_MAX; t++) { + UnitDependencyAtom b; + + if (t == d) + continue; + + assert_se((b = unit_dependency_to_atom(t)) >= 0); + + if ((a & b) == a) { + has_superset = true; + break; + } + } + + reverse = unit_dependency_from_unique_atom(a); + assert_se(reverse == _UNIT_DEPENDENCY_INVALID || reverse >= 0); + + assert_se((reverse < 0) == has_superset); /* If one dependency type is a superset of another, + * then the reverse mapping is not unique, verify + * that. */ + + log_info("Verified dependency type: %s", unit_dependency_to_string(d)); + + multi_use_atoms |= combined & a; + combined |= a; + } + + /* Make sure all atoms are used, i.e. there's at least one dependency type that references it. */ + assert_se(combined == _UNIT_DEPENDENCY_ATOM_MAX); + + for (UnitDependencyAtom a = 1; a <= _UNIT_DEPENDENCY_ATOM_MAX; a <<= 1) { + + if (multi_use_atoms & a) { + /* If an atom is used by multiple dep types, then mapping the atom to a dependency is + * not unique and *must* fail */ + assert_se(unit_dependency_from_unique_atom(a) == _UNIT_DEPENDENCY_INVALID); + continue; + } + + /* If only a single dep type uses specific atom, let's guarantee our mapping table is + complete, and thus the atom can be mapped to the single dep type that is used. */ + assert_se(unit_dependency_from_unique_atom(a) >= 0); + } +} + +int main(int argc, char *argv[]) { + _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error err = SD_BUS_ERROR_NULL; + _cleanup_(manager_freep) Manager *m = NULL; + Unit *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL, *g = NULL, + *h = NULL, *i = NULL, *a_conj = NULL, *unit_with_multiple_dashes = NULL, *stub = NULL, + *tomato = NULL, *sauce = NULL, *fruit = NULL, *zupa = NULL; + Job *j; + int r; + + test_setup_logging(LOG_DEBUG); + + r = enter_cgroup_subroot(NULL); + if (r == -ENOMEDIUM) + return log_tests_skipped("cgroupfs not available"); + + /* prepare the test */ + _cleanup_free_ char *unit_dir = NULL; + assert_se(get_testdata_dir("units", &unit_dir) >= 0); + assert_se(set_unit_path(unit_dir) >= 0); + assert_se(runtime_dir = setup_fake_runtime_dir()); + + r = manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &m); + if (manager_errno_skip_test(r)) + return log_tests_skipped_errno(r, "manager_new"); + assert_se(r >= 0); + assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + + printf("Load1:\n"); + assert_se(manager_load_startable_unit_or_warn(m, "a.service", NULL, &a) >= 0); + assert_se(manager_load_startable_unit_or_warn(m, "b.service", NULL, &b) >= 0); + assert_se(manager_load_startable_unit_or_warn(m, "c.service", NULL, &c) >= 0); + manager_dump_units(m, stdout, /* patterns= */ NULL, "\t"); + + printf("Test1: (Trivial)\n"); + r = manager_add_job(m, JOB_START, c, JOB_REPLACE, NULL, &err, &j); + if (sd_bus_error_is_set(&err)) + log_error("error: %s: %s", err.name, err.message); + assert_se(r == 0); + manager_dump_jobs(m, stdout, /* patterns= */ NULL, "\t"); + + printf("Load2:\n"); + manager_clear_jobs(m); + assert_se(manager_load_startable_unit_or_warn(m, "d.service", NULL, &d) >= 0); + assert_se(manager_load_startable_unit_or_warn(m, "e.service", NULL, &e) >= 0); + manager_dump_units(m, stdout, /* patterns= */ NULL, "\t"); + + printf("Test2: (Cyclic Order, Unfixable)\n"); + assert_se(manager_add_job(m, JOB_START, d, JOB_REPLACE, NULL, NULL, &j) == -EDEADLK); + manager_dump_jobs(m, stdout, /* patterns= */ NULL, "\t"); + + printf("Test3: (Cyclic Order, Fixable, Garbage Collector)\n"); + assert_se(manager_add_job(m, JOB_START, e, JOB_REPLACE, NULL, NULL, &j) == 0); + manager_dump_jobs(m, stdout, /* patterns= */ NULL, "\t"); + + printf("Test4: (Identical transaction)\n"); + assert_se(manager_add_job(m, JOB_START, e, JOB_FAIL, NULL, NULL, &j) == 0); + manager_dump_jobs(m, stdout, /* patterns= */ NULL, "\t"); + + printf("Load3:\n"); + assert_se(manager_load_startable_unit_or_warn(m, "g.service", NULL, &g) >= 0); + manager_dump_units(m, stdout, /* patterns= */ NULL, "\t"); + + printf("Test5: (Colliding transaction, fail)\n"); + assert_se(manager_add_job(m, JOB_START, g, JOB_FAIL, NULL, NULL, &j) == -EDEADLK); + + printf("Test6: (Colliding transaction, replace)\n"); + assert_se(manager_add_job(m, JOB_START, g, JOB_REPLACE, NULL, NULL, &j) == 0); + manager_dump_jobs(m, stdout, /* patterns= */ NULL, "\t"); + + printf("Test7: (Unmergeable job type, fail)\n"); + assert_se(manager_add_job(m, JOB_STOP, g, JOB_FAIL, NULL, NULL, &j) == -EDEADLK); + + printf("Test8: (Mergeable job type, fail)\n"); + assert_se(manager_add_job(m, JOB_RESTART, g, JOB_FAIL, NULL, NULL, &j) == 0); + manager_dump_jobs(m, stdout, /* patterns= */ NULL, "\t"); + + printf("Test9: (Unmergeable job type, replace)\n"); + assert_se(manager_add_job(m, JOB_STOP, g, JOB_REPLACE, NULL, NULL, &j) == 0); + manager_dump_jobs(m, stdout, /* patterns= */ NULL, "\t"); + + printf("Load4:\n"); + assert_se(manager_load_startable_unit_or_warn(m, "h.service", NULL, &h) >= 0); + manager_dump_units(m, stdout, /* patterns= */ NULL, "\t"); + + printf("Test10: (Unmergeable job type of auxiliary job, fail)\n"); + assert_se(manager_add_job(m, JOB_START, h, JOB_FAIL, NULL, NULL, &j) == 0); + manager_dump_jobs(m, stdout, /* patterns= */ NULL, "\t"); + + printf("Load5:\n"); + manager_clear_jobs(m); + assert_se(manager_load_startable_unit_or_warn(m, "i.service", NULL, &i) >= 0); + SERVICE(a)->state = SERVICE_RUNNING; + SERVICE(d)->state = SERVICE_RUNNING; + manager_dump_units(m, stdout, /* patterns= */ NULL, "\t"); + + printf("Test11: (Start/stop job ordering, execution cycle)\n"); + assert_se(manager_add_job(m, JOB_START, i, JOB_FAIL, NULL, NULL, &j) == 0); + assert_se(unit_has_job_type(a, JOB_STOP)); + assert_se(unit_has_job_type(d, JOB_STOP)); + assert_se(unit_has_job_type(b, JOB_START)); + manager_dump_jobs(m, stdout, /* patterns= */ NULL, "\t"); + + printf("Load6:\n"); + manager_clear_jobs(m); + assert_se(manager_load_startable_unit_or_warn(m, "a-conj.service", NULL, &a_conj) >= 0); + SERVICE(a)->state = SERVICE_DEAD; + manager_dump_units(m, stdout, /* patterns= */ NULL, "\t"); + + printf("Test12: (Trivial cycle, Unfixable)\n"); + assert_se(manager_add_job(m, JOB_START, a_conj, JOB_REPLACE, NULL, NULL, &j) == -EDEADLK); + manager_dump_jobs(m, stdout, /* patterns= */ NULL, "\t"); + + assert_se(!hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), b)); + assert_se(!hashmap_get(unit_get_dependencies(b, UNIT_RELOAD_PROPAGATED_FROM), a)); + assert_se(!hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), c)); + assert_se(!hashmap_get(unit_get_dependencies(c, UNIT_RELOAD_PROPAGATED_FROM), a)); + + assert_se(unit_add_dependency(a, UNIT_PROPAGATES_RELOAD_TO, b, true, UNIT_DEPENDENCY_UDEV) >= 0); + assert_se(unit_add_dependency(a, UNIT_PROPAGATES_RELOAD_TO, c, true, UNIT_DEPENDENCY_PROC_SWAP) >= 0); + + assert_se( hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), b)); + assert_se( hashmap_get(unit_get_dependencies(b, UNIT_RELOAD_PROPAGATED_FROM), a)); + assert_se( hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), c)); + assert_se( hashmap_get(unit_get_dependencies(c, UNIT_RELOAD_PROPAGATED_FROM), a)); + + unit_remove_dependencies(a, UNIT_DEPENDENCY_UDEV); + + assert_se(!hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), b)); + assert_se(!hashmap_get(unit_get_dependencies(b, UNIT_RELOAD_PROPAGATED_FROM), a)); + assert_se( hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), c)); + assert_se( hashmap_get(unit_get_dependencies(c, UNIT_RELOAD_PROPAGATED_FROM), a)); + + unit_remove_dependencies(a, UNIT_DEPENDENCY_PROC_SWAP); + + assert_se(!hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), b)); + assert_se(!hashmap_get(unit_get_dependencies(b, UNIT_RELOAD_PROPAGATED_FROM), a)); + assert_se(!hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), c)); + assert_se(!hashmap_get(unit_get_dependencies(c, UNIT_RELOAD_PROPAGATED_FROM), a)); + + assert_se(manager_load_unit(m, "unit-with-multiple-dashes.service", NULL, NULL, &unit_with_multiple_dashes) >= 0); + + assert_se(strv_equal(unit_with_multiple_dashes->documentation, STRV_MAKE("man:test", "man:override2", "man:override3"))); + assert_se(streq_ptr(unit_with_multiple_dashes->description, "override4")); + + /* Now merge a synthetic unit into the existing one */ + assert_se(unit_new_for_name(m, sizeof(Service), "merged.service", &stub) >= 0); + assert_se(unit_add_dependency_by_name(stub, UNIT_AFTER, SPECIAL_BASIC_TARGET, true, UNIT_DEPENDENCY_FILE) >= 0); + assert_se(unit_add_dependency_by_name(stub, UNIT_AFTER, "quux.target", true, UNIT_DEPENDENCY_FILE) >= 0); + assert_se(unit_add_dependency_by_name(stub, UNIT_AFTER, SPECIAL_ROOT_SLICE, true, UNIT_DEPENDENCY_FILE) >= 0); + assert_se(unit_add_dependency_by_name(stub, UNIT_REQUIRES, "non-existing.mount", true, UNIT_DEPENDENCY_FILE) >= 0); + assert_se(unit_add_dependency_by_name(stub, UNIT_ON_FAILURE, "non-existing-on-failure.target", true, UNIT_DEPENDENCY_FILE) >= 0); + assert_se(unit_add_dependency_by_name(stub, UNIT_ON_SUCCESS, "non-existing-on-success.target", true, UNIT_DEPENDENCY_FILE) >= 0); + + log_info("/* Merging a+stub, dumps before */"); + unit_dump(a, stderr, NULL); + unit_dump(stub, stderr, NULL); + assert_se(unit_merge(a, stub) >= 0); + log_info("/* Dump of merged a+stub */"); + unit_dump(a, stderr, NULL); + + assert_se( unit_has_dependency(a, UNIT_ATOM_AFTER, manager_get_unit(m, SPECIAL_BASIC_TARGET))); + assert_se( unit_has_dependency(a, UNIT_ATOM_AFTER, manager_get_unit(m, "quux.target"))); + assert_se( unit_has_dependency(a, UNIT_ATOM_AFTER, manager_get_unit(m, SPECIAL_ROOT_SLICE))); + assert_se( unit_has_dependency(a, UNIT_ATOM_PULL_IN_START, manager_get_unit(m, "non-existing.mount"))); + assert_se( unit_has_dependency(a, UNIT_ATOM_RETROACTIVE_START_REPLACE, manager_get_unit(m, "non-existing.mount"))); + assert_se( unit_has_dependency(a, UNIT_ATOM_ON_FAILURE, manager_get_unit(m, "non-existing-on-failure.target"))); + assert_se( unit_has_dependency(manager_get_unit(m, "non-existing-on-failure.target"), UNIT_ATOM_ON_FAILURE_OF, a)); + assert_se( unit_has_dependency(a, UNIT_ATOM_ON_SUCCESS, manager_get_unit(m, "non-existing-on-success.target"))); + assert_se( unit_has_dependency(manager_get_unit(m, "non-existing-on-success.target"), UNIT_ATOM_ON_SUCCESS_OF, a)); + assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_FAILURE, manager_get_unit(m, "basic.target"))); + assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_SUCCESS, manager_get_unit(m, "basic.target"))); + assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_FAILURE_OF, manager_get_unit(m, "basic.target"))); + assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_SUCCESS_OF, manager_get_unit(m, "basic.target"))); + assert_se(!unit_has_dependency(a, UNIT_ATOM_PROPAGATES_RELOAD_TO, manager_get_unit(m, "non-existing-on-failure.target"))); + + assert_se(unit_has_name(a, "a.service")); + assert_se(unit_has_name(a, "merged.service")); + + unsigned mm = 1; + Unit *other; + + UNIT_FOREACH_DEPENDENCY(other, a, UNIT_ATOM_AFTER) { + mm *= unit_has_name(other, SPECIAL_BASIC_TARGET) ? 3 : 1; + mm *= unit_has_name(other, "quux.target") ? 5 : 1; + mm *= unit_has_name(other, SPECIAL_ROOT_SLICE) ? 7 : 1; + } + + UNIT_FOREACH_DEPENDENCY(other, a, UNIT_ATOM_ON_FAILURE) + mm *= unit_has_name(other, "non-existing-on-failure.target") ? 11 : 1; + + UNIT_FOREACH_DEPENDENCY(other, a, UNIT_ATOM_PULL_IN_START) + mm *= unit_has_name(other, "non-existing.mount") ? 13 : 1; + + assert_se(mm == 3U*5U*7U*11U*13U); + + verify_dependency_atoms(); + + /* Test adding multiple Slice= dependencies; only the last should remain */ + assert_se(unit_new_for_name(m, sizeof(Service), "tomato.service", &tomato) >= 0); + assert_se(unit_new_for_name(m, sizeof(Slice), "sauce.slice", &sauce) >= 0); + assert_se(unit_new_for_name(m, sizeof(Slice), "fruit.slice", &fruit) >= 0); + assert_se(unit_new_for_name(m, sizeof(Slice), "zupa.slice", &zupa) >= 0); + + unit_set_slice(tomato, sauce); + unit_set_slice(tomato, fruit); + unit_set_slice(tomato, zupa); + + assert_se(UNIT_GET_SLICE(tomato) == zupa); + assert_se(!unit_has_dependency(tomato, UNIT_ATOM_IN_SLICE, sauce)); + assert_se(!unit_has_dependency(tomato, UNIT_ATOM_IN_SLICE, fruit)); + assert_se( unit_has_dependency(tomato, UNIT_ATOM_IN_SLICE, zupa)); + + assert_se(!unit_has_dependency(tomato, UNIT_ATOM_REFERENCES, sauce)); + assert_se(!unit_has_dependency(tomato, UNIT_ATOM_REFERENCES, fruit)); + assert_se( unit_has_dependency(tomato, UNIT_ATOM_REFERENCES, zupa)); + + assert_se(!unit_has_dependency(sauce, UNIT_ATOM_SLICE_OF, tomato)); + assert_se(!unit_has_dependency(fruit, UNIT_ATOM_SLICE_OF, tomato)); + assert_se( unit_has_dependency(zupa, UNIT_ATOM_SLICE_OF, tomato)); + + assert_se(!unit_has_dependency(sauce, UNIT_ATOM_REFERENCED_BY, tomato)); + assert_se(!unit_has_dependency(fruit, UNIT_ATOM_REFERENCED_BY, tomato)); + assert_se( unit_has_dependency(zupa, UNIT_ATOM_REFERENCED_BY, tomato)); + + return 0; +} diff --git a/src/test/test-env-file.c b/src/test/test-env-file.c new file mode 100644 index 0000000..3fc6d62 --- /dev/null +++ b/src/test/test-env-file.c @@ -0,0 +1,191 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "env-file.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "macro.h" +#include "strv.h" +#include "tests.h" +#include "tmpfile-util.h" + +/* In case of repeating keys, later entries win. */ + +#define env_file_1 \ + "a=a\n" \ + "a=b\n" \ + "a=b\n" \ + "a=a\n" \ + "b=b\\\n" \ + "c\n" \ + "d= d\\\n" \ + "e \\\n" \ + "f \n" \ + "g=g\\ \n" \ + "h= ąęół\\ śćńźżμ \n" \ + "i=i\\" + +#define env_file_2 \ + "a=a\\\n" + +#define env_file_3 \ + "#SPAMD_ARGS=\"-d --socketpath=/var/lib/bulwark/spamd \\\n" \ + "#--nouser-config \\\n" \ + "normal1=line\\\n" \ + "111\n" \ + ";normal=ignored \\\n" \ + "normal2=line222\n" \ + "normal ignored \\\n" + +#define env_file_4 \ + "# Generated\n" \ + "\n" \ + "HWMON_MODULES=\"coretemp f71882fg\"\n" \ + "\n" \ + "# For compatibility reasons\n" \ + "\n" \ + "MODULE_0=coretemp\n" \ + "MODULE_1=f71882fg" + +#define env_file_5 \ + "a=\n" \ + "b=" + +#define env_file_6 \ + "a=\\ \\n \\t \\x \\y \\' \n" \ + "b= \\$' \n" \ + "c= ' \\n\\t\\$\\`\\\\\n" \ + "' \n" \ + "d= \" \\n\\t\\$\\`\\\\\n" \ + "\" \n" + +TEST(load_env_file_1) { + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-load-env-file.XXXXXX"; + assert_se(write_tmpfile(name, env_file_1) == 0); + + _cleanup_strv_free_ char **data = NULL; + assert_se(load_env_file(NULL, name, &data) == 0); + assert_se(streq(data[0], "a=a")); + assert_se(streq(data[1], "b=bc")); + assert_se(streq(data[2], "d=de f")); + assert_se(streq(data[3], "g=g ")); + assert_se(streq(data[4], "h=ąęół śćńźżμ")); + assert_se(streq(data[5], "i=i")); + assert_se(data[6] == NULL); +} + +TEST(load_env_file_2) { + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-load-env-file.XXXXXX"; + assert_se(write_tmpfile(name, env_file_2) == 0); + + _cleanup_strv_free_ char **data = NULL; + assert_se(load_env_file(NULL, name, &data) == 0); + assert_se(streq(data[0], "a=a")); + assert_se(data[1] == NULL); +} + +TEST(load_env_file_3) { + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-load-env-file.XXXXXX"; + assert_se(write_tmpfile(name, env_file_3) == 0); + + _cleanup_strv_free_ char **data = NULL; + assert_se(load_env_file(NULL, name, &data) == 0); + assert_se(streq(data[0], "normal1=line111")); + assert_se(streq(data[1], "normal2=line222")); + assert_se(data[2] == NULL); +} + +TEST(load_env_file_4) { + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-load-env-file.XXXXXX"; + assert_se(write_tmpfile(name, env_file_4) == 0); + + _cleanup_strv_free_ char **data = NULL; + assert_se(load_env_file(NULL, name, &data) == 0); + assert_se(streq(data[0], "HWMON_MODULES=coretemp f71882fg")); + assert_se(streq(data[1], "MODULE_0=coretemp")); + assert_se(streq(data[2], "MODULE_1=f71882fg")); + assert_se(data[3] == NULL); +} + +TEST(load_env_file_5) { + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-load-env-file.XXXXXX"; + assert_se(write_tmpfile(name, env_file_5) == 0); + + _cleanup_strv_free_ char **data = NULL; + assert_se(load_env_file(NULL, name, &data) == 0); + assert_se(streq(data[0], "a=")); + assert_se(streq(data[1], "b=")); + assert_se(data[2] == NULL); +} + +TEST(load_env_file_6) { + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-load-env-file.XXXXXX"; + assert_se(write_tmpfile(name, env_file_6) == 0); + + _cleanup_strv_free_ char **data = NULL; + assert_se(load_env_file(NULL, name, &data) == 0); + assert_se(streq(data[0], "a= n t x y '")); + assert_se(streq(data[1], "b=$'")); + assert_se(streq(data[2], "c= \\n\\t\\$\\`\\\\\n")); + assert_se(streq(data[3], "d= \\n\\t$`\\\n")); + assert_se(data[4] == NULL); +} + +TEST(load_env_file_invalid_utf8) { + /* Test out a couple of assignments where the key/value has an invalid + * UTF-8 character ("noncharacter") + * + * See: https://en.wikipedia.org/wiki/Universal_Character_Set_characters#Non-characters + */ + FOREACH_STRING(s, + "fo\ufffeo=bar", + "foo=b\uffffar", + "baz=hello world\ufffe") { + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-load-env-file.XXXXXX"; + assert_se(write_tmpfile(name, s) == 0); + + _cleanup_strv_free_ char **data = NULL; + assert_se(load_env_file(NULL, name, &data) == -EINVAL); + assert_se(!data); + } +} + +TEST(write_and_load_env_file) { + /* Make sure that our writer, parser and the shell agree on what our env var files mean */ + + FOREACH_STRING(v, + "obbardc-laptop", + "obbardc\\-laptop", + "obbardc-lap\\top", + "obbardc-lap\\top", + "obbardc-lap\\\\top", + "double\"quote", + "single\'quote", + "dollar$dollar", + "newline\nnewline") { + _cleanup_(unlink_and_freep) char *p = NULL; + _cleanup_strv_free_ char **l = NULL; + _cleanup_free_ char *j = NULL, *w = NULL, *cmd = NULL, *from_shell = NULL; + _cleanup_pclose_ FILE *f = NULL; + size_t sz; + + assert_se(tempfn_random_child(NULL, NULL, &p) >= 0); + + assert_se(j = strjoin("TEST=", v)); + assert_se(write_env_file(AT_FDCWD, p, STRV_MAKE("# header 1", "", "# header 2"), STRV_MAKE(j)) >= 0); + + assert_se(cmd = strjoin(". ", p, " && /bin/echo -n \"$TEST\"")); + assert_se(f = popen(cmd, "re")); + assert_se(read_full_stream(f, &from_shell, &sz) >= 0); + assert_se(sz == strlen(v)); + assert_se(streq(from_shell, v)); + + assert_se(load_env_file(NULL, p, &l) >= 0); + assert_se(strv_equal(l, STRV_MAKE(j))); + + assert_se(parse_env_file(NULL, p, "TEST", &w) >= 0); + assert_se(streq_ptr(w, v)); + } +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-env-util.c b/src/test/test-env-util.c new file mode 100644 index 0000000..dffbad6 --- /dev/null +++ b/src/test/test-env-util.c @@ -0,0 +1,563 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "env-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "parse-util.h" +#include "process-util.h" +#include "serialize.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" + +TEST(strv_env_delete) { + _cleanup_strv_free_ char **a = NULL, **b = NULL, **c = NULL, **d = NULL; + + a = strv_new("FOO=BAR", "WALDO=WALDO", "WALDO=", "PIEP", "SCHLUMPF=SMURF"); + assert_se(a); + + b = strv_new("PIEP", "FOO"); + assert_se(b); + + c = strv_new("SCHLUMPF"); + assert_se(c); + + d = strv_env_delete(a, 2, b, c); + assert_se(d); + + assert_se(streq(d[0], "WALDO=WALDO")); + assert_se(streq(d[1], "WALDO=")); + assert_se(strv_length(d) == 2); +} + +TEST(strv_env_get) { + char **l = STRV_MAKE("ONE_OR_TWO=1", "THREE=3", "ONE_OR_TWO=2", "FOUR=4"); + + assert_se(streq(strv_env_get(l, "ONE_OR_TWO"), "2")); + assert_se(streq(strv_env_get(l, "THREE"), "3")); + assert_se(streq(strv_env_get(l, "FOUR"), "4")); +} + +TEST(strv_env_pairs_get) { + char **l = STRV_MAKE("ONE_OR_TWO", "1", "THREE", "3", "ONE_OR_TWO", "2", "FOUR", "4", "FIVE", "5", "SIX", "FIVE", "SEVEN", "7"); + + assert_se(streq(strv_env_pairs_get(l, "ONE_OR_TWO"), "2")); + assert_se(streq(strv_env_pairs_get(l, "THREE"), "3")); + assert_se(streq(strv_env_pairs_get(l, "FOUR"), "4")); + assert_se(streq(strv_env_pairs_get(l, "FIVE"), "5")); +} + +TEST(strv_env_unset) { + _cleanup_strv_free_ char **l = NULL; + + l = strv_new("PIEP", "SCHLUMPF=SMURFF", "NANANANA=YES"); + assert_se(l); + + assert_se(strv_env_unset(l, "SCHLUMPF") == l); + + assert_se(streq(l[0], "PIEP")); + assert_se(streq(l[1], "NANANANA=YES")); + assert_se(strv_length(l) == 2); +} + +TEST(strv_env_merge) { + char **a = STRV_MAKE("FOO=BAR", "WALDO=WALDO", "WALDO=", "PIEP", "SCHLUMPF=SMURF", "EQ==="); + char **b = STRV_MAKE("FOO=KKK", "FOO=", "PIEP=", "SCHLUMPF=SMURFF", "NANANANA=YES"); + + _cleanup_strv_free_ char **r = strv_env_merge(NULL, a, NULL, b, NULL, a, b, b, NULL); + assert_se(r); + assert_se(streq(r[0], "FOO=")); + assert_se(streq(r[1], "WALDO=")); + assert_se(streq(r[2], "PIEP")); + assert_se(streq(r[3], "SCHLUMPF=SMURFF")); + assert_se(streq(r[4], "EQ===")); + assert_se(streq(r[5], "PIEP=")); + assert_se(streq(r[6], "NANANANA=YES")); + assert_se(strv_length(r) == 7); + + assert_se(strv_env_clean(r) == r); + assert_se(streq(r[0], "FOO=")); + assert_se(streq(r[1], "WALDO=")); + assert_se(streq(r[2], "SCHLUMPF=SMURFF")); + assert_se(streq(r[3], "EQ===")); + assert_se(streq(r[4], "PIEP=")); + assert_se(streq(r[5], "NANANANA=YES")); + assert_se(strv_length(r) == 6); +} + +TEST(strv_env_replace_strdup) { + _cleanup_strv_free_ char **a = NULL; + + assert_se(strv_env_replace_strdup(&a, "a=a") == 1); + assert_se(strv_env_replace_strdup(&a, "b=b") == 1); + assert_se(strv_env_replace_strdup(&a, "a=A") == 0); + assert_se(strv_env_replace_strdup(&a, "c") == -EINVAL); + + assert_se(strv_length(a) == 2); + strv_sort(a); + assert_se(streq(a[0], "a=A")); + assert_se(streq(a[1], "b=b")); +} + +TEST(strv_env_replace_strdup_passthrough) { + _cleanup_strv_free_ char **a = NULL; + + assert_se(putenv((char*) "a=a") == 0); + assert_se(putenv((char*) "b=") == 0); + assert_se(unsetenv("c") == 0); + + assert_se(strv_env_replace_strdup_passthrough(&a, "a") == 1); + assert_se(strv_env_replace_strdup_passthrough(&a, "b") == 1); + assert_se(strv_env_replace_strdup_passthrough(&a, "c") == 1); + assert_se(strv_env_replace_strdup_passthrough(&a, "a") == 0); + assert_se(strv_env_replace_strdup_passthrough(&a, "$a") == -EINVAL); + + assert_se(strv_length(a) == 3); + assert_se(streq(a[0], "a=a")); + assert_se(streq(a[1], "b=")); + assert_se(streq(a[2], "c=")); +} + +TEST(strv_env_assign) { + _cleanup_strv_free_ char **a = NULL; + + assert_se(strv_env_assign(&a, "a", "a") == 1); + assert_se(strv_env_assign(&a, "b", "b") == 1); + assert_se(strv_env_assign(&a, "a", "A") == 0); + assert_se(strv_env_assign(&a, "b", NULL) == 0); + + assert_se(strv_env_assign(&a, "a=", "B") == -EINVAL); + + assert_se(strv_length(a) == 1); + assert_se(streq(a[0], "a=A")); +} + +TEST(strv_env_assign_many) { + _cleanup_strv_free_ char **a = NULL; + + assert_se(strv_env_assign_many(&a, "a", "a", "b", "b") >= 0); + + assert_se(strv_length(a) == 2); + assert_se(strv_contains(a, "a=a")); + assert_se(strv_contains(a, "b=b")); + + assert_se(strv_env_assign_many(&a, "a", "A", "b", "b", "c", "c") >= 0); + assert_se(strv_length(a) == 3); + assert_se(strv_contains(a, "a=A")); + assert_se(strv_contains(a, "b=b")); + assert_se(strv_contains(a, "c=c")); + + assert_se(strv_env_assign_many(&a, "b", NULL, "c", "C") >= 0); + assert_se(strv_length(a) == 2); + assert_se(strv_contains(a, "a=A")); + assert_se(strv_contains(a, "c=C")); + + assert_se(strv_env_assign_many(&a, "a=", "B") == -EINVAL); + assert_se(strv_length(a) == 2); + assert_se(strv_contains(a, "a=A")); + assert_se(strv_contains(a, "c=C")); +} + +TEST(env_strv_get_n) { + const char *_env[] = { + "FOO=NO NO NO", + "FOO=BAR BAR", + "BAR=waldo", + "PATH=unset", + NULL + }; + char **env = (char**) _env; + + assert_se(streq(strv_env_get_n(env, "FOO__", 3, 0), "BAR BAR")); + assert_se(streq(strv_env_get_n(env, "FOO__", 3, REPLACE_ENV_USE_ENVIRONMENT), "BAR BAR")); + assert_se(streq(strv_env_get_n(env, "FOO", 3, 0), "BAR BAR")); + assert_se(streq(strv_env_get_n(env, "FOO", 3, REPLACE_ENV_USE_ENVIRONMENT), "BAR BAR")); + + assert_se(streq(strv_env_get_n(env, "PATH__", 4, 0), "unset")); + assert_se(streq(strv_env_get_n(env, "PATH", 4, 0), "unset")); + assert_se(streq(strv_env_get_n(env, "PATH__", 4, REPLACE_ENV_USE_ENVIRONMENT), "unset")); + assert_se(streq(strv_env_get_n(env, "PATH", 4, REPLACE_ENV_USE_ENVIRONMENT), "unset")); + + env[3] = NULL; /* kill our $PATH */ + + assert_se(!strv_env_get_n(env, "PATH__", 4, 0)); + assert_se(!strv_env_get_n(env, "PATH", 4, 0)); + assert_se(streq_ptr(strv_env_get_n(env, "PATH__", 4, REPLACE_ENV_USE_ENVIRONMENT), + getenv("PATH"))); + assert_se(streq_ptr(strv_env_get_n(env, "PATH", 4, REPLACE_ENV_USE_ENVIRONMENT), + getenv("PATH"))); +} + +static void test_replace_env1(bool braceless) { + log_info("/* %s(braceless=%s) */", __func__, yes_no(braceless)); + + const char *env[] = { + "FOO=BAR BAR", + "BAR=waldo", + NULL + }; + _cleanup_free_ char *t = NULL, *s = NULL, *q = NULL, *r = NULL, *p = NULL; + unsigned flags = REPLACE_ENV_ALLOW_BRACELESS*braceless; + + assert_se(replace_env("FOO=$FOO=${FOO}", (char**) env, flags, &t) >= 0); + assert_se(streq(t, braceless ? "FOO=BAR BAR=BAR BAR" : "FOO=$FOO=BAR BAR")); + + assert_se(replace_env("BAR=$BAR=${BAR}", (char**) env, flags, &s) >= 0); + assert_se(streq(s, braceless ? "BAR=waldo=waldo" : "BAR=$BAR=waldo")); + + assert_se(replace_env("BARBAR=$BARBAR=${BARBAR}", (char**) env, flags, &q) >= 0); + assert_se(streq(q, braceless ? "BARBAR==" : "BARBAR=$BARBAR=")); + + assert_se(replace_env("BAR=$BAR$BAR${BAR}${BAR}", (char**) env, flags, &r) >= 0); + assert_se(streq(r, braceless ? "BAR=waldowaldowaldowaldo" : "BAR=$BAR$BARwaldowaldo")); + + assert_se(replace_env("${BAR}$BAR$BAR", (char**) env, flags, &p) >= 0); + assert_se(streq(p, braceless ? "waldowaldowaldo" : "waldo$BAR$BAR")); +} + +static void test_replace_env2(bool extended) { + log_info("/* %s(extended=%s) */", __func__, yes_no(extended)); + + const char *env[] = { + "FOO=foo", + "BAR=bar", + NULL + }; + _cleanup_free_ char *t = NULL, *s = NULL, *q = NULL, *r = NULL, *p = NULL, *x = NULL, *y = NULL; + unsigned flags = REPLACE_ENV_ALLOW_EXTENDED*extended; + + assert_se(replace_env("FOO=${FOO:-${BAR}}", (char**) env, flags, &t) >= 0); + assert_se(streq(t, extended ? "FOO=foo" : "FOO=${FOO:-bar}")); + + assert_se(replace_env("BAR=${XXX:-${BAR}}", (char**) env, flags, &s) >= 0); + assert_se(streq(s, extended ? "BAR=bar" : "BAR=${XXX:-bar}")); + + assert_se(replace_env("XXX=${XXX:+${BAR}}", (char**) env, flags, &q) >= 0); + assert_se(streq(q, extended ? "XXX=" : "XXX=${XXX:+bar}")); + + assert_se(replace_env("FOO=${FOO:+${BAR}}", (char**) env, flags, &r) >= 0); + assert_se(streq(r, extended ? "FOO=bar" : "FOO=${FOO:+bar}")); + + assert_se(replace_env("FOO=${FOO:-${BAR}post}", (char**) env, flags, &p) >= 0); + assert_se(streq(p, extended ? "FOO=foo" : "FOO=${FOO:-barpost}")); + + assert_se(replace_env("XXX=${XXX:+${BAR}post}", (char**) env, flags, &x) >= 0); + assert_se(streq(x, extended ? "XXX=" : "XXX=${XXX:+barpost}")); + + assert_se(replace_env("FOO=${FOO}between${BAR:-baz}", (char**) env, flags, &y) >= 0); + assert_se(streq(y, extended ? "FOO=foobetweenbar" : "FOO=foobetween${BAR:-baz}")); +} + +TEST(replace_env) { + test_replace_env1(false); + test_replace_env1(true); + test_replace_env2(false); + test_replace_env2(true); +} + +TEST(replace_env_argv) { + const char *env[] = { + "FOO=BAR BAR", + "BAR=waldo", + NULL + }; + const char *line[] = { + "FOO$FOO", + "FOO$FOOFOO", + "FOO${FOO}$FOO", + "FOO${FOO}", + "${FOO}", + "$FOO", + "$FOO$FOO", + "${FOO}${BAR}", + "${FOO", + "FOO$$${FOO}", + "$$FOO${FOO}", + "${FOO:-${BAR}}", + "${QUUX:-${FOO}}", + "${FOO:+${BAR}}", + "${QUUX:+${BAR}}", + "${FOO:+|${BAR}|}}", + "${FOO:+|${BAR}{|}", + NULL + }; + _cleanup_strv_free_ char **r = NULL; + + assert_se(replace_env_argv((char**) line, (char**) env, &r, NULL, NULL) >= 0); + assert_se(r); + assert_se(streq(r[0], "FOO$FOO")); + assert_se(streq(r[1], "FOO$FOOFOO")); + assert_se(streq(r[2], "FOOBAR BAR$FOO")); + assert_se(streq(r[3], "FOOBAR BAR")); + assert_se(streq(r[4], "BAR BAR")); + assert_se(streq(r[5], "BAR")); + assert_se(streq(r[6], "BAR")); + assert_se(streq(r[7], "BAR BARwaldo")); + assert_se(streq(r[8], "${FOO")); + assert_se(streq(r[9], "FOO$BAR BAR")); + assert_se(streq(r[10], "$FOOBAR BAR")); + assert_se(streq(r[11], "${FOO:-waldo}")); + assert_se(streq(r[12], "${QUUX:-BAR BAR}")); + assert_se(streq(r[13], "${FOO:+waldo}")); + assert_se(streq(r[14], "${QUUX:+waldo}")); + assert_se(streq(r[15], "${FOO:+|waldo|}}")); + assert_se(streq(r[16], "${FOO:+|waldo{|}")); + assert_se(strv_length(r) == 17); +} + +TEST(replace_env_argv_bad) { + + const char *env[] = { + "FOO=BAR BAR", + "BAR=waldo", + NULL + }; + + const char *line[] = { + "$FOO", + "A${FOO}B", + "a${~}${%}b", + "x${}y", + "$UNSET2", + "z${UNSET3}z${UNSET1}z", + "piff${UNSET2}piff", + NULL + }; + + _cleanup_strv_free_ char **bad = NULL, **unset = NULL, **replaced = NULL; + + assert_se(replace_env_argv((char**) line, (char**) env, &replaced, &unset, &bad) >= 0); + + assert_se(strv_equal(replaced, STRV_MAKE( + "BAR", + "BAR", + "ABAR BARB", + "ab", + "xy", + "zzz", + "piffpiff"))); + + assert_se(strv_equal(unset, STRV_MAKE( + "UNSET1", + "UNSET2", + "UNSET3"))); + assert_se(strv_equal(bad, STRV_MAKE("", + "%", + "~"))); +} + +TEST(env_clean) { + _cleanup_strv_free_ char **e = strv_new("FOOBAR=WALDO", + "FOOBAR=WALDO", + "FOOBAR", + "F", + "X=", + "F=F", + "=", + "=F", + "", + "0000=000", + "äöüß=abcd", + "abcd=äöüß", + "xyz\n=xyz", + "xyz=xyz\n", + "another=one", + "another=final one", + "CRLF=\r\n", + "LESS_TERMCAP_mb=\x1b[01;31m", + "BASH_FUNC_foo%%=() { echo foo\n}"); + assert_se(e); + assert_se(!strv_env_is_valid(e)); + assert_se(strv_env_clean(e) == e); + assert_se(strv_env_is_valid(e)); + + assert_se(streq(e[0], "FOOBAR=WALDO")); + assert_se(streq(e[1], "X=")); + assert_se(streq(e[2], "F=F")); + assert_se(streq(e[3], "abcd=äöüß")); + assert_se(streq(e[4], "xyz=xyz\n")); + assert_se(streq(e[5], "another=final one")); + assert_se(streq(e[6], "CRLF=\r\n")); + assert_se(streq(e[7], "LESS_TERMCAP_mb=\x1b[01;31m")); + assert_se(e[8] == NULL); +} + +TEST(env_name_is_valid) { + assert_se(env_name_is_valid("test")); + + assert_se(!env_name_is_valid(NULL)); + assert_se(!env_name_is_valid("")); + assert_se(!env_name_is_valid("xxx\a")); + assert_se(!env_name_is_valid("xxx\007b")); + assert_se(!env_name_is_valid("\007\009")); + assert_se(!env_name_is_valid("5_starting_with_a_number_is_wrong")); + assert_se(!env_name_is_valid("#¤%&?_only_numbers_letters_and_underscore_allowed")); +} + +TEST(env_value_is_valid) { + assert_se(env_value_is_valid("")); + assert_se(env_value_is_valid("głąb kapuściany")); + assert_se(env_value_is_valid("printf \"\\x1b]0;<mock-chroot>\\x07<mock-chroot>\"")); + assert_se(env_value_is_valid("tab\tcharacter")); + assert_se(env_value_is_valid("new\nline")); + assert_se(env_value_is_valid("Show this?\rNope. Show that!")); + assert_se(env_value_is_valid("new DOS\r\nline")); + + assert_se(!env_value_is_valid("\xc5")); /* A truncated utf-8-encoded "ł". + * We currently disallow that. */ +} + +TEST(env_assignment_is_valid) { + assert_se(env_assignment_is_valid("a=")); + assert_se(env_assignment_is_valid("b=głąb kapuściany")); + assert_se(env_assignment_is_valid("c=\\007\\009\\011")); + assert_se(env_assignment_is_valid("e=printf \"\\x1b]0;<mock-chroot>\\x07<mock-chroot>\"")); + assert_se(env_assignment_is_valid("f=tab\tcharacter")); + assert_se(env_assignment_is_valid("g=new\nline")); + + assert_se(!env_assignment_is_valid("=")); + assert_se(!env_assignment_is_valid("a b=")); + assert_se(!env_assignment_is_valid("a =")); + assert_se(!env_assignment_is_valid(" b=")); + /* no dots or dashes: http://tldp.org/LDP/abs/html/gotchas.html */ + assert_se(!env_assignment_is_valid("a.b=")); + assert_se(!env_assignment_is_valid("a-b=")); + assert_se(!env_assignment_is_valid("\007=głąb kapuściany")); + assert_se(!env_assignment_is_valid("c\009=\007\009\011")); + assert_se(!env_assignment_is_valid("głąb=printf \"\x1b]0;<mock-chroot>\x07<mock-chroot>\"")); +} + +TEST(putenv_dup) { + assert_se(putenv_dup("A=a1", true) == 0); + assert_se(streq_ptr(getenv("A"), "a1")); + assert_se(putenv_dup("A=a1", true) == 0); + assert_se(streq_ptr(getenv("A"), "a1")); + assert_se(putenv_dup("A=a2", false) == 0); + assert_se(streq_ptr(getenv("A"), "a1")); + assert_se(putenv_dup("A=a2", true) == 0); + assert_se(streq_ptr(getenv("A"), "a2")); +} + +TEST(setenv_systemd_exec_pid) { + _cleanup_free_ char *saved = NULL; + const char *e; + pid_t p; + + e = getenv("SYSTEMD_EXEC_PID"); + if (e) + assert_se(saved = strdup(e)); + + assert_se(unsetenv("SYSTEMD_EXEC_PID") >= 0); + assert_se(setenv_systemd_exec_pid(true) == 0); + assert_se(!getenv("SYSTEMD_EXEC_PID")); + + assert_se(setenv("SYSTEMD_EXEC_PID", "*", 1) >= 0); + assert_se(setenv_systemd_exec_pid(true) == 0); + assert_se(e = getenv("SYSTEMD_EXEC_PID")); + assert_se(streq(e, "*")); + + assert_se(setenv("SYSTEMD_EXEC_PID", "123abc", 1) >= 0); + assert_se(setenv_systemd_exec_pid(true) == 1); + assert_se(e = getenv("SYSTEMD_EXEC_PID")); + assert_se(parse_pid(e, &p) >= 0); + assert_se(p == getpid_cached()); + + assert_se(unsetenv("SYSTEMD_EXEC_PID") >= 0); + assert_se(setenv_systemd_exec_pid(false) == 1); + assert_se(e = getenv("SYSTEMD_EXEC_PID")); + assert_se(parse_pid(e, &p) >= 0); + assert_se(p == getpid_cached()); + + assert_se(set_unset_env("SYSTEMD_EXEC_PID", saved, 1) >= 0); +} + +TEST(getenv_steal_erase) { + int r; + + r = safe_fork("(sd-getenvstealerase)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL); + if (r == 0) { + _cleanup_strv_free_ char **l = NULL; + + /* child */ + + assert_se(getenv_steal_erase("thisenvvardefinitelywontexist", NULL) == 0); + + l = strv_new("FOO=BAR", "QUUX=PIFF", "ONE=TWO", "A=B"); + assert_se(strv_length(l) == 4); + + environ = l; + + STRV_FOREACH(e, environ) { + _cleanup_free_ char *n = NULL, *copy1 = NULL, *copy2 = NULL; + char *eq; + + eq = strchr(*e, '='); + if (!eq) + continue; + + n = strndup(*e, eq - *e); + assert_se(n); + + copy1 = strdup(eq + 1); + assert_se(copy1); + + assert_se(streq_ptr(getenv(n), copy1)); + assert_se(getenv(n) == eq + 1); + assert_se(getenv_steal_erase(n, ©2) > 0); + assert_se(streq_ptr(copy1, copy2)); + assert_se(isempty(eq + 1)); + assert_se(!getenv(n)); + } + + environ = NULL; + l = strv_free(l); + + _exit(EXIT_SUCCESS); + } + + assert_se(r > 0); +} + +TEST(strv_env_name_is_valid) { + assert_se(strv_env_name_is_valid(STRV_MAKE("HOME", "USER", "SHELL", "PATH"))); + assert_se(!strv_env_name_is_valid(STRV_MAKE("", "PATH", "home", "user", "SHELL"))); + assert_se(!strv_env_name_is_valid(STRV_MAKE("HOME", "USER", "SHELL", "USER"))); +} + +TEST(getenv_path_list) { + _cleanup_strv_free_ char **path_list = NULL; + + /* Empty paths */ + FOREACH_STRING(s, "", ":", ":::::", " : ::: :: :") { + assert_se(setenv("TEST_GETENV_PATH_LIST", s, 1) >= 0); + assert_se(getenv_path_list("TEST_GETENV_PATH_LIST", &path_list) == -EINVAL); + assert_se(!path_list); + } + + /* Invalid paths */ + FOREACH_STRING(s, ".", "..", "/../", "/", "/foo/bar/baz/../foo", "foo/bar/baz") { + assert_se(setenv("TEST_GETENV_PATH_LIST", s, 1) >= 0); + assert_se(getenv_path_list("TEST_GETENV_PATH_LIST", &path_list) == -EINVAL); + assert_se(!path_list); + } + + /* Valid paths mixed with invalid ones */ + assert_se(setenv("TEST_GETENV_PATH_LIST", "/foo:/bar/baz:/../:/hello", 1) >= 0); + assert_se(getenv_path_list("TEST_GETENV_PATH_LIST", &path_list) == -EINVAL); + assert_se(!path_list); + + /* Finally some valid paths */ + assert_se(setenv("TEST_GETENV_PATH_LIST", "/foo:/bar/baz:/hello/world:/path with spaces:/final", 1) >= 0); + assert_se(getenv_path_list("TEST_GETENV_PATH_LIST", &path_list) >= 0); + assert_se(streq(path_list[0], "/foo")); + assert_se(streq(path_list[1], "/bar/baz")); + assert_se(streq(path_list[2], "/hello/world")); + assert_se(streq(path_list[3], "/path with spaces")); + assert_se(streq(path_list[4], "/final")); + assert_se(path_list[5] == NULL); + + assert_se(unsetenv("TEST_GETENV_PATH_LIST") >= 0); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-errno-list.c b/src/test/test-errno-list.c new file mode 100644 index 0000000..f91a1f7 --- /dev/null +++ b/src/test/test-errno-list.c @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> + +#include "errno-list.h" +#include "errno-to-name.h" +#include "macro.h" +#include "string-util.h" +#include "tests.h" + +TEST(errno_list) { + for (size_t i = 0; i < ELEMENTSOF(errno_names); i++) { + if (errno_names[i]) { + assert_se(streq(errno_to_name(i), errno_names[i])); + assert_se(errno_from_name(errno_names[i]) == (int) i); + } + } + +#ifdef ECANCELLED + /* ECANCELLED is an alias of ECANCELED. */ + assert_se(streq(errno_to_name(ECANCELLED), "ECANCELED")); +#endif + assert_se(streq(errno_to_name(ECANCELED), "ECANCELED")); + +#ifdef EREFUSED + /* EREFUSED is an alias of ECONNREFUSED. */ + assert_se(streq(errno_to_name(EREFUSED), "ECONNREFUSED")); +#endif + assert_se(streq(errno_to_name(ECONNREFUSED), "ECONNREFUSED")); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-errno-util.c b/src/test/test-errno-util.c new file mode 100644 index 0000000..376d532 --- /dev/null +++ b/src/test/test-errno-util.c @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "errno-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "tests.h" + +TEST(strerror_not_threadsafe) { + /* Just check that strerror really is not thread-safe. */ + log_info("strerror(%d) → %s", 200, strerror(200)); + log_info("strerror(%d) → %s", 201, strerror(201)); + log_info("strerror(%d) → %s", INT_MAX, strerror(INT_MAX)); + + log_info("strerror(%d), strerror(%d) → %p, %p", 200, 201, strerror(200), strerror(201)); + + /* This call is not allowed, because the first returned string becomes invalid when + * we call strerror the second time: + * + * log_info("strerror(%d), strerror(%d) → %s, %s", 200, 201, strerror(200), strerror(201)); + */ +} + +TEST(STRERROR) { + /* Just check that STRERROR really is thread-safe. */ + log_info("STRERROR(%d) → %s", 200, STRERROR(200)); + log_info("STRERROR(%d) → %s", 201, STRERROR(201)); + log_info("STRERROR(%d), STRERROR(%d) → %s, %s", 200, 201, STRERROR(200), STRERROR(201)); + + const char *a = STRERROR(200), *b = STRERROR(201); + assert_se(strstr(a, "200")); + assert_se(strstr(b, "201")); + + /* Check with negative values */ + assert_se(streq(a, STRERROR(-200))); + assert_se(streq(b, STRERROR(-201))); + + const char *c = STRERROR(INT_MAX); + char buf[DECIMAL_STR_MAX(int)]; + xsprintf(buf, "%d", INT_MAX); /* INT_MAX is hexadecimal, use printf to convert to decimal */ + log_info("STRERROR(%d) → %s", INT_MAX, c); + assert_se(strstr(c, buf)); +} + +TEST(STRERROR_OR_ELSE) { + log_info("STRERROR_OR_ELSE(0, \"EOF\") → %s", STRERROR_OR_EOF(0)); + log_info("STRERROR_OR_ELSE(EPERM, \"EOF\") → %s", STRERROR_OR_EOF(EPERM)); + log_info("STRERROR_OR_ELSE(-EPERM, \"EOF\") → %s", STRERROR_OR_EOF(-EPERM)); +} + +TEST(PROTECT_ERRNO) { + errno = 12; + { + PROTECT_ERRNO; + errno = 11; + } + assert_se(errno == 12); +} + +static void test_unprotect_errno_inner_function(void) { + PROTECT_ERRNO; + + errno = 2222; +} + +TEST(UNPROTECT_ERRNO) { + errno = 4711; + + PROTECT_ERRNO; + + errno = 815; + + UNPROTECT_ERRNO; + + assert_se(errno == 4711); + + test_unprotect_errno_inner_function(); + + assert_se(errno == 4711); +} + +TEST(RET_GATHER) { + int x = 0, y = 2; + + assert_se(RET_GATHER(x, 5) == 0); + assert_se(RET_GATHER(x, -5) == -5); + assert_se(RET_GATHER(x, -1) == -5); + + assert_se(RET_GATHER(x, y++) == -5); + assert_se(y == 3); +} + +TEST(ERRNO_IS_TRANSIENT) { + assert_se( ERRNO_IS_NEG_TRANSIENT(-EINTR)); + assert_se(!ERRNO_IS_NEG_TRANSIENT(EINTR)); + assert_se( ERRNO_IS_TRANSIENT(-EINTR)); + assert_se( ERRNO_IS_TRANSIENT(EINTR)); + + /* Test with type wider than int */ + ssize_t r = -EAGAIN; + assert_se( ERRNO_IS_NEG_TRANSIENT(r)); + + /* On 64-bit arches, now (int) r == EAGAIN */ + r = SSIZE_MAX - EAGAIN + 1; + assert_se(!ERRNO_IS_NEG_TRANSIENT(r)); + + assert_se(!ERRNO_IS_NEG_TRANSIENT(INT_MAX)); + assert_se(!ERRNO_IS_NEG_TRANSIENT(INT_MIN)); + assert_se(!ERRNO_IS_NEG_TRANSIENT(INTMAX_MAX)); + assert_se(!ERRNO_IS_NEG_TRANSIENT(INTMAX_MIN)); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-escape.c b/src/test/test-escape.c new file mode 100644 index 0000000..21786ae --- /dev/null +++ b/src/test/test-escape.c @@ -0,0 +1,242 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "escape.h" +#include "macro.h" +#include "tests.h" + +TEST(cescape) { + _cleanup_free_ char *t = NULL; + + assert_se(t = cescape("abc\\\"\b\f\n\r\t\v\a\003\177\234\313")); + assert_se(streq(t, "abc\\\\\\\"\\b\\f\\n\\r\\t\\v\\a\\003\\177\\234\\313")); +} + +TEST(xescape) { + _cleanup_free_ char *t = NULL; + + assert_se(t = xescape("abc\\\"\b\f\n\r\t\v\a\003\177\234\313", "")); + assert_se(streq(t, "abc\\x5c\"\\x08\\x0c\\x0a\\x0d\\x09\\x0b\\x07\\x03\\x7f\\x9c\\xcb")); +} + +static void test_xescape_full_one(bool eight_bits) { + const char* escaped = !eight_bits ? + "a\\x62c\\x5c\"\\x08\\x0c\\x0a\\x0d\\x09\\x0b\\x07\\x03\\x7f\\x9c\\xcb" : + "a\\x62c\\x5c\"\\x08\\x0c\\x0a\\x0d\\x09\\x0b\\x07\\x03\177\234\313"; + const unsigned full_fit = !eight_bits ? 55 : 46; + XEscapeFlags flags = eight_bits * XESCAPE_8_BIT; + + log_info("/* %s */", __func__); + + for (unsigned i = 0; i < 60; i++) { + _cleanup_free_ char *t = NULL, *q = NULL; + + assert_se(t = xescape_full("abc\\\"\b\f\n\r\t\v\a\003\177\234\313", "b", i, flags)); + + log_info("%02u: <%s>", i, t); + + if (i >= full_fit) + assert_se(streq(t, escaped)); + else if (i >= 3) { + /* We need up to four columns, so up to three columns may be wasted */ + assert_se(strlen(t) == i || strlen(t) == i - 1 || strlen(t) == i - 2 || strlen(t) == i - 3); + assert_se(strneq(t, escaped, i - 3) || strneq(t, escaped, i - 4) || + strneq(t, escaped, i - 5) || strneq(t, escaped, i - 6)); + assert_se(endswith(t, "...")); + } else { + assert_se(strlen(t) == i); + assert_se(strneq(t, "...", i)); + } + + assert_se(q = xescape_full("abc\\\"\b\f\n\r\t\v\a\003\177\234\313", "b", i, + flags | XESCAPE_FORCE_ELLIPSIS)); + + log_info("%02u: <%s>", i, q); + if (i > 0) + assert_se(endswith(q, ".")); + assert_se(strlen(q) <= i); + assert_se(strlen(q) + 3 >= strlen(t)); + } +} + +TEST(xescape_full) { + test_xescape_full_one(false); + test_xescape_full_one(true); +} + +TEST(cunescape) { + _cleanup_free_ char *unescaped = NULL; + + assert_se(cunescape("abc\\\\\\\"\\b\\f\\a\\n\\r\\t\\v\\003\\177\\234\\313\\000\\x00", 0, &unescaped) < 0); + assert_se(cunescape("abc\\\\\\\"\\b\\f\\a\\n\\r\\t\\v\\003\\177\\234\\313\\000\\x00", UNESCAPE_RELAX, &unescaped) >= 0); + assert_se(streq_ptr(unescaped, "abc\\\"\b\f\a\n\r\t\v\003\177\234\313\\000\\x00")); + unescaped = mfree(unescaped); + + /* incomplete sequences */ + assert_se(cunescape("\\x0", 0, &unescaped) < 0); + assert_se(cunescape("\\x0", UNESCAPE_RELAX, &unescaped) >= 0); + assert_se(streq_ptr(unescaped, "\\x0")); + unescaped = mfree(unescaped); + + assert_se(cunescape("\\x", 0, &unescaped) < 0); + assert_se(cunescape("\\x", UNESCAPE_RELAX, &unescaped) >= 0); + assert_se(streq_ptr(unescaped, "\\x")); + unescaped = mfree(unescaped); + + assert_se(cunescape("\\", 0, &unescaped) < 0); + assert_se(cunescape("\\", UNESCAPE_RELAX, &unescaped) >= 0); + assert_se(streq_ptr(unescaped, "\\")); + unescaped = mfree(unescaped); + + assert_se(cunescape("\\11", 0, &unescaped) < 0); + assert_se(cunescape("\\11", UNESCAPE_RELAX, &unescaped) >= 0); + assert_se(streq_ptr(unescaped, "\\11")); + unescaped = mfree(unescaped); + + assert_se(cunescape("\\1", 0, &unescaped) < 0); + assert_se(cunescape("\\1", UNESCAPE_RELAX, &unescaped) >= 0); + assert_se(streq_ptr(unescaped, "\\1")); + unescaped = mfree(unescaped); + + assert_se(cunescape("\\u0000", 0, &unescaped) < 0); + assert_se(cunescape("\\u00DF\\U000000df\\u03a0\\U00000041", UNESCAPE_RELAX, &unescaped) >= 0); + assert_se(streq_ptr(unescaped, "ßßΠA")); + unescaped = mfree(unescaped); + + assert_se(cunescape("\\073", 0, &unescaped) >= 0); + assert_se(streq_ptr(unescaped, ";")); + unescaped = mfree(unescaped); + + assert_se(cunescape("A=A\\\\x0aB", 0, &unescaped) >= 0); + assert_se(streq_ptr(unescaped, "A=A\\x0aB")); + unescaped = mfree(unescaped); + + assert_se(cunescape("A=A\\\\x0aB", UNESCAPE_RELAX, &unescaped) >= 0); + assert_se(streq_ptr(unescaped, "A=A\\x0aB")); + unescaped = mfree(unescaped); + + assert_se(cunescape("\\x00\\x00\\x00", UNESCAPE_ACCEPT_NUL, &unescaped) == 3); + assert_se(memcmp(unescaped, "\0\0\0", 3) == 0); + unescaped = mfree(unescaped); + + assert_se(cunescape("\\u0000\\u0000\\u0000", UNESCAPE_ACCEPT_NUL, &unescaped) == 3); + assert_se(memcmp(unescaped, "\0\0\0", 3) == 0); + unescaped = mfree(unescaped); + + assert_se(cunescape("\\U00000000\\U00000000\\U00000000", UNESCAPE_ACCEPT_NUL, &unescaped) == 3); + assert_se(memcmp(unescaped, "\0\0\0", 3) == 0); + unescaped = mfree(unescaped); + + assert_se(cunescape("\\000\\000\\000", UNESCAPE_ACCEPT_NUL, &unescaped) == 3); + assert_se(memcmp(unescaped, "\0\0\0", 3) == 0); +} + +static void test_shell_escape_one(const char *s, const char *bad, const char *expected) { + _cleanup_free_ char *r = NULL; + + assert_se(r = shell_escape(s, bad)); + log_debug("%s → %s (expected %s)", s, r, expected); + assert_se(streq_ptr(r, expected)); +} + +TEST(shell_escape) { + test_shell_escape_one("", "", ""); + test_shell_escape_one("\\", "", "\\\\"); + test_shell_escape_one("foobar", "", "foobar"); + test_shell_escape_one("foobar", "o", "f\\o\\obar"); + test_shell_escape_one("foo:bar,baz", ",:", "foo\\:bar\\,baz"); + test_shell_escape_one("foo\nbar\nbaz", ",:", "foo\\nbar\\nbaz"); +} + +static void test_shell_maybe_quote_one(const char *s, ShellEscapeFlags flags, const char *expected) { + _cleanup_free_ char *ret = NULL; + + assert_se(ret = shell_maybe_quote(s, flags)); + log_debug("[%s] → [%s] (%s)", s, ret, expected); + assert_se(streq(ret, expected)); +} + +TEST(shell_maybe_quote) { + test_shell_maybe_quote_one("", 0, ""); + test_shell_maybe_quote_one("", SHELL_ESCAPE_EMPTY, "\"\""); + test_shell_maybe_quote_one("", SHELL_ESCAPE_POSIX, ""); + test_shell_maybe_quote_one("", SHELL_ESCAPE_POSIX | SHELL_ESCAPE_EMPTY, "\"\""); + test_shell_maybe_quote_one("\\", 0, "\"\\\\\""); + test_shell_maybe_quote_one("\\", SHELL_ESCAPE_POSIX, "$'\\\\'"); + test_shell_maybe_quote_one("\"", 0, "\"\\\"\""); + test_shell_maybe_quote_one("\"", SHELL_ESCAPE_POSIX, "$'\"'"); + test_shell_maybe_quote_one("foobar", 0, "foobar"); + test_shell_maybe_quote_one("foobar", SHELL_ESCAPE_POSIX, "foobar"); + test_shell_maybe_quote_one("foo bar", 0, "\"foo bar\""); + test_shell_maybe_quote_one("foo bar", SHELL_ESCAPE_POSIX, "$'foo bar'"); + test_shell_maybe_quote_one("foo\tbar", 0, "\"foo\\tbar\""); + test_shell_maybe_quote_one("foo\tbar", SHELL_ESCAPE_POSIX, "$'foo\\tbar'"); + test_shell_maybe_quote_one("foo\nbar", 0, "\"foo\\nbar\""); + test_shell_maybe_quote_one("foo\nbar", SHELL_ESCAPE_POSIX, "$'foo\\nbar'"); + test_shell_maybe_quote_one("foo \"bar\" waldo", 0, "\"foo \\\"bar\\\" waldo\""); + test_shell_maybe_quote_one("foo \"bar\" waldo", SHELL_ESCAPE_POSIX, "$'foo \"bar\" waldo'"); + test_shell_maybe_quote_one("foo$bar", 0, "\"foo\\$bar\""); + test_shell_maybe_quote_one("foo$bar", SHELL_ESCAPE_EMPTY, "\"foo\\$bar\""); + test_shell_maybe_quote_one("foo$bar", SHELL_ESCAPE_POSIX, "$'foo$bar'"); + test_shell_maybe_quote_one("foo$bar", SHELL_ESCAPE_POSIX | SHELL_ESCAPE_EMPTY, "$'foo$bar'"); + + /* Exclamation mark is special in the interactive shell, but we don't treat it so. */ + test_shell_maybe_quote_one("foo!bar", 0, "\"foo!bar\""); + test_shell_maybe_quote_one("foo!bar", SHELL_ESCAPE_POSIX, "$'foo!bar'"); + + /* Control characters and unicode */ + test_shell_maybe_quote_one("a\nb\001", 0, "\"a\\nb\\001\""); + test_shell_maybe_quote_one("a\nb\001", SHELL_ESCAPE_POSIX, "$'a\\nb\\001'"); + + test_shell_maybe_quote_one("głąb", 0, "głąb"); + test_shell_maybe_quote_one("głąb", SHELL_ESCAPE_POSIX, "głąb"); + + test_shell_maybe_quote_one("głąb\002\003", 0, "\"głąb\\002\\003\""); + test_shell_maybe_quote_one("głąb\002\003", SHELL_ESCAPE_POSIX, "$'głąb\\002\\003'"); + + test_shell_maybe_quote_one("głąb\002\003rząd", 0, "\"głąb\\002\\003rząd\""); + test_shell_maybe_quote_one("głąb\002\003rząd", SHELL_ESCAPE_POSIX, "$'głąb\\002\\003rząd'"); + + /* Bogus UTF-8 strings */ + test_shell_maybe_quote_one("\250\350", 0, "\"\\250\\350\""); + test_shell_maybe_quote_one("\250\350", SHELL_ESCAPE_POSIX, "$'\\250\\350'"); +} + +static void test_quote_command_line_one(char **argv, const char *expected) { + _cleanup_free_ char *s = NULL; + + assert_se(s = quote_command_line(argv, SHELL_ESCAPE_EMPTY)); + log_info("%s", s); + assert_se(streq(s, expected)); +} + +TEST(quote_command_line) { + test_quote_command_line_one(STRV_MAKE("true", "true"), + "true true"); + test_quote_command_line_one(STRV_MAKE("true", "with a space"), + "true \"with a space\""); + test_quote_command_line_one(STRV_MAKE("true", "with a 'quote'"), + "true \"with a 'quote'\""); + test_quote_command_line_one(STRV_MAKE("true", "with a \"quote\""), + "true \"with a \\\"quote\\\"\""); + test_quote_command_line_one(STRV_MAKE("true", "$dollar"), + "true \"\\$dollar\""); +} + +static void test_octescape_one(const char *s, const char *expected) { + _cleanup_free_ char *ret = NULL; + + assert_se(ret = octescape(s, strlen_ptr(s))); + log_debug("octescape(\"%s\") → \"%s\" (expected: \"%s\")", strnull(s), ret, expected); + assert_se(streq(ret, expected)); +} + +TEST(octescape) { + test_octescape_one(NULL, ""); + test_octescape_one("", ""); + test_octescape_one("foo", "foo"); + test_octescape_one("\"\\\"", "\\042\\134\\042"); + test_octescape_one("\123\213\222", "\123\\213\\222"); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-ether-addr-util.c b/src/test/test-ether-addr-util.c new file mode 100644 index 0000000..d680f80 --- /dev/null +++ b/src/test/test-ether-addr-util.c @@ -0,0 +1,162 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "ether-addr-util.h" +#include "string-util.h" +#include "tests.h" + +TEST(ether_addr_helpers) { + struct ether_addr a; + + a = ETHER_ADDR_NULL; + assert_se(ether_addr_is_null(&a)); + assert_se(!ether_addr_is_broadcast(&a)); + assert_se(!ether_addr_is_multicast(&a)); + assert_se(ether_addr_is_unicast(&a)); + assert_se(!ether_addr_is_local(&a)); + assert_se(ether_addr_is_global(&a)); + + memset(a.ether_addr_octet, 0xff, sizeof(a)); + assert_se(!ether_addr_is_null(&a)); + assert_se(ether_addr_is_broadcast(&a)); + assert_se(ether_addr_is_multicast(&a)); + assert_se(!ether_addr_is_unicast(&a)); + assert_se(ether_addr_is_local(&a)); + assert_se(!ether_addr_is_global(&a)); + + a = (struct ether_addr) { { 0x01, 0x23, 0x34, 0x56, 0x78, 0x9a } }; + assert_se(!ether_addr_is_null(&a)); + assert_se(!ether_addr_is_broadcast(&a)); + assert_se(ether_addr_is_multicast(&a)); + assert_se(!ether_addr_is_unicast(&a)); + assert_se(!ether_addr_is_local(&a)); + assert_se(ether_addr_is_global(&a)); +} + +#define INFINIBAD_ADDR_1 ((const struct hw_addr_data){ .length = 20, .infiniband = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20} }) + +TEST(HW_ADDR_TO_STRING) { + const char *s = HW_ADDR_TO_STR(&(const struct hw_addr_data){6}); + log_info("null: %s", s); + + log_info("null×2: %s, %s", + HW_ADDR_TO_STR(&(const struct hw_addr_data){6}), + HW_ADDR_TO_STR(&(const struct hw_addr_data){6})); + log_info("null×3: %s, %s, %s", + HW_ADDR_TO_STR(&(const struct hw_addr_data){6}), + s, + HW_ADDR_TO_STR(&(const struct hw_addr_data){6})); + + log_info("infiniband: %s", HW_ADDR_TO_STR(&INFINIBAD_ADDR_1)); + + /* Let's nest function calls in a stupid way. */ + _cleanup_free_ char *t = NULL; + log_info("infiniband×3: %s\n%14s%s\n%14s%s", + HW_ADDR_TO_STR(&(const struct hw_addr_data){20}), "", + t = strdup(HW_ADDR_TO_STR(&INFINIBAD_ADDR_1)), "", + HW_ADDR_TO_STR(&(const struct hw_addr_data){20})); + + const char *p; + /* Let's use a separate selection statement */ + if ((p = HW_ADDR_TO_STR(&(const struct hw_addr_data){6}))) + log_info("joint: %s, %s", s, p); +} + +static void test_parse_hw_addr_full_one(const char *in, size_t expected_len, const char *expected) { + struct hw_addr_data h; + int r; + + r = parse_hw_addr_full(in, expected_len, &h); + log_debug_errno(r, "parse_hw_addr(\"%s\", len=%zu) → \"%s\" (expected: \"%s\") : %d/%m", + in, expected_len, r >= 0 ? HW_ADDR_TO_STR(&h) : "n/a", strna(expected), r); + assert_se((r >= 0) == !!expected); + if (r >= 0) { + if (!IN_SET(expected_len, 0, SIZE_MAX)) + assert_se(h.length == expected_len); + assert_se(streq(HW_ADDR_TO_STR(&h), expected)); + } +} + +TEST(parse_hw_addr) { + /* IPv4 */ + test_parse_hw_addr_full_one("10.0.0.1", 0, "0a:00:00:01"); + test_parse_hw_addr_full_one("10.0.0.1", 4, "0a:00:00:01"); + test_parse_hw_addr_full_one("192.168.0.1", 0, "c0:a8:00:01"); + test_parse_hw_addr_full_one("192.168.0.1", 4, "c0:a8:00:01"); + /* IPv6 */ + test_parse_hw_addr_full_one("::", 0, "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00"); + test_parse_hw_addr_full_one("::", 16, "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00"); + test_parse_hw_addr_full_one("::1", 0, "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:01"); + test_parse_hw_addr_full_one("::1", 16, "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:01"); + test_parse_hw_addr_full_one("1234::", 0, "12:34:00:00:00:00:00:00:00:00:00:00:00:00:00:00"); + test_parse_hw_addr_full_one("1234::", 16, "12:34:00:00:00:00:00:00:00:00:00:00:00:00:00:00"); + test_parse_hw_addr_full_one("12:34::56", 0, "00:12:00:34:00:00:00:00:00:00:00:00:00:00:00:56"); + test_parse_hw_addr_full_one("12:34::56", 16, "00:12:00:34:00:00:00:00:00:00:00:00:00:00:00:56"); + test_parse_hw_addr_full_one("12aa:34::56", 0, "12:aa:00:34:00:00:00:00:00:00:00:00:00:00:00:56"); + test_parse_hw_addr_full_one("12aa:34::56", 16, "12:aa:00:34:00:00:00:00:00:00:00:00:00:00:00:56"); + test_parse_hw_addr_full_one("1234:5678:90ab:cdef:1234:5678:90ab:cdef", 0, "12:34:56:78:90:ab:cd:ef:12:34:56:78:90:ab:cd:ef"); + test_parse_hw_addr_full_one("1234:5678:90ab:cdef:1234:5678:90ab:cdef", 16, "12:34:56:78:90:ab:cd:ef:12:34:56:78:90:ab:cd:ef"); + /* Dot */ + test_parse_hw_addr_full_one("12.34", 0, "00:12:00:34"); + test_parse_hw_addr_full_one("12.34", 4, "00:12:00:34"); + test_parse_hw_addr_full_one("12.34", SIZE_MAX, "00:12:00:34"); + test_parse_hw_addr_full_one("12.34.56", 0, "00:12:00:34:00:56"); + test_parse_hw_addr_full_one("12.34.56", 6, "00:12:00:34:00:56"); + test_parse_hw_addr_full_one("12.34.56", SIZE_MAX, "00:12:00:34:00:56"); + test_parse_hw_addr_full_one("12.34.56.78", 0, "0c:22:38:4e"); /* IPv4 address */ + test_parse_hw_addr_full_one("12.34.56.78", 4, "0c:22:38:4e"); /* IPv4 address */ + test_parse_hw_addr_full_one("12.34.56.78", 8, "00:12:00:34:00:56:00:78"); + test_parse_hw_addr_full_one("12.34.56.78", SIZE_MAX, "00:12:00:34:00:56:00:78"); + test_parse_hw_addr_full_one("12.34.56.78.90", 0, NULL); + test_parse_hw_addr_full_one("12.34.56.78.90", 10, "00:12:00:34:00:56:00:78:00:90"); + test_parse_hw_addr_full_one("12.34.56.78.90", SIZE_MAX, "00:12:00:34:00:56:00:78:00:90"); + test_parse_hw_addr_full_one("aabb.ccdd", 0, "aa:bb:cc:dd"); + test_parse_hw_addr_full_one("aabb.ccdd", 4, "aa:bb:cc:dd"); + test_parse_hw_addr_full_one("aabb.ccdd", SIZE_MAX, "aa:bb:cc:dd"); + test_parse_hw_addr_full_one("aabb.ccdd.eeff", 0, "aa:bb:cc:dd:ee:ff"); + test_parse_hw_addr_full_one("aabb.ccdd.eeff", 6, "aa:bb:cc:dd:ee:ff"); + test_parse_hw_addr_full_one("aabb.ccdd.eeff", SIZE_MAX, "aa:bb:cc:dd:ee:ff"); + /* Colon */ + test_parse_hw_addr_full_one("12:34", 0, NULL); + test_parse_hw_addr_full_one("12:34", 2, "12:34"); + test_parse_hw_addr_full_one("12:34", SIZE_MAX, "12:34"); + test_parse_hw_addr_full_one("12:34:56:78:90:ab", 0, "12:34:56:78:90:ab"); + test_parse_hw_addr_full_one("12:34:56:78:90:ab", 6, "12:34:56:78:90:ab"); + test_parse_hw_addr_full_one("12:34:56:78:90:ab", SIZE_MAX, "12:34:56:78:90:ab"); + test_parse_hw_addr_full_one("12:34:56:78:90:ab:cd:ef", 0, "00:12:00:34:00:56:00:78:00:90:00:ab:00:cd:00:ef"); /* IPv6 */ + test_parse_hw_addr_full_one("12:34:56:78:90:ab:cd:ef", 8, "12:34:56:78:90:ab:cd:ef"); + test_parse_hw_addr_full_one("12:34:56:78:90:ab:cd:ef", 16, "00:12:00:34:00:56:00:78:00:90:00:ab:00:cd:00:ef"); /* IPv6 */ + test_parse_hw_addr_full_one("12:34:56:78:90:ab:cd:ef", SIZE_MAX, "12:34:56:78:90:ab:cd:ef"); + test_parse_hw_addr_full_one("12:34:56:78:90:AB:CD:EF", 0, "00:12:00:34:00:56:00:78:00:90:00:ab:00:cd:00:ef"); /* IPv6 */ + test_parse_hw_addr_full_one("12:34:56:78:90:AB:CD:EF", 8, "12:34:56:78:90:ab:cd:ef"); + test_parse_hw_addr_full_one("12:34:56:78:90:AB:CD:EF", 16, "00:12:00:34:00:56:00:78:00:90:00:ab:00:cd:00:ef"); /* IPv6 */ + test_parse_hw_addr_full_one("12:34:56:78:90:AB:CD:EF", SIZE_MAX, "12:34:56:78:90:ab:cd:ef"); + /* Hyphen */ + test_parse_hw_addr_full_one("12-34", 0, NULL); + test_parse_hw_addr_full_one("12-34", 2, "12:34"); + test_parse_hw_addr_full_one("12-34", SIZE_MAX, "12:34"); + test_parse_hw_addr_full_one("12-34-56-78-90-ab-cd-ef", 0, NULL); + test_parse_hw_addr_full_one("12-34-56-78-90-ab-cd-ef", 8, "12:34:56:78:90:ab:cd:ef"); + test_parse_hw_addr_full_one("12-34-56-78-90-ab-cd-ef", SIZE_MAX, "12:34:56:78:90:ab:cd:ef"); + test_parse_hw_addr_full_one("12-34-56-78-90-AB-CD-EF", 0, NULL); + test_parse_hw_addr_full_one("12-34-56-78-90-AB-CD-EF", 8, "12:34:56:78:90:ab:cd:ef"); + test_parse_hw_addr_full_one("12-34-56-78-90-AB-CD-EF", SIZE_MAX, "12:34:56:78:90:ab:cd:ef"); + + /* Invalid */ + test_parse_hw_addr_full_one("", SIZE_MAX, NULL); + test_parse_hw_addr_full_one("12", SIZE_MAX, NULL); + test_parse_hw_addr_full_one("12.", SIZE_MAX, NULL); + test_parse_hw_addr_full_one("12.34.", SIZE_MAX, NULL); + test_parse_hw_addr_full_one(".12", SIZE_MAX, NULL); + test_parse_hw_addr_full_one(".12.34", SIZE_MAX, NULL); + test_parse_hw_addr_full_one("12.34:56", SIZE_MAX, NULL); + test_parse_hw_addr_full_one("1234:56", SIZE_MAX, NULL); + test_parse_hw_addr_full_one("1234:56", SIZE_MAX, NULL); + test_parse_hw_addr_full_one("12:34:", SIZE_MAX, NULL); + test_parse_hw_addr_full_one(":12:34", SIZE_MAX, NULL); + test_parse_hw_addr_full_one("::1", SIZE_MAX, NULL); + test_parse_hw_addr_full_one("aa:bb-cc", SIZE_MAX, NULL); + test_parse_hw_addr_full_one("aa:xx", SIZE_MAX, NULL); + test_parse_hw_addr_full_one("aa bb", SIZE_MAX, NULL); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-exec-util.c b/src/test/test-exec-util.c new file mode 100644 index 0000000..2304f6a --- /dev/null +++ b/src/test/test-exec-util.c @@ -0,0 +1,456 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "copy.h" +#include "constants.h" +#include "env-util.h" +#include "exec-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "log.h" +#include "macro.h" +#include "path-util.h" +#include "rm-rf.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" +#include "tmpfile-util.h" + +static int here = 0, here2 = 0, here3 = 0; +static void *ignore_stdout_args[] = { &here, &here2, &here3 }; + +/* noop handlers, just check that arguments are passed correctly */ +static int ignore_stdout_func(int fd, void *arg) { + assert_se(fd >= 0); + assert_se(arg == &here); + safe_close(fd); + + return 0; +} +static int ignore_stdout_func2(int fd, void *arg) { + assert_se(fd >= 0); + assert_se(arg == &here2); + safe_close(fd); + + return 0; +} +static int ignore_stdout_func3(int fd, void *arg) { + assert_se(fd >= 0); + assert_se(arg == &here3); + safe_close(fd); + + return 0; +} + +static const gather_stdout_callback_t ignore_stdout[] = { + ignore_stdout_func, + ignore_stdout_func2, + ignore_stdout_func3, +}; + +static void test_execute_directory_one(bool gather_stdout) { + _cleanup_(rm_rf_physical_and_freep) char *tmp_lo = NULL, *tmp_hi = NULL; + const char *name, *name2, *name3, + *overridden, *override, + *masked, *mask, + *masked2, *mask2, /* the mask is non-executable */ + *masked2e, *mask2e; /* the mask is executable */ + + log_info("/* %s (%s) */", __func__, gather_stdout ? "gathering stdout" : "asynchronous"); + + assert_se(mkdtemp_malloc("/tmp/test-exec-util.lo.XXXXXXX", &tmp_lo) >= 0); + assert_se(mkdtemp_malloc("/tmp/test-exec-util.hi.XXXXXXX", &tmp_hi) >= 0); + + const char * dirs[] = { tmp_hi, tmp_lo, NULL }; + + name = strjoina(tmp_lo, "/script"); + name2 = strjoina(tmp_hi, "/script2"); + name3 = strjoina(tmp_lo, "/useless"); + overridden = strjoina(tmp_lo, "/overridden"); + override = strjoina(tmp_hi, "/overridden"); + masked = strjoina(tmp_lo, "/masked"); + mask = strjoina(tmp_hi, "/masked"); + masked2 = strjoina(tmp_lo, "/masked2"); + mask2 = strjoina(tmp_hi, "/masked2"); + masked2e = strjoina(tmp_lo, "/masked2e"); + mask2e = strjoina(tmp_hi, "/masked2e"); + + assert_se(write_string_file(name, + "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/it_works", + WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file(name2, + "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/it_works2", + WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file(overridden, + "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed", + WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file(override, + "#!/bin/sh\necho 'Executing '$0", + WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file(masked, + "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed", + WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file(masked2, + "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed", + WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file(masked2e, + "#!/bin/sh\necho 'Executing '$0\ntouch $(dirname $0)/failed", + WRITE_STRING_FILE_CREATE) == 0); + assert_se(symlink("/dev/null", mask) == 0); + assert_se(touch(mask2) == 0); + assert_se(touch(mask2e) == 0); + assert_se(touch(name3) >= 0); + + assert_se(chmod(name, 0755) == 0); + assert_se(chmod(name2, 0755) == 0); + assert_se(chmod(overridden, 0755) == 0); + assert_se(chmod(override, 0755) == 0); + assert_se(chmod(masked, 0755) == 0); + assert_se(chmod(masked2, 0755) == 0); + assert_se(chmod(masked2e, 0755) == 0); + assert_se(chmod(mask2e, 0755) == 0); + + if (access(name, X_OK) < 0 && ERRNO_IS_PRIVILEGE(errno)) + return; + + if (gather_stdout) + execute_directories(dirs, DEFAULT_TIMEOUT_USEC, ignore_stdout, ignore_stdout_args, NULL, NULL, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS); + else + execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, NULL, NULL, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS); + + assert_se(chdir(tmp_lo) == 0); + assert_se(access("it_works", F_OK) >= 0); + assert_se(access("failed", F_OK) < 0); + + assert_se(chdir(tmp_hi) == 0); + assert_se(access("it_works2", F_OK) >= 0); + assert_se(access("failed", F_OK) < 0); +} + +TEST(execute_directory) { + test_execute_directory_one(true); + test_execute_directory_one(false); +} + +TEST(execution_order) { + _cleanup_(rm_rf_physical_and_freep) char *tmp_lo = NULL, *tmp_hi = NULL; + const char *name, *name2, *name3, *overridden, *override, *masked, *mask; + const char *output, *t; + _cleanup_free_ char *contents = NULL; + + assert_se(mkdtemp_malloc("/tmp/test-exec-util-lo.XXXXXXX", &tmp_lo) >= 0); + assert_se(mkdtemp_malloc("/tmp/test-exec-util-hi.XXXXXXX", &tmp_hi) >= 0); + + const char *dirs[] = { tmp_hi, tmp_lo, NULL }; + + output = strjoina(tmp_hi, "/output"); + + log_info("/* %s >>%s */", __func__, output); + + /* write files in "random" order */ + name2 = strjoina(tmp_lo, "/90-bar"); + name = strjoina(tmp_hi, "/80-foo"); + name3 = strjoina(tmp_lo, "/last"); + overridden = strjoina(tmp_lo, "/30-override"); + override = strjoina(tmp_hi, "/30-override"); + masked = strjoina(tmp_lo, "/10-masked"); + mask = strjoina(tmp_hi, "/10-masked"); + + t = strjoina("#!/bin/sh\necho $(basename $0) >>", output); + assert_se(write_string_file(name, t, WRITE_STRING_FILE_CREATE) == 0); + + t = strjoina("#!/bin/sh\necho $(basename $0) >>", output); + assert_se(write_string_file(name2, t, WRITE_STRING_FILE_CREATE) == 0); + + t = strjoina("#!/bin/sh\necho $(basename $0) >>", output); + assert_se(write_string_file(name3, t, WRITE_STRING_FILE_CREATE) == 0); + + t = strjoina("#!/bin/sh\necho OVERRIDDEN >>", output); + assert_se(write_string_file(overridden, t, WRITE_STRING_FILE_CREATE) == 0); + + t = strjoina("#!/bin/sh\necho $(basename $0) >>", output); + assert_se(write_string_file(override, t, WRITE_STRING_FILE_CREATE) == 0); + + t = strjoina("#!/bin/sh\necho MASKED >>", output); + assert_se(write_string_file(masked, t, WRITE_STRING_FILE_CREATE) == 0); + + assert_se(symlink("/dev/null", mask) == 0); + + assert_se(chmod(name, 0755) == 0); + assert_se(chmod(name2, 0755) == 0); + assert_se(chmod(name3, 0755) == 0); + assert_se(chmod(overridden, 0755) == 0); + assert_se(chmod(override, 0755) == 0); + assert_se(chmod(masked, 0755) == 0); + + if (access(name, X_OK) < 0 && ERRNO_IS_PRIVILEGE(errno)) + return; + + execute_directories(dirs, DEFAULT_TIMEOUT_USEC, ignore_stdout, ignore_stdout_args, NULL, NULL, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS); + + assert_se(read_full_file(output, &contents, NULL) >= 0); + assert_se(streq(contents, "30-override\n80-foo\n90-bar\nlast\n")); +} + +static int gather_stdout_one(int fd, void *arg) { + char ***s = arg, *t; + char buf[128] = {}; + + assert_se(s); + assert_se(read(fd, buf, sizeof buf) >= 0); + safe_close(fd); + + assert_se(t = strndup(buf, sizeof buf)); + assert_se(strv_push(s, t) >= 0); + + return 0; +} +static int gather_stdout_two(int fd, void *arg) { + char ***s = arg; + + STRV_FOREACH(t, *s) + assert_se(write(fd, *t, strlen(*t)) == (ssize_t) strlen(*t)); + safe_close(fd); + + return 0; +} +static int gather_stdout_three(int fd, void *arg) { + char **s = arg; + char buf[128] = {}; + + assert_se(read(fd, buf, sizeof buf - 1) > 0); + safe_close(fd); + assert_se(*s = strndup(buf, sizeof buf)); + + return 0; +} + +const gather_stdout_callback_t gather_stdouts[] = { + gather_stdout_one, + gather_stdout_two, + gather_stdout_three, +}; + +TEST(stdout_gathering) { + _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; + const char *name, *name2, *name3; + int r; + + char **tmp = NULL; /* this is only used in the forked process, no cleanup here */ + _cleanup_free_ char *output = NULL; + + void* args[] = {&tmp, &tmp, &output}; + + assert_se(mkdtemp_malloc("/tmp/test-exec-util.XXXXXXX", &tmpdir) >= 0); + + const char *dirs[] = { tmpdir, NULL }; + + /* write files */ + name = strjoina(tmpdir, "/10-foo"); + name2 = strjoina(tmpdir, "/20-bar"); + name3 = strjoina(tmpdir, "/30-last"); + + assert_se(write_string_file(name, + "#!/bin/sh\necho a\necho b\necho c\n", + WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file(name2, + "#!/bin/sh\necho d\n", + WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file(name3, + "#!/bin/sh\nsleep 1", + WRITE_STRING_FILE_CREATE) == 0); + + assert_se(chmod(name, 0755) == 0); + assert_se(chmod(name2, 0755) == 0); + assert_se(chmod(name3, 0755) == 0); + + if (access(name, X_OK) < 0 && ERRNO_IS_PRIVILEGE(errno)) + return; + + r = execute_directories(dirs, DEFAULT_TIMEOUT_USEC, gather_stdouts, args, NULL, NULL, + EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS); + assert_se(r >= 0); + + log_info("got: %s", output); + + assert_se(streq(output, "a\nb\nc\nd\n")); +} + +TEST(environment_gathering) { + _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; + const char *name, *name2, *name3, *old; + int r; + + char **tmp = NULL; /* this is only used in the forked process, no cleanup here */ + _cleanup_strv_free_ char **env = NULL; + + void* const args[] = { &tmp, &tmp, &env }; + + assert_se(mkdtemp_malloc("/tmp/test-exec-util.XXXXXXX", &tmpdir) >= 0); + + const char *dirs[] = { tmpdir, NULL }; + + /* write files */ + name = strjoina(tmpdir, "/10-foo"); + name2 = strjoina(tmpdir, "/20-bar"); + name3 = strjoina(tmpdir, "/30-last"); + + assert_se(write_string_file(name, + "#!/bin/sh\n" + "echo A=23\n", + WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file(name2, + "#!/bin/sh\n" + "echo A=22:$A\n\n\n", /* substitution from previous generator */ + WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file(name3, + "#!/bin/sh\n" + "echo A=$A:24\n" + "echo B=12\n" + "echo C=000\n" + "echo C=001\n" /* variable overwriting */ + /* various invalid entries */ + "echo unset A\n" + "echo unset A=\n" + "echo unset A=B\n" + "echo unset \n" + "echo A B=C\n" + "echo A\n" + /* test variable assignment without newline */ + "echo PATH=$PATH:/no/such/file", /* no newline */ + WRITE_STRING_FILE_CREATE) == 0); + + assert_se(chmod(name, 0755) == 0); + assert_se(chmod(name2, 0755) == 0); + assert_se(chmod(name3, 0755) == 0); + + /* When booting in containers or without initrd there might not be any PATH in the environment and if + * there is no PATH /bin/sh built-in PATH may leak and override systemd's DEFAULT_PATH which is not + * good. Force our own PATH in environment, to prevent expansion of sh built-in $PATH */ + old = getenv("PATH"); + r = setenv("PATH", "no-sh-built-in-path", 1); + assert_se(r >= 0); + + if (access(name, X_OK) < 0 && ERRNO_IS_PRIVILEGE(errno)) + return; + + r = execute_directories(dirs, DEFAULT_TIMEOUT_USEC, gather_environment, args, NULL, NULL, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS); + assert_se(r >= 0); + + STRV_FOREACH(p, env) + log_info("got env: \"%s\"", *p); + + assert_se(streq(strv_env_get(env, "A"), "22:23:24")); + assert_se(streq(strv_env_get(env, "B"), "12")); + assert_se(streq(strv_env_get(env, "C"), "001")); + assert_se(streq(strv_env_get(env, "PATH"), "no-sh-built-in-path:/no/such/file")); + + /* now retest with "default" path passed in, as created by + * manager_default_environment */ + env = strv_free(env); + env = strv_new("PATH=" DEFAULT_PATH); + assert_se(env); + + r = execute_directories(dirs, DEFAULT_TIMEOUT_USEC, gather_environment, args, NULL, env, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS); + assert_se(r >= 0); + + STRV_FOREACH(p, env) + log_info("got env: \"%s\"", *p); + + assert_se(streq(strv_env_get(env, "A"), "22:23:24")); + assert_se(streq(strv_env_get(env, "B"), "12")); + assert_se(streq(strv_env_get(env, "C"), "001")); + assert_se(streq(strv_env_get(env, "PATH"), DEFAULT_PATH ":/no/such/file")); + + /* reset environ PATH */ + assert_se(set_unset_env("PATH", old, true) == 0); +} + +TEST(error_catching) { + _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; + const char *name, *name2, *name3; + int r; + + assert_se(mkdtemp_malloc("/tmp/test-exec-util.XXXXXXX", &tmpdir) >= 0); + + const char *dirs[] = { tmpdir, NULL }; + + /* write files */ + name = strjoina(tmpdir, "/10-foo"); + name2 = strjoina(tmpdir, "/20-bar"); + name3 = strjoina(tmpdir, "/30-last"); + + assert_se(write_string_file(name, + "#!/bin/sh\necho a\necho b\necho c\n", + WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file(name2, + "#!/bin/sh\nexit 42\n", + WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file(name3, + "#!/bin/sh\nexit 12", + WRITE_STRING_FILE_CREATE) == 0); + + assert_se(chmod(name, 0755) == 0); + assert_se(chmod(name2, 0755) == 0); + assert_se(chmod(name3, 0755) == 0); + + if (access(name, X_OK) < 0 && ERRNO_IS_PRIVILEGE(errno)) + return; + + r = execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, NULL, NULL, EXEC_DIR_NONE); + + /* we should exit with the error code of the first script that failed */ + assert_se(r == 42); +} + +TEST(exec_command_flags_from_strv) { + ExecCommandFlags flags = 0; + char **valid_strv = STRV_MAKE("no-env-expand", "no-setuid", "ignore-failure"); + char **invalid_strv = STRV_MAKE("no-env-expand", "no-setuid", "nonexistent-option", "ignore-failure"); + int r; + + r = exec_command_flags_from_strv(valid_strv, &flags); + + assert_se(r == 0); + assert_se(FLAGS_SET(flags, EXEC_COMMAND_NO_ENV_EXPAND)); + assert_se(FLAGS_SET(flags, EXEC_COMMAND_NO_SETUID)); + assert_se(FLAGS_SET(flags, EXEC_COMMAND_IGNORE_FAILURE)); + assert_se(!FLAGS_SET(flags, EXEC_COMMAND_AMBIENT_MAGIC)); + assert_se(!FLAGS_SET(flags, EXEC_COMMAND_FULLY_PRIVILEGED)); + + r = exec_command_flags_from_strv(invalid_strv, &flags); + + assert_se(r == -EINVAL); +} + +TEST(exec_command_flags_to_strv) { + _cleanup_strv_free_ char **opts = NULL, **empty_opts = NULL, **invalid_opts = NULL; + ExecCommandFlags flags = 0; + int r; + + flags |= (EXEC_COMMAND_AMBIENT_MAGIC|EXEC_COMMAND_NO_ENV_EXPAND|EXEC_COMMAND_IGNORE_FAILURE); + + r = exec_command_flags_to_strv(flags, &opts); + + assert_se(r == 0); + assert_se(strv_equal(opts, STRV_MAKE("ignore-failure", "ambient", "no-env-expand"))); + + r = exec_command_flags_to_strv(0, &empty_opts); + + assert_se(r == 0); + assert_se(strv_equal(empty_opts, STRV_MAKE_EMPTY)); + + flags = _EXEC_COMMAND_FLAGS_INVALID; + + r = exec_command_flags_to_strv(flags, &invalid_opts); + + assert_se(r == -EINVAL); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-execute.c b/src/test/test-execute.c new file mode 100644 index 0000000..4f6ad5d --- /dev/null +++ b/src/test/test-execute.c @@ -0,0 +1,1550 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdio.h> +#include <sys/mount.h> +#include <sys/prctl.h> +#include <sys/types.h> + +#include "sd-event.h" + +#include "capability-util.h" +#include "cpu-set-util.h" +#include "copy.h" +#include "dropin.h" +#include "errno-list.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "macro.h" +#include "manager.h" +#include "missing_prctl.h" +#include "mkdir.h" +#include "mount-util.h" +#include "path-util.h" +#include "process-util.h" +#include "rm-rf.h" +#include "seccomp-util.h" +#include "service.h" +#include "signal-util.h" +#include "static-destruct.h" +#include "stat-util.h" +#include "tests.h" +#include "tmpfile-util.h" +#include "unit.h" +#include "user-util.h" +#include "virt.h" + +#define PRIVATE_UNIT_DIR "/run/test-execute-unit-dir" + +static char *user_runtime_unit_dir = NULL; +static bool can_unshare; +static bool have_net_dummy; +static bool have_netns; +static unsigned n_ran_tests = 0; + +STATIC_DESTRUCTOR_REGISTER(user_runtime_unit_dir, freep); + +typedef void (*test_function_t)(Manager *m); + +static int cld_dumped_to_killed(int code) { + /* Depending on the system, seccomp version, … some signals might result in dumping, others in plain + * killing. Let's ignore the difference here, and map both cases to CLD_KILLED */ + return code == CLD_DUMPED ? CLD_KILLED : code; +} + +static void wait_for_service_finish(Manager *m, Unit *unit) { + Service *service = NULL; + usec_t ts; + usec_t timeout = 2 * USEC_PER_MINUTE; + + assert_se(m); + assert_se(unit); + + service = SERVICE(unit); + printf("%s\n", unit->id); + exec_context_dump(&service->exec_context, stdout, "\t"); + ts = now(CLOCK_MONOTONIC); + while (!IN_SET(service->state, SERVICE_DEAD, SERVICE_FAILED)) { + int r; + usec_t n; + + r = sd_event_run(m->event, 100 * USEC_PER_MSEC); + assert_se(r >= 0); + + n = now(CLOCK_MONOTONIC); + if (ts + timeout < n) { + log_error("Test timeout when testing %s", unit->id); + r = unit_kill(unit, KILL_ALL, SIGKILL, SI_USER, 0, NULL); + if (r < 0) + log_error_errno(r, "Failed to kill %s: %m", unit->id); + exit(EXIT_FAILURE); + } + } +} + +static void check_main_result(const char *file, unsigned line, const char *func, + Manager *m, Unit *unit, int status_expected, int code_expected) { + Service *service = NULL; + + assert_se(m); + assert_se(unit); + + wait_for_service_finish(m, unit); + + service = SERVICE(unit); + exec_status_dump(&service->main_exec_status, stdout, "\t"); + + if (cld_dumped_to_killed(service->main_exec_status.code) != cld_dumped_to_killed(code_expected)) { + log_error("%s:%u:%s %s: can_unshare=%s: exit code %d, expected %d", + file, line, func, unit->id, yes_no(can_unshare), + service->main_exec_status.code, code_expected); + abort(); + } + + if (service->main_exec_status.status != status_expected) { + log_error("%s:%u:%s: %s: can_unshare=%s: exit status %d, expected %d", + file, line, func, unit->id, yes_no(can_unshare), + service->main_exec_status.status, status_expected); + abort(); + } +} + +static void check_service_result(const char *file, unsigned line, const char *func, + Manager *m, Unit *unit, ServiceResult result_expected) { + Service *service = NULL; + + assert_se(m); + assert_se(unit); + + wait_for_service_finish(m, unit); + + service = SERVICE(unit); + + if (service->result != result_expected) { + log_error("%s:%u:%s: %s: can_unshare=%s: service end result %s, expected %s", + file, line, func, unit->id, yes_no(can_unshare), + service_result_to_string(service->result), + service_result_to_string(result_expected)); + abort(); + } +} + +static bool check_nobody_user_and_group(void) { + static int cache = -1; + struct passwd *p; + struct group *g; + + if (cache >= 0) + return !!cache; + + if (!synthesize_nobody()) + goto invalid; + + p = getpwnam(NOBODY_USER_NAME); + if (!p || + !streq(p->pw_name, NOBODY_USER_NAME) || + p->pw_uid != UID_NOBODY || + p->pw_gid != GID_NOBODY) + goto invalid; + + p = getpwuid(UID_NOBODY); + if (!p || + !streq(p->pw_name, NOBODY_USER_NAME) || + p->pw_uid != UID_NOBODY || + p->pw_gid != GID_NOBODY) + goto invalid; + + g = getgrnam(NOBODY_GROUP_NAME); + if (!g || + !streq(g->gr_name, NOBODY_GROUP_NAME) || + g->gr_gid != GID_NOBODY) + goto invalid; + + g = getgrgid(GID_NOBODY); + if (!g || + !streq(g->gr_name, NOBODY_GROUP_NAME) || + g->gr_gid != GID_NOBODY) + goto invalid; + + cache = 1; + return true; + +invalid: + cache = 0; + return false; +} + +static bool check_user_has_group_with_same_name(const char *name) { + struct passwd *p; + struct group *g; + + assert_se(name); + + p = getpwnam(name); + if (!p || + !streq(p->pw_name, name)) + return false; + + g = getgrgid(p->pw_gid); + if (!g || + !streq(g->gr_name, name)) + return false; + + return true; +} + +static bool is_inaccessible_available(void) { + FOREACH_STRING(p, + "/run/systemd/inaccessible/reg", + "/run/systemd/inaccessible/dir", + "/run/systemd/inaccessible/chr", + "/run/systemd/inaccessible/blk", + "/run/systemd/inaccessible/fifo", + "/run/systemd/inaccessible/sock") + if (access(p, F_OK) < 0) + return false; + + return true; +} + +static void start_parent_slices(Unit *unit) { + Unit *slice; + + slice = UNIT_GET_SLICE(unit); + if (slice) { + start_parent_slices(slice); + int r = unit_start(slice, NULL); + assert_se(r >= 0 || r == -EALREADY); + } +} + +static bool have_userns_privileges(void) { + pid_t pid; + int r; + + r = safe_fork("(sd-test-check-userns)", + FORK_RESET_SIGNALS | + FORK_CLOSE_ALL_FDS | + FORK_DEATHSIG_SIGKILL, + &pid); + assert(r >= 0); + if (r == 0) { + /* Keep CAP_SYS_ADMIN if we have it to ensure we give an + * accurate result to the caller. Some kernels have a + * kernel.unprivileged_userns_clone sysctl which can be + * configured to make CLONE_NEWUSER require CAP_SYS_ADMIN. + * Additionally, AppArmor may restrict unprivileged user + * namespace creation. */ + r = capability_bounding_set_drop(UINT64_C(1) << CAP_SYS_ADMIN, /* right_now = */ true); + if (r < 0) { + log_debug_errno(r, "Failed to drop capabilities: %m"); + _exit(2); + } + + r = RET_NERRNO(unshare(CLONE_NEWUSER)); + if (r < 0 && !ERRNO_IS_NEG_PRIVILEGE(r)) + log_debug_errno(r, "Failed to create user namespace: %m"); + + _exit(r >= 0 ? EXIT_SUCCESS : ERRNO_IS_NEG_PRIVILEGE(r) ? EXIT_FAILURE : 2); + } + + /* The exit code records the result of the check: + * EXIT_SUCCESS => we can use user namespaces + * EXIT_FAILURE => we can NOT use user namespaces + * 2 => some other error occurred */ + r = wait_for_terminate_and_check("(sd-test-check-userns)", pid, 0); + if (!IN_SET(r, EXIT_SUCCESS, EXIT_FAILURE)) + log_debug("Failed to check if user namespaces can be used, assuming not."); + + return r == EXIT_SUCCESS; +} + +static void _test(const char *file, unsigned line, const char *func, + Manager *m, const char *unit_name, int status_expected, int code_expected) { + Unit *unit; + + assert_se(unit_name); + + assert_se(manager_load_startable_unit_or_warn(m, unit_name, NULL, &unit) >= 0); + /* We need to start the slices as well otherwise the slice cgroups might be pruned + * in on_cgroup_empty_event. */ + start_parent_slices(unit); + assert_se(unit_start(unit, NULL) >= 0); + check_main_result(file, line, func, m, unit, status_expected, code_expected); + + ++n_ran_tests; +} +#define test(m, unit_name, status_expected, code_expected) \ + _test(PROJECT_FILE, __LINE__, __func__, m, unit_name, status_expected, code_expected) + +static void _test_service(const char *file, unsigned line, const char *func, + Manager *m, const char *unit_name, ServiceResult result_expected) { + Unit *unit; + + assert_se(unit_name); + + assert_se(manager_load_startable_unit_or_warn(m, unit_name, NULL, &unit) >= 0); + assert_se(unit_start(unit, NULL) >= 0); + check_service_result(file, line, func, m, unit, result_expected); +} +#define test_service(m, unit_name, result_expected) \ + _test_service(PROJECT_FILE, __LINE__, __func__, m, unit_name, result_expected) + +static void test_exec_bindpaths(Manager *m) { + assert_se(mkdir_p("/tmp/test-exec-bindpaths", 0755) >= 0); + assert_se(mkdir_p("/tmp/test-exec-bindreadonlypaths", 0755) >= 0); + + test(m, "exec-bindpaths.service", can_unshare ? 0 : EXIT_NAMESPACE, CLD_EXITED); + + (void) rm_rf("/tmp/test-exec-bindpaths", REMOVE_ROOT|REMOVE_PHYSICAL); + (void) rm_rf("/tmp/test-exec-bindreadonlypaths", REMOVE_ROOT|REMOVE_PHYSICAL); +} + +static void test_exec_cpuaffinity(Manager *m) { + _cleanup_(cpu_set_reset) CPUSet c = {}; + + assert_se(cpu_set_realloc(&c, 8192) >= 0); /* just allocate the maximum possible size */ + assert_se(sched_getaffinity(0, c.allocated, c.set) >= 0); + + if (!CPU_ISSET_S(0, c.allocated, c.set)) { + log_notice("Cannot use CPU 0, skipping %s", __func__); + return; + } + + test(m, "exec-cpuaffinity1.service", 0, CLD_EXITED); + test(m, "exec-cpuaffinity2.service", 0, CLD_EXITED); + + if (!CPU_ISSET_S(1, c.allocated, c.set) || + !CPU_ISSET_S(2, c.allocated, c.set)) { + log_notice("Cannot use CPU 1 or 2, skipping remaining tests in %s", __func__); + return; + } + + test(m, "exec-cpuaffinity3.service", 0, CLD_EXITED); +} + +static void test_exec_credentials(Manager *m) { + test(m, "exec-set-credential.service", 0, CLD_EXITED); + test(m, "exec-load-credential.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_CREDENTIALS, CLD_EXITED); + test(m, "exec-credentials-dir-specifier.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_CREDENTIALS, CLD_EXITED); +} + +static void test_exec_workingdirectory(Manager *m) { + assert_se(mkdir_p("/tmp/test-exec_workingdirectory", 0755) >= 0); + + test(m, "exec-workingdirectory.service", 0, CLD_EXITED); + test(m, "exec-workingdirectory-trailing-dot.service", 0, CLD_EXITED); + + (void) rm_rf("/tmp/test-exec_workingdirectory", REMOVE_ROOT|REMOVE_PHYSICAL); +} + +static void test_exec_execsearchpath(Manager *m) { + assert_se(mkdir_p("/tmp/test-exec_execsearchpath", 0755) >= 0); + + assert_se(copy_file("/bin/ls", "/tmp/test-exec_execsearchpath/ls_temp", 0, 0777, COPY_REPLACE) >= 0); + + test(m, "exec-execsearchpath.service", 0, CLD_EXITED); + + assert_se(rm_rf("/tmp/test-exec_execsearchpath", REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + + test(m, "exec-execsearchpath.service", EXIT_EXEC, CLD_EXITED); +} + +static void test_exec_execsearchpath_specifier(Manager *m) { + test(m, "exec-execsearchpath-unit-specifier.service", 0, CLD_EXITED); +} + +static void test_exec_execsearchpath_environment(Manager *m) { + test(m, "exec-execsearchpath-environment.service", 0, CLD_EXITED); + test(m, "exec-execsearchpath-environment-path-set.service", 0, CLD_EXITED); +} + +static void test_exec_execsearchpath_environment_files(Manager *m) { + static const char path_not_set[] = + "VAR1='word1 word2'\n" + "VAR2=word3 \n" + "# comment1\n" + "\n" + "; comment2\n" + " ; # comment3\n" + "line without an equal\n" + "VAR3='$word 5 6'\n" + "VAR4='new\nline'\n" + "VAR5=password\\with\\backslashes"; + + static const char path_set[] = + "VAR1='word1 word2'\n" + "VAR2=word3 \n" + "# comment1\n" + "\n" + "; comment2\n" + " ; # comment3\n" + "line without an equal\n" + "VAR3='$word 5 6'\n" + "VAR4='new\nline'\n" + "VAR5=password\\with\\backslashes\n" + "PATH=/usr"; + + int r; + + r = write_string_file("/tmp/test-exec_execsearchpath_environmentfile.conf", path_not_set, WRITE_STRING_FILE_CREATE); + + assert_se(r == 0); + + test(m, "exec-execsearchpath-environmentfile.service", 0, CLD_EXITED); + + (void) unlink("/tmp/test-exec_environmentfile.conf"); + + + r = write_string_file("/tmp/test-exec_execsearchpath_environmentfile-set.conf", path_set, WRITE_STRING_FILE_CREATE); + + assert_se(r == 0); + + test(m, "exec-execsearchpath-environmentfile-set.service", 0, CLD_EXITED); + + (void) unlink("/tmp/test-exec_environmentfile-set.conf"); +} + +static void test_exec_execsearchpath_passenvironment(Manager *m) { + assert_se(setenv("VAR1", "word1 word2", 1) == 0); + assert_se(setenv("VAR2", "word3", 1) == 0); + assert_se(setenv("VAR3", "$word 5 6", 1) == 0); + assert_se(setenv("VAR4", "new\nline", 1) == 0); + assert_se(setenv("VAR5", "passwordwithbackslashes", 1) == 0); + + test(m, "exec-execsearchpath-passenvironment.service", 0, CLD_EXITED); + + assert_se(setenv("PATH", "/usr", 1) == 0); + test(m, "exec-execsearchpath-passenvironment-set.service", 0, CLD_EXITED); + + assert_se(unsetenv("VAR1") == 0); + assert_se(unsetenv("VAR2") == 0); + assert_se(unsetenv("VAR3") == 0); + assert_se(unsetenv("VAR4") == 0); + assert_se(unsetenv("VAR5") == 0); + assert_se(unsetenv("PATH") == 0); +} + +static void test_exec_personality(Manager *m) { +#if defined(__x86_64__) + test(m, "exec-personality-x86-64.service", 0, CLD_EXITED); + +#elif defined(__s390__) + test(m, "exec-personality-s390.service", 0, CLD_EXITED); + +#elif defined(__powerpc64__) +# if __BYTE_ORDER == __BIG_ENDIAN + test(m, "exec-personality-ppc64.service", 0, CLD_EXITED); +# else + test(m, "exec-personality-ppc64le.service", 0, CLD_EXITED); +# endif + +#elif defined(__aarch64__) + test(m, "exec-personality-aarch64.service", 0, CLD_EXITED); + +#elif defined(__i386__) + test(m, "exec-personality-x86.service", 0, CLD_EXITED); +#elif defined(__loongarch_lp64) + test(m, "exec-personality-loongarch64.service", 0, CLD_EXITED); +#else + log_notice("Unknown personality, skipping %s", __func__); +#endif +} + +static void test_exec_ignoresigpipe(Manager *m) { + test(m, "exec-ignoresigpipe-yes.service", 0, CLD_EXITED); + test(m, "exec-ignoresigpipe-no.service", SIGPIPE, CLD_KILLED); +} + +static void test_exec_privatetmp(Manager *m) { + assert_se(touch("/tmp/test-exec_privatetmp") >= 0); + + if (MANAGER_IS_SYSTEM(m) || have_userns_privileges()) { + test(m, "exec-privatetmp-yes.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED); + test(m, "exec-privatetmp-disabled-by-prefix.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED); + } + + test(m, "exec-privatetmp-no.service", 0, CLD_EXITED); + + (void) unlink("/tmp/test-exec_privatetmp"); +} + +static void test_exec_privatedevices(Manager *m) { + int r; + + if (detect_container() > 0) { + log_notice("Testing in container, skipping %s", __func__); + return; + } + if (!is_inaccessible_available()) { + log_notice("Testing without inaccessible, skipping %s", __func__); + return; + } + + if (MANAGER_IS_SYSTEM(m) || have_userns_privileges()) { + test(m, "exec-privatedevices-yes.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED); + if (access("/dev/kmsg", F_OK) >= 0) + test(m, "exec-privatedevices-bind.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED); + test(m, "exec-privatedevices-disabled-by-prefix.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED); + test(m, "exec-privatedevices-yes-with-group.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED); + } + + test(m, "exec-privatedevices-no.service", 0, CLD_EXITED); + + /* We use capsh to test if the capabilities are + * properly set, so be sure that it exists */ + r = find_executable("capsh", NULL); + if (r < 0) { + log_notice_errno(r, "Could not find capsh binary, skipping remaining tests in %s: %m", __func__); + return; + } + + if (MANAGER_IS_SYSTEM(m) || have_userns_privileges()) { + test(m, "exec-privatedevices-yes-capability-mknod.service", can_unshare || MANAGER_IS_SYSTEM(m) ? 0 : EXIT_NAMESPACE, CLD_EXITED); + test(m, "exec-privatedevices-yes-capability-sys-rawio.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_NAMESPACE, CLD_EXITED); + } + + test(m, "exec-privatedevices-no-capability-mknod.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_FAILURE, CLD_EXITED); + test(m, "exec-privatedevices-no-capability-sys-rawio.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_FAILURE, CLD_EXITED); +} + +static void test_exec_protecthome(Manager *m) { + if (!can_unshare) { + log_notice("Cannot reliably unshare, skipping %s", __func__); + return; + } + + test(m, "exec-protecthome-tmpfs-vs-protectsystem-strict.service", 0, CLD_EXITED); +} + +static void test_exec_protectkernelmodules(Manager *m) { + int r; + + if (detect_container() > 0) { + log_notice("Testing in container, skipping %s", __func__); + return; + } + if (!is_inaccessible_available()) { + log_notice("Testing without inaccessible, skipping %s", __func__); + return; + } + + r = find_executable("capsh", NULL); + if (r < 0) { + log_notice_errno(r, "Skipping %s, could not find capsh binary: %m", __func__); + return; + } + + test(m, "exec-protectkernelmodules-no-capabilities.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_FAILURE, CLD_EXITED); + + if (MANAGER_IS_SYSTEM(m) || have_userns_privileges()) { + test(m, "exec-protectkernelmodules-yes-capabilities.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_NAMESPACE, CLD_EXITED); + test(m, "exec-protectkernelmodules-yes-mount-propagation.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED); + } +} + +static void test_exec_readonlypaths(Manager *m) { + + if (MANAGER_IS_SYSTEM(m) || have_userns_privileges()) + test(m, "exec-readonlypaths-simple.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED); + + if (path_is_read_only_fs("/var") > 0) { + log_notice("Directory /var is readonly, skipping remaining tests in %s", __func__); + return; + } + + test(m, "exec-readonlypaths.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED); + test(m, "exec-readonlypaths-with-bindpaths.service", can_unshare ? 0 : EXIT_NAMESPACE, CLD_EXITED); + test(m, "exec-readonlypaths-mount-propagation.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED); +} + +static void test_exec_readwritepaths(Manager *m) { + + if (path_is_read_only_fs("/") > 0) { + log_notice("Root directory is readonly, skipping %s", __func__); + return; + } + + test(m, "exec-readwritepaths-mount-propagation.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED); +} + +static void test_exec_inaccessiblepaths(Manager *m) { + + if (!is_inaccessible_available()) { + log_notice("Testing without inaccessible, skipping %s", __func__); + return; + } + + if (MANAGER_IS_SYSTEM(m) || have_userns_privileges()) + test(m, "exec-inaccessiblepaths-sys.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED); + + if (path_is_read_only_fs("/") > 0) { + log_notice("Root directory is readonly, skipping remaining tests in %s", __func__); + return; + } + + test(m, "exec-inaccessiblepaths-mount-propagation.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED); +} + +static int on_spawn_io(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + char **result = userdata; + char buf[4096]; + ssize_t l; + + assert_se(s); + assert_se(fd >= 0); + + l = read(fd, buf, sizeof(buf) - 1); + if (l < 0) { + if (errno == EAGAIN) + goto reenable; + + return 0; + } + if (l == 0) + return 0; + + buf[l] = '\0'; + if (result) + assert_se(strextend(result, buf)); + else + log_error("ldd: %s", buf); + +reenable: + /* Re-enable the event source if we did not encounter EOF */ + assert_se(sd_event_source_set_enabled(s, SD_EVENT_ONESHOT) >= 0); + return 0; +} + +static int on_spawn_timeout(sd_event_source *s, uint64_t usec, void *userdata) { + pid_t *pid = userdata; + + assert_se(pid); + + (void) kill(*pid, SIGKILL); + + return 1; +} + +static int on_spawn_sigchld(sd_event_source *s, const siginfo_t *si, void *userdata) { + int ret = -EIO; + + assert_se(si); + + if (si->si_code == CLD_EXITED) + ret = si->si_status; + + sd_event_exit(sd_event_source_get_event(s), ret); + return 1; +} + +static int find_libraries(const char *exec, char ***ret) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *sigchld_source = NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *stdout_source = NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *stderr_source = NULL; + _cleanup_close_pair_ int outpipe[2] = EBADF_PAIR, errpipe[2] = EBADF_PAIR; + _cleanup_strv_free_ char **libraries = NULL; + _cleanup_free_ char *result = NULL; + pid_t pid; + int r; + + assert_se(exec); + assert_se(ret); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0); + + assert_se(pipe2(outpipe, O_NONBLOCK|O_CLOEXEC) == 0); + assert_se(pipe2(errpipe, O_NONBLOCK|O_CLOEXEC) == 0); + + r = safe_fork_full("(spawn-ldd)", + (int[]) { -EBADF, outpipe[1], errpipe[1] }, + NULL, 0, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_LOG, &pid); + assert_se(r >= 0); + if (r == 0) { + execlp("ldd", "ldd", exec, NULL); + _exit(EXIT_FAILURE); + } + + outpipe[1] = safe_close(outpipe[1]); + errpipe[1] = safe_close(errpipe[1]); + + assert_se(sd_event_new(&e) >= 0); + + assert_se(sd_event_add_time_relative(e, NULL, CLOCK_MONOTONIC, + 10 * USEC_PER_SEC, USEC_PER_SEC, on_spawn_timeout, &pid) >= 0); + assert_se(sd_event_add_io(e, &stdout_source, outpipe[0], EPOLLIN, on_spawn_io, &result) >= 0); + assert_se(sd_event_source_set_enabled(stdout_source, SD_EVENT_ONESHOT) >= 0); + assert_se(sd_event_add_io(e, &stderr_source, errpipe[0], EPOLLIN, on_spawn_io, NULL) >= 0); + assert_se(sd_event_source_set_enabled(stderr_source, SD_EVENT_ONESHOT) >= 0); + assert_se(sd_event_add_child(e, &sigchld_source, pid, WEXITED, on_spawn_sigchld, NULL) >= 0); + /* SIGCHLD should be processed after IO is complete */ + assert_se(sd_event_source_set_priority(sigchld_source, SD_EVENT_PRIORITY_NORMAL + 1) >= 0); + + assert_se(sd_event_loop(e) >= 0); + + _cleanup_strv_free_ char **v = NULL; + assert_se(strv_split_newlines_full(&v, result, 0) >= 0); + + STRV_FOREACH(q, v) { + _cleanup_free_ char *word = NULL; + const char *p = *q; + + r = extract_first_word(&p, &word, NULL, 0); + assert_se(r >= 0); + if (r == 0) + continue; + + if (path_is_absolute(word)) { + assert_se(strv_consume(&libraries, TAKE_PTR(word)) >= 0); + continue; + } + + word = mfree(word); + r = extract_first_word(&p, &word, NULL, 0); + assert_se(r >= 0); + if (r == 0) + continue; + + if (!streq_ptr(word, "=>")) + continue; + + word = mfree(word); + r = extract_first_word(&p, &word, NULL, 0); + assert_se(r >= 0); + if (r == 0) + continue; + + if (path_is_absolute(word)) { + assert_se(strv_consume(&libraries, TAKE_PTR(word)) >= 0); + continue; + } + } + + *ret = TAKE_PTR(libraries); + return 0; +} + +static void test_exec_mount_apivfs(Manager *m) { + _cleanup_free_ char *fullpath_touch = NULL, *fullpath_test = NULL, *data = NULL; + _cleanup_strv_free_ char **libraries = NULL, **libraries_test = NULL; + int r; + + assert_se(user_runtime_unit_dir); + + r = find_executable("ldd", NULL); + if (r < 0) { + log_notice_errno(r, "Skipping %s, could not find 'ldd' command: %m", __func__); + return; + } + r = find_executable("touch", &fullpath_touch); + if (r < 0) { + log_notice_errno(r, "Skipping %s, could not find 'touch' command: %m", __func__); + return; + } + r = find_executable("test", &fullpath_test); + if (r < 0) { + log_notice_errno(r, "Skipping %s, could not find 'test' command: %m", __func__); + return; + } + + if (MANAGER_IS_USER(m) && !have_userns_privileges()) + return (void)log_notice("Skipping %s, do not have user namespace privileges", __func__); + + assert_se(find_libraries(fullpath_touch, &libraries) >= 0); + assert_se(find_libraries(fullpath_test, &libraries_test) >= 0); + assert_se(strv_extend_strv(&libraries, libraries_test, true) >= 0); + + assert_se(strextend(&data, "[Service]\n")); + assert_se(strextend(&data, "ExecStart=", fullpath_touch, " /aaa\n")); + assert_se(strextend(&data, "ExecStart=", fullpath_test, " -f /aaa\n")); + assert_se(strextend(&data, "BindReadOnlyPaths=", fullpath_touch, "\n")); + assert_se(strextend(&data, "BindReadOnlyPaths=", fullpath_test, "\n")); + + STRV_FOREACH(p, libraries) + assert_se(strextend(&data, "BindReadOnlyPaths=", *p, "\n")); + + assert_se(write_drop_in(user_runtime_unit_dir, "exec-mount-apivfs-no.service", 10, "bind-mount", data) >= 0); + + assert_se(mkdir_p("/tmp/test-exec-mount-apivfs-no/root", 0755) >= 0); + + test(m, "exec-mount-apivfs-no.service", can_unshare || !MANAGER_IS_SYSTEM(m) ? 0 : EXIT_NAMESPACE, CLD_EXITED); + + (void) rm_rf("/tmp/test-exec-mount-apivfs-no/root", REMOVE_ROOT|REMOVE_PHYSICAL); +} + +static void test_exec_noexecpaths(Manager *m) { + + if (MANAGER_IS_SYSTEM(m) || have_userns_privileges()) + test(m, "exec-noexecpaths-simple.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED); + else + return (void)log_notice("Skipping %s, do not have user namespace privileges", __func__); +} + +static void test_exec_temporaryfilesystem(Manager *m) { + + test(m, "exec-temporaryfilesystem-options.service", can_unshare ? 0 : EXIT_NAMESPACE, CLD_EXITED); + test(m, "exec-temporaryfilesystem-ro.service", can_unshare ? 0 : EXIT_NAMESPACE, CLD_EXITED); + test(m, "exec-temporaryfilesystem-rw.service", can_unshare ? 0 : EXIT_NAMESPACE, CLD_EXITED); + test(m, "exec-temporaryfilesystem-usr.service", can_unshare ? 0 : EXIT_NAMESPACE, CLD_EXITED); +} + +static void test_exec_systemcallfilter(Manager *m) { +#if HAVE_SECCOMP + int r; + + if (!is_seccomp_available()) { + log_notice("Seccomp not available, skipping %s", __func__); + return; + } + + test(m, "exec-systemcallfilter-not-failing.service", 0, CLD_EXITED); + test(m, "exec-systemcallfilter-not-failing2.service", 0, CLD_EXITED); + test(m, "exec-systemcallfilter-not-failing3.service", 0, CLD_EXITED); + test(m, "exec-systemcallfilter-failing.service", SIGSYS, CLD_KILLED); + test(m, "exec-systemcallfilter-failing2.service", SIGSYS, CLD_KILLED); + test(m, "exec-systemcallfilter-failing3.service", SIGSYS, CLD_KILLED); + + r = find_executable("python3", NULL); + if (r < 0) { + log_notice_errno(r, "Skipping remaining tests in %s, could not find python3 binary: %m", __func__); + return; + } + + test(m, "exec-systemcallfilter-with-errno-name.service", errno_from_name("EILSEQ"), CLD_EXITED); + test(m, "exec-systemcallfilter-with-errno-number.service", 255, CLD_EXITED); + test(m, "exec-systemcallfilter-with-errno-multi.service", errno_from_name("EILSEQ"), CLD_EXITED); + test(m, "exec-systemcallfilter-with-errno-in-allow-list.service", errno_from_name("EILSEQ"), CLD_EXITED); + test(m, "exec-systemcallfilter-override-error-action.service", SIGSYS, CLD_KILLED); + test(m, "exec-systemcallfilter-override-error-action2.service", errno_from_name("EILSEQ"), CLD_EXITED); + + test(m, "exec-systemcallfilter-nonewprivileges.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP, CLD_EXITED); + test(m, "exec-systemcallfilter-nonewprivileges-protectclock.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP, CLD_EXITED); + + r = find_executable("capsh", NULL); + if (r < 0) { + log_notice_errno(r, "Skipping %s, could not find capsh binary: %m", __func__); + return; + } + + test(m, "exec-systemcallfilter-nonewprivileges-bounding1.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP, CLD_EXITED); + test(m, "exec-systemcallfilter-nonewprivileges-bounding2.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP, CLD_EXITED); +#endif +} + +static void test_exec_systemcallerrornumber(Manager *m) { +#if HAVE_SECCOMP + int r; + + if (!is_seccomp_available()) { + log_notice("Seccomp not available, skipping %s", __func__); + return; + } + + r = find_executable("python3", NULL); + if (r < 0) { + log_notice_errno(r, "Skipping %s, could not find python3 binary: %m", __func__); + return; + } + + test(m, "exec-systemcallerrornumber-name.service", errno_from_name("EACCES"), CLD_EXITED); + test(m, "exec-systemcallerrornumber-number.service", 255, CLD_EXITED); +#endif +} + +static void test_exec_restrictnamespaces(Manager *m) { +#if HAVE_SECCOMP + if (!is_seccomp_available()) { + log_notice("Seccomp not available, skipping %s", __func__); + return; + } + + test(m, "exec-restrictnamespaces-no.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED); + test(m, "exec-restrictnamespaces-yes.service", 1, CLD_EXITED); + test(m, "exec-restrictnamespaces-mnt.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED); + test(m, "exec-restrictnamespaces-mnt-deny-list.service", 1, CLD_EXITED); + test(m, "exec-restrictnamespaces-merge-and.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED); + test(m, "exec-restrictnamespaces-merge-or.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED); + test(m, "exec-restrictnamespaces-merge-all.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED); +#endif +} + +static void test_exec_systemcallfilter_system(Manager *m) { +/* Skip this particular test case when running under ASan, as + * LSan intermittently segfaults when accessing memory right + * after the test finishes. Generally, ASan & LSan don't like + * the seccomp stuff. + */ +#if HAVE_SECCOMP && !HAS_FEATURE_ADDRESS_SANITIZER + if (!is_seccomp_available()) { + log_notice("Seccomp not available, skipping %s", __func__); + return; + } + + test(m, "exec-systemcallfilter-system-user.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP, CLD_EXITED); + + if (!check_nobody_user_and_group()) { + log_notice("nobody user/group is not synthesized or may conflict to other entries, skipping remaining tests in %s", __func__); + return; + } + + if (!STR_IN_SET(NOBODY_USER_NAME, "nobody", "nfsnobody")) { + log_notice("Unsupported nobody user name '%s', skipping remaining tests in %s", NOBODY_USER_NAME, __func__); + return; + } + + test(m, "exec-systemcallfilter-system-user-" NOBODY_USER_NAME ".service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP, CLD_EXITED); +#endif +} + +static void test_exec_user(Manager *m) { + test(m, "exec-user.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP, CLD_EXITED); + + if (!check_nobody_user_and_group()) { + log_notice("nobody user/group is not synthesized or may conflict to other entries, skipping remaining tests in %s", __func__); + return; + } + + if (!STR_IN_SET(NOBODY_USER_NAME, "nobody", "nfsnobody")) { + log_notice("Unsupported nobody user name '%s', skipping remaining tests in %s", NOBODY_USER_NAME, __func__); + return; + } + + test(m, "exec-user-" NOBODY_USER_NAME ".service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP, CLD_EXITED); +} + +static void test_exec_group(Manager *m) { + test(m, "exec-group.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP, CLD_EXITED); + + if (!check_nobody_user_and_group()) { + log_notice("nobody user/group is not synthesized or may conflict to other entries, skipping remaining tests in %s", __func__); + return; + } + + if (!STR_IN_SET(NOBODY_GROUP_NAME, "nobody", "nfsnobody", "nogroup")) { + log_notice("Unsupported nobody group name '%s', skipping remaining tests in %s", NOBODY_GROUP_NAME, __func__); + return; + } + + test(m, "exec-group-" NOBODY_GROUP_NAME ".service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP, CLD_EXITED); +} + +static void test_exec_supplementarygroups(Manager *m) { + int status = MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP; + test(m, "exec-supplementarygroups.service", status, CLD_EXITED); + test(m, "exec-supplementarygroups-single-group.service", status, CLD_EXITED); + test(m, "exec-supplementarygroups-single-group-user.service", status, CLD_EXITED); + test(m, "exec-supplementarygroups-multiple-groups-default-group-user.service", status, CLD_EXITED); + test(m, "exec-supplementarygroups-multiple-groups-withgid.service", status, CLD_EXITED); + test(m, "exec-supplementarygroups-multiple-groups-withuid.service", status, CLD_EXITED); +} + +static char* private_directory_bad(Manager *m) { + /* This mirrors setup_exec_directory(). */ + + for (ExecDirectoryType dt = 0; dt < _EXEC_DIRECTORY_TYPE_MAX; dt++) { + _cleanup_free_ char *p = NULL; + struct stat st; + + assert_se(p = path_join(m->prefix[dt], "private")); + + if (stat(p, &st) >= 0 && + (st.st_mode & (S_IRWXG|S_IRWXO))) + return TAKE_PTR(p); + } + + return NULL; +} + +static void test_exec_dynamicuser(Manager *m) { + _cleanup_free_ char *bad = private_directory_bad(m); + if (bad) { + log_warning("%s: %s has bad permissions, skipping test.", __func__, bad); + return; + } + + if (strstr_ptr(ci_environment(), "github-actions")) { + log_notice("%s: skipping test on GH Actions because of systemd/systemd#10337", __func__); + return; + } + + int status = can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_NAMESPACE : EXIT_GROUP; + + test(m, "exec-dynamicuser-fixeduser.service", status, CLD_EXITED); + if (check_user_has_group_with_same_name("adm")) + test(m, "exec-dynamicuser-fixeduser-adm.service", status, CLD_EXITED); + if (check_user_has_group_with_same_name("games")) + test(m, "exec-dynamicuser-fixeduser-games.service", status, CLD_EXITED); + test(m, "exec-dynamicuser-fixeduser-one-supplementarygroup.service", status, CLD_EXITED); + test(m, "exec-dynamicuser-supplementarygroups.service", status, CLD_EXITED); + test(m, "exec-dynamicuser-statedir.service", status, CLD_EXITED); + + (void) rm_rf("/var/lib/quux", REMOVE_ROOT|REMOVE_PHYSICAL); + (void) rm_rf("/var/lib/test-dynamicuser-migrate", REMOVE_ROOT|REMOVE_PHYSICAL); + (void) rm_rf("/var/lib/test-dynamicuser-migrate2", REMOVE_ROOT|REMOVE_PHYSICAL); + (void) rm_rf("/var/lib/waldo", REMOVE_ROOT|REMOVE_PHYSICAL); + (void) rm_rf("/var/lib/private/quux", REMOVE_ROOT|REMOVE_PHYSICAL); + (void) rm_rf("/var/lib/private/test-dynamicuser-migrate", REMOVE_ROOT|REMOVE_PHYSICAL); + (void) rm_rf("/var/lib/private/test-dynamicuser-migrate2", REMOVE_ROOT|REMOVE_PHYSICAL); + (void) rm_rf("/var/lib/private/waldo", REMOVE_ROOT|REMOVE_PHYSICAL); + + test(m, "exec-dynamicuser-statedir-migrate-step1.service", 0, CLD_EXITED); + test(m, "exec-dynamicuser-statedir-migrate-step2.service", status, CLD_EXITED); + test(m, "exec-dynamicuser-statedir-migrate-step1.service", 0, CLD_EXITED); + + (void) rm_rf("/var/lib/test-dynamicuser-migrate", REMOVE_ROOT|REMOVE_PHYSICAL); + (void) rm_rf("/var/lib/test-dynamicuser-migrate2", REMOVE_ROOT|REMOVE_PHYSICAL); + (void) rm_rf("/var/lib/private/test-dynamicuser-migrate", REMOVE_ROOT|REMOVE_PHYSICAL); + (void) rm_rf("/var/lib/private/test-dynamicuser-migrate2", REMOVE_ROOT|REMOVE_PHYSICAL); + + test(m, "exec-dynamicuser-runtimedirectory1.service", status, CLD_EXITED); + test(m, "exec-dynamicuser-runtimedirectory2.service", status, CLD_EXITED); + test(m, "exec-dynamicuser-runtimedirectory3.service", status, CLD_EXITED); +} + +static void test_exec_environment(Manager *m) { + test(m, "exec-environment-no-substitute.service", 0, CLD_EXITED); + test(m, "exec-environment.service", 0, CLD_EXITED); + test(m, "exec-environment-multiple.service", 0, CLD_EXITED); + test(m, "exec-environment-empty.service", 0, CLD_EXITED); +} + +static void test_exec_environmentfile(Manager *m) { + static const char e[] = + "VAR1='word1 word2'\n" + "VAR2=word3 \n" + "# comment1\n" + "\n" + "; comment2\n" + " ; # comment3\n" + "line without an equal\n" + "VAR3='$word 5 6'\n" + "VAR4='new\nline'\n" + "VAR5=password\\with\\backslashes"; + int r; + + r = write_string_file("/tmp/test-exec_environmentfile.conf", e, WRITE_STRING_FILE_CREATE); + assert_se(r == 0); + + test(m, "exec-environmentfile.service", 0, CLD_EXITED); + + (void) unlink("/tmp/test-exec_environmentfile.conf"); +} + +static void test_exec_passenvironment(Manager *m) { + /* test-execute runs under MANAGER_USER which, by default, forwards all + * variables present in the environment, but only those that are + * present _at the time it is created_! + * + * So these PassEnvironment checks are still expected to work, since we + * are ensuring the variables are not present at manager creation (they + * are unset explicitly in main) and are only set here. + * + * This is still a good approximation of how a test for MANAGER_SYSTEM + * would work. + */ + assert_se(setenv("VAR1", "word1 word2", 1) == 0); + assert_se(setenv("VAR2", "word3", 1) == 0); + assert_se(setenv("VAR3", "$word 5 6", 1) == 0); + assert_se(setenv("VAR4", "new\nline", 1) == 0); + assert_se(setenv("VAR5", "passwordwithbackslashes", 1) == 0); + test(m, "exec-passenvironment.service", 0, CLD_EXITED); + test(m, "exec-passenvironment-repeated.service", 0, CLD_EXITED); + test(m, "exec-passenvironment-empty.service", 0, CLD_EXITED); + assert_se(unsetenv("VAR1") == 0); + assert_se(unsetenv("VAR2") == 0); + assert_se(unsetenv("VAR3") == 0); + assert_se(unsetenv("VAR4") == 0); + assert_se(unsetenv("VAR5") == 0); + test(m, "exec-passenvironment-absent.service", 0, CLD_EXITED); +} + +static void test_exec_umask(Manager *m) { + if (MANAGER_IS_SYSTEM(m) || have_userns_privileges()) { + test(m, "exec-umask-default.service", can_unshare || MANAGER_IS_SYSTEM(m) ? 0 : EXIT_NAMESPACE, CLD_EXITED); + test(m, "exec-umask-0177.service", can_unshare || MANAGER_IS_SYSTEM(m) ? 0 : EXIT_NAMESPACE, CLD_EXITED); + } else + return (void)log_notice("Skipping %s, do not have user namespace privileges", __func__); +} + +static void test_exec_runtimedirectory(Manager *m) { + (void) rm_rf("/run/test-exec_runtimedirectory2", REMOVE_ROOT|REMOVE_PHYSICAL); + test(m, "exec-runtimedirectory.service", 0, CLD_EXITED); + (void) rm_rf("/run/test-exec_runtimedirectory2", REMOVE_ROOT|REMOVE_PHYSICAL); + + test(m, "exec-runtimedirectory-mode.service", 0, CLD_EXITED); + test(m, "exec-runtimedirectory-owner.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP, CLD_EXITED); + + if (!check_nobody_user_and_group()) { + log_notice("nobody user/group is not synthesized or may conflict to other entries, skipping remaining tests in %s", __func__); + return; + } + + if (!STR_IN_SET(NOBODY_GROUP_NAME, "nobody", "nfsnobody", "nogroup")) { + log_notice("Unsupported nobody group name '%s', skipping remaining tests in %s", NOBODY_GROUP_NAME, __func__); + return; + } + + test(m, "exec-runtimedirectory-owner-" NOBODY_GROUP_NAME ".service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_GROUP, CLD_EXITED); +} + +static void test_exec_capabilityboundingset(Manager *m) { + int r; + + r = find_executable("capsh", NULL); + if (r < 0) { + log_notice_errno(r, "Skipping %s, could not find capsh binary: %m", __func__); + return; + } + + if (have_effective_cap(CAP_CHOWN) <= 0 || + have_effective_cap(CAP_FOWNER) <= 0 || + have_effective_cap(CAP_KILL) <= 0) { + log_notice("Skipping %s, this process does not have enough capabilities", __func__); + return; + } + + test(m, "exec-capabilityboundingset-simple.service", 0, CLD_EXITED); + test(m, "exec-capabilityboundingset-reset.service", 0, CLD_EXITED); + test(m, "exec-capabilityboundingset-merge.service", 0, CLD_EXITED); + test(m, "exec-capabilityboundingset-invert.service", 0, CLD_EXITED); +} + +static void test_exec_basic(Manager *m) { + if (MANAGER_IS_SYSTEM(m) || have_userns_privileges()) + test(m, "exec-basic.service", can_unshare || MANAGER_IS_SYSTEM(m) ? 0 : EXIT_NAMESPACE, CLD_EXITED); + else + return (void)log_notice("Skipping %s, do not have user namespace privileges", __func__); +} + +static void test_exec_ambientcapabilities(Manager *m) { + int r; + + /* Check if the kernel has support for ambient capabilities. Run + * the tests only if that's the case. Clearing all ambient + * capabilities is fine, since we are expecting them to be unset + * in the first place for the tests. */ + r = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0); + if (r < 0 && IN_SET(errno, EINVAL, EOPNOTSUPP, ENOSYS)) { + log_notice("Skipping %s, the kernel does not support ambient capabilities", __func__); + return; + } + + if (have_effective_cap(CAP_CHOWN) <= 0 || + have_effective_cap(CAP_NET_RAW) <= 0) { + log_notice("Skipping %s, this process does not have enough capabilities", __func__); + return; + } + + test(m, "exec-ambientcapabilities.service", 0, CLD_EXITED); + test(m, "exec-ambientcapabilities-merge.service", 0, CLD_EXITED); + + if (have_effective_cap(CAP_SETUID) > 0) + test(m, "exec-ambientcapabilities-dynuser.service", can_unshare ? 0 : EXIT_NAMESPACE, CLD_EXITED); + + if (!check_nobody_user_and_group()) { + log_notice("nobody user/group is not synthesized or may conflict to other entries, skipping remaining tests in %s", __func__); + return; + } + + if (!STR_IN_SET(NOBODY_USER_NAME, "nobody", "nfsnobody")) { + log_notice("Unsupported nobody user name '%s', skipping remaining tests in %s", NOBODY_USER_NAME, __func__); + return; + } + + test(m, "exec-ambientcapabilities-" NOBODY_USER_NAME ".service", 0, CLD_EXITED); + test(m, "exec-ambientcapabilities-merge-" NOBODY_USER_NAME ".service", 0, CLD_EXITED); +} + +static void test_exec_privatenetwork(Manager *m) { + int r; + + if (!have_net_dummy) + return (void)log_notice("Skipping %s, dummy network interface not available", __func__); + + if (MANAGER_IS_USER(m) && !have_userns_privileges()) + return (void)log_notice("Skipping %s, do not have user namespace privileges", __func__); + + r = find_executable("ip", NULL); + if (r < 0) { + log_notice_errno(r, "Skipping %s, could not find ip binary: %m", __func__); + return; + } + + test(m, "exec-privatenetwork-yes-privatemounts-no.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_NETWORK : EXIT_FAILURE, CLD_EXITED); + test(m, "exec-privatenetwork-yes-privatemounts-yes.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_NETWORK : EXIT_NAMESPACE, CLD_EXITED); +} + +static void test_exec_networknamespacepath(Manager *m) { + int r; + + if (!have_net_dummy) + return (void)log_notice("Skipping %s, dummy network interface not available", __func__); + + if (!have_netns) + return (void)log_notice("Skipping %s, network namespace not available", __func__); + + if (MANAGER_IS_USER(m) && !have_userns_privileges()) + return (void)log_notice("Skipping %s, do not have user namespace privileges", __func__); + + r = find_executable("ip", NULL); + if (r < 0) { + log_notice_errno(r, "Skipping %s, could not find ip binary: %m", __func__); + return; + } + + test(m, "exec-networknamespacepath-privatemounts-no.service", MANAGER_IS_SYSTEM(m) ? EXIT_SUCCESS : EXIT_FAILURE, CLD_EXITED); + test(m, "exec-networknamespacepath-privatemounts-yes.service", can_unshare ? EXIT_SUCCESS : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED); +} + +static void test_exec_oomscoreadjust(Manager *m) { + test(m, "exec-oomscoreadjust-positive.service", 0, CLD_EXITED); + + if (detect_container() > 0) { + log_notice("Testing in container, skipping remaining tests in %s", __func__); + return; + } + test(m, "exec-oomscoreadjust-negative.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_FAILURE, CLD_EXITED); +} + +static void test_exec_ioschedulingclass(Manager *m) { + test(m, "exec-ioschedulingclass-none.service", 0, CLD_EXITED); + test(m, "exec-ioschedulingclass-idle.service", 0, CLD_EXITED); + test(m, "exec-ioschedulingclass-best-effort.service", 0, CLD_EXITED); + + if (detect_container() > 0) { + log_notice("Testing in container, skipping remaining tests in %s", __func__); + return; + } + test(m, "exec-ioschedulingclass-realtime.service", MANAGER_IS_SYSTEM(m) ? 0 : EXIT_IOPRIO, CLD_EXITED); +} + +static void test_exec_unsetenvironment(Manager *m) { + test(m, "exec-unsetenvironment.service", 0, CLD_EXITED); +} + +static void test_exec_specifier(Manager *m) { + test(m, "exec-specifier.service", 0, CLD_EXITED); + if (MANAGER_IS_SYSTEM(m)) + test(m, "exec-specifier-system.service", 0, CLD_EXITED); + else + test(m, "exec-specifier-user.service", 0, CLD_EXITED); + test(m, "exec-specifier@foo-bar.service", 0, CLD_EXITED); + test(m, "exec-specifier-interpolation.service", 0, CLD_EXITED); +} + +static void test_exec_standardinput(Manager *m) { + test(m, "exec-standardinput-data.service", 0, CLD_EXITED); + test(m, "exec-standardinput-file.service", 0, CLD_EXITED); + test(m, "exec-standardinput-file-cat.service", 0, CLD_EXITED); +} + +static void test_exec_standardoutput(Manager *m) { + test(m, "exec-standardoutput-file.service", 0, CLD_EXITED); +} + +static void test_exec_standardoutput_append(Manager *m) { + test(m, "exec-standardoutput-append.service", 0, CLD_EXITED); +} + +static void test_exec_standardoutput_truncate(Manager *m) { + test(m, "exec-standardoutput-truncate.service", 0, CLD_EXITED); +} + +static void test_exec_condition(Manager *m) { + test_service(m, "exec-condition-failed.service", SERVICE_FAILURE_EXIT_CODE); + test_service(m, "exec-condition-skip.service", SERVICE_SKIP_CONDITION); +} + +static void test_exec_umask_namespace(Manager *m) { + /* exec-specifier-credentials-dir.service creates /run/credentials and enables implicit + * InaccessiblePath= for the directory for all later services with mount namespace. */ + if (!is_inaccessible_available()) { + log_notice("Testing without inaccessible, skipping %s", __func__); + return; + } + test(m, "exec-umask-namespace.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_NAMESPACE : EXIT_GROUP, CLD_EXITED); +} + +typedef struct test_entry { + test_function_t f; + const char *name; +} test_entry; + +#define entry(x) {x, #x} + +static void run_tests(RuntimeScope scope, char **patterns) { + _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; + _cleanup_free_ char *unit_paths = NULL; + _cleanup_(manager_freep) Manager *m = NULL; + usec_t start, finish; + int r; + + static const test_entry tests[] = { + entry(test_exec_basic), + entry(test_exec_ambientcapabilities), + entry(test_exec_bindpaths), + entry(test_exec_capabilityboundingset), + entry(test_exec_condition), + entry(test_exec_cpuaffinity), + entry(test_exec_credentials), + entry(test_exec_dynamicuser), + entry(test_exec_environment), + entry(test_exec_environmentfile), + entry(test_exec_execsearchpath), + entry(test_exec_execsearchpath_environment), + entry(test_exec_execsearchpath_environment_files), + entry(test_exec_execsearchpath_passenvironment), + entry(test_exec_execsearchpath_specifier), + entry(test_exec_group), + entry(test_exec_ignoresigpipe), + entry(test_exec_inaccessiblepaths), + entry(test_exec_ioschedulingclass), + entry(test_exec_mount_apivfs), + entry(test_exec_networknamespacepath), + entry(test_exec_noexecpaths), + entry(test_exec_oomscoreadjust), + entry(test_exec_passenvironment), + entry(test_exec_personality), + entry(test_exec_privatedevices), + entry(test_exec_privatenetwork), + entry(test_exec_privatetmp), + entry(test_exec_protecthome), + entry(test_exec_protectkernelmodules), + entry(test_exec_readonlypaths), + entry(test_exec_readwritepaths), + entry(test_exec_restrictnamespaces), + entry(test_exec_runtimedirectory), + entry(test_exec_specifier), + entry(test_exec_standardinput), + entry(test_exec_standardoutput), + entry(test_exec_standardoutput_append), + entry(test_exec_standardoutput_truncate), + entry(test_exec_supplementarygroups), + entry(test_exec_systemcallerrornumber), + entry(test_exec_systemcallfilter), + entry(test_exec_systemcallfilter_system), + entry(test_exec_temporaryfilesystem), + entry(test_exec_umask), + entry(test_exec_umask_namespace), + entry(test_exec_unsetenvironment), + entry(test_exec_user), + entry(test_exec_workingdirectory), + {}, + }; + + assert_se(unsetenv("USER") == 0); + assert_se(unsetenv("LOGNAME") == 0); + assert_se(unsetenv("SHELL") == 0); + assert_se(unsetenv("HOME") == 0); + assert_se(unsetenv("TMPDIR") == 0); + + /* Unset VARx, especially, VAR1, VAR2 and VAR3, which are used in the PassEnvironment test cases, + * otherwise (and if they are present in the environment), `manager_default_environment` will copy + * them into the default environment which is passed to each created job, which will make the tests + * that expect those not to be present to fail. */ + assert_se(unsetenv("VAR1") == 0); + assert_se(unsetenv("VAR2") == 0); + assert_se(unsetenv("VAR3") == 0); + assert_se(unsetenv("VAR4") == 0); + assert_se(unsetenv("VAR5") == 0); + + assert_se(runtime_dir = setup_fake_runtime_dir()); + assert_se(user_runtime_unit_dir = path_join(runtime_dir, "systemd/user")); + assert_se(unit_paths = strjoin(PRIVATE_UNIT_DIR, ":", user_runtime_unit_dir)); + assert_se(set_unit_path(unit_paths) >= 0); + + r = manager_new(scope, MANAGER_TEST_RUN_BASIC, &m); + if (manager_errno_skip_test(r)) + return (void) log_tests_skipped_errno(r, "manager_new"); + assert_se(r >= 0); + + m->defaults.std_output = EXEC_OUTPUT_NULL; /* don't rely on host journald */ + assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + + /* Uncomment below if you want to make debugging logs stored to journal. */ + //manager_override_log_target(m, LOG_TARGET_AUTO); + //manager_override_log_level(m, LOG_DEBUG); + + /* Measure and print the time that it takes to run tests, excluding startup of the manager object, + * to try and measure latency of spawning services */ + n_ran_tests = 0; + start = now(CLOCK_MONOTONIC); + + for (const test_entry *test = tests; test->f; test++) + if (strv_fnmatch_or_empty(patterns, test->name, FNM_NOESCAPE)) + test->f(m); + else + log_info("Skipping %s because it does not match any pattern.", test->name); + + finish = now(CLOCK_MONOTONIC); + + log_info("ran %u tests with %s manager + unshare=%s in: %s", + n_ran_tests, + scope == RUNTIME_SCOPE_SYSTEM ? "system" : "user", + yes_no(can_unshare), + FORMAT_TIMESPAN(finish - start, USEC_PER_MSEC)); +} + +static int prepare_ns(const char *process_name) { + int r; + + r = safe_fork(process_name, + FORK_RESET_SIGNALS | + FORK_CLOSE_ALL_FDS | + FORK_DEATHSIG_SIGTERM | + FORK_WAIT | + FORK_REOPEN_LOG | + FORK_LOG | + FORK_NEW_MOUNTNS | + FORK_MOUNTNS_SLAVE, + NULL); + assert_se(r >= 0); + if (r == 0) { + _cleanup_free_ char *unit_dir = NULL; + + /* Make "/" read-only. */ + assert_se(mount_nofollow_verbose(LOG_DEBUG, NULL, "/", NULL, MS_BIND|MS_REMOUNT|MS_RDONLY, NULL) >= 0); + + /* Creating a new user namespace in the above means all MS_SHARED mounts become MS_SLAVE. + * Let's put them back to MS_SHARED here, since that's what we want as defaults. (This will + * not reconnect propagation, but simply create new peer groups for all our mounts). */ + assert_se(mount_follow_verbose(LOG_DEBUG, NULL, "/", NULL, MS_SHARED|MS_REC, NULL) >= 0); + + assert_se(mkdir_p(PRIVATE_UNIT_DIR, 0755) >= 0); + assert_se(mount_nofollow_verbose(LOG_DEBUG, "tmpfs", PRIVATE_UNIT_DIR, "tmpfs", MS_NOSUID|MS_NODEV, NULL) >= 0); + + /* Copy unit files to make them accessible even when unprivileged. */ + assert_se(get_testdata_dir("test-execute/", &unit_dir) >= 0); + assert_se(copy_directory_at(AT_FDCWD, unit_dir, AT_FDCWD, PRIVATE_UNIT_DIR, COPY_MERGE_EMPTY) >= 0); + + /* Mount tmpfs on the following directories to make not StateDirectory= or friends disturb the host. */ + FOREACH_STRING(p, "/dev/shm", "/root", "/tmp", "/var/tmp", "/var/lib") + assert_se(mount_nofollow_verbose(LOG_DEBUG, "tmpfs", p, "tmpfs", MS_NOSUID|MS_NODEV, NULL) >= 0); + + /* Prepare credstore like tmpfiles.d/credstore.conf for LoadCredential= tests. */ + FOREACH_STRING(p, "/run/credstore", "/run/credstore.encrypted") { + assert_se(mkdir_p(p, 0) >= 0); + assert_se(mount_nofollow_verbose(LOG_DEBUG, "tmpfs", p, "tmpfs", MS_NOSUID|MS_NODEV, "mode=0000") >= 0); + } + + assert_se(write_string_file("/run/credstore/test-execute.load-credential", "foo", WRITE_STRING_FILE_CREATE) >= 0); + } + + return r; +} + +TEST(run_tests_root) { + _cleanup_strv_free_ char **filters = NULL; + + if (!have_namespaces()) + return (void) log_tests_skipped("unshare() is disabled"); + + /* safe_fork() clears saved_argv in the child process. Let's copy it. */ + assert_se(filters = strv_copy(strv_skip(saved_argv, 1))); + + if (prepare_ns("(test-execute-root)") == 0) { + can_unshare = true; + run_tests(RUNTIME_SCOPE_SYSTEM, filters); + _exit(EXIT_SUCCESS); + } +} + +TEST(run_tests_without_unshare) { + if (!have_namespaces()) { + /* unshare() is already filtered. */ + can_unshare = false; + run_tests(RUNTIME_SCOPE_SYSTEM, strv_skip(saved_argv, 1)); + return; + } + +#if HAVE_SECCOMP + _cleanup_strv_free_ char **filters = NULL; + int r; + + /* The following tests are for 1beab8b0d0ff2d7d1436b52d4a0c3d56dc908962. */ + if (!is_seccomp_available()) + return (void) log_tests_skipped("Seccomp not available, cannot run unshare() filtered tests"); + + /* safe_fork() clears saved_argv in the child process. Let's copy it. */ + assert_se(filters = strv_copy(strv_skip(saved_argv, 1))); + + if (prepare_ns("(test-execute-without-unshare)") == 0) { + _cleanup_hashmap_free_ Hashmap *s = NULL; + + r = seccomp_syscall_resolve_name("unshare"); + assert_se(r != __NR_SCMP_ERROR); + assert_se(hashmap_ensure_put(&s, NULL, UINT32_TO_PTR(r + 1), INT_TO_PTR(-1)) >= 0); + assert_se(seccomp_load_syscall_filter_set_raw(SCMP_ACT_ALLOW, s, SCMP_ACT_ERRNO(EOPNOTSUPP), true) >= 0); + + /* Check unshare() is actually filtered. */ + assert_se(unshare(CLONE_NEWNS) < 0); + assert_se(errno == EOPNOTSUPP); + + can_unshare = false; + run_tests(RUNTIME_SCOPE_SYSTEM, filters); + _exit(EXIT_SUCCESS); + } +#else + log_tests_skipped("Built without seccomp support, cannot run unshare() filtered tests"); +#endif +} + +TEST(run_tests_unprivileged) { + _cleanup_strv_free_ char **filters = NULL; + + if (!have_namespaces()) + return (void) log_tests_skipped("unshare() is disabled"); + + /* safe_fork() clears saved_argv in the child process. Let's copy it. */ + assert_se(filters = strv_copy(strv_skip(saved_argv, 1))); + + if (prepare_ns("(test-execute-unprivileged)") == 0) { + assert_se(capability_bounding_set_drop(0, /* right_now = */ true) >= 0); + + can_unshare = false; + run_tests(RUNTIME_SCOPE_USER, filters); + _exit(EXIT_SUCCESS); + } +} + +static int intro(void) { +#if HAS_FEATURE_ADDRESS_SANITIZER + if (strstr_ptr(ci_environment(), "travis") || strstr_ptr(ci_environment(), "github-actions")) + return log_tests_skipped("Running on Travis CI/GH Actions under ASan, see https://github.com/systemd/systemd/issues/10696"); +#endif + /* It is needed otherwise cgroup creation fails */ + if (geteuid() != 0 || have_effective_cap(CAP_SYS_ADMIN) <= 0) + return log_tests_skipped("not privileged"); + + if (enter_cgroup_subroot(NULL) == -ENOMEDIUM) + return log_tests_skipped("cgroupfs not available"); + + if (path_is_read_only_fs("/sys") > 0) + return log_tests_skipped("/sys is mounted read-only"); + + /* Create dummy network interface for testing PrivateNetwork=yes */ + have_net_dummy = system("ip link add dummy-test-exec type dummy") == 0; + + if (have_net_dummy) { + /* Create a network namespace and a dummy interface in it for NetworkNamespacePath= */ + have_netns = system("ip netns add test-execute-netns") == 0; + have_netns = have_netns && system("ip netns exec test-execute-netns ip link add dummy-test-ns type dummy") == 0; + } + + return EXIT_SUCCESS; +} + +static int outro(void) { + if (have_net_dummy) { + (void) system("ip link del dummy-test-exec"); + (void) system("ip netns del test-execute-netns"); + } + + (void) rmdir(PRIVATE_UNIT_DIR); + + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_FULL(LOG_DEBUG, intro, outro); diff --git a/src/test/test-execve.c b/src/test/test-execve.c new file mode 100644 index 0000000..e7a9a51 --- /dev/null +++ b/src/test/test-execve.c @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "exec-util.h" +#include "fd-util.h" +#include "log.h" +#include "main-func.h" +#include "strv.h" +#include "tests.h" + +/* This program can be used to call programs through fexecve / execveat(…, "", …, AT_EMPTY_PATH), + * when compiled with -Dfexecve=true, and the fallback paths, when -Dfexecve=false. + * + * Example: + * $ strace -e execveat build/test-execve /bin/grep Name /proc/self/status + * execveat(3, "", ["/bin/grep", "Name", "/proc/self/status"], NULL, AT_EMPTY_PATH) = 0 + * Name: 3 + * + * FIXME: use the new kernel api to set COMM properly when the kernel makes that available. + * C.f. ceedbf8185fc7593366679f02d31da63af8c4bd1. + */ + +static int run(int argc, char **argv) { + _cleanup_close_ int fd = -EBADF; + char **args = strv_skip(argv, 1); + int r; + + test_setup_logging(LOG_DEBUG); + + args = !strv_isempty(args) ? args : STRV_MAKE("/bin/true"); + + fd = open(args[0], O_RDONLY | O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "open(%s) failed: %m", args[0]); + + r = fexecve_or_execve(fd, args[0], args, NULL); + assert_se(r < 0); + return log_error_errno(r, "fexecve_or_execve(%s) failed: %m", args[0]); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/test/test-exit-status.c b/src/test/test-exit-status.c new file mode 100644 index 0000000..86d3976 --- /dev/null +++ b/src/test/test-exit-status.c @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "exit-status.h" +#include "string-util.h" +#include "tests.h" + +TEST(exit_status_to_string) { + for (int i = -1; i <= 256; i++) { + const char *s, *class; + + s = exit_status_to_string(i, EXIT_STATUS_FULL); + class = exit_status_class(i); + log_info("%d: %s%s%s%s", + i, s ?: "-", + class ? " (" : "", strempty(class), class ? ")" : ""); + + if (s) + assert_se(exit_status_from_string(s) == i); + } +} + +TEST(exit_status_from_string) { + assert_se(exit_status_from_string("11") == 11); + assert_se(exit_status_from_string("-1") == -ERANGE); + assert_se(exit_status_from_string("256") == -ERANGE); + assert_se(exit_status_from_string("foo") == -EINVAL); + assert_se(exit_status_from_string("SUCCESS") == 0); + assert_se(exit_status_from_string("FAILURE") == 1); +} + +TEST(exit_status_NUMA_POLICY) { + assert_se(streq(exit_status_to_string(EXIT_NUMA_POLICY, EXIT_STATUS_FULL), "NUMA_POLICY")); + assert_se(streq(exit_status_to_string(EXIT_NUMA_POLICY, EXIT_STATUS_SYSTEMD), "NUMA_POLICY")); + assert_se(!exit_status_to_string(EXIT_NUMA_POLICY, EXIT_STATUS_BSD)); + assert_se(!exit_status_to_string(EXIT_NUMA_POLICY, EXIT_STATUS_LSB)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-extract-word.c b/src/test/test-extract-word.c new file mode 100644 index 0000000..6e12fbe --- /dev/null +++ b/src/test/test-extract-word.c @@ -0,0 +1,763 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> +#include <stdlib.h> + +#include "extract-word.h" +#include "log.h" +#include "string-util.h" +#include "tests.h" + +TEST(extract_first_word) { + const char *p, *original; + char *t; + + p = original = "foobar waldo"; + assert_se(extract_first_word(&p, &t, NULL, 0) > 0); + assert_se(streq(t, "foobar")); + free(t); + assert_se(p == original + 7); + + assert_se(extract_first_word(&p, &t, NULL, 0) > 0); + assert_se(streq(t, "waldo")); + free(t); + assert_se(isempty(p)); + + assert_se(extract_first_word(&p, &t, NULL, 0) == 0); + assert_se(!t); + assert_se(isempty(p)); + + p = original = "\"foobar\" \'waldo\'"; + assert_se(extract_first_word(&p, &t, NULL, 0) > 0); + assert_se(streq(t, "\"foobar\"")); + free(t); + assert_se(p == original + 9); + + assert_se(extract_first_word(&p, &t, NULL, 0) > 0); + assert_se(streq(t, "\'waldo\'")); + free(t); + assert_se(isempty(p)); + + assert_se(extract_first_word(&p, &t, NULL, 0) == 0); + assert_se(!t); + assert_se(isempty(p)); + + p = original = "\"foobar\" \'waldo\'"; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE) > 0); + assert_se(streq(t, "foobar")); + free(t); + assert_se(p == original + 9); + + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE) > 0); + assert_se(streq(t, "waldo")); + free(t); + assert_se(isempty(p)); + + assert_se(extract_first_word(&p, &t, NULL, 0) == 0); + assert_se(!t); + assert_se(isempty(p)); + + p = original = "\""; + assert_se(extract_first_word(&p, &t, NULL, 0) == 1); + assert_se(streq(t, "\"")); + free(t); + assert_se(isempty(p)); + + p = original = "\""; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE) == -EINVAL); + assert_se(p == original + 1); + + p = original = "\'"; + assert_se(extract_first_word(&p, &t, NULL, 0) == 1); + assert_se(streq(t, "\'")); + free(t); + assert_se(isempty(p)); + + p = original = "\'"; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE) == -EINVAL); + assert_se(p == original + 1); + + p = original = "\'fooo"; + assert_se(extract_first_word(&p, &t, NULL, 0) == 1); + assert_se(streq(t, "\'fooo")); + free(t); + assert_se(isempty(p)); + + p = original = "KEY=val \"KEY2=val with space\" \"KEY3=val with \\\"quotation\\\"\""; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE) == 1); + assert_se(streq(t, "KEY=val")); + free(t); + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE) == 1); + assert_se(streq(t, "KEY2=val with space")); + free(t); + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE) == 1); + assert_se(streq(t, "KEY3=val with \"quotation\"")); + free(t); + assert_se(isempty(p)); + + p = original = "KEY=val \"KEY2=val space\" \"KEY3=val with \\\"quotation\\\"\""; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_RETAIN_ESCAPE) == 1); + assert_se(streq(t, "KEY=val")); + free(t); + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_RETAIN_ESCAPE) == 1); + assert_se(streq(t, "\"KEY2=val")); + free(t); + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_RETAIN_ESCAPE) == 1); + assert_se(streq(t, "space\"")); + free(t); + assert_se(startswith(p, "\"KEY3=")); + + p = original = "\'fooo"; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE) == -EINVAL); + assert_se(p == original + 5); + + p = original = "\'fooo"; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX) > 0); + assert_se(streq(t, "fooo")); + free(t); + assert_se(isempty(p)); + + p = original = "\"fooo"; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX) > 0); + assert_se(streq(t, "fooo")); + free(t); + assert_se(isempty(p)); + + p = original = "yay\'foo\'bar"; + assert_se(extract_first_word(&p, &t, NULL, 0) > 0); + assert_se(streq(t, "yay\'foo\'bar")); + free(t); + assert_se(isempty(p)); + + p = original = "yay\'foo\'bar"; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE) > 0); + assert_se(streq(t, "yayfoobar")); + free(t); + assert_se(isempty(p)); + + p = original = " foobar "; + assert_se(extract_first_word(&p, &t, NULL, 0) > 0); + assert_se(streq(t, "foobar")); + free(t); + assert_se(isempty(p)); + + p = original = " foo\\ba\\x6ar "; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE) > 0); + assert_se(streq(t, "foo\ba\x6ar")); + free(t); + assert_se(isempty(p)); + + p = original = " foo\\ba\\x6ar "; + assert_se(extract_first_word(&p, &t, NULL, 0) > 0); + assert_se(streq(t, "foobax6ar")); + free(t); + assert_se(isempty(p)); + + p = original = " f\\u00f6o \"pi\\U0001F4A9le\" "; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE) > 0); + assert_se(streq(t, "föo")); + free(t); + assert_se(p == original + 13); + + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE) > 0); + assert_se(streq(t, "pi\360\237\222\251le")); + free(t); + assert_se(isempty(p)); + + p = original = "fooo\\"; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_RELAX) > 0); + assert_se(streq(t, "fooo")); + free(t); + assert_se(isempty(p)); + + p = original = "fooo\\"; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNESCAPE_RELAX) > 0); + assert_se(streq(t, "fooo\\")); + free(t); + assert_se(isempty(p)); + + p = original = "fooo\\"; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNESCAPE_RELAX|EXTRACT_RELAX) > 0); + assert_se(streq(t, "fooo\\")); + free(t); + assert_se(isempty(p)); + + p = original = "fooo\\"; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_RELAX) > 0); + assert_se(streq(t, "fooo\\")); + free(t); + assert_se(isempty(p)); + + p = original = "\"foo\\"; + assert_se(extract_first_word(&p, &t, NULL, 0) == -EINVAL); + assert_se(p == original + 5); + + p = original = "\"foo\\"; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX) > 0); + assert_se(streq(t, "foo")); + free(t); + assert_se(isempty(p)); + + p = original = "foo::bar"; + assert_se(extract_first_word(&p, &t, ":", 0) == 1); + assert_se(streq(t, "foo")); + free(t); + assert_se(p == original + 5); + + assert_se(extract_first_word(&p, &t, ":", 0) == 1); + assert_se(streq(t, "bar")); + free(t); + assert_se(isempty(p)); + + assert_se(extract_first_word(&p, &t, ":", 0) == 0); + assert_se(!t); + assert_se(isempty(p)); + + p = original = "foo\\:bar::waldo"; + assert_se(extract_first_word(&p, &t, ":", 0) == 1); + assert_se(streq(t, "foo:bar")); + free(t); + assert_se(p == original + 10); + + assert_se(extract_first_word(&p, &t, ":", 0) == 1); + assert_se(streq(t, "waldo")); + free(t); + assert_se(isempty(p)); + + assert_se(extract_first_word(&p, &t, ":", 0) == 0); + assert_se(!t); + assert_se(isempty(p)); + + p = original = "\"foo\\"; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_UNESCAPE_RELAX) == -EINVAL); + assert_se(p == original + 5); + + p = original = "\"foo\\"; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_UNESCAPE_RELAX|EXTRACT_RELAX) > 0); + assert_se(streq(t, "foo\\")); + free(t); + assert_se(isempty(p)); + + p = original = "\"foo\\"; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_RELAX|EXTRACT_RELAX) > 0); + assert_se(streq(t, "foo\\")); + free(t); + assert_se(isempty(p)); + + p = original = "fooo\\ bar quux"; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_RELAX) > 0); + assert_se(streq(t, "fooo bar")); + free(t); + assert_se(p == original + 10); + + p = original = "fooo\\ bar quux"; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNESCAPE_RELAX) > 0); + assert_se(streq(t, "fooo bar")); + free(t); + assert_se(p == original + 10); + + p = original = "fooo\\ bar quux"; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNESCAPE_RELAX|EXTRACT_RELAX) > 0); + assert_se(streq(t, "fooo bar")); + free(t); + assert_se(p == original + 10); + + p = original = "fooo\\ bar quux"; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE) == -EINVAL); + assert_se(p == original + 5); + + p = original = "fooo\\ bar quux"; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_RELAX) > 0); + assert_se(streq(t, "fooo\\ bar")); + free(t); + assert_se(p == original + 10); + + p = original = "\\w+@\\K[\\d.]+"; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE) == -EINVAL); + assert_se(p == original + 1); + + p = original = "\\w+@\\K[\\d.]+"; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_RELAX) > 0); + assert_se(streq(t, "\\w+@\\K[\\d.]+")); + free(t); + assert_se(isempty(p)); + + p = original = "\\w+\\b"; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_RELAX) > 0); + assert_se(streq(t, "\\w+\b")); + free(t); + assert_se(isempty(p)); + + p = original = "-N ''"; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE) > 0); + assert_se(streq(t, "-N")); + free(t); + assert_se(p == original + 3); + + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE) > 0); + assert_se(streq(t, "")); + free(t); + assert_se(isempty(p)); + + p = original = ":foo\\:bar::waldo:"; + assert_se(extract_first_word(&p, &t, ":", EXTRACT_DONT_COALESCE_SEPARATORS) == 1); + assert_se(t); + assert_se(streq(t, "")); + free(t); + assert_se(p == original + 1); + + assert_se(extract_first_word(&p, &t, ":", EXTRACT_DONT_COALESCE_SEPARATORS) == 1); + assert_se(streq(t, "foo:bar")); + free(t); + assert_se(p == original + 10); + + assert_se(extract_first_word(&p, &t, ":", EXTRACT_DONT_COALESCE_SEPARATORS) == 1); + assert_se(t); + assert_se(streq(t, "")); + free(t); + assert_se(p == original + 11); + + assert_se(extract_first_word(&p, &t, ":", EXTRACT_DONT_COALESCE_SEPARATORS) == 1); + assert_se(streq(t, "waldo")); + free(t); + assert_se(p == original + 17); + + assert_se(extract_first_word(&p, &t, ":", EXTRACT_DONT_COALESCE_SEPARATORS) == 1); + assert_se(streq(t, "")); + free(t); + assert_se(p == NULL); + + assert_se(extract_first_word(&p, &t, ":", EXTRACT_DONT_COALESCE_SEPARATORS) == 0); + assert_se(!t); + assert_se(!p); + + p = "foo\\xbar"; + assert_se(extract_first_word(&p, &t, NULL, 0) > 0); + assert_se(streq(t, "fooxbar")); + free(t); + assert_se(p == NULL); + + p = "foo\\xbar"; + assert_se(extract_first_word(&p, &t, NULL, EXTRACT_RETAIN_ESCAPE) > 0); + assert_se(streq(t, "foo\\xbar")); + free(t); + assert_se(p == NULL); + + p = "\\:"; + assert_se(extract_first_word(&p, &t, ":", EXTRACT_UNESCAPE_SEPARATORS) == 1); + assert_se(streq(t, ":")); + free(t); + assert_se(p == NULL); + + p = "a\\:b"; + assert_se(extract_first_word(&p, &t, ":", EXTRACT_UNESCAPE_SEPARATORS) == 1); + assert_se(streq(t, "a:b")); + free(t); + assert_se(p == NULL); + + p = "a\\ b:c"; + assert_se(extract_first_word(&p, &t, WHITESPACE ":", EXTRACT_UNESCAPE_SEPARATORS) == 1); + assert_se(streq(t, "a b")); + free(t); + assert_se(extract_first_word(&p, &t, WHITESPACE ":", EXTRACT_UNESCAPE_SEPARATORS) == 1); + assert_se(streq(t, "c")); + free(t); + assert_se(p == NULL); + + p = "a\\ b:c\\x"; + assert_se(extract_first_word(&p, &t, ":", EXTRACT_UNESCAPE_SEPARATORS) == -EINVAL); + + p = "a\\\\ b:c\\\\x"; + assert_se(extract_first_word(&p, &t, ":", EXTRACT_UNESCAPE_SEPARATORS) == 1); + assert_se(streq(t, "a\\ b")); + free(t); + assert_se(extract_first_word(&p, &t, ":", EXTRACT_UNESCAPE_SEPARATORS) == 1); + assert_se(streq(t, "c\\x")); + free(t); + assert_se(p == NULL); + + p = "\\:"; + assert_se(extract_first_word(&p, &t, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS) == 1); + assert_se(streq(t, ":")); + free(t); + assert_se(p == NULL); + + p = "a\\:b"; + assert_se(extract_first_word(&p, &t, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS) == 1); + assert_se(streq(t, "a:b")); + free(t); + assert_se(p == NULL); + + p = "a\\ b:c"; + assert_se(extract_first_word(&p, &t, WHITESPACE ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS) == 1); + assert_se(streq(t, "a b")); + free(t); + assert_se(extract_first_word(&p, &t, WHITESPACE ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS) == 1); + assert_se(streq(t, "c")); + free(t); + assert_se(p == NULL); + + p = "a\\ b:c\\x"; + assert_se(extract_first_word(&p, &t, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS) == -EINVAL); + + p = "a\\\\ b:c\\\\x"; + assert_se(extract_first_word(&p, &t, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS) == 1); + assert_se(streq(t, "a\\ b")); + free(t); + assert_se(extract_first_word(&p, &t, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS) == 1); + assert_se(streq(t, "c\\x")); + free(t); + assert_se(p == NULL); + + p = "\\:"; + assert_se(extract_first_word(&p, &t, ":", EXTRACT_CUNESCAPE) == -EINVAL); + + p = "a\\:b"; + assert_se(extract_first_word(&p, &t, ":", EXTRACT_CUNESCAPE) == -EINVAL); + assert_se(extract_first_word(&p, &t, ":", EXTRACT_CUNESCAPE) == 1); + assert_se(streq(t, "b")); + free(t); + + p = "a\\ b:c"; + assert_se(extract_first_word(&p, &t, WHITESPACE ":", EXTRACT_CUNESCAPE) == -EINVAL); + assert_se(extract_first_word(&p, &t, WHITESPACE ":", EXTRACT_CUNESCAPE) == 1); + assert_se(streq(t, "b")); + free(t); + assert_se(extract_first_word(&p, &t, WHITESPACE ":", EXTRACT_CUNESCAPE) == 1); + assert_se(streq(t, "c")); + free(t); + assert_se(p == NULL); + + p = original = "foobar=\"waldo\"maldo, baldo"; + assert_se(extract_first_word(&p, &t, "=\", ", 0) > 0); + assert_se(streq(t, "foobar")); + free(t); + assert_se(extract_first_word(&p, &t, "=\", ", 0) > 0); + assert_se(streq(t, "waldo")); + free(t); + assert_se(extract_first_word(&p, &t, "=\", ", 0) > 0); + assert_se(streq(t, "maldo")); + free(t); + assert_se(extract_first_word(&p, &t, "=\", ", 0) > 0); + assert_se(streq(t, "baldo")); + free(t); + + p = original = "mode=\"1777\",size=\"10%\",nr_inodes=\"400\"k,uid=\"496,,107\"520,gi\"\"'d=49610,'\"\"7520,context=\"system_u:object_r:svirt_sandbox_file_t:s0:c0,c1\""; + assert_se(extract_first_word(&p, &t, ",", EXTRACT_KEEP_QUOTE) > 0); + assert_se(streq(t, "mode=\"1777\"")); + free(t); + assert_se(extract_first_word(&p, &t, ",", EXTRACT_KEEP_QUOTE) > 0); + assert_se(streq(t, "size=\"10%\"")); + free(t); + assert_se(extract_first_word(&p, &t, ",", EXTRACT_KEEP_QUOTE) > 0); + assert_se(streq(t, "nr_inodes=\"400\"k")); + free(t); + assert_se(extract_first_word(&p, &t, ",", EXTRACT_KEEP_QUOTE) > 0); + assert_se(streq(t, "uid=\"496,,107\"520")); + free(t); + assert_se(extract_first_word(&p, &t, ",", EXTRACT_KEEP_QUOTE) > 0); + assert_se(streq(t, "gi\"\"'d=49610,'\"\"7520")); + free(t); + assert_se(extract_first_word(&p, &t, ",", EXTRACT_KEEP_QUOTE) > 0); + assert_se(streq(t, "context=\"system_u:object_r:svirt_sandbox_file_t:s0:c0,c1\"")); + free(t); + + p = original = "mode=\"1777\",size=\"10%\",nr_inodes=\"400\"k,uid=\"496,,107\"520,gi\"\"'d=49610,'\"\"7520,context=\"system_u:object_r:svirt_sandbox_file_t:s0:c0,c1\""; + assert_se(extract_first_word(&p, &t, ",", EXTRACT_UNQUOTE) > 0); + assert_se(streq(t, "mode=1777")); + free(t); + assert_se(extract_first_word(&p, &t, ",", EXTRACT_UNQUOTE) > 0); + assert_se(streq(t, "size=10%")); + free(t); + assert_se(extract_first_word(&p, &t, ",", EXTRACT_UNQUOTE) > 0); + assert_se(streq(t, "nr_inodes=400k")); + free(t); + assert_se(extract_first_word(&p, &t, ",", EXTRACT_UNQUOTE) > 0); + assert_se(streq(t, "uid=496,,107520")); + free(t); + assert_se(extract_first_word(&p, &t, ",", EXTRACT_UNQUOTE) > 0); + assert_se(streq(t, "gid=49610,7520")); + free(t); + assert_se(extract_first_word(&p, &t, ",", EXTRACT_UNQUOTE) > 0); + assert_se(streq(t, "context=system_u:object_r:svirt_sandbox_file_t:s0:c0,c1")); + free(t); + + p = "a:b"; + assert_se(extract_first_word(&p, &t, ":", EXTRACT_RETAIN_SEPARATORS) == 1); + assert_se(streq(t, "a")); + assert_se(streq(p, ":b")); + free(t); + assert_se(extract_first_word(&p, &t, ":", EXTRACT_RETAIN_SEPARATORS) == 1); + assert_se(streq(t, "b")); + free(t); + + p = "a>:b"; + assert_se(extract_first_word(&p, &t, ">:", EXTRACT_RETAIN_SEPARATORS) == 1); + assert_se(streq(t, "a")); + assert_se(streq(p, ">:b")); + free(t); + assert_se(extract_first_word(&p, &t, ">:", EXTRACT_RETAIN_SEPARATORS) == 1); + assert_se(streq(t, "b")); + free(t); + + p = "a>:b"; + assert_se(extract_first_word(&p, &t, ">:", EXTRACT_RETAIN_SEPARATORS|EXTRACT_DONT_COALESCE_SEPARATORS) == 1); + assert_se(streq(t, "a")); + assert_se(streq(p, ">:b")); + free(t); + assert_se(extract_first_word(&p, &t, ">:", EXTRACT_RETAIN_SEPARATORS|EXTRACT_DONT_COALESCE_SEPARATORS) == 1); + assert_se(streq(t, "")); + assert_se(streq(p, ">:b")); + free(t); + + p = "a\\:b"; + assert_se(extract_first_word(&p, &t, ":", EXTRACT_RETAIN_SEPARATORS|EXTRACT_RETAIN_ESCAPE) == 1); + assert_se(streq(t, "a\\")); + assert_se(streq(p, ":b")); + free(t); + + p = "a\\:b"; + assert_se(extract_first_word(&p, &t, ":", EXTRACT_RETAIN_SEPARATORS) == 1); + assert_se(streq(t, "a:b")); + assert_se(!p); + free(t); + + p = "a\\:b"; + assert_se(extract_first_word(&p, &t, ":", EXTRACT_RETAIN_SEPARATORS|EXTRACT_UNESCAPE_SEPARATORS) == 1); + assert_se(streq(t, "a:b")); + assert_se(!p); + free(t); + + p = "a\\:a:b"; + assert_se(extract_first_word(&p, &t, ":", EXTRACT_RETAIN_SEPARATORS|EXTRACT_UNESCAPE_SEPARATORS) == 1); + assert_se(streq(t, "a:a")); + assert_se(streq(p, ":b")); + free(t); +} + +TEST(extract_first_word_and_warn) { + const char *p, *original; + char *t; + + p = original = "foobar waldo"; + assert_se(extract_first_word_and_warn(&p, &t, NULL, 0, NULL, "fake", 1, original) > 0); + assert_se(streq(t, "foobar")); + free(t); + assert_se(p == original + 7); + + assert_se(extract_first_word_and_warn(&p, &t, NULL, 0, NULL, "fake", 1, original) > 0); + assert_se(streq(t, "waldo")); + free(t); + assert_se(isempty(p)); + + assert_se(extract_first_word_and_warn(&p, &t, NULL, 0, NULL, "fake", 1, original) == 0); + assert_se(!t); + assert_se(isempty(p)); + + p = original = "\"foobar\" \'waldo\'"; + assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_UNQUOTE, NULL, "fake", 1, original) > 0); + assert_se(streq(t, "foobar")); + free(t); + assert_se(p == original + 9); + + assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_UNQUOTE, NULL, "fake", 1, original) > 0); + assert_se(streq(t, "waldo")); + free(t); + assert_se(isempty(p)); + + assert_se(extract_first_word_and_warn(&p, &t, NULL, 0, NULL, "fake", 1, original) == 0); + assert_se(!t); + assert_se(isempty(p)); + + p = original = "\""; + assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_UNQUOTE, NULL, "fake", 1, original) == -EINVAL); + assert_se(p == original + 1); + + p = original = "\'"; + assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_UNQUOTE, NULL, "fake", 1, original) == -EINVAL); + assert_se(p == original + 1); + + p = original = "\'fooo"; + assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_UNQUOTE, NULL, "fake", 1, original) == -EINVAL); + assert_se(p == original + 5); + + p = original = "\'fooo"; + assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX, NULL, "fake", 1, original) > 0); + assert_se(streq(t, "fooo")); + free(t); + assert_se(isempty(p)); + + p = original = " foo\\ba\\x6ar "; + assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_CUNESCAPE, NULL, "fake", 1, original) > 0); + assert_se(streq(t, "foo\ba\x6ar")); + free(t); + assert_se(isempty(p)); + + p = original = " foo\\ba\\x6ar "; + assert_se(extract_first_word_and_warn(&p, &t, NULL, 0, NULL, "fake", 1, original) > 0); + assert_se(streq(t, "foobax6ar")); + free(t); + assert_se(isempty(p)); + + p = original = " f\\u00f6o \"pi\\U0001F4A9le\" "; + assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_CUNESCAPE, NULL, "fake", 1, original) > 0); + assert_se(streq(t, "föo")); + free(t); + assert_se(p == original + 13); + + assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE, NULL, "fake", 1, original) > 0); + assert_se(streq(t, "pi\360\237\222\251le")); + free(t); + assert_se(isempty(p)); + + p = original = "fooo\\"; + assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_RELAX, NULL, "fake", 1, original) > 0); + assert_se(streq(t, "fooo")); + free(t); + assert_se(isempty(p)); + + p = original = "fooo\\"; + assert_se(extract_first_word_and_warn(&p, &t, NULL, 0, NULL, "fake", 1, original) > 0); + assert_se(streq(t, "fooo\\")); + free(t); + assert_se(isempty(p)); + + p = original = "fooo\\"; + assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_CUNESCAPE, NULL, "fake", 1, original) > 0); + assert_se(streq(t, "fooo\\")); + free(t); + assert_se(isempty(p)); + + p = original = "\"foo\\"; + assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_UNQUOTE, NULL, "fake", 1, original) == -EINVAL); + assert_se(p == original + 5); + + p = original = "\"foo\\"; + assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX, NULL, "fake", 1, original) > 0); + assert_se(streq(t, "foo")); + free(t); + assert_se(isempty(p)); + + p = original = "\"foo\\"; + assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE, NULL, "fake", 1, original) == -EINVAL); + assert_se(p == original + 5); + + p = original = "\"foo\\"; + assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE|EXTRACT_RELAX, NULL, "fake", 1, original) > 0); + assert_se(streq(t, "foo")); + free(t); + assert_se(isempty(p)); + + p = original = "fooo\\ bar quux"; + assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_RELAX, NULL, "fake", 1, original) > 0); + assert_se(streq(t, "fooo bar")); + free(t); + assert_se(p == original + 10); + + p = original = "fooo\\ bar quux"; + assert_se(extract_first_word_and_warn(&p, &t, NULL, 0, NULL, "fake", 1, original) > 0); + assert_se(streq(t, "fooo bar")); + free(t); + assert_se(p == original + 10); + + p = original = "fooo\\ bar quux"; + assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_CUNESCAPE, NULL, "fake", 1, original) > 0); + assert_se(streq(t, "fooo\\ bar")); + free(t); + assert_se(p == original + 10); + + p = original = "\\w+@\\K[\\d.]+"; + assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_CUNESCAPE, NULL, "fake", 1, original) > 0); + assert_se(streq(t, "\\w+@\\K[\\d.]+")); + free(t); + assert_se(isempty(p)); + + p = original = "\\w+\\b"; + assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_CUNESCAPE, NULL, "fake", 1, original) > 0); + assert_se(streq(t, "\\w+\b")); + free(t); + assert_se(isempty(p)); +} + +TEST(extract_many_words) { + const char *p, *original; + char *a, *b, *c, *d, *e, *f; + + p = original = "foobar waldi piep"; + assert_se(extract_many_words(&p, NULL, 0, &a, &b, &c, NULL) == 3); + assert_se(isempty(p)); + assert_se(streq_ptr(a, "foobar")); + assert_se(streq_ptr(b, "waldi")); + assert_se(streq_ptr(c, "piep")); + free(a); + free(b); + free(c); + + p = original = "foobar:waldi:piep ba1:ba2"; + assert_se(extract_many_words(&p, ":" WHITESPACE, 0, &a, &b, &c, NULL) == 3); + assert_se(!isempty(p)); + assert_se(streq_ptr(a, "foobar")); + assert_se(streq_ptr(b, "waldi")); + assert_se(streq_ptr(c, "piep")); + assert_se(extract_many_words(&p, ":" WHITESPACE, 0, &d, &e, &f, NULL) == 2); + assert_se(isempty(p)); + assert_se(streq_ptr(d, "ba1")); + assert_se(streq_ptr(e, "ba2")); + assert_se(isempty(f)); + free(a); + free(b); + free(c); + free(d); + free(e); + free(f); + + p = original = "'foobar' wa\"ld\"i "; + assert_se(extract_many_words(&p, NULL, 0, &a, &b, &c, NULL) == 2); + assert_se(isempty(p)); + assert_se(streq_ptr(a, "'foobar'")); + assert_se(streq_ptr(b, "wa\"ld\"i")); + assert_se(streq_ptr(c, NULL)); + free(a); + free(b); + + p = original = "'foobar' wa\"ld\"i "; + assert_se(extract_many_words(&p, NULL, EXTRACT_UNQUOTE, &a, &b, &c, NULL) == 2); + assert_se(isempty(p)); + assert_se(streq_ptr(a, "foobar")); + assert_se(streq_ptr(b, "waldi")); + assert_se(streq_ptr(c, NULL)); + free(a); + free(b); + + p = original = ""; + assert_se(extract_many_words(&p, NULL, 0, &a, &b, &c, NULL) == 0); + assert_se(isempty(p)); + assert_se(streq_ptr(a, NULL)); + assert_se(streq_ptr(b, NULL)); + assert_se(streq_ptr(c, NULL)); + + p = original = " "; + assert_se(extract_many_words(&p, NULL, 0, &a, &b, &c, NULL) == 0); + assert_se(isempty(p)); + assert_se(streq_ptr(a, NULL)); + assert_se(streq_ptr(b, NULL)); + assert_se(streq_ptr(c, NULL)); + + p = original = "foobar"; + assert_se(extract_many_words(&p, NULL, 0, NULL) == 0); + assert_se(p == original); + + p = original = "foobar waldi"; + assert_se(extract_many_words(&p, NULL, 0, &a, NULL) == 1); + assert_se(p == original+7); + assert_se(streq_ptr(a, "foobar")); + free(a); + + p = original = " foobar "; + assert_se(extract_many_words(&p, NULL, 0, &a, NULL) == 1); + assert_se(isempty(p)); + assert_se(streq_ptr(a, "foobar")); + free(a); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-fd-util.c b/src/test/test-fd-util.c new file mode 100644 index 0000000..021d4b4 --- /dev/null +++ b/src/test/test-fd-util.c @@ -0,0 +1,765 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <fcntl.h> +#include <sys/eventfd.h> +#include <sys/mount.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "data-fd-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "macro.h" +#include "memory-util.h" +#include "missing_syscall.h" +#include "mkdir.h" +#include "mount-util.h" +#include "namespace-util.h" +#include "path-util.h" +#include "process-util.h" +#include "random-util.h" +#include "rlimit-util.h" +#include "rm-rf.h" +#include "seccomp-util.h" +#include "serialize.h" +#include "stat-util.h" +#include "string-util.h" +#include "tests.h" +#include "tmpfile-util.h" + +TEST(close_many) { + int fds[3]; + _cleanup_(unlink_tempfilep) char name0[] = "/tmp/test-close-many.XXXXXX"; + _cleanup_(unlink_tempfilep) char name1[] = "/tmp/test-close-many.XXXXXX"; + _cleanup_(unlink_tempfilep) char name2[] = "/tmp/test-close-many.XXXXXX"; + + fds[0] = mkostemp_safe(name0); + fds[1] = mkostemp_safe(name1); + fds[2] = mkostemp_safe(name2); + + close_many(fds, 2); + + assert_se(fcntl(fds[0], F_GETFD) == -1); + assert_se(fcntl(fds[1], F_GETFD) == -1); + assert_se(fcntl(fds[2], F_GETFD) >= 0); + + safe_close(fds[2]); +} + +TEST(close_nointr) { + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-test-close_nointr.XXXXXX"; + int fd; + + fd = mkostemp_safe(name); + assert_se(fd >= 0); + assert_se(close_nointr(fd) >= 0); + assert_se(close_nointr(fd) < 0); +} + +TEST(same_fd) { + _cleanup_close_pair_ int p[2]; + _cleanup_close_ int a, b, c; + + assert_se(pipe2(p, O_CLOEXEC) >= 0); + assert_se((a = fcntl(p[0], F_DUPFD, 3)) >= 0); + assert_se((b = open("/dev/null", O_RDONLY|O_CLOEXEC)) >= 0); + assert_se((c = fcntl(a, F_DUPFD, 3)) >= 0); + + assert_se(same_fd(p[0], p[0]) > 0); + assert_se(same_fd(p[1], p[1]) > 0); + assert_se(same_fd(a, a) > 0); + assert_se(same_fd(b, b) > 0); + + assert_se(same_fd(a, p[0]) > 0); + assert_se(same_fd(p[0], a) > 0); + assert_se(same_fd(c, p[0]) > 0); + assert_se(same_fd(p[0], c) > 0); + assert_se(same_fd(a, c) > 0); + assert_se(same_fd(c, a) > 0); + + assert_se(same_fd(p[0], p[1]) == 0); + assert_se(same_fd(p[1], p[0]) == 0); + assert_se(same_fd(p[0], b) == 0); + assert_se(same_fd(b, p[0]) == 0); + assert_se(same_fd(p[1], a) == 0); + assert_se(same_fd(a, p[1]) == 0); + assert_se(same_fd(p[1], b) == 0); + assert_se(same_fd(b, p[1]) == 0); + + assert_se(same_fd(a, b) == 0); + assert_se(same_fd(b, a) == 0); +} + +TEST(open_serialization_fd) { + _cleanup_close_ int fd = -EBADF; + + fd = open_serialization_fd("test"); + assert_se(fd >= 0); + + assert_se(write(fd, "test\n", 5) == 5); +} + +TEST(open_serialization_file) { + _cleanup_fclose_ FILE *f = NULL; + int r; + + r = open_serialization_file("test", &f); + assert_se(r >= 0); + assert_se(f); + + assert_se(fwrite("test\n", 1, 5, f) == 5); +} + +TEST(fd_move_above_stdio) { + int original_stdin, new_fd; + + original_stdin = fcntl(0, F_DUPFD, 3); + assert_se(original_stdin >= 3); + assert_se(close_nointr(0) != EBADF); + + new_fd = open("/dev/null", O_RDONLY); + assert_se(new_fd == 0); + + new_fd = fd_move_above_stdio(new_fd); + assert_se(new_fd >= 3); + + assert_se(dup(original_stdin) == 0); + assert_se(close_nointr(original_stdin) != EBADF); + assert_se(close_nointr(new_fd) != EBADF); +} + +TEST(rearrange_stdio) { + pid_t pid; + int r; + + r = safe_fork("rearrange", FORK_WAIT|FORK_LOG, &pid); + assert_se(r >= 0); + + if (r == 0) { + _cleanup_free_ char *path = NULL; + char buffer[10]; + + /* Child */ + + safe_close(STDERR_FILENO); /* Let's close an fd < 2, to make it more interesting */ + + assert_se(rearrange_stdio(-EBADF, -EBADF, -EBADF) >= 0); + + assert_se(fd_get_path(STDIN_FILENO, &path) >= 0); + assert_se(path_equal(path, "/dev/null")); + path = mfree(path); + + assert_se(fd_get_path(STDOUT_FILENO, &path) >= 0); + assert_se(path_equal(path, "/dev/null")); + path = mfree(path); + + assert_se(fd_get_path(STDOUT_FILENO, &path) >= 0); + assert_se(path_equal(path, "/dev/null")); + path = mfree(path); + + safe_close(STDIN_FILENO); + safe_close(STDOUT_FILENO); + safe_close(STDERR_FILENO); + + { + int pair[2]; + assert_se(pipe(pair) >= 0); + assert_se(pair[0] == 0); + assert_se(pair[1] == 1); + assert_se(fd_move_above_stdio(0) == 3); + } + assert_se(open("/dev/full", O_WRONLY|O_CLOEXEC) == 0); + assert_se(acquire_data_fd("foobar", 6, 0) == 2); + + assert_se(rearrange_stdio(2, 0, 1) >= 0); + + assert_se(write(1, "x", 1) < 0 && errno == ENOSPC); + assert_se(write(2, "z", 1) == 1); + assert_se(read(3, buffer, sizeof(buffer)) == 1); + assert_se(buffer[0] == 'z'); + assert_se(read(0, buffer, sizeof(buffer)) == 6); + assert_se(memcmp(buffer, "foobar", 6) == 0); + + assert_se(rearrange_stdio(-EBADF, 1, 2) >= 0); + assert_se(write(1, "a", 1) < 0 && errno == ENOSPC); + assert_se(write(2, "y", 1) == 1); + assert_se(read(3, buffer, sizeof(buffer)) == 1); + assert_se(buffer[0] == 'y'); + + assert_se(fd_get_path(0, &path) >= 0); + assert_se(path_equal(path, "/dev/null")); + path = mfree(path); + + _exit(EXIT_SUCCESS); + } +} + +TEST(read_nr_open) { + log_info("nr-open: %i", read_nr_open()); +} + +static size_t validate_fds( + bool opened, + const int *fds, + size_t n_fds) { + + size_t c = 0; + + /* Validates that fds in the specified array are one of the following three: + * + * 1. < 0 (test is skipped) or + * 2. opened (if 'opened' param is true) or + * 3. closed (if 'opened' param is false) + */ + + for (size_t i = 0; i < n_fds; i++) { + if (fds[i] < 0) + continue; + + if (opened) + assert_se(fcntl(fds[i], F_GETFD) >= 0); + else + assert_se(fcntl(fds[i], F_GETFD) < 0 && errno == EBADF); + + c++; + } + + return c; /* Return number of fds >= 0 in the array */ +} + +static void test_close_all_fds_inner(void) { + _cleanup_free_ int *fds = NULL, *keep = NULL; + size_t n_fds, n_keep; + int max_fd; + + log_info("/* %s */", __func__); + + rlimit_nofile_bump(-1); + + max_fd = get_max_fd(); + assert_se(max_fd > 10); + + if (max_fd > 7000) { + /* If the worst fallback is activated we need to iterate through all possible fds, hence, + * let's lower the limit a small bit, so that we don't run for too long. Yes, this undoes the + * rlimit_nofile_bump() call above partially. */ + + (void) setrlimit_closest(RLIMIT_NOFILE, &(struct rlimit) { 7000, 7000 }); + max_fd = 7000; + } + + /* Try to use 5000 fds, but when we can't bump the rlimit to make that happen use the whole limit minus 10 */ + n_fds = MIN(((size_t) max_fd & ~1U) - 10U, 5000U); + assert_se((n_fds & 1U) == 0U); /* make sure even number of fds */ + + /* Allocate the determined number of fds, always two at a time */ + assert_se(fds = new(int, n_fds)); + for (size_t i = 0; i < n_fds; i += 2) + assert_se(pipe2(fds + i, O_CLOEXEC) >= 0); + + /* Validate this worked */ + assert_se(validate_fds(true, fds, n_fds) == n_fds); + + /* Randomized number of fds to keep, but at most every second */ + n_keep = (random_u64() % (n_fds / 2)); + + /* Now randomly select a number of fds from the array above to keep */ + assert_se(keep = new(int, n_keep)); + for (size_t k = 0; k < n_keep; k++) { + for (;;) { + size_t p; + + p = random_u64() % n_fds; + if (fds[p] >= 0) { + keep[k] = TAKE_FD(fds[p]); + break; + } + } + } + + /* Check that all fds from both arrays are still open, and test how many in each are >= 0 */ + assert_se(validate_fds(true, fds, n_fds) == n_fds - n_keep); + assert_se(validate_fds(true, keep, n_keep) == n_keep); + + /* Close logging fd first, so that we don't confuse it by closing its fd */ + log_close(); + log_set_open_when_needed(true); + log_settle_target(); + + /* Close all but the ones to keep */ + assert_se(close_all_fds(keep, n_keep) >= 0); + + assert_se(validate_fds(false, fds, n_fds) == n_fds - n_keep); + assert_se(validate_fds(true, keep, n_keep) == n_keep); + + /* Close everything else too! */ + assert_se(close_all_fds(NULL, 0) >= 0); + + assert_se(validate_fds(false, fds, n_fds) == n_fds - n_keep); + assert_se(validate_fds(false, keep, n_keep) == n_keep); + + log_set_open_when_needed(false); + log_open(); +} + +static int seccomp_prohibit_close_range(void) { +#if HAVE_SECCOMP && defined(__SNR_close_range) + _cleanup_(seccomp_releasep) scmp_filter_ctx seccomp = NULL; + int r; + + r = seccomp_init_for_arch(&seccomp, SCMP_ARCH_NATIVE, SCMP_ACT_ALLOW); + if (r < 0) + return log_warning_errno(r, "Failed to acquire seccomp context, ignoring: %m"); + + r = seccomp_rule_add_exact( + seccomp, + SCMP_ACT_ERRNO(EPERM), + SCMP_SYS(close_range), + 0); + if (r < 0) + return log_warning_errno(r, "Failed to add close_range() rule, ignoring: %m"); + + r = seccomp_load(seccomp); + if (r < 0) + return log_warning_errno(r, "Failed to apply close_range() restrictions, ignoring: %m"); + + return 0; +#else + return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Seccomp support or close_range() syscall definition not available."); +#endif +} + +TEST(close_all_fds) { + int r; + + /* Runs the test four times. Once as is. Once with close_range() syscall blocked via seccomp, once + * with /proc/ overmounted, and once with the combination of both. This should trigger all fallbacks + * in the close_range_all() function. */ + + r = safe_fork("(caf-plain)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL); + if (r == 0) { + test_close_all_fds_inner(); + _exit(EXIT_SUCCESS); + } + assert_se(r >= 0); + + if (geteuid() != 0) + return (void) log_tests_skipped("Lacking privileges for test with close_range() blocked and /proc/ overmounted"); + + r = safe_fork("(caf-noproc)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, NULL); + if (r == 0) { + r = mount_nofollow_verbose(LOG_WARNING, "tmpfs", "/proc", "tmpfs", 0, NULL); + if (r < 0) + log_notice("Overmounting /proc/ didn't work, skipping close_all_fds() with masked /proc/."); + else + test_close_all_fds_inner(); + _exit(EXIT_SUCCESS); + } + assert_se(r >= 0); + + if (!is_seccomp_available()) + return (void) log_tests_skipped("Seccomp not available"); + + r = safe_fork("(caf-seccomp)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL); + if (r == 0) { + r = seccomp_prohibit_close_range(); + if (r < 0) + log_notice("Applying seccomp filter didn't work, skipping close_all_fds() test with masked close_range()."); + else + test_close_all_fds_inner(); + + _exit(EXIT_SUCCESS); + } + assert_se(r >= 0); + + r = safe_fork("(caf-scnp)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, NULL); + if (r == 0) { + r = seccomp_prohibit_close_range(); + if (r < 0) + log_notice("Applying seccomp filter didn't work, skipping close_all_fds() test with masked close_range()."); + else { + r = mount_nofollow_verbose(LOG_WARNING, "tmpfs", "/proc", "tmpfs", 0, NULL); + if (r < 0) + log_notice("Overmounting /proc/ didn't work, skipping close_all_fds() with masked /proc/."); + else + test_close_all_fds_inner(); + } + + test_close_all_fds_inner(); + _exit(EXIT_SUCCESS); + } + assert_se(r >= 0); +} + +TEST(format_proc_fd_path) { + assert_se(streq_ptr(FORMAT_PROC_FD_PATH(0), "/proc/self/fd/0")); + assert_se(streq_ptr(FORMAT_PROC_FD_PATH(1), "/proc/self/fd/1")); + assert_se(streq_ptr(FORMAT_PROC_FD_PATH(2), "/proc/self/fd/2")); + assert_se(streq_ptr(FORMAT_PROC_FD_PATH(3), "/proc/self/fd/3")); + assert_se(streq_ptr(FORMAT_PROC_FD_PATH(2147483647), "/proc/self/fd/2147483647")); +} + +TEST(fd_reopen) { + _cleanup_close_ int fd1 = -EBADF, fd2 = -EBADF; + struct stat st1, st2; + int fl; + + /* Test this with a directory */ + fd1 = open("/proc", O_DIRECTORY|O_PATH|O_CLOEXEC); + assert_se(fd1 >= 0); + + assert_se(fstat(fd1, &st1) >= 0); + assert_se(S_ISDIR(st1.st_mode)); + + fl = fcntl(fd1, F_GETFL); + assert_se(fl >= 0); + assert_se(FLAGS_SET(fl, O_DIRECTORY)); + assert_se(FLAGS_SET(fl, O_PATH)); + + /* fd_reopen() with O_NOFOLLOW will systematically fail, since it is implemented via a symlink in /proc/self/fd/ */ + assert_se(fd_reopen(fd1, O_RDONLY|O_CLOEXEC|O_NOFOLLOW) == -ELOOP); + assert_se(fd_reopen(fd1, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW) == -ELOOP); + + fd2 = fd_reopen(fd1, O_RDONLY|O_DIRECTORY|O_CLOEXEC); /* drop the O_PATH */ + assert_se(fd2 >= 0); + + assert_se(fstat(fd2, &st2) >= 0); + assert_se(S_ISDIR(st2.st_mode)); + assert_se(stat_inode_same(&st1, &st2)); + + fl = fcntl(fd2, F_GETFL); + assert_se(fl >= 0); + assert_se(FLAGS_SET(fl, O_DIRECTORY)); + assert_se(!FLAGS_SET(fl, O_PATH)); + + safe_close(fd1); + + fd1 = fd_reopen(fd2, O_DIRECTORY|O_PATH|O_CLOEXEC); /* reacquire the O_PATH */ + assert_se(fd1 >= 0); + + assert_se(fstat(fd1, &st1) >= 0); + assert_se(S_ISDIR(st1.st_mode)); + assert_se(stat_inode_same(&st1, &st2)); + + fl = fcntl(fd1, F_GETFL); + assert_se(fl >= 0); + assert_se(FLAGS_SET(fl, O_DIRECTORY)); + assert_se(FLAGS_SET(fl, O_PATH)); + + safe_close(fd1); + + /* And now, test this with a file. */ + fd1 = open("/proc/version", O_PATH|O_CLOEXEC); + assert_se(fd1 >= 0); + + assert_se(fstat(fd1, &st1) >= 0); + assert_se(S_ISREG(st1.st_mode)); + + fl = fcntl(fd1, F_GETFL); + assert_se(fl >= 0); + assert_se(!FLAGS_SET(fl, O_DIRECTORY)); + assert_se(FLAGS_SET(fl, O_PATH)); + + assert_se(fd_reopen(fd1, O_RDONLY|O_DIRECTORY|O_CLOEXEC) == -ENOTDIR); + fd2 = fd_reopen(fd1, O_RDONLY|O_CLOEXEC); /* drop the O_PATH */ + assert_se(fd2 >= 0); + + assert_se(fstat(fd2, &st2) >= 0); + assert_se(S_ISREG(st2.st_mode)); + assert_se(stat_inode_same(&st1, &st2)); + + fl = fcntl(fd2, F_GETFL); + assert_se(fl >= 0); + assert_se(!FLAGS_SET(fl, O_DIRECTORY)); + assert_se(!FLAGS_SET(fl, O_PATH)); + + safe_close(fd1); + + assert_se(fd_reopen(fd2, O_DIRECTORY|O_PATH|O_CLOEXEC) == -ENOTDIR); + fd1 = fd_reopen(fd2, O_PATH|O_CLOEXEC); /* reacquire the O_PATH */ + assert_se(fd1 >= 0); + + assert_se(fstat(fd1, &st1) >= 0); + assert_se(S_ISREG(st1.st_mode)); + assert_se(stat_inode_same(&st1, &st2)); + + fl = fcntl(fd1, F_GETFL); + assert_se(fl >= 0); + assert_se(!FLAGS_SET(fl, O_DIRECTORY)); + assert_se(FLAGS_SET(fl, O_PATH)); + + /* Also check the right error is generated if the fd is already closed */ + safe_close(fd1); + assert_se(fd_reopen(fd1, O_RDONLY|O_CLOEXEC) == -EBADF); + fd1 = -EBADF; + + /* Validate what happens if we reopen a symlink */ + fd1 = open("/proc/self", O_PATH|O_CLOEXEC|O_NOFOLLOW); + assert_se(fd1 >= 0); + assert_se(fstat(fd1, &st1) >= 0); + assert_se(S_ISLNK(st1.st_mode)); + + fd2 = fd_reopen(fd1, O_PATH|O_CLOEXEC); + assert_se(fd2 >= 0); + assert_se(fstat(fd2, &st2) >= 0); + assert_se(S_ISLNK(st2.st_mode)); + assert_se(stat_inode_same(&st1, &st2)); + fd2 = safe_close(fd2); + + /* So here's the thing: if we have an O_PATH fd to a symlink, we *cannot* convert it to a regular fd + * with that. i.e. you cannot have the VFS follow a symlink pinned via an O_PATH fd. */ + assert_se(fd_reopen(fd1, O_RDONLY|O_CLOEXEC) == -ELOOP); +} + +TEST(fd_reopen_condition) { + _cleanup_close_ int fd1 = -EBADF, fd3 = -EBADF; + int fd2, fl; + + /* Open without O_PATH */ + fd1 = open("/usr/", O_RDONLY|O_DIRECTORY|O_CLOEXEC); + assert_se(fd1 >= 0); + + fl = fcntl(fd1, F_GETFL); + assert_se(FLAGS_SET(fl, O_DIRECTORY)); + assert_se(!FLAGS_SET(fl, O_PATH)); + + fd2 = fd_reopen_condition(fd1, O_DIRECTORY, O_DIRECTORY|O_PATH, &fd3); + assert_se(fd2 == fd1); + assert_se(fd3 < 0); + + /* Switch on O_PATH */ + fd2 = fd_reopen_condition(fd1, O_DIRECTORY|O_PATH, O_DIRECTORY|O_PATH, &fd3); + assert_se(fd2 != fd1); + assert_se(fd3 == fd2); + + fl = fcntl(fd2, F_GETFL); + assert_se(FLAGS_SET(fl, O_DIRECTORY)); + assert_se(FLAGS_SET(fl, O_PATH)); + + close_and_replace(fd1, fd3); + + fd2 = fd_reopen_condition(fd1, O_DIRECTORY|O_PATH, O_DIRECTORY|O_PATH, &fd3); + assert_se(fd2 == fd1); + assert_se(fd3 < 0); + + /* Switch off O_PATH again */ + fd2 = fd_reopen_condition(fd1, O_DIRECTORY, O_DIRECTORY|O_PATH, &fd3); + assert_se(fd2 != fd1); + assert_se(fd3 == fd2); + + fl = fcntl(fd2, F_GETFL); + assert_se(FLAGS_SET(fl, O_DIRECTORY)); + assert_se(!FLAGS_SET(fl, O_PATH)); + + close_and_replace(fd1, fd3); + + fd2 = fd_reopen_condition(fd1, O_DIRECTORY, O_DIRECTORY|O_PATH, &fd3); + assert_se(fd2 == fd1); + assert_se(fd3 < 0); +} + +TEST(take_fd) { + _cleanup_close_ int fd1 = -EBADF, fd2 = -EBADF; + int array[2] = EBADF_PAIR, i = 0; + + assert_se(fd1 == -EBADF); + assert_se(fd2 == -EBADF); + + fd1 = eventfd(0, EFD_CLOEXEC); + assert_se(fd1 >= 0); + + fd2 = TAKE_FD(fd1); + assert_se(fd1 == -EBADF); + assert_se(fd2 >= 0); + + assert_se(array[0] == -EBADF); + assert_se(array[1] == -EBADF); + + array[0] = TAKE_FD(fd2); + assert_se(fd1 == -EBADF); + assert_se(fd2 == -EBADF); + assert_se(array[0] >= 0); + assert_se(array[1] == -EBADF); + + array[1] = TAKE_FD(array[i]); + assert_se(array[0] == -EBADF); + assert_se(array[1] >= 0); + + i = 1 - i; + array[0] = TAKE_FD(*(array + i)); + assert_se(array[0] >= 0); + assert_se(array[1] == -EBADF); + + i = 1 - i; + fd1 = TAKE_FD(array[i]); + assert_se(fd1 >= 0); + assert_se(array[0] == -EBADF); + assert_se(array[1] == -EBADF); +} + +TEST(dir_fd_is_root) { + _cleanup_close_ int fd = -EBADF; + int r; + + assert_se(dir_fd_is_root_or_cwd(AT_FDCWD) > 0); + + assert_se((fd = open("/", O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW)) >= 0); + assert_se(dir_fd_is_root(fd) > 0); + assert_se(dir_fd_is_root_or_cwd(fd) > 0); + + fd = safe_close(fd); + + assert_se((fd = open("/usr", O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW)) >= 0); + assert_se(dir_fd_is_root(fd) == 0); + assert_se(dir_fd_is_root_or_cwd(fd) == 0); + + r = detach_mount_namespace(); + if (r < 0) + return (void) log_tests_skipped_errno(r, "Failed to detach mount namespace"); + + _cleanup_(rm_rf_physical_and_freep) char *tmp = NULL; + _cleanup_free_ char *x = NULL, *y = NULL; + + assert_se(mkdtemp_malloc("/tmp/test-mkdir-XXXXXX", &tmp) >= 0); + assert_se(x = path_join(tmp, "x")); + assert_se(y = path_join(tmp, "x/y")); + assert_se(mkdir_p(y, 0755) >= 0); + assert_se(mount_nofollow_verbose(LOG_DEBUG, x, y, NULL, MS_BIND, NULL) >= 0); + + fd = safe_close(fd); + + assert_se((fd = open(tmp, O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW)) >= 0); + assert_se(dir_fd_is_root(fd) == 0); + assert_se(dir_fd_is_root_or_cwd(fd) == 0); + + fd = safe_close(fd); + + assert_se((fd = open(x, O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW)) >= 0); + assert_se(dir_fd_is_root(fd) == 0); + assert_se(dir_fd_is_root_or_cwd(fd) == 0); + + fd = safe_close(fd); + + assert_se((fd = open(y, O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW)) >= 0); + assert_se(dir_fd_is_root(fd) == 0); + assert_se(dir_fd_is_root_or_cwd(fd) == 0); +} + +TEST(fd_get_path) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int tfd = -EBADF, fd = -EBADF; + _cleanup_free_ char *p = NULL, *q = NULL, *saved_cwd = NULL; + + tfd = mkdtemp_open(NULL, O_PATH, &t); + assert_se(tfd >= 0); + assert_se(fd_get_path(tfd, &p) >= 0); + assert_se(streq(p, t)); + + p = mfree(p); + + assert_se(safe_getcwd(&saved_cwd) >= 0); + assert_se(chdir(t) >= 0); + + assert_se(fd_get_path(AT_FDCWD, &p) >= 0); + assert_se(streq(p, t)); + + p = mfree(p); + + assert_se(q = path_join(t, "regular")); + assert_se(touch(q) >= 0); + assert_se(mkdirat_parents(tfd, "subdir/symlink", 0755) >= 0); + assert_se(symlinkat("../regular", tfd, "subdir/symlink") >= 0); + assert_se(symlinkat("subdir", tfd, "symdir") >= 0); + + fd = openat(tfd, "regular", O_CLOEXEC|O_PATH); + assert_se(fd >= 0); + assert_se(fd_get_path(fd, &p) >= 0); + assert_se(streq(p, q)); + + p = mfree(p); + fd = safe_close(fd); + + fd = openat(AT_FDCWD, "regular", O_CLOEXEC|O_PATH); + assert_se(fd >= 0); + assert_se(fd_get_path(fd, &p) >= 0); + assert_se(streq(p, q)); + + p = mfree(p); + fd = safe_close(fd); + + fd = openat(tfd, "subdir/symlink", O_CLOEXEC|O_PATH); + assert_se(fd >= 0); + assert_se(fd_verify_regular(fd) >= 0); + assert_se(fd_get_path(fd, &p) >= 0); + assert_se(streq(p, q)); + + p = mfree(p); + fd = safe_close(fd); + + fd = openat(AT_FDCWD, "subdir/symlink", O_CLOEXEC|O_PATH); + assert_se(fd >= 0); + assert_se(fd_verify_regular(fd) >= 0); + assert_se(fd_get_path(fd, &p) >= 0); + assert_se(streq(p, q)); + + p = mfree(p); + fd = safe_close(fd); + + fd = openat(tfd, "symdir//./symlink", O_CLOEXEC|O_PATH); + assert_se(fd >= 0); + assert_se(fd_verify_regular(fd) >= 0); + assert_se(fd_get_path(fd, &p) >= 0); + assert_se(streq(p, q)); + + p = mfree(p); + fd = safe_close(fd); + + fd = openat(AT_FDCWD, "symdir//./symlink", O_CLOEXEC|O_PATH); + assert_se(fd >= 0); + assert_se(fd_verify_regular(fd) >= 0); + assert_se(fd_get_path(fd, &p) >= 0); + assert_se(streq(p, q)); + + p = mfree(p); + q = mfree(q); + fd = safe_close(fd); + + assert_se(q = path_join(t, "subdir/symlink")); + fd = openat(tfd, "subdir/symlink", O_CLOEXEC|O_PATH|O_NOFOLLOW); + assert_se(fd >= 0); + assert_se(fd_verify_regular(fd) == -ELOOP); + assert_se(fd_get_path(fd, &p) >= 0); + assert_se(streq(p, q)); + + p = mfree(p); + fd = safe_close(fd); + + fd = openat(AT_FDCWD, "subdir/symlink", O_CLOEXEC|O_PATH|O_NOFOLLOW); + assert_se(fd >= 0); + assert_se(fd_verify_regular(fd) == -ELOOP); + assert_se(fd_get_path(fd, &p) >= 0); + assert_se(streq(p, q)); + + p = mfree(p); + fd = safe_close(fd); + + fd = openat(tfd, "symdir//./symlink", O_CLOEXEC|O_PATH|O_NOFOLLOW); + assert_se(fd >= 0); + assert_se(fd_verify_regular(fd) == -ELOOP); + assert_se(fd_get_path(fd, &p) >= 0); + assert_se(streq(p, q)); + + p = mfree(p); + fd = safe_close(fd); + + fd = openat(AT_FDCWD, "symdir//./symlink", O_CLOEXEC|O_PATH|O_NOFOLLOW); + assert_se(fd >= 0); + assert_se(fd_verify_regular(fd) == -ELOOP); + assert_se(fd_get_path(fd, &p) >= 0); + assert_se(streq(p, q)); + + assert_se(chdir(saved_cwd) >= 0); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-fdset.c b/src/test/test-fdset.c new file mode 100644 index 0000000..8f00e59 --- /dev/null +++ b/src/test/test-fdset.c @@ -0,0 +1,212 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <fcntl.h> +#include <unistd.h> + +#include "fd-util.h" +#include "fdset.h" +#include "fs-util.h" +#include "macro.h" +#include "tests.h" +#include "tmpfile-util.h" + +TEST(fdset_new_fill) { + _cleanup_fdset_free_ FDSet *fdset = NULL; + int fd = -EBADF, flags; + + log_close(); + log_set_open_when_needed(true); + + fd = open("/dev/null", O_CLOEXEC|O_RDONLY); + assert_se(fd >= 0); + + assert_se(fdset_new_fill(/* filter_cloexec= */ -1, &fdset) >= 0); + assert_se(fdset_contains(fdset, fd)); + fdset = fdset_free(fdset); + assert_se(fcntl(fd, F_GETFD) < 0); + assert_se(errno == EBADF); + + fd = open("/dev/null", O_CLOEXEC|O_RDONLY); + assert_se(fd >= 0); + + assert_se(fdset_new_fill(/* filter_cloexec= */ 0, &fdset) >= 0); + assert_se(!fdset_contains(fdset, fd)); + fdset = fdset_free(fdset); + assert_se(fcntl(fd, F_GETFD) >= 0); + + assert_se(fdset_new_fill(/* filter_cloexec= */ 1, &fdset) >= 0); + assert_se(fdset_contains(fdset, fd)); + fdset = fdset_free(fdset); + assert_se(fcntl(fd, F_GETFD) < 0); + assert_se(errno == EBADF); + + fd = open("/dev/null", O_RDONLY); + assert_se(fd >= 0); + + assert_se(fdset_new_fill(/* filter_cloexec= */ 1, &fdset) >= 0); + assert_se(!fdset_contains(fdset, fd)); + fdset = fdset_free(fdset); + assert_se(fcntl(fd, F_GETFD) >= 0); + + assert_se(fdset_new_fill(/* filter_cloexec= */ 0, &fdset) >= 0); + assert_se(fdset_contains(fdset, fd)); + flags = fcntl(fd, F_GETFD); + assert_se(flags >= 0); + assert_se(FLAGS_SET(flags, FD_CLOEXEC)); + fdset = fdset_free(fdset); + assert_se(fcntl(fd, F_GETFD) < 0); + assert_se(errno == EBADF); + + log_open(); +} + +TEST(fdset_put_dup) { + _cleanup_close_ int fd = -EBADF; + int copyfd = -EBADF; + _cleanup_fdset_free_ FDSet *fdset = NULL; + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fdset_put_dup.XXXXXX"; + + fd = mkostemp_safe(name); + assert_se(fd >= 0); + + fdset = fdset_new(); + assert_se(fdset); + copyfd = fdset_put_dup(fdset, fd); + assert_se(copyfd >= 0 && copyfd != fd); + assert_se(fdset_contains(fdset, copyfd)); + assert_se(!fdset_contains(fdset, fd)); +} + +TEST(fdset_cloexec) { + int fd = -EBADF; + _cleanup_fdset_free_ FDSet *fdset = NULL; + int flags = -1; + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fdset_cloexec.XXXXXX"; + + fd = mkostemp_safe(name); + assert_se(fd >= 0); + + fdset = fdset_new(); + assert_se(fdset); + assert_se(fdset_put(fdset, fd)); + + assert_se(fdset_cloexec(fdset, false) >= 0); + flags = fcntl(fd, F_GETFD); + assert_se(flags >= 0); + assert_se(!(flags & FD_CLOEXEC)); + + assert_se(fdset_cloexec(fdset, true) >= 0); + flags = fcntl(fd, F_GETFD); + assert_se(flags >= 0); + assert_se(flags & FD_CLOEXEC); +} + +TEST(fdset_close_others) { + int fd = -EBADF; + int copyfd = -EBADF; + _cleanup_fdset_free_ FDSet *fdset = NULL; + int flags = -1; + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fdset_close_others.XXXXXX"; + + fd = mkostemp_safe(name); + assert_se(fd >= 0); + + fdset = fdset_new(); + assert_se(fdset); + copyfd = fdset_put_dup(fdset, fd); + assert_se(copyfd >= 0); + + assert_se(fdset_close_others(fdset) >= 0); + flags = fcntl(fd, F_GETFD); + assert_se(flags < 0); + flags = fcntl(copyfd, F_GETFD); + assert_se(flags >= 0); +} + +TEST(fdset_remove) { + _cleanup_close_ int fd = -EBADF; + _cleanup_fdset_free_ FDSet *fdset = NULL; + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fdset_remove.XXXXXX"; + + fd = mkostemp_safe(name); + assert_se(fd >= 0); + + fdset = fdset_new(); + assert_se(fdset); + assert_se(fdset_put(fdset, fd) >= 0); + assert_se(fdset_remove(fdset, fd) >= 0); + assert_se(!fdset_contains(fdset, fd)); + + assert_se(fcntl(fd, F_GETFD) >= 0); +} + +TEST(fdset_iterate) { + int fd = -EBADF; + _cleanup_fdset_free_ FDSet *fdset = NULL; + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fdset_iterate.XXXXXX"; + int c = 0; + int a; + + fd = mkostemp_safe(name); + assert_se(fd >= 0); + + fdset = fdset_new(); + assert_se(fdset); + assert_se(fdset_put(fdset, fd) >= 0); + assert_se(fdset_put(fdset, fd) >= 0); + assert_se(fdset_put(fdset, fd) >= 0); + + FDSET_FOREACH(a, fdset) { + c++; + assert_se(a == fd); + } + assert_se(c == 1); +} + +TEST(fdset_isempty) { + int fd; + _cleanup_fdset_free_ FDSet *fdset = NULL; + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fdset_isempty.XXXXXX"; + + fd = mkostemp_safe(name); + assert_se(fd >= 0); + + fdset = fdset_new(); + assert_se(fdset); + + assert_se(fdset_isempty(fdset)); + assert_se(fdset_put(fdset, fd) >= 0); + assert_se(!fdset_isempty(fdset)); +} + +TEST(fdset_steal_first) { + int fd; + _cleanup_fdset_free_ FDSet *fdset = NULL; + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fdset_steal_first.XXXXXX"; + + fd = mkostemp_safe(name); + assert_se(fd >= 0); + + fdset = fdset_new(); + assert_se(fdset); + + assert_se(fdset_steal_first(fdset) < 0); + assert_se(fdset_put(fdset, fd) >= 0); + assert_se(fdset_steal_first(fdset) == fd); + assert_se(fdset_steal_first(fdset) < 0); + assert_se(fdset_put(fdset, fd) >= 0); +} + +TEST(fdset_new_array) { + int fds[] = {10, 11, 12, 13}; + _cleanup_fdset_free_ FDSet *fdset = NULL; + + assert_se(fdset_new_array(&fdset, fds, 4) >= 0); + assert_se(fdset_size(fdset) == 4); + assert_se(fdset_contains(fdset, 10)); + assert_se(fdset_contains(fdset, 11)); + assert_se(fdset_contains(fdset, 12)); + assert_se(fdset_contains(fdset, 13)); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-fiemap.c b/src/test/test-fiemap.c new file mode 100644 index 0000000..380638b --- /dev/null +++ b/src/test/test-fiemap.c @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <fcntl.h> +#include <inttypes.h> +#include <linux/fiemap.h> +#include <stdio.h> +#include <sys/types.h> +#include <unistd.h> + +#include "errno-util.h" +#include "fd-util.h" +#include "log.h" +#include "hibernate-util.h" +#include "tests.h" + +static int test_fiemap_one(const char *path) { + _cleanup_free_ struct fiemap *fiemap = NULL; + _cleanup_close_ int fd = -EBADF; + int r; + + log_info("/* %s */", __func__); + + fd = open(path, O_RDONLY | O_CLOEXEC | O_NONBLOCK); + if (fd < 0) + return log_error_errno(errno, "failed to open %s: %m", path); + r = read_fiemap(fd, &fiemap); + if (r == -EOPNOTSUPP) + exit(log_tests_skipped("Not supported")); + if (r < 0) + return log_error_errno(r, "Unable to read extent map for '%s': %m", path); + log_info("extent map information for %s:", path); + log_info("\t start: %" PRIu64, (uint64_t) fiemap->fm_start); + log_info("\t length: %" PRIu64, (uint64_t) fiemap->fm_length); + log_info("\t flags: %" PRIu32, fiemap->fm_flags); + log_info("\t number of mapped extents: %" PRIu32, fiemap->fm_mapped_extents); + log_info("\t extent count: %" PRIu32, fiemap->fm_extent_count); + if (fiemap->fm_extent_count > 0) + log_info("\t first extent location: %" PRIu64, + (uint64_t) (fiemap->fm_extents[0].fe_physical / page_size())); + + return 0; +} + +TEST_RET(fiemap) { + int r = 0; + + assert_se(test_fiemap_one(saved_argv[0]) == 0); + for (int i = 1; i < saved_argc; i++) { + int k = test_fiemap_one(saved_argv[i]); + if (r == 0) + r = k; + } + + return r; +} + +static int intro(void) { + if (getuid() != 0) + log_warning("This program is unlikely to work for unprivileged users"); + + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c new file mode 100644 index 0000000..ad98a92 --- /dev/null +++ b/src/test/test-fileio.c @@ -0,0 +1,1151 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "ctype.h" +#include "env-file.h" +#include "env-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "iovec-util.h" +#include "memfd-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "random-util.h" +#include "rm-rf.h" +#include "socket-util.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" +#include "tmpfile-util.h" + +TEST(parse_env_file) { + _cleanup_(unlink_tempfilep) char + t[] = "/tmp/test-fileio-in-XXXXXX", + p[] = "/tmp/test-fileio-out-XXXXXX"; + FILE *f; + _cleanup_free_ char *one = NULL, *two = NULL, *three = NULL, *four = NULL, *five = NULL, + *six = NULL, *seven = NULL, *eight = NULL, *nine = NULL, *ten = NULL, + *eleven = NULL, *twelve = NULL, *thirteen = NULL; + _cleanup_strv_free_ char **a = NULL, **b = NULL; + unsigned k; + int r; + + assert_se(fmkostemp_safe(t, "w", &f) == 0); + fputs("one=BAR \n" + "# comment\n" + " # comment \n" + " ; comment \n" + " two = bar \n" + "invalid line\n" + "invalid line #comment\n" + "three = \"333\n" + "xxxx\"\n" + "four = \'44\\\"44\'\n" + "five = \"55\\\"55\" \"FIVE\" cinco \n" + "six = seis sechs\\\n" + " sis\n" + "seven=\"sevenval\" #nocomment\n" + "eight=eightval #nocomment\n" + "export nine=nineval\n" + "ten=ignored\n" + "ten=ignored\n" + "ten=\n" + "eleven=\\value\n" + "twelve=\"\\value\"\n" + "thirteen='\\value'", f); + + fflush(f); + fclose(f); + + r = load_env_file(NULL, t, &a); + assert_se(r >= 0); + + STRV_FOREACH(i, a) + log_info("Got: <%s>", *i); + + assert_se(streq_ptr(a[0], "one=BAR")); + assert_se(streq_ptr(a[1], "two=bar")); + assert_se(streq_ptr(a[2], "three=333\nxxxx")); + assert_se(streq_ptr(a[3], "four=44\\\"44")); + assert_se(streq_ptr(a[4], "five=55\"55FIVEcinco")); + assert_se(streq_ptr(a[5], "six=seis sechs sis")); + assert_se(streq_ptr(a[6], "seven=sevenval#nocomment")); + assert_se(streq_ptr(a[7], "eight=eightval #nocomment")); + assert_se(streq_ptr(a[8], "export nine=nineval")); + assert_se(streq_ptr(a[9], "ten=")); + assert_se(streq_ptr(a[10], "eleven=value")); + assert_se(streq_ptr(a[11], "twelve=\\value")); + assert_se(streq_ptr(a[12], "thirteen=\\value")); + assert_se(a[13] == NULL); + + strv_env_clean(a); + + k = 0; + STRV_FOREACH(i, b) { + log_info("Got2: <%s>", *i); + assert_se(streq(*i, a[k++])); + } + + r = parse_env_file( + NULL, t, + "one", &one, + "two", &two, + "three", &three, + "four", &four, + "five", &five, + "six", &six, + "seven", &seven, + "eight", &eight, + "export nine", &nine, + "ten", &ten, + "eleven", &eleven, + "twelve", &twelve, + "thirteen", &thirteen); + assert_se(r == 0); + + log_info("one=[%s]", strna(one)); + log_info("two=[%s]", strna(two)); + log_info("three=[%s]", strna(three)); + log_info("four=[%s]", strna(four)); + log_info("five=[%s]", strna(five)); + log_info("six=[%s]", strna(six)); + log_info("seven=[%s]", strna(seven)); + log_info("eight=[%s]", strna(eight)); + log_info("export nine=[%s]", strna(nine)); + log_info("ten=[%s]", strna(nine)); + log_info("eleven=[%s]", strna(eleven)); + log_info("twelve=[%s]", strna(twelve)); + log_info("thirteen=[%s]", strna(thirteen)); + + assert_se(streq(one, "BAR")); + assert_se(streq(two, "bar")); + assert_se(streq(three, "333\nxxxx")); + assert_se(streq(four, "44\\\"44")); + assert_se(streq(five, "55\"55FIVEcinco")); + assert_se(streq(six, "seis sechs sis")); + assert_se(streq(seven, "sevenval#nocomment")); + assert_se(streq(eight, "eightval #nocomment")); + assert_se(streq(nine, "nineval")); + assert_se(ten == NULL); + assert_se(streq(eleven, "value")); + assert_se(streq(twelve, "\\value")); + assert_se(streq(thirteen, "\\value")); + + { + /* prepare a temporary file to write the environment to */ + _cleanup_close_ int fd = mkostemp_safe(p); + assert_se(fd >= 0); + } + + r = write_env_file(AT_FDCWD, p, NULL, a); + assert_se(r >= 0); + + r = load_env_file(NULL, p, &b); + assert_se(r >= 0); +} + +static void test_one_shell_var(const char *file, const char *variable, const char *value) { + _cleanup_free_ char *cmd = NULL, *from_shell = NULL; + _cleanup_pclose_ FILE *f = NULL; + size_t sz; + + assert_se(cmd = strjoin(". ", file, " && /bin/echo -n \"$", variable, "\"")); + assert_se(f = popen(cmd, "re")); + assert_se(read_full_stream(f, &from_shell, &sz) >= 0); + assert_se(sz == strlen(value)); + assert_se(streq(from_shell, value)); +} + +TEST(parse_multiline_env_file) { + _cleanup_(unlink_tempfilep) char + t[] = "/tmp/test-fileio-in-XXXXXX", + p[] = "/tmp/test-fileio-out-XXXXXX"; + FILE *f; + _cleanup_strv_free_ char **a = NULL, **b = NULL; + int r; + + assert_se(fmkostemp_safe(t, "w", &f) == 0); + fputs("one=BAR\\\n" + "\\ \\ \\ \\ VAR\\\n" + "\\\tGAR\n" + "#comment\n" + "two=\"bar\\\n" + " var\\\n" + "\tgar\"\n" + "#comment\n" + "tri=\"bar \\\n" + " var \\\n" + "\tgar \"\n", f); + + assert_se(fflush_and_check(f) >= 0); + fclose(f); + + test_one_shell_var(t, "one", "BAR VAR\tGAR"); + test_one_shell_var(t, "two", "bar var\tgar"); + test_one_shell_var(t, "tri", "bar var \tgar "); + + r = load_env_file(NULL, t, &a); + assert_se(r >= 0); + + STRV_FOREACH(i, a) + log_info("Got: <%s>", *i); + + assert_se(streq_ptr(a[0], "one=BAR VAR\tGAR")); + assert_se(streq_ptr(a[1], "two=bar var\tgar")); + assert_se(streq_ptr(a[2], "tri=bar var \tgar ")); + assert_se(a[3] == NULL); + + { + _cleanup_close_ int fd = mkostemp_safe(p); + assert_se(fd >= 0); + } + + r = write_env_file(AT_FDCWD, p, NULL, a); + assert_se(r >= 0); + + r = load_env_file(NULL, p, &b); + assert_se(r >= 0); +} + +TEST(merge_env_file) { + _cleanup_(unlink_tempfilep) char t[] = "/tmp/test-fileio-XXXXXX"; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_strv_free_ char **a = NULL; + int r; + + assert_se(fmkostemp_safe(t, "w", &f) == 0); + log_info("/* %s (%s) */", __func__, t); + + r = write_string_stream(f, + "one=1 \n" + "twelve=${one}2\n" + "twentyone=2${one}\n" + "one=2\n" + "twentytwo=2${one}\n" + "xxx_minus_three=$xxx - 3\n" + "xxx=0x$one$one$one\n" + "yyy=${one:-fallback}\n" + "zzz=${one:+replacement}\n" + "zzzz=${foobar:-${nothing}}\n" + "zzzzz=${nothing:+${nothing}}\n" + , WRITE_STRING_FILE_AVOID_NEWLINE); + assert_se(r >= 0); + + r = merge_env_file(&a, NULL, t); + assert_se(r >= 0); + strv_sort(a); + + STRV_FOREACH(i, a) + log_info("Got: <%s>", *i); + + assert_se(streq(a[0], "one=2")); + assert_se(streq(a[1], "twelve=12")); + assert_se(streq(a[2], "twentyone=21")); + assert_se(streq(a[3], "twentytwo=22")); + assert_se(streq(a[4], "xxx=0x222")); + assert_se(streq(a[5], "xxx_minus_three= - 3")); + assert_se(streq(a[6], "yyy=2")); + assert_se(streq(a[7], "zzz=replacement")); + assert_se(streq(a[8], "zzzz=")); + assert_se(streq(a[9], "zzzzz=")); + assert_se(a[10] == NULL); + + r = merge_env_file(&a, NULL, t); + assert_se(r >= 0); + strv_sort(a); + + STRV_FOREACH(i, a) + log_info("Got2: <%s>", *i); + + assert_se(streq(a[0], "one=2")); + assert_se(streq(a[1], "twelve=12")); + assert_se(streq(a[2], "twentyone=21")); + assert_se(streq(a[3], "twentytwo=22")); + assert_se(streq(a[4], "xxx=0x222")); + assert_se(streq(a[5], "xxx_minus_three=0x222 - 3")); + assert_se(streq(a[6], "yyy=2")); + assert_se(streq(a[7], "zzz=replacement")); + assert_se(streq(a[8], "zzzz=")); + assert_se(streq(a[9], "zzzzz=")); + assert_se(a[10] == NULL); +} + +TEST(merge_env_file_invalid) { + _cleanup_(unlink_tempfilep) char t[] = "/tmp/test-fileio-XXXXXX"; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_strv_free_ char **a = NULL; + int r; + + assert_se(fmkostemp_safe(t, "w", &f) == 0); + log_info("/* %s (%s) */", __func__, t); + + r = write_string_stream(f, + "unset one \n" + "unset one= \n" + "unset one=1 \n" + "one \n" + "one = \n" + "one two =\n" + "\x20two=\n" + "#comment=comment\n" + ";comment2=comment2\n" + "#\n" + "\n\n" /* empty line */ + , WRITE_STRING_FILE_AVOID_NEWLINE); + assert_se(r >= 0); + + r = merge_env_file(&a, NULL, t); + assert_se(r >= 0); + + STRV_FOREACH(i, a) + log_info("Got: <%s>", *i); + + assert_se(strv_isempty(a)); +} + +TEST(executable_is_script) { + _cleanup_(unlink_tempfilep) char t[] = "/tmp/test-fileio-XXXXXX"; + _cleanup_fclose_ FILE *f = NULL; + char *command; + int r; + + assert_se(fmkostemp_safe(t, "w", &f) == 0); + fputs("#! /bin/script -a -b \ngoo goo", f); + fflush(f); + + r = executable_is_script(t, &command); + assert_se(r > 0); + assert_se(streq(command, "/bin/script")); + free(command); + + r = executable_is_script("/bin/sh", &command); + assert_se(r == 0); + + r = executable_is_script("/usr/bin/yum", &command); + if (r > 0) { + assert_se(startswith(command, "/")); + free(command); + } +} + +TEST(status_field) { + _cleanup_free_ char *p = NULL, *s = NULL, *z = NULL; + unsigned long long total = 0, buffers = 0; + int r; + + r = get_proc_field("/proc/meminfo", "MemTotal", WHITESPACE, &p); + if (r != -ENOENT) { + assert_se(r == 0); + puts(p); + assert_se(safe_atollu(p, &total) == 0); + } + + r = get_proc_field("/proc/meminfo", "Buffers", WHITESPACE, &s); + if (r != -ENOENT) { + assert_se(r == 0); + puts(s); + assert_se(safe_atollu(s, &buffers) == 0); + } + + if (p) + assert_se(buffers < total); + + /* Seccomp should be a good test for field full of zeros. */ + r = get_proc_field("/proc/meminfo", "Seccomp", WHITESPACE, &z); + if (r != -ENOENT) { + assert_se(r == 0); + puts(z); + assert_se(safe_atollu(z, &buffers) == 0); + } +} + +TEST(capeff) { + for (int pid = 0; pid < 2; pid++) { + _cleanup_free_ char *capeff = NULL; + int r, p; + + r = get_process_capeff(0, &capeff); + log_info("capeff: '%s' (r=%d)", capeff, r); + + if (IN_SET(r, -ENOENT, -EPERM)) + return; + + assert_se(r == 0); + assert_se(*capeff); + p = capeff[strspn(capeff, HEXDIGITS)]; + assert_se(!p || isspace(p)); + } +} + +TEST(read_one_line_file) { + _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-fileio-1lf-XXXXXX"; + int fd; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *buf, *buf2, *buf3, *buf4, *buf5; + + fd = mkostemp_safe(fn); + assert_se(fd >= 0); + + f = fdopen(fd, "we"); + assert_se(f); + + assert_se(read_one_line_file(fn, &buf) == 0); + assert_se(streq_ptr(buf, "")); + assert_se(read_one_line_file(fn, &buf2) == 0); + assert_se(streq_ptr(buf2, "")); + + assert_se(write_string_stream(f, "x", WRITE_STRING_FILE_AVOID_NEWLINE) >= 0); + fflush(f); + + assert_se(read_one_line_file(fn, &buf3) == 1); + assert_se(streq_ptr(buf3, "x")); + + assert_se(write_string_stream(f, "\n", WRITE_STRING_FILE_AVOID_NEWLINE) >= 0); + fflush(f); + + assert_se(read_one_line_file(fn, &buf4) == 2); + assert_se(streq_ptr(buf4, "x")); + + assert_se(write_string_stream(f, "\n", WRITE_STRING_FILE_AVOID_NEWLINE) >= 0); + fflush(f); + + assert_se(read_one_line_file(fn, &buf5) == 2); + assert_se(streq_ptr(buf5, "x")); +} + +TEST(write_string_stream) { + _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-write_string_stream-XXXXXX"; + _cleanup_fclose_ FILE *f = NULL; + int fd; + char buf[64]; + + fd = mkostemp_safe(fn); + assert_se(fd >= 0); + + f = fdopen(fd, "r"); + assert_se(f); + assert_se(write_string_stream(f, "boohoo", 0) < 0); + f = safe_fclose(f); + + f = fopen(fn, "r+"); + assert_se(f); + + assert_se(write_string_stream(f, "boohoo", 0) == 0); + rewind(f); + + assert_se(fgets(buf, sizeof(buf), f)); + assert_se(streq(buf, "boohoo\n")); + f = safe_fclose(f); + + f = fopen(fn, "w+"); + assert_se(f); + + assert_se(write_string_stream(f, "boohoo", WRITE_STRING_FILE_AVOID_NEWLINE) == 0); + rewind(f); + + assert_se(fgets(buf, sizeof(buf), f)); + printf(">%s<", buf); + assert_se(streq(buf, "boohoo")); +} + +TEST(write_string_file) { + _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-write_string_file-XXXXXX"; + char buf[64] = {}; + _cleanup_close_ int fd = -EBADF; + + fd = mkostemp_safe(fn); + assert_se(fd >= 0); + + assert_se(write_string_file(fn, "boohoo", WRITE_STRING_FILE_CREATE) == 0); + + assert_se(read(fd, buf, sizeof(buf)) == 7); + assert_se(streq(buf, "boohoo\n")); +} + +TEST(write_string_file_no_create) { + _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-write_string_file_no_create-XXXXXX"; + _cleanup_close_ int fd = -EBADF; + char buf[64] = {}; + + fd = mkostemp_safe(fn); + assert_se(fd >= 0); + + assert_se(write_string_file("/a/file/which/does/not/exists/i/guess", "boohoo", 0) < 0); + assert_se(write_string_file(fn, "boohoo", 0) == 0); + + assert_se(read(fd, buf, sizeof buf) == (ssize_t) strlen("boohoo\n")); + assert_se(streq(buf, "boohoo\n")); +} + +TEST(write_string_file_verify) { + _cleanup_free_ char *buf = NULL, *buf2 = NULL; + int r; + + r = read_one_line_file("/proc/version", &buf); + if (ERRNO_IS_NEG_PRIVILEGE(r)) + return; + assert_se(r >= 0); + assert_se(buf2 = strjoin(buf, "\n")); + + r = write_string_file("/proc/version", buf, 0); + assert_se(IN_SET(r, -EACCES, -EIO)); + r = write_string_file("/proc/version", buf2, 0); + assert_se(IN_SET(r, -EACCES, -EIO)); + + assert_se(write_string_file("/proc/version", buf, WRITE_STRING_FILE_VERIFY_ON_FAILURE) == 0); + assert_se(write_string_file("/proc/version", buf2, WRITE_STRING_FILE_VERIFY_ON_FAILURE) == 0); + + r = write_string_file("/proc/version", buf, WRITE_STRING_FILE_VERIFY_ON_FAILURE|WRITE_STRING_FILE_AVOID_NEWLINE); + assert_se(IN_SET(r, -EACCES, -EIO)); + assert_se(write_string_file("/proc/version", buf2, WRITE_STRING_FILE_VERIFY_ON_FAILURE|WRITE_STRING_FILE_AVOID_NEWLINE) == 0); +} + +static void check_file_pairs_one(char **l) { + assert_se(l); + assert_se(strv_length(l) == 14); + + STRV_FOREACH_PAIR(k, v, l) { + assert_se(STR_IN_SET(*k, "NAME", "ID", "PRETTY_NAME", "ANSI_COLOR", "HOME_URL", "SUPPORT_URL", "BUG_REPORT_URL")); + printf("%s=%s\n", *k, *v); + assert_se(!streq(*k, "NAME") || streq(*v, "Arch Linux")); + assert_se(!streq(*k, "ID") || streq(*v, "arch")); + assert_se(!streq(*k, "PRETTY_NAME") || streq(*v, "Arch Linux")); + assert_se(!streq(*k, "ANSI_COLOR") || streq(*v, "0;36")); + assert_se(!streq(*k, "HOME_URL") || streq(*v, "https://www.archlinux.org/")); + assert_se(!streq(*k, "SUPPORT_URL") || streq(*v, "https://bbs.archlinux.org/")); + assert_se(!streq(*k, "BUG_REPORT_URL") || streq(*v, "https://bugs.archlinux.org/")); + } +} + +TEST(load_env_file_pairs) { + _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-load_env_file_pairs-XXXXXX"; + int fd, r; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_strv_free_ char **l = NULL; + + fd = mkostemp_safe(fn); + assert_se(fd >= 0); + + r = write_string_file(fn, + "NAME=\"Arch Linux\"\n" + "ID=arch\n" + "PRETTY_NAME=\"Arch Linux\"\n" + "ANSI_COLOR=\"0;36\"\n" + "HOME_URL=\"https://www.archlinux.org/\"\n" + "SUPPORT_URL=\"https://bbs.archlinux.org/\"\n" + "BUG_REPORT_URL=\"https://bugs.archlinux.org/\"\n", + WRITE_STRING_FILE_CREATE); + assert_se(r == 0); + + r = load_env_file_pairs_fd(fd, fn, &l); + assert_se(r >= 0); + check_file_pairs_one(l); + l = strv_free(l); + + f = fdopen(fd, "r"); + assert_se(f); + + r = load_env_file_pairs(f, fn, &l); + assert_se(r >= 0); + check_file_pairs_one(l); +} + +TEST(search_and_fopen) { + static const char* const dirs[] = { + "/tmp/foo/bar", + "/tmp", + NULL + }; + char name[] = "/tmp/test-search_and_fopen.XXXXXX"; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *p = NULL; + _cleanup_close_ int fd = -EBADF; + const char *e; + int r; + + fd = mkostemp_safe(name); + assert_se(fd >= 0); + fd = safe_close(fd); + + r = search_and_fopen(basename(name), "re", NULL, (const char**) dirs, &f, &p); + assert_se(r >= 0); + assert_se(e = path_startswith(p, "/tmp/")); + assert_se(streq(basename(name), e)); + f = safe_fclose(f); + p = mfree(p); + + r = search_and_fopen(basename(name), NULL, NULL, (const char**) dirs, NULL, &p); + assert_se(r >= 0); + assert_se(e = path_startswith(p, "/tmp/")); + assert_se(streq(basename(name), e)); + p = mfree(p); + + r = search_and_fopen(name, "re", NULL, (const char**) dirs, &f, &p); + assert_se(r >= 0); + assert_se(path_equal(name, p)); + f = safe_fclose(f); + p = mfree(p); + + r = search_and_fopen(name, NULL, NULL, (const char**) dirs, NULL, &p); + assert_se(r >= 0); + assert_se(path_equal(name, p)); + p = mfree(p); + + r = search_and_fopen(basename(name), "re", "/", (const char**) dirs, &f, &p); + assert_se(r >= 0); + assert_se(e = path_startswith(p, "/tmp/")); + assert_se(streq(basename(name), e)); + f = safe_fclose(f); + p = mfree(p); + + r = search_and_fopen(basename(name), NULL, "/", (const char**) dirs, NULL, &p); + assert_se(r >= 0); + assert_se(e = path_startswith(p, "/tmp/")); + assert_se(streq(basename(name), e)); + p = mfree(p); + + r = search_and_fopen("/a/file/which/does/not/exist/i/guess", "re", NULL, (const char**) dirs, &f, &p); + assert_se(r == -ENOENT); + r = search_and_fopen("/a/file/which/does/not/exist/i/guess", NULL, NULL, (const char**) dirs, NULL, &p); + assert_se(r == -ENOENT); + r = search_and_fopen("afilewhichdoesnotexistiguess", "re", NULL, (const char**) dirs, &f, &p); + assert_se(r == -ENOENT); + r = search_and_fopen("afilewhichdoesnotexistiguess", NULL, NULL, (const char**) dirs, NULL, &p); + assert_se(r == -ENOENT); + + r = unlink(name); + assert_se(r == 0); + + r = search_and_fopen(basename(name), "re", NULL, (const char**) dirs, &f, &p); + assert_se(r == -ENOENT); + r = search_and_fopen(basename(name), NULL, NULL, (const char**) dirs, NULL, &p); + assert_se(r == -ENOENT); +} + +TEST(search_and_fopen_nulstr) { + static const char dirs[] = + "/tmp/foo/bar\0" + "/tmp\0"; + + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-search_and_fopen.XXXXXX"; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *p = NULL; + _cleanup_close_ int fd = -EBADF; + const char *e; + int r; + + fd = mkostemp_safe(name); + assert_se(fd >= 0); + fd = safe_close(fd); + + r = search_and_fopen_nulstr(basename(name), "re", NULL, dirs, &f, &p); + assert_se(r >= 0); + assert_se(e = path_startswith(p, "/tmp/")); + assert_se(streq(basename(name), e)); + f = safe_fclose(f); + p = mfree(p); + + r = search_and_fopen_nulstr(name, "re", NULL, dirs, &f, &p); + assert_se(r >= 0); + assert_se(path_equal(name, p)); + f = safe_fclose(f); + p = mfree(p); + + r = search_and_fopen_nulstr("/a/file/which/does/not/exist/i/guess", "re", NULL, dirs, &f, &p); + assert_se(r == -ENOENT); + r = search_and_fopen_nulstr("afilewhichdoesnotexistiguess", "re", NULL, dirs, &f, &p); + assert_se(r == -ENOENT); + + r = unlink(name); + assert_se(r == 0); + + r = search_and_fopen_nulstr(basename(name), "re", NULL, dirs, &f, &p); + assert_se(r == -ENOENT); +} + +TEST(writing_tmpfile) { + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-systemd_writing_tmpfile.XXXXXX"; + _cleanup_free_ char *contents = NULL; + size_t size; + _cleanup_close_ int fd = -EBADF; + int r; + + struct iovec iov[] = { + IOVEC_MAKE_STRING("abc\n"), + IOVEC_MAKE_STRING(ALPHANUMERICAL "\n"), + IOVEC_MAKE_STRING(""), + }; + + fd = mkostemp_safe(name); + printf("tmpfile: %s", name); + + r = writev(fd, iov, 3); + assert_se(r >= 0); + + r = read_full_file(name, &contents, &size); + assert_se(r == 0); + printf("contents: %s", contents); + assert_se(streq(contents, "abc\n" ALPHANUMERICAL "\n")); +} + +TEST(tempfn) { + char *ret = NULL, *p; + + assert_se(tempfn_xxxxxx("/foo/bar/waldo", NULL, &ret) >= 0); + assert_se(streq_ptr(ret, "/foo/bar/.#waldoXXXXXX")); + free(ret); + + assert_se(tempfn_xxxxxx("/foo/bar/waldo", "[miau]", &ret) >= 0); + assert_se(streq_ptr(ret, "/foo/bar/.#[miau]waldoXXXXXX")); + free(ret); + + assert_se(tempfn_random("/foo/bar/waldo", NULL, &ret) >= 0); + assert_se(p = startswith(ret, "/foo/bar/.#waldo")); + assert_se(strlen(p) == 16); + assert_se(in_charset(p, "0123456789abcdef")); + free(ret); + + assert_se(tempfn_random("/foo/bar/waldo", "[wuff]", &ret) >= 0); + assert_se(p = startswith(ret, "/foo/bar/.#[wuff]waldo")); + assert_se(strlen(p) == 16); + assert_se(in_charset(p, "0123456789abcdef")); + free(ret); + + assert_se(tempfn_random_child("/foo/bar/waldo", NULL, &ret) >= 0); + assert_se(p = startswith(ret, "/foo/bar/waldo/.#")); + assert_se(strlen(p) == 16); + assert_se(in_charset(p, "0123456789abcdef")); + free(ret); + + assert_se(tempfn_random_child("/foo/bar/waldo", "[kikiriki]", &ret) >= 0); + assert_se(p = startswith(ret, "/foo/bar/waldo/.#[kikiriki]")); + assert_se(strlen(p) == 16); + assert_se(in_charset(p, "0123456789abcdef")); + free(ret); +} + +static const char chars[] = + "Aąę„”\n루\377"; + +DISABLE_WARNING_TYPE_LIMITS; + +TEST(fgetc) { + _cleanup_fclose_ FILE *f = NULL; + char c; + + assert_se(f = fmemopen_unlocked((void*) chars, sizeof(chars), "r")); + + for (size_t i = 0; i < sizeof(chars); i++) { + assert_se(safe_fgetc(f, &c) == 1); + assert_se(c == chars[i]); + + if (ungetc(c, f) == EOF) { + /* EOF is -1, and hence we can't push value 255 in this way – if char is signed */ + assert_se(c == (char) EOF); + assert_se(CHAR_MIN == -128); /* verify that char is signed on this platform */ + } else { + assert_se(safe_fgetc(f, &c) == 1); + assert_se(c == chars[i]); + } + + /* But it works when we push it properly cast */ + assert_se(ungetc((unsigned char) c, f) != EOF); + assert_se(safe_fgetc(f, &c) == 1); + assert_se(c == chars[i]); + } + + assert_se(safe_fgetc(f, &c) == 0); +} + +REENABLE_WARNING; + +static const char buffer[] = + "Some test data\n" + "루Non-ascii chars: ąę„”\n" + "terminators\r\n" + "and even more\n\r" + "now the same with a NUL\n\0" + "and more\r\0" + "and even more\r\n\0" + "and yet even more\n\r\0" + "With newlines, and a NUL byte\0" + "\n" + "an empty line\n" + "an ignored line\n" + "and a very long line that is supposed to be truncated, because it is so long\n"; + +static void test_read_line_one_file(FILE *f) { + _cleanup_free_ char *line = NULL; + + assert_se(read_line(f, SIZE_MAX, &line) == 15 && streq(line, "Some test data")); + line = mfree(line); + + assert_se(read_line(f, SIZE_MAX, &line) > 0 && streq(line, "루Non-ascii chars: ąę„”")); + line = mfree(line); + + assert_se(read_line(f, SIZE_MAX, &line) == 13 && streq(line, "terminators")); + line = mfree(line); + + assert_se(read_line(f, SIZE_MAX, &line) == 15 && streq(line, "and even more")); + line = mfree(line); + + assert_se(read_line(f, SIZE_MAX, &line) == 25 && streq(line, "now the same with a NUL")); + line = mfree(line); + + assert_se(read_line(f, SIZE_MAX, &line) == 10 && streq(line, "and more")); + line = mfree(line); + + assert_se(read_line(f, SIZE_MAX, &line) == 16 && streq(line, "and even more")); + line = mfree(line); + + assert_se(read_line(f, SIZE_MAX, &line) == 20 && streq(line, "and yet even more")); + line = mfree(line); + + assert_se(read_line(f, 1024, &line) == 30 && streq(line, "With newlines, and a NUL byte")); + line = mfree(line); + + assert_se(read_line(f, 1024, &line) == 1 && streq(line, "")); + line = mfree(line); + + assert_se(read_line(f, 1024, &line) == 14 && streq(line, "an empty line")); + line = mfree(line); + + assert_se(read_line(f, SIZE_MAX, NULL) == 16); + + assert_se(read_line(f, 16, &line) == -ENOBUFS); + line = mfree(line); + + /* read_line() stopped when it hit the limit, that means when we continue reading we'll read at the first + * character after the previous limit. Let's make use of that to continue our test. */ + assert_se(read_line(f, 1024, &line) == 62 && streq(line, "line that is supposed to be truncated, because it is so long")); + line = mfree(line); + + assert_se(read_line(f, 1024, &line) == 0 && streq(line, "")); +} + +TEST(read_line1) { + _cleanup_fclose_ FILE *f = NULL; + + assert_se(f = fmemopen_unlocked((void*) buffer, sizeof(buffer), "r")); + test_read_line_one_file(f); +} + +TEST(read_line2) { + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-fileio.XXXXXX"; + int fd; + _cleanup_fclose_ FILE *f = NULL; + + fd = mkostemp_safe(name); + assert_se(fd >= 0); + assert_se((size_t) write(fd, buffer, sizeof(buffer)) == sizeof(buffer)); + + assert_se(lseek(fd, 0, SEEK_SET) == 0); + assert_se(f = fdopen(fd, "r")); + + test_read_line_one_file(f); +} + +TEST(read_line3) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *line = NULL; + int r; + + f = fopen("/proc/uptime", "re"); + if (!f && IN_SET(errno, ENOENT, EPERM)) + return; + assert_se(f); + + r = read_line(f, LINE_MAX, &line); + assert_se(r >= 0); + if (r == 0) + assert_se(line && isempty(line)); + else + assert_se((size_t) r == strlen(line) + 1); + assert_se(read_line(f, LINE_MAX, NULL) == 0); +} + +TEST(read_line4) { + static const struct { + size_t length; + const char *string; + } eof_endings[] = { + /* Each of these will be followed by EOF and should generate the one same single string */ + { 3, "foo" }, + { 4, "foo\n" }, + { 4, "foo\r" }, + { 4, "foo\0" }, + { 5, "foo\n\0" }, + { 5, "foo\r\0" }, + { 5, "foo\r\n" }, + { 5, "foo\n\r" }, + { 6, "foo\r\n\0" }, + { 6, "foo\n\r\0" }, + }; + + int r; + + for (size_t i = 0; i < ELEMENTSOF(eof_endings); i++) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *s = NULL; + + assert_se(f = fmemopen_unlocked((void*) eof_endings[i].string, eof_endings[i].length, "r")); + + r = read_line(f, SIZE_MAX, &s); + assert_se((size_t) r == eof_endings[i].length); + assert_se(streq_ptr(s, "foo")); + + assert_se(read_line(f, SIZE_MAX, NULL) == 0); /* Ensure we hit EOF */ + } +} + +TEST(read_nul_string) { + static const char test[] = "string nr. 1\0" + "string nr. 2\n\0" + "\377empty string follows\0" + "\0" + "final string\n is empty\0" + "\0"; + + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *s = NULL; + + assert_se(f = fmemopen_unlocked((void*) test, sizeof(test)-1, "r")); + + assert_se(read_nul_string(f, LONG_LINE_MAX, &s) == 13 && streq_ptr(s, "string nr. 1")); + s = mfree(s); + + assert_se(read_nul_string(f, LONG_LINE_MAX, &s) == 14 && streq_ptr(s, "string nr. 2\n")); + s = mfree(s); + + assert_se(read_nul_string(f, LONG_LINE_MAX, &s) == 22 && streq_ptr(s, "\377empty string follows")); + s = mfree(s); + + assert_se(read_nul_string(f, LONG_LINE_MAX, &s) == 1 && streq_ptr(s, "")); + s = mfree(s); + + assert_se(read_nul_string(f, LONG_LINE_MAX, &s) == 23 && streq_ptr(s, "final string\n is empty")); + s = mfree(s); + + assert_se(read_nul_string(f, LONG_LINE_MAX, &s) == 1 && streq_ptr(s, "")); + s = mfree(s); + + assert_se(read_nul_string(f, LONG_LINE_MAX, &s) == 0 && streq_ptr(s, "")); +} + +TEST(read_full_file_socket) { + _cleanup_(rm_rf_physical_and_freep) char *z = NULL; + _cleanup_close_ int listener = -EBADF; + _cleanup_free_ char *data = NULL, *clientname = NULL; + union sockaddr_union sa; + const char *j, *jj; + size_t size; + pid_t pid; + int r; + + listener = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); + assert_se(listener >= 0); + + assert_se(mkdtemp_malloc(NULL, &z) >= 0); + j = strjoina(z, "/socket"); + + assert_se(sockaddr_un_set_path(&sa.un, j) >= 0); + + assert_se(bind(listener, &sa.sa, SOCKADDR_UN_LEN(sa.un)) >= 0); + assert_se(listen(listener, 1) >= 0); + + /* Make sure the socket doesn't fit into a struct sockaddr_un, but we can still access it */ + jj = strjoina(z, "/a_very_long_patha_very_long_patha_very_long_patha_very_long_patha_very_long_patha_very_long_patha_very_long_patha_very_long_path"); + assert_se(strlen(jj) > sizeof_field(struct sockaddr_un, sun_path)); + assert_se(rename(j, jj) >= 0); + + /* Bind the *client* socket to some randomized name, to verify that this works correctly. */ + assert_se(asprintf(&clientname, "@%" PRIx64 "/test-bindname", random_u64()) >= 0); + + r = safe_fork("(server)", FORK_DEATHSIG_SIGTERM|FORK_LOG, &pid); + assert_se(r >= 0); + if (r == 0) { + union sockaddr_union peer = {}; + socklen_t peerlen = sizeof(peer); + _cleanup_close_ int rfd = -EBADF; + /* child */ + + rfd = accept4(listener, NULL, 0, SOCK_CLOEXEC); + assert_se(rfd >= 0); + + assert_se(getpeername(rfd, &peer.sa, &peerlen) >= 0); + + assert_se(peer.un.sun_family == AF_UNIX); + assert_se(peerlen > offsetof(struct sockaddr_un, sun_path)); + assert_se(peer.un.sun_path[0] == 0); + assert_se(streq(peer.un.sun_path + 1, clientname + 1)); + +#define TEST_STR "This is a test\nreally." + + assert_se(write(rfd, TEST_STR, strlen(TEST_STR)) == strlen(TEST_STR)); + _exit(EXIT_SUCCESS); + } + + assert_se(read_full_file_full(AT_FDCWD, jj, UINT64_MAX, SIZE_MAX, 0, NULL, &data, &size) == -ENXIO); + assert_se(read_full_file_full(AT_FDCWD, jj, UINT64_MAX, SIZE_MAX, READ_FULL_FILE_CONNECT_SOCKET, clientname, &data, &size) >= 0); + assert_se(size == strlen(TEST_STR)); + assert_se(streq(data, TEST_STR)); + + assert_se(wait_for_terminate_and_check("(server)", pid, WAIT_LOG) >= 0); +#undef TEST_STR +} + +TEST(read_full_file_offset_size) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_(unlink_and_freep) char *fn = NULL; + _cleanup_free_ char *rbuf = NULL; + size_t rbuf_size; + uint8_t buf[4711]; + + random_bytes(buf, sizeof(buf)); + + assert_se(tempfn_random_child(NULL, NULL, &fn) >= 0); + assert_se(f = fopen(fn, "we")); + assert_se(fwrite(buf, 1, sizeof(buf), f) == sizeof(buf)); + assert_se(fflush_and_check(f) >= 0); + + assert_se(read_full_file_full(AT_FDCWD, fn, UINT64_MAX, SIZE_MAX, 0, NULL, &rbuf, &rbuf_size) >= 0); + assert_se(rbuf_size == sizeof(buf)); + assert_se(memcmp(buf, rbuf, rbuf_size) == 0); + rbuf = mfree(rbuf); + + assert_se(read_full_file_full(AT_FDCWD, fn, UINT64_MAX, 128, 0, NULL, &rbuf, &rbuf_size) >= 0); + assert_se(rbuf_size == 128); + assert_se(memcmp(buf, rbuf, rbuf_size) == 0); + rbuf = mfree(rbuf); + + assert_se(read_full_file_full(AT_FDCWD, fn, UINT64_MAX, 128, READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &rbuf, &rbuf_size) == -E2BIG); + assert_se(read_full_file_full(AT_FDCWD, fn, UINT64_MAX, sizeof(buf)-1, READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &rbuf, &rbuf_size) == -E2BIG); + assert_se(read_full_file_full(AT_FDCWD, fn, UINT64_MAX, sizeof(buf), READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &rbuf, &rbuf_size) >= 0); + assert_se(rbuf_size == sizeof(buf)); + assert_se(memcmp(buf, rbuf, rbuf_size) == 0); + rbuf = mfree(rbuf); + + assert_se(read_full_file_full(AT_FDCWD, fn, 47, 128, READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &rbuf, &rbuf_size) == -E2BIG); + assert_se(read_full_file_full(AT_FDCWD, fn, 47, sizeof(buf)-47-1, READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &rbuf, &rbuf_size) == -E2BIG); + assert_se(read_full_file_full(AT_FDCWD, fn, 47, sizeof(buf)-47, READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &rbuf, &rbuf_size) >= 0); + assert_se(rbuf_size == sizeof(buf)-47); + assert_se(memcmp(buf+47, rbuf, rbuf_size) == 0); + rbuf = mfree(rbuf); + + assert_se(read_full_file_full(AT_FDCWD, fn, UINT64_MAX, sizeof(buf)+1, READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &rbuf, &rbuf_size) >= 0); + assert_se(rbuf_size == sizeof(buf)); + assert_se(memcmp(buf, rbuf, rbuf_size) == 0); + rbuf = mfree(rbuf); + + assert_se(read_full_file_full(AT_FDCWD, fn, 1234, SIZE_MAX, 0, NULL, &rbuf, &rbuf_size) >= 0); + assert_se(rbuf_size == sizeof(buf) - 1234); + assert_se(memcmp(buf + 1234, rbuf, rbuf_size) == 0); + rbuf = mfree(rbuf); + + assert_se(read_full_file_full(AT_FDCWD, fn, 2345, 777, 0, NULL, &rbuf, &rbuf_size) >= 0); + assert_se(rbuf_size == 777); + assert_se(memcmp(buf + 2345, rbuf, rbuf_size) == 0); + rbuf = mfree(rbuf); + + assert_se(read_full_file_full(AT_FDCWD, fn, 4700, 20, 0, NULL, &rbuf, &rbuf_size) >= 0); + assert_se(rbuf_size == 11); + assert_se(memcmp(buf + 4700, rbuf, rbuf_size) == 0); + rbuf = mfree(rbuf); + + assert_se(read_full_file_full(AT_FDCWD, fn, 10000, 99, 0, NULL, &rbuf, &rbuf_size) >= 0); + assert_se(rbuf_size == 0); + rbuf = mfree(rbuf); +} + +static void test_read_virtual_file_one(size_t max_size) { + int r; + + log_info("/* %s (max_size=%zu) */", __func__, max_size); + + FOREACH_STRING(filename, + "/proc/1/cmdline", + "/etc/nsswitch.conf", + "/sys/kernel/uevent_seqnum", + "/proc/kcore", + "/proc/kallsyms", + "/proc/self/exe", + "/proc/self/pagemap") { + + _cleanup_free_ char *buf = NULL; + size_t size = 0; + + r = read_virtual_file(filename, max_size, &buf, &size); + if (r < 0) { + log_info_errno(r, "read_virtual_file(\"%s\", %zu): %m", filename, max_size); + assert_se(ERRNO_IS_PRIVILEGE(r) || /* /proc/kcore is not accessible to unpriv */ + IN_SET(r, + -ENOENT, /* Some of the files might be absent */ + -EINVAL, /* too small reads from /proc/self/pagemap trigger EINVAL */ + -EFBIG, /* /proc/kcore and /proc/self/pagemap should be too large */ + -EBADF)); /* /proc/kcore is masked when we are running in docker. */ + } else + log_info("read_virtual_file(\"%s\", %zu): %s (%zu bytes)", filename, max_size, r ? "non-truncated" : "truncated", size); + } +} + +TEST(read_virtual_file) { + test_read_virtual_file_one(0); + test_read_virtual_file_one(1); + test_read_virtual_file_one(2); + test_read_virtual_file_one(20); + test_read_virtual_file_one(4096); + test_read_virtual_file_one(4097); + test_read_virtual_file_one(SIZE_MAX); +} + +TEST(fdopen_independent) { +#define TEST_TEXT "this is some random test text we are going to write to a memfd" + _cleanup_close_ int fd = -EBADF; + _cleanup_fclose_ FILE *f = NULL; + char buf[STRLEN(TEST_TEXT) + 1]; + + fd = memfd_new("fdopen_independent"); + if (fd < 0) { + assert_se(ERRNO_IS_NOT_SUPPORTED(fd)); + return; + } + + assert_se(write(fd, TEST_TEXT, strlen(TEST_TEXT)) == strlen(TEST_TEXT)); + /* we'll leave the read offset at the end of the memfd, the fdopen_independent() descriptors should + * start at the beginning anyway */ + + assert_se(fdopen_independent(fd, "re", &f) >= 0); + zero(buf); + assert_se(fread(buf, 1, sizeof(buf), f) == strlen(TEST_TEXT)); + assert_se(streq(buf, TEST_TEXT)); + assert_se((fcntl(fileno(f), F_GETFL) & O_ACCMODE) == O_RDONLY); + assert_se(FLAGS_SET(fcntl(fileno(f), F_GETFD), FD_CLOEXEC)); + f = safe_fclose(f); + + assert_se(fdopen_independent(fd, "r", &f) >= 0); + zero(buf); + assert_se(fread(buf, 1, sizeof(buf), f) == strlen(TEST_TEXT)); + assert_se(streq(buf, TEST_TEXT)); + assert_se((fcntl(fileno(f), F_GETFL) & O_ACCMODE) == O_RDONLY); + assert_se(!FLAGS_SET(fcntl(fileno(f), F_GETFD), FD_CLOEXEC)); + f = safe_fclose(f); + + assert_se(fdopen_independent(fd, "r+e", &f) >= 0); + zero(buf); + assert_se(fread(buf, 1, sizeof(buf), f) == strlen(TEST_TEXT)); + assert_se(streq(buf, TEST_TEXT)); + assert_se((fcntl(fileno(f), F_GETFL) & O_ACCMODE) == O_RDWR); + assert_se(FLAGS_SET(fcntl(fileno(f), F_GETFD), FD_CLOEXEC)); + f = safe_fclose(f); +} + + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-firewall-util.c b/src/test/test-firewall-util.c new file mode 100644 index 0000000..3f47a30 --- /dev/null +++ b/src/test/test-firewall-util.c @@ -0,0 +1,123 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <unistd.h> + +#include "firewall-util.h" +#include "firewall-util-private.h" +#include "log.h" +#include "random-util.h" +#include "socket-util.h" +#include "tests.h" + +static void test_v6(FirewallContext *ctx) { + union in_addr_union u1, u2, u3; + uint8_t prefixlen; + int r; + + assert_se(ctx); + + log_info("/* %s(backend=%s) */", __func__, firewall_backend_to_string(ctx->backend)); + + if (!socket_ipv6_is_supported()) + return log_info("IPv6 is not supported by kernel, skipping tests."); + + assert_se(in_addr_from_string(AF_INET6, "dead::beef", &u1) >= 0); + assert_se(in_addr_from_string(AF_INET6, "1c3::c01d", &u2) >= 0); + + prefixlen = random_u64_range(128 + 1 - 8) + 8; + random_bytes(&u3, sizeof(u3)); + + assert_se(fw_add_masquerade(&ctx, true, AF_INET6, &u1, 128) >= 0); + assert_se(fw_add_masquerade(&ctx, false, AF_INET6, &u1, 128) >= 0); + assert_se(fw_add_masquerade(&ctx, true, AF_INET6, &u1, 64) >= 0); + assert_se(fw_add_masquerade(&ctx, false, AF_INET6, &u1, 64) >= 0); + assert_se(fw_add_masquerade(&ctx, true, AF_INET6, &u3, prefixlen) >= 0); + assert_se(fw_add_masquerade(&ctx, false, AF_INET6, &u3, prefixlen) >= 0); + + r = fw_add_local_dnat(&ctx, true, AF_INET6, IPPROTO_TCP, 4711, &u1, 815, NULL); + if (r == -EOPNOTSUPP) { + log_info("IPv6 DNAT seems not supported, skipping the following tests."); + return; + } + assert_se(r >= 0); + + assert_se(fw_add_local_dnat(&ctx, true, AF_INET6, IPPROTO_TCP, 4711, &u2, 815, &u1) >= 0); + assert_se(fw_add_local_dnat(&ctx, false, AF_INET6, IPPROTO_TCP, 4711, &u2, 815, NULL) >= 0); + +} + +static union in_addr_union *parse_addr(const char *str, union in_addr_union *u) { + assert_se(str); + assert_se(u); + assert_se(in_addr_from_string(AF_INET, str, u) >= 0); + return u; +} + +static bool test_v4(FirewallContext *ctx) { + union in_addr_union u, v; + int r; + + assert_se(ctx); + + log_info("/* %s(backend=%s) */", __func__, firewall_backend_to_string(ctx->backend)); + +#if HAVE_LIBIPTC + if (ctx->backend == FW_BACKEND_IPTABLES && fw_iptables_init_nat(NULL) < 0) { + log_debug("iptables backend is used, but nat table is not enabled, skipping tests"); + return false; + } +#endif + + assert_se(fw_add_masquerade(&ctx, true, AF_INET, NULL, 0) == -EINVAL); + assert_se(fw_add_masquerade(&ctx, true, AF_INET, parse_addr("10.1.2.0", &u), 0) == -EINVAL); + + r = fw_add_masquerade(&ctx, true, AF_INET, parse_addr("10.1.2.3", &u), 32); + if (r < 0) { + bool ignore = IN_SET(r, -EPERM, -EOPNOTSUPP, -ENOPROTOOPT); + + log_full_errno(ignore ? LOG_DEBUG : LOG_ERR, r, + "Failed to add IPv4 masquerade%s: %m", + ignore ? ", skipping following tests" : ""); + + if (ignore) + return false; + } + assert_se(r >= 0); + + assert_se(fw_add_masquerade(&ctx, true, AF_INET, parse_addr("10.0.2.0", &u), 28) >= 0); + assert_se(fw_add_masquerade(&ctx, false, AF_INET, parse_addr("10.0.2.0", &u), 28) >= 0); + assert_se(fw_add_masquerade(&ctx, false, AF_INET, parse_addr("10.1.2.3", &u), 32) >= 0); + assert_se(fw_add_local_dnat(&ctx, true, AF_INET, IPPROTO_TCP, 4711, parse_addr("1.2.3.4", &u), 815, NULL) >= 0); + assert_se(fw_add_local_dnat(&ctx, true, AF_INET, IPPROTO_TCP, 4711, parse_addr("1.2.3.4", &u), 815, NULL) >= 0); + assert_se(fw_add_local_dnat(&ctx, true, AF_INET, IPPROTO_TCP, 4711, parse_addr("1.2.3.5", &u), 815, parse_addr("1.2.3.4", &v)) >= 0); + assert_se(fw_add_local_dnat(&ctx, false, AF_INET, IPPROTO_TCP, 4711, parse_addr("1.2.3.5", &u), 815, NULL) >= 0); + + return true; +} + +int main(int argc, char *argv[]) { + _cleanup_(fw_ctx_freep) FirewallContext *ctx = NULL; + + test_setup_logging(LOG_DEBUG); + + if (getuid() != 0) + return log_tests_skipped("not root"); + + assert_se(fw_ctx_new(&ctx) >= 0); + assert_se(ctx); + + if (ctx->backend == FW_BACKEND_NONE) + return log_tests_skipped("no firewall backend supported"); + + if (test_v4(ctx) && ctx->backend == FW_BACKEND_NFTABLES) + test_v6(ctx); + +#if HAVE_LIBIPTC + if (ctx->backend != FW_BACKEND_IPTABLES) { + ctx->backend = FW_BACKEND_IPTABLES; + test_v4(ctx); + } +#endif + + return 0; +} diff --git a/src/test/test-format-table.c b/src/test/test-format-table.c new file mode 100644 index 0000000..7d544b1 --- /dev/null +++ b/src/test/test-format-table.c @@ -0,0 +1,635 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <unistd.h> + +#include "alloc-util.h" +#include "format-table.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" +#include "time-util.h" + +TEST(issue_9549) { + _cleanup_(table_unrefp) Table *table = NULL; + _cleanup_free_ char *formatted = NULL; + + assert_se(table = table_new("name", "type", "ro", "usage", "created", "modified")); + assert_se(table_set_align_percent(table, TABLE_HEADER_CELL(3), 100) >= 0); + assert_se(table_add_many(table, + TABLE_STRING, "foooo", + TABLE_STRING, "raw", + TABLE_BOOLEAN, false, + TABLE_SIZE, (uint64_t) (673.7*1024*1024), + TABLE_STRING, "Wed 2018-07-11 00:10:33 JST", + TABLE_STRING, "Wed 2018-07-11 00:16:00 JST") >= 0); + + table_set_width(table, 75); + assert_se(table_format(table, &formatted) >= 0); + + printf("%s\n", formatted); + assert_se(streq(formatted, + "NAME TYPE RO USAGE CREATED MODIFIED\n" + "foooo raw no 673.6M Wed 2018-07-11 00:10:33 J… Wed 2018-07-11 00:16:00 JST\n" + )); +} + +TEST(multiline) { + _cleanup_(table_unrefp) Table *table = NULL; + _cleanup_free_ char *formatted = NULL; + + assert_se(table = table_new("foo", "bar")); + + assert_se(table_set_align_percent(table, TABLE_HEADER_CELL(1), 100) >= 0); + + assert_se(table_add_many(table, + TABLE_STRING, "three\ndifferent\nlines", + TABLE_STRING, "two\nlines\n") >= 0); + + table_set_cell_height_max(table, 1); + assert_se(table_format(table, &formatted) >= 0); + fputs(formatted, stdout); + assert_se(streq(formatted, + "FOO BAR\n" + "three… two…\n")); + formatted = mfree(formatted); + + table_set_cell_height_max(table, 2); + assert_se(table_format(table, &formatted) >= 0); + fputs(formatted, stdout); + assert_se(streq(formatted, + "FOO BAR\n" + "three two\n" + "different… lines\n")); + formatted = mfree(formatted); + + table_set_cell_height_max(table, 3); + assert_se(table_format(table, &formatted) >= 0); + fputs(formatted, stdout); + assert_se(streq(formatted, + "FOO BAR\n" + "three two\n" + "different lines\n" + "lines \n")); + formatted = mfree(formatted); + + table_set_cell_height_max(table, SIZE_MAX); + assert_se(table_format(table, &formatted) >= 0); + fputs(formatted, stdout); + assert_se(streq(formatted, + "FOO BAR\n" + "three two\n" + "different lines\n" + "lines \n")); + formatted = mfree(formatted); + + assert_se(table_add_many(table, + TABLE_STRING, "short", + TABLE_STRING, "a\npair") >= 0); + + assert_se(table_add_many(table, + TABLE_STRING, "short2\n", + TABLE_STRING, "a\nfour\nline\ncell") >= 0); + + table_set_cell_height_max(table, 1); + assert_se(table_format(table, &formatted) >= 0); + fputs(formatted, stdout); + assert_se(streq(formatted, + "FOO BAR\n" + "three… two…\n" + "short a…\n" + "short2 a…\n")); + formatted = mfree(formatted); + + table_set_cell_height_max(table, 2); + assert_se(table_format(table, &formatted) >= 0); + fputs(formatted, stdout); + assert_se(streq(formatted, + "FOO BAR\n" + "three two\n" + "different… lines\n" + "short a\n" + " pair\n" + "short2 a\n" + " four…\n")); + formatted = mfree(formatted); + + table_set_cell_height_max(table, 3); + assert_se(table_format(table, &formatted) >= 0); + fputs(formatted, stdout); + assert_se(streq(formatted, + "FOO BAR\n" + "three two\n" + "different lines\n" + "lines \n" + "short a\n" + " pair\n" + "short2 a\n" + " four\n" + " line…\n")); + formatted = mfree(formatted); + + table_set_cell_height_max(table, SIZE_MAX); + assert_se(table_format(table, &formatted) >= 0); + fputs(formatted, stdout); + assert_se(streq(formatted, + "FOO BAR\n" + "three two\n" + "different lines\n" + "lines \n" + "short a\n" + " pair\n" + "short2 a\n" + " four\n" + " line\n" + " cell\n")); + formatted = mfree(formatted); +} + +TEST(strv) { + _cleanup_(table_unrefp) Table *table = NULL; + _cleanup_free_ char *formatted = NULL; + + assert_se(table = table_new("foo", "bar")); + + assert_se(table_set_align_percent(table, TABLE_HEADER_CELL(1), 100) >= 0); + + assert_se(table_add_many(table, + TABLE_STRV, STRV_MAKE("three", "different", "lines"), + TABLE_STRV, STRV_MAKE("two", "lines")) >= 0); + + table_set_cell_height_max(table, 1); + assert_se(table_format(table, &formatted) >= 0); + fputs(formatted, stdout); + assert_se(streq(formatted, + "FOO BAR\n" + "three… two…\n")); + formatted = mfree(formatted); + + table_set_cell_height_max(table, 2); + assert_se(table_format(table, &formatted) >= 0); + fputs(formatted, stdout); + assert_se(streq(formatted, + "FOO BAR\n" + "three two\n" + "different… lines\n")); + formatted = mfree(formatted); + + table_set_cell_height_max(table, 3); + assert_se(table_format(table, &formatted) >= 0); + fputs(formatted, stdout); + assert_se(streq(formatted, + "FOO BAR\n" + "three two\n" + "different lines\n" + "lines \n")); + formatted = mfree(formatted); + + table_set_cell_height_max(table, SIZE_MAX); + assert_se(table_format(table, &formatted) >= 0); + fputs(formatted, stdout); + assert_se(streq(formatted, + "FOO BAR\n" + "three two\n" + "different lines\n" + "lines \n")); + formatted = mfree(formatted); + + assert_se(table_add_many(table, + TABLE_STRING, "short", + TABLE_STRV, STRV_MAKE("a", "pair")) >= 0); + + assert_se(table_add_many(table, + TABLE_STRV, STRV_MAKE("short2"), + TABLE_STRV, STRV_MAKE("a", "four", "line", "cell")) >= 0); + + table_set_cell_height_max(table, 1); + assert_se(table_format(table, &formatted) >= 0); + fputs(formatted, stdout); + assert_se(streq(formatted, + "FOO BAR\n" + "three… two…\n" + "short a…\n" + "short2 a…\n")); + formatted = mfree(formatted); + + table_set_cell_height_max(table, 2); + assert_se(table_format(table, &formatted) >= 0); + fputs(formatted, stdout); + assert_se(streq(formatted, + "FOO BAR\n" + "three two\n" + "different… lines\n" + "short a\n" + " pair\n" + "short2 a\n" + " four…\n")); + formatted = mfree(formatted); + + table_set_cell_height_max(table, 3); + assert_se(table_format(table, &formatted) >= 0); + fputs(formatted, stdout); + assert_se(streq(formatted, + "FOO BAR\n" + "three two\n" + "different lines\n" + "lines \n" + "short a\n" + " pair\n" + "short2 a\n" + " four\n" + " line…\n")); + formatted = mfree(formatted); + + table_set_cell_height_max(table, SIZE_MAX); + assert_se(table_format(table, &formatted) >= 0); + fputs(formatted, stdout); + assert_se(streq(formatted, + "FOO BAR\n" + "three two\n" + "different lines\n" + "lines \n" + "short a\n" + " pair\n" + "short2 a\n" + " four\n" + " line\n" + " cell\n")); + formatted = mfree(formatted); +} + +TEST(strv_wrapped) { + _cleanup_(table_unrefp) Table *table = NULL; + _cleanup_free_ char *formatted = NULL; + + assert_se(table = table_new("foo", "bar")); + + assert_se(table_set_align_percent(table, TABLE_HEADER_CELL(1), 100) >= 0); + + assert_se(table_add_many(table, + TABLE_STRV_WRAPPED, STRV_MAKE("three", "different", "lines"), + TABLE_STRV_WRAPPED, STRV_MAKE("two", "lines")) >= 0); + + table_set_cell_height_max(table, 1); + assert_se(table_format(table, &formatted) >= 0); + fputs(formatted, stdout); + assert_se(streq(formatted, + "FOO BAR\n" + "three different lines two lines\n")); + formatted = mfree(formatted); + + table_set_cell_height_max(table, 2); + assert_se(table_format(table, &formatted) >= 0); + fputs(formatted, stdout); + assert_se(streq(formatted, + "FOO BAR\n" + "three different lines two lines\n")); + formatted = mfree(formatted); + + table_set_cell_height_max(table, 3); + assert_se(table_format(table, &formatted) >= 0); + fputs(formatted, stdout); + assert_se(streq(formatted, + "FOO BAR\n" + "three different lines two lines\n")); + formatted = mfree(formatted); + + table_set_cell_height_max(table, SIZE_MAX); + assert_se(table_format(table, &formatted) >= 0); + fputs(formatted, stdout); + assert_se(streq(formatted, + "FOO BAR\n" + "three different lines two lines\n")); + formatted = mfree(formatted); + + assert_se(table_add_many(table, + TABLE_STRING, "short", + TABLE_STRV_WRAPPED, STRV_MAKE("a", "pair")) >= 0); + + assert_se(table_add_many(table, + TABLE_STRV_WRAPPED, STRV_MAKE("short2"), + TABLE_STRV_WRAPPED, STRV_MAKE("a", "eight", "line", "ćęłł", + "___5___", "___6___", "___7___", "___8___")) >= 0); + + table_set_cell_height_max(table, 1); + assert_se(table_format(table, &formatted) >= 0); + fputs(formatted, stdout); + assert_se(streq(formatted, + "FOO BAR\n" + "three different… two lines\n" + "short a pair\n" + "short2 a eight line ćęłł…\n")); + formatted = mfree(formatted); + + table_set_cell_height_max(table, 2); + assert_se(table_format(table, &formatted) >= 0); + fputs(formatted, stdout); + assert_se(streq(formatted, + "FOO BAR\n" + "three different two lines\n" + "lines \n" + "short a pair\n" + "short2 a eight line ćęłł\n" + " ___5___ ___6___…\n")); + formatted = mfree(formatted); + + table_set_cell_height_max(table, 3); + assert_se(table_format(table, &formatted) >= 0); + fputs(formatted, stdout); + assert_se(streq(formatted, + "FOO BAR\n" + "three different two lines\n" + "lines \n" + "short a pair\n" + "short2 a eight line ćęłł\n" + " ___5___ ___6___\n" + " ___7___ ___8___\n")); + formatted = mfree(formatted); + + table_set_cell_height_max(table, SIZE_MAX); + assert_se(table_format(table, &formatted) >= 0); + fputs(formatted, stdout); + assert_se(streq(formatted, + "FOO BAR\n" + "three different two lines\n" + "lines \n" + "short a pair\n" + "short2 a eight line ćęłł\n" + " ___5___ ___6___\n" + " ___7___ ___8___\n")); + formatted = mfree(formatted); +} + +TEST(json) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL; + _cleanup_(table_unrefp) Table *t = NULL; + + assert_se(t = table_new("foo bar", "quux", "piep miau")); + assert_se(table_set_json_field_name(t, 2, "zzz") >= 0); + + assert_se(table_add_many(t, + TABLE_STRING, "v1", + TABLE_UINT64, UINT64_C(4711), + TABLE_BOOLEAN, true) >= 0); + + assert_se(table_add_many(t, + TABLE_STRV, STRV_MAKE("a", "b", "c"), + TABLE_EMPTY, + TABLE_MODE, 0755) >= 0); + + assert_se(table_to_json(t, &v) >= 0); + + assert_se(json_build(&w, + JSON_BUILD_ARRAY( + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("foo_bar", JSON_BUILD_CONST_STRING("v1")), + JSON_BUILD_PAIR("quux", JSON_BUILD_UNSIGNED(4711)), + JSON_BUILD_PAIR("zzz", JSON_BUILD_BOOLEAN(true))), + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("foo_bar", JSON_BUILD_STRV(STRV_MAKE("a", "b", "c"))), + JSON_BUILD_PAIR("quux", JSON_BUILD_NULL), + JSON_BUILD_PAIR("zzz", JSON_BUILD_UNSIGNED(0755))))) >= 0); + + assert_se(json_variant_equal(v, w)); +} + +TEST(table) { + _cleanup_(table_unrefp) Table *t = NULL; + _cleanup_free_ char *formatted = NULL; + + assert_se(t = table_new("one", "two", "three", "four")); + + assert_se(table_set_align_percent(t, TABLE_HEADER_CELL(3), 100) >= 0); + + assert_se(table_add_many(t, + TABLE_STRING, "xxx", + TABLE_STRING, "yyy", + TABLE_BOOLEAN, true, + TABLE_INT, -1) >= 0); + + assert_se(table_add_many(t, + TABLE_STRING, "a long field", + TABLE_STRING, "yyy", + TABLE_SET_UPPERCASE, 1, + TABLE_BOOLEAN, false, + TABLE_INT, -999999) >= 0); + + assert_se(table_format(t, &formatted) >= 0); + printf("%s\n", formatted); + + assert_se(streq(formatted, + "ONE TWO THREE FOUR\n" + "xxx yyy yes -1\n" + "a long field YYY no -999999\n")); + + formatted = mfree(formatted); + + table_set_width(t, 40); + + assert_se(table_format(t, &formatted) >= 0); + printf("%s\n", formatted); + + assert_se(streq(formatted, + "ONE TWO THREE FOUR\n" + "xxx yyy yes -1\n" + "a long field YYY no -999999\n")); + + formatted = mfree(formatted); + + table_set_width(t, 15); + assert_se(table_format(t, &formatted) >= 0); + printf("%s\n", formatted); + + assert_se(streq(formatted, + "ONE TWO TH… FO…\n" + "xxx yyy yes -1\n" + "a … YYY no -9…\n")); + + formatted = mfree(formatted); + + table_set_width(t, 5); + assert_se(table_format(t, &formatted) >= 0); + printf("%s\n", formatted); + + assert_se(streq(formatted, + "… … … …\n" + "… … … …\n" + "… … … …\n")); + + formatted = mfree(formatted); + + table_set_width(t, 3); + assert_se(table_format(t, &formatted) >= 0); + printf("%s\n", formatted); + + assert_se(streq(formatted, + "… … … …\n" + "… … … …\n" + "… … … …\n")); + + formatted = mfree(formatted); + + table_set_width(t, SIZE_MAX); + assert_se(table_set_sort(t, (size_t) 0, (size_t) 2, SIZE_MAX) >= 0); + + assert_se(table_format(t, &formatted) >= 0); + printf("%s\n", formatted); + + assert_se(streq(formatted, + "ONE TWO THREE FOUR\n" + "a long field YYY no -999999\n" + "xxx yyy yes -1\n")); + + formatted = mfree(formatted); + + table_set_header(t, false); + + assert_se(table_add_many(t, + TABLE_STRING, "fäää", + TABLE_STRING, "uuu", + TABLE_BOOLEAN, true, + TABLE_INT, 42) >= 0); + + assert_se(table_add_many(t, + TABLE_STRING, "fäää", + TABLE_STRING, "zzz", + TABLE_BOOLEAN, false, + TABLE_INT, 0) >= 0); + + assert_se(table_add_many(t, + TABLE_EMPTY, + TABLE_SIZE, (uint64_t) 4711, + TABLE_TIMESPAN, (usec_t) 5*USEC_PER_MINUTE, + TABLE_INT64, (uint64_t) -123456789) >= 0); + + assert_se(table_format(t, &formatted) >= 0); + printf("%s\n", formatted); + + assert_se(streq(formatted, + "a long field YYY no -999999\n" + "fäää zzz no 0\n" + "fäää uuu yes 42\n" + "xxx yyy yes -1\n" + " 4.6K 5min -123456789\n")); + + formatted = mfree(formatted); + + assert_se(table_set_display(t, (size_t) 2, (size_t) 0, (size_t) 2, (size_t) 0, (size_t) 0, SIZE_MAX) >= 0); + + assert_se(table_format(t, &formatted) >= 0); + printf("%s\n", formatted); + + if (isatty(STDOUT_FILENO)) + assert_se(streq(formatted, + "no a long f… no a long f… a long fi…\n" + "no fäää no fäää fäää\n" + "yes fäää yes fäää fäää\n" + "yes xxx yes xxx xxx\n" + "5min 5min \n")); + else + assert_se(streq(formatted, + "no a long field no a long field a long field\n" + "no fäää no fäää fäää\n" + "yes fäää yes fäää fäää\n" + "yes xxx yes xxx xxx\n" + "5min 5min \n")); +} + +TEST(vertical) { + _cleanup_(table_unrefp) Table *t = NULL; + _cleanup_free_ char *formatted = NULL; + + assert_se(t = table_new_vertical()); + + assert_se(table_add_many(t, + TABLE_FIELD, "pfft aa", TABLE_STRING, "foo", + TABLE_FIELD, "uuu o", TABLE_SIZE, UINT64_C(1024), + TABLE_FIELD, "lllllllllllo", TABLE_STRING, "jjjjjjjjjjjjjjjjj") >= 0); + + assert_se(table_set_json_field_name(t, 1, "dimpfelmoser") >= 0); + + assert_se(table_format(t, &formatted) >= 0); + + assert_se(streq(formatted, + " pfft aa: foo\n" + " uuu o: 1.0K\n" + "lllllllllllo: jjjjjjjjjjjjjjjjj\n")); + + _cleanup_(json_variant_unrefp) JsonVariant *a = NULL, *b = NULL; + assert_se(table_to_json(t, &a) >= 0); + + assert_se(json_build(&b, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("pfft_aa", JSON_BUILD_STRING("foo")), + JSON_BUILD_PAIR("dimpfelmoser", JSON_BUILD_UNSIGNED(1024)), + JSON_BUILD_PAIR("lllllllllllo", JSON_BUILD_STRING("jjjjjjjjjjjjjjjjj")))) >= 0); + + assert_se(json_variant_equal(a, b)); +} + +TEST(path_basename) { + _cleanup_(table_unrefp) Table *t = NULL; + _cleanup_free_ char *formatted = NULL; + + assert_se(t = table_new("x")); + + table_set_header(t, false); + + assert_se(table_add_many(t, + TABLE_PATH_BASENAME, "/foo/bar", + TABLE_PATH_BASENAME, "/quux/bar", + TABLE_PATH_BASENAME, "/foo/baz") >= 0); + + assert_se(table_format(t, &formatted) >= 0); + + assert_se(streq(formatted, "bar\nbar\nbaz\n")); +} + +TEST(dup_cell) { + _cleanup_(table_unrefp) Table *t = NULL; + _cleanup_free_ char *formatted = NULL; + + assert_se(t = table_new("foo", "bar", "x", "baz", ".", "%", "!", "~", "+")); + table_set_width(t, 75); + + assert_se(table_add_many(t, + TABLE_STRING, "hello", + TABLE_UINT8, UINT8_C(42), + TABLE_UINT16, UINT16_C(666), + TABLE_UINT32, UINT32_C(253), + TABLE_PERCENT, 0, + TABLE_PATH_BASENAME, "/foo/bar", + TABLE_STRING, "aaa", + TABLE_STRING, "bbb", + TABLE_STRING, "ccc") >= 0); + + /* Add the second row by duping cells */ + for (size_t i = 0; i < table_get_columns(t); i++) + assert_se(table_dup_cell(t, table_get_cell(t, 1, i)) >= 0); + + /* Another row, but dupe the last three strings from the same cell */ + assert_se(table_add_many(t, + TABLE_STRING, "aaa", + TABLE_UINT8, UINT8_C(0), + TABLE_UINT16, UINT16_C(65535), + TABLE_UINT32, UINT32_C(4294967295), + TABLE_PERCENT, 100, + TABLE_PATH_BASENAME, "../") >= 0); + + for (size_t i = 6; i < table_get_columns(t); i++) + assert_se(table_dup_cell(t, table_get_cell(t, 2, 0)) >= 0); + + assert_se(table_format(t, &formatted) >= 0); + printf("%s\n", formatted); + assert_se(streq(formatted, + "FOO BAR X BAZ . % ! ~ +\n" + "hello 42 666 253 0% bar aaa bbb ccc\n" + "hello 42 666 253 0% bar aaa bbb ccc\n" + "aaa 0 65535 4294967295 100% ../ hello hello hello\n")); +} + +static int intro(void) { + assert_se(setenv("SYSTEMD_COLORS", "0", 1) >= 0); + assert_se(setenv("COLUMNS", "40", 1) >= 0); + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/test/test-format-util.c b/src/test/test-format-util.c new file mode 100644 index 0000000..3063509 --- /dev/null +++ b/src/test/test-format-util.c @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "format-util.h" +#include "macro.h" +#include "string-util.h" +#include "tests.h" +#include "uchar.h" + +/* Do some basic checks on STRLEN() and DECIMAL_STR_MAX() */ +assert_cc(STRLEN("") == 0); +assert_cc(STRLEN("a") == 1); +assert_cc(STRLEN("123") == 3); +assert_cc(STRLEN(u8"") == 0); +assert_cc(STRLEN(u8"a") == 1); +assert_cc(STRLEN(u8"123") == 3); +assert_cc(STRLEN(u"") == 0); +assert_cc(STRLEN(u"a") == sizeof(char16_t)); +assert_cc(STRLEN(u"123") == 3 * sizeof(char16_t)); +assert_cc(STRLEN(U"") == 0); +assert_cc(STRLEN(U"a") == sizeof(char32_t)); +assert_cc(STRLEN(U"123") == 3 * sizeof(char32_t)); +assert_cc(STRLEN(L"") == 0); +assert_cc(STRLEN(L"a") == sizeof(wchar_t)); +assert_cc(STRLEN(L"123") == 3 * sizeof(wchar_t)); +assert_cc(DECIMAL_STR_MAX(uint8_t) == STRLEN("255")+1); +assert_cc(DECIMAL_STR_MAX(int8_t) == STRLEN("-127")+1); +assert_cc(DECIMAL_STR_MAX(uint64_t) == STRLEN("18446744073709551615")+1); +assert_cc(DECIMAL_STR_MAX(int64_t) == CONST_MAX(STRLEN("-9223372036854775808"), STRLEN("9223372036854775807"))+1); +assert_cc(DECIMAL_STR_MAX(signed char) == STRLEN("-127")+1); +assert_cc(DECIMAL_STR_MAX(unsigned char) == STRLEN("255")+1); +assert_cc(CONST_MAX(DECIMAL_STR_MAX(int8_t), STRLEN("xxx")) == 5); + +static void test_format_bytes_one(uint64_t val, bool trailing_B, const char *iec_with_p, const char *iec_without_p, + const char *si_with_p, const char *si_without_p) { + char buf[FORMAT_BYTES_MAX]; + + assert_se(streq_ptr(format_bytes_full(buf, sizeof buf, val, FORMAT_BYTES_USE_IEC | FORMAT_BYTES_BELOW_POINT | (trailing_B ? FORMAT_BYTES_TRAILING_B : 0)), iec_with_p)); + assert_se(streq_ptr(format_bytes_full(buf, sizeof buf, val, FORMAT_BYTES_USE_IEC | (trailing_B ? FORMAT_BYTES_TRAILING_B : 0)), iec_without_p)); + assert_se(streq_ptr(format_bytes_full(buf, sizeof buf, val, FORMAT_BYTES_BELOW_POINT | (trailing_B ? FORMAT_BYTES_TRAILING_B : 0)), si_with_p)); + assert_se(streq_ptr(format_bytes_full(buf, sizeof buf, val, trailing_B ? FORMAT_BYTES_TRAILING_B : 0), si_without_p)); +} + +TEST(format_bytes) { + test_format_bytes_one(900, true, "900B", "900B", "900B", "900B"); + test_format_bytes_one(900, false, "900", "900", "900", "900"); + test_format_bytes_one(1023, true, "1023B", "1023B", "1.0K", "1K"); + test_format_bytes_one(1023, false, "1023", "1023", "1.0K", "1K"); + test_format_bytes_one(1024, true, "1.0K", "1K", "1.0K", "1K"); + test_format_bytes_one(1024, false, "1.0K", "1K", "1.0K", "1K"); + test_format_bytes_one(1100, true, "1.0K", "1K", "1.1K", "1K"); + test_format_bytes_one(1500, true, "1.4K", "1K", "1.5K", "1K"); + test_format_bytes_one(UINT64_C(3)*1024*1024, true, "3.0M", "3M", "3.1M", "3M"); + test_format_bytes_one(UINT64_C(3)*1024*1024*1024, true, "3.0G", "3G", "3.2G", "3G"); + test_format_bytes_one(UINT64_C(3)*1024*1024*1024*1024, true, "3.0T", "3T", "3.2T", "3T"); + test_format_bytes_one(UINT64_C(3)*1024*1024*1024*1024*1024, true, "3.0P", "3P", "3.3P", "3P"); + test_format_bytes_one(UINT64_C(3)*1024*1024*1024*1024*1024*1024, true, "3.0E", "3E", "3.4E", "3E"); + test_format_bytes_one(UINT64_MAX, true, NULL, NULL, NULL, NULL); + test_format_bytes_one(UINT64_MAX, false, NULL, NULL, NULL, NULL); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c new file mode 100644 index 0000000..ef335b4 --- /dev/null +++ b/src/test/test-fs-util.c @@ -0,0 +1,796 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <sys/file.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "copy.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "macro.h" +#include "mkdir.h" +#include "path-util.h" +#include "process-util.h" +#include "random-util.h" +#include "rm-rf.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "sync-util.h" +#include "tests.h" +#include "tmpfile-util.h" +#include "umask-util.h" +#include "user-util.h" +#include "virt.h" + +static const char *arg_test_dir = NULL; + +TEST(readlink_and_make_absolute) { + const char *tempdir, *name, *name2, *name_alias; + _cleanup_free_ char *r1 = NULL, *r2 = NULL, *pwd = NULL; + + tempdir = strjoina(arg_test_dir ?: "/tmp", "/test-readlink_and_make_absolute"); + name = strjoina(tempdir, "/original"); + name2 = "test-readlink_and_make_absolute/original"; + name_alias = strjoina(arg_test_dir ?: "/tmp", "/test-readlink_and_make_absolute-alias"); + + assert_se(mkdir_safe(tempdir, 0755, getuid(), getgid(), MKDIR_WARN_MODE) >= 0); + assert_se(touch(name) >= 0); + + if (symlink(name, name_alias) < 0) { + assert_se(IN_SET(errno, EINVAL, ENOSYS, ENOTTY, EPERM)); + log_tests_skipped_errno(errno, "symlink() not possible"); + } else { + assert_se(readlink_and_make_absolute(name_alias, &r1) >= 0); + assert_se(streq(r1, name)); + assert_se(unlink(name_alias) >= 0); + + assert_se(safe_getcwd(&pwd) >= 0); + + assert_se(chdir(tempdir) >= 0); + assert_se(symlink(name2, name_alias) >= 0); + assert_se(readlink_and_make_absolute(name_alias, &r2) >= 0); + assert_se(streq(r2, name)); + assert_se(unlink(name_alias) >= 0); + + assert_se(chdir(pwd) >= 0); + } + + assert_se(rm_rf(tempdir, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); +} + +TEST(get_files_in_directory) { + _cleanup_strv_free_ char **l = NULL, **t = NULL; + + assert_se(get_files_in_directory(arg_test_dir ?: "/tmp", &l) >= 0); + assert_se(get_files_in_directory(".", &t) >= 0); + assert_se(get_files_in_directory(".", NULL) >= 0); +} + +TEST(var_tmp) { + _cleanup_free_ char *tmpdir_backup = NULL, *temp_backup = NULL, *tmp_backup = NULL; + const char *tmp_dir = NULL, *t; + + t = getenv("TMPDIR"); + if (t) { + tmpdir_backup = strdup(t); + assert_se(tmpdir_backup); + } + + t = getenv("TEMP"); + if (t) { + temp_backup = strdup(t); + assert_se(temp_backup); + } + + t = getenv("TMP"); + if (t) { + tmp_backup = strdup(t); + assert_se(tmp_backup); + } + + assert_se(unsetenv("TMPDIR") >= 0); + assert_se(unsetenv("TEMP") >= 0); + assert_se(unsetenv("TMP") >= 0); + + assert_se(var_tmp_dir(&tmp_dir) >= 0); + assert_se(streq(tmp_dir, "/var/tmp")); + + assert_se(setenv("TMPDIR", "/tmp", true) >= 0); + assert_se(streq(getenv("TMPDIR"), "/tmp")); + + assert_se(var_tmp_dir(&tmp_dir) >= 0); + assert_se(streq(tmp_dir, "/tmp")); + + assert_se(setenv("TMPDIR", "/88_does_not_exist_88", true) >= 0); + assert_se(streq(getenv("TMPDIR"), "/88_does_not_exist_88")); + + assert_se(var_tmp_dir(&tmp_dir) >= 0); + assert_se(streq(tmp_dir, "/var/tmp")); + + if (tmpdir_backup) { + assert_se(setenv("TMPDIR", tmpdir_backup, true) >= 0); + assert_se(streq(getenv("TMPDIR"), tmpdir_backup)); + } + + if (temp_backup) { + assert_se(setenv("TEMP", temp_backup, true) >= 0); + assert_se(streq(getenv("TEMP"), temp_backup)); + } + + if (tmp_backup) { + assert_se(setenv("TMP", tmp_backup, true) >= 0); + assert_se(streq(getenv("TMP"), tmp_backup)); + } +} + +TEST(dot_or_dot_dot) { + assert_se(!dot_or_dot_dot(NULL)); + assert_se(!dot_or_dot_dot("")); + assert_se(!dot_or_dot_dot("xxx")); + assert_se(dot_or_dot_dot(".")); + assert_se(dot_or_dot_dot("..")); + assert_se(!dot_or_dot_dot(".foo")); + assert_se(!dot_or_dot_dot("..foo")); +} + +TEST(access_fd) { + _cleanup_(rmdir_and_freep) char *p = NULL; + _cleanup_close_ int fd = -EBADF; + const char *a; + + a = strjoina(arg_test_dir ?: "/tmp", "/access-fd.XXXXXX"); + assert_se(mkdtemp_malloc(a, &p) >= 0); + + fd = open(p, O_RDONLY|O_DIRECTORY|O_CLOEXEC); + assert_se(fd >= 0); + + assert_se(access_fd(fd, R_OK) >= 0); + assert_se(access_fd(fd, F_OK) >= 0); + assert_se(access_fd(fd, W_OK) >= 0); + + assert_se(fchmod(fd, 0000) >= 0); + + assert_se(access_fd(fd, F_OK) >= 0); + + if (geteuid() == 0) { + assert_se(access_fd(fd, R_OK) >= 0); + assert_se(access_fd(fd, W_OK) >= 0); + } else { + assert_se(access_fd(fd, R_OK) == -EACCES); + assert_se(access_fd(fd, W_OK) == -EACCES); + } +} + +TEST(touch_file) { + uid_t test_uid, test_gid; + _cleanup_(rm_rf_physical_and_freep) char *p = NULL; + struct stat st; + const char *a; + usec_t test_mtime; + int r; + + test_uid = geteuid() == 0 ? 65534 : getuid(); + test_gid = geteuid() == 0 ? 65534 : getgid(); + + test_mtime = usec_sub_unsigned(now(CLOCK_REALTIME), USEC_PER_WEEK); + + a = strjoina(arg_test_dir ?: "/dev/shm", "/touch-file-XXXXXX"); + assert_se(mkdtemp_malloc(a, &p) >= 0); + + a = strjoina(p, "/regular"); + r = touch_file(a, false, test_mtime, test_uid, test_gid, 0640); + if (r < 0) { + assert_se(IN_SET(r, -EINVAL, -ENOSYS, -ENOTTY, -EPERM)); + log_tests_skipped_errno(errno, "touch_file() not possible"); + return; + } + + assert_se(lstat(a, &st) >= 0); + assert_se(st.st_uid == test_uid); + assert_se(st.st_gid == test_gid); + assert_se(S_ISREG(st.st_mode)); + assert_se((st.st_mode & 0777) == 0640); + assert_se(timespec_load(&st.st_mtim) == test_mtime); + + a = strjoina(p, "/dir"); + assert_se(mkdir(a, 0775) >= 0); + assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0); + assert_se(lstat(a, &st) >= 0); + assert_se(st.st_uid == test_uid); + assert_se(st.st_gid == test_gid); + assert_se(S_ISDIR(st.st_mode)); + assert_se((st.st_mode & 0777) == 0640); + assert_se(timespec_load(&st.st_mtim) == test_mtime); + + a = strjoina(p, "/fifo"); + assert_se(mkfifo(a, 0775) >= 0); + assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0); + assert_se(lstat(a, &st) >= 0); + assert_se(st.st_uid == test_uid); + assert_se(st.st_gid == test_gid); + assert_se(S_ISFIFO(st.st_mode)); + assert_se((st.st_mode & 0777) == 0640); + assert_se(timespec_load(&st.st_mtim) == test_mtime); + + a = strjoina(p, "/sock"); + assert_se(mknod(a, 0775 | S_IFSOCK, 0) >= 0); + assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0); + assert_se(lstat(a, &st) >= 0); + assert_se(st.st_uid == test_uid); + assert_se(st.st_gid == test_gid); + assert_se(S_ISSOCK(st.st_mode)); + assert_se((st.st_mode & 0777) == 0640); + assert_se(timespec_load(&st.st_mtim) == test_mtime); + + if (geteuid() == 0) { + a = strjoina(p, "/bdev"); + r = mknod(a, 0775 | S_IFBLK, makedev(0, 0)); + if (r < 0 && errno == EPERM && detect_container() > 0) { + log_notice("Running in unprivileged container? Skipping remaining tests in %s", __func__); + return; + } + assert_se(r >= 0); + assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0); + assert_se(lstat(a, &st) >= 0); + assert_se(st.st_uid == test_uid); + assert_se(st.st_gid == test_gid); + assert_se(S_ISBLK(st.st_mode)); + assert_se((st.st_mode & 0777) == 0640); + assert_se(timespec_load(&st.st_mtim) == test_mtime); + + a = strjoina(p, "/cdev"); + assert_se(mknod(a, 0775 | S_IFCHR, makedev(0, 0)) >= 0); + assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0); + assert_se(lstat(a, &st) >= 0); + assert_se(st.st_uid == test_uid); + assert_se(st.st_gid == test_gid); + assert_se(S_ISCHR(st.st_mode)); + assert_se((st.st_mode & 0777) == 0640); + assert_se(timespec_load(&st.st_mtim) == test_mtime); + } + + a = strjoina(p, "/lnk"); + assert_se(symlink("target", a) >= 0); + assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0); + assert_se(lstat(a, &st) >= 0); + assert_se(st.st_uid == test_uid); + assert_se(st.st_gid == test_gid); + assert_se(S_ISLNK(st.st_mode)); + assert_se(timespec_load(&st.st_mtim) == test_mtime); +} + +TEST(unlinkat_deallocate) { + _cleanup_free_ char *p = NULL; + _cleanup_close_ int fd = -EBADF; + struct stat st; + + assert_se(tempfn_random_child(arg_test_dir, "unlink-deallocation", &p) >= 0); + + fd = open(p, O_WRONLY|O_CLOEXEC|O_CREAT|O_EXCL, 0600); + assert_se(fd >= 0); + + assert_se(write(fd, "hallo\n", 6) == 6); + + assert_se(fstat(fd, &st) >= 0); + assert_se(st.st_size == 6); + assert_se(st.st_blocks > 0); + assert_se(st.st_nlink == 1); + + assert_se(unlinkat_deallocate(AT_FDCWD, p, UNLINK_ERASE) >= 0); + + assert_se(fstat(fd, &st) >= 0); + assert_se(IN_SET(st.st_size, 0, 6)); /* depending on whether hole punching worked the size will be 6 + (it worked) or 0 (we had to resort to truncation) */ + assert_se(st.st_blocks == 0); + assert_se(st.st_nlink == 0); +} + +TEST(fsync_directory_of_file) { + _cleanup_close_ int fd = -EBADF; + + fd = open_tmpfile_unlinkable(arg_test_dir, O_RDWR); + assert_se(fd >= 0); + + assert_se(fsync_directory_of_file(fd) >= 0); +} + +TEST(rename_noreplace) { + static const char* const table[] = { + "/reg", + "/dir", + "/fifo", + "/socket", + "/symlink", + NULL + }; + + _cleanup_(rm_rf_physical_and_freep) char *z = NULL; + const char *j = NULL; + + if (arg_test_dir) + j = strjoina(arg_test_dir, "/testXXXXXX"); + assert_se(mkdtemp_malloc(j, &z) >= 0); + + j = strjoina(z, table[0]); + assert_se(touch(j) >= 0); + + j = strjoina(z, table[1]); + assert_se(mkdir(j, 0777) >= 0); + + j = strjoina(z, table[2]); + (void) mkfifo(j, 0777); + + j = strjoina(z, table[3]); + (void) mknod(j, S_IFSOCK | 0777, 0); + + j = strjoina(z, table[4]); + (void) symlink("foobar", j); + + STRV_FOREACH(a, table) { + _cleanup_free_ char *x = NULL, *y = NULL; + + x = strjoin(z, *a); + assert_se(x); + + if (access(x, F_OK) < 0) { + assert_se(errno == ENOENT); + continue; + } + + STRV_FOREACH(b, table) { + _cleanup_free_ char *w = NULL; + + w = strjoin(z, *b); + assert_se(w); + + if (access(w, F_OK) < 0) { + assert_se(errno == ENOENT); + continue; + } + + assert_se(rename_noreplace(AT_FDCWD, x, AT_FDCWD, w) == -EEXIST); + } + + y = strjoin(z, "/somethingelse"); + assert_se(y); + + assert_se(rename_noreplace(AT_FDCWD, x, AT_FDCWD, y) >= 0); + assert_se(rename_noreplace(AT_FDCWD, y, AT_FDCWD, x) >= 0); + } +} + +TEST(chmod_and_chown) { + _cleanup_(rm_rf_physical_and_freep) char *d = NULL; + struct stat st; + const char *p; + + if (geteuid() != 0) + return; + + BLOCK_WITH_UMASK(0000); + + assert_se(mkdtemp_malloc(NULL, &d) >= 0); + + p = strjoina(d, "/reg"); + assert_se(mknod(p, S_IFREG | 0123, 0) >= 0); + + assert_se(chmod_and_chown(p, S_IFREG | 0321, 1, 2) >= 0); + assert_se(chmod_and_chown(p, S_IFDIR | 0555, 3, 4) == -EINVAL); + + assert_se(lstat(p, &st) >= 0); + assert_se(S_ISREG(st.st_mode)); + assert_se((st.st_mode & 07777) == 0321); + + p = strjoina(d, "/dir"); + assert_se(mkdir(p, 0123) >= 0); + + assert_se(chmod_and_chown(p, S_IFDIR | 0321, 1, 2) >= 0); + assert_se(chmod_and_chown(p, S_IFREG | 0555, 3, 4) == -EINVAL); + + assert_se(lstat(p, &st) >= 0); + assert_se(S_ISDIR(st.st_mode)); + assert_se((st.st_mode & 07777) == 0321); + + p = strjoina(d, "/lnk"); + assert_se(symlink("idontexist", p) >= 0); + + assert_se(chmod_and_chown(p, S_IFLNK | 0321, 1, 2) >= 0); + assert_se(chmod_and_chown(p, S_IFREG | 0555, 3, 4) == -EINVAL); + assert_se(chmod_and_chown(p, S_IFDIR | 0555, 3, 4) == -EINVAL); + + assert_se(lstat(p, &st) >= 0); + assert_se(S_ISLNK(st.st_mode)); +} + +static void create_binary_file(const char *p, const void *data, size_t l) { + _cleanup_close_ int fd = -EBADF; + + fd = open(p, O_CREAT|O_WRONLY|O_EXCL|O_CLOEXEC, 0600); + assert_se(fd >= 0); + assert_se(write(fd, data, l) == (ssize_t) l); +} + +TEST(conservative_rename) { + _cleanup_(unlink_and_freep) char *p = NULL; + _cleanup_free_ char *q = NULL; + size_t l = 16*1024 + random_u64() % (32 * 1024); /* some randomly sized buffer 16k…48k */ + uint8_t buffer[l+1]; + + random_bytes(buffer, l); + + assert_se(tempfn_random_child(NULL, NULL, &p) >= 0); + create_binary_file(p, buffer, l); + + assert_se(tempfn_random_child(NULL, NULL, &q) >= 0); + + /* Check that the hardlinked "copy" is detected */ + assert_se(link(p, q) >= 0); + assert_se(conservative_renameat(AT_FDCWD, q, AT_FDCWD, p) == 0); + assert_se(access(q, F_OK) < 0 && errno == ENOENT); + + /* Check that a manual copy is detected */ + assert_se(copy_file(p, q, 0, MODE_INVALID, COPY_REFLINK) >= 0); + assert_se(conservative_renameat(AT_FDCWD, q, AT_FDCWD, p) == 0); + assert_se(access(q, F_OK) < 0 && errno == ENOENT); + + /* Check that a manual new writeout is also detected */ + create_binary_file(q, buffer, l); + assert_se(conservative_renameat(AT_FDCWD, q, AT_FDCWD, p) == 0); + assert_se(access(q, F_OK) < 0 && errno == ENOENT); + + /* Check that a minimally changed version is detected */ + buffer[47] = ~buffer[47]; + create_binary_file(q, buffer, l); + assert_se(conservative_renameat(AT_FDCWD, q, AT_FDCWD, p) > 0); + assert_se(access(q, F_OK) < 0 && errno == ENOENT); + + /* Check that this really is new updated version */ + create_binary_file(q, buffer, l); + assert_se(conservative_renameat(AT_FDCWD, q, AT_FDCWD, p) == 0); + assert_se(access(q, F_OK) < 0 && errno == ENOENT); + + /* Make sure we detect extended files */ + buffer[l++] = 47; + create_binary_file(q, buffer, l); + assert_se(conservative_renameat(AT_FDCWD, q, AT_FDCWD, p) > 0); + assert_se(access(q, F_OK) < 0 && errno == ENOENT); + + /* Make sure we detect truncated files */ + l--; + create_binary_file(q, buffer, l); + assert_se(conservative_renameat(AT_FDCWD, q, AT_FDCWD, p) > 0); + assert_se(access(q, F_OK) < 0 && errno == ENOENT); +} + +static void test_rmdir_parents_one( + const char *prefix, + const char *path, + const char *stop, + int expected, + const char *test_exist, + const char *test_nonexist_subdir) { + + const char *p, *s; + + log_debug("/* %s(%s, %s) */", __func__, path, stop); + + p = strjoina(prefix, path); + s = strjoina(prefix, stop); + + if (expected >= 0) + assert_se(mkdir_parents(p, 0700) >= 0); + + assert_se(rmdir_parents(p, s) == expected); + + if (expected >= 0) { + const char *e, *f; + + e = strjoina(prefix, test_exist); + f = strjoina(e, test_nonexist_subdir); + + assert_se(access(e, F_OK) >= 0); + assert_se(access(f, F_OK) < 0); + } +} + +TEST(rmdir_parents) { + char *temp; + + temp = strjoina(arg_test_dir ?: "/tmp", "/test-rmdir.XXXXXX"); + assert_se(mkdtemp(temp)); + + test_rmdir_parents_one(temp, "/aaa/../hoge/foo", "/hoge/foo", -EINVAL, NULL, NULL); + test_rmdir_parents_one(temp, "/aaa/bbb/ccc", "/hoge/../aaa", -EINVAL, NULL, NULL); + + test_rmdir_parents_one(temp, "/aaa/bbb/ccc/ddd/eee", "/aaa/bbb/ccc/ddd", 0, "/aaa/bbb/ccc/ddd", "/eee"); + test_rmdir_parents_one(temp, "/aaa/bbb/ccc/ddd/eee", "/aaa/bbb/ccc", 0, "/aaa/bbb/ccc", "/ddd"); + test_rmdir_parents_one(temp, "/aaa/bbb/ccc/ddd/eee", "/aaa/bbb", 0, "/aaa/bbb", "/ccc"); + test_rmdir_parents_one(temp, "/aaa/bbb/ccc/ddd/eee", "/aaa", 0, "/aaa", "/bbb"); + test_rmdir_parents_one(temp, "/aaa/bbb/ccc/ddd/eee", "/", 0, "/", "/aaa"); + + test_rmdir_parents_one(temp, "/aaa/bbb/ccc/ddd/eee", "/aaa/hoge/foo", 0, "/aaa", "/bbb"); + test_rmdir_parents_one(temp, "/aaa////bbb/.//ccc//ddd/eee///./.", "///././aaa/.", 0, "/aaa", "/bbb"); + + assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); +} + +static void test_parse_cifs_service_one(const char *f, const char *h, const char *s, const char *d, int ret) { + _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL; + + assert_se(parse_cifs_service(f, &a, &b, &c) == ret); + assert_se(streq_ptr(a, h)); + assert_se(streq_ptr(b, s)); + assert_se(streq_ptr(c, d)); +} + +TEST(parse_cifs_service) { + test_parse_cifs_service_one("//foo/bar/baz", "foo", "bar", "baz", 0); + test_parse_cifs_service_one("\\\\foo\\bar\\baz", "foo", "bar", "baz", 0); + test_parse_cifs_service_one("//foo/bar", "foo", "bar", NULL, 0); + test_parse_cifs_service_one("\\\\foo\\bar", "foo", "bar", NULL, 0); + test_parse_cifs_service_one("//foo/bar/baz/uuu", "foo", "bar", "baz/uuu", 0); + test_parse_cifs_service_one("\\\\foo\\bar\\baz\\uuu", "foo", "bar", "baz/uuu", 0); + + test_parse_cifs_service_one(NULL, NULL, NULL, NULL, -EINVAL); + test_parse_cifs_service_one("", NULL, NULL, NULL, -EINVAL); + test_parse_cifs_service_one("abc", NULL, NULL, NULL, -EINVAL); + test_parse_cifs_service_one("abc/cde/efg", NULL, NULL, NULL, -EINVAL); + test_parse_cifs_service_one("//foo/bar/baz/..", NULL, NULL, NULL, -EINVAL); + test_parse_cifs_service_one("//foo///", NULL, NULL, NULL, -EINVAL); + test_parse_cifs_service_one("//foo/.", NULL, NULL, NULL, -EINVAL); + test_parse_cifs_service_one("//foo/a/.", NULL, NULL, NULL, -EINVAL); + test_parse_cifs_service_one("//./a", NULL, NULL, NULL, -EINVAL); +} + +TEST(open_mkdir_at) { + _cleanup_close_ int fd = -EBADF, subdir_fd = -EBADF, subsubdir_fd = -EBADF; + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + struct stat sta, stb; + + assert_se(open_mkdir_at(AT_FDCWD, "/", O_EXCL|O_CLOEXEC, 0) == -EEXIST); + assert_se(open_mkdir_at(AT_FDCWD, ".", O_EXCL|O_CLOEXEC, 0) == -EEXIST); + + fd = open_mkdir_at(AT_FDCWD, "/", O_CLOEXEC, 0); + assert_se(fd >= 0); + assert_se(stat("/", &sta) >= 0); + assert_se(fstat(fd, &stb) >= 0); + assert_se(stat_inode_same(&sta, &stb)); + fd = safe_close(fd); + + fd = open_mkdir_at(AT_FDCWD, ".", O_CLOEXEC, 0); + assert_se(stat(".", &sta) >= 0); + assert_se(fstat(fd, &stb) >= 0); + assert_se(stat_inode_same(&sta, &stb)); + fd = safe_close(fd); + + assert_se(open_mkdir_at(AT_FDCWD, "/proc", O_EXCL|O_CLOEXEC, 0) == -EEXIST); + + fd = open_mkdir_at(AT_FDCWD, "/proc", O_CLOEXEC, 0); + assert_se(fd >= 0); + fd = safe_close(fd); + + assert_se(open_mkdir_at(AT_FDCWD, "/bin/sh", O_EXCL|O_CLOEXEC, 0) == -EEXIST); + assert_se(open_mkdir_at(AT_FDCWD, "/bin/sh", O_CLOEXEC, 0) == -EEXIST); + + assert_se(mkdtemp_malloc(NULL, &t) >= 0); + + assert_se(open_mkdir_at(AT_FDCWD, t, O_EXCL|O_CLOEXEC, 0) == -EEXIST); + assert_se(open_mkdir_at(AT_FDCWD, t, O_PATH|O_EXCL|O_CLOEXEC, 0) == -EEXIST); + + fd = open_mkdir_at(AT_FDCWD, t, O_CLOEXEC, 0000); + assert_se(fd >= 0); + fd = safe_close(fd); + + fd = open_mkdir_at(AT_FDCWD, t, O_PATH|O_CLOEXEC, 0000); + assert_se(fd >= 0); + + subdir_fd = open_mkdir_at(fd, "xxx", O_PATH|O_EXCL|O_CLOEXEC, 0700); + assert_se(subdir_fd >= 0); + + assert_se(open_mkdir_at(fd, "xxx", O_PATH|O_EXCL|O_CLOEXEC, 0) == -EEXIST); + + subsubdir_fd = open_mkdir_at(subdir_fd, "yyy", O_EXCL|O_CLOEXEC, 0700); + assert_se(subsubdir_fd >= 0); + subsubdir_fd = safe_close(subsubdir_fd); + + assert_se(open_mkdir_at(subdir_fd, "yyy", O_EXCL|O_CLOEXEC, 0) == -EEXIST); + + assert_se(open_mkdir_at(fd, "xxx/yyy", O_EXCL|O_CLOEXEC, 0) == -EEXIST); + + subsubdir_fd = open_mkdir_at(fd, "xxx/yyy", O_CLOEXEC, 0700); + assert_se(subsubdir_fd >= 0); +} + +TEST(openat_report_new) { + _cleanup_free_ char *j = NULL; + _cleanup_(rm_rf_physical_and_freep) char *d = NULL; + _cleanup_close_ int fd = -EBADF; + bool b; + + assert_se(mkdtemp_malloc(NULL, &d) >= 0); + + j = path_join(d, "test"); + assert_se(j); + + fd = openat_report_new(AT_FDCWD, j, O_RDWR|O_CREAT, 0666, &b); + assert_se(fd >= 0); + fd = safe_close(fd); + assert_se(b); + + fd = openat_report_new(AT_FDCWD, j, O_RDWR|O_CREAT, 0666, &b); + assert_se(fd >= 0); + fd = safe_close(fd); + assert_se(!b); + + fd = openat_report_new(AT_FDCWD, j, O_RDWR|O_CREAT, 0666, &b); + assert_se(fd >= 0); + fd = safe_close(fd); + assert_se(!b); + + assert_se(unlink(j) >= 0); + + fd = openat_report_new(AT_FDCWD, j, O_RDWR|O_CREAT, 0666, &b); + assert_se(fd >= 0); + fd = safe_close(fd); + assert_se(b); + + fd = openat_report_new(AT_FDCWD, j, O_RDWR|O_CREAT, 0666, &b); + assert_se(fd >= 0); + fd = safe_close(fd); + assert_se(!b); + + assert_se(unlink(j) >= 0); + + fd = openat_report_new(AT_FDCWD, j, O_RDWR|O_CREAT, 0666, NULL); + assert_se(fd >= 0); + fd = safe_close(fd); + + fd = openat_report_new(AT_FDCWD, j, O_RDWR|O_CREAT, 0666, &b); + assert_se(fd >= 0); + fd = safe_close(fd); + assert_se(!b); + + fd = openat_report_new(AT_FDCWD, j, O_RDWR, 0666, &b); + assert_se(fd >= 0); + fd = safe_close(fd); + assert_se(!b); + + fd = openat_report_new(AT_FDCWD, j, O_RDWR|O_CREAT|O_EXCL, 0666, &b); + assert_se(fd == -EEXIST); + + assert_se(unlink(j) >= 0); + + fd = openat_report_new(AT_FDCWD, j, O_RDWR, 0666, &b); + assert_se(fd == -ENOENT); + + fd = openat_report_new(AT_FDCWD, j, O_RDWR|O_CREAT|O_EXCL, 0666, &b); + assert_se(fd >= 0); + fd = safe_close(fd); + assert_se(b); +} + +TEST(xopenat) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int tfd = -EBADF, fd = -EBADF, fd2 = -EBADF; + + assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0); + + /* Test that xopenat() creates directories if O_DIRECTORY is specified. */ + + assert_se((fd = xopenat(tfd, "abc", O_DIRECTORY|O_CREAT|O_EXCL|O_CLOEXEC, 0, 0755)) >= 0); + assert_se((fd_verify_directory(fd) >= 0)); + fd = safe_close(fd); + + assert_se(xopenat(tfd, "abc", O_DIRECTORY|O_CREAT|O_EXCL|O_CLOEXEC, 0, 0755) == -EEXIST); + + assert_se((fd = xopenat(tfd, "abc", O_DIRECTORY|O_CREAT|O_CLOEXEC, 0, 0755)) >= 0); + assert_se((fd_verify_directory(fd) >= 0)); + fd = safe_close(fd); + + /* Test that xopenat() creates regular files if O_DIRECTORY is not specified. */ + + assert_se((fd = xopenat(tfd, "def", O_CREAT|O_EXCL|O_CLOEXEC, 0, 0644)) >= 0); + assert_se(fd_verify_regular(fd) >= 0); + fd = safe_close(fd); + + /* Test that we can reopen an existing fd with xopenat() by specifying an empty path. */ + + assert_se((fd = xopenat(tfd, "def", O_PATH|O_CLOEXEC, 0, 0)) >= 0); + assert_se((fd2 = xopenat(fd, "", O_RDWR|O_CLOEXEC, 0, 0644)) >= 0); +} + +TEST(xopenat_lock) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int tfd = -EBADF, fd = -EBADF; + siginfo_t si; + + assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0); + + /* Test that we can acquire an exclusive lock on a directory in one process, remove the directory, + * and close the file descriptor and still properly create the directory and acquire the lock in + * another process. */ + + fd = xopenat_lock(tfd, "abc", O_CREAT|O_DIRECTORY|O_CLOEXEC, 0, 0755, LOCK_BSD, LOCK_EX); + assert_se(fd >= 0); + assert_se(faccessat(tfd, "abc", F_OK, 0) >= 0); + assert_se(fd_verify_directory(fd) >= 0); + assert_se(xopenat_lock(tfd, "abc", O_DIRECTORY|O_CLOEXEC, 0, 0755, LOCK_BSD, LOCK_EX|LOCK_NB) == -EAGAIN); + + pid_t pid = fork(); + assert_se(pid >= 0); + + if (pid == 0) { + safe_close(fd); + + fd = xopenat_lock(tfd, "abc", O_CREAT|O_DIRECTORY|O_CLOEXEC, 0, 0755, LOCK_BSD, LOCK_EX); + assert_se(fd >= 0); + assert_se(faccessat(tfd, "abc", F_OK, 0) >= 0); + assert_se(fd_verify_directory(fd) >= 0); + assert_se(xopenat_lock(tfd, "abc", O_DIRECTORY|O_CLOEXEC, 0, 0755, LOCK_BSD, LOCK_EX|LOCK_NB) == -EAGAIN); + + _exit(EXIT_SUCCESS); + } + + /* We need to give the child process some time to get past the xopenat() call in xopenat_lock() and + * block in the call to lock_generic() waiting for the lock to become free. We can't modify + * xopenat_lock() to signal an eventfd to let us know when that has happened, so we just sleep for a + * little and assume that's enough time for the child process to get along far enough. It doesn't + * matter if it doesn't get far enough, in that case we just won't trigger the fallback logic in + * xopenat_lock(), but the test will still succeed. */ + assert_se(usleep_safe(20 * USEC_PER_MSEC) >= 0); + + assert_se(unlinkat(tfd, "abc", AT_REMOVEDIR) >= 0); + fd = safe_close(fd); + + assert_se(wait_for_terminate(pid, &si) >= 0); + assert_se(si.si_code == CLD_EXITED); + + assert_se(xopenat_lock(tfd, "abc", 0, 0, 0755, LOCK_POSIX, LOCK_EX) == -EBADF); + assert_se(xopenat_lock(tfd, "def", O_DIRECTORY, 0, 0755, LOCK_POSIX, LOCK_EX) == -EBADF); +} + +static int intro(void) { + arg_test_dir = saved_argv[1]; + return EXIT_SUCCESS; +} + +TEST(readlinkat_malloc) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int tfd = -EBADF, fd = -EBADF; + _cleanup_free_ char *p = NULL, *q = NULL; + const char *expect = "hgoehogefoobar"; + + tfd = mkdtemp_open(NULL, O_PATH, &t); + assert_se(tfd >= 0); + + assert_se(symlinkat(expect, tfd, "linkname") >= 0); + + assert_se(readlinkat_malloc(tfd, "linkname", &p) >= 0); + assert_se(streq(p, expect)); + p = mfree(p); + + fd = openat(tfd, "linkname", O_PATH | O_NOFOLLOW | O_CLOEXEC); + assert_se(fd >= 0); + assert_se(readlinkat_malloc(fd, NULL, &p) >= 0); + assert_se(streq(p, expect)); + p = mfree(p); + assert_se(readlinkat_malloc(fd, "", &p) >= 0); + assert_se(streq(p, expect)); + p = mfree(p); + fd = safe_close(fd); + + assert_se(q = path_join(t, "linkname")); + assert_se(readlinkat_malloc(AT_FDCWD, q, &p) >= 0); + assert_se(streq(p, expect)); + p = mfree(p); + assert_se(readlinkat_malloc(INT_MAX, q, &p) >= 0); + assert_se(streq(p, expect)); + p = mfree(p); + q = mfree(q); +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/test/test-fstab-util.c b/src/test/test-fstab-util.c new file mode 100644 index 0000000..89365b0 --- /dev/null +++ b/src/test/test-fstab-util.c @@ -0,0 +1,192 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdio.h> + +#include "alloc-util.h" +#include "fstab-util.h" +#include "log.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" + +/* +int fstab_filter_options( + const char *opts, + const char *names, + const char **ret_namefound, + const char **ret_value, + const char **ret_values, + char **ret_filtered); +*/ + +static void do_fstab_filter_options(const char *opts, + const char *remove, + int r_expected, + int r_values_expected, + const char *name_expected, + const char *value_expected, + const char *values_expected, + const char *filtered_expected) { + int r; + const char *name; + _cleanup_free_ char *value = NULL, *filtered = NULL, *joined = NULL; + _cleanup_strv_free_ char **values = NULL; + + /* test mode which returns the last value */ + + r = fstab_filter_options(opts, remove, &name, &value, NULL, &filtered); + log_info("1: \"%s\" → %d, \"%s\", \"%s\", \"%s\", expected %d, \"%s\", \"%s\", \"%s\"", + opts, r, strnull(name), value, filtered, + r_expected, strnull(name_expected), strnull(value_expected), filtered_expected ?: opts); + assert_se(r == r_expected); + assert_se(streq_ptr(name, name_expected)); + assert_se(streq_ptr(value, value_expected)); + assert_se(streq_ptr(filtered, filtered_expected ?: opts)); + + /* test mode which returns all the values */ + + r = fstab_filter_options(opts, remove, &name, NULL, &values, NULL); + assert_se(joined = strv_join(values, ":")); + log_info("2: \"%s\" → %d, \"%s\", \"%s\", expected %d, \"%s\", \"%s\"", + opts, r, strnull(name), joined, + r_values_expected, strnull(name_expected), strnull(values_expected)); + assert_se(r == r_values_expected); + assert_se(streq_ptr(name, r_values_expected > 0 ? name_expected : NULL)); + assert_se(streq_ptr(joined, values_expected)); + + /* also test the malloc-less mode */ + r = fstab_filter_options(opts, remove, &name, NULL, NULL, NULL); + log_info("3: \"%s\" → %d, \"%s\", expected %d, \"%s\"\n-", + opts, r, strnull(name), + r_expected, strnull(name_expected)); + assert_se(r == r_expected); + assert_se(streq_ptr(name, name_expected)); +} + +TEST(fstab_filter_options) { + do_fstab_filter_options("opt=0", "opt\0x-opt\0", 1, 1, "opt", "0", "0", ""); + do_fstab_filter_options("opt=0", "x-opt\0opt\0", 1, 1, "opt", "0", "0", ""); + do_fstab_filter_options("opt", "opt\0x-opt\0", 1, 0, "opt", NULL, "", ""); + do_fstab_filter_options("opt", "x-opt\0opt\0", 1, 0, "opt", NULL, "", ""); + do_fstab_filter_options("x-opt", "x-opt\0opt\0", 1, 0, "x-opt", NULL, "", ""); + + do_fstab_filter_options("opt=0,other", "opt\0x-opt\0", 1, 1, "opt", "0", "0", "other"); + do_fstab_filter_options("opt=0,other", "x-opt\0opt\0", 1, 1, "opt", "0", "0", "other"); + do_fstab_filter_options("opt,other", "opt\0x-opt\0", 1, 0, "opt", NULL, "", "other"); + do_fstab_filter_options("opt,other", "x-opt\0opt\0", 1, 0, "opt", NULL, "", "other"); + do_fstab_filter_options("x-opt,other", "opt\0x-opt\0", 1, 0, "x-opt", NULL, "", "other"); + + do_fstab_filter_options("opt=0\\,1,other", "opt\0x-opt\0", 1, 1, "opt", "0,1", "0,1", "other"); + do_fstab_filter_options("opt=0,other,x-opt\\,foobar", "x-opt\0opt\0", 1, 1, "opt", "0", "0", "other,x-opt\\,foobar"); + do_fstab_filter_options("opt,other,x-opt\\,part", "opt\0x-opt\0", 1, 0, "opt", NULL, "", "other,x-opt\\,part"); + do_fstab_filter_options("opt,other,part\\,x-opt", "x-opt\0opt\0", 1, 0, "opt", NULL, "", "other,part\\,x-opt"); + do_fstab_filter_options("opt,other\\,\\,\\,opt,x-part", "opt\0x-opt\0", 1, 0, "opt", NULL, "", "other\\,\\,\\,opt,x-part"); + + do_fstab_filter_options("opto=0,other", "opt\0x-opt\0", 0, 0, NULL, NULL, "", NULL); + do_fstab_filter_options("opto,other", "opt\0x-opt\0", 0, 0, NULL, NULL, "", NULL); + do_fstab_filter_options("x-opto,other", "opt\0x-opt\0", 0, 0, NULL, NULL, "", NULL); + + do_fstab_filter_options("first,opt=0", "opt\0x-opt\0", 1, 1, "opt", "0", "0", "first"); + do_fstab_filter_options("first=1,opt=0", "opt\0x-opt\0", 1, 1, "opt", "0", "0", "first=1"); + do_fstab_filter_options("first,opt=", "opt\0x-opt\0", 1, 1, "opt", "", "", "first"); + do_fstab_filter_options("first=1,opt", "opt\0x-opt\0", 1, 0, "opt", NULL, "", "first=1"); + do_fstab_filter_options("first=1,x-opt", "opt\0x-opt\0", 1, 0, "x-opt", NULL, "", "first=1"); + + do_fstab_filter_options("first,opt=0,last=1", "opt\0x-opt\0", 1, 1, "opt", "0", "0", "first,last=1"); + do_fstab_filter_options("first=1,opt=0,last=2", "x-opt\0opt\0", 1, 1, "opt", "0", "0", "first=1,last=2"); + do_fstab_filter_options("first,opt,last", "opt\0", 1, 0, "opt", NULL, "", "first,last"); + do_fstab_filter_options("first=1,opt,last", "x-opt\0opt\0", 1, 0, "opt", NULL, "", "first=1,last"); + do_fstab_filter_options("first=,opt,last", "opt\0noopt\0", 1, 0, "opt", NULL, "", "first=,last"); + + /* check repeated options */ + do_fstab_filter_options("first,opt=0,noopt=1,last=1", "opt\0noopt\0", 1, 1, "noopt", "1", "0:1", "first,last=1"); + do_fstab_filter_options("first=1,opt=0,last=2,opt=1", "opt\0", 1, 1, "opt", "1", "0:1", "first=1,last=2"); + do_fstab_filter_options("x-opt=0,x-opt=1", "opt\0x-opt\0", 1, 1, "x-opt", "1", "0:1", ""); + do_fstab_filter_options("opt=0,x-opt=1", "opt\0x-opt\0", 1, 1, "x-opt", "1", "0:1", ""); + do_fstab_filter_options("opt=0,opt=1,opt=,opt=,opt=2", "opt\0noopt\0", 1, 1, "opt", "2", "0:1:::2", ""); + + /* check that semicolons are not misinterpreted */ + do_fstab_filter_options("opt=0;", "opt\0", 1, 1, "opt", "0;", "0;", ""); + do_fstab_filter_options("opt;=0", "x-opt\0opt\0noopt\0x-noopt\0", 0, 0, NULL, NULL, "", NULL); + do_fstab_filter_options("opt;", "opt\0x-opt\0", 0, 0, NULL, NULL, "", NULL); + + /* check that spaces are not misinterpreted */ + do_fstab_filter_options("opt=0 ", "opt\0", 1, 1, "opt", "0 ", "0 ", ""); + do_fstab_filter_options("opt =0", "x-opt\0opt\0noopt\0x-noopt\0", 0, 0, NULL, NULL, "", NULL); + do_fstab_filter_options(" opt ", "opt\0x-opt\0", 0, 0, NULL, NULL, "", NULL); + + /* check function with NULL args */ + do_fstab_filter_options(NULL, "opt\0", 0, 0, NULL, NULL, "", ""); + do_fstab_filter_options("", "opt\0", 0, 0, NULL, NULL, "", ""); + + /* unnecessary comma separators */ + do_fstab_filter_options("opt=x,,,,", "opt\0", 1, 1, "opt", "x", "x", ""); + do_fstab_filter_options(",,,opt=x,,,,", "opt\0", 1, 1, "opt", "x", "x", ""); + + /* escaped characters */ + do_fstab_filter_options("opt1=\\\\,opt2=\\xff", "opt1\0", 1, 1, "opt1", "\\", "\\", "opt2=\\xff"); + do_fstab_filter_options("opt1=\\\\,opt2=\\xff", "opt2\0", 1, 1, "opt2", "\\xff", "\\xff", "opt1=\\"); +} + +TEST(fstab_find_pri) { + int pri = -1; + + assert_se(fstab_find_pri("pri", &pri) == 0); + assert_se(pri == -1); + + assert_se(fstab_find_pri("pri=11", &pri) == 1); + assert_se(pri == 11); + + assert_se(fstab_find_pri("pri=-2", &pri) == 1); + assert_se(pri == -2); + + assert_se(fstab_find_pri("opt,pri=12,opt", &pri) == 1); + assert_se(pri == 12); + + assert_se(fstab_find_pri("opt,opt,pri=12,pri=13", &pri) == 1); + assert_se(pri == 13); +} + +TEST(fstab_yes_no_option) { + assert_se(fstab_test_yes_no_option("nofail,fail,nofail", "nofail\0fail\0") == true); + assert_se(fstab_test_yes_no_option("nofail,nofail,fail", "nofail\0fail\0") == false); + assert_se(fstab_test_yes_no_option("abc,cde,afail", "nofail\0fail\0") == false); + assert_se(fstab_test_yes_no_option("nofail,fail=0,nofail=0", "nofail\0fail\0") == true); + assert_se(fstab_test_yes_no_option("nofail,nofail=0,fail=0", "nofail\0fail\0") == false); +} + +TEST(fstab_node_to_udev_node) { + char *n; + + n = fstab_node_to_udev_node("LABEL=applé/jack"); + puts(n); + assert_se(streq(n, "/dev/disk/by-label/applé\\x2fjack")); + free(n); + + n = fstab_node_to_udev_node("PARTLABEL=pinkié pie"); + puts(n); + assert_se(streq(n, "/dev/disk/by-partlabel/pinkié\\x20pie")); + free(n); + + n = fstab_node_to_udev_node("UUID=037b9d94-148e-4ee4-8d38-67bfe15bb535"); + puts(n); + assert_se(streq(n, "/dev/disk/by-uuid/037b9d94-148e-4ee4-8d38-67bfe15bb535")); + free(n); + + n = fstab_node_to_udev_node("PARTUUID=037b9d94-148e-4ee4-8d38-67bfe15bb535"); + puts(n); + assert_se(streq(n, "/dev/disk/by-partuuid/037b9d94-148e-4ee4-8d38-67bfe15bb535")); + free(n); + + n = fstab_node_to_udev_node("PONIES=awesome"); + puts(n); + assert_se(streq(n, "PONIES=awesome")); + free(n); + + n = fstab_node_to_udev_node("/dev/xda1"); + puts(n); + assert_se(streq(n, "/dev/xda1")); + free(n); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-glob-util.c b/src/test/test-glob-util.c new file mode 100644 index 0000000..9b3e73c --- /dev/null +++ b/src/test/test-glob-util.c @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <fcntl.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "dirent-util.h" +#include "fs-util.h" +#include "glob-util.h" +#include "macro.h" +#include "rm-rf.h" +#include "tests.h" +#include "tmpfile-util.h" + +TEST(glob_first) { + char *first, name[] = "/tmp/test-glob_first.XXXXXX"; + int fd = -EBADF; + int r; + + fd = mkostemp_safe(name); + assert_se(fd >= 0); + close(fd); + + r = glob_first("/tmp/test-glob_first*", &first); + assert_se(r == 1); + assert_se(streq(name, first)); + first = mfree(first); + + r = unlink(name); + assert_se(r == 0); + r = glob_first("/tmp/test-glob_first*", &first); + assert_se(r == 0); + assert_se(first == NULL); +} + +TEST(glob_exists) { + char name[] = "/tmp/test-glob_exists.XXXXXX"; + int fd = -EBADF; + int r; + + fd = mkostemp_safe(name); + assert_se(fd >= 0); + close(fd); + + r = glob_exists("/tmp/test-glob_exists*"); + assert_se(r == 1); + + r = unlink(name); + assert_se(r == 0); + r = glob_exists("/tmp/test-glob_exists*"); + assert_se(r == 0); +} + +static void closedir_wrapper(void* v) { + (void) closedir(v); +} + +TEST(glob_no_dot) { + char template[] = "/tmp/test-glob-util.XXXXXXX"; + const char *fn; + + _cleanup_globfree_ glob_t g = { + .gl_closedir = closedir_wrapper, + .gl_readdir = (struct dirent *(*)(void *)) readdir_no_dot, + .gl_opendir = (void *(*)(const char *)) opendir, + .gl_lstat = lstat, + .gl_stat = stat, + }; + + int r; + + assert_se(mkdtemp(template)); + + fn = strjoina(template, "/*"); + r = glob(fn, GLOB_NOSORT|GLOB_BRACE|GLOB_ALTDIRFUNC, NULL, &g); + assert_se(r == GLOB_NOMATCH); + + fn = strjoina(template, "/.*"); + r = glob(fn, GLOB_NOSORT|GLOB_BRACE|GLOB_ALTDIRFUNC, NULL, &g); + assert_se(r == GLOB_NOMATCH); + + (void) rm_rf(template, REMOVE_ROOT|REMOVE_PHYSICAL); +} + +TEST(safe_glob) { + char template[] = "/tmp/test-glob-util.XXXXXXX"; + const char *fn, *fn2, *fname; + + _cleanup_globfree_ glob_t g = {}; + int r; + + assert_se(mkdtemp(template)); + + fn = strjoina(template, "/*"); + r = safe_glob(fn, 0, &g); + assert_se(r == -ENOENT); + + fn2 = strjoina(template, "/.*"); + r = safe_glob(fn2, GLOB_NOSORT|GLOB_BRACE, &g); + assert_se(r == -ENOENT); + + fname = strjoina(template, "/.foobar"); + assert_se(touch(fname) == 0); + + r = safe_glob(fn, 0, &g); + assert_se(r == -ENOENT); + + r = safe_glob(fn2, GLOB_NOSORT|GLOB_BRACE, &g); + assert_se(r == 0); + assert_se(g.gl_pathc == 1); + assert_se(streq(g.gl_pathv[0], fname)); + assert_se(g.gl_pathv[1] == NULL); + + (void) rm_rf(template, REMOVE_ROOT|REMOVE_PHYSICAL); +} + +static void test_glob_non_glob_prefix_one(const char *path, const char *expected) { + _cleanup_free_ char *t; + + assert_se(glob_non_glob_prefix(path, &t) == 0); + assert_se(streq(t, expected)); +} + +TEST(glob_non_glob) { + test_glob_non_glob_prefix_one("/tmp/.X11-*", "/tmp/"); + test_glob_non_glob_prefix_one("/tmp/*", "/tmp/"); + test_glob_non_glob_prefix_one("/tmp*", "/"); + test_glob_non_glob_prefix_one("/tmp/*/whatever", "/tmp/"); + test_glob_non_glob_prefix_one("/tmp/*/whatever?", "/tmp/"); + test_glob_non_glob_prefix_one("/?", "/"); + + char *x; + assert_se(glob_non_glob_prefix("?", &x) == -ENOENT); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-gpt.c b/src/test/test-gpt.c new file mode 100644 index 0000000..fa5923e --- /dev/null +++ b/src/test/test-gpt.c @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "architecture.h" +#include "glyph-util.h" +#include "gpt.h" +#include "log.h" +#include "pretty-print.h" +#include "strv.h" +#include "terminal-util.h" +#include "tests.h" + +TEST(gpt_types_against_architectures) { + int r; + + /* Dumps a table indicating for which architectures we know we have matching GPT partition + * types. Also validates whether we can properly categorize the entries. */ + + FOREACH_STRING(prefix, "root-", "usr-") + for (Architecture a = 0; a < _ARCHITECTURE_MAX; a++) + FOREACH_STRING(suffix, "", "-verity", "-verity-sig") { + _cleanup_free_ char *joined = NULL; + GptPartitionType type; + + joined = strjoin(prefix, architecture_to_string(a), suffix); + if (!joined) + return (void) log_oom(); + + r = gpt_partition_type_from_string(joined, &type); + if (r < 0) { + printf("%s %s\n", RED_CROSS_MARK(), joined); + continue; + } + + printf("%s %s\n", GREEN_CHECK_MARK(), joined); + + if (streq(prefix, "root-") && streq(suffix, "")) + assert_se(type.designator == PARTITION_ROOT); + if (streq(prefix, "root-") && streq(suffix, "-verity")) + assert_se(type.designator == PARTITION_ROOT_VERITY); + if (streq(prefix, "usr-") && streq(suffix, "")) + assert_se(type.designator == PARTITION_USR); + if (streq(prefix, "usr-") && streq(suffix, "-verity")) + assert_se(type.designator == PARTITION_USR_VERITY); + + assert_se(type.arch == a); + } +} + +TEST(verity_mappings) { + for (PartitionDesignator p = 0; p < _PARTITION_DESIGNATOR_MAX; p++) { + PartitionDesignator q; + + q = partition_verity_of(p); + assert_se(q < 0 || partition_verity_to_data(q) == p); + + q = partition_verity_sig_of(p); + assert_se(q < 0 || partition_verity_sig_to_data(q) == p); + + q = partition_verity_to_data(p); + assert_se(q < 0 || partition_verity_of(q) == p); + + q = partition_verity_sig_to_data(p); + assert_se(q < 0 || partition_verity_sig_of(q) == p); + } +} + +TEST(type_alias_same) { + /* Check that the partition type table is consistent, i.e. all aliases of the same partition type + * carry the same metadata */ + + for (const GptPartitionType *t = gpt_partition_type_table; t->name; t++) { + GptPartitionType x, y; + + x = gpt_partition_type_from_uuid(t->uuid); /* search first by uuid */ + assert_se(gpt_partition_type_from_string(t->name, &y) >= 0); /* search first by name */ + + assert_se(t->arch == x.arch); + assert_se(t->arch == y.arch); + assert_se(t->designator == x.designator); + assert_se(t->designator == y.designator); + } +} + +TEST(override_architecture) { + GptPartitionType x, y; + + assert_se(gpt_partition_type_from_string("root-x86-64", &x) >= 0); + assert_se(x.arch == ARCHITECTURE_X86_64); + + assert_se(gpt_partition_type_from_string("root-arm64", &y) >= 0); + assert(y.arch == ARCHITECTURE_ARM64); + + x = gpt_partition_type_override_architecture(x, ARCHITECTURE_ARM64); + assert_se(x.arch == y.arch); + assert_se(x.designator == y.designator); + assert_se(sd_id128_equal(x.uuid, y.uuid)); + assert_se(streq(x.name, y.name)); + + /* If the partition type does not have an architecture, nothing should change. */ + + assert_se(gpt_partition_type_from_string("esp", &x) >= 0); + y = x; + + x = gpt_partition_type_override_architecture(x, ARCHITECTURE_ARM64); + assert_se(x.arch == y.arch); + assert_se(x.designator == y.designator); + assert_se(sd_id128_equal(x.uuid, y.uuid)); + assert_se(streq(x.name, y.name)); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-gunicode.c b/src/test/test-gunicode.c new file mode 100644 index 0000000..1836cdc --- /dev/null +++ b/src/test/test-gunicode.c @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "gunicode.h" +#include "tests.h" +#include "utf8.h" + +TEST(unichar_iswide) { + char32_t c; + int r; + + /* FIXME: the cats are wide, but we get this wrong */ + for (const char *narrow = "abX_…ąęµ!" "😼😿🙀😸😻"; *narrow; narrow += r) { + r = utf8_encoded_to_unichar(narrow, &c); + bool w = unichar_iswide(c); + assert_se(r > 0); + assert_se(!w); + } + + for (const char *wide = "🐱/¥"; *wide; wide += r) { + r = utf8_encoded_to_unichar(wide, &c); + bool w = unichar_iswide(c); + assert_se(r > 0); + assert_se(w); + } +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-hash-funcs.c b/src/test/test-hash-funcs.c new file mode 100644 index 0000000..f5166c1 --- /dev/null +++ b/src/test/test-hash-funcs.c @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "tests.h" +#include "hash-funcs.h" +#include "set.h" + +TEST(path_hash_set) { + /* The goal is to make sure that non-simplified path are hashed as expected, + * and that we don't need to simplify them beforehand. */ + + /* No freeing of keys, we operate on static strings here… */ + _cleanup_set_free_ Set *set = NULL; + + assert_se(set_isempty(set)); + assert_se(set_ensure_put(&set, &path_hash_ops, "foo") == 1); + assert_se(set_ensure_put(&set, &path_hash_ops, "foo") == 0); + assert_se(set_ensure_put(&set, &path_hash_ops, "bar") == 1); + assert_se(set_ensure_put(&set, &path_hash_ops, "bar") == 0); + assert_se(set_ensure_put(&set, &path_hash_ops, "/foo") == 1); + assert_se(set_ensure_put(&set, &path_hash_ops, "/bar") == 1); + assert_se(set_ensure_put(&set, &path_hash_ops, "/foo/.") == 0); + assert_se(set_ensure_put(&set, &path_hash_ops, "/./bar/./.") == 0); + + assert_se(set_contains(set, "foo")); + assert_se(set_contains(set, "bar")); + assert_se(set_contains(set, "./foo")); + assert_se(set_contains(set, "./foo/.")); + assert_se(set_contains(set, "./bar")); + assert_se(set_contains(set, "./bar/.")); + assert_se(set_contains(set, "/foo")); + assert_se(set_contains(set, "/bar")); + assert_se(set_contains(set, "//./foo")); + assert_se(set_contains(set, "///./foo/.")); + assert_se(set_contains(set, "////./bar")); + assert_se(set_contains(set, "/////./bar/.")); + + assert_se(set_contains(set, "foo/")); + assert_se(set_contains(set, "bar/")); + assert_se(set_contains(set, "./foo/")); + assert_se(set_contains(set, "./foo/./")); + assert_se(set_contains(set, "./bar/")); + assert_se(set_contains(set, "./bar/./")); + assert_se(set_contains(set, "/foo/")); + assert_se(set_contains(set, "/bar/")); + assert_se(set_contains(set, "//./foo/")); + assert_se(set_contains(set, "///./foo/./")); + assert_se(set_contains(set, "////./bar/")); + assert_se(set_contains(set, "/////./bar/./")); + + assert_se(!set_contains(set, "foo.")); + assert_se(!set_contains(set, ".bar")); + assert_se(!set_contains(set, "./foo.")); + assert_se(!set_contains(set, "./.foo/.")); + assert_se(!set_contains(set, "../bar")); + assert_se(!set_contains(set, "./bar/..")); + assert_se(!set_contains(set, "./foo..")); + assert_se(!set_contains(set, "/..bar")); + assert_se(!set_contains(set, "//../foo")); + assert_se(!set_contains(set, "///../foo/.")); + assert_se(!set_contains(set, "////../bar")); + assert_se(!set_contains(set, "/////../bar/.")); + + assert_se(!set_contains(set, "foo./")); + assert_se(!set_contains(set, ".bar/")); + assert_se(!set_contains(set, "./foo./")); + assert_se(!set_contains(set, "./.foo/./")); + assert_se(!set_contains(set, "../bar/")); + assert_se(!set_contains(set, "./bar/../")); + assert_se(!set_contains(set, "./foo../")); + assert_se(!set_contains(set, "/..bar/")); + assert_se(!set_contains(set, "//../foo/")); + assert_se(!set_contains(set, "///../foo/./")); + assert_se(!set_contains(set, "////../bar/")); + assert_se(!set_contains(set, "/////../bar/./")); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-hashmap-ordered.awk b/src/test/test-hashmap-ordered.awk new file mode 100644 index 0000000..88ffc25 --- /dev/null +++ b/src/test/test-hashmap-ordered.awk @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +BEGIN { + print "/* GENERATED FILE */"; + print "#define ORDERED" +} +{ + if (!match($0, "^#include")) + gsub(/hashmap/, "ordered_hashmap"); + gsub(/HASHMAP/, "ORDERED_HASHMAP"); + gsub(/Hashmap/, "OrderedHashmap"); + print +} diff --git a/src/test/test-hashmap-plain.c b/src/test/test-hashmap-plain.c new file mode 100644 index 0000000..152b1c0 --- /dev/null +++ b/src/test/test-hashmap-plain.c @@ -0,0 +1,1008 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "hashmap.h" +#include "log.h" +#include "nulstr-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" +#include "time-util.h" + +TEST(hashmap_replace) { + _cleanup_hashmap_free_ Hashmap *m = NULL; + _cleanup_free_ char *val1 = NULL, *val2 = NULL, *val3 = NULL, *val4 = NULL, *val5 = NULL; + char *r; + + m = hashmap_new(&string_hash_ops); + + val1 = strdup("val1"); + assert_se(val1); + val2 = strdup("val2"); + assert_se(val2); + val3 = strdup("val3"); + assert_se(val3); + val4 = strdup("val4"); + assert_se(val4); + val5 = strdup("val5"); + assert_se(val5); + + hashmap_put(m, "key 1", val1); + hashmap_put(m, "key 2", val2); + hashmap_put(m, "key 3", val3); + hashmap_put(m, "key 4", val4); + + hashmap_replace(m, "key 3", val1); + r = hashmap_get(m, "key 3"); + assert_se(streq(r, "val1")); + + hashmap_replace(m, "key 5", val5); + r = hashmap_get(m, "key 5"); + assert_se(streq(r, "val5")); +} + +TEST(hashmap_copy) { + _cleanup_hashmap_free_ Hashmap *m = NULL; + _cleanup_hashmap_free_free_ Hashmap *copy = NULL; + char *val1, *val2, *val3, *val4, *r; + + val1 = strdup("val1"); + assert_se(val1); + val2 = strdup("val2"); + assert_se(val2); + val3 = strdup("val3"); + assert_se(val3); + val4 = strdup("val4"); + assert_se(val4); + + m = hashmap_new(&string_hash_ops); + + hashmap_put(m, "key 1", val1); + hashmap_put(m, "key 2", val2); + hashmap_put(m, "key 3", val3); + hashmap_put(m, "key 4", val4); + + copy = hashmap_copy(m); + + r = hashmap_get(copy, "key 1"); + assert_se(streq(r, "val1")); + r = hashmap_get(copy, "key 2"); + assert_se(streq(r, "val2")); + r = hashmap_get(copy, "key 3"); + assert_se(streq(r, "val3")); + r = hashmap_get(copy, "key 4"); + assert_se(streq(r, "val4")); +} + +TEST(hashmap_get_strv) { + _cleanup_hashmap_free_ Hashmap *m = NULL; + _cleanup_strv_free_ char **strv = NULL; + char *val1, *val2, *val3, *val4; + + val1 = strdup("val1"); + assert_se(val1); + val2 = strdup("val2"); + assert_se(val2); + val3 = strdup("val3"); + assert_se(val3); + val4 = strdup("val4"); + assert_se(val4); + + m = hashmap_new(&string_hash_ops); + + hashmap_put(m, "key 1", val1); + hashmap_put(m, "key 2", val2); + hashmap_put(m, "key 3", val3); + hashmap_put(m, "key 4", val4); + + strv = hashmap_get_strv(m); + +#ifndef ORDERED + strv = strv_sort(strv); +#endif + + assert_se(streq(strv[0], "val1")); + assert_se(streq(strv[1], "val2")); + assert_se(streq(strv[2], "val3")); + assert_se(streq(strv[3], "val4")); +} + +TEST(hashmap_move_one) { + _cleanup_hashmap_free_free_ Hashmap *m = NULL, *n = NULL; + char *val1, *val2, *val3, *val4, *r; + + val1 = strdup("val1"); + assert_se(val1); + val2 = strdup("val2"); + assert_se(val2); + val3 = strdup("val3"); + assert_se(val3); + val4 = strdup("val4"); + assert_se(val4); + + m = hashmap_new(&string_hash_ops); + n = hashmap_new(&string_hash_ops); + + hashmap_put(m, "key 1", val1); + hashmap_put(m, "key 2", val2); + hashmap_put(m, "key 3", val3); + hashmap_put(m, "key 4", val4); + + assert_se(hashmap_move_one(n, NULL, "key 3") == -ENOENT); + assert_se(hashmap_move_one(n, m, "key 5") == -ENOENT); + assert_se(hashmap_move_one(n, m, "key 3") == 0); + assert_se(hashmap_move_one(n, m, "key 4") == 0); + + r = hashmap_get(n, "key 3"); + assert_se(r && streq(r, "val3")); + r = hashmap_get(n, "key 4"); + assert_se(r && streq(r, "val4")); + r = hashmap_get(m, "key 3"); + assert_se(!r); + + assert_se(hashmap_move_one(n, m, "key 3") == -EEXIST); +} + +TEST(hashmap_move) { + _cleanup_hashmap_free_free_ Hashmap *m = NULL, *n = NULL; + char *val1, *val2, *val3, *val4, *r; + + val1 = strdup("val1"); + assert_se(val1); + val2 = strdup("val2"); + assert_se(val2); + val3 = strdup("val3"); + assert_se(val3); + val4 = strdup("val4"); + assert_se(val4); + + m = hashmap_new(&string_hash_ops); + n = hashmap_new(&string_hash_ops); + + hashmap_put(n, "key 1", strdup(val1)); + hashmap_put(m, "key 1", val1); + hashmap_put(m, "key 2", val2); + hashmap_put(m, "key 3", val3); + hashmap_put(m, "key 4", val4); + + assert_se(hashmap_move(n, NULL) == 0); + assert_se(hashmap_move(n, m) == 0); + + assert_se(hashmap_size(m) == 1); + r = hashmap_get(m, "key 1"); + assert_se(r && streq(r, "val1")); + + r = hashmap_get(n, "key 1"); + assert_se(r && streq(r, "val1")); + r = hashmap_get(n, "key 2"); + assert_se(r && streq(r, "val2")); + r = hashmap_get(n, "key 3"); + assert_se(r && streq(r, "val3")); + r = hashmap_get(n, "key 4"); + assert_se(r && streq(r, "val4")); +} + +TEST(hashmap_update) { + _cleanup_hashmap_free_ Hashmap *m = NULL; + _cleanup_free_ char *val1 = NULL, *val2 = NULL; + char *r; + + m = hashmap_new(&string_hash_ops); + val1 = strdup("old_value"); + assert_se(val1); + val2 = strdup("new_value"); + assert_se(val2); + + hashmap_put(m, "key 1", val1); + r = hashmap_get(m, "key 1"); + assert_se(streq(r, "old_value")); + + assert_se(hashmap_update(m, "key 2", val2) == -ENOENT); + r = hashmap_get(m, "key 1"); + assert_se(streq(r, "old_value")); + + assert_se(hashmap_update(m, "key 1", val2) == 0); + r = hashmap_get(m, "key 1"); + assert_se(streq(r, "new_value")); +} + +TEST(hashmap_put) { + _cleanup_hashmap_free_ Hashmap *m = NULL; + int valid_hashmap_put; + void *val1 = (void*) "val 1"; + void *val2 = (void*) "val 2"; + _cleanup_free_ char* key1 = NULL; + + assert_se(hashmap_ensure_allocated(&m, &string_hash_ops) == 1); + assert_se(m); + + valid_hashmap_put = hashmap_put(m, "key 1", val1); + assert_se(valid_hashmap_put == 1); + assert_se(hashmap_put(m, "key 1", val1) == 0); + assert_se(hashmap_put(m, "key 1", val2) == -EEXIST); + key1 = strdup("key 1"); + assert_se(hashmap_put(m, key1, val1) == 0); + assert_se(hashmap_put(m, key1, val2) == -EEXIST); +} + +TEST(hashmap_remove1) { + _cleanup_hashmap_free_ Hashmap *m = NULL; + char *r; + + r = hashmap_remove(NULL, "key 1"); + assert_se(r == NULL); + + m = hashmap_new(&string_hash_ops); + assert_se(m); + + r = hashmap_remove(m, "no such key"); + assert_se(r == NULL); + + hashmap_put(m, "key 1", (void*) "val 1"); + hashmap_put(m, "key 2", (void*) "val 2"); + + r = hashmap_remove(m, "key 1"); + assert_se(streq(r, "val 1")); + + r = hashmap_get(m, "key 2"); + assert_se(streq(r, "val 2")); + assert_se(!hashmap_get(m, "key 1")); +} + +TEST(hashmap_remove2) { + _cleanup_hashmap_free_free_free_ Hashmap *m = NULL; + char key1[] = "key 1"; + char key2[] = "key 2"; + char val1[] = "val 1"; + char val2[] = "val 2"; + void *r, *r2; + + r = hashmap_remove2(NULL, "key 1", &r2); + assert_se(r == NULL); + + m = hashmap_new(&string_hash_ops); + assert_se(m); + + r = hashmap_remove2(m, "no such key", &r2); + assert_se(r == NULL); + + hashmap_put(m, strdup(key1), strdup(val1)); + hashmap_put(m, strdup(key2), strdup(val2)); + + r = hashmap_remove2(m, key1, &r2); + assert_se(streq(r, val1)); + assert_se(streq(r2, key1)); + free(r); + free(r2); + + r = hashmap_get(m, key2); + assert_se(streq(r, val2)); + assert_se(!hashmap_get(m, key1)); +} + +TEST(hashmap_remove_value) { + _cleanup_hashmap_free_ Hashmap *m = NULL; + char *r; + + char val1[] = "val 1"; + char val2[] = "val 2"; + + r = hashmap_remove_value(NULL, "key 1", val1); + assert_se(r == NULL); + + m = hashmap_new(&string_hash_ops); + assert_se(m); + + r = hashmap_remove_value(m, "key 1", val1); + assert_se(r == NULL); + + hashmap_put(m, "key 1", val1); + hashmap_put(m, "key 2", val2); + + r = hashmap_remove_value(m, "key 1", val1); + assert_se(streq(r, "val 1")); + + r = hashmap_get(m, "key 2"); + assert_se(streq(r, "val 2")); + assert_se(!hashmap_get(m, "key 1")); + + r = hashmap_remove_value(m, "key 2", val1); + assert_se(r == NULL); + + r = hashmap_get(m, "key 2"); + assert_se(streq(r, "val 2")); + assert_se(!hashmap_get(m, "key 1")); +} + +TEST(hashmap_remove_and_put) { + _cleanup_hashmap_free_ Hashmap *m = NULL; + int valid; + char *r; + + m = hashmap_new(&string_hash_ops); + assert_se(m); + + valid = hashmap_remove_and_put(m, "invalid key", "new key", NULL); + assert_se(valid == -ENOENT); + + valid = hashmap_put(m, "key 1", (void*) (const char *) "val 1"); + assert_se(valid == 1); + + valid = hashmap_remove_and_put(NULL, "key 1", "key 2", (void*) (const char *) "val 2"); + assert_se(valid == -ENOENT); + + valid = hashmap_remove_and_put(m, "key 1", "key 2", (void*) (const char *) "val 2"); + assert_se(valid == 0); + + r = hashmap_get(m, "key 2"); + assert_se(streq(r, "val 2")); + assert_se(!hashmap_get(m, "key 1")); + + valid = hashmap_put(m, "key 3", (void*) (const char *) "val 3"); + assert_se(valid == 1); + valid = hashmap_remove_and_put(m, "key 3", "key 2", (void*) (const char *) "val 2"); + assert_se(valid == -EEXIST); +} + +TEST(hashmap_remove_and_replace) { + _cleanup_hashmap_free_ Hashmap *m = NULL; + int valid; + void *key1 = UINT_TO_PTR(1); + void *key2 = UINT_TO_PTR(2); + void *key3 = UINT_TO_PTR(3); + void *r; + int i, j; + + m = hashmap_new(&trivial_hash_ops); + assert_se(m); + + valid = hashmap_remove_and_replace(m, key1, key2, NULL); + assert_se(valid == -ENOENT); + + valid = hashmap_put(m, key1, key1); + assert_se(valid == 1); + + valid = hashmap_remove_and_replace(NULL, key1, key2, key2); + assert_se(valid == -ENOENT); + + valid = hashmap_remove_and_replace(m, key1, key2, key2); + assert_se(valid == 0); + + r = hashmap_get(m, key2); + assert_se(r == key2); + assert_se(!hashmap_get(m, key1)); + + valid = hashmap_put(m, key3, key3); + assert_se(valid == 1); + valid = hashmap_remove_and_replace(m, key3, key2, key2); + assert_se(valid == 0); + r = hashmap_get(m, key2); + assert_se(r == key2); + assert_se(!hashmap_get(m, key3)); + + /* Repeat this test several times to increase the chance of hitting + * the less likely case in hashmap_remove_and_replace where it + * compensates for the backward shift. */ + for (i = 0; i < 20; i++) { + hashmap_clear(m); + + for (j = 1; j < 7; j++) + hashmap_put(m, UINT_TO_PTR(10*i + j), UINT_TO_PTR(10*i + j)); + valid = hashmap_remove_and_replace(m, UINT_TO_PTR(10*i + 1), + UINT_TO_PTR(10*i + 2), + UINT_TO_PTR(10*i + 2)); + assert_se(valid == 0); + assert_se(!hashmap_get(m, UINT_TO_PTR(10*i + 1))); + for (j = 2; j < 7; j++) { + r = hashmap_get(m, UINT_TO_PTR(10*i + j)); + assert_se(r == UINT_TO_PTR(10*i + j)); + } + } +} + +TEST(hashmap_ensure_allocated) { + _cleanup_hashmap_free_ Hashmap *m = NULL; + int r; + + r = hashmap_ensure_allocated(&m, &string_hash_ops); + assert_se(r == 1); + + r = hashmap_ensure_allocated(&m, &string_hash_ops); + assert_se(r == 0); + + /* different hash ops shouldn't matter at this point */ + r = hashmap_ensure_allocated(&m, &trivial_hash_ops); + assert_se(r == 0); +} + +TEST(hashmap_foreach_key) { + _cleanup_hashmap_free_ Hashmap *m = NULL; + bool key_found[] = { false, false, false, false }; + const char *s; + const char *key; + static const char key_table[] = + "key 1\0" + "key 2\0" + "key 3\0" + "key 4\0"; + + m = hashmap_new(&string_hash_ops); + + NULSTR_FOREACH(k, key_table) + hashmap_put(m, k, (void*) (const char*) "my dummy val"); + + HASHMAP_FOREACH_KEY(s, key, m) { + assert_se(s); + if (!key_found[0] && streq(key, "key 1")) + key_found[0] = true; + else if (!key_found[1] && streq(key, "key 2")) + key_found[1] = true; + else if (!key_found[2] && streq(key, "key 3")) + key_found[2] = true; + else if (!key_found[3] && streq(key, "fail")) + key_found[3] = true; + } + + assert_se(m); + assert_se(key_found[0] && key_found[1] && key_found[2] && !key_found[3]); +} + +TEST(hashmap_foreach) { + _cleanup_hashmap_free_free_ Hashmap *m = NULL; + bool value_found[] = { false, false, false, false }; + char *val1, *val2, *val3, *val4, *s; + unsigned count; + + val1 = strdup("my val1"); + assert_se(val1); + val2 = strdup("my val2"); + assert_se(val2); + val3 = strdup("my val3"); + assert_se(val3); + val4 = strdup("my val4"); + assert_se(val4); + + count = 0; + HASHMAP_FOREACH(s, m) + count++; + assert_se(count == 0); + + m = hashmap_new(&string_hash_ops); + + count = 0; + HASHMAP_FOREACH(s, m) + count++; + assert_se(count == 0); + + hashmap_put(m, "Key 1", val1); + hashmap_put(m, "Key 2", val2); + hashmap_put(m, "Key 3", val3); + hashmap_put(m, "Key 4", val4); + + HASHMAP_FOREACH(s, m) { + if (!value_found[0] && streq(s, val1)) + value_found[0] = true; + else if (!value_found[1] && streq(s, val2)) + value_found[1] = true; + else if (!value_found[2] && streq(s, val3)) + value_found[2] = true; + else if (!value_found[3] && streq(s, val4)) + value_found[3] = true; + } + + assert_se(m); + assert_se(value_found[0] && value_found[1] && value_found[2] && value_found[3]); +} + +TEST(hashmap_merge) { + _cleanup_hashmap_free_free_ Hashmap *m = NULL; + _cleanup_hashmap_free_ Hashmap *n = NULL; + char *val1, *val2, *val3, *val4, *r; + + val1 = strdup("my val1"); + assert_se(val1); + val2 = strdup("my val2"); + assert_se(val2); + val3 = strdup("my val3"); + assert_se(val3); + val4 = strdup("my val4"); + assert_se(val4); + + m = hashmap_new(&string_hash_ops); + n = hashmap_new(&string_hash_ops); + + hashmap_put(m, "Key 1", val1); + hashmap_put(m, "Key 2", val2); + hashmap_put(n, "Key 3", val3); + hashmap_put(n, "Key 4", val4); + + assert_se(hashmap_merge(m, n) == 0); + r = hashmap_get(m, "Key 3"); + assert_se(r && streq(r, "my val3")); + r = hashmap_get(m, "Key 4"); + assert_se(r && streq(r, "my val4")); + + assert_se(m); + assert_se(n); +} + +TEST(hashmap_contains) { + _cleanup_hashmap_free_free_ Hashmap *m = NULL; + char *val1; + + val1 = strdup("my val"); + assert_se(val1); + + m = hashmap_new(&string_hash_ops); + + assert_se(!hashmap_contains(m, "Key 1")); + hashmap_put(m, "Key 1", val1); + assert_se(hashmap_contains(m, "Key 1")); + assert_se(!hashmap_contains(m, "Key 2")); + + assert_se(!hashmap_contains(NULL, "Key 1")); + + assert_se(m); +} + +TEST(hashmap_isempty) { + _cleanup_hashmap_free_free_ Hashmap *m = NULL; + char *val1; + + val1 = strdup("my val"); + assert_se(val1); + + m = hashmap_new(&string_hash_ops); + + assert_se(hashmap_isempty(m)); + hashmap_put(m, "Key 1", val1); + assert_se(!hashmap_isempty(m)); + + assert_se(m); +} + +TEST(hashmap_size) { + _cleanup_hashmap_free_free_ Hashmap *m = NULL; + char *val1, *val2, *val3, *val4; + + val1 = strdup("my val"); + assert_se(val1); + val2 = strdup("my val"); + assert_se(val2); + val3 = strdup("my val"); + assert_se(val3); + val4 = strdup("my val"); + assert_se(val4); + + assert_se(hashmap_size(NULL) == 0); + assert_se(hashmap_buckets(NULL) == 0); + + m = hashmap_new(&string_hash_ops); + + hashmap_put(m, "Key 1", val1); + hashmap_put(m, "Key 2", val2); + hashmap_put(m, "Key 3", val3); + hashmap_put(m, "Key 4", val4); + + assert_se(m); + assert_se(hashmap_size(m) == 4); + assert_se(hashmap_buckets(m) >= 4); +} + +TEST(hashmap_get) { + _cleanup_hashmap_free_free_ Hashmap *m = NULL; + char *r; + char *val; + + val = strdup("my val"); + assert_se(val); + + r = hashmap_get(NULL, "Key 1"); + assert_se(r == NULL); + + m = hashmap_new(&string_hash_ops); + + hashmap_put(m, "Key 1", val); + + r = hashmap_get(m, "Key 1"); + assert_se(streq(r, val)); + + r = hashmap_get(m, "no such key"); + assert_se(r == NULL); + + assert_se(m); +} + +TEST(hashmap_get2) { + _cleanup_(hashmap_free_free_freep) Hashmap *m = NULL; + char *r; + char *val; + char key_orig[] = "Key 1"; + void *key_copy; + + val = strdup("my val"); + assert_se(val); + + key_copy = strdup(key_orig); + assert_se(key_copy); + + r = hashmap_get2(NULL, key_orig, &key_copy); + assert_se(r == NULL); + + m = hashmap_new(&string_hash_ops); + + hashmap_put(m, key_copy, val); + key_copy = NULL; + + r = hashmap_get2(m, key_orig, &key_copy); + assert_se(streq(r, val)); + assert_se(key_orig != key_copy); + assert_se(streq(key_orig, key_copy)); + + r = hashmap_get2(m, "no such key", NULL); + assert_se(r == NULL); + + assert_se(m); +} + +static void crippled_hashmap_func(const void *p, struct siphash *state) { + return trivial_hash_func(INT_TO_PTR(PTR_TO_INT(p) & 0xff), state); +} + +static const struct hash_ops crippled_hashmap_ops = { + .hash = crippled_hashmap_func, + .compare = trivial_compare_func, +}; + +TEST(hashmap_many) { + Hashmap *h; + unsigned i, j; + void *v, *k; + bool slow = slow_tests_enabled(); + const struct { + const char *title; + const struct hash_ops *ops; + unsigned n_entries; + } tests[] = { + { "trivial_hashmap_ops", NULL, slow ? 1 << 20 : 240 }, + { "crippled_hashmap_ops", &crippled_hashmap_ops, slow ? 1 << 14 : 140 }, + }; + + log_info("/* %s (%s) */", __func__, slow ? "slow" : "fast"); + + for (j = 0; j < ELEMENTSOF(tests); j++) { + usec_t ts = now(CLOCK_MONOTONIC), n; + + assert_se(h = hashmap_new(tests[j].ops)); + + for (i = 1; i < tests[j].n_entries*3; i+=3) { + assert_se(hashmap_put(h, UINT_TO_PTR(i), UINT_TO_PTR(i)) >= 0); + assert_se(PTR_TO_UINT(hashmap_get(h, UINT_TO_PTR(i))) == i); + } + + for (i = 1; i < tests[j].n_entries*3; i++) + assert_se(hashmap_contains(h, UINT_TO_PTR(i)) == (i % 3 == 1)); + + log_info("%s %u <= %u * 0.8 = %g", + tests[j].title, hashmap_size(h), hashmap_buckets(h), hashmap_buckets(h) * 0.8); + + assert_se(hashmap_size(h) <= hashmap_buckets(h) * 0.8); + assert_se(hashmap_size(h) == tests[j].n_entries); + + while (!hashmap_isempty(h)) { + k = hashmap_first_key(h); + v = hashmap_remove(h, k); + assert_se(v == k); + } + + hashmap_free(h); + + n = now(CLOCK_MONOTONIC); + log_info("test took %s", FORMAT_TIMESPAN(n - ts, 0)); + } +} + +extern unsigned custom_counter; +extern const struct hash_ops boring_hash_ops, custom_hash_ops; + +TEST(hashmap_free) { + Hashmap *h; + bool slow = slow_tests_enabled(); + usec_t ts, n; + unsigned n_entries = slow ? 1 << 20 : 240; + + const struct { + const char *title; + const struct hash_ops *ops; + unsigned expect_counter; + } tests[] = { + { "string_hash_ops", &boring_hash_ops, 2 * n_entries}, + { "custom_free_hash_ops", &custom_hash_ops, 0 }, + }; + + log_info("/* %s (%s, %u entries) */", __func__, slow ? "slow" : "fast", n_entries); + + for (unsigned j = 0; j < ELEMENTSOF(tests); j++) { + ts = now(CLOCK_MONOTONIC); + assert_se(h = hashmap_new(tests[j].ops)); + + custom_counter = 0; + for (unsigned i = 0; i < n_entries; i++) { + char s[DECIMAL_STR_MAX(unsigned)]; + char *k, *v; + + xsprintf(s, "%u", i); + assert_se(k = strdup(s)); + assert_se(v = strdup(s)); + custom_counter += 2; + + assert_se(hashmap_put(h, k, v) >= 0); + } + + hashmap_free(h); + + n = now(CLOCK_MONOTONIC); + log_info("%s test took %s", tests[j].title, FORMAT_TIMESPAN(n - ts, 0)); + + assert_se(custom_counter == tests[j].expect_counter); + } +} + +typedef struct Item { + int seen; +} Item; +static void item_seen(Item *item) { + item->seen++; +} + +TEST(hashmap_free_with_destructor) { + Hashmap *m; + struct Item items[4] = {}; + unsigned i; + + assert_se(m = hashmap_new(NULL)); + for (i = 0; i < ELEMENTSOF(items) - 1; i++) + assert_se(hashmap_put(m, INT_TO_PTR(i), items + i) == 1); + + m = hashmap_free_with_destructor(m, item_seen); + assert_se(items[0].seen == 1); + assert_se(items[1].seen == 1); + assert_se(items[2].seen == 1); + assert_se(items[3].seen == 0); +} + +TEST(hashmap_first) { + _cleanup_hashmap_free_ Hashmap *m = NULL; + + m = hashmap_new(&string_hash_ops); + assert_se(m); + + assert_se(!hashmap_first(m)); + assert_se(hashmap_put(m, "key 1", (void*) "val 1") == 1); + assert_se(streq(hashmap_first(m), "val 1")); + assert_se(hashmap_put(m, "key 2", (void*) "val 2") == 1); +#ifdef ORDERED + assert_se(streq(hashmap_first(m), "val 1")); + assert_se(hashmap_remove(m, "key 1")); + assert_se(streq(hashmap_first(m), "val 2")); +#endif +} + +TEST(hashmap_first_key) { + _cleanup_hashmap_free_ Hashmap *m = NULL; + + m = hashmap_new(&string_hash_ops); + assert_se(m); + + assert_se(!hashmap_first_key(m)); + assert_se(hashmap_put(m, "key 1", NULL) == 1); + assert_se(streq(hashmap_first_key(m), "key 1")); + assert_se(hashmap_put(m, "key 2", NULL) == 1); +#ifdef ORDERED + assert_se(streq(hashmap_first_key(m), "key 1")); + assert_se(hashmap_remove(m, "key 1") == NULL); + assert_se(streq(hashmap_first_key(m), "key 2")); +#endif +} + +TEST(hashmap_steal_first_key) { + _cleanup_hashmap_free_ Hashmap *m = NULL; + + m = hashmap_new(&string_hash_ops); + assert_se(m); + + assert_se(!hashmap_steal_first_key(m)); + assert_se(hashmap_put(m, "key 1", NULL) == 1); + assert_se(streq(hashmap_steal_first_key(m), "key 1")); + + assert_se(hashmap_isempty(m)); +} + +TEST(hashmap_steal_first) { + _cleanup_hashmap_free_ Hashmap *m = NULL; + int seen[3] = {}; + char *val; + + m = hashmap_new(&string_hash_ops); + assert_se(m); + + assert_se(hashmap_put(m, "key 1", (void*) "1") == 1); + assert_se(hashmap_put(m, "key 2", (void*) "22") == 1); + assert_se(hashmap_put(m, "key 3", (void*) "333") == 1); + + while ((val = hashmap_steal_first(m))) + seen[strlen(val) - 1]++; + + assert_se(seen[0] == 1 && seen[1] == 1 && seen[2] == 1); + + assert_se(hashmap_isempty(m)); +} + +TEST(hashmap_clear_free_free) { + _cleanup_hashmap_free_ Hashmap *m = NULL; + + m = hashmap_new(&string_hash_ops); + assert_se(m); + + assert_se(hashmap_put(m, strdup("key 1"), NULL) == 1); + assert_se(hashmap_put(m, strdup("key 2"), NULL) == 1); + assert_se(hashmap_put(m, strdup("key 3"), NULL) == 1); + + hashmap_clear_free_free(m); + assert_se(hashmap_isempty(m)); + + assert_se(hashmap_put(m, strdup("key 1"), strdup("value 1")) == 1); + assert_se(hashmap_put(m, strdup("key 2"), strdup("value 2")) == 1); + assert_se(hashmap_put(m, strdup("key 3"), strdup("value 3")) == 1); + + hashmap_clear_free_free(m); + assert_se(hashmap_isempty(m)); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(test_hash_ops_key, char, string_hash_func, string_compare_func, free); +DEFINE_PRIVATE_HASH_OPS_FULL(test_hash_ops_full, char, string_hash_func, string_compare_func, free, char, free); + +TEST(hashmap_clear_free_with_destructor) { + _cleanup_hashmap_free_ Hashmap *m = NULL; + + m = hashmap_new(&test_hash_ops_key); + assert_se(m); + + assert_se(hashmap_put(m, strdup("key 1"), NULL) == 1); + assert_se(hashmap_put(m, strdup("key 2"), NULL) == 1); + assert_se(hashmap_put(m, strdup("key 3"), NULL) == 1); + + hashmap_clear_free(m); + assert_se(hashmap_isempty(m)); + m = hashmap_free(m); + + m = hashmap_new(&test_hash_ops_full); + assert_se(m); + + assert_se(hashmap_put(m, strdup("key 1"), strdup("value 1")) == 1); + assert_se(hashmap_put(m, strdup("key 2"), strdup("value 2")) == 1); + assert_se(hashmap_put(m, strdup("key 3"), strdup("value 3")) == 1); + + hashmap_clear_free(m); + assert_se(hashmap_isempty(m)); +} + +TEST(hashmap_reserve) { + _cleanup_hashmap_free_ Hashmap *m = NULL; + + m = hashmap_new(&string_hash_ops); + + assert_se(hashmap_reserve(m, 1) == 0); + assert_se(hashmap_buckets(m) < 1000); + assert_se(hashmap_reserve(m, 1000) == 0); + assert_se(hashmap_buckets(m) >= 1000); + assert_se(hashmap_isempty(m)); + + assert_se(hashmap_put(m, "key 1", (void*) "val 1") == 1); + + assert_se(hashmap_reserve(m, UINT_MAX) == -ENOMEM); + assert_se(hashmap_reserve(m, UINT_MAX - 1) == -ENOMEM); +} + +TEST(path_hashmap) { + _cleanup_hashmap_free_ Hashmap *h = NULL; + + assert_se(h = hashmap_new(&path_hash_ops)); + + assert_se(hashmap_put(h, "foo", INT_TO_PTR(1)) >= 0); + assert_se(hashmap_put(h, "/foo", INT_TO_PTR(2)) >= 0); + assert_se(hashmap_put(h, "//foo", INT_TO_PTR(3)) == -EEXIST); + assert_se(hashmap_put(h, "//foox/", INT_TO_PTR(4)) >= 0); + assert_se(hashmap_put(h, "/foox////", INT_TO_PTR(5)) == -EEXIST); + assert_se(hashmap_put(h, "//././/foox//.//.", INT_TO_PTR(5)) == -EEXIST); + assert_se(hashmap_put(h, "foo//////bar/quux//", INT_TO_PTR(6)) >= 0); + assert_se(hashmap_put(h, "foo/bar//quux/", INT_TO_PTR(8)) == -EEXIST); + assert_se(hashmap_put(h, "foo./ba.r//.quux/", INT_TO_PTR(9)) >= 0); + assert_se(hashmap_put(h, "foo./ba.r//.//.quux///./", INT_TO_PTR(10)) == -EEXIST); + + assert_se(hashmap_get(h, "foo") == INT_TO_PTR(1)); + assert_se(hashmap_get(h, "foo/") == INT_TO_PTR(1)); + assert_se(hashmap_get(h, "foo////") == INT_TO_PTR(1)); + assert_se(hashmap_get(h, "/foo") == INT_TO_PTR(2)); + assert_se(hashmap_get(h, "//foo") == INT_TO_PTR(2)); + assert_se(hashmap_get(h, "/////foo////") == INT_TO_PTR(2)); + assert_se(hashmap_get(h, "/////foox////") == INT_TO_PTR(4)); + assert_se(hashmap_get(h, "/.///./foox//.//") == INT_TO_PTR(4)); + assert_se(hashmap_get(h, "/foox/") == INT_TO_PTR(4)); + assert_se(hashmap_get(h, "/foox") == INT_TO_PTR(4)); + assert_se(!hashmap_get(h, "foox")); + assert_se(hashmap_get(h, "foo/bar/quux") == INT_TO_PTR(6)); + assert_se(hashmap_get(h, "foo////bar////quux/////") == INT_TO_PTR(6)); + assert_se(!hashmap_get(h, "/foo////bar////quux/////")); + assert_se(hashmap_get(h, "foo././//ba.r////.quux///.//.") == INT_TO_PTR(9)); +} + +TEST(string_strv_hashmap) { + _cleanup_hashmap_free_ Hashmap *m = NULL; + char **s; + + assert_se(string_strv_hashmap_put(&m, "foo", "bar") == 1); + assert_se(string_strv_hashmap_put(&m, "foo", "bar") == 0); + assert_se(string_strv_hashmap_put(&m, "foo", "BAR") == 1); + assert_se(string_strv_hashmap_put(&m, "foo", "BAR") == 0); + assert_se(string_strv_hashmap_put(&m, "foo", "bar") == 0); + assert_se(hashmap_contains(m, "foo")); + + s = hashmap_get(m, "foo"); + assert_se(strv_equal(s, STRV_MAKE("bar", "BAR"))); + + assert_se(string_strv_hashmap_put(&m, "xxx", "bar") == 1); + assert_se(string_strv_hashmap_put(&m, "xxx", "bar") == 0); + assert_se(string_strv_hashmap_put(&m, "xxx", "BAR") == 1); + assert_se(string_strv_hashmap_put(&m, "xxx", "BAR") == 0); + assert_se(string_strv_hashmap_put(&m, "xxx", "bar") == 0); + assert_se(hashmap_contains(m, "xxx")); + + s = hashmap_get(m, "xxx"); + assert_se(strv_equal(s, STRV_MAKE("bar", "BAR"))); +} + +TEST(hashmap_dump_sorted) { + static void * const expected[] = { UINT_TO_PTR(123U), UINT_TO_PTR(12U), UINT_TO_PTR(345U), }; + _cleanup_hashmap_free_ Hashmap *m = NULL; + _cleanup_free_ void **vals = NULL; + size_t n; + + assert_se(m = hashmap_new(&string_hash_ops)); + + assert_se(hashmap_dump_sorted(m, &vals, &n) >= 0); + assert_se(n == 0); + assert_se(!vals); + + assert_se(hashmap_put(m, "key 0", expected[0]) == 1); + assert_se(hashmap_put(m, "key 1", expected[1]) == 1); + assert_se(hashmap_put(m, "key 2", expected[2]) == 1); + + assert_se(hashmap_dump_sorted(m, &vals, &n) >= 0); + assert_se(n == ELEMENTSOF(expected)); + assert_se(memcmp(vals, expected, n * sizeof(void*)) == 0); + + vals = mfree(vals); + m = hashmap_free(m); + + assert_se(m = hashmap_new(NULL)); + + assert_se(hashmap_dump_sorted(m, &vals, &n) >= 0); + assert_se(n == 0); + assert_se(!vals); + + assert_se(hashmap_put(m, UINT_TO_PTR(333U), expected[2]) == 1); + assert_se(hashmap_put(m, UINT_TO_PTR(222U), expected[1]) == 1); + assert_se(hashmap_put(m, UINT_TO_PTR(111U), expected[0]) == 1); + + assert_se(hashmap_dump_sorted(m, &vals, &n) >= 0); + assert_se(n == ELEMENTSOF(expected)); + assert_se(memcmp(vals, expected, n * sizeof(void*)) == 0); +} + +/* Signal to test-hashmap.c that tests from this compilation unit were run. */ +extern int n_extern_tests_run; +TEST(ensure_extern_hashmap_tests) { + n_extern_tests_run++; +} diff --git a/src/test/test-hashmap.c b/src/test/test-hashmap.c new file mode 100644 index 0000000..5daa0e6 --- /dev/null +++ b/src/test/test-hashmap.c @@ -0,0 +1,171 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "hashmap.h" +#include "string-util.h" +#include "tests.h" + +unsigned custom_counter = 0; +static void custom_destruct(void* p) { + custom_counter--; + free(p); +} + +DEFINE_HASH_OPS_FULL(boring_hash_ops, char, string_hash_func, string_compare_func, free, char, free); +DEFINE_HASH_OPS_FULL(custom_hash_ops, char, string_hash_func, string_compare_func, custom_destruct, char, custom_destruct); + +TEST(ordered_hashmap_next) { + _cleanup_ordered_hashmap_free_ OrderedHashmap *m = NULL; + int i; + + assert_se(m = ordered_hashmap_new(NULL)); + for (i = -2; i <= 2; i++) + assert_se(ordered_hashmap_put(m, INT_TO_PTR(i), INT_TO_PTR(i+10)) == 1); + for (i = -2; i <= 1; i++) + assert_se(ordered_hashmap_next(m, INT_TO_PTR(i)) == INT_TO_PTR(i+11)); + assert_se(!ordered_hashmap_next(m, INT_TO_PTR(2))); + assert_se(!ordered_hashmap_next(NULL, INT_TO_PTR(1))); + assert_se(!ordered_hashmap_next(m, INT_TO_PTR(3))); +} + +TEST(uint64_compare_func) { + const uint64_t a = 0x100, b = 0x101; + + assert_se(uint64_compare_func(&a, &a) == 0); + assert_se(uint64_compare_func(&a, &b) == -1); + assert_se(uint64_compare_func(&b, &a) == 1); +} + +TEST(trivial_compare_func) { + assert_se(trivial_compare_func(INT_TO_PTR('a'), INT_TO_PTR('a')) == 0); + assert_se(trivial_compare_func(INT_TO_PTR('a'), INT_TO_PTR('b')) == -1); + assert_se(trivial_compare_func(INT_TO_PTR('b'), INT_TO_PTR('a')) == 1); +} + +TEST(string_compare_func) { + assert_se(string_compare_func("fred", "wilma") != 0); + assert_se(string_compare_func("fred", "fred") == 0); +} + +static void compare_cache(Hashmap *map, IteratedCache *cache) { + const void **keys = NULL, **values = NULL; + unsigned num, idx; + void *k, *v; + + assert_se(iterated_cache_get(cache, &keys, &values, &num) == 0); + assert_se(num == 0 || keys); + assert_se(num == 0 || values); + + idx = 0; + HASHMAP_FOREACH_KEY(v, k, map) { + assert_se(v == values[idx]); + assert_se(k == keys[idx]); + + idx++; + } + + assert_se(idx == num); +} + +TEST(iterated_cache) { + Hashmap *m; + IteratedCache *c; + + assert_se(m = hashmap_new(NULL)); + assert_se(c = hashmap_iterated_cache_new(m)); + compare_cache(m, c); + + for (int stage = 0; stage < 100; stage++) { + + for (int i = 0; i < 100; i++) { + int foo = stage * 1000 + i; + + assert_se(hashmap_put(m, INT_TO_PTR(foo), INT_TO_PTR(foo + 777)) == 1); + } + + compare_cache(m, c); + + if (!(stage % 10)) { + for (int i = 0; i < 100; i++) { + int foo = stage * 1000 + i; + + assert_se(hashmap_remove(m, INT_TO_PTR(foo)) == INT_TO_PTR(foo + 777)); + } + + compare_cache(m, c); + } + } + + hashmap_clear(m); + compare_cache(m, c); + + assert_se(hashmap_free(m) == NULL); + assert_se(iterated_cache_free(c) == NULL); +} + +TEST(hashmap_put_strdup) { + _cleanup_hashmap_free_ Hashmap *m = NULL; + char *s; + + /* We don't have ordered_hashmap_put_strdup() yet. If it is added, + * these tests should be moved to test-hashmap-plain.c. */ + + assert_se(hashmap_put_strdup(&m, "foo", "bar") == 1); + assert_se(hashmap_put_strdup(&m, "foo", "bar") == 0); + assert_se(hashmap_put_strdup(&m, "foo", "BAR") == -EEXIST); + assert_se(hashmap_put_strdup(&m, "foo", "bar") == 0); + assert_se(hashmap_contains(m, "foo")); + + s = hashmap_get(m, "foo"); + assert_se(streq(s, "bar")); + + assert_se(hashmap_put_strdup(&m, "xxx", "bar") == 1); + assert_se(hashmap_put_strdup(&m, "xxx", "bar") == 0); + assert_se(hashmap_put_strdup(&m, "xxx", "BAR") == -EEXIST); + assert_se(hashmap_put_strdup(&m, "xxx", "bar") == 0); + assert_se(hashmap_contains(m, "xxx")); + + s = hashmap_get(m, "xxx"); + assert_se(streq(s, "bar")); +} + +TEST(hashmap_put_strdup_null) { + _cleanup_hashmap_free_ Hashmap *m = NULL; + char *s; + + assert_se(hashmap_put_strdup(&m, "foo", "bar") == 1); + assert_se(hashmap_put_strdup(&m, "foo", "bar") == 0); + assert_se(hashmap_put_strdup(&m, "foo", NULL) == -EEXIST); + assert_se(hashmap_put_strdup(&m, "foo", "bar") == 0); + assert_se(hashmap_contains(m, "foo")); + + s = hashmap_get(m, "foo"); + assert_se(streq(s, "bar")); + + assert_se(hashmap_put_strdup(&m, "xxx", NULL) == 1); + assert_se(hashmap_put_strdup(&m, "xxx", "bar") == -EEXIST); + assert_se(hashmap_put_strdup(&m, "xxx", NULL) == 0); + assert_se(hashmap_contains(m, "xxx")); + + s = hashmap_get(m, "xxx"); + assert_se(s == NULL); +} + +/* This file tests in test-hashmap-plain.c, and tests in test-hashmap-ordered.c, which is generated + * from test-hashmap-plain.c. Hashmap tests should be added to test-hashmap-plain.c, and here only if + * they don't apply to ordered hashmaps. */ + +/* This variable allows us to assert that the tests from different compilation units were actually run. */ +int n_extern_tests_run = 0; + +static int intro(void) { + assert_se(n_extern_tests_run == 0); + return EXIT_SUCCESS; +} + +static int outro(void) { + /* Ensure hashmap and ordered_hashmap were tested. */ + assert_se(n_extern_tests_run == 2); + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_FULL(LOG_INFO, intro, outro); diff --git a/src/test/test-hexdecoct.c b/src/test/test-hexdecoct.c new file mode 100644 index 0000000..f884008 --- /dev/null +++ b/src/test/test-hexdecoct.c @@ -0,0 +1,548 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> + +#include "alloc-util.h" +#include "hexdecoct.h" +#include "macro.h" +#include "random-util.h" +#include "string-util.h" +#include "tests.h" + +TEST(hexchar) { + assert_se(hexchar(0xa) == 'a'); + assert_se(hexchar(0x0) == '0'); +} + +TEST(unhexchar) { + assert_se(unhexchar('a') == 0xA); + assert_se(unhexchar('A') == 0xA); + assert_se(unhexchar('0') == 0x0); +} + +TEST(base32hexchar) { + assert_se(base32hexchar(0) == '0'); + assert_se(base32hexchar(9) == '9'); + assert_se(base32hexchar(10) == 'A'); + assert_se(base32hexchar(31) == 'V'); +} + +TEST(unbase32hexchar) { + assert_se(unbase32hexchar('0') == 0); + assert_se(unbase32hexchar('9') == 9); + assert_se(unbase32hexchar('A') == 10); + assert_se(unbase32hexchar('V') == 31); + assert_se(unbase32hexchar('=') == -EINVAL); +} + +TEST(base64char) { + assert_se(base64char(0) == 'A'); + assert_se(base64char(26) == 'a'); + assert_se(base64char(63) == '/'); +} + +TEST(unbase64char) { + assert_se(unbase64char('A') == 0); + assert_se(unbase64char('Z') == 25); + assert_se(unbase64char('a') == 26); + assert_se(unbase64char('z') == 51); + assert_se(unbase64char('0') == 52); + assert_se(unbase64char('9') == 61); + assert_se(unbase64char('+') == 62); + assert_se(unbase64char('/') == 63); + assert_se(unbase64char('=') == -EINVAL); +} + +TEST(octchar) { + assert_se(octchar(00) == '0'); + assert_se(octchar(07) == '7'); +} + +TEST(unoctchar) { + assert_se(unoctchar('0') == 00); + assert_se(unoctchar('7') == 07); +} + +TEST(decchar) { + assert_se(decchar(0) == '0'); + assert_se(decchar(9) == '9'); +} + +TEST(undecchar) { + assert_se(undecchar('0') == 0); + assert_se(undecchar('9') == 9); +} + +static void test_hexmem_one(const char *in, const char *expected) { + _cleanup_free_ char *result = NULL; + _cleanup_free_ void *mem = NULL; + size_t len; + + assert_se(result = hexmem(in, strlen_ptr(in))); + log_debug("hexmem(\"%s\") → \"%s\" (expected: \"%s\")", strnull(in), result, expected); + assert_se(streq(result, expected)); + + assert_se(unhexmem(result, SIZE_MAX, &mem, &len) >= 0); + assert_se(memcmp_safe(mem, in, len) == 0); +} + +TEST(hexmem) { + test_hexmem_one(NULL, ""); + test_hexmem_one("", ""); + test_hexmem_one("foo", "666f6f"); +} + +static void test_unhexmem_one(const char *s, size_t l, int retval) { + _cleanup_free_ char *hex = NULL; + _cleanup_free_ void *mem = NULL; + size_t len; + + assert_se(unhexmem(s, l, &mem, &len) == retval); + if (retval == 0) { + char *answer; + + if (l == SIZE_MAX) + l = strlen(s); + + assert_se(hex = hexmem(mem, len)); + answer = strndupa_safe(strempty(s), l); + assert_se(streq(delete_chars(answer, WHITESPACE), hex)); + } +} + +TEST(unhexmem) { + const char *hex = "efa2149213"; + const char *hex_space = " e f a\n 2\r 14\n\r\t9\t2 \n1\r3 \r\r\t"; + const char *hex_invalid = "efa214921o"; + + test_unhexmem_one(NULL, 0, 0); + test_unhexmem_one("", 0, 0); + test_unhexmem_one("", SIZE_MAX, 0); + test_unhexmem_one(" \n \t\r \t\t \n\n\n", SIZE_MAX, 0); + test_unhexmem_one(hex_invalid, strlen(hex_invalid), -EINVAL); + test_unhexmem_one(hex_invalid, (size_t) - 1, -EINVAL); + test_unhexmem_one(hex, strlen(hex) - 1, -EPIPE); + test_unhexmem_one(hex, strlen(hex), 0); + test_unhexmem_one(hex, SIZE_MAX, 0); + test_unhexmem_one(hex_space, strlen(hex_space), 0); + test_unhexmem_one(hex_space, SIZE_MAX, 0); +} + +/* https://tools.ietf.org/html/rfc4648#section-10 */ +TEST(base32hexmem) { + char *b32; + + b32 = base32hexmem("", STRLEN(""), true); + assert_se(b32); + assert_se(streq(b32, "")); + free(b32); + + b32 = base32hexmem("f", STRLEN("f"), true); + assert_se(b32); + assert_se(streq(b32, "CO======")); + free(b32); + + b32 = base32hexmem("fo", STRLEN("fo"), true); + assert_se(b32); + assert_se(streq(b32, "CPNG====")); + free(b32); + + b32 = base32hexmem("foo", STRLEN("foo"), true); + assert_se(b32); + assert_se(streq(b32, "CPNMU===")); + free(b32); + + b32 = base32hexmem("foob", STRLEN("foob"), true); + assert_se(b32); + assert_se(streq(b32, "CPNMUOG=")); + free(b32); + + b32 = base32hexmem("fooba", STRLEN("fooba"), true); + assert_se(b32); + assert_se(streq(b32, "CPNMUOJ1")); + free(b32); + + b32 = base32hexmem("foobar", STRLEN("foobar"), true); + assert_se(b32); + assert_se(streq(b32, "CPNMUOJ1E8======")); + free(b32); + + b32 = base32hexmem("", STRLEN(""), false); + assert_se(b32); + assert_se(streq(b32, "")); + free(b32); + + b32 = base32hexmem("f", STRLEN("f"), false); + assert_se(b32); + assert_se(streq(b32, "CO")); + free(b32); + + b32 = base32hexmem("fo", STRLEN("fo"), false); + assert_se(b32); + assert_se(streq(b32, "CPNG")); + free(b32); + + b32 = base32hexmem("foo", STRLEN("foo"), false); + assert_se(b32); + assert_se(streq(b32, "CPNMU")); + free(b32); + + b32 = base32hexmem("foob", STRLEN("foob"), false); + assert_se(b32); + assert_se(streq(b32, "CPNMUOG")); + free(b32); + + b32 = base32hexmem("fooba", STRLEN("fooba"), false); + assert_se(b32); + assert_se(streq(b32, "CPNMUOJ1")); + free(b32); + + b32 = base32hexmem("foobar", STRLEN("foobar"), false); + assert_se(b32); + assert_se(streq(b32, "CPNMUOJ1E8")); + free(b32); +} + +static void test_unbase32hexmem_one(const char *hex, bool padding, int retval, const char *ans) { + _cleanup_free_ void *mem = NULL; + size_t len; + + assert_se(unbase32hexmem(hex, SIZE_MAX, padding, &mem, &len) == retval); + if (retval == 0) { + char *str; + + str = strndupa_safe(mem, len); + assert_se(streq(str, ans)); + } +} + +TEST(unbase32hexmem) { + test_unbase32hexmem_one("", true, 0, ""); + + test_unbase32hexmem_one("CO======", true, 0, "f"); + test_unbase32hexmem_one("CPNG====", true, 0, "fo"); + test_unbase32hexmem_one("CPNMU===", true, 0, "foo"); + test_unbase32hexmem_one("CPNMUOG=", true, 0, "foob"); + test_unbase32hexmem_one("CPNMUOJ1", true, 0, "fooba"); + test_unbase32hexmem_one("CPNMUOJ1E8======", true, 0, "foobar"); + + test_unbase32hexmem_one("A", true, -EINVAL, NULL); + test_unbase32hexmem_one("A=======", true, -EINVAL, NULL); + test_unbase32hexmem_one("AAA=====", true, -EINVAL, NULL); + test_unbase32hexmem_one("AAAAAA==", true, -EINVAL, NULL); + test_unbase32hexmem_one("AB======", true, -EINVAL, NULL); + test_unbase32hexmem_one("AAAB====", true, -EINVAL, NULL); + test_unbase32hexmem_one("AAAAB===", true, -EINVAL, NULL); + test_unbase32hexmem_one("AAAAAAB=", true, -EINVAL, NULL); + + test_unbase32hexmem_one("XPNMUOJ1", true, -EINVAL, NULL); + test_unbase32hexmem_one("CXNMUOJ1", true, -EINVAL, NULL); + test_unbase32hexmem_one("CPXMUOJ1", true, -EINVAL, NULL); + test_unbase32hexmem_one("CPNXUOJ1", true, -EINVAL, NULL); + test_unbase32hexmem_one("CPNMXOJ1", true, -EINVAL, NULL); + test_unbase32hexmem_one("CPNMUXJ1", true, -EINVAL, NULL); + test_unbase32hexmem_one("CPNMUOX1", true, -EINVAL, NULL); + test_unbase32hexmem_one("CPNMUOJX", true, -EINVAL, NULL); + + test_unbase32hexmem_one("", false, 0, ""); + test_unbase32hexmem_one("CO", false, 0, "f"); + test_unbase32hexmem_one("CPNG", false, 0, "fo"); + test_unbase32hexmem_one("CPNMU", false, 0, "foo"); + test_unbase32hexmem_one("CPNMUOG", false, 0, "foob"); + test_unbase32hexmem_one("CPNMUOJ1", false, 0, "fooba"); + test_unbase32hexmem_one("CPNMUOJ1E8", false, 0, "foobar"); + test_unbase32hexmem_one("CPNMUOG=", false, -EINVAL, NULL); + test_unbase32hexmem_one("CPNMUOJ1E8======", false, -EINVAL, NULL); + + test_unbase32hexmem_one("A", false, -EINVAL, NULL); + test_unbase32hexmem_one("AAA", false, -EINVAL, NULL); + test_unbase32hexmem_one("AAAAAA", false, -EINVAL, NULL); + test_unbase32hexmem_one("AB", false, -EINVAL, NULL); + test_unbase32hexmem_one("AAAB", false, -EINVAL, NULL); + test_unbase32hexmem_one("AAAAB", false, -EINVAL, NULL); + test_unbase32hexmem_one("AAAAAAB", false, -EINVAL, NULL); +} + +/* https://tools.ietf.org/html/rfc4648#section-10 */ +TEST(base64mem) { + char *b64; + + assert_se(base64mem("", STRLEN(""), &b64) == 0); + assert_se(streq(b64, "")); + free(b64); + + assert_se(base64mem("f", STRLEN("f"), &b64) == 4); + assert_se(streq(b64, "Zg==")); + free(b64); + + assert_se(base64mem("fo", STRLEN("fo"), &b64) == 4); + assert_se(streq(b64, "Zm8=")); + free(b64); + + assert_se(base64mem("foo", STRLEN("foo"), &b64) == 4); + assert_se(streq(b64, "Zm9v")); + free(b64); + + assert_se(base64mem("foob", STRLEN("foob"), &b64) == 8); + assert_se(streq(b64, "Zm9vYg==")); + free(b64); + + assert_se(base64mem("fooba", STRLEN("fooba"), &b64) == 8); + assert_se(streq(b64, "Zm9vYmE=")); + free(b64); + + assert_se(base64mem("foobar", STRLEN("foobar"), &b64) == 8); + assert_se(streq(b64, "Zm9vYmFy")); + free(b64); +} + +TEST(base64mem_linebreak) { + uint8_t data[4096]; + + for (size_t i = 0; i < 20; i++) { + _cleanup_free_ char *encoded = NULL; + _cleanup_free_ void *decoded = NULL; + size_t decoded_size; + uint64_t n, m; + ssize_t l; + + /* Try a bunch of differently sized blobs */ + n = random_u64_range(sizeof(data)); + random_bytes(data, n); + + /* Break at various different columns */ + m = 1 + random_u64_range(n + 5); + + l = base64mem_full(data, n, m, &encoded); + assert_se(l >= 0); + assert_se(encoded); + assert_se((size_t) l == strlen(encoded)); + + assert_se(unbase64mem(encoded, SIZE_MAX, &decoded, &decoded_size) >= 0); + assert_se(decoded_size == n); + assert_se(memcmp(data, decoded, n) == 0); + + /* Also try in secure mode */ + decoded = mfree(decoded); + decoded_size = 0; + assert_se(unbase64mem_full(encoded, SIZE_MAX, /* secure= */ true, &decoded, &decoded_size) >= 0); + assert_se(decoded_size == n); + assert_se(memcmp(data, decoded, n) == 0); + + for (size_t j = 0; j < (size_t) l; j++) + assert_se((encoded[j] == '\n') == (j % (m + 1) == m)); + } +} + +static void test_base64_append_one(char **buf, size_t *len, const char *in, const char *expected) { + ssize_t new_len; + + new_len = base64_append(buf, *len, in, strlen_ptr(in), 8, 12); + assert_se(new_len >= 0); + log_debug("base64_append_one(\"%s\")\nresult:\n%s\nexpected:\n%s", in, strnull(*buf), strnull(expected)); + assert_se((size_t) new_len == strlen_ptr(*buf)); + assert_se(streq_ptr(*buf, expected)); + *len = new_len; +} + +TEST(base64_append) { + _cleanup_free_ char *buf = NULL; + size_t len = 0; + + test_base64_append_one(&buf, &len, "", NULL); + test_base64_append_one(&buf, &len, "f", + "Zg=="); + test_base64_append_one(&buf, &len, "fo", + "Zg== Zm8="); + test_base64_append_one(&buf, &len, "foo", + "Zg== Zm8=\n" + " Zm9v"); + test_base64_append_one(&buf, &len, "foob", + "Zg== Zm8=\n" + " Zm9v\n" + " Zm9v\n" + " Yg=="); + test_base64_append_one(&buf, &len, "fooba", + "Zg== Zm8=\n" + " Zm9v\n" + " Zm9v\n" + " Yg==\n" + " Zm9v\n" + " YmE="); + test_base64_append_one(&buf, &len, "foobar", + "Zg== Zm8=\n" + " Zm9v\n" + " Zm9v\n" + " Yg==\n" + " Zm9v\n" + " YmE=\n" + " Zm9v\n" + " YmFy"); + + assert_se(free_and_strdup(&buf, "hogehogehogehoge") >= 0); + len = strlen(buf); + + test_base64_append_one(&buf, &len, "", + "hogehogehogehoge"); + test_base64_append_one(&buf, &len, "f", + "hogehogehogehoge\n" + " Zg=="); + test_base64_append_one(&buf, &len, "fo", + "hogehogehogehoge\n" + " Zg==\n" + " Zm8="); + test_base64_append_one(&buf, &len, "foo", + "hogehogehogehoge\n" + " Zg==\n" + " Zm8=\n" + " Zm9v"); + test_base64_append_one(&buf, &len, "foob", + "hogehogehogehoge\n" + " Zg==\n" + " Zm8=\n" + " Zm9v\n" + " Zm9v\n" + " Yg=="); + test_base64_append_one(&buf, &len, "fooba", + "hogehogehogehoge\n" + " Zg==\n" + " Zm8=\n" + " Zm9v\n" + " Zm9v\n" + " Yg==\n" + " Zm9v\n" + " YmE="); + test_base64_append_one(&buf, &len, "foobar", + "hogehogehogehoge\n" + " Zg==\n" + " Zm8=\n" + " Zm9v\n" + " Zm9v\n" + " Yg==\n" + " Zm9v\n" + " YmE=\n" + " Zm9v\n" + " YmFy"); + + assert_se(free_and_strdup(&buf, "hogehogehogehoge") >= 0); + len = strlen(buf); + + test_base64_append_one(&buf, &len, "foobarfoobarfoobarfoobar", + "hogehogehogehoge\n" + " Zm9v\n" + " YmFy\n" + " Zm9v\n" + " YmFy\n" + " Zm9v\n" + " YmFy\n" + " Zm9v\n" + " YmFy"); + + assert_se(free_and_strdup(&buf, "aaa") >= 0); + len = strlen(buf); + + test_base64_append_one(&buf, &len, "foobarfoobarfoobarfoobar", + "aaa Zm9vYmFy\n" + " Zm9vYmFy\n" + " Zm9vYmFy\n" + " Zm9vYmFy"); +} + +static void test_unbase64mem_one(const char *input, const char *output, int ret) { + _cleanup_free_ void *buffer = NULL; + size_t size = 0; + + assert_se(unbase64mem(input, SIZE_MAX, &buffer, &size) == ret); + if (ret >= 0) { + assert_se(size == strlen(output)); + assert_se(memcmp(buffer, output, size) == 0); + assert_se(((char*) buffer)[size] == 0); + } + + /* also try in secure mode */ + buffer = mfree(buffer); + size = 0; + + assert_se(unbase64mem_full(input, SIZE_MAX, /* secure=*/ true, &buffer, &size) == ret); + if (ret >= 0) { + assert_se(size == strlen(output)); + assert_se(memcmp(buffer, output, size) == 0); + assert_se(((char*) buffer)[size] == 0); + } +} + +TEST(unbase64mem) { + + test_unbase64mem_one("", "", 0); + test_unbase64mem_one("Zg==", "f", 0); + test_unbase64mem_one("Zm8=", "fo", 0); + test_unbase64mem_one("Zm9v", "foo", 0); + test_unbase64mem_one("Zm9vYg==", "foob", 0); + test_unbase64mem_one("Zm9vYmE=", "fooba", 0); + test_unbase64mem_one("Zm9vYmFy", "foobar", 0); + + test_unbase64mem_one(" ", "", 0); + test_unbase64mem_one(" \n\r ", "", 0); + test_unbase64mem_one(" Zg\n== ", "f", 0); + test_unbase64mem_one(" Zm 8=\r", "fo", 0); + test_unbase64mem_one(" Zm9\n\r\r\nv ", "foo", 0); + test_unbase64mem_one(" Z m9vYg==\n\r", "foob", 0); + test_unbase64mem_one(" Zm 9vYmE= ", "fooba", 0); + test_unbase64mem_one(" Z m9v YmFy ", "foobar", 0); + + test_unbase64mem_one("A", NULL, -EPIPE); + test_unbase64mem_one("A====", NULL, -EINVAL); + test_unbase64mem_one("AAB==", NULL, -EINVAL); + test_unbase64mem_one(" A A A B = ", NULL, -EINVAL); + test_unbase64mem_one(" Z m 8 = q u u x ", NULL, -ENAMETOOLONG); +} + +TEST(hexdump) { + uint8_t data[146]; + unsigned i; + + hexdump(stdout, NULL, 0); + hexdump(stdout, "", 0); + hexdump(stdout, "", 1); + hexdump(stdout, "x", 1); + hexdump(stdout, "x", 2); + hexdump(stdout, "foobar", 7); + hexdump(stdout, "f\nobar", 7); + hexdump(stdout, "xxxxxxxxxxxxxxxxxxxxyz", 23); + + for (i = 0; i < ELEMENTSOF(data); i++) + data[i] = i*2; + + hexdump(stdout, data, sizeof(data)); +} + +TEST(base64withwithouturl) { + static const uint8_t plaintext[] = { + 0xcc, 0xa1, 0x72, 0x22, 0xae, 0xda, 0x66, 0x7e, 0x04, 0xa6, 0xe0, 0x82, + 0x9a, 0x97, 0x05, 0xf6, 0x33, 0xe0, 0x0f, 0xc2, 0x45, 0x13, 0x58, 0x3f, + 0xc5, 0xf4, 0xf4, 0x31, 0xab, 0x3c, 0x5f, 0x83, 0x34, 0x5b, 0x27, 0x32, + 0x8a, 0x04, 0x6c, 0x43, 0x82, 0x07, 0xe3, 0x2c, 0xac, 0xb9, 0xfb, 0xac, + 0xd0, 0x03, 0x91, 0x42, 0xcb, 0xa4, 0xde, 0x87, 0x86, 0x85, 0x10, 0xbb, + 0xb7, 0x5b, 0x4b, 0xc8, 0xa0, 0xf4, 0x22, 0x1d, 0x15, 0x71, 0x87, 0x9d, + 0xbf, 0x9f, 0xa9, 0xf1, 0xee, 0xa2, 0xb6, 0xaa, 0xc8, 0xc3, 0x37, 0x9c, + 0xbb, 0xdf, 0x3e, 0xac, 0xdc, 0x94, 0x54, 0x38, 0x56, 0x07, 0x34, 0xb4, + 0x3c, 0xcc, 0x31, 0x13 + }; + + _cleanup_free_ void *buffer = NULL; + size_t size; + + /* This is regular base64 */ + assert_se(unbase64mem("zKFyIq7aZn4EpuCCmpcF9jPgD8JFE1g/xfT0Mas8X4M0WycyigRsQ4IH4yysufus0AORQsuk3oeGhRC7t1tLyKD0Ih0VcYedv5+p8e6itqrIwzecu98+rNyUVDhWBzS0PMwxEw==", SIZE_MAX, &buffer, &size) >= 0); + assert_se(memcmp_nn(plaintext, sizeof(plaintext), buffer, size) == 0); + buffer = mfree(buffer); + + /* This is the same but in base64url */ + assert_se(unbase64mem("zKFyIq7aZn4EpuCCmpcF9jPgD8JFE1g_xfT0Mas8X4M0WycyigRsQ4IH4yysufus0AORQsuk3oeGhRC7t1tLyKD0Ih0VcYedv5-p8e6itqrIwzecu98-rNyUVDhWBzS0PMwxEw==", SIZE_MAX, &buffer, &size) >= 0); + assert_se(memcmp_nn(plaintext, sizeof(plaintext), buffer, size) == 0); + + /* Hint: use xxd -i to generate the static C array from some data, and basenc --base64 + basenc + * --base64url to generate the correctly encoded base64 strings */ +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-hmac.c b/src/test/test-hmac.c new file mode 100644 index 0000000..1b788b1 --- /dev/null +++ b/src/test/test-hmac.c @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "hexdecoct.h" +#include "hmac.h" +#include "string-util.h" +#include "tests.h" + +static void hmac_sha256_by_string(const char *key, const char *value, uint8_t res[static SHA256_DIGEST_SIZE]) { + hmac_sha256(key, strlen(key), value, strlen(value), res); +} + +TEST(hmac) { + uint8_t result[SHA256_DIGEST_SIZE]; + char *hex_result = NULL; + + /* Results compared with output of 'echo -n "<input>" | openssl dgst -sha256 -hmac "<key>"' */ + + hmac_sha256_by_string("waldo", + "", + result); + hex_result = hexmem(result, sizeof(result)); + assert_se(streq_ptr(hex_result, "cadd5e42114351181f3abff477641d88efb57d2b5641a1e5c6d623363a6d3bad")); + hex_result = mfree(hex_result); + + hmac_sha256_by_string("waldo", + "baldohaldo", + result); + hex_result = hexmem(result, sizeof(result)); + assert_se(streq_ptr(hex_result, "c47ad5031ba21605e52c6ca68090d66a2dd5ccf84efa4bace15361a8cba63cda")); + hex_result = mfree(hex_result); + + hmac_sha256_by_string("waldo", + "baldo haldo", + result); + hex_result = hexmem(result, sizeof(result)); + assert_se(streq_ptr(hex_result, "4e8974ad6c08b98cc2519cd1e27aa7195769fcf86db1dd7ceaab4d44c490ad69")); + hex_result = mfree(hex_result); + + hmac_sha256_by_string("waldo", + "baldo 4e8974ad6c08b98cc2519cd1e27aa7195769fcf86db1dd7ceaab4d44c490ad69 haldo", + result); + hex_result = hexmem(result, sizeof(result)); + assert_se(streq_ptr(hex_result, "039f3df430b19753ffb493e5b90708f75c5210b63c6bcbef3374eb3f0a3f97f7")); + hex_result = mfree(hex_result); + + hmac_sha256_by_string("4e8974ad6c08b98cc2519cd1e27aa7195769fcf86db1dd7ceaab4d44c490ad69", + "baldo haldo", + result); + hex_result = hexmem(result, sizeof(result)); + assert_se(streq_ptr(hex_result, "c4cfaf48077cbb0bbd177a09e59ec4c248f4ca771503410f5b54b98d88d2f47b")); + hex_result = mfree(hex_result); + + hmac_sha256_by_string("4e8974ad6c08b98cc2519cd1e27aa7195769fcf86db1dd7ceaab4d44c490ad69", + "supercalifragilisticexpialidocious", + result); + hex_result = hexmem(result, sizeof(result)); + assert_se(streq_ptr(hex_result, "2c059e7a63c4c3b23f47966a65fd2f8a2f5d7161e2e90d78ff68866b5c375cb7")); + hex_result = mfree(hex_result); + + hmac_sha256_by_string("4e8974ad6c08b98cc2519cd1e27aa7195769fcf86db1dd7ceaab4d44c490ad69c47ad5031ba21605e52c6ca68090d66a2dd5ccf84efa4bace15361a8cba63cda", + "supercalifragilisticexpialidocious", + result); + hex_result = hexmem(result, sizeof(result)); + assert_se(streq_ptr(hex_result, "1dd1d1d45b9d9f9673dc9983c968c46ff3168e03cfeb4156a219eba1af4cff5f")); + hex_result = mfree(hex_result); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-hostname-setup.c b/src/test/test-hostname-setup.c new file mode 100644 index 0000000..94e5ece --- /dev/null +++ b/src/test/test-hostname-setup.c @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <unistd.h> + +#include "alloc-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "hostname-setup.h" +#include "string-util.h" +#include "tests.h" +#include "tmpfile-util.h" + +TEST(read_etc_hostname) { + _cleanup_(unlink_tempfilep) char path[] = "/tmp/hostname.XXXXXX"; + char *hostname; + int fd; + + fd = mkostemp_safe(path); + assert_se(fd > 0); + close(fd); + + /* simple hostname */ + assert_se(write_string_file(path, "foo", WRITE_STRING_FILE_CREATE) == 0); + assert_se(read_etc_hostname(path, &hostname) == 0); + assert_se(streq(hostname, "foo")); + hostname = mfree(hostname); + + /* with comment */ + assert_se(write_string_file(path, "# comment\nfoo", WRITE_STRING_FILE_CREATE) == 0); + assert_se(read_etc_hostname(path, &hostname) == 0); + assert_se(hostname); + assert_se(streq(hostname, "foo")); + hostname = mfree(hostname); + + /* with comment and extra whitespace */ + assert_se(write_string_file(path, "# comment\n\n foo ", WRITE_STRING_FILE_CREATE) == 0); + assert_se(read_etc_hostname(path, &hostname) == 0); + assert_se(hostname); + assert_se(streq(hostname, "foo")); + hostname = mfree(hostname); + + /* cleans up name */ + assert_se(write_string_file(path, "!foo/bar.com", WRITE_STRING_FILE_CREATE) == 0); + assert_se(read_etc_hostname(path, &hostname) == 0); + assert_se(hostname); + assert_se(streq(hostname, "foobar.com")); + hostname = mfree(hostname); + + /* no value set */ + hostname = (char*) 0x1234; + assert_se(write_string_file(path, "# nothing here\n", WRITE_STRING_FILE_CREATE) == 0); + assert_se(read_etc_hostname(path, &hostname) == -ENOENT); + assert_se(hostname == (char*) 0x1234); /* does not touch argument on error */ + + /* nonexisting file */ + assert_se(read_etc_hostname("/non/existing", &hostname) == -ENOENT); + assert_se(hostname == (char*) 0x1234); /* does not touch argument on error */ +} + +TEST(hostname_setup) { + hostname_setup(false); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-hostname-util.c b/src/test/test-hostname-util.c new file mode 100644 index 0000000..77e9a19 --- /dev/null +++ b/src/test/test-hostname-util.c @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <unistd.h> + +#include "alloc-util.h" +#include "fileio.h" +#include "hostname-util.h" +#include "string-util.h" +#include "tests.h" +#include "tmpfile-util.h" + +TEST(hostname_is_valid) { + assert_se(hostname_is_valid("foobar", 0)); + assert_se(hostname_is_valid("foobar.com", 0)); + assert_se(!hostname_is_valid("foobar.com.", 0)); + assert_se(hostname_is_valid("fooBAR", 0)); + assert_se(hostname_is_valid("fooBAR.com", 0)); + assert_se(!hostname_is_valid("fooBAR.", 0)); + assert_se(!hostname_is_valid("fooBAR.com.", 0)); + assert_se(!hostname_is_valid("fööbar", 0)); + assert_se(!hostname_is_valid("", 0)); + assert_se(!hostname_is_valid(".", 0)); + assert_se(!hostname_is_valid("..", 0)); + assert_se(!hostname_is_valid("foobar.", 0)); + assert_se(!hostname_is_valid(".foobar", 0)); + assert_se(!hostname_is_valid("foo..bar", 0)); + assert_se(!hostname_is_valid("foo.bar..", 0)); + assert_se(!hostname_is_valid("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 0)); + assert_se(!hostname_is_valid("au-xph5-rvgrdsb5hcxc-47et3a5vvkrc-server-wyoz4elpdpe3.openstack.local", 0)); + + assert_se(hostname_is_valid("foobar", VALID_HOSTNAME_TRAILING_DOT)); + assert_se(hostname_is_valid("foobar.com", VALID_HOSTNAME_TRAILING_DOT)); + assert_se(hostname_is_valid("foobar.com.", VALID_HOSTNAME_TRAILING_DOT)); + assert_se(hostname_is_valid("fooBAR", VALID_HOSTNAME_TRAILING_DOT)); + assert_se(hostname_is_valid("fooBAR.com", VALID_HOSTNAME_TRAILING_DOT)); + assert_se(!hostname_is_valid("fooBAR.", VALID_HOSTNAME_TRAILING_DOT)); + assert_se(hostname_is_valid("fooBAR.com.", VALID_HOSTNAME_TRAILING_DOT)); + assert_se(!hostname_is_valid("fööbar", VALID_HOSTNAME_TRAILING_DOT)); + assert_se(!hostname_is_valid("", VALID_HOSTNAME_TRAILING_DOT)); + assert_se(!hostname_is_valid(".", VALID_HOSTNAME_TRAILING_DOT)); + assert_se(!hostname_is_valid("..", VALID_HOSTNAME_TRAILING_DOT)); + assert_se(!hostname_is_valid("foobar.", VALID_HOSTNAME_TRAILING_DOT)); + assert_se(!hostname_is_valid(".foobar", VALID_HOSTNAME_TRAILING_DOT)); + assert_se(!hostname_is_valid("foo..bar", VALID_HOSTNAME_TRAILING_DOT)); + assert_se(!hostname_is_valid("foo.bar..", VALID_HOSTNAME_TRAILING_DOT)); + assert_se(!hostname_is_valid("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", VALID_HOSTNAME_TRAILING_DOT)); +} + +TEST(hostname_cleanup) { + char *s; + + s = strdupa_safe("foobar"); + assert_se(streq(hostname_cleanup(s), "foobar")); + s = strdupa_safe("foobar.com"); + assert_se(streq(hostname_cleanup(s), "foobar.com")); + s = strdupa_safe("foobar.com."); + assert_se(streq(hostname_cleanup(s), "foobar.com")); + s = strdupa_safe("foo-bar.-com-."); + assert_se(streq(hostname_cleanup(s), "foo-bar.com")); + s = strdupa_safe("foo-bar-.-com-."); + assert_se(streq(hostname_cleanup(s), "foo-bar--com")); + s = strdupa_safe("--foo-bar.-com"); + assert_se(streq(hostname_cleanup(s), "foo-bar.com")); + s = strdupa_safe("fooBAR"); + assert_se(streq(hostname_cleanup(s), "fooBAR")); + s = strdupa_safe("fooBAR.com"); + assert_se(streq(hostname_cleanup(s), "fooBAR.com")); + s = strdupa_safe("fooBAR."); + assert_se(streq(hostname_cleanup(s), "fooBAR")); + s = strdupa_safe("fooBAR.com."); + assert_se(streq(hostname_cleanup(s), "fooBAR.com")); + s = strdupa_safe("fööbar"); + assert_se(streq(hostname_cleanup(s), "fbar")); + s = strdupa_safe(""); + assert_se(isempty(hostname_cleanup(s))); + s = strdupa_safe("."); + assert_se(isempty(hostname_cleanup(s))); + s = strdupa_safe(".."); + assert_se(isempty(hostname_cleanup(s))); + s = strdupa_safe("foobar."); + assert_se(streq(hostname_cleanup(s), "foobar")); + s = strdupa_safe(".foobar"); + assert_se(streq(hostname_cleanup(s), "foobar")); + s = strdupa_safe("foo..bar"); + assert_se(streq(hostname_cleanup(s), "foo.bar")); + s = strdupa_safe("foo.bar.."); + assert_se(streq(hostname_cleanup(s), "foo.bar")); + s = strdupa_safe("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + assert_se(streq(hostname_cleanup(s), "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")); + s = strdupa_safe("xxxx........xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + assert_se(streq(hostname_cleanup(s), "xxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")); +} + +TEST(hostname_malloc) { + _cleanup_free_ char *h = NULL, *l = NULL; + + assert_se(h = gethostname_malloc()); + log_info("hostname_malloc: \"%s\"", h); + + assert_se(l = gethostname_short_malloc()); + log_info("hostname_short_malloc: \"%s\"", l); +} + +TEST(default_hostname) { + if (!hostname_is_valid(FALLBACK_HOSTNAME, 0)) { + log_error("Configured fallback hostname \"%s\" is not valid.", FALLBACK_HOSTNAME); + exit(EXIT_FAILURE); + } + + _cleanup_free_ char *n = get_default_hostname(); + assert_se(n); + log_info("get_default_hostname: \"%s\"", n); + assert_se(hostname_is_valid(n, 0)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-id128.c b/src/test/test-id128.c new file mode 100644 index 0000000..ae7df27 --- /dev/null +++ b/src/test/test-id128.c @@ -0,0 +1,339 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <fcntl.h> +#include <sys/types.h> +#include <unistd.h> + +#include "sd-daemon.h" +#include "sd-id128.h" + +#include "alloc-util.h" +#include "fd-util.h" +#include "id128-util.h" +#include "macro.h" +#include "path-util.h" +#include "rm-rf.h" +#include "string-util.h" +#include "tests.h" +#include "tmpfile-util.h" + +#define ID128_WALDI SD_ID128_MAKE(01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f, 10) +#define STR_WALDI "0102030405060708090a0b0c0d0e0f10" +#define UUID_WALDI "01020304-0506-0708-090a-0b0c0d0e0f10" +#define STR_NULL "00000000000000000000000000000000" + +TEST(id128) { + sd_id128_t id, id2; + char t[SD_ID128_STRING_MAX], q[SD_ID128_UUID_STRING_MAX]; + _cleanup_free_ char *b = NULL; + _cleanup_close_ int fd = -EBADF; + + assert_se(sd_id128_randomize(&id) == 0); + printf("random: %s\n", sd_id128_to_string(id, t)); + + assert_se(sd_id128_from_string(t, &id2) == 0); + assert_se(sd_id128_equal(id, id2)); + assert_se(sd_id128_in_set(id, id)); + assert_se(sd_id128_in_set(id, id2)); + assert_se(sd_id128_in_set(id, id2, id)); + assert_se(sd_id128_in_set(id, ID128_WALDI, id)); + assert_se(!sd_id128_in_set(id)); + assert_se(!sd_id128_in_set(id, ID128_WALDI)); + assert_se(!sd_id128_in_set(id, ID128_WALDI, ID128_WALDI)); + + if (sd_booted() > 0 && sd_id128_get_machine(NULL) >= 0) { + assert_se(sd_id128_get_machine(&id) == 0); + printf("machine: %s\n", sd_id128_to_string(id, t)); + + assert_se(sd_id128_get_boot(&id) == 0); + printf("boot: %s\n", sd_id128_to_string(id, t)); + } + + printf("waldi: %s\n", sd_id128_to_string(ID128_WALDI, t)); + assert_se(streq(t, STR_WALDI)); + + assert_se(asprintf(&b, SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(ID128_WALDI)) == 32); + printf("waldi2: %s\n", b); + assert_se(streq(t, b)); + + printf("waldi3: %s\n", sd_id128_to_uuid_string(ID128_WALDI, q)); + assert_se(streq(q, UUID_WALDI)); + + b = mfree(b); + assert_se(asprintf(&b, SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(ID128_WALDI)) == 36); + printf("waldi4: %s\n", b); + assert_se(streq(q, b)); + + assert_se(sd_id128_from_string(STR_WALDI, &id) >= 0); + assert_se(sd_id128_equal(id, ID128_WALDI)); + + assert_se(sd_id128_from_string(UUID_WALDI, &id) >= 0); + assert_se(sd_id128_equal(id, ID128_WALDI)); + + assert_se(sd_id128_from_string("", &id) < 0); + assert_se(sd_id128_from_string("01020304-0506-0708-090a-0b0c0d0e0f101", &id) < 0); + assert_se(sd_id128_from_string("01020304-0506-0708-090a-0b0c0d0e0f10-", &id) < 0); + assert_se(sd_id128_from_string("01020304-0506-0708-090a0b0c0d0e0f10", &id) < 0); + assert_se(sd_id128_from_string("010203040506-0708-090a-0b0c0d0e0f10", &id) < 0); + + assert_se(id128_from_string_nonzero(STR_WALDI, &id) == 0); + assert_se(id128_from_string_nonzero(STR_NULL, &id) == -ENXIO); + assert_se(id128_from_string_nonzero("01020304-0506-0708-090a-0b0c0d0e0f101", &id) < 0); + assert_se(id128_from_string_nonzero("01020304-0506-0708-090a-0b0c0d0e0f10-", &id) < 0); + assert_se(id128_from_string_nonzero("01020304-0506-0708-090a0b0c0d0e0f10", &id) < 0); + assert_se(id128_from_string_nonzero("010203040506-0708-090a-0b0c0d0e0f10", &id) < 0); + + assert_se(id128_is_valid(STR_WALDI)); + assert_se(id128_is_valid(UUID_WALDI)); + assert_se(!id128_is_valid("")); + assert_se(!id128_is_valid("01020304-0506-0708-090a-0b0c0d0e0f101")); + assert_se(!id128_is_valid("01020304-0506-0708-090a-0b0c0d0e0f10-")); + assert_se(!id128_is_valid("01020304-0506-0708-090a0b0c0d0e0f10")); + assert_se(!id128_is_valid("010203040506-0708-090a-0b0c0d0e0f10")); + + fd = open_tmpfile_unlinkable(NULL, O_RDWR|O_CLOEXEC); + assert_se(fd >= 0); + + /* First, write as UUID */ + assert_se(sd_id128_randomize(&id) >= 0); + assert_se(id128_write_fd(fd, ID128_FORMAT_UUID, id) >= 0); + + assert_se(lseek(fd, 0, SEEK_SET) == 0); + assert_se(id128_read_fd(fd, ID128_FORMAT_PLAIN, &id2) == -EUCLEAN); + + assert_se(lseek(fd, 0, SEEK_SET) == 0); + assert_se(id128_read_fd(fd, ID128_FORMAT_UUID, &id2) >= 0); + assert_se(sd_id128_equal(id, id2)); + + assert_se(lseek(fd, 0, SEEK_SET) == 0); + assert_se(id128_read_fd(fd, ID128_FORMAT_ANY, &id2) >= 0); + assert_se(sd_id128_equal(id, id2)); + + /* Second, write as plain */ + assert_se(lseek(fd, 0, SEEK_SET) == 0); + assert_se(ftruncate(fd, 0) >= 0); + + assert_se(sd_id128_randomize(&id) >= 0); + assert_se(id128_write_fd(fd, ID128_FORMAT_PLAIN, id) >= 0); + + assert_se(lseek(fd, 0, SEEK_SET) == 0); + assert_se(id128_read_fd(fd, ID128_FORMAT_UUID, &id2) == -EUCLEAN); + + assert_se(lseek(fd, 0, SEEK_SET) == 0); + assert_se(id128_read_fd(fd, ID128_FORMAT_PLAIN, &id2) >= 0); + assert_se(sd_id128_equal(id, id2)); + + assert_se(lseek(fd, 0, SEEK_SET) == 0); + assert_se(id128_read_fd(fd, ID128_FORMAT_ANY, &id2) >= 0); + assert_se(sd_id128_equal(id, id2)); + + /* Third, write plain without trailing newline */ + assert_se(lseek(fd, 0, SEEK_SET) == 0); + assert_se(ftruncate(fd, 0) >= 0); + + assert_se(sd_id128_randomize(&id) >= 0); + assert_se(write(fd, sd_id128_to_string(id, t), 32) == 32); + + assert_se(lseek(fd, 0, SEEK_SET) == 0); + assert_se(id128_read_fd(fd, ID128_FORMAT_UUID, &id2) == -EUCLEAN); + + assert_se(lseek(fd, 0, SEEK_SET) == 0); + assert_se(id128_read_fd(fd, ID128_FORMAT_PLAIN, &id2) >= 0); + assert_se(sd_id128_equal(id, id2)); + + /* Fourth, write UUID without trailing newline */ + assert_se(lseek(fd, 0, SEEK_SET) == 0); + assert_se(ftruncate(fd, 0) >= 0); + + assert_se(sd_id128_randomize(&id) >= 0); + assert_se(write(fd, sd_id128_to_uuid_string(id, q), 36) == 36); + + assert_se(lseek(fd, 0, SEEK_SET) == 0); + assert_se(id128_read_fd(fd, ID128_FORMAT_PLAIN, &id2) == -EUCLEAN); + + assert_se(lseek(fd, 0, SEEK_SET) == 0); + assert_se(id128_read_fd(fd, ID128_FORMAT_UUID, &id2) >= 0); + assert_se(sd_id128_equal(id, id2)); + + /* Fifth, tests for "uninitialized" */ + assert_se(lseek(fd, 0, SEEK_SET) == 0); + assert_se(ftruncate(fd, 0) >= 0); + assert_se(write(fd, "uninitialized", STRLEN("uninitialized")) == STRLEN("uninitialized")); + assert_se(lseek(fd, 0, SEEK_SET) == 0); + assert_se(id128_read_fd(fd, ID128_FORMAT_ANY, NULL) == -ENOPKG); + + assert_se(lseek(fd, 0, SEEK_SET) == 0); + assert_se(ftruncate(fd, 0) >= 0); + assert_se(write(fd, "uninitialized\n", STRLEN("uninitialized\n")) == STRLEN("uninitialized\n")); + assert_se(lseek(fd, 0, SEEK_SET) == 0); + assert_se(id128_read_fd(fd, ID128_FORMAT_ANY, NULL) == -ENOPKG); + + assert_se(lseek(fd, 0, SEEK_SET) == 0); + assert_se(ftruncate(fd, 0) >= 0); + assert_se(write(fd, "uninitialized\nfoo", STRLEN("uninitialized\nfoo")) == STRLEN("uninitialized\nfoo")); + assert_se(lseek(fd, 0, SEEK_SET) == 0); + assert_se(id128_read_fd(fd, ID128_FORMAT_ANY, NULL) == -EUCLEAN); + + assert_se(lseek(fd, 0, SEEK_SET) == 0); + assert_se(ftruncate(fd, 0) >= 0); + assert_se(write(fd, "uninit", STRLEN("uninit")) == STRLEN("uninit")); + assert_se(lseek(fd, 0, SEEK_SET) == 0); + assert_se(id128_read_fd(fd, ID128_FORMAT_ANY, NULL) == -EUCLEAN); + + /* build/systemd-id128 -a f03daaeb1c334b43a732172944bf772e show 51df0b4bc3b04c9780e299b98ca373b8 */ + assert_se(sd_id128_get_app_specific(SD_ID128_MAKE(51,df,0b,4b,c3,b0,4c,97,80,e2,99,b9,8c,a3,73,b8), + SD_ID128_MAKE(f0,3d,aa,eb,1c,33,4b,43,a7,32,17,29,44,bf,77,2e), &id) >= 0); + assert_se(sd_id128_equal(id, SD_ID128_MAKE(1d,ee,59,54,e7,5c,4d,6f,b9,6c,c6,c0,4c,a1,8a,86))); + + if (sd_booted() > 0 && sd_id128_get_machine(NULL) >= 0) { + assert_se(sd_id128_get_machine_app_specific(SD_ID128_MAKE(f0,3d,aa,eb,1c,33,4b,43,a7,32,17,29,44,bf,77,2e), &id) >= 0); + assert_se(sd_id128_get_machine_app_specific(SD_ID128_MAKE(f0,3d,aa,eb,1c,33,4b,43,a7,32,17,29,44,bf,77,2e), &id2) >= 0); + assert_se(sd_id128_equal(id, id2)); + assert_se(sd_id128_get_machine_app_specific(SD_ID128_MAKE(51,df,0b,4b,c3,b0,4c,97,80,e2,99,b9,8c,a3,73,b8), &id2) >= 0); + assert_se(!sd_id128_equal(id, id2)); + } + + /* Check return values */ + assert_se(sd_id128_get_app_specific(SD_ID128_ALLF, SD_ID128_NULL, &id) == -ENXIO); + assert_se(sd_id128_get_app_specific(SD_ID128_NULL, SD_ID128_ALLF, &id) == 0); +} + +TEST(sd_id128_get_invocation) { + sd_id128_t id; + int r; + + /* Query the invocation ID */ + r = sd_id128_get_invocation(&id); + if (r < 0) + log_warning_errno(r, "Failed to get invocation ID, ignoring: %m"); + else + log_info("Invocation ID: " SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id)); +} + +TEST(benchmark_sd_id128_get_machine_app_specific) { + unsigned iterations = slow_tests_enabled() ? 1000000 : 1000; + usec_t t, q; + + if (sd_id128_get_machine(NULL) < 0) + return (void) log_tests_skipped("/etc/machine-id is not initialized"); + + log_info("/* %s (%u iterations) */", __func__, iterations); + + sd_id128_t id = ID128_WALDI, id2; + + t = now(CLOCK_MONOTONIC); + + for (unsigned i = 0; i < iterations; i++) { + id.qwords[1] = i; + + assert_se(sd_id128_get_machine_app_specific(id, &id2) >= 0); + } + + q = now(CLOCK_MONOTONIC) - t; + + log_info("%lf μs each", (double) q / iterations); +} + +TEST(id128_at) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int tfd = -EBADF; + _cleanup_free_ char *p = NULL; + sd_id128_t id, i; + + tfd = mkdtemp_open(NULL, O_PATH, &t); + assert_se(tfd >= 0); + assert_se(mkdirat(tfd, "etc", 0755) >= 0); + assert_se(symlinkat("etc", tfd, "etc2") >= 0); + assert_se(symlinkat("machine-id", tfd, "etc/hoge-id") >= 0); + + assert_se(sd_id128_randomize(&id) == 0); + + assert_se(id128_write_at(tfd, "etc/machine-id", ID128_FORMAT_PLAIN, id) >= 0); + if (geteuid() == 0) + assert_se(id128_write_at(tfd, "etc/machine-id", ID128_FORMAT_PLAIN, id) >= 0); + else + assert_se(id128_write_at(tfd, "etc/machine-id", ID128_FORMAT_PLAIN, id) == -EACCES); + assert_se(unlinkat(tfd, "etc/machine-id", 0) >= 0); + assert_se(id128_write_at(tfd, "etc2/machine-id", ID128_FORMAT_PLAIN, id) >= 0); + assert_se(unlinkat(tfd, "etc/machine-id", 0) >= 0); + assert_se(id128_write_at(tfd, "etc/hoge-id", ID128_FORMAT_PLAIN, id) >= 0); + assert_se(unlinkat(tfd, "etc/machine-id", 0) >= 0); + assert_se(id128_write_at(tfd, "etc2/hoge-id", ID128_FORMAT_PLAIN, id) >= 0); + + /* id128_read_at() */ + i = SD_ID128_NULL; /* Not necessary in real code, but for testing that the id is really assigned. */ + assert_se(id128_read_at(tfd, "etc/machine-id", ID128_FORMAT_PLAIN, &i) >= 0); + assert_se(sd_id128_equal(id, i)); + + i = SD_ID128_NULL; + assert_se(id128_read_at(tfd, "etc2/machine-id", ID128_FORMAT_PLAIN, &i) >= 0); + assert_se(sd_id128_equal(id, i)); + + i = SD_ID128_NULL; + assert_se(id128_read_at(tfd, "etc/hoge-id", ID128_FORMAT_PLAIN, &i) >= 0); + assert_se(sd_id128_equal(id, i)); + + i = SD_ID128_NULL; + assert_se(id128_read_at(tfd, "etc2/hoge-id", ID128_FORMAT_PLAIN, &i) >= 0); + assert_se(sd_id128_equal(id, i)); + + /* id128_read() */ + assert_se(p = path_join(t, "/etc/machine-id")); + + i = SD_ID128_NULL; + assert_se(id128_read(p, ID128_FORMAT_PLAIN, &i) >= 0); + assert_se(sd_id128_equal(id, i)); + + free(p); + assert_se(p = path_join(t, "/etc2/machine-id")); + + i = SD_ID128_NULL; + assert_se(id128_read(p, ID128_FORMAT_PLAIN, &i) >= 0); + assert_se(sd_id128_equal(id, i)); + + free(p); + assert_se(p = path_join(t, "/etc/hoge-id")); + + i = SD_ID128_NULL; + assert_se(id128_read(p, ID128_FORMAT_PLAIN, &i) >= 0); + assert_se(sd_id128_equal(id, i)); + + free(p); + assert_se(p = path_join(t, "/etc2/hoge-id")); + + i = SD_ID128_NULL; + assert_se(id128_read(p, ID128_FORMAT_PLAIN, &i) >= 0); + assert_se(sd_id128_equal(id, i)); + + /* id128_get_machine_at() */ + i = SD_ID128_NULL; + assert_se(id128_get_machine_at(tfd, &i) >= 0); + assert_se(sd_id128_equal(id, i)); + + /* id128_get_machine() */ + i = SD_ID128_NULL; + assert_se(id128_get_machine(t, &i) >= 0); + assert_se(sd_id128_equal(id, i)); +} + +TEST(ID128_REFUSE_NULL) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int tfd = -EBADF; + sd_id128_t id; + + tfd = mkdtemp_open(NULL, O_PATH, &t); + assert_se(tfd >= 0); + + assert_se(id128_write_at(tfd, "zero-id", ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, (sd_id128_t) {}) == -ENOMEDIUM); + assert_se(unlinkat(tfd, "zero-id", 0) >= 0); + assert_se(id128_write_at(tfd, "zero-id", ID128_FORMAT_PLAIN, (sd_id128_t) {}) >= 0); + + assert_se(sd_id128_randomize(&id) == 0); + assert_se(!sd_id128_equal(id, SD_ID128_NULL)); + assert_se(id128_read_at(tfd, "zero-id", ID128_FORMAT_PLAIN, &id) >= 0); + assert_se(sd_id128_equal(id, SD_ID128_NULL)); + + assert_se(id128_read_at(tfd, "zero-id", ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, &id) == -ENOMEDIUM); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-image-policy.c b/src/test/test-image-policy.c new file mode 100644 index 0000000..d9fe556 --- /dev/null +++ b/src/test/test-image-policy.c @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "image-policy.h" +#include "pretty-print.h" +#include "string-util.h" +#include "tests.h" +#include "pager.h" + +static void test_policy(const ImagePolicy *p, const char *name) { + _cleanup_free_ char *as_string = NULL, *as_string_simplified = NULL; + _cleanup_free_ ImagePolicy *parsed = NULL; + + assert_se(image_policy_to_string(p, /* simplified= */ false, &as_string) >= 0); + assert_se(image_policy_to_string(p, /* simplified= */ true, &as_string_simplified) >= 0); + + printf("%s%s", ansi_underline(), name); + + if (!streq(as_string_simplified, name)) { + printf(" → %s", as_string_simplified); + + if (!streq(as_string, as_string_simplified)) + printf(" (aka %s)", as_string); + } + + printf("%s\n", ansi_normal()); + + assert_se(image_policy_from_string(as_string, &parsed) >= 0); + assert_se(image_policy_equal(p, parsed)); + parsed = image_policy_free(parsed); + + assert_se(image_policy_from_string(as_string_simplified, &parsed) >= 0); + assert_se(image_policy_equivalent(p, parsed)); + parsed = image_policy_free(parsed); + + for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) { + _cleanup_free_ char *k = NULL; + PartitionPolicyFlags f; + + f = image_policy_get(p, d); + if (f < 0) { + f = image_policy_get_exhaustively(p, d); + assert_se(f >= 0); + assert_se(partition_policy_flags_to_string(f, /* simplified= */ true, &k) >= 0); + + printf("%s\t%s → n/a (exhaustively: %s)%s\n", ansi_grey(), partition_designator_to_string(d), k, ansi_normal()); + } else { + assert_se(partition_policy_flags_to_string(f, /* simplified= */ true, &k) >= 0); + printf("\t%s → %s\n", partition_designator_to_string(d), k); + } + } + + _cleanup_free_ char *w = NULL; + assert_se(partition_policy_flags_to_string(image_policy_default(p), /* simplified= */ true, &w) >= 0); + printf("\tdefault → %s\n", w); +} + +static void test_policy_string(const char *t) { + _cleanup_free_ ImagePolicy *parsed = NULL; + + assert_se(image_policy_from_string(t, &parsed) >= 0); + test_policy(parsed, t); +} + +static void test_policy_equiv(const char *s, bool (*func)(const ImagePolicy *p)) { + _cleanup_(image_policy_freep) ImagePolicy *p = NULL; + + assert_se(image_policy_from_string(s, &p) >= 0); + + assert_se(func(p)); + assert_se(func == image_policy_equiv_ignore || !image_policy_equiv_ignore(p)); + assert_se(func == image_policy_equiv_allow || !image_policy_equiv_allow(p)); + assert_se(func == image_policy_equiv_deny || !image_policy_equiv_deny(p)); +} + +TEST_RET(test_image_policy_to_string) { + test_policy(&image_policy_allow, "*"); + test_policy(&image_policy_ignore, "-"); + test_policy(&image_policy_deny, "~"); + test_policy(&image_policy_sysext, "sysext"); + test_policy(&image_policy_sysext_strict, "sysext-strict"); + test_policy(&image_policy_confext, "confext"); + test_policy(&image_policy_container, "container"); + test_policy(&image_policy_host, "host"); + test_policy(&image_policy_service, "service"); + test_policy(NULL, "null"); + + test_policy_string(""); + test_policy_string("-"); + test_policy_string("*"); + test_policy_string("~"); + test_policy_string("swap=open"); + test_policy_string("swap=open:root=signed"); + test_policy_string("swap=open:root=signed+read-only-on+growfs-off:=absent"); + test_policy_string("=-"); + test_policy_string("="); + + test_policy_equiv("", image_policy_equiv_ignore); + test_policy_equiv("-", image_policy_equiv_ignore); + test_policy_equiv("*", image_policy_equiv_allow); + test_policy_equiv("~", image_policy_equiv_deny); + test_policy_equiv("=absent", image_policy_equiv_deny); + test_policy_equiv("=open", image_policy_equiv_allow); + test_policy_equiv("=verity+signed+encrypted+unprotected+unused+absent", image_policy_equiv_allow); + test_policy_equiv("=signed+verity+encrypted+unused+unprotected+absent", image_policy_equiv_allow); + test_policy_equiv("=ignore", image_policy_equiv_ignore); + test_policy_equiv("=absent+unused", image_policy_equiv_ignore); + test_policy_equiv("=unused+absent", image_policy_equiv_ignore); + test_policy_equiv("root=ignore:=ignore", image_policy_equiv_ignore); + + assert_se(image_policy_from_string("pfft", NULL) == -EINVAL); + assert_se(image_policy_from_string("öäüß", NULL) == -EINVAL); + assert_se(image_policy_from_string(":", NULL) == -EINVAL); + assert_se(image_policy_from_string("a=", NULL) == -EBADSLT); + assert_se(image_policy_from_string("=a", NULL) == -EBADRQC); + assert_se(image_policy_from_string("==", NULL) == -EBADRQC); + assert_se(image_policy_from_string("root=verity:root=encrypted", NULL) == -ENOTUNIQ); + assert_se(image_policy_from_string("root=grbl", NULL) == -EBADRQC); + assert_se(image_policy_from_string("wowza=grbl", NULL) == -EBADSLT); + + return 0; +} + +TEST(extend) { + assert_se(partition_policy_flags_extend(0) == _PARTITION_POLICY_MASK); + assert_se(partition_policy_flags_extend(_PARTITION_POLICY_MASK) == _PARTITION_POLICY_MASK); + assert_se(partition_policy_flags_extend(PARTITION_POLICY_UNPROTECTED) == (PARTITION_POLICY_UNPROTECTED|_PARTITION_POLICY_PFLAGS_MASK)); + assert_se(partition_policy_flags_extend(PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_READ_ONLY_ON) == (PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_READ_ONLY_ON|_PARTITION_POLICY_GROWFS_MASK)); + assert_se(partition_policy_flags_extend(PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_READ_ONLY_ON|PARTITION_POLICY_GROWFS_OFF) == (PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_READ_ONLY_ON|PARTITION_POLICY_GROWFS_OFF)); + assert_se(partition_policy_flags_extend(PARTITION_POLICY_GROWFS_ON) == (PARTITION_POLICY_GROWFS_ON|_PARTITION_POLICY_USE_MASK|_PARTITION_POLICY_READ_ONLY_MASK)); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-import-util.c b/src/test/test-import-util.c new file mode 100644 index 0000000..7930fe5 --- /dev/null +++ b/src/test/test-import-util.c @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "import-util.h" +#include "log.h" +#include "string-util.h" +#include "tests.h" + +static void test_import_url_last_component_one(const char *input, const char *output, int ret) { + _cleanup_free_ char *s = NULL; + + assert_se(import_url_last_component(input, &s) == ret); + assert_se(streq_ptr(output, s)); +} + +TEST(import_url_last_component) { + test_import_url_last_component_one("https://foobar/waldo/quux", "quux", 0); + test_import_url_last_component_one("https://foobar/waldo/quux/", "quux", 0); + test_import_url_last_component_one("https://foobar/waldo/", "waldo", 0); + test_import_url_last_component_one("https://foobar/", NULL, -EADDRNOTAVAIL); + test_import_url_last_component_one("https://foobar", NULL, -EADDRNOTAVAIL); + test_import_url_last_component_one("https://foobar/waldo/quux?foo=bar", "quux", 0); + test_import_url_last_component_one("https://foobar/waldo/quux/?foo=bar", "quux", 0); + test_import_url_last_component_one("https://foobar/waldo/quux/?foo=bar#piep", "quux", 0); + test_import_url_last_component_one("https://foobar/waldo/quux/#piep", "quux", 0); + test_import_url_last_component_one("https://foobar/waldo/quux#piep", "quux", 0); + test_import_url_last_component_one("https://", NULL, -EINVAL); + test_import_url_last_component_one("", NULL, -EINVAL); + test_import_url_last_component_one(":", NULL, -EINVAL); + test_import_url_last_component_one(":/", NULL, -EINVAL); + test_import_url_last_component_one("x:/", NULL, -EINVAL); + test_import_url_last_component_one("x:y", NULL, -EADDRNOTAVAIL); + test_import_url_last_component_one("x:y/z", "z", 0); +} + +static void test_import_url_change_suffix_one(const char *input, size_t n, const char *suffix, const char *output, int ret) { + _cleanup_free_ char *s = NULL; + + assert_se(import_url_change_suffix(input, n, suffix, &s) == ret); + assert_se(streq_ptr(output, s)); +} + +TEST(import_url_change_suffix) { + test_import_url_change_suffix_one("https://foobar/waldo/quux", 1, "wuff", "https://foobar/waldo/wuff", 0); + test_import_url_change_suffix_one("https://foobar/waldo/quux/", 1, "wuff", "https://foobar/waldo/wuff", 0); + test_import_url_change_suffix_one("https://foobar/waldo/quux///?mief", 1, "wuff", "https://foobar/waldo/wuff", 0); + test_import_url_change_suffix_one("https://foobar/waldo/quux///?mief#opopo", 1, "wuff", "https://foobar/waldo/wuff", 0); + test_import_url_change_suffix_one("https://foobar/waldo/quux/quff", 2, "wuff", "https://foobar/waldo/wuff", 0); + test_import_url_change_suffix_one("https://foobar/waldo/quux/quff/", 2, "wuff", "https://foobar/waldo/wuff", 0); + test_import_url_change_suffix_one("https://foobar/waldo/quux/quff", 0, "wuff", "https://foobar/waldo/quux/quff/wuff", 0); + test_import_url_change_suffix_one("https://foobar/waldo/quux/quff?aa?bb##4", 0, "wuff", "https://foobar/waldo/quux/quff/wuff", 0); + test_import_url_change_suffix_one("https://", 0, "wuff", NULL, -EINVAL); + test_import_url_change_suffix_one("", 0, "wuff", NULL, -EINVAL); + test_import_url_change_suffix_one(":", 0, "wuff", NULL, -EINVAL); + test_import_url_change_suffix_one(":/", 0, "wuff", NULL, -EINVAL); + test_import_url_change_suffix_one("x:/", 0, "wuff", NULL, -EINVAL); + test_import_url_change_suffix_one("x:y", 0, "wuff", "x:y/wuff", 0); + test_import_url_change_suffix_one("x:y/z", 0, "wuff", "x:y/z/wuff", 0); + test_import_url_change_suffix_one("x:y/z/", 0, "wuff", "x:y/z/wuff", 0); + test_import_url_change_suffix_one("x:y/z/", 1, "wuff", "x:y/wuff", 0); + test_import_url_change_suffix_one("x:y/z/", 2, "wuff", "x:y/wuff", 0); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-in-addr-prefix-util.c b/src/test/test-in-addr-prefix-util.c new file mode 100644 index 0000000..661ca8f --- /dev/null +++ b/src/test/test-in-addr-prefix-util.c @@ -0,0 +1,121 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "in-addr-prefix-util.h" +#include "tests.h" + +static void test_in_addr_prefix_to_string_one(int f, const char *addr, unsigned prefixlen) { + union in_addr_union ua; + assert_se(in_addr_from_string(f, addr, &ua) >= 0); + + const char *r = IN_ADDR_PREFIX_TO_STRING(f, &ua, prefixlen); + assert_se(r); + printf("%s: %s/%u == %s\n", __func__, addr, prefixlen, r); + assert_se(startswith(r, addr)); + + assert_se(streq(r, IN_ADDR_PREFIX_TO_STRING(f, &ua, prefixlen))); + assert_se(streq(IN_ADDR_PREFIX_TO_STRING(f, &ua, prefixlen), r)); +} + +TEST(in_addr_to_string_prefix) { + test_in_addr_prefix_to_string_one(AF_INET, "192.168.0.1", 0); + test_in_addr_prefix_to_string_one(AF_INET, "192.168.0.1", 1); + test_in_addr_prefix_to_string_one(AF_INET, "192.168.0.1", 31); + test_in_addr_prefix_to_string_one(AF_INET, "192.168.0.1", 32); + test_in_addr_prefix_to_string_one(AF_INET, "192.168.0.1", 256); + test_in_addr_prefix_to_string_one(AF_INET, "10.11.12.13", UINT_MAX); + test_in_addr_prefix_to_string_one(AF_INET6, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 0); + test_in_addr_prefix_to_string_one(AF_INET6, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", UINT_MAX); + test_in_addr_prefix_to_string_one(AF_INET6, "::1", 11); + test_in_addr_prefix_to_string_one(AF_INET6, "fe80::", 33); +} + +static void test_config_parse_in_addr_prefixes_one(int family, const union in_addr_union *addr, uint8_t prefixlen, Set **prefixes) { + const char *str = IN_ADDR_PREFIX_TO_STRING(family, addr, prefixlen); + assert_se(str); + + assert_se(config_parse_in_addr_prefixes("unit", "filename", 1, "Service", 1, "IPAddressAllow", 0, str, prefixes, NULL) >= 0); + + assert_se(streq(str, IN_ADDR_PREFIX_TO_STRING(family, addr, prefixlen))); + assert_se(streq(IN_ADDR_PREFIX_TO_STRING(family, addr, prefixlen), str)); +} + +static void test_config_parse_in_addr_prefixes(Set **ret) { + _cleanup_set_free_ Set *prefixes = NULL; + + log_info("/* %s() */", __func__); + + for (uint32_t i = 0; i < 256; i++) { + /* ipv4 link-local address */ + test_config_parse_in_addr_prefixes_one(AF_INET, &(union in_addr_union) { + .in.s_addr = htobe32((UINT32_C(169) << 24) | + (UINT32_C(254) << 16) | + (i << 8)), + }, 24, &prefixes); + + /* ipv6 multicast address */ + test_config_parse_in_addr_prefixes_one(AF_INET6, &(union in_addr_union) { + .in6.s6_addr[0] = 0xff, + .in6.s6_addr[1] = i, + }, 16, &prefixes); + + for (uint32_t j = 0; j < 256; j++) { + test_config_parse_in_addr_prefixes_one(AF_INET, &(union in_addr_union) { + .in.s_addr = htobe32((UINT32_C(169) << 24) | + (UINT32_C(254) << 16) | + (i << 8) | j), + }, 32, &prefixes); + + test_config_parse_in_addr_prefixes_one(AF_INET6, &(union in_addr_union) { + .in6.s6_addr[0] = 0xff, + .in6.s6_addr[1] = i, + .in6.s6_addr[2] = j, + }, 24, &prefixes); + } + } + + *ret = TAKE_PTR(prefixes); +} + +static void test_in_addr_prefixes_reduce(Set *prefixes) { + log_info("/* %s() */", __func__); + + assert_se(set_size(prefixes) == 2 * 256 * 257); + assert_se(!in_addr_prefixes_is_any(prefixes)); + + assert_se(in_addr_prefixes_reduce(prefixes) >= 0); + assert_se(set_size(prefixes) == 2 * 256); + assert_se(!in_addr_prefixes_is_any(prefixes)); + + assert_se(config_parse_in_addr_prefixes("unit", "filename", 1, "Service", 1, "IPAddressAllow", 0, "link-local", &prefixes, NULL) == 0); + assert_se(set_size(prefixes) == 2 * 256 + 2); + assert_se(!in_addr_prefixes_is_any(prefixes)); + + assert_se(in_addr_prefixes_reduce(prefixes) >= 0); + assert_se(set_size(prefixes) == 256 + 2); + assert_se(!in_addr_prefixes_is_any(prefixes)); + + assert_se(config_parse_in_addr_prefixes("unit", "filename", 1, "Service", 1, "IPAddressAllow", 0, "multicast", &prefixes, NULL) == 0); + assert_se(set_size(prefixes) == 256 + 4); + assert_se(!in_addr_prefixes_is_any(prefixes)); + + assert_se(in_addr_prefixes_reduce(prefixes) >= 0); + assert_se(set_size(prefixes) == 4); + assert_se(!in_addr_prefixes_is_any(prefixes)); + + assert_se(config_parse_in_addr_prefixes("unit", "filename", 1, "Service", 1, "IPAddressAllow", 0, "any", &prefixes, NULL) == 0); + assert_se(set_size(prefixes) == 6); + assert_se(in_addr_prefixes_is_any(prefixes)); + + assert_se(in_addr_prefixes_reduce(prefixes) >= 0); + assert_se(set_size(prefixes) == 2); + assert_se(in_addr_prefixes_is_any(prefixes)); +} + +TEST(in_addr_prefixes) { + _cleanup_set_free_ Set *prefixes = NULL; + + test_config_parse_in_addr_prefixes(&prefixes); + test_in_addr_prefixes_reduce(prefixes); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-in-addr-util.c b/src/test/test-in-addr-util.c new file mode 100644 index 0000000..93ab1c5 --- /dev/null +++ b/src/test/test-in-addr-util.c @@ -0,0 +1,408 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <fnmatch.h> +#include <netinet/in.h> + +#include "in-addr-util.h" +#include "strv.h" +#include "tests.h" + +static void test_in_addr_prefix_from_string_one( + const char *p, + int family, + int ret, + const union in_addr_union *u, + unsigned char prefixlen, + int ret_refuse, + unsigned char prefixlen_refuse) { + + union in_addr_union q; + unsigned char l; + int f, r; + + r = in_addr_prefix_from_string(p, family, &q, &l); + assert_se(r == ret); + + if (r < 0) + return; + + assert_se(in_addr_equal(family, &q, u)); + assert_se(l == prefixlen); + + r = in_addr_prefix_from_string_auto(p, &f, &q, &l); + assert_se(r >= 0); + + assert_se(f == family); + assert_se(in_addr_equal(family, &q, u)); + assert_se(l == prefixlen); + + r = in_addr_prefix_from_string_auto_internal(p, PREFIXLEN_REFUSE, &f, &q, &l); + assert_se(r == ret_refuse); + + if (r >= 0) { + assert_se(f == family); + assert_se(in_addr_equal(family, &q, u)); + assert_se(l == prefixlen_refuse); + } +} + +TEST(in_addr_prefix_from_string) { + test_in_addr_prefix_from_string_one("", AF_INET, -EINVAL, NULL, 0, -EINVAL, 0); + test_in_addr_prefix_from_string_one("/", AF_INET, -EINVAL, NULL, 0, -EINVAL, 0); + test_in_addr_prefix_from_string_one("/8", AF_INET, -EINVAL, NULL, 0, -EINVAL, 0); + test_in_addr_prefix_from_string_one("1.2.3.4", AF_INET, 0, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 32, -ENOANO, 0); + test_in_addr_prefix_from_string_one("1.2.3.4/0", AF_INET, 0, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 0, 0, 0); + test_in_addr_prefix_from_string_one("1.2.3.4/1", AF_INET, 0, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 1, 0, 1); + test_in_addr_prefix_from_string_one("1.2.3.4/2", AF_INET, 0, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 2, 0, 2); + test_in_addr_prefix_from_string_one("1.2.3.4/32", AF_INET, 0, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 32, 0, 32); + test_in_addr_prefix_from_string_one("1.2.3.4/33", AF_INET, -ERANGE, NULL, 0, -ERANGE, 0); + test_in_addr_prefix_from_string_one("1.2.3.4/-1", AF_INET, -ERANGE, NULL, 0, -ERANGE, 0); + test_in_addr_prefix_from_string_one("::1", AF_INET, -EINVAL, NULL, 0, -EINVAL, 0); + + test_in_addr_prefix_from_string_one("", AF_INET6, -EINVAL, NULL, 0, -EINVAL, 0); + test_in_addr_prefix_from_string_one("/", AF_INET6, -EINVAL, NULL, 0, -EINVAL, 0); + test_in_addr_prefix_from_string_one("/8", AF_INET6, -EINVAL, NULL, 0, -EINVAL, 0); + test_in_addr_prefix_from_string_one("::1", AF_INET6, 0, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 128, -ENOANO, 0); + test_in_addr_prefix_from_string_one("::1/0", AF_INET6, 0, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 0, 0, 0); + test_in_addr_prefix_from_string_one("::1/1", AF_INET6, 0, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 1, 0, 1); + test_in_addr_prefix_from_string_one("::1/2", AF_INET6, 0, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 2, 0, 2); + test_in_addr_prefix_from_string_one("::1/32", AF_INET6, 0, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 32, 0, 32); + test_in_addr_prefix_from_string_one("::1/33", AF_INET6, 0, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 33, 0, 33); + test_in_addr_prefix_from_string_one("::1/64", AF_INET6, 0, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 64, 0, 64); + test_in_addr_prefix_from_string_one("::1/128", AF_INET6, 0, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 128, 0, 128); + test_in_addr_prefix_from_string_one("::1/129", AF_INET6, -ERANGE, NULL, 0, -ERANGE, 0); + test_in_addr_prefix_from_string_one("::1/-1", AF_INET6, -ERANGE, NULL, 0, -ERANGE, 0); +} + +static void test_in_addr_prefix_to_string_valid(int family, const char *p) { + union in_addr_union u; + unsigned char l; + + log_info("%s: %s", __func__, p); + + assert_se(in_addr_prefix_from_string(p, family, &u, &l) >= 0); + assert_se(streq(p, IN_ADDR_PREFIX_TO_STRING(family, &u, l))); +} + +static void test_in_addr_prefix_to_string_unoptimized(int family, const char *p) { + union in_addr_union u1, u2; + unsigned char len1, len2; + + log_info("%s: %s", __func__, p); + + assert_se(in_addr_prefix_from_string(p, family, &u1, &len1) >= 0); + const char *str1 = IN_ADDR_PREFIX_TO_STRING(family, &u1, len1); + assert_se(str1); + assert_se(in_addr_prefix_from_string(str1, family, &u2, &len2) >= 0); + const char *str2 = IN_ADDR_PREFIX_TO_STRING(family, &u2, len2); + assert_se(str2); + + assert_se(streq(str1, str2)); + assert_se(len1 == len2); + assert_se(in_addr_equal(family, &u1, &u2) > 0); +} + +TEST(in_addr_prefix_to_string) { + test_in_addr_prefix_to_string_valid(AF_INET, "0.0.0.0/32"); + test_in_addr_prefix_to_string_valid(AF_INET, "1.2.3.4/0"); + test_in_addr_prefix_to_string_valid(AF_INET, "1.2.3.4/24"); + test_in_addr_prefix_to_string_valid(AF_INET, "1.2.3.4/32"); + test_in_addr_prefix_to_string_valid(AF_INET, "255.255.255.255/32"); + + test_in_addr_prefix_to_string_valid(AF_INET6, "::1/128"); + test_in_addr_prefix_to_string_valid(AF_INET6, "fd00:abcd::1/64"); + test_in_addr_prefix_to_string_valid(AF_INET6, "fd00:abcd::1234:1/64"); + test_in_addr_prefix_to_string_valid(AF_INET6, "1111:2222:3333:4444:5555:6666:7777:8888/128"); + + test_in_addr_prefix_to_string_unoptimized(AF_INET, "0.0.0.0"); + test_in_addr_prefix_to_string_unoptimized(AF_INET, "192.168.0.1"); + + test_in_addr_prefix_to_string_unoptimized(AF_INET6, "fd00:0000:0000:0000:0000:0000:0000:0001/64"); + test_in_addr_prefix_to_string_unoptimized(AF_INET6, "fd00:1111::0000:2222:3333:4444:0001/64"); +} + +TEST(in_addr_random_prefix) { + _cleanup_free_ char *str = NULL; + union in_addr_union a; + + assert_se(in_addr_from_string(AF_INET, "192.168.10.1", &a) >= 0); + + assert_se(in_addr_random_prefix(AF_INET, &a, 31, 32) >= 0); + assert_se(in_addr_to_string(AF_INET, &a, &str) >= 0); + assert_se(STR_IN_SET(str, "192.168.10.0", "192.168.10.1")); + str = mfree(str); + + assert_se(in_addr_random_prefix(AF_INET, &a, 24, 26) >= 0); + assert_se(in_addr_to_string(AF_INET, &a, &str) >= 0); + assert_se(startswith(str, "192.168.10.")); + str = mfree(str); + + assert_se(in_addr_random_prefix(AF_INET, &a, 16, 24) >= 0); + assert_se(in_addr_to_string(AF_INET, &a, &str) >= 0); + assert_se(fnmatch("192.168.[0-9]*.0", str, 0) == 0); + str = mfree(str); + + assert_se(in_addr_random_prefix(AF_INET, &a, 8, 24) >= 0); + assert_se(in_addr_to_string(AF_INET, &a, &str) >= 0); + assert_se(fnmatch("192.[0-9]*.[0-9]*.0", str, 0) == 0); + str = mfree(str); + + assert_se(in_addr_random_prefix(AF_INET, &a, 8, 16) >= 0); + assert_se(in_addr_to_string(AF_INET, &a, &str) >= 0); + assert_se(fnmatch("192.[0-9]*.0.0", str, 0) == 0); + str = mfree(str); + + assert_se(in_addr_from_string(AF_INET6, "fd00::1", &a) >= 0); + + assert_se(in_addr_random_prefix(AF_INET6, &a, 16, 64) >= 0); + assert_se(in_addr_to_string(AF_INET6, &a, &str) >= 0); + assert_se(startswith(str, "fd00:")); + str = mfree(str); + + assert_se(in_addr_random_prefix(AF_INET6, &a, 8, 16) >= 0); + assert_se(in_addr_to_string(AF_INET6, &a, &str) >= 0); + assert_se(fnmatch("fd??::", str, 0) == 0); + str = mfree(str); +} + +TEST(in_addr_is_null) { + union in_addr_union i = {}; + + assert_se(in_addr_is_null(AF_INET, &i) == true); + assert_se(in_addr_is_null(AF_INET6, &i) == true); + + i.in.s_addr = 0x1000000; + assert_se(in_addr_is_null(AF_INET, &i) == false); + assert_se(in_addr_is_null(AF_INET6, &i) == false); + + assert_se(in_addr_is_null(-1, &i) == -EAFNOSUPPORT); +} + +static void test_in_addr_prefix_intersect_one(unsigned f, const char *a, unsigned apl, const char *b, unsigned bpl, int result) { + union in_addr_union ua, ub; + + assert_se(in_addr_from_string(f, a, &ua) >= 0); + assert_se(in_addr_from_string(f, b, &ub) >= 0); + + assert_se(in_addr_prefix_intersect(f, &ua, apl, &ub, bpl) == result); +} + +TEST(in_addr_prefix_intersect) { + test_in_addr_prefix_intersect_one(AF_INET, "255.255.255.255", 32, "255.255.255.254", 32, 0); + test_in_addr_prefix_intersect_one(AF_INET, "255.255.255.255", 0, "255.255.255.255", 32, 1); + test_in_addr_prefix_intersect_one(AF_INET, "0.0.0.0", 0, "47.11.8.15", 32, 1); + + test_in_addr_prefix_intersect_one(AF_INET, "1.1.1.1", 24, "1.1.1.1", 24, 1); + test_in_addr_prefix_intersect_one(AF_INET, "2.2.2.2", 24, "1.1.1.1", 24, 0); + + test_in_addr_prefix_intersect_one(AF_INET, "1.1.1.1", 24, "1.1.1.127", 25, 1); + test_in_addr_prefix_intersect_one(AF_INET, "1.1.1.1", 24, "1.1.1.127", 26, 1); + test_in_addr_prefix_intersect_one(AF_INET, "1.1.1.1", 25, "1.1.1.127", 25, 1); + test_in_addr_prefix_intersect_one(AF_INET, "1.1.1.1", 25, "1.1.1.255", 25, 0); + + test_in_addr_prefix_intersect_one(AF_INET6, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 128, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe", 128, 0); + test_in_addr_prefix_intersect_one(AF_INET6, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 0, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 128, 1); + test_in_addr_prefix_intersect_one(AF_INET6, "::", 0, "beef:beef:beef:beef:beef:beef:beef:beef", 128, 1); + + test_in_addr_prefix_intersect_one(AF_INET6, "1::2", 64, "1::2", 64, 1); + test_in_addr_prefix_intersect_one(AF_INET6, "2::2", 64, "1::2", 64, 0); + + test_in_addr_prefix_intersect_one(AF_INET6, "1::1", 120, "1::007f", 121, 1); + test_in_addr_prefix_intersect_one(AF_INET6, "1::1", 120, "1::007f", 122, 1); + test_in_addr_prefix_intersect_one(AF_INET6, "1::1", 121, "1::007f", 121, 1); + test_in_addr_prefix_intersect_one(AF_INET6, "1::1", 121, "1::00ff", 121, 0); +} + +static void test_in_addr_prefix_next_one(unsigned f, const char *before, unsigned pl, const char *after) { + union in_addr_union ubefore, uafter, t; + + log_debug("/* %s(%s, prefixlen=%u) */", __func__, before, pl); + + assert_se(in_addr_from_string(f, before, &ubefore) >= 0); + + t = ubefore; + assert_se((in_addr_prefix_next(f, &t, pl) >= 0) == !!after); + + if (after) { + assert_se(in_addr_from_string(f, after, &uafter) >= 0); + assert_se(in_addr_equal(f, &t, &uafter) > 0); + } +} + +TEST(in_addr_prefix_next) { + test_in_addr_prefix_next_one(AF_INET, "192.168.0.0", 24, "192.168.1.0"); + test_in_addr_prefix_next_one(AF_INET, "192.168.0.0", 16, "192.169.0.0"); + test_in_addr_prefix_next_one(AF_INET, "192.168.0.0", 20, "192.168.16.0"); + + test_in_addr_prefix_next_one(AF_INET, "0.0.0.0", 32, "0.0.0.1"); + test_in_addr_prefix_next_one(AF_INET, "255.255.255.254", 32, "255.255.255.255"); + test_in_addr_prefix_next_one(AF_INET, "255.255.255.255", 32, NULL); + test_in_addr_prefix_next_one(AF_INET, "255.255.255.0", 24, NULL); + + test_in_addr_prefix_next_one(AF_INET6, "4400::", 128, "4400::0001"); + test_in_addr_prefix_next_one(AF_INET6, "4400::", 120, "4400::0100"); + test_in_addr_prefix_next_one(AF_INET6, "4400::", 127, "4400::0002"); + test_in_addr_prefix_next_one(AF_INET6, "4400::", 8, "4500::"); + test_in_addr_prefix_next_one(AF_INET6, "4400::", 7, "4600::"); + + test_in_addr_prefix_next_one(AF_INET6, "::", 128, "::1"); + + test_in_addr_prefix_next_one(AF_INET6, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 128, NULL); + test_in_addr_prefix_next_one(AF_INET6, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00", 120, NULL); +} + +static void test_in_addr_prefix_nth_one(unsigned f, const char *before, unsigned pl, uint64_t nth, const char *after) { + union in_addr_union ubefore, uafter, t; + + log_debug("/* %s(%s, prefixlen=%u, nth=%"PRIu64") */", __func__, before, pl, nth); + + assert_se(in_addr_from_string(f, before, &ubefore) >= 0); + + t = ubefore; + assert_se((in_addr_prefix_nth(f, &t, pl, nth) >= 0) == !!after); + + if (after) { + assert_se(in_addr_from_string(f, after, &uafter) >= 0); + assert_se(in_addr_equal(f, &t, &uafter) > 0); + } +} + +TEST(in_addr_prefix_nth) { + test_in_addr_prefix_nth_one(AF_INET, "192.168.0.0", 24, 0, "192.168.0.0"); + test_in_addr_prefix_nth_one(AF_INET, "192.168.0.123", 24, 0, "192.168.0.0"); + test_in_addr_prefix_nth_one(AF_INET, "192.168.0.123", 24, 1, "192.168.1.0"); + test_in_addr_prefix_nth_one(AF_INET, "192.168.0.0", 24, 4, "192.168.4.0"); + test_in_addr_prefix_nth_one(AF_INET, "192.168.0.0", 25, 1, "192.168.0.128"); + test_in_addr_prefix_nth_one(AF_INET, "192.168.255.0", 25, 1, "192.168.255.128"); + test_in_addr_prefix_nth_one(AF_INET, "192.168.255.0", 24, 0, "192.168.255.0"); + test_in_addr_prefix_nth_one(AF_INET, "255.255.255.255", 32, 1, NULL); + test_in_addr_prefix_nth_one(AF_INET, "255.255.255.255", 0, 1, NULL); + + test_in_addr_prefix_nth_one(AF_INET6, "4400::", 8, 1, "4500::"); + test_in_addr_prefix_nth_one(AF_INET6, "4400::", 7, 1, "4600::"); + test_in_addr_prefix_nth_one(AF_INET6, "4400::", 64, 1, "4400:0:0:1::"); + test_in_addr_prefix_nth_one(AF_INET6, "4400::", 64, 2, "4400:0:0:2::"); + test_in_addr_prefix_nth_one(AF_INET6, "4400::", 64, 0xbad, "4400:0:0:0bad::"); + test_in_addr_prefix_nth_one(AF_INET6, "4400:0:0:ffff::", 64, 1, "4400:0:1::"); + test_in_addr_prefix_nth_one(AF_INET6, "4400::", 56, ((uint64_t)1<<48) -1, "44ff:ffff:ffff:ff00::"); + test_in_addr_prefix_nth_one(AF_INET6, "0000::", 8, 255, "ff00::"); + test_in_addr_prefix_nth_one(AF_INET6, "0000::", 8, 256, NULL); + test_in_addr_prefix_nth_one(AF_INET6, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 128, 1, NULL); + test_in_addr_prefix_nth_one(AF_INET6, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 0, 1, NULL); + test_in_addr_prefix_nth_one(AF_INET6, "1234:5678:90ab:cdef:1234:5678:90ab:cdef", 12, 1, "1240::"); +} + +static void test_in_addr_prefix_range_one( + int family, + const char *in, + unsigned prefixlen, + const char *expected_start, + const char *expected_end) { + + union in_addr_union a, s, e; + + log_debug("/* %s(%s, prefixlen=%u) */", __func__, in, prefixlen); + + assert_se(in_addr_from_string(family, in, &a) >= 0); + assert_se((in_addr_prefix_range(family, &a, prefixlen, &s, &e) >= 0) == !!expected_start); + + if (expected_start) { + union in_addr_union es; + + assert_se(in_addr_from_string(family, expected_start, &es) >= 0); + assert_se(in_addr_equal(family, &s, &es) > 0); + } + if (expected_end) { + union in_addr_union ee; + + assert_se(in_addr_from_string(family, expected_end, &ee) >= 0); + assert_se(in_addr_equal(family, &e, &ee) > 0); + } +} + +TEST(in_addr_prefix_range) { + test_in_addr_prefix_range_one(AF_INET, "192.168.123.123", 24, "192.168.123.0", "192.168.124.0"); + test_in_addr_prefix_range_one(AF_INET, "192.168.123.123", 16, "192.168.0.0", "192.169.0.0"); + + test_in_addr_prefix_range_one(AF_INET6, "dead:beef::", 64, "dead:beef::", "dead:beef:0:1::"); + test_in_addr_prefix_range_one(AF_INET6, "dead:0:0:beef::", 64, "dead:0:0:beef::", "dead:0:0:bef0::"); + test_in_addr_prefix_range_one(AF_INET6, "2001::", 48, "2001::", "2001:0:1::"); + test_in_addr_prefix_range_one(AF_INET6, "2001::", 56, "2001::", "2001:0:0:0100::"); + test_in_addr_prefix_range_one(AF_INET6, "2001::", 65, "2001::", "2001::8000:0:0:0"); + test_in_addr_prefix_range_one(AF_INET6, "2001::", 66, "2001::", "2001::4000:0:0:0"); + test_in_addr_prefix_range_one(AF_INET6, "2001::", 127, "2001::", "2001::2"); +} + +static void test_in_addr_to_string_one(int f, const char *addr) { + union in_addr_union ua; + _cleanup_free_ char *r; + + assert_se(in_addr_from_string(f, addr, &ua) >= 0); + assert_se(in_addr_to_string(f, &ua, &r) >= 0); + printf("%s: %s == %s\n", __func__, addr, r); + assert_se(streq(addr, r)); + + assert_se(streq(r, IN_ADDR_TO_STRING(f, &ua))); +} + +TEST(in_addr_to_string) { + test_in_addr_to_string_one(AF_INET, "192.168.0.1"); + test_in_addr_to_string_one(AF_INET, "10.11.12.13"); + test_in_addr_to_string_one(AF_INET6, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); + test_in_addr_to_string_one(AF_INET6, "::1"); + test_in_addr_to_string_one(AF_INET6, "fe80::"); +} + +TEST(in_addr_prefixlen_to_netmask) { + union in_addr_union addr; + static const char *const ipv4_netmasks[] = { + "0.0.0.0", "128.0.0.0", "192.0.0.0", "224.0.0.0", "240.0.0.0", + "248.0.0.0", "252.0.0.0", "254.0.0.0", "255.0.0.0", + "255.128.0.0", "255.192.0.0", "255.224.0.0", "255.240.0.0", + "255.248.0.0", "255.252.0.0", "255.254.0.0", "255.255.0.0", + "255.255.128.0", "255.255.192.0", "255.255.224.0", "255.255.240.0", + "255.255.248.0", "255.255.252.0", "255.255.254.0", "255.255.255.0", + "255.255.255.128", "255.255.255.192", "255.255.255.224", "255.255.255.240", + "255.255.255.248", "255.255.255.252", "255.255.255.254", "255.255.255.255", + }; + + static const char *const ipv6_netmasks[] = { + [0] = "::", + [1] = "8000::", + [2] = "c000::", + [7] = "fe00::", + [8] = "ff00::", + [9] = "ff80::", + [16] = "ffff::", + [17] = "ffff:8000::", + [32] = "ffff:ffff::", + [33] = "ffff:ffff:8000::", + [64] = "ffff:ffff:ffff:ffff::", + [65] = "ffff:ffff:ffff:ffff:8000::", + [96] = "ffff:ffff:ffff:ffff:ffff:ffff::", + [97] = "ffff:ffff:ffff:ffff:ffff:ffff:8000:0", + [127] = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe", + [128] = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" + }; + + for (unsigned char prefixlen = 0; prefixlen <= 32; prefixlen++) { + _cleanup_free_ char *result = NULL; + + assert_se(in_addr_prefixlen_to_netmask(AF_INET, &addr, prefixlen) >= 0); + assert_se(in_addr_to_string(AF_INET, &addr, &result) >= 0); + printf("test_in_addr_prefixlen_to_netmask: %s == %s\n", ipv4_netmasks[prefixlen], result); + assert_se(streq(ipv4_netmasks[prefixlen], result)); + } + + for (unsigned char prefixlen = 0; prefixlen <= 128; prefixlen++) { + _cleanup_free_ char *result = NULL; + + assert_se(in_addr_prefixlen_to_netmask(AF_INET6, &addr, prefixlen) >= 0); + assert_se(in_addr_to_string(AF_INET6, &addr, &result) >= 0); + printf("test_in_addr_prefixlen_to_netmask: %s\n", result); + if (ipv6_netmasks[prefixlen]) + assert_se(streq(ipv6_netmasks[prefixlen], result)); + } +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-install-file.c b/src/test/test-install-file.c new file mode 100644 index 0000000..8206eb0 --- /dev/null +++ b/src/test/test-install-file.c @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "fileio.h" +#include "install-file.h" +#include "path-util.h" +#include "rm-rf.h" +#include "tests.h" +#include "tmpfile-util.h" +#include "umask-util.h" + +TEST(install_file) { + _cleanup_(rm_rf_physical_and_freep) char *p = NULL; + _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL; + struct stat stat1, stat2; + + assert_se(mkdtemp_malloc(NULL, &p) >= 0); + assert_se(a = path_join(p, "foo")); + assert_se(b = path_join(p, "bar")); + + WITH_UMASK(0077) + assert_se(write_string_file(a, "wups", WRITE_STRING_FILE_CREATE) >= 0); + + assert_se(lstat(a, &stat1) >= 0); + assert_se(S_ISREG(stat1.st_mode)); + + assert_se(install_file(AT_FDCWD, a, AT_FDCWD, b, 0) >= 0); + assert_se(install_file(AT_FDCWD, b, AT_FDCWD, a, INSTALL_FSYNC) >= 0); + + assert_se(write_string_file(b, "ttss", WRITE_STRING_FILE_CREATE) >= 0); + assert_se(install_file(AT_FDCWD, a, AT_FDCWD, b, INSTALL_FSYNC_FULL) == -EEXIST); + assert_se(install_file(AT_FDCWD, a, AT_FDCWD, b, INSTALL_FSYNC_FULL|INSTALL_REPLACE) >= 0); + + assert_se(stat(b, &stat2) >= 0); + assert_se(stat1.st_dev == stat2.st_dev); + assert_se(stat1.st_ino == stat2.st_ino); + assert_se((stat2.st_mode & 0222) != 0); /* writable */ + + assert_se(install_file(AT_FDCWD, b, AT_FDCWD, a, INSTALL_FSYNC_FULL|INSTALL_REPLACE|INSTALL_READ_ONLY) >= 0); + + assert_se(stat(a, &stat2) >= 0); + assert_se(stat1.st_dev == stat2.st_dev); + assert_se(stat1.st_ino == stat2.st_ino); + assert_se((stat2.st_mode & 0222) == 0); /* read-only */ + + assert_se(mkdir(b, 0755) >= 0); + assert_se(c = path_join(b, "dir")); + assert_se(mkdir(c, 0755) >= 0); + free(c); + assert_se(c = path_join(b, "reg")); + assert_se(mknod(c, S_IFREG|0755, 0) >= 0); + free(c); + assert_se(c = path_join(b, "fifo")); + assert_se(mknod(c, S_IFIFO|0755, 0) >= 0); + + assert_se(install_file(AT_FDCWD, b, AT_FDCWD, a, INSTALL_FSYNC_FULL) == -EEXIST); + assert_se(install_file(AT_FDCWD, b, AT_FDCWD, a, INSTALL_FSYNC_FULL|INSTALL_REPLACE) == 0); + + assert_se(write_string_file(b, "ttss", WRITE_STRING_FILE_CREATE) >= 0); + + assert_se(install_file(AT_FDCWD, b, AT_FDCWD, a, INSTALL_FSYNC_FULL) == -EEXIST); + assert_se(install_file(AT_FDCWD, b, AT_FDCWD, a, INSTALL_FSYNC_FULL|INSTALL_REPLACE) == 0); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-install-root.c b/src/test/test-install-root.c new file mode 100644 index 0000000..efd75b2 --- /dev/null +++ b/src/test/test-install-root.c @@ -0,0 +1,1299 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <unistd.h> + +#include "alloc-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "install.h" +#include "mkdir.h" +#include "rm-rf.h" +#include "special.h" +#include "string-util.h" +#include "tests.h" +#include "tmpfile-util.h" + +static char *root = NULL; + +STATIC_DESTRUCTOR_REGISTER(root, rm_rf_physical_and_freep); + +TEST(basic_mask_and_enable) { + const char *p; + UnitFileState state; + InstallChange *changes = NULL; + size_t n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "a.service", NULL) == -ENOENT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "b.service", NULL) == -ENOENT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "c.service", NULL) == -ENOENT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "d.service", NULL) == -ENOENT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "e.service", NULL) == -ENOENT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "f.service", NULL) == -ENOENT); + + p = strjoina(root, "/usr/lib/systemd/system/a.service"); + assert_se(write_string_file(p, + "[Install]\n" + "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "a.service", NULL) >= 0); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "a.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + + p = strjoina(root, "/usr/lib/systemd/system/b.service"); + assert_se(symlink("a.service", p) >= 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "b.service", NULL) >= 0); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "b.service", &state) >= 0 && state == UNIT_FILE_ALIAS); + + p = strjoina(root, "/usr/lib/systemd/system/c.service"); + assert_se(symlink("/usr/lib/systemd/system/a.service", p) >= 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "c.service", NULL) >= 0); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "c.service", &state) >= 0 && state == UNIT_FILE_ALIAS); + + p = strjoina(root, "/usr/lib/systemd/system/d.service"); + assert_se(symlink("c.service", p) >= 0); + + /* This one is interesting, as d follows a relative, then an absolute symlink */ + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "d.service", NULL) >= 0); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "d.service", &state) >= 0 && state == UNIT_FILE_ALIAS); + + assert_se(unit_file_mask(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("a.service"), &changes, &n_changes) >= 0); + assert_se(n_changes == 1); + assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); + assert_se(streq(changes[0].source, "/dev/null")); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/a.service"); + assert_se(streq(changes[0].path, p)); + + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "a.service", &state) >= 0 && state == UNIT_FILE_MASKED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "b.service", &state) >= 0 && state == UNIT_FILE_MASKED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "c.service", &state) >= 0 && state == UNIT_FILE_MASKED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "d.service", &state) >= 0 && state == UNIT_FILE_MASKED); + + /* Enabling a masked unit should fail! */ + assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("a.service"), &changes, &n_changes) == -ERFKILL); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_unmask(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("a.service"), &changes, &n_changes) >= 0); + assert_se(n_changes == 1); + assert_se(changes[0].type == INSTALL_CHANGE_UNLINK); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/a.service"); + assert_se(streq(changes[0].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("a.service"), &changes, &n_changes) == 1); + assert_se(n_changes == 1); + assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); + assert_se(streq(changes[0].source, "/usr/lib/systemd/system/a.service")); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/a.service"); + assert_se(streq(changes[0].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "a.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "b.service", &state) >= 0 && state == UNIT_FILE_ALIAS); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "c.service", &state) >= 0 && state == UNIT_FILE_ALIAS); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "d.service", &state) >= 0 && state == UNIT_FILE_ALIAS); + + /* Enabling it again should succeed but be a NOP */ + assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("a.service"), &changes, &n_changes) >= 0); + assert_se(n_changes == 0); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_disable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("a.service"), &changes, &n_changes) >= 0); + assert_se(n_changes == 1); + assert_se(changes[0].type == INSTALL_CHANGE_UNLINK); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/a.service"); + assert_se(streq(changes[0].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "a.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "b.service", &state) >= 0 && state == UNIT_FILE_ALIAS); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "c.service", &state) >= 0 && state == UNIT_FILE_ALIAS); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "d.service", &state) >= 0 && state == UNIT_FILE_ALIAS); + + /* Disabling a disabled unit must succeed but be a NOP */ + assert_se(unit_file_disable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("a.service"), &changes, &n_changes) >= 0); + assert_se(n_changes == 0); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + /* Let's enable this indirectly via a symlink */ + assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("d.service"), &changes, &n_changes) >= 0); + assert_se(n_changes == 1); + assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); + assert_se(streq(changes[0].source, "/usr/lib/systemd/system/a.service")); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/a.service"); + assert_se(streq(changes[0].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "a.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "b.service", &state) >= 0 && state == UNIT_FILE_ALIAS); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "c.service", &state) >= 0 && state == UNIT_FILE_ALIAS); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "d.service", &state) >= 0 && state == UNIT_FILE_ALIAS); + + /* Let's try to reenable */ + + assert_se(unit_file_reenable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("b.service"), &changes, &n_changes) >= 0); + assert_se(n_changes == 2); + assert_se(changes[0].type == INSTALL_CHANGE_UNLINK); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/a.service"); + assert_se(streq(changes[0].path, p)); + assert_se(changes[1].type == INSTALL_CHANGE_SYMLINK); + assert_se(streq(changes[1].source, "/usr/lib/systemd/system/a.service")); + assert_se(streq(changes[1].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "a.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "b.service", &state) >= 0 && state == UNIT_FILE_ALIAS); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "c.service", &state) >= 0 && state == UNIT_FILE_ALIAS); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "d.service", &state) >= 0 && state == UNIT_FILE_ALIAS); + + /* Test masking with relative symlinks */ + + p = strjoina(root, "/usr/lib/systemd/system/e.service"); + assert_se(symlink("../../../../../../dev/null", p) >= 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "e.service", NULL) >= 0); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "e.service", &state) >= 0 && state == UNIT_FILE_MASKED); + + assert_se(unlink(p) == 0); + assert_se(symlink("/usr/../dev/null", p) >= 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "e.service", NULL) >= 0); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "e.service", &state) >= 0 && state == UNIT_FILE_MASKED); + + assert_se(unlink(p) == 0); + + /* Test enabling with unknown dependency target */ + + p = strjoina(root, "/usr/lib/systemd/system/f.service"); + assert_se(write_string_file(p, + "[Install]\n" + "WantedBy=x.target\n", WRITE_STRING_FILE_CREATE) >= 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "f.service", NULL) >= 0); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "f.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + + assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("f.service"), &changes, &n_changes) == 1); + assert_se(n_changes == 2); + assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); + assert_se(streq(changes[0].source, "/usr/lib/systemd/system/f.service")); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/x.target.wants/f.service"); + assert_se(streq(changes[0].path, p)); + assert_se(changes[1].type == INSTALL_CHANGE_DESTINATION_NOT_PRESENT); + p = strjoina(root, "/usr/lib/systemd/system/f.service"); + assert_se(streq(changes[1].source, p)); + assert_se(streq(changes[1].path, "x.target")); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "f.service", &state) >= 0 && state == UNIT_FILE_ENABLED); +} + +TEST(linked_units) { + const char *p, *q; + UnitFileState state; + InstallChange *changes = NULL; + size_t n_changes = 0, i; + + /* + * We'll test three cases here: + * + * a) a unit file in /opt, that we use "systemctl link" and + * "systemctl enable" on to make it available to the system + * + * b) a unit file in /opt, that is statically linked into + * /usr/lib/systemd/system, that "enable" should work on + * correctly. + * + * c) a unit file in /opt, that is linked into + * /etc/systemd/system, and where "enable" should result in + * -ELOOP, since using information from /etc to generate + * information in /etc should not be allowed. + */ + + p = strjoina(root, "/opt/linked.service"); + assert_se(write_string_file(p, + "[Install]\n" + "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0); + + p = strjoina(root, "/opt/linked2.service"); + assert_se(write_string_file(p, + "[Install]\n" + "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0); + + p = strjoina(root, "/opt/linked3.service"); + assert_se(write_string_file(p, + "[Install]\n" + "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "linked.service", NULL) == -ENOENT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "linked2.service", NULL) == -ENOENT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "linked3.service", NULL) == -ENOENT); + + p = strjoina(root, "/usr/lib/systemd/system/linked2.service"); + assert_se(symlink("/opt/linked2.service", p) >= 0); + + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/linked3.service"); + assert_se(symlink("/opt/linked3.service", p) >= 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "linked.service", &state) == -ENOENT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "linked2.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "linked3.service", &state) >= 0 && state == UNIT_FILE_LINKED); + + /* First, let's link the unit into the search path */ + assert_se(unit_file_link(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("/opt/linked.service"), &changes, &n_changes) >= 0); + assert_se(n_changes == 1); + assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); + assert_se(streq(changes[0].source, "/opt/linked.service")); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/linked.service"); + assert_se(streq(changes[0].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "linked.service", &state) >= 0 && state == UNIT_FILE_LINKED); + + /* Let's unlink it from the search path again */ + assert_se(unit_file_disable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("linked.service"), &changes, &n_changes) >= 0); + assert_se(n_changes == 1); + assert_se(changes[0].type == INSTALL_CHANGE_UNLINK); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/linked.service"); + assert_se(streq(changes[0].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "linked.service", NULL) == -ENOENT); + + /* Now, let's not just link it, but also enable it */ + assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("/opt/linked.service"), &changes, &n_changes) >= 0); + assert_se(n_changes == 2); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/linked.service"); + q = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/linked.service"); + for (i = 0 ; i < n_changes; i++) { + assert_se(changes[i].type == INSTALL_CHANGE_SYMLINK); + assert_se(streq(changes[i].source, "/opt/linked.service")); + + if (p && streq(changes[i].path, p)) + p = NULL; + else if (q && streq(changes[i].path, q)) + q = NULL; + else + assert_not_reached(); + } + assert_se(!p && !q); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "linked.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + + /* And let's unlink it again */ + assert_se(unit_file_disable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("linked.service"), &changes, &n_changes) >= 0); + assert_se(n_changes == 2); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/linked.service"); + q = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/linked.service"); + for (i = 0; i < n_changes; i++) { + assert_se(changes[i].type == INSTALL_CHANGE_UNLINK); + + if (p && streq(changes[i].path, p)) + p = NULL; + else if (q && streq(changes[i].path, q)) + q = NULL; + else + assert_not_reached(); + } + assert_se(!p && !q); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "linked.service", NULL) == -ENOENT); + + assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("linked2.service"), &changes, &n_changes) >= 0); + assert_se(n_changes == 2); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/linked2.service"); + q = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/linked2.service"); + for (i = 0 ; i < n_changes; i++) { + assert_se(changes[i].type == INSTALL_CHANGE_SYMLINK); + assert_se(streq(changes[i].source, "/opt/linked2.service")); + + if (p && streq(changes[i].path, p)) + p = NULL; + else if (q && streq(changes[i].path, q)) + q = NULL; + else + assert_not_reached(); + } + assert_se(!p && !q); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("linked3.service"), &changes, &n_changes) >= 0); + assert_se(n_changes == 1); + assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); + assert_se(startswith(changes[0].path, root)); + assert_se(endswith(changes[0].path, "linked3.service")); + assert_se(streq(changes[0].source, "/opt/linked3.service")); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; +} + +TEST(default) { + _cleanup_free_ char *def = NULL; + InstallChange *changes = NULL; + size_t n_changes = 0; + const char *p; + + p = strjoina(root, "/usr/lib/systemd/system/test-default-real.target"); + assert_se(write_string_file(p, "# pretty much empty", WRITE_STRING_FILE_CREATE) >= 0); + + p = strjoina(root, "/usr/lib/systemd/system/test-default.target"); + assert_se(symlink("test-default-real.target", p) >= 0); + + assert_se(unit_file_get_default(RUNTIME_SCOPE_SYSTEM, root, &def) == -ENOENT); + + assert_se(unit_file_set_default(RUNTIME_SCOPE_SYSTEM, 0, root, "idontexist.target", &changes, &n_changes) == -ENOENT); + assert_se(n_changes == 1); + assert_se(changes[0].type == -ENOENT); + assert_se(streq_ptr(changes[0].path, "idontexist.target")); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_get_default(RUNTIME_SCOPE_SYSTEM, root, &def) == -ENOENT); + + assert_se(unit_file_set_default(RUNTIME_SCOPE_SYSTEM, 0, root, "test-default.target", &changes, &n_changes) >= 0); + assert_se(n_changes == 1); + assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); + assert_se(streq(changes[0].source, "/usr/lib/systemd/system/test-default-real.target")); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR "/" SPECIAL_DEFAULT_TARGET); + assert_se(streq(changes[0].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_get_default(RUNTIME_SCOPE_SYSTEM, root, &def) >= 0); + assert_se(streq_ptr(def, "test-default-real.target")); +} + +TEST(add_dependency) { + InstallChange *changes = NULL; + size_t n_changes = 0; + const char *p; + + p = strjoina(root, "/usr/lib/systemd/system/real-add-dependency-test-target.target"); + assert_se(write_string_file(p, "# pretty much empty", WRITE_STRING_FILE_CREATE) >= 0); + + p = strjoina(root, "/usr/lib/systemd/system/add-dependency-test-target.target"); + assert_se(symlink("real-add-dependency-test-target.target", p) >= 0); + + p = strjoina(root, "/usr/lib/systemd/system/real-add-dependency-test-service.service"); + assert_se(write_string_file(p, "# pretty much empty", WRITE_STRING_FILE_CREATE) >= 0); + + p = strjoina(root, "/usr/lib/systemd/system/add-dependency-test-service.service"); + assert_se(symlink("real-add-dependency-test-service.service", p) >= 0); + + assert_se(unit_file_add_dependency(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("add-dependency-test-service.service"), "add-dependency-test-target.target", UNIT_WANTS, &changes, &n_changes) >= 0); + assert_se(n_changes == 1); + assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); + assert_se(streq(changes[0].source, "/usr/lib/systemd/system/real-add-dependency-test-service.service")); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/real-add-dependency-test-target.target.wants/real-add-dependency-test-service.service"); + assert_se(streq(changes[0].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; +} + +TEST(template_enable) { + InstallChange *changes = NULL; + size_t n_changes = 0; + UnitFileState state; + const char *p; + + log_info("== %s ==", __func__); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template@.service", &state) == -ENOENT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template@def.service", &state) == -ENOENT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template@foo.service", &state) == -ENOENT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template-symlink@foo.service", &state) == -ENOENT); + + p = strjoina(root, "/usr/lib/systemd/system/template@.service"); + assert_se(write_string_file(p, + "[Install]\n" + "DefaultInstance=def\n" + "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0); + + p = strjoina(root, "/usr/lib/systemd/system/template-symlink@.service"); + assert_se(symlink("template@.service", p) >= 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template@.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template-symlink@.service", &state) >= 0 && state == UNIT_FILE_ALIAS); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template-symlink@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template-symlink@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + + log_info("== %s with template@.service enabled ==", __func__); + + assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("template@.service"), &changes, &n_changes) >= 0); + assert_se(n_changes == 1); + assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); + assert_se(streq(changes[0].source, "/usr/lib/systemd/system/template@.service")); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/template@def.service"); + assert_se(streq(changes[0].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template@.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template@def.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template-symlink@.service", &state) >= 0 && state == UNIT_FILE_ALIAS); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template-symlink@def.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template-symlink@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + + assert_se(unit_file_disable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("template@.service"), &changes, &n_changes) >= 0); + assert_se(n_changes == 1); + assert_se(changes[0].type == INSTALL_CHANGE_UNLINK); + assert_se(streq(changes[0].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template@.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template-symlink@.service", &state) >= 0 && state == UNIT_FILE_ALIAS); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template-symlink@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template-symlink@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + + log_info("== %s with template@foo.service enabled ==", __func__); + + assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("template@foo.service"), &changes, &n_changes) >= 0); + assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); + assert_se(streq(changes[0].source, "/usr/lib/systemd/system/template@.service")); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/template@foo.service"); + assert_se(streq(changes[0].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template@.service", &state) >= 0 && state == UNIT_FILE_INDIRECT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template@foo.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template-symlink@foo.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template-symlink@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template-symlink@foo.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + + assert_se(unit_file_disable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("template@foo.service"), &changes, &n_changes) >= 0); + assert_se(n_changes == 1); + assert_se(changes[0].type == INSTALL_CHANGE_UNLINK); + assert_se(streq(changes[0].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template@.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template@quux.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template-symlink@.service", &state) >= 0 && state == UNIT_FILE_ALIAS); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template-symlink@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template-symlink@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template-symlink@quux.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + + log_info("== %s with template-symlink@quux.service enabled ==", __func__); + + assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("template-symlink@quux.service"), &changes, &n_changes) >= 0); + assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); + assert_se(streq(changes[0].source, "/usr/lib/systemd/system/template@.service")); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/template@quux.service"); + assert_se(streq(changes[0].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template@.service", &state) >= 0 && state == UNIT_FILE_INDIRECT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template@quux.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template-symlink@.service", &state) >= 0 && state == UNIT_FILE_ALIAS); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template-symlink@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template-symlink@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "template-symlink@quux.service", &state) >= 0 && state == UNIT_FILE_ENABLED); +} + +TEST(indirect) { + InstallChange *changes = NULL; + size_t n_changes = 0; + UnitFileState state; + const char *p; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "indirecta.service", &state) == -ENOENT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "indirectb.service", &state) == -ENOENT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "indirectc.service", &state) == -ENOENT); + + p = strjoina(root, "/usr/lib/systemd/system/indirecta.service"); + assert_se(write_string_file(p, + "[Install]\n" + "Also=indirectb.service\n", WRITE_STRING_FILE_CREATE) >= 0); + + p = strjoina(root, "/usr/lib/systemd/system/indirectb.service"); + assert_se(write_string_file(p, + "[Install]\n" + "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0); + + p = strjoina(root, "/usr/lib/systemd/system/indirectc.service"); + assert_se(symlink("indirecta.service", p) >= 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "indirecta.service", &state) >= 0 && state == UNIT_FILE_INDIRECT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "indirectb.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "indirectc.service", &state) >= 0 && state == UNIT_FILE_ALIAS); + + assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("indirectc.service"), &changes, &n_changes) >= 0); + assert_se(n_changes == 1); + assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); + assert_se(streq(changes[0].source, "/usr/lib/systemd/system/indirectb.service")); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/indirectb.service"); + assert_se(streq(changes[0].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "indirecta.service", &state) >= 0 && state == UNIT_FILE_INDIRECT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "indirectb.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "indirectc.service", &state) >= 0 && state == UNIT_FILE_ALIAS); + + assert_se(unit_file_disable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("indirectc.service"), &changes, &n_changes) >= 0); + assert_se(n_changes == 1); + assert_se(changes[0].type == INSTALL_CHANGE_UNLINK); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/indirectb.service"); + assert_se(streq(changes[0].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; +} + +TEST(preset_and_list) { + InstallChange *changes = NULL; + size_t n_changes = 0, i; + const char *p, *q; + UnitFileState state; + bool got_yes = false, got_no = false; + UnitFileList *fl; + _cleanup_hashmap_free_ Hashmap *h = NULL; + + CLEANUP_ARRAY(changes, n_changes, install_changes_free); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-yes.service", &state) == -ENOENT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-no.service", &state) == -ENOENT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) == -ENOENT); + + p = strjoina(root, "/usr/lib/systemd/system/preset-yes.service"); + assert_se(write_string_file(p, + "[Install]\n" + "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0); + + p = strjoina(root, "/usr/lib/systemd/system/preset-no.service"); + assert_se(write_string_file(p, + "[Install]\n" + "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0); + + p = strjoina(root, "/usr/lib/systemd/system/preset-ignore.service"); + assert_se(write_string_file(p, + "[Install]\n" + "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0); + + p = strjoina(root, "/usr/lib/systemd/system-preset/test.preset"); + assert_se(write_string_file(p, + "enable *-yes.*\n" + "ignore *-ignore.*\n" + "disable *\n", WRITE_STRING_FILE_CREATE) >= 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + + assert_se(unit_file_preset(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("preset-yes.service"), UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0); + assert_se(n_changes == 1); + assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); + assert_se(streq(changes[0].source, "/usr/lib/systemd/system/preset-yes.service")); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/preset-yes.service"); + assert_se(streq(changes[0].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + + assert_se(unit_file_disable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("preset-yes.service"), &changes, &n_changes) >= 0); + assert_se(n_changes == 1); + assert_se(changes[0].type == INSTALL_CHANGE_UNLINK); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/preset-yes.service"); + assert_se(streq(changes[0].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + + assert_se(unit_file_preset(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("preset-no.service"), UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0); + assert_se(n_changes == 0); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + + assert_se(unit_file_preset_all(RUNTIME_SCOPE_SYSTEM, 0, root, UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0); + + assert_se(n_changes > 0); + + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/preset-yes.service"); + + for (i = 0; i < n_changes; i++) { + + if (changes[i].type == INSTALL_CHANGE_SYMLINK) { + assert_se(streq(changes[i].source, "/usr/lib/systemd/system/preset-yes.service")); + assert_se(streq(changes[i].path, p)); + } else + assert_se(changes[i].type == INSTALL_CHANGE_UNLINK); + } + + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + + assert_se(h = hashmap_new(&unit_file_list_hash_ops_free)); + assert_se(unit_file_get_list(RUNTIME_SCOPE_SYSTEM, root, h, NULL, NULL) >= 0); + + p = strjoina(root, "/usr/lib/systemd/system/preset-yes.service"); + q = strjoina(root, "/usr/lib/systemd/system/preset-no.service"); + + HASHMAP_FOREACH(fl, h) { + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, basename(fl->path), &state) >= 0); + assert_se(fl->state == state); + + if (streq(fl->path, p)) { + got_yes = true; + assert_se(fl->state == UNIT_FILE_ENABLED); + } else if (streq(fl->path, q)) { + got_no = true; + assert_se(fl->state == UNIT_FILE_DISABLED); + } else + assert_se(IN_SET(fl->state, UNIT_FILE_DISABLED, UNIT_FILE_STATIC, UNIT_FILE_INDIRECT, UNIT_FILE_ALIAS)); + } + + assert_se(got_yes && got_no); + + assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("preset-ignore.service"), &changes, &n_changes) >= 0); + assert_se(unit_file_preset(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("preset-ignore.service"), UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) >= 0 && state == UNIT_FILE_ENABLED); +} + +TEST(revert) { + const char *p; + UnitFileState state; + InstallChange *changes = NULL; + size_t n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "xx.service", NULL) == -ENOENT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "yy.service", NULL) == -ENOENT); + + p = strjoina(root, "/usr/lib/systemd/system/xx.service"); + assert_se(write_string_file(p, "# Empty\n", WRITE_STRING_FILE_CREATE) >= 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "xx.service", NULL) >= 0); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "xx.service", &state) >= 0 && state == UNIT_FILE_STATIC); + + /* Initially there's nothing to revert */ + assert_se(unit_file_revert(RUNTIME_SCOPE_SYSTEM, root, STRV_MAKE("xx.service"), &changes, &n_changes) >= 0); + assert_se(n_changes == 0); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/xx.service"); + assert_se(write_string_file(p, "# Empty override\n", WRITE_STRING_FILE_CREATE) >= 0); + + /* Revert the override file */ + assert_se(unit_file_revert(RUNTIME_SCOPE_SYSTEM, root, STRV_MAKE("xx.service"), &changes, &n_changes) >= 0); + assert_se(n_changes == 1); + assert_se(changes[0].type == INSTALL_CHANGE_UNLINK); + assert_se(streq(changes[0].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/xx.service.d/dropin.conf"); + assert_se(write_string_file(p, "# Empty dropin\n", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755) >= 0); + + /* Revert the dropin file */ + assert_se(unit_file_revert(RUNTIME_SCOPE_SYSTEM, root, STRV_MAKE("xx.service"), &changes, &n_changes) >= 0); + assert_se(n_changes == 2); + assert_se(changes[0].type == INSTALL_CHANGE_UNLINK); + assert_se(streq(changes[0].path, p)); + + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/xx.service.d"); + assert_se(changes[1].type == INSTALL_CHANGE_UNLINK); + assert_se(streq(changes[1].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; +} + +TEST(preset_order) { + InstallChange *changes = NULL; + size_t n_changes = 0; + const char *p; + UnitFileState state; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "prefix-1.service", &state) == -ENOENT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "prefix-2.service", &state) == -ENOENT); + + p = strjoina(root, "/usr/lib/systemd/system/prefix-1.service"); + assert_se(write_string_file(p, + "[Install]\n" + "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0); + + p = strjoina(root, "/usr/lib/systemd/system/prefix-2.service"); + assert_se(write_string_file(p, + "[Install]\n" + "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0); + + p = strjoina(root, "/usr/lib/systemd/system-preset/test.preset"); + assert_se(write_string_file(p, + "enable prefix-1.service\n" + "disable prefix-*.service\n" + "enable prefix-2.service\n", WRITE_STRING_FILE_CREATE) >= 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "prefix-1.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "prefix-2.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + + assert_se(unit_file_preset(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("prefix-1.service"), UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0); + assert_se(n_changes == 1); + assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); + assert_se(streq(changes[0].source, "/usr/lib/systemd/system/prefix-1.service")); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/prefix-1.service"); + assert_se(streq(changes[0].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "prefix-1.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "prefix-2.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + + assert_se(unit_file_preset(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("prefix-2.service"), UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0); + assert_se(n_changes == 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "prefix-1.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "prefix-2.service", &state) >= 0 && state == UNIT_FILE_DISABLED); +} + +TEST(static_instance) { + UnitFileState state; + const char *p; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "static-instance@.service", &state) == -ENOENT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "static-instance@foo.service", &state) == -ENOENT); + + p = strjoina(root, "/usr/lib/systemd/system/static-instance@.service"); + assert_se(write_string_file(p, + "[Install]\n" + "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "static-instance@.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "static-instance@foo.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + + p = strjoina(root, "/usr/lib/systemd/system/static-instance@foo.service"); + assert_se(symlink("static-instance@.service", p) >= 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "static-instance@.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "static-instance@foo.service", &state) >= 0 && state == UNIT_FILE_STATIC); +} + +TEST(with_dropin) { + const char *p; + UnitFileState state; + InstallChange *changes = NULL; + size_t n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-1.service", &state) == -ENOENT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-2.service", &state) == -ENOENT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-3.service", &state) == -ENOENT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-4a.service", &state) == -ENOENT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-4b.service", &state) == -ENOENT); + + p = strjoina(root, "/usr/lib/systemd/system/with-dropin-1.service"); + assert_se(write_string_file(p, + "[Install]\n" + "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0); + + p = strjoina(root, "/usr/lib/systemd/system/with-dropin-1.service.d/dropin.conf"); + assert_se(write_string_file(p, + "[Install]\n" + "WantedBy=graphical.target\n", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755) >= 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-1.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/with-dropin-2.service"); + assert_se(write_string_file(p, + "[Install]\n" + "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0); + + p = strjoina(root, "/usr/lib/systemd/system/with-dropin-2.service.d/dropin.conf"); + assert_se(write_string_file(p, + "[Install]\n" + "WantedBy=graphical.target\n", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755) >= 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-2.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + + p = strjoina(root, "/usr/lib/systemd/system/with-dropin-3.service"); + assert_se(write_string_file(p, + "[Install]\n" + "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0); + + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/with-dropin-3.service.d/dropin.conf"); + assert_se(write_string_file(p, + "[Install]\n" + "WantedBy=graphical.target\n", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755) >= 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-3.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + + p = strjoina(root, "/usr/lib/systemd/system/with-dropin-4a.service"); + assert_se(write_string_file(p, + "[Install]\n" + "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0); + + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/with-dropin-4a.service.d/dropin.conf"); + assert_se(write_string_file(p, + "[Install]\n" + "Also=with-dropin-4b.service\n", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755) >= 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-4a.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + + p = strjoina(root, "/usr/lib/systemd/system/with-dropin-4b.service"); + assert_se(write_string_file(p, + "[Install]\n" + "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-4b.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + + assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("with-dropin-1.service"), &changes, &n_changes) == 1); + assert_se(n_changes == 2); + assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); + assert_se(changes[1].type == INSTALL_CHANGE_SYMLINK); + assert_se(streq(changes[0].source, "/usr/lib/systemd/system/with-dropin-1.service")); + assert_se(streq(changes[1].source, "/usr/lib/systemd/system/with-dropin-1.service")); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/with-dropin-1.service"); + assert_se(streq(changes[0].path, p)); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/graphical.target.wants/with-dropin-1.service"); + assert_se(streq(changes[1].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("with-dropin-2.service"), &changes, &n_changes) == 1); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-2.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(n_changes == 2); + assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); + assert_se(changes[1].type == INSTALL_CHANGE_SYMLINK); + assert_se(streq(changes[0].source, SYSTEM_CONFIG_UNIT_DIR"/with-dropin-2.service")); + assert_se(streq(changes[1].source, SYSTEM_CONFIG_UNIT_DIR"/with-dropin-2.service")); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/with-dropin-2.service"); + assert_se(streq(changes[0].path, p)); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/graphical.target.wants/with-dropin-2.service"); + assert_se(streq(changes[1].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("with-dropin-3.service"), &changes, &n_changes) == 1); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-3.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(n_changes == 2); + assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); + assert_se(changes[1].type == INSTALL_CHANGE_SYMLINK); + assert_se(streq(changes[0].source, "/usr/lib/systemd/system/with-dropin-3.service")); + assert_se(streq(changes[1].source, "/usr/lib/systemd/system/with-dropin-3.service")); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/with-dropin-3.service"); + assert_se(streq(changes[0].path, p)); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/graphical.target.wants/with-dropin-3.service"); + assert_se(streq(changes[1].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("with-dropin-4a.service"), &changes, &n_changes) == 2); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-3.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(n_changes == 2); + assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); + assert_se(changes[1].type == INSTALL_CHANGE_SYMLINK); + assert_se(streq(changes[0].source, "/usr/lib/systemd/system/with-dropin-4a.service")); + assert_se(streq(changes[1].source, "/usr/lib/systemd/system/with-dropin-4b.service")); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/with-dropin-4a.service"); + assert_se(streq(changes[0].path, p)); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/with-dropin-4b.service"); + assert_se(streq(changes[1].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-1.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-2.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-3.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-4a.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-4b.service", &state) >= 0 && state == UNIT_FILE_ENABLED); +} + +TEST(with_dropin_template) { + const char *p; + UnitFileState state; + InstallChange *changes = NULL; + size_t n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-1@.service", &state) == -ENOENT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-2@.service", &state) == -ENOENT); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-3@.service", &state) == -ENOENT); + + p = strjoina(root, "/usr/lib/systemd/system/with-dropin-1@.service"); + assert_se(write_string_file(p, + "[Install]\n" + "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0); + + p = strjoina(root, "/usr/lib/systemd/system/with-dropin-1@.service.d/dropin.conf"); + assert_se(write_string_file(p, + "[Install]\n" + "WantedBy=graphical.target\n", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755) >= 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-1@.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + + p = strjoina(root, "/usr/lib/systemd/system/with-dropin-2@.service"); + assert_se(write_string_file(p, + "[Install]\n" + "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0); + + p = strjoina(root, "/usr/lib/systemd/system/with-dropin-2@instance-1.service.d/dropin.conf"); + assert_se(write_string_file(p, + "[Install]\n" + "WantedBy=graphical.target\n", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755) >= 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-2@.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + + p = strjoina(root, "/usr/lib/systemd/system/with-dropin-3@.service"); + assert_se(write_string_file(p, + "[Install]\n" + "DefaultInstance=instance-1\n" + "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0); + + p = strjoina(root, "/usr/lib/systemd/system/with-dropin-3@.service.d/dropin.conf"); + assert_se(write_string_file(p, + "[Install]\n" + "DefaultInstance=instance-2\n", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755) >= 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-3@.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + + assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("with-dropin-1@instance-1.service"), &changes, &n_changes) == 1); + assert_se(n_changes == 2); + assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); + assert_se(changes[1].type == INSTALL_CHANGE_SYMLINK); + assert_se(streq(changes[0].source, "/usr/lib/systemd/system/with-dropin-1@.service")); + assert_se(streq(changes[1].source, "/usr/lib/systemd/system/with-dropin-1@.service")); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/with-dropin-1@instance-1.service"); + assert_se(streq(changes[0].path, p)); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/graphical.target.wants/with-dropin-1@instance-1.service"); + assert_se(streq(changes[1].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("with-dropin-2@instance-1.service"), &changes, &n_changes) == 1); + assert_se(n_changes == 2); + assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); + assert_se(changes[1].type == INSTALL_CHANGE_SYMLINK); + assert_se(streq(changes[0].source, "/usr/lib/systemd/system/with-dropin-2@.service")); + assert_se(streq(changes[1].source, "/usr/lib/systemd/system/with-dropin-2@.service")); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/with-dropin-2@instance-1.service"); + assert_se(streq(changes[0].path, p)); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/graphical.target.wants/with-dropin-2@instance-1.service"); + assert_se(streq(changes[1].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("with-dropin-2@instance-2.service"), &changes, &n_changes) == 1); + assert_se(n_changes == 1); + assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); + assert_se(streq(changes[0].source, "/usr/lib/systemd/system/with-dropin-2@.service")); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/with-dropin-2@instance-2.service"); + assert_se(streq(changes[0].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("with-dropin-3@.service"), &changes, &n_changes) == 1); + assert_se(n_changes == 1); + assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); + assert_se(streq(changes[0].source, "/usr/lib/systemd/system/with-dropin-3@.service")); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/with-dropin-3@instance-2.service"); + assert_se(streq(changes[0].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-1@instance-1.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-2@instance-1.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-2@instance-2.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-3@instance-1.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "with-dropin-3@instance-2.service", &state) >= 0 && state == UNIT_FILE_ENABLED); +} + +TEST(preset_multiple_instances) { + InstallChange *changes = NULL; + size_t n_changes = 0; + const char *p; + UnitFileState state; + + /* Set up template service files and preset file */ + p = strjoina(root, "/usr/lib/systemd/system/foo@.service"); + assert_se(write_string_file(p, + "[Install]\n" + "DefaultInstance=def\n" + "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "foo@.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + + p = strjoina(root, "/usr/lib/systemd/system-preset/test.preset"); + assert_se(write_string_file(p, + "enable foo@.service bar0 bar1 bartest\n" + "enable emptylist@.service\n" /* This line ensures the old functionality for templated unit still works */ + "disable *\n" , WRITE_STRING_FILE_CREATE) >= 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "foo@bar0.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + + /* Preset a single instantiated unit specified in the list */ + assert_se(unit_file_preset(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("foo@bar0.service"), UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "foo@bar0.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(n_changes == 1); + assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/foo@bar0.service"); + assert_se(streq(changes[0].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + assert_se(unit_file_disable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("foo@bar0.service"), &changes, &n_changes) >= 0); + assert_se(n_changes == 1); + assert_se(changes[0].type == INSTALL_CHANGE_UNLINK); + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/foo@bar0.service"); + assert_se(streq(changes[0].path, p)); + install_changes_free(changes, n_changes); + changes = NULL; n_changes = 0; + + /* Check for preset-all case, only instances on the list should be enabled, not including the default instance */ + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "foo@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "foo@bar1.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "foo@bartest.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + + assert_se(unit_file_preset_all(RUNTIME_SCOPE_SYSTEM, 0, root, UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0); + assert_se(n_changes > 0); + + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "foo@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "foo@bar0.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "foo@bar1.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "foo@bartest.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + + install_changes_free(changes, n_changes); +} + +static void verify_one( + const InstallInfo *i, + const char *alias, + int expected, + const char *updated_name) { + int r; + static const InstallInfo *last_info = NULL; + _cleanup_free_ char *alias2 = NULL; + + if (i != last_info) + log_info("-- %s --", (last_info = i)->name); + + r = unit_file_verify_alias(i, alias, &alias2, NULL, NULL); + log_info_errno(r, "alias %s ← %s: %d/%m (expected %d)%s%s%s", + i->name, alias, r, expected, + alias2 ? " [" : "", strempty(alias2), + alias2 ? "]" : ""); + assert_se(r == expected); + + /* This is test for "instance propagation". This propagation matters mostly for WantedBy= and + * RequiredBy= settings, and less so for Alias=. The only case where it should happen is when we have + * an Alias=alias@.service an instantiated template template@instance. In that case the instance name + * should be propagated into the alias as alias@instance. */ + assert_se(streq_ptr(alias2, updated_name)); +} + +TEST(verify_alias) { + const InstallInfo + plain_service = { .name = (char*) "plain.service" }, + bare_template = { .name = (char*) "template1@.service" }, + di_template = { .name = (char*) "template2@.service", + .default_instance = (char*) "di" }, + inst_template = { .name = (char*) "template3@inst.service" }, + di_inst_template = { .name = (char*) "template4@inst.service", + .default_instance = (char*) "di" }; + + verify_one(&plain_service, "alias.service", 0, NULL); + verify_one(&plain_service, "alias.socket", -EXDEV, NULL); + verify_one(&plain_service, "alias@.service", -EXDEV, NULL); + verify_one(&plain_service, "alias@inst.service", -EXDEV, NULL); + + /* Setting WantedBy= and RequiredBy= through Alias= is supported for the sake of backwards + * compatibility. */ + verify_one(&plain_service, "foo.target.wants/plain.service", 0, NULL); + verify_one(&plain_service, "foo.target.wants/plain.socket", -EXDEV, NULL); + verify_one(&plain_service, "foo.target.wants/plain@.service", -EXDEV, NULL); + verify_one(&plain_service, "foo.target.wants/service", -EXDEV, NULL); + verify_one(&plain_service, "foo.target.requires/plain.service", 0, NULL); + verify_one(&plain_service, "foo.target.requires/plain.socket", -EXDEV, NULL); + verify_one(&plain_service, "foo.target.requires/plain@.service", -EXDEV, NULL); + verify_one(&plain_service, "foo.target.requires/service", -EXDEV, NULL); + verify_one(&plain_service, "asdf.requires/plain.service", -EXDEV, NULL); /* invalid unit name component */ + /* The newly-added UpheldBy= (.upholds/) and other suffixes should be rejected */ + verify_one(&plain_service, "foo.target.upholds/plain.service", -EXDEV, NULL); + verify_one(&plain_service, "foo.target.upholds/plain.socket", -EXDEV, NULL); + verify_one(&plain_service, "foo.target.upholds/plain@.service", -EXDEV, NULL); + verify_one(&plain_service, "foo.target.upholds/service", -EXDEV, NULL); + verify_one(&plain_service, "foo.service/plain.service", -EXDEV, NULL); /* missing dir suffix */ + verify_one(&plain_service, "foo.target.conf/plain.service", -EXDEV, NULL); + + verify_one(&bare_template, "alias.service", -EXDEV, NULL); + verify_one(&bare_template, "alias.socket", -EXDEV, NULL); + verify_one(&bare_template, "alias@.socket", -EXDEV, NULL); + verify_one(&bare_template, "alias@inst.socket", -EXDEV, NULL); + /* A general alias alias@.service → template1@.service. */ + verify_one(&bare_template, "alias@.service", 0, NULL); + /* Only a specific instance is aliased, see the discussion in https://github.com/systemd/systemd/pull/13119. */ + verify_one(&bare_template, "alias@inst.service", 0, NULL); + verify_one(&bare_template, "foo.target.wants/plain.service", -EXDEV, NULL); + verify_one(&bare_template, "foo.target.wants/plain.socket", -EXDEV, NULL); + verify_one(&bare_template, "foo.target.wants/plain@.service", -EXDEV, NULL); + /* Name mismatch: we cannot allow this, because plain@foo.service would be pulled in by foo.target, + * but would not be resolveable on its own, since systemd doesn't know how to load the fragment. */ + verify_one(&bare_template, "foo.target.wants/plain@foo.service", -EXDEV, NULL); + verify_one(&bare_template, "foo.target.wants/template1@foo.service", 0, NULL); + verify_one(&bare_template, "foo.target.wants/service", -EXDEV, NULL); + verify_one(&bare_template, "foo.target.requires/plain.service", -EXDEV, NULL); + verify_one(&bare_template, "foo.target.requires/plain.socket", -EXDEV, NULL); + verify_one(&bare_template, "foo.target.requires/plain@.service", -EXDEV, NULL); /* instance missing */ + verify_one(&bare_template, "foo.target.requires/template1@inst.service", 0, NULL); + verify_one(&bare_template, "foo.target.requires/service", -EXDEV, NULL); + verify_one(&bare_template, "foo.target.conf/plain.service", -EXDEV, NULL); + verify_one(&bare_template, "FOO@.target.requires/plain@.service", -EXDEV, NULL); /* template name mismatch */ + verify_one(&bare_template, "FOO@inst.target.requires/plain@.service", -EXDEV, NULL); + verify_one(&bare_template, "FOO@inst.target.requires/plain@inst.service", -EXDEV, NULL); + verify_one(&bare_template, "FOO@.target.requires/template1@.service", 0, NULL); /* instance propagated */ + verify_one(&bare_template, "FOO@inst.target.requires/template1@.service", -EXDEV, NULL); /* instance missing */ + verify_one(&bare_template, "FOO@inst.target.requires/template1@inst.service", 0, NULL); /* instance provided */ + + verify_one(&di_template, "alias.service", -EXDEV, NULL); + verify_one(&di_template, "alias.socket", -EXDEV, NULL); + verify_one(&di_template, "alias@.socket", -EXDEV, NULL); + verify_one(&di_template, "alias@inst.socket", -EXDEV, NULL); + verify_one(&di_template, "alias@inst.service", 0, NULL); + verify_one(&di_template, "alias@.service", 0, NULL); + verify_one(&di_template, "alias@di.service", 0, NULL); + verify_one(&di_template, "foo.target.wants/plain.service", -EXDEV, NULL); + verify_one(&di_template, "foo.target.wants/plain.socket", -EXDEV, NULL); + verify_one(&di_template, "foo.target.wants/plain@.service", -EXDEV, NULL); + verify_one(&di_template, "foo.target.wants/plain@di.service", -EXDEV, NULL); + verify_one(&di_template, "foo.target.wants/template2@di.service", 0, NULL); + verify_one(&di_template, "foo.target.wants/service", -EXDEV, NULL); + verify_one(&di_template, "foo.target.requires/plain.service", -EXDEV, NULL); + verify_one(&di_template, "foo.target.requires/plain.socket", -EXDEV, NULL); + verify_one(&di_template, "foo.target.requires/plain@.service", -EXDEV, NULL); + verify_one(&di_template, "foo.target.requires/plain@di.service", -EXDEV, NULL); + verify_one(&di_template, "foo.target.requires/plain@foo.service", -EXDEV, NULL); + verify_one(&di_template, "foo.target.requires/template2@.service", -EXDEV, NULL); /* instance missing */ + verify_one(&di_template, "foo.target.requires/template2@di.service", 0, NULL); + verify_one(&di_template, "foo.target.requires/service", -EXDEV, NULL); + verify_one(&di_template, "foo.target.conf/plain.service", -EXDEV, NULL); + + verify_one(&inst_template, "alias.service", -EXDEV, NULL); + verify_one(&inst_template, "alias.socket", -EXDEV, NULL); + verify_one(&inst_template, "alias@.socket", -EXDEV, NULL); + verify_one(&inst_template, "alias@inst.socket", -EXDEV, NULL); + verify_one(&inst_template, "alias@inst.service", 0, NULL); + verify_one(&inst_template, "alias@.service", 0, "alias@inst.service"); + verify_one(&inst_template, "alias@di.service", -EXDEV, NULL); + verify_one(&inst_template, "bar.target.wants/plain.service", -EXDEV, NULL); + verify_one(&inst_template, "bar.target.wants/plain.socket", -EXDEV, NULL); + verify_one(&inst_template, "bar.target.wants/plain@.service", -EXDEV, NULL); + verify_one(&inst_template, "bar.target.wants/plain@di.service", -EXDEV, NULL); + verify_one(&inst_template, "bar.target.wants/plain@inst.service", -EXDEV, NULL); + verify_one(&inst_template, "bar.target.wants/template3@foo.service", -EXDEV, NULL); + verify_one(&inst_template, "bar.target.wants/template3@inst.service", 0, NULL); + verify_one(&inst_template, "bar.target.wants/service", -EXDEV, NULL); + verify_one(&inst_template, "bar.target.requires/plain.service", -EXDEV, NULL); + verify_one(&inst_template, "bar.target.requires/plain.socket", -EXDEV, NULL); + verify_one(&inst_template, "bar.target.requires/plain@.service", -EXDEV, NULL); + verify_one(&inst_template, "bar.target.requires/plain@di.service", -EXDEV, NULL); + verify_one(&inst_template, "bar.target.requires/plain@inst.service", -EXDEV, NULL); + verify_one(&inst_template, "bar.target.requires/template3@foo.service", -EXDEV, NULL); + verify_one(&inst_template, "bar.target.requires/template3@inst.service", 0, NULL); + verify_one(&inst_template, "bar.target.requires/service", -EXDEV, NULL); + verify_one(&inst_template, "bar.target.conf/plain.service", -EXDEV, NULL); + verify_one(&inst_template, "BAR@.target.requires/plain@.service", -EXDEV, NULL); /* template name mismatch */ + verify_one(&inst_template, "BAR@inst.target.requires/plain@.service", -EXDEV, NULL); + verify_one(&inst_template, "BAR@inst.target.requires/plain@inst.service", -EXDEV, NULL); + verify_one(&inst_template, "BAR@.target.requires/template3@.service", -EXDEV, NULL); /* instance missing */ + verify_one(&inst_template, "BAR@inst.target.requires/template3@.service", -EXDEV, NULL); /* instance missing */ + verify_one(&inst_template, "BAR@inst.target.requires/template3@inst.service", 0, NULL); /* instance provided */ + verify_one(&inst_template, "BAR@inst.target.requires/template3@ins2.service", -EXDEV, NULL); /* instance mismatch */ + + /* explicit alias overrides DefaultInstance */ + verify_one(&di_inst_template, "alias.service", -EXDEV, NULL); + verify_one(&di_inst_template, "alias.socket", -EXDEV, NULL); + verify_one(&di_inst_template, "alias@.socket", -EXDEV, NULL); + verify_one(&di_inst_template, "alias@inst.socket", -EXDEV, NULL); + verify_one(&di_inst_template, "alias@inst.service", 0, NULL); + verify_one(&di_inst_template, "alias@.service", 0, "alias@inst.service"); + verify_one(&di_inst_template, "alias@di.service", -EXDEV, NULL); + verify_one(&di_inst_template, "goo.target.wants/plain.service", -EXDEV, NULL); + verify_one(&di_inst_template, "goo.target.wants/plain.socket", -EXDEV, NULL); + verify_one(&di_inst_template, "goo.target.wants/plain@.service", -EXDEV, NULL); + verify_one(&di_inst_template, "goo.target.wants/plain@di.service", -EXDEV, NULL); + verify_one(&di_inst_template, "goo.target.wants/template4@foo.service", -EXDEV, NULL); + verify_one(&di_inst_template, "goo.target.wants/template4@inst.service", 0, NULL); + verify_one(&di_inst_template, "goo.target.wants/template4@di.service", -EXDEV, NULL); + verify_one(&di_inst_template, "goo.target.wants/service", -EXDEV, NULL); + verify_one(&di_inst_template, "goo.target.requires/plain.service", -EXDEV, NULL); + verify_one(&di_inst_template, "goo.target.requires/plain.socket", -EXDEV, NULL); + verify_one(&di_inst_template, "goo.target.requires/plain@.service", -EXDEV, NULL); + verify_one(&di_inst_template, "goo.target.requires/plain@di.service", -EXDEV, NULL); + verify_one(&di_inst_template, "goo.target.requires/plain@inst.service", -EXDEV, NULL); + verify_one(&di_inst_template, "goo.target.requires/template4@foo.service", -EXDEV, NULL); + verify_one(&di_inst_template, "goo.target.requires/template4@inst.service", 0, NULL); + verify_one(&di_inst_template, "goo.target.requires/service", -EXDEV, NULL); + verify_one(&di_inst_template, "goo.target.conf/plain.service", -EXDEV, NULL); +} + +static int intro(void) { + const char *p; + + assert_se(mkdtemp_malloc("/tmp/rootXXXXXX", &root) >= 0); + + p = strjoina(root, "/usr/lib/systemd/system/"); + assert_se(mkdir_p(p, 0755) >= 0); + + p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/"); + assert_se(mkdir_p(p, 0755) >= 0); + + p = strjoina(root, "/run/systemd/system/"); + assert_se(mkdir_p(p, 0755) >= 0); + + p = strjoina(root, "/opt/"); + assert_se(mkdir_p(p, 0755) >= 0); + + p = strjoina(root, "/usr/lib/systemd/system-preset/"); + assert_se(mkdir_p(p, 0755) >= 0); + + p = strjoina(root, "/usr/lib/systemd/system/multi-user.target"); + assert_se(write_string_file(p, "# pretty much empty", WRITE_STRING_FILE_CREATE) >= 0); + + p = strjoina(root, "/usr/lib/systemd/system/graphical.target"); + assert_se(write_string_file(p, "# pretty much empty", WRITE_STRING_FILE_CREATE) >= 0); + + return EXIT_SUCCESS; +} + + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/test/test-install.c b/src/test/test-install.c new file mode 100644 index 0000000..b54252e --- /dev/null +++ b/src/test/test-install.c @@ -0,0 +1,270 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdio.h> +#include <string.h> + +#include "install.h" +#include "tests.h" + +static void dump_changes(InstallChange *c, unsigned n) { + unsigned i; + + assert_se(n == 0 || c); + + for (i = 0; i < n; i++) { + if (c[i].type == INSTALL_CHANGE_UNLINK) + printf("rm '%s'\n", c[i].path); + else if (c[i].type == INSTALL_CHANGE_SYMLINK) + printf("ln -s '%s' '%s'\n", c[i].source, c[i].path); + } +} + +int main(int argc, char* argv[]) { + _cleanup_hashmap_free_ Hashmap *h = NULL; + UnitFileList *p; + int r; + const char *const files[] = { "avahi-daemon.service", NULL }; + const char *const files2[] = { "/home/lennart/test.service", NULL }; + InstallChange *changes = NULL; + size_t n_changes = 0; + UnitFileState state = 0; + + test_setup_logging(LOG_DEBUG); + + h = hashmap_new(&unit_file_list_hash_ops_free); + r = unit_file_get_list(RUNTIME_SCOPE_SYSTEM, NULL, h, NULL, NULL); + assert_se(r == 0); + + HASHMAP_FOREACH(p, h) { + UnitFileState s = _UNIT_FILE_STATE_INVALID; + + r = unit_file_get_state(RUNTIME_SCOPE_SYSTEM, NULL, basename(p->path), &s); + + assert_se((r < 0 && p->state == UNIT_FILE_BAD) || + (p->state == s)); + + fprintf(stderr, "%s (%s)\n", + p->path, + unit_file_state_to_string(p->state)); + } + + log_info("/*** enable **/"); + + r = unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, NULL, (char**) files, &changes, &n_changes); + assert_se(r >= 0); + + log_info("/*** enable2 **/"); + + r = unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, NULL, (char**) files, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + install_changes_free(changes, n_changes); + + r = unit_file_get_state(RUNTIME_SCOPE_SYSTEM, NULL, files[0], &state); + assert_se(r >= 0); + assert_se(state == UNIT_FILE_ENABLED); + + log_info("/*** disable ***/"); + changes = NULL; + n_changes = 0; + + r = unit_file_disable(RUNTIME_SCOPE_SYSTEM, 0, NULL, (char**) files, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + install_changes_free(changes, n_changes); + + r = unit_file_get_state(RUNTIME_SCOPE_SYSTEM, NULL, files[0], &state); + assert_se(r >= 0); + assert_se(state == UNIT_FILE_DISABLED); + + log_info("/*** mask ***/"); + changes = NULL; + n_changes = 0; + + r = unit_file_mask(RUNTIME_SCOPE_SYSTEM, 0, NULL, (char**) files, &changes, &n_changes); + assert_se(r >= 0); + log_info("/*** mask2 ***/"); + r = unit_file_mask(RUNTIME_SCOPE_SYSTEM, 0, NULL, (char**) files, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + install_changes_free(changes, n_changes); + + r = unit_file_get_state(RUNTIME_SCOPE_SYSTEM, NULL, files[0], &state); + assert_se(r >= 0); + assert_se(state == UNIT_FILE_MASKED); + + log_info("/*** unmask ***/"); + changes = NULL; + n_changes = 0; + + r = unit_file_unmask(RUNTIME_SCOPE_SYSTEM, 0, NULL, (char**) files, &changes, &n_changes); + assert_se(r >= 0); + log_info("/*** unmask2 ***/"); + r = unit_file_unmask(RUNTIME_SCOPE_SYSTEM, 0, NULL, (char**) files, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + install_changes_free(changes, n_changes); + + r = unit_file_get_state(RUNTIME_SCOPE_SYSTEM, NULL, files[0], &state); + assert_se(r >= 0); + assert_se(state == UNIT_FILE_DISABLED); + + log_info("/*** mask ***/"); + changes = NULL; + n_changes = 0; + + r = unit_file_mask(RUNTIME_SCOPE_SYSTEM, 0, NULL, (char**) files, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + install_changes_free(changes, n_changes); + + r = unit_file_get_state(RUNTIME_SCOPE_SYSTEM, NULL, files[0], &state); + assert_se(r >= 0); + assert_se(state == UNIT_FILE_MASKED); + + log_info("/*** disable ***/"); + changes = NULL; + n_changes = 0; + + r = unit_file_disable(RUNTIME_SCOPE_SYSTEM, 0, NULL, (char**) files, &changes, &n_changes); + assert_se(r >= 0); + log_info("/*** disable2 ***/"); + r = unit_file_disable(RUNTIME_SCOPE_SYSTEM, 0, NULL, (char**) files, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + install_changes_free(changes, n_changes); + + r = unit_file_get_state(RUNTIME_SCOPE_SYSTEM, NULL, files[0], &state); + assert_se(r >= 0); + assert_se(state == UNIT_FILE_MASKED); + + log_info("/*** umask ***/"); + changes = NULL; + n_changes = 0; + + r = unit_file_unmask(RUNTIME_SCOPE_SYSTEM, 0, NULL, (char**) files, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + install_changes_free(changes, n_changes); + + r = unit_file_get_state(RUNTIME_SCOPE_SYSTEM, NULL, files[0], &state); + assert_se(r >= 0); + assert_se(state == UNIT_FILE_DISABLED); + + log_info("/*** enable files2 ***/"); + changes = NULL; + n_changes = 0; + + r = unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, NULL, (char**) files2, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + install_changes_free(changes, n_changes); + + r = unit_file_get_state(RUNTIME_SCOPE_SYSTEM, NULL, basename(files2[0]), &state); + assert_se(r >= 0); + assert_se(state == UNIT_FILE_ENABLED); + + log_info("/*** disable files2 ***/"); + changes = NULL; + n_changes = 0; + + r = unit_file_disable(RUNTIME_SCOPE_SYSTEM, 0, NULL, STRV_MAKE(basename(files2[0])), &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + install_changes_free(changes, n_changes); + + r = unit_file_get_state(RUNTIME_SCOPE_SYSTEM, NULL, basename(files2[0]), &state); + assert_se(r < 0); + + log_info("/*** link files2 ***/"); + changes = NULL; + n_changes = 0; + + r = unit_file_link(RUNTIME_SCOPE_SYSTEM, 0, NULL, (char**) files2, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + install_changes_free(changes, n_changes); + + r = unit_file_get_state(RUNTIME_SCOPE_SYSTEM, NULL, basename(files2[0]), &state); + assert_se(r >= 0); + assert_se(state == UNIT_FILE_LINKED); + + log_info("/*** disable files2 ***/"); + changes = NULL; + n_changes = 0; + + r = unit_file_disable(RUNTIME_SCOPE_SYSTEM, 0, NULL, STRV_MAKE(basename(files2[0])), &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + install_changes_free(changes, n_changes); + + r = unit_file_get_state(RUNTIME_SCOPE_SYSTEM, NULL, basename(files2[0]), &state); + assert_se(r < 0); + + log_info("/*** link files2 ***/"); + changes = NULL; + n_changes = 0; + + r = unit_file_link(RUNTIME_SCOPE_SYSTEM, 0, NULL, (char**) files2, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + install_changes_free(changes, n_changes); + + r = unit_file_get_state(RUNTIME_SCOPE_SYSTEM, NULL, basename(files2[0]), &state); + assert_se(r >= 0); + assert_se(state == UNIT_FILE_LINKED); + + log_info("/*** reenable files2 ***/"); + changes = NULL; + n_changes = 0; + + r = unit_file_reenable(RUNTIME_SCOPE_SYSTEM, 0, NULL, (char**) files2, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + install_changes_free(changes, n_changes); + + r = unit_file_get_state(RUNTIME_SCOPE_SYSTEM, NULL, basename(files2[0]), &state); + assert_se(r >= 0); + assert_se(state == UNIT_FILE_ENABLED); + + log_info("/*** disable files2 ***/"); + changes = NULL; + n_changes = 0; + + r = unit_file_disable(RUNTIME_SCOPE_SYSTEM, 0, NULL, STRV_MAKE(basename(files2[0])), &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + install_changes_free(changes, n_changes); + + r = unit_file_get_state(RUNTIME_SCOPE_SYSTEM, NULL, basename(files2[0]), &state); + assert_se(r < 0); + log_info("/*** preset files ***/"); + changes = NULL; + n_changes = 0; + + r = unit_file_preset(RUNTIME_SCOPE_SYSTEM, 0, NULL, (char**) files, UNIT_FILE_PRESET_FULL, &changes, &n_changes); + assert_se(r >= 0); + + dump_changes(changes, n_changes); + install_changes_free(changes, n_changes); + + r = unit_file_get_state(RUNTIME_SCOPE_SYSTEM, NULL, basename(files[0]), &state); + assert_se(r >= 0); + assert_se(state == UNIT_FILE_ENABLED); + + return 0; +} diff --git a/src/test/test-io-util.c b/src/test/test-io-util.c new file mode 100644 index 0000000..fa3ef86 --- /dev/null +++ b/src/test/test-io-util.c @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "macro.h" +#include "tests.h" + +static void test_sparse_write_one(int fd, const char *buffer, size_t n) { + char check[n]; + + assert_se(lseek(fd, 0, SEEK_SET) == 0); + assert_se(ftruncate(fd, 0) >= 0); + assert_se(sparse_write(fd, buffer, n, 4) == (ssize_t) n); + + assert_se(lseek(fd, 0, SEEK_CUR) == (off_t) n); + assert_se(ftruncate(fd, n) >= 0); + + assert_se(lseek(fd, 0, SEEK_SET) == 0); + assert_se(read(fd, check, n) == (ssize_t) n); + + assert_se(memcmp(buffer, check, n) == 0); +} + +TEST(sparse_write) { + const char test_a[] = "test"; + const char test_b[] = "\0\0\0\0test\0\0\0\0"; + const char test_c[] = "\0\0test\0\0\0\0"; + const char test_d[] = "\0\0test\0\0\0test\0\0\0\0test\0\0\0\0\0test\0\0\0test\0\0\0\0test\0\0\0\0\0\0\0\0"; + const char test_e[] = "test\0\0\0\0test"; + _cleanup_close_ int fd = -EBADF; + char fn[] = "/tmp/sparseXXXXXX"; + + fd = mkostemp(fn, O_CLOEXEC); + assert_se(fd >= 0); + (void) unlink(fn); + + test_sparse_write_one(fd, test_a, sizeof(test_a)); + test_sparse_write_one(fd, test_b, sizeof(test_b)); + test_sparse_write_one(fd, test_c, sizeof(test_c)); + test_sparse_write_one(fd, test_d, sizeof(test_d)); + test_sparse_write_one(fd, test_e, sizeof(test_e)); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-ip-protocol-list.c b/src/test/test-ip-protocol-list.c new file mode 100644 index 0000000..dfff015 --- /dev/null +++ b/src/test/test-ip-protocol-list.c @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <netinet/in.h> + +#include "macro.h" +#include "ip-protocol-list.h" +#include "stdio-util.h" +#include "string-util.h" +#include "tests.h" + +static void test_int(int i) { + char str[DECIMAL_STR_MAX(int)]; + + assert_se(ip_protocol_from_name(ip_protocol_to_name(i)) == i); + + xsprintf(str, "%i", i); + assert_se(ip_protocol_from_name(ip_protocol_to_name(parse_ip_protocol(str))) == i); +} + +static void test_int_fail(int i, int error) { + char str[DECIMAL_STR_MAX(int)]; + + assert_se(!ip_protocol_to_name(i)); + + xsprintf(str, "%i", i); + assert_se(parse_ip_protocol(str) == error); +} + +static void test_str(const char *s) { + assert_se(streq(ip_protocol_to_name(ip_protocol_from_name(s)), s)); + assert_se(streq(ip_protocol_to_name(parse_ip_protocol(s)), s)); +} + +static void test_str_fail(const char *s, int error) { + assert_se(ip_protocol_from_name(s) == -EINVAL); + assert_se(parse_ip_protocol(s) == error); +} + +TEST(integer) { + test_int(IPPROTO_TCP); + test_int(IPPROTO_DCCP); + test_int_fail(-1, -ERANGE); + test_int_fail(1024 * 1024, -EPROTONOSUPPORT); +} + +TEST(string) { + test_str("sctp"); + test_str("udp"); + test_str_fail("hoge", -EINVAL); + test_str_fail("-1", -ERANGE); + test_str_fail("1000000000", -EPROTONOSUPPORT); +} + +TEST(parse_ip_protocol) { + assert_se(parse_ip_protocol("sctp") == IPPROTO_SCTP); + assert_se(parse_ip_protocol("ScTp") == IPPROTO_SCTP); + assert_se(parse_ip_protocol("ip") == IPPROTO_IP); + assert_se(parse_ip_protocol("") == IPPROTO_IP); + assert_se(parse_ip_protocol("1") == 1); + assert_se(parse_ip_protocol("0") == 0); + assert_se(parse_ip_protocol("-10") == -ERANGE); + assert_se(parse_ip_protocol("100000000") == -EPROTONOSUPPORT); +} + +TEST(parse_ip_protocol_full) { + assert_se(parse_ip_protocol_full("-1", true) == -ERANGE); + assert_se(parse_ip_protocol_full("0", true) == 0); + assert_se(parse_ip_protocol_full("11", true) == 11); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-ipcrm.c b/src/test/test-ipcrm.c new file mode 100644 index 0000000..238f0bf --- /dev/null +++ b/src/test/test-ipcrm.c @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "clean-ipc.h" +#include "errno-util.h" +#include "main-func.h" +#include "tests.h" +#include "user-util.h" + +static int run(int argc, char *argv[]) { + uid_t uid; + int r; + const char* name = argv[1] ?: NOBODY_USER_NAME; + + test_setup_logging(LOG_INFO); + + r = get_user_creds(&name, &uid, NULL, NULL, NULL, 0); + if (r == -ESRCH) + return log_tests_skipped("Failed to resolve user"); + if (r < 0) + return log_error_errno(r, "Failed to resolve \"%s\": %m", name); + + r = clean_ipc_by_uid(uid); + if (ERRNO_IS_PRIVILEGE(r)) + return log_tests_skipped("No privileges"); + + return r; +} + +DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/test/test-job-type.c b/src/test/test-job-type.c new file mode 100644 index 0000000..519fff7 --- /dev/null +++ b/src/test/test-job-type.c @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdio.h> + +#include "service.h" +#include "tests.h" +#include "unit.h" + +int main(int argc, char *argv[]) { + const ServiceState test_states[] = { SERVICE_DEAD, SERVICE_RUNNING }; + + test_setup_logging(LOG_DEBUG); + + for (size_t i = 0; i < ELEMENTSOF(test_states); i++) { + /* fake a unit */ + Service s = { + .meta.load_state = UNIT_LOADED, + .type = SERVICE_SIMPLE, + .state = test_states[i], + }; + Unit *u = UNIT(&s); + + printf("\nWith collapsing for service state %s\n" + "=========================================\n", service_state_to_string(s.state)); + for (JobType a = 0; a < _JOB_TYPE_MAX_MERGING; a++) { + for (JobType b = 0; b < _JOB_TYPE_MAX_MERGING; b++) { + + JobType ab = a; + bool merged_ab = job_type_merge_and_collapse(&ab, b, u) >= 0; + + if (!job_type_is_mergeable(a, b)) { + assert_se(!merged_ab); + printf("Not mergeable: %s + %s\n", job_type_to_string(a), job_type_to_string(b)); + continue; + } + + assert_se(merged_ab); + printf("%s + %s = %s\n", job_type_to_string(a), job_type_to_string(b), job_type_to_string(ab)); + + for (JobType c = 0; c < _JOB_TYPE_MAX_MERGING; c++) { + + /* Verify transitivity of mergeability of job types */ + assert_se(!job_type_is_mergeable(a, b) || + !job_type_is_mergeable(b, c) || + job_type_is_mergeable(a, c)); + + /* Verify that merged entries can be merged with the same entries + * they can be merged with separately */ + assert_se(!job_type_is_mergeable(a, c) || job_type_is_mergeable(ab, c)); + assert_se(!job_type_is_mergeable(b, c) || job_type_is_mergeable(ab, c)); + + /* Verify that if a merged with b is not mergeable with c, then + * either a or b is not mergeable with c either. */ + assert_se(job_type_is_mergeable(ab, c) || !job_type_is_mergeable(a, c) || !job_type_is_mergeable(b, c)); + + JobType bc = b; + if (job_type_merge_and_collapse(&bc, c, u) >= 0) { + + /* Verify associativity */ + + JobType ab_c = ab; + assert_se(job_type_merge_and_collapse(&ab_c, c, u) == 0); + + JobType bc_a = bc; + assert_se(job_type_merge_and_collapse(&bc_a, a, u) == 0); + + JobType a_bc = a; + assert_se(job_type_merge_and_collapse(&a_bc, bc, u) == 0); + + assert_se(ab_c == bc_a); + assert_se(ab_c == a_bc); + + printf("%s + %s + %s = %s\n", job_type_to_string(a), job_type_to_string(b), job_type_to_string(c), job_type_to_string(ab_c)); + } + } + } + } + } + + return 0; +} diff --git a/src/test/test-journal-importer.c b/src/test/test-journal-importer.c new file mode 100644 index 0000000..ec9e49e --- /dev/null +++ b/src/test/test-journal-importer.c @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "alloc-util.h" +#include "log.h" +#include "journal-importer.h" +#include "path-util.h" +#include "string-util.h" +#include "tests.h" + +static void assert_iovec_entry(const struct iovec *iovec, const char* content) { + assert_se(strlen(content) == iovec->iov_len); + assert_se(memcmp(content, iovec->iov_base, iovec->iov_len) == 0); +} + +#define COREDUMP_PROC_GROUP \ + "COREDUMP_PROC_CGROUP=1:name=systemd:/\n" \ + "0::/user.slice/user-1002.slice/user@1002.service/gnome-terminal-server.service\n" + +TEST(basic_parsing) { + _cleanup_(journal_importer_cleanup) JournalImporter imp = JOURNAL_IMPORTER_INIT(-1); + _cleanup_free_ char *journal_data_path = NULL; + int r; + + assert_se(get_testdata_dir("journal-data/journal-1.txt", &journal_data_path) >= 0); + imp.fd = open(journal_data_path, O_RDONLY|O_CLOEXEC); + assert_se(imp.fd >= 0); + + do + r = journal_importer_process_data(&imp); + while (r == 0 && !journal_importer_eof(&imp)); + assert_se(r == 1); + + /* We read one entry, so we should get EOF on next read, but not yet */ + assert_se(!journal_importer_eof(&imp)); + + assert_se(imp.iovw.count == 6); + assert_iovec_entry(&imp.iovw.iovec[0], "_BOOT_ID=1531fd22ec84429e85ae888b12fadb91"); + assert_iovec_entry(&imp.iovw.iovec[1], "_TRANSPORT=journal"); + assert_iovec_entry(&imp.iovw.iovec[2], COREDUMP_PROC_GROUP); + assert_iovec_entry(&imp.iovw.iovec[3], "COREDUMP_RLIMIT=-1"); + assert_iovec_entry(&imp.iovw.iovec[4], COREDUMP_PROC_GROUP); + assert_iovec_entry(&imp.iovw.iovec[5], "_SOURCE_REALTIME_TIMESTAMP=1478389147837945"); + + /* Let's check if we get EOF now */ + r = journal_importer_process_data(&imp); + assert_se(r == 0); + assert_se(journal_importer_eof(&imp)); +} + +TEST(bad_input) { + _cleanup_(journal_importer_cleanup) JournalImporter imp = JOURNAL_IMPORTER_INIT(-1); + _cleanup_free_ char *journal_data_path = NULL; + int r; + + assert_se(get_testdata_dir("journal-data/journal-1.txt", &journal_data_path) >= 0); + imp.fd = open(journal_data_path, O_RDONLY|O_CLOEXEC); + assert_se(imp.fd >= 0); + + do + r = journal_importer_process_data(&imp); + while (!journal_importer_eof(&imp)); + assert_se(r == 0); /* If we don't have enough input, 0 is returned */ + + assert_se(journal_importer_eof(&imp)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-json.c b/src/test/test-json.c new file mode 100644 index 0000000..c120a70 --- /dev/null +++ b/src/test/test-json.c @@ -0,0 +1,816 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <float.h> + +#include "alloc-util.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "json-internal.h" +#include "json.h" +#include "math-util.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" + +static void test_tokenizer_one(const char *data, ...) { + unsigned line = 0, column = 0; + void *state = NULL; + va_list ap; + + _cleanup_free_ char *cdata = NULL; + assert_se(cdata = cescape(data)); + log_info("/* %s data=\"%s\" */", __func__, cdata); + + va_start(ap, data); + + for (;;) { + unsigned token_line, token_column; + _cleanup_free_ char *str = NULL; + JsonValue v = JSON_VALUE_NULL; + int t, tt; + + t = json_tokenize(&data, &str, &v, &token_line, &token_column, &state, &line, &column); + tt = va_arg(ap, int); + + assert_se(t == tt); + + if (t == JSON_TOKEN_END || t < 0) + break; + + else if (t == JSON_TOKEN_STRING) { + const char *nn; + + nn = va_arg(ap, const char *); + assert_se(streq_ptr(nn, str)); + + } else if (t == JSON_TOKEN_REAL) { + double d; + + d = va_arg(ap, double); + + assert_se(fabs(d - v.real) < 1e-10 || + fabs((d - v.real) / v.real) < 1e-10); + + } else if (t == JSON_TOKEN_INTEGER) { + int64_t i; + + i = va_arg(ap, int64_t); + assert_se(i == v.integer); + + } else if (t == JSON_TOKEN_UNSIGNED) { + uint64_t u; + + u = va_arg(ap, uint64_t); + assert_se(u == v.unsig); + + } else if (t == JSON_TOKEN_BOOLEAN) { + bool b; + + b = va_arg(ap, int); + assert_se(b == v.boolean); + } + } + + va_end(ap); +} + +typedef void (*Test)(JsonVariant *); + +static void test_variant_one(const char *data, Test test) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL; + _cleanup_free_ char *s = NULL; + int r; + + _cleanup_free_ char *cdata; + assert_se(cdata = cescape(data)); + log_info("/* %s data=\"%s\" */", __func__, cdata); + + r = json_parse(data, 0, &v, NULL, NULL); + assert_se(r == 0); + assert_se(v); + + r = json_variant_format(v, 0, &s); + assert_se(r >= 0); + assert_se(s); + assert_se((size_t) r == strlen(s)); + + log_info("formatted normally: %s", s); + + r = json_parse(data, JSON_PARSE_SENSITIVE, &w, NULL, NULL); + assert_se(r == 0); + assert_se(w); + assert_se(json_variant_has_type(v, json_variant_type(w))); + assert_se(json_variant_has_type(w, json_variant_type(v))); + assert_se(json_variant_equal(v, w)); + + s = mfree(s); + w = json_variant_unref(w); + + r = json_variant_format(v, JSON_FORMAT_PRETTY, &s); + assert_se(r >= 0); + assert_se(s); + assert_se((size_t) r == strlen(s)); + + log_info("formatted prettily:\n%s", s); + + r = json_parse(data, 0, &w, NULL, NULL); + assert_se(r == 0); + assert_se(w); + + assert_se(json_variant_has_type(v, json_variant_type(w))); + assert_se(json_variant_has_type(w, json_variant_type(v))); + assert_se(json_variant_equal(v, w)); + + s = mfree(s); + r = json_variant_format(v, JSON_FORMAT_COLOR, &s); + assert_se(r >= 0); + assert_se(s); + assert_se((size_t) r == strlen(s)); + printf("Normal with color: %s\n", s); + + s = mfree(s); + r = json_variant_format(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, &s); + assert_se(r >= 0); + assert_se(s); + assert_se((size_t) r == strlen(s)); + printf("Pretty with color:\n%s\n", s); + + if (test) + test(v); +} + +static void test_1(JsonVariant *v) { + JsonVariant *p, *q; + unsigned i; + + log_info("/* %s */", __func__); + + /* 3 keys + 3 values */ + assert_se(json_variant_elements(v) == 6); + + /* has k */ + p = json_variant_by_key(v, "k"); + assert_se(p && json_variant_type(p) == JSON_VARIANT_STRING); + + /* k equals v */ + assert_se(streq(json_variant_string(p), "v")); + + /* has foo */ + p = json_variant_by_key(v, "foo"); + assert_se(p && json_variant_type(p) == JSON_VARIANT_ARRAY && json_variant_elements(p) == 3); + + /* check foo[0] = 1, foo[1] = 2, foo[2] = 3 */ + for (i = 0; i < 3; ++i) { + q = json_variant_by_index(p, i); + assert_se(q && json_variant_type(q) == JSON_VARIANT_UNSIGNED && json_variant_unsigned(q) == (i+1)); + assert_se(q && json_variant_has_type(q, JSON_VARIANT_INTEGER) && json_variant_integer(q) == (i+1)); + } + + /* has bar */ + p = json_variant_by_key(v, "bar"); + assert_se(p && json_variant_type(p) == JSON_VARIANT_OBJECT && json_variant_elements(p) == 2); + + /* zap is null */ + q = json_variant_by_key(p, "zap"); + assert_se(q && json_variant_type(q) == JSON_VARIANT_NULL); +} + +static void test_2(JsonVariant *v) { + JsonVariant *p, *q; + + log_info("/* %s */", __func__); + + /* 2 keys + 2 values */ + assert_se(json_variant_elements(v) == 4); + + /* has mutant */ + p = json_variant_by_key(v, "mutant"); + assert_se(p && json_variant_type(p) == JSON_VARIANT_ARRAY && json_variant_elements(p) == 4); + + /* mutant[0] == 1 */ + q = json_variant_by_index(p, 0); + assert_se(q && json_variant_type(q) == JSON_VARIANT_UNSIGNED && json_variant_unsigned(q) == 1); + assert_se(q && json_variant_has_type(q, JSON_VARIANT_INTEGER) && json_variant_integer(q) == 1); + + /* mutant[1] == null */ + q = json_variant_by_index(p, 1); + assert_se(q && json_variant_type(q) == JSON_VARIANT_NULL); + + /* mutant[2] == "1" */ + q = json_variant_by_index(p, 2); + assert_se(q && json_variant_type(q) == JSON_VARIANT_STRING && streq(json_variant_string(q), "1")); + + /* mutant[3] == JSON_VARIANT_OBJECT */ + q = json_variant_by_index(p, 3); + assert_se(q && json_variant_type(q) == JSON_VARIANT_OBJECT && json_variant_elements(q) == 2); + + /* has 1 */ + p = json_variant_by_key(q, "1"); + assert_se(p && json_variant_type(p) == JSON_VARIANT_ARRAY && json_variant_elements(p) == 2); + + /* "1"[0] == 1 */ + q = json_variant_by_index(p, 0); + assert_se(q && json_variant_type(q) == JSON_VARIANT_UNSIGNED && json_variant_unsigned(q) == 1); + assert_se(q && json_variant_has_type(q, JSON_VARIANT_INTEGER) && json_variant_integer(q) == 1); + + /* "1"[1] == "1" */ + q = json_variant_by_index(p, 1); + assert_se(q && json_variant_type(q) == JSON_VARIANT_STRING && streq(json_variant_string(q), "1")); + + /* has thisisaverylongproperty */ + p = json_variant_by_key(v, "thisisaverylongproperty"); + assert_se(p && json_variant_type(p) == JSON_VARIANT_REAL && fabs(json_variant_real(p) - 1.27) < 0.001); +} + +static void test_zeroes(JsonVariant *v) { + /* Make sure zero is how we expect it. */ + log_info("/* %s */", __func__); + + assert_se(json_variant_elements(v) == 13); + + for (size_t i = 0; i < json_variant_elements(v); i++) { + JsonVariant *w; + size_t j; + + assert_se(w = json_variant_by_index(v, i)); + + assert_se(json_variant_integer(w) == 0); + assert_se(json_variant_unsigned(w) == 0U); + + assert_se(iszero_safe(json_variant_real(w))); + + assert_se(json_variant_is_integer(w)); + assert_se(json_variant_is_unsigned(w)); + assert_se(json_variant_is_real(w)); + assert_se(json_variant_is_number(w)); + + assert_se(!json_variant_is_negative(w)); + + assert_se(IN_SET(json_variant_type(w), JSON_VARIANT_INTEGER, JSON_VARIANT_UNSIGNED, JSON_VARIANT_REAL)); + + for (j = 0; j < json_variant_elements(v); j++) { + JsonVariant *q; + + assert_se(q = json_variant_by_index(v, j)); + + assert_se(json_variant_equal(w, q)); + } + } +} + +TEST(build) { + _cleanup_(json_variant_unrefp) JsonVariant *a = NULL, *b = NULL; + _cleanup_free_ char *s = NULL, *t = NULL; + + assert_se(json_build(&a, JSON_BUILD_STRING("hallo")) >= 0); + assert_se(json_build(&b, JSON_BUILD_LITERAL(" \"hallo\" ")) >= 0); + assert_se(json_variant_equal(a, b)); + + b = json_variant_unref(b); + + assert_se(json_build(&b, JSON_BUILD_VARIANT(a)) >= 0); + assert_se(json_variant_equal(a, b)); + + b = json_variant_unref(b); + assert_se(json_build(&b, JSON_BUILD_STRING("pief")) >= 0); + assert_se(!json_variant_equal(a, b)); + + a = json_variant_unref(a); + b = json_variant_unref(b); + + assert_se(json_build(&a, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("one", JSON_BUILD_INTEGER(7)), + JSON_BUILD_PAIR("two", JSON_BUILD_REAL(2.0)), + JSON_BUILD_PAIR("three", JSON_BUILD_INTEGER(0)))) >= 0); + + assert_se(json_build(&b, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("two", JSON_BUILD_INTEGER(2)), + JSON_BUILD_PAIR("three", JSON_BUILD_REAL(0)), + JSON_BUILD_PAIR("one", JSON_BUILD_REAL(7)))) >= 0); + + assert_se(json_variant_equal(a, b)); + + a = json_variant_unref(a); + b = json_variant_unref(b); + + const char* arr_1234[] = {"one", "two", "three", "four", NULL}; + assert_se(json_build(&a, JSON_BUILD_ARRAY(JSON_BUILD_OBJECT(JSON_BUILD_PAIR("x", JSON_BUILD_BOOLEAN(true)), + JSON_BUILD_PAIR("y", JSON_BUILD_OBJECT(JSON_BUILD_PAIR("this", JSON_BUILD_NULL)))), + JSON_BUILD_VARIANT(NULL), + JSON_BUILD_LITERAL(NULL), + JSON_BUILD_STRING(NULL), + JSON_BUILD_NULL, + JSON_BUILD_INTEGER(77), + JSON_BUILD_ARRAY(JSON_BUILD_VARIANT(JSON_VARIANT_STRING_CONST("foobar")), + JSON_BUILD_VARIANT(JSON_VARIANT_STRING_CONST("zzz"))), + JSON_BUILD_STRV((char**) arr_1234))) >= 0); + + assert_se(json_variant_format(a, 0, &s) >= 0); + log_info("GOT: %s", s); + assert_se(json_parse(s, 0, &b, NULL, NULL) >= 0); + assert_se(json_variant_equal(a, b)); + + a = json_variant_unref(a); + b = json_variant_unref(b); + + assert_se(json_build(&a, JSON_BUILD_REAL(M_PI)) >= 0); + + s = mfree(s); + assert_se(json_variant_format(a, 0, &s) >= 0); + log_info("GOT: %s", s); + assert_se(json_parse(s, 0, &b, NULL, NULL) >= 0); + assert_se(json_variant_format(b, 0, &t) >= 0); + log_info("GOT: %s", t); + + assert_se(streq(s, t)); + + a = json_variant_unref(a); + b = json_variant_unref(b); + + assert_se(json_build(&a, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("x", JSON_BUILD_STRING("y")), + JSON_BUILD_PAIR("z", JSON_BUILD_CONST_STRING("a")), + JSON_BUILD_PAIR("b", JSON_BUILD_CONST_STRING("c")) + )) >= 0); + + assert_se(json_build(&b, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("x", JSON_BUILD_STRING("y")), + JSON_BUILD_PAIR_CONDITION(false, "p", JSON_BUILD_STRING("q")), + JSON_BUILD_PAIR_CONDITION(true, "z", JSON_BUILD_CONST_STRING("a")), + JSON_BUILD_PAIR_CONDITION(false, "j", JSON_BUILD_ARRAY(JSON_BUILD_STRING("k"), JSON_BUILD_CONST_STRING("u"), JSON_BUILD_CONST_STRING("i"))), + JSON_BUILD_PAIR("b", JSON_BUILD_CONST_STRING("c")) + )) >= 0); + + assert_se(json_variant_equal(a, b)); +} + +TEST(json_parse_file_empty) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + assert_se(fopen_unlocked("/dev/null", "re", &f) >= 0); + assert_se(json_parse_file(f, "waldo", 0, &v, NULL, NULL) == -ENODATA); + assert_se(v == NULL); +} + +TEST(json_parse_file_invalid) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + assert_se(f = fmemopen_unlocked((void*) "kookoo", 6, "r")); + assert_se(json_parse_file(f, "waldo", 0, &v, NULL, NULL) == -EINVAL); + assert_se(v == NULL); +} + +TEST(source) { + static const char data[] = + "\n" + "\n" + "{\n" + "\"foo\" : \"bar\", \n" + "\"qüüx\" : [ 1, 2, 3,\n" + "4,\n" + "5 ],\n" + "\"miep\" : { \"hallo\" : 1 },\n" + "\n" + "\"zzzzzz\" \n" + ":\n" + "[ true, \n" + "false, 7.5, {} ]\n" + "}\n"; + + _cleanup_fclose_ FILE *f = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + printf("--- original begin ---\n" + "%s" + "--- original end ---\n", data); + + assert_se(f = fmemopen_unlocked((void*) data, strlen(data), "r")); + + assert_se(json_parse_file(f, "waldo", 0, &v, NULL, NULL) >= 0); + + printf("--- non-pretty begin ---\n"); + json_variant_dump(v, 0, stdout, NULL); + printf("\n--- non-pretty end ---\n"); + + printf("--- pretty begin ---\n"); + json_variant_dump(v, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR|JSON_FORMAT_SOURCE, stdout, NULL); + printf("--- pretty end ---\n"); +} + +TEST(depth) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + int r; + + v = JSON_VARIANT_STRING_CONST("start"); + + /* Let's verify that the maximum depth checks work */ + + for (unsigned i = 0;; i++) { + _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; + + assert_se(i <= UINT16_MAX); + if (i & 1) + r = json_variant_new_array(&w, &v, 1); + else + r = json_variant_new_object(&w, (JsonVariant*[]) { JSON_VARIANT_STRING_CONST("key"), v }, 2); + if (r == -ELNRNG) { + log_info("max depth at %u", i); + break; + } +#if HAS_FEATURE_MEMORY_SANITIZER + /* msan doesn't like the stack nesting to be too deep. Let's quit early. */ + if (i >= 128) { + log_info("quitting early at depth %u", i); + break; + } +#endif + + assert_se(r >= 0); + + json_variant_unref(v); + v = TAKE_PTR(w); + } + + json_variant_dump(v, 0, stdout, NULL); + fputs("\n", stdout); +} + +TEST(normalize) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL; + _cleanup_free_ char *t = NULL; + + assert_se(json_build(&v, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("b", JSON_BUILD_STRING("x")), + JSON_BUILD_PAIR("c", JSON_BUILD_CONST_STRING("y")), + JSON_BUILD_PAIR("a", JSON_BUILD_CONST_STRING("z")))) >= 0); + + assert_se(!json_variant_is_sorted(v)); + assert_se(!json_variant_is_normalized(v)); + + assert_se(json_variant_format(v, 0, &t) >= 0); + assert_se(streq(t, "{\"b\":\"x\",\"c\":\"y\",\"a\":\"z\"}")); + t = mfree(t); + + assert_se(json_build(&w, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("bar", JSON_BUILD_STRING("zzz")), + JSON_BUILD_PAIR("foo", JSON_BUILD_VARIANT(v)))) >= 0); + + assert_se(json_variant_is_sorted(w)); + assert_se(!json_variant_is_normalized(w)); + + assert_se(json_variant_format(w, 0, &t) >= 0); + assert_se(streq(t, "{\"bar\":\"zzz\",\"foo\":{\"b\":\"x\",\"c\":\"y\",\"a\":\"z\"}}")); + t = mfree(t); + + assert_se(json_variant_sort(&v) >= 0); + assert_se(json_variant_is_sorted(v)); + assert_se(json_variant_is_normalized(v)); + + assert_se(json_variant_format(v, 0, &t) >= 0); + assert_se(streq(t, "{\"a\":\"z\",\"b\":\"x\",\"c\":\"y\"}")); + t = mfree(t); + + assert_se(json_variant_normalize(&w) >= 0); + assert_se(json_variant_is_sorted(w)); + assert_se(json_variant_is_normalized(w)); + + assert_se(json_variant_format(w, 0, &t) >= 0); + assert_se(streq(t, "{\"bar\":\"zzz\",\"foo\":{\"a\":\"z\",\"b\":\"x\",\"c\":\"y\"}}")); + t = mfree(t); +} + +TEST(bisect) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + /* Tests the bisection logic in json_variant_by_key() */ + + for (char c = 'z'; c >= 'a'; c--) { + + if ((c % 3) == 0) + continue; + + _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; + assert_se(json_variant_new_stringn(&w, (char[4]) { '<', c, c, '>' }, 4) >= 0); + assert_se(json_variant_set_field(&v, (char[2]) { c, 0 }, w) >= 0); + } + + json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL); + + assert_se(!json_variant_is_sorted(v)); + assert_se(!json_variant_is_normalized(v)); + assert_se(json_variant_normalize(&v) >= 0); + assert_se(json_variant_is_sorted(v)); + assert_se(json_variant_is_normalized(v)); + + json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL); + + for (char c = 'a'; c <= 'z'; c++) { + JsonVariant *k; + const char *z; + + k = json_variant_by_key(v, (char[2]) { c, 0 }); + assert_se(!k == ((c % 3) == 0)); + + if (!k) + continue; + + assert_se(json_variant_is_string(k)); + + z = (char[5]){ '<', c, c, '>', 0}; + assert_se(streq(json_variant_string(k), z)); + } +} + +static void test_float_match(JsonVariant *v) { + const double delta = 0.0001; + + assert_se(json_variant_is_array(v)); + assert_se(json_variant_elements(v) == 11); + assert_se(fabs(1.0 - (DBL_MIN / json_variant_real(json_variant_by_index(v, 0)))) <= delta); + assert_se(fabs(1.0 - (DBL_MAX / json_variant_real(json_variant_by_index(v, 1)))) <= delta); + assert_se(json_variant_is_null(json_variant_by_index(v, 2))); /* nan is not supported by json → null */ + assert_se(json_variant_is_null(json_variant_by_index(v, 3))); /* +inf is not supported by json → null */ + assert_se(json_variant_is_null(json_variant_by_index(v, 4))); /* -inf is not supported by json → null */ + assert_se(json_variant_is_null(json_variant_by_index(v, 5)) || + fabs(1.0 - (HUGE_VAL / json_variant_real(json_variant_by_index(v, 5)))) <= delta); /* HUGE_VAL might be +inf, but might also be something else */ + assert_se(json_variant_is_real(json_variant_by_index(v, 6)) && + json_variant_is_integer(json_variant_by_index(v, 6)) && + json_variant_integer(json_variant_by_index(v, 6)) == 0); + assert_se(json_variant_is_real(json_variant_by_index(v, 7)) && + json_variant_is_integer(json_variant_by_index(v, 7)) && + json_variant_integer(json_variant_by_index(v, 7)) == 10); + assert_se(json_variant_is_real(json_variant_by_index(v, 8)) && + json_variant_is_integer(json_variant_by_index(v, 8)) && + json_variant_integer(json_variant_by_index(v, 8)) == -10); + assert_se(json_variant_is_real(json_variant_by_index(v, 9)) && + !json_variant_is_integer(json_variant_by_index(v, 9))); + assert_se(fabs(1.0 - (DBL_MIN / 2 / json_variant_real(json_variant_by_index(v, 9)))) <= delta); + assert_se(json_variant_is_real(json_variant_by_index(v, 10)) && + !json_variant_is_integer(json_variant_by_index(v, 10))); + assert_se(fabs(1.0 - (-DBL_MIN / 2 / json_variant_real(json_variant_by_index(v, 10)))) <= delta); +} + +TEST(float) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL; + _cleanup_free_ char *text = NULL; + + assert_se(json_build(&v, JSON_BUILD_ARRAY( + JSON_BUILD_REAL(DBL_MIN), + JSON_BUILD_REAL(DBL_MAX), + JSON_BUILD_REAL(NAN), + JSON_BUILD_REAL(INFINITY), + JSON_BUILD_REAL(-INFINITY), + JSON_BUILD_REAL(HUGE_VAL), + JSON_BUILD_REAL(0), + JSON_BUILD_REAL(10), + JSON_BUILD_REAL(-10), + JSON_BUILD_REAL(DBL_MIN / 2), + JSON_BUILD_REAL(-DBL_MIN / 2))) >= 0); + + json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL); + + test_float_match(v); + + assert_se(json_variant_format(v, 0, &text) >= 0); + assert_se(json_parse(text, 0, &w, NULL, NULL) >= 0); + + json_variant_dump(w, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL); + + test_float_match(w); +} + +static void test_equal_text(JsonVariant *v, const char *text) { + _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; + + assert_se(json_parse(text, 0, &w, NULL, NULL) >= 0); + assert_se(json_variant_equal(v, w) || (!v && json_variant_is_null(w))); +} + +TEST(set_field) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + test_equal_text(v, "null"); + assert_se(json_variant_set_field(&v, "foo", NULL) >= 0); + test_equal_text(v, "{\"foo\" : null}"); + assert_se(json_variant_set_field(&v, "bar", JSON_VARIANT_STRING_CONST("quux")) >= 0); + test_equal_text(v, "{\"foo\" : null, \"bar\" : \"quux\"}"); + assert_se(json_variant_set_field(&v, "foo", JSON_VARIANT_STRING_CONST("quux2")) >= 0); + test_equal_text(v, "{\"foo\" : \"quux2\", \"bar\" : \"quux\"}"); + assert_se(json_variant_set_field(&v, "bar", NULL) >= 0); + test_equal_text(v, "{\"foo\" : \"quux2\", \"bar\" : null}"); +} + +TEST(tokenizer) { + test_tokenizer_one("x", -EINVAL); + test_tokenizer_one("", JSON_TOKEN_END); + test_tokenizer_one(" ", JSON_TOKEN_END); + test_tokenizer_one("0", JSON_TOKEN_UNSIGNED, (uint64_t) 0, JSON_TOKEN_END); + test_tokenizer_one("-0", JSON_TOKEN_INTEGER, (int64_t) 0, JSON_TOKEN_END); + test_tokenizer_one("1234", JSON_TOKEN_UNSIGNED, (uint64_t) 1234, JSON_TOKEN_END); + test_tokenizer_one("-1234", JSON_TOKEN_INTEGER, (int64_t) -1234, JSON_TOKEN_END); + test_tokenizer_one("18446744073709551615", JSON_TOKEN_UNSIGNED, (uint64_t) UINT64_MAX, JSON_TOKEN_END); + test_tokenizer_one("-9223372036854775808", JSON_TOKEN_INTEGER, (int64_t) INT64_MIN, JSON_TOKEN_END); + test_tokenizer_one("18446744073709551616", JSON_TOKEN_REAL, (double) 18446744073709551616.0L, JSON_TOKEN_END); + test_tokenizer_one("-9223372036854775809", JSON_TOKEN_REAL, (double) -9223372036854775809.0L, JSON_TOKEN_END); + test_tokenizer_one("-1234", JSON_TOKEN_INTEGER, (int64_t) -1234, JSON_TOKEN_END); + test_tokenizer_one("3.141", JSON_TOKEN_REAL, (double) 3.141, JSON_TOKEN_END); + test_tokenizer_one("0.0", JSON_TOKEN_REAL, (double) 0.0, JSON_TOKEN_END); + test_tokenizer_one("7e3", JSON_TOKEN_REAL, (double) 7e3, JSON_TOKEN_END); + test_tokenizer_one("-7e-3", JSON_TOKEN_REAL, (double) -7e-3, JSON_TOKEN_END); + test_tokenizer_one("true", JSON_TOKEN_BOOLEAN, true, JSON_TOKEN_END); + test_tokenizer_one("false", JSON_TOKEN_BOOLEAN, false, JSON_TOKEN_END); + test_tokenizer_one("null", JSON_TOKEN_NULL, JSON_TOKEN_END); + test_tokenizer_one("{}", JSON_TOKEN_OBJECT_OPEN, JSON_TOKEN_OBJECT_CLOSE, JSON_TOKEN_END); + test_tokenizer_one("\t {\n} \n", JSON_TOKEN_OBJECT_OPEN, JSON_TOKEN_OBJECT_CLOSE, JSON_TOKEN_END); + test_tokenizer_one("[]", JSON_TOKEN_ARRAY_OPEN, JSON_TOKEN_ARRAY_CLOSE, JSON_TOKEN_END); + test_tokenizer_one("\t [] \n\n", JSON_TOKEN_ARRAY_OPEN, JSON_TOKEN_ARRAY_CLOSE, JSON_TOKEN_END); + test_tokenizer_one("\"\"", JSON_TOKEN_STRING, "", JSON_TOKEN_END); + test_tokenizer_one("\"foo\"", JSON_TOKEN_STRING, "foo", JSON_TOKEN_END); + test_tokenizer_one("\"foo\\nfoo\"", JSON_TOKEN_STRING, "foo\nfoo", JSON_TOKEN_END); + test_tokenizer_one("{\"foo\" : \"bar\"}", JSON_TOKEN_OBJECT_OPEN, JSON_TOKEN_STRING, "foo", JSON_TOKEN_COLON, JSON_TOKEN_STRING, "bar", JSON_TOKEN_OBJECT_CLOSE, JSON_TOKEN_END); + test_tokenizer_one("{\"foo\" : [true, false]}", JSON_TOKEN_OBJECT_OPEN, JSON_TOKEN_STRING, "foo", JSON_TOKEN_COLON, JSON_TOKEN_ARRAY_OPEN, JSON_TOKEN_BOOLEAN, true, JSON_TOKEN_COMMA, JSON_TOKEN_BOOLEAN, false, JSON_TOKEN_ARRAY_CLOSE, JSON_TOKEN_OBJECT_CLOSE, JSON_TOKEN_END); + test_tokenizer_one("\"\xef\xbf\xbd\"", JSON_TOKEN_STRING, "\xef\xbf\xbd", JSON_TOKEN_END); + test_tokenizer_one("\"\\ufffd\"", JSON_TOKEN_STRING, "\xef\xbf\xbd", JSON_TOKEN_END); + test_tokenizer_one("\"\\uf\"", -EINVAL); + test_tokenizer_one("\"\\ud800a\"", -EINVAL); + test_tokenizer_one("\"\\udc00\\udc00\"", -EINVAL); + test_tokenizer_one("\"\\ud801\\udc37\"", JSON_TOKEN_STRING, "\xf0\x90\x90\xb7", JSON_TOKEN_END); + + test_tokenizer_one("[1, 2, -3]", JSON_TOKEN_ARRAY_OPEN, JSON_TOKEN_UNSIGNED, (uint64_t) 1, JSON_TOKEN_COMMA, JSON_TOKEN_UNSIGNED, (uint64_t) 2, JSON_TOKEN_COMMA, JSON_TOKEN_INTEGER, (int64_t) -3, JSON_TOKEN_ARRAY_CLOSE, JSON_TOKEN_END); +} + +TEST(variant) { + test_variant_one("{\"k\": \"v\", \"foo\": [1, 2, 3], \"bar\": {\"zap\": null}}", test_1); + test_variant_one("{\"mutant\": [1, null, \"1\", {\"1\": [1, \"1\"]}], \"thisisaverylongproperty\": 1.27}", test_2); + test_variant_one("{\"foo\" : \"\\u0935\\u093f\\u0935\\u0947\\u0915\\u0916\\u094d\\u092f\\u093e\\u0924\\u093f\\u0930\\u0935\\u093f\\u092a\\u094d\\u0932\\u0935\\u093e\\u0020\\u0939\\u093e\\u0928\\u094b\\u092a\\u093e\\u092f\\u0903\\u0964\"}", NULL); + + test_variant_one("[ 0, -0, 0.0, -0.0, 0.000, -0.000, 0e0, -0e0, 0e+0, -0e-0, 0e-0, -0e000, 0e+000 ]", test_zeroes); +} + +TEST(json_variant_merge_objectb) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL; + + assert_se(json_build(&v, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("b", JSON_BUILD_STRING("x")), + JSON_BUILD_PAIR("c", JSON_BUILD_CONST_STRING("y")), + JSON_BUILD_PAIR("a", JSON_BUILD_CONST_STRING("z")))) >= 0); + + assert_se(json_variant_merge_objectb(&w, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("b", JSON_BUILD_STRING("x")))) >= 0); + assert_se(json_variant_merge_objectb(&w, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("c", JSON_BUILD_STRING("y")))) >= 0); + assert_se(json_variant_merge_objectb(&w, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("a", JSON_BUILD_STRING("z")))) >= 0); + + assert_se(json_variant_equal(v, w)); +} + +static void json_array_append_with_source_one(bool source) { + _cleanup_(json_variant_unrefp) JsonVariant *a, *b; + + /* Parse two sources, each with a different name and line/column numbers */ + + assert_se(json_parse_with_source(" [41]", source ? "string 1" : NULL, 0, + &a, NULL, NULL) >= 0); + assert_se(json_parse_with_source("\n\n [42]", source ? "string 2" : NULL, 0, + &b, NULL, NULL) >= 0); + + assert_se(json_variant_is_array(a)); + assert_se(json_variant_elements(a) == 1); + assert_se(json_variant_is_array(b)); + assert_se(json_variant_elements(b) == 1); + + /* Verify source information */ + + const char *s1, *s2; + unsigned line1, col1, line2, col2; + assert_se(json_variant_get_source(a, &s1, &line1, &col1) >= 0); + assert_se(json_variant_get_source(b, &s2, &line2, &col2) >= 0); + + assert_se(streq_ptr(s1, source ? "string 1" : NULL)); + assert_se(streq_ptr(s2, source ? "string 2" : NULL)); + assert_se(line1 == 1); + assert_se(col1 == 2); + assert_se(line2 == 3); + assert_se(col2 == 4); + + /* Append one elem from the second array (and source) to the first. */ + + JsonVariant *elem; + assert_se(elem = json_variant_by_index(b, 0)); + assert_se(json_variant_is_integer(elem)); + assert_se(json_variant_elements(elem) == 0); + + assert_se(json_variant_append_array(&a, elem) >= 0); + + assert_se(json_variant_is_array(a)); + assert_se(json_variant_elements(a) == 2); + + /* Verify that source information was propagated correctly */ + + assert_se(json_variant_get_source(elem, &s1, &line1, &col1) >= 0); + assert_se(elem = json_variant_by_index(a, 1)); + assert_se(json_variant_get_source(elem, &s2, &line2, &col2) >= 0); + + assert_se(streq_ptr(s1, source ? "string 2" : NULL)); + assert_se(streq_ptr(s2, source ? "string 2" : NULL)); + assert_se(line1 == 3); + assert_se(col1 == 5); + assert_se(line2 == 3); + assert_se(col2 == 5); +} + +TEST(json_array_append_with_source) { + json_array_append_with_source_one(true); +} + +TEST(json_array_append_without_source) { + json_array_append_with_source_one(false); +} + +TEST(json_array_append_nodup) { + _cleanup_(json_variant_unrefp) JsonVariant *l = NULL, *s = NULL, *wd = NULL, *nd = NULL; + + assert_se(json_build(&l, JSON_BUILD_STRV(STRV_MAKE("foo", "bar", "baz", "bar", "baz", "foo", "qux", "baz"))) >= 0); + assert_se(json_build(&s, JSON_BUILD_STRV(STRV_MAKE("foo", "bar", "baz", "qux"))) >= 0); + + assert_se(!json_variant_equal(l, s)); + assert_se(json_variant_elements(l) == 8); + assert_se(json_variant_elements(s) == 4); + + JsonVariant *i; + JSON_VARIANT_ARRAY_FOREACH(i, l) { + assert_se(json_variant_append_array(&wd, i) >= 0); + assert_se(json_variant_append_array_nodup(&nd, i) >= 0); + } + + assert_se(json_variant_elements(wd) == 8); + assert_se(json_variant_equal(l, wd)); + assert_se(!json_variant_equal(s, wd)); + + assert_se(json_variant_elements(nd) == 4); + assert_se(!json_variant_equal(l, nd)); + assert_se(json_variant_equal(s, nd)); +} + +TEST(json_dispatch) { + struct foobar { + uint64_t a, b; + int64_t c, d; + uint32_t e, f; + int32_t g, h; + uint16_t i, j; + int16_t k, l; + } foobar = {}; + + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + assert_se(json_build(&v, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("a", JSON_BUILD_UNSIGNED(UINT64_MAX)), + JSON_BUILD_PAIR("b", JSON_BUILD_STRING("18446744073709551615")), + JSON_BUILD_PAIR("c", JSON_BUILD_INTEGER(INT64_MIN)), + JSON_BUILD_PAIR("d", JSON_BUILD_STRING("-9223372036854775808")), + JSON_BUILD_PAIR("e", JSON_BUILD_UNSIGNED(UINT32_MAX)), + JSON_BUILD_PAIR("f", JSON_BUILD_STRING("4294967295")), + JSON_BUILD_PAIR("g", JSON_BUILD_INTEGER(INT32_MIN)), + JSON_BUILD_PAIR("h", JSON_BUILD_STRING("-2147483648")), + JSON_BUILD_PAIR("i", JSON_BUILD_UNSIGNED(UINT16_MAX)), + JSON_BUILD_PAIR("j", JSON_BUILD_STRING("65535")), + JSON_BUILD_PAIR("k", JSON_BUILD_INTEGER(INT16_MIN)), + JSON_BUILD_PAIR("l", JSON_BUILD_STRING("-32768")))) >= 0); + + assert_se(json_variant_dump(v, JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO, stdout, /* prefix= */ NULL) >= 0); + + JsonDispatch table[] = { + { "a", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(struct foobar, a) }, + { "b", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(struct foobar, b) }, + { "c", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int64, offsetof(struct foobar, c) }, + { "d", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int64, offsetof(struct foobar, d) }, + { "e", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint32, offsetof(struct foobar, e) }, + { "f", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint32, offsetof(struct foobar, f) }, + { "g", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int32, offsetof(struct foobar, g) }, + { "h", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int32, offsetof(struct foobar, h) }, + { "i", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint16, offsetof(struct foobar, i) }, + { "j", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint16, offsetof(struct foobar, j) }, + { "k", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int16, offsetof(struct foobar, k) }, + { "l", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int16, offsetof(struct foobar, l) }, + {} + }; + + assert_se(json_dispatch(v, table, JSON_LOG, &foobar) >= 0); + + assert_se(foobar.a == UINT64_MAX); + assert_se(foobar.b == UINT64_MAX); + assert_se(foobar.c == INT64_MIN); + assert_se(foobar.d == INT64_MIN); + + assert_se(foobar.e == UINT32_MAX); + assert_se(foobar.f == UINT32_MAX); + assert_se(foobar.g == INT32_MIN); + assert_se(foobar.h == INT32_MIN); + + assert_se(foobar.i == UINT16_MAX); + assert_se(foobar.j == UINT16_MAX); + assert_se(foobar.k == INT16_MIN); + assert_se(foobar.l == INT16_MIN); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-kbd-util.c b/src/test/test-kbd-util.c new file mode 100644 index 0000000..0a166c6 --- /dev/null +++ b/src/test/test-kbd-util.c @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "kbd-util.h" +#include "log.h" +#include "strv.h" +#include "tests.h" + +int main(int argc, char *argv[]) { + _cleanup_strv_free_ char **maps = NULL; + int r; + + log_show_color(true); + test_setup_logging(LOG_DEBUG); + + r = get_keymaps(&maps); + if (r < 0) { + log_error_errno(r, "Failed to acquire keymaps: %m"); + return 0; + } + + STRV_FOREACH(m, maps) { + log_info("Found keymap: %s", *m); + assert_se(keymap_exists(*m) > 0); + } + + return 0; +} diff --git a/src/test/test-libcrypt-util.c b/src/test/test-libcrypt-util.c new file mode 100644 index 0000000..f88a9f9 --- /dev/null +++ b/src/test/test-libcrypt-util.c @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#if HAVE_CRYPT_H +# include <crypt.h> +#else +# include <unistd.h> +#endif + +#include "strv.h" +#include "tests.h" +#include "libcrypt-util.h" + +static void test_crypt_preferred_method(void) { + log_info("/* %s */", __func__); + + log_info("crypt_preferred_method: %s", +#if HAVE_CRYPT_PREFERRED_METHOD + crypt_preferred_method() +#else + "(not available)" +#endif + ); +} + +static void test_make_salt(void) { + log_info("/* %s */", __func__); + + for (int i = 0; i < 10; i++) { + _cleanup_free_ char *t; + + assert_se(make_salt(&t) == 0); + log_info("%s", t); + } +} + +static int test_hash_password(void) { + log_info("/* %s */", __func__); + + /* As a warm-up exercise, check if we can hash passwords. */ + + bool have_sane_hash = false; + + FOREACH_STRING(hash, + "ew3bU1.hoKk4o", + "$1$gc5rWpTB$wK1aul1PyBn9AX1z93stk1", + "$2b$12$BlqcGkB/7BFvNMXKGxDea.5/8D6FTny.cbNcHW/tqcrcyo6ZJd8u2", + "$5$lGhDrcrao9zb5oIK$05KlOVG3ocknx/ThreqXE/gk.XzFFBMTksc4t2CPDUD", + "$6$c7wB/3GiRk0VHf7e$zXJ7hN0aLZapE.iO4mn/oHu6.prsXTUG/5k1AxpgR85ELolyAcaIGRgzfwJs3isTChMDBjnthZyaMCfCNxo9I.", + "$y$j9T$$9cKOWsAm4m97WiYk61lPPibZpy3oaGPIbsL4koRe/XD") { + int b; + + b = test_password_one(hash, "ppp"); + log_info("%s: %s", hash, yes_no(b)); +#if defined(XCRYPT_VERSION_MAJOR) + /* xcrypt is supposed to always implement all methods. */ + assert_se(b); +#endif + + if (b && IN_SET(hash[1], '6', 'y')) + have_sane_hash = true; + } + + return have_sane_hash; +} + +static void test_hash_password_full(void) { + log_info("/* %s */", __func__); + + _cleanup_free_ void *cd_data = NULL; + int cd_size = 0; + + log_info("sizeof(struct crypt_data): %zu bytes", sizeof(struct crypt_data)); + + for (unsigned c = 0; c < 2; c++) + FOREACH_STRING(i, "abc123", "h⸿sło") { + _cleanup_free_ char *hashed; + + if (c == 0) + assert_se(hash_password_full(i, &cd_data, &cd_size, &hashed) == 0); + else + assert_se(hash_password_full(i, NULL, NULL, &hashed) == 0); + log_debug("\"%s\" → \"%s\"", i, hashed); + log_info("crypt_r[a] buffer size: %i bytes", cd_size); + + assert_se(test_password_one(hashed, i) == true); + assert_se(test_password_one(i, hashed) <= 0); /* We get an error for non-utf8 */ + assert_se(test_password_one(hashed, "foobar") == false); + assert_se(test_password_many(STRV_MAKE(hashed), i) == true); + assert_se(test_password_many(STRV_MAKE(hashed), "foobar") == false); + assert_se(test_password_many(STRV_MAKE(hashed, hashed, hashed), "foobar") == false); + assert_se(test_password_many(STRV_MAKE("$y$j9T$dlCXwkX0GC5L6B8Gf.4PN/$VCyEH", + hashed, + "$y$j9T$SAayASazWZIQeJd9AS02m/$"), + i) == true); + assert_se(test_password_many(STRV_MAKE("$W$j9T$dlCXwkX0GC5L6B8Gf.4PN/$VCyEH", /* no such method exists... */ + hashed, + "$y$j9T$SAayASazWZIQeJd9AS02m/$"), + i) == true); + assert_se(test_password_many(STRV_MAKE("$y$j9T$dlCXwkX0GC5L6B8Gf.4PN/$VCyEH", + hashed, + "$y$j9T$SAayASazWZIQeJd9AS02m/$"), + "") == false); + assert_se(test_password_many(STRV_MAKE("$W$j9T$dlCXwkX0GC5L6B8Gf.4PN/$VCyEH", /* no such method exists... */ + hashed, + "$y$j9T$SAayASazWZIQeJd9AS02m/$"), + "") == false); + } +} + +int main(int argc, char *argv[]) { + test_setup_logging(LOG_DEBUG); + +#if defined(__powerpc__) && !defined(XCRYPT_VERSION_MAJOR) + return log_tests_skipped("crypt_r() causes a buffer overflow on ppc64el, see https://github.com/systemd/systemd/pull/16981#issuecomment-691203787"); +#endif + + test_crypt_preferred_method(); + test_make_salt(); + + if (!test_hash_password()) + return log_tests_skipped("crypt doesn't support yescrypt or sha512crypt"); + + test_hash_password_full(); + + return 0; +} diff --git a/src/test/test-libmount.c b/src/test/test-libmount.c new file mode 100644 index 0000000..9ba428e --- /dev/null +++ b/src/test/test-libmount.c @@ -0,0 +1,110 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "fd-util.h" +#include "escape.h" +#include "libmount-util.h" +#include "tests.h" + +static void test_libmount_unescaping_one( + const char *title, + const char *string, + bool may_fail, + const char *expected_source, + const char *expected_target) { + /* A test for libmount really */ + int r; + + log_info("/* %s %s */", __func__, title); + + _cleanup_(mnt_free_tablep) struct libmnt_table *table = NULL; + _cleanup_(mnt_free_iterp) struct libmnt_iter *iter = NULL; + _cleanup_fclose_ FILE *f = NULL; + + f = fmemopen((char*) string, strlen(string), "r"); + assert_se(f); + + assert_se(libmount_parse(title, f, &table, &iter) >= 0); + + struct libmnt_fs *fs; + const char *source, *target; + _cleanup_free_ char *x = NULL, *cs = NULL, *s = NULL, *ct = NULL, *t = NULL; + + /* We allow this call and the checks below to fail in some cases. See the case definitions below. */ + + r = mnt_table_next_fs(table, iter, &fs); + if (r != 0 && may_fail) { + log_error_errno(r, "mnt_table_next_fs failed: %m"); + return; + } + assert_se(r == 0); + + assert_se(x = cescape(string)); + + assert_se(source = mnt_fs_get_source(fs)); + assert_se(target = mnt_fs_get_target(fs)); + + assert_se(cs = cescape(source)); + assert_se(ct = cescape(target)); + + assert_se(cunescape(source, UNESCAPE_RELAX, &s) >= 0); + assert_se(cunescape(target, UNESCAPE_RELAX, &t) >= 0); + + log_info("from '%s'", x); + log_info("source: '%s'", source); + log_info("source: '%s'", cs); + log_info("source: '%s'", s); + log_info("expected: '%s'", strna(expected_source)); + log_info("target: '%s'", target); + log_info("target: '%s'", ct); + log_info("target: '%s'", t); + log_info("expected: '%s'", strna(expected_target)); + + assert_se(may_fail || streq(source, expected_source)); + assert_se(may_fail || streq(target, expected_target)); + + assert_se(mnt_table_next_fs(table, iter, &fs) == 1); +} + +TEST(libmount_unescaping) { + test_libmount_unescaping_one( + "escaped space + utf8", + "729 38 0:59 / /tmp/„zupa\\040zębowa” rw,relatime shared:395 - tmpfs die\\040Brühe rw,seclabel", + false, + "die Brühe", + "/tmp/„zupa zębowa”" + ); + + test_libmount_unescaping_one( + "escaped newline", + "729 38 0:59 / /tmp/x\\012y rw,relatime shared:395 - tmpfs newline rw,seclabel", + false, + "newline", + "/tmp/x\ny" + ); + + /* The result of "mount -t tmpfs '' /tmp/emptysource". + * This will fail with libmount <= v2.33. + * See https://github.com/karelzak/util-linux/commit/18a52a5094. + */ + test_libmount_unescaping_one( + "empty source", + "760 38 0:60 / /tmp/emptysource rw,relatime shared:410 - tmpfs rw,seclabel", + true, + "", + "/tmp/emptysource" + ); + + /* The kernel leaves \r as is. + * Also see https://github.com/karelzak/util-linux/issues/780. + */ + test_libmount_unescaping_one( + "foo\\rbar", + "790 38 0:61 / /tmp/foo\rbar rw,relatime shared:425 - tmpfs tmpfs rw,seclabel", + true, + "tmpfs", + "/tmp/foo\rbar" + ); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-limits-util.c b/src/test/test-limits-util.c new file mode 100644 index 0000000..3b6c8c0 --- /dev/null +++ b/src/test/test-limits-util.c @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "format-util.h" +#include "limits-util.h" +#include "tests.h" + +TEST(physical_memory) { + uint64_t p; + + p = physical_memory(); + assert_se(p > 0); + assert_se(p < UINT64_MAX); + assert_se(p % page_size() == 0); + + log_info("Memory: %s (%" PRIu64 ")", FORMAT_BYTES(p), p); +} + +TEST(physical_memory_scale) { + uint64_t p; + + p = physical_memory(); + + assert_se(physical_memory_scale(0, 100) == 0); + assert_se(physical_memory_scale(100, 100) == p); + + log_info("Memory original: %" PRIu64, physical_memory()); + log_info("Memory scaled by 50%%: %" PRIu64, physical_memory_scale(50, 100)); + log_info("Memory divided by 2: %" PRIu64, physical_memory() / 2); + log_info("Page size: %zu", page_size()); + + /* There might be an uneven number of pages, hence permit these calculations to be half a page off... */ + assert_se(page_size()/2 + physical_memory_scale(50, 100) - p/2 <= page_size()); + assert_se(physical_memory_scale(200, 100) == p*2); + + assert_se(physical_memory_scale(0, 1) == 0); + assert_se(physical_memory_scale(1, 1) == p); + assert_se(physical_memory_scale(2, 1) == p*2); + + assert_se(physical_memory_scale(0, 2) == 0); + + assert_se(page_size()/2 + physical_memory_scale(1, 2) - p/2 <= page_size()); + assert_se(physical_memory_scale(2, 2) == p); + assert_se(physical_memory_scale(4, 2) == p*2); + + assert_se(physical_memory_scale(0, UINT32_MAX) == 0); + assert_se(physical_memory_scale(UINT32_MAX, UINT32_MAX) == p); + + /* overflow */ + assert_se(physical_memory_scale(UINT64_MAX/4, UINT64_MAX) == UINT64_MAX); +} + +TEST(system_tasks_max) { + uint64_t t; + + t = system_tasks_max(); + assert_se(t > 0); + assert_se(t < UINT64_MAX); + + log_info("Max tasks: %" PRIu64, t); +} + +TEST(system_tasks_max_scale) { + uint64_t t; + + t = system_tasks_max(); + + assert_se(system_tasks_max_scale(0, 100) == 0); + assert_se(system_tasks_max_scale(100, 100) == t); + + assert_se(system_tasks_max_scale(0, 1) == 0); + assert_se(system_tasks_max_scale(1, 1) == t); + assert_se(system_tasks_max_scale(2, 1) == 2*t); + + assert_se(system_tasks_max_scale(0, 2) == 0); + assert_se(system_tasks_max_scale(1, 2) == t/2); + assert_se(system_tasks_max_scale(2, 2) == t); + assert_se(system_tasks_max_scale(3, 2) == (3*t)/2); + assert_se(system_tasks_max_scale(4, 2) == t*2); + + assert_se(system_tasks_max_scale(0, UINT32_MAX) == 0); + assert_se(system_tasks_max_scale((UINT32_MAX-1)/2, UINT32_MAX-1) == t/2); + assert_se(system_tasks_max_scale(UINT32_MAX, UINT32_MAX) == t); + + /* overflow */ + + assert_se(system_tasks_max_scale(UINT64_MAX/4, UINT64_MAX) == UINT64_MAX); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-list.c b/src/test/test-list.c new file mode 100644 index 0000000..87f7c7b --- /dev/null +++ b/src/test/test-list.c @@ -0,0 +1,286 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "list.h" +#include "tests.h" + +int main(int argc, const char *argv[]) { + test_setup_logging(LOG_DEBUG); + + size_t i; + typedef struct list_item { + LIST_FIELDS(struct list_item, item_list); + } list_item; + LIST_HEAD(list_item, head); + LIST_HEAD(list_item, head2); + list_item items[4]; + + LIST_HEAD_INIT(head); + LIST_HEAD_INIT(head2); + assert_se(head == NULL); + assert_se(head2 == NULL); + + for (i = 0; i < ELEMENTSOF(items); i++) { + LIST_INIT(item_list, &items[i]); + assert_se(LIST_JUST_US(item_list, &items[i])); + assert_se(LIST_PREPEND(item_list, head, &items[i]) == &items[i]); + } + + i = 0; + LIST_FOREACH_OTHERS(item_list, cursor, &items[2]) { + i++; + assert_se(cursor != &items[2]); + } + assert_se(i == ELEMENTSOF(items)-1); + + i = 0; + LIST_FOREACH_OTHERS(item_list, cursor, &items[0]) { + i++; + assert_se(cursor != &items[0]); + } + assert_se(i == ELEMENTSOF(items)-1); + + i = 0; + LIST_FOREACH_OTHERS(item_list, cursor, &items[3]) { + i++; + assert_se(cursor != &items[3]); + } + assert_se(i == ELEMENTSOF(items)-1); + + assert_se(!LIST_JUST_US(item_list, head)); + + assert_se(items[0].item_list_next == NULL); + assert_se(items[1].item_list_next == &items[0]); + assert_se(items[2].item_list_next == &items[1]); + assert_se(items[3].item_list_next == &items[2]); + + assert_se(items[0].item_list_prev == &items[1]); + assert_se(items[1].item_list_prev == &items[2]); + assert_se(items[2].item_list_prev == &items[3]); + assert_se(items[3].item_list_prev == NULL); + + list_item *cursor = LIST_FIND_HEAD(item_list, &items[0]); + assert_se(cursor == &items[3]); + + cursor = LIST_FIND_TAIL(item_list, &items[3]); + assert_se(cursor == &items[0]); + + assert_se(LIST_REMOVE(item_list, head, &items[1]) == &items[1]); + assert_se(LIST_JUST_US(item_list, &items[1])); + + assert_se(items[0].item_list_next == NULL); + assert_se(items[2].item_list_next == &items[0]); + assert_se(items[3].item_list_next == &items[2]); + + assert_se(items[0].item_list_prev == &items[2]); + assert_se(items[2].item_list_prev == &items[3]); + assert_se(items[3].item_list_prev == NULL); + + assert_se(LIST_INSERT_AFTER(item_list, head, &items[3], &items[1]) == &items[1]); + assert_se(items[0].item_list_next == NULL); + assert_se(items[2].item_list_next == &items[0]); + assert_se(items[1].item_list_next == &items[2]); + assert_se(items[3].item_list_next == &items[1]); + + assert_se(items[0].item_list_prev == &items[2]); + assert_se(items[2].item_list_prev == &items[1]); + assert_se(items[1].item_list_prev == &items[3]); + assert_se(items[3].item_list_prev == NULL); + + assert_se(LIST_REMOVE(item_list, head, &items[1]) == &items[1]); + assert_se(LIST_JUST_US(item_list, &items[1])); + + assert_se(items[0].item_list_next == NULL); + assert_se(items[2].item_list_next == &items[0]); + assert_se(items[3].item_list_next == &items[2]); + + assert_se(items[0].item_list_prev == &items[2]); + assert_se(items[2].item_list_prev == &items[3]); + assert_se(items[3].item_list_prev == NULL); + + assert_se(LIST_INSERT_BEFORE(item_list, head, &items[2], &items[1]) == &items[1]); + assert_se(items[0].item_list_next == NULL); + assert_se(items[2].item_list_next == &items[0]); + assert_se(items[1].item_list_next == &items[2]); + assert_se(items[3].item_list_next == &items[1]); + + assert_se(items[0].item_list_prev == &items[2]); + assert_se(items[2].item_list_prev == &items[1]); + assert_se(items[1].item_list_prev == &items[3]); + assert_se(items[3].item_list_prev == NULL); + + assert_se(LIST_REMOVE(item_list, head, &items[0]) == &items[0]); + assert_se(LIST_JUST_US(item_list, &items[0])); + + assert_se(items[2].item_list_next == NULL); + assert_se(items[1].item_list_next == &items[2]); + assert_se(items[3].item_list_next == &items[1]); + + assert_se(items[2].item_list_prev == &items[1]); + assert_se(items[1].item_list_prev == &items[3]); + assert_se(items[3].item_list_prev == NULL); + + assert_se(LIST_INSERT_BEFORE(item_list, head, &items[3], &items[0]) == &items[0]); + assert_se(items[2].item_list_next == NULL); + assert_se(items[1].item_list_next == &items[2]); + assert_se(items[3].item_list_next == &items[1]); + assert_se(items[0].item_list_next == &items[3]); + + assert_se(items[2].item_list_prev == &items[1]); + assert_se(items[1].item_list_prev == &items[3]); + assert_se(items[3].item_list_prev == &items[0]); + assert_se(items[0].item_list_prev == NULL); + assert_se(head == &items[0]); + + assert_se(LIST_REMOVE(item_list, head, &items[0]) == &items[0]); + assert_se(LIST_JUST_US(item_list, &items[0])); + + assert_se(items[2].item_list_next == NULL); + assert_se(items[1].item_list_next == &items[2]); + assert_se(items[3].item_list_next == &items[1]); + + assert_se(items[2].item_list_prev == &items[1]); + assert_se(items[1].item_list_prev == &items[3]); + assert_se(items[3].item_list_prev == NULL); + + assert_se(LIST_INSERT_BEFORE(item_list, head, NULL, &items[0]) == &items[0]); + assert_se(items[0].item_list_next == NULL); + assert_se(items[2].item_list_next == &items[0]); + assert_se(items[1].item_list_next == &items[2]); + assert_se(items[3].item_list_next == &items[1]); + + assert_se(items[0].item_list_prev == &items[2]); + assert_se(items[2].item_list_prev == &items[1]); + assert_se(items[1].item_list_prev == &items[3]); + assert_se(items[3].item_list_prev == NULL); + + assert_se(LIST_REMOVE(item_list, head, &items[0]) == &items[0]); + assert_se(LIST_JUST_US(item_list, &items[0])); + + assert_se(items[2].item_list_next == NULL); + assert_se(items[1].item_list_next == &items[2]); + assert_se(items[3].item_list_next == &items[1]); + + assert_se(items[2].item_list_prev == &items[1]); + assert_se(items[1].item_list_prev == &items[3]); + assert_se(items[3].item_list_prev == NULL); + + assert_se(LIST_REMOVE(item_list, head, &items[1]) == &items[1]); + assert_se(LIST_JUST_US(item_list, &items[1])); + + assert_se(items[2].item_list_next == NULL); + assert_se(items[3].item_list_next == &items[2]); + + assert_se(items[2].item_list_prev == &items[3]); + assert_se(items[3].item_list_prev == NULL); + + assert_se(LIST_REMOVE(item_list, head, &items[2]) == &items[2]); + assert_se(LIST_JUST_US(item_list, &items[2])); + assert_se(LIST_JUST_US(item_list, head)); + + assert_se(LIST_REMOVE(item_list, head, &items[3]) == &items[3]); + assert_se(LIST_JUST_US(item_list, &items[3])); + + assert_se(head == NULL); + + for (i = 0; i < ELEMENTSOF(items); i++) { + assert_se(LIST_JUST_US(item_list, &items[i])); + assert_se(LIST_APPEND(item_list, head, &items[i]) == &items[i]); + } + + assert_se(!LIST_JUST_US(item_list, head)); + + assert_se(items[0].item_list_next == &items[1]); + assert_se(items[1].item_list_next == &items[2]); + assert_se(items[2].item_list_next == &items[3]); + assert_se(items[3].item_list_next == NULL); + + assert_se(items[0].item_list_prev == NULL); + assert_se(items[1].item_list_prev == &items[0]); + assert_se(items[2].item_list_prev == &items[1]); + assert_se(items[3].item_list_prev == &items[2]); + + for (i = 0; i < ELEMENTSOF(items); i++) + assert_se(LIST_REMOVE(item_list, head, &items[i]) == &items[i]); + + assert_se(head == NULL); + + for (i = 0; i < ELEMENTSOF(items) / 2; i++) { + LIST_INIT(item_list, &items[i]); + assert_se(LIST_JUST_US(item_list, &items[i])); + assert_se(LIST_PREPEND(item_list, head, &items[i]) == &items[i]); + } + + for (i = ELEMENTSOF(items) / 2; i < ELEMENTSOF(items); i++) { + LIST_INIT(item_list, &items[i]); + assert_se(LIST_JUST_US(item_list, &items[i])); + assert_se(LIST_PREPEND(item_list, head2, &items[i]) == &items[i]); + } + + assert_se(items[0].item_list_next == NULL); + assert_se(items[1].item_list_next == &items[0]); + assert_se(items[2].item_list_next == NULL); + assert_se(items[3].item_list_next == &items[2]); + + assert_se(items[0].item_list_prev == &items[1]); + assert_se(items[1].item_list_prev == NULL); + assert_se(items[2].item_list_prev == &items[3]); + assert_se(items[3].item_list_prev == NULL); + + assert_se(LIST_JOIN(item_list, head2, head) == head2); + assert_se(head == NULL); + + assert_se(items[0].item_list_next == NULL); + assert_se(items[1].item_list_next == &items[0]); + assert_se(items[2].item_list_next == &items[1]); + assert_se(items[3].item_list_next == &items[2]); + + assert_se(items[0].item_list_prev == &items[1]); + assert_se(items[1].item_list_prev == &items[2]); + assert_se(items[2].item_list_prev == &items[3]); + assert_se(items[3].item_list_prev == NULL); + + assert_se(LIST_JOIN(item_list, head, head2) == head); + assert_se(head2 == NULL); + assert_se(head); + + for (i = 0; i < ELEMENTSOF(items); i++) + assert_se(LIST_REMOVE(item_list, head, &items[i]) == &items[i]); + + assert_se(head == NULL); + + assert_se(LIST_PREPEND(item_list, head, items + 0) == items + 0); + assert_se(LIST_PREPEND(item_list, head, items + 1) == items + 1); + assert_se(LIST_PREPEND(item_list, head, items + 2) == items + 2); + + assert_se(LIST_POP(item_list, head) == items + 2); + assert_se(LIST_POP(item_list, head) == items + 1); + assert_se(LIST_POP(item_list, head) == items + 0); + assert_se(LIST_POP(item_list, head) == NULL); + + /* No-op on an empty list */ + + LIST_CLEAR(item_list, head, free); + + /* A non-empty list is cleared */ + + assert_se(LIST_PREPEND(item_list, head, new0(list_item, 1))); + assert_se(LIST_PREPEND(item_list, head, new0(list_item, 1))); + + LIST_CLEAR(item_list, head, free); + + assert_se(head == NULL); + + /* A list can be cleared partially */ + + assert_se(LIST_PREPEND(item_list, head, new0(list_item, 1))); + assert_se(LIST_PREPEND(item_list, head, new0(list_item, 1))); + assert_se(LIST_PREPEND(item_list, head, items + 0) == items + 0); + + LIST_CLEAR(item_list, head->item_list_next, free); + + assert_se(head == items + 0); + assert_se(head->item_list_next == NULL); + + return 0; +} diff --git a/src/test/test-load-fragment.c b/src/test/test-load-fragment.c new file mode 100644 index 0000000..8d2cec0 --- /dev/null +++ b/src/test/test-load-fragment.c @@ -0,0 +1,1105 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <fcntl.h> +#include <stddef.h> +#include <stdio.h> +#include <unistd.h> + +#include "sd-id128.h" + +#include "all-units.h" +#include "alloc-util.h" +#include "capability-util.h" +#include "conf-parser.h" +#include "fd-util.h" +#include "fileio.h" +#include "format-util.h" +#include "fs-util.h" +#include "hashmap.h" +#include "hostname-util.h" +#include "install-printf.h" +#include "install.h" +#include "load-fragment.h" +#include "macro.h" +#include "memory-util.h" +#include "open-file.h" +#include "pcre2-util.h" +#include "rm-rf.h" +#include "specifier.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" +#include "tmpfile-util.h" +#include "user-util.h" + +/* Nontrivial value serves as a placeholder to check that parsing function (didn't) change it */ +#define CGROUP_LIMIT_DUMMY 3 + +static char *runtime_dir = NULL; + +STATIC_DESTRUCTOR_REGISTER(runtime_dir, rm_rf_physical_and_freep); + +/* For testing type compatibility. */ +_unused_ ConfigPerfItemLookup unused_lookup = load_fragment_gperf_lookup; + +TEST_RET(unit_file_get_set) { + int r; + _cleanup_hashmap_free_ Hashmap *h = NULL; + UnitFileList *p; + + h = hashmap_new(&unit_file_list_hash_ops_free); + assert_se(h); + + r = unit_file_get_list(RUNTIME_SCOPE_SYSTEM, NULL, h, NULL, NULL); + if (IN_SET(r, -EPERM, -EACCES)) + return log_tests_skipped_errno(r, "unit_file_get_list"); + + log_full_errno(r == 0 ? LOG_INFO : LOG_ERR, r, + "unit_file_get_list: %m"); + if (r < 0) + return EXIT_FAILURE; + + HASHMAP_FOREACH(p, h) + printf("%s = %s\n", p->path, unit_file_state_to_string(p->state)); + + return 0; +} + +static void check_execcommand(ExecCommand *c, + const char* path, + const char* argv0, + const char* argv1, + const char* argv2, + bool ignore) { + size_t n; + + assert_se(c); + log_info("expect: \"%s\" [\"%s\" \"%s\" \"%s\"]", + path, argv0 ?: path, strnull(argv1), strnull(argv2)); + n = strv_length(c->argv); + log_info("actual: \"%s\" [\"%s\" \"%s\" \"%s\"]", + c->path, c->argv[0], n > 0 ? c->argv[1] : "(null)", n > 1 ? c->argv[2] : "(null)"); + assert_se(streq(c->path, path)); + assert_se(streq(c->argv[0], argv0 ?: path)); + if (n > 0) + assert_se(streq_ptr(c->argv[1], argv1)); + if (n > 1) + assert_se(streq_ptr(c->argv[2], argv2)); + assert_se(!!(c->flags & EXEC_COMMAND_IGNORE_FAILURE) == ignore); +} + +TEST(config_parse_exec) { + /* int config_parse_exec( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) */ + int r; + + ExecCommand *c = NULL, *c1; + const char *ccc; + _cleanup_(manager_freep) Manager *m = NULL; + _cleanup_(unit_freep) Unit *u = NULL; + + r = manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_MINIMAL, &m); + if (manager_errno_skip_test(r)) { + log_notice_errno(r, "Skipping test: manager_new: %m"); + return; + } + + assert_se(r >= 0); + assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + + assert_se(u = unit_new(m, sizeof(Service))); + + log_info("/* basic test */"); + r = config_parse_exec(NULL, "fake", 1, "section", 1, + "LValue", 0, "/RValue r1", + &c, u); + assert_se(r >= 0); + check_execcommand(c, "/RValue", "/RValue", "r1", NULL, false); + + r = config_parse_exec(NULL, "fake", 2, "section", 1, + "LValue", 0, "/RValue///slashes r1///", + &c, u); + + log_info("/* test slashes */"); + assert_se(r >= 0); + c1 = c->command_next; + check_execcommand(c1, "/RValue/slashes", "/RValue///slashes", "r1///", NULL, false); + + log_info("/* trailing slash */"); + r = config_parse_exec(NULL, "fake", 4, "section", 1, + "LValue", 0, "/RValue/ argv0 r1", + &c, u); + assert_se(r == -ENOEXEC); + assert_se(c1->command_next == NULL); + + log_info("/* honour_argv0 */"); + r = config_parse_exec(NULL, "fake", 3, "section", 1, + "LValue", 0, "@/RValue///slashes2 ///argv0 r1", + &c, u); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, "/RValue/slashes2", "///argv0", "r1", NULL, false); + + log_info("/* honour_argv0, no args */"); + r = config_parse_exec(NULL, "fake", 3, "section", 1, + "LValue", 0, "@/RValue", + &c, u); + assert_se(r == -ENOEXEC); + assert_se(c1->command_next == NULL); + + log_info("/* no command, whitespace only, reset */"); + r = config_parse_exec(NULL, "fake", 3, "section", 1, + "LValue", 0, "", + &c, u); + assert_se(r == 0); + assert_se(c == NULL); + + log_info("/* ignore && honour_argv0 */"); + r = config_parse_exec(NULL, "fake", 4, "section", 1, + "LValue", 0, "-@/RValue///slashes3 argv0a r1", + &c, u); + assert_se(r >= 0); + c1 = c; + check_execcommand(c1, "/RValue/slashes3", "argv0a", "r1", NULL, true); + + log_info("/* ignore && honour_argv0 */"); + r = config_parse_exec(NULL, "fake", 4, "section", 1, + "LValue", 0, "@-/RValue///slashes4 argv0b r1", + &c, u); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, "/RValue/slashes4", "argv0b", "r1", NULL, true); + + log_info("/* ignore && ignore */"); + r = config_parse_exec(NULL, "fake", 4, "section", 1, + "LValue", 0, "--/RValue argv0 r1", + &c, u); + assert_se(r == 0); + assert_se(c1->command_next == NULL); + + log_info("/* ignore && ignore (2) */"); + r = config_parse_exec(NULL, "fake", 4, "section", 1, + "LValue", 0, "-@-/RValue argv0 r1", + &c, u); + assert_se(r == 0); + assert_se(c1->command_next == NULL); + + log_info("/* semicolon */"); + r = config_parse_exec(NULL, "fake", 5, "section", 1, + "LValue", 0, + "-@/RValue argv0 r1 ; " + "/goo/goo boo", + &c, u); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, "/RValue", "argv0", "r1", NULL, true); + + c1 = c1->command_next; + check_execcommand(c1, "/goo/goo", NULL, "boo", NULL, false); + + log_info("/* two semicolons in a row */"); + r = config_parse_exec(NULL, "fake", 5, "section", 1, + "LValue", 0, + "-@/RValue argv0 r1 ; ; " + "/goo/goo boo", + &c, u); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, "/RValue", "argv0", "r1", NULL, true); + c1 = c1->command_next; + check_execcommand(c1, "/goo/goo", "/goo/goo", "boo", NULL, false); + + log_info("/* trailing semicolon */"); + r = config_parse_exec(NULL, "fake", 5, "section", 1, + "LValue", 0, + "-@/RValue argv0 r1 ; ", + &c, u); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, "/RValue", "argv0", "r1", NULL, true); + + assert_se(c1->command_next == NULL); + + log_info("/* trailing semicolon, no whitespace */"); + r = config_parse_exec(NULL, "fake", 5, "section", 1, + "LValue", 0, + "-@/RValue argv0 r1 ;", + &c, u); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, "/RValue", "argv0", "r1", NULL, true); + + assert_se(c1->command_next == NULL); + + log_info("/* trailing semicolon in single quotes */"); + r = config_parse_exec(NULL, "fake", 5, "section", 1, + "LValue", 0, + "-@/RValue argv0 r1 ';'", + &c, u); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, "/RValue", "argv0", "r1", ";", true); + + log_info("/* escaped semicolon */"); + r = config_parse_exec(NULL, "fake", 5, "section", 1, + "LValue", 0, + "/bin/find \\;", + &c, u); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, "/bin/find", NULL, ";", NULL, false); + + log_info("/* escaped semicolon with following arg */"); + r = config_parse_exec(NULL, "fake", 5, "section", 1, + "LValue", 0, + "/sbin/find \\; /x", + &c, u); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, + "/sbin/find", NULL, ";", "/x", false); + + log_info("/* escaped semicolon as part of an expression */"); + r = config_parse_exec(NULL, "fake", 5, "section", 1, + "LValue", 0, + "/sbin/find \\;x", + &c, u); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, + "/sbin/find", NULL, "\\;x", NULL, false); + + log_info("/* encoded semicolon */"); + r = config_parse_exec(NULL, "fake", 5, "section", 1, + "LValue", 0, + "/bin/find \\073", + &c, u); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, "/bin/find", NULL, ";", NULL, false); + + log_info("/* quoted semicolon */"); + r = config_parse_exec(NULL, "fake", 5, "section", 1, + "LValue", 0, + "/bin/find \";\"", + &c, u); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, "/bin/find", NULL, ";", NULL, false); + + log_info("/* quoted semicolon with following arg */"); + r = config_parse_exec(NULL, "fake", 5, "section", 1, + "LValue", 0, + "/sbin/find \";\" /x", + &c, u); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, + "/sbin/find", NULL, ";", "/x", false); + + log_info("/* spaces in the filename */"); + r = config_parse_exec(NULL, "fake", 5, "section", 1, + "LValue", 0, + "\"/PATH WITH SPACES/daemon\" -1 -2", + &c, u); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, + "/PATH WITH SPACES/daemon", NULL, "-1", "-2", false); + + log_info("/* spaces in the filename, no args */"); + r = config_parse_exec(NULL, "fake", 5, "section", 1, + "LValue", 0, + "\"/PATH WITH SPACES/daemon -1 -2\"", + &c, u); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, + "/PATH WITH SPACES/daemon -1 -2", NULL, NULL, NULL, false); + + log_info("/* spaces in the filename, everything quoted */"); + r = config_parse_exec(NULL, "fake", 5, "section", 1, + "LValue", 0, + "\"/PATH WITH SPACES/daemon\" \"-1\" '-2'", + &c, u); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, + "/PATH WITH SPACES/daemon", NULL, "-1", "-2", false); + + log_info("/* escaped spaces in the filename */"); + r = config_parse_exec(NULL, "fake", 5, "section", 1, + "LValue", 0, + "\"/PATH\\sWITH\\sSPACES/daemon\" '-1 -2'", + &c, u); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, + "/PATH WITH SPACES/daemon", NULL, "-1 -2", NULL, false); + + log_info("/* escaped spaces in the filename (2) */"); + r = config_parse_exec(NULL, "fake", 5, "section", 1, + "LValue", 0, + "\"/PATH\\x20WITH\\x20SPACES/daemon\" \"-1 -2\"", + &c, u); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, + "/PATH WITH SPACES/daemon", NULL, "-1 -2", NULL, false); + + for (ccc = "abfnrtv\\\'\"x"; *ccc; ccc++) { + /* \\x is an incomplete hexadecimal sequence, invalid because of the slash */ + char path[] = "/path\\X"; + path[sizeof(path) - 2] = *ccc; + + log_info("/* invalid character: \\%c */", *ccc); + r = config_parse_exec(NULL, "fake", 4, "section", 1, + "LValue", 0, path, + &c, u); + assert_se(r == -ENOEXEC); + assert_se(c1->command_next == NULL); + } + + log_info("/* valid character: \\s */"); + r = config_parse_exec(NULL, "fake", 4, "section", 1, + "LValue", 0, "/path\\s", + &c, u); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, "/path ", NULL, NULL, NULL, false); + + log_info("/* quoted backslashes */"); + r = config_parse_exec(NULL, "fake", 5, "section", 1, + "LValue", 0, + "/bin/grep '\\w+\\K'", + &c, u); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, "/bin/grep", NULL, "\\w+\\K", NULL, false); + + log_info("/* trailing backslash: \\ */"); + /* backslash is invalid */ + r = config_parse_exec(NULL, "fake", 4, "section", 1, + "LValue", 0, "/path\\", + &c, u); + assert_se(r == -ENOEXEC); + assert_se(c1->command_next == NULL); + + log_info("/* missing ending ' */"); + r = config_parse_exec(NULL, "fake", 4, "section", 1, + "LValue", 0, "/path 'foo", + &c, u); + assert_se(r == -ENOEXEC); + assert_se(c1->command_next == NULL); + + log_info("/* missing ending ' with trailing backslash */"); + r = config_parse_exec(NULL, "fake", 4, "section", 1, + "LValue", 0, "/path 'foo\\", + &c, u); + assert_se(r == -ENOEXEC); + assert_se(c1->command_next == NULL); + + log_info("/* invalid space between modifiers */"); + r = config_parse_exec(NULL, "fake", 4, "section", 1, + "LValue", 0, "- /path", + &c, u); + assert_se(r == 0); + assert_se(c1->command_next == NULL); + + log_info("/* only modifiers, no path */"); + r = config_parse_exec(NULL, "fake", 4, "section", 1, + "LValue", 0, "-", + &c, u); + assert_se(r == 0); + assert_se(c1->command_next == NULL); + + log_info("/* long arg */"); /* See issue #22957. */ + + char x[LONG_LINE_MAX-100], *y; + y = mempcpy(x, "/bin/echo ", STRLEN("/bin/echo ")); + memset(y, 'x', sizeof(x) - STRLEN("/bin/echo ") - 1); + x[sizeof(x) - 1] = '\0'; + + r = config_parse_exec(NULL, "fake", 5, "section", 1, + "LValue", 0, x, + &c, u); + assert_se(r >= 0); + c1 = c1->command_next; + check_execcommand(c1, + "/bin/echo", NULL, y, NULL, false); + + log_info("/* empty argument, reset */"); + r = config_parse_exec(NULL, "fake", 4, "section", 1, + "LValue", 0, "", + &c, u); + assert_se(r == 0); + assert_se(c == NULL); + + exec_command_free_list(c); +} + +TEST(config_parse_log_extra_fields) { + /* int config_parse_log_extra_fields( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) */ + + int r; + + _cleanup_(manager_freep) Manager *m = NULL; + _cleanup_(unit_freep) Unit *u = NULL; + ExecContext c = {}; + + r = manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_MINIMAL, &m); + if (manager_errno_skip_test(r)) { + log_notice_errno(r, "Skipping test: manager_new: %m"); + return; + } + + assert_se(r >= 0); + assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + + assert_se(u = unit_new(m, sizeof(Service))); + + log_info("/* %s – basic test */", __func__); + r = config_parse_log_extra_fields(NULL, "fake", 1, "section", 1, + "LValue", 0, "FOO=BAR \"QOOF=quux ' ' \"", + &c, u); + assert_se(r >= 0); + assert_se(c.n_log_extra_fields == 2); + assert_se(strneq(c.log_extra_fields[0].iov_base, "FOO=BAR", c.log_extra_fields[0].iov_len)); + assert_se(strneq(c.log_extra_fields[1].iov_base, "QOOF=quux ' ' ", c.log_extra_fields[1].iov_len)); + + log_info("/* %s – add some */", __func__); + r = config_parse_log_extra_fields(NULL, "fake", 1, "section", 1, + "LValue", 0, "FOO2=BAR2 QOOF2=quux ' '", + &c, u); + assert_se(r >= 0); + assert_se(c.n_log_extra_fields == 4); + assert_se(strneq(c.log_extra_fields[0].iov_base, "FOO=BAR", c.log_extra_fields[0].iov_len)); + assert_se(strneq(c.log_extra_fields[1].iov_base, "QOOF=quux ' ' ", c.log_extra_fields[1].iov_len)); + assert_se(strneq(c.log_extra_fields[2].iov_base, "FOO2=BAR2", c.log_extra_fields[2].iov_len)); + assert_se(strneq(c.log_extra_fields[3].iov_base, "QOOF2=quux", c.log_extra_fields[3].iov_len)); + + exec_context_dump(&c, stdout, " --> "); + + log_info("/* %s – reset */", __func__); + r = config_parse_log_extra_fields(NULL, "fake", 1, "section", 1, + "LValue", 0, "", + &c, u); + assert_se(r >= 0); + assert_se(c.n_log_extra_fields == 0); + + exec_context_free_log_extra_fields(&c); + + log_info("/* %s – bye */", __func__); +} + +TEST(install_printf, .sd_booted = true) { + char name[] = "name.service", + path[] = "/run/systemd/system/name.service"; + InstallInfo i = { .name = name, .path = path, }; + InstallInfo i2 = { .name= name, .path = path, }; + char name3[] = "name@inst.service", + path3[] = "/run/systemd/system/name.service"; + InstallInfo i3 = { .name = name3, .path = path3, }; + InstallInfo i4 = { .name = name3, .path = path3, }; + + _cleanup_free_ char *mid = NULL, *bid = NULL, *host = NULL, *gid = NULL, *group = NULL, *uid = NULL, *user = NULL; + + if (sd_id128_get_machine(NULL) >= 0) + assert_se(specifier_machine_id('m', NULL, NULL, NULL, &mid) >= 0 && mid); + if (sd_booted() > 0) + assert_se(specifier_boot_id('b', NULL, NULL, NULL, &bid) >= 0 && bid); + assert_se(host = gethostname_malloc()); + assert_se(group = gid_to_name(getgid())); + assert_se(asprintf(&gid, UID_FMT, getgid()) >= 0); + assert_se(user = uid_to_name(getuid())); + assert_se(asprintf(&uid, UID_FMT, getuid()) >= 0); + +#define expect(scope, src, pattern, result) \ + do { \ + _cleanup_free_ char *t = NULL, \ + *d1 = ASSERT_PTR(strdup(i.name)), \ + *d2 = ASSERT_PTR(strdup(i.path)); \ + int r = install_name_printf(scope, &src, pattern, &t); \ + assert_se(result ? r >= 0 : r < 0); \ + memzero(i.name, strlen(i.name)); \ + memzero(i.path, strlen(i.path)); \ + if (result) { \ + printf("%s\n", t); \ + assert_se(streq(t, result)); \ + } else \ + assert_se(!t); \ + strcpy(i.name, d1); \ + strcpy(i.path, d2); \ + } while (false) + + expect(RUNTIME_SCOPE_SYSTEM, i, "%n", "name.service"); + expect(RUNTIME_SCOPE_SYSTEM, i, "%N", "name"); + expect(RUNTIME_SCOPE_SYSTEM, i, "%p", "name"); + expect(RUNTIME_SCOPE_SYSTEM, i, "%i", ""); + expect(RUNTIME_SCOPE_SYSTEM, i, "%j", "name"); + expect(RUNTIME_SCOPE_SYSTEM, i, "%g", "root"); + expect(RUNTIME_SCOPE_SYSTEM, i, "%G", "0"); + expect(RUNTIME_SCOPE_SYSTEM, i, "%u", "root"); + expect(RUNTIME_SCOPE_SYSTEM, i, "%U", "0"); + + expect(RUNTIME_SCOPE_SYSTEM, i, "%m", mid); + expect(RUNTIME_SCOPE_SYSTEM, i, "%b", bid); + expect(RUNTIME_SCOPE_SYSTEM, i, "%H", host); + + expect(RUNTIME_SCOPE_SYSTEM, i2, "%g", "root"); + expect(RUNTIME_SCOPE_SYSTEM, i2, "%G", "0"); + expect(RUNTIME_SCOPE_SYSTEM, i2, "%u", "root"); + expect(RUNTIME_SCOPE_SYSTEM, i2, "%U", "0"); + + expect(RUNTIME_SCOPE_USER, i2, "%g", group); + expect(RUNTIME_SCOPE_USER, i2, "%G", gid); + expect(RUNTIME_SCOPE_USER, i2, "%u", user); + expect(RUNTIME_SCOPE_USER, i2, "%U", uid); + + /* gcc-12.0.1-0.9.fc36.x86_64 insist that streq(…, NULL) is called, + * even though the call is inside of a conditional where the pointer is checked. :( */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnonnull" + expect(RUNTIME_SCOPE_GLOBAL, i2, "%g", NULL); + expect(RUNTIME_SCOPE_GLOBAL, i2, "%G", NULL); + expect(RUNTIME_SCOPE_GLOBAL, i2, "%u", NULL); + expect(RUNTIME_SCOPE_GLOBAL, i2, "%U", NULL); +#pragma GCC diagnostic pop + + expect(RUNTIME_SCOPE_SYSTEM, i3, "%n", "name@inst.service"); + expect(RUNTIME_SCOPE_SYSTEM, i3, "%N", "name@inst"); + expect(RUNTIME_SCOPE_SYSTEM, i3, "%p", "name"); + expect(RUNTIME_SCOPE_USER, i3, "%g", group); + expect(RUNTIME_SCOPE_USER, i3, "%G", gid); + expect(RUNTIME_SCOPE_USER, i3, "%u", user); + expect(RUNTIME_SCOPE_USER, i3, "%U", uid); + + expect(RUNTIME_SCOPE_SYSTEM, i3, "%m", mid); + expect(RUNTIME_SCOPE_SYSTEM, i3, "%b", bid); + expect(RUNTIME_SCOPE_SYSTEM, i3, "%H", host); + + expect(RUNTIME_SCOPE_USER, i4, "%g", group); + expect(RUNTIME_SCOPE_USER, i4, "%G", gid); + expect(RUNTIME_SCOPE_USER, i4, "%u", user); + expect(RUNTIME_SCOPE_USER, i4, "%U", uid); +} + +static uint64_t make_cap(int cap) { + return ((uint64_t) 1ULL << (uint64_t) cap); +} + +TEST(config_parse_capability_set) { + /* int config_parse_capability_set( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) */ + int r; + uint64_t capability_bounding_set = 0; + + r = config_parse_capability_set(NULL, "fake", 1, "section", 1, + "CapabilityBoundingSet", 0, "CAP_NET_RAW", + &capability_bounding_set, NULL); + assert_se(r >= 0); + assert_se(capability_bounding_set == make_cap(CAP_NET_RAW)); + + r = config_parse_capability_set(NULL, "fake", 1, "section", 1, + "CapabilityBoundingSet", 0, "CAP_NET_ADMIN", + &capability_bounding_set, NULL); + assert_se(r >= 0); + assert_se(capability_bounding_set == (make_cap(CAP_NET_RAW) | make_cap(CAP_NET_ADMIN))); + + r = config_parse_capability_set(NULL, "fake", 1, "section", 1, + "CapabilityBoundingSet", 0, "~CAP_NET_ADMIN", + &capability_bounding_set, NULL); + assert_se(r >= 0); + assert_se(capability_bounding_set == make_cap(CAP_NET_RAW)); + + r = config_parse_capability_set(NULL, "fake", 1, "section", 1, + "CapabilityBoundingSet", 0, "", + &capability_bounding_set, NULL); + assert_se(r >= 0); + assert_se(capability_bounding_set == UINT64_C(0)); + + r = config_parse_capability_set(NULL, "fake", 1, "section", 1, + "CapabilityBoundingSet", 0, "~", + &capability_bounding_set, NULL); + assert_se(r >= 0); + assert_se(cap_test_all(capability_bounding_set)); + + capability_bounding_set = 0; + r = config_parse_capability_set(NULL, "fake", 1, "section", 1, + "CapabilityBoundingSet", 0, " 'CAP_NET_RAW' WAT_CAP??? CAP_NET_ADMIN CAP'_trailing_garbage", + &capability_bounding_set, NULL); + assert_se(r >= 0); + assert_se(capability_bounding_set == (make_cap(CAP_NET_RAW) | make_cap(CAP_NET_ADMIN))); +} + +TEST(config_parse_rlimit) { + struct rlimit * rl[_RLIMIT_MAX] = {}; + + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "55", rl, NULL) >= 0); + assert_se(rl[RLIMIT_NOFILE]); + assert_se(rl[RLIMIT_NOFILE]->rlim_cur == 55); + assert_se(rl[RLIMIT_NOFILE]->rlim_cur == rl[RLIMIT_NOFILE]->rlim_max); + + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "55:66", rl, NULL) >= 0); + assert_se(rl[RLIMIT_NOFILE]); + assert_se(rl[RLIMIT_NOFILE]->rlim_cur == 55); + assert_se(rl[RLIMIT_NOFILE]->rlim_max == 66); + + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "infinity", rl, NULL) >= 0); + assert_se(rl[RLIMIT_NOFILE]); + assert_se(rl[RLIMIT_NOFILE]->rlim_cur == RLIM_INFINITY); + assert_se(rl[RLIMIT_NOFILE]->rlim_cur == rl[RLIMIT_NOFILE]->rlim_max); + + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "infinity:infinity", rl, NULL) >= 0); + assert_se(rl[RLIMIT_NOFILE]); + assert_se(rl[RLIMIT_NOFILE]->rlim_cur == RLIM_INFINITY); + assert_se(rl[RLIMIT_NOFILE]->rlim_cur == rl[RLIMIT_NOFILE]->rlim_max); + + rl[RLIMIT_NOFILE]->rlim_cur = 10; + rl[RLIMIT_NOFILE]->rlim_max = 20; + + /* Invalid values don't change rl */ + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "10:20:30", rl, NULL) >= 0); + assert_se(rl[RLIMIT_NOFILE]); + assert_se(rl[RLIMIT_NOFILE]->rlim_cur == 10); + assert_se(rl[RLIMIT_NOFILE]->rlim_max == 20); + + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "wat:wat", rl, NULL) >= 0); + assert_se(rl[RLIMIT_NOFILE]); + assert_se(rl[RLIMIT_NOFILE]->rlim_cur == 10); + assert_se(rl[RLIMIT_NOFILE]->rlim_max == 20); + + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "66:wat", rl, NULL) >= 0); + assert_se(rl[RLIMIT_NOFILE]); + assert_se(rl[RLIMIT_NOFILE]->rlim_cur == 10); + assert_se(rl[RLIMIT_NOFILE]->rlim_max == 20); + + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "200:100", rl, NULL) >= 0); + assert_se(rl[RLIMIT_NOFILE]); + assert_se(rl[RLIMIT_NOFILE]->rlim_cur == 10); + assert_se(rl[RLIMIT_NOFILE]->rlim_max == 20); + + rl[RLIMIT_NOFILE] = mfree(rl[RLIMIT_NOFILE]); + + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "56", rl, NULL) >= 0); + assert_se(rl[RLIMIT_CPU]); + assert_se(rl[RLIMIT_CPU]->rlim_cur == 56); + assert_se(rl[RLIMIT_CPU]->rlim_cur == rl[RLIMIT_CPU]->rlim_max); + + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "57s", rl, NULL) >= 0); + assert_se(rl[RLIMIT_CPU]); + assert_se(rl[RLIMIT_CPU]->rlim_cur == 57); + assert_se(rl[RLIMIT_CPU]->rlim_cur == rl[RLIMIT_CPU]->rlim_max); + + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "40s:1m", rl, NULL) >= 0); + assert_se(rl[RLIMIT_CPU]); + assert_se(rl[RLIMIT_CPU]->rlim_cur == 40); + assert_se(rl[RLIMIT_CPU]->rlim_max == 60); + + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "infinity", rl, NULL) >= 0); + assert_se(rl[RLIMIT_CPU]); + assert_se(rl[RLIMIT_CPU]->rlim_cur == RLIM_INFINITY); + assert_se(rl[RLIMIT_CPU]->rlim_cur == rl[RLIMIT_CPU]->rlim_max); + + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "1234ms", rl, NULL) >= 0); + assert_se(rl[RLIMIT_CPU]); + assert_se(rl[RLIMIT_CPU]->rlim_cur == 2); + assert_se(rl[RLIMIT_CPU]->rlim_cur == rl[RLIMIT_CPU]->rlim_max); + + rl[RLIMIT_CPU] = mfree(rl[RLIMIT_CPU]); + + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "58", rl, NULL) >= 0); + assert_se(rl[RLIMIT_RTTIME]); + assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 58); + assert_se(rl[RLIMIT_RTTIME]->rlim_cur == rl[RLIMIT_RTTIME]->rlim_max); + + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "58:60", rl, NULL) >= 0); + assert_se(rl[RLIMIT_RTTIME]); + assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 58); + assert_se(rl[RLIMIT_RTTIME]->rlim_max == 60); + + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "59s", rl, NULL) >= 0); + assert_se(rl[RLIMIT_RTTIME]); + assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 59 * USEC_PER_SEC); + assert_se(rl[RLIMIT_RTTIME]->rlim_cur == rl[RLIMIT_RTTIME]->rlim_max); + + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "59s:123s", rl, NULL) >= 0); + assert_se(rl[RLIMIT_RTTIME]); + assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 59 * USEC_PER_SEC); + assert_se(rl[RLIMIT_RTTIME]->rlim_max == 123 * USEC_PER_SEC); + + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "infinity", rl, NULL) >= 0); + assert_se(rl[RLIMIT_RTTIME]); + assert_se(rl[RLIMIT_RTTIME]->rlim_cur == RLIM_INFINITY); + assert_se(rl[RLIMIT_RTTIME]->rlim_cur == rl[RLIMIT_RTTIME]->rlim_max); + + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "infinity:infinity", rl, NULL) >= 0); + assert_se(rl[RLIMIT_RTTIME]); + assert_se(rl[RLIMIT_RTTIME]->rlim_cur == RLIM_INFINITY); + assert_se(rl[RLIMIT_RTTIME]->rlim_cur == rl[RLIMIT_RTTIME]->rlim_max); + + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "2345ms", rl, NULL) >= 0); + assert_se(rl[RLIMIT_RTTIME]); + assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 2345 * USEC_PER_MSEC); + assert_se(rl[RLIMIT_RTTIME]->rlim_cur == rl[RLIMIT_RTTIME]->rlim_max); + + rl[RLIMIT_RTTIME] = mfree(rl[RLIMIT_RTTIME]); +} + +TEST(config_parse_pass_environ) { + /* int config_parse_pass_environ( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) */ + int r; + _cleanup_strv_free_ char **passenv = NULL; + + r = config_parse_pass_environ(NULL, "fake", 1, "section", 1, + "PassEnvironment", 0, "A B", + &passenv, NULL); + assert_se(r >= 0); + assert_se(strv_length(passenv) == 2); + assert_se(streq(passenv[0], "A")); + assert_se(streq(passenv[1], "B")); + + r = config_parse_pass_environ(NULL, "fake", 1, "section", 1, + "PassEnvironment", 0, "", + &passenv, NULL); + assert_se(r >= 0); + assert_se(strv_isempty(passenv)); + + r = config_parse_pass_environ(NULL, "fake", 1, "section", 1, + "PassEnvironment", 0, "'invalid name' 'normal_name' A=1 'special_name$$' \\", + &passenv, NULL); + assert_se(r >= 0); + assert_se(strv_length(passenv) == 1); + assert_se(streq(passenv[0], "normal_name")); +} + +TEST(config_parse_unit_env_file) { + /* int config_parse_unit_env_file( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) */ + + _cleanup_(manager_freep) Manager *m = NULL; + Unit *u; + _cleanup_strv_free_ char **files = NULL; + int r; + + r = manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_MINIMAL, &m); + if (manager_errno_skip_test(r)) { + log_notice_errno(r, "Skipping test: manager_new: %m"); + return; + } + + assert_se(r >= 0); + assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + + assert_se(u = unit_new(m, sizeof(Service))); + assert_se(unit_add_name(u, "foobar.service") == 0); + + r = config_parse_unit_env_file(u->id, "fake", 1, "section", 1, + "EnvironmentFile", 0, "not-absolute", + &files, u); + assert_se(r == 0); + assert_se(strv_isempty(files)); + + r = config_parse_unit_env_file(u->id, "fake", 1, "section", 1, + "EnvironmentFile", 0, "/absolute1", + &files, u); + assert_se(r == 0); + assert_se(strv_length(files) == 1); + + r = config_parse_unit_env_file(u->id, "fake", 1, "section", 1, + "EnvironmentFile", 0, "/absolute2", + &files, u); + assert_se(r == 0); + assert_se(strv_length(files) == 2); + assert_se(streq(files[0], "/absolute1")); + assert_se(streq(files[1], "/absolute2")); + + r = config_parse_unit_env_file(u->id, "fake", 1, "section", 1, + "EnvironmentFile", 0, "", + &files, u); + assert_se(r == 0); + assert_se(strv_isempty(files)); + + r = config_parse_unit_env_file(u->id, "fake", 1, "section", 1, + "EnvironmentFile", 0, "/path/%n.conf", + &files, u); + assert_se(r == 0); + assert_se(strv_length(files) == 1); + assert_se(streq(files[0], "/path/foobar.service.conf")); +} + +TEST(unit_dump_config_items) { + unit_dump_config_items(stdout); +} + +TEST(config_parse_memory_limit) { + /* int config_parse_memory_limit( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) */ + CGroupContext c; + struct limit_test { + const char *limit; + const char *value; + uint64_t *result; + uint64_t expected; + } limit_tests[]= { + { "MemoryMin", "", &c.memory_min, CGROUP_LIMIT_MIN }, + { "MemoryMin", "0", &c.memory_min, CGROUP_LIMIT_MIN }, + { "MemoryMin", "10", &c.memory_min, 10 }, + { "MemoryMin", "infinity", &c.memory_min, CGROUP_LIMIT_MAX }, + { "MemoryLow", "", &c.memory_low, CGROUP_LIMIT_MIN }, + { "MemoryLow", "0", &c.memory_low, CGROUP_LIMIT_MIN }, + { "MemoryLow", "10", &c.memory_low, 10 }, + { "MemoryLow", "infinity", &c.memory_low, CGROUP_LIMIT_MAX }, + { "MemoryHigh", "", &c.memory_high, CGROUP_LIMIT_MAX }, + { "MemoryHigh", "0", &c.memory_high, CGROUP_LIMIT_DUMMY }, + { "MemoryHigh", "10", &c.memory_high, 10 }, + { "MemoryHigh", "infinity", &c.memory_high, CGROUP_LIMIT_MAX }, + { "MemoryMax", "", &c.memory_max, CGROUP_LIMIT_MAX }, + { "MemoryMax", "0", &c.memory_max, CGROUP_LIMIT_DUMMY }, + { "MemoryMax", "10", &c.memory_max, 10 }, + { "MemoryMax", "infinity", &c.memory_max, CGROUP_LIMIT_MAX }, + }; + size_t i; + int r; + + for (i = 0; i < ELEMENTSOF(limit_tests); i++) { + c.memory_min = CGROUP_LIMIT_DUMMY; + c.memory_low = CGROUP_LIMIT_DUMMY; + c.memory_high = CGROUP_LIMIT_DUMMY; + c.memory_max = CGROUP_LIMIT_DUMMY; + r = config_parse_memory_limit(NULL, "fake", 1, "section", 1, + limit_tests[i].limit, 1, + limit_tests[i].value, &c, NULL); + log_info("%s=%s\t%"PRIu64"==%"PRIu64, + limit_tests[i].limit, limit_tests[i].value, + *limit_tests[i].result, limit_tests[i].expected); + assert_se(r >= 0); + assert_se(*limit_tests[i].result == limit_tests[i].expected); + } + +} + +TEST(contains_instance_specifier_superset) { + assert_se(contains_instance_specifier_superset("foobar@a%i")); + assert_se(contains_instance_specifier_superset("foobar@%ia")); + assert_se(contains_instance_specifier_superset("foobar@%n")); + assert_se(contains_instance_specifier_superset("foobar@%n.service")); + assert_se(contains_instance_specifier_superset("foobar@%N")); + assert_se(contains_instance_specifier_superset("foobar@%N.service")); + assert_se(contains_instance_specifier_superset("foobar@baz.%N.service")); + assert_se(contains_instance_specifier_superset("@%N.service")); + assert_se(contains_instance_specifier_superset("@%N")); + assert_se(contains_instance_specifier_superset("@%a%N")); + + assert_se(!contains_instance_specifier_superset("foobar@%i.service")); + assert_se(!contains_instance_specifier_superset("foobar%ia.service")); + assert_se(!contains_instance_specifier_superset("foobar@%%n.service")); + assert_se(!contains_instance_specifier_superset("foobar@baz.service")); + assert_se(!contains_instance_specifier_superset("%N.service")); + assert_se(!contains_instance_specifier_superset("%N")); + assert_se(!contains_instance_specifier_superset("@%aN")); + assert_se(!contains_instance_specifier_superset("@%a%b")); +} + +TEST(unit_is_recursive_template_dependency) { + _cleanup_(manager_freep) Manager *m = NULL; + Unit *u; + int r; + + r = manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_MINIMAL, &m); + if (manager_errno_skip_test(r)) { + log_notice_errno(r, "Skipping test: manager_new: %m"); + return; + } + + assert_se(r >= 0); + assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + + assert_se(u = unit_new(m, sizeof(Service))); + assert_se(unit_add_name(u, "foobar@1.service") == 0); + u->fragment_path = strdup("/foobar@.service"); + + assert_se(hashmap_put_strdup(&m->unit_id_map, "foobar@foobar@123.service", "/foobar@.service")); + assert_se(hashmap_put_strdup(&m->unit_id_map, "foobar@foobar@456.service", "/custom.service")); + + /* Test that %n, %N and any extension of %i specifiers in the instance are detected as recursive. */ + assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.service", "foobar@%N.service") == 1); + assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.service", "foobar@%n.service") == 1); + assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.service", "foobar@a%i.service") == 1); + assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.service", "foobar@%ia.service") == 1); + assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.service", "foobar@%x%n.service") == 1); + /* Test that %i on its own is not detected as recursive. */ + assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.service", "foobar@%i.service") == 0); + /* Test that a specifier other than %i, %n and %N is not detected as recursive. */ + assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.service", "foobar@%xn.service") == 0); + /* Test that an expanded specifier is not detected as recursive. */ + assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.service", "foobar@foobar@123.service") == 0); + /* Test that a dependency with a custom fragment path is not detected as recursive. */ + assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@456.service", "foobar@%n.service") == 0); + /* Test that a dependency without a fragment path is not detected as recursive. */ + assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@789.service", "foobar@%n.service") == 0); + /* Test that a dependency with a different prefix is not detected as recursive. */ + assert_se(unit_is_likely_recursive_template_dependency(u, "quux@foobar@123.service", "quux@%n.service") == 0); + /* Test that a dependency of a different type is not detected as recursive. */ + assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.mount", "foobar@%n.mount") == 0); +} + +#define TEST_PATTERN(_regex, _allowed_patterns_count, _denied_patterns_count) \ + { \ + .regex = _regex, \ + .allowed_patterns_count = _allowed_patterns_count, \ + .denied_patterns_count = _denied_patterns_count \ + } + +TEST(config_parse_log_filter_patterns) { + ExecContext c = {}; + + static const struct { + const char *regex; + size_t allowed_patterns_count; + size_t denied_patterns_count; + } regex_tests[] = { + TEST_PATTERN("", 0, 0), + TEST_PATTERN(".*", 1, 0), + TEST_PATTERN("~.*", 1, 1), + TEST_PATTERN("", 0, 0), + TEST_PATTERN("~.*", 0, 1), + TEST_PATTERN("[.*", 0, 1), /* Invalid pattern. */ + TEST_PATTERN(".*gg.*", 1, 1), + TEST_PATTERN("~.*", 1, 1), /* Already in the patterns list. */ + TEST_PATTERN("[.*", 1, 1), /* Invalid pattern. */ + TEST_PATTERN("\\x7ehello", 2, 1), + TEST_PATTERN("", 0, 0), + TEST_PATTERN("~foobar", 0, 1), + }; + + if (ERRNO_IS_NOT_SUPPORTED(dlopen_pcre2())) + return (void) log_tests_skipped("PCRE2 support is not available"); + + for (size_t i = 0; i < ELEMENTSOF(regex_tests); i++) { + assert_se(config_parse_log_filter_patterns(NULL, "fake", 1, "section", 1, "LogFilterPatterns", 1, + regex_tests[i].regex, &c, NULL) >= 0); + + assert_se(set_size(c.log_filter_allowed_patterns) == regex_tests[i].allowed_patterns_count); + assert_se(set_size(c.log_filter_denied_patterns) == regex_tests[i].denied_patterns_count); + + /* Ensure `~` is properly removed */ + const char *p; + SET_FOREACH(p, c.log_filter_allowed_patterns) + assert_se(p && p[0] != '~'); + SET_FOREACH(p, c.log_filter_denied_patterns) + assert_se(p && p[0] != '~'); + } + + exec_context_done(&c); +} + +TEST(config_parse_open_file) { + _cleanup_(manager_freep) Manager *m = NULL; + _cleanup_(unit_freep) Unit *u = NULL; + _cleanup_(open_file_freep) OpenFile *of = NULL; + int r; + + r = manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_MINIMAL, &m); + if (manager_errno_skip_test(r)) { + log_notice_errno(r, "Skipping test: manager_new: %m"); + return; + } + + assert_se(r >= 0); + assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + + assert_se(u = unit_new(m, sizeof(Service))); + assert_se(unit_add_name(u, "foobar.service") == 0); + + r = config_parse_open_file(NULL, "fake", 1, "section", 1, + "OpenFile", 0, "/proc/1/ns/mnt:host-mount-namespace:read-only", + &of, u); + assert_se(r >= 0); + assert_se(of); + assert_se(streq(of->path, "/proc/1/ns/mnt")); + assert_se(streq(of->fdname, "host-mount-namespace")); + assert_se(of->flags == OPENFILE_READ_ONLY); + + of = open_file_free(of); + r = config_parse_open_file(NULL, "fake", 1, "section", 1, + "OpenFile", 0, "/proc/1/ns/mnt::read-only", + &of, u); + assert_se(r >= 0); + assert_se(of); + assert_se(streq(of->path, "/proc/1/ns/mnt")); + assert_se(streq(of->fdname, "mnt")); + assert_se(of->flags == OPENFILE_READ_ONLY); + + r = config_parse_open_file(NULL, "fake", 1, "section", 1, + "OpenFile", 0, "", + &of, u); + assert_se(r >= 0); + assert_se(!of); +} + +static int intro(void) { + if (enter_cgroup_subroot(NULL) == -ENOMEDIUM) + return log_tests_skipped("cgroupfs not available"); + + assert_se(runtime_dir = setup_fake_runtime_dir()); + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/test/test-local-addresses.c b/src/test/test-local-addresses.c new file mode 100644 index 0000000..5a02465 --- /dev/null +++ b/src/test/test-local-addresses.c @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdio.h> + +#include "af-list.h" +#include "alloc-util.h" +#include "in-addr-util.h" +#include "local-addresses.h" +#include "tests.h" + +static void print_local_addresses(struct local_address *a, unsigned n) { + for (unsigned i = 0; i < n; i++) { + _cleanup_free_ char *b = NULL; + + assert_se(in_addr_to_string(a[i].family, &a[i].address, &b) >= 0); + log_debug("%s if%i scope=%i metric=%u address=%s", af_to_name(a[i].family), a[i].ifindex, a[i].scope, a[i].metric, b); + } +} + +TEST(local_addresses) { + struct local_address *a = NULL; + int n; + + n = local_addresses(NULL, 0, AF_INET, &a); + assert_se(n >= 0); + log_debug("/* Local Addresses(ifindex:0, AF_INET) */"); + print_local_addresses(a, (unsigned) n); + a = mfree(a); + + n = local_addresses(NULL, 0, AF_INET6, &a); + assert_se(n >= 0); + log_debug("/* Local Addresses(ifindex:0, AF_INET6) */"); + print_local_addresses(a, (unsigned) n); + a = mfree(a); + + n = local_addresses(NULL, 0, AF_UNSPEC, &a); + assert_se(n >= 0); + log_debug("/* Local Addresses(ifindex:0, AF_UNSPEC) */"); + print_local_addresses(a, (unsigned) n); + a = mfree(a); + + n = local_addresses(NULL, 1, AF_INET, &a); + assert_se(n >= 0); + log_debug("/* Local Addresses(ifindex:1, AF_INET) */"); + print_local_addresses(a, (unsigned) n); + a = mfree(a); + + n = local_addresses(NULL, 1, AF_INET6, &a); + assert_se(n >= 0); + log_debug("/* Local Addresses(ifindex:1, AF_INET6) */"); + print_local_addresses(a, (unsigned) n); + a = mfree(a); + + n = local_addresses(NULL, 1, AF_UNSPEC, &a); + assert_se(n >= 0); + log_debug("/* Local Addresses(ifindex:1, AF_UNSPEC) */"); + print_local_addresses(a, (unsigned) n); + a = mfree(a); + + n = local_gateways(NULL, 0, AF_UNSPEC, &a); + assert_se(n >= 0); + log_debug("/* Local Gateways */"); + print_local_addresses(a, (unsigned) n); + a = mfree(a); + + n = local_outbounds(NULL, 0, AF_UNSPEC, &a); + assert_se(n >= 0); + log_debug("/* Local Outbounds */"); + print_local_addresses(a, (unsigned) n); + free(a); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-locale-util.c b/src/test/test-locale-util.c new file mode 100644 index 0000000..39f71c6 --- /dev/null +++ b/src/test/test-locale-util.c @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "glyph-util.h" +#include "kbd-util.h" +#include "locale-util.h" +#include "macro.h" +#include "strv.h" +#include "tests.h" + +TEST(get_locales) { + _cleanup_strv_free_ char **locales = NULL; + int r; + + r = get_locales(&locales); + assert_se(r >= 0); + assert_se(locales); + + STRV_FOREACH(p, locales) { + puts(*p); + assert_se(locale_is_valid(*p)); + } +} + +TEST(locale_is_valid) { + assert_se(locale_is_valid("en_EN.utf8")); + assert_se(locale_is_valid("fr_FR.utf8")); + assert_se(locale_is_valid("fr_FR@euro")); + assert_se(locale_is_valid("fi_FI")); + assert_se(locale_is_valid("POSIX")); + assert_se(locale_is_valid("C")); + + assert_se(!locale_is_valid("")); + assert_se(!locale_is_valid("/usr/bin/foo")); + assert_se(!locale_is_valid("\x01gar\x02 bage\x03")); +} + +TEST(locale_is_installed) { + /* Always available */ + assert_se(locale_is_installed("POSIX") > 0); + assert_se(locale_is_installed("C") > 0); + + /* Might, or might not be installed. */ + assert_se(locale_is_installed("en_EN.utf8") >= 0); + assert_se(locale_is_installed("fr_FR.utf8") >= 0); + assert_se(locale_is_installed("fr_FR@euro") >= 0); + assert_se(locale_is_installed("fi_FI") >= 0); + + /* Definitely not valid */ + assert_se(locale_is_installed("") == 0); + assert_se(locale_is_installed("/usr/bin/foo") == 0); + assert_se(locale_is_installed("\x01gar\x02 bage\x03") == 0); + + /* Definitely not installed */ + assert_se(locale_is_installed("zz_ZZ") == 0); +} + +TEST(keymaps) { + _cleanup_strv_free_ char **kmaps = NULL; + int r; + + assert_se(!keymap_is_valid("")); + assert_se(!keymap_is_valid("/usr/bin/foo")); + assert_se(!keymap_is_valid("\x01gar\x02 bage\x03")); + + r = get_keymaps(&kmaps); + if (r == -ENOENT) + return; /* skip test if no keymaps are installed */ + + assert_se(r >= 0); + assert_se(kmaps); + + STRV_FOREACH(p, kmaps) { + puts(*p); + assert_se(keymap_is_valid(*p)); + } + + assert_se(keymap_is_valid("uk")); + assert_se(keymap_is_valid("de-nodeadkeys")); + assert_se(keymap_is_valid("ANSI-dvorak")); + assert_se(keymap_is_valid("unicode")); +} + +#define dump_glyph(x) log_info(STRINGIFY(x) ": %s", special_glyph(x)) +TEST(dump_special_glyphs) { + assert_cc(SPECIAL_GLYPH_WORLD + 1 == _SPECIAL_GLYPH_MAX); + + log_info("is_locale_utf8: %s", yes_no(is_locale_utf8())); + + dump_glyph(SPECIAL_GLYPH_TREE_VERTICAL); + dump_glyph(SPECIAL_GLYPH_TREE_BRANCH); + dump_glyph(SPECIAL_GLYPH_TREE_RIGHT); + dump_glyph(SPECIAL_GLYPH_TREE_SPACE); + dump_glyph(SPECIAL_GLYPH_TREE_TOP); + dump_glyph(SPECIAL_GLYPH_VERTICAL_DOTTED); + dump_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET); + dump_glyph(SPECIAL_GLYPH_BLACK_CIRCLE); + dump_glyph(SPECIAL_GLYPH_WHITE_CIRCLE); + dump_glyph(SPECIAL_GLYPH_MULTIPLICATION_SIGN); + dump_glyph(SPECIAL_GLYPH_CIRCLE_ARROW); + dump_glyph(SPECIAL_GLYPH_BULLET); + dump_glyph(SPECIAL_GLYPH_MU); + dump_glyph(SPECIAL_GLYPH_CHECK_MARK); + dump_glyph(SPECIAL_GLYPH_CROSS_MARK); + dump_glyph(SPECIAL_GLYPH_LIGHT_SHADE); + dump_glyph(SPECIAL_GLYPH_DARK_SHADE); + dump_glyph(SPECIAL_GLYPH_FULL_BLOCK); + dump_glyph(SPECIAL_GLYPH_SIGMA); + dump_glyph(SPECIAL_GLYPH_ARROW_UP); + dump_glyph(SPECIAL_GLYPH_ARROW_DOWN); + dump_glyph(SPECIAL_GLYPH_ARROW_LEFT); + dump_glyph(SPECIAL_GLYPH_ARROW_RIGHT); + dump_glyph(SPECIAL_GLYPH_ELLIPSIS); + dump_glyph(SPECIAL_GLYPH_EXTERNAL_LINK); + dump_glyph(SPECIAL_GLYPH_ECSTATIC_SMILEY); + dump_glyph(SPECIAL_GLYPH_HAPPY_SMILEY); + dump_glyph(SPECIAL_GLYPH_SLIGHTLY_HAPPY_SMILEY); + dump_glyph(SPECIAL_GLYPH_NEUTRAL_SMILEY); + dump_glyph(SPECIAL_GLYPH_SLIGHTLY_UNHAPPY_SMILEY); + dump_glyph(SPECIAL_GLYPH_UNHAPPY_SMILEY); + dump_glyph(SPECIAL_GLYPH_DEPRESSED_SMILEY); + dump_glyph(SPECIAL_GLYPH_LOCK_AND_KEY); + dump_glyph(SPECIAL_GLYPH_TOUCH); + dump_glyph(SPECIAL_GLYPH_RECYCLING); + dump_glyph(SPECIAL_GLYPH_DOWNLOAD); + dump_glyph(SPECIAL_GLYPH_SPARKLES); + dump_glyph(SPECIAL_GLYPH_LOW_BATTERY); + dump_glyph(SPECIAL_GLYPH_WARNING_SIGN); + dump_glyph(SPECIAL_GLYPH_COMPUTER_DISK); + dump_glyph(SPECIAL_GLYPH_WORLD); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-lock-util.c b/src/test/test-lock-util.c new file mode 100644 index 0000000..5edd087 --- /dev/null +++ b/src/test/test-lock-util.c @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <sys/file.h> +#include <unistd.h> + +#include "fd-util.h" +#include "lock-util.h" +#include "rm-rf.h" +#include "tests.h" +#include "tmpfile-util.h" + +TEST(make_lock_file) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int tfd = -EBADF; + _cleanup_(release_lock_file) LockFile lock1 = LOCK_FILE_INIT, lock2 = LOCK_FILE_INIT; + + assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0); + + assert_se(make_lock_file_at(tfd, "lock", LOCK_EX, &lock1) >= 0); + assert_se(faccessat(tfd, "lock", F_OK, 0) >= 0); + assert_se(make_lock_file_at(tfd, "lock", LOCK_EX|LOCK_NB, &lock2) == -EBUSY); + release_lock_file(&lock1); + assert_se(RET_NERRNO(faccessat(tfd, "lock", F_OK, 0)) == -ENOENT); + assert_se(make_lock_file_at(tfd, "lock", LOCK_EX, &lock2) >= 0); + release_lock_file(&lock2); + assert_se(make_lock_file_at(tfd, "lock", LOCK_SH, &lock1) >= 0); + assert_se(faccessat(tfd, "lock", F_OK, 0) >= 0); + assert_se(make_lock_file_at(tfd, "lock", LOCK_SH, &lock2) >= 0); + release_lock_file(&lock1); + assert_se(faccessat(tfd, "lock", F_OK, 0) >= 0); + release_lock_file(&lock2); + + assert_se(fchdir(tfd) >= 0); + assert_se(make_lock_file_at(tfd, "lock", LOCK_EX, &lock1) >= 0); + assert_se(make_lock_file("lock", LOCK_EX|LOCK_NB, &lock2) == -EBUSY); +} + +static void test_lock_generic_with_timeout_for_type(LockType type) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int tfd = -EBADF, tfd2 = -EBADF; + + tfd = mkdtemp_open(NULL, 0, &t); + assert_se(tfd >= 0); + + tfd2 = fd_reopen(tfd, O_CLOEXEC|O_DIRECTORY); + assert_se(tfd2 >= 0); + + assert_se(lock_generic(tfd, LOCK_BSD, LOCK_EX) >= 0); + assert_se(lock_generic(tfd2, LOCK_BSD, LOCK_EX|LOCK_NB) == -EWOULDBLOCK); + + usec_t start = now(CLOCK_MONOTONIC); + assert_se(lock_generic_with_timeout(tfd2, LOCK_BSD, LOCK_EX, 200 * USEC_PER_MSEC) == -ETIMEDOUT); + assert_se(usec_sub_unsigned(now(CLOCK_MONOTONIC), start) >= 200 * USEC_PER_MSEC); + + assert_se(lock_generic(tfd, LOCK_BSD, LOCK_UN) >= 0); + assert_se(lock_generic_with_timeout(tfd2, LOCK_BSD, LOCK_EX, 200 * USEC_PER_MSEC) == 0); + assert_se(lock_generic(tfd, LOCK_BSD, LOCK_EX|LOCK_NB) == -EWOULDBLOCK); +} + +TEST(lock_generic_with_timeout) { + test_lock_generic_with_timeout_for_type(LOCK_BSD); + test_lock_generic_with_timeout_for_type(LOCK_UNPOSIX); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-log.c b/src/test/test-log.c new file mode 100644 index 0000000..b5ba67b --- /dev/null +++ b/src/test/test-log.c @@ -0,0 +1,226 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stddef.h> +#include <unistd.h> + +#include "format-util.h" +#include "io-util.h" +#include "iovec-util.h" +#include "iovec-wrapper.h" +#include "log.h" +#include "process-util.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" + +assert_cc(IS_SYNTHETIC_ERRNO(SYNTHETIC_ERRNO(EINVAL))); +assert_cc(!IS_SYNTHETIC_ERRNO(EINVAL)); +assert_cc(IS_SYNTHETIC_ERRNO(SYNTHETIC_ERRNO(0))); +assert_cc(!IS_SYNTHETIC_ERRNO(0)); + +#define X10(x) x x x x x x x x x x +#define X100(x) X10(X10(x)) +#define X1000(x) X100(X10(x)) + +static void test_file(void) { + log_info("__FILE__: %s", __FILE__); + log_info("RELATIVE_SOURCE_PATH: %s", RELATIVE_SOURCE_PATH); + log_info("PROJECT_FILE: %s", PROJECT_FILE); + + assert_se(startswith(__FILE__, RELATIVE_SOURCE_PATH "/")); +} + +static void test_log_struct(void) { + log_struct(LOG_INFO, + "MESSAGE=Waldo PID="PID_FMT" (no errno)", getpid_cached(), + "SERVICE=piepapo"); + + /* The same as above, just using LOG_MESSAGE(), which is generally recommended */ + log_struct(LOG_INFO, + LOG_MESSAGE("Waldo PID="PID_FMT" (no errno)", getpid_cached()), + "SERVICE=piepapo"); + + log_struct_errno(LOG_INFO, EILSEQ, + LOG_MESSAGE("Waldo PID="PID_FMT": %m (normal)", getpid_cached()), + "SERVICE=piepapo"); + + log_struct_errno(LOG_INFO, SYNTHETIC_ERRNO(EILSEQ), + LOG_MESSAGE("Waldo PID="PID_FMT": %m (synthetic)", getpid_cached()), + "SERVICE=piepapo"); + + log_struct(LOG_INFO, + LOG_MESSAGE("Foobar PID="PID_FMT, getpid_cached()), + "FORMAT_STR_TEST=1=%i A=%c 2=%hi 3=%li 4=%lli 1=%p foo=%s 2.5=%g 3.5=%g 4.5=%Lg", + (int) 1, 'A', (short) 2, (long int) 3, (long long int) 4, (void*) 1, "foo", (float) 2.5f, (double) 3.5, (long double) 4.5, + "SUFFIX=GOT IT"); +} + +static void test_long_lines(void) { + log_object_internal(LOG_NOTICE, + EUCLEAN, + X1000("abcd_") ".txt", + 1000000, + X1000("fff") "unc", + "OBJECT=", + X1000("obj_") "ect", + "EXTRA=", + X1000("ext_") "tra", + "asdfasdf %s asdfasdfa", "foobar"); +} + +static void test_log_syntax(void) { + assert_se(log_syntax("unit", LOG_ERR, "filename", 10, EINVAL, "EINVAL: %s: %m", "hogehoge") == -EINVAL); + assert_se(log_syntax("unit", LOG_ERR, "filename", 10, -ENOENT, "ENOENT: %s: %m", "hogehoge") == -ENOENT); + assert_se(log_syntax("unit", LOG_ERR, "filename", 10, SYNTHETIC_ERRNO(ENOTTY), "ENOTTY: %s: %m", "hogehoge") == -ENOTTY); +} + +static void test_log_context(void) { + { + char **strv = STRV_MAKE("FIRST=abc", "SECOND=qrs"); + + LOG_CONTEXT_PUSH("THIRD=pfs"); + LOG_CONTEXT_PUSH("FOURTH=def"); + LOG_CONTEXT_PUSH_STRV(strv); + LOG_CONTEXT_PUSH_STRV(strv); + + /* Test that the log context was set up correctly. The strv we pushed twice should only + * result in one log context which is reused. */ + assert_se(log_context_num_contexts() == 3); + assert_se(log_context_num_fields() == 4); + + /* Test that everything still works with modifications to the log context. */ + test_log_struct(); + test_long_lines(); + test_log_syntax(); + + { + LOG_CONTEXT_PUSH("FIFTH=123"); + LOG_CONTEXT_PUSH_STRV(strv); + + /* Check that our nested fields got added correctly. */ + assert_se(log_context_num_contexts() == 4); + assert_se(log_context_num_fields() == 5); + + /* Test that everything still works in a nested block. */ + test_log_struct(); + test_long_lines(); + test_log_syntax(); + } + + /* Check that only the fields from the nested block got removed. */ + assert_se(log_context_num_contexts() == 3); + assert_se(log_context_num_fields() == 4); + } + + assert_se(log_context_num_contexts() == 0); + assert_se(log_context_num_fields() == 0); + + { + _cleanup_(log_context_unrefp) LogContext *ctx = NULL; + + char **strv = STRV_MAKE("SIXTH=ijn", "SEVENTH=PRP"); + assert_se(ctx = log_context_new_strv(strv, /*owned=*/ false)); + + assert_se(log_context_num_contexts() == 1); + assert_se(log_context_num_fields() == 2); + + /* Test that everything still works with a manually configured log context. */ + test_log_struct(); + test_long_lines(); + test_log_syntax(); + } + + { + char **strv = NULL; + + assert_se(strv = strv_new("ABC", "DEF")); + LOG_CONTEXT_CONSUME_STRV(strv); + + assert_se(log_context_num_contexts() == 1); + assert_se(log_context_num_fields() == 2); + } + + { + /* Test that everything still works with a mixed strv and iov. */ + struct iovec iov[] = { + IOVEC_MAKE_STRING("ABC=def"), + IOVEC_MAKE_STRING("GHI=jkl"), + }; + _cleanup_free_ struct iovec_wrapper *iovw = iovw_new(); + assert_se(iovw); + assert_se(iovw_consume(iovw, strdup("MNO=pqr"), STRLEN("MNO=pqr") + 1) == 0); + + LOG_CONTEXT_PUSH_IOV(iov, ELEMENTSOF(iov)); + LOG_CONTEXT_PUSH_IOV(iov, ELEMENTSOF(iov)); + LOG_CONTEXT_CONSUME_IOV(iovw->iovec, iovw->count); + LOG_CONTEXT_PUSH("STU=vwx"); + + assert_se(log_context_num_contexts() == 3); + assert_se(log_context_num_fields() == 4); + + test_log_struct(); + test_long_lines(); + test_log_syntax(); + } + + { + LOG_CONTEXT_PUSH_KEY_VALUE("ABC=", "QED"); + LOG_CONTEXT_PUSH_KEY_VALUE("ABC=", "QED"); + assert_se(log_context_num_contexts() == 1); + assert_se(log_context_num_fields() == 1); + + test_log_struct(); + test_long_lines(); + test_log_syntax(); + } + + assert_se(log_context_num_contexts() == 0); + assert_se(log_context_num_fields() == 0); +} + +static void test_log_prefix(void) { + { + LOG_SET_PREFIX("ABC"); + + test_log_struct(); + test_long_lines(); + test_log_syntax(); + + { + LOG_SET_PREFIX("QED"); + + test_log_struct(); + test_long_lines(); + test_log_syntax(); + } + + test_log_struct(); + test_long_lines(); + test_log_syntax(); + } + + test_log_struct(); + test_long_lines(); + test_log_syntax(); +} + +int main(int argc, char* argv[]) { + test_setup_logging(LOG_DEBUG); + + test_file(); + + assert_se(log_info_errno(SYNTHETIC_ERRNO(EUCLEAN), "foo") == -EUCLEAN); + + for (int target = 0; target < _LOG_TARGET_MAX; target++) { + log_set_target(target); + log_open(); + + test_log_struct(); + test_long_lines(); + test_log_syntax(); + test_log_context(); + test_log_prefix(); + } + + return 0; +} diff --git a/src/test/test-logarithm.c b/src/test/test-logarithm.c new file mode 100644 index 0000000..b35fea9 --- /dev/null +++ b/src/test/test-logarithm.c @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "logarithm.h" +#include "tests.h" + +TEST(LOG2ULL) { + assert_se(LOG2ULL(0) == 0); + assert_se(LOG2ULL(1) == 0); + assert_se(LOG2ULL(8) == 3); + assert_se(LOG2ULL(9) == 3); + assert_se(LOG2ULL(15) == 3); + assert_se(LOG2ULL(16) == 4); + assert_se(LOG2ULL(1024*1024) == 20); + assert_se(LOG2ULL(1024*1024+5) == 20); +} + +TEST(CONST_LOG2ULL) { + assert_se(CONST_LOG2ULL(0) == 0); + assert_se(CONST_LOG2ULL(1) == 0); + assert_se(CONST_LOG2ULL(8) == 3); + assert_se(CONST_LOG2ULL(9) == 3); + assert_se(CONST_LOG2ULL(15) == 3); + assert_se(CONST_LOG2ULL(16) == 4); + assert_se(CONST_LOG2ULL(1024*1024) == 20); + assert_se(CONST_LOG2ULL(1024*1024+5) == 20); +} + +TEST(NONCONST_LOG2ULL) { + assert_se(NONCONST_LOG2ULL(0) == 0); + assert_se(NONCONST_LOG2ULL(1) == 0); + assert_se(NONCONST_LOG2ULL(8) == 3); + assert_se(NONCONST_LOG2ULL(9) == 3); + assert_se(NONCONST_LOG2ULL(15) == 3); + assert_se(NONCONST_LOG2ULL(16) == 4); + assert_se(NONCONST_LOG2ULL(1024*1024) == 20); + assert_se(NONCONST_LOG2ULL(1024*1024+5) == 20); +} + +TEST(log2u64) { + assert_se(log2u64(0) == 0); + assert_se(log2u64(1) == 0); + assert_se(log2u64(8) == 3); + assert_se(log2u64(9) == 3); + assert_se(log2u64(15) == 3); + assert_se(log2u64(16) == 4); + assert_se(log2u64(1024*1024) == 20); + assert_se(log2u64(1024*1024+5) == 20); +} + +TEST(log2u) { + assert_se(log2u(0) == 0); + assert_se(log2u(1) == 0); + assert_se(log2u(2) == 1); + assert_se(log2u(3) == 1); + assert_se(log2u(4) == 2); + assert_se(log2u(32) == 5); + assert_se(log2u(33) == 5); + assert_se(log2u(63) == 5); + assert_se(log2u(INT_MAX) == sizeof(int)*8-2); +} + +TEST(log2i) { + assert_se(log2i(0) == 0); + assert_se(log2i(1) == 0); + assert_se(log2i(2) == 1); + assert_se(log2i(3) == 1); + assert_se(log2i(4) == 2); + assert_se(log2i(32) == 5); + assert_se(log2i(33) == 5); + assert_se(log2i(63) == 5); + assert_se(log2i(INT_MAX) == sizeof(int)*8-2); +} + +TEST(popcount) { + uint16_t u16a = 0x0000; + uint16_t u16b = 0xFFFF; + uint32_t u32a = 0x00000010; + uint32_t u32b = 0xFFFFFFFF; + uint64_t u64a = 0x0000000000000010; + uint64_t u64b = 0x0100000000100010; + + assert_se(popcount(u16a) == 0); + assert_se(popcount(u16b) == 16); + assert_se(popcount(u32a) == 1); + assert_se(popcount(u32b) == 32); + assert_se(popcount(u64a) == 1); + assert_se(popcount(u64b) == 3); + + /* This would fail: + * error: ‘_Generic’ selector of type ‘int’ is not compatible with any association + * assert_se(popcount(0x10) == 1); + */ +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-loop-block.c b/src/test/test-loop-block.c new file mode 100644 index 0000000..1bd00d1 --- /dev/null +++ b/src/test/test-loop-block.c @@ -0,0 +1,350 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <fcntl.h> +#include <linux/loop.h> +#include <pthread.h> +#include <sys/file.h> +#include <sys/ioctl.h> +#include <sys/mount.h> + +#include "alloc-util.h" +#include "capability-util.h" +#include "dissect-image.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "gpt.h" +#include "main-func.h" +#include "missing_loop.h" +#include "mkfs-util.h" +#include "mount-util.h" +#include "namespace-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" +#include "tmpfile-util.h" +#include "user-util.h" +#include "virt.h" + +static unsigned arg_n_threads = 5; +static unsigned arg_n_iterations = 3; +static usec_t arg_timeout = 0; + +#if HAVE_BLKID +static usec_t end = 0; + +static void verify_dissected_image(DissectedImage *dissected) { + assert_se(dissected->partitions[PARTITION_ESP].found); + assert_se(dissected->partitions[PARTITION_ESP].node); + assert_se(dissected->partitions[PARTITION_XBOOTLDR].found); + assert_se(dissected->partitions[PARTITION_XBOOTLDR].node); + assert_se(dissected->partitions[PARTITION_ROOT].found); + assert_se(dissected->partitions[PARTITION_ROOT].node); + assert_se(dissected->partitions[PARTITION_HOME].found); + assert_se(dissected->partitions[PARTITION_HOME].node); +} + +static void verify_dissected_image_harder(DissectedImage *dissected) { + verify_dissected_image(dissected); + + assert_se(streq(dissected->partitions[PARTITION_ESP].fstype, "vfat")); + assert_se(streq(dissected->partitions[PARTITION_XBOOTLDR].fstype, "vfat")); + assert_se(streq(dissected->partitions[PARTITION_ROOT].fstype, "ext4")); + assert_se(streq(dissected->partitions[PARTITION_HOME].fstype, "ext4")); +} + +static void* thread_func(void *ptr) { + int fd = PTR_TO_FD(ptr); + int r; + + for (unsigned i = 0; i < arg_n_iterations; i++) { + _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL; + _cleanup_(umount_and_rmdir_and_freep) char *mounted = NULL; + _cleanup_(dissected_image_unrefp) DissectedImage *dissected = NULL; + + if (now(CLOCK_MONOTONIC) >= end) { + log_notice("Time's up, exiting thread's loop"); + break; + } + + log_notice("> Thread iteration #%u.", i); + + assert_se(mkdtemp_malloc(NULL, &mounted) >= 0); + + r = loop_device_make(fd, O_RDONLY, 0, UINT64_MAX, 0, LO_FLAGS_PARTSCAN, LOCK_SH, &loop); + if (r < 0) + log_error_errno(r, "Failed to allocate loopback device: %m"); + assert_se(r >= 0); + assert_se(loop->dev); + assert_se(loop->backing_file); + + log_notice("Acquired loop device %s, will mount on %s", loop->node, mounted); + + r = dissect_loop_device(loop, NULL, NULL, NULL, DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected); + if (r < 0) + log_error_errno(r, "Failed dissect loopback device %s: %m", loop->node); + assert_se(r >= 0); + + log_info("Dissected loop device %s", loop->node); + + for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) { + if (!dissected->partitions[d].found) + continue; + + log_notice("Found node %s fstype %s designator %s", + dissected->partitions[d].node, + dissected->partitions[d].fstype, + partition_designator_to_string(d)); + } + + verify_dissected_image(dissected); + + r = dissected_image_mount( + dissected, + mounted, + /* uid_shift= */ UID_INVALID, + /* uid_range= */ UID_INVALID, + /* userns_fd= */ -EBADF, + DISSECT_IMAGE_READ_ONLY); + log_notice_errno(r, "Mounted %s → %s: %m", loop->node, mounted); + assert_se(r >= 0); + + /* Now the block device is mounted, we don't need no manual lock anymore, the devices are now + * pinned by the mounts. */ + assert_se(loop_device_flock(loop, LOCK_UN) >= 0); + + log_notice("Unmounting %s", mounted); + mounted = umount_and_rmdir_and_free(mounted); + + log_notice("Unmounted."); + + dissected = dissected_image_unref(dissected); + + log_notice("Detaching loop device %s", loop->node); + loop = loop_device_unref(loop); + log_notice("Detached loop device."); + } + + log_notice("Leaving thread"); + + return NULL; +} +#endif + +static bool have_root_gpt_type(void) { +#ifdef SD_GPT_ROOT_NATIVE + return true; +#else + return false; +#endif +} + +static int run(int argc, char *argv[]) { +#if HAVE_BLKID + _cleanup_(dissected_image_unrefp) DissectedImage *dissected = NULL; + _cleanup_(umount_and_rmdir_and_freep) char *mounted = NULL; + pthread_t threads[arg_n_threads]; + sd_id128_t id; +#endif + _cleanup_free_ char *p = NULL, *cmd = NULL; + _cleanup_pclose_ FILE *sfdisk = NULL; + _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL; + _cleanup_close_ int fd = -EBADF; + int r; + + test_setup_logging(LOG_DEBUG); + log_show_tid(true); + log_show_time(true); + log_show_color(true); + + if (argc >= 2) { + r = safe_atou(argv[1], &arg_n_threads); + if (r < 0) + return log_error_errno(r, "Failed to parse first argument (number of threads): %s", argv[1]); + if (arg_n_threads <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Number of threads must be at least 1, refusing."); + } + + if (argc >= 3) { + r = safe_atou(argv[2], &arg_n_iterations); + if (r < 0) + return log_error_errno(r, "Failed to parse second argument (number of iterations): %s", argv[2]); + if (arg_n_iterations <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Number of iterations must be at least 1, refusing."); + } + + if (argc >= 4) { + r = parse_sec(argv[3], &arg_timeout); + if (r < 0) + return log_error_errno(r, "Failed to parse third argument (timeout): %s", argv[3]); + } + + if (argc >= 5) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments (expected 3 at max)."); + + if (!have_root_gpt_type()) + return log_tests_skipped("No root partition GPT defined for this architecture"); + + r = find_executable("sfdisk", NULL); + if (r < 0) + return log_tests_skipped_errno(r, "Could not find sfdisk command"); + + assert_se(tempfn_random_child("/var/tmp", "sfdisk", &p) >= 0); + fd = open(p, O_CREAT|O_EXCL|O_RDWR|O_CLOEXEC|O_NOFOLLOW, 0666); + assert_se(fd >= 0); + assert_se(ftruncate(fd, 256*1024*1024) >= 0); + + assert_se(cmd = strjoin("sfdisk ", p)); + assert_se(sfdisk = popen(cmd, "we")); + + /* A reasonably complex partition table that fits on a 64K disk */ + fputs("label: gpt\n" + "size=32M, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B\n" + "size=32M, type=BC13C2FF-59E6-4262-A352-B275FD6F7172\n" + "size=32M, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F\n" + "size=32M, type=", sfdisk); + +#ifdef SD_GPT_ROOT_NATIVE + fprintf(sfdisk, SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(SD_GPT_ROOT_NATIVE)); +#else + fprintf(sfdisk, SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(SD_GPT_ROOT_X86_64)); +#endif + + fputs("\n" + "size=32M, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915\n", sfdisk); + + assert_se(pclose(sfdisk) == 0); + sfdisk = NULL; + +#if HAVE_BLKID + assert_se(dissect_image_file(p, NULL, NULL, NULL, 0, &dissected) >= 0); + verify_dissected_image(dissected); + dissected = dissected_image_unref(dissected); +#endif + + if (geteuid() != 0 || have_effective_cap(CAP_SYS_ADMIN) <= 0) { + log_tests_skipped("not running privileged"); + return 0; + } + + if (detect_container() > 0) { + log_tests_skipped("Test not supported in a container, requires udev/uevent notifications"); + return 0; + } + + assert_se(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 0, LO_FLAGS_PARTSCAN, LOCK_EX, &loop) >= 0); + +#if HAVE_BLKID + assert_se(dissect_loop_device(loop, NULL, NULL, NULL, DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected) >= 0); + verify_dissected_image(dissected); + + FOREACH_STRING(fs, "vfat", "ext4") { + r = mkfs_exists(fs); + assert_se(r >= 0); + if (!r) { + log_tests_skipped("mkfs.{vfat|ext4} not installed"); + return 0; + } + } + assert_se(r >= 0); + + assert_se(sd_id128_randomize(&id) >= 0); + assert_se(make_filesystem(dissected->partitions[PARTITION_ESP].node, "vfat", "EFI", NULL, id, true, false, 0, NULL) >= 0); + + assert_se(sd_id128_randomize(&id) >= 0); + assert_se(make_filesystem(dissected->partitions[PARTITION_XBOOTLDR].node, "vfat", "xbootldr", NULL, id, true, false, 0, NULL) >= 0); + + assert_se(sd_id128_randomize(&id) >= 0); + assert_se(make_filesystem(dissected->partitions[PARTITION_ROOT].node, "ext4", "root", NULL, id, true, false, 0, NULL) >= 0); + + assert_se(sd_id128_randomize(&id) >= 0); + assert_se(make_filesystem(dissected->partitions[PARTITION_HOME].node, "ext4", "home", NULL, id, true, false, 0, NULL) >= 0); + + dissected = dissected_image_unref(dissected); + + /* We created the file systems now via the per-partition block devices. But the dissection code might + * probe them via the whole block device. These block devices have separate buffer caches though, + * hence what was written via the partition device might not appear on the whole block device + * yet. Let's hence explicitly flush the whole block device, so that the read-back definitely + * works. */ + assert_se(ioctl(loop->fd, BLKFLSBUF, 0) >= 0); + + /* Try to read once, without pinning or adding partitions, i.e. by only accessing the whole block + * device. */ + assert_se(dissect_loop_device(loop, NULL, NULL, NULL, 0, &dissected) >= 0); + verify_dissected_image_harder(dissected); + dissected = dissected_image_unref(dissected); + + /* Now go via the loopback device after all, but this time add/pin, because now we want to mount it. */ + assert_se(dissect_loop_device(loop, NULL, NULL, NULL, DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, &dissected) >= 0); + verify_dissected_image_harder(dissected); + + assert_se(mkdtemp_malloc(NULL, &mounted) >= 0); + + /* We are particularly correct here, and now downgrade LOCK → LOCK_SH. That's because we are done + * with formatting the file systems, so we don't need the exclusive lock anymore. From now on a + * shared one is fine. This way udev can now probe the device if it wants, but still won't call + * BLKRRPART on it, and that's good, because that would destroy our partition table while we are at + * it. */ + assert_se(loop_device_flock(loop, LOCK_SH) >= 0); + + /* This is a test for the loopback block device setup code and it's use by the image dissection + * logic: since the kernel APIs are hard use and prone to races, let's test this in a heavy duty + * test: we open a bunch of threads and repeatedly allocate and deallocate loopback block devices in + * them in parallel, with an image file with a number of partitions. */ + assert_se(detach_mount_namespace() >= 0); + + /* This first (writable) mount will initialize the mount point dirs, so that the subsequent read-only ones can work */ + assert_se(dissected_image_mount( + dissected, + mounted, + /* uid_shift= */ UID_INVALID, + /* uid_range= */ UID_INVALID, + /* usernfs_fd= */ -EBADF, + 0) >= 0); + + /* Now we mounted everything, the partitions are pinned. Now it's fine to release the lock + * fully. This means udev could now issue BLKRRPART again, but that's OK given this will fail because + * we now mounted the device. */ + assert_se(loop_device_flock(loop, LOCK_UN) >= 0); + + assert_se(umount_recursive(mounted, 0) >= 0); + loop = loop_device_unref(loop); + + log_notice("Threads are being started now"); + + /* zero timeout means pick default: let's make sure we run for 10s on slow systems at max */ + if (arg_timeout == 0) + arg_timeout = slow_tests_enabled() ? 5 * USEC_PER_SEC : 1 * USEC_PER_SEC; + + end = usec_add(now(CLOCK_MONOTONIC), arg_timeout); + + if (arg_n_threads > 1) + for (unsigned i = 0; i < arg_n_threads; i++) + assert_se(pthread_create(threads + i, NULL, thread_func, FD_TO_PTR(fd)) == 0); + + log_notice("All threads started now."); + + if (arg_n_threads == 1) + assert_se(thread_func(FD_TO_PTR(fd)) == NULL); + else + for (unsigned i = 0; i < arg_n_threads; i++) { + log_notice("Joining thread #%u.", i); + + void *k; + assert_se(pthread_join(threads[i], &k) == 0); + assert_se(!k); + + log_notice("Joined thread #%u.", i); + } + + log_notice("Threads are all terminated now."); +#else + log_notice("Cutting test short, since we do not have libblkid."); +#endif + return 0; +} + +DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/test/test-loopback.c b/src/test/test-loopback.c new file mode 100644 index 0000000..48869ae --- /dev/null +++ b/src/test/test-loopback.c @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <sched.h> +#include <stdio.h> +#include <string.h> + +#include "errno-util.h" +#include "log.h" +#include "loopback-setup.h" +#include "tests.h" + +TEST_RET(loopback_setup) { + int r; + + if (unshare(CLONE_NEWUSER | CLONE_NEWNET) < 0) { + if (ERRNO_IS_PRIVILEGE(errno) || ERRNO_IS_NOT_SUPPORTED(errno)) + return log_tests_skipped("lacking privileges or namespaces not supported"); + return log_error_errno(errno, "Failed to create user+network namespace: %m"); + } + + r = loopback_setup(); + if (r < 0) + return log_error_errno(r, "loopback: %m"); + + log_info("> ipv6 main"); + /* <0 → fork error, ==0 → success, >0 → error in child */ + assert_se(system("ip -6 route show table main") >= 0); + + log_info("> ipv6 local"); + assert_se(system("ip -6 route show table local") >=0); + + log_info("> ipv4 main"); + assert_se(system("ip -4 route show table main") >= 0); + + log_info("> ipv4 local"); + assert_se(system("ip -4 route show table local") >= 0); + + return EXIT_SUCCESS; +} + +static int intro(void) { + log_show_color(true); + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/test/test-macro.c b/src/test/test-macro.c new file mode 100644 index 0000000..b91a1f9 --- /dev/null +++ b/src/test/test-macro.c @@ -0,0 +1,1040 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stddef.h> + +#include "log.h" +#include "macro.h" +#include "tests.h" + +TEST(saturate_add) { + assert_se(saturate_add(1, 2, UINT8_MAX) == 3); + assert_se(saturate_add(1, UINT8_MAX-2, UINT8_MAX) == UINT8_MAX-1); + assert_se(saturate_add(1, UINT8_MAX-1, UINT8_MAX) == UINT8_MAX); + assert_se(saturate_add(1, UINT8_MAX, UINT8_MAX) == UINT8_MAX); + assert_se(saturate_add(2, UINT8_MAX, UINT8_MAX) == UINT8_MAX); + assert_se(saturate_add(60, 60, 50) == 50); +} + +TEST(ALIGN_POWER2) { + unsigned long i, p2; + + assert_se(ALIGN_POWER2(0) == 0); + assert_se(ALIGN_POWER2(1) == 1); + assert_se(ALIGN_POWER2(2) == 2); + assert_se(ALIGN_POWER2(3) == 4); + assert_se(ALIGN_POWER2(4) == 4); + assert_se(ALIGN_POWER2(5) == 8); + assert_se(ALIGN_POWER2(6) == 8); + assert_se(ALIGN_POWER2(7) == 8); + assert_se(ALIGN_POWER2(9) == 16); + assert_se(ALIGN_POWER2(10) == 16); + assert_se(ALIGN_POWER2(11) == 16); + assert_se(ALIGN_POWER2(12) == 16); + assert_se(ALIGN_POWER2(13) == 16); + assert_se(ALIGN_POWER2(14) == 16); + assert_se(ALIGN_POWER2(15) == 16); + assert_se(ALIGN_POWER2(16) == 16); + assert_se(ALIGN_POWER2(17) == 32); + + assert_se(ALIGN_POWER2(ULONG_MAX) == 0); + assert_se(ALIGN_POWER2(ULONG_MAX - 1) == 0); + assert_se(ALIGN_POWER2(ULONG_MAX - 1024) == 0); + assert_se(ALIGN_POWER2(ULONG_MAX / 2) == ULONG_MAX / 2 + 1); + assert_se(ALIGN_POWER2(ULONG_MAX + 1) == 0); + + for (i = 1; i < 131071; ++i) { + for (p2 = 1; p2 < i; p2 <<= 1) + /* empty */ ; + + assert_se(ALIGN_POWER2(i) == p2); + } + + for (i = ULONG_MAX - 1024; i < ULONG_MAX; ++i) { + for (p2 = 1; p2 && p2 < i; p2 <<= 1) + /* empty */ ; + + assert_se(ALIGN_POWER2(i) == p2); + } +} + +TEST(MAX) { + static const struct { + int a; + int b[CONST_MAX(10, 100)]; + } val1 = { + .a = CONST_MAX(10, 100), + }; + int d = 0; + unsigned long x = 12345; + unsigned long y = 54321; + const char str[] = "a_string_constant"; + const unsigned long long arr[] = {9999ULL, 10ULL, 0ULL, 3000ULL, 2000ULL, 1000ULL, 100ULL, 9999999ULL}; + void *p = (void *)str; + void *q = (void *)&str[16]; + + assert_cc(sizeof(val1.b) == sizeof(int) * 100); + + /* CONST_MAX returns (void) instead of a value if the passed arguments + * are not of the same type or not constant expressions. */ + assert_cc(__builtin_types_compatible_p(typeof(CONST_MAX(1, 10)), int)); + assert_cc(__builtin_types_compatible_p(typeof(CONST_MAX(1, 1U)), void)); + + assert_se(val1.a == 100); + assert_se(MAX(++d, 0) == 1); + assert_se(d == 1); + + assert_cc(MAXSIZE(char[3], uint16_t) == 3); + assert_cc(MAXSIZE(char[3], uint32_t) == 4); + assert_cc(MAXSIZE(char, long) == sizeof(long)); + + assert_se(MAX(-5, 5) == 5); + assert_se(MAX(5, 5) == 5); + assert_se(MAX(MAX(1, MAX(2, MAX(3, 4))), 5) == 5); + assert_se(MAX(MAX(1, MAX(2, MAX(3, 2))), 1) == 3); + assert_se(MAX(MIN(1, MIN(2, MIN(3, 4))), 5) == 5); + assert_se(MAX(MAX(1, MIN(2, MIN(3, 2))), 1) == 2); + assert_se(LESS_BY(8, 4) == 4); + assert_se(LESS_BY(8, 8) == 0); + assert_se(LESS_BY(4, 8) == 0); + assert_se(LESS_BY(16, LESS_BY(8, 4)) == 12); + assert_se(LESS_BY(4, LESS_BY(8, 4)) == 0); + assert_se(CMP(3, 5) == -1); + assert_se(CMP(5, 3) == 1); + assert_se(CMP(5, 5) == 0); + assert_se(CMP(x, y) == -1); + assert_se(CMP(y, x) == 1); + assert_se(CMP(x, x) == 0); + assert_se(CMP(y, y) == 0); + assert_se(CMP(UINT64_MAX, (uint64_t) 0) == 1); + assert_se(CMP((uint64_t) 0, UINT64_MAX) == -1); + assert_se(CMP(UINT64_MAX, UINT64_MAX) == 0); + assert_se(CMP(INT64_MIN, INT64_MAX) == -1); + assert_se(CMP(INT64_MAX, INT64_MIN) == 1); + assert_se(CMP(INT64_MAX, INT64_MAX) == 0); + assert_se(CMP(INT64_MIN, INT64_MIN) == 0); + assert_se(CMP(INT64_MAX, (int64_t) 0) == 1); + assert_se(CMP((int64_t) 0, INT64_MIN) == 1); + assert_se(CMP(INT64_MIN, (int64_t) 0) == -1); + assert_se(CMP((int64_t) 0, INT64_MAX) == -1); + assert_se(CMP(&str[2], &str[7]) == -1); + assert_se(CMP(&str[2], &str[2]) == 0); + assert_se(CMP(&str[7], (const char *)str) == 1); + assert_se(CMP(str[2], str[7]) == 1); + assert_se(CMP(str[7], *str) == 1); + assert_se(CMP((const unsigned long long *)arr, &arr[3]) == -1); + assert_se(CMP(*arr, arr[3]) == 1); + assert_se(CMP(p, q) == -1); + assert_se(CMP(q, p) == 1); + assert_se(CMP(p, p) == 0); + assert_se(CMP(q, q) == 0); + assert_se(CLAMP(-5, 0, 1) == 0); + assert_se(CLAMP(5, 0, 1) == 1); + assert_se(CLAMP(5, -10, 1) == 1); + assert_se(CLAMP(5, -10, 10) == 5); + assert_se(CLAMP(CLAMP(0, -10, 10), CLAMP(-5, 10, 20), CLAMP(100, -5, 20)) == 10); +} + +#pragma GCC diagnostic push +#ifdef __clang__ +# pragma GCC diagnostic ignored "-Waddress-of-packed-member" +#endif + +TEST(container_of) { + struct mytype { + uint8_t pad1[3]; + uint64_t v1; + uint8_t pad2[2]; + uint32_t v2; + } myval = { }; + + assert_cc(sizeof(myval) >= 17); + assert_se(container_of(&myval.v1, struct mytype, v1) == &myval); + assert_se(container_of(&myval.v2, struct mytype, v2) == &myval); + assert_se(container_of(&container_of(&myval.v2, + struct mytype, + v2)->v1, + struct mytype, + v1) == &myval); +} + +#pragma GCC diagnostic pop + +TEST(DIV_ROUND_UP) { + int div; + + /* basic tests */ + assert_se(DIV_ROUND_UP(0, 8) == 0); + assert_se(DIV_ROUND_UP(1, 8) == 1); + assert_se(DIV_ROUND_UP(8, 8) == 1); + assert_se(DIV_ROUND_UP(12, 8) == 2); + assert_se(DIV_ROUND_UP(16, 8) == 2); + + /* test multiple evaluation */ + div = 0; + assert_se(DIV_ROUND_UP(div++, 8) == 0 && div == 1); + assert_se(DIV_ROUND_UP(++div, 8) == 1 && div == 2); + assert_se(DIV_ROUND_UP(8, div++) == 4 && div == 3); + assert_se(DIV_ROUND_UP(8, ++div) == 2 && div == 4); + + /* overflow test with exact division */ + assert_se(sizeof(0U) == 4); + assert_se(0xfffffffaU % 10U == 0U); + assert_se(0xfffffffaU / 10U == 429496729U); + assert_se(DIV_ROUND_UP(0xfffffffaU, 10U) == 429496729U); + assert_se((0xfffffffaU + 10U - 1U) / 10U == 0U); + assert_se(0xfffffffaU / 10U + !!(0xfffffffaU % 10U) == 429496729U); + + /* overflow test with rounded division */ + assert_se(0xfffffffdU % 10U == 3U); + assert_se(0xfffffffdU / 10U == 429496729U); + assert_se(DIV_ROUND_UP(0xfffffffdU, 10U) == 429496730U); + assert_se((0xfffffffdU + 10U - 1U) / 10U == 0U); + assert_se(0xfffffffdU / 10U + !!(0xfffffffdU % 10U) == 429496730U); +} + +TEST(PTR_TO_INT) { + /* Primary reason to have this test is to validate that pointers are large enough to hold entire int range */ + assert_se(PTR_TO_INT(INT_TO_PTR(0)) == 0); + assert_se(PTR_TO_INT(INT_TO_PTR(1)) == 1); + assert_se(PTR_TO_INT(INT_TO_PTR(-1)) == -1); + assert_se(PTR_TO_INT(INT_TO_PTR(INT_MAX)) == INT_MAX); + assert_se(PTR_TO_INT(INT_TO_PTR(INT_MIN)) == INT_MIN); +} + +TEST(IN_SET) { + assert_se(IN_SET(1, 1, 2)); + assert_se(IN_SET(1, 1, 2, 3, 4)); + assert_se(IN_SET(2, 1, 2, 3, 4)); + assert_se(IN_SET(3, 1, 2, 3, 4)); + assert_se(IN_SET(4, 1, 2, 3, 4)); + assert_se(!IN_SET(0, 1, 2)); + assert_se(!IN_SET(0, 1, 2, 3, 4)); + + struct { + unsigned x:3; + } t = { 1 }; + + assert_se(IN_SET(t.x, 1, 2)); + assert_se(IN_SET(t.x, 1, 2, 3, 4)); + assert_se(IN_SET(t.x, 2, 3, 4, 1)); + assert_se(!IN_SET(t.x, 0, 2)); + assert_se(!IN_SET(t.x, 2, 3, 4)); +} + +TEST(FOREACH_POINTER) { + int a, b, c, *i; + size_t k = 0; + + FOREACH_POINTER(i, &a, &b, &c) { + switch (k) { + + case 0: + assert_se(i == &a); + break; + + case 1: + assert_se(i == &b); + break; + + case 2: + assert_se(i == &c); + break; + + default: + assert_not_reached(); + break; + } + + k++; + } + + assert_se(k == 3); + + FOREACH_POINTER(i, &b) { + assert_se(k == 3); + assert_se(i == &b); + k = 4; + } + + assert_se(k == 4); + + FOREACH_POINTER(i, NULL, &c, NULL, &b, NULL, &a, NULL) { + switch (k) { + + case 4: + assert_se(i == NULL); + break; + + case 5: + assert_se(i == &c); + break; + + case 6: + assert_se(i == NULL); + break; + + case 7: + assert_se(i == &b); + break; + + case 8: + assert_se(i == NULL); + break; + + case 9: + assert_se(i == &a); + break; + + case 10: + assert_se(i == NULL); + break; + + default: + assert_not_reached(); + break; + } + + k++; + } + + assert_se(k == 11); +} + +TEST(FOREACH_VA_ARGS) { + size_t i; + + i = 0; + uint8_t u8, u8_1 = 1, u8_2 = 2, u8_3 = 3; + VA_ARGS_FOREACH(u8, u8_2, 8, 0xff, u8_1, u8_3, 0, 1) { + switch(i++) { + case 0: assert_se(u8 == u8_2); break; + case 1: assert_se(u8 == 8); break; + case 2: assert_se(u8 == 0xff); break; + case 3: assert_se(u8 == u8_1); break; + case 4: assert_se(u8 == u8_3); break; + case 5: assert_se(u8 == 0); break; + case 6: assert_se(u8 == 1); break; + default: assert_se(false); + } + } + assert_se(i == 7); + i = 0; + VA_ARGS_FOREACH(u8, 0) { + assert_se(u8 == 0); + assert_se(i++ == 0); + } + assert_se(i == 1); + i = 0; + VA_ARGS_FOREACH(u8, 0xff) { + assert_se(u8 == 0xff); + assert_se(i++ == 0); + } + assert_se(i == 1); + VA_ARGS_FOREACH(u8) + assert_se(false); + + i = 0; + uint32_t u32, u32_1 = 0xffff0000, u32_2 = 10, u32_3 = 0xffff; + VA_ARGS_FOREACH(u32, 1, 100, u32_2, 1000, u32_3, u32_1, 1, 0) { + switch(i++) { + case 0: assert_se(u32 == 1); break; + case 1: assert_se(u32 == 100); break; + case 2: assert_se(u32 == u32_2); break; + case 3: assert_se(u32 == 1000); break; + case 4: assert_se(u32 == u32_3); break; + case 5: assert_se(u32 == u32_1); break; + case 6: assert_se(u32 == 1); break; + case 7: assert_se(u32 == 0); break; + default: assert_se(false); + } + } + assert_se(i == 8); + i = 0; + VA_ARGS_FOREACH(u32, 0) { + assert_se(u32 == 0); + assert_se(i++ == 0); + } + assert_se(i == 1); + i = 0; + VA_ARGS_FOREACH(u32, 1000) { + assert_se(u32 == 1000); + assert_se(i++ == 0); + } + assert_se(i == 1); + VA_ARGS_FOREACH(u32) + assert_se(false); + + i = 0; + uint64_t u64, u64_1 = 0xffffffffffffffff, u64_2 = 50, u64_3 = 0xffff; + VA_ARGS_FOREACH(u64, 44, 0, u64_3, 100, u64_2, u64_1, 50000) { + switch(i++) { + case 0: assert_se(u64 == 44); break; + case 1: assert_se(u64 == 0); break; + case 2: assert_se(u64 == u64_3); break; + case 3: assert_se(u64 == 100); break; + case 4: assert_se(u64 == u64_2); break; + case 5: assert_se(u64 == u64_1); break; + case 6: assert_se(u64 == 50000); break; + default: assert_se(false); + } + } + assert_se(i == 7); + i = 0; + VA_ARGS_FOREACH(u64, 0) { + assert_se(u64 == 0); + assert_se(i++ == 0); + } + assert_se(i == 1); + i = 0; + VA_ARGS_FOREACH(u64, 0xff00ff00000000) { + assert_se(u64 == 0xff00ff00000000); + assert_se(i++ == 0); + } + assert_se(i == 1); + VA_ARGS_FOREACH(u64) + assert_se(false); + + struct test { + int a; + char b; + }; + + i = 0; + struct test s, + s_1 = { .a = 0, .b = 'c', }, + s_2 = { .a = 100000, .b = 'z', }, + s_3 = { .a = 0xff, .b = 'q', }, + s_4 = { .a = 1, .b = 'x', }; + VA_ARGS_FOREACH(s, s_1, (struct test){ .a = 10, .b = 'd', }, s_2, (struct test){}, s_3, s_4) { + switch(i++) { + case 0: assert_se(s.a == 0 ); assert_se(s.b == 'c'); break; + case 1: assert_se(s.a == 10 ); assert_se(s.b == 'd'); break; + case 2: assert_se(s.a == 100000); assert_se(s.b == 'z'); break; + case 3: assert_se(s.a == 0 ); assert_se(s.b == 0 ); break; + case 4: assert_se(s.a == 0xff ); assert_se(s.b == 'q'); break; + case 5: assert_se(s.a == 1 ); assert_se(s.b == 'x'); break; + default: assert_se(false); + } + } + assert_se(i == 6); + i = 0; + VA_ARGS_FOREACH(s, (struct test){ .a = 1, .b = 'A', }) { + assert_se(s.a == 1); + assert_se(s.b == 'A'); + assert_se(i++ == 0); + } + assert_se(i == 1); + VA_ARGS_FOREACH(s) + assert_se(false); + + i = 0; + struct test *p, *p_1 = &s_1, *p_2 = &s_2, *p_3 = &s_3, *p_4 = &s_4; + VA_ARGS_FOREACH(p, p_1, NULL, p_2, p_3, NULL, p_4, NULL) { + switch(i++) { + case 0: assert_se(p == p_1); break; + case 1: assert_se(p == NULL); break; + case 2: assert_se(p == p_2); break; + case 3: assert_se(p == p_3); break; + case 4: assert_se(p == NULL); break; + case 5: assert_se(p == p_4); break; + case 6: assert_se(p == NULL); break; + default: assert_se(false); + } + } + assert_se(i == 7); + i = 0; + VA_ARGS_FOREACH(p, p_3) { + assert_se(p == p_3); + assert_se(i++ == 0); + } + assert_se(i == 1); + VA_ARGS_FOREACH(p) + assert_se(false); + + i = 0; + void *v, *v_1 = p_1, *v_2 = p_2, *v_3 = p_3; + uint32_t *u32p = &u32; + VA_ARGS_FOREACH(v, v_1, NULL, u32p, v_3, p_2, p_4, v_2, NULL) { + switch(i++) { + case 0: assert_se(v == v_1); break; + case 1: assert_se(v == NULL); break; + case 2: assert_se(v == u32p); break; + case 3: assert_se(v == v_3); break; + case 4: assert_se(v == p_2); break; + case 5: assert_se(v == p_4); break; + case 6: assert_se(v == v_2); break; + case 7: assert_se(v == NULL); break; + default: assert_se(false); + } + } + assert_se(i == 8); + i = 0; + VA_ARGS_FOREACH(v, NULL) { + assert_se(v == NULL); + assert_se(i++ == 0); + } + assert_se(i == 1); + i = 0; + VA_ARGS_FOREACH(v, v_1) { + assert_se(v == v_1); + assert_se(i++ == 0); + } + assert_se(i == 1); + VA_ARGS_FOREACH(v) + assert_se(false); +} + +TEST(ALIGN_TO) { + assert_se(ALIGN_TO(0, 1) == 0); + assert_se(ALIGN_TO(1, 1) == 1); + assert_se(ALIGN_TO(2, 1) == 2); + assert_se(ALIGN_TO(3, 1) == 3); + assert_se(ALIGN_TO(4, 1) == 4); + assert_se(ALIGN_TO(SIZE_MAX-1, 1) == SIZE_MAX-1); + assert_se(ALIGN_TO(SIZE_MAX, 1) == SIZE_MAX); + + assert_se(ALIGN_TO(0, 2) == 0); + assert_se(ALIGN_TO(1, 2) == 2); + assert_se(ALIGN_TO(2, 2) == 2); + assert_se(ALIGN_TO(3, 2) == 4); + assert_se(ALIGN_TO(4, 2) == 4); + assert_se(ALIGN_TO(SIZE_MAX-3, 2) == SIZE_MAX-3); + assert_se(ALIGN_TO(SIZE_MAX-2, 2) == SIZE_MAX-1); + assert_se(ALIGN_TO(SIZE_MAX-1, 2) == SIZE_MAX-1); + assert_se(ALIGN_TO(SIZE_MAX, 2) == SIZE_MAX); /* overflow */ + + assert_se(ALIGN_TO(0, 4) == 0); + assert_se(ALIGN_TO(1, 4) == 4); + assert_se(ALIGN_TO(2, 4) == 4); + assert_se(ALIGN_TO(3, 4) == 4); + assert_se(ALIGN_TO(4, 4) == 4); + assert_se(ALIGN_TO(SIZE_MAX-3, 4) == SIZE_MAX-3); + assert_se(ALIGN_TO(SIZE_MAX-2, 4) == SIZE_MAX); /* overflow */ + assert_se(ALIGN_TO(SIZE_MAX-1, 4) == SIZE_MAX); /* overflow */ + assert_se(ALIGN_TO(SIZE_MAX, 4) == SIZE_MAX); /* overflow */ + + assert_se(ALIGN_TO_U64(0, 1) == 0); + assert_se(ALIGN_TO_U64(1, 1) == 1); + assert_se(ALIGN_TO_U64(2, 1) == 2); + assert_se(ALIGN_TO_U64(3, 1) == 3); + assert_se(ALIGN_TO_U64(4, 1) == 4); + assert_se(ALIGN_TO_U64(UINT64_MAX-1, 1) == UINT64_MAX-1); + assert_se(ALIGN_TO_U64(UINT64_MAX, 1) == UINT64_MAX); + + assert_se(ALIGN_TO_U64(0, 2) == 0); + assert_se(ALIGN_TO_U64(1, 2) == 2); + assert_se(ALIGN_TO_U64(2, 2) == 2); + assert_se(ALIGN_TO_U64(3, 2) == 4); + assert_se(ALIGN_TO_U64(4, 2) == 4); + assert_se(ALIGN_TO_U64(UINT64_MAX-3, 2) == UINT64_MAX-3); + assert_se(ALIGN_TO_U64(UINT64_MAX-2, 2) == UINT64_MAX-1); + assert_se(ALIGN_TO_U64(UINT64_MAX-1, 2) == UINT64_MAX-1); + assert_se(ALIGN_TO_U64(UINT64_MAX, 2) == UINT64_MAX); /* overflow */ + + assert_se(ALIGN_TO_U64(0, 4) == 0); + assert_se(ALIGN_TO_U64(1, 4) == 4); + assert_se(ALIGN_TO_U64(2, 4) == 4); + assert_se(ALIGN_TO_U64(3, 4) == 4); + assert_se(ALIGN_TO_U64(4, 4) == 4); + assert_se(ALIGN_TO_U64(UINT64_MAX-3, 4) == UINT64_MAX-3); + assert_se(ALIGN_TO_U64(UINT64_MAX-2, 4) == UINT64_MAX); /* overflow */ + assert_se(ALIGN_TO_U64(UINT64_MAX-1, 4) == UINT64_MAX); /* overflow */ + assert_se(ALIGN_TO_U64(UINT64_MAX, 4) == UINT64_MAX); /* overflow */ + + assert_cc(CONST_ALIGN_TO(96, 512) == 512); + assert_cc(CONST_ALIGN_TO(511, 512) == 512); + assert_cc(CONST_ALIGN_TO(512, 512) == 512); + assert_cc(CONST_ALIGN_TO(513, 512) == 1024); + assert_cc(CONST_ALIGN_TO(sizeof(int), 64) == 64); + + assert_cc(__builtin_types_compatible_p(typeof(CONST_ALIGN_TO(4, 3)), void)); + assert_cc(__builtin_types_compatible_p(typeof(CONST_ALIGN_TO(SIZE_MAX, 512)), void)); +} + +TEST(align_down) { + assert_se(ALIGN_DOWN(0, 1) == 0); + assert_se(ALIGN_DOWN(1, 1) == 1); + assert_se(ALIGN_DOWN(2, 1) == 2); + assert_se(ALIGN_DOWN(3, 1) == 3); + assert_se(ALIGN_DOWN(4, 1) == 4); + assert_se(ALIGN_DOWN(SIZE_MAX-1, 1) == SIZE_MAX-1); + assert_se(ALIGN_DOWN(SIZE_MAX, 1) == SIZE_MAX); + + assert_se(ALIGN_DOWN(0, 2) == 0); + assert_se(ALIGN_DOWN(1, 2) == 0); + assert_se(ALIGN_DOWN(2, 2) == 2); + assert_se(ALIGN_DOWN(3, 2) == 2); + assert_se(ALIGN_DOWN(4, 2) == 4); + assert_se(ALIGN_DOWN(SIZE_MAX-1, 2) == SIZE_MAX-1); + assert_se(ALIGN_DOWN(SIZE_MAX, 2) == SIZE_MAX-1); + + assert_se(ALIGN_DOWN(0, 4) == 0); + assert_se(ALIGN_DOWN(1, 4) == 0); + assert_se(ALIGN_DOWN(2, 4) == 0); + assert_se(ALIGN_DOWN(3, 4) == 0); + assert_se(ALIGN_DOWN(4, 4) == 4); + assert_se(ALIGN_DOWN(SIZE_MAX-1, 4) == SIZE_MAX-3); + assert_se(ALIGN_DOWN(SIZE_MAX, 4) == SIZE_MAX-3); + + assert_se(ALIGN_DOWN_U64(0, 1) == 0); + assert_se(ALIGN_DOWN_U64(1, 1) == 1); + assert_se(ALIGN_DOWN_U64(2, 1) == 2); + assert_se(ALIGN_DOWN_U64(3, 1) == 3); + assert_se(ALIGN_DOWN_U64(4, 1) == 4); + assert_se(ALIGN_DOWN_U64(UINT64_MAX-1, 1) == UINT64_MAX-1); + assert_se(ALIGN_DOWN_U64(UINT64_MAX, 1) == UINT64_MAX); + + assert_se(ALIGN_DOWN_U64(0, 2) == 0); + assert_se(ALIGN_DOWN_U64(1, 2) == 0); + assert_se(ALIGN_DOWN_U64(2, 2) == 2); + assert_se(ALIGN_DOWN_U64(3, 2) == 2); + assert_se(ALIGN_DOWN_U64(4, 2) == 4); + assert_se(ALIGN_DOWN_U64(UINT64_MAX-1, 2) == UINT64_MAX-1); + assert_se(ALIGN_DOWN_U64(UINT64_MAX, 2) == UINT64_MAX-1); + + assert_se(ALIGN_DOWN_U64(0, 4) == 0); + assert_se(ALIGN_DOWN_U64(1, 4) == 0); + assert_se(ALIGN_DOWN_U64(2, 4) == 0); + assert_se(ALIGN_DOWN_U64(3, 4) == 0); + assert_se(ALIGN_DOWN_U64(4, 4) == 4); + assert_se(ALIGN_DOWN_U64(UINT64_MAX-1, 4) == UINT64_MAX-3); + assert_se(ALIGN_DOWN_U64(UINT64_MAX, 4) == UINT64_MAX-3); +} + +TEST(align_offset) { + assert_se(ALIGN_OFFSET(0, 1) == 0); + assert_se(ALIGN_OFFSET(1, 1) == 0); + assert_se(ALIGN_OFFSET(2, 1) == 0); + assert_se(ALIGN_OFFSET(3, 1) == 0); + assert_se(ALIGN_OFFSET(4, 1) == 0); + assert_se(ALIGN_OFFSET(SIZE_MAX-1, 1) == 0); + assert_se(ALIGN_OFFSET(SIZE_MAX, 1) == 0); + + assert_se(ALIGN_OFFSET(0, 2) == 0); + assert_se(ALIGN_OFFSET(1, 2) == 1); + assert_se(ALIGN_OFFSET(2, 2) == 0); + assert_se(ALIGN_OFFSET(3, 2) == 1); + assert_se(ALIGN_OFFSET(4, 2) == 0); + assert_se(ALIGN_OFFSET(SIZE_MAX-1, 2) == 0); + assert_se(ALIGN_OFFSET(SIZE_MAX, 2) == 1); + + assert_se(ALIGN_OFFSET(0, 4) == 0); + assert_se(ALIGN_OFFSET(1, 4) == 1); + assert_se(ALIGN_OFFSET(2, 4) == 2); + assert_se(ALIGN_OFFSET(3, 4) == 3); + assert_se(ALIGN_OFFSET(4, 4) == 0); + assert_se(ALIGN_OFFSET(SIZE_MAX-1, 4) == 2); + assert_se(ALIGN_OFFSET(SIZE_MAX, 4) == 3); + + assert_se(ALIGN_OFFSET_U64(0, 1) == 0); + assert_se(ALIGN_OFFSET_U64(1, 1) == 0); + assert_se(ALIGN_OFFSET_U64(2, 1) == 0); + assert_se(ALIGN_OFFSET_U64(3, 1) == 0); + assert_se(ALIGN_OFFSET_U64(4, 1) == 0); + assert_se(ALIGN_OFFSET_U64(UINT64_MAX-1, 1) == 0); + assert_se(ALIGN_OFFSET_U64(UINT64_MAX, 1) == 0); + + assert_se(ALIGN_OFFSET_U64(0, 2) == 0); + assert_se(ALIGN_OFFSET_U64(1, 2) == 1); + assert_se(ALIGN_OFFSET_U64(2, 2) == 0); + assert_se(ALIGN_OFFSET_U64(3, 2) == 1); + assert_se(ALIGN_OFFSET_U64(4, 2) == 0); + assert_se(ALIGN_OFFSET_U64(UINT64_MAX-1, 2) == 0); + assert_se(ALIGN_OFFSET_U64(UINT64_MAX, 2) == 1); + + assert_se(ALIGN_OFFSET_U64(0, 4) == 0); + assert_se(ALIGN_OFFSET_U64(1, 4) == 1); + assert_se(ALIGN_OFFSET_U64(2, 4) == 2); + assert_se(ALIGN_OFFSET_U64(3, 4) == 3); + assert_se(ALIGN_OFFSET_U64(4, 4) == 0); + assert_se(ALIGN_OFFSET_U64(UINT64_MAX-1, 4) == 2); + assert_se(ALIGN_OFFSET_U64(UINT64_MAX, 4) == 3); +} + +TEST(flags) { + enum { + F1 = 1 << 0, + F2 = 1 << 1, + F3 = 1 << 2, + F_ALL = F1 | F2 | F3 + }; + unsigned n, f; + + assert_se(FLAGS_SET(0, 0)); + assert_se(FLAGS_SET(F1, F1)); + assert_se(FLAGS_SET(F1 | F2, F1)); + assert_se(FLAGS_SET(F1 | F3, F1 | F3)); + assert_se(FLAGS_SET(F1 | F2 | F3, F_ALL)); + assert_se(!FLAGS_SET(0, F1)); + assert_se(!FLAGS_SET(F2, F1)); + assert_se(!FLAGS_SET(F1 | F2, F3)); + assert_se(!FLAGS_SET(F1 | F2, F1 | F3)); + assert_se(!FLAGS_SET(F1 | F2 | F3, ~F_ALL)); + + /* Check for no double eval. */ + n = F2; + f = F1; + assert_se(!FLAGS_SET(--n, ++f)); + assert_se(n == F1); + assert_se(f == F2); + + SET_FLAG(n, F3, true); + assert_se(n == (F1 | F3)); + SET_FLAG(n, F2, false); + assert_se(n == (F1 | F3)); + SET_FLAG(n, F3, false); + assert_se(n == F1); + SET_FLAG(n, F1, true); + assert_se(n == F1); + SET_FLAG(n, F1 | F3, true); + assert_se(n == (F1 | F3)); + SET_FLAG(n, F_ALL, false); + assert_se(n == 0); + + assert_se(UPDATE_FLAG(0, 0, true) == 0); + assert_se(UPDATE_FLAG(0, F1, true) == F1); + assert_se(UPDATE_FLAG(0, F1 | F2, true) == (F1 | F2)); + assert_se(UPDATE_FLAG(F1, 0, true) == F1); + assert_se(UPDATE_FLAG(F1, F1, true) == F1); + assert_se(UPDATE_FLAG(F1, F3, true) == (F1 | F3)); + assert_se(UPDATE_FLAG(F1, F1 | F3, true) == (F1 | F3)); + assert_se(UPDATE_FLAG(F1, F_ALL, true) == F_ALL); + assert_se(UPDATE_FLAG(0, 0, false) == 0); + assert_se(UPDATE_FLAG(0, F1, false) == 0); + assert_se(UPDATE_FLAG(0, F1 | F2, false) == 0); + assert_se(UPDATE_FLAG(F1, 0, false) == F1); + assert_se(UPDATE_FLAG(F1, F1, false) == 0); + assert_se(UPDATE_FLAG(F1, F3, false) == F1); + assert_se(UPDATE_FLAG(F1, F1 | F3, false) == 0); + assert_se(UPDATE_FLAG(F1, F2 | F3, false) == F1); + assert_se(UPDATE_FLAG(F1, F_ALL, false) == 0); + assert_se(UPDATE_FLAG(F_ALL, F_ALL, false) == 0); + + /* Check for no double eval. */ + n = F2; + f = F1; + assert_se(UPDATE_FLAG(--n, ++f, true) == (F1 | F2)); + assert_se(n == F1); + assert_se(f == F2); +} + +TEST(DECIMAL_STR_WIDTH) { + assert_se(DECIMAL_STR_WIDTH(0) == 1); + assert_se(DECIMAL_STR_WIDTH(1) == 1); + assert_se(DECIMAL_STR_WIDTH(2) == 1); + assert_se(DECIMAL_STR_WIDTH(9) == 1); + assert_se(DECIMAL_STR_WIDTH(10) == 2); + assert_se(DECIMAL_STR_WIDTH(11) == 2); + assert_se(DECIMAL_STR_WIDTH(99) == 2); + assert_se(DECIMAL_STR_WIDTH(100) == 3); + assert_se(DECIMAL_STR_WIDTH(101) == 3); + assert_se(DECIMAL_STR_WIDTH(-1) == 2); + assert_se(DECIMAL_STR_WIDTH(-2) == 2); + assert_se(DECIMAL_STR_WIDTH(-9) == 2); + assert_se(DECIMAL_STR_WIDTH(-10) == 3); + assert_se(DECIMAL_STR_WIDTH(-11) == 3); + assert_se(DECIMAL_STR_WIDTH(-99) == 3); + assert_se(DECIMAL_STR_WIDTH(-100) == 4); + assert_se(DECIMAL_STR_WIDTH(-101) == 4); + assert_se(DECIMAL_STR_WIDTH(UINT64_MAX) == STRLEN("18446744073709551615")); + assert_se(DECIMAL_STR_WIDTH(INT64_MAX) == STRLEN("9223372036854775807")); + assert_se(DECIMAL_STR_WIDTH(INT64_MIN) == STRLEN("-9223372036854775808")); +} + +TEST(DECIMAL_STR_MAX) { + int8_t s8_longest = INT8_MIN; + int16_t s16_longest = INT16_MIN; + int32_t s32_longest = INT32_MIN; + int64_t s64_longest = INT64_MIN; + uint8_t u8_longest = UINT8_MAX; + uint16_t u16_longest = UINT16_MAX; + uint32_t u32_longest = UINT32_MAX; + uint64_t u64_longest = UINT64_MAX; + + /* NB: Always add +1, because DECIMAL_STR_MAX() includes space for trailing NUL byte, but + * DECIMAL_STR_WIDTH() does not! */ + assert_se(DECIMAL_STR_MAX(int8_t) == DECIMAL_STR_WIDTH(s8_longest)+1); + assert_se(DECIMAL_STR_MAX(int16_t) == DECIMAL_STR_WIDTH(s16_longest)+1); + assert_se(DECIMAL_STR_MAX(int32_t) == DECIMAL_STR_WIDTH(s32_longest)+1); + assert_se(DECIMAL_STR_MAX(int64_t) == DECIMAL_STR_WIDTH(s64_longest)+1); + + assert_se(DECIMAL_STR_MAX(uint8_t) == DECIMAL_STR_WIDTH(u8_longest)+1); + assert_se(DECIMAL_STR_MAX(uint16_t) == DECIMAL_STR_WIDTH(u16_longest)+1); + assert_se(DECIMAL_STR_MAX(uint32_t) == DECIMAL_STR_WIDTH(u32_longest)+1); + assert_se(DECIMAL_STR_MAX(uint64_t) == DECIMAL_STR_WIDTH(u64_longest)+1); +} + +TEST(PTR_SUB1) { + static const uint64_t x[4] = { 2, 3, 4, 5 }; + const uint64_t *p; + + p = x + ELEMENTSOF(x)-1; + assert_se(*p == 5); + + p = PTR_SUB1(p, x); + assert_se(*p == 4); + + p = PTR_SUB1(p, x); + assert_se(*p == 3); + + p = PTR_SUB1(p, x); + assert_se(*p == 2); + + p = PTR_SUB1(p, x); + assert_se(!p); + + p = PTR_SUB1(p, x); + assert_se(!p); +} + +TEST(ISPOWEROF2) { + uint64_t u; + int64_t i; + + /* First, test constant expressions */ + assert_se(!ISPOWEROF2(-2)); + assert_se(!ISPOWEROF2(-1)); + assert_se(!ISPOWEROF2(0)); + assert_se(ISPOWEROF2(1)); + assert_se(ISPOWEROF2(2)); + assert_se(!ISPOWEROF2(3)); + assert_se(ISPOWEROF2(4)); + assert_se(!ISPOWEROF2(5)); + assert_se(!ISPOWEROF2(6)); + assert_se(!ISPOWEROF2(7)); + assert_se(ISPOWEROF2(8)); + assert_se(!ISPOWEROF2(9)); + assert_se(!ISPOWEROF2(1022)); + assert_se(ISPOWEROF2(1024)); + assert_se(!ISPOWEROF2(1025)); + assert_se(!ISPOWEROF2(UINT64_C(0xffffffff))); + assert_se(ISPOWEROF2(UINT64_C(0x100000000))); + assert_se(!ISPOWEROF2(UINT64_C(0x100000001))); + + /* Then, test dynamic expressions, and if they are side-effect free */ + i = -2; + assert_se(!ISPOWEROF2(i++)); + assert_se(i == -1); + assert_se(!ISPOWEROF2(i++)); + assert_se(i == 0); + assert_se(!ISPOWEROF2(i++)); + assert_se(i == 1); + assert_se(ISPOWEROF2(i++)); + assert_se(i == 2); + assert_se(ISPOWEROF2(i++)); + assert_se(i == 3); + assert_se(!ISPOWEROF2(i++)); + assert_se(i == 4); + assert_se(ISPOWEROF2(i++)); + assert_se(i == 5); + assert_se(!ISPOWEROF2(i)); + + u = 0; + assert_se(!ISPOWEROF2(u++)); + assert_se(u == 1); + assert_se(ISPOWEROF2(u++)); + assert_se(u == 2); + assert_se(ISPOWEROF2(u++)); + assert_se(u == 3); + assert_se(!ISPOWEROF2(u++)); + assert_se(u == 4); + assert_se(ISPOWEROF2(u++)); + assert_se(u == 5); + assert_se(!ISPOWEROF2(u)); +} + +TEST(ALIGNED) { + assert_se(IS_ALIGNED16(NULL)); + assert_se(IS_ALIGNED32(NULL)); + assert_se(IS_ALIGNED64(NULL)); + + uint64_t u64; + uint32_t u32; + uint16_t u16; + + assert_se(IS_ALIGNED16(&u16)); + assert_se(IS_ALIGNED16(&u32)); + assert_se(IS_ALIGNED16(&u64)); + assert_se(IS_ALIGNED32(&u32)); + assert_se(IS_ALIGNED32(&u64)); + assert_se(IS_ALIGNED64(&u64)); + + _align_(32) uint8_t ua256; + _align_(8) uint8_t ua64; + _align_(4) uint8_t ua32; + _align_(2) uint8_t ua16; + + assert_se(IS_ALIGNED16(&ua256)); + assert_se(IS_ALIGNED32(&ua256)); + assert_se(IS_ALIGNED64(&ua256)); + + assert_se(IS_ALIGNED16(&ua64)); + assert_se(IS_ALIGNED32(&ua64)); + assert_se(IS_ALIGNED64(&ua64)); + + assert_se(IS_ALIGNED16(&ua32)); + assert_se(IS_ALIGNED32(&ua32)); + + assert_se(IS_ALIGNED16(&ua16)); + +#ifdef __x86_64__ + /* Conditionalized on x86-64, since there we know for sure that all three types are aligned to + * their size. Too lazy to figure it out for other archs */ + void *p = UINT_TO_PTR(1); /* definitely not aligned */ + assert_se(!IS_ALIGNED16(p)); + assert_se(!IS_ALIGNED32(p)); + assert_se(!IS_ALIGNED64(p)); + + assert_se(IS_ALIGNED16(ALIGN2_PTR(p))); + assert_se(IS_ALIGNED32(ALIGN4_PTR(p))); + assert_se(IS_ALIGNED64(ALIGN8_PTR(p))); + + p = UINT_TO_PTR(-1); /* also definitely not aligned */ + assert_se(!IS_ALIGNED16(p)); + assert_se(!IS_ALIGNED32(p)); + assert_se(!IS_ALIGNED64(p)); +#endif +} + +TEST(FOREACH_ARRAY) { + int a[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + int b[10] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; + int x, n; + + x = n = 0; + FOREACH_ARRAY(i, a, 10) { + x += *i; + n++; + } + assert_se(x == 45); + assert_se(n == 10); + + x = n = 0; + FOREACH_ARRAY(i, a, 10) + FOREACH_ARRAY(j, b, 10) { + x += (*i) * (*j); + n++; + } + assert_se(x == 45 * 45); + assert_se(n == 10 * 10); + + x = n = 0; + FOREACH_ARRAY(i, a, 5) + FOREACH_ARRAY(j, b, 5) { + x += (*i) * (*j); + n++; + } + assert_se(x == 10 * 35); + assert_se(n == 5 * 5); + + x = n = 0; + FOREACH_ARRAY(i, a, 0) + FOREACH_ARRAY(j, b, 0) { + x += (*i) * (*j); + n++; + } + assert_se(x == 0); + assert_se(n == 0); + + x = n = 0; + FOREACH_ARRAY(i, a, -1) + FOREACH_ARRAY(j, b, -1) { + x += (*i) * (*j); + n++; + } + assert_se(x == 0); + assert_se(n == 0); +} + +#define TEST_ROUND_UP_BY_TYPE(type, max_value) \ + ({ \ + type x, y; \ + x = 0, y = 1; \ + assert_se(ROUND_UP(x, y) == 0); \ + x = 0, y = 2; \ + assert_se(ROUND_UP(x, y) == 0); \ + x = 0, y = 3; \ + assert_se(ROUND_UP(x, y) == 0); \ + x = 0, y = 4; \ + assert_se(ROUND_UP(x, y) == 0); \ + x = 1, y = 1; \ + assert_se(ROUND_UP(x, y) == 1); \ + x = 1, y = 2; \ + assert_se(ROUND_UP(x, y) == 2); \ + x = 1, y = 3; \ + assert_se(ROUND_UP(x, y) == 3); \ + x = 1, y = 4; \ + assert_se(ROUND_UP(x, y) == 4); \ + x = 2, y = 1; \ + assert_se(ROUND_UP(x, y) == 2); \ + x = 2, y = 2; \ + assert_se(ROUND_UP(x, y) == 2); \ + x = 2, y = 3; \ + assert_se(ROUND_UP(x, y) == 3); \ + x = 2, y = 4; \ + assert_se(ROUND_UP(x, y) == 4); \ + x = 3, y = 1; \ + assert_se(ROUND_UP(x, y) == 3); \ + x = 3, y = 2; \ + assert_se(ROUND_UP(x, y) == 4); \ + x = 3, y = 3; \ + assert_se(ROUND_UP(x, y) == 3); \ + x = 3, y = 4; \ + assert_se(ROUND_UP(x, y) == 4); \ + x = 4, y = 1; \ + assert_se(ROUND_UP(x, y) == 4); \ + x = 4, y = 2; \ + assert_se(ROUND_UP(x, y) == 4); \ + x = 4, y = 3; \ + assert_se(ROUND_UP(x, y) == 6); \ + x = 4, y = 4; \ + assert_se(ROUND_UP(x, y) == 4); \ + x = max_value, y = 1; \ + assert_se(ROUND_UP(x, y) == max_value); \ + x = max_value, y = 2; \ + assert_se(ROUND_UP(x, y) == max_value); \ + x = max_value, y = 3; \ + assert_se(ROUND_UP(x, y) == max_value); \ + x = max_value, y = 4; \ + assert_se(ROUND_UP(x, y) == max_value); \ + x = max_value-1, y = 1; \ + assert_se(ROUND_UP(x, y) == max_value-1); \ + x = max_value-1, y = 2; \ + assert_se(ROUND_UP(x, y) == max_value-1); \ + x = max_value-1, y = 4; \ + assert_se(ROUND_UP(x, y) == max_value); \ + }) + +TEST(ROUND_UP) { + TEST_ROUND_UP_BY_TYPE(uint8_t, UINT8_MAX); + TEST_ROUND_UP_BY_TYPE(uint16_t, UINT16_MAX); + TEST_ROUND_UP_BY_TYPE(uint32_t, UINT32_MAX); + TEST_ROUND_UP_BY_TYPE(uint64_t, UINT64_MAX); +} + +TEST(u64_multiply_safe) { + assert_se(u64_multiply_safe(0, 0) == 0); + assert_se(u64_multiply_safe(10, 0) == 0); + assert_se(u64_multiply_safe(0, 10) == 0); + assert_se(u64_multiply_safe(10, 10) == 100); + + assert_se(u64_multiply_safe(UINT64_MAX, 0) == 0); + assert_se(u64_multiply_safe(UINT64_MAX, 1) == UINT64_MAX); + assert_se(u64_multiply_safe(UINT64_MAX, 2) == 0); + assert_se(u64_multiply_safe(0, UINT64_MAX) == 0); + assert_se(u64_multiply_safe(1, UINT64_MAX) == UINT64_MAX); + assert_se(u64_multiply_safe(2, UINT64_MAX) == 0); + + assert_se(u64_multiply_safe(UINT64_MAX / 2, 0) == 0); + assert_se(u64_multiply_safe(UINT64_MAX / 2, 1) == UINT64_MAX / 2); + assert_se(u64_multiply_safe(UINT64_MAX / 2, 2) == UINT64_MAX - 1); + assert_se(u64_multiply_safe(UINT64_MAX / 2, 3) == 0); + assert_se(u64_multiply_safe(0, UINT64_MAX / 2) == 0); + assert_se(u64_multiply_safe(1, UINT64_MAX / 2) == UINT64_MAX / 2); + assert_se(u64_multiply_safe(2, UINT64_MAX / 2) == UINT64_MAX - 1); + assert_se(u64_multiply_safe(3, UINT64_MAX / 2) == 0); + + assert_se(u64_multiply_safe(UINT64_MAX, UINT64_MAX) == 0); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-manager.c b/src/test/test-manager.c new file mode 100644 index 0000000..76e094b --- /dev/null +++ b/src/test/test-manager.c @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "manager.h" +#include "tests.h" + +TEST(manager_taint_string) { + Manager m = {}; + + _cleanup_free_ char *a = manager_taint_string(&m); + assert_se(a); + log_debug("taint string: '%s'", a); + + if (cg_all_unified() == 0) + assert_se(strstr(a, "cgroupsv1")); + else + assert_se(!strstr(a, "cgroupsv1")); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-math-util.c b/src/test/test-math-util.c new file mode 100644 index 0000000..9771576 --- /dev/null +++ b/src/test/test-math-util.c @@ -0,0 +1,110 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <float.h> + +#include "math-util.h" +#include "tests.h" + +TEST(iszero_safe) { + /* zeros */ + assert_se(iszero_safe(0.0)); + assert_se(iszero_safe(-0.0)); + assert_se(iszero_safe(0e0)); + assert_se(iszero_safe(-0e0)); + assert_se(iszero_safe(0e+0)); + assert_se(iszero_safe(0e-0)); + assert_se(iszero_safe(-0e-0)); + assert_se(iszero_safe(-0e000)); + assert_se(iszero_safe(0e000)); + + /* non-zero normal values */ + assert_se(!iszero_safe(42.0)); + assert_se(!iszero_safe(M_PI)); + assert_se(!iszero_safe(DBL_MAX)); + assert_se(!iszero_safe(-DBL_MAX)); + assert_se(!iszero_safe(DBL_MIN)); + assert_se(!iszero_safe(-DBL_MIN)); + assert_se(!iszero_safe(1 / DBL_MAX)); + + /* subnormal values */ + assert_se(!iszero_safe(DBL_MIN / 2)); + assert_se(!iszero_safe(-DBL_MIN / 42)); + assert_se(!iszero_safe(1 / DBL_MAX / 2)); + + /* too small values which cannot be in subnormal form */ + assert_se( iszero_safe(DBL_MIN / DBL_MAX)); + assert_se( iszero_safe(DBL_MIN / -DBL_MAX)); + assert_se( iszero_safe(-DBL_MIN / DBL_MAX)); + assert_se( iszero_safe(-DBL_MIN / -DBL_MAX)); + + /* NaN or infinity */ + assert_se(!iszero_safe(NAN)); + assert_se(!iszero_safe(INFINITY)); + assert_se(!iszero_safe(-INFINITY)); + assert_se(!iszero_safe(1 / NAN)); + + /* inverse of infinity */ + assert_se( iszero_safe(1 / INFINITY)); + assert_se( iszero_safe(1 / -INFINITY)); + assert_se( iszero_safe(-1 / INFINITY)); + assert_se( iszero_safe(-1 / -INFINITY)); + assert_se( iszero_safe(42 / -INFINITY)); + assert_se( iszero_safe(-42 / -INFINITY)); + assert_se( iszero_safe(DBL_MIN / INFINITY)); + assert_se( iszero_safe(DBL_MIN / -INFINITY)); + assert_se( iszero_safe(DBL_MAX / INFINITY / 2)); + assert_se( iszero_safe(DBL_MAX / -INFINITY * DBL_MAX)); + + /* infinity / infinity is NaN */ + assert_se(!iszero_safe(INFINITY / INFINITY)); + assert_se(!iszero_safe(INFINITY * 2 / INFINITY)); + assert_se(!iszero_safe(INFINITY / DBL_MAX / INFINITY)); +} + +TEST(fp_equal) { + /* normal values */ + assert_se( fp_equal(0.0, -0e0)); + assert_se( fp_equal(3.0, 3)); + assert_se(!fp_equal(3.000001, 3)); + assert_se( fp_equal(M_PI, M_PI)); + assert_se(!fp_equal(M_PI, -M_PI)); + assert_se( fp_equal(DBL_MAX, DBL_MAX)); + assert_se(!fp_equal(DBL_MAX, -DBL_MAX)); + assert_se(!fp_equal(-DBL_MAX, DBL_MAX)); + assert_se( fp_equal(-DBL_MAX, -DBL_MAX)); + assert_se( fp_equal(DBL_MIN, DBL_MIN)); + assert_se(!fp_equal(DBL_MIN, -DBL_MIN)); + assert_se(!fp_equal(-DBL_MIN, DBL_MIN)); + assert_se( fp_equal(-DBL_MIN, -DBL_MIN)); + + /* subnormal values */ + assert_se( fp_equal(DBL_MIN / 10, DBL_MIN / 10)); + assert_se(!fp_equal(DBL_MIN / 10, -DBL_MIN / 10)); + assert_se(!fp_equal(-DBL_MIN / 10, DBL_MIN / 10)); + assert_se( fp_equal(-DBL_MIN / 10, -DBL_MIN / 10)); + assert_se(!fp_equal(DBL_MIN / 10, DBL_MIN / 15)); + assert_se(!fp_equal(DBL_MIN / 10, DBL_MIN / 15)); + + /* subnormal difference */ + assert_se(!fp_equal(DBL_MIN / 10, DBL_MIN + DBL_MIN / 10)); + assert_se( fp_equal(3.0, 3.0 + DBL_MIN / 2)); /* 3.0 + DBL_MIN / 2 is truncated to 3.0 */ + + /* too small values */ + assert_se( fp_equal(DBL_MIN / DBL_MAX, -DBL_MIN / DBL_MAX)); + + /* NaN or infinity */ + assert_se(!fp_equal(NAN, NAN)); + assert_se(!fp_equal(NAN, 0)); + assert_se(!fp_equal(NAN, INFINITY)); + assert_se(!fp_equal(INFINITY, INFINITY)); + assert_se(!fp_equal(INFINITY, -INFINITY)); + assert_se(!fp_equal(-INFINITY, INFINITY)); + assert_se(!fp_equal(-INFINITY, -INFINITY)); + + /* inverse of infinity */ + assert_se( fp_equal(0, 1 / INFINITY)); + assert_se( fp_equal(42 / INFINITY, 1 / -INFINITY)); + assert_se(!fp_equal(42 / INFINITY, INFINITY / INFINITY)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-memfd-util.c b/src/test/test-memfd-util.c new file mode 100644 index 0000000..f8e1b46 --- /dev/null +++ b/src/test/test-memfd-util.c @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <unistd.h> + +#include "errno-util.h" +#include "fd-util.h" +#include "memfd-util.h" +#include "string-util.h" +#include "tests.h" + +TEST(memfd_get_sealed) { +#define TEST_TEXT "this is some random test text we are going to write to a memfd" + _cleanup_close_ int fd = -EBADF; + + fd = memfd_new("test-memfd-get-sealed"); + if (fd < 0) { + assert_se(ERRNO_IS_NOT_SUPPORTED(fd)); + return; + } + + assert_se(write(fd, TEST_TEXT, strlen(TEST_TEXT)) == strlen(TEST_TEXT)); + /* we'll leave the read offset at the end of the memfd, the fdopen_independent() descriptors should + * start at the beginning anyway */ + + assert_se(memfd_get_sealed(fd) == 0); + assert_se(memfd_set_sealed(fd) >= 0); + assert_se(memfd_get_sealed(fd) > 0); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-memory-util.c b/src/test/test-memory-util.c new file mode 100644 index 0000000..cd4b64a --- /dev/null +++ b/src/test/test-memory-util.c @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "memory-util.h" +#include "tests.h" + +TEST(eqzero) { + const uint32_t zeros[] = {0, 0, 0}; + const uint32_t ones[] = {1, 1}; + const uint32_t mixed[] = {0, 1, 0, 0, 0}; + const uint8_t longer[] = {[55] = 255}; + + assert_se(eqzero(zeros)); + assert_se(!eqzero(ones)); + assert_se(!eqzero(mixed)); + assert_se(!eqzero(longer)); +} + +static void my_destructor(struct iovec *iov, size_t n) { + /* not really a destructor, just something we can use to check if the destruction worked */ + memset(iov, 'y', sizeof(struct iovec) * n); +} + +TEST(cleanup_array) { + struct iovec *iov, *saved_iov; + size_t n, saved_n; + + n = 7; + iov = new(struct iovec, n); + assert_se(iov); + + memset(iov, 'x', sizeof(struct iovec) * n); + + saved_iov = iov; + saved_n = n; + + { + assert_se(memeqbyte('x', saved_iov, sizeof(struct iovec) * saved_n)); + assert_se(iov); + assert_se(n > 0); + + CLEANUP_ARRAY(iov, n, my_destructor); + + assert_se(memeqbyte('x', saved_iov, sizeof(struct iovec) * saved_n)); + assert_se(iov); + assert_se(n > 0); + } + + assert_se(memeqbyte('y', saved_iov, sizeof(struct iovec) * saved_n)); + assert_se(!iov); + assert_se(n == 0); + + free(saved_iov); +} + +TEST(page_align) { + assert_se(PAGE_ALIGN(page_size() - 1) == page_size()); + assert_se(PAGE_ALIGN(page_size() ) == page_size()); + assert_se(PAGE_ALIGN(page_size() + 1) == page_size() * 2); + assert_se(PAGE_ALIGN(page_size() * 123 - 1) == page_size() * 123); + assert_se(PAGE_ALIGN(page_size() * 123 ) == page_size() * 123); + assert_se(PAGE_ALIGN(page_size() * 123 + 1) == page_size() * 124); + assert_se(PAGE_ALIGN(SIZE_MAX - page_size() - 1) == SIZE_MAX - page_size() + 1); + assert_se(PAGE_ALIGN(SIZE_MAX - page_size() ) == SIZE_MAX - page_size() + 1); + assert_se(PAGE_ALIGN(SIZE_MAX - page_size() + 1) == SIZE_MAX - page_size() + 1); + assert_se(PAGE_ALIGN(SIZE_MAX - page_size() + 2) == SIZE_MAX); /* overflow */ + assert_se(PAGE_ALIGN(SIZE_MAX) == SIZE_MAX); /* overflow */ + + assert_se(PAGE_ALIGN_U64(page_size() - 1) == page_size()); + assert_se(PAGE_ALIGN_U64(page_size() ) == page_size()); + assert_se(PAGE_ALIGN_U64(page_size() + 1) == page_size() * 2); + assert_se(PAGE_ALIGN_U64(page_size() * 123 - 1) == page_size() * 123); + assert_se(PAGE_ALIGN_U64(page_size() * 123 ) == page_size() * 123); + assert_se(PAGE_ALIGN_U64(page_size() * 123 + 1) == page_size() * 124); + assert_se(PAGE_ALIGN_U64(UINT64_MAX - page_size() - 1) == UINT64_MAX - page_size() + 1); + assert_se(PAGE_ALIGN_U64(UINT64_MAX - page_size() ) == UINT64_MAX - page_size() + 1); + assert_se(PAGE_ALIGN_U64(UINT64_MAX - page_size() + 1) == UINT64_MAX - page_size() + 1); + assert_se(PAGE_ALIGN_U64(UINT64_MAX - page_size() + 2) == UINT64_MAX); /* overflow */ + assert_se(PAGE_ALIGN_U64(UINT64_MAX) == UINT64_MAX); /* overflow */ + + assert_se(PAGE_ALIGN_DOWN(page_size() - 1) == 0); + assert_se(PAGE_ALIGN_DOWN(page_size() ) == page_size()); + assert_se(PAGE_ALIGN_DOWN(page_size() + 1) == page_size()); + assert_se(PAGE_ALIGN_DOWN(page_size() * 123 - 1) == page_size() * 122); + assert_se(PAGE_ALIGN_DOWN(page_size() * 123 ) == page_size() * 123); + assert_se(PAGE_ALIGN_DOWN(page_size() * 123 + 1) == page_size() * 123); + assert_se(PAGE_ALIGN_DOWN(SIZE_MAX - page_size() - 1) == SIZE_MAX - page_size() * 2 + 1); + assert_se(PAGE_ALIGN_DOWN(SIZE_MAX - page_size() ) == SIZE_MAX - page_size() * 2 + 1); + assert_se(PAGE_ALIGN_DOWN(SIZE_MAX - page_size() + 1) == SIZE_MAX - page_size() + 1); + assert_se(PAGE_ALIGN_DOWN(SIZE_MAX - page_size() + 2) == SIZE_MAX - page_size() + 1); + + assert_se(PAGE_ALIGN_DOWN_U64(page_size() - 1) == 0); + assert_se(PAGE_ALIGN_DOWN_U64(page_size() ) == page_size()); + assert_se(PAGE_ALIGN_DOWN_U64(page_size() + 1) == page_size()); + assert_se(PAGE_ALIGN_DOWN_U64(page_size() * 123 - 1) == page_size() * 122); + assert_se(PAGE_ALIGN_DOWN_U64(page_size() * 123 ) == page_size() * 123); + assert_se(PAGE_ALIGN_DOWN_U64(page_size() * 123 + 1) == page_size() * 123); + assert_se(PAGE_ALIGN_DOWN_U64(SIZE_MAX - page_size() - 1) == SIZE_MAX - page_size() * 2 + 1); + assert_se(PAGE_ALIGN_DOWN_U64(SIZE_MAX - page_size() ) == SIZE_MAX - page_size() * 2 + 1); + assert_se(PAGE_ALIGN_DOWN_U64(SIZE_MAX - page_size() + 1) == SIZE_MAX - page_size() + 1); + assert_se(PAGE_ALIGN_DOWN_U64(SIZE_MAX - page_size() + 2) == SIZE_MAX - page_size() + 1); + + assert_se(PAGE_OFFSET(page_size() - 1) == page_size() - 1); + assert_se(PAGE_OFFSET(page_size() ) == 0); + assert_se(PAGE_OFFSET(page_size() + 1) == 1); + assert_se(PAGE_OFFSET(page_size() * 123 - 1) == page_size() - 1); + assert_se(PAGE_OFFSET(page_size() * 123 ) == 0); + assert_se(PAGE_OFFSET(page_size() * 123 + 1) == 1); + assert_se(PAGE_OFFSET(SIZE_MAX - page_size() - 1) == page_size() - 2); + assert_se(PAGE_OFFSET(SIZE_MAX - page_size() ) == page_size() - 1); + assert_se(PAGE_OFFSET(SIZE_MAX - page_size() + 1) == 0); + assert_se(PAGE_OFFSET(SIZE_MAX - page_size() + 2) == 1); + + assert_se(PAGE_OFFSET_U64(page_size() - 1) == page_size() - 1); + assert_se(PAGE_OFFSET_U64(page_size() ) == 0); + assert_se(PAGE_OFFSET_U64(page_size() + 1) == 1); + assert_se(PAGE_OFFSET_U64(page_size() * 123 - 1) == page_size() - 1); + assert_se(PAGE_OFFSET_U64(page_size() * 123 ) == 0); + assert_se(PAGE_OFFSET_U64(page_size() * 123 + 1) == 1); + assert_se(PAGE_OFFSET_U64(UINT64_MAX - page_size() - 1) == page_size() - 2); + assert_se(PAGE_OFFSET_U64(UINT64_MAX - page_size() ) == page_size() - 1); + assert_se(PAGE_OFFSET_U64(UINT64_MAX - page_size() + 1) == 0); + assert_se(PAGE_OFFSET_U64(UINT64_MAX - page_size() + 2) == 1); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-mempool.c b/src/test/test-mempool.c new file mode 100644 index 0000000..d3bc173 --- /dev/null +++ b/src/test/test-mempool.c @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "mempool.h" +#include "random-util.h" +#include "tests.h" + +struct element { + uint64_t value; +}; + +DEFINE_MEMPOOL(test_mempool, struct element, 8); + +TEST(mempool_trim) { + +#define NN 4000 + struct element *a[NN]; + size_t n_freed = 0; + + assert_se(!test_mempool.first_pool); + assert_se(!test_mempool.freelist); + + mempool_trim(&test_mempool); + + for (size_t i = 0; i < NN; i++) { + assert_se(a[i] = mempool_alloc_tile(&test_mempool)); + a[i]->value = i; + } + + mempool_trim(&test_mempool); + + /* free up to one third randomly */ + size_t x = 0; + for (size_t i = 0; i < NN/3; i++) { + x = (x + random_u64()) % ELEMENTSOF(a); + assert_se(!a[x] || a[x]->value == x); + + if (a[x]) + n_freed ++; + + a[x] = mempool_free_tile(&test_mempool, a[x]); + } + + mempool_trim(&test_mempool); + + /* free definitely at least one third */ + for (size_t i = 2; i < NN; i += 3) { + assert_se(!a[i] || a[i]->value == i); + if (a[i]) + n_freed ++; + a[i] = mempool_free_tile(&test_mempool, a[i]); + } + + mempool_trim(&test_mempool); + + /* Allocate another set of tiles, which will fill up the free list and allocate some new tiles */ + struct element *b[NN]; + for (size_t i = 0; i < NN; i++) { + assert_se(b[i] = mempool_alloc_tile(&test_mempool)); + b[i]->value = ~(uint64_t) i; + } + + mempool_trim(&test_mempool); + + /* free everything from the original set*/ + + for (size_t i = 0; i < NN; i += 1) { + assert_se(!a[i] || a[i]->value == i); + if (a[i]) + n_freed ++; + a[i] = mempool_free_tile(&test_mempool, a[i]); + } + + mempool_trim(&test_mempool); + + /* and now everything from the second set too */ + + for (size_t i = 0; i < NN; i += 1) { + assert_se(!b[i] || b[i]->value == ~(uint64_t) i); + if (b[i]) + n_freed ++; + b[i] = mempool_free_tile(&test_mempool, b[i]); + } + + assert_se(n_freed == NN * 2); + + mempool_trim(&test_mempool); + + assert_se(!test_mempool.first_pool); + assert_se(!test_mempool.freelist); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-mempress.c b/src/test/test-mempress.c new file mode 100644 index 0000000..26ce4ce --- /dev/null +++ b/src/test/test-mempress.c @@ -0,0 +1,309 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <fcntl.h> +#include <pthread.h> +#include <sys/mman.h> +#include <unistd.h> + +#include <sd-bus.h> +#include <sd-event.h> + +#include "bus-locator.h" +#include "bus-wait-for-jobs.h" +#include "fd-util.h" +#include "path-util.h" +#include "process-util.h" +#include "random-util.h" +#include "rm-rf.h" +#include "signal-util.h" +#include "socket-util.h" +#include "tests.h" +#include "tmpfile-util.h" +#include "unit-def.h" + +struct fake_pressure_context { + int fifo_fd; + int socket_fd; +}; + +static void *fake_pressure_thread(void *p) { + _cleanup_free_ struct fake_pressure_context *c = ASSERT_PTR(p); + _cleanup_close_ int cfd = -EBADF; + + usleep_safe(150); + + assert_se(write(c->fifo_fd, &(const char) { 'x' }, 1) == 1); + + usleep_safe(150); + + cfd = accept4(c->socket_fd, NULL, NULL, SOCK_CLOEXEC); + assert_se(cfd >= 0); + char buf[STRLEN("hello")+1] = {}; + assert_se(read(cfd, buf, sizeof(buf)-1) == sizeof(buf)-1); + assert_se(streq(buf, "hello")); + assert_se(write(cfd, &(const char) { 'z' }, 1) == 1); + + return 0; +} + +static int fake_pressure_callback(sd_event_source *s, void *userdata) { + int *value = userdata; + const char *d; + + assert_se(s); + assert_se(sd_event_source_get_description(s, &d) >= 0); + + *value *= d[0]; + + log_notice("memory pressure event: %s", d); + + if (*value == 7 * 'f' * 's') + assert_se(sd_event_exit(sd_event_source_get_event(s), 0) >= 0); + + return 0; +} + +TEST(fake_pressure) { + _cleanup_(sd_event_source_unrefp) sd_event_source *es = NULL, *ef = NULL; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_free_ char *j = NULL, *k = NULL; + _cleanup_(rm_rf_physical_and_freep) char *tmp = NULL; + _cleanup_close_ int fifo_fd = -EBADF, socket_fd = -EBADF; + union sockaddr_union sa; + pthread_t th; + int value = 7; + + assert_se(sd_event_default(&e) >= 0); + + assert_se(mkdtemp_malloc(NULL, &tmp) >= 0); + + assert_se(j = path_join(tmp, "fifo")); + assert_se(mkfifo(j, 0600) >= 0); + fifo_fd = open(j, O_CLOEXEC|O_RDWR|O_NONBLOCK); + assert_se(fifo_fd >= 0); + + assert_se(k = path_join(tmp, "sock")); + socket_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); + assert_se(socket_fd >= 0); + assert_se(sockaddr_un_set_path(&sa.un, k) >= 0); + assert_se(bind(socket_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) >= 0); + assert_se(listen(socket_fd, 1) >= 0); + + /* Ideally we'd just allocate this on the stack, but AddressSanitizer doesn't like it if threads + * access each other's stack */ + struct fake_pressure_context *fp = new(struct fake_pressure_context, 1); + assert_se(fp); + *fp = (struct fake_pressure_context) { + .fifo_fd = fifo_fd, + .socket_fd = socket_fd, + }; + + assert_se(pthread_create(&th, NULL, fake_pressure_thread, TAKE_PTR(fp)) == 0); + + assert_se(setenv("MEMORY_PRESSURE_WATCH", j, /* override= */ true) >= 0); + assert_se(unsetenv("MEMORY_PRESSURE_WRITE") >= 0); + + assert_se(sd_event_add_memory_pressure(e, &es, fake_pressure_callback, &value) >= 0); + assert_se(sd_event_source_set_description(es, "fifo event source") >= 0); + + assert_se(setenv("MEMORY_PRESSURE_WATCH", k, /* override= */ true) >= 0); + assert_se(setenv("MEMORY_PRESSURE_WRITE", "aGVsbG8K", /* override= */ true) >= 0); + + assert_se(sd_event_add_memory_pressure(e, &ef, fake_pressure_callback, &value) >= 0); + assert_se(sd_event_source_set_description(ef, "socket event source") >= 0); + + assert_se(sd_event_loop(e) >= 0); + + assert_se(value == 7 * 'f' * 's'); + + assert_se(pthread_join(th, NULL) == 0); +} + +struct real_pressure_context { + sd_event_source *pid; +}; + +static int real_pressure_callback(sd_event_source *s, void *userdata) { + struct real_pressure_context *c = ASSERT_PTR(userdata); + const char *d; + + assert_se(s); + assert_se(sd_event_source_get_description(s, &d) >= 0); + + log_notice("real_memory pressure event: %s", d); + + sd_event_trim_memory(); + + assert_se(c->pid); + assert_se(sd_event_source_send_child_signal(c->pid, SIGKILL, NULL, 0) >= 0); + c->pid = NULL; + + return 0; +} + +#define MMAP_SIZE (10 * 1024 * 1024) + +_noreturn_ static void real_pressure_eat_memory(int pipe_fd) { + size_t ate = 0; + + /* Allocates and touches 10M at a time, until runs out of memory */ + + char x; + assert_se(read(pipe_fd, &x, 1) == 1); /* Wait for the GO! */ + + for (;;) { + void *p; + + p = mmap(NULL, MMAP_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + assert_se(p != MAP_FAILED); + + log_info("Eating another %s.", FORMAT_BYTES(MMAP_SIZE)); + + memset(p, random_u32() & 0xFF, MMAP_SIZE); + ate += MMAP_SIZE; + + log_info("Ate %s in total.", FORMAT_BYTES(ate)); + + usleep_safe(50 * USEC_PER_MSEC); + } +} + +static int real_pressure_child_callback(sd_event_source *s, const siginfo_t *si, void *userdata) { + assert_se(s); + assert_se(si); + + log_notice("child dead"); + + assert_se(si->si_signo == SIGCHLD); + assert_se(si->si_status == SIGKILL); + assert_se(si->si_code == CLD_KILLED); + + assert_se(sd_event_exit(sd_event_source_get_event(s), 31) >= 0); + return 0; +} + +TEST(real_pressure) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *es = NULL, *cs = NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_close_pair_ int pipe_fd[2] = EBADF_PAIR; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_free_ char *scope = NULL; + const char *object; + int r; + pid_t pid; + + r = sd_bus_open_system(&bus); + if (r < 0) { + log_notice_errno(r, "Can't connect to system bus, skipping test: %m"); + return; + } + + assert_se(bus_wait_for_jobs_new(bus, &w) >= 0); + + assert_se(bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit") >= 0); + assert_se(asprintf(&scope, "test-%" PRIu64 ".scope", random_u64()) >= 0); + assert_se(sd_bus_message_append(m, "ss", scope, "fail") >= 0); + assert_se(sd_bus_message_open_container(m, 'a', "(sv)") >= 0); + assert_se(sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, 0) >= 0); + assert_se(sd_bus_message_append(m, "(sv)", "MemoryAccounting", "b", true) >= 0); + assert_se(sd_bus_message_close_container(m) >= 0); + assert_se(sd_bus_message_append(m, "a(sa(sv))", 0) >= 0); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + log_notice_errno(r, "Can't issue transient unit call, skipping test: %m"); + return; + } + + assert_se(sd_bus_message_read(reply, "o", &object) >= 0); + + assert_se(bus_wait_for_jobs_one(w, object, /* quiet= */ false, /* extra_args= */ NULL) >= 0); + + assert_se(sd_event_default(&e) >= 0); + + assert_se(pipe2(pipe_fd, O_CLOEXEC) >= 0); + + r = safe_fork("(eat-memory)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM, &pid); + assert_se(r >= 0); + if (r == 0) { + real_pressure_eat_memory(pipe_fd[0]); + _exit(EXIT_SUCCESS); + } + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0); + assert_se(sd_event_add_child(e, &cs, pid, WEXITED, real_pressure_child_callback, NULL) >= 0); + assert_se(sd_event_source_set_child_process_own(cs, true) >= 0); + + assert_se(unsetenv("MEMORY_PRESSURE_WATCH") >= 0); + assert_se(unsetenv("MEMORY_PRESSURE_WRITE") >= 0); + + struct real_pressure_context context = { + .pid = cs, + }; + + r = sd_event_add_memory_pressure(e, &es, real_pressure_callback, &context); + if (r < 0) { + log_notice_errno(r, "Can't allocate memory pressure fd, skipping test: %m"); + return; + } + + assert_se(sd_event_source_set_description(es, "real pressure event source") >= 0); + assert_se(sd_event_source_set_memory_pressure_type(es, "some") == 0); + assert_se(sd_event_source_set_memory_pressure_type(es, "full") > 0); + assert_se(sd_event_source_set_memory_pressure_type(es, "full") == 0); + assert_se(sd_event_source_set_memory_pressure_type(es, "some") > 0); + assert_se(sd_event_source_set_memory_pressure_type(es, "some") == 0); + assert_se(sd_event_source_set_memory_pressure_period(es, 70 * USEC_PER_MSEC, USEC_PER_SEC) > 0); + assert_se(sd_event_source_set_memory_pressure_period(es, 70 * USEC_PER_MSEC, USEC_PER_SEC) == 0); + assert_se(sd_event_source_set_enabled(es, SD_EVENT_ONESHOT) >= 0); + + _cleanup_free_ char *uo = NULL; + assert_se(uo = unit_dbus_path_from_name(scope)); + + uint64_t mcurrent = UINT64_MAX; + assert_se(sd_bus_get_property_trivial(bus, "org.freedesktop.systemd1", uo, "org.freedesktop.systemd1.Scope", "MemoryCurrent", &error, 't', &mcurrent) >= 0); + + printf("current: %" PRIu64 "\n", mcurrent); + if (mcurrent == UINT64_MAX) { + log_notice_errno(r, "Memory accounting not available, skipping test: %m"); + return; + } + + m = sd_bus_message_unref(m); + + assert_se(bus_message_new_method_call(bus, &m, bus_systemd_mgr, "SetUnitProperties") >= 0); + assert_se(sd_bus_message_append(m, "sb", scope, true) >= 0); + assert_se(sd_bus_message_open_container(m, 'a', "(sv)") >= 0); + assert_se(sd_bus_message_append(m, "(sv)", "MemoryHigh", "t", mcurrent + (15 * 1024 * 1024)) >= 0); + assert_se(sd_bus_message_append(m, "(sv)", "MemoryMax", "t", mcurrent + (50 * 1024 * 1024)) >= 0); + assert_se(sd_bus_message_close_container(m) >= 0); + + assert_se(sd_bus_call(bus, m, 0, NULL, NULL) >= 0); + + /* Generate some memory allocations via mempool */ +#define NN (1024) + Hashmap **h = new(Hashmap*, NN); + for (int i = 0; i < NN; i++) + h[i] = hashmap_new(NULL); + for (int i = 0; i < NN; i++) + hashmap_free(h[i]); + free(h); + + /* Now start eating memory */ + assert_se(write(pipe_fd[1], &(const char) { 'x' }, 1) == 1); + + assert_se(sd_event_loop(e) >= 0); + int ex = 0; + assert_se(sd_event_get_exit_code(e, &ex) >= 0); + assert_se(ex == 31); +} + +static int outro(void) { + hashmap_trim_pools(); + return 0; +} + +DEFINE_TEST_MAIN_FULL(LOG_DEBUG, NULL, outro); diff --git a/src/test/test-memstream-util.c b/src/test/test-memstream-util.c new file mode 100644 index 0000000..254bdca --- /dev/null +++ b/src/test/test-memstream-util.c @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "memstream-util.h" +#include "string-util.h" +#include "tests.h" + +TEST(memstream_done) { + _cleanup_(memstream_done) MemStream m = {}; + + assert_se(memstream_init(&m)); +} + +TEST(memstream_empty) { + _cleanup_(memstream_done) MemStream m = {}; + _cleanup_free_ char *buf = NULL; + size_t sz; + + assert_se(memstream_init(&m)); + assert_se(memstream_finalize(&m, &buf, &sz) >= 0); + assert_se(streq(buf, "")); + assert_se(sz == 0); +} + +TEST(memstream) { + _cleanup_(memstream_done) MemStream m = {}; + _cleanup_free_ char *buf = NULL; + size_t sz; + FILE *f; + + assert_se(f = memstream_init(&m)); + fputs("hoge", f); + fputs("おはよう!", f); + fputs(u8"😀😀😀", f); + assert_se(memstream_finalize(&m, &buf, &sz) >= 0); + assert_se(streq(buf, u8"hogeおはよう!😀😀😀")); + assert_se(sz == strlen(u8"hogeおはよう!😀😀😀")); + + buf = mfree(buf); + + assert_se(f = memstream_init(&m)); + fputs("second", f); + assert_se(memstream_finalize(&m, &buf, &sz) >= 0); + assert_se(streq(buf, "second")); + assert_se(sz == strlen("second")); +} + +TEST(memstream_dump) { + _cleanup_(memstream_done) MemStream m = {}; + FILE *f; + + assert_se(f = memstream_init(&m)); + fputs("first", f); + assert_se(memstream_dump(LOG_DEBUG, &m) >= 0); + + assert_se(f = memstream_init(&m)); + fputs("second", f); + assert_se(memstream_dump(LOG_DEBUG, &m) >= 0); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-mkdir.c b/src/test/test-mkdir.c new file mode 100644 index 0000000..4820b32 --- /dev/null +++ b/src/test/test-mkdir.c @@ -0,0 +1,141 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <unistd.h> + +#include "capability-util.h" +#include "fs-util.h" +#include "mkdir.h" +#include "path-util.h" +#include "process-util.h" +#include "rm-rf.h" +#include "stat-util.h" +#include "tests.h" +#include "tmpfile-util.h" +#include "user-util.h" + +TEST(mkdir_p_safe) { + _cleanup_(rm_rf_physical_and_freep) char *tmp = NULL; + _cleanup_free_ char *p = NULL, *q = NULL; + int r; + + assert_se(mkdtemp_malloc("/tmp/test-mkdir-XXXXXX", &tmp) >= 0); + + assert_se(p = path_join(tmp, "run/aaa/bbb")); + assert_se(mkdir_p(p, 0755) >= 0); + assert_se(is_dir(p, false) > 0); + assert_se(is_dir(p, true) > 0); + + p = mfree(p); + assert_se(p = path_join(tmp, "run/ccc/ddd")); + assert_se(mkdir_p_safe(tmp, p, 0755, UID_INVALID, GID_INVALID, 0) >= 0); + assert_se(is_dir(p, false) > 0); + assert_se(is_dir(p, true) > 0); + + p = mfree(p); + assert_se(p = path_join(tmp, "var/run")); + assert_se(mkdir_parents_safe(tmp, p, 0755, UID_INVALID, GID_INVALID, 0) >= 0); + assert_se(symlink("../run", p) >= 0); + assert_se(is_dir(p, false) == 0); + assert_se(is_dir(p, true) > 0); + + assert_se(mkdir_safe(p, 0755, UID_INVALID, GID_INVALID, 0) == -ENOTDIR); + assert_se(mkdir_safe(p, 0755, UID_INVALID, GID_INVALID, MKDIR_IGNORE_EXISTING) >= 0); + assert_se(mkdir_safe(p, 0755, UID_INVALID, GID_INVALID, MKDIR_FOLLOW_SYMLINK) >= 0); + assert_se(is_dir(p, false) == 0); + assert_se(is_dir(p, true) > 0); + + p = mfree(p); + assert_se(p = path_join(tmp, "var/run/hoge/foo/baz")); + assert_se(mkdir_p_safe(tmp, p, 0755, UID_INVALID, GID_INVALID, 0) >= 0); + assert_se(is_dir(p, false) > 0); + assert_se(is_dir(p, true) > 0); + + p = mfree(p); + assert_se(p = path_join(tmp, "not-exists")); + assert_se(q = path_join(p, "aaa")); + assert_se(mkdir_p_safe(p, q, 0755, UID_INVALID, GID_INVALID, 0) == -ENOENT); + + p = mfree(p); + q = mfree(q); + assert_se(p = path_join(tmp, "regular-file")); + assert_se(q = path_join(p, "aaa")); + assert_se(touch(p) >= 0); + assert_se(mkdir_p_safe(p, q, 0755, UID_INVALID, GID_INVALID, 0) == -ENOTDIR); + + p = mfree(p); + q = mfree(q); + assert_se(p = path_join(tmp, "symlink")); + assert_se(q = path_join(p, "hoge/foo")); + assert_se(symlink("aaa", p) >= 0); + assert_se(mkdir_p_safe(tmp, q, 0755, UID_INVALID, GID_INVALID, 0) >= 0); + assert_se(is_dir(q, false) > 0); + assert_se(is_dir(q, true) > 0); + q = mfree(q); + assert_se(q = path_join(tmp, "aaa/hoge/foo")); + assert_se(is_dir(q, false) > 0); + assert_se(is_dir(q, true) > 0); + + assert_se(mkdir_p_safe(tmp, "/tmp/test-mkdir-outside", 0755, UID_INVALID, GID_INVALID, 0) == -ENOTDIR); + + p = mfree(p); + assert_se(p = path_join(tmp, "zero-mode/should-fail-to-create-child")); + assert_se(mkdir_parents_safe(tmp, p, 0000, UID_INVALID, GID_INVALID, 0) >= 0); + r = safe_fork("(test-mkdir-no-cap)", FORK_DEATHSIG_SIGTERM | FORK_WAIT | FORK_LOG, NULL); + if (r == 0) { + (void) capability_bounding_set_drop(0, /* right_now = */ true); + assert_se(mkdir_p_safe(tmp, p, 0000, UID_INVALID, GID_INVALID, 0) == -EACCES); + _exit(EXIT_SUCCESS); + } + assert_se(r >= 0); +} + +TEST(mkdir_p_root) { + _cleanup_(rm_rf_physical_and_freep) char *tmp = NULL; + _cleanup_free_ char *p = NULL; + + assert_se(mkdtemp_malloc("/tmp/test-mkdir-XXXXXX", &tmp) >= 0); + + assert_se(p = path_join(tmp, "run/aaa/bbb")); + assert_se(mkdir_p_root(tmp, "/run/aaa/bbb", UID_INVALID, GID_INVALID, 0755, NULL) >= 0); + assert_se(is_dir(p, false) > 0); + assert_se(is_dir(p, true) > 0); + + p = mfree(p); + assert_se(p = path_join(tmp, "var/run")); + assert_se(mkdir_parents_safe(tmp, p, 0755, UID_INVALID, GID_INVALID, 0) >= 0); + assert_se(symlink("../run", p) >= 0); + assert_se(is_dir(p, false) == 0); + assert_se(is_dir(p, true) > 0); + + p = mfree(p); + assert_se(p = path_join(tmp, "var/run/hoge/foo/baz")); + assert_se(mkdir_p_root(tmp, "/var/run/hoge/foo/baz", UID_INVALID, GID_INVALID, 0755, NULL) >= 0); + assert_se(is_dir(p, false) > 0); + assert_se(is_dir(p, true) > 0); + + p = mfree(p); + assert_se(p = path_join(tmp, "not-exists")); + assert_se(mkdir_p_root(p, "/aaa", UID_INVALID, GID_INVALID, 0755, NULL) == -ENOENT); + + p = mfree(p); + assert_se(p = path_join(tmp, "regular-file")); + assert_se(touch(p) >= 0); + assert_se(mkdir_p_root(p, "/aaa", UID_INVALID, GID_INVALID, 0755, NULL) == -ENOTDIR); + + /* FIXME: The tests below do not work. + p = mfree(p); + assert_se(p = path_join(tmp, "symlink")); + assert_se(symlink("aaa", p) >= 0); + assert_se(mkdir_p_root(tmp, "/symlink/hoge/foo", UID_INVALID, GID_INVALID, 0755) >= 0); + p = mfree(p); + assert_se(p = path_join(tmp, "symlink/hoge/foo")); + assert_se(is_dir(p, false) > 0); + assert_se(is_dir(p, true) > 0); + p = mfree(p); + assert_se(p = path_join(tmp, "aaa/hoge/foo")); + assert_se(is_dir(p, false) > 0); + assert_se(is_dir(p, true) > 0); + */ +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-modhex.c b/src/test/test-modhex.c new file mode 100644 index 0000000..6725732 --- /dev/null +++ b/src/test/test-modhex.c @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "recovery-key.h" +#include "alloc-util.h" +#include "string-util.h" +#include "tests.h" + +static void test_normalize_recovery_key(const char *t, const char *expected) { + _cleanup_free_ char *z = NULL; + int r; + + assert_se(t); + + r = normalize_recovery_key(t, &z); + assert_se(expected ? + (r >= 0 && streq(z, expected)) : + (r == -EINVAL && z == NULL)); +} + +TEST(normalize_recovery_key_all) { + test_normalize_recovery_key("iefgcelh-biduvkjv-cjvuncnk-vlfchdid-jhtuhhde-urkllkeg-ilkjgbrt-hjkbgktj", + "iefgcelh-biduvkjv-cjvuncnk-vlfchdid-jhtuhhde-urkllkeg-ilkjgbrt-hjkbgktj"); + + test_normalize_recovery_key("iefgcelhbiduvkjvcjvuncnkvlfchdidjhtuhhdeurkllkegilkjgbrthjkbgktj", + "iefgcelh-biduvkjv-cjvuncnk-vlfchdid-jhtuhhde-urkllkeg-ilkjgbrt-hjkbgktj"); + + test_normalize_recovery_key("IEFGCELH-BIDUVKJV-CJVUNCNK-VLFCHDID-JHTUHHDE-URKLLKEG-ILKJGBRT-HJKBGKTJ", + "iefgcelh-biduvkjv-cjvuncnk-vlfchdid-jhtuhhde-urkllkeg-ilkjgbrt-hjkbgktj"); + + test_normalize_recovery_key("IEFGCELHBIDUVKJVCJVUNCNKVLFCHDIDJHTUHHDEURKLLKEGILKJGBRTHJKBGKTJ", + "iefgcelh-biduvkjv-cjvuncnk-vlfchdid-jhtuhhde-urkllkeg-ilkjgbrt-hjkbgktj"); + + test_normalize_recovery_key("Iefgcelh-Biduvkjv-Cjvuncnk-Vlfchdid-Jhtuhhde-Urkllkeg-Ilkjgbrt-Hjkbgktj", + "iefgcelh-biduvkjv-cjvuncnk-vlfchdid-jhtuhhde-urkllkeg-ilkjgbrt-hjkbgktj"); + + test_normalize_recovery_key("Iefgcelhbiduvkjvcjvuncnkvlfchdidjhtuhhdeurkllkegilkjgbrthjkbgktj", + "iefgcelh-biduvkjv-cjvuncnk-vlfchdid-jhtuhhde-urkllkeg-ilkjgbrt-hjkbgktj"); + + test_normalize_recovery_key("iefgcelh-biduvkjv-cjvuncnk-vlfchdid-jhtuhhde-urkllkeg-ilkjgbrt-hjkbgkt", NULL); + test_normalize_recovery_key("iefgcelhbiduvkjvcjvuncnkvlfchdidjhtuhhdeurkllkegilkjgbrthjkbgkt", NULL); + test_normalize_recovery_key("IEFGCELHBIDUVKJVCJVUNCNKVLFCHDIDJHTUHHDEURKLLKEGILKJGBRTHJKBGKT", NULL); + + test_normalize_recovery_key("xefgcelh-biduvkjv-cjvuncnk-vlfchdid-jhtuhhde-urkllkeg-ilkjgbrt-hjkbgktj", NULL); + test_normalize_recovery_key("Xefgcelh-biduvkjv-cjvuncnk-vlfchdid-jhtuhhde-urkllkeg-ilkjgbrt-hjkbgktj", NULL); + test_normalize_recovery_key("iefgcelh+biduvkjv-cjvuncnk-vlfchdid-jhtuhhde-urkllkeg-ilkjgbrt-hjkbgktj", NULL); + test_normalize_recovery_key("iefgcelhebiduvkjv-cjvuncnk-vlfchdid-jhtuhhde-urkllkeg-ilkjgbrt-hjkbgktj", NULL); + + test_normalize_recovery_key("", NULL); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-mount-util.c b/src/test/test-mount-util.c new file mode 100644 index 0000000..c3d0acb --- /dev/null +++ b/src/test/test-mount-util.c @@ -0,0 +1,509 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <sys/mount.h> +#include <sys/statvfs.h> + +#include "alloc-util.h" +#include "capability-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "libmount-util.h" +#include "missing_magic.h" +#include "missing_mount.h" +#include "mkdir.h" +#include "mount-util.h" +#include "mountpoint-util.h" +#include "namespace-util.h" +#include "path-util.h" +#include "process-util.h" +#include "random-util.h" +#include "rm-rf.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" +#include "tmpfile-util.h" + +TEST(mount_option_mangle) { + char *opts = NULL; + unsigned long f; + + assert_se(mount_option_mangle(NULL, MS_RDONLY|MS_NOSUID, &f, &opts) == 0); + assert_se(f == (MS_RDONLY|MS_NOSUID)); + assert_se(opts == NULL); + + assert_se(mount_option_mangle("", MS_RDONLY|MS_NOSUID, &f, &opts) == 0); + assert_se(f == (MS_RDONLY|MS_NOSUID)); + assert_se(opts == NULL); + + assert_se(mount_option_mangle("ro,nosuid,nodev,noexec", 0, &f, &opts) == 0); + assert_se(f == (MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC)); + assert_se(opts == NULL); + + assert_se(mount_option_mangle("ro,nosuid,nodev,noexec,mode=0755", 0, &f, &opts) == 0); + assert_se(f == (MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC)); + assert_se(streq(opts, "mode=0755")); + opts = mfree(opts); + + assert_se(mount_option_mangle("rw,nosuid,foo,hogehoge,nodev,mode=0755", 0, &f, &opts) == 0); + assert_se(f == (MS_NOSUID|MS_NODEV)); + assert_se(streq(opts, "foo,hogehoge,mode=0755")); + opts = mfree(opts); + + assert_se(mount_option_mangle("rw,nosuid,nodev,noexec,relatime,net_cls,net_prio", MS_RDONLY, &f, &opts) == 0); + assert_se(f == (MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_RELATIME)); + assert_se(streq(opts, "net_cls,net_prio")); + opts = mfree(opts); + + assert_se(mount_option_mangle("rw,nosuid,nodev,relatime,size=1630748k,mode=0700,uid=1000,gid=1000", MS_RDONLY, &f, &opts) == 0); + assert_se(f == (MS_NOSUID|MS_NODEV|MS_RELATIME)); + assert_se(streq(opts, "size=1630748k,mode=0700,uid=1000,gid=1000")); + opts = mfree(opts); + + assert_se(mount_option_mangle("size=1630748k,rw,gid=1000,,,nodev,relatime,,mode=0700,nosuid,uid=1000", MS_RDONLY, &f, &opts) == 0); + assert_se(f == (MS_NOSUID|MS_NODEV|MS_RELATIME)); + assert_se(streq(opts, "size=1630748k,gid=1000,mode=0700,uid=1000")); + opts = mfree(opts); + + assert_se(mount_option_mangle("rw,exec,size=8143984k,nr_inodes=2035996,mode=0755", MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, &f, &opts) == 0); + assert_se(f == (MS_NOSUID|MS_NODEV)); + assert_se(streq(opts, "size=8143984k,nr_inodes=2035996,mode=0755")); + opts = mfree(opts); + + assert_se(mount_option_mangle("rw,relatime,fmask=0022,,,dmask=0022", MS_RDONLY, &f, &opts) == 0); + assert_se(f == MS_RELATIME); + assert_se(streq(opts, "fmask=0022,dmask=0022")); + opts = mfree(opts); + + assert_se(mount_option_mangle("rw,relatime,fmask=0022,dmask=0022,\"hogehoge", MS_RDONLY, &f, &opts) < 0); + + assert_se(mount_option_mangle("mode=01777,size=10%,nr_inodes=400k,uid=496107520,gid=496107520,context=\"system_u:object_r:svirt_sandbox_file_t:s0:c0,c1\"", 0, &f, &opts) == 0); + assert_se(f == 0); + assert_se(streq(opts, "mode=01777,size=10%,nr_inodes=400k,uid=496107520,gid=496107520,context=\"system_u:object_r:svirt_sandbox_file_t:s0:c0,c1\"")); + opts = mfree(opts); +} + +static void test_mount_flags_to_string_one(unsigned long flags, const char *expected) { + _cleanup_free_ char *x = NULL; + int r; + + r = mount_flags_to_string(flags, &x); + log_info("flags: %#lX → %d/\"%s\"", flags, r, strnull(x)); + assert_se(r >= 0); + assert_se(streq(x, expected)); +} + +TEST(mount_flags_to_string) { + test_mount_flags_to_string_one(0, "0"); + test_mount_flags_to_string_one(MS_RDONLY, "MS_RDONLY"); + test_mount_flags_to_string_one(MS_NOSUID, "MS_NOSUID"); + test_mount_flags_to_string_one(MS_NODEV, "MS_NODEV"); + test_mount_flags_to_string_one(MS_NOEXEC, "MS_NOEXEC"); + test_mount_flags_to_string_one(MS_SYNCHRONOUS, "MS_SYNCHRONOUS"); + test_mount_flags_to_string_one(MS_REMOUNT, "MS_REMOUNT"); + test_mount_flags_to_string_one(MS_MANDLOCK, "MS_MANDLOCK"); + test_mount_flags_to_string_one(MS_DIRSYNC, "MS_DIRSYNC"); + test_mount_flags_to_string_one(MS_NOSYMFOLLOW, "MS_NOSYMFOLLOW"); + test_mount_flags_to_string_one(MS_NOATIME, "MS_NOATIME"); + test_mount_flags_to_string_one(MS_NODIRATIME, "MS_NODIRATIME"); + test_mount_flags_to_string_one(MS_BIND, "MS_BIND"); + test_mount_flags_to_string_one(MS_MOVE, "MS_MOVE"); + test_mount_flags_to_string_one(MS_REC, "MS_REC"); + test_mount_flags_to_string_one(MS_SILENT, "MS_SILENT"); + test_mount_flags_to_string_one(MS_POSIXACL, "MS_POSIXACL"); + test_mount_flags_to_string_one(MS_UNBINDABLE, "MS_UNBINDABLE"); + test_mount_flags_to_string_one(MS_PRIVATE, "MS_PRIVATE"); + test_mount_flags_to_string_one(MS_SLAVE, "MS_SLAVE"); + test_mount_flags_to_string_one(MS_SHARED, "MS_SHARED"); + test_mount_flags_to_string_one(MS_RELATIME, "MS_RELATIME"); + test_mount_flags_to_string_one(MS_KERNMOUNT, "MS_KERNMOUNT"); + test_mount_flags_to_string_one(MS_I_VERSION, "MS_I_VERSION"); + test_mount_flags_to_string_one(MS_STRICTATIME, "MS_STRICTATIME"); + test_mount_flags_to_string_one(MS_LAZYTIME, "MS_LAZYTIME"); + test_mount_flags_to_string_one(MS_LAZYTIME|MS_STRICTATIME, "MS_STRICTATIME|MS_LAZYTIME"); + test_mount_flags_to_string_one(UINT_MAX, + "MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_SYNCHRONOUS|MS_REMOUNT|" + "MS_MANDLOCK|MS_DIRSYNC|MS_NOSYMFOLLOW|MS_NOATIME|MS_NODIRATIME|" + "MS_BIND|MS_MOVE|MS_REC|MS_SILENT|MS_POSIXACL|MS_UNBINDABLE|" + "MS_PRIVATE|MS_SLAVE|MS_SHARED|MS_RELATIME|MS_KERNMOUNT|" + "MS_I_VERSION|MS_STRICTATIME|MS_LAZYTIME|fc000200"); +} + +TEST(bind_remount_recursive) { + _cleanup_(rm_rf_physical_and_freep) char *tmp = NULL; + _cleanup_free_ char *subdir = NULL; + + if (geteuid() != 0 || have_effective_cap(CAP_SYS_ADMIN) <= 0) { + (void) log_tests_skipped("not running privileged"); + return; + } + + assert_se(mkdtemp_malloc("/tmp/XXXXXX", &tmp) >= 0); + subdir = path_join(tmp, "subdir"); + assert_se(subdir); + assert_se(mkdir(subdir, 0755) >= 0); + + FOREACH_STRING(p, "/usr", "/sys", "/", tmp) { + pid_t pid; + + pid = fork(); + assert_se(pid >= 0); + + if (pid == 0) { + struct statvfs svfs; + /* child */ + assert_se(detach_mount_namespace() >= 0); + + /* Check that the subdir is writable (it must be because it's in /tmp) */ + assert_se(statvfs(subdir, &svfs) >= 0); + assert_se(!FLAGS_SET(svfs.f_flag, ST_RDONLY)); + + /* Make the subdir a bind mount */ + assert_se(mount_nofollow(subdir, subdir, NULL, MS_BIND|MS_REC, NULL) >= 0); + + /* Ensure it's still writable */ + assert_se(statvfs(subdir, &svfs) >= 0); + assert_se(!FLAGS_SET(svfs.f_flag, ST_RDONLY)); + + /* Now mark the path we currently run for read-only */ + assert_se(bind_remount_recursive(p, MS_RDONLY, MS_RDONLY, path_equal(p, "/sys") ? STRV_MAKE("/sys/kernel") : NULL) >= 0); + + /* Ensure that this worked on the top-level */ + assert_se(statvfs(p, &svfs) >= 0); + assert_se(FLAGS_SET(svfs.f_flag, ST_RDONLY)); + + /* And ensure this had an effect on the subdir exactly if we are talking about a path above the subdir */ + assert_se(statvfs(subdir, &svfs) >= 0); + assert_se(FLAGS_SET(svfs.f_flag, ST_RDONLY) == !!path_startswith(subdir, p)); + + _exit(EXIT_SUCCESS); + } + + assert_se(wait_for_terminate_and_check("test-remount-rec", pid, WAIT_LOG) == EXIT_SUCCESS); + } +} + +TEST(bind_remount_one) { + pid_t pid; + + if (geteuid() != 0 || have_effective_cap(CAP_SYS_ADMIN) <= 0) { + (void) log_tests_skipped("not running privileged"); + return; + } + + pid = fork(); + assert_se(pid >= 0); + + if (pid == 0) { + /* child */ + + _cleanup_fclose_ FILE *proc_self_mountinfo = NULL; + + assert_se(detach_mount_namespace() >= 0); + + assert_se(fopen_unlocked("/proc/self/mountinfo", "re", &proc_self_mountinfo) >= 0); + + assert_se(bind_remount_one_with_mountinfo("/run", MS_RDONLY, MS_RDONLY, proc_self_mountinfo) >= 0); + assert_se(bind_remount_one_with_mountinfo("/run", MS_NOEXEC, MS_RDONLY|MS_NOEXEC, proc_self_mountinfo) >= 0); + assert_se(bind_remount_one_with_mountinfo("/proc/idontexist", MS_RDONLY, MS_RDONLY, proc_self_mountinfo) == -ENOENT); + assert_se(bind_remount_one_with_mountinfo("/proc/self", MS_RDONLY, MS_RDONLY, proc_self_mountinfo) == -EINVAL); + assert_se(bind_remount_one_with_mountinfo("/", MS_RDONLY, MS_RDONLY, proc_self_mountinfo) >= 0); + + _exit(EXIT_SUCCESS); + } + + assert_se(wait_for_terminate_and_check("test-remount-one", pid, WAIT_LOG) == EXIT_SUCCESS); +} + +TEST(make_mount_point_inode) { + _cleanup_(rm_rf_physical_and_freep) char *d = NULL; + const char *src_file, *src_dir, *dst_file, *dst_dir; + struct stat st; + + assert_se(mkdtemp_malloc(NULL, &d) >= 0); + + src_file = strjoina(d, "/src/file"); + src_dir = strjoina(d, "/src/dir"); + dst_file = strjoina(d, "/dst/file"); + dst_dir = strjoina(d, "/dst/dir"); + + assert_se(mkdir_p(src_dir, 0755) >= 0); + assert_se(mkdir_parents(dst_file, 0755) >= 0); + assert_se(touch(src_file) >= 0); + + assert_se(make_mount_point_inode_from_path(src_file, dst_file, 0755) >= 0); + assert_se(make_mount_point_inode_from_path(src_dir, dst_dir, 0755) >= 0); + + assert_se(stat(dst_dir, &st) == 0); + assert_se(S_ISDIR(st.st_mode)); + assert_se(stat(dst_file, &st) == 0); + assert_se(S_ISREG(st.st_mode)); + assert_se(!(S_IXUSR & st.st_mode)); + assert_se(!(S_IXGRP & st.st_mode)); + assert_se(!(S_IXOTH & st.st_mode)); + + assert_se(unlink(dst_file) == 0); + assert_se(rmdir(dst_dir) == 0); + + assert_se(stat(src_file, &st) == 0); + assert_se(make_mount_point_inode_from_stat(&st, dst_file, 0755) >= 0); + assert_se(stat(src_dir, &st) == 0); + assert_se(make_mount_point_inode_from_stat(&st, dst_dir, 0755) >= 0); + + assert_se(stat(dst_dir, &st) == 0); + assert_se(S_ISDIR(st.st_mode)); + assert_se(stat(dst_file, &st) == 0); + assert_se(S_ISREG(st.st_mode)); + assert_se(!(S_IXUSR & st.st_mode)); + assert_se(!(S_IXGRP & st.st_mode)); + assert_se(!(S_IXOTH & st.st_mode)); +} + +TEST(make_mount_switch_root) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_free_ char *s = NULL; + int r; + + if (geteuid() != 0 || have_effective_cap(CAP_SYS_ADMIN) <= 0) { + (void) log_tests_skipped("not running privileged"); + return; + } + + assert_se(mkdtemp_malloc(NULL, &t) >= 0); + + assert_se(asprintf(&s, "%s/somerandomname%" PRIu64, t, random_u64()) >= 0); + assert_se(s); + assert_se(touch(s) >= 0); + + for (int force_ms_move = 0; force_ms_move < 2; force_ms_move++) { + r = safe_fork("(switch-root)", + FORK_RESET_SIGNALS | + FORK_CLOSE_ALL_FDS | + FORK_DEATHSIG_SIGTERM | + FORK_WAIT | + FORK_REOPEN_LOG | + FORK_LOG | + FORK_NEW_MOUNTNS | + FORK_MOUNTNS_SLAVE, + NULL); + assert_se(r >= 0); + + if (r == 0) { + assert_se(make_mount_point(t) >= 0); + assert_se(mount_switch_root_full(t, /* mount_propagation_flag= */ 0, force_ms_move) >= 0); + + assert_se(access(ASSERT_PTR(strrchr(s, '/')), F_OK) >= 0); /* absolute */ + assert_se(access(ASSERT_PTR(strrchr(s, '/')) + 1, F_OK) >= 0); /* relative */ + assert_se(access(s, F_OK) < 0 && errno == ENOENT); /* doesn't exist in our new environment */ + + _exit(EXIT_SUCCESS); + } + } +} + +TEST(umount_recursive) { + static const struct { + const char *prefix; + const char * const keep[3]; + } test_table[] = { + { + .prefix = NULL, + .keep = {}, + }, + { + .prefix = "/run", + .keep = {}, + }, + { + .prefix = NULL, + .keep = { "/dev/shm", NULL }, + }, + { + .prefix = "/dev", + .keep = { "/dev/pts", "/dev/shm", NULL }, + }, + }; + + int r; + + FOREACH_ARRAY(t, test_table, ELEMENTSOF(test_table)) { + + r = safe_fork("(umount-rec)", + FORK_RESET_SIGNALS | + FORK_CLOSE_ALL_FDS | + FORK_DEATHSIG_SIGTERM | + FORK_WAIT | + FORK_REOPEN_LOG | + FORK_LOG | + FORK_NEW_MOUNTNS | + FORK_MOUNTNS_SLAVE, + NULL); + + if (ERRNO_IS_NEG_PRIVILEGE(r)) + return (void) log_notice("Skipping umount_recursive() test, lacking privileges"); + + assert_se(r >= 0); + if (r == 0) { /* child */ + _cleanup_(mnt_free_tablep) struct libmnt_table *table = NULL; + _cleanup_(mnt_free_iterp) struct libmnt_iter *iter = NULL; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *k = NULL; + + /* Open /p/s/m file before we unmount everything (which might include /proc/) */ + f = fopen("/proc/self/mountinfo", "re"); + if (!f) { + log_error_errno(errno, "Failed to open /proc/self/mountinfo: %m"); + _exit(EXIT_FAILURE); + } + + assert_se(k = strv_join((char**) t->keep, " ")); + log_info("detaching just %s (keep: %s)", strna(t->prefix), strna(empty_to_null(k))); + + assert_se(umount_recursive_full(t->prefix, MNT_DETACH, (char**) t->keep) >= 0); + + r = libmount_parse("/proc/self/mountinfo", f, &table, &iter); + if (r < 0) { + log_error_errno(r, "Failed to parse /proc/self/mountinfo: %m"); + _exit(EXIT_FAILURE); + } + + for (;;) { + struct libmnt_fs *fs; + + r = mnt_table_next_fs(table, iter, &fs); + if (r == 1) + break; + if (r < 0) { + log_error_errno(r, "Failed to get next entry from /proc/self/mountinfo: %m"); + _exit(EXIT_FAILURE); + } + + log_debug("left after complete umount: %s", mnt_fs_get_target(fs)); + } + + _exit(EXIT_SUCCESS); + } + } +} + +TEST(fd_make_mount_point) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_free_ char *s = NULL; + int r; + + if (geteuid() != 0 || have_effective_cap(CAP_SYS_ADMIN) <= 0) { + (void) log_tests_skipped("not running privileged"); + return; + } + + assert_se(mkdtemp_malloc(NULL, &t) >= 0); + + assert_se(asprintf(&s, "%s/somerandomname%" PRIu64, t, random_u64()) >= 0); + assert_se(s); + assert_se(mkdir(s, 0700) >= 0); + + r = safe_fork("(make_mount-point)", + FORK_RESET_SIGNALS | + FORK_CLOSE_ALL_FDS | + FORK_DEATHSIG_SIGTERM | + FORK_WAIT | + FORK_REOPEN_LOG | + FORK_LOG | + FORK_NEW_MOUNTNS | + FORK_MOUNTNS_SLAVE, + NULL); + assert_se(r >= 0); + + if (r == 0) { + _cleanup_close_ int fd = -EBADF, fd2 = -EBADF; + + fd = open(s, O_PATH|O_CLOEXEC); + assert_se(fd >= 0); + + assert_se(fd_is_mount_point(fd, NULL, AT_SYMLINK_FOLLOW) == 0); + + assert_se(fd_make_mount_point(fd) > 0); + + /* Reopen the inode so that we end up on the new mount */ + fd2 = open(s, O_PATH|O_CLOEXEC); + + assert_se(fd_is_mount_point(fd2, NULL, AT_SYMLINK_FOLLOW) > 0); + + assert_se(fd_make_mount_point(fd2) == 0); + + _exit(EXIT_SUCCESS); + } +} + +TEST(bind_mount_submounts) { + _cleanup_(rmdir_and_freep) char *a = NULL, *b = NULL; + _cleanup_free_ char *x = NULL; + int r; + + assert_se(mkdtemp_malloc(NULL, &a) >= 0); + r = mount_nofollow_verbose(LOG_INFO, "tmpfs", a, "tmpfs", 0, NULL); + if (ERRNO_IS_NEG_PRIVILEGE(r)) + return (void) log_tests_skipped("Skipping bind_mount_submounts() test, lacking privileges"); + + assert_se(r >= 0); + + assert_se(x = path_join(a, "foo")); + assert_se(touch(x) >= 0); + free(x); + + assert_se(x = path_join(a, "x")); + assert_se(mkdir(x, 0755) >= 0); + assert_se(mount_nofollow_verbose(LOG_INFO, "tmpfs", x, "tmpfs", 0, NULL) >= 0); + free(x); + + assert_se(x = path_join(a, "x/xx")); + assert_se(touch(x) >= 0); + free(x); + + assert_se(x = path_join(a, "y")); + assert_se(mkdir(x, 0755) >= 0); + assert_se(mount_nofollow_verbose(LOG_INFO, "tmpfs", x, "tmpfs", 0, NULL) >= 0); + free(x); + + assert_se(x = path_join(a, "y/yy")); + assert_se(touch(x) >= 0); + free(x); + + assert_se(mkdtemp_malloc(NULL, &b) >= 0); + assert_se(mount_nofollow_verbose(LOG_INFO, "tmpfs", b, "tmpfs", 0, NULL) >= 0); + + assert_se(x = path_join(b, "x")); + assert_se(mkdir(x, 0755) >= 0); + free(x); + + assert_se(x = path_join(b, "y")); + assert_se(mkdir(x, 0755) >= 0); + free(x); + + assert_se(bind_mount_submounts(a, b) >= 0); + + assert_se(x = path_join(b, "foo")); + assert_se(access(x, F_OK) < 0 && errno == ENOENT); + free(x); + + assert_se(x = path_join(b, "x/xx")); + assert_se(access(x, F_OK) >= 0); + free(x); + + assert_se(x = path_join(b, "y/yy")); + assert_se(access(x, F_OK) >= 0); + free(x); + + assert_se(x = path_join(b, "x")); + assert_se(path_is_mount_point(x, NULL, 0) > 0); + free(x); + + assert_se(x = path_join(b, "y")); + assert_se(path_is_mount_point(x, NULL, 0) > 0); + + assert_se(umount_recursive(a, 0) >= 0); + assert_se(umount_recursive(b, 0) >= 0); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-mountpoint-util.c b/src/test/test-mountpoint-util.c new file mode 100644 index 0000000..ff447c6 --- /dev/null +++ b/src/test/test-mountpoint-util.c @@ -0,0 +1,434 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <sched.h> +#include <sys/mount.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "constants.h" +#include "fd-util.h" +#include "fileio.h" +#include "hashmap.h" +#include "log.h" +#include "mountpoint-util.h" +#include "path-util.h" +#include "rm-rf.h" +#include "string-util.h" +#include "tests.h" +#include "tmpfile-util.h" + +static void test_mount_propagation_flag_one(const char *name, int ret, unsigned long expected) { + unsigned long flags; + + log_info("/* %s(%s) */", __func__, strnull(name)); + + assert_se(mount_propagation_flag_from_string(name, &flags) == ret); + + if (ret >= 0) { + const char *c; + + assert_se(flags == expected); + + c = mount_propagation_flag_to_string(flags); + if (isempty(name)) + assert_se(isempty(c)); + else + assert_se(streq(c, name)); + } +} + +TEST(mount_propagation_flag) { + test_mount_propagation_flag_one("shared", 0, MS_SHARED); + test_mount_propagation_flag_one("slave", 0, MS_SLAVE); + test_mount_propagation_flag_one("private", 0, MS_PRIVATE); + test_mount_propagation_flag_one(NULL, 0, 0); + test_mount_propagation_flag_one("", 0, 0); + test_mount_propagation_flag_one("xxxx", -EINVAL, 0); + test_mount_propagation_flag_one(" ", -EINVAL, 0); +} + +TEST(mnt_id) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_hashmap_free_free_ Hashmap *h = NULL; + char *p; + void *k; + int r; + + assert_se(f = fopen("/proc/self/mountinfo", "re")); + assert_se(h = hashmap_new(&trivial_hash_ops)); + + for (;;) { + _cleanup_free_ char *line = NULL, *path = NULL; + int mnt_id; + + r = read_line(f, LONG_LINE_MAX, &line); + if (r == 0) + break; + assert_se(r > 0); + + assert_se(sscanf(line, "%i %*s %*s %*s %ms", &mnt_id, &path) == 2); +#if HAS_FEATURE_MEMORY_SANITIZER + /* We don't know the length of the string, so we need to unpoison it one char at a time */ + for (const char *c = path; ;c++) { + msan_unpoison(c, 1); + if (!*c) + break; + } +#endif + log_debug("mountinfo: %s → %i", path, mnt_id); + + assert_se(hashmap_put(h, INT_TO_PTR(mnt_id), path) >= 0); + path = NULL; + } + + HASHMAP_FOREACH_KEY(p, k, h) { + int mnt_id = PTR_TO_INT(k), mnt_id2; + const char *q; + + r = path_get_mnt_id(p, &mnt_id2); + if (r < 0) { + log_debug_errno(r, "Failed to get the mnt id of %s: %m", p); + continue; + } + + if (mnt_id == mnt_id2) { + log_debug("mnt ids of %s is %i.", p, mnt_id); + continue; + } else + log_debug("mnt ids of %s are %i (from /proc/self/mountinfo), %i (from path_get_mnt_id()).", p, mnt_id, mnt_id2); + + /* The ids don't match? This can easily happen e.g. running with "unshare --mount-proc". + * See #11505. */ + assert_se(q = hashmap_get(h, INT_TO_PTR(mnt_id2))); + + assert_se((r = path_is_mount_point(p, NULL, 0)) >= 0); + if (r == 0) { + /* If the path is not a mount point anymore, then it must be a sub directory of + * the path corresponds to mnt_id2. */ + log_debug("The path %s for mnt id %i is not a mount point.", p, mnt_id2); + assert_se(!isempty(path_startswith(p, q))); + } else { + /* If the path is still a mount point, then it must be equivalent to the path + * corresponds to mnt_id2 */ + log_debug("There are multiple mounts on the same path %s.", p); + assert_se(path_equal(p, q)); + } + } +} + +TEST(path_is_mount_point) { + int fd; + char tmp_dir[] = "/tmp/test-path-is-mount-point-XXXXXX"; + _cleanup_free_ char *file1 = NULL, *file2 = NULL, *link1 = NULL, *link2 = NULL; + _cleanup_free_ char *dir1 = NULL, *dir1file = NULL, *dirlink1 = NULL, *dirlink1file = NULL; + _cleanup_free_ char *dir2 = NULL, *dir2file = NULL; + + assert_se(path_is_mount_point("/", NULL, AT_SYMLINK_FOLLOW) > 0); + assert_se(path_is_mount_point("/", NULL, 0) > 0); + assert_se(path_is_mount_point("//", NULL, AT_SYMLINK_FOLLOW) > 0); + assert_se(path_is_mount_point("//", NULL, 0) > 0); + + assert_se(path_is_mount_point("/proc", NULL, AT_SYMLINK_FOLLOW) > 0); + assert_se(path_is_mount_point("/proc", NULL, 0) > 0); + assert_se(path_is_mount_point("/proc/", NULL, AT_SYMLINK_FOLLOW) > 0); + assert_se(path_is_mount_point("/proc/", NULL, 0) > 0); + + assert_se(path_is_mount_point("/proc/1", NULL, AT_SYMLINK_FOLLOW) == 0); + assert_se(path_is_mount_point("/proc/1", NULL, 0) == 0); + assert_se(path_is_mount_point("/proc/1/", NULL, AT_SYMLINK_FOLLOW) == 0); + assert_se(path_is_mount_point("/proc/1/", NULL, 0) == 0); + + assert_se(path_is_mount_point("/sys", NULL, AT_SYMLINK_FOLLOW) > 0); + assert_se(path_is_mount_point("/sys", NULL, 0) > 0); + assert_se(path_is_mount_point("/sys/", NULL, AT_SYMLINK_FOLLOW) > 0); + assert_se(path_is_mount_point("/sys/", NULL, 0) > 0); + + /* we'll create a hierarchy of different kinds of dir/file/link + * layouts: + * + * <tmp>/file1, <tmp>/file2 + * <tmp>/link1 -> file1, <tmp>/link2 -> file2 + * <tmp>/dir1/ + * <tmp>/dir1/file + * <tmp>/dirlink1 -> dir1 + * <tmp>/dirlink1file -> dirlink1/file + * <tmp>/dir2/ + * <tmp>/dir2/file + */ + + /* file mountpoints */ + assert_se(mkdtemp(tmp_dir) != NULL); + file1 = path_join(tmp_dir, "file1"); + assert_se(file1); + file2 = path_join(tmp_dir, "file2"); + assert_se(file2); + fd = open(file1, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0664); + assert_se(fd > 0); + close(fd); + fd = open(file2, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0664); + assert_se(fd > 0); + close(fd); + link1 = path_join(tmp_dir, "link1"); + assert_se(link1); + assert_se(symlink("file1", link1) == 0); + link2 = path_join(tmp_dir, "link2"); + assert_se(link1); + assert_se(symlink("file2", link2) == 0); + + assert_se(path_is_mount_point(file1, NULL, AT_SYMLINK_FOLLOW) == 0); + assert_se(path_is_mount_point(file1, NULL, 0) == 0); + assert_se(path_is_mount_point(link1, NULL, AT_SYMLINK_FOLLOW) == 0); + assert_se(path_is_mount_point(link1, NULL, 0) == 0); + + /* directory mountpoints */ + dir1 = path_join(tmp_dir, "dir1"); + assert_se(dir1); + assert_se(mkdir(dir1, 0755) == 0); + dirlink1 = path_join(tmp_dir, "dirlink1"); + assert_se(dirlink1); + assert_se(symlink("dir1", dirlink1) == 0); + dirlink1file = path_join(tmp_dir, "dirlink1file"); + assert_se(dirlink1file); + assert_se(symlink("dirlink1/file", dirlink1file) == 0); + dir2 = path_join(tmp_dir, "dir2"); + assert_se(dir2); + assert_se(mkdir(dir2, 0755) == 0); + + assert_se(path_is_mount_point(dir1, NULL, AT_SYMLINK_FOLLOW) == 0); + assert_se(path_is_mount_point(dir1, NULL, 0) == 0); + assert_se(path_is_mount_point(dirlink1, NULL, AT_SYMLINK_FOLLOW) == 0); + assert_se(path_is_mount_point(dirlink1, NULL, 0) == 0); + + /* file in subdirectory mountpoints */ + dir1file = path_join(dir1, "file"); + assert_se(dir1file); + fd = open(dir1file, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0664); + assert_se(fd > 0); + close(fd); + + assert_se(path_is_mount_point(dir1file, NULL, AT_SYMLINK_FOLLOW) == 0); + assert_se(path_is_mount_point(dir1file, NULL, 0) == 0); + assert_se(path_is_mount_point(dirlink1file, NULL, AT_SYMLINK_FOLLOW) == 0); + assert_se(path_is_mount_point(dirlink1file, NULL, 0) == 0); + + /* these tests will only work as root */ + if (mount(file1, file2, NULL, MS_BIND, NULL) >= 0) { + int rf, rt, rdf, rdt, rlf, rlt, rl1f, rl1t; + const char *file2d; + + /* files */ + /* capture results in vars, to avoid dangling mounts on failure */ + log_info("%s: %s", __func__, file2); + rf = path_is_mount_point(file2, NULL, 0); + rt = path_is_mount_point(file2, NULL, AT_SYMLINK_FOLLOW); + + file2d = strjoina(file2, "/"); + log_info("%s: %s", __func__, file2d); + rdf = path_is_mount_point(file2d, NULL, 0); + rdt = path_is_mount_point(file2d, NULL, AT_SYMLINK_FOLLOW); + + log_info("%s: %s", __func__, link2); + rlf = path_is_mount_point(link2, NULL, 0); + rlt = path_is_mount_point(link2, NULL, AT_SYMLINK_FOLLOW); + + assert_se(umount(file2) == 0); + + assert_se(rf == 1); + assert_se(rt == 1); + assert_se(rdf == -ENOTDIR); + assert_se(rdt == -ENOTDIR); + assert_se(rlf == 0); + assert_se(rlt == 1); + + /* dirs */ + dir2file = path_join(dir2, "file"); + assert_se(dir2file); + fd = open(dir2file, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0664); + assert_se(fd > 0); + close(fd); + + assert_se(mount(dir2, dir1, NULL, MS_BIND, NULL) >= 0); + + log_info("%s: %s", __func__, dir1); + rf = path_is_mount_point(dir1, NULL, 0); + rt = path_is_mount_point(dir1, NULL, AT_SYMLINK_FOLLOW); + log_info("%s: %s", __func__, dirlink1); + rlf = path_is_mount_point(dirlink1, NULL, 0); + rlt = path_is_mount_point(dirlink1, NULL, AT_SYMLINK_FOLLOW); + log_info("%s: %s", __func__, dirlink1file); + /* its parent is a mount point, but not /file itself */ + rl1f = path_is_mount_point(dirlink1file, NULL, 0); + rl1t = path_is_mount_point(dirlink1file, NULL, AT_SYMLINK_FOLLOW); + + assert_se(umount(dir1) == 0); + + assert_se(rf == 1); + assert_se(rt == 1); + assert_se(rlf == 0); + assert_se(rlt == 1); + assert_se(rl1f == 0); + assert_se(rl1t == 0); + + } else + log_info("Skipping bind mount file test"); + + assert_se(rm_rf(tmp_dir, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); +} + +TEST(fd_is_mount_point) { + _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; + _cleanup_close_ int fd = -EBADF; + int r; + + fd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOCTTY); + assert_se(fd >= 0); + + /* Not allowed, since "/" is a path, not a plain filename */ + assert_se(fd_is_mount_point(fd, "/", 0) == -EINVAL); + assert_se(fd_is_mount_point(fd, ".", 0) == -EINVAL); + assert_se(fd_is_mount_point(fd, "./", 0) == -EINVAL); + assert_se(fd_is_mount_point(fd, "..", 0) == -EINVAL); + assert_se(fd_is_mount_point(fd, "../", 0) == -EINVAL); + assert_se(fd_is_mount_point(fd, "", 0) == -EINVAL); + assert_se(fd_is_mount_point(fd, "/proc", 0) == -EINVAL); + assert_se(fd_is_mount_point(fd, "/proc/", 0) == -EINVAL); + assert_se(fd_is_mount_point(fd, "proc/sys", 0) == -EINVAL); + assert_se(fd_is_mount_point(fd, "proc/sys/", 0) == -EINVAL); + + /* This one definitely is a mount point */ + assert_se(fd_is_mount_point(fd, "proc", 0) > 0); + assert_se(fd_is_mount_point(fd, "proc/", 0) > 0); + + safe_close(fd); + fd = open("/tmp", O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOCTTY); + assert_se(fd >= 0); + + assert_se(mkdtemp_malloc("/tmp/not-mounted-XXXXXX", &tmpdir) >= 0); + assert_se(fd_is_mount_point(fd, basename(tmpdir), 0) == 0); + assert_se(fd_is_mount_point(fd, strjoina(basename(tmpdir), "/"), 0) == 0); + + safe_close(fd); + fd = open("/proc", O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOCTTY); + assert_se(fd >= 0); + + assert_se(fd_is_mount_point(fd, NULL, 0) > 0); + assert_se(fd_is_mount_point(fd, "", 0) == -EINVAL); + assert_se(fd_is_mount_point(fd, "version", 0) == 0); + + safe_close(fd); + fd = open("/proc/version", O_RDONLY|O_CLOEXEC|O_NOCTTY); + assert_se(fd >= 0); + + r = fd_is_mount_point(fd, NULL, 0); + assert_se(IN_SET(r, 0, -ENOTDIR)); /* on old kernels we can't determine if regular files are mount points if we have no directory fd */ + assert_se(fd_is_mount_point(fd, "", 0) == -EINVAL); +} + +TEST(ms_nosymfollow_supported) { + log_info("MS_NOSYMFOLLOW supported: %s", yes_no(ms_nosymfollow_supported())); +} + +TEST(mount_option_supported) { + int r; + + r = mount_option_supported("tmpfs", "size", "64M"); + log_info("tmpfs supports size=64M: %s (%i)", r < 0 ? "don't know" : yes_no(r), r); + assert_se(r > 0 || r == -EAGAIN || (r < 0 && ERRNO_IS_PRIVILEGE(r))); + + r = mount_option_supported("ext4", "discard", NULL); + log_info("ext4 supports discard: %s (%i)", r < 0 ? "don't know" : yes_no(r), r); + assert_se(r > 0 || r == -EAGAIN || (r < 0 && ERRNO_IS_PRIVILEGE(r))); + + r = mount_option_supported("tmpfs", "idontexist", "64M"); + log_info("tmpfs supports idontexist: %s (%i)", r < 0 ? "don't know" : yes_no(r), r); + assert_se(r == 0 || r == -EAGAIN || (r < 0 && ERRNO_IS_PRIVILEGE(r))); + + r = mount_option_supported("tmpfs", "ialsodontexist", NULL); + log_info("tmpfs supports ialsodontexist: %s (%i)", r < 0 ? "don't know" : yes_no(r), r); + assert_se(r == 0 || r == -EAGAIN || (r < 0 && ERRNO_IS_PRIVILEGE(r))); + + r = mount_option_supported("proc", "hidepid", "1"); + log_info("proc supports hidepid=1: %s (%i)", r < 0 ? "don't know" : yes_no(r), r); + assert_se(r >= 0 || r == -EAGAIN || (r < 0 && ERRNO_IS_PRIVILEGE(r))); +} + +TEST(fstype_can_discard) { + assert_se(fstype_can_discard("ext4")); + assert_se(!fstype_can_discard("squashfs")); + assert_se(!fstype_can_discard("iso9660")); +} + +TEST(fstype_can_norecovery) { + assert_se(fstype_can_norecovery("ext4")); + assert_se(!fstype_can_norecovery("vfat")); + assert_se(!fstype_can_norecovery("tmpfs")); +} + +TEST(fstype_can_umask) { + assert_se(fstype_can_umask("vfat")); + assert_se(!fstype_can_umask("tmpfs")); +} + +TEST(path_get_mnt_id_at_null) { + _cleanup_close_ int root_fd = -EBADF, run_fd = -EBADF; + int id1, id2; + + assert_se(path_get_mnt_id_at(AT_FDCWD, "/run/", &id1) >= 0); + assert_se(id1 > 0); + + assert_se(path_get_mnt_id_at(AT_FDCWD, "/run", &id2) >= 0); + assert_se(id1 == id2); + id2 = -1; + + root_fd = open("/", O_DIRECTORY|O_CLOEXEC); + assert_se(root_fd >= 0); + + assert_se(path_get_mnt_id_at(root_fd, "/run/", &id2) >= 0); + assert_se(id1 = id2); + id2 = -1; + + assert_se(path_get_mnt_id_at(root_fd, "/run", &id2) >= 0); + assert_se(id1 = id2); + id2 = -1; + + assert_se(path_get_mnt_id_at(root_fd, "run", &id2) >= 0); + assert_se(id1 = id2); + id2 = -1; + + assert_se(path_get_mnt_id_at(root_fd, "run/", &id2) >= 0); + assert_se(id1 = id2); + id2 = -1; + + run_fd = openat(root_fd, "run", O_DIRECTORY|O_CLOEXEC); + assert_se(run_fd >= 0); + + id2 = -1; + assert_se(path_get_mnt_id_at(run_fd, "", &id2) >= 0); + assert_se(id1 = id2); + id2 = -1; + + assert_se(path_get_mnt_id_at(run_fd, NULL, &id2) >= 0); + assert_se(id1 = id2); + id2 = -1; + + assert_se(path_get_mnt_id_at(run_fd, ".", &id2) >= 0); + assert_se(id1 = id2); + id2 = -1; +} + +static int intro(void) { + /* let's move into our own mount namespace with all propagation from the host turned off, so + * that /proc/self/mountinfo is static and constant for the whole time our test runs. */ + + if (unshare(CLONE_NEWNS) < 0) { + if (!ERRNO_IS_PRIVILEGE(errno)) + return log_error_errno(errno, "Failed to detach mount namespace: %m"); + + log_notice("Lacking privilege to create separate mount namespace, proceeding in originating mount namespace."); + } else + assert_se(mount(NULL, "/", NULL, MS_PRIVATE | MS_REC, NULL) >= 0); + + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/src/test/test-namespace.c b/src/test/test-namespace.c new file mode 100644 index 0000000..65d0825 --- /dev/null +++ b/src/test/test-namespace.c @@ -0,0 +1,199 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <fcntl.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include "alloc-util.h" +#include "fd-util.h" +#include "namespace.h" +#include "process-util.h" +#include "string-util.h" +#include "tests.h" +#include "user-util.h" +#include "virt.h" + +TEST(namespace_cleanup_tmpdir) { + { + _cleanup_(namespace_cleanup_tmpdirp) char *dir; + assert_se(dir = strdup(RUN_SYSTEMD_EMPTY)); + } + + { + _cleanup_(namespace_cleanup_tmpdirp) char *dir; + assert_se(dir = strdup("/tmp/systemd-test-namespace.XXXXXX")); + assert_se(mkdtemp(dir)); + } +} + +static void test_tmpdir_one(const char *id, const char *A, const char *B) { + _cleanup_free_ char *a, *b; + struct stat x, y; + char *c, *d; + + assert_se(setup_tmp_dirs(id, &a, &b) == 0); + + assert_se(stat(a, &x) >= 0); + assert_se(stat(b, &y) >= 0); + + assert_se(S_ISDIR(x.st_mode)); + assert_se(S_ISDIR(y.st_mode)); + + if (!streq(a, RUN_SYSTEMD_EMPTY)) { + assert_se(startswith(a, A)); + assert_se((x.st_mode & 01777) == 0700); + c = strjoina(a, "/tmp"); + assert_se(stat(c, &x) >= 0); + assert_se(S_ISDIR(x.st_mode)); + assert_se(FLAGS_SET(x.st_mode, 01777)); + assert_se(rmdir(c) >= 0); + assert_se(rmdir(a) >= 0); + } + + if (!streq(b, RUN_SYSTEMD_EMPTY)) { + assert_se(startswith(b, B)); + assert_se((y.st_mode & 01777) == 0700); + d = strjoina(b, "/tmp"); + assert_se(stat(d, &y) >= 0); + assert_se(S_ISDIR(y.st_mode)); + assert_se(FLAGS_SET(y.st_mode, 01777)); + assert_se(rmdir(d) >= 0); + assert_se(rmdir(b) >= 0); + } +} + +TEST(tmpdir) { + _cleanup_free_ char *x = NULL, *y = NULL, *z = NULL, *zz = NULL; + sd_id128_t bid; + + assert_se(sd_id128_get_boot(&bid) >= 0); + + x = strjoin("/tmp/systemd-private-", SD_ID128_TO_STRING(bid), "-abcd.service-"); + y = strjoin("/var/tmp/systemd-private-", SD_ID128_TO_STRING(bid), "-abcd.service-"); + assert_se(x && y); + + test_tmpdir_one("abcd.service", x, y); + + z = strjoin("/tmp/systemd-private-", SD_ID128_TO_STRING(bid), "-sys-devices-pci0000:00-0000:00:1a.0-usb3-3\\x2d1-3\\x2d1:1.0-bluetooth-hci0.device-"); + zz = strjoin("/var/tmp/systemd-private-", SD_ID128_TO_STRING(bid), "-sys-devices-pci0000:00-0000:00:1a.0-usb3-3\\x2d1-3\\x2d1:1.0-bluetooth-hci0.device-"); + + assert_se(z && zz); + + test_tmpdir_one("sys-devices-pci0000:00-0000:00:1a.0-usb3-3\\x2d1-3\\x2d1:1.0-bluetooth-hci0.device", z, zz); +} + +static void test_shareable_ns(unsigned long nsflag) { + _cleanup_close_pair_ int s[2] = EBADF_PAIR; + pid_t pid1, pid2, pid3; + int r, n = 0; + siginfo_t si; + + if (geteuid() > 0) { + (void) log_tests_skipped("not root"); + return; + } + + assert_se(socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, s) >= 0); + + pid1 = fork(); + assert_se(pid1 >= 0); + + if (pid1 == 0) { + r = setup_shareable_ns(s, nsflag); + assert_se(r >= 0); + _exit(r); + } + + pid2 = fork(); + assert_se(pid2 >= 0); + + if (pid2 == 0) { + r = setup_shareable_ns(s, nsflag); + assert_se(r >= 0); + exit(r); + } + + pid3 = fork(); + assert_se(pid3 >= 0); + + if (pid3 == 0) { + r = setup_shareable_ns(s, nsflag); + assert_se(r >= 0); + exit(r); + } + + r = wait_for_terminate(pid1, &si); + assert_se(r >= 0); + assert_se(si.si_code == CLD_EXITED); + n += si.si_status; + + r = wait_for_terminate(pid2, &si); + assert_se(r >= 0); + assert_se(si.si_code == CLD_EXITED); + n += si.si_status; + + r = wait_for_terminate(pid3, &si); + assert_se(r >= 0); + assert_se(si.si_code == CLD_EXITED); + n += si.si_status; + + assert_se(n == 1); +} + +TEST(netns) { + test_shareable_ns(CLONE_NEWNET); +} + +TEST(ipcns) { + test_shareable_ns(CLONE_NEWIPC); +} + +TEST(protect_kernel_logs) { + static const NamespaceParameters p = { + .runtime_scope = RUNTIME_SCOPE_SYSTEM, + .protect_kernel_logs = true, + }; + pid_t pid; + int r; + + if (geteuid() > 0) { + (void) log_tests_skipped("not root"); + return; + } + + /* In a container we likely don't have access to /dev/kmsg */ + if (detect_container() > 0) { + (void) log_tests_skipped("in container"); + return; + } + + pid = fork(); + assert_se(pid >= 0); + + if (pid == 0) { + _cleanup_close_ int fd = -EBADF; + + fd = open("/dev/kmsg", O_RDONLY | O_CLOEXEC); + assert_se(fd > 0); + + r = setup_namespace(&p, NULL); + assert_se(r == 0); + + assert_se(setresuid(UID_NOBODY, UID_NOBODY, UID_NOBODY) >= 0); + assert_se(open("/dev/kmsg", O_RDONLY | O_CLOEXEC) < 0); + assert_se(errno == EACCES); + + _exit(EXIT_SUCCESS); + } + + assert_se(wait_for_terminate_and_check("ns-kernellogs", pid, WAIT_LOG) == EXIT_SUCCESS); +} + +static int intro(void) { + if (!have_namespaces()) + return log_tests_skipped("Don't have namespace support"); + + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/test/test-net-naming-scheme.c b/src/test/test-net-naming-scheme.c new file mode 100644 index 0000000..f7ec5a6 --- /dev/null +++ b/src/test/test-net-naming-scheme.c @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "netif-naming-scheme.h" +#include "string-util.h" +#include "tests.h" + +#ifdef _DEFAULT_NET_NAMING_SCHEME +/* The primary purpose of this check is to verify that _DEFAULT_NET_NAMING_SCHEME_TEST + * is a valid identifier. If an invalid name is given during configuration, this will + * fail with a name error. */ +assert_cc(_DEFAULT_NET_NAMING_SCHEME >= 0); +#endif + +TEST(default_net_naming_scheme) { + const NamingScheme *n; + assert_se(n = naming_scheme_from_name(DEFAULT_NET_NAMING_SCHEME)); + log_info("default → %s", n->name); + + assert_se(naming_scheme_from_name(n->name) == n); +} + +TEST(naming_scheme_conversions) { + const NamingScheme *n; + assert_se(n = naming_scheme_from_name("latest")); + log_info("latest → %s", n->name); + + assert_se(n = naming_scheme_from_name("v238")); + assert_se(streq(n->name, "v238")); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-netlink-manual.c b/src/test/test-netlink-manual.c new file mode 100644 index 0000000..6543c61 --- /dev/null +++ b/src/test/test-netlink-manual.c @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <arpa/inet.h> +#include <linux/if_tunnel.h> +#include <linux/ip.h> +#include <sys/types.h> +#include <unistd.h> + +#include "sd-netlink.h" + +#include "macro.h" +#include "module-util.h" +#include "tests.h" + +static int load_module(const char *mod_name) { + _cleanup_(kmod_unrefp) struct kmod_ctx *ctx = NULL; + _cleanup_(kmod_module_unref_listp) struct kmod_list *list = NULL; + struct kmod_list *l; + int r; + + ctx = kmod_new(NULL, NULL); + if (!ctx) + return log_oom(); + + r = kmod_module_new_from_lookup(ctx, mod_name, &list); + if (r < 0) + return r; + + kmod_list_foreach(l, list) { + _cleanup_(kmod_module_unrefp) struct kmod_module *mod = NULL; + + mod = kmod_module_get_module(l); + + r = kmod_module_probe_insert_module(mod, 0, NULL, NULL, NULL, NULL); + if (r > 0) + r = -EINVAL; + } + + return r; +} + +static int test_tunnel_configure(sd_netlink *rtnl) { + int r; + sd_netlink_message *m, *n; + struct in_addr local, remote; + + /* skip test if module cannot be loaded */ + r = load_module("ipip"); + if (r < 0) + return log_tests_skipped_errno(r, "failed to load module 'ipip'"); + + r = load_module("sit"); + if (r < 0) + return log_tests_skipped_errno(r, "failed to load module 'sit'"); + + if (getuid() != 0) + return log_tests_skipped("not root"); + + /* IPIP tunnel */ + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0) >= 0); + assert_se(m); + + assert_se(sd_netlink_message_append_string(m, IFLA_IFNAME, "ipip-tunnel") >= 0); + assert_se(sd_netlink_message_append_u32(m, IFLA_MTU, 1234)>= 0); + + assert_se(sd_netlink_message_open_container(m, IFLA_LINKINFO) >= 0); + + assert_se(sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "ipip") >= 0); + + inet_pton(AF_INET, "192.168.21.1", &local.s_addr); + assert_se(sd_netlink_message_append_u32(m, IFLA_IPTUN_LOCAL, local.s_addr) >= 0); + + inet_pton(AF_INET, "192.168.21.2", &remote.s_addr); + assert_se(sd_netlink_message_append_u32(m, IFLA_IPTUN_REMOTE, remote.s_addr) >= 0); + + assert_se(sd_netlink_message_close_container(m) >= 0); + assert_se(sd_netlink_message_close_container(m) >= 0); + + assert_se(sd_netlink_call(rtnl, m, -1, 0) == 1); + + assert_se((m = sd_netlink_message_unref(m)) == NULL); + + /* sit */ + assert_se(sd_rtnl_message_new_link(rtnl, &n, RTM_NEWLINK, 0) >= 0); + assert_se(n); + + assert_se(sd_netlink_message_append_string(n, IFLA_IFNAME, "sit-tunnel") >= 0); + assert_se(sd_netlink_message_append_u32(n, IFLA_MTU, 1234)>= 0); + + assert_se(sd_netlink_message_open_container(n, IFLA_LINKINFO) >= 0); + + assert_se(sd_netlink_message_open_container_union(n, IFLA_INFO_DATA, "sit") >= 0); + + assert_se(sd_netlink_message_append_u8(n, IFLA_IPTUN_PROTO, IPPROTO_IPIP) >= 0); + + inet_pton(AF_INET, "192.168.21.3", &local.s_addr); + assert_se(sd_netlink_message_append_u32(n, IFLA_IPTUN_LOCAL, local.s_addr) >= 0); + + inet_pton(AF_INET, "192.168.21.4", &remote.s_addr); + assert_se(sd_netlink_message_append_u32(n, IFLA_IPTUN_REMOTE, remote.s_addr) >= 0); + + assert_se(sd_netlink_message_close_container(n) >= 0); + assert_se(sd_netlink_message_close_container(n) >= 0); + + assert_se(sd_netlink_call(rtnl, n, -1, 0) == 1); + + assert_se((n = sd_netlink_message_unref(n)) == NULL); + + return EXIT_SUCCESS; +} + +int main(int argc, char *argv[]) { + sd_netlink *rtnl; + int r; + + test_setup_logging(LOG_INFO); + + assert_se(sd_netlink_open(&rtnl) >= 0); + assert_se(rtnl); + + r = test_tunnel_configure(rtnl); + + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); + + return r; +} diff --git a/src/test/test-nft-set.c b/src/test/test-nft-set.c new file mode 100644 index 0000000..bb0c902 --- /dev/null +++ b/src/test/test-nft-set.c @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <assert.h> +#include <unistd.h> + +#include "firewall-util.h" +#include "in-addr-util.h" +#include "log.h" +#include "netlink-internal.h" +#include "parse-util.h" +#include "string-util.h" +#include "tests.h" + +int main(int argc, char **argv) { + int r; + + assert_se(argc == 7); + + test_setup_logging(LOG_DEBUG); + + if (getuid() != 0) + return log_tests_skipped("not root"); + + int nfproto; + nfproto = nfproto_from_string(argv[2]); + assert_se(nfproto_is_valid(nfproto)); + + const char *table = argv[3], *set = argv[4]; + + FirewallContext *ctx; + r = fw_ctx_new(&ctx); + assert_se(r == 0); + + bool add; + if (streq(argv[1], "add")) + add = true; + else + add = false; + + if (streq(argv[5], "uint32")) { + uint32_t element; + + r = safe_atou32(argv[6], &element); + assert_se(r == 0); + + r = nft_set_element_modify_any(ctx, add, nfproto, table, set, &element, sizeof(element)); + assert_se(r == 0); + } else if (streq(argv[5], "uint64")) { + uint64_t element; + + r = safe_atou64(argv[6], &element); + assert_se(r == 0); + + r = nft_set_element_modify_any(ctx, add, nfproto, table, set, &element, sizeof(element)); + assert_se(r == 0); + } else if (streq(argv[5], "in_addr")) { + union in_addr_union addr; + int af; + + r = in_addr_from_string_auto(argv[6], &af, &addr); + assert_se(r == 0); + + r = nft_set_element_modify_ip(ctx, add, nfproto, af, table, set, &addr); + assert_se(r == 0); + } else if (streq(argv[5], "network")) { + union in_addr_union addr; + int af; + unsigned char prefixlen; + + r = in_addr_prefix_from_string_auto(argv[6], &af, &addr, &prefixlen); + assert_se(r == 0); + + r = nft_set_element_modify_iprange(ctx, add, nfproto, af, table, set, &addr, prefixlen); + assert_se(r == 0); + } + + return 0; +} diff --git a/src/test/test-ns.c b/src/test/test-ns.c new file mode 100644 index 0000000..97b9fc9 --- /dev/null +++ b/src/test/test-ns.c @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> + +#include "log.h" +#include "namespace.h" +#include "tests.h" + +int main(int argc, char *argv[]) { + const char * const writable[] = { + "/home", + "-/home/lennart/projects/foobar", /* this should be masked automatically */ + NULL + }; + + const char * const readonly[] = { + /* "/", */ + /* "/usr", */ + "/boot", + "/lib", + "/usr/lib", + "-/lib64", + "-/usr/lib64", + NULL + }; + + const char * const exec[] = { + "/lib", + "/usr", + "-/lib64", + "-/usr/lib64", + NULL + }; + + const char * const no_exec[] = { + "/var", + NULL + }; + + const char *inaccessible[] = { + "/home/lennart/projects", + NULL + }; + + static const BindMount bind_mount = { + .source = (char*) "/usr/bin", + .destination = (char*) "/etc/systemd", + .read_only = true, + }; + + static const TemporaryFileSystem tmpfs = { + .path = (char*) "/var", + .options = (char*) "ro", + }; + + char *root_directory; + char *projects_directory; + int r; + char tmp_dir[] = "/tmp/systemd-private-XXXXXX", + var_tmp_dir[] = "/var/tmp/systemd-private-XXXXXX"; + + test_setup_logging(LOG_DEBUG); + + assert_se(mkdtemp(tmp_dir)); + assert_se(mkdtemp(var_tmp_dir)); + + root_directory = getenv("TEST_NS_CHROOT"); + projects_directory = getenv("TEST_NS_PROJECTS"); + + if (projects_directory) + inaccessible[0] = projects_directory; + + log_info("Inaccessible directory: '%s'", inaccessible[0]); + if (root_directory) + log_info("Chroot: '%s'", root_directory); + else + log_info("Not chrooted"); + + NamespaceParameters p = { + .runtime_scope = RUNTIME_SCOPE_SYSTEM, + + .root_directory = root_directory, + + .read_write_paths = (char**) writable, + .read_only_paths = (char**) readonly, + .inaccessible_paths = (char**) inaccessible, + + .exec_paths = (char**) exec, + .no_exec_paths = (char**) no_exec, + + .tmp_dir = tmp_dir, + .var_tmp_dir = var_tmp_dir, + + .bind_mounts = &bind_mount, + .n_bind_mounts = 1, + + .temporary_filesystems = &tmpfs, + .n_temporary_filesystems = 1, + + .private_dev = true, + .protect_control_groups = true, + .protect_kernel_tunables = true, + .protect_kernel_modules = true, + .protect_proc = PROTECT_PROC_NOACCESS, + .proc_subset = PROC_SUBSET_PID, + }; + + r = setup_namespace(&p, NULL); + if (r < 0) { + log_error_errno(r, "Failed to set up namespace: %m"); + + log_info("Usage:\n" + " sudo TEST_NS_PROJECTS=/home/lennart/projects ./test-ns\n" + " sudo TEST_NS_CHROOT=/home/alban/debian-tree TEST_NS_PROJECTS=/home/alban/debian-tree/home/alban/Documents ./test-ns"); + + return 1; + } + + execl("/bin/sh", "/bin/sh", NULL); + log_error_errno(errno, "execl(): %m"); + + return 1; +} diff --git a/src/test/test-nscd-flush.c b/src/test/test-nscd-flush.c new file mode 100644 index 0000000..1a5a808 --- /dev/null +++ b/src/test/test-nscd-flush.c @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "main-func.h" +#include "nscd-flush.h" +#include "strv.h" +#include "tests.h" + +static int run(int argc, char *argv[]) { + int r; + + test_setup_logging(LOG_DEBUG); + + r = nscd_flush_cache(STRV_MAKE("group", "passwd", "hosts")); + if (r < 0) + return log_error_errno(r, "Failed to flush NSCD cache"); + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/test/test-nss-hosts.c b/src/test/test-nss-hosts.c new file mode 100644 index 0000000..72a9c64 --- /dev/null +++ b/src/test/test-nss-hosts.c @@ -0,0 +1,495 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <net/if.h> +#include <stdlib.h> +#include <unistd.h> + +#include "af-list.h" +#include "alloc-util.h" +#include "dlfcn-util.h" +#include "env-util.h" +#include "errno-list.h" +#include "format-util.h" +#include "hexdecoct.h" +#include "hostname-util.h" +#include "in-addr-util.h" +#include "local-addresses.h" +#include "log.h" +#include "main-func.h" +#include "nss-test-util.h" +#include "nss-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "socket-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" + +static size_t arg_bufsize = 1024; + +static const char* af_to_string(int family, char *buf, size_t buf_len) { + const char *name; + + if (family == AF_UNSPEC) + return "*"; + + name = af_to_name(family); + if (name) + return name; + + (void) snprintf(buf, buf_len, "%i", family); + return buf; +} + +static int print_gaih_addrtuples(const struct gaih_addrtuple *tuples) { + int r, n = 0; + + for (const struct gaih_addrtuple *it = tuples; it; it = it->next) { + _cleanup_free_ char *a = NULL; + union in_addr_union u; + char family_name[DECIMAL_STR_MAX(int)]; + + memcpy(&u, it->addr, 16); + r = in_addr_to_string(it->family, &u, &a); + assert_se(IN_SET(r, 0, -EAFNOSUPPORT)); + if (r == -EAFNOSUPPORT) + assert_se(a = hexmem(it->addr, 16)); + + log_info(" \"%s\" %s %s %s", + it->name, + af_to_string(it->family, family_name, sizeof family_name), + a, + FORMAT_IFNAME_FULL(it->scopeid, FORMAT_IFNAME_IFINDEX_WITH_PERCENT)); + + n++; + } + return n; +} + +static void print_struct_hostent(struct hostent *host, const char *canon) { + log_info(" \"%s\"", host->h_name); + STRV_FOREACH(s, host->h_aliases) + log_info(" alias \"%s\"", *s); + STRV_FOREACH(s, host->h_addr_list) { + union in_addr_union u; + _cleanup_free_ char *a = NULL; + char family_name[DECIMAL_STR_MAX(int)]; + int r; + + assert_se((unsigned) host->h_length == FAMILY_ADDRESS_SIZE(host->h_addrtype)); + memcpy(&u, *s, host->h_length); + r = in_addr_to_string(host->h_addrtype, &u, &a); + assert_se(r == 0); + log_info(" %s %s", + af_to_string(host->h_addrtype, family_name, sizeof family_name), + a); + } + if (canon) + log_info(" canonical: \"%s\"", canon); +} + +static void test_gethostbyname4_r(void *handle, const char *module, const char *name) { + const char *fname; + _nss_gethostbyname4_r_t f; + char buffer[arg_bufsize]; + struct gaih_addrtuple *pat = NULL; + int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */ + int32_t ttl = INT32_MAX; /* nss-dns wants to return the lowest ttl, + and will access this variable through *ttlp, + so we need to set it to something. + I'm not sure if this is a bug in nss-dns + or not. */ + enum nss_status status; + char pretty_status[DECIMAL_STR_MAX(enum nss_status)]; + int n; + + fname = strjoina("_nss_", module, "_gethostbyname4_r"); + f = dlsym(handle, fname); + log_debug("dlsym(0x%p, %s) → 0x%p", handle, fname, f); + if (!f) { + log_info("%s not defined", fname); + return; + } + + status = f(name, &pat, buffer, sizeof buffer, &errno1, &errno2, &ttl); + if (status == NSS_STATUS_SUCCESS) { + log_info("%s(\"%s\") → status=%s%-20spat=buffer+0x%"PRIxPTR" errno=%d/%s h_errno=%d/%s ttl=%"PRIi32, + fname, name, + nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n", + pat ? (uintptr_t) pat - (uintptr_t) buffer : 0, + errno1, errno_to_name(errno1) ?: "---", + errno2, hstrerror(errno2), + ttl); + n = print_gaih_addrtuples(pat); + } else { + log_info("%s(\"%s\") → status=%s%-20spat=0x%p errno=%d/%s h_errno=%d/%s", + fname, name, + nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n", + pat, + errno1, errno_to_name(errno1) ?: "---", + errno2, hstrerror(errno2)); + n = 0; + } + + if (STR_IN_SET(module, "resolve", "mymachines") && status == NSS_STATUS_UNAVAIL) + return; + + if (streq(name, "localhost")) { + if (streq(module, "myhostname")) { + assert_se(status == NSS_STATUS_SUCCESS); + assert_se(n == socket_ipv6_is_enabled() + 1); + + } else if (streq(module, "resolve") && getenv_bool_secure("SYSTEMD_NSS_RESOLVE_SYNTHESIZE") != 0) { + assert_se(status == NSS_STATUS_SUCCESS); + if (socket_ipv6_is_enabled()) + assert_se(n == 2); + else + assert_se(n <= 2); /* Even if IPv6 is disabled, /etc/hosts may contain ::1. */ + } + } +} + +static void test_gethostbyname3_r(void *handle, const char *module, const char *name, int af) { + const char *fname; + _nss_gethostbyname3_r_t f; + char buffer[arg_bufsize]; + int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */ + int32_t ttl = INT32_MAX; /* nss-dns wants to return the lowest ttl, + and will access this variable through *ttlp, + so we need to set it to something. + I'm not sure if this is a bug in nss-dns + or not. */ + enum nss_status status; + char pretty_status[DECIMAL_STR_MAX(enum nss_status)]; + struct hostent host; + char *canon; + char family_name[DECIMAL_STR_MAX(int)]; + + fname = strjoina("_nss_", module, "_gethostbyname3_r"); + f = dlsym(handle, fname); + log_debug("dlsym(0x%p, %s) → 0x%p", handle, fname, f); + if (!f) { + log_info("%s not defined", fname); + return; + } + + status = f(name, af, &host, buffer, sizeof buffer, &errno1, &errno2, &ttl, &canon); + log_info("%s(\"%s\", %s) → status=%s%-20serrno=%d/%s h_errno=%d/%s ttl=%"PRIi32, + fname, name, af_to_string(af, family_name, sizeof family_name), + nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n", + errno1, errno_to_name(errno1) ?: "---", + errno2, hstrerror(errno2), + ttl); + if (status == NSS_STATUS_SUCCESS) + print_struct_hostent(&host, canon); +} + +static void test_gethostbyname2_r(void *handle, const char *module, const char *name, int af) { + const char *fname; + _nss_gethostbyname2_r_t f; + char buffer[arg_bufsize]; + int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */ + enum nss_status status; + char pretty_status[DECIMAL_STR_MAX(enum nss_status)]; + struct hostent host; + char family_name[DECIMAL_STR_MAX(int)]; + + fname = strjoina("_nss_", module, "_gethostbyname2_r"); + f = dlsym(handle, fname); + log_debug("dlsym(0x%p, %s) → 0x%p", handle, fname, f); + if (!f) { + log_info("%s not defined", fname); + return; + } + + status = f(name, af, &host, buffer, sizeof buffer, &errno1, &errno2); + log_info("%s(\"%s\", %s) → status=%s%-20serrno=%d/%s h_errno=%d/%s", + fname, name, af_to_string(af, family_name, sizeof family_name), + nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n", + errno1, errno_to_name(errno1) ?: "---", + errno2, hstrerror(errno2)); + if (status == NSS_STATUS_SUCCESS) + print_struct_hostent(&host, NULL); +} + +static void test_gethostbyname_r(void *handle, const char *module, const char *name) { + const char *fname; + _nss_gethostbyname_r_t f; + char buffer[arg_bufsize]; + int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */ + enum nss_status status; + char pretty_status[DECIMAL_STR_MAX(enum nss_status)]; + struct hostent host; + + fname = strjoina("_nss_", module, "_gethostbyname_r"); + f = dlsym(handle, fname); + log_debug("dlsym(0x%p, %s) → 0x%p", handle, fname, f); + if (!f) { + log_info("%s not defined", fname); + return; + } + + status = f(name, &host, buffer, sizeof buffer, &errno1, &errno2); + log_info("%s(\"%s\") → status=%s%-20serrno=%d/%s h_errno=%d/%s", + fname, name, + nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n", + errno1, errno_to_name(errno1) ?: "---", + errno2, hstrerror(errno2)); + if (status == NSS_STATUS_SUCCESS) + print_struct_hostent(&host, NULL); +} + +static void test_gethostbyaddr2_r(void *handle, + const char *module, + const void* addr, socklen_t len, + int af) { + + const char *fname; + _nss_gethostbyaddr2_r_t f; + char buffer[arg_bufsize]; + int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */ + enum nss_status status; + char pretty_status[DECIMAL_STR_MAX(enum nss_status)]; + struct hostent host; + int32_t ttl = INT32_MAX; + _cleanup_free_ char *addr_pretty = NULL; + + fname = strjoina("_nss_", module, "_gethostbyaddr2_r"); + f = dlsym(handle, fname); + + log_full_errno(f ? LOG_DEBUG : LOG_INFO, errno, + "dlsym(0x%p, %s) → 0x%p: %m", handle, fname, f); + if (!f) { + log_info("%s not defined", fname); + return; + } + + assert_se(in_addr_to_string(af, addr, &addr_pretty) >= 0); + + status = f(addr, len, af, &host, buffer, sizeof buffer, &errno1, &errno2, &ttl); + log_info("%s(\"%s\") → status=%s%-20serrno=%d/%s h_errno=%d/%s ttl=%"PRIi32, + fname, addr_pretty, + nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n", + errno1, errno_to_name(errno1) ?: "---", + errno2, hstrerror(errno2), + ttl); + if (status == NSS_STATUS_SUCCESS) + print_struct_hostent(&host, NULL); +} + +static void test_gethostbyaddr_r(void *handle, + const char *module, + const void* addr, socklen_t len, + int af) { + + const char *fname; + _nss_gethostbyaddr_r_t f; + char buffer[arg_bufsize]; + int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */ + enum nss_status status; + char pretty_status[DECIMAL_STR_MAX(enum nss_status)]; + struct hostent host; + _cleanup_free_ char *addr_pretty = NULL; + + fname = strjoina("_nss_", module, "_gethostbyaddr_r"); + f = dlsym(handle, fname); + + log_full_errno(f ? LOG_DEBUG : LOG_INFO, errno, + "dlsym(0x%p, %s) → 0x%p: %m", handle, fname, f); + if (!f) { + log_info("%s not defined", fname); + return; + } + + assert_se(in_addr_to_string(af, addr, &addr_pretty) >= 0); + + status = f(addr, len, af, &host, buffer, sizeof buffer, &errno1, &errno2); + log_info("%s(\"%s\") → status=%s%-20serrno=%d/%s h_errno=%d/%s", + fname, addr_pretty, + nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n", + errno1, errno_to_name(errno1) ?: "---", + errno2, hstrerror(errno2)); + if (status == NSS_STATUS_SUCCESS) + print_struct_hostent(&host, NULL); +} + +static void test_byname(void *handle, const char *module, const char *name) { + test_gethostbyname4_r(handle, module, name); + puts(""); + + test_gethostbyname3_r(handle, module, name, AF_INET); + puts(""); + test_gethostbyname3_r(handle, module, name, AF_INET6); + puts(""); + test_gethostbyname3_r(handle, module, name, AF_UNSPEC); + puts(""); + test_gethostbyname3_r(handle, module, name, AF_UNIX); + puts(""); + + test_gethostbyname2_r(handle, module, name, AF_INET); + puts(""); + test_gethostbyname2_r(handle, module, name, AF_INET6); + puts(""); + test_gethostbyname2_r(handle, module, name, AF_UNSPEC); + puts(""); + test_gethostbyname2_r(handle, module, name, AF_UNIX); + puts(""); + + test_gethostbyname_r(handle, module, name); + puts(""); +} + +static void test_byaddr(void *handle, + const char *module, + const void* addr, socklen_t len, + int af) { + test_gethostbyaddr2_r(handle, module, addr, len, af); + puts(""); + + test_gethostbyaddr_r(handle, module, addr, len, af); + puts(""); +} + +static int make_addresses(struct local_address **addresses) { + int n; + _cleanup_free_ struct local_address *addrs = NULL; + + n = local_addresses(NULL, 0, AF_UNSPEC, &addrs); + if (n < 0) + log_info_errno(n, "Failed to query local addresses: %m"); + + assert_se(GREEDY_REALLOC(addrs, n + 3)); + + addrs[n++] = (struct local_address) { .family = AF_INET, + .address.in = { htobe32(0x7F000001) } }; + addrs[n++] = (struct local_address) { .family = AF_INET, + .address.in = { htobe32(0x7F000002) } }; + addrs[n++] = (struct local_address) { .family = AF_INET6, + .address.in6 = in6addr_loopback }; + + *addresses = TAKE_PTR(addrs); + return n; +} + +static int test_one_module(const char *dir, + const char *module, + char **names, + struct local_address *addresses, + int n_addresses) { + + log_info("======== %s ========", module); + + _cleanup_(dlclosep) void *handle = nss_open_handle(dir, module, RTLD_LAZY|RTLD_NODELETE); + if (!handle) + return -EINVAL; + + STRV_FOREACH(name, names) + test_byname(handle, module, *name); + + for (int i = 0; i < n_addresses; i++) + test_byaddr(handle, module, + &addresses[i].address, + FAMILY_ADDRESS_SIZE(addresses[i].family), + addresses[i].family); + + log_info(" "); + return 0; +} + +static int parse_argv(int argc, char **argv, + char ***the_modules, + char ***the_names, + struct local_address **the_addresses, int *n_addresses) { + + _cleanup_strv_free_ char **modules = NULL, **names = NULL; + _cleanup_free_ struct local_address *addrs = NULL; + const char *p; + int r, n = 0; + + p = getenv("SYSTEMD_TEST_NSS_BUFSIZE"); + if (p) { + r = safe_atozu(p, &arg_bufsize); + if (r < 0) + return log_error_errno(r, "Failed to parse $SYSTEMD_TEST_NSS_BUFSIZE"); + } + + if (argc > 1) + modules = strv_new(argv[1]); + else + modules = strv_new( +#if ENABLE_NSS_MYHOSTNAME + "myhostname", +#endif +#if ENABLE_NSS_RESOLVE + "resolve", +#endif +#if ENABLE_NSS_MYMACHINES + "mymachines", +#endif + NULL); + assert_se(modules); + + if (argc > 2) { + int family; + union in_addr_union address; + + STRV_FOREACH(name, argv + 2) { + r = in_addr_from_string_auto(*name, &family, &address); + if (r < 0) { + /* assume this is a name */ + r = strv_extend(&names, *name); + if (r < 0) + return r; + } else { + assert_se(GREEDY_REALLOC0(addrs, n + 1)); + + addrs[n++] = (struct local_address) { .family = family, + .address = address }; + } + } + } else { + _cleanup_free_ char *hostname = NULL; + assert_se(hostname = gethostname_malloc()); + assert_se(names = strv_new("localhost", + "_gateway", + "_outbound", + hostname, + slow_tests_enabled() ? "foo_no_such_host" : NULL)); + + n = make_addresses(&addrs); + assert_se(n >= 0); + } + + *the_modules = TAKE_PTR(modules); + *the_names = TAKE_PTR(names); + *the_addresses = TAKE_PTR(addrs); + *n_addresses = n; + return 0; +} + +static int run(int argc, char **argv) { + _cleanup_free_ char *dir = NULL; + _cleanup_strv_free_ char **modules = NULL, **names = NULL; + _cleanup_free_ struct local_address *addresses = NULL; + int n_addresses = 0; + int r; + + test_setup_logging(LOG_INFO); + + r = parse_argv(argc, argv, &modules, &names, &addresses, &n_addresses); + if (r < 0) + return log_error_errno(r, "Failed to parse arguments: %m"); + + assert_se(path_extract_directory(argv[0], &dir) >= 0); + + STRV_FOREACH(module, modules) { + r = test_one_module(dir, *module, names, addresses, n_addresses); + if (r < 0) + return r; + } + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/test/test-nss-users.c b/src/test/test-nss-users.c new file mode 100644 index 0000000..5178779 --- /dev/null +++ b/src/test/test-nss-users.c @@ -0,0 +1,256 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <pwd.h> +#include <stdlib.h> +#include <sys/types.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "dlfcn-util.h" +#include "errno-list.h" +#include "format-util.h" +#include "log.h" +#include "main-func.h" +#include "nss-test-util.h" +#include "nss-util.h" +#include "path-util.h" +#include "parse-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" +#include "user-util.h" + +static size_t arg_bufsize = 1024; + +static void print_struct_passwd(const struct passwd *pwd) { + log_info(" \"%s\" / "UID_FMT":"GID_FMT, + pwd->pw_name, pwd->pw_uid, pwd->pw_gid); + log_info(" passwd=\"%s\"", pwd->pw_passwd); + log_info(" gecos=\"%s\"", pwd->pw_gecos); + log_info(" dir=\"%s\"", pwd->pw_dir); + log_info(" shell=\"%s\"", pwd->pw_shell); +} + +static void print_struct_group(const struct group *gr) { + _cleanup_free_ char *members = NULL; + + log_info(" \"%s\" / "GID_FMT, + gr->gr_name, gr->gr_gid); + log_info(" passwd=\"%s\"", gr->gr_passwd); + + assert_se(members = strv_join(gr->gr_mem, ", ")); + // FIXME: use shell_maybe_quote(SHELL_ESCAPE_EMPTY) when it becomes available + log_info(" members=%s", members); +} + +static void test_getpwnam_r(void *handle, const char *module, const char *name) { + const char *fname; + _nss_getpwnam_r_t f; + char buffer[arg_bufsize]; + int errno1 = 999; /* nss-dns doesn't set those */ + enum nss_status status; + char pretty_status[DECIMAL_STR_MAX(enum nss_status)]; + struct passwd pwd; + + fname = strjoina("_nss_", module, "_getpwnam_r"); + f = dlsym(handle, fname); + log_debug("dlsym(0x%p, %s) → 0x%p", handle, fname, f); + if (!f) { + log_info("%s not defined", fname); + return; + } + + status = f(name, &pwd, buffer, sizeof buffer, &errno1); + log_info("%s(\"%s\") → status=%s%-20serrno=%d/%s", + fname, name, + nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n", + errno1, errno_to_name(errno1) ?: "---"); + if (status == NSS_STATUS_SUCCESS) + print_struct_passwd(&pwd); +} + +static void test_getgrnam_r(void *handle, const char *module, const char *name) { + const char *fname; + _nss_getgrnam_r_t f; + char buffer[arg_bufsize]; + int errno1 = 999; /* nss-dns doesn't set those */ + enum nss_status status; + char pretty_status[DECIMAL_STR_MAX(enum nss_status)]; + struct group gr; + + fname = strjoina("_nss_", module, "_getgrnam_r"); + f = dlsym(handle, fname); + log_debug("dlsym(0x%p, %s) → 0x%p", handle, fname, f); + if (!f) { + log_info("%s not defined", fname); + return; + } + + status = f(name, &gr, buffer, sizeof buffer, &errno1); + log_info("%s(\"%s\") → status=%s%-20serrno=%d/%s", + fname, name, + nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n", + errno1, errno_to_name(errno1) ?: "---"); + if (status == NSS_STATUS_SUCCESS) + print_struct_group(&gr); +} + +static void test_getpwuid_r(void *handle, const char *module, uid_t uid) { + const char *fname; + _nss_getpwuid_r_t f; + char buffer[arg_bufsize]; + int errno1 = 999; /* nss-dns doesn't set those */ + enum nss_status status; + char pretty_status[DECIMAL_STR_MAX(enum nss_status)]; + struct passwd pwd; + + fname = strjoina("_nss_", module, "_getpwuid_r"); + f = dlsym(handle, fname); + log_debug("dlsym(0x%p, %s) → 0x%p", handle, fname, f); + if (!f) { + log_info("%s not defined", fname); + return; + } + + status = f(uid, &pwd, buffer, sizeof buffer, &errno1); + log_info("%s("UID_FMT") → status=%s%-20serrno=%d/%s", + fname, uid, + nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n", + errno1, errno_to_name(errno1) ?: "---"); + if (status == NSS_STATUS_SUCCESS) + print_struct_passwd(&pwd); +} + +static void test_getgrgid_r(void *handle, const char *module, gid_t gid) { + const char *fname; + _nss_getgrgid_r_t f; + char buffer[arg_bufsize]; + int errno1 = 999; /* nss-dns doesn't set those */ + enum nss_status status; + char pretty_status[DECIMAL_STR_MAX(enum nss_status)]; + struct group gr; + + fname = strjoina("_nss_", module, "_getgrgid_r"); + f = dlsym(handle, fname); + log_debug("dlsym(0x%p, %s) → 0x%p", handle, fname, f); + if (!f) { + log_info("%s not defined", fname); + return; + } + + status = f(gid, &gr, buffer, sizeof buffer, &errno1); + log_info("%s("GID_FMT") → status=%s%-20serrno=%d/%s", + fname, gid, + nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n", + errno1, errno_to_name(errno1) ?: "---"); + if (status == NSS_STATUS_SUCCESS) + print_struct_group(&gr); +} + +static void test_byname(void *handle, const char *module, const char *name) { + test_getpwnam_r(handle, module, name); + test_getgrnam_r(handle, module, name); + puts(""); +} + +static void test_byuid(void *handle, const char *module, uid_t uid) { + test_getpwuid_r(handle, module, uid); + test_getgrgid_r(handle, module, uid); + puts(""); +} + +static int test_one_module(const char *dir, + const char *module, + char **names) { + + log_info("======== %s ========", module); + + _cleanup_(dlclosep) void *handle = nss_open_handle(dir, module, RTLD_LAZY|RTLD_NODELETE); + if (!handle) + return -EINVAL; + + STRV_FOREACH(name, names) + test_byname(handle, module, *name); + + STRV_FOREACH(name, names) { + uid_t uid; + + assert_cc(sizeof(uid_t) == sizeof(uint32_t)); + /* We use safe_atou32 because we don't want to refuse invalid uids. */ + if (safe_atou32(*name, &uid) < 0) + continue; + + test_byuid(handle, module, uid); + } + + log_info(" "); + return 0; +} + +static int parse_argv(int argc, char **argv, + char ***the_modules, + char ***the_names) { + + _cleanup_strv_free_ char **modules = NULL, **names = NULL; + const char *p; + int r; + + p = getenv("SYSTEMD_TEST_NSS_BUFSIZE"); + if (p) { + r = safe_atozu(p, &arg_bufsize); + if (r < 0) + return log_error_errno(r, "Failed to parse $SYSTEMD_TEST_NSS_BUFSIZE"); + } + + if (argc > 1) + modules = strv_new(argv[1]); + else + modules = strv_new( +#if ENABLE_NSS_SYSTEMD + "systemd", +#endif +#if ENABLE_NSS_MYMACHINES + "mymachines", +#endif + NULL); + assert_se(modules); + + if (argc > 2) + names = strv_copy(strv_skip(argv, 2)); + else + names = strv_new("root", + NOBODY_USER_NAME, + "foo_no_such_user", + "0", + "65534"); + assert_se(names); + + *the_modules = TAKE_PTR(modules); + *the_names = TAKE_PTR(names); + return 0; +} + +static int run(int argc, char **argv) { + _cleanup_free_ char *dir = NULL; + _cleanup_strv_free_ char **modules = NULL, **names = NULL; + int r; + + test_setup_logging(LOG_INFO); + + r = parse_argv(argc, argv, &modules, &names); + if (r < 0) + return log_error_errno(r, "Failed to parse arguments: %m"); + + assert_se(path_extract_directory(argv[0], &dir) >= 0); + + STRV_FOREACH(module, modules) { + r = test_one_module(dir, *module, names); + if (r < 0) + return r; + } + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/test/test-nulstr-util.c b/src/test/test-nulstr-util.c new file mode 100644 index 0000000..95c25f1 --- /dev/null +++ b/src/test/test-nulstr-util.c @@ -0,0 +1,184 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "nulstr-util.h" +#include "set.h" +#include "strv.h" +#include "tests.h" + +TEST(strv_split_nulstr) { + _cleanup_strv_free_ char **l = NULL; + const char nulstr[] = "str0\0str1\0str2\0str3\0"; + + l = strv_split_nulstr(nulstr); + assert_se(l); + + assert_se(streq(l[0], "str0")); + assert_se(streq(l[1], "str1")); + assert_se(streq(l[2], "str2")); + assert_se(streq(l[3], "str3")); +} + +#define strv_parse_nulstr_full_one(s, n, e0, e1) \ + ({ \ + _cleanup_strv_free_ char **v0 = NULL, **v1 = NULL; \ + \ + assert_se(v0 = strv_parse_nulstr_full(s, n, false)); \ + assert_se(strv_equal(v0, e0)); \ + assert_se(v1 = strv_parse_nulstr_full(s, n, true)); \ + assert_se(strv_equal(v1, e1)); \ + }) + +TEST(strv_parse_nulstr_full) { + const char nulstr1[] = "hoge\0hoge2\0hoge3\0\0hoge5\0\0xxx"; + const char nulstr2[] = "hoge\0hoge2\0hoge3\0\0hoge5\0\0xxx\0\0\0"; + + strv_parse_nulstr_full_one(nulstr1, sizeof(nulstr1) - 1, + STRV_MAKE("hoge", "hoge2", "hoge3", "", "hoge5", "", "xxx"), + STRV_MAKE("hoge", "hoge2", "hoge3", "", "hoge5", "", "xxx")); + + strv_parse_nulstr_full_one(nulstr2, sizeof(nulstr2) - 1, + STRV_MAKE("hoge", "hoge2", "hoge3", "", "hoge5", "", "xxx", "", ""), + STRV_MAKE("hoge", "hoge2", "hoge3", "", "hoge5", "", "xxx")); + + strv_parse_nulstr_full_one(((const char[0]) {}), 0, + STRV_MAKE_EMPTY, STRV_MAKE_EMPTY); + + strv_parse_nulstr_full_one(((const char[1]) { 0 }), 1, + STRV_MAKE(""), STRV_MAKE_EMPTY); + + strv_parse_nulstr_full_one(((const char[1]) { 'x' }), 1, + STRV_MAKE("x"), STRV_MAKE("x")); + + strv_parse_nulstr_full_one(((const char[2]) { 0, 0 }), 2, + STRV_MAKE("", ""), STRV_MAKE_EMPTY); + + strv_parse_nulstr_full_one(((const char[2]) { 'x', 0 }), 2, + STRV_MAKE("x"), STRV_MAKE("x")); + + strv_parse_nulstr_full_one(((const char[3]) { 0, 0, 0 }), 3, + STRV_MAKE("", "", ""), STRV_MAKE_EMPTY); + + strv_parse_nulstr_full_one(((const char[3]) { 'x', 0, 0 }), 3, + STRV_MAKE("x", ""), STRV_MAKE("x")); + + strv_parse_nulstr_full_one(((const char[3]) { 0, 'x', 0 }), 3, + STRV_MAKE("", "x"), STRV_MAKE("", "x")); + + strv_parse_nulstr_full_one(((const char[3]) { 0, 0, 'x' }), 3, + STRV_MAKE("", "", "x"), STRV_MAKE("", "", "x")); + + strv_parse_nulstr_full_one(((const char[3]) { 'x', 'x', 0 }), 3, + STRV_MAKE("xx"), STRV_MAKE("xx")); + + strv_parse_nulstr_full_one(((const char[3]) { 0, 'x', 'x' }), 3, + STRV_MAKE("", "xx"), STRV_MAKE("", "xx")); + + strv_parse_nulstr_full_one(((const char[3]) { 'x', 0, 'x' }), 3, + STRV_MAKE("x", "x"), STRV_MAKE("x", "x")); + + strv_parse_nulstr_full_one(((const char[3]) { 'x', 'x', 'x' }), 3, + STRV_MAKE("xxx"), STRV_MAKE("xxx")); +} + +static void test_strv_make_nulstr_one(char **l) { + _cleanup_free_ char *b = NULL, *c = NULL; + _cleanup_strv_free_ char **q = NULL; + size_t n, m; + unsigned i = 0; + + log_info("/* %s */", __func__); + + assert_se(strv_make_nulstr(l, &b, &n) >= 0); + assert_se(q = strv_parse_nulstr(b, n)); + assert_se(strv_equal(l, q)); + + assert_se(strv_make_nulstr(q, &c, &m) >= 0); + assert_se(memcmp_nn(b, n, c, m) == 0); + + NULSTR_FOREACH(s, b) + assert_se(streq(s, l[i++])); + assert_se(i == strv_length(l)); +} + +TEST(strv_make_nulstr) { + test_strv_make_nulstr_one(NULL); + test_strv_make_nulstr_one(STRV_MAKE(NULL)); + test_strv_make_nulstr_one(STRV_MAKE("foo")); + test_strv_make_nulstr_one(STRV_MAKE("foo", "bar")); + test_strv_make_nulstr_one(STRV_MAKE("foo", "bar", "quuux")); +} + +TEST(set_make_nulstr) { + _cleanup_set_free_free_ Set *set = NULL; + size_t len = 0; + int r; + + { + /* Unallocated and empty set. */ + static const char expect[] = { 0x00, 0x00 }; + _cleanup_free_ char *nulstr = NULL; + + r = set_make_nulstr(set, &nulstr, &len); + assert_se(r == 0); + assert_se(len == 0); + assert_se(memcmp(expect, nulstr, len + 2) == 0); + } + + { + /* Allocated by empty set. */ + static const char expect[] = { 0x00, 0x00 }; + _cleanup_free_ char *nulstr = NULL; + + set = set_new(NULL); + assert_se(set); + + r = set_make_nulstr(set, &nulstr, &len); + assert_se(r == 0); + assert_se(len == 0); + assert_se(memcmp(expect, nulstr, len + 2) == 0); + } + + { + /* Non-empty set. */ + static const char expect[] = { 'a', 'a', 'a', 0x00, 0x00 }; + _cleanup_free_ char *nulstr = NULL; + + assert_se(set_put_strdup(&set, "aaa") >= 0); + + r = set_make_nulstr(set, &nulstr, &len); + assert_se(r == 0); + assert_se(len == 4); + assert_se(memcmp(expect, nulstr, len + 1) == 0); + } +} + +static void test_strv_make_nulstr_binary_one(char **l, const char *b, size_t n) { + _cleanup_strv_free_ char **z = NULL; + _cleanup_free_ char *a = NULL; + size_t m; + + assert_se(strv_make_nulstr(l, &a, &m) >= 0); + assert_se(memcmp_nn(a, m, b, n) == 0); + assert_se(z = strv_parse_nulstr(a, m)); + assert_se(strv_equal(l, z)); +} + +TEST(strv_make_nulstr_binary) { + test_strv_make_nulstr_binary_one(NULL, (const char[0]) {}, 0); + test_strv_make_nulstr_binary_one(STRV_MAKE(NULL), (const char[0]) {}, 0); + test_strv_make_nulstr_binary_one(STRV_MAKE(""), (const char[1]) { 0 }, 1); + test_strv_make_nulstr_binary_one(STRV_MAKE("", ""), (const char[2]) { 0, 0 }, 2); + test_strv_make_nulstr_binary_one(STRV_MAKE("x", ""), (const char[3]) { 'x', 0, 0 }, 3); + test_strv_make_nulstr_binary_one(STRV_MAKE("", "x"), (const char[3]) { 0, 'x', 0 }, 3); + test_strv_make_nulstr_binary_one(STRV_MAKE("", "", ""), (const char[3]) { 0, 0, 0 }, 3); + test_strv_make_nulstr_binary_one(STRV_MAKE("x", "", ""), (const char[4]) { 'x', 0, 0, 0 }, 4); + test_strv_make_nulstr_binary_one(STRV_MAKE("", "x", ""), (const char[4]) { 0, 'x', 0, 0 }, 4); + test_strv_make_nulstr_binary_one(STRV_MAKE("", "", "x"), (const char[4]) { 0, 0, 'x', 0 }, 4); + test_strv_make_nulstr_binary_one(STRV_MAKE("x", "x", ""), (const char[5]) { 'x', 0, 'x', 0, 0 }, 5); + test_strv_make_nulstr_binary_one(STRV_MAKE("", "x", "x"), (const char[5]) { 0, 'x', 0, 'x', 0 }, 5); + test_strv_make_nulstr_binary_one(STRV_MAKE("x", "", "x"), (const char[5]) { 'x', 0, 0, 'x', 0 }, 5); + test_strv_make_nulstr_binary_one(STRV_MAKE("x", "x", "x"), (const char[6]) { 'x', 0, 'x', 0, 'x', 0 }, 6); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-open-file.c b/src/test/test-open-file.c new file mode 100644 index 0000000..1b938ec --- /dev/null +++ b/src/test/test-open-file.c @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "open-file.h" +#include "string-util.h" +#include "tests.h" + +TEST(open_file_parse) { + _cleanup_(open_file_freep) OpenFile *of = NULL; + int r; + + r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:read-only", &of); + + assert_se(r >= 0); + assert_se(streq(of->path, "/proc/1/ns/mnt")); + assert_se(streq(of->fdname, "host-mount-namespace")); + assert_se(of->flags == OPENFILE_READ_ONLY); + + of = open_file_free(of); + r = open_file_parse("/proc/1/ns/mnt", &of); + + assert_se(r >= 0); + assert_se(streq(of->path, "/proc/1/ns/mnt")); + assert_se(streq(of->fdname, "mnt")); + assert_se(of->flags == 0); + + of = open_file_free(of); + r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace", &of); + + assert_se(r >= 0); + assert_se(streq(of->path, "/proc/1/ns/mnt")); + assert_se(streq(of->fdname, "host-mount-namespace")); + assert_se(of->flags == 0); + + of = open_file_free(of); + r = open_file_parse("/proc/1/ns/mnt::read-only", &of); + + assert_se(r >= 0); + assert_se(streq(of->path, "/proc/1/ns/mnt")); + assert_se(streq(of->fdname, "mnt")); + assert_se(of->flags == OPENFILE_READ_ONLY); + + of = open_file_free(of); + r = open_file_parse("../file.dat:file:read-only", &of); + + assert_se(r == -EINVAL); + + of = open_file_free(of); + r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:rw", &of); + + assert_se(r == -EINVAL); + + of = open_file_free(of); + r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:append", &of); + + assert_se(r >= 0); + assert_se(streq(of->path, "/proc/1/ns/mnt")); + assert_se(streq(of->fdname, "host-mount-namespace")); + assert_se(of->flags == OPENFILE_APPEND); + + of = open_file_free(of); + r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:truncate", &of); + + assert_se(r >= 0); + assert_se(streq(of->path, "/proc/1/ns/mnt")); + assert_se(streq(of->fdname, "host-mount-namespace")); + assert_se(of->flags == OPENFILE_TRUNCATE); + + of = open_file_free(of); + r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:read-only,append", &of); + + assert_se(r == -EINVAL); + + of = open_file_free(of); + r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:read-only,truncate", &of); + + assert_se(r == -EINVAL); + + of = open_file_free(of); + r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:append,truncate", &of); + + assert_se(r == -EINVAL); + + of = open_file_free(of); + r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:read-only,read-only", &of); + + assert_se(r == -EINVAL); + + of = open_file_free(of); + r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:graceful", &of); + + assert_se(r >= 0); + assert_se(streq(of->path, "/proc/1/ns/mnt")); + assert_se(streq(of->fdname, "host-mount-namespace")); + assert_se(of->flags == OPENFILE_GRACEFUL); + + of = open_file_free(of); + r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:read-only,graceful", &of); + + assert_se(r >= 0); + assert_se(streq(of->path, "/proc/1/ns/mnt")); + assert_se(streq(of->fdname, "host-mount-namespace")); + assert_se(of->flags == (OPENFILE_READ_ONLY | OPENFILE_GRACEFUL)); + + of = open_file_free(of); + r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:read-only:other", &of); + + assert_se(r == -EINVAL); +} + +TEST(open_file_to_string) { + _cleanup_free_ char *s = NULL; + _cleanup_(open_file_freep) OpenFile *of = NULL; + int r; + + assert_se(of = new (OpenFile, 1)); + *of = (OpenFile){ .path = strdup("/proc/1/ns/mnt"), + .fdname = strdup("host-mount-namespace"), + .flags = OPENFILE_READ_ONLY }; + + r = open_file_to_string(of, &s); + + assert_se(r >= 0); + assert_se(streq(s, "/proc/1/ns/mnt:host-mount-namespace:read-only")); + + s = mfree(s); + of->flags = OPENFILE_APPEND; + + r = open_file_to_string(of, &s); + + assert_se(r >= 0); + assert_se(streq(s, "/proc/1/ns/mnt:host-mount-namespace:append")); + + s = mfree(s); + of->flags = OPENFILE_TRUNCATE; + + r = open_file_to_string(of, &s); + + assert_se(r >= 0); + assert_se(streq(s, "/proc/1/ns/mnt:host-mount-namespace:truncate")); + + s = mfree(s); + of->flags = OPENFILE_GRACEFUL; + + r = open_file_to_string(of, &s); + + assert_se(r >= 0); + assert_se(streq(s, "/proc/1/ns/mnt:host-mount-namespace:graceful")); + + s = mfree(s); + of->flags = OPENFILE_READ_ONLY | OPENFILE_GRACEFUL; + + r = open_file_to_string(of, &s); + + assert_se(r >= 0); + assert_se(streq(s, "/proc/1/ns/mnt:host-mount-namespace:read-only,graceful")); + + s = mfree(s); + of->flags = 0; + + r = open_file_to_string(of, &s); + + assert_se(r >= 0); + assert_se(streq(s, "/proc/1/ns/mnt:host-mount-namespace")); + + s = mfree(s); + assert_se(free_and_strdup(&of->fdname, "mnt")); + of->flags = OPENFILE_READ_ONLY; + + r = open_file_to_string(of, &s); + + assert_se(r >= 0); + assert_se(streq(s, "/proc/1/ns/mnt::read-only")); + + s = mfree(s); + assert_se(free_and_strdup(&of->path, "/path:with:colon")); + assert_se(free_and_strdup(&of->fdname, "path:with:colon")); + of->flags = 0; + + r = open_file_to_string(of, &s); + + assert_se(r >= 0); + assert_se(streq(s, "/path\\:with\\:colon")); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-openssl.c b/src/test/test-openssl.c new file mode 100644 index 0000000..dfdd1ab --- /dev/null +++ b/src/test/test-openssl.c @@ -0,0 +1,483 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "hexdecoct.h" +#include "openssl-util.h" +#include "tests.h" + +TEST(openssl_pkey_from_pem) { + DEFINE_HEX_PTR(key_ecc, "2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a30444151634451674145726a6e4575424c73496c3972687068777976584e50686a346a426e500a44586e794a304b395579724e6764365335413532542b6f5376746b436a365a726c34685847337741515558706f426c532b7448717452714c35513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a"); + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_ecc = NULL; + assert_se(openssl_pkey_from_pem(key_ecc, key_ecc_len, &pkey_ecc) >= 0); + + _cleanup_free_ void *x = NULL, *y = NULL; + size_t x_len, y_len; + int curve_id; + assert_se(ecc_pkey_to_curve_x_y(pkey_ecc, &curve_id, &x, &x_len, &y, &y_len) >= 0); + assert_se(curve_id == NID_X9_62_prime256v1); + + DEFINE_HEX_PTR(expected_x, "ae39c4b812ec225f6b869870caf5cd3e18f88c19cf0d79f22742bd532acd81de"); + assert_se(memcmp_nn(x, x_len, expected_x, expected_x_len) == 0); + + DEFINE_HEX_PTR(expected_y, "92e40e764fea12bed9028fa66b9788571b7c004145e9a01952fad1eab51a8be5"); + assert_se(memcmp_nn(y, y_len, expected_y, expected_y_len) == 0); + + DEFINE_HEX_PTR(key_rsa, "2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d494942496a414e42676b71686b6947397730424151454641414f43415138414d49494243674b4341514541795639434950652f505852337a436f63787045300a6a575262546c3568585844436b472f584b79374b6d2f4439584942334b734f5a31436a5937375571372f674359363170697838697552756a73413464503165380a593445336c68556d374a332b6473766b626f4b64553243626d52494c2f6675627771694c4d587a41673342575278747234547545443533527a373634554650640a307a70304b68775231496230444c67772f344e67566f314146763378784b4d6478774d45683567676b73733038326332706c354a504e32587677426f744e6b4d0a5471526c745a4a35355244436170696e7153334577376675646c4e735851357746766c7432377a7637344b585165616d704c59433037584f6761304c676c536b0a79754774586b6a50542f735542544a705374615769674d5a6f714b7479563463515a58436b4a52684459614c47587673504233687a766d5671636e6b47654e540a65774944415141420a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a"); + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_rsa = NULL; + assert_se(openssl_pkey_from_pem(key_rsa, key_rsa_len, &pkey_rsa) >= 0); + + _cleanup_free_ void *n = NULL, *e = NULL; + size_t n_len, e_len; + assert_se(rsa_pkey_to_n_e(pkey_rsa, &n, &n_len, &e, &e_len) >= 0); + + DEFINE_HEX_PTR(expected_n, "c95f4220f7bf3d7477cc2a1cc691348d645b4e5e615d70c2906fd72b2eca9bf0fd5c80772ac399d428d8efb52aeff80263ad698b1f22b91ba3b00e1d3f57bc638137961526ec9dfe76cbe46e829d53609b99120bfdfb9bc2a88b317cc0837056471b6be13b840f9dd1cfbeb85053ddd33a742a1c11d486f40cb830ff8360568d4016fdf1c4a31dc7030487982092cb34f36736a65e493cdd97bf0068b4d90c4ea465b59279e510c26a98a7a92dc4c3b7ee76536c5d0e7016f96ddbbcefef829741e6a6a4b602d3b5ce81ad0b8254a4cae1ad5e48cf4ffb140532694ad6968a0319a2a2adc95e1c4195c29094610d868b197bec3c1de1cef995a9c9e419e3537b"); + assert_se(memcmp_nn(n, n_len, expected_n, expected_n_len) == 0); + + DEFINE_HEX_PTR(expected_e, "010001"); + assert_se(memcmp_nn(e, e_len, expected_e, expected_e_len) == 0); +} + +TEST(rsa_pkey_n_e) { + DEFINE_HEX_PTR(n, "e3975a2124a7c9fe57752d106314ff62f6da731632eac221f1c0255bdcf2a34eeb21e3ab89ba8759ddad3b68be99463c7f03f3d004028a35e6f7c6596aeab2558d490f1e1c38aed2ff796bda8d6d55704eefb6ac55842dd6e606bb707f66acc02f0db2aed0dabab885bd0c850f1bdc8ac4b6bc1f74858db8ca2ab57a3d4217c091e9cd78727a2e36b8126ea629e81fecc69b0bea601000a6c0b749c5be16f53f4fa9f208a581d804234eb6526ba3fee9822d58d1ab9cac2761d7f630eb7ad6054dff0856d41aea219e1adfd87256aa1532202a070f4b1044e718d1f38bbc5a4b1fcb024f04afaafda5edeacfdf0d0bdf35c359acd059e3edb5024e588458f9b5"); + uint32_t e = htobe32(0x10001); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + assert_se(rsa_pkey_from_n_e(n, n_len, &e, sizeof(e), &pkey) >= 0); + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new((EVP_PKEY*) pkey, NULL); + assert_se(ctx); + assert_se(EVP_PKEY_verify_init(ctx) == 1); + + const char *msg = "this is a secret"; + DEFINE_HEX_PTR(sig, "14b53e0c6ad99a350c3d7811e8160f4ae03ad159815bb91bddb9735b833588df2eac221fbd3fc4ece0dd63bfaeddfdaf4ae67021e759f3638bc194836413414f54e8c4d01c9c37fa4488ea2ef772276b8a33822a53c97b1c35acfb4bc621cfb8fad88f0cf7d5491f05236886afbf9ed47f9469536482f50f74a20defa59d99676bed62a17b5eb98641df5a2f8080fa4b24f2749cc152fa65ba34c14022fcb27f1b36f52021950d7b9b6c3042c50b84cfb7d55a5f9235bfd58e1bf1f604eb93416c5fb5fd90cb68f1270dfa9daf67f52c604f62c2f2beee5e7e672b0e6e9833dd43dba99b77668540c850c9a81a5ea7aaf6297383e6135bd64572362333121fc7"); + assert_se(EVP_PKEY_verify(ctx, sig, sig_len, (unsigned char*) msg, strlen(msg)) == 1); + + DEFINE_HEX_PTR(invalid_sig, "1234"); + assert_se(EVP_PKEY_verify(ctx, invalid_sig, invalid_sig_len, (unsigned char*) msg, strlen(msg)) != 1); + + _cleanup_free_ void *n2 = NULL, *e2 = NULL; + size_t n2_size, e2_size; + assert_se(rsa_pkey_to_n_e(pkey, &n2, &n2_size, &e2, &e2_size) >= 0); + assert_se(memcmp_nn(n, n_len, n2, n2_size) == 0); + assert_se(e2_size <= sizeof(uint32_t)); + assert_se(memcmp(&((uint8_t*) &e)[sizeof(uint32_t) - e2_size], e2, e2_size) == 0); +} + +TEST(ecc_pkey_curve_x_y) { + int curveid = NID_X9_62_prime256v1; + DEFINE_HEX_PTR(x, "2830d2c8f65d3efbef12303b968b91692f8bd04045dcb8a9656374e4ae61d818"); + DEFINE_HEX_PTR(y, "8a80750f76729defdcc2a4bc1a91c22e60109dd6e1ffde634a650a20bab172e9"); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + assert_se(ecc_pkey_from_curve_x_y(curveid, x, x_len, y, y_len, &pkey) >= 0); + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new((EVP_PKEY*) pkey, NULL); + assert_se(ctx); + assert_se(EVP_PKEY_verify_init(ctx) == 1); + + const char *msg = "this is a secret"; + DEFINE_HEX_PTR(sig, "3045022100f6ca10f7ed57a020679899b26dd5ac5a1079265885e2a6477f527b6a3f02b5ca02207b550eb3e7b69360aff977f7f6afac99c3f28266b6c5338ce373f6b59263000a"); + assert_se(EVP_PKEY_verify(ctx, sig, sig_len, (unsigned char*) msg, strlen(msg)) == 1); + + DEFINE_HEX_PTR(invalid_sig, "1234"); + assert_se(EVP_PKEY_verify(ctx, invalid_sig, invalid_sig_len, (unsigned char*) msg, strlen(msg)) != 1); + + _cleanup_free_ void *x2 = NULL, *y2 = NULL; + size_t x2_size, y2_size; + int curveid2; + assert_se(ecc_pkey_to_curve_x_y(pkey, &curveid2, &x2, &x2_size, &y2, &y2_size) >= 0); + assert_se(curveid == curveid2); + assert_se(memcmp_nn(x, x_len, x2, x2_size) == 0); + assert_se(memcmp_nn(y, y_len, y2, y2_size) == 0); +} + +TEST(invalid) { + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + + DEFINE_HEX_PTR(key, "2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b7b"); + assert_se(openssl_pkey_from_pem(key, key_len, &pkey) == -EIO); + assert_se(pkey == NULL); +} + +static const struct { + const char *alg; + size_t size; +} digest_size_table[] = { + /* SHA1 "family" */ + { "sha1", 20, }, +#if OPENSSL_VERSION_MAJOR >= 3 + { "sha-1", 20, }, +#endif + /* SHA2 family */ + { "sha224", 28, }, + { "sha256", 32, }, + { "sha384", 48, }, + { "sha512", 64, }, +#if OPENSSL_VERSION_MAJOR >= 3 + { "sha-224", 28, }, + { "sha2-224", 28, }, + { "sha-256", 32, }, + { "sha2-256", 32, }, + { "sha-384", 48, }, + { "sha2-384", 48, }, + { "sha-512", 64, }, + { "sha2-512", 64, }, +#endif + /* SHA3 family */ + { "sha3-224", 28, }, + { "sha3-256", 32, }, + { "sha3-384", 48, }, + { "sha3-512", 64, }, + /* SM3 family */ + { "sm3", 32, }, + /* MD5 family */ + { "md5", 16, }, +}; + +TEST(digest_size) { + size_t size; + + FOREACH_ARRAY(t, digest_size_table, ELEMENTSOF(digest_size_table)) { + assert(openssl_digest_size(t->alg, &size) >= 0); + assert_se(size == t->size); + + _cleanup_free_ char *uppercase_alg = strdup(t->alg); + assert_se(uppercase_alg); + assert_se(openssl_digest_size(ascii_strupper(uppercase_alg), &size) >= 0); + assert_se(size == t->size); + } + + assert_se(openssl_digest_size("invalid.alg", &size) == -EOPNOTSUPP); +} + +static void verify_digest(const char *digest_alg, const struct iovec *data, size_t n_data, const char *expect) { + _cleanup_free_ void *digest = NULL; + size_t digest_size; + int r; + + r = openssl_digest_many(digest_alg, data, n_data, &digest, &digest_size); + if (r == -EOPNOTSUPP) + return; + assert_se(r >= 0); + + DEFINE_HEX_PTR(e, expect); + assert_se(memcmp_nn(e, e_len, digest, digest_size) == 0); +} + +#define _DEFINE_DIGEST_TEST(uniq, alg, expect, ...) \ + const struct iovec UNIQ_T(i, uniq)[] = { __VA_ARGS__ }; \ + verify_digest(alg, \ + UNIQ_T(i, uniq), \ + ELEMENTSOF(UNIQ_T(i, uniq)), \ + expect); +#define DEFINE_DIGEST_TEST(alg, expect, ...) _DEFINE_DIGEST_TEST(UNIQ, alg, expect, __VA_ARGS__) +#define DEFINE_SHA1_TEST(expect, ...) DEFINE_DIGEST_TEST("SHA1", expect, __VA_ARGS__) +#define DEFINE_SHA256_TEST(expect, ...) DEFINE_DIGEST_TEST("SHA256", expect, __VA_ARGS__) +#define DEFINE_SHA384_TEST(expect, ...) DEFINE_DIGEST_TEST("SHA384", expect, __VA_ARGS__) +#define DEFINE_SHA512_TEST(expect, ...) DEFINE_DIGEST_TEST("SHA512", expect, __VA_ARGS__) + +TEST(digest_many) { + const struct iovec test = IOVEC_MAKE_STRING("test"); + + /* Empty digests */ + DEFINE_SHA1_TEST("da39a3ee5e6b4b0d3255bfef95601890afd80709"); + DEFINE_SHA256_TEST("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); + DEFINE_SHA384_TEST("38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b"); + DEFINE_SHA512_TEST("cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"); + + DEFINE_SHA1_TEST("a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", test); + DEFINE_SHA256_TEST("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", test); + DEFINE_SHA384_TEST("768412320f7b0aa5812fce428dc4706b3cae50e02a64caa16a782249bfe8efc4b7ef1ccb126255d196047dfedf17a0a9", test); + DEFINE_SHA512_TEST("ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff", test); + + DEFINE_HEX_PTR(h1, "e9ff2b6dfbc03b8dd0471a0f23840334e3ef51c64a325945524563c0375284a092751eca8d084fae22f74a104559a0ee8339d1845538481e674e6d31d4f63089"); + DEFINE_HEX_PTR(h2, "5b6e809933a1b8d5a4a6bb62e20b36ae82d9408141e7479d0aa067273bd2d04007fb1977bad549d54330a49ed98f82b495ba"); + DEFINE_HEX_PTR(h3, "d2aeef94d7ba2a"); + DEFINE_HEX_PTR(h4, "1557db45ded3e38c79b5bb25c83ade42fa7d13047ef1b9a0b21a3c2ab2d4eee5c75e2927ce643163addbda65331035850a436c0acffc723f419e1d1cbf04c9064e6d850580c0732a12600f9feb"); + + const struct iovec i1 = IOVEC_MAKE(h1, h1_len); + const struct iovec i2 = IOVEC_MAKE(h2, h2_len); + const struct iovec i3 = IOVEC_MAKE(h3, h3_len); + const struct iovec i4 = IOVEC_MAKE(h4, h4_len); + + DEFINE_SHA1_TEST("8e7c659a6331508b06adf98b430759dafb92fc43", i1, i2, i3, i4); + DEFINE_SHA256_TEST("4d6be38798786a5500651c1a02d96aa010e9d7b2bece1695294cd396d456cde8", i1, i2, i3, i4); + DEFINE_SHA384_TEST("82e6ec14f8d90f1ae1fd4fb7f415ea6fdb674515b13092e3e548a8d37a8faed30cda8ea613ec2a015a51bc578dacc995", i1, i2, i3, i4); + DEFINE_SHA512_TEST("21fe5beb15927257a9143ff59010e51d4c65c7c5237b0cd9a8db3c3fabe429be3a0759f9ace3cdd70f6ea543f998bec9bc3308833d70aa1bd380364de872a62c", i1, i2, i3, i4); + + DEFINE_SHA256_TEST("0e0ed67d6717dc08dd6f472f6c35107a92b8c2695dcba344b884436f97a9eb4d", i1, i1, i1, i4); + + DEFINE_SHA256_TEST("8fe8b8d1899c44bfb82e1edc4ff92642db5b2cb25c4210ea06c3846c757525a8", i1, i1, i1, i4, i4, i4, i4, i3, i3, i2); +} + +static void verify_hmac( + const char *digest_alg, + const char *key, + const struct iovec *data, + size_t n_data, + const char *expect) { + + DEFINE_HEX_PTR(k, key); + DEFINE_HEX_PTR(e, expect); + _cleanup_free_ void *digest = NULL; + size_t digest_size; + + if (n_data == 0) { + assert_se(openssl_hmac(digest_alg, k, k_len, NULL, 0, &digest, &digest_size) == 0); + assert_se(memcmp_nn(e, e_len, digest, digest_size) == 0); + digest = mfree(digest); + } else if(n_data == 1) { + assert_se(openssl_hmac(digest_alg, k, k_len, data[0].iov_base, data[0].iov_len, &digest, &digest_size) == 0); + assert_se(memcmp_nn(e, e_len, digest, digest_size) == 0); + digest = mfree(digest); + } + + assert_se(openssl_hmac_many(digest_alg, k, k_len, data, n_data, &digest, &digest_size) == 0); + assert_se(memcmp_nn(e, e_len, digest, digest_size) == 0); +} + +#define _DEFINE_HMAC_TEST(uniq, alg, key, expect, ...) \ + const struct iovec UNIQ_T(i, uniq)[] = { __VA_ARGS__ }; \ + verify_hmac(alg, \ + key, \ + UNIQ_T(i, uniq), \ + ELEMENTSOF(UNIQ_T(i, uniq)), \ + expect); +#define DEFINE_HMAC_TEST(alg, key, expect, ...) _DEFINE_HMAC_TEST(UNIQ, alg, key, expect, __VA_ARGS__) +#define DEFINE_HMAC_SHA1_TEST(key, expect, ...) DEFINE_HMAC_TEST("SHA1", key, expect, __VA_ARGS__) +#define DEFINE_HMAC_SHA256_TEST(key, expect, ...) DEFINE_HMAC_TEST("SHA256", key, expect, __VA_ARGS__) +#define DEFINE_HMAC_SHA384_TEST(key, expect, ...) DEFINE_HMAC_TEST("SHA384", key, expect, __VA_ARGS__) +#define DEFINE_HMAC_SHA512_TEST(key, expect, ...) DEFINE_HMAC_TEST("SHA512", key, expect, __VA_ARGS__) + +TEST(hmac_many) { + const char *key1 = "760eb6845073862c1914c6d188bf8214", + *key2 = "0628d1a5f83fce99779e12e2336d87046d42d74b755f00d9f72350668860fd00", + *key3 = "b61158912b76348c54f104629924be4178b8a9c9459c3a6e9daa1885445a61fccc1aa0f749c31f3ade4e227f64dd0e86a94b25c2e181f044af22d0a8c07074c3"; + const struct iovec test = IOVEC_MAKE_STRING("test"); + + /* Empty digests */ + DEFINE_HMAC_SHA1_TEST(key1, "EB9725FC9A99A652C3171E0863984AC42461F88B"); + DEFINE_HMAC_SHA256_TEST(key1, "82A15D4DD5F583CF8F06D3E447DF0FDFF95A24E29229934B48BD0A5B4E0ADC85"); + DEFINE_HMAC_SHA384_TEST(key1, "C60F15C4E18736750D91095ADA148C4179825A487CCA3AE047A2FB94F85A5587AB6AF57678AA79715FEF848129C108C3"); + DEFINE_HMAC_SHA512_TEST(key1, "2B10DC9BFC0349400F8965482EA149C1C51C865BB7B16097623F41C14CF6C8A678724BFAE0CE842EED899C12CC17B5D8C4287F72BE788532FE7CF0BE2EBCD447"); + + DEFINE_HMAC_SHA1_TEST(key2, "F9AA74F129681E91807EB264EA6E1B5C5F9B4CFD"); + DEFINE_HMAC_SHA256_TEST(key2, "B4ADEBF8B3044A5B0668B742C0A49B61D8380F89938C84794C92567F5A33CC7D"); + DEFINE_HMAC_SHA384_TEST(key2, "E5EACAB7A13CF5BE60FA228D771E183CD6E57536BB9EAFC34A6BB52B1B1324BD6FB8A1713F91EC040790AE97F5672D53"); + DEFINE_HMAC_SHA512_TEST(key2, "75A597D83A6270FC3204DE741E76DEFCF42D3E1812C71E41EEA8C0F23C07315822E83BE8B54705CB00FEF4CE1BAF80E3975414925C83BF3719CEBC27DD133F7D"); + + DEFINE_HMAC_SHA1_TEST(key3, "4B8EACB3C3935ACC8C58995C89F16020FC993569"); + DEFINE_HMAC_SHA256_TEST(key3, "520E8C0323A1994D58EF5456611BCB6CD701399B24F8FBA0B5A3CD3186780E8E"); + DEFINE_HMAC_SHA384_TEST(key3, "52ADAF691EFDC377B7349EAA45EE1BFAFA27CAC1FFE08B942C80426D1CA9F3464E3A71D611DA0B415435E82D6EE9F34A"); + DEFINE_HMAC_SHA512_TEST(key3, "22D8C17BAF591E07CD2BD58A1B3D76D5904EC45C9099F0171A243F07611E25208A395833BC3F9BBD425636FD8D574BE1A1A367DCB6C40AD3C06E2B57E8FD2729"); + + /* test message */ + DEFINE_HMAC_SHA1_TEST(key2, "DEE6313BE6391523D0B2B326890F13A65F3965B2", test); + DEFINE_HMAC_SHA256_TEST(key2, "496FF3E9DA52B2B490CD5EAE23457F8A33E61AB7B42F6E6374B7629CFBE1FCED", test); + DEFINE_HMAC_SHA384_TEST(key2, "F5223F750D671453CA6159C1354242DB13E0189CB79AC73E4964F623181B00C811A596F7CE3408DDE06B96C6D792F41E", test); + DEFINE_HMAC_SHA512_TEST(key2, "8755A8B0D85D89AFFE7A15702BBA0F835CDE454334EC952ED777A30035D6BD9407EA5DF8DCB89814C1DF7EE215022EA68D9D2BC4E4B299CD6F55CD60C269A706", test); + + DEFINE_HEX_PTR(h1, "e9ff2b6dfbc03b8dd0471a0f23840334e3ef51c64a325945524563c0375284a092751eca8d084fae22f74a104559a0ee8339d1845538481e674e6d31d4f63089"); + DEFINE_HEX_PTR(h2, "5b6e809933a1b8d5a4a6bb62e20b36ae82d9408141e7479d0aa067273bd2d04007fb1977bad549d54330a49ed98f82b495ba"); + DEFINE_HEX_PTR(h3, "d2aeef94d7ba2a"); + DEFINE_HEX_PTR(h4, "1557db45ded3e38c79b5bb25c83ade42fa7d13047ef1b9a0b21a3c2ab2d4eee5c75e2927ce643163addbda65331035850a436c0acffc723f419e1d1cbf04c9064e6d850580c0732a12600f9feb"); + + const struct iovec i1 = IOVEC_MAKE(h1, h1_len); + const struct iovec i2 = IOVEC_MAKE(h2, h2_len); + const struct iovec i3 = IOVEC_MAKE(h3, h3_len); + const struct iovec i4 = IOVEC_MAKE(h4, h4_len); + + DEFINE_HMAC_SHA1_TEST(key2, "28C041532012BFF1B7C87B2A15A8C43EB8037D27", i1, i2, i3, i4); + DEFINE_HMAC_SHA256_TEST(key2, "F8A1FBDEE3CD383EA2B4940A3C8E72F443DB5B247016C9F84E2D2FEF3C5A0A23", i1, i2, i3, i4); + DEFINE_HMAC_SHA384_TEST(key2, "4D2AB0516F1F5C73BD0761407E0AF42361C1CAE761685FC65D1199598315EE3DCA4DB88E4D96FB06C2DA215A33FA9CE9", i1, i2, i3, i4); + DEFINE_HMAC_SHA512_TEST(key2, "E9BF8FC6FDE75FD5E4EF2DF399EE675C57B60C59A7B331F30535FDE68D8072185552E9A8BFA2008C52437F1BCC1472D16FBCF2A77C37339752938E42D2642150", i1, i2, i3, i4); + + DEFINE_HMAC_SHA256_TEST(key3, "94D4E4B55368A533F6A7FDCC3B93E1F283BB1CA387BB5D14FAFF44A009EDF040", i1, i1, i1, i4); + + DEFINE_HMAC_SHA256_TEST(key3, "5BE1F4D9C2AFAA2BB3F58FCE967BC7D3084BB8F512659875BDA634991145B0F0", i1, i1, i1, i4, i4, i4, i4, i3, i3, i2); +} + +TEST(kdf_kb_hmac_derive) { +#if OPENSSL_VERSION_MAJOR >= 3 + _cleanup_free_ void *derived_key = NULL; + + DEFINE_HEX_PTR(key, "d7ac57124f28371eacaec475b74869d26b4cd64586412a607ce0a9e0c63d468c"); + const char *salt = "salty chocolate"; + DEFINE_HEX_PTR(info, "6721a2012d9554f5a64593ed3eaa8fe15e6a21e1c8c8736ea4d234eb55b9e31a"); + DEFINE_HEX_PTR(expected_derived_key, "A9DA9CEEB9578DBE7DD2862F82898B086E85FF2D10C4E8EC5BD99D0D7F003A2DE1574EB4BD789C03EF5235259BCB3A009DA303EA4DB4CA6BF507DB7C5A063279"); + + assert_se(kdf_kb_hmac_derive("COUNTER", "SHA256", key, key_len, salt, strlen(salt), info, info_len, /* seed= */ NULL, /* seed_size= */ 0, 64, &derived_key) >= 0); + assert_se(memcmp_nn(derived_key, 64, expected_derived_key, expected_derived_key_len) == 0); +#else + log_tests_skipped("KDF-KB requires OpenSSL >= 3"); +#endif +} + +#if OPENSSL_VERSION_MAJOR >= 3 +static void check_ss_derive(const char *hex_key, const char *hex_salt, const char *hex_info, const char *hex_expected) { + DEFINE_HEX_PTR(key, hex_key); + DEFINE_HEX_PTR(salt, hex_salt); + DEFINE_HEX_PTR(info, hex_info); + DEFINE_HEX_PTR(expected, hex_expected); + + _cleanup_free_ void *derived_key = NULL; + assert_se(kdf_ss_derive("SHA256", key, key_len, salt, salt_len, info, info_len, expected_len, &derived_key) >= 0); + assert_se(memcmp_nn(derived_key, expected_len, expected, expected_len) == 0); +} +#endif + +TEST(kdf_ss_derive) { +#if OPENSSL_VERSION_MAJOR >= 3 + check_ss_derive( + "01166ad6b05d1fad8cdb50d1902170e9", + "feea805789dc8d0b57da5d4d61886b1a", + "af4cb6d1d0a996e21e3788584165e2ae", + "46CECAB4544E11EF986641BA6F843FAFFD111D3974C34E3B9592311E8579C6BD"); + + check_ss_derive( + "d1c39e37260d79d6e766f1d1412c4b61fc0801db469b97c897b0fbcaebea5178", + "b75e3b65d1bb845dee581c7e14cfebc6e882946e90273b77ebe289faaf7de248", + "ed25a0043d6c1eb28296da1f9ab138dafee18f4c937bfc43601d4ee6e7634199", + "30EB1A1E9DEA7DE4DDB8F3FDF50A01E3"); + /* Same inputs as above, but derive more bytes */ + check_ss_derive( + "d1c39e37260d79d6e766f1d1412c4b61fc0801db469b97c897b0fbcaebea5178", + "b75e3b65d1bb845dee581c7e14cfebc6e882946e90273b77ebe289faaf7de248", + "ed25a0043d6c1eb28296da1f9ab138dafee18f4c937bfc43601d4ee6e7634199", + "30EB1A1E9DEA7DE4DDB8F3FDF50A01E30581D606C1228D98AFF691DF743AC2EE9D99EFD2AE1946C079AA18C9524877FA65D5065F0DAED058AB3416AF80EB2B73"); +#else + log_tests_skipped("KDF-SS requires OpenSSL >= 3"); +#endif +} + +static void check_cipher( + const char *alg, + size_t bits, + const char *mode, + const char *hex_key, + const char *hex_iv, + const struct iovec data[], + size_t n_data, + const char *hex_expected) { + + _cleanup_free_ void *enc_buf = NULL; + size_t enc_buf_len; + + DEFINE_HEX_PTR(key, hex_key); + DEFINE_HEX_PTR(iv, hex_iv); + DEFINE_HEX_PTR(expected, hex_expected); + + if (n_data == 0) { + assert_se(openssl_cipher(alg, bits, mode, key, key_len, iv, iv_len, NULL, 0, &enc_buf, &enc_buf_len) >= 0); + assert_se(memcmp_nn(enc_buf, enc_buf_len, expected, expected_len) == 0); + enc_buf = mfree(enc_buf); + } else if (n_data == 1) { + assert_se(openssl_cipher(alg, bits, mode, key, key_len, iv, iv_len, data[0].iov_base, data[0].iov_len, &enc_buf, &enc_buf_len) >= 0); + assert_se(memcmp_nn(enc_buf, enc_buf_len, expected, expected_len) == 0); + enc_buf = mfree(enc_buf); + } + + assert_se(openssl_cipher_many(alg, bits, mode, key, key_len, iv, iv_len, data, n_data, &enc_buf, &enc_buf_len) >= 0); + assert_se(memcmp_nn(enc_buf, enc_buf_len, expected, expected_len) == 0); +} + +TEST(openssl_cipher) { + struct iovec data[] = { + IOVEC_MAKE_STRING("my"), + IOVEC_MAKE_STRING(" "), + IOVEC_MAKE_STRING("secret"), + IOVEC_MAKE_STRING(" "), + IOVEC_MAKE_STRING("text"), + IOVEC_MAKE_STRING("!"), + }; + + check_cipher( + "aes", 256, "cfb", + "32c62bbaeb0decc5c874b8e0148f86475b5bb10a36f7078a75a6f11704c2f06a", + /* hex_iv= */ NULL, + data, ELEMENTSOF(data), + "bd4a46f8762bf4bef4430514aaec5e"); + + check_cipher( + "aes", 256, "cfb", + "32c62bbaeb0decc5c874b8e0148f86475b5bb10a36f7078a75a6f11704c2f06a", + "00000000000000000000000000000000", + data, ELEMENTSOF(data), + "bd4a46f8762bf4bef4430514aaec5e"); + + check_cipher( + "aes", 256, "cfb", + "32c62bbaeb0decc5c874b8e0148f86475b5bb10a36f7078a75a6f11704c2f06a", + "9088fd5c4ad9b9419eced86283021a59", + data, ELEMENTSOF(data), + "6dfbf8dc972f9a462ad7427a1fa41a"); + + check_cipher( + "aes", 256, "cfb", + "32c62bbaeb0decc5c874b8e0148f86475b5bb10a36f7078a75a6f11704c2f06a", + /* hex_iv= */ NULL, + &data[2], 1, + "a35605f9763c"); + + check_cipher( + "aes", 256, "cfb", + "32c62bbaeb0decc5c874b8e0148f86475b5bb10a36f7078a75a6f11704c2f06a", + /* hex_iv= */ NULL, + /* data= */ NULL, /* n_data= */ 0, + /* expected= */ NULL); + + check_cipher( + "aes", 128, "cfb", + "b8fe4b89f6f25dd58cadceb68c99d508", + /* hex_iv= */ NULL, + data, ELEMENTSOF(data), + "9c0fe3abb904ab419d950ae00c93a1"); + + check_cipher( + "aes", 128, "cfb", + "b8fe4b89f6f25dd58cadceb68c99d508", + "00000000000000000000000000000000", + data, ELEMENTSOF(data), + "9c0fe3abb904ab419d950ae00c93a1"); + + check_cipher( + "aes", 128, "cfb", + "b8fe4b89f6f25dd58cadceb68c99d508", + "9088fd5c4ad9b9419eced86283021a59", + data, ELEMENTSOF(data), + "e765617aceb1326f5309008c14f4e1"); + + check_cipher( + "aes", 128, "cfb", + "b8fe4b89f6f25dd58cadceb68c99d508", + /* hex_iv= */ NULL, + /* data= */ NULL, /* n_data= */ 0, + /* expected= */ NULL); + + check_cipher( + "aes", 128, "cfb", + "b8fe4b89f6f25dd58cadceb68c99d508", + "00000000000000000000000000000000", + /* data= */ NULL, /* n_data= */ 0, + /* expected= */ NULL); +} + +TEST(ecc_ecdh) { + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkeyA = NULL, *pkeyB = NULL, *pkeyC = NULL; + _cleanup_free_ void *secretAB = NULL, *secretBA = NULL, *secretAC = NULL, *secretCA = NULL; + size_t secretAB_size, secretBA_size, secretAC_size, secretCA_size; + + assert_se(ecc_pkey_new(NID_X9_62_prime256v1, &pkeyA) >= 0); + assert_se(ecc_pkey_new(NID_X9_62_prime256v1, &pkeyB) >= 0); + assert_se(ecc_pkey_new(NID_X9_62_prime256v1, &pkeyC) >= 0); + + assert_se(ecc_ecdh(pkeyA, pkeyB, &secretAB, &secretAB_size) >= 0); + assert_se(ecc_ecdh(pkeyB, pkeyA, &secretBA, &secretBA_size) >= 0); + assert_se(ecc_ecdh(pkeyA, pkeyC, &secretAC, &secretAC_size) >= 0); + assert_se(ecc_ecdh(pkeyC, pkeyA, &secretCA, &secretCA_size) >= 0); + + assert_se(memcmp_nn(secretAB, secretAB_size, secretBA, secretBA_size) == 0); + assert_se(memcmp_nn(secretAC, secretAC_size, secretCA, secretCA_size) == 0); + assert_se(memcmp_nn(secretAC, secretAC_size, secretAB, secretAB_size) != 0); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-ordered-set.c b/src/test/test-ordered-set.c new file mode 100644 index 0000000..c055411 --- /dev/null +++ b/src/test/test-ordered-set.c @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdio.h> + +#include "ordered-set.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" + +TEST(set_steal_first) { + _cleanup_ordered_set_free_ OrderedSet *m = NULL; + int seen[3] = {}; + char *val; + + m = ordered_set_new(&string_hash_ops); + assert_se(m); + + assert_se(ordered_set_put(m, (void*) "1") == 1); + assert_se(ordered_set_put(m, (void*) "22") == 1); + assert_se(ordered_set_put(m, (void*) "333") == 1); + + ordered_set_print(stdout, "SET=", m); + + while ((val = ordered_set_steal_first(m))) + seen[strlen(val) - 1]++; + + assert_se(seen[0] == 1 && seen[1] == 1 && seen[2] == 1); + + assert_se(ordered_set_isempty(m)); + + ordered_set_print(stdout, "SET=", m); +} + +typedef struct Item { + int seen; +} Item; +static void item_seen(Item *item) { + item->seen++; +} + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(item_hash_ops, void, trivial_hash_func, trivial_compare_func, Item, item_seen); + +TEST(set_free_with_hash_ops) { + OrderedSet *m; + struct Item items[4] = {}; + + assert_se(m = ordered_set_new(&item_hash_ops)); + + for (size_t i = 0; i < ELEMENTSOF(items) - 1; i++) + assert_se(ordered_set_put(m, items + i) == 1); + + for (size_t i = 0; i < ELEMENTSOF(items) - 1; i++) + assert_se(ordered_set_put(m, items + i) == 0); /* We get 0 here, because we use trivial hash + * ops. Also see below... */ + + m = ordered_set_free(m); + assert_se(items[0].seen == 1); + assert_se(items[1].seen == 1); + assert_se(items[2].seen == 1); + assert_se(items[3].seen == 0); +} + +TEST(set_put) { + _cleanup_ordered_set_free_ OrderedSet *m = NULL; + _cleanup_free_ char **t = NULL, *str = NULL; + + m = ordered_set_new(&string_hash_ops); + assert_se(m); + + assert_se(ordered_set_put(m, (void*) "1") == 1); + assert_se(ordered_set_put(m, (void*) "22") == 1); + assert_se(ordered_set_put(m, (void*) "333") == 1); + assert_se(ordered_set_put(m, (void*) "333") == 0); + assert_se(ordered_set_remove(m, (void*) "333")); + assert_se(ordered_set_put(m, (void*) "333") == 1); + assert_se(ordered_set_put(m, (void*) "333") == 0); + assert_se(ordered_set_put(m, (void*) "22") == 0); + + assert_se(str = strdup("333")); + assert_se(ordered_set_put(m, str) == -EEXIST); /* ... and we get -EEXIST here, because we use + * non-trivial hash ops. */ + + assert_se(t = ordered_set_get_strv(m)); + assert_se(streq(t[0], "1")); + assert_se(streq(t[1], "22")); + assert_se(streq(t[2], "333")); + assert_se(!t[3]); + + ordered_set_print(stdout, "FOO=", m); +} + +TEST(set_put_string_set) { + _cleanup_ordered_set_free_ OrderedSet *m = NULL, *q = NULL; + _cleanup_free_ char **final = NULL; /* "just free" because the strings are in the set */ + + assert_se(ordered_set_put_strdup(&m, "1") == 1); + assert_se(ordered_set_put_strdup(&m, "22") == 1); + assert_se(ordered_set_put_strdup(&m, "333") == 1); + + assert_se(ordered_set_put_strdup(&q, "11") == 1); + assert_se(ordered_set_put_strdup(&q, "22") == 1); + assert_se(ordered_set_put_strdup(&q, "33") == 1); + + assert_se(ordered_set_put_string_set(&m, q) == 2); + + assert_se(final = ordered_set_get_strv(m)); + assert_se(strv_equal(final, STRV_MAKE("1", "22", "333", "11", "33"))); + + ordered_set_print(stdout, "BAR=", m); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-os-util.c b/src/test/test-os-util.c new file mode 100644 index 0000000..84e55e1 --- /dev/null +++ b/src/test/test-os-util.c @@ -0,0 +1,135 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> +#include "fileio.h" +#include "fs-util.h" +#include "log.h" +#include "mkdir.h" +#include "os-util.h" +#include "path-util.h" +#include "rm-rf.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" +#include "tmpfile-util.h" + +TEST(path_is_os_tree) { + assert_se(path_is_os_tree("/") > 0); + assert_se(path_is_os_tree("/etc") == 0); + assert_se(path_is_os_tree("/idontexist") == -ENOENT); +} + +TEST(parse_os_release) { + /* Let's assume that we're running in a valid system, so os-release is available */ + _cleanup_free_ char *id = NULL, *id2 = NULL, *name = NULL, *foobar = NULL; + assert_se(parse_os_release(NULL, "ID", &id) == 0); + log_info("ID: %s", id); + + assert_se(setenv("SYSTEMD_OS_RELEASE", "/dev/null", 1) == 0); + assert_se(parse_os_release(NULL, "ID", &id2) == 0); + log_info("ID: %s", strnull(id2)); + + _cleanup_(unlink_tempfilep) char tmpfile[] = "/tmp/test-os-util.XXXXXX"; + assert_se(write_tmpfile(tmpfile, + "ID=the-id \n" + "NAME=the-name") == 0); + + assert_se(setenv("SYSTEMD_OS_RELEASE", tmpfile, 1) == 0); + assert_se(parse_os_release(NULL, "ID", &id, "NAME", &name) == 0); + log_info("ID: %s NAME: %s", id, name); + assert_se(streq(id, "the-id")); + assert_se(streq(name, "the-name")); + + _cleanup_(unlink_tempfilep) char tmpfile2[] = "/tmp/test-os-util.XXXXXX"; + assert_se(write_tmpfile(tmpfile2, + "ID=\"ignored\" \n" + "ID=\"the-id\" \n" + "NAME='the-name'") == 0); + + assert_se(setenv("SYSTEMD_OS_RELEASE", tmpfile2, 1) == 0); + assert_se(parse_os_release(NULL, "ID", &id, "NAME", &name) == 0); + log_info("ID: %s NAME: %s", id, name); + assert_se(streq(id, "the-id")); + assert_se(streq(name, "the-name")); + + assert_se(parse_os_release(NULL, "FOOBAR", &foobar) == 0); + log_info("FOOBAR: %s", strnull(foobar)); + assert_se(foobar == NULL); + + assert_se(unsetenv("SYSTEMD_OS_RELEASE") == 0); +} + +TEST(parse_extension_release) { + /* Let's assume that we have a valid extension image */ + _cleanup_free_ char *id = NULL, *version_id = NULL, *foobar = NULL, *a = NULL, *b = NULL; + _cleanup_(rm_rf_physical_and_freep) char *tempdir = NULL; + + int r = mkdtemp_malloc("/tmp/test-os-util.XXXXXX", &tempdir); + if (r < 0) + log_error_errno(r, "Failed to setup working directory: %m"); + + assert_se(a = path_join(tempdir, "/usr/lib/extension-release.d/extension-release.test")); + assert_se(mkdir_parents(a, 0777) >= 0); + + r = write_string_file(a, "ID=the-id \n VERSION_ID=the-version-id", WRITE_STRING_FILE_CREATE); + if (r < 0) + log_error_errno(r, "Failed to write file: %m"); + + assert_se(parse_extension_release(tempdir, IMAGE_SYSEXT, "test", false, "ID", &id, "VERSION_ID", &version_id) == 0); + log_info("ID: %s VERSION_ID: %s", id, version_id); + assert_se(streq(id, "the-id")); + assert_se(streq(version_id, "the-version-id")); + + assert_se(b = path_join(tempdir, "/etc/extension-release.d/extension-release.tester")); + assert_se(mkdir_parents(b, 0777) >= 0); + + r = write_string_file(b, "ID=\"ignored\" \n ID=\"the-id\" \n VERSION_ID='the-version-id'", WRITE_STRING_FILE_CREATE); + if (r < 0) + log_error_errno(r, "Failed to write file: %m"); + + assert_se(parse_extension_release(tempdir, IMAGE_CONFEXT, "tester", false, "ID", &id, "VERSION_ID", &version_id) == 0); + log_info("ID: %s VERSION_ID: %s", id, version_id); + assert_se(streq(id, "the-id")); + assert_se(streq(version_id, "the-version-id")); + + assert_se(parse_extension_release(tempdir, IMAGE_CONFEXT, "tester", false, "FOOBAR", &foobar) == 0); + log_info("FOOBAR: %s", strnull(foobar)); + assert_se(foobar == NULL); + + assert_se(parse_extension_release(tempdir, IMAGE_SYSEXT, "test", false, "FOOBAR", &foobar) == 0); + log_info("FOOBAR: %s", strnull(foobar)); + assert_se(foobar == NULL); +} + +TEST(load_os_release_pairs) { + _cleanup_(unlink_tempfilep) char tmpfile[] = "/tmp/test-os-util.XXXXXX"; + assert_se(write_tmpfile(tmpfile, + "ID=\"ignored\" \n" + "ID=\"the-id\" \n" + "NAME='the-name'") == 0); + + assert_se(setenv("SYSTEMD_OS_RELEASE", tmpfile, 1) == 0); + + _cleanup_strv_free_ char **pairs = NULL; + assert_se(load_os_release_pairs(NULL, &pairs) == 0); + assert_se(strv_equal(pairs, STRV_MAKE("ID", "the-id", + "NAME", "the-name"))); + + assert_se(unsetenv("SYSTEMD_OS_RELEASE") == 0); +} + +TEST(os_release_support_ended) { + int r; + + assert_se(os_release_support_ended("1999-01-01", false, NULL) == true); + assert_se(os_release_support_ended("2037-12-31", false, NULL) == false); + assert_se(os_release_support_ended("-1-1-1", true, NULL) == -EINVAL); + + r = os_release_support_ended(NULL, false, NULL); + if (r < 0) + log_info_errno(r, "Failed to check host: %m"); + else + log_info_errno(r, "Host is supported: %s", yes_no(!r)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-parse-argument.c b/src/test/test-parse-argument.c new file mode 100644 index 0000000..cf3d542 --- /dev/null +++ b/src/test/test-parse-argument.c @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <signal.h> + +#include "parse-argument.h" +#include "stdio-util.h" +#include "tests.h" + +TEST(parse_json_argument) { + JsonFormatFlags flags = JSON_FORMAT_PRETTY; + + assert_se(parse_json_argument("help", &flags) == 0); + assert_se(flags == JSON_FORMAT_PRETTY); + + assert_se(parse_json_argument("off", &flags) == 1); + assert_se(flags == JSON_FORMAT_OFF); +} + +TEST(parse_path_argument) { + _cleanup_free_ char *path = NULL; + + assert_se(parse_path_argument("help", false, &path) == 0); + assert_se(streq(basename(path), "help")); + + assert_se(parse_path_argument("/", false, &path) == 0); + assert_se(streq(path, "/")); + + assert_se(parse_path_argument("/", true, &path) == 0); + assert_se(path == NULL); +} + +TEST(parse_signal_argument) { + int signal = -1; + + assert_se(parse_signal_argument("help", &signal) == 0); + assert_se(signal == -1); + + assert_se(parse_signal_argument("list", &signal) == 0); + assert_se(signal == -1); + + assert_se(parse_signal_argument("SIGABRT", &signal) == 1); + assert_se(signal == SIGABRT); + + assert_se(parse_signal_argument("ABRT", &signal) == 1); + assert_se(signal == SIGABRT); + + char buf[DECIMAL_STR_MAX(int)]; + xsprintf(buf, "%d", SIGABRT); + assert_se(parse_signal_argument(buf, &signal) == 1); + assert_se(signal == SIGABRT); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-parse-helpers.c b/src/test/test-parse-helpers.c new file mode 100644 index 0000000..052e251 --- /dev/null +++ b/src/test/test-parse-helpers.c @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <linux/in.h> +#include <sys/socket.h> +#include <stdio.h> + +#include "macro.h" +#include "parse-helpers.h" +#include "tests.h" + +static void test_valid_item( + const char *str, + int expected_af, + int expected_ip_protocol, + uint16_t expected_nr_ports, + uint16_t expected_port_min) { + uint16_t nr_ports, port_min; + int af, ip_protocol; + + assert_se(parse_socket_bind_item(str, &af, &ip_protocol, &nr_ports, &port_min) >= 0); + assert_se(af == expected_af); + assert_se(ip_protocol == expected_ip_protocol); + assert_se(nr_ports == expected_nr_ports); + assert_se(port_min == expected_port_min); + + log_info("%s: \"%s\" ok", __func__, str); +} + +static void test_invalid_item(const char *str) { + uint16_t nr_ports, port_min; + int af, ip_protocol; + + assert_se(parse_socket_bind_item(str, &af, &ip_protocol, &nr_ports, &port_min) == -EINVAL); + + log_info("%s: \"%s\" ok", __func__, str); +} + +TEST(valid_items) { + test_valid_item("any", AF_UNSPEC, 0, 0, 0); + test_valid_item("ipv4", AF_INET, 0, 0, 0); + test_valid_item("ipv6", AF_INET6, 0, 0, 0); + test_valid_item("ipv4:any", AF_INET, 0, 0, 0); + test_valid_item("ipv6:any", AF_INET6, 0, 0, 0); + test_valid_item("tcp", AF_UNSPEC, IPPROTO_TCP, 0, 0); + test_valid_item("udp", AF_UNSPEC, IPPROTO_UDP, 0, 0); + test_valid_item("tcp:any", AF_UNSPEC, IPPROTO_TCP, 0, 0); + test_valid_item("udp:any", AF_UNSPEC, IPPROTO_UDP, 0, 0); + test_valid_item("6666", AF_UNSPEC, 0, 1, 6666); + test_valid_item("6666-6667", AF_UNSPEC, 0, 2, 6666); + test_valid_item("65535", AF_UNSPEC, 0, 1, 65535); + test_valid_item("1-65535", AF_UNSPEC, 0, 65535, 1); + test_valid_item("ipv4:tcp", AF_INET, IPPROTO_TCP, 0, 0); + test_valid_item("ipv4:udp", AF_INET, IPPROTO_UDP, 0, 0); + test_valid_item("ipv6:tcp", AF_INET6, IPPROTO_TCP, 0, 0); + test_valid_item("ipv6:udp", AF_INET6, IPPROTO_UDP, 0, 0); + test_valid_item("ipv4:6666", AF_INET, 0, 1, 6666); + test_valid_item("ipv6:6666", AF_INET6, 0, 1, 6666); + test_valid_item("tcp:6666", AF_UNSPEC, IPPROTO_TCP, 1, 6666); + test_valid_item("udp:6666", AF_UNSPEC, IPPROTO_UDP, 1, 6666); + test_valid_item("ipv4:tcp:6666", AF_INET, IPPROTO_TCP, 1, 6666); + test_valid_item("ipv6:tcp:6666", AF_INET6, IPPROTO_TCP, 1, 6666); + test_valid_item("ipv6:udp:6666-6667", AF_INET6, IPPROTO_UDP, 2, 6666); + test_valid_item("ipv6:tcp:any", AF_INET6, IPPROTO_TCP, 0, 0); +} + +TEST(invalid_items) { + test_invalid_item(""); + test_invalid_item(":"); + test_invalid_item("::"); + test_invalid_item("any:"); + test_invalid_item("meh"); + test_invalid_item("zupa:meh"); + test_invalid_item("zupa:meh:eh"); + test_invalid_item("ip"); + test_invalid_item("dccp"); + test_invalid_item("ipv6meh"); + test_invalid_item("ipv6::"); + test_invalid_item("ipv6:ipv6"); + test_invalid_item("ipv6:icmp"); + test_invalid_item("ipv6:tcp:0"); + test_invalid_item("65536"); + test_invalid_item("0-65535"); + test_invalid_item("ipv6:tcp:6666-6665"); + test_invalid_item("ipv6:tcp:6666-100000"); + test_invalid_item("ipv6::6666"); + test_invalid_item("ipv6:tcp:any:"); + test_invalid_item("ipv6:tcp:any:ipv6"); + test_invalid_item("ipv6:tcp:6666:zupa"); + test_invalid_item("ipv6:tcp:6666:any"); + test_invalid_item("ipv6:tcp:6666 zupa"); + test_invalid_item("ipv6:tcp:6666: zupa"); + test_invalid_item("ipv6:tcp:6666\n zupa"); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-parse-util.c b/src/test/test-parse-util.c new file mode 100644 index 0000000..58d22b6 --- /dev/null +++ b/src/test/test-parse-util.c @@ -0,0 +1,979 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> +#include <locale.h> +#include <math.h> +#include <sys/socket.h> + +#include "alloc-util.h" +#include "errno-list.h" +#include "log.h" +#include "parse-util.h" +#include "string-util.h" +#include "tests.h" + +TEST(parse_boolean) { + assert_se(parse_boolean("1") == 1); + assert_se(parse_boolean("y") == 1); + assert_se(parse_boolean("Y") == 1); + assert_se(parse_boolean("yes") == 1); + assert_se(parse_boolean("YES") == 1); + assert_se(parse_boolean("true") == 1); + assert_se(parse_boolean("TRUE") == 1); + assert_se(parse_boolean("on") == 1); + assert_se(parse_boolean("ON") == 1); + + assert_se(parse_boolean("0") == 0); + assert_se(parse_boolean("n") == 0); + assert_se(parse_boolean("N") == 0); + assert_se(parse_boolean("no") == 0); + assert_se(parse_boolean("NO") == 0); + assert_se(parse_boolean("false") == 0); + assert_se(parse_boolean("FALSE") == 0); + assert_se(parse_boolean("off") == 0); + assert_se(parse_boolean("OFF") == 0); + + assert_se(parse_boolean("garbage") < 0); + assert_se(parse_boolean("") < 0); + assert_se(parse_boolean("full") < 0); +} + +TEST(parse_pid) { + int r; + pid_t pid; + + r = parse_pid("100", &pid); + assert_se(r == 0); + assert_se(pid == 100); + + r = parse_pid("0x7FFFFFFF", &pid); + assert_se(r == 0); + assert_se(pid == 2147483647); + + pid = 65; /* pid is left unchanged on ERANGE. Set to known arbitrary value. */ + r = parse_pid("0", &pid); + assert_se(r == -ERANGE); + assert_se(pid == 65); + + pid = 65; /* pid is left unchanged on ERANGE. Set to known arbitrary value. */ + r = parse_pid("-100", &pid); + assert_se(r == -ERANGE); + assert_se(pid == 65); + + pid = 65; /* pid is left unchanged on ERANGE. Set to known arbitrary value. */ + r = parse_pid("0xFFFFFFFFFFFFFFFFF", &pid); + assert_se(r == -ERANGE); + assert_se(pid == 65); + + r = parse_pid("junk", &pid); + assert_se(r == -EINVAL); + + r = parse_pid("", &pid); + assert_se(r == -EINVAL); +} + +TEST(parse_mode) { + mode_t m; + + assert_se(parse_mode("-1", &m) < 0); + assert_se(parse_mode("+1", &m) < 0); + assert_se(parse_mode("", &m) < 0); + assert_se(parse_mode("888", &m) < 0); + assert_se(parse_mode("77777", &m) < 0); + + assert_se(parse_mode("544", &m) >= 0 && m == 0544); + assert_se(parse_mode("0544", &m) >= 0 && m == 0544); + assert_se(parse_mode("00544", &m) >= 0 && m == 0544); + assert_se(parse_mode("777", &m) >= 0 && m == 0777); + assert_se(parse_mode("0777", &m) >= 0 && m == 0777); + assert_se(parse_mode("00777", &m) >= 0 && m == 0777); + assert_se(parse_mode("7777", &m) >= 0 && m == 07777); + assert_se(parse_mode("07777", &m) >= 0 && m == 07777); + assert_se(parse_mode("007777", &m) >= 0 && m == 07777); + assert_se(parse_mode("0", &m) >= 0 && m == 0); + assert_se(parse_mode(" 1", &m) >= 0 && m == 1); +} + +TEST(parse_size_iec) { + uint64_t bytes; + + assert_se(parse_size("", 1024, &bytes) == -EINVAL); + + assert_se(parse_size("111", 1024, &bytes) == 0); + assert_se(bytes == 111); + + assert_se(parse_size("111.4", 1024, &bytes) == 0); + assert_se(bytes == 111); + + assert_se(parse_size(" 112 B", 1024, &bytes) == 0); + assert_se(bytes == 112); + + assert_se(parse_size(" 112.6 B", 1024, &bytes) == 0); + assert_se(bytes == 112); + + assert_se(parse_size("3.5 K", 1024, &bytes) == 0); + assert_se(bytes == 3*1024 + 512); + + assert_se(parse_size("3. K", 1024, &bytes) == 0); + assert_se(bytes == 3*1024); + + assert_se(parse_size("3.0 K", 1024, &bytes) == 0); + assert_se(bytes == 3*1024); + + assert_se(parse_size("3. 0 K", 1024, &bytes) == -EINVAL); + + assert_se(parse_size(" 4 M 11.5K", 1024, &bytes) == 0); + assert_se(bytes == 4*1024*1024 + 11 * 1024 + 512); + + assert_se(parse_size("3B3.5G", 1024, &bytes) == -EINVAL); + + assert_se(parse_size("3.5G3B", 1024, &bytes) == 0); + assert_se(bytes == 3ULL*1024*1024*1024 + 512*1024*1024 + 3); + + assert_se(parse_size("3.5G 4B", 1024, &bytes) == 0); + assert_se(bytes == 3ULL*1024*1024*1024 + 512*1024*1024 + 4); + + assert_se(parse_size("3B3G4T", 1024, &bytes) == -EINVAL); + + assert_se(parse_size("4T3G3B", 1024, &bytes) == 0); + assert_se(bytes == (4ULL*1024 + 3)*1024*1024*1024 + 3); + + assert_se(parse_size(" 4 T 3 G 3 B", 1024, &bytes) == 0); + assert_se(bytes == (4ULL*1024 + 3)*1024*1024*1024 + 3); + + assert_se(parse_size("12P", 1024, &bytes) == 0); + assert_se(bytes == 12ULL * 1024*1024*1024*1024*1024); + + assert_se(parse_size("12P12P", 1024, &bytes) == -EINVAL); + + assert_se(parse_size("3E 2P", 1024, &bytes) == 0); + assert_se(bytes == (3 * 1024 + 2ULL) * 1024*1024*1024*1024*1024); + + assert_se(parse_size("12X", 1024, &bytes) == -EINVAL); + + assert_se(parse_size("12.5X", 1024, &bytes) == -EINVAL); + + assert_se(parse_size("12.5e3", 1024, &bytes) == -EINVAL); + + assert_se(parse_size("1024E", 1024, &bytes) == -ERANGE); + assert_se(parse_size("-1", 1024, &bytes) == -ERANGE); + assert_se(parse_size("-1024E", 1024, &bytes) == -ERANGE); + + assert_se(parse_size("-1024P", 1024, &bytes) == -ERANGE); + + assert_se(parse_size("-10B 20K", 1024, &bytes) == -ERANGE); +} + +TEST(parse_size_si) { + uint64_t bytes; + + assert_se(parse_size("", 1000, &bytes) == -EINVAL); + + assert_se(parse_size("111", 1000, &bytes) == 0); + assert_se(bytes == 111); + + assert_se(parse_size("111.4", 1000, &bytes) == 0); + assert_se(bytes == 111); + + assert_se(parse_size(" 112 B", 1000, &bytes) == 0); + assert_se(bytes == 112); + + assert_se(parse_size(" 112.6 B", 1000, &bytes) == 0); + assert_se(bytes == 112); + + assert_se(parse_size("3.5 K", 1000, &bytes) == 0); + assert_se(bytes == 3*1000 + 500); + + assert_se(parse_size("3. K", 1000, &bytes) == 0); + assert_se(bytes == 3*1000); + + assert_se(parse_size("3.0 K", 1000, &bytes) == 0); + assert_se(bytes == 3*1000); + + assert_se(parse_size("3. 0 K", 1000, &bytes) == -EINVAL); + + assert_se(parse_size(" 4 M 11.5K", 1000, &bytes) == 0); + assert_se(bytes == 4*1000*1000 + 11 * 1000 + 500); + + assert_se(parse_size("3B3.5G", 1000, &bytes) == -EINVAL); + + assert_se(parse_size("3.5G3B", 1000, &bytes) == 0); + assert_se(bytes == 3ULL*1000*1000*1000 + 500*1000*1000 + 3); + + assert_se(parse_size("3.5G 4B", 1000, &bytes) == 0); + assert_se(bytes == 3ULL*1000*1000*1000 + 500*1000*1000 + 4); + + assert_se(parse_size("3B3G4T", 1000, &bytes) == -EINVAL); + + assert_se(parse_size("4T3G3B", 1000, &bytes) == 0); + assert_se(bytes == (4ULL*1000 + 3)*1000*1000*1000 + 3); + + assert_se(parse_size(" 4 T 3 G 3 B", 1000, &bytes) == 0); + assert_se(bytes == (4ULL*1000 + 3)*1000*1000*1000 + 3); + + assert_se(parse_size("12P", 1000, &bytes) == 0); + assert_se(bytes == 12ULL * 1000*1000*1000*1000*1000); + + assert_se(parse_size("12P12P", 1000, &bytes) == -EINVAL); + + assert_se(parse_size("3E 2P", 1000, &bytes) == 0); + assert_se(bytes == (3 * 1000 + 2ULL) * 1000*1000*1000*1000*1000); + + assert_se(parse_size("12X", 1000, &bytes) == -EINVAL); + + assert_se(parse_size("12.5X", 1000, &bytes) == -EINVAL); + + assert_se(parse_size("12.5e3", 1000, &bytes) == -EINVAL); + + assert_se(parse_size("1000E", 1000, &bytes) == -ERANGE); + assert_se(parse_size("-1", 1000, &bytes) == -ERANGE); + assert_se(parse_size("-1000E", 1000, &bytes) == -ERANGE); + + assert_se(parse_size("-1000P", 1000, &bytes) == -ERANGE); + + assert_se(parse_size("-10B 20K", 1000, &bytes) == -ERANGE); +} + +TEST(parse_range) { + unsigned lower, upper; + + /* Successful cases */ + assert_se(parse_range("111", &lower, &upper) == 0); + assert_se(lower == 111); + assert_se(upper == 111); + + assert_se(parse_range("111-123", &lower, &upper) == 0); + assert_se(lower == 111); + assert_se(upper == 123); + + assert_se(parse_range("123-111", &lower, &upper) == 0); + assert_se(lower == 123); + assert_se(upper == 111); + + assert_se(parse_range("123-123", &lower, &upper) == 0); + assert_se(lower == 123); + assert_se(upper == 123); + + assert_se(parse_range("0", &lower, &upper) == 0); + assert_se(lower == 0); + assert_se(upper == 0); + + assert_se(parse_range("0-15", &lower, &upper) == 0); + assert_se(lower == 0); + assert_se(upper == 15); + + assert_se(parse_range("15-0", &lower, &upper) == 0); + assert_se(lower == 15); + assert_se(upper == 0); + + assert_se(parse_range("128-65535", &lower, &upper) == 0); + assert_se(lower == 128); + assert_se(upper == 65535); + + assert_se(parse_range("1024-4294967295", &lower, &upper) == 0); + assert_se(lower == 1024); + assert_se(upper == 4294967295); + + /* Leading whitespace is acceptable */ + assert_se(parse_range(" 111", &lower, &upper) == 0); + assert_se(lower == 111); + assert_se(upper == 111); + + assert_se(parse_range(" 111-123", &lower, &upper) == 0); + assert_se(lower == 111); + assert_se(upper == 123); + + assert_se(parse_range("111- 123", &lower, &upper) == 0); + assert_se(lower == 111); + assert_se(upper == 123); + + assert_se(parse_range("\t111-\t123", &lower, &upper) == 0); + assert_se(lower == 111); + assert_se(upper == 123); + + assert_se(parse_range(" \t 111- \t 123", &lower, &upper) == 0); + assert_se(lower == 111); + assert_se(upper == 123); + + /* Error cases, make sure they fail as expected */ + lower = upper = 9999; + assert_se(parse_range("111garbage", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + assert_se(parse_range("garbage111", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + assert_se(parse_range("garbage", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + assert_se(parse_range("111-123garbage", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + assert_se(parse_range("111garbage-123", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + /* Empty string */ + lower = upper = 9999; + assert_se(parse_range("", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + /* 111--123 will pass -123 to safe_atou which returns -ERANGE for negative */ + assert_se(parse_range("111--123", &lower, &upper) == -ERANGE); + assert_se(lower == 9999); + assert_se(upper == 9999); + + assert_se(parse_range("-123", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + assert_se(parse_range("-111-123", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + assert_se(parse_range("111-123-", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + assert_se(parse_range("111.4-123", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + assert_se(parse_range("111-123.4", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + assert_se(parse_range("111,4-123", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + assert_se(parse_range("111-123,4", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + /* Error on trailing dash */ + assert_se(parse_range("111-", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + assert_se(parse_range("111-123-", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + assert_se(parse_range("111--", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + assert_se(parse_range("111- ", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + /* Whitespace is not a separator */ + assert_se(parse_range("111 123", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + assert_se(parse_range("111\t123", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + assert_se(parse_range("111 \t 123", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + /* Trailing whitespace is invalid (from safe_atou) */ + assert_se(parse_range("111 ", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + assert_se(parse_range("111-123 ", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + assert_se(parse_range("111 -123", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + assert_se(parse_range("111 -123 ", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + assert_se(parse_range("111\t-123\t", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + assert_se(parse_range("111 \t -123 \t ", &lower, &upper) == -EINVAL); + assert_se(lower == 9999); + assert_se(upper == 9999); + + /* Out of the "unsigned" range, this is 1<<64 */ + assert_se(parse_range("0-18446744073709551616", &lower, &upper) == -ERANGE); + assert_se(lower == 9999); + assert_se(upper == 9999); +} + +TEST(safe_atou_bounded) { + int r; + unsigned x; + + r = safe_atou_bounded("12345", 12, 20000, &x); + assert_se(r == 0); + assert_se(x == 12345); + + r = safe_atou_bounded("12", 12, 20000, &x); + assert_se(r == 0); + assert_se(x == 12); + + r = safe_atou_bounded("20000", 12, 20000, &x); + assert_se(r == 0); + assert_se(x == 20000); + + r = safe_atou_bounded("-1", 12, 20000, &x); + assert_se(r == -ERANGE); + + r = safe_atou_bounded("11", 12, 20000, &x); + assert_se(r == -ERANGE); + + r = safe_atou_bounded("20001", 12, 20000, &x); + assert_se(r == -ERANGE); +} + +TEST(safe_atolli) { + int r; + long long l; + + r = safe_atolli("12345", &l); + assert_se(r == 0); + assert_se(l == 12345); + + r = safe_atolli(" 12345", &l); + assert_se(r == 0); + assert_se(l == 12345); + + r = safe_atolli("-12345", &l); + assert_se(r == 0); + assert_se(l == -12345); + + r = safe_atolli(" -12345", &l); + assert_se(r == 0); + assert_se(l == -12345); + + r = safe_atolli("0x5", &l); + assert_se(r == 0); + assert_se(l == 5); + + r = safe_atolli("0o6", &l); + assert_se(r == 0); + assert_se(l == 6); + + r = safe_atolli("0B101", &l); + assert_se(r == 0); + assert_se(l == 5); + + r = safe_atolli("12345678901234567890", &l); + assert_se(r == -ERANGE); + + r = safe_atolli("-12345678901234567890", &l); + assert_se(r == -ERANGE); + + r = safe_atolli("junk", &l); + assert_se(r == -EINVAL); + + r = safe_atolli("123x", &l); + assert_se(r == -EINVAL); + + r = safe_atolli("12.3", &l); + assert_se(r == -EINVAL); + + r = safe_atolli("", &l); + assert_se(r == -EINVAL); +} + +TEST(safe_atou16) { + int r; + uint16_t l; + + r = safe_atou16("12345", &l); + assert_se(r == 0); + assert_se(l == 12345); + + r = safe_atou16(" 12345", &l); + assert_se(r == 0); + assert_se(l == 12345); + + r = safe_atou16("+12345", &l); + assert_se(r == 0); + assert_se(l == 12345); + + r = safe_atou16(" +12345", &l); + assert_se(r == 0); + assert_se(l == 12345); + + r = safe_atou16("123456", &l); + assert_se(r == -ERANGE); + + r = safe_atou16("-1", &l); + assert_se(r == -ERANGE); + + r = safe_atou16(" -1", &l); + assert_se(r == -ERANGE); + + r = safe_atou16("junk", &l); + assert_se(r == -EINVAL); + + r = safe_atou16("123x", &l); + assert_se(r == -EINVAL); + + r = safe_atou16("12.3", &l); + assert_se(r == -EINVAL); + + r = safe_atou16("", &l); + assert_se(r == -EINVAL); +} + +TEST(safe_atoi16) { + int r; + int16_t l; + + r = safe_atoi16("-12345", &l); + assert_se(r == 0); + assert_se(l == -12345); + + r = safe_atoi16(" -12345", &l); + assert_se(r == 0); + assert_se(l == -12345); + + r = safe_atoi16("+12345", &l); + assert_se(r == 0); + assert_se(l == 12345); + + r = safe_atoi16(" +12345", &l); + assert_se(r == 0); + assert_se(l == 12345); + + r = safe_atoi16("32767", &l); + assert_se(r == 0); + assert_se(l == 32767); + + r = safe_atoi16(" 32767", &l); + assert_se(r == 0); + assert_se(l == 32767); + + r = safe_atoi16("0o11", &l); + assert_se(r == 0); + assert_se(l == 9); + + r = safe_atoi16("0B110", &l); + assert_se(r == 0); + assert_se(l == 6); + + r = safe_atoi16("36536", &l); + assert_se(r == -ERANGE); + + r = safe_atoi16("-32769", &l); + assert_se(r == -ERANGE); + + r = safe_atoi16("junk", &l); + assert_se(r == -EINVAL); + + r = safe_atoi16("123x", &l); + assert_se(r == -EINVAL); + + r = safe_atoi16("12.3", &l); + assert_se(r == -EINVAL); + + r = safe_atoi16("", &l); + assert_se(r == -EINVAL); +} + +TEST(safe_atoux16) { + int r; + uint16_t l; + + r = safe_atoux16("1234", &l); + assert_se(r == 0); + assert_se(l == 0x1234); + + r = safe_atoux16("abcd", &l); + assert_se(r == 0); + assert_se(l == 0xabcd); + + r = safe_atoux16(" 1234", &l); + assert_se(r == 0); + assert_se(l == 0x1234); + + r = safe_atoux16("12345", &l); + assert_se(r == -ERANGE); + + r = safe_atoux16("-1", &l); + assert_se(r == -ERANGE); + + r = safe_atoux16(" -1", &l); + assert_se(r == -ERANGE); + + r = safe_atoux16("0b1", &l); + assert_se(r == 0); + assert_se(l == 177); + + r = safe_atoux16("0o70", &l); + assert_se(r == -EINVAL); + + r = safe_atoux16("junk", &l); + assert_se(r == -EINVAL); + + r = safe_atoux16("123x", &l); + assert_se(r == -EINVAL); + + r = safe_atoux16("12.3", &l); + assert_se(r == -EINVAL); + + r = safe_atoux16("", &l); + assert_se(r == -EINVAL); +} + +TEST(safe_atou64) { + int r; + uint64_t l; + + r = safe_atou64("12345", &l); + assert_se(r == 0); + assert_se(l == 12345); + + r = safe_atou64(" 12345", &l); + assert_se(r == 0); + assert_se(l == 12345); + + r = safe_atou64("0o11", &l); + assert_se(r == 0); + assert_se(l == 9); + + r = safe_atou64("0b11", &l); + assert_se(r == 0); + assert_se(l == 3); + + r = safe_atou64("18446744073709551617", &l); + assert_se(r == -ERANGE); + + r = safe_atou64("-1", &l); + assert_se(r == -ERANGE); + + r = safe_atou64(" -1", &l); + assert_se(r == -ERANGE); + + r = safe_atou64("junk", &l); + assert_se(r == -EINVAL); + + r = safe_atou64("123x", &l); + assert_se(r == -EINVAL); + + r = safe_atou64("12.3", &l); + assert_se(r == -EINVAL); + + r = safe_atou64("", &l); + assert_se(r == -EINVAL); +} + +TEST(safe_atoi64) { + int r; + int64_t l; + + r = safe_atoi64("-12345", &l); + assert_se(r == 0); + assert_se(l == -12345); + + r = safe_atoi64(" -12345", &l); + assert_se(r == 0); + assert_se(l == -12345); + + r = safe_atoi64("32767", &l); + assert_se(r == 0); + assert_se(l == 32767); + + r = safe_atoi64(" 32767", &l); + assert_se(r == 0); + assert_se(l == 32767); + + r = safe_atoi64(" 0o20", &l); + assert_se(r == 0); + assert_se(l == 16); + + r = safe_atoi64(" 0b01010", &l); + assert_se(r == 0); + assert_se(l == 10); + + r = safe_atoi64("9223372036854775813", &l); + assert_se(r == -ERANGE); + + r = safe_atoi64("-9223372036854775813", &l); + assert_se(r == -ERANGE); + + r = safe_atoi64("junk", &l); + assert_se(r == -EINVAL); + + r = safe_atoi64("123x", &l); + assert_se(r == -EINVAL); + + r = safe_atoi64("12.3", &l); + assert_se(r == -EINVAL); + + r = safe_atoi64("", &l); + assert_se(r == -EINVAL); +} + +TEST(safe_atoux64) { + int r; + uint64_t l; + + r = safe_atoux64("12345", &l); + assert_se(r == 0); + assert_se(l == 0x12345); + + r = safe_atoux64(" 12345", &l); + assert_se(r == 0); + assert_se(l == 0x12345); + + r = safe_atoux64("0x12345", &l); + assert_se(r == 0); + assert_se(l == 0x12345); + + r = safe_atoux64("0b11011", &l); + assert_se(r == 0); + assert_se(l == 11603985); + + r = safe_atoux64("+12345", &l); + assert_se(r == 0); + assert_se(l == 0x12345); + + r = safe_atoux64(" +12345", &l); + assert_se(r == 0); + assert_se(l == 0x12345); + + r = safe_atoux64("+0x12345", &l); + assert_se(r == 0); + assert_se(l == 0x12345); + + r = safe_atoux64("+0b11011", &l); + assert_se(r == 0); + assert_se(l == 11603985); + + r = safe_atoux64("0o11011", &l); + assert_se(r == -EINVAL); + + r = safe_atoux64("18446744073709551617", &l); + assert_se(r == -ERANGE); + + r = safe_atoux64("-1", &l); + assert_se(r == -ERANGE); + + r = safe_atoux64(" -1", &l); + assert_se(r == -ERANGE); + + r = safe_atoux64("junk", &l); + assert_se(r == -EINVAL); + + r = safe_atoux64("123x", &l); + assert_se(r == -EINVAL); + + r = safe_atoux64("12.3", &l); + assert_se(r == -EINVAL); + + r = safe_atoux64("", &l); + assert_se(r == -EINVAL); +} + +TEST(safe_atod) { + int r; + double d; + char *e; + + r = safe_atod("junk", &d); + assert_se(r == -EINVAL); + + r = safe_atod("0.2244", &d); + assert_se(r == 0); + assert_se(fabs(d - 0.2244) < 0.000001); + + r = safe_atod("0,5", &d); + assert_se(r == -EINVAL); + + errno = 0; + strtod("0,5", &e); + assert_se(*e == ','); + + r = safe_atod("", &d); + assert_se(r == -EINVAL); + + /* Check if this really is locale independent */ + if (setlocale(LC_NUMERIC, "de_DE.utf8")) { + + r = safe_atod("0.2244", &d); + assert_se(r == 0); + assert_se(fabs(d - 0.2244) < 0.000001); + + r = safe_atod("0,5", &d); + assert_se(r == -EINVAL); + + errno = 0; + assert_se(fabs(strtod("0,5", &e) - 0.5) < 0.00001); + + r = safe_atod("", &d); + assert_se(r == -EINVAL); + } + + /* And check again, reset */ + assert_se(setlocale(LC_NUMERIC, "C")); + + r = safe_atod("0.2244", &d); + assert_se(r == 0); + assert_se(fabs(d - 0.2244) < 0.000001); + + r = safe_atod("0,5", &d); + assert_se(r == -EINVAL); + + errno = 0; + strtod("0,5", &e); + assert_se(*e == ','); + + r = safe_atod("", &d); + assert_se(r == -EINVAL); +} + +TEST(parse_nice) { + int n; + + assert_se(parse_nice("0", &n) >= 0 && n == 0); + assert_se(parse_nice("+0", &n) >= 0 && n == 0); + assert_se(parse_nice("-1", &n) >= 0 && n == -1); + assert_se(parse_nice("-2", &n) >= 0 && n == -2); + assert_se(parse_nice("1", &n) >= 0 && n == 1); + assert_se(parse_nice("2", &n) >= 0 && n == 2); + assert_se(parse_nice("+1", &n) >= 0 && n == 1); + assert_se(parse_nice("+2", &n) >= 0 && n == 2); + assert_se(parse_nice("-20", &n) >= 0 && n == -20); + assert_se(parse_nice("19", &n) >= 0 && n == 19); + assert_se(parse_nice("+19", &n) >= 0 && n == 19); + + assert_se(parse_nice("", &n) == -EINVAL); + assert_se(parse_nice("-", &n) == -EINVAL); + assert_se(parse_nice("+", &n) == -EINVAL); + assert_se(parse_nice("xx", &n) == -EINVAL); + assert_se(parse_nice("-50", &n) == -ERANGE); + assert_se(parse_nice("50", &n) == -ERANGE); + assert_se(parse_nice("+50", &n) == -ERANGE); + assert_se(parse_nice("-21", &n) == -ERANGE); + assert_se(parse_nice("20", &n) == -ERANGE); + assert_se(parse_nice("+20", &n) == -ERANGE); +} + +TEST(parse_errno) { + assert_se(parse_errno("EILSEQ") == EILSEQ); + assert_se(parse_errno("EINVAL") == EINVAL); + assert_se(parse_errno("0") == 0); + assert_se(parse_errno("1") == 1); + assert_se(parse_errno("4095") == 4095); + + assert_se(parse_errno("-1") == -ERANGE); + assert_se(parse_errno("-3") == -ERANGE); + assert_se(parse_errno("4096") == -ERANGE); + + assert_se(parse_errno("") == -EINVAL); + assert_se(parse_errno("12.3") == -EINVAL); + assert_se(parse_errno("123junk") == -EINVAL); + assert_se(parse_errno("junk123") == -EINVAL); + assert_se(parse_errno("255EILSEQ") == -EINVAL); + assert_se(parse_errno("EINVAL12") == -EINVAL); + assert_se(parse_errno("-EINVAL") == -EINVAL); + assert_se(parse_errno("EINVALaaa") == -EINVAL); +} + +TEST(parse_fd) { + assert_se(parse_fd("0") == 0); + assert_se(parse_fd("1") == 1); + + assert_se(parse_fd("-1") == -EBADF); + assert_se(parse_fd("-3") == -EBADF); + + assert_se(parse_fd("") == -EINVAL); + assert_se(parse_fd("12.3") == -EINVAL); + assert_se(parse_fd("123junk") == -EINVAL); + assert_se(parse_fd("junk123") == -EINVAL); +} + +TEST(parse_mtu) { + uint32_t mtu = 0; + + assert_se(parse_mtu(AF_UNSPEC, "1500", &mtu) >= 0 && mtu == 1500); + assert_se(parse_mtu(AF_UNSPEC, "1400", &mtu) >= 0 && mtu == 1400); + assert_se(parse_mtu(AF_UNSPEC, "65535", &mtu) >= 0 && mtu == 65535); + assert_se(parse_mtu(AF_UNSPEC, "65536", &mtu) >= 0 && mtu == 65536); + assert_se(parse_mtu(AF_UNSPEC, "4294967295", &mtu) >= 0 && mtu == 4294967295); + assert_se(parse_mtu(AF_UNSPEC, "500", &mtu) >= 0 && mtu == 500); + assert_se(parse_mtu(AF_UNSPEC, "1280", &mtu) >= 0 && mtu == 1280); + assert_se(parse_mtu(AF_UNSPEC, "4294967296", &mtu) == -ERANGE); + assert_se(parse_mtu(AF_UNSPEC, "68", &mtu) >= 0 && mtu == 68); + assert_se(parse_mtu(AF_UNSPEC, "67", &mtu) >= 0 && mtu == 67); + assert_se(parse_mtu(AF_UNSPEC, "0", &mtu) >= 0 && mtu == 0); + assert_se(parse_mtu(AF_UNSPEC, "", &mtu) == -EINVAL); + + assert_se(parse_mtu(AF_INET, "1500", &mtu) >= 0 && mtu == 1500); + assert_se(parse_mtu(AF_INET, "1400", &mtu) >= 0 && mtu == 1400); + assert_se(parse_mtu(AF_INET, "65535", &mtu) >= 0 && mtu == 65535); + assert_se(parse_mtu(AF_INET, "65536", &mtu) >= 0 && mtu == 65536); + assert_se(parse_mtu(AF_INET, "4294967295", &mtu) >= 0 && mtu == 4294967295); + assert_se(parse_mtu(AF_INET, "500", &mtu) >= 0 && mtu == 500); + assert_se(parse_mtu(AF_INET, "1280", &mtu) >= 0 && mtu == 1280); + assert_se(parse_mtu(AF_INET, "4294967296", &mtu) == -ERANGE); + assert_se(parse_mtu(AF_INET, "68", &mtu) >= 0 && mtu == 68); + assert_se(parse_mtu(AF_INET, "67", &mtu) == -ERANGE); + assert_se(parse_mtu(AF_INET, "0", &mtu) == -ERANGE); + assert_se(parse_mtu(AF_INET, "", &mtu) == -EINVAL); + + assert_se(parse_mtu(AF_INET6, "1280", &mtu) >= 0 && mtu == 1280); + assert_se(parse_mtu(AF_INET6, "1279", &mtu) == -ERANGE); + assert_se(parse_mtu(AF_INET6, "4294967296", &mtu) == -ERANGE); + assert_se(parse_mtu(AF_INET6, "68", &mtu) == -ERANGE); + assert_se(parse_mtu(AF_INET6, "", &mtu) == -EINVAL); +} + +TEST(parse_loadavg_fixed_point) { + loadavg_t fp; + + assert_se(parse_loadavg_fixed_point("1.23", &fp) == 0); + assert_se(LOADAVG_INT_SIDE(fp) == 1); + assert_se(LOADAVG_DECIMAL_SIDE(fp) == 23); + + assert_se(parse_loadavg_fixed_point("1.80", &fp) == 0); + assert_se(LOADAVG_INT_SIDE(fp) == 1); + assert_se(LOADAVG_DECIMAL_SIDE(fp) == 80); + + assert_se(parse_loadavg_fixed_point("0.07", &fp) == 0); + assert_se(LOADAVG_INT_SIDE(fp) == 0); + assert_se(LOADAVG_DECIMAL_SIDE(fp) == 7); + + assert_se(parse_loadavg_fixed_point("0.00", &fp) == 0); + assert_se(LOADAVG_INT_SIDE(fp) == 0); + assert_se(LOADAVG_DECIMAL_SIDE(fp) == 0); + + assert_se(parse_loadavg_fixed_point("4096.57", &fp) == 0); + assert_se(LOADAVG_INT_SIDE(fp) == 4096); + assert_se(LOADAVG_DECIMAL_SIDE(fp) == 57); + + /* Caps out at 2 digit fracs */ + assert_se(parse_loadavg_fixed_point("1.100", &fp) == -ERANGE); + + assert_se(parse_loadavg_fixed_point("4096.4096", &fp) == -ERANGE); + assert_se(parse_loadavg_fixed_point("-4000.5", &fp) == -ERANGE); + assert_se(parse_loadavg_fixed_point("18446744073709551615.5", &fp) == -ERANGE); + assert_se(parse_loadavg_fixed_point("foobar", &fp) == -EINVAL); + assert_se(parse_loadavg_fixed_point("3333", &fp) == -EINVAL); + assert_se(parse_loadavg_fixed_point("1.2.3", &fp) == -EINVAL); + assert_se(parse_loadavg_fixed_point(".", &fp) == -EINVAL); + assert_se(parse_loadavg_fixed_point("", &fp) == -EINVAL); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-path-lookup.c b/src/test/test-path-lookup.c new file mode 100644 index 0000000..431a859 --- /dev/null +++ b/src/test/test-path-lookup.c @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdlib.h> +#include <sys/stat.h> + +#include "log.h" +#include "path-lookup.h" +#include "rm-rf.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" +#include "tmpfile-util.h" + +static void test_paths_one(RuntimeScope scope) { + _cleanup_(rm_rf_physical_and_freep) char *tmp = NULL; + _cleanup_(lookup_paths_free) LookupPaths lp_without_env = {}; + _cleanup_(lookup_paths_free) LookupPaths lp_with_env = {}; + char *systemd_unit_path; + + assert_se(mkdtemp_malloc("/tmp/test-path-lookup.XXXXXXX", &tmp) >= 0); + + assert_se(unsetenv("SYSTEMD_UNIT_PATH") == 0); + assert_se(lookup_paths_init(&lp_without_env, scope, 0, NULL) >= 0); + assert_se(!strv_isempty(lp_without_env.search_path)); + lookup_paths_log(&lp_without_env); + + systemd_unit_path = strjoina(tmp, "/systemd-unit-path"); + assert_se(setenv("SYSTEMD_UNIT_PATH", systemd_unit_path, 1) == 0); + assert_se(lookup_paths_init(&lp_with_env, scope, 0, NULL) == 0); + assert_se(strv_length(lp_with_env.search_path) == 1); + assert_se(streq(lp_with_env.search_path[0], systemd_unit_path)); + lookup_paths_log(&lp_with_env); + assert_se(strv_equal(lp_with_env.search_path, STRV_MAKE(systemd_unit_path))); +} + +TEST(paths) { + test_paths_one(RUNTIME_SCOPE_SYSTEM); + test_paths_one(RUNTIME_SCOPE_USER); + test_paths_one(RUNTIME_SCOPE_GLOBAL); +} + +TEST(user_and_global_paths) { + _cleanup_(lookup_paths_free) LookupPaths lp_global = {}, lp_user = {}; + char **u, **g; + unsigned k = 0; + + assert_se(unsetenv("SYSTEMD_UNIT_PATH") == 0); + assert_se(unsetenv("XDG_DATA_DIRS") == 0); + assert_se(unsetenv("XDG_CONFIG_DIRS") == 0); + + assert_se(lookup_paths_init(&lp_global, RUNTIME_SCOPE_GLOBAL, 0, NULL) == 0); + assert_se(lookup_paths_init(&lp_user, RUNTIME_SCOPE_USER, 0, NULL) == 0); + g = lp_global.search_path; + u = lp_user.search_path; + + /* Go over all entries in global search path, and verify + * that they also exist in the user search path. Skip any + * entries in user search path which don't exist in the global + * one, but not vice versa. */ + STRV_FOREACH(p, g) { + while (u[k] && !streq(*p, u[k])) { + log_info("+ %s", u[k]); + k++; + } + log_info(" %s", *p); + assert_se(u[k]); /* If NULL, we didn't find a matching entry */ + k++; + } + STRV_FOREACH(p, u + k) + log_info("+ %s", *p); +} + +static void test_generator_binary_paths_one(RuntimeScope scope) { + _cleanup_(rm_rf_physical_and_freep) char *tmp = NULL; + _cleanup_strv_free_ char **gp_without_env = NULL; + _cleanup_strv_free_ char **env_gp_without_env = NULL; + _cleanup_strv_free_ char **gp_with_env = NULL; + _cleanup_strv_free_ char **env_gp_with_env = NULL; + char *systemd_generator_path = NULL; + char *systemd_env_generator_path = NULL; + + assert_se(mkdtemp_malloc("/tmp/test-path-lookup.XXXXXXX", &tmp) >= 0); + + assert_se(unsetenv("SYSTEMD_GENERATOR_PATH") == 0); + assert_se(unsetenv("SYSTEMD_ENVIRONMENT_GENERATOR_PATH") == 0); + + gp_without_env = generator_binary_paths(scope); + env_gp_without_env = env_generator_binary_paths(scope); + + log_info("Generators dirs (%s):", runtime_scope_to_string(scope)); + STRV_FOREACH(dir, gp_without_env) + log_info(" %s", *dir); + + log_info("Environment generators dirs (%s):", runtime_scope_to_string(scope)); + STRV_FOREACH(dir, env_gp_without_env) + log_info(" %s", *dir); + + assert_se(!strv_isempty(gp_without_env)); + assert_se(!strv_isempty(env_gp_without_env)); + + systemd_generator_path = strjoina(tmp, "/systemd-generator-path"); + systemd_env_generator_path = strjoina(tmp, "/systemd-environment-generator-path"); + assert_se(setenv("SYSTEMD_GENERATOR_PATH", systemd_generator_path, 1) == 0); + assert_se(setenv("SYSTEMD_ENVIRONMENT_GENERATOR_PATH", systemd_env_generator_path, 1) == 0); + + gp_with_env = generator_binary_paths(scope); + env_gp_with_env = env_generator_binary_paths(scope); + + log_info("Generators dirs (%s):", runtime_scope_to_string(scope)); + STRV_FOREACH(dir, gp_with_env) + log_info(" %s", *dir); + + log_info("Environment generators dirs (%s):", runtime_scope_to_string(scope)); + STRV_FOREACH(dir, env_gp_with_env) + log_info(" %s", *dir); + + assert_se(strv_equal(gp_with_env, STRV_MAKE(systemd_generator_path))); + assert_se(strv_equal(env_gp_with_env, STRV_MAKE(systemd_env_generator_path))); +} + +TEST(generator_binary_paths) { + test_generator_binary_paths_one(RUNTIME_SCOPE_SYSTEM); + test_generator_binary_paths_one(RUNTIME_SCOPE_USER); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c new file mode 100644 index 0000000..f5a4256 --- /dev/null +++ b/src/test/test-path-util.c @@ -0,0 +1,1308 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdio.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "exec-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "macro.h" +#include "path-util.h" +#include "process-util.h" +#include "rm-rf.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" +#include "tmpfile-util.h" + +TEST(print_paths) { + log_info("DEFAULT_PATH=%s", DEFAULT_PATH); + log_info("DEFAULT_USER_PATH=%s", DEFAULT_USER_PATH); +} + +TEST(path) { + assert_se(path_is_absolute("/")); + assert_se(!path_is_absolute("./")); + + assert_se(streq(basename("./aa/bb/../file.da."), "file.da.")); + assert_se(streq(basename("/aa///.file"), ".file")); + assert_se(streq(basename("/aa///file..."), "file...")); + assert_se(streq(basename("file.../"), "")); + + assert_se(PATH_IN_SET("/bin", "/", "/bin", "/foo")); + assert_se(PATH_IN_SET("/bin", "/bin")); + assert_se(PATH_IN_SET("/bin", "/foo/bar", "/bin")); + assert_se(PATH_IN_SET("/", "/", "/", "/foo/bar")); + assert_se(!PATH_IN_SET("/", "/abc", "/def")); + + assert_se(path_equal_ptr(NULL, NULL)); + assert_se(path_equal_ptr("/a", "/a")); + assert_se(!path_equal_ptr("/a", "/b")); + assert_se(!path_equal_ptr("/a", NULL)); + assert_se(!path_equal_ptr(NULL, "/a")); +} + +TEST(is_path) { + assert_se(!is_path("foo")); + assert_se(!is_path("dos.ext")); + assert_se( is_path("/dir")); + assert_se( is_path("a/b")); + assert_se( is_path("a/b.ext")); + + assert_se(!is_path(".")); + assert_se(!is_path("")); + assert_se(!is_path("..")); + + assert_se( is_path("/dev")); + assert_se( is_path("/./dev")); + assert_se( is_path("/./dev/.")); + assert_se( is_path("/./dev.")); + assert_se( is_path("//dev")); + assert_se( is_path("///dev")); + assert_se( is_path("/dev/")); + assert_se( is_path("///dev/")); + assert_se( is_path("/./dev/")); + assert_se( is_path("/../dev/")); + assert_se( is_path("/dev/sda")); + assert_se( is_path("/dev/sda5")); + assert_se( is_path("/dev/sda5b3")); + assert_se( is_path("/dev/sda5b3/idontexit")); + assert_se( is_path("/../dev/sda")); + assert_se( is_path("/../../dev/sda5")); + assert_se( is_path("/../../../dev/sda5b3")); + assert_se( is_path("/.././.././dev/sda5b3/idontexit")); + assert_se( is_path("/sys")); + assert_se( is_path("/sys/")); + assert_se( is_path("/./sys")); + assert_se( is_path("/./sys/.")); + assert_se( is_path("/./sys.")); + assert_se( is_path("/sys/what")); + assert_se( is_path("/sys/something/..")); + assert_se( is_path("/sys/something/../")); + assert_se( is_path("/sys////")); + assert_se( is_path("/sys////.")); + assert_se( is_path("/sys/..")); + assert_se( is_path("/sys/../")); + assert_se( is_path("/usr/../dev/sda")); +} + +TEST(is_device_path) { + assert_se(!is_device_path("foo")); + assert_se(!is_device_path("dos.ext")); + assert_se(!is_device_path("/dir")); + assert_se(!is_device_path("a/b")); + assert_se(!is_device_path("a/b.ext")); + + assert_se(!is_device_path(".")); + assert_se(!is_device_path("")); + assert_se(!is_device_path("..")); + + assert_se(!is_device_path("/dev")); + assert_se(!is_device_path("/./dev")); + assert_se(!is_device_path("/./dev/.")); + assert_se(!is_device_path("/./dev.")); + assert_se( is_device_path("/./dev/foo")); + assert_se( is_device_path("/./dev/./foo")); + assert_se(!is_device_path("/./dev./foo")); + assert_se(!is_device_path("//dev")); + assert_se(!is_device_path("///dev")); + assert_se(!is_device_path("/dev/")); + assert_se(!is_device_path("///dev/")); + assert_se(!is_device_path("/./dev/")); + assert_se(!is_device_path("/../dev/")); + assert_se( is_device_path("/dev/sda")); + assert_se( is_device_path("/dev/sda5")); + assert_se( is_device_path("/dev/sda5b3")); + assert_se( is_device_path("/dev/sda5b3/idontexit")); + assert_se(!is_device_path("/../dev/sda")); + assert_se(!is_device_path("/../../dev/sda5")); + assert_se(!is_device_path("/../../../dev/sda5b3")); + assert_se(!is_device_path("/.././.././dev/sda5b3/idontexit")); + assert_se(!is_device_path("/sys")); + assert_se(!is_device_path("/sys/")); + assert_se(!is_device_path("/./sys")); + assert_se(!is_device_path("/./sys/.")); + assert_se(!is_device_path("/./sys.")); + assert_se( is_device_path("/./sys/foo")); + assert_se( is_device_path("/./sys/./foo")); + assert_se(!is_device_path("/./sys./foo")); + assert_se( is_device_path("/sys/what")); + assert_se( is_device_path("/sys/something/..")); + assert_se( is_device_path("/sys/something/../")); + assert_se(!is_device_path("/sys////")); + assert_se(!is_device_path("/sys////.")); + assert_se( is_device_path("/sys/..")); + assert_se( is_device_path("/sys/../")); + assert_se(!is_device_path("/usr/../dev/sda")); +} + +static void test_path_simplify_one(const char *in, const char *out, PathSimplifyFlags flags) { + char *p; + + p = strdupa_safe(in); + path_simplify_full(p, flags); + log_debug("/* test_path_simplify(%s) → %s (expected: %s) */", in, p, out); + assert_se(streq(p, out)); +} + +TEST(path_simplify) { + _cleanup_free_ char *hoge = NULL, *hoge_out = NULL; + char foo[NAME_MAX * 2]; + + test_path_simplify_one("", "", 0); + test_path_simplify_one("aaa/bbb////ccc", "aaa/bbb/ccc", 0); + test_path_simplify_one("//aaa/.////ccc", "/aaa/ccc", 0); + test_path_simplify_one("///", "/", 0); + test_path_simplify_one("///", "/", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("///.//", "/", 0); + test_path_simplify_one("///.//.///", "/", 0); + test_path_simplify_one("////.././///../.", "/", 0); + test_path_simplify_one(".", ".", 0); + test_path_simplify_one("./", ".", 0); + test_path_simplify_one("./", "./", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one(".///.//./.", ".", 0); + test_path_simplify_one(".///.//././/", ".", 0); + test_path_simplify_one("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", + "/aaa/.bbb/../c./d.dd/..eeee", 0); + test_path_simplify_one("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/..", + "/aaa/.bbb/../c./d.dd/..eeee/..", 0); + test_path_simplify_one(".//./aaa///.//./.bbb/..///c.//d.dd///..eeee/..", + "aaa/.bbb/../c./d.dd/..eeee/..", 0); + test_path_simplify_one("..//./aaa///.//./.bbb/..///c.//d.dd///..eeee/..", + "../aaa/.bbb/../c./d.dd/..eeee/..", 0); + test_path_simplify_one("abc///", "abc/", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + + test_path_simplify_one("/../abc", "/abc", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("/../abc///", "/abc/", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("/../abc///", "/abc", 0); + test_path_simplify_one("/../abc", "/abc", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("/../abc///..", "/abc/..", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("/../abc///../", "/abc/../", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("/../abc///../", "/abc/..", 0); + + test_path_simplify_one("/../../abc", "/abc", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("/../../abc///", "/abc/", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("/../../abc///", "/abc", 0); + test_path_simplify_one("/../../abc", "/abc", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("/../../abc///../..", "/abc/../..", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("/../../abc///../../", "/abc/../../", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("/../../abc///../../", "/abc/../..", 0); + + test_path_simplify_one("/.././../abc", "/abc", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("/.././../abc///", "/abc/", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("/.././../abc///", "/abc", 0); + test_path_simplify_one("/.././../abc", "/abc", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("/.././../abc///../..", "/abc/../..", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("/.././../abc///../../", "/abc/../../", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("/.././../abc///../../", "/abc/../..", 0); + + test_path_simplify_one("/./.././../abc", "/abc", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("/./.././../abc///", "/abc/", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("/./.././../abc///", "/abc", 0); + test_path_simplify_one("/./.././../abc", "/abc", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("/./.././../abc///../..", "/abc/../..", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("/./.././../abc///../../", "/abc/../../", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("/./.././../abc///../../", "/abc/../..", 0); + + test_path_simplify_one("/.../abc", "/.../abc", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("/.../abc///", "/.../abc/", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("/.../abc///", "/.../abc", 0); + test_path_simplify_one("/.../abc", "/.../abc", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("/.../abc///...", "/.../abc/...", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("/.../abc///.../", "/.../abc/.../", PATH_SIMPLIFY_KEEP_TRAILING_SLASH); + test_path_simplify_one("/.../abc///.../", "/.../abc/...", 0); + + memset(foo, 'a', sizeof(foo) -1); + char_array_0(foo); + + test_path_simplify_one(foo, foo, 0); + + hoge = strjoin("/", foo); + assert_se(hoge); + test_path_simplify_one(hoge, hoge, 0); + hoge = mfree(hoge); + + hoge = strjoin("a////.//././//./b///././/./c/////././//./", foo, "//.//////d/e/.//f/"); + assert_se(hoge); + + hoge_out = strjoin("a/b/c/", foo, "//.//////d/e/.//f/"); + assert_se(hoge_out); + + test_path_simplify_one(hoge, hoge_out, 0); +} + +static void test_path_compare_one(const char *a, const char *b, int expected) { + int r; + + assert_se(path_compare(a, a) == 0); + assert_se(path_compare(b, b) == 0); + + r = path_compare(a, b); + assert_se((r > 0) == (expected > 0) && (r < 0) == (expected < 0)); + r = path_compare(b, a); + assert_se((r < 0) == (expected > 0) && (r > 0) == (expected < 0)); + + assert_se(path_equal(a, a) == 1); + assert_se(path_equal(b, b) == 1); + assert_se(path_equal(a, b) == (expected == 0)); + assert_se(path_equal(b, a) == (expected == 0)); +} + +TEST(path_compare) { + test_path_compare_one("/goo", "/goo", 0); + test_path_compare_one("/goo", "/goo", 0); + test_path_compare_one("//goo", "/goo", 0); + test_path_compare_one("//goo/////", "/goo", 0); + test_path_compare_one("goo/////", "goo", 0); + test_path_compare_one("/goo/boo", "/goo//boo", 0); + test_path_compare_one("//goo/boo", "/goo/boo//", 0); + test_path_compare_one("//goo/././//./boo//././//", "/goo/boo//.", 0); + test_path_compare_one("/.", "//.///", 0); + test_path_compare_one("/x", "x/", 1); + test_path_compare_one("x/", "/", -1); + test_path_compare_one("/x/./y", "x/y", 1); + test_path_compare_one("/x/./y", "/x/y", 0); + test_path_compare_one("/x/./././y", "/x/y/././.", 0); + test_path_compare_one("./x/./././y", "./x/y/././.", 0); + test_path_compare_one(".", "./.", 0); + test_path_compare_one(".", "././.", 0); + test_path_compare_one("./..", ".", 1); + test_path_compare_one("x/.y", "x/y", -1); + test_path_compare_one("foo", "/foo", -1); + test_path_compare_one("/foo", "/foo/bar", -1); + test_path_compare_one("/foo/aaa", "/foo/b", -1); + test_path_compare_one("/foo/aaa", "/foo/b/a", -1); + test_path_compare_one("/foo/a", "/foo/aaa", -1); + test_path_compare_one("/foo/a/b", "/foo/aaa", -1); +} + +static void test_path_compare_filename_one(const char *a, const char *b, int expected) { + int r; + + assert_se(path_compare_filename(a, a) == 0); + assert_se(path_compare_filename(b, b) == 0); + + r = path_compare_filename(a, b); + assert_se((r > 0) == (expected > 0) && (r < 0) == (expected < 0)); + r = path_compare_filename(b, a); + assert_se((r < 0) == (expected > 0) && (r > 0) == (expected < 0)); + + assert_se(path_equal_filename(a, a) == 1); + assert_se(path_equal_filename(b, b) == 1); + assert_se(path_equal_filename(a, b) == (expected == 0)); + assert_se(path_equal_filename(b, a) == (expected == 0)); +} + +TEST(path_compare_filename) { + test_path_compare_filename_one("/goo", "/goo", 0); + test_path_compare_filename_one("/goo", "/goo", 0); + test_path_compare_filename_one("//goo", "/goo", 0); + test_path_compare_filename_one("//goo/////", "/goo", 0); + test_path_compare_filename_one("goo/////", "goo", 0); + test_path_compare_filename_one("/goo/boo", "/goo//boo", 0); + test_path_compare_filename_one("//goo/boo", "/goo/boo//", 0); + test_path_compare_filename_one("//goo/././//./boo//././//", "/goo/boo//.", 0); + test_path_compare_filename_one("/.", "//.///", -1); + test_path_compare_filename_one("/x", "x/", 0); + test_path_compare_filename_one("x/", "/", 1); + test_path_compare_filename_one("/x/./y", "x/y", 0); + test_path_compare_filename_one("/x/./y", "/x/y", 0); + test_path_compare_filename_one("/x/./././y", "/x/y/././.", 0); + test_path_compare_filename_one("./x/./././y", "./x/y/././.", 0); + test_path_compare_filename_one(".", "./.", -1); + test_path_compare_filename_one(".", "././.", -1); + test_path_compare_filename_one("./..", ".", 1); + test_path_compare_filename_one("x/.y", "x/y", -1); + test_path_compare_filename_one("foo", "/foo", 0); + test_path_compare_filename_one("/foo", "/foo/bar", 1); + test_path_compare_filename_one("/foo/aaa", "/foo/b", -1); + test_path_compare_filename_one("/foo/aaa", "/foo/b/a", 1); + test_path_compare_filename_one("/foo/a", "/foo/aaa", -1); + test_path_compare_filename_one("/foo/a/b", "/foo/aaa", 1); + test_path_compare_filename_one("/a/c", "/b/c", 0); + test_path_compare_filename_one("/a", "/a", 0); + test_path_compare_filename_one("/a/b", "/a/c", -1); + test_path_compare_filename_one("/b", "/c", -1); +} + +TEST(path_equal_root) { + /* Nail down the details of how path_equal("/", ...) works. */ + + assert_se(path_equal("/", "/")); + assert_se(path_equal("/", "//")); + + assert_se(path_equal("/", "/./")); + assert_se(!path_equal("/", "/../")); + + assert_se(!path_equal("/", "/.../")); + + /* Make sure that files_same works as expected. */ + + assert_se(inode_same("/", "/", 0) > 0); + assert_se(inode_same("/", "/", AT_SYMLINK_NOFOLLOW) > 0); + assert_se(inode_same("/", "//", 0) > 0); + assert_se(inode_same("/", "//", AT_SYMLINK_NOFOLLOW) > 0); + + assert_se(inode_same("/", "/./", 0) > 0); + assert_se(inode_same("/", "/./", AT_SYMLINK_NOFOLLOW) > 0); + assert_se(inode_same("/", "/../", 0) > 0); + assert_se(inode_same("/", "/../", AT_SYMLINK_NOFOLLOW) > 0); + + assert_se(inode_same("/", "/.../", 0) == -ENOENT); + assert_se(inode_same("/", "/.../", AT_SYMLINK_NOFOLLOW) == -ENOENT); + + /* The same for path_equal_or_files_same. */ + + assert_se(path_equal_or_inode_same("/", "/", 0)); + assert_se(path_equal_or_inode_same("/", "/", AT_SYMLINK_NOFOLLOW)); + assert_se(path_equal_or_inode_same("/", "//", 0)); + assert_se(path_equal_or_inode_same("/", "//", AT_SYMLINK_NOFOLLOW)); + + assert_se(path_equal_or_inode_same("/", "/./", 0)); + assert_se(path_equal_or_inode_same("/", "/./", AT_SYMLINK_NOFOLLOW)); + assert_se(path_equal_or_inode_same("/", "/../", 0)); + assert_se(path_equal_or_inode_same("/", "/../", AT_SYMLINK_NOFOLLOW)); + + assert_se(!path_equal_or_inode_same("/", "/.../", 0)); + assert_se(!path_equal_or_inode_same("/", "/.../", AT_SYMLINK_NOFOLLOW)); +} + +TEST(find_executable_full) { + char *p; + char* test_file_name; + _cleanup_close_ int fd = -EBADF; + char fn[] = "/tmp/test-XXXXXX"; + + assert_se(find_executable_full("sh", NULL, NULL, true, &p, NULL) == 0); + puts(p); + assert_se(streq(basename(p), "sh")); + free(p); + + assert_se(find_executable_full("sh", NULL, NULL, false, &p, NULL) == 0); + puts(p); + assert_se(streq(basename(p), "sh")); + free(p); + + _cleanup_free_ char *oldpath = NULL; + p = getenv("PATH"); + if (p) + assert_se(oldpath = strdup(p)); + + assert_se(unsetenv("PATH") == 0); + + assert_se(find_executable_full("sh", NULL, NULL, true, &p, NULL) == 0); + puts(p); + assert_se(streq(basename(p), "sh")); + free(p); + + assert_se(find_executable_full("sh", NULL, NULL, false, &p, NULL) == 0); + puts(p); + assert_se(streq(basename(p), "sh")); + free(p); + + if (oldpath) + assert_se(setenv("PATH", oldpath, true) >= 0); + + assert_se((fd = mkostemp_safe(fn)) >= 0); + assert_se(fchmod(fd, 0755) >= 0); + + test_file_name = basename(fn); + + assert_se(find_executable_full(test_file_name, NULL, STRV_MAKE("/doesnotexist", "/tmp", "/bin"), false, &p, NULL) == 0); + puts(p); + assert_se(streq(p, fn)); + free(p); + + (void) unlink(fn); + assert_se(find_executable_full(test_file_name, NULL, STRV_MAKE("/doesnotexist", "/tmp", "/bin"), false, &p, NULL) == -ENOENT); +} + +TEST(find_executable) { + char *p; + + assert_se(find_executable("/bin/sh", &p) == 0); + puts(p); + assert_se(path_equal(p, "/bin/sh")); + free(p); + + assert_se(find_executable(saved_argv[0], &p) == 0); + puts(p); + assert_se(endswith(p, "/test-path-util")); + assert_se(path_is_absolute(p)); + free(p); + + assert_se(find_executable("sh", &p) == 0); + puts(p); + assert_se(endswith(p, "/sh")); + assert_se(path_is_absolute(p)); + free(p); + + assert_se(find_executable("/bin/touch", &p) == 0); + assert_se(streq(p, "/bin/touch")); + free(p); + + assert_se(find_executable("touch", &p) == 0); + assert_se(path_is_absolute(p)); + assert_se(streq(basename(p), "touch")); + free(p); + + assert_se(find_executable("xxxx-xxxx", &p) == -ENOENT); + assert_se(find_executable("/some/dir/xxxx-xxxx", &p) == -ENOENT); + assert_se(find_executable("/proc/filesystems", &p) == -EACCES); +} + +static void test_find_executable_exec_one(const char *path) { + _cleanup_free_ char *t = NULL; + _cleanup_close_ int fd = -EBADF; + pid_t pid; + int r; + + r = find_executable_full(path, NULL, NULL, false, &t, &fd); + + log_info_errno(r, "%s: %s → %s: %d/%m", __func__, path, t ?: "-", fd); + + assert_se(fd > STDERR_FILENO); + assert_se(path_is_absolute(t)); + if (path_is_absolute(path)) + assert_se(streq(t, path)); + + pid = fork(); + assert_se(pid >= 0); + if (pid == 0) { + r = fexecve_or_execve(fd, t, STRV_MAKE(t, "--version"), STRV_MAKE(NULL)); + log_error_errno(r, "[f]execve: %m"); + _exit(EXIT_FAILURE); + } + + assert_se(wait_for_terminate_and_check(t, pid, WAIT_LOG) == 0); +} + +TEST(find_executable_exec) { + test_find_executable_exec_one("touch"); + test_find_executable_exec_one("/bin/touch"); + + _cleanup_free_ char *script = NULL; + assert_se(get_testdata_dir("test-path-util/script.sh", &script) >= 0); + test_find_executable_exec_one(script); +} + +TEST(prefixes) { + static const char* const values[] = { + "/a/b/c/d", + "/a/b/c", + "/a/b", + "/a", + "", + NULL + }; + unsigned i; + char s[PATH_MAX]; + bool b; + + i = 0; + PATH_FOREACH_PREFIX_MORE(s, "/a/b/c/d") { + log_error("---%s---", s); + assert_se(streq(s, values[i++])); + } + assert_se(values[i] == NULL); + + i = 1; + PATH_FOREACH_PREFIX(s, "/a/b/c/d") { + log_error("---%s---", s); + assert_se(streq(s, values[i++])); + } + assert_se(values[i] == NULL); + + i = 0; + PATH_FOREACH_PREFIX_MORE(s, "////a////b////c///d///////") + assert_se(streq(s, values[i++])); + assert_se(values[i] == NULL); + + i = 1; + PATH_FOREACH_PREFIX(s, "////a////b////c///d///////") + assert_se(streq(s, values[i++])); + assert_se(values[i] == NULL); + + PATH_FOREACH_PREFIX(s, "////") + assert_not_reached(); + + b = false; + PATH_FOREACH_PREFIX_MORE(s, "////") { + assert_se(!b); + assert_se(streq(s, "")); + b = true; + } + assert_se(b); + + PATH_FOREACH_PREFIX(s, "") + assert_not_reached(); + + b = false; + PATH_FOREACH_PREFIX_MORE(s, "") { + assert_se(!b); + assert_se(streq(s, "")); + b = true; + } +} + +TEST(path_join) { +#define test_join(expected, ...) { \ + _cleanup_free_ char *z = NULL; \ + z = path_join(__VA_ARGS__); \ + log_debug("got \"%s\", expected \"%s\"", z, expected); \ + assert_se(streq(z, expected)); \ + } + + test_join("/root/a/b/c", "/root", "/a/b", "/c"); + test_join("/root/a/b/c", "/root", "a/b", "c"); + test_join("/root/a/b/c", "/root", "/a/b", "c"); + test_join("/root/c", "/root", "/", "c"); + test_join("/root/", "/root", "/", NULL); + + test_join("/a/b/c", "", "/a/b", "/c"); + test_join("a/b/c", "", "a/b", "c"); + test_join("/a/b/c", "", "/a/b", "c"); + test_join("/c", "", "/", "c"); + test_join("/", "", "/", NULL); + + test_join("/a/b/c", NULL, "/a/b", "/c"); + test_join("a/b/c", NULL, "a/b", "c"); + test_join("/a/b/c", NULL, "/a/b", "c"); + test_join("/c", NULL, "/", "c"); + test_join("/", NULL, "/", NULL); + + test_join("", "", NULL); + test_join("", NULL, ""); + test_join("", NULL, NULL); + + test_join("foo/bar", "foo", "bar"); + test_join("foo/bar", "", "foo", "bar"); + test_join("foo/bar", NULL, "foo", NULL, "bar"); + test_join("foo/bar", "", "foo", "", "bar", ""); + test_join("foo/bar", "", "", "", "", "foo", "", "", "", "bar", "", "", ""); + + test_join("//foo///bar//", "", "/", "", "/foo/", "", "/", "", "/bar/", "", "/", ""); + test_join("/foo/bar/", "/", "foo", "/", "bar", "/"); + test_join("foo/bar/baz", "foo", "bar", "baz"); + test_join("foo/bar/baz", "foo/", "bar", "/baz"); + test_join("foo//bar//baz", "foo/", "/bar/", "/baz"); + test_join("//foo////bar////baz//", "//foo/", "///bar/", "///baz//"); +} + +TEST(path_extend) { + _cleanup_free_ char *p = NULL; + + assert_se(path_extend(&p, "foo", "bar", "baz") == p); + assert_se(streq(p, "foo/bar/baz")); + + assert_se(path_extend(&p, "foo", "bar", "baz") == p); + assert_se(streq(p, "foo/bar/baz/foo/bar/baz")); + + p = mfree(p); + assert_se(path_extend(&p, "foo") == p); + assert_se(streq(p, "foo")); + + assert_se(path_extend(&p, "/foo") == p); + assert_se(streq(p, "foo/foo")); + assert_se(path_extend(&p, "/waaaah/wahhh//") == p); + assert_se(streq(p, "foo/foo/waaaah/wahhh//")); /* path_extend() does not drop redundant slashes */ + assert_se(path_extend(&p, "/aaa/bbb/") == p); + assert_se(streq(p, "foo/foo/waaaah/wahhh///aaa/bbb/")); /* but not add an extra slash */ + + assert_se(free_and_strdup(&p, "/") >= 0); + assert_se(path_extend(&p, "foo") == p); + assert_se(streq(p, "/foo")); +} + +TEST(fsck_exists) { + /* Ensure we use a sane default for PATH. */ + assert_se(unsetenv("PATH") == 0); + + /* We might or might not find one of these, so keep the test lax. */ + assert_se(fsck_exists_for_fstype("minix") >= 0); + + assert_se(fsck_exists_for_fstype("AbCdE") == 0); + assert_se(fsck_exists_for_fstype("/../bin/") == 0); +} + +static void test_path_make_relative_one(const char *from, const char *to, const char *expected) { + _cleanup_free_ char *z = NULL; + int r; + + log_info("/* %s(%s, %s) */", __func__, from, to); + + r = path_make_relative(from, to, &z); + assert_se((r >= 0) == !!expected); + assert_se(streq_ptr(z, expected)); +} + +TEST(path_make_relative) { + test_path_make_relative_one("some/relative/path", "/some/path", NULL); + test_path_make_relative_one("/some/path", "some/relative/path", NULL); + test_path_make_relative_one("/some/dotdot/../path", "/some/path", NULL); + + test_path_make_relative_one("/", "/", "."); + test_path_make_relative_one("/", "/some/path", "some/path"); + test_path_make_relative_one("/some/path", "/some/path", "."); + test_path_make_relative_one("/some/path", "/some/path/in/subdir", "in/subdir"); + test_path_make_relative_one("/some/path", "/", "../.."); + test_path_make_relative_one("/some/path", "/some/other/path", "../other/path"); + test_path_make_relative_one("/some/path/./dot", "/some/further/path", "../../further/path"); + test_path_make_relative_one("//extra.//.//./.slashes//./won't////fo.ol///anybody//", "/././/extra././/.slashes////ar.e/.just/././.fine///", "../../../ar.e/.just/.fine"); +} + +static void test_path_make_relative_parent_one(const char *from, const char *to, const char *expected) { + _cleanup_free_ char *z = NULL; + int r; + + log_info("/* %s(%s, %s) */", __func__, from, to); + + r = path_make_relative_parent(from, to, &z); + assert_se((r >= 0) == !!expected); + assert_se(streq_ptr(z, expected)); +} + +TEST(path_make_relative_parent) { + test_path_make_relative_parent_one("some/relative/path/hoge", "/some/path", NULL); + test_path_make_relative_parent_one("/some/path/hoge", "some/relative/path", NULL); + test_path_make_relative_parent_one("/some/dotdot/../path/hoge", "/some/path", NULL); + test_path_make_relative_parent_one("/", "/aaa", NULL); + + test_path_make_relative_parent_one("/hoge", "/", "."); + test_path_make_relative_parent_one("/hoge", "/some/path", "some/path"); + test_path_make_relative_parent_one("/some/path/hoge", "/some/path", "."); + test_path_make_relative_parent_one("/some/path/hoge", "/some/path/in/subdir", "in/subdir"); + test_path_make_relative_parent_one("/some/path/hoge", "/", "../.."); + test_path_make_relative_parent_one("/some/path/hoge", "/some/other/path", "../other/path"); + test_path_make_relative_parent_one("/some/path/./dot/hoge", "/some/further/path", "../../further/path"); + test_path_make_relative_parent_one("//extra.//.//./.slashes//./won't////fo.ol///anybody//hoge", "/././/extra././/.slashes////ar.e/.just/././.fine///", "../../../ar.e/.just/.fine"); +} + +TEST(path_strv_resolve) { + char tmp_dir[] = "/tmp/test-path-util-XXXXXX"; + _cleanup_strv_free_ char **search_dirs = NULL; + _cleanup_strv_free_ char **absolute_dirs = NULL; + + assert_se(mkdtemp(tmp_dir) != NULL); + + search_dirs = strv_new("/dir1", "/dir2", "/dir3"); + assert_se(search_dirs); + STRV_FOREACH(d, search_dirs) { + char *p = path_join(tmp_dir, *d); + assert_se(p); + assert_se(strv_push(&absolute_dirs, p) == 0); + } + + assert_se(mkdir(absolute_dirs[0], 0700) == 0); + assert_se(mkdir(absolute_dirs[1], 0700) == 0); + assert_se(symlink("dir2", absolute_dirs[2]) == 0); + + path_strv_resolve(search_dirs, tmp_dir); + assert_se(streq(search_dirs[0], "/dir1")); + assert_se(streq(search_dirs[1], "/dir2")); + assert_se(streq(search_dirs[2], "/dir2")); + + assert_se(rm_rf(tmp_dir, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); +} + +static void test_path_startswith_one(const char *path, const char *prefix, const char *skipped, const char *expected) { + const char *p, *q; + + log_debug("/* %s(%s, %s) */", __func__, path, prefix); + + p = path_startswith(path, prefix); + assert_se(streq_ptr(p, expected)); + if (p) { + q = strjoina(skipped, p); + assert_se(streq(q, path)); + assert_se(p == path + strlen(skipped)); + } +} + +TEST(path_startswith) { + test_path_startswith_one("/foo/bar/barfoo/", "/foo", "/foo/", "bar/barfoo/"); + test_path_startswith_one("/foo/bar/barfoo/", "/foo/", "/foo/", "bar/barfoo/"); + test_path_startswith_one("/foo/bar/barfoo/", "/", "/", "foo/bar/barfoo/"); + test_path_startswith_one("/foo/bar/barfoo/", "////", "/", "foo/bar/barfoo/"); + test_path_startswith_one("/foo/bar/barfoo/", "/foo//bar/////barfoo///", "/foo/bar/barfoo/", ""); + test_path_startswith_one("/foo/bar/barfoo/", "/foo/bar/barfoo////", "/foo/bar/barfoo/", ""); + test_path_startswith_one("/foo/bar/barfoo/", "/foo/bar///barfoo/", "/foo/bar/barfoo/", ""); + test_path_startswith_one("/foo/bar/barfoo/", "/foo////bar/barfoo/", "/foo/bar/barfoo/", ""); + test_path_startswith_one("/foo/bar/barfoo/", "////foo/bar/barfoo/", "/foo/bar/barfoo/", ""); + test_path_startswith_one("/foo/bar/barfoo/", "/foo/bar/barfoo", "/foo/bar/barfoo/", ""); + + test_path_startswith_one("/foo/./bar///barfoo/./.", "/foo", "/foo/./", "bar///barfoo/./."); + test_path_startswith_one("/foo/./bar///barfoo/./.", "/foo/", "/foo/./", "bar///barfoo/./."); + test_path_startswith_one("/foo/./bar///barfoo/./.", "/", "/", "foo/./bar///barfoo/./."); + test_path_startswith_one("/foo/./bar///barfoo/./.", "////", "/", "foo/./bar///barfoo/./."); + test_path_startswith_one("/foo/./bar///barfoo/./.", "/foo//bar/////barfoo///", "/foo/./bar///barfoo/./.", ""); + test_path_startswith_one("/foo/./bar///barfoo/./.", "/foo/bar/barfoo////", "/foo/./bar///barfoo/./.", ""); + test_path_startswith_one("/foo/./bar///barfoo/./.", "/foo/bar///barfoo/", "/foo/./bar///barfoo/./.", ""); + test_path_startswith_one("/foo/./bar///barfoo/./.", "/foo////bar/barfoo/", "/foo/./bar///barfoo/./.", ""); + test_path_startswith_one("/foo/./bar///barfoo/./.", "////foo/bar/barfoo/", "/foo/./bar///barfoo/./.", ""); + test_path_startswith_one("/foo/./bar///barfoo/./.", "/foo/bar/barfoo", "/foo/./bar///barfoo/./.", ""); + + test_path_startswith_one("/foo/bar/barfoo/", "/foo/bar/barfooa/", NULL, NULL); + test_path_startswith_one("/foo/bar/barfoo/", "/foo/bar/barfooa", NULL, NULL); + test_path_startswith_one("/foo/bar/barfoo/", "", NULL, NULL); + test_path_startswith_one("/foo/bar/barfoo/", "/bar/foo", NULL, NULL); + test_path_startswith_one("/foo/bar/barfoo/", "/f/b/b/", NULL, NULL); + test_path_startswith_one("/foo/bar/barfoo/", "/foo/bar/barfo", NULL, NULL); + test_path_startswith_one("/foo/bar/barfoo/", "/foo/bar/bar", NULL, NULL); + test_path_startswith_one("/foo/bar/barfoo/", "/fo", NULL, NULL); +} + +static void test_prefix_root_one(const char *r, const char *p, const char *expected) { + _cleanup_free_ char *s = NULL; + const char *t; + + assert_se(s = path_join(r, p)); + assert_se(path_equal_ptr(s, expected)); + + t = prefix_roota(r, p); + assert_se(t); + assert_se(path_equal_ptr(t, expected)); +} + +TEST(prefix_root) { + test_prefix_root_one("/", "/foo", "/foo"); + test_prefix_root_one(NULL, "/foo", "/foo"); + test_prefix_root_one("", "/foo", "/foo"); + test_prefix_root_one("///", "/foo", "/foo"); + test_prefix_root_one("/", "////foo", "/foo"); + test_prefix_root_one(NULL, "////foo", "/foo"); + test_prefix_root_one("/", "foo", "/foo"); + test_prefix_root_one("", "foo", "foo"); + test_prefix_root_one(NULL, "foo", "foo"); + + test_prefix_root_one("/foo", "/bar", "/foo/bar"); + test_prefix_root_one("/foo", "bar", "/foo/bar"); + test_prefix_root_one("foo", "bar", "foo/bar"); + test_prefix_root_one("/foo/", "/bar", "/foo/bar"); + test_prefix_root_one("/foo/", "//bar", "/foo/bar"); + test_prefix_root_one("/foo///", "//bar", "/foo/bar"); +} + +TEST(file_in_same_dir) { + char *t; + + assert_se(file_in_same_dir("/", "a", &t) == -EADDRNOTAVAIL); + + assert_se(file_in_same_dir("/", "/a", &t) >= 0); + assert_se(streq(t, "/a")); + free(t); + + assert_se(file_in_same_dir("", "a", &t) == -EINVAL); + + assert_se(file_in_same_dir("a/", "x", &t) >= 0); + assert_se(streq(t, "x")); + free(t); + + assert_se(file_in_same_dir("bar/foo", "bar", &t) >= 0); + assert_se(streq(t, "bar/bar")); + free(t); +} + +static void test_path_find_first_component_one( + const char *path, + bool accept_dot_dot, + char **expected, + int ret) { + + log_debug("/* %s(\"%s\", accept_dot_dot=%s) */", __func__, strnull(path), yes_no(accept_dot_dot)); + + for (const char *p = path;;) { + const char *e; + int r; + + r = path_find_first_component(&p, accept_dot_dot, &e); + if (r <= 0) { + if (r == 0) { + if (path) { + assert_se(p == path + strlen_ptr(path)); + assert_se(isempty(p)); + } else + assert_se(!p); + assert_se(!e); + } + assert_se(r == ret); + assert_se(strv_isempty(expected)); + return; + } + + assert_se(e); + assert_se(strcspn(e, "/") == (size_t) r); + assert_se(strlen_ptr(*expected) == (size_t) r); + assert_se(strneq(e, *expected++, r)); + + assert_se(p); + log_debug("p=%s", p); + if (!isempty(*expected)) + assert_se(startswith(p, *expected)); + else if (ret >= 0) { + assert_se(p == path + strlen_ptr(path)); + assert_se(isempty(p)); + } + } +} + +TEST(path_find_first_component) { + _cleanup_free_ char *hoge = NULL; + char foo[NAME_MAX * 2]; + + test_path_find_first_component_one(NULL, false, NULL, 0); + test_path_find_first_component_one("", false, NULL, 0); + test_path_find_first_component_one("/", false, NULL, 0); + test_path_find_first_component_one(".", false, NULL, 0); + test_path_find_first_component_one("./", false, NULL, 0); + test_path_find_first_component_one("./.", false, NULL, 0); + test_path_find_first_component_one("..", false, NULL, -EINVAL); + test_path_find_first_component_one("/..", false, NULL, -EINVAL); + test_path_find_first_component_one("./..", false, NULL, -EINVAL); + test_path_find_first_component_one("////./././//.", false, NULL, 0); + test_path_find_first_component_one("a/b/c", false, STRV_MAKE("a", "b", "c"), 0); + test_path_find_first_component_one("././//.///aa/bbb//./ccc", false, STRV_MAKE("aa", "bbb", "ccc"), 0); + test_path_find_first_component_one("././//.///aa/.../../bbb//./ccc/.", false, STRV_MAKE("aa", "..."), -EINVAL); + test_path_find_first_component_one("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", false, STRV_MAKE("aaa", ".bbb"), -EINVAL); + test_path_find_first_component_one("a/foo./b//././/", false, STRV_MAKE("a", "foo.", "b"), 0); + + test_path_find_first_component_one(NULL, true, NULL, 0); + test_path_find_first_component_one("", true, NULL, 0); + test_path_find_first_component_one("/", true, NULL, 0); + test_path_find_first_component_one(".", true, NULL, 0); + test_path_find_first_component_one("./", true, NULL, 0); + test_path_find_first_component_one("./.", true, NULL, 0); + test_path_find_first_component_one("..", true, STRV_MAKE(".."), 0); + test_path_find_first_component_one("/..", true, STRV_MAKE(".."), 0); + test_path_find_first_component_one("./..", true, STRV_MAKE(".."), 0); + test_path_find_first_component_one("////./././//.", true, NULL, 0); + test_path_find_first_component_one("a/b/c", true, STRV_MAKE("a", "b", "c"), 0); + test_path_find_first_component_one("././//.///aa/bbb//./ccc", true, STRV_MAKE("aa", "bbb", "ccc"), 0); + test_path_find_first_component_one("././//.///aa/.../../bbb//./ccc/.", true, STRV_MAKE("aa", "...", "..", "bbb", "ccc"), 0); + test_path_find_first_component_one("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", true, STRV_MAKE("aaa", ".bbb", "..", "c.", "d.dd", "..eeee"), 0); + test_path_find_first_component_one("a/foo./b//././/", true, STRV_MAKE("a", "foo.", "b"), 0); + + memset(foo, 'a', sizeof(foo) -1); + char_array_0(foo); + + test_path_find_first_component_one(foo, false, NULL, -EINVAL); + test_path_find_first_component_one(foo, true, NULL, -EINVAL); + + hoge = strjoin("a/b/c/", foo, "//d/e/.//f/"); + assert_se(hoge); + + test_path_find_first_component_one(hoge, false, STRV_MAKE("a", "b", "c"), -EINVAL); + test_path_find_first_component_one(hoge, true, STRV_MAKE("a", "b", "c"), -EINVAL); +} + +static void test_path_find_last_component_one( + const char *path, + bool accept_dot_dot, + char **expected, + int ret) { + + log_debug("/* %s(\"%s\", accept_dot_dot=%s) */", __func__, strnull(path), yes_no(accept_dot_dot)); + + for (const char *next = NULL;;) { + const char *e; + int r; + + r = path_find_last_component(path, accept_dot_dot, &next, &e); + if (r <= 0) { + if (r == 0) { + assert_se(next == path); + assert_se(!e); + } + assert_se(r == ret); + assert_se(strv_isempty(expected)); + return; + } + + assert_se(e); + assert_se(strcspn(e, "/") == (size_t) r); + assert_se(strlen_ptr(*expected) == (size_t) r); + assert_se(strneq(e, *expected++, r)); + + assert_se(next); + log_debug("path=%s\nnext=%s", path, next); + if (!isempty(*expected)) { + assert_se(next < path + strlen(path)); + assert_se(next >= path + strlen(*expected)); + assert_se(startswith(next - strlen(*expected), *expected)); + } else if (ret >= 0) + assert_se(next == path); + } +} + +TEST(path_find_last_component) { + _cleanup_free_ char *hoge = NULL; + char foo[NAME_MAX * 2]; + + test_path_find_last_component_one(NULL, false, NULL, 0); + test_path_find_last_component_one("", false, NULL, 0); + test_path_find_last_component_one("/", false, NULL, 0); + test_path_find_last_component_one(".", false, NULL, 0); + test_path_find_last_component_one("./", false, NULL, 0); + test_path_find_last_component_one("./.", false, NULL, 0); + test_path_find_last_component_one("..", false, NULL, -EINVAL); + test_path_find_last_component_one("/..", false, NULL, -EINVAL); + test_path_find_last_component_one("./..", false, NULL, -EINVAL); + test_path_find_last_component_one("////./././//.", false, NULL, 0); + test_path_find_last_component_one("a/b/c", false, STRV_MAKE("c", "b", "a"), 0); + test_path_find_last_component_one("././//.///aa./.bbb//./ccc/././/", false, STRV_MAKE("ccc", ".bbb", "aa."), 0); + test_path_find_last_component_one("././//.///aa/../.../bbb//./ccc/.", false, STRV_MAKE("ccc", "bbb", "..."), -EINVAL); + test_path_find_last_component_one("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", false, STRV_MAKE("..eeee", "d.dd", "c."), -EINVAL); + + test_path_find_last_component_one(NULL, true, NULL, 0); + test_path_find_last_component_one("", true, NULL, 0); + test_path_find_last_component_one("/", true, NULL, 0); + test_path_find_last_component_one(".", true, NULL, 0); + test_path_find_last_component_one("./", true, NULL, 0); + test_path_find_last_component_one("./.", true, NULL, 0); + test_path_find_last_component_one("..", true, STRV_MAKE(".."), 0); + test_path_find_last_component_one("/..", true, STRV_MAKE(".."), 0); + test_path_find_last_component_one("./..", true, STRV_MAKE(".."), 0); + test_path_find_last_component_one("////./././//.", true, NULL, 0); + test_path_find_last_component_one("a/b/c", true, STRV_MAKE("c", "b", "a"), 0); + test_path_find_last_component_one("././//.///aa./.bbb//./ccc/././/", true, STRV_MAKE("ccc", ".bbb", "aa."), 0); + test_path_find_last_component_one("././//.///aa/../.../bbb//./ccc/.", true, STRV_MAKE("ccc", "bbb", "...", "..", "aa"), 0); + test_path_find_last_component_one("//./aaa///.//./.bbb/..///c.//d.dd///..eeee/.", true, STRV_MAKE("..eeee", "d.dd", "c.", "..", ".bbb", "aaa"), 0); + + memset(foo, 'a', sizeof(foo) -1); + char_array_0(foo); + + test_path_find_last_component_one(foo, false, NULL, -EINVAL); + test_path_find_last_component_one(foo, true, NULL, -EINVAL); + + hoge = strjoin(foo, "/a/b/c/"); + assert_se(hoge); + + test_path_find_last_component_one(hoge, false, STRV_MAKE("c", "b", "a"), -EINVAL); + test_path_find_last_component_one(hoge, true, STRV_MAKE("c", "b", "a"), -EINVAL); +} + +TEST(last_path_component) { + assert_se(last_path_component(NULL) == NULL); + assert_se(streq(last_path_component("a/b/c"), "c")); + assert_se(streq(last_path_component("a/b/c/"), "c/")); + assert_se(streq(last_path_component("/"), "/")); + assert_se(streq(last_path_component("//"), "/")); + assert_se(streq(last_path_component("///"), "/")); + assert_se(streq(last_path_component("."), ".")); + assert_se(streq(last_path_component("./."), ".")); + assert_se(streq(last_path_component("././"), "./")); + assert_se(streq(last_path_component("././/"), ".//")); + assert_se(streq(last_path_component("/foo/a"), "a")); + assert_se(streq(last_path_component("/foo/a/"), "a/")); + assert_se(streq(last_path_component(""), "")); + assert_se(streq(last_path_component("a"), "a")); + assert_se(streq(last_path_component("a/"), "a/")); + assert_se(streq(last_path_component("/a"), "a")); + assert_se(streq(last_path_component("/a/"), "a/")); +} + +static void test_path_extract_filename_one(const char *input, const char *output, int ret) { + _cleanup_free_ char *k = NULL; + int r; + + r = path_extract_filename(input, &k); + log_info("%s → %s/%s [expected: %s/%s]", + strnull(input), + strnull(k), r < 0 ? STRERROR(r) : "-", + strnull(output), ret < 0 ? STRERROR(ret) : "-"); + assert_se(streq_ptr(k, output)); + assert_se(r == ret); +} + +TEST(path_extract_filename) { + test_path_extract_filename_one(NULL, NULL, -EINVAL); + test_path_extract_filename_one("a/b/c", "c", 0); + test_path_extract_filename_one("a/b/c/", "c", O_DIRECTORY); + test_path_extract_filename_one("/", NULL, -EADDRNOTAVAIL); + test_path_extract_filename_one("//", NULL, -EADDRNOTAVAIL); + test_path_extract_filename_one("///", NULL, -EADDRNOTAVAIL); + test_path_extract_filename_one("/.", NULL, -EADDRNOTAVAIL); + test_path_extract_filename_one(".", NULL, -EADDRNOTAVAIL); + test_path_extract_filename_one("./", NULL, -EADDRNOTAVAIL); + test_path_extract_filename_one("./.", NULL, -EADDRNOTAVAIL); + test_path_extract_filename_one("././", NULL, -EADDRNOTAVAIL); + test_path_extract_filename_one("././/", NULL, -EADDRNOTAVAIL); + test_path_extract_filename_one("/foo/a", "a", 0); + test_path_extract_filename_one("/foo/a/", "a", O_DIRECTORY); + test_path_extract_filename_one("", NULL, -EINVAL); + test_path_extract_filename_one("a", "a", 0); + test_path_extract_filename_one("a/", "a", O_DIRECTORY); + test_path_extract_filename_one("a/././//.", "a", O_DIRECTORY); + test_path_extract_filename_one("/a", "a", 0); + test_path_extract_filename_one("/a/", "a", O_DIRECTORY); + test_path_extract_filename_one("/a//./.", "a", O_DIRECTORY); + test_path_extract_filename_one("/////////////a/////////////", "a", O_DIRECTORY); + test_path_extract_filename_one("//./a/.///b./././.c//./d//.", "d", O_DIRECTORY); + test_path_extract_filename_one("xx/.", "xx", O_DIRECTORY); + test_path_extract_filename_one("xx/..", NULL, -EINVAL); + test_path_extract_filename_one("..", NULL, -EINVAL); + test_path_extract_filename_one("/..", NULL, -EINVAL); + test_path_extract_filename_one("../", NULL, -EINVAL); +} + +static void test_path_extract_directory_one(const char *input, const char *output, int ret) { + _cleanup_free_ char *k = NULL; + int r; + + r = path_extract_directory(input, &k); + log_info("%s → %s/%s [expected: %s/%s]", + strnull(input), + strnull(k), r < 0 ? STRERROR(r) : "-", + strnull(output), STRERROR(ret)); + assert_se(streq_ptr(k, output)); + assert_se(r == ret); + + /* Extra safety check: let's make sure that if we split out the filename too (and it works) the + * joined parts are identical to the original again */ + if (r >= 0) { + _cleanup_free_ char *f = NULL; + + r = path_extract_filename(input, &f); + if (r >= 0) { + _cleanup_free_ char *j = NULL; + + assert_se(j = path_join(k, f)); + assert_se(path_equal(input, j)); + } + } +} + +TEST(path_extract_directory) { + test_path_extract_directory_one(NULL, NULL, -EINVAL); + test_path_extract_directory_one("a/b/c", "a/b", 0); + test_path_extract_directory_one("a/b/c/", "a/b", 0); + test_path_extract_directory_one("/", NULL, -EADDRNOTAVAIL); + test_path_extract_directory_one("//", NULL, -EADDRNOTAVAIL); + test_path_extract_directory_one("///", NULL, -EADDRNOTAVAIL); + test_path_extract_directory_one("/.", NULL, -EADDRNOTAVAIL); + test_path_extract_directory_one(".", NULL, -EADDRNOTAVAIL); + test_path_extract_directory_one("./", NULL, -EADDRNOTAVAIL); + test_path_extract_directory_one("./.", NULL, -EADDRNOTAVAIL); + test_path_extract_directory_one("././", NULL, -EADDRNOTAVAIL); + test_path_extract_directory_one("././/", NULL, -EADDRNOTAVAIL); + test_path_extract_directory_one("/foo/a", "/foo", 0); + test_path_extract_directory_one("/foo/a/", "/foo", 0); + test_path_extract_directory_one("", NULL, -EINVAL); + test_path_extract_directory_one("a", NULL, -EDESTADDRREQ); + test_path_extract_directory_one("a/", NULL, -EDESTADDRREQ); + test_path_extract_directory_one("a/././//.", NULL, -EDESTADDRREQ); + test_path_extract_directory_one("/a", "/", 0); + test_path_extract_directory_one("/a/", "/", 0); + test_path_extract_directory_one("/a//./.", "/", 0); + test_path_extract_directory_one("/////////////a/////////////", "/", 0); + test_path_extract_directory_one("//./a/.///b./././.c//./d//.", "/a/b./.c", 0); + test_path_extract_directory_one("xx/.", NULL, -EDESTADDRREQ); + test_path_extract_directory_one("xx/..", NULL, -EINVAL); + test_path_extract_directory_one("..", NULL, -EINVAL); + test_path_extract_directory_one("/..", NULL, -EINVAL); + test_path_extract_directory_one("../", NULL, -EINVAL); +} + +TEST(filename_is_valid) { + char foo[NAME_MAX+2]; + + assert_se(!filename_is_valid("")); + assert_se(!filename_is_valid("/bar/foo")); + assert_se(!filename_is_valid("/")); + assert_se(!filename_is_valid(".")); + assert_se(!filename_is_valid("..")); + assert_se(!filename_is_valid("bar/foo")); + assert_se(!filename_is_valid("bar/foo/")); + assert_se(!filename_is_valid("bar//")); + + memset(foo, 'a', sizeof(foo) - 1); + char_array_0(foo); + + assert_se(!filename_is_valid(foo)); + + assert_se(filename_is_valid("foo_bar-333")); + assert_se(filename_is_valid("o.o")); +} + +static void test_path_is_valid_and_safe_one(const char *p, bool ret) { + log_debug("/* %s(\"%s\") */", __func__, strnull(p)); + + assert_se(path_is_valid(p) == ret); + if (ret) + ret = !streq(p, "..") && + !startswith(p, "../") && + !endswith(p, "/..") && + !strstr(p, "/../"); + assert_se(path_is_safe(p) == ret); +} + +TEST(path_is_valid_and_safe) { + char foo[PATH_MAX+2]; + const char *c; + + test_path_is_valid_and_safe_one("", false); + test_path_is_valid_and_safe_one("/bar/foo", true); + test_path_is_valid_and_safe_one("/bar/foo/", true); + test_path_is_valid_and_safe_one("/bar/foo/", true); + test_path_is_valid_and_safe_one("//bar//foo//", true); + test_path_is_valid_and_safe_one("/", true); + test_path_is_valid_and_safe_one("/////", true); + test_path_is_valid_and_safe_one("/////.///.////...///..//.", true); + test_path_is_valid_and_safe_one(".", true); + test_path_is_valid_and_safe_one("..", true); + test_path_is_valid_and_safe_one("bar/foo", true); + test_path_is_valid_and_safe_one("bar/foo/", true); + test_path_is_valid_and_safe_one("bar//", true); + + memset(foo, 'a', sizeof(foo) -1); + char_array_0(foo); + + test_path_is_valid_and_safe_one(foo, false); + + c = strjoina("/xxx/", foo, "/yyy"); + test_path_is_valid_and_safe_one(c, false); + + test_path_is_valid_and_safe_one("foo_bar-333", true); + test_path_is_valid_and_safe_one("o.o", true); +} + +TEST(hidden_or_backup_file) { + assert_se(hidden_or_backup_file(".hidden")); + assert_se(hidden_or_backup_file("..hidden")); + assert_se(!hidden_or_backup_file("hidden.")); + + assert_se(hidden_or_backup_file("backup~")); + assert_se(hidden_or_backup_file(".backup~")); + + assert_se(hidden_or_backup_file("lost+found")); + assert_se(hidden_or_backup_file("aquota.user")); + assert_se(hidden_or_backup_file("aquota.group")); + + assert_se(hidden_or_backup_file("test.rpmnew")); + assert_se(hidden_or_backup_file("test.dpkg-old")); + assert_se(hidden_or_backup_file("test.dpkg-remove")); + assert_se(hidden_or_backup_file("test.swp")); + + assert_se(!hidden_or_backup_file("test.rpmnew.")); + assert_se(!hidden_or_backup_file("test.dpkg-old.foo")); +} + +TEST(skip_dev_prefix) { + assert_se(streq(skip_dev_prefix("/"), "/")); + assert_se(streq(skip_dev_prefix("/dev"), "")); + assert_se(streq(skip_dev_prefix("/dev/"), "")); + assert_se(streq(skip_dev_prefix("/dev/foo"), "foo")); + assert_se(streq(skip_dev_prefix("/dev/foo/bar"), "foo/bar")); + assert_se(streq(skip_dev_prefix("//dev"), "")); + assert_se(streq(skip_dev_prefix("//dev//"), "")); + assert_se(streq(skip_dev_prefix("/dev///foo"), "foo")); + assert_se(streq(skip_dev_prefix("///dev///foo///bar"), "foo///bar")); + assert_se(streq(skip_dev_prefix("//foo"), "//foo")); + assert_se(streq(skip_dev_prefix("foo"), "foo")); +} + +TEST(empty_or_root) { + assert_se(empty_or_root(NULL)); + assert_se(empty_or_root("")); + assert_se(empty_or_root("/")); + assert_se(empty_or_root("//")); + assert_se(empty_or_root("///")); + assert_se(empty_or_root("/////////////////")); + assert_se(!empty_or_root("xxx")); + assert_se(!empty_or_root("/xxx")); + assert_se(!empty_or_root("/xxx/")); + assert_se(!empty_or_root("//yy//")); +} + +TEST(path_startswith_set) { + assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar", "/foo/quux", "/foo/bar", "/zzz"), "")); + assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar", "/foo/quux", "/foo/", "/zzz"), "bar")); + assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar", "/foo/quux", "/foo", "/zzz"), "bar")); + assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar", "/foo/quux", "/", "/zzz"), "foo/bar")); + assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar", "/foo/quux", "", "/zzz"), NULL)); + + assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar2", "/foo/quux", "/foo/bar", "/zzz"), NULL)); + assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar2", "/foo/quux", "/foo/", "/zzz"), "bar2")); + assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar2", "/foo/quux", "/foo", "/zzz"), "bar2")); + assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar2", "/foo/quux", "/", "/zzz"), "foo/bar2")); + assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar2", "/foo/quux", "", "/zzz"), NULL)); + + assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo2/bar", "/foo/quux", "/foo/bar", "/zzz"), NULL)); + assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo2/bar", "/foo/quux", "/foo/", "/zzz"), NULL)); + assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo2/bar", "/foo/quux", "/foo", "/zzz"), NULL)); + assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo2/bar", "/foo/quux", "/", "/zzz"), "foo2/bar")); + assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo2/bar", "/foo/quux", "", "/zzz"), NULL)); +} + +TEST(path_startswith_strv) { + assert_se(streq_ptr(path_startswith_strv("/foo/bar", STRV_MAKE("/foo/quux", "/foo/bar", "/zzz")), "")); + assert_se(streq_ptr(path_startswith_strv("/foo/bar", STRV_MAKE("/foo/quux", "/foo/", "/zzz")), "bar")); + assert_se(streq_ptr(path_startswith_strv("/foo/bar", STRV_MAKE("/foo/quux", "/foo", "/zzz")), "bar")); + assert_se(streq_ptr(path_startswith_strv("/foo/bar", STRV_MAKE("/foo/quux", "/", "/zzz")), "foo/bar")); + assert_se(streq_ptr(path_startswith_strv("/foo/bar", STRV_MAKE("/foo/quux", "", "/zzz")), NULL)); + + assert_se(streq_ptr(path_startswith_strv("/foo/bar2", STRV_MAKE("/foo/quux", "/foo/bar", "/zzz")), NULL)); + assert_se(streq_ptr(path_startswith_strv("/foo/bar2", STRV_MAKE("/foo/quux", "/foo/", "/zzz")), "bar2")); + assert_se(streq_ptr(path_startswith_strv("/foo/bar2", STRV_MAKE("/foo/quux", "/foo", "/zzz")), "bar2")); + assert_se(streq_ptr(path_startswith_strv("/foo/bar2", STRV_MAKE("/foo/quux", "/", "/zzz")), "foo/bar2")); + assert_se(streq_ptr(path_startswith_strv("/foo/bar2", STRV_MAKE("/foo/quux", "", "/zzz")), NULL)); + + assert_se(streq_ptr(path_startswith_strv("/foo2/bar", STRV_MAKE("/foo/quux", "/foo/bar", "/zzz")), NULL)); + assert_se(streq_ptr(path_startswith_strv("/foo2/bar", STRV_MAKE("/foo/quux", "/foo/", "/zzz")), NULL)); + assert_se(streq_ptr(path_startswith_strv("/foo2/bar", STRV_MAKE("/foo/quux", "/foo", "/zzz")), NULL)); + assert_se(streq_ptr(path_startswith_strv("/foo2/bar", STRV_MAKE("/foo/quux", "/", "/zzz")), "foo2/bar")); + assert_se(streq_ptr(path_startswith_strv("/foo2/bar", STRV_MAKE("/foo/quux", "", "/zzz")), NULL)); +} + +static void test_path_glob_can_match_one(const char *pattern, const char *prefix, const char *expected) { + _cleanup_free_ char *result = NULL; + + log_debug("%s(%s, %s, %s)", __func__, pattern, prefix, strnull(expected)); + + assert_se(path_glob_can_match(pattern, prefix, &result) == !!expected); + assert_se(streq_ptr(result, expected)); +} + +TEST(path_glob_can_match) { + test_path_glob_can_match_one("/foo/hoge/aaa", "/foo/hoge/aaa/bbb", NULL); + test_path_glob_can_match_one("/foo/hoge/aaa", "/foo/hoge/aaa", "/foo/hoge/aaa"); + test_path_glob_can_match_one("/foo/hoge/aaa", "/foo/hoge", "/foo/hoge/aaa"); + test_path_glob_can_match_one("/foo/hoge/aaa", "/foo", "/foo/hoge/aaa"); + test_path_glob_can_match_one("/foo/hoge/aaa", "/", "/foo/hoge/aaa"); + + test_path_glob_can_match_one("/foo/*/aaa", "/foo/hoge/aaa/bbb", NULL); + test_path_glob_can_match_one("/foo/*/aaa", "/foo/hoge/aaa", "/foo/hoge/aaa"); + test_path_glob_can_match_one("/foo/*/aaa", "/foo/hoge", "/foo/hoge/aaa"); + test_path_glob_can_match_one("/foo/*/aaa", "/foo", "/foo/*/aaa"); + test_path_glob_can_match_one("/foo/*/aaa", "/", "/foo/*/aaa"); + + test_path_glob_can_match_one("/foo/*/*/aaa", "/foo/xxx/yyy/aaa/bbb", NULL); + test_path_glob_can_match_one("/foo/*/*/aaa", "/foo/xxx/yyy/aaa", "/foo/xxx/yyy/aaa"); + test_path_glob_can_match_one("/foo/*/*/aaa", "/foo/xxx/yyy", "/foo/xxx/yyy/aaa"); + test_path_glob_can_match_one("/foo/*/*/aaa", "/foo/xxx", "/foo/xxx/*/aaa"); + test_path_glob_can_match_one("/foo/*/*/aaa", "/foo", "/foo/*/*/aaa"); + test_path_glob_can_match_one("/foo/*/*/aaa", "/", "/foo/*/*/aaa"); + + test_path_glob_can_match_one("/foo/*/aaa/*", "/foo/xxx/aaa/bbb/ccc", NULL); + test_path_glob_can_match_one("/foo/*/aaa/*", "/foo/xxx/aaa/bbb", "/foo/xxx/aaa/bbb"); + test_path_glob_can_match_one("/foo/*/aaa/*", "/foo/xxx/ccc", NULL); + test_path_glob_can_match_one("/foo/*/aaa/*", "/foo/xxx/aaa", "/foo/xxx/aaa/*"); + test_path_glob_can_match_one("/foo/*/aaa/*", "/foo/xxx", "/foo/xxx/aaa/*"); + test_path_glob_can_match_one("/foo/*/aaa/*", "/foo", "/foo/*/aaa/*"); + test_path_glob_can_match_one("/foo/*/aaa/*", "/", "/foo/*/aaa/*"); +} + +TEST(print_MAX) { + log_info("PATH_MAX=%zu\n" + "FILENAME_MAX=%zu\n" + "NAME_MAX=%zu", + (size_t) PATH_MAX, + (size_t) FILENAME_MAX, + (size_t) NAME_MAX); + + assert_cc(FILENAME_MAX == PATH_MAX); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-path.c b/src/test/test-path.c new file mode 100644 index 0000000..22ed88f --- /dev/null +++ b/src/test/test-path.c @@ -0,0 +1,418 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdbool.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "alloc-util.h" +#include "all-units.h" +#include "fd-util.h" +#include "fs-util.h" +#include "macro.h" +#include "manager.h" +#include "mkdir.h" +#include "path-util.h" +#include "rm-rf.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" +#include "unit.h" + +typedef void (*test_function_t)(Manager *m); + +static int setup_test(Manager **m) { + char **tests_path = STRV_MAKE("exists", "existsglobFOOBAR", "changed", "modified", "unit", + "directorynotempty", "makedirectory"); + Manager *tmp = NULL; + int r; + + assert_se(m); + + r = enter_cgroup_subroot(NULL); + if (r == -ENOMEDIUM) + return log_tests_skipped("cgroupfs not available"); + + r = manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &tmp); + if (manager_errno_skip_test(r)) + return log_tests_skipped_errno(r, "manager_new"); + assert_se(r >= 0); + assert_se(manager_startup(tmp, NULL, NULL, NULL) >= 0); + + STRV_FOREACH(test_path, tests_path) { + _cleanup_free_ char *p = NULL; + + p = strjoin("/tmp/test-path_", *test_path); + assert_se(p); + + (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL); + } + + *m = tmp; + + return 0; +} + +static void shutdown_test(Manager *m) { + assert_se(m); + + manager_free(m); +} + +static Service *service_for_path(Manager *m, Path *path, const char *service_name) { + _cleanup_free_ char *tmp = NULL; + Unit *service_unit = NULL; + + assert_se(m); + assert_se(path); + + if (!service_name) { + assert_se(tmp = strreplace(UNIT(path)->id, ".path", ".service")); + service_unit = manager_get_unit(m, tmp); + } else + service_unit = manager_get_unit(m, service_name); + assert_se(service_unit); + + return SERVICE(service_unit); +} + +static int _check_states(unsigned line, + Manager *m, Path *path, Service *service, PathState path_state, ServiceState service_state) { + assert_se(m); + assert_se(service); + + usec_t end = now(CLOCK_MONOTONIC) + 30 * USEC_PER_SEC; + + while (path->state != path_state || service->state != service_state || + path->result != PATH_SUCCESS || service->result != SERVICE_SUCCESS) { + + assert_se(sd_event_run(m->event, 100 * USEC_PER_MSEC) >= 0); + + usec_t n = now(CLOCK_MONOTONIC); + log_info("line %u: %s: state = %s; result = %s (left: %" PRIi64 ")", + line, + UNIT(path)->id, + path_state_to_string(path->state), + path_result_to_string(path->result), + (int64_t) (end - n)); + log_info("line %u: %s: state = %s; result = %s", + line, + UNIT(service)->id, + service_state_to_string(service->state), + service_result_to_string(service->result)); + + if (service->state == SERVICE_FAILED && service->main_exec_status.status == EXIT_CGROUP) { + const char *ci = ci_environment(); + + /* On a general purpose system we may fail to start the service for reasons which are + * not under our control: permission limits, resource exhaustion, etc. Let's skip the + * test in those cases. On developer machines we require proper setup. */ + if (!ci) + return log_notice_errno(SYNTHETIC_ERRNO(ECANCELED), + "Failed to start service %s, aborting test: %s/%s", + UNIT(service)->id, + service_state_to_string(service->state), + service_result_to_string(service->result)); + + /* On Salsa we can't setup cgroups so the unit always fails. The test checks if it + * can but continues if it cannot at the beginning, but on Salsa it fails here. */ + if (streq(ci, "salsa-ci")) + exit(EXIT_TEST_SKIP); + } + + if (n >= end) { + log_error("Test timeout when testing %s", UNIT(path)->id); + exit(EXIT_FAILURE); + } + } + + return 0; +} +#define check_states(...) _check_states(__LINE__, __VA_ARGS__) + +static void test_path_exists(Manager *m) { + const char *test_path = "/tmp/test-path_exists"; + Unit *unit = NULL; + Path *path = NULL; + Service *service = NULL; + + assert_se(m); + + assert_se(manager_load_startable_unit_or_warn(m, "path-exists.path", NULL, &unit) >= 0); + + path = PATH(unit); + service = service_for_path(m, path, NULL); + + assert_se(unit_start(unit, NULL) >= 0); + if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) + return; + + assert_se(touch(test_path) >= 0); + if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) + return; + + /* Service restarts if file still exists */ + assert_se(unit_stop(UNIT(service)) >= 0); + if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) + return; + + assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); + assert_se(unit_stop(UNIT(service)) >= 0); + if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) + return; + + assert_se(unit_stop(unit) >= 0); +} + +static void test_path_existsglob(Manager *m) { + const char *test_path = "/tmp/test-path_existsglobFOOBAR"; + Unit *unit = NULL; + Path *path = NULL; + Service *service = NULL; + + assert_se(m); + + assert_se(manager_load_startable_unit_or_warn(m, "path-existsglob.path", NULL, &unit) >= 0); + + path = PATH(unit); + service = service_for_path(m, path, NULL); + + assert_se(unit_start(unit, NULL) >= 0); + if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) + return; + + assert_se(touch(test_path) >= 0); + if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) + return; + + /* Service restarts if file still exists */ + assert_se(unit_stop(UNIT(service)) >= 0); + if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) + return; + + assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); + assert_se(unit_stop(UNIT(service)) >= 0); + if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) + return; + + assert_se(unit_stop(unit) >= 0); +} + +static void test_path_changed(Manager *m) { + const char *test_path = "/tmp/test-path_changed"; + FILE *f; + Unit *unit = NULL; + Path *path = NULL; + Service *service = NULL; + + assert_se(m); + + assert_se(manager_load_startable_unit_or_warn(m, "path-changed.path", NULL, &unit) >= 0); + + path = PATH(unit); + service = service_for_path(m, path, NULL); + + assert_se(unit_start(unit, NULL) >= 0); + if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) + return; + + assert_se(touch(test_path) >= 0); + if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) + return; + + /* Service does not restart if file still exists */ + assert_se(unit_stop(UNIT(service)) >= 0); + if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) + return; + + f = fopen(test_path, "w"); + assert_se(f); + fclose(f); + + if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) + return; + + assert_se(unit_stop(UNIT(service)) >= 0); + if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) + return; + + (void) rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL); + assert_se(unit_stop(unit) >= 0); +} + +static void test_path_modified(Manager *m) { + _cleanup_fclose_ FILE *f = NULL; + const char *test_path = "/tmp/test-path_modified"; + Unit *unit = NULL; + Path *path = NULL; + Service *service = NULL; + + assert_se(m); + + assert_se(manager_load_startable_unit_or_warn(m, "path-modified.path", NULL, &unit) >= 0); + + path = PATH(unit); + service = service_for_path(m, path, NULL); + + assert_se(unit_start(unit, NULL) >= 0); + if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) + return; + + assert_se(touch(test_path) >= 0); + if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) + return; + + /* Service does not restart if file still exists */ + assert_se(unit_stop(UNIT(service)) >= 0); + if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) + return; + + f = fopen(test_path, "w"); + assert_se(f); + fputs("test", f); + + if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) + return; + + assert_se(unit_stop(UNIT(service)) >= 0); + if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) + return; + + (void) rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL); + assert_se(unit_stop(unit) >= 0); +} + +static void test_path_unit(Manager *m) { + const char *test_path = "/tmp/test-path_unit"; + Unit *unit = NULL; + Path *path = NULL; + Service *service = NULL; + + assert_se(m); + + assert_se(manager_load_startable_unit_or_warn(m, "path-unit.path", NULL, &unit) >= 0); + + path = PATH(unit); + service = service_for_path(m, path, "path-mycustomunit.service"); + + assert_se(unit_start(unit, NULL) >= 0); + if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) + return; + + assert_se(touch(test_path) >= 0); + if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) + return; + + assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); + assert_se(unit_stop(UNIT(service)) >= 0); + if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) + return; + + assert_se(unit_stop(unit) >= 0); +} + +static void test_path_directorynotempty(Manager *m) { + const char *test_file, *test_path = "/tmp/test-path_directorynotempty/"; + Unit *unit = NULL; + Path *path = NULL; + Service *service = NULL; + + assert_se(m); + + assert_se(manager_load_startable_unit_or_warn(m, "path-directorynotempty.path", NULL, &unit) >= 0); + + path = PATH(unit); + service = service_for_path(m, path, NULL); + + assert_se(access(test_path, F_OK) < 0); + + assert_se(unit_start(unit, NULL) >= 0); + if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) + return; + + /* MakeDirectory default to no */ + assert_se(access(test_path, F_OK) < 0); + + assert_se(mkdir_p(test_path, 0755) >= 0); + test_file = strjoina(test_path, "test_file"); + assert_se(touch(test_file) >= 0); + if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) + return; + + /* Service restarts if directory is still not empty */ + assert_se(unit_stop(UNIT(service)) >= 0); + if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) + return; + + assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); + assert_se(unit_stop(UNIT(service)) >= 0); + if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) + return; + + assert_se(unit_stop(unit) >= 0); +} + +static void test_path_makedirectory_directorymode(Manager *m) { + const char *test_path = "/tmp/test-path_makedirectory/"; + Unit *unit = NULL; + struct stat s; + + assert_se(m); + + assert_se(manager_load_startable_unit_or_warn(m, "path-makedirectory.path", NULL, &unit) >= 0); + + assert_se(access(test_path, F_OK) < 0); + + assert_se(unit_start(unit, NULL) >= 0); + + /* Check if the directory has been created */ + assert_se(access(test_path, F_OK) >= 0); + + /* Check the mode we specified with DirectoryMode=0744 */ + assert_se(stat(test_path, &s) >= 0); + assert_se((s.st_mode & S_IRWXU) == 0700); + assert_se((s.st_mode & S_IRWXG) == 0040); + assert_se((s.st_mode & S_IRWXO) == 0004); + + assert_se(unit_stop(unit) >= 0); + (void) rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL); +} + +int main(int argc, char *argv[]) { + static const test_function_t tests[] = { + test_path_exists, + test_path_existsglob, + test_path_changed, + test_path_modified, + test_path_unit, + test_path_directorynotempty, + test_path_makedirectory_directorymode, + NULL, + }; + + _cleanup_free_ char *test_path = NULL; + _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; + + umask(022); + + test_setup_logging(LOG_INFO); + + assert_se(get_testdata_dir("test-path", &test_path) >= 0); + assert_se(set_unit_path(test_path) >= 0); + assert_se(runtime_dir = setup_fake_runtime_dir()); + + for (const test_function_t *test = tests; *test; test++) { + Manager *m = NULL; + int r; + + /* We create a clean environment for each test */ + r = setup_test(&m); + if (r != 0) + return r; + + (*test)(m); + + shutdown_test(m); + } + + return 0; +} diff --git a/src/test/test-percent-util.c b/src/test/test-percent-util.c new file mode 100644 index 0000000..7e8e11b --- /dev/null +++ b/src/test/test-percent-util.c @@ -0,0 +1,199 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "percent-util.h" +#include "tests.h" +#include "time-util.h" + +TEST(parse_percent) { + assert_se(parse_percent("") == -EINVAL); + assert_se(parse_percent("foo") == -EINVAL); + assert_se(parse_percent("0") == -EINVAL); + assert_se(parse_percent("0.1") == -EINVAL); + assert_se(parse_percent("50") == -EINVAL); + assert_se(parse_percent("100") == -EINVAL); + assert_se(parse_percent("-1") == -EINVAL); + assert_se(parse_percent("0%") == 0); + assert_se(parse_percent("55%") == 55); + assert_se(parse_percent("100%") == 100); + assert_se(parse_percent("-7%") == -ERANGE); + assert_se(parse_percent("107%") == -ERANGE); + assert_se(parse_percent("%") == -EINVAL); + assert_se(parse_percent("%%") == -EINVAL); + assert_se(parse_percent("%1") == -EINVAL); + assert_se(parse_percent("1%%") == -EINVAL); + assert_se(parse_percent("3.2%") == -EINVAL); +} + +TEST(parse_percent_unbounded) { + assert_se(parse_percent_unbounded("101%") == 101); + assert_se(parse_percent_unbounded("400%") == 400); +} + +TEST(parse_permille) { + assert_se(parse_permille("") == -EINVAL); + assert_se(parse_permille("foo") == -EINVAL); + assert_se(parse_permille("0") == -EINVAL); + assert_se(parse_permille("50") == -EINVAL); + assert_se(parse_permille("100") == -EINVAL); + assert_se(parse_permille("-1") == -EINVAL); + assert_se(parse_permille("0.1") == -EINVAL); + assert_se(parse_permille("5%") == 50); + assert_se(parse_permille("5.5%") == 55); + assert_se(parse_permille("5.12%") == -EINVAL); + + assert_se(parse_permille("0‰") == 0); + assert_se(parse_permille("555‰") == 555); + assert_se(parse_permille("1000‰") == 1000); + assert_se(parse_permille("-7‰") == -ERANGE); + assert_se(parse_permille("1007‰") == -ERANGE); + assert_se(parse_permille("‰") == -EINVAL); + assert_se(parse_permille("‰‰") == -EINVAL); + assert_se(parse_permille("‰1") == -EINVAL); + assert_se(parse_permille("1‰‰") == -EINVAL); + assert_se(parse_permille("3.2‰") == -EINVAL); + assert_se(parse_permille("0.1‰") == -EINVAL); + + assert_se(parse_permille("0%") == 0); + assert_se(parse_permille("55%") == 550); + assert_se(parse_permille("55.5%") == 555); + assert_se(parse_permille("100%") == 1000); + assert_se(parse_permille("-7%") == -ERANGE); + assert_se(parse_permille("107%") == -ERANGE); + assert_se(parse_permille("%") == -EINVAL); + assert_se(parse_permille("%%") == -EINVAL); + assert_se(parse_permille("%1") == -EINVAL); + assert_se(parse_permille("1%%") == -EINVAL); + assert_se(parse_permille("3.21%") == -EINVAL); + assert_se(parse_permille("0.1%") == 1); +} + +TEST(parse_permille_unbounded) { + assert_se(parse_permille_unbounded("1001‰") == 1001); + assert_se(parse_permille_unbounded("4000‰") == 4000); + assert_se(parse_permille_unbounded("2147483647‰") == 2147483647); + assert_se(parse_permille_unbounded("2147483648‰") == -ERANGE); + assert_se(parse_permille_unbounded("4294967295‰") == -ERANGE); + assert_se(parse_permille_unbounded("4294967296‰") == -ERANGE); + + assert_se(parse_permille_unbounded("101%") == 1010); + assert_se(parse_permille_unbounded("400%") == 4000); + assert_se(parse_permille_unbounded("214748364.7%") == 2147483647); + assert_se(parse_permille_unbounded("214748364.8%") == -ERANGE); + assert_se(parse_permille_unbounded("429496729.5%") == -ERANGE); + assert_se(parse_permille_unbounded("429496729.6%") == -ERANGE); +} + +TEST(parse_permyriad) { + assert_se(parse_permyriad("") == -EINVAL); + assert_se(parse_permyriad("foo") == -EINVAL); + assert_se(parse_permyriad("0") == -EINVAL); + assert_se(parse_permyriad("50") == -EINVAL); + assert_se(parse_permyriad("100") == -EINVAL); + assert_se(parse_permyriad("-1") == -EINVAL); + + assert_se(parse_permyriad("0‱") == 0); + assert_se(parse_permyriad("555‱") == 555); + assert_se(parse_permyriad("1000‱") == 1000); + assert_se(parse_permyriad("-7‱") == -ERANGE); + assert_se(parse_permyriad("10007‱") == -ERANGE); + assert_se(parse_permyriad("‱") == -EINVAL); + assert_se(parse_permyriad("‱‱") == -EINVAL); + assert_se(parse_permyriad("‱1") == -EINVAL); + assert_se(parse_permyriad("1‱‱") == -EINVAL); + assert_se(parse_permyriad("3.2‱") == -EINVAL); + + assert_se(parse_permyriad("0‰") == 0); + assert_se(parse_permyriad("555.5‰") == 5555); + assert_se(parse_permyriad("1000.0‰") == 10000); + assert_se(parse_permyriad("-7‰") == -ERANGE); + assert_se(parse_permyriad("1007‰") == -ERANGE); + assert_se(parse_permyriad("‰") == -EINVAL); + assert_se(parse_permyriad("‰‰") == -EINVAL); + assert_se(parse_permyriad("‰1") == -EINVAL); + assert_se(parse_permyriad("1‰‰") == -EINVAL); + assert_se(parse_permyriad("3.22‰") == -EINVAL); + + assert_se(parse_permyriad("0%") == 0); + assert_se(parse_permyriad("55%") == 5500); + assert_se(parse_permyriad("55.5%") == 5550); + assert_se(parse_permyriad("55.50%") == 5550); + assert_se(parse_permyriad("55.53%") == 5553); + assert_se(parse_permyriad("100%") == 10000); + assert_se(parse_permyriad("-7%") == -ERANGE); + assert_se(parse_permyriad("107%") == -ERANGE); + assert_se(parse_permyriad("%") == -EINVAL); + assert_se(parse_permyriad("%%") == -EINVAL); + assert_se(parse_permyriad("%1") == -EINVAL); + assert_se(parse_permyriad("1%%") == -EINVAL); + assert_se(parse_permyriad("3.212%") == -EINVAL); +} + +TEST(parse_permyriad_unbounded) { + assert_se(parse_permyriad_unbounded("1001‱") == 1001); + assert_se(parse_permyriad_unbounded("4000‱") == 4000); + assert_se(parse_permyriad_unbounded("2147483647‱") == 2147483647); + assert_se(parse_permyriad_unbounded("2147483648‱") == -ERANGE); + assert_se(parse_permyriad_unbounded("4294967295‱") == -ERANGE); + assert_se(parse_permyriad_unbounded("4294967296‱") == -ERANGE); + + assert_se(parse_permyriad_unbounded("101‰") == 1010); + assert_se(parse_permyriad_unbounded("400‰") == 4000); + assert_se(parse_permyriad_unbounded("214748364.7‰") == 2147483647); + assert_se(parse_permyriad_unbounded("214748364.8‰") == -ERANGE); + assert_se(parse_permyriad_unbounded("429496729.5‰") == -ERANGE); + assert_se(parse_permyriad_unbounded("429496729.6‰") == -ERANGE); + + assert_se(parse_permyriad_unbounded("99%") == 9900); + assert_se(parse_permyriad_unbounded("40%") == 4000); + assert_se(parse_permyriad_unbounded("21474836.47%") == 2147483647); + assert_se(parse_permyriad_unbounded("21474836.48%") == -ERANGE); + assert_se(parse_permyriad_unbounded("42949672.95%") == -ERANGE); + assert_se(parse_permyriad_unbounded("42949672.96%") == -ERANGE); +} + +TEST(scale) { + /* Check some fixed values */ + assert_se(UINT32_SCALE_FROM_PERCENT(0) == 0); + assert_se(UINT32_SCALE_FROM_PERCENT(50) == UINT32_MAX/2+1); + assert_se(UINT32_SCALE_FROM_PERCENT(100) == UINT32_MAX); + + assert_se(UINT32_SCALE_FROM_PERMILLE(0) == 0); + assert_se(UINT32_SCALE_FROM_PERMILLE(500) == UINT32_MAX/2+1); + assert_se(UINT32_SCALE_FROM_PERMILLE(1000) == UINT32_MAX); + + assert_se(UINT32_SCALE_FROM_PERMYRIAD(0) == 0); + assert_se(UINT32_SCALE_FROM_PERMYRIAD(5000) == UINT32_MAX/2+1); + assert_se(UINT32_SCALE_FROM_PERMYRIAD(10000) == UINT32_MAX); + + /* Make sure there's no numeric noise on the 0%…100% scale when converting from percent and back. */ + for (int percent = 0; percent <= 100; percent++) { + log_debug("%i%% → %" PRIu32 " → %i%%", + percent, + UINT32_SCALE_FROM_PERCENT(percent), + UINT32_SCALE_TO_PERCENT(UINT32_SCALE_FROM_PERCENT(percent))); + + assert_se(UINT32_SCALE_TO_PERCENT(UINT32_SCALE_FROM_PERCENT(percent)) == percent); + } + + /* Make sure there's no numeric noise on the 0‰…1000‰ scale when converting from permille and back. */ + for (int permille = 0; permille <= 1000; permille++) { + log_debug("%i‰ → %" PRIu32 " → %i‰", + permille, + UINT32_SCALE_FROM_PERMILLE(permille), + UINT32_SCALE_TO_PERMILLE(UINT32_SCALE_FROM_PERMILLE(permille))); + + assert_se(UINT32_SCALE_TO_PERMILLE(UINT32_SCALE_FROM_PERMILLE(permille)) == permille); + } + + /* Make sure there's no numeric noise on the 0‱…10000‱ scale when converting from permyriad and back. */ + for (int permyriad = 0; permyriad <= 10000; permyriad++) { + log_debug("%i‱ → %" PRIu32 " → %i‱", + permyriad, + UINT32_SCALE_FROM_PERMYRIAD(permyriad), + UINT32_SCALE_TO_PERMYRIAD(UINT32_SCALE_FROM_PERMYRIAD(permyriad))); + + assert_se(UINT32_SCALE_TO_PERMYRIAD(UINT32_SCALE_FROM_PERMYRIAD(permyriad)) == permyriad); + } +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-pretty-print.c b/src/test/test-pretty-print.c new file mode 100644 index 0000000..52b2bc8 --- /dev/null +++ b/src/test/test-pretty-print.c @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdio.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "macro.h" +#include "pretty-print.h" +#include "strv.h" +#include "tests.h" + +#define CYLON_WIDTH 6 + +static void test_draw_cylon_one(unsigned pos) { + char buf[CYLON_WIDTH + CYLON_BUFFER_EXTRA + 1]; + + log_debug("/* %s(%u) */", __func__, pos); + + assert(pos <= CYLON_WIDTH + 1); + + memset(buf, 0xff, sizeof(buf)); + draw_cylon(buf, sizeof(buf), CYLON_WIDTH, pos); + assert_se(strlen(buf) < sizeof(buf)); +} + +TEST(draw_cylon) { + bool saved = log_get_show_color(); + + log_show_color(false); + for (unsigned i = 0; i <= CYLON_WIDTH + 1; i++) + test_draw_cylon_one(i); + + log_show_color(true); + for (unsigned i = 0; i <= CYLON_WIDTH + 1; i++) + test_draw_cylon_one(i); + + log_show_color(saved); +} + +TEST(terminal_urlify) { + _cleanup_free_ char *formatted = NULL; + + assert_se(terminal_urlify("https://www.freedesktop.org/wiki/Software/systemd", "systemd homepage", &formatted) >= 0); + printf("Hey, consider visiting the %s right now! It is very good!\n", formatted); + + formatted = mfree(formatted); + + assert_se(terminal_urlify_path("/etc/fstab", "this link to your /etc/fstab", &formatted) >= 0); + printf("Or click on %s to have a look at it!\n", formatted); +} + +TEST(cat_files) { + assert_se(cat_files("/no/such/file", NULL, 0) == -ENOENT); + assert_se(cat_files(NULL, NULL, 0) == 0); + + if (access("/etc/fstab", R_OK) >= 0) + assert_se(cat_files("/etc/fstab", STRV_MAKE("/etc/fstab", "/etc/fstab"), 0) == 0); +} + +TEST(red_green_cross_check_mark) { + bool b = false; + + printf("yea: <%s>\n", GREEN_CHECK_MARK()); + printf("nay: <%s>\n", RED_CROSS_MARK()); + + printf("%s → %s → %s → %s\n", + COLOR_MARK_BOOL(b), + COLOR_MARK_BOOL(!b), + COLOR_MARK_BOOL(!!b), + COLOR_MARK_BOOL(!!!b)); +} + +TEST(print_separator) { + print_separator(); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-prioq.c b/src/test/test-prioq.c new file mode 100644 index 0000000..540863c --- /dev/null +++ b/src/test/test-prioq.c @@ -0,0 +1,123 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdlib.h> + +#include "alloc-util.h" +#include "prioq.h" +#include "set.h" +#include "siphash24.h" +#include "sort-util.h" +#include "tests.h" + +#define SET_SIZE 1024*4 + +static int unsigned_compare(const unsigned *a, const unsigned *b) { + return CMP(*a, *b); +} + +TEST(unsigned) { + _cleanup_(prioq_freep) Prioq *q = NULL; + unsigned buffer[SET_SIZE], i, u, n; + + srand(0); + + assert_se(q = prioq_new(trivial_compare_func)); + + for (i = 0; i < ELEMENTSOF(buffer); i++) { + u = (unsigned) rand(); + buffer[i] = u; + assert_se(prioq_put(q, UINT_TO_PTR(u), NULL) >= 0); + + n = prioq_size(q); + assert_se(prioq_remove(q, UINT_TO_PTR(u), &n) == 0); + } + + typesafe_qsort(buffer, ELEMENTSOF(buffer), unsigned_compare); + + for (i = 0; i < ELEMENTSOF(buffer); i++) { + assert_se(prioq_size(q) == ELEMENTSOF(buffer) - i); + + u = PTR_TO_UINT(prioq_pop(q)); + assert_se(buffer[i] == u); + } + + assert_se(prioq_isempty(q)); +} + +struct test { + unsigned value; + unsigned idx; +}; + +static int test_compare(const struct test *x, const struct test *y) { + return CMP(x->value, y->value); +} + +static void test_hash(const struct test *x, struct siphash *state) { + siphash24_compress(&x->value, sizeof(x->value), state); +} + +DEFINE_PRIVATE_HASH_OPS(test_hash_ops, struct test, test_hash, test_compare); + +TEST(struct) { + _cleanup_(prioq_freep) Prioq *q = NULL; + _cleanup_set_free_ Set *s = NULL; + unsigned previous = 0, i; + struct test *t; + + srand(0); + + assert_se(q = prioq_new((compare_func_t) test_compare)); + assert_se(s = set_new(&test_hash_ops)); + + assert_se(prioq_peek(q) == NULL); + assert_se(prioq_peek_by_index(q, 0) == NULL); + assert_se(prioq_peek_by_index(q, 1) == NULL); + assert_se(prioq_peek_by_index(q, UINT_MAX) == NULL); + + for (i = 0; i < SET_SIZE; i++) { + assert_se(t = new0(struct test, 1)); + t->value = (unsigned) rand(); + + assert_se(prioq_put(q, t, &t->idx) >= 0); + + if (i % 4 == 0) + assert_se(set_consume(s, t) >= 0); + } + + for (i = 0; i < SET_SIZE; i++) + assert_se(prioq_peek_by_index(q, i)); + assert_se(prioq_peek_by_index(q, SET_SIZE) == NULL); + + unsigned count = 0; + PRIOQ_FOREACH_ITEM(q, t) { + assert_se(t); + count++; + } + assert_se(count == SET_SIZE); + + while ((t = set_steal_first(s))) { + assert_se(prioq_remove(q, t, &t->idx) == 1); + assert_se(prioq_remove(q, t, &t->idx) == 0); + assert_se(prioq_remove(q, t, NULL) == 0); + + free(t); + } + + for (i = 0; i < SET_SIZE * 3 / 4; i++) { + assert_se(prioq_size(q) == (SET_SIZE * 3 / 4) - i); + + assert_se(t = prioq_pop(q)); + assert_se(prioq_remove(q, t, &t->idx) == 0); + assert_se(prioq_remove(q, t, NULL) == 0); + assert_se(previous <= t->value); + + previous = t->value; + free(t); + } + + assert_se(prioq_isempty(q)); + assert_se(set_isempty(s)); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-proc-cmdline.c b/src/test/test-proc-cmdline.c new file mode 100644 index 0000000..8b5bbb0 --- /dev/null +++ b/src/test/test-proc-cmdline.c @@ -0,0 +1,354 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "env-util.h" +#include "errno-util.h" +#include "initrd-util.h" +#include "log.h" +#include "macro.h" +#include "nulstr-util.h" +#include "proc-cmdline.h" +#include "process-util.h" +#include "special.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" + +static int obj; + +static int parse_item(const char *key, const char *value, void *data) { + assert_se(key); + assert_se(data == &obj); + + log_info("kernel cmdline option <%s> = <%s>", key, strna(value)); + return 0; +} + +TEST(proc_cmdline_parse) { + assert_se(proc_cmdline_parse(parse_item, &obj, PROC_CMDLINE_STRIP_RD_PREFIX) >= 0); +} + +TEST(proc_cmdline_override) { + _cleanup_free_ char *line = NULL, *value = NULL; + _cleanup_strv_free_ char **args = NULL; + + assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar=quux wuff-piep=tuet zumm some_arg_with_space='foo bar' and_one_more=\"zzz aaa\"") == 0); + assert_se(putenv((char*) "SYSTEMD_EFI_OPTIONS=different") == 0); + + /* First test if the overrides for /proc/cmdline still work */ + assert_se(proc_cmdline(&line) >= 0); + assert_se(streq(line, "foo_bar=quux wuff-piep=tuet zumm some_arg_with_space='foo bar' and_one_more=\"zzz aaa\"")); + line = mfree(line); + assert_se(proc_cmdline_strv(&args) >= 0); + assert_se(strv_equal(args, STRV_MAKE("foo_bar=quux", "wuff-piep=tuet", "zumm", "some_arg_with_space=foo bar", "and_one_more=zzz aaa"))); + args = strv_free(args); + + /* Test if parsing makes uses of the override */ + assert_se(proc_cmdline_get_key("foo_bar", 0, &value) > 0 && streq_ptr(value, "quux")); + value = mfree(value); + + assert_se(proc_cmdline_get_key("some_arg_with_space", 0, &value) > 0 && streq_ptr(value, "foo bar")); + value = mfree(value); + + assert_se(proc_cmdline_get_key("and_one_more", 0, &value) > 0 && streq_ptr(value, "zzz aaa")); + value = mfree(value); + + assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=hoge") == 0); + assert_se(putenv((char*) "SYSTEMD_EFI_OPTIONS=foo_bar=quux wuff-piep=tuet zumm some_arg_with_space='foo bar' and_one_more=\"zzz aaa\"") == 0); + + assert_se(proc_cmdline(&line) >= 0); + assert_se(streq(line, "hoge")); + line = mfree(line); + assert_se(proc_cmdline_strv(&args) >= 0); + assert_se(strv_equal(args, STRV_MAKE("hoge"))); + args = strv_free(args); + +#if ENABLE_EFI + assert_se(proc_cmdline_get_key("foo_bar", 0, &value) > 0 && streq_ptr(value, "quux")); + value = mfree(value); + + assert_se(proc_cmdline_get_key("some_arg_with_space", 0, &value) > 0 && streq_ptr(value, "foo bar")); + value = mfree(value); + + assert_se(proc_cmdline_get_key("and_one_more", 0, &value) > 0 && streq_ptr(value, "zzz aaa")); + value = mfree(value); +#endif +} + +static int parse_item_given(const char *key, const char *value, void *data) { + assert_se(key); + assert_se(data); + + bool *strip = data; + + log_info("%s: option <%s> = <%s>", __func__, key, strna(value)); + if (proc_cmdline_key_streq(key, "foo_bar")) + assert_se(streq(value, "quux")); + else if (proc_cmdline_key_streq(key, "wuff-piep")) + assert_se(streq(value, "tuet ")); + else if (proc_cmdline_key_streq(key, "space")) + assert_se(streq(value, "x y z")); + else if (proc_cmdline_key_streq(key, "miepf")) + assert_se(streq(value, "uuu")); + else if (in_initrd() && *strip && proc_cmdline_key_streq(key, "zumm")) + assert_se(!value); + else if (in_initrd() && !*strip && proc_cmdline_key_streq(key, "rd.zumm")) + assert_se(!value); + else + assert_not_reached(); + + return 0; +} + +static void test_proc_cmdline_given_one(bool flip_initrd) { + log_info("/* %s (flip: %s) */", __func__, yes_no(flip_initrd)); + + if (flip_initrd) + in_initrd_force(!in_initrd()); + + bool t = true, f = false; + assert_se(proc_cmdline_parse(parse_item_given, &t, PROC_CMDLINE_STRIP_RD_PREFIX) >= 0); + assert_se(proc_cmdline_parse(parse_item_given, &f, 0) >= 0); + + if (flip_initrd) + in_initrd_force(!in_initrd()); +} + +TEST(proc_cmdline_given) { + assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar=quux wuff-piep=\"tuet \" rd.zumm space='x y z' miepf=\"uuu\"") == 0); + assert_se(putenv((char*) "SYSTEMD_EFI_OPTIONS=miepf=\"uuu\"") == 0); + + test_proc_cmdline_given_one(false); + /* Repeat the same thing, but now flip our ininitrdness */ + test_proc_cmdline_given_one(true); +} + +TEST(proc_cmdline_get_key) { + _cleanup_free_ char *value = NULL; + + assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar=quux wuff-piep=tuet zumm-ghh spaaace='ö ü ß' ticks=\"''\"\n\nkkk=uuu\n\n\n") == 0); + + assert_se(proc_cmdline_get_key("", 0, &value) == -EINVAL); + assert_se(proc_cmdline_get_key("abc", 0, NULL) == 0); + assert_se(proc_cmdline_get_key("abc", 0, &value) == 0 && value == NULL); + assert_se(proc_cmdline_get_key("abc", PROC_CMDLINE_VALUE_OPTIONAL, &value) == 0 && value == NULL); + + assert_se(proc_cmdline_get_key("foo_bar", 0, &value) > 0 && streq_ptr(value, "quux")); + value = mfree(value); + assert_se(proc_cmdline_get_key("foo_bar", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && streq_ptr(value, "quux")); + value = mfree(value); + assert_se(proc_cmdline_get_key("foo_bar", 0, NULL) == 0); + assert_se(proc_cmdline_get_key("foo-bar", 0, &value) > 0 && streq_ptr(value, "quux")); + value = mfree(value); + assert_se(proc_cmdline_get_key("foo-bar", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && streq_ptr(value, "quux")); + value = mfree(value); + assert_se(proc_cmdline_get_key("foo-bar", 0, NULL) == 0); + assert_se(proc_cmdline_get_key("foo-bar", PROC_CMDLINE_VALUE_OPTIONAL, NULL) == -EINVAL); + + assert_se(proc_cmdline_get_key("wuff-piep", 0, &value) > 0 && streq_ptr(value, "tuet")); + value = mfree(value); + assert_se(proc_cmdline_get_key("wuff-piep", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && streq_ptr(value, "tuet")); + value = mfree(value); + assert_se(proc_cmdline_get_key("wuff_piep", 0, &value) > 0 && streq_ptr(value, "tuet")); + value = mfree(value); + assert_se(proc_cmdline_get_key("wuff_piep", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && streq_ptr(value, "tuet")); + value = mfree(value); + assert_se(proc_cmdline_get_key("wuff_piep", 0, NULL) == 0); + assert_se(proc_cmdline_get_key("wuff_piep", PROC_CMDLINE_VALUE_OPTIONAL, NULL) == -EINVAL); + + assert_se(proc_cmdline_get_key("zumm-ghh", 0, &value) == 0 && value == NULL); + assert_se(proc_cmdline_get_key("zumm-ghh", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && value == NULL); + assert_se(proc_cmdline_get_key("zumm-ghh", 0, NULL) > 0); + assert_se(proc_cmdline_get_key("zumm_ghh", 0, &value) == 0 && value == NULL); + assert_se(proc_cmdline_get_key("zumm_ghh", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && value == NULL); + assert_se(proc_cmdline_get_key("zumm_ghh", 0, NULL) > 0); + + assert_se(proc_cmdline_get_key("spaaace", 0, &value) > 0 && streq_ptr(value, "ö ü ß")); + value = mfree(value); + + assert_se(proc_cmdline_get_key("ticks", 0, &value) > 0 && streq_ptr(value, "''")); + value = mfree(value); + + assert_se(proc_cmdline_get_key("kkk", 0, &value) > 0 && streq_ptr(value, "uuu")); +} + +TEST(proc_cmdline_get_bool) { + bool value = false; + + assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar bar-waldo=1 x_y-z=0 quux=miep\nda=yes\nthe=1") == 0); + assert_se(putenv((char*) "SYSTEMD_EFI_OPTIONS=") == 0); + + assert_se(proc_cmdline_get_bool("", /* flags = */ 0, &value) == -EINVAL); + assert_se(proc_cmdline_get_bool("abc", /* flags = */ 0, &value) == 0 && value == false); + assert_se(proc_cmdline_get_bool("unspecified", PROC_CMDLINE_TRUE_WHEN_MISSING, &value) == 0 && value == true); + assert_se(proc_cmdline_get_bool("foo_bar", /* flags = */ 0, &value) > 0 && value == true); + assert_se(proc_cmdline_get_bool("foo-bar", /* flags = */ 0, &value) > 0 && value == true); + assert_se(proc_cmdline_get_bool("bar-waldo", /* flags = */ 0, &value) > 0 && value == true); + assert_se(proc_cmdline_get_bool("bar_waldo", /* flags = */ 0, &value) > 0 && value == true); + assert_se(proc_cmdline_get_bool("x_y-z", /* flags = */ 0, &value) > 0 && value == false); + assert_se(proc_cmdline_get_bool("x-y-z", /* flags = */ 0, &value) > 0 && value == false); + assert_se(proc_cmdline_get_bool("x-y_z", /* flags = */ 0, &value) > 0 && value == false); + assert_se(proc_cmdline_get_bool("x_y_z", /* flags = */ 0, &value) > 0 && value == false); + assert_se(proc_cmdline_get_bool("quux", /* flags = */ 0, &value) == -EINVAL && value == false); + assert_se(proc_cmdline_get_bool("da", /* flags = */ 0, &value) > 0 && value == true); + assert_se(proc_cmdline_get_bool("the", /* flags = */ 0, &value) > 0 && value == true); +} + +#if ENABLE_EFI +TEST(proc_cmdline_get_bool_efi) { + bool value = false; + + assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=") == 0); + assert_se(putenv((char*) "SYSTEMD_EFI_OPTIONS=foo_bar bar-waldo=1 x_y-z=0 quux=miep\nda=yes\nthe=1") == 0); + + assert_se(proc_cmdline_get_bool("", /* flags = */ 0, &value) == -EINVAL); + assert_se(proc_cmdline_get_bool("abc", /* flags = */ 0, &value) == 0 && value == false); + assert_se(proc_cmdline_get_bool("foo_bar", /* flags = */ 0, &value) > 0 && value == true); + assert_se(proc_cmdline_get_bool("foo-bar", /* flags = */ 0, &value) > 0 && value == true); + assert_se(proc_cmdline_get_bool("bar-waldo", /* flags = */ 0, &value) > 0 && value == true); + assert_se(proc_cmdline_get_bool("bar_waldo", /* flags = */ 0, &value) > 0 && value == true); + assert_se(proc_cmdline_get_bool("x_y-z", /* flags = */ 0, &value) > 0 && value == false); + assert_se(proc_cmdline_get_bool("x-y-z", /* flags = */ 0, &value) > 0 && value == false); + assert_se(proc_cmdline_get_bool("x-y_z", /* flags = */ 0, &value) > 0 && value == false); + assert_se(proc_cmdline_get_bool("x_y_z", /* flags = */ 0, &value) > 0 && value == false); + assert_se(proc_cmdline_get_bool("quux", /* flags = */ 0, &value) == -EINVAL && value == false); + assert_se(proc_cmdline_get_bool("da", /* flags = */ 0, &value) > 0 && value == true); + assert_se(proc_cmdline_get_bool("the", /* flags = */ 0, &value) > 0 && value == true); +} +#endif + +TEST(proc_cmdline_get_key_many) { + _cleanup_free_ char *value1 = NULL, *value2 = NULL, *value3 = NULL, *value4 = NULL, *value5 = NULL, *value6 = NULL, *value7 = NULL; + + assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar=quux wuff-piep=tuet zumm SPACE='one two' doubleticks=\" aaa aaa \"\n\nzummm='\n'\n") == 0); + + assert_se(proc_cmdline_get_key_many(0, + "wuff-piep", &value3, + "foo_bar", &value1, + "idontexist", &value2, + "zumm", &value4, + "SPACE", &value5, + "doubleticks", &value6, + "zummm", &value7) == 5); + + assert_se(streq_ptr(value1, "quux")); + assert_se(!value2); + assert_se(streq_ptr(value3, "tuet")); + assert_se(!value4); + assert_se(streq_ptr(value5, "one two")); + assert_se(streq_ptr(value6, " aaa aaa ")); + assert_se(streq_ptr(value7, "\n")); +} + +TEST(proc_cmdline_key_streq) { + assert_se(proc_cmdline_key_streq("", "")); + assert_se(proc_cmdline_key_streq("a", "a")); + assert_se(!proc_cmdline_key_streq("", "a")); + assert_se(!proc_cmdline_key_streq("a", "")); + assert_se(proc_cmdline_key_streq("a", "a")); + assert_se(!proc_cmdline_key_streq("a", "b")); + assert_se(proc_cmdline_key_streq("x-y-z", "x-y-z")); + assert_se(proc_cmdline_key_streq("x-y-z", "x_y_z")); + assert_se(proc_cmdline_key_streq("x-y-z", "x-y_z")); + assert_se(proc_cmdline_key_streq("x-y-z", "x_y-z")); + assert_se(proc_cmdline_key_streq("x_y-z", "x-y_z")); + assert_se(!proc_cmdline_key_streq("x_y-z", "x-z_z")); +} + +TEST(proc_cmdline_key_startswith) { + assert_se(proc_cmdline_key_startswith("", "")); + assert_se(proc_cmdline_key_startswith("x", "")); + assert_se(!proc_cmdline_key_startswith("", "x")); + assert_se(proc_cmdline_key_startswith("x", "x")); + assert_se(!proc_cmdline_key_startswith("x", "y")); + assert_se(!proc_cmdline_key_startswith("foo-bar", "quux")); + assert_se(proc_cmdline_key_startswith("foo-bar", "foo")); + assert_se(proc_cmdline_key_startswith("foo-bar", "foo-bar")); + assert_se(proc_cmdline_key_startswith("foo-bar", "foo_bar")); + assert_se(proc_cmdline_key_startswith("foo-bar", "foo_")); + assert_se(!proc_cmdline_key_startswith("foo-bar", "foo_xx")); +} + +#define test_proc_cmdline_filter_pid1_args_one(nulstr, expected) \ + ({ \ + _cleanup_strv_free_ char **a = NULL, **b = NULL; \ + const char s[] = (nulstr); \ + \ + /* This emulates pid_get_cmdline_strv(). */ \ + assert_se(a = strv_parse_nulstr_full(s, ELEMENTSOF(s), \ + /* drop_trailing_nuls = */ true)); \ + assert_se(proc_cmdline_filter_pid1_args(a, &b) >= 0); \ + assert_se(strv_equal(b, expected)); \ + }) + +TEST(proc_cmdline_filter_pid1_args) { + test_proc_cmdline_filter_pid1_args_one("systemd\0", + STRV_MAKE_EMPTY); + + /* short option */ + test_proc_cmdline_filter_pid1_args_one("systemd\0" + "-a\0" /* unknown option */ + "-abc\0" /* unknown options */ + "-h\0" /* known option */ + "-hDbs\0" /* known options */ + "-hsx\0" /* mixed (known and unknown) options */ + "-z\0drop1\0" /* option with argument */ + "-z\0-z\0accept1\0" /* the second -z is handled as argument */ + "-az\0drop2\0" /* options with argument */ + "-za\0accept2\0" /* options with argument */ + "-z\0--\0-x\0", /* "--" is handled as argument */ + STRV_MAKE("accept1", "accept2")); + + /* long option */ + test_proc_cmdline_filter_pid1_args_one("systemd\0" + "--unknown\0accept1\0" /* unknown option */ + "--system\0accept2\0" /* no argument */ + "--log-level\0drop1\0" /* required argument (separated with space) */ + "--log-level=drop2\0accept3\0" /* required argument (concatenated with '=') */ + "--log-level\0--log-level\0accept4\0" /* the second "--log-level" is handled as argument */ + "--log-level\0--\0-x\0" /* "--" is handled as argument */ + "--log-color\0--log-level\0drop3\0" /* optional argument ("--log-level" is handled as another option) */ + "--log-color\0accept5\0" /* optional argument (separated with space) */ + "--log-color=drop4\0accept6\0" /* optional argument (concatenated with '=') */ + "--log-color\0--\0" /* "--" is _not_ handled as argument, and remaining strings are accepted */ + "remaining\0-x\0--foo\0", + STRV_MAKE("accept1", "accept2", "accept3", "accept4", "accept5", "accept6", "remaining", "-x", "--foo")); + + /* test for "--" */ + test_proc_cmdline_filter_pid1_args_one("systemd\0" + "-a\0" + "--dropped\0" + "--\0" /* remaining strings are accepted */ + "-x\0" + "-abc\0" + "--hoge\0" + "accepted\0", + STRV_MAKE("-x", "-abc", "--hoge", "accepted")); + + /* test for space */ + test_proc_cmdline_filter_pid1_args_one("/usr/lib/systemd/systemd\0" + "--switched-root\0" + "--system\0" + "--deserialize\030\0" /* followed with space */ + "--deserialize=31\0" /* followed with '=' */ + "--exit-code=42\0" + "\0\0\0" + "systemd.log_level=debug\0" + "--unit\0foo.target\0" + " ' quoted '\0" + "systemd.log_target=console\0" + "\t\0" + " arg with space \0" + "3\0" + "\0\0\0", + STRV_MAKE("", "", "", "systemd.log_level=debug", " ' quoted '", "systemd.log_target=console", "\t", " arg with space ", "3")); +} + +static int intro(void) { + if (access("/proc/cmdline", R_OK) < 0 && ERRNO_IS_PRIVILEGE(errno)) + return log_tests_skipped("can't read /proc/cmdline"); + + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/test/test-process-util.c b/src/test/test-process-util.c new file mode 100644 index 0000000..957e214 --- /dev/null +++ b/src/test/test-process-util.c @@ -0,0 +1,954 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <fcntl.h> +#include <linux/oom.h> +#include <pthread.h> +#include <sys/mount.h> +#include <sys/personality.h> +#include <sys/prctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#if HAVE_VALGRIND_VALGRIND_H +#include <valgrind/valgrind.h> +#endif + +#include "alloc-util.h" +#include "architecture.h" +#include "dirent-util.h" +#include "errno-list.h" +#include "errno-util.h" +#include "fd-util.h" +#include "ioprio-util.h" +#include "log.h" +#include "macro.h" +#include "missing_sched.h" +#include "missing_syscall.h" +#include "namespace-util.h" +#include "parse-util.h" +#include "process-util.h" +#include "procfs-util.h" +#include "rlimit-util.h" +#include "signal-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "terminal-util.h" +#include "tests.h" +#include "user-util.h" +#include "virt.h" + +static void test_pid_get_comm_one(pid_t pid) { + struct stat st; + _cleanup_free_ char *a = NULL, *c = NULL, *d = NULL, *f = NULL, *i = NULL; + _cleanup_free_ char *env = NULL; + char path[STRLEN("/proc//comm") + DECIMAL_STR_MAX(pid_t)]; + pid_t e; + uid_t u; + gid_t g; + dev_t h; + int r; + + log_info("/* %s */", __func__); + + xsprintf(path, "/proc/"PID_FMT"/comm", pid); + + if (stat(path, &st) == 0) { + assert_se(pid_get_comm(pid, &a) >= 0); + log_info("PID"PID_FMT" comm: '%s'", pid, a); + } else + log_warning("%s not exist.", path); + + assert_se(pid_get_cmdline(pid, 0, PROCESS_CMDLINE_COMM_FALLBACK, &c) >= 0); + log_info("PID"PID_FMT" cmdline: '%s'", pid, c); + + assert_se(pid_get_cmdline(pid, 8, 0, &d) >= 0); + log_info("PID"PID_FMT" cmdline truncated to 8: '%s'", pid, d); + + free(d); + assert_se(pid_get_cmdline(pid, 1, 0, &d) >= 0); + log_info("PID"PID_FMT" cmdline truncated to 1: '%s'", pid, d); + + r = get_process_ppid(pid, &e); + assert_se(pid == 1 ? r == -EADDRNOTAVAIL : r >= 0); + if (r >= 0) { + log_info("PID"PID_FMT" PPID: "PID_FMT, pid, e); + assert_se(e > 0); + } + + assert_se(pid_is_kernel_thread(pid) == 0 || pid != 1); + + r = get_process_exe(pid, &f); + assert_se(r >= 0 || r == -EACCES); + log_info("PID"PID_FMT" exe: '%s'", pid, strna(f)); + + assert_se(pid_get_uid(pid, &u) == 0); + log_info("PID"PID_FMT" UID: "UID_FMT, pid, u); + + assert_se(get_process_gid(pid, &g) == 0); + log_info("PID"PID_FMT" GID: "GID_FMT, pid, g); + + r = get_process_environ(pid, &env); + assert_se(r >= 0 || r == -EACCES); + log_info("PID"PID_FMT" strlen(environ): %zi", pid, env ? (ssize_t)strlen(env) : (ssize_t)-errno); + + if (!detect_container()) + assert_se(get_ctty_devnr(pid, &h) == -ENXIO || pid != 1); + + (void) getenv_for_pid(pid, "PATH", &i); + log_info("PID"PID_FMT" $PATH: '%s'", pid, strna(i)); +} + +TEST(pid_get_comm) { + if (saved_argc > 1) { + pid_t pid = 0; + + (void) parse_pid(saved_argv[1], &pid); + test_pid_get_comm_one(pid); + } else { + TEST_REQ_RUNNING_SYSTEMD(test_pid_get_comm_one(1)); + test_pid_get_comm_one(getpid()); + } +} + +static void test_pid_get_cmdline_one(pid_t pid) { + _cleanup_free_ char *c = NULL, *d = NULL, *e = NULL, *f = NULL, *g = NULL, *h = NULL, *joined = NULL; + _cleanup_strv_free_ char **strv_a = NULL, **strv_b = NULL; + int r; + + r = pid_get_cmdline(pid, SIZE_MAX, 0, &c); + log_info("PID "PID_FMT": %s", pid, r >= 0 ? c : errno_to_name(r)); + + r = pid_get_cmdline(pid, SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK, &d); + log_info(" %s", r >= 0 ? d : errno_to_name(r)); + + r = pid_get_cmdline(pid, SIZE_MAX, PROCESS_CMDLINE_QUOTE, &e); + log_info(" %s", r >= 0 ? e : errno_to_name(r)); + + r = pid_get_cmdline(pid, SIZE_MAX, PROCESS_CMDLINE_QUOTE | PROCESS_CMDLINE_COMM_FALLBACK, &f); + log_info(" %s", r >= 0 ? f : errno_to_name(r)); + + r = pid_get_cmdline(pid, SIZE_MAX, PROCESS_CMDLINE_QUOTE_POSIX, &g); + log_info(" %s", r >= 0 ? g : errno_to_name(r)); + + r = pid_get_cmdline(pid, SIZE_MAX, PROCESS_CMDLINE_QUOTE_POSIX | PROCESS_CMDLINE_COMM_FALLBACK, &h); + log_info(" %s", r >= 0 ? h : errno_to_name(r)); + + r = pid_get_cmdline_strv(pid, 0, &strv_a); + if (r >= 0) + assert_se(joined = strv_join(strv_a, "\", \"")); + log_info(" \"%s\"", r >= 0 ? joined : errno_to_name(r)); + + joined = mfree(joined); + + r = pid_get_cmdline_strv(pid, PROCESS_CMDLINE_COMM_FALLBACK, &strv_b); + if (r >= 0) + assert_se(joined = strv_join(strv_b, "\", \"")); + log_info(" \"%s\"", r >= 0 ? joined : errno_to_name(r)); +} + +TEST(pid_get_cmdline) { + _cleanup_closedir_ DIR *d = NULL; + int r; + + assert_se(proc_dir_open(&d) >= 0); + + for (;;) { + pid_t pid; + + r = proc_dir_read(d, &pid); + assert_se(r >= 0); + + if (r == 0) /* EOF */ + break; + + test_pid_get_cmdline_one(pid); + } +} + +static void test_pid_get_comm_escape_one(const char *input, const char *output) { + _cleanup_free_ char *n = NULL; + + log_debug("input: <%s> — output: <%s>", input, output); + + assert_se(prctl(PR_SET_NAME, input) >= 0); + assert_se(pid_get_comm(0, &n) >= 0); + + log_debug("got: <%s>", n); + + assert_se(streq_ptr(n, output)); +} + +TEST(pid_get_comm_escape) { + _cleanup_free_ char *saved = NULL; + + assert_se(pid_get_comm(0, &saved) >= 0); + + test_pid_get_comm_escape_one("", ""); + test_pid_get_comm_escape_one("foo", "foo"); + test_pid_get_comm_escape_one("012345678901234", "012345678901234"); + test_pid_get_comm_escape_one("0123456789012345", "012345678901234"); + test_pid_get_comm_escape_one("äöüß", "\\303\\244\\303\\266\\303\\274\\303\\237"); + test_pid_get_comm_escape_one("xäöüß", "x\\303\\244\\303\\266\\303\\274\\303\\237"); + test_pid_get_comm_escape_one("xxäöüß", "xx\\303\\244\\303\\266\\303\\274\\303\\237"); + test_pid_get_comm_escape_one("xxxäöüß", "xxx\\303\\244\\303\\266\\303\\274\\303\\237"); + test_pid_get_comm_escape_one("xxxxäöüß", "xxxx\\303\\244\\303\\266\\303\\274\\303\\237"); + test_pid_get_comm_escape_one("xxxxxäöüß", "xxxxx\\303\\244\\303\\266\\303\\274\\303\\237"); + + assert_se(prctl(PR_SET_NAME, saved) >= 0); +} + +TEST(pid_is_unwaited) { + pid_t pid; + + pid = fork(); + assert_se(pid >= 0); + if (pid == 0) { + _exit(EXIT_SUCCESS); + } else { + int status; + + assert_se(waitpid(pid, &status, 0) == pid); + assert_se(pid_is_unwaited(pid) == 0); + } + assert_se(pid_is_unwaited(getpid_cached()) > 0); + assert_se(pid_is_unwaited(-1) < 0); +} + +TEST(pid_is_alive) { + pid_t pid; + + pid = fork(); + assert_se(pid >= 0); + if (pid == 0) { + _exit(EXIT_SUCCESS); + } else { + int status; + + assert_se(waitpid(pid, &status, 0) == pid); + assert_se(pid_is_alive(pid) == 0); + } + assert_se(pid_is_alive(getpid_cached()) > 0); + assert_se(pid_is_alive(-1) < 0); +} + +TEST(personality) { + assert_se(personality_to_string(PER_LINUX)); + assert_se(!personality_to_string(PERSONALITY_INVALID)); + + assert_se(streq(personality_to_string(PER_LINUX), architecture_to_string(native_architecture()))); + + assert_se(personality_from_string(personality_to_string(PER_LINUX)) == PER_LINUX); + assert_se(personality_from_string(architecture_to_string(native_architecture())) == PER_LINUX); + +#ifdef __x86_64__ + assert_se(streq_ptr(personality_to_string(PER_LINUX), "x86-64")); + assert_se(streq_ptr(personality_to_string(PER_LINUX32), "x86")); + + assert_se(personality_from_string("x86-64") == PER_LINUX); + assert_se(personality_from_string("x86") == PER_LINUX32); + assert_se(personality_from_string("ia64") == PERSONALITY_INVALID); + assert_se(personality_from_string(NULL) == PERSONALITY_INVALID); + + assert_se(personality_from_string(personality_to_string(PER_LINUX32)) == PER_LINUX32); +#endif +} + +TEST(pid_get_cmdline_harder) { + char path[] = "/tmp/test-cmdlineXXXXXX"; + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *line = NULL; + _cleanup_strv_free_ char **args = NULL; + pid_t pid; + int r; + + if (geteuid() != 0) { + log_info("Skipping %s: not root", __func__); + return; + } + + if (!have_namespaces()) { + log_notice("Testing without namespaces, skipping %s", __func__); + return; + } + +#if HAVE_VALGRIND_VALGRIND_H + /* valgrind patches open(/proc//cmdline) + * so, test_pid_get_cmdline_harder fails always + * See https://github.com/systemd/systemd/pull/3555#issuecomment-226564908 */ + if (RUNNING_ON_VALGRIND) { + log_info("Skipping %s: running on valgrind", __func__); + return; + } +#endif + + pid = fork(); + if (pid > 0) { + siginfo_t si; + + (void) wait_for_terminate(pid, &si); + + assert_se(si.si_code == CLD_EXITED); + assert_se(si.si_status == 0); + + return; + } + + assert_se(pid == 0); + + r = detach_mount_namespace(); + if (r < 0) { + log_warning_errno(r, "detach mount namespace failed: %m"); + assert_se(ERRNO_IS_PRIVILEGE(r)); + return; + } + + fd = mkostemp(path, O_CLOEXEC); + assert_se(fd >= 0); + + /* Note that we don't unmount the following bind-mount at the end of the test because the kernel + * will clear up its /proc/PID/ hierarchy automatically as soon as the test stops. */ + if (mount(path, "/proc/self/cmdline", "bind", MS_BIND, NULL) < 0) { + /* This happens under selinux… Abort the test in this case. */ + log_warning_errno(errno, "mount(..., \"/proc/self/cmdline\", \"bind\", ...) failed: %m"); + assert_se(IN_SET(errno, EPERM, EACCES)); + return; + } + + /* Set RLIMIT_STACK to infinity to test we don't try to allocate unnecessarily large values to read + * the cmdline. */ + if (setrlimit(RLIMIT_STACK, &RLIMIT_MAKE_CONST(RLIM_INFINITY)) < 0) + log_warning("Testing without RLIMIT_STACK=infinity"); + + assert_se(unlink(path) >= 0); + + assert_se(prctl(PR_SET_NAME, "testa") >= 0); + + assert_se(pid_get_cmdline(0, SIZE_MAX, 0, &line) == -ENOENT); + + assert_se(pid_get_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "[testa]")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK | PROCESS_CMDLINE_QUOTE, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "\"[testa]\"")); /* quoting is enabled here */ + line = mfree(line); + + assert_se(pid_get_cmdline(0, 0, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 1, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(streq(line, "…")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 2, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(streq(line, "[…")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 3, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(streq(line, "[t…")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 4, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(streq(line, "[te…")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 5, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(streq(line, "[tes…")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 6, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(streq(line, "[test…")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 7, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(streq(line, "[testa]")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 8, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(streq(line, "[testa]")); + line = mfree(line); + + assert_se(pid_get_cmdline_strv(0, PROCESS_CMDLINE_COMM_FALLBACK, &args) >= 0); + assert_se(strv_equal(args, STRV_MAKE("[testa]"))); + args = strv_free(args); + + /* Test with multiple arguments that don't require quoting */ + + assert_se(write(fd, "foo\0bar", 8) == 8); + + assert_se(pid_get_cmdline(0, SIZE_MAX, 0, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "foo bar")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(streq(line, "foo bar")); + line = mfree(line); + + assert_se(pid_get_cmdline_strv(0, PROCESS_CMDLINE_COMM_FALLBACK, &args) >= 0); + assert_se(strv_equal(args, STRV_MAKE("foo", "bar"))); + args = strv_free(args); + + assert_se(write(fd, "quux", 4) == 4); + assert_se(pid_get_cmdline(0, SIZE_MAX, 0, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "foo bar quux")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "foo bar quux")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 1, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "…")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 2, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "f…")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 3, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "fo…")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 4, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "foo…")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 5, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "foo …")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 6, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "foo b…")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 7, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "foo ba…")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 8, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "foo bar…")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 9, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "foo bar …")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 10, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "foo bar q…")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 11, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "foo bar qu…")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 12, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "foo bar quux")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 13, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "foo bar quux")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 14, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "foo bar quux")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 1000, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "foo bar quux")); + line = mfree(line); + + assert_se(pid_get_cmdline_strv(0, PROCESS_CMDLINE_COMM_FALLBACK, &args) >= 0); + assert_se(strv_equal(args, STRV_MAKE("foo", "bar", "quux"))); + args = strv_free(args); + + assert_se(ftruncate(fd, 0) >= 0); + assert_se(prctl(PR_SET_NAME, "aaaa bbbb cccc") >= 0); + + assert_se(pid_get_cmdline(0, SIZE_MAX, 0, &line) == -ENOENT); + + assert_se(pid_get_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "[aaaa bbbb cccc]")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 10, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "[aaaa bbb…")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 11, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "[aaaa bbbb…")); + line = mfree(line); + + assert_se(pid_get_cmdline(0, 12, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "[aaaa bbbb …")); + line = mfree(line); + + assert_se(pid_get_cmdline_strv(0, PROCESS_CMDLINE_COMM_FALLBACK, &args) >= 0); + assert_se(strv_equal(args, STRV_MAKE("[aaaa bbbb cccc]"))); + args = strv_free(args); + + /* Test with multiple arguments that do require quoting */ + +#define CMDLINE1 "foo\0'bar'\0\"bar$\"\0x y z\0!``\0" +#define EXPECT1 "foo \"'bar'\" \"\\\"bar\\$\\\"\" \"x y z\" \"!\\`\\`\"" +#define EXPECT1p "foo $'\\'bar\\'' $'\"bar$\"' $'x y z' $'!``'" +#define EXPECT1v STRV_MAKE("foo", "'bar'", "\"bar$\"", "x y z", "!``") + + assert_se(lseek(fd, SEEK_SET, 0) == 0); + assert_se(write(fd, CMDLINE1, sizeof CMDLINE1) == sizeof CMDLINE1); + assert_se(ftruncate(fd, sizeof CMDLINE1) == 0); + + assert_se(pid_get_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_QUOTE, &line) >= 0); + log_debug("got: ==%s==", line); + log_debug("exp: ==%s==", EXPECT1); + assert_se(streq(line, EXPECT1)); + line = mfree(line); + + assert_se(pid_get_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_QUOTE_POSIX, &line) >= 0); + log_debug("got: ==%s==", line); + log_debug("exp: ==%s==", EXPECT1p); + assert_se(streq(line, EXPECT1p)); + line = mfree(line); + + assert_se(pid_get_cmdline_strv(0, 0, &args) >= 0); + assert_se(strv_equal(args, EXPECT1v)); + args = strv_free(args); + +#define CMDLINE2 "foo\0\1\2\3\0\0" +#define EXPECT2 "foo \"\\001\\002\\003\"" +#define EXPECT2p "foo $'\\001\\002\\003'" +#define EXPECT2v STRV_MAKE("foo", "\1\2\3") + + assert_se(lseek(fd, SEEK_SET, 0) == 0); + assert_se(write(fd, CMDLINE2, sizeof CMDLINE2) == sizeof CMDLINE2); + assert_se(ftruncate(fd, sizeof CMDLINE2) == 0); + + assert_se(pid_get_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_QUOTE, &line) >= 0); + log_debug("got: ==%s==", line); + log_debug("exp: ==%s==", EXPECT2); + assert_se(streq(line, EXPECT2)); + line = mfree(line); + + assert_se(pid_get_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_QUOTE_POSIX, &line) >= 0); + log_debug("got: ==%s==", line); + log_debug("exp: ==%s==", EXPECT2p); + assert_se(streq(line, EXPECT2p)); + line = mfree(line); + + assert_se(pid_get_cmdline_strv(0, 0, &args) >= 0); + assert_se(strv_equal(args, EXPECT2v)); + args = strv_free(args); + + safe_close(fd); + _exit(EXIT_SUCCESS); +} + +TEST(getpid_cached) { + siginfo_t si; + pid_t a, b, c, d, e, f, child; + + a = raw_getpid(); + b = getpid_cached(); + c = getpid(); + + assert_se(a == b && a == c); + + child = fork(); + assert_se(child >= 0); + + if (child == 0) { + /* In child */ + a = raw_getpid(); + b = getpid_cached(); + c = getpid(); + + assert_se(a == b && a == c); + _exit(EXIT_SUCCESS); + } + + d = raw_getpid(); + e = getpid_cached(); + f = getpid(); + + assert_se(a == d && a == e && a == f); + + assert_se(wait_for_terminate(child, &si) >= 0); + assert_se(si.si_status == 0); + assert_se(si.si_code == CLD_EXITED); +} + +TEST(getpid_measure) { + usec_t t, q; + + unsigned long long iterations = slow_tests_enabled() ? 1000000 : 1000; + + log_info("/* %s (%llu iterations) */", __func__, iterations); + + t = now(CLOCK_MONOTONIC); + for (unsigned long long i = 0; i < iterations; i++) + (void) getpid(); + q = now(CLOCK_MONOTONIC) - t; + + log_info(" glibc getpid(): %lf μs each", (double) q / iterations); + + iterations *= 50; /* _cached() is about 50 times faster, so we need more iterations */ + + t = now(CLOCK_MONOTONIC); + for (unsigned long long i = 0; i < iterations; i++) + (void) getpid_cached(); + q = now(CLOCK_MONOTONIC) - t; + + log_info("getpid_cached(): %lf μs each", (double) q / iterations); +} + +TEST(safe_fork) { + siginfo_t status; + pid_t pid; + int r; + + BLOCK_SIGNALS(SIGCHLD); + + r = safe_fork("(test-child)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_REOPEN_LOG, &pid); + assert_se(r >= 0); + + if (r == 0) { + /* child */ + usleep_safe(100 * USEC_PER_MSEC); + + _exit(88); + } + + assert_se(wait_for_terminate(pid, &status) >= 0); + assert_se(status.si_code == CLD_EXITED); + assert_se(status.si_status == 88); +} + +TEST(pid_to_ptr) { + assert_se(PTR_TO_PID(NULL) == 0); + assert_se(PID_TO_PTR(0) == NULL); + + assert_se(PTR_TO_PID(PID_TO_PTR(1)) == 1); + assert_se(PTR_TO_PID(PID_TO_PTR(2)) == 2); + assert_se(PTR_TO_PID(PID_TO_PTR(-1)) == -1); + assert_se(PTR_TO_PID(PID_TO_PTR(-2)) == -2); + + assert_se(PTR_TO_PID(PID_TO_PTR(INT16_MAX)) == INT16_MAX); + assert_se(PTR_TO_PID(PID_TO_PTR(INT16_MIN)) == INT16_MIN); + + assert_se(PTR_TO_PID(PID_TO_PTR(INT32_MAX)) == INT32_MAX); + assert_se(PTR_TO_PID(PID_TO_PTR(INT32_MIN)) == INT32_MIN); +} + +static void test_ioprio_class_from_to_string_one(const char *val, int expected, int normalized) { + assert_se(ioprio_class_from_string(val) == expected); + if (expected >= 0) { + _cleanup_free_ char *s = NULL; + unsigned ret; + int combined; + + assert_se(ioprio_class_to_string_alloc(expected, &s) == 0); + /* We sometimes get a class number and sometimes a name back */ + assert_se(streq(s, val) || + safe_atou(val, &ret) == 0); + + /* Make sure normalization works, i.e. NONE → BE gets normalized */ + combined = ioprio_normalize(ioprio_prio_value(expected, 0)); + assert_se(ioprio_prio_class(combined) == normalized); + assert_se(expected != IOPRIO_CLASS_NONE || ioprio_prio_data(combined) == 4); + } +} + +TEST(ioprio_class_from_to_string) { + test_ioprio_class_from_to_string_one("none", IOPRIO_CLASS_NONE, IOPRIO_CLASS_BE); + test_ioprio_class_from_to_string_one("realtime", IOPRIO_CLASS_RT, IOPRIO_CLASS_RT); + test_ioprio_class_from_to_string_one("best-effort", IOPRIO_CLASS_BE, IOPRIO_CLASS_BE); + test_ioprio_class_from_to_string_one("idle", IOPRIO_CLASS_IDLE, IOPRIO_CLASS_IDLE); + test_ioprio_class_from_to_string_one("0", IOPRIO_CLASS_NONE, IOPRIO_CLASS_BE); + test_ioprio_class_from_to_string_one("1", 1, 1); + test_ioprio_class_from_to_string_one("7", 7, 7); + test_ioprio_class_from_to_string_one("8", 8, 8); + test_ioprio_class_from_to_string_one("9", -EINVAL, -EINVAL); + test_ioprio_class_from_to_string_one("-1", -EINVAL, -EINVAL); +} + +TEST(setpriority_closest) { + int r; + + r = safe_fork("(test-setprio)", + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_WAIT|FORK_LOG, NULL); + assert_se(r >= 0); + + if (r == 0) { + bool full_test; + int p, q; + /* child */ + + /* rlimit of 30 equals nice level of -10 */ + if (setrlimit(RLIMIT_NICE, &RLIMIT_MAKE_CONST(30)) < 0) { + /* If this fails we are probably unprivileged or in a userns of some kind, let's skip + * the full test */ + assert_se(ERRNO_IS_PRIVILEGE(errno)); + full_test = false; + } else { + /* However, if the hard limit was above 30, setrlimit would succeed unprivileged, so + * check if the UID/GID can be changed before enabling the full test. */ + if (setresgid(GID_NOBODY, GID_NOBODY, GID_NOBODY) < 0) { + assert_se(ERRNO_IS_PRIVILEGE(errno)); + full_test = false; + } else if (setresuid(UID_NOBODY, UID_NOBODY, UID_NOBODY) < 0) { + assert_se(ERRNO_IS_PRIVILEGE(errno)); + full_test = false; + } else + full_test = true; + } + + errno = 0; + p = getpriority(PRIO_PROCESS, 0); + assert_se(errno == 0); + + /* It should always be possible to set our nice level to the current one */ + assert_se(setpriority_closest(p) > 0); + + errno = 0; + q = getpriority(PRIO_PROCESS, 0); + assert_se(errno == 0 && p == q); + + /* It should also be possible to set the nice level to one higher */ + if (p < PRIO_MAX-1) { + assert_se(setpriority_closest(++p) > 0); + + errno = 0; + q = getpriority(PRIO_PROCESS, 0); + assert_se(errno == 0 && p == q); + } + + /* It should also be possible to set the nice level to two higher */ + if (p < PRIO_MAX-1) { + assert_se(setpriority_closest(++p) > 0); + + errno = 0; + q = getpriority(PRIO_PROCESS, 0); + assert_se(errno == 0 && p == q); + } + + if (full_test) { + /* These two should work, given the RLIMIT_NICE we set above */ + assert_se(setpriority_closest(-10) > 0); + errno = 0; + q = getpriority(PRIO_PROCESS, 0); + assert_se(errno == 0 && q == -10); + + assert_se(setpriority_closest(-9) > 0); + errno = 0; + q = getpriority(PRIO_PROCESS, 0); + assert_se(errno == 0 && q == -9); + + /* This should succeed but should be clamped to the limit */ + assert_se(setpriority_closest(-11) == 0); + errno = 0; + q = getpriority(PRIO_PROCESS, 0); + assert_se(errno == 0 && q == -10); + + assert_se(setpriority_closest(-8) > 0); + errno = 0; + q = getpriority(PRIO_PROCESS, 0); + assert_se(errno == 0 && q == -8); + + /* This should succeed but should be clamped to the limit */ + assert_se(setpriority_closest(-12) == 0); + errno = 0; + q = getpriority(PRIO_PROCESS, 0); + assert_se(errno == 0 && q == -10); + } + + _exit(EXIT_SUCCESS); + } +} + +TEST(get_process_ppid) { + uint64_t limit; + int r; + + assert_se(get_process_ppid(1, NULL) == -EADDRNOTAVAIL); + + /* the process with the PID above the global limit definitely doesn't exist. Verify that */ + assert_se(procfs_get_pid_max(&limit) >= 0); + log_debug("kernel.pid_max = %"PRIu64, limit); + + if (limit < INT_MAX) { + r = get_process_ppid(limit + 1, NULL); + log_debug_errno(r, "get_process_limit(%"PRIu64") → %d/%m", limit + 1, r); + assert(r == -ESRCH); + } + + for (pid_t pid = 0;;) { + _cleanup_free_ char *c1 = NULL, *c2 = NULL; + pid_t ppid; + + r = get_process_ppid(pid, &ppid); + if (r == -EADDRNOTAVAIL) { + log_info("No further parent PID"); + break; + } + + assert_se(r >= 0); + + assert_se(pid_get_cmdline(pid, SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK, &c1) >= 0); + assert_se(pid_get_cmdline(ppid, SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK, &c2) >= 0); + + log_info("Parent of " PID_FMT " (%s) is " PID_FMT " (%s).", pid, c1, ppid, c2); + + pid = ppid; + } +} + +TEST(set_oom_score_adjust) { + int a, b, r; + + assert_se(get_oom_score_adjust(&a) >= 0); + + r = set_oom_score_adjust(OOM_SCORE_ADJ_MIN); + assert_se(r >= 0 || ERRNO_IS_PRIVILEGE(r)); + + if (r >= 0) { + assert_se(get_oom_score_adjust(&b) >= 0); + assert_se(b == OOM_SCORE_ADJ_MIN); + } + + assert_se(set_oom_score_adjust(a) >= 0); + assert_se(get_oom_score_adjust(&b) >= 0); + assert_se(b == a); +} + +static void* dummy_thread(void *p) { + int fd = PTR_TO_FD(p); + char x; + + /* let main thread know we are ready */ + assert_se(write(fd, &(const char) { 'x' }, 1) == 1); + + /* wait for the main thread to tell us to shut down */ + assert_se(read(fd, &x, 1) == 1); + return NULL; +} + +TEST(get_process_threads) { + int r; + + /* Run this test in a child, so that we can guarantee there's exactly one thread around in the child */ + r = safe_fork("(nthreads)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_REOPEN_LOG|FORK_WAIT|FORK_LOG, NULL); + assert_se(r >= 0); + + if (r == 0) { + _cleanup_close_pair_ int pfd[2] = EBADF_PAIR, ppfd[2] = EBADF_PAIR; + pthread_t t, tt; + char x; + + assert_se(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, pfd) >= 0); + assert_se(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, ppfd) >= 0); + + assert_se(get_process_threads(0) == 1); + assert_se(pthread_create(&t, NULL, &dummy_thread, FD_TO_PTR(pfd[0])) == 0); + assert_se(read(pfd[1], &x, 1) == 1); + assert_se(get_process_threads(0) == 2); + assert_se(pthread_create(&tt, NULL, &dummy_thread, FD_TO_PTR(ppfd[0])) == 0); + assert_se(read(ppfd[1], &x, 1) == 1); + assert_se(get_process_threads(0) == 3); + + assert_se(write(pfd[1], &(const char) { 'x' }, 1) == 1); + assert_se(pthread_join(t, NULL) == 0); + + /* the value reported via /proc/ is decreased asynchronously, and there appears to be no nice + * way to sync on it. Hence we do the weak >= 2 check, even though == 2 is what we'd actually + * like to check here */ + assert_se(get_process_threads(0) >= 2); + + assert_se(write(ppfd[1], &(const char) { 'x' }, 1) == 1); + assert_se(pthread_join(tt, NULL) == 0); + + /* similar here */ + assert_se(get_process_threads(0) >= 1); + + _exit(EXIT_SUCCESS); + } +} + +TEST(is_reaper_process) { + int r; + + r = safe_fork("(regular)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_WAIT, NULL); + assert_se(r >= 0); + if (r == 0) { + /* child */ + + assert_se(is_reaper_process() == 0); + _exit(EXIT_SUCCESS); + } + + r = safe_fork("(newpid)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_WAIT, NULL); + assert_se(r >= 0); + if (r == 0) { + /* child */ + + if (unshare(CLONE_NEWPID) < 0) { + if (ERRNO_IS_PRIVILEGE(errno) || ERRNO_IS_NOT_SUPPORTED(errno)) { + log_notice("Skipping CLONE_NEWPID reaper check, lacking privileges/support"); + _exit(EXIT_SUCCESS); + } + } + + r = safe_fork("(newpid1)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_WAIT, NULL); + assert_se(r >= 0); + if (r == 0) { + /* grandchild, which is PID1 in a pidns */ + assert_se(getpid_cached() == 1); + assert_se(is_reaper_process() > 0); + _exit(EXIT_SUCCESS); + } + + _exit(EXIT_SUCCESS); + } + + r = safe_fork("(subreaper)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_WAIT, NULL); + assert_se(r >= 0); + if (r == 0) { + /* child */ + assert_se(make_reaper_process(true) >= 0); + + assert_se(is_reaper_process() > 0); + _exit(EXIT_SUCCESS); + } +} + +static int intro(void) { + log_show_color(true); + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/test/test-procfs-util.c b/src/test/test-procfs-util.c new file mode 100644 index 0000000..2b19a98 --- /dev/null +++ b/src/test/test-procfs-util.c @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> + +#include "errno-util.h" +#include "format-util.h" +#include "log.h" +#include "procfs-util.h" +#include "process-util.h" +#include "tests.h" + +int main(int argc, char *argv[]) { + nsec_t nsec; + uint64_t v, pid_max, threads_max, limit; + int r; + + test_setup_logging(LOG_DEBUG); + + assert_se(procfs_cpu_get_usage(&nsec) >= 0); + log_info("Current system CPU time: %s", FORMAT_TIMESPAN(nsec/NSEC_PER_USEC, 1)); + + assert_se(procfs_memory_get_used(&v) >= 0); + log_info("Current memory usage: %s", FORMAT_BYTES(v)); + + assert_se(procfs_tasks_get_current(&v) >= 0); + log_info("Current number of tasks: %" PRIu64, v); + + pid_max = TASKS_MAX; + r = procfs_get_pid_max(&pid_max); + if (r == -ENOENT || ERRNO_IS_NEG_PRIVILEGE(r)) + return log_tests_skipped_errno(r, "can't get pid max"); + assert(r >= 0); + log_info("kernel.pid_max: %"PRIu64, pid_max); + + threads_max = TASKS_MAX; + r = procfs_get_threads_max(&threads_max); + if (r == -ENOENT || ERRNO_IS_NEG_PRIVILEGE(r)) + return log_tests_skipped_errno(r, "can't get threads max"); + assert(r >= 0); + log_info("kernel.threads-max: %"PRIu64, threads_max); + + limit = MIN(pid_max - (pid_max > 0), threads_max); + + assert_se(r >= 0); + log_info("Limit of tasks: %" PRIu64, limit); + assert_se(limit > 0); + + /* This call should never fail, as we're trying to set it to the same limit */ + assert(procfs_tasks_set_limit(limit) >= 0); + + if (limit > 100) { + log_info("Reducing limit by one to %"PRIu64"…", limit-1); + + r = procfs_tasks_set_limit(limit-1); + if (IN_SET(r, -ENOENT, -EROFS) || ERRNO_IS_PRIVILEGE(r)) + return log_tests_skipped_errno(r, "can't set tasks limit"); + assert_se(r >= 0); + + assert_se(procfs_get_pid_max(&v) >= 0); + /* We never decrease the pid_max, so it shouldn't have changed */ + assert_se(v == pid_max); + + assert_se(procfs_get_threads_max(&v) >= 0); + assert_se(v == limit-1); + + assert_se(procfs_tasks_set_limit(limit) >= 0); + + assert_se(procfs_get_pid_max(&v) >= 0); + assert_se(v == pid_max); + + assert_se(procfs_get_threads_max(&v) >= 0); + assert_se(v == limit); + } + + return 0; +} diff --git a/src/test/test-psi-util.c b/src/test/test-psi-util.c new file mode 100644 index 0000000..4ce0811 --- /dev/null +++ b/src/test/test-psi-util.c @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "parse-util.h" +#include "psi-util.h" +#include "tests.h" +#include "tmpfile-util.h" + +TEST(read_mem_pressure) { + _cleanup_(unlink_tempfilep) char path[] = "/tmp/pressurereadtestXXXXXX"; + _cleanup_close_ int fd = -EBADF; + ResourcePressure rp; + + if (geteuid() != 0) + return (void) log_tests_skipped("not root"); + + assert_se((fd = mkostemp_safe(path)) >= 0); + + assert_se(read_resource_pressure("/verylikelynonexistentpath", PRESSURE_TYPE_SOME, &rp) < 0); + assert_se(read_resource_pressure(path, PRESSURE_TYPE_SOME, &rp) < 0); + + assert_se(write_string_file(path, "herpdederp\n", WRITE_STRING_FILE_CREATE) == 0); + assert_se(read_resource_pressure(path, PRESSURE_TYPE_SOME, &rp) < 0); + + /* Pressure file with some invalid values */ + assert_se(write_string_file(path, "some avg10=0.22=55 avg60=0.17=8 avg300=1.11=00 total=58761459\n" + "full avg10=0.23=55 avg60=0.16=8 avg300=1.08=00 total=58464525", WRITE_STRING_FILE_CREATE) == 0); + assert_se(read_resource_pressure(path, PRESSURE_TYPE_SOME, &rp) < 0); + + /* Same pressure valid values as below but with duplicate avg60 field */ + assert_se(write_string_file(path, "some avg10=0.22 avg60=0.17 avg60=0.18 avg300=1.11 total=58761459\n" + "full avg10=0.23 avg60=0.16 avg300=1.08 total=58464525", WRITE_STRING_FILE_CREATE) == 0); + assert_se(read_resource_pressure(path, PRESSURE_TYPE_SOME, &rp) < 0); + + assert_se(write_string_file(path, "some avg10=0.22 avg60=0.17 avg300=1.11 total=58761459\n" + "full avg10=0.23 avg60=0.16 avg300=1.08 total=58464525", WRITE_STRING_FILE_CREATE) == 0); + assert_se(read_resource_pressure(path, PRESSURE_TYPE_SOME, &rp) == 0); + assert_se(LOADAVG_INT_SIDE(rp.avg10) == 0); + assert_se(LOADAVG_DECIMAL_SIDE(rp.avg10) == 22); + assert_se(LOADAVG_INT_SIDE(rp.avg60) == 0); + assert_se(LOADAVG_DECIMAL_SIDE(rp.avg60) == 17); + assert_se(LOADAVG_INT_SIDE(rp.avg300) == 1); + assert_se(LOADAVG_DECIMAL_SIDE(rp.avg300) == 11); + assert_se(rp.total == 58761459); + assert_se(read_resource_pressure(path, PRESSURE_TYPE_FULL, &rp) == 0); + assert_se(LOADAVG_INT_SIDE(rp.avg10) == 0); + assert_se(LOADAVG_DECIMAL_SIDE(rp.avg10) == 23); + assert_se(LOADAVG_INT_SIDE(rp.avg60) == 0); + assert_se(LOADAVG_DECIMAL_SIDE(rp.avg60) == 16); + assert_se(LOADAVG_INT_SIDE(rp.avg300) == 1); + assert_se(LOADAVG_DECIMAL_SIDE(rp.avg300) == 8); + assert_se(rp.total == 58464525); + + /* Pressure file with extra unsupported fields */ + assert_se(write_string_file(path, "some avg5=0.55 avg10=0.22 avg60=0.17 avg300=1.11 total=58761459\n" + "full avg10=0.23 avg60=0.16 avg300=1.08 avg600=2.00 total=58464525", WRITE_STRING_FILE_CREATE) == 0); + assert_se(read_resource_pressure(path, PRESSURE_TYPE_SOME, &rp) == 0); + assert_se(LOADAVG_INT_SIDE(rp.avg10) == 0); + assert_se(LOADAVG_DECIMAL_SIDE(rp.avg10) == 22); + assert_se(LOADAVG_INT_SIDE(rp.avg60) == 0); + assert_se(LOADAVG_DECIMAL_SIDE(rp.avg60) == 17); + assert_se(LOADAVG_INT_SIDE(rp.avg300) == 1); + assert_se(LOADAVG_DECIMAL_SIDE(rp.avg300) == 11); + assert_se(rp.total == 58761459); + assert_se(read_resource_pressure(path, PRESSURE_TYPE_FULL, &rp) == 0); + assert_se(LOADAVG_INT_SIDE(rp.avg10) == 0); + assert_se(LOADAVG_DECIMAL_SIDE(rp.avg10) == 23); + assert_se(LOADAVG_INT_SIDE(rp.avg60) == 0); + assert_se(LOADAVG_DECIMAL_SIDE(rp.avg60) == 16); + assert_se(LOADAVG_INT_SIDE(rp.avg300) == 1); + assert_se(LOADAVG_DECIMAL_SIDE(rp.avg300) == 8); + assert_se(rp.total == 58464525); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-qrcode-util.c b/src/test/test-qrcode-util.c new file mode 100644 index 0000000..221ad85 --- /dev/null +++ b/src/test/test-qrcode-util.c @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "locale-util.h" +#include "main-func.h" +#include "qrcode-util.h" +#include "tests.h" + +static int run(int argc, char **argv) { + int r; + + test_setup_logging(LOG_DEBUG); + + assert_se(setenv("SYSTEMD_COLORS", "1", 1) == 0); /* Force the qrcode to be printed */ + + r = print_qrcode(stdout, "This should say \"TEST\"", "TEST"); + if (r == -EOPNOTSUPP) + return log_tests_skipped("not supported"); + if (r < 0) + return log_error_errno(r, "Failed to print QR code: %m"); + return 0; +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/test/test-random-util.c b/src/test/test-random-util.c new file mode 100644 index 0000000..e597271 --- /dev/null +++ b/src/test/test-random-util.c @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <math.h> + +#include "hexdecoct.h" +#include "log.h" +#include "memory-util.h" +#include "random-util.h" +#include "terminal-util.h" +#include "tests.h" + +TEST(random_bytes) { + uint8_t buf[16] = {}; + + for (size_t i = 1; i < sizeof buf; i++) { + random_bytes(buf, i); + if (i + 1 < sizeof buf) + assert_se(buf[i] == 0); + + hexdump(stdout, buf, i); + } +} + +TEST(crypto_random_bytes) { + uint8_t buf[16] = {}; + + for (size_t i = 1; i < sizeof buf; i++) { + assert_se(crypto_random_bytes(buf, i) == 0); + if (i + 1 < sizeof buf) + assert_se(buf[i] == 0); + + hexdump(stdout, buf, i); + } +} + +#define TOTAL 100000 + +static void test_random_u64_range_one(unsigned mod) { + log_info("/* %s(%u) */", __func__, mod); + + unsigned max = 0, count[mod]; + zero(count); + + for (unsigned i = 0; i < TOTAL; i++) { + uint64_t x; + + x = random_u64_range(mod); + + count[x]++; + max = MAX(max, count[x]); + } + + /* Print histogram: vertical axis — value, horizontal axis — count. + * + * The expected value is always TOTAL/mod, because the distribution should be flat. The expected + * variance is TOTAL×p×(1-p), where p==1/mod, and standard deviation the root of the variance. + * Assert that the deviation from the expected value is less than 6 standard deviations. + */ + unsigned scale = 2 * max / (columns() < 20 ? 80 : columns() - 20); + double exp = (double) TOTAL / mod; + + for (size_t i = 0; i < mod; i++) { + double dev = (count[i] - exp) / sqrt(exp * (mod > 1 ? mod - 1 : 1) / mod); + log_debug("%02zu: %5u (%+.3f)%*s", + i, count[i], dev, + (int) (count[i] / scale), "x"); + + assert_se(fabs(dev) < 6); /* 6 sigma is excessive, but this check should be enough to + * identify catastrophic failure while minimizing false + * positives. */ + } +} + +TEST(random_u64_range) { + for (unsigned mod = 1; mod < 29; mod++) + test_random_u64_range_one(mod); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-ratelimit.c b/src/test/test-ratelimit.c new file mode 100644 index 0000000..de208c7 --- /dev/null +++ b/src/test/test-ratelimit.c @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <unistd.h> + +#include "macro.h" +#include "ratelimit.h" +#include "tests.h" +#include "time-util.h" + +TEST(ratelimit_below) { + int i; + RateLimit ratelimit = { 1 * USEC_PER_SEC, 10 }; + + for (i = 0; i < 10; i++) + assert_se(ratelimit_below(&ratelimit)); + assert_se(!ratelimit_below(&ratelimit)); + sleep(1); + for (i = 0; i < 10; i++) + assert_se(ratelimit_below(&ratelimit)); + + ratelimit = (const RateLimit) { 0, 10 }; + for (i = 0; i < 10000; i++) + assert_se(ratelimit_below(&ratelimit)); +} + +TEST(ratelimit_num_dropped) { + int i; + RateLimit ratelimit = { 1 * USEC_PER_SEC, 10 }; + + for (i = 0; i < 10; i++) { + assert_se(ratelimit_below(&ratelimit)); + assert_se(ratelimit_num_dropped(&ratelimit) == 0); + } + assert_se(!ratelimit_below(&ratelimit)); + assert_se(ratelimit_num_dropped(&ratelimit) == 1); + assert_se(!ratelimit_below(&ratelimit)); + assert_se(ratelimit_num_dropped(&ratelimit) == 2); + sleep(1); + assert_se(ratelimit_below(&ratelimit)); + assert_se(ratelimit_num_dropped(&ratelimit) == 0); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-raw-clone.c b/src/test/test-raw-clone.c new file mode 100644 index 0000000..23ec7d1 --- /dev/null +++ b/src/test/test-raw-clone.c @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <sys/wait.h> +#include <unistd.h> + +#include "errno-util.h" +#include "format-util.h" +#include "missing_syscall.h" +#include "raw-clone.h" +#include "tests.h" + +TEST(raw_clone) { + pid_t parent, pid, pid2; + + parent = getpid(); + log_info("before clone: getpid()→"PID_FMT, parent); + assert_se(raw_getpid() == parent); + + pid = raw_clone(0); + assert_se(pid >= 0); + + pid2 = raw_getpid(); + log_info("raw_clone: "PID_FMT" getpid()→"PID_FMT" raw_getpid()→"PID_FMT, + pid, getpid(), pid2); + if (pid == 0) { + assert_se(pid2 != parent); + _exit(EXIT_SUCCESS); + } else { + int status; + + assert_se(pid2 == parent); + waitpid(pid, &status, __WCLONE); + assert_se(WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS); + } + + errno = 0; + assert_se(raw_clone(CLONE_FS|CLONE_NEWNS) == -1); + assert_se(errno == EINVAL || ERRNO_IS_PRIVILEGE(errno)); /* Certain container environments prohibit namespaces to us, don't fail in that case */ +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-recurse-dir.c b/src/test/test-recurse-dir.c new file mode 100644 index 0000000..8684d06 --- /dev/null +++ b/src/test/test-recurse-dir.c @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <ftw.h> + +#include "fd-util.h" +#include "log.h" +#include "missing_magic.h" +#include "recurse-dir.h" +#include "strv.h" +#include "tests.h" + +static char **list_nftw = NULL; + +static int nftw_cb( + const char *fpath, + const struct stat *sb, + int typeflag, + struct FTW *ftwbuf) { + + if (ftwbuf->level == 0) /* skip top-level */ + return FTW_CONTINUE; + + switch (typeflag) { + + case FTW_F: + log_debug("ftw found %s", fpath); + assert_se(strv_extend(&list_nftw, fpath) >= 0); + break; + + case FTW_SL: + log_debug("ftw found symlink %s, ignoring.", fpath); + break; + + case FTW_D: + log_debug("ftw entering %s", fpath); + assert_se(strv_extendf(&list_nftw, "%s/", fpath) >= 0); + break; + + case FTW_DNR: + log_debug("ftw open directory failed %s", fpath); + break; + + case FTW_NS: + log_debug("ftw stat inode failed %s", fpath); + break; + + case FTW_DP: + case FTW_SLN: + default: + assert_not_reached(); + } + + return FTW_CONTINUE; +} + +static int recurse_dir_callback( + RecurseDirEvent event, + const char *path, + int dir_fd, + int inode_fd, + const struct dirent *de, + const struct statx *sx, + void *userdata) { + + char ***l = userdata; + + assert_se(path); + assert_se(de); + + switch (event) { + + case RECURSE_DIR_ENTRY: + assert_se(!IN_SET(de->d_type, DT_UNKNOWN, DT_DIR)); + + log_debug("found %s%s", path, + de->d_type == DT_LNK ? ", ignoring." : ""); + + if (de->d_type != DT_LNK) + assert_se(strv_extend(l, path) >= 0); + break; + + case RECURSE_DIR_ENTER: + assert_se(de->d_type == DT_DIR); + + log_debug("entering %s", path); + assert_se(strv_extendf(l, "%s/", path) >= 0); + break; + + case RECURSE_DIR_LEAVE: + log_debug("leaving %s", path); + break; + + case RECURSE_DIR_SKIP_MOUNT: + log_debug("skipping mount %s", path); + break; + + case RECURSE_DIR_SKIP_DEPTH: + log_debug("skipping depth %s", path); + break; + + case RECURSE_DIR_SKIP_OPEN_DIR_ERROR_BASE...RECURSE_DIR_SKIP_OPEN_DIR_ERROR_MAX: + log_debug_errno(event - RECURSE_DIR_SKIP_OPEN_DIR_ERROR_BASE, "failed to open dir %s: %m", path); + break; + + case RECURSE_DIR_SKIP_OPEN_INODE_ERROR_BASE...RECURSE_DIR_SKIP_OPEN_INODE_ERROR_MAX: + log_debug_errno(event - RECURSE_DIR_SKIP_OPEN_INODE_ERROR_BASE, "failed to open inode %s: %m", path); + break; + + case RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE...RECURSE_DIR_SKIP_STAT_INODE_ERROR_MAX: + log_debug_errno(event - RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE, "failed to stat inode %s: %m", path); + break; + + default: + assert_not_reached(); + } + + return RECURSE_DIR_CONTINUE; +} + +int main(int argc, char *argv[]) { + _cleanup_strv_free_ char **list_recurse_dir = NULL; + const char *p; + usec_t t1, t2, t3, t4; + _cleanup_close_ int fd = -EBADF; + + log_show_color(true); + test_setup_logging(LOG_INFO); + + if (argc > 1) + p = argv[1]; + else + p = "/usr/share/man"; /* something hopefully reasonably stable while we run (and limited in size) */ + + fd = open(p, O_DIRECTORY|O_CLOEXEC); + if (fd < 0 && errno == ENOENT) + return log_tests_skipped_errno(errno, "Couldn't open directory %s", p); + assert_se(fd >= 0); + + /* If the test directory is on an overlayfs then files and their directory may return different + * st_dev in stat results, which confuses nftw into thinking they're on different filesystems and + * won't return the result when the FTW_MOUNT flag is set. */ + if (fd_is_fs_type(fd, OVERLAYFS_SUPER_MAGIC)) + return log_tests_skipped("nftw mountpoint detection produces false-positives on overlayfs"); + + /* Enumerate the specified dirs in full, once via nftw(), and once via recurse_dir(), and ensure the + * results are identical. nftw() sometimes skips symlinks (see + * https://github.com/systemd/systemd/issues/29603), so ignore them to avoid bogus errors. */ + + t1 = now(CLOCK_MONOTONIC); + assert_se(recurse_dir(fd, p, 0, UINT_MAX, RECURSE_DIR_SORT|RECURSE_DIR_ENSURE_TYPE|RECURSE_DIR_SAME_MOUNT, recurse_dir_callback, &list_recurse_dir) >= 0); + t2 = now(CLOCK_MONOTONIC); + + t3 = now(CLOCK_MONOTONIC); + assert_se(nftw(p, nftw_cb, 64, FTW_PHYS|FTW_MOUNT) >= 0); + t4 = now(CLOCK_MONOTONIC); + + log_info("recurse_dir(): %s – nftw(): %s", FORMAT_TIMESPAN(t2 - t1, 1), FORMAT_TIMESPAN(t4 - t3, 1)); + + strv_sort(list_recurse_dir); + strv_sort(list_nftw); + + for (size_t i = 0;; i++) { + const char *a = list_nftw ? list_nftw[i] : NULL, + *b = list_recurse_dir ? list_recurse_dir[i] : NULL; + + if (!streq_ptr(a, b)) { + log_error("entry %zu different: %s vs %s", i, strna(a), strna(b)); + assert_not_reached(); + } + + if (!a) + break; + } + + list_nftw = strv_free(list_nftw); + return 0; +} diff --git a/src/test/test-replace-var.c b/src/test/test-replace-var.c new file mode 100644 index 0000000..f861b27 --- /dev/null +++ b/src/test/test-replace-var.c @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdio.h> + +#include "macro.h" +#include "replace-var.h" +#include "string-util.h" +#include "tests.h" + +static char *lookup(const char *variable, void *userdata) { + return strjoin("<<<", variable, ">>>"); +} + +TEST(replace_var) { + char *r; + + assert_se(r = replace_var("@@@foobar@xyz@HALLO@foobar@test@@testtest@TEST@...@@@", lookup, NULL)); + puts(r); + assert_se(streq(r, "@@@foobar@xyz<<<HALLO>>>foobar@test@@testtest<<<TEST>>>...@@@")); + free(r); +} + +TEST(strreplace) { + char *r; + + assert_se(r = strreplace("XYZFFFFXYZFFFFXYZ", "XYZ", "ABC")); + puts(r); + assert_se(streq(r, "ABCFFFFABCFFFFABC")); + free(r); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-rlimit-util.c b/src/test/test-rlimit-util.c new file mode 100644 index 0000000..86d0c04 --- /dev/null +++ b/src/test/test-rlimit-util.c @@ -0,0 +1,139 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <sys/resource.h> + +#include "alloc-util.h" +#include "capability-util.h" +#include "macro.h" +#include "missing_resource.h" +#include "rlimit-util.h" +#include "string-util.h" +#include "tests.h" +#include "time-util.h" + +static void test_rlimit_parse_format_one(int resource, const char *string, rlim_t soft, rlim_t hard, int ret, const char *formatted) { + _cleanup_free_ char *f = NULL; + struct rlimit rl = { + .rlim_cur = 4711, + .rlim_max = 4712, + }, rl2 = { + .rlim_cur = 4713, + .rlim_max = 4714 + }; + + assert_se(rlimit_parse(resource, string, &rl) == ret); + if (ret < 0) + return; + + assert_se(rl.rlim_cur == soft); + assert_se(rl.rlim_max == hard); + + assert_se(rlimit_format(&rl, &f) >= 0); + assert_se(streq(formatted, f)); + + assert_se(rlimit_parse(resource, formatted, &rl2) >= 0); + assert_se(memcmp(&rl, &rl2, sizeof(struct rlimit)) == 0); +} + +TEST(rlimit_parse_format) { + test_rlimit_parse_format_one(RLIMIT_NOFILE, "4:5", 4, 5, 0, "4:5"); + test_rlimit_parse_format_one(RLIMIT_NOFILE, "6", 6, 6, 0, "6"); + test_rlimit_parse_format_one(RLIMIT_NOFILE, "infinity", RLIM_INFINITY, RLIM_INFINITY, 0, "infinity"); + test_rlimit_parse_format_one(RLIMIT_NOFILE, "infinity:infinity", RLIM_INFINITY, RLIM_INFINITY, 0, "infinity"); + test_rlimit_parse_format_one(RLIMIT_NOFILE, "8:infinity", 8, RLIM_INFINITY, 0, "8:infinity"); + test_rlimit_parse_format_one(RLIMIT_CPU, "25min:13h", (25*USEC_PER_MINUTE) / USEC_PER_SEC, (13*USEC_PER_HOUR) / USEC_PER_SEC, 0, "1500:46800"); + test_rlimit_parse_format_one(RLIMIT_NOFILE, "", 0, 0, -EINVAL, NULL); + test_rlimit_parse_format_one(RLIMIT_NOFILE, "5:4", 0, 0, -EILSEQ, NULL); + test_rlimit_parse_format_one(RLIMIT_NOFILE, "5:4:3", 0, 0, -EINVAL, NULL); + test_rlimit_parse_format_one(RLIMIT_NICE, "20", 20, 20, 0, "20"); + test_rlimit_parse_format_one(RLIMIT_NICE, "40", 40, 40, 0, "40"); + test_rlimit_parse_format_one(RLIMIT_NICE, "41", 41, 41, -ERANGE, "41"); + test_rlimit_parse_format_one(RLIMIT_NICE, "0", 0, 0, 0, "0"); + test_rlimit_parse_format_one(RLIMIT_NICE, "-7", 27, 27, 0, "27"); + test_rlimit_parse_format_one(RLIMIT_NICE, "-20", 40, 40, 0, "40"); + test_rlimit_parse_format_one(RLIMIT_NICE, "-21", 41, 41, -ERANGE, "41"); + test_rlimit_parse_format_one(RLIMIT_NICE, "-0", 20, 20, 0, "20"); + test_rlimit_parse_format_one(RLIMIT_NICE, "+7", 13, 13, 0, "13"); + test_rlimit_parse_format_one(RLIMIT_NICE, "+19", 1, 1, 0, "1"); + test_rlimit_parse_format_one(RLIMIT_NICE, "+20", 0, 0, -ERANGE, "0"); + test_rlimit_parse_format_one(RLIMIT_NICE, "+0", 20, 20, 0, "20"); +} + +TEST(rlimit_from_string) { + assert_se(rlimit_from_string("NOFILE") == RLIMIT_NOFILE); + assert_se(rlimit_from_string("LimitNOFILE") == -EINVAL); + assert_se(rlimit_from_string("RLIMIT_NOFILE") == -EINVAL); + assert_se(rlimit_from_string("xxxNOFILE") == -EINVAL); + assert_se(rlimit_from_string("DefaultLimitNOFILE") == -EINVAL); +} + +TEST(rlimit_from_string_harder) { + assert_se(rlimit_from_string_harder("NOFILE") == RLIMIT_NOFILE); + assert_se(rlimit_from_string_harder("LimitNOFILE") == RLIMIT_NOFILE); + assert_se(rlimit_from_string_harder("RLIMIT_NOFILE") == RLIMIT_NOFILE); + assert_se(rlimit_from_string_harder("xxxNOFILE") == -EINVAL); + assert_se(rlimit_from_string_harder("DefaultLimitNOFILE") == -EINVAL); +} + +TEST(rlimit_to_string_all) { + for (int i = 0; i < _RLIMIT_MAX; i++) { + _cleanup_free_ char *prefixed = NULL; + const char *p; + + assert_se(p = rlimit_to_string(i)); + log_info("%i = %s", i, p); + + assert_se(rlimit_from_string(p) == i); + assert_se(rlimit_from_string_harder(p) == i); + + assert_se(prefixed = strjoin("Limit", p)); + + assert_se(rlimit_from_string(prefixed) < 0); + assert_se(rlimit_from_string_harder(prefixed) == i); + + prefixed = mfree(prefixed); + assert_se(prefixed = strjoin("RLIMIT_", p)); + + assert_se(rlimit_from_string(prefixed) < 0); + assert_se(rlimit_from_string_harder(prefixed) == i); + } +} + +TEST(setrlimit) { + struct rlimit old, new, high; + struct rlimit err = { + .rlim_cur = 10, + .rlim_max = 5, + }; + + assert_se(drop_capability(CAP_SYS_RESOURCE) == 0); + + assert_se(getrlimit(RLIMIT_NOFILE, &old) == 0); + new.rlim_cur = MIN(5U, old.rlim_max); + new.rlim_max = old.rlim_max; + assert_se(setrlimit(RLIMIT_NOFILE, &new) >= 0); + + assert_se(streq_ptr(rlimit_to_string(RLIMIT_NOFILE), "NOFILE")); + assert_se(rlimit_to_string(-1) == NULL); + + assert_se(getrlimit(RLIMIT_NOFILE, &old) == 0); + assert_se(setrlimit_closest(RLIMIT_NOFILE, &old) == 0); + assert_se(getrlimit(RLIMIT_NOFILE, &new) == 0); + assert_se(old.rlim_cur == new.rlim_cur); + assert_se(old.rlim_max == new.rlim_max); + + assert_se(getrlimit(RLIMIT_NOFILE, &old) == 0); + high = RLIMIT_MAKE_CONST(old.rlim_max == RLIM_INFINITY ? old.rlim_max : old.rlim_max + 1); + assert_se(setrlimit_closest(RLIMIT_NOFILE, &high) == 0); + assert_se(getrlimit(RLIMIT_NOFILE, &new) == 0); + assert_se(new.rlim_max == old.rlim_max); + assert_se(new.rlim_cur == new.rlim_max); + + assert_se(getrlimit(RLIMIT_NOFILE, &old) == 0); + assert_se(setrlimit_closest(RLIMIT_NOFILE, &err) == -EINVAL); + assert_se(getrlimit(RLIMIT_NOFILE, &new) == 0); + assert_se(old.rlim_cur == new.rlim_cur); + assert_se(old.rlim_max == new.rlim_max); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-rm-rf.c b/src/test/test-rm-rf.c new file mode 100644 index 0000000..4c69bd2 --- /dev/null +++ b/src/test/test-rm-rf.c @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <unistd.h> + +#include "alloc-util.h" +#include "process-util.h" +#include "rm-rf.h" +#include "string-util.h" +#include "tests.h" +#include "tmpfile-util.h" + +static void test_rm_rf_chmod_inner(void) { + _cleanup_(rm_rf_physical_and_freep) char *d = NULL; + const char *a, *b, *x, *y; + struct stat st; + + assert_se(getuid() != 0); + + assert_se(mkdtemp_malloc("/tmp/test-rm-rf.XXXXXXX", &d) >= 0); + a = strjoina(d, "/a"); + b = strjoina(a, "/b"); + x = strjoina(d, "/x"); + y = strjoina(x, "/y"); + + assert_se(mkdir(x, 0700) >= 0); + assert_se(mknod(y, S_IFREG | 0600, 0) >= 0); + + assert_se(chmod(y, 0400) >= 0); + assert_se(chmod(x, 0500) >= 0); + assert_se(chmod(d, 0500) >= 0); + + assert_se(rm_rf(d, REMOVE_PHYSICAL) == -EACCES); + + assert_se(access(d, F_OK) >= 0); + assert_se(access(x, F_OK) >= 0); + assert_se(access(y, F_OK) >= 0); + + assert_se(rm_rf(d, REMOVE_PHYSICAL|REMOVE_CHMOD) >= 0); + + assert_se(access(d, F_OK) >= 0); + assert_se(access(x, F_OK) < 0 && errno == ENOENT); + assert_se(access(y, F_OK) < 0 && errno == ENOENT); + + assert_se(mkdir(a, 0700) >= 0); + assert_se(mkdir(b, 0700) >= 0); + assert_se(mkdir(x, 0700) >= 0); + assert_se(mknod(y, S_IFREG | 0600, 0) >= 0); + + assert_se(chmod(b, 0000) >= 0); + assert_se(chmod(a, 0000) >= 0); + assert_se(chmod(y, 0000) >= 0); + assert_se(chmod(x, 0000) >= 0); + assert_se(chmod(d, 0500) >= 0); + + assert_se(rm_rf(d, REMOVE_PHYSICAL|REMOVE_CHMOD|REMOVE_CHMOD_RESTORE|REMOVE_ONLY_DIRECTORIES) == -ENOTEMPTY); + + assert_se(access(a, F_OK) < 0 && errno == ENOENT); + assert_se(access(d, F_OK) >= 0); + assert_se(stat(d, &st) >= 0 && (st.st_mode & 07777) == 0500); + assert_se(access(x, F_OK) >= 0); + assert_se(stat(x, &st) >= 0 && (st.st_mode & 07777) == 0000); + assert_se(chmod(x, 0700) >= 0); + assert_se(access(y, F_OK) >= 0); + assert_se(stat(y, &st) >= 0 && (st.st_mode & 07777) == 0000); + + assert_se(chmod(y, 0000) >= 0); + assert_se(chmod(x, 0000) >= 0); + assert_se(chmod(d, 0000) >= 0); + + assert_se(rm_rf(d, REMOVE_PHYSICAL|REMOVE_CHMOD|REMOVE_CHMOD_RESTORE) >= 0); + + assert_se(stat(d, &st) >= 0 && (st.st_mode & 07777) == 0000); + assert_se(access(d, F_OK) >= 0); + assert_se(chmod(d, 0700) >= 0); + assert_se(access(x, F_OK) < 0 && errno == ENOENT); + + assert_se(mkdir(x, 0700) >= 0); + assert_se(mknod(y, S_IFREG | 0600, 0) >= 0); + + assert_se(chmod(y, 0000) >= 0); + assert_se(chmod(x, 0000) >= 0); + assert_se(chmod(d, 0000) >= 0); + + assert_se(rm_rf(d, REMOVE_PHYSICAL|REMOVE_CHMOD|REMOVE_ROOT) >= 0); + + assert_se(access(d, F_OK) < 0 && errno == ENOENT); +} + +TEST(rm_rf_chmod) { + int r; + + if (getuid() == 0) { + /* This test only works unpriv (as only then the access mask for the owning user matters), + * hence drop privs here */ + + r = safe_fork("(setresuid)", FORK_DEATHSIG_SIGTERM|FORK_WAIT, NULL); + assert_se(r >= 0); + + if (r == 0) { + /* child */ + + assert_se(setresuid(1, 1, 1) >= 0); + + test_rm_rf_chmod_inner(); + _exit(EXIT_SUCCESS); + } + + return; + } + + test_rm_rf_chmod_inner(); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-sbat.c b/src/test/test-sbat.c new file mode 100644 index 0000000..d8546b1 --- /dev/null +++ b/src/test/test-sbat.c @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* We include efi_config.h after undefining PROJECT_VERSION which is also defined in config.h. */ +#undef PROJECT_VERSION +#include "efi_config.h" + +#include "build.h" +#include "sbat.h" +#include "tests.h" + +TEST(sbat_section_text) { + log_info("---SBAT-----------&<----------------------------------------\n" + "%s" + "%s" + "------------------>&-----------------------------------------", +#ifdef SBAT_DISTRO + SBAT_BOOT_SECTION_TEXT, + SBAT_STUB_SECTION_TEXT +#else + "(not defined)", + "" +#endif + ); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-sched-prio.c b/src/test/test-sched-prio.c new file mode 100644 index 0000000..3c3b8dc --- /dev/null +++ b/src/test/test-sched-prio.c @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2012 Holger Hans Peter Freyther +***/ + +#include <sched.h> + +#include "all-units.h" +#include "macro.h" +#include "manager.h" +#include "rm-rf.h" +#include "tests.h" + +int main(int argc, char *argv[]) { + _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; + _cleanup_(manager_freep) Manager *m = NULL; + Unit *idle_ok, *idle_bad, *rr_ok, *rr_bad, *rr_sched; + Service *ser; + int r; + + test_setup_logging(LOG_INFO); + + r = enter_cgroup_subroot(NULL); + if (r == -ENOMEDIUM) + return log_tests_skipped("cgroupfs not available"); + + /* prepare the test */ + _cleanup_free_ char *unit_dir = NULL; + assert_se(get_testdata_dir("units", &unit_dir) >= 0); + assert_se(set_unit_path(unit_dir) >= 0); + assert_se(runtime_dir = setup_fake_runtime_dir()); + + r = manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &m); + if (manager_errno_skip_test(r)) + return log_tests_skipped_errno(r, "manager_new"); + assert_se(r >= 0); + assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + + /* load idle ok */ + assert_se(manager_load_startable_unit_or_warn(m, "sched_idle_ok.service", NULL, &idle_ok) >= 0); + ser = SERVICE(idle_ok); + assert_se(ser->exec_context.cpu_sched_policy == SCHED_OTHER); + assert_se(ser->exec_context.cpu_sched_priority == 0); + + /* + * load idle bad. This should print a warning but we have no way to look at it. + */ + assert_se(manager_load_startable_unit_or_warn(m, "sched_idle_bad.service", NULL, &idle_bad) >= 0); + ser = SERVICE(idle_ok); + assert_se(ser->exec_context.cpu_sched_policy == SCHED_OTHER); + assert_se(ser->exec_context.cpu_sched_priority == 0); + + /* + * load rr ok. + * Test that the default priority is moving from 0 to 1. + */ + assert_se(manager_load_startable_unit_or_warn(m, "sched_rr_ok.service", NULL, &rr_ok) >= 0); + ser = SERVICE(rr_ok); + assert_se(ser->exec_context.cpu_sched_policy == SCHED_RR); + assert_se(ser->exec_context.cpu_sched_priority == 1); + + /* + * load rr bad. + * Test that the value of 0 and 100 is ignored. + */ + assert_se(manager_load_startable_unit_or_warn(m, "sched_rr_bad.service", NULL, &rr_bad) >= 0); + ser = SERVICE(rr_bad); + assert_se(ser->exec_context.cpu_sched_policy == SCHED_RR); + assert_se(ser->exec_context.cpu_sched_priority == 1); + + /* + * load rr change. + * Test that anything between 1 and 99 can be set. + */ + assert_se(manager_load_startable_unit_or_warn(m, "sched_rr_change.service", NULL, &rr_sched) >= 0); + ser = SERVICE(rr_sched); + assert_se(ser->exec_context.cpu_sched_policy == SCHED_RR); + assert_se(ser->exec_context.cpu_sched_priority == 99); + + return EXIT_SUCCESS; +} diff --git a/src/test/test-sd-hwdb.c b/src/test/test-sd-hwdb.c new file mode 100644 index 0000000..ecb6118 --- /dev/null +++ b/src/test/test-sd-hwdb.c @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-hwdb.h" + +#include "alloc-util.h" +#include "errno-util.h" +#include "errno.h" +#include "hwdb-internal.h" +#include "nulstr-util.h" +#include "tests.h" + +TEST(failed_enumerate) { + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + const char *key, *value; + + assert_se(sd_hwdb_new(&hwdb) == 0); + + assert_se(sd_hwdb_seek(hwdb, "no-such-modalias-should-exist") == 0); + + assert_se(sd_hwdb_enumerate(hwdb, &key, &value) == 0); + assert_se(sd_hwdb_enumerate(hwdb, &key, NULL) == -EINVAL); + assert_se(sd_hwdb_enumerate(hwdb, NULL, &value) == -EINVAL); +} + +#define DELL_MODALIAS \ + "evdev:atkbd:dmi:bvnXXX:bvrYYY:bdZZZ:svnDellXXX:pnYYY:" + +TEST(basic_enumerate) { + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + const char *key, *value; + size_t len1 = 0, len2 = 0; + int r; + + assert_se(sd_hwdb_new(&hwdb) == 0); + + assert_se(sd_hwdb_seek(hwdb, DELL_MODALIAS) == 0); + + for (;;) { + r = sd_hwdb_enumerate(hwdb, &key, &value); + assert_se(IN_SET(r, 0, 1)); + if (r == 0) + break; + assert_se(key); + assert_se(value); + log_debug("A: \"%s\" → \"%s\"", key, value); + len1 += strlen(key) + strlen(value); + } + + SD_HWDB_FOREACH_PROPERTY(hwdb, DELL_MODALIAS, key, value) { + log_debug("B: \"%s\" → \"%s\"", key, value); + len2 += strlen(key) + strlen(value); + } + + assert_se(len1 == len2); +} + +TEST(sd_hwdb_new_from_path) { + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + int r; + + assert_se(sd_hwdb_new_from_path(NULL, &hwdb) == -EINVAL); + assert_se(sd_hwdb_new_from_path("", &hwdb) == -EINVAL); + assert_se(sd_hwdb_new_from_path("/path/that/should/not/exist", &hwdb) < 0); + + NULSTR_FOREACH(hwdb_bin_path, hwdb_bin_paths) { + r = sd_hwdb_new_from_path(hwdb_bin_path, &hwdb); + if (r >= 0) + break; + } + + assert_se(r >= 0); +} + +static int intro(void) { + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + int r; + + r = sd_hwdb_new(&hwdb); + if (r == -ENOENT || ERRNO_IS_PRIVILEGE(r)) + return log_tests_skipped_errno(r, "cannot open hwdb"); + + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/src/test/test-sd-path.c b/src/test/test-sd-path.c new file mode 100644 index 0000000..4f23e3b --- /dev/null +++ b/src/test/test-sd-path.c @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-path.h" + +#include "alloc-util.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" + +TEST(sd_path_lookup) { + for (uint64_t i = 0; i < _SD_PATH_MAX; i++) { + _cleanup_free_ char *t = NULL, *s = NULL; + int r; + + r = sd_path_lookup(i, NULL, &t); + if (i == SD_PATH_USER_RUNTIME && r == -ENXIO) + continue; + assert_se(r == 0); + assert_se(t); + log_info("%02"PRIu64": \"%s\"", i, t); + + assert_se(sd_path_lookup(i, "suffix", &s) == 0); + assert_se(s); + log_info("%02"PRIu64": \"%s\"", i, s); + assert_se(endswith(s, "/suffix")); + } + + char *tt; + assert_se(sd_path_lookup(_SD_PATH_MAX, NULL, &tt) == -EOPNOTSUPP); +} + +TEST(sd_path_lookup_strv) { + for (uint64_t i = 0; i < _SD_PATH_MAX; i++) { + _cleanup_strv_free_ char **t = NULL, **s = NULL; + int r; + + r = sd_path_lookup_strv(i, NULL, &t); + if (i == SD_PATH_USER_RUNTIME && r == -ENXIO) + continue; + assert_se(r == 0); + assert_se(t); + log_info("%02"PRIu64":", i); + STRV_FOREACH(item, t) + log_debug(" %s", *item); + + assert_se(sd_path_lookup_strv(i, "suffix", &s) == 0); + assert_se(s); + log_info("%02"PRIu64":", i); + STRV_FOREACH(item, s) { + assert_se(endswith(*item, "/suffix")); + log_debug(" %s", *item); + } + } + + char *tt; + assert_se(sd_path_lookup(_SD_PATH_MAX, NULL, &tt) == -EOPNOTSUPP); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-seccomp.c b/src/test/test-seccomp.c new file mode 100644 index 0000000..279a155 --- /dev/null +++ b/src/test/test-seccomp.c @@ -0,0 +1,1234 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <fcntl.h> +#include <poll.h> +#include <stdlib.h> +#include <sys/eventfd.h> +#include <sys/mman.h> +#include <sys/personality.h> +#include <sys/shm.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <unistd.h> +#if HAVE_VALGRIND_VALGRIND_H +#include <valgrind/valgrind.h> +#endif + +#include "alloc-util.h" +#include "capability-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "macro.h" +#include "memory-util.h" +#include "missing_sched.h" +#include "missing_syscall.h" +#include "nsflags.h" +#include "nulstr-util.h" +#include "process-util.h" +#include "raw-clone.h" +#include "rm-rf.h" +#include "seccomp-util.h" +#include "set.h" +#include "string-util.h" +#include "tests.h" +#include "tmpfile-util.h" +#include "virt.h" + +/* __NR_socket may be invalid due to libseccomp */ +#if !defined(__NR_socket) || __NR_socket < 0 || defined(__i386__) || defined(__s390x__) || defined(__s390__) || defined(__powerpc64__) || defined(__powerpc__) +/* On these archs, socket() is implemented via the socketcall() syscall multiplexer, + * and we can't restrict it hence via seccomp. */ +# define SECCOMP_RESTRICT_ADDRESS_FAMILIES_BROKEN 1 +#else +# define SECCOMP_RESTRICT_ADDRESS_FAMILIES_BROKEN 0 +#endif + +static bool have_seccomp_privs(void) { + return geteuid() == 0 && have_effective_cap(CAP_SYS_ADMIN) > 0; /* If we are root but CAP_SYS_ADMIN we can't do caps (unless we also do NNP) */ +} + +TEST(parse_syscall_and_errno) { + _cleanup_free_ char *n = NULL; + int e; + + assert_se(parse_syscall_and_errno("uname:EILSEQ", &n, &e) >= 0); + assert_se(streq(n, "uname")); + assert_se(e == errno_from_name("EILSEQ") && e >= 0); + n = mfree(n); + + assert_se(parse_syscall_and_errno("uname:EINVAL", &n, &e) >= 0); + assert_se(streq(n, "uname")); + assert_se(e == errno_from_name("EINVAL") && e >= 0); + n = mfree(n); + + assert_se(parse_syscall_and_errno("@sync:4095", &n, &e) >= 0); + assert_se(streq(n, "@sync")); + assert_se(e == 4095); + n = mfree(n); + + /* If errno is omitted, then e is set to -1 */ + assert_se(parse_syscall_and_errno("mount", &n, &e) >= 0); + assert_se(streq(n, "mount")); + assert_se(e == -1); + n = mfree(n); + + /* parse_syscall_and_errno() does not check the syscall name is valid or not. */ + assert_se(parse_syscall_and_errno("hoge:255", &n, &e) >= 0); + assert_se(streq(n, "hoge")); + assert_se(e == 255); + n = mfree(n); + + /* 0 is also a valid errno. */ + assert_se(parse_syscall_and_errno("hoge:0", &n, &e) >= 0); + assert_se(streq(n, "hoge")); + assert_se(e == 0); + n = mfree(n); + + assert_se(parse_syscall_and_errno("hoge:kill", &n, &e) >= 0); + assert_se(streq(n, "hoge")); + assert_se(e == SECCOMP_ERROR_NUMBER_KILL); + n = mfree(n); + + /* The function checks the syscall name is empty or not. */ + assert_se(parse_syscall_and_errno("", &n, &e) == -EINVAL); + assert_se(parse_syscall_and_errno(":255", &n, &e) == -EINVAL); + + /* errno must be a valid errno name or number between 0 and ERRNO_MAX == 4095, or "kill" */ + assert_se(parse_syscall_and_errno("hoge:4096", &n, &e) == -ERANGE); + assert_se(parse_syscall_and_errno("hoge:-3", &n, &e) == -ERANGE); + assert_se(parse_syscall_and_errno("hoge:12.3", &n, &e) == -EINVAL); + assert_se(parse_syscall_and_errno("hoge:123junk", &n, &e) == -EINVAL); + assert_se(parse_syscall_and_errno("hoge:junk123", &n, &e) == -EINVAL); + assert_se(parse_syscall_and_errno("hoge:255:EILSEQ", &n, &e) == -EINVAL); + assert_se(parse_syscall_and_errno("hoge:-EINVAL", &n, &e) == -EINVAL); + assert_se(parse_syscall_and_errno("hoge:EINVALaaa", &n, &e) == -EINVAL); + assert_se(parse_syscall_and_errno("hoge:", &n, &e) == -EINVAL); +} + +TEST(seccomp_arch_to_string) { + uint32_t a, b; + const char *name; + + a = seccomp_arch_native(); + assert_se(a > 0); + name = seccomp_arch_to_string(a); + assert_se(name); + assert_se(seccomp_arch_from_string(name, &b) >= 0); + assert_se(a == b); +} + +TEST(architecture_table) { + const char *n2; + + NULSTR_FOREACH(n, + "native\0" + "x86\0" + "x86-64\0" + "x32\0" + "arm\0" + "arm64\0" +#ifdef SCMP_ARCH_LOONGARCH64 + "loongarch64\0" +#endif + "mips\0" + "mips64\0" + "mips64-n32\0" + "mips-le\0" + "mips64-le\0" + "mips64-le-n32\0" + "parisc\0" + "parisc64\0" + "ppc\0" + "ppc64\0" + "ppc64-le\0" +#ifdef SCMP_ARCH_RISCV64 + "riscv64\0" +#endif + "s390\0" + "s390x\0") { + uint32_t c; + + assert_se(seccomp_arch_from_string(n, &c) >= 0); + n2 = seccomp_arch_to_string(c); + log_info("seccomp-arch: %s → 0x%"PRIx32" → %s", n, c, n2); + assert_se(streq_ptr(n, n2)); + } +} + +TEST(syscall_filter_set_find) { + assert_se(!syscall_filter_set_find(NULL)); + assert_se(!syscall_filter_set_find("")); + assert_se(!syscall_filter_set_find("quux")); + assert_se(!syscall_filter_set_find("@quux")); + + assert_se(syscall_filter_set_find("@clock") == syscall_filter_sets + SYSCALL_FILTER_SET_CLOCK); + assert_se(syscall_filter_set_find("@default") == syscall_filter_sets + SYSCALL_FILTER_SET_DEFAULT); + assert_se(syscall_filter_set_find("@raw-io") == syscall_filter_sets + SYSCALL_FILTER_SET_RAW_IO); +} + +TEST(filter_sets) { + if (!is_seccomp_available()) { + log_notice("Seccomp not available, skipping %s", __func__); + return; + } + if (!have_seccomp_privs()) { + log_notice("Not privileged, skipping %s", __func__); + return; + } + + for (unsigned i = 0; i < _SYSCALL_FILTER_SET_MAX; i++) { + pid_t pid; + +#if HAVE_VALGRIND_VALGRIND_H + if (RUNNING_ON_VALGRIND && IN_SET(i, SYSCALL_FILTER_SET_DEFAULT, SYSCALL_FILTER_SET_BASIC_IO, SYSCALL_FILTER_SET_SIGNAL)) { + /* valgrind at least requires rt_sigprocmask(), read(), write(). */ + log_info("Running on valgrind, skipping %s", syscall_filter_sets[i].name); + continue; + } +#endif +#if HAS_FEATURE_ADDRESS_SANITIZER + if (IN_SET(i, SYSCALL_FILTER_SET_DEFAULT, SYSCALL_FILTER_SET_BASIC_IO, SYSCALL_FILTER_SET_SIGNAL)) { + /* ASAN at least requires sigaltstack(), read(), write(). */ + log_info("Running on address sanitizer, skipping %s", syscall_filter_sets[i].name); + continue; + } +#endif + + log_info("Testing %s", syscall_filter_sets[i].name); + + pid = fork(); + assert_se(pid >= 0); + + if (pid == 0) { /* Child? */ + int fd, r; + + /* If we look at the default set (or one that includes it), allow-list instead of deny-list */ + if (IN_SET(i, SYSCALL_FILTER_SET_DEFAULT, + SYSCALL_FILTER_SET_SYSTEM_SERVICE, + SYSCALL_FILTER_SET_KNOWN)) + r = seccomp_load_syscall_filter_set(SCMP_ACT_ERRNO(EUCLEAN), syscall_filter_sets + i, SCMP_ACT_ALLOW, true); + else + r = seccomp_load_syscall_filter_set(SCMP_ACT_ALLOW, syscall_filter_sets + i, SCMP_ACT_ERRNO(EUCLEAN), true); + if (r < 0) + _exit(EXIT_FAILURE); + + /* Test the sycall filter with one random system call */ + fd = eventfd(0, EFD_NONBLOCK|EFD_CLOEXEC); + if (IN_SET(i, SYSCALL_FILTER_SET_IO_EVENT, SYSCALL_FILTER_SET_DEFAULT)) + assert_se(fd < 0 && errno == EUCLEAN); + else { + assert_se(fd >= 0); + safe_close(fd); + } + + _exit(EXIT_SUCCESS); + } + + assert_se(wait_for_terminate_and_check(syscall_filter_sets[i].name, pid, WAIT_LOG) == EXIT_SUCCESS); + } +} + +TEST(filter_sets_ordered) { + /* Ensure "@default" always remains at the beginning of the list */ + assert_se(SYSCALL_FILTER_SET_DEFAULT == 0); + assert_se(streq(syscall_filter_sets[0].name, "@default")); + + /* Ensure "@known" always remains at the end of the list */ + assert_se(SYSCALL_FILTER_SET_KNOWN == _SYSCALL_FILTER_SET_MAX - 1); + assert_se(streq(syscall_filter_sets[SYSCALL_FILTER_SET_KNOWN].name, "@known")); + + for (size_t i = 0; i < _SYSCALL_FILTER_SET_MAX; i++) { + const char *p = NULL; + + /* Make sure each group has a description */ + assert_se(!isempty(syscall_filter_sets[0].help)); + + /* Make sure the groups are ordered alphabetically, except for the first and last entries */ + assert_se(i < 2 || i == _SYSCALL_FILTER_SET_MAX - 1 || + strcmp(syscall_filter_sets[i-1].name, syscall_filter_sets[i].name) < 0); + + NULSTR_FOREACH(k, syscall_filter_sets[i].value) { + + /* Ensure each syscall list is in itself ordered, but groups before names */ + assert_se(!p || + (*p == '@' && *k != '@') || + (((*p == '@' && *k == '@') || + (*p != '@' && *k != '@')) && + strcmp(p, k) < 0)); + + p = k; + } + } +} + +TEST(restrict_namespace) { + char *s = NULL; + unsigned long ul; + pid_t pid; + + if (!have_namespaces()) { + log_notice("Testing without namespaces, skipping %s", __func__); + return; + } + + assert_se(namespace_flags_to_string(0, &s) == 0 && isempty(s)); + s = mfree(s); + assert_se(namespace_flags_to_string(CLONE_NEWNS, &s) == 0 && streq(s, "mnt")); + s = mfree(s); + assert_se(namespace_flags_to_string(CLONE_NEWNS|CLONE_NEWIPC, &s) == 0 && streq(s, "ipc mnt")); + s = mfree(s); + assert_se(namespace_flags_to_string(CLONE_NEWCGROUP, &s) == 0 && streq(s, "cgroup")); + s = mfree(s); + + assert_se(namespace_flags_from_string("mnt", &ul) == 0 && ul == CLONE_NEWNS); + assert_se(namespace_flags_from_string(NULL, &ul) == 0 && ul == 0); + assert_se(namespace_flags_from_string("", &ul) == 0 && ul == 0); + assert_se(namespace_flags_from_string("uts", &ul) == 0 && ul == CLONE_NEWUTS); + assert_se(namespace_flags_from_string("mnt uts ipc", &ul) == 0 && ul == (CLONE_NEWNS|CLONE_NEWUTS|CLONE_NEWIPC)); + + assert_se(namespace_flags_to_string(CLONE_NEWUTS, &s) == 0 && streq(s, "uts")); + assert_se(namespace_flags_from_string(s, &ul) == 0 && ul == CLONE_NEWUTS); + s = mfree(s); + assert_se(namespace_flags_from_string("ipc", &ul) == 0 && ul == CLONE_NEWIPC); + assert_se(namespace_flags_to_string(ul, &s) == 0 && streq(s, "ipc")); + s = mfree(s); + + assert_se(namespace_flags_to_string(NAMESPACE_FLAGS_ALL, &s) == 0); + assert_se(streq(s, "cgroup ipc net mnt pid user uts")); + assert_se(namespace_flags_from_string(s, &ul) == 0 && ul == NAMESPACE_FLAGS_ALL); + s = mfree(s); + + if (!is_seccomp_available()) { + log_notice("Seccomp not available, skipping remaining tests in %s", __func__); + return; + } + if (!have_seccomp_privs()) { + log_notice("Not privileged, skipping remaining tests in %s", __func__); + return; + } + + pid = fork(); + assert_se(pid >= 0); + + if (pid == 0) { + + assert_se(seccomp_restrict_namespaces(CLONE_NEWNS|CLONE_NEWNET) >= 0); + + assert_se(unshare(CLONE_NEWNS) == 0); + assert_se(unshare(CLONE_NEWNET) == 0); + assert_se(unshare(CLONE_NEWUTS) == -1); + assert_se(errno == EPERM); + assert_se(unshare(CLONE_NEWIPC) == -1); + assert_se(errno == EPERM); + assert_se(unshare(CLONE_NEWNET|CLONE_NEWUTS) == -1); + assert_se(errno == EPERM); + + /* We use fd 0 (stdin) here, which of course will fail with EINVAL on setns(). Except of course our + * seccomp filter worked, and hits first and makes it return EPERM */ + assert_se(setns(0, CLONE_NEWNS) == -1); + assert_se(errno == EINVAL); + assert_se(setns(0, CLONE_NEWNET) == -1); + assert_se(errno == EINVAL); + assert_se(setns(0, CLONE_NEWUTS) == -1); + assert_se(errno == EPERM); + assert_se(setns(0, CLONE_NEWIPC) == -1); + assert_se(errno == EPERM); + assert_se(setns(0, CLONE_NEWNET|CLONE_NEWUTS) == -1); + assert_se(errno == EPERM); + assert_se(setns(0, 0) == -1); + assert_se(errno == EPERM); + + pid = raw_clone(CLONE_NEWNS); + assert_se(pid >= 0); + if (pid == 0) + _exit(EXIT_SUCCESS); + pid = raw_clone(CLONE_NEWNET); + assert_se(pid >= 0); + if (pid == 0) + _exit(EXIT_SUCCESS); + pid = raw_clone(CLONE_NEWUTS); + assert_se(pid < 0); + assert_se(errno == EPERM); + pid = raw_clone(CLONE_NEWIPC); + assert_se(pid < 0); + assert_se(errno == EPERM); + pid = raw_clone(CLONE_NEWNET|CLONE_NEWUTS); + assert_se(pid < 0); + assert_se(errno == EPERM); + + _exit(EXIT_SUCCESS); + } + + assert_se(wait_for_terminate_and_check("nsseccomp", pid, WAIT_LOG) == EXIT_SUCCESS); +} + +TEST(protect_sysctl) { + pid_t pid; + _cleanup_free_ char *seccomp = NULL; + + if (!is_seccomp_available()) { + log_notice("Seccomp not available, skipping %s", __func__); + return; + } + if (!have_seccomp_privs()) { + log_notice("Not privileged, skipping %s", __func__); + return; + } + + /* in containers _sysctl() is likely missing anyway */ + if (detect_container() > 0) { + log_notice("Testing in container, skipping %s", __func__); + return; + } + + assert_se(get_proc_field("/proc/self/status", "Seccomp", WHITESPACE, &seccomp) == 0); + if (!streq(seccomp, "0")) + log_warning("Warning: seccomp filter detected, results may be unreliable for %s", __func__); + + pid = fork(); + assert_se(pid >= 0); + + if (pid == 0) { +#if defined __NR__sysctl && __NR__sysctl >= 0 + assert_se(syscall(__NR__sysctl, NULL) < 0); + assert_se(IN_SET(errno, EFAULT, ENOSYS)); +#endif + + assert_se(seccomp_protect_sysctl() >= 0); + +#if HAVE_VALGRIND_VALGRIND_H + if (RUNNING_ON_VALGRIND) { + log_info("Running on valgrind, skipping syscall/EPERM test"); + _exit(EXIT_SUCCESS); + } +#endif + +#if defined __NR__sysctl && __NR__sysctl >= 0 + assert_se(syscall(__NR__sysctl, 0, 0, 0) < 0); + assert_se(errno == EPERM); +#endif + + _exit(EXIT_SUCCESS); + } + + assert_se(wait_for_terminate_and_check("sysctlseccomp", pid, WAIT_LOG) == EXIT_SUCCESS); +} + +TEST(protect_syslog) { + pid_t pid; + + if (!is_seccomp_available()) { + log_notice("Seccomp not available, skipping %s", __func__); + return; + } + if (!have_seccomp_privs()) { + log_notice("Not privileged, skipping %s", __func__); + return; + } + + /* in containers syslog() is likely missing anyway */ + if (detect_container() > 0) { + log_notice("Testing in container, skipping %s", __func__); + return; + } + + pid = fork(); + assert_se(pid >= 0); + + if (pid == 0) { +#if defined __NR_syslog && __NR_syslog >= 0 + assert_se(syscall(__NR_syslog, -1, NULL, 0) < 0); + assert_se(errno == EINVAL); +#endif + + assert_se(seccomp_protect_syslog() >= 0); + +#if defined __NR_syslog && __NR_syslog >= 0 + assert_se(syscall(__NR_syslog, 0, 0, 0) < 0); + assert_se(errno == EPERM); +#endif + + _exit(EXIT_SUCCESS); + } + + assert_se(wait_for_terminate_and_check("syslogseccomp", pid, WAIT_LOG) == EXIT_SUCCESS); +} + +TEST(restrict_address_families) { + pid_t pid; + + if (!is_seccomp_available()) { + log_notice("Seccomp not available, skipping %s", __func__); + return; + } + if (!have_seccomp_privs()) { + log_notice("Not privileged, skipping %s", __func__); + return; + } + + pid = fork(); + assert_se(pid >= 0); + + if (pid == 0) { + int fd; + Set *s; + + fd = socket(AF_INET, SOCK_DGRAM, 0); + assert_se(fd >= 0); + safe_close(fd); + + fd = socket(AF_UNIX, SOCK_DGRAM, 0); + assert_se(fd >= 0); + safe_close(fd); + + fd = socket(AF_NETLINK, SOCK_DGRAM, 0); + assert_se(fd >= 0); + safe_close(fd); + + assert_se(s = set_new(NULL)); + assert_se(set_put(s, INT_TO_PTR(AF_UNIX)) >= 0); + + assert_se(seccomp_restrict_address_families(s, false) >= 0); + + fd = socket(AF_INET, SOCK_DGRAM, 0); + assert_se(fd >= 0); + safe_close(fd); + + fd = socket(AF_UNIX, SOCK_DGRAM, 0); +#if SECCOMP_RESTRICT_ADDRESS_FAMILIES_BROKEN + assert_se(fd >= 0); + safe_close(fd); +#else + assert_se(fd < 0); + assert_se(errno == EAFNOSUPPORT); +#endif + + fd = socket(AF_NETLINK, SOCK_DGRAM, 0); + assert_se(fd >= 0); + safe_close(fd); + + set_clear(s); + + assert_se(set_put(s, INT_TO_PTR(AF_INET)) >= 0); + + assert_se(seccomp_restrict_address_families(s, true) >= 0); + + fd = socket(AF_INET, SOCK_DGRAM, 0); + assert_se(fd >= 0); + safe_close(fd); + + fd = socket(AF_UNIX, SOCK_DGRAM, 0); +#if SECCOMP_RESTRICT_ADDRESS_FAMILIES_BROKEN + assert_se(fd >= 0); + safe_close(fd); +#else + assert_se(fd < 0); + assert_se(errno == EAFNOSUPPORT); +#endif + + fd = socket(AF_NETLINK, SOCK_DGRAM, 0); +#if SECCOMP_RESTRICT_ADDRESS_FAMILIES_BROKEN + assert_se(fd >= 0); + safe_close(fd); +#else + assert_se(fd < 0); + assert_se(errno == EAFNOSUPPORT); +#endif + + _exit(EXIT_SUCCESS); + } + + assert_se(wait_for_terminate_and_check("socketseccomp", pid, WAIT_LOG) == EXIT_SUCCESS); +} + +TEST(restrict_realtime) { + pid_t pid; + + if (!is_seccomp_available()) { + log_notice("Seccomp not available, skipping %s", __func__); + return; + } + if (!have_seccomp_privs()) { + log_notice("Not privileged, skipping %s", __func__); + return; + } + + /* in containers RT privs are likely missing anyway */ + if (detect_container() > 0) { + log_notice("Testing in container, skipping %s", __func__); + return; + } + + pid = fork(); + assert_se(pid >= 0); + + if (pid == 0) { + /* On some CI environments, the restriction may be already enabled. */ + if (sched_setscheduler(0, SCHED_FIFO, &(struct sched_param) { .sched_priority = 1 }) < 0) { + log_full_errno(errno == EPERM ? LOG_DEBUG : LOG_WARNING, errno, + "Failed to set scheduler parameter for FIFO: %m"); + assert(errno == EPERM); + } + if (sched_setscheduler(0, SCHED_RR, &(struct sched_param) { .sched_priority = 1 }) < 0) { + log_full_errno(errno == EPERM ? LOG_DEBUG : LOG_WARNING, errno, + "Failed to set scheduler parameter for RR: %m"); + assert(errno == EPERM); + } + + assert_se(sched_setscheduler(0, SCHED_IDLE, &(struct sched_param) { .sched_priority = 0 }) >= 0); + assert_se(sched_setscheduler(0, SCHED_BATCH, &(struct sched_param) { .sched_priority = 0 }) >= 0); + assert_se(sched_setscheduler(0, SCHED_OTHER, &(struct sched_param) {}) >= 0); + + assert_se(seccomp_restrict_realtime_full(ENOANO) >= 0); + + assert_se(sched_setscheduler(0, SCHED_IDLE, &(struct sched_param) { .sched_priority = 0 }) >= 0); + assert_se(sched_setscheduler(0, SCHED_BATCH, &(struct sched_param) { .sched_priority = 0 }) >= 0); + assert_se(sched_setscheduler(0, SCHED_OTHER, &(struct sched_param) {}) >= 0); + + assert_se(sched_setscheduler(0, SCHED_FIFO, &(struct sched_param) { .sched_priority = 1 }) < 0); + assert_se(errno == ENOANO); + assert_se(sched_setscheduler(0, SCHED_RR, &(struct sched_param) { .sched_priority = 1 }) < 0); + assert_se(errno == ENOANO); + + _exit(EXIT_SUCCESS); + } + + assert_se(wait_for_terminate_and_check("realtimeseccomp", pid, WAIT_LOG) == EXIT_SUCCESS); +} + +TEST(memory_deny_write_execute_mmap) { + pid_t pid; + + if (!is_seccomp_available()) { + log_notice("Seccomp not available, skipping %s", __func__); + return; + } + if (!have_seccomp_privs()) { + log_notice("Not privileged, skipping %s", __func__); + return; + } +#if HAVE_VALGRIND_VALGRIND_H + if (RUNNING_ON_VALGRIND) { + log_notice("Running on valgrind, skipping %s", __func__); + return; + } +#endif +#if HAS_FEATURE_ADDRESS_SANITIZER + log_notice("Running on address sanitizer, skipping %s", __func__); + return; +#endif + + pid = fork(); + assert_se(pid >= 0); + + if (pid == 0) { + void *p; + + p = mmap(NULL, page_size(), PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1,0); + assert_se(p != MAP_FAILED); + assert_se(munmap(p, page_size()) >= 0); + + p = mmap(NULL, page_size(), PROT_WRITE|PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1,0); + assert_se(p != MAP_FAILED); + assert_se(munmap(p, page_size()) >= 0); + + assert_se(seccomp_memory_deny_write_execute() >= 0); + + p = mmap(NULL, page_size(), PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1,0); +#if defined(__x86_64__) || defined(__i386__) || defined(__powerpc64__) || defined(__arm__) || defined(__aarch64__) || defined(__loongarch_lp64) + assert_se(p == MAP_FAILED); + assert_se(errno == EPERM); +#endif + /* Depending on kernel, libseccomp, and glibc versions, other architectures + * might fail or not. Let's not assert success. */ + if (p != MAP_FAILED) + assert_se(munmap(p, page_size()) == 0); + + p = mmap(NULL, page_size(), PROT_WRITE|PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1,0); + assert_se(p != MAP_FAILED); + assert_se(munmap(p, page_size()) >= 0); + + _exit(EXIT_SUCCESS); + } + + assert_se(wait_for_terminate_and_check("memoryseccomp-mmap", pid, WAIT_LOG) == EXIT_SUCCESS); +} + +TEST(memory_deny_write_execute_shmat) { + int shmid; + pid_t pid; + uint32_t arch; + + SECCOMP_FOREACH_LOCAL_ARCH(arch) { + log_debug("arch %s: SCMP_SYS(mmap) = %d", seccomp_arch_to_string(arch), SCMP_SYS(mmap)); + log_debug("arch %s: SCMP_SYS(mmap2) = %d", seccomp_arch_to_string(arch), SCMP_SYS(mmap2)); + log_debug("arch %s: SCMP_SYS(shmget) = %d", seccomp_arch_to_string(arch), SCMP_SYS(shmget)); + log_debug("arch %s: SCMP_SYS(shmat) = %d", seccomp_arch_to_string(arch), SCMP_SYS(shmat)); + log_debug("arch %s: SCMP_SYS(shmdt) = %d", seccomp_arch_to_string(arch), SCMP_SYS(shmdt)); + } + + if (!is_seccomp_available()) { + log_notice("Seccomp not available, skipping %s", __func__); + return; + } + if (!have_seccomp_privs() || have_effective_cap(CAP_IPC_OWNER) <= 0) { + log_notice("Not privileged, skipping %s", __func__); + return; + } +#if HAVE_VALGRIND_VALGRIND_H + if (RUNNING_ON_VALGRIND) { + log_notice("Running on valgrind, skipping %s", __func__); + return; + } +#endif +#if HAS_FEATURE_ADDRESS_SANITIZER + log_notice("Running on address sanitizer, skipping %s", __func__); + return; +#endif + + shmid = shmget(IPC_PRIVATE, page_size(), 0); + assert_se(shmid >= 0); + + pid = fork(); + assert_se(pid >= 0); + + if (pid == 0) { + void *p; + + p = shmat(shmid, NULL, 0); + assert_se(p != MAP_FAILED); + assert_se(shmdt(p) == 0); + + p = shmat(shmid, NULL, SHM_EXEC); + assert_se(p != MAP_FAILED); + assert_se(shmdt(p) == 0); + + assert_se(seccomp_memory_deny_write_execute() >= 0); + + p = shmat(shmid, NULL, SHM_EXEC); + log_debug_errno(p == MAP_FAILED ? errno : 0, "shmat(SHM_EXEC): %m"); +#if defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) || defined(__loongarch_lp64) + assert_se(p == MAP_FAILED); + assert_se(errno == EPERM); +#endif + /* Depending on kernel, libseccomp, and glibc versions, other architectures + * might fail or not. Let's not assert success. */ + if (p != MAP_FAILED) + assert_se(shmdt(p) == 0); + + p = shmat(shmid, NULL, 0); + log_debug_errno(p == MAP_FAILED ? errno : 0, "shmat(0): %m"); + assert_se(p != MAP_FAILED); + assert_se(shmdt(p) == 0); + + _exit(EXIT_SUCCESS); + } + + assert_se(wait_for_terminate_and_check("memoryseccomp-shmat", pid, WAIT_LOG) == EXIT_SUCCESS); +} + +TEST(restrict_archs) { + pid_t pid; + + if (!is_seccomp_available()) { + log_notice("Seccomp not available, skipping %s", __func__); + return; + } + if (!have_seccomp_privs()) { + log_notice("Not privileged, skipping %s", __func__); + return; + } + + pid = fork(); + assert_se(pid >= 0); + + if (pid == 0) { + _cleanup_set_free_ Set *s = NULL; + + assert_se(access("/", F_OK) >= 0); + + assert_se(s = set_new(NULL)); + +#ifdef __x86_64__ + assert_se(set_put(s, UINT32_TO_PTR(SCMP_ARCH_X86+1)) >= 0); +#endif + assert_se(seccomp_restrict_archs(s) >= 0); + + assert_se(access("/", F_OK) >= 0); + assert_se(seccomp_restrict_archs(NULL) >= 0); + + assert_se(access("/", F_OK) >= 0); + + _exit(EXIT_SUCCESS); + } + + assert_se(wait_for_terminate_and_check("archseccomp", pid, WAIT_LOG) == EXIT_SUCCESS); +} + +TEST(load_syscall_filter_set_raw) { + pid_t pid; + + if (!is_seccomp_available()) { + log_notice("Seccomp not available, skipping %s", __func__); + return; + } + if (!have_seccomp_privs()) { + log_notice("Not privileged, skipping %s", __func__); + return; + } + + pid = fork(); + assert_se(pid >= 0); + + if (pid == 0) { + _cleanup_hashmap_free_ Hashmap *s = NULL; + + assert_se(access("/", F_OK) >= 0); + assert_se(poll(NULL, 0, 0) == 0); + + assert_se(seccomp_load_syscall_filter_set_raw(SCMP_ACT_ALLOW, NULL, scmp_act_kill_process(), true) >= 0); + assert_se(access("/", F_OK) >= 0); + assert_se(poll(NULL, 0, 0) == 0); + + assert_se(s = hashmap_new(NULL)); +#if defined __NR_access && __NR_access >= 0 + assert_se(hashmap_put(s, UINT32_TO_PTR(__NR_access + 1), INT_TO_PTR(-1)) >= 0); + log_debug("has access()"); +#endif +#if defined __NR_faccessat && __NR_faccessat >= 0 + assert_se(hashmap_put(s, UINT32_TO_PTR(__NR_faccessat + 1), INT_TO_PTR(-1)) >= 0); + log_debug("has faccessat()"); +#endif +#if defined __NR_faccessat2 && __NR_faccessat2 >= 0 + assert_se(hashmap_put(s, UINT32_TO_PTR(__NR_faccessat2 + 1), INT_TO_PTR(-1)) >= 0); + log_debug("has faccessat2()"); +#endif + + assert_se(!hashmap_isempty(s)); + assert_se(seccomp_load_syscall_filter_set_raw(SCMP_ACT_ALLOW, s, SCMP_ACT_ERRNO(EUCLEAN), true) >= 0); + + assert_se(access("/", F_OK) < 0); + assert_se(errno == EUCLEAN); + + assert_se(poll(NULL, 0, 0) == 0); + + hashmap_clear(s); +#if defined __NR_access && __NR_access >= 0 + assert_se(hashmap_put(s, UINT32_TO_PTR(__NR_access + 1), INT_TO_PTR(EILSEQ)) >= 0); +#endif +#if defined __NR_faccessat && __NR_faccessat >= 0 + assert_se(hashmap_put(s, UINT32_TO_PTR(__NR_faccessat + 1), INT_TO_PTR(EILSEQ)) >= 0); +#endif +#if defined __NR_faccessat2 && __NR_faccessat2 >= 0 + assert_se(hashmap_put(s, UINT32_TO_PTR(__NR_faccessat2 + 1), INT_TO_PTR(EILSEQ)) >= 0); +#endif + + assert_se(seccomp_load_syscall_filter_set_raw(SCMP_ACT_ALLOW, s, SCMP_ACT_ERRNO(EUCLEAN), true) >= 0); + + assert_se(access("/", F_OK) < 0); + assert_se(errno == EILSEQ); + + assert_se(poll(NULL, 0, 0) == 0); + + hashmap_clear(s); +#if defined __NR_poll && __NR_poll >= 0 + assert_se(hashmap_put(s, UINT32_TO_PTR(__NR_poll + 1), INT_TO_PTR(-1)) >= 0); + log_debug("has poll()"); +#endif +#if defined __NR_ppoll && __NR_ppoll >= 0 + assert_se(hashmap_put(s, UINT32_TO_PTR(__NR_ppoll + 1), INT_TO_PTR(-1)) >= 0); + log_debug("has ppoll()"); +#endif +#if defined __NR_ppoll_time64 && __NR_ppoll_time64 >= 0 + assert_se(hashmap_put(s, UINT32_TO_PTR(__NR_ppoll_time64 + 1), INT_TO_PTR(-1)) >= 0); + log_debug("has ppoll_time64()"); +#endif + + assert_se(!hashmap_isempty(s)); + assert_se(seccomp_load_syscall_filter_set_raw(SCMP_ACT_ALLOW, s, SCMP_ACT_ERRNO(EUNATCH), true) >= 0); + + assert_se(access("/", F_OK) < 0); + assert_se(errno == EILSEQ); + + assert_se(poll(NULL, 0, 0) < 0); + assert_se(errno == EUNATCH); + + hashmap_clear(s); +#if defined __NR_poll && __NR_poll >= 0 + assert_se(hashmap_put(s, UINT32_TO_PTR(__NR_poll + 1), INT_TO_PTR(EILSEQ)) >= 0); +#endif +#if defined __NR_ppoll && __NR_ppoll >= 0 + assert_se(hashmap_put(s, UINT32_TO_PTR(__NR_ppoll + 1), INT_TO_PTR(EILSEQ)) >= 0); +#endif +#if defined __NR_ppoll_time64 && __NR_ppoll_time64 >= 0 + assert_se(hashmap_put(s, UINT32_TO_PTR(__NR_ppoll_time64 + 1), INT_TO_PTR(EILSEQ)) >= 0); +#endif + + assert_se(seccomp_load_syscall_filter_set_raw(SCMP_ACT_ALLOW, s, SCMP_ACT_ERRNO(EUNATCH), true) >= 0); + + assert_se(access("/", F_OK) < 0); + assert_se(errno == EILSEQ); + + assert_se(poll(NULL, 0, 0) < 0); + assert_se(errno == EILSEQ); + + _exit(EXIT_SUCCESS); + } + + assert_se(wait_for_terminate_and_check("syscallrawseccomp", pid, WAIT_LOG) == EXIT_SUCCESS); +} + +TEST(native_syscalls_filtered) { + pid_t pid; + + if (!is_seccomp_available()) { + log_notice("Seccomp not available, skipping %s", __func__); + return; + } + if (!have_seccomp_privs()) { + log_notice("Not privileged, skipping %s", __func__); + return; + } + + pid = fork(); + assert_se(pid >= 0); + + if (pid == 0) { + _cleanup_set_free_ Set *arch_s = NULL; + _cleanup_hashmap_free_ Hashmap *s = NULL; + + /* Passing "native" or an empty set is equivalent, just do both here. */ + assert_se(arch_s = set_new(NULL)); + assert_se(seccomp_restrict_archs(arch_s) >= 0); + assert_se(set_put(arch_s, SCMP_ARCH_NATIVE) >= 0); + assert_se(seccomp_restrict_archs(arch_s) >= 0); + + assert_se(access("/", F_OK) >= 0); + assert_se(poll(NULL, 0, 0) == 0); + + assert_se(seccomp_load_syscall_filter_set_raw(SCMP_ACT_ALLOW, NULL, scmp_act_kill_process(), true) >= 0); + assert_se(access("/", F_OK) >= 0); + assert_se(poll(NULL, 0, 0) == 0); + + assert_se(s = hashmap_new(NULL)); +#if defined __NR_access && __NR_access >= 0 + assert_se(hashmap_put(s, UINT32_TO_PTR(__NR_access + 1), INT_TO_PTR(-1)) >= 0); + log_debug("has access()"); +#endif +#if defined __NR_faccessat && __NR_faccessat >= 0 + assert_se(hashmap_put(s, UINT32_TO_PTR(__NR_faccessat + 1), INT_TO_PTR(-1)) >= 0); + log_debug("has faccessat()"); +#endif +#if defined __NR_faccessat2 && __NR_faccessat2 >= 0 + assert_se(hashmap_put(s, UINT32_TO_PTR(__NR_faccessat2 + 1), INT_TO_PTR(-1)) >= 0); + log_debug("has faccessat2()"); +#endif + + assert_se(!hashmap_isempty(s)); + assert_se(seccomp_load_syscall_filter_set_raw(SCMP_ACT_ALLOW, s, SCMP_ACT_ERRNO(EUCLEAN), true) >= 0); + + assert_se(access("/", F_OK) < 0); + assert_se(errno == EUCLEAN); + + _exit(EXIT_SUCCESS); + } + + assert_se(wait_for_terminate_and_check("nativeseccomp", pid, WAIT_LOG) == EXIT_SUCCESS); +} + +TEST(lock_personality) { + unsigned long current; + pid_t pid; + + if (!is_seccomp_available()) { + log_notice("Seccomp not available, skipping %s", __func__); + return; + } + if (!have_seccomp_privs()) { + log_notice("Not privileged, skipping %s", __func__); + return; + } + + assert_se(opinionated_personality(¤t) >= 0); + /* On ppc64le sanitizers disable ASLR (i.e. by setting ADDR_NO_RANDOMIZE), + * which opinionated_personality() doesn't return. Let's tweak the current + * personality ourselves in such cases. + * See: https://github.com/llvm/llvm-project/commit/78f7a6eaa601bfdd6ae70ffd3da2254c21ff77f9 + */ + if (FLAGS_SET(safe_personality(PERSONALITY_INVALID), ADDR_NO_RANDOMIZE)) + current |= ADDR_NO_RANDOMIZE; + + log_info("current personality=0x%lX", current); + + pid = fork(); + assert_se(pid >= 0); + + if (pid == 0) { + assert_se(seccomp_lock_personality(current) >= 0); + + assert_se((unsigned long) safe_personality(current) == current); + + /* Note, we also test that safe_personality() works correctly, by checking whether errno is properly + * set, in addition to the return value */ + errno = 0; + assert_se(safe_personality(PER_LINUX | MMAP_PAGE_ZERO) == -EPERM); + assert_se(errno == EPERM); + + if (!FLAGS_SET(current, ADDR_NO_RANDOMIZE)) + assert_se(safe_personality(PER_LINUX | ADDR_NO_RANDOMIZE) == -EPERM); + assert_se(safe_personality(PER_LINUX | ADDR_COMPAT_LAYOUT) == -EPERM); + assert_se(safe_personality(PER_LINUX | READ_IMPLIES_EXEC) == -EPERM); + assert_se(safe_personality(PER_LINUX_32BIT) == -EPERM); + assert_se(safe_personality(PER_SVR4) == -EPERM); + assert_se(safe_personality(PER_BSD) == -EPERM); + assert_se(safe_personality(current == PER_LINUX ? PER_LINUX32 : PER_LINUX) == -EPERM); + assert_se(safe_personality(PER_LINUX32_3GB) == -EPERM); + assert_se(safe_personality(PER_UW7) == -EPERM); + assert_se(safe_personality(0x42) == -EPERM); + + assert_se(safe_personality(PERSONALITY_INVALID) == -EPERM); /* maybe remove this later */ + + assert_se((unsigned long) personality(current) == current); + _exit(EXIT_SUCCESS); + } + + assert_se(wait_for_terminate_and_check("lockpersonalityseccomp", pid, WAIT_LOG) == EXIT_SUCCESS); +} + +static int real_open(const char *path, int flags, mode_t mode) { + /* glibc internally calls openat() when open() is requested. Let's hence define our own wrapper for + * testing purposes that calls the real syscall, on architectures where SYS_open is defined. On + * other architectures, let's just fall back to the glibc call. */ + +#if defined __NR_open && __NR_open >= 0 + return (int) syscall(__NR_open, path, flags, mode); +#else + return open(path, flags, mode); +#endif +} + +static int try_fchmodat2(int dirfd, const char *path, mode_t mode, int flags) { + int r; + + /* glibc does not provide a direct wrapper for fchmodat2(). Let's hence define our own wrapper for + * testing purposes that calls the real syscall, on architectures and in environments where + * SYS_fchmodat2 is defined. Otherwise, let's just fall back to the glibc fchmodat() call. */ + + /* Not supported by fchmodat() */ + assert_se(!FLAGS_SET(flags, AT_EMPTY_PATH)); + + r = RET_NERRNO(fchmodat2(dirfd, path, mode, flags)); + if (r != -ENOSYS) + return r; + + /* The syscall might still be unsupported by kernel or libseccomp. */ + return RET_NERRNO(fchmodat(dirfd, path, mode, flags)); +} + +TEST(restrict_suid_sgid) { + pid_t pid; + + if (!is_seccomp_available()) { + log_notice("Seccomp not available, skipping %s", __func__); + return; + } + if (!have_seccomp_privs()) { + log_notice("Not privileged, skipping %s", __func__); + return; + } + + pid = fork(); + assert_se(pid >= 0); + + if (pid == 0) { + char path[] = "/tmp/suidsgidXXXXXX", dir[] = "/tmp/suidsgiddirXXXXXX"; + int fd = -EBADF, k = -EBADF; + const char *z; + + fd = mkostemp_safe(path); + assert_se(fd >= 0); + + assert_se(mkdtemp(dir)); + z = strjoina(dir, "/test"); + + assert_se(chmod(path, 0755 | S_ISUID) >= 0); + assert_se(chmod(path, 0755 | S_ISGID) >= 0); + assert_se(chmod(path, 0755 | S_ISGID | S_ISUID) >= 0); + assert_se(chmod(path, 0755) >= 0); + + assert_se(fchmod(fd, 0755 | S_ISUID) >= 0); + assert_se(fchmod(fd, 0755 | S_ISGID) >= 0); + assert_se(fchmod(fd, 0755 | S_ISGID | S_ISUID) >= 0); + assert_se(fchmod(fd, 0755) >= 0); + + assert_se(fchmodat(AT_FDCWD, path, 0755 | S_ISUID, 0) >= 0); + assert_se(fchmodat(AT_FDCWD, path, 0755 | S_ISGID, 0) >= 0); + assert_se(fchmodat(AT_FDCWD, path, 0755 | S_ISGID | S_ISUID, 0) >= 0); + assert_se(fchmodat(AT_FDCWD, path, 0755, 0) >= 0); + + assert_se(try_fchmodat2(AT_FDCWD, path, 0755 | S_ISUID, 0) >= 0); + assert_se(try_fchmodat2(AT_FDCWD, path, 0755 | S_ISGID, 0) >= 0); + assert_se(try_fchmodat2(AT_FDCWD, path, 0755 | S_ISGID | S_ISUID, 0) >= 0); + assert_se(try_fchmodat2(AT_FDCWD, path, 0755, 0) >= 0); + + k = real_open(z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644 | S_ISUID); + k = safe_close(k); + assert_se(unlink(z) >= 0); + + k = real_open(z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644 | S_ISGID); + k = safe_close(k); + assert_se(unlink(z) >= 0); + + k = real_open(z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644 | S_ISUID | S_ISGID); + k = safe_close(k); + assert_se(unlink(z) >= 0); + + k = real_open(z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644); + k = safe_close(k); + assert_se(unlink(z) >= 0); + + k = creat(z, 0644 | S_ISUID); + k = safe_close(k); + assert_se(unlink(z) >= 0); + + k = creat(z, 0644 | S_ISGID); + k = safe_close(k); + assert_se(unlink(z) >= 0); + + k = creat(z, 0644 | S_ISUID | S_ISGID); + k = safe_close(k); + assert_se(unlink(z) >= 0); + + k = creat(z, 0644); + k = safe_close(k); + assert_se(unlink(z) >= 0); + + k = openat(AT_FDCWD, z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644 | S_ISUID); + k = safe_close(k); + assert_se(unlink(z) >= 0); + + k = openat(AT_FDCWD, z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644 | S_ISGID); + k = safe_close(k); + assert_se(unlink(z) >= 0); + + k = openat(AT_FDCWD, z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644 | S_ISUID | S_ISGID); + k = safe_close(k); + assert_se(unlink(z) >= 0); + + k = openat(AT_FDCWD, z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644); + k = safe_close(k); + assert_se(unlink(z) >= 0); + + assert_se(mkdir(z, 0755 | S_ISUID) >= 0); + assert_se(rmdir(z) >= 0); + assert_se(mkdir(z, 0755 | S_ISGID) >= 0); + assert_se(rmdir(z) >= 0); + assert_se(mkdir(z, 0755 | S_ISUID | S_ISGID) >= 0); + assert_se(rmdir(z) >= 0); + assert_se(mkdir(z, 0755) >= 0); + assert_se(rmdir(z) >= 0); + + assert_se(mkdirat(AT_FDCWD, z, 0755 | S_ISUID) >= 0); + assert_se(rmdir(z) >= 0); + assert_se(mkdirat(AT_FDCWD, z, 0755 | S_ISGID) >= 0); + assert_se(rmdir(z) >= 0); + assert_se(mkdirat(AT_FDCWD, z, 0755 | S_ISUID | S_ISGID) >= 0); + assert_se(rmdir(z) >= 0); + assert_se(mkdirat(AT_FDCWD, z, 0755) >= 0); + assert_se(rmdir(z) >= 0); + + assert_se(mknod(z, S_IFREG | 0755 | S_ISUID, 0) >= 0); + assert_se(unlink(z) >= 0); + assert_se(mknod(z, S_IFREG | 0755 | S_ISGID, 0) >= 0); + assert_se(unlink(z) >= 0); + assert_se(mknod(z, S_IFREG | 0755 | S_ISUID | S_ISGID, 0) >= 0); + assert_se(unlink(z) >= 0); + assert_se(mknod(z, S_IFREG | 0755, 0) >= 0); + assert_se(unlink(z) >= 0); + + assert_se(mknodat(AT_FDCWD, z, S_IFREG | 0755 | S_ISUID, 0) >= 0); + assert_se(unlink(z) >= 0); + assert_se(mknodat(AT_FDCWD, z, S_IFREG | 0755 | S_ISGID, 0) >= 0); + assert_se(unlink(z) >= 0); + assert_se(mknodat(AT_FDCWD, z, S_IFREG | 0755 | S_ISUID | S_ISGID, 0) >= 0); + assert_se(unlink(z) >= 0); + assert_se(mknodat(AT_FDCWD, z, S_IFREG | 0755, 0) >= 0); + assert_se(unlink(z) >= 0); + + assert_se(seccomp_restrict_suid_sgid() >= 0); + + assert_se(chmod(path, 0775 | S_ISUID) < 0 && errno == EPERM); + assert_se(chmod(path, 0775 | S_ISGID) < 0 && errno == EPERM); + assert_se(chmod(path, 0775 | S_ISGID | S_ISUID) < 0 && errno == EPERM); + assert_se(chmod(path, 0775) >= 0); + + assert_se(fchmod(fd, 0775 | S_ISUID) < 0 && errno == EPERM); + assert_se(fchmod(fd, 0775 | S_ISGID) < 0 && errno == EPERM); + assert_se(fchmod(fd, 0775 | S_ISGID | S_ISUID) < 0 && errno == EPERM); + assert_se(fchmod(fd, 0775) >= 0); + + assert_se(fchmodat(AT_FDCWD, path, 0755 | S_ISUID, 0) < 0 && errno == EPERM); + assert_se(fchmodat(AT_FDCWD, path, 0755 | S_ISGID, 0) < 0 && errno == EPERM); + assert_se(fchmodat(AT_FDCWD, path, 0755 | S_ISGID | S_ISUID, 0) < 0 && errno == EPERM); + assert_se(fchmodat(AT_FDCWD, path, 0755, 0) >= 0); + + assert_se(try_fchmodat2(AT_FDCWD, path, 0755 | S_ISUID, 0) < 0 && errno == EPERM); + assert_se(try_fchmodat2(AT_FDCWD, path, 0755 | S_ISGID, 0) < 0 && errno == EPERM); + assert_se(try_fchmodat2(AT_FDCWD, path, 0755 | S_ISGID | S_ISUID, 0) < 0 && errno == EPERM); + assert_se(try_fchmodat2(AT_FDCWD, path, 0755, 0) >= 0); + + assert_se(real_open(z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644 | S_ISUID) < 0 && errno == EPERM); + assert_se(real_open(z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644 | S_ISGID) < 0 && errno == EPERM); + assert_se(real_open(z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644 | S_ISUID | S_ISGID) < 0 && errno == EPERM); + k = real_open(z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644); + k = safe_close(k); + assert_se(unlink(z) >= 0); + + assert_se(creat(z, 0644 | S_ISUID) < 0 && errno == EPERM); + assert_se(creat(z, 0644 | S_ISGID) < 0 && errno == EPERM); + assert_se(creat(z, 0644 | S_ISUID | S_ISGID) < 0 && errno == EPERM); + k = creat(z, 0644); + k = safe_close(k); + assert_se(unlink(z) >= 0); + + assert_se(openat(AT_FDCWD, z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644 | S_ISUID) < 0 && errno == EPERM); + assert_se(openat(AT_FDCWD, z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644 | S_ISGID) < 0 && errno == EPERM); + assert_se(openat(AT_FDCWD, z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644 | S_ISUID | S_ISGID) < 0 && errno == EPERM); + k = openat(AT_FDCWD, z, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL, 0644); + k = safe_close(k); + assert_se(unlink(z) >= 0); + + assert_se(mkdir(z, 0755 | S_ISUID) < 0 && errno == EPERM); + assert_se(mkdir(z, 0755 | S_ISGID) < 0 && errno == EPERM); + assert_se(mkdir(z, 0755 | S_ISUID | S_ISGID) < 0 && errno == EPERM); + assert_se(mkdir(z, 0755) >= 0); + assert_se(rmdir(z) >= 0); + + assert_se(mkdirat(AT_FDCWD, z, 0755 | S_ISUID) < 0 && errno == EPERM); + assert_se(mkdirat(AT_FDCWD, z, 0755 | S_ISGID) < 0 && errno == EPERM); + assert_se(mkdirat(AT_FDCWD, z, 0755 | S_ISUID | S_ISGID) < 0 && errno == EPERM); + assert_se(mkdirat(AT_FDCWD, z, 0755) >= 0); + assert_se(rmdir(z) >= 0); + + assert_se(mknod(z, S_IFREG | 0755 | S_ISUID, 0) < 0 && errno == EPERM); + assert_se(mknod(z, S_IFREG | 0755 | S_ISGID, 0) < 0 && errno == EPERM); + assert_se(mknod(z, S_IFREG | 0755 | S_ISUID | S_ISGID, 0) < 0 && errno == EPERM); + assert_se(mknod(z, S_IFREG | 0755, 0) >= 0); + assert_se(unlink(z) >= 0); + + assert_se(mknodat(AT_FDCWD, z, S_IFREG | 0755 | S_ISUID, 0) < 0 && errno == EPERM); + assert_se(mknodat(AT_FDCWD, z, S_IFREG | 0755 | S_ISGID, 0) < 0 && errno == EPERM); + assert_se(mknodat(AT_FDCWD, z, S_IFREG | 0755 | S_ISUID | S_ISGID, 0) < 0 && errno == EPERM); + assert_se(mknodat(AT_FDCWD, z, S_IFREG | 0755, 0) >= 0); + assert_se(unlink(z) >= 0); + + assert_se(unlink(path) >= 0); + assert_se(rm_rf(dir, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + + _exit(EXIT_SUCCESS); + } + + assert_se(wait_for_terminate_and_check("suidsgidseccomp", pid, WAIT_LOG) == EXIT_SUCCESS); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-secure-bits.c b/src/test/test-secure-bits.c new file mode 100644 index 0000000..27e6a20 --- /dev/null +++ b/src/test/test-secure-bits.c @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> + +#include "securebits-util.h" +#include "strv.h" +#include "tests.h" +#include "unit-file.h" + +static const char * const string_bits[] = { + "keep-caps", + "keep-caps-locked", + "no-setuid-fixup", + "no-setuid-fixup-locked", + "noroot", + "noroot-locked", + NULL +}; + +TEST(secure_bits_basic) { + _cleanup_free_ char *joined = NULL, *str = NULL; + int r; + + /* Check if converting each bit from string and back to string yields + * the same value */ + STRV_FOREACH(bit, string_bits) { + _cleanup_free_ char *s = NULL; + + r = secure_bits_from_string(*bit); + assert_se(r > 0); + assert_se(secure_bits_is_valid(r)); + assert_se(secure_bits_to_string_alloc(r, &s) >= 0); + printf("%s = 0x%x = %s\n", *bit, (unsigned)r, s); + assert_se(streq(*bit, s)); + } + + /* Ditto, but with all bits at once */ + joined = strv_join((char**)string_bits, " "); + assert_se(joined); + r = secure_bits_from_string(joined); + assert_se(r > 0); + assert_se(secure_bits_is_valid(r)); + assert_se(secure_bits_to_string_alloc(r, &str) >= 0); + printf("%s = 0x%x = %s\n", joined, (unsigned)r, str); + assert_se(streq(joined, str)); + + str = mfree(str); + + /* Empty string */ + assert_se(secure_bits_from_string("") == 0); + assert_se(secure_bits_from_string(" ") == 0); + + /* Only invalid entries */ + assert_se(secure_bits_from_string("foo bar baz") == 0); + + /* Empty secure bits */ + assert_se(secure_bits_to_string_alloc(0, &str) >= 0); + assert_se(isempty(str)); + + str = mfree(str); + + /* Bits to string with check */ + assert_se(secure_bits_to_string_alloc_with_check(INT_MAX, &str) == -EINVAL); + assert_se(str == NULL); + assert_se(secure_bits_to_string_alloc_with_check( + (1 << SECURE_KEEP_CAPS) | (1 << SECURE_KEEP_CAPS_LOCKED), + &str) >= 0); + assert_se(streq(str, "keep-caps keep-caps-locked")); +} + +TEST(secure_bits_mix) { + static struct sbit_table { + const char *input; + const char *expected; + } sbit_table[] = { + { "keep-caps keep-caps keep-caps", "keep-caps" }, + { "keep-caps noroot keep-caps", "keep-caps noroot" }, + { "noroot foo bar baz noroot", "noroot" }, + { "noroot \"foo\" \"bar keep-caps", "noroot" }, + { "\"noroot foo\" bar keep-caps", "keep-caps" }, + {} + }; + + for (const struct sbit_table *s = sbit_table; s->input; s++) { + _cleanup_free_ char *str = NULL; + int r; + + r = secure_bits_from_string(s->input); + assert_se(r > 0); + assert_se(secure_bits_is_valid(r)); + assert_se(secure_bits_to_string_alloc(r, &str) >= 0); + printf("%s = 0x%x = %s\n", s->input, (unsigned)r, str); + assert_se(streq(s->expected, str)); + } +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-selinux.c b/src/test/test-selinux.c new file mode 100644 index 0000000..04b5ba1 --- /dev/null +++ b/src/test/test-selinux.c @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <sys/stat.h> + +#include "alloc-util.h" +#include "fd-util.h" +#include "log.h" +#include "selinux-util.h" +#include "string-util.h" +#include "tests.h" +#include "time-util.h" + +static void test_testing(void) { + bool b; + + log_info("============ %s ==========", __func__); + + b = mac_selinux_use(); + log_info("mac_selinux_use → %s", yes_no(b)); + + b = mac_selinux_use(); + log_info("mac_selinux_use → %s", yes_no(b)); + + mac_selinux_retest(); + + b = mac_selinux_use(); + log_info("mac_selinux_use → %s", yes_no(b)); + + b = mac_selinux_use(); + log_info("mac_selinux_use → %s", yes_no(b)); +} + +static void test_loading(void) { + usec_t n1, n2; + int r; + + log_info("============ %s ==========", __func__); + + n1 = now(CLOCK_MONOTONIC); + r = mac_selinux_init(); + n2 = now(CLOCK_MONOTONIC); + log_info_errno(r, "mac_selinux_init → %d %.2fs (%m)", r, (n2 - n1)/1e6); +} + +static void test_cleanup(void) { + usec_t n1, n2; + + log_info("============ %s ==========", __func__); + + n1 = now(CLOCK_MONOTONIC); + mac_selinux_finish(); + n2 = now(CLOCK_MONOTONIC); + log_info("mac_selinux_finish → %.2fs", (n2 - n1)/1e6); +} + +static void test_misc(const char* fname) { + _cleanup_(mac_selinux_freep) char *label = NULL, *label2 = NULL, *label3 = NULL; + int r; + _cleanup_close_ int fd = -EBADF; + + log_info("============ %s ==========", __func__); + + r = mac_selinux_get_our_label(&label); + log_info_errno(r, "mac_selinux_get_our_label → %d, \"%s\" (%m)", + r, strnull(label)); + + r = mac_selinux_get_create_label_from_exe(fname, &label2); + log_info_errno(r, "mac_selinux_create_label_from_exe → %d, \"%s\" (%m)", + r, strnull(label2)); + + fd = socket(AF_INET, SOCK_DGRAM, 0); + assert_se(fd >= 0); + + r = mac_selinux_get_child_mls_label(fd, fname, label2, &label3); + log_info_errno(r, "mac_selinux_get_child_mls_label → %d, \"%s\" (%m)", + r, strnull(label3)); +} + +static void test_create_file_prepare(const char* fname) { + int r; + + log_info("============ %s ==========", __func__); + + r = mac_selinux_create_file_prepare(fname, S_IRWXU); + log_info_errno(r, "mac_selinux_create_file_prepare → %d (%m)", r); + + mac_selinux_create_file_clear(); +} + +int main(int argc, char **argv) { + const char *path = SYSTEMD_BINARY_PATH; + if (argc >= 2) + path = argv[1]; + + test_setup_logging(LOG_DEBUG); + + test_testing(); + test_loading(); + test_misc(path); + test_create_file_prepare(path); + test_cleanup(); + + return 0; +} diff --git a/src/test/test-serialize.c b/src/test/test-serialize.c new file mode 100644 index 0000000..8f74472 --- /dev/null +++ b/src/test/test-serialize.c @@ -0,0 +1,265 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "log.h" +#include "serialize.h" +#include "strv.h" +#include "tests.h" +#include "tmpfile-util.h" + +static char long_string[LONG_LINE_MAX+1]; + +TEST(serialize_item) { + _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-serialize.XXXXXX"; + _cleanup_fclose_ FILE *f = NULL; + + assert_se(fmkostemp_safe(fn, "r+", &f) == 0); + log_info("/* %s (%s) */", __func__, fn); + + assert_se(serialize_item(f, "a", NULL) == 0); + assert_se(serialize_item(f, "a", "bbb") == 1); + assert_se(serialize_item(f, "a", "bbb") == 1); + assert_se(serialize_bool_elide(f, "c", true) == 1); + assert_se(serialize_bool_elide(f, "d", false) == 0); + assert_se(serialize_item(f, "a", long_string) == -EINVAL); + assert_se(serialize_item(f, long_string, "a") == -EINVAL); + assert_se(serialize_item(f, long_string, long_string) == -EINVAL); + + rewind(f); + + _cleanup_free_ char *line1 = NULL, *line2 = NULL, *line3 = NULL, *line4 = NULL; + assert_se(read_line(f, LONG_LINE_MAX, &line1) > 0); + assert_se(streq(line1, "a=bbb")); + assert_se(read_line(f, LONG_LINE_MAX, &line2) > 0); + assert_se(streq(line2, "a=bbb")); + assert_se(read_line(f, LONG_LINE_MAX, &line3) > 0); + assert_se(streq(line3, "c=yes")); + assert_se(read_line(f, LONG_LINE_MAX, &line4) == 0); + assert_se(streq(line4, "")); +} + +TEST(serialize_item_escaped) { + _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-serialize.XXXXXX"; + _cleanup_fclose_ FILE *f = NULL; + + assert_se(fmkostemp_safe(fn, "r+", &f) == 0); + log_info("/* %s (%s) */", __func__, fn); + + assert_se(serialize_item_escaped(f, "a", NULL) == 0); + assert_se(serialize_item_escaped(f, "a", "bbb") == 1); + assert_se(serialize_item_escaped(f, "a", "bbb") == 1); + assert_se(serialize_item_escaped(f, "a", long_string) == -EINVAL); + assert_se(serialize_item_escaped(f, long_string, "a") == -EINVAL); + assert_se(serialize_item_escaped(f, long_string, long_string) == -EINVAL); + + rewind(f); + + _cleanup_free_ char *line1 = NULL, *line2 = NULL, *line3 = NULL; + assert_se(read_line(f, LONG_LINE_MAX, &line1) > 0); + assert_se(streq(line1, "a=bbb")); + assert_se(read_line(f, LONG_LINE_MAX, &line2) > 0); + assert_se(streq(line2, "a=bbb")); + assert_se(read_line(f, LONG_LINE_MAX, &line3) == 0); + assert_se(streq(line3, "")); +} + +TEST(serialize_usec) { + _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-serialize.XXXXXX"; + _cleanup_fclose_ FILE *f = NULL; + + assert_se(fmkostemp_safe(fn, "r+", &f) == 0); + log_info("/* %s (%s) */", __func__, fn); + + assert_se(serialize_usec(f, "usec1", USEC_INFINITY) == 0); + assert_se(serialize_usec(f, "usec2", 0) == 1); + assert_se(serialize_usec(f, "usec3", USEC_INFINITY-1) == 1); + + rewind(f); + + _cleanup_free_ char *line1 = NULL, *line2 = NULL; + usec_t x; + + assert_se(read_line(f, LONG_LINE_MAX, &line1) > 0); + assert_se(streq(line1, "usec2=0")); + assert_se(deserialize_usec(line1 + 6, &x) == 0); + assert_se(x == 0); + + assert_se(read_line(f, LONG_LINE_MAX, &line2) > 0); + assert_se(startswith(line2, "usec3=")); + assert_se(deserialize_usec(line2 + 6, &x) == 0); + assert_se(x == USEC_INFINITY-1); +} + +TEST(serialize_strv) { + _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-serialize.XXXXXX"; + _cleanup_fclose_ FILE *f = NULL; + + char **strv = STRV_MAKE("a", "b", "foo foo", + "nasty1 \"", + "\"nasty2 ", + "nasty3 '", + "\"nasty4 \"", + "nasty5\n", + "\nnasty5\nfoo=bar", + "\nnasty5\nfoo=bar"); + + assert_se(fmkostemp_safe(fn, "r+", &f) == 0); + log_info("/* %s (%s) */", __func__, fn); + + assert_se(serialize_strv(f, "strv1", NULL) == 0); + assert_se(serialize_strv(f, "strv2", STRV_MAKE_EMPTY) == 0); + assert_se(serialize_strv(f, "strv3", strv) == 1); + assert_se(serialize_strv(f, "strv4", STRV_MAKE(long_string)) == -EINVAL); + + rewind(f); + + _cleanup_strv_free_ char **strv2 = NULL; + for (;;) { + _cleanup_free_ char *line = NULL; + int r; + + r = read_line(f, LONG_LINE_MAX, &line); + if (r == 0) + break; + assert_se(r > 0); + + const char *t = startswith(line, "strv3="); + assert_se(t); + assert_se(deserialize_strv(t, &strv2) >= 0); + } + + assert_se(strv_equal(strv, strv2)); +} + +TEST(deserialize_environment) { + _cleanup_strv_free_ char **env; + + assert_se(env = strv_new("A=1")); + + assert_se(deserialize_environment("B=2", &env) >= 0); + assert_se(deserialize_environment("FOO%%=a\\177b\\nc\\td e", &env) >= 0); + + assert_se(strv_equal(env, STRV_MAKE("A=1", "B=2", "FOO%%=a\177b\nc\td e"))); + + assert_se(deserialize_environment("foo\\", &env) < 0); + assert_se(deserialize_environment("bar\\_baz", &env) < 0); +} + +TEST(serialize_environment) { + _cleanup_strv_free_ char **env = NULL, **env2 = NULL; + _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-env-util.XXXXXXX"; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert_se(fmkostemp_safe(fn, "r+", &f) == 0); + log_info("/* %s (%s) */", __func__, fn); + + assert_se(env = strv_new("A=1", + "B=2", + "C=ąęółń", + "D=D=a\\x0Ab", + "FOO%%=a\177b\nc\td e")); + + assert_se(serialize_strv(f, "env", env) == 1); + assert_se(fflush_and_check(f) == 0); + + rewind(f); + + for (;;) { + _cleanup_free_ char *line = NULL; + const char *l; + + r = read_line(f, LONG_LINE_MAX, &line); + assert_se(r >= 0); + + if (r == 0) + break; + + l = strstrip(line); + + assert_se(startswith(l, "env=")); + + r = deserialize_environment(l+4, &env2); + assert_se(r >= 0); + } + assert_se(feof(f)); + + assert_se(strv_equal(env, env2)); +} + +TEST(serialize_item_hexmem) { + _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-serialize.XXXXXX"; + _cleanup_fclose_ FILE *f = NULL; + + assert_se(fmkostemp_safe(fn, "r+", &f) == 0); + log_info("/* %s (%s) */", __func__, fn); + + assert_se(serialize_item_hexmem(f, "a", NULL, 0) == 0); + assert_se(serialize_item_hexmem(f, "a", (uint8_t []){0xff, 0xff, 0xff}, sizeof(uint8_t) * 3) == 1); + + rewind(f); + + _cleanup_free_ char *line = NULL; + assert_se(read_line(f, LONG_LINE_MAX, &line) > 0); + assert_se(streq(line, "a=ffffff")); + +} + +TEST(serialize_item_base64mem) { + _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-serialize.XXXXXX"; + _cleanup_fclose_ FILE *f = NULL; + + assert_se(fmkostemp_safe(fn, "r+", &f) == 0); + log_info("/* %s (%s) */", __func__, fn); + + assert_se(serialize_item_base64mem(f, "a", NULL, 0) == 0); + assert_se(serialize_item_base64mem(f, "a", (uint8_t []){0xff, 0xff, 0xff}, sizeof(uint8_t) * 3) == 1); + + rewind(f); + + _cleanup_free_ char *line = NULL; + assert_se(read_line(f, LONG_LINE_MAX, &line) > 0); + assert_se(streq(line, "a=////")); +} + +TEST(serialize_string_set) { + _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-serialize.XXXXXX"; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_set_free_free_ Set *s = NULL; + _cleanup_free_ char *line1 = NULL, *line2 = NULL; + char *p, *q; + + assert_se(fmkostemp_safe(fn, "r+", &f) == 0); + log_info("/* %s (%s) */", __func__, fn); + + assert_se(set_ensure_allocated(&s, &string_hash_ops) >= 0); + + assert_se(serialize_string_set(f, "a", s) == 0); + + assert_se(set_put_strsplit(s, "abc def,ghi jkl", ",", 0) >= 0); + + assert_se(serialize_string_set(f, "a", s) == 1); + + rewind(f); + + assert_se(read_line(f, LONG_LINE_MAX, &line1) > 0); + assert_se((p = startswith(line1, "a="))); + + assert_se(read_line(f, LONG_LINE_MAX, &line2) > 0); + assert_se((q = startswith(line2, "a="))); + + assert_se(!streq(p, q)); + assert_se(STR_IN_SET(p, "abc def", "ghi jkl")); + assert_se(STR_IN_SET(q, "abc def", "ghi jkl")); +} + +static int intro(void) { + memset(long_string, 'x', sizeof(long_string)-1); + char_array_0(long_string); + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/test/test-set-disable-mempool.c b/src/test/test-set-disable-mempool.c new file mode 100644 index 0000000..91244b2 --- /dev/null +++ b/src/test/test-set-disable-mempool.c @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <pthread.h> + +#include "mempool.h" +#include "process-util.h" +#include "set.h" +#include "tests.h" + +#define NUM 100 + +static void* thread(void *p) { + Set **s = p; + + assert_se(s); + assert_se(*s); + + assert_se(!is_main_thread()); + assert_se(mempool_enabled); + assert_se(!mempool_enabled()); + + assert_se(set_size(*s) == NUM); + *s = set_free(*s); + + return NULL; +} + +static void test_one(const char *val) { + pthread_t t; + int x[NUM] = {}; + unsigned i; + Set *s; + + log_info("Testing with SYSTEMD_MEMPOOL=%s", val); + assert_se(setenv("SYSTEMD_MEMPOOL", val, true) == 0); + + assert_se(is_main_thread()); + assert_se(mempool_enabled); /* It is a weak symbol, but we expect it to be available */ + assert_se(!mempool_enabled()); + + assert_se(s = set_new(NULL)); + for (i = 0; i < NUM; i++) + assert_se(set_put(s, &x[i])); + + assert_se(pthread_create(&t, NULL, thread, &s) == 0); + assert_se(pthread_join(t, NULL) == 0); + + assert_se(!s); +} + +TEST(disable_mempool) { + test_one("0"); + /* The value $SYSTEMD_MEMPOOL= is cached. So the following + * test should also succeed. */ + test_one("1"); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-set.c b/src/test/test-set.c new file mode 100644 index 0000000..0d5a6a1 --- /dev/null +++ b/src/test/test-set.c @@ -0,0 +1,403 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "random-util.h" +#include "set.h" +#include "strv.h" +#include "tests.h" + +TEST(set_steal_first) { + _cleanup_set_free_ Set *m = NULL; + int seen[3] = {}; + char *val; + + m = set_new(&string_hash_ops); + assert_se(m); + + assert_se(set_put(m, (void*) "1") == 1); + assert_se(set_put(m, (void*) "22") == 1); + assert_se(set_put(m, (void*) "333") == 1); + + while ((val = set_steal_first(m))) + seen[strlen(val) - 1]++; + + assert_se(seen[0] == 1 && seen[1] == 1 && seen[2] == 1); + + assert_se(set_isempty(m)); +} + +typedef struct Item { + int seen; +} Item; +static void item_seen(Item *item) { + item->seen++; +} + +TEST(set_free_with_destructor) { + Set *m; + struct Item items[4] = {}; + + assert_se(m = set_new(NULL)); + for (size_t i = 0; i < ELEMENTSOF(items) - 1; i++) + assert_se(set_put(m, items + i) == 1); + + m = set_free_with_destructor(m, item_seen); + assert_se(items[0].seen == 1); + assert_se(items[1].seen == 1); + assert_se(items[2].seen == 1); + assert_se(items[3].seen == 0); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(item_hash_ops, void, trivial_hash_func, trivial_compare_func, Item, item_seen); + +TEST(set_free_with_hash_ops) { + Set *m; + struct Item items[4] = {}; + + assert_se(m = set_new(&item_hash_ops)); + for (size_t i = 0; i < ELEMENTSOF(items) - 1; i++) + assert_se(set_put(m, items + i) == 1); + + m = set_free(m); + assert_se(items[0].seen == 1); + assert_se(items[1].seen == 1); + assert_se(items[2].seen == 1); + assert_se(items[3].seen == 0); +} + +TEST(set_put) { + _cleanup_set_free_ Set *m = NULL; + + m = set_new(&string_hash_ops); + assert_se(m); + + assert_se(set_put(m, (void*) "1") == 1); + assert_se(set_put(m, (void*) "22") == 1); + assert_se(set_put(m, (void*) "333") == 1); + assert_se(set_put(m, (void*) "333") == 0); + assert_se(set_remove(m, (void*) "333")); + assert_se(set_put(m, (void*) "333") == 1); + assert_se(set_put(m, (void*) "333") == 0); + assert_se(set_put(m, (void*) "22") == 0); + + _cleanup_free_ char **t = set_get_strv(m); + assert_se(strv_contains(t, "1")); + assert_se(strv_contains(t, "22")); + assert_se(strv_contains(t, "333")); + assert_se(strv_length(t) == 3); +} + +TEST(set_put_strndup) { + _cleanup_set_free_ Set *m = NULL; + + assert_se(set_put_strndup(&m, "12345", 0) == 1); + assert_se(set_put_strndup(&m, "12345", 1) == 1); + assert_se(set_put_strndup(&m, "12345", 2) == 1); + assert_se(set_put_strndup(&m, "12345", 3) == 1); + assert_se(set_put_strndup(&m, "12345", 4) == 1); + assert_se(set_put_strndup(&m, "12345", 5) == 1); + assert_se(set_put_strndup(&m, "12345", 6) == 0); + + assert_se(set_contains(m, "")); + assert_se(set_contains(m, "1")); + assert_se(set_contains(m, "12")); + assert_se(set_contains(m, "123")); + assert_se(set_contains(m, "1234")); + assert_se(set_contains(m, "12345")); + + assert_se(set_size(m) == 6); +} + +TEST(set_put_strdup) { + _cleanup_set_free_ Set *m = NULL; + + assert_se(set_put_strdup(&m, "aaa") == 1); + assert_se(set_put_strdup(&m, "aaa") == 0); + assert_se(set_put_strdup(&m, "bbb") == 1); + assert_se(set_put_strdup(&m, "bbb") == 0); + assert_se(set_put_strdup(&m, "aaa") == 0); + + assert_se(set_contains(m, "aaa")); + assert_se(set_contains(m, "bbb")); + + assert_se(set_size(m) == 2); +} + +TEST(set_put_strdupv) { + _cleanup_set_free_ Set *m = NULL; + + assert_se(set_put_strdupv(&m, STRV_MAKE("aaa", "aaa", "bbb", "bbb", "aaa")) == 2); + assert_se(set_put_strdupv(&m, STRV_MAKE("aaa", "aaa", "bbb", "bbb", "ccc")) == 1); + + assert_se(set_contains(m, "aaa")); + assert_se(set_contains(m, "bbb")); + assert_se(set_contains(m, "ccc")); + + assert_se(set_size(m) == 3); +} + +TEST(set_ensure_allocated) { + _cleanup_set_free_ Set *m = NULL; + + assert_se(set_ensure_allocated(&m, &string_hash_ops) == 1); + assert_se(set_ensure_allocated(&m, &string_hash_ops) == 0); + assert_se(set_ensure_allocated(&m, NULL) == 0); + assert_se(set_size(m) == 0); +} + +TEST(set_copy) { + _cleanup_set_free_ Set *s = NULL; + _cleanup_set_free_free_ Set *copy = NULL; + char *key1, *key2, *key3, *key4; + + key1 = strdup("key1"); + assert_se(key1); + key2 = strdup("key2"); + assert_se(key2); + key3 = strdup("key3"); + assert_se(key3); + key4 = strdup("key4"); + assert_se(key4); + + s = set_new(&string_hash_ops); + assert_se(s); + + assert_se(set_put(s, key1) >= 0); + assert_se(set_put(s, key2) >= 0); + assert_se(set_put(s, key3) >= 0); + assert_se(set_put(s, key4) >= 0); + + copy = set_copy(s); + assert_se(copy); + + assert_se(set_equal(s, copy)); +} + +TEST(set_ensure_put) { + _cleanup_set_free_ Set *m = NULL; + + assert_se(set_ensure_put(&m, &string_hash_ops, "a") == 1); + assert_se(set_ensure_put(&m, &string_hash_ops, "a") == 0); + assert_se(set_ensure_put(&m, NULL, "a") == 0); + assert_se(set_ensure_put(&m, &string_hash_ops, "b") == 1); + assert_se(set_ensure_put(&m, &string_hash_ops, "b") == 0); + assert_se(set_ensure_put(&m, &string_hash_ops, "a") == 0); + assert_se(set_size(m) == 2); +} + +TEST(set_ensure_consume) { + _cleanup_set_free_ Set *m = NULL; + char *s, *t; + + assert_se(s = strdup("a")); + assert_se(set_ensure_consume(&m, &string_hash_ops_free, s) == 1); + + assert_se(t = strdup("a")); + assert_se(set_ensure_consume(&m, &string_hash_ops_free, t) == 0); + + assert_se(t = strdup("a")); + assert_se(set_ensure_consume(&m, &string_hash_ops_free, t) == 0); + + assert_se(t = strdup("b")); + assert_se(set_ensure_consume(&m, &string_hash_ops_free, t) == 1); + + assert_se(t = strdup("b")); + assert_se(set_ensure_consume(&m, &string_hash_ops_free, t) == 0); + + assert_se(set_size(m) == 2); +} + +TEST(set_strjoin) { + _cleanup_set_free_ Set *m = NULL; + _cleanup_free_ char *joined = NULL; + + /* Empty set */ + assert_se(set_strjoin(m, NULL, false, &joined) >= 0); + assert_se(!joined); + assert_se(set_strjoin(m, "", false, &joined) >= 0); + assert_se(!joined); + assert_se(set_strjoin(m, " ", false, &joined) >= 0); + assert_se(!joined); + assert_se(set_strjoin(m, "xxx", false, &joined) >= 0); + assert_se(!joined); + assert_se(set_strjoin(m, NULL, true, &joined) >= 0); + assert_se(!joined); + assert_se(set_strjoin(m, "", true, &joined) >= 0); + assert_se(!joined); + assert_se(set_strjoin(m, " ", true, &joined) >= 0); + assert_se(!joined); + assert_se(set_strjoin(m, "xxx", true, &joined) >= 0); + assert_se(!joined); + + /* Single entry */ + assert_se(set_put_strdup(&m, "aaa") == 1); + assert_se(set_strjoin(m, NULL, false, &joined) >= 0); + assert_se(streq(joined, "aaa")); + joined = mfree(joined); + assert_se(set_strjoin(m, "", false, &joined) >= 0); + assert_se(streq(joined, "aaa")); + joined = mfree(joined); + assert_se(set_strjoin(m, " ", false, &joined) >= 0); + assert_se(streq(joined, "aaa")); + joined = mfree(joined); + assert_se(set_strjoin(m, "xxx", false, &joined) >= 0); + assert_se(streq(joined, "aaa")); + joined = mfree(joined); + assert_se(set_strjoin(m, NULL, true, &joined) >= 0); + assert_se(streq(joined, "aaa")); + joined = mfree(joined); + assert_se(set_strjoin(m, "", true, &joined) >= 0); + assert_se(streq(joined, "aaa")); + joined = mfree(joined); + assert_se(set_strjoin(m, " ", true, &joined) >= 0); + assert_se(streq(joined, " aaa ")); + joined = mfree(joined); + assert_se(set_strjoin(m, "xxx", true, &joined) >= 0); + assert_se(streq(joined, "xxxaaaxxx")); + + /* Two entries */ + assert_se(set_put_strdup(&m, "bbb") == 1); + assert_se(set_put_strdup(&m, "aaa") == 0); + joined = mfree(joined); + assert_se(set_strjoin(m, NULL, false, &joined) >= 0); + assert_se(STR_IN_SET(joined, "aaabbb", "bbbaaa")); + joined = mfree(joined); + assert_se(set_strjoin(m, "", false, &joined) >= 0); + assert_se(STR_IN_SET(joined, "aaabbb", "bbbaaa")); + joined = mfree(joined); + assert_se(set_strjoin(m, " ", false, &joined) >= 0); + assert_se(STR_IN_SET(joined, "aaa bbb", "bbb aaa")); + joined = mfree(joined); + assert_se(set_strjoin(m, "xxx", false, &joined) >= 0); + assert_se(STR_IN_SET(joined, "aaaxxxbbb", "bbbxxxaaa")); + joined = mfree(joined); + assert_se(set_strjoin(m, NULL, true, &joined) >= 0); + assert_se(STR_IN_SET(joined, "aaabbb", "bbbaaa")); + joined = mfree(joined); + assert_se(set_strjoin(m, "", true, &joined) >= 0); + assert_se(STR_IN_SET(joined, "aaabbb", "bbbaaa")); + joined = mfree(joined); + assert_se(set_strjoin(m, " ", true, &joined) >= 0); + assert_se(STR_IN_SET(joined, " aaa bbb ", " bbb aaa ")); + joined = mfree(joined); + assert_se(set_strjoin(m, "xxx", true, &joined) >= 0); + assert_se(STR_IN_SET(joined, "xxxaaaxxxbbbxxx", "xxxbbbxxxaaaxxx")); +} + +TEST(set_equal) { + _cleanup_set_free_ Set *a = NULL, *b = NULL; + void *p; + int r; + + assert_se(a = set_new(NULL)); + assert_se(b = set_new(NULL)); + + assert_se(set_equal(a, a)); + assert_se(set_equal(b, b)); + assert_se(set_equal(a, b)); + assert_se(set_equal(b, a)); + assert_se(set_equal(NULL, a)); + assert_se(set_equal(NULL, b)); + assert_se(set_equal(a, NULL)); + assert_se(set_equal(b, NULL)); + assert_se(set_equal(NULL, NULL)); + + for (unsigned i = 0; i < 333; i++) { + p = INT32_TO_PTR(1 + (random_u32() & 0xFFFU)); + + r = set_put(a, p); + assert_se(r >= 0 || r == -EEXIST); + } + + assert_se(set_put(a, INT32_TO_PTR(0x1000U)) >= 0); + + assert_se(set_size(a) >= 2); + assert_se(set_size(a) <= 334); + + assert_se(!set_equal(a, b)); + assert_se(!set_equal(b, a)); + assert_se(!set_equal(a, NULL)); + + SET_FOREACH(p, a) + assert_se(set_put(b, p) >= 0); + + assert_se(set_equal(a, b)); + assert_se(set_equal(b, a)); + + assert_se(set_remove(a, INT32_TO_PTR(0x1000U)) == INT32_TO_PTR(0x1000U)); + + assert_se(!set_equal(a, b)); + assert_se(!set_equal(b, a)); + + assert_se(set_remove(b, INT32_TO_PTR(0x1000U)) == INT32_TO_PTR(0x1000U)); + + assert_se(set_equal(a, b)); + assert_se(set_equal(b, a)); + + assert_se(set_put(b, INT32_TO_PTR(0x1001U)) >= 0); + + assert_se(!set_equal(a, b)); + assert_se(!set_equal(b, a)); + + assert_se(set_put(a, INT32_TO_PTR(0x1001U)) >= 0); + + assert_se(set_equal(a, b)); + assert_se(set_equal(b, a)); + + set_clear(a); + + assert_se(!set_equal(a, b)); + assert_se(!set_equal(b, a)); + + set_clear(b); + + assert_se(set_equal(a, b)); + assert_se(set_equal(b, a)); +} + +TEST(set_fnmatch) { + _cleanup_set_free_ Set *match = NULL, *nomatch = NULL; + + assert_se(set_put_strdup(&match, "aaa") >= 0); + assert_se(set_put_strdup(&match, "bbb*") >= 0); + assert_se(set_put_strdup(&match, "*ccc") >= 0); + + assert_se(set_put_strdup(&nomatch, "a*") >= 0); + assert_se(set_put_strdup(&nomatch, "bbb") >= 0); + assert_se(set_put_strdup(&nomatch, "ccc*") >= 0); + + assert_se(set_fnmatch(NULL, NULL, "")); + assert_se(set_fnmatch(NULL, NULL, "hoge")); + + assert_se(set_fnmatch(match, NULL, "aaa")); + assert_se(set_fnmatch(match, NULL, "bbb")); + assert_se(set_fnmatch(match, NULL, "bbbXXX")); + assert_se(set_fnmatch(match, NULL, "ccc")); + assert_se(set_fnmatch(match, NULL, "XXXccc")); + assert_se(!set_fnmatch(match, NULL, "")); + assert_se(!set_fnmatch(match, NULL, "aaaa")); + assert_se(!set_fnmatch(match, NULL, "XXbbb")); + assert_se(!set_fnmatch(match, NULL, "cccXX")); + + assert_se(set_fnmatch(NULL, nomatch, "")); + assert_se(set_fnmatch(NULL, nomatch, "Xa")); + assert_se(set_fnmatch(NULL, nomatch, "bbbb")); + assert_se(set_fnmatch(NULL, nomatch, "XXXccc")); + assert_se(!set_fnmatch(NULL, nomatch, "a")); + assert_se(!set_fnmatch(NULL, nomatch, "aXXXX")); + assert_se(!set_fnmatch(NULL, nomatch, "bbb")); + assert_se(!set_fnmatch(NULL, nomatch, "ccc")); + assert_se(!set_fnmatch(NULL, nomatch, "cccXXX")); + + assert_se(set_fnmatch(match, nomatch, "bbbbb")); + assert_se(set_fnmatch(match, nomatch, "XXccc")); + assert_se(!set_fnmatch(match, nomatch, "")); + assert_se(!set_fnmatch(match, nomatch, "a")); + assert_se(!set_fnmatch(match, nomatch, "aaa")); + assert_se(!set_fnmatch(match, nomatch, "b")); + assert_se(!set_fnmatch(match, nomatch, "bbb")); + assert_se(!set_fnmatch(match, nomatch, "ccc")); + assert_se(!set_fnmatch(match, nomatch, "ccccc")); + assert_se(!set_fnmatch(match, nomatch, "cccXX")); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-sha256.c b/src/test/test-sha256.c new file mode 100644 index 0000000..f168e4c --- /dev/null +++ b/src/test/test-sha256.c @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "hexdecoct.h" +#include "sha256.h" +#include "string-util.h" +#include "tests.h" + +static void sha256_process_string(const char *key, struct sha256_ctx *ctx) { + sha256_process_bytes(key, strlen(key), ctx); +} + +static void test_sha256_one(const char *key, const char *expect) { + uint8_t result[SHA256_DIGEST_SIZE + 3]; + _cleanup_free_ char *str = NULL; + struct sha256_ctx ctx; + + log_debug("\"%s\" → %s", key, expect); + + assert_se(str = new(char, strlen(key) + 4)); + + /* This tests unaligned buffers. */ + + for (size_t i = 0; i < 4; i++) { + strcpy(str + i, key); + + for (size_t j = 0; j < 4; j++) { + _cleanup_free_ char *hex_result = NULL; + + sha256_init_ctx(&ctx); + sha256_process_string(str + i, &ctx); + sha256_finish_ctx(&ctx, result + j); + + hex_result = hexmem(result + j, SHA256_DIGEST_SIZE); + assert_se(streq_ptr(hex_result, expect)); + } + } +} + +TEST(sha256) { + /* Results compared with output of 'echo -n "<input>" | sha256sum -' */ + + test_sha256_one("abcdefghijklmnopqrstuvwxyz", + "71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73"); + test_sha256_one("ほげほげあっちょんぶりけ", + "ce7225683653be3b74861c5a4323b6baf3c3ceb361413ca99e3a5b52c04411bd"); + test_sha256_one("0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", + "9cfe7faff7054298ca87557e15a10262de8d3eee77827417fbdfea1c41b9ec23"); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-sigbus.c b/src/test/test-sigbus.c new file mode 100644 index 0000000..299463c --- /dev/null +++ b/src/test/test-sigbus.c @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <fcntl.h> +#include <sys/mman.h> +#include <unistd.h> + +#if HAVE_VALGRIND_VALGRIND_H +# include <valgrind/valgrind.h> +#endif + +#include "fd-util.h" +#include "fs-util.h" +#include "memory-util.h" +#include "sigbus.h" +#include "tests.h" + +int main(int argc, char *argv[]) { + _cleanup_close_ int fd = -EBADF; + char template[] = "/tmp/sigbus-test-XXXXXX"; + void *addr = NULL; + uint8_t *p; + + test_setup_logging(LOG_INFO); + +#if HAS_FEATURE_ADDRESS_SANITIZER + return log_tests_skipped("address-sanitizer is enabled"); +#endif +#if HAVE_VALGRIND_VALGRIND_H + if (RUNNING_ON_VALGRIND) + return log_tests_skipped("This test cannot run on valgrind"); +#endif + + sigbus_install(); + + assert_se(sigbus_pop(&addr) == 0); + + assert_se((fd = mkostemp(template, O_RDWR|O_CREAT|O_EXCL)) >= 0); + assert_se(unlink(template) >= 0); + assert_se(posix_fallocate_loop(fd, 0, page_size() * 8) >= 0); + + p = mmap(NULL, page_size() * 16, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); + assert_se(p != MAP_FAILED); + + assert_se(sigbus_pop(&addr) == 0); + + p[0] = 0xFF; + assert_se(sigbus_pop(&addr) == 0); + + p[page_size()] = 0xFF; + assert_se(sigbus_pop(&addr) == 0); + + p[page_size()*8] = 0xFF; + p[page_size()*8+1] = 0xFF; + p[page_size()*10] = 0xFF; + assert_se(sigbus_pop(&addr) > 0); + assert_se(addr == p + page_size() * 8); + assert_se(sigbus_pop(&addr) > 0); + assert_se(addr == p + page_size() * 10); + assert_se(sigbus_pop(&addr) == 0); + + sigbus_reset(); +} diff --git a/src/test/test-signal-util.c b/src/test/test-signal-util.c new file mode 100644 index 0000000..335066a --- /dev/null +++ b/src/test/test-signal-util.c @@ -0,0 +1,175 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <unistd.h> + +#include "log.h" +#include "macro.h" +#include "signal-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "tests.h" +#include "process-util.h" + +#define info(sig) log_info(#sig " = " STRINGIFY(sig) " = %d", sig) + +TEST(rt_signals) { + info(SIGRTMIN); + info(SIGRTMAX); + + /* We use signals SIGRTMIN+0 to SIGRTMIN+24 unconditionally */ + assert_se(SIGRTMAX - SIGRTMIN >= 24); +} + +static void test_signal_to_string_one(int val) { + const char *p; + + assert_se(p = signal_to_string(val)); + + assert_se(signal_from_string(p) == val); + + p = strjoina("SIG", p); + assert_se(signal_from_string(p) == val); +} + +static void test_signal_from_string_one(const char *s, int val) { + const char *p; + + assert_se(signal_from_string(s) == val); + + p = strjoina("SIG", s); + assert_se(signal_from_string(p) == val); +} + +static void test_signal_from_string_number(const char *s, int val) { + const char *p; + + assert_se(signal_from_string(s) == val); + + p = strjoina("SIG", s); + assert_se(signal_from_string(p) == -EINVAL); +} + +TEST(signal_from_string) { + char buf[STRLEN("RTMIN+") + DECIMAL_STR_MAX(int) + 1]; + + test_signal_to_string_one(SIGHUP); + test_signal_to_string_one(SIGTERM); + test_signal_to_string_one(SIGRTMIN); + test_signal_to_string_one(SIGRTMIN+3); + test_signal_to_string_one(SIGRTMAX-4); + + test_signal_from_string_one("RTMIN", SIGRTMIN); + test_signal_from_string_one("RTMAX", SIGRTMAX); + + xsprintf(buf, "RTMIN+%d", SIGRTMAX-SIGRTMIN); + test_signal_from_string_one(buf, SIGRTMAX); + + xsprintf(buf, "RTMIN+%d", INT_MAX); + test_signal_from_string_one(buf, -ERANGE); + + xsprintf(buf, "RTMAX-%d", SIGRTMAX-SIGRTMIN); + test_signal_from_string_one(buf, SIGRTMIN); + + xsprintf(buf, "RTMAX-%d", INT_MAX); + test_signal_from_string_one(buf, -ERANGE); + + test_signal_from_string_one("", -EINVAL); + test_signal_from_string_one("hup", -EINVAL); + test_signal_from_string_one("HOGEHOGE", -EINVAL); + + test_signal_from_string_one("RTMIN-5", -EINVAL); + test_signal_from_string_one("RTMIN- 5", -EINVAL); + test_signal_from_string_one("RTMIN -5", -EINVAL); + test_signal_from_string_one("RTMIN+ 5", -EINVAL); + test_signal_from_string_one("RTMIN +5", -EINVAL); + test_signal_from_string_one("RTMIN+100", -ERANGE); + test_signal_from_string_one("RTMIN+-3", -EINVAL); + test_signal_from_string_one("RTMIN++3", -EINVAL); + test_signal_from_string_one("RTMIN+HUP", -EINVAL); + test_signal_from_string_one("RTMIN3", -EINVAL); + + test_signal_from_string_one("RTMAX+5", -EINVAL); + test_signal_from_string_one("RTMAX+ 5", -EINVAL); + test_signal_from_string_one("RTMAX +5", -EINVAL); + test_signal_from_string_one("RTMAX- 5", -EINVAL); + test_signal_from_string_one("RTMAX -5", -EINVAL); + test_signal_from_string_one("RTMAX-100", -ERANGE); + test_signal_from_string_one("RTMAX-+3", -EINVAL); + test_signal_from_string_one("RTMAX--3", -EINVAL); + test_signal_from_string_one("RTMAX-HUP", -EINVAL); + + test_signal_from_string_number("3", 3); + test_signal_from_string_number("+5", 5); + test_signal_from_string_number(" +5", 5); + test_signal_from_string_number("10000", -ERANGE); + test_signal_from_string_number("-2", -ERANGE); +} + +TEST(block_signals) { + assert_se(signal_is_blocked(SIGUSR1) == 0); + assert_se(signal_is_blocked(SIGALRM) == 0); + assert_se(signal_is_blocked(SIGVTALRM) == 0); + + { + BLOCK_SIGNALS(SIGUSR1, SIGVTALRM); + + assert_se(signal_is_blocked(SIGUSR1) > 0); + assert_se(signal_is_blocked(SIGALRM) == 0); + assert_se(signal_is_blocked(SIGVTALRM) > 0); + } + + assert_se(signal_is_blocked(SIGUSR1) == 0); + assert_se(signal_is_blocked(SIGALRM) == 0); + assert_se(signal_is_blocked(SIGVTALRM) == 0); +} + +TEST(ignore_signals) { + assert_se(ignore_signals(SIGINT) >= 0); + assert_se(kill(getpid_cached(), SIGINT) >= 0); + assert_se(ignore_signals(SIGUSR1, SIGUSR2, SIGTERM, SIGPIPE) >= 0); + assert_se(kill(getpid_cached(), SIGUSR1) >= 0); + assert_se(kill(getpid_cached(), SIGUSR2) >= 0); + assert_se(kill(getpid_cached(), SIGTERM) >= 0); + assert_se(kill(getpid_cached(), SIGPIPE) >= 0); + assert_se(default_signals(SIGINT, SIGUSR1, SIGUSR2, SIGTERM, SIGPIPE) >= 0); +} + +TEST(pop_pending_signal) { + + assert_se(signal_is_blocked(SIGUSR1) == 0); + assert_se(signal_is_blocked(SIGUSR2) == 0); + assert_se(pop_pending_signal(SIGUSR1) == 0); + assert_se(pop_pending_signal(SIGUSR2) == 0); + + { + BLOCK_SIGNALS(SIGUSR1, SIGUSR2); + + assert_se(signal_is_blocked(SIGUSR1) > 0); + assert_se(signal_is_blocked(SIGUSR2) > 0); + + assert_se(pop_pending_signal(SIGUSR1) == 0); + assert_se(pop_pending_signal(SIGUSR2) == 0); + + assert_se(raise(SIGUSR1) >= 0); + + assert_se(pop_pending_signal(SIGUSR2) == 0); + assert_se(pop_pending_signal(SIGUSR1) == SIGUSR1); + assert_se(pop_pending_signal(SIGUSR1) == 0); + + assert_se(raise(SIGUSR1) >= 0); + assert_se(raise(SIGUSR2) >= 0); + + assert_cc(SIGUSR1 < SIGUSR2); + + assert_se(pop_pending_signal(SIGUSR1, SIGUSR2) == SIGUSR1); + assert_se(pop_pending_signal(SIGUSR1, SIGUSR2) == SIGUSR2); + assert_se(pop_pending_signal(SIGUSR1, SIGUSR2) == 0); + } + + assert_se(signal_is_blocked(SIGUSR1) == 0); + assert_se(signal_is_blocked(SIGUSR2) == 0); + assert_se(pop_pending_signal(SIGUSR1) == 0); + assert_se(pop_pending_signal(SIGUSR2) == 0); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-siphash24.c b/src/test/test-siphash24.c new file mode 100644 index 0000000..de91eb2 --- /dev/null +++ b/src/test/test-siphash24.c @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "memory-util.h" +#include "siphash24.h" +#include "tests.h" + +#define ITERATIONS 10000000ULL + +static void test_alignment_one(const uint8_t *in, size_t len, const uint8_t *key) { + struct siphash state = {}; + uint64_t out; + unsigned i, j; + + out = siphash24(in, len, key); + assert_se(out == 0xa129ca6149be45e5); + + /* verify the internal state as given in the above paper */ + siphash24_init(&state, key); + assert_se(state.v0 == 0x7469686173716475); + assert_se(state.v1 == 0x6b617f6d656e6665); + assert_se(state.v2 == 0x6b7f62616d677361); + assert_se(state.v3 == 0x7b6b696e727e6c7b); + siphash24_compress(in, len, &state); + assert_se(state.v0 == 0x4a017198de0a59e0); + assert_se(state.v1 == 0x0d52f6f62a4f59a4); + assert_se(state.v2 == 0x634cb3577b01fd3d); + assert_se(state.v3 == 0xa5224d6f55c7d9c8); + out = siphash24_finalize(&state); + assert_se(out == 0xa129ca6149be45e5); + assert_se(state.v0 == 0xf6bcd53893fecff1); + assert_se(state.v1 == 0x54b9964c7ea0d937); + assert_se(state.v2 == 0x1b38329c099bb55a); + assert_se(state.v3 == 0x1814bb89ad7be679); + + /* verify that decomposing the input in three chunks gives the + same result */ + for (i = 0; i < len; i++) { + for (j = i; j < len; j++) { + siphash24_init(&state, key); + siphash24_compress(in, i, &state); + siphash24_compress(&in[i], j - i, &state); + siphash24_compress(&in[j], len - j, &state); + out = siphash24_finalize(&state); + assert_se(out == 0xa129ca6149be45e5); + } + } +} + +TEST(alignment) { + const uint8_t in[15] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e }; + const uint8_t key[16] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}; + uint8_t in_buf[20]; + + /* Test with same input but different alignments. */ + memcpy(in_buf, in, sizeof(in)); + test_alignment_one(in_buf, sizeof(in), key); + memcpy(in_buf + 1, in, sizeof(in)); + test_alignment_one(in_buf + 1, sizeof(in), key); + memcpy(in_buf + 2, in, sizeof(in)); + test_alignment_one(in_buf + 2, sizeof(in), key); + memcpy(in_buf + 4, in, sizeof(in)); + test_alignment_one(in_buf + 4, sizeof(in), key); +} + +TEST(short_hashes) { + const uint8_t one[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 }; + const uint8_t key[16] = { 0x22, 0x24, 0x41, 0x22, 0x55, 0x77, 0x88, 0x07, + 0x23, 0x09, 0x23, 0x14, 0x0c, 0x33, 0x0e, 0x0f}; + uint8_t two[sizeof one] = {}; + + struct siphash state1 = {}, state2 = {}; + unsigned i, j; + + siphash24_init(&state1, key); + siphash24_init(&state2, key); + + /* hashing 1, 2, 3, 4, 5, ..., 16 bytes, with the byte after the buffer different */ + for (i = 1; i <= sizeof one; i++) { + siphash24_compress(one, i, &state1); + + two[i-1] = one[i-1]; + siphash24_compress(two, i, &state2); + + assert_se(memcmp(&state1, &state2, sizeof state1) == 0); + } + + /* hashing n and 1, n and 2, n and 3, ..., n-1 and 1, n-2 and 2, ... */ + for (i = sizeof one; i > 0; i--) { + zero(two); + + for (j = 1; j <= sizeof one; j++) { + siphash24_compress(one, i, &state1); + siphash24_compress(one, j, &state1); + + siphash24_compress(one, i, &state2); + two[j-1] = one[j-1]; + siphash24_compress(two, j, &state2); + + assert_se(memcmp(&state1, &state2, sizeof state1) == 0); + } + } +} + +/* see https://131002.net/siphash/siphash.pdf, Appendix A */ +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-sizeof.c b/src/test/test-sizeof.c new file mode 100644 index 0000000..ea0c587 --- /dev/null +++ b/src/test/test-sizeof.c @@ -0,0 +1,130 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <sched.h> +#include <stdio.h> +#include <string.h> +#include <sys/resource.h> +#include <sys/socket.h> +#include <sys/timex.h> +#include <sys/types.h> + +#define __STDC_WANT_IEC_60559_TYPES_EXT__ +#include <float.h> + +#include "time-util.h" + +/* Print information about various types. Useful when diagnosing + * gcc diagnostics on an unfamiliar architecture. */ + +DISABLE_WARNING_TYPE_LIMITS; + +#define info_no_sign(t) \ + printf("%s → %zu bits, %zu byte alignment\n", STRINGIFY(t), \ + sizeof(t)*CHAR_BIT, \ + alignof(t)) + +#define info(t) \ + printf("%s → %zu bits%s, %zu byte alignment\n", STRINGIFY(t), \ + sizeof(t)*CHAR_BIT, \ + strstr(STRINGIFY(t), "signed") ? "" : \ + (t)-1 < (t)0 ? ", signed" : ", unsigned", \ + alignof(t)) + +#define check_no_sign(t, size) \ + do { \ + info_no_sign(t); \ + assert_se(sizeof(t) == size); \ + } while (false) + +#define check(t, size) \ + do { \ + info(t); \ + assert_se(sizeof(t) == size); \ + } while (false) + +enum Enum { + enum_value, +}; + +enum BigEnum { + big_enum_value = UINT64_C(1), +}; + +enum BigEnum2 { + big_enum2_pos = UINT64_C(1), + big_enum2_neg = UINT64_C(-1), +}; + +int main(void) { + int (*function_pointer)(void); + + check_no_sign(dev_t, SIZEOF_DEV_T); + check_no_sign(ino_t, SIZEOF_INO_T); + check_no_sign(rlim_t, SIZEOF_RLIM_T); + check(time_t, SIZEOF_TIME_T); + check(typeof(((struct timex *)0)->freq), SIZEOF_TIMEX_MEMBER); + + info_no_sign(typeof(function_pointer)); + info_no_sign(void*); + info(char*); + + info(char); + info(signed char); + info(unsigned char); + info(short unsigned); + info(unsigned); + info(unsigned long); + info(unsigned long long); +#ifdef __GLIBC__ + info(__syscall_ulong_t); + info(__syscall_slong_t); +#endif + info(intmax_t); + info(uintmax_t); + + info(float); + info(double); + info(long double); + +#ifdef FLT128_MAX + info(_Float128); + info(_Float64); + info(_Float64x); + info(_Float32); + info(_Float32x); +#endif + + info(size_t); + info(ssize_t); + info(usec_t); +#ifdef __GLIBC__ + info(__time_t); +#endif + info(pid_t); + info(uid_t); + info(gid_t); + info(socklen_t); + +#ifdef __GLIBC__ + info(__cpu_mask); +#endif + + info(enum Enum); + info(enum BigEnum); + info(enum BigEnum2); + assert_cc(sizeof(enum BigEnum2) == 8); + printf("big_enum2_pos → %zu\n", sizeof(big_enum2_pos)); + printf("big_enum2_neg → %zu\n", sizeof(big_enum2_neg)); + + printf("timeval: %zu\n", sizeof(struct timeval)); + printf("timespec: %zu\n", sizeof(struct timespec)); + + void *x = malloc(100); + + printf("local variable: %p\n", &function_pointer); + printf("glibc function: %p\n", memcpy); + printf("heap allocation: %p\n", x); + free(x); + + return 0; +} diff --git a/src/test/test-sleep-config.c b/src/test/test-sleep-config.c new file mode 100644 index 0000000..112fec6 --- /dev/null +++ b/src/test/test-sleep-config.c @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <unistd.h> + +#include "efivars.h" +#include "errno-util.h" +#include "log.h" +#include "sleep-config.h" +#include "strv.h" +#include "tests.h" + +TEST(parse_sleep_config) { + _cleanup_(sleep_config_freep) SleepConfig *sleep_config = NULL; + + assert_se(parse_sleep_config(&sleep_config) == 0); + + _cleanup_free_ char *sum = NULL, *sus = NULL, *him = NULL, *his = NULL, *hym = NULL, *hys = NULL; + + sum = strv_join(sleep_config->modes[SLEEP_SUSPEND], ", "); + sus = strv_join(sleep_config->states[SLEEP_SUSPEND], ", "); + him = strv_join(sleep_config->modes[SLEEP_HIBERNATE], ", "); + his = strv_join(sleep_config->states[SLEEP_HIBERNATE], ", "); + hym = strv_join(sleep_config->modes[SLEEP_HYBRID_SLEEP], ", "); + hys = strv_join(sleep_config->states[SLEEP_HYBRID_SLEEP], ", "); + log_debug(" allow_suspend: %s", yes_no(sleep_config->allow[SLEEP_SUSPEND])); + log_debug(" allow_hibernate: %s", yes_no(sleep_config->allow[SLEEP_HIBERNATE])); + log_debug(" allow_s2h: %s", yes_no(sleep_config->allow[SLEEP_SUSPEND_THEN_HIBERNATE])); + log_debug(" allow_hybrid_sleep: %s", yes_no(sleep_config->allow[SLEEP_HYBRID_SLEEP])); + log_debug(" suspend modes: %s", sum); + log_debug(" states: %s", sus); + log_debug(" hibernate modes: %s", him); + log_debug(" states: %s", his); + log_debug(" hybrid modes: %s", hym); + log_debug(" states: %s", hys); +} + +TEST(sleep_supported) { + _cleanup_strv_free_ char + **standby = strv_new("standby"), + **mem = strv_new("mem"), + **disk = strv_new("disk"), + **suspend = strv_new("suspend"), + **reboot = strv_new("reboot"), + **platform = strv_new("platform"), + **shutdown = strv_new("shutdown"), + **freeze = strv_new("freeze"); + int r; + + printf("Secure boot: %sd\n", enable_disable(is_efi_secure_boot())); + + log_info("/= individual sleep modes =/"); + log_info("Standby configured: %s", yes_no(sleep_state_supported(standby) > 0)); + log_info("Suspend configured: %s", yes_no(sleep_state_supported(mem) > 0)); + log_info("Hibernate configured: %s", yes_no(sleep_state_supported(disk) > 0)); + log_info("Hibernate+Suspend (Hybrid-Sleep) configured: %s", yes_no(sleep_mode_supported(suspend) > 0)); + log_info("Hibernate+Reboot configured: %s", yes_no(sleep_mode_supported(reboot) > 0)); + log_info("Hibernate+Platform configured: %s", yes_no(sleep_mode_supported(platform) > 0)); + log_info("Hibernate+Shutdown configured: %s", yes_no(sleep_mode_supported(shutdown) > 0)); + log_info("Freeze configured: %s", yes_no(sleep_state_supported(freeze) > 0)); + + log_info("/= high-level sleep verbs =/"); + r = sleep_supported(SLEEP_SUSPEND); + log_info("Suspend configured and possible: %s", r >= 0 ? yes_no(r) : STRERROR(r)); + r = sleep_supported(SLEEP_HIBERNATE); + log_info("Hibernation configured and possible: %s", r >= 0 ? yes_no(r) : STRERROR(r)); + r = sleep_supported(SLEEP_HYBRID_SLEEP); + log_info("Hybrid-sleep configured and possible: %s", r >= 0 ? yes_no(r) : STRERROR(r)); + r = sleep_supported(SLEEP_SUSPEND_THEN_HIBERNATE); + log_info("Suspend-then-Hibernate configured and possible: %s", r >= 0 ? yes_no(r) : STRERROR(r)); +} + +static int intro(void) { + if (getuid() != 0) + log_warning("This program is unlikely to work for unprivileged users"); + + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/src/test/test-socket-bind.c b/src/test/test-socket-bind.c new file mode 100644 index 0000000..84a8978 --- /dev/null +++ b/src/test/test-socket-bind.c @@ -0,0 +1,149 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "bpf-socket-bind.h" +#include "load-fragment.h" +#include "manager.h" +#include "process-util.h" +#include "rlimit-util.h" +#include "rm-rf.h" +#include "service.h" +#include "strv.h" +#include "tests.h" +#include "unit.h" +#include "virt.h" + +static int find_netcat_executable(char **ret_path) { + char **candidates = STRV_MAKE("ncat", "nc", "netcat"); + int r = 0; + + STRV_FOREACH(c, candidates) { + r = find_executable(*c, ret_path); + if (r == 0) + break; + } + + return r; +} + +static int test_socket_bind( + Manager *m, + const char *unit_name, + const char *netcat_path, + const char *port, + char **allow_rules, + char **deny_rules) { + _cleanup_free_ char *exec_start = NULL; + _cleanup_(unit_freep) Unit *u = NULL; + CGroupContext *cc = NULL; + int cld_code, r; + + assert_se(u = unit_new(m, sizeof(Service))); + assert_se(unit_add_name(u, unit_name) == 0); + assert_se(cc = unit_get_cgroup_context(u)); + + STRV_FOREACH(rule, allow_rules) { + r = config_parse_cgroup_socket_bind( + u->id, "filename", 1, "Service", 1, "SocketBindAllow", 0, + *rule, &cc->socket_bind_allow, u); + if (r < 0) + return log_unit_error_errno(u, r, "Failed to parse SocketBindAllow: %m"); + } + + fprintf(stderr, "SocketBindAllow: "); + cgroup_context_dump_socket_bind_items(cc->socket_bind_allow, stderr); + fputc('\n', stderr); + + STRV_FOREACH(rule, deny_rules) { + r = config_parse_cgroup_socket_bind( + u->id, "filename", 1, "Service", 1, "SocketBindDeny", 0, + *rule, &cc->socket_bind_deny, u); + if (r < 0) + return log_unit_error_errno(u, r, "Failed to parse SocketBindDeny: %m"); + } + + fprintf(stderr, "SocketBindDeny: "); + cgroup_context_dump_socket_bind_items(cc->socket_bind_deny, stderr); + fputc('\n', stderr); + + exec_start = strjoin("-timeout --preserve-status -sSIGTERM 1s ", netcat_path, " -l ", port, " -vv"); + assert_se(exec_start != NULL); + + r = config_parse_exec(u->id, "filename", 1, "Service", 1, "ExecStart", + SERVICE_EXEC_START, exec_start, SERVICE(u)->exec_command, u); + if (r < 0) + return log_error_errno(r, "Failed to parse ExecStart"); + + SERVICE(u)->type = SERVICE_ONESHOT; + u->load_state = UNIT_LOADED; + + r = unit_start(u, NULL); + if (r < 0) + return log_error_errno(r, "Unit start failed %m"); + + while (!IN_SET(SERVICE(u)->state, SERVICE_DEAD, SERVICE_FAILED)) { + r = sd_event_run(m->event, UINT64_MAX); + if (r < 0) + return log_error_errno(errno, "Event run failed %m"); + } + + cld_code = SERVICE(u)->exec_command[SERVICE_EXEC_START]->exec_status.code; + if (cld_code != CLD_EXITED) + return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "ExecStart didn't exited, code='%s'", sigchld_code_to_string(cld_code)); + + if (SERVICE(u)->state != SERVICE_DEAD) + return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Service is not dead"); + + return 0; +} + +int main(int argc, char *argv[]) { + _cleanup_free_ char *unit_dir = NULL, *netcat_path = NULL; + _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; + _cleanup_(manager_freep) Manager *m = NULL; + struct rlimit rl; + int r; + + test_setup_logging(LOG_DEBUG); + + if (detect_container() > 0) + return log_tests_skipped("test-socket-bind fails inside LXC and Docker containers: https://github.com/systemd/systemd/issues/9666"); + + assert_se(getrlimit(RLIMIT_MEMLOCK, &rl) >= 0); + rl.rlim_cur = rl.rlim_max = MAX(rl.rlim_max, CAN_MEMLOCK_SIZE); + (void) setrlimit_closest(RLIMIT_MEMLOCK, &rl); + + if (!can_memlock()) + return log_tests_skipped("Can't use mlock()"); + + r = bpf_socket_bind_supported(); + if (r <= 0) + return log_tests_skipped("socket-bind is not supported"); + + if (find_netcat_executable(&netcat_path) != 0) + return log_tests_skipped("Cannot find netcat executable"); + + r = enter_cgroup_subroot(NULL); + if (r == -ENOMEDIUM) + return log_tests_skipped("cgroupfs not available"); + + assert_se(get_testdata_dir("units", &unit_dir) >= 0); + assert_se(set_unit_path(unit_dir) >= 0); + assert_se(runtime_dir = setup_fake_runtime_dir()); + + assert_se(manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &m) >= 0); + assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + + assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "2000", STRV_MAKE("2000"), STRV_MAKE("any")) >= 0); + assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "2000", STRV_MAKE("ipv6:2001-2002"), STRV_MAKE("any")) >= 0); + assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "6666", STRV_MAKE("ipv4:6666", "6667"), STRV_MAKE("any")) >= 0); + assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "6666", STRV_MAKE("6667", "6668", ""), STRV_MAKE("any")) >= 0); + assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "7777", STRV_MAKE_EMPTY, STRV_MAKE_EMPTY) >= 0); + assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "8888", STRV_MAKE("any"), STRV_MAKE("any")) >= 0); + assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "8888", STRV_MAKE("ipv6:tcp:8888-8889"), STRV_MAKE("any")) >= 0); + assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "10000", STRV_MAKE("ipv6:udp:9999-10000"), STRV_MAKE("any")) >= 0); + assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "6666", STRV_MAKE("ipv4:tcp:6666"), STRV_MAKE("any")) >= 0); + assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "6666", STRV_MAKE("ipv4:udp:6666"), STRV_MAKE("any")) >= 0); + assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "6666", STRV_MAKE("tcp:6666"), STRV_MAKE("any")) >= 0); + + return 0; +} diff --git a/src/test/test-socket-netlink.c b/src/test/test-socket-netlink.c new file mode 100644 index 0000000..6dbd50f --- /dev/null +++ b/src/test/test-socket-netlink.c @@ -0,0 +1,372 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "missing_network.h" +#include "tests.h" +#include "socket-netlink.h" +#include "string-util.h" + +static void test_socket_address_parse_one(const char *in, int ret, int family, const char *expected) { + SocketAddress a; + _cleanup_free_ char *out = NULL; + int r; + + r = socket_address_parse(&a, in); + if (r >= 0) { + r = socket_address_print(&a, &out); + if (r < 0) + log_error_errno(r, "Printing failed for \"%s\": %m", in); + assert_se(r >= 0); + assert_se(a.type == 0); + } + + log_info("\"%s\" → %s %d → \"%s\" (expect %d / \"%s\")", + in, + r >= 0 ? "✓" : "✗", r, + empty_to_dash(out), + ret, + ret >= 0 ? expected ?: in : "-"); + assert_se(r == ret); + if (r >= 0) { + assert_se(a.sockaddr.sa.sa_family == family); + assert_se(streq(out, expected ?: in)); + } +} + +TEST(socket_address_parse) { + test_socket_address_parse_one("junk", -EINVAL, 0, NULL); + test_socket_address_parse_one("192.168.1.1", -EINVAL, 0, NULL); + test_socket_address_parse_one(".168.1.1", -EINVAL, 0, NULL); + test_socket_address_parse_one("989.168.1.1", -EINVAL, 0, NULL); + test_socket_address_parse_one("192.168.1.1:65536", -ERANGE, 0, NULL); + test_socket_address_parse_one("192.168.1.1:0", -EINVAL, 0, NULL); + test_socket_address_parse_one("0", -EINVAL, 0, NULL); + test_socket_address_parse_one("65536", -ERANGE, 0, NULL); + + const int default_family = socket_ipv6_is_supported() ? AF_INET6 : AF_INET; + + test_socket_address_parse_one("65535", 0, default_family, + default_family == AF_INET6 ? "[::]:65535": "0.0.0.0:65535"); + + /* The checks below will pass even if ipv6 is disabled in + * kernel. The underlying glibc's inet_pton() is just a string + * parser and doesn't make any syscalls. */ + + test_socket_address_parse_one("[::1]", -EINVAL, 0, NULL); + test_socket_address_parse_one("[::1]8888", -EINVAL, 0, NULL); + test_socket_address_parse_one("::1", -EINVAL, 0, NULL); + test_socket_address_parse_one("[::1]:0", -EINVAL, 0, NULL); + test_socket_address_parse_one("[::1]:65536", -ERANGE, 0, NULL); + test_socket_address_parse_one("[a:b:1]:8888", -EINVAL, 0, NULL); + test_socket_address_parse_one("[::1]%lo:1234", -EINVAL, 0, NULL); + test_socket_address_parse_one("[::1]%lo:0", -EINVAL, 0, NULL); + test_socket_address_parse_one("[::1]%lo", -EINVAL, 0, NULL); + test_socket_address_parse_one("[::1]%lo%lo:1234", -EINVAL, 0, NULL); + test_socket_address_parse_one("[::1]% lo:1234", -EINVAL, 0, NULL); + + test_socket_address_parse_one("8888", 0, default_family, + default_family == AF_INET6 ? "[::]:8888": "0.0.0.0:8888"); + test_socket_address_parse_one("[2001:0db8:0000:85a3:0000:0000:ac1f:8001]:8888", 0, AF_INET6, + "[2001:db8:0:85a3::ac1f:8001]:8888"); + test_socket_address_parse_one("[::1]:8888", 0, AF_INET6, NULL); + test_socket_address_parse_one("[::1]:1234%lo", 0, AF_INET6, NULL); + test_socket_address_parse_one("[::1]:0%lo", -EINVAL, 0, NULL); + test_socket_address_parse_one("[::1]%lo", -EINVAL, 0, NULL); + test_socket_address_parse_one("[::1]:1234%lo%lo", -EINVAL, 0, NULL); + test_socket_address_parse_one("[::1]:1234%xxxxasdf", -ENODEV, 0, NULL); + test_socket_address_parse_one("192.168.1.254:8888", 0, AF_INET, NULL); + test_socket_address_parse_one("/foo/bar", 0, AF_UNIX, NULL); + test_socket_address_parse_one("/", -EINVAL, 0, NULL); + test_socket_address_parse_one("@abstract", 0, AF_UNIX, NULL); + + { + char aaa[SUN_PATH_LEN + 1] = "@"; + + memset(aaa + 1, 'a', SUN_PATH_LEN - 1); + char_array_0(aaa); + + test_socket_address_parse_one(aaa, -EINVAL, 0, NULL); + + aaa[SUN_PATH_LEN - 1] = '\0'; + test_socket_address_parse_one(aaa, 0, AF_UNIX, NULL); + } + + test_socket_address_parse_one("vsock:2:1234", 0, AF_VSOCK, NULL); + test_socket_address_parse_one("vsock::1234", 0, AF_VSOCK, NULL); + test_socket_address_parse_one("vsock:2:1234x", -EINVAL, 0, NULL); + test_socket_address_parse_one("vsock:2x:1234", -EINVAL, 0, NULL); + test_socket_address_parse_one("vsock:2", -EINVAL, 0, NULL); +} + +TEST(socket_address_parse_netlink) { + SocketAddress a; + + assert_se(socket_address_parse_netlink(&a, "junk") < 0); + assert_se(socket_address_parse_netlink(&a, "") < 0); + + assert_se(socket_address_parse_netlink(&a, "route") >= 0); + assert_se(a.sockaddr.nl.nl_family == AF_NETLINK); + assert_se(a.sockaddr.nl.nl_groups == 0); + assert_se(a.protocol == NETLINK_ROUTE); + assert_se(socket_address_parse_netlink(&a, "route") >= 0); + assert_se(socket_address_parse_netlink(&a, "route 10") >= 0); + assert_se(a.sockaddr.nl.nl_family == AF_NETLINK); + assert_se(a.sockaddr.nl.nl_groups == 10); + assert_se(a.protocol == NETLINK_ROUTE); + + /* With spaces and tabs */ + assert_se(socket_address_parse_netlink(&a, " kobject-uevent ") >= 0); + assert_se(a.sockaddr.nl.nl_family == AF_NETLINK); + assert_se(a.sockaddr.nl.nl_groups == 0); + assert_se(a.protocol == NETLINK_KOBJECT_UEVENT); + assert_se(socket_address_parse_netlink(&a, " \t kobject-uevent \t 10") >= 0); + assert_se(a.sockaddr.nl.nl_family == AF_NETLINK); + assert_se(a.sockaddr.nl.nl_groups == 10); + assert_se(a.protocol == NETLINK_KOBJECT_UEVENT); + assert_se(socket_address_parse_netlink(&a, "kobject-uevent\t10") >= 0); + assert_se(a.sockaddr.nl.nl_family == AF_NETLINK); + assert_se(a.sockaddr.nl.nl_groups == 10); + assert_se(a.protocol == NETLINK_KOBJECT_UEVENT); + + /* trailing space is not supported */ + assert_se(socket_address_parse_netlink(&a, "kobject-uevent\t10 ") < 0); + + /* Group must be unsigned */ + assert_se(socket_address_parse_netlink(&a, "kobject-uevent -1") < 0); + + /* oss-fuzz #6884 */ + assert_se(socket_address_parse_netlink(&a, "\xff") < 0); +} + +TEST(socket_address_equal) { + SocketAddress a, b; + + assert_se(socket_address_parse(&a, "192.168.1.1:8888") >= 0); + assert_se(socket_address_parse(&b, "192.168.1.1:888") >= 0); + assert_se(!socket_address_equal(&a, &b)); + + assert_se(socket_address_parse(&a, "192.168.1.1:8888") >= 0); + assert_se(socket_address_parse(&b, "192.16.1.1:8888") >= 0); + assert_se(!socket_address_equal(&a, &b)); + + assert_se(socket_address_parse(&a, "192.168.1.1:8888") >= 0); + assert_se(socket_address_parse(&b, "8888") >= 0); + assert_se(!socket_address_equal(&a, &b)); + + assert_se(socket_address_parse(&a, "192.168.1.1:8888") >= 0); + assert_se(socket_address_parse(&b, "/foo/bar/") >= 0); + assert_se(!socket_address_equal(&a, &b)); + + assert_se(socket_address_parse(&a, "192.168.1.1:8888") >= 0); + assert_se(socket_address_parse(&b, "192.168.1.1:8888") >= 0); + assert_se(socket_address_equal(&a, &b)); + + assert_se(socket_address_parse(&a, "/foo/bar") >= 0); + assert_se(socket_address_parse(&b, "/foo/bar") >= 0); + assert_se(socket_address_equal(&a, &b)); + + assert_se(socket_address_parse(&a, "[::1]:8888") >= 0); + assert_se(socket_address_parse(&b, "[::1]:8888") >= 0); + assert_se(socket_address_equal(&a, &b)); + + assert_se(socket_address_parse(&a, "@abstract") >= 0); + assert_se(socket_address_parse(&b, "@abstract") >= 0); + assert_se(socket_address_equal(&a, &b)); + + assert_se(socket_address_parse_netlink(&a, "firewall") >= 0); + assert_se(socket_address_parse_netlink(&b, "firewall") >= 0); + assert_se(socket_address_equal(&a, &b)); + + assert_se(socket_address_parse(&a, "vsock:2:1234") >= 0); + assert_se(socket_address_parse(&b, "vsock:2:1234") >= 0); + assert_se(socket_address_equal(&a, &b)); + assert_se(socket_address_parse(&b, "vsock:2:1235") >= 0); + assert_se(!socket_address_equal(&a, &b)); + assert_se(socket_address_parse(&b, "vsock:3:1234") >= 0); + assert_se(!socket_address_equal(&a, &b)); +} + +TEST(socket_address_get_path) { + SocketAddress a; + + assert_se(socket_address_parse(&a, "192.168.1.1:8888") >= 0); + assert_se(!socket_address_get_path(&a)); + + assert_se(socket_address_parse(&a, "@abstract") >= 0); + assert_se(!socket_address_get_path(&a)); + + assert_se(socket_address_parse(&a, "[::1]:8888") >= 0); + assert_se(!socket_address_get_path(&a)); + + assert_se(socket_address_parse(&a, "/foo/bar") >= 0); + assert_se(streq(socket_address_get_path(&a), "/foo/bar")); + + assert_se(socket_address_parse(&a, "vsock:2:1234") >= 0); + assert_se(!socket_address_get_path(&a)); +} + +TEST(socket_address_is) { + SocketAddress a; + + assert_se(socket_address_parse(&a, "192.168.1.1:8888") >= 0); + assert_se( socket_address_is(&a, "192.168.1.1:8888", 0 /* unspecified yet */)); + assert_se(!socket_address_is(&a, "route", 0)); + assert_se(!socket_address_is(&a, "route", SOCK_STREAM)); + assert_se(!socket_address_is(&a, "192.168.1.1:8888", SOCK_RAW)); + assert_se(!socket_address_is(&a, "192.168.1.1:8888", SOCK_STREAM)); + a.type = SOCK_STREAM; + assert_se( socket_address_is(&a, "192.168.1.1:8888", SOCK_STREAM)); +} + +TEST(socket_address_is_netlink) { + SocketAddress a; + + assert_se(socket_address_parse_netlink(&a, "route 10") >= 0); + assert_se( socket_address_is_netlink(&a, "route 10")); + assert_se(!socket_address_is_netlink(&a, "192.168.1.1:8888")); + assert_se(!socket_address_is_netlink(&a, "route 1")); +} + +static void test_in_addr_ifindex_to_string_one(int f, const char *a, int ifindex, const char *b) { + _cleanup_free_ char *r = NULL; + union in_addr_union ua, uuaa; + int ff, ifindex2; + + assert_se(in_addr_from_string(f, a, &ua) >= 0); + assert_se(in_addr_ifindex_to_string(f, &ua, ifindex, &r) >= 0); + printf("test_in_addr_ifindex_to_string_one: %s == %s\n", b, r); + assert_se(streq(b, r)); + + assert_se(in_addr_ifindex_from_string_auto(b, &ff, &uuaa, &ifindex2) >= 0); + assert_se(ff == f); + assert_se(in_addr_equal(f, &ua, &uuaa)); + assert_se(ifindex2 == ifindex || ifindex2 == 0); +} + +TEST(in_addr_ifindex_to_string) { + test_in_addr_ifindex_to_string_one(AF_INET, "192.168.0.1", 7, "192.168.0.1"); + test_in_addr_ifindex_to_string_one(AF_INET, "10.11.12.13", 9, "10.11.12.13"); + test_in_addr_ifindex_to_string_one(AF_INET6, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 10, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); + test_in_addr_ifindex_to_string_one(AF_INET6, "::1", 11, "::1"); + test_in_addr_ifindex_to_string_one(AF_INET6, "fe80::", 12, "fe80::%12"); + test_in_addr_ifindex_to_string_one(AF_INET6, "fe80::", 0, "fe80::"); + test_in_addr_ifindex_to_string_one(AF_INET6, "fe80::14", 12, "fe80::14%12"); + test_in_addr_ifindex_to_string_one(AF_INET6, "fe80::15", -7, "fe80::15"); + test_in_addr_ifindex_to_string_one(AF_INET6, "fe80::16", LOOPBACK_IFINDEX, "fe80::16%1"); +} + +TEST(in_addr_ifindex_from_string_auto) { + int family, ifindex; + union in_addr_union ua; + + /* Most in_addr_ifindex_from_string_auto() invocations have already been tested above, but let's test some more */ + + assert_se(in_addr_ifindex_from_string_auto("fe80::17", &family, &ua, &ifindex) >= 0); + assert_se(family == AF_INET6); + assert_se(ifindex == 0); + + assert_se(in_addr_ifindex_from_string_auto("fe80::18%19", &family, &ua, &ifindex) >= 0); + assert_se(family == AF_INET6); + assert_se(ifindex == 19); + + assert_se(in_addr_ifindex_from_string_auto("fe80::18%lo", &family, &ua, &ifindex) >= 0); + assert_se(family == AF_INET6); + assert_se(ifindex == LOOPBACK_IFINDEX); + + assert_se(in_addr_ifindex_from_string_auto("fe80::19%thisinterfacecantexist", &family, &ua, &ifindex) == -ENODEV); +} + +static void test_in_addr_ifindex_name_from_string_auto_one(const char *a, const char *expected) { + int family, ifindex; + union in_addr_union ua; + _cleanup_free_ char *server_name = NULL; + + assert_se(in_addr_ifindex_name_from_string_auto(a, &family, &ua, &ifindex, &server_name) >= 0); + assert_se(streq_ptr(server_name, expected)); +} + +TEST(in_addr_ifindex_name_from_string_auto) { + test_in_addr_ifindex_name_from_string_auto_one("192.168.0.1", NULL); + test_in_addr_ifindex_name_from_string_auto_one("192.168.0.1#test.com", "test.com"); + test_in_addr_ifindex_name_from_string_auto_one("fe80::18%19", NULL); + test_in_addr_ifindex_name_from_string_auto_one("fe80::18%19#another.test.com", "another.test.com"); +} + +static void test_in_addr_port_ifindex_name_from_string_auto_one(const char *str, int family, uint16_t port, int ifindex, + const char *server_name, const char *str_repr) { + union in_addr_union a; + uint16_t p; + int f, i; + char *fake; + + log_info("%s: %s", __func__, str); + + { + _cleanup_free_ char *name = NULL, *x = NULL; + assert_se(in_addr_port_ifindex_name_from_string_auto(str, &f, &a, &p, &i, &name) == 0); + assert_se(family == f); + assert_se(port == p); + assert_se(ifindex == i); + assert_se(streq_ptr(server_name, name)); + assert_se(in_addr_port_ifindex_name_to_string(f, &a, p, i, name, &x) >= 0); + assert_se(streq(str_repr ?: str, x)); + } + + if (port > 0) + assert_se(in_addr_port_ifindex_name_from_string_auto(str, &f, &a, NULL, &i, &fake) == -EINVAL); + else { + _cleanup_free_ char *name = NULL, *x = NULL; + assert_se(in_addr_port_ifindex_name_from_string_auto(str, &f, &a, NULL, &i, &name) == 0); + assert_se(family == f); + assert_se(ifindex == i); + assert_se(streq_ptr(server_name, name)); + assert_se(in_addr_port_ifindex_name_to_string(f, &a, 0, i, name, &x) >= 0); + assert_se(streq(str_repr ?: str, x)); + } + + if (ifindex > 0) + assert_se(in_addr_port_ifindex_name_from_string_auto(str, &f, &a, &p, NULL, &fake) == -EINVAL); + else { + _cleanup_free_ char *name = NULL, *x = NULL; + assert_se(in_addr_port_ifindex_name_from_string_auto(str, &f, &a, &p, NULL, &name) == 0); + assert_se(family == f); + assert_se(port == p); + assert_se(streq_ptr(server_name, name)); + assert_se(in_addr_port_ifindex_name_to_string(f, &a, p, 0, name, &x) >= 0); + assert_se(streq(str_repr ?: str, x)); + } + + if (server_name) + assert_se(in_addr_port_ifindex_name_from_string_auto(str, &f, &a, &p, &i, NULL) == -EINVAL); + else { + _cleanup_free_ char *x = NULL; + assert_se(in_addr_port_ifindex_name_from_string_auto(str, &f, &a, &p, &i, NULL) == 0); + assert_se(family == f); + assert_se(port == p); + assert_se(ifindex == i); + assert_se(in_addr_port_ifindex_name_to_string(f, &a, p, i, NULL, &x) >= 0); + assert_se(streq(str_repr ?: str, x)); + } +} + +TEST(in_addr_port_ifindex_name_from_string_auto) { + test_in_addr_port_ifindex_name_from_string_auto_one("192.168.0.1", AF_INET, 0, 0, NULL, NULL); + test_in_addr_port_ifindex_name_from_string_auto_one("192.168.0.1#test.com", AF_INET, 0, 0, "test.com", NULL); + test_in_addr_port_ifindex_name_from_string_auto_one("192.168.0.1:53", AF_INET, 53, 0, NULL, NULL); + test_in_addr_port_ifindex_name_from_string_auto_one("192.168.0.1:53#example.com", AF_INET, 53, 0, "example.com", NULL); + test_in_addr_port_ifindex_name_from_string_auto_one("fe80::18", AF_INET6, 0, 0, NULL, NULL); + test_in_addr_port_ifindex_name_from_string_auto_one("fe80::18#hoge.com", AF_INET6, 0, 0, "hoge.com", NULL); + test_in_addr_port_ifindex_name_from_string_auto_one("fe80::18%19", AF_INET6, 0, 19, NULL, NULL); + test_in_addr_port_ifindex_name_from_string_auto_one("fe80::18%lo", AF_INET6, 0, 1, NULL, "fe80::18%1"); + test_in_addr_port_ifindex_name_from_string_auto_one("[fe80::18]:53", AF_INET6, 53, 0, NULL, NULL); + test_in_addr_port_ifindex_name_from_string_auto_one("[fe80::18]:53%19", AF_INET6, 53, 19, NULL, NULL); + test_in_addr_port_ifindex_name_from_string_auto_one("[fe80::18]:53%lo", AF_INET6, 53, 1, NULL, "[fe80::18]:53%1"); + test_in_addr_port_ifindex_name_from_string_auto_one("fe80::18%19#hoge.com", AF_INET6, 0, 19, "hoge.com", NULL); + test_in_addr_port_ifindex_name_from_string_auto_one("[fe80::18]:53#hoge.com", AF_INET6, 53, 0, "hoge.com", NULL); + test_in_addr_port_ifindex_name_from_string_auto_one("[fe80::18]:53%19", AF_INET6, 53, 19, NULL, NULL); + test_in_addr_port_ifindex_name_from_string_auto_one("[fe80::18]:53%19#hoge.com", AF_INET6, 53, 19, "hoge.com", NULL); + test_in_addr_port_ifindex_name_from_string_auto_one("[fe80::18]:53%lo", AF_INET6, 53, 1, NULL, "[fe80::18]:53%1"); + test_in_addr_port_ifindex_name_from_string_auto_one("[fe80::18]:53%lo#hoge.com", AF_INET6, 53, 1, "hoge.com", "[fe80::18]:53%1#hoge.com"); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-socket-util.c b/src/test/test-socket-util.c new file mode 100644 index 0000000..e9c776a --- /dev/null +++ b/src/test/test-socket-util.c @@ -0,0 +1,593 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <fcntl.h> +#include <grp.h> +#include <net/if_arp.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "async.h" +#include "escape.h" +#include "exit-status.h" +#include "fd-util.h" +#include "fs-util.h" +#include "in-addr-util.h" +#include "iovec-util.h" +#include "log.h" +#include "macro.h" +#include "path-util.h" +#include "process-util.h" +#include "random-util.h" +#include "rm-rf.h" +#include "socket-util.h" +#include "string-util.h" +#include "tests.h" +#include "tmpfile-util.h" + +assert_cc(SUN_PATH_LEN == 108); + +TEST(ifname_valid) { + assert_se( ifname_valid("foo")); + assert_se( ifname_valid("eth0")); + + assert_se(!ifname_valid("0")); + assert_se(!ifname_valid("99")); + assert_se( ifname_valid("a99")); + assert_se( ifname_valid("99a")); + + assert_se(!ifname_valid(NULL)); + assert_se(!ifname_valid("")); + assert_se(!ifname_valid(" ")); + assert_se(!ifname_valid(" foo")); + assert_se(!ifname_valid("bar\n")); + assert_se(!ifname_valid(".")); + assert_se(!ifname_valid("..")); + assert_se(ifname_valid("foo.bar")); + assert_se(!ifname_valid("x:y")); + + assert_se( ifname_valid_full("xxxxxxxxxxxxxxx", 0)); + assert_se(!ifname_valid_full("xxxxxxxxxxxxxxxx", 0)); + assert_se( ifname_valid_full("xxxxxxxxxxxxxxxx", IFNAME_VALID_ALTERNATIVE)); + assert_se( ifname_valid_full("xxxxxxxxxxxxxxxx", IFNAME_VALID_ALTERNATIVE)); + assert_se(!ifname_valid_full("999", IFNAME_VALID_ALTERNATIVE)); + assert_se( ifname_valid_full("999", IFNAME_VALID_ALTERNATIVE | IFNAME_VALID_NUMERIC)); + assert_se(!ifname_valid_full("0", IFNAME_VALID_ALTERNATIVE | IFNAME_VALID_NUMERIC)); +} + +static void test_socket_print_unix_one(const char *in, size_t len_in, const char *expected) { + _cleanup_free_ char *out = NULL, *c = NULL; + + assert_se(len_in <= SUN_PATH_LEN); + SocketAddress a = { .sockaddr = { .un = { .sun_family = AF_UNIX } }, + .size = offsetof(struct sockaddr_un, sun_path) + len_in, + .type = SOCK_STREAM, + }; + memcpy(a.sockaddr.un.sun_path, in, len_in); + + assert_se(socket_address_print(&a, &out) >= 0); + assert_se(c = cescape(in)); + log_info("\"%s\" → \"%s\" (expect \"%s\")", in, out, expected); + assert_se(streq(out, expected)); +} + +TEST(socket_print_unix) { + /* Some additional tests for abstract addresses which we don't parse */ + + test_socket_print_unix_one("\0\0\0\0", 4, "@\\000\\000\\000"); + test_socket_print_unix_one("@abs", 5, "@abs"); + test_socket_print_unix_one("\n", 2, "\\n"); + test_socket_print_unix_one("", 1, "<unnamed>"); + test_socket_print_unix_one("\0", 1, "<unnamed>"); + test_socket_print_unix_one("\0_________________________there's 108 characters in this string_____________________________________________", 108, + "@_________________________there\\'s 108 characters in this string_____________________________________________"); + test_socket_print_unix_one("////////////////////////////////////////////////////////////////////////////////////////////////////////////", 108, + "////////////////////////////////////////////////////////////////////////////////////////////////////////////"); + test_socket_print_unix_one("\0\a\b\n\255", 6, "@\\a\\b\\n\\255\\000"); +} + +TEST(sockaddr_equal) { + union sockaddr_union a = { + .in.sin_family = AF_INET, + .in.sin_port = 0, + .in.sin_addr.s_addr = htobe32(INADDR_ANY), + }; + union sockaddr_union b = { + .in.sin_family = AF_INET, + .in.sin_port = 0, + .in.sin_addr.s_addr = htobe32(INADDR_ANY), + }; + union sockaddr_union c = { + .in.sin_family = AF_INET, + .in.sin_port = 0, + .in.sin_addr.s_addr = htobe32(1234), + }; + union sockaddr_union d = { + .in6.sin6_family = AF_INET6, + .in6.sin6_port = 0, + .in6.sin6_addr = IN6ADDR_ANY_INIT, + }; + union sockaddr_union e = { + .vm.svm_family = AF_VSOCK, + .vm.svm_port = 0, + .vm.svm_cid = VMADDR_CID_ANY, + }; + + assert_se(sockaddr_equal(&a, &a)); + assert_se(sockaddr_equal(&a, &b)); + assert_se(sockaddr_equal(&d, &d)); + assert_se(sockaddr_equal(&e, &e)); + assert_se(!sockaddr_equal(&a, &c)); + assert_se(!sockaddr_equal(&b, &c)); + assert_se(!sockaddr_equal(&a, &e)); +} + +TEST(sockaddr_un_len) { + static const struct sockaddr_un fs = { + .sun_family = AF_UNIX, + .sun_path = "/foo/bar/waldo", + }; + + static const struct sockaddr_un abstract = { + .sun_family = AF_UNIX, + .sun_path = "\0foobar", + }; + + assert_se(SOCKADDR_UN_LEN(fs) == offsetof(struct sockaddr_un, sun_path) + strlen(fs.sun_path) + 1); + assert_se(SOCKADDR_UN_LEN(abstract) == offsetof(struct sockaddr_un, sun_path) + 1 + strlen(abstract.sun_path + 1)); +} + +TEST(in_addr_is_multicast) { + union in_addr_union a, b; + int f; + + assert_se(in_addr_from_string_auto("192.168.3.11", &f, &a) >= 0); + assert_se(in_addr_is_multicast(f, &a) == 0); + + assert_se(in_addr_from_string_auto("224.0.0.1", &f, &a) >= 0); + assert_se(in_addr_is_multicast(f, &a) == 1); + + assert_se(in_addr_from_string_auto("FF01:0:0:0:0:0:0:1", &f, &b) >= 0); + assert_se(in_addr_is_multicast(f, &b) == 1); + + assert_se(in_addr_from_string_auto("2001:db8::c:69b:aeff:fe53:743e", &f, &b) >= 0); + assert_se(in_addr_is_multicast(f, &b) == 0); +} + +TEST(getpeercred_getpeergroups) { + int r; + + r = safe_fork("(getpeercred)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL); + assert_se(r >= 0); + + if (r == 0) { + static const gid_t gids[] = { 3, 4, 5, 6, 7 }; + gid_t *test_gids; + size_t n_test_gids; + uid_t test_uid; + gid_t test_gid; + struct ucred ucred; + int pair[2]; + + if (geteuid() == 0) { + test_uid = 1; + test_gid = 2; + test_gids = (gid_t*) gids; + n_test_gids = ELEMENTSOF(gids); + + assert_se(setgroups(n_test_gids, test_gids) >= 0); + assert_se(setresgid(test_gid, test_gid, test_gid) >= 0); + assert_se(setresuid(test_uid, test_uid, test_uid) >= 0); + + } else { + long ngroups_max; + + test_uid = getuid(); + test_gid = getgid(); + + ngroups_max = sysconf(_SC_NGROUPS_MAX); + assert_se(ngroups_max > 0); + + test_gids = newa(gid_t, ngroups_max); + + r = getgroups(ngroups_max, test_gids); + assert_se(r >= 0); + n_test_gids = (size_t) r; + } + + assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, pair) >= 0); + + assert_se(getpeercred(pair[0], &ucred) >= 0); + + assert_se(ucred.uid == test_uid); + assert_se(ucred.gid == test_gid); + assert_se(ucred.pid == getpid_cached()); + + { + _cleanup_free_ gid_t *peer_groups = NULL; + + r = getpeergroups(pair[0], &peer_groups); + assert_se(r >= 0 || IN_SET(r, -EOPNOTSUPP, -ENOPROTOOPT)); + + if (r >= 0) { + assert_se((size_t) r == n_test_gids); + assert_se(memcmp(peer_groups, test_gids, sizeof(gid_t) * n_test_gids) == 0); + } + } + + safe_close_pair(pair); + _exit(EXIT_SUCCESS); + } +} + +TEST(passfd_read) { + static const char file_contents[] = "test contents for passfd"; + _cleanup_close_pair_ int pair[2]; + int r; + + assert_se(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) >= 0); + + r = safe_fork("(passfd_read)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL); + assert_se(r >= 0); + + if (r == 0) { + /* Child */ + pair[0] = safe_close(pair[0]); + + char tmpfile[] = "/tmp/test-socket-util-passfd-read-XXXXXX"; + assert_se(write_tmpfile(tmpfile, file_contents) == 0); + + _cleanup_close_ int tmpfd = open(tmpfile, O_RDONLY); + assert_se(tmpfd >= 0); + assert_se(unlink(tmpfile) == 0); + + assert_se(send_one_fd(pair[1], tmpfd, MSG_DONTWAIT) == 0); + _exit(EXIT_SUCCESS); + } + + /* Parent */ + char buf[64]; + struct iovec iov = IOVEC_MAKE(buf, sizeof(buf)-1); + _cleanup_close_ int fd; + + pair[1] = safe_close(pair[1]); + + assert_se(receive_one_fd_iov(pair[0], &iov, 1, MSG_DONTWAIT, &fd) == 0); + + assert_se(fd >= 0); + r = read(fd, buf, sizeof(buf)-1); + assert_se(r >= 0); + buf[r] = 0; + assert_se(streq(buf, file_contents)); +} + +TEST(passfd_contents_read) { + _cleanup_close_pair_ int pair[2]; + static const char file_contents[] = "test contents in the file"; + static const char wire_contents[] = "test contents on the wire"; + int r; + + assert_se(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) >= 0); + + r = safe_fork("(passfd_contents_read)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL); + assert_se(r >= 0); + + if (r == 0) { + /* Child */ + struct iovec iov = IOVEC_MAKE_STRING(wire_contents); + char tmpfile[] = "/tmp/test-socket-util-passfd-contents-read-XXXXXX"; + + pair[0] = safe_close(pair[0]); + + assert_se(write_tmpfile(tmpfile, file_contents) == 0); + + _cleanup_close_ int tmpfd = open(tmpfile, O_RDONLY); + assert_se(tmpfd >= 0); + assert_se(unlink(tmpfile) == 0); + + assert_se(send_one_fd_iov(pair[1], tmpfd, &iov, 1, MSG_DONTWAIT) > 0); + _exit(EXIT_SUCCESS); + } + + /* Parent */ + char buf[64]; + struct iovec iov = IOVEC_MAKE(buf, sizeof(buf)-1); + _cleanup_close_ int fd; + ssize_t k; + + pair[1] = safe_close(pair[1]); + + k = receive_one_fd_iov(pair[0], &iov, 1, MSG_DONTWAIT, &fd); + assert_se(k > 0); + buf[k] = 0; + assert_se(streq(buf, wire_contents)); + + assert_se(fd >= 0); + r = read(fd, buf, sizeof(buf)-1); + assert_se(r >= 0); + buf[r] = 0; + assert_se(streq(buf, file_contents)); +} + +TEST(pass_many_fds_contents_read) { + _cleanup_close_pair_ int pair[2]; + static const char file_contents[][STRLEN("test contents in the fileX") + 1] = { + "test contents in the file0", + "test contents in the file1", + "test contents in the file2" + }; + static const char wire_contents[] = "test contents on the wire"; + int r; + + assert_se(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) >= 0); + + r = safe_fork("(passfd_contents_read)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL); + assert_se(r >= 0); + + if (r == 0) { + /* Child */ + struct iovec iov = IOVEC_MAKE_STRING(wire_contents); + char tmpfile[][STRLEN("/tmp/test-socket-util-passfd-contents-read-XXXXXX") + 1] = { + "/tmp/test-socket-util-passfd-contents-read-XXXXXX", + "/tmp/test-socket-util-passfd-contents-read-XXXXXX", + "/tmp/test-socket-util-passfd-contents-read-XXXXXX" + }; + int tmpfds[3] = EBADF_TRIPLET; + + pair[0] = safe_close(pair[0]); + + for (size_t i = 0; i < 3; ++i) { + assert_se(write_tmpfile(tmpfile[i], file_contents[i]) == 0); + tmpfds[i] = open(tmpfile[i], O_RDONLY); + assert_se(tmpfds[i] >= 0); + assert_se(unlink(tmpfile[i]) == 0); + } + + assert_se(send_many_fds_iov(pair[1], tmpfds, 3, &iov, 1, MSG_DONTWAIT) > 0); + close_many(tmpfds, 3); + _exit(EXIT_SUCCESS); + } + + /* Parent */ + char buf[64]; + struct iovec iov = IOVEC_MAKE(buf, sizeof(buf)-1); + _cleanup_free_ int *fds = NULL; + size_t n_fds = 0; + ssize_t k; + + pair[1] = safe_close(pair[1]); + + k = receive_many_fds_iov(pair[0], &iov, 1, &fds, &n_fds, MSG_DONTWAIT); + assert_se(k > 0); + buf[k] = 0; + assert_se(streq(buf, wire_contents)); + + assert_se(n_fds == 3); + + for (size_t i = 0; i < 3; ++i) { + assert_se(fds[i] >= 0); + r = read(fds[i], buf, sizeof(buf)-1); + assert_se(r >= 0); + buf[r] = 0; + assert_se(streq(buf, file_contents[i])); + safe_close(fds[i]); + } +} + +TEST(receive_nopassfd) { + _cleanup_close_pair_ int pair[2]; + static const char wire_contents[] = "no fd passed here"; + int r; + + assert_se(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) >= 0); + + r = safe_fork("(receive_nopassfd)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL); + assert_se(r >= 0); + + if (r == 0) { + /* Child */ + struct iovec iov = IOVEC_MAKE_STRING(wire_contents); + + pair[0] = safe_close(pair[0]); + + assert_se(send_one_fd_iov(pair[1], -1, &iov, 1, MSG_DONTWAIT) > 0); + _exit(EXIT_SUCCESS); + } + + /* Parent */ + char buf[64]; + struct iovec iov = IOVEC_MAKE(buf, sizeof(buf)-1); + int fd = -999; + ssize_t k; + + pair[1] = safe_close(pair[1]); + + k = receive_one_fd_iov(pair[0], &iov, 1, MSG_DONTWAIT, &fd); + assert_se(k > 0); + buf[k] = 0; + assert_se(streq(buf, wire_contents)); + + /* no fd passed here, confirm it was reset */ + assert_se(fd == -EBADF); +} + +TEST(send_nodata_nofd) { + _cleanup_close_pair_ int pair[2]; + int r; + + assert_se(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) >= 0); + + r = safe_fork("(send_nodata_nofd)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL); + assert_se(r >= 0); + + if (r == 0) { + /* Child */ + pair[0] = safe_close(pair[0]); + + assert_se(send_one_fd_iov(pair[1], -1, NULL, 0, MSG_DONTWAIT) == -EINVAL); + _exit(EXIT_SUCCESS); + } + + /* Parent */ + char buf[64]; + struct iovec iov = IOVEC_MAKE(buf, sizeof(buf)-1); + int fd = -999; + ssize_t k; + + pair[1] = safe_close(pair[1]); + + k = receive_one_fd_iov(pair[0], &iov, 1, MSG_DONTWAIT, &fd); + /* recvmsg() will return errno EAGAIN if nothing was sent */ + assert_se(k == -EAGAIN); + + /* receive_one_fd_iov returned error, so confirm &fd wasn't touched */ + assert_se(fd == -999); +} + +TEST(send_emptydata) { + _cleanup_close_pair_ int pair[2]; + int r; + + assert_se(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) >= 0); + + r = safe_fork("(send_emptydata)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL); + assert_se(r >= 0); + + if (r == 0) { + /* Child */ + struct iovec iov = IOVEC_MAKE_STRING(""); /* zero-length iov */ + assert_se(iov.iov_len == 0); + + pair[0] = safe_close(pair[0]); + + /* This will succeed, since iov is set. */ + assert_se(send_one_fd_iov(pair[1], -1, &iov, 1, MSG_DONTWAIT) == 0); + _exit(EXIT_SUCCESS); + } + + /* Parent */ + char buf[64]; + struct iovec iov = IOVEC_MAKE(buf, sizeof(buf)-1); + int fd = -999; + ssize_t k; + + pair[1] = safe_close(pair[1]); + + k = receive_one_fd_iov(pair[0], &iov, 1, MSG_DONTWAIT, &fd); + /* receive_one_fd_iov() returns -EIO if an fd is not found and no data was returned. */ + assert_se(k == -EIO); + + /* receive_one_fd_iov returned error, so confirm &fd wasn't touched */ + assert_se(fd == -999); +} + +TEST(flush_accept) { + _cleanup_close_ int listen_stream, listen_dgram, listen_seqpacket, connect_stream, connect_dgram, connect_seqpacket; + static const union sockaddr_union sa = { .un.sun_family = AF_UNIX }; + union sockaddr_union lsa; + socklen_t l; + + listen_stream = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + assert_se(listen_stream >= 0); + + listen_dgram = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + assert_se(listen_dgram >= 0); + + listen_seqpacket = socket(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + assert_se(listen_seqpacket >= 0); + + assert_se(flush_accept(listen_stream) < 0); + assert_se(flush_accept(listen_dgram) < 0); + assert_se(flush_accept(listen_seqpacket) < 0); + + assert_se(bind(listen_stream, &sa.sa, sizeof(sa_family_t)) >= 0); + assert_se(bind(listen_dgram, &sa.sa, sizeof(sa_family_t)) >= 0); + assert_se(bind(listen_seqpacket, &sa.sa, sizeof(sa_family_t)) >= 0); + + assert_se(flush_accept(listen_stream) < 0); + assert_se(flush_accept(listen_dgram) < 0); + assert_se(flush_accept(listen_seqpacket) < 0); + + assert_se(listen(listen_stream, SOMAXCONN_DELUXE) >= 0); + assert_se(listen(listen_dgram, SOMAXCONN_DELUXE) < 0); + assert_se(listen(listen_seqpacket, SOMAXCONN_DELUXE) >= 0); + + assert_se(flush_accept(listen_stream) >= 0); + assert_se(flush_accept(listen_dgram) < 0); + assert_se(flush_accept(listen_seqpacket) >= 0); + + connect_stream = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + assert_se(connect_stream >= 0); + + connect_dgram = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + assert_se(connect_dgram >= 0); + + connect_seqpacket = socket(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + assert_se(connect_seqpacket >= 0); + + l = sizeof(lsa); + assert_se(getsockname(listen_stream, &lsa.sa, &l) >= 0); + assert_se(connect(connect_stream, &lsa.sa, l) >= 0); + + l = sizeof(lsa); + assert_se(getsockname(listen_dgram, &lsa.sa, &l) >= 0); + assert_se(connect(connect_dgram, &lsa.sa, l) >= 0); + + l = sizeof(lsa); + assert_se(getsockname(listen_seqpacket, &lsa.sa, &l) >= 0); + assert_se(connect(connect_seqpacket, &lsa.sa, l) >= 0); + + assert_se(flush_accept(listen_stream) >= 0); + assert_se(flush_accept(listen_dgram) < 0); + assert_se(flush_accept(listen_seqpacket) >= 0); +} + +TEST(ipv6_enabled) { + log_info("IPv6 supported: %s", yes_no(socket_ipv6_is_supported())); + log_info("IPv6 enabled: %s", yes_no(socket_ipv6_is_enabled())); +} + +TEST(sockaddr_un_set_path) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_(unlink_and_freep) char *sh = NULL; + _cleanup_free_ char *j = NULL; + union sockaddr_union sa; + _cleanup_close_ int fd1, fd2, fd3; + + assert_se(mkdtemp_malloc("/tmp/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaXXXXXX", &t) >= 0); + assert_se(strlen(t) > SUN_PATH_LEN); + + assert_se(j = path_join(t, "sock")); + assert_se(sockaddr_un_set_path(&sa.un, j) == -ENAMETOOLONG); /* too long for AF_UNIX socket */ + + assert_se(asprintf(&sh, "/tmp/%" PRIx64, random_u64()) >= 0); + assert_se(symlink(t, sh) >= 0); /* create temporary symlink, to access it anyway */ + + free(j); + assert_se(j = path_join(sh, "sock")); + assert_se(sockaddr_un_set_path(&sa.un, j) >= 0); + + fd1 = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); + assert_se(fd1 >= 0); + assert_se(bind(fd1, &sa.sa, SOCKADDR_LEN(sa)) >= 0); + assert_se(listen(fd1, 1) >= 0); + + sh = unlink_and_free(sh); /* remove temporary symlink */ + + fd2 = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); + assert_se(fd2 >= 0); + assert_se(connect(fd2, &sa.sa, SOCKADDR_LEN(sa)) < 0); + assert_se(errno == ENOENT); /* we removed the symlink, must fail */ + + free(j); + assert_se(j = path_join(t, "sock")); + + fd3 = open(j, O_CLOEXEC|O_PATH|O_NOFOLLOW); + assert_se(fd3 > 0); + assert_se(sockaddr_un_set_path(&sa.un, FORMAT_PROC_FD_PATH(fd3)) >= 0); /* connect via O_PATH instead, circumventing 108ch limit */ + + assert_se(connect(fd2, &sa.sa, SOCKADDR_LEN(sa)) >= 0); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-specifier.c b/src/test/test-specifier.c new file mode 100644 index 0000000..d6a8b79 --- /dev/null +++ b/src/test/test-specifier.c @@ -0,0 +1,188 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-id128.h" + +#include "alloc-util.h" +#include "log.h" +#include "specifier.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" +#include "unit-file.h" + +static void test_specifier_escape_one(const char *a, const char *b) { + _cleanup_free_ char *x = NULL; + + x = specifier_escape(a); + assert_se(streq_ptr(x, b)); +} + +TEST(specifier_escape) { + test_specifier_escape_one(NULL, NULL); + test_specifier_escape_one("", ""); + test_specifier_escape_one("%", "%%"); + test_specifier_escape_one("foo bar", "foo bar"); + test_specifier_escape_one("foo%bar", "foo%%bar"); + test_specifier_escape_one("%%%%%", "%%%%%%%%%%"); +} + +static void test_specifier_escape_strv_one(char **a, char **b) { + _cleanup_strv_free_ char **x = NULL; + + assert_se(specifier_escape_strv(a, &x) >= 0); + assert_se(strv_equal(x, b)); +} + +TEST(specifier_escape_strv) { + test_specifier_escape_strv_one(NULL, NULL); + test_specifier_escape_strv_one(STRV_MAKE(NULL), STRV_MAKE(NULL)); + test_specifier_escape_strv_one(STRV_MAKE(""), STRV_MAKE("")); + test_specifier_escape_strv_one(STRV_MAKE("foo"), STRV_MAKE("foo")); + test_specifier_escape_strv_one(STRV_MAKE("%"), STRV_MAKE("%%")); + test_specifier_escape_strv_one(STRV_MAKE("foo", "%", "foo%", "%foo", "foo%foo", "quux", "%%%"), + STRV_MAKE("foo", "%%", "foo%%", "%%foo", "foo%%foo", "quux", "%%%%%%")); +} + +/* Any specifier functions which don't need an argument. */ +static const Specifier specifier_table[] = { + COMMON_SYSTEM_SPECIFIERS, + + COMMON_CREDS_SPECIFIERS(RUNTIME_SCOPE_USER), + { 'h', specifier_user_home, NULL }, + + COMMON_TMP_SPECIFIERS, + {} +}; + +TEST(specifier_printf) { + static const Specifier table[] = { + { 'X', specifier_string, (char*) "AAAA" }, + { 'Y', specifier_string, (char*) "BBBB" }, + { 'e', specifier_string, NULL }, + COMMON_SYSTEM_SPECIFIERS, + {} + }; + + _cleanup_free_ char *w = NULL; + int r; + + r = specifier_printf("xxx a=%X b=%Y e=%e yyy", SIZE_MAX, table, NULL, NULL, &w); + assert_se(r >= 0); + assert_se(w); + + puts(w); + assert_se(streq(w, "xxx a=AAAA b=BBBB e= yyy")); + + free(w); + r = specifier_printf("boot=%b, host=%H, pretty=%q, version=%v, arch=%a, empty=%e", SIZE_MAX, table, NULL, NULL, &w); + assert_se(r >= 0); + assert_se(w); + puts(w); + + w = mfree(w); + specifier_printf("os=%o, os-version=%w, build=%B, variant=%W, empty=%e%e%e", SIZE_MAX, table, NULL, NULL, &w); + if (w) + puts(w); +} + +TEST(specifier_real_path) { + static const Specifier table[] = { + { 'p', specifier_string, "/dev/initctl" }, + { 'y', specifier_real_path, "/dev/initctl" }, + { 'Y', specifier_real_directory, "/dev/initctl" }, + { 'w', specifier_real_path, "/dev/tty" }, + { 'W', specifier_real_directory, "/dev/tty" }, + {} + }; + + _cleanup_free_ char *w = NULL; + int r; + + r = specifier_printf("p=%p y=%y Y=%Y w=%w W=%W", SIZE_MAX, table, NULL, NULL, &w); + assert_se(r >= 0 || r == -ENOENT); + assert_se(w || r == -ENOENT); + puts(strnull(w)); + + /* /dev/initctl should normally be a symlink to /run/initctl */ + if (inode_same("/dev/initctl", "/run/initctl", 0) > 0) + assert_se(streq(w, "p=/dev/initctl y=/run/initctl Y=/run w=/dev/tty W=/dev")); +} + +TEST(specifier_real_path_missing_file) { + static const Specifier table[] = { + { 'p', specifier_string, "/dev/-no-such-file--" }, + { 'y', specifier_real_path, "/dev/-no-such-file--" }, + { 'Y', specifier_real_directory, "/dev/-no-such-file--" }, + {} + }; + + _cleanup_free_ char *w = NULL; + int r; + + r = specifier_printf("p=%p y=%y", SIZE_MAX, table, NULL, NULL, &w); + assert_se(r == -ENOENT); + + r = specifier_printf("p=%p Y=%Y", SIZE_MAX, table, NULL, NULL, &w); + assert_se(r == -ENOENT); +} + +TEST(specifiers) { + int r; + + for (const Specifier *s = specifier_table; s->specifier; s++) { + char spec[3]; + _cleanup_free_ char *resolved = NULL; + + xsprintf(spec, "%%%c", s->specifier); + + r = specifier_printf(spec, SIZE_MAX, specifier_table, NULL, NULL, &resolved); + if (s->specifier == 'm' && IN_SET(r, -EUNATCH, -ENOMEDIUM, -ENOPKG)) /* machine-id might be missing in build chroots */ + continue; + assert_se(r >= 0); + + log_info("%%%c → %s", s->specifier, resolved); + } +} + +/* Bunch of specifiers that are not part of the common lists */ +TEST(specifiers_assorted) { + const sd_id128_t id = SD_ID128_ALLF; + const uint64_t llu = UINT64_MAX; + const Specifier table[] = { + /* Used in src/partition/repart.c */ + { 'a', specifier_uuid, &id }, + { 'b', specifier_uint64, &llu }, + {} + }; + + for (const Specifier *s = table; s->specifier; s++) { + char spec[3]; + _cleanup_free_ char *resolved = NULL; + int r; + + xsprintf(spec, "%%%c", s->specifier); + + r = specifier_printf(spec, SIZE_MAX, table, NULL, NULL, &resolved); + assert_se(r >= 0); + + log_info("%%%c → %s", s->specifier, resolved); + } +} + +TEST(specifiers_missing_data_ok) { + _cleanup_free_ char *resolved = NULL; + + assert_se(setenv("SYSTEMD_OS_RELEASE", "/dev/null", 1) == 0); + assert_se(specifier_printf("%A-%B-%M-%o-%w-%W", SIZE_MAX, specifier_table, NULL, NULL, &resolved) >= 0); + assert_se(streq(resolved, "-----")); + + assert_se(setenv("SYSTEMD_OS_RELEASE", "/nosuchfileordirectory", 1) == 0); + assert_se(specifier_printf("%A-%B-%M-%o-%w-%W", SIZE_MAX, specifier_table, NULL, NULL, &resolved) == -EUNATCH); + assert_se(streq(resolved, "-----")); + + assert_se(unsetenv("SYSTEMD_OS_RELEASE") == 0); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-stat-util.c b/src/test/test-stat-util.c new file mode 100644 index 0000000..5aca207 --- /dev/null +++ b/src/test/test-stat-util.c @@ -0,0 +1,188 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <fcntl.h> +#include <linux/magic.h> +#include <sched.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "errno-list.h" +#include "fd-util.h" +#include "fs-util.h" +#include "macro.h" +#include "mountpoint-util.h" +#include "namespace-util.h" +#include "path-util.h" +#include "rm-rf.h" +#include "stat-util.h" +#include "tests.h" +#include "tmpfile-util.h" + +TEST(null_or_empty_path) { + assert_se(null_or_empty_path("/dev/null") == 1); + assert_se(null_or_empty_path("/dev/tty") == 1); /* We assume that any character device is "empty", bleh. */ + assert_se(null_or_empty_path("../../../../../../../../../../../../../../../../../../../../dev/null") == 1); + assert_se(null_or_empty_path("/proc/self/exe") == 0); + assert_se(null_or_empty_path("/nosuchfileordir") == -ENOENT); +} + +TEST(null_or_empty_path_with_root) { + assert_se(null_or_empty_path_with_root("/dev/null", NULL) == 1); + assert_se(null_or_empty_path_with_root("/dev/null", "/") == 1); + assert_se(null_or_empty_path_with_root("/dev/null", "/.././../") == 1); + assert_se(null_or_empty_path_with_root("/dev/null", "/.././..") == 1); + assert_se(null_or_empty_path_with_root("../../../../../../../../../../../../../../../../../../../../dev/null", NULL) == 1); + assert_se(null_or_empty_path_with_root("../../../../../../../../../../../../../../../../../../../../dev/null", "/") == 1); + assert_se(null_or_empty_path_with_root("/proc/self/exe", NULL) == 0); + assert_se(null_or_empty_path_with_root("/proc/self/exe", "/") == 0); + assert_se(null_or_empty_path_with_root("/nosuchfileordir", NULL) == -ENOENT); + assert_se(null_or_empty_path_with_root("/nosuchfileordir", "/.././../") == -ENOENT); + assert_se(null_or_empty_path_with_root("/nosuchfileordir", "/.././..") == -ENOENT); + assert_se(null_or_empty_path_with_root("/foobar/barbar/dev/null", "/foobar/barbar") == 1); + assert_se(null_or_empty_path_with_root("/foobar/barbar/dev/null", "/foobar/barbar/") == 1); +} + +TEST(inode_same) { + _cleanup_close_ int fd = -EBADF; + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-files_same.XXXXXX"; + _cleanup_(unlink_tempfilep) char name_alias[] = "/tmp/test-files_same.alias"; + + fd = mkostemp_safe(name); + assert_se(fd >= 0); + assert_se(symlink(name, name_alias) >= 0); + + assert_se(inode_same(name, name, 0)); + assert_se(inode_same(name, name, AT_SYMLINK_NOFOLLOW)); + assert_se(inode_same(name, name_alias, 0)); + assert_se(!inode_same(name, name_alias, AT_SYMLINK_NOFOLLOW)); +} + +TEST(is_symlink) { + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-is_symlink.XXXXXX"; + _cleanup_(unlink_tempfilep) char name_link[] = "/tmp/test-is_symlink.link"; + _cleanup_close_ int fd = -EBADF; + + fd = mkostemp_safe(name); + assert_se(fd >= 0); + assert_se(symlink(name, name_link) >= 0); + + assert_se(is_symlink(name) == 0); + assert_se(is_symlink(name_link) == 1); + assert_se(is_symlink("/a/file/which/does/not/exist/i/guess") < 0); +} + +TEST(path_is_fs_type) { + /* run might not be a mount point in build chroots */ + if (path_is_mount_point("/run", NULL, AT_SYMLINK_FOLLOW) > 0) { + assert_se(path_is_fs_type("/run", TMPFS_MAGIC) > 0); + assert_se(path_is_fs_type("/run", BTRFS_SUPER_MAGIC) == 0); + } + if (path_is_mount_point("/proc", NULL, AT_SYMLINK_FOLLOW) > 0) { + assert_se(path_is_fs_type("/proc", PROC_SUPER_MAGIC) > 0); + assert_se(path_is_fs_type("/proc", BTRFS_SUPER_MAGIC) == 0); + } + assert_se(path_is_fs_type("/i-dont-exist", BTRFS_SUPER_MAGIC) == -ENOENT); +} + +TEST(path_is_temporary_fs) { + int r; + + FOREACH_STRING(s, "/", "/run", "/sys", "/sys/", "/proc", "/i-dont-exist", "/var", "/var/lib") { + r = path_is_temporary_fs(s); + + log_info_errno(r, "path_is_temporary_fs(\"%s\"): %d, %s", + s, r, r < 0 ? errno_to_name(r) : yes_no(r)); + } + + /* run might not be a mount point in build chroots */ + if (path_is_mount_point("/run", NULL, AT_SYMLINK_FOLLOW) > 0) + assert_se(path_is_temporary_fs("/run") > 0); + assert_se(path_is_temporary_fs("/proc") == 0); + assert_se(path_is_temporary_fs("/i-dont-exist") == -ENOENT); +} + +TEST(path_is_read_only_fs) { + int r; + + FOREACH_STRING(s, "/", "/run", "/sys", "/sys/", "/proc", "/i-dont-exist", "/var", "/var/lib") { + r = path_is_read_only_fs(s); + + log_info_errno(r, "path_is_read_only_fs(\"%s\"): %d, %s", + s, r, r < 0 ? errno_to_name(r) : yes_no(r)); + } + + if (path_is_mount_point("/sys", NULL, AT_SYMLINK_FOLLOW) > 0) + assert_se(IN_SET(path_is_read_only_fs("/sys"), 0, 1)); + + assert_se(path_is_read_only_fs("/proc") == 0); + assert_se(path_is_read_only_fs("/i-dont-exist") == -ENOENT); +} + +TEST(fd_is_ns) { + _cleanup_close_ int fd = -EBADF; + + assert_se(fd_is_ns(STDIN_FILENO, CLONE_NEWNET) == 0); + assert_se(fd_is_ns(STDERR_FILENO, CLONE_NEWNET) == 0); + assert_se(fd_is_ns(STDOUT_FILENO, CLONE_NEWNET) == 0); + + fd = open("/proc/self/ns/mnt", O_CLOEXEC|O_RDONLY); + if (fd < 0) { + assert_se(errno == ENOENT); + log_notice("Path %s not found, skipping test", "/proc/self/ns/mnt"); + return; + } + assert_se(fd >= 0); + assert_se(IN_SET(fd_is_ns(fd, CLONE_NEWNET), 0, -EUCLEAN)); + fd = safe_close(fd); + + assert_se((fd = open("/proc/self/ns/ipc", O_CLOEXEC|O_RDONLY)) >= 0); + assert_se(IN_SET(fd_is_ns(fd, CLONE_NEWIPC), 1, -EUCLEAN)); + fd = safe_close(fd); + + assert_se((fd = open("/proc/self/ns/net", O_CLOEXEC|O_RDONLY)) >= 0); + assert_se(IN_SET(fd_is_ns(fd, CLONE_NEWNET), 1, -EUCLEAN)); +} + +TEST(dir_is_empty) { + _cleanup_(rm_rf_physical_and_freep) char *empty_dir = NULL; + _cleanup_free_ char *j = NULL, *jj = NULL, *jjj = NULL; + + assert_se(dir_is_empty_at(AT_FDCWD, "/proc", /* ignore_hidden_or_backup= */ true) == 0); + assert_se(dir_is_empty_at(AT_FDCWD, "/icertainlydontexistdoi", /* ignore_hidden_or_backup= */ true) == -ENOENT); + + assert_se(mkdtemp_malloc("/tmp/emptyXXXXXX", &empty_dir) >= 0); + assert_se(dir_is_empty_at(AT_FDCWD, empty_dir, /* ignore_hidden_or_backup= */ true) > 0); + + j = path_join(empty_dir, "zzz"); + assert_se(j); + assert_se(touch(j) >= 0); + + assert_se(dir_is_empty_at(AT_FDCWD, empty_dir, /* ignore_hidden_or_backup= */ true) == 0); + + jj = path_join(empty_dir, "ppp"); + assert_se(jj); + assert_se(touch(jj) >= 0); + + jjj = path_join(empty_dir, ".qqq"); + assert_se(jjj); + assert_se(touch(jjj) >= 0); + + assert_se(dir_is_empty_at(AT_FDCWD, empty_dir, /* ignore_hidden_or_backup= */ true) == 0); + assert_se(dir_is_empty_at(AT_FDCWD, empty_dir, /* ignore_hidden_or_backup= */ false) == 0); + assert_se(unlink(j) >= 0); + assert_se(dir_is_empty_at(AT_FDCWD, empty_dir, /* ignore_hidden_or_backup= */ true) == 0); + assert_se(dir_is_empty_at(AT_FDCWD, empty_dir, /* ignore_hidden_or_backup= */ false) == 0); + assert_se(unlink(jj) >= 0); + assert_se(dir_is_empty_at(AT_FDCWD, empty_dir, /* ignore_hidden_or_backup= */ true) > 0); + assert_se(dir_is_empty_at(AT_FDCWD, empty_dir, /* ignore_hidden_or_backup= */ false) == 0); + assert_se(unlink(jjj) >= 0); + assert_se(dir_is_empty_at(AT_FDCWD, empty_dir, /* ignore_hidden_or_backup= */ true) > 0); + assert_se(dir_is_empty_at(AT_FDCWD, empty_dir, /* ignore_hidden_or_backup= */ false) > 0); +} + +static int intro(void) { + log_show_color(true); + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/test/test-static-destruct.c b/src/test/test-static-destruct.c new file mode 100644 index 0000000..ef8648f --- /dev/null +++ b/src/test/test-static-destruct.c @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "static-destruct.h" +#include "strv.h" +#include "tests.h" + +static int foo = 0; +static int bar = 0; +static int baz = 0; +static char *memory = NULL; +static char **strings = NULL; +static size_t n_strings = 0; +static int *integers = NULL; +static size_t n_integers = 0; + +static void test_destroy(int *b) { + (*b)++; +} + +static void test_strings_destroy(char **array, size_t n) { + assert_se(n == 3); + assert_se(strv_equal(array, STRV_MAKE("a", "bbb", "ccc"))); + + strv_free(array); +} + +static void test_integers_destroy(int *array, size_t n) { + assert_se(n == 10); + + for (size_t i = 0; i < n; i++) + assert_se(array[i] == (int)(i * i)); + + free(array); +} + +STATIC_DESTRUCTOR_REGISTER(foo, test_destroy); +STATIC_DESTRUCTOR_REGISTER(bar, test_destroy); +STATIC_DESTRUCTOR_REGISTER(bar, test_destroy); +STATIC_DESTRUCTOR_REGISTER(baz, test_destroy); +STATIC_DESTRUCTOR_REGISTER(baz, test_destroy); +STATIC_DESTRUCTOR_REGISTER(baz, test_destroy); +STATIC_DESTRUCTOR_REGISTER(memory, freep); +STATIC_ARRAY_DESTRUCTOR_REGISTER(strings, n_strings, test_strings_destroy); +STATIC_ARRAY_DESTRUCTOR_REGISTER(integers, n_integers, test_integers_destroy); + +TEST(static_destruct) { + assert_se(foo == 0 && bar == 0 && baz == 0); + assert_se(memory = strdup("hallo")); + assert_se(strings = strv_new("a", "bbb", "ccc")); + n_strings = strv_length(strings); + n_integers = 10; + assert_se(integers = new(int, n_integers)); + for (size_t i = 0; i < n_integers; i++) + integers[i] = i * i; + + static_destruct(); + + assert_se(foo == 1 && bar == 2 && baz == 3); + assert_se(!memory); + assert_se(!strings); + assert_se(n_strings == 0); + assert_se(!integers); + assert_se(n_integers == 0); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-strbuf.c b/src/test/test-strbuf.c new file mode 100644 index 0000000..39a7142 --- /dev/null +++ b/src/test/test-strbuf.c @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdlib.h> + +#include "nulstr-util.h" +#include "strbuf.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" + +static ssize_t add_string(struct strbuf *sb, const char *s) { + return strbuf_add_string(sb, s, strlen(s)); +} + +TEST(strbuf) { + _cleanup_(strbuf_freep) struct strbuf *sb = NULL; + _cleanup_strv_free_ char **l = NULL; + ssize_t a, b, c, d, e, f, g, h; + + sb = strbuf_new(); + + a = add_string(sb, "waldo"); + b = add_string(sb, "foo"); + c = add_string(sb, "bar"); + d = add_string(sb, "waldo"); /* duplicate */ + e = add_string(sb, "aldo"); /* duplicate */ + f = add_string(sb, "do"); /* duplicate */ + g = add_string(sb, "waldorf"); /* not a duplicate: matches from tail */ + h = add_string(sb, ""); + + /* check the content of the buffer directly */ + l = strv_parse_nulstr(sb->buf, sb->len); + assert_se(l); + + assert_se(streq(l[0], "")); /* root */ + assert_se(streq(l[1], "waldo")); + assert_se(streq(l[2], "foo")); + assert_se(streq(l[3], "bar")); + assert_se(streq(l[4], "waldorf")); + assert_se(l[5] == NULL); + + assert_se(sb->nodes_count == 5); /* root + 4 non-duplicates */ + assert_se(sb->dedup_count == 4); + assert_se(sb->in_count == 8); + + assert_se(sb->in_len == 29); /* length of all strings added */ + assert_se(sb->dedup_len == 11); /* length of all strings duplicated */ + assert_se(sb->len == 23); /* buffer length: in - dedup + \0 for each node */ + + /* check the returned offsets and the respective content in the buffer */ + assert_se(a == 1); + assert_se(b == 7); + assert_se(c == 11); + assert_se(d == 1); + assert_se(e == 2); + assert_se(f == 4); + assert_se(g == 15); + assert_se(h == 0); + + assert_se(streq(sb->buf + a, "waldo")); + assert_se(streq(sb->buf + b, "foo")); + assert_se(streq(sb->buf + c, "bar")); + assert_se(streq(sb->buf + d, "waldo")); + assert_se(streq(sb->buf + e, "aldo")); + assert_se(streq(sb->buf + f, "do")); + assert_se(streq(sb->buf + g, "waldorf")); + assert_se(streq(sb->buf + h, "")); + + strbuf_complete(sb); + assert_se(sb->root == NULL); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-string-util.c b/src/test/test-string-util.c new file mode 100644 index 0000000..a8fd45d --- /dev/null +++ b/src/test/test-string-util.c @@ -0,0 +1,1327 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <ctype.h> + +#include "alloc-util.h" +#include "locale-util.h" +#include "macro.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" +#include "utf8.h" + +TEST(string_erase) { + char *x; + x = strdupa_safe(""); + assert_se(streq(string_erase(x), "")); + + x = strdupa_safe("1"); + assert_se(streq(string_erase(x), "")); + + x = strdupa_safe("123456789"); + assert_se(streq(string_erase(x), "")); + + assert_se(x[1] == '\0'); + assert_se(x[2] == '\0'); + assert_se(x[3] == '\0'); + assert_se(x[4] == '\0'); + assert_se(x[5] == '\0'); + assert_se(x[6] == '\0'); + assert_se(x[7] == '\0'); + assert_se(x[8] == '\0'); + assert_se(x[9] == '\0'); +} + +static void test_free_and_strndup_one(char **t, const char *src, size_t l, const char *expected, bool change) { + log_debug("%s: \"%s\", \"%s\", %zu (expect \"%s\", %s)", + __func__, strnull(*t), strnull(src), l, strnull(expected), yes_no(change)); + + int r = free_and_strndup(t, src, l); + assert_se(streq_ptr(*t, expected)); + assert_se(r == change); /* check that change occurs only when necessary */ +} + +TEST(free_and_strndup) { + static const struct test_case { + const char *src; + size_t len; + const char *expected; + } cases[] = { + {"abc", 0, ""}, + {"abc", 0, ""}, + {"abc", 1, "a"}, + {"abc", 2, "ab"}, + {"abc", 3, "abc"}, + {"abc", 4, "abc"}, + {"abc", 5, "abc"}, + {"abc", 5, "abc"}, + {"abc", 4, "abc"}, + {"abc", 3, "abc"}, + {"abc", 2, "ab"}, + {"abc", 1, "a"}, + {"abc", 0, ""}, + + {"", 0, ""}, + {"", 1, ""}, + {"", 2, ""}, + {"", 0, ""}, + {"", 1, ""}, + {"", 2, ""}, + {"", 2, ""}, + {"", 1, ""}, + {"", 0, ""}, + + {NULL, 0, NULL}, + + {"foo", 3, "foo"}, + {"foobar", 6, "foobar"}, + }; + + _cleanup_free_ char *t = NULL; + const char *prev_expected = t; + + for (unsigned i = 0; i < ELEMENTSOF(cases); i++) { + test_free_and_strndup_one(&t, + cases[i].src, cases[i].len, cases[i].expected, + !streq_ptr(cases[i].expected, prev_expected)); + prev_expected = t; + } +} + +TEST(ascii_strcasecmp_n) { + assert_se(ascii_strcasecmp_n("", "", 0) == 0); + assert_se(ascii_strcasecmp_n("", "", 1) == 0); + assert_se(ascii_strcasecmp_n("", "a", 1) < 0); + assert_se(ascii_strcasecmp_n("", "a", 2) < 0); + assert_se(ascii_strcasecmp_n("a", "", 1) > 0); + assert_se(ascii_strcasecmp_n("a", "", 2) > 0); + assert_se(ascii_strcasecmp_n("a", "a", 1) == 0); + assert_se(ascii_strcasecmp_n("a", "a", 2) == 0); + assert_se(ascii_strcasecmp_n("a", "b", 1) < 0); + assert_se(ascii_strcasecmp_n("a", "b", 2) < 0); + assert_se(ascii_strcasecmp_n("b", "a", 1) > 0); + assert_se(ascii_strcasecmp_n("b", "a", 2) > 0); + assert_se(ascii_strcasecmp_n("xxxxyxxxx", "xxxxYxxxx", 9) == 0); + assert_se(ascii_strcasecmp_n("xxxxxxxxx", "xxxxyxxxx", 9) < 0); + assert_se(ascii_strcasecmp_n("xxxxXxxxx", "xxxxyxxxx", 9) < 0); + assert_se(ascii_strcasecmp_n("xxxxxxxxx", "xxxxYxxxx", 9) < 0); + assert_se(ascii_strcasecmp_n("xxxxXxxxx", "xxxxYxxxx", 9) < 0); + + assert_se(ascii_strcasecmp_n("xxxxYxxxx", "xxxxYxxxx", 9) == 0); + assert_se(ascii_strcasecmp_n("xxxxyxxxx", "xxxxxxxxx", 9) > 0); + assert_se(ascii_strcasecmp_n("xxxxyxxxx", "xxxxXxxxx", 9) > 0); + assert_se(ascii_strcasecmp_n("xxxxYxxxx", "xxxxxxxxx", 9) > 0); + assert_se(ascii_strcasecmp_n("xxxxYxxxx", "xxxxXxxxx", 9) > 0); +} + +TEST(ascii_strcasecmp_nn) { + assert_se(ascii_strcasecmp_nn("", 0, "", 0) == 0); + assert_se(ascii_strcasecmp_nn("", 0, "", 1) < 0); + assert_se(ascii_strcasecmp_nn("", 1, "", 0) > 0); + assert_se(ascii_strcasecmp_nn("", 1, "", 1) == 0); + + assert_se(ascii_strcasecmp_nn("aaaa", 4, "aaAa", 4) == 0); + assert_se(ascii_strcasecmp_nn("aaa", 3, "aaAa", 4) < 0); + assert_se(ascii_strcasecmp_nn("aaa", 4, "aaAa", 4) < 0); + assert_se(ascii_strcasecmp_nn("aaaa", 4, "aaA", 3) > 0); + assert_se(ascii_strcasecmp_nn("aaaa", 4, "AAA", 4) > 0); + + assert_se(ascii_strcasecmp_nn("aaaa", 4, "bbbb", 4) < 0); + assert_se(ascii_strcasecmp_nn("aaAA", 4, "BBbb", 4) < 0); + assert_se(ascii_strcasecmp_nn("BBbb", 4, "aaaa", 4) > 0); +} + +TEST(cellescape) { + char buf[40]; + + assert_se(streq(cellescape(buf, 1, ""), "")); + assert_se(streq(cellescape(buf, 1, "1"), "")); + assert_se(streq(cellescape(buf, 1, "12"), "")); + + assert_se(streq(cellescape(buf, 2, ""), "")); + assert_se(streq(cellescape(buf, 2, "1"), "1")); + assert_se(streq(cellescape(buf, 2, "12"), ".")); + assert_se(streq(cellescape(buf, 2, "123"), ".")); + + assert_se(streq(cellescape(buf, 3, ""), "")); + assert_se(streq(cellescape(buf, 3, "1"), "1")); + assert_se(streq(cellescape(buf, 3, "12"), "12")); + assert_se(streq(cellescape(buf, 3, "123"), "..")); + assert_se(streq(cellescape(buf, 3, "1234"), "..")); + + assert_se(streq(cellescape(buf, 4, ""), "")); + assert_se(streq(cellescape(buf, 4, "1"), "1")); + assert_se(streq(cellescape(buf, 4, "12"), "12")); + assert_se(streq(cellescape(buf, 4, "123"), "123")); + assert_se(streq(cellescape(buf, 4, "1234"), is_locale_utf8() ? "…" : "...")); + assert_se(streq(cellescape(buf, 4, "12345"), is_locale_utf8() ? "…" : "...")); + + assert_se(streq(cellescape(buf, 5, ""), "")); + assert_se(streq(cellescape(buf, 5, "1"), "1")); + assert_se(streq(cellescape(buf, 5, "12"), "12")); + assert_se(streq(cellescape(buf, 5, "123"), "123")); + assert_se(streq(cellescape(buf, 5, "1234"), "1234")); + assert_se(streq(cellescape(buf, 5, "12345"), is_locale_utf8() ? "1…" : "1...")); + assert_se(streq(cellescape(buf, 5, "123456"), is_locale_utf8() ? "1…" : "1...")); + + assert_se(streq(cellescape(buf, 1, "\020"), "")); + assert_se(streq(cellescape(buf, 2, "\020"), ".")); + assert_se(streq(cellescape(buf, 3, "\020"), "..")); + assert_se(streq(cellescape(buf, 4, "\020"), is_locale_utf8() ? "…" : "...")); + assert_se(streq(cellescape(buf, 5, "\020"), "\\020")); + + assert_se(streq(cellescape(buf, 5, "1234\020"), is_locale_utf8() ? "1…" : "1...")); + assert_se(streq(cellescape(buf, 6, "1234\020"), is_locale_utf8() ? "12…" : "12...")); + assert_se(streq(cellescape(buf, 7, "1234\020"), is_locale_utf8() ? "123…" : "123...")); + assert_se(streq(cellescape(buf, 8, "1234\020"), is_locale_utf8() ? "1234…" : "1234...")); + assert_se(streq(cellescape(buf, 9, "1234\020"), "1234\\020")); + + assert_se(streq(cellescape(buf, 1, "\t\n"), "")); + assert_se(streq(cellescape(buf, 2, "\t\n"), ".")); + assert_se(streq(cellescape(buf, 3, "\t\n"), "..")); + assert_se(streq(cellescape(buf, 4, "\t\n"), is_locale_utf8() ? "…" : "...")); + assert_se(streq(cellescape(buf, 5, "\t\n"), "\\t\\n")); + + assert_se(streq(cellescape(buf, 5, "1234\t\n"), is_locale_utf8() ? "1…" : "1...")); + assert_se(streq(cellescape(buf, 6, "1234\t\n"), is_locale_utf8() ? "12…" : "12...")); + assert_se(streq(cellescape(buf, 7, "1234\t\n"), is_locale_utf8() ? "123…" : "123...")); + assert_se(streq(cellescape(buf, 8, "1234\t\n"), is_locale_utf8() ? "1234…" : "1234...")); + assert_se(streq(cellescape(buf, 9, "1234\t\n"), "1234\\t\\n")); + + assert_se(streq(cellescape(buf, 4, "x\t\020\n"), is_locale_utf8() ? "…" : "...")); + assert_se(streq(cellescape(buf, 5, "x\t\020\n"), is_locale_utf8() ? "x…" : "x...")); + assert_se(streq(cellescape(buf, 6, "x\t\020\n"), is_locale_utf8() ? "x…" : "x...")); + assert_se(streq(cellescape(buf, 7, "x\t\020\n"), is_locale_utf8() ? "x\\t…" : "x\\t...")); + assert_se(streq(cellescape(buf, 8, "x\t\020\n"), is_locale_utf8() ? "x\\t…" : "x\\t...")); + assert_se(streq(cellescape(buf, 9, "x\t\020\n"), is_locale_utf8() ? "x\\t…" : "x\\t...")); + assert_se(streq(cellescape(buf, 10, "x\t\020\n"), "x\\t\\020\\n")); + + assert_se(streq(cellescape(buf, 6, "1\011"), "1\\t")); + assert_se(streq(cellescape(buf, 6, "1\020"), "1\\020")); + assert_se(streq(cellescape(buf, 6, "1\020x"), is_locale_utf8() ? "1…" : "1...")); + + assert_se(streq(cellescape(buf, 40, "1\020"), "1\\020")); + assert_se(streq(cellescape(buf, 40, "1\020x"), "1\\020x")); + + assert_se(streq(cellescape(buf, 40, "\a\b\f\n\r\t\v\\\"'"), "\\a\\b\\f\\n\\r\\t\\v\\\\\\\"\\'")); + assert_se(streq(cellescape(buf, 6, "\a\b\f\n\r\t\v\\\"'"), is_locale_utf8() ? "\\a…" : "\\a...")); + assert_se(streq(cellescape(buf, 7, "\a\b\f\n\r\t\v\\\"'"), is_locale_utf8() ? "\\a…" : "\\a...")); + assert_se(streq(cellescape(buf, 8, "\a\b\f\n\r\t\v\\\"'"), is_locale_utf8() ? "\\a\\b…" : "\\a\\b...")); + + assert_se(streq(cellescape(buf, sizeof buf, "1\020"), "1\\020")); + assert_se(streq(cellescape(buf, sizeof buf, "1\020x"), "1\\020x")); +} + +TEST(streq_ptr) { + assert_se(streq_ptr(NULL, NULL)); + assert_se(!streq_ptr("abc", "cdef")); +} + +TEST(strstrip) { + char *ret, input[] = " hello, waldo. "; + + ret = strstrip(input); + assert_se(streq(ret, "hello, waldo.")); +} + +TEST(strextend) { + _cleanup_free_ char *str = NULL; + + assert_se(strextend(&str, NULL)); + assert_se(streq_ptr(str, "")); + assert_se(strextend(&str, "", "0", "", "", "123")); + assert_se(streq_ptr(str, "0123")); + assert_se(strextend(&str, "456", "78", "9")); + assert_se(streq_ptr(str, "0123456789")); +} + +TEST(strextend_with_separator) { + _cleanup_free_ char *str = NULL; + + assert_se(strextend_with_separator(&str, NULL, NULL)); + assert_se(streq_ptr(str, "")); + str = mfree(str); + + assert_se(strextend_with_separator(&str, "...", NULL)); + assert_se(streq_ptr(str, "")); + assert_se(strextend_with_separator(&str, "...", NULL)); + assert_se(streq_ptr(str, "")); + str = mfree(str); + + assert_se(strextend_with_separator(&str, "xyz", "a", "bb", "ccc")); + assert_se(streq_ptr(str, "axyzbbxyzccc")); + str = mfree(str); + + assert_se(strextend_with_separator(&str, ",", "start", "", "1", "234")); + assert_se(streq_ptr(str, "start,,1,234")); + assert_se(strextend_with_separator(&str, ";", "more", "5", "678")); + assert_se(streq_ptr(str, "start,,1,234;more;5;678")); +} + +TEST(strrep) { + _cleanup_free_ char *one = NULL, *three = NULL, *zero = NULL; + char *onea, *threea; + + one = strrep("waldo", 1); + three = strrep("waldo", 3); + zero = strrep("waldo", 0); + + assert_se(streq(one, "waldo")); + assert_se(streq(three, "waldowaldowaldo")); + assert_se(streq(zero, "")); + + onea = strrepa("waldo", 1); + threea = strrepa("waldo", 3); + + assert_se(streq(onea, "waldo")); + assert_se(streq(threea, "waldowaldowaldo")); +} + +TEST(string_has_cc) { + assert_se(string_has_cc("abc\1", NULL)); + assert_se(string_has_cc("abc\x7f", NULL)); + assert_se(string_has_cc("abc\x7f", NULL)); + assert_se(string_has_cc("abc\t\x7f", "\t")); + assert_se(string_has_cc("abc\t\x7f", "\t")); + assert_se(string_has_cc("\x7f", "\t")); + assert_se(string_has_cc("\x7f", "\t\a")); + + assert_se(!string_has_cc("abc\t\t", "\t")); + assert_se(!string_has_cc("abc\t\t\a", "\t\a")); + assert_se(!string_has_cc("a\ab\tc", "\t\a")); +} + +TEST(ascii_strlower) { + char a[] = "AabBcC Jk Ii Od LKJJJ kkd LK"; + assert_se(streq(ascii_strlower(a), "aabbcc jk ii od lkjjj kkd lk")); +} + +TEST(strshorten) { + char s[] = "foobar"; + + assert_se(strlen(strshorten(s, 6)) == 6); + assert_se(strlen(strshorten(s, 12)) == 6); + assert_se(strlen(strshorten(s, 2)) == 2); + assert_se(strlen(strshorten(s, 0)) == 0); +} + +TEST(strjoina) { + char *actual; + + actual = strjoina("", "foo", "bar"); + assert_se(streq(actual, "foobar")); + + actual = strjoina("foo", "bar", "baz"); + assert_se(streq(actual, "foobarbaz")); + + actual = strjoina("foo", "", "bar", "baz"); + assert_se(streq(actual, "foobarbaz")); + + actual = strjoina("foo"); + assert_se(streq(actual, "foo")); + + actual = strjoina(NULL); + assert_se(streq(actual, "")); + + actual = strjoina(NULL, "foo"); + assert_se(streq(actual, "")); + + actual = strjoina("foo", NULL, "bar"); + assert_se(streq(actual, "foo")); + + actual = strjoina("/sys/fs/cgroup/", "dn", "/a/b/c", "/cgroup.procs"); + assert_se(streq(actual, "/sys/fs/cgroup/dn/a/b/c/cgroup.procs")); + + actual = strjoina("/sys/fs/cgroup/", "dn", NULL, NULL); + assert_se(streq(actual, "/sys/fs/cgroup/dn")); +} + +TEST(strjoin) { + char *actual; + + actual = strjoin("", "foo", "bar"); + assert_se(streq(actual, "foobar")); + mfree(actual); + + actual = strjoin("foo", "bar", "baz"); + assert_se(streq(actual, "foobarbaz")); + mfree(actual); + + actual = strjoin("foo", "", "bar", "baz"); + assert_se(streq(actual, "foobarbaz")); + mfree(actual); + + actual = strjoin("foo", NULL); + assert_se(streq(actual, "foo")); + mfree(actual); + + actual = strjoin(NULL, NULL); + assert_se(streq(actual, "")); + mfree(actual); + + actual = strjoin(NULL, "foo"); + assert_se(streq(actual, "")); + mfree(actual); + + actual = strjoin("foo", NULL, "bar"); + assert_se(streq(actual, "foo")); + mfree(actual); +} + +TEST(strcmp_ptr) { + assert_se(strcmp_ptr(NULL, NULL) == 0); + assert_se(strcmp_ptr("", NULL) > 0); + assert_se(strcmp_ptr("foo", NULL) > 0); + assert_se(strcmp_ptr(NULL, "") < 0); + assert_se(strcmp_ptr(NULL, "bar") < 0); + assert_se(strcmp_ptr("foo", "bar") > 0); + assert_se(strcmp_ptr("bar", "baz") < 0); + assert_se(strcmp_ptr("foo", "foo") == 0); + assert_se(strcmp_ptr("", "") == 0); +} + +TEST(foreach_word) { + const char *test = "test abc d\te f "; + const char * const expected[] = { + "test", + "abc", + "d", + "e", + "f", + }; + + size_t i = 0; + int r; + for (const char *p = test;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, NULL, 0); + if (r == 0) { + assert_se(i == ELEMENTSOF(expected)); + break; + } + assert_se(r > 0); + + assert_se(streq(expected[i++], word)); + } +} + +static void check(const char *test, char** expected, bool trailing) { + size_t i = 0; + int r; + + printf("<<<%s>>>\n", test); + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&test, &word, NULL, EXTRACT_UNQUOTE); + if (r == 0) { + assert_se(!trailing); + break; + } else if (r < 0) { + assert_se(trailing); + break; + } + + assert_se(streq(word, expected[i++])); + printf("<%s>\n", word); + } + assert_se(expected[i] == NULL); +} + +TEST(foreach_word_quoted) { + check("test a b c 'd' e '' '' hhh '' '' \"a b c\"", + STRV_MAKE("test", + "a", + "b", + "c", + "d", + "e", + "", + "", + "hhh", + "", + "", + "a b c"), + false); + + check("test \"xxx", + STRV_MAKE("test"), + true); + + check("test\\", + STRV_MAKE_EMPTY, + true); +} + +TEST(endswith) { + assert_se(endswith("foobar", "bar")); + assert_se(endswith("foobar", "")); + assert_se(endswith("foobar", "foobar")); + assert_se(endswith("", "")); + + assert_se(!endswith("foobar", "foo")); + assert_se(!endswith("foobar", "foobarfoofoo")); +} + +TEST(endswith_no_case) { + assert_se(endswith_no_case("fooBAR", "bar")); + assert_se(endswith_no_case("foobar", "")); + assert_se(endswith_no_case("foobar", "FOOBAR")); + assert_se(endswith_no_case("", "")); + + assert_se(!endswith_no_case("foobar", "FOO")); + assert_se(!endswith_no_case("foobar", "FOOBARFOOFOO")); +} + +TEST(delete_chars) { + char *s, input[] = " hello, waldo. abc"; + + s = delete_chars(input, WHITESPACE); + assert_se(streq(s, "hello,waldo.abc")); + assert_se(s == input); +} + +TEST(delete_trailing_chars) { + char *s, + input1[] = " \n \r k \n \r ", + input2[] = "kkkkthiskkkiskkkaktestkkk", + input3[] = "abcdef"; + + s = delete_trailing_chars(input1, WHITESPACE); + assert_se(streq(s, " \n \r k")); + assert_se(s == input1); + + s = delete_trailing_chars(input2, "kt"); + assert_se(streq(s, "kkkkthiskkkiskkkaktes")); + assert_se(s == input2); + + s = delete_trailing_chars(input3, WHITESPACE); + assert_se(streq(s, "abcdef")); + assert_se(s == input3); + + s = delete_trailing_chars(input3, "fe"); + assert_se(streq(s, "abcd")); + assert_se(s == input3); +} + +TEST(delete_trailing_slashes) { + char s1[] = "foobar//", + s2[] = "foobar/", + s3[] = "foobar", + s4[] = ""; + + assert_se(streq(delete_trailing_chars(s1, "_"), "foobar//")); + assert_se(streq(delete_trailing_chars(s1, "/"), "foobar")); + assert_se(streq(delete_trailing_chars(s2, "/"), "foobar")); + assert_se(streq(delete_trailing_chars(s3, "/"), "foobar")); + assert_se(streq(delete_trailing_chars(s4, "/"), "")); +} + +TEST(skip_leading_chars) { + char input1[] = " \n \r k \n \r ", + input2[] = "kkkkthiskkkiskkkaktestkkk", + input3[] = "abcdef"; + + assert_se(streq(skip_leading_chars(input1, WHITESPACE), "k \n \r ")); + assert_se(streq(skip_leading_chars(input2, "k"), "thiskkkiskkkaktestkkk")); + assert_se(streq(skip_leading_chars(input2, "tk"), "hiskkkiskkkaktestkkk")); + assert_se(streq(skip_leading_chars(input3, WHITESPACE), "abcdef")); + assert_se(streq(skip_leading_chars(input3, "bcaef"), "def")); +} + +TEST(in_charset) { + assert_se(in_charset("dddaaabbbcccc", "abcd")); + assert_se(!in_charset("dddaaabbbcccc", "abc f")); +} + +TEST(split_pair) { + _cleanup_free_ char *a = NULL, *b = NULL; + + assert_se(split_pair("", "", &a, &b) == -EINVAL); + assert_se(split_pair("foo=bar", "", &a, &b) == -EINVAL); + assert_se(split_pair("", "=", &a, &b) == -EINVAL); + assert_se(split_pair("foo=bar", "=", &a, &b) >= 0); + assert_se(streq(a, "foo")); + assert_se(streq(b, "bar")); + free(a); + free(b); + assert_se(split_pair("==", "==", &a, &b) >= 0); + assert_se(streq(a, "")); + assert_se(streq(b, "")); + free(a); + free(b); + + assert_se(split_pair("===", "==", &a, &b) >= 0); + assert_se(streq(a, "")); + assert_se(streq(b, "=")); +} + +TEST(first_word) { + assert_se(first_word("Hello", "")); + assert_se(first_word("Hello", "Hello")); + assert_se(first_word("Hello world", "Hello")); + assert_se(first_word("Hello\tworld", "Hello")); + assert_se(first_word("Hello\nworld", "Hello")); + assert_se(first_word("Hello\rworld", "Hello")); + assert_se(first_word("Hello ", "Hello")); + + assert_se(!first_word("Hello", "Hellooo")); + assert_se(!first_word("Hello", "xxxxx")); + assert_se(!first_word("Hellooo", "Hello")); +} + +TEST(strlen_ptr) { + assert_se(strlen_ptr("foo") == 3); + assert_se(strlen_ptr("") == 0); + assert_se(strlen_ptr(NULL) == 0); +} + +TEST(memory_startswith) { + assert_se(streq(memory_startswith("", 0, ""), "")); + assert_se(streq(memory_startswith("", 1, ""), "")); + assert_se(streq(memory_startswith("x", 2, ""), "x")); + assert_se(!memory_startswith("", 1, "x")); + assert_se(!memory_startswith("", 1, "xxxxxxxx")); + assert_se(streq(memory_startswith("xxx", 4, "x"), "xx")); + assert_se(streq(memory_startswith("xxx", 4, "xx"), "x")); + assert_se(streq(memory_startswith("xxx", 4, "xxx"), "")); + assert_se(!memory_startswith("xxx", 4, "xxxx")); +} + +TEST(memory_startswith_no_case) { + assert_se(streq(memory_startswith_no_case("", 0, ""), "")); + assert_se(streq(memory_startswith_no_case("", 1, ""), "")); + assert_se(streq(memory_startswith_no_case("x", 2, ""), "x")); + assert_se(streq(memory_startswith_no_case("X", 2, ""), "X")); + assert_se(!memory_startswith_no_case("", 1, "X")); + assert_se(!memory_startswith_no_case("", 1, "xxxxXXXX")); + assert_se(streq(memory_startswith_no_case("xxx", 4, "X"), "xx")); + assert_se(streq(memory_startswith_no_case("XXX", 4, "x"), "XX")); + assert_se(streq(memory_startswith_no_case("XXX", 4, "X"), "XX")); + assert_se(streq(memory_startswith_no_case("xxx", 4, "XX"), "x")); + assert_se(streq(memory_startswith_no_case("XXX", 4, "xx"), "X")); + assert_se(streq(memory_startswith_no_case("XXX", 4, "XX"), "X")); + assert_se(streq(memory_startswith_no_case("xxx", 4, "XXX"), "")); + assert_se(streq(memory_startswith_no_case("XXX", 4, "xxx"), "")); + assert_se(streq(memory_startswith_no_case("XXX", 4, "XXX"), "")); + + assert_se(memory_startswith_no_case((char[2]){'x', 'x'}, 2, "xx")); + assert_se(memory_startswith_no_case((char[2]){'x', 'X'}, 2, "xX")); + assert_se(memory_startswith_no_case((char[2]){'X', 'x'}, 2, "Xx")); + assert_se(memory_startswith_no_case((char[2]){'X', 'X'}, 2, "XX")); +} + +static void test_string_truncate_lines_one(const char *input, size_t n_lines, const char *output, bool truncation) { + _cleanup_free_ char *b = NULL; + int k; + + assert_se((k = string_truncate_lines(input, n_lines, &b)) >= 0); + assert_se(streq(b, output)); + assert_se(!!k == truncation); +} + +TEST(string_truncate_lines) { + test_string_truncate_lines_one("", 0, "", false); + test_string_truncate_lines_one("", 1, "", false); + test_string_truncate_lines_one("", 2, "", false); + test_string_truncate_lines_one("", 3, "", false); + + test_string_truncate_lines_one("x", 0, "", true); + test_string_truncate_lines_one("x", 1, "x", false); + test_string_truncate_lines_one("x", 2, "x", false); + test_string_truncate_lines_one("x", 3, "x", false); + + test_string_truncate_lines_one("x\n", 0, "", true); + test_string_truncate_lines_one("x\n", 1, "x", false); + test_string_truncate_lines_one("x\n", 2, "x", false); + test_string_truncate_lines_one("x\n", 3, "x", false); + + test_string_truncate_lines_one("x\ny", 0, "", true); + test_string_truncate_lines_one("x\ny", 1, "x", true); + test_string_truncate_lines_one("x\ny", 2, "x\ny", false); + test_string_truncate_lines_one("x\ny", 3, "x\ny", false); + + test_string_truncate_lines_one("x\ny\n", 0, "", true); + test_string_truncate_lines_one("x\ny\n", 1, "x", true); + test_string_truncate_lines_one("x\ny\n", 2, "x\ny", false); + test_string_truncate_lines_one("x\ny\n", 3, "x\ny", false); + + test_string_truncate_lines_one("x\ny\nz", 0, "", true); + test_string_truncate_lines_one("x\ny\nz", 1, "x", true); + test_string_truncate_lines_one("x\ny\nz", 2, "x\ny", true); + test_string_truncate_lines_one("x\ny\nz", 3, "x\ny\nz", false); + + test_string_truncate_lines_one("x\ny\nz\n", 0, "", true); + test_string_truncate_lines_one("x\ny\nz\n", 1, "x", true); + test_string_truncate_lines_one("x\ny\nz\n", 2, "x\ny", true); + test_string_truncate_lines_one("x\ny\nz\n", 3, "x\ny\nz", false); + + test_string_truncate_lines_one("\n", 0, "", false); + test_string_truncate_lines_one("\n", 1, "", false); + test_string_truncate_lines_one("\n", 2, "", false); + test_string_truncate_lines_one("\n", 3, "", false); + + test_string_truncate_lines_one("\n\n", 0, "", false); + test_string_truncate_lines_one("\n\n", 1, "", false); + test_string_truncate_lines_one("\n\n", 2, "", false); + test_string_truncate_lines_one("\n\n", 3, "", false); + + test_string_truncate_lines_one("\n\n\n", 0, "", false); + test_string_truncate_lines_one("\n\n\n", 1, "", false); + test_string_truncate_lines_one("\n\n\n", 2, "", false); + test_string_truncate_lines_one("\n\n\n", 3, "", false); + + test_string_truncate_lines_one("\nx\n\n", 0, "", true); + test_string_truncate_lines_one("\nx\n\n", 1, "", true); + test_string_truncate_lines_one("\nx\n\n", 2, "\nx", false); + test_string_truncate_lines_one("\nx\n\n", 3, "\nx", false); + + test_string_truncate_lines_one("\n\nx\n", 0, "", true); + test_string_truncate_lines_one("\n\nx\n", 1, "", true); + test_string_truncate_lines_one("\n\nx\n", 2, "", true); + test_string_truncate_lines_one("\n\nx\n", 3, "\n\nx", false); +} + +static void test_string_extract_lines_one(const char *input, size_t i, const char *output, bool more) { + _cleanup_free_ char *b = NULL; + int k; + + assert_se((k = string_extract_line(input, i, &b)) >= 0); + assert_se(streq(b ?: input, output)); + assert_se(!!k == more); +} + +TEST(string_extract_line) { + test_string_extract_lines_one("", 0, "", false); + test_string_extract_lines_one("", 1, "", false); + test_string_extract_lines_one("", 2, "", false); + test_string_extract_lines_one("", 3, "", false); + + test_string_extract_lines_one("x", 0, "x", false); + test_string_extract_lines_one("x", 1, "", false); + test_string_extract_lines_one("x", 2, "", false); + test_string_extract_lines_one("x", 3, "", false); + + test_string_extract_lines_one("x\n", 0, "x", false); + test_string_extract_lines_one("x\n", 1, "", false); + test_string_extract_lines_one("x\n", 2, "", false); + test_string_extract_lines_one("x\n", 3, "", false); + + test_string_extract_lines_one("x\ny", 0, "x", true); + test_string_extract_lines_one("x\ny", 1, "y", false); + test_string_extract_lines_one("x\ny", 2, "", false); + test_string_extract_lines_one("x\ny", 3, "", false); + + test_string_extract_lines_one("x\ny\n", 0, "x", true); + test_string_extract_lines_one("x\ny\n", 1, "y", false); + test_string_extract_lines_one("x\ny\n", 2, "", false); + test_string_extract_lines_one("x\ny\n", 3, "", false); + + test_string_extract_lines_one("x\ny\nz", 0, "x", true); + test_string_extract_lines_one("x\ny\nz", 1, "y", true); + test_string_extract_lines_one("x\ny\nz", 2, "z", false); + test_string_extract_lines_one("x\ny\nz", 3, "", false); + + test_string_extract_lines_one("\n", 0, "", false); + test_string_extract_lines_one("\n", 1, "", false); + test_string_extract_lines_one("\n", 2, "", false); + test_string_extract_lines_one("\n", 3, "", false); + + test_string_extract_lines_one("\n\n", 0, "", true); + test_string_extract_lines_one("\n\n", 1, "", false); + test_string_extract_lines_one("\n\n", 2, "", false); + test_string_extract_lines_one("\n\n", 3, "", false); + + test_string_extract_lines_one("\n\n\n", 0, "", true); + test_string_extract_lines_one("\n\n\n", 1, "", true); + test_string_extract_lines_one("\n\n\n", 2, "", false); + test_string_extract_lines_one("\n\n\n", 3, "", false); + + test_string_extract_lines_one("\n\n\n\n", 0, "", true); + test_string_extract_lines_one("\n\n\n\n", 1, "", true); + test_string_extract_lines_one("\n\n\n\n", 2, "", true); + test_string_extract_lines_one("\n\n\n\n", 3, "", false); + + test_string_extract_lines_one("\nx\n\n\n", 0, "", true); + test_string_extract_lines_one("\nx\n\n\n", 1, "x", true); + test_string_extract_lines_one("\nx\n\n\n", 2, "", true); + test_string_extract_lines_one("\nx\n\n\n", 3, "", false); + + test_string_extract_lines_one("\n\nx\n\n", 0, "", true); + test_string_extract_lines_one("\n\nx\n\n", 1, "", true); + test_string_extract_lines_one("\n\nx\n\n", 2, "x", true); + test_string_extract_lines_one("\n\nx\n\n", 3, "", false); + + test_string_extract_lines_one("\n\n\nx\n", 0, "", true); + test_string_extract_lines_one("\n\n\nx\n", 1, "", true); + test_string_extract_lines_one("\n\n\nx\n", 2, "", true); + test_string_extract_lines_one("\n\n\nx\n", 3, "x", false); +} + +TEST(string_contains_word_strv) { + const char *w; + + assert_se(string_contains_word_strv("a b cc", NULL, STRV_MAKE("a", "b"), NULL)); + + assert_se(string_contains_word_strv("a b cc", NULL, STRV_MAKE("a", "b"), &w)); + assert_se(streq(w, "a")); + + assert_se(!string_contains_word_strv("a b cc", NULL, STRV_MAKE("d"), &w)); + assert_se(w == NULL); + + assert_se(string_contains_word_strv("a b cc", NULL, STRV_MAKE("b", "a"), &w)); + assert_se(streq(w, "a")); + + assert_se(string_contains_word_strv("b a b cc", NULL, STRV_MAKE("b", "a", "b"), &w)); + assert_se(streq(w, "b")); + + assert_se(string_contains_word_strv("a b cc", NULL, STRV_MAKE("b", ""), &w)); + assert_se(streq(w, "b")); + + assert_se(!string_contains_word_strv("a b cc", NULL, STRV_MAKE(""), &w)); + assert_se(w == NULL); + + assert_se(string_contains_word_strv("a b cc", " ", STRV_MAKE(""), &w)); + assert_se(streq(w, "")); +} + +TEST(string_contains_word) { + assert_se( string_contains_word("a b cc", NULL, "a")); + assert_se( string_contains_word("a b cc", NULL, "b")); + assert_se(!string_contains_word("a b cc", NULL, "c")); + assert_se( string_contains_word("a b cc", NULL, "cc")); + assert_se(!string_contains_word("a b cc", NULL, "d")); + assert_se(!string_contains_word("a b cc", NULL, "a b")); + assert_se(!string_contains_word("a b cc", NULL, "a b c")); + assert_se(!string_contains_word("a b cc", NULL, "b c")); + assert_se(!string_contains_word("a b cc", NULL, "b cc")); + assert_se(!string_contains_word("a b cc", NULL, "a ")); + assert_se(!string_contains_word("a b cc", NULL, " b ")); + assert_se(!string_contains_word("a b cc", NULL, " cc")); + + assert_se( string_contains_word(" a b\t\tcc", NULL, "a")); + assert_se( string_contains_word(" a b\t\tcc", NULL, "b")); + assert_se(!string_contains_word(" a b\t\tcc", NULL, "c")); + assert_se( string_contains_word(" a b\t\tcc", NULL, "cc")); + assert_se(!string_contains_word(" a b\t\tcc", NULL, "d")); + assert_se(!string_contains_word(" a b\t\tcc", NULL, "a b")); + assert_se(!string_contains_word(" a b\t\tcc", NULL, "a b\t\tc")); + assert_se(!string_contains_word(" a b\t\tcc", NULL, "b\t\tc")); + assert_se(!string_contains_word(" a b\t\tcc", NULL, "b\t\tcc")); + assert_se(!string_contains_word(" a b\t\tcc", NULL, "a ")); + assert_se(!string_contains_word(" a b\t\tcc", NULL, " b ")); + assert_se(!string_contains_word(" a b\t\tcc", NULL, " cc")); + + assert_se(!string_contains_word(" a b\t\tcc", NULL, "")); + assert_se(!string_contains_word(" a b\t\tcc", NULL, " ")); + assert_se(!string_contains_word(" a b\t\tcc", NULL, " ")); + assert_se( string_contains_word(" a b\t\tcc", " ", "")); + assert_se( string_contains_word(" a b\t\tcc", "\t", "")); + assert_se( string_contains_word(" a b\t\tcc", WHITESPACE, "")); + + assert_se( string_contains_word("a:b:cc", ":#", "a")); + assert_se( string_contains_word("a:b:cc", ":#", "b")); + assert_se(!string_contains_word("a:b:cc", ":#", "c")); + assert_se( string_contains_word("a:b:cc", ":#", "cc")); + assert_se(!string_contains_word("a:b:cc", ":#", "d")); + assert_se(!string_contains_word("a:b:cc", ":#", "a:b")); + assert_se(!string_contains_word("a:b:cc", ":#", "a:b:c")); + assert_se(!string_contains_word("a:b:cc", ":#", "b:c")); + assert_se(!string_contains_word("a#b#cc", ":#", "b:cc")); + assert_se( string_contains_word("a#b#cc", ":#", "b")); + assert_se( string_contains_word("a#b#cc", ":#", "cc")); + assert_se(!string_contains_word("a:b:cc", ":#", "a:")); + assert_se(!string_contains_word("a:b cc", ":#", "b")); + assert_se( string_contains_word("a:b cc", ":#", "b cc")); + assert_se(!string_contains_word("a:b:cc", ":#", ":cc")); +} + +static void test_strverscmp_improved_one(const char* a, const char *b, int expected) { + int r = strverscmp_improved(a, b); + + log_info("'%s' %s '%s'%s", + strnull(a), + comparison_operator(r), + strnull(b), + r == expected ? "" : " !!!!!!!!!!!!!"); + assert_se(r == expected); +} + +static void test_strverscmp_improved_newer(const char *older, const char *newer) { + test_strverscmp_improved_one(older, newer, -1); + + assert_se(strverscmp_improved(older, older) == 0); + assert_se(strverscmp_improved(older, newer) < 0); + assert_se(strverscmp_improved(newer, older) > 0); + assert_se(strverscmp_improved(newer, newer) == 0); +} + +TEST(strverscmp_improved) { + static const char * const versions[] = { + "~1", + "", + "ab", + "abb", + "abc", + "0001", + "002", + "12", + "122", + "122.9", + "123~rc1", + "123", + "123-a", + "123-a.1", + "123-a1", + "123-a1.1", + "123-3", + "123-3.1", + "123^patch1", + "123^1", + "123.a-1", + "123.1-1", + "123a-1", + "124", + NULL, + }; + + STRV_FOREACH(p, versions) + STRV_FOREACH(q, p + 1) + test_strverscmp_improved_newer(*p, *q); + + test_strverscmp_improved_newer("123.45-67.88", "123.45-67.89"); + test_strverscmp_improved_newer("123.45-67.89", "123.45-67.89a"); + test_strverscmp_improved_newer("123.45-67.ab", "123.45-67.89"); + test_strverscmp_improved_newer("123.45-67.9", "123.45-67.89"); + test_strverscmp_improved_newer("123.45-67", "123.45-67.89"); + test_strverscmp_improved_newer("123.45-66.89", "123.45-67.89"); + test_strverscmp_improved_newer("123.45-9.99", "123.45-67.89"); + test_strverscmp_improved_newer("123.42-99.99", "123.45-67.89"); + test_strverscmp_improved_newer("123-99.99", "123.45-67.89"); + + /* '~' : pre-releases */ + test_strverscmp_improved_newer("123~rc1-99.99", "123.45-67.89"); + test_strverscmp_improved_newer("123~rc1-99.99", "123-45.67.89"); + test_strverscmp_improved_newer("123~rc1-99.99", "123~rc2-67.89"); + test_strverscmp_improved_newer("123~rc1-99.99", "123^aa2-67.89"); + test_strverscmp_improved_newer("123~rc1-99.99", "123aa2-67.89"); + + /* '-' : separator between version and release. */ + test_strverscmp_improved_newer("123-99.99", "123.45-67.89"); + test_strverscmp_improved_newer("123-99.99", "123^aa2-67.89"); + test_strverscmp_improved_newer("123-99.99", "123aa2-67.89"); + + /* '^' : patch releases */ + test_strverscmp_improved_newer("123^45-67.89", "123.45-67.89"); + test_strverscmp_improved_newer("123^aa1-99.99", "123^aa2-67.89"); + test_strverscmp_improved_newer("123^aa2-67.89", "123aa2-67.89"); + + /* '.' : point release */ + test_strverscmp_improved_newer("123.aa2-67.89", "123aa2-67.89"); + test_strverscmp_improved_newer("123.aa2-67.89", "123.ab2-67.89"); + + /* invalid characters */ + assert_se(strverscmp_improved("123_aa2-67.89", "123aa+2-67.89") == 0); + + /* some corner cases */ + assert_se(strverscmp_improved("123.", "123") > 0); /* One more version segment */ + assert_se(strverscmp_improved("12_3", "123") < 0); /* 12 < 123 */ + assert_se(strverscmp_improved("12_3", "12") > 0); /* 3 > '' */ + assert_se(strverscmp_improved("12_3", "12.3") > 0); /* 3 > '' */ + assert_se(strverscmp_improved("123.0", "123") > 0); /* 0 > '' */ + assert_se(strverscmp_improved("123_0", "123") > 0); /* 0 > '' */ + assert_se(strverscmp_improved("123..0", "123.0") < 0); /* '' < 0 */ + + /* empty strings or strings with ignored characters only */ + assert_se(strverscmp_improved("", NULL) == 0); + assert_se(strverscmp_improved(NULL, "") == 0); + assert_se(strverscmp_improved("0_", "0") == 0); + assert_se(strverscmp_improved("_0_", "0") == 0); + assert_se(strverscmp_improved("_0", "0") == 0); + assert_se(strverscmp_improved("0", "0___") == 0); + assert_se(strverscmp_improved("", "_") == 0); + assert_se(strverscmp_improved("_", "") == 0); + assert_se(strverscmp_improved("_", "_") == 0); + assert_se(strverscmp_improved("", "~") > 0); + assert_se(strverscmp_improved("~", "") < 0); + assert_se(strverscmp_improved("~", "~") == 0); + + /* non-ASCII digits */ + (void) setlocale(LC_NUMERIC, "ar_YE.utf8"); + assert_se(strverscmp_improved("1٠١٢٣٤٥٦٧٨٩", "1") == 0); + + (void) setlocale(LC_NUMERIC, "th_TH.utf8"); + assert_se(strverscmp_improved("1๐๑๒๓๔๕๖๗๘๙", "1") == 0); +} + +#define RPMVERCMP(a, b, c) \ + test_strverscmp_improved_one(STRINGIFY(a), STRINGIFY(b), (c)) + +TEST(strverscmp_improved_rpm) { + /* Tests copied from rmp's rpmio test suite, under the LGPL license: + * https://github.com/rpm-software-management/rpm/blob/master/tests/rpmvercmp.at. + * The original form is retained for easy comparisons and updates. + */ + + RPMVERCMP(1.0, 1.0, 0); + RPMVERCMP(1.0, 2.0, -1); + RPMVERCMP(2.0, 1.0, 1); + + RPMVERCMP(2.0.1, 2.0.1, 0); + RPMVERCMP(2.0, 2.0.1, -1); + RPMVERCMP(2.0.1, 2.0, 1); + + RPMVERCMP(2.0.1a, 2.0.1a, 0); + RPMVERCMP(2.0.1a, 2.0.1, 1); + RPMVERCMP(2.0.1, 2.0.1a, -1); + + RPMVERCMP(5.5p1, 5.5p1, 0); + RPMVERCMP(5.5p1, 5.5p2, -1); + RPMVERCMP(5.5p2, 5.5p1, 1); + + RPMVERCMP(5.5p10, 5.5p10, 0); + RPMVERCMP(5.5p1, 5.5p10, -1); + RPMVERCMP(5.5p10, 5.5p1, 1); + + RPMVERCMP(10xyz, 10.1xyz, 1); /* Note: this is reversed from rpm's vercmp */ + RPMVERCMP(10.1xyz, 10xyz, -1); /* Note: this is reversed from rpm's vercmp */ + + RPMVERCMP(xyz10, xyz10, 0); + RPMVERCMP(xyz10, xyz10.1, -1); + RPMVERCMP(xyz10.1, xyz10, 1); + + RPMVERCMP(xyz.4, xyz.4, 0); + RPMVERCMP(xyz.4, 8, -1); + RPMVERCMP(8, xyz.4, 1); + RPMVERCMP(xyz.4, 2, -1); + RPMVERCMP(2, xyz.4, 1); + + RPMVERCMP(5.5p2, 5.6p1, -1); + RPMVERCMP(5.6p1, 5.5p2, 1); + + RPMVERCMP(5.6p1, 6.5p1, -1); + RPMVERCMP(6.5p1, 5.6p1, 1); + + RPMVERCMP(6.0.rc1, 6.0, 1); + RPMVERCMP(6.0, 6.0.rc1, -1); + + RPMVERCMP(10b2, 10a1, 1); + RPMVERCMP(10a2, 10b2, -1); + + RPMVERCMP(1.0aa, 1.0aa, 0); + RPMVERCMP(1.0a, 1.0aa, -1); + RPMVERCMP(1.0aa, 1.0a, 1); + + RPMVERCMP(10.0001, 10.0001, 0); + RPMVERCMP(10.0001, 10.1, 0); + RPMVERCMP(10.1, 10.0001, 0); + RPMVERCMP(10.0001, 10.0039, -1); + RPMVERCMP(10.0039, 10.0001, 1); + + RPMVERCMP(4.999.9, 5.0, -1); + RPMVERCMP(5.0, 4.999.9, 1); + + RPMVERCMP(20101121, 20101121, 0); + RPMVERCMP(20101121, 20101122, -1); + RPMVERCMP(20101122, 20101121, 1); + + RPMVERCMP(2_0, 2_0, 0); + RPMVERCMP(2.0, 2_0, -1); /* Note: in rpm those compare equal */ + RPMVERCMP(2_0, 2.0, 1); /* Note: in rpm those compare equal */ + + /* RhBug:178798 case */ + RPMVERCMP(a, a, 0); + RPMVERCMP(a+, a+, 0); + RPMVERCMP(a+, a_, 0); + RPMVERCMP(a_, a+, 0); + RPMVERCMP(+a, +a, 0); + RPMVERCMP(+a, _a, 0); + RPMVERCMP(_a, +a, 0); + RPMVERCMP(+_, +_, 0); + RPMVERCMP(_+, +_, 0); + RPMVERCMP(_+, _+, 0); + RPMVERCMP(+, _, 0); + RPMVERCMP(_, +, 0); + + /* Basic testcases for tilde sorting */ + RPMVERCMP(1.0~rc1, 1.0~rc1, 0); + RPMVERCMP(1.0~rc1, 1.0, -1); + RPMVERCMP(1.0, 1.0~rc1, 1); + RPMVERCMP(1.0~rc1, 1.0~rc2, -1); + RPMVERCMP(1.0~rc2, 1.0~rc1, 1); + RPMVERCMP(1.0~rc1~git123, 1.0~rc1~git123, 0); + RPMVERCMP(1.0~rc1~git123, 1.0~rc1, -1); + RPMVERCMP(1.0~rc1, 1.0~rc1~git123, 1); + + /* Basic testcases for caret sorting */ + RPMVERCMP(1.0^, 1.0^, 0); + RPMVERCMP(1.0^, 1.0, 1); + RPMVERCMP(1.0, 1.0^, -1); + RPMVERCMP(1.0^git1, 1.0^git1, 0); + RPMVERCMP(1.0^git1, 1.0, 1); + RPMVERCMP(1.0, 1.0^git1, -1); + RPMVERCMP(1.0^git1, 1.0^git2, -1); + RPMVERCMP(1.0^git2, 1.0^git1, 1); + RPMVERCMP(1.0^git1, 1.01, -1); + RPMVERCMP(1.01, 1.0^git1, 1); + RPMVERCMP(1.0^20160101, 1.0^20160101, 0); + RPMVERCMP(1.0^20160101, 1.0.1, -1); + RPMVERCMP(1.0.1, 1.0^20160101, 1); + RPMVERCMP(1.0^20160101^git1, 1.0^20160101^git1, 0); + RPMVERCMP(1.0^20160102, 1.0^20160101^git1, 1); + RPMVERCMP(1.0^20160101^git1, 1.0^20160102, -1); + + /* Basic testcases for tilde and caret sorting */ + RPMVERCMP(1.0~rc1^git1, 1.0~rc1^git1, 0); + RPMVERCMP(1.0~rc1^git1, 1.0~rc1, 1); + RPMVERCMP(1.0~rc1, 1.0~rc1^git1, -1); + RPMVERCMP(1.0^git1~pre, 1.0^git1~pre, 0); + RPMVERCMP(1.0^git1, 1.0^git1~pre, 1); + RPMVERCMP(1.0^git1~pre, 1.0^git1, -1); + + /* These are included here to document current, arguably buggy behaviors + * for reference purposes and for easy checking against unintended + * behavior changes. */ + log_info("/* RPM version comparison oddities */"); + /* RhBug:811992 case */ + RPMVERCMP(1b.fc17, 1b.fc17, 0); + RPMVERCMP(1b.fc17, 1.fc17, 1); /* Note: this is reversed from rpm's vercmp, WAT! */ + RPMVERCMP(1.fc17, 1b.fc17, -1); + RPMVERCMP(1g.fc17, 1g.fc17, 0); + RPMVERCMP(1g.fc17, 1.fc17, 1); + RPMVERCMP(1.fc17, 1g.fc17, -1); + + /* Non-ascii characters are considered equal so these are all the same, eh… */ + RPMVERCMP(1.1.α, 1.1.α, 0); + RPMVERCMP(1.1.α, 1.1.β, 0); + RPMVERCMP(1.1.β, 1.1.α, 0); + RPMVERCMP(1.1.αα, 1.1.α, 0); + RPMVERCMP(1.1.α, 1.1.ββ, 0); + RPMVERCMP(1.1.ββ, 1.1.αα, 0); +} + +TEST(strextendf) { + _cleanup_free_ char *p = NULL; + + assert_se(strextendf(&p, "<%i>", 77) >= 0); + assert_se(streq(p, "<77>")); + + assert_se(strextendf(&p, "<%i>", 99) >= 0); + assert_se(streq(p, "<77><99>")); + + assert_se(strextendf(&p, "<%80i>", 88) >= 0); + assert_se(streq(p, "<77><99>< 88>")); + + assert_se(strextendf(&p, "<%08x>", 0x1234u) >= 0); + assert_se(streq(p, "<77><99>< 88><00001234>")); + + p = mfree(p); + + assert_se(strextendf_with_separator(&p, ",", "<%i>", 77) >= 0); + assert_se(streq(p, "<77>")); + + assert_se(strextendf_with_separator(&p, ",", "<%i>", 99) >= 0); + assert_se(streq(p, "<77>,<99>")); + + assert_se(strextendf_with_separator(&p, ",", "<%80i>", 88) >= 0); + assert_se(streq(p, "<77>,<99>,< 88>")); + + assert_se(strextendf_with_separator(&p, ",", "<%08x>", 0x1234u) >= 0); + assert_se(streq(p, "<77>,<99>,< 88>,<00001234>")); +} + +TEST(string_replace_char) { + assert_se(streq(string_replace_char(strdupa_safe(""), 'a', 'b'), "")); + assert_se(streq(string_replace_char(strdupa_safe("abc"), 'a', 'b'), "bbc")); + assert_se(streq(string_replace_char(strdupa_safe("hoge"), 'a', 'b'), "hoge")); + assert_se(streq(string_replace_char(strdupa_safe("aaaa"), 'a', 'b'), "bbbb")); + assert_se(streq(string_replace_char(strdupa_safe("aaaa"), 'a', '\t'), "\t\t\t\t")); +} + +TEST(strspn_from_end) { + assert_se(strspn_from_end(NULL, NULL) == 0); + assert_se(strspn_from_end("hoge", NULL) == 0); + assert_se(strspn_from_end(NULL, DIGITS) == 0); + assert_se(strspn_from_end("", DIGITS) == 0); + assert_se(strspn_from_end("hoge", DIGITS) == 0); + assert_se(strspn_from_end("1234", DIGITS) == 4); + assert_se(strspn_from_end("aaa1234", DIGITS) == 4); + assert_se(strspn_from_end("aaa1234aaa", DIGITS) == 0); + assert_se(strspn_from_end("aaa12aa34", DIGITS) == 2); +} + +TEST(streq_skip_trailing_chars) { + /* NULL is WHITESPACE by default*/ + assert_se(streq_skip_trailing_chars("foo bar", "foo bar", NULL)); + assert_se(streq_skip_trailing_chars("foo", "foo", NULL)); + assert_se(streq_skip_trailing_chars("foo bar ", "foo bar", NULL)); + assert_se(streq_skip_trailing_chars("foo bar", "foo bar\t\t", NULL)); + assert_se(streq_skip_trailing_chars("foo bar ", "foo bar\t\t", NULL)); + assert_se(streq_skip_trailing_chars("foo\nbar", "foo\nbar", NULL)); + assert_se(streq_skip_trailing_chars("\t\tfoo bar", "\t\tfoo bar", NULL)); + assert_se(streq_skip_trailing_chars(" foo bar\t", " foo bar\n", NULL)); + + assert_se(!streq_skip_trailing_chars("foobar", "foo bar", NULL)); + assert_se(!streq_skip_trailing_chars("foo\nbar", "foo\tbar", NULL)); + assert_se(!streq_skip_trailing_chars("\t\nfoo bar", "\t foo bar", NULL)); + + assert_se(streq_skip_trailing_chars("foo bar ", "foo bar", WHITESPACE)); + assert_se(!streq_skip_trailing_chars("foo bar ", "foo bar", NEWLINE)); + + assert_se(streq_skip_trailing_chars(NULL, NULL, NULL)); + assert_se(streq_skip_trailing_chars("", "", NULL)); + assert_se(!streq_skip_trailing_chars(NULL, "foo bar", NULL)); + assert_se(!streq_skip_trailing_chars("foo", NULL, NULL)); + assert_se(!streq_skip_trailing_chars("", "f", NULL)); +} + +#define TEST_MAKE_CSTRING_ONE(x, ret, mode, expect) \ + do { \ + _cleanup_free_ char *b = NULL; \ + assert_se(make_cstring((x), ELEMENTSOF(x), (mode), &b) == (ret)); \ + assert_se(streq_ptr(b, (expect))); \ + } while(false) + +TEST(make_cstring) { + static const char test1[] = "this is a test", + test2[] = "", + test3[] = "a", + test4[] = "aa\0aa", + test5[] = { 'b', 'b', 0, 'b' , 'b' }, + test6[] = {}, + test7[] = { 'x' }, + test8[] = { 'x', 'y', 'z' }; + + TEST_MAKE_CSTRING_ONE(test1, -EINVAL, MAKE_CSTRING_REFUSE_TRAILING_NUL, NULL); + TEST_MAKE_CSTRING_ONE(test1, 0, MAKE_CSTRING_ALLOW_TRAILING_NUL, "this is a test"); + TEST_MAKE_CSTRING_ONE(test1, 0, MAKE_CSTRING_REQUIRE_TRAILING_NUL, "this is a test"); + + TEST_MAKE_CSTRING_ONE(test2, -EINVAL, MAKE_CSTRING_REFUSE_TRAILING_NUL, NULL); + TEST_MAKE_CSTRING_ONE(test2, 0, MAKE_CSTRING_ALLOW_TRAILING_NUL, ""); + TEST_MAKE_CSTRING_ONE(test2, 0, MAKE_CSTRING_REQUIRE_TRAILING_NUL, ""); + + TEST_MAKE_CSTRING_ONE(test3, -EINVAL, MAKE_CSTRING_REFUSE_TRAILING_NUL, NULL); + TEST_MAKE_CSTRING_ONE(test3, 0, MAKE_CSTRING_ALLOW_TRAILING_NUL, "a"); + TEST_MAKE_CSTRING_ONE(test3, 0, MAKE_CSTRING_REQUIRE_TRAILING_NUL, "a"); + + TEST_MAKE_CSTRING_ONE(test4, -EINVAL, MAKE_CSTRING_REFUSE_TRAILING_NUL, NULL); + TEST_MAKE_CSTRING_ONE(test4, -EINVAL, MAKE_CSTRING_ALLOW_TRAILING_NUL, NULL); + TEST_MAKE_CSTRING_ONE(test4, -EINVAL, MAKE_CSTRING_REQUIRE_TRAILING_NUL, NULL); + + TEST_MAKE_CSTRING_ONE(test5, -EINVAL, MAKE_CSTRING_REFUSE_TRAILING_NUL, NULL); + TEST_MAKE_CSTRING_ONE(test5, -EINVAL, MAKE_CSTRING_ALLOW_TRAILING_NUL, NULL); + TEST_MAKE_CSTRING_ONE(test5, -EINVAL, MAKE_CSTRING_REQUIRE_TRAILING_NUL, NULL); + + TEST_MAKE_CSTRING_ONE(test6, 0, MAKE_CSTRING_REFUSE_TRAILING_NUL, ""); + TEST_MAKE_CSTRING_ONE(test6, 0, MAKE_CSTRING_ALLOW_TRAILING_NUL, ""); + TEST_MAKE_CSTRING_ONE(test6, -EINVAL, MAKE_CSTRING_REQUIRE_TRAILING_NUL, NULL); + + TEST_MAKE_CSTRING_ONE(test7, 0, MAKE_CSTRING_REFUSE_TRAILING_NUL, "x"); + TEST_MAKE_CSTRING_ONE(test7, 0, MAKE_CSTRING_ALLOW_TRAILING_NUL, "x"); + TEST_MAKE_CSTRING_ONE(test7, -EINVAL, MAKE_CSTRING_REQUIRE_TRAILING_NUL, NULL); + + TEST_MAKE_CSTRING_ONE(test8, 0, MAKE_CSTRING_REFUSE_TRAILING_NUL, "xyz"); + TEST_MAKE_CSTRING_ONE(test8, 0, MAKE_CSTRING_ALLOW_TRAILING_NUL, "xyz"); + TEST_MAKE_CSTRING_ONE(test8, -EINVAL, MAKE_CSTRING_REQUIRE_TRAILING_NUL, NULL); +} + +TEST(find_line_startswith) { + static const char text[] = + "foobar\n" + "this is a test\n" + "foobar: waldo\n" + "more\n" + "\n" + "piff\n" + "foobarfoobar\n" + "iff\n"; + static const char emptystring[] = ""; + + assert_se(find_line_startswith(text, "") == text); + assert_se(find_line_startswith(text, "f") == text+1); + assert_se(find_line_startswith(text, "foobar") == text+6); + assert_se(!find_line_startswith(text, "foobarx")); + assert_se(!find_line_startswith(text, "oobar")); + assert_se(find_line_startswith(text, "t") == text + 8); + assert_se(find_line_startswith(text, "th") == text + 9); + assert_se(find_line_startswith(text, "this") == text + 11); + assert_se(find_line_startswith(text, "foobarf") == text + 54); + assert_se(find_line_startswith(text, "more\n") == text + 41); + assert_se(find_line_startswith(text, "\n") == text + 42); + assert_se(find_line_startswith(text, "iff") == text + 63); + + assert_se(find_line_startswith(emptystring, "") == emptystring); + assert_se(!find_line_startswith(emptystring, "x")); +} + +TEST(strstrafter) { + static const char buffer[] = "abcdefghijklmnopqrstuvwxyz"; + + assert_se(!strstrafter(NULL, NULL)); + assert_se(!strstrafter("", NULL)); + assert_se(!strstrafter(NULL, "")); + assert_se(streq_ptr(strstrafter("", ""), "")); + + assert_se(strstrafter(buffer, "a") == buffer + 1); + assert_se(strstrafter(buffer, "") == buffer); + assert_se(strstrafter(buffer, "ab") == buffer + 2); + assert_se(strstrafter(buffer, "cde") == buffer + 5); + assert_se(strstrafter(buffer, "xyz") == strchr(buffer, 0)); + assert_se(strstrafter(buffer, buffer) == strchr(buffer, 0)); + assert_se(!strstrafter(buffer, "-")); +} + +TEST(version_is_valid) { + assert_se(!version_is_valid(NULL)); + assert_se(!version_is_valid("")); + assert_se(version_is_valid("0")); + assert_se(version_is_valid("5")); + assert_se(version_is_valid("999999")); + assert_se(version_is_valid("999999.5")); + assert_se(version_is_valid("6.2.12-300.fc38.x86_64")); +} + +TEST(strextendn) { + _cleanup_free_ char *x = NULL; + + assert_se(streq_ptr(strextendn(&x, NULL, 0), "")); + x = mfree(x); + + assert_se(streq_ptr(strextendn(&x, "", 0), "")); + x = mfree(x); + + assert_se(streq_ptr(strextendn(&x, "xxx", 3), "xxx")); + assert_se(streq_ptr(strextendn(&x, "xxx", 3), "xxxxxx")); + assert_se(streq_ptr(strextendn(&x, "...", 1), "xxxxxx.")); + assert_se(streq_ptr(strextendn(&x, "...", 2), "xxxxxx...")); + assert_se(streq_ptr(strextendn(&x, "...", 3), "xxxxxx......")); + assert_se(streq_ptr(strextendn(&x, "...", 4), "xxxxxx.........")); + x = mfree(x); +} + +TEST(strlevenshtein) { + assert_se(strlevenshtein(NULL, NULL) == 0); + assert_se(strlevenshtein("", "") == 0); + assert_se(strlevenshtein("", NULL) == 0); + assert_se(strlevenshtein(NULL, "") == 0); + + assert_se(strlevenshtein("a", "a") == 0); + assert_se(strlevenshtein("a", "b") == 1); + assert_se(strlevenshtein("b", "a") == 1); + assert_se(strlevenshtein("a", "") == 1); + assert_se(strlevenshtein("", "a") == 1); + + assert_se(strlevenshtein("xxx", "xxx") == 0); + assert_se(strlevenshtein("xxx", "yyy") == 3); + assert_se(strlevenshtein("yyy", "xxx") == 3); + assert_se(strlevenshtein("xx", "xxx") == 1); + assert_se(strlevenshtein("xxx", "xx") == 1); + assert_se(strlevenshtein("x", "xxx") == 2); + assert_se(strlevenshtein("xxx", "x") == 2); + + assert_se(strlevenshtein("sitting", "kitten") == 3); + assert_se(strlevenshtein("sunday", "saturday") == 3); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-strip-tab-ansi.c b/src/test/test-strip-tab-ansi.c new file mode 100644 index 0000000..6f73d26 --- /dev/null +++ b/src/test/test-strip-tab-ansi.c @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdio.h> + +#include "alloc-util.h" +#include "pretty-print.h" +#include "string-util.h" +#include "terminal-util.h" +#include "tests.h" + +TEST(strip_tab_ansi) { + _cleanup_free_ char *urlified = NULL, *q = NULL, *qq = NULL; + char *p, *z; + + assert_se(p = strdup("\tFoobar\tbar\twaldo\t")); + assert_se(strip_tab_ansi(&p, NULL, NULL)); + fprintf(stdout, "<%s>\n", p); + assert_se(streq(p, " Foobar bar waldo ")); + free(p); + + assert_se(p = strdup(ANSI_HIGHLIGHT "Hello" ANSI_NORMAL ANSI_HIGHLIGHT_RED " world!" ANSI_NORMAL)); + assert_se(strip_tab_ansi(&p, NULL, NULL)); + fprintf(stdout, "<%s>\n", p); + assert_se(streq(p, "Hello world!")); + free(p); + + assert_se(p = strdup("\x1B[\x1B[\t\x1B[" ANSI_HIGHLIGHT "\x1B[" "Hello" ANSI_NORMAL ANSI_HIGHLIGHT_RED " world!" ANSI_NORMAL)); + assert_se(strip_tab_ansi(&p, NULL, NULL)); + assert_se(streq(p, "\x1B[\x1B[ \x1B[\x1B[Hello world!")); + free(p); + + assert_se(p = strdup("\x1B[waldo")); + assert_se(strip_tab_ansi(&p, NULL, NULL)); + assert_se(streq(p, "\x1B[waldo")); + free(p); + + assert_se(p = strdup("\r\rwaldo")); + assert_se(strip_tab_ansi(&p, NULL, NULL)); + assert_se(streq(p, "\r\rwaldo")); + free(p); + + assert_se(p = strdup("waldo\r\r")); + assert_se(strip_tab_ansi(&p, NULL, NULL)); + assert_se(streq(p, "waldo")); + free(p); + + assert_se(p = strdup("waldo\r\r\n\r\n")); + assert_se(strip_tab_ansi(&p, NULL, NULL)); + assert_se(streq(p, "waldo\n\n")); + free(p); + + assert_se(terminal_urlify_path("/etc/fstab", "i am a fabulous link", &urlified) >= 0); + assert_se(p = strjoin("something ", urlified, " something-else")); + assert_se(q = strdup(p)); + printf("<%s>\n", p); + assert_se(strip_tab_ansi(&p, NULL, NULL)); + printf("<%s>\n", p); + assert_se(streq(p, "something i am a fabulous link something-else")); + p = mfree(p); + + /* Truncate the formatted string in the middle of an ANSI sequence (in which case we shouldn't touch the + * incomplete sequence) */ + z = strstr(q, "fstab"); + if (z) { + *z = 0; + assert_se(qq = strdup(q)); + assert_se(strip_tab_ansi(&q, NULL, NULL)); + assert_se(streq(q, qq)); + } +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-strv.c b/src/test/test-strv.c new file mode 100644 index 0000000..cfd662b --- /dev/null +++ b/src/test/test-strv.c @@ -0,0 +1,1009 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "escape.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" + +TEST(str_in_set) { + assert_se(STR_IN_SET("x", "x", "y", "z")); + assert_se(!STR_IN_SET("X", "x", "y", "z")); + assert_se(!STR_IN_SET("", "x", "y", "z")); + assert_se(STR_IN_SET("x", "w", "x")); +} + +TEST(strptr_in_set) { + assert_se(STRPTR_IN_SET("x", "x", "y", "z")); + assert_se(!STRPTR_IN_SET("X", "x", "y", "z")); + assert_se(!STRPTR_IN_SET("", "x", "y", "z")); + assert_se(STRPTR_IN_SET("x", "w", "x")); + + assert_se(!STRPTR_IN_SET(NULL, "x", "y", "z")); + assert_se(!STRPTR_IN_SET(NULL, "")); + /* strv cannot contain a null, hence the result below */ + assert_se(!STRPTR_IN_SET(NULL, NULL)); +} + +TEST(startswith_set) { + assert_se(!STARTSWITH_SET("foo", "bar", "baz", "waldo")); + assert_se(!STARTSWITH_SET("foo", "bar")); + + assert_se(STARTSWITH_SET("abc", "a", "ab", "abc")); + assert_se(STARTSWITH_SET("abc", "ax", "ab", "abc")); + assert_se(STARTSWITH_SET("abc", "ax", "abx", "abc")); + assert_se(!STARTSWITH_SET("abc", "ax", "abx", "abcx")); + + assert_se(streq_ptr(STARTSWITH_SET("foobar", "hhh", "kkk", "foo", "zzz"), "bar")); + assert_se(streq_ptr(STARTSWITH_SET("foobar", "hhh", "kkk", "", "zzz"), "foobar")); + assert_se(streq_ptr(STARTSWITH_SET("", "hhh", "kkk", "zzz", ""), "")); +} + +static const char* const input_table_multiple[] = { + "one", + "two", + "three", + NULL, +}; + +static const char* const input_table_quoted[] = { + "one", + " two\t three ", + " four five", + NULL, +}; + +static const char* const input_table_quoted_joined[] = { + "one", + " two\t three " " four five", + NULL, +}; + +static const char* const input_table_one[] = { + "one", + NULL, +}; + +static const char* const input_table_none[] = { + NULL, +}; + +static const char* const input_table_two_empties[] = { + "", + "", + NULL, +}; + +static const char* const input_table_one_empty[] = { + "", + NULL, +}; + +static const char* const input_table_unescape[] = { + "ID_VENDOR=QEMU", + "ID_VENDOR_ENC=QEMUx20x20x20x20", + "ID_MODEL_ENC=QEMUx20HARDDISKx20x20x20", + NULL, +}; + +static const char* const input_table_retain_escape[] = { + "ID_VENDOR=QEMU", + "ID_VENDOR_ENC=QEMU\\x20\\x20\\x20\\x20", + "ID_MODEL_ENC=QEMU\\x20HARDDISK\\x20\\x20\\x20", + NULL, +}; + +TEST(strv_find) { + assert_se(strv_find((char **)input_table_multiple, "three")); + assert_se(!strv_find((char **)input_table_multiple, "four")); +} + +TEST(strv_find_prefix) { + assert_se(strv_find_prefix((char **)input_table_multiple, "o")); + assert_se(strv_find_prefix((char **)input_table_multiple, "one")); + assert_se(strv_find_prefix((char **)input_table_multiple, "")); + assert_se(!strv_find_prefix((char **)input_table_multiple, "xxx")); + assert_se(!strv_find_prefix((char **)input_table_multiple, "onee")); +} + +TEST(strv_find_startswith) { + char *r; + + r = strv_find_startswith((char **)input_table_multiple, "o"); + assert_se(r && streq(r, "ne")); + + r = strv_find_startswith((char **)input_table_multiple, "one"); + assert_se(r && streq(r, "")); + + r = strv_find_startswith((char **)input_table_multiple, ""); + assert_se(r && streq(r, "one")); + + assert_se(!strv_find_startswith((char **)input_table_multiple, "xxx")); + assert_se(!strv_find_startswith((char **)input_table_multiple, "onee")); +} + +TEST(strv_join) { + _cleanup_free_ char *p = strv_join((char **)input_table_multiple, ", "); + assert_se(p); + assert_se(streq(p, "one, two, three")); + + _cleanup_free_ char *q = strv_join((char **)input_table_multiple, ";"); + assert_se(q); + assert_se(streq(q, "one;two;three")); + + _cleanup_free_ char *r = strv_join((char **)input_table_multiple, NULL); + assert_se(r); + assert_se(streq(r, "one two three")); + + _cleanup_free_ char *s = strv_join(STRV_MAKE("1", "2", "3,3"), ","); + assert_se(s); + assert_se(streq(s, "1,2,3,3")); + + _cleanup_free_ char *t = strv_join((char **)input_table_one, ", "); + assert_se(t); + assert_se(streq(t, "one")); + + _cleanup_free_ char *u = strv_join((char **)input_table_none, ", "); + assert_se(u); + assert_se(streq(u, "")); + + _cleanup_free_ char *v = strv_join((char **)input_table_two_empties, ", "); + assert_se(v); + assert_se(streq(v, ", ")); + + _cleanup_free_ char *w = strv_join((char **)input_table_one_empty, ", "); + assert_se(w); + assert_se(streq(w, "")); +} + +TEST(strv_join_full) { + _cleanup_free_ char *p = strv_join_full((char **)input_table_multiple, ", ", "foo", false); + assert_se(p); + assert_se(streq(p, "fooone, footwo, foothree")); + + _cleanup_free_ char *q = strv_join_full((char **)input_table_multiple, ";", "foo", false); + assert_se(q); + assert_se(streq(q, "fooone;footwo;foothree")); + + _cleanup_free_ char *r = strv_join_full(STRV_MAKE("a", "a;b", "a:c"), ";", NULL, true); + assert_se(r); + assert_se(streq(r, "a;a\\;b;a:c")); + + _cleanup_free_ char *s = strv_join_full(STRV_MAKE("a", "a;b", "a;;c", ";", ";x"), ";", NULL, true); + assert_se(s); + assert_se(streq(s, "a;a\\;b;a\\;\\;c;\\;;\\;x")); + + _cleanup_free_ char *t = strv_join_full(STRV_MAKE("a", "a;b", "a:c", ";"), ";", "=", true); + assert_se(t); + assert_se(streq(t, "=a;=a\\;b;=a:c;=\\;")); + t = mfree(t); + + _cleanup_free_ char *u = strv_join_full((char **)input_table_multiple, NULL, "foo", false); + assert_se(u); + assert_se(streq(u, "fooone footwo foothree")); + + _cleanup_free_ char *v = strv_join_full((char **)input_table_one, ", ", "foo", false); + assert_se(v); + assert_se(streq(v, "fooone")); + + _cleanup_free_ char *w = strv_join_full((char **)input_table_none, ", ", "foo", false); + assert_se(w); + assert_se(streq(w, "")); + + _cleanup_free_ char *x = strv_join_full((char **)input_table_two_empties, ", ", "foo", false); + assert_se(x); + assert_se(streq(x, "foo, foo")); + + _cleanup_free_ char *y = strv_join_full((char **)input_table_one_empty, ", ", "foo", false); + assert_se(y); + assert_se(streq(y, "foo")); +} + +static void test_strv_unquote_one(const char *quoted, char **list) { + _cleanup_strv_free_ char **s = NULL; + _cleanup_free_ char *j = NULL; + unsigned i = 0; + int r; + + log_info("/* %s */", __func__); + + r = strv_split_full(&s, quoted, WHITESPACE, EXTRACT_UNQUOTE); + assert_se(r == (int) strv_length(list)); + assert_se(s); + j = strv_join(s, " | "); + assert_se(j); + puts(j); + + STRV_FOREACH(t, s) + assert_se(streq(list[i++], *t)); + + assert_se(list[i] == NULL); +} + +TEST(strv_unquote) { + test_strv_unquote_one(" foo=bar \"waldo\" zzz ", STRV_MAKE("foo=bar", "waldo", "zzz")); + test_strv_unquote_one("", STRV_MAKE_EMPTY); + test_strv_unquote_one(" ", STRV_MAKE_EMPTY); + test_strv_unquote_one(" ", STRV_MAKE_EMPTY); + test_strv_unquote_one(" x", STRV_MAKE("x")); + test_strv_unquote_one("x ", STRV_MAKE("x")); + test_strv_unquote_one(" x ", STRV_MAKE("x")); + test_strv_unquote_one(" \"x\" ", STRV_MAKE("x")); + test_strv_unquote_one(" 'x' ", STRV_MAKE("x")); + test_strv_unquote_one(" 'x\"' ", STRV_MAKE("x\"")); + test_strv_unquote_one(" \"x'\" ", STRV_MAKE("x'")); + test_strv_unquote_one("a '--b=c \"d e\"'", STRV_MAKE("a", "--b=c \"d e\"")); + + /* trailing backslashes */ + test_strv_unquote_one(" x\\\\", STRV_MAKE("x\\")); +} + +static void test_invalid_unquote_one(const char *quoted) { + char **s = NULL; + int r; + + log_info("/* %s */", __func__); + + r = strv_split_full(&s, quoted, WHITESPACE, EXTRACT_UNQUOTE); + assert_se(s == NULL); + assert_se(r == -EINVAL); +} + +TEST(invalid_unquote) { + test_invalid_unquote_one(" x\\"); + test_invalid_unquote_one("a --b='c \"d e\"''"); + test_invalid_unquote_one("a --b='c \"d e\" '\""); + test_invalid_unquote_one("a --b='c \"d e\"garbage"); + test_invalid_unquote_one("'"); + test_invalid_unquote_one("\""); + test_invalid_unquote_one("'x'y'g"); +} + +TEST(strv_split) { + _cleanup_strv_free_erase_ char **l = NULL; + const char str[] = "one,two,three"; + + l = strv_split(str, ","); + assert_se(l); + assert_se(strv_equal(l, (char**) input_table_multiple)); + + strv_free_erase(l); + + l = strv_split(" one two\t three", WHITESPACE); + assert_se(l); + assert_se(strv_equal(l, (char**) input_table_multiple)); + + strv_free_erase(l); + + /* Setting NULL for separator is equivalent to WHITESPACE */ + l = strv_split(" one two\t three", NULL); + assert_se(l); + assert_se(strv_equal(l, (char**) input_table_multiple)); + + strv_free_erase(l); + + assert_se(strv_split_full(&l, " one two\t three", NULL, 0) == 3); + assert_se(strv_equal(l, (char**) input_table_multiple)); + + strv_free_erase(l); + + assert_se(strv_split_full(&l, " 'one' \" two\t three \" ' four five'", NULL, EXTRACT_UNQUOTE) == 3); + assert_se(strv_equal(l, (char**) input_table_quoted)); + + l = strv_free_erase(l); + + /* missing last quote causes extraction to fail. */ + assert_se(strv_split_full(&l, " 'one' \" two\t three \" ' four five", NULL, EXTRACT_UNQUOTE) == -EINVAL); + assert_se(!l); + + /* missing last quote, but the last element is _not_ ignored with EXTRACT_RELAX. */ + assert_se(strv_split_full(&l, " 'one' \" two\t three \" ' four five", NULL, EXTRACT_UNQUOTE | EXTRACT_RELAX) == 3); + assert_se(strv_equal(l, (char**) input_table_quoted)); + + l = strv_free_erase(l); + + /* missing separator between items */ + assert_se(strv_split_full(&l, " 'one' \" two\t three \"' four five'", NULL, EXTRACT_UNQUOTE | EXTRACT_RELAX) == 2); + assert_se(strv_equal(l, (char**) input_table_quoted_joined)); + + l = strv_free_erase(l); + + assert_se(strv_split_full(&l, " 'one' \" two\t three \"' four five", NULL, + EXTRACT_UNQUOTE | EXTRACT_RELAX | EXTRACT_UNESCAPE_RELAX) == 2); + assert_se(strv_equal(l, (char**) input_table_quoted_joined)); + + l = strv_free_erase(l); + + assert_se(strv_split_full(&l, "\\", NULL, EXTRACT_UNQUOTE | EXTRACT_RELAX | EXTRACT_UNESCAPE_RELAX) == 1); + assert_se(strv_equal(l, STRV_MAKE("\\"))); + + l = strv_free_erase(l); + + assert_se(l = strv_split("\\", NULL)); + assert_se(strv_equal(l, STRV_MAKE("\\"))); + + l = strv_free_erase(l); + + assert_se(l = strv_split("aa\\ bb\\", NULL)); + assert_se(strv_equal(l, STRV_MAKE("aa\\", "bb\\"))); + + l = strv_free_erase(l); + + assert_se(l = strv_split("aa\" bb'", NULL)); + assert_se(strv_equal(l, STRV_MAKE("aa\"", "bb'"))); +} + +TEST(strv_split_empty) { + _cleanup_strv_free_ char **l = NULL; + + l = strv_split("", WHITESPACE); + assert_se(l); + assert_se(strv_isempty(l)); + l = strv_free(l); + + assert_se(l = strv_split("", NULL)); + assert_se(strv_isempty(l)); + l = strv_free(l); + + assert_se(strv_split_full(&l, "", NULL, 0) == 0); + assert_se(l); + assert_se(strv_isempty(l)); + l = strv_free(l); + + assert_se(strv_split_full(&l, "", NULL, EXTRACT_UNQUOTE) == 0); + assert_se(l); + assert_se(strv_isempty(l)); + l = strv_free(l); + + assert_se(strv_split_full(&l, "", WHITESPACE, EXTRACT_UNQUOTE) == 0); + assert_se(l); + assert_se(strv_isempty(l)); + l = strv_free(l); + + assert_se(strv_split_full(&l, "", WHITESPACE, EXTRACT_UNQUOTE | EXTRACT_RELAX) == 0); + assert_se(l); + assert_se(strv_isempty(l)); + strv_free(l); + + l = strv_split(" ", WHITESPACE); + assert_se(l); + assert_se(strv_isempty(l)); + strv_free(l); + + l = strv_split(" ", NULL); + assert_se(l); + assert_se(strv_isempty(l)); + l = strv_free(l); + + assert_se(strv_split_full(&l, " ", NULL, 0) == 0); + assert_se(l); + assert_se(strv_isempty(l)); + l = strv_free(l); + + assert_se(strv_split_full(&l, " ", WHITESPACE, EXTRACT_UNQUOTE) == 0); + assert_se(l); + assert_se(strv_isempty(l)); + l = strv_free(l); + + assert_se(strv_split_full(&l, " ", NULL, EXTRACT_UNQUOTE) == 0); + assert_se(l); + assert_se(strv_isempty(l)); + l = strv_free(l); + + assert_se(strv_split_full(&l, " ", NULL, EXTRACT_UNQUOTE | EXTRACT_RELAX) == 0); + assert_se(l); + assert_se(strv_isempty(l)); +} + +TEST(strv_split_full) { + _cleanup_strv_free_ char **l = NULL; + const char *str = ":foo\\:bar::waldo:"; + int r; + + r = strv_split_full(&l, str, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + assert_se(r == (int) strv_length(l)); + assert_se(streq_ptr(l[0], "")); + assert_se(streq_ptr(l[1], "foo:bar")); + assert_se(streq_ptr(l[2], "")); + assert_se(streq_ptr(l[3], "waldo")); + assert_se(streq_ptr(l[4], "")); + assert_se(streq_ptr(l[5], NULL)); +} + +TEST(strv_split_and_extend_full) { + _cleanup_strv_free_ char **l = NULL; + const char *str1 = ":foo\\:bar:"; + const char *str2 = "waldo::::::baz"; + int r; + + r = strv_split_and_extend(&l, "", ":", false); + assert_se(r == (int) strv_length(l)); + r = strv_split_and_extend_full(&l, str1, ":", false, EXTRACT_DONT_COALESCE_SEPARATORS); + assert_se(r == (int) strv_length(l)); + assert_se(streq_ptr(l[0], "")); + assert_se(streq_ptr(l[1], "foo:bar")); + assert_se(streq_ptr(l[2], "")); + r = strv_split_and_extend_full(&l, str2, ":", false, 0); + assert_se(r == (int) strv_length(l)); + assert_se(streq_ptr(l[3], "waldo")); + assert_se(streq_ptr(l[4], "baz")); + assert_se(streq_ptr(l[5], NULL)); +} + +TEST(strv_split_colon_pairs) { + _cleanup_strv_free_ char **l = NULL; + const char *str = "one:two three four:five six seven:eight\\:nine ten\\:eleven\\\\", + *str_inval="one:two three:four:five"; + int r; + + r = strv_split_colon_pairs(&l, str); + assert_se(r == (int) strv_length(l)); + assert_se(r == 12); + assert_se(streq_ptr(l[0], "one")); + assert_se(streq_ptr(l[1], "two")); + assert_se(streq_ptr(l[2], "three")); + assert_se(streq_ptr(l[3], "")); + assert_se(streq_ptr(l[4], "four")); + assert_se(streq_ptr(l[5], "five")); + assert_se(streq_ptr(l[6], "six")); + assert_se(streq_ptr(l[7], "")); + assert_se(streq_ptr(l[8], "seven")); + assert_se(streq_ptr(l[9], "eight:nine")); + assert_se(streq_ptr(l[10], "ten:eleven\\")); + assert_se(streq_ptr(l[11], "")); + assert_se(streq_ptr(l[12], NULL)); + + r = strv_split_colon_pairs(&l, str_inval); + assert_se(r == -EINVAL); +} + +TEST(strv_split_newlines) { + unsigned i = 0; + _cleanup_strv_free_ char **l = NULL; + const char str[] = "one\ntwo\nthree"; + + l = strv_split_newlines(str); + assert_se(l); + + STRV_FOREACH(s, l) + assert_se(streq(*s, input_table_multiple[i++])); +} + +TEST(strv_split_newlines_full) { + const char str[] = + "ID_VENDOR=QEMU\n" + "ID_VENDOR_ENC=QEMU\\x20\\x20\\x20\\x20\n" + "ID_MODEL_ENC=QEMU\\x20HARDDISK\\x20\\x20\\x20\n" + "\n\n\n"; + _cleanup_strv_free_ char **l = NULL; + + assert_se(strv_split_newlines_full(&l, str, 0) == 3); + assert_se(strv_equal(l, (char**) input_table_unescape)); + + l = strv_free(l); + + assert_se(strv_split_newlines_full(&l, str, EXTRACT_RETAIN_ESCAPE) == 3); + assert_se(strv_equal(l, (char**) input_table_retain_escape)); +} + +TEST(strv_overlap) { + const char * const input_table[] = { + "one", + "two", + "three", + NULL + }; + const char * const input_table_overlap[] = { + "two", + NULL + }; + const char * const input_table_unique[] = { + "four", + "five", + "six", + NULL + }; + + assert_se(strv_overlap((char **)input_table, (char**)input_table_overlap)); + assert_se(!strv_overlap((char **)input_table, (char**)input_table_unique)); +} + +TEST(strv_sort) { + const char* input_table[] = { + "durian", + "apple", + "citrus", + "CAPITAL LETTERS FIRST", + "banana", + NULL + }; + + strv_sort((char **)input_table); + + assert_se(streq(input_table[0], "CAPITAL LETTERS FIRST")); + assert_se(streq(input_table[1], "apple")); + assert_se(streq(input_table[2], "banana")); + assert_se(streq(input_table[3], "citrus")); + assert_se(streq(input_table[4], "durian")); +} + +TEST(strv_extend_strv_concat) { + _cleanup_strv_free_ char **a = NULL, **b = NULL; + + a = strv_new("without", "suffix"); + b = strv_new("with", "suffix"); + assert_se(a); + assert_se(b); + + assert_se(strv_extend_strv_concat(&a, b, "_suffix") >= 0); + + assert_se(streq(a[0], "without")); + assert_se(streq(a[1], "suffix")); + assert_se(streq(a[2], "with_suffix")); + assert_se(streq(a[3], "suffix_suffix")); +} + +TEST(strv_extend_strv) { + _cleanup_strv_free_ char **a = NULL, **b = NULL, **n = NULL; + + a = strv_new("abc", "def", "ghi"); + b = strv_new("jkl", "mno", "abc", "pqr"); + assert_se(a); + assert_se(b); + + assert_se(strv_extend_strv(&a, b, true) == 3); + + assert_se(streq(a[0], "abc")); + assert_se(streq(a[1], "def")); + assert_se(streq(a[2], "ghi")); + assert_se(streq(a[3], "jkl")); + assert_se(streq(a[4], "mno")); + assert_se(streq(a[5], "pqr")); + assert_se(strv_length(a) == 6); + + assert_se(strv_extend_strv(&n, b, false) >= 0); + assert_se(streq(n[0], "jkl")); + assert_se(streq(n[1], "mno")); + assert_se(streq(n[2], "abc")); + assert_se(streq(n[3], "pqr")); + assert_se(strv_length(n) == 4); +} + +TEST(strv_extend_with_size) { + _cleanup_strv_free_ char **a = NULL; + size_t n = SIZE_MAX; + + a = strv_new("test", "test1"); + assert_se(a); + + assert_se(strv_extend_with_size(&a, &n, "test2") >= 0); + assert_se(n == 3); + assert_se(strv_extend_with_size(&a, &n, "test3") >= 0); + assert_se(n == 4); + + assert_se(streq(a[0], "test")); + assert_se(streq(a[1], "test1")); + assert_se(streq(a[2], "test2")); + assert_se(streq(a[3], "test3")); + assert_se(a[4] == NULL); +} + +TEST(strv_extend) { + _cleanup_strv_free_ char **a = NULL, **b = NULL; + + a = strv_new("test", "test1"); + assert_se(a); + assert_se(strv_extend(&a, "test2") >= 0); + assert_se(strv_extend(&b, "test3") >= 0); + + assert_se(streq(a[0], "test")); + assert_se(streq(a[1], "test1")); + assert_se(streq(a[2], "test2")); + assert_se(streq(b[0], "test3")); +} + +TEST(strv_extendf) { + _cleanup_strv_free_ char **a = NULL, **b = NULL; + + a = strv_new("test", "test1"); + assert_se(a); + assert_se(strv_extendf(&a, "test2 %s %d %s", "foo", 128, "bar") >= 0); + assert_se(strv_extendf(&b, "test3 %s %s %d", "bar", "foo", 128) >= 0); + + assert_se(streq(a[0], "test")); + assert_se(streq(a[1], "test1")); + assert_se(streq(a[2], "test2 foo 128 bar")); + assert_se(streq(b[0], "test3 bar foo 128")); +} + +TEST(strv_foreach) { + _cleanup_strv_free_ char **a; + unsigned i = 0; + + a = strv_new("one", "two", "three"); + assert_se(a); + + STRV_FOREACH(check, a) + assert_se(streq(*check, input_table_multiple[i++])); +} + +TEST(strv_foreach_backwards) { + _cleanup_strv_free_ char **a; + unsigned i = 2; + + a = strv_new("one", "two", "three"); + + assert_se(a); + + STRV_FOREACH_BACKWARDS(check, a) + assert_se(streq_ptr(*check, input_table_multiple[i--])); + + STRV_FOREACH_BACKWARDS(check, (char**) NULL) + assert_not_reached(); + + STRV_FOREACH_BACKWARDS(check, STRV_MAKE_EMPTY) + assert_not_reached(); + + unsigned count = 0; + STRV_FOREACH_BACKWARDS(check, STRV_MAKE("ONE")) + count++; + assert_se(count == 1); +} + +TEST(strv_foreach_pair) { + _cleanup_strv_free_ char **a = NULL; + + a = strv_new("pair_one", "pair_one", + "pair_two", "pair_two", + "pair_three", "pair_three"); + STRV_FOREACH_PAIR(x, y, a) + assert_se(streq(*x, *y)); +} + +static void test_strv_from_stdarg_alloca_one(char **l, const char *first, ...) { + char **j; + unsigned i; + + log_info("/* %s */", __func__); + + j = strv_from_stdarg_alloca(first); + + for (i = 0;; i++) { + assert_se(streq_ptr(l[i], j[i])); + + if (!l[i]) + break; + } +} + +TEST(strv_from_stdarg_alloca) { + test_strv_from_stdarg_alloca_one(STRV_MAKE("foo", "bar"), "foo", "bar", NULL); + test_strv_from_stdarg_alloca_one(STRV_MAKE("foo"), "foo", NULL); + test_strv_from_stdarg_alloca_one(STRV_MAKE_EMPTY, NULL); +} + +TEST(strv_insert) { + _cleanup_strv_free_ char **a = NULL; + + assert_se(strv_insert(&a, 0, strdup("first")) == 0); + assert_se(streq(a[0], "first")); + assert_se(!a[1]); + + assert_se(strv_insert(&a, 0, NULL) == 0); + assert_se(streq(a[0], "first")); + assert_se(!a[1]); + + assert_se(strv_insert(&a, 1, strdup("two")) == 0); + assert_se(streq(a[0], "first")); + assert_se(streq(a[1], "two")); + assert_se(!a[2]); + + assert_se(strv_insert(&a, 4, strdup("tri")) == 0); + assert_se(streq(a[0], "first")); + assert_se(streq(a[1], "two")); + assert_se(streq(a[2], "tri")); + assert_se(!a[3]); + + assert_se(strv_insert(&a, 1, strdup("duo")) == 0); + assert_se(streq(a[0], "first")); + assert_se(streq(a[1], "duo")); + assert_se(streq(a[2], "two")); + assert_se(streq(a[3], "tri")); + assert_se(!a[4]); +} + +TEST(strv_push_prepend) { + _cleanup_strv_free_ char **a = NULL; + + assert_se(a = strv_new("foo", "bar", "three")); + + assert_se(strv_push_prepend(&a, strdup("first")) >= 0); + assert_se(streq(a[0], "first")); + assert_se(streq(a[1], "foo")); + assert_se(streq(a[2], "bar")); + assert_se(streq(a[3], "three")); + assert_se(!a[4]); + + assert_se(strv_consume_prepend(&a, strdup("first2")) >= 0); + assert_se(streq(a[0], "first2")); + assert_se(streq(a[1], "first")); + assert_se(streq(a[2], "foo")); + assert_se(streq(a[3], "bar")); + assert_se(streq(a[4], "three")); + assert_se(!a[5]); +} + +TEST(strv_push_with_size) { + _cleanup_strv_free_ char **a = NULL; + size_t n = 0; + char *i, *j; + + assert_se(i = strdup("foo")); + assert_se(strv_push_with_size(&a, &n, i) >= 0); + assert_se(n == 1); + + assert_se(i = strdup("a")); + assert_se(j = strdup("b")); + assert_se(strv_push_with_size(&a, &n, i) >= 0); + assert_se(n == 2); + assert_se(strv_push_with_size(&a, &n, j) >= 0); + assert_se(n == 3); + + assert_se(streq_ptr(a[0], "foo")); + assert_se(streq_ptr(a[1], "a")); + assert_se(streq_ptr(a[2], "b")); + assert_se(streq_ptr(a[3], NULL)); + + assert_se(n = strv_length(a)); +} + +TEST(strv_push) { + _cleanup_strv_free_ char **a = NULL; + char *i, *j; + + assert_se(i = strdup("foo")); + assert_se(strv_push(&a, i) >= 0); + + assert_se(i = strdup("a")); + assert_se(j = strdup("b")); + assert_se(strv_push_pair(&a, i, j) >= 0); + + assert_se(streq_ptr(a[0], "foo")); + assert_se(streq_ptr(a[1], "a")); + assert_se(streq_ptr(a[2], "b")); + assert_se(streq_ptr(a[3], NULL)); +} + +TEST(strv_compare) { + _cleanup_strv_free_ char **a = NULL; + _cleanup_strv_free_ char **b = NULL; + _cleanup_strv_free_ char **c = NULL; + _cleanup_strv_free_ char **d = NULL; + + a = strv_new("one", "two", "three"); + assert_se(a); + b = strv_new("one", "two", "three"); + assert_se(b); + c = strv_new("one", "two", "three", "four"); + assert_se(c); + d = strv_new(NULL); + assert_se(d); + + assert_se(strv_compare(a, a) == 0); + assert_se(strv_compare(a, b) == 0); + assert_se(strv_compare(d, d) == 0); + assert_se(strv_compare(d, NULL) == 0); + assert_se(strv_compare(NULL, NULL) == 0); + + assert_se(strv_compare(a, c) < 0); + assert_se(strv_compare(b, c) < 0); + assert_se(strv_compare(b, d) == 1); + assert_se(strv_compare(b, NULL) == 1); +} + +TEST(strv_is_uniq) { + _cleanup_strv_free_ char **a = NULL, **b = NULL, **c = NULL, **d = NULL; + + a = strv_new(NULL); + assert_se(a); + assert_se(strv_is_uniq(a)); + + b = strv_new("foo"); + assert_se(b); + assert_se(strv_is_uniq(b)); + + c = strv_new("foo", "bar"); + assert_se(c); + assert_se(strv_is_uniq(c)); + + d = strv_new("foo", "bar", "waldo", "bar", "piep"); + assert_se(d); + assert_se(!strv_is_uniq(d)); +} + +TEST(strv_reverse) { + _cleanup_strv_free_ char **a = NULL, **b = NULL, **c = NULL, **d = NULL; + + a = strv_new(NULL); + assert_se(a); + + strv_reverse(a); + assert_se(strv_isempty(a)); + + b = strv_new("foo"); + assert_se(b); + strv_reverse(b); + assert_se(streq_ptr(b[0], "foo")); + assert_se(streq_ptr(b[1], NULL)); + + c = strv_new("foo", "bar"); + assert_se(c); + strv_reverse(c); + assert_se(streq_ptr(c[0], "bar")); + assert_se(streq_ptr(c[1], "foo")); + assert_se(streq_ptr(c[2], NULL)); + + d = strv_new("foo", "bar", "waldo"); + assert_se(d); + strv_reverse(d); + assert_se(streq_ptr(d[0], "waldo")); + assert_se(streq_ptr(d[1], "bar")); + assert_se(streq_ptr(d[2], "foo")); + assert_se(streq_ptr(d[3], NULL)); +} + +TEST(strv_shell_escape) { + _cleanup_strv_free_ char **v = NULL; + + v = strv_new("foo:bar", "bar,baz", "wal\\do"); + assert_se(v); + assert_se(strv_shell_escape(v, ",:")); + assert_se(streq_ptr(v[0], "foo\\:bar")); + assert_se(streq_ptr(v[1], "bar\\,baz")); + assert_se(streq_ptr(v[2], "wal\\\\do")); + assert_se(streq_ptr(v[3], NULL)); +} + +static void test_strv_skip_one(char **a, size_t n, char **b) { + a = strv_skip(a, n); + assert_se(strv_equal(a, b)); +} + +TEST(strv_skip) { + test_strv_skip_one(STRV_MAKE("foo", "bar", "baz"), 0, STRV_MAKE("foo", "bar", "baz")); + test_strv_skip_one(STRV_MAKE("foo", "bar", "baz"), 1, STRV_MAKE("bar", "baz")); + test_strv_skip_one(STRV_MAKE("foo", "bar", "baz"), 2, STRV_MAKE("baz")); + test_strv_skip_one(STRV_MAKE("foo", "bar", "baz"), 3, STRV_MAKE(NULL)); + test_strv_skip_one(STRV_MAKE("foo", "bar", "baz"), 4, STRV_MAKE(NULL)); + test_strv_skip_one(STRV_MAKE("foo", "bar", "baz"), 55, STRV_MAKE(NULL)); + + test_strv_skip_one(STRV_MAKE("quux"), 0, STRV_MAKE("quux")); + test_strv_skip_one(STRV_MAKE("quux"), 1, STRV_MAKE(NULL)); + test_strv_skip_one(STRV_MAKE("quux"), 55, STRV_MAKE(NULL)); + + test_strv_skip_one(STRV_MAKE(NULL), 0, STRV_MAKE(NULL)); + test_strv_skip_one(STRV_MAKE(NULL), 1, STRV_MAKE(NULL)); + test_strv_skip_one(STRV_MAKE(NULL), 55, STRV_MAKE(NULL)); +} + +TEST(strv_extend_n) { + _cleanup_strv_free_ char **v = NULL; + + v = strv_new("foo", "bar"); + assert_se(v); + + assert_se(strv_extend_n(&v, "waldo", 3) >= 0); + assert_se(strv_extend_n(&v, "piep", 2) >= 0); + + assert_se(streq(v[0], "foo")); + assert_se(streq(v[1], "bar")); + assert_se(streq(v[2], "waldo")); + assert_se(streq(v[3], "waldo")); + assert_se(streq(v[4], "waldo")); + assert_se(streq(v[5], "piep")); + assert_se(streq(v[6], "piep")); + assert_se(v[7] == NULL); + + v = strv_free(v); + + assert_se(strv_extend_n(&v, "foo", 1) >= 0); + assert_se(strv_extend_n(&v, "bar", 0) >= 0); + + assert_se(streq(v[0], "foo")); + assert_se(v[1] == NULL); +} + +TEST(foreach_string) { + const char * const t[] = { + "foo", + "bar", + "waldo", + NULL + }; + + unsigned i = 0; + FOREACH_STRING(x, "foo", "bar", "waldo") + assert_se(streq_ptr(t[i++], x)); + assert_se(i == 3); + + FOREACH_STRING(x, "zzz") + assert_se(streq(x, "zzz")); +} + +TEST(strv_fnmatch) { + _cleanup_strv_free_ char **v = NULL; + size_t pos; + + assert_se(!strv_fnmatch(STRV_MAKE_EMPTY, "a")); + + v = strv_new("xxx", "*\\*", "yyy"); + assert_se(!strv_fnmatch_full(v, "\\", 0, NULL)); + assert_se(strv_fnmatch_full(v, "\\", FNM_NOESCAPE, &pos)); + assert_se(pos == 1); +} + +TEST(strv_extend_join) { + _cleanup_strv_free_ char **v = NULL; + + assert_se(strv_extend_assignment(&v, "MESSAGE", "ABC") >= 0); + assert_se(strv_extend_assignment(&v, "ABC", "QER") >= 0); + assert_se(strv_extend_assignment(&v, "MISSING", NULL) >= 0); + + assert_se(strv_length(v) == 2); + assert_se(streq(v[0], "MESSAGE=ABC")); + assert_se(streq(v[1], "ABC=QER")); +} + +TEST(strv_copy_n) { + char **x = STRV_MAKE("a", "b", "c", "d", "e"); + _cleanup_strv_free_ char **l = NULL; + + l = strv_copy_n(x, 0); + assert_se(strv_equal(l, NULL)); + strv_free(l); + + l = strv_copy_n(x, 0); + assert_se(strv_equal(l, (char**) { NULL })); + strv_free(l); + + l = strv_copy_n(x, 1); + assert_se(strv_equal(l, STRV_MAKE("a"))); + strv_free(l); + + l = strv_copy_n(x, 2); + assert_se(strv_equal(l, STRV_MAKE("a", "b"))); + strv_free(l); + + l = strv_copy_n(x, 3); + assert_se(strv_equal(l, STRV_MAKE("a", "b", "c"))); + strv_free(l); + + l = strv_copy_n(x, 4); + assert_se(strv_equal(l, STRV_MAKE("a", "b", "c", "d"))); + strv_free(l); + + l = strv_copy_n(x, 5); + assert_se(strv_equal(l, STRV_MAKE("a", "b", "c", "d", "e"))); + strv_free(l); + + l = strv_copy_n(x, 6); + assert_se(strv_equal(l, STRV_MAKE("a", "b", "c", "d", "e"))); + strv_free(l); + + l = strv_copy_n(x, SIZE_MAX); + assert_se(strv_equal(l, STRV_MAKE("a", "b", "c", "d", "e"))); +} + +TEST(strv_find_first_field) { + char **haystack = STRV_MAKE("a", "b", "c", "d", "e", "f", "g", "h", "i", "j"); + + assert_se(strv_find_first_field(NULL, NULL) == NULL); + assert_se(strv_find_first_field(NULL, haystack) == NULL); + assert_se(strv_find_first_field(STRV_MAKE("k", "l", "m", "d", "b"), NULL) == NULL); + assert_se(strv_find_first_field(STRV_MAKE("k", "l", "m", "d", "b"), haystack) == NULL); + assert_se(streq_ptr(strv_find_first_field(STRV_MAKE("k", "l", "m", "d", "a", "c"), haystack), "b")); + assert_se(streq_ptr(strv_find_first_field(STRV_MAKE("k", "l", "m", "d", "c", "a"), haystack), "d")); + assert_se(streq_ptr(strv_find_first_field(STRV_MAKE("i", "k", "l", "m", "d", "c", "a", "b"), haystack), "j")); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-strxcpyx.c b/src/test/test-strxcpyx.c new file mode 100644 index 0000000..b679522 --- /dev/null +++ b/src/test/test-strxcpyx.c @@ -0,0 +1,175 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdio.h> + +#include "string-util.h" +#include "strxcpyx.h" +#include "tests.h" + +TEST(strpcpy) { + char target[25]; + char *s = target; + size_t space_left; + bool truncated; + + space_left = sizeof(target); + space_left = strpcpy_full(&s, space_left, "12345", &truncated); + assert_se(!truncated); + space_left = strpcpy_full(&s, space_left, "hey hey hey", &truncated); + assert_se(!truncated); + space_left = strpcpy_full(&s, space_left, "waldo", &truncated); + assert_se(!truncated); + space_left = strpcpy_full(&s, space_left, "ba", &truncated); + assert_se(!truncated); + space_left = strpcpy_full(&s, space_left, "r", &truncated); + assert_se(!truncated); + assert_se(space_left == 1); + assert_se(streq(target, "12345hey hey heywaldobar")); + + space_left = strpcpy_full(&s, space_left, "", &truncated); + assert_se(!truncated); + assert_se(space_left == 1); + assert_se(streq(target, "12345hey hey heywaldobar")); + + space_left = strpcpy_full(&s, space_left, "f", &truncated); + assert_se(truncated); + assert_se(space_left == 0); + assert_se(streq(target, "12345hey hey heywaldobar")); + + space_left = strpcpy_full(&s, space_left, "", &truncated); + assert_se(!truncated); + assert_se(space_left == 0); + assert_se(streq(target, "12345hey hey heywaldobar")); + + space_left = strpcpy_full(&s, space_left, "foo", &truncated); + assert_se(truncated); + assert_se(space_left == 0); + assert_se(streq(target, "12345hey hey heywaldobar")); +} + +TEST(strpcpyf) { + char target[25]; + char *s = target; + size_t space_left; + bool truncated; + + space_left = sizeof(target); + space_left = strpcpyf_full(&s, space_left, &truncated, "space left: %zu. ", space_left); + assert_se(!truncated); + space_left = strpcpyf_full(&s, space_left, &truncated, "foo%s", "bar"); + assert_se(!truncated); + assert_se(space_left == 3); + assert_se(streq(target, "space left: 25. foobar")); + + space_left = strpcpyf_full(&s, space_left, &truncated, "%i", 42); + assert_se(!truncated); + assert_se(space_left == 1); + assert_se(streq(target, "space left: 25. foobar42")); + + space_left = strpcpyf_full(&s, space_left, &truncated, "%s", ""); + assert_se(!truncated); + assert_se(space_left == 1); + assert_se(streq(target, "space left: 25. foobar42")); + + space_left = strpcpyf_full(&s, space_left, &truncated, "%c", 'x'); + assert_se(truncated); + assert_se(space_left == 0); + assert_se(streq(target, "space left: 25. foobar42")); + + space_left = strpcpyf_full(&s, space_left, &truncated, "%s", ""); + assert_se(!truncated); + assert_se(space_left == 0); + assert_se(streq(target, "space left: 25. foobar42")); + + space_left = strpcpyf_full(&s, space_left, &truncated, "abc%s", "hoge"); + assert_se(truncated); + assert_se(space_left == 0); + assert_se(streq(target, "space left: 25. foobar42")); + + /* test overflow */ + s = target; + space_left = strpcpyf_full(&s, 12, &truncated, "00 left: %i. ", 999); + assert_se(truncated); + assert_se(streq(target, "00 left: 99")); + assert_se(space_left == 0); + assert_se(target[12] == '2'); +} + +TEST(strpcpyl) { + char target[25]; + char *s = target; + size_t space_left; + bool truncated; + + space_left = sizeof(target); + space_left = strpcpyl_full(&s, space_left, &truncated, "waldo", " test", " waldo. ", NULL); + assert_se(!truncated); + space_left = strpcpyl_full(&s, space_left, &truncated, "Banana", NULL); + assert_se(!truncated); + assert_se(space_left == 1); + assert_se(streq(target, "waldo test waldo. Banana")); + + space_left = strpcpyl_full(&s, space_left, &truncated, "", "", "", NULL); + assert_se(!truncated); + assert_se(space_left == 1); + assert_se(streq(target, "waldo test waldo. Banana")); + + space_left = strpcpyl_full(&s, space_left, &truncated, "", "x", "", NULL); + assert_se(truncated); + assert_se(space_left == 0); + assert_se(streq(target, "waldo test waldo. Banana")); + + space_left = strpcpyl_full(&s, space_left, &truncated, "hoge", NULL); + assert_se(truncated); + assert_se(space_left == 0); + assert_se(streq(target, "waldo test waldo. Banana")); +} + +TEST(strscpy) { + char target[25]; + size_t space_left; + bool truncated; + + space_left = sizeof(target); + space_left = strscpy_full(target, space_left, "12345", &truncated); + assert_se(!truncated); + + assert_se(streq(target, "12345")); + assert_se(space_left == 20); +} + +TEST(strscpyl) { + char target[25]; + size_t space_left; + bool truncated; + + space_left = sizeof(target); + space_left = strscpyl_full(target, space_left, &truncated, "12345", "waldo", "waldo", NULL); + assert_se(!truncated); + + assert_se(streq(target, "12345waldowaldo")); + assert_se(space_left == 10); +} + +TEST(sd_event_code_migration) { + char b[100 * DECIMAL_STR_MAX(unsigned) + 1]; + char c[100 * DECIMAL_STR_MAX(unsigned) + 1], *p; + unsigned i; + size_t l; + int o, r; + + for (i = o = 0; i < 100; i++) { + r = snprintf(&b[o], sizeof(b) - o, "%u ", i); + assert_se(r >= 0 && r < (int) sizeof(b) - o); + o += r; + } + + p = c; + l = sizeof(c); + for (i = 0; i < 100; i++) + l = strpcpyf(&p, l, "%u ", i); + + assert_se(streq(b, c)); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-sysctl-util.c b/src/test/test-sysctl-util.c new file mode 100644 index 0000000..81207f5 --- /dev/null +++ b/src/test/test-sysctl-util.c @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <sys/utsname.h> + +#include "sd-id128.h" + +#include "errno-util.h" +#include "hostname-util.h" +#include "strv.h" +#include "sysctl-util.h" +#include "tests.h" + +static const char* const cases[] = { + "a.b.c", "a/b/c", + "a/b/c", "a/b/c", + "a/b.c/d", "a/b.c/d", + "a.b/c.d", "a/b.c/d", + + "net.ipv4.conf.enp3s0/200.forwarding", "net/ipv4/conf/enp3s0.200/forwarding", + "net/ipv4/conf/enp3s0.200/forwarding", "net/ipv4/conf/enp3s0.200/forwarding", + + "a...b...c", "a/b/c", + "a///b///c", "a/b/c", + ".a...b...c", "a/b/c", + "/a///b///c", "a/b/c", + NULL, +}; + +TEST(sysctl_normalize) { + STRV_FOREACH_PAIR(s, expected, cases) { + _cleanup_free_ char *t; + + assert_se(t = strdup(*s)); + assert_se(sysctl_normalize(t) == t); + + log_info("\"%s\" → \"%s\", expected \"%s\"", *s, t, *expected); + assert_se(streq(t, *expected)); + } +} + +TEST(sysctl_read) { + _cleanup_free_ char *s = NULL; + struct utsname u; + sd_id128_t a, b; + int r; + + assert_se(sysctl_read("kernel/random/boot_id", &s) >= 0); + assert_se(sd_id128_from_string(s, &a) >= 0); + assert_se(sd_id128_get_boot(&b) >= 0); + assert_se(sd_id128_equal(a, b)); + s = mfree(s); + + assert_se(sysctl_read_ip_property(AF_INET, "lo", "forwarding", &s)); + assert_se(STR_IN_SET(s, "0", "1")); + + r = sysctl_write_ip_property(AF_INET, "lo", "forwarding", s); + assert_se(r >= 0 || ERRNO_IS_PRIVILEGE(r) || r == -EROFS); + s = mfree(s); + + assert_se(sysctl_read_ip_property(AF_INET, NULL, "ip_forward", &s)); + assert_se(STR_IN_SET(s, "0", "1")); + + r = sysctl_write_ip_property(AF_INET, NULL, "ip_forward", s); + assert_se(r >= 0 || ERRNO_IS_PRIVILEGE(r) || r == -EROFS); + s = mfree(s); + + assert_se(sysctl_read("kernel/hostname", &s) >= 0); + assert_se(uname(&u) >= 0); + assert_se(streq_ptr(s, u.nodename)); + + r = sysctl_write("kernel/hostname", s); + assert_se(r >= 0 || ERRNO_IS_PRIVILEGE(r) || r == -EROFS); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-tables.c b/src/test/test-tables.c new file mode 100644 index 0000000..8abfba5 --- /dev/null +++ b/src/test/test-tables.c @@ -0,0 +1,131 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "architecture.h" +#include "automount.h" +#include "cgroup.h" +#include "cgroup-util.h" +#include "compress.h" +#include "condition.h" +#include "confidential-virt.h" +#include "device-private.h" +#include "device.h" +#include "discover-image.h" +#include "execute.h" +#include "import-util.h" +#include "install.h" +#include "job.h" +#include "kill.h" +#include "locale-util.h" +#include "log.h" +#include "logs-show.h" +#include "mount.h" +#include "netif-naming-scheme.h" +#include "path.h" +#include "process-util.h" +#include "resolve-util.h" +#include "rlimit-util.h" +#include "scope.h" +#include "service.h" +#include "show-status.h" +#include "slice.h" +#include "socket-util.h" +#include "socket.h" +#include "swap.h" +#include "target.h" +#include "test-tables.h" +#include "tests.h" +#include "timer.h" +#include "unit-name.h" +#include "unit.h" +#include "virt.h" + +int main(int argc, char **argv) { + test_setup_logging(LOG_DEBUG); + + test_table(architecture, ARCHITECTURE); + test_table(assert_type, CONDITION_TYPE); + test_table(automount_result, AUTOMOUNT_RESULT); + test_table(automount_state, AUTOMOUNT_STATE); + test_table(cgroup_controller, CGROUP_CONTROLLER); + test_table(cgroup_device_policy, CGROUP_DEVICE_POLICY); + test_table(cgroup_io_limit_type, CGROUP_IO_LIMIT_TYPE); + test_table(collect_mode, COLLECT_MODE); + test_table(condition_result, CONDITION_RESULT); + test_table(condition_type, CONDITION_TYPE); + test_table(confidential_virtualization, CONFIDENTIAL_VIRTUALIZATION); + test_table(device_action, SD_DEVICE_ACTION); + test_table(device_state, DEVICE_STATE); + test_table(dns_over_tls_mode, DNS_OVER_TLS_MODE); + test_table(dnssec_mode, DNSSEC_MODE); + test_table(emergency_action, EMERGENCY_ACTION); + test_table(exec_directory_type, EXEC_DIRECTORY_TYPE); + test_table(exec_input, EXEC_INPUT); + test_table(exec_keyring_mode, EXEC_KEYRING_MODE); + test_table(exec_output, EXEC_OUTPUT); + test_table(exec_preserve_mode, EXEC_PRESERVE_MODE); + test_table(exec_utmp_mode, EXEC_UTMP_MODE); + test_table(image_type, IMAGE_TYPE); + test_table(import_verify, IMPORT_VERIFY); + test_table(job_mode, JOB_MODE); + test_table(job_result, JOB_RESULT); + test_table(job_state, JOB_STATE); + test_table(job_type, JOB_TYPE); + test_table(kill_mode, KILL_MODE); + test_table(kill_who, KILL_WHO); + test_table(locale_variable, VARIABLE_LC); + test_table(log_target, LOG_TARGET); + test_table(managed_oom_mode, MANAGED_OOM_MODE); + test_table(managed_oom_preference, MANAGED_OOM_PREFERENCE); + test_table(manager_state, MANAGER_STATE); + test_table(manager_timestamp, MANAGER_TIMESTAMP); + test_table(mount_exec_command, MOUNT_EXEC_COMMAND); + test_table(mount_result, MOUNT_RESULT); + test_table(mount_state, MOUNT_STATE); + test_table(name_policy, NAMEPOLICY); + test_table(namespace_type, NAMESPACE_TYPE); + test_table(notify_access, NOTIFY_ACCESS); + test_table(notify_state, NOTIFY_STATE); + test_table(output_mode, OUTPUT_MODE); + test_table(partition_designator, PARTITION_DESIGNATOR); + test_table(path_result, PATH_RESULT); + test_table(path_state, PATH_STATE); + test_table(path_type, PATH_TYPE); + test_table(protect_home, PROTECT_HOME); + test_table(protect_system, PROTECT_SYSTEM); + test_table(resolve_support, RESOLVE_SUPPORT); + test_table(rlimit, RLIMIT); + test_table(scope_result, SCOPE_RESULT); + test_table(scope_state, SCOPE_STATE); + test_table(service_exec_command, SERVICE_EXEC_COMMAND); + test_table(service_restart, SERVICE_RESTART); + test_table(service_restart_mode, SERVICE_RESTART_MODE); + test_table(service_result, SERVICE_RESULT); + test_table(service_state, SERVICE_STATE); + test_table(service_type, SERVICE_TYPE); + test_table(show_status, SHOW_STATUS); + test_table(slice_state, SLICE_STATE); + test_table(socket_address_bind_ipv6_only, SOCKET_ADDRESS_BIND_IPV6_ONLY); + test_table(socket_exec_command, SOCKET_EXEC_COMMAND); + test_table(socket_result, SOCKET_RESULT); + test_table(socket_state, SOCKET_STATE); + test_table(swap_exec_command, SWAP_EXEC_COMMAND); + test_table(swap_result, SWAP_RESULT); + test_table(swap_state, SWAP_STATE); + test_table(target_state, TARGET_STATE); + test_table(timer_base, TIMER_BASE); + test_table(timer_result, TIMER_RESULT); + test_table(timer_state, TIMER_STATE); + test_table(unit_active_state, UNIT_ACTIVE_STATE); + test_table(unit_dependency, UNIT_DEPENDENCY); + test_table(install_change_type, INSTALL_CHANGE_TYPE); + test_table(unit_file_preset_mode, UNIT_FILE_PRESET_MODE); + test_table(unit_file_state, UNIT_FILE_STATE); + test_table(unit_load_state, UNIT_LOAD_STATE); + test_table(unit_type, UNIT_TYPE); + test_table(virtualization, VIRTUALIZATION); + test_table(compression, COMPRESSION); + + assert_cc(sizeof(sd_device_action_t) == sizeof(int64_t)); + + return EXIT_SUCCESS; +} diff --git a/src/test/test-terminal-util.c b/src/test/test-terminal-util.c new file mode 100644 index 0000000..a2b7101 --- /dev/null +++ b/src/test/test-terminal-util.c @@ -0,0 +1,168 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <fcntl.h> +#include <stdbool.h> +#include <stdio.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "macro.h" +#include "path-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "tests.h" +#include "tmpfile-util.h" + +#define LOREM_IPSUM "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor " \ + "incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation " \ + "ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit " \ + "in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat " \ + "non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + +TEST(default_term_for_tty) { + puts(default_term_for_tty("/dev/tty23")); + puts(default_term_for_tty("/dev/ttyS23")); + puts(default_term_for_tty("/dev/tty0")); + puts(default_term_for_tty("/dev/pty0")); + puts(default_term_for_tty("/dev/pts/0")); + puts(default_term_for_tty("/dev/console")); + puts(default_term_for_tty("tty23")); + puts(default_term_for_tty("ttyS23")); + puts(default_term_for_tty("tty0")); + puts(default_term_for_tty("pty0")); + puts(default_term_for_tty("pts/0")); + puts(default_term_for_tty("console")); +} + +TEST(read_one_char) { + _cleanup_fclose_ FILE *file = NULL; + char r; + bool need_nl; + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-read_one_char.XXXXXX"; + + assert_se(fmkostemp_safe(name, "r+", &file) == 0); + + assert_se(fputs("c\n", file) >= 0); + rewind(file); + assert_se(read_one_char(file, &r, 1000000, &need_nl) >= 0); + assert_se(!need_nl); + assert_se(r == 'c'); + assert_se(read_one_char(file, &r, 1000000, &need_nl) < 0); + + rewind(file); + assert_se(fputs("foobar\n", file) >= 0); + rewind(file); + assert_se(read_one_char(file, &r, 1000000, &need_nl) < 0); + + rewind(file); + assert_se(fputs("\n", file) >= 0); + rewind(file); + assert_se(read_one_char(file, &r, 1000000, &need_nl) < 0); +} + +TEST(getttyname_malloc) { + _cleanup_free_ char *ttyname = NULL; + _cleanup_close_ int master = -EBADF; + + assert_se((master = posix_openpt(O_RDWR|O_NOCTTY)) >= 0); + assert_se(getttyname_malloc(master, &ttyname) >= 0); + log_info("ttyname = %s", ttyname); + + assert_se(PATH_IN_SET(ttyname, "ptmx", "pts/ptmx")); +} + +typedef struct { + const char *name; + const char* (*func)(void); +} Color; + +static const Color colors[] = { + { "normal", ansi_normal }, + { "highlight", ansi_highlight }, + { "black", ansi_black }, + { "red", ansi_red }, + { "green", ansi_green }, + { "yellow", ansi_yellow }, + { "blue", ansi_blue }, + { "magenta", ansi_magenta }, + { "cyan", ansi_cyan }, + { "white", ansi_white }, + { "grey", ansi_grey }, + + { "bright-black", ansi_bright_black }, + { "bright-red", ansi_bright_red }, + { "bright-green", ansi_bright_green }, + { "bright-yellow", ansi_bright_yellow }, + { "bright-blue", ansi_bright_blue }, + { "bright-magenta", ansi_bright_magenta }, + { "bright-cyan", ansi_bright_cyan }, + { "bright-white", ansi_bright_white }, + + { "highlight-black", ansi_highlight_black }, + { "highlight-red", ansi_highlight_red }, + { "highlight-green", ansi_highlight_green }, + { "highlight-yellow (original)", _ansi_highlight_yellow }, + { "highlight-yellow (replacement)", ansi_highlight_yellow }, + { "highlight-blue", ansi_highlight_blue }, + { "highlight-magenta", ansi_highlight_magenta }, + { "highlight-cyan", ansi_highlight_cyan }, + { "highlight-white", ansi_highlight_white }, + { "highlight-grey", ansi_highlight_grey }, + + { "underline", ansi_underline }, + { "highlight-underline", ansi_highlight_underline }, + { "highlight-red-underline", ansi_highlight_red_underline }, + { "highlight-green-underline", ansi_highlight_green_underline }, + { "highlight-yellow-underline", ansi_highlight_yellow_underline }, + { "highlight-blue-underline", ansi_highlight_blue_underline }, + { "highlight-magenta-underline", ansi_highlight_magenta_underline }, + { "highlight-grey-underline", ansi_highlight_grey_underline }, +}; + +TEST(colors) { + for (size_t i = 0; i < ELEMENTSOF(colors); i++) + printf("<%s%s%s>\n", colors[i].func(), colors[i].name, ansi_normal()); +} + +TEST(text) { + for (size_t i = 0; !streq(colors[i].name, "underline"); i++) { + bool blwh = strstr(colors[i].name, "black") + || strstr(colors[i].name, "white"); + + printf("\n" + "Testing color %s%s\n%s%s%s\n", + colors[i].name, + blwh ? "" : ", this text should be readable", + colors[i].func(), + LOREM_IPSUM, + ansi_normal()); + } +} + +TEST(get_ctty) { + _cleanup_free_ char *ctty = NULL; + struct stat st; + dev_t devnr; + int r; + + r = get_ctty(0, &devnr, &ctty); + if (r < 0) { + log_notice_errno(r, "Apparently called without a controlling TTY, cutting get_ctty() test short: %m"); + return; + } + + /* In almost all cases STDIN will match our controlling TTY. Let's verify that and then compare paths */ + assert_se(fstat(STDIN_FILENO, &st) >= 0); + if (S_ISCHR(st.st_mode) && st.st_rdev == devnr) { + _cleanup_free_ char *stdin_name = NULL; + + assert_se(getttyname_malloc(STDIN_FILENO, &stdin_name) >= 0); + assert_se(path_equal(stdin_name, ctty)); + } else + log_notice("Not invoked with stdin == ctty, cutting get_ctty() test short"); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c new file mode 100644 index 0000000..53bc779 --- /dev/null +++ b/src/test/test-time-util.c @@ -0,0 +1,1195 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "env-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "random-util.h" +#include "serialize.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" +#include "time-util.h" + +#define TRIAL 100u + +TEST(parse_sec) { + usec_t u; + + assert_se(parse_sec("5s", &u) >= 0); + assert_se(u == 5 * USEC_PER_SEC); + assert_se(parse_sec("5s500ms", &u) >= 0); + assert_se(u == 5 * USEC_PER_SEC + 500 * USEC_PER_MSEC); + assert_se(parse_sec(" 5s 500ms ", &u) >= 0); + assert_se(u == 5 * USEC_PER_SEC + 500 * USEC_PER_MSEC); + assert_se(parse_sec(" 5.5s ", &u) >= 0); + assert_se(u == 5 * USEC_PER_SEC + 500 * USEC_PER_MSEC); + assert_se(parse_sec(" 5.5s 0.5ms ", &u) >= 0); + assert_se(u == 5 * USEC_PER_SEC + 500 * USEC_PER_MSEC + 500); + assert_se(parse_sec(" .22s ", &u) >= 0); + assert_se(u == 220 * USEC_PER_MSEC); + assert_se(parse_sec(" .50y ", &u) >= 0); + assert_se(u == USEC_PER_YEAR / 2); + assert_se(parse_sec("2.5", &u) >= 0); + assert_se(u == 2500 * USEC_PER_MSEC); + assert_se(parse_sec(".7", &u) >= 0); + assert_se(u == 700 * USEC_PER_MSEC); + assert_se(parse_sec("23us", &u) >= 0); + assert_se(u == 23); + assert_se(parse_sec("23μs", &u) >= 0); /* greek small letter mu */ + assert_se(u == 23); + assert_se(parse_sec("23µs", &u) >= 0); /* micro symbol */ + assert_se(u == 23); + assert_se(parse_sec("infinity", &u) >= 0); + assert_se(u == USEC_INFINITY); + assert_se(parse_sec(" infinity ", &u) >= 0); + assert_se(u == USEC_INFINITY); + assert_se(parse_sec("+3.1s", &u) >= 0); + assert_se(u == 3100 * USEC_PER_MSEC); + assert_se(parse_sec("3.1s.2", &u) >= 0); + assert_se(u == 3300 * USEC_PER_MSEC); + assert_se(parse_sec("3.1 .2", &u) >= 0); + assert_se(u == 3300 * USEC_PER_MSEC); + assert_se(parse_sec("3.1 sec .2 sec", &u) >= 0); + assert_se(u == 3300 * USEC_PER_MSEC); + assert_se(parse_sec("3.1 sec 1.2 sec", &u) >= 0); + assert_se(u == 4300 * USEC_PER_MSEC); + + assert_se(parse_sec(" xyz ", &u) < 0); + assert_se(parse_sec("", &u) < 0); + assert_se(parse_sec(" . ", &u) < 0); + assert_se(parse_sec(" 5. ", &u) < 0); + assert_se(parse_sec(".s ", &u) < 0); + assert_se(parse_sec("-5s ", &u) < 0); + assert_se(parse_sec("-0.3s ", &u) < 0); + assert_se(parse_sec("-0.0s ", &u) < 0); + assert_se(parse_sec("-0.-0s ", &u) < 0); + assert_se(parse_sec("0.-0s ", &u) < 0); + assert_se(parse_sec("3.-0s ", &u) < 0); + assert_se(parse_sec(" infinity .7", &u) < 0); + assert_se(parse_sec(".3 infinity", &u) < 0); + assert_se(parse_sec("3.+1s", &u) < 0); + assert_se(parse_sec("3. 1s", &u) < 0); + assert_se(parse_sec("3.s", &u) < 0); + assert_se(parse_sec("12.34.56", &u) < 0); + assert_se(parse_sec("12..34", &u) < 0); + assert_se(parse_sec("..1234", &u) < 0); + assert_se(parse_sec("1234..", &u) < 0); +} + +TEST(parse_sec_fix_0) { + usec_t u; + + assert_se(parse_sec_fix_0("5s", &u) >= 0); + assert_se(u == 5 * USEC_PER_SEC); + assert_se(parse_sec_fix_0("0s", &u) >= 0); + assert_se(u == USEC_INFINITY); + assert_se(parse_sec_fix_0("0", &u) >= 0); + assert_se(u == USEC_INFINITY); + assert_se(parse_sec_fix_0(" 0", &u) >= 0); + assert_se(u == USEC_INFINITY); +} + +TEST(parse_sec_def_infinity) { + usec_t u; + + assert_se(parse_sec_def_infinity("5s", &u) >= 0); + assert_se(u == 5 * USEC_PER_SEC); + assert_se(parse_sec_def_infinity("", &u) >= 0); + assert_se(u == USEC_INFINITY); + assert_se(parse_sec_def_infinity(" ", &u) >= 0); + assert_se(u == USEC_INFINITY); + assert_se(parse_sec_def_infinity("0s", &u) >= 0); + assert_se(u == 0); + assert_se(parse_sec_def_infinity("0", &u) >= 0); + assert_se(u == 0); + assert_se(parse_sec_def_infinity(" 0", &u) >= 0); + assert_se(u == 0); + assert_se(parse_sec_def_infinity("-5s", &u) < 0); +} + +TEST(parse_time) { + usec_t u; + + assert_se(parse_time("5", &u, 1) >= 0); + assert_se(u == 5); + + assert_se(parse_time("5", &u, USEC_PER_MSEC) >= 0); + assert_se(u == 5 * USEC_PER_MSEC); + + assert_se(parse_time("5", &u, USEC_PER_SEC) >= 0); + assert_se(u == 5 * USEC_PER_SEC); + + assert_se(parse_time("5s", &u, 1) >= 0); + assert_se(u == 5 * USEC_PER_SEC); + + assert_se(parse_time("5s", &u, USEC_PER_SEC) >= 0); + assert_se(u == 5 * USEC_PER_SEC); + + assert_se(parse_time("5s", &u, USEC_PER_MSEC) >= 0); + assert_se(u == 5 * USEC_PER_SEC); + + assert_se(parse_time("11111111111111y", &u, 1) == -ERANGE); + assert_se(parse_time("1.1111111111111y", &u, 1) >= 0); +} + +TEST(parse_nsec) { + nsec_t u; + + assert_se(parse_nsec("5s", &u) >= 0); + assert_se(u == 5 * NSEC_PER_SEC); + assert_se(parse_nsec("5s500ms", &u) >= 0); + assert_se(u == 5 * NSEC_PER_SEC + 500 * NSEC_PER_MSEC); + assert_se(parse_nsec(" 5s 500ms ", &u) >= 0); + assert_se(u == 5 * NSEC_PER_SEC + 500 * NSEC_PER_MSEC); + assert_se(parse_nsec(" 5.5s ", &u) >= 0); + assert_se(u == 5 * NSEC_PER_SEC + 500 * NSEC_PER_MSEC); + assert_se(parse_nsec(" 5.5s 0.5ms ", &u) >= 0); + assert_se(u == 5 * NSEC_PER_SEC + 500 * NSEC_PER_MSEC + 500 * NSEC_PER_USEC); + assert_se(parse_nsec(" .22s ", &u) >= 0); + assert_se(u == 220 * NSEC_PER_MSEC); + assert_se(parse_nsec(" .50y ", &u) >= 0); + assert_se(u == NSEC_PER_YEAR / 2); + assert_se(parse_nsec("2.5", &u) >= 0); + assert_se(u == 2); + assert_se(parse_nsec(".7", &u) >= 0); + assert_se(u == 0); + assert_se(parse_nsec("infinity", &u) >= 0); + assert_se(u == NSEC_INFINITY); + assert_se(parse_nsec(" infinity ", &u) >= 0); + assert_se(u == NSEC_INFINITY); + assert_se(parse_nsec("+3.1s", &u) >= 0); + assert_se(u == 3100 * NSEC_PER_MSEC); + assert_se(parse_nsec("3.1s.2", &u) >= 0); + assert_se(u == 3100 * NSEC_PER_MSEC); + assert_se(parse_nsec("3.1 .2s", &u) >= 0); + assert_se(u == 200 * NSEC_PER_MSEC + 3); + assert_se(parse_nsec("3.1 sec .2 sec", &u) >= 0); + assert_se(u == 3300 * NSEC_PER_MSEC); + assert_se(parse_nsec("3.1 sec 1.2 sec", &u) >= 0); + assert_se(u == 4300 * NSEC_PER_MSEC); + + assert_se(parse_nsec(" xyz ", &u) < 0); + assert_se(parse_nsec("", &u) < 0); + assert_se(parse_nsec(" . ", &u) < 0); + assert_se(parse_nsec(" 5. ", &u) < 0); + assert_se(parse_nsec(".s ", &u) < 0); + assert_se(parse_nsec(" infinity .7", &u) < 0); + assert_se(parse_nsec(".3 infinity", &u) < 0); + assert_se(parse_nsec("-5s ", &u) < 0); + assert_se(parse_nsec("-0.3s ", &u) < 0); + assert_se(parse_nsec("-0.0s ", &u) < 0); + assert_se(parse_nsec("-0.-0s ", &u) < 0); + assert_se(parse_nsec("0.-0s ", &u) < 0); + assert_se(parse_nsec("3.-0s ", &u) < 0); + assert_se(parse_nsec(" infinity .7", &u) < 0); + assert_se(parse_nsec(".3 infinity", &u) < 0); + assert_se(parse_nsec("3.+1s", &u) < 0); + assert_se(parse_nsec("3. 1s", &u) < 0); + assert_se(parse_nsec("3.s", &u) < 0); + assert_se(parse_nsec("12.34.56", &u) < 0); + assert_se(parse_nsec("12..34", &u) < 0); + assert_se(parse_nsec("..1234", &u) < 0); + assert_se(parse_nsec("1234..", &u) < 0); + assert_se(parse_nsec("1111111111111y", &u) == -ERANGE); + assert_se(parse_nsec("1.111111111111y", &u) >= 0); +} + +static void test_format_timespan_one(usec_t x, usec_t accuracy) { + char l[FORMAT_TIMESPAN_MAX]; + const char *t; + usec_t y; + + log_debug(USEC_FMT" (at accuracy "USEC_FMT")", x, accuracy); + + assert_se(t = format_timespan(l, sizeof l, x, accuracy)); + log_debug(" = <%s>", t); + + assert_se(parse_sec(t, &y) >= 0); + log_debug(" = "USEC_FMT, y); + + if (accuracy <= 0) + accuracy = 1; + + assert_se(x / accuracy == y / accuracy); +} + +static void test_format_timespan_accuracy(usec_t accuracy) { + log_info("/* %s accuracy="USEC_FMT" */", __func__, accuracy); + + test_format_timespan_one(0, accuracy); + test_format_timespan_one(1, accuracy); + test_format_timespan_one(1*USEC_PER_SEC, accuracy); + test_format_timespan_one(999*USEC_PER_MSEC, accuracy); + test_format_timespan_one(1234567, accuracy); + test_format_timespan_one(12, accuracy); + test_format_timespan_one(123, accuracy); + test_format_timespan_one(1234, accuracy); + test_format_timespan_one(12345, accuracy); + test_format_timespan_one(123456, accuracy); + test_format_timespan_one(1234567, accuracy); + test_format_timespan_one(12345678, accuracy); + test_format_timespan_one(1200000, accuracy); + test_format_timespan_one(1230000, accuracy); + test_format_timespan_one(1234000, accuracy); + test_format_timespan_one(1234500, accuracy); + test_format_timespan_one(1234560, accuracy); + test_format_timespan_one(1234567, accuracy); + test_format_timespan_one(986087, accuracy); + test_format_timespan_one(500 * USEC_PER_MSEC, accuracy); + test_format_timespan_one(9*USEC_PER_YEAR/5 - 23, accuracy); + test_format_timespan_one(USEC_INFINITY, accuracy); +} + +TEST(format_timespan) { + test_format_timespan_accuracy(1); + test_format_timespan_accuracy(USEC_PER_MSEC); + test_format_timespan_accuracy(USEC_PER_SEC); + + /* See issue #23928. */ + _cleanup_free_ char *buf = NULL; + assert_se(buf = new(char, 5)); + assert_se(buf == format_timespan(buf, 5, 100005, 1000)); +} + +TEST(verify_timezone) { + assert_se(verify_timezone("Europe/Berlin", LOG_DEBUG) == 0); + assert_se(verify_timezone("Australia/Sydney", LOG_DEBUG) == 0); + assert_se(verify_timezone("Europe/Do not exist", LOG_DEBUG) == -EINVAL); + assert_se(verify_timezone("Europe/DoNotExist", LOG_DEBUG) == -ENOENT); + assert_se(verify_timezone("/DoNotExist", LOG_DEBUG) == -EINVAL); + assert_se(verify_timezone("DoNotExist/", LOG_DEBUG) == -EINVAL); +} + +TEST(timezone_is_valid) { + assert_se(timezone_is_valid("Europe/Berlin", LOG_ERR)); + assert_se(timezone_is_valid("Australia/Sydney", LOG_ERR)); + assert_se(!timezone_is_valid("Europe/Do not exist", LOG_ERR)); +} + +TEST(get_timezones) { + _cleanup_strv_free_ char **zones = NULL; + int r; + + r = get_timezones(&zones); + assert_se(r == 0); + + STRV_FOREACH(zone, zones) { + r = verify_timezone(*zone, LOG_ERR); + log_debug_errno(r, "verify_timezone(\"%s\"): %m", *zone); + assert_se(r >= 0 || r == -ENOENT); + } +} + +TEST(usec_add) { + assert_se(usec_add(0, 0) == 0); + assert_se(usec_add(1, 4) == 5); + assert_se(usec_add(USEC_INFINITY, 5) == USEC_INFINITY); + assert_se(usec_add(5, USEC_INFINITY) == USEC_INFINITY); + assert_se(usec_add(USEC_INFINITY-5, 2) == USEC_INFINITY-3); + assert_se(usec_add(USEC_INFINITY-2, 2) == USEC_INFINITY); + assert_se(usec_add(USEC_INFINITY-1, 2) == USEC_INFINITY); + assert_se(usec_add(USEC_INFINITY, 2) == USEC_INFINITY); +} + +TEST(usec_sub_unsigned) { + assert_se(usec_sub_unsigned(0, 0) == 0); + assert_se(usec_sub_unsigned(0, 2) == 0); + assert_se(usec_sub_unsigned(0, USEC_INFINITY) == 0); + assert_se(usec_sub_unsigned(1, 0) == 1); + assert_se(usec_sub_unsigned(1, 1) == 0); + assert_se(usec_sub_unsigned(1, 2) == 0); + assert_se(usec_sub_unsigned(1, 3) == 0); + assert_se(usec_sub_unsigned(1, USEC_INFINITY) == 0); + assert_se(usec_sub_unsigned(USEC_INFINITY-1, 0) == USEC_INFINITY-1); + assert_se(usec_sub_unsigned(USEC_INFINITY-1, 1) == USEC_INFINITY-2); + assert_se(usec_sub_unsigned(USEC_INFINITY-1, 2) == USEC_INFINITY-3); + assert_se(usec_sub_unsigned(USEC_INFINITY-1, USEC_INFINITY-2) == 1); + assert_se(usec_sub_unsigned(USEC_INFINITY-1, USEC_INFINITY-1) == 0); + assert_se(usec_sub_unsigned(USEC_INFINITY-1, USEC_INFINITY) == 0); + assert_se(usec_sub_unsigned(USEC_INFINITY, 0) == USEC_INFINITY); + assert_se(usec_sub_unsigned(USEC_INFINITY, 1) == USEC_INFINITY); + assert_se(usec_sub_unsigned(USEC_INFINITY, 2) == USEC_INFINITY); + assert_se(usec_sub_unsigned(USEC_INFINITY, USEC_INFINITY) == USEC_INFINITY); +} + +TEST(usec_sub_signed) { + assert_se(usec_sub_signed(0, 0) == 0); + assert_se(usec_sub_signed(4, 1) == 3); + assert_se(usec_sub_signed(4, 4) == 0); + assert_se(usec_sub_signed(4, 5) == 0); + + assert_se(usec_sub_signed(USEC_INFINITY-3, -3) == USEC_INFINITY); + assert_se(usec_sub_signed(USEC_INFINITY-3, -4) == USEC_INFINITY); + assert_se(usec_sub_signed(USEC_INFINITY-3, -5) == USEC_INFINITY); + assert_se(usec_sub_signed(USEC_INFINITY, 5) == USEC_INFINITY); + + assert_se(usec_sub_signed(0, INT64_MAX) == 0); + assert_se(usec_sub_signed(0, -INT64_MAX) == INT64_MAX); + assert_se(usec_sub_signed(0, INT64_MIN) == (usec_t) INT64_MAX + 1); + assert_se(usec_sub_signed(0, -(INT64_MIN+1)) == 0); + + assert_se(usec_sub_signed(USEC_INFINITY, INT64_MAX) == USEC_INFINITY); + assert_se(usec_sub_signed(USEC_INFINITY, -INT64_MAX) == USEC_INFINITY); + assert_se(usec_sub_signed(USEC_INFINITY, INT64_MIN) == USEC_INFINITY); + assert_se(usec_sub_signed(USEC_INFINITY, -(INT64_MIN+1)) == USEC_INFINITY); + + assert_se(usec_sub_signed(USEC_INFINITY-1, INT64_MAX) == USEC_INFINITY-1-INT64_MAX); + assert_se(usec_sub_signed(USEC_INFINITY-1, -INT64_MAX) == USEC_INFINITY); + assert_se(usec_sub_signed(USEC_INFINITY-1, INT64_MIN) == USEC_INFINITY); + assert_se(usec_sub_signed(USEC_INFINITY-1, -(INT64_MIN+1)) == USEC_INFINITY-1-((usec_t) (-(INT64_MIN+1)))); +} + +TEST(format_timestamp) { + for (unsigned i = 0; i < TRIAL; i++) { + char buf[CONST_MAX(FORMAT_TIMESTAMP_MAX, FORMAT_TIMESPAN_MAX)]; + usec_t x, y; + + x = random_u64_range(USEC_TIMESTAMP_FORMATTABLE_MAX - USEC_PER_SEC) + USEC_PER_SEC; + + assert_se(format_timestamp(buf, sizeof(buf), x)); + log_debug("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + assert_se(x / USEC_PER_SEC == y / USEC_PER_SEC); + + assert_se(format_timestamp_style(buf, sizeof(buf), x, TIMESTAMP_UNIX)); + log_debug("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + assert_se(x / USEC_PER_SEC == y / USEC_PER_SEC); + + assert_se(format_timestamp_style(buf, sizeof(buf), x, TIMESTAMP_UTC)); + log_debug("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + assert_se(x / USEC_PER_SEC == y / USEC_PER_SEC); + + assert_se(format_timestamp_style(buf, sizeof(buf), x, TIMESTAMP_US)); + log_debug("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + assert_se(x == y); + + assert_se(format_timestamp_style(buf, sizeof(buf), x, TIMESTAMP_US_UTC)); + log_debug("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + assert_se(x == y); + + if (x > 2 * USEC_PER_DAY) { + assert_se(format_timestamp_style(buf, sizeof(buf), x, TIMESTAMP_DATE)); + log_debug("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + assert_se(y > usec_sub_unsigned(x, 2 * USEC_PER_DAY) && y < usec_add(x, 2 * USEC_PER_DAY)); + } + + assert_se(format_timestamp_relative(buf, sizeof(buf), x)); + log_debug("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + + /* The two calls above will run with a slightly different local time. Make sure we are in the same + * range however, but give enough leeway that this is unlikely to explode. And of course, + * format_timestamp_relative() scales the accuracy with the distance from the current time up to one + * month, cover for that too. */ + assert_se(y > x ? y - x : x - y <= USEC_PER_MONTH + USEC_PER_DAY); + } +} + +static void test_format_timestamp_impl(usec_t x) { + bool success, override; + const char *xx, *yy; + usec_t y; + + xx = FORMAT_TIMESTAMP(x); + assert_se(xx); + assert_se(parse_timestamp(xx, &y) >= 0); + yy = FORMAT_TIMESTAMP(y); + assert_se(yy); + + success = (x / USEC_PER_SEC == y / USEC_PER_SEC) && streq(xx, yy); + /* Workaround for https://github.com/systemd/systemd/issues/28472 */ + override = !success && + (STRPTR_IN_SET(tzname[0], "CAT", "EAT") || + STRPTR_IN_SET(tzname[1], "CAT", "EAT")) && + DIV_ROUND_UP(y - x, USEC_PER_SEC) == 3600; /* 1 hour, ignore fractional second */ + log_full(success ? LOG_DEBUG : override ? LOG_WARNING : LOG_ERR, + "@" USEC_FMT " → %s → @" USEC_FMT " → %s%s", + x, xx, y, yy, + override ? ", ignoring." : ""); + if (!override) { + assert_se(x / USEC_PER_SEC == y / USEC_PER_SEC); + assert_se(streq(xx, yy)); + } +} + +static void test_format_timestamp_loop(void) { + test_format_timestamp_impl(USEC_PER_SEC); + test_format_timestamp_impl(USEC_TIMESTAMP_FORMATTABLE_MAX_32BIT-1); + test_format_timestamp_impl(USEC_TIMESTAMP_FORMATTABLE_MAX_32BIT); + test_format_timestamp_impl(USEC_TIMESTAMP_FORMATTABLE_MAX-1); + test_format_timestamp_impl(USEC_TIMESTAMP_FORMATTABLE_MAX); + + /* Two cases which trigger https://github.com/systemd/systemd/issues/28472 */ + test_format_timestamp_impl(1504938962980066); + test_format_timestamp_impl(1509482094632752); + + for (unsigned i = 0; i < TRIAL; i++) { + usec_t x; + + x = random_u64_range(USEC_TIMESTAMP_FORMATTABLE_MAX - USEC_PER_SEC) + USEC_PER_SEC; + test_format_timestamp_impl(x); + } +} + +TEST(FORMAT_TIMESTAMP) { + test_format_timestamp_loop(); +} + +static void test_format_timestamp_with_tz_one(const char *tz) { + const char *saved_tz, *colon_tz; + + if (!timezone_is_valid(tz, LOG_DEBUG)) + return; + + log_info("/* %s(%s) */", __func__, tz); + + saved_tz = getenv("TZ"); + + assert_se(colon_tz = strjoina(":", tz)); + assert_se(setenv("TZ", colon_tz, 1) >= 0); + tzset(); + log_debug("%s: tzname[0]=%s, tzname[1]=%s", tz, strempty(tzname[0]), strempty(tzname[1])); + + test_format_timestamp_loop(); + + assert_se(set_unset_env("TZ", saved_tz, true) == 0); + tzset(); +} + +TEST(FORMAT_TIMESTAMP_with_tz) { + _cleanup_strv_free_ char **timezones = NULL; + + test_format_timestamp_with_tz_one("UTC"); + + if (!slow_tests_enabled()) + return (void) log_tests_skipped("slow tests are disabled"); + + assert_se(get_timezones(&timezones) >= 0); + STRV_FOREACH(tz, timezones) + test_format_timestamp_with_tz_one(*tz); +} + +TEST(format_timestamp_relative_full) { + char buf[CONST_MAX(FORMAT_TIMESTAMP_MAX, FORMAT_TIMESPAN_MAX)]; + usec_t x; + + /* Years and months */ + x = now(CLOCK_REALTIME) - (1*USEC_PER_YEAR + 1*USEC_PER_MONTH); + assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_REALTIME, true)); + log_debug("%s", buf); + assert_se(streq(buf, "1 year 1 month ago")); + + x = now(CLOCK_MONOTONIC) + (1*USEC_PER_YEAR + 1.5*USEC_PER_MONTH); + assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_MONOTONIC, false)); + log_debug("%s", buf); + assert_se(streq(buf, "1 year 1 month left")); + + x = now(CLOCK_REALTIME) - (1*USEC_PER_YEAR + 2*USEC_PER_MONTH); + assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_REALTIME, true)); + log_debug("%s", buf); + assert_se(streq(buf, "1 year 2 months ago")); + + x = now(CLOCK_REALTIME) - (2*USEC_PER_YEAR + 1*USEC_PER_MONTH); + assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_REALTIME, true)); + log_debug("%s", buf); + assert_se(streq(buf, "2 years 1 month ago")); + + x = now(CLOCK_REALTIME) - (2*USEC_PER_YEAR + 2*USEC_PER_MONTH); + assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_REALTIME, true)); + log_debug("%s", buf); + assert_se(streq(buf, "2 years 2 months ago")); + + /* Months and days */ + x = now(CLOCK_REALTIME) - (1*USEC_PER_MONTH + 1*USEC_PER_DAY); + assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_REALTIME, true)); + log_debug("%s", buf); + assert_se(streq(buf, "1 month 1 day ago")); + + x = now(CLOCK_REALTIME) - (1*USEC_PER_MONTH + 2*USEC_PER_DAY); + assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_REALTIME, true)); + log_debug("%s", buf); + assert_se(streq(buf, "1 month 2 days ago")); + + x = now(CLOCK_REALTIME) - (2*USEC_PER_MONTH + 1*USEC_PER_DAY); + assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_REALTIME, true)); + log_debug("%s", buf); + assert_se(streq(buf, "2 months 1 day ago")); + + x = now(CLOCK_REALTIME) - (2*USEC_PER_MONTH + 2*USEC_PER_DAY); + assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_REALTIME, true)); + log_debug("%s", buf); + assert_se(streq(buf, "2 months 2 days ago")); + + /* Weeks and days */ + x = now(CLOCK_REALTIME) - (1*USEC_PER_WEEK + 1*USEC_PER_DAY); + assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_REALTIME, true)); + log_debug("%s", buf); + assert_se(streq(buf, "1 week 1 day ago")); + + x = now(CLOCK_REALTIME) - (1*USEC_PER_WEEK + 2*USEC_PER_DAY); + assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_REALTIME, true)); + log_debug("%s", buf); + assert_se(streq(buf, "1 week 2 days ago")); + + x = now(CLOCK_REALTIME) - (2*USEC_PER_WEEK + 1*USEC_PER_DAY); + assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_REALTIME, true)); + log_debug("%s", buf); + assert_se(streq(buf, "2 weeks 1 day ago")); + + x = now(CLOCK_REALTIME) - (2*USEC_PER_WEEK + 2*USEC_PER_DAY); + assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_REALTIME, true)); + log_debug("%s", buf); + assert_se(streq(buf, "2 weeks 2 days ago")); +} + +TEST(format_timestamp_relative) { + char buf[CONST_MAX(FORMAT_TIMESTAMP_MAX, FORMAT_TIMESPAN_MAX)]; + usec_t x; + + /* Only testing timestamps in the past so we don't need to add some delta to account for time passing + * by while we are running the tests (unless we're running on potatoes and 24 hours somehow passes + * between our call to now() and format_timestamp_relative's call to now()). */ + + /* Years and months */ + x = now(CLOCK_REALTIME) - (1*USEC_PER_YEAR + 1*USEC_PER_MONTH); + assert_se(format_timestamp_relative(buf, sizeof(buf), x)); + log_debug("%s", buf); + assert_se(streq(buf, "1 year 1 month ago")); + + x = now(CLOCK_REALTIME) - (1*USEC_PER_YEAR + 2*USEC_PER_MONTH); + assert_se(format_timestamp_relative(buf, sizeof(buf), x)); + log_debug("%s", buf); + assert_se(streq(buf, "1 year 2 months ago")); + + x = now(CLOCK_REALTIME) - (2*USEC_PER_YEAR + 1*USEC_PER_MONTH); + assert_se(format_timestamp_relative(buf, sizeof(buf), x)); + log_debug("%s", buf); + assert_se(streq(buf, "2 years 1 month ago")); + + x = now(CLOCK_REALTIME) - (2*USEC_PER_YEAR + 2*USEC_PER_MONTH); + assert_se(format_timestamp_relative(buf, sizeof(buf), x)); + log_debug("%s", buf); + assert_se(streq(buf, "2 years 2 months ago")); + + /* Months and days */ + x = now(CLOCK_REALTIME) - (1*USEC_PER_MONTH + 1*USEC_PER_DAY); + assert_se(format_timestamp_relative(buf, sizeof(buf), x)); + log_debug("%s", buf); + assert_se(streq(buf, "1 month 1 day ago")); + + x = now(CLOCK_REALTIME) - (1*USEC_PER_MONTH + 2*USEC_PER_DAY); + assert_se(format_timestamp_relative(buf, sizeof(buf), x)); + log_debug("%s", buf); + assert_se(streq(buf, "1 month 2 days ago")); + + x = now(CLOCK_REALTIME) - (2*USEC_PER_MONTH + 1*USEC_PER_DAY); + assert_se(format_timestamp_relative(buf, sizeof(buf), x)); + log_debug("%s", buf); + assert_se(streq(buf, "2 months 1 day ago")); + + x = now(CLOCK_REALTIME) - (2*USEC_PER_MONTH + 2*USEC_PER_DAY); + assert_se(format_timestamp_relative(buf, sizeof(buf), x)); + log_debug("%s", buf); + assert_se(streq(buf, "2 months 2 days ago")); + + /* Weeks and days */ + x = now(CLOCK_REALTIME) - (1*USEC_PER_WEEK + 1*USEC_PER_DAY); + assert_se(format_timestamp_relative(buf, sizeof(buf), x)); + log_debug("%s", buf); + assert_se(streq(buf, "1 week 1 day ago")); + + x = now(CLOCK_REALTIME) - (1*USEC_PER_WEEK + 2*USEC_PER_DAY); + assert_se(format_timestamp_relative(buf, sizeof(buf), x)); + log_debug("%s", buf); + assert_se(streq(buf, "1 week 2 days ago")); + + x = now(CLOCK_REALTIME) - (2*USEC_PER_WEEK + 1*USEC_PER_DAY); + assert_se(format_timestamp_relative(buf, sizeof(buf), x)); + log_debug("%s", buf); + assert_se(streq(buf, "2 weeks 1 day ago")); + + x = now(CLOCK_REALTIME) - (2*USEC_PER_WEEK + 2*USEC_PER_DAY); + assert_se(format_timestamp_relative(buf, sizeof(buf), x)); + log_debug("%s", buf); + assert_se(streq(buf, "2 weeks 2 days ago")); +} + +static void test_format_timestamp_one(usec_t val, TimestampStyle style, const char *result) { + char buf[FORMAT_TIMESTAMP_MAX]; + const char *t; + + t = format_timestamp_style(buf, sizeof(buf), val, style); + assert_se(streq_ptr(t, result)); +} + +TEST(format_timestamp_range) { + test_format_timestamp_one(0, TIMESTAMP_UTC, NULL); + test_format_timestamp_one(0, TIMESTAMP_DATE, NULL); + test_format_timestamp_one(0, TIMESTAMP_US_UTC, NULL); + + test_format_timestamp_one(1, TIMESTAMP_UTC, "Thu 1970-01-01 00:00:00 UTC"); + test_format_timestamp_one(1, TIMESTAMP_DATE, "Thu 1970-01-01"); + test_format_timestamp_one(1, TIMESTAMP_US_UTC, "Thu 1970-01-01 00:00:00.000001 UTC"); + + test_format_timestamp_one(USEC_PER_SEC, TIMESTAMP_UTC, "Thu 1970-01-01 00:00:01 UTC"); + test_format_timestamp_one(USEC_PER_SEC, TIMESTAMP_DATE, "Thu 1970-01-01"); + test_format_timestamp_one(USEC_PER_SEC, TIMESTAMP_US_UTC, "Thu 1970-01-01 00:00:01.000000 UTC"); + +#if SIZEOF_TIME_T == 8 + test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX, TIMESTAMP_UTC, "Thu 9999-12-30 23:59:59 UTC"); + test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX, TIMESTAMP_DATE, "Thu 9999-12-30"); + test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, TIMESTAMP_UTC, "--- XXXX-XX-XX XX:XX:XX UTC"); + test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, TIMESTAMP_US_UTC, "--- XXXX-XX-XX XX:XX:XX.XXXXXX UTC"); + test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, TIMESTAMP_DATE, "--- XXXX-XX-XX"); +#elif SIZEOF_TIME_T == 4 + test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX, TIMESTAMP_UTC, "Mon 2038-01-18 03:14:07 UTC"); + test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX, TIMESTAMP_DATE, "Mon 2038-01-18"); + test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, TIMESTAMP_UTC, "--- XXXX-XX-XX XX:XX:XX UTC"); + test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, TIMESTAMP_US_UTC, "--- XXXX-XX-XX XX:XX:XX.XXXXXX UTC"); + test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, TIMESTAMP_DATE, "--- XXXX-XX-XX"); +#endif + + test_format_timestamp_one(USEC_INFINITY, TIMESTAMP_UTC, NULL); +} + +static void test_parse_timestamp_one(const char *str, usec_t max_diff, usec_t expected) { + usec_t usec = USEC_INFINITY; + int r; + + r = parse_timestamp(str, &usec); + log_debug("/* %s(%s): max_diff="USEC_FMT", expected="USEC_FMT", result="USEC_FMT" */", __func__, str, max_diff, expected, usec); + assert_se(r >= 0); + assert_se(usec >= expected); + assert_se(usec_sub_unsigned(usec, expected) <= max_diff); +} + +static bool time_is_zero(usec_t usec) { + const char *s; + + s = FORMAT_TIMESTAMP(usec); + return strstr(s, " 00:00:00 "); +} + +static bool timezone_equal(usec_t today, usec_t target) { + const char *s, *t, *sz, *tz; + + s = FORMAT_TIMESTAMP(today); + t = FORMAT_TIMESTAMP(target); + assert_se(sz = strrchr(s, ' ')); + assert_se(tz = strrchr(t, ' ')); + log_debug("%s("USEC_FMT", "USEC_FMT") -> %s, %s", __func__, today, target, s, t); + return streq(sz, tz); +} + +static void test_parse_timestamp_impl(const char *tz) { + usec_t today, today2, now_usec; + + /* Invalid: Ensure that systemctl reboot --when=show and --when=cancel + * will not result in ambiguities */ + assert_se(parse_timestamp("show", NULL) == -EINVAL); + assert_se(parse_timestamp("cancel", NULL) == -EINVAL); + + /* UTC */ + test_parse_timestamp_one("Thu 1970-01-01 00:01 UTC", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("Thu 1970-01-01 00:00:01 UTC", 0, USEC_PER_SEC); + test_parse_timestamp_one("Thu 1970-01-01 00:00:01.001 UTC", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("Thu 1970-01-01 00:00:01.0010 UTC", 0, USEC_PER_SEC + 1000); + + test_parse_timestamp_one("Thu 70-01-01 00:01 UTC", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("Thu 70-01-01 00:00:01 UTC", 0, USEC_PER_SEC); + test_parse_timestamp_one("Thu 70-01-01 00:00:01.001 UTC", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("Thu 70-01-01 00:00:01.0010 UTC", 0, USEC_PER_SEC + 1000); + + test_parse_timestamp_one("1970-01-01 00:01 UTC", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("1970-01-01 00:00:01 UTC", 0, USEC_PER_SEC); + test_parse_timestamp_one("1970-01-01 00:00:01.001 UTC", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("1970-01-01 00:00:01.0010 UTC", 0, USEC_PER_SEC + 1000); + + test_parse_timestamp_one("70-01-01 00:01 UTC", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("70-01-01 00:00:01 UTC", 0, USEC_PER_SEC); + test_parse_timestamp_one("70-01-01 00:00:01.001 UTC", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("70-01-01 00:00:01.0010 UTC", 0, USEC_PER_SEC + 1000); + + /* Examples from RFC3339 */ + test_parse_timestamp_one("1985-04-12T23:20:50.52Z", 0, 482196050 * USEC_PER_SEC + 520000); + test_parse_timestamp_one("1996-12-19T16:39:57-08:00", 0, 851042397 * USEC_PER_SEC + 000000); + test_parse_timestamp_one("1996-12-20T00:39:57Z", 0, 851042397 * USEC_PER_SEC + 000000); + test_parse_timestamp_one("1990-12-31T23:59:60Z", 0, 662688000 * USEC_PER_SEC + 000000); + test_parse_timestamp_one("1990-12-31T15:59:60-08:00", 0, 662688000 * USEC_PER_SEC + 000000); + assert_se(parse_timestamp("1937-01-01T12:00:27.87+00:20", NULL) == -EINVAL); /* we don't support pre-epoch timestamps */ + /* We accept timestamps without seconds as well */ + test_parse_timestamp_one("1996-12-20T00:39Z", 0, (851042397 - 57) * USEC_PER_SEC + 000000); + test_parse_timestamp_one("1990-12-31T15:59-08:00", 0, (662688000-60) * USEC_PER_SEC + 000000); + /* We drop day-of-week before parsing the timestamp */ + test_parse_timestamp_one("Thu 1970-01-01T00:01 UTC", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("Thu 1970-01-01T00:00:01 UTC", 0, USEC_PER_SEC); + test_parse_timestamp_one("Thu 1970-01-01T00:01Z", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("Thu 1970-01-01T00:00:01Z", 0, USEC_PER_SEC); + /* RFC3339-style timezones can be welded to all formats */ + assert_se(parse_timestamp("today UTC", &today) == 0); + assert_se(parse_timestamp("todayZ", &today2) == 0); + assert_se(today == today2); + assert_se(parse_timestamp("today +0200", &today) == 0); + assert_se(parse_timestamp("today+02:00", &today2) == 0); + assert_se(today == today2); + + /* https://ijmacd.github.io/rfc3339-iso8601/ */ + test_parse_timestamp_one("2023-09-06 12:49:27-00:00", 0, 1694004567 * USEC_PER_SEC + 000000); + test_parse_timestamp_one("2023-09-06 12:49:27.284-00:00", 0, 1694004567 * USEC_PER_SEC + 284000); + test_parse_timestamp_one("2023-09-06 12:49:27.284029Z", 0, 1694004567 * USEC_PER_SEC + 284029); + test_parse_timestamp_one("2023-09-06 12:49:27.284Z", 0, 1694004567 * USEC_PER_SEC + 284000); + test_parse_timestamp_one("2023-09-06 12:49:27.28Z", 0, 1694004567 * USEC_PER_SEC + 280000); + test_parse_timestamp_one("2023-09-06 12:49:27.2Z", 0, 1694004567 * USEC_PER_SEC + 200000); + test_parse_timestamp_one("2023-09-06 12:49:27Z", 0, 1694004567 * USEC_PER_SEC + 000000); + test_parse_timestamp_one("2023-09-06 14:49:27+02:00", 0, 1694004567 * USEC_PER_SEC + 000000); + test_parse_timestamp_one("2023-09-06 14:49:27.2+02:00", 0, 1694004567 * USEC_PER_SEC + 200000); + test_parse_timestamp_one("2023-09-06 14:49:27.28+02:00", 0, 1694004567 * USEC_PER_SEC + 280000); + test_parse_timestamp_one("2023-09-06 14:49:27.284+02:00", 0, 1694004567 * USEC_PER_SEC + 284000); + test_parse_timestamp_one("2023-09-06 14:49:27.284029+02:00", 0, 1694004567 * USEC_PER_SEC + 284029); + test_parse_timestamp_one("2023-09-06T12:49:27+00:00", 0, 1694004567 * USEC_PER_SEC + 000000); + test_parse_timestamp_one("2023-09-06T12:49:27-00:00", 0, 1694004567 * USEC_PER_SEC + 000000); + test_parse_timestamp_one("2023-09-06T12:49:27.284+00:00", 0, 1694004567 * USEC_PER_SEC + 284000); + test_parse_timestamp_one("2023-09-06T12:49:27.284-00:00", 0, 1694004567 * USEC_PER_SEC + 284000); + test_parse_timestamp_one("2023-09-06T12:49:27.284029Z", 0, 1694004567 * USEC_PER_SEC + 284029); + test_parse_timestamp_one("2023-09-06T12:49:27.284Z", 0, 1694004567 * USEC_PER_SEC + 284000); + test_parse_timestamp_one("2023-09-06T12:49:27.28Z", 0, 1694004567 * USEC_PER_SEC + 280000); + test_parse_timestamp_one("2023-09-06T12:49:27.2Z", 0, 1694004567 * USEC_PER_SEC + 200000); + test_parse_timestamp_one("2023-09-06T12:49:27Z", 0, 1694004567 * USEC_PER_SEC + 000000); + test_parse_timestamp_one("2023-09-06T14:49:27+02:00", 0, 1694004567 * USEC_PER_SEC + 000000); + test_parse_timestamp_one("2023-09-06T14:49:27.284+02:00", 0, 1694004567 * USEC_PER_SEC + 284000); + test_parse_timestamp_one("2023-09-06T14:49:27.284029+02:00", 0, 1694004567 * USEC_PER_SEC + 284029); + test_parse_timestamp_one("2023-09-06T21:34:27+08:45", 0, 1694004567 * USEC_PER_SEC + 000000); + + if (timezone_is_valid("Asia/Tokyo", LOG_DEBUG)) { + /* Asia/Tokyo (+0900) */ + test_parse_timestamp_one("Thu 1970-01-01 09:01 Asia/Tokyo", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("Thu 1970-01-01 09:00:01 Asia/Tokyo", 0, USEC_PER_SEC); + test_parse_timestamp_one("Thu 1970-01-01 09:00:01.001 Asia/Tokyo", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("Thu 1970-01-01 09:00:01.0010 Asia/Tokyo", 0, USEC_PER_SEC + 1000); + + test_parse_timestamp_one("Thu 70-01-01 09:01 Asia/Tokyo", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("Thu 70-01-01 09:00:01 Asia/Tokyo", 0, USEC_PER_SEC); + test_parse_timestamp_one("Thu 70-01-01 09:00:01.001 Asia/Tokyo", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("Thu 70-01-01 09:00:01.0010 Asia/Tokyo", 0, USEC_PER_SEC + 1000); + + test_parse_timestamp_one("1970-01-01 09:01 Asia/Tokyo", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("1970-01-01 09:00:01 Asia/Tokyo", 0, USEC_PER_SEC); + test_parse_timestamp_one("1970-01-01 09:00:01.001 Asia/Tokyo", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("1970-01-01 09:00:01.0010 Asia/Tokyo", 0, USEC_PER_SEC + 1000); + + test_parse_timestamp_one("70-01-01 09:01 Asia/Tokyo", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("70-01-01 09:00:01 Asia/Tokyo", 0, USEC_PER_SEC); + test_parse_timestamp_one("70-01-01 09:00:01.001 Asia/Tokyo", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("70-01-01 09:00:01.0010 Asia/Tokyo", 0, USEC_PER_SEC + 1000); + } + + if (streq_ptr(tz, "Asia/Tokyo")) { + /* JST (+0900) */ + test_parse_timestamp_one("Thu 1970-01-01 09:01 JST", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("Thu 1970-01-01 09:00:01 JST", 0, USEC_PER_SEC); + test_parse_timestamp_one("Thu 1970-01-01 09:00:01.001 JST", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("Thu 1970-01-01 09:00:01.0010 JST", 0, USEC_PER_SEC + 1000); + + test_parse_timestamp_one("Thu 70-01-01 09:01 JST", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("Thu 70-01-01 09:00:01 JST", 0, USEC_PER_SEC); + test_parse_timestamp_one("Thu 70-01-01 09:00:01.001 JST", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("Thu 70-01-01 09:00:01.0010 JST", 0, USEC_PER_SEC + 1000); + + test_parse_timestamp_one("1970-01-01 09:01 JST", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("1970-01-01 09:00:01 JST", 0, USEC_PER_SEC); + test_parse_timestamp_one("1970-01-01 09:00:01.001 JST", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("1970-01-01 09:00:01.0010 JST", 0, USEC_PER_SEC + 1000); + + test_parse_timestamp_one("70-01-01 09:01 JST", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("70-01-01 09:00:01 JST", 0, USEC_PER_SEC); + test_parse_timestamp_one("70-01-01 09:00:01.001 JST", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("70-01-01 09:00:01.0010 JST", 0, USEC_PER_SEC + 1000); + } + + if (timezone_is_valid("America/New_York", LOG_DEBUG)) { + /* America/New_York (-0500) */ + test_parse_timestamp_one("Wed 1969-12-31 19:01 America/New_York", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("Wed 1969-12-31 19:00:01 America/New_York", 0, USEC_PER_SEC); + test_parse_timestamp_one("Wed 1969-12-31 19:00:01.001 America/New_York", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("Wed 1969-12-31 19:00:01.0010 America/New_York", 0, USEC_PER_SEC + 1000); + + test_parse_timestamp_one("Wed 69-12-31 19:01 America/New_York", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("Wed 69-12-31 19:00:01 America/New_York", 0, USEC_PER_SEC); + test_parse_timestamp_one("Wed 69-12-31 19:00:01.001 America/New_York", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("Wed 69-12-31 19:00:01.0010 America/New_York", 0, USEC_PER_SEC + 1000); + + test_parse_timestamp_one("1969-12-31 19:01 America/New_York", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("1969-12-31 19:00:01 America/New_York", 0, USEC_PER_SEC); + test_parse_timestamp_one("1969-12-31 19:00:01.001 America/New_York", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("1969-12-31 19:00:01.0010 America/New_York", 0, USEC_PER_SEC + 1000); + + test_parse_timestamp_one("69-12-31 19:01 America/New_York", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("69-12-31 19:00:01 America/New_York", 0, USEC_PER_SEC); + test_parse_timestamp_one("69-12-31 19:00:01.001 America/New_York", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("69-12-31 19:00:01.0010 America/New_York", 0, USEC_PER_SEC + 1000); + } + + if (streq_ptr(tz, "America/New_York")) { + /* EST (-0500) */ + test_parse_timestamp_one("Wed 1969-12-31 19:01 EST", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("Wed 1969-12-31 19:00:01 EST", 0, USEC_PER_SEC); + test_parse_timestamp_one("Wed 1969-12-31 19:00:01.001 EST", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("Wed 1969-12-31 19:00:01.0010 EST", 0, USEC_PER_SEC + 1000); + + test_parse_timestamp_one("Wed 69-12-31 19:01 EST", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("Wed 69-12-31 19:00:01 EST", 0, USEC_PER_SEC); + test_parse_timestamp_one("Wed 69-12-31 19:00:01.001 EST", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("Wed 69-12-31 19:00:01.0010 EST", 0, USEC_PER_SEC + 1000); + + test_parse_timestamp_one("1969-12-31 19:01 EST", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("1969-12-31 19:00:01 EST", 0, USEC_PER_SEC); + test_parse_timestamp_one("1969-12-31 19:00:01.001 EST", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("1969-12-31 19:00:01.0010 EST", 0, USEC_PER_SEC + 1000); + + test_parse_timestamp_one("69-12-31 19:01 EST", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("69-12-31 19:00:01 EST", 0, USEC_PER_SEC); + test_parse_timestamp_one("69-12-31 19:00:01.001 EST", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("69-12-31 19:00:01.0010 EST", 0, USEC_PER_SEC + 1000); + } + + /* -06 */ + test_parse_timestamp_one("Wed 1969-12-31 18:01 -06", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("Wed 1969-12-31 18:00:01 -06", 0, USEC_PER_SEC); + test_parse_timestamp_one("Wed 1969-12-31 18:00:01.001 -06", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("Wed 1969-12-31 18:00:01.0010 -06", 0, USEC_PER_SEC + 1000); + + test_parse_timestamp_one("Wed 69-12-31 18:01 -06", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("Wed 69-12-31 18:00:01 -06", 0, USEC_PER_SEC); + test_parse_timestamp_one("Wed 69-12-31 18:00:01.001 -06", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("Wed 69-12-31 18:00:01.0010 -06", 0, USEC_PER_SEC + 1000); + + test_parse_timestamp_one("1969-12-31 18:01 -06", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("1969-12-31 18:00:01 -06", 0, USEC_PER_SEC); + test_parse_timestamp_one("1969-12-31 18:00:01.001 -06", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("1969-12-31 18:00:01.0010 -06", 0, USEC_PER_SEC + 1000); + + test_parse_timestamp_one("69-12-31 18:01 -06", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("69-12-31 18:00:01 -06", 0, USEC_PER_SEC); + test_parse_timestamp_one("69-12-31 18:00:01.001 -06", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("69-12-31 18:00:01.0010 -06", 0, USEC_PER_SEC + 1000); + + /* -0600 */ + test_parse_timestamp_one("Wed 1969-12-31 18:01 -0600", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("Wed 1969-12-31 18:00:01 -0600", 0, USEC_PER_SEC); + test_parse_timestamp_one("Wed 1969-12-31 18:00:01.001 -0600", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("Wed 1969-12-31 18:00:01.0010 -0600", 0, USEC_PER_SEC + 1000); + + test_parse_timestamp_one("Wed 69-12-31 18:01 -0600", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("Wed 69-12-31 18:00:01 -0600", 0, USEC_PER_SEC); + test_parse_timestamp_one("Wed 69-12-31 18:00:01.001 -0600", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("Wed 69-12-31 18:00:01.0010 -0600", 0, USEC_PER_SEC + 1000); + + test_parse_timestamp_one("1969-12-31 18:01 -0600", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("1969-12-31 18:00:01 -0600", 0, USEC_PER_SEC); + test_parse_timestamp_one("1969-12-31 18:00:01.001 -0600", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("1969-12-31 18:00:01.0010 -0600", 0, USEC_PER_SEC + 1000); + + test_parse_timestamp_one("69-12-31 18:01 -0600", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("69-12-31 18:00:01 -0600", 0, USEC_PER_SEC); + test_parse_timestamp_one("69-12-31 18:00:01.001 -0600", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("69-12-31 18:00:01.0010 -0600", 0, USEC_PER_SEC + 1000); + + /* -06:00 */ + test_parse_timestamp_one("Wed 1969-12-31 18:01 -06:00", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("Wed 1969-12-31 18:00:01 -06:00", 0, USEC_PER_SEC); + test_parse_timestamp_one("Wed 1969-12-31 18:00:01.001 -06:00", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("Wed 1969-12-31 18:00:01.0010 -06:00", 0, USEC_PER_SEC + 1000); + + test_parse_timestamp_one("Wed 69-12-31 18:01 -06:00", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("Wed 69-12-31 18:00:01 -06:00", 0, USEC_PER_SEC); + test_parse_timestamp_one("Wed 69-12-31 18:00:01.001 -06:00", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("Wed 69-12-31 18:00:01.0010 -06:00", 0, USEC_PER_SEC + 1000); + + test_parse_timestamp_one("1969-12-31 18:01 -06:00", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("1969-12-31 18:00:01 -06:00", 0, USEC_PER_SEC); + test_parse_timestamp_one("1969-12-31 18:00:01.001 -06:00", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("1969-12-31 18:00:01.0010 -06:00", 0, USEC_PER_SEC + 1000); + + test_parse_timestamp_one("69-12-31 18:01 -06:00", 0, USEC_PER_MINUTE); + test_parse_timestamp_one("69-12-31 18:00:01 -06:00", 0, USEC_PER_SEC); + test_parse_timestamp_one("69-12-31 18:00:01.001 -06:00", 0, USEC_PER_SEC + 1000); + test_parse_timestamp_one("69-12-31 18:00:01.0010 -06:00", 0, USEC_PER_SEC + 1000); + + /* without date */ + assert_se(parse_timestamp("today", &today) == 0); + if (time_is_zero(today)) { + test_parse_timestamp_one("00:01", 0, today + USEC_PER_MINUTE); + test_parse_timestamp_one("00:00:01", 0, today + USEC_PER_SEC); + test_parse_timestamp_one("00:00:01.001", 0, today + USEC_PER_SEC + 1000); + test_parse_timestamp_one("00:00:01.0010", 0, today + USEC_PER_SEC + 1000); + + if (timezone_equal(today, today + USEC_PER_DAY) && time_is_zero(today + USEC_PER_DAY)) + test_parse_timestamp_one("tomorrow", 0, today + USEC_PER_DAY); + if (timezone_equal(today, today - USEC_PER_DAY) && time_is_zero(today - USEC_PER_DAY)) + test_parse_timestamp_one("yesterday", 0, today - USEC_PER_DAY); + } + + /* relative */ + assert_se(parse_timestamp("now", &now_usec) == 0); + test_parse_timestamp_one("+5hours", USEC_PER_MINUTE, now_usec + 5 * USEC_PER_HOUR); + if (now_usec >= 10 * USEC_PER_DAY) + test_parse_timestamp_one("-10days", USEC_PER_MINUTE, now_usec - 10 * USEC_PER_DAY); + test_parse_timestamp_one("2weeks left", USEC_PER_MINUTE, now_usec + 2 * USEC_PER_WEEK); + if (now_usec >= 30 * USEC_PER_MINUTE) + test_parse_timestamp_one("30minutes ago", USEC_PER_MINUTE, now_usec - 30 * USEC_PER_MINUTE); +} + +TEST(parse_timestamp) { + test_parse_timestamp_impl(NULL); +} + +static void test_parse_timestamp_with_tz_one(const char *tz) { + const char *saved_tz, *colon_tz; + + if (!timezone_is_valid(tz, LOG_DEBUG)) + return; + + log_info("/* %s(%s) */", __func__, tz); + + saved_tz = getenv("TZ"); + + assert_se(colon_tz = strjoina(":", tz)); + assert_se(setenv("TZ", colon_tz, 1) >= 0); + tzset(); + log_debug("%s: tzname[0]=%s, tzname[1]=%s", tz, strempty(tzname[0]), strempty(tzname[1])); + + test_parse_timestamp_impl(tz); + + assert_se(set_unset_env("TZ", saved_tz, true) == 0); + tzset(); +} + +TEST(parse_timestamp_with_tz) { + _cleanup_strv_free_ char **timezones = NULL; + + test_parse_timestamp_with_tz_one("UTC"); + + if (!slow_tests_enabled()) + return (void) log_tests_skipped("slow tests are disabled"); + + assert_se(get_timezones(&timezones) >= 0); + STRV_FOREACH(tz, timezones) + test_parse_timestamp_with_tz_one(*tz); +} + +TEST(deserialize_dual_timestamp) { + int r; + dual_timestamp t; + + r = deserialize_dual_timestamp("1234 5678", &t); + assert_se(r == 0); + assert_se(t.realtime == 1234); + assert_se(t.monotonic == 5678); + + r = deserialize_dual_timestamp("1234x 5678", &t); + assert_se(r == -EINVAL); + + r = deserialize_dual_timestamp("1234 5678y", &t); + assert_se(r == -EINVAL); + + r = deserialize_dual_timestamp("-1234 5678", &t); + assert_se(r == -EINVAL); + + r = deserialize_dual_timestamp("1234 -5678", &t); + assert_se(r == -EINVAL); + + /* Check that output wasn't modified. */ + assert_se(t.realtime == 1234); + assert_se(t.monotonic == 5678); + + r = deserialize_dual_timestamp("+123 567", &t); + assert_se(r == 0); + assert_se(t.realtime == 123); + assert_se(t.monotonic == 567); + + /* Check that we get "infinity" on overflow. */ + r = deserialize_dual_timestamp("18446744073709551617 0", &t); + assert_se(r == 0); + assert_se(t.realtime == USEC_INFINITY); + assert_se(t.monotonic == 0); +} + +static void assert_similar(usec_t a, usec_t b) { + usec_t d; + + if (a > b) + d = a - b; + else + d = b - a; + + assert_se(d < 10*USEC_PER_SEC); +} + +TEST(usec_shift_clock) { + usec_t rt, mn, bt; + + rt = now(CLOCK_REALTIME); + mn = now(CLOCK_MONOTONIC); + bt = now(CLOCK_BOOTTIME); + + assert_se(usec_shift_clock(USEC_INFINITY, CLOCK_REALTIME, CLOCK_MONOTONIC) == USEC_INFINITY); + + assert_similar(usec_shift_clock(rt + USEC_PER_HOUR, CLOCK_REALTIME, CLOCK_MONOTONIC), mn + USEC_PER_HOUR); + assert_similar(usec_shift_clock(rt + 2*USEC_PER_HOUR, CLOCK_REALTIME, CLOCK_BOOTTIME), bt + 2*USEC_PER_HOUR); + assert_se(usec_shift_clock(rt + 3*USEC_PER_HOUR, CLOCK_REALTIME, CLOCK_REALTIME_ALARM) == rt + 3*USEC_PER_HOUR); + + assert_similar(usec_shift_clock(mn + 4*USEC_PER_HOUR, CLOCK_MONOTONIC, CLOCK_REALTIME_ALARM), rt + 4*USEC_PER_HOUR); + assert_similar(usec_shift_clock(mn + 5*USEC_PER_HOUR, CLOCK_MONOTONIC, CLOCK_BOOTTIME), bt + 5*USEC_PER_HOUR); + assert_se(usec_shift_clock(mn + 6*USEC_PER_HOUR, CLOCK_MONOTONIC, CLOCK_MONOTONIC) == mn + 6*USEC_PER_HOUR); + + assert_similar(usec_shift_clock(bt + 7*USEC_PER_HOUR, CLOCK_BOOTTIME, CLOCK_MONOTONIC), mn + 7*USEC_PER_HOUR); + assert_similar(usec_shift_clock(bt + 8*USEC_PER_HOUR, CLOCK_BOOTTIME, CLOCK_REALTIME_ALARM), rt + 8*USEC_PER_HOUR); + assert_se(usec_shift_clock(bt + 9*USEC_PER_HOUR, CLOCK_BOOTTIME, CLOCK_BOOTTIME) == bt + 9*USEC_PER_HOUR); + + if (mn > USEC_PER_MINUTE) { + assert_similar(usec_shift_clock(rt - 30 * USEC_PER_SEC, CLOCK_REALTIME_ALARM, CLOCK_MONOTONIC), mn - 30 * USEC_PER_SEC); + assert_similar(usec_shift_clock(rt - 50 * USEC_PER_SEC, CLOCK_REALTIME, CLOCK_BOOTTIME), bt - 50 * USEC_PER_SEC); + } +} + +TEST(in_utc_timezone) { + const char *tz = getenv("TZ"); + + assert_se(setenv("TZ", ":UTC", 1) >= 0); + assert_se(in_utc_timezone()); + assert_se(streq(tzname[0], "UTC")); + assert_se(streq(tzname[1], "UTC")); + assert_se(timezone == 0); + assert_se(daylight == 0); + + assert_se(setenv("TZ", ":Europe/Berlin", 1) >= 0); + assert_se(!in_utc_timezone()); + assert_se(streq(tzname[0], "CET")); + assert_se(streq(tzname[1], "CEST")); + + assert_se(set_unset_env("TZ", tz, true) == 0); + tzset(); +} + +TEST(map_clock_usec) { + usec_t nowr, x, y, z; + + x = nowr = now(CLOCK_REALTIME); /* right now */ + y = map_clock_usec(x, CLOCK_REALTIME, CLOCK_MONOTONIC); + z = map_clock_usec(y, CLOCK_MONOTONIC, CLOCK_REALTIME); + /* Converting forth and back will introduce inaccuracies, since we cannot query both clocks atomically, but it should be small. Even on the slowest CI smaller than 1h */ + + assert_se((z > x ? z - x : x - z) < USEC_PER_HOUR); + + assert_se(nowr < USEC_INFINITY - USEC_PER_DAY*7); /* overflow check */ + x = nowr + USEC_PER_DAY*7; /* 1 week from now */ + y = map_clock_usec(x, CLOCK_REALTIME, CLOCK_MONOTONIC); + assert_se(y > 0 && y < USEC_INFINITY); + z = map_clock_usec(y, CLOCK_MONOTONIC, CLOCK_REALTIME); + assert_se(z > 0 && z < USEC_INFINITY); + assert_se((z > x ? z - x : x - z) < USEC_PER_HOUR); + + assert_se(nowr > USEC_PER_DAY * 7); /* underflow check */ + x = nowr - USEC_PER_DAY*7; /* 1 week ago */ + y = map_clock_usec(x, CLOCK_REALTIME, CLOCK_MONOTONIC); + if (y != 0) { /* might underflow if machine is not up long enough for the monotonic clock to be beyond 1w */ + assert_se(y < USEC_INFINITY); + z = map_clock_usec(y, CLOCK_MONOTONIC, CLOCK_REALTIME); + assert_se(z > 0 && z < USEC_INFINITY); + assert_se((z > x ? z - x : x - z) < USEC_PER_HOUR); + } +} + +static void test_timezone_offset_change_one(const char *utc, const char *pretty) { + usec_t x, y, z; + char *s; + + assert_se(parse_timestamp(utc, &x) >= 0); + + s = FORMAT_TIMESTAMP_STYLE(x, TIMESTAMP_UTC); + assert_se(parse_timestamp(s, &y) >= 0); + log_debug("%s -> " USEC_FMT " -> %s -> " USEC_FMT, utc, x, s, y); + assert_se(streq(s, utc)); + assert_se(x == y); + + assert_se(parse_timestamp(pretty, &y) >= 0); + s = FORMAT_TIMESTAMP_STYLE(y, TIMESTAMP_PRETTY); + assert_se(parse_timestamp(s, &z) >= 0); + log_debug("%s -> " USEC_FMT " -> %s -> " USEC_FMT, pretty, y, s, z); + assert_se(streq(s, pretty)); + assert_se(x == y); + assert_se(x == z); +} + +TEST(timezone_offset_change) { + const char *tz = getenv("TZ"); + + /* See issue #26370. */ + + if (timezone_is_valid("Africa/Casablanca", LOG_DEBUG)) { + assert_se(setenv("TZ", ":Africa/Casablanca", 1) >= 0); + tzset(); + log_debug("Africa/Casablanca: tzname[0]=%s, tzname[1]=%s", strempty(tzname[0]), strempty(tzname[1])); + + test_timezone_offset_change_one("Sun 2015-10-25 01:59:59 UTC", "Sun 2015-10-25 02:59:59 +01"); + test_timezone_offset_change_one("Sun 2015-10-25 02:00:00 UTC", "Sun 2015-10-25 02:00:00 +00"); + test_timezone_offset_change_one("Sun 2018-06-17 01:59:59 UTC", "Sun 2018-06-17 01:59:59 +00"); + test_timezone_offset_change_one("Sun 2018-06-17 02:00:00 UTC", "Sun 2018-06-17 03:00:00 +01"); + test_timezone_offset_change_one("Sun 2018-10-28 01:59:59 UTC", "Sun 2018-10-28 02:59:59 +01"); + test_timezone_offset_change_one("Sun 2018-10-28 02:00:00 UTC", "Sun 2018-10-28 03:00:00 +01"); + } + + if (timezone_is_valid("Asia/Atyrau", LOG_DEBUG)) { + assert_se(setenv("TZ", ":Asia/Atyrau", 1) >= 0); + tzset(); + log_debug("Asia/Atyrau: tzname[0]=%s, tzname[1]=%s", strempty(tzname[0]), strempty(tzname[1])); + + test_timezone_offset_change_one("Sat 2004-03-27 21:59:59 UTC", "Sun 2004-03-28 01:59:59 +04"); + test_timezone_offset_change_one("Sat 2004-03-27 22:00:00 UTC", "Sun 2004-03-28 03:00:00 +05"); + test_timezone_offset_change_one("Sat 2004-10-30 21:59:59 UTC", "Sun 2004-10-31 02:59:59 +05"); + test_timezone_offset_change_one("Sat 2004-10-30 22:00:00 UTC", "Sun 2004-10-31 03:00:00 +05"); + } + + if (timezone_is_valid("Chile/EasterIsland", LOG_DEBUG)) { + assert_se(setenv("TZ", ":Chile/EasterIsland", 1) >= 0); + tzset(); + log_debug("Chile/EasterIsland: tzname[0]=%s, tzname[1]=%s", strempty(tzname[0]), strempty(tzname[1])); + + test_timezone_offset_change_one("Sun 1981-10-11 03:59:59 UTC", "Sat 1981-10-10 20:59:59 -07"); + test_timezone_offset_change_one("Sun 1981-10-11 04:00:00 UTC", "Sat 1981-10-10 22:00:00 -06"); + test_timezone_offset_change_one("Sun 1982-03-14 02:59:59 UTC", "Sat 1982-03-13 20:59:59 -06"); + test_timezone_offset_change_one("Sun 1982-03-14 03:00:00 UTC", "Sat 1982-03-13 21:00:00 -06"); + } + + assert_se(set_unset_env("TZ", tz, true) == 0); + tzset(); +} + +static int intro(void) { + /* Tests have hard-coded results that do not expect a specific timezone to be set by the caller */ + assert_se(unsetenv("TZ") >= 0); + + log_info("realtime=" USEC_FMT "\n" + "monotonic=" USEC_FMT "\n" + "boottime=" USEC_FMT "\n", + now(CLOCK_REALTIME), + now(CLOCK_MONOTONIC), + now(CLOCK_BOOTTIME)); + + /* Ensure time_t is signed */ + assert_cc((time_t) -1 < (time_t) 1); + + /* Ensure TIME_T_MAX works correctly */ + uintmax_t x = TIME_T_MAX; + x++; + assert_se((time_t) x < 0); + + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/test/test-tmpfile-util.c b/src/test/test-tmpfile-util.c new file mode 100644 index 0000000..4859f62 --- /dev/null +++ b/src/test/test-tmpfile-util.c @@ -0,0 +1,306 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "format-util.h" +#include "fs-util.h" +#include "log.h" +#include "path-util.h" +#include "process-util.h" +#include "string-util.h" +#include "tests.h" +#include "tmpfile-util.h" + +static void test_tempfn_random_one(const char *p, const char *extra, const char *expect, int ret) { + _cleanup_free_ char *s = NULL; + int r; + + r = tempfn_random(p, extra, &s); + log_info("%s+%s → %s vs. %s (%i/%s vs. %i/%s)", + p, strna(extra), strna(s), strna(expect), + r, STRERROR(r), ret, STRERROR(ret)); + + assert_se(!s == !expect); + if (s) { + const char *suffix; + + assert_se(suffix = startswith(s, expect)); + assert_se(in_charset(suffix, HEXDIGITS)); + assert_se(strlen(suffix) == 16); + } + assert_se(ret == r); +} + +TEST(tempfn_random) { + _cleanup_free_ char *dir = NULL, *p = NULL, *q = NULL; + + test_tempfn_random_one("", NULL, NULL, -EINVAL); + test_tempfn_random_one(".", NULL, NULL, -EADDRNOTAVAIL); + test_tempfn_random_one("..", NULL, NULL, -EINVAL); + test_tempfn_random_one("/", NULL, NULL, -EADDRNOTAVAIL); + test_tempfn_random_one("foo", "hoge/aaa", NULL, -EINVAL); + + test_tempfn_random_one("foo", NULL, ".#foo", 0); + test_tempfn_random_one("foo", "bar", ".#barfoo", 0); + test_tempfn_random_one("/tmp/foo", NULL, "/tmp/.#foo", 0); + test_tempfn_random_one("/tmp/foo", "bar", "/tmp/.#barfoo", 0); + test_tempfn_random_one("./foo", NULL, ".#foo", 0); + test_tempfn_random_one("./foo", "bar", ".#barfoo", 0); + test_tempfn_random_one("../foo", NULL, "../.#foo", 0); + test_tempfn_random_one("../foo", "bar", "../.#barfoo", 0); + + test_tempfn_random_one("foo/", NULL, ".#foo", 0); + test_tempfn_random_one("foo/", "bar", ".#barfoo", 0); + test_tempfn_random_one("/tmp/foo/", NULL, "/tmp/.#foo", 0); + test_tempfn_random_one("/tmp/foo/", "bar", "/tmp/.#barfoo", 0); + test_tempfn_random_one("./foo/", NULL, ".#foo", 0); + test_tempfn_random_one("./foo/", "bar", ".#barfoo", 0); + test_tempfn_random_one("../foo/", NULL, "../.#foo", 0); + test_tempfn_random_one("../foo/", "bar", "../.#barfoo", 0); + + assert_se(dir = new(char, PATH_MAX - 20)); + memset(dir, 'x', PATH_MAX - 21); + dir[PATH_MAX - 21] = '\0'; + for (size_t i = 0; i < PATH_MAX - 21; i += NAME_MAX + 1) + dir[i] = '/'; + + assert_se(p = path_join(dir, "a")); + assert_se(q = path_join(dir, ".#a")); + + test_tempfn_random_one(p, NULL, q, 0); + test_tempfn_random_one(p, "b", NULL, -EINVAL); + + p = mfree(p); + q = mfree(q); + + assert_se(p = new(char, NAME_MAX + 1)); + memset(p, 'x', NAME_MAX); + p[NAME_MAX] = '\0'; + + assert_se(q = new(char, NAME_MAX + 1)); + memset(stpcpy(q, ".#"), 'x', NAME_MAX - STRLEN(".#") - 16); + q[NAME_MAX - 16] = '\0'; + + test_tempfn_random_one(p, NULL, q, 0); + + memset(stpcpy(q, ".#hoge"), 'x', NAME_MAX - STRLEN(".#hoge") - 16); + q[NAME_MAX - 16] = '\0'; + + test_tempfn_random_one(p, "hoge", q, 0); +} + +static void test_tempfn_xxxxxx_one(const char *p, const char *extra, const char *expect, int ret) { + _cleanup_free_ char *s = NULL; + int r; + + r = tempfn_xxxxxx(p, extra, &s); + log_info("%s+%s → %s vs. %s (%i/%s vs. %i/%s)", + p, strna(extra), strna(s), strna(expect), + r, STRERROR(r), ret, STRERROR(ret)); + + assert_se(!s == !expect); + if (s) { + const char *suffix; + + assert_se(suffix = startswith(s, expect)); + assert_se(streq(suffix, "XXXXXX")); + } + assert_se(ret == r); +} + +TEST(tempfn_xxxxxx) { + _cleanup_free_ char *dir = NULL, *p = NULL, *q = NULL; + + test_tempfn_xxxxxx_one("", NULL, NULL, -EINVAL); + test_tempfn_xxxxxx_one(".", NULL, NULL, -EADDRNOTAVAIL); + test_tempfn_xxxxxx_one("..", NULL, NULL, -EINVAL); + test_tempfn_xxxxxx_one("/", NULL, NULL, -EADDRNOTAVAIL); + test_tempfn_xxxxxx_one("foo", "hoge/aaa", NULL, -EINVAL); + + test_tempfn_xxxxxx_one("foo", NULL, ".#foo", 0); + test_tempfn_xxxxxx_one("foo", "bar", ".#barfoo", 0); + test_tempfn_xxxxxx_one("/tmp/foo", NULL, "/tmp/.#foo", 0); + test_tempfn_xxxxxx_one("/tmp/foo", "bar", "/tmp/.#barfoo", 0); + test_tempfn_xxxxxx_one("./foo", NULL, ".#foo", 0); + test_tempfn_xxxxxx_one("./foo", "bar", ".#barfoo", 0); + test_tempfn_xxxxxx_one("../foo", NULL, "../.#foo", 0); + test_tempfn_xxxxxx_one("../foo", "bar", "../.#barfoo", 0); + + test_tempfn_xxxxxx_one("foo/", NULL, ".#foo", 0); + test_tempfn_xxxxxx_one("foo/", "bar", ".#barfoo", 0); + test_tempfn_xxxxxx_one("/tmp/foo/", NULL, "/tmp/.#foo", 0); + test_tempfn_xxxxxx_one("/tmp/foo/", "bar", "/tmp/.#barfoo", 0); + test_tempfn_xxxxxx_one("./foo/", NULL, ".#foo", 0); + test_tempfn_xxxxxx_one("./foo/", "bar", ".#barfoo", 0); + test_tempfn_xxxxxx_one("../foo/", NULL, "../.#foo", 0); + test_tempfn_xxxxxx_one("../foo/", "bar", "../.#barfoo", 0); + + assert_se(dir = new(char, PATH_MAX - 10)); + memset(dir, 'x', PATH_MAX - 11); + dir[PATH_MAX - 11] = '\0'; + for (size_t i = 0; i < PATH_MAX - 11; i += NAME_MAX + 1) + dir[i] = '/'; + + assert_se(p = path_join(dir, "a")); + assert_se(q = path_join(dir, ".#a")); + + test_tempfn_xxxxxx_one(p, NULL, q, 0); + test_tempfn_xxxxxx_one(p, "b", NULL, -EINVAL); + + p = mfree(p); + q = mfree(q); + + assert_se(p = new(char, NAME_MAX + 1)); + memset(p, 'x', NAME_MAX); + p[NAME_MAX] = '\0'; + + assert_se(q = new(char, NAME_MAX + 1)); + memset(stpcpy(q, ".#"), 'x', NAME_MAX - STRLEN(".#") - 6); + q[NAME_MAX - 6] = '\0'; + + test_tempfn_xxxxxx_one(p, NULL, q, 0); + + memset(stpcpy(q, ".#hoge"), 'x', NAME_MAX - STRLEN(".#hoge") - 6); + q[NAME_MAX - 6] = '\0'; + + test_tempfn_xxxxxx_one(p, "hoge", q, 0); +} + +static void test_tempfn_random_child_one(const char *p, const char *extra, const char *expect, int ret) { + _cleanup_free_ char *s = NULL; + int r; + + r = tempfn_random_child(p, extra, &s); + log_info_errno(r, "%s+%s → %s vs. %s (%i/%s vs. %i/%s)", + p, strna(extra), strna(s), strna(expect), + r, STRERROR(r), ret, STRERROR(ret)); + + assert_se(!s == !expect); + if (s) { + const char *suffix; + + assert_se(suffix = startswith(s, expect)); + assert_se(in_charset(suffix, HEXDIGITS)); + assert_se(strlen(suffix) == 16); + } + assert_se(ret == r); +} + +TEST(tempfn_random_child) { + _cleanup_free_ char *dir = NULL, *p = NULL, *q = NULL; + + test_tempfn_random_child_one("", NULL, ".#", 0); + test_tempfn_random_child_one(".", NULL, ".#", 0); + test_tempfn_random_child_one("..", NULL, "../.#", 0); + test_tempfn_random_child_one("/", NULL, "/.#", 0); + test_tempfn_random_child_one("foo", "hoge/aaa", NULL, -EINVAL); + + test_tempfn_random_child_one("foo", NULL, "foo/.#", 0); + test_tempfn_random_child_one("foo", "bar", "foo/.#bar", 0); + test_tempfn_random_child_one("/tmp/foo", NULL, "/tmp/foo/.#", 0); + test_tempfn_random_child_one("/tmp/foo", "bar", "/tmp/foo/.#bar", 0); + test_tempfn_random_child_one("./foo", NULL, "foo/.#", 0); + test_tempfn_random_child_one("./foo", "bar", "foo/.#bar", 0); + test_tempfn_random_child_one("../foo", NULL, "../foo/.#", 0); + test_tempfn_random_child_one("../foo", "bar", "../foo/.#bar", 0); + + test_tempfn_random_child_one("foo/", NULL, "foo/.#", 0); + test_tempfn_random_child_one("foo/", "bar", "foo/.#bar", 0); + test_tempfn_random_child_one("/tmp/foo/", NULL, "/tmp/foo/.#", 0); + test_tempfn_random_child_one("/tmp/foo/", "bar", "/tmp/foo/.#bar", 0); + test_tempfn_random_child_one("./foo/", NULL, "foo/.#", 0); + test_tempfn_random_child_one("./foo/", "bar", "foo/.#bar", 0); + test_tempfn_random_child_one("../foo/", NULL, "../foo/.#", 0); + test_tempfn_random_child_one("../foo/", "bar", "../foo/.#bar", 0); + + assert_se(dir = new(char, PATH_MAX - 21)); + memset(dir, 'x', PATH_MAX - 22); + dir[PATH_MAX - 22] = '\0'; + for (size_t i = 0; i < PATH_MAX - 22; i += NAME_MAX + 1) + dir[i] = '/'; + + assert_se(p = path_join(dir, "a")); + assert_se(q = path_join(p, ".#")); + + test_tempfn_random_child_one(p, NULL, q, 0); + test_tempfn_random_child_one(p, "b", NULL, -EINVAL); + + p = mfree(p); + q = mfree(q); + + assert_se(p = new(char, NAME_MAX + 1)); + memset(p, 'x', NAME_MAX); + p[NAME_MAX] = '\0'; + + assert_se(q = path_join(p, ".#")); + + test_tempfn_random_child_one(p, NULL, q, 0); + + assert_se(strextend(&q, "hoge")); + test_tempfn_random_child_one(p, "hoge", q, 0); +} + +TEST(link_tmpfile) { + _cleanup_free_ char *cmd = NULL, *cmd2 = NULL, *ans = NULL, *ans2 = NULL, *d = NULL, *tmp = NULL, *line = NULL; + _cleanup_close_ int fd = -EBADF, fd2 = -EBADF; + const char *p = saved_argv[1] ?: "/tmp"; + char *pattern; + + pattern = strjoina(p, "/systemd-test-XXXXXX"); + + fd = open_tmpfile_unlinkable(p, O_RDWR|O_CLOEXEC); + assert_se(fd >= 0); + + assert_se(asprintf(&cmd, "ls -l /proc/"PID_FMT"/fd/%d", getpid_cached(), fd) > 0); + (void) system(cmd); + assert_se(readlink_malloc(cmd + 6, &ans) >= 0); + log_debug("link1: %s", ans); + assert_se(endswith(ans, " (deleted)")); + + fd2 = mkostemp_safe(pattern); + assert_se(fd2 >= 0); + assert_se(unlink(pattern) == 0); + + assert_se(asprintf(&cmd2, "ls -l /proc/"PID_FMT"/fd/%d", getpid_cached(), fd2) > 0); + (void) system(cmd2); + assert_se(readlink_malloc(cmd2 + 6, &ans2) >= 0); + log_debug("link2: %s", ans2); + assert_se(endswith(ans2, " (deleted)")); + + pattern = strjoina(p, "/tmpfiles-test"); + assert_se(tempfn_random(pattern, NULL, &d) >= 0); + + fd = safe_close(fd); + fd = open_tmpfile_linkable(d, O_RDWR|O_CLOEXEC, &tmp); + assert_se(fd >= 0); + assert_se(write(fd, "foobar\n", 7) == 7); + + assert_se(touch(d) >= 0); + assert_se(link_tmpfile(fd, tmp, d, /* flags= */ 0) == -EEXIST); + assert_se(unlink(d) >= 0); + assert_se(link_tmpfile(fd, tmp, d, /* flags= */ 0) >= 0); + + assert_se(read_one_line_file(d, &line) >= 0); + assert_se(streq(line, "foobar")); + + fd = safe_close(fd); + tmp = mfree(tmp); + + fd = open_tmpfile_linkable(d, O_RDWR|O_CLOEXEC, &tmp); + assert_se(fd >= 0); + + assert_se(write(fd, "waumiau\n", 8) == 8); + + assert_se(link_tmpfile(fd, tmp, d, /* flags= */ 0) == -EEXIST); + assert_se(link_tmpfile(fd, tmp, d, LINK_TMPFILE_REPLACE) >= 0); + + line = mfree(line); + assert_se(read_one_line_file(d, &line) >= 0); + assert_se(streq(line, "waumiau")); + + assert_se(unlink(d) >= 0); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c new file mode 100644 index 0000000..e3c7da8 --- /dev/null +++ b/src/test/test-tpm2.c @@ -0,0 +1,1324 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "hexdecoct.h" +#include "macro.h" +#include "tests.h" +#include "tpm2-util.h" +#include "virt.h" + +TEST(tpm2_pcr_index_from_string) { + assert_se(tpm2_pcr_index_from_string("platform-code") == 0); + assert_se(tpm2_pcr_index_from_string("0") == 0); + assert_se(tpm2_pcr_index_from_string("platform-config") == 1); + assert_se(tpm2_pcr_index_from_string("1") == 1); + assert_se(tpm2_pcr_index_from_string("external-code") == 2); + assert_se(tpm2_pcr_index_from_string("2") == 2); + assert_se(tpm2_pcr_index_from_string("external-config") == 3); + assert_se(tpm2_pcr_index_from_string("3") == 3); + assert_se(tpm2_pcr_index_from_string("boot-loader-code") == 4); + assert_se(tpm2_pcr_index_from_string("4") == 4); + assert_se(tpm2_pcr_index_from_string("boot-loader-config") == 5); + assert_se(tpm2_pcr_index_from_string("5") == 5); + assert_se(tpm2_pcr_index_from_string("secure-boot-policy") == 7); + assert_se(tpm2_pcr_index_from_string("7") == 7); + assert_se(tpm2_pcr_index_from_string("kernel-initrd") == 9); + assert_se(tpm2_pcr_index_from_string("9") == 9); + assert_se(tpm2_pcr_index_from_string("ima") == 10); + assert_se(tpm2_pcr_index_from_string("10") == 10); + assert_se(tpm2_pcr_index_from_string("kernel-boot") == 11); + assert_se(tpm2_pcr_index_from_string("11") == 11); + assert_se(tpm2_pcr_index_from_string("kernel-config") == 12); + assert_se(tpm2_pcr_index_from_string("12") == 12); + assert_se(tpm2_pcr_index_from_string("sysexts") == 13); + assert_se(tpm2_pcr_index_from_string("13") == 13); + assert_se(tpm2_pcr_index_from_string("shim-policy") == 14); + assert_se(tpm2_pcr_index_from_string("14") == 14); + assert_se(tpm2_pcr_index_from_string("system-identity") == 15); + assert_se(tpm2_pcr_index_from_string("15") == 15); + assert_se(tpm2_pcr_index_from_string("debug") == 16); + assert_se(tpm2_pcr_index_from_string("16") == 16); + assert_se(tpm2_pcr_index_from_string("application-support") == 23); + assert_se(tpm2_pcr_index_from_string("23") == 23); + assert_se(tpm2_pcr_index_from_string("hello") == -EINVAL); + assert_se(tpm2_pcr_index_from_string("8") == 8); + assert_se(tpm2_pcr_index_from_string("44") == -EINVAL); + assert_se(tpm2_pcr_index_from_string("-5") == -EINVAL); + assert_se(tpm2_pcr_index_from_string("24") == -EINVAL); +} + +TEST(tpm2_util_pbkdf2_hmac_sha256) { + + /* + * The test vectors from RFC 6070 [1] are for dkLen of 20 as it's SHA1 + * other RFCs I bumped into had various differing dkLen and iter counts, + * so this was generated using Python's hmacmodule. + * + * 1. https://www.rfc-editor.org/rfc/rfc6070.html#page-2 + */ + static const struct { + const uint8_t pass[256]; + size_t passlen; + const uint8_t salt[256]; + size_t saltlen; + uint8_t expected[SHA256_DIGEST_SIZE]; + } test_vectors[] = { + { .pass={'f', 'o', 'o', 'p', 'a', 's', 's'}, .passlen=7, .salt={'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5'}, .saltlen=16, .expected={0xCB, 0xEA, 0x27, 0x23, 0x9A, 0x65, 0x99, 0xF6, 0x8C, 0x26, 0x54, 0x80, 0x5C, 0x63, 0x61, 0xD2, 0x91, 0x0A, 0x60, 0x3F, 0xC2, 0xF5, 0xF0, 0xAB, 0x55, 0x8B, 0x46, 0x07, 0x60, 0x93, 0xAB, 0xCB} }, + { .pass={'f', 'o', 'o', 'p', 'a', 's', 's'}, .passlen=7, .salt={0x00, 'h', 'f', 's', 'd', 'j', 'h', 'f', 'd', 'j', 'h', 'j', 'd', 'f', 's'}, .saltlen=15, .expected={0x2B, 0xDF, 0x52, 0x29, 0x48, 0x3F, 0x98, 0x25, 0x01, 0x19, 0xB4, 0x42, 0xBC, 0xA7, 0x38, 0x5D, 0xCD, 0x08, 0xBD, 0xDC, 0x33, 0xBF, 0x32, 0x5E, 0x31, 0x87, 0x54, 0xFF, 0x2C, 0x23, 0x68, 0xFF} }, + { .pass={'f', 'o', 'o', 'p', 'a', 's', 's'}, .passlen=7, .salt={'m', 'y', 's', 'a', 0x00, 'l', 't'}, .saltlen=7, .expected={0x7C, 0x24, 0xB4, 0x4D, 0x30, 0x11, 0x53, 0x24, 0x87, 0x56, 0x24, 0x10, 0xBA, 0x9F, 0xF2, 0x4E, 0xBB, 0xF5, 0x03, 0x56, 0x2B, 0xB1, 0xA1, 0x92, 0x8B, 0x5F, 0x32, 0x02, 0x23, 0x1F, 0x79, 0xE6} }, + { .pass={'p', 'a', 's', 's', 'w', 'i', 't', 'h', 'n', 'u', 'l', 'l', 0x00, 'p', 'a', 's', 's', 'w', 'o', 'r', 'd'}, .passlen=21, .salt={'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5'}, .saltlen=16, .expected={0xE9, 0x53, 0xB7, 0x1D, 0xAB, 0xD1, 0xC1, 0xF3, 0xC4, 0x7F, 0x18, 0x96, 0xDD, 0xD7, 0x6B, 0xC6, 0x6A, 0xBD, 0xFB, 0x12, 0x7C, 0xF8, 0x68, 0xDC, 0x6E, 0xEF, 0x29, 0xCC, 0x1B, 0x30, 0x5B, 0x74} }, + { .pass={'p', 'a', 's', 's', 'w', 'i', 't', 'h', 'n', 'u', 'l', 'l', 0x00, 'p', 'a', 's', 's', 'w', 'o', 'r', 'd'}, .passlen=21, .salt={0x00, 'h', 'f', 's', 'd', 'j', 'h', 'f', 'd', 'j', 'h', 'j', 'd', 'f', 's'}, .saltlen=15, .expected={0x51, 0xA3, 0x82, 0xA5, 0x2F, 0x48, 0x84, 0xB3, 0x02, 0x0D, 0xC2, 0x42, 0x9A, 0x8F, 0x86, 0xCC, 0x66, 0xFD, 0x65, 0x87, 0x89, 0x07, 0x2B, 0x07, 0x82, 0x42, 0xD6, 0x6D, 0x43, 0xB8, 0xFD, 0xCF} }, + { .pass={'p', 'a', 's', 's', 'w', 'i', 't', 'h', 'n', 'u', 'l', 'l', 0x00, 'p', 'a', 's', 's', 'w', 'o', 'r', 'd'}, .passlen=21, .salt={'m', 'y', 's', 'a', 0x00, 'l', 't'}, .saltlen=7, .expected={0xEC, 0xFB, 0x5D, 0x5F, 0xF6, 0xA6, 0xE0, 0x79, 0x50, 0x64, 0x36, 0x64, 0xA3, 0x9A, 0x5C, 0xF3, 0x7A, 0x87, 0x0B, 0x64, 0x51, 0x59, 0x75, 0x64, 0x8B, 0x78, 0x2B, 0x62, 0x8F, 0x68, 0xD9, 0xCC} }, + { .pass={0x00, 'p', 'a', 's', 's'}, .passlen=5, .salt={'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5'}, .saltlen=16, .expected={0x8A, 0x9A, 0x47, 0x9A, 0x91, 0x22, 0x2F, 0x56, 0x29, 0x4F, 0x26, 0x00, 0xE7, 0xB3, 0xEB, 0x63, 0x6D, 0x51, 0xF2, 0x60, 0x17, 0x08, 0x20, 0x70, 0x82, 0x8F, 0xA3, 0xD7, 0xBE, 0x2B, 0xD5, 0x5D} }, + { .pass={0x00, 'p', 'a', 's', 's'}, .passlen=5, .salt={0x00, 'h', 'f', 's', 'd', 'j', 'h', 'f', 'd', 'j', 'h', 'j', 'd', 'f', 's'}, .saltlen=15, .expected={0x72, 0x3A, 0xF5, 0xF7, 0xCD, 0x6C, 0x12, 0xDD, 0x53, 0x28, 0x46, 0x0C, 0x19, 0x0E, 0xF2, 0x91, 0xDE, 0xEA, 0xF9, 0x6F, 0x74, 0x32, 0x34, 0x3F, 0x84, 0xED, 0x8D, 0x2A, 0xDE, 0xC9, 0xC6, 0x34} }, + { .pass={0x00, 'p', 'a', 's', 's'}, .passlen=5, .salt={'m', 'y', 's', 'a', 0x00, 'l', 't'}, .saltlen=7, .expected={0xE3, 0x07, 0x12, 0xBE, 0xEE, 0xF5, 0x5D, 0x18, 0x72, 0xF4, 0xCF, 0xF1, 0x20, 0x6B, 0xD6, 0x66, 0xCD, 0x7C, 0xE7, 0x4F, 0xC2, 0x16, 0x70, 0x5B, 0x9B, 0x2F, 0x7D, 0xE2, 0x3B, 0x42, 0x3A, 0x1B} }, + }; + + uint8_t res[SHA256_DIGEST_SIZE]; + for(size_t i = 0; i < sizeof(test_vectors)/sizeof(test_vectors[0]); i++) { + + int rc = tpm2_util_pbkdf2_hmac_sha256( + test_vectors[i].pass, + test_vectors[i].passlen, + test_vectors[i].salt, + test_vectors[i].saltlen, + res); + assert_se(rc == 0); + assert_se(memcmp(test_vectors[i].expected, res, SHA256_DIGEST_SIZE) == 0); + } +} + +#if HAVE_TPM2 + +#define POISON(type) \ + ({ \ + type _p; \ + memset(&_p, 0xaa, sizeof(_p)); \ + _p; \ + }) +#define POISON_TPML POISON(TPML_PCR_SELECTION) +#define POISON_TPMS POISON(TPMS_PCR_SELECTION) +#define POISON_U32 POISON(uint32_t) + +static void assert_tpms_pcr_selection_eq(TPMS_PCR_SELECTION *a, TPMS_PCR_SELECTION *b) { + assert_se(a); + assert_se(b); + + assert_se(a->hash == b->hash); + assert_se(a->sizeofSelect == b->sizeofSelect); + + for (size_t i = 0; i < a->sizeofSelect; i++) + assert_se(a->pcrSelect[i] == b->pcrSelect[i]); +} + +static void assert_tpml_pcr_selection_eq(TPML_PCR_SELECTION *a, TPML_PCR_SELECTION *b) { + assert_se(a); + assert_se(b); + + assert_se(a->count == b->count); + for (size_t i = 0; i < a->count; i++) + assert_tpms_pcr_selection_eq(&a->pcrSelections[i], &b->pcrSelections[i]); +} + +static void verify_tpms_pcr_selection(TPMS_PCR_SELECTION *s, uint32_t mask, TPMI_ALG_HASH hash) { + assert_se(s->hash == hash); + assert_se(s->sizeofSelect == 3); + assert_se(s->pcrSelect[0] == (mask & 0xff)); + assert_se(s->pcrSelect[1] == ((mask >> 8) & 0xff)); + assert_se(s->pcrSelect[2] == ((mask >> 16) & 0xff)); + assert_se(s->pcrSelect[3] == 0); + + assert_se(tpm2_tpms_pcr_selection_to_mask(s) == mask); +} + +static void verify_tpml_pcr_selection(TPML_PCR_SELECTION *l, TPMS_PCR_SELECTION s[], size_t count) { + assert_se(l->count == count); + for (size_t i = 0; i < count; i++) { + assert_tpms_pcr_selection_eq(&s[i], &l->pcrSelections[i]); + + TPMI_ALG_HASH hash = l->pcrSelections[i].hash; + verify_tpms_pcr_selection(&l->pcrSelections[i], tpm2_tpml_pcr_selection_to_mask(l, hash), hash); + } +} + +static void _test_pcr_selection_mask_hash(uint32_t mask, TPMI_ALG_HASH hash) { + TPMS_PCR_SELECTION s = POISON_TPMS; + tpm2_tpms_pcr_selection_from_mask(mask, hash, &s); + verify_tpms_pcr_selection(&s, mask, hash); + + TPML_PCR_SELECTION l = POISON_TPML; + tpm2_tpml_pcr_selection_from_mask(mask, hash, &l); + verify_tpml_pcr_selection(&l, &s, 1); + verify_tpms_pcr_selection(&l.pcrSelections[0], mask, hash); + + uint32_t test_masks[] = { + 0x0, 0x1, 0x100, 0x10000, 0xf0f0f0, 0xaaaaaa, 0xffffff, + }; + for (unsigned i = 0; i < ELEMENTSOF(test_masks); i++) { + uint32_t test_mask = test_masks[i]; + + TPMS_PCR_SELECTION a = POISON_TPMS, b = POISON_TPMS, test_s = POISON_TPMS; + tpm2_tpms_pcr_selection_from_mask(test_mask, hash, &test_s); + + a = s; + b = test_s; + tpm2_tpms_pcr_selection_add(&a, &b); + verify_tpms_pcr_selection(&a, UPDATE_FLAG(mask, test_mask, true), hash); + verify_tpms_pcr_selection(&b, test_mask, hash); + + a = s; + b = test_s; + tpm2_tpms_pcr_selection_sub(&a, &b); + verify_tpms_pcr_selection(&a, UPDATE_FLAG(mask, test_mask, false), hash); + verify_tpms_pcr_selection(&b, test_mask, hash); + + a = s; + b = test_s; + tpm2_tpms_pcr_selection_move(&a, &b); + verify_tpms_pcr_selection(&a, UPDATE_FLAG(mask, test_mask, true), hash); + verify_tpms_pcr_selection(&b, 0, hash); + } +} + +TEST(tpms_pcr_selection_mask_and_hash) { + TPMI_ALG_HASH HASH_ALGS[] = { TPM2_ALG_SHA1, TPM2_ALG_SHA256, }; + + for (unsigned i = 0; i < ELEMENTSOF(HASH_ALGS); i++) + for (uint32_t m2 = 0; m2 <= 0xffffff; m2 += 0x50000) + for (uint32_t m1 = 0; m1 <= 0xffff; m1 += 0x500) + for (uint32_t m0 = 0; m0 <= 0xff; m0 += 0x5) + _test_pcr_selection_mask_hash(m0 | m1 | m2, HASH_ALGS[i]); +} + +static void _test_tpms_sw( + TPMI_ALG_HASH hash, + uint32_t mask, + const char *expected_str, + size_t expected_weight) { + + TPMS_PCR_SELECTION s = POISON_TPMS; + tpm2_tpms_pcr_selection_from_mask(mask, hash, &s); + + _cleanup_free_ char *tpms_str = tpm2_tpms_pcr_selection_to_string(&s); + assert_se(streq(tpms_str, expected_str)); + + assert_se(tpm2_tpms_pcr_selection_weight(&s) == expected_weight); + assert_se(tpm2_tpms_pcr_selection_is_empty(&s) == (expected_weight == 0)); +} + +TEST(tpms_pcr_selection_string_and_weight) { + TPMI_ALG_HASH sha1 = TPM2_ALG_SHA1, sha256 = TPM2_ALG_SHA256; + + _test_tpms_sw(sha1, 0, "sha1()", 0); + _test_tpms_sw(sha1, 1, "sha1(0)", 1); + _test_tpms_sw(sha1, 0xf, "sha1(0+1+2+3)", 4); + _test_tpms_sw(sha1, 0x00ff00, "sha1(8+9+10+11+12+13+14+15)", 8); + _test_tpms_sw(sha1, 0xffffff, "sha1(0+1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20+21+22+23)", 24); + _test_tpms_sw(sha256, 0, "sha256()", 0); + _test_tpms_sw(sha256, 1, "sha256(0)", 1); + _test_tpms_sw(sha256, 7, "sha256(0+1+2)", 3); + _test_tpms_sw(sha256, 0xf00000, "sha256(20+21+22+23)", 4); + _test_tpms_sw(sha256, 0xffffff, "sha256(0+1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20+21+22+23)", 24); +} + +static void _tpml_pcr_selection_add_tpms(TPMS_PCR_SELECTION s[], size_t count, TPML_PCR_SELECTION *ret) { + for (size_t i = 0; i < count; i++) + tpm2_tpml_pcr_selection_add_tpms_pcr_selection(ret, &s[i]); +} + +static void _tpml_pcr_selection_sub_tpms(TPMS_PCR_SELECTION s[], size_t count, TPML_PCR_SELECTION *ret) { + for (size_t i = 0; i < count; i++) + tpm2_tpml_pcr_selection_sub_tpms_pcr_selection(ret, &s[i]); +} + +static void _test_tpml_sw( + TPMS_PCR_SELECTION s[], + size_t count, + size_t expected_count, + const char *expected_str, + size_t expected_weight) { + + TPML_PCR_SELECTION l = {}; + _tpml_pcr_selection_add_tpms(s, count, &l); + assert_se(l.count == expected_count); + + _cleanup_free_ char *tpml_str = tpm2_tpml_pcr_selection_to_string(&l); + assert_se(streq(tpml_str, expected_str)); + + assert_se(tpm2_tpml_pcr_selection_weight(&l) == expected_weight); + assert_se(tpm2_tpml_pcr_selection_is_empty(&l) == (expected_weight == 0)); +} + +TEST(tpml_pcr_selection_string_and_weight) { + size_t size = 0xaa; + TPMI_ALG_HASH sha1 = TPM2_ALG_SHA1, + sha256 = TPM2_ALG_SHA256, + sha384 = TPM2_ALG_SHA384, + sha512 = TPM2_ALG_SHA512; + TPMS_PCR_SELECTION s[4] = { POISON_TPMS, POISON_TPMS, POISON_TPMS, POISON_TPMS, }; + + size = 0; + tpm2_tpms_pcr_selection_from_mask(0x000002, sha1 , &s[size++]); + tpm2_tpms_pcr_selection_from_mask(0x0080f0, sha384, &s[size++]); + tpm2_tpms_pcr_selection_from_mask(0x010100, sha512, &s[size++]); + tpm2_tpms_pcr_selection_from_mask(0xff0000, sha256, &s[size++]); + _test_tpml_sw(s, + size, + /* expected_count= */ 4, + "[sha1(1),sha384(4+5+6+7+15),sha512(8+16),sha256(16+17+18+19+20+21+22+23)]", + /* expected_weight= */ 16); + + size = 0; + tpm2_tpms_pcr_selection_from_mask(0x0403aa, sha512, &s[size++]); + tpm2_tpms_pcr_selection_from_mask(0x0080f0, sha256, &s[size++]); + _test_tpml_sw(s, + size, + /* expected_count= */ 2, + "[sha512(1+3+5+7+8+9+18),sha256(4+5+6+7+15)]", + /* expected_weight= */ 12); + + size = 0; + /* Empty hashes should be ignored */ + tpm2_tpms_pcr_selection_from_mask(0x0300ce, sha384, &s[size++]); + tpm2_tpms_pcr_selection_from_mask(0xffffff, sha512, &s[size++]); + tpm2_tpms_pcr_selection_from_mask(0x000000, sha1 , &s[size++]); + tpm2_tpms_pcr_selection_from_mask(0x330010, sha256, &s[size++]); + _test_tpml_sw(s, + size, + /* expected_count= */ 3, + "[sha384(1+2+3+6+7+16+17),sha512(0+1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20+21+22+23),sha256(4+16+17+20+21)]", + /* expected_weight= */ 36); + + size = 0; + /* Verify same-hash entries are properly combined. */ + tpm2_tpms_pcr_selection_from_mask(0x000001, sha1 , &s[size++]); + tpm2_tpms_pcr_selection_from_mask(0x000001, sha256, &s[size++]); + tpm2_tpms_pcr_selection_from_mask(0x000010, sha1 , &s[size++]); + tpm2_tpms_pcr_selection_from_mask(0x000010, sha256, &s[size++]); + _test_tpml_sw(s, + size, + /* expected_count= */ 2, + "[sha1(0+4),sha256(0+4)]", + /* expected_weight= */ 4); +} + +/* Test tpml add/sub by changing the tpms individually */ +static void _test_tpml_addsub_tpms( + TPML_PCR_SELECTION *start, + TPMS_PCR_SELECTION add[], + size_t add_count, + TPMS_PCR_SELECTION expected1[], + size_t expected1_count, + TPMS_PCR_SELECTION sub[], + size_t sub_count, + TPMS_PCR_SELECTION expected2[], + size_t expected2_count) { + + TPML_PCR_SELECTION l = *start; + + _tpml_pcr_selection_add_tpms(add, add_count, &l); + verify_tpml_pcr_selection(&l, expected1, expected1_count); + + _tpml_pcr_selection_sub_tpms(sub, sub_count, &l); + verify_tpml_pcr_selection(&l, expected2, expected2_count); +} + +/* Test tpml add/sub by creating new tpmls */ +static void _test_tpml_addsub_tpml( + TPML_PCR_SELECTION *start, + TPMS_PCR_SELECTION add[], + size_t add_count, + TPMS_PCR_SELECTION expected1[], + size_t expected1_count, + TPMS_PCR_SELECTION sub[], + size_t sub_count, + TPMS_PCR_SELECTION expected2[], + size_t expected2_count) { + + TPML_PCR_SELECTION l = {}; + tpm2_tpml_pcr_selection_add(&l, start); + assert_tpml_pcr_selection_eq(&l, start); + + TPML_PCR_SELECTION addl = {}; + _tpml_pcr_selection_add_tpms(add, add_count, &addl); + tpm2_tpml_pcr_selection_add(&l, &addl); + + TPML_PCR_SELECTION e1 = {}; + _tpml_pcr_selection_add_tpms(expected1, expected1_count, &e1); + assert_tpml_pcr_selection_eq(&l, &e1); + + TPML_PCR_SELECTION subl = {}; + _tpml_pcr_selection_add_tpms(sub, sub_count, &subl); + tpm2_tpml_pcr_selection_sub(&l, &subl); + + TPML_PCR_SELECTION e2 = {}; + _tpml_pcr_selection_add_tpms(expected2, expected2_count, &e2); + assert_tpml_pcr_selection_eq(&l, &e2); +} + +#define _test_tpml_addsub(...) \ + ({ \ + _test_tpml_addsub_tpms(__VA_ARGS__); \ + _test_tpml_addsub_tpml(__VA_ARGS__); \ + }) + +TEST(tpml_pcr_selection_add_sub) { + size_t add_count = 0xaa, expected1_count = 0xaa, sub_count = 0xaa, expected2_count = 0xaa; + TPMI_ALG_HASH sha1 = TPM2_ALG_SHA1, + sha256 = TPM2_ALG_SHA256, + sha384 = TPM2_ALG_SHA384, + sha512 = TPM2_ALG_SHA512; + TPML_PCR_SELECTION l = POISON_TPML; + TPMS_PCR_SELECTION add[4] = { POISON_TPMS, POISON_TPMS, POISON_TPMS, POISON_TPMS, }, + sub[4] = { POISON_TPMS, POISON_TPMS, POISON_TPMS, POISON_TPMS, }, + expected1[4] = { POISON_TPMS, POISON_TPMS, POISON_TPMS, POISON_TPMS, }, + expected2[4] = { POISON_TPMS, POISON_TPMS, POISON_TPMS, POISON_TPMS, }; + + l = (TPML_PCR_SELECTION){}; + add_count = 0; + expected1_count = 0; + sub_count = 0; + expected2_count = 0; + tpm2_tpms_pcr_selection_from_mask(0x010101, sha256, &add[add_count++]); + tpm2_tpms_pcr_selection_from_mask(0x101010, sha256, &add[add_count++]); + tpm2_tpms_pcr_selection_from_mask(0x0000ff, sha512, &add[add_count++]); + tpm2_tpms_pcr_selection_from_mask(0x111111, sha256, &expected1[expected1_count++]); + tpm2_tpms_pcr_selection_from_mask(0x0000ff, sha512, &expected1[expected1_count++]); + tpm2_tpms_pcr_selection_from_mask(0x000001, sha256, &sub[sub_count++]); + tpm2_tpms_pcr_selection_from_mask(0xff0000, sha512, &sub[sub_count++]); + tpm2_tpms_pcr_selection_from_mask(0x111110, sha256, &expected2[expected2_count++]); + tpm2_tpms_pcr_selection_from_mask(0x0000ff, sha512, &expected2[expected2_count++]); + _test_tpml_addsub(&l, + add, add_count, + expected1, expected1_count, + sub, sub_count, + expected2, expected2_count); + + l = (TPML_PCR_SELECTION){ + .count = 1, + .pcrSelections[0].hash = sha1, + .pcrSelections[0].sizeofSelect = 3, + .pcrSelections[0].pcrSelect[0] = 0xf0, + }; + add_count = 0; + expected1_count = 0; + sub_count = 0; + expected2_count = 0; + tpm2_tpms_pcr_selection_from_mask(0xff0000, sha256, &add[add_count++]); + tpm2_tpms_pcr_selection_from_mask(0xffff00, sha384, &add[add_count++]); + tpm2_tpms_pcr_selection_from_mask(0x0000ff, sha512, &add[add_count++]); + tpm2_tpms_pcr_selection_from_mask(0xf00000, sha1 , &add[add_count++]); + tpm2_tpms_pcr_selection_from_mask(0xf000f0, sha1 , &expected1[expected1_count++]); + tpm2_tpms_pcr_selection_from_mask(0xff0000, sha256, &expected1[expected1_count++]); + tpm2_tpms_pcr_selection_from_mask(0xffff00, sha384, &expected1[expected1_count++]); + tpm2_tpms_pcr_selection_from_mask(0x0000ff, sha512, &expected1[expected1_count++]); + tpm2_tpms_pcr_selection_from_mask(0x00ffff, sha256, &sub[sub_count++]); + tpm2_tpms_pcr_selection_from_mask(0xf000f0, sha1 , &expected2[expected2_count++]); + tpm2_tpms_pcr_selection_from_mask(0xff0000, sha256, &expected2[expected2_count++]); + tpm2_tpms_pcr_selection_from_mask(0xffff00, sha384, &expected2[expected2_count++]); + tpm2_tpms_pcr_selection_from_mask(0x0000ff, sha512, &expected2[expected2_count++]); + _test_tpml_addsub(&l, + add, add_count, + expected1, expected1_count, + sub, sub_count, + expected2, expected2_count); +} + +static bool digest_check(const TPM2B_DIGEST *digest, const char *expect) { + _cleanup_free_ char *h = NULL; + + assert_se(digest); + assert_se(expect); + + h = hexmem(digest->buffer, digest->size); + assert_se(h); + + return strcaseeq(expect, h); +} + +static void digest_init(TPM2B_DIGEST *digest, const char *hash) { + assert_se(strlen(hash) <= sizeof(digest->buffer) * 2); + + DEFINE_HEX_PTR(h, hash); + + /* Make sure the length matches a known hash algorithm */ + assert_se(IN_SET(h_len, TPM2_SHA1_DIGEST_SIZE, TPM2_SHA256_DIGEST_SIZE, TPM2_SHA384_DIGEST_SIZE, TPM2_SHA512_DIGEST_SIZE)); + + *digest = TPM2B_DIGEST_MAKE(h, h_len); + + assert_se(digest_check(digest, hash)); +} + +TEST(digest_many) { + TPM2B_DIGEST d, d0, d1, d2, d3, d4; + + digest_init(&d0, "0000000000000000000000000000000000000000000000000000000000000000"); + digest_init(&d1, "17b7703d9d00776310ba032e88c1a8c2a9c630ebdd799db622f6631530789175"); + digest_init(&d2, "12998c017066eb0d2a70b94e6ed3192985855ce390f321bbdb832022888bd251"); + digest_init(&d3, "c3a65887fedd3fb4f5d0047e906dff830bcbd1293160909eb4b05f485e7387ad"); + digest_init(&d4, "6491fb4bc08fc0b2ef47fc63db57e249917885e69d8c0d99667df83a59107a33"); + + /* tpm2_digest_init, tpm2_digest_rehash */ + d = (TPM2B_DIGEST){ .size = 1, .buffer = { 2, }, }; + assert_se(tpm2_digest_init(TPM2_ALG_SHA256, &d) == 0); + assert_se(digest_check(&d, "0000000000000000000000000000000000000000000000000000000000000000")); + assert_se(tpm2_digest_rehash(TPM2_ALG_SHA256, &d) == 0); + assert_se(digest_check(&d, "66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925")); + + d = d1; + assert_se(tpm2_digest_rehash(TPM2_ALG_SHA256, &d) == 0); + assert_se(digest_check(&d, "ab55014b5ace12ba70c3acc887db571585a83539aad3633d252a710f268f405c")); + assert_se(tpm2_digest_init(TPM2_ALG_SHA256, &d) == 0); + assert_se(digest_check(&d, "0000000000000000000000000000000000000000000000000000000000000000")); + + /* tpm2_digest_many_digests */ + assert_se(tpm2_digest_many_digests(TPM2_ALG_SHA256, &d, &d2, 1, false) == 0); + assert_se(digest_check(&d, "56571a1be3fbeab18d215f549095915a004b5788ca0d535be668559129a76f25")); + assert_se(tpm2_digest_many_digests(TPM2_ALG_SHA256, &d, &d2, 1, true) == 0); + assert_se(digest_check(&d, "99dedaee8f4d8d10a8be184399fde8740d5e17ff783ee5c288a4486e4ce3a1fe")); + + const TPM2B_DIGEST da1[] = { d2, d3, }; + assert_se(tpm2_digest_many_digests(TPM2_ALG_SHA256, &d, da1, ELEMENTSOF(da1), false) == 0); + assert_se(digest_check(&d, "525aa13ef9a61827778ec3acf16fbb23b65ae8770b8fb2684d3a33f9457dd6d8")); + assert_se(tpm2_digest_many_digests(TPM2_ALG_SHA256, &d, da1, ELEMENTSOF(da1), true) == 0); + assert_se(digest_check(&d, "399ca2aa98963d1bd81a2b58a7e5cda24bba1be88fb4da9aa73d97706846566b")); + + const TPM2B_DIGEST da2[] = { d3, d2, d0 }; + assert_se(tpm2_digest_many_digests(TPM2_ALG_SHA256, &d, da2, ELEMENTSOF(da2), false) == 0); + assert_se(digest_check(&d, "b26fd22db74d4cd896bff01c61aa498a575e4a553a7fb5a322a5fee36954313e")); + assert_se(tpm2_digest_many_digests(TPM2_ALG_SHA256, &d, da2, ELEMENTSOF(da2), true) == 0); + assert_se(digest_check(&d, "091e79a5b09d4048df49a680f966f3ff67910afe185c3baf9704c9ca45bcf259")); + + const TPM2B_DIGEST da3[] = { d4, d4, d4, d4, d3, d4, d4, d4, d4, }; + assert_se(tpm2_digest_many_digests(TPM2_ALG_SHA256, &d, da3, ELEMENTSOF(da3), false) == 0); + assert_se(digest_check(&d, "8eca947641b6002df79dfb571a7f78b7d0a61370a366f722386dfbe444d18830")); + assert_se(tpm2_digest_many_digests(TPM2_ALG_SHA256, &d, da3, ELEMENTSOF(da3), true) == 0); + assert_se(digest_check(&d, "f9ba17bc0bbe8794e9bcbf112e4d59a11eb68fffbcd5516a746e4857829dff04")); + + /* tpm2_digest_buffer */ + const uint8_t b1[] = { 1, 2, 3, 4, }; + assert_se(tpm2_digest_buffer(TPM2_ALG_SHA256, &d, b1, ELEMENTSOF(b1), false) == 0); + assert_se(digest_check(&d, "9f64a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + assert_se(tpm2_digest_buffer(TPM2_ALG_SHA256, &d, b1, ELEMENTSOF(b1), true) == 0); + assert_se(digest_check(&d, "ff3bd307b287e9b29bb572f6ccfd19deb0106d0c4c3c5cfe8a1d03a396092ed4")); + + const void *b2 = d2.buffer; + assert_se(tpm2_digest_buffer(TPM2_ALG_SHA256, &d, b2, d2.size, false) == 0); + assert_se(digest_check(&d, "56571a1be3fbeab18d215f549095915a004b5788ca0d535be668559129a76f25")); + assert_se(tpm2_digest_buffer(TPM2_ALG_SHA256, &d, b2, d2.size, true) == 0); + assert_se(digest_check(&d, "99dedaee8f4d8d10a8be184399fde8740d5e17ff783ee5c288a4486e4ce3a1fe")); + + /* tpm2_digest_many */ + const struct iovec iov1[] = { + IOVEC_MAKE((void*) b1, ELEMENTSOF(b1)), + IOVEC_MAKE(d2.buffer, d2.size), + IOVEC_MAKE(d3.buffer, d3.size), + }; + assert_se(tpm2_digest_many(TPM2_ALG_SHA256, &d, iov1, ELEMENTSOF(iov1), false) == 0); + assert_se(digest_check(&d, "cd7bde4a047af976b6f1b282309976229be59f96a78aa186de32a1aee488ab09")); + assert_se(tpm2_digest_many(TPM2_ALG_SHA256, &d, iov1, ELEMENTSOF(iov1), true) == 0); + assert_se(digest_check(&d, "02ecb0628264235111e0053e271092981c8b15d59cd46617836bee3149a4ecb0")); +} + +static void check_parse_pcr_argument( + const char *arg, + const Tpm2PCRValue *prev_values, + size_t n_prev_values, + const Tpm2PCRValue *expected_values, + size_t n_expected_values) { + + _cleanup_free_ Tpm2PCRValue *values = NULL; + size_t n_values = 0; + + if (n_prev_values > 0) { + assert_se(GREEDY_REALLOC_APPEND(values, n_values, prev_values, n_prev_values)); + assert_se(tpm2_parse_pcr_argument_append(arg, &values, &n_values) == 0); + } else + assert_se(tpm2_parse_pcr_argument(arg, &values, &n_values) == 0); + + assert_se(n_values == n_expected_values); + for (size_t i = 0; i < n_values; i++) { + const Tpm2PCRValue *v = &values[i], *e = &expected_values[i]; + //tpm2_log_debug_pcr_value(e, "Expected value"); + //tpm2_log_debug_pcr_value(v, "Actual value"); + + assert_se(v->index == e->index); + assert_se(v->hash == e->hash); + assert_se(v->value.size == e->value.size); + assert_se(memcmp(v->value.buffer, e->value.buffer, e->value.size) == 0); + } + + size_t hash_count; + assert_se(tpm2_pcr_values_hash_count(expected_values, n_expected_values, &hash_count) == 0); + if (hash_count == 1) { + uint32_t mask = UINT32_MAX, expected_mask = 0; + + if (n_prev_values > 0) + assert_se(tpm2_pcr_values_to_mask(prev_values, n_prev_values, prev_values[0].hash, &mask) == 0); + + assert_se(tpm2_pcr_values_to_mask(expected_values, n_expected_values, expected_values[0].hash, &expected_mask) == 0); + + assert_se(tpm2_parse_pcr_argument_to_mask(arg, &mask) == 0); + assert_se(mask == expected_mask); + } + + size_t old_n_values = n_values; + assert_se(tpm2_parse_pcr_argument_append("", &values, &n_values) == 0); + assert_se(values); + assert_se(n_values == old_n_values); +} + +static void check_parse_pcr_argument_to_mask(const char *arg, int mask) { + uint32_t m = 0; + int r = tpm2_parse_pcr_argument_to_mask(arg, &m); + + if (mask < 0) + assert_se(mask == r); + else + assert_se((uint32_t) mask == m); +} + +TEST(parse_pcr_argument) { + _cleanup_free_ Tpm2PCRValue *t0p = NULL; + size_t n_t0p; + assert_se(tpm2_parse_pcr_argument("", &t0p, &n_t0p) == 0); + assert_se(n_t0p == 0); + assert_se(tpm2_parse_pcr_argument_append("", &t0p, &n_t0p) == 0); + assert_se(n_t0p == 0); + uint32_t m0 = 0xf; + assert_se(tpm2_parse_pcr_argument_to_mask("", &m0) == 0); + assert_se(m0 == 0); + assert_se(tpm2_parse_pcr_argument_to_mask("", &m0) == 0); + assert_se(m0 == 0); + + Tpm2PCRValue t1[] = { + TPM2_PCR_VALUE_MAKE(0, 0, {}), + TPM2_PCR_VALUE_MAKE(4, 0, {}), + TPM2_PCR_VALUE_MAKE(7, 0, {}), + TPM2_PCR_VALUE_MAKE(11, 0, {}), + }; + check_parse_pcr_argument("0,4,7,11", NULL, 0, t1, ELEMENTSOF(t1)); + check_parse_pcr_argument("11,4,7,0", NULL, 0, t1, ELEMENTSOF(t1)); + check_parse_pcr_argument("7,4,0,11", NULL, 0, t1, ELEMENTSOF(t1)); + check_parse_pcr_argument("11,7,4,0", NULL, 0, t1, ELEMENTSOF(t1)); + check_parse_pcr_argument("0+4+7+11", NULL, 0, t1, ELEMENTSOF(t1)); + check_parse_pcr_argument("0,4+7,11", NULL, 0, t1, ELEMENTSOF(t1)); + + Tpm2PCRValue t2[] = { + TPM2_PCR_VALUE_MAKE(0, TPM2_ALG_SHA1, {}), + TPM2_PCR_VALUE_MAKE(4, TPM2_ALG_SHA1, {}), + TPM2_PCR_VALUE_MAKE(7, TPM2_ALG_SHA1, {}), + TPM2_PCR_VALUE_MAKE(11, TPM2_ALG_SHA1, {}), + }; + check_parse_pcr_argument("0:sha1,4,7,11", NULL, 0, t2, ELEMENTSOF(t2)); + check_parse_pcr_argument("11,4,7,0:sha1", NULL, 0, t2, ELEMENTSOF(t2)); + check_parse_pcr_argument("7,4:sha1,0,11", NULL, 0, t2, ELEMENTSOF(t2)); + check_parse_pcr_argument("0:sha1,4:sha1,7:sha1,11:sha1", NULL, 0, t2, ELEMENTSOF(t2)); + check_parse_pcr_argument("0:sha1+4:sha1,11:sha1+7:sha1", NULL, 0, t2, ELEMENTSOF(t2)); + + Tpm2PCRValue t3[] = { + TPM2_PCR_VALUE_MAKE(0, TPM2_ALG_SHA1, {}), + TPM2_PCR_VALUE_MAKE(1, TPM2_ALG_SHA1, {}), + TPM2_PCR_VALUE_MAKE(2, TPM2_ALG_SHA1, {}), + TPM2_PCR_VALUE_MAKE(3, TPM2_ALG_SHA1, {}), + TPM2_PCR_VALUE_MAKE(4, TPM2_ALG_SHA1, {}), + TPM2_PCR_VALUE_MAKE(7, TPM2_ALG_SHA1, {}), + TPM2_PCR_VALUE_MAKE(11, TPM2_ALG_SHA1, {}), + TPM2_PCR_VALUE_MAKE(12, TPM2_ALG_SHA1, {}), + }; + check_parse_pcr_argument("1,2,3,12", t2, ELEMENTSOF(t2), t3, ELEMENTSOF(t3)); + check_parse_pcr_argument("12,2,3,1", t2, ELEMENTSOF(t2), t3, ELEMENTSOF(t3)); + check_parse_pcr_argument("1,2,3,12:sha1", t1, ELEMENTSOF(t1), t3, ELEMENTSOF(t3)); + check_parse_pcr_argument("1,2,3,12:sha1", t2, ELEMENTSOF(t2), t3, ELEMENTSOF(t3)); + check_parse_pcr_argument("1:sha1,2,3,12", t1, ELEMENTSOF(t1), t3, ELEMENTSOF(t3)); + check_parse_pcr_argument("1:sha1,2,3,12", t2, ELEMENTSOF(t2), t3, ELEMENTSOF(t3)); + check_parse_pcr_argument("1:sha1,2:sha1,3:sha1,12:sha1", t1, ELEMENTSOF(t1), t3, ELEMENTSOF(t3)); + check_parse_pcr_argument("1:sha1,2:sha1,3:sha1,12:sha1", t2, ELEMENTSOF(t2), t3, ELEMENTSOF(t3)); + + TPM2B_DIGEST d4; + digest_init(&d4, "FCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2"); + Tpm2PCRValue t4[] = { + TPM2_PCR_VALUE_MAKE(0, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(1, TPM2_ALG_SHA256, d4), + TPM2_PCR_VALUE_MAKE(2, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(3, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(4, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(7, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(11, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(12, TPM2_ALG_SHA256, {}), + }; + check_parse_pcr_argument("1:sha256=0xFCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2,2,3,12", t1, ELEMENTSOF(t1), t4, ELEMENTSOF(t4)); + check_parse_pcr_argument("12,2,3,1:sha256=FCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2", t1, ELEMENTSOF(t1), t4, ELEMENTSOF(t4)); + check_parse_pcr_argument("12,2,3,1:sha256=0xFCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2", t1, ELEMENTSOF(t1), t4, ELEMENTSOF(t4)); + check_parse_pcr_argument("1:sha256=0xFCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2,2,3,12:SHA256", t1, ELEMENTSOF(t1), t4, ELEMENTSOF(t4)); + check_parse_pcr_argument("1:sha256=0xFCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2,2,3,12", t1, ELEMENTSOF(t1), t4, ELEMENTSOF(t4)); + check_parse_pcr_argument("1:sha256=FCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2,2:sha256,3:sha256,12:sha256", t1, ELEMENTSOF(t1), t4, ELEMENTSOF(t4)); + check_parse_pcr_argument("1:sha256=0xFCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2,2:sha256,3:sha256,12:sha256", t1, ELEMENTSOF(t1), t4, ELEMENTSOF(t4)); + + TPM2B_DIGEST d5; + digest_init(&d5, "0F21EADB7F27377668E3C8069BE88D116491FBEE"); + Tpm2PCRValue t5[] = { + TPM2_PCR_VALUE_MAKE(1, TPM2_ALG_SHA1, d5), + TPM2_PCR_VALUE_MAKE(0, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(1, TPM2_ALG_SHA256, d4), + TPM2_PCR_VALUE_MAKE(2, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(3, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(4, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(7, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(11, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(12, TPM2_ALG_SHA256, {}), + TPM2_PCR_VALUE_MAKE(5, TPM2_ALG_SHA384, {}), + TPM2_PCR_VALUE_MAKE(6, TPM2_ALG_SHA512, {}), + }; + check_parse_pcr_argument("0,1:sha256=0xFCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2,1:sha1=0F21EADB7F27377668E3C8069BE88D116491FBEE,2,3,4,7,11,12,5:sha384,6:sha512", NULL, 0, t5, ELEMENTSOF(t5)); + check_parse_pcr_argument("1:sha1=0F21EADB7F27377668E3C8069BE88D116491FBEE,6:sha512,5:sha384", t4, ELEMENTSOF(t4), t5, ELEMENTSOF(t5)); + + Tpm2PCRValue *v = NULL; + size_t n_v = 0; + assert_se(tpm2_parse_pcr_argument("1,100", &v, &n_v) < 0); + assert_se(tpm2_parse_pcr_argument("1,2=123456abc", &v, &n_v) < 0); + assert_se(tpm2_parse_pcr_argument("1,2:invalid", &v, &n_v) < 0); + assert_se(tpm2_parse_pcr_argument("1:sha1=invalid", &v, &n_v) < 0); + assert_se(v == NULL); + assert_se(n_v == 0); + + check_parse_pcr_argument_to_mask("", 0x0); + check_parse_pcr_argument_to_mask("0", 0x1); + check_parse_pcr_argument_to_mask("1", 0x2); + check_parse_pcr_argument_to_mask("0,1", 0x3); + check_parse_pcr_argument_to_mask("0+1", 0x3); + check_parse_pcr_argument_to_mask("0-1", -EINVAL); + check_parse_pcr_argument_to_mask("foo", -EINVAL); + check_parse_pcr_argument_to_mask("0,1,2", 0x7); + check_parse_pcr_argument_to_mask("0+1+2", 0x7); + check_parse_pcr_argument_to_mask("0+1,2", 0x7); + check_parse_pcr_argument_to_mask("0,1+2", 0x7); + check_parse_pcr_argument_to_mask("0,2", 0x5); + check_parse_pcr_argument_to_mask("0+2", 0x5); + check_parse_pcr_argument_to_mask("7+application-support", 0x800080); + check_parse_pcr_argument_to_mask("8+boot-loader-code", 0x110); + check_parse_pcr_argument_to_mask("7,shim-policy,4", 0x4090); + check_parse_pcr_argument_to_mask("sysexts,shim-policy+kernel-boot", 0x6800); + check_parse_pcr_argument_to_mask("sysexts,shim+kernel-boot", -EINVAL); + check_parse_pcr_argument_to_mask("sysexts+17+23", 0x822000); + check_parse_pcr_argument_to_mask("6+boot-loader-code,44", -EINVAL); + check_parse_pcr_argument_to_mask("debug+24", -EINVAL); +} + +static const TPMT_PUBLIC test_rsa_template = { + .type = TPM2_ALG_RSA, + .nameAlg = TPM2_ALG_SHA256, + .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, + .parameters.rsaDetail = { + .symmetric = { + .algorithm = TPM2_ALG_AES, + .keyBits.aes = 128, + .mode.aes = TPM2_ALG_CFB, + }, + .scheme.scheme = TPM2_ALG_NULL, + .keyBits = 2048, + }, +}; + +static const TPMT_PUBLIC test_ecc_template = { + .type = TPM2_ALG_ECC, + .nameAlg = TPM2_ALG_SHA256, + .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, + .parameters.eccDetail = { + .symmetric = { + .algorithm = TPM2_ALG_AES, + .keyBits.aes = 128, + .mode.aes = TPM2_ALG_CFB, + }, + .scheme.scheme = TPM2_ALG_NULL, + .curveID = TPM2_ECC_NIST_P256, + .kdf.scheme = TPM2_ALG_NULL, + }, +}; + +static const TPMT_PUBLIC *test_templates[] = { + &test_rsa_template, + &test_ecc_template, +}; + +static void tpm2b_public_rsa_init(TPM2B_PUBLIC *public, const char *rsa_n) { + TPMT_PUBLIC tpmt = test_rsa_template; + + DEFINE_HEX_PTR(key, rsa_n); + tpmt.unique.rsa = TPM2B_PUBLIC_KEY_RSA_MAKE(key, key_len); + + public->size = sizeof(tpmt); + public->publicArea = tpmt; +} + +static void tpm2b_public_ecc_init(TPM2B_PUBLIC *public, TPMI_ECC_CURVE curve, const char *x, const char *y) { + TPMT_PUBLIC tpmt = test_ecc_template; + tpmt.parameters.eccDetail.curveID = curve; + + DEFINE_HEX_PTR(buf_x, x); + tpmt.unique.ecc.x = TPM2B_ECC_PARAMETER_MAKE(buf_x, buf_x_len); + + DEFINE_HEX_PTR(buf_y, y); + tpmt.unique.ecc.y = TPM2B_ECC_PARAMETER_MAKE(buf_y, buf_y_len); + + public->size = sizeof(tpmt); + public->publicArea = tpmt; +} + +#if HAVE_OPENSSL +TEST(tpm2b_public_to_openssl_pkey) { + DEFINE_HEX_PTR(msg, "edc64c6523778961fe9ba03ab7d624b27ca1dd5b01e7734cc6c891d50db04269"); + TPM2B_PUBLIC public; + + /* RSA */ + tpm2b_public_rsa_init(&public, "d71cff5bba2173f0434a389171048e7da8cf8409b892c62946481cc383089bc754324620967fea3d00a02a717cdda4bfe1525ad957d294b88434e0a3933e86fb40f234e4935fd2ba27eb1d21da87efa466b74eb4ad18d26059904643441cf402ee933d138a2151f40459c49d87fef59e2cb822768b2d8689a9b58f82bf9a37e70693f2b2d40dfa388d365c1b1f029a14c4fc8dadb68978ef377d20ff2ca24e7078464c705eab42f531557c9c6dc0df66b506d0c26ef604f8110c64867099267453c71871e7ed22505a09daf102afc34355209ca7680eccc0ed368d148f402fa58cbb6c9d52351f535f09e4e24ad805e149f130edaa2f5e7efed3a4d2d03adb85"); + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_rsa = NULL; + assert_se(tpm2_tpm2b_public_to_openssl_pkey(&public, &pkey_rsa) >= 0); + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx_rsa = EVP_PKEY_CTX_new((EVP_PKEY*) pkey_rsa, NULL); + assert_se(ctx_rsa); + assert_se(EVP_PKEY_verify_init(ctx_rsa) == 1); + assert_se(EVP_PKEY_CTX_set_signature_md(ctx_rsa, EVP_sha256()) > 0); + + DEFINE_HEX_PTR(sig_rsa, "9f70a9e68911be3ec464cae91126328307bf355872127e042d6c61e0a80982872c151033bcf727abfae5fc9500c923120011e7ef4aa5fc690a59a034697b6022c141b4b209e2df6f4b282288cd9181073fbe7158ce113c79d87623423c1f3996ff931e59cc91db74f8e8656215b1436fc93ddec0f1f8fa8510826e674b250f047e6cba94c95ff98072a286baca94646b577974a1e00d56c21944e38960d8ee90511a2f938e5cf1ac7b7cc7ff8e3ac001d321254d3e4f988b90e9f6f873c26ecd0a12a626b3474833cdbb9e9f793238f6c97ee5b75a1a89bb7a7858d34ecfa6d34ac58d95085e6c4fbbebd47a4364be2725c2c6b3fa15d916f3c0b62a66fe76ae"); + assert_se(EVP_PKEY_verify(ctx_rsa, sig_rsa, sig_rsa_len, (unsigned char*) msg, msg_len) == 1); + + /* ECC */ + tpm2b_public_ecc_init(&public, TPM2_ECC_NIST_P256, "6fc0ecf3645c673ab7e86d1ec5b315afb950257c5f68ab23296160006711fac2", "8dd2ef7a2c9ecede91493ba98c8fb3f893aff325c6a1e0f752c657b2d6ca1413"); + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_ecc = NULL; + assert_se(tpm2_tpm2b_public_to_openssl_pkey(&public, &pkey_ecc) >= 0); + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx_ecc = EVP_PKEY_CTX_new((EVP_PKEY*) pkey_ecc, NULL); + assert_se(ctx_ecc); + assert_se(EVP_PKEY_verify_init(ctx_ecc) == 1); + + DEFINE_HEX_PTR(sig_ecc, "304602210092447ac0b5b32e90923f79bb4aba864b9c546a9900cf193a83243d35d189a2110221009a8b4df1dfa85e225eff9c606694d4d205a7a3968c9552f50bc2790209a90001"); + assert_se(EVP_PKEY_verify(ctx_ecc, sig_ecc, sig_ecc_len, (unsigned char*) msg, msg_len) == 1); +} + +static void get_tpm2b_public_from_pem(const void *pem, size_t pem_size, TPM2B_PUBLIC *ret) { + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + TPM2B_PUBLIC p1 = {}, p2 = {}; + + assert(pem); + assert(ret); + + assert_se(openssl_pkey_from_pem(pem, pem_size, &pkey) >= 0); + assert_se(tpm2_tpm2b_public_from_openssl_pkey(pkey, &p1) >= 0); + assert_se(tpm2_tpm2b_public_from_pem(pem, pem_size, &p2) >= 0); + assert_se(memcmp_nn(&p1, sizeof(p1), &p2, sizeof(p2)) == 0); + + *ret = p1; +} + +static void check_tpm2b_public_fingerprint(const TPM2B_PUBLIC *public, const char *hexfp) { + DEFINE_HEX_PTR(expected, hexfp); + _cleanup_free_ void *fp = NULL; + size_t fp_size; + + assert_se(tpm2_tpm2b_public_to_fingerprint(public, &fp, &fp_size) >= 0); + assert_se(memcmp_nn(fp, fp_size, expected, expected_len) == 0); +} + +static void check_tpm2b_public_name(const TPM2B_PUBLIC *public, const char *hexname) { + DEFINE_HEX_PTR(expected, hexname); + TPM2B_NAME name = {}; + + assert_se(tpm2_calculate_pubkey_name(&public->publicArea, &name) >= 0); + assert_se(memcmp_nn(name.name, name.size, expected, expected_len) == 0); +} + +static void check_tpm2b_public_from_ecc_pem(const char *pem, const char *hexx, const char *hexy, const char *hexfp, const char *hexname) { + TPM2B_PUBLIC public = {}; + TPMT_PUBLIC *p = &public.publicArea; + + DEFINE_HEX_PTR(key, pem); + get_tpm2b_public_from_pem(key, key_len, &public); + + assert_se(p->type == TPM2_ALG_ECC); + assert_se(p->parameters.eccDetail.curveID == TPM2_ECC_NIST_P256); + + DEFINE_HEX_PTR(expected_x, hexx); + assert_se(memcmp_nn(p->unique.ecc.x.buffer, p->unique.ecc.x.size, expected_x, expected_x_len) == 0); + + DEFINE_HEX_PTR(expected_y, hexy); + assert_se(memcmp_nn(p->unique.ecc.y.buffer, p->unique.ecc.y.size, expected_y, expected_y_len) == 0); + + check_tpm2b_public_fingerprint(&public, hexfp); + check_tpm2b_public_name(&public, hexname); +} + +static void check_tpm2b_public_from_rsa_pem(const char *pem, const char *hexn, uint32_t exponent, const char *hexfp, const char *hexname) { + TPM2B_PUBLIC public = {}; + TPMT_PUBLIC *p = &public.publicArea; + + DEFINE_HEX_PTR(key, pem); + get_tpm2b_public_from_pem(key, key_len, &public); + + assert_se(p->type == TPM2_ALG_RSA); + + DEFINE_HEX_PTR(expected_n, hexn); + assert_se(memcmp_nn(p->unique.rsa.buffer, p->unique.rsa.size, expected_n, expected_n_len) == 0); + + assert_se(p->parameters.rsaDetail.keyBits == expected_n_len * 8); + + assert_se(p->parameters.rsaDetail.exponent == exponent); + + check_tpm2b_public_fingerprint(&public, hexfp); + check_tpm2b_public_name(&public, hexname); +} + +TEST(tpm2b_public_from_openssl_pkey) { + /* standard ECC key */ + check_tpm2b_public_from_ecc_pem("2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a30444151634451674145726a6e4575424c73496c3972687068777976584e50686a346a426e500a44586e794a304b395579724e6764365335413532542b6f5376746b436a365a726c34685847337741515558706f426c532b7448717452714c35513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a", + "ae39c4b812ec225f6b869870caf5cd3e18f88c19cf0d79f22742bd532acd81de", + "92e40e764fea12bed9028fa66b9788571b7c004145e9a01952fad1eab51a8be5", + "cd3373293b62a52b48c12100e80ea9bfd806266ce76893a5ec31cb128052d97c", + "000b5c127e4dbaf8fb7bac641e8db25a84a48db876ca7ee3bd317ae1a4554ff72f17"); + + /* standard RSA key */ + check_tpm2b_public_from_rsa_pem("2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d494942496a414e42676b71686b6947397730424151454641414f43415138414d49494243674b4341514541795639434950652f505852337a436f63787045300a6a575262546c3568585844436b472f584b79374b6d2f4439584942334b734f5a31436a5937375571372f674359363170697838697552756a73413464503165380a593445336c68556d374a332b6473766b626f4b64553243626d52494c2f6675627771694c4d587a41673342575278747234547545443533527a373634554650640a307a70304b68775231496230444c67772f344e67566f314146763378784b4d6478774d45683567676b73733038326332706c354a504e32587677426f744e6b4d0a5471526c745a4a35355244436170696e7153334577376675646c4e735851357746766c7432377a7637344b585165616d704c59433037584f6761304c676c536b0a79754774586b6a50542f735542544a705374615769674d5a6f714b7479563463515a58436b4a52684459614c47587673504233687a766d5671636e6b47654e540a65774944415141420a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a", + "c95f4220f7bf3d7477cc2a1cc691348d645b4e5e615d70c2906fd72b2eca9bf0fd5c80772ac399d428d8efb52aeff80263ad698b1f22b91ba3b00e1d3f57bc638137961526ec9dfe76cbe46e829d53609b99120bfdfb9bc2a88b317cc0837056471b6be13b840f9dd1cfbeb85053ddd33a742a1c11d486f40cb830ff8360568d4016fdf1c4a31dc7030487982092cb34f36736a65e493cdd97bf0068b4d90c4ea465b59279e510c26a98a7a92dc4c3b7ee76536c5d0e7016f96ddbbcefef829741e6a6a4b602d3b5ce81ad0b8254a4cae1ad5e48cf4ffb140532694ad6968a0319a2a2adc95e1c4195c29094610d868b197bec3c1de1cef995a9c9e419e3537b", + 0x10001, + "d9186d13a7fd5b3644cee05448f49ad3574e82a2942ff93cf89598d36cca78a9", + "000be1bd75c7976e7a30e9e82223b81a9eff0d42c30618e588db592ed5da94455e81"); + + /* RSA key with non-default (i.e. not 0x10001) exponent */ + check_tpm2b_public_from_rsa_pem("2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d494942496a414e42676b71686b6947397730424151454641414f43415138414d49494243674b434151454179566c7551664b75565171596a5a71436a657a760a364e4a6f58654c736f702f72765375666330773769544d4f73566741557462515452505451725874397065537a4370524467634378656b6a544144577279304b0a6d59786a7a3634776c6a7030463959383068636a6b6b4b3759414d333054664c4648656c2b377574427370777142467a6e2b385a6659567353434b397354706f0a316c61376e5347514e7451576f36444a366c525a336a676d6d584f61544654416145304a432b7046584273564471736d46326438362f314e51714a755a5154520a575852636954704e58357649792f37766b6c5a6a685569526c78764e594f4e3070636476534a37364e74496e447a3048506f775a38705a454f4d2f4a454f59780a617a4c4a6a644936446b355279593578325a7949375074566a3057537242524f4d696f2b674c6556457a43343456336438315a38445138564e334c69625130330a70514944415141460a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a", + "c9596e41f2ae550a988d9a828decefe8d2685de2eca29febbd2b9f734c3b89330eb1580052d6d04d13d342b5edf69792cc2a510e0702c5e9234c00d6af2d0a998c63cfae30963a7417d63cd217239242bb600337d137cb1477a5fbbbad06ca70a811739fef197d856c4822bdb13a68d656bb9d219036d416a3a0c9ea5459de382699739a4c54c0684d090bea455c1b150eab2617677cebfd4d42a26e6504d159745c893a4d5f9bc8cbfeef925663854891971bcd60e374a5c76f489efa36d2270f3d073e8c19f2964438cfc910e6316b32c98dd23a0e4e51c98e71d99c88ecfb558f4592ac144e322a3e80b7951330b8e15dddf3567c0d0f153772e26d0d37a5", + 0x10005, + "c8ca80a687d5972e1d961aaa2cfde2ff2e7a20d85e3ea0382804e70e013d65af", + "000beb8974d36d8cf58fdc87460dda00319e10c94c1b9f222ac9ce29d1c4776246cc"); +} +#endif + +static void check_name(const TPM2B_NAME *name, const char *expect) { + assert_se(name->size == SHA256_DIGEST_SIZE + 2); + + DEFINE_HEX_PTR(e, expect); + assert_se(name->size == e_len); + assert_se(memcmp(name->name, e, e_len) == 0); +} + +TEST(calculate_pubkey_name) { + TPM2B_PUBLIC public; + TPM2B_NAME name; + + /* RSA */ + tpm2b_public_rsa_init(&public, "9ec7341c52093ac40a1965a5df10432513c539adcf905e30577ab6ebc88ffe53cd08cef12ed9bec6125432f4fada3629b8b96d31b8f507aa35029188fe396da823fcb236027f7fbb01b0da3d87be7f999390449ced604bdf7e26c48657cc0671000f1147da195c3861c96642e54427cb7a11572e07567ec3fd6316978abc4bd92b27bb0a0e4958e599804eeb41d682b3b7fc1f960209f80a4fb8a1b64abfd96bf5d554e73cdd6ad1c8becb4fcf5e8f0c3e621d210e5e2f308f6520ad9a966779231b99f06c5989e5a23a9415c8808ab89ce81117632e2f8461cd4428bded40979236aeadafe8de3f51660a45e1dbc87694e6a36360201cca3ff9e7263e712727"); + assert_se(tpm2_calculate_pubkey_name(&public.publicArea, &name) >= 0); + check_name(&name, "000be78f74a470dd92e979ca067cdb2293a35f075e8560b436bd2ccea5da21486a07"); + + /* ECC */ + tpm2b_public_ecc_init(&public, TPM2_ECC_NIST_P256, "238e02ee4fd5598add6b502429f1815418515e4b0d6551c8e816b38cb15451d1", "70c2d491769775ec43ccd5a571c429233e9d30cf0f486c2e01acd6cb32ba93b6"); + assert_se(tpm2_calculate_pubkey_name(&public.publicArea, &name) >= 0); + check_name(&name, "000b302787187ba19c82011c987bd2dcdbb652b3a543ccc5cb0b49c33d4caae604a6"); +} + +TEST(calculate_policy_auth_value) { + TPM2B_DIGEST d; + + digest_init(&d, "0000000000000000000000000000000000000000000000000000000000000000"); + assert_se(tpm2_calculate_policy_auth_value(&d) == 0); + assert_se(digest_check(&d, "8fcd2169ab92694e0c633f1ab772842b8241bbc20288981fc7ac1eddc1fddb0e")); + assert_se(tpm2_calculate_policy_auth_value(&d) == 0); + assert_se(digest_check(&d, "759ebd5ed65100e0b4aa2d04b4b789c2672d92ecc9cdda4b5fa16a303132e008")); +} + +TEST(calculate_policy_authorize) { + TPM2B_PUBLIC public; + TPM2B_DIGEST d; + + /* RSA */ + tpm2b_public_rsa_init(&public, "9ec7341c52093ac40a1965a5df10432513c539adcf905e30577ab6ebc88ffe53cd08cef12ed9bec6125432f4fada3629b8b96d31b8f507aa35029188fe396da823fcb236027f7fbb01b0da3d87be7f999390449ced604bdf7e26c48657cc0671000f1147da195c3861c96642e54427cb7a11572e07567ec3fd6316978abc4bd92b27bb0a0e4958e599804eeb41d682b3b7fc1f960209f80a4fb8a1b64abfd96bf5d554e73cdd6ad1c8becb4fcf5e8f0c3e621d210e5e2f308f6520ad9a966779231b99f06c5989e5a23a9415c8808ab89ce81117632e2f8461cd4428bded40979236aeadafe8de3f51660a45e1dbc87694e6a36360201cca3ff9e7263e712727"); + digest_init(&d, "0000000000000000000000000000000000000000000000000000000000000000"); + assert_se(tpm2_calculate_policy_authorize(&public, NULL, &d) == 0); + assert_se(digest_check(&d, "95213a3784eaab04f427bc7e8851c2f1df0903be8e42428ec25dcefd907baff1")); + assert_se(tpm2_calculate_policy_authorize(&public, NULL, &d) == 0); + assert_se(digest_check(&d, "95213a3784eaab04f427bc7e8851c2f1df0903be8e42428ec25dcefd907baff1")); + + /* ECC */ + tpm2b_public_ecc_init(&public, TPM2_ECC_NIST_P256, "423a89da6f0998f510489ab9682706e762031ef8f9faef2a185eff67065a187e", "996f73291670cef9e303d6cd9fa19ddf2c9c1fb1e283324ca9acca07c405c8d0"); + digest_init(&d, "0000000000000000000000000000000000000000000000000000000000000000"); + assert_se(tpm2_calculate_policy_authorize(&public, NULL, &d) == 0); + assert_se(digest_check(&d, "2a5b705e83f949c27ac4d2e79e54fb5fb0a60f0b37bbd54a0ee1022ba00d3628")); + assert_se(tpm2_calculate_policy_authorize(&public, NULL, &d) == 0); + assert_se(digest_check(&d, "2a5b705e83f949c27ac4d2e79e54fb5fb0a60f0b37bbd54a0ee1022ba00d3628")); +} + +TEST(calculate_policy_pcr) { + TPM2B_DIGEST d, dN[16]; + + digest_init(&dN[ 0], "2124793cbbe60c3a8637d3b84a5d054e87c351e1469a285acc04755e8b204dec"); + digest_init(&dN[ 1], "bf7592f18adcfdc549fc0b94939f5069a24697f9cff4a0dca29014767b97559d"); + digest_init(&dN[ 2], "4b00cff9dee3a364979b2dc241b34568a8ad49fcf2713df259e47dff8875feed"); + digest_init(&dN[ 3], "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"); + digest_init(&dN[ 4], "368f85b3013041dfe203faaa364f00b07c5da7b1e5f1dbf2efb06fa6b9bd92de"); + digest_init(&dN[ 5], "c97c40369691c8e4aa78fb3a52655cd193b780a838b8e23f5f476576919db5e5"); + digest_init(&dN[ 6], "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"); + digest_init(&dN[ 7], "aa1154c9e0a774854ccbed4c8ce7e9b906b3d700a1a8db1772d0341a62dbe51b"); + digest_init(&dN[ 8], "cfde439a2c06af3479ca6bdc60429b90553d65300c5cfcc40004a08c6b5ad81a"); + digest_init(&dN[ 9], "9c2bac22ef5ec84fcdb71c3ebf776cba1247e5da980e5ee08e45666a2edf0b8b"); + digest_init(&dN[10], "9885873f4d7348199ad286f8f2476d4f866940950f6f9fb9f945ed352dbdcbd2"); + digest_init(&dN[11], "42400ab950d21aa79d12cc4fdef67d1087a39ad64900619831c0974dbae54e44"); + digest_init(&dN[12], "767d064382e56ca1ad3bdcc6bc596112e6c2008b593d3570d24c2bfa64c4628c"); + digest_init(&dN[13], "30c16133175959408c9745d8dafadef5daf4b39cb2be04df0d60089bd46d3cc4"); + digest_init(&dN[14], "e3991b7ddd47be7e92726a832d6874c5349b52b789fa0db8b558c69fea29574e"); + digest_init(&dN[15], "852dae3ecb992bdeb13d6002fefeeffdd90feca8b378d56681ef2c885d0e5137"); + + digest_init(&d, "0000000000000000000000000000000000000000000000000000000000000000"); + Tpm2PCRValue v1[] = { + TPM2_PCR_VALUE_MAKE(4, TPM2_ALG_SHA256, dN[4]), + TPM2_PCR_VALUE_MAKE(7, TPM2_ALG_SHA256, dN[7]), + TPM2_PCR_VALUE_MAKE(8, TPM2_ALG_SHA256, dN[8]), + }; + assert_se(tpm2_calculate_policy_pcr(v1, ELEMENTSOF(v1), &d) == 0); + assert_se(digest_check(&d, "76532a0e16f7e6bf6b02918c11f75d99d729fab0cc81d0df2c4284a2c4fe6e05")); + assert_se(tpm2_calculate_policy_pcr(v1, ELEMENTSOF(v1), &d) == 0); + assert_se(digest_check(&d, "97e64bcabb64c1fa4b726528644926c8029f5b4458b0575c98c04fe225629a0b")); + + digest_init(&d, "0000000000000000000000000000000000000000000000000000000000000000"); + Tpm2PCRValue v2[] = { + TPM2_PCR_VALUE_MAKE( 0, TPM2_ALG_SHA256, dN[ 0]), + TPM2_PCR_VALUE_MAKE( 1, TPM2_ALG_SHA256, dN[ 1]), + TPM2_PCR_VALUE_MAKE( 2, TPM2_ALG_SHA256, dN[ 2]), + TPM2_PCR_VALUE_MAKE( 3, TPM2_ALG_SHA256, dN[ 3]), + TPM2_PCR_VALUE_MAKE( 4, TPM2_ALG_SHA256, dN[ 4]), + TPM2_PCR_VALUE_MAKE( 5, TPM2_ALG_SHA256, dN[ 5]), + TPM2_PCR_VALUE_MAKE( 6, TPM2_ALG_SHA256, dN[ 6]), + TPM2_PCR_VALUE_MAKE( 7, TPM2_ALG_SHA256, dN[ 7]), + TPM2_PCR_VALUE_MAKE( 8, TPM2_ALG_SHA256, dN[ 8]), + TPM2_PCR_VALUE_MAKE( 9, TPM2_ALG_SHA256, dN[ 9]), + TPM2_PCR_VALUE_MAKE(10, TPM2_ALG_SHA256, dN[10]), + TPM2_PCR_VALUE_MAKE(11, TPM2_ALG_SHA256, dN[11]), + TPM2_PCR_VALUE_MAKE(12, TPM2_ALG_SHA256, dN[12]), + TPM2_PCR_VALUE_MAKE(13, TPM2_ALG_SHA256, dN[13]), + TPM2_PCR_VALUE_MAKE(14, TPM2_ALG_SHA256, dN[14]), + TPM2_PCR_VALUE_MAKE(15, TPM2_ALG_SHA256, dN[15]), + }; + assert_se(tpm2_calculate_policy_pcr(v2, ELEMENTSOF(v2), &d) == 0); + assert_se(digest_check(&d, "22be4f1674f792d6345cea9427701068f0e8d9f42755dcc0e927e545a68f9c13")); + assert_se(tpm2_calculate_policy_pcr(v2, ELEMENTSOF(v2), &d) == 0); + assert_se(digest_check(&d, "7481fd1b116078eb3ac2456e4ad542c9b46b9b8eb891335771ca8e7c8f8e4415")); +} + +static void check_srk_rsa_template(TPMT_PUBLIC *template) { + assert_se(template->type == TPM2_ALG_RSA); + assert_se(template->nameAlg == TPM2_ALG_SHA256); + assert_se(template->objectAttributes == 0x30472); + assert_se(template->parameters.rsaDetail.symmetric.algorithm == TPM2_ALG_AES); + assert_se(template->parameters.rsaDetail.symmetric.keyBits.sym == 128); + assert_se(template->parameters.rsaDetail.symmetric.mode.sym == TPM2_ALG_CFB); + assert_se(template->parameters.rsaDetail.scheme.scheme == TPM2_ALG_NULL); + assert_se(template->parameters.rsaDetail.keyBits == 2048); +} + +static void check_srk_ecc_template(TPMT_PUBLIC *template) { + assert_se(template->type == TPM2_ALG_ECC); + assert_se(template->nameAlg == TPM2_ALG_SHA256); + assert_se(template->objectAttributes == 0x30472); + assert_se(template->parameters.eccDetail.symmetric.algorithm == TPM2_ALG_AES); + assert_se(template->parameters.eccDetail.symmetric.keyBits.sym == 128); + assert_se(template->parameters.eccDetail.symmetric.mode.sym == TPM2_ALG_CFB); + assert_se(template->parameters.eccDetail.scheme.scheme == TPM2_ALG_NULL); + assert_se(template->parameters.eccDetail.kdf.scheme == TPM2_ALG_NULL); + assert_se(template->parameters.eccDetail.curveID == TPM2_ECC_NIST_P256); +} + +TEST(tpm2_get_srk_template) { + TPMT_PUBLIC template; + + assert_se(tpm2_get_srk_template(TPM2_ALG_RSA, &template) >= 0); + check_srk_rsa_template(&template); + + assert_se(tpm2_get_srk_template(TPM2_ALG_ECC, &template) >= 0); + check_srk_ecc_template(&template); +} + +static void check_best_srk_template(Tpm2Context *c) { + TEST_LOG_FUNC(); + + TPMT_PUBLIC template; + assert_se(tpm2_get_best_srk_template(c, &template) >= 0); + + assert_se(IN_SET(template.type, TPM2_ALG_ECC, TPM2_ALG_RSA)); + + if (template.type == TPM2_ALG_RSA) + check_srk_rsa_template(&template); + else + check_srk_ecc_template(&template); +} + +static void check_test_parms(Tpm2Context *c) { + assert(c); + + TEST_LOG_FUNC(); + + TPMU_PUBLIC_PARMS parms = { + .symDetail.sym = { + .algorithm = TPM2_ALG_AES, + .keyBits.aes = 128, + .mode.aes = TPM2_ALG_CFB, + }, + }; + + /* Test with invalid parms */ + assert_se(!tpm2_test_parms(c, TPM2_ALG_CFB, &parms)); + + TPMU_PUBLIC_PARMS invalid_parms = parms; + invalid_parms.symDetail.sym.keyBits.aes = 1; + assert_se(!tpm2_test_parms(c, TPM2_ALG_SYMCIPHER, &invalid_parms)); + + /* Test with valid parms */ + assert_se(tpm2_test_parms(c, TPM2_ALG_SYMCIPHER, &parms)); +} + +static void check_supports_alg(Tpm2Context *c) { + assert(c); + + TEST_LOG_FUNC(); + + /* Test invalid algs */ + assert_se(!tpm2_supports_alg(c, TPM2_ALG_ERROR)); + assert_se(!tpm2_supports_alg(c, TPM2_ALG_LAST + 1)); + + /* Test valid algs */ + assert_se(tpm2_supports_alg(c, TPM2_ALG_RSA)); + assert_se(tpm2_supports_alg(c, TPM2_ALG_AES)); + assert_se(tpm2_supports_alg(c, TPM2_ALG_CFB)); +} + +static void check_supports_command(Tpm2Context *c) { + assert(c); + + TEST_LOG_FUNC(); + + /* Test invalid commands. TPM specification Part 2 ("Structures") section "TPM_CC (Command Codes)" + * states bits 31:30 and 28:16 are reserved and must be 0. */ + assert_se(!tpm2_supports_command(c, UINT32_C(0x80000000))); + assert_se(!tpm2_supports_command(c, UINT32_C(0x40000000))); + assert_se(!tpm2_supports_command(c, UINT32_C(0x00100000))); + assert_se(!tpm2_supports_command(c, UINT32_C(0x80000144))); + assert_se(!tpm2_supports_command(c, UINT32_C(0x40000144))); + assert_se(!tpm2_supports_command(c, UINT32_C(0x00100144))); + + /* Test valid commands. We should be able to expect all TPMs support these. */ + assert_se(tpm2_supports_command(c, TPM2_CC_Startup)); + assert_se(tpm2_supports_command(c, TPM2_CC_StartAuthSession)); + assert_se(tpm2_supports_command(c, TPM2_CC_Create)); + assert_se(tpm2_supports_command(c, TPM2_CC_CreatePrimary)); + assert_se(tpm2_supports_command(c, TPM2_CC_Unseal)); +} + +static void check_get_or_create_srk(Tpm2Context *c) { + TEST_LOG_FUNC(); + + _cleanup_free_ TPM2B_PUBLIC *public = NULL; + _cleanup_free_ TPM2B_NAME *name = NULL, *qname = NULL; + _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL; + assert_se(tpm2_get_or_create_srk(c, NULL, &public, &name, &qname, &handle) >= 0); + assert_se(public && name && qname && handle); + + _cleanup_free_ TPM2B_PUBLIC *public2 = NULL; + _cleanup_free_ TPM2B_NAME *name2 = NULL, *qname2 = NULL; + _cleanup_(tpm2_handle_freep) Tpm2Handle *handle2 = NULL; + assert_se(tpm2_get_srk(c, NULL, &public2, &name2, &qname2, &handle2) >= 0); + assert_se(public2 && name2 && qname2 && handle2); + + assert_se(memcmp_nn(public, sizeof(*public), public2, sizeof(*public2)) == 0); + assert_se(memcmp_nn(name->name, name->size, name2->name, name2->size) == 0); + assert_se(memcmp_nn(qname->name, qname->size, qname2->name, qname2->size) == 0); +} + +#if HAVE_OPENSSL && OPENSSL_VERSION_MAJOR >= 3 +static void calculate_seal_and_unseal( + Tpm2Context *c, + TPM2_HANDLE parent_index, + const TPM2B_PUBLIC *parent_public) { + + _cleanup_free_ char *secret_string = NULL; + assert_se(asprintf(&secret_string, "The classified documents are in room %x", parent_index) > 0); + size_t secret_size = strlen(secret_string) + 1; + + _cleanup_free_ void *blob = NULL; + size_t blob_size = 0; + _cleanup_free_ void *serialized_parent = NULL; + size_t serialized_parent_size; + assert_se(tpm2_calculate_seal( + parent_index, + parent_public, + /* attributes= */ NULL, + secret_string, secret_size, + /* policy= */ NULL, + /* pin= */ NULL, + /* ret_secret= */ NULL, /* ret_secret_size= */ 0, + &blob, &blob_size, + &serialized_parent, &serialized_parent_size) >= 0); + + _cleanup_free_ void *unsealed_secret = NULL; + size_t unsealed_secret_size; + assert_se(tpm2_unseal( + c, + /* hash_pcr_mask= */ 0, + /* pcr_bank= */ 0, + /* pubkey= */ NULL, /* pubkey_size= */ 0, + /* pubkey_pcr_mask= */ 0, + /* signature= */ NULL, + /* pin= */ NULL, + /* pcrlock_policy= */ NULL, + /* primary_alg= */ 0, + blob, blob_size, + /* known_policy_hash= */ NULL, /* known_policy_hash_size= */ 0, + serialized_parent, serialized_parent_size, + &unsealed_secret, &unsealed_secret_size) >= 0); + + assert_se(memcmp_nn(secret_string, secret_size, unsealed_secret, unsealed_secret_size) == 0); + + char unsealed_string[unsealed_secret_size]; + assert_se(snprintf(unsealed_string, unsealed_secret_size, "%s", (char*) unsealed_secret) == (int) unsealed_secret_size - 1); + log_debug("Unsealed secret is: %s", unsealed_string); +} + +static int check_calculate_seal(Tpm2Context *c) { + assert(c); + int r; + + if (detect_virtualization() == VIRTUALIZATION_NONE && !slow_tests_enabled()) { + log_notice("Skipping slow calculate seal TPM2 tests. Physical system detected, and slow tests disabled."); + return 0; + } + + TEST_LOG_FUNC(); + + _cleanup_free_ TPM2B_PUBLIC *srk_public = NULL; + assert_se(tpm2_get_srk(c, NULL, &srk_public, NULL, NULL, NULL) >= 0); + calculate_seal_and_unseal(c, TPM2_SRK_HANDLE, srk_public); + + TPMI_ALG_ASYM test_algs[] = { TPM2_ALG_RSA, TPM2_ALG_ECC, }; + for (unsigned i = 0; i < ELEMENTSOF(test_algs); i++) { + TPMI_ALG_ASYM alg = test_algs[i]; + + TPM2B_PUBLIC template = { .size = sizeof(TPMT_PUBLIC), }; + assert_se(tpm2_get_srk_template(alg, &template.publicArea) >= 0); + + _cleanup_free_ TPM2B_PUBLIC *public = NULL; + _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL; + assert_se(tpm2_create_primary(c, NULL, &template, NULL, &public, &handle) >= 0); + + /* Once our minimum libtss2-esys version is 2.4.0 or later, this can assume + * tpm2_index_from_handle() should always work. */ + TPM2_HANDLE index; + r = tpm2_index_from_handle(c, handle, &index); + if (r == -EOPNOTSUPP) + return log_tests_skipped("libtss2-esys version too old to support tpm2_index_from_handle()"); + assert_se(r >= 0); + + calculate_seal_and_unseal(c, index, public); + } + + return 0; +} +#endif /* HAVE_OPENSSL && OPENSSL_VERSION_MAJOR >= 3 */ + +static void check_seal_unseal_for_handle(Tpm2Context *c, TPM2_HANDLE handle) { + TPM2B_DIGEST policy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); + + assert(c); + + log_debug("Check seal/unseal for handle 0x%" PRIx32, handle); + + _cleanup_free_ void *secret = NULL, *blob = NULL, *srk = NULL, *unsealed_secret = NULL; + size_t secret_size, blob_size, srk_size, unsealed_secret_size; + assert_se(tpm2_seal( + c, + handle, + &policy, + /* pin= */ NULL, + &secret, &secret_size, + &blob, &blob_size, + /* ret_primary_alg= */ NULL, + &srk, &srk_size) >= 0); + + assert_se(tpm2_unseal( + c, + /* hash_pcr_mask= */ 0, + /* pcr_bank= */ 0, + /* pubkey= */ NULL, /* pubkey_size= */ 0, + /* pubkey_pcr_mask= */ 0, + /* signature= */ NULL, + /* pin= */ NULL, + /* pcrlock_policy= */ NULL, + /* primary_alg= */ 0, + blob, blob_size, + /* policy_hash= */ NULL, /* policy_hash_size= */ 0, + srk, srk_size, + &unsealed_secret, &unsealed_secret_size) >= 0); + + assert_se(memcmp_nn(secret, secret_size, unsealed_secret, unsealed_secret_size) == 0); +} + +static void check_seal_unseal(Tpm2Context *c) { + int r; + + assert(c); + + if (detect_virtualization() == VIRTUALIZATION_NONE && !slow_tests_enabled()) { + log_notice("Skipping slow seal/unseal TPM2 tests. Physical system detected, and slow tests disabled."); + return; + } + + TEST_LOG_FUNC(); + + check_seal_unseal_for_handle(c, 0); + check_seal_unseal_for_handle(c, TPM2_SRK_HANDLE); + + FOREACH_ARRAY(template, test_templates, ELEMENTSOF(test_templates)) { + TPM2B_PUBLIC public = { + .publicArea = **template, + .size = sizeof(**template), + }; + _cleanup_(tpm2_handle_freep) Tpm2Handle *transient_handle = NULL; + assert_se(tpm2_create_primary( + c, + /* session= */ NULL, + &public, + /* sensitive= */ NULL, + /* ret_public= */ NULL, + &transient_handle) >= 0); + + TPMI_DH_PERSISTENT transient_handle_index; + r = tpm2_index_from_handle(c, transient_handle, &transient_handle_index); + if (r == -EOPNOTSUPP) { + /* libesys too old */ + log_tests_skipped("libesys too old for tpm2_index_from_handle"); + return; + } + assert_se(r >= 0); + + check_seal_unseal_for_handle(c, transient_handle_index); + } +} + +TEST_RET(tests_which_require_tpm) { + _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; + int r = 0; + + if (tpm2_context_new(NULL, &c) < 0) + return log_tests_skipped("Could not find TPM"); + + check_test_parms(c); + check_supports_alg(c); + check_supports_command(c); + check_best_srk_template(c); + check_get_or_create_srk(c); + check_seal_unseal(c); + +#if HAVE_OPENSSL && OPENSSL_VERSION_MAJOR >= 3 /* calculating sealed object requires openssl >= 3 */ + r = check_calculate_seal(c); +#endif + + return r; +} + +#endif /* HAVE_TPM2 */ + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-udev-util.c b/src/test/test-udev-util.c new file mode 100644 index 0000000..cb80c69 --- /dev/null +++ b/src/test/test-udev-util.c @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdlib.h> +#include <string.h> + +#include "macro.h" +#include "string-util.h" +#include "tests.h" +#include "udev-util.h" + +static void test_udev_replace_whitespace_one_len(const char *str, size_t len, const char *expected) { + _cleanup_free_ char *result = NULL; + int r; + + result = new(char, len + 1); + assert_se(result); + r = udev_replace_whitespace(str, result, len); + assert_se((size_t) r == strlen(expected)); + assert_se(streq(result, expected)); +} + +static void test_udev_replace_whitespace_one(const char *str, const char *expected) { + test_udev_replace_whitespace_one_len(str, strlen(str), expected); +} + +TEST(udev_replace_whitespace) { + test_udev_replace_whitespace_one("hogehoge", "hogehoge"); + test_udev_replace_whitespace_one("hoge hoge", "hoge_hoge"); + test_udev_replace_whitespace_one(" hoge hoge ", "hoge_hoge"); + test_udev_replace_whitespace_one(" ", ""); + test_udev_replace_whitespace_one("hoge ", "hoge"); + + test_udev_replace_whitespace_one_len("hoge hoge ", 9, "hoge_hoge"); + test_udev_replace_whitespace_one_len("hoge hoge ", 8, "hoge_hog"); + test_udev_replace_whitespace_one_len("hoge hoge ", 7, "hoge_ho"); + test_udev_replace_whitespace_one_len("hoge hoge ", 6, "hoge_h"); + test_udev_replace_whitespace_one_len("hoge hoge ", 5, "hoge"); + test_udev_replace_whitespace_one_len("hoge hoge ", 4, "hoge"); + test_udev_replace_whitespace_one_len("hoge hoge ", 3, "hog"); + test_udev_replace_whitespace_one_len("hoge hoge ", 2, "ho"); + test_udev_replace_whitespace_one_len("hoge hoge ", 1, "h"); + test_udev_replace_whitespace_one_len("hoge hoge ", 0, ""); + + test_udev_replace_whitespace_one_len(" hoge hoge ", 16, "hoge_hoge"); + test_udev_replace_whitespace_one_len(" hoge hoge ", 15, "hoge_hoge"); + test_udev_replace_whitespace_one_len(" hoge hoge ", 14, "hoge_hog"); + test_udev_replace_whitespace_one_len(" hoge hoge ", 13, "hoge_ho"); + test_udev_replace_whitespace_one_len(" hoge hoge ", 12, "hoge_h"); + test_udev_replace_whitespace_one_len(" hoge hoge ", 11, "hoge"); + test_udev_replace_whitespace_one_len(" hoge hoge ", 10, "hoge"); + test_udev_replace_whitespace_one_len(" hoge hoge ", 9, "hoge"); + test_udev_replace_whitespace_one_len(" hoge hoge ", 8, "hoge"); + test_udev_replace_whitespace_one_len(" hoge hoge ", 7, "hog"); + test_udev_replace_whitespace_one_len(" hoge hoge ", 6, "ho"); + test_udev_replace_whitespace_one_len(" hoge hoge ", 5, "h"); + test_udev_replace_whitespace_one_len(" hoge hoge ", 4, ""); + test_udev_replace_whitespace_one_len(" hoge hoge ", 3, ""); + test_udev_replace_whitespace_one_len(" hoge hoge ", 2, ""); + test_udev_replace_whitespace_one_len(" hoge hoge ", 1, ""); + test_udev_replace_whitespace_one_len(" hoge hoge ", 0, ""); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-uid-alloc-range.c b/src/test/test-uid-alloc-range.c new file mode 100644 index 0000000..cd06463 --- /dev/null +++ b/src/test/test-uid-alloc-range.c @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <unistd.h> +#include <sys/types.h> + +#include "fd-util.h" +#include "fileio.h" +#include "format-util.h" +#include "fs-util.h" +#include "tests.h" +#include "tmpfile-util.h" +#include "uid-alloc-range.h" + +static void test_read_login_defs_one(const char *path) { + log_info("/* %s(\"%s\") */", __func__, path ?: "<custom>"); + + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-user-record.XXXXXX"; + _cleanup_fclose_ FILE *f = NULL; + if (!path) { + assert_se(fmkostemp_safe(name, "r+", &f) == 0); + fprintf(f, + "SYS_UID_MIN "UID_FMT"\n" + "SYS_UID_MAX "UID_FMT"\n" + "SYS_GID_MIN "GID_FMT"\n" + "SYS_GID_MAX "GID_FMT"\n", + (uid_t) (SYSTEM_ALLOC_UID_MIN + 5), + (uid_t) (SYSTEM_UID_MAX + 5), + (gid_t) (SYSTEM_ALLOC_GID_MIN + 5), + (gid_t) (SYSTEM_GID_MAX + 5)); + assert_se(fflush_and_check(f) >= 0); + } + + UGIDAllocationRange defs; + assert_se(read_login_defs(&defs, path ?: name, NULL) >= 0); + + log_info("system_alloc_uid_min="UID_FMT, defs.system_alloc_uid_min); + log_info("system_uid_max="UID_FMT, defs.system_uid_max); + log_info("system_alloc_gid_min="GID_FMT, defs.system_alloc_gid_min); + log_info("system_gid_max="GID_FMT, defs.system_gid_max); + + if (!path) { + uid_t offset = ENABLE_COMPAT_MUTABLE_UID_BOUNDARIES ? 5 : 0; + assert_se(defs.system_alloc_uid_min == SYSTEM_ALLOC_UID_MIN + offset); + assert_se(defs.system_uid_max == SYSTEM_UID_MAX + offset); + assert_se(defs.system_alloc_gid_min == SYSTEM_ALLOC_GID_MIN + offset); + assert_se(defs.system_gid_max == SYSTEM_GID_MAX + offset); + } else if (streq(path, "/dev/null")) { + assert_se(defs.system_alloc_uid_min == SYSTEM_ALLOC_UID_MIN); + assert_se(defs.system_uid_max == SYSTEM_UID_MAX); + assert_se(defs.system_alloc_gid_min == SYSTEM_ALLOC_GID_MIN); + assert_se(defs.system_gid_max == SYSTEM_GID_MAX); + } +} + +TEST(read_login_defs) { + test_read_login_defs_one("/dev/null"); + test_read_login_defs_one("/etc/login.defs"); + test_read_login_defs_one(NULL); +} + +TEST(acquire_ugid_allocation_range) { + const UGIDAllocationRange *defs; + assert_se(defs = acquire_ugid_allocation_range()); + + log_info("system_alloc_uid_min="UID_FMT, defs->system_alloc_uid_min); + log_info("system_uid_max="UID_FMT, defs->system_uid_max); + log_info("system_alloc_gid_min="GID_FMT, defs->system_alloc_gid_min); + log_info("system_gid_max="GID_FMT, defs->system_gid_max); +} + +TEST(uid_is_system) { + uid_t uid = 0; + log_info("uid_is_system("UID_FMT") = %s", uid, yes_no(uid_is_system(uid))); + + uid = 999; + log_info("uid_is_system("UID_FMT") = %s", uid, yes_no(uid_is_system(uid))); + + uid = getuid(); + log_info("uid_is_system("UID_FMT") = %s", uid, yes_no(uid_is_system(uid))); +} + +TEST(gid_is_system) { + gid_t gid = 0; + log_info("gid_is_system("GID_FMT") = %s", gid, yes_no(gid_is_system(gid))); + + gid = 999; + log_info("gid_is_system("GID_FMT") = %s", gid, yes_no(gid_is_system(gid))); + + gid = getgid(); + log_info("gid_is_system("GID_FMT") = %s", gid, yes_no(gid_is_system(gid))); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-uid-range.c b/src/test/test-uid-range.c new file mode 100644 index 0000000..186f6ee --- /dev/null +++ b/src/test/test-uid-range.c @@ -0,0 +1,175 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stddef.h> + +#include "alloc-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "tests.h" +#include "tmpfile-util.h" +#include "uid-range.h" +#include "user-util.h" +#include "virt.h" + +TEST(uid_range) { + _cleanup_(uid_range_freep) UidRange *p = NULL; + uid_t search; + + assert_se(uid_range_covers(p, 0, 0)); + assert_se(!uid_range_covers(p, 0, 1)); + assert_se(!uid_range_covers(p, 100, UINT32_MAX)); + + assert_se(uid_range_add_str(&p, "500-999") >= 0); + assert_se(p); + assert_se(p->n_entries == 1); + assert_se(p->entries[0].start == 500); + assert_se(p->entries[0].nr == 500); + + assert_se(!uid_range_contains(p, 499)); + assert_se(uid_range_contains(p, 500)); + assert_se(uid_range_contains(p, 999)); + assert_se(!uid_range_contains(p, 1000)); + + assert_se(!uid_range_covers(p, 100, 150)); + assert_se(!uid_range_covers(p, 400, 200)); + assert_se(!uid_range_covers(p, 499, 1)); + assert_se(uid_range_covers(p, 500, 1)); + assert_se(uid_range_covers(p, 501, 10)); + assert_se(uid_range_covers(p, 999, 1)); + assert_se(!uid_range_covers(p, 999, 2)); + assert_se(!uid_range_covers(p, 1000, 1)); + assert_se(!uid_range_covers(p, 1000, 100)); + assert_se(!uid_range_covers(p, 1001, 100)); + + search = UID_INVALID; + assert_se(uid_range_next_lower(p, &search)); + assert_se(search == 999); + assert_se(uid_range_next_lower(p, &search)); + assert_se(search == 998); + search = 501; + assert_se(uid_range_next_lower(p, &search)); + assert_se(search == 500); + assert_se(uid_range_next_lower(p, &search) == -EBUSY); + + assert_se(uid_range_add_str(&p, "1000") >= 0); + assert_se(p->n_entries == 1); + assert_se(p->entries[0].start == 500); + assert_se(p->entries[0].nr == 501); + + assert_se(uid_range_add_str(&p, "30-40") >= 0); + assert_se(p->n_entries == 2); + assert_se(p->entries[0].start == 30); + assert_se(p->entries[0].nr == 11); + assert_se(p->entries[1].start == 500); + assert_se(p->entries[1].nr == 501); + + assert_se(uid_range_add_str(&p, "60-70") >= 0); + assert_se(p->n_entries == 3); + assert_se(p->entries[0].start == 30); + assert_se(p->entries[0].nr == 11); + assert_se(p->entries[1].start == 60); + assert_se(p->entries[1].nr == 11); + assert_se(p->entries[2].start == 500); + assert_se(p->entries[2].nr == 501); + + assert_se(uid_range_add_str(&p, "20-2000") >= 0); + assert_se(p->n_entries == 1); + assert_se(p->entries[0].start == 20); + assert_se(p->entries[0].nr == 1981); + + assert_se(uid_range_add_str(&p, "2002") >= 0); + assert_se(p->n_entries == 2); + assert_se(p->entries[0].start == 20); + assert_se(p->entries[0].nr == 1981); + assert_se(p->entries[1].start == 2002); + assert_se(p->entries[1].nr == 1); + + assert_se(uid_range_add_str(&p, "2001") >= 0); + assert_se(p->n_entries == 1); + assert_se(p->entries[0].start == 20); + assert_se(p->entries[0].nr == 1983); +} + +TEST(load_userns) { + _cleanup_(uid_range_freep) UidRange *p = NULL; + _cleanup_(unlink_and_freep) char *fn = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + r = uid_range_load_userns(&p, NULL); + if (r < 0 && ERRNO_IS_NOT_SUPPORTED(r)) + return; + + assert_se(r >= 0); + assert_se(uid_range_contains(p, getuid())); + + r = running_in_userns(); + if (r == 0) { + assert_se(p->n_entries == 1); + assert_se(p->entries[0].start == 0); + assert_se(p->entries[0].nr == UINT32_MAX); + + assert_se(uid_range_covers(p, 0, UINT32_MAX)); + } + + assert_se(fopen_temporary_child(NULL, &f, &fn) >= 0); + fputs("0 0 20\n" + "100 0 20\n", f); + assert_se(fflush_and_check(f) >= 0); + + p = uid_range_free(p); + + assert_se(uid_range_load_userns(&p, fn) >= 0); + + assert_se(uid_range_contains(p, 0)); + assert_se(uid_range_contains(p, 19)); + assert_se(!uid_range_contains(p, 20)); + + assert_se(!uid_range_contains(p, 99)); + assert_se(uid_range_contains(p, 100)); + assert_se(uid_range_contains(p, 119)); + assert_se(!uid_range_contains(p, 120)); +} + +TEST(uid_range_coalesce) { + _cleanup_(uid_range_freep) UidRange *p = NULL; + + for (size_t i = 0; i < 10; i++) { + assert_se(uid_range_add_internal(&p, i * 10, 10, /* coalesce = */ false) >= 0); + assert_se(uid_range_add_internal(&p, i * 10 + 5, 10, /* coalesce = */ false) >= 0); + } + + assert_se(uid_range_add_internal(&p, 100, 1, /* coalesce = */ true) >= 0); + assert_se(p->n_entries == 1); + assert_se(p->entries[0].start == 0); + assert_se(p->entries[0].nr == 105); + + p = uid_range_free(p); + + for (size_t i = 0; i < 10; i++) { + assert_se(uid_range_add_internal(&p, (10 - i) * 10, 10, /* coalesce = */ false) >= 0); + assert_se(uid_range_add_internal(&p, (10 - i) * 10 + 5, 10, /* coalesce = */ false) >= 0); + } + + assert_se(uid_range_add_internal(&p, 100, 1, /* coalesce = */ true) >= 0); + assert_se(p->n_entries == 1); + assert_se(p->entries[0].start == 10); + assert_se(p->entries[0].nr == 105); + + p = uid_range_free(p); + + for (size_t i = 0; i < 10; i++) { + assert_se(uid_range_add_internal(&p, i * 10, 10, /* coalesce = */ false) >= 0); + assert_se(uid_range_add_internal(&p, i * 10 + 5, 10, /* coalesce = */ false) >= 0); + assert_se(uid_range_add_internal(&p, (10 - i) * 10, 10, /* coalesce = */ false) >= 0); + assert_se(uid_range_add_internal(&p, (10 - i) * 10 + 5, 10, /* coalesce = */ false) >= 0); + } + assert_se(uid_range_add_internal(&p, 100, 1, /* coalesce = */ true) >= 0); + assert_se(p->n_entries == 1); + assert_se(p->entries[0].start == 0); + assert_se(p->entries[0].nr == 115); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-umask-util.c b/src/test/test-umask-util.c new file mode 100644 index 0000000..8316dfb --- /dev/null +++ b/src/test/test-umask-util.c @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "process-util.h" +#include "tests.h" +#include "umask-util.h" + +int main(int argc, char *argv[]) { + size_t n; + mode_t u, t; + + test_setup_logging(LOG_DEBUG); + + u = umask(0111); + + n = 0; + WITH_UMASK(0123) { + assert_se(umask(000) == 0123); + n++; + + assert_se(get_process_umask(0, &t) == 0); + assert_se(t == 000); + } + + assert_se(n == 1); + assert_se(umask(u) == 0111); + + assert_se(get_process_umask(getpid_cached(), &t) == 0); + assert_se(t == u); + + WITH_UMASK(0135) { + assert_se(umask(000) == 0135); + n++; + + assert_se(get_process_umask(0, &t) == 0); + assert_se(t == 000); + } + + assert_se(n == 2); + assert_se(umask(0111) == u); + + assert_se(get_process_umask(0, &t) == 0); + assert_se(t == 0111); + + WITH_UMASK(0315) { + assert_se(umask(000) == 0315); + n++; + break; + } + + assert_se(n == 3); + assert_se(umask(u) == 0111); + + assert_se(get_process_umask(0, &t) == 0); + assert_se(t == u); + + return EXIT_SUCCESS; +} diff --git a/src/test/test-unaligned.c b/src/test/test-unaligned.c new file mode 100644 index 0000000..728c193 --- /dev/null +++ b/src/test/test-unaligned.c @@ -0,0 +1,168 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "memory-util.h" +#include "sparse-endian.h" +#include "tests.h" +#include "unaligned.h" + +static uint8_t data[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, +}; + +TEST(be) { + uint8_t scratch[16]; + + assert_se(unaligned_read_be16(&data[0]) == 0x0001); + assert_se(unaligned_read_be16(&data[1]) == 0x0102); + + assert_se(unaligned_read_be32(&data[0]) == 0x00010203); + assert_se(unaligned_read_be32(&data[1]) == 0x01020304); + assert_se(unaligned_read_be32(&data[2]) == 0x02030405); + assert_se(unaligned_read_be32(&data[3]) == 0x03040506); + + assert_se(unaligned_read_be64(&data[0]) == 0x0001020304050607); + assert_se(unaligned_read_be64(&data[1]) == 0x0102030405060708); + assert_se(unaligned_read_be64(&data[2]) == 0x0203040506070809); + assert_se(unaligned_read_be64(&data[3]) == 0x030405060708090a); + assert_se(unaligned_read_be64(&data[4]) == 0x0405060708090a0b); + assert_se(unaligned_read_be64(&data[5]) == 0x05060708090a0b0c); + assert_se(unaligned_read_be64(&data[6]) == 0x060708090a0b0c0d); + assert_se(unaligned_read_be64(&data[7]) == 0x0708090a0b0c0d0e); + + zero(scratch); + unaligned_write_be16(&scratch[0], 0x0001); + assert_se(memcmp(&scratch[0], &data[0], sizeof(uint16_t)) == 0); + zero(scratch); + unaligned_write_be16(&scratch[1], 0x0102); + assert_se(memcmp(&scratch[1], &data[1], sizeof(uint16_t)) == 0); + + zero(scratch); + unaligned_write_be32(&scratch[0], 0x00010203); + assert_se(memcmp(&scratch[0], &data[0], sizeof(uint32_t)) == 0); + zero(scratch); + unaligned_write_be32(&scratch[1], 0x01020304); + assert_se(memcmp(&scratch[1], &data[1], sizeof(uint32_t)) == 0); + zero(scratch); + unaligned_write_be32(&scratch[2], 0x02030405); + assert_se(memcmp(&scratch[2], &data[2], sizeof(uint32_t)) == 0); + zero(scratch); + unaligned_write_be32(&scratch[3], 0x03040506); + assert_se(memcmp(&scratch[3], &data[3], sizeof(uint32_t)) == 0); + + zero(scratch); + unaligned_write_be64(&scratch[0], 0x0001020304050607); + assert_se(memcmp(&scratch[0], &data[0], sizeof(uint64_t)) == 0); + zero(scratch); + unaligned_write_be64(&scratch[1], 0x0102030405060708); + assert_se(memcmp(&scratch[1], &data[1], sizeof(uint64_t)) == 0); + zero(scratch); + unaligned_write_be64(&scratch[2], 0x0203040506070809); + assert_se(memcmp(&scratch[2], &data[2], sizeof(uint64_t)) == 0); + zero(scratch); + unaligned_write_be64(&scratch[3], 0x030405060708090a); + assert_se(memcmp(&scratch[3], &data[3], sizeof(uint64_t)) == 0); + zero(scratch); + unaligned_write_be64(&scratch[4], 0x0405060708090a0b); + assert_se(memcmp(&scratch[4], &data[4], sizeof(uint64_t)) == 0); + zero(scratch); + unaligned_write_be64(&scratch[5], 0x05060708090a0b0c); + assert_se(memcmp(&scratch[5], &data[5], sizeof(uint64_t)) == 0); + zero(scratch); + unaligned_write_be64(&scratch[6], 0x060708090a0b0c0d); + assert_se(memcmp(&scratch[6], &data[6], sizeof(uint64_t)) == 0); + zero(scratch); + unaligned_write_be64(&scratch[7], 0x0708090a0b0c0d0e); + assert_se(memcmp(&scratch[7], &data[7], sizeof(uint64_t)) == 0); +} + +TEST(le) { + uint8_t scratch[16]; + + assert_se(unaligned_read_le16(&data[0]) == 0x0100); + assert_se(unaligned_read_le16(&data[1]) == 0x0201); + + assert_se(unaligned_read_le32(&data[0]) == 0x03020100); + assert_se(unaligned_read_le32(&data[1]) == 0x04030201); + assert_se(unaligned_read_le32(&data[2]) == 0x05040302); + assert_se(unaligned_read_le32(&data[3]) == 0x06050403); + + assert_se(unaligned_read_le64(&data[0]) == 0x0706050403020100); + assert_se(unaligned_read_le64(&data[1]) == 0x0807060504030201); + assert_se(unaligned_read_le64(&data[2]) == 0x0908070605040302); + assert_se(unaligned_read_le64(&data[3]) == 0x0a09080706050403); + assert_se(unaligned_read_le64(&data[4]) == 0x0b0a090807060504); + assert_se(unaligned_read_le64(&data[5]) == 0x0c0b0a0908070605); + assert_se(unaligned_read_le64(&data[6]) == 0x0d0c0b0a09080706); + assert_se(unaligned_read_le64(&data[7]) == 0x0e0d0c0b0a090807); + + zero(scratch); + unaligned_write_le16(&scratch[0], 0x0100); + assert_se(memcmp(&scratch[0], &data[0], sizeof(uint16_t)) == 0); + zero(scratch); + unaligned_write_le16(&scratch[1], 0x0201); + assert_se(memcmp(&scratch[1], &data[1], sizeof(uint16_t)) == 0); + + zero(scratch); + unaligned_write_le32(&scratch[0], 0x03020100); + + assert_se(memcmp(&scratch[0], &data[0], sizeof(uint32_t)) == 0); + zero(scratch); + unaligned_write_le32(&scratch[1], 0x04030201); + assert_se(memcmp(&scratch[1], &data[1], sizeof(uint32_t)) == 0); + zero(scratch); + unaligned_write_le32(&scratch[2], 0x05040302); + assert_se(memcmp(&scratch[2], &data[2], sizeof(uint32_t)) == 0); + zero(scratch); + unaligned_write_le32(&scratch[3], 0x06050403); + assert_se(memcmp(&scratch[3], &data[3], sizeof(uint32_t)) == 0); + + zero(scratch); + unaligned_write_le64(&scratch[0], 0x0706050403020100); + assert_se(memcmp(&scratch[0], &data[0], sizeof(uint64_t)) == 0); + zero(scratch); + unaligned_write_le64(&scratch[1], 0x0807060504030201); + assert_se(memcmp(&scratch[1], &data[1], sizeof(uint64_t)) == 0); + zero(scratch); + unaligned_write_le64(&scratch[2], 0x0908070605040302); + assert_se(memcmp(&scratch[2], &data[2], sizeof(uint64_t)) == 0); + zero(scratch); + unaligned_write_le64(&scratch[3], 0x0a09080706050403); + assert_se(memcmp(&scratch[3], &data[3], sizeof(uint64_t)) == 0); + zero(scratch); + unaligned_write_le64(&scratch[4], 0x0B0A090807060504); + assert_se(memcmp(&scratch[4], &data[4], sizeof(uint64_t)) == 0); + zero(scratch); + unaligned_write_le64(&scratch[5], 0x0c0b0a0908070605); + assert_se(memcmp(&scratch[5], &data[5], sizeof(uint64_t)) == 0); + zero(scratch); + unaligned_write_le64(&scratch[6], 0x0d0c0b0a09080706); + assert_se(memcmp(&scratch[6], &data[6], sizeof(uint64_t)) == 0); + zero(scratch); + unaligned_write_le64(&scratch[7], 0x0e0d0c0b0a090807); + assert_se(memcmp(&scratch[7], &data[7], sizeof(uint64_t)) == 0); +} + +TEST(ne) { + uint16_t x = 4711; + uint32_t y = 123456; + uint64_t z = 9876543210; + + /* Note that we don't bother actually testing alignment issues in this function, after all the _ne() functions + * are just aliases for the _le() or _be() implementations, which we test extensively above. Hence, in this + * function, just ensure that they map to the right version on the local architecture. */ + + assert_se(unaligned_read_ne16(&x) == 4711); + assert_se(unaligned_read_ne32(&y) == 123456); + assert_se(unaligned_read_ne64(&z) == 9876543210); + + unaligned_write_ne16(&x, 1); + unaligned_write_ne32(&y, 2); + unaligned_write_ne64(&z, 3); + + assert_se(x == 1); + assert_se(y == 2); + assert_se(z == 3); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-unit-file.c b/src/test/test-unit-file.c new file mode 100644 index 0000000..9f8787b --- /dev/null +++ b/src/test/test-unit-file.c @@ -0,0 +1,110 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "initrd-util.h" +#include "path-lookup.h" +#include "set.h" +#include "special.h" +#include "strv.h" +#include "tests.h" +#include "unit-file.h" + +TEST(unit_validate_alias_symlink_and_warn) { + assert_se(unit_validate_alias_symlink_or_warn(LOG_INFO, "/path/a.service", "/other/b.service") == 0); + assert_se(unit_validate_alias_symlink_or_warn(LOG_INFO, "/path/a.service", "/other/b.socket") == -EXDEV); + assert_se(unit_validate_alias_symlink_or_warn(LOG_INFO, "/path/a.service", "/other/b.foobar") == -EXDEV); + assert_se(unit_validate_alias_symlink_or_warn(LOG_INFO, "/path/a@.service", "/other/b@.service") == 0); + assert_se(unit_validate_alias_symlink_or_warn(LOG_INFO, "/path/a@.service", "/other/b@.socket") == -EXDEV); + assert_se(unit_validate_alias_symlink_or_warn(LOG_INFO, "/path/a@XXX.service", "/other/b@YYY.service") == -EXDEV); + assert_se(unit_validate_alias_symlink_or_warn(LOG_INFO, "/path/a@XXX.service", "/other/b@YYY.socket") == -EXDEV); + assert_se(unit_validate_alias_symlink_or_warn(LOG_INFO, "/path/a@.service", "/other/b@YYY.service") == -EXDEV); + assert_se(unit_validate_alias_symlink_or_warn(LOG_INFO, "/path/a@XXX.service", "/other/b@XXX.service") == 0); + assert_se(unit_validate_alias_symlink_or_warn(LOG_INFO, "/path/a@XXX.service", "/other/b@.service") == 0); + assert_se(unit_validate_alias_symlink_or_warn(LOG_INFO, "/path/a@.service", "/other/b.service") == -EXDEV); + assert_se(unit_validate_alias_symlink_or_warn(LOG_INFO, "/path/a.service", "/other/b@.service") == -EXDEV); + assert_se(unit_validate_alias_symlink_or_warn(LOG_INFO, "/path/a@.slice", "/other/b.slice") == -EINVAL); + assert_se(unit_validate_alias_symlink_or_warn(LOG_INFO, "/path/a.slice", "/other/b.slice") == -EINVAL); +} + +TEST(unit_file_build_name_map) { + _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_hashmap_free_ Hashmap *unit_ids = NULL; + _cleanup_hashmap_free_ Hashmap *unit_names = NULL; + const char *k, *dst; + char **v, **ids; + usec_t mtime = 0; + int r; + + ids = strv_skip(saved_argv, 1); + + assert_se(lookup_paths_init(&lp, RUNTIME_SCOPE_SYSTEM, 0, NULL) >= 0); + + assert_se(unit_file_build_name_map(&lp, &mtime, &unit_ids, &unit_names, NULL) == 1); + + HASHMAP_FOREACH_KEY(dst, k, unit_ids) + log_info("ids: %s → %s", k, dst); + + HASHMAP_FOREACH_KEY(v, k, unit_names) { + _cleanup_free_ char *j = strv_join(v, ", "); + log_info("aliases: %s ← %s", k, j); + } + + char buf[FORMAT_TIMESTAMP_MAX]; + log_debug("Last modification time: %s", format_timestamp(buf, sizeof buf, mtime)); + + r = unit_file_build_name_map(&lp, &mtime, &unit_ids, &unit_names, NULL); + assert_se(IN_SET(r, 0, 1)); + if (r == 0) + log_debug("Cache rebuild skipped based on mtime."); + + STRV_FOREACH(id, ids) { + const char *fragment, *name; + _cleanup_set_free_free_ Set *names = NULL; + log_info("*** %s ***", *id); + r = unit_file_find_fragment(unit_ids, + unit_names, + *id, + &fragment, + &names); + assert_se(r == 0); + log_info("fragment: %s", fragment); + log_info("names:"); + SET_FOREACH(name, names) + log_info(" %s", name); + } + + /* Make sure everything still works if we don't collect names. */ + STRV_FOREACH(id, ids) { + const char *fragment; + log_info("*** %s ***", *id); + r = unit_file_find_fragment(unit_ids, + unit_names, + *id, + &fragment, + NULL); + assert_se(r == 0); + log_info("fragment: %s", fragment); + } +} + +TEST(runlevel_to_target) { + in_initrd_force(false); + assert_se(streq_ptr(runlevel_to_target(NULL), NULL)); + assert_se(streq_ptr(runlevel_to_target("unknown-runlevel"), NULL)); + assert_se(streq_ptr(runlevel_to_target("rd.unknown-runlevel"), NULL)); + assert_se(streq_ptr(runlevel_to_target("3"), SPECIAL_MULTI_USER_TARGET)); + assert_se(streq_ptr(runlevel_to_target("rd.rescue"), NULL)); + + in_initrd_force(true); + assert_se(streq_ptr(runlevel_to_target(NULL), NULL)); + assert_se(streq_ptr(runlevel_to_target("unknown-runlevel"), NULL)); + assert_se(streq_ptr(runlevel_to_target("rd.unknown-runlevel"), NULL)); + assert_se(streq_ptr(runlevel_to_target("3"), NULL)); + assert_se(streq_ptr(runlevel_to_target("rd.rescue"), SPECIAL_RESCUE_TARGET)); +} + +static int intro(void) { + log_show_color(true); + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/src/test/test-unit-name.c b/src/test/test-unit-name.c new file mode 100644 index 0000000..8e9332c --- /dev/null +++ b/src/test/test-unit-name.c @@ -0,0 +1,1009 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdio.h> +#include <stdlib.h> + +#include "sd-id128.h" + +#include "alloc-util.h" +#include "all-units.h" +#include "glob-util.h" +#include "format-util.h" +#include "hostname-util.h" +#include "macro.h" +#include "manager.h" +#include "path-util.h" +#include "rm-rf.h" +#include "special.h" +#include "specifier.h" +#include "string-util.h" +#include "tests.h" +#include "tmpfile-util.h" +#include "unit-def.h" +#include "unit-name.h" +#include "unit-printf.h" +#include "unit.h" +#include "user-util.h" + +static char *runtime_dir = NULL; + +STATIC_DESTRUCTOR_REGISTER(runtime_dir, rm_rf_physical_and_freep); + +static void test_unit_name_is_valid_one(const char *name, UnitNameFlags flags, bool expected) { + log_info("%s ( %s%s%s ): %s", + name, + (flags & UNIT_NAME_PLAIN) ? "plain" : "", + (flags & UNIT_NAME_INSTANCE) ? " instance" : "", + (flags & UNIT_NAME_TEMPLATE) ? " template" : "", + yes_no(expected)); + assert_se(unit_name_is_valid(name, flags) == expected); +} + +TEST(unit_name_is_valid) { + test_unit_name_is_valid_one("foo.service", UNIT_NAME_ANY, true); + test_unit_name_is_valid_one("foo.service", UNIT_NAME_PLAIN, true); + test_unit_name_is_valid_one("foo.service", UNIT_NAME_INSTANCE, false); + test_unit_name_is_valid_one("foo.service", UNIT_NAME_TEMPLATE, false); + test_unit_name_is_valid_one("foo.service", UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE, false); + + test_unit_name_is_valid_one("foo@bar.service", UNIT_NAME_ANY, true); + test_unit_name_is_valid_one("foo@bar.service", UNIT_NAME_PLAIN, false); + test_unit_name_is_valid_one("foo@bar.service", UNIT_NAME_INSTANCE, true); + test_unit_name_is_valid_one("foo@bar.service", UNIT_NAME_TEMPLATE, false); + test_unit_name_is_valid_one("foo@bar.service", UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE, true); + + test_unit_name_is_valid_one("foo@bar@bar.service", UNIT_NAME_ANY, true); + test_unit_name_is_valid_one("foo@bar@bar.service", UNIT_NAME_PLAIN, false); + test_unit_name_is_valid_one("foo@bar@bar.service", UNIT_NAME_INSTANCE, true); + test_unit_name_is_valid_one("foo@bar@bar.service", UNIT_NAME_TEMPLATE, false); + test_unit_name_is_valid_one("foo@bar@bar.service", UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE, true); + + test_unit_name_is_valid_one("foo@.service", UNIT_NAME_ANY, true); + test_unit_name_is_valid_one("foo@.service", UNIT_NAME_PLAIN, false); + test_unit_name_is_valid_one("foo@.service", UNIT_NAME_INSTANCE, false); + test_unit_name_is_valid_one("foo@.service", UNIT_NAME_TEMPLATE, true); + test_unit_name_is_valid_one("foo@.service", UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE, true); + test_unit_name_is_valid_one(".test.service", UNIT_NAME_PLAIN, true); + test_unit_name_is_valid_one(".test@.service", UNIT_NAME_TEMPLATE, true); + test_unit_name_is_valid_one("_strange::::.service", UNIT_NAME_ANY, true); + + test_unit_name_is_valid_one(".service", UNIT_NAME_ANY, false); + test_unit_name_is_valid_one("", UNIT_NAME_ANY, false); + test_unit_name_is_valid_one("foo.waldo", UNIT_NAME_ANY, false); + test_unit_name_is_valid_one("@.service", UNIT_NAME_ANY, false); + test_unit_name_is_valid_one("@piep.service", UNIT_NAME_ANY, false); + + test_unit_name_is_valid_one("user@1000.slice", UNIT_NAME_ANY, true); + test_unit_name_is_valid_one("user@1000.slice", UNIT_NAME_INSTANCE, true); + test_unit_name_is_valid_one("user@1000.slice", UNIT_NAME_TEMPLATE, false); + + test_unit_name_is_valid_one("foo@%i.service", UNIT_NAME_ANY, false); + test_unit_name_is_valid_one("foo@%i.service", UNIT_NAME_INSTANCE, false); + test_unit_name_is_valid_one("foo@%%i.service", UNIT_NAME_INSTANCE, false); + test_unit_name_is_valid_one("foo@%%i%f.service", UNIT_NAME_INSTANCE, false); + test_unit_name_is_valid_one("foo@%F.service", UNIT_NAME_INSTANCE, false); + + test_unit_name_is_valid_one("foo.target.wants/plain.service", UNIT_NAME_ANY, false); + test_unit_name_is_valid_one("foo.target.conf/foo.conf", UNIT_NAME_ANY, false); + test_unit_name_is_valid_one("foo.target.requires/plain.socket", UNIT_NAME_ANY, false); +} + +static void test_unit_name_replace_instance_one(const char *pattern, const char *repl, const char *expected, int ret) { + _cleanup_free_ char *t = NULL; + assert_se(unit_name_replace_instance(pattern, repl, &t) == ret); + puts(strna(t)); + assert_se(streq_ptr(t, expected)); +} + +TEST(unit_name_replace_instance) { + test_unit_name_replace_instance_one("foo@.service", "waldo", "foo@waldo.service", 0); + test_unit_name_replace_instance_one("foo@xyz.service", "waldo", "foo@waldo.service", 0); + test_unit_name_replace_instance_one("xyz", "waldo", NULL, -EINVAL); + test_unit_name_replace_instance_one("", "waldo", NULL, -EINVAL); + test_unit_name_replace_instance_one("foo.service", "waldo", NULL, -EINVAL); + test_unit_name_replace_instance_one(".service", "waldo", NULL, -EINVAL); + test_unit_name_replace_instance_one("foo@", "waldo", NULL, -EINVAL); + test_unit_name_replace_instance_one("@bar", "waldo", NULL, -EINVAL); +} + +static void test_unit_name_from_path_one(const char *path, const char *suffix, const char *expected, int ret) { + _cleanup_free_ char *t = NULL; + int r; + + assert_se(unit_name_from_path(path, suffix, &t) == ret); + puts(strna(t)); + assert_se(streq_ptr(t, expected)); + + if (t) { + _cleanup_free_ char *k = NULL; + + /* We don't support converting hashed unit names back to paths */ + r = unit_name_to_path(t, &k); + if (r == -ENAMETOOLONG) + return; + assert(r == 0); + + puts(strna(k)); + assert_se(path_equal(k, empty_to_root(path))); + } +} + +TEST(unit_name_is_hashed) { + assert_se(!unit_name_is_hashed("")); + assert_se(!unit_name_is_hashed("foo@bar.service")); + assert_se(!unit_name_is_hashed("foo@.service")); + assert_se(unit_name_is_hashed("waldoaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_7736d9ed33c2ec55.mount")); + assert_se(!unit_name_is_hashed("waldoaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_7736D9ED33C2EC55.mount")); + assert_se(!unit_name_is_hashed("waldoaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!7736d9ed33c2ec55.mount")); + assert_se(!unit_name_is_hashed("waldoaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_7736d9gd33c2ec55.mount")); + assert_se(!unit_name_is_hashed("waldoaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_.mount")); + assert_se(!unit_name_is_hashed("waldoaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_2103e1466b87f7f7@waldo.mount")); + assert_se(!unit_name_is_hashed("waldoaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_2103e1466b87f7f7@.mount")); +} + +TEST(unit_name_from_path) { + test_unit_name_from_path_one("/waldo", ".mount", "waldo.mount", 0); + test_unit_name_from_path_one("/waldo/quuix", ".mount", "waldo-quuix.mount", 0); + test_unit_name_from_path_one("/waldo/quuix/", ".mount", "waldo-quuix.mount", 0); + test_unit_name_from_path_one("", ".mount", "-.mount", 0); + test_unit_name_from_path_one("/", ".mount", "-.mount", 0); + test_unit_name_from_path_one("///", ".mount", "-.mount", 0); + test_unit_name_from_path_one("/foo/../bar", ".mount", NULL, -EINVAL); + test_unit_name_from_path_one("/foo/./bar", ".mount", "foo-bar.mount", 0); + test_unit_name_from_path_one("/waldoaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", ".mount", + "waldoaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_7736d9ed33c2ec55.mount", 0); +} + +static void test_unit_name_from_path_instance_one(const char *pattern, const char *path, const char *suffix, const char *expected, int ret) { + _cleanup_free_ char *t = NULL; + + assert_se(unit_name_from_path_instance(pattern, path, suffix, &t) == ret); + puts(strna(t)); + assert_se(streq_ptr(t, expected)); + + if (t) { + _cleanup_free_ char *k = NULL, *v = NULL; + + assert_se(unit_name_to_instance(t, &k) > 0); + assert_se(unit_name_path_unescape(k, &v) == 0); + assert_se(path_equal(v, empty_to_root(path))); + } +} + +TEST(unit_name_from_path_instance) { + test_unit_name_from_path_instance_one("waldo", "/waldo", ".mount", "waldo@waldo.mount", 0); + test_unit_name_from_path_instance_one("waldo", "/waldo////quuix////", ".mount", "waldo@waldo-quuix.mount", 0); + test_unit_name_from_path_instance_one("waldo", "/", ".mount", "waldo@-.mount", 0); + test_unit_name_from_path_instance_one("waldo", "", ".mount", "waldo@-.mount", 0); + test_unit_name_from_path_instance_one("waldo", "///", ".mount", "waldo@-.mount", 0); + test_unit_name_from_path_instance_one("waldo", "..", ".mount", NULL, -EINVAL); + test_unit_name_from_path_instance_one("waldo", "/foo", ".waldi", NULL, -EINVAL); + test_unit_name_from_path_instance_one("wa--ldo", "/--", ".mount", "wa--ldo@\\x2d\\x2d.mount", 0); +} + +static void test_unit_name_to_path_one(const char *unit, const char *path, int ret) { + _cleanup_free_ char *p = NULL; + + assert_se(unit_name_to_path(unit, &p) == ret); + assert_se(streq_ptr(path, p)); +} + +TEST(unit_name_to_path) { + test_unit_name_to_path_one("home.mount", "/home", 0); + test_unit_name_to_path_one("home-lennart.mount", "/home/lennart", 0); + test_unit_name_to_path_one("home-lennart-.mount", NULL, -EINVAL); + test_unit_name_to_path_one("-home-lennart.mount", NULL, -EINVAL); + test_unit_name_to_path_one("-home--lennart.mount", NULL, -EINVAL); + test_unit_name_to_path_one("home-..-lennart.mount", NULL, -EINVAL); + test_unit_name_to_path_one("", NULL, -EINVAL); + test_unit_name_to_path_one("home/foo", NULL, -EINVAL); +} + +static void test_unit_name_mangle_one(bool allow_globs, const char *pattern, const char *expect, int ret) { + _cleanup_free_ char *t = NULL; + int r; + + r = unit_name_mangle(pattern, (allow_globs * UNIT_NAME_MANGLE_GLOB) | UNIT_NAME_MANGLE_WARN, &t); + log_debug("%s: %s -> %d, %s", __func__, pattern, r, strnull(t)); + + assert_se(r == ret); + puts(strna(t)); + assert_se(streq_ptr(t, expect)); + + if (t) { + _cleanup_free_ char *k = NULL; + + assert_se(unit_name_is_valid(t, UNIT_NAME_ANY) || + (allow_globs && string_is_glob(t))); + + assert_se(unit_name_mangle(t, (allow_globs * UNIT_NAME_MANGLE_GLOB) | UNIT_NAME_MANGLE_WARN, &k) == 0); + assert_se(streq_ptr(t, k)); + } +} + +TEST(unit_name_mangle) { + test_unit_name_mangle_one(false, "foo.service", "foo.service", 0); + test_unit_name_mangle_one(false, "/home", "home.mount", 1); + test_unit_name_mangle_one(false, "/dev/sda", "dev-sda.device", 1); + test_unit_name_mangle_one(false, "üxknürz.service", "\\xc3\\xbcxkn\\xc3\\xbcrz.service", 1); + test_unit_name_mangle_one(false, "foobar-meh...waldi.service", "foobar-meh...waldi.service", 0); + test_unit_name_mangle_one(false, "_____####----.....service", "_____\\x23\\x23\\x23\\x23----.....service", 1); + test_unit_name_mangle_one(false, "_____##@;;;,,,##----.....service", "_____\\x23\\x23@\\x3b\\x3b\\x3b\\x2c\\x2c\\x2c\\x23\\x23----.....service", 1); + test_unit_name_mangle_one(false, "xxx@@@@/////\\\\\\\\\\yyy.service", "xxx@@@@-----\\\\\\\\\\yyy.service", 1); + test_unit_name_mangle_one(false, "", NULL, -EINVAL); + + test_unit_name_mangle_one(true, "foo.service", "foo.service", 0); + test_unit_name_mangle_one(true, "foo", "foo.service", 1); + test_unit_name_mangle_one(true, "foo*", "foo*", 0); + test_unit_name_mangle_one(true, "ü*", "\\xc3\\xbc*", 1); +} + +static void test_unit_name_mangle_with_suffix_one(const char *arg, int expected, const char *expected_name) { + _cleanup_free_ char *s = NULL; + int r; + + r = unit_name_mangle_with_suffix(arg, NULL, 0, ".service", &s); + log_debug("%s: %s -> %d, %s", __func__, arg, r, strnull(s)); + + assert_se(r == expected); + assert_se(streq_ptr(s, expected_name)); +} + +TEST(unit_name_mangle_with_suffix) { + test_unit_name_mangle_with_suffix_one("", -EINVAL, NULL); + + test_unit_name_mangle_with_suffix_one("/dev", 1, "dev.mount"); + test_unit_name_mangle_with_suffix_one("/../dev", 1, "dev.mount"); + test_unit_name_mangle_with_suffix_one("/../dev/.", 1, "dev.mount"); + /* We don't skip the last '..', and it makes this an invalid device or mount name */ + test_unit_name_mangle_with_suffix_one("/.././dev/..", 1, "-..-.-dev-...service"); + test_unit_name_mangle_with_suffix_one("/.././dev", 1, "dev.mount"); + test_unit_name_mangle_with_suffix_one("/./.././../dev/", 1, "dev.mount"); + + test_unit_name_mangle_with_suffix_one("/dev/sda", 1, "dev-sda.device"); + test_unit_name_mangle_with_suffix_one("/dev/sda5", 1, "dev-sda5.device"); + + test_unit_name_mangle_with_suffix_one("/sys", 1, "sys.mount"); + test_unit_name_mangle_with_suffix_one("/../sys", 1, "sys.mount"); + test_unit_name_mangle_with_suffix_one("/../sys/.", 1, "sys.mount"); + /* We don't skip the last '..', and it makes this an invalid device or mount name */ + test_unit_name_mangle_with_suffix_one("/.././sys/..", 1, "-..-.-sys-...service"); + test_unit_name_mangle_with_suffix_one("/.././sys", 1, "sys.mount"); + test_unit_name_mangle_with_suffix_one("/./.././../sys/", 1, "sys.mount"); + + test_unit_name_mangle_with_suffix_one("/proc", 1, "proc.mount"); + test_unit_name_mangle_with_suffix_one("/../proc", 1, "proc.mount"); + test_unit_name_mangle_with_suffix_one("/../proc/.", 1, "proc.mount"); + /* We don't skip the last '..', and it makes this an invalid device or mount name */ + test_unit_name_mangle_with_suffix_one("/.././proc/..", 1, "-..-.-proc-...service"); + test_unit_name_mangle_with_suffix_one("/.././proc", 1, "proc.mount"); + test_unit_name_mangle_with_suffix_one("/./.././../proc/", 1, "proc.mount"); +} + +TEST_RET(unit_printf, .sd_booted = true) { + _cleanup_free_ char + *architecture, *os_image_version, *boot_id = NULL, *os_build_id, + *hostname, *short_hostname, *pretty_hostname, + *machine_id = NULL, *os_image_id, *os_id, *os_version_id, *os_variant_id, + *user, *group, *uid, *gid, *home, *shell, + *tmp_dir, *var_tmp_dir; + _cleanup_(manager_freep) Manager *m = NULL; + _cleanup_close_ int fd = -EBADF; + Unit *u; + int r; + + _cleanup_(unlink_tempfilep) char filename[] = "/tmp/test-unit_printf.XXXXXX"; + fd = mkostemp_safe(filename); + assert_se(fd >= 0); + + /* Using the specifier functions is admittedly a bit circular, but we don't want to reimplement the + * logic a second time. We're at least testing that the hookup works. */ + assert_se(specifier_architecture('a', NULL, NULL, NULL, &architecture) >= 0); + assert_se(architecture); + assert_se(specifier_os_image_version('A', NULL, NULL, NULL, &os_image_version) >= 0); + if (sd_booted() > 0) { + assert_se(specifier_boot_id('b', NULL, NULL, NULL, &boot_id) >= 0); + assert_se(boot_id); + } + assert_se(specifier_os_build_id('B', NULL, NULL, NULL, &os_build_id) >= 0); + assert_se(hostname = gethostname_malloc()); + assert_se(specifier_short_hostname('l', NULL, NULL, NULL, &short_hostname) == 0); + assert_se(short_hostname); + assert_se(specifier_pretty_hostname('q', NULL, NULL, NULL, &pretty_hostname) == 0); + assert_se(pretty_hostname); + if (sd_id128_get_machine(NULL) >= 0) { + assert_se(specifier_machine_id('m', NULL, NULL, NULL, &machine_id) >= 0); + assert_se(machine_id); + } + assert_se(specifier_os_image_id('M', NULL, NULL, NULL, &os_image_id) >= 0); + assert_se(specifier_os_id('o', NULL, NULL, NULL, &os_id) >= 0); + assert_se(specifier_os_version_id('w', NULL, NULL, NULL, &os_version_id) >= 0); + assert_se(specifier_os_variant_id('W', NULL, NULL, NULL, &os_variant_id) >= 0); + assert_se(user = uid_to_name(getuid())); + assert_se(group = gid_to_name(getgid())); + assert_se(asprintf(&uid, UID_FMT, getuid())); + assert_se(asprintf(&gid, UID_FMT, getgid())); + assert_se(get_home_dir(&home) >= 0); + assert_se(get_shell(&shell) >= 0); + assert_se(specifier_tmp_dir('T', NULL, NULL, NULL, &tmp_dir) >= 0); + assert_se(tmp_dir); + assert_se(specifier_var_tmp_dir('V', NULL, NULL, NULL, &var_tmp_dir) >= 0); + assert_se(var_tmp_dir); + + r = manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_MINIMAL, &m); + if (manager_errno_skip_test(r)) + return log_tests_skipped_errno(r, "manager_new"); + assert_se(r == 0); + + assert_se(free_and_strdup(&m->cgroup_root, "/cgroup-root") == 1); + +#define expect(unit, pattern, _expected) \ + { \ + _cleanup_free_ char *t = NULL; \ + assert_se(unit_full_printf(unit, pattern, &t) >= 0); \ + const char *expected = strempty(_expected); \ + printf("%s: result: %s\n expect: %s\n", pattern, t, expected); \ + assert_se(fnmatch(expected, t, FNM_NOESCAPE) == 0); \ + } + + assert_se(u = unit_new(m, sizeof(Service))); + assert_se(unit_add_name(u, "blah.service") == 0); + assert_se(unit_add_name(u, "blah.service") == 0); + + /* We need *a* file that exists, but it doesn't even need to have the right suffix. */ + assert_se(free_and_strdup(&u->fragment_path, filename) == 1); + + /* This sets the slice to /app.slice. */ + assert_se(unit_set_default_slice(u) == 1); + + /* general tests */ + expect(u, "%%", "%"); + expect(u, "%%s", "%s"); + expect(u, "%,", "%,"); + expect(u, "%", "%"); + + /* normal unit */ + expect(u, "%a", architecture); + expect(u, "%A", os_image_version); + if (boot_id) + expect(u, "%b", boot_id); + expect(u, "%B", os_build_id); + expect(u, "%H", hostname); + expect(u, "%l", short_hostname); + expect(u, "%q", pretty_hostname); + if (machine_id) + expect(u, "%m", machine_id); + expect(u, "%M", os_image_id); + expect(u, "%o", os_id); + expect(u, "%w", os_version_id); + expect(u, "%W", os_variant_id); + expect(u, "%g", group); + expect(u, "%G", gid); + expect(u, "%u", user); + expect(u, "%U", uid); + expect(u, "%T", tmp_dir); + expect(u, "%V", var_tmp_dir); + + expect(u, "%i", ""); + expect(u, "%I", ""); + expect(u, "%j", "blah"); + expect(u, "%J", "blah"); + expect(u, "%n", "blah.service"); + expect(u, "%N", "blah"); + expect(u, "%p", "blah"); + expect(u, "%P", "blah"); + expect(u, "%f", "/blah"); + expect(u, "%y", filename); + expect(u, "%Y", "/tmp"); + expect(u, "%C", m->prefix[EXEC_DIRECTORY_CACHE]); + expect(u, "%d", "*/credentials/blah.service"); + expect(u, "%E", m->prefix[EXEC_DIRECTORY_CONFIGURATION]); + expect(u, "%L", m->prefix[EXEC_DIRECTORY_LOGS]); + expect(u, "%S", m->prefix[EXEC_DIRECTORY_STATE]); + expect(u, "%t", m->prefix[EXEC_DIRECTORY_RUNTIME]); + expect(u, "%h", home); + expect(u, "%s", shell); + + /* deprecated */ + expect(u, "%c", "/cgroup-root/app.slice/blah.service"); + expect(u, "%r", "/cgroup-root/app.slice"); + expect(u, "%R", "/cgroup-root"); + + /* templated */ + assert_se(u = unit_new(m, sizeof(Service))); + assert_se(unit_add_name(u, "blah@foo-foo.service") == 0); + assert_se(unit_add_name(u, "blah@foo-foo.service") == 0); + + assert_se(free_and_strdup(&u->fragment_path, filename) == 1); + + /* This sets the slice to /app.slice/app-blah.slice. */ + assert_se(unit_set_default_slice(u) == 1); + + expect(u, "%i", "foo-foo"); + expect(u, "%I", "foo/foo"); + expect(u, "%j", "blah"); + expect(u, "%J", "blah"); + expect(u, "%n", "blah@foo-foo.service"); + expect(u, "%N", "blah@foo-foo"); + expect(u, "%p", "blah"); + expect(u, "%P", "blah"); + expect(u, "%f", "/foo/foo"); + expect(u, "%y", filename); + expect(u, "%Y", "/tmp"); + expect(u, "%C", m->prefix[EXEC_DIRECTORY_CACHE]); + expect(u, "%d", "*/credentials/blah@foo-foo.service"); + expect(u, "%E", m->prefix[EXEC_DIRECTORY_CONFIGURATION]); + expect(u, "%L", m->prefix[EXEC_DIRECTORY_LOGS]); + expect(u, "%S", m->prefix[EXEC_DIRECTORY_STATE]); + expect(u, "%t", m->prefix[EXEC_DIRECTORY_RUNTIME]); + expect(u, "%h", home); + expect(u, "%s", shell); + + /* deprecated */ + expect(u, "%c", "/cgroup-root/app.slice/app-blah.slice/blah@foo-foo.service"); + expect(u, "%r", "/cgroup-root/app.slice/app-blah.slice"); + expect(u, "%R", "/cgroup-root"); + + /* templated with components */ + assert_se(u = unit_new(m, sizeof(Slice))); + assert_se(unit_add_name(u, "blah-blah\\x2d.slice") == 0); + + expect(u, "%i", ""); + expect(u, "%I", ""); + expect(u, "%j", "blah\\x2d"); + expect(u, "%J", "blah-"); + expect(u, "%n", "blah-blah\\x2d.slice"); + expect(u, "%N", "blah-blah\\x2d"); + expect(u, "%p", "blah-blah\\x2d"); + expect(u, "%P", "blah/blah-"); + expect(u, "%f", "/blah/blah-"); + + /* deprecated */ + expect(u, "%c", "/cgroup-root/blah-blah\\x2d.slice"); + expect(u, "%r", "/cgroup-root"); + expect(u, "%R", "/cgroup-root"); + +#undef expect + + return 0; +} + +TEST(unit_instance_is_valid) { + assert_se(unit_instance_is_valid("fooBar")); + assert_se(unit_instance_is_valid("foo-bar")); + assert_se(unit_instance_is_valid("foo.stUff")); + assert_se(unit_instance_is_valid("fOo123.stuff")); + assert_se(unit_instance_is_valid("@f_oo123.Stuff")); + + assert_se(!unit_instance_is_valid("$¢£")); + assert_se(!unit_instance_is_valid("")); + assert_se(!unit_instance_is_valid("foo bar")); + assert_se(!unit_instance_is_valid("foo/bar")); +} + +TEST(unit_prefix_is_valid) { + assert_se(unit_prefix_is_valid("fooBar")); + assert_se(unit_prefix_is_valid("foo-bar")); + assert_se(unit_prefix_is_valid("foo.stUff")); + assert_se(unit_prefix_is_valid("fOo123.stuff")); + assert_se(unit_prefix_is_valid("foo123.Stuff")); + + assert_se(!unit_prefix_is_valid("$¢£")); + assert_se(!unit_prefix_is_valid("")); + assert_se(!unit_prefix_is_valid("foo bar")); + assert_se(!unit_prefix_is_valid("foo/bar")); + assert_se(!unit_prefix_is_valid("@foo-bar")); +} + +TEST(unit_name_change_suffix) { + char *t; + + assert_se(unit_name_change_suffix("foo.mount", ".service", &t) == 0); + assert_se(streq(t, "foo.service")); + free(t); + + assert_se(unit_name_change_suffix("foo@stuff.service", ".socket", &t) == 0); + assert_se(streq(t, "foo@stuff.socket")); + free(t); +} + +TEST(unit_name_build) { + char *t; + + assert_se(unit_name_build("foo", "bar", ".service", &t) == 0); + assert_se(streq(t, "foo@bar.service")); + free(t); + + assert_se(unit_name_build("fo0-stUff_b", "bar", ".mount", &t) == 0); + assert_se(streq(t, "fo0-stUff_b@bar.mount")); + free(t); + + assert_se(unit_name_build("foo", NULL, ".service", &t) == 0); + assert_se(streq(t, "foo.service")); + free(t); +} + +TEST(slice_name_is_valid) { + assert_se( slice_name_is_valid(SPECIAL_ROOT_SLICE)); + assert_se( slice_name_is_valid("foo.slice")); + assert_se( slice_name_is_valid("foo-bar.slice")); + assert_se( slice_name_is_valid("foo-bar-baz.slice")); + assert_se(!slice_name_is_valid("-foo-bar-baz.slice")); + assert_se(!slice_name_is_valid("foo-bar-baz-.slice")); + assert_se(!slice_name_is_valid("-foo-bar-baz-.slice")); + assert_se(!slice_name_is_valid("foo-bar--baz.slice")); + assert_se(!slice_name_is_valid("foo--bar--baz.slice")); + assert_se(!slice_name_is_valid(".slice")); + assert_se(!slice_name_is_valid("")); + assert_se(!slice_name_is_valid("foo.service")); + + assert_se(!slice_name_is_valid("foo@.slice")); + assert_se(!slice_name_is_valid("foo@bar.slice")); + assert_se(!slice_name_is_valid("foo-bar@baz.slice")); + assert_se(!slice_name_is_valid("foo@bar@baz.slice")); + assert_se(!slice_name_is_valid("foo@bar-baz.slice")); + assert_se(!slice_name_is_valid("-foo-bar-baz@.slice")); + assert_se(!slice_name_is_valid("foo-bar-baz@-.slice")); + assert_se(!slice_name_is_valid("foo-bar-baz@a--b.slice")); + assert_se(!slice_name_is_valid("-foo-bar-baz@-.slice")); + assert_se(!slice_name_is_valid("foo-bar--baz@.slice")); + assert_se(!slice_name_is_valid("foo--bar--baz@.slice")); + assert_se(!slice_name_is_valid("@.slice")); + assert_se(!slice_name_is_valid("foo@bar.service")); +} + +TEST(build_subslice) { + char *a; + char *b; + + assert_se(slice_build_subslice(SPECIAL_ROOT_SLICE, "foo", &a) >= 0); + assert_se(slice_build_subslice(a, "bar", &b) >= 0); + free(a); + assert_se(slice_build_subslice(b, "barfoo", &a) >= 0); + free(b); + assert_se(slice_build_subslice(a, "foobar", &b) >= 0); + free(a); + assert_se(streq(b, "foo-bar-barfoo-foobar.slice")); + free(b); + + assert_se(slice_build_subslice("foo.service", "bar", &a) < 0); + assert_se(slice_build_subslice("foo", "bar", &a) < 0); +} + +static void test_build_parent_slice_one(const char *name, const char *expect, int ret) { + _cleanup_free_ char *s = NULL; + + assert_se(slice_build_parent_slice(name, &s) == ret); + assert_se(streq_ptr(s, expect)); +} + +TEST(build_parent_slice) { + test_build_parent_slice_one(SPECIAL_ROOT_SLICE, NULL, 0); + test_build_parent_slice_one("foo.slice", SPECIAL_ROOT_SLICE, 1); + test_build_parent_slice_one("foo-bar.slice", "foo.slice", 1); + test_build_parent_slice_one("foo-bar-baz.slice", "foo-bar.slice", 1); + test_build_parent_slice_one("foo-bar--baz.slice", NULL, -EINVAL); + test_build_parent_slice_one("-foo-bar.slice", NULL, -EINVAL); + test_build_parent_slice_one("foo-bar-.slice", NULL, -EINVAL); + test_build_parent_slice_one("foo-bar.service", NULL, -EINVAL); + test_build_parent_slice_one(".slice", NULL, -EINVAL); + test_build_parent_slice_one("foo@bar.slice", NULL, -EINVAL); + test_build_parent_slice_one("foo-bar@baz.slice", NULL, -EINVAL); + test_build_parent_slice_one("foo-bar--@baz.slice", NULL, -EINVAL); + test_build_parent_slice_one("-foo-bar@bar.slice", NULL, -EINVAL); + test_build_parent_slice_one("foo-bar@-.slice", NULL, -EINVAL); + test_build_parent_slice_one("foo@bar.service", NULL, -EINVAL); + test_build_parent_slice_one("@.slice", NULL, -EINVAL); +} + +TEST(unit_name_to_instance) { + UnitNameFlags r; + char *instance; + + r = unit_name_to_instance("foo@bar.service", &instance); + assert_se(r == UNIT_NAME_INSTANCE); + assert_se(streq(instance, "bar")); + free(instance); + + r = unit_name_to_instance("foo@.service", &instance); + assert_se(r == UNIT_NAME_TEMPLATE); + assert_se(streq(instance, "")); + free(instance); + + r = unit_name_to_instance("fo0-stUff_b@b.service", &instance); + assert_se(r == UNIT_NAME_INSTANCE); + assert_se(streq(instance, "b")); + free(instance); + + r = unit_name_to_instance("foo.service", &instance); + assert_se(r == UNIT_NAME_PLAIN); + assert_se(!instance); + + r = unit_name_to_instance("fooj@unk", &instance); + assert_se(r < 0); + assert_se(!instance); + + r = unit_name_to_instance("foo@", &instance); + assert_se(r < 0); + assert_se(!instance); +} + +TEST(unit_name_escape) { + _cleanup_free_ char *r = NULL; + + r = unit_name_escape("ab+-c.a/bc@foo.service"); + assert_se(r); + assert_se(streq(r, "ab\\x2b\\x2dc.a-bc\\x40foo.service")); +} + +static void test_u_n_t_one(const char *name, const char *expected, int ret) { + _cleanup_free_ char *f = NULL; + + assert_se(unit_name_template(name, &f) == ret); + printf("got: %s, expected: %s\n", strna(f), strna(expected)); + assert_se(streq_ptr(f, expected)); +} + +TEST(unit_name_template) { + test_u_n_t_one("foo@bar.service", "foo@.service", 0); + test_u_n_t_one("foo.mount", NULL, -EINVAL); +} + +static void test_unit_name_path_unescape_one(const char *name, const char *path, int ret) { + _cleanup_free_ char *p = NULL; + + assert_se(unit_name_path_unescape(name, &p) == ret); + assert_se(streq_ptr(path, p)); +} + +TEST(unit_name_path_unescape) { + test_unit_name_path_unescape_one("foo", "/foo", 0); + test_unit_name_path_unescape_one("foo-bar", "/foo/bar", 0); + test_unit_name_path_unescape_one("foo-.bar", "/foo/.bar", 0); + test_unit_name_path_unescape_one("foo-bar-baz", "/foo/bar/baz", 0); + test_unit_name_path_unescape_one("-", "/", 0); + test_unit_name_path_unescape_one("--", NULL, -EINVAL); + test_unit_name_path_unescape_one("-foo-bar", NULL, -EINVAL); + test_unit_name_path_unescape_one("foo--bar", NULL, -EINVAL); + test_unit_name_path_unescape_one("foo-bar-", NULL, -EINVAL); + test_unit_name_path_unescape_one(".-bar", NULL, -EINVAL); + test_unit_name_path_unescape_one("foo-..", NULL, -EINVAL); + test_unit_name_path_unescape_one("", NULL, -EINVAL); +} + +static void test_unit_name_to_prefix_one(const char *input, int ret, const char *output) { + _cleanup_free_ char *k = NULL; + + assert_se(unit_name_to_prefix(input, &k) == ret); + assert_se(streq_ptr(k, output)); +} + +TEST(unit_name_to_prefix) { + test_unit_name_to_prefix_one("foobar.service", 0, "foobar"); + test_unit_name_to_prefix_one("", -EINVAL, NULL); + test_unit_name_to_prefix_one("foobar", -EINVAL, NULL); + test_unit_name_to_prefix_one(".service", -EINVAL, NULL); + test_unit_name_to_prefix_one("quux.quux", -EINVAL, NULL); + test_unit_name_to_prefix_one("quux.mount", 0, "quux"); + test_unit_name_to_prefix_one("quux-quux.mount", 0, "quux-quux"); + test_unit_name_to_prefix_one("quux@bar.mount", 0, "quux"); + test_unit_name_to_prefix_one("quux-@.mount", 0, "quux-"); + test_unit_name_to_prefix_one("@.mount", -EINVAL, NULL); +} + +static void test_unit_name_from_dbus_path_one(const char *input, int ret, const char *output) { + _cleanup_free_ char *k = NULL; + + assert_se(unit_name_from_dbus_path(input, &k) == ret); + assert_se(streq_ptr(k, output)); +} + +TEST(unit_name_from_dbus_path) { + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dbus_2esocket", 0, "dbus.socket"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/_2d_2emount", 0, "-.mount"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/_2d_2eslice", 0, "-.slice"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/accounts_2ddaemon_2eservice", 0, "accounts-daemon.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/auditd_2eservice", 0, "auditd.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/basic_2etarget", 0, "basic.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/bluetooth_2etarget", 0, "bluetooth.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/boot_2eautomount", 0, "boot.automount"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/boot_2emount", 0, "boot.mount"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/btrfs_2emount", 0, "btrfs.mount"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/cryptsetup_2dpre_2etarget", 0, "cryptsetup-pre.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/cryptsetup_2etarget", 0, "cryptsetup.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dbus_2eservice", 0, "dbus.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dbus_2esocket", 0, "dbus.socket"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dcdrom_2edevice", 0, "dev-cdrom.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2did_2data_5cx2dINTEL_5fSSDSA2M120G2GC_5fCVPO044405HH120QGN_2edevice", 0, "dev-disk-by\\x2did-ata\\x2dINTEL_SSDSA2M120G2GC_CVPO044405HH120QGN.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2did_2data_5cx2dINTEL_5fSSDSA2M120G2GC_5fCVPO044405HH120QGN_5cx2dpart1_2edevice", 0, "dev-disk-by\\x2did-ata\\x2dINTEL_SSDSA2M120G2GC_CVPO044405HH120QGN\\x2dpart1.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2did_2data_5cx2dINTEL_5fSSDSA2M160G2GC_5fCVPO951003RY160AGN_2edevice", 0, "dev-disk-by\\x2did-ata\\x2dINTEL_SSDSA2M160G2GC_CVPO951003RY160AGN.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2did_2data_5cx2dINTEL_5fSSDSA2M160G2GC_5fCVPO951003RY160AGN_5cx2dpart1_2edevice", 0, "dev-disk-by\\x2did-ata\\x2dINTEL_SSDSA2M160G2GC_CVPO951003RY160AGN\\x2dpart1.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2did_2data_5cx2dINTEL_5fSSDSA2M160G2GC_5fCVPO951003RY160AGN_5cx2dpart2_2edevice", 0, "dev-disk-by\\x2did-ata\\x2dINTEL_SSDSA2M160G2GC_CVPO951003RY160AGN\\x2dpart2.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2did_2data_5cx2dINTEL_5fSSDSA2M160G2GC_5fCVPO951003RY160AGN_5cx2dpart3_2edevice", 0, "dev-disk-by\\x2did-ata\\x2dINTEL_SSDSA2M160G2GC_CVPO951003RY160AGN\\x2dpart3.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2did_2data_5cx2dTSSTcorp_5fCDDVDW_5fTS_5cx2dL633C_5fR6176GLZB14646_2edevice", 0, "dev-disk-by\\x2did-ata\\x2dTSSTcorp_CDDVDW_TS\\x2dL633C_R6176GLZB14646.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2did_2dwwn_5cx2d0x50015179591245ae_2edevice", 0, "dev-disk-by\\x2did-wwn\\x2d0x50015179591245ae.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2did_2dwwn_5cx2d0x50015179591245ae_5cx2dpart1_2edevice", 0, "dev-disk-by\\x2did-wwn\\x2d0x50015179591245ae\\x2dpart1.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2did_2dwwn_5cx2d0x50015179591245ae_5cx2dpart2_2edevice", 0, "dev-disk-by\\x2did-wwn\\x2d0x50015179591245ae\\x2dpart2.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2did_2dwwn_5cx2d0x50015179591245ae_5cx2dpart3_2edevice", 0, "dev-disk-by\\x2did-wwn\\x2d0x50015179591245ae\\x2dpart3.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2did_2dwwn_5cx2d0x500151795946eab5_2edevice", 0, "dev-disk-by\\x2did-wwn\\x2d0x500151795946eab5.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2did_2dwwn_5cx2d0x500151795946eab5_5cx2dpart1_2edevice", 0, "dev-disk-by\\x2did-wwn\\x2d0x500151795946eab5\\x2dpart1.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2dlabel_2d_5cxe3_5cx82_5cxb7_5cxe3_5cx82_5cxb9_5cxe3_5cx83_5cx86_5cxe3_5cx83_5cxa0_5cxe3_5cx81_5cxa7_5cxe4_5cxba_5cx88_5cxe7_5cxb4_5cx84_5cxe6_5cxb8_5cx88_5cxe3_5cx81_5cxbf_2edevice", 0, "dev-disk-by\\x2dlabel-\\xe3\\x82\\xb7\\xe3\\x82\\xb9\\xe3\\x83\\x86\\xe3\\x83\\xa0\\xe3\\x81\\xa7\\xe4\\xba\\x88\\xe7\\xb4\\x84\\xe6\\xb8\\x88\\xe3\\x81\\xbf.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2dpartuuid_2d59834e50_5cx2d01_2edevice", 0, "dev-disk-by\\x2dpartuuid-59834e50\\x2d01.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2dpartuuid_2d63e2a7b3_5cx2d01_2edevice", 0, "dev-disk-by\\x2dpartuuid-63e2a7b3\\x2d01.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2dpartuuid_2d63e2a7b3_5cx2d02_2edevice", 0, "dev-disk-by\\x2dpartuuid-63e2a7b3\\x2d02.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2dpartuuid_2d63e2a7b3_5cx2d03_2edevice", 0, "dev-disk-by\\x2dpartuuid-63e2a7b3\\x2d03.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2dpath_2dpci_5cx2d0000_3a00_3a1f_2e2_5cx2data_5cx2d1_2edevice", 0, "dev-disk-by\\x2dpath-pci\\x2d0000:00:1f.2\\x2data\\x2d1.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2dpath_2dpci_5cx2d0000_3a00_3a1f_2e2_5cx2data_5cx2d1_5cx2dpart1_2edevice", 0, "dev-disk-by\\x2dpath-pci\\x2d0000:00:1f.2\\x2data\\x2d1\\x2dpart1.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2dpath_2dpci_5cx2d0000_3a00_3a1f_2e2_5cx2data_5cx2d1_5cx2dpart2_2edevice", 0, "dev-disk-by\\x2dpath-pci\\x2d0000:00:1f.2\\x2data\\x2d1\\x2dpart2.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2dpath_2dpci_5cx2d0000_3a00_3a1f_2e2_5cx2data_5cx2d1_5cx2dpart3_2edevice", 0, "dev-disk-by\\x2dpath-pci\\x2d0000:00:1f.2\\x2data\\x2d1\\x2dpart3.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2dpath_2dpci_5cx2d0000_3a00_3a1f_2e2_5cx2data_5cx2d2_2edevice", 0, "dev-disk-by\\x2dpath-pci\\x2d0000:00:1f.2\\x2data\\x2d2.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2dpath_2dpci_5cx2d0000_3a00_3a1f_2e2_5cx2data_5cx2d6_2edevice", 0, "dev-disk-by\\x2dpath-pci\\x2d0000:00:1f.2\\x2data\\x2d6.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2dpath_2dpci_5cx2d0000_3a00_3a1f_2e2_5cx2data_5cx2d6_5cx2dpart1_2edevice", 0, "dev-disk-by\\x2dpath-pci\\x2d0000:00:1f.2\\x2data\\x2d6\\x2dpart1.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2duuid_2d1A34E3F034E3CD37_2edevice", 0, "dev-disk-by\\x2duuid-1A34E3F034E3CD37.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2duuid_2dB670EBFE70EBC2EB_2edevice", 0, "dev-disk-by\\x2duuid-B670EBFE70EBC2EB.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2duuid_2dFCD4F509D4F4C6C4_2edevice", 0, "dev-disk-by\\x2duuid-FCD4F509D4F4C6C4.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2duuid_2db49ead57_5cx2d907c_5cx2d446c_5cx2db405_5cx2d5ca6cd865f5e_2edevice", 0, "dev-disk-by\\x2duuid-b49ead57\\x2d907c\\x2d446c\\x2db405\\x2d5ca6cd865f5e.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dhugepages_2emount", 0, "dev-hugepages.mount"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dmqueue_2emount", 0, "dev-mqueue.mount"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2drfkill_2edevice", 0, "dev-rfkill.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dsda1_2edevice", 0, "dev-sda1.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dsda2_2edevice", 0, "dev-sda2.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dsda3_2edevice", 0, "dev-sda3.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dsda_2edevice", 0, "dev-sda.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dsdb1_2edevice", 0, "dev-sdb1.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dsdb_2edevice", 0, "dev-sdb.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dsr0_2edevice", 0, "dev-sr0.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS0_2edevice", 0, "dev-ttyS0.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS10_2edevice", 0, "dev-ttyS10.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS11_2edevice", 0, "dev-ttyS11.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS12_2edevice", 0, "dev-ttyS12.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS13_2edevice", 0, "dev-ttyS13.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS14_2edevice", 0, "dev-ttyS14.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS15_2edevice", 0, "dev-ttyS15.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS16_2edevice", 0, "dev-ttyS16.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS17_2edevice", 0, "dev-ttyS17.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS18_2edevice", 0, "dev-ttyS18.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS19_2edevice", 0, "dev-ttyS19.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS1_2edevice", 0, "dev-ttyS1.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS20_2edevice", 0, "dev-ttyS20.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS21_2edevice", 0, "dev-ttyS21.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS22_2edevice", 0, "dev-ttyS22.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS23_2edevice", 0, "dev-ttyS23.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS24_2edevice", 0, "dev-ttyS24.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS25_2edevice", 0, "dev-ttyS25.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS26_2edevice", 0, "dev-ttyS26.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS27_2edevice", 0, "dev-ttyS27.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS28_2edevice", 0, "dev-ttyS28.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS29_2edevice", 0, "dev-ttyS29.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS2_2edevice", 0, "dev-ttyS2.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS30_2edevice", 0, "dev-ttyS30.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS31_2edevice", 0, "dev-ttyS31.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS3_2edevice", 0, "dev-ttyS3.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS4_2edevice", 0, "dev-ttyS4.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS5_2edevice", 0, "dev-ttyS5.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS6_2edevice", 0, "dev-ttyS6.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS7_2edevice", 0, "dev-ttyS7.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS8_2edevice", 0, "dev-ttyS8.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dev_2dttyS9_2edevice", 0, "dev-ttyS9.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dracut_2dcmdline_2eservice", 0, "dracut-cmdline.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dracut_2dinitqueue_2eservice", 0, "dracut-initqueue.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dracut_2dmount_2eservice", 0, "dracut-mount.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dracut_2dpre_2dmount_2eservice", 0, "dracut-pre-mount.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dracut_2dpre_2dpivot_2eservice", 0, "dracut-pre-pivot.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dracut_2dpre_2dtrigger_2eservice", 0, "dracut-pre-trigger.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dracut_2dpre_2dudev_2eservice", 0, "dracut-pre-udev.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/dracut_2dshutdown_2eservice", 0, "dracut-shutdown.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/ebtables_2eservice", 0, "ebtables.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/emergency_2eservice", 0, "emergency.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/emergency_2etarget", 0, "emergency.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/fedora_2dimport_2dstate_2eservice", 0, "fedora-import-state.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/fedora_2dreadonly_2eservice", 0, "fedora-readonly.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/firewalld_2eservice", 0, "firewalld.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/getty_2dpre_2etarget", 0, "getty-pre.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/getty_2etarget", 0, "getty.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/getty_40tty1_2eservice", 0, "getty@tty1.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/graphical_2etarget", 0, "graphical.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/home_2emount", 0, "home.mount"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/init_2escope", 0, "init.scope"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/initrd_2dcleanup_2eservice", 0, "initrd-cleanup.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/initrd_2dfs_2etarget", 0, "initrd-fs.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/initrd_2dparse_2detc_2eservice", 0, "initrd-parse-etc.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/initrd_2droot_2ddevice_2etarget", 0, "initrd-root-device.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/initrd_2droot_2dfs_2etarget", 0, "initrd-root-fs.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/initrd_2dswitch_2droot_2eservice", 0, "initrd-switch-root.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/initrd_2dswitch_2droot_2etarget", 0, "initrd-switch-root.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/initrd_2dudevadm_2dcleanup_2ddb_2eservice", 0, "initrd-udevadm-cleanup-db.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/initrd_2etarget", 0, "initrd.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/ip6tables_2eservice", 0, "ip6tables.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/ipset_2eservice", 0, "ipset.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/iptables_2eservice", 0, "iptables.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/irqbalance_2eservice", 0, "irqbalance.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/kmod_2dstatic_2dnodes_2eservice", 0, "kmod-static-nodes.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/ldconfig_2eservice", 0, "ldconfig.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/lightdm_2eservice", 0, "lightdm.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/livesys_2dlate_2eservice", 0, "livesys-late.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/lm_5fsensors_2eservice", 0, "lm_sensors.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/local_2dfs_2dpre_2etarget", 0, "local-fs-pre.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/local_2dfs_2etarget", 0, "local-fs.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/machines_2etarget", 0, "machines.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/mcelog_2eservice", 0, "mcelog.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/multi_2duser_2etarget", 0, "multi-user.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/network_2dpre_2etarget", 0, "network-pre.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/network_2etarget", 0, "network.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/nss_2dlookup_2etarget", 0, "nss-lookup.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/nss_2duser_2dlookup_2etarget", 0, "nss-user-lookup.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/paths_2etarget", 0, "paths.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/plymouth_2dquit_2dwait_2eservice", 0, "plymouth-quit-wait.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/plymouth_2dquit_2eservice", 0, "plymouth-quit.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/plymouth_2dstart_2eservice", 0, "plymouth-start.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/polkit_2eservice", 0, "polkit.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/proc_2dsys_2dfs_2dbinfmt_5fmisc_2eautomount", 0, "proc-sys-fs-binfmt_misc.automount"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/proc_2dsys_2dfs_2dbinfmt_5fmisc_2emount", 0, "proc-sys-fs-binfmt_misc.mount"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/rc_2dlocal_2eservice", 0, "rc-local.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/remote_2dcryptsetup_2etarget", 0, "remote-cryptsetup.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/remote_2dfs_2dpre_2etarget", 0, "remote-fs-pre.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/remote_2dfs_2etarget", 0, "remote-fs.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/rescue_2eservice", 0, "rescue.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/rescue_2etarget", 0, "rescue.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/run_2duser_2d1000_2emount", 0, "run-user-1000.mount"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/session_2d2_2escope", 0, "session-2.scope"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/shutdown_2etarget", 0, "shutdown.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/slices_2etarget", 0, "slices.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/smartd_2eservice", 0, "smartd.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sockets_2etarget", 0, "sockets.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sound_2etarget", 0, "sound.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sshd_2dkeygen_2etarget", 0, "sshd-keygen.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sshd_2dkeygen_40ecdsa_2eservice", 0, "sshd-keygen@ecdsa.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sshd_2dkeygen_40ed25519_2eservice", 0, "sshd-keygen@ed25519.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sshd_2dkeygen_40rsa_2eservice", 0, "sshd-keygen@rsa.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sshd_2eservice", 0, "sshd.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/swap_2etarget", 0, "swap.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dpci0000_3a00_2d0000_3a00_3a02_2e0_2dbacklight_2dacpi_5fvideo0_2edevice", 0, "sys-devices-pci0000:00-0000:00:02.0-backlight-acpi_video0.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dpci0000_3a00_2d0000_3a00_3a02_2e0_2ddrm_2dcard0_2dcard0_5cx2dLVDS_5cx2d1_2dintel_5fbacklight_2edevice", 0, "sys-devices-pci0000:00-0000:00:02.0-drm-card0-card0\\x2dLVDS\\x2d1-intel_backlight.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dpci0000_3a00_2d0000_3a00_3a1a_2e0_2dusb1_2d1_5cx2d1_2d1_5cx2d1_2e6_2d1_5cx2d1_2e6_3a1_2e0_2dbluetooth_2dhci0_2edevice", 0, "sys-devices-pci0000:00-0000:00:1a.0-usb1-1\\x2d1-1\\x2d1.6-1\\x2d1.6:1.0-bluetooth-hci0.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dpci0000_3a00_2d0000_3a00_3a1b_2e0_2dsound_2dcard0_2edevice", 0, "sys-devices-pci0000:00-0000:00:1b.0-sound-card0.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dpci0000_3a00_2d0000_3a00_3a1c_2e0_2d0000_3a02_3a00_2e0_2dnet_2dwlp2s0_2edevice", 0, "sys-devices-pci0000:00-0000:00:1c.0-0000:02:00.0-net-wlp2s0.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dpci0000_3a00_2d0000_3a00_3a1c_2e2_2d0000_3a04_3a00_2e0_2dnet_2denp4s0_2edevice", 0, "sys-devices-pci0000:00-0000:00:1c.2-0000:04:00.0-net-enp4s0.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dpci0000_3a00_2d0000_3a00_3a1f_2e2_2data1_2dhost0_2dtarget0_3a0_3a0_2d0_3a0_3a0_3a0_2dblock_2dsda_2dsda1_2edevice", 0, "sys-devices-pci0000:00-0000:00:1f.2-ata1-host0-target0:0:0-0:0:0:0-block-sda-sda1.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dpci0000_3a00_2d0000_3a00_3a1f_2e2_2data1_2dhost0_2dtarget0_3a0_3a0_2d0_3a0_3a0_3a0_2dblock_2dsda_2dsda2_2edevice", 0, "sys-devices-pci0000:00-0000:00:1f.2-ata1-host0-target0:0:0-0:0:0:0-block-sda-sda2.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dpci0000_3a00_2d0000_3a00_3a1f_2e2_2data1_2dhost0_2dtarget0_3a0_3a0_2d0_3a0_3a0_3a0_2dblock_2dsda_2dsda3_2edevice", 0, "sys-devices-pci0000:00-0000:00:1f.2-ata1-host0-target0:0:0-0:0:0:0-block-sda-sda3.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dpci0000_3a00_2d0000_3a00_3a1f_2e2_2data1_2dhost0_2dtarget0_3a0_3a0_2d0_3a0_3a0_3a0_2dblock_2dsda_2edevice", 0, "sys-devices-pci0000:00-0000:00:1f.2-ata1-host0-target0:0:0-0:0:0:0-block-sda.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dpci0000_3a00_2d0000_3a00_3a1f_2e2_2data2_2dhost1_2dtarget1_3a0_3a0_2d1_3a0_3a0_3a0_2dblock_2dsr0_2edevice", 0, "sys-devices-pci0000:00-0000:00:1f.2-ata2-host1-target1:0:0-1:0:0:0-block-sr0.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dpci0000_3a00_2d0000_3a00_3a1f_2e2_2data6_2dhost5_2dtarget5_3a0_3a0_2d5_3a0_3a0_3a0_2dblock_2dsdb_2dsdb1_2edevice", 0, "sys-devices-pci0000:00-0000:00:1f.2-ata6-host5-target5:0:0-5:0:0:0-block-sdb-sdb1.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dpci0000_3a00_2d0000_3a00_3a1f_2e2_2data6_2dhost5_2dtarget5_3a0_3a0_2d5_3a0_3a0_3a0_2dblock_2dsdb_2edevice", 0, "sys-devices-pci0000:00-0000:00:1f.2-ata6-host5-target5:0:0-5:0:0:0-block-sdb.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS0_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS0.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS10_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS10.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS11_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS11.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS12_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS12.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS13_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS13.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS14_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS14.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS15_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS15.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS16_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS16.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS17_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS17.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS18_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS18.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS19_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS19.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS1_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS1.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS20_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS20.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS21_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS21.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS22_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS22.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS23_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS23.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS24_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS24.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS25_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS25.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS26_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS26.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS27_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS27.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS28_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS28.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS29_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS29.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS2_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS2.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS30_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS30.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS31_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS31.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS3_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS3.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS4_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS4.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS5_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS5.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS6_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS6.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS7_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS7.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS8_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS8.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dplatform_2dserial8250_2dtty_2dttyS9_2edevice", 0, "sys-devices-platform-serial8250-tty-ttyS9.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2ddevices_2dvirtual_2dmisc_2drfkill_2edevice", 0, "sys-devices-virtual-misc-rfkill.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2dfs_2dfuse_2dconnections_2emount", 0, "sys-fs-fuse-connections.mount"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2dkernel_2dconfig_2emount", 0, "sys-kernel-config.mount"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2dkernel_2ddebug_2emount", 0, "sys-kernel-debug.mount"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2dmodule_2dconfigfs_2edevice", 0, "sys-module-configfs.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2dsubsystem_2dbluetooth_2ddevices_2dhci0_2edevice", 0, "sys-subsystem-bluetooth-devices-hci0.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2dsubsystem_2dnet_2ddevices_2denp4s0_2edevice", 0, "sys-subsystem-net-devices-enp4s0.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sys_2dsubsystem_2dnet_2ddevices_2dwlp2s0_2edevice", 0, "sys-subsystem-net-devices-wlp2s0.device"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sysinit_2etarget", 0, "sysinit.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/syslog_2eservice", 0, "syslog.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/syslog_2esocket", 0, "syslog.socket"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/syslog_2etarget", 0, "syslog.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/sysroot_2emount", 0, "sysroot.mount"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/system_2dgetty_2eslice", 0, "system-getty.slice"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/system_2dsshd_5cx2dkeygen_2eslice", 0, "system-sshd\\x2dkeygen.slice"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/system_2dsystemd_5cx2dbacklight_2eslice", 0, "system-systemd\\x2dbacklight.slice"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/system_2dsystemd_5cx2dcoredump_2eslice", 0, "system-systemd\\x2dcoredump.slice"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/system_2duser_5cx2druntime_5cx2ddir_2eslice", 0, "system-user\\x2druntime\\x2ddir.slice"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/system_2eslice", 0, "system.slice"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dask_2dpassword_2dconsole_2epath", 0, "systemd-ask-password-console.path"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dask_2dpassword_2dconsole_2eservice", 0, "systemd-ask-password-console.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dask_2dpassword_2dwall_2epath", 0, "systemd-ask-password-wall.path"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dask_2dpassword_2dwall_2eservice", 0, "systemd-ask-password-wall.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dbacklight_40backlight_3aacpi_5fvideo0_2eservice", 0, "systemd-backlight@backlight:acpi_video0.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dbacklight_40backlight_3aintel_5fbacklight_2eservice", 0, "systemd-backlight@backlight:intel_backlight.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dbinfmt_2eservice", 0, "systemd-binfmt.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dcoredump_2esocket", 0, "systemd-coredump.socket"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dcoredump_400_2eservice", 0, "systemd-coredump@0.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dfirstboot_2eservice", 0, "systemd-firstboot.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dfsck_2droot_2eservice", 0, SPECIAL_FSCK_ROOT_SERVICE); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dhwdb_2dupdate_2eservice", 0, "systemd-hwdb-update.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dinitctl_2eservice", 0, "systemd-initctl.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dinitctl_2esocket", 0, "systemd-initctl.socket"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2djournal_2dcatalog_2dupdate_2eservice", 0, "systemd-journal-catalog-update.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2djournal_2dflush_2eservice", 0, "systemd-journal-flush.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2djournald_2daudit_2esocket", 0, "systemd-journald-audit.socket"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2djournald_2ddev_2dlog_2esocket", 0, "systemd-journald-dev-log.socket"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2djournald_2eservice", 0, "systemd-journald.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2djournald_2esocket", 0, "systemd-journald.socket"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dlogind_2eservice", 0, "systemd-logind.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dmachine_2did_2dcommit_2eservice", 0, "systemd-machine-id-commit.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dmodules_2dload_2eservice", 0, "systemd-modules-load.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dnetworkd_2eservice", 0, "systemd-networkd.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dnetworkd_2esocket", 0, "systemd-networkd.socket"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2drandom_2dseed_2eservice", 0, "systemd-random-seed.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dremount_2dfs_2eservice", 0, "systemd-remount-fs.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dresolved_2eservice", 0, "systemd-resolved.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2drfkill_2eservice", 0, "systemd-rfkill.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2drfkill_2esocket", 0, "systemd-rfkill.socket"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dsysctl_2eservice", 0, "systemd-sysctl.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dsysusers_2eservice", 0, "systemd-sysusers.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dtimesyncd_2eservice", 0, "systemd-timesyncd.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dtmpfiles_2dclean_2eservice", 0, "systemd-tmpfiles-clean.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dtmpfiles_2dclean_2etimer", 0, "systemd-tmpfiles-clean.timer"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dtmpfiles_2dsetup_2ddev_2eservice", 0, "systemd-tmpfiles-setup-dev.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dtmpfiles_2dsetup_2eservice", 0, "systemd-tmpfiles-setup.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dudev_2dtrigger_2eservice", 0, "systemd-udev-trigger.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dudevd_2dcontrol_2esocket", 0, "systemd-udevd-control.socket"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dudevd_2dkernel_2esocket", 0, "systemd-udevd-kernel.socket"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dudevd_2eservice", 0, "systemd-udevd.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dupdate_2ddone_2eservice", 0, "systemd-update-done.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dupdate_2dutmp_2drunlevel_2eservice", 0, "systemd-update-utmp-runlevel.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dupdate_2dutmp_2eservice", 0, "systemd-update-utmp.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2duser_2dsessions_2eservice", 0, "systemd-user-sessions.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/systemd_2dvconsole_2dsetup_2eservice", 0, "systemd-vconsole-setup.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/time_2dsync_2etarget", 0, "time-sync.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/timers_2etarget", 0, "timers.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/tmp_2emount", 0, "tmp.mount"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/umount_2etarget", 0, "umount.target"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/unbound_2danchor_2eservice", 0, "unbound-anchor.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/unbound_2danchor_2etimer", 0, "unbound-anchor.timer"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/upower_2eservice", 0, "upower.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/user_2d1000_2eslice", 0, "user-1000.slice"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/user_2druntime_2ddir_401000_2eservice", 0, "user-runtime-dir@1000.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/user_2eslice", 0, "user.slice"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/user_401000_2eservice", 0, "user@1000.service"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/usr_2dlocal_2dtexlive_2emount", 0, "usr-local-texlive.mount"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/var_2dlib_2dmachines_2emount", 0, "var-lib-machines.mount"); + test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/wpa_5fsupplicant_2eservice", 0, "wpa_supplicant.service"); +} + +TEST(unit_name_prefix_equal) { + assert_se(unit_name_prefix_equal("a.service", "a.service")); + assert_se(unit_name_prefix_equal("a.service", "a.mount")); + assert_se(unit_name_prefix_equal("a@b.service", "a.service")); + assert_se(unit_name_prefix_equal("a@b.service", "a@c.service")); + + assert_se(!unit_name_prefix_equal("a.service", "b.service")); + assert_se(!unit_name_prefix_equal("a.service", "b.mount")); + assert_se(!unit_name_prefix_equal("a@a.service", "b.service")); + assert_se(!unit_name_prefix_equal("a@a.service", "b@a.service")); + assert_se(!unit_name_prefix_equal("a", "b")); + assert_se(!unit_name_prefix_equal("a", "a")); +} + +static int intro(void) { + if (enter_cgroup_subroot(NULL) == -ENOMEDIUM) + return log_tests_skipped("cgroupfs not available"); + + assert_se(runtime_dir = setup_fake_runtime_dir()); + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/test/test-unit-serialize.c b/src/test/test-unit-serialize.c new file mode 100644 index 0000000..7a1e8a0 --- /dev/null +++ b/src/test/test-unit-serialize.c @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "rm-rf.h" +#include "service.h" +#include "tests.h" + +static char *runtime_dir = NULL; + +STATIC_DESTRUCTOR_REGISTER(runtime_dir, rm_rf_physical_and_freep); + +#define EXEC_START_ABSOLUTE \ + "ExecStart 0 /bin/sh \"sh\" \"-e\" \"-x\" \"-c\" \"systemctl --state=failed --no-legend --no-pager >/failed ; systemctl daemon-reload ; echo OK >/testok\"" +#define EXEC_START_RELATIVE \ + "ExecStart 0 sh \"sh\" \"-e\" \"-x\" \"-c\" \"systemctl --state=failed --no-legend --no-pager >/failed ; systemctl daemon-reload ; echo OK >/testok\"" + +static void test_deserialize_exec_command_one(Manager *m, const char *key, const char *line, int expected) { + _cleanup_(unit_freep) Unit *u = NULL; + int r; + + assert_se(unit_new_for_name(m, sizeof(Service), "test.service", &u) >= 0); + + r = service_deserialize_exec_command(u, key, line); + log_debug("[%s] → %d (expected: %d)", line, r, expected); + assert_se(r == expected); + + /* Note that the command doesn't match any command in the empty list of commands in 's', so it is + * always rejected with "Current command vanished from the unit file", and we don't leak anything. */ +} + +TEST(deserialize_exec_command) { + _cleanup_(manager_freep) Manager *m = NULL; + int r; + + r = manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_MINIMAL, &m); + if (manager_errno_skip_test(r)) { + log_notice_errno(r, "Skipping test: manager_new: %m"); + return; + } + + assert_se(r >= 0); + + test_deserialize_exec_command_one(m, "main-command", EXEC_START_ABSOLUTE, 0); + test_deserialize_exec_command_one(m, "main-command", EXEC_START_RELATIVE, 0); + test_deserialize_exec_command_one(m, "control-command", EXEC_START_ABSOLUTE, 0); + test_deserialize_exec_command_one(m, "control-command", EXEC_START_RELATIVE, 0); + + test_deserialize_exec_command_one(m, "control-command", "ExecStart 0 /bin/sh \"sh\"", 0); + test_deserialize_exec_command_one(m, "control-command", "ExecStart 0 /no/command ", -EINVAL); + test_deserialize_exec_command_one(m, "control-command", "ExecStart 0 /bad/quote \"", -EINVAL); + test_deserialize_exec_command_one(m, "control-command", "ExecStart s /bad/id x y z", -EINVAL); + test_deserialize_exec_command_one(m, "control-command", "ExecStart 11", -EINVAL); + test_deserialize_exec_command_one(m, "control-command", "ExecWhat 11 /a/b c d e", -EINVAL); +} + +static int intro(void) { + if (enter_cgroup_subroot(NULL) == -ENOMEDIUM) + return log_tests_skipped("cgroupfs not available"); + + assert_se(runtime_dir = setup_fake_runtime_dir()); + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/src/test/test-user-util.c b/src/test/test-user-util.c new file mode 100644 index 0000000..db76cde --- /dev/null +++ b/src/test/test-user-util.c @@ -0,0 +1,484 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "format-util.h" +#include "libcrypt-util.h" +#include "log.h" +#include "macro.h" +#include "memory-util.h" +#include "path-util.h" +#include "string-util.h" +#include "tests.h" +#include "user-util.h" + +static void test_uid_to_name_one(uid_t uid, const char *name) { + _cleanup_free_ char *t = NULL; + + log_info("/* %s("UID_FMT", \"%s\") */", __func__, uid, name); + + assert_se(t = uid_to_name(uid)); + if (!synthesize_nobody() && streq(name, NOBODY_USER_NAME)) { + log_info("(skipping detailed tests because nobody is not synthesized)"); + return; + } + assert_se(streq_ptr(t, name)); +} + +TEST(uid_to_name) { + test_uid_to_name_one(0, "root"); + test_uid_to_name_one(UID_NOBODY, NOBODY_USER_NAME); + test_uid_to_name_one(0xFFFF, "65535"); + test_uid_to_name_one(0xFFFFFFFF, "4294967295"); +} + +static void test_gid_to_name_one(gid_t gid, const char *name) { + _cleanup_free_ char *t = NULL; + + log_info("/* %s("GID_FMT", \"%s\") */", __func__, gid, name); + + assert_se(t = gid_to_name(gid)); + if (!synthesize_nobody() && streq(name, NOBODY_GROUP_NAME)) { + log_info("(skipping detailed tests because nobody is not synthesized)"); + return; + } + assert_se(streq_ptr(t, name)); +} + +TEST(gid_to_name) { + test_gid_to_name_one(0, "root"); + test_gid_to_name_one(GID_NOBODY, NOBODY_GROUP_NAME); + test_gid_to_name_one(0xFFFF, "65535"); + test_gid_to_name_one(0xFFFFFFFF, "4294967295"); +} + +TEST(parse_uid) { + int r; + uid_t uid; + + r = parse_uid("0", &uid); + assert_se(r == 0); + assert_se(uid == 0); + + r = parse_uid("1", &uid); + assert_se(r == 0); + assert_se(uid == 1); + + r = parse_uid("01", &uid); + assert_se(r == -EINVAL); + assert_se(uid == 1); + + r = parse_uid("001", &uid); + assert_se(r == -EINVAL); + assert_se(uid == 1); + + r = parse_uid("100", &uid); + assert_se(r == 0); + assert_se(uid == 100); + + r = parse_uid("65535", &uid); + assert_se(r == -ENXIO); + assert_se(uid == 100); + + r = parse_uid("0x1234", &uid); + assert_se(r == -EINVAL); + assert_se(uid == 100); + + r = parse_uid("0o1234", &uid); + assert_se(r == -EINVAL); + assert_se(uid == 100); + + r = parse_uid("0b1234", &uid); + assert_se(r == -EINVAL); + assert_se(uid == 100); + + r = parse_uid("+1234", &uid); + assert_se(r == -EINVAL); + assert_se(uid == 100); + + r = parse_uid("-1234", &uid); + assert_se(r == -EINVAL); + assert_se(uid == 100); + + r = parse_uid(" 1234", &uid); + assert_se(r == -EINVAL); + assert_se(uid == 100); + + r = parse_uid("01234", &uid); + assert_se(r == -EINVAL); + assert_se(uid == 100); + + r = parse_uid("001234", &uid); + assert_se(r == -EINVAL); + assert_se(uid == 100); + + r = parse_uid("0001234", &uid); + assert_se(r == -EINVAL); + assert_se(uid == 100); + + r = parse_uid("-0", &uid); + assert_se(r == -EINVAL); + assert_se(uid == 100); + + r = parse_uid("+0", &uid); + assert_se(r == -EINVAL); + assert_se(uid == 100); + + r = parse_uid("00", &uid); + assert_se(r == -EINVAL); + assert_se(uid == 100); + + r = parse_uid("000", &uid); + assert_se(r == -EINVAL); + assert_se(uid == 100); + + r = parse_uid("asdsdas", &uid); + assert_se(r == -EINVAL); + assert_se(uid == 100); +} + +TEST(uid_ptr) { + assert_se(UID_TO_PTR(0) != NULL); + assert_se(UID_TO_PTR(1000) != NULL); + + assert_se(PTR_TO_UID(UID_TO_PTR(0)) == 0); + assert_se(PTR_TO_UID(UID_TO_PTR(1000)) == 1000); +} + +TEST(valid_user_group_name_relaxed) { + assert_se(!valid_user_group_name(NULL, VALID_USER_RELAX)); + assert_se(!valid_user_group_name("", VALID_USER_RELAX)); + assert_se(!valid_user_group_name("1", VALID_USER_RELAX)); + assert_se(!valid_user_group_name("65535", VALID_USER_RELAX)); + assert_se(!valid_user_group_name("-1", VALID_USER_RELAX)); + assert_se(!valid_user_group_name("foo\nbar", VALID_USER_RELAX)); + assert_se(!valid_user_group_name("0123456789012345678901234567890123456789", VALID_USER_RELAX)); + assert_se(!valid_user_group_name("aaa:bbb", VALID_USER_RELAX|VALID_USER_ALLOW_NUMERIC)); + assert_se(!valid_user_group_name(".aaa:bbb", VALID_USER_RELAX|VALID_USER_ALLOW_NUMERIC)); + assert_se(!valid_user_group_name(".", VALID_USER_RELAX)); + assert_se(!valid_user_group_name("..", VALID_USER_RELAX)); + + assert_se(valid_user_group_name("root", VALID_USER_RELAX)); + assert_se(valid_user_group_name("lennart", VALID_USER_RELAX)); + assert_se(valid_user_group_name("LENNART", VALID_USER_RELAX)); + assert_se(valid_user_group_name("_kkk", VALID_USER_RELAX)); + assert_se(valid_user_group_name("kkk-", VALID_USER_RELAX)); + assert_se(valid_user_group_name("kk-k", VALID_USER_RELAX)); + assert_se(valid_user_group_name("eff.eff", VALID_USER_RELAX)); + assert_se(valid_user_group_name("eff.", VALID_USER_RELAX)); + assert_se(valid_user_group_name("-kkk", VALID_USER_RELAX)); + assert_se(valid_user_group_name("rööt", VALID_USER_RELAX)); + assert_se(valid_user_group_name(".eff", VALID_USER_RELAX)); + assert_se(valid_user_group_name(".1", VALID_USER_RELAX)); + assert_se(valid_user_group_name(".65535", VALID_USER_RELAX)); + assert_se(valid_user_group_name(".-1", VALID_USER_RELAX)); + assert_se(valid_user_group_name(".-kkk", VALID_USER_RELAX)); + assert_se(valid_user_group_name(".rööt", VALID_USER_RELAX)); + assert_se(valid_user_group_name("...", VALID_USER_RELAX)); + + assert_se(valid_user_group_name("some5", VALID_USER_RELAX)); + assert_se(valid_user_group_name("5some", VALID_USER_RELAX)); + assert_se(valid_user_group_name("INNER5NUMBER", VALID_USER_RELAX)); + + assert_se(valid_user_group_name("piff.paff@ad.domain.example", VALID_USER_RELAX)); + assert_se(valid_user_group_name("Dāvis", VALID_USER_RELAX)); +} + +TEST(valid_user_group_name) { + assert_se(!valid_user_group_name(NULL, 0)); + assert_se(!valid_user_group_name("", 0)); + assert_se(!valid_user_group_name("1", 0)); + assert_se(!valid_user_group_name("65535", 0)); + assert_se(!valid_user_group_name("-1", 0)); + assert_se(!valid_user_group_name("-kkk", 0)); + assert_se(!valid_user_group_name("rööt", 0)); + assert_se(!valid_user_group_name(".", 0)); + assert_se(!valid_user_group_name(".eff", 0)); + assert_se(!valid_user_group_name("foo\nbar", 0)); + assert_se(!valid_user_group_name("0123456789012345678901234567890123456789", 0)); + assert_se(!valid_user_group_name("aaa:bbb", VALID_USER_ALLOW_NUMERIC)); + assert_se(!valid_user_group_name(".", 0)); + assert_se(!valid_user_group_name("..", 0)); + assert_se(!valid_user_group_name("...", 0)); + assert_se(!valid_user_group_name(".1", 0)); + assert_se(!valid_user_group_name(".65535", 0)); + assert_se(!valid_user_group_name(".-1", 0)); + assert_se(!valid_user_group_name(".-kkk", 0)); + assert_se(!valid_user_group_name(".rööt", 0)); + assert_se(!valid_user_group_name(".aaa:bbb", VALID_USER_ALLOW_NUMERIC)); + + assert_se(valid_user_group_name("root", 0)); + assert_se(valid_user_group_name("lennart", 0)); + assert_se(valid_user_group_name("LENNART", 0)); + assert_se(valid_user_group_name("_kkk", 0)); + assert_se(valid_user_group_name("kkk-", 0)); + assert_se(valid_user_group_name("kk-k", 0)); + assert_se(!valid_user_group_name("eff.eff", 0)); + assert_se(!valid_user_group_name("eff.", 0)); + + assert_se(valid_user_group_name("some5", 0)); + assert_se(!valid_user_group_name("5some", 0)); + assert_se(valid_user_group_name("INNER5NUMBER", 0)); + + assert_se(!valid_user_group_name("piff.paff@ad.domain.example", 0)); + assert_se(!valid_user_group_name("Dāvis", 0)); +} + +TEST(valid_user_group_name_or_numeric_relaxed) { + assert_se(!valid_user_group_name(NULL, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(!valid_user_group_name("", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(valid_user_group_name("0", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(valid_user_group_name("1", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(valid_user_group_name("65534", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(!valid_user_group_name("65535", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(valid_user_group_name("65536", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(!valid_user_group_name("-1", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(!valid_user_group_name("foo\nbar", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(!valid_user_group_name("0123456789012345678901234567890123456789", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(!valid_user_group_name("aaa:bbb", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(!valid_user_group_name(".", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(!valid_user_group_name("..", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + + assert_se(valid_user_group_name("root", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(valid_user_group_name("lennart", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(valid_user_group_name("LENNART", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(valid_user_group_name("_kkk", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(valid_user_group_name("kkk-", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(valid_user_group_name("kk-k", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(valid_user_group_name("-kkk", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(valid_user_group_name("rööt", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(valid_user_group_name(".eff", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(valid_user_group_name("eff.eff", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(valid_user_group_name("eff.", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(valid_user_group_name("...", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + + assert_se(valid_user_group_name("some5", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(valid_user_group_name("5some", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(valid_user_group_name("INNER5NUMBER", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + + assert_se(valid_user_group_name("piff.paff@ad.domain.example", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); + assert_se(valid_user_group_name("Dāvis", VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)); +} + +TEST(valid_user_group_name_or_numeric) { + assert_se(!valid_user_group_name(NULL, VALID_USER_ALLOW_NUMERIC)); + assert_se(!valid_user_group_name("", VALID_USER_ALLOW_NUMERIC)); + assert_se(valid_user_group_name("0", VALID_USER_ALLOW_NUMERIC)); + assert_se(valid_user_group_name("1", VALID_USER_ALLOW_NUMERIC)); + assert_se(valid_user_group_name("65534", VALID_USER_ALLOW_NUMERIC)); + assert_se(!valid_user_group_name("65535", VALID_USER_ALLOW_NUMERIC)); + assert_se(valid_user_group_name("65536", VALID_USER_ALLOW_NUMERIC)); + assert_se(!valid_user_group_name("-1", VALID_USER_ALLOW_NUMERIC)); + assert_se(!valid_user_group_name("-kkk", VALID_USER_ALLOW_NUMERIC)); + assert_se(!valid_user_group_name("rööt", VALID_USER_ALLOW_NUMERIC)); + assert_se(!valid_user_group_name(".", VALID_USER_ALLOW_NUMERIC)); + assert_se(!valid_user_group_name("..", VALID_USER_ALLOW_NUMERIC)); + assert_se(!valid_user_group_name("...", VALID_USER_ALLOW_NUMERIC)); + assert_se(!valid_user_group_name(".eff", VALID_USER_ALLOW_NUMERIC)); + assert_se(!valid_user_group_name("eff.eff", VALID_USER_ALLOW_NUMERIC)); + assert_se(!valid_user_group_name("eff.", VALID_USER_ALLOW_NUMERIC)); + assert_se(!valid_user_group_name("foo\nbar", VALID_USER_ALLOW_NUMERIC)); + assert_se(!valid_user_group_name("0123456789012345678901234567890123456789", VALID_USER_ALLOW_NUMERIC)); + assert_se(!valid_user_group_name("aaa:bbb", VALID_USER_ALLOW_NUMERIC)); + + assert_se(valid_user_group_name("root", VALID_USER_ALLOW_NUMERIC)); + assert_se(valid_user_group_name("lennart", VALID_USER_ALLOW_NUMERIC)); + assert_se(valid_user_group_name("LENNART", VALID_USER_ALLOW_NUMERIC)); + assert_se(valid_user_group_name("_kkk", VALID_USER_ALLOW_NUMERIC)); + assert_se(valid_user_group_name("kkk-", VALID_USER_ALLOW_NUMERIC)); + assert_se(valid_user_group_name("kk-k", VALID_USER_ALLOW_NUMERIC)); + + assert_se(valid_user_group_name("some5", VALID_USER_ALLOW_NUMERIC)); + assert_se(!valid_user_group_name("5some", VALID_USER_ALLOW_NUMERIC)); + assert_se(valid_user_group_name("INNER5NUMBER", VALID_USER_ALLOW_NUMERIC)); + + assert_se(!valid_user_group_name("piff.paff@ad.domain.example", VALID_USER_ALLOW_NUMERIC)); + assert_se(!valid_user_group_name("Dāvis", VALID_USER_ALLOW_NUMERIC)); +} + +TEST(valid_gecos) { + assert_se(!valid_gecos(NULL)); + assert_se(valid_gecos("")); + assert_se(valid_gecos("test")); + assert_se(valid_gecos("Ümläüt")); + assert_se(!valid_gecos("In\nvalid")); + assert_se(!valid_gecos("In:valid")); +} + +TEST(valid_home) { + assert_se(!valid_home(NULL)); + assert_se(!valid_home("")); + assert_se(!valid_home(".")); + assert_se(!valid_home("/home/..")); + assert_se(!valid_home("/home/../")); + assert_se(!valid_home("/home\n/foo")); + assert_se(!valid_home("./piep")); + assert_se(!valid_home("piep")); + assert_se(!valid_home("/home/user:lennart")); + + assert_se(valid_home("/")); + assert_se(valid_home("/home")); + assert_se(valid_home("/home/foo")); +} + +static void test_get_user_creds_one(const char *id, const char *name, uid_t uid, gid_t gid, const char *home, const char *shell) { + const char *rhome = NULL; + const char *rshell = NULL; + uid_t ruid = UID_INVALID; + gid_t rgid = GID_INVALID; + int r; + + log_info("/* %s(\"%s\", \"%s\", "UID_FMT", "GID_FMT", \"%s\", \"%s\") */", + __func__, id, name, uid, gid, home, shell); + + r = get_user_creds(&id, &ruid, &rgid, &rhome, &rshell, 0); + log_info_errno(r, "got \"%s\", "UID_FMT", "GID_FMT", \"%s\", \"%s\": %m", + id, ruid, rgid, strnull(rhome), strnull(rshell)); + if (!synthesize_nobody() && streq(name, NOBODY_USER_NAME)) { + log_info("(skipping detailed tests because nobody is not synthesized)"); + return; + } + assert_se(r == 0); + assert_se(streq_ptr(id, name)); + assert_se(ruid == uid); + assert_se(rgid == gid); + assert_se(path_equal(rhome, home)); +} + +TEST(get_user_creds) { + test_get_user_creds_one("root", "root", 0, 0, "/root", DEFAULT_USER_SHELL); + test_get_user_creds_one("0", "root", 0, 0, "/root", DEFAULT_USER_SHELL); + test_get_user_creds_one(NOBODY_USER_NAME, NOBODY_USER_NAME, UID_NOBODY, GID_NOBODY, "/", NOLOGIN); + test_get_user_creds_one("65534", NOBODY_USER_NAME, UID_NOBODY, GID_NOBODY, "/", NOLOGIN); +} + +static void test_get_group_creds_one(const char *id, const char *name, gid_t gid) { + gid_t rgid = GID_INVALID; + int r; + + log_info("/* %s(\"%s\", \"%s\", "GID_FMT") */", __func__, id, name, gid); + + r = get_group_creds(&id, &rgid, 0); + log_info_errno(r, "got \"%s\", "GID_FMT": %m", id, rgid); + if (!synthesize_nobody() && streq(name, NOBODY_GROUP_NAME)) { + log_info("(skipping detailed tests because nobody is not synthesized)"); + return; + } + assert_se(r == 0); + assert_se(streq_ptr(id, name)); + assert_se(rgid == gid); +} + +TEST(get_group_creds) { + test_get_group_creds_one("root", "root", 0); + test_get_group_creds_one("0", "root", 0); + test_get_group_creds_one(NOBODY_GROUP_NAME, NOBODY_GROUP_NAME, GID_NOBODY); + test_get_group_creds_one("65534", NOBODY_GROUP_NAME, GID_NOBODY); +} + +TEST(make_salt) { + _cleanup_free_ char *s, *t; + + assert_se(make_salt(&s) == 0); + log_info("got %s", s); + + assert_se(make_salt(&t) == 0); + log_info("got %s", t); + + assert_se(!streq(s, t)); +} + +TEST(in_gid) { + assert_se(in_gid(getgid()) >= 0); + assert_se(in_gid(getegid()) >= 0); + assert_se(in_gid(GID_INVALID) < 0); + assert_se(in_gid(TTY_GID) == 0); /* The TTY gid is for owning ttys, it would be really really weird if we were in it. */ +} + +TEST(gid_lists_ops) { + static const gid_t l1[] = { 5, 10, 15, 20, 25}; + static const gid_t l2[] = { 1, 2, 3, 15, 20, 25}; + static const gid_t l3[] = { 5, 10, 15, 20, 25, 26, 27}; + static const gid_t l4[] = { 25, 26, 20, 15, 5, 27, 10}; + + static const gid_t result1[] = {1, 2, 3, 5, 10, 15, 20, 25, 26, 27}; + static const gid_t result2[] = {5, 10, 15, 20, 25, 26, 27}; + + _cleanup_free_ gid_t *gids = NULL; + _cleanup_free_ gid_t *res1 = NULL; + _cleanup_free_ gid_t *res2 = NULL; + _cleanup_free_ gid_t *res3 = NULL; + _cleanup_free_ gid_t *res4 = NULL; + int nresult; + + nresult = merge_gid_lists(l2, ELEMENTSOF(l2), l3, ELEMENTSOF(l3), &res1); + assert_se(nresult >= 0); + assert_se(memcmp_nn(res1, nresult, result1, ELEMENTSOF(result1)) == 0); + + nresult = merge_gid_lists(NULL, 0, l2, ELEMENTSOF(l2), &res2); + assert_se(nresult >= 0); + assert_se(memcmp_nn(res2, nresult, l2, ELEMENTSOF(l2)) == 0); + + nresult = merge_gid_lists(l1, ELEMENTSOF(l1), l1, ELEMENTSOF(l1), &res3); + assert_se(nresult >= 0); + assert_se(memcmp_nn(l1, ELEMENTSOF(l1), res3, nresult) == 0); + + nresult = merge_gid_lists(l1, ELEMENTSOF(l1), l4, ELEMENTSOF(l4), &res4); + assert_se(nresult >= 0); + assert_se(memcmp_nn(result2, ELEMENTSOF(result2), res4, nresult) == 0); + + nresult = getgroups_alloc(&gids); + assert_se(nresult >= 0 || nresult == -EINVAL || nresult == -ENOMEM); + assert_se(gids); +} + +TEST(parse_uid_range) { + uid_t a = 4711, b = 4711; + + assert_se(parse_uid_range("", &a, &b) == -EINVAL && a == 4711 && b == 4711); + assert_se(parse_uid_range(" ", &a, &b) == -EINVAL && a == 4711 && b == 4711); + assert_se(parse_uid_range("x", &a, &b) == -EINVAL && a == 4711 && b == 4711); + + assert_se(parse_uid_range("0", &a, &b) >= 0 && a == 0 && b == 0); + assert_se(parse_uid_range("1", &a, &b) >= 0 && a == 1 && b == 1); + assert_se(parse_uid_range("2-2", &a, &b) >= 0 && a == 2 && b == 2); + assert_se(parse_uid_range("3-3", &a, &b) >= 0 && a == 3 && b == 3); + assert_se(parse_uid_range("4-5", &a, &b) >= 0 && a == 4 && b == 5); + + assert_se(parse_uid_range("7-6", &a, &b) == -EINVAL && a == 4 && b == 5); + assert_se(parse_uid_range("-1", &a, &b) == -EINVAL && a == 4 && b == 5); + assert_se(parse_uid_range("01", &a, &b) == -EINVAL && a == 4 && b == 5); + assert_se(parse_uid_range("001", &a, &b) == -EINVAL && a == 4 && b == 5); + assert_se(parse_uid_range("+1", &a, &b) == -EINVAL && a == 4 && b == 5); + assert_se(parse_uid_range("1--1", &a, &b) == -EINVAL && a == 4 && b == 5); + assert_se(parse_uid_range(" 1", &a, &b) == -EINVAL && a == 4 && b == 5); + assert_se(parse_uid_range(" 1-2", &a, &b) == -EINVAL && a == 4 && b == 5); + assert_se(parse_uid_range("1 -2", &a, &b) == -EINVAL && a == 4 && b == 5); + assert_se(parse_uid_range("1- 2", &a, &b) == -EINVAL && a == 4 && b == 5); + assert_se(parse_uid_range("1-2 ", &a, &b) == -EINVAL && a == 4 && b == 5); + assert_se(parse_uid_range("01-2", &a, &b) == -EINVAL && a == 4 && b == 5); + assert_se(parse_uid_range("1-02", &a, &b) == -EINVAL && a == 4 && b == 5); + assert_se(parse_uid_range("001-2", &a, &b) == -EINVAL && a == 4 && b == 5); + assert_se(parse_uid_range("1-002", &a, &b) == -EINVAL && a == 4 && b == 5); + assert_se(parse_uid_range(" 01", &a, &b) == -EINVAL && a == 4 && b == 5); +} + +static void test_mangle_gecos_one(const char *input, const char *expected) { + _cleanup_free_ char *p = NULL; + + assert_se(p = mangle_gecos(input)); + assert_se(streq(p, expected)); + assert_se(valid_gecos(p)); +} + +TEST(mangle_gecos) { + test_mangle_gecos_one("", ""); + test_mangle_gecos_one("root", "root"); + test_mangle_gecos_one("wuff\nwuff", "wuff wuff"); + test_mangle_gecos_one("wuff:wuff", "wuff wuff"); + test_mangle_gecos_one("wuff\r\n:wuff", "wuff wuff"); + test_mangle_gecos_one("\n--wüff-wäff-wöff::", " --wüff-wäff-wöff "); + test_mangle_gecos_one("\xc3\x28", " ("); + test_mangle_gecos_one("\xe2\x28\xa1", " ( "); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-utf8.c b/src/test/test-utf8.c new file mode 100644 index 0000000..a0d7dc1 --- /dev/null +++ b/src/test/test-utf8.c @@ -0,0 +1,235 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" +#include "utf8.h" + +TEST(utf8_is_printable) { + assert_se(utf8_is_printable("ascii is valid\tunicode", 22)); + assert_se(utf8_is_printable("\342\204\242", 3)); + assert_se(!utf8_is_printable("\341\204", 2)); + assert_se(utf8_is_printable("ąę", 4)); + assert_se(!utf8_is_printable("\r", 1)); + assert_se(utf8_is_printable("\n", 1)); + assert_se(utf8_is_printable("\t", 1)); +} + +TEST(utf8_n_is_valid) { + assert_se( utf8_is_valid_n("ascii is valid unicode", 21)); + assert_se( utf8_is_valid_n("ascii is valid unicode", 22)); + assert_se(!utf8_is_valid_n("ascii is valid unicode", 23)); + assert_se( utf8_is_valid_n("\342\204\242", 0)); + assert_se(!utf8_is_valid_n("\342\204\242", 1)); + assert_se(!utf8_is_valid_n("\342\204\242", 2)); + assert_se( utf8_is_valid_n("\342\204\242", 3)); + assert_se(!utf8_is_valid_n("\342\204\242", 4)); + assert_se( utf8_is_valid_n("<ZZ>", 0)); + assert_se( utf8_is_valid_n("<ZZ>", 1)); + assert_se( utf8_is_valid_n("<ZZ>", 2)); + assert_se( utf8_is_valid_n("<ZZ>", 3)); + assert_se( utf8_is_valid_n("<ZZ>", 4)); + assert_se(!utf8_is_valid_n("<ZZ>", 5)); +} + +TEST(utf8_is_valid) { + assert_se(utf8_is_valid("ascii is valid unicode")); + assert_se(utf8_is_valid("\342\204\242")); + assert_se(!utf8_is_valid("\341\204")); +} + +TEST(ascii_is_valid) { + assert_se( ascii_is_valid("alsdjf\t\vbarr\nba z")); + assert_se(!ascii_is_valid("\342\204\242")); + assert_se(!ascii_is_valid("\341\204")); +} + +TEST(ascii_is_valid_n) { + assert_se( ascii_is_valid_n("alsdjf\t\vbarr\nba z", 17)); + assert_se( ascii_is_valid_n("alsdjf\t\vbarr\nba z", 16)); + assert_se(!ascii_is_valid_n("alsdjf\t\vbarr\nba z", 18)); + assert_se(!ascii_is_valid_n("\342\204\242", 3)); + assert_se(!ascii_is_valid_n("\342\204\242", 2)); + assert_se(!ascii_is_valid_n("\342\204\242", 1)); + assert_se( ascii_is_valid_n("\342\204\242", 0)); +} + +static void test_utf8_to_ascii_one(const char *s, int r_expected, const char *expected) { + _cleanup_free_ char *ans = NULL; + int r; + + r = utf8_to_ascii(s, '*', &ans); + log_debug("\"%s\" → %d/\"%s\" (expected %d/\"%s\")", s, r, strnull(ans), r_expected, strnull(expected)); + assert_se(r == r_expected); + assert_se(streq_ptr(ans, expected)); +} + +TEST(utf8_to_ascii) { + test_utf8_to_ascii_one("asdf", 0, "asdf"); + test_utf8_to_ascii_one("dąb", 0, "d*b"); + test_utf8_to_ascii_one("żęśłą óźń", 0, "***** ***"); + test_utf8_to_ascii_one("\342\204\242", 0, "*"); + test_utf8_to_ascii_one("\342\204", -EINVAL, NULL); /* truncated */ + test_utf8_to_ascii_one("\342", -EINVAL, NULL); /* truncated */ + test_utf8_to_ascii_one("\302\256", 0, "*"); + test_utf8_to_ascii_one("", 0, ""); + test_utf8_to_ascii_one(" ", 0, " "); + test_utf8_to_ascii_one("\t", 0, "\t"); + test_utf8_to_ascii_one("串", 0, "*"); + test_utf8_to_ascii_one("…👊🔪💐…", 0, "*****"); +} + +TEST(utf8_encoded_valid_unichar) { + assert_se(utf8_encoded_valid_unichar("\342\204\242", 1) == -EINVAL); /* truncated */ + assert_se(utf8_encoded_valid_unichar("\342\204\242", 2) == -EINVAL); /* truncated */ + assert_se(utf8_encoded_valid_unichar("\342\204\242", 3) == 3); + assert_se(utf8_encoded_valid_unichar("\342\204\242", 4) == 3); + assert_se(utf8_encoded_valid_unichar("\302\256", 1) == -EINVAL); /* truncated */ + assert_se(utf8_encoded_valid_unichar("\302\256", 2) == 2); + assert_se(utf8_encoded_valid_unichar("\302\256", 3) == 2); + assert_se(utf8_encoded_valid_unichar("\302\256", SIZE_MAX) == 2); + assert_se(utf8_encoded_valid_unichar("a", 1) == 1); + assert_se(utf8_encoded_valid_unichar("a", 2) == 1); + assert_se(utf8_encoded_valid_unichar("\341\204", 1) == -EINVAL); /* truncated, potentially valid */ + assert_se(utf8_encoded_valid_unichar("\341\204", 2) == -EINVAL); /* truncated, potentially valid */ + assert_se(utf8_encoded_valid_unichar("\341\204", 3) == -EINVAL); + assert_se(utf8_encoded_valid_unichar("\341\204\341\204", 4) == -EINVAL); + assert_se(utf8_encoded_valid_unichar("\341\204\341\204", 5) == -EINVAL); +} + +TEST(utf8_escape_invalid) { + _cleanup_free_ char *p1 = NULL, *p2 = NULL, *p3 = NULL; + + p1 = utf8_escape_invalid("goo goo goo"); + log_debug("\"%s\"", p1); + assert_se(utf8_is_valid(p1)); + + p2 = utf8_escape_invalid("\341\204\341\204"); + log_debug("\"%s\"", p2); + assert_se(utf8_is_valid(p2)); + + p3 = utf8_escape_invalid("\341\204"); + log_debug("\"%s\"", p3); + assert_se(utf8_is_valid(p3)); +} + +TEST(utf8_escape_non_printable) { + _cleanup_free_ char *p1 = NULL, *p2 = NULL, *p3 = NULL, *p4 = NULL, *p5 = NULL, *p6 = NULL; + + p1 = utf8_escape_non_printable("goo goo goo"); + log_debug("\"%s\"", p1); + assert_se(utf8_is_valid(p1)); + + p2 = utf8_escape_non_printable("\341\204\341\204"); + log_debug("\"%s\"", p2); + assert_se(utf8_is_valid(p2)); + + p3 = utf8_escape_non_printable("\341\204"); + log_debug("\"%s\"", p3); + assert_se(utf8_is_valid(p3)); + + p4 = utf8_escape_non_printable("ąę\n가너도루\n1234\n\341\204\341\204\n\001 \019\20\a"); + log_debug("\"%s\"", p4); + assert_se(utf8_is_valid(p4)); + + p5 = utf8_escape_non_printable("\001 \019\20\a"); + log_debug("\"%s\"", p5); + assert_se(utf8_is_valid(p5)); + + p6 = utf8_escape_non_printable("\xef\xbf\x30\x13"); + log_debug("\"%s\"", p6); + assert_se(utf8_is_valid(p6)); +} + +TEST(utf8_escape_non_printable_full) { + FOREACH_STRING(s, + "goo goo goo", /* ASCII */ + "\001 \019\20\a", /* control characters */ + "\xef\xbf\x30\x13") /* misplaced continuation bytes followed by a digit and cc */ + for (size_t cw = 0; cw < 22; cw++) { + _cleanup_free_ char *p = NULL, *q = NULL; + size_t ew; + + p = utf8_escape_non_printable_full(s, cw, false); + ew = utf8_console_width(p); + log_debug("%02zu \"%s\" (%zu wasted)", cw, p, cw - ew); + assert_se(utf8_is_valid(p)); + assert_se(ew <= cw); + + q = utf8_escape_non_printable_full(s, cw, true); + ew = utf8_console_width(q); + log_debug(" \"%s\" (%zu wasted)", q, cw - ew); + assert_se(utf8_is_valid(q)); + assert_se(ew <= cw); + if (cw > 0) + assert_se(endswith(q, "…")); + } +} + +TEST(utf16_to_utf8) { + const char16_t utf16[] = { htole16('a'), htole16(0xd800), htole16('b'), htole16(0xdc00), htole16('c'), htole16(0xd801), htole16(0xdc37) }; + static const char utf8[] = { 'a', 'b', 'c', 0xf0, 0x90, 0x90, 0xb7 }; + _cleanup_free_ char16_t *b = NULL; + _cleanup_free_ char *a = NULL; + + /* Convert UTF-16 to UTF-8, filtering embedded bad chars */ + a = utf16_to_utf8(utf16, sizeof(utf16)); + assert_se(a); + assert_se(memcmp(a, utf8, sizeof(utf8)) == 0); + + /* Convert UTF-8 to UTF-16, and back */ + b = utf8_to_utf16(utf8, sizeof(utf8)); + assert_se(b); + + free(a); + a = utf16_to_utf8(b, SIZE_MAX); + assert_se(a); + assert_se(strlen(a) == sizeof(utf8)); + assert_se(memcmp(a, utf8, sizeof(utf8)) == 0); +} + +TEST(utf8_n_codepoints) { + assert_se(utf8_n_codepoints("abc") == 3); + assert_se(utf8_n_codepoints("zażółcić gęślą jaźń") == 19); + assert_se(utf8_n_codepoints("串") == 1); + assert_se(utf8_n_codepoints("") == 0); + assert_se(utf8_n_codepoints("…👊🔪💐…") == 5); + assert_se(utf8_n_codepoints("\xF1") == SIZE_MAX); +} + +TEST(utf8_console_width) { + assert_se(utf8_console_width("abc") == 3); + assert_se(utf8_console_width("zażółcić gęślą jaźń") == 19); + assert_se(utf8_console_width("串") == 2); + assert_se(utf8_console_width("") == 0); + assert_se(utf8_console_width("…👊🔪💐…") == 8); + assert_se(utf8_console_width("\xF1") == SIZE_MAX); +} + +TEST(utf8_to_utf16) { + FOREACH_STRING(p, + "abc", + "zażółcić gęślą jaźń", + "串", + "", + "…👊🔪💐…") { + + _cleanup_free_ char16_t *a = NULL; + _cleanup_free_ char *b = NULL; + + a = utf8_to_utf16(p, SIZE_MAX); + assert_se(a); + + b = utf16_to_utf8(a, SIZE_MAX); + assert_se(b); + assert_se(streq(p, b)); + } +} + +static int intro(void) { + log_show_color(true); + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/test/test-utmp.c b/src/test/test-utmp.c new file mode 100644 index 0000000..06a0fce --- /dev/null +++ b/src/test/test-utmp.c @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "format-util.h" +#include "socket-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "utmp-wtmp.h" +#include "tests.h" + +#ifndef UT_LINESIZE +# define UT_LINESIZE 32 +#endif +#ifndef UT_NAMESIZE +# define UT_NAMESIZE 32 +#endif +#ifndef UT_HOSTSIZE +# define UT_HOSTSIZE 256 +#endif + +TEST(dump_run_utmp) { + _unused_ _cleanup_(utxent_cleanup) bool utmpx = false; + + utmpx = utxent_start(); + + for (struct utmpx *u; (u = getutxent()); ) { + char _type_buf[DECIMAL_STR_MAX(short)]; + const char *type = + u->ut_type == EMPTY ? "EMPTY" : + u->ut_type == RUN_LVL ? "RUN_LVL" : + u->ut_type == BOOT_TIME ? "BOOT_TIME" : + u->ut_type == NEW_TIME ? "NEW_TIME" : + u->ut_type == OLD_TIME ? "OLD_TIME" : + u->ut_type == INIT_PROCESS ? "INIT_PROCESS" : + u->ut_type == LOGIN_PROCESS ? "LOGIN_PROCESS" : + u->ut_type == USER_PROCESS ? "USER_PROCESS" : + u->ut_type == DEAD_PROCESS ? "DEAD_PROCESS" : + u->ut_type == ACCOUNTING ? "ACCOUNTING" : + _type_buf; + if (type == _type_buf) + xsprintf(_type_buf, "%hd", u->ut_type); + + union in_addr_union addr = {}; + memcpy(&addr, u->ut_addr_v6, MIN(sizeof(addr), sizeof(u->ut_addr_v6))); + bool is_ipv4 = memeqzero((const uint8_t*) &addr + 4, sizeof(addr) - 4); + + log_info("%14s %10"PID_PRI" line=%-7.*s id=%-4.4s name=%-8.*s session=%lu host=%.*s addr=%s", + type, + u->ut_pid, + UT_LINESIZE, u->ut_line, + u->ut_id, + UT_NAMESIZE, u->ut_user, + (long unsigned) u->ut_session, + UT_HOSTSIZE, u->ut_host, + IN_ADDR_TO_STRING(is_ipv4 ? AF_INET : AF_INET6, &addr)); + } +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-varlink-idl.c b/src/test/test-varlink-idl.c new file mode 100644 index 0000000..cbdb9c6 --- /dev/null +++ b/src/test/test-varlink-idl.c @@ -0,0 +1,385 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <pthread.h> + +#include "fd-util.h" +#include "pretty-print.h" +#include "tests.h" +#include "varlink.h" +#include "varlink-idl.h" +#include "varlink-io.systemd.h" +#include "varlink-io.systemd.Journal.h" +#include "varlink-io.systemd.ManagedOOM.h" +#include "varlink-io.systemd.PCRExtend.h" +#include "varlink-io.systemd.Resolve.Monitor.h" +#include "varlink-io.systemd.Resolve.h" +#include "varlink-io.systemd.UserDatabase.h" +#include "varlink-io.systemd.oom.h" +#include "varlink-io.systemd.service.h" +#include "varlink-io.systemd.sysext.h" +#include "varlink-org.varlink.service.h" + +static VARLINK_DEFINE_ENUM_TYPE( + EnumTest, + VARLINK_DEFINE_ENUM_VALUE(foo), + VARLINK_DEFINE_ENUM_VALUE(bar), + VARLINK_DEFINE_ENUM_VALUE(baz)); + +static VARLINK_DEFINE_STRUCT_TYPE( + NestedStructTest, + VARLINK_DEFINE_FIELD(x, VARLINK_INT, 0)); + +static VARLINK_DEFINE_STRUCT_TYPE( + StructTest, + + VARLINK_DEFINE_FIELD(bbb, VARLINK_BOOL, 0), + VARLINK_DEFINE_FIELD(bbbn, VARLINK_BOOL, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(bbba, VARLINK_BOOL, VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(bbbna, VARLINK_BOOL, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(bbbm, VARLINK_BOOL, VARLINK_MAP), + VARLINK_DEFINE_FIELD(bbbnm, VARLINK_BOOL, VARLINK_NULLABLE|VARLINK_MAP), + + VARLINK_DEFINE_FIELD(iii, VARLINK_INT, 0), + VARLINK_DEFINE_FIELD(iiin, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(iiia, VARLINK_INT, VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(iiina, VARLINK_INT, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(iiim, VARLINK_INT, VARLINK_MAP), + VARLINK_DEFINE_FIELD(iiinm, VARLINK_INT, VARLINK_NULLABLE|VARLINK_MAP), + + VARLINK_DEFINE_FIELD(fff, VARLINK_FLOAT, 0), + VARLINK_DEFINE_FIELD(fffn, VARLINK_FLOAT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(fffa, VARLINK_FLOAT, VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(fffna, VARLINK_FLOAT, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(fffm, VARLINK_FLOAT, VARLINK_MAP), + VARLINK_DEFINE_FIELD(fffnm, VARLINK_FLOAT, VARLINK_NULLABLE|VARLINK_MAP), + + VARLINK_DEFINE_FIELD(sss, VARLINK_STRING, 0), + VARLINK_DEFINE_FIELD(sssn, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(sssa, VARLINK_STRING, VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(sssna, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(sssm, VARLINK_STRING, VARLINK_MAP), + VARLINK_DEFINE_FIELD(sssnm, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_MAP), + + VARLINK_DEFINE_FIELD(ooo, VARLINK_OBJECT, 0), + VARLINK_DEFINE_FIELD(ooon, VARLINK_OBJECT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(oooa, VARLINK_OBJECT, VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(ooona, VARLINK_OBJECT, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(ooom, VARLINK_OBJECT, VARLINK_MAP), + VARLINK_DEFINE_FIELD(ooonm, VARLINK_OBJECT, VARLINK_NULLABLE|VARLINK_MAP), + + VARLINK_DEFINE_FIELD_BY_TYPE(eee, EnumTest, 0), + VARLINK_DEFINE_FIELD_BY_TYPE(eeen, EnumTest, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD_BY_TYPE(eeea, EnumTest, VARLINK_ARRAY), + VARLINK_DEFINE_FIELD_BY_TYPE(eeena, EnumTest, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_FIELD_BY_TYPE(eeem, EnumTest, VARLINK_MAP), + VARLINK_DEFINE_FIELD_BY_TYPE(eeenm, EnumTest, VARLINK_NULLABLE|VARLINK_MAP), + + VARLINK_DEFINE_FIELD_BY_TYPE(nnn, NestedStructTest, 0), + VARLINK_DEFINE_FIELD_BY_TYPE(nnnn, NestedStructTest, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD_BY_TYPE(nnna, NestedStructTest, VARLINK_ARRAY), + VARLINK_DEFINE_FIELD_BY_TYPE(nnnna, NestedStructTest, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_FIELD_BY_TYPE(nnnm, NestedStructTest, VARLINK_MAP), + VARLINK_DEFINE_FIELD_BY_TYPE(nnnnm, NestedStructTest, VARLINK_NULLABLE|VARLINK_MAP)); + +static VARLINK_DEFINE_METHOD( + MethodTest, + VARLINK_DEFINE_INPUT(x, VARLINK_BOOL, 0), + VARLINK_DEFINE_INPUT_BY_TYPE(y, EnumTest, 0), + VARLINK_DEFINE_INPUT_BY_TYPE(z, StructTest, 0), + VARLINK_DEFINE_OUTPUT(x, VARLINK_BOOL, 0), + VARLINK_DEFINE_OUTPUT_BY_TYPE(y, EnumTest, 0), + VARLINK_DEFINE_OUTPUT_BY_TYPE(z, StructTest, 0)); + +static VARLINK_DEFINE_ERROR( + ErrorTest, + VARLINK_DEFINE_FIELD(x, VARLINK_BOOL, 0), + VARLINK_DEFINE_FIELD_BY_TYPE(y, EnumTest, 0), + VARLINK_DEFINE_FIELD_BY_TYPE(z, StructTest, 0)); + +static VARLINK_DEFINE_INTERFACE( + xyz_test, + "xyz.test", + &vl_type_EnumTest, + &vl_type_NestedStructTest, + &vl_type_StructTest, + &vl_method_MethodTest, + &vl_error_ErrorTest); + +static void test_parse_format_one(const VarlinkInterface *iface) { + _cleanup_(varlink_interface_freep) VarlinkInterface *parsed = NULL; + _cleanup_free_ char *text = NULL, *text2 = NULL; + + assert_se(iface); + + assert_se(varlink_idl_dump(stdout, /* use_colors=*/ true, iface) >= 0); + assert_se(varlink_idl_consistent(iface, LOG_ERR) >= 0); + assert_se(varlink_idl_format(iface, &text) >= 0); + assert_se(varlink_idl_parse(text, NULL, NULL, &parsed) >= 0); + assert_se(varlink_idl_consistent(parsed, LOG_ERR) >= 0); + assert_se(varlink_idl_format(parsed, &text2) >= 0); + assert_se(streq(text, text2)); +} + +TEST(parse_format) { + test_parse_format_one(&vl_interface_org_varlink_service); + print_separator(); + test_parse_format_one(&vl_interface_io_systemd_UserDatabase); + print_separator(); + test_parse_format_one(&vl_interface_io_systemd_Journal); + print_separator(); + test_parse_format_one(&vl_interface_io_systemd_Resolve); + print_separator(); + test_parse_format_one(&vl_interface_io_systemd_Resolve_Monitor); + print_separator(); + test_parse_format_one(&vl_interface_io_systemd_ManagedOOM); + print_separator(); + test_parse_format_one(&vl_interface_io_systemd_oom); + print_separator(); + test_parse_format_one(&vl_interface_io_systemd); + print_separator(); + test_parse_format_one(&vl_interface_io_systemd_PCRExtend); + print_separator(); + test_parse_format_one(&vl_interface_io_systemd_service); + print_separator(); + test_parse_format_one(&vl_interface_io_systemd_sysext); + print_separator(); + test_parse_format_one(&vl_interface_xyz_test); +} + +TEST(parse) { + _cleanup_(varlink_interface_freep) VarlinkInterface *parsed = NULL; + + /* This one has (nested) enonymous enums and structs */ + static const char text[] = + "interface quu.waa\n" + "type Fooenum ( a, b, c )\n" + "type Barstruct ( a : (x, y, z), b : (x : int), c: (f, ff, fff), d: object, e : (sub : (subsub: (subsubsub: string, subsubsub2: (iii, ooo)))))" + ; + + assert_se(varlink_idl_parse(text, NULL, NULL, &parsed) >= 0); + test_parse_format_one(parsed); + + assert_se(varlink_idl_parse("interface org.freedesktop.Foo\n" + "type Foo (b: bool, c: foo, c: int)", NULL, NULL, NULL) == -ENETUNREACH); /* unresolved type */ + assert_se(varlink_idl_parse("interface org.freedesktop.Foo\n" + "type Foo ()", NULL, NULL, NULL) == -EBADMSG); /* empty struct/enum */ + +} + +TEST(interface_name_is_valid) { + assert_se(!varlink_idl_interface_name_is_valid(NULL)); + assert_se(!varlink_idl_interface_name_is_valid("")); + assert_se(!varlink_idl_interface_name_is_valid(",")); + assert_se(!varlink_idl_interface_name_is_valid(".")); + assert_se(!varlink_idl_interface_name_is_valid("-")); + assert_se(varlink_idl_interface_name_is_valid("a")); + assert_se(varlink_idl_interface_name_is_valid("a.a")); + assert_se(!varlink_idl_interface_name_is_valid("-.a")); + assert_se(!varlink_idl_interface_name_is_valid("-a.a")); + assert_se(!varlink_idl_interface_name_is_valid("a-.a")); + assert_se(varlink_idl_interface_name_is_valid("a-a.a")); + assert_se(!varlink_idl_interface_name_is_valid("a-a.a-")); + assert_se(!varlink_idl_interface_name_is_valid("a-a.-a")); + assert_se(!varlink_idl_interface_name_is_valid("a-a.-")); + assert_se(varlink_idl_interface_name_is_valid("a-a.a-a")); + assert_se(varlink_idl_interface_name_is_valid("io.systemd.Foobar")); +} + +TEST(symbol_name_is_valid) { + assert_se(!varlink_idl_symbol_name_is_valid(NULL)); + assert_se(!varlink_idl_symbol_name_is_valid("")); + assert_se(!varlink_idl_symbol_name_is_valid("_")); + assert_se(!varlink_idl_symbol_name_is_valid("_foo")); + assert_se(varlink_idl_symbol_name_is_valid("Foofoo")); + assert_se(varlink_idl_symbol_name_is_valid("Foo")); + assert_se(varlink_idl_symbol_name_is_valid("Foo0")); + assert_se(!varlink_idl_symbol_name_is_valid("0Foo")); + assert_se(!varlink_idl_symbol_name_is_valid("foo")); + assert_se(varlink_idl_symbol_name_is_valid("Foo0foo")); + assert_se(!varlink_idl_symbol_name_is_valid("bool")); + assert_se(!varlink_idl_symbol_name_is_valid("int")); + assert_se(!varlink_idl_symbol_name_is_valid("float")); + assert_se(!varlink_idl_symbol_name_is_valid("string")); + assert_se(!varlink_idl_symbol_name_is_valid("object")); +} + +TEST(field_name_is_valid) { + assert_se(!varlink_idl_field_name_is_valid(NULL)); + assert_se(!varlink_idl_field_name_is_valid("")); + assert_se(!varlink_idl_field_name_is_valid("_")); + assert_se(!varlink_idl_field_name_is_valid("_foo")); + assert_se(!varlink_idl_field_name_is_valid("_foo_")); + assert_se(!varlink_idl_field_name_is_valid("foo_")); + assert_se(varlink_idl_field_name_is_valid("foo_foo")); + assert_se(varlink_idl_field_name_is_valid("f_o_o_f_o_o")); + assert_se(!varlink_idl_field_name_is_valid("foo__foo")); + assert_se(varlink_idl_field_name_is_valid("Foofoo")); + assert_se(varlink_idl_field_name_is_valid("Foo")); + assert_se(varlink_idl_field_name_is_valid("Foo0")); + assert_se(!varlink_idl_field_name_is_valid("0Foo")); + assert_se(varlink_idl_field_name_is_valid("foo")); + assert_se(varlink_idl_field_name_is_valid("Foo0foo")); + assert_se(varlink_idl_field_name_is_valid("foo0foo")); +} + +TEST(validate_json) { + + _cleanup_(varlink_interface_freep) VarlinkInterface *parsed = NULL; + + /* This one has (nested) enonymous enums and structs */ + static const char text[] = + "interface validate.test\n" + "method Mymethod ( a:string, b:int, c:?bool, d:[]int, e:?[string]bool, f:?(piff, paff), g:(f:float) ) -> ()\n"; + + assert_se(varlink_idl_parse(text, NULL, NULL, &parsed) >= 0); + test_parse_format_one(parsed); + + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + assert_se(json_build(&v, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("a", JSON_BUILD_STRING("x")), + JSON_BUILD_PAIR("b", JSON_BUILD_UNSIGNED(44)), + JSON_BUILD_PAIR("d", JSON_BUILD_ARRAY(JSON_BUILD_UNSIGNED(5), JSON_BUILD_UNSIGNED(7), JSON_BUILD_UNSIGNED(107))), + JSON_BUILD_PAIR("g", JSON_BUILD_OBJECT(JSON_BUILD_PAIR("f", JSON_BUILD_REAL(0.5f)))))) >= 0); + + json_variant_dump(v, JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO, stdout, NULL); + + const VarlinkSymbol* symbol = ASSERT_PTR(varlink_idl_find_symbol(parsed, VARLINK_METHOD, "Mymethod")); + + assert_se(varlink_idl_validate_method_call(symbol, v, NULL) >= 0); +} + +static int test_recursive_one(unsigned depth) { + _cleanup_(varlink_interface_freep) VarlinkInterface *parsed = NULL; + _cleanup_free_ char *pre = NULL, *post = NULL, *text = NULL; + static const char header[] = + "interface recursive.test\n" + "type Foo (\n"; + + /* Generate a chain of nested structures, i.e. a: (a: (... (int))...) */ + pre = strrep("a:(", depth); + post = strrep(")", depth); + if (!pre || !post) + return log_oom(); + + text = strjoin(header, pre, "int", post, ")"); + if (!text) + return log_oom(); + + return varlink_idl_parse(text, NULL, NULL, &parsed); +} + +TEST(recursive) { + assert_se(test_recursive_one(32) >= 0); + assert_se(test_recursive_one(64) >= 0); + + /* We should handle this gracefully without a stack overflow */ + assert_se(test_recursive_one(65) < 0); + assert_se(test_recursive_one(20000) < 0 ); +} + +static int test_method(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + JsonVariant *foo = json_variant_by_key(parameters, "foo"), *bar = json_variant_by_key(parameters, "bar"); + + return varlink_replyb(link, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_UNSIGNED("waldo", json_variant_unsigned(foo) * json_variant_unsigned(bar)), + JSON_BUILD_PAIR_UNSIGNED("quux", json_variant_unsigned(foo) + json_variant_unsigned(bar)))); +} + +static int done_method(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + assert_se(sd_event_exit(varlink_get_event(link), 0) >= 0); + return 0; +} + +static VARLINK_DEFINE_METHOD( + TestMethod, + VARLINK_DEFINE_INPUT(foo, VARLINK_INT, 0), + VARLINK_DEFINE_INPUT(bar, VARLINK_INT, 0), + VARLINK_DEFINE_INPUT(optional, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(waldo, VARLINK_INT, 0), + VARLINK_DEFINE_OUTPUT(quux, VARLINK_INT, 0)); + +static VARLINK_DEFINE_METHOD(Done); + +static VARLINK_DEFINE_INTERFACE( + xyz, + "xyz", + &vl_method_TestMethod, + &vl_method_Done); + + +static void* server_thread(void *userdata) { + _cleanup_(varlink_server_unrefp) VarlinkServer *server = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + + assert_se(varlink_server_new(&server, 0) >= 0); + assert_se(varlink_server_add_interface(server, &vl_interface_xyz) >= 0); + assert_se(varlink_server_bind_method(server, "xyz.TestMethod", test_method) >= 0); + assert_se(varlink_server_bind_method(server, "xyz.Done", done_method) >= 0); + + assert_se(sd_event_new(&event) >= 0); + assert_se(varlink_server_attach_event(server, event, 0) >= 0); + + assert_se(varlink_server_add_connection(server, PTR_TO_FD(userdata), NULL) >= 0); + + assert_se(sd_event_loop(event) >= 0); + return NULL; +} + +TEST(validate_method_call) { + _cleanup_close_pair_ int fd[2] = EBADF_PAIR; + _cleanup_(varlink_unrefp) Varlink *v = NULL; + pthread_t t; + + assert_se(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, fd) >= 0); + assert_se(pthread_create(&t, NULL, server_thread, FD_TO_PTR(TAKE_FD(fd[1]))) == 0); + assert_se(varlink_connect_fd(&v, TAKE_FD(fd[0])) >= 0); + + JsonVariant *reply = NULL; + const char *error_id = NULL; + assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id, NULL, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_UNSIGNED("foo", 8), + JSON_BUILD_PAIR_UNSIGNED("bar", 9))) >= 0); + + _cleanup_(json_variant_unrefp) JsonVariant *expected_reply = NULL; + assert_se(json_build(&expected_reply, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_UNSIGNED("waldo", 8*9), + JSON_BUILD_PAIR_UNSIGNED("quux", 8+9))) >= 0); + + assert_se(!error_id); + + json_variant_dump(reply, JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO, NULL, NULL); + json_variant_dump(expected_reply, JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO, NULL, NULL); + assert_se(json_variant_equal(reply, expected_reply)); + + assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id, NULL, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_UNSIGNED("foo", 9), + JSON_BUILD_PAIR_UNSIGNED("bar", 8), + JSON_BUILD_PAIR_STRING("optional", "pfft"))) >= 0); + + assert_se(!error_id); + assert_se(json_variant_equal(reply, expected_reply)); + + assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id, NULL, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_UNSIGNED("foo", 8), + JSON_BUILD_PAIR_UNSIGNED("bar", 9), + JSON_BUILD_PAIR_STRING("zzz", "pfft"))) >= 0); + assert_se(streq_ptr(error_id, VARLINK_ERROR_INVALID_PARAMETER)); + + assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id, NULL, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_STRING("foo", "wuff"), + JSON_BUILD_PAIR_UNSIGNED("bar", 9))) >= 0); + assert_se(streq_ptr(error_id, VARLINK_ERROR_INVALID_PARAMETER)); + + assert_se(varlink_send(v, "xyz.Done", NULL) >= 0); + assert_se(varlink_flush(v) >= 0); + assert_se(pthread_join(t, NULL) == 0); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-varlink.c b/src/test/test-varlink.c new file mode 100644 index 0000000..2617ed0 --- /dev/null +++ b/src/test/test-varlink.c @@ -0,0 +1,376 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <fcntl.h> +#include <poll.h> +#include <pthread.h> + +#include "sd-event.h" + +#include "data-fd-util.h" +#include "fd-util.h" +#include "json.h" +#include "rm-rf.h" +#include "strv.h" +#include "tests.h" +#include "tmpfile-util.h" +#include "user-util.h" +#include "varlink.h" + +/* Let's pick some high value, that is higher than the largest listen() backlog, but leaves enough room below + the typical RLIMIT_NOFILE value of 1024 so that we can process both sides of each socket in our + process. Or in other words: "OVERLOAD_CONNECTIONS * 2 + x < 1024" should hold, for some small x that + should cover any auxiliary fds, the listener server fds, stdin/stdout/stderr and whatever else. */ +#define OVERLOAD_CONNECTIONS 333 + +static int n_done = 0; +static int block_write_fd = -EBADF; + +static int method_something(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + _cleanup_(json_variant_unrefp) JsonVariant *ret = NULL; + JsonVariant *a, *b; + int64_t x, y; + int r; + + a = json_variant_by_key(parameters, "a"); + if (!a) + return varlink_error(link, "io.test.BadParameters", NULL); + + x = json_variant_integer(a); + + b = json_variant_by_key(parameters, "b"); + if (!b) + return varlink_error(link, "io.test.BadParameters", NULL); + + y = json_variant_integer(b); + + r = json_build(&ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("sum", JSON_BUILD_INTEGER(x + y)))); + if (r < 0) + return r; + + return varlink_reply(link, ret); +} + +static int method_something_more(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + _cleanup_(json_variant_unrefp) JsonVariant *ret = NULL; + int r; + + struct Something { + int x; + int y; + }; + + static const JsonDispatch dispatch_table[] = { + { "a", JSON_VARIANT_INTEGER, json_dispatch_int, offsetof(struct Something, x), JSON_MANDATORY }, + { "b", JSON_VARIANT_INTEGER, json_dispatch_int, offsetof(struct Something, y), JSON_MANDATORY}, + {} + }; + struct Something s = {}; + + r = varlink_dispatch(link, parameters, dispatch_table, &s); + if (r != 0) + return r; + + for (int i = 0; i < 5; i++) { + _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; + + r = json_build(&w, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("sum", JSON_BUILD_INTEGER(s.x + (s.y * i))))); + if (r < 0) + return r; + + r = varlink_notify(link, w); + if (r < 0) + return r; + } + + r = json_build(&ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("sum", JSON_BUILD_INTEGER(s.x + (s.y * 5))))); + if (r < 0) + return r; + + return varlink_reply(link, ret); +} + +static void test_fd(int fd, const void *buf, size_t n) { + char rbuf[n + 1]; + ssize_t m; + + m = read(fd, rbuf, n + 1); + assert_se(m >= 0); + assert_se(memcmp_nn(buf, n, rbuf, m) == 0); +} + +static int method_passfd(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + _cleanup_(json_variant_unrefp) JsonVariant *ret = NULL; + JsonVariant *a; + int r; + + a = json_variant_by_key(parameters, "fd"); + if (!a) + return varlink_error(link, "io.test.BadParameters", NULL); + + assert_se(streq_ptr(json_variant_string(a), "whoop")); + + int xx = varlink_peek_fd(link, 0), + yy = varlink_peek_fd(link, 1), + zz = varlink_peek_fd(link, 2); + + log_info("%i %i %i", xx, yy, zz); + + assert_se(xx >= 0); + assert_se(yy >= 0); + assert_se(zz >= 0); + + test_fd(xx, "foo", 3); + test_fd(yy, "bar", 3); + test_fd(zz, "quux", 4); + + _cleanup_close_ int vv = acquire_data_fd("miau", 4, 0); + _cleanup_close_ int ww = acquire_data_fd("wuff", 4, 0); + + assert_se(vv >= 0); + assert_se(ww >= 0); + + r = json_build(&ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("yo", JSON_BUILD_INTEGER(88)))); + if (r < 0) + return r; + + assert_se(varlink_push_fd(link, vv) == 0); + assert_se(varlink_push_fd(link, ww) == 1); + + TAKE_FD(vv); + TAKE_FD(ww); + + return varlink_reply(link, ret); +} + +static int method_done(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + + if (++n_done == 2) + sd_event_exit(varlink_get_event(link), EXIT_FAILURE); + + return 0; +} + +static int reply(Varlink *link, JsonVariant *parameters, const char *error_id, VarlinkReplyFlags flags, void *userdata) { + JsonVariant *sum; + + sum = json_variant_by_key(parameters, "sum"); + + assert_se(json_variant_integer(sum) == 7+22); + + if (++n_done == 2) + sd_event_exit(varlink_get_event(link), EXIT_FAILURE); + + return 0; +} + +static int on_connect(VarlinkServer *s, Varlink *link, void *userdata) { + uid_t uid = UID_INVALID; + + assert_se(s); + assert_se(link); + + assert_se(varlink_get_peer_uid(link, &uid) >= 0); + assert_se(getuid() == uid); + assert_se(varlink_set_allow_fd_passing_input(link, true) >= 0); + assert_se(varlink_set_allow_fd_passing_output(link, true) >= 0); + + return 0; +} + +static int overload_reply(Varlink *link, JsonVariant *parameters, const char *error_id, VarlinkReplyFlags flags, void *userdata) { + + /* This method call reply should always be called with a disconnection, since the method call should + * be talking to an overloaded server */ + + log_debug("Over reply triggered with error: %s", strna(error_id)); + assert_se(streq(error_id, VARLINK_ERROR_DISCONNECTED)); + sd_event_exit(varlink_get_event(link), 0); + + return 0; +} + +static void flood_test(const char *address) { + _cleanup_(varlink_flush_close_unrefp) Varlink *c = NULL; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_free_ Varlink **connections = NULL; + size_t k; + char x = 'x'; + + log_debug("Flooding server..."); + + /* Block the main event loop while we flood */ + assert_se(write(block_write_fd, &x, sizeof(x)) == sizeof(x)); + + assert_se(sd_event_default(&e) >= 0); + + /* Flood the server with connections */ + assert_se(connections = new0(Varlink*, OVERLOAD_CONNECTIONS)); + for (k = 0; k < OVERLOAD_CONNECTIONS; k++) { + _cleanup_free_ char *t = NULL; + log_debug("connection %zu", k); + assert_se(varlink_connect_address(connections + k, address) >= 0); + + assert_se(asprintf(&t, "flood-%zu", k) >= 0); + assert_se(varlink_set_description(connections[k], t) >= 0); + assert_se(varlink_attach_event(connections[k], e, k) >= 0); + assert_se(varlink_sendb(connections[k], "io.test.Rubbish", JSON_BUILD_OBJECT(JSON_BUILD_PAIR("id", JSON_BUILD_INTEGER(k)))) >= 0); + } + + /* Then, create one more, which should fail */ + log_debug("Creating overload connection..."); + assert_se(varlink_connect_address(&c, address) >= 0); + assert_se(varlink_set_description(c, "overload-client") >= 0); + assert_se(varlink_attach_event(c, e, k) >= 0); + assert_se(varlink_bind_reply(c, overload_reply) >= 0); + assert_se(varlink_invokeb(c, "io.test.Overload", JSON_BUILD_OBJECT(JSON_BUILD_PAIR("foo", JSON_BUILD_CONST_STRING("bar")))) >= 0); + + /* Unblock it */ + log_debug("Unblocking server..."); + block_write_fd = safe_close(block_write_fd); + + /* This loop will terminate as soon as the overload reply callback is called */ + assert_se(sd_event_loop(e) >= 0); + + /* And close all connections again */ + for (k = 0; k < OVERLOAD_CONNECTIONS; k++) + connections[k] = varlink_unref(connections[k]); +} + +static void *thread(void *arg) { + _cleanup_(varlink_flush_close_unrefp) Varlink *c = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *i = NULL, *j = NULL; + JsonVariant *o = NULL, *k = NULL; + const char *error_id; + VarlinkReplyFlags flags = 0; + const char *e; + int x = 0; + + assert_se(json_build(&i, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("a", JSON_BUILD_INTEGER(88)), + JSON_BUILD_PAIR("b", JSON_BUILD_INTEGER(99)))) >= 0); + + assert_se(varlink_connect_address(&c, arg) >= 0); + assert_se(varlink_set_description(c, "thread-client") >= 0); + assert_se(varlink_set_allow_fd_passing_input(c, true) >= 0); + assert_se(varlink_set_allow_fd_passing_output(c, true) >= 0); + + assert_se(varlink_collect(c, "io.test.DoSomethingMore", i, &j, &error_id, &flags) >= 0); + + assert_se(!error_id); + assert_se(!flags); + assert_se(json_variant_is_array(j) && !json_variant_is_blank_array(j)); + + JSON_VARIANT_ARRAY_FOREACH(k, j) { + assert_se(json_variant_integer(json_variant_by_key(k, "sum")) == 88 + (99 * x)); + x++; + } + assert_se(x == 6); + + assert_se(varlink_call(c, "io.test.DoSomething", i, &o, &e, NULL) >= 0); + assert_se(json_variant_integer(json_variant_by_key(o, "sum")) == 88 + 99); + assert_se(!e); + + int fd1 = acquire_data_fd("foo", 3, 0); + int fd2 = acquire_data_fd("bar", 3, 0); + int fd3 = acquire_data_fd("quux", 4, 0); + + assert_se(fd1 >= 0); + assert_se(fd2 >= 0); + assert_se(fd3 >= 0); + + assert_se(varlink_push_fd(c, fd1) == 0); + assert_se(varlink_push_fd(c, fd2) == 1); + assert_se(varlink_push_fd(c, fd3) == 2); + + assert_se(varlink_callb(c, "io.test.PassFD", &o, &e, NULL, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("fd", JSON_BUILD_STRING("whoop")))) >= 0); + + int fd4 = varlink_peek_fd(c, 0); + int fd5 = varlink_peek_fd(c, 1); + + assert_se(fd4 >= 0); + assert_se(fd5 >= 0); + + test_fd(fd4, "miau", 4); + test_fd(fd5, "wuff", 4); + + assert_se(varlink_callb(c, "io.test.IDontExist", &o, &e, NULL, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("x", JSON_BUILD_REAL(5.5)))) >= 0); + assert_se(streq_ptr(json_variant_string(json_variant_by_key(o, "method")), "io.test.IDontExist")); + assert_se(streq(e, VARLINK_ERROR_METHOD_NOT_FOUND)); + + flood_test(arg); + + assert_se(varlink_send(c, "io.test.Done", NULL) >= 0); + + return NULL; +} + +static int block_fd_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + char c; + + assert_se(fd_nonblock(fd, false) >= 0); + + assert_se(read(fd, &c, sizeof(c)) == sizeof(c)); + /* When a character is written to this pipe we'll block until the pipe is closed. */ + + assert_se(read(fd, &c, sizeof(c)) == 0); + + assert_se(fd_nonblock(fd, true) >= 0); + + assert_se(sd_event_source_set_enabled(s, SD_EVENT_OFF) >= 0); + + return 0; +} + +int main(int argc, char *argv[]) { + _cleanup_(sd_event_source_unrefp) sd_event_source *block_event = NULL; + _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL; + _cleanup_(varlink_flush_close_unrefp) Varlink *c = NULL; + _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_close_pair_ int block_fds[2] = EBADF_PAIR; + pthread_t t; + const char *sp; + + test_setup_logging(LOG_DEBUG); + + assert_se(mkdtemp_malloc("/tmp/varlink-test-XXXXXX", &tmpdir) >= 0); + sp = strjoina(tmpdir, "/socket"); + + assert_se(sd_event_default(&e) >= 0); + + assert_se(pipe2(block_fds, O_NONBLOCK|O_CLOEXEC) >= 0); + assert_se(sd_event_add_io(e, &block_event, block_fds[0], EPOLLIN, block_fd_handler, NULL) >= 0); + assert_se(sd_event_source_set_priority(block_event, SD_EVENT_PRIORITY_IMPORTANT) >= 0); + block_write_fd = TAKE_FD(block_fds[1]); + + assert_se(varlink_server_new(&s, VARLINK_SERVER_ACCOUNT_UID) >= 0); + assert_se(varlink_server_set_description(s, "our-server") >= 0); + + assert_se(varlink_server_bind_method(s, "io.test.PassFD", method_passfd) >= 0); + assert_se(varlink_server_bind_method(s, "io.test.DoSomething", method_something) >= 0); + assert_se(varlink_server_bind_method(s, "io.test.DoSomethingMore", method_something_more) >= 0); + assert_se(varlink_server_bind_method(s, "io.test.Done", method_done) >= 0); + assert_se(varlink_server_bind_connect(s, on_connect) >= 0); + assert_se(varlink_server_listen_address(s, sp, 0600) >= 0); + assert_se(varlink_server_attach_event(s, e, 0) >= 0); + assert_se(varlink_server_set_connections_max(s, OVERLOAD_CONNECTIONS) >= 0); + + assert_se(json_build(&v, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("a", JSON_BUILD_INTEGER(7)), + JSON_BUILD_PAIR("b", JSON_BUILD_INTEGER(22)))) >= 0); + + assert_se(varlink_connect_address(&c, sp) >= 0); + assert_se(varlink_set_description(c, "main-client") >= 0); + assert_se(varlink_bind_reply(c, reply) >= 0); + + assert_se(varlink_invoke(c, "io.test.DoSomething", v) >= 0); + + assert_se(varlink_attach_event(c, e, 0) >= 0); + + assert_se(pthread_create(&t, NULL, thread, (void*) sp) == 0); + + assert_se(sd_event_loop(e) >= 0); + + assert_se(pthread_join(t, NULL) == 0); + + return 0; +} diff --git a/src/test/test-verbs.c b/src/test/test-verbs.c new file mode 100644 index 0000000..6e30794 --- /dev/null +++ b/src/test/test-verbs.c @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <getopt.h> + +#include "macro.h" +#include "strv.h" +#include "tests.h" +#include "verbs.h" + +static int noop_dispatcher(int argc, char *argv[], void *userdata) { + return 0; +} + +#define test_dispatch_one(argv, verbs, expected) \ + optind = 0; \ + assert_se(dispatch_verb(strv_length(argv), argv, verbs, NULL) == expected); + +TEST(verbs) { + static const Verb verbs[] = { + { "help", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, + { "list-images", VERB_ANY, 1, 0, noop_dispatcher }, + { "list", VERB_ANY, 2, VERB_DEFAULT, noop_dispatcher }, + { "status", 2, VERB_ANY, 0, noop_dispatcher }, + { "show", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, + { "terminate", 2, VERB_ANY, 0, noop_dispatcher }, + { "login", 2, 2, 0, noop_dispatcher }, + { "copy-to", 3, 4, 0, noop_dispatcher }, + {} + }; + + /* not found */ + test_dispatch_one(STRV_MAKE("command-not-found"), verbs, -EINVAL); + + /* found */ + test_dispatch_one(STRV_MAKE("show"), verbs, 0); + + /* found, too few args */ + test_dispatch_one(STRV_MAKE("copy-to", "foo"), verbs, -EINVAL); + + /* found, meets min args */ + test_dispatch_one(STRV_MAKE("status", "foo", "bar"), verbs, 0); + + /* found, too many args */ + test_dispatch_one(STRV_MAKE("copy-to", "foo", "bar", "baz", "quux", "qaax"), verbs, -EINVAL); + + /* no verb, but a default is set */ + test_dispatch_one(STRV_MAKE_EMPTY, verbs, 0); +} + +TEST(verbs_no_default) { + static const Verb verbs[] = { + { "help", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, + {}, + }; + + test_dispatch_one(STRV_MAKE(NULL), verbs, -EINVAL); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-watch-pid.c b/src/test/test-watch-pid.c new file mode 100644 index 0000000..b0c2c06 --- /dev/null +++ b/src/test/test-watch-pid.c @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "log.h" +#include "manager.h" +#include "process-util.h" +#include "rm-rf.h" +#include "service.h" +#include "tests.h" + +int main(int argc, char *argv[]) { + _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; + _cleanup_(manager_freep) Manager *m = NULL; + Unit *a, *b, *c, *u; + int r; + + test_setup_logging(LOG_DEBUG); + + if (getuid() != 0) + return log_tests_skipped("not root"); + r = enter_cgroup_subroot(NULL); + if (r == -ENOMEDIUM) + return log_tests_skipped("cgroupfs not available"); + + _cleanup_free_ char *unit_dir = NULL; + assert_se(get_testdata_dir("units/", &unit_dir) >= 0); + assert_se(set_unit_path(unit_dir) >= 0); + + assert_se(runtime_dir = setup_fake_runtime_dir()); + + assert_se(manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &m) >= 0); + assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + + assert_se(a = unit_new(m, sizeof(Service))); + assert_se(unit_add_name(a, "a.service") >= 0); + assert_se(set_isempty(a->pids)); + + assert_se(b = unit_new(m, sizeof(Service))); + assert_se(unit_add_name(b, "b.service") >= 0); + assert_se(set_isempty(b->pids)); + + assert_se(c = unit_new(m, sizeof(Service))); + assert_se(unit_add_name(c, "c.service") >= 0); + assert_se(set_isempty(c->pids)); + + /* Fork off a child so that we have a PID to watch */ + _cleanup_(sigkill_waitp) pid_t pid = 0; + pid = fork(); + if (pid == 0) { + /* Child */ + pause(); + _exit(EXIT_SUCCESS); + } + + assert_se(pid >= 0); + + assert_se(hashmap_isempty(m->watch_pids)); + assert_se(manager_get_unit_by_pid(m, pid) == NULL); + + assert_se(unit_watch_pid(a, pid, false) >= 0); + assert_se(manager_get_unit_by_pid(m, pid) == a); + + assert_se(unit_watch_pid(a, pid, false) >= 0); + assert_se(manager_get_unit_by_pid(m, pid) == a); + + assert_se(unit_watch_pid(b, pid, false) >= 0); + u = manager_get_unit_by_pid(m, pid); + assert_se(u == a || u == b); + + assert_se(unit_watch_pid(b, pid, false) >= 0); + u = manager_get_unit_by_pid(m, pid); + assert_se(u == a || u == b); + + assert_se(unit_watch_pid(c, pid, false) >= 0); + u = manager_get_unit_by_pid(m, pid); + assert_se(u == a || u == b || u == c); + + assert_se(unit_watch_pid(c, pid, false) >= 0); + u = manager_get_unit_by_pid(m, pid); + assert_se(u == a || u == b || u == c); + + unit_unwatch_pid(b, pid); + u = manager_get_unit_by_pid(m, pid); + assert_se(u == a || u == c); + + unit_unwatch_pid(b, pid); + u = manager_get_unit_by_pid(m, pid); + assert_se(u == a || u == c); + + unit_unwatch_pid(a, pid); + assert_se(manager_get_unit_by_pid(m, pid) == c); + + unit_unwatch_pid(a, pid); + assert_se(manager_get_unit_by_pid(m, pid) == c); + + unit_unwatch_pid(c, pid); + assert_se(manager_get_unit_by_pid(m, pid) == NULL); + + unit_unwatch_pid(c, pid); + assert_se(manager_get_unit_by_pid(m, pid) == NULL); + + return 0; +} diff --git a/src/test/test-watchdog.c b/src/test/test-watchdog.c new file mode 100644 index 0000000..70d069c --- /dev/null +++ b/src/test/test-watchdog.c @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <string.h> +#include <unistd.h> + +#include "log.h" +#include "tests.h" +#include "watchdog.h" + +int main(int argc, char *argv[]) { + usec_t t; + unsigned i, count; + int r; + bool slow; + + test_setup_logging(LOG_DEBUG); + + slow = slow_tests_enabled(); + + t = slow ? 10 * USEC_PER_SEC : 2 * USEC_PER_SEC; + count = slow ? 5 : 3; + + r = watchdog_setup(t); + if (r < 0) + log_warning_errno(r, "Failed to open watchdog: %m"); + + for (i = 0; i < count; i++) { + t = watchdog_runtime_wait(); + log_info("Sleeping " USEC_FMT " microseconds...", t); + usleep_safe(t); + log_info("Pinging..."); + r = watchdog_ping(); + if (r < 0) + log_warning_errno(r, "Failed to ping watchdog: %m"); + } + + watchdog_close(true); + return 0; +} diff --git a/src/test/test-web-util.c b/src/test/test-web-util.c new file mode 100644 index 0000000..d376d4a --- /dev/null +++ b/src/test/test-web-util.c @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "macro.h" +#include "tests.h" +#include "web-util.h" + +TEST(is_valid_documentation_url) { + assert_se(documentation_url_is_valid("https://www.freedesktop.org/wiki/Software/systemd")); + assert_se(documentation_url_is_valid("https://www.kernel.org/doc/Documentation/binfmt_misc.txt")); /* dead */ + assert_se(documentation_url_is_valid("https://www.kernel.org/doc/Documentation/admin-guide/binfmt-misc.rst")); + assert_se(documentation_url_is_valid("https://docs.kernel.org/admin-guide/binfmt-misc.html")); + assert_se(documentation_url_is_valid("file:/foo/foo")); + assert_se(documentation_url_is_valid("man:systemd.special(7)")); + assert_se(documentation_url_is_valid("info:bar")); + + assert_se(!documentation_url_is_valid("foo:")); + assert_se(!documentation_url_is_valid("info:")); + assert_se(!documentation_url_is_valid("")); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-xattr-util.c b/src/test/test-xattr-util.c new file mode 100644 index 0000000..85901c9 --- /dev/null +++ b/src/test/test-xattr-util.c @@ -0,0 +1,129 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/xattr.h> +#include <unistd.h> + +#include "alloc-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "macro.h" +#include "rm-rf.h" +#include "string-util.h" +#include "tests.h" +#include "tmpfile-util.h" +#include "xattr-util.h" + +TEST(getxattr_at_malloc) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_free_ char *value = NULL; + _cleanup_close_ int fd = -EBADF; + const char *x; + int r; + + fd = mkdtemp_open("/var/tmp/test-xattrtestXXXXXX", O_RDONLY|O_NOCTTY, &t); + assert_se(fd >= 0); + x = strjoina(t, "/test"); + assert_se(touch(x) >= 0); + + r = setxattr(x, "user.foo", "bar", 3, 0); + if (r < 0 && ERRNO_IS_NOT_SUPPORTED(errno)) + return (void) log_tests_skipped_errno(errno, "no xattrs supported on /var/tmp"); + assert_se(r >= 0); + + assert_se(getxattr_at_malloc(fd, "test", "user.foo", 0, &value) == 3); + assert_se(memcmp(value, "bar", 3) == 0); + value = mfree(value); + + assert_se(getxattr_at_malloc(AT_FDCWD, x, "user.foo", 0, &value) == 3); + assert_se(memcmp(value, "bar", 3) == 0); + value = mfree(value); + + safe_close(fd); + fd = open("/", O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY); + assert_se(fd >= 0); + r = getxattr_at_malloc(fd, "usr", "user.idontexist", 0, &value); + assert_se(r < 0 && ERRNO_IS_XATTR_ABSENT(r)); + + safe_close(fd); + fd = open(x, O_PATH|O_CLOEXEC); + assert_se(fd >= 0); + assert_se(getxattr_at_malloc(fd, NULL, "user.foo", 0, &value) == 3); + assert_se(streq(value, "bar")); +} + +TEST(getcrtime) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int fd = -EBADF; + usec_t usec, k; + int r; + + fd = mkdtemp_open("/var/tmp/test-xattrtestXXXXXX", 0, &t); + assert_se(fd >= 0); + + r = fd_getcrtime(fd, &usec); + if (r < 0) + log_debug_errno(r, "btime: %m"); + else + log_debug("btime: %s", FORMAT_TIMESTAMP(usec)); + + k = now(CLOCK_REALTIME); + + r = fd_setcrtime(fd, 1519126446UL * USEC_PER_SEC); + if (!IN_SET(r, -EOPNOTSUPP, -ENOTTY)) { + assert_se(fd_getcrtime(fd, &usec) >= 0); + assert_se(k < 1519126446UL * USEC_PER_SEC || + usec == 1519126446UL * USEC_PER_SEC); + } +} + +static void verify_xattr(int dfd, const char *expected) { + _cleanup_free_ char *value = NULL; + + assert_se(getxattr_at_malloc(dfd, "test", "user.foo", 0, &value) == (int) strlen(expected)); + assert_se(streq(value, expected)); +} + +TEST(xsetxattr) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int dfd = -EBADF, fd = -EBADF; + const char *x; + int r; + + dfd = mkdtemp_open("/var/tmp/test-xattrtestXXXXXX", O_PATH, &t); + assert_se(dfd >= 0); + x = strjoina(t, "/test"); + assert_se(touch(x) >= 0); + + /* by full path */ + r = xsetxattr(AT_FDCWD, x, "user.foo", "fullpath", SIZE_MAX, 0); + if (r < 0 && ERRNO_IS_NOT_SUPPORTED(r)) + return (void) log_tests_skipped_errno(r, "no xattrs supported on /var/tmp"); + assert_se(r >= 0); + verify_xattr(dfd, "fullpath"); + + /* by dirfd */ + assert_se(xsetxattr(dfd, "test", "user.foo", "dirfd", SIZE_MAX, 0) >= 0); + verify_xattr(dfd, "dirfd"); + + /* by fd (O_PATH) */ + fd = openat(dfd, "test", O_PATH|O_CLOEXEC); + assert_se(fd >= 0); + assert_se(xsetxattr(fd, NULL, "user.foo", "fd_opath", SIZE_MAX, 0) >= 0); + verify_xattr(dfd, "fd_opath"); + assert_se(xsetxattr(fd, "", "user.foo", "fd_opath", SIZE_MAX, 0) == -EINVAL); + assert_se(xsetxattr(fd, "", "user.foo", "fd_opath_empty", SIZE_MAX, AT_EMPTY_PATH) >= 0); + verify_xattr(dfd, "fd_opath_empty"); + fd = safe_close(fd); + + fd = openat(dfd, "test", O_RDONLY|O_CLOEXEC); + assert_se(xsetxattr(fd, NULL, "user.foo", "fd_regular", SIZE_MAX, 0) >= 0); + verify_xattr(dfd, "fd_regular"); + assert_se(xsetxattr(fd, "", "user.foo", "fd_regular_empty", SIZE_MAX, 0) == -EINVAL); + assert_se(xsetxattr(fd, "", "user.foo", "fd_regular_empty", SIZE_MAX, AT_EMPTY_PATH) >= 0); + verify_xattr(dfd, "fd_regular_empty"); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-xml.c b/src/test/test-xml.c new file mode 100644 index 0000000..a8cb635 --- /dev/null +++ b/src/test/test-xml.c @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdarg.h> + +#include "alloc-util.h" +#include "string-util.h" +#include "tests.h" +#include "xml.h" + +static void test_one(const char *data, ...) { + void *state = NULL; + va_list ap; + + va_start(ap, data); + + for (;;) { + _cleanup_free_ char *name = NULL; + int t, tt; + const char *nn; + + t = xml_tokenize(&data, &name, &state, NULL); + assert_se(t >= 0); + + tt = va_arg(ap, int); + assert_se(tt >= 0); + + assert_se(t == tt); + if (t == XML_END) + break; + + nn = va_arg(ap, const char *); + assert_se(streq_ptr(nn, name)); + } + + va_end(ap); +} + +int main(int argc, char *argv[]) { + + test_setup_logging(LOG_DEBUG); + + test_one("", XML_END); + + test_one("<foo></foo>", + XML_TAG_OPEN, "foo", + XML_TAG_CLOSE, "foo", + XML_END); + + test_one("<foo waldo=piep meh=\"huhu\"/>", + XML_TAG_OPEN, "foo", + XML_ATTRIBUTE_NAME, "waldo", + XML_ATTRIBUTE_VALUE, "piep", + XML_ATTRIBUTE_NAME, "meh", + XML_ATTRIBUTE_VALUE, "huhu", + XML_TAG_CLOSE_EMPTY, NULL, + XML_END); + + test_one("xxxx\n" + "<foo><?xml foo?> <!-- zzzz --> </foo>", + XML_TEXT, "xxxx\n", + XML_TAG_OPEN, "foo", + XML_TEXT, " ", + XML_TEXT, " ", + XML_TAG_CLOSE, "foo", + XML_END); + + return 0; +} |