]>
Commit | Line | Data |
---|---|---|
507116e3 A |
1 | #include "internal.h" |
2 | ||
3 | #pragma mark Definitions | |
4 | #define CTL_OUTPUT_WIDTH (80) | |
5 | #define CTL_OUTPUT_OPTARG_PAD (28) | |
6 | #define CTL_OUTPUT_LIST_PAD (4) | |
7 | #define SUBCOMMAND_LINKER_SET "__subcommands" | |
8 | ||
9 | #pragma mark Module Globals | |
10 | static const os_subcommand_t _help_cmd; | |
11 | ||
12 | #pragma mark Module Routines | |
13 | static char * | |
14 | _os_subcommand_copy_optarg_usage(const os_subcommand_t *osc, | |
15 | const struct option *opt, os_subcommand_optarg_format_t format, | |
16 | os_subcommand_option_t *scopt) | |
17 | { | |
18 | char optbuff[64] = ""; | |
19 | char argbuff[64] = ""; | |
20 | char *final = NULL; | |
21 | int ret = -1; | |
22 | ||
23 | snprintf(optbuff, sizeof(optbuff), "--%s", opt->name); | |
24 | ||
25 | if (osc->osc_info) { | |
26 | osc->osc_info(osc, format, opt, scopt); | |
27 | } | |
28 | ||
29 | switch (opt->has_arg) { | |
30 | case no_argument: | |
31 | break; | |
32 | case optional_argument: | |
33 | snprintf(argbuff, sizeof(argbuff), "[=%s]", scopt->osco_argdesc); | |
34 | break; | |
35 | case required_argument: | |
36 | snprintf(argbuff, sizeof(argbuff), "=<%s>", scopt->osco_argdesc); | |
37 | break; | |
38 | default: | |
39 | __builtin_unreachable(); | |
40 | } | |
41 | ||
42 | ret = asprintf(&final, "%s%s", optbuff, argbuff); | |
43 | if (ret < 0) { | |
44 | os_assert_zero(ret); | |
45 | } | |
46 | ||
47 | return final; | |
48 | } | |
49 | ||
50 | static void | |
51 | _os_subcommand_print_optarg_usage(const os_subcommand_t *osc, | |
52 | const struct option *opt, FILE *f) | |
53 | { | |
54 | os_subcommand_option_t scopt = { | |
55 | .osco_flags = 0, | |
56 | .osco_argdesc = opt->name, | |
57 | }; | |
58 | char *__os_free usage = NULL; | |
59 | char *braces[2] = { | |
60 | "", | |
61 | "", | |
62 | }; | |
63 | ||
64 | usage = _os_subcommand_copy_optarg_usage(osc, opt, | |
65 | OS_SUBCOMMAND_OPTARG_USAGE, &scopt); | |
66 | if (scopt.osco_flags & OS_SUBCOMMAND_OPTION_FLAG_OPTIONAL) { | |
67 | braces[0] = "["; | |
68 | braces[1] = "]"; | |
69 | } | |
70 | ||
71 | fprintf(f, " %s%s%s", braces[0], usage, braces[1]); | |
72 | } | |
73 | ||
74 | static void | |
75 | _os_subcommand_print_usage(const os_subcommand_t *osc, FILE *f) | |
76 | { | |
77 | const struct option *opts = osc->osc_options; | |
78 | const struct option *curopt = NULL; | |
79 | size_t i = 0; | |
80 | ||
81 | fprintf(f, "usage: %s %s", getprogname(), osc->osc_name); | |
82 | ||
83 | while ((curopt = &opts[i]) && curopt->name) { | |
84 | _os_subcommand_print_optarg_usage(osc, curopt, f); | |
85 | i++; | |
86 | } | |
87 | ||
88 | fprintf(f, "\n"); | |
89 | } | |
90 | ||
91 | static void | |
92 | _os_subcommand_print_optarg_human(const os_subcommand_t *osc, | |
93 | const struct option *opt, FILE *f) | |
94 | { | |
95 | os_subcommand_option_t scopt = { | |
96 | .osco_flags = 0, | |
97 | .osco_argdesc = opt->name, | |
98 | }; | |
99 | char *__os_free usage = NULL; | |
100 | char *__os_free human = NULL; | |
101 | ||
102 | usage = _os_subcommand_copy_optarg_usage(osc, opt, | |
103 | OS_SUBCOMMAND_OPTARG_USAGE, &scopt); | |
104 | fprintf(f, " %-24s", usage); | |
105 | ||
106 | human = _os_subcommand_copy_optarg_usage(osc, opt, | |
107 | OS_SUBCOMMAND_OPTARG_HUMAN, &scopt); | |
108 | wfprintf_np(f, -CTL_OUTPUT_OPTARG_PAD, CTL_OUTPUT_OPTARG_PAD, | |
109 | CTL_OUTPUT_WIDTH, "%s", scopt.osco_argdesc); | |
110 | } | |
111 | ||
112 | static void | |
113 | _os_subcommand_print_human(const os_subcommand_t *osc, FILE *f) | |
114 | { | |
115 | const struct option *opts = osc->osc_options; | |
116 | const struct option *curopt = NULL; | |
117 | size_t i = 0; | |
118 | ||
119 | _os_subcommand_print_usage(osc, f); | |
120 | ||
121 | while ((curopt = &opts[i]) && curopt->name) { | |
122 | _os_subcommand_print_optarg_human(osc, curopt, f); | |
123 | i++; | |
124 | } | |
125 | } | |
126 | ||
127 | static void | |
128 | _os_subcommand_print_list(const os_subcommand_t *osc, FILE *f) | |
129 | { | |
130 | wfprintf_np(f, CTL_OUTPUT_LIST_PAD, 0, 0, "%-24s %s", | |
131 | osc->osc_name, osc->osc_desc); | |
132 | } | |
133 | ||
134 | static const os_subcommand_t * | |
135 | _os_subcommand_find(const char *name) | |
136 | { | |
137 | const os_subcommand_t **oscip = NULL; | |
138 | ||
139 | if (strcmp(_help_cmd.osc_name, name) == 0) { | |
140 | return &_help_cmd; | |
141 | } | |
142 | ||
143 | LINKER_SET_FOREACH(oscip, const os_subcommand_t **, SUBCOMMAND_LINKER_SET) { | |
144 | const os_subcommand_t *osci = *oscip; | |
145 | ||
146 | if (strcmp(osci->osc_name, name) == 0) { | |
147 | return osci; | |
148 | } | |
149 | } | |
150 | ||
151 | return NULL; | |
152 | } | |
153 | ||
154 | #pragma mark Default Usage | |
155 | static void | |
156 | _usage_default(FILE *f) | |
157 | { | |
158 | const os_subcommand_t **oscip = NULL; | |
159 | ||
160 | crfprintf_np(f, "usage: %s <subcommand> [...] | help [subcommand]", | |
161 | getprogname()); | |
162 | crfprintf_np(f, ""); | |
163 | ||
164 | crfprintf_np(f, "subcommands:"); | |
165 | LINKER_SET_FOREACH(oscip, const os_subcommand_t **, SUBCOMMAND_LINKER_SET) { | |
166 | const os_subcommand_t *osci = *oscip; | |
167 | _os_subcommand_print_list(osci, f); | |
168 | } | |
169 | ||
170 | _os_subcommand_print_list(&_help_cmd, f); | |
171 | } | |
172 | ||
173 | static int | |
174 | _usage(FILE *f) | |
175 | { | |
176 | _usage_default(f); | |
177 | return EX_USAGE; | |
178 | } | |
179 | ||
180 | #pragma mark Help Subcommand | |
181 | static int _help_invoke(const os_subcommand_t *osc, | |
182 | int argc, | |
183 | const char *argv[] | |
184 | ); | |
185 | ||
186 | static const os_subcommand_t _help_cmd = { | |
187 | .osc_version = OS_SUBCOMMAND_VERSION, | |
188 | .osc_flags = 0, | |
189 | .osc_name = "help", | |
190 | .osc_desc = "prints helpful information", | |
191 | .osc_optstring = NULL, | |
192 | .osc_options = NULL, | |
193 | .osc_info = NULL, | |
194 | .osc_invoke = &_help_invoke, | |
195 | }; | |
196 | ||
197 | static void | |
198 | _help_print_subcommand(const os_subcommand_t *osc, FILE *f) | |
199 | { | |
200 | wfprintf_np(f, 4, 4, 76, "%-16s%s", osc->osc_name, osc->osc_desc); | |
201 | } | |
202 | ||
203 | static void | |
204 | _help_print_all(FILE *f) | |
205 | { | |
206 | const os_subcommand_t **oscip = NULL; | |
207 | ||
208 | _usage_default(f); | |
209 | crfprintf_np(f, ""); | |
210 | ||
211 | crfprintf_np(f, "subcommands:"); | |
212 | LINKER_SET_FOREACH(oscip, const os_subcommand_t **, SUBCOMMAND_LINKER_SET) { | |
213 | const os_subcommand_t *osci = *oscip; | |
214 | if (osci->osc_flags & OS_SUBCOMMAND_FLAG_HIDDEN) { | |
215 | continue; | |
216 | } | |
217 | _help_print_subcommand(osci, f); | |
218 | } | |
219 | } | |
220 | ||
221 | static int | |
222 | _help_invoke(const os_subcommand_t *osc, int argc, const char *argv[]) | |
223 | { | |
224 | const os_subcommand_t *target = NULL; | |
225 | ||
226 | if (argc == 1) { | |
227 | _help_print_all(stdout); | |
228 | } else { | |
229 | target = _os_subcommand_find(argv[1]); | |
230 | if (!target) { | |
231 | crfprintf_np(stderr, "unrecognized subcommand: %s", argv[1]); | |
232 | _usage_default(stderr); | |
233 | return EX_USAGE; | |
234 | } | |
235 | ||
236 | _os_subcommand_print_human(target, stdout); | |
237 | } | |
238 | ||
239 | return 0; | |
240 | } | |
241 | ||
242 | #pragma mark API | |
243 | int | |
244 | os_subcommand_main(int argc, const char *argv[]) | |
245 | { | |
246 | int exitcode = -1; | |
247 | const char *cmdname = NULL; | |
248 | const os_subcommand_t *osci = NULL; | |
249 | ||
250 | if (argc < 2) { | |
251 | exitcode = _usage(stderr); | |
252 | goto __out; | |
253 | } | |
254 | ||
255 | // Advance argument pointer and make the subcommand argv[0]. | |
256 | argc -= 1; | |
257 | argv += 1; | |
258 | cmdname = argv[0]; | |
259 | ||
260 | osci = _os_subcommand_find(cmdname); | |
261 | if (osci) { | |
262 | if (osci->osc_flags & OS_SUBCOMMAND_FLAG_REQUIRE_ROOT) { | |
263 | if (geteuid()) { | |
264 | crfprintf_np(stderr, "subcommand requires root: %s", cmdname); | |
265 | exitcode = EX_NOPERM; | |
266 | goto __out; | |
267 | } | |
268 | } | |
269 | ||
270 | if (osci->osc_flags & OS_SUBCOMMAND_FLAG_TTYONLY) { | |
271 | if (!isatty(STDOUT_FILENO) || !isatty(STDIN_FILENO)) { | |
272 | crfprintf_np(stderr, "subcommand requires a tty: %s", cmdname); | |
273 | exitcode = EX_UNAVAILABLE; | |
274 | goto __out; | |
275 | } | |
276 | } | |
277 | ||
278 | exitcode = osci->osc_invoke(osci, argc, argv); | |
279 | if (exitcode == EX_USAGE) { | |
280 | _os_subcommand_print_usage(osci, stderr); | |
281 | } | |
282 | } else { | |
283 | crfprintf_np(stderr, "unrecognized subcommand: %s", cmdname); | |
284 | exitcode = _usage(stderr); | |
285 | } | |
286 | ||
287 | __out: | |
288 | return exitcode; | |
289 | } |