Coverage Report

Created: 2025-07-11 06:20

/src/tmux/compat/getopt_long.c
Line
Count
Source (jump to first uncovered line)
1
/* This file is obtained from OpenSSH:
2
 *   Repository: https://github.com/openssh/openssh-portable
3
 *   Commit: b5b405fee7f3e79d44e2d2971a4b6b4cc53f112e
4
 *   File: /openbsd-compat/getopt_long.c */
5
6
/*
7
 * Copyright (c) 2002 Todd C. Miller <Todd.Miller@courtesan.com>
8
 *
9
 * Permission to use, copy, modify, and distribute this software for any
10
 * purpose with or without fee is hereby granted, provided that the above
11
 * copyright notice and this permission notice appear in all copies.
12
 *
13
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20
 *
21
 * Sponsored in part by the Defense Advanced Research Projects
22
 * Agency (DARPA) and Air Force Research Laboratory, Air Force
23
 * Materiel Command, USAF, under agreement number F39502-99-1-0512.
24
 */
25
/*-
26
 * Copyright (c) 2000 The NetBSD Foundation, Inc.
27
 * All rights reserved.
28
 *
29
 * This code is derived from software contributed to The NetBSD Foundation
30
 * by Dieter Baron and Thomas Klausner.
31
 *
32
 * Redistribution and use in source and binary forms, with or without
33
 * modification, are permitted provided that the following conditions
34
 * are met:
35
 * 1. Redistributions of source code must retain the above copyright
36
 *    notice, this list of conditions and the following disclaimer.
37
 * 2. Redistributions in binary form must reproduce the above copyright
38
 *    notice, this list of conditions and the following disclaimer in the
39
 *    documentation and/or other materials provided with the distribution.
40
 *
41
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
42
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
43
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
44
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
45
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
46
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
47
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
48
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
49
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
50
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
51
 * POSSIBILITY OF SUCH DAMAGE.
52
 */
53
54
/* OPENBSD ORIGINAL: lib/libc/stdlib/getopt_long.c */
55
#include "compat.h"
56
57
/* The following macro constants are taken from getopt.h of OpenSSH:
58
 *   Repository: https://github.com/openssh/openssh-portable
59
 *   Commit: b5b405fee7f3e79d44e2d2971a4b6b4cc53f112e
60
 *   File: /openbsd-compat/getopt.h
61
 *
62
 * ---- BEGIN - Copyright notice and license of getopt.h ----
63
 * Copyright (c) 2000 The NetBSD Foundation, Inc.
64
 * All rights reserved.
65
 *
66
 * This code is derived from software contributed to The NetBSD Foundation
67
 * by Dieter Baron and Thomas Klausner.
68
 *
69
 * Redistribution and use in source and binary forms, with or without
70
 * modification, are permitted provided that the following conditions
71
 * are met:
72
 * 1. Redistributions of source code must retain the above copyright
73
 *    notice, this list of conditions and the following disclaimer.
74
 * 2. Redistributions in binary form must reproduce the above copyright
75
 *    notice, this list of conditions and the following disclaimer in the
76
 *    documentation and/or other materials provided with the distribution.
77
 *
78
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
79
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
80
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
81
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
82
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
83
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
84
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
85
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
86
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
87
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
88
 * POSSIBILITY OF SUCH DAMAGE.
89
 * ---- END ----
90
 */
