blob: 195f9a79d99249ab0c5e0bd5b1122e788db5d50b [file] [log] [blame]
Willy Tarreaubaaee002006-06-26 02:48:02 +02001/*
2 * Regex and string management functions.
3 *
Willy Tarreauf4f04122010-01-28 18:10:50 +01004 * Copyright 2000-2010 Willy Tarreau <w@1wt.eu>
Willy Tarreaubaaee002006-06-26 02:48:02 +02005 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version
9 * 2 of the License, or (at your option) any later version.
10 *
11 */
12
13#include <ctype.h>
14#include <stdlib.h>
15#include <string.h>
16
Willy Tarreau7a9ac6d2016-12-21 19:13:14 +010017#include <types/global.h>
Willy Tarreaue3ba5f02006-06-29 18:54:54 +020018#include <common/config.h>
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +020019#include <common/defaults.h>
Willy Tarreau2dd0d472006-06-29 17:53:05 +020020#include <common/regex.h>
21#include <common/standard.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020022#include <proto/log.h>
23
24/* regex trash buffer used by various regex tests */
Emeric Brun272e2522017-06-15 11:53:49 +020025THREAD_LOCAL regmatch_t pmatch[MAX_MATCH]; /* rm_so, rm_eo for regular expressions */
Willy Tarreaubaaee002006-06-26 02:48:02 +020026
Willy Tarreauc8746532014-05-28 23:05:07 +020027int exp_replace(char *dst, unsigned int dst_size, char *src, const char *str, const regmatch_t *matches)
Willy Tarreaubaaee002006-06-26 02:48:02 +020028{
29 char *old_dst = dst;
Sasha Pachevc6002042014-05-26 12:33:48 -060030 char* dst_end = dst + dst_size;
Willy Tarreaubaaee002006-06-26 02:48:02 +020031
32 while (*str) {
33 if (*str == '\\') {
34 str++;
Sasha Pachevc6002042014-05-26 12:33:48 -060035 if (!*str)
36 return -1;
37
Willy Tarreau8f8e6452007-06-17 21:51:38 +020038 if (isdigit((unsigned char)*str)) {
Willy Tarreaubaaee002006-06-26 02:48:02 +020039 int len, num;
40
41 num = *str - '0';
42 str++;
43
44 if (matches[num].rm_eo > -1 && matches[num].rm_so > -1) {
45 len = matches[num].rm_eo - matches[num].rm_so;
Sasha Pachevc6002042014-05-26 12:33:48 -060046
47 if (dst + len >= dst_end)
48 return -1;
49
Willy Tarreaubaaee002006-06-26 02:48:02 +020050 memcpy(dst, src + matches[num].rm_so, len);
51 dst += len;
52 }
53
54 } else if (*str == 'x') {
55 unsigned char hex1, hex2;
56 str++;
57
Sasha Pachevc6002042014-05-26 12:33:48 -060058 if (!*str)
59 return -1;
60
Willy Tarreaubaaee002006-06-26 02:48:02 +020061 hex1 = toupper(*str++) - '0';
Sasha Pachevc6002042014-05-26 12:33:48 -060062
63 if (!*str)
64 return -1;
65
Willy Tarreaubaaee002006-06-26 02:48:02 +020066 hex2 = toupper(*str++) - '0';
67
68 if (hex1 > 9) hex1 -= 'A' - '9' - 1;
69 if (hex2 > 9) hex2 -= 'A' - '9' - 1;
Sasha Pachevc6002042014-05-26 12:33:48 -060070
71 if (dst >= dst_end)
72 return -1;
73
Willy Tarreaubaaee002006-06-26 02:48:02 +020074 *dst++ = (hex1<<4) + hex2;
75 } else {
Sasha Pachevc6002042014-05-26 12:33:48 -060076 if (dst >= dst_end)
77 return -1;
78
Willy Tarreaubaaee002006-06-26 02:48:02 +020079 *dst++ = *str++;
80 }
81 } else {
Sasha Pachevc6002042014-05-26 12:33:48 -060082 if (dst >= dst_end)
83 return -1;
84
Willy Tarreaubaaee002006-06-26 02:48:02 +020085 *dst++ = *str++;
86 }
87 }
Sasha Pachevc6002042014-05-26 12:33:48 -060088 if (dst >= dst_end)
89 return -1;
90
Willy Tarreaubaaee002006-06-26 02:48:02 +020091 *dst = '\0';
92 return dst - old_dst;
93}
94
95/* returns NULL if the replacement string <str> is valid, or the pointer to the first error */
Willy Tarreaub17916e2006-10-15 15:17:57 +020096const char *check_replace_string(const char *str)
Willy Tarreaubaaee002006-06-26 02:48:02 +020097{
Willy Tarreaub17916e2006-10-15 15:17:57 +020098 const char *err = NULL;
Willy Tarreaubaaee002006-06-26 02:48:02 +020099 while (*str) {
100 if (*str == '\\') {
101 err = str; /* in case of a backslash, we return the pointer to it */
102 str++;
103 if (!*str)
104 return err;
Willy Tarreau8f8e6452007-06-17 21:51:38 +0200105 else if (isdigit((unsigned char)*str))
Willy Tarreaubaaee002006-06-26 02:48:02 +0200106 err = NULL;
107 else if (*str == 'x') {
108 str++;
109 if (!ishex(*str))
110 return err;
111 str++;
112 if (!ishex(*str))
113 return err;
114 err = NULL;
115 }
116 else {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100117 ha_warning("'\\%c' : deprecated use of a backslash before something not '\\','x' or a digit.\n", *str);
Willy Tarreaubaaee002006-06-26 02:48:02 +0200118 err = NULL;
119 }
120 }
121 str++;
122 }
123 return err;
124}
125
126
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200127/* This function apply regex. It take const null terminated char as input.
128 * If the function doesn't match, it returns false, else it returns true.
129 * When it is compiled with JIT, this function execute strlen on the subject.
Willy Tarreau15a53a42015-01-21 13:39:42 +0100130 * Currently the only supported flag is REG_NOTBOL.
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200131 */
132int regex_exec_match(const struct my_regex *preg, const char *subject,
Willy Tarreau15a53a42015-01-21 13:39:42 +0100133 size_t nmatch, regmatch_t pmatch[], int flags) {
David Carlierf2592b22016-11-21 21:25:58 +0000134#if defined(USE_PCRE) || defined(USE_PCRE_JIT) || defined(USE_PCRE2) || defined(USE_PCRE2_JIT)
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200135 int ret;
David Carlierf2592b22016-11-21 21:25:58 +0000136#ifdef USE_PCRE2
137 PCRE2_SIZE *matches;
138 pcre2_match_data *pm;
139#else
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200140 int matches[MAX_MATCH * 3];
David Carlierf2592b22016-11-21 21:25:58 +0000141#endif
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200142 int enmatch;
143 int i;
Willy Tarreau15a53a42015-01-21 13:39:42 +0100144 int options;
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200145
146 /* Silently limit the number of allowed matches. max
147 * match i the maximum value for match, in fact this
148 * limit is not applyied.
149 */
David Carlierf2592b22016-11-21 21:25:58 +0000150
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200151 enmatch = nmatch;
152 if (enmatch > MAX_MATCH)
153 enmatch = MAX_MATCH;
154
Willy Tarreau15a53a42015-01-21 13:39:42 +0100155 options = 0;
156 if (flags & REG_NOTBOL)
David Carlierf2592b22016-11-21 21:25:58 +0000157#ifdef USE_PCRE2
158 options |= PCRE2_NOTBOL;
159#else
Willy Tarreau15a53a42015-01-21 13:39:42 +0100160 options |= PCRE_NOTBOL;
David Carlierf2592b22016-11-21 21:25:58 +0000161#endif
Willy Tarreau15a53a42015-01-21 13:39:42 +0100162
David Carlierf2592b22016-11-21 21:25:58 +0000163 /* The value returned by pcre_exec()/pcre2_match() is one more than the highest numbered
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200164 * pair that has been set. For example, if two substrings have been captured,
165 * the returned value is 3. If there are no capturing subpatterns, the return
166 * value from a successful match is 1, indicating that just the first pair of
167 * offsets has been set.
168 *
Joseph Herlanteda75482018-11-15 14:46:29 -0800169 * It seems that this function returns 0 if it detects more matches than available
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200170 * space in the matches array.
171 */
David Carlierf2592b22016-11-21 21:25:58 +0000172#ifdef USE_PCRE2
173 pm = pcre2_match_data_create_from_pattern(preg->reg, NULL);
174 ret = pcre2_match(preg->reg, (PCRE2_SPTR)subject, (PCRE2_SIZE)strlen(subject), 0, options, pm, NULL);
175
176 if (ret < 0) {
177 pcre2_match_data_free(pm);
178 return 0;
179 }
180
181 matches = pcre2_get_ovector_pointer(pm);
182#else
Willy Tarreau15a53a42015-01-21 13:39:42 +0100183 ret = pcre_exec(preg->reg, preg->extra, subject, strlen(subject), 0, options, matches, enmatch * 3);
David Carlierf2592b22016-11-21 21:25:58 +0000184
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200185 if (ret < 0)
186 return 0;
David Carlierf2592b22016-11-21 21:25:58 +0000187#endif
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200188
189 if (ret == 0)
190 ret = enmatch;
191
192 for (i=0; i<nmatch; i++) {
193 /* Copy offset. */
194 if (i < ret) {
195 pmatch[i].rm_so = matches[(i*2)];
196 pmatch[i].rm_eo = matches[(i*2)+1];
197 continue;
198 }
199 /* Set the unmatvh flag (-1). */
200 pmatch[i].rm_so = -1;
201 pmatch[i].rm_eo = -1;
202 }
David Carlierf2592b22016-11-21 21:25:58 +0000203#ifdef USE_PCRE2
204 pcre2_match_data_free(pm);
205#endif
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200206 return 1;
207#else
208 int match;
Willy Tarreau15a53a42015-01-21 13:39:42 +0100209
210 flags &= REG_NOTBOL;
211 match = regexec(&preg->regex, subject, nmatch, pmatch, flags);
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200212 if (match == REG_NOMATCH)
213 return 0;
214 return 1;
215#endif
216}
217
218/* This function apply regex. It take a "char *" ans length as input. The
219 * <subject> can be modified during the processing. If the function doesn't
220 * match, it returns false, else it returns true.
221 * When it is compiled with standard POSIX regex or PCRE, this function add
222 * a temporary null chracters at the end of the <subject>. The <subject> must
Willy Tarreau15a53a42015-01-21 13:39:42 +0100223 * have a real length of <length> + 1. Currently the only supported flag is
224 * REG_NOTBOL.
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200225 */
226int regex_exec_match2(const struct my_regex *preg, char *subject, int length,
Willy Tarreau15a53a42015-01-21 13:39:42 +0100227 size_t nmatch, regmatch_t pmatch[], int flags) {
David Carlierf2592b22016-11-21 21:25:58 +0000228#if defined(USE_PCRE) || defined(USE_PCRE_JIT) || defined(USE_PCRE2) || defined(USE_PCRE2_JIT)
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200229 int ret;
David Carlierf2592b22016-11-21 21:25:58 +0000230#ifdef USE_PCRE2
231 PCRE2_SIZE *matches;
232 pcre2_match_data *pm;
233#else
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200234 int matches[MAX_MATCH * 3];
David Carlierf2592b22016-11-21 21:25:58 +0000235#endif
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200236 int enmatch;
237 int i;
Willy Tarreau15a53a42015-01-21 13:39:42 +0100238 int options;
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200239
240 /* Silently limit the number of allowed matches. max
241 * match i the maximum value for match, in fact this
242 * limit is not applyied.
243 */
244 enmatch = nmatch;
245 if (enmatch > MAX_MATCH)
246 enmatch = MAX_MATCH;
247
Willy Tarreau15a53a42015-01-21 13:39:42 +0100248 options = 0;
249 if (flags & REG_NOTBOL)
David Carlierf2592b22016-11-21 21:25:58 +0000250#ifdef USE_PCRE2
251 options |= PCRE2_NOTBOL;
252#else
Willy Tarreau15a53a42015-01-21 13:39:42 +0100253 options |= PCRE_NOTBOL;
David Carlierf2592b22016-11-21 21:25:58 +0000254#endif
Willy Tarreau15a53a42015-01-21 13:39:42 +0100255
David Carlierf2592b22016-11-21 21:25:58 +0000256 /* The value returned by pcre_exec()/pcre2_match() is one more than the highest numbered
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200257 * pair that has been set. For example, if two substrings have been captured,
258 * the returned value is 3. If there are no capturing subpatterns, the return
259 * value from a successful match is 1, indicating that just the first pair of
260 * offsets has been set.
261 *
Joseph Herlanteda75482018-11-15 14:46:29 -0800262 * It seems that this function returns 0 if it detects more matches than available
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200263 * space in the matches array.
264 */
David Carlierf2592b22016-11-21 21:25:58 +0000265#ifdef USE_PCRE2
266 pm = pcre2_match_data_create_from_pattern(preg->reg, NULL);
267 ret = pcre2_match(preg->reg, (PCRE2_SPTR)subject, (PCRE2_SIZE)length, 0, options, pm, NULL);
268
269 if (ret < 0) {
270 pcre2_match_data_free(pm);
271 return 0;
272 }
273
274 matches = pcre2_get_ovector_pointer(pm);
275#else
Willy Tarreau15a53a42015-01-21 13:39:42 +0100276 ret = pcre_exec(preg->reg, preg->extra, subject, length, 0, options, matches, enmatch * 3);
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200277 if (ret < 0)
278 return 0;
David Carlierf2592b22016-11-21 21:25:58 +0000279#endif
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200280
281 if (ret == 0)
282 ret = enmatch;
283
284 for (i=0; i<nmatch; i++) {
285 /* Copy offset. */
286 if (i < ret) {
287 pmatch[i].rm_so = matches[(i*2)];
288 pmatch[i].rm_eo = matches[(i*2)+1];
289 continue;
290 }
291 /* Set the unmatvh flag (-1). */
292 pmatch[i].rm_so = -1;
293 pmatch[i].rm_eo = -1;
294 }
David Carlierf2592b22016-11-21 21:25:58 +0000295#ifdef USE_PCRE2
296 pcre2_match_data_free(pm);
297#endif
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200298 return 1;
299#else
300 char old_char = subject[length];
301 int match;
Willy Tarreau15a53a42015-01-21 13:39:42 +0100302
303 flags &= REG_NOTBOL;
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200304 subject[length] = 0;
Willy Tarreau15a53a42015-01-21 13:39:42 +0100305 match = regexec(&preg->regex, subject, nmatch, pmatch, flags);
Thierry FOURNIERb8f980c2014-06-11 13:59:05 +0200306 subject[length] = old_char;
307 if (match == REG_NOMATCH)
308 return 0;
309 return 1;
310#endif
311}
312
Dragan Dosen26743032019-04-30 15:54:36 +0200313struct my_regex *regex_comp(const char *str, int cs, int cap, char **err)
Thierry FOURNIERed5a4ae2013-10-14 14:07:36 +0200314{
Dragan Dosen26743032019-04-30 15:54:36 +0200315 struct my_regex *regex = NULL;
Thierry FOURNIER26202762014-06-18 11:50:51 +0200316#if defined(USE_PCRE) || defined(USE_PCRE_JIT)
Thierry FOURNIERed5a4ae2013-10-14 14:07:36 +0200317 int flags = 0;
318 const char *error;
319 int erroffset;
Dragan Dosen26743032019-04-30 15:54:36 +0200320#elif defined(USE_PCRE2) || defined(USE_PCRE2_JIT)
321 int flags = 0;
322 int errn;
323#if defined(USE_PCRE2_JIT)
324 int jit;
325#endif
326 PCRE2_UCHAR error[256];
327 PCRE2_SIZE erroffset;
328#else
329 int flags = REG_EXTENDED;
330#endif
331
332 regex = calloc(1, sizeof(*regex));
333 if (!regex) {
334 memprintf(err, "not enough memory to build regex");
335 goto out_fail_alloc;
336 }
Thierry FOURNIERed5a4ae2013-10-14 14:07:36 +0200337
Dragan Dosen26743032019-04-30 15:54:36 +0200338#if defined(USE_PCRE) || defined(USE_PCRE_JIT)
Thierry FOURNIERed5a4ae2013-10-14 14:07:36 +0200339 if (!cs)
340 flags |= PCRE_CASELESS;
341 if (!cap)
342 flags |= PCRE_NO_AUTO_CAPTURE;
343
344 regex->reg = pcre_compile(str, flags, &error, &erroffset, NULL);
345 if (!regex->reg) {
346 memprintf(err, "regex '%s' is invalid (error=%s, erroffset=%d)", str, error, erroffset);
Dragan Dosen26743032019-04-30 15:54:36 +0200347 goto out_fail_alloc;
Thierry FOURNIERed5a4ae2013-10-14 14:07:36 +0200348 }
349
350 regex->extra = pcre_study(regex->reg, PCRE_STUDY_JIT_COMPILE, &error);
Christian Ruppert955f4612014-10-29 17:05:53 +0100351 if (!regex->extra && error != NULL) {
Thierry FOURNIERed5a4ae2013-10-14 14:07:36 +0200352 pcre_free(regex->reg);
353 memprintf(err, "failed to compile regex '%s' (error=%s)", str, error);
Dragan Dosen26743032019-04-30 15:54:36 +0200354 goto out_fail_alloc;
Thierry FOURNIERed5a4ae2013-10-14 14:07:36 +0200355 }
David Carlierf2592b22016-11-21 21:25:58 +0000356#elif defined(USE_PCRE2) || defined(USE_PCRE2_JIT)
David Carlierf2592b22016-11-21 21:25:58 +0000357 if (!cs)
358 flags |= PCRE2_CASELESS;
359 if (!cap)
360 flags |= PCRE2_NO_AUTO_CAPTURE;
361
362 regex->reg = pcre2_compile((PCRE2_SPTR)str, PCRE2_ZERO_TERMINATED, flags, &errn, &erroffset, NULL);
363 if (!regex->reg) {
364 pcre2_get_error_message(errn, error, sizeof(error));
365 memprintf(err, "regex '%s' is invalid (error=%s, erroffset=%zu)", str, error, erroffset);
Dragan Dosen26743032019-04-30 15:54:36 +0200366 goto out_fail_alloc;
David Carlierf2592b22016-11-21 21:25:58 +0000367 }
368
369#if defined(USE_PCRE2_JIT)
370 jit = pcre2_jit_compile(regex->reg, PCRE2_JIT_COMPLETE);
371 /*
372 * We end if it is an error not related to lack of JIT support
373 * in a case of JIT support missing pcre2_jit_compile is "no-op"
374 */
375 if (jit < 0 && jit != PCRE2_ERROR_JIT_BADOPTION) {
376 pcre2_code_free(regex->reg);
377 memprintf(err, "regex '%s' jit compilation failed", str);
Dragan Dosen26743032019-04-30 15:54:36 +0200378 goto out_fail_alloc;
David Carlierf2592b22016-11-21 21:25:58 +0000379 }
380#endif
381
Thierry FOURNIERed5a4ae2013-10-14 14:07:36 +0200382#else
Thierry FOURNIERed5a4ae2013-10-14 14:07:36 +0200383 if (!cs)
384 flags |= REG_ICASE;
385 if (!cap)
386 flags |= REG_NOSUB;
Willy Tarreaubaaee002006-06-26 02:48:02 +0200387
Thierry FOURNIER799c0422013-12-06 20:36:20 +0100388 if (regcomp(&regex->regex, str, flags) != 0) {
Thierry FOURNIERed5a4ae2013-10-14 14:07:36 +0200389 memprintf(err, "regex '%s' is invalid", str);
Dragan Dosen26743032019-04-30 15:54:36 +0200390 goto out_fail_alloc;
Thierry FOURNIERed5a4ae2013-10-14 14:07:36 +0200391 }
392#endif
Dragan Dosen26743032019-04-30 15:54:36 +0200393 return regex;
394
395 out_fail_alloc:
396 free(regex);
397 return NULL;
Thierry FOURNIERed5a4ae2013-10-14 14:07:36 +0200398}
Willy Tarreaubaaee002006-06-26 02:48:02 +0200399
Willy Tarreau80713382018-11-26 10:19:54 +0100400static void regex_register_build_options(void)
Willy Tarreau7a9ac6d2016-12-21 19:13:14 +0100401{
402 char *ptr = NULL;
403
404#ifdef USE_PCRE
405 memprintf(&ptr, "Built with PCRE version : %s", (HAP_XSTRING(Z PCRE_PRERELEASE)[1] == 0)?
406 HAP_XSTRING(PCRE_MAJOR.PCRE_MINOR PCRE_DATE) :
407 HAP_XSTRING(PCRE_MAJOR.PCRE_MINOR) HAP_XSTRING(PCRE_PRERELEASE PCRE_DATE));
408 memprintf(&ptr, "%s\nRunning on PCRE version : %s", ptr, pcre_version());
409
410 memprintf(&ptr, "%s\nPCRE library supports JIT : %s", ptr,
411#ifdef USE_PCRE_JIT
412 ({
413 int r;
414 pcre_config(PCRE_CONFIG_JIT, &r);
415 r ? "yes" : "no (libpcre build without JIT?)";
416 })
417#else
418 "no (USE_PCRE_JIT not set)"
419#endif
420 );
David Carlierf2592b22016-11-21 21:25:58 +0000421#endif /* USE_PCRE */
422
423#ifdef USE_PCRE2
424 memprintf(&ptr, "Built with PCRE2 version : %s", (HAP_XSTRING(Z PCRE2_PRERELEASE)[1] == 0) ?
425 HAP_XSTRING(PCRE2_MAJOR.PCRE2_MINOR PCRE2_DATE) :
426 HAP_XSTRING(PCRE2_MAJOR.PCRE2_MINOR) HAP_XSTRING(PCRE2_PRERELEASE PCRE2_DATE));
427 memprintf(&ptr, "%s\nPCRE2 library supports JIT : %s", ptr,
428#ifdef USE_PCRE2_JIT
429 ({
430 int r;
431 pcre2_config(PCRE2_CONFIG_JIT, &r);
432 r ? "yes" : "no (libpcre2 build without JIT?)";
433 })
Willy Tarreau7a9ac6d2016-12-21 19:13:14 +0100434#else
David Carlierf2592b22016-11-21 21:25:58 +0000435 "no (USE_PCRE2_JIT not set)"
436#endif
437 );
438#endif /* USE_PCRE2 */
439
440#if !defined(USE_PCRE) && !defined(USE_PCRE2)
441 memprintf(&ptr, "Built without PCRE or PCRE2 support (using libc's regex instead)");
Willy Tarreau7a9ac6d2016-12-21 19:13:14 +0100442#endif
443 hap_register_build_opts(ptr, 1);
444}
445
Willy Tarreau80713382018-11-26 10:19:54 +0100446INITCALL0(STG_REGISTER, regex_register_build_options);
447
Willy Tarreaubaaee002006-06-26 02:48:02 +0200448/*
449 * Local variables:
450 * c-indent-level: 8
451 * c-basic-offset: 8
452 * End:
453 */