When looking at how dlopen for various libs is implemented, I found that the macros hide too much. I find it much easier to see what is going on if 'extern' and '= NULL' are written explicitly. After all, we don't hide those for other definitions, e.g. our style guide says that static variables should be initialized with '= NULL'. With that change, it's much more obvious what is a variable declaration and what is a variable initialization.
172 lines
6.2 KiB
C
172 lines
6.2 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include "dlfcn-util.h"
|
|
#include "log.h"
|
|
#include "pcre2-util.h"
|
|
|
|
#if HAVE_PCRE2
|
|
static void *pcre2_dl = NULL;
|
|
|
|
DLSYM_PROTOTYPE(pcre2_match_data_create) = NULL;
|
|
DLSYM_PROTOTYPE(pcre2_match_data_free) = NULL;
|
|
DLSYM_PROTOTYPE(pcre2_code_free) = NULL;
|
|
DLSYM_PROTOTYPE(pcre2_compile) = NULL;
|
|
DLSYM_PROTOTYPE(pcre2_get_error_message) = NULL;
|
|
DLSYM_PROTOTYPE(pcre2_match) = NULL;
|
|
DLSYM_PROTOTYPE(pcre2_get_ovector_pointer) = NULL;
|
|
|
|
DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(
|
|
pcre2_code_hash_ops_free,
|
|
pcre2_code,
|
|
(void (*)(const pcre2_code *, struct siphash*))trivial_hash_func,
|
|
(int (*)(const pcre2_code *, const pcre2_code*))trivial_compare_func,
|
|
sym_pcre2_code_free);
|
|
#else
|
|
const struct hash_ops pcre2_code_hash_ops_free = {};
|
|
#endif
|
|
|
|
int dlopen_pcre2(void) {
|
|
#if HAVE_PCRE2
|
|
ELF_NOTE_DLOPEN("pcre2",
|
|
"Support for regular expressions",
|
|
ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
|
|
"libpcre2-8.so.0");
|
|
|
|
/* So here's something weird: PCRE2 actually renames the symbols exported by the library via C
|
|
* macros, so that the exported symbols carry a suffix "_8" but when used from C the suffix is
|
|
* gone. In the argument list below we ignore this mangling. Surprisingly (at least to me), we
|
|
* actually get away with that. That's because DLSYM_ARG() useses STRINGIFY() to generate a string
|
|
* version of the symbol name, and that resolves the macro mapping implicitly already, so that the
|
|
* string actually contains the "_8" suffix already due to that and we don't have to append it
|
|
* manually anymore. C is weird. 🤯 */
|
|
|
|
return dlopen_many_sym_or_warn(
|
|
&pcre2_dl, "libpcre2-8.so.0", LOG_ERR,
|
|
DLSYM_ARG(pcre2_match_data_create),
|
|
DLSYM_ARG(pcre2_match_data_free),
|
|
DLSYM_ARG(pcre2_code_free),
|
|
DLSYM_ARG(pcre2_compile),
|
|
DLSYM_ARG(pcre2_get_error_message),
|
|
DLSYM_ARG(pcre2_match),
|
|
DLSYM_ARG(pcre2_get_ovector_pointer));
|
|
#else
|
|
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "PCRE2 support is not compiled in.");
|
|
#endif
|
|
}
|
|
|
|
int pattern_compile_and_log(const char *pattern, PatternCompileCase case_, pcre2_code **ret) {
|
|
#if HAVE_PCRE2
|
|
PCRE2_SIZE erroroffset;
|
|
_cleanup_(sym_pcre2_code_freep) pcre2_code *p = NULL;
|
|
unsigned flags = 0;
|
|
int errorcode, r;
|
|
|
|
assert(pattern);
|
|
|
|
r = dlopen_pcre2();
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (case_ == PATTERN_COMPILE_CASE_INSENSITIVE)
|
|
flags = PCRE2_CASELESS;
|
|
else if (case_ == PATTERN_COMPILE_CASE_AUTO) {
|
|
_cleanup_(sym_pcre2_match_data_freep) pcre2_match_data *md = NULL;
|
|
bool has_case;
|
|
_cleanup_(sym_pcre2_code_freep) pcre2_code *cs = NULL;
|
|
|
|
md = sym_pcre2_match_data_create(1, NULL);
|
|
if (!md)
|
|
return log_oom();
|
|
|
|
r = pattern_compile_and_log("[[:upper:]]", PATTERN_COMPILE_CASE_SENSITIVE, &cs);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = sym_pcre2_match(cs, (PCRE2_SPTR8) pattern, PCRE2_ZERO_TERMINATED, 0, 0, md, NULL);
|
|
has_case = r >= 0;
|
|
|
|
flags = !has_case * PCRE2_CASELESS;
|
|
}
|
|
|
|
log_debug("Doing case %s matching based on %s",
|
|
flags & PCRE2_CASELESS ? "insensitive" : "sensitive",
|
|
case_ != PATTERN_COMPILE_CASE_AUTO ? "request" : "pattern casing");
|
|
|
|
p = sym_pcre2_compile((PCRE2_SPTR8) pattern,
|
|
PCRE2_ZERO_TERMINATED, flags, &errorcode, &erroroffset, NULL);
|
|
if (!p) {
|
|
unsigned char buf[LINE_MAX];
|
|
|
|
r = sym_pcre2_get_error_message(errorcode, buf, sizeof buf);
|
|
|
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
|
"Bad pattern \"%s\": %s", pattern,
|
|
r < 0 ? "unknown error" : (char *)buf);
|
|
}
|
|
|
|
if (ret)
|
|
*ret = TAKE_PTR(p);
|
|
|
|
return 0;
|
|
#else
|
|
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "PCRE2 support is not compiled in.");
|
|
#endif
|
|
}
|
|
|
|
int pattern_matches_and_log(pcre2_code *compiled_pattern, const char *message, size_t size, size_t *ret_ovec) {
|
|
#if HAVE_PCRE2
|
|
_cleanup_(sym_pcre2_match_data_freep) pcre2_match_data *md = NULL;
|
|
int r;
|
|
|
|
assert(compiled_pattern);
|
|
assert(message);
|
|
/* pattern_compile_and_log() must be called before this function is called and that function already
|
|
* dlopens pcre2 so we can assert on it being available here. */
|
|
assert(pcre2_dl);
|
|
|
|
md = sym_pcre2_match_data_create(1, NULL);
|
|
if (!md)
|
|
return log_oom();
|
|
|
|
r = sym_pcre2_match(compiled_pattern,
|
|
(const unsigned char *)message,
|
|
size,
|
|
0, /* start at offset 0 in the subject */
|
|
0, /* default options */
|
|
md,
|
|
NULL);
|
|
if (r == PCRE2_ERROR_NOMATCH)
|
|
return false;
|
|
if (r < 0) {
|
|
unsigned char buf[LINE_MAX];
|
|
|
|
r = sym_pcre2_get_error_message(r, buf, sizeof(buf));
|
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Pattern matching failed: %s",
|
|
r < 0 ? "unknown error" : (char*) buf);
|
|
}
|
|
|
|
if (ret_ovec) {
|
|
ret_ovec[0] = sym_pcre2_get_ovector_pointer(md)[0];
|
|
ret_ovec[1] = sym_pcre2_get_ovector_pointer(md)[1];
|
|
}
|
|
|
|
return true;
|
|
#else
|
|
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "PCRE2 support is not compiled in.");
|
|
#endif
|
|
}
|
|
|
|
void *pattern_free(pcre2_code *p) {
|
|
#if HAVE_PCRE2
|
|
if (!p)
|
|
return NULL;
|
|
|
|
assert(pcre2_dl);
|
|
sym_pcre2_code_free(p);
|
|
return NULL;
|
|
#else
|
|
assert(p == NULL);
|
|
return NULL;
|
|
#endif
|
|
}
|