91
0
#define no_argument        0
92
0
#define required_argument  1
93
0
#define optional_argument  2
94
95
#if !defined(HAVE_GETOPT) || !defined(HAVE_GETOPT_OPTRESET)
96
97
#if 0
98
#include <err.h>
99
#include <getopt.h>
100
#endif
101
#include <errno.h>
102
#include <stdlib.h>
103
#include <string.h>
104
#include <stdarg.h>
105
106
struct option {
107
  /* name of long option */
108
  const char *name;
109
  /*
110
   * one of no_argument, required_argument, and optional_argument:
111
   * whether option takes an argument
112
   */
113
  int has_arg;
114
  /* if not NULL, set *flag to val when option found */
115
  int *flag;
116
  /* if flag not NULL, value to set *flag to; else return value */
117
  int val;
118
};
119
120
int opterr = 1;   /* if error message should be printed */
121
int optind = 1;   /* index into parent argv vector */
122
int optopt = '?';   /* character checked for validity */
123
int optreset;   /* reset getopt */
124
char    *optarg;    /* argument associated with option */
125
126
0
#define PRINT_ERROR ((opterr) && (*options != ':'))
127
128
0
#define FLAG_PERMUTE  0x01  /* permute non-options to the end of argv */
129
0
#define FLAG_ALLARGS  0x02  /* treat non-options as args to option "-1" */
130
0
#define FLAG_LONGONLY 0x04  /* operate as getopt_long_only */
131
132
/* return values */
133
0
#define BADCH   (int)'?'
134
0
#define BADARG    ((*options == ':') ? (int)':' : (int)'?')
135
0
#define INORDER   (int)1
136
137
0
#define EMSG    (char *)""
138
139
static int getopt_internal(int, char * const *, const char *,
140
         const struct option *, int *, int);
141
static int parse_long_options(char * const *, const char *,
142
            const struct option *, int *, int);
143
static int gcd(int, int);
144
static void permute_args(int, int, int, char * const *);
145
146
static char *place = EMSG; /* option letter processing */
147
148
/* XXX: set optreset to 1 rather than these two */
149
static int nonopt_start = -1; /* first non option argument (for permute) */
150
static int nonopt_end = -1;   /* first option after non options (for permute) */
151
152
/* Error messages */
153
static const char recargchar[] = "option requires an argument -- %c";
154
static const char recargstring[] = "option requires an argument -- %s";
155
static const char ambig[] = "ambiguous option -- %.*s";
156
static const char noarg[] = "option doesn't take an argument -- %.*s";
157
static const char illoptchar[] = "unknown option -- %c";
158
static const char illoptstring[] = "unknown option -- %s";
159
160
/*
161
 * Compute the greatest common divisor of a and b.
162
 */
163
static int
164
gcd(int a, int b)
165
0
{
166
0
  int c;
167
168
0
  c = a % b;
169
0
  while (c != 0) {
170
0
    a = b;
171
0
    b = c;
172
0
    c = a % b;
173
0
  }
174
175
0
  return (b);
176
0
}
177
178
/*
179
 * Exchange the block from nonopt_start to nonopt_end with the block
180
 * from nonopt_end to opt_end (keeping the same order of arguments
181
 * in each block).
182
 */
183
static void
184
permute_args(int panonopt_start, int panonopt_end, int opt_end,
185
  char * const *nargv)
186
0
{
187
0
  int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos;
188
0
  char *swap;
189
190
  /*
191
   * compute lengths of blocks and number and size of cycles
192
   */
193
0
  nnonopts = panonopt_end - panonopt_start;
194
0
  nopts = opt_end - panonopt_end;
195
0
  ncycle = gcd(nnonopts, nopts);
196
0
  cyclelen = (opt_end - panonopt_start) / ncycle;
197
198
0
  for (i = 0; i < ncycle; i++) {
199
0
    cstart = panonopt_end+i;
200
0
    pos = cstart;
201
0
    for (j = 0; j < cyclelen; j++) {
202
0
      if (pos >= panonopt_end)
203
0
        pos -= nnonopts;
204
0
      else
205
0
        pos += nopts;
206
0
      swap = nargv[pos];
207
      /* LINTED const cast */
208
0
      ((char **) nargv)[pos] = nargv[cstart];
209
      /* LINTED const cast */
210
0
      ((char **)nargv)[cstart] = swap;
211
0
    }
212
0
  }
213
0
}
214
215
/*
216
 * parse_long_options --
217
 *  Parse long options in argc/argv argument vector.
218
 * Returns -1 if short_too is set and the option does not match long_options.
219
 */
220
static int
221
parse_long_options(char * const *nargv, const char *options,
222
  const struct option *long_options, int *idx, int short_too)
