German language improvements (bug #1606)
[asterisk/asterisk.git] / config.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Configuration File Parser
5  * 
6  * Copyright (C) 1999, Mark Spencer
7  *
8  * Mark Spencer <markster@linux-support.net>
9  *
10  * This program is free software, distributed under the terms of
11  * the GNU General Public License
12  */
13
14 #include <stdio.h>
15 #include <unistd.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <errno.h>
19 #include <time.h>
20 #include <asterisk/config.h>
21 #include <asterisk/options.h>
22 #include <asterisk/logger.h>
23 #include <asterisk/utils.h>
24 #include "asterisk.h"
25 #include "astconf.h"
26
27 #define MAX_INCLUDE_LEVEL 10
28
29 struct ast_category {
30         char name[80];
31         struct ast_variable *root;
32         struct ast_category *next;
33 #ifdef PRESERVE_COMMENTS
34         struct ast_comment *precomments;
35         struct ast_comment *sameline;
36 #endif  
37 };
38
39 struct ast_config {
40         /* Maybe this structure isn't necessary but we'll keep it
41            for now */
42         struct ast_category *root;
43         struct ast_category *prev;
44 #ifdef PRESERVE_COMMENTS
45         struct ast_comment *trailingcomments;
46 #endif  
47 };
48
49 #ifdef PRESERVE_COMMENTS
50 struct ast_comment_struct
51 {
52         struct ast_comment *root;
53         struct ast_comment *prev;
54 };
55 #endif
56
57 static char *strip(char *buf)
58 {
59         char *start;
60         /* Strip off trailing whitespace, returns, etc */
61         while(!ast_strlen_zero(buf) && (buf[strlen(buf)-1]<33))
62                 buf[strlen(buf)-1] = '\0';
63         start = buf;
64         /* Strip off leading whitespace, returns, etc */
65         while(*start && (*start < 33))
66                 *start++ = '\0';
67         return start;
68 }
69
70 #ifdef PRESERVE_COMMENTS
71 static void free_comments(struct ast_comment *com)
72 {
73         struct ast_comment *l;
74         while (com) {
75                 l = com;
76                 com = com->next;
77                 free(l);
78         }
79 }
80 #endif
81
82 void ast_destroy(struct ast_config *ast)
83 {
84         struct ast_category *cat, *catn;
85         struct ast_variable *v, *vn;
86
87         if (!ast)
88                 return;
89
90         cat = ast->root;
91         while(cat) {
92                 v = cat->root;
93                 while(v) {
94                         vn = v;
95                         free(v->name);
96                         free(v->value);
97 #ifdef PRESERVE_COMMENTS
98                         free_comments(v->precomments);
99                         free_comments(v->sameline);
100 #endif                  
101                         v = v->next;
102                         free(vn);
103                 }
104                 catn = cat;
105 #ifdef PRESERVE_COMMENTS
106                 free_comments(cat->precomments);
107                 free_comments(cat->sameline);
108 #endif          
109                 cat = cat->next;
110                 free(catn);
111         }
112 #ifdef PRESERVE_COMMENTS
113         free_comments(ast->trailingcomments);
114 #endif  
115         free(ast);
116 }
117
118 int ast_true(char *s)
119 {
120         if (!s)
121                 return 0;
122         /* Determine if this is a true value */
123         if (!strcasecmp(s, "yes") ||
124             !strcasecmp(s, "true") ||
125                 !strcasecmp(s, "y") ||
126                 !strcasecmp(s, "t") ||
127                 !strcasecmp(s, "1"))
128                         return -1;
129         return 0;
130 }
131
132 int ast_false(char *s)
133 {
134         if (!s)
135                 return 0;
136         /* Determine if this is a false value */
137         if (!strcasecmp(s, "no") ||
138             !strcasecmp(s, "false") ||
139                 !strcasecmp(s, "n") ||
140                 !strcasecmp(s, "f") ||
141                 !strcasecmp(s, "0"))
142                         return -1;
143         return 0;
144 }
145
146 struct ast_variable *ast_variable_browse(struct ast_config *config, char *category)
147 {
148         struct ast_category *cat;
149         cat = config->root;
150         while(cat) {
151                 if (cat->name == category)
152                         return cat->root;
153                 cat = cat->next;
154         }
155         cat = config->root;
156         while(cat) {
157                 if (!strcasecmp(cat->name, category))
158                         return cat->root;
159                 cat = cat->next;
160         }
161         return NULL;
162 }
163
164 char *ast_variable_retrieve(struct ast_config *config, char *category, char *value)
165 {
166         struct ast_variable *v;
167         if (category) {
168                 v = ast_variable_browse(config, category);
169                 while (v) {
170                         if (value == v->name)
171                                 return v->value;
172                         v=v->next;
173                 }
174                 v = ast_variable_browse(config, category);
175                 while (v) {
176                         if (!strcasecmp(value, v->name))
177                                 return v->value;
178                         v=v->next;
179                 }
180         } else {
181                 struct ast_category *cat;
182                 cat = config->root;
183                 while(cat) {
184                         v = cat->root;
185                         while (v) {
186                                 if (!strcasecmp(value, v->name))
187                                         return v->value;
188                                 v=v->next;
189                         }
190                         cat = cat->next;
191                 }
192         }
193         return NULL;
194 }
195
196 #ifdef PRESERVE_COMMENTS
197 int ast_variable_delete(struct ast_config *cfg, char *category, char *variable, char *value)
198 {
199         struct ast_variable *v, *pv, *bv, *bpv;
200         struct ast_category *cat;
201         cat = cfg->root;
202         while(cat) {
203                 if (cat->name == category) {
204                         break;
205                 }
206                 cat = cat->next;
207         }
208         if (!cat) {
209                 cat = cfg->root;
210                 while(cat) {
211                         if (!strcasecmp(cat->name, category)) {
212                                 break;
213                         }
214                         cat = cat->next;
215                 }
216         }
217         if (!cat)
218                 return -1;
219         v = cat->root;
220         pv = NULL;
221         while (v) {
222                 if ((variable == v->name) && (!value || !strcmp(v->value, value)))
223                         break;
224                 pv = v;
225                 v=v->next;
226         }
227         if (!v) {
228                 /* Get the last one that looks like it */
229                 bv = NULL;
230                 bpv = NULL;
231                 v = cat->root;
232                 pv = NULL;
233                 while (v) {
234                         if (!strcasecmp(variable, v->name) && (!value || !strcmp(v->value, value))) {
235                                 bv = v;
236                                 bpv = pv;
237                         }
238                         pv = v;
239                         v=v->next;
240                 }
241                 v = bv;
242         }
243
244         if (v) {
245                 /* Unlink from original position */
246                 if (pv) 
247                         pv->next = v->next;
248                 else
249                         cat->root = v->next;
250                 v->next = NULL;
251                 free(v->name);
252                 if (v->value)
253                         free(v->value);
254                 free_comments(v->sameline);
255                 free_comments(v->precomments);
256                 return 0;
257         }
258         return -1;
259 }
260
261 int ast_category_delete(struct ast_config *cfg, char *category)
262 {
263         struct ast_variable *v, *pv;
264         struct ast_category *cat, *cprev;
265         cat = cfg->root;
266         cprev = NULL;
267         while(cat) {
268                 if (cat->name == category) {
269                         break;
270                 }
271                 cprev = cat;
272                 cat = cat->next;
273         }
274         if (!cat) {
275                 cat = cfg->root;
276                 cprev = NULL;
277                 while(cat) {
278                         if (!strcasecmp(cat->name, category)) {
279                                 break;
280                         }
281                         cprev = cat;
282                         cat = cat->next;
283                 }
284         }
285         if (!cat)
286                 return -1;
287         /* Unlink it */
288         if (cprev)
289                 cprev->next = cat->next;
290         else
291                 cfg->root = cat->next;
292         v = cat->root;
293         while (v) {
294                 pv = v;
295                 v=v->next;
296                 if (pv->value)
297                         free(pv->value);
298                 if (pv->name)
299                         free(pv->name);
300                 free_comments(pv->sameline);
301                 free_comments(pv->precomments);
302                 free(pv);
303         }
304         free_comments(cat->sameline);
305         free_comments(cat->precomments);
306         free(cat);
307         return 0;
308 }
309
310 struct ast_variable *ast_variable_append_modify(struct ast_config *config, char *category, char *variable, char *value, int newcat, int newvar, int move)
311 {
312         struct ast_variable *v, *pv=NULL, *bv, *bpv;
313         struct ast_category *cat, *pcat;
314         cat = config->root;
315         if (!newcat) {
316                 while(cat) {
317                         if (cat->name == category) {
318                                 break;
319                         }
320                         cat = cat->next;
321                 }
322                 if (!cat) {
323                         cat = config->root;
324                         while(cat) {
325                                 if (!strcasecmp(cat->name, category)) {
326                                         break;
327                                 }
328                                 cat = cat->next;
329                         }
330                 }
331         }
332         if (!cat) {
333                 cat = malloc(sizeof(struct ast_category));
334                 if (!cat)
335                         return NULL;
336                 memset(cat, 0, sizeof(struct ast_category));
337                 strncpy(cat->name, category, sizeof(cat->name));
338                 if (config->root) {
339                         /* Put us at the end */
340                         pcat = config->root;
341                         while(pcat->next)
342                                 pcat = pcat->next;
343                         pcat->next = cat;
344                 } else {
345                         /* We're the first one */
346                         config->root = cat;
347                 }
348                         
349         }
350         if (!newvar) {
351                 v = cat->root;
352                 pv = NULL;
353                 while (v) {
354                         if (variable == v->name)
355                                 break;
356                         pv = v;
357                         v=v->next;
358                 }
359                 if (!v) {
360                         /* Get the last one that looks like it */
361                         bv = NULL;
362                         bpv = NULL;
363                         v = cat->root;
364                         pv = NULL;
365                         while (v) {
366                                 if (!strcasecmp(variable, v->name)) {
367                                         bv = v;
368                                         bpv = pv;
369                                 }
370                                 pv = v;
371                                 v=v->next;
372                         }
373                         v = bv;
374                 }
375         } else v = NULL;
376         if (v && move) {
377                 /* Unlink from original position */
378                 if (pv) 
379                         pv->next = v->next;
380                 else
381                         cat->root = v->next;
382                 v->next = NULL;
383         }
384         if (!v) {
385                 v = malloc(sizeof(struct ast_variable));
386                 if (!v)
387                         return NULL;
388                 memset(v, 0, sizeof(struct ast_variable));
389                 v->name = strdup(variable);
390                 move = 1;
391         }
392         if (v->value)
393                 free(v->value);
394         if (value)
395                 v->value = strdup(value);
396         else
397                 v->value = strdup("");
398         if (move) {
399                 if (cat->root) {
400                         pv = cat->root;
401                         while (pv->next) 
402                                 pv = pv->next;
403                         pv->next = v;
404                 } else {
405                         cat->root = v;
406                 }
407         }
408         return v;
409 }
410 #endif          
411
412 int ast_category_exist(struct ast_config *config, char *category_name)
413 {
414         struct ast_category *category = NULL;
415
416         category = config->root;
417
418         while(category) {
419                 if (!strcasecmp(category->name,category_name)) 
420                         return 1;
421                 category = category->next;
422         } 
423
424         return 0;
425 }
426
427 #ifdef PRESERVE_COMMENTS
428 static struct ast_comment *build_comment(char *cmt)
429 {
430         struct ast_comment *c;
431         int len = strlen(cmt) + 1;
432         c = malloc(sizeof(struct ast_comment) + len);
433         if (c) {
434                 /* Memset the header */
435                 memset(c, 0, sizeof(struct ast_comment));
436                 /* Copy the rest */
437                 strcpy(c->cmt, cmt);
438         }
439         return c;
440 }
441 #endif
442
443 static struct ast_config *__ast_load(char *configfile, struct ast_config *tmp, struct ast_category **_tmpc, struct ast_variable **_last, int includelevel
444 #ifdef PRESERVE_COMMENTS
445 , struct ast_comment_struct *acs
446 #endif
447 );
448
449 static int cfg_process(struct ast_config *tmp, struct ast_category **_tmpc, struct ast_variable **_last, char *buf, int lineno, char *configfile, int includelevel 
450 #ifdef PRESERVE_COMMENTS
451 ,struct ast_comment_struct *acs
452 #endif
453 )
454 {
455         char *c;
456         char *cur;
457         struct ast_variable *v;
458 #ifdef PRESERVE_COMMENTS
459         struct ast_comment *com = NULL;
460 #endif  
461         int object;
462         /* Strip off lines using ; as comment */
463         c = strchr(buf, ';');
464         if (c) {
465                 *c = '\0';
466 #ifdef PRESERVE_COMMENTS
467                 c++;
468                 if (*c != '!')
469                         com = build_comment(c);
470 #endif                  
471         }
472         cur = strip(buf);
473         if (!ast_strlen_zero(cur)) {
474                 /* Actually parse the entry */
475                 if (cur[0] == '[') {
476                         /* A category header */
477                         c = strchr(cur, ']');
478                         if (c) {
479                                 *c = 0;
480                                 *_tmpc = malloc(sizeof(struct ast_category));
481                                 if (!*_tmpc) {
482                                         ast_destroy(tmp);
483                                         ast_log(LOG_WARNING,
484                                                 "Out of memory, line %d\n", lineno);
485                                         return -1;
486                                 }
487                                 memset(*_tmpc, 0, sizeof(struct ast_category));
488                                 strncpy((*_tmpc)->name, cur+1, sizeof((*_tmpc)->name) - 1);
489                                 (*_tmpc)->root =  NULL;
490 #ifdef PRESERVE_COMMENTS
491                                 (*_tmpc)->precomments = acs->root;
492                                 (*_tmpc)->sameline = com;
493 #endif                          
494                                 if (!tmp->prev)
495                                         tmp->root = *_tmpc;
496                                 else
497                                         tmp->prev->next = *_tmpc;
498
499                                 tmp->prev = *_tmpc;
500 #ifdef PRESERVE_COMMENTS
501                                 acs->root = NULL;
502                                 acs->prev = NULL;
503 #endif                          
504                                 *_last =  NULL;
505                         } else {
506                                 ast_log(LOG_WARNING, 
507                                         "parse error: no closing ']', line %d of %s\n", lineno, configfile);
508                         }
509                 } else if (cur[0] == '#') {
510                         /* A directive */
511                         cur++;
512                         c = cur;
513                         while(*c && (*c > 32)) c++;
514                         if (*c) {
515                                 *c = '\0';
516                                 c++;
517                                 /* Find real argument */
518                                 while(*c  && (*c < 33)) c++;
519                                 if (!*c)
520                                         c = NULL;
521                         } else 
522                                 c = NULL;
523                         if (!strcasecmp(cur, "include")) {
524                                 /* A #include */
525                                 if (c) {
526                                         while((*c == '<') || (*c == '>') || (*c == '\"')) c++;
527                                         /* Get rid of leading mess */
528                                         cur = c;
529                                         while(!ast_strlen_zero(cur)) {
530                                                 c = cur + strlen(cur) - 1;
531                                                 if ((*c == '>') || (*c == '<') || (*c == '\"'))
532                                                         *c = '\0';
533                                                 else
534                                                         break;
535                                         }
536                                         if (includelevel < MAX_INCLUDE_LEVEL) {
537                                                 __ast_load(cur, tmp, _tmpc, _last, includelevel + 1
538 #ifdef PRESERVE_COMMENTS
539                                                 ,acs
540 #endif
541                                                 );
542                                         } else 
543                                                 ast_log(LOG_WARNING, "Maximum Include level (%d) exceeded\n", includelevel);
544                                 } else
545                                         ast_log(LOG_WARNING, "Directive '#include' needs an argument (filename) at line %d of %s\n", lineno, configfile);
546                                 /* Strip off leading and trailing "'s and <>'s */
547                         } else 
548                                 ast_log(LOG_WARNING, "Unknown directive '%s' at line %d of %s\n", cur, lineno, configfile);
549                 } else {
550                         /* Just a line (variable = value) */
551                         if (!*_tmpc) {
552                                 ast_log(LOG_WARNING,
553                                         "parse error: No category context for line %d of %s\n", lineno, configfile);
554                                 ast_destroy(tmp);
555                                 return -1;
556                         }
557                         c = strchr(cur, '=');
558                         if (c) {
559                                 *c = 0;
560                                 c++;
561                                 /* Ignore > in => */
562                                 if (*c== '>') {
563                                         object = 1;
564                                         c++;
565                                 } else
566                                         object = 0;
567                                 v = malloc(sizeof(struct ast_variable));
568                                 if (v) {
569                                         memset(v, 0, sizeof(struct ast_variable));
570                                         v->next = NULL;
571                                         v->name = strdup(strip(cur));
572                                         v->value = strdup(strip(c));
573                                         v->lineno = lineno;
574                                         v->object = object;
575                                         /* Put and reset comments */
576 #ifdef PRESERVE_COMMENTS
577                                         v->precomments = acs->root;
578                                         v->sameline = com;
579                                         acs->prev = NULL;
580                                         acs->root = NULL;
581 #endif                                  
582                                         v->blanklines = 0;
583                                         if (*_last)
584                                                 (*_last)->next = v;
585                                         else
586                                                 (*_tmpc)->root = v;
587                                         *_last = v;
588                                 } else {
589                                         ast_destroy(tmp);
590                                         ast_log(LOG_WARNING, "Out of memory, line %d\n", lineno);
591                                         return -1;
592                                 }
593                         } else {
594                                 ast_log(LOG_WARNING, "No '=' (equal sign) in line %d of %s\n", lineno, configfile);
595                         }
596                                                                                                                 
597                 }
598         } else {
599                 /* store any comments if there are any */
600 #ifdef PRESERVE_COMMENTS
601                 if (com) {
602                         if (acs->prev)
603                                 acs->prev->next = com;
604                         else
605                                 acs->root = com;
606                         acs->prev = com;
607                 } else {
608                 if (*_last) 
609                         (*_last)->blanklines++;
610
611                 }
612 #endif
613         }
614         return 0;
615 }
616
617 #ifdef PRESERVE_COMMENTS
618 static void dump_comments(FILE *f, struct ast_comment *comment)
619 {
620         while (comment) {
621                 fprintf(f, ";%s", comment->cmt);
622                 comment = comment->next;
623         }
624 }
625 #endif
626
627 int ast_save(char *configfile, struct ast_config *cfg, char *generator)
628 {
629         FILE *f;
630         char fn[256];
631         char date[256];
632         time_t t;
633         struct ast_variable *var;
634         struct ast_category *cat;
635         int blanklines = 0;
636         if (configfile[0] == '/') {
637                 strncpy(fn, configfile, sizeof(fn)-1);
638         } else {
639                 snprintf(fn, sizeof(fn), "%s/%s", AST_CONFIG_DIR, configfile);
640         }
641         time(&t);
642         strncpy(date, ctime(&t), sizeof(date));
643         if ((f = fopen(fn, "w"))) {
644                 if ((option_verbose > 1) && !option_debug)
645                         ast_verbose(  VERBOSE_PREFIX_2 "Saving '%s': ", fn);
646                 fprintf(f, ";!\n");
647                 fprintf(f, ";! Automatically generated configuration file\n");
648                 fprintf(f, ";! Filename: %s (%s)\n", configfile, fn);
649                 fprintf(f, ";! Generator: %s\n", generator);
650                 fprintf(f, ";! Creation Date: %s", date);
651                 fprintf(f, ";!\n");
652                 cat = cfg->root;
653                 while(cat) {
654 #ifdef PRESERVE_COMMENTS
655                         /* Dump any precomments */
656                         dump_comments(f, cat->precomments);
657 #endif
658                         /* Dump section with any appropriate comment */
659 #ifdef PRESERVE_COMMENTS
660                         if (cat->sameline) 
661                                 fprintf(f, "[%s]  ; %s\n", cat->name, cat->sameline->cmt);
662                         else
663 #endif
664                                 fprintf(f, "[%s]\n", cat->name);
665                         var = cat->root;
666                         while(var) {
667 #ifdef PRESERVE_COMMENTS
668                                 dump_comments(f, var->precomments);
669 #endif                          
670                                 if (var->sameline) 
671                                         fprintf(f, "%s %s %s  ; %s\n", var->name, (var->object ? "=>" : "="), var->value, var->sameline->cmt);
672                                 else    
673                                         fprintf(f, "%s %s %s\n", var->name, (var->object ? "=>" : "="), var->value);
674                                 if (var->blanklines) {
675                                         blanklines = var->blanklines;
676                                         while (blanklines) {
677                                                 fprintf(f, "\n");
678                                                 blanklines--;
679                                         }
680                                 }
681                                         
682                                 var = var->next;
683                         }
684 #if 0
685                         /* Put an empty line */
686                         fprintf(f, "\n");
687 #endif
688                         cat = cat->next;
689                 }
690 #ifdef PRESERVE_COMMENTS
691                 dump_comments(f, cfg->trailingcomments);
692 #endif          
693         } else {
694                 if (option_debug)
695                         printf("Unable to open for writing: %s\n", fn);
696                 else if (option_verbose > 1)
697                         printf( "Unable to write (%s)", strerror(errno));
698                 return -1;
699         }
700         fclose(f);
701         return 0;
702 }
703
704 static struct ast_config *__ast_load(char *configfile, struct ast_config *tmp, struct ast_category **_tmpc, struct ast_variable **_last, int includelevel
705 #ifdef PRESERVE_COMMENTS
706 , struct ast_comment_struct *acs
707 #endif
708 )
709 {
710         char fn[256];
711         char buf[512];
712         FILE *f;
713         int lineno=0;
714         int master=0;
715
716         if (configfile[0] == '/') {
717                 strncpy(fn, configfile, sizeof(fn)-1);
718         } else {
719                 snprintf(fn, sizeof(fn), "%s/%s", (char *)ast_config_AST_CONFIG_DIR, configfile);
720         }
721         if ((option_verbose > 1) && !option_debug) {
722                 ast_verbose(  VERBOSE_PREFIX_2 "Parsing '%s': ", fn);
723                 fflush(stdout);
724         }
725         if ((f = fopen(fn, "r"))) {
726                 if (option_debug)
727                         ast_log(LOG_DEBUG, "Parsing %s\n", fn);
728                 else if (option_verbose > 1)
729                         ast_verbose( "Found\n");
730                 if (!tmp) {
731                         tmp = malloc(sizeof(struct ast_config));
732                         if (tmp)
733                                 memset(tmp, 0, sizeof(struct ast_config));
734
735                         master = 1;
736                 }
737                 if (!tmp) {
738                         ast_log(LOG_WARNING, "Out of memory\n");
739                         fclose(f);
740                         return NULL;
741                 }
742                 while(!feof(f)) {
743                         fgets(buf, sizeof(buf), f);
744                         lineno++;
745                         if (!feof(f)) {
746                                 if (cfg_process(tmp, _tmpc, _last, buf, lineno, configfile, includelevel
747 #ifdef PRESERVE_COMMENTS
748                                 , acs
749 #endif
750                                 )) {
751                                         fclose(f);
752                                         return NULL;
753                                 }
754                         }
755                 }
756                 fclose(f);              
757         } else {
758                 if (option_debug)
759                         ast_log(LOG_DEBUG, "No file to parse: %s\n", fn);
760                 else if (option_verbose > 1)
761                         ast_verbose( "Not found (%s)\n", strerror(errno));
762         }
763 #ifdef PRESERVE_COMMENTS
764         if (master) {
765                 /* Keep trailing comments */
766                 tmp->trailingcomments = acs->root;
767                 acs->root = NULL;
768                 acs->prev = NULL;
769         }
770 #endif
771         return tmp;
772 }
773
774 struct ast_config *ast_load(char *configfile)
775 {
776         struct ast_category *tmpc=NULL;
777         struct ast_variable *last = NULL;
778 #ifdef PRESERVE_COMMENTS
779         struct ast_comment_struct acs = { NULL, NULL };
780 #endif  
781         return __ast_load(configfile, NULL, &tmpc, &last, 0 
782 #ifdef PRESERVE_COMMENTS
783         ,&acs
784 #endif
785         );
786 }
787
788 char *ast_category_browse(struct ast_config *config, char *prev)
789 {       
790         struct ast_category *cat;
791         if (!prev) {
792                 if (config->root)
793                         return config->root->name;
794                 else
795                         return NULL;
796         }
797         cat = config->root;
798         while(cat) {
799                 if (cat->name == prev) {
800                         if (cat->next)
801                                 return cat->next->name;
802                         else
803                                 return NULL;
804                 }
805                 cat = cat->next;
806         }
807         cat = config->root;
808         while(cat) {
809                 if (!strcasecmp(cat->name, prev)) {
810                         if (cat->next)
811                                 return cat->next->name;
812                         else
813                                 return NULL;
814                 }
815                 cat = cat->next;
816         }
817         return NULL;
818 }