xref: /netbsd-src/sys/external/bsd/acpica/dist/tools/acpisrc/asfile.c (revision aceb213538ec08a74028e213127af18aa17bf1cf)
1 
2 /******************************************************************************
3  *
4  * Module Name: asfile - Main module for the acpi source processor utility
5  *
6  *****************************************************************************/
7 
8 /*
9  * Copyright (C) 2000 - 2011, Intel Corp.
10  * All rights reserved.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions, and the following disclaimer,
17  *    without modification.
18  * 2. Redistributions in binary form must reproduce at minimum a disclaimer
19  *    substantially similar to the "NO WARRANTY" disclaimer below
20  *    ("Disclaimer") and any redistribution must be conditioned upon
21  *    including a substantially similar Disclaimer requirement for further
22  *    binary redistribution.
23  * 3. Neither the names of the above-listed copyright holders nor the names
24  *    of any contributors may be used to endorse or promote products derived
25  *    from this software without specific prior written permission.
26  *
27  * Alternatively, this software may be distributed under the terms of the
28  * GNU General Public License ("GPL") version 2 as published by the Free
29  * Software Foundation.
30  *
31  * NO WARRANTY
32  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
33  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
34  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
35  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
36  * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
37  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
38  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
39  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
40  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
41  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
42  * POSSIBILITY OF SUCH DAMAGES.
43  */
44 
45 #include "acpisrc.h"
46 
47 /* Local prototypes */
48 
49 void
50 AsDoWildcard (
51     ACPI_CONVERSION_TABLE   *ConversionTable,
52     char                    *SourcePath,
53     char                    *TargetPath,
54     int                     MaxPathLength,
55     int                     FileType,
56     char                    *WildcardSpec);
57 
58 BOOLEAN
59 AsDetectLoneLineFeeds (
60     char                    *Filename,
61     char                    *Buffer);
62 
63 static ACPI_INLINE int
64 AsMaxInt (int a, int b)
65 {
66     return (a > b ? a : b);
67 }
68 
69 
70 /******************************************************************************
71  *
72  * FUNCTION:    AsDoWildcard
73  *
74  * DESCRIPTION: Process files via wildcards
75  *
76  ******************************************************************************/
77 
78 void
79 AsDoWildcard (
80     ACPI_CONVERSION_TABLE   *ConversionTable,
81     char                    *SourcePath,
82     char                    *TargetPath,
83     int                     MaxPathLength,
84     int                     FileType,
85     char                    *WildcardSpec)
86 {
87     void                    *DirInfo;
88     char                    *Filename;
89     char                    *SourceDirPath;
90     char                    *TargetDirPath;
91     char                    RequestedFileType;
92 
93 
94     if (FileType == FILE_TYPE_DIRECTORY)
95     {
96         RequestedFileType = REQUEST_DIR_ONLY;
97     }
98     else
99     {
100         RequestedFileType = REQUEST_FILE_ONLY;
101     }
102 
103     VERBOSE_PRINT (("Checking for %s source files in directory \"%s\"\n",
104             WildcardSpec, SourcePath));
105 
106     /* Open the directory for wildcard search */
107 
108     DirInfo = AcpiOsOpenDirectory (SourcePath, WildcardSpec, RequestedFileType);
109     if (DirInfo)
110     {
111         /*
112          * Get all of the files that match both the
113          * wildcard and the requested file type
114          */
115         while ((Filename = AcpiOsGetNextFilename (DirInfo)))
116         {
117             /* Looking for directory files, must check file type */
118 
119             switch (RequestedFileType)
120             {
121             case REQUEST_DIR_ONLY:
122 
123                 /* If we actually have a dir, process the subtree */
124 
125                 if (!AsCheckForDirectory (SourcePath, TargetPath, Filename,
126                         &SourceDirPath, &TargetDirPath))
127                 {
128                     VERBOSE_PRINT (("Subdirectory: %s\n", Filename));
129 
130                     AsProcessTree (ConversionTable, SourceDirPath, TargetDirPath);
131                     free (SourceDirPath);
132                     free (TargetDirPath);
133                 }
134                 break;
135 
136             case REQUEST_FILE_ONLY:
137 
138                 /* Otherwise, this is a file, not a directory */
139 
140                 VERBOSE_PRINT (("File: %s\n", Filename));
141 
142                 AsProcessOneFile (ConversionTable, SourcePath, TargetPath,
143                         MaxPathLength, Filename, FileType);
144                 break;
145 
146             default:
147                 break;
148             }
149         }
150 
151         /* Cleanup */
152 
153         AcpiOsCloseDirectory (DirInfo);
154     }
155 }
156 
157 
158 /******************************************************************************
159  *
160  * FUNCTION:    AsProcessTree
161  *
162  * DESCRIPTION: Process the directory tree.  Files with the extension ".C" and
163  *              ".H" are processed as the tree is traversed.
164  *
165  ******************************************************************************/
166 
167 ACPI_NATIVE_INT
168 AsProcessTree (
169     ACPI_CONVERSION_TABLE   *ConversionTable,
170     char                    *SourcePath,
171     char                    *TargetPath)
172 {
173     int                     MaxPathLength;
174 
175 
176     MaxPathLength = AsMaxInt (strlen (SourcePath), strlen (TargetPath));
177 
178     if (!(ConversionTable->Flags & FLG_NO_FILE_OUTPUT))
179     {
180         if (ConversionTable->Flags & FLG_LOWERCASE_DIRNAMES)
181         {
182             strlwr (TargetPath);
183         }
184 
185         VERBOSE_PRINT (("Creating Directory \"%s\"\n", TargetPath));
186         if (mkdir (TargetPath))
187         {
188             if (errno != EEXIST)
189             {
190                 printf ("Could not create target directory\n");
191                 return -1;
192             }
193         }
194     }
195 
196     /* Do the C source files */
197 
198     AsDoWildcard (ConversionTable, SourcePath, TargetPath, MaxPathLength,
199             FILE_TYPE_SOURCE, "*.c");
200 
201     /* Do the C header files */
202 
203     AsDoWildcard (ConversionTable, SourcePath, TargetPath, MaxPathLength,
204             FILE_TYPE_HEADER, "*.h");
205 
206     /* Do the Lex file(s) */
207 
208     AsDoWildcard (ConversionTable, SourcePath, TargetPath, MaxPathLength,
209             FILE_TYPE_SOURCE, "*.l");
210 
211     /* Do the yacc file(s) */
212 
213     AsDoWildcard (ConversionTable, SourcePath, TargetPath, MaxPathLength,
214             FILE_TYPE_SOURCE, "*.y");
215 
216     /* Do any ASL files */
217 
218     AsDoWildcard (ConversionTable, SourcePath, TargetPath, MaxPathLength,
219             FILE_TYPE_HEADER, "*.asl");
220 
221     /* Do any subdirectories */
222 
223     AsDoWildcard (ConversionTable, SourcePath, TargetPath, MaxPathLength,
224             FILE_TYPE_DIRECTORY, "*");
225 
226     return 0;
227 }
228 
229 
230 /******************************************************************************
231  *
232  * FUNCTION:    AsDetectLoneLineFeeds
233  *
234  * DESCRIPTION: Find LF without CR.
235  *
236  ******************************************************************************/
237 
238 BOOLEAN
239 AsDetectLoneLineFeeds (
240     char                    *Filename,
241     char                    *Buffer)
242 {
243     UINT32                  i = 1;
244     UINT32                  LfCount = 0;
245     UINT32                  LineCount = 0;
246 
247 
248     if (!Buffer[0])
249     {
250         return FALSE;
251     }
252 
253     while (Buffer[i])
254     {
255         if (Buffer[i] == 0x0A)
256         {
257             if (Buffer[i-1] != 0x0D)
258             {
259                 LfCount++;
260             }
261             LineCount++;
262         }
263         i++;
264     }
265 
266     if (LfCount)
267     {
268         if (LineCount == LfCount)
269         {
270             if (!Gbl_IgnoreLoneLineFeeds)
271             {
272                 printf ("%s: ****File has UNIX format**** (LF only, not CR/LF) %u lines\n",
273                     Filename, LfCount);
274             }
275         }
276         else
277         {
278             printf ("%s: %u lone linefeeds in file\n", Filename, LfCount);
279         }
280         return TRUE;
281     }
282 
283     return (FALSE);
284 }
285 
286 
287 /******************************************************************************
288  *
289  * FUNCTION:    AsConvertFile
290  *
291  * DESCRIPTION: Perform the requested transforms on the file buffer (as
292  *              determined by the ConversionTable and the FileType).
293  *
294  ******************************************************************************/
295 
296 void
297 AsConvertFile (
298     ACPI_CONVERSION_TABLE   *ConversionTable,
299     char                    *FileBuffer,
300     char                    *Filename,
301     ACPI_NATIVE_INT         FileType)
302 {
303     UINT32                  i;
304     UINT32                  Functions;
305     ACPI_STRING_TABLE       *StringTable;
306     ACPI_IDENTIFIER_TABLE   *ConditionalTable;
307     ACPI_IDENTIFIER_TABLE   *LineTable;
308     ACPI_IDENTIFIER_TABLE   *MacroTable;
309     ACPI_TYPED_IDENTIFIER_TABLE *StructTable;
310 
311 
312     switch (FileType)
313     {
314     case FILE_TYPE_SOURCE:
315         Functions           = ConversionTable->SourceFunctions;
316         StringTable         = ConversionTable->SourceStringTable;
317         LineTable           = ConversionTable->SourceLineTable;
318         ConditionalTable    = ConversionTable->SourceConditionalTable;
319         MacroTable          = ConversionTable->SourceMacroTable;
320         StructTable         = ConversionTable->SourceStructTable;
321        break;
322 
323     case FILE_TYPE_HEADER:
324         Functions           = ConversionTable->HeaderFunctions;
325         StringTable         = ConversionTable->HeaderStringTable;
326         LineTable           = ConversionTable->HeaderLineTable;
327         ConditionalTable    = ConversionTable->HeaderConditionalTable;
328         MacroTable          = ConversionTable->HeaderMacroTable;
329         StructTable         = ConversionTable->HeaderStructTable;
330         break;
331 
332     default:
333         printf ("Unknown file type, cannot process\n");
334         return;
335     }
336 
337 
338     Gbl_StructDefs = strstr (FileBuffer, "/* acpisrc:StructDefs");
339     Gbl_Files++;
340     VERBOSE_PRINT (("Processing %u bytes\n",
341         (unsigned int) strlen (FileBuffer)));
342 
343     if (ConversionTable->LowerCaseTable)
344     {
345         for (i = 0; ConversionTable->LowerCaseTable[i].Identifier; i++)
346         {
347             AsLowerCaseString (ConversionTable->LowerCaseTable[i].Identifier,
348                                 FileBuffer);
349         }
350     }
351 
352     /* Process all the string replacements */
353 
354     if (StringTable)
355     {
356         for (i = 0; StringTable[i].Target; i++)
357         {
358             AsReplaceString (StringTable[i].Target, StringTable[i].Replacement,
359                     StringTable[i].Type, FileBuffer);
360         }
361     }
362 
363     if (LineTable)
364     {
365         for (i = 0; LineTable[i].Identifier; i++)
366         {
367             AsRemoveLine (FileBuffer, LineTable[i].Identifier);
368         }
369     }
370 
371     if (ConditionalTable)
372     {
373         for (i = 0; ConditionalTable[i].Identifier; i++)
374         {
375             AsRemoveConditionalCompile (FileBuffer, ConditionalTable[i].Identifier);
376         }
377     }
378 
379     if (MacroTable)
380     {
381         for (i = 0; MacroTable[i].Identifier; i++)
382         {
383             AsRemoveMacro (FileBuffer, MacroTable[i].Identifier);
384         }
385     }
386 
387     if (StructTable)
388     {
389         for (i = 0; StructTable[i].Identifier; i++)
390         {
391             AsInsertPrefix (FileBuffer, StructTable[i].Identifier, StructTable[i].Type);
392         }
393     }
394 
395     /* Process the function table */
396 
397     for (i = 0; i < 32; i++)
398     {
399         /* Decode the function bitmap */
400 
401         switch ((1 << i) & Functions)
402         {
403         case 0:
404             /* This function not configured */
405             break;
406 
407 
408         case CVT_COUNT_TABS:
409 
410             AsCountTabs (FileBuffer, Filename);
411             break;
412 
413 
414         case CVT_COUNT_NON_ANSI_COMMENTS:
415 
416             AsCountNonAnsiComments (FileBuffer, Filename);
417             break;
418 
419 
420         case CVT_CHECK_BRACES:
421 
422             AsCheckForBraces (FileBuffer, Filename);
423             break;
424 
425 
426         case CVT_TRIM_LINES:
427 
428             AsTrimLines (FileBuffer, Filename);
429             break;
430 
431 
432         case CVT_COUNT_LINES:
433 
434             AsCountSourceLines (FileBuffer, Filename);
435             break;
436 
437 
438         case CVT_BRACES_ON_SAME_LINE:
439 
440             AsBracesOnSameLine (FileBuffer);
441             break;
442 
443 
444         case CVT_MIXED_CASE_TO_UNDERSCORES:
445 
446             AsMixedCaseToUnderscores (FileBuffer);
447             break;
448 
449 
450         case CVT_LOWER_CASE_IDENTIFIERS:
451 
452             AsLowerCaseIdentifiers (FileBuffer);
453             break;
454 
455 
456         case CVT_REMOVE_DEBUG_MACROS:
457 
458             AsRemoveDebugMacros (FileBuffer);
459             break;
460 
461 
462         case CVT_TRIM_WHITESPACE:
463 
464             AsTrimWhitespace (FileBuffer);
465             break;
466 
467 
468         case CVT_REMOVE_EMPTY_BLOCKS:
469 
470             AsRemoveEmptyBlocks (FileBuffer, Filename);
471             break;
472 
473 
474         case CVT_REDUCE_TYPEDEFS:
475 
476             AsReduceTypedefs (FileBuffer, "typedef union");
477             AsReduceTypedefs (FileBuffer, "typedef struct");
478             break;
479 
480 
481         case CVT_SPACES_TO_TABS4:
482 
483             AsTabify4 (FileBuffer);
484             break;
485 
486 
487         case CVT_SPACES_TO_TABS8:
488 
489             AsTabify8 (FileBuffer);
490             break;
491 
492         case CVT_COUNT_SHORTMULTILINE_COMMENTS:
493 
494 #ifdef ACPI_FUTURE_IMPLEMENTATION
495             AsTrimComments (FileBuffer, Filename);
496 #endif
497             break;
498 
499         default:
500 
501             printf ("Unknown conversion subfunction opcode\n");
502             break;
503         }
504     }
505 
506     if (ConversionTable->NewHeader)
507     {
508         AsReplaceHeader (FileBuffer, ConversionTable->NewHeader);
509     }
510 }
511 
512 
513 /******************************************************************************
514  *
515  * FUNCTION:    AsProcessOneFile
516  *
517  * DESCRIPTION: Process one source file.  The file is opened, read entirely
518  *              into a buffer, converted, then written to a new file.
519  *
520  ******************************************************************************/
521 
522 ACPI_NATIVE_INT
523 AsProcessOneFile (
524     ACPI_CONVERSION_TABLE   *ConversionTable,
525     char                    *SourcePath,
526     char                    *TargetPath,
527     int                     MaxPathLength,
528     char                    *Filename,
529     ACPI_NATIVE_INT         FileType)
530 {
531     char                    *Pathname;
532     char                    *OutPathname = NULL;
533 
534 
535     /* Allocate a file pathname buffer for both source and target */
536 
537     Pathname = calloc (MaxPathLength + strlen (Filename) + 2, 1);
538     if (!Pathname)
539     {
540         printf ("Could not allocate buffer for file pathnames\n");
541         return -1;
542     }
543 
544     Gbl_FileType = FileType;
545 
546     /* Generate the source pathname and read the file */
547 
548     if (SourcePath)
549     {
550         strcpy (Pathname, SourcePath);
551         strcat (Pathname, "/");
552     }
553 
554     strcat (Pathname, Filename);
555 
556     if (AsGetFile (Pathname, &Gbl_FileBuffer, &Gbl_FileSize))
557     {
558         return -1;
559     }
560 
561     Gbl_HeaderSize = 0;
562     if (strstr (Filename, ".asl"))
563     {
564         Gbl_HeaderSize = LINES_IN_ASL_HEADER; /* Lines in default ASL header */
565     }
566     else if (strstr (Gbl_FileBuffer, LEGAL_HEADER_SIGNATURE))
567     {
568         Gbl_HeaderSize = LINES_IN_LEGAL_HEADER; /* Normal C file and H header */
569     }
570     else if (strstr (Gbl_FileBuffer, LINUX_HEADER_SIGNATURE))
571     {
572         Gbl_HeaderSize = LINES_IN_LINUX_HEADER; /* Linuxized C file and H header */
573     }
574 
575     /* Process the file in the buffer */
576 
577     Gbl_MadeChanges = FALSE;
578     if (!Gbl_IgnoreLoneLineFeeds && Gbl_HasLoneLineFeeds)
579     {
580         /*
581          * All lone LFs will be converted to CR/LF
582          * (when file is written, Windows version only)
583          */
584         printf ("Converting lone linefeeds\n");
585         Gbl_MadeChanges = TRUE;
586     }
587 
588     AsConvertFile (ConversionTable, Gbl_FileBuffer, Pathname, FileType);
589 
590     if (!(ConversionTable->Flags & FLG_NO_FILE_OUTPUT))
591     {
592         if (!(Gbl_Overwrite && !Gbl_MadeChanges))
593         {
594             /* Generate the target pathname and write the file */
595 
596             OutPathname = calloc (MaxPathLength + strlen (Filename) + 2 + strlen (TargetPath), 1);
597             if (!OutPathname)
598             {
599                 printf ("Could not allocate buffer for file pathnames\n");
600                 return -1;
601             }
602 
603             strcpy (OutPathname, TargetPath);
604             if (SourcePath)
605             {
606                 strcat (OutPathname, "/");
607                 strcat (OutPathname, Filename);
608             }
609 
610             AsPutFile (OutPathname, Gbl_FileBuffer, ConversionTable->Flags);
611         }
612     }
613 
614     free (Gbl_FileBuffer);
615     free (Pathname);
616     if (OutPathname)
617     {
618         free (OutPathname);
619     }
620 
621     return 0;
622 }
623 
624 
625 /******************************************************************************
626  *
627  * FUNCTION:    AsCheckForDirectory
628  *
629  * DESCRIPTION: Check if the current file is a valid directory.  If not,
630  *              construct the full pathname for the source and target paths.
631  *              Checks for the dot and dot-dot files (they are ignored)
632  *
633  ******************************************************************************/
634 
635 ACPI_NATIVE_INT
636 AsCheckForDirectory (
637     char                    *SourceDirPath,
638     char                    *TargetDirPath,
639     char                    *Filename,
640     char                    **SourcePath,
641     char                    **TargetPath)
642 {
643     char                    *SrcPath;
644     char                    *TgtPath;
645 
646 
647     if (!(strcmp (Filename, ".")) ||
648         !(strcmp (Filename, "..")))
649     {
650         return -1;
651     }
652 
653     SrcPath = calloc (strlen (SourceDirPath) + strlen (Filename) + 2, 1);
654     if (!SrcPath)
655     {
656         printf ("Could not allocate buffer for directory source pathname\n");
657         return -1;
658     }
659 
660     TgtPath = calloc (strlen (TargetDirPath) + strlen (Filename) + 2, 1);
661     if (!TgtPath)
662     {
663         printf ("Could not allocate buffer for directory target pathname\n");
664         free (SrcPath);
665         return -1;
666     }
667 
668     strcpy (SrcPath, SourceDirPath);
669     strcat (SrcPath, "/");
670     strcat (SrcPath, Filename);
671 
672     strcpy (TgtPath, TargetDirPath);
673     strcat (TgtPath, "/");
674     strcat (TgtPath, Filename);
675 
676     *SourcePath = SrcPath;
677     *TargetPath = TgtPath;
678     return 0;
679 }
680 
681 
682 /******************************************************************************
683  *
684  * FUNCTION:    AsGetFile
685  *
686  * DESCRIPTION: Open a file and read it entirely into a an allocated buffer
687  *
688  ******************************************************************************/
689 
690 int
691 AsGetFile (
692     char                    *Filename,
693     char                    **FileBuffer,
694     UINT32                  *FileSize)
695 {
696 
697     int                     FileHandle;
698     UINT32                  Size;
699     char                    *Buffer;
700 
701 
702     /* Binary mode leaves CR/LF pairs */
703 
704     FileHandle = open (Filename, O_BINARY | O_RDONLY);
705     if (!FileHandle)
706     {
707         printf ("Could not open %s\n", Filename);
708         return -1;
709     }
710 
711     if (fstat (FileHandle, &Gbl_StatBuf))
712     {
713         printf ("Could not get file status for %s\n", Filename);
714         goto ErrorExit;
715     }
716 
717     /*
718      * Create a buffer for the entire file
719      * Add plenty extra buffer to accomodate string replacements
720      */
721     Size = Gbl_StatBuf.st_size;
722     Gbl_TotalSize += Size;
723 
724     Buffer = calloc (Size * 2, 1);
725     if (!Buffer)
726     {
727         printf ("Could not allocate buffer of size %u\n", Size * 2);
728         goto ErrorExit;
729     }
730 
731     /* Read the entire file */
732 
733     Size = read (FileHandle, Buffer, Size);
734     if (Size == -1)
735     {
736         printf ("Could not read the input file %s\n", Filename);
737         goto ErrorExit;
738     }
739 
740     Buffer [Size] = 0;         /* Null terminate the buffer */
741     close (FileHandle);
742 
743     /* Check for unix contamination */
744 
745     Gbl_HasLoneLineFeeds = AsDetectLoneLineFeeds (Filename, Buffer);
746 
747     /*
748      * Convert all CR/LF pairs to LF only.  We do this locally so that
749      * this code is portable across operating systems.
750      */
751     AsConvertToLineFeeds (Buffer);
752 
753     *FileBuffer = Buffer;
754     *FileSize = Size;
755 
756     return 0;
757 
758 
759 ErrorExit:
760 
761     close (FileHandle);
762     return -1;
763 }
764 
765 
766 /******************************************************************************
767  *
768  * FUNCTION:    AsPutFile
769  *
770  * DESCRIPTION: Create a new output file and write the entire contents of the
771  *              buffer to the new file.  Buffer must be a zero terminated string
772  *
773  ******************************************************************************/
774 
775 int
776 AsPutFile (
777     char                    *Pathname,
778     char                    *FileBuffer,
779     UINT32                  SystemFlags)
780 {
781     UINT32                  FileSize;
782     int                     DestHandle;
783     int                     OpenFlags;
784 
785 
786     /* Create the target file */
787 
788     OpenFlags = O_TRUNC | O_CREAT | O_WRONLY | O_BINARY;
789 
790     if (!(SystemFlags & FLG_NO_CARRIAGE_RETURNS))
791     {
792         /* Put back the CR before each LF */
793 
794         AsInsertCarriageReturns (FileBuffer);
795     }
796 
797     DestHandle = open (Pathname, OpenFlags, S_IREAD | S_IWRITE);
798     if (DestHandle == -1)
799     {
800         perror ("Could not create destination file");
801         printf ("Could not create destination file \"%s\"\n", Pathname);
802         return -1;
803     }
804 
805     /* Write the buffer to the file */
806 
807     FileSize = strlen (FileBuffer);
808     write (DestHandle, FileBuffer, FileSize);
809 
810     close (DestHandle);
811 
812     return 0;
813 }
814 
815 
816