223
0
{
224
0
  char *current_argv, *has_equal;
225
0
  size_t current_argv_len;
226
0
  int i, match;
227
228
0
  current_argv = place;
229
0
  match = -1;
230
231
0
  optind++;
232
233
0
  if ((has_equal = strchr(current_argv, '=')) != NULL) {
234
    /* argument found (--option=arg) */
235
0
    current_argv_len = has_equal - current_argv;
236
0
    has_equal++;
237
0
  } else
238
0
    current_argv_len = strlen(current_argv);
239
240
0
  for (i = 0; long_options[i].name; i++) {
241
    /* find matching long option */
242
0
    if (strncmp(current_argv, long_options[i].name,
243
0
        current_argv_len))
244
0
      continue;
245
246
0
    if (strlen(long_options[i].name) == current_argv_len) {
247
      /* exact match */
248
0
      match = i;
249
0
      break;
250
0
    }
251
    /*
252
     * If this is a known short option, don't allow
253
     * a partial match of a single character.
254
     */
255
0
    if (short_too && current_argv_len == 1)
256
0
      continue;
257
258
0
    if (match == -1) /* partial match */
259
0
      match = i;
260
0
    else {
261
      /* ambiguous abbreviation */
262
0
      if (PRINT_ERROR)
263
0
        warnx(ambig, (int)current_argv_len,
264
0
             current_argv);
265
0
      optopt = 0;
266
0
      return (BADCH);
267
0
    }
268
0
  }
269
0
  if (match != -1) {   /* option found */
270
0
    if (long_options[match].has_arg == no_argument
271
0
        && has_equal) {
272
0
      if (PRINT_ERROR)
273
0
        warnx(noarg, (int)current_argv_len,
274
0
             current_argv);
275
      /*
276
       * XXX: GNU sets optopt to val regardless of flag
277
       */
278
0
      if (long_options[match].flag == NULL)
279
0
        optopt = long_options[match].val;
280
0
      else
281
0
        optopt = 0;
282
0
      return (BADARG);
283
0
    }
284
0
    if (long_options[match].has_arg == required_argument ||
285
0
        long_options[match].has_arg == optional_argument) {
286
0
      if (has_equal)
287
0
        optarg = has_equal;
288
0
      else if (long_options[match].has_arg ==
289
0
          required_argument) {
290
        /*
291
         * optional argument doesn't use next nargv
292
         */
293
0
        optarg = nargv[optind++];
294
0
      }
295
0
    }
296
0
    if ((long_options[match].has_arg == required_argument)
297
0
        && (optarg == NULL)) {
298
      /*
299
       * Missing argument; leading ':' indicates no error
300
       * should be generated.
301
       */
302
0
      if (PRINT_ERROR)
303
0
        warnx(recargstring,
304
0
            current_argv);
305
      /*
306
       * XXX: GNU sets optopt to val regardless of flag
307
       */
308
0
      if (long_options[match].flag == NULL)
309
0
        optopt = long_options[match].val;
310
0
      else
311
0
        optopt = 0;
312
0
      --optind;
313
0
      return (BADARG);
314
0
    }
315
0
  } else {     /* unknown option */
316
0
    if (short_too) {
317
0
      --optind;
318
0
      return (-1);
319
0
    }
320
0
    if (PRINT_ERROR)
321
0
      warnx(illoptstring, current_argv);
322
0
    optopt = 0;
323
0
    return (BADCH);
324
0
  }
325
0
  if (idx)
326
0
    *idx = match;
327
0
  if (long_options[match].flag) {
328
0
    *long_options[match].flag = long_options[match].val;
329
0
    return (0);
330
0
  } else
331
0
    return (long_options[match].val);
332
0
}
333
334
/*
335
 * getopt_internal --
336
 *  Parse argc/argv argument vector.  Called by user level routines.
337
 */
338
static int
339
getopt_internal(int nargc, char * const *nargv, const char *options,
340
  const struct option *long_options, int *idx, int flags)
