xref: /netbsd-src/external/bsd/kyua-testers/dist/cli.c (revision 754f425fc237c181450c91977727274098801c74)
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
check_error(kyua_error_t error)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
parse_ulong(const char * str,const char * message,const unsigned long max)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
reset_getopt(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
list_command(const int argc,char * const * const argv,const kyua_cli_tester_t * tester,const kyua_run_params_t * run_params)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
test_command(int argc,char * const * argv,const kyua_cli_tester_t * tester,const kyua_run_params_t * run_params)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
kyua_cli_main(int argc,char * const * argv,const kyua_cli_tester_t * tester)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