/* Copyright 1990,1991,1992 Eric R. Smith. Copyright 1992,1993,1994 Atari Corporation. All rights reserved. */ /* * GEMDOS emulation routines: these are for the GEMDOS system calls * concerning allocating/freeing memory, including Pexec() (since * this allocates memory) and Pterm() (since this, implicitly, frees * it). */ #include "mint.h" #define DIRSEP(c) ((c) == '\\') int procdate, proctime; /* set when any processes are created/destroyed */ static long do_vfork P_((int)); /* * new call for TT TOS, for the user to inform DOS of alternate memory * FIXME: we really shouldn't trust the user so completely * FIXME: doesn't work if memory protection is on */ long ARGS_ON_STACK m_addalt(start, size) long start, size; { extern int no_mem_prot; /* see main.c and memprot.c */ if (!no_mem_prot) return 0; /* pretend to succeed */ if (!add_region(alt, start, size, M_ALT)) return EINTRN; else return 0; } /* * internal routine for doing Malloc on a particular memory map */ long _do_malloc(map, size, mode) MMAP map; long size; int mode; { virtaddr v; MEMREGION *m; long maxsize, mleft; if (size == -1L) { maxsize = max_rsize(map, 0L); if (curproc->maxmem) { mleft = curproc->maxmem - memused(curproc); if (maxsize > mleft) maxsize = mleft; if (maxsize < 0) maxsize = 0; } /* make sure to round down */ return maxsize & ~MASKBITS; } /* special case: Malloc(0) should always return 0 */ if (size == 0) return 0; if (curproc->maxmem) { /* memory limit? */ if (size > curproc->maxmem - memused(curproc)) { DEBUG(("malloc: memory request would exceed limit")); return 0; } } m = get_region(map, size, mode); if (!m) { return 0; } v = attach_region(curproc, m); if (!v) { m->links = 0; free_region(m); return 0; } /* NOTE: get_region returns a region with link count 1; since attach_region * increments the link count, we have to remember to decrement the count * to correct for this. */ m->links--; if ((mode & F_KEEP)) { /* request for permanent memory */ m->mflags |= M_KEEP; } return (long)v; } long ARGS_ON_STACK m_xalloc(size, mode) long size; int mode; { long r, r1; int protmode; #ifdef DEBUG_INFO int origmode = mode; #endif TRACE(("Mxalloc(%ld,%x)",size,mode)); /* * AKP: Hack here: if the calling process' PC is in ROM, then this is a * Malloc call made by VDI's v_opnvwk routine. So we change mode to * include "super accessible." This is temporary, until VDI catches up * with multitasking TOS. */ if (((mode & F_PROTMODE) == 0) && (curproc->ctxt[SYSCALL].pc > 0x00e00000L) && (curproc->ctxt[SYSCALL].pc < 0x00efffffL)) { #ifndef MULTITOS extern long gem_start; if (gem_start && curproc->ctxt[SYSCALL].pc >= gem_start) { mode |= F_PROT_G + 0x10; TRACE (("m_xalloc: AES special (call from ROM)")); } else #endif { mode |= (F_PROT_S + 0x10) | F_KEEP; TRACE(("m_xalloc: VDI special (call from ROM)")); } } /* * If the mode argument comes in a zero, then set it to the default * value from prgflags. Otherwise subtract one from it to bring it * into line with the actual argument to alloc_region. */ protmode = (mode & F_PROTMODE) >> F_PROTSHIFT; if (protmode == 0) { protmode = (curproc->memflags & F_PROTMODE) >> F_PROTSHIFT; } else --protmode; if (protmode > PROT_MAX_MODE) { DEBUG(("Mxalloc: invalid protection mode changed to private")); protmode = PROT_P; } #if 0 /* I'm very suspicious of the 0x08 flag; I can't see how it could * work as the comment below seems to indicate -- ERS */ /* * if the mode argument has the 0x08 bit set then you're trying to change * the protection mode of a block you already own. "size" is really its * base address. (new as of 2/6/92) */ if (mode & 0x08) change_prot_status(curproc,size,protmode); #endif /* * Copy the F_KEEP attribute into protmode. We didn't do that * before now because change_prot_status don't want to see no * steenking nofree attributes. */ protmode |= (mode & F_KEEP); /* mask off all but the ST/alternative RAM bits before further use */ mode &= 3; if (mode == 0) { r = _do_malloc(core, size, protmode); goto ret; } else if (mode == 1) { r = _do_malloc(alt, size, protmode); goto ret; } else if (size == -1) { /* modes 2 and 3 are the same for for size -1 */ r = _do_malloc(core, -1L, PROT_P); r1 = _do_malloc(alt, -1L, PROT_P); if (r1 > r) r = r1; goto ret; } else if (mode == 2) { r = _do_malloc(core, size, protmode); if (r == 0) r = _do_malloc(alt, size, protmode); goto ret; } else /* if (mode == 3) */ { r = _do_malloc(alt, size, protmode); if (r == 0) r = _do_malloc(core, size, protmode); goto ret; } ret: if (r == 0) { DEBUG(("m_xalloc(%lx,%x) returns 0",size,origmode)); } else { TRACE(("m_xalloc(%lx,%x) returns %lx",size,origmode,r)); } return r; } long ARGS_ON_STACK m_alloc(size) long size; { long r; TRACE(("Malloc(%lx)", size)); if (curproc->memflags & F_ALTALLOC) r = m_xalloc(size, 3); else r = m_xalloc(size, 0); TRACE(("Malloc: returning %lx", r)); return r; } long ARGS_ON_STACK m_free(block) virtaddr block; { MEMREGION *m; int i; TRACE(("Mfree(%lx)", block)); if (!block) { DEBUG(("Mfree: null pointer")); return EIMBA; } /* search backwards so that most recently allocated incarnations of shared memory blocks are freed first (this doesn't matter very often) */ for (i = curproc->num_reg - 1; i >= 0; i--) { if (curproc->addr[i] == block) { m = curproc->mem[i]; assert(m != NULL); assert(m->loc == (long)block); curproc->mem[i] = 0; curproc->addr[i] = 0; m->links--; if (m->links == 0) { free_region(m); } return 0; } } /* hmmm... if we didn't find the region, perhaps it's a global * one (with the M_KEEP flag set) belonging to a process that * terminated */ for (i = rootproc->num_reg - 1; i >= 0; i--) { if (rootproc->addr[i] == block) { m = rootproc->mem[i]; assert(m != NULL); assert(m->loc == (long)block); if (!(m->mflags & M_KEEP)) continue; TRACE(("Freeing M_KEPT memory")); rootproc->mem[i] = 0; rootproc->addr[i] = 0; m->links--; if (m->links == 0) { free_region(m); } return 0; } } DEBUG(("Mfree: bad address %lx", block)); return EIMBA; } long ARGS_ON_STACK m_shrink(dummy, block, size) int dummy; virtaddr block; long size; { MEMREGION *m; int i; UNUSED(dummy); TRACE(("Mshrink: %lx to %ld", block, size)); if (!block) { DEBUG(("Mshrink: null pointer")); return EIMBA; } for (i = 0; i < curproc->num_reg; i++) { if (curproc->addr[i] == block) { m = curproc->mem[i]; assert(m != NULL); assert(m->loc == (long)block); return shrink_region(m, size); } } DEBUG(("Mshrink: bad address (%lx)", block)); return EIMBA; } long ARGS_ON_STACK p_exec(mode, ptr1, ptr2, ptr3) int mode; void *ptr1, *ptr2, *ptr3; { MEMREGION *base, *env = 0; /* assignment suppresses spurious warning */ MEMREGION *text = 0; /* for shared text regions */ PROC *p; long r, flags = 0; int i; char mkbase = 0, mkload = 0, mkgo = 0, mkwait = 0, mkfree = 0; char overlay = 0; char thread = 0; char ptrace; char mkname = 0, *newname, *lastslash; char localname[PNAMSIZ+1]; XATTR xattr; int newpid; #ifndef MULTITOS extern BASEPAGE *gem_base; #endif #ifdef DEBUG_INFO /* tfmt and tail_offs are used for debugging only */ const char *tfmt = "Pexec(%d,%s,\"%s\",%lx)"; int tail_offs = 1; #endif /* the high bit of mode controls process tracing */ switch(mode & 0x7fff) { case 0: mkwait = 1; /* fall through */ case 100: mkload = mkgo = mkfree = 1; mkname = 1; break; case 200: /* overlay current process */ mkload = mkgo = 1; overlay = mkname = 1; break; case 3: mkload = 1; break; case 6: mkfree = 1; /* fall through */ case 4: mkwait = mkgo = 1; thread = (mode == 4); #ifdef DEBUG_INFO tfmt = "Pexec(%d,%lx,BP:%lx,%lx)"; tail_offs = 0; #endif break; case 106: mkfree = 1; /* fall through */ case 104: thread = (mode == 104); mkgo = 1; mkname = (ptr1 != 0); #ifdef DEBUG_INFO tfmt = "Pexec(%d,%s,BP:%lx,%lx)"; tail_offs = 0; #endif break; case 206: #if 0 /* mkfree has no effect when overlay is set, since * in this case the "parent" and "child" are the same * process; since the "child" will run in memory that the * "parent" allocated, we don't want that memory freed! */ mkfree = 1; #endif /* fall through */ case 204: mkgo = overlay = 1; mkname = (ptr1 != 0); #ifdef DEBUG_INFO tfmt = "Pexec(%d,%s,BP:%lx,%lx)"; tail_offs = 0; #endif break; case 7: flags = (long)ptr1; /* set program flags */ if (((flags & F_PROTMODE) >> F_PROTSHIFT) > PROT_MAX_MODE) { DEBUG (("Pexec: invalid protection mode changed to private")); flags = (flags & ~F_PROTMODE) | F_PROT_P; } #ifndef MULTITOS if ((flags & F_PROTMODE) == 0 && curproc->base == gem_base) { flags |= F_PROT_G; TRACE (("p_exec: AES special")); } #endif /* and fall through */ case 5: mkbase = 1; #ifdef DEBUG_INFO tfmt = "Pexec(%d,%lx,%s,%lx)"; tail_offs = 0; #endif break; default: DEBUG(("Pexec(%d,%lx,%lx,%lx): bad mode",mode,ptr1,ptr2,ptr3)); return EINVFN; } TRACE((tfmt,mode,ptr1,(char *)ptr2+tail_offs,ptr3)); /* Pexec with mode 0x8000 indicates tracing should be active */ ptrace = (!mkwait && (mode & 0x8000)); /* in most cases, we'll want a process struct to exist, * so make sure one is around. Note that we must be * careful to free it later! */ TRACE(("Checking for memory for new PROC structure")); p = 0; if (!overlay) { p = new_proc(); if (!p) { DEBUG(("Pexec: couldn't get a PROC struct")); return ENSMEM; } } TRACE(("creating environment")); if (mkload || mkbase) { env = create_env((char *)ptr3, flags); if (!env) { DEBUG(("Pexec: unable to create environment")); if (p) dispose_proc(p); return ENSMEM; } } TRACE(("creating base page")); if (mkbase) { base = create_base((char *)ptr2, env, flags, 0L, 0L, 0L, 0L, 0L, 0L); if (!base) { DEBUG(("Pexec: unable to create basepage")); detach_region(curproc, env); if (p) dispose_proc(p); return ENSMEM; } TRACELOW(("Pexec: basepage region(%lx) is %ld bytes at %lx", base, base->len, base->loc)); } else if (mkload) { char cbuf[128], *tail = ptr2; if (overlay) { static char fbuf[PATH_MAX]; cbuf[127] = 0; ptr1 = strncpy (fbuf, ptr1, PATH_MAX-2); tail = strncpy (cbuf, ptr2, 127); } #if 0 base = load_region((char *)ptr1, env, (char *)tail, &xattr, &text, &flags, 0); #else base = load_region((char *)ptr1, env, (char *)tail, &xattr, &text, &flags, overlay); #endif if (!base) { DEBUG(("Pexec: load_region failed")); detach_region(curproc, env); if (p) dispose_proc(p); return mint_errno; } TRACE(("Pexec: basepage region(%lx) is %ld bytes at %lx", base, base->len, base->loc)); } else { /* mode == 4,6,104,106,204, or 206 -- just go */ base = addr2mem((virtaddr)ptr2); if (base) env = addr2mem(*(void **)(base->loc + 0x2c)); else env = 0; if (!env) { DEBUG(("Pexec: memory not owned by parent")); if (p) dispose_proc(p); return EIMBA; } #if 0 /* make sure that the PC we are about to use is in a region which is * attached to the process; this is most commonly a problem for * shared text segment programs. * BUG: we should verify that the PC is in a region to which the * child process should legitimately have access. */ text = addr2region(((BASEPAGE *)base->loc)->p_tbase); if (text == base) { /* text segment is part of base region */ text = NULL; } #endif } /* make a local copy of the name, in case we are overlaying the current * process */ if (mkname) { lastslash = 0; newname = ptr1; while (*newname) { if (*newname == '\\' || *newname == '/') lastslash = newname; ++newname; } if (!lastslash) lastslash = ptr1; else lastslash++; i = 0; newname = localname; while (i++ < PNAMSIZ) { if (*lastslash == '.' || *lastslash == 0) { *newname = 0; break; } else *newname++ = *lastslash++; } *newname = 0; } if (mkload || mkbase) { /* * Now that the file's loaded, flags is set to the prgflags * for the file. In the case of mkbase it's been right all along. * Here's where we change the protection on the environment to * match those flags. */ mark_region(env,(short)((flags & F_PROTMODE) >> F_PROTSHIFT)); } if (p) { /* free the PROC struct so fork_proc will succeed */ /* FIXME: it would be much better to pass the PROC as a parameter * to fork_proc!! */ dispose_proc(p); p = 0; } if (mkgo) { BASEPAGE *b; /* tell the child who the parent was */ b = (BASEPAGE *)base->loc; if (overlay) { b->p_parent = curproc->base->p_parent; p = curproc; /* make sure that exec_region doesn't free the base and env */ base->links++; env->links++; if (text) text->links++; } else { b->p_parent = curproc->base; p = fork_proc(); } if (!p) { if (mkbase) { detach_region(curproc, base); detach_region(curproc, env); if (text) detach_region(curproc, text); } return mint_errno; } /* jr: add Pexec information to PROC struct */ strncpy(p->cmdlin, b->p_cmdlin, 128); p->fname[0] = 0; if (mkload) { char tmp[PATH_MAX]; char *source = ptr1; tmp[1] = ':'; if (source[1] == ':') { tmp[0] = source[0]; source += 2; } else tmp[0] = 'A' + curproc->curdrv; if (DIRSEP(source[0])) /* absolute path? */ { strncpy (&tmp[2], &source[0], PATH_MAX-2); strcpy (p->fname, tmp); } else { if (! d_getcwd (&tmp[2], tmp[0] - 'A' + 1, PATH_MAX - 2)) ksprintf (p->fname, "%s\\%s", tmp, source); } } if (ptrace) p->ptracer = pid2proc(p->ppid); /* Even though the file system won't allow unauthorized access * to setuid/setgid programs, it's better to err on the side of * caution and forbid them to be traced (since the parent can arrange * to share the child's address space, not all accesses need to * go through the file system.) */ if (mkload && mkgo && !p->ptracer) { /* setuid/setgid is OK */ if (xattr.mode & S_ISUID) p->euid = xattr.uid; if (xattr.mode & S_ISGID) p->egid = xattr.gid; } /* exec_region frees the memory attached to p; that's always what * we want, since fork_proc duplicates the memory, and since * if we didn't call fork_proc then we're overlaying. * NOTE: after this call, we may not be able to access the * original address space that the Pexec was taking place in * (if this is an overlaid Pexec, we just freed that memory). */ (void)exec_region(p, base, thread); attach_region(p, env); attach_region(p, base); if (text) attach_region(p, text); if (mkname) { /* interesting coincidence -- if a process needs a name, it usually * needs to have its domain reset to DOM_TOS. Doing it this way * (instead of doing it in exec_region) means that Pexec(4,...) * can be used to create new threads of execution which retain * the same domain. */ if (!thread) p->domain = DOM_TOS; /* put in the new process name we saved above */ strcpy(p->name, localname); } /* turn on tracing for the new process */ if (p->ptracer) p->ctxt[CURRENT].ptrace = 1; /* set the time/date stamp of u:\proc */ proctime = timestamp; procdate = datestamp; if (overlay) { /* correct for temporary increase in links (see above) */ base->links--; env->links--; if (text) text->links--; /* let our parent run, if it Vfork'd() */ if ( (p = pid2proc(curproc->ppid)) != 0 ) { if (p->wait_q == WAIT_Q && p->wait_cond == (long)curproc) { short sr = spl7(); rm_q(WAIT_Q, p); add_q(READY_Q, p); spl(sr); } } /* OK, let's run our new code */ /* we guarantee ourselves at least 3 timeslices to do an Mshrink */ assert(curproc->magic == CTXT_MAGIC); fresh_slices(3); leave_kernel(); change_context(&(curproc->ctxt[CURRENT])); } else { /* we want this process to run ASAP */ /* so we temporarily give it high priority and put it first on the * run queue */ run_next(p, 3); } } if (mkfree) { detach_region(curproc, base); detach_region(curproc, env); if (text) detach_region(curproc, text); } if (mkwait) { long oldsigint, oldsigquit; oldsigint = curproc->sighandle[SIGINT]; oldsigquit = curproc->sighandle[SIGQUIT]; curproc->sighandle[SIGINT] = curproc->sighandle[SIGQUIT] = SIG_IGN; newpid = p->pid; for(;;) { r = p_wait3(0, (long *)0); if (r < 0) { ALERT("p_exec: wait error"); return EINTRN; } if ( newpid == ((r&0xffff0000L) >> 16) ) { TRACE(("leaving Pexec; child return code %ld", r)); r = r & 0x0000ffffL; break; } if (curproc->pid) DEBUG(("Pexec: wrong child found")); } curproc->sighandle[SIGINT] = oldsigint; curproc->sighandle[SIGQUIT] = oldsigquit; return r; } else if (mkgo) { /* warning: after the yield() the "p" structure may not exist any more * (if the child exits right away) */ newpid = p->pid; yield(); /* let the new process run */ return newpid; } else { /* guarantee ourselves at least 3 timeslices to do an Mshrink */ fresh_slices(3); TRACE(("leaving Pexec with basepage address %lx", base->loc)); return base->loc; } } /* * terminate a process, with return code "code". If que == ZOMBIE_Q, free * all resources attached to the child; if que == TSR_Q, free everything * but memory. * NOTE: terminate() should be called only when the process is to be * "terminated with extreme prejuidice". Most times, p_term or p_termres * are the functions to use, since they allow the user to do some cleaning * up, etc. */ long terminate(code, que) int code, que; { extern PROC *dlockproc[]; /* in dosdir.c */ PROC *p; FILEPTR *fp; MEMREGION *m; MEMREGION **hold_mem; virtaddr *hold_addr; int i, wakemint = 0; DIR *dirh, *nexth; extern short bconbsiz; /* in bios.c */ if (bconbsiz) (void) bflush(); assert(que == ZOMBIE_Q || que == TSR_Q); if (curproc->pid == 0) { FATAL("attempt to terminate MiNT"); } /* cancel all user-specified interrupt signals */ cancelsigintrs(); /* cancel all pending timeouts for this process */ cancelalltimeouts(); /* cancel alarm clock */ curproc->alarmtim = 0; /* release any drives locked by Dlock */ for(i = 0; i < NUM_DRIVES; i++) { if (dlockproc[i] == curproc) { dlockproc[i] = 0; changedrv(i); } } #if 0 /* release the controlling terminal, if we're a process group leader */ fp = curproc->handle[-1]; if (fp && is_terminal(fp) && curproc->pgrp == curproc->pid) { struct tty *tty = (struct tty *)fp->devinfo; if (curproc->pgrp == tty->pgrp) tty->pgrp = 0; } #else #if 0 /* release the controlling terminal, if we're the last member of this pgroup */ fp = curproc->control; if (fp && is_terminal(fp)) { struct tty *tty = (struct tty *)fp->devinfo; int pgrp = curproc->pgrp; if (pgrp == tty->pgrp) { PROC *p; FILEPTR *pfp; if (tty->use_cnt > 1) { for (p = proclist; p; p = p->gl_next) { if (p->pgrp == pgrp && p != curproc && (0 != (pfp = p->control)) && pfp->fc.index == fp->fc.index && pfp->fc.dev == fp->fc.dev) goto found; } } else { for (p = proclist; p; p = p->gl_next) { if (p->pgrp == pgrp && p != curproc && p->control == fp) goto found; } } tty->pgrp = 0; } found: ; } #endif #endif /* close all files */ for (i = MIN_HANDLE; i < MAX_OPEN; i++) { if ((fp = curproc->handle[i]) != 0) do_close(fp); curproc->handle[i] = 0; } /* close any unresolved Fsfirst/Fsnext directory searches */ for (i = 0; i < NUM_SEARCH; i++) { if (curproc->srchdta[i]) { DIR *dirh = &curproc->srchdir[i]; (*dirh->fc.fs->closedir)(dirh); release_cookie(&dirh->fc); dirh->fc.fs = 0; } } /* close pending opendir/readdir searches */ for (dirh = curproc->searches; dirh; ) { if (dirh->fc.fs) { (*dirh->fc.fs->closedir)(dirh); release_cookie(&dirh->fc); } nexth = dirh->next; kfree(dirh); dirh = nexth; } /* release the directory cookies held by the process */ for (i = 0; i < NUM_DRIVES; i++) { release_cookie(&curproc->curdir[i]); curproc->curdir[i].fs = 0; release_cookie(&curproc->root[i]); curproc->root[i].fs = 0; } /* release all semaphores owned by this process */ free_semaphores(curproc->pid); /* free all memory */ /* if mflags & M_KEEP then attach it to process 0 */ if (que == ZOMBIE_Q) { for (i = curproc->num_reg - 1; i >=0; i--) { m = curproc->mem[i]; curproc->mem[i] = 0; curproc->addr[i] = 0; if (m) { /* don't free specially allocated memory */ if ((m->mflags & M_KEEP) && (m->links <= 1)) { if (curproc != rootproc) attach_region(rootproc, m); } /* Leave shared text regions in memory, get_region will reclaim them if memory runs low. Assume that we will never have 65535 processes using a particular memory region. */ m->links--; if (m->links == 0) { if (m->mflags & M_SHTEXT_T) m->links = 0xffff; else free_region(m); } } } /* * mark the mem & addr arrays as void so the memory * protection code won't try to walk them. Do this before * freeing them so we don't try to walk them when marking * those pages themselves as free! * * Note: when a process terminates, the MMU root pointer * still points to that process' page table, until the next * process is dispatched. This is OK, since the process' * page table is in system memory, and it isn't going to be * freed. It is going to wind up on the free process list, * though, after dispose_proc. This might be Not A Good * Thing. */ hold_addr = curproc->addr; hold_mem = curproc->mem; curproc->mem = NULL; curproc->addr = NULL; curproc->num_reg = 0; kfree(hold_addr); kfree(hold_mem); } /* else make TSR process non-swappable */ /* * make sure that any open files that refer to this process are * closed */ changedrv(PROC_RDEV_BASE | curproc->pid); /* find our parent (if parent not found, then use process 0 as parent * since that process is constantly in a wait loop) */ p = pid2proc(curproc->ppid); if (!p) { TRACE(("terminate: parent not found")); p = pid2proc(0); } /* NOTE: normally just post_sig is sufficient for sending a signal; but * in this particular case, we have to worry about processes that are * blocking all signals because they Vfork'd and are waiting for us to * finish (which is indicated by a wait_cond matching our PROC * structure), and also processes that are ignoring SIGCHLD but are * waiting for us. */ if (p->wait_q == WAIT_Q && (p->wait_cond == (long)curproc || p->wait_cond == (long)p_waitpid) ) { short sr = spl7(); TRACE(("terminate: waking up parent")); rm_q(WAIT_Q, p); add_q(READY_Q, p); spl(sr); } if (curproc->ptracer && curproc->ptracer != p) { /* BUG: should we ensure curproc->ptracer is awake ? */ post_sig(curproc->ptracer, SIGCHLD); /* tell tracing process */ } post_sig(p, SIGCHLD); /* inform of process termination */ /* find our children, and orphan them * also, check for processes we were tracing, and * cancel the trace */ i = curproc->pid; for (p = proclist; p; p = p->gl_next) { if (p->ppid == i) { p->ppid = 0; /* have the system adopt it */ if (p->wait_q == ZOMBIE_Q) wakemint = 1; /* we need to wake proc. 0 */ } if (p->ptracer == curproc) { p->ptracer = 0; /* * `FEATURE': we terminate traced processes when the tracer terminates. * It might plausibly be argued that it would be better to let them * continue, to let some (new) tracer take them over. On the other hand, * if the tracer terminated normally, it should have used Fcntl(PTRACESFLAGS) * to reset the trace nicely, so something must be wrong for us to have * reached here. */ post_sig(p, SIGTERM); /* arrange for termination */ } } if (wakemint) { p = rootproc; /* pid 0 */ if (p->wait_q == WAIT_Q) { short sr = spl7(); rm_q(WAIT_Q, p); add_q(READY_Q, p); spl(sr); } } /* this makes sure that our children are inherited by the system; * plus, it may help avoid problems if somehow a signal gets * through to us */ for(i = 0; i < NSIG; i++) curproc->sighandle[i] = SIG_IGN; /* finally, reset the time/date stamp for u:\proc */ proctime = timestamp; procdate = datestamp; sleep(que, (long)(unsigned)code); /* we shouldn't ever get here */ FATAL("terminate: sleep woke up when it shouldn't have"); return 0; } /* * TOS process termination entry points: * p_term terminates the process, freeing its memory * p_termres lets the process hang around resident in memory, after * shrinking its transient program area to "save" bytes */ long ARGS_ON_STACK p_term(code) int code; { CONTEXT *syscall; TRACE(("Pterm(%d)", code)); /* call the process termination vector */ syscall = &curproc->ctxt[SYSCALL]; if (syscall->term_vec != (long)rts) { TRACE(("term_vec: user has something to do")); /* * we handle the termination vector just like Supexec(), by * sending signal 0 to the process. See supexec() in xbios.c for details. * Note that we _always_ want to unwind the signal stack, and setting * bit 1 of curproc->sigmask tells handle_sig to do that -- see signal.c. */ curproc->sigmask |= 1L; (void)supexec((Func)syscall->term_vec, 0L, 0L, 0L, 0L, (long)code); /* * if we arrive here, continue with the termination... */ } return terminate(code, ZOMBIE_Q); } long ARGS_ON_STACK p_term0() { return p_term(0); } long ARGS_ON_STACK p_termres(save, code) long save; int code; { MEMREGION *m; int i; TRACE(("Ptermres(%ld, %d)", save, code)); m = curproc->mem[1]; /* should be the basepage (0 is env.) */ if (m) { (void)shrink_region(m, save); } /* * make all of the TSR's private memory globally accessible; * this means that more TSR's will "do the right thing" * without having to have prgflags set. */ for (i = 0; i < curproc->num_reg; i++) { m = curproc->mem[i]; if (m && m->links == 1) { /* only the TSR is owner */ if (get_prot_mode(m) == PROT_P) { mark_region(m, PROT_G); } } } return terminate(code, TSR_Q); } /* * routine for waiting for children to die. Return has the pid of the * found child in the high word, and the child's exit code in * the low word. If no children exist, return "File Not Found". * If (nohang & 1) is nonzero, then return a 0 immediately if we have * no dead children but some living ones that we still have to wait * for. If (nohang & 2) is nonzero, then we return any stopped * children; otherwise, only children that have exited or are stopped * due to a trace trap are returned. * If "rusage" is non-zero and a child is found, put the child's * resource usage into it (currently only the user and system time are * sent back). * The pid argument specifies a set of child processes for which status * is requested: * If pid is equal to -1, status is requested for any child process. * * If pid is greater than zero, it specifies the process ID of a * single child process for which status is requested. * * If pid is equal to zero, status is requested for any child * process whose process group ID is equal to that of the calling * process. * * If pid is less than -1, status is requested for any child process * whose process group ID is equal to the absolute value of pid. * * Note this call is a real standard crosser... POSIX.1 doesn't have the * rusage stuff, BSD doesn't have the pid stuff; both are useful, so why * not have it all! */ long ARGS_ON_STACK p_waitpid(pid, nohang, rusage) int pid; int nohang; long *rusage; { long r; PROC *p, *q; int ourpid; int found; TRACE(("Pwaitpid(%d, %d, %lx)", pid, nohang, rusage)); ourpid = curproc->pid; /* if there are terminated children, clean up and return their info; * if there are children, but still running, wait for them; * if there are no children, return an error */ do { /* look for any children */ found = 0; for (p = proclist; p; p = p->gl_next) { if ((p->ppid == ourpid || p->ptracer == curproc) && (pid == -1 || (pid > 0 && pid == p->pid) || (pid == 0 && p->pgrp == ourpid) || (pid < -1 && p->pgrp == -pid))) { found++; if (p->wait_q == ZOMBIE_Q || p->wait_q == TSR_Q) break; /* p->wait_cond == 0 if a stopped process has already been waited for */ if (p->wait_q == STOP_Q && p->wait_cond) { if ((nohang & 2) || ((p->wait_cond&0x1f00) == (SIGTRAP<<8))) break; } } } if (!p) { if (found) { if (nohang & 1) return 0; if (curproc->pid) TRACE(("Pwaitpid: going to sleep")); sleep(WAIT_Q, (long)p_waitpid); } else { DEBUG(("Pwaitpid: no children found")); return EFILNF; } } } while (!p); /* OK, we've found our child */ /* calculate the return code from the child's exit code and pid */ r = (((unsigned long)p->pid) << 16) | (p->wait_cond & 0x0000ffff); /* check resource usage */ if (rusage) { *rusage++ = p->usrtime + p->chldutime; *rusage = p->systime + p->chldstime; } /* avoid adding adopted trace processes usage to the foster parent */ if (curproc->pid == p->ppid) { /* add child's resource usage to parent's */ if (p->wait_q == TSR_Q || p->wait_q == ZOMBIE_Q) { curproc->chldstime += p->systime + p->chldstime; curproc->chldutime += p->usrtime + p->chldutime; } } /* if it was stopped, mark it as having been found and again return */ if (p->wait_q == STOP_Q) { p->wait_cond = 0; return r; } /* We have to worry about processes which attach themselves to running * processes which they want to trace. We fix things up so that the * second time the signal gets delivered we will go all the way to the * end of this function. */ if (p->ptracer && p->ptracer->pid != p->ppid) { if (curproc == p->ptracer) { /* deliver the signal to the tracing process first */ TRACE(("Pwaitpid(ptracer): returning status to tracing process")); p->ptracer = NULL; if (p->ppid != -1) return r; } else { /* Hmmm, the real parent got here first */ TRACE(("Pwaitpid(ptracer): returning status to parent process")); p->ppid = -1; return r; } } /* if it was a TSR, mark it as having been found and return */ if (p->wait_q == TSR_Q) { p->ppid = -1; return r; } /* it better have been on the ZOMBIE queue from here on in... */ assert(p->wait_q == ZOMBIE_Q); assert(p != curproc); /* take the child off both the global and ZOMBIE lists */ { short sr = spl7(); rm_q(ZOMBIE_Q, p); spl(sr); } if (proclist == p) { proclist = p->gl_next; p->gl_next = 0; } else { q = proclist; while(q && q->gl_next != p) q = q->gl_next; assert(q); q->gl_next = p->gl_next; p->gl_next = 0; } dispose_proc(p); /* free the PROC structure */ return r; } /* p_wait3: BSD process termination primitive, here to maintain * compatibility with existing binaries. */ long ARGS_ON_STACK p_wait3(nohang, rusage) int nohang; long *rusage; { return p_waitpid(-1, nohang, rusage); } /* p_wait: block until a child has exited, and don't worry about resource stats. this is provided as a convenience, and to maintain compatibility with existing binaries (yes, I'm lazy...). we could make do with Pwaitpid(). */ long ARGS_ON_STACK p_wait() { /* * BEWARE: * POSIX says that wait() should be implemented as * Pwaitpid(-1, 0, (long *)0). Pwait is really not * useful for much at all, but we'll keep it around * for a while (with it's old, crufty semantics) * for backwards compatibility. People implementing * POSIX style libraries should use Pwaitpid even * to implement wait(). */ return p_wait3(2, (long *)0); } /* * do_vfork(save): create a duplicate of the current process. This is * essentially a vfork() algorithm, except that if (save == 1) the * parent's address space is saved, and then restored when the process * is made runnable again. The parent is suspended until either the child * process (the duplicate) exits or does a Pexec which overlays its * memory space. * * "txtsize" is the size of the process' TEXT area, if it has a valid one; * this is part of the second memory region attached (the basepage one) * and need not be saved (we assume processes don't write on their own * code segment) */ static long do_vfork(save) int save; { PROC *p; long sigmask; MEMREGION *m, *savemem = 0; long savesize, txtsize; int i, newpid; char *saveplace; p = fork_proc(); if (!p) { DEBUG(("do_vfork: couldn't get new PROC struct")); return mint_errno; } /* set u:\proc time+date */ proctime = timestamp; procdate = datestamp; /* * maybe save the parent's address space */ txtsize = p->txtsize; if (save) { TRACE(("do_vfork: saving parent")); savesize = memused(curproc) - txtsize; if (!txtsize && (p->base->p_flags & F_SHTEXT)) { for (i = 0; i < curproc->num_reg; i++) { m = curproc->mem[i]; if (m && (m->mflags & M_SHTEXT)) { savesize -= m->len; break; } } } assert(savesize >= 0); saveplace = (char *)alloc_region(alt, savesize, PROT_P); if (!saveplace) saveplace = (char *)alloc_region(core, savesize, PROT_P); if (!saveplace) { DEBUG(("do_vfork: can't save parent's memory")); p->ppid = 0; /* abandon the child */ post_sig(p, SIGKILL); /* then kill it */ p->ctxt[CURRENT].pc = (long)check_sigs; p->ctxt[CURRENT].sr |= 0x2000; /* use supervisor stack */ #if 0 /* stack set up in fork_proc() */ p->ctxt[CURRENT].ssp = (long)(p->stack + ISTKSIZE); #endif p->pri = MAX_NICE+1; run_next(p, 1); yield(); return ENSMEM; } savemem = addr2mem((virtaddr)saveplace); assert(savemem); savemem->mflags |= M_FSAVED; for (i = 0; i < curproc->num_reg; i++) { m = curproc->mem[i]; if (m && !(m->mflags & (M_SEEN|M_FSAVED|M_SHTEXT))) { m->mflags |= M_SEEN; /* save links only once */ if (i != 1 || txtsize == 0) { quickmove(saveplace, (char *)m->loc, m->len); saveplace += m->len; } else { quickmove(saveplace, (char *)m->loc+txtsize, m->len - txtsize); saveplace += m->len - txtsize; } } } for (i = 0; i < curproc->num_reg; i++) { if (curproc->mem[i]) curproc->mem[i]->mflags &= ~M_SEEN; } } p->ctxt[CURRENT] = p->ctxt[SYSCALL]; p->ctxt[CURRENT].regs[0] = 0; /* child returns a 0 from call */ p->ctxt[CURRENT].sr &= ~(0x2000); /* child must be in user mode */ #if 0 /* set up in fork_proc() */ p->ctxt[CURRENT].ssp = (long)(p->stack + ISTKSIZE); #endif /* watch out for job control signals, since our parent can never wake * up to respond to them. solution: block them; exec_region (in mem.c) * clears the signal mask, so an exec() will unblock them. */ p->sigmask |= (1L << SIGTSTP) | (1L << SIGTTIN) | (1L << SIGTTOU); TRACE(("do_vfork: parent going to sleep, wait_cond == %lx", (long)p)); /* WARNING: This sleep() must absolutely not wake up until the child * has released the memory space correctly. That's why we mask off * all signals. */ sigmask = curproc->sigmask; curproc->sigmask = ~(((unsigned long)1 << SIGKILL) | 1); { short sr = spl7(); add_q(READY_Q, p); /* put it on the ready queue */ sleep(WAIT_Q, (long)p); /* while we wait for it */ spl(sr); } TRACE(("do_vfork: parent waking up")); if (save) { fork_restore(curproc, 0L); } curproc->sigmask = sigmask; /* note that the PROC structure pointed to by p may be freed during * the check_sigs call! */ newpid = p->pid; check_sigs(); /* did we get any signals while sleeping? */ return newpid; } /* * fork_restore(p): restore process memory after a blocking fork */ void fork_restore(p, savemem) PROC *p; MEMREGION *savemem; { MEMREGION *m; long txtsize = p->txtsize; char *saveplace; int i; extern int no_mem_prot; if (!savemem) { for (i = 0; i < p->num_reg; i++) { m = p->mem[i]; if (m && (m->mflags & M_FSAVED)) { savemem = m; break; } } if (!savemem) return; } saveplace = (char *)savemem->loc; TRACE(("do_vfork: parent restoring memory")); #if 1 if (p != curproc && !no_mem_prot) /* memprot doesn't like it? try p's mmu context... */ set_mmu (p->ctxt[CURRENT].crp, p->ctxt[CURRENT].tc); #endif for (i = 0; i < p->num_reg; i++) { m = p->mem[i]; if (m && !(m->mflags & (M_SEEN|M_FSAVED|M_SHTEXT))) { m->mflags |= M_SEEN; if (i != 1 || txtsize == 0) { quickmove((char *)m->loc, saveplace, m->len); saveplace += m->len; } else { quickmove((char *)m->loc+txtsize, saveplace, m->len - txtsize); saveplace += m->len - txtsize; } } } for (i = 0; i < p->num_reg; i++) { if (p->mem[i]) p->mem[i]->mflags &= ~M_SEEN; } #if 1 if (p != curproc && !no_mem_prot) set_mmu (curproc->ctxt[CURRENT].crp, curproc->ctxt[CURRENT].tc); #endif detach_region(p, savemem); } /* * here are the interfaces that the user sees. Pvfork() doesn't save * the child's address space; Pfork() does. Someday Pfork() should * allow asynchronous execution of both child and parent, but this * will do for now. */ long ARGS_ON_STACK p_vfork() { return do_vfork(0); } long ARGS_ON_STACK p_fork() { return do_vfork(1); }