341
0
{
342
0
  char *oli;        /* option letter list index */
343
0
  int optchar, short_too;
344
0
  static int posixly_correct = -1;
345
346
0
  if (options == NULL)
347
0
    return (-1);
348
349
  /*
350
   * XXX Some GNU programs (like cvs) set optind to 0 instead of
351
   * XXX using optreset.  Work around this braindamage.
352
   */
353
0
  if (optind == 0)
354
0
    optind = optreset = 1;
355
356
  /*
357
   * Disable GNU extensions if POSIXLY_CORRECT is set or options
358
   * string begins with a '+'.
359
   */
360
0
  if (posixly_correct == -1 || optreset)
361
0
    posixly_correct = (getenv("POSIXLY_CORRECT") != NULL);
362
0
  if (*options == '-')
363
0
    flags |= FLAG_ALLARGS;
364
0
  else if (posixly_correct || *options == '+')
365
0
    flags &= ~FLAG_PERMUTE;
366
0
  if (*options == '+' || *options == '-')
367
0
    options++;
368
369
0
  optarg = NULL;
370
0
  if (optreset)
371
0
    nonopt_start = nonopt_end = -1;
372
0
start:
373
0
  if (optreset || !*place) {   /* update scanning pointer */
374
0
    optreset = 0;
375
0
    if (optind >= nargc) {          /* end of argument vector */
376
0
      place = EMSG;
377
0
      if (nonopt_end != -1) {
378
        /* do permutation, if we have to */
379
0
        permute_args(nonopt_start, nonopt_end,
380
0
            optind, nargv);
381
0
        optind -= nonopt_end - nonopt_start;
382
0
      }
383
0
      else if (nonopt_start != -1) {
384
        /*
385
         * If we skipped non-options, set optind
386
         * to the first of them.
387
         */
388
0
        optind = nonopt_start;
389
0
      }
390
0
      nonopt_start = nonopt_end = -1;
391
0
      return (-1);
392
0
    }
393
0
    if (*(place = nargv[optind]) != '-' ||
394
0
        (place[1] == '\0' && strchr(options, '-') == NULL)) {
395
0
      place = EMSG;   /* found non-option */
396
0
      if (flags & FLAG_ALLARGS) {
397
        /*
398
         * GNU extension:
399
         * return non-option as argument to option 1
400
         */
401
0
        optarg = nargv[optind++];
402
0
        return (INORDER);
403
0
      }
404
0
      if (!(flags & FLAG_PERMUTE)) {
405
        /*
406
         * If no permutation wanted, stop parsing
407
         * at first non-option.
408
         */
409
0
        return (-1);
410
0
      }
411
      /* do permutation */
412
0
      if (nonopt_start == -1)
413
0
        nonopt_start = optind;
414
0
      else if (nonopt_end != -1) {
415
0
        permute_args(nonopt_start, nonopt_end,
416
0
            optind, nargv);
417
0
        nonopt_start = optind -
418
0
            (nonopt_end - nonopt_start);
419
0
        nonopt_end = -1;
420
0
      }
421
0
      optind++;
422
      /* process next argument */
423
0
      goto start;
424
0
    }
425
0
    if (nonopt_start != -1 && nonopt_end == -1)
426
0
      nonopt_end = optind;
427
428
    /*
429
     * If we have "-" do nothing, if "--" we are done.
430
     */
431
0
    if (place[1] != '\0' && *++place == '-' && place[1] == '\0') {
432
0
      optind++;
433
0
      place = EMSG;
434
      /*
435
       * We found an option (--), so if we skipped
436
       * non-options, we have to permute.
437
       */
438
0
      if (nonopt_end != -1) {
439
0
        permute_args(nonopt_start, nonopt_end,
440
0
            optind, nargv);
441
0
        optind -= nonopt_end - nonopt_start;
442
0
      }
443
0
      nonopt_start = nonopt_end = -1;
444
0
      return (-1);
445
0
    }
446
0
  }
447
448
  /*
449
   * Check long options if:
450
   *  1) we were passed some
451
   *  2) the arg is not just "-"
452
   *  3) either the arg starts with -- we are getopt_long_only()
453
   */
454
0
  if (long_options != NULL && place != nargv[optind] &&
455
0
      (*place == '-' || (flags & FLAG_LONGONLY))) {
456
0
    short_too = 0;
457
0
    if (*place == '-')
458
0
      place++;   /* --foo long option */
459
0
    else if (*place != ':' && strchr(options, *place) != NULL)
460
0
      short_too = 1;   /* could be short option too */
461
462
0
    optchar = parse_long_options(nargv, options, long_options,
463
0
        idx, short_too);
464
0
    if (optchar != -1) {
465
0
      place = EMSG;
466
0
      return (optchar);
467
0
    }
468
0
  }
469
470
0
  if ((optchar = (int)*place++) == (int)':' ||
471
0
      (optchar == (int)'-' && *place != '\0') ||
472
0
      (oli = strchr(options, optchar)) == NULL) {
473
    /*
474
     * If the user specified "-" and  '-' isn't listed in
475
     * options, return -1 (non-option) as per POSIX.
476
     * Otherwise, it is an unknown option character (or ':').
477
     */
478
0
    if (optchar == (int)'-' && *place == '\0')
479
0
      return (-1);
480
0
    if (!*place)
481
0
      ++optind;
482
0
    if (PRINT_ERROR)
483
0
      warnx(illoptchar, optchar);
484
0
    optopt = optchar;
485
0
    return (BADCH);
486
0
  }
487
0
  if (long_options != NULL && optchar == 'W' && oli[1] == ';') {
488
    /* -W long-option */
489
0
    if (*place)     /* no space */
490
0
      /* NOTHING */;
491
0
    else if (++optind >= nargc) { /* no arg */
492
0
      place = EMSG;
493
0
      if (PRINT_ERROR)
494
0
        warnx(recargchar, optchar);
495
0
      optopt = optchar;
496
0
      return (BADARG);
497
0
    } else        /* white space */
498
0
      place = nargv[optind];
499
0
    optchar = parse_long_options(nargv, options, long_options,
500
0
        idx, 0);
501
0
    place = EMSG;
502
0
    return (optchar);
503
0
  }
504
0
  if (*++oli != ':') {     /* doesn't take argument */
505
0
    if (!*place)
506
0
      ++optind;
507
0
  } else {       /* takes (optional) argument */
508
0
    optarg = NULL;
509
0
    if (*place)     /* no white space */
510
0
      optarg = place;
511
0
    else if (oli[1] != ':') { /* arg not optional */
512
0
      if (++optind >= nargc) { /* no arg */
513
0
        place = EMSG;
514
0
        if (PRINT_ERROR)
515
0
          warnx(recargchar, optchar);
516
0
        optopt = optchar;
517
0
        return (BADARG);
518
0
      } else
519
0
        optarg = nargv[optind];
520
0
    }
521
0
    place = EMSG;
522
0
    ++optind;
523
0
  }
524
  /* dump back option letter */
525
0
  return (optchar);
526
0
}
527
528
/*
529
 * getopt --
530
 *  Parse argc/argv argument vector.
531
 *
532
 * [eventually this will replace the BSD getopt]
533
 */
