1// Copyright 2012 Google Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8// * Redistributions of source code must retain the above copyright
9//   notice, this list of conditions and the following disclaimer.
10// * Redistributions in binary form must reproduce the above copyright
11//   notice, this list of conditions and the following disclaimer in the
12//   documentation and/or other materials provided with the distribution.
13// * Neither the name of Google Inc. nor the names of its contributors
14//   may be used to endorse or promote products derived from this software
15//   without specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29#include "atf_list.h"
30
31#include <assert.h>
32#include <errno.h>
33#include <stdarg.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37#include <unistd.h>
38
39#include "error.h"
40
41
42/// Expected header in the test program list.
43#define TP_LIST_HEADER "Content-Type: application/X-atf-tp; version=\"1\""
44
45
46/// Same as fgets, but removes any trailing newline from the output string.
47///
48/// \param [out] str Pointer to the output buffer.
49/// \param size Length of the output buffer.
50/// \param [in,out] stream File from which to read the line.
51///
52/// \return A pointer to the output buffer if successful; otherwise NULL.
53static char*
54fgets_no_newline(char* str, int size, FILE* stream)
55{
56    char* result = fgets(str, size, stream);
57    if (result != NULL) {
58        const size_t length = strlen(str);
59        if (length > 0 && str[length - 1] == '\n')
60            str[length - 1] = '\0';
61    }
62    return result;
63}
64
65
66/// Generates an error for the case where fgets() returns NULL.
67///
68/// \param input Stream on which fgets() returned an error.
69/// \param message Error message.
70///
71/// \return An error object with the error message and any relevant details.
72static kyua_error_t
73fgets_error(FILE* input, const char* message)
74{
75    if (feof(input)) {
76        return kyua_generic_error_new("%s: unexpected EOF", message);
77    } else {
78        assert(ferror(input));
79        return kyua_libc_error_new(errno, "%s", message);
80    }
81}
82
83
84/// Reads the header of the test cases list.
85///
86/// The header does not carry any useful information, so all this function does
87/// is ensure the header is valid.
88///
89/// \param [in,out] input File from which to read the header.
90///
91/// \return OK if the header is valid; an error if it is not.
92static kyua_error_t
93parse_header(FILE* input)
94{
95    char line[80];  // It's ugly to have a limit, but it's easier this way.
96
97    if (fgets_no_newline(line, sizeof(line), input) == NULL)
98        return fgets_error(input, "fgets failed to read test cases list "
99                           "header");
100    if (strcmp(line, TP_LIST_HEADER) != 0)
101        return kyua_generic_error_new("Invalid test cases list header '%s'",
102                                      line);
103
104    if (fgets_no_newline(line, sizeof(line), input) == NULL)
105        return fgets_error(input, "fgets failed to read test cases list "
106                           "header");
107    if (strcmp(line, "") != 0)
108        return kyua_generic_error_new("Incomplete test cases list header");
109
110    return kyua_error_ok();
111}
112
113
114/// Looks for the first occurrence of any of the specified delimiters.
115///
116/// \param container String in which to look for the delimiters.
117/// \param delimiters List of delimiters to look for.
118///
119/// \return A pointer to the first occurrence of the delimiter, or NULL if
120/// there is none.
121static char*
122find_first_of(char* container, const char* delimiters)
123{
124    char* ptr = container;
125    while (*ptr != '\0') {
126        if (strchr(delimiters, *ptr) != NULL)
127            return ptr;
128        ++ptr;
129    }
130    return NULL;
131}
132
133
134/// Prints a string within single quotes, with proper escaping.
135///
136/// \param [in,out] line The line to be printed.  This is a non-const pointer
137///     and the input string is modified to simplify tokenization.
138/// \param [in,out] output Buffer onto which to write the quoted string.
139/// \param surrounding If true, surround the printed value with single quotes.
140static void
141print_quoted(char* line, FILE* output, const bool surrounding)
142{
143    if (surrounding)
144        fprintf(output, "'");
145
146    char* quoteptr;
147    while ((quoteptr = find_first_of(line, "\'\\")) != NULL) {
148        const char quote = *quoteptr;
149        *quoteptr = '\0';
150        fprintf(output, "%s\\%c", line, quote);
151        line = quoteptr + 1;
152    }
153
154    if (surrounding)
155        fprintf(output, "%s'", line);
156    else
157        fprintf(output, "%s", line);
158}
159
160
161/// Parses a property from the test cases list.
162///
163/// The property is of the form "name: value", where the value extends to the
164/// end of the line without quotations.
165///
166/// \param [in,out] line The line to be parsed.  This is a non-const pointer
167///     and the input string is modified to simplify tokenization.
168/// \param [out] key The name of the property if the parsing succeeds.  This
169///     is a pointer within the input line.
170/// \param [out] value The value of the property if the parsing succeeds.  This
171///     is a pointer within the input line.
172///
173/// \return OK if the line contains a valid property; an error otherwise.
174/// In case of success, both key and value are updated.
175static kyua_error_t
176parse_property(char* line, char** const key, char** const value)
177{
178    char* delim = strstr(line, ": ");
179    if (delim == NULL)
180        return kyua_generic_error_new("Invalid property '%s'", line);
181    *delim = '\0'; *(delim + 1) = '\0';
182
183    *key = line;
184    *value = delim + 2;
185    return kyua_error_ok();
186}
187
188
189/// Static value to denote an error in the return of rewrite_property;
190static const char* rewrite_error = "ERROR";
191
192
193/// Converts the name of an ATF property to a Kyua generic property.
194///
195/// \param name The name of the ATF property to process.
196///
197/// \return The name of the corresponding Kyua property if the input property is
198/// valid; NULL if the property has a custom name that has to be handled in the
199/// parent; or rewrite_error if the property is invalid.  If this returns
200/// rewrite_error, it's OK to pointer-compare the return value to the static
201/// symbol for equality.
202static const char*
203rewrite_property(const char* name)
204{
205    if (strcmp(name, "descr") == 0)
206        return "description";
207    else if (strcmp(name, "has.cleanup") == 0)
208        return "has_cleanup";
209    else if (strcmp(name, "require.arch") == 0)
210        return "allowed_architectures";
211    else if (strcmp(name, "require.config") == 0)
212        return "required_configs";
213    else if (strcmp(name, "require.files") == 0)
214        return "required_files";
215    else if (strcmp(name, "require.machine") == 0)
216        return "allowed_platforms";
217    else if (strcmp(name, "require.memory") == 0)
218        return "required_memory";
219    else if (strcmp(name, "require.progs") == 0)
220        return "required_programs";
221    else if (strcmp(name, "require.user") == 0)
222        return "required_user";
223    else if (strcmp(name, "timeout") == 0)
224        return "timeout";
225    else if (strlen(name) > 2 && name[0] == 'X' && name[1] == '-')
226        return NULL;
227    else
228        return rewrite_error;
229}
230
231
232/// Parses a single test case and writes it to the output.
233///
234/// This has to be called after the ident property has been read, and takes care
235/// of reading the rest of the test case and printing the parsed result.
236///
237/// Be aware that this consumes the newline after the test case.  The caller
238/// should not look for it.
239///
240/// \param [in,out] input File from which to read the header.
241/// \param [in,out] output File to which to write the parsed test case.
242/// \param [in,out] name The name of the test case.  This is a non-const pointer
243///     and the input string is modified to simplify tokenization.
244///
245/// \return OK if the parsing succeeds; an error otherwise.
246static kyua_error_t
247parse_test_case(FILE* input, FILE* output, char* name)
248{
249    kyua_error_t error;
250    char line[1024];  // It's ugly to have a limit, but it's easier this way.
251
252    fprintf(output, "test_case{name=");
253    print_quoted(name, output, true);
254
255    error = kyua_error_ok();
256    while (!kyua_error_is_set(error) &&
257           fgets_no_newline(line, sizeof(line), input) != NULL &&
258           strcmp(line, "") != 0) {
259        char* key; char* value;
260        error = parse_property(line, &key, &value);
261        if (!kyua_error_is_set(error)) {
262            const char* out_key = rewrite_property(key);
263            if (out_key == rewrite_error) {
264                error = kyua_generic_error_new("Unknown ATF property %s", key);
265            } else if (out_key == NULL) {
266                fprintf(output, ", ['custom.");
267                print_quoted(key, output, false);
268                fprintf(output, "']=");
269                print_quoted(value, output, true);
270            } else {
271                fprintf(output, ", %s=", out_key);
272                print_quoted(value, output, true);
273            }
274        }
275    }
276
277    fprintf(output, "}\n");
278
279    return error;
280}
281
282
283/// Rewrites the test cases list from the input to the output.
284///
285/// \param [in,out] input Stream from which to read the test program's test
286///     cases list.  The current location must be after the header and at the
287///     first identifier (if any).
288/// \param [out] output Stream to which to write the generic list.
289///
290/// \return An error object.
291static kyua_error_t
292parse_tests(FILE* input, FILE* output)
293{
294    char line[512];  // It's ugly to have a limit, but it's easier this way.
295
296    if (fgets_no_newline(line, sizeof(line), input) == NULL) {
297        return fgets_error(input, "Empty test cases list");
298    }
299
300    kyua_error_t error;
301
302    do {
303        char* key; char* value;
304        error = parse_property(line, &key, &value);
305        if (kyua_error_is_set(error))
306            break;
307
308        if (strcmp(key, "ident") == 0) {
309            error = parse_test_case(input, output, value);
310        } else {
311            error = kyua_generic_error_new("Expected ident property, got %s",
312                                           key);
313        }
314    } while (!kyua_error_is_set(error) &&
315             fgets_no_newline(line, sizeof(line), input) != NULL);
316
317    if (!kyua_error_is_set(error)) {
318        if (ferror(input))
319            error = kyua_libc_error_new(errno, "fgets failed");
320        else
321            assert(feof(input));
322    }
323
324    return error;
325}
326
327
328/// Reads an ATF test cases list and prints a Kyua definition.
329///
330/// \param fd A file descriptor from which to read the test cases list of a test
331///     program.  Should be connected to the stdout of the latter.  This
332///     function grabs ownership of the descriptor and releases it in all cases.
333/// \param [in,out] output File to which to write the Kyua definition.
334///
335/// \return OK if the parsing succeeds; an error otherwise.  Note that, if there
336/// is an error, the output may not be consistent and should not be used.
337kyua_error_t
338atf_list_parse(const int fd, FILE* output)
339{
340    kyua_error_t error;
341
342    FILE* input = fdopen(fd, "r");
343    if (input == NULL) {
344        error = kyua_libc_error_new(errno, "fdopen(%d) failed", fd);
345        close(fd);
346    } else {
347        error = parse_header(input);
348        if (!kyua_error_is_set(error)) {
349            error = parse_tests(input, output);
350        }
351        fclose(input);
352    }
353
354    return error;
355}
356