1 /* $NetBSD: commandline.c,v 1.8 2024/02/21 22:52:28 christos 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/print.h> 58 #include <isc/string.h> 59 #include <isc/util.h> 60 61 /*% Index into parent argv vector. */ 62 int isc_commandline_index = 1; 63 /*% Character checked for validity. */ 64 int isc_commandline_option; 65 /*% Argument associated with option. */ 66 char *isc_commandline_argument; 67 /*% For printing error messages. */ 68 char *isc_commandline_progname; 69 /*% Print error messages. */ 70 bool isc_commandline_errprint = true; 71 /*% Reset processing. */ 72 bool isc_commandline_reset = true; 73 74 static char endopt = '\0'; 75 76 #define BADOPT '?' 77 #define BADARG ':' 78 #define ENDOPT &endopt 79 80 /*! 81 * getopt -- 82 * Parse argc/argv argument vector. 83 */ 84 int 85 isc_commandline_parse(int argc, char *const *argv, const char *options) { 86 static char *place = ENDOPT; 87 const char *option; /* Index into *options of option. */ 88 89 REQUIRE(argc >= 0 && argv != NULL && options != NULL); 90 91 /* 92 * Update scanning pointer, either because a reset was requested or 93 * the previous argv was finished. 94 */ 95 if (isc_commandline_reset || *place == '\0') { 96 if (isc_commandline_reset) { 97 isc_commandline_index = 1; 98 isc_commandline_reset = false; 99 } 100 101 if (isc_commandline_progname == NULL) { 102 isc_commandline_progname = argv[0]; 103 } 104 105 if (isc_commandline_index >= argc || 106 *(place = argv[isc_commandline_index]) != '-') 107 { 108 /* 109 * Index out of range or points to non-option. 110 */ 111 place = ENDOPT; 112 return (-1); 113 } 114 115 if (place[1] != '\0' && *++place == '-' && place[1] == '\0') { 116 /* 117 * Found '--' to signal end of options. Advance 118 * index to next argv, the first non-option. 119 */ 120 isc_commandline_index++; 121 place = ENDOPT; 122 return (-1); 123 } 124 } 125 126 isc_commandline_option = *place++; 127 option = strchr(options, isc_commandline_option); 128 129 /* 130 * Ensure valid option has been passed as specified by options string. 131 * '-:' is never a valid command line option because it could not 132 * distinguish ':' from the argument specifier in the options string. 133 */ 134 if (isc_commandline_option == ':' || option == NULL) { 135 if (*place == '\0') { 136 isc_commandline_index++; 137 } 138 139 if (isc_commandline_errprint && *options != ':') { 140 fprintf(stderr, "%s: illegal option -- %c\n", 141 isc_commandline_progname, 142 isc_commandline_option); 143 } 144 145 return (BADOPT); 146 } 147 148 if (*++option != ':') { 149 /* 150 * Option does not take an argument. 151 */ 152 isc_commandline_argument = NULL; 153 154 /* 155 * Skip to next argv if at the end of the current argv. 156 */ 157 if (*place == '\0') { 158 ++isc_commandline_index; 159 } 160 } else { 161 /* 162 * Option needs an argument. 163 */ 164 if (*place != '\0') { 165 /* 166 * Option is in this argv, -D1 style. 167 */ 168 isc_commandline_argument = place; 169 } else if (argc > ++isc_commandline_index) { 170 /* 171 * Option is next argv, -D 1 style. 172 */ 173 isc_commandline_argument = argv[isc_commandline_index]; 174 } else { 175 /* 176 * Argument needed, but no more argv. 177 */ 178 place = ENDOPT; 179 180 /* 181 * Silent failure with "missing argument" return 182 * when ':' starts options string, per historical spec. 183 */ 184 if (*options == ':') { 185 return (BADARG); 186 } 187 188 if (isc_commandline_errprint) { 189 fprintf(stderr, 190 "%s: option requires an argument -- " 191 "%c\n", 192 isc_commandline_progname, 193 isc_commandline_option); 194 } 195 196 return (BADOPT); 197 } 198 199 place = ENDOPT; 200 201 /* 202 * Point to argv that follows argument. 203 */ 204 isc_commandline_index++; 205 } 206 207 return (isc_commandline_option); 208 } 209 210 isc_result_t 211 isc_commandline_strtoargv(isc_mem_t *mctx, char *s, unsigned int *argcp, 212 char ***argvp, unsigned int n) { 213 isc_result_t result; 214 215 restart: 216 /* Discard leading whitespace. */ 217 while (*s == ' ' || *s == '\t') { 218 s++; 219 } 220 221 if (*s == '\0') { 222 /* We have reached the end of the string. */ 223 *argcp = n; 224 *argvp = isc_mem_get(mctx, n * sizeof(char *)); 225 } else { 226 char *p = s; 227 while (*p != ' ' && *p != '\t' && *p != '\0' && *p != '{') { 228 if (*p == '\n') { 229 *p = ' '; 230 goto restart; 231 } 232 p++; 233 } 234 235 /* do "grouping", items between { and } are one arg */ 236 if (*p == '{') { 237 char *t = p; 238 /* 239 * shift all characters to left by 1 to get rid of '{' 240 */ 241 while (*t != '\0') { 242 t++; 243 *(t - 1) = *t; 244 } 245 while (*p != '\0' && *p != '}') { 246 p++; 247 } 248 /* get rid of '}' character */ 249 if (*p == '}') { 250 *p = '\0'; 251 p++; 252 } 253 /* normal case, no "grouping" */ 254 } else if (*p != '\0') { 255 *p++ = '\0'; 256 } 257 258 result = isc_commandline_strtoargv(mctx, p, argcp, argvp, 259 n + 1); 260 if (result != ISC_R_SUCCESS) { 261 return (result); 262 } 263 (*argvp)[n] = s; 264 } 265 266 return (ISC_R_SUCCESS); 267 } 268