| #include <u.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include "threadimpl.h" |
| |
| static void efork(int[3], int[2], char*, char**); |
| int |
| _threadexec(Channel *pidc, int fd[3], char *prog, char *args[], int freeargs) |
| { |
| int pfd[2]; |
| int n, pid; |
| char exitstr[ERRMAX]; |
| |
| _threaddebug(DBGEXEC, "threadexec %s", prog); |
| |
| /* |
| * We want threadexec to behave like exec; if exec succeeds, |
| * never return, and if it fails, return with errstr set. |
| * Unfortunately, the exec happens in another proc since |
| * we have to wait for the exec'ed process to finish. |
| * To provide the semantics, we open a pipe with the |
| * write end close-on-exec and hand it to the proc that |
| * is doing the exec. If the exec succeeds, the pipe will |
| * close so that our read below fails. If the exec fails, |
| * then the proc doing the exec sends the errstr down the |
| * pipe to us. |
| */ |
| if(pipe(pfd) < 0) |
| goto Bad; |
| if(fcntl(pfd[0], F_SETFD, 1) < 0) |
| goto Bad; |
| if(fcntl(pfd[1], F_SETFD, 1) < 0) |
| goto Bad; |
| |
| switch(pid = fork()){ |
| case -1: |
| close(pfd[0]); |
| close(pfd[1]); |
| goto Bad; |
| case 0: |
| efork(fd, pfd, prog, args); |
| _threaddebug(DBGSCHED, "exit after efork"); |
| _exit(0); |
| default: |
| if(freeargs) |
| free(args); |
| break; |
| } |
| |
| close(pfd[1]); |
| if((n = read(pfd[0], exitstr, ERRMAX-1)) > 0){ /* exec failed */ |
| exitstr[n] = '\0'; |
| errstr(exitstr, ERRMAX); |
| close(pfd[0]); |
| goto Bad; |
| } |
| close(pfd[0]); |
| close(fd[0]); |
| if(fd[1] != fd[0]) |
| close(fd[1]); |
| if(fd[2] != fd[1] && fd[2] != fd[0]) |
| close(fd[2]); |
| if(pidc) |
| sendul(pidc, pid); |
| |
| _threaddebug(DBGEXEC, "threadexec schedexecwait"); |
| return pid; |
| |
| Bad: |
| _threaddebug(DBGEXEC, "threadexec bad %r"); |
| if(pidc) |
| sendul(pidc, ~0); |
| return -1; |
| } |
| |
| void |
| threadexec(Channel *pidc, int fd[3], char *prog, char *args[]) |
| { |
| if(_kthreadexec(pidc, fd, prog, args, 0) >= 0) |
| threadexits(nil); |
| } |
| |
| int |
| threadspawn(int fd[3], char *prog, char *args[]) |
| { |
| return _kthreadexec(nil, fd, prog, args, 0); |
| } |
| |
| /* |
| * The &f+1 trick doesn't work on SunOS, so we might |
| * as well bite the bullet and do this correctly. |
| */ |
| void |
| threadexecl(Channel *pidc, int fd[3], char *f, ...) |
| { |
| char **args, *s; |
| int n; |
| va_list arg; |
| |
| va_start(arg, f); |
| for(n=0; va_arg(arg, char*) != 0; n++) |
| ; |
| n++; |
| va_end(arg); |
| |
| args = malloc(n*sizeof(args[0])); |
| if(args == nil){ |
| if(pidc) |
| sendul(pidc, ~0); |
| return; |
| } |
| |
| va_start(arg, f); |
| for(n=0; (s=va_arg(arg, char*)) != 0; n++) |
| args[n] = s; |
| args[n] = 0; |
| va_end(arg); |
| |
| if(_kthreadexec(pidc, fd, f, args, 1) >= 0) |
| threadexits(nil); |
| } |
| |
| static void |
| efork(int stdfd[3], int fd[2], char *prog, char **args) |
| { |
| char buf[ERRMAX]; |
| int i; |
| |
| _threaddebug(DBGEXEC, "_schedexec %s -- calling execv", prog); |
| dup(stdfd[0], 0); |
| dup(stdfd[1], 1); |
| dup(stdfd[2], 2); |
| for(i=3; i<40; i++) |
| if(i != fd[1]) |
| close(i); |
| rfork(RFNOTEG); |
| execvp(prog, args); |
| _threaddebug(DBGEXEC, "_schedexec failed: %r"); |
| rerrstr(buf, sizeof buf); |
| if(buf[0]=='\0') |
| strcpy(buf, "exec failed"); |
| write(fd[1], buf, strlen(buf)); |
| close(fd[1]); |
| _threaddebug(DBGSCHED, "_exits in exec-unix"); |
| _exits(buf); |
| } |
| |