|
DataMuseum.dkPresents historical artifacts from the history of: Commodore CBM-900 |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about Commodore CBM-900 Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - download
Length: 21565 (0x543d) Types: TextFile Notes: UNIX file Names: »learn.c«
└─⟦f27320a65⟧ Bits:30001972 Commodore 900 hard disk image with partial source code └─⟦f4b8d8c84⟧ UNIX Filesystem └─⟦this⟧ »cmd/learn.c«
/* * Learn - Computer Aided Computer Instruction * Script Interpreter. * [NOTE: This command should be marked as * setuid to root -- i.e. the owner of * the directory `/usr/learn/play'] */ #include <stdio.h> #include <sys/stat.h> #include <sgtty.h> #include <pwd.h> #include <errno.h> #include <signal.h> #include <time.h> #define MAXLINE 120 #define NCPS 30 #define NPAGE 20 /* Line per page (#print) */ #define NFNAME 60 /* Number of chars in a filename */ #define NSTREAM 10 /* Number of maximum streams for #next */ #define INSEP 0177 /* Input separator character */ #define INCROK 1 /* Speed increment for correct lesson */ #define INCRNOK (-4) /* Speed increment for incorrect lesson */ #define HISPEED 10 /* Largest speed rating allowed */ #define YES 0 /* User's answer of `yes' */ #define NO 1 /* User's answer of `no' */ #define NOTHING (-1) /* No answer at all yet */ #define RIGHT 1 /* Right answer */ #define WRONG 0 /* Wrong answer */ extern char *ctime(); extern char *mktemp(); char *tutorial(); int gexit(); int interrupt(); struct passwd *getpwuid(); int xprint(); int xcreate(); int xuser(); int xcpin(); int xuncpin(); int xcpout(); int xuncpout(); int xpipe(); int xunpipe(); int xcmp(); int xmatch(); int xbad(); int xsucceed(); int xfail(); int xlog(); int xnext(); int xend(); int xnegate(); struct keytab { char *k_name; int (*k_func)(); } keytab[] = { "print", xprint, "create", xcreate, "user", xuser, "copyin", xcpin, "uncopyin", xuncpin, "copyout", xcpout, "uncopyout", xuncpout, "pipe", xpipe, "unpipe", xunpipe, "cmp", xcmp, "match", xmatch, "bad", xbad, "succeed", xsucceed, "fail", xfail, "log", xlog, "next", xnext, "end", xend, "negate", xnegate, NULL, NULL, }; /* * This is the structure for threading lessons * with #next. */ struct lestab { char l_name[NCPS]; /* Name of lesson */ char l_seen; /* Already been used? */ char l_speed; /* Speed rating for this lesson */ } lestab[2][NSTREAM] = { "0", 1 }; struct lestab *currles = &lestab[1][0]; /* Current lesson */ struct lestab *prevles = &lestab[0][0]; /* Current lesson */ int nexti; /* Index for #next */ int testflg = 0; /* Test out script */ int lineno = 0; int oln; /* Line number for pagination of #print */ int speed = 0; /* Student's speed rating */ int answer; int result; /* result of lesson */ int iflag; /* Ignore input until next control line */ int nflag; /* Reading next lesson values */ int pflag; /* For #print from script */ int rptflag; /* Repeating lesson */ FILE *cpif; /* copy file for user's input */ FILE *cpof; /* copy for for user's output */ FILE *of; /* Output file */ FILE *pf; /* #pipe output file */ FILE *sf; /* Script file */ FILE *logfp; /* Default logging file */ int cpofd; /* Saved stdout */ char user[NCPS]; /* User-id */ char playarea[] = "uXXXXXX"; char more[] = "[Type `RETURN' to continue..]"; char erase[] = "\r \r"; char date[30]; /* Current date */ char *lesson; /* Current lesson name */ char line[MAXLINE]; char uline[MAXLINE]; /* User's input line */ char *lastline; /* Last user's input line */ char prompt[] = "$ "; char snull[] = ""; char sorry[] = "The last lesson was incorrect. Would you like to try it again?"; char *script; /* Script in learndir */ char cpifile[] = ".copy"; char cpofile[] = ".ocopy"; char *learndir = "/usr/learn"; main(argc, argv) int argc; char *argv[]; { signal(SIGHUP, gexit); signal(SIGINT, interrupt); while (argc>1 && (*argv[1]=='-' || *argv[1]=='+')) { if (*argv[1] == '+') testflg = 1; else learndir = &argv[1][1]; argc--; argv++; } if (argc > 4) usage(); if (argc > 3) { speed = atoi(argv[3]); if (speed>HISPEED || speed<0) { speed = HISPEED; fprintf(stderr, "learn: speed set to %d\n", HISPEED); } } lesson = script = NULL; if (argc > 2) lesson = argv[2]; if (argc > 1) script = argv[1]; else script = tutorial(); setup(script, lesson); driver(); /* NOTREACHED */ } /* * Tutorial version of learn. * This code is invoked when the user * just types: `learn'. * It then lists all of the available * subjects and asks the user to pick * one. */ char * tutorial() { register char *cp; register int c; register FILE *hfp; static char subj[50]; struct stat sb; sprintf(line, "%s/lib/subjects", learndir); if ((hfp = fopen(line, "r")) != NULL) { while (fgets(line, MAXLINE, hfp) != NULL) fputs(line, stdout); fclose(hfp); } for (;;) { printf("\nEnter subject ? "); cp = subj; while (cp < &subj[50-1]) { if ((c = getchar()) == EOF) gexit(1); if (c == '\n') break; *cp++ = c; } *cp = '\0'; if (cp == &subj[0]) gexit(1); while (c != '\n') c = getchar(); sprintf(line, "%s/lib/%s", learndir, subj); if (stat(line, &sb)>=0 && (sb.st_mode&S_IFMT)==S_IFDIR) break; printf("No lessons about `%s', try again.\n", subj); } return (subj); } /* * driver code for learn */ driver() { lopen(); for (;;) { dolesson(); if (rptflag == 0) score(); else rptflag = 0; if (result == RIGHT) { printf("Good. Lesson %s", lesson); printf(" (%d)", speed); printf("\n\n"); nextlesson(); lopen(); } else if (result == WRONG) { printf(sorry); for (;;) { if (fgets(line, MAXLINE, stdin) == NULL) gexit(1); if (match(line, "bye")) { printf("Good bye.\n"); gexit(0); } else if (match(line, "[yY]*")) { printf("Try the problem again.\n"); rewind(sf); rptflag = 1; break; } else { nextlesson(); lopen(); break; } } } else { nextlesson(); lopen(); } } } /* * nextlesson tries to select a new lesson. If the result * was RIGHT it goes to the next lesson selected by #next. * Otherwise, it is looking for another lesson to try at that * point (of another speed), and if none, moves on to * the next lesson anyway. */ nextlesson() { register struct lestab *lp, *slp, *tp; register diff; int d; int i; int pass; pass = 0; if (result != WRONG) { nomatch: tp = prevles; for (i=0; i<NSTREAM; i++, tp++) { tp->l_name[0] = '\0'; tp->l_seen = 0; } tp = prevles; prevles = currles; currles = tp; } lp = prevles; slp = NULL; while (lp->l_name[0] && lp-prevles < NSTREAM) { if (!lp->l_seen) { d = (d = lp->l_speed-speed) >= 0 ? d : -d; if (d < diff) { diff = d; slp = lp; } } lp++; } if (slp != NULL) { slp->l_seen = 1; lesson = slp->l_name; } else if (result==WRONG && pass++ == 0) goto nomatch; else gexit(0); } usage() { fprintf(stderr, "Usage: learn [-directory] [subject [lesson [speed]]]\n"); exit(1); } diag(args) { fprintf(stderr, "%d: ", lineno); fprintf(stderr, "%r", &args); fprintf(stderr, "\n"); } /* * For each lesson, this is called to process the * script which is found on the file pointer `sf'. */ dolesson() { register char *lp; lineno = 0; iflag = nflag = pflag = 0; result = NOTHING; lastline = NULL; uclose(&cpif); while (fgets(lp=line, MAXLINE, sf) != NULL) { lineno++; if (*lp == '#') { control(lp+1); continue; } if (*lp == '\\') lp++; if (nflag) next(lp); else if (pflag) output(lp); else if (!iflag) result = toshell(lp); } if (pf != NULL) unbal("#pipe"); if (cpif != NULL) unbal("#copyin"); if (cpofd > 0) unbal("#copyout"); } /* * Process control lines in learn * scripts. */ control(s) register char *s; { register char *cp; register struct keytab *kp; char keyword[NCPS]; uclose(&of); pflag = iflag = nflag = 0; while (*s==' ' || *s=='\t') s++; for (cp=keyword; *s!='\n' && *s!=' ' && *s!='\t' && *s; s++) if (cp < &keyword[NCPS-1]) *cp++ = *s; *cp = '\0'; cp = keyword; for (kp=keytab; kp->k_name!=NULL; kp++) if (streq(kp->k_name, cp)) { (*kp->k_func)(s); return; } diag("Illegal script control `#%s'", cp); } /* * Report a warning about one * of the three possible bracketins * commands being unbalanced. */ unbal(type) char *type; { fprintf(stderr, "Lesson %s: nonterminated %s command\n", lesson, type); } /* * Each line describing the next lesson in the graph is * passed here. */ next(s) register char *s; { register struct lestab *lp; register char *cp; if (nexti >= NSTREAM) { diag("Too many #next streams"); return; } lp = &currles[nexti++]; while (*s==' ' || *s=='\t') s++; if (*s=='\0' || *s=='\n') { diag("#next syntax error"); return; } for (cp=lp->l_name; *s!=' ' && *s!='\t' && *s!='\n' && *s; s++) if (cp < &lp->l_name[NCPS-1]) *cp++ = *s; *cp = '\0'; if (cp == lp->l_name) { diag("Null #next lesson"); return; } while (*s==' ' || *s=='\t') s++; lp->l_speed = atoi(s); } /* * The following are the control functions * implementing script `#' directive lines. */ /* * #print directive */ xprint(s) register char *s; { register c; register char *cp; FILE *pfp; char fname[NFNAME]; while (*s==' ' || *s=='\t') s++; for (cp=fname; *s!=' ' && *s!='\t' && *s!='\n' && *s; s++) if (cp < &fname[NFNAME-1]) *cp++ = *s; *cp = '\0'; if (rptflag) iflag = 1; else if (cp == fname) { pflag = 1; of = stdout; } else if ((pfp = fopen(fname, "r")) == NULL) diag("Cannot open #print file %s", fname); else { while ((c = getc(pfp)) != EOF) putchar(c); fclose(pfp); } } /* * #create - create file containing contents * of script until next control (`#') line. */ xcreate(s) register char *s; { register char *cp; char fname[NFNAME]; while (*s==' ' || *s=='\t') s++; for (cp=fname; *s!=' ' && *s!='\t' && *s!='\n' && *s; s++) if (cp < &fname[NFNAME-1]) *cp++ = *s; *cp = '\0'; if ((of = fopen(fname, "w")) == NULL) { diag("Cannot create %s", fname); iflag++; } pflag = 1; } /* * #user - take input from user's terminal */ xuser(s) char *s; { register char *cp, *wp; char word[NCPS]; for (;;) { moutput(prompt); oln = 0; if (fgets(uline, MAXLINE, stdin) == NULL) gexit(1); if (cpif != NULL) fprintf(cpif, "%s", uline); lastline = uline; for (cp=uline; *cp==' ' || *cp=='\t'; ) cp++; wp = word; while (*cp!=' ' && *cp!='\t' && *cp!='\n' && *cp) { if (wp < &word[NCPS-1]) *wp++ = *cp; cp++; } *wp = '\0'; if (streq(word, "answer")) { lastline = cp; break; } else if (streq(word, "yes")) { answer = YES; break; } else if (streq(word, "no")) { answer = NO; break; } else if (streq(word, "bye")) { printf("Good bye.\n"); gexit(0); } else if (streq(word, "ready")) break; else toshell(uline); } result = NOTHING; } /* * #copyin - initiate redirection of user's input to * `cpifile'. */ xcpin(s) char *s; { uclose(&cpif); if ((cpif = fopen(cpifile, "w")) == NULL) diag("#copyin file create failure"); } /* * #uncopyin - terminate redirection of user's input * to `cpifile'. */ xuncpin(s) char *s; { uclose(&cpif); } xcpout(s) char *s; { int pdes[2], pid; FILE *ifp; register int c; register int ignoring = 0; if (pipe(pdes)<0 || (pid=fork())<0) diag("#copyout failure"); if (pid) { /* parent */ /* * Save stdout and * change to pipe */ close(pdes[0]); cpofd = dup(fileno(stdout)); dup2(pdes[1], fileno(stdout)); close(pdes[1]); } else { /* child */ close(pdes[1]); if ((cpof = fopen(cpofile, "w")) == NULL) { diag("#copyout failure"); exit(1); } ifp = fdopen(pdes[0], "r"); while ((c = getc(ifp)) != EOF) { if (c == INSEP) { ignoring ^= 01; continue; } putc(c, stderr); if (!ignoring) putc(c, cpof); } exit(0); } } /* * #uncopyout - wait for child to get all the data * and restore stdout to original value. */ xuncpout(s) char *s; { int status; if (cpofd > 0) { dup2(cpofd, fileno(stdout)); close(cpofd); cpofd = 0; } else diag("Not in #copyout"); wait(&status); } xpipe(s) char *s; { int pdes[2], pid; if (pipe(pdes)<0 || (pid = fork())<0) diag("#pipe failure"); if (pid) { /* parent */ pf = fdopen(pdes[1], "w"); close(pdes[0]); } else { /* child */ close(pdes[1]); if (dup2(pdes[0], 0) != 0) diag("#pipe dup error"); close(pdes[0]); execl("/bin/sh", "learnsh", NULL); diag("#pipe cannot exec shell"); } } xunpipe(s) char *s; { int status; if (pf != NULL) { fclose(pf); pf = NULL; wait(&status); } } /* * Compare two files. * #cmp file1 file2 * #cmp file1 * this uses the script file until next # line */ xcmp(s) register char *s; { register char *cp; register c; FILE *f1, *f2; char fname[NFNAME]; while (*s==' ' || *s=='\t') s++; for (cp=fname; *s!=' ' && *s!='\t' && *s!='\n' && *s; s++) if (cp < &fname[NFNAME-1]) *cp++ = *s; *cp = '\0'; if ((f1 = fopen(fname, "r")) == NULL) diag("Cannot open %s in #cmp", fname); while (*s==' ' || *s=='\t') s++; for (cp=fname; *s!=' ' && *s!='\t' && *s!='\n' && *s; s++) if (cp < &fname[NFNAME-1]) *cp++ = *s; *cp = '\0'; if (cp == fname) { register int nlf = 0; if (f1==NULL) { iflag++; return; } result = RIGHT; for (;;) { if ((c = getc(sf))=='#' && nlf) { ungetc(c, sf); c = EOF; } nlf = 0; if (c == '\n') nlf++; if (c != getc(f1)) result = WRONG; if (c == EOF) break; } } else { if ((f2 = fopen(fname, "r")) == NULL) diag("Cannot open %s in #cmp", fname); if (f1!=NULL && f2!=NULL) { if (result == NOTHING) result = RIGHT; while ((c = getc(f1)) != EOF) if (c != getc(f2)) result = WRONG; } uclose(&f2); } uclose(&f1); } /* * #match - if string matches last input, set result to RIGHT. * Also, print out text if right. */ xmatch(s) register char *s; { while (*s==' ' || *s=='\t') s++; if (lastline == NULL) lastline = snull; if (match(lastline, s)) { pflag = 1; of = stdout; result = RIGHT; } else iflag = 1; } /* * #bad - opposite of #match - checks for wrong answers and * will print helpful hints. */ xbad(s) register char *s; { while (*s==' ' || *s=='\t') s++; if (lastline == NULL) lastline = snull; if (match(lastline, s)) { pflag = 1; of = stdout; result = WRONG; } else iflag = 1; } /* * #succeed - print message on success. */ xsucceed(s) char *s; { if (result == RIGHT) { pflag = 1; of = stdout; } else iflag = 1; } /* * #fail - print message upon failure in lesson */ xfail(s) char *s; { if (result != RIGHT) { pflag = 1; of = stdout; } else iflag = 1; } /* * #log - write out a logging entry * showing the status of this lesson. */ xlog(s) register char *s; { register char *cp; char fname[NFNAME]; FILE *lf; while (*s==' ' || *s=='\t') s++; for (cp=fname; *s!=' ' && *s!='\t' && *s!='\n' && *s; s++) if (cp < &fname[NFNAME-1]) *cp++ = *s; *cp = '\0'; if (cp == fname) lf = logfp; else lf = fopen(fname, "a"); if (lf == NULL) { if (lf == logfp) diag("cannot open default log file"); else diag("Cannot open log file `%s'", fname); return; } fprintf(lf, "%s:\t%s\t%s\tspeed=%d\t%s\n", lesson, user, result==RIGHT ? "right" : "wrong", speed, date); if (lf != logfp) fclose(lf); } /* * #next - selection of next lessons * -- fills up current lesson structure * which `nextlesson' will use if right * answer. */ xnext(s) char *s; { register struct lestab *lp; register i; lp = currles; for (i=0; i<NSTREAM; i++, lp++) { lp->l_name[0] = '\0'; lp->l_seen = 0; } nflag++; nexti = 0; } /* * Do nothing. This is to terminate text * for example in #print or #create so a * command can be inserted. */ xend(s) /* ARGSUSED */ char *s; { } /* * Negate the result that has been produced * up to now. If no result has been found, * don't do anything. */ /* ARGSUSED */ xnegate(s) char *s; { if (result == WRONG) result = RIGHT; else if (result == RIGHT) result = WRONG; } /* * Change speed score, dependent upon the * `result' of the lesson. */ score() { if (result == RIGHT) speed += INCROK; else speed += INCRNOK; if (speed > 10) speed = HISPEED; if (speed < 0) speed = 0; } /* * Conditionally close given unit, making the file * pointer null for future reference. */ uclose(fpp) register FILE **fpp; { if (*fpp != NULL) { if (*fpp != stdout) fclose(*fpp); *fpp = NULL; } } /* * Upon an interrupt either leave or resume * where we left off. */ interrupt() { signal(SIGINT, SIG_IGN); if (question("Do you really want to leave LEARN")) gexit(1); else signal(SIGINT, interrupt); } /* * Ask a question and return 1 if true, 0 otherwise. */ question(s) char *s; { register int c, c1; printf("%s ?", s); for (;;) { c1 = c = getchar(); while (c1!=EOF && c1!='\n') c1 = getchar(); if (c=='y' || c=='Y') return (1); else if (c=='n' || c=='N') return (0); printf("`yes' or `no' ? "); } } /* * Graceful exit from learn */ gexit(s) int s; { int status; if (pf != NULL) { fclose(pf); wait(&status); } printf("\n"); if (user[0] != '\0') rmdir(); exit(s); } /* * Remove user-directory as * part of cleaning up. * Chdir("/") necessary as we cannot remove * our current directory (limitation in rmdir). */ rmdir() { int pid; int status; char fname[NFNAME]; chdir("/"); if ((pid = fork()) < 0) diag("Cannot cleanup"); else if (pid) wait(&status); else { close(0); close(1); close(2); open("/dev/null", 2); dup(0); dup(0); sprintf(fname, "%s/play/%s", learndir, playarea); execl("/bin/rm", "rm", "-rf", fname, NULL); cerr("cannot cleanup"); } } /* * Pass string (`s') to shell as a command * line to be executed. */ toshell(s) char *s; { register char *cp; if (pf != NULL) { fprintf(pf, "%s", s); fflush(pf); return (NOTHING); } else { for (cp = s; *cp != '\0'; cp++) if (*cp == '\n') *cp = '\0'; if (*s == '\0') return (RIGHT); return (system(s) ? WRONG : RIGHT); } } /* * Output a line to file or terminal. * If it is the terminal (stdout) paginate * the output. */ output(s) register char *s; { static struct sgttyb sgb; static int sgflag; if (of == NULL) return; if (of == stdout) if (++oln > NPAGE) { fputs(more, stdout); if (sgflag == 0) { ioctl(fileno(stdout), TIOCGETP, &sgb); sgflag = sgb.sg_flags; } sgb.sg_flags &= ~ECHO; ioctl(fileno(stdout), TIOCSETN, &sgb); while (getchar() != '\n') ; sgb.sg_flags = sgflag; ioctl(fileno(stdout), TIOCSETN, &sgb); fputs(erase, stdout); oln = 0; } fputs(s, of); } /* * Output message. If in #copyout, have * to surround this message with `INSEP' * so it gets stripped by the #uncopyout processor. * If in #pipe, just return and print no prompt. */ /* VARARGS */ moutput(x) { if (pf != NULL) return; if (cpofd > 0) printf("%c%r%c", INSEP, &x, INSEP); else printf("%r", &x); } /* * Initial setup code * Passed script name (directory) for lopen's benefit. * Unless lesson is non-NULL, all of the script * is setup. * During this code, the play area is created and * we lose our setuid privileges once this is done. */ setup(sn, ln) register char *sn, *ln; { extern int errno; char fname[NFNAME]; register char *cp; register struct passwd *pwp; short int uid, gid; long xtime; uid = getuid(); gid = getgid(); if ((pwp = getpwuid(uid)) == NULL) pwp->pw_name = "Guess who?"; strcpy(user, pwp->pw_name); if (chdir(learndir)<0 || chdir("play")<0) cerr("bad learn directory structure"); if (mkdir(mktemp(playarea, 0777))<0) cerr("can't create user area (learn probably not setuid)"); chown(playarea, uid, gid); chdir(playarea); time(&xtime); cp = ctime(&xtime); cp[24] = '\0'; strcpy(date, cp); script = sn; if (ln == NULL) { printf("If you left off in the middle, type in the lesson\n"); printf("number (e.g. 2.3) that learn last typed out.\n"); printf("To start at the beginning just type RETURN:\n"); fgets(line, MAXLINE, stdin); for (cp=line; *cp!='\0'; cp++) if (*cp == '\n') *cp = '\0'; for (cp=line; *cp==' ' || *cp=='\t'; cp++) ; if (*cp != '\0') ln = cp; } if (ln != NULL) { for (cp=prevles->l_name; *ln; ln++) if (cp < &prevles->l_name[NCPS-1]) *cp++ = *ln; *cp = '\0'; } lesson = prevles->l_name; sprintf(fname, "%s/log/%s", learndir, script); logfp = fopen(fname, "a"); /* * Turn off our setuid * privileges here. */ setuid(uid); } /* * Match string to pattern. Pattern possibly contains * glob-type metacharacters. */ match(s, p) register char *s, *p; { register char *cp; for (cp = s; *cp==' ' || *cp=='\t'; cp++) ; for (s = cp; *cp!='\n' && *cp!='\0'; cp++) ; *cp = '\0'; for (cp = p; *cp!='\n' && *cp!='\0'; cp++) ; *cp = '\0'; return (pnmatch(s, p, 0)); } /* * Open lesson script file */ lopen() { char fname[NFNAME]; static notfirst; sprintf(fname, "%s/lib/%s/L%s", learndir, script, lesson); uclose(&sf); if ((sf = fopen(fname, "r")) == NULL) { if (notfirst) cerr("script error--lesson `%s' not found", lesson); else cerr("There are no lessons about `%s'", script); } if (!notfirst) notfirst = 1; } /* * Kludge until sys mkdir comes back. */ mkdir(fn, mode) char *fn; int mode; { extern int errno; register int pid; int status; if ((pid = fork()) < 0) return (-1); if (pid) { while (wait(&status) >= 0) ; if (status) { errno = status & 0377; return (-1); } return (0); } close(0); close(1); close(2); open("/dev/null", 2); dup(0); dup(0); execl("/bin/mkdir", "mkdir", fn, NULL); exit(1); } /* * Error output */ /* VARARGS */ cerr(x) { fprintf(stderr, "learn: %r\n", &x); exit(1); }