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 \l\i\n\k\.\c
sed 's/^X//' > \l\i\n\k\.\c << '+ END-OF-FILE '\l\i\n\k\.\c
X/* This file handles the LINK and UNLINK system calls. It also deals with
X * deallocating the storage used by a file when the last UNLINK is done to a
X * file and the blocks must be returned to the free block pool.
X *
X * The entry points into this file are
X * do_link: perform the LINK system call
X * do_unlink: perform the UNLINK system call
X * truncate: release all the blocks associated with an inode
X */
X
X#include "../h/const.h"
X#include "../h/type.h"
X#include "../h/error.h"
X#include "const.h"
X#include "type.h"
X#include "buf.h"
X#include "file.h"
X#include "fproc.h"
X#include "glo.h"
X#include "inode.h"
X#include "param.h"
X
X/*===========================================================================*
X * do_link *
X *===========================================================================*/
XPUBLIC int do_link()
X{
X/* Perform the link(name, name2) system call. */
X
X register struct inode *ip, *rip;
X register int r;
X char string[NAME_SIZE];
X struct inode *new_ip;
X extern struct inode *advance(), *last_dir(), *eat_path();
X
X /* See if 'name' (file to be linked) exists. */
X if (fetch_name(name1, name1_length, M1) != OK) return(err_code);
X if ( (rip = eat_path(user_path)) == NIL_INODE) return(err_code);
X
X /* Check to see if the file has maximum number of links already. */
X r = OK;
X if ( (rip->i_nlinks & BYTE) == MAX_LINKS) r = EMLINK;
X
X /* Only super_user may link to directories. */
X if (r == OK)
X if ( (rip->i_mode & I_TYPE) == I_DIRECTORY && !super_user) r = EPERM;
X
X /* If error with 'name', return the inode. */
X if (r != OK) {
X put_inode(rip);
X return(r);
X }
X
X /* Does the final directory of 'name2' exist? */
X if (fetch_name(name2, name2_length, M1) != OK) {
X put_inode(rip);
X return(err_code);
X }
X if ( (ip = last_dir(user_path, string)) == NIL_INODE) r = err_code;
X
X /* If 'name2' exists in full (even if no space) set 'r' to error. */
X if (r == OK) {
X if ( (new_ip = advance(ip, string)) == NIL_INODE) {
X r = err_code;
X if (r == ENOENT) r = OK;
X } else {
X put_inode(new_ip);
X r = EEXIST;
X }
X }
X
X /* Check for links across devices. */
X if (r == OK)
X if (rip->i_dev != ip->i_dev) r = EXDEV;
X
X /* Try to link. */
X if (r == OK)
X r = search_dir(ip, string, &rip->i_num, ENTER);
X
X /* If success, register the linking. */
X if (r == OK) {
X rip->i_nlinks++;
X rip->i_dirt = DIRTY;
X }
X
X /* Done. Release both inodes. */
X put_inode(rip);
X put_inode(ip);
X return(r);
X}
X
X
X/*===========================================================================*
X * do_unlink *
X *===========================================================================*/
XPUBLIC int do_unlink()
X{
X/* Perform the unlink(name) system call. */
X
X register struct inode *rip, *rlast_dir_ptr;
X register int r;
X inode_nr numb;
X char string[NAME_SIZE];
X extern struct inode *advance(), *last_dir();
X
X /* Get the last directory in the path. */
X if (fetch_name(name, name_length, M3) != OK) return(err_code);
X if ( (rlast_dir_ptr = last_dir(user_path, string)) == NIL_INODE)
X return(err_code);
X
X /* The last directory exists. Does the file also exist? */
X r = OK;
X if ( (rip = advance(rlast_dir_ptr, string)) == NIL_INODE) r = err_code;
X
X /* If error, return inode. */
X if (r != OK) {
X put_inode(rlast_dir_ptr);
X return(r);
X }
X
X /* See if the file is a directory. */
X if ( (rip->i_mode & I_TYPE) == I_DIRECTORY && !super_user)
X r = EPERM; /* only super_user can unlink directory */
X if (r == OK)
X r = search_dir(rlast_dir_ptr, string, &numb, DELETE);
X
X if (r == OK) {
X rip->i_nlinks--;
X rip->i_dirt = DIRTY;
X }
X
X /* If unlink was possible, it has been done, otherwise it has not. */
X put_inode(rip);
X put_inode(rlast_dir_ptr);
X return(r);
X}
X
X
X/*===========================================================================*
X * truncate *
X *===========================================================================*/
XPUBLIC truncate(rip)
Xregister struct inode *rip; /* pointer to inode to be truncated */
X{
X/* Remove all the zones from the inode 'rip' and mark it dirty. */
X
X register file_pos position;
X register zone_type zone_size;
X register block_nr b;
X register zone_nr z, *iz;
X register int scale;
X register struct buf *bp;
X register dev_nr dev;
X extern struct buf *get_block();
X extern block_nr read_map();
X
X dev = rip->i_dev; /* device on which inode resides */
X scale = scale_factor(rip);
X zone_size = (zone_type) BLOCK_SIZE << scale;
X if (rip->i_pipe == I_PIPE) rip->i_size = PIPE_SIZE; /* pipes can shrink */
X
X /* Step through the file a zone at a time, finding and freeing the zones. */
X for (position = 0; position < rip->i_size; position += zone_size) {
X if ( (b = read_map(rip, position)) != NO_BLOCK) {
X z = (zone_nr) b >> scale;
X free_zone(dev, z);
X }
X }
X
X /* All the data zones have been freed. Now free the indirect zones. */
X free_zone(dev, rip->i_zone[NR_DZONE_NUM]); /* single indirect zone */
X if ( (z = rip->i_zone[NR_DZONE_NUM+1]) != NO_ZONE) {
X b = (block_nr) z << scale;
X bp = get_block(dev, b, NORMAL); /* get double indirect zone */
X for (iz = &bp->b_ind[0]; iz < &bp->b_ind[NR_INDIRECTS]; iz++) {
X free_zone(dev, *iz);
X }
X
X /* Now free the double indirect zone itself. */
X put_block(bp, INDIRECT_BLOCK);
X free_zone(dev, z);
X }
X
X /* The inode being truncated might currently be open, so certain fields must
X * be cleared immediately, even though these fields are also cleared by
X * alloc_inode(). The function wipe_inode() does the dirty work in both cases.
X */
X wipe_inode(rip);
X}
+ END-OF-FILE link.c
chmod 'u=rw,g=r,o=r' \l\i\n\k\.\c
set `sum \l\i\n\k\.\c`
sum=$1
case $sum in
36084) :;;
*) echo 'Bad sum in '\l\i\n\k\.\c >&2
esac
echo Extracting \m\i\s\c\.\c
sed 's/^X//' > \m\i\s\c\.\c << '+ END-OF-FILE '\m\i\s\c\.\c
X/* This file contains a collection of miscellaneous procedures. Some of them
X * perform simple system calls. Some others do a little part of system calls
X * that are mostly performed by the Memory Manager.
X *
X * The entry points into this file are
X * do_dup: perform the DUP system call
X * do_sync: perform the SYNC system call
X * do_fork: adjust the tables after MM has performed a FORK system call
X * do_exit: a process has exited; note that in the tables
X * do_set: set uid or gid for some process
X * do_revive: revive a process that was waiting for something (e.g. TTY)
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 "const.h"
X#include "type.h"
X#include "buf.h"
X#include "dev.h"
X#include "file.h"
X#include "fproc.h"
X#include "glo.h"
X#include "inode.h"
X#include "param.h"
X#include "super.h"
X
X/*===========================================================================*
X * do_dup *
X *===========================================================================*/
XPUBLIC int do_dup()
X{
X/* Perform the dup(fd) or dup(fd,fd2) system call. */
X
X register int rfd;
X register struct fproc *rfp;
X struct filp *dummy;
X int r;
X extern struct filp *get_filp();
X
X /* Is the file descriptor valid? */
X rfd = fd & ~DUP_MASK; /* kill off dup2 bit, if on */
X rfp = fp;
X if (get_filp(rfd) == NIL_FILP) return(err_code);
X
X /* Distinguish between dup and dup2. */
X if (fd == rfd) { /* bit not on */
X /* dup(fd) */
X if ( (r = get_fd(0, &fd2, &dummy)) != OK) return(r);
X } else {
X /* dup2(fd, fd2) */
X if (fd2 < 0 || fd2 >= NR_FDS) return(EBADF);
X if (rfd == fd2) return(fd2); /* ignore the call: dup2(x, x) */
X fd = fd2; /* prepare to close fd2 */
X do_close(); /* cannot fail */
X }
X
X /* Success. Set up new file descriptors. */
X rfp->fp_filp[fd2] = rfp->fp_filp[rfd];
X rfp->fp_filp[fd2]->filp_count++;
X return(fd2);
X}
X
X
X/*===========================================================================*
X * do_sync *
X *===========================================================================*/
XPUBLIC int do_sync()
X{
X/* Perform the sync() system call. Flush all the tables. */
X
X register struct inode *rip;
X register struct buf *bp;
X register struct super_block *sp;
X dev_nr d;
X extern real_time clock_time();
X extern struct super_block *get_super();
X
X /* The order in which the various tables are flushed is critical. The
X * blocks must be flushed last, since rw_inode() and rw_super() leave their
X * results in the block cache.
X */
X
X /* Update the time in the root super_block. */
X sp = get_super(ROOT_DEV);
X sp->s_time = clock_time();
X if (sp->s_rd_only == FALSE) sp->s_dirt = DIRTY;
X
X /* Write all the dirty inodes to the disk. */
X for (rip = &inode[0]; rip < &inode[NR_INODES]; rip++)
X if (rip->i_count > 0 && rip->i_dirt == DIRTY) rw_inode(rip, WRITING);
X
X /* Write all the dirty super_blocks to the disk. */
X for (sp = &super_block[0]; sp < &super_block[NR_SUPERS]; sp++)
X if (sp->s_dev != NO_DEV && sp->s_dirt == DIRTY) rw_super(sp, WRITING);
X
X /* Write all the dirty blocks to the disk. First do drive 0, then the rest.
X * This avoids starting drive 0, then starting drive 1, etc.
X */
X for (bp = &buf[0]; bp < &buf[NR_BUFS]; bp++) {
X d = bp->b_dev;
X if (d != NO_DEV && bp->b_dirt == DIRTY && ((d>>MINOR) & BYTE) == 0)
X rw_block(bp, WRITING);
X }
X
X for (bp = &buf[0]; bp < &buf[NR_BUFS]; bp++) {
X d = bp->b_dev;
X if (d != NO_DEV && bp->b_dirt == DIRTY && ((d>>MINOR) & BYTE) != 0)
X rw_block(bp, WRITING);
X }
X
X return(OK); /* sync() can't fail */
X}
X
X
X/*===========================================================================*
X * do_fork *
X *===========================================================================*/
XPUBLIC int do_fork()
X{
X/* Perform those aspects of the fork() system call that relate to files.
X * In particular, let the child inherit its parents file descriptors.
X * The parent and child parameters tell who forked off whom. The file
X * system uses the same slot numbers as the kernel. Only MM makes this call.
X */
X
X register struct fproc *cp;
X register char *sptr, *dptr;
X int i;
X
X /* Only MM may make this call directly. */
X if (who != MM_PROC_NR) return(ERROR);
X
X /* Copy the parent's fproc struct to the child. */
X sptr = (char *) &fproc[parent]; /* pointer to parent's 'fproc' struct */
X dptr = (char *) &fproc[child]; /* pointer to child's 'fproc' struct */
X i = sizeof(struct fproc); /* how many bytes to copy */
X while (i--) *dptr++ = *sptr++; /* fproc[child] = fproc[parent] */
X
X /* Increase the counters in the 'filp' table. */
X cp = &fproc[child];
X for (i = 0; i < NR_FDS; i++)
X if (cp->fp_filp[i] != NIL_FILP) cp->fp_filp[i]->filp_count++;
X
X /* Record the fact that both root and working dir have another user. */
X dup_inode(cp->fp_rootdir);
X dup_inode(cp->fp_workdir);
X return(OK);
X}
X
X
X/*===========================================================================*
X * do_exit *
X *===========================================================================*/
XPUBLIC int do_exit()
X{
X/* Perform the file system portion of the exit(status) system call. */
X
X register int i, exitee;
X
X /* Only MM may do the EXIT call directly. */
X if (who != MM_PROC_NR) return(ERROR);
X
X /* Nevertheless, pretend that the call came from the user. */
X fp = &fproc[slot1]; /* get_filp() needs 'fp' */
X exitee = slot1;
X
X if (fp->fp_suspended == SUSPENDED) {
X if (fp->fp_task == XPIPE) susp_count--;
X pro = exitee;
X do_unpause();
X fp->fp_suspended = NOT_SUSPENDED;
X }
X
X /* Loop on file descriptors, closing any that are open. */
X for (i=0; i < NR_FDS; i++) {
X fd = i;
X do_close();
X }
X
X /* Release root and working directories. */
X put_inode(fp->fp_rootdir);
X put_inode(fp->fp_workdir);
X
X return(OK);
X}
X
X
X/*===========================================================================*
X * do_set *
X *===========================================================================*/
XPUBLIC int do_set()
X{
X/* Set uid or gid field. */
X
X register struct fproc *tfp;
X
X /* Only MM may make this call directly. */
X if (who != MM_PROC_NR) return(ERROR);
X
X tfp = &fproc[slot1];
X if (fs_call == SETUID) {
X tfp->fp_realuid = (uid) real_user_id;
X tfp->fp_effuid = (uid) eff_user_id;
X }
X if (fs_call == SETGID) {
X tfp->fp_effgid = (gid) eff_grp_id;
X tfp->fp_realgid = (gid) real_grp_id;
X }
X return(OK);
X}
X
X
X/*===========================================================================*
X * do_revive *
X *===========================================================================*/
XPUBLIC int do_revive()
X{
X/* A task, typically TTY, has now gotten the characters that were needed for a
X * previous read. The process did not get a reply when it made the call.
X * Instead it was suspended. Now we can send the reply to wake it up. This
X * business has to be done carefully, since the incoming message is from
X * a task (to which no reply can be sent), and the reply must go to a process
X * that blocked earlier. The reply to the caller is inhibited by setting the
X * 'dont_reply' flag, and the reply to the blocked process is done explicitly
X * in revive().
X */
X
X if (who > 0) return(EPERM);
X revive(m.REP_PROC_NR, m.REP_STATUS);
X dont_reply = TRUE; /* don't reply to the TTY task */
X return(OK);
X}
+ END-OF-FILE misc.c
chmod 'u=rw,g=r,o=r' \m\i\s\c\.\c
set `sum \m\i\s\c\.\c`
sum=$1
case $sum in
04234) :;;
*) echo 'Bad sum in '\m\i\s\c\.\c >&2
esac
echo Extracting \p\i\p\e\.\c
sed 's/^X//' > \p\i\p\e\.\c << '+ END-OF-FILE '\p\i\p\e\.\c
X/* This file deals with the suspension and revival of processes. A process can
X * be suspended because it wants to read or write from a pipe and can't, or
X * because it wants to read or write from a special file and can't. When a
X * process can't continue it is suspended, and revived later when it is able
X * to continue.
X *
X * The entry points into this file are
X * do_pipe: perform the PIPE system call
X * pipe_check: check to see that a read or write on a pipe is feasible now
X * suspend: suspend a process that cannot do a requested read or write
X * release: check to see if a suspended process can be released and do it
X * revive: mark a suspended process as able to run again
X * do_unpause: a signal has been sent to a process; see if it suspended
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 "const.h"
X#include "type.h"
X#include "file.h"
X#include "fproc.h"
X#include "glo.h"
X#include "inode.h"
X#include "param.h"
X
XPRIVATE message mess;
X
X/*===========================================================================*
X * do_pipe *
X *===========================================================================*/
XPUBLIC int do_pipe()
X{
X/* Perform the pipe(fil_des) system call. */
X
X register struct fproc *rfp;
X register struct inode *rip;
X int r;
X dev_nr device;
X struct filp *fil_ptr0, *fil_ptr1;
X int fil_des[2]; /* reply goes here */
X extern struct inode *alloc_inode();
X
X /* Acquire two file descriptors. */
X rfp = fp;
X if ( (r = get_fd(R_BIT, &fil_des[0], &fil_ptr0)) != OK) return(r);
X rfp->fp_filp[fil_des[0]] = fil_ptr0;
X fil_ptr0->filp_count = 1;
X if ( (r = get_fd(W_BIT, &fil_des[1], &fil_ptr1)) != OK) {
X rfp->fp_filp[fil_des[0]] = NIL_FILP;
X fil_ptr0->filp_count = 0;
X return(r);
X }
X rfp->fp_filp[fil_des[1]] = fil_ptr1;
X fil_ptr1->filp_count = 1;
X
X /* Make the inode in the current working directory. */
X device = rfp->fp_workdir->i_dev; /* inode dev is same as working dir */
X if ( (rip = alloc_inode(device, I_REGULAR)) == NIL_INODE) {
X rfp->fp_filp[fil_des[0]] = NIL_FILP;
X fil_ptr0->filp_count = 0;
X rfp->fp_filp[fil_des[1]] = NIL_FILP;
X fil_ptr1->filp_count = 0;
X return(err_code);
X }
X
X rip->i_pipe = I_PIPE;
X fil_ptr0->filp_ino = rip;
X dup_inode(rip); /* for double usage */
X fil_ptr1->filp_ino = rip;
X rw_inode(rip, WRITING); /* mark inode as allocated */
X reply_i1 = fil_des[0];
X reply_i2 = fil_des[1];
X return(OK);
X}
X
X
X/*===========================================================================*
X * pipe_check *
X *===========================================================================*/
XPUBLIC int pipe_check(rip, rw_flag, virgin, bytes, position)
Xregister struct inode *rip; /* the inode of the pipe */
Xint rw_flag; /* READING or WRITING */
Xint virgin; /* 1 if no data transferred yet, else 0 */
Xregister int bytes; /* bytes to be read or written (all chunks) */
Xregister file_pos *position; /* pointer to current file position */
X{
X/* Pipes are a little different. If a process reads from an empty pipe for
X * which a writer still exists, suspend the reader. If the pipe is empty
X * and there is no writer, return 0 bytes. If a process is writing to a
X * pipe and no one is reading from it, give a broken pipe error.
X */
X
X extern struct filp *find_filp();
X
X /* If reading, check for empty pipe. */
X if (rw_flag == READING) {
X if (*position >= rip->i_size) {
X /* Process is reading from an empty pipe. */
X if (find_filp(rip, W_BIT) != NIL_FILP) {
X /* Writer exists; suspend rdr if no data already read.*/
X if (virgin) suspend(XPIPE); /* block reader */
X
X /* If need be, activate sleeping writer. */
X if (susp_count > 0) release(rip, WRITE, 1);
X }
X return(0);
X }
X } else {
X /* Process is writing to a pipe. */
X if (bytes > PIPE_SIZE) return(EFBIG);
X if (find_filp(rip, R_BIT) == NIL_FILP) {
X /* Tell MM to generate a SIGPIPE signal. */
X mess.m_type = KSIG;
X mess.PROC1 = fp - fproc;
X mess.SIG_MAP = 1 << (SIGPIPE - 1);
X send(MM_PROC_NR, &mess);
X return(EPIPE);
X }
X
X if (*position + bytes > PIPE_SIZE) {
X suspend(XPIPE); /* stop writer -- pipe full */
X return(0);
X }
X
X /* Writing to an empty pipe. Search for suspended reader. */
X if (*position == 0) release(rip, READ, 1);
X }
X
X return(1);
X}
X
X
X/*===========================================================================*
X * suspend *
X *===========================================================================*/
XPUBLIC suspend(task)
Xint task; /* who is proc waiting for? (PIPE = pipe) */
X{
X/* Take measures to suspend the processing of the present system call.
X * Store the parameters to be used upon resuming in the process table.
X * (Actually they are not used when a process is waiting for an I/O device,
X * but they are needed for pipes, and it is not worth making the distinction.)
X */
X
X if (task == XPIPE) susp_count++; /* count procs suspended on pipe */
X fp->fp_suspended = SUSPENDED;
X fp->fp_fd = fd << 8 | fs_call;
X fp->fp_buffer = buffer;
X fp->fp_nbytes = nbytes;
X fp->fp_task = -task;
X dont_reply = TRUE; /* do not send caller a reply message now */
X}
X
X
X/*===========================================================================*
X * release *
X *===========================================================================*/
XPUBLIC release(ip, call_nr, count)
Xregister struct inode *ip; /* inode of pipe */
Xint call_nr; /* READ or WRITE */
Xint count; /* max number of processes to release */
X{
X/* Check to see if any process is hanging on the pipe whose inode is in 'ip'.
X * If one is, and it was trying to perform the call indicated by 'call_nr'
X * (READ or WRITE), release it.
X */
X
X register struct fproc *rp;
X
X /* Search the proc table. */
X for (rp = &fproc[0]; rp < &fproc[NR_PROCS]; rp++) {
X if (rp->fp_suspended == SUSPENDED && (rp->fp_fd & BYTE) == call_nr &&
X rp->fp_filp[rp->fp_fd>>8]->filp_ino == ip) {
X revive(rp - fproc, 0);
X susp_count--; /* keep track of who is suspended */
X if (--count == 0) return;
X }
X }
X}
X
X
X/*===========================================================================*
X * revive *
X *===========================================================================*/
XPUBLIC revive(proc_nr, bytes)
Xint proc_nr; /* process to revive */
Xint bytes; /* if hanging on task, how many bytes read */
X{
X/* Revive a previously blocked process. When a process hangs on tty, this
X * is the way it is eventually released.
X */
X
X register struct fproc *rfp;
X
X if (proc_nr < 0 || proc_nr >= NR_PROCS) panic("revive err", proc_nr);
X rfp = &fproc[proc_nr];
X if (rfp->fp_suspended == NOT_SUSPENDED || rfp->fp_revived == REVIVING)return;
X
X /* The 'reviving' flag only applies to pipes. Processes waiting for TTY get
X * a message right away. The revival process is different for TTY and pipes.
X * For TTY revival, the work is already done, for pipes it is not: the proc
X * must be restarted so it can try again.
X */
X if (rfp->fp_task == XPIPE) {
X /* Revive a process suspended on a pipe. */
X rfp->fp_revived = REVIVING;
X reviving++; /* process was waiting on pipe */
X } else {
X /* Revive a process suspended on TTY or other device. */
X rfp->fp_suspended = NOT_SUSPENDED;
X rfp->fp_nbytes = bytes; /* pretend it only wants what there is */
X reply(proc_nr, bytes); /* unblock the process */
X }
X}
X
X
X/*===========================================================================*
X * do_unpause *
X *===========================================================================*/
XPUBLIC int do_unpause()
X{
X/* A signal has been sent to a user who is paused on the file system.
X * Abort the system call with the EINTR error message.
X */
X
X register struct fproc *rfp;
X int proc_nr, task, fild;
X struct filp *f;
X dev_nr dev;
X extern struct filp *get_filp();
X
X if (who > MM_PROC_NR) return(EPERM);
X proc_nr = pro;
X if (proc_nr < 0 || proc_nr >= NR_PROCS) panic("unpause err 1", proc_nr);
X rfp = &fproc[proc_nr];
X if (rfp->fp_suspended == NOT_SUSPENDED) return(OK);
X task = -rfp->fp_task;
X
X if (task != XPIPE) {
X fild = rfp->fp_fd >> 8; /* extract file descriptor */
X if (fild < 0 || fild >= NR_FDS) return(EBADF);
X f = rfp->fp_filp[fild];
X dev = f->filp_ino->i_zone[0]; /* device on which proc is hanging */
X mess.TTY_LINE = (dev >> MINOR) & BYTE;
X mess.PROC_NR = proc_nr;
X mess.m_type = CANCEL;
X if (sendrec(task, &mess) != OK) panic("unpause err 3", NO_NUM);
X while (mess.REP_PROC_NR != proc_nr) {
X revive(mess.REP_PROC_NR, mess.REP_STATUS);
X if (receive(task, &m) != OK) panic("unpause err 3", NO_NUM);
X }
X revive(proc_nr, EINTR); /* signal interrupted call */
X }
X
X return(OK);
X}
+ END-OF-FILE pipe.c
chmod 'u=rw,g=r,o=r' \p\i\p\e\.\c
set `sum \p\i\p\e\.\c`
sum=$1
case $sum in
23565) :;;
*) echo 'Bad sum in '\p\i\p\e\.\c >&2
esac
exit 0