534
int
535
getopt(int nargc, char * const *nargv, const char *options)
536
0
{
537
538
  /*
539
   * We don't pass FLAG_PERMUTE to getopt_internal() since
540
   * the BSD getopt(3) (unlike GNU) has never done this.
541
   *
542
   * Furthermore, since many privileged programs call getopt()
543
   * before dropping privileges it makes sense to keep things
544
   * as simple (and bug-free) as possible.
545
   */
546
0
  return (getopt_internal(nargc, nargv, options, NULL, NULL, 0));
547
0
}
548
549
#if 0
550
/*
551
 * getopt_long --
552
 *  Parse argc/argv argument vector.
553
 */
554
int
555
getopt_long(int nargc, char * const *nargv, const char *options,
556
    const struct option *long_options, int *idx)
557
{
558
559
  return (getopt_internal(nargc, nargv, options, long_options, idx,
560
      FLAG_PERMUTE));
561
}
562
563
/*
564
 * getopt_long_only --
565
 *  Parse argc/argv argument vector.
566
 */
567
int
568
getopt_long_only(int nargc, char * const *nargv, const char *options,
569
    const struct option *long_options, int *idx)
570
{
571
572
  return (getopt_internal(nargc, nargv, options, long_options, idx,
573
      FLAG_PERMUTE|FLAG_LONGONLY));
574
}
575
#endif
576
577
#endif /* !defined(HAVE_GETOPT) || !defined(HAVE_OPTRESET) */