1 /*
2 * libxlu_cfg.c - xl configuration file parsing: setup and helper functions
3 *
4 * Copyright (C) 2010 Citrix Ltd.
5 * Author Ian Jackson <ian.jackson@eu.citrix.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as published
9 * by the Free Software Foundation; version 2.1 only. with the special
10 * exception on linking described in file LICENSE.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Lesser General Public License for more details.
16 */
17
18
19 #include "libxl_osdeps.h" /* must come before any other headers */
20
21 #include <limits.h>
22
23 #include "libxlu_internal.h"
24 #include "libxlu_cfg_y.h"
25 #include "libxlu_cfg_l.h"
26 #include "libxlu_cfg_i.h"
27
xlu_cfg_init(FILE * report,const char * report_source)28 XLU_Config *xlu_cfg_init(FILE *report, const char *report_source) {
29 XLU_Config *cfg;
30
31 cfg= malloc(sizeof(*cfg));
32 if (!cfg) return 0;
33
34 cfg->report= report;
35 cfg->config_source= strdup(report_source);
36 if (!cfg->config_source) { free(cfg); return 0; }
37
38 cfg->settings= 0;
39 return cfg;
40 }
41
ctx_prep(CfgParseContext * ctx,XLU_Config * cfg)42 static int ctx_prep(CfgParseContext *ctx, XLU_Config *cfg) {
43 int e;
44
45 ctx->cfg= cfg;
46 ctx->err= 0;
47 ctx->lexerrlineno= -1;
48 ctx->likely_python= 0;
49 ctx->scanner= 0;
50
51 e= xlu__cfg_yylex_init_extra(ctx, &ctx->scanner);
52 if (e) {
53 fprintf(cfg->report,"%s: unable to create scanner: %s\n",
54 cfg->config_source, strerror(e));
55 return e;
56 }
57 return 0;
58 }
59
ctx_dispose(CfgParseContext * ctx)60 static void ctx_dispose(CfgParseContext *ctx) {
61 if (ctx->scanner) xlu__cfg_yylex_destroy(ctx->scanner);
62 }
63
parse(CfgParseContext * ctx)64 static void parse(CfgParseContext *ctx) {
65 /* On return, ctx.err will be updated with the error status. */
66 int r;
67
68 xlu__cfg_yyset_lineno(1, ctx->scanner);
69
70 r= xlu__cfg_yyparse(ctx);
71 if (r) assert(ctx->err);
72
73 if (ctx->err && ctx->likely_python) {
74 fputs(
75 "warning: Config file looks like it contains Python code.\n"
76 "warning: Arbitrary Python is no longer supported.\n"
77 "warning: See https://wiki.xen.org/wiki/PythonInXlConfig\n",
78 ctx->cfg->report);
79 }
80 }
81
xlu_cfg_readfile(XLU_Config * cfg,const char * real_filename)82 int xlu_cfg_readfile(XLU_Config *cfg, const char *real_filename) {
83 FILE *f = 0;
84 int e;
85
86 CfgParseContext ctx;
87 e = ctx_prep(&ctx, cfg);
88 if (e) { ctx.err= e; goto xe; }
89
90 f= fopen(real_filename, "r");
91 if (!f) {
92 ctx.err = errno;
93 fprintf(cfg->report,"%s: unable to open configuration file: %s\n",
94 real_filename, strerror(e));
95 goto xe;
96 }
97
98 xlu__cfg_yyrestart(f, ctx.scanner);
99
100 parse(&ctx);
101
102 xe:
103 ctx_dispose(&ctx);
104 if (f) fclose(f);
105
106 return ctx.err;
107 }
108
xlu_cfg_readdata(XLU_Config * cfg,const char * data,int length)109 int xlu_cfg_readdata(XLU_Config *cfg, const char *data, int length) {
110 int e;
111 YY_BUFFER_STATE buf= 0;
112
113 CfgParseContext ctx;
114 e= ctx_prep(&ctx, cfg);
115 if (e) { ctx.err= e; goto xe; }
116
117 buf = xlu__cfg_yy_scan_bytes(data, length, ctx.scanner);
118 if (!buf) {
119 fprintf(cfg->report,"%s: unable to allocate scanner buffer\n",
120 cfg->config_source);
121 ctx.err= ENOMEM;
122 goto xe;
123 }
124
125 parse(&ctx);
126
127 xe:
128 if (buf) xlu__cfg_yy_delete_buffer(buf, ctx.scanner);
129 ctx_dispose(&ctx);
130
131 return ctx.err;
132 }
133
xlu__cfg_value_free(XLU_ConfigValue * value)134 void xlu__cfg_value_free(XLU_ConfigValue *value)
135 {
136 int i;
137
138 if (!value) return;
139
140 switch (value->type) {
141 case XLU_STRING:
142 free(value->u.string);
143 break;
144 case XLU_LIST:
145 for (i = 0; i < value->u.list.nvalues; i++)
146 xlu__cfg_value_free(value->u.list.values[i]);
147 free(value->u.list.values);
148 }
149 free(value);
150 }
151
xlu__cfg_set_free(XLU_ConfigSetting * set)152 void xlu__cfg_set_free(XLU_ConfigSetting *set) {
153 if (!set) return;
154 free(set->name);
155 xlu__cfg_value_free(set->value);
156 free(set);
157 }
158
xlu_cfg_destroy(XLU_Config * cfg)159 void xlu_cfg_destroy(XLU_Config *cfg) {
160 XLU_ConfigSetting *set, *set_next;
161
162 if (!cfg) return;
163 for (set= cfg->settings;
164 set;
165 set= set_next) {
166 set_next= set->next;
167 xlu__cfg_set_free(set);
168 }
169 free(cfg->config_source);
170 free(cfg);
171 }
172
find(const XLU_Config * cfg,const char * n)173 static XLU_ConfigSetting *find(const XLU_Config *cfg, const char *n) {
174 XLU_ConfigSetting *set;
175
176 for (set= cfg->settings;
177 set;
178 set= set->next)
179 if (!strcmp(set->name, n))
180 return set;
181 return 0;
182 }
183
find_atom(const XLU_Config * cfg,const char * n,XLU_ConfigSetting ** set_r,int dont_warn)184 static int find_atom(const XLU_Config *cfg, const char *n,
185 XLU_ConfigSetting **set_r, int dont_warn) {
186 XLU_ConfigSetting *set;
187
188 set= find(cfg,n);
189 if (!set) return ESRCH;
190
191 if (set->value->type!=XLU_STRING) {
192 if (!dont_warn)
193 fprintf(cfg->report,
194 "%s:%d: warning: parameter `%s' is"
195 " a list but should be a single value\n",
196 cfg->config_source, set->lineno, n);
197 return EINVAL;
198 }
199 *set_r= set;
200 return 0;
201 }
202
203
xlu_cfg_value_type(const XLU_ConfigValue * value)204 enum XLU_ConfigValueType xlu_cfg_value_type(const XLU_ConfigValue *value)
205 {
206 return value->type;
207 }
208
xlu_cfg_value_get_string(const XLU_Config * cfg,XLU_ConfigValue * value,char ** value_r,int dont_warn)209 int xlu_cfg_value_get_string(const XLU_Config *cfg, XLU_ConfigValue *value,
210 char **value_r, int dont_warn)
211 {
212 if (value->type != XLU_STRING) {
213 if (!dont_warn)
214 fprintf(cfg->report,
215 "%s:%d:%d: warning: value is not a string\n",
216 cfg->config_source, value->loc.first_line,
217 value->loc.first_column);
218 *value_r = NULL;
219 return EINVAL;
220 }
221
222 *value_r = value->u.string;
223 return 0;
224 }
225
xlu_cfg_value_get_list(const XLU_Config * cfg,XLU_ConfigValue * value,XLU_ConfigList ** value_r,int dont_warn)226 int xlu_cfg_value_get_list(const XLU_Config *cfg, XLU_ConfigValue *value,
227 XLU_ConfigList **value_r, int dont_warn)
228 {
229 if (value->type != XLU_LIST) {
230 if (!dont_warn)
231 fprintf(cfg->report,
232 "%s:%d:%d: warning: value is not a list\n",
233 cfg->config_source, value->loc.first_line,
234 value->loc.first_column);
235 *value_r = NULL;
236 return EINVAL;
237 }
238
239 *value_r = &value->u.list;
240 return 0;
241 }
242
xlu_cfg_get_listitem2(const XLU_ConfigList * list,int entry)243 XLU_ConfigValue *xlu_cfg_get_listitem2(const XLU_ConfigList *list,
244 int entry)
245 {
246 if (entry < 0 || entry >= list->nvalues) return NULL;
247 return list->values[entry];
248 }
249
xlu_cfg_get_string(const XLU_Config * cfg,const char * n,const char ** value_r,int dont_warn)250 int xlu_cfg_get_string(const XLU_Config *cfg, const char *n,
251 const char **value_r, int dont_warn) {
252 XLU_ConfigSetting *set;
253 int e;
254
255 e= find_atom(cfg,n,&set,dont_warn); if (e) return e;
256 *value_r= set->value->u.string;
257 return 0;
258 }
259
xlu_cfg_replace_string(const XLU_Config * cfg,const char * n,char ** value_r,int dont_warn)260 int xlu_cfg_replace_string(const XLU_Config *cfg, const char *n,
261 char **value_r, int dont_warn) {
262 XLU_ConfigSetting *set;
263 int e;
264
265 e= find_atom(cfg,n,&set,dont_warn); if (e) return e;
266 free(*value_r);
267 *value_r= strdup(set->value->u.string);
268 return 0;
269 }
270
xlu_cfg_get_bounded_long(const XLU_Config * cfg,const char * n,long min,long max,long * value_r,int dont_warn)271 int xlu_cfg_get_bounded_long(const XLU_Config *cfg, const char *n,
272 long min, long max, long *value_r,
273 int dont_warn) {
274 long l;
275 XLU_ConfigSetting *set;
276 int e;
277 char *ep;
278
279 e= find_atom(cfg,n,&set,dont_warn); if (e) return e;
280 if (set->op == XLU_OP_ADDITION) {
281 if (!dont_warn)
282 fprintf(cfg->report,
283 "%s:%d: warning: can't use += with numbers"
284 " for parameter `%s'\n",
285 cfg->config_source, set->lineno, n);
286 return EINVAL;
287 }
288 errno= 0; l= strtol(set->value->u.string, &ep, 0);
289 e= errno;
290 if (errno) {
291 e= errno;
292 assert(e==EINVAL || e==ERANGE);
293 if (!dont_warn)
294 fprintf(cfg->report,
295 "%s:%d: warning: parameter `%s' could not be parsed"
296 " as a number: %s\n",
297 cfg->config_source, set->lineno, n, strerror(e));
298 return e;
299 }
300 if (*ep || ep==set->value->u.string) {
301 if (!dont_warn)
302 fprintf(cfg->report,
303 "%s:%d: warning: parameter `%s' is not a valid number\n",
304 cfg->config_source, set->lineno, n);
305 return EINVAL;
306 }
307 if (l < min) {
308 if (!dont_warn)
309 fprintf(cfg->report,
310 "%s:%d: warning: value `%ld' is smaller than minimum bound '%ld'\n",
311 cfg->config_source, set->lineno, l, min);
312 return EINVAL;
313 }
314 if (l > max) {
315 if (!dont_warn)
316 fprintf(cfg->report,
317 "%s:%d: warning: value `%ld' is greater than maximum bound '%ld'\n",
318 cfg->config_source, set->lineno, l, max);
319 return EINVAL;
320 }
321
322 *value_r= l;
323 return 0;
324 }
325
xlu_cfg_get_long(const XLU_Config * cfg,const char * n,long * value_r,int dont_warn)326 int xlu_cfg_get_long(const XLU_Config *cfg, const char *n,
327 long *value_r, int dont_warn) {
328 return xlu_cfg_get_bounded_long(cfg, n, LONG_MIN, LONG_MAX, value_r,
329 dont_warn);
330 }
331
xlu_cfg_get_defbool(const XLU_Config * cfg,const char * n,libxl_defbool * b,int dont_warn)332 int xlu_cfg_get_defbool(const XLU_Config *cfg, const char *n, libxl_defbool *b,
333 int dont_warn)
334 {
335 int ret;
336 long l;
337
338 ret = xlu_cfg_get_long(cfg, n, &l, dont_warn);
339 if (ret) return ret;
340 libxl_defbool_set(b, !!l);
341 return 0;
342 }
343
xlu_cfg_get_list(const XLU_Config * cfg,const char * n,XLU_ConfigList ** list_r,int * entries_r,int dont_warn)344 int xlu_cfg_get_list(const XLU_Config *cfg, const char *n,
345 XLU_ConfigList **list_r, int *entries_r, int dont_warn) {
346 XLU_ConfigSetting *set;
347 set= find(cfg,n); if (!set) return ESRCH;
348 if (set->value->type!=XLU_LIST) {
349 if (!dont_warn) {
350 fprintf(cfg->report,
351 "%s:%d: warning: parameter `%s' is a single value"
352 " but should be a list\n",
353 cfg->config_source, set->lineno, n);
354 }
355 return EINVAL;
356 }
357 if (list_r) *list_r= &set->value->u.list;
358 if (entries_r) *entries_r= set->value->u.list.nvalues;
359 return 0;
360 }
361
xlu_cfg_get_list_as_string_list(const XLU_Config * cfg,const char * n,libxl_string_list * psl,int dont_warn)362 int xlu_cfg_get_list_as_string_list(const XLU_Config *cfg, const char *n,
363 libxl_string_list *psl, int dont_warn) {
364 int i, rc, nr;
365 XLU_ConfigList *list;
366 libxl_string_list sl;
367
368 rc = xlu_cfg_get_list(cfg, n, &list, &nr, dont_warn);
369 if (rc) return rc;
370
371 sl = malloc(sizeof(char*)*(nr + 1));
372 if (sl == NULL) return ENOMEM;
373
374 for (i=0; i<nr; i++) {
375 const char *a = xlu_cfg_get_listitem(list, i);
376 sl[i] = a ? strdup(a) : NULL;
377 }
378
379 sl[nr] = NULL;
380
381 *psl = sl;
382 return 0;
383 }
384
xlu_cfg_get_listitem(const XLU_ConfigList * list,int entry)385 const char *xlu_cfg_get_listitem(const XLU_ConfigList *list, int entry) {
386 if (entry < 0 || entry >= list->nvalues) return 0;
387 if (list->values[entry]->type != XLU_STRING) return 0;
388 return list->values[entry]->u.string;
389 }
390
391
xlu__cfg_string_mk(CfgParseContext * ctx,char * atom,YYLTYPE * loc)392 XLU_ConfigValue *xlu__cfg_string_mk(CfgParseContext *ctx, char *atom,
393 YYLTYPE *loc)
394 {
395 XLU_ConfigValue *value = NULL;
396
397 if (ctx->err) goto x;
398
399 value = malloc(sizeof(*value));
400 if (!value) goto xe;
401 value->type = XLU_STRING;
402 value->u.string = atom;
403 memcpy(&value->loc, loc, sizeof(*loc));
404
405 return value;
406
407 xe:
408 ctx->err= errno;
409 x:
410 free(value);
411 free(atom);
412 return NULL;
413 }
414
xlu__cfg_list_mk(CfgParseContext * ctx,XLU_ConfigValue * val,YYLTYPE * loc)415 XLU_ConfigValue *xlu__cfg_list_mk(CfgParseContext *ctx,
416 XLU_ConfigValue *val,
417 YYLTYPE *loc)
418 {
419 XLU_ConfigValue *value = NULL;
420 XLU_ConfigValue **values = NULL;
421
422 if (ctx->err) goto x;
423
424 values = malloc(sizeof(*values));
425 if (!values) goto xe;
426 values[0] = val;
427
428 value = malloc(sizeof(*value));
429 if (!value) goto xe;
430 value->type = XLU_LIST;
431 value->u.list.nvalues = !!val;
432 value->u.list.avalues = 1;
433 value->u.list.values = values;
434 memcpy(&value->loc, loc, sizeof(*loc));
435
436 return value;
437
438 xe:
439 ctx->err= errno;
440 x:
441 free(value);
442 free(values);
443 xlu__cfg_value_free(val);
444 return NULL;
445 }
446
xlu__cfg_list_append(CfgParseContext * ctx,XLU_ConfigValue * list,XLU_ConfigValue * val)447 void xlu__cfg_list_append(CfgParseContext *ctx,
448 XLU_ConfigValue *list,
449 XLU_ConfigValue *val)
450 {
451 if (ctx->err) return;
452
453 assert(val);
454 assert(list->type == XLU_LIST);
455
456 if (list->u.list.nvalues >= list->u.list.avalues) {
457 int new_avalues;
458 XLU_ConfigValue **new_values = NULL;
459
460 if (list->u.list.avalues > INT_MAX / 100) {
461 ctx->err = ERANGE;
462 xlu__cfg_value_free(val);
463 return;
464 }
465
466 new_avalues = list->u.list.avalues * 4;
467 new_values = realloc(list->u.list.values,
468 sizeof(*new_values) * new_avalues);
469 if (!new_values) {
470 ctx->err = errno;
471 xlu__cfg_value_free(val);
472 return;
473 }
474
475 list->u.list.avalues = new_avalues;
476 list->u.list.values = new_values;
477 }
478
479 list->u.list.values[list->u.list.nvalues] = val;
480 list->u.list.nvalues++;
481 }
482
xlu__cfg_concat_vals(CfgParseContext * ctx,XLU_ConfigValue * prev,XLU_ConfigValue * to_add)483 static int xlu__cfg_concat_vals(CfgParseContext *ctx,
484 XLU_ConfigValue *prev,
485 XLU_ConfigValue *to_add)
486 {
487 int r;
488
489 if (prev->type != to_add->type) {
490 xlu__cfgl_lexicalerror(ctx,
491 "can't add [list] to \"string\" or vice versa");
492 return EINVAL;
493 }
494
495 switch (to_add->type) {
496 case XLU_STRING: {
497 char *new_string = NULL;
498
499 r = asprintf(&new_string, "%s%s", prev->u.string,
500 to_add->u.string);
501 if (r < 0) {
502 return errno;
503 }
504 free(to_add->u.string);
505 to_add->u.string = new_string;
506 return 0;
507 }
508 case XLU_LIST: {
509 XLU_ConfigList *const prev_list = &prev->u.list;
510 XLU_ConfigList *const cur_list = &to_add->u.list;
511 int nvalues;
512
513 if (prev->u.list.nvalues > INT_MAX - to_add->u.list.nvalues) {
514 return ERANGE;
515 }
516 nvalues = prev->u.list.nvalues + to_add->u.list.nvalues;
517
518 if (nvalues >= cur_list->avalues) {
519 XLU_ConfigValue **new_vals;
520 new_vals = realloc(cur_list->values,
521 nvalues * sizeof(*new_vals));
522 if (!new_vals) {
523 return ENOMEM;
524 }
525 cur_list->avalues = nvalues;
526 cur_list->values = new_vals;
527 }
528
529 /* make space for `prev' into `to_add' */
530 memmove(cur_list->values + prev_list->nvalues,
531 cur_list->values,
532 cur_list->nvalues * sizeof(XLU_ConfigValue *));
533 /* move values from `prev' to `to_add' as the list in `prev' will
534 * not be reachable by find(). */
535 memcpy(cur_list->values,
536 prev_list->values,
537 prev_list->nvalues * sizeof(XLU_ConfigValue *));
538 cur_list->nvalues = nvalues;
539 prev_list->nvalues = 0;
540 memset(prev_list->values, 0,
541 prev_list->nvalues * sizeof(XLU_ConfigValue *));
542 return 0;
543 }
544 default:
545 abort();
546 }
547 return -1;
548 }
549
xlu__cfg_set_store(CfgParseContext * ctx,char * name,enum XLU_Operation op,XLU_ConfigValue * val,int lineno)550 void xlu__cfg_set_store(CfgParseContext *ctx, char *name,
551 enum XLU_Operation op,
552 XLU_ConfigValue *val, int lineno) {
553 XLU_ConfigSetting *set;
554 int r;
555
556 if (ctx->err) goto out;
557
558 assert(name);
559
560 if (op == XLU_OP_ADDITION) {
561 /* If we have += concatenate with previous value with same name */
562 XLU_ConfigSetting *prev_set = find(ctx->cfg, name);
563 if (prev_set) {
564 r = xlu__cfg_concat_vals(ctx, prev_set->value, val);
565 if (r) {
566 ctx->err = r;
567 goto out;
568 }
569 }
570 }
571
572 set = malloc(sizeof(*set));
573 if (!set) {
574 ctx->err = errno;
575 goto out;
576 }
577 set->name= name;
578 set->value = val;
579 set->op = op;
580 set->lineno= lineno;
581 set->next= ctx->cfg->settings;
582 ctx->cfg->settings= set;
583 return;
584 out:
585 assert(ctx->err);
586 free(name);
587 xlu__cfg_value_free(val);
588 }
589
xlu__cfgl_strdup(CfgParseContext * ctx,const char * src)590 char *xlu__cfgl_strdup(CfgParseContext *ctx, const char *src) {
591 char *result;
592
593 if (ctx->err) return 0;
594 result= strdup(src);
595 if (!result) ctx->err= errno;
596 return result;
597 }
598
xlu__cfgl_dequote(CfgParseContext * ctx,const char * src)599 char *xlu__cfgl_dequote(CfgParseContext *ctx, const char *src) {
600 char *result;
601 const char *p;
602 char *q;
603 int len, c, nc;
604
605 if (ctx->err) return 0;
606
607 len= strlen(src);
608 assert(len>=2 && src[0]==src[len-1]);
609
610 result= malloc(len-1);
611 if (!result) { ctx->err= errno; return 0; }
612
613 q= result;
614
615 for (p= src+1;
616 p < src+len-1;
617 ) {
618 c= *p++;
619 if (c=='\\') {
620 assert(p < src+len-1);
621 nc= *p++;
622 if (nc=='"' || nc=='\'' || nc=='\\') {
623 *q++= nc;
624 } else if (nc=='a') { *q++= '\007';
625 } else if (nc=='b') { *q++= '\010';
626 } else if (nc=='f') { *q++= '\014';
627 } else if (nc=='n') { *q++= '\n';
628 } else if (nc=='r') { *q++= '\r';
629 } else if (nc=='t') { *q++= '\t';
630 } else if (nc=='v') { *q++= '\013';
631 } else if (nc=='x') {
632
633 #define NUMERIC_CHAR(minlen,maxlen,base,basetext) do{ \
634 char numbuf[(maxlen)+1], *ep; \
635 unsigned long val; \
636 \
637 strncpy(numbuf,p,(maxlen)); \
638 numbuf[(maxlen)]= 0; \
639 val= strtoul(numbuf, &ep, (base)); \
640 if (ep <= numbuf+(minlen)) { \
641 xlu__cfgl_lexicalerror(ctx,"invalid digit after" \
642 " backslash " basetext "numerical character escape" \
643 " in quoted string"); \
644 ctx->err= EINVAL; \
645 goto x; \
646 } \
647 p += (ep - numbuf); \
648 }while(0)
649
650 p++;
651 NUMERIC_CHAR(2,2,16,"hex");
652 } else if (nc>='0' && nc<='7') {
653 NUMERIC_CHAR(1,3,10,"octal");
654 } else {
655 xlu__cfgl_lexicalerror(ctx,
656 "invalid character after backlash in quoted string");
657 ctx->err= EINVAL;
658 goto x;
659 }
660 assert(p <= src+len-1);
661 } else {
662 *q++= c;
663 }
664 }
665
666 x:
667 *q++= 0;
668 return result;
669 }
670
xlu__cfgl_lexicalerror(CfgParseContext * ctx,char const * msg)671 void xlu__cfgl_lexicalerror(CfgParseContext *ctx, char const *msg) {
672 YYLTYPE loc;
673 loc.first_line= xlu__cfg_yyget_lineno(ctx->scanner);
674 xlu__cfg_yyerror(&loc, ctx, msg);
675 ctx->lexerrlineno= loc.first_line;
676 }
677
xlu__cfg_yyerror(YYLTYPE * loc,CfgParseContext * ctx,char const * msg)678 void xlu__cfg_yyerror(YYLTYPE *loc, CfgParseContext *ctx, char const *msg) {
679 const char *text, *newline;
680 int len, lineno;
681
682 lineno= loc->first_line;
683 if (lineno <= ctx->lexerrlineno) return;
684
685 text= xlu__cfg_yyget_text(ctx->scanner);
686 len= xlu__cfg_yyget_leng(ctx->scanner);
687 newline= "";
688 if (len>0 && text[len-1]=='\n') {
689 len--;
690 lineno--;
691 if (!len) {
692 newline= "<newline>";
693 }
694 }
695 while (len>0 && (text[len-1]=='\t' || text[len-1]==' ')) {
696 len--;
697 }
698
699 fprintf(ctx->cfg->report,
700 "%s:%d: config parsing error near %s%.*s%s%s: %s\n",
701 ctx->cfg->config_source, lineno,
702 len?"`":"", len, text, len?"'":"", newline,
703 msg);
704 if (!ctx->err) ctx->err= EINVAL;
705 }
706
707 /*
708 * Local variables:
709 * mode: C
710 * c-basic-offset: 4
711 * indent-tabs-mode: nil
712 * End:
713 */
714