ast@cs.vu.nl (Andy Tanenbaum) (07/01/87)
: This is a shar archive. Extract with sh, not csh. : This archive ends with exit, so do not worry about trailing junk. : --------------------------- cut here -------------------------- PATH=/bin:/usr/bin echo Extracting \f\o\r\k\e\x\i\t\.\c sed 's/^X//' > \f\o\r\k\e\x\i\t\.\c << '+ END-OF-FILE '\f\o\r\k\e\x\i\t\.\c X/* This file deals with creating processes (via FORK) and deleting them (via X * EXIT/WAIT). When a process forks, a new slot in the 'mproc' table is X * allocated for it, and a copy of the parent's core image is made for the X * child. Then the kernel and file system are informed. A process is removed X * from the 'mproc' table when two events have occurred: (1) it has exited or X * been killed by a signal, and (2) the parent has done a WAIT. If the process X * exits first, it continues to occupy a slot until the parent does a WAIT. X * X * The entry points into this file are: X * do_fork: perform the FORK system call X * do_mm_exit: perform the EXIT system call (by calling mm_exit()) X * mm_exit: actually do the exiting X * do_wait: perform the WAIT system call X */ X X#include "../h/const.h" X#include "../h/type.h" X#include "../h/callnr.h" X#include "../h/error.h" X#include "const.h" X#include "glo.h" X#include "mproc.h" X#include "param.h" X X#define LAST_FEW 2 /* last few slots reserved for superuser */ X XPRIVATE next_pid = INIT_PROC_NR+1; /* next pid to be assigned */ XPRIVATE process_group = 1; /* next process grp to be assigned */ X X/* Some C compilers require static declarations to precede their first use. */ X X/*===========================================================================* X * do_fork * X *===========================================================================*/ XPUBLIC int do_fork() X{ X/* The process pointed to by 'mp' has forked. Create a child process. */ X X register struct mproc *rmp; /* pointer to parent */ X register struct mproc *rmc; /* pointer to child */ X int i, child_nr, t; X char *sptr, *dptr; X long prog_bytes; X phys_clicks prog_clicks, child_base; X long parent_abs, child_abs; X extern phys_clicks alloc_mem(); X X /* If tables might fill up during FORK, don't even start since recovery half X * way through is such a nuisance. X */ X X rmp = mp; X if (procs_in_use == NR_PROCS) return(EAGAIN); X if (procs_in_use >= NR_PROCS - LAST_FEW && rmp->mp_effuid != 0)return(EAGAIN); X X /* Determine how much memory to allocate. */ X prog_clicks = (phys_clicks) rmp->mp_seg[T].mem_len + rmp->mp_seg[D].mem_len + X rmp->mp_seg[S].mem_len; X#ifdef i8088 X prog_clicks += rmp->mp_seg[S].mem_vir - rmp->mp_seg[D].mem_len; /* gap too */ X#endif X prog_bytes = (long) prog_clicks << CLICK_SHIFT; X if ( (child_base = alloc_mem(prog_clicks)) == NO_MEM) return(EAGAIN); X X /* Create a copy of the parent's core image for the child. */ X child_abs = (long) child_base << CLICK_SHIFT; X parent_abs = (long) rmp->mp_seg[T].mem_phys << CLICK_SHIFT; X i = mem_copy(ABS, 0, parent_abs, ABS, 0, child_abs, prog_bytes); X if ( i < 0) panic("do_fork can't copy", i); X X /* Find a slot in 'mproc' for the child process. A slot must exist. */ X for (rmc = &mproc[0]; rmc < &mproc[NR_PROCS]; rmc++) X if ( (rmc->mp_flags & IN_USE) == 0) break; X X /* Set up the child and its memory map; copy its 'mproc' slot from parent. */ X child_nr = rmc - mproc; /* slot number of the child */ X procs_in_use++; X sptr = (char *) rmp; /* pointer to parent's 'mproc' slot */ X dptr = (char *) rmc; /* pointer to child's 'mproc' slot */ X i = sizeof(struct mproc); /* number of bytes in a proc slot. */ X while (i--) *dptr++ = *sptr++;/* copy from parent slot to child's */ X X /* Set process group. */ X if (who == INIT_PROC_NR) rmc->mp_procgrp = process_group++; X X rmc->mp_parent = who; /* record child's parent */ X rmc->mp_seg[T].mem_phys = child_base; X rmc->mp_seg[D].mem_phys = child_base + rmc->mp_seg[T].mem_len; X rmc->mp_seg[S].mem_phys = rmc->mp_seg[D].mem_phys + X (rmp->mp_seg[S].mem_phys - rmp->mp_seg[D].mem_phys); X rmc->mp_exitstatus = 0; X rmc->mp_sigstatus = 0; X X /* Find a free pid for the child and put it in the table. */ X do { X t = 0; /* 't' = 0 means pid still free */ X next_pid = (next_pid < 30000 ? next_pid + 1 : INIT_PROC_NR + 1); X for (rmp = &mproc[0]; rmp < &mproc[NR_PROCS]; rmp++) X if (rmp->mp_pid == next_pid) { X t = 1; X break; X } X rmc->mp_pid = next_pid; /* assign pid to child */ X } while (t); X X /* Tell kernel and file system about the (now successful) FORK. */ X sys_fork(who, child_nr, rmc->mp_pid); X tell_fs(FORK, who, child_nr, 0); X X /* Report child's memory map to kernel. */ X sys_newmap(child_nr, rmc->mp_seg); X X /* Reply to child to wake it up. */ X reply(child_nr, 0, 0, NIL_PTR); X return(next_pid); /* child's pid */ X} X X X/*===========================================================================* X * do_mm_exit * X *===========================================================================*/ XPUBLIC int do_mm_exit() X{ X/* Perform the exit(status) system call. The real work is done by mm_exit(), X * which is also called when a process is killed by a signal. X */ X X mm_exit(mp, status); X dont_reply = TRUE; /* don't reply to newly terminated process */ X return(OK); /* pro forma return code */ X} X X X/*===========================================================================* X * mm_exit * X *===========================================================================*/ XPUBLIC mm_exit(rmp, exit_status) Xregister struct mproc *rmp; /* pointer to the process to be terminated */ Xint exit_status; /* the process' exit status (for parent) */ X{ X/* A process is done. If parent is waiting for it, clean it up, else hang. */ X X /* How to terminate a process is determined by whether or not the X * parent process has already done a WAIT. Test to see if it has. X */ X rmp->mp_exitstatus = (char) exit_status; /* store status in 'mproc' */ X X if (mproc[rmp->mp_parent].mp_flags & WAITING) X cleanup(rmp); /* release parent and tell everybody */ X else X rmp->mp_flags |= HANGING; /* Parent not waiting. Suspend proc */ X X /* If the exited process has a timer pending, kill it. */ X if (rmp->mp_flags & ALARM_ON) set_alarm(rmp - mproc, (unsigned) 0); X X /* Tell the kernel and FS that the process is no longer runnable. */ X sys_xit(rmp->mp_parent, rmp - mproc); X tell_fs(EXIT, rmp - mproc, 0, 0); /* file system can free the proc slot */ X} X X X/*===========================================================================* X * do_wait * X *===========================================================================*/ XPUBLIC int do_wait() X{ X/* A process wants to wait for a child to terminate. If one is already waiting, X * go clean it up and let this WAIT call terminate. Otherwise, really wait. X */ X X register struct mproc *rp; X register int children; X X /* A process calling WAIT never gets a reply in the usual way via the X * reply() in the main loop. If a child has already exited, the routine X * cleanup() sends the reply to awaken the caller. X */ X X /* Is there a child waiting to be collected? */ X children = 0; X for (rp = &mproc[0]; rp < &mproc[NR_PROCS]; rp++) { X if ( (rp->mp_flags & IN_USE) && rp->mp_parent == who) { X children++; X if (rp->mp_flags & HANGING) { X cleanup(rp); /* a child has already exited */ X dont_reply = TRUE; X return(OK); X } X } X } X X /* No child has exited. Wait for one, unless none exists. */ X if (children > 0) { /* does this process have any children? */ X mp->mp_flags |= WAITING; X dont_reply = TRUE; X return(OK); /* yes - wait for one to exit */ X } else X return(ECHILD); /* no - parent has no children */ X} X X X/*===========================================================================* X * cleanup * X *===========================================================================*/ XPRIVATE cleanup(child) Xregister struct mproc *child; /* tells which process is exiting */ X{ X/* Clean up the remains of a process. This routine is only called if two X * conditions are satisfied: X * 1. The process has done an EXIT or has been killed by a signal. X * 2. The process' parent has done a WAIT. X * X * It releases the memory, if that has not been done yet. Whether it has or X * has not been done depends on the order of the EXIT and WAIT calls. X */ X X register struct mproc *parent, *rp; X int init_waiting, child_nr; X unsigned int r; X phys_clicks s; X X child_nr = child - mproc; X parent = &mproc[child->mp_parent]; X X /* Wakeup the parent. */ X r = child->mp_sigstatus & 0377; X r = r | (child->mp_exitstatus << 8); X reply(child->mp_parent, child->mp_pid, r, NIL_PTR); X X /* Release the memory occupied by the child. */ X s = (phys_clicks) child->mp_seg[S].mem_vir + child->mp_seg[S].mem_len; X if (child->mp_flags & SEPARATE) s += child->mp_seg[T].mem_len; X free_mem(child->mp_seg[T].mem_phys, s); /* free the memory */ X X /* Update flags. */ X child->mp_flags &= ~HANGING; /* turn off HANGING bit */ X child->mp_flags &= ~PAUSED; /* turn off PAUSED bit */ X parent->mp_flags &= ~WAITING; /* parent is no longer waiting */ X child->mp_flags &= ~IN_USE; /* release the table slot */ X procs_in_use--; X X /* If exiting process has children, disinherit them. INIT is new parent. */ X init_waiting = (mproc[INIT_PROC_NR].mp_flags & WAITING ? 1 : 0); X for (rp = &mproc[0]; rp < &mproc[NR_PROCS]; rp++) { X if (rp->mp_parent == child_nr) { X /* 'rp' points to a child to be disinherited. */ X rp->mp_parent = INIT_PROC_NR; /* init takes over */ X if (init_waiting && (rp->mp_flags & HANGING) ) { X /* Init was waiting. */ X cleanup(rp); /* recursive call */ X init_waiting = 0; X } X } X } X} + END-OF-FILE forkexit.c chmod 'u=rw,g=r,o=r' \f\o\r\k\e\x\i\t\.\c set `sum \f\o\r\k\e\x\i\t\.\c` sum=$1 case $sum in 48964) :;; *) echo 'Bad sum in '\f\o\r\k\e\x\i\t\.\c >&2 esac echo Extracting \s\i\g\n\a\l\.\c sed 's/^X//' > \s\i\g\n\a\l\.\c << '+ END-OF-FILE '\s\i\g\n\a\l\.\c X/* This file handles signals, which are asynchronous events and are generally X * a messy and unpleasant business. Signals can be generated by the KILL X * system call, or from the keyboard (SIGINT) or from the clock (SIGALRM). X * In all cases control eventually passes to check_sig() to see which processes X * can be signalled. The actual signalling is done by sig_proc(). X * X * The entry points into this file are: X * do_signal: perform the SIGNAL system call X * do_kill: perform the KILL system call X * do_ksig: accept a signal originating in the kernel (e.g., SIGINT) X * sig_proc: interrupt or terminate a signalled process X * do_alarm: perform the ALARM system call by calling set_alarm() X * set_alarm: tell the clock task to start or stop a timer X * do_pause: perform the PAUSE system call X * unpause: check to see if process is suspended on anything X */ X X#include "../h/const.h" X#include "../h/type.h" X#include "../h/callnr.h" X#include "../h/com.h" X#include "../h/error.h" X#include "../h/signal.h" X#include "../h/stat.h" X#include "const.h" X#include "glo.h" X#include "mproc.h" X#include "param.h" X X#define DUMP_SIZE 256 /* buffer size for core dumps */ X#define CORE_MODE 0777 /* mode to use on core image files */ X#define DUMPED 0200 /* bit set in status when core dumped */ X XPRIVATE message m_sig; X X/*===========================================================================* X * do_signal * X *===========================================================================*/ XPUBLIC int do_signal() X{ X/* Perform the signal(sig, func) call by setting bits to indicate that a signal X * is to be caught or ignored. X */ X X int mask; X X if (sig < 1 || sig > NR_SIGS) return(EINVAL); X if (sig == SIGKILL) return(OK); /* SIGKILL may not ignored/caught */ X mask = 1 << (sig - 1); /* singleton set with 'sig' bit on */ X X /* All this func does is set the bit maps for subsequent sig processing. */ X if (func == SIG_IGN) { X mp->mp_ignore |= mask; X mp->mp_catch &= ~mask; X } else if (func == SIG_DFL) { X mp->mp_ignore &= ~mask; X mp->mp_catch &= ~mask; X } else { X mp->mp_ignore &= ~mask; X mp->mp_catch |= mask; X mp->mp_func = func; X } X return(OK); X} X X X/*===========================================================================* X * do_kill * X *===========================================================================*/ XPUBLIC int do_kill() X{ X/* Perform the kill(pid, kill_sig) system call. */ X X return check_sig(pid, kill_sig, mp->mp_effuid); X} X X X/*===========================================================================* X * do_ksig * X *===========================================================================*/ XPUBLIC int do_ksig() X{ X/* Certain signals, such as segmentation violations and DEL, originate in the X * kernel. When the kernel detects such signals, it sets bits in a bit map. X * As soon is MM is awaiting new work, the kernel sends MM a message containing X * the process slot and bit map. That message comes here. The File System X * also uses this mechanism to signal writing on broken pipes (SIGPIPE). X */ X X register struct mproc *rmp; X int i, proc_id, proc_nr, id; X unshort sig_map; /* bits 0 - 15 for sigs 1 - 16 */ X X /* Only kernel and FS may make this call. */ X if (who != HARDWARE && who != FS_PROC_NR) return(EPERM); X X proc_nr = mm_in.PROC1; X rmp = &mproc[proc_nr]; X if ( (rmp->mp_flags & IN_USE) == 0 || (rmp->mp_flags & HANGING) ) return(OK); X proc_id = rmp->mp_pid; X sig_map = (unshort) mm_in.SIG_MAP; X mp = &mproc[0]; /* pretend kernel signals are from MM */ X mp->mp_procgrp = rmp->mp_procgrp; /* get process group right */ X X /* Stack faults are passed from kernel to MM as pseudo-signal 16. */ X if (sig_map == 1 << (STACK_FAULT - 1)) { X stack_fault(proc_nr); X return(OK); X } X X /* Check each bit in turn to see if a signal is to be sent. Unlike X * kill(), the kernel may collect several unrelated signals for a process X * and pass them to MM in one blow. Thus loop on the bit map. For SIGINT X * and SIGQUIT, use proc_id 0, since multiple processes may have to signalled. X */ X for (i = 0; i < NR_SIGS; i++) { X id = (i+1 == SIGINT || i+1 == SIGQUIT ? 0 : proc_id); X if ( (sig_map >> i) & 1) check_sig(id, i + 1, SUPER_USER); X } X X dont_reply = TRUE; /* don't reply to the kernel */ X return(OK); X} X X X/*===========================================================================* X * check_sig * X *===========================================================================*/ XPRIVATE int check_sig(proc_id, sig_nr, send_uid) Xint proc_id; /* pid of process to signal, or 0 or -1 */ Xint sig_nr; /* which signal to send (1-16) */ Xuid send_uid; /* identity of process sending the signal */ X{ X/* Check to see if it is possible to send a signal. The signal may have to be X * sent to a group of processes. This routine is invoked by the KILL system X * call, and also when the kernel catches a DEL or other signal. SIGALRM too. X */ X X register struct mproc *rmp; X int count, send_sig; X unshort mask; X extern unshort core_bits; X X if (sig_nr < 1 || sig_nr > NR_SIGS) return(EINVAL); X count = 0; /* count # of signals sent */ X mask = 1 << (sig_nr - 1); X X /* Search the proc table for processes to signal. Several tests are made: X * - if proc's uid != sender's, and sender is not superuser, don't signal X * - if specific process requested (i.e., 'procpid' > 0, check for match X * - if a process has already exited, it can't receive signals X * - if 'proc_id' is 0 signal everyone in same process group except caller X */ X for (rmp = &mproc[INIT_PROC_NR + 1]; rmp < &mproc[NR_PROCS]; rmp++ ) { X if ( (rmp->mp_flags & IN_USE) == 0) continue; X send_sig = TRUE; /* if it's FALSE at end of loop, don't signal */ X if (send_uid != rmp->mp_effuid && send_uid != SUPER_USER)send_sig=FALSE; X if (proc_id > 0 && proc_id != rmp->mp_pid) send_sig = FALSE; X if (rmp->mp_flags & HANGING) send_sig = FALSE; /*don't wake the dead*/ X if (proc_id == 0 && mp->mp_procgrp != rmp->mp_procgrp) send_sig = FALSE; X if (send_uid == SUPER_USER && proc_id == -1) send_sig = TRUE; X X /* SIGALARM is a little special. When a process exits, a clock signal X * can arrive just as the timer is being turned off. Also, turn off X * ALARM_ON bit when timer goes off to keep it accurate. X */ X if (sig_nr == SIGALRM) { X if ( (rmp->mp_flags & ALARM_ON) == 0) continue; X rmp->mp_flags &= ~ALARM_ON; X } X X if (send_sig == FALSE || rmp->mp_ignore & mask) continue; X count++; X X /* Send the signal or kill the process, possibly with core dump. */ X sig_proc(rmp, sig_nr); X X /* If process is hanging on PAUSE, WAIT, tty, pipe, etc. release it. */ X unpause(rmp - mproc); /* check to see if process is paused */ X if (proc_id > 0) break; /* only one process being signalled */ X } X X /* If the calling process has killed itself, don't reply. */ X if ((mp->mp_flags & IN_USE) == 0 || (mp->mp_flags & HANGING))dont_reply =TRUE; X return(count > 0 ? OK : ESRCH); X} X X X/*===========================================================================* X * sig_proc * X *===========================================================================*/ XPUBLIC sig_proc(rmp, sig_nr) Xregister struct mproc *rmp; /* pointer to the process to be signalled */ Xint sig_nr; /* signal to send to process (1-16) */ X{ X/* Send a signal to a process. Check to see if the signal is to be caught. X * If so, the pc, psw, and signal number are to be pushed onto the process' X * stack. If the stack cannot grow or the signal is not to be caught, kill X * the process. X */ X X unshort mask; X int core_file; X vir_bytes new_sp; X extern unshort core_bits; X X if ( (rmp->mp_flags & IN_USE) == 0) return; /* if already dead forget it */ X mask = 1 << (sig_nr - 1); X if (rmp->mp_catch & mask) { X /* Signal should be caught. */ X rmp->mp_catch &= ~mask; /* disable further signals */ X sys_getsp(rmp - mproc, &new_sp); X new_sp -= SIG_PUSH_BYTES; X if (adjust(rmp, rmp->mp_seg[D].mem_len, new_sp) == OK) { X sys_sig(rmp - mproc, sig_nr, rmp->mp_func); X return; /* successful signal */ X } X } X X /* Signal should not or cannot be caught. Take default action. */ X core_file = ( core_bits >> (sig_nr - 1 )) & 1; X rmp->mp_sigstatus = (char) sig_nr; X if (core_file) dump_core(rmp); /* dump core */ X mm_exit(rmp, 0); /* terminate process */ X} X X X/*===========================================================================* X * do_alarm * X *===========================================================================*/ XPUBLIC int do_alarm() X{ X/* Perform the alarm(seconds) system call. */ X X register int r; X unsigned sec; X X sec = (unsigned) seconds; X r = set_alarm(who, sec); X return(r); X} X X X/*===========================================================================* X * set_alarm * X *===========================================================================*/ XPUBLIC int set_alarm(proc_nr, sec) Xint proc_nr; /* process that wants the alarm */ Xunsigned sec; /* how many seconds delay before the signal */ X{ X/* This routine is used by do_alarm() to set the alarm timer. It is also X * to turn the timer off when a process exits with the timer still on. X */ X X int remaining; X X m_sig.m_type = SET_ALARM; X m_sig.CLOCK_PROC_NR = proc_nr; X m_sig.DELTA_TICKS = HZ * sec; X if (sec != 0) X mproc[proc_nr].mp_flags |= ALARM_ON; /* turn ALARM_ON bit on */ X else X mproc[proc_nr].mp_flags &= ~ALARM_ON; /* turn ALARM_ON bit off */ X X /* Tell the clock task to provide a signal message when the time comes. */ X if (sendrec(CLOCK, &m_sig) != OK) panic("alarm er", NO_NUM); X remaining = (int) m_sig.SECONDS_LEFT; X return(remaining); X} X X X/*===========================================================================* X * do_pause * X *===========================================================================*/ XPUBLIC int do_pause() X{ X/* Perform the pause() system call. */ X X mp->mp_flags |= PAUSED; /* turn on PAUSE bit */ X dont_reply = TRUE; X return(OK); X} X X X/*===========================================================================* X * unpause * X *===========================================================================*/ XPUBLIC unpause(pro) Xint pro; /* which process number */ X{ X/* A signal is to be sent to a process. It that process is hanging on a X * system call, the system call must be terminated with EINTR. Possible X * calls are PAUSE, WAIT, READ and WRITE, the latter two for pipes and ttys. X * First check if the process is hanging on PAUSE or WAIT. If not, tell FS, X * so it can check for READs and WRITEs from pipes, ttys and the like. X */ X X register struct mproc *rmp; X X rmp = &mproc[pro]; X X /* Check to see if process is hanging on PAUSE call. */ X if ( (rmp->mp_flags & PAUSED) && (rmp->mp_flags & HANGING) == 0) { X rmp->mp_flags &= ~PAUSED; /* turn off PAUSED bit */ X reply(pro, EINTR, 0, NIL_PTR); X return; X } X X /* Check to see if process is hanging on a WAIT call. */ X if ( (rmp->mp_flags & WAITING) && (rmp->mp_flags & HANGING) == 0) { X rmp->mp_flags &= ~ WAITING; /* turn off WAITING bit */ X reply(pro, EINTR, 0, NIL_PTR); X return; X } X X /* Process is not hanging on an MM call. Ask FS to take a look. */ X tell_fs(UNPAUSE, pro, 0, 0); X X return; X} X X X/*===========================================================================* X * dump_core * X *===========================================================================*/ XPRIVATE dump_core(rmp) Xregister struct mproc *rmp; /* whose core is to be dumped */ X{ X/* Make a core dump on the file "core", if possible. */ X X struct stat s_buf, d_buf; X char buf[DUMP_SIZE]; X int i, r, s, er1, er2, slot; X vir_bytes v_buf; X long len, a, c, ct, dest; X struct mproc *xmp; X extern char core_name[]; X X X /* Change to working directory of dumpee. */ X slot = rmp - mproc; X tell_fs(CHDIR, slot, 0, 0); X X /* Can core file be written? */ X if (rmp->mp_realuid != rmp->mp_effuid) { X tell_fs(CHDIR, 0, 1, 0); /* go back to MM's directory */ X return; X } X xmp = mp; /* allowed() looks at 'mp' */ X mp = rmp; X r = allowed(core_name, &s_buf, W_BIT); /* is core_file writable */ X s = allowed(".", &d_buf, W_BIT); /* is directory writable? */ X mp = xmp; X if (r >= 0) close(r); X if (s >= 0) close(s); X if (rmp->mp_effuid == SUPER_USER) r = 0; /* su can always dump core */ X X if (s >= 0 && (r >= 0 || r == ENOENT)) { X /* Either file is writable or it doesn't exist & dir is writable */ X r = creat(core_name, CORE_MODE); X tell_fs(CHDIR, 0, 1, 0); /* go back to MM's own dir */ X if (r < 0) return; X rmp->mp_sigstatus |= DUMPED; X X /* First loop through segments and write each length on core file. */ X for (i = 0; i < NR_SEGS; i++) { X len = rmp->mp_seg[i].mem_len << CLICK_SHIFT; X if (write(r, (char *) &len, sizeof len) < 0) { X close(r); X return; X } X } X X /* Now loop through segments and write the segments themselves out. */ X v_buf = (vir_bytes) buf; X dest = (long) v_buf; X for (i = 0; i < NR_SEGS; i++) { X a = (phys_bytes) rmp->mp_seg[i].mem_vir << CLICK_SHIFT; X c = (phys_bytes) rmp->mp_seg[i].mem_len << CLICK_SHIFT; X X /* Loop through a segment, dumping it. */ X while (c > 0) { X ct = MIN(c, DUMP_SIZE); X er1 = mem_copy(slot, i, a, MM_PROC_NR, D, dest, ct); X er2 = write(r, buf, (int) ct); X if (er1 < 0 || er2 < 0) { X close(r); X return; X } X a += ct; X c -= ct; X } X } X } else { X tell_fs(CHDIR, 0, 1, 0); /* go back to MM's own dir */ X close(r); X return; X } X X close(r); X} + END-OF-FILE signal.c chmod 'u=rw,g=r,o=r' \s\i\g\n\a\l\.\c set `sum \s\i\g\n\a\l\.\c` sum=$1 case $sum in 09347) :;; *) echo 'Bad sum in '\s\i\g\n\a\l\.\c >&2 esac exit 0