Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * OpenSIPS configuration file pre-processing |
3 | | * |
4 | | * Copyright (C) 2019 OpenSIPS Solutions |
5 | | * |
6 | | * This file is part of opensips, a free SIP server. |
7 | | * |
8 | | * opensips is free software; you can redistribute it and/or modify |
9 | | * it under the terms of the GNU General Public License as published by |
10 | | * the Free Software Foundation; either version 2 of the License, or |
11 | | * (at your option) any later version |
12 | | * |
13 | | * opensips is distributed in the hope that it will be useful, |
14 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | | * GNU General Public License for more details. |
17 | | * |
18 | | * You should have received a copy of the GNU General Public License |
19 | | * along with this program; if not, write to the Free Software |
20 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA |
21 | | */ |
22 | | |
23 | | #define _WITH_GETLINE |
24 | | |
25 | | #include <stdlib.h> |
26 | | #include <stdio.h> |
27 | | #include <fcntl.h> |
28 | | #include <errno.h> |
29 | | #include <libgen.h> |
30 | | #include <sys/stat.h> |
31 | | |
32 | | #include "config.h" |
33 | | #include "globals.h" |
34 | | #include "cfg_pp.h" |
35 | | #include "ut.h" |
36 | | |
37 | | extern char *finame; |
38 | | extern int startline; |
39 | | extern int column; |
40 | | |
41 | | extern FILE *yyin; |
42 | | extern int yyparse(); |
43 | | extern int yyrestart(FILE*); |
44 | | #ifdef DEBUG_PARSER |
45 | | extern int yydebug; |
46 | | #endif |
47 | | |
48 | | str include_v1 = str_init("include_file"); |
49 | | str include_v2 = str_init("import_file"); |
50 | | |
51 | | str cfgtok_line = str_init("__OSSPP_LINE__"); |
52 | | str cfgtok_filebegin = str_init("__OSSPP_FILEBEGIN__"); |
53 | | str cfgtok_fileend = str_init("__OSSPP_FILEEND__"); |
54 | | |
55 | | static int flatten_opensips_cfg(FILE *cfg, const char *cfg_path, str *out); |
56 | | static int exec_preprocessor(FILE *flat_cfg, const char *preproc_cmdline, |
57 | | str *out); |
58 | | |
59 | | static struct cfg_context *cfg_context_new_file(const char *path); |
60 | | static void cfg_context_reset_all(void); |
61 | | static void cfg_context_append_line(struct cfg_context *con, |
62 | | char *line, int len); |
63 | | |
64 | | int parse_opensips_cfg(const char *cfg_file, const char *preproc_cmdline, |
65 | | str *ret_buffer) |
66 | 0 | { |
67 | 0 | FILE *cfg_stream; |
68 | 0 | str cfg_buf, pp_buf; |
69 | | |
70 | | /* fill missing arguments with the default values*/ |
71 | 0 | if (!cfg_file) |
72 | 0 | cfg_file = CFG_FILE; |
73 | |
|
74 | 0 | if (strlen(cfg_file) == 1 && cfg_file[0] == '-') { |
75 | 0 | cfg_stream = stdin; |
76 | 0 | } else { |
77 | | /* load config file or die */ |
78 | 0 | cfg_stream = fopen(cfg_file, "r"); |
79 | 0 | if (!cfg_stream) { |
80 | 0 | LM_ERR("loading config file %s: %s\n", cfg_file, |
81 | 0 | strerror(errno)); |
82 | 0 | return -1; |
83 | 0 | } |
84 | 0 | } |
85 | | |
86 | 0 | cfg_context_reset_all(); |
87 | |
|
88 | 0 | if (flatten_opensips_cfg(cfg_stream, |
89 | 0 | cfg_stream == stdin ? "stdin" : cfg_file, &cfg_buf) < 0) { |
90 | 0 | LM_ERR("failed to resolve file imports for %s\n", cfg_file); |
91 | 0 | return -1; |
92 | 0 | } |
93 | | |
94 | 0 | cfg_stream = fmemopen(cfg_buf.s, cfg_buf.len, "r"); |
95 | 0 | if (!cfg_stream) { |
96 | 0 | LM_ERR("failed to open file for flattened cfg buffer\n"); |
97 | 0 | goto out_free; |
98 | 0 | } |
99 | | |
100 | 0 | if (preproc_cmdline) { |
101 | 0 | if (exec_preprocessor(cfg_stream, preproc_cmdline, &pp_buf) < 0) { |
102 | 0 | LM_ERR("failed to exec preprocessor cmd: '%s'\n", preproc_cmdline); |
103 | 0 | goto out_free; |
104 | 0 | } |
105 | 0 | free(cfg_buf.s); |
106 | 0 | cfg_buf = pp_buf; |
107 | |
|
108 | 0 | cfg_stream = fmemopen(cfg_buf.s, cfg_buf.len, "r"); |
109 | 0 | if (!cfg_stream) { |
110 | 0 | LM_ERR("failed to open file for processed cfg buffer\n"); |
111 | 0 | goto out_free; |
112 | 0 | } |
113 | 0 | } |
114 | | |
115 | | #ifdef DEBUG_PARSER |
116 | | /* used for parser debugging */ |
117 | | yydebug = 1; |
118 | | #endif |
119 | | |
120 | | /* parse the config file, prior to this only default values |
121 | | e.g. for debugging settings will be used */ |
122 | 0 | yyin = cfg_stream; |
123 | 0 | yyrestart(yyin); |
124 | 0 | cfg_errors = 0; |
125 | 0 | if (yyparse() != 0 || cfg_errors) { |
126 | 0 | LM_ERR("bad config file (%d errors)\n", cfg_errors); |
127 | 0 | fclose(cfg_stream); |
128 | 0 | goto out_free; |
129 | 0 | } |
130 | | |
131 | 0 | fclose(cfg_stream); |
132 | | |
133 | | /* do we have to return the cfg buffer? */ |
134 | 0 | if (ret_buffer) |
135 | 0 | *ret_buffer = cfg_buf; |
136 | 0 | else |
137 | 0 | free(cfg_buf.s); |
138 | |
|
139 | 0 | return 0; |
140 | | |
141 | 0 | out_free: |
142 | 0 | free(cfg_buf.s); |
143 | 0 | return -1; |
144 | 0 | } |
145 | | |
146 | | static int extend_cfg_buf(char **buf, int *sz, int *bytes_left, int needed) |
147 | 0 | { |
148 | 0 | if (needed < 4096) |
149 | 0 | needed = 4096; |
150 | |
|
151 | 0 | *buf = realloc(*buf, *sz + needed); |
152 | 0 | if (!*buf) { |
153 | 0 | LM_ERR("failed to extend cfg buf to %d\n", *sz + needed); |
154 | 0 | return -1; |
155 | 0 | } |
156 | | |
157 | 0 | *sz += needed; |
158 | 0 | *bytes_left += needed; |
159 | 0 | return 0; |
160 | 0 | } |
161 | | |
162 | | /* search for '(include|import)_file "filepath"' patterns */ |
163 | | int mk_included_file_path(char *line, int line_len, const char *current_dir, |
164 | | char **out_path) |
165 | 0 | { |
166 | 0 | #define MAX_INCLUDE_FNAME 256 |
167 | 0 | static char full_path[MAX_INCLUDE_FNAME]; |
168 | 0 | struct stat _; |
169 | 0 | char *p = NULL, enclose = 0; |
170 | 0 | int len1, len2, fplen; |
171 | |
|
172 | 0 | while (line_len > 0 && is_ws(*line)) { |
173 | 0 | line_len--; |
174 | 0 | line++; |
175 | 0 | } |
176 | |
|
177 | 0 | if (line_len > include_v1.len && |
178 | 0 | !memcmp(line, include_v1.s, include_v1.len)) { |
179 | 0 | p = line + include_v1.len; |
180 | 0 | line_len -= include_v1.len; |
181 | 0 | } else if (line_len > include_v2.len && |
182 | 0 | !memcmp(line, include_v2.s, include_v2.len)) { |
183 | 0 | p = line + include_v2.len; |
184 | 0 | line_len -= include_v2.len; |
185 | 0 | } |
186 | |
|
187 | 0 | if (!p) |
188 | 0 | return 1; |
189 | | |
190 | 0 | while (line_len > 0 && isspace(*p)) { |
191 | 0 | line_len--; |
192 | 0 | p++; |
193 | 0 | } |
194 | |
|
195 | 0 | if (line_len < 3) // "f" |
196 | 0 | return -1; |
197 | | |
198 | 0 | if (*p != '"' && *p != '\'') |
199 | 0 | return -1; |
200 | | |
201 | 0 | enclose = *p++; |
202 | 0 | line_len--; |
203 | |
|
204 | 0 | *out_path = p; |
205 | |
|
206 | 0 | while (line_len > 0 && *p != enclose) { |
207 | 0 | line_len--; |
208 | 0 | p++; |
209 | 0 | } |
210 | |
|
211 | 0 | if (line_len == 0 || p - *out_path < 2) // ""_ |
212 | 0 | return -1; |
213 | | |
214 | 0 | *p = '\0'; |
215 | | |
216 | | /* is it a relative-path import? */ |
217 | 0 | if (**out_path != '/' && stat(*out_path, &_) < 0) { |
218 | 0 | LM_DBG("%s not found (%d, %s), assuming it's relative to source cfg\n", |
219 | 0 | *out_path, errno, strerror(errno)); |
220 | | |
221 | | /* this relative path is not inside the startup dir, |
222 | | * so maybe it's relative to the importing file */ |
223 | 0 | len1 = strlen(current_dir); |
224 | 0 | len2 = strlen(*out_path); |
225 | |
|
226 | 0 | if (len1 + 1 + len2 + 1 > MAX_INCLUDE_FNAME) { |
227 | 0 | LM_ERR("file path too long (max %d): '%s' + '%s'\n", |
228 | 0 | MAX_INCLUDE_FNAME, current_dir, *out_path); |
229 | 0 | return -1; |
230 | 0 | } |
231 | | |
232 | 0 | memcpy(full_path, current_dir, len1); |
233 | 0 | fplen = len1; |
234 | | |
235 | | /* this test can only fail when opensips runs from '/' */ |
236 | 0 | if (current_dir[len1 - 1] != '/') |
237 | 0 | full_path[fplen++] = '/'; |
238 | |
|
239 | 0 | memcpy(full_path + fplen, *out_path, len2); |
240 | 0 | fplen += len2; |
241 | |
|
242 | 0 | full_path[fplen] = '\0'; |
243 | 0 | *out_path = full_path; |
244 | 0 | } |
245 | | |
246 | 0 | LM_DBG("preparing to include %s\n", *out_path); |
247 | 0 | return 0; |
248 | 0 | } |
249 | | |
250 | | static struct cfg_context { |
251 | | const char *path; |
252 | | const char *dirname; /* useful for relative path includes */ |
253 | | int loc; |
254 | | char **lines; |
255 | | int bufsz; |
256 | | struct cfg_context *next; |
257 | | } *__ccon; |
258 | | |
259 | | static void cfg_context_reset_all(void) |
260 | 0 | { |
261 | 0 | struct cfg_context *pos = NULL, *it = __ccon; |
262 | |
|
263 | 0 | while ( it && (it != __ccon || !pos) ) { |
264 | 0 | pos = it; |
265 | 0 | it = it->next; |
266 | 0 | free((char*)pos->path); |
267 | 0 | free((char*)pos->dirname); |
268 | 0 | free(pos->lines); |
269 | 0 | free(pos); |
270 | 0 | }; |
271 | 0 | __ccon = NULL; |
272 | 0 | } |
273 | | |
274 | | static struct cfg_context *cfg_context_new_file(const char *path) |
275 | 0 | { |
276 | 0 | struct cfg_context *con, *it; |
277 | 0 | char *cpy; |
278 | |
|
279 | 0 | for (it = __ccon; it; it = it->next) |
280 | 0 | if (!strcmp(it->path, path)) |
281 | 0 | return it; |
282 | | |
283 | 0 | con = malloc(sizeof *con); |
284 | 0 | memset(con, 0, sizeof *con); |
285 | |
|
286 | 0 | con->path = strdup(path); |
287 | |
|
288 | 0 | cpy = strdup(path); |
289 | 0 | con->dirname = strdup(dirname(cpy)); |
290 | 0 | free(cpy); |
291 | |
|
292 | 0 | con->lines = malloc(32 * sizeof *con->lines); |
293 | 0 | con->bufsz = 32; |
294 | |
|
295 | 0 | add_last(con, __ccon); |
296 | 0 | return con; |
297 | 0 | } |
298 | | |
299 | | static void cfg_context_append_line(struct cfg_context *con, |
300 | | char *line, int len) |
301 | 0 | { |
302 | 0 | if (con->loc == con->bufsz) { |
303 | 0 | con->bufsz *= 2; |
304 | 0 | con->lines = realloc(con->lines, con->bufsz * sizeof *con->lines); |
305 | 0 | if (!con->lines) |
306 | 0 | return; |
307 | 0 | } |
308 | | |
309 | 0 | con->lines[con->loc] = malloc(len + 1); |
310 | 0 | memcpy(con->lines[con->loc], line, len); |
311 | 0 | con->lines[con->loc][len] = '\0'; |
312 | |
|
313 | 0 | con->loc++; |
314 | 0 | } |
315 | | |
316 | | static int __flatten_opensips_cfg(FILE *cfg, const char *cfg_path, |
317 | | char **flattened, int *sz, int *bytes_left, int reclev) |
318 | 0 | { |
319 | 0 | FILE *included_cfg; |
320 | 0 | ssize_t line_len; |
321 | 0 | char *line = NULL, *included_cfg_path; |
322 | 0 | unsigned long line_buf_sz = 0; |
323 | 0 | int cfg_path_len = strlen(cfg_path); |
324 | 0 | int line_counter = 1, needed, printed; |
325 | 0 | struct cfg_context *con = NULL; |
326 | |
|
327 | 0 | if (reclev > 50) { |
328 | 0 | LM_ERR("Maximum import depth reached (50) or " |
329 | 0 | "you have an infinite include_file loop!\n"); |
330 | 0 | goto out_err; |
331 | 0 | } |
332 | | |
333 | 0 | if (cfg_path_len >= 2048) { |
334 | 0 | LM_ERR("file path too large: %.*s...\n", 2048, cfg_path); |
335 | 0 | goto out_err; |
336 | 0 | } |
337 | | |
338 | 0 | con = cfg_context_new_file(cfg_path); |
339 | 0 | needed = cfgtok_filebegin.len + 1 + 1+cfg_path_len+1 + 1 + 1; |
340 | 0 | if (*bytes_left < needed) { |
341 | 0 | if (extend_cfg_buf(flattened, sz, bytes_left, needed) < 0) { |
342 | 0 | LM_ERR("oom\n"); |
343 | 0 | goto out_err; |
344 | 0 | } |
345 | 0 | } |
346 | | |
347 | | /* print "start of file" adnotation */ |
348 | 0 | printed = snprintf(*flattened + *sz - *bytes_left, *bytes_left, "%.*s \"%.*s\"\n", |
349 | 0 | cfgtok_filebegin.len, cfgtok_filebegin.s, cfg_path_len, cfg_path); |
350 | 0 | *bytes_left -= printed; |
351 | |
|
352 | 0 | for (;;) { |
353 | 0 | line_len = getline(&line, (size_t*)&line_buf_sz, cfg); |
354 | 0 | if (line_len == -1) { |
355 | 0 | if (ferror(cfg)) { |
356 | 0 | if (errno == EINTR) { |
357 | 0 | continue; |
358 | 0 | } else { |
359 | 0 | LM_ERR("failed to read from cfg file %.*s: %d (%s)\n", |
360 | 0 | cfg_path_len, cfg_path, errno, strerror(errno)); |
361 | 0 | goto out_err; |
362 | 0 | } |
363 | 0 | } |
364 | | |
365 | 0 | if (!feof(cfg)) { |
366 | 0 | LM_ERR("unhandled read error in cfg file %.*s: %d (%s)\n", |
367 | 0 | cfg_path_len, cfg_path, errno, strerror(errno)); |
368 | 0 | goto out_err; |
369 | 0 | } |
370 | | |
371 | 0 | line_len = 0; |
372 | 0 | break; |
373 | |
|
374 | 0 | } else if (line_len == 0) { |
375 | 0 | continue; |
376 | 0 | } |
377 | | |
378 | | /* fix ending lines with a missing '\n' character ;) */ |
379 | 0 | if (feof(cfg)) { |
380 | 0 | if (line[line_len - 1] != '\n') { |
381 | 0 | if (line_buf_sz < line_len + 1) { |
382 | 0 | line = realloc(line, line_len + 1); |
383 | 0 | line_buf_sz = line_len + 1; |
384 | 0 | } |
385 | |
|
386 | 0 | line[line_len] = '\n'; |
387 | 0 | line_len += 1; |
388 | 0 | } |
389 | 0 | } |
390 | | |
391 | | /* finally... we have a line! print "line number" adnotation */ |
392 | 0 | needed = cfgtok_line.len + 1 + 10 + 1 + 1; |
393 | 0 | if (*bytes_left < needed) { |
394 | 0 | if (extend_cfg_buf(flattened, sz, bytes_left, needed) < 0) { |
395 | 0 | LM_ERR("oom\n"); |
396 | 0 | goto out_err; |
397 | 0 | } |
398 | 0 | } |
399 | | |
400 | 0 | printed = snprintf(*flattened + *sz - *bytes_left, *bytes_left, |
401 | 0 | "%.*s %d\n", cfgtok_line.len, cfgtok_line.s, line_counter); |
402 | 0 | line_counter++; |
403 | 0 | *bytes_left -= printed; |
404 | |
|
405 | 0 | if (con) |
406 | 0 | cfg_context_append_line(con, line, line_len); |
407 | | |
408 | | /* if it's an include, skip printing the line, but do print the file */ |
409 | 0 | if (mk_included_file_path(line, line_len, con->dirname, &included_cfg_path) == 0) { |
410 | 0 | included_cfg = fopen(included_cfg_path, "r"); |
411 | 0 | if (!included_cfg) { |
412 | 0 | LM_ERR("failed to open %s: %d (%s)\n", included_cfg_path, |
413 | 0 | errno, strerror(errno)); |
414 | 0 | goto out_err; |
415 | 0 | } |
416 | | |
417 | 0 | included_cfg_path = strdup(included_cfg_path); |
418 | 0 | if (__flatten_opensips_cfg(included_cfg, included_cfg_path, |
419 | 0 | flattened, sz, bytes_left, reclev + 1)) { |
420 | 0 | free(included_cfg_path); |
421 | 0 | LM_ERR("failed to flatten cfg file %s\n", cfg_path); |
422 | 0 | goto out_err; |
423 | 0 | } |
424 | 0 | free(included_cfg_path); |
425 | 0 | } else { |
426 | 0 | needed = line_len + 1; |
427 | 0 | if (*bytes_left < needed) { |
428 | 0 | if (extend_cfg_buf(flattened, sz, bytes_left, needed) < 0) { |
429 | 0 | LM_ERR("oom\n"); |
430 | 0 | goto out_err; |
431 | 0 | } |
432 | 0 | } |
433 | | |
434 | 0 | printed = snprintf(*flattened + *sz - *bytes_left, *bytes_left, |
435 | 0 | "%.*s", (int)line_len, line); |
436 | 0 | *bytes_left -= printed; |
437 | 0 | } |
438 | 0 | } |
439 | | |
440 | 0 | free(line); |
441 | 0 | line = NULL; |
442 | |
|
443 | 0 | needed = cfgtok_fileend.len + 1 + 1; |
444 | 0 | if (*bytes_left < needed) { |
445 | 0 | if (extend_cfg_buf(flattened, sz, bytes_left, needed) < 0) { |
446 | 0 | LM_ERR("oom\n"); |
447 | 0 | goto out_err; |
448 | 0 | } |
449 | 0 | } |
450 | | |
451 | | /* print "end of file" adnotation */ |
452 | 0 | printed = snprintf(*flattened + *sz - *bytes_left, *bytes_left, "%.*s\n", |
453 | 0 | cfgtok_fileend.len, cfgtok_fileend.s); |
454 | 0 | *bytes_left -= printed; |
455 | |
|
456 | 0 | fclose(cfg); |
457 | 0 | return 0; |
458 | | |
459 | 0 | out_err: |
460 | 0 | if (line) |
461 | 0 | free(line); |
462 | 0 | fclose(cfg); |
463 | 0 | return -1; |
464 | 0 | } |
465 | | |
466 | | /* |
467 | | * - flatten any recursive includes into one big resulting file |
468 | | * - adnotate each line of the final file |
469 | | * - close given FILE * and return a buffer corresponding to the new file |
470 | | */ |
471 | | static int flatten_opensips_cfg(FILE *cfg, const char *cfg_path, str *out) |
472 | 0 | { |
473 | 0 | int sz = 0, bytes_left = 0; |
474 | 0 | char *flattened = NULL; |
475 | |
|
476 | 0 | if (__flatten_opensips_cfg(cfg, cfg_path, &flattened, &sz, &bytes_left, 0)) { |
477 | 0 | LM_ERR("failed to flatten cfg file %s\n", cfg_path); |
478 | 0 | return -1; |
479 | 0 | } |
480 | | |
481 | 0 | out->s = flattened; |
482 | 0 | out->len = sz - bytes_left; |
483 | |
|
484 | 0 | if (strlen(out->s) != out->len) { |
485 | 0 | LM_BUG("preprocessed buffer check failed (%lu vs. %d)", |
486 | 0 | (unsigned long)strlen(out->s), out->len); |
487 | 0 | LM_ERR("either this is a bug or your script contains '\\0' chars, " |
488 | 0 | "which are obviously NOT allowed!\n"); |
489 | 0 | return -1; |
490 | 0 | } |
491 | | |
492 | 0 | return 0; |
493 | 0 | } |
494 | | |
495 | | static char *cfg_include_stack[CFG_MAX_INCLUDE_DEPTH]; |
496 | | static char **cfg_include_stackp; |
497 | | int cfg_push(const str *cfg_file) |
498 | 0 | { |
499 | 0 | if (!cfg_include_stackp) { |
500 | 0 | cfg_include_stackp = cfg_include_stack; |
501 | 0 | } else if (cfg_include_stackp - cfg_include_stack + 1 >= |
502 | 0 | CFG_MAX_INCLUDE_DEPTH) { |
503 | 0 | LM_ERR("max nested cfg files reached! (%d)\n", CFG_MAX_INCLUDE_DEPTH); |
504 | 0 | return -1; |
505 | 0 | } else { |
506 | 0 | cfg_include_stackp++; |
507 | 0 | } |
508 | | |
509 | 0 | *cfg_include_stackp = malloc(cfg_file->len + 1); |
510 | 0 | if (!*cfg_include_stackp) { |
511 | 0 | LM_ERR("oom\n"); |
512 | 0 | return -1; |
513 | 0 | } |
514 | 0 | memcpy(*cfg_include_stackp, cfg_file->s, cfg_file->len); |
515 | 0 | (*cfg_include_stackp)[cfg_file->len] = '\0'; |
516 | |
|
517 | 0 | finame = *cfg_include_stackp; |
518 | 0 | startline = 1; |
519 | 0 | column = 1; |
520 | 0 | return 0; |
521 | 0 | } |
522 | | |
523 | | int cfg_pop(void) |
524 | 0 | { |
525 | 0 | if (!cfg_include_stackp) { |
526 | 0 | LM_ERR("no more files to pop!\n"); |
527 | 0 | return -1; |
528 | 0 | } |
529 | | |
530 | | /* the file path MUST NOT be freed, as the lexer and parser work in tandem, |
531 | | * so by this point, there are plenty of structures referencing it */ |
532 | | |
533 | 0 | if (cfg_include_stackp == cfg_include_stack) { |
534 | 0 | cfg_include_stackp = NULL; |
535 | 0 | } else { |
536 | 0 | cfg_include_stackp--; |
537 | 0 | finame = *cfg_include_stackp; |
538 | 0 | column = 1; |
539 | 0 | } |
540 | |
|
541 | 0 | return 0; |
542 | 0 | } |
543 | | |
544 | | void _cfg_dump_context(const char *file, int line, int colstart, int colend, |
545 | | int run_once) |
546 | 0 | { |
547 | 0 | static int called_before; |
548 | 0 | struct cfg_context *con; |
549 | 0 | int i, iter = 1, len; |
550 | 0 | char *p, *end, *wsbuf, *wb, *hiline; |
551 | |
|
552 | 0 | if (!file) |
553 | 0 | return; |
554 | | |
555 | 0 | for (con = __ccon; con; con = con->next) |
556 | 0 | if (!strcmp(con->path, file)) |
557 | 0 | break; |
558 | |
|
559 | 0 | if (!con || !con->lines[0] || (run_once && called_before)) |
560 | 0 | return; |
561 | | |
562 | 0 | called_before = 1; |
563 | | |
564 | | /* 2 lines above */ |
565 | 0 | if (line >= 3) { |
566 | 0 | startline = line - 2; |
567 | 0 | iter += 2; |
568 | 0 | } else { |
569 | 0 | startline = 1; |
570 | 0 | iter += line - 1; |
571 | 0 | } |
572 | |
|
573 | 0 | for (i = startline - 1; iter > 0; i++, iter--) |
574 | 0 | LM_GEN1(L_CRIT, "%s", con->lines[i]); |
575 | | |
576 | | /* error indicator line */ |
577 | 0 | len = strlen(con->lines[i-1]); |
578 | 0 | wsbuf = malloc(len + 1); |
579 | 0 | if (!wsbuf) { |
580 | 0 | LM_ERR("oom\n"); |
581 | 0 | return; |
582 | 0 | } |
583 | | |
584 | 0 | wb = wsbuf; |
585 | 0 | for (p = con->lines[i-1], end = p + len; p < end && is_ws(*p); p++) |
586 | 0 | *wb++ = *p; |
587 | 0 | *wb = '\0'; |
588 | |
|
589 | 0 | if (colend < colstart) { |
590 | 0 | hiline = NULL; |
591 | 0 | } else { |
592 | 0 | hiline = malloc(colend - colstart); |
593 | 0 | if (!hiline) { |
594 | 0 | LM_ERR("oom\n"); |
595 | 0 | free(wsbuf); |
596 | 0 | return; |
597 | 0 | } |
598 | 0 | memset(hiline, '~', colend - colstart); |
599 | 0 | } |
600 | | |
601 | 0 | LM_GEN1(L_CRIT, "%s^%.*s\n", wsbuf, |
602 | 0 | colend >= colstart ? colend - colstart : 0, hiline); |
603 | 0 | free(hiline); |
604 | 0 | free(wsbuf); |
605 | | |
606 | | /* 2 lines below */ |
607 | 0 | if (line <= con->loc - 2) |
608 | 0 | iter = 2; |
609 | 0 | else |
610 | 0 | iter = line <= con->loc ? con->loc - line : 0; |
611 | |
|
612 | 0 | for (; iter > 0; i++, iter--) |
613 | 0 | LM_GEN1(L_CRIT, "%s", con->lines[i]); |
614 | 0 | } |
615 | | |
616 | | void cfg_dump_backtrace(void) |
617 | 0 | { |
618 | 0 | static int called_before; |
619 | 0 | char **it; |
620 | 0 | int frame = 0; |
621 | |
|
622 | 0 | if (called_before || !cfg_include_stackp) |
623 | 0 | return; |
624 | | |
625 | 0 | called_before = 1; |
626 | 0 | LM_GEN1(L_CRIT, "Traceback (last included file at the bottom):\n"); |
627 | 0 | for (it = cfg_include_stack; it <= cfg_include_stackp; it++) |
628 | 0 | LM_GEN1(L_CRIT, "%2d. %s\n", frame++, *it); |
629 | 0 | } |
630 | | |
631 | | static int exec_preprocessor(FILE *flat_cfg, const char *preproc_cmdline, |
632 | | str *out) |
633 | 0 | { |
634 | 0 | int parent_w[2], parent_r[2], cfgsz = 0, cfgbufsz = 0; |
635 | 0 | char chunk[1024], *cfgbuf = NULL; |
636 | 0 | ssize_t written, bytes; |
637 | 0 | size_t bytes2write; |
638 | 0 | char *p, *tok, *cmd, **argv = NULL, *pp_binary = NULL; |
639 | 0 | int argv_len = 0, flags, have_input = 0, done_writing = 0; |
640 | |
|
641 | 0 | if (strlen(preproc_cmdline) == 0) { |
642 | 0 | LM_ERR("preprocessor command (-p) is an empty string!\n"); |
643 | 0 | goto out_err; |
644 | 0 | } |
645 | | |
646 | 0 | if (pipe(parent_w) != 0 || pipe(parent_r) != 0) { |
647 | 0 | LM_ERR("failed to create pipe: %d (%s)\n", errno, strerror(errno)); |
648 | 0 | goto out_err; |
649 | 0 | } |
650 | | |
651 | | /* fork a data-hungry preprocessor beast! (a.k.a. some tiny sed) */ |
652 | 0 | if (fork() == 0) { |
653 | 0 | close(parent_w[1]); |
654 | 0 | if (dup2(parent_w[0], STDIN_FILENO) < 0) { |
655 | 0 | LM_ERR("dup2 failed with: %d (%s)\n", errno, strerror(errno)); |
656 | 0 | exit(-1); |
657 | 0 | } |
658 | 0 | close(parent_w[0]); |
659 | |
|
660 | 0 | close(parent_r[0]); |
661 | 0 | if (dup2(parent_r[1], STDOUT_FILENO) < 0) { |
662 | 0 | LM_ERR("dup2 failed with: %d (%s)\n", errno, strerror(errno)); |
663 | 0 | exit(-1); |
664 | 0 | } |
665 | 0 | close(parent_w[1]); |
666 | |
|
667 | 0 | for (cmd = strdup(preproc_cmdline); ; cmd = NULL) { |
668 | 0 | tok = strtok(cmd, " \t\r\n"); |
669 | 0 | if (!tok) |
670 | 0 | break; |
671 | | |
672 | 0 | if (!pp_binary) |
673 | 0 | pp_binary = tok; |
674 | |
|
675 | 0 | argv = realloc(argv, (argv_len + 1) * sizeof *argv); |
676 | 0 | argv[argv_len++] = tok; |
677 | 0 | } |
678 | |
|
679 | 0 | argv = realloc(argv, (argv_len + 1) * sizeof *argv); |
680 | 0 | argv[argv_len++] = NULL; |
681 | |
|
682 | 0 | if (pp_binary) { |
683 | 0 | execvp(pp_binary, argv); |
684 | 0 | LM_ERR("failed to exec preprocessor '%s': %d (%s)\n", |
685 | 0 | preproc_cmdline, errno, strerror(errno)); |
686 | 0 | } else |
687 | 0 | LM_ERR("no binary to run: '%s'\n", preproc_cmdline); |
688 | |
|
689 | 0 | exit(-1); |
690 | 0 | } |
691 | | |
692 | 0 | close(parent_w[0]); |
693 | 0 | close(parent_r[1]); |
694 | |
|
695 | 0 | flags = fcntl(parent_w[1], F_GETFL); |
696 | 0 | if (flags == -1) { |
697 | 0 | LM_ERR("fcntl GET 1 failed: %d - %s\n", errno, strerror(errno)); |
698 | 0 | goto out_err_pipes; |
699 | 0 | } |
700 | | |
701 | 0 | if (fcntl(parent_w[1], F_SETFL, flags | O_NONBLOCK) == -1) { |
702 | 0 | LM_ERR("fcntl SET 1 failed: %d - %s\n", errno, strerror(errno)); |
703 | 0 | goto out_err_pipes; |
704 | 0 | } |
705 | | |
706 | 0 | flags = fcntl(parent_r[0], F_GETFL); |
707 | 0 | if (flags == -1) { |
708 | 0 | LM_ERR("fcntl GET 2 failed: %d - %s\n", errno, strerror(errno)); |
709 | 0 | goto out_err_pipes; |
710 | 0 | } |
711 | | |
712 | 0 | if (fcntl(parent_r[0], F_SETFL, flags | O_NONBLOCK) == -1) { |
713 | 0 | LM_ERR("fcntl SET 2 failed: %d - %s\n", errno, strerror(errno)); |
714 | 0 | goto out_err_pipes; |
715 | 0 | } |
716 | | |
717 | | /* communicate with the preprocessor using alternating, |
718 | | * non-blocking writes and reads */ |
719 | 0 | while (!done_writing) { |
720 | | /* fetch bytes to write */ |
721 | 0 | bytes2write = fread(chunk, 1, 1024, flat_cfg); |
722 | 0 | if (ferror(flat_cfg)) { |
723 | 0 | LM_ERR("failed to read from flat cfg: %d (%s)\n", |
724 | 0 | errno, strerror(errno)); |
725 | 0 | goto out_err_pipes; |
726 | 0 | } |
727 | | |
728 | 0 | if (bytes2write == 0) { |
729 | 0 | done_writing = 1; |
730 | 0 | close(parent_w[1]); /* signal EOF to the outside process! */ |
731 | 0 | } else { |
732 | 0 | have_input = 1; |
733 | 0 | } |
734 | |
|
735 | 0 | p = chunk; |
736 | |
|
737 | 0 | send_bytes: |
738 | | /* write phase */ |
739 | 0 | while (bytes2write > 0) { |
740 | 0 | written = write(parent_w[1], p, bytes2write); |
741 | 0 | if (written < 0) { |
742 | 0 | if (errno == EAGAIN || errno == EWOULDBLOCK) |
743 | 0 | break; |
744 | 0 | else if (errno == EINTR) |
745 | 0 | continue; |
746 | 0 | else |
747 | 0 | goto out_err_pipes; |
748 | 0 | } |
749 | | |
750 | 0 | bytes2write -= written; |
751 | 0 | p += written; |
752 | 0 | } |
753 | | |
754 | | /* read phase */ |
755 | 0 | for (;;) { |
756 | 0 | if (cfgsz + 1024 > cfgbufsz) { |
757 | 0 | if (cfgbufsz == 0) |
758 | 0 | cfgbufsz = 4096; |
759 | 0 | else |
760 | 0 | cfgbufsz *= 2; |
761 | |
|
762 | 0 | cfgbuf = realloc(cfgbuf, cfgbufsz); |
763 | 0 | if (!cfgbuf) { |
764 | 0 | LM_ERR("oom, failed to build config buffer\n"); |
765 | 0 | goto out_err; |
766 | 0 | } |
767 | 0 | } |
768 | | |
769 | 0 | bytes = read(parent_r[0], cfgbuf + cfgsz, 1024); |
770 | 0 | if (bytes < 0) { |
771 | 0 | if (errno == EAGAIN || errno == EWOULDBLOCK) { |
772 | 0 | if (done_writing) { |
773 | 0 | usleep(10); |
774 | 0 | continue; |
775 | 0 | } else { |
776 | 0 | break; |
777 | 0 | } |
778 | 0 | } else if (errno == EINTR) { |
779 | 0 | continue; |
780 | 0 | } else { |
781 | 0 | goto out_err_pipes; |
782 | 0 | } |
783 | 0 | } else if (bytes == 0) { |
784 | 0 | bytes2write = 0; |
785 | 0 | done_writing = 1; |
786 | 0 | break; |
787 | 0 | } |
788 | | |
789 | 0 | cfgsz += bytes; |
790 | 0 | } |
791 | | |
792 | 0 | if (bytes2write > 0) |
793 | 0 | goto send_bytes; |
794 | 0 | } |
795 | | |
796 | 0 | if (have_input && cfgsz == 0) |
797 | 0 | LM_WARN("no output from the preprocessor! " |
798 | 0 | "Does it print to standard output?\n"); |
799 | |
|
800 | 0 | fclose(flat_cfg); |
801 | 0 | close(parent_r[0]); |
802 | |
|
803 | 0 | out->s = cfgbuf; |
804 | 0 | out->len = cfgsz; |
805 | 0 | return 0; |
806 | | |
807 | 0 | out_err_pipes: |
808 | 0 | close(parent_w[1]); |
809 | 0 | close(parent_r[0]); |
810 | 0 | out_err: |
811 | 0 | fclose(flat_cfg); |
812 | 0 | free(cfgbuf); |
813 | 0 | return -1; |
814 | 0 | } |
815 | | |
816 | | int eatback_pp_tok(struct str_buf *buf) |
817 | 0 | { |
818 | 0 | char *p; |
819 | 0 | str last_line; |
820 | |
|
821 | 0 | if (!buf->s) |
822 | 0 | return 0; |
823 | | |
824 | 0 | for (p = buf->crt - 1; p >= buf->s; p--) |
825 | 0 | if (*p == '\n') { |
826 | 0 | p++; |
827 | 0 | goto match_pp_tok; |
828 | 0 | } |
829 | | |
830 | 0 | return 0; |
831 | | |
832 | 0 | match_pp_tok: |
833 | 0 | last_line.s = p; |
834 | 0 | last_line.len = buf->crt - p; |
835 | |
|
836 | 0 | if (last_line.len < 0) { |
837 | 0 | LM_BUG("negative line len"); |
838 | 0 | return 0; |
839 | 0 | } |
840 | | |
841 | 0 | if (last_line.len >= cfgtok_line.len && |
842 | 0 | !memcmp(last_line.s, cfgtok_line.s, cfgtok_line.len)) |
843 | 0 | goto clear_last_line; |
844 | | |
845 | 0 | if (last_line.len >= cfgtok_filebegin.len && |
846 | 0 | !memcmp(last_line.s, cfgtok_filebegin.s, cfgtok_filebegin.len)) |
847 | 0 | goto clear_last_line; |
848 | | |
849 | 0 | if (last_line.len >= cfgtok_fileend.len && |
850 | 0 | !memcmp(last_line.s, cfgtok_fileend.s, cfgtok_fileend.len)) |
851 | 0 | goto clear_last_line; |
852 | | |
853 | | /* don't touch anything, this is an actual script line! */ |
854 | 0 | return 0; |
855 | | |
856 | 0 | clear_last_line: |
857 | 0 | LM_DBG("clearing pp token line: '%.*s'\n", (int)(buf->crt - p), p); |
858 | 0 | buf->left += buf->crt - p; |
859 | 0 | *p = '\0'; |
860 | 0 | buf->crt = p; |
861 | 0 | return 1; |
862 | 0 | } |