From decwrl!purdue!haven!aplcen!ginosko!uunet!allbery Thu Aug 3 08:48:23 PDT 1989 Article 957 of comp.sources.misc: Path: decwrl!purdue!haven!aplcen!ginosko!uunet!allbery From: allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc) Newsgroups: comp.sources.misc Subject: v07i065: Gosip -- fixes a major bug Message-ID: <59944@uunet.UU.NET> Date: 9 Jul 89 00:32:52 GMT Sender: allbery@uunet.UU.NET Reply-To: Andreas Pagel Organization: Mathematics Dept., Warwick University, UK Lines: 2993 Approved: allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc) Posting-number: Volume 7, Issue 65 Submitted-by: mauhk@cu.warwick.ac.uk (Andreas Pagel) Archive-name: gosip2 [Too late.... ++bsa] I noticed a terrible error in the gosip shar I sent you recently, in that I failed to specify customisation correctly. I also noticed you haven't sent it out yet, so could you post this (hopefully) correct version instead? thanks, Andreas. ---------------------------------cut here--------------------------------- #! /bin/sh # This is a shell archive. Remove anything before this line, then unpack # it by saving it into a file and typing "sh file". To overwrite existing # files, type "sh file -c". You can also feed this as standard input via # unshar, or by typing "sh 'History' <<'END_OF_FILE' XAPS Gosip: History X================== X XThe original idea was conceived by Jonathan Hughes of Warwick University, back Xin 1987. He simply created two writable files, gossip and help, in his home Xdirectory and invited people to edit them: gossip for general chatting, and Xhelp for queries about the UNIX system or programming assignments. X XI soon realised that if two people edited a file simultaneously, only one Xperson's changes would be saved. So I wrote my first ever shell program, Xsomething like: X Xif test -f tmp.gossip Xthen X echo "Sorry, gossip is being edited." X cat text.gossip Xelse X touch tmp.gossip X ded text.gossip # ded is the local editor X rm temp.gossip Xfi X Xand a similar one for the help file. Took me weeks to get that right! This Xevolved for a bit, but didn't really get drastically better. As more gosip Xfiles were created, Geoff Rimmer had the idea of keeping just one version of Xthe source, hardlinked to all the different gosip files, with all occurrences Xof "gossip" replaced by "$0". He also thought of some options to give it, eg. Xto send the file to a printer, or to list it rather than edit it. X XMike Taylor deserves a mention as being the one to polish up this basic idea Xover the next year or so. He added in bits to keep a record of who edited Xwhich file, and he wrote a separate shell program (lu) that would list this Xinformation, and would also print a short summary of the status of each file X(the basic help and gossip files soon expanded to include events, suggest, Xjokes, etc.). X XSome of these early versions had some pretty bad bugs in. Of particular note Xwas the time that the shell script was made setuid, in order to prevent a Xcertain person from deleting text.gossip. Of course, one could then spawn a Xsetuid shell from the editor - I had about a week of access to Mike's code Xbefore he finally noticed this security hole. X XA major problem all the time was that the lock files/directories (both were Xused at various stages) used to ensure exclusive access would frequently not Xbe deleted after a given file was no longer being edited. The final version Xof the shell script for gosip included code that would delete lock files more Xthan half an hour old, on the assumption that their continued existence was a Xmanifestation of this problem. There were also a few other bugs in it, and it Xwas quite slow too. X XAt this point, I began to re-write the whole thing in C. I was still fairly Xnew to C at that stage of my life, and tried simply to convert the shell Xversion into C, almost on a line for line basis. The program went through a Xlot of refinements, most of which are recorded in the file 'notes' - at Xaround version 31 I had the brilliant idea of keeping a list of my changes in Xthis file. X XFor the user, the C version offered greater speed, and that was about all. XHowever, the mutual exclusion problem was finally solved in version 73, and Xthere was much rejoicing. X XAndreas Pagel. END_OF_FILE if test 2950 -ne `wc -c <'History'`; then echo shar: \"'History'\" unpacked with wrong size! fi # end of 'History' fi if test -f 'Makefile' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'Makefile'\" else echo shar: Extracting \"'Makefile'\" \(211 characters\) sed "s/^X//" >'Makefile' <<'END_OF_FILE' X# Makefile for gosip (rather short because I lost the previous one). X XCFLAGS= Xsources=cat.c control.c edit.c history.c lu.c main.c util.c X Xreal: X $(CC) $(CFLAGS) -DREAL -O -o real $(sources) X rm *.o X strip real END_OF_FILE if test 211 -ne `wc -c <'Makefile'`; then echo shar: \"'Makefile'\" unpacked with wrong size! fi # end of 'Makefile' fi if test -f 'README' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'README'\" else echo shar: Extracting \"'README'\" \(4961 characters\) sed "s/^X//" >'README' <<'END_OF_FILE' XAPS Gosip X========= X XGosip is a program that allows a user community to interchange lively debate, Xfriendly banter, helpful insights, or anything else, by means of editing a Xglobally writable (set of) file(s). Gosip itself is also a term used to stand Xfor any particular file's name, a bit like the shell meta character ``*''. [1] X XThe file 'History' gives some details on how the whole thing evolved, but the Xbasic idea is that the compiled program is (hard-)linked to whatever files you Xwish to support, eg. help. Then when anyone runs 'help', the program invokes Xan editor on the file text.help while ensuring exclusive access to that file. X XObviously the binaries need to be kept in a directory which is in every Xpotential user's path. Here at Warwick, we have a system called 'Newwords+' Xwhich provides not only gosip, but quite a few other extra commands written by Xthe users for the user community. It basically works by putting a Xsubdirectory of the newwords+ administrator in every subscriber's path. X XContents X======== X XIn the shar file you should find the source files edit.c main.c util.c lu.c Xhistory.c cat.c control.c with corresponding header files plus global.h. Dw Xis a shell script that displays changes in gosip files [3], and finally setup Xis a shell program that installs gosip files (more below). X XIn addition, the following manual pages are supplied: allgoss.1, lu.1, dw.1, Xgosip.1. Other documentation provided: README you already know about, History Xas mentioned above and notes, which is mainly a record of changes by version Xnumber. X XCustomisation X============= X XYou will need to alter some file and directory names and other #define's: X X(1) In global.h, change DATA_FILE_DIRECTORY to a directory which you [will] X own and which can be accessed by all potential gosip users. X X(2) Change SECRET_DIR to something not easily guessed. X X(3) In main.c, function init_file_names(), change the names of history and X last files (and lock files if used). The history and last files should X not be edited directly by anyone: giving them unusual names in an X unreadable directory makes this less likely to occur. X X(4) In setup, change the definitions of the above accordingly. X X(5) In control.h, you might like to specify yourself as the SUPER_USER. X SUPER_USER is the only one who can use the -# and -@ options, which are X used to deny or restore access to gosip (normally used when a new version X is being installed). You might like to change DOWN_FILE as well. X X(6) In global.h, alter MAX_FILE_LENGTH to reflect your choice of file names. X XFor quite a while lockf() did not work on our system. I have left in the Xprevious locking code, based on lock files and flock(), which you can use if Xyour lockf() is broken: add 'CFLAGS=-DLOCKF_BROKEN' to the makefile, and alter X'setup' so that DATA_FILE_DIRECTORY has global write permission. [2] X XThe manual pages will need a few minor changes: you will need to alter the Xlist of gosip files to specify those you choose to provide. X XOf course, they may be lots of portability problems. I don't know which Xsystem calls are specific to this version of UNIX (SunOS 4.0.1). X XInstallation X============ X XTo install gosip, run "setup gosip", where gosip should be replaced with Xwhatever files you wish to install. Setup will create the directory for data Xfiles if it doesn't already exist - note that it must be owned by you. The Xdirectory for data files should not be used to keep anything other than what Xgosip puts there. X XProblems X======== X XAs I've already said, I really don't know what system calls will or will not Xwork on your system. I will be pleased to receive bug reports or suggestions Xfor modifications mailed to 'mauhk@uk.ac.warwick'. However, since term ends Xon 1 July 1989 and I will go home then, I will not be able to reply to mail Xreceived after that date. I do, though, expect to come back for a day at some Xpoint during the summer so I will still see any mail sent. X XAndreas Pagel Magician, Programmer and Mathematician XUUCP: ...!mcvax!ukc!warwick!mauhk University of Warwick XJANET: mauhk@uk.ac.warwick Coventry XARPA: mauhk%cu.warwick@nss.cs.ucl.ac.uk Great Britain X X XFootnotes X========= X X[1] Gosip files used to be known as "the gossip files" in the manual pages X and colloquially. When I rewrote gosip in C, I needed to compile test X versions. Obviously I needed a name other than gossip by which to call X the binary, so I chose gosip. From this, the current usage of gosip X arose. X X[2] I'm not sure whether this will suffice. I haven't got time to test that X aspect, since I'm running out of time in which to post the program. X X[3] Dw's name comes from 'dw gosip' meaning "deal with gosip". I understand X that Bourne shell functions are not universal, so this program may be X difficult to port to some systems. END_OF_FILE if test 4961 -ne `wc -c <'README'`; then echo shar: \"'README'\" unpacked with wrong size! fi # end of 'README' fi if test -f 'allgoss.1' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'allgoss.1'\" else echo shar: Extracting \"'allgoss.1'\" \(382 characters\) sed "s/^X//" >'allgoss.1' <<'END_OF_FILE' X.TH ALLGOSS 1 "25 February 1989" X.SH NAME Xallgoss \- prints a list of all gosip files X.SH SYNOPSIS Xallgoss X.SH DESCRIPTION Xallgoss prints a list of all gosip files. X.SH SEE ALSO Xdw(1), lu(1), gossip(1), events(1), etc. X.SH BUGS XIf the gosip file 'frog' exists, but the corresponding 'text.frog' has been Xdeleted by some unfriendly lifeform, frog will not be included in the output. END_OF_FILE if test 382 -ne `wc -c <'allgoss.1'`; then echo shar: \"'allgoss.1'\" unpacked with wrong size! fi # end of 'allgoss.1' fi if test -f 'cat.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'cat.c'\" else echo shar: Extracting \"'cat.c'\" \(2794 characters\) sed "s/^X//" >'cat.c' <<'END_OF_FILE' X#include X#include X X#include "global.h" X#include "cat.h" X#include "util.h" X#include "history.h" X X/* void page() X * X * If the stdout is connected to a terminal, page will fork off the pager in X * the environment variable PAGER, or more(1) if PAGER is not defined. It X * makes all subsequent output to stdout go through the pager. Any errors, X * eg. from fork() or pipe(), cause an exit. Note that the parent becomes X * the pager, and that the return happens in the child. X */ Xvoid page() X{ X int filedes[2], pid; X char *pager = getenv ( "PAGER" ), *tmp; X X if ( ! isatty ( 1 ) ) X return; X error ( pipe ( filedes ), "Couldn't create pipe" ); X error ( pid = fork (), "Couldn't fork" ); X if ( pid == 0 ) X { X error ( close ( filedes[0] ), "Close failed" ); X error ( dup2 ( filedes[1], 1 ), "dup2 failed for output" ); X error ( close ( filedes[1] ), "Close failed" ); X return; /* error() will cause an exit if anything */ X } /* fails, which closes file descriptors. */ X X error ( close ( filedes[1] ), "Close failed" ); X error ( dup2 ( filedes[0], 0 ), "dup2 failed for input" ); X error ( close ( filedes[0] ), "Close failed" ); X if ( ! pager ) /* here though, there should really be a */ X pager = "more"; /* kill(pid) before an exit, not error() */ X else /* get just the pager, not options */ X for ( tmp = pager; *tmp != '\0'; tmp++ ) X if ( *tmp == ' ' ) X *tmp = '\0'; X execlp ( pager, pager, (char *) 0 ); X (void) fprintf ( stderr, "Couldn't invoke your pager.\n" ); X perror ( pager ); X (void) kill ( SIGTERM, pid ); X exit ( 2 ); X} X X/* void catfile ( type ) X * X * Catfile will print the gosip file, using the program in the environment X * variable PAGER to display it. It forks off a process to run the pager and X * feeds it, via a pipe, a linefeed, the existing gosip file, and another X * linefeed. X * X * type : controls the type of message passed to lastedit() and history. X */ Xvoid catfile ( type ) Xenum edit_type type; X{ X FILE *fp; X X if ( type != raw ) X { X update_history ( time ( (time_t *) 0 ), 0, type == being_edited ? edit_failed : list ); X page(); X if ( type == being_edited ) /* can't just use lastedit ( AUTO ) here, */ X lastedit ( EDIT ); /* since status might have changed. */ X else X lastedit ( AUTO ); X (void) putchar ( '\n' ); X } X X if ( fp = fopen ( text_file, "r" ) ) X { X int c; X X while ( ( c = getc ( fp ) ) != EOF ) X (void) putchar ( (char) c ); X (void) fclose ( fp ); X } X else X { X (void) printf ( "\n\n", file ); X exit ( 2 ); X } X X if ( type != raw ) X (void) putchar ( '\n' ); X exit ( 0 ); X} END_OF_FILE if test 2794 -ne `wc -c <'cat.c'`; then echo shar: \"'cat.c'\" unpacked with wrong size! fi # end of 'cat.c' fi if test -f 'cat.h' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'cat.h'\" else echo shar: Extracting \"'cat.h'\" \(1080 characters\) sed "s/^X//" >'cat.h' <<'END_OF_FILE' X/* The cat module contains the code that will simply print the gosip file. X */ X X/* void page() X * X * Page causes all subsequent output to be piped via a pager. If the X * environment variable PAGER is set, it is used (without options), otherwise X * more(1). X */ Xextern void page(); X X/* void catfile ( type ) X * X * The function prints lastedit information, and then the gosip file itself. X * It never returns, but calls exit(). X * X * type : controls the last usage message text. X */ Xextern void catfile(); X Xenum edit_type /* used to specify type of edit. */ X{ X normal, /* default edit */ X being_edited, /* tells catfile() to send "being edited" string */ X /* to lastedit() */ X abort, /* tells edit() not to call catfile() if gosip is being X edited. */ X raw, /* tells catfile to print gosip file without any extra X information, and without a pager */ X cflag /* means gosip was invoked with the c flag. Used X exclusively by Geoff's program. */ X}; END_OF_FILE if test 1080 -ne `wc -c <'cat.h'`; then echo shar: \"'cat.h'\" unpacked with wrong size! fi # end of 'cat.h' fi if test -f 'control.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'control.c'\" else echo shar: Extracting \"'control.c'\" \(4456 characters\) sed "s/^X//" >'control.c' <<'END_OF_FILE' X#include "global.h" X#include "control.h" X#include "util.h" X X/* byte parse ( line, code, term, file ) X * X * This routine will parse the line its given, picking out usercode, terminal X * and file. The format of the line should be ":code,term,file!". If term X * is a null pointer, just the code will be returned. X * X * Parse() returns 0 if the line didn't begin with a ':', otherwise it X * segmentation faults if the rest of the line doesn't contain the right X * number of commas and exclamation marks. If it does, parse() returns 1. X * X * line : points to the line to be parsed. X * code : the code will be written where this points. X * term : the terminal will be written where this points. X * file : the file will be written where this points. X */ Xstatic byte parse ( line, code, term, file ) Xchar *line, *code, *term, *file; X{ X if ( *line++ != ':' ) X return 0; X while ( *line != ',' ) X *code++ = *line++; X *code = '\0'; X if ( ! term ) X return 1; X while ( *++line != ',' ) X *term++ = *line; X *term = '\0'; X while ( *++line != '!' ) X *file++ = *line; X *file = '\0'; X return 1; X} X X/* byte add_user() X * X * If the gosip files are down, add_user() will check DOWN_FILE for the X * user's code. If he is mentioned, it will return 1. If he isn't, add_user X * checks he's on a terminal, and then adds a line to DOWN_FILE in the format X * ":,,!". X */ Xbyte add_user() X{ X FILE *fp = fopen ( DOWN_FILE, "r+" ); X char line[30], *term, *ttyname(); X X if ( ! fp ) X return 0; X (void) fseek ( fp, 0L, 0 ); X while ( fgets ( line, 29, fp ) ) X { X char code[10]; X X if ( parse ( line, code, (char *) 0, (char *) 0 ) ) X if ( ! strcmp ( code, usercode() ) ) X { X (void) fclose ( fp ); X return 1; X } X } X if ( term = ttyname ( 2 ) ) /* get the terminal stderr is attached to */ X { /* stderr is least likely to be redirected */ X struct stat info; X X (void) fprintf ( fp, ":%s,%s,%s!\n", usercode(), term, file ); X (void) fclose ( fp ); X if ( stat ( term, &info ) == 0 ) /* make user's terminal world writeable */ X (void) chmod ( term, (int) info.st_mode & 07777 | 02 ); /* so he can */ X } /* be told when gosip is available again */ X return 0; X} X X/* void inform_users() X * X * This routine will attempt to write a message to each of the users in the X * DOWN_FILE, to tell them that gosip is up again. X */ Xvoid inform_users() X{ X FILE *fp = fopen ( DOWN_FILE, "r" ); X char line[40]; X X if ( ! fp ) X return; X while ( fgets ( line, 40, fp ) ) X { X char code[10], term[12], file[15]; X FILE *wr; X X if ( ! parse ( line, code, term, file ) ) X continue; X (void) printf ( "Informing %s ...", code ); X (void) fflush ( stdout ); X if ( wr = fopen ( term, "w" ) ) X { X struct stat info; X X (void) fprintf ( wr, "\007\r\n** The %s file is now in service again. **\r\n", file ); X (void) fclose ( wr ); X if ( stat ( term, &info ) == 0 ) /* futile attempt to protect the */ X (void) chmod ( term, (int) info.st_mode & 07775 ); /* terminal again */ X puts ( " done." ); X } X else X puts ( " couldn't write his terminal." ); X } X (void) fclose ( fp ); X} X X/* void check_if_up() X * X * Checks for the presence of DOWN_FILE. If it exists, it indicates that X * gosip is down. If the user is SUPER_USER, it just prints a warning X * message, otherwise it calls add_user() and prints an appropiate message X * depending on the return value. X */ Xvoid check_if_up() X{ X if ( access ( DOWN_FILE, F_OK ) == -1 ) X return; /* ought really to check that owner == SUPER_USER */ X else X if ( ! strcmp ( usercode(), SUPER_USER ) ) X { X (void) printf ( "Warning: %s is down.\n\n", file ); X return; X } X if ( add_user() ) X { X (void) printf ( "\007I've already told you that the %s files are down.\n", file ); X puts ( "You'll be told when service is restored, provided your terminal is writeable." ); X } X else X { X FILE *fp = fopen ( DOWN_FILE, "r" ); X char mes[80], *temp; X extern char *rindex(); X X (void) fgets ( mes, 80, fp ); X if ( temp = rindex ( mes, '\n' ) ) X *temp = '\0'; X (void) printf ( "Sorry, the %s file is down for maintenance.\n", file ); X (void) printf ( "\t... %s.\n", mes ); X puts ( "You will probably be informed when it returns to service." ); X } X exit ( 1 ); X} END_OF_FILE if test 4456 -ne `wc -c <'control.c'`; then echo shar: \"'control.c'\" unpacked with wrong size! fi # end of 'control.c' fi if test -f 'control.h' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'control.h'\" else echo shar: Extracting \"'control.h'\" \(1486 characters\) sed "s/^X//" >'control.h' <<'END_OF_FILE' X/* This module contains routines that can be used to stop people from using X * gosip. This would be needed, for example, if a new version was to be X * installed which used a different lock mechanism. X */ X X/* byte add_user() X * X * If the gosip files are down, add_user() will check DOWN_FILE for the X * user's code. If he is mentioned, it will return 1. If he isn't, X * add_user() checks he's on a terminal, and then adds a line to DOWN_FILE in X * the format ":,,!". X */ Xextern byte add_user(); X X/* void inform_users() X * X * This routine will attempt to write a message to each of the users in the X * DOWN_FILE, to tell them that gosip is up again. X */ Xextern void inform_users(); X X/* void check_if_up() X * X * Checks for the presence of DOWN_FILE. It's existance would indicate that X * gosip is down. If the user is SUPER_USER, it just prints a warning X * message, otherwise an entry is added to DOWN_FILE, if necessary, and an X * appropiate message printed. X */ Xextern void check_if_up(); X X#ifdef REAL X#define DOWN_FILE "/tmp/sutech" /* DOWN_FILE exists when gosip is down. */ X#else REAL /* It is created by the -# option and */ X#define DOWN_FILE "/tmp/frog" /* removed by the -@ option. */ X#endif REAL X X#define SUPER_USER "mauhk" /* SUPER_USER is immune to gosip being down */ X /* and is the only one who can use the -# */ X /* and -@ options. */ END_OF_FILE if test 1486 -ne `wc -c <'control.h'`; then echo shar: \"'control.h'\" unpacked with wrong size! fi # end of 'control.h' fi if test -f 'dw.1' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'dw.1'\" else echo shar: Extracting \"'dw.1'\" \(2672 characters\) sed "s/^X//" >'dw.1' <<'END_OF_FILE' X.TH DW 1 "10 June 1989" X.SH NAME Xdw - show changes in the gosip files. X.SH SYNOPSIS X.B dw [ [ ...]] X.SH DESCRIPTION XDw uses diff(1) to show changes in the specified gosip files. This Xsaves you having to wade through 40K of file just to find that the Xonly change is a spelling correction. On its own, dw will show Xcontext diffs for all gosip files (obtained from allgoss(1)); with Xarguments, differences for those files only. Note that the diffs are Xrun asynchronously, so doing dw for a lot of files will push the load Xaverage up quite a bit. X.sp XThe copies of the gosip files are kept in $HOME/.backups. If you do Xnot have such a directory, dw will not work. X.SH USAGE XDw first copies all the specified gosip files into $HOME/.backups, and Xrun diff(1) in the background for each. It then loops through all Xgosip files, until it finds one for which diff has completed. The Xchanges, if any, are displayed, followed by the last four lines of Xhistory information for that file. You are then prompted for a command. X.TP X.B b XBackup the new version of the current gosip file. Equivalent to just Xpressing return. X.TP X.B n XDo not back this file up. X.TP X.B c XBack this file up and check it again. You might want to use this Xcommand if the history information shows that the file has been edited Xin the last few minutes. X.TP X.B C XCheck this file again but do not back it up first. X.TP X.B e XBackup this file, then edit it. Dw will use gosip -c, with the extra Xfile being the context diffs. This flag causes gosip to use emacs to edit Xthe file, and load the context diffs into a separate buffer. X.sp XWhen the edit finishes, dw will automatically recheck that file. You Xmight say, why not just back up the new version, since you presumably Xknow what you changed, but you'd be surprised how many spelling errors Xyou spot looking over the diffs. X.TP X.B E XEdit the file but do not backup it up first. Useful if you wish to Xreplace some text some cheese head has deleted. X.TP X.B q XBackup up the current file and quit dw. This involves killing all Xdiffs that may still be running, and removing any temporary files. X.TP X.B Q XQuit without backing up the current file. X.TP X.B a XAdd another gosip file to those being checked. You are prompted for Xthe gosip file you wish to add. X.SH FILES X.TP 20 X.B $HOME/.backups XDirectory containing gosip file backups. X.SH SEE ALSO Xallgoss(1), lu(1), gossip(1). X.SH AUTHOR XAndreas Pagel, at Warwick University, UK. X.SH BUGS XThe tidying routine called by the quit option not work. I have no Xidea what is wrong, but quitting will entirely fail to delete all the Xfiles it should, or indeed to do anything useful at all. END_OF_FILE if test 2672 -ne `wc -c <'dw.1'`; then echo shar: \"'dw.1'\" unpacked with wrong size! fi # end of 'dw.1' fi if test -f 'edit.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'edit.c'\" else echo shar: Extracting \"'edit.c'\" \(10183 characters\) sed "s/^X//" >'edit.c' <<'END_OF_FILE' X#include X#include X#include X#include X X#include "edit.h" X#include "global.h" X#include "cat.h" X#include "control.h" X#include "util.h" X#include "history.h" X X#include X#include X X#define DEFAULT_EDITOR "ded" X#define MIN_AGE 4*60*60 /* minimum age of drivel files in secs */ X X#define smaller(a,b) a * 10 < b * 7 || b - a > 1700 X Xextern char *strcat(), *rindex(), *strncpy(); Xextern int strncmp(); X Xstatic int editor; /* pid of editor child */ Xstatic byte edit_over = 0; /* flag set by SIGCHLD */ Xstatic char *text_copy; /* file name of text_file's copy */ X X/* void tidy_up() X * X * Routine called on receiving a terminating signal, such as TERM, QUIT or X * HUP. It releases locks, and removes backup files before quitting. Also X * kills the editor. X */ Xstatic void tidy_up() X{ X (void) kill ( editor, SIGTERM ); X (void) lock ( REMVE ); X (void) unlink ( text_copy ); X (void) fprintf ( stderr, "\r\n\007** %s: received terminating signal - quitting.\n", file ); X exit ( 3 ); X} X X/* void force_off() X * X * This routine is called when SIGALRM is received. It prints a final X * warning to the user, and then kills his editor after waiting a further X * short time. This will cause him to stop using gosip. X */ Xstatic void force_off() X{ X puts ( "\r\n\007** Edit session terminating almost immediately. **\r" ); X sleep ( 5 ); X (void) kill ( editor, SIGTERM ); X} X X/* void child() X * X * Called whenever a SIGCHLD is received. It checks if the editor has X * actually exit()ed - it might have changed state by being suspended - and X * sets the global flag edit_over if it has. X */ Xstatic void child() X{ X if ( editor == wait3 ( (union wait *) 0, WNOHANG, (struct rusage *) 0 ) ) X edit_over = 1; X} X X/* void do_nothing() X * X * This routine does literally nothing. It is needed as you have to call X * something when you get SIGALRM. X */ Xstatic void do_nothing() X{ X} X X/* void manifesto() X * X * Checks for the file info.gosip and prints its contents. X * Currently unimplemented. X */ X/* Xstatic void manifesto() X{ X FILE *info_file; X char info_filename[MAX_LENGTH]; X int c; X X (void) strcpy ( info_filename, "info." ); X (void) strcat ( info_filename, file ); X if ( info_file = fopen ( info_filename, "r" ) ) X { X while ( ( c = getc ( info_file ) ) != EOF ) X (void) putchar ( (char) c ); X (void) fclose ( info_file ); X } X} X*/ X X/* char *copy ( source, destination ) X * X * Copies the source file into the destination file. If destination is null, X * a filename in /tmp is generated and used as destination. Returns the X * destination file on success, otherwise a null pointer. X * X * source : filename from which to copy. X * destination : filename to which to copy. X */ Xstatic char *copy ( source, destination ) Xchar *source, *destination; X{ X FILE *sp, *dp; X int c; X X if ( ! destination ) X { X char temp[6]; X X (void) strncpy ( temp, file, 5 ); X temp[5] = '\0'; X destination = tempnam ( "/tmp", temp ); X } X if ( ! destination ) X { X perror ( "Couldn't get a temp file" ); X return (char *) 0; X } X if ( ! ( sp = fopen ( source, "r" ) ) ) X { X perror ( "Couldn't open source file" ); X return (char *) 0; X } X if ( ! ( dp = fopen ( destination, "w" ) ) ) X { X perror ( "Couldn't open destination file" ); X (void) fclose ( sp ); X return (char *) 0; X } X while ( ( c = getc ( sp ) ) != EOF ) X if ( putc ( (char) c, dp ) == EOF ) X { X puts ( "Couldn't copy to destination file." ); X (void) fclose ( dp ); X (void) fclose ( sp ); X return (char *) 0; X } X (void) fclose ( sp ); X if ( fclose ( dp ) == EOF ) X { X perror ( "Error closing destination file" ); X return (char *) 0; X } X return destination; X} X X/* byte changed ( i_size ) X * X * This will compare the text_file against the copy, and returns 0 if they X * are identical in content, otherwise 1. That is, it also returns 1 if X * either file couldn't be read or some other error occured. It also X * contains code checking for an excessive reduction in the size of the copy, X * which asks the user to confirm that he wants to keep the smaller file. X * X * i_size : inital size of text_file. X */ Xstatic byte changed ( i_size ) Xint i_size; X{ X FILE *tp, *cp; X int c; X byte result = 0; X struct stat final; X X if ( stat ( text_copy, &final ) == 0 ) X if ( smaller ( final.st_size, i_size ) ) X { X char ans[10]; X X while ( 1 ) /* leave loop by 'break' */ X { X (void) printf ( "The %s file is now somewhat smaller - from %d down to %d bytes.\nDo you want to keep it like this? ", file, i_size, final.st_size ); X result = 1; X if ( ! fgets ( ans, 10, stdin ) ) X *ans = '\0'; X if ( ! strncmp ( "no", ans, 2 ) ) X { X (void) unlink ( text_copy ); X (void) free ( text_copy ); X result = 0; X break; X } X if ( ! strncmp ( "yes", ans, 3 ) ) X { X result = 2; /* flag proceed as normal */ X break; X } X puts ( "Answer 'yes' or 'no'.\n" ); X } X if ( result < 2 ) X { X (void) unlink ( text_copy ); X (void) free ( text_copy ); X return result; X } X } X X if ( result < 2 ) X { X if ( ! ( tp = fopen ( text_file, "r" ) ) ) X result = 1; X else X { X if ( ! ( cp = fopen ( text_copy, "r" ) ) ) X result = 1; X else X { X do X { X if ( ( c = getc ( tp ) ) != getc ( cp ) ) X { X result = 1; X break; X } X } X while ( c != EOF ); X (void) fclose ( cp ); X } X (void) fclose ( tp ); X } X } X X if ( ! copy ( text_copy, text_file ) ) X result = 0; X (void) unlink ( text_copy ); X (void) free ( text_copy ); X return result; X} X X/* void do_edit ( kind, extra_file ) X * X * This is the routine that actually lets you edit the gosip file. It X * fork()s off a process which becomes the editor. While that is running it X * check every ten seconds whether the DOWN_FILE exists, and stops the edit X * if it does. X * X * It also updates last_file & history_file, checks for files that shouldn't X * be in the directory and removes them, prints info about the file (not yet X * implemented), and deals with the file already being edited - normally by X * calling catfile(), but see desciption of 'kind' below. X * X * kind : specifes the type of edit. It is an enumerated type X * covering X * normal - straight edit X * abort - do not invoke catfile() if gosip is being edited. X * cflag - use emacs with extra_file as editor. X * extra_file : this file is passed to emacs as well as text_file if kind X * is cflag. This is only used by Geoff's program. X */ Xvoid do_edit ( kind, extra_file ) Xenum edit_type kind; Xchar *extra_file; X{ X int mask, init_size = -1; X FILE *fp; X time_t now; X struct stat info; X byte lock_status; X X mask = sigblock ( sigmask ( SIGALRM ) | sigmask ( SIGCHLD ) ); X (void) signal ( SIGINT, SIG_IGN ); X (void) signal ( SIGCHLD, child ); X (void) signal ( SIGTERM, tidy_up ); X (void) signal ( SIGHUP, tidy_up ); X (void) signal ( SIGQUIT, tidy_up ); X if ( ( lock_status = lock ( CREAT ) ) == -1 ) X { /* error creating lock */ X (void) printf ( "Please refer to your local %s maintainer.\n", file ); X exit ( 2 ); X } X if ( lock_status ) X { /* gosip is already being edited */ X if ( kind == abort || kind == cflag ) X { X lastedit ( EDIT ); X exit ( 1 ); X } X else X catfile ( being_edited ); X } X lastedit ( INFO ); X (void) putchar ( '\n' ); X X if ( fp = fopen ( last_file , "w" ) ) X { /* put user into last_file */ X (void) fprintf ( fp, "%s (%s)\n", gosname(), usercode() ); X if ( fclose ( fp ) == -1 ) X perror ( "Problem closing last file" ); X } X else X perror ( "Couldn't write last usage info" ); X X (void) time ( &now ); X#if 0 X manifesto(); /* print info about this gosip file */ X#endif X if ( stat ( text_file, &info ) == 0 ) X init_size = info.st_size; /* store initial size for history info. */ X if ( ! ( text_copy = copy ( text_file, (char *) 0 ) ) ) X exit ( 2 ); /* make copy which is actually edited */ X X if ( ( editor = fork() ) == -1 ) X { X perror ( "Fork failed" ); X (void) lock ( REMVE ); X (void) unlink ( text_copy ); X exit ( 2 ); X } X if ( editor == 0 ) X { X char *visual; X X if ( ! ( visual = getenv ( "VISUAL" ) ) ) X if ( ! ( visual = getenv ( "EDITOR" ) ) ) X visual = DEFAULT_EDITOR; X if ( kind == cflag ) X execlp ( visual, visual, extra_file, text_copy, (char *) 0 ); X else X execlp ( visual, visual, text_copy, (char *) 0 ); X (void) fprintf ( stderr, "Couldn't invoke your editor.\n" ); X perror ( visual ); X exit ( 2 ); X } X X (void) signal ( SIGALRM, do_nothing ); X while ( ! edit_over ) /* wait for child to die */ X { X static byte mes_delivered = 0; X FILE *fp; X X if ( ! mes_delivered ) X (void) alarm ( 10 ); X (void) sigpause ( mask ); X if ( ! ( mes_delivered || edit_over ) ) X if ( fp = fopen ( DOWN_FILE, "r" ) ) X { X char mes[80], *temp; X X (void) fgets ( mes, 79, fp ); X if ( temp = rindex ( mes, '\n' ) ) X *temp = '\0'; X (void) fclose ( fp ); X (void) printf ( "\r\n\007** %s is going down for maintenance. **\r\n", file ); X (void) printf ( " ... %s. \r\n", mes ); X puts ( "** Please stop editing now. **\r" ); X (void) printf ( "** You will probably be informed when %s is available again. **\r\n", file ); X mes_delivered = 1; X (void) add_user(); X (void) signal ( SIGALRM, force_off ); X (void) alarm ( 50 ); X } X } X X (void) alarm ( 0 ); X (void) sigsetmask ( mask ); X update_history ( now, init_size, changed ( init_size ) ? edit : no_change ); X (void) lock ( REMVE ); X X exit ( 0 ); X} END_OF_FILE if test 10183 -ne `wc -c <'edit.c'`; then echo shar: \"'edit.c'\" unpacked with wrong size! fi # end of 'edit.c' fi if test -f 'edit.h' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'edit.h'\" else echo shar: Extracting \"'edit.h'\" \(673 characters\) sed "s/^X//" >'edit.h' <<'END_OF_FILE' X/* void doedit ( kind, extra_file ) X * X * This routine handles editing of the gosip file. It takes care of all X * bookkeeping, and does not return - it calls exit(). X * X * kind : specifes the type of edit. It is an enumerated type X * covering X * normal - straight edit X * abort - do not invoke catfile() if gosip is being edited. X * cflag - use emacs and extra_file as editor. X * see also "cat.h" X * extra_file : this file is passed to emacs as well as the text_file if X * kind is cflag. This is only used by Geoff's program. X */ Xextern void do_edit(); END_OF_FILE if test 673 -ne `wc -c <'edit.h'`; then echo shar: \"'edit.h'\" unpacked with wrong size! fi # end of 'edit.h' fi if test -f 'global.h' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'global.h'\" else echo shar: Extracting \"'global.h'\" \(1007 characters\) sed "s/^X//" >'global.h' <<'END_OF_FILE' X#include X#include X#include X#include X X#define error(result,errmes) if ( (result) == -1 ) {perror(errmes); exit(2);} X#define MAX_GOSIP_LENGTH 8 /* max length of a gosip fileanme */ X#define MAX_FILE_LENGTH (sizeof(DATA_FILE_DIRECTORY)+MAX_GOSIP_LENGTH+13) X /* max. length of global file names - the additional 13 allows for X the longest prefix, "/Rush/text.", and terminating '\0' */ X X#ifdef REAL X#define DATA_FILE_DIRECTORY "/poppy/cs/upt/open/dubbin" X#else REAL X#define DATA_FILE_DIRECTORY "/poppy/ma/uhk/gtfd/.files" X#endif REAL X X#define SECRET_DIR "/Rush" X Xextern char *getenv(), *rindex(), *strcpy(); Xextern time_t time(); X Xextern char text_file[MAX_FILE_LENGTH], file[MAX_FILE_LENGTH]; Xextern char history_file[MAX_FILE_LENGTH], last_file[MAX_FILE_LENGTH]; X#ifdef LOCKF_BROKEN Xextern char lock_file[MAX_FILE_LENGTH]; X#endif LOCKF_BROKEN X Xtypedef char byte; /* use byte for small ints (-1 up to c.25) */ END_OF_FILE if test 1007 -ne `wc -c <'global.h'`; then echo shar: \"'global.h'\" unpacked with wrong size! fi # end of 'global.h' fi if test -f 'gosip.1' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'gosip.1'\" else echo shar: Extracting \"'gosip.1'\" \(3737 characters\) sed "s/^X//" >'gosip.1' <<'END_OF_FILE' X.TH GOSSIP 1 "25 February 1989" X.SH NAME Xelection, events, faith, flames, games, gossip, help, jokes, musik, politics, Xsmut, suggest, xian - Print or edit the relevant file. X.SH SYNOPSIS X.B gosip [-e|-l|-p[]|-r|-s|-t|-v|-x] X.SH DESCRIPTION XGosip (general term meaning any one of events, flames, etc.) allows you to Xedit a text file containing material contributed by other gosip users. When Xinvoked without flags, it tells you who last edited the file, then calls up Xthe editor in the environment variable VISUAL, failing that EDITOR, or vi if Xthat doesn't exist either. At this point you edit the file, enjoying other Xpeople's contributions, and adding your own as you wish. When you quit the Xeditor, gosip will store details of your edit, which you can examine using lu, Xbefore ending. X.sp XYour name is taken from the environment variable GOSNAME, if defined, Xotherwise from the variable NAME. X.sp XCertain general unwritten rules of etiquette regarding the editing of gosip Xfiles have evolved over time. X.IP + XLeave your name or alias after your contribution. Many people will Xwrite '*name*' or something similar after unattributed contributions, or even Xdelete them. There is a list of names and aliases near the beginning of the Xgossip file; feel free to add yours there. X.IP + XDoing global replaces, eg. turning all instance of 'and' to '&', is considered Xpretty tedious. X.IP + XDon't delete other people's contributions, just because you don't like them. XTo keep the size of the files reasonable, removing older material is OK, but Xtry to leave some context for any comments following those you delete. X.IP + XAltering what other people have written to make them look silly isn't Xconsidered clever or witty, but anti-social. X.IP + XMost people find it rather boring to read the same thing several times, so Xdon't put an indentical piece of text in more than one gosip files. X.PP XNote that the symbol :-) (called a smiley face) or some variation is often Xplaced beside text that is intended to be of a non-serious nature. X.SH OPTIONS X.TP X.B \-e XIf the gosip file is being edited, this is reported without listing it. X.TP X.B \-l XList the gosip file instead of editing it. The program in the environment Xvariable PAGER is used to display it, or more(1) if PAGER is not defined. X.TP X.B \-p[] XSend the gosip file to the (specified) printer. X.TP X.B \-r XInform the user as soon as gosip becomes free for editing. X.TP X.B \-s XReturn exit status 1 if the file is being edited, or zero if it isn't, without Xproducing any output. X.TP X.B \-t XReport whether gosip is being edited or not. X.TP X.B \-v XPrint the version number. Details of changes corresponding to each version Xnumber are currently available in ~mauhk/gtfd/doc/notes - at least at XWarwick. X.TP X.B \-x XAct as though called without arguments. It's use is that it overrides any Xdefault argument in GOSIP. X.SH DEFAULT OPTIONS XIf no flag (or -x) is present, the program will attempt to edit the file. If, Xhowever, gosip is being edited by someone else, it will be printed instead. X.sp XFor historical reasons, any single letter is equivalent to the '-l' Xoption, eg. 'gosip s' == 'gosip -l'. X.sp XYou can use the environment variable GOSIP to store a default option for Xgosip. That is, if gosip is invoked without flags, the flag stored in GOSIP Xwill be used. X.SH SEE ALSO Xallgoss(1), lu(1), dw(1). X.SH AUTHOR XOriginal concept by Jonathan Hughes. X.br XBasically written by Andreas Pagel. X.br XSubstantially modified by Geoff Rimmer. X.br XCompletely rewritten by Mike Taylor. X.br XCompletely re-rewritten by Andreas Pagel. X.SH BUGS XIf you are near your hard quota when trying to edit gosip, you might Xend up not being able to save it or even deleting it, so be careful. END_OF_FILE if test 3737 -ne `wc -c <'gosip.1'`; then echo shar: \"'gosip.1'\" unpacked with wrong size! fi # end of 'gosip.1' fi if test -f 'history.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'history.c'\" else echo shar: Extracting \"'history.c'\" \(5270 characters\) sed "s/^X//" >'history.c' <<'END_OF_FILE' X#include "global.h" X#include "util.h" X#include "history.h" X X#define RECENT 395724 /* entries older than this are pruned */ X#define MIN_LINES 40 /* history.gosip is not pruned to less than */ X /* this number of lines. */ X/* static FILE *his_prune() X * X * This routine will prune history.gosip. It first works out the number of X * entries it currently contains, and subtracts MIN_LINES to get the maximum X * number of entries to remove. It then reads in each entry, and copies it X * back into the file only if it is less than RECENT seconds old. X * X * If history.gosip fails to exist, his_prune() will try to create it. X * X * A pointer to history.gosip is returned, or a null pointer in case of X * errors. X */ Xstatic FILE *his_prune() X{ X FILE *his_file; X long start, end; X HIS_ENTRY entry; X time_t now; X int removable = 1; /* number of removable lines in history.gosip */ X struct stat info; X X (void) time ( &now ); X if ( stat ( history_file, &info ) == 0 ) X removable = 1 + info.st_size / sizeof ( HIS_ENTRY ) - MIN_LINES; X if ( ! ( his_file = fopen ( history_file, "a+" ) ) ) X { X perror ( "Couldn't open history file" ); X return (FILE *) 0; X } X start = end = 0L; X if ( fseek ( his_file, end, 0 ) == -1 ) X { X perror ( "Error seeking in history file" ); X (void) fclose ( his_file ); X return (FILE *) 0; X } X while ( fread ( (char *) &entry, sizeof ( entry ), 1, his_file ) ) X { X end = ftell ( his_file ); X if ( now - entry.date < RECENT || removable-- < 1 ) X { X if ( fseek ( his_file, start, 0 ) == -1 ) X { X perror ( "Error seeking in history file" ); X (void) fclose ( his_file ); X return (FILE *) 0; X } X if ( fwrite ( (char *) &entry, sizeof ( entry ), 1, his_file ) < 1 ) X { X perror ( "Error writing to history file" ); X (void) fclose ( his_file ); X return (FILE *) 0; X } X start = ftell ( his_file ); X } X if ( fseek ( his_file, end, 0 ) == -1 ) X { X perror ( "Error seeking in history file" ); X (void) fclose ( his_file ); X return (FILE *) 0; X } X } X if ( fseek ( his_file, start, 0 ) == -1 ) X { X perror ( "Error seeking in history file" ); X (void) fclose ( his_file ); X return (FILE *) 0; X } X return his_file; X} X X/* void update_history ( date, size1, how ) X * X * This routine is used to add an entry to history.gosip. It also X * automatically creates the file if it doesn't exist, and keeps it down to a X * reasonable size if it does. It is called by do_edit() and catfile() to X * record whenever a user read or edited the gosip file. X * X * Data stored consists of the time the edit commenced, the (gosip) name and X * usercode of the person invoking gosip and the initial and final size of X * the gosip file. The final size can be replaced by one of the following: X * listing - the gosip file was listed, not edited X * edit failed - the user tried to edit the gosip file, but it was X * already being edited. This entry is only stored if X * catfile() was invoked, not if gosip was called with the X * '-e' option. The idea behind this is that an entry is X * only made if a user actually got to see the text of the X * gosip file. X * unchanged - the user did not alter the gosip file during his edit. X * If the backup of the gosip is somehow deleted during the X * user's edit, gosip will obviously not be able to X * determine whether the file has changed, and will provide X * a final size. X * X * First his_prune() is called to remove older entries. It will return a X * file pointer to history.gosip, leaving it pointing to the correct place in X * the file to add the new entry. The new entry is then contructed - some X * information is passed in parameters as detailed below, the rest can be X * worked out. X * X * Finally the new entry is written and then history.gosip is truncated to X * reflect its new size, and chmod() is called to give it appropiate X * permissions. This should only be necessary if the file was created by X * update_history(). X * X * date : time of access to gosip file. X * size1 : initial size of file - only relevent if how == edit. X * how : type of access. X */ Xvoid update_history ( date, size1, how ) Xtime_t date; Xint size1; XMODE how; X{ X FILE *his_file; X X if ( his_file = his_prune() ) X { X HIS_ENTRY entry; X struct stat info; X long new_size; X X (void) strcpy ( entry.name, gosname() ); X (void) strcpy ( entry.user, usercode() ); X entry.date = date; X entry.how = how; X entry.size1 = size1; X entry.size2 = -1; X if ( stat ( text_file, &info ) == 0 ) X entry.size2 = info.st_size; X if ( fwrite ( (char *) &entry, sizeof ( entry ), 1, his_file ) < 1 ) X perror ( "Write of history information failed" ); X new_size = ftell ( his_file ); X if ( fclose ( his_file ) ) X perror ( "Error closing history file" ); X if ( truncate ( history_file, new_size ) ) X perror ( "Error adjusting size" ); X } X (void) chmod ( history_file, 0666 ); X} END_OF_FILE if test 5270 -ne `wc -c <'history.c'`; then echo shar: \"'history.c'\" unpacked with wrong size! fi # end of 'history.c' fi if test -f 'history.h' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'history.h'\" else echo shar: Extracting \"'history.h'\" \(1327 characters\) sed "s/^X//" >'history.h' <<'END_OF_FILE' X/* void update_history ( date, size1, how ) X * X * This will add an entry to history.gosip if it can. It will aso try to X * ensure that the file has the correct permissions, and will create it if it X * doesn't exist. Some of the information it works out itself, the rest is X * passed as arguments. X * X * It will also remove entries older than RECENT seconds from the file, X * provided at least MIN_LINES remain. X * X * date : time of access to gosip file. X * size1 : initial size of file - only relevent if how == edit. X * how : type of access. X */ Xextern void update_history(); X Xtypedef enum { edit, list, edit_failed, no_change } MODE; X Xtypedef struct /* entry in history.gosip */ X{ X char name[MAX_NAME_LEN]; /* name of user */ X char user[MAX_CODE_LEN]; /* code of user */ X time_t date; /* time at which they used gosip */ X int size1, size2; /* sizes, before & after, of text.gosip */ X MODE how; /* whether they edited or listed, etc. */ X} HIS_ENTRY; X X/* Portability warning: note that the above struct may not be stored the way X * on different machines. Here at Warwick, MAX_NAME_LEN had to carefully X * chosen to make the history entries compatible between the two computers X * from which gosip could be invoked. X */ END_OF_FILE if test 1327 -ne `wc -c <'history.h'`; then echo shar: \"'history.h'\" unpacked with wrong size! fi # end of 'history.h' fi if test -f 'lu.1' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'lu.1'\" else echo shar: Extracting \"'lu.1'\" \(2055 characters\) sed "s/^X//" >'lu.1' <<'END_OF_FILE' X.TH LU 1 "25 February 1989" X.SH NAME Xlu \- Print history information for gosip files. X.SH SYNOPSIS X.B lu [-[:]] [gosip-file [gosip-file ...]] X.SH DESCRIPTION XWithout arguments, lu prints a summary of information about all gosip files. XFor each gosip file, it gives the time when the last edit was commenced, its Xcurrent size and the name and the usercode of the last person to edit it. A Xtrailing '*' indicates the file is currently being edited. X.sp XWith arguments, some of the recent editing history for each gosip file is Xgiven. Each line corresponds to a person looking at the text of the gosip Xfile, and gives the time of access, the name and usercode of the person Xinvolved, the initial size, and information on the type of access. If two Xnumbers are given, this implies an edit of the gosip file, where the two Xnumbers are the initial and final sizes. X.sp X\'Listing' implies use of the '-l' option. 'Edit failed' means the user Xattempted to edit the gosip file, only to find it was already being edited; if Xthe '-e' option was used, an edit failed line is not recorded. 'Unchanged' Ximplies that the user edited gosip without making any changes. X.SH OPTIONS XThe number of lines to print from the history file can be specified Xby '-' or '-:'. The later form will list all Xentries dated within the last hours and minutes. Either option Xcan be given more than once, eg. X.sp X.RS Xlu events -10 gossip -15 flames help X.RE X.sp Xwhich will list 20 lines (the default) from events, 10 from gossip and 15 from Xflames and help. X.sp XAlternatively, the usage exemplified by X.sp X.RS Xlu -1: X.RE X.sp Xwill print changes for the past hour for all gosip files. X.sp XHistory information is currently kept for about 3 days. X.SH AUTHOR XOriginally Mike Taylor. X.br XModified by Andreas Pagel. X.br XRewritten by Andreas Pagel and incoporated into gosip. X.SH SEE ALSO Xallgoss(1), gossip(1), dw(1). X.SH BUGS XThe usage 'lu -0 gossip' is accepted and treated as 'lu -1 gossip'. Similar Xproblems with options '-:' and '-'. END_OF_FILE if test 2055 -ne `wc -c <'lu.1'`; then echo shar: \"'lu.1'\" unpacked with wrong size! fi # end of 'lu.1' fi if test -f 'lu.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'lu.c'\" else echo shar: Extracting \"'lu.c'\" \(9217 characters\) sed "s/^X//" >'lu.c' <<'END_OF_FILE' X#include "global.h" X#include "lu.h" X#include "cat.h" X#include "main.h" X#include "util.h" X#include "history.h" X X#include X#include X Xextern char *ctime(), *malloc(), *strcat(); X Xtypedef struct fmt X{ X union X { X time_t date; /* parameter - secs */ X int max_lines; /* maximum number of lines */ X } param; X byte time; /* whether time or length param */ X struct fmt *next; X} FORMAT; X Xstatic FORMAT *lines = NULL; X X/* byte gosip_file ( file_name ) X * X * This routine examines file_name and returns 1 if it matches text.[a-z]+, X * which is what valid gosip files would match, and provided that [a-z]+ bit X * is not longer than MAX_GOSIP_LENGTH. Zero otherwise. X * X * file_name : points to filename to check. X */ Xstatic byte gosip_file ( file_name ) Xchar *file_name; X{ X byte index; X char *temp; X X for ( index = 0; index < 5; index++ ) X if ( *file_name++ != "text."[index] ) X return 0; X if ( ! *file_name ) /* prefix on its own is illegal */ X return 0; X temp = file_name; /* to work out length */ X while ( *file_name != '\0' ) X if ( ! isalpha ( *file_name++ ) ) X return 0; X return file_name - temp <= MAX_GOSIP_LENGTH; X} X X/* byte allgoss ( files ) X * X * Returns an alphabetical list of gosip files in the array files. It X * obtains this information by searching DATA_FILE_DIRECTORY for files X * matched by gosip_file(). It returns 0 if no files could be found, either X * because of an error or because there weren't any; otherwise it returns the X * number of files found. X * X * files : the array in which the result is stored. X */ Xbyte allgoss ( files ) Xchar *files[FILE_NUM]; X{ X DIR *dirp; X struct dirent *dep; X byte num = 0, sorted = 0, index; X char text_dir[ sizeof ( DATA_FILE_DIRECTORY ) + sizeof ( SECRET_DIR ) ]; X X (void) strcpy ( text_dir, DATA_FILE_DIRECTORY ); X (void) strcat ( text_dir, SECRET_DIR ); X if ( ! ( dirp = opendir ( text_dir ) ) ) X { X perror ( "Couldn't read directory" ); X return 0; X } X /* obtain list of gosip files */ X for ( dep = readdir ( dirp ); dep; dep = readdir ( dirp ) ) X if ( gosip_file ( dep->d_name ) ) X { X if ( num >= FILE_NUM ) X { X (void) fprintf ( stderr, "Too many gosip files! (Max %d).\n", FILE_NUM ); X return 0; X } X if ( ! ( files[num] = malloc ( (unsigned) strlen ( dep->d_name ) - 4 ) ) ) X { X (void) fprintf ( stderr, "No memory.\n" ); X return 0; X } X (void) strcpy ( files[num++], dep->d_name + 5 ); X } X (void) closedir ( dirp ); X X while ( ! sorted ) /* sort gosip files alphabetically */ X { X char *temp; X X sorted = 1; X for ( index = 1; index < num; index++ ) X if ( strcmp ( files[index-1], files[index] ) > 0 ) X { X temp = files[index-1]; X files[index-1] = files[index]; X files[index] = temp; X sorted = 0; X } X } X if ( ! num ) X (void) fprintf ( stderr, "No files!\n" ); X return num; X} X X/* void history ( gosip ) X * X * History will list the last n lines from the file history.gosip, where n is X * determined by the contents of lines. X * X * This is done by building up a linked list of lines, and throwing away X * those that don't meet the requirements. (**write this out properly**) X * X * gosip : the file for which history is required. X */ X static void history ( gosip ) Xchar *gosip; X{ X extern char *sprintf(); X time_t now; X FILE *his_file; X HIS_ENTRY entry; X char line[120]; X int ent_num = 0; X struct one_entry X { X int number; /* used to check line number limit */ X char text[120]; /* the line that will be printed */ X struct one_entry *next; X } *first, *last, *temp; X X first = last = 0; X (void) time ( &now ); X init_file_names ( gosip ); X if ( ! ( his_file = fopen ( history_file, "r" ) ) ) X { X (void) printf ( "Couldn't open history file for %s.\n", file ); X return; X } X (void) printf ( "Editing history of %s:\n", file ); X while ( fread ( (char *) &entry, sizeof ( entry ), 1, his_file ) ) X { X ent_num++; X if ( lines->time == 0 || now - entry.date < lines->param.date ) X { X temp = last; X last = (struct one_entry *) malloc ( sizeof ( struct one_entry ) ); X if ( ! last ) X { X perror ( "No memory" ); X return; X } X if ( temp != 0 ) X temp->next = last; X last->number = ent_num; X last->next = 0; X X (void) sprintf ( line, "%3d %.16s %8s: %-27s ", ent_num, ctime ( &entry.date ), entry.user, entry.name ); X switch ( entry.how ) X { X default: X (void) sprintf ( last->text, "%s ***data error***\n", line ); X break; X case list: X (void) sprintf ( last->text, "%s%7d listing\n", line, entry.size2 ); X break; X case edit_failed: X (void) sprintf ( last->text, "%s%7d edit failed\n", line, entry.size2 ); X break; X case edit: X (void) sprintf ( last->text, "%s%7d%13d\n", line, entry.size1, entry.size2 ); X break; X case no_change: X (void) sprintf ( last->text, "%s%7d unchanged\n", line, entry.size2 ); X } X X if ( ! first ) X first = last; X else X if ( lines->time == 0 && last->number - first->number >= lines->param.max_lines ) X { X temp = first->next; X (void) free ( (char *) first ); X first = temp; X } X } X } X (void) fclose ( his_file ); X X while ( first ) X { X (void) printf ( "%s", first->text ); X temp = first->next; X (void) free ( (char *) first ); X first = temp; X } X X lastedit ( AUTO ); X} X X/* void summary() X * X * Prints up a summary of gosip file status. It gets a list of files from X * allgoss(), and prints a one line summary of their current state, taking X * information from history.gosip and last.gosip, as well at stat()ing X * text.gosip. X */ Xstatic void summary() X{ X char *files[FILE_NUM]; X byte index, num; X X if ( ! ( num = allgoss ( files ) ) ) X exit ( 2 ); X for ( index = 0; index < num; index++ ) X { X struct stat info; X FILE *lfp; X X init_file_names ( files[index] ); X (void) free ( files[index] ); X (void) printf ( "%-*s: ", MAX_GOSIP_LENGTH + 1, file ); X if ( stat ( last_file, &info ) == 0 ) X (void) printf ( "%.12s ", ctime ( &info.st_mtime ) + 4); X else X (void) printf ( "%-13s", "" ); X if ( stat ( text_file, &info ) == 0 ) X (void) printf ( "(%6d bytes ) ", info.st_size ); X if ( lfp = fopen ( last_file, "r" ) ) X { X int c; X X while ( ( c = getc ( lfp ) ) != '\n' && c != EOF ) X (void) putchar ( c ); X (void) fclose ( lfp ); X } X else X (void) printf ( "" ); X if ( lock ( CHECK ) ) X puts ( " *" ); X else X (void) putchar ( '\n' ); X } X exit ( 0 ); X} X X/* byte parse ( str ) X * X * Works out whether str is for the form '' or ':' and updates X * lines accordingly. Returns 0 on correct format, 1 otherwise. X * X * str : points to string to be parsed. X */ Xstatic byte parse ( str ) Xchar *str; X{ X int number = 0; X FORMAT *temp, *end = lines; X X if ( ! ( temp = (FORMAT *) malloc ( sizeof ( FORMAT ) ) ) ) X { X perror ( "No memory" ); X exit ( 2 ); X } X if ( lines ) X { X while ( end->next ) X end = end->next; X end->next = temp; X } X else X lines = temp; X temp->next = NULL; X temp->time = 0; X temp->param.max_lines = 0; X while ( isdigit ( *str ) ) X number = number * 10 + *str++ - '0'; X if ( *str == '\0' ) X { X temp->param.max_lines = number; X return 0; X } X if ( *str++ != ':' ) X return 1; X temp->time = 1; X temp->param.date = number * 60; X number = 0; X while ( isdigit ( *str ) ) X number = number * 10 + *str++ - '0'; X if ( *str != '\0' ) X return 1; X temp->param.date += number; X temp->param.date *= 60; X return 0; X} X X/* void lu ( argc, argv ) X * X * Lu prints last usage information. X * X * argc : argc with which gosip was invoked X * argv : argv with which gosip was invoked X */ Xvoid lu ( argc, argv ) Xint argc; Xchar *argv[]; X{ X char **tempv = argv; X int tempc = argc; X byte all = 1; /* dual purpose flag */ X X if ( argc == 1 ) X summary(); X X (void) parse ( "20" ); X while ( ++tempv, --tempc ) X if ( (*tempv)[0] == '-' ) X if ( parse ( *tempv + 1 ) ) X { X (void) fprintf ( stderr, "Usage: %s [-[:]] [] ...\n", file ); X exit ( 2 ); X } X X page(); X while ( ++argv, --argc ) X { X if ( (*argv)[0] == '-' ) X { X FORMAT *temp = lines->next; X X (void) free ( (char *) lines ); X lines = temp; X } X else X { X if ( ! all ) X (void) putchar ( '\n' ); X history ( *argv ); X all = 0; X } X } X X if ( all ) /* do all gossip files */ X { X char *files[FILE_NUM], **fptr = files; X byte num; X X if ( ! ( num = allgoss ( files ) ) ) X exit ( 2 ); X while ( num-- ) X { X if ( ! all ) X (void) putchar ( '\n' ); X else X all = 0; X history ( *fptr++ ); X } X } X X exit ( 0 ); X} END_OF_FILE if test 9217 -ne `wc -c <'lu.c'`; then echo shar: \"'lu.c'\" unpacked with wrong size! fi # end of 'lu.c' fi if test -f 'lu.h' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'lu.h'\" else echo shar: Extracting \"'lu.h'\" \(629 characters\) sed "s/^X//" >'lu.h' <<'END_OF_FILE' X/* void lu ( argc, argv ) X * X * Lu prints last usage information, obtained from last and history files. X * X * argc : argc with which gosip was invoked X * argv : argv with which gosip was invoked X */ Xextern void lu(); X X/* byte allgoss ( files ) X * X * Returns an alphabetical list of gosip files in the array files. It X * returns 0 if no files could be found, either because of an error or X * because there weren't any; otherwise it returns the number of files found. X * X * files : the array in which the result is stored. X */ Xextern byte allgoss(); X X#define FILE_NUM 14 /* max number of gosip files */ END_OF_FILE if test 629 -ne `wc -c <'lu.h'`; then echo shar: \"'lu.h'\" unpacked with wrong size! fi # end of 'lu.h' fi if test -f 'main.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'main.c'\" else echo shar: Extracting \"'main.c'\" \(5550 characters\) sed "s/^X//" >'main.c' <<'END_OF_FILE' X#include "global.h" X#include "cat.h" X#include "edit.h" X#include "control.h" X#include "util.h" X#include "lu.h" X X#define VERSION 104 X#define usage() (void) fprintf ( stderr, "Usage: %s [-p|-l|-e|-t|-s|-r|-v|-x|-z]\n", file ); exit ( 4 ); X#define check_flag() if ( argv[1][2] ) { usage() } X Xextern char *sprintf(); Xchar text_file[MAX_FILE_LENGTH], file[MAX_FILE_LENGTH]; Xchar history_file[MAX_FILE_LENGTH], last_file[MAX_FILE_LENGTH]; X#ifdef LOCKF_BROKEN Xchar lock_file[MAX_FILE_LENGTH]; X#endif LOCKF_BROKEN X X/* void init_file_names ( invocation ) X * X * This function works out the names of the files used by gosip, from the X * name by which it is invoked, and places them in various global variables. X * X * invocation : points to the name by which gosip was invoked (argv[0]). X */ Xvoid init_file_names ( invocation ) Xchar *invocation; X{ X char *ptr = invocation, *temp = invocation; X X while ( *ptr != '\0' ) X if ( *ptr++ == '/' ) X temp = ptr; X (void) strcpy ( file, temp); X (void) sprintf ( history_file, "%s/HISTOrY.%s", DATA_FILE_DIRECTORY, file ); X (void) sprintf ( text_file, "%s%s/text.%s", DATA_FILE_DIRECTORY, SECRET_DIR, file ); X (void) sprintf ( last_file, "%s/LAsT.%s", DATA_FILE_DIRECTORY, file ); X#ifdef LOCKF_BROKEN X (void) sprintf ( lock_file, "%s/lLo$Ck.%s", DATA_FILE_DIRECTORY, file ); X#endif LOCKF_BROKEN X} X X/* void reserve() X * X * This function prints a message as soon as the gosip file is no longer X * being edited. It firsts checks if gosip is being edit. If so, it X * fork()s, and the child checks for the lock every 5 seconds. X */ Xstatic void reserve() X{ X int pid; X X if ( ! lock ( CHECK ) ) X { X (void) printf ( "The %s file is not being edited.\n", file ); X exit ( 1 ); X } X error ( pid = fork(), "fork failed" ); X if ( pid == 0 ) X { X while ( lock ( CHECK ) ) X sleep ( 5 ); X (void) printf ( "\007\r\nThe %s file is no longer being edited.\r\n", file ); X } X else X (void) printf ( "You'll be informed when %s becomes available.\n", file ); X exit ( 0 ); X} X Xmain ( argc, argv ) Xint argc; Xchar *argv[]; X{ X init_file_names ( argv[0] ); X check_if_up(); X X if ( ! strcmp ( file, "lu" ) ) X lu ( argc, argv ); X if ( ! strcmp ( file, "allgoss" ) ) X { X char *files[FILE_NUM], **fptr = files; X byte num = allgoss ( files ); X X while ( num-- ) X puts ( *fptr++ ); X exit ( 0 ); X } X X if ( argc == 1 ) X { X char *opt = getenv ( "GOSIP" ); X X if ( opt ) X { X argc = 2; X argv[1] = opt; X } X } X switch ( argc ) X { X default: X usage(); X case 1: X do_edit ( normal, "dummy" ); X case 2: X if ( argv[1][0] == '-' ) X switch ( argv[1][1] ) X { X case '#': X if ( strcmp ( usercode(), SUPER_USER ) ) X catfile ( normal ); /* pretend the option doesn't exist */ X if ( argv[1][2] == '\0' ) X { X puts ( "Use the option correctly, fishhead.\n" ); X exit ( 3 ); X } X if ( ! access ( DOWN_FILE, F_OK ) ) X { X puts ( "The system is already down, fool.\n" ); X exit ( 2 ); X } X (void) printf ( "Bringing down the %s files.\n", file ); X { /* set up a 'lock' file */ X FILE *fp = fopen ( DOWN_FILE, "w" ); X X if ( ! fp ) X { X perror ( "Couldn't create lock file" ); X exit ( 2 ); X } X (void) fprintf ( fp, "%s\n", &argv[1][2] ); X error ( fclose ( fp ), "Couldn't close lock file" ); X if ( chmod ( DOWN_FILE, 0666 ) == -1 ) X perror ( "Couldn't alter permissions" ); X } X (void) printf ( "The %s files are down.\n", file ); X exit ( 0 ); X case '@': X check_flag(); X if ( strcmp ( usercode(), SUPER_USER ) ) X catfile ( normal ); /* pretend the option doesn't exist */ X if ( access ( DOWN_FILE, F_OK ) ) X { X puts ( "The system isn't down, fool.\n" ); X exit ( 2 ); X } X inform_users(); X if ( unlink ( DOWN_FILE ) ) X perror ( "Couldn't remove the down file" ); X else X (void) printf ( "Brought %s up again.\n", file ); X exit ( 0 ); X case 'c': X do_edit ( cflag, &argv[1][2] ); X case 'e': X check_flag(); X do_edit ( abort, "dummy" ); X case 'l': X check_flag(); X catfile ( normal ); X case 'p': X (void) printf ( "Sending %s to the printer.\n", file ); X (void) fflush ( stdout ); X if ( argv[1][2] ) X { X argv[1][1] = 'P'; X execlp ( "lpr", "lpr", argv[1], text_file, (char *) 0 ); X } X else X execlp ( "lpr", "lpr", text_file, (char *) 0 ); X perror ( "Couldn't run lpr" ); X exit ( 2 ); X case 'r': X check_flag(); X reserve(); X case 's': X check_flag(); X exit ( lock ( CHECK ) != 0); X case 't': X check_flag(); X if ( lock ( CHECK ) ) X { X lastedit ( EDIT ); X exit ( 1 ); X } X else X { X (void) printf ( "The %s file is not being edited.\n", file ); X exit ( 0 ); X } X case 'v': X check_flag(); X (void) printf ( "APS %s version %d.\n", file, VERSION ); X exit ( 0 ); X case 'x': X check_flag(); X do_edit ( normal, "dummy" ); X case 'z': X check_flag(); X catfile ( raw ); X default: X usage(); X } X else X if ( argv[1][1] == '\0' ) X catfile ( normal ); X else X usage(); X } X} END_OF_FILE if test 5550 -ne `wc -c <'main.c'`; then echo shar: \"'main.c'\" unpacked with wrong size! fi # end of 'main.c' fi if test -f 'main.h' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'main.h'\" else echo shar: Extracting \"'main.h'\" \(310 characters\) sed "s/^X//" >'main.h' <<'END_OF_FILE' X/* void init_file_names ( invocation ) X * X * This function works out the names of the files used by gosip, from the X * name by which it is invoked, and places them in various global variables. X * X * invocation : points to the name by which gosip was invoked (argv[0]). X */ Xextern void init_file_names(); END_OF_FILE if test 310 -ne `wc -c <'main.h'`; then echo shar: \"'main.h'\" unpacked with wrong size! fi # end of 'main.h' fi if test -f 'notes' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'notes'\" else echo shar: Extracting \"'notes'\" \(15168 characters\) sed "s/^X//" >'notes' <<'END_OF_FILE' X[ Note for non Warwick readers: poppy and orchid are the local machines on X which gosip is installed. The dubbin directory is the DATA_FILE_DIRECTORY X which started life as 'dubbin and broken glass', but then changed to simply X dubbin. ap. ] X X XThings still to do include: X X1. Check the process is not suspended indefinitely. (Fork() and alarm.) X2. Informing users after going down should be improved, or removed. X3. Move history code into history module. X4. Add option to skip printing of info.text. (add info.text item first :-) X5. Add option to lu to print out details for a given user. X6. Maybe keep all history information in one history file? X X X[ The above list is really more for my personal benefit, but I leave it here X for you all to enjoy. Perhaps some enterprising programmer can implement X some of the things on the list, or some analyst add to them. ap. ] X X XHistory of changes by version number X==================================== X X29 or possibly 30 introduced a better handling of the '-r' option. X X31 or thereabouts saw gosip split into several modules. It was around this X time that the facility to act as a primitive 'lu' was added. X X32 or maybe 33 corrected a bug in the automatic deletion of backup files that X emacs users might leave around. This version also saw the catfile() X option altered, so that it didn't fork 'cat' but opened the file and X printed it itself. X X33 Introduced bug fixes for shutdown messages that were too long. Previously X if the message exceeded 80 chars, gosip would segmentation fault. X X34 General recompilation after the makefile was hacked around a bit. No X functional difference from 33 (I think). Installed unintentionally. X X35 introduced proper handling of non-writable text files. If text.gosip is X readable but not writable (someone would have to go and chmod it X deliberately), gosip will now first attempt to chmod it to 666, and, X failing that, copy text.gosip into a temporary file and rename it to X text.gosip, and then chmod it to 666. X X36 features a lot of changes, mainly internal. Lu is hard linked to gosip, X however it is not yet fully functional. The history file has changed to X an internal format. The extra line feed coming with last edit information X has gone. X X37 has improvements to lu. Lu can take an unlimited number of args, and will X print out history details for each gosip file passed as an arg. The -n X flag is recognised, but ignored. X X38 has some improvements to the history mechanism. The size of text.gosip is X now stored even if access was read-only. History.gosip will have the X correct permissions if it is created by gosip. Previously if created by a X read-only operation, it wouldn't have. X X39 has a new version of lu invoked without options. It now produced roughly X the same output as the old 'lu -a' did. X X40 improved the information held in history.gosip. Gosip now keeps a copy of X text.gosip as you edit, and then checks whether you have actually changed X the file or not. This information is recorded in the history file. X X41 has an improved layout for lu invoked without options. It now resembles X the original 'lu -a' much more. X X42 has a check on the size of text.gosip. If it is significantly less than X at the start of the edit session, gosip asks the user whether he's sure X that's what he wants, and offers to restore the backup. X X43 has better checking of the command line for lu. It is first scanned for X any invalid flags, in which case a usage message is given. Previous X behaviour was to complain about invalid flags as it came to them. X X44 removes a bug: if a file shrinks a lot, and the user confirms he wants to X keep it, gosip would record the history information as "no change". This X has now been corrected. X X45 has a minor improvement in that if you just press return in answer to the X question about whether you want to keep the shorter file, gosip would hang X until a second return was pressed. Also, if you chose to restore the old X version, text.gosip would end up with permission 0400. X X46 saw improvements to lu. It now skips files like "text.gosip~" when X printing its summary information. X X47 has the usercode field in the history struct changed to 12 bytes in X length, to avoid alignment problems between orchid and poppy. X X48 has a few minor redundancies removed. It also has lu improved such that X it no longer leaves an unwritable history.gosip file around if one didn't X exist. X X49 has an improved message about reductions in the size of the file. It now X tells you the new size when asking you whether to keep it. X X50 The summary provided by lu is now sorted alphabetically by gosip file. X X51 has quite a few minor changes. Lu summary mode now checks properly for X there being too many gosip files. The 'going down' message has a tab X replaced by blanks for extra readability, and waiting times before killing X the editor have been altered slightly. X X52 has one minor change (hardly worth a new version number really). Lu X summary information now right aligns the size of the files, causing the X names that follow to be aligned. X X53 contains additions to the lock mechanism, using (as previously) flock() on X the local machine but a lock file for the remote machine. All lock code X as been moved to the function lock(). X X54 has a small bug removed. If a lock file already exists when someone tries X to use gosip, it would exit with an error message about not being able to X create the lock file. X X55 has -n option working on lu. X X56 Whenever gosip is asked to check whether it is invoked, it will delete any X local lock file that may exist, if flock() says that gosip's not being X used locally. This helps to minimise damage when lock files are left X around by means I don't fully understand - it never happens to me. X X57 The usage 'lu -n ' as been replaced by 'lu -'. This also X removes the bug whereby 'lu -n gosip' is not caught as an error. X X58 introduces 'lu -' which works as 'lu - `allgoss`'. X X59 introduces 'lu -n: gosip' which lists entries from the history file that X are less than n minutes old. X X60 now has 'lu -hour:min gosip' working. Still no checking for invalid X command line arguments. X X61 has proper checking for invalid args to lu. X X62 removes the gratuitous blank line at the beginning of 'lu gosip' output. X X63 takes default gosip options from the environment variable GOSIP. X X64 improves signal handling, trapping SIGTERM and SIGQUIT and calling a tidy X up routine. X X65 prompts for a name when editing, if environment variable NAME is not X defined. X X66 improves handling of the above facility. fgets() is used to read the X name, which handles delete correctly, and more info is provided. X X67 has alterations to the above (minor), a couple of wording changes and use X of environment variable EDITOR if VISUAL is undefined. X X68 introduces environment variable GOSNAME. If undefined, NAME is used as X your name instead. X X69 has the lu usage message corrected. I know it's a pretty minor X alteration, but I want to try installing a new version, now that I've X moved the binaries to one of my directories. X X70 Gosip prints a list of gosip files when invoked with the name 'allgoss'. X A few other minor changes have also occurred. X X71 has a few internal changes - structure in lu now has a union to ensure the X correct type, depending on whether a time or line number constraint is X required. Previously the same field was used, which worked ok since X time_t == int on this system. Main external changes are that X update_history() now removes from history.gosip entries more than RECENT X seconds old, and you can now do 'gosip -p' to specify a printer. X X72 removes a small inefficiency from the allgoss section, which meant that X allgoss() was accidentally called twice. X X73 increases RECENT a little. Introduces a new module, history.[ch], to X which a couple of functions from lu are moved. X X74 The recent religious discussion in events, which meant events was invoked X much more frequently than is normal, has revealed deficiencies in the X lock() function. It has now been rewritten to just use a simple lock X file. Since SIGHUP, SIGTERM, SIGQUIT are now trapped, instances of the X lock file being left around when it shouldn't be, should be rare. X X75 Includes the name of the gosip file in the advisory message when SIGHUP, X etc., are caught. The message is now sent to stderr rather than stdout. X X76 reintroduces flock() to the lock() function. However, this time the X lock_file is always checked first, and flock() is only used if lock_file X is more than 12 seconds old. X X77 introduces an extra space in the bytes field of lu summary. This keeps X the names aligned, now that xian (Christian) file has exceeded 100K. X X78 includes the user's name/alias in the history file information. A X function gosname() has been added, similar to usercode(), that returns X this information. It is also given by history() in lu. X X79 has the function monitor_usage() included. It keeps a record of how gosip X is invoked and by whom. I want to see exactly how many people know about X which options. X X80 If gosip is invoked by SUPER_USER, he is prompted for the removal of any X files in DATA_FILE_DIRECTORY which do not begin with either "lock.", X "history.", "last." or "text." and which are older than the oldest lock X file and at least half an hour old. X X81 ups the value of RECENT a little. Also has a newline added between the X prompts for whether to delete old files, and old files now need to be at X least four hours old. X X82 has the bit that checks if you want to keep a smaller gosip file altered. X Since Steve was complaining that he typed 'lu' ahead and so lost his X changes, I've fixed it so that only responses beginning with 'yes' or 'no' X are accepted. X X83 ensures that history file pruning leaves at least MIN_LINES entries in the X history file. X X84 checks for non-printable characters in the user's name. If the name came X from an environment variable, the user is prompted to enter a name. If he X entered an invalid name, his name is set to "". An invalid X option is no longer treated as '-l'. X X85 due to popular demand, the anomaly removed in 84 is partially restored, in X as much as a single letter is treated as '-l'. X X86 features changes to usercode(). The code is obtained from the password X file entry for getuid(), rather than using getlogin(). This means the X correct person is returned if the user has used su(1). X X87 Changed the fopen(..., "w+") to "a+" when updating history information. X This means that the history file is created if it doesn't exist. X X88 removes a slight bug, in that '-sdfa' would be treated as '-l' rather than X as an illegal usage. X X89 improves the removal of drivel files routine. Now also files with a legit X prefix ("text." or "last.") are killed if the rest of their name contains X non-alphabetic characters (eg. text.gosip~). Also, history and lock files X are moved into a non-readable sub-directory of DATA_FILE_DIRECTORY, to X stop people editing/removing them. X X90 corrects the bug which failed to check the return value of lock() for the X possibility of failure in creating the lock file. X X91 corrects clean_directory(), in that files beginning "history." and "lock." X are no longer valid, now that they are stored in the sub directory. X Monitor_usage() includes the hostname. X X92 Lots of internal changes. I went through the source to check for errors X before posting it, and found lots. In some places, return values weren't X checked properly for errors, in others harmless errors caused exit()s, and X in some places, inefficient code was left over from the beginning, when I X was still relatively inexperienced with C. X X93 Installs removal of drivel files. Now files that do not match the emacs X regexp "^\(text\.\|last\.\)[a-zA-Z]+$" are automatically removed by anyone X who uses gosip for editing (that is, not by gosip -v or -l, etc.). I have X temporarily arranged to have to have a log kept of all files so deleted. X X94 Altered usercode() to strcpy() the code it gets from getpwuid(). Since I X call getpwuid() again to get the code of the owner of a drivel file so I X can log it (see version 93), usercode()'s value was previously X overwritten. X X95 Places a limit on the length of a gosip file. The recent creation of the X 'politics' gosip file messed up lu output a little, and very long names X would completely destroy it. X X96 Removes the bits that keep a records of usage (monitor_usage()) and of X deleted files. Alters clean_directory() such that "Makefil" is a valid X prefix. This pacifies Mike, who keeps trying to put a Makefile in X DATA_FILE_DIRECTORY. MAX_FILES is upped to 14, as we now have 13 gosip X files. X X97 Alters the locking mechanism to use lockf(). Last_file is now just opened X for writing, not deleted, since all files in dubbin now have to be owned X by someone on poppy because orchid users don't have a poppy quota. X X98 Copies the text file into /tmp, and edits the copy. This single change is X so simple and yet so powerful. It now allows the dubbin directory to be X unwritable, and at a stroke removes a the need for all the tedious X checking for text.gosip unwritable, for drivel files in the dubbin X directory, etc; and allows the restructuring mentioned in version 100. X Further, chdir() is not called - all filenames are given with a full X pathname. X X99 Moves last_file into the .data directory. X X100 Alters the structure of the dubbin directory. Dubbin is unreadable, X allowing last, lock and history files to be stored there, while a X subdirectory of dubbin is readable, in which the text files are stored. X This means the the directory can be searched, to allow allgoss to work, X yet the text files cannot be edited directly. X X101 Introduces the option 'z' to print out the gosip file directly - no pager X is invoked, nor is last usage information printed. Also, page() is X altered in that it does not fork() a pager if stdout is not connected to a X terminal. X X102 I've discovered that lockf() has a bug: testing for a lock does not work X on orchid if the lock was applied on poppy. Therefore I've changed lock() X so that it applies a lock in order to check, and then removes it. Also, X if gosip is smaller, both initial and final sizes are given in the X confirmation message. X X103 Alters the error message produced by catfile() so that it doesn't give X away the name of the hidden directory in which text.gosip is stored. X X104 I found that I'd written the current name of the secret subdirectory of X DATA_FILE_DIRECTORY directly into the code for allgoss(). I've now X defined SECRET_DIR and use that instead. END_OF_FILE if test 15168 -ne `wc -c <'notes'`; then echo shar: \"'notes'\" unpacked with wrong size! fi # end of 'notes' fi if test -f 'setup' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'setup'\" else echo shar: Extracting \"'setup'\" \(2586 characters\) sed "s/^X//" >'setup' <<'END_OF_FILE' X#! /bin/sh X# X# Simple prog to install a gosip file. Give names of new gosip files as X# arguments, and remember that only alphabetic characters are allowed. X X# The following things should be customised before use. Don't forget to X# alter the source as well. X Xsource_dir=/poppy/ma/uhk/gtfd # location of gosip sources Xdata_dir=/poppy/ma/uhk/gtfd/.files # location of data files Xsecret_dir=.data # subdirectory of the above X Xhistory=HISTOrY # prefix for history file (no period) Xlast=LAsT # prefix for last file (also no period) X Xtrap '{ echo Installation failed.; exit; }' 30 X Xif test $# -eq 0 Xthen X echo "Usage: $0 [ ...]" X exit Xfi X Xcat << zhuanshukuan X XGosip Installation X================== X XThis program shoud be the last one you run to install gosip. By the time you Xrun it, you should already have customised the source code and this program. XIt can also be used at any time to add in more gosip files. X XThere will be problems if you already have a program called 'lu' in the Xdirectory where you intend to keep the binaries. If lu does not exist, the Xprogram assumes that no gosip files exist yet, and will compile gosip, and Xcreate a data directory. X Xzhuanshukuan X Xecho -n "Where should the binaries go? " Xread bin_dir Xcd $bin_dir Xwhile test $# -gt 0 Xdo X gosip=$1 X shift X echo "Installing new gosip-file: $gosip." X if test -f lu X then X ln lu $gosip # link in new binary X else X echo '' X echo "If this is not the first gosip file, something's wrong." X echo "(Maybe you've lost lu?)" X echo '' X cd $source_dir X make real || kill -USR1 $$ # compile gosip X cd $bin_dir X mv $source_dir/real lu || kill -USR1 $$ X ln lu allgoss # create lu and allgoss X ln lu $gosip # as well as the requested gosip file X ( X mkdir $data_dir # Doesn't matter if the directory X cd $data_dir # already exists X chmod 711 . || kill -USR1 $$ # use 733 if LOCKF_BROKEN is defined. X mkdir $secret_dir X chmod 755 $secret_dir || kill -USR1 $$ X ) X fi X ( # now create the data files X cd $data_dir X touch $history.$gosip || kill -USR1 $$ X chmod 666 $history.$gosip || kill -USR1 $$ X touch $last.$gosip || kill -USR1 $$ X chmod 666 $last.$gosip || kill -USR1 $$ X cd $secret_dir X touch text.$gosip || kill -USR1 $$ X chmod 666 text.$gosip || kill -USR1 $$ X ) Xdone Xecho Done. END_OF_FILE if test 2586 -ne `wc -c <'setup'`; then echo shar: \"'setup'\" unpacked with wrong size! fi chmod +x 'setup' # end of 'setup' fi if test -f 'util.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'util.c'\" else echo shar: Extracting \"'util.c'\" \(9807 characters\) sed "s/^X//" >'util.c' <<'END_OF_FILE' X#include "global.h" X#include "util.h" X X#include X#include X#include X#include X X#ifdef LOCKF_BROKEN Xextern long gethostid(); X#endif LOCKF_BROKEN X Xextern char *strncpy(); X X/* char *usercode() X * X * Usercode() tries to return the usercode of the person using gosip. It X * uses getuid() to obtain the id, then obtains the usercode from the X * password file. It returns two question marks if this doesn't succeed. X */ Xchar *usercode() X{ X static char text[MAX_CODE_LEN], *code = (char *) 0; X X if ( ! code ) X { X struct passwd *pwent = getpwuid ( getuid() ); X X if ( ! pwent ) X return " ?? "; X (void) strncpy ( text, pwent->pw_name, MAX_CODE_LEN ); X text[MAX_CODE_LEN - 1] = '\0'; X code = text; X } X return code; X} X X/* byte valid ( name ) X * X * Scans string pointed to by name, returning zero if it contains any X * non-printing characters, 1 otherwise. X * X * name : string to be scanned X */ Xstatic byte valid ( name ) Xchar *name; X{ X while ( *name ) X if ( ! ( isascii ( *name ) && isprint ( *name++ ) ) ) X return 0; X return 1; X} X X/* char *gosname() X * X * This will return the user's name, taken from the environment variable X * GOSNAME, or NAME if GOSNAME is not defined. If neither is defined, or the X * name so obtained contains non-printable characters, the user is prompted X * to enter his name. X */ Xchar *gosname() X{ X static char *name = 0, input[MAX_NAME_LEN]; X X if ( ! name ) X { X if ( ! ( name = getenv ( "GOSNAME" ) ) ) X name = getenv ( "NAME" ); X if ( ! name || ! valid ( name ) ) X { X puts ( "If you wish to remain anonymous, just press return." ); X (void) printf ( "Please enter your name: " ); X if ( ! fgets ( input, MAX_NAME_LEN, stdin ) ) X name = ""; X else X { X input[strlen ( input ) - 1] = '\0'; X if ( *input == '\0' ) X name = ""; X else X name = input; X } X } X else X { X (void) strncpy ( input, name, MAX_NAME_LEN ); X name = input; X } X name[MAX_NAME_LEN - 1] = '\0'; /* ensure name is not too long */ X if ( ! valid ( name ) ) X name = ""; X } X return name; X} X X#ifdef LOCKF_BROKEN X/* static byte try_flock() X * X * This provides a secondary locking mechanism. The trouble with lock files X * is that they can often be left around. I've never quite understood why - X * the only obvious cause would be if the computer crashed or gosip was sent X * a SIGKILL - but it happened with annoying regularity. Now a lock_file has X * is flock()ed as well, but this flock() can only be checked for on the same X * computer as it was applied. X * X * Hence the lock_file contains the hostid of the computer from which gosip X * was invoked. This function checks whether that hostid matches the one of X * the current machine. If so, it checks for the flock if the lock_file is X * more than 12 seconds old. This delay is much more than is needed, and X * serves to allow a flock() to be applied to a freshly created lock_file. X * This is needed if two people invoke gosip at the same time. X * X * Now a flock() is guaranteed to be removed when a process dies, therefore X * if a lock_file older than 12 seconds exists without a flock on it on the X * same machine as the one on which it was created, it is deemed to be X * incorrect and removed. This system has so far worked very well, with no X * complaints of either more than one person editing a particular gosip file, X * or of a free file being considered unfree. X * X * This system does depend on gosip being used fairly regularly on every X * machine from which it is available, which is not a problem here. X * X * If the lock was valid, 1 is returned: 0 if the lock_file was removed. X */ Xstatic byte try_flock() X{ X struct stat info; X long host; X int fd; X byte retval = 1; X X if ( ( fd = open ( lock_file, O_RDONLY ) ) != -1 ) X if ( read ( fd, (char *) &host, sizeof ( host ) ) == sizeof ( host ) ) X if ( host == gethostid() ) X if ( fstat ( fd, &info ) == 0 ) X if ( time ( (time_t *) 0 ) - info.st_mtime > 12 ) X if ( flock ( fd, LOCK_SH | LOCK_NB ) != -1 ) X { X retval = 0; X (void) unlink ( lock_file ); X } X (void) close ( fd ); X return retval; X} X X/* byte lock ( type ) X * X * General function to manipulate locks on the gosip file to prevent X * simultaneous editing. The parameter type controls the exact behaviour. X * If type is REMVE a previously applied lock will be removed, which involves X * removing the lock_file and the flock() on history.gosip. X * X * Type CHECK will first check for a lock file. If present, 1 is returned if X * try_flock() agrees the lock is genuine. Otherwise, 0 is returned to X * indicate gosip is free for use. X * X * Type CREAT makes similar checks as CHECK, but it will create a lock if X * there isn't one. That is it creates lock_file and uses flock() to give it X * an exclusive lock. It returns 0 on success, and 1 if a lock already X * existed. Return value of -1 if the lock could not be created. X * X * type : type of function to perform as detailed above. X */ Xbyte lock ( type ) Xbyte type; X{ X static int lfd; X long host; X X switch ( type ) X { X case REMVE: X if ( ! lock ( CHECK ) ) X { X puts ( "Internal error: tried to remove non-existant lock." ); X return -1; X } X (void) close ( lfd ); X if ( unlink ( lock_file ) ) X perror ( "Couldn't remove lock file" ); X return 0; X case CREAT: X lfd = open ( lock_file, O_CREAT | O_EXCL | O_WRONLY, 0644 ); X if ( lfd == -1 ) X if ( errno == EEXIST ) X if ( try_flock() ) X return 1; /* valid lock exists */ X else X return lock ( CREAT ); /* lock was invalid, try again */ X else X { X perror ( "Couldn't create lock file" ); X return -1; X } X if ( flock ( lfd, LOCK_EX | LOCK_NB ) ) X { X perror ( "Flock failed" ); X (void) fprintf ( stderr, "This may be a bug in %s!\n", file ); X (void) lock ( REMVE ); X return -1; X } X if ( fchmod ( lfd, 0644 ) ) X perror ( "Couldn't alter lock file permissions" ); X host = gethostid(); X if ( write ( lfd, (char *) &host, sizeof ( host ) ) < sizeof ( host ) ) X perror ( "Error writing host id" ); X return 0; X case CHECK: X if ( access ( lock_file, F_OK ) == 0 ) X return try_flock(); X else X return 0; X } X puts ( "Internal error: lock called with invalid argument." ); X return -1; X} X X#else LOCKF_BROKEN X X/* byte lock ( type ) X * X * General function to manipulate locks on the gosip file to prevent X * simultaneous editing. The parameter type controls the exact behaviour. X * If type is REMVE a previously applied lock will be removed, which involves X * simply releasing the lockf(). X * X * Type CHECK returns 1 if lockf() indicates a lock on history.gosip, X * otherwise 0. X * X * Type CREAT creates a lock (on history.gosip) by calling flock() It returns X * 0 on success, and 1 if a lock already existed. Return value of -1 if the X * lock could not be created. X * X * type : type of function to perform as detailed above. X */ Xbyte lock ( type ) Xbyte type; X{ X static int lfd = -2, fd, ret; X X switch ( type ) X { X case REMVE: X switch ( lock ( CHECK ) ) X { X case 0: X puts ( "Internal error: tried to remove non-existant lock." ); X return -1; X case -1: X puts ( "Lock checking error." ); X return -1; X } X (void) lockf ( lfd, F_ULOCK, 0L ); X (void) close ( lfd ); X return 0; X case CREAT: X fd = open ( history_file, O_CREAT | O_WRONLY, 0666 ); X if ( fd == -1 ) X { X perror ( "Couldn't create lock" ); X return -1; X } X if ( lockf ( fd, F_TLOCK, 0L ) == -1 ) X if ( errno == EAGAIN || errno == EACCES ) /* check EACCES because of */ X return 1; /* lockf() bug */ X else X { X perror ( "Lockf() failed" ); X (void) close ( fd ); X return -1; X } X lfd = fd; X return 0; X case CHECK: X if ( lfd > -2 ) X return 1; X if ( ( fd = open ( history_file, O_CREAT | O_WRONLY, 0666 ) ) == -1 ) X { X perror ( "Couldn't check lock" ); X return -1; X } /* ret = lockf ( fd, F_TEST, 0L ); */ X ret = lockf ( fd, F_TLOCK, 0L ); /* cannot be used: it has a bug. */ X (void) close ( fd ); /* releases the lock */ X if ( ret == -1 ) X return ( errno == EAGAIN || errno == EACCES ) ? 1 : -1; X else X return 0; X } X puts ( "Internal error: lock called with invalid argument." ); X return -1; X} X#endif LOCKF_BROKEN X X/* void lastedit ( format ) X * X * Lastedit prints out the name and usercode of the last person to edit the X * gosip file. The exact form of the message depends on format, which also X * controls whether elapsed edit time is displayed. X * X * format : indicates what type of message to print. X */ Xvoid lastedit ( format ) Xbyte format; X{ X FILE *last; X int c; X time_t edit_time; X struct stat info; X X if ( format == AUTO ) X format = lock ( CHECK ) ? EDIT : INFO; X if ( last = fopen ( last_file , "r" ) ) X { X if ( ( c = getc ( last ) ) != EOF ) X { X if ( format == EDIT ) X (void) printf ( "The %s file is being edited by ", file ); X else X (void) printf ( "Last %s edit by ", file ); X do X (void) putchar ( (char) c ); X while ( ( c = getc ( last ) ) != '\n' && c != EOF ); X } X (void) fclose ( last ); X } X if ( format == EDIT) X if ( stat ( last_file, &info ) == 0 ) X { X edit_time = time ( (time_t *) 0 ) - info.st_mtime; X (void) printf ( " [%d:%.2d]", edit_time / 60, edit_time % 60 ); X } X puts ( "." ); X} END_OF_FILE if test 9807 -ne `wc -c <'util.c'`; then echo shar: \"'util.c'\" unpacked with wrong size! fi # end of 'util.c' fi if test -f 'util.h' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'util.h'\" else echo shar: Extracting \"'util.h'\" \(1589 characters\) sed "s/^X//" >'util.h' <<'END_OF_FILE' X/* The utilities module contains functions of general use, that are called by X * many of the other modules, yet don't really fit into any of them. X */ X X#define MAX_NAME_LEN 28 /* longest gosip name [returned by gosname()] */ X#define MAX_CODE_LEN 12 /* max length of usercode [from usercode()] */ X X/* char *usercode() X * X * Returns the user's usercode if possible, otherwise '??'. X */ Xextern char *usercode(); X X/* char *gosname() X * X * This will return the user's chosen gosip name (GOSNAME), if defined. X * Otherwise NAME, or failing that, the user is prompted to enter a name. X */ Xextern char *gosname(); X X/* void lastedit ( format ) X * X * Lastedit prints out the name and usercode of the last person to edit the X * gosip file. The exact form of the message depends on type, which also X * controls whether elapsed edit time is displayed. X * X * format : defines type of message, as specified below. X */ Xextern void lastedit(); X X#define AUTO 0 /* use lock ( CHECK ) to decide itself */ X#define EDIT 1 /* print 'being edited' message */ X#define INFO 2 /* just print info */ X X/* byte lock ( type ) X * X * Returns 0 if there is no lock, 1 if there is, -1 for errors (CREAT and X * REMVE only). It will also create or remove a lock as specified by type. X * X * type : specifies whether to create a lock or not (see below). X */ Xextern byte lock(); X X#define CHECK 0 /* do not alter the lock */ X#define CREAT 1 /* create a lock */ X#define REMVE 2 /* remove a lock */ END_OF_FILE if test 1589 -ne `wc -c <'util.h'`; then echo shar: \"'util.h'\" unpacked with wrong size! fi # end of 'util.h' fi echo shar: End of shell archive. exit 0