1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (C) 2019 Netflix, Inc
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28#include <sys/param.h>
29#include <sys/ioccom.h>
30
31#include <ctype.h>
32#include <dirent.h>
33#include <dlfcn.h>
34#include <err.h>
35#include <fcntl.h>
36#include <getopt.h>
37#include <libutil.h>
38#include <stdbool.h>
39#include <stddef.h>
40#include <stdio.h>
41#include <stdlib.h>
42#include <string.h>
43#include <sysexits.h>
44#include <unistd.h>
45
46#include "comnd.h"
47
48static struct cmd top;
49
50static void
51print_tree(const struct cmd *f)
52{
53
54	if (f->parent != NULL)
55		print_tree(f->parent);
56	if (f->name != NULL)
57		fprintf(stderr, " %s", f->name);
58}
59
60static void
61print_usage(const struct cmd *f)
62{
63
64	fprintf(stderr, "    %s", getprogname());
65	print_tree(f->parent);
66	fprintf(stderr, " %-15s - %s\n", f->name, f->descr);
67}
68
69static void
70gen_usage(const struct cmd *t)
71{
72	struct cmd *walker;
73
74	fprintf(stderr, "usage:\n");
75	SLIST_FOREACH(walker, &t->subcmd, link) {
76		print_usage(walker);
77	}
78	exit(EX_USAGE);
79}
80
81int
82cmd_dispatch(int argc, char *argv[], const struct cmd *t)
83{
84	struct cmd *walker;
85
86	if (t == NULL)
87		t = &top;
88
89	if (argv[1] == NULL) {
90		gen_usage(t);
91		return (1);
92	}
93	SLIST_FOREACH(walker, &t->subcmd, link) {
94		if (strcmp(argv[1], walker->name) == 0) {
95			walker->fn(walker, argc-1, &argv[1]);
96			return (0);
97		}
98	}
99	fprintf(stderr, "Unknown command: %s\n", argv[1]);
100	gen_usage(t);
101	return (1);
102}
103
104static void
105arg_suffix(char *buf, size_t len, arg_type at)
106{
107	switch (at) {
108	case arg_none:
109		break;
110	case arg_string:
111		strlcat(buf, "=<STRING>", len);
112		break;
113	case arg_path:
114		strlcat(buf, "=<FILE>", len);
115		break;
116	default:
117		strlcat(buf, "=<NUM>", len);
118		break;
119	}
120}
121
122void
123arg_help(int argc __unused, char * const *argv, const struct cmd *f)
124{
125	int i;
126	char buf[31];
127	const struct opts *opts = f->opts;
128	const struct args *args = f->args;
129
130	// XXX walk up the cmd list...
131	if (argv[optind])
132		fprintf(stderr, "Unknown argument: %s\n", argv[optind]);
133	fprintf(stderr, "Usage:\n    %s", getprogname());
134	print_tree(f);
135	if (opts)
136		fprintf(stderr, " <args>");
137	if (args) {
138		while (args->descr != NULL) {
139			fprintf(stderr, " %s", args->descr);
140			args++;
141		}
142	}
143	fprintf(stderr, "\n\n%s\n", f->descr);
144	if (opts != NULL) {
145		fprintf(stderr, "Options:\n");
146		for (i = 0; opts[i].long_arg != NULL; i++) {
147			*buf = '\0';
148			if (isprint(opts[i].short_arg)) {
149				snprintf(buf, sizeof(buf), " -%c, ", opts[i].short_arg);
150			} else {
151				strlcpy(buf, "    ", sizeof(buf));
152			}
153			strlcat(buf, "--", sizeof(buf));
154			strlcat(buf, opts[i].long_arg, sizeof(buf));
155			arg_suffix(buf, sizeof(buf), opts[i].at);
156			fprintf(stderr, "%-30.30s - %s\n", buf, opts[i].descr);
157		}
158	}
159	exit(EX_USAGE);
160}
161
162static int
163find_long(struct option *lopts, int ch)
164{
165	int i;
166
167	for (i = 0; lopts[i].val != ch && lopts[i].name != NULL; i++)
168		continue;
169	return (i);
170}
171
172int
173arg_parse(int argc, char * const * argv, const struct cmd *f)
174{
175	int i, n, idx, ch;
176	uint64_t v;
177	struct option *lopts;
178	char *shortopts, *p;
179	const struct opts *opts = f->opts;
180	const struct args *args = f->args;
181
182	if (opts == NULL)
183		n = 0;
184	else
185		for (n = 0; opts[n].long_arg != NULL;)
186			n++;
187	lopts = malloc((n + 2) * sizeof(struct option));
188	if (lopts == NULL)
189		err(EX_OSERR, "option memory");
190	p = shortopts = malloc((2 * n + 3) * sizeof(char));
191	if (shortopts == NULL)
192		err(EX_OSERR, "shortopts memory");
193	idx = 0;
194	for (i = 0; i < n; i++) {
195		lopts[i].name = opts[i].long_arg;
196		lopts[i].has_arg = opts[i].at == arg_none ? no_argument : required_argument;
197		lopts[i].flag = NULL;
198		lopts[i].val = opts[i].short_arg;
199		if (isprint(opts[i].short_arg)) {
200			*p++ = opts[i].short_arg;
201			if (lopts[i].has_arg)
202				*p++ = ':';
203		}
204	}
205	lopts[n].name = "help";
206	lopts[n].has_arg = no_argument;
207	lopts[n].flag = NULL;
208	lopts[n].val = '?';
209	*p++ = '?';
210	*p++ = '\0';
211	memset(lopts + n + 1, 0, sizeof(struct option));
212	while ((ch = getopt_long(argc, argv, shortopts, lopts, &idx)) != -1) {
213		/*
214		 * If ch != 0, we've found a short option, and we have to
215		 * look it up lopts table. Otherwise idx is valid.
216		 */
217		if (ch != 0)
218			idx = find_long(lopts, ch);
219		if (idx == n)
220			arg_help(argc, argv, f);
221		switch (opts[idx].at) {
222		case arg_none:
223			*(bool *)opts[idx].ptr = true;
224			break;
225		case arg_string:
226		case arg_path:
227			*(const char **)opts[idx].ptr = optarg;
228			break;
229		case arg_uint8:
230			v = strtoul(optarg, NULL, 0);
231			if (v > 0xff)
232				goto bad_arg;
233			*(uint8_t *)opts[idx].ptr = v;
234			break;
235		case arg_uint16:
236			v = strtoul(optarg, NULL, 0);
237			if (v > 0xffff)
238				goto bad_arg;
239			*(uint16_t *)opts[idx].ptr = v;
240			break;
241		case arg_uint32:
242			v = strtoul(optarg, NULL, 0);
243			if (v > 0xffffffffu)
244				goto bad_arg;
245			*(uint32_t *)opts[idx].ptr = v;
246			break;
247		case arg_uint64:
248			v = strtoul(optarg, NULL, 0);
249			if (v > 0xffffffffffffffffull)
250				goto bad_arg;
251			*(uint64_t *)opts[idx].ptr = v;
252			break;
253		case arg_size:
254			if (expand_number(optarg, &v) < 0)
255				goto bad_arg;
256			*(uint64_t *)opts[idx].ptr = v;
257			break;
258		}
259	}
260	if (args) {
261		while (args->descr) {
262			if (optind >= argc) {
263				fprintf(stderr, "Missing arg %s\n", args->descr);
264				arg_help(argc, argv, f);
265				free(lopts);
266				free(shortopts);
267				return (1);
268			}
269			*(char **)args->ptr = argv[optind++];
270			args++;
271		}
272	}
273	free(lopts);
274	free(shortopts);
275	return (0);
276bad_arg:
277	fprintf(stderr, "Bad value to --%s: %s\n", opts[idx].long_arg, optarg);
278	free(lopts);
279	free(shortopts);
280	exit(EX_USAGE);
281}
282
283/*
284 * Loads all the .so's from the specified directory.
285 */
286void
287cmd_load_dir(const char *dir, cmd_load_cb_t cb, void *argp)
288{
289	DIR *d;
290	struct dirent *dent;
291	char *path = NULL;
292	void *h;
293
294	d = opendir(dir);
295	if (d == NULL)
296		return;
297	for (dent = readdir(d); dent != NULL; dent = readdir(d)) {
298		if (strcmp(".so", dent->d_name + dent->d_namlen - 3) != 0)
299			continue;
300		asprintf(&path, "%s/%s", dir, dent->d_name);
301		if (path == NULL)
302			err(EX_OSERR, "Can't malloc for path, giving up.");
303		if ((h = dlopen(path, RTLD_NOW | RTLD_GLOBAL)) == NULL)
304			warnx("Can't load %s: %s", path, dlerror());
305		else {
306			if (cb != NULL)
307				cb(argp, h);
308		}
309		free(path);
310		path = NULL;
311	}
312	closedir(d);
313}
314
315void
316cmd_register(struct cmd *up, struct cmd *cmd)
317{
318	struct cmd *walker, *last;
319
320	if (up == NULL)
321		up = &top;
322	SLIST_INIT(&cmd->subcmd);
323	cmd->parent = up;
324	last = NULL;
325	SLIST_FOREACH(walker, &up->subcmd, link) {
326		if (strcmp(walker->name, cmd->name) > 0)
327			break;
328		last = walker;
329	}
330	if (last == NULL) {
331		SLIST_INSERT_HEAD(&up->subcmd, cmd, link);
332	} else {
333		SLIST_INSERT_AFTER(last, cmd, link);
334	}
335}
336
337void
338cmd_init(void)
339{
340
341}
342