libthread: copy exec args into malloc'ed memory

On Solaris, the child of fork does not have access to the stacks
of other threads that existed at the time of the fork, as demonstrated
by the C program below (fails on Solaris, works everywhere else).
Work around this by making a copy of everything the child needs
in malloc'ed memory, which is inherited properly.

    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <unistd.h>
    #include <sys/wait.h>
    #include <sys/types.h>

    int *p;

    pthread_mutex_t mu = PTHREAD_MUTEX_INITIALIZER;
    pthread_cond_t ready = PTHREAD_COND_INITIALIZER;
    pthread_cond_t never = PTHREAD_COND_INITIALIZER;

    void*
    thread1(void *v)
    {
        int local = 42;
        pthread_mutex_lock(&mu);
        p = &local;
        pthread_cond_signal(&ready);
        pthread_cond_wait(&never, &mu);
        return 0;
    }

    void*
    thread2(void *v)
    {
        pthread_mutex_lock(&mu);
        while(p == 0)
            pthread_cond_wait(&ready, &mu);
        pthread_mutex_unlock(&mu);

        printf("parent: *p = %d\n", *p);

        int pid = fork();
        if (pid == 0) {
            printf("child: *p = %d\n", *p);
            exit(0);
        }
        printf("parent: pid = %d\n", pid);
        int status;
        waitpid(pid, &status, 0);
        if(WIFEXITED(status))
            printf("child exit %d\n", WEXITSTATUS(status));
        else if(WIFSIGNALED(status))
            printf("child signal %d\n", WTERMSIG(status));
        else
            printf("unexpected child status %d\n", status);
        return 0;
    }

    int
    main(void)
    {
        pthread_t t1, t2;
        pthread_create(&t1, 0, thread1, 0);
        pthread_create(&t2, 0, thread2, 0);
        void *v;
        pthread_join(t2, &v);
        return 0;
    }
This commit is contained in:
Russ Cox
2026-02-26 09:47:54 -05:00
parent cb7001c8d2
commit 6654cad3c5
2 changed files with 40 additions and 12 deletions

View File

@@ -27,17 +27,45 @@ execproc(void *v)
int int
_runthreadspawn(int *fd, char *cmd, char **argv, char *dir) _runthreadspawn(int *fd, char *cmd, char **argv, char *dir)
{ {
int pid; int i, pid, argc;
Execjob e; Execjob *e;
e.fd = fd; // Copy all args into malloc'ed memory, in case the arguments
e.cmd = cmd; // point into a thread stack. On Solaris, the stacks of other
e.argv = argv; // threads are not inherited by the forked child.
e.dir = dir; e = mallocz(sizeof *e, 1);
e.c = chancreate(sizeof(void*), 0); memmove(e->fd, fd, 3*sizeof fd[0]);
proccreate(execproc, &e, 65536); e->cmd = strdup(cmd);
pid = recvul(e.c); if(e->cmd == nil)
chanfree(e.c); sysfatal("out of memory");
argc = 0;
while(argv[argc] != nil)
argc++;
e->argv = mallocz((argc+1)*sizeof argv[0], 1);
if(e->argv == nil)
sysfatal("out of memory");
for(i=0; i<argc; i++) {
e->argv[i] = strdup(argv[i]);
if(e->argv[i] == nil)
sysfatal("out of memory");
}
if(e->dir != nil) {
e->dir = strdup(dir);
if(e->dir == nil)
sysfatal("out of memory");
}
e->c = chancreate(sizeof(void*), 0);
proccreate(execproc, e, 65536);
pid = recvul(e->c);
free(e->cmd);
for(i=0; i<argc; i++)
free(e->argv[i]);
free(e->argv);
free(e->dir);
chanfree(e->c);
free(e);
return pid; return pid;
} }
@@ -78,7 +106,7 @@ _threadspawn(int fd[3], char *cmd, char *argv[], char *dir)
return -1; return -1;
case 0: case 0:
/* can't RFNOTEG - will lose tty */ /* can't RFNOTEG - will lose tty */
if(dir != nil ) if(dir != nil)
chdir(dir); /* best effort */ chdir(dir); /* best effort */
dup2(fd[0], 0); dup2(fd[0], 0);
dup2(fd[1], 1); dup2(fd[1], 1);

View File

@@ -31,7 +31,7 @@ enum
struct Execjob struct Execjob
{ {
int *fd; int fd[3];
char *cmd; char *cmd;
char **argv; char **argv;
char *dir; char *dir;