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