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 #if defined(HAVE_CONFIG_H) 30 #include "config.h" 31 #endif 32 33 #include "cli.h" 34 35 #include <assert.h> 36 #include <err.h> 37 #include <errno.h> 38 #include <limits.h> 39 #include <stdbool.h> 40 #include <stdlib.h> 41 #include <string.h> 42 #include <unistd.h> 43 44 #include "defs.h" 45 #include "error.h" 46 #include "run.h" 47 48 #if !defined(GID_MAX) 49 # define GID_MAX INT_MAX 50 #endif 51 #if !defined(UID_MAX) 52 # define UID_MAX INT_MAX 53 #endif 54 55 #if defined(HAVE_GETOPT_GNU) 56 # define GETOPT_PLUS "+" 57 #else 58 # define GETOPT_PLUS 59 #endif 60 61 62 /// Terminates execution if the given error is set. 63 /// 64 /// \param error The error to validate. 65 /// 66 /// \post The program terminates if the given error is set. 67 static void 68 check_error(kyua_error_t error) 69 { 70 if (kyua_error_is_set(error)) { 71 const bool usage_error = kyua_error_is_type(error, 72 kyua_usage_error_type); 73 74 char buffer[1024]; 75 kyua_error_format(error, buffer, sizeof(buffer)); 76 kyua_error_free(error); 77 78 errx(usage_error ? EXIT_USAGE_ERROR : EXIT_INTERNAL_ERROR, 79 "%s", buffer); 80 } 81 } 82 83 84 /// Converts a string to an unsigned long. 85 /// 86 /// \param str The string to convert. 87 /// \param message Part of the error message to print if the string does not 88 /// represent a valid unsigned long number. 89 /// \param max Maximum accepted value. 90 /// 91 /// \return The converted numerical value. 92 /// 93 /// \post The program terminates if the value is invalid. 94 static unsigned long 95 parse_ulong(const char* str, const char* message, const unsigned long max) 96 { 97 char *endptr; 98 99 errno = 0; 100 const unsigned long value = strtoul(str, &endptr, 10); 101 if (str[0] == '\0' || *endptr != '\0') 102 errx(EXIT_USAGE_ERROR, "%s '%s' (not a number)", message, str); 103 else if (errno == ERANGE || value == LONG_MAX || value > max) 104 errx(EXIT_USAGE_ERROR, "%s '%s' (out of range)", message, str); 105 return value; 106 } 107 108 109 /// Clears getopt(3) state to allow calling the function again. 110 static void 111 reset_getopt(void) 112 { 113 opterr = 0; 114 optind = GETOPT_OPTIND_RESET_VALUE; 115 #if defined(HAVE_GETOPT_WITH_OPTRESET) 116 optreset = 1; 117 #endif 118 } 119 120 121 /// Prints the list of test cases and their metadata in a test program. 122 /// 123 /// \param argc Number of arguments to the command, including the command name. 124 /// \param argv Arguments to the command, including the command name. 125 /// \param tester Description of the tester implemented by this binary. 126 /// \param run_params Execution parameters to configure the test process. 127 /// 128 /// \return An exit status to indicate the success or failure of the listing. 129 /// 130 /// \post Usage errors terminate the execution of the program right away. 131 static int 132 list_command(const int argc, char* const* const argv, 133 const kyua_cli_tester_t* tester, 134 const kyua_run_params_t* run_params) 135 { 136 if (argc < 2) 137 errx(EXIT_USAGE_ERROR, "No test program provided"); 138 else if (argc > 2) 139 errx(EXIT_USAGE_ERROR, "Only one test program allowed"); 140 const char* test_program = argv[1]; 141 142 check_error(tester->list_test_cases(test_program, run_params)); 143 return EXIT_SUCCESS; 144 } 145 146 147 /// Runs and cleans up a single test case. 148 /// 149 /// \param argc Number of arguments to the command, including the command name. 150 /// \param argv Arguments to the command, including the command name. 151 /// \param tester Description of the tester implemented by this binary. 152 /// \param run_params Execution parameters to configure the test process. 153 /// 154 /// \return An exit status to indicate the success or failure of the test case 155 /// execution. 156 /// 157 /// \post Usage errors terminate the execution of the program right away. 158 static int 159 test_command(int argc, char* const* argv, const kyua_cli_tester_t* tester, 160 const kyua_run_params_t* run_params) 161 { 162 # define MAX_USER_VARIABLES 256 163 const char* user_variables[MAX_USER_VARIABLES]; 164 165 const char** last_variable = user_variables; 166 int ch; 167 while ((ch = getopt(argc, argv, GETOPT_PLUS":v:")) != -1) { 168 switch (ch) { 169 case 'v': 170 *last_variable++ = optarg; 171 break; 172 173 case ':': 174 errx(EXIT_USAGE_ERROR, "%s's -%c requires an argument", argv[0], 175 optopt); 176 177 case '?': 178 errx(EXIT_USAGE_ERROR, "Unknown %s option -%c", argv[0], optopt); 179 180 default: 181 assert(false); 182 } 183 } 184 argc -= optind; 185 argv += optind; 186 *last_variable = NULL; 187 188 if (argc != 3) 189 errx(EXIT_USAGE_ERROR, "Must provide a test program, a test case name " 190 "and a result file"); 191 const char* test_program = argv[0]; 192 const char* test_case = argv[1]; 193 const char* result_file = argv[2]; 194 195 bool success; 196 check_error(tester->run_test_case(test_program, test_case, result_file, 197 user_variables, run_params, &success)); 198 return success ? EXIT_SUCCESS : EXIT_FAILURE; 199 } 200 201 202 /// Generic entry point to the tester's command-line interface. 203 /// 204 /// \param argc Verbatim argc passed to main(). 205 /// \param argv Verbatim argv passed to main(). 206 /// \param tester Description of the tester implemented by this binary. 207 /// 208 /// \return An exit status. 209 /// 210 /// \post Usage errors terminate the execution of the program right away. 211 int 212 kyua_cli_main(int argc, char* const* argv, const kyua_cli_tester_t* tester) 213 { 214 kyua_run_params_t run_params; 215 kyua_run_params_init(&run_params); 216 217 int ch; 218 while ((ch = getopt(argc, argv, GETOPT_PLUS":g:t:u:")) != -1) { 219 switch (ch) { 220 case 'g': 221 run_params.unprivileged_group = (uid_t)parse_ulong( 222 optarg, "Invalid GID", GID_MAX); 223 break; 224 225 case 't': 226 run_params.timeout_seconds = parse_ulong( 227 optarg, "Invalid timeout value", LONG_MAX); 228 break; 229 230 case 'u': 231 run_params.unprivileged_user = (uid_t)parse_ulong( 232 optarg, "Invalid UID", UID_MAX); 233 break; 234 235 case ':': 236 errx(EXIT_USAGE_ERROR, "-%c requires an argument", optopt); 237 238 case '?': 239 errx(EXIT_USAGE_ERROR, "Unknown option -%c", optopt); 240 241 default: 242 assert(false); 243 } 244 } 245 argc -= optind; 246 argv += optind; 247 reset_getopt(); 248 249 if (argc == 0) 250 errx(EXIT_USAGE_ERROR, "Must provide a command"); 251 const char* command = argv[0]; 252 253 // Keep sorted by order of likelyhood (yeah, micro-optimization). 254 if (strcmp(command, "test") == 0) 255 return test_command(argc, argv, tester, &run_params); 256 else if (strcmp(command, "list") == 0) 257 return list_command(argc, argv, tester, &run_params); 258 else 259 errx(EXIT_USAGE_ERROR, "Unknown command '%s'", command); 260 } 261