111be35a1SLionel Sambuc // Copyright 2012 Google Inc.
211be35a1SLionel Sambuc // All rights reserved.
311be35a1SLionel Sambuc //
411be35a1SLionel Sambuc // Redistribution and use in source and binary forms, with or without
511be35a1SLionel Sambuc // modification, are permitted provided that the following conditions are
611be35a1SLionel Sambuc // met:
711be35a1SLionel Sambuc //
811be35a1SLionel Sambuc // * Redistributions of source code must retain the above copyright
911be35a1SLionel Sambuc // notice, this list of conditions and the following disclaimer.
1011be35a1SLionel Sambuc // * Redistributions in binary form must reproduce the above copyright
1111be35a1SLionel Sambuc // notice, this list of conditions and the following disclaimer in the
1211be35a1SLionel Sambuc // documentation and/or other materials provided with the distribution.
1311be35a1SLionel Sambuc // * Neither the name of Google Inc. nor the names of its contributors
1411be35a1SLionel Sambuc // may be used to endorse or promote products derived from this software
1511be35a1SLionel Sambuc // without specific prior written permission.
1611be35a1SLionel Sambuc //
1711be35a1SLionel Sambuc // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1811be35a1SLionel Sambuc // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1911be35a1SLionel Sambuc // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
2011be35a1SLionel Sambuc // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
2111be35a1SLionel Sambuc // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2211be35a1SLionel Sambuc // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
2311be35a1SLionel Sambuc // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2411be35a1SLionel Sambuc // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2511be35a1SLionel Sambuc // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2611be35a1SLionel Sambuc // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2711be35a1SLionel Sambuc // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2811be35a1SLionel Sambuc
2911be35a1SLionel Sambuc #include "atf_list.h"
3011be35a1SLionel Sambuc
3111be35a1SLionel Sambuc #include <assert.h>
3211be35a1SLionel Sambuc #include <errno.h>
3311be35a1SLionel Sambuc #include <stdarg.h>
3411be35a1SLionel Sambuc #include <stdio.h>
3511be35a1SLionel Sambuc #include <stdlib.h>
3611be35a1SLionel Sambuc #include <string.h>
3711be35a1SLionel Sambuc #include <unistd.h>
3811be35a1SLionel Sambuc
3911be35a1SLionel Sambuc #include "error.h"
4011be35a1SLionel Sambuc
4111be35a1SLionel Sambuc
4211be35a1SLionel Sambuc /// Expected header in the test program list.
4311be35a1SLionel Sambuc #define TP_LIST_HEADER "Content-Type: application/X-atf-tp; version=\"1\""
4411be35a1SLionel Sambuc
4511be35a1SLionel Sambuc
4611be35a1SLionel Sambuc /// Same as fgets, but removes any trailing newline from the output string.
4711be35a1SLionel Sambuc ///
4811be35a1SLionel Sambuc /// \param [out] str Pointer to the output buffer.
4911be35a1SLionel Sambuc /// \param size Length of the output buffer.
5011be35a1SLionel Sambuc /// \param [in,out] stream File from which to read the line.
5111be35a1SLionel Sambuc ///
5211be35a1SLionel Sambuc /// \return A pointer to the output buffer if successful; otherwise NULL.
5311be35a1SLionel Sambuc static char*
fgets_no_newline(char * str,int size,FILE * stream)5411be35a1SLionel Sambuc fgets_no_newline(char* str, int size, FILE* stream)
5511be35a1SLionel Sambuc {
5611be35a1SLionel Sambuc char* result = fgets(str, size, stream);
5711be35a1SLionel Sambuc if (result != NULL) {
5811be35a1SLionel Sambuc const size_t length = strlen(str);
5911be35a1SLionel Sambuc if (length > 0 && str[length - 1] == '\n')
6011be35a1SLionel Sambuc str[length - 1] = '\0';
6111be35a1SLionel Sambuc }
6211be35a1SLionel Sambuc return result;
6311be35a1SLionel Sambuc }
6411be35a1SLionel Sambuc
6511be35a1SLionel Sambuc
6611be35a1SLionel Sambuc /// Generates an error for the case where fgets() returns NULL.
6711be35a1SLionel Sambuc ///
6811be35a1SLionel Sambuc /// \param input Stream on which fgets() returned an error.
6911be35a1SLionel Sambuc /// \param message Error message.
7011be35a1SLionel Sambuc ///
7111be35a1SLionel Sambuc /// \return An error object with the error message and any relevant details.
7211be35a1SLionel Sambuc static kyua_error_t
fgets_error(FILE * input,const char * message)7311be35a1SLionel Sambuc fgets_error(FILE* input, const char* message)
7411be35a1SLionel Sambuc {
7511be35a1SLionel Sambuc if (feof(input)) {
7611be35a1SLionel Sambuc return kyua_generic_error_new("%s: unexpected EOF", message);
7711be35a1SLionel Sambuc } else {
7811be35a1SLionel Sambuc assert(ferror(input));
7911be35a1SLionel Sambuc return kyua_libc_error_new(errno, "%s", message);
8011be35a1SLionel Sambuc }
8111be35a1SLionel Sambuc }
8211be35a1SLionel Sambuc
8311be35a1SLionel Sambuc
8411be35a1SLionel Sambuc /// Reads the header of the test cases list.
8511be35a1SLionel Sambuc ///
8611be35a1SLionel Sambuc /// The header does not carry any useful information, so all this function does
8711be35a1SLionel Sambuc /// is ensure the header is valid.
8811be35a1SLionel Sambuc ///
8911be35a1SLionel Sambuc /// \param [in,out] input File from which to read the header.
9011be35a1SLionel Sambuc ///
9111be35a1SLionel Sambuc /// \return OK if the header is valid; an error if it is not.
9211be35a1SLionel Sambuc static kyua_error_t
parse_header(FILE * input)9311be35a1SLionel Sambuc parse_header(FILE* input)
9411be35a1SLionel Sambuc {
9511be35a1SLionel Sambuc char line[80]; // It's ugly to have a limit, but it's easier this way.
9611be35a1SLionel Sambuc
9711be35a1SLionel Sambuc if (fgets_no_newline(line, sizeof(line), input) == NULL)
9811be35a1SLionel Sambuc return fgets_error(input, "fgets failed to read test cases list "
9911be35a1SLionel Sambuc "header");
10011be35a1SLionel Sambuc if (strcmp(line, TP_LIST_HEADER) != 0)
10111be35a1SLionel Sambuc return kyua_generic_error_new("Invalid test cases list header '%s'",
10211be35a1SLionel Sambuc line);
10311be35a1SLionel Sambuc
10411be35a1SLionel Sambuc if (fgets_no_newline(line, sizeof(line), input) == NULL)
10511be35a1SLionel Sambuc return fgets_error(input, "fgets failed to read test cases list "
10611be35a1SLionel Sambuc "header");
10711be35a1SLionel Sambuc if (strcmp(line, "") != 0)
10811be35a1SLionel Sambuc return kyua_generic_error_new("Incomplete test cases list header");
10911be35a1SLionel Sambuc
11011be35a1SLionel Sambuc return kyua_error_ok();
11111be35a1SLionel Sambuc }
11211be35a1SLionel Sambuc
11311be35a1SLionel Sambuc
11411be35a1SLionel Sambuc /// Looks for the first occurrence of any of the specified delimiters.
11511be35a1SLionel Sambuc ///
11611be35a1SLionel Sambuc /// \param container String in which to look for the delimiters.
11711be35a1SLionel Sambuc /// \param delimiters List of delimiters to look for.
11811be35a1SLionel Sambuc ///
11911be35a1SLionel Sambuc /// \return A pointer to the first occurrence of the delimiter, or NULL if
12011be35a1SLionel Sambuc /// there is none.
12111be35a1SLionel Sambuc static char*
find_first_of(char * container,const char * delimiters)12211be35a1SLionel Sambuc find_first_of(char* container, const char* delimiters)
12311be35a1SLionel Sambuc {
12411be35a1SLionel Sambuc char* ptr = container;
12511be35a1SLionel Sambuc while (*ptr != '\0') {
12611be35a1SLionel Sambuc if (strchr(delimiters, *ptr) != NULL)
12711be35a1SLionel Sambuc return ptr;
12811be35a1SLionel Sambuc ++ptr;
12911be35a1SLionel Sambuc }
13011be35a1SLionel Sambuc return NULL;
13111be35a1SLionel Sambuc }
13211be35a1SLionel Sambuc
13311be35a1SLionel Sambuc
13411be35a1SLionel Sambuc /// Prints a string within single quotes, with proper escaping.
13511be35a1SLionel Sambuc ///
13611be35a1SLionel Sambuc /// \param [in,out] line The line to be printed. This is a non-const pointer
13711be35a1SLionel Sambuc /// and the input string is modified to simplify tokenization.
13811be35a1SLionel Sambuc /// \param [in,out] output Buffer onto which to write the quoted string.
13911be35a1SLionel Sambuc /// \param surrounding If true, surround the printed value with single quotes.
14011be35a1SLionel Sambuc static void
print_quoted(char * line,FILE * output,const bool surrounding)14111be35a1SLionel Sambuc print_quoted(char* line, FILE* output, const bool surrounding)
14211be35a1SLionel Sambuc {
14311be35a1SLionel Sambuc if (surrounding)
14411be35a1SLionel Sambuc fprintf(output, "'");
14511be35a1SLionel Sambuc
14611be35a1SLionel Sambuc char* quoteptr;
14711be35a1SLionel Sambuc while ((quoteptr = find_first_of(line, "\'\\")) != NULL) {
14811be35a1SLionel Sambuc const char quote = *quoteptr;
14911be35a1SLionel Sambuc *quoteptr = '\0';
15011be35a1SLionel Sambuc fprintf(output, "%s\\%c", line, quote);
15111be35a1SLionel Sambuc line = quoteptr + 1;
15211be35a1SLionel Sambuc }
15311be35a1SLionel Sambuc
15411be35a1SLionel Sambuc if (surrounding)
15511be35a1SLionel Sambuc fprintf(output, "%s'", line);
15611be35a1SLionel Sambuc else
15711be35a1SLionel Sambuc fprintf(output, "%s", line);
15811be35a1SLionel Sambuc }
15911be35a1SLionel Sambuc
16011be35a1SLionel Sambuc
16111be35a1SLionel Sambuc /// Parses a property from the test cases list.
16211be35a1SLionel Sambuc ///
16311be35a1SLionel Sambuc /// The property is of the form "name: value", where the value extends to the
16411be35a1SLionel Sambuc /// end of the line without quotations.
16511be35a1SLionel Sambuc ///
16611be35a1SLionel Sambuc /// \param [in,out] line The line to be parsed. This is a non-const pointer
16711be35a1SLionel Sambuc /// and the input string is modified to simplify tokenization.
16811be35a1SLionel Sambuc /// \param [out] key The name of the property if the parsing succeeds. This
16911be35a1SLionel Sambuc /// is a pointer within the input line.
17011be35a1SLionel Sambuc /// \param [out] value The value of the property if the parsing succeeds. This
17111be35a1SLionel Sambuc /// is a pointer within the input line.
17211be35a1SLionel Sambuc ///
17311be35a1SLionel Sambuc /// \return OK if the line contains a valid property; an error otherwise.
17411be35a1SLionel Sambuc /// In case of success, both key and value are updated.
17511be35a1SLionel Sambuc static kyua_error_t
parse_property(char * line,char ** const key,char ** const value)17611be35a1SLionel Sambuc parse_property(char* line, char** const key, char** const value)
17711be35a1SLionel Sambuc {
17811be35a1SLionel Sambuc char* delim = strstr(line, ": ");
17911be35a1SLionel Sambuc if (delim == NULL)
18011be35a1SLionel Sambuc return kyua_generic_error_new("Invalid property '%s'", line);
18111be35a1SLionel Sambuc *delim = '\0'; *(delim + 1) = '\0';
18211be35a1SLionel Sambuc
18311be35a1SLionel Sambuc *key = line;
18411be35a1SLionel Sambuc *value = delim + 2;
18511be35a1SLionel Sambuc return kyua_error_ok();
18611be35a1SLionel Sambuc }
18711be35a1SLionel Sambuc
18811be35a1SLionel Sambuc
18911be35a1SLionel Sambuc /// Static value to denote an error in the return of rewrite_property;
19011be35a1SLionel Sambuc static const char* rewrite_error = "ERROR";
19111be35a1SLionel Sambuc
19211be35a1SLionel Sambuc
19311be35a1SLionel Sambuc /// Converts the name of an ATF property to a Kyua generic property.
19411be35a1SLionel Sambuc ///
19511be35a1SLionel Sambuc /// \param name The name of the ATF property to process.
19611be35a1SLionel Sambuc ///
19711be35a1SLionel Sambuc /// \return The name of the corresponding Kyua property if the input property is
19811be35a1SLionel Sambuc /// valid; NULL if the property has a custom name that has to be handled in the
19911be35a1SLionel Sambuc /// parent; or rewrite_error if the property is invalid. If this returns
20011be35a1SLionel Sambuc /// rewrite_error, it's OK to pointer-compare the return value to the static
20111be35a1SLionel Sambuc /// symbol for equality.
20211be35a1SLionel Sambuc static const char*
rewrite_property(const char * name)20311be35a1SLionel Sambuc rewrite_property(const char* name)
20411be35a1SLionel Sambuc {
20511be35a1SLionel Sambuc if (strcmp(name, "descr") == 0)
20611be35a1SLionel Sambuc return "description";
20711be35a1SLionel Sambuc else if (strcmp(name, "has.cleanup") == 0)
20811be35a1SLionel Sambuc return "has_cleanup";
20911be35a1SLionel Sambuc else if (strcmp(name, "require.arch") == 0)
21011be35a1SLionel Sambuc return "allowed_architectures";
21111be35a1SLionel Sambuc else if (strcmp(name, "require.config") == 0)
21211be35a1SLionel Sambuc return "required_configs";
21311be35a1SLionel Sambuc else if (strcmp(name, "require.files") == 0)
21411be35a1SLionel Sambuc return "required_files";
21511be35a1SLionel Sambuc else if (strcmp(name, "require.machine") == 0)
21611be35a1SLionel Sambuc return "allowed_platforms";
21711be35a1SLionel Sambuc else if (strcmp(name, "require.memory") == 0)
21811be35a1SLionel Sambuc return "required_memory";
21911be35a1SLionel Sambuc else if (strcmp(name, "require.progs") == 0)
22011be35a1SLionel Sambuc return "required_programs";
22111be35a1SLionel Sambuc else if (strcmp(name, "require.user") == 0)
22211be35a1SLionel Sambuc return "required_user";
22311be35a1SLionel Sambuc else if (strcmp(name, "timeout") == 0)
22411be35a1SLionel Sambuc return "timeout";
22511be35a1SLionel Sambuc else if (strlen(name) > 2 && name[0] == 'X' && name[1] == '-')
22611be35a1SLionel Sambuc return NULL;
22711be35a1SLionel Sambuc else
22811be35a1SLionel Sambuc return rewrite_error;
22911be35a1SLionel Sambuc }
23011be35a1SLionel Sambuc
23111be35a1SLionel Sambuc
23211be35a1SLionel Sambuc /// Parses a single test case and writes it to the output.
23311be35a1SLionel Sambuc ///
23411be35a1SLionel Sambuc /// This has to be called after the ident property has been read, and takes care
23511be35a1SLionel Sambuc /// of reading the rest of the test case and printing the parsed result.
23611be35a1SLionel Sambuc ///
23711be35a1SLionel Sambuc /// Be aware that this consumes the newline after the test case. The caller
23811be35a1SLionel Sambuc /// should not look for it.
23911be35a1SLionel Sambuc ///
24011be35a1SLionel Sambuc /// \param [in,out] input File from which to read the header.
24111be35a1SLionel Sambuc /// \param [in,out] output File to which to write the parsed test case.
24211be35a1SLionel Sambuc /// \param [in,out] name The name of the test case. This is a non-const pointer
24311be35a1SLionel Sambuc /// and the input string is modified to simplify tokenization.
24411be35a1SLionel Sambuc ///
24511be35a1SLionel Sambuc /// \return OK if the parsing succeeds; an error otherwise.
24611be35a1SLionel Sambuc static kyua_error_t
parse_test_case(FILE * input,FILE * output,char * name)24711be35a1SLionel Sambuc parse_test_case(FILE* input, FILE* output, char* name)
24811be35a1SLionel Sambuc {
24911be35a1SLionel Sambuc kyua_error_t error;
25011be35a1SLionel Sambuc char line[1024]; // It's ugly to have a limit, but it's easier this way.
25111be35a1SLionel Sambuc
25211be35a1SLionel Sambuc fprintf(output, "test_case{name=");
25311be35a1SLionel Sambuc print_quoted(name, output, true);
25411be35a1SLionel Sambuc
25511be35a1SLionel Sambuc error = kyua_error_ok();
25611be35a1SLionel Sambuc while (!kyua_error_is_set(error) &&
25711be35a1SLionel Sambuc fgets_no_newline(line, sizeof(line), input) != NULL &&
25811be35a1SLionel Sambuc strcmp(line, "") != 0) {
2590a6a1f1dSLionel Sambuc char* key; char* value;
260*e1cdaee1SLionel Sambuc #if defined(__minix)
261*e1cdaee1SLionel Sambuc /* LSC: -Werror=maybe-uninitialized, with -O3 */
262*e1cdaee1SLionel Sambuc key = value = NULL;
263*e1cdaee1SLionel Sambuc #endif /* defined(__minix) */
26411be35a1SLionel Sambuc error = parse_property(line, &key, &value);
26511be35a1SLionel Sambuc if (!kyua_error_is_set(error)) {
26611be35a1SLionel Sambuc const char* out_key = rewrite_property(key);
26711be35a1SLionel Sambuc if (out_key == rewrite_error) {
26811be35a1SLionel Sambuc error = kyua_generic_error_new("Unknown ATF property %s", key);
26911be35a1SLionel Sambuc } else if (out_key == NULL) {
27011be35a1SLionel Sambuc fprintf(output, ", ['custom.");
27111be35a1SLionel Sambuc print_quoted(key, output, false);
27211be35a1SLionel Sambuc fprintf(output, "']=");
27311be35a1SLionel Sambuc print_quoted(value, output, true);
27411be35a1SLionel Sambuc } else {
27511be35a1SLionel Sambuc fprintf(output, ", %s=", out_key);
27611be35a1SLionel Sambuc print_quoted(value, output, true);
27711be35a1SLionel Sambuc }
27811be35a1SLionel Sambuc }
27911be35a1SLionel Sambuc }
28011be35a1SLionel Sambuc
28111be35a1SLionel Sambuc fprintf(output, "}\n");
28211be35a1SLionel Sambuc
28311be35a1SLionel Sambuc return error;
28411be35a1SLionel Sambuc }
28511be35a1SLionel Sambuc
28611be35a1SLionel Sambuc
28711be35a1SLionel Sambuc /// Rewrites the test cases list from the input to the output.
28811be35a1SLionel Sambuc ///
28911be35a1SLionel Sambuc /// \param [in,out] input Stream from which to read the test program's test
29011be35a1SLionel Sambuc /// cases list. The current location must be after the header and at the
29111be35a1SLionel Sambuc /// first identifier (if any).
29211be35a1SLionel Sambuc /// \param [out] output Stream to which to write the generic list.
29311be35a1SLionel Sambuc ///
29411be35a1SLionel Sambuc /// \return An error object.
29511be35a1SLionel Sambuc static kyua_error_t
parse_tests(FILE * input,FILE * output)29611be35a1SLionel Sambuc parse_tests(FILE* input, FILE* output)
29711be35a1SLionel Sambuc {
29811be35a1SLionel Sambuc char line[512]; // It's ugly to have a limit, but it's easier this way.
29911be35a1SLionel Sambuc
30011be35a1SLionel Sambuc if (fgets_no_newline(line, sizeof(line), input) == NULL) {
30111be35a1SLionel Sambuc return fgets_error(input, "Empty test cases list");
30211be35a1SLionel Sambuc }
30311be35a1SLionel Sambuc
30411be35a1SLionel Sambuc kyua_error_t error;
30511be35a1SLionel Sambuc
30611be35a1SLionel Sambuc do {
3070a6a1f1dSLionel Sambuc char* key; char* value;
308*e1cdaee1SLionel Sambuc #if defined(__minix)
309*e1cdaee1SLionel Sambuc /* LSC: -Werror=maybe-uninitialized, with -O3 */
310*e1cdaee1SLionel Sambuc key = value = NULL;
311*e1cdaee1SLionel Sambuc #endif /* defined(__minix) */
31211be35a1SLionel Sambuc error = parse_property(line, &key, &value);
31311be35a1SLionel Sambuc if (kyua_error_is_set(error))
31411be35a1SLionel Sambuc break;
31511be35a1SLionel Sambuc
31611be35a1SLionel Sambuc if (strcmp(key, "ident") == 0) {
31711be35a1SLionel Sambuc error = parse_test_case(input, output, value);
31811be35a1SLionel Sambuc } else {
31911be35a1SLionel Sambuc error = kyua_generic_error_new("Expected ident property, got %s",
32011be35a1SLionel Sambuc key);
32111be35a1SLionel Sambuc }
32211be35a1SLionel Sambuc } while (!kyua_error_is_set(error) &&
32311be35a1SLionel Sambuc fgets_no_newline(line, sizeof(line), input) != NULL);
32411be35a1SLionel Sambuc
32511be35a1SLionel Sambuc if (!kyua_error_is_set(error)) {
32611be35a1SLionel Sambuc if (ferror(input))
32711be35a1SLionel Sambuc error = kyua_libc_error_new(errno, "fgets failed");
32811be35a1SLionel Sambuc else
32911be35a1SLionel Sambuc assert(feof(input));
33011be35a1SLionel Sambuc }
33111be35a1SLionel Sambuc
33211be35a1SLionel Sambuc return error;
33311be35a1SLionel Sambuc }
33411be35a1SLionel Sambuc
33511be35a1SLionel Sambuc
33611be35a1SLionel Sambuc /// Reads an ATF test cases list and prints a Kyua definition.
33711be35a1SLionel Sambuc ///
33811be35a1SLionel Sambuc /// \param fd A file descriptor from which to read the test cases list of a test
33911be35a1SLionel Sambuc /// program. Should be connected to the stdout of the latter. This
34011be35a1SLionel Sambuc /// function grabs ownership of the descriptor and releases it in all cases.
34111be35a1SLionel Sambuc /// \param [in,out] output File to which to write the Kyua definition.
34211be35a1SLionel Sambuc ///
34311be35a1SLionel Sambuc /// \return OK if the parsing succeeds; an error otherwise. Note that, if there
34411be35a1SLionel Sambuc /// is an error, the output may not be consistent and should not be used.
34511be35a1SLionel Sambuc kyua_error_t
atf_list_parse(const int fd,FILE * output)34611be35a1SLionel Sambuc atf_list_parse(const int fd, FILE* output)
34711be35a1SLionel Sambuc {
34811be35a1SLionel Sambuc kyua_error_t error;
34911be35a1SLionel Sambuc
35011be35a1SLionel Sambuc FILE* input = fdopen(fd, "r");
35111be35a1SLionel Sambuc if (input == NULL) {
35211be35a1SLionel Sambuc error = kyua_libc_error_new(errno, "fdopen(%d) failed", fd);
35311be35a1SLionel Sambuc close(fd);
35411be35a1SLionel Sambuc } else {
35511be35a1SLionel Sambuc error = parse_header(input);
35611be35a1SLionel Sambuc if (!kyua_error_is_set(error)) {
35711be35a1SLionel Sambuc error = parse_tests(input, output);
35811be35a1SLionel Sambuc }
35911be35a1SLionel Sambuc fclose(input);
36011be35a1SLionel Sambuc }
36111be35a1SLionel Sambuc
36211be35a1SLionel Sambuc return error;
36311be35a1SLionel Sambuc }
364