Add experimental musl support (#38825)

This adds experimental support of building systemd with musl. This
allows to build systemd with musl and run most of the unit tests.
Running integration tests is not yet supported, however. Hopefully, this
should be a good starting point to support musl.

This requires musl-1.2.5 with
fde29c04ad.
The patch is already backported to Alpine/postmarketOS's musl package
since musl-1.2.5-r11. See
333e50c205.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2025-11-17 11:14:47 +01:00
committed by GitHub
15 changed files with 803 additions and 0 deletions

94
.github/workflows/build-test-musl.sh vendored Executable file
View File

@@ -0,0 +1,94 @@
#!/bin/bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -eux
if ! command -v musl-gcc >/dev/null; then
echo "musl-gcc is not installed, skipping the test."
exit 77
fi
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}/usr/include"
mkdir -p "${TMPDIR}/usr/lib64/pkgconfig"
CFLAGS="-idirafter ${TMPDIR}/usr/include"
export PKG_CONFIG_PATH="${TMPDIR}"/usr/lib64/pkgconfig
LINKS=(
acl
archive.h
archive_entry.h
asm
asm-generic
audit-records.h
audit_logging.h
bpf
bzlib.h
curl
dwarf.h
elfutils
fido.h
gcrypt.h
gelf.h
gnutls
gpg-error.h
idn2.h
libaudit.h
libcryptsetup.h
libelf.h
libkmod.h
linux
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
selinux
sys/acl.h
sys/capability.h
tss2
xen
xkbcommon
zconf.h
zlib.h
zstd.h
zstd_errors.h
)
for t in "${LINKS[@]}"; do
[[ -e /usr/include/"$t" ]]
link="${TMPDIR}"/usr/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

View File

@@ -49,6 +49,10 @@ jobs:
[Build]
ToolsTreeDistribution=fedora
ToolsTreeRelease=rawhide
ToolsTreePackages=
libgcrypt-devel
libgpg-error-devel
musl-gcc
EOF
mkosi -f box -- true
@@ -77,3 +81,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

59
.github/workflows/unit-tests-musl.sh vendored Executable file
View File

@@ -0,0 +1,59 @@
#!/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 systemd phase"
run_meson setup --werror -Dtests=unsafe -Dslow-tests=true -Dfuzz-tests=true -Dlibc=musl build
ninja -v -C build
;;
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 --print-errorlogs
;;
CLEANUP)
info "Cleanup phase"
;;
*)
echo >&2 "Unknown phase '$phase'"
exit 1
esac
done

113
.github/workflows/unit-tests-musl.yml vendored Normal file
View File

@@ -0,0 +1,113 @@
---
# 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
utmps-dev
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}

View File

@@ -1028,6 +1028,7 @@ threads = dependency('threads')
librt = cc.find_library('rt')
libm = cc.find_library('m')
libdl = cc.find_library('dl')
libutmps = dependency('libutmps', required : false)
# On some distributions that use 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.

View File

