Article 6 of alt.sources: Path: mit-eddie!husc6!labrea!decwrl!mejac!hoptoad!ncoast!devon!stb!michael From: michael@stb.UUCP (Michael) Newsgroups: alt.sources Subject: Bulletin board program Message-ID: <1644@stb.UUCP> Date: 15 Jul 87 06:42:23 GMT Reply-To: michael@stb.UUCP (Michael) Distribution: alt Organization: STB BBS, La, Ca, USA, 90402, (213) 459-7231 Lines: 1473 This is a bulletin board system, a program designed to let people read and leave messages and electronic mail. It is not as powerful as news nor /bin/mail; it was originally written for an 8 bit micro (in basic), and roughly translated over time. It is relatively bug free (however, the chat command was never implemented), will work with multiple users at once, and is "completely secure" -- after losing directories when I ran it as root (unlink ("") did wonders in V7) I made sure it was secure enough to be a login "shell" that does not need a password, and still usable by regular users. If you want to see it in action, give me a call (# is in header, PC-pursuit will work, 3/12/2400) I have it use the directory /bbs (not /usr/lib/bbs) for personal reasons; feel free to change it. (Oh yes-- the code comments make reference to a tree structured message base. It never got implemented.) This is not a shar; there are several lines with CUT in them. PAcking list: /bbs/help userlog.h fixbbs.c logsort.c bbs.c --- Lights, camera, help file --- (/bbs/help) Rough draft of help file B: read bulletin C: Chat (not working) F: Leave feedback G: goodbye, hangup H: This file I: System information L: Leave message M: Mail user O: Other systems Q: Quit (hangup, don't update auto-reads) R: Read U: Userlog X: eXpert mode (this call only) Y: Return to system Z: Logon again ZS: same as Y ?: Same as H Note: if a second word is given on L, M, and F commands, the system will prompt before each line (for automated uploads) --- He hacked him to bits with his CUTlass --- (userlog.h) /* sys/types.h and sys/dir.h MUST be included already */ struct userlog { char name[DIRSIZ+1]; char password[9]; long lastread[6]; /* allow enough for 5 boards */ char extra[7]; /* size=64 bytes after alignment */ time_t lastcall; short numcalls; char lastbyte; }; typedef struct userlog ulog; --- CUT through the trees over there --- (fixbbs.c) /* @(#)bbsfix.c 1.2 7/15/87 06:19:41 ( 10/5/86 15:26:56 ) */ # include extern int errno; /* 1.2 reduced the amount of output (no longer copy board to output) */ /* Fix an index file if it gets clobbered. Only 200 messages (unless you change the constants */ main(argc, argv) char *argv[]; { int fidx, msg; FILE *fdat; char sidx[200], sdat[200]; if (argc != 3) {printf("Useage: fixbbs . Only one board fixed per call\n "); exit(1); } umask (0002); /* Only turn off world write */ sprintf (sidx, "/bbs/idx/%s", argv[1]); sprintf (sdat, "/bbs/dat/%s", argv[1]); msg=0; sscanf (argv[2], "%d", &msg); if (msg==0) die ("Can't read message number"); fidx = creat (sidx, 0666); if (fidx == -1) die ("Index file?!?!?!?"); fdat = fopen (sdat, "r"); if (fdat == NULL) die ("Data file ?!?!?!?!!"); fixit (fdat, fidx, msg); } die(s) char *s; { printf ("Death: %s %d\n", s, errno); abort(); } mywrite (idx, addr) long addr; { write (idx, &addr, sizeof addr); } findmsg (data) FILE *data; { int x; while ((x=getc(data)) != '\001' && x != EOF) ; if ((x=ungetc(getc(data), data)) != EOF) return; printf("EOF Found\n"); } fixit (data, idx, lowmsg) FILE * data; { int cnt=lowmsg; mywrite (idx, lowmsg); if (empty(idx)) return; mywrite (idx, 0L); /* first message is at 0 */ while (!feof(data)) { printf("Message %d found at %d\n", cnt++, ftell(data) ); findmsg(data); if (!feof(data)) mywrite (idx, ftell(data)); else printf("Final message, terminating \n"); }; } empty(fd) { return (! lseek(fd, 0L, 2)); /* lseek=0 if empty */ } --- "Enough of the CUTe cut lines" --- (logsort.c) /* @(#)logsort.c 1.1 7/15/87 06:23:32 ( 10/2/86 18:51:59 ) */ /* Log file sort routine for stb. */ # define NUMLOGS 250 /* Maximun # of userlog entries */ # include # include # include # include # include ulog *logs; int number, fd; FILE *fp; main() { readit(); sortit(); writeit(); } comp_log(p1, p2) ulog *p1, *p2; { return (p2->lastcall - p1->lastcall); /* if = calls, sort by time */ /* NOTREACHED */ /* old code to sort by calls */ if (p1->numcalls < p2->numcalls) /* return larger calls */ return 1; if (p1->numcalls > p2->numcalls) return -1; } sortit() { qsort(logs, number, sizeof (*logs), comp_log); } readit() { fd=open("/bbs/userlog",2); fp=fdopen(fd,"r+"); if (fp==NULL) { perror("log_sort: /bbs/userlog"); abort(); } locking (fileno(fp), LK_LOCK, 0); logs=(ulog *) calloc (NUMLOGS, sizeof (*logs)); number=fread(logs, sizeof (*logs), NUMLOGS, fp); } writeit() { rewind(fp); fwrite(logs, sizeof (*logs), number, fp); } --- "CUT!" yelled the director. --- (bbs.c) /* @(#)stb.c 3.25 7/15/87 06:13:39 ( 2/1/87 20:34:27 ) */ #define VERSION 3 #define FIX 25 #define STB_UID 13 #define STB_GID 13 #define OUTBUF 20 /* output buffering quantity, tunable */ #define crypt(string, salt) string /* No encryption */ #define alarm(time) /* 1.1 No more hangup detection */ /* Serial Tree Board, ver 4.0 (basic algorythm), 0.3 (C conversion) */ /* similarities in algorthm between C and basic are rapidly disapearing, and the only similarity left seems to be the basic structure of the program. */ /* Changes: he_left now updates the userlog at every logon (not after final hangup) hangup signal is not trapped; o/s kills the job resulting in effective quit command. */ /* Version 3. Minor fixes, improvements. Speed up output, anything else I think of */ /* Version 2 Get the editor working correctly */ /* 1.6 strchr/strrchr for index/rindex */ /* 1.1 Message read is now by message number. */ /* .8 Final (?) ctrl-c/k fix: cbreak/cooked maintain a count of state; cbreak also disables echo, cooked re-enables. Where to put a out for ctrl-c/k? FNStop? */ /* .7 fix ctrl-c/k that .6 destroyed. Fixed by switching between cbreak and cooked. */ /* .6 changes: Will remove ALL calls to signal (except for sighup) */ /* .3 changes: Will use better signal operations (actually turn them on/off) and other things. Goal: Make it look done. This includes the bbs answering the phone, with getty never seeing the line (minor change to /etc/default/newgetty). New command: Y, myexits program without hanging up (re-spawn getty) EOF in message now means end of message, not hang up. Tree might get implemented one of these days. */ /* Tree implementation details: /bin/mkdir is used to create a directory whose name is the title, which has .text text of message dir1 child message 1 dir2 child message 2 etc. The root is in /bbs/tree/root, and should be on a mountable file system so it doesn't gobble up root space. /bbs/idx/tree has the following: msg # [7] parent # [7] left # [7] right # [7] child # [7] title [15] date [11] total record space: 61. Fields are padded with spaces on the right on disk, with the line terminated by a newline. Msg titles need not be distinct, but only the first of duplicates will be found. Numbers are in ascii. All the data is on one line. Number first, parent second, title third, date forth. Read/written with scanf/printf, not read/write. */ /* Serial format: Index files are arrays of longs. [0]=lowest number message, sequentially up from there. */ # include # include # include # include # include # include # include # include # include # include # include # include #if defined(LK_UNLCK) && !defined(LK_UNLK) /* sys 3 spelling change */ # define LK_UNLK LK_UNLCK #else # define LK_UNLCK LK_UNLK #endif #if defined(SYS3) || defined (M_SYS3) || defined (ATT) || defined (SYS5) # define index strchr # define rindex strrchr #endif # define FALSE 0 #ifdef lint # define TRUE 1 #else # define TRUE !FALSE #endif # define CTRL_K 1 # define CTRL_C 2 # define UPDATE TRUE # define NUPDATE FALSE # define LINESIZE 257 /* most that can be typed +1 */ # define WAIT_TIME 10*60 /* hangup assumed after 10 minutes */ struct lines { struct lines *next; /* pointer to next line */ char text[LINESIZE]; /* define line for input text */ }; # define NUMSIZE 7 # define DATESIZE 11 struct treejunk { char t_msgnum[NUMSIZE]; char t_parent[NUMSIZE]; char t_left[NUMSIZE]; char t_right[NUMSIZE]; char t_child[NUMSIZE]; char t_date[DATESIZE]; char t_title[DIRSIZ+1]; }; # define TREESIZE NUMSIZE*5+DATESIZE+DIRSIZE+1 /* Sum of all the field sizes in treejunk, != sizeof(treejunk). Size of TREEIDX records */ struct treestuff { int msgnum; int parent; int left; int right; int child; time_t date; char title[DIRSIZ+1]; }; typedef struct treestuf treemem; typedef struct treejunk treedisk; typedef struct lines line; typedef int func(); typedef func *fp; #ifndef __STRING typedef char *STRING; #define __STRING #endif ulog a_log; extern errno; extern char **environ; extern time_t time (); extern long lseek (); extern long tell (); long filesize(); extern char *malloc(); extern char *realloc(); extern char *calloc(); extern STRING strcat(); extern STRING strncat(); extern STRING strcpy(); extern STRING strncpy(); extern char *index(); extern char *rindex(); extern int read(); extern int write(); treemem go_left(); treemem go_right(); treemem go_up(); treemem go_down(); treemem go_x(); treemem disk_to_mem(); treedisk mem_to_disk(); /* char *crypt(); /* no encryption now 0.3 */ char *ctime(); char *getpass(); int line_lost(); long msg_write(), input_message(); # define prints(string) { printf(string); free(string); } # define len(s1) strlen(s1) # define yes(string) (string[0]=='Y' || string[0]=='y') # define no(string) (string[0]=='N' || string[0]=='n') # define instr(s, c) ((_temp = index((s),(c))-(s)) >=0 ? _temp :-2) # define msg_quit() ((*(get_command("Delete message?")))=='y') # define CTRL(c) ('c'-'a'+1) # define readline(buf) { if (fgets (buf, BUFSIZ, stdin)==0 && \ errno!=EINTR) fortune(2,NUPDATE); \ } /* .8 */ # define FNStop(x) (\ (_temp=(x))==CTRL(c) || (_temp)==CTRL(k) ?\ (putchar('\n'), TRUE) : FALSE\ ) /* 1.2 */ # define NOGOOD(name) (name[0] == '.' || name[0] == '\0'\ || name[0] == ' ' || name[0] == '/') int _temp; struct sgttyb termp, origp; struct tchars termc, origc; fp oldsig[NSIG]; STRING goof="/bbs/goofmesg"; STRING bulletin="/bbs/bulletin"; STRING mhelp="/bbs/help"; STRING other="/bbs/other"; STRING sysinfo="/bbs/info"; STRING ehelp="/bbs/entryhelp"; STRING notice="/bbs/manycalls"; STRING complaints="/bbs/mail/sysop"; STRING errorfile="/bbs/errorlog"; STRING command="b,c,f,g,h,i,l,m,o,q,r,u,x,y,z,?,a"; STRING ecommand="c,e,h,l,n,q,s,?"; STRING tcommand="luqdruq"; STRING CmdA, CmdB; /* first word, rest of command */ STRING currentuser; char buffer[BUFSIZ]; /* character buffer for whatever */ char log_entry[8000]; /* big enough to hold all commands you could possibly enter */ /* These defines aren't actually used (except MAINB) */ # define MAINB 0 # define WEIRD 1 # define ODDBALL 2 # define DandD 3 # define newboard 4 # define AMIGAB 5 # define BOARDS 6 # define DAT 0 # define IDX 1 # define BTYPES 2 # define USERLOG 0 # define LOG 1 # define MAIL 2 # define TREEIDX 3 # define OTHERS 4 /* display is fopened, and does not use a file descriptor. Mail is re-opened each time it is used, and may not always have the same descriptor. Tree index is fopened/fclosed EACH time it is written to or read from. */ int files[OTHERS], board[BOARDS][BTYPES], lastmsg[BOARDS], justread[BOARDS]; /* Next arrays must be in the same order as the #define statements */ STRING bnames [] [BTYPES] = { {"/bbs/dat/board0", "/bbs/idx/board0"}, {"/bbs/dat/board1", "/bbs/idx/board1"}, {"/bbs/dat/board2", "/bbs/idx/board2"}, {"/bbs/dat/board3", "/bbs/idx/board3"}, {"/bbs/dat/board4", "/bbs/idx/board4"}, {"/bbs/dat/board5", "/bbs/idx/board5"}, }; STRING onames [] [2] = { /* [2] to prevent compiler error */ {"/bbs/userlog",""}, {"/bbs/logfile",""}, /* log of who called and what they did */ {"/bbs/idx/tree",""}, {"",""}, /* mail is reopened each time */ }; int beginner=TRUE, cboard=MAINB, new_caller=FALSE, breakcnt=0, __; /* end of globals */ # define CBOARD (board[cboard][DAT]) # define CIDX (board[cboard][IDX]) int argc; char **argv; main (c,v) char **v; { if (sizeof (ulog)!=64) { printf("Userlog size not 64. Current size=%d\n",sizeof(ulog)); myexit(1); } umask (0002); setgid (STB_GID); setuid (STB_UID); /* Don't run as root on V7 systems (and dump core correctly) */ argc=c; argv=v; /* keep track of who we are for z command */ initialize(); /* set rnd # generator, signals, files */ /* skip_line(); /* wait for call (DELETED) */ printf("Can't turn off linefeeds, sorry\n"); printf("Welcome to the serial tree board (C %d.%d).\n", VERSION, FIX); do ; while (!get_password()); log_user(); read_bulletin(); if (read_mail()==-1) printf("Sorry, your mailbox is empty\n"); do_commands(); } hangup() /* come here if they hang up */ { add_log("\n"); add_log("HANGUP RECEIVED"); fortune(0,NUPDATE); /* Die normally */ } initialize () { int t,b,f; chdir ("/bbs"); /* make sure we can core dump if there is a problem */ srand ((int) time(0)); defopen("/etc/default/stb"); for (b=0; b. Returns same string if there is no space, different string if there is */ STRING s1; { STRING s2; char *temp; temp=index(s1,' '); if (temp==NULL) return strsave(s1); s2=strncpy (makestring( (int)(temp-s1) ),s1,temp-s1); s2[temp-s1]='\0'; return s2; } STRING rest(s1) /* s1 is . Null string if no space */ STRING s1; { if (index(s1,' ')==0) return makestring(0); return strsave(index(s1,' ')+1); } STRING strleft(s1,n) STRING s1; { STRING s2; s2=strncpy(makestring(n),s1,n); s2[n]='\0'; /* strncpy doesn't guarentee null terminator */ return s2; } STRING strright(s1,n) STRING s1; { if (n>strlen(s1)) return strsave(s1); return strsave(s1+n-strlen(s1)); } STRING lower_convert(string) /* converts a string to lowercase */ STRING string; { STRING s2; for (s2=string; *string!=0; string++) if (isascii(*string) && isupper(*string)) *string=tolower(*string); return s2; /* return the converted string. */ } skip_line() /* skip rest of line (until newline) */ { int c; while ((c=getchar())!=EOF && c!='\n') ; } STRING getline(s, lim) /* input a string up to lim-1 characters, NO \n */ STRING s; { int temp; if (fgets (s,lim,stdin)==NULL) fortune (2,NUPDATE); temp=len(s)-1; if (s[temp]!='\n') skip_line(); else s[temp]='\0'; /* remove newline */ return s; } STRING get_command(string) /* put the command in CmdA, CmdB */ STRING string; { char buffer[100]; printf(string); getline(buffer, 100); if (CmdA) { free(CmdA); CmdA=NULL; } if (CmdB) { free(CmdB); CmdB=NULL; } CmdA=first(buffer); CmdB=rest(buffer); lower_convert(CmdA); lower_convert(CmdB); add_log(CmdA); add_log(" "); return CmdA; } STRING strsqueeze (s1) /* remove blanks in string. Note that there will be an extra 0 for each space removed. */ STRING s1; { char *cp; for (cp=s1; *cp!=0; cp++) if (*cp==' ') strcpy(cp,cp+1), --cp; return s1; } cbreak() /* .8 */ { if (breakcnt++ != 0) return; termp.sg_flags |= CBREAK; termp.sg_flags &= ~ECHO; ioctl (0, TIOCSETP, &termp); } cooked() /* .8 */ { if (--breakcnt !=0) return; termp.sg_flags &= ~CBREAK; termp.sg_flags |= ECHO; ioctl (0, TIOCSETP, &termp); } input (prompt) /* 2.1 */ STRING prompt; { int x; printf (prompt); getline (buffer, BUFSIZ); if (sscanf (buffer, "%d", &x) == 1) return x; else return -1; } do_commands() /* .6 */ { for (;;) { /* Exit is in goodbye() */ alarm (WAIT_TIME); /* 10 minutes of inactivity = hangup */ if (beginner) printf("Command list: %s\n",command); get_command("Command? "); switch (instr(command, *CmdA)/2) { case 0: chboard(); break; case 1: chat(); break; case 2: feedback(); break; case 3: goodbye(); myexit (-1); /* exit should be done from goodbye; if error, then return -1 */ case 4: help(); break; case 5: info(); break; case 6: leave_message(); break; case 7: mail(); break; case 8: other_systems(); break; case 9: log_quit(); myexit(-1); /* logoff w/out updating auto-read */ case 10: msg_read(); break; case 11: userlog(); break; case 12: xpert(); break; case 13: yagain(); break; /* it should myexit w/out hang up */ case 14: relog(); break; case 15: help(); break; case 16: attach(CmdB); break; default: printf("No such command\n"); break; } } } attach (parent) STRING parent; { treemem tree; if (parent==NULL) { printf("Please specify a parent\n"); return; } not_ready_yet(); } chboard() { int x; printf("Enter board number (0-%d): ",BOARDS-1); readline (buffer); sscanf(buffer, "%d",&x); if (x32K */ /* (Unsigned ints are no better (64 K)) */ lseek (fdboard, 0L, 0); return( read (fdboard, &li, sizeof (li) ) == sizeof li ? li : syscrash ("Can't read low message number") ); } maxmsg(fdboard) /* 1.1 */ { return ( (lseek(fdboard, 0L, 2) / sizeof (long)) -2 + minmsg(fdboard) ) ; /* (Size of file) # of messages in file +lowest message == highest message */ /* -2 corrects for first message being #1 */ /* (for one item file: 8/4 (=2) -2 (=0) +minmsg (1) = 1 */ } long wheremsg (fdboard, msgnum) { long li; lseek (fdboard, (long) ((msgnum-minmsg(fdboard)+1) * sizeof (long)), 0); return( read (fdboard, &li, sizeof li) == sizeof li ? li : syscrash ("Can't find message pointer in read") ); } msg_read () /* .6 */ /* Major changes in 1.1: A. Index files are now used as arrays, with known seek offsets B. Read is now by message number, not byte number. Message 0 is NOT valid, but instead holds the lowest message number. */ { long start, current; printf("Starting message number (%d-%d, %d suggested)? ", minmsg(CIDX), maxmsg(CIDX), justread[cboard]); readline(buffer); start=0; if (sscanf(buffer, "%D", &start)==0) start=0; if (start==0) start=justread[cboard]; if (start maxmsg(CIDX)) { printf("No such message\n"); return; } lseek (CBOARD, wheremsg(CIDX, start), 0); cbreak(); current = start; do { justread[cboard]=current++; /* assign old value, then inc */ } while (!empty(CBOARD) && displ_message(CBOARD)); cooked(); } userlog () /* .7 */ { ulog log; lseek(files[USERLOG], 0L, 0); printf("Name Calls Last called\n"); cbreak(); while (read (files[USERLOG], &log, sizeof log)!=NULL && !FNStop(finkey(stdin))) printf ("%-14s %4d %s", log.name, log.numcalls, ctime(&log.lastcall)); cooked(); } xpert () { beginner= !beginner; } yagain() { if (new_caller) make_account(); fortune(2, UPDATE); myexit (-1); } relog () { int temp=0; temp = ( strcmp(CmdA,"zs")==0 ? 1: 0); if (new_caller) make_account(); if (temp==1) fortune(2,UPDATE); /* Return to where we came from */ else fortune(1,UPDATE); /* DON'T hang up the phone in either case */ } not_ready_yet () { printf("Not finished yet. Please try next week\n"); } syscrash(s1) STRING s1; { int x; perror(""); printf("Fatal error (%s) has occured. System crashing\n", s1); fflush(stdout); freopen(errorfile,"a", stderr); /* use freopen to make sure that a file descriptor is available */ fprintf(stderr, "%s %d", s1, errno); x=time(0); fputs(" ",stderr); fputs(ctime(&x),stderr); fflush(stderr); fclose (stderr); fclose(fopen("/bbs/core", "w")); /* Must be empty for core to dump */ restoretty(); abort(); } long filesize(file) /* Wasn't used til 1.1. Hmm...*/ { return lseek (file, 0L, 2); } int display(fd) /* 3.0 */ { char c; int x, count; setbuf (stdout, buffer); while (read(fd, &c, 1)==1 && c!='\001') { putchar(c); if (count++ >= OUTBUF) { fflush(stdout); count = 0; if FNStop(x=finkey(stdin)) break; } } fflush(stdout); setbuf (stdout, NULL); return x; } displ_file (file) STRING file; /* 3.0 */ { int fd; int c; fd = open(file, O_RDONLY); if (fd == -1) return -1; /* no file, then return */ cbreak(); /* .7 */ display(fd); /* 3.0 */ cooked(); /* .7 */ close(fd); return 0; } displ_message (file) /* display's the file until a chr$(1) or EOF */ /* .6 Returns false if ctrl_c, true otherwise */ /* .7 Runs in cbreak */ /* .8 Now accepts ctrl-c/k at ? prompt */ /* 1.1 Puts a blank line after return hit */ /* 1.2 Puts 2 blanks */ /* 3.0 one blank before, one after */ { char c; int x; cbreak(); x=display (file); cooked(); if (x==CTRL(c)) return FALSE; if (x==CTRL(k)) { skip_message(file); return TRUE; } /* .7 */ if (empty(file)) return TRUE; cbreak(); putchar('\n'); putchar('?'); alarm (WAIT_TIME); /* hangup detection */ x=getchar(); /* .8 */ if (x==EOF) fortune(2,NUPDATE); cooked(); /* No need to worry about exit throu fortune; that clears cbreak */ puts("\n\n"); return (x == CTRL(c) ? FALSE:TRUE); } skip_message(file) /* move to next ctrl-a */ { char c; while (read(file, &c, 1)==1 && c!=CTRL(a)) ; } get_password() { char *pass; loop: printf("What is your name (or handle)? "); getline(buffer, BUFSIZ); strcpy(currentuser=makestring(BUFSIZ), lower_convert(buffer)); if (NOGOOD(currentuser)) { printf ("Bad name, try again. \n"); goto loop; /* NOTREACHED */ } if (!get_log(currentuser)) { printf("Are you a new user? "); /* 1.1 */ getline (buffer, BUFSIZ); if (*buffer != 'y' && *buffer != 'Y') goto loop; new_caller=TRUE; return TRUE; }; pass=lower_convert(getpass("What is your password?")); if (strcmp(a_log.password, pass)==0) { int x; char *temp; printf((temp=defread("GREET=")) ? temp : "Welcome back. Last call was %s\nTotal calls: %d\n", ctime(&a_log.lastcall), a_log.numcalls); for (x=0; x #include struct termio t; ioctl (0, TCGETA, &t); t.c_cflag &= ~HUPCL; /* Don't hang up */ ioctl (0, TCSETA, &t); #endif /* End sys 3 */ wait (0); /* let the fortune print out */ myexit (0); } /* termp.sg_flags ^= ECHO; /* no echo */ /* stty(1,&termp); /* wait (0); /* let the fortune print out */ /* printf("\r"); /* sleep (3); /* wait a bit */ /* printf("+++"); /* hang up phone */ /* sleep (3); /* printf("ath\r"); /* termp.sg_flags ^= ECHO; /* turn echo on for local use */ /* sleep (1); /* just to be safe */ /* stty(1,&termp); /* Lines deleted since the system now hangs up properly. */ wait(0); myexit (0); /* exit program */ } myexit(arg) /* Restore terminal and exit */ { restoretty(); exit (arg); } restoretty() { ioctl (0, TIOCSETP, &origp); ioctl (0, TIOCSETC, &origc); } empty(file) /* Checks to see if a file has at least 2 characters (ctrl-a and newline usually) */ { char c; if (read (file,&c,2)==2) { /* if a character was read, */ lseek (file, -2L, 1); /* go back to it */ return FALSE; } return TRUE; /* nothing left in file */ } line *findline (message, l) /* 2.1 */ line *message; { while (message != NULL && (--l) > 0) message = message->next; return message; } line *cont (message) line *message; { line *buffer, *last; /* find end of message */ for (last=message; last!=NULL && last->next!=NULL; last=last->next) ; printf("Input text now\n"); do { alarm (WAIT_TIME); /* hangup detection */ if ((buffer=(line *)calloc((unsigned)1, (unsigned)sizeof(line)))==NULL) syscrash("No memory"); printf("%s",CmdB); /* Do not use getline (EOF) */ fgets (buffer->text, LINESIZE, stdin); /* if EOF then break loop */ if (*buffer->text=='\n' || *buffer->text == '\0') break; else /* adjust pointers */ if (last==NULL) message=buffer; else last->next=buffer; last=buffer; buffer->next=NULL; } while (*buffer->text!='\n'); /* continue until blank line */ return message; } line *edit (message) /* 2.1 */ line *message; { int x; line *edline; x = input ("Line number to change? "); edline=findline(message, x); if (edline == NULL) return message; printf ("Old line:\n"); printf (edline->text); printf ("Enter new line\n"); getline (buffer, BUFSIZ); /* 2.4 */ if (*buffer == '\0' || *buffer == '\n') return message; else sprintf (edline->text, "%s\n", buffer); /* 2.7 */ return message; } list_message (message) /* 2.1 now stops on ctrl-c/k */ line *message; { line *cline; int line_no=1; for (cline=message; cline!=NULL && !FNStop (finkey(stdin)); cline=cline->next) printf ("%d: %s", line_no++, cline->text); } line *delete (message) line *message; { return NULL; /* temp. version; doesn't actually delete anything */ } line *new (message) line *message; { if (msg_quit()) { message=delete (message); return cont(message); } return message; } long msg_write (message,file,header) /* Actual header is header+"from xx date yyy" */ line *message; STRING header; { time_t tim; long retval; nodie(); locking (file, LK_RLCK, 0L); /* don't let anyone else write; wait if someone is writting */ retval=lseek (file, 0L, 2); /* EOF */ tim=time(0); sprintf (buffer, "%sfrom %s date %s", header, currentuser, ctime(&tim)); write (file, buffer, len(buffer)); for (; message != NULL; message=message->next) if (write (file, message->text, len(message->text)) !=len(message->text)) syscrash("write error in message entry"); delete(message); /* recover memory used by message */ write (file, "\001", 1); /* ctrl-a terminates each message */ locking (file, LK_UNLK, 0L); /* let others write to it */ yesdie(); return retval; } line *in_message () /* Input a message at the end of file. */ /* Message terminated by ctrl-a */ { line *message=NULL; /* message points to the first line in the message */ message=cont(message); for (;;) { if (beginner) printf("Command list: %s\n",ecommand); get_command("Message entry command? "); switch (instr(ecommand, *CmdA)/2) { case 0: message=cont(message); break; case 1: message=edit(message); break; case 2: displ_file(ehelp); break; case 3: list_message(message); break; case 4: message=new(message); break; case 5: if (msg_quit()) return NULL; else break; case 6: return message; case 7: displ_file(ehelp); break; default: printf("No such command\n"); break; } } } long input_message (file, header) /* 3.10 */ STRING header; { line *message = in_message (); if (message == 0) return -1; return msg_write (message, file, header); } /* Tree routines follow. Routines that return a treestuff structure (treemem) go_left(treemem) go_right(treemem) go_up(treemem) go_down(treemem) go_x(msgnum) disk_to_mem(treedisk) make_down(treemem) (makes a child and returns its treemem) All the above change the current directory to whatever the message is. Other routines: returns STRING message_name(treemem) List of children is obtained by system(ls), or childfile=popen("ls","r") mem_to_disk(treedisk) returns a treejunk structure. Remember, treejunk is used for TREEIDX file, treestuff is used for internal computation. TREEIDX is NOT memory resident; it is read a record at a time when needed. Note: The message number in treejunk/treestuff MUST be the record number in TREEIDX; if messages are deleted, they must be renumbered. Future improvment number one: Get around this restriction. */ -- : Michael Gersten seismo!scgvaxd!stb!michael : Copy protection? Just say Pirate! (if its worth pirating)