[PATCH v19 00/11] Support translated long option names in getopt and argp

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Mon Jan 12 18:55:00 GMT 2026



On 06/12/25 10:19, Vivien Kraus wrote:
> Dear Adhemerval Zanella Netto and other glibc developers,
> 
> Thank you for your feedback.  It is difficult to find the last bits of
> bad indentation, however I have set a dir-local variable in emacs to
> use tabs as much as possible, so the indentation should be correct in
> the future.
> 
> I added a dependency on $(gen-locales) for the tests.
> 
> Le 01/12/2025 à 21:34, Adhemerval Zanella Netto a écrit :
>>> # Test for the glob symbol version that was replaced in glibc 2.27.
>>>  ifeq ($(have-GLIBC_2.26)$(build-shared),yesyes)
>>>  tests += \
>>> diff --git a/posix/getopt.c b/posix/getopt.c
>>> index 66b43850ee..b49dfb643f 100644
>>> --- a/posix/getopt.c
>>> +++ b/posix/getopt.c
>>> @@ -182,6 +182,21 @@ exchange (char **argv, struct _getopt_data *d)
>>>    d->__last_nonopt = d->optind;
>>>  }
>>>  
>>> +/* Return 1 iff a translation for opt_name has been found and it
>>> +   matches the substring from argument, length argument_length.
>>> +*/
>>> +static const int
>>> +match_translated_option_name (char *(*translate) (const char *msgid),
>>> +			      const char *argument, size_t argument_length,
>>> +			      const char *opt_name)
>>> +{
>>> +  const char *translated = opt_name;
>>> +  if (translate != NULL)
>>> +    translated = translate (opt_name);
>>> +  return (!strncmp (translated, argument, argument_length)
>>> +	  && argument_length == strlen (translated));
>>> +}
>>> +
>> I think that we can optimize it to the following:
>>
>>   if (translated[argument_length] != '\0')
>>     return 0;
>>   return memcmp (translated, argument, argument_length) == 0;
> 
> I’m not sure about that; what if getopt tries to match a very long
> argument against a shorter translation? This would index the
> translation out of bounds.  Am I missing something?

Yeah, you are correct. I was trying to avoid the need to iterate over 'translated'
twice (one for strncmp and another for strlen).  I think we can avoid the last
strlen with:
 
 const char *translated = opt_name;
 if (translate != NULL)
   translated = translate (opt_name);

 if (strncmp (translated, argument, argument_length) != 0)
   return 0;
 /* We know that strlen(translated) >= argument_length.  */
 return translated[argument_length] == '\0';

> 
>>> +	      if (strcmp (translated_option_name, pfound->name) != 0)
>>> +		{
>>> +		  /* Print both names of the option.  */
>>> +		  fprintf (stderr,
>>> +			   _("%s: option '%s%s' / '%s%s' doesn't allow an argument\n"),
>>> +			   argv[0], prefix, translated_option_name, prefix, pfound->name);
>>> +		}
>>> +	      else
>>> +		{
>>> +		  /* Either the option name is not translated, or its
>>> +		     translation is the same as the option name.  */
>>> +		  fprintf (stderr,
>>> +			   _("%s: option '%s%s' doesn't allow an argument\n"),
>>> +			   argv[0], prefix, pfound->name);
>>> +		}
>>
>> I think the usual GNU code style is to omit brackets for one-line directive.
> 
> I thought the braces were required around a block composed of a
> comment and an instruction, but seeing surrounding code, I now
> understand.

I think I saw both ways on glibc code, so I won't fret about it.

> 
>>> diff --git a/posix/tstgetoptl.c b/posix/tstgetoptl.c
>>> [...]
>>> +
>>> +/* This tests that --colour is accepted as a translation of --color.
>>> +   This echoes tstgetopt.c, where --colour was an option name alias
>>> +   for --color, so it had to be listed twice.  */
>>> +
>>> +/* This uses the en_GB locale so that colour means color.  As a
>>> +   special case, we also check that non-translated options have
>>> +   precedence over translated options, by translated "optional" as
>>> +   "required".  */
>>
>> Should we also check when a translation is required, but there is no
>> suitable option to check for the error paths?
> 
> Do you mean: if the user passes --flavour, there is a “flavor” ->
> “flavour” en_GB translation, but flavor is not an option recognized by
> the program, then getopt should detect an unrecognized option?  I
> added a test for this.

Yes.

> 
>>> diff --git a/posix/getopt1.c b/posix/getopt1.c
>>> [...]
>>> +/* FIXME: use pgettext_expr.  */
>>> +static char *
>>> +do_translate (const char *context, const char *msgid)
>>> +{
>>> +  char *full_msgid;
>>> +  const char *translated = msgid;
>>> +  int output_length = 0;
>>> +
>>> +  if (context != NULL)
>>> +    {
>>> +      output_length = __asprintf (&full_msgid, "%s\004%s", context, msgid);
>>> +      if (output_length >= 0)
>>> +	{
>>> +	  translated = __dcgettext (NULL, full_msgid, LC_MESSAGES);
>>> +	  if (strcmp (translated, full_msgid) == 0)
>>> +	    {
>>> +	      /* No translation for this context and message, so drop
>>> +		 the context + ^D prefix.  */
>>> +	      translated = msgid;
>>> +	    }
>>> +	}
>>> +      /* Otherwise, if memory allocation failed, then we won’t accept
>>> +	 translations.  translated remains an alias to msgid.  */
>>> +      free (full_msgid);
>>
>> Wouldn't this possible return a free pointer as translated? On __dcgettext,
>> it calls __dcigettext (NULL, full_msgid, NULL, 0, 0, LC_MESSAGES) and then:
>>
>>  454 char *
>>  455 DCIGETTEXT (const char *domainname, const char *msgid1, const char *msgid2,
>>  456             int plural, unsigned long int n, int category)
>>  457 #endif
>>  458 {
>>  [...]
>>  826   __set_errno (saved_errno);
>>  827   return (plural == 0
>>  828           ? (char *) msgid1
>>  829           /* Use the Germanic plural rule.  */
>>  830           : n == 1 ? (char *) msgid1 : (char *) msgid2);
>>  831 }
>>  488 #endif
>>
>> For the unstranslted case? Or am I missing something here?
> 
> It should not happen, because if __dcgettext returns the allocated
> full_msgid, then we detect it in the “No translation” case, and
> replace it with the non-allocated msgid.  In any case, it should only
> return the exact full_msgid pointer, or nothing that aliases it.  I
> agree that it depends a bit too much on the dgettext implementation,
> so this revision adds a pointer to allocated data that must be kept
> around until we are done with the translation.  Do you prefer it that
> way?
> 
I don't have a strong preference, it is just the __dcgettext is quite complex
and full of redirection that I sometime get lost in it.


More information about the Libc-alpha mailing list