muller@sdcc3.UUCP (Keith Muller) (02/11/85)
This is a shar file of the second part of eight shar files for the load control system as described in net.unix-wizards. NOTE: Part 1 must be unpacked BEFORE any other part. Keith Muller ucbvax!sdcsvax!muller # This is a shell archive. Remove anything before this line, # then unpack it by saving it in a file and typing "sh file". # # Wrapped by sdcc3!muller on Sat Feb 9 13:44:50 PST 1985 # Contents: client/Makefile client/main.c scripts/addldd scripts/makedirs # scripts/qaddldd scripts/rmldd scripts/saddldd echo x - client/Makefile sed 's/^@//' > "client/Makefile" <<'@//E*O*F client/Makefile//' # # Makefile for batch client # CFLAGS= -O HDR= ../h/common.h ../h/client.h SRC= main.c DEST1= /bin TARG1= binclient TARG1Q= qbinclient DEST2= /usr/bin TARG2= usrbinclient TARG2Q= qusrbinclient DEST3= /usr/local TARG3= usrlocclient TARG3Q= qusrlocclient DEST4= /usr/ucb TARG4= usrucbclient TARG4Q= qusrucbclient DEST5= /usr/games TARG5= gamesclient TARG5Q= qgamesclient DEST6= /usr/new TARG6= usrnewclient TARG6Q= qusrnewclient # #the spec macros have the name of the program #SPEC1= test #DESTSPEC1= /tmp all: $(TARG1) $(TARG1Q) $(TARG2) $(TARG2Q) $(TARG3) $(TARG3Q) $(TARG4) \ $(TARG4Q) $(TARG5) $(TARG5Q) $(TARG6) $(TARG6Q) clean: rm -f core *client lint: lint -abchx main.c install: $(DEST1)/.client $(DEST1)/.qclient \ $(DEST2)/.client $(DEST2)/.qclient \ $(DEST3)/.client $(DEST3)/.qclient \ $(DEST4)/.client $(DEST4)/.qclient \ $(DEST5)/.client $(DEST5)/.qclient # $(DEST5)/.client $(DEST5)/.qclient \ # $(DEST6)/.client $(DEST6)/.qclient #################################################################### # Have the two commented lines replace the last line of the # dependency list if your machine has /usr/new. # # The following line is a sample for $(SPEC1) which would # have to be added into the install dependency for each # SPEC defined. # $(DESTSPEC1)/.$(SPEC1)client #################################################################### #################################################################### # $(SPEC1) is a sample of a special client for controlling # a single binary (like pi) # NOARGV should be the full path name of where the real binary is # is store (usually a .code directory) #################################################################### $(SPEC1)client: main.c $(HDR) cc $(CFLAGS) -DNOARGV=\"$(DESTSPEC1)/.code/$(SPEC1)\" main.c -o $(SPEC1)client $(DESTSPEC1)/.$(SPEC1)client: $(SPEC1)client install -c -m 4711 -o root $(SPEC1)client $(DESTSPEC1)/.$(SPEC1)client #################################################################### $(TARG1): main.c $(HDR) cc $(CFLAGS) -DCODEPATH=\"$(DEST1)/.code/\" main.c -o $(TARG1) $(TARG1Q): main.c $(HDR) cc $(CFLAGS) -DCODEPATH=\"$(DEST1)/.code/\" -DQUIET main.c -o $(TARG1Q) $(TARG2): main.c $(HDR) cc $(CFLAGS) -DCODEPATH=\"$(DEST2)/.code/\" main.c -o $(TARG2) $(TARG2Q): main.c $(HDR) cc $(CFLAGS) -DCODEPATH=\"$(DEST2)/.code/\" -DQUIET main.c -o $(TARG2Q) $(TARG3): main.c $(HDR) cc $(CFLAGS) -DCODEPATH=\"$(DEST3)/.code/\" main.c -o $(TARG3) $(TARG3Q): main.c $(HDR) cc $(CFLAGS) -DCODEPATH=\"$(DEST3)/.code/\" -DQUIET main.c -o $(TARG3Q) $(TARG4): main.c $(HDR) cc $(CFLAGS) -DCODEPATH=\"$(DEST4)/.code/\" main.c -o $(TARG4) $(TARG4Q): main.c $(HDR) cc $(CFLAGS) -DCODEPATH=\"$(DEST4)/.code/\" -DQUIET main.c -o $(TARG4Q) $(TARG5): main.c $(HDR) cc $(CFLAGS) -DCODEPATH=\"$(DEST5)/.code/\" main.c -o $(TARG5) $(TARG5Q): main.c $(HDR) cc $(CFLAGS) -DCODEPATH=\"$(DEST5)/.code/\" -DQUIET main.c -o $(TARG5Q) $(TARG6): main.c $(HDR) cc $(CFLAGS) -DCODEPATH=\"$(DEST6)/.code/\" main.c -o $(TARG6) $(TARG6Q): main.c $(HDR) cc $(CFLAGS) -DCODEPATH=\"$(DEST6)/.code/\" -DQUIET main.c -o $(TARG6Q) $(DEST1)/.client: $(TARG1) install -c -m 4711 -o root $(TARG1) $(DEST1)/.client $(DEST1)/.qclient: $(TARG1Q) install -c -m 4711 -o root $(TARG1Q) $(DEST1)/.qclient $(DEST2)/.client: $(TARG2) install -c -m 4711 -o root $(TARG2) $(DEST2)/.client $(DEST2)/.qclient: $(TARG2Q) install -c -m 4711 -o root $(TARG2Q) $(DEST2)/.qclient $(DEST3)/.client: $(TARG3) install -c -m 4711 -o root $(TARG3) $(DEST3)/.client $(DEST3)/.qclient: $(TARG3Q) install -c -m 4711 -o root $(TARG3Q) $(DEST3)/.qclient $(DEST4)/.client: $(TARG4) install -c -m 4711 -o root $(TARG4) $(DEST4)/.client $(DEST4)/.qclient: $(TARG4Q) install -c -m 4711 -o root $(TARG4Q) $(DEST4)/.qclient $(DEST5)/.client: $(TARG5) install -c -m 4711 -o root $(TARG5) $(DEST5)/.client $(DEST5)/.qclient: $(TARG5Q) install -c -m 4711 -o root $(TARG5Q) $(DEST5)/.qclient $(DEST6)/.client: $(TARG6) install -c -m 4711 -o root $(TARG6) $(DEST6)/.client $(DEST6)/.qclient: $(TARG6Q) install -c -m 4711 -o root $(TARG6Q) $(DEST6)/.qclient #################################################################### # tmpclient is used for testing the ldc system with a /tmp/.code # directory. This is not normally used. #################################################################### tmpclient: main.c $(HDR) cc $(CFLAGS) -DCODEPATH=\"/tmp/.code/\" main.c -o tmpclient install -c -m 4711 -o root tmpclient /tmp/.client @//E*O*F client/Makefile// chmod u=r,g=r,o=r client/Makefile echo x - client/main.c sed 's/^@//' > "client/main.c" <<'@//E*O*F client/main.c//' /*-------------------------------------------------------------------------- * main.c - client * * Front end program that communicates with the ldd server. This front * end replaces the program to be controlled. The controlled binary is * hidden in a directory that is only accessable through group privledges. * Only one client executable is needed for each protected binary directory. * The real name of the program to be executed is extracted from argv[0] * unless NOARGV is defined. When defined, NOARGV has the name of the program * to be exec'ed wired in. The NOARGV option is necessary for programs like * pi and px which use argv[0] to pass data to them (YUCK!!!) when they are * called from pix. Usually all the front ends can just be links (hard or soft) * to the same code file. * The QUIET option allows suppression of the client status messages (this * is good for nroff). The ONETIME option exempts all child processes from * being queued once the parent process has passed through load control * once. (Good for queueing individual passes of a compiler, make, etc). * If for any reason the server is dead or not responding, this program will * simply exec the proper code file. This allows the load control system to * be quickly disabled by killing off the ldd server program. * The front end checks every QUEUETIME seconds to see if the server is * still running and has this process queued up. If this poll fails the * control program is exec'ed. This protects against the system locking up due * to server death. The system WILL NOT be overloaded from a rash of executing * jobs as each job will expire relative to the time it was queued (which will * be spread out over time). * * Author : Keith Muller * University of California, San Diego * Academic Computer Center C - 010 * La Jolla Ca 92093 * ucbvax!sdcsvax!sdcc3!muller * (619) 452-6090 *--------------------------------------------------------------------------- */ /* $Log$ */ #include "../h/common.h" #include "../h/client.h" #include <sys/uio.h> #include <sys/socket.h> #include <sys/un.h> #include <sys/time.h> #include <stdio.h> #include <signal.h> #include <errno.h> int queued = 0; /* 1 if queued */ int msgsock = -1; /* desciptor of socket */ int len = 0; /* used to store address len */ char *ptr; /* ptr for pulling apart argv[0] */ char clientpath[255]; /* buffer for socket name */ char binary[255]; /* buffer for real binary's path */ struct request job; /* datagram for the server */ struct sockaddr_un name; /* socket address of server */ struct timeval polltime = {WAITTIME, 0}; /* waitime to check server */ extern int onint(); /* interrupt handler */ /*----------------------------------------------------------------------- * main * *----------------------------------------------------------------------- */ main(argc, argv, envp) int argc; char **argv, **envp; { register int i; /* general counter */ int msgmask; /* mask for select */ int readfds; /* mask for desc to select on */ int numfds; /* number of desc select ret */ int egid; /* effective group id */ int rgid; /* real group id */ int uid; /* real user id */ int pollcount; /* number of polls to server */ int descsize; /* size of desc table */ int sigmask; /* signal mask before block */ char msg; /* answer from server */ struct timeval now; /* time (secs) value */ struct timezone zone; /* timezone value (unused) */ int fromlen = 0; #ifndef QUIET int announce; /* limits "queued" messages */ char *eptr; extern char *getenv(); extern int strcmp(); #endif QUIET extern char *strcpy(); extern char *strncpy(); extern char *strcat(); extern int getpid(); extern int getegid(); extern int getgid(); extern int getuid(); extern int sigblock(); extern int sigsetmask(); extern int errno; extern char *sprintf(); extern char *rindex(); /* * the client front end runs ONLY setuid to root. so get real user * and both effective and real gids. */ egid = getegid(); rgid = getgid(); uid = getuid(); /* * set the users real and effective uid (no limits on root). also set * the group id to LDDGID so a socket can be bound in the spool * directory and a datagram can be sent to the server. (the spool * directory MUST BE in group LDDGID and mode 0730 only! * NO OTHER PRIVLEDGES AT ALL!!!!!) */ (void)setregid(rgid, LDDGID); (void)setreuid(uid, uid); /* * If NOARGV is defined, then this is a special client which * will only exec a SINGLE program. This is to get around things * like pi which can use argv[0] to pass data. Otherwise we must * find the base name of the requested program. Since argv[0] * can be a long ugly path name, ugly looking code is needed * to strip off the path. */ #ifdef NOARGV /* * NOARGV is set in the makefile to have the FULL path of where the * real binary lives: for example /usr/bin/.code/yuck */ (void)strcpy(binary, NOARGV); if ((ptr = rindex(binary, '/')) == (char *)0) ptr = binary; else ptr++; #else /* * must pull the path out of the argv[0] */ if ((ptr = rindex(argv[0], '/')) == (char *)0) ptr = argv[0]; else ptr++; (void)sprintf(binary, "%s%s", CODEPATH, ptr); #endif /* * If ONETIME is defined, then all child processes of this job are * EXEMPT from being queued. This is useful for things like pi which * can be called both by a user and from pix. * This works because if the effective gid of the process * is the group LDDGID this process must be a decendent of a process * that has already passed through the load control system. This * mechanism will work only if this program is setuid and IS NOT * setgid. * * root is always exempt! * * NOTE: ptr will be used later to build up the command line buffer * in the datagram request packet sent to the server. */ #ifdef ONETIME if ((egid == LDDGID) || (uid == 0)) run(argv, envp); #else if (uid == 0) run(argv, envp); #endif ONETIME /* * create the socket and the datagram. if anything fails * just run. cannot afford to have this process HANG! */ msgsock = socket(AF_UNIX, SOCK_DGRAM, 0); if (msgsock < 0) run(argv, envp); /* * bind the handler to clean up */ (void)signal(SIGINT, onint); (void)signal(SIGHUP, onint); (void)signal(SIGQUIT, onint); (void)signal(SIGTERM, onint); /* * make the datagram up */ job.pid = (u_long)getpid(); (void)sprintf(clientpath,"%s/%s%u",SPOOLDIR,CLIENTPRE,job.pid); (void)strcpy(name.sun_path, clientpath); name.sun_family = AF_UNIX; len = strlen(name.sun_path) + 1 + sizeof(name.sun_family); /* * block off interrupt and control z until we get datagram * sent */ sigmask = sigblock((1<<(SIGINT-1)) | (1<<(SIGTSTP-1)) | (1<<(SIGHUP-1)) | (1<<(SIGQUIT-1)) | (1<<(SIGTERM-1))); /* * bind the socket, if it fails just run */ (void)unlink(name.sun_path); if (bind(msgsock, &name, len) < 0){ (void)sigsetmask(sigmask); run(argv, envp); } /* * build up the command line that will be displayed in the * when the user interrogates the queue. This helps in identifying * which job is which. */ (void)strncpy(job.com, ptr, COMLEN - 1); i = 1; len = strlen(job.com) + 1; while((i < argc) && ((len = len + strlen(argv[i]) + 1) <= COMLEN)){ (void)strcat(job.com, " "); (void)strcat(job.com, argv[i]); i++; } /* * put the path name of the servers datagram in the sockaddr struct */ (void)strcpy(name.sun_path, MSGPATH); len = strlen(name.sun_path) + 1 + sizeof(name.sun_family); /* * time stamp the datagram, and place my pid in it (identfies the name * of this clients bound socket */ if (gettimeofday(&now, &zone) < 0) run(argv, envp); job.time = now.tv_sec; job.type = QCMD; job.uid = (u_long)uid; /* * send the request to the server */ if (sendto(msgsock, &job, sizeof(struct request), 0, &name, len) < 0){ (void)sigsetmask(sigmask); run(argv, envp); } descsize = getdtablesize(); msgmask = 1 << msgsock; pollcount = 0; /* * unblock the signals */ (void)sigsetmask(sigmask); #ifndef QUIET /* * set announce to 0 to get at least one status message * if user has ENVNAME = quiet in his environment, no messages! */ if (((eptr=getenv(ENVNAME))==(char *)0)||(strcmp(eptr,"quiet")!=0)) announce = 0; else announce = -1; #endif QUIET /* * wait for the word from the server! */ for(;;){ readfds = msgmask; numfds = select(descsize,&readfds,(int *)0,(int *)0,&polltime); /* * if there is a screwup here just run */ if (((numfds<0)&&(errno!=EINTR))||((numfds<=0)&&(pollcount>MAXPOLLS))){ #ifndef QUIET if (announce == 1) fprintf(stderr,"running\n"); #endif QUIET run(argv, envp); } /* * we waitied polltime seconds and no word from the server * so send the datagram again in case the system lost it * OR else we got a garbage answer! */ if ((numfds<=0)||(recvfrom(msgsock,&msg,sizeof(msg),0,(struct sockaddr *)0,&fromlen)<=0)){ pollcount++; /* * oh server where are you? */ if (sendto(msgsock,&job,sizeof(struct request),0,&name,len)<0){ #ifndef QUIET if (announce == 1) fprintf(stderr,"running\n"); #endif QUIET run(argv, envp); }else{ /* * the datagram was sent so switch to WAITTIME * for an answer. WAITTIME is much shorter * than QUEUETIME as we want to be queued. */ polltime.tv_sec = WAITTIME; continue; } } /* * we got the word see what to do */ switch(msg){ case RUNCMD: /* * we can run */ #ifndef QUIET if (announce == 1) fprintf(stderr,"running\n"); #endif QUIET run(argv, envp); break; case STOPCMD: /* * bye bye */ #ifndef QUIET if (announce == 1) fprintf(stderr,"stopped\n"); #endif QUIET (void)close(msgsock); (void)unlink(clientpath); exit(1); case QCMD: /* * we have been queued * switch to QUEUETIME so to check later * that the server is still around (so we * don't wait forever! * Switch to POLLCMD so that the server knows * we have been in the queue at least once. */ polltime.tv_sec = QUEUETIME; queued = 1; pollcount = 0; job.type = POLLCMD; #ifndef QUIET /* * tell user he is being queued */ if (announce == 0){ fprintf(stderr,"Queued, waiting to run...."); announce = 1; } #endif QUIET break; case FULLQUEUE: /* * The queue is full, this job cannot be * accepted. This prevents the system * from running out of slots in the * process table. */ fprintf(stderr,"Cannot run, the system is overloaded. Try again later.\n"); (void)close(msgsock); (void)unlink(clientpath); exit(1); case POLLCMD: /* * server wants the data again * The only way we can get this is from * a new server during startup. * So reset the datagram to a QCMD. * (fall through to default below), */ job.type = QCMD; default: /* * or got garbage */ if (sendto(msgsock,&job,sizeof(struct request),0,&name,len)<0){ #ifndef QUIET if (announce == 1) fprintf(stderr,"running\n"); #endif QUIET run(argv, envp); } /* * switch back to WAITTIME to be a pest until * we get queued */ polltime.tv_sec = WAITTIME; queued = 0; pollcount = 0; break; } } } /*----------------------------------------------------------------------------- * onint * * what to do when the user wants out *----------------------------------------------------------------------------- */ onint() { /* * if we are already queued say goodbye to the server */ if (queued == 1){ /* * Send a message to the server we are quitting so the server * can remove us from the queue. */ job.type = PJOBCMD; job.time = job.pid; (void)sendto(msgsock,&job,sizeof(struct request),0,&name,len); } (void)close(msgsock); (void)unlink(clientpath); exit(0); } /*----------------------------------------------------------------------------- * run * * routine that execs the real program after getting the ok *----------------------------------------------------------------------------- */ run(argv, envp) char **argv, **envp; { extern int msgsock; extern char binary[]; extern char clientpath[]; /* * shut down the socket and remove it from the spool */ if (msgsock != -1) (void)close(msgsock); (void)unlink(clientpath); /* * all is set try to run! * this works because the directory where the REAL code file is * is mode 0730 and the group MUST BE LDDGID! * and we are now running with an effective gid of LDDGID */ (void)execve(binary, argv, envp); /* * from now on something screwed up! print the error and * hope the user reports it! */ perror(binary); exit(0); } @//E*O*F client/main.c// chmod u=r,g=r,o=r client/main.c echo x - scripts/addldd sed 's/^@//' > "scripts/addldd" <<'@//E*O*F scripts/addldd//' #! /bin/csh -f # # script to add file1 through filen located in directory to the processes # controlled by the ldd system. # # THIS IS FOR COMMANDS THAT SHOULD HAVE STATUS ANNOUNCEMENTS umask 022 if ($#argv < 2) then echo "usage: addldd directory file1 file2 .. filen" else echo "cd $1" cd $1 shift while($#argv) if (-e .client == 0) then echo "there is no .client front end. Do a make install." break else if (-e .code/$1) then echo "$1 is already load controlled" else if (-e $1) then echo "putting $1 under load control" echo "mv $1 .code/$1" /bin/mv $1 .code/$1 echo "ln -s .client $1" /bin/ln -s .client $1 else echo "$1 does not exsist" endif shift end endif @//E*O*F scripts/addldd// chmod u=rx,g=rx,o=rx scripts/addldd echo x - scripts/makedirs sed 's/^@//' > "scripts/makedirs" <<'@//E*O*F scripts/makedirs//' #! /bin/csh -f # make all the directories needed by the load control system # NOTE: lddgrp must be defined as the same group id as in h/client.h # echo "making the directories for load control" foreach i (/bin /usr/bin /usr/local /usr/ucb /usr/new /usr/games) if (-e $i && -e $i/.code == 0) then echo "mkdir $i/.code" /bin/mkdir $i/.code endif if (-e $i/.code) then echo "chown root $i/.code" /etc/chown root $i/.code echo "chgrp lddgrp $i/.code" /bin/chgrp lddgrp $i/.code echo "chmod 0710 $i/.code" /bin/chmod 0710 $i/.code endif end # if (-e /usr/spool/ldd == 0) then echo "mkdir /usr/spool/ldd" /bin/mkdir /usr/spool/ldd endif if (-e /usr/spool/ldd/sr == 0) then echo "mkdir /usr/spool/ldd/sr" /bin/mkdir /usr/spool/ldd/sr endif if (-e /usr/spool/ldd/cl == 0) then echo "mkdir /usr/spool/ldd/cl" /bin/mkdir /usr/spool/ldd/cl endif # # spool/ldd/cl MUST BE GROUP writeable # all others should NOT be GROUP writeable # echo "chown root /usr/spool/ldd" /etc/chown root /usr/spool/ldd echo "chgrp lddgrp /usr/spool/ldd" /bin/chgrp lddgrp /usr/spool/ldd echo "chmod 0710 /usr/spool/ldd" /bin/chmod 0710 /usr/spool/ldd echo "chown root /usr/spool/ldd/cl" /etc/chown root /usr/spool/ldd/cl echo "chgrp lddgrp /usr/spool/ldd/cl" /bin/chgrp lddgrp /usr/spool/ldd/cl echo "chmod 0730 /usr/spool/ldd/cl" /bin/chmod 0730 /usr/spool/ldd/cl echo "chown root /usr/spool/ldd/sr" /etc/chown root /usr/spool/ldd/sr echo "chgrp lddgrp /usr/spool/ldd/sr" /bin/chgrp lddgrp /usr/spool/ldd/sr echo "chmod 0710 /usr/spool/ldd/sr" /bin/chmod 0710 /usr/spool/ldd/sr @//E*O*F scripts/makedirs// chmod u=rx,g=rx,o=rx scripts/makedirs echo x - scripts/qaddldd sed 's/^@//' > "scripts/qaddldd" <<'@//E*O*F scripts/qaddldd//' #! /bin/csh -f # # script to add file1 through filen located in directory to the processes # controlled by the ldd system. # # THIS IS FOR COMMANDS THAT *DO* *NOT* HAVE STATUS ANNOUNCEMENTS umask 022 if ($#argv < 2) then echo "usage: qaddldd directory file1 file2 .. filen" else echo "cd $1" cd $1 shift while($#argv) if (-e .qclient == 0) then echo "there is no .qclient front end. Do a make install." break else if (-e .code/$1) then echo "$1 is already load controlled" else if (-e $1) then echo "putting $1 under load control (quiet mode)" echo "mv $1 .code/$1" /bin/mv $1 .code/$1 echo "ln -s .qclient $1" /bin/ln -s .qclient $1 else echo "$1 does not exsist" endif shift end endif @//E*O*F scripts/qaddldd// chmod u=rx,g=rx,o=rx scripts/qaddldd echo x - scripts/rmldd sed 's/^@//' > "scripts/rmldd" <<'@//E*O*F scripts/rmldd//' #! /bin/csh -f # # script to remove process file1 through filen in directory from the # load control system. umask 022 if ($#argv < 2) then echo "usage: rmldd directory file1 file2 .. filen" else echo "cd $1" cd $1 shift while($#argv) if (-e .code/$1) then echo "removing $1 from load control" echo "rm $1" /bin/rm $1 echo "mv .code/$1 $1" /bin/mv .code/$1 $1 else echo "$1 is not load controlled" endif shift end endif @//E*O*F scripts/rmldd// chmod u=rx,g=rx,o=rx scripts/rmldd echo x - scripts/saddldd sed 's/^@//' > "scripts/saddldd" <<'@//E*O*F scripts/saddldd//' #! /bin/csh -f # # script to add file file1 as a SPECIAL client in directory directory. # # THIS IS FOR COMMANDS THAT REQUIRE SPECIAL PRIVATE CLIENTS umask 022 if ($#argv != 2) then echo "usage: saddldd directory file" else echo "cd $1" cd $1 shift if (-e .$1client == 0) then echo "there is no .$1client front end. Do a make install." break else if (-e .code/$1) then echo "$1 is already load controlled" else if (-e $1) then echo "putting $1 under load control (special client)" echo "mv $1 .code/$1" /bin/mv $1 .code/$1 echo "ln -s .$1client $1" /bin/ln -s .$1client $1 else echo "$1 does not exsist" endif endif @//E*O*F scripts/saddldd// chmod u=rx,g=rx,o=rx scripts/saddldd echo Inspecting for damage in transit... temp=/tmp/shar$$; dtemp=/tmp/.shar$$ trap "rm -f $temp $dtemp; exit" 0 1 2 3 15 cat > $temp <<\!!! 154 491 4545 Makefile 471 1929 12916 main.c 32 123 711 addldd 63 202 1580 makedirs 32 126 733 qaddldd 25 76 454 rmldd 28 113 644 saddldd 805 3060 21583 total !!! wc client/Makefile client/main.c scripts/addldd scripts/makedirs scripts/qaddldd scripts/rmldd scripts/saddldd | sed 's=[^ ]*/==' | diff -b $temp - >$dtemp if [ -s $dtemp ] then echo "Ouch [diff of wc output]:" ; cat $dtemp else echo "No problems found." fi exit 0
muller@sdcc3.UUCP (Keith Muller) (02/12/85)
This is part 3 of the load control system. Part 1 must be unpacked before any other part. Keith Muller ucbvax!sdcsvax!muller # This is a shell archive. Remove anything before this line, # then unpack it by saving it in a file and typing "sh file". # # Wrapped by sdcc3!muller on Sat Feb 9 13:48:06 PST 1985 # Contents: h/client.h h/common.h h/control.h h/server.h echo x - h/client.h sed 's/^@//' > "h/client.h" <<'@//E*O*F h/client.h//' /*----------------------------------------------------------------------- * client.h * * definitions used by the client process. *----------------------------------------------------------------------- */ /* $Log$ */ #define MAXPOLLS 5 /* max polls to server from a client if the */ /* server doesnt respond after, just run */ #define WAITTIME 60 /* time (secs) wait for response between each */ /* poll of the server */ #define QUEUETIME 600 /* time (secs) wait before waking up to see if*/ /* if the server is still running */ #define ENVNAME "LDDOPTS" /* name to set in environment, read by client */ #define ONETIME /* When defined once a program passed through */ /* the ldd system and inherits the effective */ /* group id of ldd, all child processes will */ /* be exempt from load control. Can be used */ /* to encourage the use of make. Note that if */ /* make is load controlled a user can bypass */ /* load control by having make create a sh. */ /* A more uesful way to use this is to load */ /* control all passes of a compiler and only */ /* first passes will be queued, all other */ /* passes will be exempt. See client/main.c */ @//E*O*F h/client.h// chmod u=r,g=r,o=r h/client.h echo x - h/common.h sed 's/^@//' > "h/common.h" <<'@//E*O*F h/common.h//' /*------------------------------------------------------------------------ * common.h * * Common definations for all the programs releted to the load control * system that MUST be EXACTLY the same. *------------------------------------------------------------------------ */ /* $Log$ */ #include <sys/types.h> /* * The basic datagram package sent TO the server. */ #define COMLEN 15 /* buffer for user command */ struct request { u_long pid; /* pid of client process (socket name) */ u_long uid; /* Usually the uid of the client, not always */ u_long time; /* Usually the submit time of the client */ char com[COMLEN]; /* Part of the command line the user typed */ char type; /* The command identifier (a single char) */ }; /* * The commands that can be sent to/from the server. */ #define CHTIMER 'N' /* change the server interval timer */ #define LOADLIMCMD 'L' /* change the server load average threshold */ #define LISTCMD 'I' /* ask server for a list of queued jobs */ #define MOVECMD 'M' /* ask server to move a job in the queue */ #define MQTIMECMD 'T' /* change the server limit on max queue time */ #define POLLCMD 'P' /* to/from server that request be resent */ #define QCMD 'Q' /* to/from server stating job is queued */ #define PALLCMD 'F' /* ask server to remove ALL queued jobs */ #define PUSRCMD 'U' /* ask server to remove all a users jobs */ #define PJOBCMD 'R' /* ask server to remove a specific job */ #define RUSRCMD 'W' /* ask server to run all a users jobs */ #define RJOBCMD 'X' /* ask server to run a specific job */ #define RUNCMD 'G' /* message to continue execution */ #define STOPCMD 'H' /* message to stop or command invalid */ #define STATUSCMD 'S' /* ask server to list parameters */ #define ABORTCMD 'A' /* ask server to terminate */ #define FULLQUEUE 'D' /* from server that queue is full */ #define QUEUESIZE 'O' /* ask server to change queue full point */ /* * path names of files used in the system */ #define CLIENTPRE "cl" /* client socket prefix */ #define CNTRLPRE "cn" /* control socket prefix */ #define LDDGID 25 /* gid used to protect */ /* REAL binaries this must*/ /* be the gid of the */ /* protected directories */ /* * Defined paths. If these are altered the onetime script makedirs must * also be altered. * * The load control system uses two subdirectories to store * sockets and status files. server directories should be owner (root) * write only and execute all others only (mode 710). The client directory * must be group writeable but not readable (mode 730). This is done for * security reasons and should not be altered. */ #define SPOOLDIR "/usr/spool/ldd/cl" /* client sockets dir */ #define SERVERDIR "/usr/spool/ldd/sr" /* servers directory */ /* * The following MUST be in the SERVERDIR directory!!!! */ #define CNTRLPATH "/usr/spool/ldd/sr/cnsock" /* serv cntrl socket */ #define MSGPATH "/usr/spool/ldd/sr/msgsock" /* serv msg socket */ #define LISTFILE "/usr/spool/ldd/sr/list" /* jobs in queue file */ #define STATUSFILE "/usr/spool/ldd/sr/status" /* status of server */ #define ERRORPATH "/usr/spool/ldd/sr/errors" /* saved errors */ #define LOCK "/usr/spool/ldd/sr/lock" /* lockfile */ @//E*O*F h/common.h// chmod u=r,g=r,o=r h/common.h echo x - h/control.h sed 's/^@//' > "h/control.h" <<'@//E*O*F h/control.h//' /*------------------------------------------------------------------------ * control.h * * definations used by the control program *------------------------------------------------------------------------ */ /* $Log$ */ /* * structure of control program command table */ struct cmd { char *c_name; /* command name */ char *c_help; /* help message */ int (*c_handler)(); /* routine to do the work */ int c_priv; /* command privledge */ }; #define RESTRICT 1 /* a 1 means restricted command */ #define NORESTRICT 0 /* a 0 mean no restrictions */ /* * structure of the hash table used by the list commands to keep a * local listing of recently referenced users in the passwd file. * (used for speedup). */ #define PNAMSIZ 12 /* size of list name bucket to store name */ #define HASHMOD 151 /* size of list hash table, MUST BE prime */ struct hashbuck{ int buckuid; char buckname[PNAMSIZ+1]; }; /* * warning message limits for ldc commands to help prevent the admin * from setting server settings that might cause problems. */ #define LOWCYCLE 5 /* If cycle time is less complain */ #define HIGHCYCLE 600 /* If cycle time is greater complain */ #define LOWMAX 90 /* If max queue time is less complain */ #define HIGHMAX 43200 /* If max queue time is greater complain */ #define LOWSIZE 10 /* If max queue size is less complain */ #define HIGHSIZE 200 /* If max queue size is greater complain */ #define LOWLOAD 1.0 /* If load limit is less complain */ #define HIGHLOAD 35.0 /* If load limit is greater complain */ /* * other defines */ #define WAITTIME 120 /* time (secs) before packet resend */ @//E*O*F h/control.h// chmod u=r,g=r,o=r h/control.h echo x - h/server.h sed 's/^@//' > "h/server.h" <<'@//E*O*F h/server.h//' /*------------------------------------------------------------------------ * server.h * * definations used by the server process. *------------------------------------------------------------------------ */ /* $Log$ */ /* * structure of the double linked queue of waiting processes. */ struct qnode { u_long pid; /* pid of waiting client */ u_long uid; /* uid of waiting client */ u_long time; /* time job submitted in queue */ struct qnode *fow; /* foward link to newer job */ struct qnode *back; /* back link to older job */ char com[COMLEN+1]; /* command line (truncated) */ }; #define QNIL (struct qnode *) 0 /* use for nil pointer */ /* * General definations. */ #define MAXLOAD 10.0 /* default load level to queue at */ /* 10 for pyramid 90x, 8.5 for 780, 7 for */ /* 750. note you can override this on /* ldd's command line */ #define ALRMTIME 60 /* default cycle time to check load */ /* 60 is good for vaxes. pyramids being */ /* so much faster should be 20. Note you */ /* can override this default on ldd's */ /* command line in seconds */ #define MAXQTIME 14400 /* default max queue time for a process */ /* waiting to run in seconds */ #define PRIO -4 /* server runs at this nice level this has */ /* little impact on the system, but makes */ /* sure packets get handled properly under */ /* heavy load. This should be -4 or smaller */ /* (-5, -6, ...) */ #define WAITTIME 60 /* wait for client during startup scan */ /* of the spool directory in seconds */ #define MAXPOLLS 2 /* how many times to send a poll request */ /* during startup polling */ #define MAXINQUEUE 150 /* max number of jobs that can be waiting to */ /* run */ @//E*O*F h/server.h// chmod u=r,g=r,o=r h/server.h echo Inspecting for damage in transit... temp=/tmp/shar$$; dtemp=/tmp/.shar$$ trap "rm -f $temp $dtemp; exit" 0 1 2 3 15 cat > $temp <<\!!! 30 200 1207 client.h 81 528 3321 common.h 54 270 1630 control.h 50 298 1737 server.h 215 1296 7895 total !!! wc h/client.h h/common.h h/control.h h/server.h | sed 's=[^ ]*/==' | diff -b $temp - >$dtemp if [ -s $dtemp ] then echo "Ouch [diff of wc output]:" ; cat $dtemp else echo "No problems found." fi exit 0
muller@sdcc3.UUCP (Keith Muller) (02/12/85)
This is part 4 of the load control system. Part 1 must be unpacked before any other part. Keith Muller ucbvax!sdcsvax!muller # This is a shell archive. Remove anything before this line, # then unpack it by saving it in a file and typing "sh file". # # Wrapped by sdcc3!muller on Sat Feb 9 13:51:28 PST 1985 # Contents: control/Makefile control/delete.c control/globals.c control/ipc.c # control/ldccmds.c echo x - control/Makefile sed 's/^@//' > "control/Makefile" <<'@//E*O*F control/Makefile//' # # Makefile for ldc control programs : ldc, ldrm, ldq # CFLAGS= -O BGID= lddgrp DEST= /etc DEST2= /usr/local HDR= ../h/common.h ../h/control.h LDCSRC= ldcmain.c ldccmds.c list.c delete.c ldccmdtab.c ipc.c globals.c LDRMSRC= ldrmmain.c delete.c ipc.c globals.c LDQSRC= ldqmain.c list.c ipc.c globals.c LDCOBJ= ldcmain.o ldccmds.o list.o delete.o ldccmdtab.o ipc.o globals.o LDRMOBJ=ldrmmain.o delete.o ipc.o globals.o LDQOBJ= ldqmain.o list.o ipc.o globals.o all: ldc ldrm ldq ldc: $(LDCOBJ) cc -o ldc $(LDCOBJ) ldrm: $(LDRMOBJ) cc -o ldrm $(LDRMOBJ) ldq: $(LDQOBJ) cc -o ldq $(LDQOBJ) $(LDCOBJ): $(HDR) $(LDRMOBJ): $(HDR) $(LDQOBJ): $(HDR) install: $(DEST)/ldc $(DEST2)/ldq $(DEST2)/ldrm $(DEST)/ldc: ldc install -c -m 4711 -o root -g $(BGID) ldc $(DEST) $(DEST2)/ldrm: ldrm install -c -m 4711 -o root -g $(BGID) ldrm $(DEST2) $(DEST2)/ldq: ldq install -c -m 4711 -o root -g $(BGID) ldq $(DEST2) clean: rm -f *.o core ldc ldrm ldq lint: @echo "*************************lint for ldc*************************" lint -abchx $(LDCSRC) @echo "*************************lint for ldrm************************" lint -abchx $(LDRMSRC) @echo "*************************lint for ldq*************************" lint -abchx $(LDQSRC) @//E*O*F control/Makefile// chmod u=r,g=r,o=r control/Makefile echo x - control/delete.c sed 's/^@//' > "control/delete.c" <<'@//E*O*F control/delete.c//' /*----------------------------------------------------------------------- * delete.c - control program * * this file contains the delete command which is common to both ldc * and ldrm *----------------------------------------------------------------------- */ /* $Log$ */ #include "../h/common.h" #include <sys/time.h> #include <stdio.h> #include <pwd.h> /*----------------------------------------------------------------------- * delete * * tell the server to remove all a users jobs or a specific job from its * queue. a user can remove only his jobs, root can remove any job. *----------------------------------------------------------------------- */ delete(argc, argv) int argc; char **argv; { register int i; struct passwd *pwd; long temp; extern int uid; extern struct request job; extern struct passwd *getpwnam(); extern long atol(); extern int endpwent(); extern int strncmp(); if (argc < 2){ printf("Usage: delete [pid(s)] [-u user(s)]\n"); return; } if (strncmp(argv[1], "-u", 2) == 0){ /* * remove all jobs by user (-u option) */ if (argc < 3){ printf("No user name\n"); printf("Usage: delete [pid(s)] [-u user(s)]\n"); return; } job.type = PUSRCMD; for (i = 2; i < argc; i++){ /* * loop through each arg and remove that users jobs */ if ((pwd = getpwnam(argv[i])) == (struct passwd *)NULL){ /* * couldn't find the user, go to next one */ printf("No such user: %s\n", argv[i]); continue; } /* * the server DOES NOT check to see if this command * can be performed by this user. */ if ((uid != 0) && (uid != pwd->pw_uid)){ printf("You are not: %s\n", argv[i]); continue; } /* * remove the specified users jobs */ job.uid = (u_long)pwd->pw_uid; printf("user %s: ", argv[i]); if (sendcntrl() == 0) printf("removed from the queue.\n"); } (void)endpwent(); }else{ /* * remove a job specified by its pid. Only the server * can determine if the pid is a valid pid for a queued job. * The server will check to see if this user can remove that * pid from the queue (root can remove any job, a user can only * remove his jobs). */ job.type = PJOBCMD; job.uid = uid; for (i = 1; i < argc; i++){ /* * loop through each arg and try to remove it (each arg * is a pid). */ if ((temp = atol(argv[i])) <= 0){ /* * bad pid, try next */ printf("Bad pid: %ld\n", temp); continue; } job.time = (u_long)temp; printf("pid %ld: ", temp); if (sendcntrl() == 0) printf("removed\n"); } } } @//E*O*F control/delete.c// chmod u=r,g=r,o=r control/delete.c echo x - control/globals.c sed 's/^@//' > "control/globals.c" <<'@//E*O*F control/globals.c//' /*------------------------------------------------------------------------- * globals.c - control program, used in ldc, ldq, and ldrm * *------------------------------------------------------------------------- */ /* $Log$ */ #include "../h/common.h" #include "../h/control.h" #include <sys/uio.h> #include <sys/socket.h> #include <sys/un.h> #include <sys/time.h> #include <stdio.h> int uid; /* uid of the user */ char path[256]; /* path to bound socket */ int sock = -1; /* socket desriptor */ int descsize; /* number of desc table */ int sockmask; /* mask to select socket */ int stdinmask; /* mask to select stdin */ int len; /* length of address */ FILE *out = NULL; /* file desc for status file */ struct sockaddr_un name; /* socket address */ struct request job; /* datagram to server */ struct timeval polltime = {WAITTIME, 0}; /* time so select not hangs */ @//E*O*F control/globals.c// chmod u=r,g=r,o=r control/globals.c echo x - control/ipc.c sed 's/^@//' > "control/ipc.c" <<'@//E*O*F control/ipc.c//' /*------------------------------------------------------------------------- * ipc.c - control * * all the routines used to communicate with the server *------------------------------------------------------------------------- */ /* $Log$ */ #include "../h/common.h" #include "../h/control.h" #include <sys/uio.h> #include <sys/socket.h> #include <sys/un.h> #include <sys/time.h> #include <signal.h> #include <stdio.h> #include <errno.h> /*------------------------------------------------------------------------- * setup * * create all the goodies needed to run the control programs *------------------------------------------------------------------------- */ setup() { extern struct sockaddr_un name; extern int uid; extern int len; extern int sock; extern int sockmask; extern int stdinmask; extern int descsize; extern char *strcpy(); extern int onint(); extern int quit(); extern struct request job; extern char path[]; extern int getpid(); extern int getuid(); extern int getgid(); extern int errno; extern char *sprintf(); /* * set the global uid as it will be used throughout the control * program */ uid = getuid(); /* * setup the socket to send and recieve messages */ name.sun_family = AF_UNIX; sock = socket(AF_UNIX, SOCK_DGRAM, 0); if (sock < 0){ perror("cannot create socket:"); exit(1); } /* * bind the interrupt handler */ (void)signal(SIGINT, onint); (void)signal(SIGHUP, quit); (void)signal(SIGQUIT, quit); (void)signal(SIGTERM, quit); /* * bind the socket */ job.pid = (u_long)getpid(); (void)sprintf(path, "%s/%s%u",SPOOLDIR, CNTRLPRE, job.pid); (void)strcpy(name.sun_path, path); name.sun_family = AF_UNIX; len = strlen(name.sun_path) + 1 + sizeof(name.sun_family); (void)unlink(name.sun_path); if (bind(sock, &name, len) < 0){ perror("cannot bind socket"); exit(1); } /* * set the users real and effective uid back to the user (we are * running setuid root). All cntrl sockets must be owned by root. * set gid to lddgrp so socket can be removed later. */ (void)setregid(getgid(), LDDGID); (void)setreuid(uid, uid); /* * setup the name structure so all messages will be sent * to the server */ (void)strcpy(name.sun_path, CNTRLPATH); len = strlen(name.sun_path) + 1 + sizeof(name.sun_family); /* * descsize for the select call */ descsize = getdtablesize(); /* * mask to select the socket for messages or stdin input */ sockmask = 1 << sock; stdinmask = 1 << fileno(stdin); } /*------------------------------------------------------------------------- * sendcntrl * * send the job datagram as set up by the specific command to the server * and then wait for a response. Print diagnostics if the command was not * executed by the server or the server does not respond. *------------------------------------------------------------------------- */ sendcntrl() { int readfds; int numfds; char msg; int fromlen = 0; extern struct timeval polltime; extern int len; extern int errno; extern int descsize; extern int sockmask; extern int sock; extern struct request job; extern struct sockaddr_un name; extern int quit(); if (sendto(sock,&job,sizeof(struct request),0,&name,len) < 0){ perror("message to server failed"); return(-1); } readfds = sockmask; /* * wait for the ok from the server */ numfds = select(descsize,&readfds,(int *)0,(int *)0,&polltime); if ((numfds < 0) && (errno != EINTR)){ perror("select failed"); return(-1); } if (numfds <= 0){ printf("Server did not respond after %ld seconds\n",WAITTIME); return(-1); } if (recvfrom(sock,&msg,sizeof(msg),0,(struct sockaddr *)0,&fromlen)<=0){ perror("message from server failed"); return(-1); } switch(msg){ case RUNCMD: return(0); case POLLCMD: printf("new server started, command lost\n"); return(-1); case STOPCMD: printf("command failed\n"); return(-1); default: printf("unknown message from server\n"); return(-1); } } @//E*O*F control/ipc.c// chmod u=r,g=r,o=r control/ipc.c echo x - control/ldccmds.c sed 's/^@//' > "control/ldccmds.c" <<'@//E*O*F control/ldccmds.c//' /*----------------------------------------------------------------------- * commands.c - control program * * this file contains the commands that the ldc program can execute *----------------------------------------------------------------------- */ /* $Log$ */ #include "../h/common.h" #include "../h/control.h" #include <sys/time.h> #include <stdio.h> #include <pwd.h> /*--------------------------------------------------------------------------- * abortserver * * tell the server to give it up * NOTE: for saftey reasons you must type both words: abort server * abort alone will NOT work (for people who cannot type!) *--------------------------------------------------------------------------- */ abortserv(argc, argv) int argc; char **argv; { extern struct request job; if (argc != 2){ printf("Usage: abort server\n"); return; } /* * second arg better be a prefix of the word server, else a typo! */ if (strncmp(argv[1], "server", strlen(argv[1])) != 0){ printf("Usage: abort server\n"); return; } /* * bye bye old server */ job.type = ABORTCMD; (void)sendcntrl(); } /*------------------------------------------------------------------------- * errprint * * print out the contents of the error logging file. *------------------------------------------------------------------------- */ errprint(argc, argv) int argc; char **argv; { char linebuf[256]; extern char *fgets(); extern FILE *out; extern int fclose(); FILE *fopen(); if (argc != 1){ printf("Usage: errprint\n"); return; } if ((out = fopen(ERRORPATH, "r")) == NULL){ printf("cannot open error file\n"); return; } /* * print out the contents of the error file exactly */ while (fgets(linebuf, sizeof(linebuf), out) != (char *)0) fputs(linebuf, stdout); (void)fclose(out); out = NULL; } /*------------------------------------------------------------------------- * loadlimt * * tell the server to change the point at which it queues jobs *------------------------------------------------------------------------- */ loadlimit(argc, argv) int argc; char **argv; { double load; extern struct request job; extern double atof(); if (argc != 2){ printf("Usage: loadlimit load\n"); return; } job.type = LOADLIMCMD; if ((load = atof(argv[1])) <= 0){ printf("Bad value for loadlimit: %.2lf\n",load); return; } if (load < LOWLOAD) printf("WARNING: %.2lf load might cause excessive queue times\n",load); else if (load > HIGHLOAD) printf("WARNING: %.2lf load might never queue jobs\n",load); /* * the value 256 is used to scale up the floating point load * average so the value fits in a long. The 256 value was picked * as that is the same value sun uses in the kernel to scale the * load averages */ job.time = (u_long)(256.0 * load); (void)sendcntrl(); } /*------------------------------------------------------------------------- * move * * move a pid around in the queue *------------------------------------------------------------------------- */ move(argc, argv) int argc; char **argv; { long temp; extern struct request job; extern long atol(); if (argc != 3){ printf("Usage: move pid rank\n"); return; } job.type = MOVECMD; if ((temp = atol(argv[1])) <= 0){ printf("Bad pid: %ld\n",temp); return; } job.uid = (u_long)temp; if ((temp = atol(argv[2])) <= 0){ printf("Bad rank: %ld\n",temp); return; } job.time = (u_long)temp; (void)sendcntrl(); } /*------------------------------------------------------------------------- * purge * * tell the server to blow away all the jobs in its queue. * NOTE: for saftey you must type purge all! *------------------------------------------------------------------------- */ purge(argc, argv) int argc; char **argv; { extern struct request job; if (argc != 2){ printf("Usage: purge all\n"); return; } /* * next arg must be a prefix of the word: all */ if (strncmp(argv[1], "all", strlen(argv[1])) != 0){ printf("Usage: purge all\n"); return; } /* * clean up the queue server */ job.type = PALLCMD; (void)sendcntrl(); } /*------------------------------------------------------------------------- * run * * tell the server to run listed job[s] regardless of the load *------------------------------------------------------------------------- */ run(argc, argv) int argc; char **argv; { register int i; long temp; struct passwd *pwd; extern int uid; extern struct request job; extern long atol(); extern int strncmp(); extern int endpwent(); extern struct passwd *getpwnam(); if (argc < 2){ printf("Usage: run [pid(s)] [-u user(s)]\n"); return; } if (strncmp(argv[1], "-u", 2) == 0){ /* * run all jobs owned by user (-u option) */ if (argc < 3){ printf("No user name\n"); printf("Usage: run [pid(s)] [-u user(s)]\n"); return; } job.type = RUSRCMD; for (i = 2; i < argc; i++){ /* * loop through each arg and run all that users jobs */ if ((pwd = getpwnam(argv[i])) == (struct passwd *)NULL){ /* * couldn't find the user go to next one */ printf("No such user: %s\n", argv[i]); continue; } /* * run all this users jobs */ job.uid = (u_long)pwd->pw_uid; printf("user %s: ", argv[i]); if (sendcntrl() == 0) printf("running.\n"); } (void)endpwent(); }else{ job.type = RJOBCMD; job.uid = uid; for (i = 1; i < argc; i++){ if ((temp = atol(argv[i])) <= 0){ printf("Bad pid: %ld\n",temp); continue; } job.time = (u_long)temp; printf("%ld: ", temp); if (sendcntrl() == 0) printf("running\n"); } } } /*------------------------------------------------------------------------- * status * * ask the server to update the status file. The status file contains the * current settings of those paramters the control program can adjust. *------------------------------------------------------------------------- */ status(argc, argv) int argc; char **argv; { int qcount; int full; int timerstop; u_long timesecs; u_long mqtime; int errorcount; #ifdef sun long loadlevel; #else double loadlevel; #endif extern int fclose(); FILE *fopen(); extern FILE *out; extern struct request job; if (argc != 1){ printf("Usage: status\n"); return; } job.type = STATUSCMD; /* * only print if ok */ if (sendcntrl() < 0) return; if ((out = fopen(STATUSFILE, "r")) == NULL){ printf("Cannot open status file\n"); return; } /* * the value 7 below is the argument count returned by fscanf. * should be the same as the number of variables read. */ #ifdef sun if (fscanf(out,"%d %d %d %ld %ld %d %ld",&qcount,&full,&timerstop, #else if (fscanf(out,"%d %d %d %ld %ld %d %lf",&qcount,&full,&timerstop, #endif ×ecs,&mqtime,&errorcount,&loadlevel) != 7){ printf("Status file has a data count error.\n"); (void)fclose(out); out = NULL; return; } (void)fclose(out); out = NULL; if ((timerstop == 1) && (qcount != 0)){ printf("WARNING: the timer is stopped (nonempty queue)\n"); printf("THIS SHOULD NEVER HAPPEN!\n"); printf("Try the timerset command to restart\n\n"); } printf("number of jobs in queue:\t%d ", qcount); if (qcount >= full) printf("\t\t*** WARNING: FULL QUEUE ***\n"); else printf("\n"); printf("current maximium queue size:\t%d ", full); if (full < LOWSIZE) printf("\t\t*** WARNING: SET TOO LOW ***\n"); else if (full > HIGHSIZE) printf("\t\t*** WARNING: SET TOO HIGH ***\n"); else printf("\n"); #ifdef sun printf("jobs queue at loads above:\t%3.2lf ",((double)loadlevel)/256.0); if (loadlevel < (LOWLOAD * 256)) printf("\t\t*** WARNING: SET TOO LOW ***\n"); else if (loadlevel > (HIGHLOAD * 256)) printf("\t\t*** WARNING: SET TOO HIGH ***\n"); else printf("\n"); #else printf("jobs queue at loads above:\t%3.2lf ", loadlevel); if (loadlevel < LOWLOAD) printf("\t\t*** WARNING: SET TOO LOW ***\n"); else if (loadlevel > HIGHLOAD) printf("\t\t*** WARNING: SET TOO HIGH ***\n"); else printf("\n"); #endif printf("jobs are queued no more than:\t%u (secs) ", mqtime); if (mqtime <= timesecs) printf("\t*** WARNING: SET TOO LOW ***\n"); else if (mqtime > HIGHMAX) printf("\t*** WARNING: SET TOO HIGH ***\n"); else printf("\n"); printf("load average is checked every:\t%u (secs) ",timesecs); if (timesecs > (mqtime/2)) printf("\t*** WARNING: SET TOO HIGH ***\n"); else if (timesecs < LOWCYCLE) printf("\t*** WARNING: SET TOO LOW ***\n"); else printf("\n"); printf("the number of server errors:\t%d\n", errorcount); } /*------------------------------------------------------------------------- * sizeset * * change the limit on the number of jobs allowed to be queued waiting for * the systems load average to drop. *------------------------------------------------------------------------- */ sizeset(argc, argv) int argc; char **argv; { long temp; extern struct request job; extern long atol(); if (argc != 2){ printf("Usage: sizeset size\n"); return; } job.type = QUEUESIZE; if ((temp = atol(argv[1])) <= 0){ printf("Bad value for size of queue: %ld\n", temp); return; } if (temp < LOWSIZE) printf("WARNING: %ld is a very small queue size limit.\n",temp); else if (temp > HIGHSIZE) printf("WARNING: %ld is a very large queue size limit.\n",temp); job.time = (u_long)temp; (void)sendcntrl(); } /*------------------------------------------------------------------------- * timerset * * change the number of second the server waits before checking the load * average to see if queued jobs can run *------------------------------------------------------------------------- */ timerset(argc, argv) int argc; char **argv; { long temp; extern struct request job; extern long atol(); if (argc != 2){ printf("Usage: timerset seconds\n"); return; } job.type = CHTIMER; if ((temp = atol(argv[1])) <= 0){ printf("Bad value for cycle time: %ld\n", temp); return; } if (temp < LOWCYCLE) printf("WARNING: %ld is a very small cycle time\n", temp); else if (temp > HIGHCYCLE) printf("WARNING: %ld is a very large cycle time\n", temp); job.time = (u_long)temp; (void)sendcntrl(); } /*------------------------------------------------------------------------- * waitset * * change the maximium time a job can remain queued. After that time the * job will run regardless of the current load. This is needed in case the * loadlimit is set too low, or not enough of the programs that are causing * the high load are NOT controlled by the server. This keeps jobs from * being queued up for excessive times. *------------------------------------------------------------------------- */ waitset(argc, argv) int argc; char **argv; { long temp; extern struct request job; extern long atol(); if (argc != 2){ printf("Usage: waitset seconds\n"); return; } job.type = MQTIMECMD; if ((temp = atol(argv[1])) <= 0){ printf("Bad value for waitset: %ld\n",temp); return; } job.time = (u_long)temp; if (job.time < LOWMAX) printf("WARNING: %u is a very short time limit for the max queue time\n",job.time); else if (job.time > HIGHMAX) printf("WARNING: %u is a very large time limit for the max queue time\n",job.time); (void)sendcntrl(); } @//E*O*F control/ldccmds.c// chmod u=r,g=r,o=r control/ldccmds.c echo Inspecting for damage in transit... temp=/tmp/shar$$; dtemp=/tmp/.shar$$ trap "rm -f $temp $dtemp; exit" 0 1 2 3 15 cat > $temp <<\!!! 65 156 1262 Makefile 112 382 2627 delete.c 28 126 907 globals.c 175 492 4008 ipc.c 476 1455 11267 ldccmds.c 856 2611 20071 total !!! wc control/Makefile control/delete.c control/globals.c control/ipc.c control/ldccmds.c | sed 's=[^ ]*/==' | diff -b $temp - >$dtemp if [ -s $dtemp ] then echo "Ouch [diff of wc output]:" ; cat $dtemp else echo "No problems found." fi exit 0
muller@sdcc3.UUCP (Keith Muller) (02/12/85)
This is part 5 of the load control system. Part 1 must be unpacked before any other part. Keith Muller ucbvax!sdcsvax!muller # This is a shell archive. Remove anything before this line, # then unpack it by saving it in a file and typing "sh file". # # Wrapped by sdcc3!muller on Sat Feb 9 13:54:09 PST 1985 # Contents: control/ldccmdtab.c control/ldcmain.c control/ldqmain.c # control/ldrmmain.c control/list.c echo x - control/ldccmdtab.c sed 's/^@//' > "control/ldccmdtab.c" <<'@//E*O*F control/ldccmdtab.c//' /*--------------------------------------------------------------------------- * commandtab.c - control * * tables of commands that the load program will call * *--------------------------------------------------------------------------- */ /* $Log$ */ #include "../h/control.h" char aborhelp[] = "terminate the server (disable load control)"; char delhelp[] = "remove specified jobs (by pid or user) from the queue"; char errhelp[] = "print the contents of the error logging file"; char helphelp[] = "get help on any command"; char listhelp[] = "list all the jobs queued for a specified user"; char loadhelp[] = "change the load level above which jobs are queued"; char longhelp[] = "list all queued jobs"; char movehelp[] = "move a processes position in the queue"; char purghelp[] = "remove ALL the jobs from the queue (the jobs terminate)"; char quithelp[] = "exit the control program"; char runhelp[] = "run specified jobs (by pid or user) from the queue"; char sizehelp[] = "change the maximium number of jobs that can be queued"; char statushelp[] = "print the current settings of changeable parameters"; char timerhelp[] = "change the number of seconds between load average checks"; char waithelp[] = "change the maximium time (seconds) a job can be queued"; /* * import the functions */ extern int abortserv(), delete(), errprint(), help(), list(), loadlimit(), longlist(); extern int move(), purge(), quit(), run(), sizeset(), status(), timerset(), waitset(); /* * the command table * the privledge column restrict certain commands to root (root only * as other changes could cause the server to be aborted with no * way to restart! */ struct cmd cmdtab[] = { /* command helpmessage routine privledge */ { "abort", aborhelp, abortserv, RESTRICT }, { "delete", delhelp, delete, NORESTRICT }, { "errors", errhelp, errprint, NORESTRICT }, { "help", helphelp, help, NORESTRICT }, { "list", listhelp, list, NORESTRICT }, { "loadlimit", loadhelp, loadlimit, RESTRICT }, { "longlist", longhelp, longlist, NORESTRICT }, { "move", movehelp, move, RESTRICT }, { "purge", purghelp, purge, RESTRICT }, { "quit", quithelp, quit, NORESTRICT }, { "run", runhelp, run, RESTRICT }, { "sizeset", sizehelp, sizeset, RESTRICT }, { "status", statushelp, status, NORESTRICT }, { "timerset", timerhelp, timerset, RESTRICT }, { "waitset", waithelp, waitset, RESTRICT }, { "?", helphelp, help, NORESTRICT }, { 0 }, }; int NCMDS = sizeof (cmdtab) / sizeof (cmdtab[0]); @//E*O*F control/ldccmdtab.c// chmod u=r,g=r,o=r control/ldccmdtab.c echo x - control/ldcmain.c sed 's/^@//' > "control/ldcmain.c" <<'@//E*O*F control/ldcmain.c//' /*------------------------------------------------------------------------- * main.c - control/ldc * * the ldc program is used to send commands to the server that can * alter the servers execution or display the servers current state * * Author: Keith Muller * University Of California, San Diego * Academic Computer Center C - 010 * La Jolla, Ca 92093 * ucbvax!sdcsvax!sdcc3!muller * (619) 452-6090 *------------------------------------------------------------------------- */ /* $Log$ */ #include "../h/common.h" #include "../h/control.h" #include <sys/uio.h> #include <sys/socket.h> #include <sys/un.h> #include <sys/time.h> #include <stdio.h> #include <setjmp.h> #include <errno.h> int fromatty; /* set to 1 if interactive, 0 otherwise */ char cmdline[256]; /* buffer for the command */ char *margv[64]; /* args in interactive mode */ int margc; /* count of args */ jmp_buf toplevel; /* jmp_buf used if a command is aborted premature */ /*------------------------------------------------------------------------- * main * *------------------------------------------------------------------------- */ main(argc, argv) int argc; char *argv[]; { register struct cmd *c; extern int uid; extern int isatty(); extern char *gets(); extern struct cmd *getcmd(); /* * set up the necessary overhead */ setup(); /* * could be a single line command */ if (--argc > 0) { if ((c = getcmd(*++argv)) == 0) quit(); if ((c->c_priv == RESTRICT) && (uid != 0)){ printf("?Privileged command\n"); quit(); } /* * run the entry from the command table */ (*c->c_handler)(argc, argv); /* * clean up */ quit(); } /* * see if the program is attached to a tty */ fromatty = isatty(fileno(stdin)); /* * on an interrupt return to this point! */ (void)setjmp(toplevel); for (;;) { if (fromatty){ printf("\nldc> "); (void)fflush(stdout); } /* * wait for io either on the tty or a status message * from the server is read. */ if (iowait() == 0) continue; if (gets(cmdline) == NULL) break; if (cmdline[0] == '\0') continue; /* * transform into a argv argc array */ makeargv(); /* * valid command? */ if ((c = getcmd(margv[0])) == 0) continue; if ((c->c_priv == RESTRICT) && (uid != 0)) { printf("?Privileged command\n"); continue; } /* * run the command from the command table */ (*c->c_handler)(margc, margv); } quit(); } /*------------------------------------------------------------------------- * onint * * when an interrupt occurs jump here! *------------------------------------------------------------------------- */ onint() { extern FILE *out; /* * if fromtty == 0 standard input is not a tty, so exit */ if (fromatty == 0) quit(); /* * if list or status command was interrupted, close off the * open file */ if (out != NULL) (void)fclose(out); /* * interactive so continue */ putchar('\n'); longjmp(toplevel, 1); } /*------------------------------------------------------------------------- * quit * *------------------------------------------------------------------------- */ quit() { extern int sock; extern char path[]; (void)close(sock); (void)unlink(path); exit(0); } /*------------------------------------------------------------------------- * getcmd * * find closest fit for the command typed in *------------------------------------------------------------------------- */ struct cmd * getcmd(comname) char *comname; { register char *p; register char *q; register struct cmd *c; register struct cmd *found; int nmatches; int longest; extern struct cmd cmdtab[]; longest = 0; nmatches = 0; found = (struct cmd *)0; for (c = cmdtab; (p = c->c_name) != (char *)0; c++) { for (q = comname; *q == *p++; q++){ /* * exact match? */ if (*q == '\0') return(c); } /* * name is a prefix */ if (*q == '\0') { if (q - comname > longest) { longest = q - comname; nmatches = 1; found = c; } else if ((q - comname) == longest) nmatches++; } } /* * More than one match found */ if (nmatches > 1){ printf("?Ambiguous command\n"); return((struct cmd *)0); } /* * No match found in table */ if (found == (struct cmd *)0) printf("?Invalid command\n"); return(found); } /*------------------------------------------------------------------------- * makeargv * * Slice a string up into argc/argv. *------------------------------------------------------------------------- */ makeargv() { register char *cp; register char **argp = margv; margc = 0; for (cp = cmdline; *cp != '\0'; ) { /* * skip over the blanks */ while (*cp == ' ') cp++; if (*cp == '\0') break; *argp++ = cp; margc = margc + 1; while ((*cp != '\0') && (*cp != ' ')) cp++; if (*cp == '\0') break; *cp++ = '\0'; } *argp++ = (char *)0; } /*------------------------------------------------------------------------- * Help command. * *------------------------------------------------------------------------- */ help(argc, argv) int argc; char *argv[]; { register struct cmd *c; register int i; register int j; register int w; char *arg; int columns; int width = 0; int lines; int len; extern int NCMDS; if (argc == 1) { printf("Commands may be abbreviated. Commands are:\n"); for (c = cmdtab; c < &(cmdtab[NCMDS-1]); c++) { len = strlen(c->c_name); if (len > width) width = len; } width = (width + 8) &~ 7; columns = 80 / width; if (columns == 0) columns = 1; lines = (NCMDS + columns - 1) / columns; for (i = 0; i < lines; i++) { for (j = 0; j < columns; j++) { c = cmdtab + j * lines + i; printf("%s", c->c_name); if (c + lines >= &(cmdtab[NCMDS-1])) { printf("\n"); break; } w = strlen(c->c_name); while (w < width) { w = (w + 8) &~ 7; putchar('\t'); } } } return; } while (--argc > 0) { arg = *++argv; if ((c = getcmd(arg)) == (struct cmd *)0) continue; printf("%-*s\t%s\n", 10, c->c_name, c->c_help); } } /*------------------------------------------------------------------------- * iowait * * wait for io on either stdin or a status message from server. *------------------------------------------------------------------------- */ iowait() { int readfds; int numfds; char msg; int fromlen = 0; extern int errno; extern int descsize; extern int sockmask; extern int stdinmask; extern int sock; for(;;){ readfds = sockmask | stdinmask; numfds = select(descsize,&readfds,(int *)0,(int *)0,(struct timeval *)0); if ((numfds < 0) && (errno != EINTR)){ perror("select failed"); quit(); } if (numfds <= 0) continue; if (readfds & stdinmask) return(1); if (recvfrom(sock,&msg,sizeof(msg),0,(struct sockaddr *)0,&fromlen)<=0){ perror("Failed message from server"); quit(); } switch(msg){ case POLLCMD: printf("A new server just started\n"); break; default: printf("Unexpected message from server\n"); break; } return(0); } } @//E*O*F control/ldcmain.c// chmod u=r,g=r,o=r control/ldcmain.c echo x - control/ldqmain.c sed 's/^@//' > "control/ldqmain.c" <<'@//E*O*F control/ldqmain.c//' /*------------------------------------------------------------------------- * ldqmain.c - control/ldq * * the ldq program is used to print the contents of the load queue. * The same function is also part of the ldc command. * * Author: Keith Muller * University Of California, San Diego * Academic Computer Center C - 010 * La Jolla, Ca 92093 * ucbvax!sdcsvax!sdcc3!muller * (619) 452-6090 *------------------------------------------------------------------------- */ /* $Log$ */ #include "../h/control.h" #include <stdio.h> /*------------------------------------------------------------------------- * main * *------------------------------------------------------------------------- */ main(argc, argv) int argc; char *argv[]; { if (argc > 2){ printf("usage: ldq [-a] [user]\n"); exit(1); } /* * set up the necessary overhead */ setup(); /* * call the correct list version (same as in ldc). */ if ((argc == 2) && (strcmp(argv[1],"-a") == 0)){ /* * decrement the argc count to get rid of the -a flag. */ argc = 1; longlist(argc,argv); }else{ /* * must be a short list call */ list(argc,argv); } /* * clean up the sockets */ quit(); } /*------------------------------------------------------------------------- * onint * * when an interrupt occurs jump here! *------------------------------------------------------------------------- */ onint() { printf("\n....interrupted\n"); quit(); } /*------------------------------------------------------------------------- * quit * *------------------------------------------------------------------------- */ quit() { extern int sock; extern char path[]; (void)close(sock); (void)unlink(path); exit(0); } @//E*O*F control/ldqmain.c// chmod u=r,g=r,o=r control/ldqmain.c echo x - control/ldrmmain.c sed 's/^@//' > "control/ldrmmain.c" <<'@//E*O*F control/ldrmmain.c//' /*------------------------------------------------------------------------- * ldrmmain.c - control/ldrm * * the ldrm program is used to delete jobs from the load queue. * The same function is also part of the ldc command. * * Author: Keith Muller * University Of California, San Diego * Academic Computer Center C - 010 * La Jolla, Ca 92093 * ucbvax!sdcsvax!sdcc3!muller * (619) 452-6090 *------------------------------------------------------------------------- */ /* $Log$ */ #include "../h/control.h" #include <stdio.h> /*------------------------------------------------------------------------- * main * *------------------------------------------------------------------------- */ main(argc, argv) int argc; char *argv[]; { if (argc < 2){ printf("useage: ldrm [pids] [-u users]\n"); exit(1); } if (strncmp(argv[1], "-u", 2) == 0){ /* * remove all jobs by user (-u option) */ if (argc < 3){ printf("No user name.\n"); printf("Usage: ldrm [pids] [-u users]\n"); return; } } /* * set up the necessary overhead */ setup(); /* * call the delete routine */ delete(argc, argv); /* * clean up the sockets */ quit(); } /*------------------------------------------------------------------------- * onint * * when an interrupt occurs jump here! *------------------------------------------------------------------------- */ onint() { printf("\n....interrupted\n"); quit(); } /*------------------------------------------------------------------------- * quit * *------------------------------------------------------------------------- */ quit() { extern int sock; extern char path[]; (void)close(sock); (void)unlink(path); exit(0); } @//E*O*F control/ldrmmain.c// chmod u=r,g=r,o=r control/ldrmmain.c echo x - control/list.c sed 's/^@//' > "control/list.c" <<'@//E*O*F control/list.c//' /*----------------------------------------------------------------------- * list.c - control program * * this file contains the commands: longlist and list which are common * to both ldc and ldq. *----------------------------------------------------------------------- */ /* $Log$ */ #include "../h/common.h" #include "../h/control.h" #include <sys/time.h> #include <stdio.h> #include <pwd.h> struct hashbuck names[HASHMOD]; /*---------------------------------------------------------------------------- * getuname * * keep a local hash table of recently requested uids. Speeds up the list * commands when repeatively invoked by ldc. This is done because looking * up passwd entries are expensive. So expensive that it SHOULD NEVER be * done by the server. The server NEVER SHOULD look up passwd entries. This * routine helps unhashed (generic 4.2) passwd files greatly. It is even * worthwhile on newer dbm passwd file. *----------------------------------------------------------------------------- */ static char * getuname(huid) register int huid; { register struct hashbuck *hashptr; register struct passwd *pwd; extern struct passwd *getpwuid(); extern char *strncpy(); /* * hash and find the address of the bucket */ hashptr = &(names[huid % HASHMOD]); /* * if this is the user return the name */ if ((hashptr->buckuid == huid) && (hashptr->buckname[0] != '\0')) return(hashptr->buckname); /* * not the same user (or empty) so update this bucket. * collisions should be rare if the hashtable is of reasonable * size reletive to the size of the passwd file. */ if ((pwd = getpwuid(huid)) != (struct passwd *)NULL){ hashptr->buckuid = huid; (void)strncpy(hashptr->buckname, pwd->pw_name, PNAMSIZ); return(hashptr->buckname); } /* * bad uid, skip it */ return((char *)0); } /*---------------------------------------------------------------------------- * longlist * * tell the server to update the list of queued jobs in the list file. When * the server says the list is updated, show ALL the queued jobs to the user *----------------------------------------------------------------------------- */ longlist(argc, argv) int argc; char **argv; { int juid; u_long jpid; u_long jtime; int pos; int qcount; char *jname; char jcom[COMLEN]; struct timezone zone; struct timeval now; extern FILE *out; extern int gettimeofday(); extern int fclose(); FILE *fopen(); extern struct request job; if (argc != 1){ printf("Usage: longlist\n"); return; } job.type = LISTCMD; /* * only try to print the list if the server says all is ok! */ if (sendcntrl() < 0) return; if ((out = fopen(LISTFILE, "r")) == NULL){ printf("cannot open listfile file\n"); return; } /* * first entry in the queue is the qcount. if it is zero just say so */ if (fscanf(out,"%d",&qcount) != 1){ printf("No queue info available\n"); (void)fclose(out); out = NULL; return; } if (qcount < 1){ printf("Queue empty\n"); (void)fclose(out); out = NULL; return; } /* * at least one job is in the queue. */ printf(" rank\t pid\t user\t time(sec) commands\n"); printf("-------------------------------------------------------\n"); pos = 1; (void)gettimeofday(&now, &zone); while(fscanf(out,"%d %ld %ld %[^\n]",&juid,&jpid,&jtime,jcom) == 4){ printf(" %3d\t",pos++); printf(" %5u\t",jpid); if ((jname = getuname(juid)) != (char *)0) printf(" %-8.8s",jname); else printf(" %-8.8d",juid); printf(" %5ld",now.tv_sec - (long)jtime); printf(" %s\n",jcom); } /* * if eof is not set, file has an error */ if (feof(out) == 0) printf("Error in list file\n"); (void)fclose(out); out = NULL; } /*---------------------------------------------------------------------------- * list * * tell the server to update the list of queued jobs in the list file. When * the server says the list is updated, show ONLY this users jobs. *----------------------------------------------------------------------------- */ list(argc, argv) int argc; char **argv; { int juid; int cmpuid; u_long jpid; u_long jtime; int pos; int qcount; char *jname; char jcom[COMLEN]; int found; struct passwd *pwd; struct timezone zone; struct timeval now; extern int uid; extern FILE *out; extern struct passwd *getpwnam(); extern int gettimeofday(); extern int fclose(); FILE *fopen(); extern struct request job; if (argc > 2){ printf("Usage: list [user]\n"); return; } if (argc == 2){ /* * if a user is specified, look him up in the passwd file */ if ((pwd = getpwnam(argv[1])) == (struct passwd *)NULL){ printf("No such user: %s\n", argv[1]); (void)endpwent(); return; } cmpuid = pwd->pw_uid; jname = pwd->pw_name; (void)endpwent(); }else{ /* * no user specified, use the current users uid */ cmpuid = uid; if ((jname = getuname(uid)) == (char *)0){ printf("Cannot find you in the passwd file\n"); return; } } job.type = LISTCMD; /* * only try to print the list if the server says all is ok! */ if (sendcntrl() < 0) return; if ((out = fopen(LISTFILE, "r")) == NULL){ printf("cannot open listfile file\n"); return; } /* * first entry in the queue is the qcount. if it is zero just say so */ if (fscanf(out,"%d",&qcount) != 1){ printf("No queue info available\n"); (void)fclose(out); out = NULL; return; } if (qcount < 1){ printf("Queue empty\n"); (void)fclose(out); out = NULL; return; } /* * at least one job is in the queue. Look for a job owned by the * specific user. */ found = 0; pos = 0; while(fscanf(out,"%d %ld %ld %[^\n]",&juid,&jpid,&jtime,jcom) == 4){ pos++; if (cmpuid == juid){ /* * found a job owned by the user */ found = 1; break; } } if (found == 0){ /* * no jobs found, either an error or no jobs for that user * is in the queue. */ if (feof(out) == 0){ printf("Error in list file\n"); }else{ printf("No jobs in queue for: %s\n", jname); } (void)fclose(out); out = NULL; return; } (void)gettimeofday(&now, &zone); printf(" rank\t pid\t user\t time(sec) commands\n"); printf("-------------------------------------------------------\n"); printf(" %3d\t %5u\t %-8.8s",pos++, jpid, jname); printf(" %5ld",now.tv_sec - (long)jtime); printf(" %s\n",jcom); while(fscanf(out,"%d %ld %ld %[^\n]",&juid,&jpid,&jtime,jcom) == 4){ /* * only print this users jobs */ if (cmpuid != juid){ pos++; continue; } printf(" %3d\t %5u\t %-8.8s",pos++, jpid, jname); printf(" %5ld",now.tv_sec - (long)jtime); printf(" %s\n",jcom); } if (feof(out) == 0) printf("Error in list file\n"); (void)fclose(out); out = NULL; } @//E*O*F control/list.c// chmod u=r,g=r,o=r control/list.c echo Inspecting for damage in transit... temp=/tmp/shar$$; dtemp=/tmp/.shar$$ trap "rm -f $temp $dtemp; exit" 0 1 2 3 15 cat > $temp <<\!!! 66 365 2506 ldccmdtab.c 373 968 7150 ldcmain.c 88 195 1754 ldqmain.c 87 185 1736 ldrmmain.c 297 910 6776 list.c 911 2623 19922 total !!! wc control/ldccmdtab.c control/ldcmain.c control/ldqmain.c control/ldrmmain.c control/list.c | sed 's=[^ ]*/==' | diff -b $temp - >$dtemp if [ -s $dtemp ] then echo "Ouch [diff of wc output]:" ; cat $dtemp else echo "No problems found." fi exit 0