Merge branch 'use-compression-libraries'

In some configurations, the use of utilities is redundant. Also, popen()
requires /bin/sh. However, not all configurations utilize the libz and
libbz2 libraries. For example, busybox has its own implementations of
gzip and bzip2.

A solution that would suit everyone is to add support for
ELF_DLOPEN_METADATA[1]. This will avoid unnecessary dependencies where
utilities are available and avoid using utilities where libraries are
available.

[1] https://github.com/systemd/systemd/blob/main/docs/ELF_DLOPEN_METADATA.md

Link: https://github.com/legionus/kbd/pull/141
Signed-off-by: Alexey Gladkov <legion@kernel.org>
This commit is contained in:
Alexey Gladkov 2025-08-17 18:31:23 +02:00
commit eddc753fd9
No known key found for this signature in database
GPG Key ID: A45ABA544CFFD434
22 changed files with 1075 additions and 81 deletions

View File

@ -55,9 +55,6 @@ jobs:
- name: "Configure"
run: |
tests/configure.sh --datadir="$PWD/data" --enable-memcheck
- name: "Check sparse"
run: |
make check-sparse V=1
- name: "Clean"
run: |
make clean
@ -87,28 +84,27 @@ jobs:
matrix:
include:
- os: ubuntu-latest
cc: gcc
cflags:
compiler: gcc
libc: glibc
configure:
check: unittest e2e
- os: ubuntu-latest
cc: clang
cflags:
compiler: clang
libc: glibc
configure:
check: unittest e2e
- os: ubuntu-latest
cc: musl-gcc -static -idirafter /usr/include/ -idirafter /usr/include/x86_64-linux-gnu/
cflags: -idirafter /usr/include/ -idirafter /usr/include/x86_64-linux-gnu/
compiler: gcc
libc: musl
configure: --disable-libkeymap --disable-vlock
configure: --host=x86_64-linux-musl --disable-vlock --without-zlib --without-bzip2 --without-lzma --without-zstd
check: unittest e2e
fail-fast: false
runs-on: ${{ matrix.os }}
needs: [ distcheck_job ]
env:
CC: ${{ matrix.cc }}
CHECK_KEYWORDS: ${{ matrix.check }}
SANDBOX: priviliged
TTY: /dev/tty60
@ -126,7 +122,7 @@ jobs:
tests/configure.sh --datadir="$PWD/tests/data" --enable-memcheck ${{ matrix.configure }}
- name: "Build"
run: |
make V=1 CFLAGS+="-g -O0"
make V=1 CFLAGS+="-g -O0 ${{ matrix.cflags }}"
- name: "Check"
run: |
sudo -E tests/check.sh

View File

@ -220,6 +220,66 @@ if test "$VLOCK_PROG" = "yes"; then
fi
fi
AC_ARG_WITH([zlib],
[AS_HELP_STRING([--with-zlib],
[support zlib compression @<:@default=auto@:>@])],
[],
[: m4_divert_text([DEFAULTS], [with_zlib=yes])])
AS_IF([test "x$with_zlib" != xno],
[PKG_CHECK_MODULES(ZLIB, zlib, [HAVE_ZLIB=yes], [HAVE_ZLIB=no])],
[HAVE_ZLIB=no])
AS_IF([test "x$HAVE_ZLIB" = "xyes"],
[AC_DEFINE([HAVE_ZLIB], [1], [support zlib compression])])
AM_CONDITIONAL(USE_ZLIB, test "x$HAVE_ZLIB" = "xyes")
AC_ARG_WITH([bzip2],
[AS_HELP_STRING([--with-bzip2],
[support bzip2 compression @<:@default=auto@:>@])],
[],
[: m4_divert_text([DEFAULTS], [with_bzip2=yes])])
AS_IF([test "x$with_bzip2" != xno],
[PKG_CHECK_MODULES(BZIP2, bzip2, [HAVE_BZIP2=yes], [HAVE_BZIP2=no])],
[HAVE_BZIP2=no])
if test "x$HAVE_BZIP2" = xno; then
AC_CHECK_LIB(bz2, BZ2_bzDecompressInit, [
HAVE_BZIP2=yes
BZIP2_LIBS=-lbz2
BZIP2_CFLAGS=''
], [HAVE_BZIP2=no])
fi
AS_IF([test "x$HAVE_BZIP2" = "xyes"],
[AC_DEFINE([HAVE_BZIP2], [1], [support bzip2 compression])])
AM_CONDITIONAL(USE_BZIP2, test "$HAVE_BZIP2" = "yes")
AC_ARG_WITH([lzma],
[AS_HELP_STRING([--with-lzma],
[support lzma compression @<:@default=auto@:>@])],
[],
[: m4_divert_text([DEFAULTS], [with_lzma=yes])])
AS_IF([test "x$with_lzma" != xno],
[PKG_CHECK_MODULES(LZMA, liblzma, [HAVE_LZMA=yes], [HAVE_LZMA=no])],
[HAVE_LZMA=no])
AS_IF([test "x$HAVE_LZMA" = "xyes"],
[AC_DEFINE([HAVE_LZMA], [1], [support lzma compression])])
AM_CONDITIONAL(USE_LZMA, test "$HAVE_LZMA" = "yes")
AC_ARG_WITH([zstd],
[AS_HELP_STRING([--with-zstd],
[support zstd compression @<:@default=auto@:>@])],
[],
[: m4_divert_text([DEFAULTS], [with_zstd=yes])])
AS_IF([test "x$with_zstd" != xno],
[PKG_CHECK_MODULES(ZSTD, libzstd, [HAVE_ZSTD=yes], [HAVE_ZSTD=no])],
[HAVE_ZSTD=no])
AS_IF([test "x$HAVE_ZSTD" = "xyes"],
[AC_DEFINE([HAVE_ZSTD], [1], [support zstd compression])])
AM_CONDITIONAL(USE_ZSTD, test "$HAVE_ZSTD" = "yes")
AC_ARG_ENABLE(tests,
[AS_HELP_STRING([--disable-tests], [do not build tests])],
[build_tests=$enableval], [build_tests=auto])

