--- /dev/null
+License
+
+Copyright © 1977-1995 by Robert Swartz.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+This software is provided by the copyright holders and contributors "as is" and
+any express or implied warranties, including, but not limited to, the implied
+warranties of merchantability and fitness for a particular purpose are
+disclaimed. In no event shall the copyright holder or contributors be liable for
+any direct, indirect, incidental, special, exemplary, or consequential damages
+(including, but not limited to, procurement of substitute goods or services;
+loss of use, data, or profits; or business interruption) however caused and on
+any theory of liability, whether in contract, strict liability, or tort
+(including negligence or otherwise) arising in any way out of the use of this
+software, even if advised of the possibility of such damage.
--- /dev/null
+CC = sdcc
+ASM = sdasz80
+AR = sdar
+LINKER = sdcc
+FCC = ../../../Library/tools/fcc
+FCCOPTS = -O2
+PLATFORM =
+#PLATFORM = -tzx128
+
+PROGLOAD=`(cat ../../Kernel/platform/config.h; echo PROGLOAD) | cpp -E | tail -n1`
+
+.SUFFIXES: .c .rel
+
+SRCSNS = test.c
+
+SRCS = ac.c almanac.c at.c col.c deroff.c moo.c pr.c tar.c ttt.c
+
+SRCSBAD = calendar.c m4.c
+
+OBJS = $(SRCS:.c=.rel)
+OBJSNS = $(SRCSNS:.c=.rel)
+OBJSBAD = $(SRCSBAD:.c=.rel)
+
+LIBS = ../../../Library/libs/syslib.lib
+
+APPSNS = $(OBJSNS:.rel=)
+
+APPS = $(OBJS:.rel=) $(OBJSBAD:.rel=) $(OBJSNS:.rel=)
+
+all: $(APPS) sizes
+
+
+$(APPSNS): OPTS = --nostdio
+
+$(OBJS): %.rel: %.c
+
+$(OBJSNS): %.rel: %.c
+
+$(OBJSBAD): %.rel: %.c
+ $(FCC) $(PLATFORM) -c $<
+
+.c.rel:
+ $(FCC) $(PLATFORM) $(FCCOPTS) -c $<
+
+%: %.rel
+ $(FCC) $(PLATFORM) $(OPTS) $< -o $@
+
+sizes: $(APPS)
+ ls -l $(APPS) >size.report
+
+clean:
+ rm -f $(OBJS) $(APPS) $(SRCS:.c=) core *~ *.asm *.lst *.sym *.map *.noi *.lk *.ihx *.tmp *.bin size.report
+
+rmbak:
+ rm -f *~ core
+
--- /dev/null
+PLATFORM = 6809
+CC = m6809-unknown-gcc
+# These are wrappers for lwasm and lwar
+ASM = m6809-unknown-as
+AR = m6809-unknown-ar
+LINKER = lwlink
+CFLAGS = -I../../../Library/include -I../../../Library/include/6502 -Wall -pedantic
+LINKER_OPT = --format=raw -L../../../Library/libs -lc6809
+LIBGCCDIR = $(dir $(shell $(CC) -print-libgcc-file-name))
+LINKER_OPT += -L$(LIBGCCDIR) -lgcc
+LINKER_OPT += --script=../../util/$(TARGET).link
+ASM_OPT = -o
+CRT0 = ../../../Library/libs/crt0_6809.o
+
+.SUFFIXES: .c .o
+
+
+SRCS = ac.c almanac.c at.c calendar.c col.c deroff.c m4.c moo.c pr.c tar.c test.c ttt.c
+
+OBJS = $(SRCS:.c=.o)
+
+APPS = $(OBJS:.o=)
+
+all: $(APPS) sizes
+
+$(OBJS): $(SRCS)
+
+$(APPS): $(CRT0)
+
+%: %.o
+ $(LINKER) -o $@ $(LINKER_OPT) $(CRT0) $<
+
+sizes: $(APPS)
+ ls -l $(APPS) > size.report
+
+clean:
+ rm -f $(OBJS) $(APPS) $(SRCS:.c=) core *~ *.asm *.lst *.sym *.map *.noi *.lk *.ihx *.tmp *.bin size.report
+
+rmbak:
+ rm -f *~ core
--- /dev/null
+/*
+ * AC
+ * Login connect-time accounting.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <utmp.h>
+#include <time.h>
+#include <err.h>
+
+#define DAYSEC (24*60*60L) /* Seconds in a day */
+#define HOUR (60*60) /* Seconds in hour */
+#define DIRSIZ 30 /* FIXME: use shorter init size anyway */
+
+char *wtmpf = "/var/log/wtmp";
+
+char *months[] = {
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December",
+};
+
+char *days[] = {
+ "Sunday", "Monday", "Tuesday", "Wednesday",
+ "Thursday", "Friday", "Saturday"
+};
+char **plist; /* List of do-only people */
+
+typedef struct TERM {
+ struct TERM *t_next;
+ char t_line[8];
+ char t_name[DIRSIZ];
+ time_t t_time;
+} TERM;
+
+TERM *terminals;
+
+typedef struct PEOPLE {
+ struct PEOPLE *p_next;
+ char p_name[DIRSIZ];
+ time_t p_time;
+} PEOPLE;
+
+PEOPLE *people;
+
+time_t lasttime; /* Last time read from file */
+time_t runtotal; /* Running total used by ac -d */
+time_t midnight; /* Time of next midnight */
+
+int dflag; /* Daily version (midnight-midnight) */
+int pflag; /* Print totals by people */
+int badflag; /* Possible bad file format */
+
+/*
+ * Print a time entry with a name.
+ */
+void prtime(const char *name, time_t time)
+{
+ if (dflag)
+ printf("\t");
+ printf("%-*.*s", DIRSIZ, DIRSIZ, name);
+ time = (time+30)/60;
+ printf("%3ld:%02ld\n", time/60, time%60);
+}
+
+/*
+ * Print the results, depending on the flags.
+ * The `flag' says whether print is called at the
+ * end (0) or for each midnight-midnight period (1).
+ */
+void print(int flag)
+{
+ struct tm *tmp;
+ PEOPLE *pp;
+ time_t total = 0;
+
+ if (dflag) {
+ time_t endofday;
+
+ endofday = midnight-2*HOUR;
+ tmp = localtime(&endofday);
+ printf("%s %s %d:\n", days[tmp->tm_wday], months[tmp->tm_mon],
+ tmp->tm_mday);
+ }
+ for (pp = people; pp != NULL; pp = pp->p_next) {
+ if (pp->p_time > 0) {
+ total += pp->p_time;
+ if (pflag)
+ prtime(pp->p_name, pp->p_time);
+ }
+ if (flag)
+ pp->p_time = 0;
+ }
+ runtotal += total;
+ prtime("Total:", total);
+ if (dflag) {
+ printf("\n");
+ if (flag == 0) {
+ dflag = 0;
+ prtime("Total:", runtotal);
+ }
+ }
+}
+
+/*
+ * Enter a terminal node into the people table.
+ */
+void enter(TERM *tp)
+{
+ PEOPLE *pp;
+
+ if (tp->t_name[0] == '\0')
+ return;
+ for (pp = people; pp != NULL; pp = pp->p_next)
+ if (strncmp(tp->t_name, pp->p_name, DIRSIZ) == 0) {
+ pp->p_time += tp->t_time;
+ return;
+ }
+ if ((pp = (PEOPLE *)malloc(sizeof(PEOPLE))) == NULL)
+ errx(1, "Out of memory for people");
+ pp->p_next = people;
+ people = pp;
+ pp->p_time = tp->t_time;
+ strncpy(pp->p_name, tp->t_name, DIRSIZ);
+}
+
+/*
+ * Log the times recorded for the terminals by
+ * midnight of the current day.
+ */
+void atmidnight(void)
+{
+ TERM *tp;
+
+ for (tp = terminals; tp != NULL; tp = tp->t_next) {
+ if (tp->t_time < midnight) {
+ tp->t_time = midnight-tp->t_time;
+ enter(tp);
+ tp->t_time = midnight;
+ }
+ }
+}
+
+/*
+ * Mark the user found on this terminal
+ * as logged in.
+ */
+void login(struct utmp *utp)
+{
+ TERM *tp;
+ char *np;
+
+ for (np = utp->ut_user; *np != '\0'; np++)
+ if (!isascii(*np) || !isprint(*np)) {
+ badflag++;
+ lasttime = 0;
+ return;
+ }
+ for (np = utp->ut_line; *np != '\0'; np++)
+ if (!isascii(*np) || (!isalpha(*np) && !isdigit(*np))) {
+ badflag++;
+ lasttime = 0;
+ return;
+ }
+ if (*plist != NULL) {
+ char **plp;
+
+ for (plp = plist; *plp != NULL; plp++)
+ if (**plp == *utp->ut_user
+ && strncmp(*plp, utp->ut_user, DIRSIZ)==0)
+ break;
+ if (*plp == NULL)
+ return;
+ }
+ if ((tp = (TERM*)malloc(sizeof(TERM)))==NULL)
+ errx(1, "Out of memory for terminals");
+ tp->t_time = utp->ut_time;
+ strncpy(tp->t_name, utp->ut_user, DIRSIZ);
+ strncpy(tp->t_line, utp->ut_line, 8);
+ tp->t_next = terminals;
+ terminals = tp;
+}
+
+/*
+ * Mark a user as logged out.
+ * The user is given by the tty-name
+ * found in the utmp structure pointer.
+ * If this pointer is NULL, log all users
+ * out (e.g. at reboot and end of file).
+ */
+void logout(struct utmp *utp)
+{
+ TERM *tp, *ptp;
+
+loop:
+ if (terminals == NULL)
+ return;
+ ptp = NULL;
+ for (tp=terminals; tp != NULL; ptp=tp, tp=tp->t_next)
+ if (utp==NULL || strncmp(tp->t_line, utp->ut_line, 8)==0) {
+ if (ptp == NULL)
+ terminals = tp->t_next; else
+ ptp->t_next = tp->t_next;
+ tp->t_time = (utp==NULL ? lasttime : utp->ut_time)
+ - tp->t_time;
+ enter(tp);
+ free((char *)tp);
+ if (utp != NULL)
+ return;
+ goto loop;
+ }
+}
+
+/*
+ * Read through the wtmp file keeping track of
+ * each individual user. At the end either print total
+ * or by individual people.
+ */
+void readwtmp(void)
+{
+ struct utmp ut;
+ struct tm *tmp;
+ TERM *tp;
+ FILE *fp;
+ time_t tdelta = 0;
+
+ if ((fp = fopen(wtmpf, "r")) == NULL) {
+ fprintf(stderr, "ac: cannot open %s\n", wtmpf);
+ exit(1);
+ }
+ while (fread(&ut, sizeof ut, 1, fp) == 1) {
+ if (dflag && midnight==0) {
+ tmp = localtime(&ut.ut_time);
+ midnight = ut.ut_time - tmp->tm_sec
+ - tmp->tm_min*60 - tmp->tm_hour*60*60L + DAYSEC;
+ } else while (dflag && lasttime>=midnight) {
+ atmidnight();
+ print(1);
+ midnight += DAYSEC;
+ }
+ if (ut.ut_line[1] == '\0')
+ switch (ut.ut_line[0]) {
+ case '~': /* Reboot */
+ lasttime = ut.ut_time;
+ logout(NULL);
+ continue;
+
+ case '|': /* Old time */
+ tdelta = ut.ut_time;
+ continue;
+
+ case '}': /* New time */
+ tdelta = ut.ut_time-tdelta;
+ lasttime = ut.ut_time;
+ for (tp = terminals; tp!=NULL; tp = tp->t_next)
+ tp->t_time += tdelta;
+ continue;
+ }
+ if (ut.ut_time < lasttime) {
+ if (lasttime-ut.ut_time > 60) {
+ badflag++;
+ continue;
+ }
+ ut.ut_time = lasttime;
+ } else
+ lasttime = ut.ut_time;
+ logout(&ut);
+ if (ut.ut_user[0] != '\0')
+ login(&ut);
+ }
+ fclose(fp);
+ time(&lasttime);
+ logout(NULL);
+}
+
+
+/*
+ * Error reporting.
+ */
+void usage(void)
+{
+ fprintf(stderr, "Usage: ac [-w wtmpfile] [-d] [-p] [username ...]\n");
+ exit(1);
+}
+
+int main(int argc, char *argv[])
+{
+ char *ap;
+
+ while (argc>1 && *argv[1]=='-') {
+ for (ap = &argv[1][1]; *ap != '\0'; ap++)
+ switch (*ap) {
+ case 'd':
+ dflag = 1;
+ break;
+
+ case 'p':
+ pflag = 1;
+ break;
+
+
+ case 'w':
+ if (argc < 2)
+ usage();
+ wtmpf = argv[2];
+ argv++;
+ argc--;
+ break;
+
+ default:
+ usage();
+ }
+ argv++;
+ argc--;
+ }
+ plist = argv+1;
+ readwtmp();
+ print(0);
+ if (badflag)
+ errx(1, "possible bad file format");
+ exit(0);
+}
--- /dev/null
+/*
+ * Print an almanac of daily events. Program reads today's date,
+ * constructs a date string, and then examines the files
+ * /usr/games/lib/almanac.birth, /usr/games/lib/almanac.death, and
+ * /usr/games/lib/almanac.event for strings that contain the date string.
+ * Originally written in the shell.
+ *
+ * By fb, 7/12/88.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#define EXIT_SUCCESS 0
+#define EXIT_FAILURE 1
+
+const char *months[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+const char *filenames[] = {
+ "/usr/games/lib/almanac.birth",
+ "/usr/games/lib/almanac.death",
+ "/usr/games/lib/almanac.event"
+};
+
+const char *slugs[] = {
+ "BIRTHS:",
+ "DEATHS:",
+ "EVENTS:"
+};
+
+/* print an error message and exit */
+void fatal(const char *message)
+{
+ fprintf(stderr, "%s\n", message);
+ exit(EXIT_FAILURE);
+}
+
+void findstring(const char *date, FILE * fileptr)
+{
+ char buffer[120];
+ int length = strlen(date);
+
+ while (fgets(buffer, 120, fileptr) != NULL) {
+ if (strncmp(buffer, date, length) == 0)
+ printf("%s", buffer + length);
+ else
+ continue;
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ time_t rawtime;
+ struct tm *brokentime;
+ FILE *fileptr;
+ char buffer[20];
+ int i;
+
+ if (argc == 3) {
+ if (strlen(argv[1]) < 3)
+ fatal("Usage: almanac [Mon date]");
+ *(argv[1] + 3) = '\0';
+ sprintf(buffer, "%3s %s ", argv[1], argv[2]);
+ } else if (argc == 1) {
+ /* build the time string */
+ rawtime = time(NULL);
+ brokentime = localtime(&rawtime);
+ sprintf(buffer, "%s %d ", months[brokentime->tm_mon],
+ brokentime->tm_mday);
+ } else
+ fatal("Usage: almanac [Mon date]");
+
+ /* open almanac text files; call string printing routine */
+ for (i = 0; i < 3; i++) {
+ if ((fileptr = fopen(filenames[i], "r")) == NULL)
+ fatal("Cannot open almanac file.");
+
+ printf("%s\n", slugs[i]);
+
+ findstring(buffer, fileptr);
+
+ fclose(fileptr);
+ }
+ exit(EXIT_SUCCESS);
+}
--- /dev/null
+/*
+ * At takes a list of commands and arranges for them to be executed at
+ * a specified time. When the commands are executed, the user id, group
+ * id, exported shell variables and current directory will all be as they
+ * were when at was executed.
+ * The format of the at command is
+ * at [-v] [-c command] time [week] [file]
+ * or
+ * at [-v] [-c command] time day_of_week [week] [file]
+ * or
+ * at [-v] [-c command] time month day_of_month [file]
+ * Here the presence of `week' implies that the command should occur
+ * on the next week. If the v-flag is specified, then at prints out
+ * when the command will be executed. If the c-flag is specified
+ * then the command come from the string `command'. If a file is
+ * specified, then they come from that file. If none of these is
+ * done, then standard input is read.
+ * Note that at should not be set uid or set gid. Anyone can write
+ * in the at spooling directory. Security is maintained because at
+ * sets the set uid, set gid and owner execute bits. Without all of
+ * these being set, atrun will not run the script.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <err.h>
+#include <sys/types.h>
+#include <time.h>
+#include <dirent.h>
+#include <signal.h>
+
+
+#define NUL '\0'
+#define TRUE (0 == 0)
+#define FALSE (0 != 0)
+#define STDOUT 1
+#define WEEK 7
+#define YEAR 365
+#define LEAPM 1 /* leap month */
+#define YBASE 1900 /* Year bias in struct tm */
+#define FSTDAY 0 /* Week day of 1st day of year 0 */
+#define UNKNOWN -1
+#define MODE 06500 /* set uid, set gid, owner read-exec */
+#define OUTNL 13 /* "yymmddhhmm.aa" */
+
+
+struct tm td = { /* time to do command */
+ 0, UNKNOWN, UNKNOWN,
+ UNKNOWN, UNKNOWN, UNKNOWN,
+ UNKNOWN, UNKNOWN, UNKNOWN
+ };
+int wflg = FALSE, /* `week' flag */
+ vflg = FALSE; /* verify flag */
+char *cstr = NULL, /* c-flag command string */
+ outn[] = "/usr/spool/at/yymmddhhmm.aa";
+FILE *outfp = NULL; /* file to place commands into */
+extern char **environ;
+
+/*
+ * Clean is called when an interrupt occurs, or on any error. Its only
+ * function is to unlink the partial file in at's spooling directory.
+ */
+void clean(int sig)
+{
+ if (outfp != NULL) {
+ fclose(outfp);
+ unlink(outn);
+ }
+ exit(1);
+}
+
+/*
+ * Die prints a message on stderr and exits with status one.
+ */
+void die(const char *str, ...)
+{
+ va_list va;
+ va_start(va, str);
+ vwarnx(str, va);
+ va_end(va);
+ clean(0);
+}
+
+/*
+ * Setsigs simply assures that on an interrupt we will clean up after
+ * ourselves so as to avoid leaving junk in at's spooling directory.
+ */
+void setsigs(void)
+{
+ sighandler_t fnc;
+
+ fnc = signal(SIGINT, SIG_IGN);
+ if (fnc != SIG_IGN)
+ signal(SIGINT, clean);
+}
+
+/*
+ * Makefile makes a file in the correct directory with a name of the
+ * form
+ * yymmddhhmm.xx
+ * where
+ * yy is the year (mod 100)
+ * mm is the month
+ * dd is the day of the month
+ * hh is the hour
+ * and
+ * mm is the minute
+ * at which the command is to be execute. The xx is simply to make
+ * the file name unique. The name is placed in outn and the stream
+ * is in outfp. Also, the mode of the file is execute for owner only.
+ * An initial line is written on the file to remove itself.
+ */
+
+void makefile(void)
+{
+ char *cp;
+
+ cp = &outn[(sizeof outn) - OUTNL - 1];
+ sprintf(cp, "%02d%02d%02d%02d%02d.aa", td.tm_year % 100,
+ 1 + td.tm_mon, td.tm_mday, td.tm_hour, td.tm_min);
+ cp = &outn[(sizeof outn) - 2];
+ while (access(outn, 0) == 0)
+ if (++*cp > 'z') {
+ *cp = 'a';
+ if (++cp[-1] > 'z')
+ die("Too many things to do at the same time");
+ }
+ outfp = fopen(outn, "w");
+ if (outfp == NULL)
+ die("Can't create %s\n", outn);
+ chmod(outn, MODE);
+ fprintf(outfp, "rm -f %s\n", outn);
+}
+
+/*
+ * Copyenv writes onto outfp a pair of lines for each item in the environment.
+ * These lines are of the form
+ * var=value
+ * export var
+ * This will cause all exported variables to be reset when the file
+ * is executed by the shell.
+ */
+
+void copyenv(void)
+{
+ char **vp, *cp, ch, *end;
+
+ for (vp=environ; (cp=*vp) != NULL; ++vp) {
+ while ((ch=*cp++) != '=' && ch != NUL)
+ putc(ch, outfp);
+ end = cp - 1;
+ fprintf(outfp, "='");
+ if (ch != '=')
+ --cp;
+ while ((ch=*cp++) != NUL)
+ if (ch == '\'')
+ fprintf(outfp, "'\\''");
+ else
+ putc(ch, outfp);
+ fprintf(outfp, "'\n");
+ fprintf(outfp, "export %.*s\n", end-*vp, *vp);
+ }
+}
+
+/*
+ * Copyumask writes out a line onto the stream outfp of the form
+ * umask number
+ * where number is the current umask. This will cause the shell
+ * to execute the commands with the current umask.
+ */
+void copyumask(void)
+{
+ int um;
+
+ um = umask(0777);
+ umask(um);
+ fprintf(outfp, "umask %03o\n", um);
+}
+
+/*
+ * Copywd writes a line onto the file outfp of the form
+ * cd current working directory
+ * This will cause the current directory to be reset when the file
+ * is executed by the shell.
+ *
+ * FIXME: quoting ?
+ */
+void copywd(void)
+{
+ int pid;
+ int stat;
+ static const char *args[] = {
+ "pwd",
+ NULL
+ };
+
+ fprintf(outfp, "cd ");
+ fflush(outfp);
+ pid = fork();
+ if (pid == 0) {
+ dup2(fileno(outfp), STDOUT);
+ fclose(outfp);
+ execv("/bin/pwd", args);
+ execv("/usr/bin/pwd", args);
+ exit(1);
+ }
+ if (pid < 0)
+ die("Try again");
+ wait(&stat);
+ if (stat != 0)
+ die("Can't find pwd");
+}
+
+/*
+ * Copycmds simply copies from stdin until EOF.
+ */
+void copycmds(void)
+{
+ int ch;
+
+ if (cstr != NULL)
+ fprintf(outfp, "%s\n", cstr);
+ else
+ while ((ch=getchar()) != EOF)
+ putc(ch, outfp);
+}
+
+/*
+ * Usage give the usage message and dies.
+ */
+void usage(void)
+{
+ static char umsg[] =
+"usage: at [-v] [-c command] time [month month_day | week_day] [file]";
+
+ die(umsg);
+}
+
+/*
+ * Mopts cracks the `minus' options. It returns the first
+ * unused argument.
+ */
+
+char **mopts(char *argv[])
+{
+ register char *str,
+ ch;
+
+ for (str=*argv; str != NULL && *str++ == '-'; str=*++argv)
+ for (ch=*str++; ch != NUL; ch=*str++)
+ switch (ch) {
+ case 'c':
+ if ((cstr = *++argv) == NULL)
+ usage();
+ break;
+ case 'v':
+ vflg = TRUE;
+ break;
+ default:
+ usage();
+ }
+ return (argv);
+}
+
+/*
+ * Gettime takes the string `str' and sets the td minute and
+ * hour fields appropriately. A time is:
+ * either
+ * 0-2 digits (hours)
+ * or
+ * 3-4 digits (hours and minutes)
+ * possibly followed by a string starting with
+ * a for A.M.
+ * p for P.M.
+ * n for noon
+ * m for mid-night.
+ */
+void gettime(char *str)
+{
+ register int ch,
+ hour;
+ int min;
+ char *chp;
+
+ hour = 0;
+ chp = str;
+ for (ch=*chp++; isascii(ch) && isdigit(ch); ch = *chp++)
+ hour = 10*hour + ch - '0';
+ if (chp - str - 1 <= 2)
+ hour *= 100;
+ min = hour % 100;
+ hour /= 100;
+ if (hour >= 24 || min >= 60)
+ usage();
+ switch (ch) {
+ case NUL:
+ break;
+ case 'a':
+ if (hour == 12)
+ hour = 0;
+ break;
+ case 'p':
+ if (hour < 12)
+ hour += 12;
+ break;
+ case 'n':
+ hour = 12;
+ break;
+ case 'm':
+ hour = 0;
+ break;
+ default:
+ usage();
+ }
+ td.tm_hour = hour;
+ td.tm_min = min;
+}
+
+/*
+ * Mut searches the table `tbl' for entrys which may be trunctated to
+ * `str'. If there is exactly one such entry, it returns its ordinal.
+ * Otherwise it returns EOF. Note that `tbl' should be NULL-terminated.
+ */
+int mut(const char *str, const char *tbl[])
+{
+ register char **tp;
+ register int len;
+ int res;
+
+ tp = tbl;
+ len = strlen(str);
+ do {
+ if (*tp == NULL)
+ return (EOF);
+ } while (strncmp(*tp++, str, len) != 0);
+ res = tp - tbl - 1;
+ do {
+ if (*tp == NULL)
+ return (res);
+ } while (strncmp(*tp++, str, len) != 0);
+ return (EOF);
+}
+
+/*
+ * Getdate fills in either the month and month-day or the week-day
+ * fields of td from the date pointed to by `argv'. It returns
+ * a pointer to the next argument.
+ */
+char **getdate(char *argv[])
+{
+ register int idx;
+ static const char *day[] = {
+ "sunday", "monday",
+ "tuesday", "wednesday",
+ "thursday", "friday",
+ "saturday", NULL
+ },
+ *month[] = {
+ "january", "february",
+ "march", "april",
+ "may", "june",
+ "july", "august",
+ "september", "october",
+ "november", "december",
+ NULL
+ };
+
+ if (*argv == NULL)
+ return (argv);
+ idx = mut(*argv, month);
+ if (idx != EOF) {
+ td.tm_mon = idx;
+ if (*++argv == NULL || (td.tm_mday=atoi(*argv++)) == 0)
+ usage();
+ return (argv);
+ }
+ idx = mut(*argv, day);
+ if (idx != EOF) {
+ td.tm_wday = idx;
+ ++argv;
+ }
+ if (*argv != NULL && (wflg = strcmp(*argv, "week")==0))
+ ++argv;
+ return (argv);
+}
+
+/*
+ * Isleap returns TRUE iff the year `year' is a leap year.
+ */
+int isleap(int year)
+{
+ return (year%4 == 0 && (year%100 != 0 || year%400 == 0));
+}
+
+/*
+ * The array mlen is an array of month lengths, indexed by the
+ * month number (0 - 11).
+ */
+static int mlen[] = { /* month lengths */
+ 31, 28, 31, 30,
+ 31, 30, 31, 31,
+ 30, 31, 30, 31
+ };
+
+
+/*
+ * Mdtoyd returns the year-day for the date with year `year', month
+ * `month' and month-day `mday'.
+ */
+int mdtoyd(int year, int month, int mday)
+{
+ int res, leap;
+
+ res = mday - 1;
+ if (leap = isleap(year))
+ ++mlen[LEAPM];
+ while (--month >= 0)
+ res += mlen[month];
+ if (leap)
+ --mlen[LEAPM];
+ return (res);
+}
+
+
+/*
+ * Ydtowd returns the week day for the date with year `year' and
+ * year-day `yday'.
+ */
+int ydtowd(int year, int yday)
+{
+ register int wday;
+
+ --year;
+ wday = FSTDAY + year * (YEAR%WEEK) + yday%WEEK;
+ wday += (year+4) / 4;
+ wday -= (year+100) / 100;
+ wday += (year+400) / 400;
+ wday %= WEEK;
+ return (wday);
+}
+
+
+/*
+ * Ydtom sets `pmonth' and `pmday' to the month and month-day of the
+ * date with year `year' and year-day `yday'.
+ */
+int ydtom(int year, int yday, int *pmonth, int *pmday)
+{
+ int mday, month;
+ int leap;
+
+ if (leap = isleap(year))
+ ++mlen[LEAPM];
+ mday = yday;
+ for (month=0; mday >= mlen[month]; ++month)
+ mday -= mlen[month];
+ *pmonth = month;
+ *pmday = mday + 1;
+ if (leap)
+ --mlen[LEAPM];
+}
+
+/*
+ * Cyday computes the year and year-day on which the command should be
+ * performed. It does this on the basis of wflg (which is true iff
+ * we should delay by a week) and td.
+ * The result is placed in td.
+ */
+void cyday(void)
+{
+ int late, len;
+ struct tm *ct; /* current time */
+ time_t now;
+
+ time(&now);
+ ct = localtime(&now);
+ td.tm_yday = ct->tm_yday;
+ td.tm_year = ct->tm_year;
+ if (td.tm_mon != UNKNOWN) {
+ late = td.tm_mon <= ct->tm_mon;
+ late &= td.tm_mon < ct->tm_mon
+ || td.tm_mday < ct->tm_mday;
+ if (late)
+ ++td.tm_year;
+ td.tm_yday = mdtoyd(td.tm_year + YBASE, td.tm_mon, td.tm_mday);
+ td.tm_wday = ydtowd(td.tm_year + YBASE, td.tm_yday);
+ } else {
+ late = td.tm_hour <= ct->tm_hour;
+ late &= td.tm_hour < ct->tm_hour
+ || td.tm_min <= ct->tm_min;
+ if (td.tm_wday != UNKNOWN) {
+ td.tm_yday += (td.tm_wday + WEEK - ct->tm_wday) % WEEK;
+ late &= td.tm_wday == ct->tm_wday;
+ if (late | wflg)
+ td.tm_yday += WEEK;
+ } else {
+ td.tm_wday = ct->tm_wday;
+ if (wflg)
+ td.tm_yday += WEEK;
+ else if (late) {
+ ++td.tm_yday;
+ td.tm_wday = (td.tm_wday+1) % WEEK;
+ }
+ }
+ len = (isleap(td.tm_year + YBASE)) ? YEAR+1 : YEAR;
+ if (td.tm_yday >= len) {
+ td.tm_yday -= len;
+ ++td.tm_year;
+ }
+ ydtom(td.tm_year + YBASE, td.tm_yday, &td.tm_mon, &td.tm_mday);
+ }
+}
+
+/*
+ * Options cracks the command line options.
+ */
+
+void options(char *argv[])
+{
+ argv = mopts(++argv);
+ if (*argv == NULL)
+ usage();
+ gettime(*argv++);
+ argv = mopts(argv);
+ argv = getdate(argv);
+ argv = mopts(argv);
+ if (*argv != NULL) {
+ if (cstr != NULL)
+ usage();
+ if (freopen(*argv, "r", stdin) == NULL)
+ die("Can't open %s", *argv);
+ argv = mopts(++argv);
+ }
+ if (*argv != NULL)
+ usage();
+ cyday();
+}
+
+
+int main(int argc, char *argv[])
+{
+ setsigs();
+ options(argv);
+ if (vflg)
+ printf("%s", asctime(&td));
+ makefile();
+ copyenv();
+ copyumask();
+ copywd();
+ copycmds();
+ return (0);
+}
+
--- /dev/null
+/*
+ * calendar
+ * Reminder utililty: read calendar files and lines with dates matching
+ * the current date or the date specified in the option string.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <err.h>
+#include <time.h>
+#include <string.h>
+#include <ctype.h>
+
+const char *message =
+"Usage: calendar [ -a ] [ -ffile ]... [ -d[date] ] [ -w[date] ] [ -m[month] ]\n\
+Options:\n\
+ -a Search calendars of all users and send mail.\n\
+ -ffile Search each \"file\" in order given.\n\
+ -d[date] Print all entries matching \"date\".\n\
+ -w[date] Print entries in the week beginning with \"date\".\n\
+ -m[month] Print entries in the given \"month\".\n\
+The default calendar is $HOME/.calendar. The default date is today.\n\
+";
+
+char *argv0;
+int all = 0;
+int wday;
+int advance;
+char CurLine[512];
+char *CurLinep;
+
+enum {NONE, DAY, WEEK, MONTH} mflag;
+
+#define CALFILE "/.calendar"
+
+/*
+ * pnmatch(string, pattern, unanchored)
+ * returns 1 if pattern matches in string.
+ * pattern:
+ * [c1c2...cn-cm] class of characters.
+ * ? any character.
+ * * any # of any character.
+ * ^ beginning of string (if unanchored)
+ * $ end of string (if unanchored)
+ * unanch:
+ * 0 normal (anchored) pattern.
+ * 1 unanchored (^$ also metacharacters)
+ * >1 end unanchored.
+ * >1 is used internally but should not be used by the user.
+ */
+
+int pnmatch(const char *s, const char *p, int unanch)
+{
+ int c1;
+ int c2;
+
+ if (unanch == 1) {
+ while (*s)
+ if (pnmatch(s++, p, ++unanch))
+ return (1);
+ return (0);
+ }
+ while ((c2 = *p++) != 0) {
+ c1 = *s++;
+ switch(c2) {
+ case '^':
+ if (unanch == 2) {
+ s--;
+ continue;
+ } else if (unanch == 0)
+ break;
+ else
+ return (0);
+
+ case '$':
+ if (unanch)
+ return (c1 == '\0');
+ break;
+
+ case '[':
+ for (;;) {
+ c2 = *p++;
+ if (c2=='\0' || c2==']')
+ return (0);
+ if (c2 == '\\' && *p == '-')
+ c2 = *p++;
+ if (c2 == c1)
+ break;
+ if (*p == '-')
+ if (c1<=*++p && c1>=c2)
+ break;
+ }
+ while (*p && *p++!=']')
+ ;
+
+ case '?':
+ if (c1)
+ continue;
+ return(0);
+
+ case '*':
+ if (!*p)
+ return(1);
+ s--;
+ do {
+ if (pnmatch(s, p, unanch))
+ return (1);
+ } while(*s++ != '\0');
+ return(0);
+
+ case '\\':
+ if ((c2 = *p++) == '\0')
+ return (0);
+ }
+ if (c1 != c2)
+ return (0);
+ }
+ return(unanch ? 1 : !*s);
+}
+
+void usage(void)
+{
+ fprintf(stderr, "%s", message);
+ exit(1);
+}
+
+int findmon(void)
+{
+ int i;
+ int month;
+ char *t;
+ static char tbuf[100];
+ static const char *mon[12] = {
+ "[Jj][Aa][Nn][Uu. :1-9]",
+ "[Ff][Ee][Bb][Rr. :1-9]",
+ "[Mm][Aa][Rr][Cc. :1-9]",
+ "[Aa][Pp][Rr][Ii. :1-9]",
+ "[Mm][Aa][Yy][ :1-9]",
+ "[Jj][Uu][Nn][Ee. :1-9]",
+ "[Jj][Uu][Ll][Yy. :1-9]",
+ "[Aa][Uu][Gg][Uu. :1-9]",
+ "[Ss][Ee][Pp][Tt. :1-9]",
+ "[Oo][Cc][Tt][Oo. :1-9]",
+ "[Nn][Oo][Vv][Ee. :1-9]",
+ "[Dd][Ee][Cc][Ee. :1-9]"
+ };
+
+ t = &tbuf[0];
+ for (i = 0;
+ ((*t = CurLinep[i]) != ':') && (*t != '\n') && (*t != '\0');
+ i++)
+ ++t;
+ t[1] = '\0';
+ t = &tbuf[0];
+
+ month = 0;
+ for (i = 0; i <= 11; i++) /* Look for month in word form. */
+ if (pnmatch(t, mon[i], 1)) {
+ month = i+1;
+ break;
+ }
+ if (month == 0) { /* Month must be in numerical form */
+ for (; *t && !isdigit(*t); t++)
+ CurLinep++;
+ while (*t && isdigit(*t)) {
+ month = 10*month + *t++ - '0';
+ CurLinep++;
+ }
+ CurLinep++; /* Truncate global line pointer */
+ }
+ if (month == 0 || month > 12)
+ month = -1;
+ return (month);
+}
+
+int findday(void)
+{
+ register int day = 0;
+
+ for (; *CurLinep && !isdigit(*CurLinep); CurLinep++);
+ ;
+ while (*CurLinep && isdigit(*CurLinep))
+ day = 10*day + *CurLinep++ - '0';
+ if (day == 0 || day > 31) /* Invalid day of the month */
+ day = -1;
+ return (day);
+}
+
+int findyear(void)
+{
+ int c;
+ int year;
+
+ year = 0;
+ for (; (c = *CurLinep) != '\0' && !isdigit(c); CurLinep++)
+ if (c == '\n' || c == ':' || c == '\0')
+ break;
+ while (isdigit(*CurLinep))
+ year = 10*year + *CurLinep++ -'0';
+ if (year >= 83 && year <= 99)
+ year += 1900;
+ else if (year < 1990 || year > 3000)
+ year = -1;
+ return(year);
+}
+
+extern int date(int day, int month, int year);
+
+int current(int opt)
+{
+ struct tm *stimep;
+ time_t timep;
+ int retval;
+
+ time(&timep);
+ stimep = localtime(&timep);
+ switch (opt) {
+ case 0:
+ retval = date(stimep->tm_mday, stimep->tm_mon+1, stimep
+ ->tm_year+1900);
+ wday = stimep->tm_wday;
+ break;
+ case 1:
+ retval = stimep->tm_year+1900;
+ break;
+ case 2:
+ retval = stimep->tm_mon+1;
+ break;
+ default:
+ errx(1, "bad opt to current");
+ }
+ return (retval);
+}
+
+int date(int day, int month, int year)
+{
+ int date=0;
+ register int i;
+ static int m[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+
+ if (year == 0)
+ year = current(1);
+ for (i = 0; i < month-1; i++)
+ date += m[i];
+ date += day;
+ if ((year%4) == 0 && month > 2 && (year%100) != 0)
+ date++;
+ for (i = 1984; i < year; i++) {
+ if ((i%4) == 0)
+ date += 366;
+ else
+ date += 365;
+ }
+ return (date);
+}
+
+/* FIXME: quoting and use getpwnam() */
+void doall(void)
+{
+ FILE *fp;
+ static char pline[256]; /* Current line in passwd file */
+ static char uname[32];
+ static char ucalfile[128];
+ static char cmd[128];
+ char *cp1, *cp2, *cp3;
+ int i;
+
+ if ((fp = fopen("/etc/passwd", "r")) == NULL)
+ err(1, "cannot open /etc/passwd");
+ while ((fgets(&pline[0],sizeof(pline),fp) != NULL) && *pline) {
+ for (cp1 = &pline[0]; *cp1 != '\0'; cp1++)
+ if (*cp1 == '\n') {
+ *cp1 = '\0';
+ break;
+ }
+ cp1 = &pline[0];
+ for (cp2 = &uname[0]; *cp1 != '\0' && *cp1 != ':'; )
+ *cp2++ = *cp1++;
+ *cp2 = '\0';
+ for (i = 0; i < 4 && *cp1 != '\0'; i++)
+ for (cp1++; *cp1 != '\0' && *cp1 != ':'; cp1++)
+ ;
+ cp1++;
+ cp3 = &ucalfile[0];
+ for (; *cp1 != '\0' && *cp1 != ':'; )
+ *cp3++ = *cp1++;
+ *cp3 = '\0';
+ if (ucalfile[0] == '\0')
+ continue;
+ strcat(ucalfile, "/.calendar");
+ if (open(ucalfile, 0) >= 0) { /* file is readable */
+ strcpy(cmd, argv0);
+ strcat(cmd, " -f");
+ strcat(cmd, ucalfile);
+ strcat(cmd, " | /bin/mail ");
+ strcat(cmd, uname);
+ system(cmd);
+ }
+ }
+ exit(0);
+}
+
+int main(int argc, char *argv[])
+{
+ int arg = 1;
+ char *cp;
+ char *thisline;
+ char *matchstr;
+ char *filename[10];
+ FILE *fp[10];
+ int matchdate;
+ int foundfiles;
+ int nfiles;
+ int thismonth, thisday, thisyear;
+ int thisdate;
+ char *atsign;
+
+ argv0 = argv[0];
+ mflag = NONE; /* Default to no match */
+ for (arg = 1; arg < argc; arg++) { /* Read option string */
+ cp = argv[arg];
+ sw: switch (*cp) {
+ case '-':
+ cp++;
+ goto sw;
+ case 'a':
+ all = 1;
+ break;
+ case 'f':
+ filename[arg-1] = ++cp;
+ nfiles++;
+ continue;
+ case 'd':
+ matchstr = ++cp;
+ mflag = DAY;
+ continue;
+ case 'w':
+ matchstr = ++cp;
+ mflag = WEEK;
+ continue;
+ case 'm':
+ matchstr = ++cp;
+ mflag = MONTH;
+ continue;
+ default:
+ fprintf(stderr, "%s: unrecognized option '%c'\n", argv0, *cp);
+ usage();
+ }
+ }
+ if (all)
+ doall();
+ /*
+ * Open files.
+ */
+ if (nfiles) {
+ for (arg = 0; arg < nfiles; arg++ ) {
+ if ((fp[arg] = fopen(filename[arg], "r")) == NULL)
+ fprintf(stderr, "cannot open file %s\n", filename[arg]);
+ else
+ foundfiles++;
+ }
+ if (!foundfiles)
+ err(1, "cannot open any files specified");
+ } else {
+ char *hp;
+
+ nfiles = 1;
+ if ((hp = getenv("HOME")) == NULL)
+ errx(1, "can't find my way back HOME");
+ filename[0] = malloc(strlen(hp) + strlen(CALFILE) + 1);
+ if (filename[0] == NULL)
+ errx(1, "out of memory");
+ strcpy(filename[0], hp);
+ strcat(filename[0], CALFILE);
+ if ((fp[0] = fopen(filename[0], "r")) == NULL)
+ err(1, "cannot open file $HOME/.calendar");
+ }
+ /*
+ * Find match condition from options or current date
+ */
+ switch (mflag) {
+ case NONE:
+ matchdate = current(0);
+ break;
+ case DAY:
+ case WEEK:
+ if (*matchstr == '\0')
+ matchdate = current(0);
+ else {
+ strncpy(CurLine, matchstr, sizeof(CurLine));
+ CurLinep = &CurLine[0];
+ if ((thismonth = findmon()) == -1)
+ errx(1, "invalid month in match date");
+ if ((thisday = findday()) == -1)
+ errx(1, "invalid day in match date");
+ if ((thisyear = findyear()) == -1)
+ thisyear = current(1);
+ matchdate = date(thisday, thismonth, thisyear);
+ }
+ break;
+ case MONTH:
+ if (*matchstr == '\0')
+ matchdate = current(2);
+ else {
+ strncpy(CurLine, matchstr, sizeof(CurLine));
+ CurLinep = &CurLine[0];
+ if ((matchdate = findmon()) == -1)
+ errx(1, "invalid month in match date");
+ }
+ break;
+ }
+ /*
+ * Read the calendar files, print matched lines.
+ */
+ for (arg = 0; arg < nfiles; arg++) {
+ if (fp[arg] == NULL)
+ continue;
+ while ((thisline = fgets(CurLine,sizeof(CurLine),fp[arg]))!=NULL) {
+ CurLinep = &CurLine[0];
+ advance = 0;
+ if ((atsign = strchr(CurLinep, '@')) != NULL)
+ advance = atoi(atsign + 1);
+ if ((thismonth = findmon()) == -1)
+ thismonth = 0;
+ if ((thisday = findday()) == -1)
+ thisday = 0;
+ if ((thisyear = findyear()) == -1)
+ thisyear = current(1);
+ thisdate = date(thisday, thismonth, thisyear);
+ if (thisdate >= matchdate &&
+ thisdate <= matchdate + advance)
+ printf("%s", thisline);
+ else switch (mflag) {
+ case NONE:
+ if (wday == 6)
+ if (thisdate == matchdate ||
+ thisdate == matchdate + 1 ||
+ thisdate == matchdate + 2 ||
+ thisdate == matchdate + 3)
+ printf("%s", thisline);
+ if (wday == 7)
+ if (thisdate == matchdate ||
+ thisdate == matchdate + 1 ||
+ thisdate == matchdate + 2)
+ printf("%s", thisline);
+ if (0 <= wday && wday < 6)
+ if (thisdate == matchdate ||
+ thisdate == matchdate + 1)
+ printf("%s", thisline);
+ break;
+ case DAY:
+ if (thisdate == matchdate)
+ printf("%s", thisline);
+ break;
+ case WEEK:
+ if (matchdate <= thisdate &&
+ thisdate <= matchdate+7)
+ printf("%s", thisline);
+ break;
+ case MONTH:
+ if (thismonth == matchdate)
+ printf("%s", thisline);
+ break;
+ }
+ thisline = NULL;
+ }
+ }
+ return 0;
+}
+
--- /dev/null
+/*
+ * Col(1). Virtual typewriter, performs motions physical typewriters cannot,
+ * like reverse line feeds. Also filters control characters.
+ * Two global variables, LineNo and ColNo, have the current line and column
+ * numbers. Both start at zero.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define Unfetch(c) Unfetched = (c)
+
+#define PAGESIZE 256 /* Default Page size. */
+#define IBUFSIZE 800 /* Number of possible columns. */
+#define EBUFSIZ 4 /* Increment size for buf in EXTRA struct. */
+
+/*
+ * Fetch returns the control characters \n, \b, \t, \r, HVT, VT, HLF, LF.
+ * We choose HVT and HLF to make a switch statement more compact in Main().
+ */
+#define OSTRK 037 /* Flag for overstruck chars. */
+#define ESC 033 /* Escape character. */
+#define SO 017 /* Shift out to alternate char set. */
+#define SI 016 /* Shift in from alternate char set. */
+#define HVT 014 /* Half vertical tab. */
+#define VT 013 /* Vertical tab, or Rev. Line Feed. */
+#define HLF 007 /* Half line feed. */
+#define LF 006 /* Line feed (but not return!). */
+#define EOT 004 /* End of line signal to InsChar. */
+#define BOT 003 /* Start of line signal to InsChar. */
+
+#define not !
+#define or ||
+#define and &&
+#define TRUE (0==0)
+#define FALSE (not TRUE)
+#define NOTREACHED return
+
+typedef uchar bool;
+
+typedef struct extra {
+ struct extra *Next;
+ int Posn;
+ uchar Howmany;
+ char Ebuf[EBUFSIZ];
+} EXTRA;
+
+typedef struct {
+ int Len; /* Number of valid columns in Line. */
+ char *Line;
+ struct extra *Extra;
+} LINE;
+
+
+/*
+ * Error Messages.
+ */
+char Usage[] = "Usage: col [-bdfx] [-pnum]";
+char BackScroll[] = "Scrolling backwards over top of page window.";
+char Confused[] = "Seem to have lost Overstruck characters.";
+
+/*
+ * Flags.
+ */
+bool Bflag = FALSE; /* Command line option 'b'. */
+bool Dflag = FALSE; /* Command line option 'd'. */
+bool Fflag = FALSE; /* Command line option 'f'. */
+bool Pflag = FALSE; /* Command line option 'p'. */
+bool Xflag = FALSE; /* Command line option 'x'. */
+
+/*
+ * External Variables.
+ */
+char Ibuf[BUFSIZ]; /* Buffer for input lines. */
+
+LINE *Page; /* Page window. */
+LINE *CurLine; /* Ptr to *(Page[LineNo % PageSize]). */
+
+int PageSize = PAGESIZE; /* Actual size of Page. */
+int Ibuflen; /* Current length of line in Ibuf. */
+int Unfetched; /* storage for unfetched char */
+int Top; /* Line number of top of Page window. */
+int Bottom; /* Line number of bottom of Page window. */
+int Wmark; /* High-water-mark of Lineno. */
+int LineNo; /* Current line number in Page. */
+int ColNo; /* Current column number. */
+
+void Warning(const char *cp)
+{
+ fputs(cp, stderr);
+ putc('\n', stderr);
+ return;
+}
+
+void Fatal(const char *cp)
+{
+ Warning(cp);
+ exit(1);
+}
+
+void OOM(void)
+{
+ Fatal("Out of memory");
+}
+
+/*
+ * Process command line arguments.
+ */
+
+void Aarghh(int ac, char *av[])
+{
+ register char *cp;
+ register int c;
+
+ while ((cp = *++av) != NULL) {
+ if (*cp++ != '-') {
+ Fatal(Usage);
+ NOTREACHED;
+ }
+ while ((c = *cp++) != '\0')
+ switch (c) {
+ case 'b':
+ Bflag = TRUE;
+ break;
+ case 'd':
+ Dflag = TRUE;
+ break;
+ case 'f':
+ Fflag = TRUE;
+ break;
+ case 'x':
+ Xflag = TRUE;
+ break;
+ case 'p':
+ Pflag = TRUE;
+ if ((PageSize = 2 * atoi(cp)) <= 0)
+ Fatal("Bad page length");
+ break;
+ default:
+ Fatal(Usage);
+ NOTREACHED;
+ }
+ }
+}
+
+void Init(void)
+{
+ register LINE *lp;
+
+ lp = CurLine = Page = (LINE *) malloc(sizeof(LINE) * PageSize);
+ if (lp == NULL)
+ OOM();
+ lp += PageSize - 1;
+ while (lp-- > Page) {
+ lp->Len = 0;
+ lp->Line = NULL;
+ lp->Extra = NULL;
+ }
+ Bottom = PageSize;
+ return;
+}
+
+
+/*
+ * Fetch grabs input characters and returns them after filtering.
+ */
+int Fetch(void)
+{
+ static int acset = 0; /* alternate character set flag */
+ register int c, c1;
+
+ if ((c = Unfetched) != '\0') {
+ Unfetched = '\0';
+ return (c);
+ }
+ for (;;) {
+ if ((c = getchar()) > ' ' && c < 0177)
+ return (acset ? c | 0200 : c);
+ switch (c) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\b':
+ case '\r':
+ case VT:
+ case EOF:
+ return (c);
+ case SO:
+ acset = 1;
+ continue;
+ case SI:
+ acset = 0;
+ continue;
+ case ESC:
+ switch (c1 = getchar()) {
+ case '7':
+ return (VT);
+ case '8':
+ return (HVT);
+ case '9':
+ return (HLF);
+ case 'B':
+ return (LF);
+ }
+ ungetc(c1, stdin);
+ continue;
+ }
+ }
+}
+
+
+
+/*
+ * Handle overstruck characters.
+ */
+void Overstrike(int c)
+{
+ register EXTRA *ep;
+ register EXTRA **epp;
+
+ /*
+ * Find the right EXTRA struct in CurLine.
+ */
+ epp = &CurLine->Extra;
+ for (ep = *epp; ep != NULL; epp = &ep->Next, ep = *epp) {
+ if (ep->Posn != ColNo)
+ continue;
+ /*
+ * We found it, check for overflow and add the char c.
+ */
+ if (ep->Howmany % EBUFSIZ == 0)
+ ep = *epp = (EXTRA *) realloc((char *) ep,
+ sizeof(EXTRA) +
+ ep->Howmany);
+ if (ep == NULL)
+ OOM();
+ ep->Ebuf[ep->Howmany++] = c;
+ return;
+ }
+
+ /*
+ * We didn't find it, so make it, and install the char c.
+ */
+ ep = (EXTRA *) malloc(sizeof(EXTRA));
+ if (ep == NULL)
+ OOM();
+ ep->Howmany = 1;
+ ep->Posn = ColNo;
+ ep->Ebuf[0] = c;
+ ep->Next = CurLine->Extra;
+ CurLine->Extra = ep;
+ return;
+}
+
+/*
+ * Insert the character c into Ibuf at column ColNo.
+ */
+
+void InsChar(int c)
+{
+
+ /*
+ * If (c == BOT) or (c == EOT) we open or close the line in Ibuf.
+ * Otherwise we are really adding a character to Ibuf.
+ */
+ if (c == BOT) {
+ register LINE *lp = CurLine;
+ if ((Ibuflen = lp->Len) != 0) {
+ strncpy(Ibuf, lp->Line, Ibuflen);
+ free(lp->Line);
+ }
+ return;
+ } else if (c == EOT) {
+ register LINE *lp = CurLine;
+ if ((lp->Len = Ibuflen) != 0) {
+ lp->Line = malloc(Ibuflen);
+ if (lp->Line == NULL)
+ OOM();
+ strncpy(lp->Line, Ibuf, Ibuflen);
+ }
+ return;
+ }
+
+ /*
+ * The case of appending a char to the end of Ibuf. Very common.
+ * Note that in this case Ibuf[ColNo] is virgin territory.
+ */
+ if (ColNo == Ibuflen) {
+ Ibuf[Ibuflen++] = c;
+ return;
+ }
+
+ /*
+ * The case of adding a char beyond the end of Ibuf. We must pad the
+ * intervening space with spaces.
+ */
+ if (ColNo > Ibuflen) {
+ register char *ibuf = Ibuf;
+ while (Ibuflen < ColNo)
+ ibuf[Ibuflen++] = ' ';
+ ibuf[Ibuflen++] = c;
+ return;
+ }
+
+
+ /*
+ * The remaining case is adding a char into the interior of Ibuf. If
+ * the present char is a space or if Bflag is set we just insert c,
+ * otherwise we have to overstrike.
+ */
+ {
+ register char *cp = Ibuf + ColNo;
+ register int c1;
+
+ if (Bflag or(c1 = *cp) == ' ') {
+ *cp = c;
+ return;
+ }
+ if (c1 != OSTRK) {
+ Overstrike(c1);
+ *cp = OSTRK;
+ }
+ Overstrike(c);
+ }
+ return;
+}
+
+/*
+ * Ostrikeout puts out all the characters overstruck in position ColNo in the
+ * LINE lp. It pays attention to alternate character sets.
+ */
+bool Ostrikeout(LINE * lp, int col, bool acset)
+{
+ EXTRA *ep;
+
+ /*
+ * Find the EXTRA struct for position 'col' and remove it from
+ * the EXTRA list.
+ */
+ {
+ register EXTRA **epp = &lp->Extra;
+ register EXTRA *e;
+ register int n = col;
+
+ for (e = *epp; e != NULL; epp = &e->Next, e = *epp) {
+ if (e->Posn != n)
+ continue;
+ *epp = e->Next;
+ ep = e;
+ goto FOUNDIT;
+ }
+ /*
+ * Didn't find it in the list.
+ */
+ putchar(' ');
+ Warning(Confused);
+ return (acset);
+ }
+
+ /*
+ * Now that we found it, write out all the characters in ep->Ebuf,
+ * paying attention to alternate char sets, then free ep.
+ */
+ FOUNDIT:
+ {
+ register int c;
+ register bool ac = acset;
+ register int count = ep->Howmany;
+ register char *cp = ep->Ebuf;
+
+ while (count-- > 0) {
+ if ((c = *cp++) & 0200) {
+ if (not ac) {
+ ac = TRUE;
+ putchar(SO);
+ }
+ putchar(c & ~0200);
+ } else {
+ if (ac) {
+ ac = FALSE;
+ putchar(SI);
+ }
+ putchar(c);
+ }
+ if (count > 0)
+ putchar('\b');
+ }
+ free(ep);
+ return (ac);
+ }
+}
+
+/*
+ * PutHalf() outputs the half-line lp with no vertical motion at the end.
+ * Alternate character sets are handled here. Entabbing is handled by Tab.
+ * Overstrikes are handled by Ostrikeout(), which also may have to handle
+ * alternate character sets.
+ */
+void PutHalf(LINE * lp)
+{
+ register int c;
+ register int colno;
+ register bool acset = FALSE;
+
+ /*
+ * Note that since lp->Len is the number of valid columns, the number
+ * of the last valid column is (lp->Len - 1). That's why the test is
+ * "<" instead of "<=".
+ */
+ for (colno = 0; colno < lp->Len; ++colno)
+ switch (c = lp->Line[colno]) {
+ case OSTRK:
+ acset = Ostrikeout(lp, colno, acset);
+ break;
+ case ' ':
+ if (Xflag) {
+ putchar(' ');
+ break;
+ }
+ /* Entab white space. */
+ c = colno;
+ while (lp->Line[c] == ' ')
+ if (++c % 8 == 0) {
+ putchar('\t');
+ colno = c;
+ }
+ while (colno < c) {
+ putchar(' ');
+ ++colno;
+ }
+ --colno;
+ break;
+ default:
+ if (c & 0200) {
+ if (not acset) {
+ acset = TRUE;
+ putchar(SO);
+ }
+ putchar(c & ~0200);
+ } else {
+ if (acset) {
+ acset = FALSE;
+ putchar(SI);
+ }
+ putchar(c);
+ }
+ break;
+ }
+ if (acset)
+ putchar(SI);
+ free(lp->Line);
+ lp->Len = 0;
+ lp->Line = NULL;
+ lp->Extra = NULL;
+ return;
+}
+
+/*
+ * Output one full line, which is possibly two half lines. All control for
+ * Dflag and Fflag takes place here.
+ */
+void PutLine(int n)
+{
+ static char HCR[] = { '\r', ESC, '9', '\0' }; /* Half Crg. Return */
+ register LINE *lp;
+
+ lp = Page + n % PageSize;
+ if (lp->Len != 0)
+ PutHalf(lp);
+ ++lp;
+
+ if (lp->Len == 0) {
+ if (Dflag and not Fflag) {
+ putchar('\n');
+ putchar('\n');
+ } else
+ putchar('\n');
+ } else {
+ if (Fflag) {
+ fputs(HCR, stdout);
+ PutHalf(lp);
+ fputs(HCR, stdout);
+ } else {
+ putchar('\n');
+ PutHalf(lp);
+ putchar('\n');
+ }
+ }
+ return;
+}
+
+void VertMove(int c)
+{
+ static bool warnflag = FALSE; /* Warn of move over top of page. */
+
+ Unfetch(c);
+ for (;;) {
+ switch (c = Fetch()) {
+ case VT:
+ LineNo -= 2;
+ break;
+ case LF:
+ LineNo += 2;
+ break;
+ case HVT:
+ LineNo -= 1;
+ break;
+ case HLF:
+ LineNo += 1;
+ break;
+ case '\n':
+ ColNo = 0;
+ LineNo += 2;
+ break;
+ default:
+ Unfetch(c);
+ CurLine = Page + LineNo % PageSize;
+ return;
+ }
+
+ /*
+ * Have we scrolled over the top of the Page window?
+ */
+ if (LineNo < Top)
+ if (not warnflag) {
+ Warning(BackScroll);
+ warnflag = TRUE;
+ }
+
+ /*
+ * Update the water mark if needed.
+ */
+ if (LineNo > Wmark)
+ Wmark = LineNo;
+
+
+ /*
+ * If we've scrolled past the bottom of the window then we
+ * put out the Top line and move the window down.
+ */
+ if (Bottom <= LineNo) {
+ PutLine(Top);
+ Top += 2;
+ Bottom += 2;
+ }
+ }
+}
+
+void Flush(void)
+{
+ register int i;
+
+ for (i = Top; i <= Wmark; i += 2)
+ PutLine(i);
+ return;
+}
+
+
+
+int main(int ac, char *av[])
+{
+ register int c;
+
+ Aarghh(ac, av);
+ Init();
+ InsChar(BOT);
+ while ((c = Fetch()) != EOF)
+ switch (c) {
+ case '\b':
+ if (ColNo > 0)
+ --ColNo;
+ break;
+ case '\r':
+ ColNo = 0;
+ break;
+ case '\t':
+ ColNo = (ColNo & ~07) + 8;
+ break;
+ case '\n':
+ ColNo = 0;
+ case VT:
+ case LF:
+ case HVT:
+ case HLF:
+ InsChar(EOT); /* Close current line. */
+ VertMove(c);
+ InsChar(BOT); /* Open current line. */
+ break;
+ default:
+ if (c != ' ')
+ InsChar(c);
+ ++ColNo;
+ break;
+ }
+ InsChar(EOT); /* Close current line. */
+ Flush();
+ return (0);
+}
--- /dev/null
+/*
+ * Strip nroff/troff control lines and eqn
+ * and tbl sequences from input.
+ * Also, optionally, produce the
+ * output as a set of words.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#define NLINE 500 /* Input line length */
+#define NFNEST 15 /* Depth of .so file nesting */
+
+FILE *ofiles[NFNEST];
+FILE **ofpp = &ofiles[0];
+char **flist; /* list of files to open */
+
+typedef struct FNAME {
+ struct FNAME *fn_next;
+ char fn_name[0];
+} FNAME;
+
+FNAME *fnames;
+
+char line[NLINE];
+
+int delim1 = EOF; /* Start embedded eqn */
+int delim2 = EOF; /* End embedd eqn */
+int skipcnt; /* Number of lines to skip */
+int skiptitle; /* Skip title text (until next nroff command) */
+int sflag; /* Divide into sentences */
+int wflag; /* Divide output into words */
+int xflag; /* Extra knowledge of macros (style/diction) */
+
+int ineqn; /* Inside embedded eqn escape */
+
+
+/*
+ * Open input files (for .so, .nx, and from
+ * command line). Do not open any files twice.
+ */
+FILE *dopen(char *fname)
+{
+ register FNAME *fnp;
+ register FILE *fp;
+
+ for (fnp = fnames; fnp != NULL; fnp = fnp->fn_next)
+ if (strcmp(fnp->fn_name, fname) == 0)
+ return (NULL);
+ if ((fp = fopen(fname, "r")) == NULL)
+ fprintf(stderr, "deroff: cannot open `%s'\n", fname);
+ else if ((fnp =
+ (FNAME *) malloc(sizeof(FNAME) + strlen(fname) + 2)) !=
+ NULL) {
+ fnp->fn_next = fnames;
+ fnames = fnp;
+ strcpy(fnp->fn_name, fname);
+ }
+ return (fp);
+}
+
+
+/*
+ * Get a character from the next file stream.
+ */
+int dgetc(void)
+{
+ register int c;
+
+ again:
+ if (*ofpp == NULL || (c = getc(*ofpp)) == EOF) {
+ if (*ofpp != stdin && *ofpp != NULL)
+ fclose(*ofpp);
+ if (ofpp > ofiles) {
+ ofpp--;
+ goto again;
+ }
+ while (*flist != NULL)
+ if ((*ofpp = dopen(*flist++)) != NULL)
+ goto again;
+ return (EOF);
+ }
+ return (c);
+}
+
+/*
+ * Like fgets, only always reads using `dgetc'
+ * into a buffer of `NLINE' characters.
+ */
+char *dgets(char *as)
+{
+ register unsigned n = NLINE;
+ register char *s;
+ register int c;
+
+ s = as;
+ while (--n > 0 && (c = dgetc()) != EOF)
+ if ((*s++ = c) == '\n')
+ break;
+ *s = '\0';
+ return (c == EOF && s == as ? NULL : as);
+}
+
+/*
+ * O\15utput for that line which isn't an nroff
+ * control line. This has to look for embedded
+ * eqn stuff and back-slash troff/nroff escapes.
+ * The embedded escapes are all handled to some degree
+ * but such things as nested quotes (e.g. \w or \h)
+ * do not quite work. However, these occur almost never
+ * in text so it should be sufficient.
+ */
+void output(char *l)
+{
+ register int c;
+ register int inword = 0;
+ register int hyphen = 0;
+
+ if (skipcnt) {
+ skipcnt--;
+ return;
+ }
+ if (skiptitle)
+ return;
+ while ((c = *l++) != '\0') {
+ if (ineqn) {
+ if (c == delim2)
+ ineqn = 0;
+ continue;
+ }
+ if (c == delim1) {
+ ineqn = 1;
+ continue;
+ }
+ if (c == '\\') {
+ if ((c = *l++) == '\0')
+ break;
+ switch (c) {
+ case '0': /* digit width space */
+ case '|': /* Narrow space */
+ case '&': /* Non-printing, 0-width char */
+ case '!': /* Transparent line indicator */
+ case '%': /* Optional hyphenation char */
+ case 't': /* Non-interpreted tab */
+ case 'u': /* Up 1/2 */
+ case 'd': /* Down 1/2 */
+ case 'a': /* Non-interpeted leader */
+ case 'c': /* Interrupt text processing */
+ case 'p': /* Break and spread */
+ case 'r': /* Rerverse vertical motion */
+ case '{': /* Begin conditional */
+ case '}': /* End conditional */
+ c = ' ';
+ break;
+
+ case 'e': /* Current escape */
+ c = '\\';
+ break;
+ case '$': /* argument */
+ if (*l != '\0')
+ l++;
+ case '^': /* Half narrow space */
+ continue;
+
+ case '(': /* Char named `xx' */
+ if (*l != '\0')
+ l++;
+ if (*l != '\0')
+ l++;
+ c = ' ';
+ break;
+
+ case 'z': /* Zero-width character */
+ if (*l != '\0')
+ c = *l++;
+ break;
+
+ case 'k': /* Mark input place in `x' */
+ case 'n': /* Expand reggister x */
+ case '*': /* Interpolate string */
+ case 'f': /* Change font */
+ if (*l != '\0')
+ if ((c = *l++) == '(') {
+ if (*l != '\0')
+ l++;
+ if (*l != '\0')
+ l++;
+ }
+ continue;
+
+ case 's': /* Change point size */
+ if (*l == '\0')
+ continue;
+ if ((c = *l++) == '-' || c == '+') {
+ if (*l == '\0')
+ continue;
+ c = *l++;
+ }
+ while (*l != '\0' && isdigit(c))
+ c = *l++;
+ break;
+
+ case 'x': /* Extra line space */
+ case 'w': /* Width function */
+ case 'v': /* Local vertical motion */
+ case 'o': /* Overstrike function */
+ case 'L': /* Vertical line */
+ case 'l': /* Horizontal line */
+ case 'h': /* Local horizontal motion */
+ case 'b': /* Bracket-builder */
+ if ((c = *l) != '\0')
+ while (*l != '\0' && *l != c)
+ l++;
+ continue;
+
+ case '"': /* Beginning of comment */
+ while (*l != '\0')
+ l++;
+ continue;
+ }
+ }
+ if (wflag) {
+ if (c == '\n')
+ continue;
+ if (!inword)
+ if (isalpha(c))
+ inword = 1;
+ else
+ continue;
+ if (c == '-' && !hyphen) {
+ hyphen = 1;
+ continue;
+ }
+ hyphen = 0;
+ if (c == '\'')
+ continue;
+ if (!isalpha(c) && !isdigit(c)) {
+ inword = 0;
+ putchar('\n');
+ continue;
+ }
+ }
+ putchar(c);
+ }
+ if (wflag && inword && !hyphen)
+ putchar('\n');
+}
+
+
+/*
+ * Skip centred lines, in extended mode,
+ * by setting a skip counter on text.
+ */
+void centre(char *l)
+{
+ if ((skipcnt = atoi(l)) == 0)
+ skipcnt = 1;
+}
+
+/*
+ * Skip displays in extended mode.
+ */
+void display(void)
+{
+ if (!xflag)
+ return;
+ while (dgets(line) != NULL) {
+ if (strncmp(line, ".KE", 3) == 0)
+ break;
+ if (strncmp(line, ".DE", 3) == 0)
+ break;
+ }
+}
+
+
+/*
+ * Process eqn directives.
+ * Currently, this simply looks for
+ * .EN lines as the terminator
+ * and delim lines to set the eqn delimiters.
+ */
+void eqn(void)
+{
+ register char *cp;
+
+ while (dgets(line) != NULL) {
+ if (strncmp(line, ".EN", 3) == 0)
+ break;
+ if (strncmp(line, "delim", 5) == 0) {
+ for (cp = line + 5; *cp == ' ' || *cp == '\t';
+ cp++);
+ if (*cp == '\n' || *cp == '\0')
+ continue;
+ if (strncmp(cp, "off", 3) == 0) {
+ delim1 = EOF;
+ delim2 = EOF;
+ continue;
+ }
+ delim1 = *cp++;
+ delim2 = *cp;
+ }
+ }
+}
+
+/*
+ * In extended knowledge mode (-ms macros),
+ * remove footnotes. This mode is for
+ * style and diction.
+ */
+void footnote(void)
+{
+ if (!xflag)
+ return;
+ while (dgets(line) != NULL)
+ if (strncmp(line, ".FE", 3) == 0)
+ break;
+}
+
+/*
+ * Include a file as per the `.so' request
+ * line.
+ */
+void dotso(char *fname)
+{
+ if (++ofpp > &ofiles[NFNEST - 1]) {
+ fprintf(stderr, "deroff: .so nested too deep--%s\n",
+ fname);
+ ofpp--;
+ return;
+ }
+ if ((*ofpp = dopen(fname)) == NULL)
+ ofpp--;
+}
+
+/*
+ * Process included files.
+ * The first argument is the pointer to where
+ * the filename is (it may have junk before and after it)
+ * The second is 's' for .so and 'n' for .nx.
+ */
+void include(char *fn, char type)
+{
+ register int c;
+ register char *ep;
+
+ while (*fn == ' ' || *fn == '\t')
+ fn++;
+ for (ep = fn; (c = *ep) != '\0'; ep++)
+ if (c == '\n' || c == ' ' || c == '\t' || c == '\\')
+ break;
+ *ep = '\0';
+ if (type == 's')
+ dotso(fn);
+ else
+ *ofpp = dopen(fn);
+}
+
+/*
+ * Remove a macro defintion.
+ */
+void macdef(void)
+{
+ while (dgets(line) != NULL)
+ if (strcmp(line, "..\n") == 0)
+ break;
+}
+
+/*
+ * Throw away nofilled text as with footnotes above.
+ */
+void nofill(void)
+{
+ if (!xflag)
+ return;
+ while (dgets(line) != NULL)
+ if (strncmp(line, ".fi", 3) == 0)
+ break;
+}
+
+
+
+/*
+ * Process tbl directives. At this time,
+ * all this does is look for the terminating
+ * .TE to end tables.
+ */
+void tbl(void)
+{
+ while (dgets(line) != NULL)
+ if (strncmp(line, ".TE", 3) == 0)
+ break;
+}
+
+/*
+ * If in extended mode, skip titles and author's
+ * names. Set a flag to skip until next nroff command.
+ */
+void titles(void)
+{
+ if (xflag)
+ skiptitle = 1;
+}
+
+/*
+ * Process nroff control lines.
+ * Remove EQN, TBL, macro defintions.
+ * Process .so and .nx here.
+ * Other lines have the rest of the line used.
+ */
+void nroff(char *l)
+{
+ skiptitle = 0;
+ if (l[1] == 'E' && l[2] == 'Q')
+ eqn();
+ else if (l[1] == 'T' && l[2] == 'S')
+ tbl();
+ else if (l[1] == 'F' && l[2] == 'S')
+ footnote();
+ else if (l[1] == 'c' && l[2] == 'e')
+ centre(l);
+ else if (l[1] == 'n' && l[2] == 'f')
+ nofill();
+ else if (l[1] == 'D' && l[2] == 'S')
+ display();
+ else if (l[1] == 'K' && (l[2] == 'F' || l[2] == 'S'))
+ display();
+ else if (l[1] == 'T' && l[2] == 'L')
+ titles();
+ else if (l[1] == 'A' && (l[2] == 'I' || l[2] == 'U'))
+ titles();
+ else if (l[2] == 'H' && (l[1] == 'S' || l[1] == 'N'))
+ titles();
+ else if (l[1] == 'n' && l[2] == 'x')
+ include(line + 3, 'n');
+ else if (l[1] == 's' && l[2] == 'o')
+ include(line + 3, 's');
+ else if (l[1] == 'd' && l[2] == 'e')
+ macdef();
+ else if (l[1] == 'd' && l[2] == 's')
+ return;
+ else {
+ while (*l != ' ' && *l != '\t' && *l != '\0')
+ l++;
+ while (*l == ' ' || *l == '\t')
+ l++;
+ if (*l != '\0')
+ output(l);
+ }
+}
+
+/*
+ * Read until end-of-file
+ * and process the special nroff/troff/eqn/tbl
+ * lines in the file.
+ */
+void deroff(void)
+{
+
+ while (dgets(line) != NULL) {
+ if (!ineqn && line[0] == '.') {
+ nroff(line);
+ continue;
+ }
+ output(line);
+ }
+}
+
+void usage(void)
+{
+ fprintf(stderr, "Usage: deroff [ -w ] [ -x ] [file ...]\n");
+ exit(1);
+}
+
+int main(int argc, char *argv[])
+{
+ register char *ap;
+
+ while (argc > 1 && *argv[1] == '-') {
+ for (ap = &argv[1][1]; *ap != '\0'; ap++)
+ switch (*ap) {
+ case 's':
+ sflag = 1;
+ break;
+
+ case 'w':
+ wflag = 1;
+ break;
+
+ case 'x':
+ xflag = 1;
+ break;
+
+ default:
+ usage();
+ }
+ argv++;
+ argc--;
+ }
+ if (argc < 2)
+ ofiles[0] = stdin;
+ flist = &argv[1];
+ deroff();
+ exit(0);
+}
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#define TMPFILE "/tmp/m4%dxxxxxx"
+#define TMPMIN 6
+
+#define isletter(c) (isalpha(c)||c=='_')
+#define BQUOTE '`'
+#define EQUOTE '\''
+#define HASHSZ 199
+#define STRBLK 16 /* block size for dynamic string storage */
+#if STRBLK < 12
+need room for decimal rep of long int
+#endif
+ enum { FUNC, MACD, DSKF, MSTR };
+enum { EX, MY, DV, MD, AD, SB, EQ, NE, LE, GE, LT, GT, AN, OR, RP, NL };
+
+typedef struct { /* for storing variable-length strings */
+ int s_refc; /* references--when zero space is freed */
+ int s_hash; /* raw hash value (sum of chars in body) */
+ char *s_body; /* start of contents */
+ char *s_next; /* end of contents */
+ char *s_last; /* end of storage */
+} STRING;
+
+typedef union ifr { /* input-source stack frame */
+ struct {
+ union ifr *i_back;
+ char i_cbuf; /* unget buffer for lookahead */
+ int i_type; /* if type is MSTRing */
+ STRING *i_pstr;
+ char *i_pchr; /* next char */
+ } ifb;
+ struct {
+ union ifr *i_back;
+ char i_cbuf;
+ int i_type; /* if type is DSKFile */
+ FILE *i_fp;
+ } ifdk;
+} IFRAME;
+
+typedef struct fstk { /* file information stack frame */
+ struct fstk *f_back;
+ STRING *f_name; /* pointer to name (NULL if stdin) */
+ int f_flag; /* flag = 0 until EOF is reached */
+ int f_line; /* line counter */
+} FFRAME;
+
+typedef struct ofr { /* argument collection stack frame */
+ struct ofr *o_back;
+ STRING *o_pstr;
+} OFRAME;
+
+typedef struct ent { /* symbol table entry */
+ struct ent *e_next;
+ int e_type; /* FUNC or MACD */
+ union {
+ void (*e_pfun) (STRING ** s);
+ STRING *e_pstr;
+ } e_at;
+ STRING *e_name;
+} ENTRY;
+
+/*
+ * output file info
+ */
+struct {
+ char *name;
+ FILE *fp;
+} outfile[10] = { {
+NULL, stdout}};
+
+/*
+ * op table for eval
+ */
+struct opdata {
+ int optype;
+ char keychar;
+ char secchar;
+ int prec;
+} optable[] = {
+#define SI 8 /* precedence of unary + and - */
+ {
+ EX, '^', '\0', 7}, {
+ EX, '*', '*', 7}, {
+ MY, '*', '\0', 6}, {
+ DV, '/', '\0', 6}, {
+ MD, '%', '\0', 6}, {
+ AD, '+', '\0', 5}, {
+ SB, '-', '\0', 5}, {
+ EQ, '=', '=', 4}, {
+ GE, '>', '=', 4}, {
+ GT, '>', '\0', 4}, {
+ NE, '!', '=', 4}, {
+ LE, '<', '=', 4}, {
+ LT, '<', '\0', 4},
+#define NT 3 /* precedence of unary ! */
+ {
+ AN, '&', '&', 2}, {
+ AN, '&', '\0', 2}, {
+ OR, '|', '|', 1}, {
+ OR, '|', '\0', 1}, {
+ RP, ')', '\0', 10}, {
+ NL, '\0', '\0', 0},
+#define LP 0 /* precedence of open paren */
+};
+
+ENTRY *e_root[HASHSZ]; /* pointers to symbol table hash buckets */
+OFRAME *ostkptr; /* output stack pointer */
+IFRAME *istkptr; /* input stack pointer */
+FFRAME *fstkptr; /* file info stack pointer */
+FILE *offp = stdout; /* current output file pointer */
+int ofnum; /* current diversion number */
+int lstdchr = '\n'; /* last char from stdin */
+int single; /* single argument flag */
+int dnlflag; /* delete to newline flag */
+
+char bqt = BQUOTE;
+char eqt = EQUOTE;
+
+/* VARARGS */
+void errorp(int f, ...)
+{
+ fprintf(stderr, "m4: ");
+#if 0
+ FIXME if (fstkptr != NULL) {
+ if (fstkptr->f_name != NULL)
+ fprintf(stderr, "%s: ", fstkptr->f_name->s_body);
+ fprintf(stderr, "%d: ", fstkptr->f_line);
+ }
+ fprintf(stderr, "%r", &x);
+ putc('\n', stderr);
+ if (f) {
+ while (lstdchr != EOF && lstdchr != '\n')
+ lstdchr = getchar();
+ exit(1);
+ }
+#endif
+}
+
+char *alloc(int n)
+{
+ char *x;
+
+ if ((x = malloc(n)) != NULL)
+ return (x);
+ errorp(1, "out of space");
+}
+
+STRING *makestr(void)
+{
+ register STRING *a;
+
+ a = (STRING *) alloc(sizeof(STRING));
+ a->s_body = a->s_next = alloc(STRBLK);
+ a->s_last = a->s_body + STRBLK - 1;
+ a->s_refc = 1;
+ a->s_hash = 0;
+ *a->s_next = '\0';
+ return (a);
+}
+
+void decstr(STRING * a)
+{
+ if (a == NULL)
+ return;
+ if (--a->s_refc)
+ return;
+ free(a->s_body);
+ free(a);
+}
+
+int cmpstr(STRING * a, STRING * b)
+{
+ if (a == NULL && b == NULL)
+ return (1);
+ if ((a == NULL && b != NULL) || (a != NULL && b == NULL))
+ return (0);
+ return (!strcmp(a->s_body, b->s_body));
+}
+
+void appendstr(STRING * a, char c)
+{
+ register char *r, *s, *t;
+ int size;
+
+ if (a->s_next < a->s_last) {
+ *a->s_next++ = c;
+ *a->s_next = '\0';
+ } else {
+ r = s = alloc(size =
+ a->s_last - (t = a->s_body) + 1 + STRBLK);
+ while ((*s++ = *t++) != 0);
+ *(a->s_next = s) = '\0';
+ *--s = c;
+ free(a->s_body);
+ a->s_last = (a->s_body = r) + size - 1;
+ }
+ a->s_hash += c;
+}
+
+void pushinp(char t)
+{
+ IFRAME *itemp;
+
+ itemp = istkptr;
+ istkptr = (IFRAME *) alloc(sizeof(IFRAME));
+ istkptr->ifb.i_back = itemp;
+ istkptr->ifb.i_type = t;
+ istkptr->ifb.i_cbuf = '\0';
+}
+
+int pushfile(char *s)
+{
+ FILE *fp;
+ register STRING *a = NULL;
+ register FFRAME *ftemp = fstkptr;
+ char c;
+
+ if (ftemp != NULL && ftemp->f_flag) {
+ decstr(ftemp->f_name);
+ fstkptr = ftemp->f_back;
+ free(ftemp);
+ }
+ if (strcmp(s, "-") == 0)
+ fp = stdin;
+ else if ((fp = fopen(s, "r")) == NULL)
+ return (0);
+ pushinp(DSKF);
+ istkptr->ifdk.i_fp = fp;
+ ftemp = (FFRAME *) alloc(sizeof(FFRAME));
+ ftemp->f_back = fstkptr;
+ ftemp->f_line = 1;
+ ftemp->f_flag = 0;
+ if (fp != stdin && !(fstkptr == NULL && single)) {
+ a = makestr();
+ while ((c = *s++) != 0)
+ appendstr(a, c);
+ }
+ ftemp->f_name = a;
+ fstkptr = ftemp;
+ return (1);
+}
+
+void pushstr(STRING * a)
+{
+ if (a == NULL || *a->s_body == '\0')
+ return;
+ pushinp(MSTR);
+ istkptr->ifb.i_pstr = a;
+ istkptr->ifb.i_pchr = a->s_body;
+ ++a->s_refc;
+}
+
+void pushnum(long x)
+{
+ register STRING *a;
+
+ a = makestr();
+ sprintf(a->s_body, "%ld", x);
+ pushstr(a);
+ decstr(a);
+}
+
+int popinp(void)
+{
+ IFRAME *itemp;
+
+ itemp = istkptr;
+ switch (istkptr->ifb.i_type) {
+ case MSTR:
+ decstr(istkptr->ifb.i_pstr);
+ break;
+ case DSKF:
+ if (istkptr->ifdk.i_fp != stdin)
+ fclose(istkptr->ifdk.i_fp);
+ fstkptr->f_flag = 1;
+ break;
+ }
+ istkptr = istkptr->ifb.i_back;
+ free(itemp);
+ return (istkptr != NULL);
+}
+
+void pushout(STRING * b)
+{
+ OFRAME *otemp;
+
+ otemp = (OFRAME *) alloc(sizeof(OFRAME));
+ otemp->o_back = ostkptr;
+ otemp->o_pstr = b;
+ ostkptr = otemp;
+ ++b->s_refc;
+}
+
+void popout(void)
+{
+ OFRAME *otemp;
+
+ otemp = ostkptr;
+ ostkptr = ostkptr->o_back;
+ decstr(otemp->o_pstr);
+ free(otemp);
+}
+
+void macro(STRING * ps, STRING ** pps)
+{
+ STRING *x;
+ register char *b, *v;
+
+ x = makestr();
+ b = ps->s_body;
+ while (*b) {
+ if (*b != '$')
+ appendstr(x, *b++);
+ else if (!isdigit(*++b))
+ appendstr(x, '$');
+ else if ((ps = pps[*b++ - '0']) != 0)
+ for (v = ps->s_body; *v; v++)
+ appendstr(x, *v);
+ }
+ pushstr(x);
+ decstr(x);
+}
+
+
+void outc(char c)
+{
+ if (offp != NULL)
+ putc(c, offp);
+}
+
+void outputc(char c)
+{
+ if (ostkptr != NULL)
+ appendstr(ostkptr->o_pstr, c);
+ else
+ outc(c);
+}
+
+void outputs(char *s)
+{
+ register char *t, c;
+
+ for (t = s; (c = *t++) != 0; outputc(c));
+}
+
+int nxch(void)
+{
+ register int c = 0;
+ FFRAME *ftemp;
+
+ if (istkptr == NULL)
+ return ('\0');
+ if ((ftemp = fstkptr)->f_flag) {
+ decstr(ftemp->f_name);
+ fstkptr = ftemp->f_back;
+ free(ftemp);
+ }
+ switch (istkptr->ifb.i_type) {
+ case DSKF:
+ if ((c = istkptr->ifdk.i_cbuf) != 0)
+ istkptr->ifdk.i_cbuf = '\0';
+ else
+ c = getc(istkptr->ifdk.i_fp);
+ if (istkptr->ifdk.i_fp == stdin)
+ lstdchr = c;
+ if (c == EOF)
+ c = '\0';
+ else if (c == '\n') {
+ if (fstkptr->f_flag)
+ ++fstkptr->f_back->f_line;
+ else
+ ++fstkptr->f_line;
+ }
+ break;
+ case MSTR:
+ if ((c = istkptr->ifb.i_cbuf) != 0)
+ istkptr->ifb.i_cbuf = '\0';
+ else
+ c = *istkptr->ifb.i_pchr++;
+ break;
+ }
+ if (c == '\0')
+ return (popinp()? nxch() : c);
+ else if (!dnlflag)
+ return (c);
+ if (c == '\n')
+ dnlflag = 0;
+ return (nxch());
+}
+
+ENTRY *find(STRING * a)
+{
+ register ENTRY *e;
+ register int hash;
+
+ hash = a->s_hash;
+ for (e = e_root[hash % HASHSZ]; e != NULL; e = e->e_next)
+ if (e->e_name->s_hash == hash
+ && strcmp(e->e_name->s_body, a->s_body) == 0)
+ return (e);
+ return (NULL);
+}
+
+int process(int pct)
+{
+ char lc = '\0';
+ register int c, qct = 0;
+ int i;
+ STRING *a[10];
+ register STRING *b;
+ ENTRY *e;
+
+ for (c = nxch(); pct && isspace(c); c = nxch());
+ while (c != '\0') {
+ if (qct)
+ if (c == eqt) {
+ if (--qct)
+ outputc(c);
+ } else {
+ if (c == bqt)
+ ++qct;
+ outputc(c);
+ } else if (c == bqt)
+ ++qct;
+ else if ((c == ')' && pct && !--pct)
+ || (c == ',' && pct == 1))
+ return (c);
+ else if (isletter(c) && !isletter(lc) && !isdigit(lc)) {
+ b = makestr();
+ do {
+ appendstr(b, c);
+ lc = c;
+ c = nxch();
+ } while (isletter(c) || isdigit(c));
+ if ((e = find(b)) == NULL) {
+ outputs(b->s_body);
+ decstr(b);
+ continue;
+ }
+ for (i = 9; i; a[i--] = NULL);
+ a[0] = b;
+ if (c != '(') {
+ istkptr->ifb.i_cbuf = c;
+ if (c == '\n'
+ && istkptr->ifb.i_type == DSKF) {
+ if (fstkptr->f_flag)
+ --fstkptr->f_back->f_line;
+ else
+ --fstkptr->f_line;
+ }
+ } else
+ do {
+ pushout(b = makestr());
+ c = process(1);
+ if (i++ < 9 && strlen(b->s_body))
+ a[i] = b;
+ else
+ decstr(b);
+ popout();
+ } while (c != ')');
+ if (e->e_type == MACD)
+ macro(e->e_at.e_pstr, a);
+ else
+ (*e->e_at.e_pfun) (a);
+ for (i = 0; i < 10; i++)
+ decstr(a[i]);
+ c = '\0';
+ } else {
+ if (c == '(' && pct)
+ ++pct;
+ outputc(c);
+ }
+ lc = c;
+ c = nxch();
+ }
+ if (pct)
+ errorp(1, "unexpected EOF");
+ return (c);
+}
+
+void buildin(char *s, void (*f) (STRING ** p))
+{
+ STRING *a;
+ register int hash;
+ register ENTRY *e;
+
+ a = makestr();
+ while (*s)
+ appendstr(a, *s++);
+ hash = a->s_hash % HASHSZ;
+ e = (ENTRY *) alloc(sizeof(ENTRY));
+ e->e_next = e_root[hash];
+ e->e_type = FUNC;
+ e->e_at.e_pfun = f;
+ e->e_name = a;
+ e_root[hash] = e;
+}
+
+void mchangequote(STRING ** pps)
+{
+ bqt = pps[1] ? *pps[1]->s_body : BQUOTE;
+ eqt = pps[2] ? *pps[2]->s_body : EQUOTE;
+}
+
+void mdefine(STRING ** pps)
+{
+ register ENTRY *e;
+ register char *s;
+ register int hash;
+ int c;
+ static char illmac[] = "illegal macro name: %s";
+
+ if (pps[1] == NULL || !isletter(*pps[1]->s_body)) {
+ errorp(0, illmac, pps[1] != NULL ? pps[1]->s_body : NULL);
+ return;
+ }
+ s = pps[1]->s_body + 1;
+ while ((c = *s++) != 0)
+ if (!(isletter(c) || isdigit(c))) {
+ errorp(0, illmac, pps[1]->s_body);
+ return;
+ }
+ if ((e = find(pps[1])) != NULL) {
+ if (e->e_type == MACD)
+ decstr(e->e_at.e_pstr);
+ } else {
+ e = (ENTRY *) alloc(sizeof(ENTRY));
+ e->e_next = e_root[hash = pps[1]->s_hash % HASHSZ];
+ e->e_name = pps[1];
+ ++pps[1]->s_refc;
+ e_root[hash] = e;
+ }
+ e->e_type = MACD;
+ if (pps[2]) {
+ e->e_at.e_pstr = pps[2];
+ ++pps[2]->s_refc;
+ } else
+ e->e_at.e_pstr = makestr();
+}
+
+void mdivert(STRING ** pps)
+{
+ char *fn;
+ int fd;
+
+ if (pps == NULL)
+ ofnum = 0;
+ else
+ ofnum = (pps[1] != NULL) ? atoi(pps[1]->s_body) : 0;
+ if (ofnum > 0 && ofnum <= 9) {
+ if (outfile[ofnum].fp == NULL) {
+ outfile[ofnum].name = fn = alloc(15);
+ sprintf(fn, TMPFILE, ofnum);
+ fd = mkstemp(fn);
+ if (fd == -1
+ || (outfile[ofnum].fp =
+ fdopen(fd, "w")) == NULL)
+ errorp(1, "m4: /tmp open error\n");
+ }
+ }
+ if (ofnum >= 0 && ofnum <= 9)
+ offp = outfile[ofnum].fp;
+ else
+ offp = NULL;
+}
+
+void mdivnum(STRING ** unused)
+{
+ pushnum((long) ofnum);
+}
+
+void mdnl(STRING ** unused)
+{
+ dnlflag = 1;
+}
+
+void outdef(ENTRY * e)
+{
+ if (e->e_type == MACD) {
+ outputc(bqt);
+ outputs(e->e_at.e_pstr->s_body);
+ outputc(eqt);
+ }
+}
+
+void mdumpdef(STRING ** pps)
+{
+ register ENTRY *e;
+ register int i, f = 0;
+ int hash;
+
+ for (i = 1; i <= 9; ++i)
+ if (pps[i] != NULL) {
+ if ((e = find(pps[i])) != NULL)
+ outdef(e);
+ f = 1;
+ }
+ if (!f)
+ for (hash = 0; hash < HASHSZ; ++hash)
+ for (e = e_root[hash]; e; e = e->e_next) {
+ outputc(bqt);
+ outputs(e->e_name->s_body);
+ outputc(eqt);
+ outputc('\t');
+ outdef(e);
+ outputc('\n');
+ }
+}
+
+void merrprint(STRING ** pps)
+{
+ register int i;
+
+ for (i = 1; i <= 9; i++)
+ if (pps[i])
+ fprintf(stderr, pps[i]->s_body);
+}
+
+long calc(int pr, char **ps)
+{
+ register struct opdata *opptr;
+ register char c;
+ char *s;
+ long val1, val2, l;
+ int oplength;
+
+ for (s = *ps; isspace(*s); ++s);
+ if (isdigit(c = *s++))
+ for (val1 = c - '0'; isdigit(c = *s); ++s)
+ val1 = 10 * val1 + c - '0';
+ else
+ switch (c) {
+ case '+':
+ val1 = calc(SI, &s);
+ break;
+ case '-':
+ val1 = -calc(SI, &s);
+ break;
+ case '!':
+ val1 = !calc(NT, &s);
+ break;
+ case '(':
+ val1 = calc(LP, &s);
+ break;
+ default:
+ if (c == '\0')
+ errorp(0, "eval: missing value");
+ else
+ errorp(0, "eval: invalid expression");
+ s = NULL;
+ }
+ if ((*ps = s) == NULL)
+ return (0);
+ for (;;) {
+ while (s != NULL && isspace(c = *s))
+ ++s;
+ oplength = 1;
+ for (opptr = optable; s != NULL; ++opptr)
+ if (c != opptr->keychar) {
+ if (opptr->keychar == '\0')
+ s = NULL;
+ } else if (opptr->secchar == '\0') {
+ break;
+ } else if (opptr->secchar == *(s + 1)) {
+ ++oplength;
+ break;
+ }
+ if ((*ps = s) == NULL) {
+ errorp(0, "eval: missing or unknown operator");
+ return (0);
+ }
+ if (opptr->prec <= pr)
+ return (val1);
+ *ps = s += oplength;
+ if (c == ')')
+ return (val1);
+ val2 = calc(opptr->prec, &s);
+ if ((*ps = s) == NULL)
+ return (0);
+ switch (opptr->optype) {
+ case EX:
+ if (val2 < 0)
+ val1 = 0;
+ else {
+ for (l = 1; val2; --val2)
+ l *= val1;
+ val1 = l;
+ }
+ break;
+ case MY:
+ val1 *= val2;
+ break;
+ case DV:
+ val1 /= val2;
+ break;
+ case MD:
+ val1 %= val2;
+ break;
+ case AD:
+ val1 += val2;
+ break;
+ case SB:
+ val1 -= val2;
+ break;
+ case EQ:
+ val1 = (val1 == val2);
+ break;
+ case NE:
+ val1 = (val1 != val2);
+ break;
+ case GE:
+ val1 = (val1 >= val2);
+ break;
+ case LE:
+ val1 = (val1 <= val2);
+ break;
+ case GT:
+ val1 = (val1 > val2);
+ break;
+ case LT:
+ val1 = (val1 < val2);
+ break;
+ case AN:
+ val1 = (val1 && val2);
+ break;
+ case OR:
+ val1 = (val1 || val2);
+ break;
+ }
+ }
+}
+
+void meval(STRING ** pps)
+{
+ char *s;
+
+ if (pps[1] == NULL)
+ pushnum((long) 0);
+ else {
+ s = pps[1]->s_body;
+ pushnum(calc(0, &s));
+ }
+}
+
+void mifdef(STRING ** pps)
+{
+ if (pps[1] && find(pps[1])) {
+ pushstr(pps[2]);
+ } else
+ pushstr(pps[3]);
+}
+
+int moreargs(STRING ** pps, int n)
+{
+ register int i;
+
+ for (i = n; i <= 9; i++)
+ if (pps[i] != NULL)
+ return (1);
+ return (0);
+}
+
+void mifelse(STRING ** pps)
+{
+ if (cmpstr(pps[1], pps[2]))
+ pushstr(pps[3]);
+ else if (moreargs(pps, 5))
+ if (cmpstr(pps[4], pps[5]))
+ pushstr(pps[6]);
+ else if (moreargs(pps, 8))
+ if (cmpstr(pps[7], pps[8]))
+ pushstr(pps[9]);
+ else
+ return;
+ else
+ pushstr(pps[7]);
+ else
+ pushstr(pps[4]);
+}
+
+int doinclude(STRING ** pps)
+{
+ return (pushfile((pps[1] != NULL) ? pps[1]->s_body : NULL));
+}
+
+void msinclude(STRING ** pps)
+{
+ doinclude(pps);
+}
+
+void minclude(STRING ** pps)
+{
+ if (doinclude(pps))
+ return;
+ errorp(1, "cannot open %s",
+ pps[1] != NULL ? pps[1]->s_body : NULL);
+}
+
+void mincr(STRING ** pps)
+{
+ pushnum((long) ((pps[1] != NULL) ? atol(pps[1]->s_body) + 1 : 1));
+}
+
+void mdecr(STRING ** pps)
+{
+ pushnum((long) ((pps[1] != NULL) ? atol(pps[1]->s_body) - 1 : -1));
+}
+
+void mindex(STRING ** pps)
+{
+ register char *pc, *pf;
+ register int ln;
+ long v;
+
+ if (pps[2] == NULL)
+ v = 0;
+ else if (pps[1] == NULL)
+ v = -1;
+ else {
+ pc = pps[1]->s_body;
+ ln = strlen(pf = pps[2]->s_body);
+ while ((pc = index(pc, *pf)) != NULL
+ && strncmp(pc, pf, ln))
+ ++pc;
+ v = (pc != NULL) ? (long) (pc - pps[1]->s_body) : -1;
+ }
+ pushnum(v);
+}
+
+void mlen(STRING ** pps)
+{
+ pushnum((long)
+ ((pps[1] != NULL) ? pps[1]->s_next - pps[1]->s_body : 0));
+}
+
+void mmaketemp(STRING ** pps)
+{
+ register char *pc;
+ if (pps[1] == NULL || strlen(pc = pps[1]->s_body) < TMPMIN)
+ return;
+ mktemp(pc);
+ pushstr(pps[1]);
+}
+
+void msubstr(STRING ** pps)
+{
+ register char *pc, *pb, *pe;
+ int len, n, s;
+ STRING *a;
+
+ if (pps[1] == NULL)
+ return;
+ len = strlen(pc = pps[1]->s_body);
+ if (pps[2] == NULL) {
+ n = (pps[3] != NULL) ? atoi(pps[3]->s_body) : len;
+ s = (n > 0) ? 0 : -1;
+ } else {
+ s = atoi(pps[2]->s_body);
+ n = (pps[3] != NULL) ? atoi(pps[3]->s_body) : (s >
+ 0) ? len :
+ -len;
+ }
+ if (n == 0)
+ return;
+ pe = pc + len;
+ if (s < 0)
+ s += len;
+ if (n < 0) {
+ s += n + 1;
+ n = -n;
+ }
+ a = makestr();
+ for (pb = pc + s; n--; ++pb)
+ if (pb >= pc && pb <= pe)
+ appendstr(a, *pb);
+ pushstr(a);
+ decstr(a);
+}
+
+void msyscmd(STRING ** pps)
+{
+ if (pps[1])
+ system(pps[1]->s_body);
+}
+
+void mtranslit(STRING ** pps)
+{
+ register char *pc, *pt, *pr;
+ char c = '\0';
+ STRING *a;
+
+ if (pps[1] == NULL || pps[2] == NULL)
+ return;
+ a = makestr();
+ pc = pps[1]->s_body;
+ do {
+ pt = pps[2]->s_body;
+ pr = (pps[3] != NULL) ? pps[3]->s_body : &c;
+ do {
+ if (*pc == *pt)
+ break;
+
+ if (*pr)
+ ++pr;
+ } while (*++pt);
+ if (*pt) {
+ if (*pr)
+ appendstr(a, *pr);
+ } else
+ appendstr(a, *pc);
+ } while (*++pc);
+ pushstr(a);
+ decstr(a);
+}
+
+void mundefine(STRING ** pps)
+{
+ register ENTRY *e, *ep = NULL;
+ register int hash;
+
+ if (pps[1] == NULL)
+ return;
+ hash = pps[1]->s_hash;
+ for (e = e_root[hash % HASHSZ]; e != NULL; e = e->e_next)
+ if (e->e_name->s_hash == hash
+ && strcmp(e->e_name->s_body, pps[1]->s_body) == 0)
+ break;
+ else
+ ep = e;
+ if (e == NULL)
+ return;
+ if (e->e_type == MACD)
+ decstr(e->e_at.e_pstr);
+ decstr(e->e_name);
+ if (ep != NULL)
+ ep->e_next = e->e_next;
+ else
+ e_root[hash % HASHSZ] = e->e_next;
+ free(e);
+}
+
+void undiv(int n)
+{
+ register int c;
+ register FILE *fp;
+
+ if (n > 0 && n <= 9 && (fp = outfile[n].fp) != NULL && ofnum != n) {
+ fclose(fp);
+ if ((fp = fopen(outfile[n].name, "r")) == NULL)
+ errorp(1, "cannot open %s", outfile[n].name);
+ while ((c = getc(fp)) != EOF)
+ outc(c);
+ fclose(fp);
+ unlink(outfile[n].name);
+ free(outfile[n].name);
+ outfile[n].name = NULL;
+ outfile[n].fp = NULL;
+ }
+}
+
+void mundivert(STRING ** pps)
+{
+ register int i, f = 0;
+
+ if (pps)
+ for (i = 1; i <= 9; i++)
+ if (pps[i]) {
+ undiv(atoi(pps[i]->s_body));
+ f = 1;
+ }
+ if (pps == NULL || f == 0)
+ for (i = 1; i <= 9; i++)
+ undiv(i);
+}
+
+int main(int argc, char *argv[])
+{
+ buildin("changequote", mchangequote);
+ buildin("decr", mdecr);
+ buildin("define", mdefine);
+ buildin("divert", mdivert);
+ buildin("divnum", mdivnum);
+ buildin("dnl", mdnl);
+ buildin("dumpdef", mdumpdef);
+ buildin("errprint", merrprint);
+ buildin("eval", meval);
+ buildin("ifdef", mifdef);
+ buildin("ifelse", mifelse);
+ buildin("include", minclude);
+ buildin("incr", mincr);
+ buildin("index", mindex);
+ buildin("len", mlen);
+ buildin("maketemp", mmaketemp);
+ buildin("sinclude", msinclude);
+ buildin("substr", msubstr);
+ buildin("syscmd", msyscmd);
+ buildin("translit", mtranslit);
+ buildin("undefine", mundefine);
+ buildin("undivert", mundivert);
+
+ single = (argc == 2);
+ if (argc > 1) {
+ while (--argc)
+ if (pushfile(*++argv))
+ process(0);
+ else
+ errorp(0, "cannot open %s", *argv);
+ } else {
+ pushfile("-");
+ process(0);
+ }
+ mdivert(NULL);
+ mundivert(NULL);
+ exit(0);
+}
--- /dev/null
+/*
+ * moo -- play the game of moo,
+ * also known as mastermind.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+
+char entdiff[] = "Please enter a string of %d digits, all different\n";
+
+int badline(char *s, int l)
+{
+ int i, j;
+ if (strlen(s) != l + 1)
+ return (1);
+ s[l] = 0;
+ for (i = 0; i != l; i++) {
+ if (s[i] < '0' || '9' < s[i])
+ return (1);
+ for (j = 0; j != i; j++)
+ if (s[i] == s[j])
+ return (1);
+ }
+ return (0);
+}
+
+int main(int argc, char *argv[])
+{
+ char moo[10];
+ int nmoo;
+ char line[256];
+ int i, j;
+ int nbull, ncow;
+ union {
+ time_t utime;
+ int ubuf[2];
+ } un;
+
+ time(&un.utime);
+ srand(un.ubuf[0] + un.ubuf[1]);
+ if (argc < 2)
+ nmoo = 4;
+ else
+ nmoo = atoi(argv[1]);
+ if (nmoo < 1 || 10 < nmoo) {
+ printf
+ ("Usage: moo [n]\n\twhere 1<=n<=10, n defaults to 4\n");
+ exit(1);
+ }
+ for (;;) {
+ printf("New game.\n");
+ for (i = 0; i != nmoo; i++) {
+ Again:
+ moo[i] = rand() / 100 % 10 + '0';
+ for (j = 0; j != i; j++)
+ if (moo[i] == moo[j])
+ goto Again;
+ }
+ for (;;) {
+ if (fgets(line, 255, stdin) == NULL)
+ exit(0);
+ if (badline(line, nmoo))
+ printf(entdiff, nmoo);
+ else {
+ nbull = 0;
+ ncow = 0;
+ for (i = 0; i != nmoo; i++)
+ for (j = 0; j != nmoo; j++)
+ if (line[i] == moo[j])
+ if (i == j)
+ nbull++;
+ else
+ ncow++;
+ if (nbull == nmoo) {
+ printf("Right!\n");
+ break;
+ }
+ printf("%d bull%c, %d cow%c\n",
+ nbull, nbull != 1 ? 's' : '\0',
+ ncow, ncow != 1 ? 's' : '\0');
+ }
+ }
+ }
+}
--- /dev/null
+
+/*
+ * pnmatch(string, pattern, unanchored)
+ * returns 1 if pattern matches in string.
+ * pattern:
+ * [c1c2...cn-cm] class of characters.
+ * ? any character.
+ * * any # of any character.
+ * ^ beginning of string (if unanchored)
+ * $ end of string (if unanchored)
+ * unanch:
+ * 0 normal (anchored) pattern.
+ * 1 unanchored (^$ also metacharacters)
+ * >1 end unanchored.
+ * >1 is used internally but should not be used by the user.
+ */
+pnmatch(s, p, unanch)
+register char *s, *p;
+{
+ register c1;
+ int c2;
+
+ if (unanch == 1) {
+ while (*s)
+ if (pnmatch(s++, p, ++unanch))
+ return (1);
+ return (0);
+ }
+ while (c2 = *p++) {
+ c1 = *s++;
+ switch(c2) {
+ case '^':
+ if (unanch == 2) {
+ s--;
+ continue;
+ } else if (unanch == 0)
+ break;
+ else
+ return (0);
+
+ case '$':
+ if (unanch)
+ return (c1 == '\0');
+ break;
+
+ case '[':
+ for (;;) {
+ c2 = *p++;
+ if (c2=='\0' || c2==']')
+ return (0);
+ if (c2 == '\\' && *p == '-')
+ c2 = *p++;
+ if (c2 == c1)
+ break;
+ if (*p == '-')
+ if (c1<=*++p && c1>=c2)
+ break;
+ }
+ while (*p && *p++!=']')
+ ;
+
+ case '?':
+ if (c1)
+ continue;
+ return(0);
+
+ case '*':
+ if (!*p)
+ return(1);
+ s--;
+ do {
+ if (pnmatch(s, p, unanch))
+ return (1);
+ } while(*s++ != '\0');
+ return(0);
+
+ case '\\':
+ if ((c2 = *p++) == '\0')
+ return (0);
+ }
+ if (c1 != c2)
+ return (0);
+ }
+ return(unanch ? 1 : !*s);
+}
--- /dev/null
+/*
+ * pr.c
+ * Print files.
+ * 7/5/94
+ * All references to page length exclude possible margin lines.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <err.h>
+#include <sys/types.h>
+#include <errno.h>
+
+#define LSIZE 256 /* line size */
+#define LENGTH (66-MARGIN) /* default page length */
+#define WIDTH 80 /* default page width */
+#define TMAR 5 /* five lines of top margin */
+#define BMAR 5 /* five lines of bottom margin */
+#define HEADER 2 /* line # where header appears */
+
+#define MARGIN (TMAR+BMAR)
+
+#define USAGE "Usage: pr [ options ] [ file ...]\n"\
+"Options:\n"\
+"\t+skip\tSkip the first skip pages of input before printing\n"\
+"\t-cols\tPrint the input in cols columns\n"\
+"\t-h\tThe next argument is the header (replaces file name)\n"\
+"\t-ln\tSet page size to n lines (default, 66)\n"\
+"\t-m\tPrint each input file in a separate column\n"\
+"\t-n\tNumber the output lines\n"\
+"\t-sc\tSeparate each column with character c\n"\
+"\t-t\tSuppress top and bottom margins and header\n"\
+"\t-wn\tPage width is set to n columns (default, 80)\n"\
+"A file named `-' means stdin.\n"
+
+/*
+ * info on input file streams
+ * Unless -m is in effect, only f[0] is used.
+ */
+struct f {
+ int f_ff; /* '\f' received */
+ FILE *f_stream; /* input stream */
+};
+
+
+int ncol = 1, /* # of columns */
+ nskip, /* # pages to skip of each file */
+ length = LENGTH, /* page length */
+ width = WIDTH, /* page width */
+ lno, /* current input line # */
+ fwidth; /* field width */
+char schar, /* separator char */
+ tflag, /* -t: no header or margins */
+ mflag, /* -m: multiple file output */
+ nflag, /* -n: line number output */
+*date, /* date string */
+*header, /* -h: header text */
+**lines; /* buffer addr for multicolumn */
+
+struct f f[20]; /* input file info */
+
+extern int page1(void (*putline) (char *, int));
+extern int page2(void (*putline) (char *, int));
+
+int (*page) (void (*fn) (char *, int)) = (void *)page1;
+
+/*
+ * read a line
+ * Simple char processing is done, including tab expansion. getline( )
+ * will return an empty line if f_ff is set. Lines are truncated to
+ * fit the field width.
+ */
+int getline(struct f *fp, char *lbuf)
+{
+ int col;
+ char *p;
+ int c;
+
+ col = 0;
+ p = lbuf;
+ if (feof(fp->f_stream) == 0 && fp->f_ff == 0) {
+ if (nflag) {
+ sprintf(p, "%4d: ", ++lno);
+ p += 6;
+ }
+ for (;;) {
+ switch (c = getc(fp->f_stream)) {
+ case '\f':
+ ++fp->f_ff;
+ case EOF:
+ if (nflag && (p == &lbuf[6]))
+ p = lbuf;
+ break;
+ case '\n':
+ break;
+ case '\r':
+ continue;
+ case '\t':
+ do {
+ if (p < &lbuf[LSIZE - 1]
+ && col < fwidth)
+ *p++ = ' ';
+ } while (++col & 7);
+ continue;
+ case '\b':
+ if (col) {
+ --col;
+ if (p < &lbuf[LSIZE - 1]
+ && col < fwidth)
+ *p++ = c;
+ }
+ continue;
+ default:
+ if (p < &lbuf[LSIZE - 1] && col < fwidth)
+ *p++ = c;
+ ++col;
+ continue;
+ }
+ break;
+ }
+
+ }
+ *p = '\0';
+ return (p - lbuf);
+}
+
+
+/*
+ * write a line, incrementally
+ * A line of output can be built by successive calls to putl( ). A line
+ * address of 0 puts the newline. Simple char processing is done,
+ * including tab optimization. Each line segment is padded to the
+ * field width, unless a there is a field separator char.
+ */
+void putl(char *lbuf, int schr)
+{
+ char *p;
+ int c, nextxcol;
+ static int col, xcol;
+
+ if ((p = lbuf) == NULL) {
+ col = 0;
+ xcol = 0;
+ putchar('\n');
+ return;
+ }
+ nextxcol = xcol + fwidth;
+
+ for (;;) {
+ if ((c = *p++) == '\0') {
+ if ((c = schr) == '\0')
+ break;
+ schr = 0;
+ --p;
+ nextxcol = xcol + 1;
+ }
+ if (c == ' ') {
+ ++xcol;
+ continue;
+ }
+ if (c == '\b') {
+ --xcol;
+ continue;
+ }
+ while ((col | 7) + 1 <= xcol) {
+ col = (col | 7) + 1;
+ putchar('\t');
+ }
+ while (col < xcol) {
+ ++col;
+ putchar(' ');
+ }
+ while (col > xcol) {
+ --col;
+ putchar('\b');
+ }
+ putchar(c);
+ xcol = ++col;
+ }
+
+ xcol = nextxcol;
+}
+
+
+/*
+ * throw away output line
+ * Used when skipping the first pages of input.
+ */
+void nop(char *lbuf, int schr)
+{
+
+}
+
+/*
+ * print from open input streams
+ * Control output of pages, perhaps provide header/footer margins and title.
+ * The paging routine is expected to give an eof warning on the last page.
+ */
+void print(char *file)
+{
+ int i, pg, eof;
+
+ for (pg = 1; pg <= nskip; ++pg)
+ if ((*page) (nop))
+ return;
+
+ do {
+ if (tflag == 0)
+ for (i = 0; i < TMAR; ++i) {
+ if (i != HEADER) {
+ putchar('\n');
+ continue;
+ }
+ printf
+ ("%.12s%.5s %s Page %d, line %ld\n",
+ date + 4, date + 19,
+ header ? header : file, pg,
+ (long) (pg -
+ 1) * (mflag ? 1 : ncol) *
+ length + 1);
+ }
+ eof = (*page) (putl);
+ if (tflag == 0)
+ for (i = 0; i < BMAR; ++i)
+ putchar('\n');
+ } while (++pg, eof == 0);
+}
+
+
+/*
+ * open input file
+ * Failure to open is fatal. openf( "-") returns stdin.
+ */
+FILE *openf(char *file)
+{
+ FILE *stream;
+
+ lno = 0;
+ if (file[0] == '-' && file[1] == '\0')
+ return (stdin);
+ if ((stream = fopen(file, "r")) == NULL)
+ if (errno == EMFILE)
+ errx(1, "too many files for -m");
+ else
+ err(1, "cannot open \"%s\"", file);
+ return (stream);
+}
+
+/*
+ * page one stream per column
+ * This handles all cases of one column per page.
+ * Formfeed advances output of that stream to the next page.
+ * Eof is only indicated when all streams give this condition.
+ */
+int page1(void (*putline) (char *, int))
+{
+ int i, j;
+ char lbuf[LSIZE];
+
+ for (i = 0; i < length; ++i) {
+ for (j = 0; j < ncol; ++j) {
+ getline(&f[j], lbuf);
+ (*putline) (lbuf, j < ncol - 1 ? schar : 0);
+ }
+ (*putline) ((char *) 0, 0);
+ }
+
+ i = 0;
+ for (j = 0; j < ncol; ++j) {
+ f[j].f_ff = 0;
+ if (feof(f[j].f_stream))
+ ++i;
+ else if (ungetc(getc(f[j].f_stream), f[j].f_stream) == EOF)
+ ++i;
+ }
+ return (i == j);
+}
+
+
+/*
+ * page one stream across multiple columns
+ * The first `ncol'-1 columns of text are buffered; the last is simply
+ * read as needed. Formfeeds advance output to the next column.
+ */
+
+int page2(void (*putline) (char *, int))
+{
+ int i, j, k;
+ char lbuf[LSIZE];
+
+ for (i = 0; i < (ncol - 1) * length; ++i) {
+ if ((i % length) == 0)
+ f[0].f_ff = 0;
+ if (k = getline(&f[0], lbuf)) {
+ if ((lines[i] = malloc(k + 1)) == NULL)
+ errx(1, "out of space");
+ strcpy(lines[i], lbuf);
+ } else
+ lines[i] = NULL;
+ }
+ f[0].f_ff = 0;
+
+ for (i = 0; i < length; ++i) {
+ for (j = 0; j < ncol - 1; ++j) {
+ k = j * length + i;
+ if (lines[k]) {
+ (*putline) (lines[k], schar);
+ free(lines[k]);
+ } else
+ (*putline) ("", schar);
+ }
+ getline(&f[0], lbuf);
+ (*putline) (lbuf, 0);
+ (*putline) ((char *) 0, 0);
+ }
+
+ if (feof(f[0].f_stream))
+ return (1);
+ if (ungetc(getc(f[0].f_stream), f[0].f_stream) == EOF)
+ return (1);
+ return (0);
+}
+
+
+/*
+ * initialize & get options
+ * Flags are recognized up to the first file name. If multi-column (-N),
+ * allocate line array. If printing multiple files (-m), open all files.
+ * There are two paging algorithms: one file per column (page1), and
+ * many columns per file (page2). The latter requires page buffering.
+ * init( ) makes this selection.
+ */
+char **init(int ac, char **av)
+{
+ int mar = MARGIN;
+ static char obuf[BUFSIZ];
+ time_t tvec;
+
+ setbuf(stdout, obuf);
+
+ while (++av, --ac) {
+ if (av[0][0] == '+')
+ if ((nskip = atoi(&av[0][1])) <= 0)
+ errx(1, "bad skip");
+ else
+ continue;
+ if (av[0][0] != '-')
+ break;
+ switch (av[0][1]) {
+ case '\0':
+ break;
+ case 'l':
+ length = atoi(&av[0][2]) - mar;
+ continue;
+ case 'w':
+ width = atoi(&av[0][2]);
+ continue;
+ case 'h':
+ if (av[0][2])
+ header = &av[0][2];
+ else {
+ if (--ac <= 0)
+ errx(1, "missing header arg");
+ header = (++av)[0];
+ }
+ continue;
+ case 's':
+ if ((schar = av[0][2]) == '\0')
+ schar = '\t';
+ continue;
+ case 'm':
+ ++mflag;
+ continue;
+ case 't':
+ ++tflag;
+ length += mar;
+ mar = 0;
+ continue;
+ case 'n':
+ ++nflag;
+ continue;
+ default:
+ /* FIXME: trap ncol < 1 */
+ if ('0' <= av[0][1] && av[0][1] <= '9')
+ ncol = atoi(&av[0][1]);
+ else {
+ fprintf(stderr, USAGE);
+ exit(1);
+ }
+ continue;
+ }
+ break;
+
+ }
+
+ f[0].f_stream = stdin;
+ if (mflag && av[0]) {
+ ncol = 0;
+ do {
+ f[ncol++].f_stream = openf(av++[0]);
+ } while (av[0]);
+ }
+
+ /*
+ * check that all options jive
+ */
+ if (length <= 0)
+ if (length == -mar) { /* gunja artifice */
+ length = 1;
+ ++tflag;
+ } else
+ errx(1, "length too small");
+ fwidth = width / ncol;
+ if (schar)
+ --fwidth;
+ if (fwidth <= 0 || (nflag && fwidth < 10))
+ errx(1, "width too small");
+ if (fwidth >= LSIZE - 1)
+ errx(1, "too wide");
+ /* FIXME: maths overflow */
+ if (ncol > 1 && mflag == 0) {
+ if ((lines =
+ malloc((ncol - 1) * length * sizeof(char *))) == NULL)
+ errx(1, "insufficient core");
+ page = (void *)page2; /* Hack cast for SDCC */
+ }
+
+ time(&tvec);
+ date = ctime(&tvec);
+
+ return (av);
+}
+
+
+/*
+ * paginate files to standard output
+ * If no files are given, use standard input. The file name "-" also
+ * means standard input.
+ */
+int main(int argc, char *argv[])
+{
+ argv = init(argc, argv);
+
+ if (*argv)
+ while (*argv) {
+ f[0].f_stream = openf(*argv);
+ print(*argv++);
+ fclose(f[0].f_stream);
+ } else
+ print("");
+
+ return (0);
+}
+
+/* end of pr.c */
--- /dev/null
+/*
+ * Tape archive
+ * tar [0-7bcflmrtuvwx]+ [blocks] [archive] pathname*
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <err.h>
+#include <time.h>
+#include <errno.h>
+#include <utime.h>
+
+#define S_PERM 07777 /* should be in stat.h */
+
+
+/* FIMXE: Non portable */
+#define DIRSIZ 30
+struct direct {
+ uint16_t d_ino;
+ uint8_t d_name[DIRSIZ];
+};
+
+/* Possible actions for do_checksum_error(). */
+#define CLEAR 0 /* Print all pending messages. */
+#define ADD 1 /* Add another pending message. */
+
+#define MAXBLK 20
+#define roundup(n, r) (((n)+(r)-1)/(r))
+
+typedef struct dirhd_t {
+ dev_t t_dev;
+ ino_t t_ino;
+ unsigned short t_nlink, t_mode, t_uid, t_gid;
+ off_t t_size;
+ time_t t_mtime;
+ char *t_name;
+ struct dirhd_t *t_cont[];
+} dirhd_t;
+
+typedef union tarhd_t {
+ struct {
+ char th_name[100],
+ th_mode[8],
+ th_uid[8],
+ th_gid[8],
+ th_size[12],
+ th_mtime[12],
+ th_check[8], th_islink, th_link[100], th_pad[255];
+ } th_info;
+ char th_data[BUFSIZ];
+} tarhd_t;
+
+typedef unsigned short flag_t; /* fastest type for machine */
+
+flag_t linkmsg = 0, /* message if not all links found */
+ modtime = 1, /* restore modtimes */
+ verbose = 0;
+int unixbug = 0; /* avoid bug in U**X tar */
+FILE *whether = (FILE *) NULL, /* ask about each file */
+ *tarfile;
+char tapedev[10] = { '\0' };
+char *archive = &tapedev[0];
+unsigned short blocking = 1; /* blocking factor */
+struct utimbuf oldtime; /* for utime */
+time_t recently; /* set to 6 months ago for tv key */
+
+void fatal(char *args, ...);
+dirhd_t *newdirhd(char *name, unsigned short len);
+void table(dirhd_t * args);
+void extract(dirhd_t * args);
+dirhd_t *update(char *name, dirhd_t * args, tarhd_t * header);
+void append(char *name, dirhd_t * args);
+dirhd_t *research(char *name, dirhd_t * args);
+int argcont(dirhd_t * args, char *name, int ret);
+int makepath(char *pathname);
+int mkparent(char *pathname);
+int recreate(char *pathname, unsigned short mode);
+int create(char *pathname, unsigned short mode);
+int disallow(char function, char *pathname);
+tarhd_t *readhdr(void);
+void skipfile(tarhd_t * header);
+void writehdr(char *name, dirhd_t * args, char *link);
+void writefil(char *name, off_t size);
+tarhd_t *readblk(void);
+void ungetblk(void);
+tarhd_t *writeblk(void);
+void flushtar(void);
+void filelink(dev_t dev, ino_t ino, unsigned short nlink, char *link);
+char *havelink(dev_t dev, ino_t ino, flag_t flag);
+void misslink(void);
+int getoct(char *cp);
+long getoctl(char *cp);
+void putoct(char *str, unsigned short val);
+void putoctl(char *str, unsigned long val);
+int checksum(char *str, int len);
+int contains(char *dname, char *fname);
+void do_checksum_error(char *name, int action);
+void scan_for_next(void);
+
+int main(int argc, char *argv[])
+{
+ char *key, unit = '\0', function = 0, deffunc, prefix[101] = { '\0' };
+ unsigned short arg = 2;
+ dirhd_t *args;
+
+ if (argc < 2) {
+ fprintf(stderr,
+ "Usage: %s [crtux][0-7bflmvw] [blocks] [archive] pathname*\n",
+ argv[0]);
+ exit(-1);
+ }
+ for (key = argv[1]; *key != '\0'; key++)
+ switch (*key) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ unit = *key;
+ deffunc = 't';
+ continue;
+ case 'b':
+ if (argc <= arg)
+ fatal("missing blocking factor");
+ else if ((blocking = atoi(argv[arg++])) <= 0
+ || blocking > MAXBLK)
+ fatal("illegal blocking factor");
+ deffunc = 'r';
+ continue;
+ case 'f':
+ if (argc <= arg)
+ fatal("missing archive name");
+ else
+ archive = argv[arg++];
+ deffunc = 't';
+ continue;
+ case 'l':
+ linkmsg = 1;
+ deffunc = 'r';
+ continue;
+ case 'm':
+ modtime = 0;
+ deffunc = 'x';
+ continue;
+ case 'v':
+ verbose = 1;
+ deffunc = 't';
+ continue;
+ case 'U':
+ unixbug = 1;
+ deffunc = 't';
+ continue;
+ case 'w':
+ whether = fopen("/dev/tty", "r+w");
+ deffunc = 'x';
+ continue;
+ case 'c':
+ case 'r':
+ case 't':
+ case 'u':
+ case 'x':
+ if (function)
+ fatal
+ ("keys c, r, t, u, x are mutually exclusive");
+ else
+ function = *key;
+ continue;
+ default:
+ fatal("illegal key '%c'", *key);
+ }
+ if (!function)
+ function = deffunc;
+ oldtime.actime = time((time_t *) 0);
+ if (verbose && function == 't')
+ recently = oldtime.actime - 6 * ((time_t) 30 * 24 * 60 * 60);
+
+ /* construct descriptors for args given */
+
+ args = (dirhd_t *) malloc(sizeof(dirhd_t)
+ + (argc - arg) * sizeof(dirhd_t *));
+ if (args == NULL)
+ fatal("out of memory");
+ args->t_mode = S_IFDIR;
+ args->t_nlink = 0;
+ for (; arg < argc; arg++) {
+ dirhd_t *argp;
+
+ if ((argp =
+ newdirhd(argv[arg], strlen(argv[arg]))) == NULL)
+ fprintf(stderr, "Tar: %s: out of memory\n",
+ argv[arg]);
+ else
+ args->t_cont[args->t_nlink++] = argp;
+ }
+
+ /* open archive file */
+
+ if (*archive == '\0')
+ sprintf(archive, "/dev/%smt%c", blocking == 1 ? "" : "r",
+ unit);
+ if (function == 't' || function == 'x')
+ if (strcmp(archive, "-") == 0)
+ tarfile = stdin;
+ else
+ tarfile = fopen(archive, "r");
+ else if (function == 'c')
+ if (strcmp(archive, "-") == 0)
+ tarfile = stdout;
+ else
+ tarfile = fopen(archive, "w");
+ else
+ tarfile = fopen(archive, "r+w");
+ if (tarfile == NULL)
+ err(1, "Tar: 1: %s", archive);
+ setbuf(tarfile, NULL);
+
+ /* perform required function */
+
+ switch (function) {
+ case 't':
+ table(args);
+ break;
+ case 'x':
+ extract(args);
+ break;
+ case 'r':
+ while (readblk() != NULL);
+ goto doappend;
+ case 'u':
+ if ((args = update(prefix, args, readhdr())) == NULL)
+ break;
+ case 'c':
+ doappend:
+ append(prefix, args);
+ flushtar();
+ }
+ /* Flush any remaining checksum messages. */
+ do_checksum_error(NULL, CLEAR);
+
+ exit(errno);
+}
+
+void fatal(char *args, ...)
+{
+ /* Flush any remaining checksum messages. */
+ do_checksum_error(NULL, CLEAR);
+
+ fprintf(stderr, "tar: %r\n", &args);
+ exit(-1);
+}
+
+dirhd_t *newdirhd(char *name, unsigned short len)
+{
+ dirhd_t *dp;
+
+ if ((dp = (dirhd_t *) malloc(sizeof(dirhd_t))) != NULL
+ && (dp->t_name = malloc(len + 1)) == NULL) {
+ free(dp);
+ dp = NULL;
+ }
+ if (dp != NULL) {
+ dp->t_nlink = 0;
+ strncpy(dp->t_name, name, len);
+ dp->t_name[len] = '\0';
+ }
+ return (dp);
+}
+
+void table(dirhd_t * args)
+{
+ tarhd_t *header;
+
+ for (; (header = readhdr()) != NULL; skipfile(header)) {
+ if (argcont(args, header->th_info.th_name, 0) < 0)
+ continue;
+ if (verbose) {
+ unsigned short mode =
+ getoct(header->th_info.th_mode);
+ unsigned short uid =
+ getoct(header->th_info.th_uid), gid =
+ getoct(header->th_info.th_gid);
+ off_t size = getoctl(header->th_info.th_size);
+ time_t mtime = getoctl(header->th_info.th_mtime);
+ char *timestr = ctime(&mtime);
+
+ if (header->th_info.
+ th_name[strnlen(header->th_info.th_name, 100) -
+ 1]
+ == '/')
+ putchar('d');
+ else
+ putchar('-');
+ printf("%c%c%c",
+ mode & S_IROTH ? 'r' : '-',
+ mode & S_IWOTH ? 'w' : '-',
+ mode & S_ISUID ? 's' :
+ mode & S_IXOTH ? 'x' : '-');
+ printf("%c%c%c",
+ mode & S_IRGRP ? 'r' : '-',
+ mode & S_IWGRP ? 'w' : '-',
+ mode & S_ISGID ? 's' :
+ mode & S_IXGRP >> 3 ? 'x' : '-');
+ printf("%c%c%c",
+ mode & S_IRUSR >> 6 ? 'r' : '-',
+ mode & S_IWUSR >> 6 ? 'w' : '-',
+ mode & S_ISVTX ? 't' :
+ mode & S_IXUSR >> 6 ? 'x' : '-');
+ printf("%3d %3d %6ld %.11s%.5s ",
+ gid,
+ uid,
+ size,
+ timestr,
+ mtime >
+ recently ? timestr + 11 : timestr + 19);
+ }
+ printf("%.100s", header->th_info.th_name);
+ if (header->th_info.th_islink)
+ if (verbose)
+ printf("\n%33s link to %s\n", "",
+ header->th_info.th_link);
+ else
+ printf(" link to %s\n",
+ header->th_info.th_link);
+ else
+ putchar('\n');
+ }
+}
+
+void extract(dirhd_t * args)
+{
+ tarhd_t *header;
+ short skipping = 0;
+
+ while ((header = readhdr()) != NULL) {
+ char name[101];
+ unsigned short namelen =
+ strnlen(header->th_info.th_name, 100), mode, uid, gid;
+
+ if (!skipping
+ || contains(name, header->th_info.th_name) >= 0) {
+ strncpy(name, header->th_info.th_name, namelen);
+ name[namelen--] = '\0';
+ if ((skipping = argcont(args, name, 0)) >= 0)
+ skipping = disallow('x', name);
+ }
+ switch (skipping) {
+ case -1:
+ skipping = 0;
+ case 1:
+ skipfile(header);
+ continue;
+ }
+ if (verbose)
+ printf("x %s\n", name);
+ mode = getoct(header->th_info.th_mode);
+ uid = getoct(header->th_info.th_uid);
+ gid = getoct(header->th_info.th_gid);
+ oldtime.modtime = getoctl(header->th_info.th_mtime);
+ if (name[namelen] == '/') {
+ name[namelen] = '\0';
+ makepath(name);
+ chmod(name, mode);
+ name[namelen] = '/';
+ } else if (header->th_info.th_islink == '\0') {
+ int fd = recreate(name, mode);
+ off_t size = getoctl(header->th_info.th_size);
+
+ for (; size > 0; size -= sizeof(tarhd_t)) {
+ int my_size;
+
+ /* Handle unexpected EOF - mods by vlad@kiev.mwc.com */
+ if ((header = readblk()) == NULL) {
+ fprintf(stderr,
+ "Unexpected end of the file %s\n",
+ name);
+ break;
+ }
+ if (size > sizeof(tarhd_t))
+ my_size = sizeof(tarhd_t);
+ else
+ my_size = (int) size;
+ if (fd >= 0 && (write(fd, header->th_data,
+ my_size) != my_size))
+
+ warn("Tar: 2: %s", name);
+ }
+ if (fd >= 0)
+ close(fd);
+ } else {
+ struct stat statbuf;
+ flag_t xlink = 1;
+
+ if (stat(header->th_info.th_link, &statbuf) < 0)
+ close(recreate
+ (header->th_info.th_link, mode));
+ else if (havelink
+ (statbuf.st_dev, statbuf.st_ino, 0)
+ != NULL)
+ xlink = 0;
+ if (xlink)
+ fprintf(stderr, "Tar: Must extract %s\n",
+ header->th_info.th_link);
+ unlink(name);
+ mkparent(name);
+ if (link(header->th_info.th_link, name) < 0)
+ fprintf(stderr,
+ "Tar: Can't link %s to %s\n", name,
+ header->th_info.th_link);
+ }
+ if (modtime)
+ utime(name, &oldtime);
+ chown(name, uid, gid);
+ }
+}
+
+dirhd_t *update(char *name, dirhd_t * args, tarhd_t * header)
+{
+ unsigned short namelen = strlen(name);
+ flag_t donedir = 0;
+
+ if (header == NULL)
+ return (args);
+ if (args->t_nlink == 0 && (args = research(name, args)) == NULL)
+ return (NULL);
+ do
+ switch (args->t_mode & S_IFMT) {
+ short arg;
+ dirhd_t *argp;
+
+ case S_IFREG:
+ if (args->t_mtime <=
+ getoctl(header->th_info.th_mtime))
+ args->t_nlink = 0;
+ skipfile(header);
+ continue;
+ case S_IFDIR:
+ if (namelen != 0 && !donedir) {
+ name[namelen] = '/';
+ donedir++;
+ }
+ if ((arg =
+ argcont(args,
+ header->th_info.th_name + namelen +
+ donedir, -1))
+ >= 0) {
+ strcpy(name + namelen + donedir,
+ (argp = args->t_cont[arg])->t_name);
+ if ((args->t_cont[arg] =
+ update(name, argp, header))
+ == NULL)
+ args->t_cont[arg]
+ = args->t_cont[--args->
+ t_nlink];
+ } else
+ skipfile(header);
+ name[namelen + donedir] = '\0';
+ } while ((header = readhdr()) != NULL
+ && contains(name, header->th_info.th_name));
+ if (header != NULL)
+ ungetblk();
+ if (args->t_nlink == 0) {
+ if (namelen != 0)
+ free(args->t_name);
+ free((char *) args);
+ return (NULL);
+ } else
+ return (args);
+}
+
+void append(char *name, dirhd_t * args)
+{
+ unsigned short namelen;
+ unsigned short arg;
+ dirhd_t *argp;
+
+ if ((namelen = strlen(name)) != 0 && disallow('a', name))
+ return;
+ if (args->t_nlink == 0 && (args = research(name, args)) == NULL)
+ return;
+ switch (args->t_mode & S_IFMT) {
+ case S_IFDIR:
+ if (namelen != 0) {
+ name[namelen++] = '/';
+ name[namelen] = '\0';
+ writehdr(name, args, "");
+ }
+ for (arg = 0; arg < args->t_nlink; arg++) {
+ strcpy(name + namelen,
+ (argp = args->t_cont[arg])->t_name);
+ append(name, argp);
+ }
+ break;
+ case S_IFREG:
+ if (args->t_nlink != 1) {
+ char *link;
+
+ if ((link = havelink(args->t_dev,
+ args->t_ino, 1)) != NULL) {
+ writehdr(name, args, link);
+ break;
+ } else {
+ filelink(args->t_dev,
+ args->t_ino, args->t_nlink, name);
+ }
+ }
+ writehdr(name, args, "");
+ writefil(name, args->t_size);
+ }
+ if (namelen != 0)
+ free(args->t_name);
+ free((char *) args);
+}
+
+dirhd_t *research(const char *name, dirhd_t * args)
+{
+ unsigned short namelen;
+ short nfile;
+ int fd;
+ struct stat statbuf;
+
+ if ((namelen = strlen(name)) == 0)
+ name = ".";
+ if (stat(name, &statbuf) < 0) {
+ warn("Tar: 3: %s", name);
+ return (args);
+ }
+ args->t_dev = statbuf.st_dev;
+ args->t_ino = statbuf.st_ino;
+ args->t_nlink = statbuf.st_nlink;
+ args->t_uid = statbuf.st_uid;
+ args->t_gid = statbuf.st_gid;
+ args->t_mtime = statbuf.st_mtime;
+ switch ((args->t_mode = statbuf.st_mode) & S_IFMT) {
+ case S_IFREG:
+ args->t_size = statbuf.st_size;
+ return (args);
+ default:
+ args->t_size = 0;
+ return (args);
+ case S_IFDIR:
+ args->t_size = 0;
+ }
+ if ((nfile =
+ (short) (statbuf.st_size / sizeof(struct direct))) > 2) {
+ args =
+ (dirhd_t *) realloc((char *) args,
+ sizeof(dirhd_t) + (nfile -
+ 2) *
+ sizeof(dirhd_t *));
+ if (args == NULL) {
+ fprintf(stderr, "Tar: %s: out of memory\n", name);
+ return (NULL);
+ }
+ }
+ args->t_nlink = 0;
+ if ((fd = open(name, 0)) < 0) {
+ warn("Tar: 4: %s", name);
+ return (args);
+ }
+ /* FIXME: readdir ? */
+ for (; nfile > 0; --nfile) {
+ struct direct dir_ent;
+ unsigned short arglen;
+ dirhd_t *argp;
+
+ switch (read(fd, (char *) &dir_ent, sizeof(struct direct))) {
+ case -1:
+ warn("Tar: 5: %s", name);
+ case 0:
+ break;
+ default:
+ if (dir_ent.d_ino == 0
+ || strcmp(dir_ent.d_name, ".") == 0
+ || strcmp(dir_ent.d_name, "..") == 0)
+ continue;
+ else if ((arglen = strnlen(dir_ent.d_name, DIRSIZ))
+ + namelen > 99)
+ fprintf(stderr,
+ "Tar: %s/%.*s: name too long\n",
+ name, DIRSIZ, dir_ent.d_name);
+ else if ((argp = newdirhd(dir_ent.d_name, arglen))
+ == NULL)
+ fprintf(stderr,
+ "Tar: %s/%.*s: out of memory\n",
+ name, DIRSIZ, dir_ent.d_name);
+ else
+ args->t_cont[args->t_nlink++] = argp;
+ continue;
+ }
+ break;
+ }
+ close(fd);
+ return (args);
+}
+
+int argcont(dirhd_t * args, char *name, int ret)
+{
+ unsigned short arg;
+
+ if (args->t_nlink == 0)
+ return (ret);
+ else
+ for (arg = 0; arg < args->t_nlink; arg++)
+ if (contains(args->t_cont[arg]->t_name, name))
+ return (arg);
+ return (-1);
+}
+
+/*
+ * Create file system paths
+ */
+
+int makepath(char *pathname)
+{
+ struct stat statbuf;
+ int err;
+
+ if (stat(pathname, &statbuf) == 0)
+ if ((statbuf.st_mode & S_IFMT) == S_IFDIR)
+ return (0);
+ else
+ errno = ENOTDIR;
+ else if (errno == ENOENT) {
+ errno = 0;
+ if ((err = mkparent(pathname)) != 0)
+ return err;
+ return mkdir(pathname, 0777);
+ }
+ warn("Tar: 6: %s", pathname);
+ return 1;
+}
+
+int mkparent(char *pathname)
+{
+ char *pathend = &pathname[strlen(pathname)];
+
+ while (pathend > pathname && *--pathend != '/');
+ if (pathend > pathname) {
+ *pathend = '\0';
+ errno = makepath(pathname);
+ *pathend = '/';
+ } else
+ errno = 0;
+ return (errno);
+}
+
+int recreate(char *pathname, unsigned short mode)
+{
+ int fd;
+
+ if ((fd = create(pathname, mode)) < 0
+ && (errno != ENOENT
+ || mkparent(pathname) == 0
+ && (fd = create(pathname, mode)) < 0))
+ warn("Tar: 8: %s", pathname);
+ errno = 0;
+ return (fd);
+}
+
+int create(char *pathname, unsigned short mode)
+{
+ int fd;
+
+ unlink(pathname);
+ if ((fd = creat(pathname, mode)) >= 0) {
+ struct stat statbuf;
+
+ fstat(fd, &statbuf);
+ filelink(statbuf.st_dev, statbuf.st_ino,
+ statbuf.st_nlink, pathname);
+ }
+ return (fd);
+}
+
+/*
+ * Ask about current action
+ */
+
+int disallow(char function, char *pathname)
+{
+ while (whether) {
+ int c1, c;
+
+ fprintf(whether, "%c %s? ", function, pathname);
+ for (c1 = c = getc(whether); c != EOF && c != '\n';
+ c = getc(whether));
+ switch (c1) {
+ case EOF:
+ case 'x':
+ if (function == 'a')
+ flushtar();
+ exit(errno);
+ case '\n':
+ case 'n':
+ case 'N':
+ return (1);
+ case 'y':
+ case 'Y':
+ return (0);
+ }
+ }
+ return (0);
+}
+
+/*
+ * High level I/O routines
+ */
+
+tarhd_t *readhdr(void)
+{
+ tarhd_t *header;
+
+ while ((header = readblk()) != NULL) {
+ if (header->th_info.th_name[0] == '\0')
+ if (unixbug) {
+ ungetblk();
+ return (NULL);
+ } else
+ continue;
+ else {
+ int check = getoct(header->th_info.th_check);
+
+ strncpy(header->th_info.th_check, " ",
+ sizeof(header->th_info.th_check));
+ if (checksum(header->th_data, sizeof(tarhd_t)) !=
+ check) {
+ do_checksum_error(header->th_info.th_name,
+ ADD);
+ } else {
+ do_checksum_error(NULL, CLEAR);
+ break;
+ }
+ }
+ }
+ /* We shouldn't have to check EVERY header, but if we do it here
+ * it gets done for every routine.
+ * Newer tar programs use the first eight bytes of the
+ * pad field as magic to indicate the type of archive.
+ * With old tar archives, these eight characters should be
+ * NUL. We'll only check the first one.
+ */
+ /* Check to see if this is a v7 style tar archive. */
+ if ((char) 0 != header->th_info.th_pad[0]) {
+ fatal("%.8s archive, not v7 archive.",
+ header->th_info.th_pad);
+
+ }
+ return (header);
+}
+
+void skipfile(tarhd_t * header)
+{
+ off_t size;
+
+ if (header->th_info.th_islink)
+ return;
+ for (size = getoctl(header->th_info.th_size);
+ size > 0 && readblk() != NULL; size -= sizeof(tarhd_t));
+}
+
+void writehdr(char *name, dirhd_t * args, char *link)
+{
+ tarhd_t *header = writeblk();
+
+ if (verbose)
+ fprintf(stderr, "a %s", name);
+ strncpy(header->th_info.th_name, name,
+ sizeof(header->th_info.th_name));
+ putoct(header->th_info.th_mode, args->t_mode & S_PERM);
+ putoct(header->th_info.th_uid, args->t_uid);
+ putoct(header->th_info.th_gid, args->t_gid);
+ putoctl(header->th_info.th_size, args->t_size);
+ putoctl(header->th_info.th_mtime, args->t_mtime);
+ strncpy(header->th_info.th_check, " ",
+ sizeof(header->th_info.th_check));
+ if (*link == '\0') {
+ header->th_info.th_islink = 0;
+ if (verbose)
+ if (args->t_size == 0)
+ putc('\n', stderr);
+ else
+ fprintf(stderr, " %ld block%s\n",
+ roundup(args->t_size, BUFSIZ),
+ args->t_size <= BUFSIZ ? "" : "s");
+ } else {
+ header->th_info.th_islink = '1';
+ if (verbose)
+ fprintf(stderr, " link to %s\n", link);
+ }
+ strncpy(header->th_info.th_link, link,
+ sizeof(header->th_info.th_link));
+ strncpy(header->th_info.th_pad, "",
+ sizeof(header->th_info.th_pad));
+ putoct(header->th_info.th_check,
+ checksum(header->th_data, sizeof(tarhd_t)));
+}
+
+void writefil(char *name, off_t size)
+{
+ int fd;
+ tarhd_t *header;
+
+ if ((fd = open(name, 0)) < 0) {
+ warn("Tar: 9: %s", name);
+ }
+ for (; size > 0; size -= sizeof(tarhd_t)) {
+ header = writeblk();
+ if (fd >= 0)
+ read(fd, header->th_data, sizeof(tarhd_t));
+ else
+ strncpy(header->th_data, "", sizeof(tarhd_t));
+ }
+ if (fd >= 0)
+ close(fd);
+}
+
+/*
+ * Tape I/O, with record blocking if specified
+ */
+
+tarhd_t buffer[MAXBLK], *current = &buffer[0];
+
+tarhd_t *readblk(void)
+{
+ static unsigned short blocks = 0;
+
+ if (feof(tarfile))
+ return (NULL);
+ if (current == &buffer[blocks]) {
+ current = &buffer[0];
+ blocks = fread((char *) buffer,
+ sizeof(tarhd_t), MAXBLK, tarfile);
+ if (ferror(tarfile)) {
+ err(1, "Tar: 10: %s", archive);
+ } else if (feof(tarfile)) {
+ /* This is a hack. We should actually find
+ * out why returning NULL is not good enough.
+ *
+ * Really a pain without a symbolic debugger!
+ */
+ exit(0);
+ }
+ }
+ return (current++);
+}
+
+void ungetblk(void)
+{
+ --current;
+}
+
+tarhd_t *writeblk(void)
+{
+ if (current == &buffer[blocking]) {
+ current = &buffer[0];
+ fwrite((char *) buffer, sizeof(tarhd_t), blocking,
+ tarfile);
+ if (ferror(tarfile) || feof(tarfile)) {
+ err(1, "Tar: 11: %s", archive);
+ }
+ }
+ return (current++);
+}
+
+void flushtar(void)
+{
+ tarhd_t *header;
+
+ while ((header = writeblk()) != &buffer[0])
+ strncpy(header->th_data, "", sizeof(tarhd_t));
+ if (linkmsg)
+ misslink();
+}
+
+/*
+ * Keep track of files by ino in hash table
+ */
+
+typedef struct link_t {
+ dev_t t_dev;
+ ino_t t_ino;
+ unsigned short t_nlink;
+ struct link_t *t_next;
+ char t_link[0];
+} link_t;
+
+#define NHASH 64
+
+link_t *linklist[NHASH];
+
+void filelink(dev_t dev, ino_t ino, unsigned short nlink, char *link)
+{
+ unsigned short id = ino % NHASH;
+ link_t *lp;
+
+ /* FIXME OVERFLOW ? */
+ lp = (link_t *) malloc(sizeof(link_t) + strlen(link) + 1);
+ if (lp == NULL)
+ fprintf(stderr, "Tar: %s: note: link info lost\n", link);
+ else {
+ lp->t_next = linklist[id];
+ linklist[id] = lp;
+ lp->t_dev = dev;
+ lp->t_ino = ino;
+ lp->t_nlink = nlink;
+ strcpy(lp->t_link, link);
+ }
+}
+
+char *havelink(dev_t dev, ino_t ino, flag_t flag)
+{
+ link_t *lp;
+
+ for (lp = linklist[ino % NHASH]; lp != NULL; lp = lp->t_next) {
+ if (lp->t_ino == ino && lp->t_dev == dev) {
+ if (flag)
+ --lp->t_nlink;
+ return (lp->t_link);
+ }
+ }
+ return (NULL);
+}
+
+void misslink(void)
+{
+ unsigned short ino;
+
+ for (ino = 0; ino < NHASH; ino++) {
+ link_t *lp;
+
+ for (lp = linklist[ino]; lp != NULL; lp = lp->t_next) {
+ short nlink;
+
+ if ((nlink = lp->t_nlink - 1) != 0)
+ fprintf(stderr,
+ "Tar: missed %d link%s to %s\n",
+ nlink, nlink == 1 ? "" : "s",
+ lp->t_link);
+ }
+ }
+}
+
+/*
+ * Read unsigned octal numbers
+ */
+
+int getoct(char *cp)
+{
+ int val = 0;
+ signed char c; /* Force signed to avoid SDCC bug */
+
+ while (*cp == ' ')
+ cp++;
+ while ((c = *cp++ - '0') >= 0 && c <= 7)
+ val = val << 3 | c;
+ return (val);
+}
+
+long getoctl(char *cp)
+{
+ long val = 0;
+ signed char c; /* Ditto */
+
+ while (*cp == ' ')
+ cp++;
+ while ((c = *cp++ - '0') >= 0 && c <= 7)
+ val = val << 3 | c;
+ return (val);
+}
+
+/*
+ * Write unsigned octal numbers
+ * in fixed format
+ */
+
+void putoct(char *str, unsigned short val)
+{
+ char *cp;
+
+ *(cp = &str[7]) = '\0';
+ *--cp = ' ';
+ *--cp = (val & 07) + '0';
+ while (cp != str)
+ *--cp = (val >>= 3) ? (val & 07) + '0' : ' ';
+}
+
+void putoctl(char *str, unsigned long val)
+{
+ char *cp;
+
+ *(cp = &str[11]) = ' ';
+ *--cp = (val & 07) + '0';
+ while (cp != str)
+ *--cp = (val >>= 3) ? (val & 07) + '0' : ' ';
+}
+
+/*
+ * Compute checksum of string
+ */
+int checksum(char *str, int len)
+{
+ int check = 0;
+
+ if (len)
+ do
+ check += *str++;
+ while (--len);
+ return (check);
+}
+
+/*
+ * Return -1 if dname is a directory prefix of fname
+ * 0 if no match
+ * 1 if complete match
+ */
+int contains(char *dname, char *fname)
+{
+ if (*dname == '\0')
+ return (-1);
+ while (*dname != '\0')
+ if (*dname++ != *fname++)
+ return (0);
+ if (*fname == '\0')
+ return (1);
+ else if (*fname == '/' || *--fname == '/')
+ return (-1);
+ else
+ return (0);
+}
+
+/* Handle the processing of a checksum error.
+ * This is special because sometimes end of archive will look like
+ * a lot of checksum errors.
+ */
+void do_checksum_error(char *name, int action)
+{
+#define MSG_SIZE (100 + sizeof("Tar: %.100s: bad checksum\n") + 1)
+#define MAX_CHECKERR 5 /* Give up after 5 consecutive checksum errors. */
+
+ static int error_count = 0; /* Number of checksums we've seen. */
+ static char *msg_list = NULL; /* Error message we've queued. */
+ char tmp_buf[MSG_SIZE]; /* Someplace to build new errors. */
+
+
+ switch (action) {
+ case ADD:
+ /* If there have been too many checksum errors, or if
+ * we can't allocate more memory for more error messages,
+ * throw away the existing messages, and look for another
+ * valid block.
+ */
+ if (++error_count > MAX_CHECKERR ||
+ (msg_list = (char *) realloc(msg_list,
+ strlen(msg_list) +
+ MSG_SIZE + 1)) == NULL) {
+ fprintf(stderr,
+ "Tar: %s: This doesn't look like a tar archive.\n",
+ archive);
+ free(msg_list);
+ msg_list = 0;
+ error_count = 0;
+ fprintf(stderr, "Tar: Scanning for next file.\n");
+ scan_for_next();
+ }
+
+ sprintf(tmp_buf, "Tar: %.100s: bad checksum\n", name);
+ strcat(msg_list, tmp_buf);
+ break;
+ case CLEAR:
+ if (NULL != msg_list) {
+ fprintf(stderr, "%s", msg_list);
+ free(msg_list);
+ msg_list = NULL;
+ }
+ error_count = 0;
+ break;
+ } /* switch (action) */
+} /* do_checksum_error() */
+
+/* Find the next valid block. */
+
+void scan_for_next(void)
+{
+ int check;
+ tarhd_t *header;
+
+ while ((header = readblk()) != NULL) {
+ check = getoct(header->th_info.th_check);
+
+ strncpy(header->th_info.th_check, " ",
+ sizeof(header->th_info.th_check));
+ if (checksum(header->th_data, sizeof(tarhd_t)) == check) {
+ /* Found a good header! */
+ ungetblk();
+ return;
+ }
+ } /* while (more blocks to read) */
+ /* If we got here, it means we ran out of things to read. */
+ fatal("no more valid blocks");
+
+} /* scan_for_next() */
--- /dev/null
+/*
+ * New, improved version of the "test" utility, hopefully P1003.2 compliant.
+ *
+ * "test" has represented a problem to implementors because of the possibility
+ * of aliasing between operators and operands; while it is possible to resolve
+ * this ambiguity with conventional tools, the resulting program is rather
+ * subtle and easily broken.
+ *
+ * The observation the underpins this implementation is that the "test" input
+ * is more accurately parsed /from the right/ than from the left. Because the
+ * last element of a form is always an operand rather than an operator, and
+ * because unary and binary primaries are lexically distinct, it is possible
+ * to create an unambiguous parse with a single right->left scan of the input.
+ *
+ * Parentheses are a unique problem, however. The easiest way to support them
+ * is to say that parentheses are matched only if they are not consumed by
+ * any operators, i.e. \( "a" \) would fail to match any operators and so the
+ * parenthesis would match.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define WRITESTR(s) write (2, s, strlen (s))
+
+enum {
+ SYNTAX_ERROR = 2
+};
+
+
+typedef int (*compare_p) (long left, long right);
+typedef int (*file_binop_p) (const struct stat * left,
+ const struct stat * right);
+typedef int (*log_binop_p) (int left, int right);
+typedef int (*string_unop_p) (char *arg);
+typedef int (*file_unop_p) (struct stat * stat);
+
+/*
+ * Miscellaneous comparison operators.
+ */
+
+#define CMP_OP2(name) static int name (long arg1, long arg2)
+
+CMP_OP2(cmp_eq)
+{
+ return arg1 != arg2;
+}
+
+CMP_OP2(cmp_neq)
+{
+ return arg1 == arg2;
+}
+
+CMP_OP2(cmp_lt)
+{
+ return arg1 >= arg2;
+}
+
+CMP_OP2(cmp_gt)
+{
+ return arg1 <= arg2;
+}
+
+CMP_OP2(cmp_le)
+{
+ return arg1 > arg2;
+}
+
+CMP_OP2(cmp_ge)
+{
+ return arg1 < arg2;
+}
+
+
+/*
+ * Miscellaneous file operators.
+ */
+
+#define FILE_BINOP(name) \
+ static int name (const struct stat * left, const struct stat * right)
+
+FILE_BINOP(file_eq)
+{
+ return left->st_dev != right->st_dev
+ || left->st_ino != right->st_ino;
+}
+
+FILE_BINOP(file_newer)
+{
+ return left->st_mtime <= right->st_mtime;
+}
+
+FILE_BINOP(file_older)
+{
+ return left->st_mtime >= right->st_mtime;
+}
+
+#define FILE_UNOP(name) static int name (const struct stat * stat)
+
+FILE_UNOP(file_exists)
+{
+ return 0;
+}
+
+FILE_UNOP(file_blockspecial)
+{
+ return (stat->st_mode & S_IFMT) != S_IFBLK;
+}
+
+FILE_UNOP(file_charspecial)
+{
+ return (stat->st_mode & S_IFMT) != S_IFCHR;
+}
+
+FILE_UNOP(file_directory)
+{
+ return (stat->st_mode & S_IFMT) != S_IFDIR;
+}
+
+FILE_UNOP(file_pipe)
+{
+ return (stat->st_mode & S_IFMT) != S_IFIFO;
+}
+
+FILE_UNOP(file_regular)
+{
+ return (stat->st_mode & S_IFMT) != S_IFREG;
+}
+
+FILE_UNOP(file_setgid)
+{
+ return (stat->st_mode & S_ISGID) == 0;
+}
+
+FILE_UNOP(file_setuid)
+{
+ return (stat->st_mode & S_ISUID) == 0;
+}
+
+FILE_UNOP(file_sticky)
+{
+ return (stat->st_mode & S_ISVTX) == 0;
+}
+
+FILE_UNOP(file_linked)
+{
+ return stat->st_nlink == 0;
+}
+
+FILE_UNOP(file_nonempty)
+{
+ return stat->st_size == 0;
+}
+
+/*
+ * Miscellaneous string operators.
+ */
+
+#define STRING_UNOP(name) static int name (const char * str)
+
+STRING_UNOP(string_empty)
+{
+ return str[0] != 0;
+}
+
+STRING_UNOP(string_nonempty)
+{
+ return str[0] == 0;
+}
+
+STRING_UNOP(strfile_istty)
+{
+ return !isatty(atoi(str));
+}
+
+STRING_UNOP(strfile_readable)
+{
+ return access(str, R_OK);
+}
+
+STRING_UNOP(strfile_writeable)
+{
+ return access(str, W_OK);
+}
+
+STRING_UNOP(strfile_executable)
+{
+ return access(str, X_OK);
+}
+
+/*
+ * Declare and fill in some tables of function names and pointers to the
+ * implementations.
+ */
+
+struct op {
+ const char *op_name;
+ void *op_func;
+};
+#define OP(name,func) { name, (void *)func }
+
+const struct op string_binop[] = {
+ OP("=", cmp_eq), OP("!=", cmp_neq), OP("<", cmp_lt),
+ OP(">", cmp_gt), OP("<=", cmp_le), OP(">=", cmp_ge)
+};
+
+const struct op arith_binop[] = {
+ OP("-eq", cmp_eq), OP("-ne", cmp_neq), OP("-lt", cmp_lt),
+ OP("-gt", cmp_gt), OP("-le", cmp_le), OP("-ge", cmp_ge)
+};
+
+const struct op file_binop[] = {
+ OP("-ef", file_eq), OP("-nt", file_newer), OP("-ot", file_older)
+};
+
+const struct op string_unop[] = {
+ OP("-z", string_empty), OP("-n", string_nonempty),
+ OP("-t", strfile_istty), OP("-r", strfile_readable),
+ OP("-w", strfile_writeable), OP("-x", strfile_executable)
+/* OP ("!", string_empty) */
+};
+
+const struct op file_unop[] = {
+ OP("-b", file_blockspecial), OP("-c", file_charspecial),
+ OP("-d", file_directory), OP("-p", file_pipe),
+ OP("-f", file_regular), OP("-g", file_setgid),
+ OP("-u", file_setuid), OP("-K", file_sticky),
+ OP("-L", file_linked), OP("-s", file_nonempty),
+ OP("-e", file_exists)
+};
+
+#define FIND_OP(table,str) find_op (sizeof (table) / sizeof (* table),\
+ table, str)
+#define is_string_binop(str) ((compare_p) FIND_OP (string_binop, str))
+#define is_arith_binop(str) ((compare_p) FIND_OP (arith_binop, str))
+#define is_file_binop(str) ((file_binop_p) FIND_OP (file_binop, str))
+#define is_string_unop(str) ((string_unop_p) FIND_OP (string_unop, str))
+#define is_file_unop(str) ((file_unop_p) FIND_OP (file_unop, str))
+
+/*
+ * Generic operator table search routine.
+ */
+
+static void *find_op(int size, const struct op *table, const char *str)
+{
+ do
+ if (strcmp(table->op_name, str) == 0)
+ return table->op_func;
+ while (table++, --size > 0);
+
+ return NULL;
+}
+
+
+/*
+ * Convert an operand of a numeric expression into a number. The rules for
+ * how strict the conversion to integer is are not clear.
+ *
+ * We return 0 on success, non-zero on failure.
+ */
+
+int convert_number(const char *string, long *numberp)
+{
+ char *cp;
+
+ *numberp = strtol(string, &cp, 0);
+
+ /* If strtol failed, it leaves cp at start of str. */
+ /* Empty arg "" represents 0 for Unix compatability. */
+ return (string == cp) ? (*cp != 0) : 0;
+}
+
+enum {
+ NO_PAREN,
+ IN_PAREN
+};
+
+int test_boolor(int argc, char *argv[], int *matchedp, int paren);
+
+
+/*
+ * Process a non-empty element of a test-expression (see test () below).
+ * The fundamental premise of this code is that argv [argc - 1] is an
+ * operand, and thus that argv [argc - 2] is an operator of some form.
+ */
+
+int test_primop(int argc, const char *argv[], int *matchedp, int paren)
+{
+ int result;
+ int operator_flag = 1;
+
+ if (argc < 1) {
+ return SYNTAX_ERROR; /* syntax error in subexpr */
+ }
+
+ /*
+ * Begin by looking for binary operator at argv [argc - 2].
+ * String operators only match if we have 3 arguments, and the
+ * same is true of arithmetic operators.
+ */
+
+ if (argc >= 3) {
+ compare_p cmp;
+ file_binop_p file;
+
+ *matchedp = 3;
+
+ if ((cmp = is_string_binop(argv[argc - 2])) != NULL) {
+ result = (*cmp) (strcmp(argv[argc - 3],
+ argv[argc - 1]), 0);
+ goto negate;
+ }
+ if ((cmp = is_arith_binop(argv[argc - 2])) != NULL) {
+ long left;
+ long right;
+
+ if (convert_number(argv[argc - 3], &left) == 0 &&
+ convert_number(argv[argc - 1], &right) == 0) {
+
+ result = (*cmp) (left, right);
+ goto negate;
+ }
+
+ return SYNTAX_ERROR;
+ }
+
+ if ((file = is_file_binop(argv[argc - 2])) != NULL) {
+ struct stat left;
+ struct stat right;
+
+ if (stat(argv[argc - 3], &left) < 0 ||
+ stat(argv[argc - 1], &right) < 0)
+ result = 1;
+ else
+ result = (*file) (&left, &right);
+ goto negate;
+ }
+ }
+
+ if (argc >= 2) {
+ string_unop_p string;
+ file_unop_p file;
+
+ *matchedp = 2;
+
+ if ((string = is_string_unop(argv[argc - 2])) != NULL) {
+ result = (*string) (argv[argc - 1]);
+ goto negate;
+ }
+ if ((file = is_file_unop(argv[argc - 2])) != NULL) {
+ struct stat statbuf;
+
+ if (stat(argv[argc - 1], &statbuf) < 0)
+ result = 1;
+ else
+ result = (*file) (&statbuf);
+ goto negate;
+ }
+ }
+
+ not_really_operator:
+ operator_flag = 0;
+
+ if (argc > 2 && strcmp(argv[argc - 1], ")") == 0 &&
+ (result = test_boolor(argc - 1, argv, matchedp,
+ IN_PAREN)) != SYNTAX_ERROR &&
+ argc - 1 > *matchedp &&
+ strcmp(argv[argc - 2 - *matchedp], "(") == 0)
+ (*matchedp) += 2;
+ else {
+ *matchedp = 1;
+ result = argv[argc - 1][0] == 0;
+ }
+
+ negate:
+ /*
+ * Try and extend the subexpression on the right by looking
+ * for negations.
+ */
+
+ while (argc > *matchedp) {
+ char *next = argv[argc - 1 - *matchedp];
+
+ if (strcmp(next, "!") == 0) {
+ (*matchedp)++;
+ result = !result;
+ continue;
+ }
+
+ /*
+ * We now have encountered something on the left... if it is
+ * a conjunction or disjunction operator, we are OK, otherwise
+ * we have hit a syntax error. If we got here by matching an
+ * operator, we can try matching a parenthesis and/or string
+ * as a fallback.
+ */
+
+ if (strcmp(next, "-a") != 0 && strcmp(next, "-o") != 0 &&
+ (paren != IN_PAREN || strcmp(next, "(") != 0) &&
+ operator_flag)
+ goto not_really_operator;
+
+ break;
+ }
+
+ return result;
+}
+
+
+/*
+ * Process an optional sequence of boolean conjunctions. This is separated
+ * from disjunction because conjunction has higher "precedence".
+ */
+
+int test_booland(int argc, const char *argv[], int *matchedp, int paren)
+{
+ int right = 0; /* "true" */
+
+ *matchedp = 0;
+
+ /*
+ * Match a sequence of the form:
+ * and_expr = and_expr AND prim_expr
+ * | prim_expr ;
+ * Note that this grammar matches the definition of "and" as left-
+ * associative, but in fact since conjunction is commutative there is
+ * no great magic to this and we right-associate instead.
+ */
+
+ for (;;) {
+ int left;
+ int matched;
+
+ if ((left = test_primop(argc, argv, &matched,
+ paren)) == SYNTAX_ERROR) {
+ return SYNTAX_ERROR;
+ }
+
+ right = right || left; /* "and" */
+ *matchedp += matched;
+ argc -= matched;
+
+ if (argc < 2 || strcmp(argv[argc - 1], "-a") != 0)
+ return right;
+
+ (*matchedp)++;
+ argc--;
+ }
+}
+
+
+/*
+ * Process an optional sequence of boolean disjunctions.
+ */
+
+int test_boolor(int argc, const char *argv[], int *matchedp, int paren)
+{
+ int right = 1; /* "false" */
+
+ *matchedp = 0;
+
+ /*
+ * Match an expression of the form:
+ * or_expr = or_expr OR and_expr
+ * | and_expr ;
+ * Note that this grammar matches the definition of "or" as being
+ * left-associative, but since disjunction is commutative this does
+ * not really matter and we actually associate to the right.
+ */
+
+ for (;;) {
+ int left;
+ int matched;
+
+ if ((left = test_booland(argc, argv, &matched,
+ paren)) == SYNTAX_ERROR) {
+ return SYNTAX_ERROR;
+ }
+
+ (*matchedp) += matched;
+ argc -= matched;
+ right = right && left; /* "or" */
+
+ if (argc < 2 || strcmp(argv[argc - 1], "-o") != 0)
+ return right;
+
+ (*matchedp)++;
+ argc--;
+ }
+}
+
+
+/*
+ * Process a test-expression. The "argc" argument specifies the number of
+ * elements in the argument-vector "argv", where each element is a string.
+ * Note that unlike main (), the first element of argv [] is a real
+ * argument.
+ */
+
+int test(int argc, char *argv[])
+{
+ int matched;
+ int value;
+
+ /*
+ * Special cases to allow some simple tests. All these are available
+ * other ways, but they are historically significant.
+ */
+
+ if (argc == 0)
+ return 1; /* false */
+
+#if 0
+ if (argc == 1)
+ return argv[0][0] == 0; /* => -n <arg> */
+#endif
+
+ if ((value = test_boolor(argc, argv, &matched,
+ NO_PAREN)) == SYNTAX_ERROR ||
+ matched != argc) {
+ return SYNTAX_ERROR;
+ }
+
+ return value;
+}
+
+
+#if ! _SHELL
+
+#define WRITECONST(s) write (2, s, sizeof (s))
+
+void error(char **argv, const char *str)
+{
+ WRITESTR(*argv);
+ WRITECONST(" ");
+
+ while (*++argv != NULL) {
+ WRITESTR(*argv);
+ WRITECONST(" ");
+ }
+ WRITECONST(": ");
+ WRITESTR(str);
+ WRITECONST("\n");
+
+#if 0
+ WRITECONST("Unary primaries:\n"
+ "\t-b file\t\tfile exists and is a block special file\n"
+ "\t-c file\t\tfile exists and is a character special file\n"
+ "\t-d file\t\tfile exists and is a directory\n"
+ "\t-e file\t\tfile exists\n"
+ "\t-f file\t\tfile exists and is a regular file\n"
+ "\t-g file\t\tfile exists and is setgid\n"
+ "\t-k file\t\tfile exists and has sticky bit set\t(not Posix)\n"
+ "\t-L file\t\tfile is a link\t\t\t\t(not Posix)\n"
+ "\t-n string\tstring length is nonzero\n"
+ "\t-p file\t\tfile exists and is a named pipe (FIFO)\n"
+ "\t-r file\t\tfile exists and is readable\n"
+ "\t-s file\t\tfile exists and has nonzero size\n"
+ "\t-t fd\t\tfd is the file descriptor of a terminal\n"
+ "\t-u file\t\tfile exists and is setuid\n"
+ "\t-w file\t\tfile exists and is writable\n"
+ "\t-x file\t\tfile exists and is executable\n"
+ "\t-z string\tstring length is zero\n"
+ "\tstring\t\tstring is not the empty string\n");
+ WRITECONST("Binary primaries:\n"
+ "\ts1 = s2\t\tstrings s1 and s2 are identical\n"
+ "\ts1 != s2\tstrings s1 and s2 are not identical\n"
+ "\ts1 < s2\t\tstring s1 is less than s2\t\t(not Posix)\n"
+ "\ts1 > s2\t\tstring s1 is greater than s2\t\t(not Posix)\n"
+ "\tfile1 -ef file2\tfile1 and file2 are identical\t\t(not Posix)\n"
+ "\tn1 -eq n2\tnumbers n1 and n2 are equal\n"
+ "\tn1 -ge n2\tnumber n1 is greater than or equal to n2\n"
+ "\tn1 -gt n2\tnumber n1 is greater than n2\n"
+ "\tn1 -le n2\tnumber n1 is less than or equal to n2\n"
+ "\tn1 -lt n2\tnumber n1 is less than n2\n"
+ "\tn1 -ne n2\tnumbers n1 and n2 are not equal\n"
+ "\tfile1 -nt file2\tfile1 is newer than file2\t\t(not Posix)\n"
+ "\tfile1 -ot file2\tfile1 is older than file2\t\t(not Posix)\n");
+ WRITECONST("Expression grouping:\n"
+ "\t! exp\t\texp is false\n"
+ "\texp1 -a exp2\texp1 and exp2 are true\t\t\t(not Posix)\n"
+ "\texp1 -o exp2\texp1 or exp2 is true\t\t\t(not Posix)\n"
+ "\t( exp )\t\tparentheses for grouping\t\t(not Posix)\n");
+#endif
+
+ exit(2);
+}
+
+
+int main(int argc, char *argv[])
+{
+ /*
+ * Let's splurge and use a variable for arg 0 length.
+ * A small price to pay for clarity and correctness.
+ */
+
+ int arg0len;
+
+ /*
+ * The following comment added by Hal. Keep it here.
+ *
+ * If argv[0] ends with '[', look for matching ']'.
+ * If we just string match "[", it's hard to debug things like
+ * /tmp/foo/[ xxx yyy zzz ].
+ */
+
+ arg0len = strlen(argv[0]);
+
+ if (argv[0][arg0len - 1] == '[') {
+ if (strcmp(argv[argc - 1], "]") != 0)
+ error(argv, "Missing ']'");
+
+ /* Discard trailing ']'. */
+
+ argc--;
+ }
+
+ if ((argc = test(argc - 1, argv + 1)) == 2)
+ error(argv, "syntax error");
+
+ return argc;
+}
+
+#endif /* ! _SHELL */
--- /dev/null
+/*
+ * ttt -- 3-dimensional tic-tac-toe.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define EMPTY 0
+#define MAN 1
+#define BEAST 5
+
+/*
+ * This is a table of all winning
+ * combinations.
+ * I stole it out of Kilobaud, April
+ * 78.
+ * You can look there to see how it
+ * is ordered.
+ */
+const char w[] = {
+ 0, 1, 2, 3,
+ 4, 5, 6, 7,
+ 8, 9, 10, 11,
+ 12, 13, 14, 15,
+ 0, 4, 8, 12,
+ 1, 5, 9, 13,
+ 2, 6, 10, 14,
+ 3, 7, 11, 15,
+ 0, 5, 10, 15,
+ 3, 6, 9, 12,
+ 16, 17, 18, 19,
+ 20, 21, 22, 23,
+ 24, 25, 26, 27,
+ 28, 29, 30, 31,
+ 16, 20, 24, 28,
+ 17, 21, 25, 29,
+ 18, 22, 26, 30,
+ 19, 23, 27, 31,
+ 16, 21, 26, 31,
+ 19, 22, 25, 28,
+ 32, 33, 34, 35,
+ 36, 37, 38, 39,
+ 40, 41, 42, 43,
+ 44, 45, 46, 47,
+ 32, 36, 40, 44,
+ 33, 37, 41, 45,
+ 34, 38, 42, 46,
+ 35, 39, 43, 47,
+ 32, 37, 42, 47,
+ 35, 38, 41, 44,
+ 48, 49, 50, 51,
+ 52, 53, 54, 55,
+ 56, 57, 58, 59,
+ 60, 61, 62, 63,
+ 48, 52, 56, 60,
+ 49, 53, 57, 61,
+ 50, 54, 58, 62,
+ 51, 55, 59, 63,
+ 48, 53, 58, 63,
+ 51, 54, 57, 60,
+ 0, 16, 32, 48,
+ 1, 17, 33, 49,
+ 2, 18, 34, 50,
+ 3, 19, 35, 51,
+ 4, 20, 36, 52,
+ 5, 21, 37, 53,
+ 6, 22, 38, 54,
+ 7, 23, 39, 55,
+ 8, 24, 40, 56,
+ 9, 25, 41, 57,
+ 10, 26, 42, 58,
+ 11, 27, 43, 59,
+ 13, 29, 45, 61,
+ 12, 28, 44, 60,
+ 14, 30, 46, 62,
+ 15, 31, 47, 63,
+ 0, 21, 42, 63,
+ 4, 21, 38, 55,
+ 8, 25, 42, 59,
+ 12, 25, 38, 51,
+ 1, 21, 41, 61,
+ 13, 25, 37, 49,
+ 2, 22, 42, 62,
+ 14, 26, 38, 50,
+ 3, 22, 41, 60,
+ 7, 22, 37, 52,
+ 11, 26, 41, 56,
+ 15, 26, 37, 48,
+ 0, 20, 40, 60,
+ 0, 17, 34, 51,
+ 3, 18, 33, 48,
+ 3, 23, 43, 63,
+ 12, 24, 36, 48,
+ 12, 29, 46, 63,
+ 15, 30, 45, 60,
+ 15, 27, 39, 51
+};
+
+char b[64];
+const char sep[] = "----------- ----------- ----------- -----------";
+
+
+int yes(const char *s)
+{
+ char b[20];
+
+ for (;;) {
+ printf("%s? ", s);
+ if (fgets(b, 20, stdin) == NULL)
+ exit(0);
+ if (b[0] == 'y')
+ return (1);
+ if (b[0] == 'n')
+ return (0);
+ printf("Answer `yes' or `no'.\n");
+ }
+}
+
+void rules(void)
+{
+ printf("Three dimensional tic-tac-toe is played on a 4x4x4\n");
+ printf("board. To win you must get 4 in a row. Your moves\n");
+ printf("are specified as a 3 digit number; the first digit\n");
+ printf("is the level, the second the row and the third the\n");
+ printf("column. Levels and columns go from left to right\n");
+ printf("from 0 to 3. Rows go from top to bottom with 0 on\n");
+ printf("the top.\n");
+}
+
+void psq(int s)
+{
+ int v;
+
+ v = b[s];
+ if (v == MAN)
+ printf("UU");
+ else if (v == BEAST)
+ printf("CC");
+ else
+ printf(" ");
+}
+
+void board(void)
+{
+ int i, j;
+
+ for (i = 0; i < 4; ++i) {
+ if (i != 0)
+ puts(sep);
+ for (j = 0; j < 64; j += 4) {
+ psq(i + j);
+ if (j == 12 || j == 28 || j == 44)
+ putchar(' ');
+ else if (j >= 60)
+ putchar('\n');
+ else
+ putchar('!');
+ }
+ }
+}
+
+void man(void)
+{
+ int i, j, t;
+ char buf[20];
+
+ board();
+ for (;;) {
+ if (gets(buf) == NULL)
+ exit(0);
+ i = 16 * (buf[0] - '0') + (buf[1] - '0') + 4 * (buf[2] -
+ '0');
+ if (i >= 0 && i <= 63 && b[i] == EMPTY)
+ break;
+ printf("?\n");
+ }
+ b[i] = MAN;
+ for (i = 0; i < 4 * 76; i += 4) {
+ t = 0;
+ for (j = 0; j < 4; ++j)
+ t += b[w[i + j]];
+ if (t == 4 * MAN) {
+ printf("You win.\n");
+ exit(0);
+ }
+ }
+}
+
+int weight(int t)
+{
+ if (t == MAN)
+ return (1);
+ if (t == 2 * MAN)
+ return (4);
+ if (t == BEAST)
+ return (1);
+ if (t == 2 * BEAST)
+ return (2);
+ return (0);
+}
+
+
+void beast(void)
+{
+ int i, j, t;
+ int s, bs, bt, v[76];
+
+ for (i = 0; i < 4 * 76; i += 4) {
+ t = 0;
+ for (j = 0; j < 4; ++j)
+ t += b[w[i + j]];
+ v[i >> 2] = t;
+ if (t == 3 * BEAST)
+ break;
+ }
+ if (i < 4 * 76) {
+ for (j = 0; j < 4; ++j)
+ if (b[w[i + j]] == EMPTY) {
+ b[w[i + j]] = BEAST;
+ break;
+ }
+ board();
+ printf("I win.\n");
+ exit(0);
+ }
+ bt = 0;
+ for (s = 0; s < 64; ++s) {
+ if (b[s] != EMPTY)
+ continue;
+ t = 0;
+ for (i = 0; i < 4 * 76; i += 4) {
+ for (j = 0; j < 4; ++j)
+ if (w[i + j] == s)
+ break;
+ if (j != 4) {
+ if (v[i >> 2] == 3 * MAN) {
+ b[s] = BEAST;
+ return;
+ }
+ t += weight(v[i >> 2]);
+ }
+ }
+ if (t > bt) {
+ bt = t;
+ bs = s;
+ }
+ }
+ if (bt != 0)
+ b[bs] = BEAST;
+ else {
+ for (s = 0; s < 64; ++s)
+ if (b[s] == EMPTY)
+ break;
+ if (s == 64) {
+ printf("Draw.\n");
+ exit(0);
+ }
+ b[s] = BEAST;
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ if (yes("Print rules"))
+ rules();
+ if (yes("Play first"))
+ man();
+ for (;;) {
+ beast();
+ man();
+ }
+}