diff --git a/pkgs/os-specific/linux/systemd/default.nix b/pkgs/os-specific/linux/systemd/default.nix index 5c140453a81f..964ebdf07d2b 100644 --- a/pkgs/os-specific/linux/systemd/default.nix +++ b/pkgs/os-specific/linux/systemd/default.nix @@ -262,55 +262,39 @@ stdenv.mkDerivation (finalAttrs: { ++ lib.optionals (stdenv.hostPlatform.isLinux && stdenv.hostPlatform.isGnu) [ ./0020-timesyncd-disable-NSCD-when-DNSSEC-validation-is-dis.patch ] - ++ lib.optionals stdenv.hostPlatform.isMusl ( - let - # NOTE: the master-next branch does not have stable URLs. - # If we need patches that aren't in master yet, they'll have to be - # vendored. - oe-core = fetchzip { - url = "https://git.openembedded.org/openembedded-core/snapshot/openembedded-core-4891f47cdaf919033bf1c02cc12e4805e5db99a0.tar.gz"; - hash = "sha256-YKL/oC+rPZ2EEVNidEV+pJihZgUv7vLb0OASplgktn4="; - }; - in - map (patch: "${oe-core}/meta/recipes-core/systemd/systemd/${patch}") [ - "0003-missing_type.h-add-comparison_fn_t.patch" - "0004-add-fallback-parse_printf_format-implementation.patch" - "0005-don-t-fail-if-GLOB_BRACE-and-GLOB_ALTDIRFUNC-is-not-.patch" - "0006-add-missing-FTW_-macros-for-musl.patch" - "0007-Use-uintmax_t-for-handling-rlim_t.patch" - "0008-Define-glibc-compatible-basename-for-non-glibc-syste.patch" - "0009-Do-not-disable-buffering-when-writing-to-oom_score_a.patch" - "0010-distinguish-XSI-compliant-strerror_r-from-GNU-specif.patch" - "0011-avoid-redefinition-of-prctl_mm_map-structure.patch" - "0012-do-not-disable-buffer-in-writing-files.patch" - "0013-Handle-__cpu_mask-usage.patch" - "0014-Handle-missing-gshadow.patch" - "0015-missing_syscall.h-Define-MIPS-ABI-defines-for-musl.patch" - "0016-pass-correct-parameters-to-getdents64.patch" - "0017-Adjust-for-musl-headers.patch" - "0018-test-bus-error-strerror-is-assumed-to-be-GNU-specifi.patch" - "0019-errno-util-Make-STRERROR-portable-for-musl.patch" - "0020-sd-event-Make-malloc_trim-conditional-on-glibc.patch" - "0021-shared-Do-not-use-malloc_info-on-musl.patch" - "0022-avoid-missing-LOCK_EX-declaration.patch" - "0023-include-signal.h-to-avoid-the-undeclared-error.patch" - "0024-undef-stdin-for-references-using-stdin-as-a-struct-m.patch" - "0025-adjust-header-inclusion-order-to-avoid-redeclaration.patch" - "0026-build-path.c-avoid-boot-time-segfault-for-musl.patch" - ] - ++ [ - # add a missing include - (fetchpatch { - url = "https://github.com/systemd/systemd/commit/34fcd3638817060c79e1186b370e46d9b3a7409f.patch"; - hash = "sha256-Uaewo3jPrZGJttlLcqO6cCj1w3IGZmvbur4+TBdIPxc="; - excludes = [ "src/udev/udevd.c" ]; - }) - (fetchpatch { - url = "https://gitlab.postmarketos.org/postmarketOS/systemd/-/commit/5760be33bd26d7e7c66a7294c5f6fd6c7044683f.patch"; - hash = "sha256-Om+OhGyZJfZNpbtMInm3vGagLbbtOY71fDMZXj6pbPY="; - }) - ] - ); + ++ lib.optionals stdenv.hostPlatform.isMusl [ + # Patchset to build with musl by an upstream systemd contributor: + # https://github.com/systemd/systemd/pull/37788 + # This is vendored here because of the lack of permanent patch urls for the unmerged PR + ./musl/0001-musl-meson-allow-to-choose-libc-implementation.patch + ./musl/0002-musl-meson-do-not-use-libcrypt-libxcrypt.patch + ./musl/0003-musl-meson-explicitly-link-with-libintl-when-necessa.patch + ./musl/0004-musl-meson-explicitly-set-_LARGEFILE64_SOURCE.patch + ./musl/0005-musl-meson-make-musl-not-define-wchar_t-in-their-hea.patch + ./musl/0006-musl-meson-check-existence-of-renameat2.patch + ./musl/0007-musl-meson-gracefully-disable-gshadow-idn-nss-and-ut.patch + ./musl/0008-musl-introduce-dummy-gshadow-header-file-for-userdb.patch + ./musl/0009-musl-add-fallback-parse_printf_format-implementation.patch + ./musl/0010-musl-introduce-GNU-specific-version-of-strerror_r.patch + ./musl/0011-musl-make-strptime-accept-z.patch + ./musl/0012-musl-make-strtoll-accept-strings-start-with-dot.patch + ./musl/0013-musl-introduce-strerrorname_np.patch + ./musl/0014-musl-introduce-dummy-functions-for-mallinfo-malloc_i.patch + ./musl/0015-musl-introduce-dummy-function-for-gnu_get_libc_versi.patch + ./musl/0016-musl-define-__THROW-when-not-defined.patch + ./musl/0017-musl-replace-sys-prctl.h-with-our-own-implementation.patch + ./musl/0018-musl-replace-netinet-if_ether.h-with-our-own-impleme.patch + ./musl/0019-musl-add-missing-FTW_CONTINUE-macro.patch + ./musl/0020-musl-add-several-missing-statx-macros.patch + ./musl/0021-musl-avoid-conflict-between-fcntl.h-and-our-forward..patch + ./musl/0022-musl-redefine-HOST_NAME_MAX-as-64.patch + ./musl/0023-musl-avoid-multiple-evaluations-in-CPU_ISSET_S-macro.patch + ./musl/0024-musl-core-there-is-one-less-usable-signal-when-built.patch + ./musl/0025-musl-build-path-fix-reading-DT_RUNPATH-or-DT_RPATH.patch + ./musl/0026-musl-format-util-use-llu-for-formatting-rlim_t.patch + ./musl/0027-musl-time-util-skip-tm.tm_wday-check.patch + ./musl/0028-musl-glob-util-filter-out-.-and-.-even-if-GLOB_ALTDI.patch + ]; postPatch = '' substituteInPlace src/basic/path-util.h --replace "@defaultPathNormal@" "${placeholder "out"}/bin/" @@ -622,6 +606,7 @@ stdenv.mkDerivation (finalAttrs: { (lib.mesonOption "zshcompletiondir" "no") ] ++ lib.optionals stdenv.hostPlatform.isMusl [ + (lib.mesonOption "libc" "musl") (lib.mesonBool "gshadow" false) (lib.mesonBool "idn" false) ]; diff --git a/pkgs/os-specific/linux/systemd/musl/0001-musl-meson-allow-to-choose-libc-implementation.patch b/pkgs/os-specific/linux/systemd/musl/0001-musl-meson-allow-to-choose-libc-implementation.patch new file mode 100644 index 000000000000..a40515f9ec29 --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0001-musl-meson-allow-to-choose-libc-implementation.patch @@ -0,0 +1,96 @@ +From 0e2b9909fed24a682c8566f9df8bbac4e9347186 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 7 Jul 2025 14:11:19 +0900 +Subject: [PATCH 01/30] musl: meson: allow to choose libc implementation + +This also introduces skelton directories for storing musl specific code. +--- + meson.build | 17 +++++++++++++++-- + meson_options.txt | 2 ++ + src/libc/meson.build | 2 ++ + src/libc/musl/meson.build | 5 +++++ + 4 files changed, 24 insertions(+), 2 deletions(-) + create mode 100644 src/libc/musl/meson.build + +diff --git a/meson.build b/meson.build +index 238b935372..bea62f0eb6 100644 +--- a/meson.build ++++ b/meson.build +@@ -72,7 +72,10 @@ conf.set10('SD_BOOT', false) + + # Create a title-less summary section early, so it ends up first in the output. + # More items are added later after they have been detected. +-summary({'build mode' : get_option('mode')}) ++summary({ ++ 'libc' : get_option('libc'), ++ 'build mode' : get_option('mode'), ++}) + + ##################################################################### + +@@ -2042,7 +2045,14 @@ dbus_programs = [] + boot_stubs = [] + + # This is similar to system_includes below, but for passing custom_target(). +-system_include_args = [ ++system_include_args = [] ++if get_option('libc') == 'musl' ++ system_include_args += [ ++ '-isystem', meson.project_build_root() / 'src/include/musl', ++ '-isystem', meson.project_source_root() / 'src/include/musl', ++ ] ++endif ++system_include_args += [ + '-isystem', meson.project_build_root() / 'src/include/override', + '-isystem', meson.project_source_root() / 'src/include/override', + '-isystem', meson.project_build_root() / 'src/include/uapi', +@@ -2060,6 +2070,9 @@ system_includes = [ + is_system : true, + ), + ] ++if get_option('libc') == 'musl' ++ system_includes += include_directories('src/include/musl', is_system : true) ++endif + + basic_includes = [ + include_directories( +diff --git a/meson_options.txt b/meson_options.txt +index d8dec33ec4..ad203ba301 100644 +--- a/meson_options.txt ++++ b/meson_options.txt +@@ -397,6 +397,8 @@ option('ima', type : 'boolean', + option('ipe', type : 'boolean', + description : 'IPE support') + ++option('libc', type : 'combo', choices : ['glibc', 'musl'], ++ description : 'libc implementation to be used') + option('acl', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'disabled' }, + description : 'libacl support') + option('audit', type : 'feature', deprecated : { 'true' : 'enabled', 'false' : 'disabled' }, +diff --git a/src/libc/meson.build b/src/libc/meson.build +index eeee98c9d6..306512ffd7 100644 +--- a/src/libc/meson.build ++++ b/src/libc/meson.build +@@ -16,6 +16,8 @@ libc_wrapper_sources = files( + 'xattr.c', + ) + ++subdir('musl') ++ + sources += libc_wrapper_sources + + libc_wrapper_static = static_library( +diff --git a/src/libc/musl/meson.build b/src/libc/musl/meson.build +new file mode 100644 +index 0000000000..a876230c67 +--- /dev/null ++++ b/src/libc/musl/meson.build +@@ -0,0 +1,5 @@ ++# SPDX-License-Identifier: LGPL-2.1-or-later ++ ++if get_option('libc') != 'musl' ++ subdir_done() ++endif +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0002-musl-meson-do-not-use-libcrypt-libxcrypt.patch b/pkgs/os-specific/linux/systemd/musl/0002-musl-meson-do-not-use-libcrypt-libxcrypt.patch new file mode 100644 index 000000000000..b59b77dd7712 --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0002-musl-meson-do-not-use-libcrypt-libxcrypt.patch @@ -0,0 +1,38 @@ +From 03010a0716f509b448c84a35bc2723a6f08e0c09 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Wed, 23 Jul 2025 10:24:14 +0900 +Subject: [PATCH 02/30] musl: meson: do not use libcrypt/libxcrypt + +Otherwise, when both glibc and musl are installed, libxcrypt or glibc's +libcrypt may be picked even when we are building systemd with musl. +Let's not use libcrypt/libxcrypt in that case. +--- + meson.build | 12 ++++++++---- + 1 file changed, 8 insertions(+), 4 deletions(-) + +diff --git a/meson.build b/meson.build +index bea62f0eb6..06be36409f 100644 +--- a/meson.build ++++ b/meson.build +@@ -1013,10 +1013,14 @@ else + libatomic = [] + endif + +-libcrypt = dependency('libcrypt', 'libxcrypt', required : false) +-if not libcrypt.found() +- # fallback to use find_library() if libcrypt is provided by glibc, e.g. for LibreELEC. +- libcrypt = cc.find_library('crypt') ++if get_option('libc') == 'musl' ++ libcrypt = [] ++else ++ libcrypt = dependency('libcrypt', 'libxcrypt', required : false) ++ if not libcrypt.found() ++ # fallback to use find_library() if libcrypt is provided by glibc, e.g. for LibreELEC. ++ libcrypt = cc.find_library('crypt') ++ endif + endif + + foreach func : [ +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0003-musl-meson-explicitly-link-with-libintl-when-necessa.patch b/pkgs/os-specific/linux/systemd/musl/0003-musl-meson-explicitly-link-with-libintl-when-necessa.patch new file mode 100644 index 000000000000..000c6c65d6f6 --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0003-musl-meson-explicitly-link-with-libintl-when-necessa.patch @@ -0,0 +1,56 @@ +From bf4cb5aeeef23c5f12e6d2258bb8f3d12059f59f Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Sat, 6 Sep 2025 16:25:41 +0900 +Subject: [PATCH 03/30] musl: meson: explicitly link with libintl when + necessary + +On some musl based distributions provides dgettext() by libintl.so. +Hence, we need to add dependency in that case. +--- + meson.build | 17 +++++++++++++++++ + src/home/meson.build | 1 + + 2 files changed, 18 insertions(+) + +diff --git a/meson.build b/meson.build +index 06be36409f..054752c339 100644 +--- a/meson.build ++++ b/meson.build +@@ -1002,6 +1002,23 @@ libm = cc.find_library('m') + libdl = cc.find_library('dl') + libcap = dependency('libcap') + ++# On some distributions that uses musl (e.g. Alpine), libintl.h may be provided by gettext rather than musl. ++# In that case, we need to explicitly link with libintl.so. ++if get_option('libc') == 'musl' ++ if cc.has_function('dgettext', prefix : '''#include ''', args : '-D_GNU_SOURCE') ++ libintl = [] ++ else ++ libintl = cc.find_library('intl') ++ if not cc.has_function('dgettext', prefix : '''#include ''', args : '-D_GNU_SOURCE', ++ dependencies : libintl) ++ error('dgettext() not found.') ++ endif ++ endif ++else ++ # When building with glibc, we assume that libintl.h is provided by glibc. ++ libintl = [] ++endif ++ + # On some architectures, libatomic is required. But on some installations, + # it is found, but actual linking fails. So let's try to use it opportunistically. + # If it is installed, but not needed, it will be dropped because of --as-needed. +diff --git a/src/home/meson.build b/src/home/meson.build +index 1937e6f56c..3305334707 100644 +--- a/src/home/meson.build ++++ b/src/home/meson.build +@@ -115,6 +115,7 @@ modules += [ + 'sources' : pam_systemd_home_sources, + 'dependencies' : [ + libcrypt, ++ libintl, + libpam_misc, + libpam, + threads, +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0004-musl-meson-explicitly-set-_LARGEFILE64_SOURCE.patch b/pkgs/os-specific/linux/systemd/musl/0004-musl-meson-explicitly-set-_LARGEFILE64_SOURCE.patch new file mode 100644 index 000000000000..17de4bd3b5dd --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0004-musl-meson-explicitly-set-_LARGEFILE64_SOURCE.patch @@ -0,0 +1,32 @@ +From 59f993d1acb069f3f2b7d6c71b91696388692702 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 10 Jun 2025 00:29:46 +0900 +Subject: [PATCH 04/30] musl: meson: explicitly set _LARGEFILE64_SOURCE + +glibc sets it when _GNU_SOURCE is defined, however, musl does not. +Let's explicitly define it to make getdents64() and struct dirent64 +available even when building with musl. +--- + meson.build | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/meson.build b/meson.build +index 054752c339..37fb1c2765 100644 +--- a/meson.build ++++ b/meson.build +@@ -560,6 +560,12 @@ conf.set10('HAVE_WARNING_ZERO_AS_NULL_POINTER_CONSTANT', have) + conf.set('_GNU_SOURCE', 1) + conf.set('__SANE_USERSPACE_TYPES__', true) + ++if get_option('libc') == 'musl' ++ # glibc always defines _LARGEFILE64_SOURCE when _GNU_SOURCE is set, but musl does not do that, ++ # and it is necessary for making getdents64() and struct dirent64 exist. ++ conf.set('_LARGEFILE64_SOURCE', 1) ++endif ++ + conf.set('SIZEOF_DEV_T', cc.sizeof('dev_t', prefix : '#include ')) + conf.set('SIZEOF_INO_T', cc.sizeof('ino_t', prefix : '#include ')) + conf.set('SIZEOF_RLIM_T', cc.sizeof('rlim_t', prefix : '#include ')) +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0005-musl-meson-make-musl-not-define-wchar_t-in-their-hea.patch b/pkgs/os-specific/linux/systemd/musl/0005-musl-meson-make-musl-not-define-wchar_t-in-their-hea.patch new file mode 100644 index 000000000000..faab7be7ca92 --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0005-musl-meson-make-musl-not-define-wchar_t-in-their-hea.patch @@ -0,0 +1,31 @@ +From a8cc582e42dd9fd6b2304003f3e5ab7637c21b73 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jun 2025 13:37:38 +0900 +Subject: [PATCH 05/30] musl: meson: make musl not define wchar_t in their + header + +Otherwise, musl defines wchar_t as int, which conflicts with the +assumption by sd-boot, i.e. wchar_t is 2 bytes. +--- + src/boot/meson.build | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/src/boot/meson.build b/src/boot/meson.build +index ba0b309acf..bea96bb2e3 100644 +--- a/src/boot/meson.build ++++ b/src/boot/meson.build +@@ -179,6 +179,11 @@ if get_option('mode') == 'developer' and get_option('debug') + efi_c_args += '-DEFI_DEBUG' + endif + ++if get_option('libc') == 'musl' ++ # To make musl not define wchar_t as int, rather than short. ++ efi_c_args += '-D__DEFINED_wchar_t' ++endif ++ + efi_c_ld_args = [ + '-nostdlib', + '-static-pie', +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0006-musl-meson-check-existence-of-renameat2.patch b/pkgs/os-specific/linux/systemd/musl/0006-musl-meson-check-existence-of-renameat2.patch new file mode 100644 index 000000000000..9109275b4eb8 --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0006-musl-meson-check-existence-of-renameat2.patch @@ -0,0 +1,91 @@ +From b9904ed6818232d9ad9bb496747b79a6c9606819 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jun 2025 13:00:37 +0900 +Subject: [PATCH 06/30] musl: meson: check existence of renameat2() + +musl-1.2.5 does not provide renameat2(). Note, it is added by +https://github.com/kraj/musl/commit/05ce67fea99ca09cd4b6625cff7aec9cc222dd5a, +hence hopefully it will be provided by musl-1.2.6 or newer. +--- + meson.build | 5 +++++ + src/include/musl/stdio.h | 13 +++++++++++++ + src/libc/musl/meson.build | 4 ++++ + src/libc/musl/stdio.c | 11 +++++++++++ + 4 files changed, 33 insertions(+) + create mode 100644 src/include/musl/stdio.h + create mode 100644 src/libc/musl/stdio.c + +diff --git a/meson.build b/meson.build +index 37fb1c2765..71e422f545 100644 +--- a/meson.build ++++ b/meson.build +@@ -581,6 +581,7 @@ assert(long_max > 100000) + conf.set_quoted('LONG_MAX_STR', '@0@'.format(long_max)) + + foreach ident : [ ++ ['renameat2', '''#include ''', get_option('libc') == 'musl'], # since musl-1.2.6 + ['set_mempolicy', '''#include '''], # declared at numaif.h provided by libnuma, which we do not use + ['get_mempolicy', '''#include '''], # declared at numaif.h provided by libnuma, which we do not use + ['strerrorname_np', '''#include '''], # since glibc-2.32 +@@ -614,6 +615,10 @@ foreach ident : [ + ['pivot_root', '''#include '''], # no known header declares pivot_root + ] + ++ if ident.length() >= 3 and not ident[2] ++ continue ++ endif ++ + have = cc.has_function(ident[0], prefix : ident[1], args : '-D_GNU_SOURCE') + conf.set10('HAVE_' + ident[0].to_upper(), have) + endforeach +diff --git a/src/include/musl/stdio.h b/src/include/musl/stdio.h +new file mode 100644 +index 0000000000..d677201f45 +--- /dev/null ++++ b/src/include/musl/stdio.h +@@ -0,0 +1,13 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++#pragma once ++ ++#include_next ++ ++#if !HAVE_RENAMEAT2 ++# define RENAME_NOREPLACE (1 << 0) ++# define RENAME_EXCHANGE (1 << 1) ++# define RENAME_WHITEOUT (1 << 2) ++ ++int missing_renameat2(int __oldfd, const char *__old, int __newfd, const char *__new, unsigned __flags); ++# define renameat2 missing_renameat2 ++#endif +diff --git a/src/libc/musl/meson.build b/src/libc/musl/meson.build +index a876230c67..8d06d919ef 100644 +--- a/src/libc/musl/meson.build ++++ b/src/libc/musl/meson.build +@@ -3,3 +3,7 @@ + if get_option('libc') != 'musl' + subdir_done() + endif ++ ++libc_wrapper_sources += files( ++ 'stdio.c', ++) +diff --git a/src/libc/musl/stdio.c b/src/libc/musl/stdio.c +new file mode 100644 +index 0000000000..102a22cd5c +--- /dev/null ++++ b/src/libc/musl/stdio.c +@@ -0,0 +1,11 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++ ++#include ++#include ++#include ++ ++#if !HAVE_RENAMEAT2 ++int missing_renameat2(int __oldfd, const char *__old, int __newfd, const char *__new, unsigned __flags) { ++ return syscall(__NR_renameat2, __oldfd, __old, __newfd, __new, __flags); ++} ++#endif +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0007-musl-meson-gracefully-disable-gshadow-idn-nss-and-ut.patch b/pkgs/os-specific/linux/systemd/musl/0007-musl-meson-gracefully-disable-gshadow-idn-nss-and-ut.patch new file mode 100644 index 000000000000..8f3d16510d20 --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0007-musl-meson-gracefully-disable-gshadow-idn-nss-and-ut.patch @@ -0,0 +1,91 @@ +From 1a5f752e2a80e206474f04fe5c1990fdb08742bd Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Sun, 22 Jun 2025 02:16:25 +0900 +Subject: [PATCH 07/30] musl: meson: gracefully disable gshadow, idn, nss, and + utmp support + +- musl does not support gshadow, and does not provide gshadow.h, +- musl does not provide NI_IDN flag, +- musl does not support nss, and does not provide nss.h which is necessary + for each nss modules, +- musl does not support utmp, and all utmp related functions do nothing, + see https://github.com/kraj/musl/blob/v1.2.5/src/legacy/utmpx.c +--- + meson.build | 33 ++++++++++++++++++++++++--------- + 1 file changed, 24 insertions(+), 9 deletions(-) + +diff --git a/meson.build b/meson.build +index 71e422f545..5f235d5113 100644 +--- a/meson.build ++++ b/meson.build +@@ -1638,11 +1638,9 @@ foreach term : ['analyze', + 'efi', + 'environment-d', + 'firstboot', +- 'gshadow', + 'hibernate', + 'hostnamed', + 'hwdb', +- 'idn', + 'ima', + 'ipe', + 'initrd', +@@ -1654,8 +1652,6 @@ foreach term : ['analyze', + 'mountfsd', + 'networkd', + 'nsresourced', +- 'nss-myhostname', +- 'nss-systemd', + 'oomd', + 'portabled', + 'pstore', +@@ -1671,7 +1667,6 @@ foreach term : ['analyze', + 'tmpfiles', + 'tpm', + 'userdb', +- 'utmp', + 'vconsole', + 'xdg-autostart'] + have = get_option(term) +@@ -1681,14 +1676,34 @@ endforeach + + enable_sysusers = conf.get('ENABLE_SYSUSERS') == 1 + ++foreach term : ['gshadow', ++ 'idn', ++ 'nss-myhostname', ++ 'nss-systemd', ++ 'utmp'] ++ ++ have = get_option(term) ++ if have and get_option('libc') == 'musl' ++ warning('@0@ support is requested but it is not supported when building with musl, disabling it.'.format(term)) ++ have = false ++ endif ++ name = 'ENABLE_' + term.underscorify().to_upper() ++ conf.set10(name, have) ++endforeach ++ + foreach tuple : [['nss-mymachines', 'machined'], + ['nss-resolve', 'resolve']] + want = get_option(tuple[0]) +- if want.allowed() +- have = get_option(tuple[1]) +- if want.enabled() and not have +- error('@0@ is requested but @1@ is disabled'.format(tuple[0], tuple[1])) ++ if want.enabled() ++ if get_option('libc') == 'musl' ++ error('@0@ is requested but it is not supported when building with musl.'.format(tuple[0])) + endif ++ if not get_option(tuple[1]) ++ error('@0@ is requested but @1@ is disabled.'.format(tuple[0], tuple[1])) ++ endif ++ have = true ++ elif want.allowed() ++ have = get_option(tuple[1]) and get_option('libc') != 'musl' + else + have = false + endif +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0008-musl-introduce-dummy-gshadow-header-file-for-userdb.patch b/pkgs/os-specific/linux/systemd/musl/0008-musl-introduce-dummy-gshadow-header-file-for-userdb.patch new file mode 100644 index 000000000000..50bc22cfe678 --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0008-musl-introduce-dummy-gshadow-header-file-for-userdb.patch @@ -0,0 +1,44 @@ +From 7e023763fb7f56b7c1ea6fa139d79ec2c3e0164f Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Sun, 21 Sep 2025 15:24:06 +0900 +Subject: [PATCH 08/30] musl: introduce dummy gshadow header file for userdb + +Even 'gshadow' meson option is disabled, src/shared/userdb.c and +src/shared/user-record-nss.c include gshadow.h unconditionally. +Let's introduce dummy header to make them compiled gracefully. +--- + src/include/musl/gshadow.h | 22 ++++++++++++++++++++++ + 1 file changed, 22 insertions(+) + create mode 100644 src/include/musl/gshadow.h + +diff --git a/src/include/musl/gshadow.h b/src/include/musl/gshadow.h +new file mode 100644 +index 0000000000..b57c74ca83 +--- /dev/null ++++ b/src/include/musl/gshadow.h +@@ -0,0 +1,22 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++#pragma once ++ ++#include ++#include ++ ++struct sgrp { ++ char *sg_namp; ++ char *sg_passwd; ++ char **sg_adm; ++ char **sg_mem; ++}; ++ ++static inline int getsgnam_r( ++ const char *__name, ++ struct sgrp *__result_buf, ++ char *__buffer, ++ size_t __buflen, ++ struct sgrp **__result) { ++ ++ return EOPNOTSUPP; /* this function returns positive errno in case of error. */ ++} +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0009-musl-add-fallback-parse_printf_format-implementation.patch b/pkgs/os-specific/linux/systemd/musl/0009-musl-add-fallback-parse_printf_format-implementation.patch new file mode 100644 index 000000000000..d4347746067e --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0009-musl-add-fallback-parse_printf_format-implementation.patch @@ -0,0 +1,447 @@ +From 444acef17881cbc27057bd85b7dc07b332c03d67 Mon Sep 17 00:00:00 2001 +From: Emil Renner Berthing +Date: Sat, 22 May 2021 20:26:24 +0200 +Subject: [PATCH 09/30] musl: add fallback parse_printf_format() implementation + +musl does not provide parse_printf_format(). Let's introduce a fallback +method. + +Co-authored-by: Yu Watanabe +--- + src/include/musl/printf.h | 35 +++++ + src/libc/musl/meson.build | 1 + + src/libc/musl/printf.c | 276 ++++++++++++++++++++++++++++++++++++++ + src/test/meson.build | 6 + + src/test/test-printf.c | 67 +++++++++ + 5 files changed, 385 insertions(+) + create mode 100644 src/include/musl/printf.h + create mode 100644 src/libc/musl/printf.c + create mode 100644 src/test/test-printf.c + +diff --git a/src/include/musl/printf.h b/src/include/musl/printf.h +new file mode 100644 +index 0000000000..4242392c89 +--- /dev/null ++++ b/src/include/musl/printf.h +@@ -0,0 +1,35 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* Copyright 2014 Emil Renner Berthing */ ++#pragma once ++ ++#include ++#include ++ ++#ifdef __GLIBC__ ++#include_next ++#else ++ ++enum { /* C type: */ ++ PA_INT, /* int */ ++ PA_CHAR, /* int, cast to char */ ++ PA_WCHAR, /* wide char */ ++ PA_STRING, /* const char *, a '\0'-terminated string */ ++ PA_WSTRING, /* const wchar_t *, wide character string */ ++ PA_POINTER, /* void * */ ++ PA_FLOAT, /* float */ ++ PA_DOUBLE, /* double */ ++ PA_LAST, ++}; ++ ++/* Flag bits that can be set in a type returned by `parse_printf_format'. */ ++# define PA_FLAG_MASK 0xff00 ++# define PA_FLAG_LONG_LONG (1 << 8) ++# define PA_FLAG_LONG_DOUBLE PA_FLAG_LONG_LONG ++# define PA_FLAG_LONG (1 << 9) ++# define PA_FLAG_SHORT (1 << 10) ++# define PA_FLAG_PTR (1 << 11) ++ ++# define parse_printf_format missing_parse_printf_format ++#endif ++ ++size_t missing_parse_printf_format(const char *fmt, size_t n, int *types); +diff --git a/src/libc/musl/meson.build b/src/libc/musl/meson.build +index 8d06d919ef..3e205e1eb1 100644 +--- a/src/libc/musl/meson.build ++++ b/src/libc/musl/meson.build +@@ -5,5 +5,6 @@ if get_option('libc') != 'musl' + endif + + libc_wrapper_sources += files( ++ 'printf.c', + 'stdio.c', + ) +diff --git a/src/libc/musl/printf.c b/src/libc/musl/printf.c +new file mode 100644 +index 0000000000..33c4acd27f +--- /dev/null ++++ b/src/libc/musl/printf.c +@@ -0,0 +1,276 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++/* Copyright 2014 Emil Renner Berthing */ ++ ++#include ++#include <../musl/printf.h> /* This file is also compiled when built with glibc. */ ++#include ++#include ++ ++static const char* consume_nonarg(const char *fmt) { ++ do { ++ if (*fmt == '\0') ++ return fmt; ++ } while (*fmt++ != '%'); ++ return fmt; ++} ++ ++static const char* consume_num(const char *fmt) { ++ for (;*fmt >= '0' && *fmt <= '9'; fmt++) ++ /* do nothing */; ++ return fmt; ++} ++ ++static const char* consume_argn(const char *fmt, size_t *arg) { ++ const char *p = fmt; ++ size_t val = 0; ++ ++ if (*p < '1' || *p > '9') ++ return fmt; ++ do { ++ val = 10*val + (*p++ - '0'); ++ } while (*p >= '0' && *p <= '9'); ++ ++ if (*p != '$') ++ return fmt; ++ *arg = val; ++ return p+1; ++} ++ ++static const char* consume_flags(const char *fmt) { ++ for (;;) ++ switch (*fmt) { ++ case '#': ++ case '0': ++ case '-': ++ case ' ': ++ case '+': ++ case '\'': ++ case 'I': ++ fmt++; ++ continue; ++ default: ++ return fmt; ++ } ++} ++ ++enum state { ++ BARE, ++ LPRE, ++ LLPRE, ++ HPRE, ++ HHPRE, ++ BIGLPRE, ++ ZTPRE, ++ JPRE, ++ STOP, ++}; ++ ++enum type { ++ NONE, ++ PTR, ++ STR, ++ WSTR, ++ INT, ++ LONG, ++ LLONG, ++ SHORT, ++ IMAX, ++ SIZET, ++ CHAR, ++ WCHAR, ++ PDIFF, ++ DBL, ++ LDBL, ++ NPTR, ++ MAXTYPE, ++}; ++ ++static const short pa_types[MAXTYPE] = { ++ [NONE] = PA_INT, ++ [PTR] = PA_POINTER, ++ [STR] = PA_STRING, ++ [WSTR] = PA_WSTRING, ++ [INT] = PA_INT, ++ [SHORT] = PA_INT | PA_FLAG_SHORT, ++ [LONG] = PA_INT | PA_FLAG_LONG, ++ [CHAR] = PA_CHAR, ++ [WCHAR] = PA_WCHAR, ++ [DBL] = PA_DOUBLE, ++ [LDBL] = PA_DOUBLE | PA_FLAG_LONG_DOUBLE, ++ [NPTR] = PA_FLAG_PTR, ++}; ++ ++static int state_to_pa_type(unsigned state) { ++ switch (state) { ++ case LLONG: ++#if LONG_MAX != LLONG_MAX ++ return PA_INT | PA_FLAG_LONG_LONG; ++#else ++ return PA_INT | PA_FLAG_LONG; ++#endif ++ ++ case IMAX: ++#if LONG_MAX != LLONG_MAX ++ if (sizeof(intmax_t) > sizeof(long)) ++ return PA_INT | PA_FLAG_LONG_LONG; ++#endif ++ if (sizeof(intmax_t) > sizeof(int)) ++ return PA_INT | PA_FLAG_LONG; ++ return PA_INT; ++ ++ case SIZET: ++#if LONG_MAX != LLONG_MAX ++ if (sizeof(size_t) > sizeof(long)) ++ return PA_INT | PA_FLAG_LONG_LONG; ++#endif ++ if (sizeof(size_t) > sizeof(int)) ++ return PA_INT | PA_FLAG_LONG; ++ return PA_INT; ++ default: ++ return pa_types[state]; ++ } ++} ++ ++#define S(x) [(x)-'A'] ++#define E(x) (STOP + (x)) ++ ++static const unsigned char states[]['z'-'A'+1] = { ++ { /* 0: bare types */ ++ S('d') = E(INT), S('i') = E(INT), ++ S('o') = E(INT), S('u') = E(INT), S('x') = E(INT), S('X') = E(INT), ++ S('e') = E(DBL), S('f') = E(DBL), S('g') = E(DBL), S('a') = E(DBL), ++ S('E') = E(DBL), S('F') = E(DBL), S('G') = E(DBL), S('A') = E(DBL), ++ S('c') = E(CHAR), S('C') = E(WCHAR), ++ S('s') = E(STR), S('S') = E(WSTR), S('p') = E(PTR), ++ S('n') = E(NPTR), ++ S('m') = E(NONE), ++ S('l') = LPRE, S('q') = LLPRE, S('h') = HPRE, S('L') = BIGLPRE, ++ S('z') = ZTPRE, S('Z') = ZTPRE, S('j') = JPRE, S('t') = ZTPRE, ++ }, ++ { /* 1: l-prefixed */ ++ S('d') = E(LONG), S('i') = E(LONG), ++ S('o') = E(LONG), S('u') = E(LONG), S('x') = E(LONG), S('X') = E(LONG), ++ S('e') = E(DBL), S('f') = E(DBL), S('g') = E(DBL), S('a') = E(DBL), ++ S('E') = E(DBL), S('F') = E(DBL), S('G') = E(DBL), S('A') = E(DBL), ++ S('c') = E(CHAR), S('s') = E(STR), ++ S('n') = E(NPTR), ++ S('l') = LLPRE, ++ }, ++ { /* 2: ll-prefixed */ ++ S('d') = E(LLONG), S('i') = E(LLONG), ++ S('o') = E(LLONG), S('u') = E(LLONG), S('x') = E(LLONG), S('X') = E(LLONG), ++ S('n') = E(NPTR), ++ }, ++ { /* 3: h-prefixed */ ++ S('d') = E(SHORT), S('i') = E(SHORT), ++ S('o') = E(SHORT), S('u') = E(SHORT), S('x') = E(SHORT), S('X') = E(SHORT), ++ S('n') = E(NPTR), ++ S('h') = HHPRE, ++ }, ++ { /* 4: hh-prefixed */ ++ S('d') = E(CHAR), S('i') = E(CHAR), ++ S('o') = E(CHAR), S('u') = E(CHAR), S('x') = E(CHAR), S('X') = E(CHAR), ++ S('n') = E(NPTR), ++ }, ++ { /* 5: L-prefixed */ ++ S('e') = E(LDBL), S('f') = E(LDBL), S('g') = E(LDBL), S('a') = E(LDBL), ++ S('E') = E(LDBL), S('F') = E(LDBL), S('G') = E(LDBL), S('A') = E(LDBL), ++ }, ++ { /* 6: z- or t-prefixed (assumed to be same size) */ ++ S('d') = E(SIZET), S('i') = E(SIZET), ++ S('o') = E(SIZET), S('u') = E(SIZET), S('x') = E(SIZET), S('X') = E(SIZET), ++ S('n') = E(NPTR), ++ }, ++ { /* 7: j-prefixed */ ++ S('d') = E(IMAX), S('i') = E(IMAX), ++ S('o') = E(IMAX), S('u') = E(IMAX), S('x') = E(IMAX), S('X') = E(IMAX), ++ S('n') = E(NPTR), ++ }, ++}; ++ ++size_t missing_parse_printf_format(const char *fmt, size_t n, int *types) { ++ size_t i = 0; ++ size_t last = 0; ++ ++ memset(types, 0, n); ++ ++ for (;;) { ++ size_t arg; ++ ++ fmt = consume_nonarg(fmt); ++ if (*fmt == '\0') ++ break; ++ if (*fmt == '%') { ++ fmt++; ++ continue; ++ } ++ arg = 0; ++ fmt = consume_argn(fmt, &arg); ++ /* flags */ ++ fmt = consume_flags(fmt); ++ /* width */ ++ if (*fmt == '*') { ++ size_t warg = 0; ++ fmt = consume_argn(fmt+1, &warg); ++ if (warg == 0) ++ warg = ++i; ++ if (warg > last) ++ last = warg; ++ if (warg <= n && types[warg-1] == NONE) ++ types[warg-1] = INT; ++ } else ++ fmt = consume_num(fmt); ++ /* precision */ ++ if (*fmt == '.') { ++ fmt++; ++ if (*fmt == '*') { ++ size_t parg = 0; ++ fmt = consume_argn(fmt+1, &parg); ++ if (parg == 0) ++ parg = ++i; ++ if (parg > last) ++ last = parg; ++ if (parg <= n && types[parg-1] == NONE) ++ types[parg-1] = INT; ++ } else { ++ if (*fmt == '-') ++ fmt++; ++ fmt = consume_num(fmt); ++ } ++ } ++ /* length modifier and conversion specifier */ ++ unsigned state = BARE; ++ for (;;) { ++ unsigned char c = *fmt; ++ ++ if (c == '\0') ++ break; ++ ++ fmt++; ++ ++ if (c < 'A' || c > 'z') ++ break; ++ ++ state = states[state]S(c); ++ if (state == 0 || state >= STOP) ++ break; ++ } ++ ++ if (state <= STOP) /* %m or invalid format */ ++ continue; ++ ++ if (arg == 0) ++ arg = ++i; ++ if (arg > last) ++ last = arg; ++ if (arg <= n) ++ types[arg-1] = state - STOP; ++ } ++ ++ if (last > n) ++ last = n; ++ for (i = 0; i < last; i++) ++ types[i] = state_to_pa_type(types[i]); ++ ++ return last; ++} +diff --git a/src/test/meson.build b/src/test/meson.build +index da04b82d47..1dd61effc0 100644 +--- a/src/test/meson.build ++++ b/src/test/meson.build +@@ -403,6 +403,12 @@ executables += [ + 'dependencies' : libacl, + 'type' : 'manual', + }, ++ test_template + { ++ 'sources' : files( ++ 'test-printf.c', ++ '../libc/musl/printf.c' ++ ), ++ }, + test_template + { + 'sources' : files('test-process-util.c'), + 'dependencies' : threads, +diff --git a/src/test/test-printf.c b/src/test/test-printf.c +new file mode 100644 +index 0000000000..28497e902d +--- /dev/null ++++ b/src/test/test-printf.c +@@ -0,0 +1,67 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++ ++#include <../musl/printf.h> ++ ++#include "memory-util.h" ++#include "strv.h" ++#include "tests.h" ++ ++static void test_parse_printf_format_one(const char *fmt) { ++ int arg_types_x[128] = {}, arg_types_y[128] = {}; ++ size_t x, y; ++ ++ log_debug("/* %s(%s) */", __func__, fmt); ++ ++ x = parse_printf_format(fmt, ELEMENTSOF(arg_types_x), arg_types_x); ++ y = missing_parse_printf_format(fmt, ELEMENTSOF(arg_types_y), arg_types_y); ++ ++ for (size_t i = 0; i < x; i++) ++ log_debug("x[%zu]=%i", i, arg_types_x[i]); ++ for (size_t i = 0; i < y; i++) ++ log_debug("y[%zu]=%i", i, arg_types_y[i]); ++ ++ ASSERT_EQ(memcmp_nn(arg_types_x, x * sizeof(int), arg_types_y, y * sizeof(int)), 0); ++} ++ ++TEST(parse_printf_format) { ++ FOREACH_STRING(s, "d", "i", "o", "u", "x", "X", "n") ++ FOREACH_STRING(p, "", "hh", "h", "l", "ll", "j", "z", "Z", "t") { ++ _cleanup_free_ char *fmt = NULL; ++ ++ ASSERT_NOT_NULL(fmt = strjoin("%", p, s)); ++ test_parse_printf_format_one(fmt); ++ } ++ ++ FOREACH_STRING(s, "e", "E", "f", "F", "g", "G", "a", "A") ++ FOREACH_STRING(p, "", "L") { ++ _cleanup_free_ char *fmt = NULL; ++ ++ ASSERT_NOT_NULL(fmt = strjoin("%", p, s)); ++ test_parse_printf_format_one(fmt); ++ } ++ ++ FOREACH_STRING(s, "c", "s") ++ FOREACH_STRING(p, "", "l") { ++ _cleanup_free_ char *fmt = NULL; ++ ++ ASSERT_NOT_NULL(fmt = strjoin("%", p, s)); ++ test_parse_printf_format_one(fmt); ++ } ++ ++ FOREACH_STRING(s, "C", "S", "p", "m", "%") { ++ _cleanup_free_ char *fmt = NULL; ++ ++ ASSERT_NOT_NULL(fmt = strjoin("%", s)); ++ test_parse_printf_format_one(fmt); ++ } ++ ++ test_parse_printf_format_one("asfhghejmlahpgakdmsalc"); ++ test_parse_printf_format_one("%d%i%o%u%x%X"); ++ test_parse_printf_format_one("%e%E%f%F%g%G%a%A"); ++ test_parse_printf_format_one("%c%s%C%S%p%n%m%%"); ++ test_parse_printf_format_one("%03d%-05d%+i%hhu%hu%hx%lx"); ++ test_parse_printf_format_one("%llx%x%LE%ji%zi%zu%zi%zu%Zi%Zu%tu"); ++ test_parse_printf_format_one("%l"); ++} ++ ++DEFINE_TEST_MAIN(LOG_DEBUG); +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0010-musl-introduce-GNU-specific-version-of-strerror_r.patch b/pkgs/os-specific/linux/systemd/musl/0010-musl-introduce-GNU-specific-version-of-strerror_r.patch new file mode 100644 index 000000000000..dd09c69ce95f --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0010-musl-introduce-GNU-specific-version-of-strerror_r.patch @@ -0,0 +1,74 @@ +From 8fd471ff028cd3d5df5ee944fdbcefc7e77abc3d Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 23 Jan 2023 23:39:46 -0800 +Subject: [PATCH 10/30] musl: introduce GNU specific version of strerror_r() + +musl provides XSI compliant strerror_r(), and it is slightly different +from the one by glibc. +Let's introduce a tiny wrapper to convert XSI strerror_r() to GNU one. +--- + src/include/musl/string.h | 7 +++++++ + src/libc/musl/meson.build | 1 + + src/libc/musl/string.c | 26 ++++++++++++++++++++++++++ + 3 files changed, 34 insertions(+) + create mode 100644 src/include/musl/string.h + create mode 100644 src/libc/musl/string.c + +diff --git a/src/include/musl/string.h b/src/include/musl/string.h +new file mode 100644 +index 0000000000..cc3da63012 +--- /dev/null ++++ b/src/include/musl/string.h +@@ -0,0 +1,7 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++#pragma once ++ ++#include_next ++ ++char* strerror_r_gnu(int errnum, char *buf, size_t buflen); ++#define strerror_r strerror_r_gnu +diff --git a/src/libc/musl/meson.build b/src/libc/musl/meson.build +index 3e205e1eb1..a64f292081 100644 +--- a/src/libc/musl/meson.build ++++ b/src/libc/musl/meson.build +@@ -7,4 +7,5 @@ endif + libc_wrapper_sources += files( + 'printf.c', + 'stdio.c', ++ 'string.c', + ) +diff --git a/src/libc/musl/string.c b/src/libc/musl/string.c +new file mode 100644 +index 0000000000..c2a9f4a169 +--- /dev/null ++++ b/src/libc/musl/string.c +@@ -0,0 +1,26 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++ ++#include ++#include ++#include ++ ++/* The header errno.h overrides strerror_r with strerror_r_gnu, hence we need to undef it here. */ ++#undef strerror_r ++ ++char* strerror_r_gnu(int errnum, char *buf, size_t buflen) { ++ int saved_errno = errno; ++ const char *msg = strerror(errnum); ++ size_t l = strlen(msg); ++ if (l >= buflen) { ++ if (buflen > 0) { ++ if (buflen > 1) ++ memcpy(buf, msg, buflen - 1); ++ buf[buflen - 1] = '\0'; ++ } ++ errno = ERANGE; ++ } else { ++ memcpy(buf, msg, l + 1); ++ errno = saved_errno; ++ } ++ return buf; ++} +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0011-musl-make-strptime-accept-z.patch b/pkgs/os-specific/linux/systemd/musl/0011-musl-make-strptime-accept-z.patch new file mode 100644 index 000000000000..bbffbc8c8a53 --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0011-musl-make-strptime-accept-z.patch @@ -0,0 +1,137 @@ +From bf670a5af2a78f81ee9396b5b5da49b2df07fcfd Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 9 Sep 2025 08:31:22 +0900 +Subject: [PATCH 11/30] musl: make strptime() accept "%z" + +musl v1.2.5 does not support %z specifier in strptime(). Since +https://github.com/kraj/musl/commit/fced99e93daeefb0192fd16304f978d4401d1d77 +%z is supported, but it only supports strict RFC-822/ISO 8601 format, +that is, 4 digits with sign (e.g. +0900 or -1400), but does not support +extended format: 2 digits or colon separated 4 digits (e.g. +09 or -14:00). +Let's add fallback logic to make it support the extended timezone spec. +--- + src/include/musl/time.h | 7 ++++ + src/libc/musl/meson.build | 1 + + src/libc/musl/time.c | 86 +++++++++++++++++++++++++++++++++++++++ + 3 files changed, 94 insertions(+) + create mode 100644 src/include/musl/time.h + create mode 100644 src/libc/musl/time.c + +diff --git a/src/include/musl/time.h b/src/include/musl/time.h +new file mode 100644 +index 0000000000..349f9a3577 +--- /dev/null ++++ b/src/include/musl/time.h +@@ -0,0 +1,7 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++#pragma once ++ ++#include_next ++ ++char* strptime_fallback(const char *s, const char *format, struct tm *tm); ++#define strptime strptime_fallback +diff --git a/src/libc/musl/meson.build b/src/libc/musl/meson.build +index a64f292081..5fb590e868 100644 +--- a/src/libc/musl/meson.build ++++ b/src/libc/musl/meson.build +@@ -8,4 +8,5 @@ libc_wrapper_sources += files( + 'printf.c', + 'stdio.c', + 'string.c', ++ 'time.c', + ) +diff --git a/src/libc/musl/time.c b/src/libc/musl/time.c +new file mode 100644 +index 0000000000..12108166b2 +--- /dev/null ++++ b/src/libc/musl/time.c +@@ -0,0 +1,86 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++ ++#include ++#include ++#include ++ ++/* The header time.h overrides strptime with strerror_fallback, hence we need to undef it here. */ ++#undef strptime ++ ++char* strptime_fallback(const char *s, const char *format, struct tm *tm) { ++ /* First try native strptime() as is, and if it succeeds, return the resuit as is. */ ++ char *k = strptime(s, format, tm); ++ if (k) ++ return k; ++ ++ /* Check inputs for safety. */ ++ if (!s || !format || !tm) ++ return NULL; ++ ++ /* We only fallback if the format is exactly "%z". */ ++ if (strcmp(format, "%z") != 0) ++ return NULL; ++ ++ /* In the below, we parse timezone specifiction compatible with RFC-822/ISO 8601 and its extensions ++ * (e.g. +06, +0900, or -03:00). */ ++ ++ bool positive; ++ switch (*s) { ++ case '+': ++ positive = true; ++ break; ++ case '-': ++ positive = false; ++ break; ++ default: ++ return NULL; ++ } ++ ++ s++; ++ ++ if (*s < '0' || *s > '9') ++ return NULL; ++ long t = (*s - '0') * 10 * 60 * 60; ++ ++ s++; ++ ++ if (*s < '0' || *s > '9') ++ return NULL; ++ t += (*s - '0') * 60 * 60; ++ ++ s++; ++ ++ if (*s == '\0') /* 2 digits case */ ++ goto finalize; ++ ++ if (*s == ':') /* skip colon */ ++ s++; ++ ++ if (*s < '0' || *s >= '6') /* refuse minutes equal to or larger than 60 */ ++ return NULL; ++ t += (*s - '0') * 10 * 60; ++ ++ s++; ++ ++ if (*s < '0' || *s > '9') ++ return NULL; ++ t += (*s - '0') * 60; ++ ++ s++; ++ ++ if (*s != '\0') ++ return NULL; ++ ++finalize: ++ if (t > 24 * 60 * 60) /* refuse larger than 24 hours */ ++ return NULL; ++ ++ if (!positive) ++ t = -t; ++ ++ *tm = (struct tm) { ++ .tm_gmtoff = t, ++ }; ++ ++ return (char*) s; ++} +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0012-musl-make-strtoll-accept-strings-start-with-dot.patch b/pkgs/os-specific/linux/systemd/musl/0012-musl-make-strtoll-accept-strings-start-with-dot.patch new file mode 100644 index 000000000000..2833a951a3d2 --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0012-musl-make-strtoll-accept-strings-start-with-dot.patch @@ -0,0 +1,69 @@ +From d97f38a2ffd0aca2a1d7b8a5bbbd1994e786c7bc Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 9 Sep 2025 09:10:44 +0900 +Subject: [PATCH 12/30] musl: make strtoll() accept strings start with dot + +glibc accepts strings start with '.' and returns 0, but musl refuses +them. Let's accept them, as our code assumes the function accept such +strings. +--- + src/include/musl/stdlib.h | 7 +++++++ + src/libc/musl/meson.build | 1 + + src/libc/musl/stdlib.c | 19 +++++++++++++++++++ + 3 files changed, 27 insertions(+) + create mode 100644 src/include/musl/stdlib.h + create mode 100644 src/libc/musl/stdlib.c + +diff --git a/src/include/musl/stdlib.h b/src/include/musl/stdlib.h +new file mode 100644 +index 0000000000..ecfd6ccb43 +--- /dev/null ++++ b/src/include/musl/stdlib.h +@@ -0,0 +1,7 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++#pragma once ++ ++#include_next ++ ++long long strtoll_fallback(const char *nptr, char **endptr, int base); ++#define strtoll strtoll_fallback +diff --git a/src/libc/musl/meson.build b/src/libc/musl/meson.build +index 5fb590e868..ea09af9fa5 100644 +--- a/src/libc/musl/meson.build ++++ b/src/libc/musl/meson.build +@@ -7,6 +7,7 @@ endif + libc_wrapper_sources += files( + 'printf.c', + 'stdio.c', ++ 'stdlib.c', + 'string.c', + 'time.c', + ) +diff --git a/src/libc/musl/stdlib.c b/src/libc/musl/stdlib.c +new file mode 100644 +index 0000000000..d4e2714217 +--- /dev/null ++++ b/src/libc/musl/stdlib.c +@@ -0,0 +1,19 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++ ++#include ++ ++/* The header stdlib.h overrides strtoll with strtoll_fallback, hence we need to undef it here. */ ++#undef strtoll ++ ++long long strtoll_fallback(const char *nptr, char **endptr, int base) { ++ /* glibc returns 0 if the first character is '.' without error, but musl returns as an error. ++ * As our code assumes the glibc behavior, let's accept string starts with '.'. */ ++ if (nptr && *nptr == '.') { ++ if (endptr) ++ *endptr = (char*) nptr; ++ return 0; ++ } ++ ++ /* Otherwise, use the native strtoll(). */ ++ return strtoll(nptr, endptr, base); ++} +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0013-musl-introduce-strerrorname_np.patch b/pkgs/os-specific/linux/systemd/musl/0013-musl-introduce-strerrorname_np.patch new file mode 100644 index 000000000000..5203caf09f3d --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0013-musl-introduce-strerrorname_np.patch @@ -0,0 +1,86 @@ +From 96b9f85d08be6f60768d1ebcdaf77e8e5dafebf6 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Fri, 5 Sep 2025 09:20:50 +0900 +Subject: [PATCH 13/30] musl: introduce strerrorname_np() + +musl does not provide strerrorname_np(). Thus, we need to implement it. +--- + src/include/musl/string.h | 2 ++ + src/libc/musl/generate-strerrorname_np.sh | 39 +++++++++++++++++++++++ + src/libc/musl/meson.build | 7 ++++ + 3 files changed, 48 insertions(+) + create mode 100755 src/libc/musl/generate-strerrorname_np.sh + +diff --git a/src/include/musl/string.h b/src/include/musl/string.h +index cc3da63012..7317c8dea0 100644 +--- a/src/include/musl/string.h ++++ b/src/include/musl/string.h +@@ -5,3 +5,5 @@ + + char* strerror_r_gnu(int errnum, char *buf, size_t buflen); + #define strerror_r strerror_r_gnu ++ ++const char* strerrorname_np(int errnum); +diff --git a/src/libc/musl/generate-strerrorname_np.sh b/src/libc/musl/generate-strerrorname_np.sh +new file mode 100755 +index 0000000000..0e0fb8b187 +--- /dev/null ++++ b/src/libc/musl/generate-strerrorname_np.sh +@@ -0,0 +1,39 @@ ++#!/usr/bin/env bash ++# SPDX-License-Identifier: LGPL-2.1-or-later ++set -eu ++set -o pipefail ++ ++# This is based on src/basic/generate-errno-list.sh. ++ ++# ECANCELLED, EDEADLOCK, ENOTSUP, EREFUSED, and EWOULDBLOCK are defined as aliases of ++# ECANCELED, EDEADLK, EOPNOTSUPP, ECONNREFUSED, and EAGAIN, respectively. Let's drop them. ++ ++CC=${1:?} ++shift ++ ++cat <<'EOF' ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++ ++#include ++#include ++#include ++ ++static const char * const errno_table[] = { ++EOF ++ ++$CC -dM -include errno.h - = sizeof(errno_table) / sizeof(errno_table[0])) ++ return NULL; ++ return errno_table[errnum]; ++} ++EOF +diff --git a/src/libc/musl/meson.build b/src/libc/musl/meson.build +index ea09af9fa5..1cb6bf28c1 100644 +--- a/src/libc/musl/meson.build ++++ b/src/libc/musl/meson.build +@@ -11,3 +11,10 @@ libc_wrapper_sources += files( + 'string.c', + 'time.c', + ) ++ ++generator= files('generate-strerrorname_np.sh') ++libc_wrapper_sources += custom_target( ++ input : generator, ++ output : 'strerrorname_np.c', ++ command : [env, 'bash', generator, cpp], ++ capture : true) +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0014-musl-introduce-dummy-functions-for-mallinfo-malloc_i.patch b/pkgs/os-specific/linux/systemd/musl/0014-musl-introduce-dummy-functions-for-mallinfo-malloc_i.patch new file mode 100644 index 000000000000..41a8c6b4e2aa --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0014-musl-introduce-dummy-functions-for-mallinfo-malloc_i.patch @@ -0,0 +1,60 @@ +From 331e8b9158f1bb3b4ff5db2c17a16a60640e0c12 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 10 Jun 2025 00:40:59 +0900 +Subject: [PATCH 14/30] musl: introduce dummy functions for mallinfo(), + malloc_info(), and malloc_trim() + +These functions are not provided by musl. +--- + src/include/musl/malloc.h | 39 +++++++++++++++++++++++++++++++++++++++ + 1 file changed, 39 insertions(+) + create mode 100644 src/include/musl/malloc.h + +diff --git a/src/include/musl/malloc.h b/src/include/musl/malloc.h +new file mode 100644 +index 0000000000..9d15d4bf91 +--- /dev/null ++++ b/src/include/musl/malloc.h +@@ -0,0 +1,39 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++#pragma once ++ ++#include ++#include ++ ++/* struct mallinfo2 will be defined and struct mallinfo is converted to struct mallinfo2 in ++ * override/malloc.h. Hence, here we define struct mallinfo. */ ++ ++struct mallinfo { ++ int arena; /* non-mmapped space allocated from system */ ++ int ordblks; /* number of free chunks */ ++ int smblks; /* number of fastbin blocks */ ++ int hblks; /* number of mmapped regions */ ++ int hblkhd; /* space in mmapped regions */ ++ int usmblks; /* always 0, preserved for backwards compatibility */ ++ int fsmblks; /* space available in freed fastbin blocks */ ++ int uordblks; /* total allocated space */ ++ int fordblks; /* total free space */ ++ int keepcost; /* top-most, releasable (via malloc_trim) space */ ++}; ++ ++static inline struct mallinfo mallinfo(void) { ++ return (struct mallinfo) {}; ++} ++ ++static inline int malloc_info(int options, FILE *stream) { ++ if (options != 0) ++ errno = EINVAL; ++ else ++ errno = EOPNOTSUPP; ++ return -1; ++} ++ ++static inline int malloc_trim(size_t pad) { ++ return 0; ++} ++ ++#include_next +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0015-musl-introduce-dummy-function-for-gnu_get_libc_versi.patch b/pkgs/os-specific/linux/systemd/musl/0015-musl-introduce-dummy-function-for-gnu_get_libc_versi.patch new file mode 100644 index 000000000000..4ec71eb7e25a --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0015-musl-introduce-dummy-function-for-gnu_get_libc_versi.patch @@ -0,0 +1,86 @@ +From 5067b1cfac07dbb50d7813b2a900b1b6f8e6e4bd Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Sun, 8 Jun 2025 10:07:54 +0900 +Subject: [PATCH 15/30] musl: introduce dummy function for + gnu_get_libc_version() + +As the header gnu/libc-version.h and gnu_get_libc_version() function +are glibc specific, and musl does not provide them. +--- + src/include/musl/gnu/libc-version.h | 8 ++++++++ + src/shared/condition.c | 9 +++++++-- + src/test/test-condition.c | 8 +++++--- + 3 files changed, 20 insertions(+), 5 deletions(-) + create mode 100644 src/include/musl/gnu/libc-version.h + +diff --git a/src/include/musl/gnu/libc-version.h b/src/include/musl/gnu/libc-version.h +new file mode 100644 +index 0000000000..8ad0ed16b8 +--- /dev/null ++++ b/src/include/musl/gnu/libc-version.h +@@ -0,0 +1,8 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++#pragma once ++ ++#include ++ ++static inline const char* gnu_get_libc_version(void) { ++ return NULL; ++} +diff --git a/src/shared/condition.c b/src/shared/condition.c +index b09eff1bfb..b27b24aba7 100644 +--- a/src/shared/condition.c ++++ b/src/shared/condition.c +@@ -255,8 +255,13 @@ static int condition_test_version(Condition *c, char **env) { + if (streq(word, "systemd")) + return condition_test_version_cmp(p, STRINGIFY(PROJECT_VERSION)); + +- if (streq(word, "glibc")) +- return condition_test_version_cmp(p, gnu_get_libc_version()); ++ if (streq(word, "glibc")) { ++ const char *v = gnu_get_libc_version(); ++ if (!v) ++ return false; /* built with musl */ ++ ++ return condition_test_version_cmp(p, v); ++ } + + /* if no predicate has been set, default to "kernel" and use the whole parameter as condition */ + if (!streq(word, "kernel")) +diff --git a/src/test/test-condition.c b/src/test/test-condition.c +index 11b3a42418..898c111ffe 100644 +--- a/src/test/test-condition.c ++++ b/src/test/test-condition.c +@@ -669,8 +669,10 @@ TEST(condition_test_version) { + condition_free(condition); + + /* Test glibc version */ ++ bool expected = !!gnu_get_libc_version(); ++ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "glibc > 1", false, false))); +- ASSERT_OK_POSITIVE(condition_test(condition, environ)); ++ ASSERT_OK_EQ(condition_test(condition, environ), expected); + condition_free(condition); + + ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "glibc < 2", false, false))); +@@ -678,7 +680,7 @@ TEST(condition_test_version) { + condition_free(condition); + + ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "glibc < 9999", false, false))); +- ASSERT_OK_POSITIVE(condition_test(condition, environ)); ++ ASSERT_OK_EQ(condition_test(condition, environ), expected); + condition_free(condition); + + ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "glibc > 9999", false, false))); +@@ -688,7 +690,7 @@ TEST(condition_test_version) { + v = strjoina("glibc = ", gnu_get_libc_version()); + + ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, v, false, false))); +- ASSERT_OK_POSITIVE(condition_test(condition, environ)); ++ ASSERT_OK_EQ(condition_test(condition, environ), expected); + condition_free(condition); + + v = strjoina("glibc != ", gnu_get_libc_version()); +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0016-musl-define-__THROW-when-not-defined.patch b/pkgs/os-specific/linux/systemd/musl/0016-musl-define-__THROW-when-not-defined.patch new file mode 100644 index 000000000000..16fc6570320a --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0016-musl-define-__THROW-when-not-defined.patch @@ -0,0 +1,30 @@ +From 65a6110f75e3bcf980aa4138ad18c06f351b78f5 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jun 2025 14:00:55 +0900 +Subject: [PATCH 16/30] musl: define __THROW when not defined + +__THROW is internally used by glibc headers, hence our implementation of +net/if.h and sys/mount.h also use it. However, musl does not provide +the macro. Let's define it when not defined. +--- + src/include/musl/features.h | 8 ++++++++ + 1 file changed, 8 insertions(+) + create mode 100644 src/include/musl/features.h + +diff --git a/src/include/musl/features.h b/src/include/musl/features.h +new file mode 100644 +index 0000000000..bd6d00a9d4 +--- /dev/null ++++ b/src/include/musl/features.h +@@ -0,0 +1,8 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++#pragma once ++ ++#include_next ++ ++#ifndef __THROW ++#define __THROW ++#endif +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0017-musl-replace-sys-prctl.h-with-our-own-implementation.patch b/pkgs/os-specific/linux/systemd/musl/0017-musl-replace-sys-prctl.h-with-our-own-implementation.patch new file mode 100644 index 000000000000..4d733f000d47 --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0017-musl-replace-sys-prctl.h-with-our-own-implementation.patch @@ -0,0 +1,29 @@ +From 75aa46c9399664d2a1cdabefcad2899ebfabc741 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 23 Jun 2025 16:00:21 +0900 +Subject: [PATCH 17/30] musl: replace sys/prctl.h with our own implementation + +To avoid conflicts between musl's sys/prctl.h and linux/prctl.h. +--- + src/include/musl/sys/prctl.h | 9 +++++++++ + 1 file changed, 9 insertions(+) + create mode 100644 src/include/musl/sys/prctl.h + +diff --git a/src/include/musl/sys/prctl.h b/src/include/musl/sys/prctl.h +new file mode 100644 +index 0000000000..2c830d2649 +--- /dev/null ++++ b/src/include/musl/sys/prctl.h +@@ -0,0 +1,9 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++#pragma once ++ ++/* This is for avoiding conflicts between musl's sys/prctl.h and linux/prctl.h. */ ++ ++#include ++#include /* IWYU pragma: export */ ++ ++int prctl(int, ...); +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0018-musl-replace-netinet-if_ether.h-with-our-own-impleme.patch b/pkgs/os-specific/linux/systemd/musl/0018-musl-replace-netinet-if_ether.h-with-our-own-impleme.patch new file mode 100644 index 000000000000..c0044956ad0f --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0018-musl-replace-netinet-if_ether.h-with-our-own-impleme.patch @@ -0,0 +1,72 @@ +From 6e77a74ca91e689a43651d9eeb5f8f40893eed76 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 23 Jun 2025 16:08:37 +0900 +Subject: [PATCH 18/30] musl: replace netinet/if_ether.h with our own + implementation + +musl's netinet/if_ether.h conflicts with linux/if_ether.h. +The reimplementation is mostly equivalent with what glibc does. + +This also unset __UAPI_DEF_ETHHDR before including linux/if_ether.h, +otherwise struct ethhdr may not be defined by the header when it is +defined. +--- + src/include/musl/linux/if_ether.h | 5 +++++ + src/include/musl/netinet/if_ether.h | 33 +++++++++++++++++++++++++++++ + 2 files changed, 38 insertions(+) + create mode 100644 src/include/musl/linux/if_ether.h + create mode 100644 src/include/musl/netinet/if_ether.h + +diff --git a/src/include/musl/linux/if_ether.h b/src/include/musl/linux/if_ether.h +new file mode 100644 +index 0000000000..e28cd4a014 +--- /dev/null ++++ b/src/include/musl/linux/if_ether.h +@@ -0,0 +1,5 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++#pragma once ++ ++#undef __UAPI_DEF_ETHHDR ++#include_next +diff --git a/src/include/musl/netinet/if_ether.h b/src/include/musl/netinet/if_ether.h +new file mode 100644 +index 0000000000..62f4ac03b3 +--- /dev/null ++++ b/src/include/musl/netinet/if_ether.h +@@ -0,0 +1,33 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++#pragma once ++ ++/* glibc's netinet/if_ether.h does the following: ++ * - include linux/if_ether.h, net/ethernet.h, and net/if_arp.h, ++ * - define struct ether_arp, and relevant macros, ++ * - define ETHER_MAP_IP_MULTICAST() macro (currently we do not use it). ++ * However, musl's netinet/if_ether.h conflicts with linux/if_ether.h. ++ * Let's use the same way that glibc uses. */ ++ ++#include /* IWYU pragma: export */ ++#include /* IWYU pragma: export */ ++#include /* IWYU pragma: export */ ++ ++/* ++ * Ethernet Address Resolution Protocol. ++ * ++ * See RFC 826 for protocol description. Structure below is adapted ++ * to resolving internet addresses. Field names used correspond to ++ * RFC 826. ++ */ ++struct ether_arp { ++ struct arphdr ea_hdr; /* fixed-size header */ ++ uint8_t arp_sha[ETH_ALEN]; /* sender hardware address */ ++ uint8_t arp_spa[4]; /* sender protocol address */ ++ uint8_t arp_tha[ETH_ALEN]; /* target hardware address */ ++ uint8_t arp_tpa[4]; /* target protocol address */ ++}; ++#define arp_hrd ea_hdr.ar_hrd ++#define arp_pro ea_hdr.ar_pro ++#define arp_hln ea_hdr.ar_hln ++#define arp_pln ea_hdr.ar_pln ++#define arp_op ea_hdr.ar_op +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0019-musl-add-missing-FTW_CONTINUE-macro.patch b/pkgs/os-specific/linux/systemd/musl/0019-musl-add-missing-FTW_CONTINUE-macro.patch new file mode 100644 index 000000000000..24855be78c21 --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0019-musl-add-missing-FTW_CONTINUE-macro.patch @@ -0,0 +1,35 @@ +From 7d4b4e49729f7c7d5164ba2c5040ed522d68ee7a Mon Sep 17 00:00:00 2001 +From: Chen Qi +Date: Mon, 25 Feb 2019 15:00:06 +0800 +Subject: [PATCH 19/30] musl: add missing FTW_CONTINUE macro +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This is to avoid build failures like below for musl. + + test-recurse-dir.c:23:24: error: ‘FTW_CONTINUE’ undeclared + +Co-authored-by: Yu Watanabe +--- + src/include/musl/ftw.h | 8 ++++++++ + 1 file changed, 8 insertions(+) + create mode 100644 src/include/musl/ftw.h + +diff --git a/src/include/musl/ftw.h b/src/include/musl/ftw.h +new file mode 100644 +index 0000000000..fd153734d0 +--- /dev/null ++++ b/src/include/musl/ftw.h +@@ -0,0 +1,8 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++#pragma once ++ ++#include_next ++ ++#ifndef FTW_CONTINUE ++#define FTW_CONTINUE 0 ++#endif +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0020-musl-add-several-missing-statx-macros.patch b/pkgs/os-specific/linux/systemd/musl/0020-musl-add-several-missing-statx-macros.patch new file mode 100644 index 000000000000..6dd1589019b0 --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0020-musl-add-several-missing-statx-macros.patch @@ -0,0 +1,90 @@ +From c306693ac26aef3d5fb06fa0981d911bda9b93d7 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Sun, 22 Jun 2025 00:38:58 +0900 +Subject: [PATCH 20/30] musl: add several missing statx macros + +glibc's sys/stat.h includes linux/stat.h, and we have copy of it from +the latest kernel, hence all new flags are always defined. +However, musl's sys/stat.h does not include linux/stat.h, and moreover, +they conflict with each other, hence we cannot include both header +simultaneously. Let's define missing macros to support musl. +--- + src/include/musl/sys/stat.h | 66 +++++++++++++++++++++++++++++++++++++ + 1 file changed, 66 insertions(+) + create mode 100644 src/include/musl/sys/stat.h + +diff --git a/src/include/musl/sys/stat.h b/src/include/musl/sys/stat.h +new file mode 100644 +index 0000000000..610dd3e699 +--- /dev/null ++++ b/src/include/musl/sys/stat.h +@@ -0,0 +1,66 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++#pragma once ++ ++#include_next ++ ++#include ++#include ++ ++/* musl's sys/stat.h does not include linux/stat.h, and unfortunately they conflict with each other. ++ * Hence, some relatively new macros need to be explicitly defined here. */ ++ ++/* Before 23ab04a8630225371455d5f4538fd078665bb646, statx.stx_mnt_id is not defined. */ ++#ifndef STATX_MNT_ID ++static_assert(offsetof(struct statx, __pad1) == offsetof(struct statx, stx_dev_minor) + sizeof(uint32_t), ""); ++#define stx_mnt_id __pad1[0] ++#endif ++ ++#ifndef STATX_MNT_ID ++#define STATX_MNT_ID 0x00001000U ++#endif ++#ifndef STATX_DIOALIGN ++#define STATX_DIOALIGN 0x00002000U ++#endif ++#ifndef STATX_MNT_ID_UNIQUE ++#define STATX_MNT_ID_UNIQUE 0x00004000U ++#endif ++#ifndef STATX_SUBVOL ++#define STATX_SUBVOL 0x00008000U ++#endif ++#ifndef STATX_WRITE_ATOMIC ++#define STATX_WRITE_ATOMIC 0x00010000U ++#endif ++#ifndef STATX_DIO_READ_ALIGN ++#define STATX_DIO_READ_ALIGN 0x00020000U ++#endif ++ ++#ifndef STATX_ATTR_COMPRESSED ++#define STATX_ATTR_COMPRESSED 0x00000004 ++#endif ++#ifndef STATX_ATTR_IMMUTABLE ++#define STATX_ATTR_IMMUTABLE 0x00000010 ++#endif ++#ifndef STATX_ATTR_APPEND ++#define STATX_ATTR_APPEND 0x00000020 ++#endif ++#ifndef STATX_ATTR_NODUMP ++#define STATX_ATTR_NODUMP 0x00000040 ++#endif ++#ifndef STATX_ATTR_ENCRYPTED ++#define STATX_ATTR_ENCRYPTED 0x00000800 ++#endif ++#ifndef STATX_ATTR_AUTOMOUNT ++#define STATX_ATTR_AUTOMOUNT 0x00001000 ++#endif ++#ifndef STATX_ATTR_MOUNT_ROOT ++#define STATX_ATTR_MOUNT_ROOT 0x00002000 ++#endif ++#ifndef STATX_ATTR_VERITY ++#define STATX_ATTR_VERITY 0x00100000 ++#endif ++#ifndef STATX_ATTR_DAX ++#define STATX_ATTR_DAX 0x00200000 ++#endif ++#ifndef STATX_ATTR_WRITE_ATOMIC ++#define STATX_ATTR_WRITE_ATOMIC 0x00400000 ++#endif +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0021-musl-avoid-conflict-between-fcntl.h-and-our-forward..patch b/pkgs/os-specific/linux/systemd/musl/0021-musl-avoid-conflict-between-fcntl.h-and-our-forward..patch new file mode 100644 index 000000000000..a024e58f2d25 --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0021-musl-avoid-conflict-between-fcntl.h-and-our-forward..patch @@ -0,0 +1,36 @@ +From a8209a45e73f4c8b1f4e4e174d2d5fbc8a9ac4df Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 22 Jul 2025 03:37:37 +0900 +Subject: [PATCH 21/30] musl: avoid conflict between fcntl.h and our forward.h + +glibc defines AT_FDCWD as -100, but musl defines it as (-100). +In forward.h, we also define AT_FDCWD as -100, hence musl's fcntl.h +conflicts with forward.h. This is for avoiding the conflict. +--- + src/include/musl/fcntl.h | 14 ++++++++++++++ + 1 file changed, 14 insertions(+) + create mode 100644 src/include/musl/fcntl.h + +diff --git a/src/include/musl/fcntl.h b/src/include/musl/fcntl.h +new file mode 100644 +index 0000000000..e69f13087d +--- /dev/null ++++ b/src/include/musl/fcntl.h +@@ -0,0 +1,14 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++#pragma once ++ ++/* glibc defines AT_FDCWD as -100, but musl defines it as (-100). Hence, musl's fcntl.h conflicts with ++ * forward.h. To avoid the conflict, here temporary undef AT_FDCWD before including fcntl.h. */ ++#ifdef AT_FDCWD ++#undef AT_FDCWD ++#endif ++ ++#include_next ++ ++/* Then, undef AT_FDCWD by fcntl.h and redefine it as consistent with forward.h */ ++#undef AT_FDCWD ++#define AT_FDCWD -100 +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0022-musl-redefine-HOST_NAME_MAX-as-64.patch b/pkgs/os-specific/linux/systemd/musl/0022-musl-redefine-HOST_NAME_MAX-as-64.patch new file mode 100644 index 000000000000..4e47c4c14e1d --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0022-musl-redefine-HOST_NAME_MAX-as-64.patch @@ -0,0 +1,29 @@ +From 4a06464ea72d06859c54fca4d7bc361e2f4b069c Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Sun, 7 Sep 2025 06:16:02 +0900 +Subject: [PATCH 22/30] musl: redefine HOST_NAME_MAX as 64 + +glibc defines HOST_NAME_MAX as 64 and our code rely on that, but musl +defines the constant as 255. Let's redefine it. +--- + src/include/musl/limits.h | 8 ++++++++ + 1 file changed, 8 insertions(+) + create mode 100644 src/include/musl/limits.h + +diff --git a/src/include/musl/limits.h b/src/include/musl/limits.h +new file mode 100644 +index 0000000000..fadf71d66f +--- /dev/null ++++ b/src/include/musl/limits.h +@@ -0,0 +1,8 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++#pragma once ++ ++#include_next ++ ++/* HOST_NAME_MAX should be 64 on linux, but musl uses the one by POSIX (255). */ ++#undef HOST_NAME_MAX ++#define HOST_NAME_MAX 64 +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0023-musl-avoid-multiple-evaluations-in-CPU_ISSET_S-macro.patch b/pkgs/os-specific/linux/systemd/musl/0023-musl-avoid-multiple-evaluations-in-CPU_ISSET_S-macro.patch new file mode 100644 index 000000000000..a397f554fb89 --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0023-musl-avoid-multiple-evaluations-in-CPU_ISSET_S-macro.patch @@ -0,0 +1,62 @@ +From 1aecd86536aef53e92a4903794259300815a9e62 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 1 Jul 2025 12:53:14 +0900 +Subject: [PATCH 23/30] musl: avoid multiple evaluations in CPU_ISSET_S() macro +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +musl's CPU_ISSET_S() macro does not avoid multiple evaluations, and it +only accepts simple variable or constant. + +Fixes the following error. +``` +../src/shared/cpu-set-util.c: In function ‘cpu_set_to_mask_string’: +../src/shared/cpu-set-util.c:101:41: warning: operation on ‘i’ may be undefined [-Werror=sequence-point] + 101 | if (CPU_ISSET_S(--i, c->allocated, c->set)) + | ^ +``` +--- + src/include/musl/sched.h | 30 ++++++++++++++++++++++++++++++ + 1 file changed, 30 insertions(+) + create mode 100644 src/include/musl/sched.h + +diff --git a/src/include/musl/sched.h b/src/include/musl/sched.h +new file mode 100644 +index 0000000000..d6e7100ee4 +--- /dev/null ++++ b/src/include/musl/sched.h +@@ -0,0 +1,30 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++#pragma once ++ ++#include_next ++ ++/* This is for avoiding multiple evaluations in musl's __CPU_op_S() macro. */ ++ ++#undef __CPU_op_S ++#undef CPU_SET_S ++#undef CPU_CLR_S ++#undef CPU_ISSET_S ++#undef CPU_SET ++#undef CPU_CLR ++#undef CPU_ISSET ++ ++#define __CPU_op_S(i, size, set, op) \ ++ ({ \ ++ typeof(i) _i = (i); \ ++ \ ++ _i / 8U >= (size) ? 0 : \ ++ (((unsigned long*) (set))[_i / 8 / sizeof(long)] op (1UL << (_i % (8 * sizeof(long))))); \ ++ }) ++ ++#define CPU_SET_S(i, size, set) __CPU_op_S(i, size, set, |=) ++#define CPU_CLR_S(i, size, set) __CPU_op_S(i, size, set, &=~) ++#define CPU_ISSET_S(i, size, set) __CPU_op_S(i, size, set, &) ++ ++#define CPU_SET(i, set) CPU_SET_S(i, sizeof(cpu_set_t), set) ++#define CPU_CLR(i, set) CPU_CLR_S(i, sizeof(cpu_set_t), set) ++#define CPU_ISSET(i, set) CPU_ISSET_S(i, sizeof(cpu_set_t), set) +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0024-musl-core-there-is-one-less-usable-signal-when-built.patch b/pkgs/os-specific/linux/systemd/musl/0024-musl-core-there-is-one-less-usable-signal-when-built.patch new file mode 100644 index 000000000000..5a173be962be --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0024-musl-core-there-is-one-less-usable-signal-when-built.patch @@ -0,0 +1,57 @@ +From c00d31373a8f5b922dd947a6d6d6098df0429762 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Sun, 7 Sep 2025 08:53:07 +0900 +Subject: [PATCH 24/30] musl: core: there is one less usable signal when built + with musl + +musl internally reserves one more signal, hence we can only use 29 +signals. +--- + src/core/manager.c | 4 ++-- + src/test/test-signal-util.c | 7 ++++++- + 2 files changed, 8 insertions(+), 3 deletions(-) + +diff --git a/src/core/manager.c b/src/core/manager.c +index d85896577f..86cb11905f 100644 +--- a/src/core/manager.c ++++ b/src/core/manager.c +@@ -526,7 +526,7 @@ static int manager_setup_signals(Manager *m) { + assert_se(sigaction(SIGCHLD, &sa, NULL) == 0); + + /* We make liberal use of realtime signals here. On Linux/glibc we have 30 of them, between +- * SIGRTMIN+0 ... SIGRTMIN+30 (aka SIGRTMAX). */ ++ * SIGRTMIN+0 ... SIGRTMIN+30 (aka SIGRTMAX). When musl is used SIGRTMAX is SIGRTMIN+29. */ + + assert_se(sigemptyset(&mask) == 0); + sigset_add_many(&mask, +@@ -571,7 +571,7 @@ static int manager_setup_signals(Manager *m) { + SIGRTMIN+28, /* systemd: set log target to kmsg */ + SIGRTMIN+29, /* systemd: set log target to syslog-or-kmsg (obsolete) */ + +- /* ... one free signal here SIGRTMIN+30 ... */ ++ /* ... one free signal here SIGRTMIN+30 (glibc only) ... */ + -1); + assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0); + +diff --git a/src/test/test-signal-util.c b/src/test/test-signal-util.c +index 7d069a7fb2..f26b48f3f2 100644 +--- a/src/test/test-signal-util.c ++++ b/src/test/test-signal-util.c +@@ -13,8 +13,13 @@ TEST(rt_signals) { + info(SIGRTMIN); + info(SIGRTMAX); + +- /* We use signals SIGRTMIN+0 to SIGRTMIN+30 unconditionally */ ++ /* We use signals SIGRTMIN+0 to SIGRTMIN+29 unconditionally. SIGRTMIN+30 can be used only when ++ * built with glibc. */ ++#ifdef __GLIBC__ + assert_se(SIGRTMAX - SIGRTMIN >= 30); ++#else ++ assert_se(SIGRTMAX - SIGRTMIN >= 29); ++#endif + } + + static void test_signal_to_string_one(int val) { +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0025-musl-build-path-fix-reading-DT_RUNPATH-or-DT_RPATH.patch b/pkgs/os-specific/linux/systemd/musl/0025-musl-build-path-fix-reading-DT_RUNPATH-or-DT_RPATH.patch new file mode 100644 index 000000000000..4a78440837ed --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0025-musl-build-path-fix-reading-DT_RUNPATH-or-DT_RPATH.patch @@ -0,0 +1,33 @@ +From 7cade54777e9b715ca5b1023dd8fead64d1f4b3e Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Sat, 6 Sep 2025 12:06:06 +0900 +Subject: [PATCH 25/30] musl: build-path: fix reading DT_RUNPATH or DT_RPATH + +musl records DT_STRTAB as offset, rather than address. So, need to add +obtained bias to read runpath or rpath. +--- + src/basic/build-path.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/basic/build-path.c b/src/basic/build-path.c +index 577ff72bce..3212089379 100644 +--- a/src/basic/build-path.c ++++ b/src/basic/build-path.c +@@ -34,11 +34,11 @@ static int get_runpath_from_dynamic(const ElfW(Dyn) *d, ElfW(Addr) bias, const c + break; + + case DT_STRTAB: +- /* On MIPS and RISC-V DT_STRTAB records an offset, not a valid address, so it has to be adjusted +- * using the bias calculated earlier. */ ++ /* On MIPS, RISC-V, or with musl, DT_STRTAB records an offset, not a valid address, ++ * so it has to be adjusted using the bias calculated earlier. */ + if (d->d_un.d_val != 0) + strtab = (const char *) ((uintptr_t) d->d_un.d_val +-#if defined(__mips__) || defined(__riscv) ++#if defined(__mips__) || defined(__riscv) || !defined(__GLIBC__) + + bias + #endif + ); +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0026-musl-format-util-use-llu-for-formatting-rlim_t.patch b/pkgs/os-specific/linux/systemd/musl/0026-musl-format-util-use-llu-for-formatting-rlim_t.patch new file mode 100644 index 000000000000..e59e6aeae62c --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0026-musl-format-util-use-llu-for-formatting-rlim_t.patch @@ -0,0 +1,57 @@ +From 65cd003563fe1b8f1f1b3e1c1198b3d63f993820 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jun 2025 12:00:01 +0900 +Subject: [PATCH 26/30] musl: format-util: use %llu for formatting rlim_t + +glibc uses uint32_t or uint64_t for rlim_t, while musl uses unsigned long long. +--- + meson.build | 7 +++++++ + src/basic/format-util.h | 14 ++++++++------ + 2 files changed, 15 insertions(+), 6 deletions(-) + +diff --git a/meson.build b/meson.build +index 5f235d5113..66579b9182 100644 +--- a/meson.build ++++ b/meson.build +@@ -572,6 +572,13 @@ conf.set('SIZEOF_RLIM_T', cc.sizeof('rlim_t', prefix : '#include ')) + conf.set('SIZEOF_TIMEX_MEMBER', cc.sizeof('typeof(((struct timex *)0)->freq)', prefix : '#include ')) + ++if get_option('libc') == 'musl' ++ if conf.get('SIZEOF_RLIM_T') != 8 ++ error('Unexpected size of rlim_t: @0@'.format(conf.get('SIZEOF_RLIM_T'))) ++ endif ++ conf.set_quoted('RLIM_FMT', '%llu') ++endif ++ + long_max = cc.compute_int( + 'LONG_MAX', + prefix : '#include ', +diff --git a/src/basic/format-util.h b/src/basic/format-util.h +index e42f788ce6..d40ca5818a 100644 +--- a/src/basic/format-util.h ++++ b/src/basic/format-util.h +@@ -39,12 +39,14 @@ assert_cc(sizeof(gid_t) == sizeof(uint32_t)); + # error Unknown timex member size + #endif + +-#if SIZEOF_RLIM_T == 8 +-# define RLIM_FMT "%" PRIu64 +-#elif SIZEOF_RLIM_T == 4 +-# define RLIM_FMT "%" PRIu32 +-#else +-# error Unknown rlim_t size ++#ifndef RLIM_FMT ++# if SIZEOF_RLIM_T == 8 ++# define RLIM_FMT "%" PRIu64 ++# elif SIZEOF_RLIM_T == 4 ++# define RLIM_FMT "%" PRIu32 ++# else ++# error Unknown rlim_t size ++# endif + #endif + + #if SIZEOF_DEV_T == 8 +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0027-musl-time-util-skip-tm.tm_wday-check.patch b/pkgs/os-specific/linux/systemd/musl/0027-musl-time-util-skip-tm.tm_wday-check.patch new file mode 100644 index 000000000000..fd9a00fb3b5d --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0027-musl-time-util-skip-tm.tm_wday-check.patch @@ -0,0 +1,47 @@ +From 524396a3058a08955683038dae86cf5aaaf10847 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 8 Sep 2025 15:08:49 +0900 +Subject: [PATCH 27/30] musl: time-util: skip tm.tm_wday check + +musl does not set tm_wday when it is explicitly requested. +The check is not necessary at all, it is just for safety. +Let's skip it when built with musl. +--- + src/basic/time-util.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/src/basic/time-util.c b/src/basic/time-util.c +index 38591b0604..c890141a99 100644 +--- a/src/basic/time-util.c ++++ b/src/basic/time-util.c +@@ -668,10 +668,14 @@ static int parse_timestamp_impl( + _cleanup_free_ char *t_alloc = NULL; + usec_t usec, plus = 0, minus = 0; + bool with_tz = false; +- int r, weekday = -1; + unsigned fractional = 0; + const char *k; + struct tm tm, copy; ++ int r; ++#ifndef __GLIBC__ ++ _unused_ ++#endif ++ int weekday = -1; + + /* Allowed syntaxes: + * +@@ -921,8 +925,11 @@ from_tm: + assert(plus == 0); + assert(minus == 0); + ++#ifdef __GLIBC__ ++ /* musl does not set tm_wday field and set 0 unless it is explicitly requested by %w or so. */ + if (weekday >= 0 && tm.tm_wday != weekday) + return -EINVAL; ++#endif + + if (gmtoff < 0) { + plus = -gmtoff * USEC_PER_SEC; +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0028-musl-glob-util-filter-out-.-and-.-even-if-GLOB_ALTDI.patch b/pkgs/os-specific/linux/systemd/musl/0028-musl-glob-util-filter-out-.-and-.-even-if-GLOB_ALTDI.patch new file mode 100644 index 000000000000..de2b0a251f02 --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0028-musl-glob-util-filter-out-.-and-.-even-if-GLOB_ALTDI.patch @@ -0,0 +1,205 @@ +From d59cd7edd78901db8fd88c86258f6830e35e91df Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 25 Feb 2019 14:56:21 +0800 +Subject: [PATCH 28/30] musl: glob-util: filter out . and .. even if + GLOB_ALTDIRFUNC is not supported + +musl neither support GLOB_ALTDIRFUNC nor GLOB_BRACE. +Let's make safe_glob() work even when GLOB_ALTDIRFUNC is not supported. +Currently, GLOB_BRACE is simply ignored when it is not supported. +--- + src/basic/glob-util.c | 75 +++++++++++++++++++++++++++++++++++++-- + src/basic/glob-util.h | 9 ++++- + src/include/musl/glob.h | 21 +++++++++++ + src/test/test-glob-util.c | 10 ++++++ + 4 files changed, 112 insertions(+), 3 deletions(-) + create mode 100644 src/include/musl/glob.h + +diff --git a/src/basic/glob-util.c b/src/basic/glob-util.c +index 5843ef088f..84b369be87 100644 +--- a/src/basic/glob-util.c ++++ b/src/basic/glob-util.c +@@ -9,9 +9,42 @@ + #include "string-util.h" + #include "strv.h" + ++static bool safe_glob_verify(const char *p, const char *prefix) { ++ if (isempty(p)) ++ return false; /* should not happen, but for safey. */ ++ ++ if (prefix) { ++ /* Skip the prefix, as we allow dots in prefix. ++ * Note, glob() does not normalize paths, hence do not use path_startswith(). */ ++ p = startswith(p, prefix); ++ if (!p) ++ return false; /* should not happen, but for safety. */ ++ } ++ ++ for (;;) { ++ p += strspn(p, "/"); ++ if (*p == '\0') ++ return true; ++ if (*p == '.') { ++ p++; ++ if (IN_SET(*p, '/', '\0')) ++ return false; /* refuse dot */ ++ if (*p == '.') { ++ p++; ++ if (IN_SET(*p, '/', '\0')) ++ return false; /* refuse dot dot */ ++ } ++ } ++ ++ p += strcspn(p, "/"); ++ if (*p == '\0') ++ return true; ++ } ++} ++ + DEFINE_TRIVIAL_DESTRUCTOR(closedir_wrapper, void, closedir); + +-int safe_glob_full(const char *path, int flags, opendir_t opendir_func, char ***ret) { ++int safe_glob_internal(const char *path, int flags, bool use_gnu_extension, opendir_t opendir_func, char ***ret) { + _cleanup_(globfree) glob_t g = { + .gl_closedir = closedir_wrapper, + .gl_readdir = (struct dirent* (*)(void *)) readdir_no_dot, +@@ -23,8 +56,16 @@ int safe_glob_full(const char *path, int flags, opendir_t opendir_func, char *** + + assert(path); + ++ // TODO: expand braces if GLOB_BRACE is specified but not supported. ++ ++#if GLOB_ALTDIRFUNC == 0 ++ use_gnu_extension = false; ++#else ++ SET_FLAG(flags, GLOB_ALTDIRFUNC, use_gnu_extension); ++#endif ++ + errno = 0; +- r = glob(path, flags | GLOB_ALTDIRFUNC, NULL, &g); ++ r = glob(path, flags, NULL, &g); + if (r == GLOB_NOMATCH) + return -ENOENT; + if (r == GLOB_NOSPACE) +@@ -32,6 +73,36 @@ int safe_glob_full(const char *path, int flags, opendir_t opendir_func, char *** + if (r != 0) + return errno_or_else(EIO); + ++ if (!use_gnu_extension) { ++ _cleanup_free_ char *prefix = NULL; ++ r = glob_non_glob_prefix(path, &prefix); ++ if (r < 0 && r != -ENOENT) ++ return r; ++ ++ _cleanup_strv_free_ char **filtered = NULL; ++ size_t n_filtered = 0; ++ STRV_FOREACH(p, g.gl_pathv) { ++ if (!safe_glob_verify(*p, prefix)) ++ continue; ++ ++ if (!ret) ++ return 0; /* Found at least one entry, let's return earlier. */ ++ ++ /* When musl is used, each entry is not a head of allocated memory. Hence, it is ++ * necessary to copy the string. */ ++ r = strv_extend_with_size(&filtered, &n_filtered, *p); ++ if (r < 0) ++ return r; ++ } ++ ++ if (n_filtered == 0) ++ return -ENOENT; ++ ++ assert(ret); ++ *ret = TAKE_PTR(filtered); ++ return 0; ++ } ++ + if (strv_isempty(g.gl_pathv)) + return -ENOENT; + +diff --git a/src/basic/glob-util.h b/src/basic/glob-util.h +index ea3e869319..36bfd6716c 100644 +--- a/src/basic/glob-util.h ++++ b/src/basic/glob-util.h +@@ -7,7 +7,14 @@ + + typedef DIR* (*opendir_t)(const char *); + +-int safe_glob_full(const char *path, int flags, opendir_t opendir_func, char ***ret); ++int safe_glob_internal(const char *path, int flags, bool use_gnu_extension, opendir_t opendir_func, char ***ret); ++static inline int safe_glob_test(const char *path, int flags, char ***ret) { ++ /* This is for testing the fallback logic for the case GLOB_ALTDIRFUNC is not supported. */ ++ return safe_glob_internal(path, flags, false, NULL, ret); ++} ++static inline int safe_glob_full(const char *path, int flags, opendir_t opendir_func, char ***ret) { ++ return safe_glob_internal(path, flags, true, opendir_func, ret); ++} + static inline int safe_glob(const char *path, int flags, char ***ret) { + return safe_glob_full(path, flags, NULL, ret); + } +diff --git a/src/include/musl/glob.h b/src/include/musl/glob.h +new file mode 100644 +index 0000000000..58e6c50678 +--- /dev/null ++++ b/src/include/musl/glob.h +@@ -0,0 +1,21 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++#pragma once ++ ++#include_next ++ ++/* Here, we set 0 to GLOB_ALTDIRFUNC and GLOB_BRACE, rather than the values used by glibc, ++ * to indicate that glob() does not support these flags. */ ++ ++#ifndef GLOB_ALTDIRFUNC ++#define GLOB_ALTDIRFUNC 0 ++#define gl_flags __dummy1 ++#define gl_closedir __dummy2[0] ++#define gl_readdir __dummy2[1] ++#define gl_opendir __dummy2[2] ++#define gl_lstat __dummy2[3] ++#define gl_stat __dummy2[4] ++#endif ++ ++#ifndef GLOB_BRACE ++#define GLOB_BRACE 0 ++#endif +diff --git a/src/test/test-glob-util.c b/src/test/test-glob-util.c +index a9880f15c8..730961d172 100644 +--- a/src/test/test-glob-util.c ++++ b/src/test/test-glob-util.c +@@ -61,20 +61,30 @@ TEST(safe_glob) { + + fn = strjoina(template, "/*"); + ASSERT_ERROR(safe_glob(fn, /* flags = */ 0, &v), ENOENT); ++ ASSERT_ERROR(safe_glob_test(fn, /* flags = */ 0, &v), ENOENT); + + fn2 = strjoina(template, "/.*"); + ASSERT_ERROR(safe_glob(fn2, GLOB_NOSORT|GLOB_BRACE, &v), ENOENT); ++ ASSERT_ERROR(safe_glob_test(fn2, GLOB_NOSORT|GLOB_BRACE, &v), ENOENT); + + fname = strjoina(template, "/.foobar"); + ASSERT_OK(touch(fname)); + + ASSERT_ERROR(safe_glob(fn, /* flags = */ 0, &v), ENOENT); ++ ASSERT_ERROR(safe_glob_test(fn, /* flags = */ 0, &v), ENOENT); + + ASSERT_OK(safe_glob(fn2, GLOB_NOSORT|GLOB_BRACE, &v)); + ASSERT_EQ(strv_length(v), 1u); + ASSERT_STREQ(v[0], fname); + ASSERT_NULL(v[1]); + ++ v = strv_free(v); ++ ++ ASSERT_OK(safe_glob_test(fn2, GLOB_NOSORT|GLOB_BRACE, &v)); ++ ASSERT_EQ(strv_length(v), 1u); ++ ASSERT_STREQ(v[0], fname); ++ ASSERT_NULL(v[1]); ++ + (void) rm_rf(template, REMOVE_ROOT|REMOVE_PHYSICAL); + } + +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0029-musl-test-several-random-fixlets-for-unit-tests.patch b/pkgs/os-specific/linux/systemd/musl/0029-musl-test-several-random-fixlets-for-unit-tests.patch new file mode 100644 index 000000000000..4e8fa289dd41 --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0029-musl-test-several-random-fixlets-for-unit-tests.patch @@ -0,0 +1,380 @@ +From 2c3c84939b930986b31bc0f28e9a08bf43786255 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 9 Sep 2025 13:34:31 +0900 +Subject: [PATCH 29/30] musl: test: several random fixlets for unit tests + +--- + src/boot/test-efi-string.c | 36 ++++++++++++++++++++------ + src/libsystemd/sd-bus/test-bus-error.c | 6 +++++ + src/test/meson.build | 1 + + src/test/test-errno-util.c | 12 ++++++++- + src/test/test-fileio.c | 10 ++++++- + src/test/test-locale-util.c | 7 ++++- + src/test/test-os-util.c | 6 ++++- + src/test/test-seccomp.c | 8 ++++-- + src/test/test-time-util.c | 24 +++++++++++++++++ + test/meson.build | 2 ++ + test/test-sysusers.sh.in | 22 +++++++++++++++- + 11 files changed, 119 insertions(+), 15 deletions(-) + +diff --git a/src/boot/test-efi-string.c b/src/boot/test-efi-string.c +index e0d3bd9b71..af6dd1fb64 100644 +--- a/src/boot/test-efi-string.c ++++ b/src/boot/test-efi-string.c +@@ -410,13 +410,27 @@ TEST(startswith8) { + ASSERT_NULL(startswith8(NULL, "")); + } + +-#define TEST_FNMATCH_ONE(pattern, haystack, expect) \ +- ({ \ +- ASSERT_EQ(fnmatch(pattern, haystack, 0), expect ? 0 : FNM_NOMATCH); \ +- ASSERT_EQ(efi_fnmatch(u##pattern, u##haystack), expect); \ ++#define TEST_FNMATCH_ONE_FULL(pattern, haystack, expect, skip_libc) \ ++ ({ \ ++ if (!skip_libc) \ ++ ASSERT_EQ(fnmatch(pattern, haystack, 0), expect ? 0 : FNM_NOMATCH); \ ++ ASSERT_EQ(efi_fnmatch(u##pattern, u##haystack), expect); \ + }) + ++#define TEST_FNMATCH_ONE(pattern, haystack, expect) \ ++ TEST_FNMATCH_ONE_FULL(pattern, haystack, expect, false) ++ + TEST(efi_fnmatch) { ++ bool skip_libc; ++ ++#ifdef __GLIBC__ ++ skip_libc = false; ++#else ++ /* It seems musl is too strict in handling "[]" (or has a bug?). Anyway, let's skip some test cases ++ * when built with musl. The behavior of efi_fnmatch() does not need to match musl's fnmatch(). */ ++ skip_libc = true; ++#endif ++ + TEST_FNMATCH_ONE("", "", true); + TEST_FNMATCH_ONE("abc", "abc", true); + TEST_FNMATCH_ONE("aBc", "abc", false); +@@ -447,18 +461,18 @@ TEST(efi_fnmatch) { + TEST_FNMATCH_ONE("[abc", "a", false); + TEST_FNMATCH_ONE("[][!] [][!] [][!]", "[ ] !", true); + TEST_FNMATCH_ONE("[]-] []-]", "] -", true); +- TEST_FNMATCH_ONE("[1\\]] [1\\]]", "1 ]", true); ++ TEST_FNMATCH_ONE_FULL("[1\\]] [1\\]]", "1 ]", true, skip_libc); + TEST_FNMATCH_ONE("[$-\\+]", "&", true); + TEST_FNMATCH_ONE("[1-3A-C] [1-3A-C]", "2 B", true); + TEST_FNMATCH_ONE("[3-5] [3-5] [3-5]", "3 4 5", true); + TEST_FNMATCH_ONE("[f-h] [f-h] [f-h]", "f g h", true); +- TEST_FNMATCH_ONE("[a-c-f] [a-c-f] [a-c-f] [a-c-f] [a-c-f]", "a b c - f", true); +- TEST_FNMATCH_ONE("[a-c-f]", "e", false); ++ TEST_FNMATCH_ONE_FULL("[a-c-f] [a-c-f] [a-c-f] [a-c-f] [a-c-f]", "a b c - f", true, skip_libc); ++ TEST_FNMATCH_ONE_FULL("[a-c-f]", "e", false, skip_libc); + TEST_FNMATCH_ONE("[--0] [--0] [--0]", "- . 0", true); + TEST_FNMATCH_ONE("[+--] [+--] [+--]", "+ , -", true); + TEST_FNMATCH_ONE("[f-l]", "m", false); + TEST_FNMATCH_ONE("[b]", "z-a", false); +- TEST_FNMATCH_ONE("[a\\-z]", "b", false); ++ TEST_FNMATCH_ONE_FULL("[a\\-z]", "b", false, skip_libc); + TEST_FNMATCH_ONE("?a*b[.-0]c", "/a/b/c", true); + TEST_FNMATCH_ONE("debian-*-*-*.*", "debian-jessie-2018-06-17-kernel-image-5.10.0-16-amd64.efi", true); + +@@ -674,8 +688,14 @@ TEST(xvasprintf_status) { + test_printf_one("string"); + test_printf_one("%%-%%%%"); + ++#ifdef __GLIBC__ + test_printf_one("%p %p %32p %*p %*p", NULL, (void *) 0xF, &errno, 0, &saved_argc, 20, &saved_argv); + test_printf_one("%-10p %-32p %-*p %-*p", NULL, &errno, 0, &saved_argc, 20, &saved_argv); ++#else ++ /* musl prints NULL as 0, while glibc and our implementation print it as (nil). */ ++ test_printf_one("%p %32p %*p %*p", (void *) 0xF, &errno, 0, &saved_argc, 20, &saved_argv); ++ test_printf_one("%-32p %-*p %-*p", &errno, 0, &saved_argc, 20, &saved_argv); ++#endif + + test_printf_one("%c %3c %*c %*c %-8c", '1', '!', 0, 'a', 9, '_', '>'); + +diff --git a/src/libsystemd/sd-bus/test-bus-error.c b/src/libsystemd/sd-bus/test-bus-error.c +index c6b86be621..d69eb984dc 100644 +--- a/src/libsystemd/sd-bus/test-bus-error.c ++++ b/src/libsystemd/sd-bus/test-bus-error.c +@@ -232,7 +232,13 @@ TEST(sd_bus_error_set_errnof) { + errno = EACCES; + assert_se(asprintf(&str, "%m") >= 0); + assert_se(streq(error.message, str)); ++#ifdef __GLIBC__ + assert_se(error._need_free == 0); ++#else ++ /* musl's strerror_l() always writes error message in the given buffer, hence the we need to ++ * free it. */ ++ assert_se(error._need_free == 1); ++#endif + + str = mfree(str); + sd_bus_error_free(&error); +diff --git a/src/test/meson.build b/src/test/meson.build +index 1dd61effc0..4bdfc65f04 100644 +--- a/src/test/meson.build ++++ b/src/test/meson.build +@@ -16,6 +16,7 @@ test_env = { + 'PROJECT_BUILD_ROOT' : meson.project_build_root(), + 'SYSTEMD_SLOW_TESTS' : want_slow_tests ? '1' : '0', + 'PYTHONDONTWRITEBYTECODE' : '1', ++ 'SYSTEMD_LIBC' : get_option('libc'), + } + if conf.get('ENABLE_LOCALED') == 1 + test_env += {'SYSTEMD_LANGUAGE_FALLBACK_MAP' : language_fallback_map} +diff --git a/src/test/test-errno-util.c b/src/test/test-errno-util.c +index 1a0154fb05..1ad067b2c7 100644 +--- a/src/test/test-errno-util.c ++++ b/src/test/test-errno-util.c +@@ -26,18 +26,28 @@ TEST(STRERROR) { + log_info("STRERROR(%d), STRERROR(%d) → %s, %s", 200, 201, STRERROR(200), STRERROR(201)); + + const char *a = STRERROR(200), *b = STRERROR(201); ++#ifdef __GLIBC__ + ASSERT_NOT_NULL(strstr(a, "200")); + ASSERT_NOT_NULL(strstr(b, "201")); ++#else ++ /* musl provides catch all error message for unknown error number. */ ++ ASSERT_STREQ(a, "No error information"); ++ ASSERT_STREQ(b, "No error information"); ++#endif + + /* Check with negative values */ + ASSERT_STREQ(a, STRERROR(-200)); + ASSERT_STREQ(b, STRERROR(-201)); + + const char *c = STRERROR(INT_MAX); ++ log_info("STRERROR(%d) → %s", INT_MAX, c); ++#ifdef __GLIBC__ + 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_NOT_NULL(strstr(c, buf)); ++#else ++ ASSERT_STREQ(c, "No error information"); ++#endif + } + + TEST(STRERROR_OR_ELSE) { +diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c +index e0ae30bbb9..ad572056c2 100644 +--- a/src/test/test-fileio.c ++++ b/src/test/test-fileio.c +@@ -400,7 +400,15 @@ TEST(write_string_stream) { + + f = fdopen(fd, "r"); + assert_se(f); +- assert_se(write_string_stream(f, "boohoo", 0) < 0); ++#ifdef __GLIBC__ ++ ASSERT_ERROR(write_string_stream(f, "boohoo", 0), EBADF); ++#else ++ /* Even the file is opened with the read-only mode, fputs() and fputc() by musl succeed but nothing ++ * actually written, thus write_string_stream() also succeeds. */ ++ ASSERT_OK(write_string_stream(f, "boohoo", 0)); ++ rewind(f); ++ ASSERT_NULL(fgets(buf, sizeof(buf), f)); ++#endif + f = safe_fclose(f); + + f = fopen(fn, "r+"); +diff --git a/src/test/test-locale-util.c b/src/test/test-locale-util.c +index e264cff5dd..faa7ac4211 100644 +--- a/src/test/test-locale-util.c ++++ b/src/test/test-locale-util.c +@@ -50,7 +50,12 @@ TEST(locale_is_installed) { + assert_se(locale_is_installed("\x01gar\x02 bage\x03") == 0); + + /* Definitely not installed */ +- assert_se(locale_is_installed("zz_ZZ") == 0); ++#ifdef __GLIBC__ ++ ASSERT_OK_ZERO(locale_is_installed("zz_ZZ")); ++#else ++ /* musl seems to return a non-null locale object even if it is not installed. */ ++ ASSERT_OK_POSITIVE(locale_is_installed("zz_ZZ")); ++#endif + } + + TEST(keymaps) { +diff --git a/src/test/test-os-util.c b/src/test/test-os-util.c +index fcab1139c2..902062560f 100644 +--- a/src/test/test-os-util.c ++++ b/src/test/test-os-util.c +@@ -127,7 +127,11 @@ TEST(os_release_support_ended) { + + ASSERT_TRUE(os_release_support_ended("1999-01-01", false, NULL)); + ASSERT_FALSE(os_release_support_ended("2037-12-31", false, NULL)); +- assert_se(os_release_support_ended("-1-1-1", true, NULL) == -EINVAL); ++#ifdef __GLIBC__ ++ ASSERT_ERROR(os_release_support_ended("-1-1-1", true, NULL), EINVAL); ++#else ++ ASSERT_ERROR(os_release_support_ended("-1-1-1", true, NULL), ERANGE); ++#endif + + r = os_release_support_ended(NULL, false, NULL); + if (r < 0) +diff --git a/src/test/test-seccomp.c b/src/test/test-seccomp.c +index 13c65a93cd..a9953b2c98 100644 +--- a/src/test/test-seccomp.c ++++ b/src/test/test-seccomp.c +@@ -553,9 +553,13 @@ TEST(memory_deny_write_execute_mmap) { + 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) ++#if defined(__x86_64__) || defined(__i386__) || defined(__powerpc64__) || defined(__arm__) || defined(__aarch64__) || defined(__loongarch_lp64) || !defined(__GLIBC__) + assert_se(p == MAP_FAILED); +- assert_se(errno == EPERM); ++# ifdef __GLIBC__ ++ ASSERT_EQ(errno, EPERM); ++# else ++ ASSERT_EQ(errno, ENOMEM); ++# endif + #endif + /* Depending on kernel, libseccomp, and glibc versions, other architectures + * might fail or not. Let's not assert success. */ +diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c +index 7569f89ca0..203f8a148d 100644 +--- a/src/test/test-time-util.c ++++ b/src/test/test-time-util.c +@@ -406,6 +406,25 @@ static void test_format_timestamp_impl(usec_t x) { + const char *xx = FORMAT_TIMESTAMP(x); + ASSERT_NOT_NULL(xx); + ++#ifndef __GLIBC__ ++ /* Because of the timezone change, format_timestamp() may set timezone that is currently unused. ++ * E.g. Africa/Juba may set EAT, but currently it uses CAT/CAST. */ ++ const char *space; ++ ASSERT_NOT_NULL(space = strrchr(xx, ' ')); ++ const char *tz = space + 1; ++ if (!streq_ptr(tz, tzname[0]) && ++ !streq_ptr(tz, tzname[1]) && ++ parse_gmtoff(tz, NULL) < 0) { ++ ++ log_warning("@" USEC_FMT " → %s, timezone '%s' is currently unused, ignoring.", x, xx, tz); ++ ++ /* Verify the generated string except for the timezone part. Of course, in most cases, parsed ++ * time does not match with the input, hence only check if it is parsable. */ ++ ASSERT_OK(parse_timestamp(strndupa_safe(xx, space - xx), NULL)); ++ return; ++ } ++#endif ++ + usec_t y; + ASSERT_OK(parse_timestamp(xx, &y)); + const char *yy = FORMAT_TIMESTAMP(y); +@@ -1116,7 +1135,12 @@ TEST(in_utc_timezone) { + assert_se(setenv("TZ", ":UTC", 1) >= 0); + assert_se(in_utc_timezone()); + ASSERT_STREQ(tzname[0], "UTC"); ++#ifdef __GLIBC__ + ASSERT_STREQ(tzname[1], "UTC"); ++#else ++ /* musl sets an empty string to tzname[1] when DST is not used by the timezone. */ ++ ASSERT_STREQ(tzname[1], ""); ++#endif + assert_se(timezone == 0); + assert_se(daylight == 0); + +diff --git a/test/meson.build b/test/meson.build +index 505a14aa81..1f1ec0a0e1 100644 +--- a/test/meson.build ++++ b/test/meson.build +@@ -13,6 +13,7 @@ if conf.get('ENABLE_SYSUSERS') == 1 + # https://github.com/mesonbuild/meson/issues/2681 + args : exe.full_path(), + depends : exe, ++ env : test_env, + suite : 'sysusers') + + if have_standalone_binaries +@@ -22,6 +23,7 @@ if conf.get('ENABLE_SYSUSERS') == 1 + # https://github.com/mesonbuild/meson/issues/2681 + args : exe.full_path(), + depends : exe, ++ env : test_env, + suite : 'sysusers') + endif + endif +diff --git a/test/test-sysusers.sh.in b/test/test-sysusers.sh.in +index 3218923590..ae7bfee2fe 100755 +--- a/test/test-sysusers.sh.in ++++ b/test/test-sysusers.sh.in +@@ -13,6 +13,12 @@ TESTDIR=$(mktemp --tmpdir --directory "test-sysusers.XXXXXXXXXX") + # shellcheck disable=SC2064 + trap "rm -rf '$TESTDIR'" EXIT INT QUIT PIPE + ++skip_nis() { ++ # musl seems to not support NIS entries. Let's skip the test case for NIS entries. ++ local path=${1:?} ++ [[ "${SYSTEMD_LIBC:-}" == musl && "${path##*/}" == "test-11.input" ]] ++} ++ + prepare_testdir() { + mkdir -p "$TESTDIR/etc/sysusers.d/" + mkdir -p "$TESTDIR/usr/lib/sysusers.d/" +@@ -50,6 +56,7 @@ rm -f "$TESTDIR"/etc/sysusers.d/* "$TESTDIR"/usr/lib/sysusers.d/* + + # happy tests + for f in $(find "$SOURCE"/test-*.input | sort -V); do ++ skip_nis "$f" && continue + echo "*** Running $f" + prepare_testdir "${f%.input}" + cp "$f" "$TESTDIR/usr/lib/sysusers.d/test.conf" +@@ -59,6 +66,7 @@ for f in $(find "$SOURCE"/test-*.input | sort -V); do + done + + for f in $(find "$SOURCE"/test-*.input | sort -V); do ++ skip_nis "$f" && continue + echo "*** Running $f on stdin" + prepare_testdir "${f%.input}" + touch "$TESTDIR/etc/sysusers.d/test.conf" +@@ -68,6 +76,7 @@ for f in $(find "$SOURCE"/test-*.input | sort -V); do + done + + for f in $(find "$SOURCE"/test-*.input | sort -V); do ++ skip_nis "$f" && continue + echo "*** Running $f on stdin with --replace" + prepare_testdir "${f%.input}" + touch "$TESTDIR/etc/sysusers.d/test.conf" +@@ -133,6 +142,7 @@ SYS_GID_MAX999 + EOF + + for f in $(find "$SOURCE"/test-*.input | sort -V); do ++ skip_nis "$f" && continue + echo "*** Running $f (with login.defs)" + prepare_testdir "${f%.input}" + cp "$f" "$TESTDIR/usr/lib/sysusers.d/test.conf" +@@ -149,6 +159,7 @@ mv "$TESTDIR/etc/login.defs" "$TESTDIR/etc/login.defs.moved" + ln -s ../../../../../etc/login.defs.moved "$TESTDIR/etc/login.defs" + + for f in $(find "$SOURCE"/test-*.input | sort -V); do ++ skip_nis "$f" && continue + echo "*** Running $f (with login.defs symlinked)" + prepare_testdir "${f%.input}" + cp "$f" "$TESTDIR/usr/lib/sysusers.d/test.conf" +@@ -161,13 +172,22 @@ done + + rm -f "$TESTDIR"/etc/sysusers.d/* "$TESTDIR"/usr/lib/sysusers.d/* + ++preprocess_error() { ++ # Convert error message for ERANGE from glibc to musl. ++ if [[ "${SYSTEMD_LIBC:-}" == musl ]]; then ++ sed -e 's/Numerical result out of range/Result not representable/' "${1:?}" ++ else ++ cat "${1:?}" ++ fi ++} ++ + # tests for error conditions + for f in $(find "$SOURCE"/unhappy-*.input | sort -V); do + echo "*** Running test $f" + prepare_testdir "${f%.input}" + cp "$f" "$TESTDIR/usr/lib/sysusers.d/test.conf" + SYSTEMD_LOG_LEVEL=info "$SYSUSERS" --root="$TESTDIR" 2>&1 | tail -n1 | sed -r 's/^[^:]+:[^:]+://' >"$TESTDIR/err" +- if ! diff -u "$TESTDIR/err" "${f%.*}.expected-err" >&2; then ++ if ! diff -u "$TESTDIR/err" <(preprocess_error "${f%.*}.expected-err") >&2; then + echo >&2 "**** Unexpected error output for $f" + cat >&2 "$TESTDIR/err" + exit 1 +-- +2.51.0 + diff --git a/pkgs/os-specific/linux/systemd/musl/0030-musl-ci-add-build-test-and-unit-tests.patch b/pkgs/os-specific/linux/systemd/musl/0030-musl-ci-add-build-test-and-unit-tests.patch new file mode 100644 index 000000000000..51eec7e78ac3 --- /dev/null +++ b/pkgs/os-specific/linux/systemd/musl/0030-musl-ci-add-build-test-and-unit-tests.patch @@ -0,0 +1,352 @@ +From b9a6cd49a4d4c6a40968224f5aac20586273b171 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 9 Sep 2025 13:42:24 +0900 +Subject: [PATCH 30/30] musl: ci: add build test and unit tests + +--- + .github/workflows/build-test-musl.sh | 125 ++++++++++++++++++++++++++ + .github/workflows/linter.yml | 9 ++ + .github/workflows/unit-tests-musl.sh | 55 ++++++++++++ + .github/workflows/unit-tests-musl.yml | 112 +++++++++++++++++++++++ + 4 files changed, 301 insertions(+) + create mode 100755 .github/workflows/build-test-musl.sh + create mode 100755 .github/workflows/unit-tests-musl.sh + create mode 100644 .github/workflows/unit-tests-musl.yml + +diff --git a/.github/workflows/build-test-musl.sh b/.github/workflows/build-test-musl.sh +new file mode 100755 +index 0000000000..bc38924666 +--- /dev/null ++++ b/.github/workflows/build-test-musl.sh +@@ -0,0 +1,125 @@ ++#!/bin/bash ++# SPDX-License-Identifier: LGPL-2.1-or-later ++ ++set -eux ++ ++if ! command -v musl-gcc 2>/dev/null; then ++ echo "musl-gcc is not installed, skipping the test." ++ exit 0 ++fi ++ ++. /etc/os-release ++ ++TMPDIR=$(mktemp -d) ++ ++cleanup() ( ++ set +e ++ ++ if [[ -d "$TMPDIR" ]]; then ++ rm -rf "$TMPDIR" ++ fi ++) ++ ++trap cleanup EXIT ERR INT TERM ++ ++mkdir -p "${TMPDIR}/build" ++mkdir -p "${TMPDIR}/include" ++ ++CFLAGS="-idirafter ${TMPDIR}/include" ++ ++LINKS=( ++ acl ++ archive.h ++ archive_entry.h ++ asm-generic ++ audit-records.h ++ audit_logging.h ++ bpf ++ bzlib.h ++ dwarf.h ++ elfutils ++ fido.h ++ gcrypt.h ++ gelf.h ++ gnutls ++ idn2.h ++ libaudit.h ++ libcryptsetup.h ++ libelf.h ++ libkmod.h ++ lz4.h ++ lz4frame.h ++ lz4hc.h ++ lzma ++ lzma.h ++ microhttpd.h ++ mtd ++ openssl ++ pcre2.h ++ pwquality.h ++ qrencode.h ++ seccomp-syscalls.h ++ seccomp.h ++ security ++ sys/acl.h ++ sys/capability.h ++ tss2 ++ xen ++ xkbcommon ++ zconf.h ++ zlib.h ++ zstd.h ++ zstd_errors.h ++) ++ ++if [[ "$ID" == arch ]]; then ++ LINKS+=( ++ asm ++ curl ++ gpg-error.h ++ libiptc ++ linux ++ ) ++elif [[ "$ID" == centos ]]; then ++ LINKS+=( ++ asm ++ curl ++ gpg-error.h ++ linux ++ selinux ++ ) ++elif [[ "$ID" == fedora ]]; then ++ LINKS+=( ++ asm ++ curl ++ gpg-error.h ++ libiptc ++ linux ++ selinux ++ ) ++elif [[ "$ID" == debian ]] || [[ "$ID" == ubuntu ]]; then ++ # Currently, debian/ubuntu does not provide crypt.h for musl. Hence, this does not work. ++ ++ CFLAGS="$CFLAGS -idirafter /usr/include/$(uname -m)-linux-gnu" ++ ++ LINKS+=( ++ linux ++ selinux ++ sys/apparmor.h ++ ) ++fi ++ ++for t in "${LINKS[@]}"; do ++ [[ -e "/usr/include/$t" ]] ++ link="${TMPDIR}/include/${t}" ++ mkdir -p "${link%/*}" ++ ln -s "/usr/include/$t" "$link" ++done ++ ++env CC=musl-gcc \ ++ CXX=musl-gcc \ ++ CFLAGS="$CFLAGS" \ ++ CXXFLAGS="$CFLAGS" \ ++ meson setup --werror -Ddbus-interfaces-dir=no -Dlibc=musl "${TMPDIR}/build" ++ ++ninja -v -C "${TMPDIR}/build" +diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml +index c107b2da1e..b92a00478c 100644 +--- a/.github/workflows/linter.yml ++++ b/.github/workflows/linter.yml +@@ -49,6 +49,12 @@ jobs: + [Build] + ToolsTreeDistribution=fedora + ToolsTreeRelease=rawhide ++ ++ [Content] ++ Packages= ++ libgcrypt-devel ++ libgpg-error-devel ++ musl-gcc + EOF + + mkosi -f box -- true +@@ -77,3 +83,6 @@ jobs: + + - name: Run clang-tidy + run: mkosi box -- meson test -C build --suite=clang-tidy --print-errorlogs --no-stdsplit ++ ++ - name: Build with musl ++ run: mkosi box -- .github/workflows/build-test-musl.sh +diff --git a/.github/workflows/unit-tests-musl.sh b/.github/workflows/unit-tests-musl.sh +new file mode 100755 +index 0000000000..f6e2b2f536 +--- /dev/null ++++ b/.github/workflows/unit-tests-musl.sh +@@ -0,0 +1,55 @@ ++#!/usr/bin/env bash ++# SPDX-License-Identifier: LGPL-2.1-or-later ++ ++# shellcheck disable=SC2206 ++PHASES=(${@:-SETUP BUILD RUN CLEANUP}) ++ ++function info() { ++ echo -e "\033[33;1m$1\033[0m" ++} ++function run_meson() { ++ if ! meson "$@"; then ++ find . -type f -name meson-log.txt -exec cat '{}' + ++ return 1 ++ fi ++} ++ ++set -ex ++ ++for phase in "${PHASES[@]}"; do ++ case $phase in ++ SETUP) ++ info "Setup phase" ++ # Alpine still uses split-usr. ++ for i in /bin/* /sbin/*; do ++ ln -rs "$i" "/usr/$i"; ++ done ++ ;; ++ BUILD) ++ info "Build phase" ++ run_meson setup --werror -Dtests=unsafe -Dslow-tests=true -Dfuzz-tests=true -Dlibc=musl build ++ ninja -C build -v ++ ;; ++ RUN) ++ info "Run phase" ++ # Create dummy machine ID. ++ echo '052e58f661f94bd080e258b96aea3f7b' > /etc/machine-id ++ ++ # Start dbus for several unit tests. ++ mkdir -p /var/run/dbus ++ /usr/bin/dbus-daemon --system || : ++ ++ # Here, we explicitly set SYSTEMD_IN_CHROOT=yes as unfortunately runnin_in_chroot() does not ++ # correctly detect the environment. ++ env \ ++ SYSTEMD_IN_CHROOT=yes \ ++ meson test -C build -v ++ ;; ++ CLEANUP) ++ info "Cleanup phase" ++ ;; ++ *) ++ echo >&2 "Unknown phase '$phase'" ++ exit 1 ++ esac ++done +diff --git a/.github/workflows/unit-tests-musl.yml b/.github/workflows/unit-tests-musl.yml +new file mode 100644 +index 0000000000..e30974254c +--- /dev/null ++++ b/.github/workflows/unit-tests-musl.yml +@@ -0,0 +1,112 @@ ++--- ++# vi: ts=2 sw=2 et: ++# SPDX-License-Identifier: LGPL-2.1-or-later ++# ++name: Unit tests (musl) ++on: ++ pull_request: ++ paths: ++ - '**/meson.build' ++ - '.github/workflows/**' ++ - 'meson_options.txt' ++ - 'src/**' ++ - 'test/fuzz/**' ++ ++permissions: ++ contents: read ++ ++jobs: ++ build: ++ runs-on: ubuntu-latest ++ steps: ++ - name: Repository checkout ++ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 ++ ++ - name: Install build dependencies ++ uses: jirutka/setup-alpine@v1 ++ with: ++ arch: x86_64 ++ branch: edge ++ packages: > ++ acl ++ acl-dev ++ audit-dev ++ bash ++ bash-completion-dev ++ bpftool ++ build-base ++ bzip2-dev ++ coreutils ++ cryptsetup-dev ++ curl-dev ++ dbus ++ dbus-dev ++ elfutils-dev ++ gettext-dev ++ git ++ glib-dev ++ gnutls-dev ++ gperf ++ grep ++ iproute2 ++ iptables-dev ++ kbd ++ kexec-tools ++ kmod ++ kmod-dev ++ libapparmor-dev ++ libarchive-dev ++ libbpf-dev ++ libcap-dev ++ libcap-utils ++ libfido2-dev ++ libgcrypt-dev ++ libidn2-dev ++ libmicrohttpd-dev ++ libpwquality-dev ++ libqrencode-dev ++ libseccomp-dev ++ libselinux-dev ++ libxkbcommon-dev ++ linux-pam-dev ++ lz4-dev ++ meson ++ openssl ++ openssl-dev ++ p11-kit-dev ++ pcre2-dev ++ pkgconf ++ polkit-dev ++ py3-elftools ++ py3-jinja2 ++ py3-pefile ++ py3-pytest ++ py3-lxml ++ quota-tools ++ rsync ++ sfdisk ++ tpm2-tss-dev ++ tpm2-tss-esys ++ tpm2-tss-rc ++ tpm2-tss-tcti-device ++ tzdata ++ util-linux-dev ++ util-linux-login ++ util-linux-misc ++ valgrind-dev ++ xen-dev ++ zlib-dev ++ zstd-dev ++ ++ - name: Setup ++ run: .github/workflows/unit-tests-musl.sh SETUP ++ shell: alpine.sh --root {0} ++ - name: Build ++ run: .github/workflows/unit-tests-musl.sh BUILD ++ shell: alpine.sh {0} ++ - name: Run ++ run: .github/workflows/unit-tests-musl.sh RUN ++ shell: alpine.sh --root {0} ++ - name: Cleanup ++ run: .github/workflows/unit-tests-musl.sh CLEANUP ++ shell: alpine.sh --root {0} +-- +2.51.0 +