View File

@ -10,9 +10,34 @@ headers = \
libkbdfile_la_SOURCES = \
$(headers) \
contextP.h \
elf-note.h \
elf-note.c \
init.c \
kbdfile.c
libkbdfile_la_LIBADD =
libkbdfile_la_CFLAGS =
if USE_ZLIB
libkbdfile_la_SOURCES += kbdfile-zlib.c
libkbdfile_la_CFLAGS += $(ZLIB_CFLAGS)
endif
if USE_BZIP2
libkbdfile_la_SOURCES += kbdfile-bzip2.c
libkbdfile_la_CFLAGS += $(BZIP2_CFLAGS)
endif
if USE_LZMA
libkbdfile_la_SOURCES += kbdfile-lzma.c
libkbdfile_la_CFLAGS += $(LZMA_CFLAGS)
endif
if USE_ZSTD
libkbdfile_la_SOURCES += kbdfile-zstd.c
libkbdfile_la_CFLAGS += $(ZSTD_CFLAGS)
endif
KBDFILE_CURRENT = 1
KBDFILE_REVISION = 0
KBDFILE_AGE = 0

View File

@ -33,6 +33,7 @@ struct kbdfile {
#define KBDFILE_CTX_INITIALIZED 0x01
#define KBDFILE_PIPE 0x02
#define KBDFILE_COMPRESSED 0x04
#define kbdfile_log_cond(ctx, level, arg...) \
do { \
@ -68,4 +69,36 @@ struct kbdfile {
*/
#define ERR(ctx, arg...) kbdfile_log_cond(ctx, LOG_ERR, ##arg)
char *kbd_strerror(int errnum, char *buf, size_t buflen);
static inline FILE *kbdfile_decompressor_dummy(struct kbdfile *file KBD_ATTR_UNUSED)
{
return NULL;
}
#define kbdfile_decompressor_zlib kbdfile_decompressor_dummy
#define kbdfile_decompressor_bzip2 kbdfile_decompressor_dummy
#define kbdfile_decompressor_lzma kbdfile_decompressor_dummy
#define kbdfile_decompressor_zstd kbdfile_decompressor_dummy
#ifdef HAVE_ZLIB
#undef kbdfile_decompressor_zlib
FILE *kbdfile_decompressor_zlib(struct kbdfile *file) KBD_ATTR_MUST_CHECK;
#endif
#ifdef HAVE_BZIP2
#undef kbdfile_decompressor_bzip2
FILE *kbdfile_decompressor_bzip2(struct kbdfile *file) KBD_ATTR_MUST_CHECK;
#endif
#ifdef HAVE_LZMA
#undef kbdfile_decompressor_lzma
FILE *kbdfile_decompressor_lzma(struct kbdfile *file) KBD_ATTR_MUST_CHECK;
#endif
#ifdef HAVE_ZSTD
#undef kbdfile_decompressor_zstd
FILE *kbdfile_decompressor_zstd(struct kbdfile *file) KBD_ATTR_MUST_CHECK;
#endif
#endif /* KBDFILE_CONTEXTP_H */

54
src/libkbdfile/elf-note.c Normal file
View File

@ -0,0 +1,54 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "config.h"
#include <unistd.h>
#include <stdint.h>
#include <stdarg.h>
#include <errno.h>
#include <dlfcn.h>
#include "elf-note.h"
static int dlsym_manyv(void *dl, va_list ap)
{
void (**fn)(void);
while ((fn = va_arg(ap, typeof(fn)))) {
const char *symbol;
symbol = va_arg(ap, typeof(symbol));
*fn = dlsym(dl, symbol);
if (!*fn)
return -ENXIO;
}
return 0;
}
int dlsym_many(void **dlp, const char *filename, ...)
{
va_list ap;
void *dl;
int r;
if (*dlp)
return 0;
dl = dlopen(filename, RTLD_LAZY);
if (!dl)
return -ENOENT;
va_start(ap, filename);
r = dlsym_manyv(dl, ap);
va_end(ap);
if (r < 0) {
dlclose(dl);
return r;
}
*dlp = dl;
return 1;
}

90
src/libkbdfile/elf-note.h Normal file
View File

@ -0,0 +1,90 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <stdint.h>
#include <stddef.h>
#define XSTRINGIFY(x) #x
#define STRINGIFY(x) XSTRINGIFY(x)
#define XCONCATENATE(x, y) x##y
#define CONCATENATE(x, y) XCONCATENATE(x, y)
#define UNIQ(x) CONCATENATE(x, __COUNTER__)
#define UNIQ_T(x, uniq) CONCATENATE(__unique_prefix_, CONCATENATE(x, uniq))
/*
* Load many various symbols from @filename.
* @dlp: pointer to the previous results of this call: it's set when it succeeds
* @filename: the library to dlopen() and look for symbols
* @...: or 1 more tuples created by DLSYM_ARG() with ( &var, "symbol name" ).
*/
int dlsym_many(void **dlp, const char *filename, ...);
/*
* Helper to create tuples passed as arguments to dlsym_many().
* @symbol__: symbol to create arguments for. Example: DLSYM_ARG(foo) expands to
* `&sym_foo, "foo"`
*/
#define DLSYM_ARG(symbol__) &sym_##symbol__, STRINGIFY(symbol__),
/* For symbols being dynamically loaded */
#define DECLARE_DLSYM(symbol) static typeof(symbol) *sym_##symbol
/*
* Helper defines, to be done locally before including this header to switch between
* implementations
*/
#define DECLARE_SYM(sym__) DECLARE_DLSYM(sym__);
/*
* Originally from systemd codebase.
*
* Reference: https://systemd.io/ELF_PACKAGE_METADATA/
*/
#define ELF_NOTE_DLOPEN_VENDOR "FDO"
#define ELF_NOTE_DLOPEN_TYPE UINT32_C(0x407c0c0a)
#define ELF_NOTE_DLOPEN_PRIORITY_REQUIRED "required"
#define ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED "recommended"
#define ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED "suggested"
/*
* Add an ".note.dlopen" ELF note to our binary that declares our weak dlopen()
* dependency. This information can be read from an ELF file via
* "readelf -p .note.dlopen" or an equivalent command.
*/
#define _ELF_NOTE_DLOPEN(json, variable_name) \
__attribute__((used, section(".note.dlopen"))) _Alignas(sizeof(uint32_t)) \
static const struct { \
struct { \
uint32_t n_namesz, n_descsz, n_type; \
} nhdr; \
char name[sizeof(ELF_NOTE_DLOPEN_VENDOR)]; \
_Alignas(sizeof(uint32_t)) char dlopen_json[sizeof(json)]; \
} variable_name = { \
.nhdr = { \
.n_namesz = sizeof(ELF_NOTE_DLOPEN_VENDOR), \
.n_descsz = sizeof(json), \
.n_type = ELF_NOTE_DLOPEN_TYPE, \
}, \
.name = ELF_NOTE_DLOPEN_VENDOR, \
.dlopen_json = json, \
}
#define _SONAME_ARRAY1(a) "[\""a"\"]"
#define _SONAME_ARRAY2(a, b) "[\""a"\",\""b"\"]"
#define _SONAME_ARRAY3(a, b, c) "[\""a"\",\""b"\",\""c"\"]"
#define _SONAME_ARRAY4(a, b, c, d) "[\""a"\",\""b"\",\""c"\"",\""d"\"]"
#define _SONAME_ARRAY5(a, b, c, d, e) "[\""a"\",\""b"\",\""c"\"",\""d"\",\""e"\"]"
#define _SONAME_ARRAY_GET(_1,_2,_3,_4,_5,NAME,...) NAME
#define _SONAME_ARRAY(...) _SONAME_ARRAY_GET(__VA_ARGS__, _SONAME_ARRAY5, _SONAME_ARRAY4, _SONAME_ARRAY3, _SONAME_ARRAY2, _SONAME_ARRAY1)(__VA_ARGS__)
/*
* The 'priority' must be one of 'required', 'recommended' or 'suggested' as per
* specification, use the macro defined above to specify it.
* Multiple sonames can be passed and they will be automatically constructed
* into a json array (but note that due to preprocessor language limitations if
* more than the limit defined above is used, a new _SONAME_ARRAY<X+1> will need
* to be added).
*/
#define ELF_NOTE_DLOPEN(feature, description, priority, ...) \
_ELF_NOTE_DLOPEN("[{\"feature\":\"" feature "\",\"description\":\"" description "\",\"priority\":\"" priority "\",\"soname\":" _SONAME_ARRAY(__VA_ARGS__) "}]", UNIQ_T(s, UNIQ))

View File

@ -0,0 +1,123 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "config.h"
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <bzlib.h>
#include <kbdfile.h>
#include "contextP.h"
#include "elf-note.h"
#define DL_SYMBOL_TABLE(M) \
M(BZ2_bzopen) \
M(BZ2_bzclose) \
M(BZ2_bzread) \
M(BZ2_bzerror)
DL_SYMBOL_TABLE(DECLARE_SYM)
static int dlopen_note(void)
{
static void *dl;
ELF_NOTE_DLOPEN("bzip2",
"Support for uncompressing bzip2-compressed files",
ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED,
"libbz2.so.1");
return dlsym_many(&dl, "libbz2.so.1", DL_SYMBOL_TABLE(DLSYM_ARG) NULL);
}
FILE *kbdfile_decompressor_bzip2(struct kbdfile *file)
{
char errbuf[200];
int retcode;
BZFILE *zf = NULL;
FILE *outf = NULL;
int memfd = -1;
retcode = dlopen_note();
if (retcode < 0) {
ERR(file->ctx, "bzip2: can't load and resolve symbols: %s",
kbd_strerror(-retcode, errbuf, sizeof(errbuf)));
return NULL;
}
retcode = -1;
memfd = memfd_create(file->pathname, MFD_CLOEXEC);
if (memfd < 0) {
ERR(file->ctx, "unable to open in-memory file: %s",
kbd_strerror(errno, errbuf, sizeof(errbuf)));
goto cleanup;
}
zf = sym_BZ2_bzopen(file->pathname, "rb");
if (!zf) {
ERR(file->ctx, "bzip2: unable to open archive: %s",
kbd_strerror(errno, errbuf, sizeof(errbuf)));
goto cleanup;
}
while (1) {
int read_bytes;
char outbuf[BUFSIZ];
read_bytes = sym_BZ2_bzread(zf, outbuf, sizeof(outbuf));
if (read_bytes < 0) {
int zerrno;
ERR(file->ctx, "bzip2: read error: %s",
sym_BZ2_bzerror(zf, &zerrno));
goto cleanup;
}
if (read_bytes == 0) {
retcode = 0;
break;
}
size_t to_write = (size_t) read_bytes;
size_t written = 0;
while (written < to_write) {
ssize_t w = write(memfd, outbuf + written, to_write - written);
if (w < 0) {
if (errno == EINTR)
continue;
ERR(file->ctx, "unable to write data: %s",
kbd_strerror(errno, errbuf, sizeof(errbuf)));
goto cleanup;
}
written += (size_t) w;
}
}
cleanup:
if (zf)
sym_BZ2_bzclose(zf);
if (retcode == 0) {
lseek(memfd, 0L, SEEK_SET);
outf = fdopen(memfd, "r");
if (!outf) {
ERR(file->ctx, "unable to create file stream from file descriptor: %s",
kbd_strerror(errno, errbuf, sizeof(errbuf)));
if (memfd >= 0)
close(memfd);
}
} else if (memfd >= 0) {
close(memfd);
}
return outf;
}

View File

@ -0,0 +1,150 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "config.h"
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <lzma.h>
#include <kbdfile.h>
#include "contextP.h"
#include "elf-note.h"
#define DL_SYMBOL_TABLE(M) \
M(lzma_stream_decoder) \
M(lzma_code) \
M(lzma_end)
DL_SYMBOL_TABLE(DECLARE_SYM)
static int dlopen_lzma(void)
{
static void *dl;
ELF_NOTE_DLOPEN("lzma",
"Support for uncompressing xz-compressed files",
ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED,
"liblzma.so.5");
return dlsym_many(&dl, "liblzma.so.5", DL_SYMBOL_TABLE(DLSYM_ARG) NULL);
}
FILE *kbdfile_decompressor_lzma(struct kbdfile *file)
{
char errbuf[200];
int retcode;
FILE *outf = NULL;
int infd = -1;
int memfd = -1;
lzma_stream strm = LZMA_STREAM_INIT;
lzma_action action = LZMA_RUN;
retcode = dlopen_lzma();
if (retcode < 0) {
ERR(file->ctx, "lzma: can't load and resolve symbols: %s",
kbd_strerror(-retcode, errbuf, sizeof(errbuf)));
return NULL;
}
retcode = -1;
if (sym_lzma_stream_decoder(&strm, UINT64_MAX, LZMA_CONCATENATED) != LZMA_OK)
goto cleanup;
infd = open(file->pathname, O_RDONLY);
if (infd < 0) {
ERR(file->ctx, "unable to open xz-archive file: %s",
kbd_strerror(errno, errbuf, sizeof(errbuf)));
goto cleanup;
}
memfd = memfd_create(file->pathname, MFD_CLOEXEC);
if (memfd < 0) {
ERR(file->ctx, "unable to open in-memory file: %s",
kbd_strerror(errno, errbuf, sizeof(errbuf)));
goto cleanup;
}
uint8_t inpbuf[BUFSIZ];
uint8_t outbuf[BUFSIZ * 4];
int eof = 0;
while (1) {
if (!eof && strm.avail_in == 0) {
ssize_t read_bytes = read(infd, inpbuf, sizeof(inpbuf));
if (read_bytes < 0) {
ERR(file->ctx, "unable to read xz-archive: %s",
kbd_strerror(errno, errbuf, sizeof(errbuf)));
goto cleanup;
} else if (read_bytes == 0) {
eof = 1;
action = LZMA_FINISH;
} else {
strm.next_in = inpbuf;
strm.avail_in = (size_t) read_bytes;
}
}
strm.next_out = outbuf;
strm.avail_out = sizeof(outbuf);
lzma_ret code_ret = sym_lzma_code(&strm, action);
if (code_ret != LZMA_OK && code_ret != LZMA_STREAM_END) {
ERR(file->ctx, "lzma: decode error (return code %d)", code_ret);
goto cleanup;
}
size_t to_write = sizeof(outbuf) - strm.avail_out;
size_t written = 0;
while (written < to_write) {
ssize_t w = write(memfd, outbuf + written, to_write - written);
if (w < 0) {
if (errno == EINTR)
continue;
ERR(file->ctx, "unable to write data: %s",
kbd_strerror(errno, errbuf, sizeof(errbuf)));
goto cleanup;
}
written += (size_t) w;
}
if (code_ret == LZMA_STREAM_END) {
retcode = 0;
goto cleanup;
}
}
cleanup:
if (infd >= 0)
close(infd);
sym_lzma_end(&strm);
if (retcode == 0) {
lseek(memfd, 0L, SEEK_SET);
outf = fdopen(memfd, "r");
if (!outf) {
ERR(file->ctx, "unable to create file stream from file descriptor: %s",
kbd_strerror(errno, errbuf, sizeof(errbuf)));
if (memfd >= 0)
close(memfd);
}
} else if (memfd >= 0) {
close(memfd);
}
return outf;
}

View File

@ -0,0 +1,123 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "config.h"
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <zlib.h>
#include <kbdfile.h>
#include "contextP.h"
#include "elf-note.h"
#define DL_SYMBOL_TABLE(M) \
M(gzclose) \
M(gzopen) \
M(gzerror) \
M(gzread)
DL_SYMBOL_TABLE(DECLARE_SYM)
static int dlopen_note(void)
{
static void *dl;
ELF_NOTE_DLOPEN("zlib",
"Support for uncompressing zlib-compressed files",
ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED,
"libz.so.1");
return dlsym_many(&dl, "libz.so.1", DL_SYMBOL_TABLE(DLSYM_ARG) NULL);
}
FILE *kbdfile_decompressor_zlib(struct kbdfile *file)
{
char errbuf[200];
int retcode;
gzFile gzf = NULL;
FILE *outf = NULL;
int memfd = -1;
retcode = dlopen_note();
if (retcode < 0) {
ERR(file->ctx, "zlib: can't load and resolve symbols: %s",
kbd_strerror(-retcode, errbuf, sizeof(errbuf)));
return NULL;
}
retcode = -1;
memfd = memfd_create(file->pathname, MFD_CLOEXEC);
if (memfd < 0) {
ERR(file->ctx, "unable to open in-memory file: %s",
kbd_strerror(errno, errbuf, sizeof(errbuf)));
goto cleanup;
}
gzf = sym_gzopen(file->pathname, "rb");
if (!gzf) {
ERR(file->ctx, "zlib: unable to open archive: %s",
kbd_strerror(errno, errbuf, sizeof(errbuf)));
goto cleanup;
}
while (1) {
int read_bytes;
char outbuf[BUFSIZ];
read_bytes = sym_gzread(gzf, outbuf, sizeof(outbuf));
if (read_bytes < 0) {
int gzerr;
ERR(file->ctx, "zlib: read error: %s",
sym_gzerror(gzf, &gzerr));
goto cleanup;
}
if (read_bytes == 0) {
retcode = 0;
break;
}
size_t to_write = (size_t) read_bytes;
size_t written = 0;
while (written < to_write) {
ssize_t w = write(memfd, outbuf + written, to_write - written);
if (w < 0) {
if (errno == EINTR)
continue;
ERR(file->ctx, "unable to write data: %s",
kbd_strerror(errno, errbuf, sizeof(errbuf)));
goto cleanup;
}
written += (size_t) w;
}
}
cleanup:
sym_gzclose(gzf);
if (retcode == 0) {
lseek(memfd, 0L, SEEK_SET);
outf = fdopen(memfd, "r");
if (!outf) {
ERR(file->ctx, "unable to create file stream from file descriptor: %s",
kbd_strerror(errno, errbuf, sizeof(errbuf)));
if (memfd >= 0)
close(memfd);
}
} else if (memfd >= 0) {
close(memfd);
}
return outf;
}

View File

@ -0,0 +1,162 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "config.h"
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <zstd.h>
#include <kbdfile.h>
#include "contextP.h"
#include "elf-note.h"
#define DL_SYMBOL_TABLE(M) \
M(ZSTD_createDStream) \
M(ZSTD_decompressStream) \
M(ZSTD_isError) \
M(ZSTD_getErrorName) \
M(ZSTD_freeDStream)
DL_SYMBOL_TABLE(DECLARE_SYM)
static int dlopen_note(void)
{
static void *dl;
ELF_NOTE_DLOPEN("zstd",
"Support for uncompressing zstd-compressed files",
ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED,
"libzstd.so.1");
return dlsym_many(&dl, "libzstd.so.1", DL_SYMBOL_TABLE(DLSYM_ARG) NULL);
}
FILE *kbdfile_decompressor_zstd(struct kbdfile *file)
{
char errbuf[200];
int retcode;
FILE *outf = NULL;
int infd = -1;
int memfd = -1;
ZSTD_DStream *dstream = NULL;
retcode = dlopen_note();
if (retcode < 0) {
ERR(file->ctx, "zstd: can't load and resolve symbols: %s",
kbd_strerror(-retcode, errbuf, sizeof(errbuf)));
return NULL;
}
retcode = -1;
infd = open(file->pathname, O_RDONLY);
if (infd < 0) {
ERR(file->ctx, "unable to open xz-archive file: %s",
kbd_strerror(errno, errbuf, sizeof(errbuf)));
goto cleanup;
}
memfd = memfd_create(file->pathname, MFD_CLOEXEC);
if (memfd < 0) {
ERR(file->ctx, "unable to open in-memory file: %s",
kbd_strerror(errno, errbuf, sizeof(errbuf)));
goto cleanup;
}
dstream = sym_ZSTD_createDStream();
if (!dstream) {
ERR(file->ctx, "prepare zstd streaming decompressor failed");
goto cleanup;
}
char inpbuf[BUFSIZ];
char outbuf[BUFSIZ * 4];
int eof = 0;
ZSTD_inBuffer input = { inpbuf, 0, 0 };
ZSTD_outBuffer output = { outbuf, sizeof(outbuf), 0 };
while (!eof || input.pos < input.size) {
if (!eof && input.pos == input.size) {
ssize_t r = read(infd, inpbuf, sizeof(inpbuf));
if (r < 0) {
ERR(file->ctx, "unable to read zstd-archive: %s",
kbd_strerror(errno, errbuf, sizeof(errbuf)));
goto cleanup;
} else if (r == 0) {
eof = 1;
input.src = inpbuf;
input.size = 0;
input.pos = 0;
} else {
input.src = inpbuf;
input.size = (size_t) r;
input.pos = 0;
}
}
output.dst = outbuf;
output.size = sizeof(outbuf);
output.pos = 0;
size_t res_decompress = sym_ZSTD_decompressStream(dstream, &output, &input);
if (sym_ZSTD_isError(res_decompress)) {
ERR(file->ctx, "zstd: unable to decompress stream: %s",
sym_ZSTD_getErrorName(res_decompress));
goto cleanup;
}
size_t to_write = output.pos;
size_t written = 0;
while (written < to_write) {
ssize_t w = write(memfd, outbuf + written, to_write - written);
if (w < 0) {
if (errno == EINTR)
continue;
ERR(file->ctx, "unable to write data: %s",
kbd_strerror(errno, errbuf, sizeof(errbuf)));
goto cleanup;
}
written += (size_t) w;
}
}
if (eof && input.pos == input.size)
retcode = 0;
cleanup:
if (infd >= 0)
close(infd);
if (dstream)
sym_ZSTD_freeDStream(dstream);
if (retcode == 0) {
lseek(memfd, 0, SEEK_SET);
outf = fdopen(memfd, "r");
if (!outf) {
ERR(file->ctx, "unable to create file stream from file descriptor: %s",
kbd_strerror(errno, errbuf, sizeof(errbuf)));
if (memfd >= 0)
close(memfd);
}
} else if (memfd >= 0) {
close(memfd);
}
return outf;
}

View File

@ -7,6 +7,7 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <dirent.h>
#include <errno.h>
@ -16,14 +17,17 @@
#include "contextP.h"
static struct decompressor {
unsigned char magic[2];
FILE *(*decompressor)(struct kbdfile *fp);
const char *ext; /* starts with `.', has no other dots */
const char *cmd;
} decompressors[] = {
{ ".gz", "gzip -d -c" },
{ ".bz2", "bzip2 -d -c" },
{ ".xz", "xz -d -c" },
{ ".zst", "zstd -d -q -c" },
{ NULL, NULL }
{ { 0x1f, 0x8b }, kbdfile_decompressor_zlib, ".gz", "gzip -d -c" },
{ { 0x1f, 0x9e }, kbdfile_decompressor_zlib, ".gz", "gzip -d -c" },
{ { 0x42, 0x5a }, kbdfile_decompressor_bzip2, ".bz2", "bzip2 -d -c" },
{ { 0xfd, 0x37 }, kbdfile_decompressor_lzma, ".xz", "xz -d -c" },
{ { 0x28, 0xb5 }, kbdfile_decompressor_zstd, ".zst", "zstd -d -q -c" },
{ { 0, 0 }, NULL, NULL, NULL }
};
struct kbdfile *
@ -74,6 +78,29 @@ kbdfile_set_pathname(struct kbdfile *fp, const char *pathname)
return 0;
}
static int KBD_ATTR_PRINTF(2, 3)
kbdfile_pathname_sprintf(struct kbdfile *fp, const char *fmt, ...)
{
ssize_t size;
va_list ap;
if (fp == NULL || fmt == NULL)
return -1;
va_start(ap, fmt);
size = vsnprintf(NULL, 0, fmt, ap);
va_end(ap);
if (size < 0 || (size_t) size >= sizeof(fp->pathname))
return -1;
va_start(ap, fmt);
vsnprintf(fp->pathname, sizeof(fp->pathname), fmt, ap);
va_end(ap);
return 0;
}
FILE *
kbdfile_get_file(struct kbdfile *fp)
{
@ -102,7 +129,7 @@ kbdfile_close(struct kbdfile *fp)
fp->pathname[0] = '\0';
}
static char *
char *
kbd_strerror(int errnum, char *buf, size_t buflen)
{
*buf = '\0';
@ -131,7 +158,7 @@ pipe_open(const struct decompressor *dc, struct kbdfile *fp)
sprintf(pipe_cmd, "%s %s", dc->cmd, fp->pathname);
fp->fd = popen(pipe_cmd, "r");
fp->flags |= KBDFILE_PIPE;
fp->flags |= KBDFILE_PIPE | KBDFILE_COMPRESSED;
if (!(fp->fd)) {
char buf[200];
@ -144,78 +171,130 @@ pipe_open(const struct decompressor *dc, struct kbdfile *fp)
return 0;
}
/* If a file PATHNAME exists, then open it.
If is has a `compressed' extension, then open a pipe reading it */
static int
maybe_pipe_open(struct kbdfile *fp)
open_pathname(struct kbdfile *fp)
{
char *t;
FILE *f;
char buf[200];
unsigned char magic[2];
struct stat st;
struct decompressor *dc;
if (stat(fp->pathname, &st) == -1 || !S_ISREG(st.st_mode) || access(fp->pathname, R_OK) == -1)
if (!fp || stat(fp->pathname, &st) < 0 || !S_ISREG(st.st_mode))
return -1;
t = strrchr(fp->pathname, '.');
if (t) {
for (dc = &decompressors[0]; dc->cmd; dc++) {
if (strcmp(t, dc->ext) == 0)
return pipe_open(dc, fp);
}
}
errno = 0;
f = fopen(fp->pathname, "r");
fp->flags &= ~KBDFILE_PIPE;
if ((fp->fd = fopen(fp->pathname, "r")) == NULL) {
char buf[200];
if (!f) {
ERR(fp->ctx, "fopen: %s: %s", fp->pathname, kbd_strerror(errno, buf, sizeof(buf)));
return -1;
}
fp->flags &= ~KBDFILE_PIPE;
fp->flags &= ~KBDFILE_COMPRESSED;
if ((size_t) st.st_size > sizeof(magic)) {
struct decompressor *dc;
errno = 0;
if (fread(magic, sizeof(magic), 1, f) != 1) {
ERR(fp->ctx, "fread: %s: %s", fp->pathname, kbd_strerror(errno, buf, sizeof(buf)));
fclose(f);
return -1;
}
/*
* We ignore the suffix and use archive magics to avoid problems
* with incorrect file naming.
*/
for (dc = &decompressors[0]; dc->cmd; dc++) {
if (memcmp(magic, dc->magic, sizeof(magic)) != 0)
continue;
fclose(f);
if (dc->decompressor && (f = dc->decompressor(fp)) != NULL) {
fp->flags |= KBDFILE_COMPRESSED;
goto uncompressed;
}
if (getenv("KBDFILE_IGNORE_DECOMP_UTILS") != NULL)
return -1;
return pipe_open(dc, fp);
}
rewind(f);
}
uncompressed:
fp->fd = f;
return 0;
}
/*
* If a file PATHNAME exists, then open it.
* If is has a `compressed' extension, then open a pipe reading it.
*/
static int
maybe_pipe_open(struct kbdfile *fp)
{
size_t len;
struct decompressor *dc;
if (!fp)
return -1;
if (!open_pathname(fp))
return 0;
len = strlen(fp->pathname);
/*
* We no longer rely on suffixes to select a decompressor, but we still
* need to check the suffix for backward compatibility.
*/
for (dc = &decompressors[0]; dc->cmd; dc++) {
if (len + strlen(dc->ext) >= sizeof(fp->pathname))
continue;
fp->pathname[len] = '\0';
strcat(fp->pathname, dc->ext);
if (!open_pathname(fp))
return 0;
}
fp->pathname[len] = '\0';
return -1;
}
static int
findfile_by_fullname(const char *fnam, const char *const *suffixes, struct kbdfile *fp)
{
int i;
struct stat st;
struct decompressor *dc;
size_t fnam_len, sp_len;
fp->flags &= ~KBDFILE_PIPE;
fnam_len = strlen(fnam);
fp->flags &= ~KBDFILE_COMPRESSED;
for (i = 0; suffixes[i]; i++) {
if (suffixes[i] == NULL)
continue; /* we tried it already */
sp_len = strlen(suffixes[i]);
if (fnam_len + sp_len + 1 > sizeof(fp->pathname))
if (kbdfile_pathname_sprintf(fp, "%s%s", fnam, suffixes[i]) < 0)
continue;
snprintf(fp->pathname, sizeof(fp->pathname), "%s%s", fnam, suffixes[i]);
if (stat(fp->pathname, &st) == 0 && S_ISREG(st.st_mode) && (fp->fd = fopen(fp->pathname, "r")) != NULL)
if (!maybe_pipe_open(fp))
return 0;
for (dc = &decompressors[0]; dc->cmd; dc++) {
if (fnam_len + sp_len + strlen(dc->ext) + 1 > sizeof(fp->pathname))
continue;
snprintf(fp->pathname, sizeof(fp->pathname), "%s%s%s", fnam, suffixes[i], dc->ext);
if (stat(fp->pathname, &st) == 0 && S_ISREG(st.st_mode) && access(fp->pathname, R_OK) == 0)
return pipe_open(dc, fp);
}
}
return -1;
}
static int
filecmp(const char *fname, const char *name, const char *const *suf, unsigned int *index, struct decompressor **d)
filecmp(const char *fname, const char *name, const char *const *suf, unsigned int *index)
{
/* Does d_name start right? */
const char *p = name;
@ -232,7 +311,6 @@ filecmp(const char *fname, const char *name, const char *const *suf, unsigned in
if (!strcmp(p, suf[i])) {
if (i < *index) {
*index = i;
*d = NULL;
}
return 0;
}
@ -246,7 +324,6 @@ filecmp(const char *fname, const char *name, const char *const *suf, unsigned in
if (!strcmp(p + l, dc->ext)) {
if (i < *index) {
*index = i;
*d = dc;
}
return 0;
}
@ -266,6 +343,7 @@ findfile_in_dir(const char *fnam, const char *dir, const int recdepth, const cha
fp->fd = NULL;
fp->flags &= ~KBDFILE_PIPE;
fp->flags &= ~KBDFILE_COMPRESSED;
dir_len = strlen(dir);
@ -291,7 +369,6 @@ findfile_in_dir(const char *fnam, const char *dir, const int recdepth, const cha
goto EndScan;
}
struct decompressor *dc = NULL;
unsigned int index = UINT_MAX;
// Scan the directory twice: first for files, then
@ -343,35 +420,35 @@ StartScan:
if (secondpass || ff)
continue;
snprintf(fp->pathname, sizeof(fp->pathname), "%s/%s", dir, namelist[n]->d_name);
if (kbdfile_pathname_sprintf(fp, "%s/%s", dir, namelist[n]->d_name) < 0)
continue;
if (stat(fp->pathname, &st) || !S_ISREG(st.st_mode))
continue;
if (!filecmp(fnam, namelist[n]->d_name, suf, &index, &dc)) {
if (!filecmp(fnam, namelist[n]->d_name, suf, &index)) {
/*
* We cannot immediately try to open the file because
* the suffixes are specified in order of priority. We
* need to find the lowest index.
*/
rc = 0;
}
}
if (!secondpass && index != UINT_MAX) {
snprintf(fp->pathname, sizeof(fp->pathname), "%s/%s%s%s", dir, fnam, suf[index], (dc ? dc->ext : ""));
if (!dc) {
rc = maybe_pipe_open(fp);
if (!secondpass) {
if (index != UINT_MAX) {
rc = rc ?: kbdfile_pathname_sprintf(fp, "%s/%s%s", dir, fnam, suf[index]);
rc = rc ?: maybe_pipe_open(fp);
goto EndScan;
}
if (pipe_open(dc, fp) < 0) {
rc = -1;
goto EndScan;
if (recdepth > 0) {
secondpass = 1;
goto StartScan;
}
}
if (recdepth > 0 && !secondpass) {
secondpass = 1;
goto StartScan;
}
EndScan:
if (namelist != NULL) {
for (int n = 0; n < dirents; n++)
@ -396,9 +473,10 @@ kbdfile_find(const char *fnam, const char *const *dirpath, const char *const *su
}
fp->flags &= ~KBDFILE_PIPE;
fp->flags &= ~KBDFILE_COMPRESSED;
/* Try explicitly given name first */
strncpy(fp->pathname, fnam, sizeof(fp->pathname) - 1);
kbdfile_set_pathname(fp, fnam);
if (!maybe_pipe_open(fp))
return 0;
@ -466,5 +544,5 @@ kbdfile_open(struct kbdfile_ctx *ctx, const char *filename)
int
kbdfile_is_compressed(struct kbdfile *fp)
{
return (fp->flags & KBDFILE_PIPE);
return (fp->flags & KBDFILE_COMPRESSED);
}

View File

@ -40,4 +40,5 @@ message "system info"
apt_get_install \
autoconf automake autopoint libtool libtool-bin pkg-config \
make bison flex gettext kbd strace valgrind libpam0g-dev \
libz-dev libbz2-dev liblzma-dev libzstd-dev \
gcc

View File

@ -62,7 +62,7 @@ AT_CLEANUP
AT_SETUP([test 13])
AT_KEYWORDS([libkbdfile unittest])
AT_SKIP_IF([ test "$(arch)" != "ppc64el" ])
AT_SKIP_IF([ test "$(arch)" = "ppc64el" ])
UNITTEST_MEMCHECK([$abs_builddir/libkbdfile/libkbdfile-test13])
AT_CLEANUP
@ -70,3 +70,8 @@ AT_SETUP([test 14])
AT_KEYWORDS([libkbdfile unittest])
UNITTEST_MEMCHECK([$abs_builddir/libkbdfile/libkbdfile-test14])
AT_CLEANUP
AT_SETUP([test 15])
AT_KEYWORDS([libkbdfile unittest])
UNITTEST_MEMCHECK([$abs_builddir/libkbdfile/libkbdfile-test15])
AT_CLEANUP

View File

@ -28,4 +28,5 @@ noinst_PROGRAMS = \
libkbdfile-test12 \
libkbdfile-test13 \
libkbdfile-test14 \
libkbdfile-test15 \
$(NULL)

View File

@ -17,14 +17,20 @@ main(int argc KBD_ATTR_UNUSED, char **argv KBD_ATTR_UNUSED)
const char *const suffixes[] = { "", ".kmap", ".map", NULL };
const char *expect = TESTDIR "/data/findfile/test_0/keymaps/i386/qwertz/test2";
const char *const searches[] = { "test2", "qwertz/test2", "i386/qwertz/test2", NULL };
int rc = kbdfile_find("test2", dirpath, suffixes, fp);
for (int i = 0; searches[i]; i++) {
int rc;
if (rc != 0)
kbd_error(EXIT_FAILURE, 0, "unable to find file");
rc = kbdfile_find(searches[i], dirpath, suffixes, fp);
if (rc != 0)
kbd_error(EXIT_FAILURE, 0, "unable to find file: %s", searches[i]);
if (strcmp(expect, kbdfile_get_pathname(fp)) != 0)
kbd_error(EXIT_FAILURE, 0, "unexpected file: %s (expected %s)", kbdfile_get_pathname(fp), expect);
if (strcmp(expect, kbdfile_get_pathname(fp)) != 0)
kbd_error(EXIT_FAILURE, 0, "unexpected file: %s (expected %s)", kbdfile_get_pathname(fp), expect);
kbdfile_close(fp);
}
kbdfile_free(fp);

View File

@ -20,7 +20,7 @@ main(int argc KBD_ATTR_UNUSED, char **argv KBD_ATTR_UNUSED)
int rc = 0;
rc = kbdfile_find("simple-1.psf.gz", dirpath, suffixes, fp);
rc = kbdfile_find("simple-1.psf", dirpath, suffixes, fp);
if (rc != 0)
kbd_error(EXIT_FAILURE, 0, "unable to find file");

View File

@ -0,0 +1,86 @@
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <kbdfile.h>
#include "libcommon.h"
int
main(int argc KBD_ATTR_UNUSED, char **argv KBD_ATTR_UNUSED)
{
struct kbdfile *fp = kbdfile_new(NULL);
if (!fp)
kbd_error(EXIT_FAILURE, 0, "unable to create kbdfile");
const char *const dirpath[] = { "", TESTDIR "/data/findfile/test_0/keymaps/**", NULL };
const char *const suffixes[] = { ".map", NULL };
struct testcase {
const char *file;
const char *text;
int compressed;
} cases[] = {
#ifdef HAVE_ZLIB
{ TESTDIR "/data/findfile/test_0/keymaps/i386/qwerty/test3.map.gz", "qwerty zlib", 1 },
#endif
#ifdef HAVE_BZIP2
{ TESTDIR "/data/findfile/test_0/keymaps/i386/qwerty/test3.map.bz2", "qwerty bzip2", 1 },
#endif
#ifdef HAVE_LZMA
{ TESTDIR "/data/findfile/test_0/keymaps/i386/qwerty/test3.map.xz", "qwerty lzma", 1 },
#endif
#ifdef HAVE_ZSTD
{ TESTDIR "/data/findfile/test_0/keymaps/i386/qwerty/test3.map.zst", "qwerty zstd", 1 },
#endif
{ TESTDIR "/data/findfile/test_0/keymaps/i386/qwerty/test3.map", "qwerty", 0 },
{ NULL, NULL, 0 }
};
struct testcase *ts = &cases[0];
setenv("KBDFILE_IGNORE_DECOMP_UTILS", "1", 1);
for (ts = &cases[0]; ts->file; ts++) {
char buf[256];
size_t len;
FILE *f;
//kbd_warning(0, "Check: %s", ts->file);
int rc = kbdfile_find(ts->file, dirpath, suffixes, fp);
if (rc != 0)
kbd_error(EXIT_FAILURE, 0, "unable to find file: %s", ts->file);
if (strcmp(ts->file, kbdfile_get_pathname(fp)) != 0)
kbd_error(EXIT_FAILURE, 0, "unexpected file: %s (expected %s)",
kbdfile_get_pathname(fp), ts->file);
if (ts->compressed && !kbdfile_is_compressed(fp))
kbd_error(EXIT_FAILURE, 0, "not compressed: %s",
kbdfile_get_pathname(fp));
f = kbdfile_get_file(fp);
if (!f)
kbd_error(EXIT_FAILURE, 0, "unable to get file: %s", ts->file);
if (fgets(buf, sizeof(buf), f) == NULL)
kbd_error(EXIT_FAILURE, 0, "unable to read file: %s", ts->file);
len = strlen(buf);
if (buf[len - 1] == '\n')
buf[len - 1] = '\0';
if (strcmp(buf, ts->text))
kbd_error(EXIT_FAILURE, 0, "unexpected content of file: %s", ts->file);
kbdfile_close(fp);
}
kbdfile_free(fp);
return EXIT_SUCCESS;
}