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