chiark / gitweb /
journal-file: protect against alloca(0)
[elogind.git] / src / shared / conf-parser.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2010 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <string.h>
23 #include <stdio.h>
24 #include <errno.h>
25 #include <assert.h>
26 #include <stdlib.h>
27 #include <netinet/ether.h>
28
29 #include "conf-parser.h"
30 #include "util.h"
31 #include "macro.h"
32 #include "strv.h"
33 #include "log.h"
34 #include "utf8.h"
35 #include "path-util.h"
36 #include "set.h"
37 #include "exit-status.h"
38 #include "sd-messages.h"
39
40 int log_syntax_internal(const char *unit, int level,
41                         const char *file, unsigned line, const char *func,
42                         const char *config_file, unsigned config_line,
43                         int error, const char *format, ...) {
44
45         _cleanup_free_ char *msg = NULL;
46         int r;
47         va_list ap;
48
49         va_start(ap, format);
50         r = vasprintf(&msg, format, ap);
51         va_end(ap);
52         if (r < 0)
53                 return log_oom();
54
55         if (unit)
56                 r = log_struct_internal(level,
57                                         file, line, func,
58                                         getpid() == 1 ? "UNIT=%s" : "USER_UNIT=%s", unit,
59                                         MESSAGE_ID(SD_MESSAGE_CONFIG_ERROR),
60                                         "CONFIG_FILE=%s", config_file,
61                                         "CONFIG_LINE=%u", config_line,
62                                         "ERRNO=%d", error > 0 ? error : EINVAL,
63                                         "MESSAGE=[%s:%u] %s", config_file, config_line, msg,
64                                         NULL);
65         else
66                 r = log_struct_internal(level,
67                                         file, line, func,
68                                         MESSAGE_ID(SD_MESSAGE_CONFIG_ERROR),
69                                         "CONFIG_FILE=%s", config_file,
70                                         "CONFIG_LINE=%u", config_line,
71                                         "ERRNO=%d", error > 0 ? error : EINVAL,
72                                         "MESSAGE=[%s:%u] %s", config_file, config_line, msg,
73                                         NULL);
74
75         return r;
76 }
77
78 int config_item_table_lookup(
79                 void *table,
80                 const char *section,
81                 const char *lvalue,
82                 ConfigParserCallback *func,
83                 int *ltype,
84                 void **data,
85                 void *userdata) {
86
87         ConfigTableItem *t;
88
89         assert(table);
90         assert(lvalue);
91         assert(func);
92         assert(ltype);
93         assert(data);
94
95         for (t = table; t->lvalue; t++) {
96
97                 if (!streq(lvalue, t->lvalue))
98                         continue;
99
100                 if (!streq_ptr(section, t->section))
101                         continue;
102
103                 *func = t->parse;
104                 *ltype = t->ltype;
105                 *data = t->data;
106                 return 1;
107         }
108
109         return 0;
110 }
111
112 int config_item_perf_lookup(
113                 void *table,
114                 const char *section,
115                 const char *lvalue,
116                 ConfigParserCallback *func,
117                 int *ltype,
118                 void **data,
119                 void *userdata) {
120
121         ConfigPerfItemLookup lookup = (ConfigPerfItemLookup) table;
122         const ConfigPerfItem *p;
123
124         assert(table);
125         assert(lvalue);
126         assert(func);
127         assert(ltype);
128         assert(data);
129
130         if (!section)
131                 p = lookup(lvalue, strlen(lvalue));
132         else {
133                 char *key;
134
135                 key = strjoin(section, ".", lvalue, NULL);
136                 if (!key)
137                         return -ENOMEM;
138
139                 p = lookup(key, strlen(key));
140                 free(key);
141         }
142
143         if (!p)
144                 return 0;
145
146         *func = p->parse;
147         *ltype = p->ltype;
148         *data = (uint8_t*) userdata + p->offset;
149         return 1;
150 }
151
152 /* Run the user supplied parser for an assignment */
153 static int next_assignment(const char *unit,
154                            const char *filename,
155                            unsigned line,
156                            ConfigItemLookup lookup,
157                            void *table,
158                            const char *section,
159                            unsigned section_line,
160                            const char *lvalue,
161                            const char *rvalue,
162                            bool relaxed,
163                            void *userdata) {
164
165         ConfigParserCallback func = NULL;
166         int ltype = 0;
167         void *data = NULL;
168         int r;
169
170         assert(filename);
171         assert(line > 0);
172         assert(lookup);
173         assert(lvalue);
174         assert(rvalue);
175
176         r = lookup(table, section, lvalue, &func, &ltype, &data, userdata);
177         if (r < 0)
178                 return r;
179
180         if (r > 0) {
181                 if (func)
182                         return func(unit, filename, line, section, section_line,
183                                     lvalue, ltype, rvalue, data, userdata);
184
185                 return 0;
186         }
187
188         /* Warn about unknown non-extension fields. */
189         if (!relaxed && !startswith(lvalue, "X-"))
190                 log_syntax(unit, LOG_WARNING, filename, line, EINVAL,
191                            "Unknown lvalue '%s' in section '%s'", lvalue, section);
192
193         return 0;
194 }
195
196 /* Parse a variable assignment line */
197 static int parse_line(const char* unit,
198                       const char *filename,
199                       unsigned line,
200                       const char *sections,
201                       ConfigItemLookup lookup,
202                       void *table,
203                       bool relaxed,
204                       bool allow_include,
205                       char **section,
206                       unsigned *section_line,
207                       char *l,
208                       void *userdata) {
209
210         char *e;
211
212         assert(filename);
213         assert(line > 0);
214         assert(lookup);
215         assert(l);
216
217         l = strstrip(l);
218
219         if (!*l)
220                 return 0;
221
222         if (strchr(COMMENTS "\n", *l))
223                 return 0;
224
225         if (startswith(l, ".include ")) {
226                 _cleanup_free_ char *fn = NULL;
227
228                 if (!allow_include) {
229                         log_syntax(unit, LOG_ERR, filename, line, EBADMSG,
230                                    ".include not allowed here. Ignoring.");
231                         return 0;
232                 }
233
234                 fn = file_in_same_dir(filename, strstrip(l+9));
235                 if (!fn)
236                         return -ENOMEM;
237
238                 return config_parse(unit, fn, NULL, sections, lookup, table, relaxed, false, userdata);
239         }
240
241         if (*l == '[') {
242                 size_t k;
243                 char *n;
244
245                 k = strlen(l);
246                 assert(k > 0);
247
248                 if (l[k-1] != ']') {
249                         log_syntax(unit, LOG_ERR, filename, line, EBADMSG,
250                                    "Invalid section header '%s'", l);
251                         return -EBADMSG;
252                 }
253
254                 n = strndup(l+1, k-2);
255                 if (!n)
256                         return -ENOMEM;
257
258                 if (sections && !nulstr_contains(sections, n)) {
259
260                         if (!relaxed)
261                                 log_syntax(unit, LOG_WARNING, filename, line, EINVAL,
262                                            "Unknown section '%s'. Ignoring.", n);
263
264                         free(n);
265                         free(*section);
266                         *section = NULL;
267                         *section_line = 0;
268                 } else {
269                         free(*section);
270                         *section = n;
271                         *section_line = line;
272                 }
273
274                 return 0;
275         }
276
277         if (sections && !*section) {
278
279                 if (!relaxed)
280                         log_syntax(unit, LOG_WARNING, filename, line, EINVAL,
281                                    "Assignment outside of section. Ignoring.");
282
283                 return 0;
284         }
285
286         e = strchr(l, '=');
287         if (!e) {
288                 log_syntax(unit, LOG_WARNING, filename, line, EINVAL, "Missing '='.");
289                 return -EBADMSG;
290         }
291
292         *e = 0;
293         e++;
294
295         return next_assignment(unit,
296                                filename,
297                                line,
298                                lookup,
299                                table,
300                                *section,
301                                *section_line,
302                                strstrip(l),
303                                strstrip(e),
304                                relaxed,
305                                userdata);
306 }
307
308 /* Go through the file and parse each line */
309 int config_parse(const char *unit,
310                  const char *filename,
311                  FILE *f,
312                  const char *sections,
313                  ConfigItemLookup lookup,
314                  void *table,
315                  bool relaxed,
316                  bool allow_include,
317                  void *userdata) {
318
319         _cleanup_free_ char *section = NULL, *continuation = NULL;
320         _cleanup_fclose_ FILE *ours = NULL;
321         unsigned line = 0, section_line = 0;
322         int r;
323
324         assert(filename);
325         assert(lookup);
326
327         if (!f) {
328                 f = ours = fopen(filename, "re");
329                 if (!f) {
330                         log_error("Failed to open configuration file '%s': %m", filename);
331                         return -errno;
332                 }
333         }
334
335         while (!feof(f)) {
336                 char l[LINE_MAX], *p, *c = NULL, *e;
337                 bool escaped = false;
338
339                 if (!fgets(l, sizeof(l), f)) {
340                         if (feof(f))
341                                 break;
342
343                         log_error("Failed to read configuration file '%s': %m", filename);
344                         return -errno;
345                 }
346
347                 truncate_nl(l);
348
349                 if (continuation) {
350                         c = strappend(continuation, l);
351                         if (!c)
352                                 return -ENOMEM;
353
354                         free(continuation);
355                         continuation = NULL;
356                         p = c;
357                 } else
358                         p = l;
359
360                 for (e = p; *e; e++) {
361                         if (escaped)
362                                 escaped = false;
363                         else if (*e == '\\')
364                                 escaped = true;
365                 }
366
367                 if (escaped) {
368                         *(e-1) = ' ';
369
370                         if (c)
371                                 continuation = c;
372                         else {
373                                 continuation = strdup(l);
374                                 if (!continuation)
375                                         return -ENOMEM;
376                         }
377
378                         continue;
379                 }
380
381                 r = parse_line(unit,
382                                filename,
383                                ++line,
384                                sections,
385                                lookup,
386                                table,
387                                relaxed,
388                                allow_include,
389                                &section,
390                                &section_line,
391                                p,
392                                userdata);
393                 free(c);
394
395                 if (r < 0)
396                         return r;
397         }
398
399         return 0;
400 }
401
402 #define DEFINE_PARSER(type, vartype, conv_func)                         \
403         int config_parse_##type(const char *unit,                       \
404                                 const char *filename,                   \
405                                 unsigned line,                          \
406                                 const char *section,                    \
407                                 unsigned section_line,                  \
408                                 const char *lvalue,                     \
409                                 int ltype,                              \
410                                 const char *rvalue,                     \
411                                 void *data,                             \
412                                 void *userdata) {                       \
413                                                                         \
414                 vartype *i = data;                                      \
415                 int r;                                                  \
416                                                                         \
417                 assert(filename);                                       \
418                 assert(lvalue);                                         \
419                 assert(rvalue);                                         \
420                 assert(data);                                           \
421                                                                         \
422                 r = conv_func(rvalue, i);                               \
423                 if (r < 0)                                              \
424                         log_syntax(unit, LOG_ERR, filename, line, -r,   \
425                                    "Failed to parse %s value, ignoring: %s", \
426                                    #vartype, rvalue);                   \
427                                                                         \
428                 return 0;                                               \
429         }
430
431 DEFINE_PARSER(int, int, safe_atoi)
432 DEFINE_PARSER(long, long, safe_atoli)
433 DEFINE_PARSER(uint64, uint64_t, safe_atou64)
434 DEFINE_PARSER(unsigned, unsigned, safe_atou)
435 DEFINE_PARSER(double, double, safe_atod)
436 DEFINE_PARSER(nsec, nsec_t, parse_nsec)
437 DEFINE_PARSER(sec, usec_t, parse_sec)
438
439
440 int config_parse_bytes_size(const char* unit,
441                             const char *filename,
442                             unsigned line,
443                             const char *section,
444                             unsigned section_line,
445                             const char *lvalue,
446                             int ltype,
447                             const char *rvalue,
448                             void *data,
449                             void *userdata) {
450
451         size_t *sz = data;
452         off_t o;
453         int r;
454
455         assert(filename);
456         assert(lvalue);
457         assert(rvalue);
458         assert(data);
459
460         r = parse_bytes(rvalue, &o);
461         if (r < 0 || (off_t) (size_t) o != o) {
462                 log_syntax(unit, LOG_ERR, filename, line, -r,
463                            "Failed to parse byte value, ignoring: %s", rvalue);
464                 return 0;
465         }
466
467         *sz = (size_t) o;
468         return 0;
469 }
470
471
472 int config_parse_bytes_off(const char* unit,
473                            const char *filename,
474                            unsigned line,
475                            const char *section,
476                            unsigned section_line,
477                            const char *lvalue,
478                            int ltype,
479                            const char *rvalue,
480                            void *data,
481                            void *userdata) {
482
483         off_t *bytes = data;
484         int r;
485
486         assert(filename);
487         assert(lvalue);
488         assert(rvalue);
489         assert(data);
490
491         assert_cc(sizeof(off_t) == sizeof(uint64_t));
492
493         r = parse_bytes(rvalue, bytes);
494         if (r < 0)
495                 log_syntax(unit, LOG_ERR, filename, line, -r,
496                            "Failed to parse bytes value, ignoring: %s", rvalue);
497
498         return 0;
499 }
500
501 int config_parse_bool(const char* unit,
502                       const char *filename,
503                       unsigned line,
504                       const char *section,
505                       unsigned section_line,
506                       const char *lvalue,
507                       int ltype,
508                       const char *rvalue,
509                       void *data,
510                       void *userdata) {
511
512         int k;
513         bool *b = data;
514
515         assert(filename);
516         assert(lvalue);
517         assert(rvalue);
518         assert(data);
519
520         k = parse_boolean(rvalue);
521         if (k < 0) {
522                 log_syntax(unit, LOG_ERR, filename, line, -k,
523                            "Failed to parse boolean value, ignoring: %s", rvalue);
524                 return 0;
525         }
526
527         *b = !!k;
528         return 0;
529 }
530
531 int config_parse_string(const char *unit,
532                         const char *filename,
533                         unsigned line,
534                         const char *section,
535                         unsigned section_line,
536                         const char *lvalue,
537                         int ltype,
538                         const char *rvalue,
539                         void *data,
540                         void *userdata) {
541
542         char **s = data;
543         char *n;
544
545         assert(filename);
546         assert(lvalue);
547         assert(rvalue);
548         assert(data);
549
550         n = strdup(rvalue);
551         if (!n)
552                 return log_oom();
553
554         if (!utf8_is_valid(n)) {
555                 log_syntax(unit, LOG_ERR, filename, line, EINVAL,
556                            "String is not UTF-8 clean, ignoring assignment: %s", rvalue);
557                 free(n);
558                 return 0;
559         }
560
561         free(*s);
562         if (*n)
563                 *s = n;
564         else {
565                 free(n);
566                 *s = NULL;
567         }
568
569         return 0;
570 }
571
572 int config_parse_path(const char *unit,
573                       const char *filename,
574                       unsigned line,
575                       const char *section,
576                       unsigned section_line,
577                       const char *lvalue,
578                       int ltype,
579                       const char *rvalue,
580                       void *data,
581                       void *userdata) {
582
583         char **s = data;
584         char *n;
585         int offset;
586
587         assert(filename);
588         assert(lvalue);
589         assert(rvalue);
590         assert(data);
591
592         if (!utf8_is_valid(rvalue)) {
593                 log_syntax(unit, LOG_ERR, filename, line, EINVAL,
594                            "Path is not UTF-8 clean, ignoring assignment: %s", rvalue);
595                 return 0;
596         }
597
598         offset = rvalue[0] == '-' && (streq(lvalue, "InaccessibleDirectories") ||
599                                       streq(lvalue, "ReadOnlyDirectories"));
600         if (!path_is_absolute(rvalue + offset)) {
601                 log_syntax(unit, LOG_ERR, filename, line, EINVAL,
602                            "Not an absolute path, ignoring: %s", rvalue);
603                 return 0;
604         }
605
606         n = strdup(rvalue);
607         if (!n)
608                 return log_oom();
609
610         path_kill_slashes(n);
611
612         free(*s);
613         *s = n;
614
615         return 0;
616 }
617
618 int config_parse_strv(const char *unit,
619                       const char *filename,
620                       unsigned line,
621                       const char *section,
622                       unsigned section_line,
623                       const char *lvalue,
624                       int ltype,
625                       const char *rvalue,
626                       void *data,
627                       void *userdata) {
628
629         char *** sv = data, *w, *state;
630         size_t l;
631         int r;
632
633         assert(filename);
634         assert(lvalue);
635         assert(rvalue);
636         assert(data);
637
638         if (isempty(rvalue)) {
639                 char **empty;
640
641                 /* Empty assignment resets the list. As a special rule
642                  * we actually fill in a real empty array here rather
643                  * than NULL, since some code wants to know if
644                  * something was set at all... */
645                 empty = strv_new(NULL, NULL);
646                 if (!empty)
647                         return log_oom();
648
649                 strv_free(*sv);
650                 *sv = empty;
651                 return 0;
652         }
653
654         FOREACH_WORD_QUOTED(w, l, rvalue, state) {
655                 _cleanup_free_ char *n;
656
657                 n = cunescape_length(w, l);
658                 if (!n)
659                         return log_oom();
660
661                 if (!utf8_is_valid(n)) {
662                         log_syntax(unit, LOG_ERR, filename, line, EINVAL,
663                                    "String is not UTF-8 clean, ignoring: %s", rvalue);
664                         continue;
665                 }
666
667                 r = strv_extend(sv, n);
668                 if (r < 0)
669                         return log_oom();
670         }
671
672         return 0;
673 }
674
675 int config_parse_path_strv(const char *unit,
676                            const char *filename,
677                            unsigned line,
678                            const char *section,
679                            unsigned section_line,
680                            const char *lvalue,
681                            int ltype,
682                            const char *rvalue,
683                            void *data,
684                            void *userdata) {
685
686         char*** sv = data, *w, *state;
687         size_t l;
688         int r;
689
690         assert(filename);
691         assert(lvalue);
692         assert(rvalue);
693         assert(data);
694
695         if (isempty(rvalue)) {
696                 /* Empty assignment resets the list */
697                 strv_free(*sv);
698                 *sv = NULL;
699                 return 0;
700         }
701
702         FOREACH_WORD_QUOTED(w, l, rvalue, state) {
703                 _cleanup_free_ char *n;
704                 int offset;
705
706                 n = strndup(w, l);
707                 if (!n)
708                         return log_oom();
709
710                 if (!utf8_is_valid(n)) {
711                         log_syntax(unit, LOG_ERR, filename, line, EINVAL,
712                                    "Path is not UTF-8 clean, ignoring assignment: %s", rvalue);
713                         continue;
714                 }
715
716                 offset = n[0] == '-' && (streq(lvalue, "InaccessibleDirectories") ||
717                                          streq(lvalue, "ReadOnlyDirectories"));
718                 if (!path_is_absolute(n + offset)) {
719                         log_syntax(unit, LOG_ERR, filename, line, EINVAL,
720                                    "Not an absolute path, ignoring: %s", rvalue);
721                         continue;
722                 }
723
724                 path_kill_slashes(n);
725                 r = strv_extend(sv, n);
726                 if (r < 0)
727                         return log_oom();
728         }
729
730         return 0;
731 }
732
733 int config_parse_mode(const char *unit,
734                       const char *filename,
735                       unsigned line,
736                       const char *section,
737                       unsigned section_line,
738                       const char *lvalue,
739                       int ltype,
740                       const char *rvalue,
741                       void *data,
742                       void *userdata) {
743
744         mode_t *m = data;
745         long l;
746         char *x = NULL;
747
748         assert(filename);
749         assert(lvalue);
750         assert(rvalue);
751         assert(data);
752
753         errno = 0;
754         l = strtol(rvalue, &x, 8);
755         if (!x || x == rvalue || *x || errno) {
756                 log_syntax(unit, LOG_ERR, filename, line, errno,
757                            "Failed to parse mode value, ignoring: %s", rvalue);
758                 return 0;
759         }
760
761         if (l < 0000 || l > 07777) {
762                 log_syntax(unit, LOG_ERR, filename, line, ERANGE,
763                            "Mode value out of range, ignoring: %s", rvalue);
764                 return 0;
765         }
766
767         *m = (mode_t) l;
768         return 0;
769 }
770
771 int config_parse_facility(const char *unit,
772                           const char *filename,
773                           unsigned line,
774                           const char *section,
775                           unsigned section_line,
776                           const char *lvalue,
777                           int ltype,
778                           const char *rvalue,
779                           void *data,
780                           void *userdata) {
781
782
783         int *o = data, x;
784
785         assert(filename);
786         assert(lvalue);
787         assert(rvalue);
788         assert(data);
789
790         x = log_facility_unshifted_from_string(rvalue);
791         if (x < 0) {
792                 log_syntax(unit, LOG_ERR, filename, line, EINVAL,
793                            "Failed to parse log facility, ignoring: %s", rvalue);
794                 return 0;
795         }
796
797         *o = (x << 3) | LOG_PRI(*o);
798
799         return 0;
800 }
801
802 int config_parse_level(const char *unit,
803                        const char *filename,
804                        unsigned line,
805                        const char *section,
806                        unsigned section_line,
807                        const char *lvalue,
808                        int ltype,
809                        const char *rvalue,
810                        void *data,
811                        void *userdata) {
812
813
814         int *o = data, x;
815
816         assert(filename);
817         assert(lvalue);
818         assert(rvalue);
819         assert(data);
820
821         x = log_level_from_string(rvalue);
822         if (x < 0) {
823                 log_syntax(unit, LOG_ERR, filename, line, EINVAL,
824                            "Failed to parse log level, ignoring: %s", rvalue);
825                 return 0;
826         }
827
828         *o = (*o & LOG_FACMASK) | x;
829         return 0;
830 }
831
832 int config_parse_set_status(const char *unit,
833                             const char *filename,
834                             unsigned line,
835                             const char *section,
836                             unsigned section_line,
837                             const char *lvalue,
838                             int ltype,
839                             const char *rvalue,
840                             void *data,
841                             void *userdata) {
842
843         char *w;
844         size_t l;
845         char *state;
846         int r;
847         ExitStatusSet *status_set = data;
848
849         assert(filename);
850         assert(lvalue);
851         assert(rvalue);
852         assert(data);
853
854         if (isempty(rvalue)) {
855                 /* Empty assignment resets the list */
856
857                 set_free(status_set->signal);
858                 set_free(status_set->code);
859
860                 status_set->signal = status_set->code = NULL;
861                 return 0;
862         }
863
864         FOREACH_WORD(w, l, rvalue, state) {
865                 int val;
866                 char *temp;
867
868                 temp = strndup(w, l);
869                 if (!temp)
870                         return log_oom();
871
872                 r = safe_atoi(temp, &val);
873                 if (r < 0) {
874                         val = signal_from_string_try_harder(temp);
875                         free(temp);
876
877                         if (val > 0) {
878                                 r = set_ensure_allocated(&status_set->signal, trivial_hash_func, trivial_compare_func);
879                                 if (r < 0)
880                                         return log_oom();
881
882                                 r = set_put(status_set->signal, INT_TO_PTR(val));
883                                 if (r < 0) {
884                                         log_syntax(unit, LOG_ERR, filename, line, -r,
885                                                    "Unable to store: %s", w);
886                                         return r;
887                                 }
888                         } else {
889                                 log_syntax(unit, LOG_ERR, filename, line, -val,
890                                            "Failed to parse value, ignoring: %s", w);
891                                 return 0;
892                         }
893                 } else {
894                         free(temp);
895
896                         if (val < 0 || val > 255)
897                                 log_syntax(unit, LOG_ERR, filename, line, ERANGE,
898                                            "Value %d is outside range 0-255, ignoring", val);
899                         else {
900                                 r = set_ensure_allocated(&status_set->code, trivial_hash_func, trivial_compare_func);
901                                 if (r < 0)
902                                         return log_oom();
903
904                                 r = set_put(status_set->code, INT_TO_PTR(val));
905                                 if (r < 0) {
906                                         log_syntax(unit, LOG_ERR, filename, line, -r,
907                                                    "Unable to store: %s", w);
908                                         return r;
909                                 }
910                         }
911                 }
912         }
913
914         return 0;
915 }