initial revision
[ucgine.git] / tools / cfzy / libconfizery / cfzy_conftree_parser.c
1 /*
2  * Copyright (c) 2013, Olivier MATZ <zer0@droids-corp.org>
3  * All rights reserved.
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  *     * Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *     * Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *     * Neither the name of the University of California, Berkeley nor the
13  *       names of its contributors may be used to endorse or promote products
14  *       derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include <stdio.h>
29 #include <string.h>
30 #include <stdlib.h>
31 #include <ctype.h>
32 #include <unistd.h>
33 #include <errno.h>
34 #include <libgen.h>
35 #include <sys/param.h>
36 #include <sys/queue.h>
37
38 #include "cfzy_log.h"
39 #include "cfzy_string.h"
40 #include "cfzy_expr.h"
41 #include "cfzy_htable.h"
42 #include "cfzy_conftree.h"
43 #include "cfzy_confnode.h"
44 #include "cfzy_conftree_parser.h"
45
46 #define LOG(level, fmt, args...)                                \
47         CFZY_LOG("conftree_parser", level, fmt, ##args)
48
49 /*
50  * Structure that stores the state of the configuration tree parser.
51  */
52 struct conftree_parse_status {
53         const char *filename;           /* name of file beeing parsed */
54         FILE *f;                        /* pointer to file beeing parsed */
55         int linenum;                    /* current line number in file */
56         char *curline;                  /* pointer to current line, or
57                                            NULL if no current line */
58         struct cfzy_token_list *tklist; /* pointer to current token
59                                            list of NULL if no current line */
60         struct cfzy_confnode *parent;   /* current parent node */
61 };
62
63 static int __cfzy_conftree_parse(const char *filename,
64                                  struct cfzy_confnode **pparent);
65
66 /* return true if line is empty or contains only spaces/comments */
67 static int line_is_empty(const char *buf)
68 {
69         while (*buf != '\0' && *buf != '#') {
70                 if (!isspace(*buf))
71                         return 0;
72                 buf++;
73         }
74         return 1;
75 }
76
77 /*
78  * Add a token in the tklist structure given as argument.
79  *
80  * 'buf': pointer to the beginning of line buffer.
81  * 'offset': offset in the line where start parsing
82  *
83  * Return the number of consumed bytes if a token is found
84  * Return 0 if there is no more token
85  * Return -1 on error
86  */
87 static int
88 append_token(struct cfzy_token_list *tklist, const char *buf, int offset)
89 {
90         struct cfzy_token *tok;
91         const char *s;
92         unsigned len, space_len = 0;
93         char *retbuf;
94
95         /* skip spaces */
96         s = buf + offset;
97         while (*s != '\0' && isspace(*s))
98                 s++;
99         if (*s == '\0' || *s == '#')
100                 return 0;
101
102         tok = malloc(sizeof(struct cfzy_token));
103         if (tok == NULL) {
104                 LOG(ERR, "not enough memory\n");
105                 return -1;
106         }
107
108         space_len = s - buf - offset;
109         tok->offset = s - buf;
110
111         /* if it's a quote, unquote the string */
112         if (*s == '"' || *s == '\'') {
113                 retbuf = cfzy_string_unquote(s, &len);
114                 if (retbuf == NULL)
115                         goto free;
116         }
117         /* else wait a space */
118         else {
119                 retbuf = strdup(s);
120                 if (retbuf == NULL) {
121                         LOG(ERR, "not enough memory\n");
122                         goto free;
123                 }
124
125                 for (len = 0;
126                      s[len] != '\0' && !isspace(s[len]) && s[len] != '#';
127                      len ++)
128                         ;
129
130                 retbuf[len] = '\0';
131         }
132
133         /* fill token structure and append it in tklist */
134         tok->str = retbuf;
135         TAILQ_INSERT_TAIL(&tklist->list, tok, next);
136         tklist->n_token ++;
137
138         return len + space_len;
139
140  free:
141         free(tok);
142         return -1;
143 }
144
145 /* free a token list previously allocated with tokenize() */
146 static void free_token_list(struct cfzy_token_list *tklist)
147 {
148         struct cfzy_token *tok;
149
150         while ((tok = TAILQ_FIRST(&tklist->list)) != NULL) {
151                 TAILQ_REMOVE(&tklist->list, tok, next);
152                 free(tok->str);
153                 free(tok);
154         }
155         free(tklist);
156 }
157
158 /*
159  * Parse the line contained in buffer 'linebuf'. This function fills a
160  * tklist structure, composed of several tokens.
161  * Return the tklist pointer on success or NULL on error.
162  */
163 static struct cfzy_token_list *tokenize(const char *linebuf)
164 {
165         struct cfzy_token_list *tklist;
166         int ret, offset = 0;
167
168         tklist = malloc(sizeof(struct cfzy_token_list));
169         if (tklist == NULL) {
170                 LOG(ERR, "not enough memory\n");
171                 return NULL;
172         }
173
174         memset(tklist, 0, sizeof(struct cfzy_token_list));
175         TAILQ_INIT(&tklist->list);
176
177         /* read tokens from buf and append it to tklist structure */
178         do {
179                 ret = append_token(tklist, linebuf, offset);
180                 if (ret < 0) {
181                         free_token_list(tklist);
182                         return NULL;
183                 }
184                 offset += ret;
185         } while (ret != 0);
186
187         tklist->linebuf = linebuf;
188         return tklist;
189 }
190
191 /* duplicate buf, replacing variables like $(VAR) by their values */
192 char *replace_variables(const char *buf)
193 {
194         char *s, *buf_dup, *var;
195         const char *start;
196         char *out = NULL;
197         char c;
198
199         buf_dup = strdup(buf);
200         if (buf_dup == NULL) {
201                 LOG(ERR, "not enough memory\n");
202                 return NULL;
203         }
204
205         s = buf_dup;
206
207         for ( ; s[0] != '\0' ; s++) {
208
209                 start = s;
210
211                 /* wait a "$(" */
212                 for ( ; s[0] != '\0' && (s[0] != '$' || s[1] != '('); s++);
213                 c = s[0];
214                 s[0] = '\0';
215                 if (cfzy_string_asprintf(&out, "%s", start) < 0)
216                         goto fail;
217
218                 if (c == '\0')
219                         break;
220
221                 s += 2;
222                 start = s;
223
224                 for ( ; s[0] != '\0' && s[0] != ')'; s++);
225                 c = s[0];
226                 if (c == '\0')
227                         goto fail;
228
229                 s[0] = '\0';
230                 var = getenv(start);
231
232                 if (var != NULL) {
233                         if (cfzy_string_asprintf(&out, var) < 0)
234                                 goto fail;
235                 }
236
237         }
238
239         free(buf_dup);
240         return out;
241
242  fail:
243         free(buf_dup);
244         if (out != NULL)
245                 free(out);
246         return NULL;
247 }
248
249 /*
250  * Fill state structure with current line. If state->curline is
251  * already set, the function does nothing, else it will read a new
252  * line in the file and tokenize it. Return 0 on success, and -1 on
253  * error. If state->curline is still NULL after a call to this
254  * function, we reached the end of the file.
255  */
256 static int get_curline(struct conftree_parse_status *state)
257 {
258         char buf[BUFSIZ];
259         int line_len;
260         char *line;
261
262         /* nothing to do */
263         if (state->curline != NULL)
264                 return 0;
265
266         state->curline = fgets(buf, sizeof(buf), state->f);
267
268         /* end of file */
269         if (state->curline == NULL) {
270                 LOG(DEBUG, "EOF\n");
271                 return 0;
272         }
273
274         state->linenum ++;
275         line_len = strlen(state->curline);
276
277         /* if line ends with '\', get another line */
278         while (line_len >= 2 &&
279                state->curline[line_len - 1] == '\n' &&
280                state->curline[line_len - 2] == '\\') {
281                 if (line_len >= (int)sizeof(buf) - 1)
282                         break;
283
284                 line = fgets(buf + line_len - 2,
285                              sizeof(buf) - line_len - 2, state->f);
286                 if (line == NULL) /* EOF */
287                         break;
288
289                 line_len = strlen(state->curline);
290                 state->linenum ++;
291         }
292
293         state->curline = replace_variables(buf);
294         if (state->curline == NULL) {
295                 LOG(ERR, "Cannot eval variables\n");
296                 return -1;
297         }
298
299         state->tklist = tokenize(state->curline);
300         if (state->tklist == NULL) {
301                 LOG(ERR, "Cannot parse line\n");
302                 free(state->curline);
303                 return -1;
304         }
305
306         return 0;
307 }
308
309 /*
310  * This function is called when we processed the current line. It will
311  * free allocated memory (token list) and set state->curline to NULL.
312  */
313 static void eat_curline(struct conftree_parse_status *state)
314 {
315         if (state->curline != NULL) {
316                 free(state->curline);
317                 state->curline = NULL;
318         }
319         if (state->tklist != NULL) {
320                 free_token_list(state->tklist);
321                 state->tklist = NULL;
322         }
323 }
324
325 /* Try to parse node help. Return NO_MATCH if the current line does
326  * not match the 'help' token, SUCCESS if help is successfully parsed,
327  * or ERROR on error. */
328 static enum cfzy_parse_return
329 parse_help(struct cfzy_confnode *n, struct conftree_parse_status *state)
330 {
331         struct cfzy_token_list *tklist;
332         struct cfzy_token *tok;
333         char *buf;
334
335         if (get_curline(state) < 0)
336                 return ERROR;
337
338         buf = state->curline;
339
340         /* EOF */
341         if (buf == NULL)
342                 return NO_MATCH;
343
344         /* we want 1 token */
345         tklist = state->tklist;
346         if (tklist->n_token != 1)
347                 return NO_MATCH;
348
349         /* we want a "help" token */
350         tok = TAILQ_FIRST(&tklist->list);
351         if (strcmp(tok->str, "help") && strcmp(tok->str, "---help---"))
352                 return NO_MATCH;
353
354         LOG(INFO, "parse help\n");
355         eat_curline(state);
356
357         /* parse help content */
358         while (1) {
359
360                 /* read next line */
361                 if (get_curline(state) < 0)
362                         return ERROR;
363
364                 buf = state->curline;
365
366                 /* EOF */
367                 if (buf == NULL)
368                         return SUCCESS;
369
370                 /* skip empty lines */
371                 if (line_is_empty(buf)) {
372                         eat_curline(state);
373                         continue;
374                 }
375
376                 /* if line does not start with space, this is this end
377                    of help parsing */
378                 if (!isspace(*buf))
379                         return SUCCESS;
380
381                 /* append string in buffer, stripping starting spaces */
382                 tok = TAILQ_FIRST(&state->tklist->list);
383                 if (cfzy_string_asprintf(&n->help, "%s",
384                                   buf + tok->offset) < 0)
385                         return ERROR;
386
387                 eat_curline(state);
388         }
389 }
390
391 /* Try to parse the "source" command. Return NO_MATCH if the current
392  * line does not match the "source" token, SUCCESS if the file is
393  * successfully sourced, or ERROR on error. */
394 static enum cfzy_parse_return source_file(struct conftree_parse_status *state)
395 {
396         struct cfzy_token_list *tklist;
397         struct cfzy_token *tok;
398
399         if (get_curline(state) < 0)
400                 return ERROR;
401
402         /* EOF */
403         if (state->curline == NULL)
404                 return NO_MATCH;
405
406         tklist = state->tklist;
407         tok = TAILQ_FIRST(&tklist->list);
408
409         /* syntax is: source FILE_NAME */
410         if (tklist->n_token != 2 || strcmp(tok->str, "source"))
411                 return NO_MATCH;
412
413         tok = TAILQ_NEXT(tok, next);
414         LOG(INFO, "source <%s>\n", tok->str);
415
416         /* parse new file */
417         if (__cfzy_conftree_parse(tok->str, &state->parent) < 0) {
418                 LOG(ERR, "error in sourced file\n");
419                 return ERROR;
420         }
421
422         eat_curline(state);
423         return SUCCESS;
424 }
425
426 /* Try to create a new node command ("config", "menuconfig", "choice",
427  * "if", ...). Return NO_MATCH if the current line is not a new node
428  * command, SUCCESS if a new node is created, or ERROR on error. This
429  * function does not parse the attributes of the node but only the
430  * first line.  */
431 static enum cfzy_parse_return
432 new_node(struct conftree_parse_status *state, struct cfzy_confnode **nodep)
433 {
434         struct cfzy_token_list *tklist;
435         enum cfzy_parse_return ret;
436
437         if (get_curline(state) < 0)
438                 return ERROR;
439
440         /* EOF */
441         if (state->curline == NULL)
442                 return NO_MATCH;
443
444         tklist = state->tklist;
445
446         *nodep = cfzy_confnode_alloc(state->parent->conftree);
447         if (*nodep == NULL) {
448                 LOG(ERR, "cannot allocate node\n");
449                 return ERROR;
450         }
451         (*nodep)->parent = state->parent;
452
453         ret = cfzy_confnode_choice_new(*nodep, tklist);
454         if (ret != NO_MATCH)
455                 return ret;
456         ret = cfzy_confnode_choiceconfig_new(*nodep, tklist);
457         if (ret != NO_MATCH)
458                 return ret;
459         ret = cfzy_confnode_comment_new(*nodep, tklist);
460         if (ret != NO_MATCH)
461                 return ret;
462         ret = cfzy_confnode_config_new(*nodep, tklist);
463         if (ret != NO_MATCH)
464                 return ret;
465         ret = cfzy_confnode_if_new(*nodep, tklist);
466         if (ret != NO_MATCH)
467                 return ret;
468         ret = cfzy_confnode_intconfig_new(*nodep, tklist);
469         if (ret != NO_MATCH)
470                 return ret;
471         ret = cfzy_confnode_menu_new(*nodep, tklist);
472         if (ret != NO_MATCH)
473                 return ret;
474         ret = cfzy_confnode_menuconfig_new(*nodep, tklist);
475         if (ret != NO_MATCH)
476                 return ret;
477         ret = cfzy_confnode_strconfig_new(*nodep, tklist);
478         if (ret != NO_MATCH)
479                 return ret;
480
481         cfzy_confnode_free(*nodep);
482         return NO_MATCH;
483 }
484
485 /* Try to create a new node with all its attributes and help. Return
486  * NO_MATCH if the current line is not a new node command, SUCCESS if
487  * a new node is created, or ERROR on error. */
488 static enum cfzy_parse_return
489 parse_confnode(struct conftree_parse_status *state)
490 {
491         char *buf;
492         struct cfzy_confnode *n;
493         enum cfzy_parse_return ret;
494         struct cfzy_token_list *tklist;
495
496         if (get_curline(state) < 0)
497                 return ERROR;
498
499         buf = state->curline;
500
501         /* EOF */
502         if (buf == NULL)
503                 return NO_MATCH;
504
505         /* line must not start with a space */
506         if (isspace(*buf))
507                 return NO_MATCH;
508
509         /* if it's a new node command */
510         ret = new_node(state, &n);
511         if (ret == ERROR || ret == NO_MATCH)
512                 return ret;
513
514         /* save parsing infos */
515         n->filename = strdup(state->filename);
516         if (n->filename == NULL) {
517                 cfzy_confnode_free(n);
518                 return ERROR;
519         }
520         n->linenum = state->linenum;
521
522         TAILQ_INSERT_TAIL(&state->parent->children, n, child_next);
523         eat_curline(state);
524
525         /* parse node attributes */
526         while (1) {
527
528                 /* fill state structure with current or next line */
529                 if (get_curline(state) < 0)
530                         return ERROR;
531
532                 buf = state->curline;
533                 tklist = state->tklist;
534
535                 /* EOF */
536                 if (buf == NULL)
537                         return SUCCESS;
538
539                 /* skip empty lines */
540                 if (line_is_empty(buf)) {
541                         eat_curline(state);
542                         continue;
543                 }
544
545                 /* if line does not start with space, this node is
546                  * parsed, break */
547                 if (!isspace(*buf))
548                         break;
549
550                 /* check if it's a node attribute */
551                 ret = cfzy_confnode_add_attr(n, tklist);
552                 if (ret == ERROR)
553                         return ERROR;
554                 if (ret == SUCCESS) {
555                         eat_curline(state);
556                         continue;
557                 }
558
559                 /* check if it's a help indication */
560                 ret = parse_help(n, state);
561                 if (ret == ERROR)
562                         return ERROR;
563                 if (ret == SUCCESS)
564                         break;
565
566                 /* the attribute is invalid */
567                 LOG(ERR, "invalid node attribute\n");
568                 return ERROR;
569         }
570
571         /* enter in a new node */
572         if (n->flags & CFZY_F_IS_DIR)
573                 state->parent = n;
574
575         return SUCCESS;
576 }
577
578 /* Try to match the command that closes the current parent node, if
579  * it's a directory. Return NO_MATCH if the line does not match this
580  * command, SUCCESS if the parent node is closed (in this case,
581  * state->parent is updated), or ERROR on error. */
582 static enum cfzy_parse_return
583 close_dir(struct conftree_parse_status *state)
584 {
585         struct cfzy_token_list *tklist;
586         struct cfzy_token *tok;
587         struct cfzy_confnode *n;
588
589         n = state->parent;
590         if ((n->flags & CFZY_F_IS_DIR) == 0)
591                 return NO_MATCH;
592
593         /* fill state structure with current or next line */
594         if (get_curline(state) < 0)
595                 return ERROR;
596
597         /* EOF */
598         if (state->curline == NULL)
599                 return NO_MATCH;
600
601         tklist = state->tklist;
602         if (tklist->n_token != 1)
603                 return NO_MATCH;
604
605         /* token must be "endchoice", "endmenu", "endif", ... */
606         tok = TAILQ_FIRST(&tklist->list);
607         if (strncmp(tok->str, "end", 3))
608                 return NO_MATCH;
609         if (strcmp(tok->str + 3, cfzy_confnode_get_type_str(n)))
610                 return NO_MATCH;
611         if (cfzy_confnode_close(n) < 0) {
612                 LOG(ERR, "cannot close node\n");
613                 return ERROR;
614         }
615
616         state->parent = n->parent;
617         return SUCCESS;
618 }
619
620 /*
621  * Parse the configuration tree by reading the file stored in the
622  * 'state' structure. Return SUCCESS or ERROR.
623  */
624 static enum cfzy_parse_return parse_conftree(struct conftree_parse_status *state)
625 {
626         enum cfzy_parse_return ret;
627         char *buf;
628
629         /* browse all lines of the file */
630         while (1) {
631
632                 /* fill state structure with current or next line */
633                 if (get_curline(state) < 0)
634                         return ERROR;
635
636                 buf = state->curline;
637
638                 /* EOF */
639                 if (buf == NULL)
640                         break;
641
642                 LOG(DEBUG, "parent=%s: %s",
643                     cfzy_confnode_name(state->parent), buf);
644
645                 /* skip empty / comments lines */
646                 if (line_is_empty(buf)) {
647                         eat_curline(state);
648                         continue;
649                 }
650
651                 /* if the line does not start with a space, syntax error */
652                 if (isspace(*buf)) {
653                         LOG(ERR, "line starts with space\n");
654                         return ERROR;
655                 }
656
657                 /* if it's a source command, parse the new file */
658                 ret = source_file(state);
659                 if (ret == ERROR)
660                         return ERROR;
661                 if (ret == SUCCESS)
662                         continue;
663
664                 /* if the command closes a menu, choice, ... */
665                 ret = close_dir(state);
666                 if (ret == ERROR)
667                         return ERROR;
668                 if (ret == SUCCESS) {
669                         eat_curline(state);
670                         continue;
671                 }
672
673                 /* parse new node */
674                 ret = parse_confnode(state);
675                 if (ret == ERROR)
676                         return ERROR;
677                 if (ret == SUCCESS)
678                         continue;
679
680                 /* invalid command */
681                 LOG(ERR, "invalid command\n");
682                 return ERROR;
683         }
684
685         return SUCCESS;
686 }
687
688 /*
689  * Parse the configuration tree by reading "filename", knowing that
690  * the current parent node is pointed by pparent (which can be
691  * modified at the end of the function). This function will fill open
692  * the file, and fill a state structure for the parser, then call
693  * parse_conftree(). Return 0 on success and -1 on error.
694  */
695 static int
696 __cfzy_conftree_parse(const char *filename, struct cfzy_confnode **pparent)
697 {
698         struct conftree_parse_status state;
699         FILE *f = NULL;
700         char old_cwd_buf[PATH_MAX];
701         char *cwd_buf = NULL, *cwd_ptr = NULL, *old_cwd = NULL;
702         int ret;
703
704         memset(&state, 0, sizeof(state));
705
706         old_cwd = getcwd(old_cwd_buf, sizeof(old_cwd_buf));
707         if (old_cwd == NULL) {
708                 LOG(ERR, "getcwd failed: %s\n", strerror(errno));
709                 goto fail;
710         }
711
712         f = fopen(filename, "r");
713         if (f == NULL) {
714                 LOG(ERR, "cannot find file <%s>\n", filename);
715                 goto fail;
716         }
717
718         cwd_buf = strdup(filename);
719         if (cwd_buf == NULL) {
720                 LOG(ERR, "not enough memory\n");
721                 goto fail;
722         }
723         cwd_ptr = dirname(cwd_buf);
724         ret = chdir(cwd_ptr);
725         if (ret < 0) {
726                 LOG(ERR, "chdir failed: %s\n", strerror(errno));
727                 goto fail;
728         }
729
730         state.filename = filename;
731         state.f = f;
732         state.linenum = 0;
733         state.parent = *pparent;
734         state.curline = NULL;
735
736         /* browse all lines of the file */
737         ret = parse_conftree(&state);
738         if (ret != SUCCESS) {
739                 /* node will be freed by caller (cfzy_conftree_new) */
740                 LOG(ERR, "parse error at %s:%d\n", filename, state.linenum);
741                 LOG(ERR, ">>> %s", state.curline);
742                 goto fail;
743         }
744
745         eat_curline(&state); /* will free token_list */
746         fclose(f);
747         free(cwd_buf);
748         if (chdir(old_cwd) < 0) {
749                 LOG(ERR, "chdir back to <%s> failed: %s\n",
750                     old_cwd, strerror(errno));
751                 return -1;
752         }
753
754         *pparent = state.parent;
755         return 0;
756
757  fail:
758         if (state.f != NULL)
759                 eat_curline(&state); /* will free token_list */
760         if (f != NULL)
761                 fclose(f);
762         if (cwd_buf != NULL)
763                 free(cwd_buf);
764         if (old_cwd != NULL)
765                 if (chdir(old_cwd) < 0)
766                         LOG(ERR, "chdir back to <%s> failed: %s\n",
767                             old_cwd, strerror(errno));
768
769         return -1;
770 }
771
772 /* External api for parsing a new configuration tree file. Return 0 on
773  * success and -1 on error. */
774 int cfzy_conftree_parse(const char *filename, struct cfzy_conftree *conftree)
775 {
776         int ret;
777         struct cfzy_confnode *parent = conftree->root;
778
779         ret = __cfzy_conftree_parse(filename, &parent);
780         if (ret < 0)
781                 return ret;
782
783         if (parent != conftree->root) {
784                 LOG(ERR, "Node <%s> not closed properly\n",
785                     cfzy_confnode_name(parent));
786                 return -1;
787         }
788
789         return 0;
790 }