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