/* Copyright 1990,1991,1992 Eric R. Smith. 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" 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 */ long m_addalt(start, size) long start, size; { 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); 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); if (!m) { DEBUG("malloc: out of memory"); 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 m_xalloc(size, mode) long size; int mode; { long r, r1; TRACE("Mxalloc(%ld,%x)",size,mode); if ( (mode & 8) ) { DEBUG("Unsupported Mxalloc mode"); return 0; /* future expansion */ } if ( (mode&3) == 0) return _do_malloc(core, size, mode); else if ( (mode&3) == 1) return _do_malloc(alt, size, mode); else if (size == -1) { /* modes 2 and 3 are the same for for size -1 */ r = _do_malloc(core, -1L, mode); r1 = _do_malloc(alt, -1L, mode); if (r > r1) return r; else return r1; } else if ( (mode&3) == 2) { r = _do_malloc(core, size, mode); if (r == 0) return _do_malloc(alt, size, mode); else return r; } else /* if ( (mode&3) == 3) */ { r = _do_malloc(alt, size, mode); if (r == 0) return _do_malloc(core, size, mode); else return r; } DEBUG("Mxalloc: bad request??"); return 0; } long 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 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; 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 m_shrink(dummy, block, size) int dummy; virtaddr block; long size; { MEMREGION *m; int i; 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 p_exec(mode, ptr1, ptr2, ptr3) int mode; void *ptr1, *ptr2, *ptr3; { MEMREGION *base, *env = 0; /* assignment suppress spurious warning */ PROC *p = 0; long r, flags = 0; int i; char mkbase = 0, mkload = 0, mkgo = 0, mkwait = 0, mkfree = 0; char overlay = 0; char thread = 0; char mkname = 0, *newname, *lastslash; char localname[PNAMSIZ+1]; XATTR xattr; switch(mode) { case 0: mkwait = 1; /* fall through */ case 100: mkload = mkgo = mkfree = 1; mkname = 1; TRACE("Pexec(%d,%s,\"%s\")", mode, ptr1, ptr2); break; case 200: /* overlay current process */ mkload = mkgo = 1; overlay = mkname = 1; TRACE("Pexec(%d,%s,\"%s\")", mode, ptr1, ptr2); break; case 3: mkload = 1; TRACE("Pexec(%d,%s,\"%s\")", mode, ptr1, ptr2); break; case 6: mkfree = 1; /* fall through */ case 4: mkwait = mkgo = 1; TRACE("Pexec(%d,%lx)", mode, ptr2); break; case 106: mkfree = 1; /* fall through */ case 104: thread = (mode == 104); mkgo = 1; mkname = (ptr1 != 0); TRACE("Pexec(%d,%s,%lx)", mode, ptr1, ptr2); break; case 7: flags = (long)ptr1; /* set program flags */ /* and fall through */ case 5: mkbase = 1; TRACE("Pexec(%d,%lx,\"%s\")", mode, ptr1, ptr2); break; default: DEBUG("Pexec: bad mode %d", mode); return EINVFN; } /* 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! */ p = 0; if (!overlay) { p = new_proc(); if (!p) { DEBUG("Pexec: couldn't get a PROC struct"); return ENSMEM; } } if (mkload || mkbase) { env = create_env((char *)ptr3); if (!env) { DEBUG("Pexec: unable to create environment"); if (p) dispose_proc(p); return ENSMEM; } } if (mkbase) { base = create_base((char *)ptr2, env, flags, 0L); if (!base) { DEBUG("Pexec: unable to create basepage"); detach_region(curproc, env); if (p) dispose_proc(p); return ENSMEM; } } else if (mkload) { base = load_region((char *)ptr1, env, (char *)ptr2, &xattr); if (!base) { DEBUG("Pexec: load_region failed"); detach_region(curproc, env); if (p) dispose_proc(p); return mint_errno; } } else { /* mode == 4 or 6 or 104 or 106 -- just go */ base = addr2mem((virtaddr)ptr2); if (base) env = addr2mem(*(void **)(base->loc + 0x2c)); else env = 0; if (!env) { if (p) dispose_proc(p); return EIMBA; } } /* 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 (p) { /* free the PROC struct so fork_proc will succeed */ dispose_proc(p); p = 0; } if (mkgo) { BASEPAGE *b; if (overlay) { p = curproc; /* make sure that exec_region doesn't free the base and env */ base->links++; env->links++; } else { p = fork_proc(); } if (!p) { if (mkbase) { detach_region(curproc, base); detach_region(curproc, env); } return mint_errno; } if (mkload && mkgo) { /* setuid/setgid is OK */ if (xattr.mode & S_ISUID) p->euid = xattr.uid; if (xattr.mode & S_ISGID) p->egid = xattr.gid; } (void)exec_region(p, base, thread); attach_region(p, env); attach_region(p, base); /* tell the child who the parent was */ b = (BASEPAGE *)base->loc; if (!overlay) b->p_parent = (BASEPAGE *)curproc->base; 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); } /* 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--; /* 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) { rm_q(WAIT_Q, p); add_q(READY_Q, p); } } /* OK, let's run our new code */ /* we guarantee ourselves at least 2 timeslices to do an Mshrink */ assert(curproc->magic == CTXT_MAGIC); fresh_slices(2); leave_kernel(); restore_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, 2); } } if (mkfree) { detach_region(curproc, base); detach_region(curproc, env); } if (mkwait) { long oldsigint, oldsigquit; oldsigint = curproc->sighandle[SIGINT]; oldsigquit = curproc->sighandle[SIGQUIT]; curproc->sighandle[SIGINT] = curproc->sighandle[SIGQUIT] = SIG_IGN; i = p->pid; for(;;) { r = p_wait3(0, (long *)0); if (r < 0) { ALERT("p_exec: wait error"); return EINTRN; } if ( i == ((r&0xffff0000) >> 16) ) { TRACE("leaving Pexec with child return code"); r = r & 0x0000ffff; break; } if (curproc->pid) DEBUG("Pexec: wrong child found"); } curproc->sighandle[SIGINT] = oldsigint; curproc->sighandle[SIGQUIT] = oldsigquit; return r; } else if (mkgo) { yield(); /* let the new process run */ return p->pid; } else { 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 */ long terminate(code, que) int code, que; { extern PROC *dlockproc[]; /* in dosdir.c */ PROC *p; FILEPTR *fp; MEMREGION *m; int i, wakemint = 0; 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 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); } } /* 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; } /* close all files */ for (i = MIN_HANDLE; i < MAX_OPEN; i++) { if ((fp = curproc->handle[i])) 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 all semaphores owned by this process */ free_semaphores(curproc->pid); /* free all memory */ 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) { if (curproc != rootproc) attach_region(rootproc, m); } m->links--; if (m->links == 0) { free_region(m); } } } kfree(curproc->mem); kfree(curproc->addr); curproc->mem = 0; curproc->addr = 0; curproc->num_reg = 0; } /* else make TSR process non-swappable */ /* * make sure that any open files that refer to this process are * closed */ changedrv(PROC_BASE_DEV | 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_wait3) ) { TRACE("terminate: waking up parent"); rm_q(WAIT_Q, p); add_q(READY_Q, p); } post_sig(p, SIGCHLD); /* inform of process termination */ /* find our children, and orphan them */ 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 (wakemint) { p = rootproc; /* pid 0 */ if (p->wait_q == WAIT_Q) { rm_q(WAIT_Q, p); add_q(READY_Q, p); } } /* 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; for(;;) { sleep(que, (long)(unsigned)code); /* we shouldn't ever get here */ FATAL("terminate: sleep woke up when it shouldn't have"); } return 0; /* fake */ } /* * TOS process termination entry points: * p_term terminates the process with extreme prejuidice * p_termres lets the process hang around resident in memory, after * shrinking its transient program area to "save" bytes */ long 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, hence the * curproc->sigmask setting here. */ 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 p_term0() { return p_term(0); } long p_termres(save, code) long save; int code; { MEMREGION *m; TRACE("Ptermres(%ld, %d)", save, code); m = curproc->mem[1]; /* should be the basepage (0 is env.) */ if (m) { (void)shrink_region(m, save); } 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) */ long p_wait3(nohang, rusage) int nohang; long *rusage; { long r; PROC *p, *q; int ourpid; int found; TRACE("Pwait3"); 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) { 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("Pwait3: going to sleep"); sleep(WAIT_Q, (long)&p_wait3); } else { DEBUG("Pwait3: 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; *rusage++ = p->systime; } /* add child's resource usage to parent's */ if (p->wait_q == TSR_Q || p->wait_q == ZOMBIE_Q) { curproc->chldstime += p->systime; curproc->chldutime += p->usrtime; } /* if it was a TSR, mark it as having been found and return */ if (p->wait_q == TSR_Q) { p->ppid = -1; return r; } /* 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; } /* 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 */ rm_q(ZOMBIE_Q, p); 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_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 Pwait3(). */ long p_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; 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; assert(savesize >= 0); saveplace = (char *)alloc_region(alt, savesize); if (!saveplace) saveplace = (char *)alloc_region(core, savesize); 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; /* just to make sure it dies */ p->ctxt[CURRENT].ssp = (long)(p->stack + ISTKSIZE); p->pri = MAX_NICE+1; run_next(p, 1); yield(); return ENSMEM; } savemem = addr2mem((virtaddr)saveplace); assert(savemem); for (i = 0; i < curproc->num_reg; i++) { m = curproc->mem[i]; if (m && m != savemem) { 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; } } } } 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 = ~(1L << SIGKILL); add_q(READY_Q, p); /* put it on the ready queue */ sleep(WAIT_Q, (long)p); /* while we wait for it */ TRACE("do_vfork: parent waking up"); if (save) { TRACE("do_vfork: parent restoring memory"); saveplace = (char *)savemem->loc; for (i = 0; i < curproc->num_reg; i++) { m = curproc->mem[i]; if (m && (m != savemem)) { 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; } } } detach_region(curproc, savemem); } curproc->sigmask = sigmask; check_sigs(); /* did we get any signals while sleeping? */ return p->pid; } /* * 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 p_vfork() { return do_vfork(0); } long p_fork() { return do_vfork(1); }