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. 53 static char* 54 fgets_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. 72 static kyua_error_t 73 fgets_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. 92 static kyua_error_t 93 parse_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. 121 static char* 122 find_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. 140 static void 141 print_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. 175 static kyua_error_t 176 parse_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; 190 static 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. 202 static const char* 203 rewrite_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. 246 static kyua_error_t 247 parse_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 #if defined(__minix) 261 /* LSC: -Werror=maybe-uninitialized, with -O3 */ 262 key = value = NULL; 263 #endif /* defined(__minix) */ 264 error = parse_property(line, &key, &value); 265 if (!kyua_error_is_set(error)) { 266 const char* out_key = rewrite_property(key); 267 if (out_key == rewrite_error) { 268 error = kyua_generic_error_new("Unknown ATF property %s", key); 269 } else if (out_key == NULL) { 270 fprintf(output, ", ['custom."); 271 print_quoted(key, output, false); 272 fprintf(output, "']="); 273 print_quoted(value, output, true); 274 } else { 275 fprintf(output, ", %s=", out_key); 276 print_quoted(value, output, true); 277 } 278 } 279 } 280 281 fprintf(output, "}\n"); 282 283 return error; 284 } 285 286 287 /// Rewrites the test cases list from the input to the output. 288 /// 289 /// \param [in,out] input Stream from which to read the test program's test 290 /// cases list. The current location must be after the header and at the 291 /// first identifier (if any). 292 /// \param [out] output Stream to which to write the generic list. 293 /// 294 /// \return An error object. 295 static kyua_error_t 296 parse_tests(FILE* input, FILE* output) 297 { 298 char line[512]; // It's ugly to have a limit, but it's easier this way. 299 300 if (fgets_no_newline(line, sizeof(line), input) == NULL) { 301 return fgets_error(input, "Empty test cases list"); 302 } 303 304 kyua_error_t error; 305 306 do { 307 char* key; char* value; 308 #if defined(__minix) 309 /* LSC: -Werror=maybe-uninitialized, with -O3 */ 310 key = value = NULL; 311 #endif /* defined(__minix) */ 312 error = parse_property(line, &key, &value); 313 if (kyua_error_is_set(error)) 314 break; 315 316 if (strcmp(key, "ident") == 0) { 317 error = parse_test_case(input, output, value); 318 } else { 319 error = kyua_generic_error_new("Expected ident property, got %s", 320 key); 321 } 322 } while (!kyua_error_is_set(error) && 323 fgets_no_newline(line, sizeof(line), input) != NULL); 324 325 if (!kyua_error_is_set(error)) { 326 if (ferror(input)) 327 error = kyua_libc_error_new(errno, "fgets failed"); 328 else 329 assert(feof(input)); 330 } 331 332 return error; 333 } 334 335 336 /// Reads an ATF test cases list and prints a Kyua definition. 337 /// 338 /// \param fd A file descriptor from which to read the test cases list of a test 339 /// program. Should be connected to the stdout of the latter. This 340 /// function grabs ownership of the descriptor and releases it in all cases. 341 /// \param [in,out] output File to which to write the Kyua definition. 342 /// 343 /// \return OK if the parsing succeeds; an error otherwise. Note that, if there 344 /// is an error, the output may not be consistent and should not be used. 345 kyua_error_t 346 atf_list_parse(const int fd, FILE* output) 347 { 348 kyua_error_t error; 349 350 FILE* input = fdopen(fd, "r"); 351 if (input == NULL) { 352 error = kyua_libc_error_new(errno, "fdopen(%d) failed", fd); 353 close(fd); 354 } else { 355 error = parse_header(input); 356 if (!kyua_error_is_set(error)) { 357 error = parse_tests(input, output); 358 } 359 fclose(input); 360 } 361 362 return error; 363 } 364