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