commandline.c revision 1.8.2.1 1 /* $NetBSD: commandline.c,v 1.8.2.1 2025/08/02 05:53:51 perseant Exp $ */
2
3 /*
4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5 *
6 * SPDX-License-Identifier: MPL-2.0 AND BSD-3-Clause
7
8 * This Source Code Form is subject to the terms of the Mozilla Public
9 * License, v. 2.0. If a copy of the MPL was not distributed with this
10 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
11 */
12
13 /*
14 * Copyright (C) 1987, 1993, 1994 The Regents of the University of California.
15 *
16 * Redistribution and use in source and binary forms, with or without
17 * modification, are permitted provided that the following conditions
18 * are met:
19 * 1. Redistributions of source code must retain the above copyright
20 * notice, this list of conditions and the following disclaimer.
21 * 2. Redistributions in binary form must reproduce the above copyright
22 * notice, this list of conditions and the following disclaimer in the
23 * documentation and/or other materials provided with the distribution.
24 * 3. Neither the name of the University nor the names of its contributors
25 * may be used to endorse or promote products derived from this software
26 * without specific prior written permission.
27 *
28 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
29 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
32 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
34 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 * SUCH DAMAGE.
39 *
40 * See the COPYRIGHT file distributed with this work for additional
41 * information regarding copyright ownership.
42 */
43
44 /*! \file
45 * This file was adapted from the NetBSD project's source tree, RCS ID:
46 * NetBSD: getopt.c,v 1.15 1999/09/20 04:39:37 lukem Exp
47 *
48 * The primary change has been to rename items to the ISC namespace
49 * and format in the ISC coding style.
50 */
51
52 #include <stdbool.h>
53 #include <stdio.h>
54
55 #include <isc/commandline.h>
56 #include <isc/mem.h>
57 #include <isc/string.h>
58 #include <isc/util.h>
59
60 /*% Index into parent argv vector. */
61 int isc_commandline_index = 1;
62 /*% Character checked for validity. */
63 int isc_commandline_option;
64 /*% Argument associated with option. */
65 char *isc_commandline_argument;
66 /*% For printing error messages. */
67 char *isc_commandline_progname;
68 /*% Print error messages. */
69 bool isc_commandline_errprint = true;
70 /*% Reset processing. */
71 bool isc_commandline_reset = true;
72
73 static char endopt = '\0';
74
75 #define BADOPT '?'
76 #define BADARG ':'
77 #define ENDOPT &endopt
78
79 /*!
80 * getopt --
81 * Parse argc/argv argument vector.
82 */
83 int
84 isc_commandline_parse(int argc, char *const *argv, const char *options) {
85 static char *place = ENDOPT;
86 const char *option; /* Index into *options of option. */
87
88 REQUIRE(argc >= 0 && argv != NULL && options != NULL);
89
90 /*
91 * Update scanning pointer, either because a reset was requested or
92 * the previous argv was finished.
93 */
94 if (isc_commandline_reset || *place == '\0') {
95 if (isc_commandline_reset) {
96 isc_commandline_index = 1;
97 isc_commandline_reset = false;
98 }
99
100 if (isc_commandline_progname == NULL) {
101 isc_commandline_progname = argv[0];
102 }
103
104 if (isc_commandline_index >= argc ||
105 *(place = argv[isc_commandline_index]) != '-')
106 {
107 /*
108 * Index out of range or points to non-option.
109 */
110 place = ENDOPT;
111 return -1;
112 }
113
114 if (place[1] != '\0' && *++place == '-' && place[1] == '\0') {
115 /*
116 * Found '--' to signal end of options. Advance
117 * index to next argv, the first non-option.
118 */
119 isc_commandline_index++;
120 place = ENDOPT;
121 return -1;
122 }
123 }
124
125 isc_commandline_option = *place++;
126 option = strchr(options, isc_commandline_option);
127
128 /*
129 * Ensure valid option has been passed as specified by options string.
130 * '-:' is never a valid command line option because it could not
131 * distinguish ':' from the argument specifier in the options string.
132 */
133 if (isc_commandline_option == ':' || option == NULL) {
134 if (*place == '\0') {
135 isc_commandline_index++;
136 }
137
138 if (isc_commandline_errprint && *options != ':') {
139 fprintf(stderr, "%s: illegal option -- %c\n",
140 isc_commandline_progname,
141 isc_commandline_option);
142 }
143
144 return BADOPT;
145 }
146
147 if (*++option != ':') {
148 /*
149 * Option does not take an argument.
150 */
151 isc_commandline_argument = NULL;
152
153 /*
154 * Skip to next argv if at the end of the current argv.
155 */
156 if (*place == '\0') {
157 ++isc_commandline_index;
158 }
159 } else {
160 /*
161 * Option needs an argument.
162 */
163 if (*place != '\0') {
164 /*
165 * Option is in this argv, -D1 style.
166 */
167 isc_commandline_argument = place;
168 } else if (argc > ++isc_commandline_index) {
169 /*
170 * Option is next argv, -D 1 style.
171 */
172 isc_commandline_argument = argv[isc_commandline_index];
173 } else {
174 /*
175 * Argument needed, but no more argv.
176 */
177 place = ENDOPT;
178
179 /*
180 * Silent failure with "missing argument" return
181 * when ':' starts options string, per historical spec.
182 */
183 if (*options == ':') {
184 return BADARG;
185 }
186
187 if (isc_commandline_errprint) {
188 fprintf(stderr,
189 "%s: option requires an argument -- "
190 "%c\n",
191 isc_commandline_progname,
192 isc_commandline_option);
193 }
194
195 return BADOPT;
196 }
197
198 place = ENDOPT;
199
200 /*
201 * Point to argv that follows argument.
202 */
203 isc_commandline_index++;
204 }
205
206 return isc_commandline_option;
207 }
208
209 isc_result_t
210 isc_commandline_strtoargv(isc_mem_t *mctx, char *s, unsigned int *argcp,
211 char ***argvp, unsigned int n) {
212 isc_result_t result;
213
214 restart:
215 /* Discard leading whitespace. */
216 while (*s == ' ' || *s == '\t') {
217 s++;
218 }
219
220 if (*s == '\0') {
221 /* We have reached the end of the string. */
222 *argcp = n;
223 *argvp = isc_mem_cget(mctx, n, sizeof(char *));
224 } else {
225 char *p = s;
226 while (*p != ' ' && *p != '\t' && *p != '\0' && *p != '{') {
227 if (*p == '\n') {
228 *p = ' ';
229 goto restart;
230 }
231 p++;
232 }
233
234 /* do "grouping", items between { and } are one arg */
235 if (*p == '{') {
236 char *t = p;
237 /*
238 * shift all characters to left by 1 to get rid of '{'
239 */
240 while (*t != '\0') {
241 t++;
242 *(t - 1) = *t;
243 }
244 while (*p != '\0' && *p != '}') {
245 p++;
246 }
247 /* get rid of '}' character */
248 if (*p == '}') {
249 *p = '\0';
250 p++;
251 }
252 /* normal case, no "grouping" */
253 } else if (*p != '\0') {
254 *p++ = '\0';
255 }
256
257 result = isc_commandline_strtoargv(mctx, p, argcp, argvp,
258 n + 1);
259 if (result != ISC_R_SUCCESS) {
260 return result;
261 }
262 (*argvp)[n] = s;
263 }
264
265 return ISC_R_SUCCESS;
266 }
267