#include "u.h"#include "tos.h"#include "../port/lib.h"#include "mem.h"#include "dat.h"#include "fns.h"#include "../port/error.h" #include "../port/edf.h"#include <a.out.h> voidsysr1(Ar0* ar0, va_list list){ USED(list); ar0->i = 0;} voidsysrfork(Ar0* ar0, va_list list){ Proc *p; int flag, i, n, pid; Fgrp *ofg; Pgrp *opg; Rgrp *org; Egrp *oeg; Mach *wm; /* * int rfork(int); */ flag = va_arg(list, int); /* Check flags before we commit */ if((flag & (RFFDG|RFCFDG)) == (RFFDG|RFCFDG)) error(Ebadarg); if((flag & (RFNAMEG|RFCNAMEG)) == (RFNAMEG|RFCNAMEG)) error(Ebadarg); if((flag & (RFENVG|RFCENVG)) == (RFENVG|RFCENVG)) error(Ebadarg); if((flag&RFPROC) == 0) { if(flag & (RFMEM|RFNOWAIT)) error(Ebadarg); if(flag & (RFFDG|RFCFDG)) { ofg = up->fgrp; if(flag & RFFDG) up->fgrp = dupfgrp(ofg); else up->fgrp = dupfgrp(nil); closefgrp(ofg); } if(flag & (RFNAMEG|RFCNAMEG)) { opg = up->pgrp; up->pgrp = newpgrp(); if(flag & RFNAMEG) pgrpcpy(up->pgrp, opg); /* inherit noattach */ up->pgrp->noattach = opg->noattach; closepgrp(opg); } if(flag & RFNOMNT) up->pgrp->noattach = 1; if(flag & RFREND) { org = up->rgrp; up->rgrp = newrgrp(); closergrp(org); } if(flag & (RFENVG|RFCENVG)) { oeg = up->egrp; up->egrp = smalloc(sizeof(Egrp)); up->egrp->ref = 1; if(flag & RFENVG) envcpy(up->egrp, oeg); closeegrp(oeg); } if(flag & RFNOTEG) up->noteid = incref(¬eidalloc); ar0->i = 0; return; } p = newproc(); p->trace = up->trace; p->scallnr = up->scallnr; memmove(p->arg, up->arg, sizeof(up->arg)); p->nerrlab = 0; p->slash = up->slash; p->dot = up->dot; incref(p->dot); memmove(p->note, up->note, sizeof(p->note)); p->privatemem = up->privatemem; p->noswap = up->noswap; p->nnote = up->nnote; p->notified = 0; p->lastnote = up->lastnote; p->notify = up->notify; p->ureg = up->ureg; p->dbgreg = 0; /* Make a new set of memory segments */ n = flag & RFMEM; qlock(&p->seglock); if(waserror()){ qunlock(&p->seglock); nexterror(); } for(i = 0; i < NSEG; i++) if(up->seg[i]) p->seg[i] = dupseg(up->seg, i, n); qunlock(&p->seglock); poperror(); /* File descriptors */ if(flag & (RFFDG|RFCFDG)) { if(flag & RFFDG) p->fgrp = dupfgrp(up->fgrp); else p->fgrp = dupfgrp(nil); } else { p->fgrp = up->fgrp; incref(p->fgrp); } /* Process groups */ if(flag & (RFNAMEG|RFCNAMEG)) { p->pgrp = newpgrp(); if(flag & RFNAMEG) pgrpcpy(p->pgrp, up->pgrp); /* inherit noattach */ p->pgrp->noattach = up->pgrp->noattach; } else { p->pgrp = up->pgrp; incref(p->pgrp); } if(flag & RFNOMNT) up->pgrp->noattach = 1; if(flag & RFREND) p->rgrp = newrgrp(); else { incref(up->rgrp); p->rgrp = up->rgrp; } /* Environment group */ if(flag & (RFENVG|RFCENVG)) { p->egrp = smalloc(sizeof(Egrp)); p->egrp->ref = 1; if(flag & RFENVG) envcpy(p->egrp, up->egrp); } else { p->egrp = up->egrp; incref(p->egrp); } p->hang = up->hang; p->procmode = up->procmode; /* Craft a return frame which will cause the child to pop out of * the scheduler in user mode with the return register zero */ sysrforkchild(p, up); p->parent = up; p->parentpid = up->pid; if(flag&RFNOWAIT) p->parentpid = 0; else { lock(&up->exl); up->nchild++; unlock(&up->exl); } if((flag&RFNOTEG) == 0) p->noteid = up->noteid; pid = p->pid; memset(p->time, 0, sizeof(p->time)); p->time[TReal] = MACHP(0)->ticks; kstrdup(&p->text, up->text); kstrdup(&p->user, up->user); /* * since the bss/data segments are now shareable, * any mmu info about this process is now stale * (i.e. has bad properties) and has to be discarded. */ mmuflush(); p->basepri = up->basepri; p->priority = up->basepri; p->fixedpri = up->fixedpri; p->mp = up->mp; wm = up->wired; if(wm) procwired(p, wm->machno); ready(p); sched(); ar0->i = pid;} static uvlongvl2be(uvlong v){ uchar *p; p = (uchar*)&v; return ((uvlong)((p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3])<<32) |((uvlong)(p[4]<<24)|(p[5]<<16)|(p[6]<<8)|p[7]);} static ulongl2be(long l){ uchar *cp; cp = (uchar*)&l; return (cp[0]<<24) | (cp[1]<<16) | (cp[2]<<8) | cp[3];} typedef struct { Exec; uvlong hdr[1];} Hdr; voidsysexec(Ar0* ar0, va_list list){ Hdr hdr; Fgrp *f; Tos *tos; Chan *chan; Image *img; Segment *s; int argc, i, n; char *a, **argv, *elem, *file, *p; char line[sizeof(Exec)], *progarg[sizeof(Exec)/2+1]; long hdrsz, magic, textsz, datasz, bsssz; uintptr textlim, datalim, bsslim, entry, stack; /* * void* exec(char* name, char* argv[]); */ /* * Open the file, remembering the final element and the full name. */ file = nil; elem = nil; chan = nil; if(waserror()){ if(file) free(file); if(elem) free(elem); if(chan) cclose(chan); nexterror(); } p = va_arg(list, char*); p = validaddr(p, 1, 0); file = validnamedup(p, 1); chan = namec(file, Aopen, OEXEC, 0); kstrdup(&elem, up->genbuf); /* * Read the header. * If it's a #!, fill in progarg[] with info then read a new header * from the file indicated by the #!. * The #! line must be less than sizeof(Exec) in size, * including the terminating \n. */ hdrsz = chan->dev->read(chan, &hdr, sizeof(Hdr), 0); if(hdrsz < 2) error(Ebadexec); p = (char*)&hdr; argc = 0; if(p[0] == '#' && p[1] == '!'){ p = memccpy(line, (char*)&hdr, '\n', MIN(sizeof(Exec), hdrsz)); if(p == nil) error(Ebadexec); *(p-1) = '\0'; argc = tokenize(line+2, progarg, nelem(progarg)); if(argc == 0) error(Ebadexec); /* The original file becomes an extra arg after #! line */ progarg[argc++] = file; /* * Take the #! $0 as a file to open, and replace * $0 with the original path's name. */ p = progarg[0]; progarg[0] = elem; cclose(chan); chan = nil; /* in case namec errors out */ USED(chan); chan = namec(p, Aopen, OEXEC, 0); hdrsz = chan->dev->read(chan, &hdr, sizeof(Hdr), 0); if(hdrsz < 2) error(Ebadexec); } /* * #! has had its chance, now we need a real binary. */ magic = l2be(hdr.magic); if(hdrsz != sizeof(Hdr) || magic != AOUT_MAGIC) error(Ebadexec); if(magic & HDR_MAGIC){ entry = vl2be(hdr.hdr[0]); hdrsz = sizeof(Hdr); } else{ entry = l2be(hdr.entry); hdrsz = sizeof(Exec); } textsz = l2be(hdr.text); datasz = l2be(hdr.data); bsssz = l2be(hdr.bss); textlim = UTROUND(UTZERO+hdrsz+textsz); datalim = PGROUND(textlim+datasz); bsslim = PGROUND(textlim+datasz+bsssz); /* * Check the binary header for consistency, * e.g. the entry point is within the text segment and * the segments don't overlap each other. */ if(entry < UTZERO+hdrsz || entry >= UTZERO+hdrsz+textsz) error(Ebadexec); if(textsz >= textlim || datasz > datalim || bsssz > bsslim || textlim >= USTKTOP || datalim >= USTKTOP || bsslim >= USTKTOP || datalim < textlim || bsslim < datalim) error(Ebadexec); /* * The new stack is created in ESEG, temporarily mapped elsewhere. * The stack contains, in descending address order: * a structure containing housekeeping and profiling data (Tos); * argument strings; * array of vectors to the argument strings with a terminating * nil (argv). * When the exec is committed, this temporary stack in ESEG will * become SSEG. * The architecture-dependent code which jumps to the new image * will also push a count of the argument array onto the stack (argc). */ qlock(&up->seglock); if(waserror()){ if(up->seg[ESEG] != nil){ putseg(up->seg[ESEG]); up->seg[ESEG] = nil; } qunlock(&up->seglock); nexterror(); } up->seg[ESEG] = newseg(SG_STACK, TSTKTOP-USTKSIZE, USTKSIZE/BY2PG); /* * Stack is a pointer into the temporary stack * segment, and will move as items are pushed. */ stack = TSTKTOP-sizeof(Tos); /* * First, the top-of-stack structure. */ tos = (Tos*)stack; tos->cyclefreq = m->cyclefreq; cycles((uvlong*)&tos->pcycles); tos->pcycles = -tos->pcycles; tos->kcycles = tos->pcycles; tos->clock = 0; /* * Next push any arguments found from a #! header. */ for(i = 0; i < argc; i++){ n = strlen(progarg[i])+1; stack -= n; memmove(UINT2PTR(stack), progarg[i], n); } /* * Copy the strings pointed to by the syscall argument argv into * the temporary stack segment, being careful to check both argv and * the strings it points to are valid. */ argv = va_arg(list, char**); evenaddr(PTR2UINT(argv)); for(i = 0;; i++, argv++){ a = *(char**)validaddr(argv, sizeof(char**), 0); if(a == nil) break; a = validaddr(a, 1, 0); n = ((char*)vmemchr(a, 0, 0x7fffffff) - a) + 1; /* * This futzing is so argv[0] gets validated even * though it will be thrown away if this is a shell * script. */ if(argc > 0 && i == 0) continue; /* * Before copying the string into the temporary stack, * which might involve a demand-page, check the string * will not overflow the bottom of the stack. */ stack -= n; if(stack < TSTKTOP-USTKSIZE) error(Enovmem); p = UINT2PTR(stack); memmove(p, a, n); p[n-1] = 0; argc++; } if(argc < 1) error(Ebadexec); /* * Before pushing the argument pointers onto the temporary stack, * which might involve a demand-page, check there is room for the * terminating nil pointer, plus pointers, plus some slop for however * argc might be passed on the stack by sysexecregs (give a page * of slop, it is an overestimate, but why not). * Sysexecstack does any architecture-dependent stack alignment. * Keep a copy of the start of the argument strings before alignment * so up->args can be created later. * Although the argument vectors are being pushed onto the stack in * the temporary segment, the values must be adjusted to reflect * the segment address after it replaces the current SSEG. */ a = p = UINT2PTR(stack); stack = sysexecstack(stack, argc); if(stack-(argc+1)*sizeof(char**)-BY2PG < TSTKTOP-USTKSIZE) error(Ebadexec); argv = (char**)stack; *--argv = nil; for(i = 0; i < argc; i++){ *--argv = p + (USTKTOP-TSTKTOP); p += strlen(p) + 1; } /* * Make a good faith copy of the args in up->args using the strings * in the temporary stack segment. The length must be > 0 as it * includes the \0 on the last argument and argc was checked earlier * to be > 0. After the memmove, compensate for any UTF character * boundary before placing the terminating \0. */ n = p - a; if(n <= 0) error(Egreg); if(n > 128) n = 128; p = smalloc(n); if(waserror()){ free(p); nexterror(); } memmove(p, a, n); while(n > 0 && (p[n-1] & 0xc0) == 0x80) n--; p[n-1] = '\0'; /* * All the argument processing is now done, ready to commit. */ free(up->text); up->text = elem; elem = nil; free(up->args); up->args = p; up->nargs = n; poperror(); /* p (up->args) */ /* * Close on exec */ f = up->fgrp; for(i=0; i<=f->maxfd; i++) fdclose(i, CCEXEC);Lines 1–500 of 1109. Load more
#include "u.h" #include "tos.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "../port/error.h" #include "../port/edf.h" #include <a.out.h> void sysr1(Ar0* ar0, va_list list) { USED(list); ar0->i = 0; } void sysrfork(Ar0* ar0, va_list list) { Proc *p; int flag, i, n, pid; Fgrp *ofg; Pgrp *opg; Rgrp *org; Egrp *oeg; Mach *wm; /* * int rfork(int); */ flag = va_arg(list, int); /* Check flags before we commit */ if((flag & (RFFDG|RFCFDG)) == (RFFDG|RFCFDG)) error(Ebadarg); if((flag & (RFNAMEG|RFCNAMEG)) == (RFNAMEG|RFCNAMEG)) error(Ebadarg); if((flag & (RFENVG|RFCENVG)) == (RFENVG|RFCENVG)) error(Ebadarg); if((flag&RFPROC) == 0) { if(flag & (RFMEM|RFNOWAIT)) error(Ebadarg); if(flag & (RFFDG|RFCFDG)) { ofg = up->fgrp; if(flag & RFFDG) up->fgrp = dupfgrp(ofg); else up->fgrp = dupfgrp(nil); closefgrp(ofg); } if(flag & (RFNAMEG|RFCNAMEG)) { opg = up->pgrp; up->pgrp = newpgrp(); if(flag & RFNAMEG) pgrpcpy(up->pgrp, opg); /* inherit noattach */ up->pgrp->noattach = opg->noattach; closepgrp(opg); } if(flag & RFNOMNT) up->pgrp->noattach = 1; if(flag & RFREND) { org = up->rgrp; up->rgrp = newrgrp(); closergrp(org); } if(flag & (RFENVG|RFCENVG)) { oeg = up->egrp; up->egrp = smalloc(sizeof(Egrp)); up->egrp->ref = 1; if(flag & RFENVG) envcpy(up->egrp, oeg); closeegrp(oeg); } if(flag & RFNOTEG) up->noteid = incref(¬eidalloc); ar0->i = 0; return; } p = newproc(); p->trace = up->trace; p->scallnr = up->scallnr; memmove(p->arg, up->arg, sizeof(up->arg)); p->nerrlab = 0; p->slash = up->slash; p->dot = up->dot; incref(p->dot); memmove(p->note, up->note, sizeof(p->note)); p->privatemem = up->privatemem; p->noswap = up->noswap; p->nnote = up->nnote; p->notified = 0; p->lastnote = up->lastnote; p->notify = up->notify; p->ureg = up->ureg; p->dbgreg = 0; /* Make a new set of memory segments */ n = flag & RFMEM; qlock(&p->seglock); if(waserror()){ qunlock(&p->seglock); nexterror(); } for(i = 0; i < NSEG; i++) if(up->seg[i]) p->seg[i] = dupseg(up->seg, i, n); qunlock(&p->seglock); poperror(); /* File descriptors */ if(flag & (RFFDG|RFCFDG)) { if(flag & RFFDG) p->fgrp = dupfgrp(up->fgrp); else p->fgrp = dupfgrp(nil); } else { p->fgrp = up->fgrp; incref(p->fgrp); } /* Process groups */ if(flag & (RFNAMEG|RFCNAMEG)) { p->pgrp = newpgrp(); if(flag & RFNAMEG) pgrpcpy(p->pgrp, up->pgrp); /* inherit noattach */ p->pgrp->noattach = up->pgrp->noattach; } else { p->pgrp = up->pgrp; incref(p->pgrp); } if(flag & RFNOMNT) up->pgrp->noattach = 1; if(flag & RFREND) p->rgrp = newrgrp(); else { incref(up->rgrp); p->rgrp = up->rgrp; } /* Environment group */ if(flag & (RFENVG|RFCENVG)) { p->egrp = smalloc(sizeof(Egrp)); p->egrp->ref = 1; if(flag & RFENVG) envcpy(p->egrp, up->egrp); } else { p->egrp = up->egrp; incref(p->egrp); } p->hang = up->hang; p->procmode = up->procmode; /* Craft a return frame which will cause the child to pop out of * the scheduler in user mode with the return register zero */ sysrforkchild(p, up); p->parent = up; p->parentpid = up->pid; if(flag&RFNOWAIT) p->parentpid = 0; else { lock(&up->exl); up->nchild++; unlock(&up->exl); } if((flag&RFNOTEG) == 0) p->noteid = up->noteid; pid = p->pid; memset(p->time, 0, sizeof(p->time)); p->time[TReal] = MACHP(0)->ticks; kstrdup(&p->text, up->text); kstrdup(&p->user, up->user); /* * since the bss/data segments are now shareable, * any mmu info about this process is now stale * (i.e. has bad properties) and has to be discarded. */ mmuflush(); p->basepri = up->basepri; p->priority = up->basepri; p->fixedpri = up->fixedpri; p->mp = up->mp; wm = up->wired; if(wm) procwired(p, wm->machno); ready(p); sched(); ar0->i = pid; } static uvlong vl2be(uvlong v) { uchar *p; p = (uchar*)&v; return ((uvlong)((p[0]<<24)|(p[1]<<16)|(p[2]<<8)|p[3])<<32) |((uvlong)(p[4]<<24)|(p[5]<<16)|(p[6]<<8)|p[7]); } static ulong l2be(long l) { uchar *cp; cp = (uchar*)&l; return (cp[0]<<24) | (cp[1]<<16) | (cp[2]<<8) | cp[3]; } typedef struct { Exec; uvlong hdr[1]; } Hdr; void sysexec(Ar0* ar0, va_list list) { Hdr hdr; Fgrp *f; Tos *tos; Chan *chan; Image *img; Segment *s; int argc, i, n; char *a, **argv, *elem, *file, *p; char line[sizeof(Exec)], *progarg[sizeof(Exec)/2+1]; long hdrsz, magic, textsz, datasz, bsssz; uintptr textlim, datalim, bsslim, entry, stack; /* * void* exec(char* name, char* argv[]); */ /* * Open the file, remembering the final element and the full name. */ file = nil; elem = nil; chan = nil; if(waserror()){ if(file) free(file); if(elem) free(elem); if(chan) cclose(chan); nexterror(); } p = va_arg(list, char*); p = validaddr(p, 1, 0); file = validnamedup(p, 1); chan = namec(file, Aopen, OEXEC, 0); kstrdup(&elem, up->genbuf); /* * Read the header. * If it's a #!, fill in progarg[] with info then read a new header * from the file indicated by the #!. * The #! line must be less than sizeof(Exec) in size, * including the terminating \n. */ hdrsz = chan->dev->read(chan, &hdr, sizeof(Hdr), 0); if(hdrsz < 2) error(Ebadexec); p = (char*)&hdr; argc = 0; if(p[0] == '#' && p[1] == '!'){ p = memccpy(line, (char*)&hdr, '\n', MIN(sizeof(Exec), hdrsz)); if(p == nil) error(Ebadexec); *(p-1) = '\0'; argc = tokenize(line+2, progarg, nelem(progarg)); if(argc == 0) error(Ebadexec); /* The original file becomes an extra arg after #! line */ progarg[argc++] = file; /* * Take the #! $0 as a file to open, and replace * $0 with the original path's name. */ p = progarg[0]; progarg[0] = elem; cclose(chan); chan = nil; /* in case namec errors out */ USED(chan); chan = namec(p, Aopen, OEXEC, 0); hdrsz = chan->dev->read(chan, &hdr, sizeof(Hdr), 0); if(hdrsz < 2) error(Ebadexec); } /* * #! has had its chance, now we need a real binary. */ magic = l2be(hdr.magic); if(hdrsz != sizeof(Hdr) || magic != AOUT_MAGIC) error(Ebadexec); if(magic & HDR_MAGIC){ entry = vl2be(hdr.hdr[0]); hdrsz = sizeof(Hdr); } else{ entry = l2be(hdr.entry); hdrsz = sizeof(Exec); } textsz = l2be(hdr.text); datasz = l2be(hdr.data); bsssz = l2be(hdr.bss); textlim = UTROUND(UTZERO+hdrsz+textsz); datalim = PGROUND(textlim+datasz); bsslim = PGROUND(textlim+datasz+bsssz); /* * Check the binary header for consistency, * e.g. the entry point is within the text segment and * the segments don't overlap each other. */ if(entry < UTZERO+hdrsz || entry >= UTZERO+hdrsz+textsz) error(Ebadexec); if(textsz >= textlim || datasz > datalim || bsssz > bsslim || textlim >= USTKTOP || datalim >= USTKTOP || bsslim >= USTKTOP || datalim < textlim || bsslim < datalim) error(Ebadexec); /* * The new stack is created in ESEG, temporarily mapped elsewhere. * The stack contains, in descending address order: * a structure containing housekeeping and profiling data (Tos); * argument strings; * array of vectors to the argument strings with a terminating * nil (argv). * When the exec is committed, this temporary stack in ESEG will * become SSEG. * The architecture-dependent code which jumps to the new image * will also push a count of the argument array onto the stack (argc). */ qlock(&up->seglock); if(waserror()){ if(up->seg[ESEG] != nil){ putseg(up->seg[ESEG]); up->seg[ESEG] = nil; } qunlock(&up->seglock); nexterror(); } up->seg[ESEG] = newseg(SG_STACK, TSTKTOP-USTKSIZE, USTKSIZE/BY2PG); /* * Stack is a pointer into the temporary stack * segment, and will move as items are pushed. */ stack = TSTKTOP-sizeof(Tos); /* * First, the top-of-stack structure. */ tos = (Tos*)stack; tos->cyclefreq = m->cyclefreq; cycles((uvlong*)&tos->pcycles); tos->pcycles = -tos->pcycles; tos->kcycles = tos->pcycles; tos->clock = 0; /* * Next push any arguments found from a #! header. */ for(i = 0; i < argc; i++){ n = strlen(progarg[i])+1; stack -= n; memmove(UINT2PTR(stack), progarg[i], n); } /* * Copy the strings pointed to by the syscall argument argv into * the temporary stack segment, being careful to check both argv and * the strings it points to are valid. */ argv = va_arg(list, char**); evenaddr(PTR2UINT(argv)); for(i = 0;; i++, argv++){ a = *(char**)validaddr(argv, sizeof(char**), 0); if(a == nil) break; a = validaddr(a, 1, 0); n = ((char*)vmemchr(a, 0, 0x7fffffff) - a) + 1; /* * This futzing is so argv[0] gets validated even * though it will be thrown away if this is a shell * script. */ if(argc > 0 && i == 0) continue; /* * Before copying the string into the temporary stack, * which might involve a demand-page, check the string * will not overflow the bottom of the stack. */ stack -= n; if(stack < TSTKTOP-USTKSIZE) error(Enovmem); p = UINT2PTR(stack); memmove(p, a, n); p[n-1] = 0; argc++; } if(argc < 1) error(Ebadexec); /* * Before pushing the argument pointers onto the temporary stack, * which might involve a demand-page, check there is room for the * terminating nil pointer, plus pointers, plus some slop for however * argc might be passed on the stack by sysexecregs (give a page * of slop, it is an overestimate, but why not). * Sysexecstack does any architecture-dependent stack alignment. * Keep a copy of the start of the argument strings before alignment * so up->args can be created later. * Although the argument vectors are being pushed onto the stack in * the temporary segment, the values must be adjusted to reflect * the segment address after it replaces the current SSEG. */ a = p = UINT2PTR(stack); stack = sysexecstack(stack, argc); if(stack-(argc+1)*sizeof(char**)-BY2PG < TSTKTOP-USTKSIZE) error(Ebadexec); argv = (char**)stack; *--argv = nil; for(i = 0; i < argc; i++){ *--argv = p + (USTKTOP-TSTKTOP); p += strlen(p) + 1; } /* * Make a good faith copy of the args in up->args using the strings * in the temporary stack segment. The length must be > 0 as it * includes the \0 on the last argument and argc was checked earlier * to be > 0. After the memmove, compensate for any UTF character * boundary before placing the terminating \0. */ n = p - a; if(n <= 0) error(Egreg); if(n > 128) n = 128; p = smalloc(n); if(waserror()){ free(p); nexterror(); } memmove(p, a, n); while(n > 0 && (p[n-1] & 0xc0) == 0x80) n--; p[n-1] = '\0'; /* * All the argument processing is now done, ready to commit. */ free(up->text); up->text = elem; elem = nil; free(up->args); up->args = p; up->nargs = n; poperror(); /* p (up->args) */ /* * Close on exec */ f = up->fgrp; for(i=0; i<=f->maxfd; i++) fdclose(i, CCEXEC);