/src/systemd/src/shared/pcre2-util.c
Line | Count | Source |
1 | | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | | |
3 | | #include "dlfcn-util.h" |
4 | | #include "hash-funcs.h" |
5 | | #include "log.h" |
6 | | #include "pcre2-util.h" |
7 | | |
8 | | #if HAVE_PCRE2 |
9 | | DLSYM_PROTOTYPE(pcre2_match_data_create) = NULL; |
10 | | DLSYM_PROTOTYPE(pcre2_match_data_free) = NULL; |
11 | | DLSYM_PROTOTYPE(pcre2_code_free) = NULL; |
12 | | DLSYM_PROTOTYPE(pcre2_compile) = NULL; |
13 | | DLSYM_PROTOTYPE(pcre2_get_error_message) = NULL; |
14 | | DLSYM_PROTOTYPE(pcre2_match) = NULL; |
15 | | DLSYM_PROTOTYPE(pcre2_get_ovector_pointer) = NULL; |
16 | | |
17 | | DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( |
18 | | pcre2_code_hash_ops_free, |
19 | | pcre2_code, |
20 | | (void (*)(const pcre2_code *, struct siphash*))trivial_hash_func, |
21 | | (int (*)(const pcre2_code *, const pcre2_code*))trivial_compare_func, |
22 | | sym_pcre2_code_free); |
23 | | #else |
24 | | const struct hash_ops pcre2_code_hash_ops_free = {}; |
25 | | #endif |
26 | | |
27 | 0 | int dlopen_pcre2(int log_level) { |
28 | | #if HAVE_PCRE2 |
29 | | static void *pcre2_dl = NULL; |
30 | | |
31 | | LIBPCRE2_NOTE(suggested); |
32 | | |
33 | | /* So here's something weird: PCRE2 actually renames the symbols exported by the library via C |
34 | | * macros, so that the exported symbols carry a suffix "_8" but when used from C the suffix is |
35 | | * gone. In the argument list below we ignore this mangling. Surprisingly (at least to me), we |
36 | | * actually get away with that. That's because DLSYM_ARG() uses STRINGIFY() to generate a string |
37 | | * version of the symbol name, and that resolves the macro mapping implicitly already, so that the |
38 | | * string actually contains the "_8" suffix already due to that and we don't have to append it |
39 | | * manually anymore. C is weird. 🤯 */ |
40 | | |
41 | | return dlopen_many_sym_or_warn( |
42 | | &pcre2_dl, "libpcre2-8.so.0", log_level, |
43 | | DLSYM_ARG(pcre2_match_data_create), |
44 | | DLSYM_ARG(pcre2_match_data_free), |
45 | | DLSYM_ARG(pcre2_code_free), |
46 | | DLSYM_ARG(pcre2_compile), |
47 | | DLSYM_ARG(pcre2_get_error_message), |
48 | | DLSYM_ARG(pcre2_match), |
49 | | DLSYM_ARG(pcre2_get_ovector_pointer)); |
50 | | #else |
51 | 0 | return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), |
52 | 0 | "PCRE2 support is not compiled in."); |
53 | 0 | #endif |
54 | 0 | } |
55 | | |
56 | 716 | int pattern_compile_and_log(const char *pattern, PatternCompileCase case_, pcre2_code **ret) { |
57 | | #if HAVE_PCRE2 |
58 | | PCRE2_SIZE erroroffset; |
59 | | _cleanup_(pcre2_code_freep) pcre2_code *p = NULL; |
60 | | unsigned flags = 0; |
61 | | int errorcode, r; |
62 | | |
63 | | assert(pattern); |
64 | | |
65 | | r = dlopen_pcre2(LOG_ERR); |
66 | | if (r < 0) |
67 | | return r; |
68 | | |
69 | | if (case_ == PATTERN_COMPILE_CASE_INSENSITIVE) |
70 | | flags = PCRE2_CASELESS; |
71 | | else if (case_ == PATTERN_COMPILE_CASE_AUTO) { |
72 | | _cleanup_(pcre2_match_data_freep) pcre2_match_data *md = NULL; |
73 | | bool has_case; |
74 | | _cleanup_(pcre2_code_freep) pcre2_code *cs = NULL; |
75 | | |
76 | | md = sym_pcre2_match_data_create(1, NULL); |
77 | | if (!md) |
78 | | return log_oom(); |
79 | | |
80 | | r = pattern_compile_and_log("[[:upper:]]", PATTERN_COMPILE_CASE_SENSITIVE, &cs); |
81 | | if (r < 0) |
82 | | return r; |
83 | | |
84 | | r = sym_pcre2_match(cs, (PCRE2_SPTR8) pattern, PCRE2_ZERO_TERMINATED, 0, 0, md, NULL); |
85 | | has_case = r >= 0; |
86 | | |
87 | | flags = !has_case * PCRE2_CASELESS; |
88 | | } |
89 | | |
90 | | log_debug("Doing case %s matching based on %s", |
91 | | flags & PCRE2_CASELESS ? "insensitive" : "sensitive", |
92 | | case_ != PATTERN_COMPILE_CASE_AUTO ? "request" : "pattern casing"); |
93 | | |
94 | | p = sym_pcre2_compile((PCRE2_SPTR8) pattern, |
95 | | PCRE2_ZERO_TERMINATED, flags, &errorcode, &erroroffset, NULL); |
96 | | if (!p) { |
97 | | unsigned char buf[LINE_MAX]; |
98 | | |
99 | | r = sym_pcre2_get_error_message(errorcode, buf, sizeof buf); |
100 | | |
101 | | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), |
102 | | "Bad pattern \"%s\": %s", pattern, |
103 | | r < 0 ? "unknown error" : (char *)buf); |
104 | | } |
105 | | |
106 | | if (ret) |
107 | | *ret = TAKE_PTR(p); |
108 | | |
109 | | return 0; |
110 | | #else |
111 | 716 | return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "PCRE2 support is not compiled in."); |
112 | 716 | #endif |
113 | 716 | } |
114 | | |
115 | 0 | int pattern_matches_and_log(pcre2_code *compiled_pattern, const char *message, size_t size, size_t *ret_ovec) { |
116 | | #if HAVE_PCRE2 |
117 | | _cleanup_(pcre2_match_data_freep) pcre2_match_data *md = NULL; |
118 | | int r; |
119 | | |
120 | | assert(compiled_pattern); |
121 | | assert(message); |
122 | | /* pattern_compile_and_log() must be called before this function is called and that function already |
123 | | * dlopens pcre2 so we can assert on it being available here. */ |
124 | | assert(sym_pcre2_match); |
125 | | |
126 | | md = sym_pcre2_match_data_create(1, NULL); |
127 | | if (!md) |
128 | | return log_oom(); |
129 | | |
130 | | r = sym_pcre2_match(compiled_pattern, |
131 | | (const unsigned char *)message, |
132 | | size, |
133 | | 0, /* start at offset 0 in the subject */ |
134 | | 0, /* default options */ |
135 | | md, |
136 | | NULL); |
137 | | if (r == PCRE2_ERROR_NOMATCH) |
138 | | return false; |
139 | | if (r < 0) { |
140 | | unsigned char buf[LINE_MAX]; |
141 | | |
142 | | r = sym_pcre2_get_error_message(r, buf, sizeof(buf)); |
143 | | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Pattern matching failed: %s", |
144 | | r < 0 ? "unknown error" : (char*) buf); |
145 | | } |
146 | | |
147 | | if (ret_ovec) { |
148 | | ret_ovec[0] = sym_pcre2_get_ovector_pointer(md)[0]; |
149 | | ret_ovec[1] = sym_pcre2_get_ovector_pointer(md)[1]; |
150 | | } |
151 | | |
152 | | return true; |
153 | | #else |
154 | | return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "PCRE2 support is not compiled in."); |
155 | 0 | #endif |
156 | 0 | } |