Hi Bruno, Paul,
On Wed, Jan 14, 2026 at 11:48:48AM +0100, Bruno Haible wrote:
> Paul Eggert wrote:
> > int
> > main ()
> > {
> > - char *argv[3] = { (char *) BOURNE_SHELL, (char *)
> > CHILD_PROGRAM_FILENAME, NULL };
> > + char argv0[] = BOURNE_SHELL;
> > + char argv1[] = CHILD_PROGRAM_FILENAME;
> > + char *argv[] = { argv0, argv1, NULL };
> > int ofd[2];
> > sigset_t blocked_signals;
> > sigset_t fatal_signal_set;
>
> On this one, I would argue that the original code was correct: None of the
> exec*
> and posix_spawn* functions writes into the argv[] strings. Therefore they are
> in fact 'char const **'. It's only the insufficient C language — that
> complains
> about casting a 'char **' to a 'char const **' — that prevents the exec* and
> posix_spawn* functions from having a correct prototype.
>
> Fortunately this is on the way of being fixed. Thanks to Alejandro for
> N3749 [1] !
You're welcome! :-)
However, the transition won't be easy. It will need some preprocessor
wrapping in the C library. I've written an example program to
experiment:
$ cat execve.c
#include <unistd.h>
int cexecve(const char *path, const char *const a[], const char *const
e[]);
int
main(void)
{
char * argv [] = {"foo", "bar", NULL}; //
-Wdiscarded-qualifiers
const char * cargv [] = {"foo", "bar", NULL};
char *const argvc[] = {"foo", "bar", NULL}; //
-Wdiscarded-qualifiers
const char *const cargvc[] = {"foo", "bar", NULL};
execve("foo", argv , NULL);
execve("foo", cargv , NULL); // -Wincompatible-pointer-types
execve("foo", argvc, NULL);
execve("foo", cargvc, NULL); // -Wincompatible-pointer-types
cexecve("foo", argv , NULL); // -Wincompatible-pointer-types;
fixed by n3749
cexecve("foo", cargv , NULL);
cexecve("foo", argvc, NULL); // -Wincompatible-pointer-types;
fixed by n3749
cexecve("foo", cargvc, NULL);
}
The signature of execve(2) and similar APIs would have to be changed to
take advantage of this language improvement, to use const correctly, but
that would be a breaking change (all existing code would stop
compiling). Thus, libc will need to only enable this if the compiler
supports these conversions.
I expect libc to do something like this:
#if HAVE_N3749
int execve(const char *path, const char *const a[], const char *const
e[]);
#else
int execve(const char *path, char *const a[], char *const e[]);
#endif
Which always accepts old code passing non-const vectors, and on modern
compilers, allows const-qualified vectors too. Here's a test using g++
as a "modern" C compiler:
$ cat execve2.c
#include <stddef.h>
#ifdef __cplusplus // This represents a compiler implementing n3749
int my_execve(const char *path, const char *const a[], const char
*const e[]);
#else
int my_execve(const char *path, char *const a[], char *const e[]);
#endif
int
main(void)
{
char * argv [] = {"foo", "bar", NULL}; //
-Wdiscarded-qualifiers
const char * cargv [] = {"foo", "bar", NULL};
char *const argvc[] = {"foo", "bar", NULL}; //
-Wdiscarded-qualifiers
const char *const cargvc[] = {"foo", "bar", NULL};
my_execve("foo", argv , NULL);
my_execve("foo", cargv , NULL); //
-Wincompatible-pointer-types; fixed by n3749
my_execve("foo", argvc, NULL);
my_execve("foo", cargvc, NULL); //
-Wincompatible-pointer-types; fixed by n3749
}
I would suggest using a fully qualified argv for the moment (what I
called cargvc in the experiment), and use a cast in the execve(2) call.
That will remind you that the problem is in execve(2) and not in the
variable.
Also, I wrote a const_cast() macro for doing this in shadow-utils, which
you may want to use (you'll want to call it CONST_CAST(), to avoid
breaking C++ code):
#define const_cast(T, p) _Generic(p, const T: (T) (p), default: (p))
which you could use as
execve("foo", const_cast(char *const *, argv), NULL);
Here's an experiment showing that it works both with and without N3749:
alx@devuan:~/tmp$ cat execve3.c
#include <stddef.h>
#ifdef __cplusplus // Have n3749
int my_execve(const char *path, const char *const a[], const char
*const e[]);
#else
int my_execve(const char *path, char *const a[], char *const e[]);
#endif
#ifdef __cplusplus // Have n3749
# define const_cast(T, p) p
#else
# define const_cast(T, p) _Generic(p, const T: (T) (p), default: (p))
#endif
int
main(void)
{
const char *const argv[] = {"foo", "bar", NULL};
my_execve("foo", const_cast(char *const *, argv), NULL);
}
alx@devuan:~/tmp$ gcc -Wwrite-strings -Wdiscarded-qualifiers -Wall
-Wextra -S execve3.c alx@devuan:~/tmp$ g++ -Wwrite-strings
-Wdiscarded-qualifiers -Wall -Wextra -S execve3.c cc1plus: warning:
command-line option ‘-Wdiscarded-qualifiers’ is valid for C/ObjC but not for C++
If you take some/all of this code, please include, depending on how much
you take, either of:
Suggested-by: Alejandro Colomar <[email protected]
or
Co-authored-by: Alejandro Colomar <[email protected]
Have a lovely day!
Alex
> Bruno
>
> [1] https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3749.txt
--
<https://www.alejandro-colomar.es>
signature.asc
Description: PGP signature
