get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/1.1/patches/2227405/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 2227405,
    "url": "http://patchwork.ozlabs.org/api/1.1/patches/2227405/?format=api",
    "web_url": "http://patchwork.ozlabs.org/project/glibc/patch/5e2bb861a461cf836105efab8cadf833ad3155b4.1776957778.git.vivien@planete-kraus.eu/",
    "project": {
        "id": 41,
        "url": "http://patchwork.ozlabs.org/api/1.1/projects/41/?format=api",
        "name": "GNU C Library",
        "link_name": "glibc",
        "list_id": "libc-alpha.sourceware.org",
        "list_email": "libc-alpha@sourceware.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": ""
    },
    "msgid": "<5e2bb861a461cf836105efab8cadf833ad3155b4.1776957778.git.vivien@planete-kraus.eu>",
    "date": "2026-04-23T16:04:05",
    "name": "[v22,7/9] posix: check for collisions in getopt_long",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "d2cfb059795ebb2842ae82e73e83ff52e5177efc",
    "submitter": {
        "id": 90948,
        "url": "http://patchwork.ozlabs.org/api/1.1/people/90948/?format=api",
        "name": "Vivien Kraus",
        "email": "vivien@planete-kraus.eu"
    },
    "delegate": null,
    "mbox": "http://patchwork.ozlabs.org/project/glibc/patch/5e2bb861a461cf836105efab8cadf833ad3155b4.1776957778.git.vivien@planete-kraus.eu/mbox/",
    "series": [
        {
            "id": 501215,
            "url": "http://patchwork.ozlabs.org/api/1.1/series/501215/?format=api",
            "web_url": "http://patchwork.ozlabs.org/project/glibc/list/?series=501215",
            "date": "2026-04-23T16:03:58",
            "name": "Support translated long option names in getopt and argp",
            "version": 22,
            "mbox": "http://patchwork.ozlabs.org/series/501215/mbox/"
        }
    ],
    "comments": "http://patchwork.ozlabs.org/api/patches/2227405/comments/",
    "check": "pending",
    "checks": "http://patchwork.ozlabs.org/api/patches/2227405/checks/",
    "tags": {},
    "headers": {
        "Return-Path": "<libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org>",
        "X-Original-To": [
            "incoming@patchwork.ozlabs.org",
            "libc-alpha@sourceware.org"
        ],
        "Delivered-To": [
            "patchwork-incoming@legolas.ozlabs.org",
            "libc-alpha@sourceware.org"
        ],
        "Authentication-Results": [
            "legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n secure) header.d=planete-kraus.eu header.i=@planete-kraus.eu\n header.a=rsa-sha1 header.s=albinoniA header.b=tDFvHciL;\n\tdkim-atps=neutral",
            "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=sourceware.org\n (client-ip=2620:52:6:3111::32; helo=vm01.sourceware.org;\n envelope-from=libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org;\n receiver=patchwork.ozlabs.org)",
            "sourceware.org;\n\tdkim=pass (2048-bit key,\n secure) header.d=planete-kraus.eu header.i=@planete-kraus.eu\n header.a=rsa-sha1 header.s=albinoniA header.b=tDFvHciL",
            "sourceware.org; dmarc=pass (p=reject dis=none)\n header.from=planete-kraus.eu",
            "sourceware.org;\n spf=pass smtp.mailfrom=planete-kraus.eu",
            "server2.sourceware.org;\n arc=none smtp.remote-ip=2a00:5881:4008:2810::309"
        ],
        "Received": [
            "from vm01.sourceware.org (vm01.sourceware.org\n [IPv6:2620:52:6:3111::32])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4g1gwl54yTz1y2d\n\tfor <incoming@patchwork.ozlabs.org>; Fri, 24 Apr 2026 02:07:51 +1000 (AEST)",
            "from vm01.sourceware.org (localhost [127.0.0.1])\n\tby sourceware.org (Postfix) with ESMTP id B426C4BBCDF5\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 23 Apr 2026 16:07:49 +0000 (GMT)",
            "from planete-kraus.eu (planete-kraus.eu\n [IPv6:2a00:5881:4008:2810::309])\n by sourceware.org (Postfix) with ESMTPS id 09D774B88954\n for <libc-alpha@sourceware.org>; Thu, 23 Apr 2026 16:05:54 +0000 (GMT)",
            "from planete-kraus.eu (localhost [127.0.0.1])\n by planete-kraus.eu (OpenSMTPD) with ESMTP id 5b5f4af9;\n Thu, 23 Apr 2026 16:05:42 +0000 (UTC)",
            "by planete-kraus.eu (OpenSMTPD) with ESMTPSA id 00bfe0f6\n (TLSv1.3:TLS_CHACHA20_POLY1305_SHA256:256:NO);\n Thu, 23 Apr 2026 16:05:41 +0000 (UTC)"
        ],
        "DKIM-Filter": [
            "OpenDKIM Filter v2.11.0 sourceware.org B426C4BBCDF5",
            "OpenDKIM Filter v2.11.0 sourceware.org 09D774B88954"
        ],
        "DMARC-Filter": "OpenDMARC Filter v1.4.2 sourceware.org 09D774B88954",
        "ARC-Filter": "OpenARC Filter v1.0.0 sourceware.org 09D774B88954",
        "ARC-Seal": "i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1776960355; cv=none;\n b=C4NyTFeWIVfKhk1LDcQ1hjpe3OgBJDUkMGvhq6OwbY3mimzCA18lVnuq7gMgANYBD2jDJsSJ9ysTEZpVXwr7mHeE9mvANI6BkyStEZ4Vy+HUUogr4rxbQGQSncDpwJlhaho0ENiYwIFNJSf8LMskVk40UtIplutVCYLZEts//kE=",
        "ARC-Message-Signature": "i=1; a=rsa-sha256; d=sourceware.org; s=key;\n t=1776960355; c=relaxed/simple;\n bh=0oI5C24yifBoEzFxX+gUlsNVpbB7t+YpxFbZW/iiCcs=;\n h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version;\n b=NFKljRQlHNpWibeVRfVvnbd02GIEnok2T8IbaXmer1y6lnL11u5qLnMEDIhOnKlFl3H3cdjmqS5WgJQt5T3xP28Y2e/22OZZUNR///zeCgzSBnUGm01WqU8NUpVMePqK7gz66oMs/s1jcsu/ezfieT7VzJ6Rb2dylJuTvvhNVmQ=",
        "ARC-Authentication-Results": "i=1; server2.sourceware.org",
        "DKIM-Signature": "v=1; a=rsa-sha1; c=relaxed; d=planete-kraus.eu; h=from\n :to:cc:subject:date:message-id:in-reply-to:references\n :mime-version:content-type:content-transfer-encoding; s=\n albinoniA; bh=O8Ur1hp/8m+d7jDJjICw0Pekapw=; b=tDFvHciLOS9b+rvjxH\n 3tJkSEl8LqNp4J0JyaICzzA3IcmZd3fQF3avt1FRPMtfOTzQ4oFlhqpnsw44qi4Z\n U/PMdpmtHhxjuR4Y/sasSYf2dBiDb3N+WAVHaoagEi9e/1qYxN1d6KGYsOh+AheI\n U1v8InbM4ffw93DYeoQzAN/KgjTv9OEsxiLFOP/uOcSM0VBWx0AAVDpiW3bOkevh\n 4Q2YekdGDDVD5VH9zIfi3LxBKQzfO4NYIQojHi/1QPMdN3t9qtHAIDTB0u61Iorn\n NGC4UVLrbIhrvnpje4hsFWbmWDAu7Bl1PH7FK8Hb7RNVSirvDtmg9l78NBn2njIb\n E6mg==",
        "From": "Vivien Kraus <vivien@planete-kraus.eu>",
        "To": "adhemerval.zanella@linaro.org,\n\tlibc-alpha@sourceware.org",
        "Cc": "Vivien Kraus <vivien@planete-kraus.eu>",
        "Subject": "[PATCH v22 7/9] posix: check for collisions in getopt_long",
        "Date": "Thu, 23 Apr 2026 18:04:05 +0200",
        "Message-ID": "\n <5e2bb861a461cf836105efab8cadf833ad3155b4.1776957778.git.vivien@planete-kraus.eu>",
        "X-Mailer": "git-send-email 2.52.0",
        "In-Reply-To": "<cover.1776957777.git.vivien@planete-kraus.eu>",
        "References": "<cover.1776957777.git.vivien@planete-kraus.eu>",
        "MIME-Version": "1.0",
        "Content-Type": "text/plain; charset=UTF-8",
        "Content-Transfer-Encoding": "8bit",
        "X-BeenThere": "libc-alpha@sourceware.org",
        "X-Mailman-Version": "2.1.30",
        "Precedence": "list",
        "List-Id": "Libc-alpha mailing list <libc-alpha.sourceware.org>",
        "List-Unsubscribe": "<https://sourceware.org/mailman/options/libc-alpha>,\n <mailto:libc-alpha-request@sourceware.org?subject=unsubscribe>",
        "List-Archive": "<https://sourceware.org/pipermail/libc-alpha/>",
        "List-Post": "<mailto:libc-alpha@sourceware.org>",
        "List-Help": "<mailto:libc-alpha-request@sourceware.org?subject=help>",
        "List-Subscribe": "<https://sourceware.org/mailman/listinfo/libc-alpha>,\n <mailto:libc-alpha-request@sourceware.org?subject=subscribe>",
        "Errors-To": "libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org"
    },
    "content": "There are 2 kinds of problems that can arise with translations:\n1. The developer introduces a new option name that collides with a\ntranslation of another option;\n2. The translator gave the same translation to 2 different options.\n\nBoth are bugs, as the program behavior changes while the invocation is\nthe same.  With this check, those bugs are detected at run-time, and\ngetopt_long returns '?' with optind set to 1, after printing a message\nto stderr.  The message helps the translator know what to change to\nfix the issue.\n\nWhile the checks do not do anything if translations are disabled, they\nnecessarily loop over all pairs of distinct options otherwise, so it\nmay be costly for a program with many arguments.  The proposal is to\nonly run the test during the first call to getopt_long, and never try\nagain after.  If we failed repeatedly until collisions were resolved,\nthen the common pattern to continue processing options until the\nreturn value is -1 would lead to an infinite loop.\n\nSince getopt does not have an initialization function, knowing whether\nthis is the first time we parse arguments is not trivial.  The\nsolution is this:\n- for reentrant use: we add a boolean to the state, initially set to\nfalse, and set to true once the first call returns.\n- for non-reentrant use: we add a global (but not exported) boolean,\ninitially set to false, set to false when we call\ngetopt_long_enable_translations, and set to true when we disable the\ntranslations or a non-reentrant getopt_long version returns.\n\nAnother approach would be to only check collisions when an option is\nrecognized (while processing argv), but the translator would then have\nto try all the program options to detect the collisions.\n\nAnother approach would be to only check collisions with the argp API,\nsince it has a proper initialization method, but it is useful also for\ngetopt_long users.\n---\n manual/getopt.texi                 |  11 ++-\n posix/Makefile                     |  11 +++\n posix/getopt.c                     | 104 ++++++++++++++++++++++-\n posix/getopt1.c                    |  28 +++++--\n posix/getopt_int.h                 |  10 ++-\n posix/tst-getopt_long_collision.c  | 128 +++++++++++++++++++++++++++++\n posix/tst-getopt_long_collision.po |  32 ++++++++\n posix/tstgetoptl.c                 |  11 +--\n posix/tstgetoptl.po                |   6 --\n 9 files changed, 317 insertions(+), 24 deletions(-)\n create mode 100644 posix/tst-getopt_long_collision.c\n create mode 100644 posix/tst-getopt_long_collision.po",
    "diff": "diff --git a/manual/getopt.texi b/manual/getopt.texi\nindex bd58e1b7d8..dbee16b62e 100644\n--- a/manual/getopt.texi\n+++ b/manual/getopt.texi\n@@ -23,7 +23,9 @@ use this facility, your program must include the header file\n @deftypevar int opterr\n @standards{POSIX.2, unistd.h}\n If the value of this variable is nonzero, then @code{getopt} prints an\n-error message to the standard error stream if it encounters an unknown\n+error message to the standard error stream if it detects a collision\n+between an untranslated long option name and a translation of a\n+different option, or it encounters an unknown\n option character or an option with a missing required argument.  This is\n the default behavior.  If you set this variable to zero, @code{getopt}\n does not print any messages, but it still returns the character @code{?}\n@@ -268,6 +270,13 @@ the default (@pxref{Interface to gettext, , The Interface, gettext,\n the GNU Gettext manual}).  If this is @code{NULL} (the default), the\n translation will be searched in the current text domain.\n \n+Once translations have been enabled, the next call to\n+@code{getopt_long} or @code{getopt_long_only}, and only this next\n+call, will check for collisions between long option name translations.\n+If a collision is found, a message is printed to @code{stderr}, this\n+next call will fail, returning @code{'?'}, while @code{optind} will be\n+set to 1.\n+\n @code{getopt_long_enable_translations} returns 0 on success, or -1 and\n sets errno.\n @end deftypefun\ndiff --git a/posix/Makefile b/posix/Makefile\nindex 8755f42bdc..e8d5d0661c 100644\n--- a/posix/Makefile\n+++ b/posix/Makefile\n@@ -289,6 +289,7 @@ tests := \\\n   tst-fork \\\n   tst-gai_strerror \\\n   tst-getopt_long1 \\\n+  tst-getopt_long_collision \\\n   tst-glob-bz30635 \\\n   tst-glob-tilde \\\n   tst-glob_symlinks \\\n@@ -534,6 +535,7 @@ LOCALES := \\\n   en_US.UTF-8 \\\n   es_US.ISO-8859-1 \\\n   es_US.UTF-8 \\\n+  fr_FR.UTF-8 \\\n   ja_JP.EUC-JP \\\n   tr_TR.UTF-8 \\\n   # LOCALES\n@@ -815,3 +817,12 @@ $(tstgetoptl_mo): tstgetoptl.po\n \n $(objpfx)tstgetoptl.out: $(tstgetoptl_mo) $(gen-locales)\n CFLAGS-tstgetoptl.c += -DOBJPFX=\\\"$(objpfx)\\\"\n+\n+tst_getopt_long_collision_mo = $(objpfx)domaindir/fr_FR/LC_MESSAGES/tst-getopt_long_collision.mo\n+$(tst_getopt_long_collision_mo): tst-getopt_long_collision.po\n+\t$(make-target-directory)\n+\tmsgfmt -o $@T $<\n+\tmv -f $@T $@\n+\n+$(objpfx)tst-getopt_long_collision.out: $(tst_getopt_long_collision_mo) $(gen-locales)\n+CFLAGS-tst-getopt_long_collision.c += -DOBJPFX=\\\"$(objpfx)\\\"\ndiff --git a/posix/getopt.c b/posix/getopt.c\nindex 399abeea74..2983fe6ea7 100644\n--- a/posix/getopt.c\n+++ b/posix/getopt.c\n@@ -483,11 +483,93 @@ _getopt_initialize (_GL_UNUSED int argc,\n     d->__ordering = REQUIRE_ORDER;\n   else\n     d->__ordering = PERMUTE;\n-\n   d->__initialized = 1;\n   return optstring;\n }\n \f\n+\n+static bool\n+has_translation_collisions (const char *domain,\n+\t\t\t    const char *context,\n+\t\t\t    const struct option *long_options,\n+\t\t\t    char *(*do_translate) (const char *__domain,\n+\t\t\t\t\t\t   const char *__context,\n+\t\t\t\t\t\t   const char *__name,\n+\t\t\t\t\t\t   char **__allocated),\n+\t\t\t    bool print_errors,\n+\t\t\t    const char *argv0)\n+{\n+  /* Otherwise, this is a double loop. */\n+  size_t n_options = 0;\n+  size_t option_index_a, option_index_b;\n+  char *a_buffer = NULL;\n+  const char *a_name = NULL;\n+  const struct option *option_a;\n+  char *b_buffer = NULL;\n+  const char *b_name = NULL;\n+  const struct option *option_b;\n+  bool has_collision = false;\n+\n+  if (do_translate == NULL || context == NULL)\n+    /* Translations are disabled, we can skip.  */\n+    return false;\n+  /* Count the number of options.  */\n+  for (n_options = 0; long_options[n_options].name; n_options++)\n+    ;\n+  /* Detect collisions between the non-translated name of an option\n+     and the translation of a *different* option, or the translations\n+     of two different options.  */\n+  for (option_index_a = 0;\n+       option_index_a < n_options;\n+       option_index_a++)\n+    {\n+      option_a = &(long_options[option_index_a]);\n+      a_name = do_translate (domain, context, option_a->name, &a_buffer);\n+      for (option_index_b = 0;\n+\t   option_index_b < n_options;\n+\t   option_index_b++)\n+\tif (option_index_b != option_index_a)\n+\t  {\n+\t    option_b = &(long_options[option_index_b]);\n+\t    b_name = do_translate (domain, context, option_b->name, &b_buffer);\n+\t    if (strcmp (option_a->name, b_name) == 0)\n+\t      {\n+\t\tif (print_errors)\n+\t\t  /* Since we do not consider a particular use of an\n+\t\t     option, but its general name, we do not know what\n+\t\t     prefix it has (\"--\", \"-\", or \"-W \").  */\n+\t\t  fprintf (stderr,\n+\t\t\t   _(\"%s: you found a translation bug!  \"\n+\t\t\t     \"domain '%s', context '%s': \"\n+\t\t\t     \"option *'%s'* exists \"\n+\t\t\t     \"and option '%s' translates to *'%s'*\\n\"),\n+\t\t\t   argv0,\n+\t\t\t   domain, context,\n+\t\t\t   option_a->name,\n+\t\t\t   option_b->name, b_name);\n+\t\thas_collision = true;\n+\t      }\n+\t    if (strcmp (a_name, b_name) == 0\n+\t\t&& strcmp (option_a->name, a_name) != 0\n+\t\t&& strcmp (option_b->name, b_name) != 0\n+\t\t&& option_index_a < option_index_b)\n+\t      {\n+\t\tif (print_errors)\n+\t\t  fprintf (stderr,\n+\t\t\t   _(\"%s: you found a translation bug!  \"\n+\t\t\t     \"domain '%s', context '%s': \"\n+\t\t\t     \"both '%s' and '%s' translate to '%s'\\n\"),\n+\t\t\t   argv0, domain, context,\n+\t\t\t   option_a->name, option_b->name, a_name);\n+\t\thas_collision = true;\n+\t      }\n+\t    free (b_buffer);\n+\t  }\n+      free (a_buffer);\n+    }\n+  return has_collision;\n+}\n+\n /* Scan elements of ARGV (whose length is ARGC) for option characters\n    given in OPTSTRING.\n \n@@ -563,6 +645,19 @@ _getopt_internal_r (int argc, char **argv, const char *optstring,\n   else if (optstring[0] == '-' || optstring[0] == '+')\n     optstring++;\n \n+  /* Only ever check translations for the first time we call\n+     getopt_long, since it is costly.  We cannot check them in\n+     _getopt_initialize, because gettext may not be set up yet when it\n+     is called.  */\n+  if (!d->__translation_collisions_checked)\n+    {\n+      d->__translation_collisions_checked = true;\n+      if (has_translation_collisions (d->opttextdomain, d->optctxt,\n+\t\t\t\t      longopts, translate, print_errors,\n+\t\t\t\t      argv[0]))\n+\treturn '?';\n+    }\n+\n   if (optstring[0] == ':')\n     print_errors = 0;\n \n@@ -788,7 +883,8 @@ _getopt_internal (int argc, char **argv, const char *optstring,\n \t\t  char *(*translate) (const char *, const char *,\n \t\t\t\t      const char *, char **),\n \t\t  const char *ctxt,\n-\t\t  const char *domain)\n+\t\t  const char *domain,\n+\t\t  bool translation_collisions_checked)\n {\n   int result;\n \n@@ -796,6 +892,8 @@ _getopt_internal (int argc, char **argv, const char *optstring,\n   getopt_data.opterr = opterr;\n   getopt_data.optctxt = ctxt;\n   getopt_data.opttextdomain = domain;\n+  getopt_data.__translation_collisions_checked =\n+    translation_collisions_checked;\n \n   result = _getopt_internal_r (argc, argv, optstring, longopts,\n \t\t\t       longind, long_only, &getopt_data,\n@@ -818,7 +916,7 @@ _getopt_internal (int argc, char **argv, const char *optstring,\n   {\t\t\t\t\t\t\t\t\\\n     return _getopt_internal (argc, (char **)argv, optstring,\t\\\n \t\t\t     NULL, NULL, 0, POSIXLY_CORRECT,\t\\\n-\t\t\t     NULL, NULL, NULL);\t\t\t\\\n+\t\t\t     NULL, NULL, NULL, true);\t\t\\\n   }\n \n #ifdef _LIBC\ndiff --git a/posix/getopt1.c b/posix/getopt1.c\nindex cc844a0508..06639c4b96 100644\n--- a/posix/getopt1.c\n+++ b/posix/getopt1.c\n@@ -41,6 +41,11 @@ char *optctxt = NULL;\n \n char *opttextdomain = NULL;\n \n+/* This is reset each time we call getopt_long_enable_translations,\n+   and set to true as soon as getopt_long is called.  */\n+\n+bool translation_collisions_checked = false;\n+\n /* FIXME: use pgettext_expr.  */\n static char *\n do_translate (const char *domain, const char *context, const char *msgid,\n@@ -77,9 +82,13 @@ int\n getopt_long (int argc, char *__getopt_argv_const *argv, const char *options,\n \t     const struct option *long_options, int *opt_index)\n {\n-  return _getopt_internal (argc, (char **) argv, options, long_options,\n-\t\t\t   opt_index, 0, 0, do_translate,\n-\t\t\t   optctxt, opttextdomain);\n+  int c = _getopt_internal (argc, (char **) argv, options, long_options,\n+\t\t\t    opt_index, 0, 0, do_translate,\n+\t\t\t    optctxt, opttextdomain,\n+\t\t\t    translation_collisions_checked);\n+  /* Translations are checked at most once. */\n+  translation_collisions_checked = true;\n+  return c;\n }\n \n int\n@@ -101,9 +110,12 @@ getopt_long_only (int argc, char *__getopt_argv_const *argv,\n \t\t  const char *options,\n \t\t  const struct option *long_options, int *opt_index)\n {\n-  return _getopt_internal (argc, (char **) argv, options, long_options,\n-\t\t\t   opt_index, 1, 0, do_translate,\n-\t\t\t   optctxt, opttextdomain);\n+  int c = _getopt_internal (argc, (char **) argv, options, long_options,\n+\t\t\t    opt_index, 1, 0, do_translate,\n+\t\t\t    optctxt, opttextdomain,\n+\t\t\t    translation_collisions_checked);\n+  translation_collisions_checked = true;\n+  return c;\n }\n \n int\n@@ -122,6 +134,8 @@ disable_translations (void)\n   free (opttextdomain);\n   optctxt = NULL;\n   opttextdomain = NULL;\n+  /* No translations so no possibilities for collisions.  */\n+  translation_collisions_checked = true;\n }\n \n int\n@@ -140,6 +154,8 @@ getopt_long_enable_translations (const char *msgctxt, const char *textdomain)\n \t  disable_translations ();\n \t  return -1;\n \t}\n+      /* Next call to getopt_long will check for collisions.  */\n+      translation_collisions_checked = false;\n     }\n   return 0;\n }\ndiff --git a/posix/getopt_int.h b/posix/getopt_int.h\nindex a770776dc1..2d7079e770 100644\n--- a/posix/getopt_int.h\n+++ b/posix/getopt_int.h\n@@ -21,6 +21,7 @@\n #define _GETOPT_INT_H\t1\n \n #include <getopt.h>\n+#include <stdbool.h>\n \n /* The translate argument here is optional (can be NULL), it is used\n    to avoid depending on the gettext functions in the posix getopt\n@@ -31,7 +32,8 @@ extern int _getopt_internal (int ___argc, char **___argv,\n \t\t\t     int __long_only, int __posixly_correct,\n \t\t\t     char *(*translate) (const char *, const char *,\n \t\t\t\t\t\t const char *, char **),\n-\t\t\t     const char *__optctxt, const char *__optdomain);\n+\t\t\t     const char *__optctxt, const char *__optdomain,\n+\t\t\t     bool __translation_collisions_checked);\n \n \f\n /* Reentrant versions which can handle parsing multiple argument\n@@ -100,6 +102,12 @@ struct _getopt_data\n \n   int __first_nonopt;\n   int __last_nonopt;\n+\n+  /* Checking for collision in translations of long options.  */\n+\n+  /* Checking for collisions is costly; it must compare O(n²) strings,\n+     when there are n options.  So, it is only done once.  */\n+  bool __translation_collisions_checked;\n };\n \n /* The initializer is necessary to set OPTIND and OPTERR to their\ndiff --git a/posix/tst-getopt_long_collision.c b/posix/tst-getopt_long_collision.c\nnew file mode 100644\nindex 0000000000..2e603ec9f8\n--- /dev/null\n+++ b/posix/tst-getopt_long_collision.c\n@@ -0,0 +1,128 @@\n+/* Copyright (C) 2026 Free Software Foundation, Inc.\n+   This file is part of the GNU C Library.\n+\n+   The GNU C Library is free software; you can redistribute it and/or\n+   modify it under the terms of the GNU Lesser General Public\n+   License as published by the Free Software Foundation; either\n+   version 2.1 of the License, or (at your option) any later version.\n+\n+   The GNU C Library is distributed in the hope that it will be useful,\n+   but WITHOUT ANY WARRANTY; without even the implied warranty of\n+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n+   Lesser General Public License for more details.\n+\n+   You should have received a copy of the GNU Lesser General Public\n+   License along with the GNU C Library; if not, see\n+   <https://www.gnu.org/licenses/>.  */\n+\n+#include <getopt.h>\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <string.h>\n+#include <unistd.h>\n+#include <libintl.h>\n+#include <locale.h>\n+#include <support/support.h>\n+#include <support/check.h>\n+\n+#define PN_(ctxt, str) (str)\n+\n+/* There are 2 types of collision that can happen: a translation equal\n+   to an existing option, or two different options translating to the\n+   same thing.\n+\n+   We test both kinds.  In the first test, we have this setup:\n+   foo -> bar\n+   bar -> baz\n+\n+   In the second test, we have this setup:\n+   foo -> same\n+   bar -> same\n+\n+   In the third, we don’t translate anything:\n+   foo -> foo\n+   bar -> bar\n+  */\n+\n+static const struct option options[] =\n+  {\n+    {\"foo\", no_argument, NULL, 'f'},\n+    {\"bar\", no_argument, NULL, 'b'},\n+    {\"help\", no_argument, NULL, 'h'},\n+    {NULL, 0, NULL, 0}\n+  };\n+\n+static void\n+setup_catalog (void)\n+{\n+  xsetlocale (LC_MESSAGES, \"fr_FR.UTF-8\");\n+  TEST_VERIFY_EXIT (\n+      bindtextdomain (\"tst-getopt_long_collision\", OBJPFX \"domaindir\")\n+      != NULL);\n+  TEST_VERIFY_EXIT (textdomain (\"tst-getopt_long_collision\") != NULL);\n+  /* Check that the catalog is OK: */\n+  TEST_COMPARE_STRING (dgettext (\"tst-getopt_long_collision\", \"kind 1\\004foo\"),\n+\t\t       \"bar\");\n+  TEST_COMPARE_STRING (dgettext (\"tst-getopt_long_collision\", \"kind 1\\004bar\"),\n+\t\t       \"baz\");\n+  TEST_COMPARE_STRING (dgettext (\"tst-getopt_long_collision\", \"kind 2\\004foo\"),\n+\t\t       \"same\");\n+  TEST_COMPARE_STRING (dgettext (\"tst-getopt_long_collision\", \"kind 2\\004bar\"),\n+\t\t       \"same\");\n+  TEST_COMPARE_STRING (dgettext (\"tst-getopt_long_collision\", \"kind 3\\004foo\"),\n+\t\t       \"kind 3\\004foo\");\n+  TEST_COMPARE_STRING (dgettext (\"tst-getopt_long_collision\", \"kind 3\\004bar\"),\n+\t\t       \"kind 3\\004bar\");\n+}\n+\n+static void\n+do_test (int kind, int expected, int optind_after_first_run,\n+\t int expected_second_run)\n+{\n+  /* Check test --help with one of the 3 tests.  We expect the first\n+     call to getopt_long to return expected, while setting optind to\n+     optind_after_first_run.  We call getopt_long a second time, and\n+     we expect it to return expected_second_run, while setting optind\n+     to 2.  */\n+  static const char *contexts[] = { \"kind 1\", \"kind 2\", \"kind 3\" };\n+  const char *context = contexts[kind];\n+  int c;\n+  int option_index = 0;\n+  const static char *argv[] =\n+    { (char *) \"tst-getopt_long_collision\", \"--help\", NULL };\n+  const static int argc = 2;\n+  optind = 0;\n+  TEST_VERIFY_EXIT (getopt_long_enable_translations (context, NULL) == 0);\n+  fprintf (stderr, \"Start test %d.\\n\", kind + 1);\n+  /* First pass should detect the problem immediately, even if we do\n+     not trigger the option.  */\n+  c = getopt_long (argc, (char **) argv, \"fbh\", options, &option_index);\n+  TEST_COMPARE (c, expected);\n+  TEST_COMPARE (optind, optind_after_first_run);\n+  /* The translations check is only run once. */\n+  fprintf (stderr, \"Restart test %d, we expect no problems.\\n\", kind + 1);\n+  c = getopt_long (argc, (char **) argv, \"fbh\", options, &option_index);\n+  TEST_COMPARE (c, expected_second_run);\n+  TEST_COMPARE (optind, 2);\n+}\n+\n+static int\n+do_all_tests (void)\n+{\n+  setup_catalog ();\n+  /* In failure cases, the first time we parse, we should get '?', and\n+     optind stays at 1.  The second time, we parse the first option.\n+\n+     In the normal case, the first time we parse, we should get the\n+     first option and optind jumps directly to 2.  The second time, we\n+     parsed everything.\n+  */\n+  do_test (0, '?', 1, 'h');\n+  do_test (1, '?', 1, 'h');\n+  do_test (2, 'h', 2, -1);\n+  getopt_long_disable_translations ();\n+  return 0;\n+}\n+\n+#define TEST_FUNCTION do_all_tests\n+#include <support/test-driver.c>\ndiff --git a/posix/tst-getopt_long_collision.po b/posix/tst-getopt_long_collision.po\nnew file mode 100644\nindex 0000000000..2f39001c6d\n--- /dev/null\n+++ b/posix/tst-getopt_long_collision.po\n@@ -0,0 +1,32 @@\n+# French translations for tst-getopt_long_collision.c\n+# Copyright (C) 2026 THE GNU C Library'S COPYRIGHT HOLDER\n+# This file is distributed under the same license as the GNU C Library.\n+#\n+msgid \"\"\n+msgstr \"\"\n+\"Project-Id-Version: GNU C Library (see version.h)\\n\"\n+\"Report-Msgid-Bugs-To: \\n\"\n+\"POT-Creation-Date: 2025-06-06 22:37+0200\\n\"\n+\"PO-Revision-Date: 2025-06-06 22:38+0200\\n\"\n+\"Language-Team: English (British) <(nothing)>\\n\"\n+\"Language: en_GB\\n\"\n+\"MIME-Version: 1.0\\n\"\n+\"Content-Type: text/plain; charset=ASCII\\n\"\n+\"Content-Transfer-Encoding: 8bit\\n\"\n+\"Plural-Forms: nplurals=2; plural=(n > 1);\\n\"\n+\n+msgctxt \"kind 1\"\n+msgid \"foo\"\n+msgstr \"bar\"\n+\n+msgctxt \"kind 1\"\n+msgid \"bar\"\n+msgstr \"baz\"\n+\n+msgctxt \"kind 2\"\n+msgid \"foo\"\n+msgstr \"same\"\n+\n+msgctxt \"kind 2\"\n+msgid \"bar\"\n+msgstr \"same\"\ndiff --git a/posix/tstgetoptl.c b/posix/tstgetoptl.c\nindex ad5755ddbd..bdc20b7e3d 100644\n--- a/posix/tstgetoptl.c\n+++ b/posix/tstgetoptl.c\n@@ -31,13 +31,10 @@\n    This echoes tstgetopt.c, where --colour was an option name alias\n    for --color, so it had to be listed twice.  */\n \n-/* This uses the en_GB locale so that colour means color.  As a\n-   special case, we also check that non-translated options have\n-   precedence over translated options, by translating \"optional\" as\n-   \"required\".  We also check that getopt only matches translations\n-   for actual options, by having the user pass --flavour (which is a\n-   known translation of flavor) without the program recognizing a\n-   --flavor option.  */\n+/* This uses the en_GB locale so that colour means color.  We also\n+   check that getopt only matches translations for actual options, by\n+   having the user pass --flavour (which is a known translation of\n+   flavor) without the program recognizing a --flavor option.  */\n \n #define TRANSLATION_CONTEXT \"command-line option\"\n \ndiff --git a/posix/tstgetoptl.po b/posix/tstgetoptl.po\nindex 7dc15e71f3..b1dc11c468 100644\n--- a/posix/tstgetoptl.po\n+++ b/posix/tstgetoptl.po\n@@ -24,9 +24,3 @@ msgstr \"colour\"\n msgctxt \"command-line option\"\n msgid \"flavor\"\n msgstr \"flavour\"\n-\n-# This is to make sure the translator cannot redirect options.\n-#: xxx.c:yy\n-msgctxt \"command-line option\"\n-msgid \"optional\"\n-msgstr \"required\"\n",
    "prefixes": [
        "v22",
        "7/9"
    ]
}