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