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>

Attachment: signature.asc
Description: PGP signature

Reply via email to