@@ -9,6 +9,78 @@
#include "string-util.h"
#include "strv.h"
#ifndef __GLIBC__
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;
}
}
static int filter_glob_result(char * const *result, const char *path, char ***ret) {
int r;
assert(path);
if (strv_isempty(result))
return -ENOENT;
_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, result) {
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;
}
#endif
DEFINE_TRIVIAL_DESTRUCTOR(closedir_wrapper, void, closedir);
int safe_glob_full(const char *path, int flags, opendir_t opendir_func, char ***ret) {
@@ -32,6 +104,10 @@ int safe_glob_full(const char *path, int flags, opendir_t opendir_func, char ***
if (r != 0)
return errno_or_else(EIO);
#ifndef __GLIBC__
return filter_glob_result(g.gl_pathv, path, ret);
#endif
if (strv_isempty(g.gl_pathv))
return -ENOENT;

18
src/include/musl/glob.h Normal file
View File

@@ -0,0 +1,18 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include_next <glob.h>
#ifndef GLOB_ALTDIRFUNC
#define GLOB_ALTDIRFUNC (1 << 9)
#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 (1 << 10)
#endif

27
src/include/musl/printf.h Normal file
View File

@@ -0,0 +1,27 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/* Copyright 2014 Emil Renner Berthing <systemd@esmil.dk> */
#pragma once
#include <stddef.h>
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)
size_t parse_printf_format(const char *fmt, size_t n, int *types);

16
src/include/musl/utmpx.h Normal file
View File

@@ -0,0 +1,16 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include_next <utmpx.h>
#ifndef UTMPX_FILE
#define UTMPX_FILE "/run/utmp"
#endif
#ifndef WTMPX_FILE
#define WTMPX_FILE "/var/log/wtmp"
#endif
#ifndef ACCOUNTING
#define ACCOUNTING 9
#endif

View File

@@ -5,6 +5,7 @@ if get_option('libc') != 'musl'
endif
libc_wrapper_sources += files(
'printf.c',
'stdio.c',
'stdlib.c',
'string.c',

263
src/libc/musl/printf.c Normal file
View File

@@ -0,0 +1,263 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/* Copyright 2014 Emil Renner Berthing <systemd@esmil.dk> */
#include <limits.h>
#include <printf.h>
#include <stdint.h>
#include <string.h>
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,
SHORT,
LONG,
LLONG,
IMAX,
SIZET,
CHAR,
WCHAR,
DBL,
LDBL,
NPTR,
_TYPE_MAX,
};
static const short pa_types[_TYPE_MAX] = {
[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,
#if ULLONG_MAX > ULONG_MAX
[LLONG] = PA_INT | PA_FLAG_LONG_LONG,
#else
[LLONG] = PA_INT | PA_FLAG_LONG,
#endif
#if UINTMAX_MAX > ULONG_MAX
[IMAX] = PA_INT | PA_FLAG_LONG_LONG,
#elif UINTMAX_MAX > UINT_MAX
[IMAX] = PA_INT | PA_FLAG_LONG,
#else
[IMAX] = PA_INT,
#endif
#if SIZE_MAX > ULONG_MAX
[SIZET] = PA_INT | PA_FLAG_LONG_LONG,
#elif SIZE_MAX > UINT_MAX
[SIZET] = PA_INT | PA_FLAG_LONG,
#else
[SIZET] = PA_INT,
#endif
[CHAR] = PA_CHAR,
[WCHAR] = PA_WCHAR,
[DBL] = PA_DOUBLE,
[LDBL] = PA_DOUBLE | PA_FLAG_LONG_DOUBLE,
[NPTR] = PA_FLAG_PTR,
};
#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 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] = pa_types[types[i]];
return last;
}

View File

@@ -49,6 +49,7 @@ executables += [
'include_directories' : [libexec_template['include_directories'], include_directories('.')],
'extract' : systemd_logind_extract_sources,
'dependencies' : [
libutmps,
threads,
],
},

View File

@@ -385,6 +385,7 @@ libshared_deps = [threads,
librt,
libseccomp_cflags,
libselinux_cflags,
libutmps,
libxenctrl_cflags,
libxz_cflags,
libzstd_cflags,

View File

@@ -164,6 +164,7 @@ simple_tests += files(
'test-percent-util.c',
'test-pidref.c',
'test-pretty-print.c',
'test-printf.c',
'test-prioq.c',
'test-proc-cmdline.c',
'test-procfs-util.c',
@@ -472,6 +473,7 @@ executables += [
test_template + {
'sources' : files('test-utmp.c'),
'conditions' : ['ENABLE_UTMP'],
'dependencies' : libutmps,
},
test_template + {
'sources' : files('test-varlink.c'),

124
src/test/test-printf.c Normal file
View File

@@ -0,0 +1,124 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <printf.h>
#include "memory-util.h"
#include "strv.h"
#include "tests.h"
static void test_parse_printf_format_one(const char *fmt, size_t m, const int *expected) {
log_debug("/* %s(%s) */", __func__, fmt);
int types[128] = {};
size_t n = parse_printf_format(fmt, ELEMENTSOF(types), types);
for (size_t i = 0; i < MAX(n, m); i++)
if (i < MIN(n, m))
log_debug("types[%zu]=%i, expected[%zu]=%i", i, types[i], i, expected[i]);
else if (i < n)
log_debug("types[%zu]=%i, expected[%zu]=n/a", i, types[i], i);
else
log_debug("types[%zu]=n/a, expected[%zu]=%i", i, i, expected[i]);
ASSERT_EQ(memcmp_nn(types, n * sizeof(int), expected, m * sizeof(int)), 0);
}
TEST(parse_printf_format) {
static struct {
const char *prefix;
int expected;
} integer_table[] = {
{ "", PA_INT },
{ "hh", PA_CHAR },
{ "h", PA_INT | PA_FLAG_SHORT },
{ "l", PA_INT | PA_FLAG_LONG },
#if ULLONG_MAX > ULONG_MAX
{ "ll", PA_INT | PA_FLAG_LONG_LONG },
#else
{ "ll", PA_INT | PA_FLAG_LONG },
#endif
#if UINTMAX_MAX > ULONG_MAX
{ "j", PA_INT | PA_FLAG_LONG_LONG },
#elif UINTMAX_MAX > UINT_MAX
{ "j", PA_INT | PA_FLAG_LONG },
#else
{ "j", PA_INT },
#endif
#if SIZE_MAX > ULONG_MAX
{ "z", PA_INT | PA_FLAG_LONG_LONG },
{ "Z", PA_INT | PA_FLAG_LONG_LONG },
{ "t", PA_INT | PA_FLAG_LONG_LONG },
#elif SIZE_MAX > UINT_MAX
{ "z", PA_INT | PA_FLAG_LONG },
{ "Z", PA_INT | PA_FLAG_LONG },
{ "t", PA_INT | PA_FLAG_LONG },
#else
{ "z", PA_INT },
{ "Z", PA_INT },
{ "t", PA_INT },
#endif
}, float_table[] = {
{ "", PA_DOUBLE },
{ "L", PA_DOUBLE | PA_FLAG_LONG_DOUBLE },
};
FOREACH_ELEMENT(i, integer_table) {
_cleanup_free_ char *fmt = NULL;
FOREACH_STRING(s, "d", "i", "o", "u", "x", "X") {
ASSERT_NOT_NULL(fmt = strjoin("%", i->prefix, s));
test_parse_printf_format_one(fmt, 1, &i->expected);
fmt = mfree(fmt);
}
ASSERT_NOT_NULL(fmt = strjoin("%", i->prefix, "n"));
test_parse_printf_format_one(fmt, 1, (int[]){ PA_INT | PA_FLAG_PTR });
fmt = mfree(fmt);
ASSERT_NOT_NULL(fmt = strjoin("%", i->prefix));
test_parse_printf_format_one(fmt, 0, NULL);
}
FOREACH_ELEMENT(i, float_table) {
_cleanup_free_ char *fmt = NULL;
FOREACH_STRING(s, "e", "E", "f", "F", "g", "G", "a", "A") {
ASSERT_NOT_NULL(fmt = strjoin("%", i->prefix, s));
test_parse_printf_format_one(fmt, 1, &i->expected);
fmt = mfree(fmt);
}
ASSERT_NOT_NULL(fmt = strjoin("%", i->prefix));
test_parse_printf_format_one(fmt, 0, NULL);
}
test_parse_printf_format_one("%c", 1, (int[]) { PA_CHAR });
test_parse_printf_format_one("%lc", 1, (int[]) { PA_CHAR });
test_parse_printf_format_one("%C", 1, (int[]) { PA_WCHAR });
test_parse_printf_format_one("%s", 1, (int[]) { PA_STRING });
test_parse_printf_format_one("%ls", 1, (int[]) { PA_STRING });
test_parse_printf_format_one("%S", 1, (int[]) { PA_WSTRING });
test_parse_printf_format_one("%p", 1, (int[]) { PA_POINTER });
test_parse_printf_format_one("%m", 0, NULL);
test_parse_printf_format_one("%%", 0, NULL);
test_parse_printf_format_one("asfhghejmlahpgakdmsalc", 0, NULL);
test_parse_printf_format_one(
"%d%i%o%u%x%X", 6,
(int[]) { PA_INT, PA_INT, PA_INT, PA_INT, PA_INT, PA_INT });
test_parse_printf_format_one(
"%e%E%f%F%g%G%a%A", 8,
(int[]) { PA_DOUBLE, PA_DOUBLE, PA_DOUBLE, PA_DOUBLE, PA_DOUBLE, PA_DOUBLE, PA_DOUBLE, PA_DOUBLE });
test_parse_printf_format_one(
"%c%s%C%S%p%n%m%%", 6,
(int[]) { PA_CHAR, PA_STRING, PA_WCHAR, PA_WSTRING, PA_POINTER, PA_INT | PA_FLAG_PTR });
test_parse_printf_format_one(
"%03d%-05d%+i%hhu%hu%hx%lx", 7,
(int[]) { PA_INT, PA_INT, PA_INT, PA_CHAR, PA_INT | PA_FLAG_SHORT, PA_INT | PA_FLAG_SHORT, PA_INT | PA_FLAG_LONG });
}
DEFINE_TEST_MAIN(LOG_DEBUG);