mirror of
https://github.com/morgan9e/systemd
synced 2026-04-14 00:14:32 +09:00
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 withfde29c04ad. The patch is already backported to Alpine/postmarketOS's musl package since musl-1.2.5-r11. See333e50c205.
This commit is contained in:
94
.github/workflows/build-test-musl.sh
vendored
Executable file
94
.github/workflows/build-test-musl.sh
vendored
Executable 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
|
||||
7
.github/workflows/linter.yml
vendored
7
.github/workflows/linter.yml
vendored
@@ -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
59
.github/workflows/unit-tests-musl.sh
vendored
Executable 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
113
.github/workflows/unit-tests-musl.yml
vendored
Normal 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}
|
||||
@@ -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.
|
||||
|
||||
@@ -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
18
src/include/musl/glob.h
Normal 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
27
src/include/musl/printf.h
Normal 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
16
src/include/musl/utmpx.h
Normal 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
|
||||
@@ -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
263
src/libc/musl/printf.c
Normal 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;
|
||||
}
|
||||
@@ -49,6 +49,7 @@ executables += [
|
||||
'include_directories' : [libexec_template['include_directories'], include_directories('.')],
|
||||
'extract' : systemd_logind_extract_sources,
|
||||
'dependencies' : [
|
||||
libutmps,
|
||||
threads,
|
||||
],
|
||||
},
|
||||
|
||||
@@ -385,6 +385,7 @@ libshared_deps = [threads,
|
||||
librt,
|
||||
libseccomp_cflags,
|
||||
libselinux_cflags,
|
||||
libutmps,
|
||||
libxenctrl_cflags,
|
||||
libxz_cflags,
|
||||
libzstd_cflags,
|
||||
|
||||
@@ -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
124
src/test/test-printf.c
Normal 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);
|
||||
Reference in New Issue
Block a user