diff --git a/src/include/port/pg_getopt_ctx.h b/src/include/port/pg_getopt_ctx.h new file mode 100644 index 00000000000..39c3f113715 --- /dev/null +++ b/src/include/port/pg_getopt_ctx.h @@ -0,0 +1,39 @@ +/* + * Re-entrant version of the standard getopt(3) function. + * + * Portions Copyright (c) 2026, PostgreSQL Global Development Group + * + * src/include/port/pg_getopt_ctx.h + */ +#ifndef PG_GETOPT_CTX_H +#define PG_GETOPT_CTX_H + +typedef struct +{ + int nargc; + char *const *nargv; + const char *ostr; + + /* + * Caller can modify 'opterr' between pg_getopt_start() and the first call + * to pg_getopt_next(). Equivalent to the global variable of the same + * name in standard getopt(3). + */ + int opterr; + + /* + * Output variables set by pg_getopt_next(). These are equivalent to the + * global variables with same names in standard getopt(3). + */ + char *optarg; + int optind; + int optopt; + + /* internal state */ + char *place; +} pg_getopt_ctx; + +extern void pg_getopt_start(pg_getopt_ctx *ctx, int nargc, char *const *nargv, const char *ostr); +extern int pg_getopt_next(pg_getopt_ctx *ctx); + +#endif /* PG_GETOPT_CTX_H */ diff --git a/src/port/Makefile b/src/port/Makefile index 47cfea1507d..7e9b5877652 100644 --- a/src/port/Makefile +++ b/src/port/Makefile @@ -45,6 +45,7 @@ OBJS = \ path.o \ pg_bitutils.o \ pg_cpu_x86.o \ + pg_getopt_ctx.o \ pg_localeconv_r.o \ pg_numa.o \ pg_popcount_aarch64.o \ diff --git a/src/port/getopt.c b/src/port/getopt.c index 2cca5a0673a..2b9f957abc0 100644 --- a/src/port/getopt.c +++ b/src/port/getopt.c @@ -32,11 +32,7 @@ #include "c.h" #include "pg_getopt.h" - -#if defined(LIBC_SCCS) && !defined(lint) -static char sccsid[] = "@(#)getopt.c 8.3 (Berkeley) 4/27/95"; -#endif /* LIBC_SCCS and not lint */ - +#include "port/pg_getopt_ctx.h" /* * On OpenBSD and some versions of Solaris, opterr and friends are defined in @@ -54,84 +50,39 @@ char *optarg; /* argument associated with option */ #endif -#define BADCH (int)'?' -#define BADARG (int)':' -#define EMSG "" - /* * getopt * Parse argc/argv argument vector. * + * We use the re-entrant pg_getopt_start/next() functions under the hood, but + * expose the standard non re-entrant API. + * * This implementation does not use optreset. Instead, we guarantee that * it can be restarted on a new argv array after a previous call returned -1, * if the caller resets optind to 1 before the first call of the new series. - * (Internally, this means we must be sure to reset "place" to EMSG before + * (Internally, this means we must be sure to reset "active" before * returning -1.) */ int getopt(int nargc, char *const *nargv, const char *ostr) { - static char *place = EMSG; /* option letter processing */ - const char *oli; /* option letter list index */ + static bool active = false; + static pg_getopt_ctx ctx; + int result; - if (!*place) - { /* update scanning pointer */ - if (optind >= nargc || *(place = nargv[optind]) != '-') - { - place = EMSG; - return -1; - } - if (place[1] && *++place == '-' && place[1] == '\0') - { /* found "--" */ - ++optind; - place = EMSG; - return -1; - } - } /* option letter okay? */ - if ((optopt = (int) *place++) == (int) ':' || - !(oli = strchr(ostr, optopt))) + if (!active) { - /* - * if the user didn't specify '-' as an option, assume it means -1. - */ - if (optopt == (int) '-') - { - place = EMSG; - return -1; - } - if (!*place) - ++optind; - if (opterr && *ostr != ':') - (void) fprintf(stderr, - "illegal option -- %c\n", optopt); - return BADCH; + pg_getopt_start(&ctx, nargc, nargv, ostr); + ctx.opterr = opterr; + active = true; } - if (*++oli != ':') - { /* don't need argument */ - optarg = NULL; - if (!*place) - ++optind; - } - else - { /* need an argument */ - if (*place) /* no white space */ - optarg = place; - else if (nargc <= ++optind) - { /* no arg */ - place = EMSG; - if (*ostr == ':') - return BADARG; - if (opterr) - (void) fprintf(stderr, - "option requires an argument -- %c\n", - optopt); - return BADCH; - } - else - /* white space */ - optarg = nargv[optind]; - place = EMSG; - ++optind; - } - return optopt; /* dump back option letter */ + + result = pg_getopt_next(&ctx); + opterr = ctx.opterr; + optind = ctx.optind; + optopt = ctx.optopt; + optarg = ctx.optarg; + if (result == -1) + active = false; + return result; } diff --git a/src/port/meson.build b/src/port/meson.build index 7296f8e3c03..d55cb0424f3 100644 --- a/src/port/meson.build +++ b/src/port/meson.build @@ -8,6 +8,7 @@ pgport_sources = [ 'path.c', 'pg_bitutils.c', 'pg_cpu_x86.c', + 'pg_getopt_ctx.c', 'pg_localeconv_r.c', 'pg_numa.c', 'pg_popcount_aarch64.c', diff --git a/src/port/pg_getopt_ctx.c b/src/port/pg_getopt_ctx.c new file mode 100644 index 00000000000..7a6d2fd6e92 --- /dev/null +++ b/src/port/pg_getopt_ctx.c @@ -0,0 +1,136 @@ +/*------------------------------------------------------------------------- + * + * pg_getopt_ctx.c + * Thread-safe implementation of getopt() + * + * Copyright (c) 1987, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * + * IDENTIFICATION + * src/port/pg_getopt_ctx.c + * + *------------------------------------------------------------------------- + */ + +#include "c.h" + +#include "port/pg_getopt_ctx.h" + +#define BADCH (int)'?' +#define BADARG (int)':' +#define EMSG "" + +/* + * Start parsing argc/argv argument vector. + * + * This is a re-entrant version of the standard library getopt(3) function. + * To use, first call pg_getopt_start() to initialize the state, and then call + * pg_getopt_next() until it returns -1. + */ +void +pg_getopt_start(pg_getopt_ctx *ctx, int nargc, char *const *nargv, const char *ostr) +{ + ctx->nargc = nargc; + ctx->nargv = nargv; + ctx->ostr = ostr; + + ctx->optind = 1; + ctx->optarg = NULL; + ctx->opterr = 1; /* Caller may clear this after the call */ + ctx->optopt = 0; + + ctx->place = EMSG; /* option letter processing */ +} + +/* + * Parse next option in argc/argv argument vector + */ +int +pg_getopt_next(pg_getopt_ctx *ctx) +{ + const char *oli; /* option letter list index */ + + if (!*ctx->place) + { /* update scanning pointer */ + if (ctx->optind >= ctx->nargc || *(ctx->place = ctx->nargv[ctx->optind]) != '-') + { + ctx->place = EMSG; + return -1; + } + if (ctx->place[1] && *++ctx->place == '-' && ctx->place[1] == '\0') + { /* found "--" */ + ++ctx->optind; + ctx->place = EMSG; + return -1; + } + } /* option letter okay? */ + if ((ctx->optopt = (int) *ctx->place++) == (int) ':' || + !(oli = strchr(ctx->ostr, ctx->optopt))) + { + /* + * if the user didn't specify '-' as an option, assume it means -1. + */ + if (ctx->optopt == (int) '-') + { + ctx->place = EMSG; + return -1; + } + if (!*ctx->place) + ++ctx->optind; + if (ctx->opterr && *ctx->ostr != ':') + (void) fprintf(stderr, + "illegal option -- %c\n", ctx->optopt); + return BADCH; + } + if (*++oli != ':') + { /* don't need argument */ + ctx->optarg = NULL; + if (!*ctx->place) + ++ctx->optind; + } + else + { /* need an argument */ + if (*ctx->place) /* no white space */ + ctx->optarg = ctx->place; + else if (ctx->nargc <= ++ctx->optind) + { /* no arg */ + ctx->place = EMSG; + if (*ctx->ostr == ':') + return BADARG; + if (ctx->opterr) + (void) fprintf(stderr, + "option requires an argument -- %c\n", + ctx->optopt); + return BADCH; + } + else + /* white space */ + ctx->optarg = ctx->nargv[ctx->optind]; + ctx->place = EMSG; + ++ctx->optind; + } + return ctx->optopt; /* dump back option letter */ +} diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index e3c1007abdf..801ab8094ed 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3980,6 +3980,7 @@ pg_enc2name pg_encname pg_fe_sasl_mech pg_funcptr_t +pg_getopt_ctx pg_gssinfo pg_hmac_ctx pg_hmac_errno