1 /*
2 * Copyright (c) 2011, Collabora Ltd.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * * Redistributions of source code must retain the above
9 * copyright notice, this list of conditions and the
10 * following disclaimer.
11 * * Redistributions in binary form must reproduce the
12 * above copyright notice, this list of conditions and
13 * the following disclaimer in the documentation and/or
14 * other materials provided with the distribution.
15 * * The names of contributors to this software may not be
16 * used to endorse or promote products derived from this
17 * software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
26 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
27 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
29 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
30 * DAMAGE.
31 *
32 * Author: Stef Walter <stefw@collabora.co.uk>
33 */
34
35 #include "config.h"
36
37 #include "buffer.h"
38 #include "compat.h"
39 #include "debug.h"
40 #include "message.h"
41 #include "path.h"
42
43 #include <assert.h>
44 #include <ctype.h>
45 #include <getopt.h>
46 #include <string.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <unistd.h>
50
51 #ifdef HAVE_LOCALE_H
52 #include <locale.h>
53 #endif
54
55 #ifdef ENABLE_NLS
56 #include <libintl.h>
57 #define _(x) dgettext(PACKAGE_NAME, x)
58 #else
59 #define _(x) (x)
60 #endif
61
62 #include "tool.h"
63
64 static char
short_option(int opt)65 short_option (int opt)
66 {
67 if (isalpha (opt) || isdigit (opt))
68 return (char)opt;
69 return 0;
70 }
71
72 static const struct option *
find_option(const struct option * longopts,int opt)73 find_option (const struct option *longopts,
74 int opt)
75 {
76 int i;
77
78 for (i = 0; longopts[i].name != NULL; i++) {
79 if (longopts[i].val == opt)
80 return longopts + i;
81 }
82
83 return NULL;
84 }
85
86 void
p11_tool_usage(const p11_tool_desc * usages,const struct option * longopts)87 p11_tool_usage (const p11_tool_desc *usages,
88 const struct option *longopts)
89 {
90 const struct option *longopt;
91 const int indent = 22;
92 const char *long_name;
93 const char *description;
94 const char *next;
95 char short_name;
96 int spaces;
97 int len;
98 int i;
99
100 for (i = 0; usages[i].text != NULL; i++) {
101
102 /* If no option, then this is a heading */
103 if (!usages[i].option) {
104 printf ("%s\n\n", usages[i].text);
105 continue;
106 }
107
108 longopt = find_option (longopts, usages[i].option);
109 long_name = longopt ? longopt->name : NULL;
110 short_name = short_option (usages[i].option);
111 description = usages[i].text;
112
113 if (short_name && long_name)
114 len = printf (" -%c, --%s", (int)short_name, long_name);
115 else if (long_name)
116 len = printf (" --%s", long_name);
117 else
118 len = printf (" -%c", (int)short_name);
119 if (longopt && longopt->has_arg)
120 len += printf ("%s<%s>",
121 long_name ? "=" : " ",
122 usages[i].arg ? usages[i].arg : "...");
123 if (len < indent) {
124 spaces = indent - len;
125 } else {
126 printf ("\n");
127 spaces = indent;
128 }
129 while (description) {
130 while (spaces-- > 0)
131 fputc (' ', stdout);
132 next = strchr (description, '\n');
133 if (next) {
134 next += 1;
135 printf ("%.*s", (int)(next - description), description);
136 description = next;
137 spaces = indent;
138 } else {
139 printf ("%s\n", description);
140 break;
141 }
142 }
143
144 }
145 }
146
147 int
p11_tool_getopt(int argc,char * argv[],const struct option * longopts)148 p11_tool_getopt (int argc,
149 char *argv[],
150 const struct option *longopts)
151 {
152 p11_buffer buf;
153 int ret;
154 char opt;
155 int i;
156
157 if (!p11_buffer_init_null (&buf, 64))
158 return_val_if_reached (-1);
159
160 for (i = 0; longopts[i].name != NULL; i++) {
161 opt = short_option (longopts[i].val);
162 if (opt != 0) {
163 p11_buffer_add (&buf, &opt, 1);
164 assert (longopts[i].has_arg != optional_argument);
165 if (longopts[i].has_arg == required_argument)
166 p11_buffer_add (&buf, ":", 1);
167 }
168 }
169
170 ret = getopt_long (argc, argv, buf.data, longopts, NULL);
171
172 p11_buffer_uninit (&buf);
173
174 return ret;
175 }
176
177 static void
command_usage(const p11_tool_command * commands)178 command_usage (const p11_tool_command *commands)
179 {
180 const char *progname;
181 int i;
182
183 progname = getprogname ();
184 printf (_("usage: %s command <args>...\n"), progname);
185 printf (_("\nCommon %s commands are:\n"), progname);
186 for (i = 0; commands[i].name != NULL; i++) {
187 if (strcmp (commands[i].name, P11_TOOL_FALLBACK) != 0)
188 #ifdef ENABLE_NLS
189 printf (" %-15s %s\n",
190 commands[i].name,
191 dgettext (PACKAGE_NAME, commands[i].text));
192 #else
193 printf (" %-15s %s\n",
194 commands[i].name,
195 commands[i].text);
196 #endif
197 }
198 printf (_("\nSee '%s <command> --help' for more information\n"), progname);
199 }
200
201 static void
verbose_arg(void)202 verbose_arg (void)
203 {
204 setenv ("P11_KIT_DEBUG", "tool", 0);
205 p11_message_loud ();
206 }
207
208 static void
quiet_arg(void)209 quiet_arg (void)
210 {
211 setenv ("P11_KIT_DEBUG", "", 1);
212 p11_message_quiet ();
213 }
214
215 int
p11_tool_main(int argc,char * argv[],const p11_tool_command * commands)216 p11_tool_main (int argc,
217 char *argv[],
218 const p11_tool_command *commands)
219 {
220 const p11_tool_command *fallback = NULL;
221 char *command = NULL;
222 bool want_help = false;
223 bool skip;
224 int in, out;
225 int i;
226
227 #ifdef HAVE_LOCALE_H
228 setlocale (LC_ALL, "");
229 #endif
230
231 #ifdef ENABLE_NLS
232 textdomain (PACKAGE_NAME);
233 #endif
234
235 /* Print messages by default. */
236 p11_message_loud ();
237
238 /*
239 * Parse the global options. We rearrange the options as
240 * necessary, in order to pass relevant options through
241 * to the commands, but also have them take effect globally.
242 */
243
244 for (in = 1, out = 1; in < argc; in++, out++) {
245
246 /* The non-option is the command, take it out of the arguments */
247 if (argv[in][0] != '-') {
248 if (!command) {
249 skip = true;
250 command = argv[in];
251 } else {
252 skip = false;
253 }
254
255 /* The global long options */
256 } else if (argv[in][1] == '-') {
257 skip = false;
258
259 if (strcmp (argv[in], "--") == 0) {
260 if (!command) {
261 p11_message (_("no command specified"));
262 return 2;
263 } else {
264 break;
265 }
266
267 } else if (strcmp (argv[in], "--verbose") == 0) {
268 verbose_arg ();
269
270 } else if (strcmp (argv[in], "--quiet") == 0) {
271 quiet_arg ();
272
273 } else if (strcmp (argv[in], "--help") == 0) {
274 want_help = true;
275
276 } else if (!command) {
277 p11_message (_("unknown global option: %s"), argv[in]);
278 return 2;
279 }
280
281 /* The global short options */
282 } else {
283 skip = false;
284
285 for (i = 1; argv[in][i] != '\0'; i++) {
286 switch (argv[in][i]) {
287 case 'h':
288 want_help = true;
289 break;
290
291 /* Compatibility option */
292 case 'l':
293 command = "list-modules";
294 break;
295
296 case 'v':
297 verbose_arg ();
298 break;
299
300 case 'q':
301 quiet_arg ();
302 break;
303
304 default:
305 if (!command) {
306 p11_message (_("unknown global option: -%c"), (int)argv[in][i]);
307 return 2;
308 }
309 break;
310 }
311 }
312 }
313
314 /* Skipping this argument? */
315 if (skip)
316 out--;
317 else
318 argv[out] = argv[in];
319 }
320
321 /* Initialize tool's debugging after setting env vars above */
322 p11_debug_init ();
323
324 if (command == NULL) {
325 /* As a special favor if someone just typed the command, help them out */
326 if (argc == 1) {
327 command_usage (commands);
328 return 2;
329 } else if (want_help) {
330 command_usage (commands);
331 return 0;
332 } else {
333 p11_message (_("no command specified"));
334 return 2;
335 }
336 }
337
338 argc = out;
339
340 /* Look for the command */
341 for (i = 0; commands[i].name != NULL; i++) {
342 if (strcmp (commands[i].name, P11_TOOL_FALLBACK) == 0) {
343 fallback = commands + i;
344
345 } else if (strcmp (commands[i].name, command) == 0) {
346 argv[0] = command;
347 return (commands[i].function) (argc, argv);
348 }
349 }
350
351 /* Got here because no command matched */
352 if (fallback != NULL) {
353 argv[0] = command;
354 return (fallback->function) (argc, argv);
355 }
356
357 /* At this point we have no command */
358 p11_message (_("'%s' is not a valid command. See '%s --help'"),
359 command, getprogname ());
360 return 2;
361 }
362