_h’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’ü’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’ķA0“Œ:!ķA0“Œ:!¤>“Œ:! ¤#“Œ:!¤ “Œ:!¤ “Œ:! ¤Ė “Œ:!!"#¤0“Œ:!$%&'()*,¤šÉ“Œ:!2345678:¤““Œ:!fghij¤¢&“Œ:!klmnopqs¤² “Œ:!vwxy¤¾“Œ:!z¤s8“Œ:!{|}~€ƒ¤“Œ:!‹ŒŽ¤A “Œ:!‘’“¤“Œ:!”•ķA`“Œ:!–¤‡“Œ:!—˜™š›œ¤ķ“Œ:!žŸ ”¢£¤¦¤2ƓŒ:!ŪÜŻŽßąį椁„”Œ:!ķA “Œ:! ¤-“Œ:!    ¤I8“Œ:!¤Ł,“Œ:!#$%&'()+¤éA“Œ:!01234568¤é/“Œ:!BCDEFGHJ¤}*“Œ:!OPQRSTUW¤\“Œ:![¤¢•Œ:!\’ź!...commands...cal.ccpdir.cdiff.cdu.cexpr.cfind.c make.c more.c fdisk.c fix.c printenv.creadfs.ctest.cuudecode.cuuencode.cminedsh/* cal - print a calendar Author: Maritn Minow */ #include "../include/stdio.h" #define do3months domonth #define IO_SUCCESS 0 /* Unix definitions */ #define IO_ERROR 1 #define EOS 0 #define ENTRY_SIZE 3 /* 3 bytes per value */ #define DAYS_PER_WEEK 7 /* Sunday, etc. */ #define WEEKS_PER_MONTH 6 /* Max. weeks in a month */ #define MONTHS_PER_LINE 3 /* Three months across */ #define MONTH_SPACE 3 /* Between each month */ char *badarg = {"Bad argument\n"}; char *how = {"Usage: cal [month] year\n"}; /* * calendar() stuffs data into layout[], * output() copies from layout[] to outline[], (then trims blanks). */ char layout[MONTHS_PER_LINE][WEEKS_PER_MONTH][DAYS_PER_WEEK][ENTRY_SIZE]; char outline[(MONTHS_PER_LINE * DAYS_PER_WEEK * ENTRY_SIZE) + (MONTHS_PER_LINE * MONTH_SPACE) + 1]; char *weekday = " S M Tu W Th F S"; char *monthname[] = { "???", /* No month 0 */ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; main(argc, argv) int argc; char *argv[]; { register int month; register int year; register int arg1val; int arg1len; int arg2val; if (argc <= 1) { usage(how); } else { arg1val = atoi(argv[1]); arg1len = strlen(argv[1]); if (argc == 2) { /* * Only one argument, if small, it's a month. If * large, it's a year. Note: * cal 0082 Year 0082 * cal 82 Year 0082 */ if (arg1len <= 2 && arg1val <= 12) do3months(year, arg1val); else doyear(arg1val); } else { /* * Two arguments, allow 1980 12 or 12 1980 */ arg2val = atoi(argv[2]); if (arg1len > 2) do3months(arg1val, arg2val); else do3months(arg2val, arg1val); } } exit(IO_SUCCESS); } doyear(year) int year; /* * Print the calendar for an entire year. */ { register int month; if (year < 1 || year > 9999) usage(badarg); if (year < 100) printf("\n\n\n 00%2d\n\n", year); else printf("\n\n\n%35d\n\n", year); for (month = 1; month <= 12; month += MONTHS_PER_LINE) { printf("%12s%23s%23s\n", monthname[month], monthname[month+1], monthname[month+2]); printf("%s %s %s\n", weekday, weekday, weekday); calendar(year, month+0, 0); calendar(year, month+1, 1); calendar(year, month+2, 2); output(3); #if MONTHS_PER_LINE != 3 << error, the above won't work >> #endif } printf("\n\n\n"); } domonth(year, month) int year; int month; /* * Do one specific month -- note: no longer used */ { if (year < 1 || year > 9999) usage(badarg); if (month <= 0 || month > 12) usage(badarg); printf("%9s%5d\n\n%s\n", monthname[month], year, weekday); calendar(year, month, 0); output(1); printf("\n\n"); } output(nmonths) int nmonths; /* Number of months to do */ /* * Clean up and output the text. */ { register int week; register int month; register char *outp; int i; char tmpbuf[21], *p; for (week = 0; week < WEEKS_PER_MONTH; week++) { outp = outline; for (month = 0; month < nmonths; month++) { /* * The -1 in the following removes * the unwanted leading blank from * the entry for Sunday. */ p = &layout[month][week][0][1]; for (i=0; i<20; i++) tmpbuf[i] = *p++; tmpbuf[20] = 0; sprintf(outp, "%s ", tmpbuf); outp += (DAYS_PER_WEEK * ENTRY_SIZE) + MONTH_SPACE - 1; } while (outp > outline && outp[-1] == ' ') outp--; *outp = EOS; puts(outline); } } calendar(year, month, index) int year; int month; int index; /* Which of the three months */ /* * Actually build the calendar for this month. */ { register char *tp; int week; register int wday; register int today; setmonth(year, month); for (week = 0; week < WEEKS_PER_MONTH; week++) { for (wday = 0; wday < DAYS_PER_WEEK; wday++) { tp = &layout[index][week][wday][0]; *tp++ = ' '; today = getdate(week, wday); if (today <= 0) { *tp++ = ' '; *tp++ = ' '; } else if (today < 10) { *tp++ = ' '; *tp = (today + '0'); } else { *tp++ = (today / 10) + '0'; *tp = (today % 10) + '0'; } } } } usage(s) char *s; { /* Fatal parameter error. */ fprintf(stderr, "%s", s); exit(IO_ERROR); } /* * Calendar routines, intended for eventual porting to TeX * * date(year, month, week, wday) * Returns the date on this week (0 is first, 5 last possible) * and day of the week (Sunday == 0) * Note: January is month 1. * * setmonth(year, month) * Parameters are as above, sets getdate() for this month. * * int * getdate(week, wday) * Parameters are as above, uses the data set by setmonth() */ /* * This structure is used to pass data between setmonth() and getdate(). * It needs considerable expansion if the Julian->Gregorian change is * to be extended to other countries. */ static struct { int this_month; /* month number used in 1752 checking */ int feb; /* Days in February for this month */ int sept; /* Days in September for this month */ int days_in_month; /* Number of days in this month */ int dow_first; /* Day of week of the 1st day in month */ } info; static int day_month[] = { /* 30 days hath September... */ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; int date(year, month, week, wday) int year; /* Calendar date being computed */ int month; /* January == 1 */ int week; /* Week in the month 0..5 inclusive */ int wday; /* Weekday, Sunday == 0 */ /* * Return the date of the month that fell on this week and weekday. * Return zero if it's out of range. */ { setmonth(year, month); return (getdate(week, wday)); } setmonth(year, month) int year; /* Year to compute */ int month; /* Month, January is month 1 */ /* * Setup the parameters needed to compute this month * (stored in the info structure). */ { register int i; if (month < 1 || month > 12) { /* Verify caller's parameters */ info.days_in_month = 0; /* Garbage flag */ return; } info.this_month = month; /* used in 1752 checking */ info.dow_first = Jan1(year); /* Day of January 1st for now */ info.feb = 29; /* Assume leap year */ info.sept = 30; /* Assume normal year */ /* * Determine whether it's an ordinary year, a leap year * or the magical calendar switch year of 1752. */ switch ((Jan1(year + 1) + 7 - info.dow_first) % 7) { case 1: /* Not a leap year */ info.feb = 28; case 2: /* Ordinary leap year */ break; default: /* The magical moment arrives */ info.sept = 19; /* 19 days hath September */ break; } info.days_in_month = (month == 2) ? info.feb : (month == 9) ? info.sept : day_month[month]; for (i = 1; i < month; i++) { switch (i) { /* Special months? */ case 2: /* February */ info.dow_first += info.feb; break; case 9: info.dow_first += info.sept; break; default: info.dow_first += day_month[i]; break; } } info.dow_first %= 7; /* Now it's Sunday to Saturday */ } int getdate(week, wday) int week; int wday; { register int today; /* * Get a first guess at today's date and make sure it's in range. */ today = (week * 7) + wday - info.dow_first + 1; if (today <= 0 || today > info.days_in_month) return (0); else if (info.sept == 19 && info.this_month == 9 && today >= 3) /* The magical month? */ return (today + 11); /* If so, some dates changed */ else /* Otherwise, */ return (today); /* Return the date */ } static int Jan1(year) int year; /* * Return day of the week for Jan 1 of the specified year. */ { register int day; day = year + 4 + ((year + 3) / 4); /* Julian Calendar */ if (year > 1800) { /* If it's recent, do */ day -= ((year - 1701) / 100); /* Clavian correction */ day += ((year - 1601) / 400); /* Gregorian correction */ } if (year > 1752) /* Adjust for Gregorian */ day += 3; /* calendar */ return (day % 7); } /* cpdir - copy directory Author: Erik Baalbergen */ /* Use "cpdir [-v] src dst" to make a copy dst of directory src. Cpdir should behave like the UNIX shell command (cd src; tar cf - .) | (mkdir dst; cd dst; tar xf -) but the linking structure of the tree, and the owner and time information of files are not yet preserved. (See the work-yet-to-be-done list below.) The -v "verbose" flag enables you to see what's going on when running cpdir. Work yet to be done: - preserve link structure, times, etc... - 'stat' optimization (stat() is invoked twice for normal files) - link checks, i.e. am I not overwriting a file/directory by itself? * has been solved by letting 'cpdir' create the target directory - handle character and block special files * they're simply not copied Please report bugs and suggestions to erikb@cs.vu.nl */ #include "stdio.h" #define MKDIR1 "/bin/mkdir" #define MKDIR2 "/usr/bin/mkdir" #ifdef UNIX #include #include #else !UNIX #include "stat.h" #endif #define BUFSIZE 1024 #define PLEN 256 #define DIRSIZ 16 char *prog; int vflag = 0; /* verbose */ char *strcpy(); main(argc, argv) char *argv[]; { int rc = 0; char *p, *s; prog = *argv++; if ((p = *argv) && *p == '-') { argv++; argc--; while (*++p) { switch (*p) { case 'v': vflag = 1; break; default: fatal("illegal flag %s", p); } } } if (argc != 3) fatal("Usage: cpdir [-v] source destination"); if (ftype(s = *argv++) != S_IFDIR) fatal("%s is not a directory", s); cpdir(s, *argv); exit(0); } cpdir(s, d) char *s, *d; { char spath[PLEN], dpath[PLEN]; char ent[DIRSIZ + 1]; register char *send = spath, *dend = dpath; int fd, n; while (*send++ = *s++) {} send--; while (*dend++ = *d++) {} if ((fd = open(spath, 0)) < 0) { nonfatal("can't read directory %s", spath); return; } *send++ = '/'; ent[DIRSIZ] = '\0'; mkdir(dpath); dend[-1] = '/'; while ((n = read(fd, ent, DIRSIZ)) == DIRSIZ) { if (!((ent[0] == '\0' && ent[1] == '\0') || (ent[2] == '.') && (ent[3] == '\0' || (ent[3] == '.' && ent[4] == '\0')) )) { strcpy(send, ent + 2); strcpy(dend, ent + 2); switch (ftype(spath)) { case S_IFDIR: cpdir(spath, dpath); break; case S_IFREG: cp(spath, dpath); break; default: nonfatal("can't copy special file %s", spath); } } } close(fd); if (n) fatal("error in reading directory %s", spath); } mkdir(s) char *s; { int pid, status; if (vflag) printf("mkdir %s\n", s); if ((pid = fork()) == 0) { execl(MKDIR1, "mkdir", s, (char *)0); execl(MKDIR2, "mkdir", s, (char *)0); fatal("can't execute %s or %s", MKDIR1, MKDIR2); } if (pid == -1) fatal("can't fork", prog); wait(&status); if (status) fatal("can't create %s", s); } cp(s, d) char *s, *d; { struct stat st; char buf[BUFSIZE]; int sfd, dfd, n; if (vflag) printf("cp %s %s\n", s, d); if ((sfd = open(s, 0)) < 0) nonfatal("can't read %s", s); else { if (fstat(sfd, &st) < 0) fatal("can't get file status of %s", s); if ((dfd = creat(d, st.st_mode & 0777)) < 0) fatal("can't create %s", d); while ((n = read(sfd, buf, BUFSIZE)) > 0) write(dfd, buf, n); close(sfd); close(dfd); if (n) fatal("error in reading file %s", s); } } ftype(s) char *s; { struct stat st; if (stat(s, &st) < 0) fatal("can't get file status of %s", s); return st.st_mode & S_IFMT; } nonfatal(s, a) char *s, *a; { fprintf(stderr, "%s: ", prog); fprintf(stderr, s, a); fprintf(stderr, "\n"); } fatal(s, a) char *s, *a; { nonfatal(s, a); exit(1); } /* diff - print differences between 2 files Author: Erik Baalbergen */ /* Poor man's implementation of diff(1) - no options available - may give more output than other diffs, due to the straight-forward algorithm - runs out of memory if the differing chunks become too large - input line length should not exceed LINELEN; longer lines are truncated, while only the first LINELEN characters are compared Please report bugs and suggestions to erikb@cs.vu.nl */ #include "stdio.h" FILE *fopen(); #define LINELEN 128 char *prog; int diffs = 0; main(argc, argv) char **argv; { FILE *fp1 = NULL, *fp2 = NULL; prog = *argv++; if (argc != 3) fatal("use: %s file1 file2", prog); if (strcmp(argv[0], "-") == 0) fp1 = stdin; else if (strcmp(argv[1], "-") == 0) fp2 = stdin; if (fp1 == NULL && (fp1 = fopen(argv[0], "r")) == NULL) fatal("can't read %s", argv[0]); if (fp2 == NULL && (fp2 = fopen(argv[1], "r")) == NULL) fatal("can't read %s", argv[1]); diff(fp1, fp2); exit(diffs > 0); } fatal(fmt, s) char *fmt, *s; { fprintf(stderr, "%s: ", prog); fprintf(stderr, fmt, s); fprintf(stderr, "\n"); exit(2); } /* the line module */ char *malloc(); char *fgets(); struct line { struct line *l_next; char l_text[LINELEN + 2]; }; struct line *freelist = 0; struct line * new_line() { register struct line *l; if (l = freelist) freelist = freelist->l_next; else if ((l = (struct line *)malloc(sizeof(struct line))) == 0) fatal("out of memory"); return l; } free_line(l) register struct line *l; { l->l_next = freelist; freelist = l; } #define equal_line(l1, l2) (strcmp((l1)->l_text, (l2)->l_text) == 0) int equal_3(l1, l2) struct line *l1, *l2; { register int i; for (i=0; i<3 && l1 && l2; ++i, l1=l1->l_next, l2=l2->l_next) { if (!equal_line(l1, l2)) return 0; } return (i==3); } struct line * read_line(fp) FILE *fp; { register struct line *l = new_line(); register char *p; register int c; (p = &(l->l_text[LINELEN]))[1] = '\377'; if (fgets(l->l_text, LINELEN + 2, fp) == NULL) { free_line(l); return 0; } if ((p[1] & 0377) != '\377' && *p != '\n') { while ((c = fgetc(fp)) != '\n' && c != EOF) {} *p++ = '\n'; *p = '\0'; } l->l_next = 0; return l; } /* file window handler */ struct f { struct line *f_bwin, *f_ewin; struct line *f_aside; int f_linecnt; /* line number in file of last advanced line */ FILE *f_fp; }; advance(f) register struct f *f; { register struct line *l; if (l = f->f_bwin) { if (f->f_ewin == l) f->f_bwin = f->f_ewin = 0; else f->f_bwin = l->l_next; free_line(l); (f->f_linecnt)++; } } aside(f, l) struct f *f; struct line *l; { register struct line *ll; if (ll = l->l_next) { while (ll->l_next) ll = ll->l_next; ll->l_next = f->f_aside; f->f_aside = l->l_next; l->l_next = 0; f->f_ewin = l; } } struct line * next(f) register struct f *f; { register struct line *l; if (l = f->f_aside) { f->f_aside = l->l_next; l->l_next = 0; } else l = read_line(f->f_fp); if (l) { if (f->f_bwin == 0) f->f_bwin = f->f_ewin = l; else { f->f_ewin->l_next = l; f->f_ewin = l; } } return l; } init_f(f, fp) register struct f *f; FILE *fp; { f->f_bwin = f->f_ewin = f->f_aside = 0; f->f_linecnt = 0; f->f_fp = fp; } update(f, s) register struct f *f; char *s; { while (f->f_bwin && f->f_bwin != f->f_ewin) { printf("%s%s", s, f->f_bwin->l_text); advance(f); } } /* diff procedure */ diff(fp1, fp2) FILE *fp1, *fp2; { struct f f1, f2; struct line *l1, *s1, *b1, *l2, *s2, *b2; register struct line *ll; init_f(&f1, fp1); init_f(&f2, fp2); l1 = next(&f1); l2 = next(&f2); while (l1 && l2) { if (equal_line(l1, l2)) { equal: advance(&f1); advance(&f2); l1 = next(&f1); l2 = next(&f2); continue; } s1 = b1 = l1; s2 = b2 = l2; /* read several more lines */ next(&f1); next(&f1); next(&f2); next(&f2); /* start searching */ search: if ((l2 = next(&f2)) == 0) continue; ll = s1; b2 = b2->l_next; do { if (equal_3(ll, b2)) { aside(&f1, ll); aside(&f2, b2); differ(&f1, &f2); goto equal; } } while (ll = ll->l_next); if ((l1 = next(&f1)) == 0) continue; ll = s2; b1 = b1->l_next; do { if (equal_3(ll, b1)) { aside(&f2, ll); aside(&f1, b1); differ(&f1, &f2); goto equal; } } while (ll = ll->l_next); goto search; } /* one of the files reached EOF */ if (l1) /* eof on 2 */ while (next(&f1)) {} if (l2) while (next(&f2)) {} f1.f_ewin = 0; f2.f_ewin = 0; differ(&f1, &f2); } differ(f1, f2) register struct f *f1, *f2; { int cnt1 = f1->f_linecnt, len1 = wlen(f1), cnt2 = f2->f_linecnt, len2 = wlen(f2); if ((len1 = wlen(f1)) || (len2 = wlen(f2))) { if (len1 == 0) { printf("%da", cnt1); range(cnt2 + 1, cnt2 + len2); } else if (len2 == 0) { range(cnt1 + 1, cnt1 + len1); printf("d%d", cnt2); } else { range(cnt1 + 1, cnt1 + len1); putchar('c'); range(cnt2 + 1, cnt2 + len2); } putchar('\n'); if (len1) update(f1, "< "); if (len1 && len2) printf("---\n"); if (len2) update(f2, "> "); diffs++; } } wlen(f) struct f *f; { register cnt = 0; register struct line *l = f->f_bwin, *e = f->f_ewin; while (l && l != e) { cnt++; l = l->l_next; } return cnt; } range(a, b) { printf(((a == b) ? "%d" : "%d,%d"), a, b); } /* du - report on disk usage Author: Alistair G. Crooks */ /* * du.c 1.1 27/5/87 agc Joypace Ltd. * * Copyright 1987, Joypace Ltd., London UK. All rights reserved. * This code may be freely distributed, provided that this notice * remains attached. * * du - a public domain interpretation of du(1). * */ #include "stdio.h" #include "stat.h" #include "blocksize.h" char *prog; /* program name */ char *optstr = "as"; /* -a and -s arguments */ int silent = 0; /* silent mode */ int all = 0; /* all directory entries mode */ char *startdir = "."; /* starting from here */ #define LINELEN 256 #define DIRNAMELEN 14 #define LSTAT stat typedef struct _dirstr { int inum; char d_name[DIRNAMELEN]; } DIR; DIR dir; #define ino_t unsigned short typedef struct _alstr { int al_dev; ino_t al_inum; } ALREADY; #define MAXALREADY 50 ALREADY already[MAXALREADY]; int alc = 0; /* * myindex - stop the scanf bug */ char *myindex(s, c) register char *s; register char c; { for (; *s; s++) if (*s == c) return(s); return(NULL); } /* * getopt - parse the arguments given. * retrieved from net.sources */ int opterr = 1; int optind = 1; int optopt; char *optarg; #define BADCH (int)'?' #define EMSG "" #define TELL(s) fputs(*nargv, stderr); fputs(s, stderr);\ fputc(optopt, stderr); fputc('\n', stderr);\ return(BADCH); int getopt(nargc, nargv, ostr) int nargc; char **nargv; char *ostr; { register char *oli; static char *place = EMSG; if (!*place) { if (optind >= nargc || *(place = nargv[optind]) != '-' || !*++place) return(EOF); if (*place == '-') { ++optind; return(EOF); } } if ((optopt = (int)*place++) == (int)':' || !(oli = myindex(ostr, optopt))) { if (!*place) ++optind; TELL(": illegal option -- "); } if (*++oli != ':') { optarg = NULL; if (!*place) ++optind; } else { if (*place) optarg = place; else if (nargc <= ++optind) { place = EMSG; TELL(": option requires an argument -- "); } else optarg = nargv[optind]; place = EMSG; ++optind; } return(optopt); } /* * makedname - make the direcory entry from the directory name, * and the file name, placing it in out. If this would overflow, * return 0, otherwise 1. */ int makedname(d, f, out, outlen) char *d; char *f; char *out; int outlen; { char *cp; if (strlen(d) + strlen(f) + 2 > outlen) return(0); for (cp = out ; *d ; *cp++ = *d++) ; if (*(cp-1) != '/') *cp++ = '/'; while (*f) *cp++ = *f++; *cp = '\0'; return(1); } /* * done - have we encountered (dev, inum) before? Returns 1 for yes, * 0 for no, and remembers (dev, inum). */ int done(dev, inum) int dev; ino_t inum; { register ALREADY *ap; register int i; int ret = 0; for (i = 0, ap = already ; i < alc ; ap++, i++) if (ap->al_dev == dev && ap->al_inum == inum) { ret = 1; break; } if (alc < MAXALREADY) { already[alc].al_dev = dev; already[alc++].al_inum = inum; } return(ret); } /* * dodir - process the directory d. Return the long size (in blocks) * of d and its descendants. */ long dodir(d) char *d; { struct stat s; long total = 0L; char dent[LINELEN]; int fd; if ((fd = open(d, 0)) < 0) return(0L); while (read(fd, &dir, sizeof(dir)) > 0) { if (strcmp(dir.d_name, ".") == 0 || strcmp(dir.d_name, "..") == 0) continue; if (dir.inum == 0) continue; if (!makedname(d, dir.d_name, dent, sizeof(dent))) continue; if (LSTAT(dent, &s) < 0) continue; if (s.st_nlink > 1 && done(s.st_dev, s.st_ino)) continue; if ((s.st_mode & S_IFMT) == S_IFDIR) total += dodir(dent); switch(s.st_mode & S_IFMT) { case S_IFREG: case S_IFDIR: total += (s.st_size + BLOCK_SIZE) / BLOCK_SIZE; break; } if (all && (s.st_mode & S_IFMT) != S_IFDIR) printf("%D\t%s\n", (s.st_size + BLOCK_SIZE) / BLOCK_SIZE, dent); } close(fd); if (!silent) printf("%D\t%s\n", total, d); return(total); } /* * OK, here goes... */ main(argc, argv) int argc; char **argv; { long tot; int c; prog = argv[0]; while ((c = getopt(argc, argv, optstr)) != EOF) switch(c) { case 'a' : all = 1; break; case 's' : silent = 1; break; default : fprintf(stderr, "Usage: %s [-a] [-s] [startdir]\n", prog); exit(1); } if (optind < argc) startdir = argv[optind]; tot = dodir(startdir); if (silent) printf("%D\t%s\n", tot, startdir); exit(0); } /* expr(1) -- author Erik Baalbergen */ /* expr accepts the following grammar: expr ::= primary | primary operator expr ; primary ::= '(' expr ')' | signed-integer ; where the priority of the operators is taken care of. The resulting value is printed on stdout. Note that the ":"-operator is not implemented. */ #define EOI 0 #define OR 1 #define AND 2 #define LT 3 #define LE 4 #define EQ 5 #define NE 6 #define GE 7 #define GT 8 #define PLUS 9 #define MINUS 10 #define TIMES 11 #define DIV 12 #define MOD 13 #define COLON 14 #define LPAREN 15 #define RPAREN 16 #define OPERAND 20 #define MAXPRIO 6 struct op { char *op_text; short op_num, op_prio; } ops[] = { {"|", OR, 6}, {"&", AND, 5}, {"<", LT, 4}, {"<=", LE, 4}, {"=", EQ, 4}, {"!=", NE, 4}, {">=", GE, 4}, {">", GT, 4}, {"+", PLUS, 3}, {"-", MINUS, 3}, {"*", TIMES, 2}, {"/", DIV, 2}, {"%", MOD, 2}, /* {":", COLON, 1}, */ {"(", LPAREN, -1}, {")", RPAREN, -1}, {0, 0, 0} }; long eval(), expr(); char *prog; char **ip; struct op *ip_op; main(argc, argv) char *argv[]; { long res; prog = argv[0]; ip = &argv[1]; res = expr(lex(*ip), MAXPRIO); if (*++ip != 0) syntax(); printf("%D\n", res); exit(0); } lex(s) register char *s; { register struct op *op = ops; if (s == 0) { ip_op = 0; return EOI; } while (op->op_text) { if (strcmp(s, op->op_text) == 0) { ip_op = op; return op->op_num; } op++; } ip_op = 0; return OPERAND; } long num(s) register char *s; { long l = 0; long sign = 1; if (*s == '\0') syntax(); if (*s == '-') { sign = -1; s++; } while (*s >= '0' && *s <= '9') l = l * 10 + *s++ - '0'; if (*s != '\0') syntax(); return sign * l; } syntax() { write(2, prog, strlen(prog)); write(2, ": syntax error\n", 15); exit(1); } long expr(n, prio) { long res; if (n == EOI) syntax(); if (n == LPAREN) { if (prio == 0) { res = expr(lex(*++ip), MAXPRIO); if (lex(*++ip) != RPAREN) syntax(); } else res = expr(n, prio - 1); } else if (n == OPERAND) { if (prio == 0) return num(*ip); res = expr(n, prio - 1); } else syntax(); while ((n = lex(*++ip)) && ip_op && ip_op->op_prio == prio) res = eval(res, n, expr(lex(*++ip), prio - 1)); ip--; return res; } long eval(l1, op, l2) long l1, l2; { switch (op) { case OR: return l1 ? l1 : l2; case AND: return (l1 && l2) ? l1 : 0; case LT: return l1 < l2; case LE: return l1 <= l2; case EQ: return l1 == l2; case NE: return l1 != l2; case GE: return l1 >= l2; case GT: return l1 > l2; case PLUS: return l1 + l2; case MINUS: return l1 - l2; case TIMES: return l1 * l2; case DIV: return l1 / l2; case MOD: return l1 % l2; } fatal(); } fatal() { write(2, "fatal\n", 6); exit(1); } /* find - look for files satisfying a predicat Author: Erik Baalbergen */ /* *** Check the switches in the SWITCHES section below. *** Differences from UNIX version 7 find(1): * -name: no name allowed; only uid e.g. find all core files: "find . -name core -a -print" The "-atime" may not work well on Minix. Please report bugs and suggestions to erikb@cs.vu.nl */ #include "stdio.h" /*######################## SWITCHES ##############################*/ #include "stat.h" #define SHELL "/usr/bin/sh" #define PLEN 256 /* maximum path length; overflows are not detected */ #define DIRSIZ 16 /* size of a directory entry */ #define MAXARG 256 /* maximum length for an argv */ #define NPATHS 256 /* maximum number of paths in path-list */ #define BSIZE 1024 /* bytes per block */ /*######################## DEFINITIONS ##############################*/ #define SECS_PER_DAY (24L * 60L * 60L) /* check your planet */ struct exec { int e_cnt; char *e_vec[MAXARG]; }; #define OP_NAME 1 #define OP_PERM 2 #define OP_TYPE 3 #define OP_LINKS 4 #define OP_USER 5 #define OP_GROUP 6 #define OP_SIZE 7 #define OP_INUM 8 #define OP_ATIME 9 #define OP_MTIME 10 #define OP_EXEC 11 #define OP_OK 12 #define OP_PRINT 13 #define OP_NEWER 14 #define OP_AND 15 #define OP_OR 16 struct oper { char *op_str; int op_val; } ops[] = { {"name", OP_NAME}, {"perm", OP_PERM}, {"type", OP_TYPE}, {"links", OP_LINKS}, {"user", OP_USER}, {"group", OP_GROUP}, {"size", OP_SIZE}, {"inum", OP_INUM}, {"atime", OP_ATIME}, {"mtime", OP_MTIME}, {"exec", OP_EXEC}, {"ok", OP_OK}, {"print", OP_PRINT}, {"newer", OP_NEWER}, {"a", OP_AND}, {"o", OP_OR}, {0, 0} }; #define EOI -1 #define NONE 0 #define LPAR 20 #define RPAR 21 #define NOT 22 char *prog, *strcpy(), *Malloc(), *find_bin(); struct node { int n_type; /* any OP_ or NOT */ union { char *n_str; struct { long n_val; int n_sign; } n_int; struct exec *n_exec; struct { struct node *n_left, *n_right; } n_opnd; } n_info; }; struct node *expr(); char **ipp; int tty; /* fd for /dev/tty when using -ok */ long current_time; char * Malloc(n) { char *malloc(), *m; if ((m = malloc(n)) == 0) fatal("out of memory", ""); return m; } char * Salloc(s) char *s; { return strcpy(Malloc(strlen(s) + 1), s); } main(argc, argv) char *argv[]; { char *pathlist[NPATHS]; int pathcnt = 0; register i; struct node *pred; prog = *argv++; while (--argc > 0 && lex(*argv) == NONE) pathlist[pathcnt++] = *argv++; if (pathcnt == 0 || argc == 0) fatal("Usage: path-list predicate-list", ""); ipp = argv; time(¤t_time); pred = expr(lex(*ipp)); if (lex(*++ipp) != EOI) fatal("syntax error: garbage at end of predicate", ""); for (i = 0; i < pathcnt; i++) find(pathlist[i], pred, ""); exit(0); } find(path, pred, last) char *path, *last; struct node *pred; { char spath[PLEN], ent[DIRSIZ + 1]; struct stat st; register char *send = spath; FILE *fp, *fopen(); if (path[1] == '\0' && *path == '/') { *send++ = '/'; *send = '\0'; } else while (*send++ = *path++) {} if (stat(spath, &st) == -1) nonfatal("can't get status of ", spath); else { (void) check(spath, &st, pred, last); if ((st.st_mode & S_IFMT) == S_IFDIR) { if ((fp = fopen(spath, "r")) == NULL) { nonfatal("can't read directory ", spath); return; } send[-1] = '/'; ent[DIRSIZ] = '\0'; while (fread(ent, DIRSIZ, 1, fp) == 1) { if (!((*ent == '\0' && ent[1] == '\0') || (ent[2] == '.') && (ent[3] == '\0' || (ent[3] == '.' && ent[4] == '\0')) )) { strcpy(send, ent + 2); find(spath, pred, ent + 2); } } fclose(fp); } } } check(path, st, n, last) char *path, *last; register struct stat *st; register struct node *n; { switch (n->n_type) { case OP_AND: return check(path, st, n->n_info.n_opnd.n_left, last) && check(path, st, n->n_info.n_opnd.n_right, last); case OP_OR: return check(path, st, n->n_info.n_opnd.n_left, last) || check(path, st, n->n_info.n_opnd.n_right, last); case NOT: return !check(path, st, n->n_info.n_opnd.n_left, last); case OP_NAME: return smatch(last, n->n_info.n_str); case OP_PERM: if (n->n_info.n_int.n_sign < 0) return st->st_mode == (int) n->n_info.n_int.n_val; return (st->st_mode & 0777) == (int) n->n_info.n_int.n_val; case OP_NEWER: return st->st_mtime > n->n_info.n_int.n_val; case OP_TYPE: return (st->st_mode & S_IFMT) == n->n_info.n_int.n_val; case OP_LINKS: return ichk((long)(st->st_nlink), n); case OP_USER: return st->st_uid == n->n_info.n_int.n_val; case OP_GROUP: return st->st_gid == n->n_info.n_int.n_val; case OP_SIZE: return ichk((st->st_size == 0) ? 0L : ((st->st_size - 1) / BSIZE + 1), n); case OP_INUM: return ichk((long)(st->st_ino), n); case OP_ATIME: return ichk(st->st_atime, n); case OP_MTIME: return ichk(st->st_mtime, n); case OP_EXEC: case OP_OK: return execute(n->n_type, n->n_info.n_exec, path); case OP_PRINT: prints("%s\n", path); return 1; } fatal("ILLEGAL NODE", ""); } ichk(val, n) long val; struct node *n; { switch (n->n_info.n_int.n_sign) { case 0: return val == n->n_info.n_int.n_val; case 1: return val > n->n_info.n_int.n_val; case -1: return val < n->n_info.n_int.n_val; } fatal("internal: bad n_sign", ""); } lex(str) char *str; { if (str == 0) return EOI; if (*str == '-') { register struct oper *op; str++; for (op = ops; op->op_str; op++) if (strcmp(str, op->op_str) == 0) break; return op->op_val; } if (str[1] == 0) { switch(*str) { case '(': return LPAR; case ')': return RPAR; case '!': return NOT; } } return NONE; } struct node * newnode(t) { struct node *n = (struct node*) Malloc(sizeof(struct node)); n->n_type = t; return n; } /*########################### PARSER ###################################*/ /* grammar: expr : primary | primary OR expr; primary : secondary | secondary AND primary | secondary primary; secondary : NOT secondary | LPAR expr RPAR | simple; simple : -OP args... */ struct node *expr(), *primary(), *secondary(), *simple(); number(str, base, pl, ps) char *str; long *pl; int *ps; { int up = '0' + base - 1; long val = 0; *ps = ((*str == '-' || *str == '+') ? ((*str++ == '-') ? -1 : 1) : 0); while (*str >= '0' && *str <= up) val = base * val + *str++ - '0'; if (*str) fatal("syntax error: illegal numeric value", ""); *pl = val; } struct node * expr(t) { struct node *nd, *p, *nd2; nd = primary(t); if ((t = lex(*++ipp)) == OP_OR) { nd2 = expr(lex(*++ipp)); p = newnode(OP_OR); p->n_info.n_opnd.n_left = nd; p->n_info.n_opnd.n_right = nd2; return p; } ipp--; return nd; } struct node * primary(t) { struct node *nd, *p, *nd2; nd = secondary(t); if ((t = lex(*++ipp)) != OP_AND) { ipp--; if (t == EOI || t == RPAR || t == OP_OR) return nd; } nd2 = primary(lex(*++ipp)); p = newnode(OP_AND); p->n_info.n_opnd.n_left = nd; p->n_info.n_opnd.n_right = nd2; return p; } struct node * secondary(t) { struct node *n, *p; if (t == LPAR) { n = expr(lex(*++ipp)); if (lex(*++ipp) != RPAR) fatal("syntax error, ) expected", ""); return n; } if (t == NOT) { n = secondary(lex(*++ipp)); p = newnode(NOT); p->n_info.n_opnd.n_left = n; return p; } return simple(t); } checkarg(arg) char *arg; { if (arg == 0) fatal("syntax error, argument expected", ""); } struct node * simple(t) { struct node *p = newnode(t); struct exec *e; struct stat est; long l; switch (t) { case OP_TYPE: checkarg(*++ipp); switch (**ipp) { case 'b': p->n_info.n_int.n_val = S_IFBLK; break; case 'c': p->n_info.n_int.n_val = S_IFCHR; break; case 'd': p->n_info.n_int.n_val = S_IFDIR; break; case 'f': p->n_info.n_int.n_val = S_IFREG; break; default: fatal("-type needs b, c, d or f", ""); } break; case OP_LINKS: case OP_USER: case OP_GROUP: case OP_SIZE: case OP_INUM: case OP_PERM: checkarg(*++ipp); number(*ipp, (t == OP_PERM) ? 8 : 10, &(p->n_info.n_int.n_val), &(p->n_info.n_int.n_sign)); break; case OP_ATIME: case OP_MTIME: checkarg(*++ipp); number(*+-./01ipp, 10, &l, &(p->n_info.n_int.n_sign)); p->n_info.n_int.n_val = current_time - l * SECS_PER_DAY; /* more than n days old means less than the absolute time */ p->n_info.n_int.n_sign *= -1; break; case OP_EXEC: case OP_OK: checkarg(*++ipp); e = (struct exec *)Malloc(sizeof(struct exec)); e->e_cnt = 2; e->e_vec[0] = SHELL; p->n_info.n_exec = e; while (*ipp) { if (**ipp == ';' && (*ipp)[1] == '\0') { e->e_vec[e->e_cnt] = 0; break; } e->e_vec[(e->e_cnt)++] = (**ipp == '{' && (*ipp)[1] == '}' && (*ipp)[2] == '\0') ? (char *)(-1) : *ipp; ipp++; } if (*ipp == 0) fatal("-exec/-ok: ; missing", ""); if ((e->e_vec[1] = find_bin(e->e_vec[2])) == 0) fatal("can't find program ", e->e_vec[2]); if (t == OP_OK) if ((tty = open("/dev/tty", 2)) < 0) fatal("can't open /dev/tty", ""); break; case OP_NEWER: checkarg(*++ipp); if (stat(*ipp, &est) == -1) fatal("-newer: can't get status of ", *ipp); p->n_info.n_int.n_val = est.st_mtime; break; case OP_NAME: checkarg(*++ipp); p->n_info.n_str = *ipp; break; case OP_PRINT: break; default: fatal("syntax error, operator expected", ""); } return p; } /*######################## DIAGNOSTICS ##############################*/ nonfatal(s1, s2) char *s1, *s2; { std_err(prog); std_err(": "); std_err(s1); std_err(s2); std_err("\n"); } fatal(s1, s2) char *s1, *s2; { nonfatal(s1, s2); exit(1); } /*################### SMATCH #########################*/ /* Don't try to understand the following one... */ smatch(s, t) /* shell-like matching */ char *s, *t; { register n; if (*t == '\0') return *s == '\0'; if (*t == '*') { ++t; do if (smatch(s,t)) return 1; while (*s++ != '\0'); return 0; } if (*s == '\0') return 0; if (*t == '\\') return (*s == *++t) ? smatch(++s, ++t) : 0; if (*t == '?') return smatch(++s, ++t); if (*t == '[') { while (*++t != ']') { if (*t == '\\') ++t; if (*(t+1) != '-') if (*t == *s) { while (*++t != ']') if (*t == '\\') ++t; return smatch(++s, ++t); } else continue; if (*(t+2) == ']') return (*s == *t || *s == '-'); n = (*(t+2) == '\\') ? 3 : 2; if (*s >= *t && *s <= *(t+n)) { while (*++t != ']') if (*t == '\\') ++t; return smatch(++s, ++t); } t += n; } return 0; } return (*s == *t) ? smatch(++s, ++t) : 0; } /*####################### EXECUTE ###########################*/ /* do -exec or -ok */ char *epath = 0; char * getpath() { extern char **environ; register char **e = environ; if (epath) /* retrieve PATH only once */ return epath; while (*e) { if (strncmp("PATH=", *e, 5) == 0) { return epath = *e + 5; } e++; } fatal("can't get PATH from environment", ""); } char * find_bin(s) char *s; { char *f, *l, buf[PLEN]; if (*s == '/') /* absolute path name */ return (access(s, 1) == 0) ? s : 0; l = f = getpath(); for (;;) { if (*l == ':' || *l == 0) { if (l == f) { if (access(s, 1) == 0) return Salloc(s); f++; } else { register char *p = buf, *q = s; while (f != l) *p++ = *f++; f++; *p++ = '/'; while (*p++ = *q++) {} if (access(buf, 1) == 0) return Salloc(buf); } if (*l == 0) break; } l++; } return 0; } execute(op, e, path) struct exec *e; char *path; { int s, pid; char *argv[MAXARG]; register char **p, **q; for (p = e->e_vec, q = argv; *p;) /* replace the {}s */ if ((*q++ = *p++) == (char *)-1) q[-1] = path; *q = '\0'; if (op == OP_OK) { char answer[10]; for (p = &argv[2]; *p; p++) { write(tty, *p, strlen(*p)); write(tty, " ", 1); } write(tty, "? ", 2); if (read(tty, answer, 10) < 2 || *answer != 'y') return 0; } if ((pid = fork()) == -1) fatal("can't fork", ""); if (pid == 0) { register i = 3; while (close(i++) == 0) {} /* wow!!! */ execv(argv[1], &argv[2]); /* binary itself? */ execv(argv[0], &argv[1]); /* shell command? */ fatal("exec failure: ", argv[1]); /* none of them! */ exit(127); } return wait(&s) == pid && s == 0; } #include "stdio.h" #include "errno.h" extern int errno; #ifdef LC int _stack = 4000; #define SWITCHCHAR '\\' /* character that separates path elements */ #define PATHCHAR ';' /* character that separates elements of the PATH */ char linecont = '+'; /* default line continuation character */ char switchc = '\\'; /* default directory separation char */ #else #define SHELL "/bin/sh" /* the shell to execute */ #define SWITCHCHAR '/' /* character that separates path elements */ #define PATHCHAR ':' /* character that separates elements of the PATH */ char linecont = '\\'; /* default line continuation character */ char switchc = '/'; /* default directory separation char */ #endif /* the argument to malloc */ #define UI unsigned int #define TIME long /* generic linked list */ struct llist { char *name; struct llist *next; }; struct defnrec { char *name; int uptodate; TIME modified; struct llist *dependson; struct llist *howto; struct defnrec *nextdefn; }; struct macrec { char *name,*mexpand; struct macrec *nextmac; }; struct rulerec { char *dep,*targ; /* order is important because there are defaults */ struct llist *rule; int def_flag; struct rulerec *nextrule; }; /* default rules for make */ /* 1. Modifiy NDRULES to reflect the total number of rules... 2. Enter rules in the following format "source-extension","object-extension","rule",NULL, and update NDRULES. 3. Note that the default rules are searched from top to bottom in this list, and that within default rules, macros are ignored if not defined (You may specify FFLAGS in the rule without requiring the user to define FFLAGS in his/her makefile) 4. The rule applied will be the first one matching. If I ever get a public domain YACC working, the rule for it will be first, and look like ".y",".o","bison $(YFLAGS) $*.y","cc $* $(CFLAGS)",NULL, There are at present no depends in rules, so you need a .y.o: and a .y.c: rule 5. The src and dest extensions are expected to be in lower case 6. SUFFIX sets the name and precedence for the prerequisite macros, $< and $?, and for looking up rules. Basically, all type extensions must be to the left of type extensions. All rules should have their suffix mentioned. See UNIX documentation or make.doc */ #define NDRULES 3 char *def_list[] = { ".c",".s","$(CC) -S $(CFLAGS) $*.c",NULL, ".c",".o","$(CC) -c $(CFLAGS) $*.c",NULL, ".s",".o","$(AS) $(AFLAGS) -o $*.o $*.s",NULL, }; #define SUFFIXES ".o .s .c" #ifndef LC TIME time(); #define now() (TIME) time( (char *) 0) /* the current time, in seconds */ #else #include "time.h" TIME now() { struct tm timer,*p; long t,ftpack(); char tim[6]; p = &timer; time(&t); p = localtime(&t); tim[0] = p->tm_year - 80; tim[1] = p->tm_mon + 1; tim[2] = p->tm_mday; tim[3] = p->tm_hour; tim[4] = p->tm_min; tim[5] = p->tm_sec; return(ftpack(tim)); } #endif char **ext_env; char *whoami = "Make"; #ifndef LC #define max(a,b) ((a)>(b)?(a):(b)) #define min(a,b) ((a)<=(b)?(a):(b)) #endif #define BKSLSH '\134' #undef ERROR #define ERROR -1 #define TRUE 1 #define FALSE 0 #define EQ(X,Y) (strcmp(X,Y) == 0) #define NUL '\0' #define isnull(X) (X == '\0' ? TRUE : FALSE) #define notnull(X) (X == '\0' ? FALSE : TRUE ) #define isspace(X) ( (X == ' ') || (X == '\011') ) /* flags for expand. Report and ignore errors, and no_target means error if $@ or $* is attempted ( as in $* : a.obj ) */ #define NO_TARG 2 #define REPT_ERR 1 #define IGN_ERR 0 #define INMAX 500 /* maximum input line length (allow for expansion)*/ #define INMAXSH 80 /* length for short strings */ #ifdef BSD4.2 #include #include #include #define WAIT union wait #else /* sysV and MINIX and LATTICE */ struct stat { short int st_dev; unsigned short st_ino; unsigned short st_mode; short int st_nlink; short int st_uid; short int st_gid; short int st_rdev; long st_size; TIME st_atime; TIME st_mtime; TIME st_ctime; } ; /* #include #include */ #define WAIT int #endif struct macrec *maclist = NULL; struct defnrec *defnlist = NULL; struct llist *dolist = NULL; struct llist *path_head = NULL; struct llist *suff_head = NULL; struct rulerec *rulelist = NULL; struct llist *makef = NULL; #ifdef LC int linkerf = TRUE; char tfilename[50] = "makefile.mac"; #endif int execute = TRUE; int stopOnErr = TRUE; int forgeahead = FALSE; int madesomething = FALSE; int knowhow = FALSE; int no_file = TRUE; int silentf = FALSE; #ifdef DEBUG int tree_and_quit = FALSE; int post_tree = FALSE; #endif DEBUG int path_set = FALSE; /* return declarations for functions not returning an int */ TIME make(),getmodified(),findexec(); char *mov_in(),*get_mem(); struct llist *MkListMem(), *mkllist(), *mkexphow(), *mkexpdep(),*add_llist(); /* temp storage variables for the prerequisite lists */ struct m_preq { char m_dep[6]; char m_targ[6]; char m_name[INMAXSH]; }; char dothis[INMAX]; /* generic temporary storage */ FILE *fopen(); main(argc,argv,envp) int argc; char *argv[],*envp[]; { ext_env = envp; init(argc,argv); #ifdef DEBUG if (tree_and_quit){ prtree(); fprintf(stderr, "tree and quit\n"); done(0); } #endif DEBUG /* now fall down the dolist and do them all */ while (dolist != NULL) { madesomething = FALSE; make(dolist->name); if (!madesomething) { if (knowhow) { if ( !silentf ) fprintf(stdout,"%s: '%s' is up to date.\n",whoami,dolist->name); } else { error2("Don't know how to make %s",dolist->name); } } dolist = dolist->next; } #ifdef DEBUG if (post_tree) prtree(); #endif DEBUG done( 0 ); } init(argc,argv) int argc; char *argv[]; { int i,k; int pdep,ptarg; struct llist *ptr; int usedefault = TRUE; for (k=i=0; i < NDRULES; i++,k++) { /* couldn't init the linked list with the compiler */ pdep = k++; ptarg = k++; ptr = NULL; while ( def_list[k] != NULL ) { ptr = add_llist(ptr,def_list[k]); k++; } add_rule2(def_list[pdep],def_list[ptarg],ptr,TRUE); } /* initialize the suffix list */ add_suff(SUFFIXES); /* load the macro MFLAGS */ for ( i = 1,dothis[0] = NUL; i < argc ; i++ ) { strcat(dothis,argv[i]); strcat(dothis," "); } add_macro("MFLAGS",dothis); /* add any default macros here, such as CC */ add_macro("CC","cc"); add_macro("AS","as"); /* step thru the args and read the flags, patterns and objects */ for (i=1; i < argc; i++) { if (argv[i][0] == '-') { /* option */ switch (argv[i][1]) { case 'f': case 'F': /* arg following is a makefile */ if (++i < argc) { makef = add_llist(makef,argv[i]); usedefault = FALSE; } else panic("'-f' requires filename"); break; case 'c': case 'C': /* line continuation char */ if ( argv[i][2] != NUL ) linecont = argv[i][2]; else { if ( ++i < argc || notnull(argv[i][1]) ) linecont = argv[i][0]; else error("'-c' requires single character"); } break; case 'k':case 'K': /* abandon work on this branch on error */ forgeahead = TRUE; break; case 'b': case 'B': /* switchchar change */ if ( argv[i][2] != NUL ) switchc = argv[i][2]; else { if ( ++i < argc || notnull(argv[i][1]) ) switchc = argv[i][0]; else error("'-b' requires single character"); } break; case 'i': case 'I': /* ignore errors on execution */ stopOnErr = FALSE; break; case 'n': case 'N': /* don't execute commands - just print */ execute = FALSE; break; case 's': case 'S': /* execute, but don't print */ silentf = TRUE; break; #ifdef DEBUG case 'p':case 'P': /* print a tree and quit */ tree_and_quit = TRUE; break; case 'v': case 'V': /* print the tree after all processing */ post_tree = TRUE; break; #endif 9;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdeDEBUG default: error2("unknown option '%s'",argv[i]); } } else { if ( !maccheck(argv[i]) ) { /* if argv[i] is not of the pattern xxx=yyy, then it must be something to make. maccheck will add the macro if it is... */ dolist = add_llist(dolist,argv[i]); no_file = FALSE; } } } /* collect the flags, then read the makefile, otherwise a construct like make -f make2 aardvark will make both aardvark and the first target in make2 */ if (usedefault) { if ( getmodified("Makefile") != (TIME) 0 ) readmakefile("Makefile"); else readmakefile("makefile"); } else { for ( ptr = makef; ptr != NULL; ptr = ptr->next ) readmakefile(ptr->name); } /* if not done by a PATH directive, set the basic PATH */ if ( !path_set ) {mkpathlist();path_set = TRUE;} }/* init */ TIME make(s) /* returns the modified date/time */ char *s; { struct defnrec *defnp,*tryrules(),*dummy; struct llist *depp,*depp2; struct llist *howp; char *m_comp,*m_ood,*m_obj; char *stradd(),*add_prereq(); TIME latest,timeof; struct m_preq ma; /* look for the definition */ for ( defnp = defnlist; defnp != NULL; defnp = defnp->nextdefn ) if ( EQ(defnp->name,s) ) break; /* there might be some adjusting of the lists for implied compilations. these additions are not done in readmakefile; they might not be required for these targets ( ie not requested ) */ if (defnp == NULL ){ defnp = tryrules(s); if (defnp == NULL){ /* tryrules returns a pointer to a defnrec */ /* tryrules fails, and there is no definition */ knowhow = FALSE; latest = getmodified(s); if (latest==0) { /* doesn't exist but don't know how to make */ panic2("Can't make %s",s); /* NOTREACHED */ } else { /* exists - assume it's up to date since we don't know how to make it. Make a defnrec with uptodate == TRUE. This is not strictly necessary, and this line may be deleted. It is a speed win, at the expense of core, when there are a lot of header files. */ /*add_defn(s,TRUE,latest, (struct llist *)NULL,(struct llist*)NULL); */ return(latest); } } } else{ /* there is a definition line */ if (defnp->uptodate) { return(defnp->modified); } dummy = tryrules(s); if (dummy != NULL && defnp->howto == NULL){ /* any explicit howto overrides an implicit one */ /*add depend*/ if (defnp->dependson == NULL) defnp->dependson = dummy->dependson; else{ for (depp2 = defnp->dependson; depp2->next != NULL; depp2 = depp2->next); /* advance to end of list */ depp2->next = dummy->dependson; } /* add howto line */ defnp->howto = dummy->howto; } } /* finished adjusting the lists */ /* check that everything that the current target depends on is uptodate */ m_comp = m_ood = m_obj = NULL; latest = 0; depp = defnp->dependson; while (depp != NULL) { m_comp = add_prereq(m_comp,depp->name,&ma);/* add to string for $< */ timeof = make(depp->name); latest = max(timeof,latest);/*written this way to avoid side effects*/ if ( defnp->modified < timeof ) { /* these are out of date with respect to the current target */ m_ood = stradd(m_ood,ma.m_name,ma.m_dep); /* add source */ m_obj = stradd(m_obj,ma.m_name,ma.m_targ);/* add object */ } depp = depp->next; } knowhow = TRUE; /* has dependencies therefore we know how */ /* if necessary, execute all of the commands to make it */ /* if (out of date) || (depends on nothing) */ if (latest > defnp->modified || defnp->dependson==NULL) { /* make those suckers */ howp = defnp->howto; if ((howp == NULL) && !in_dolist(s)) { fprintf(stderr,"%s: %s is out of date, but there is no command line\n", whoami,s); if ( stopOnErr ) mystop_err(); } while (howp != NULL) { /* the execute flag controls whether execution takes place */ p_expand(howp->name,dothis,m_comp,m_ood,m_obj); if ( (exec_how(dothis) != 0) ) if (forgeahead) break; else if (stopOnErr) panicstop(); howp = howp->next; } /* we just made something. Set the time of modification to now. */ defnp->modified = now(); defnp->uptodate = TRUE; if (defnp->howto != NULL) /* we had instructions */ madesomething = TRUE; } if ( m_comp != NULL ) free( m_comp ); if ( m_ood != NULL ) free( m_ood ); if ( m_obj != NULL ) free( m_obj ); return(defnp->modified); } struct llist *add_llist(head,s) /*this adds something to a linked list */ char *s; struct llist *head; { struct llist *ptr1; /* find a slot in the list */ if (head == NULL) { ptr1 = MkListMem(); ptr1->name = mov_in(s); ptr1->next = NULL; return ( ptr1 ); } else { for (ptr1 = head; ptr1->next != NULL ; ptr1 = ptr1->next ) ; ptr1->next = MkListMem(); ptr1->next->name = mov_in(s); ptr1->next->next = NULL; return( head ); } } expand(src,dest,target,flag) /* expand any macros found*/ char *src,*dest,*target; int flag; { char thismac[INMAX],*ismac(),*ismac_c(); char thismac2[INMAX],*macptr; int i,pos,back, j; back = pos = 0; if ( src == NULL ) { /* NULL pointer, shouldn't happen */ dest[0] = NUL; return; } while( notnull(src[pos]) ) { if (src[pos] != '$') dest[back++] = src[pos++]; else { pos++; /*found '$'. What kind of macro is this? */ switch(src[pos]){ case '(': /*regular macro*/ case '{': /*regular macro*/ /* do macro stuff */ pos = x_scan(src,pos,thismac); /* get macro */ if ( maclist == NULL && (flag & REPT_ERR) ) error2("No macros defined -- %s",thismac); else if ( (macptr=ismac(thismac)) == NULL) { expand(thismac,thismac2,target,flag); if ( (macptr = ismac(thismac2)) != NULL) /* the recursive expand found it */ back = mv_install(macptr,dest,back); else { if ( flag & REPT_ERR ) error2("Can't expand macro -- %s",thismac2); } } else { /* macptr points to appropriate macro */ back = mv_install(macptr,dest,back); } break; case '*': /*target without extension*/ case '@': /*whole target*/ if ((flag & NO_TARG) && (flag & REPT_ERR) ){ fprintf(stderr,"%s: '$%c' not in a command or dependency line\n", whoami,src[pos]); if ( stopOnErr ) mystop_err(); else return; } else { for (i=0; notnull(target[i]) ; i++) { if (target[i] == '.' && src[pos] == '*') { j = i; while (notnull(target[j]) && target[j] != switchc) j++; if (notnull(target[j]) == FALSE) break; } dest[back++] = target[i]; } } break; default: if ( (macptr = ismac_c(src[pos])) != NULL ) back = mv_install(macptr,dest,back); else { /*not an approved macro, ignore*/ dest[back++] = '$'; dest[back++] = src[pos]; } break; }/* else */ pos++; } } dest[back] = NUL; } p_expand(src,dest,compl_preq,ood_preq,ood_obj) char *src,*dest; char *compl_preq,*ood_preq,*ood_obj; { /* expand the special macros $< $? $> just before execution */ int back,pos,i; if ( src == NULL ) { dest[0] = NUL; return; } back = pos = 0; while( notnull(src[pos]) ) { if (src[pos] != '$' ) dest[back++] = src[pos++]; else { pos++; switch( src[pos] ) { case '<': /* put in the complete list of prerequisites */ for ( i = 0; notnull(compl_preq[i]); i++ ) dest[back++] = compl_preq[i]; break; case '?': /* the prerequisites that are out of date */ for ( i = 0; notnull(ood_obj[i]); i++ ) dest[back++] = ood_obj[i]; break; case '>': /* the source files that are out of date (NOT STANDARD)*/ for ( i = 0; notnull(ood_preq[i]); i++ ) dest[back++] = ood_preq[i]; break; case '$': /* get rid of the doubled $$ */ dest[back++] = '$'; break; default: dest[back++] = '$'; dest[back++] = src[pos]; break; } pos++; } /* else */ } /* switch */ dest[back] = NUL; } /* is this a character macro? */ char *ismac_c(cc) char cc; { char *ismac(); char *str = " "; /* space for a one char string */ str[0] = cc; return(ismac(str)); } /* is this string a macro? */ char *ismac(test) char *test; { struct macrec *ptr; ptr = maclist; if ( ptr == NULL ) return( NULL ); while( TRUE ) { if ( EQ(ptr->name,test) ) return( ptr->mexpand ); else if ( ptr->nextmac == NULL ) return( NULL ); else ptr = ptr->nextmac; } } maccheck(sptr) /* if this string has a '=', then add it as a macro */ char *sptr; { int k; for ( k=0; notnull(sptr[k]) && (sptr[k] != '='); k++ ); if ( isnull(sptr[k]) ) return( FALSE ); else { /* found a macro */ sptr[k] = NUL; add_macro(sptr,sptr + k + 1); return( TRUE ); } } x_scan(src,pos,dest) char *src,*dest; int pos; { char bterm,eterm; int cnt; /* load dest with macro, allowing for nesting */ if ( src[pos] == '(' ) eterm = ')'; else if ( src[pos] == '{' ) eterm = '}'; else panic("very bad things happening in x_scan"); bterm = src[pos++]; cnt = 1; while ( notnull(src[pos]) ) { if ( src[pos] == bterm ) cnt++; else if ( src[pos] == eterm ) { cnt--; if ( cnt == 0 ) { *dest = NUL; return( pos ); } } *dest++ = src[pos++]; } panic2("No closing brace/paren for %s",src); /* NOTREACHED */ } /* expand and move to dest */ mv_install(from,to,back) char *from,*to; int back; { int i; for ( i=0; notnull(from[i]) ; i++) to[back++] = from[i]; return( back ); } /* attempts to apply a rule. If an applicable rule is found, returns a pointer to a (new) struct which can be added to the defnlist An applicable rule is one in which target ext matches, and a source file exists. */ struct defnrec *tryrules(string) char *string; { struct rulerec *rptr,*isrule(); struct llist *sptr; struct defnrec *retval; char s[INMAXSH],buf[INMAXSH],sext[10]; my_strcpy(s,string); get_ext(s,sext); /* s is now truncated */ if ( sext[0] == NUL ) { /*target has no extension*/ return(NULL); } /* look for this extension in the suffix list */ for ( sptr = suff_head; sptr != NULL; sptr = sptr->next ) if ( EQ(sptr->name,sext) ) break; if ( sptr == NULL ) { /* not a valid extension */ return( NULL ); } /* continue looking, now for existence of a source file **/ for ( sptr = sptr->next; sptr != NULL; sptr = sptr->next ) if ( exists(s,sptr->name) && ((rptr = isrule(rulelist,sptr->name,sext)) != NULL) ) break; if ( sptr == NULL ) { /* no rule applies */ return( NULL ); } retval = (struct defnrec *)get_mem((UI) sizeof(struct defnrec)); my_strcpy(buf,s); /* s -- is the stem of the object*/ strcat(buf,rptr->targ); retval->name = mov_in(buf); my_strcpy(buf,s); strcat(buf,rptr->dep); retval->dependson = mkllist(buf); retval->uptodate = FALSE; retval->modified = getmodified(retval->name); retval->nextdefn = NULL; retval->howto = mkexphow(rptr->rule,retval->name, (rptr->def_flag)? IGN_ERR : REPT_ERR); return(retval); /*whew*/ } /* does the file exist? */ exists(name,suffix) char *name,*suffix; { char t[INMAXSH]; my_strcpy(t,name); strcat(t,suffix); return (getmodified(t) != (TIME) 0 ? TRUE : FALSE ); } struct rulerec *isrule(head,src,dest) struct rulerec *head; char *src,*dest; { /* return ptr to rule if this is a legit rule */ struct rulerec *ptr; if ( head == NULL ) return( NULL ); else { for ( ptr = head; ptr != NULL; ptr = ptr->nextrule ) if ( EQ(ptr->dep,src) && EQ(ptr->targ,dest) ) return( ptr ); return( NULL ); } } #ifdef DEBUG /*debugging*/ /* print the dependencies and command lines... */ prtree() { struct defnrec *dummy; struct macrec *mdum; struct llist *dum2,*dum3,*rdum2; struct rulerec *rdum; int cnt; dummy = defnlist; while (dummy != NULL){ fprintf(stdout,"name '%s' exists: %s\n", dummy->name,(dummy->modified)?"no":"yes"); dum2 = dummy->dependson; fprintf(stdout," depends-on:"); cnt =0; while (dum2 != NULL){ fprintf(stdout," %13s ",dum2->name); cnt++; if (cnt == 4){ cnt = 0; fprintf(stdout,"\n "); } dum2 = dum2->next; } fprintf(stdout,"\n"); dum3 = dummy->howto; while (dum3 != NULL){ fprintf(stdout," command: %s\n",dum3->name); dum3 = dum3->next; } dummy = dummy->nextdefn; fprintf(stdout,"\n"); } fprintf(stdout,"\n *RULES*\n\n"); fprintf(stdout,"src= dest= rule=\n"); rdum = rulelist; while ( rdum != NULL ) { if ( rdum->rule == NULL ) fprintf(stdout,"%4s %4s %s\n", rdum->dep,rdum->targ,"** Empty Rule **"); else { fprintf(stdout,"%4s %4s %s\n", rdum->dep,rdum->targ,rdum->rule->name); rdum2 = rdum->rule->next; while ( rdum2 != NULL ) { fprintf(stdout," %s\n",rdum2->name); rdum2 = rdum2->next; } } rdum = rdum->nextrule; } mdum = maclist; if ( mdum == NULL ) fprintf(stdout,"\n *NO MACROS*\n"); else { fprintf(stdout,"\n *MACROS*\n\n"); fprintf(stdout," macro expansion\n"); while ( mdum != NULL ) { fprintf(stdout," %8s %s\n",mdum->name,mdum->mexpand); mdum = mdum->nextmac; } } fprintf(stdout,"\n\nsuffix list is"); if ( suff_head == NULL ) fprintf(stdout," empty."); else for ( dum2 = suff_head; dum2 != NULL; dum2 = dum2->next){ fprintf(stdout," %s",dum2->name); } fprintf(stdout,"\npath is "); if (path_head == NULL) fprintf(stdout," empty."); else for ( dum2 = path_head; dum2 != NULL; dum2 = dum2->next) fprintf(stdout," %s:",dum2->name); fprintf(stdout,"\nswitch character '%c'\n",switchc); fprintf(stdout,"line continuation '%c'\n",linecont); } /*debug*/ #endif DEBUG error(s1) char *s1; { fprintf(stderr,"%s: ",whoami); fprintf(stderr,s1); fprintf(stderr,"\n"); if (stopOnErr) mystop_err(); else return; } error2(s1,s2) char *s1,*s2; { fprintf(stderr,"%s: ",whoami); fprintf(stderr,s1,s2); fprintf(stderr,"\n"); if (stopOnErr) mystop_err(); else return; } panic(s1) char *s1; { fprintf(stderr,"%s: ",whoami); fprintf(stderr,s1); fprintf(stderr,"\n"); mystop_err(); /* NOTREACHED */ } panic2(s1,s2) char *s1,*s2; { fprintf(stderr,"%s: ",whoami); fprintf(stderr,s1,s2); fprintf(stderr,"\n"); mystop_err(); /* NOTREACHED */ } panicstop() { fprintf(stderr,"\n\n ***Stop.\n"); mystop_err(); /* NOTREACHED */ } mystop_err() { done( -1 ); /* NOTREACHED */ } in_dolist(s) /* true if this is something we are to make */ char *s; { struct llist *ptr; for ( ptr = dolist; ptr != NULL ; ptr = ptr->next ) if ( EQ(ptr->name,s) ) return( TRUE ); return( FALSE ); } char *add_prereq(head,nam,f) char *head,*nam; struct m_preq *f; { char *stradd(); struct llist *ptr; /* move the stem, src and dest extensions and the current time of modification to where make() can get at them. returns the updated list of prerequisites */ /* get the prerequisite */ /* if ext does not exist, return */ my_strcpy(f->m_name,nam); get_ext(f->m_name,f->m_targ); if ( f->m_targ[0] == NUL ) return( head ); /* if ext not on suffix list, return */ for ( ptr = suff_head; ptr != NULL; ptr = ptr->next ) if ( EQ(ptr->name,f->m_targ) ) break; if ( ptr == NULL ) return( head ); /* if there does not exist a corresponding file, return */ for ( ; ptr != NULL; ptr = ptr->next ) if ( exists(f->m_name,ptr->name) ) break; if ( ptr == NULL ) return( head ); /* add the source file to the string m_comp with a strcat */ my_strcpy(f->m_dep,ptr->name); return( stradd(head,f->m_name,f->m_dep) ); } char *stradd(f1,f2,f3) char *f1,*f2,*f3; { char *ptr; /* return a new pointer to a string containing the three strings */ ptr = get_mem((UI)(my_strlen(f1) + strlen(f2) + strlen(f3) + 2) ); my_strcpy(ptr,f1); strcat(ptr," "); strcat(ptr,f2); strcat(ptr,f3); if ( f1 != NULL ) free( f1 ); return( ptr ); } get_ext(n,ex) char *ex,*n; { int start,end,x; /* put the extension of n in ex ( with the period ) and truncate name ( at period ) */ start = my_strlen(n); end = ( start > 6 ) ? start - 6 : 0; for ( x = start; x > end; x-- ) if ( n[x] == '.' ) break; if ( x == end ) { ex[0] = NUL; return; } else { my_strcpy(ex,n + x); n[x] = NUL; } } /* read the makefile, and generate the linked lists */ #define DONE 1 #define ADEFN 2 #define ARULE 3 #define AMACRO 4 #define DIRECTIVE 5 /* local variables */ FILE *fil; char *fword,*restline,line[INMAX],backup[INMAX]; char *frule; struct llist *fhowto,*howp3; struct llist *fdeps,*deprec3; struct defnrec *defnp; int sending,def_ready,gdone,rule_send,rule_ready; struct llist *targ,*q_how,*q_dep,*targ2; readmakefile(s) char *s; { int i,k; char temp[50],tempdep[50]; FILE *fopen(); /* open the file */ if ( EQ(s,"-") ) fil = stdin; else if ( (fil = fopen(s,"r")) == NULL) { error2("couldn't open %s",s); return; } /* initialise getnxt() */ sending = def_ready = gdone = rule_send = rule_ready = FALSE; targ = q_how = q_dep = NULL; if (getline(fil, backup) == FALSE) panic("Empty Makefile"); /* getnxt() parses the next line, and returns the parts */ while (TRUE) switch( getnxt() ){ case DONE: fclose(fil);return; case AMACRO: /* add a macro to the list */ add_macro(fword,restline); break; case DIRECTIVE: /* perform the actions specified for a directive */ squeezesp(temp,fword); if ( EQ(temp,"PATH") ) { if ( my_strlen(restline) == 0 ) {free_list(path_head);path_head = NULL;path_set = TRUE;} else { /* this fooling around is because the switchchar may not be set yet, and the PATH variable will use it */ if ( !path_set ) {mkpathlist();path_set = TRUE;} add_path(restline); } } else if ( EQ(temp,"SUFFIXES") ) { /* this is easier, suffix syntax characters don't change */ if ( my_strlen(restline) == 0 ) {free_list(suff_head);suff_head = NULL;} else add_suff(restline); } else if ( EQ(temp,"IGNORE") ) stopOnErr = FALSE; else if ( EQ(temp,"SWITCH") ) switchc = (isnull(restline[0])) ? switchc : restline[0]; else if ( EQ(temp,"LINECONT") ) linecont = ( isnull( restline[0] ) ) ? linecont : restline[0]; else if ( EQ(temp,"SILENT") ) silentf = TRUE; #ifdef LC else if ( EQ(temp,"LINKER") ) { if ( my_strlen(restline) == 0 ) linkerf = TRUE; else switch (restline[0]) { case 'f': case 'F': linkerf = FALSE; case 't': case 'T': linkerf = TRUE; default: panic("Bad argument to LINKER (TRUE/FALSE)"); } } else if ( EQ(temp,"MACFILE") ) { if ( my_strlen(restline) == 0 ) { warn2("no MACFILE name, defaulting to %s",tfilename); } else my_strcpy(tfilename,restline); } #endif else { error2("unknown directive \(rule?\) '%s'",temp); } break; case ARULE: /* add a rule to the list */ /*fword[0] is defined to be a period*/ for (i=1; notnull(fword[i]) ;i++) if ( fword[i] == '.' ) break; if ( i == my_strlen(fword) ) { panic2("Bad rule '%s'",fword); /* NOTREACHED */ } fword[i] = NUL; /* dep */ my_strcpy(tempdep,fword); /* be sure object extension has no spaces */ for ( k = i+1 ; notnull(fword[k]) && !isspace(fword[k]) ; k++ ); if ( isspace(fword[k]) ) fword[k] = NUL; my_strcpy(temp,"."); strcat(temp,fword + i + 1); /* targ */ add_rule2(tempdep, temp, fhowto, FALSE); /* update the suffix list if required. To get fancier than this, the use has to do it himself. Start at the beginning, and if not present, add to the end. */ add_s_suff(temp); /* order is important -- add targ first **/ add_s_suff(tempdep); /* then dep */ break; case ADEFN: /* add a definition */ if (no_file) { /*if no target specified on command line... */ dolist = add_llist(dolist,fword); /*push target on to-do list */ no_file = FALSE; } /* getnxt() returns target ( fword ) , pointer to expanded howto list ( fhowto ) and pointer to expanded depends ( fdeps ) */ if ( defnlist == NULL ) { /* add the current definition to the end */ add_defn(fword,FALSE,getmodified(fword),fdeps,fhowto); } else { defnp = defnlist; while ( defnp != NULL ) { if ( EQ(defnp->name,fword) ) break; else defnp = defnp->nextdefn; } if ( defnp == NULL ) { /* target not currently in list */ add_defn(fword,FALSE,getmodified(fword),fdeps,fhowto); } else { /* target is on list, add new depends and howtos */ if (defnp->dependson == NULL) defnp->dependson = fdeps; else { deprec3 = defnp->dependson; while (deprec3->next != NULL) deprec3 = deprec3->next; deprec3->next = fdeps; } /* add new howtos */ if (defnp->howto == NULL) defnp->howto = fhowto; else { howp3 = defnp->howto; while ( howp3->next != NULL) howp3 = howp3->next; howp3->next = fhowto; } } } break; } } add_defn(n,u,m,d,h) /* add a new definition */ char *n; /* name */ int u; /* uptodate */ TIME m; /* time of modification */ struct llist *d,*h; /* pointers to depends, howto */ { struct defnrec *ptr,*ptr2; ptr = (struct defnrec *)get_mem(sizeof(struct defnrec)); ptr->name = mov_in(n); ptr->uptodate = u; ptr->modified = m; ptr->dependson = d; ptr->howto = h; ptr->nextdefn = NULL; if ( defnlist == NULL ) { defnlist = ptr; return; } else { ptr2 = defnlist; while ( ptr2->nextdefn != NULL ) ptr2 = ptr2->nextdefn; ptr2->nextdefn = ptr; } } getnxt() { int pos,mark,parsed,x; char exp_line[INMAX]; struct llist *q_how2,*q_how3; while ( TRUE ) { /* if we are currently sending targets */ if ( sending ) { if ( targ2->next == NULL) { sending = def_ready = FALSE; } fword = mov_in(targ2->name); fhowto = mkexphow(q_how,targ2->name,REPT_ERR); fdeps = mkexpdep(q_dep,targ2->name); targ2 = targ2->next; return ( ADEFN ); } /* are we sending a rule? */ if ( rule_send ) { fword = frule; fhowto = mkexphow(q_how,(char *)NULL,IGN_ERR); /* target == NULL -> don't expand */ rule_send = rule_ready = FALSE; return( ARULE ); } if ( gdone ) return ( DONE ); /* if we are not currently sending... */ /* load the next line into 'line' ( which may be backed-up ) */ if ( backup[0] != NUL ) { my_strcpy(line,backup); backup[0] = NUL; } else { if ( getline(fil,line) == FALSE ) { if ( def_ready ) sending = TRUE; if ( rule_ready ) rule_send = TRUE; gdone = TRUE; continue; /* break the loop, and flush any definitions */ } } parsed = FALSE; /* check for rule or directive, and begin loading it if there */ if (line[0] == '.'){ for (pos = 0 ; line[pos] != ':' && notnull(line[pos]) ; pos++) ; if (isnull(line[pos])) error2("bad rule or directive, needs colon separator:\n%s",line); mark = pos; for ( x = 1 ; x < mark ; x++ ) if ( line[x] == '.' ) break; if ( x == mark ) { /* found DIRECTIVE -- .XXXXX: */ line[mark] = NUL; fword = line + 1; for ( x++ ; isspace(line[x]) ; x++ ) ; restline = line + x ; return( DIRECTIVE ); } else { /* found RULE -- .XX.XXX: */ parsed = TRUE; if ( rule_ready || def_ready ) { if ( def_ready ) sending = TRUE; else rule_send = TRUE; /* push this line, and send what we already have */ my_strcpy(backup,line); } else { /* begin a new rule */ rule_ready = TRUE; line[mark] = NUL; frule = mov_in(line); free_list(q_how); /* one last decision to make. If next non-space char is ';', then the first howto of the rule follows, unless there is a '#', in which case we ignore the comment */ for ( pos++ ; line[pos] != ';' && notnull(line[pos]) ; pos++) if ( !isspace(line[pos]) ) break; if ( notnull(line[pos]) ) { if ( line[pos] == '#' ) ; /* do nothing, fall thru */ else if ( line[pos] != ';' ) /* found non-, non-';' after rule declaration */ error("rule needs ';' or after ':'"); else { /* found :; */ q_how = MkListMem(); q_how->name = mov_in(line + pos + 1 ); q_how->next = NULL; } } } } } /* check for macro, and return it if there */ if ( !parsed ) { pos = 0; while ( line[pos] != '=' && line[pos] != ':' && !isspace(line[pos]) && notnull(line[pos])) pos++; mark = pos; if (notnull(line[pos]) && line[pos] != ':'){ /* found a macro */ if (isspace(line[pos])) while (isspace(line[pos]) && notnull(line[pos])) pos++; if (isnull(line[pos])) panic2("bad macro or definition '%s'",line); if ( line[pos] == '=' ) { /* found a macro */ line[mark] = NUL; fword = line; mark = pos + 1; while ( isspace(line[mark]) ) mark++; /* skip spaces before macro starts */ restline = line + mark; return ( AMACRO ); } } } /* check for and add howto line */ if ( isspace(line[0]) ) { if (!def_ready && !rule_ready) error2("how-to line without preceeding definition or rule\n%s",line); q_how2 = MkListMem(); if ( q_how == NULL ) { q_how = q_how2; } else { for (q_how3 = q_how; q_how3->next != NULL; q_how3 = q_how3->next); /* go to end of list */ q_how3->next = q_how2; } q_how2->name = mov_in(line); q_how2->next = NULL; parsed = TRUE; } /* check for definition */ if (!parsed) { pos = 0; while ( notnull(line[pos]) && line[pos] != ':') pos++; if (line[pos] == ':') { /* found a definition */ parsed = TRUE; if (def_ready || rule_ready) { if ( def_ready ) sending = TRUE; else rule_send = TRUE; my_strcpy(backup,line); } else { /* free up the space used by the previous lists */ free_list(targ);targ = NULL; free_list(q_how);q_how = NULL; free_list(q_dep);q_dep = NULL; line[pos] = NUL; expand(line,exp_line,"",NO_TARG); targ2 = targ = mkllist(exp_line); q_dep = mkllist(line + pos + 1); def_ready = TRUE; } } } if (!parsed) panic2("unable to parse line '%s'",line); } } /* load the next line from 'stream' to 'where' allowing for comment char and line continuations */ getline(stream,where) char *where; FILE *stream; { int i; if (get_stripped_line(where,INMAX,stream) == TRUE) { i = my_strlen(where); where[--i] = NUL; while ( where[i-1] == linecont ) { if ( get_stripped_line(where + i -1,INMAX,stream) == FALSE ) panic("end of file before end of line"); i = my_strlen(where); where[--i] = NUL; } if ( i >= INMAX ) { where[INMAX] = NUL; panic2("line too long\n'%s'",where); } return ( TRUE ); } else return ( FALSE ); } get_stripped_line(where,len,stream) char *where; int len; FILE *stream; { int x; /* return a meaningful line */ while ( TRUE ) { if ( fgets(where,len,stream) == NULL ) return( FALSE ); if ( where[my_strlen(where)-1] != '\n' ) { x = my_strlen(where); where[x] = '\n'; where[x+1] = NUL; } /* terminate standard input with a period alone */ if ( (stream == stdin) && EQ(where,".\n") ) return( FALSE ); /* if the line is only or #, skip it */ for ( x = 0; isspace(where[x]) && (where[x] != '\n') ; x++) ; if ( (where[x] == '\n') || (where[x] == '#') ) continue; /* no reason to throw it out... */ return( TRUE ); } /* NOTREACHED */ } struct llist *mkllist( s ) /* make a linked list */ char *s; { int pos; char lname[INMAX]; struct llist *ptr,*ptr2,*retval; pos = 0; retval = NULL; while ( TRUE ) { /* get the next element, may have quotes */ pos = get_element(s,pos,lname); if ( isnull(lname[0]) ) return( retval ); /* found something to add */ ptr = MkListMem(); if ( retval == NULL ) retval = ptr; else { for (ptr2 = retval; ptr2->next != NULL ; ptr2 = ptr2->next ) ; ptr2->next = ptr; } ptr->name = mov_in(lname); ptr->next = NULL; } } get_element(src,p,dest) char *src,*dest; int p; { int i,quotestop; i = 0; dest[0] = NUL; while ( notnull(src[p]) && isspace(src[p]) ) p++; if ( isnull(src[p]) ) return( p ); if ( src[p] == '"' ) { quotestop = TRUE; p++; } else quotestop = FALSE; while ( TRUE ) { if ( isnull(src[p]) ) break; else if (src[p] == BKSLSH) { if (src[p+1] == '"') p++; dest[i++] = src[p++]; } else if ( !quotestop && isspace(src[p]) ) break; else if ( quotestop && (src[p] == '"') ) break; else dest[i++] = src[p++]; } dest[i] = NUL; return( p ); } struct llist *mkexphow(head,target,eflag) /* make an expanded linked list for how */ struct llist *head; char *target; int eflag; { struct llist *p,*p2,*retval; int x; char temp[INMAX]; if ( head == NULL ) { return ( NULL ); } retval = NULL; while ( head != NULL ) { if ( target != NULL ) expand(head->name,temp,target,eflag); else my_strcpy(temp,head->name); p = MkListMem(); for ( x = 0 ; notnull(temp[x]) ; x++ ) if ( !isspace(temp[x]) ) break; p->name = mov_in(temp + x); p->next = NULL; if ( retval == NULL ) retval = p; else { p2 = retval; while ( p2->next != NULL ) p2 = p2->next; p2->next = p; } head = head->next; } return( retval ); } struct llist *mkexpdep(head,target) /* make an expanded linked list for dep*/ struct llist *head; char *target; { struct llist *p,*p2,*p3,*retval; char temp[INMAX]; if ( head == NULL ) { return ( NULL ); } retval = NULL; while ( head != NULL ) { expand(head->name,temp,target, REPT_ERR); p3 = mkllist(temp); while ( p3 != NULL ) { p = MkListMem(); p->name = mov_in(p3->name); p->next = NULL; if ( retval == NULL ) retval = p; else { p2 = retval; while ( p2->next != NULL ) p2 = p2->next; p2->next = p; } p3 = p3->next; } free_list(p3); head = head->next; } return( retval ); } add_suff(lin) char *lin; { struct llist *ptr; /* add *lin to the list suff_head */ if ( lin == NULL ) return; if ( suff_head == NULL ) suff_head = mkllist(lin); else { /* go to the tail of suff_head */ for ( ptr = suff_head; ptr->next != NULL; ptr = ptr->next ); ptr->next = mkllist(lin); } /* do a little error checking */ for ( ptr = suff_head; ptr != NULL; ptr = ptr->next ) if ( ptr->name[0] != '.' ) error2("add_suffix: bad syntax '%s'",ptr->name); } add_s_suff(lext) char *lext; { struct llist *sptr; /* add this extension to suff_list, if its not already there */ for ( sptr = suff_head; sptr != NULL; sptr = sptr->next ) if ( EQ(sptr->name,lext) ) return; /* must not be there... */ suff_head = add_llist(suff_head,lext); } add_macro(mname,expan) char *mname,*expan; { struct macrec *macp,*macp2; if (maclist == NULL) maclist = macp = (struct macrec *)get_mem((UI) sizeof(struct macrec)); else { macp2 = maclist; while (macp2->nextmac != NULL) { if ( EQ(macp2->name,mname) ) { macp2->mexpand = mov_in(expan); /* previous contents not freed cause mostly they were not malloc()-ed */ return; } macp2 = macp2->nextmac; } if ( EQ(macp2->name,mname) ) { /* if the last on the list matches, replace it */ macp2->mexpand = mov_in(expan); return; } macp2->nextmac = macp = (struct macrec *)get_mem((UI) sizeof(struct macrec)); } macp->name = mov_in(mname); macp->mexpand = mov_in(expan); macp->nextmac = NULL; } add_rule2(adep,atarg,arule,aflag) char *adep,*atarg; struct llist *arule; int aflag; { struct rulerec *rulep,*rulep2; if (rulelist == NULL) rulelist = rulep = (struct rulerec *)get_mem((UI) sizeof(struct rulerec)); else { rulep2 = rulelist; while (rulep2->nextrule != NULL) { if ( EQ(rulep2->dep,adep) && EQ(rulep2->targ,atarg) ) { free_list(rulep2->rule); rulep2->rule = arule; return; } rulep2 = rulep2->nextrule; } if ( EQ(rulep2->dep,adep) && EQ(rulep2->targ,atarg) ) { free_list(rulep2->rule); rulep2->rule = arule; return; } rulep2->nextrule = rulep = (struct rulerec *)get_mem((UI) sizeof(struct rulerec)); } rulep->dep = mov_in(adep); rulep->targ = mov_in(atarg); rulep->rule = arule; rulep->def_flag = aflag; rulep->nextrule = NULL; } free_list(head) /* kill a linked list */ struct llist *head; { struct llist *ptr; if ( head == NULL ) return; else if ( head->next == NULL ) { free( head->name ); free( (char *)head ); return; } else { while ( TRUE ) { for ( ptr = head; ptr->next->next != NULL; ptr = ptr->next ) ; free(ptr->next->name); free((char *)ptr->next); ptr->next = NULL; if ( ptr == head ) { free( ptr->name ); free( (char *)ptr); return; } } } } exec_how(cmd) char *cmd; { int pos,this_echo,this_ign,x,i,no_more_flags; int err_ret; char cmdname[INMAXSH]; i = pos = 0; this_echo = !silentf; this_ign = FALSE; no_more_flags = FALSE; while ( TRUE ) { while ( isspace( cmd[pos] ) ) pos++; switch ( cmd[pos] ) { case '@':this_echo = FALSE;break; case '-':this_ign = TRUE;break; default: no_more_flags = TRUE;break; } if (no_more_flags) break; else pos++; } /* get the name of the command */ for (x=pos; !isspace(cmd[x]) && notnull(cmd[x]); x++) cmdname[i++] = cmd[x]; cmdname[i] = NUL; /* echo if appropriate */ if ( this_echo ) { fprintf(stdout," %s\n",cmd+pos); } else if ( !execute && !this_echo) { fprintf(stdout," %s\n",cmd+pos); return(0); } /* if we are not to actually do it... */ if ( !execute ) return(0); #ifdef LC else if( EQ(cmdname,"write-macro") || EQ(cmdname,"WRITE-MACRO") ) { err_ret = w_macros(cmd+x); return( (this_ign) ? 0 : err_ret); } #endif else { err_ret = perform( cmdname, cmd+pos); return( (this_ign) ? 0 : err_ret ); } } perform(cname, syscmd) char *cname; /* the name of the command */ char *syscmd; /* the string to send to 'system' */ { int x,ccode; #ifndef LC int pid; WAIT status; #endif struct llist *largs; char **vargs,**mkargs(); char wholenam[INMAXSH]; /* if there are metacharacters, use 'system' */ for ( x= 0; notnull(syscmd[x]); x++ ) if ( (syscmd[x] == '>') || (syscmd[x] == '<') || (syscmd[x] == '|') || (syscmd[x] == '*') || (syscmd[x] == '?') || (syscmd[x] == '&') ) { return( mysystem(syscmd) ); } /* is this a builtin command? */ if ( findexec(cname,wholenam) == (TIME) 0 ) { /* file doesn't exist -- yes */ return(mysystem(syscmd)); } /* directly exec a file with args */ largs = mkllist(syscmd); vargs = mkargs(largs); #ifndef LC if ( (pid = fork()) == 0 ) { execv(wholenam,vargs); done( -1 ); } free( (char *)vargs ); free_list( largs ); if ( pid < 0 ) { perror(whoami); return( -1 ); } else { while( ((ccode = wait(&status)) != pid) && (ccode != -1) ); if ( pid < 0 ) { perror(whoami); return( -1 ); } return( pr_warning(&status) ); } #else if ( forkv(wholenam,vargs) != 0 ) { perror(whoami); panicstop(); } free( (char *)vargs ); free_list( largs ); ccode = wait(); return( pr_warning(&ccode) ); #endif } #ifndef LC mysystem(cmd) /* use the SHELL to execute a command line, print warnings */ char *cmd; { int ccode,pid; WAIT status; if ( (pid = fork()) == 0 ) { execl(SHELL, "sh", "-c", cmd, (char *) 0); done( -1 ); } if ( pid < 0 ) { perror(whoami); /* say why we couldn't fork */ return( -1 ); } else { while ( ((ccode = wait(&status)) != pid) && (ccode != -1)); if ( pid < 0 ) { /* no children? */ perror(whoami); return( -1 ); } return( pr_warning(&status) ); } } #else mysystem(cmd) char *cmd; { extern int _oserr; if ( system(cmd) != 0 ) { if ( _oserr == 0 ) panic("Can't process system() args"); else panic("error calling system()"); } return( pr_warning(wait()) ); } #endif pr_warning(s) /* print a warning, if any */ WAIT *s; { #ifdef BSD4.2 if ( (s->w_T.w_Termsig == 0) && (s->w_T.w_Retcode == 0) ) return( 0 ); else { if ( s->w_T.w_Termsig ) fprintf(stderr,"%s: received signal %x\n",whoami,s->w_T.w_Termsig); else { fprintf(stderr,"%s: Error code %x",whoami,s->w_T.w_Retcode); fprintf(stderr,"%s\n",( stopOnErr ) ? "" : " (ignored)"); } return( -1 ); } } #else if ( *s == 0 ) return( 0 ); else { fprintf(stderr,"%s:",whoami); if ( *s & 0xFF) { fprintf(stderr," received signal %x\n",*s & 0xFF); } else { fprintf(stderr," Error code %x",(*s & ~0xFF) >> 8); fprintf(stderr,"%s\n",( stopOnErr ) ? "" : " (ignored)"); } return( -1 ); } } #endif char **mkargs(arglist) struct llist *arglist; { struct llist *ptr; char **retval; int i; /* count up the number of args */ for ( i=0,ptr = arglist; ptr != NULL; ptr = ptr->next,i++); retval = (char **)get_mem(((UI)(i+1)*sizeof(char *))); for ( i=0,ptr=arglist; ptr != NULL; ptr = ptr->next,i++) retval[i] = ptr->name; retval[i] = NULL; return( retval ); } char *get_mem(size) UI size; { char *p,*malloc(); if ((p = malloc(size)) == 0) panic("Ran out of memory"); return(p); } struct llist *MkListMem() { struct llist *p; char *malloc(); if ((p = (struct llist *)malloc(sizeof(struct llist))) == 0 ) panic("Ran out of memory"); return(p); } char *mov_in(string) /* return pointer to fresh memory with copy of string*/ char *string; { char *ptr; ptr = get_mem((UI)(my_strlen(string) + 1)); my_strcpy(ptr,string); return(ptr); } mkpathlist() { char *getenv(); /* go get the PATH variable, and turn it into a linked list */ char *path; path_head = NULL; path = getenv("PATH"); add_path(path); } squeezesp(to,from) char *to,*from; { /* copy from from to to, squeezing out any spaces */ if ( from == NULL ) return; while( *from ) { if ( isspace(*from) ) from++; else *to++ = *from++; } *to = NUL; } TIME findexec(s,where) char *s,*where; { int i; TIME retval; struct llist *ptr; my_strcpy(where,s); /* search for switch character, if present, then this is full name and we won't search the path */ for ( i = 0; notnull(s[i]); i++) if ( s[i] == switchc ) return(getmodified(where)); if ( (retval=getmodified(where)) != (TIME) 0 ) return( retval ); /* if there is no prefix to this file name */ for ( ptr = path_head; ptr != NULL; ptr = ptr->next) { my_strcpy( where, ptr->name); strcat( where, s); if ( (retval=getmodified(where)) != (TIME) 0 ) return( retval ); } return( (TIME)0 ); /* didn't find it */ } TIME getmodified(s) char *s; { struct stat statb; if ( stat(s,&statb) != 0 ) { if ( errno == ENOENT ) return( (TIME) 0 ); else { perror(whoami); if (stopOnErr) panicstop(); else return( (TIME) 0 ); } } else { return( statb.st_mtime ); } /* NOTREACHED */ } add_path(p) char *p; { char temp[50]; int i,k; /* add to the linked list path_head */ k = i = 0; squeezesp(p,p); if ( p == NULL ) return; while ( TRUE ) { while ( notnull(p[k]) && (p[k] != PATHCHAR ) ) temp[i++] = p[k++]; if ( temp[i-1] != switchc ) temp[i++] = switchc; temp[i] = NUL; if ( i == 0 ) return; path_head = add_llist(path_head,temp); if ( isnull(p[k]) ) { return; } k++; i = 0; } } /* emulation of getenv() */ char *getenv(s) char *s; { char **p,*tp,*ematch(); p = ext_env; while ( *p != NULL ) { if ( (tp = ematch(s,*p)) != NULL ) return(mov_in(tp)); p++; } return( NULL ); } char *ematch(s,p) char *s,*p; { /* if match up to the '=', return second half */ while( *s != NULL ) if ( *s++ != *p++ ) return( NULL ); if ( *p++ != '=' ) return( NULL ); return( p ); } #ifdef LC #include stat(st,ptr) char *st; struct stat *ptr; { int fd; TIME getft(),retval; /* return 0 for success, -1 on failure */ fd = open(st,O_RDONLY); if ( fd < 0 ) return( -1 ); retval = getft(fd); if ( retval == (TIME)(-1) ) return( -1 ); else { ptr->st_mtime = retval; return( 0 ); } } #define W_PERLINE 4 #define W_BUFLEN 80 char w_buf[W_BUFLEN]; int w_count; int w_first; w_macros(list) char *list; { FILE *tfp; struct llist *ptr,*ptr2,*mkllist(); if ( (tfp = fopen(tfilename,"w")) == NULL ) { warn2("Can't write to '%s'",tfilename); return( 1 ); /* non-zero is error */ } w_buf[0] = NUL; w_count = 0; w_first = TRUE; ptr = mkllist(list); for ( ptr2 = ptr ; ptr2 != NULL ; ptr2 = ptr2->next ) if ( w_mac2(ptr2->name,tfp,ptr2->next) != 0 ) return( 1 ); /* non-zero is error */ free_list(ptr); if ( w_buf[0] != NUL ) fprintf(tfp,"%s\n",w_buf); fclose(tfp); return( 0 ); } w_mac2(w_word,stream,n) char *w_word; FILE *stream; struct llist *n; { /* write to stream */ if ( !linkerf ) { fprintf(stream,"%s\n",w_word); return( 0 ); } else { if ( w_first ) { my_strcpy(w_buf,w_word); w_first = FALSE; w_count++; } else { strcat(w_buf," + "); if ( (my_strlen(w_buf) + strlen(w_word)) > W_BUFLEN ) { fprintf(stream,"%s\n",w_buf); w_buf[0] = NUL; w_count = 1; } strcat(w_buf,w_word); w_count++; if ( w_count >= W_PERLINE ) { w_count = 0; w_first = TRUE; fprintf(stream,"%s %c\n",w_buf,( n == NULL )?' ':'+'); w_buf[0] = NUL; } else w_count++; } return( 0 ); } /* NOTREACHED */ } warn2(s1,s2) char *s1,*s2; { fprintf(stderr,"%s: ",whoami); fprintf(stderr,s1,s2); fprintf(stderr,"\n"); } #endif done(n) int n; { _cleanup(); exit(n); } my_strlen (src) char *src; { if (src == NULL) return 0; return strlen (src); } my_strcpy (dest, src) char *dest, *src; { if (src == NULL) *dest = '\0'; else return strcpy (dest, src); } /* more - terminal pager Author: Brandon S. Allbery */ /* Pager commands: * display next page * scroll up 1 line * q quit */ #define reverse() write(1, "\033z\160", 3) /* reverse video */ #define normal() write(1, "\033z\7", 3) /* undo reverse() */ #define clearln() write(1, "\r\033~0", 4) /* clear line */ #define LINES 23 /* lines/screen (- 1 to retain last line) */ #define COLS 80 /* columns/line */ #define TABSTOP 8 /* tabstop expansion */ #include #include extern int byebye(); extern char *index(); int line = 0; /* current terminal line */ int col = 0; /* current terminal column */ int fd = -1; /* terminal file descriptor (/dev/tty) */ struct sgttyb ttymode; /* and the terminal modes */ char ibuf[1024]; /* input buffer */ char obuf[1024]; /* output buffer */ int ibl = 0; /* chars in input buffer */ int ibc = 0; /* position in input buffer */ int obc = 0; /* position in output buffer (== chars in) */ int isrewind = 0; /* flag: ' command -- next input() rewind */ int isdone = 0; /* flag: return EOF next read even if not */ main(argc, argv) char **argv; { char ch; int fd, arg; signal(SIGINT, byebye); fd = 0; cbreak(); if (argc < 2) while ((ch = input(fd)) != 0) output(ch); else for (arg = 1; argv[arg] != (char *) 0; arg++) { if ((fd = open(argv[arg], 0)) == -1) { write(1, "more: cannot open ", 18); write(1, argv[arg], strlen(argv[arg])); write(1, "\n", 1); nocbreak(); exit(1); } while ((ch = input(fd)) != 0) output(ch); close(fd); if (argv[arg + 1] != (char *) 0) { oflush(); if (isdone) { /* 'n' command */ reverse(); write(1, "*** Skipping to next file ***\n", 30); normal(); isdone = 0; } reverse(); write(1, "--More-- (Next file: ", 21); write(1, argv[arg + 1], strlen(argv[arg + 1])); write(1, ")", 1); normal(); switch (wtch()) { case ' ': case '\'': case 'n': case 'N': line = 0; break; case '\r': case '\n': line = LINES - 1; break; case 'q': case 'Q': clearln(); byebye(); } clearln(); } } oflush(); byebye(); } input(fd) { if (isdone) { ibl = 0; ibc = 0; return 0; } if (isrewind) { lseek(fd, 0L, 0); ibl = 0; ibc = 0; isrewind = 0; } if (ibc == ibl) { ibc = 0; if ((ibl = read(fd, ibuf, sizeof ibuf)) <= 0) return 0; } return ibuf[ibc++]; } output(c) char c; { if (obc == sizeof obuf) { lwrite(1, obuf, sizeof obuf); obc = 0; } if (!isrewind) obuf[obc++] = c; } oflush() { if (!isdone) lwrite(1, obuf, obc); obc = 0; } lwrite(fd, buf, len) char *buf; unsigned len; { unsigned here, start; char cmd; start = 0; here = 0; while (here != len) { cmd = '\0'; switch (buf[here++]) { case '\n': col = 0; if (++line == LINES) { write(fd, buf + start, here - start); reverse(); write(1, "--More--", 8); normal(); cmd = wtch(); clearln(); line = 0; start = here; } break; case '\r': col = 0; break; case '\b': if (col != 0) col--; else { line--; col = COLS - 1; } break; case '\t': do { col++; } while (col % TABSTOP != 0); break; default: if (++col == COLS) { col = 0; if (++line == LINES) { write(fd, buf + start, here - start); reverse(); write(1, "--More--", 8); normal(); cmd = wtch(); clearln(); line = 0; start = here; } } } switch (cmd) { case '\0': break; case ' ': line = 0; break; case '\r': case '\n': line = LINES - 1; break; case 'q': case 'Q': byebye(); case '\'': isrewind = 1; reverse(); write(1, "*** Back ***\n", 13); normal(); return; case 'n': case 'N': isdone = 1; return; default: break; } } if (here != start) write(fd, buf + start, here - start); } wtch() { char ch; do { read(fd, &ch, 1); } while (index(" \r\nqQ'nN", ch) == (char *) 0); return ch; } cbreak() { if (fd != -1) return; if ((fd = open("/dev/tty", 0)) == -1) { write(2, "OOPS -- can't open /dev/tty\n", 28); exit(1); } ioctl(fd, TIOCGETP, &ttymode); ttymode.sg_flags |= CBREAK; ttymode.sg_flags &= ~ECHO; ioctl(fd, TIOCSETP, &ttymode); /* NB: add TIOCSETN! */ } nocbreak() { if (fd == -1) return; ttymode.sg_flags &= ~CBREAK; ttymode.sg_flags |= ECHO; ioctl(fd, TIOCSETP, &ttymode); close(fd); fd = -1; } byebye() { nocbreak(); exit(0); } /* fdisk - partition a hard disk Author: Jakob Schripsema */ /* * First run the DOS utilities FDISK and FORMAT. FDISK * puts the boot code in sector 0. * Then run fdisk * * fdisk /dev/hdx (MINIX) * fdisk x: (DOS) * * Compiling * * cc -o fdisk -DUNIX fdisk.c (MINIX) * cl -DDOS fdisk.c (DOS with MS C compiler) */ #include "stdio.h" #define UNIX /* for MINIX */ #ifdef DOS #include #endif /* * Constants */ #define NHEAD 4 /* # heads */ #define NSEC 17 /* sectors / track */ #define SECSIZE 512 /* sector size */ #define OK 0 #define ERR 1 #define TABLEOFFSET 0x1be /* offset in boot sector*/ /* * Description of entry in partition table */ struct part_entry { char bootind; /* boot indicator 0/0x80 */ char start_head; /* head value for first sector */ char start_sec; /* sector value for first sector*/ char start_cyl; /* track value for first sector */ char sysind; /* system indicator 00=?? 01=DOS*/ char last_head; /* head value for last sector */ char last_sec; /* sector value for last sector */ char last_cyl; /* track value for last sector */ long lowsec; /* logical first sector */ long size; /* size of partion in sectors */ }; /* * Globals */ char secbuf[SECSIZE]; char *devname; char *dosstr = " DOS "; char *ndosstr = "Non-DOS"; #ifdef DOS union REGS regs; struct SREGS sregs; int drivenum; #endif #ifdef UNIX int devfd; #endif main(argc,argv) int argc; char *argv[]; { char ch; /* init */ if (argc != 2) { printf("Usage: fdisk /dev/hdx\n"); exit(1); } devname = argv[1]; getboot(secbuf); /* get boot sector */ do { dpl_partitions(); print_menu(); ch = get_a_char(); putchar('\n'); switch (ch) { case 'c' : change_table(); break; case 'w' : if (chk_table() == OK) { putboot(secbuf); exit(0); } break; case 'l' : load_from_file(); break; case 's' : save_to_file(); break; case 'p' : dump_table(); break; case 'q' : exit(0); default : printf(" %c ????\n",ch); } } while (1); } /* * Read boot sector */ #ifdef DOS getboot(buffer) char *buffer; { segread(&sregs); /* get ds */ if (devname[1] != ':') { printf("Invalid drive %s\n",devname); exit(1); } if (*devname >= 'a') *devname += 'A' - 'a'; drivenum = (*devname - 'C') & 0xff; if (drivenum < 0 || drivenum > 7) { printf("Funny drive number %d\n",drivenum); exit(1); } regs.x.ax = 0x201; /* read 1 sectors */ regs.h.ch = 0; /* track */ regs.h.cl = 1; /* first sector = 1 */ regs.h.dh = 0; /* head = 0 */ regs.h.dl = 0x80+drivenum;/* drive = 0 */ sregs.es = sregs.ds; /* buffer address */ regs.x.bx = (int)secbuf; int86x(0x13,®s,®s,&sregs); if (regs.x.cflag) { printf("Cannot read boot sector\n"); exit(1); } } #endif #ifdef UNIX getboot(buffer) char *buffer; { devfd = open(devname,2); if (devfd < 0) { printf("Cannot open device %s\n",devname); exit(1); } if (read(devfd,buffer,SECSIZE) != SECSIZE) { printf("Cannot read boot sector\n"); exit(2); } } #endif /* * Write boot sector */ #ifdef DOS putboot(buffer) char *buffer; { regs.x.ax = 0x301; /* read 1 sectors */ regs.h.ch = 0; /* track */ regs.h.cl = 1; /* first sector = 1 */ regs.h.dh = 0; /* head = 0 */ regs.h.dl = 0x80+drivenum;/* drive = 0 */ sregs.es = sregs.ds; /* buffer address */ regs.x.bx = (int)secbuf; int86x(0x13,®s,®s,&sregs); if (regs.x.cflag) { printf("Cannot write boot sector\n"); exit(1); } } #endif #ifdef UNIX putboot(buffer) char *buffer; { int r; if (lseek(devfd,0L,0) < 0) { printf("Seek error during write\n"); exit(1); } if (write(devfd,buffer,SECSIZE) != SECSIZE) { printf("Write error\n"); exit(1); } sync(); } #endif /* * Load buffer from file */ load_from_file() { char file[80]; int fd; printf("Enter file name: "); gets(file); #ifdef UNIX fd = open(file,0); #endif #ifdef DOS fd = open(file,0x8000); #endif if (fd < 0) { fprintf(stderr,"Cannot load from %s\n",file); exit(1); } if (read(fd,secbuf,SECSIZE) != SECSIZE) { fprintf(stderr,"Read error\n"); exit(1); } close(fd); } /* * Save to file */ save_to_file() { char file[80]; int fd; printf("Enter file name: "); gets(file); #ifdef UNIX fd = creat(file,0644); #endif #ifdef DOS fd = creat(file,0644); if (fd < 0) { fprintf(stderr,"Cannot creat %s\n",file); exit(1); } close(fd); fd = open(file,0x8001); #endif if (fd < 0) { fprintf(stderr,"Cannot save to %s\n",file); exit(1); } if (write(fd,secbuf,SECSIZE) != SECSIZE) { fprintf(stderr,"Write error\n"); exit(1); } close(fd); } /* * Dump partition table */ dump_table() { struct part_entry *pe; int i; pe = (struct part_entry *)&secbuf[TABLEOFFSET]; printf("\n --first--- ---last---\n"); printf("Prt ac hd sec cyl sys hd sec cyl low size\n"); for (i = 1 ; i < 5 ; i++) { printf(" %x %2x %x %2x %2x %2x %x %2x %2x %6D %9D\n", i, pe->bootind & 0xff, pe->start_head & 0xff, pe->start_sec & 0xff, pe->start_cyl & 0xff, pe->sysind & 0xff, pe->last_head & 0xff, pe->last_sec & 0xff, pe->last_cyl & 0xff, pe->lowsec, pe->size); pe++; } } /* * Display partition table */ dpl_partitions() { struct part_entry *pe; int i; printf("\nPartition Type Begin End Active\n"); pe = (struct part_entry *)&secbuf[TABLEOFFSET]; for (i = 1 ; i <= 4 ; i++) { dpl_entry(i,pe); pe++; } } /* * Display an entry */ dpl_entry(number,entry) int number; struct part_entry *entry; { int low_cyl,high_cyl,temp; char *typestring; char active; if (entry->sysind == 0x01) typestring = dosstr; else typestring = ndosstr; printf("%5d %s ",number,typestring); temp = entry->start_sec & 0xc0; low_cyl = (entry->start_cyl & 0xff) + (temp << 2); temp = entry->last_sec & 0xc0; high_cyl = (entry->last_cyl & 0xff) + (temp << 2); printf("%4d %4d",low_cyl,high_cyl); if ((entry->bootind & 0xff) == 0x80) active = 'A'; else active = 'N'; printf(" %c\n",active); } /* * Check partition table */ chk_table() { struct part_entry *pe; int i; int active; long limit; pe = (struct part_entry *)&secbuf[TABLEOFFSET]; limit = 0L; active = 0; for (i = 1 ; i < 5 ; i++) { if (pe->size == 0L) return(OK); if (pe->lowsec <= limit) { printf("Overlap between part. %d and %d\n",i,i-1); return(ERR); } limit = pe->lowsec + pe->size - 1L; if (pe->bootind == 0x80) active++; pe++; } if (active > 1) { printf("%d active partitions\n",active); return(ERR); } return(OK); } /* * Check entry * head/sector/track info must match logical sector number * Used to check 'foreign' partition table during debugging */ chk_entry(entry) struct part_entry *entry; { char head; char sector; char track; sec_to_hst(entry->lowsec,&head,§or,&track); if ( (head != entry->start_head) || (sector != entry->start_sec) || (track != entry->start_cyl)) return(ERR); sec_to_hst(entry->lowsec + entry->size - 1L,&head,§or,&track); if ( (head != entry->last_head) || (sector != entry->last_sec) || (track != entry->last_cyl)) return(ERR); return(OK); } /* * Convert a logical sector number to * head / sector / track */ sec_to_hst(logsec,hd,sec,cyl) long logsec; char *hd,*sec,*cyl; { int bigcyl; bigcyl = logsec / (NHEAD * NSEC); *sec = (logsec % NSEC) + 1 + ((bigcyl >> 2) & 0xc0); *cyl = bigcyl & 0xff; *hd = (logsec % (NHEAD * NSEC)) / NSEC; } /* * change partition table */ change_table() { struct part_entry *pe; int i,temp,low_cyl,high_cyl; char ch; pe = (struct part_entry *)&secbuf[TABLEOFFSET]; for (i = 1 ; i <= 4 ; i++) { temp = pe->start_sec & 0xc0; low_cyl = (pe->start_cyl & 0xff) + (temp << 2); temp = pe->last_sec & 0xc0; high_cyl = (pe->last_cyl & 0xff) + (temp << 2); printf("Partition %d from %d to %d. Change? (y/n) ", i,low_cyl,high_cyl); ch = get_a_char(); if (ch == 'y' || ch == 'Y') get_partition(pe); pe++; } putchar('\n'); } /* * Get partition info : first & last cylinder */ get_partition(entry) struct part_entry *entry; { char buf[80]; int first,last; long low,high; rtu char ch; printf(" Enter first cylinder: "); gets(buf); sscanf(buf,"%d",&first); printf(" Enter last cylinder: "); gets(buf); sscanf(buf,"%d",&last);; if (first == 0 && last == 0) { entry->bootind = 0; entry->start_head = 0; entry->start_sec = 0; entry->start_cyl = 0; entry->sysind = 0; entry->last_head = 0; entry->last_sec = 0; entry->last_cyl = 0; entry->lowsec = 0L; entry->size = 0L ; return; } low = first & 0xffff; low = low * NSEC * NHEAD; if (low == 0) low = 1; /* sec0 is master boot record */ high = last & 0xffff; high = (high + 1)*NSEC*NHEAD - 1; entry->lowsec = low; entry->size = high - low + 1; sec_to_hst(low, &entry->start_head, &entry->start_sec, &entry->start_cyl); sec_to_hst(high, &entry->last_head, &entry->last_sec, &entry->last_cyl); printf(" DOS partition? (y/n) "); ch = get_a_char(); if (ch == 'y' || ch == 'Y') entry->sysind = 1; else entry->sysind = 0; printf(" Active partition? (y/n) "); ch = get_a_char(); if (ch == 'y' || ch == 'Y') entry->bootind = 0x80; else entry->bootind = 0; } /* * Read 1 character and discard rest of line */ get_a_char() { char ch; ch = getchar(); if (ch != '\n') while (getchar() != '\n'); return(ch); } print_menu() { printf("\nType a command letter, then a carriage return:\n"); printf(" c - change a partition\n"); printf(" w - write changed partition table back to disk and exit\n"); printf(" q - quit without making any changes\n"); printf(" s - save boot block (including partition table) on a file\n"); printf(" l - load boot block (including partition table) from a file\n"); printf(" p - print partition table\n\n"); } /* fix - combine file and diff listing Author: Erik Baalbergen */ /* Notes: * files old and old.fix are equal after the following commands diff old new > difflist fix old difflist > old.fix * the diff output is assumed to be produced by my diff program. * the difflist has the following form: difflist ::= chunk* chunk ::= append | delete | change ; append ::= n1 'a' n2 [',' n3]? '\n' ['> ' line '\n'](n3 - n2 + 1) delete ::= n1 [',' n2]? 'd' n3 '\n' ['< ' line '\n'](n2 - n1 + 1) change ::= n1 [',' n2]? 'c' n3 [',' n4]? '\n' ['< ' line '\n'](n2 - n1 + 1) '---\n' ['> ' line '\n'](n4 - n3 + 1) where - n[1234] is an unsigned integer - "[pat](expr)" means "(expr) occurences of pat" - "[pat]?" means "either pat or nothing" * the information in the diff listing is checked against the file to which it is applied; an error is printed if there is a conflict */ #include extern char *fgets(); extern FILE *fopen(); #define LINELEN 1024 char *prog = 0; char * getline(fp, b) FILE *fp; char *b; { if (fgets(b, LINELEN, fp) == NULL) fatal("unexpected eof"); return b; } #define copy(str) printf("%s", str) main(argc, argv) char **argv; { char cmd, *fl, *fd, obuf[LINELEN], nbuf[LINELEN]; int o1, o2, n1, n2, here; FILE *fpf, *fpd; prog = argv[0]; if (argc != 3) fatal("use: %s original-file diff-list-file", prog); if ((fpf = fopen(argv[1], "r")) == NULL) fatal("can't read %s", argv[1]); if ((fpd = fopen(argv[2], "r")) == NULL) fatal("can't read %s", argv[2]); here = 0; while (getcommand(fpd, &o1, &o2, &cmd, &n1, &n2)) { while (here < o1 - 1) { here++; copy(getline(fpf, obuf)); } switch (cmd) { case 'c': case 'd': if (cmd == 'd' && n1 != n2) fatal("delete count conflict"); while (o1 <= o2) { fl = getline(fpf, obuf); here++; fd = getline(fpd, nbuf); if (strncmp(fd, "< ", 2)) fatal("illegal delete line"); if (strcmp(fl, fd + 2)) fatal("delete line conflict"); o1++; } if (cmd == 'd') break; if (strcmp(getline(fpd, nbuf), "---\n")) fatal("illegal separator in chunk"); /*FALLTHROUGH*/ case 'a': if (cmd == 'a') { if (o1 != o2) fatal("append count conflict"); copy(getline(fpf, obuf)); here++; } while (n1 <= n2) { if (strncmp(getline(fpd, nbuf), "> ", 2)) fatal("illegal append line"); copy(nbuf + 2); n1++; } break; } } while (fgets(obuf, LINELEN, fpf) != NULL) copy(obuf); exit(0); } isdigit(c) char c; { return c >= '0' && c <= '9'; } char * range(s, p1, p2) char *s; int *p1, *p2; { register int v1 = 0, v2; while (isdigit(*s)) v1 = 10 * v1 + *s++ - '0'; v2 = v1; if (*s == ',') { s++; v2 = 0; while (isdigit(*s)) v2 = 10 * v2 + *s++ - '0'; } if (v1 == 0 || v2 == 0 || v1 > v2) fatal("illegal range"); *p1 = v1; *p2 = v2; return s; } getcommand(fp, o1, o2, pcmd, n1, n2) FILE *fp; int *o1, *o2, *n1, *n2; char *pcmd; { char buf[LINELEN]; register char *s; char cmd; if ((s = fgets(buf, LINELEN, fp)) == NULL) return 0; s = range(s, o1, o2); if ((cmd = *s++) != 'a' && cmd != 'c' && cmd != 'd') fatal("illegal command"); s = range(s, n1, n2); if (*s != '\n' && s[1] != '\0') fatal("extra characters at end of command: %s", s); *pcmd = cmd; return 1; } fatal(s, a) char *s, *a; { fprintf(stderr, "%s: fatal: ", prog); fprintf(stderr, s, a); fprintf(stderr, "\n"); exit(1); } /* printenv - print the current environment Author: Richard Todd */ main () { extern char **environ; char **sptr; for (sptr = environ ; *sptr ; ++sptr) { prints("%s\n",*sptr); } } /* readfs - read a MINIX file system Author: Paul Polderman */ /* Command: readfs - read and extract a MINIX filesystem. Syntax: readfs [-li] block-special [directory] Flags: -l: extract all directories and files from the filesystem and produce a mkfs-listing on standard output. -i: give the listing, but do not extract files. Examples: readfs /dev/fd1 # extract all files from /dev/fd1. readfs -i /dev/hd2 # see what's on /dev/hd2. readfs -l /dev/at0 rootfs # extract and list the filesystem # of /dev/at0 and put the tree # in the directory `rootfs'. Readfs reads a MINIX filesystem and extracts recursively all directories and files, and (optionally) produces a mkfs-listing of them on standard output. The root directory contents are placed in the current directory, unless a directory is given as argument, in which case the contents are put there. Readfs tries to restore the attributes (mode/uid/gid/time) of the files extracted to those of the original files. Special files are created as ordinary files, but the mkfs-listing enables mkfs to restore them to original. */ #include #include "fs/const.h" #include "h/type.h" #include "h/const.h" #include "fs/type.h" #include "fs/buf.h" #include "fs/super.h" #undef printf /* Definition used only in the kernel */ extern file_pos lseek(); /* * Compile with -I/user0/ast/minix * (i.e. the directory containing the MINIX system sources) * * Author: Paul Polderman (polder@cs.vu.nl) April 1987 */ char verbose = 0; /* give a mkfs-listing of the filesystem */ /* and extracts its contents. */ char noaction = 0; /* just give a mkfs-listing, do not extract files. */ struct super_block sb; char pathname[1024]; main(argc, argv) int argc; char **argv; { switch (argc) { case 2: pathname[0] = '\0'; readfs(argv[1], pathname); break; case 3: if (argv[1][0] == '-') { get_flags(&argv[1][1]); pathname[0] = '\0'; readfs(argv[2], pathname); } else { strcpy(pathname, argv[2]); readfs(argv[1], pathname); } break; case 4: if (argv[1][0] == '-') { get_flags(&argv[1][1]); strcpy(pathname, argv[3]); readfs(argv[2], pathname); break; } /* else fall through .. */ default: fprintf(stderr,"Usage: %s [-li] [dirname]\n",argv[0]); exit(1); } exit(0); } get_flags(flags) register char *flags; { while (*flags) { switch (*flags) { case 'L': case 'l': verbose = 1; break; case 'I': case 'i': noaction = 1; verbose = 1; break; default: fprintf(stderr, "Bad flag: %c\n", *flags); break; } flags++; } } readfs(special_file, directory) char *special_file, *directory; /* * Readfs: opens the given special file (with MINIX filesystem), * and extracts its contents into the given directory. */ { d_inode root_inode; int special, inum; file_pos super_b; umask(0); /* Open the special file */ if ( (special = open(special_file, 0)) < 0) { fprintf(stderr, "cannot open %s\n", special_file); return; } /* Read the superblock */ super_b = (file_pos) SUPER_BLOCK * (file_pos) BLOCK_SIZE; if (lseek(special, super_b, 0) != super_b) { fprintf(stderr, "cannot seek to superblock\n"); return; } if (read(special, &sb, sizeof(struct super_block)) != sizeof(struct super_block)) { fprintf(stderr, "cannot read superblock\n"); return; } /* Is it really a MINIX filesystem ? */ if (sb.s_magic != SUPER_MAGIC) { fprintf(stderr, "%s is not a valid MINIX filesystem\n", special_file); return; } /* Fetch the inode of the root directory */ if (get_inode(special, (inode_nr) ROOT_INODE, &root_inode) < 0) { fprintf(stderr, "cannot get inode of root directory\n"); return; } /* Print number of zones and inodes */ if (verbose) printf("boot\n%d %d\n", sb.s_nzones, sb.s_ninodes); /* Extract (recursively) the root directory */ dump_dir(special, &root_inode, directory); } /* * different type of blocks: (used in routine get_block for caching) */ #define B_INODE 0 /* Cache #0 is the inode cache */ #define B_INDIRECT 1 /* Cache #1 is the (dbl) indirect block cache */ #define B_DATA 2 /* No cache for data blocks (only read once) */ int get_inode(fd, inum, ip) int fd; inode_nr inum; d_inode *ip; /* * Get inode `inum' from the MINIX filesystem. (Uses the inode-cache) */ { struct buf bp; block_nr block; block_nr ino_block; unshort ino_offset; int r; /* Calculate start of i-list */ block = SUPER_BLOCK + 1 + sb.s_imap_blocks + sb.s_zmap_blocks; /* Calculate block with inode inum */ ino_block = ((inum - 1) / INODES_PER_BLOCK); ino_offset = ((inum - 1) % INODES_PER_BLOCK); block += ino_block; /* Fetch the block */ if (get_block(fd, block, &bp, B_INODE) == 0) { bcopy(&bp.b_inode[ino_offset], ip, sizeof(d_inode)); return(0); } /* Oeps, foutje .. */ fprintf(stderr, "cannot find inode %d\n", inum); return(-1); } static int indent = 0; /* current indent (used for mkfs-listing) */ dump_dir(special, ip, directory) int special; d_inode *ip; char *directory; /* * Make the given directory (if non-NULL), * and recursively extract its contents. */ { register dir_struct *dp; register int n_entries; register char *name; block_nr b = 0; d_inode dip; struct buf bp; if (verbose) { show_info(directory, ip, ""); indent++; } if (!noaction && *directory) { /* Try to make the directory if not already there */ if (mkdir(directory) != 0 || chdir(directory) < 0) { fprintf(stderr,"mkdir %s failed\n", directory); return; } } for (name = directory; *name; name++) /* Find end of pathname */ ; *name++ = '/'; /* Add trailing slash */ n_entries = (int) (ip->i_size / (file_pos) sizeof(dir_struct)); while (n_entries > 0) { /* Read next block of the directory */ if (get_fileblock(special, ip, b, &bp) < 0) return(-1); dp = &bp.b_dir[0]; if (b++ == (block_nr) 0) { dp += 2; /* Skip "." and ".." */ n_entries -= 2; } /* Extract the files/directories listed in the block */ while (n_entries-- > 0 && dp < &bp.b_dir[NR_DIR_ENTRIES]) { if (dp->d_inum != (inode_nr) 0) { if (get_inode(special, dp->d_inum, &dip) < 0) { /* Bad luck */ dp++; continue; } /* Add new pathname-component to `pathname'. */ strncpy(name, dp->d_name, NAME_SIZE); name[NAME_SIZE] = '\0'; /* Call the right routine */ if ((dip.i_mode & I_TYPE) == I_DIRECTORY) dump_dir(special, &dip, name); else dump_file(special, &dip, name); } dp++; /* Next entry, please. */ } } *--name = '\0'; /* Restore `pathname' to what it was. */ if (!noaction && *directory) { chdir(".."); /* Go back up. */ restore(directory, ip); /* Restore mode/owner/accesstime */ } if (verbose) { do_indent(--indent); /* Let mkfs know we are done */ printf("$\n"); /* with this directory. */ } } dump_file(special, ip, filename) int special; d_inode *ip; char *filename; /* * Extract given filename from the MINIX-filesystem, * and store it on the local filesystem. */ { dir_struct *dp; int file; block_nr b = 0; struct buf bp; file_pos size; if (verbose) show_info(filename, ip, pathname); if (noaction) return(0); if (access(filename, 0) == 0) { /* Should not happen, but just in case .. */ fprintf(stderr, "Will not create %s: file exists\n", filename); return(-1); } if ( (file = creat(filename, (ip->i_mode & ALL_MODES) )) < 0) { fprintf(stderr, "cannot create %s\n", filename); return(-1); } /* Don't try to extract /dev/hd0 */ if ((ip->i_mode & I_TYPE) == I_REGULAR) { size = ip->i_size; while (size > (file_pos) 0) { /* Get next block of file */ if (get_fileblock(special, ip, b++, &bp) < 0) { close(file); return(-1); } /* Write it to the file */ if (size > (file_pos) BLOCK_SIZE) write(file, bp.b_data, BLOCK_SIZE); else write(file, bp.b_data, (int) size); size -= (file_pos) BLOCK_SIZE; } } close(file); restore(filename, ip); /* Restore mode/owner/filetimes */ return(0); } #define zone_shift (sb.s_log_zone_size) /* zone to block ratio */ int get_fileblock(special, ip, b, bp) int special; d_inode *ip; block_nr b; struct buf *bp; /* * Read the `b'-th block fr‚„…†‡ˆ‰Šom the file whose inode is `ip'. */ { zone_nr zone, ind_zone; block_nr z, zone_index; int r; /* Calculate zone in which the datablock number is contained */ zone = (zone_nr) (b >> zone_shift); /* Calculate index of the block number in the zone */ zone_index = b - ((block_nr) zone << zone_shift); /* Go get the zone */ if (zone < (zone_nr) NR_DZONE_NUM) { /* direct block */ zone = ip->i_zone[zone]; z = ((block_nr) zone << zone_shift) + zone_index; r = get_block(special, z, bp, B_DATA); return(r); } /* The zone is not a direct one */ zone -= (zone_nr) NR_DZONE_NUM; /* Is it single indirect ? */ if (zone < (zone_nr) NR_INDIRECTS) { /* single indirect block */ ind_zone = ip->i_zone[NR_DZONE_NUM]; } else { /* double indirect block */ /* Fetch the double indirect block */ ind_zone = ip->i_zone[NR_DZONE_NUM + 1]; r = get_block(special, (block_nr) ind_zone, bp, B_INDIRECT); if (r < 0) return(r); /* Extract the indirect zone number from it */ zone -= (zone_nr) NR_INDIRECTS; ind_zone = bp->b_ind[zone / (zone_nr) NR_INDIRECTS]; zone %= (zone_nr) NR_INDIRECTS; } /* Extract the datablock number from the indirect zone */ r = get_block(special, (block_nr) ind_zone, bp, B_INDIRECT); if (r < 0) return(r); zone = bp->b_ind[zone]; /* Calculate datablock number to be fetched */ z = ((block_nr) zone << zone_shift) + zone_index; r = get_block(special, z, bp, B_DATA); return(r); } /* * The following routines simulate a LRU block cache. * * Definition of a cache block: */ struct cache_block { block_nr b_block; /* block number of block */ long b_access; /* counter value of last access */ char b_buf[BLOCK_SIZE]; /* buffer for block */ }; #define NR_CACHES 2 /* total number of caches */ #define NR_CBLOCKS 5 /* number of blocks in a cache */ static struct cache_block cache[NR_CACHES][NR_CBLOCKS]; static long counter = 0L; /* Counter used as a sense of time. */ /* Incremented after each cache operation. */ get_block(fd, block, bp, type) int fd; block_nr block; struct buf *bp; int type; /* * Get the requested block from the device with filedescriptor fd. * If it is in the cache, no (floppy-) disk access is needed, * if not, allocate a cache block and read the block into it. */ { register int i; register struct cache_block *cache_p, *cp; if (block == (block_nr) NO_ZONE) { /* Should never happen in a good filesystem. */ fprintf(stderr, "get_block: NO_ZONE requested !\n"); return(-1); } if (type < 0 || type >= NR_CACHES) /* No cache for this type */ return( get_rawblock(fd, block, bp) ); cache_p = cache[type]; cp = (struct cache_block *) 0; /* First find out if block requested is in the cache */ for (i = 0; i < NR_CBLOCKS; i++) { if (cache_p[i].b_block == block) { /* found right block */ cp = &cache_p[i]; break; } } if (cp == (struct cache_block *) 0) { /* block is not in cache */ cp = cache_p; /* go find oldest buffer */ for (i = 0; i < NR_CBLOCKS; i++) { if (cache_p[i].b_access < cp->b_access) cp = &cache_p[i]; } /* Fill the buffer with the right block */ if (get_rawblock(fd, block, cp->b_buf) < 0) return(-1); } /* Update/store last access counter */ cp->b_access = ++counter; cp->b_block = block; bcopy(cp->b_buf, bp, BLOCK_SIZE); return(0); } int get_rawblock(special, blockno, bufp) int special; block_nr blockno; char *bufp; /* * Read a block from the disk. */ { int r; file_pos pos; /* Calculate the position of the block on the disk */ pos = (file_pos) blockno * (file_pos) BLOCK_SIZE; /* Read the block from the disk */ if (lseek(special, pos, 0) == pos && read(special, bufp, BLOCK_SIZE) == BLOCK_SIZE) return(0); /* Should never get here .. */ fprintf(stderr, "read block %d failed\n", blockno); return(-1); } restore(name, ip) char *name; d_inode *ip; /* * Restores given file's attributes. * `ip' contains the attributes of the file on the MINIX filesystem, * `name' is the filename of the extracted file on the local filesystem. */ { long ttime[2]; chown(name, ip->i_uid, ip->i_gid); /* Fails if not superuser */ chmod(name, (ip->i_mode & ALL_MODES)); ttime[0] = ttime[1] = ip->i_modtime; utime(name, ttime); } /* * characters to use as prefix to `mkfs' mode field */ static char special_chars[] = { '-', /* I_REGULAR */ 'c', /* I_CHAR_SPECIAL */ 'd', /* I_DIRECTORY */ 'b' /* I_BLOCK_SPECIAL */ }; show_info(name, ip, path) char *name; d_inode *ip; char *path; /* * Show information about the given file/dir in `mkfs'-format */ { char c1, c2, c3; c1 = special_chars[(ip->i_mode >> 13) & 03]; c2 = ((ip->i_mode & ALL_MODES & ~RWX_MODES)==I_SET_UID_BIT) ? 'u' : '-'; c3 = ((ip->i_mode & ALL_MODES & ~RWX_MODES)==I_SET_GID_BIT) ? 'g' : '-'; if (*name) { do_indent(indent); printf("%-14s ", name); } printf("%c%c%c%03o %d %d", c1, c2, c3, (ip->i_mode & RWX_MODES), ip->i_uid, ip->i_gid); switch (ip->i_mode & I_TYPE) { case I_DIRECTORY: break; case I_CHAR_SPECIAL: /* Print major and minor dev numbers */ printf(" %d %d", (ip->i_zone[0] >> MAJOR) & 0377, (ip->i_zone[0] >> MINOR) & 0377); break; case I_BLOCK_SPECIAL: /* Print major and minor dev numbers */ printf(" %d %d", (ip->i_zone[0] >> MAJOR) & 0377, (ip->i_zone[0] >> MINOR) & 0377); /* Also print the number of blocks on the device */ printf(" %D", (ip->i_size / (file_pos) BLOCK_SIZE)); break; default: /* Just print the pathname */ printf(" %s", path); break; } putchar('\n'); } #define INDENT_SIZE 4 do_indent(i) int i; { i *= INDENT_SIZE; while (i-- > 0) putchar(' '); } int mkdir(directory) int directory; /* * Make a directory, return exit status. * This routine is not necessary on systems that * have a system call to make directories. */ { int pid, status; if ( (pid = fork()) == 0) { execl("/bin/mkdir", "mkdir", directory, 0); execl("/usr/bin/mkdir", "mkdir", directory, 0); exit(1); } else if (pid < 0) return(-1); while (wait(&status) != pid) ; return(status); } bcopy(src, dest, bytes) register char *src, *dest; register int bytes; /* * Copy bytes from src to dest. * May also be in the standard libc.a on some systems. */ { while (bytes--) *dest++ = *src++; } /* test(1); version 7-like -- author Erik Baalbergen */ #include #include /* test(1) accepts the following grammar: expr ::= bexpr | bexpr "-o" expr ; bexpr ::= primary | primary "-a" bexpr ; primary ::= unary-operator operand | operand binary-operator operand | operand | "(" expr ")" | "!" expr ; unary-operator ::= "-r"|"-w"|"-f"|"-d"|"-s"|"-t"|"-z"|"-n"; binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"; operand ::= */ #define EOI 0 #define FILRD 1 #define FILWR 2 #define FILND 3 #define FILID 4 #define FILGZ 5 #define FILTT 6 #define STZER 7 #define STNZE 8 #define STEQL 9 #define STNEQ 10 #define INTEQ 11 #define INTNE 12 #define INTGE 13 #define INTGT 14 #define INTLE 15 #define INTLT 16 #define UNEGN 17 #define BAND 18 #define BOR 19 #define LPAREN 20 #define RPAREN 21 #define OPERAND 22 #define UNOP 1 #define BINOP 2 #define BUNOP 3 #define BBINOP 4 #define PAREN 5 struct op { char *op_text; short op_num, op_type; } ops[] = { {"-r", FILRD, UNOP}, {"-w", FILWR, UNOP}, {"-f", FILND, UNOP}, {"-d", FILID, UNOP}, {"-s", FILGZ, UNOP}, {"-t", FILTT, UNOP}, {"-z", STZER, UNOP}, {"-n", STNZE, UNOP}, {"=", STEQL, BINOP}, {"!=", STNEQ, BINOP}, {"-eq", INTEQ, BINOP}, {"-ne", INTNE, BINOP}, {"-ge", INTGE, BINOP}, {"-gt", INTGT, BINOP}, {"-le", INTLE, BINOP}, {"-lt", INTLT, BINOP}, {"!", UNEGN, BUNOP}, {"-a", BAND, BBINOP}, {"-o", BOR, BBINOP}, {"(", LPAREN, PAREN}, {")", RPAREN, PAREN}, {0, 0, 0} }; long num(); char **ip; char *prog; struct op *ip_op; main(argc, argv) char *argv[]; { prog = argv[0]; ip = &argv[1]; exit(!(expr(lex(*ip)) && *++ip == 0)); } expr(n) { int res; if (n == EOI) syntax(); res = bexpr(n); if (lex(*++ip) == BOR) return expr(lex(*++ip)) || res; ip--; return res; } bexpr(n) { int res; if (n == EOI) syntax(); res = primary(n); if (lex(*++ip) == BAND) return bexpr(lex(*++ip)) && res; ip--; return res; } primary(n) { register char *opnd1, *opnd2; int res; if (n == EOI) syntax(); if (n == UNEGN) return !expr(lex(*++ip)); if (n == LPAREN) { res = expr(lex(*++ip)); if (lex(*++ip) != RPAREN) syntax(); return res; } if (n == OPERAND) { opnd1 = *ip; (void) lex(*++ip); if (ip_op && ip_op->op_type == BINOP) { struct op *op = ip_op; if ((opnd2 = *++ip) == (char *)0) syntax(); switch (op->op_num) { case STEQL: return strcmp(opnd1, opnd2) == 0; case STNEQ: return strcmp(opnd1, opnd2) != 0; case INTEQ: return num(opnd1) == num(opnd2); case INTNE: return num(opnd1) != num(opnd2); case INTGE: return num(opnd1) >= num(opnd2); case INTGT: return num(opnd1) > num(opnd2); case INTLE: return num(opnd1) <= num(opnd2); case INTLT: return num(opnd1) < num(opnd2); } } ip--; return strlen(opnd1) > 0; } /* unary expression */ if (ip_op->op_type != UNOP || *++ip == 0) syntax(); if (n == STZER) return strlen(*ip) == 0; if (n == STNZE) return strlen(*ip) != 0; return filstat(*ip, n); } filstat(nm, mode) char *nm; { struct stat s; struct sgttyb t; switch (mode) { case FILRD: return access(nm, 4) == 0; case FILWR: return access(nm, 2) == 0; case FILND: return stat(nm, &s) == 0 && ((s.st_mode & S_IFMT) != S_IFDIR); case FILID: return stat(nm, &s) == 0 && ((s.st_mode & S_IFMT) == S_IFDIR); case FILGZ: return stat(nm, &s) == 0 && (s.st_size > 0L); case FILTT: return ioctl((int)num(nm), TIOCGETP, &t) == 0; } } int lex(s) register char *s; { register struct op *op = ops; if (s == 0) return EOI; while (op->op_text) { if (strcmp(s, op->op_text) == 0) { ip_op = op; return op->op_num; } op++; } ip_op = (struct op *)0; return OPERAND; } long num(s) register char *s; { long l = 0; long sign = 1; if (*s == '\0') syntax(); if (*s == '-') { sign = -1; s++; } while (*s >= '0' && *s <= '9') l = l * 10 + *s++ - '0'; if (*s != '\0') syntax(); return sign * l; } syntax() { write(2, prog, strlen(prog)); write(2, ": syntax error\n", 15); exit(1); } /* uudecode - decode a uuencoded file */ /* call: uudecode [input_file] */ /* hgm - June 9, 1987 - added code to handle short lines and tabs */ #include "stdio.h" #include "pwd.h" #include "stat.h" extern FILE *fopen(); char *fgets(); #define NULLF (FILE *) 0 #define NULLP (char *) 0 /* single character decode */ #define DEC(c) (((c) - ' ') & 077) main(argc, argv) char **argv; { FILE *in, *out; struct stat sbuf; int mode; char dest[128]; char buf[80]; /* optional input arg */ if (argc > 1) { if ((in = fopen(argv[1], "r")) == NULLF) { perror(argv[1]); xit(1); } argv++; argc--; } else in = stdin; if (argc != 1) { printf("Usage: uudecode [infile]\n"); xit(2); } /* search for header line */ for (;;) { if (fgets(buf, sizeof buf, in) == NULLP) { fprintf(stderr, "No begin line\n"); xit(3); } if (strncmp(buf, "begin ", 6) == 0) break; } sscanf(buf, "begin %o %s", &mode, dest); /* handle ~user/file format */ if (dest[0] == '~') { char *sl; struct passwd *getpwnam(); char *index(); struct passwd *user; char dnbuf[100]; sl = index(dest, '/'); if (sl == NULLP) { fprintf(stderr, "Illegal ~user\n"); xit(3); } *sl++ = 0; user = getpwnam(dest+1); if (user == NULL) { fprintf(stderr, "No such user as %s\n", dest); xit(4); } strcpy(dnbuf, user->pw_dir); strcat(dnbuf, "/"); strcat(dnbuf, sl); strcpy(dest, dnbuf); } /* create output file */ out = fopen(dest, "w"); if (out == NULLF) { perror(dest); xit(4); } chmod(dest, mode); decode(in, out); if (fgets(buf, sizeof buf, in) == NULLP || strcmp(buf, "end\n")) { fprintf(stderr, "No end line\n"); xit(5); } xit(0); } /* * copy from in to out, decoding as you go along. */ decode(in, out) FILE *in; FILE *out; { char tbuf[82]; char buf[82]; char *bp; char *p; int n; int col; for (;;) { /* for each input line */ if (fgets(tbuf, sizeof tbuf, in) == NULLP) { printf("Short file\n"); xit(10); } n = tbuf[0] - ' '; if (n <= 0) break; /* expand tabs */ for (col = 0, p = tbuf; ((*p) && (*p != '\n') && (col < 78)); p++) { if (*p == '\t') { do { buf[col++] = ' '; } while (col % 8); } else { buf[col++] = *p; } } /* fill with trailing blanks */ for (; ((col < (4*n + 1)) && (col < 78)); col++) buf[col] = ' '; buf[col++] = '\n'; buf[col] = '\0'; bp = &buf[1]; while (n > 0) { outdec(bp, out, n); bp += 4; n -= 3; } } } /* * output a group of 3 bytes (4 input characters). * the input chars are pointed to by p, they are to * be output to file f. n is used to tell us not to * output all of them at the end of the file. */ outdec(p, f, n) char *p; FILE *f; { int c1, c2, c3; c1 = DEC(*p) << 2 | DEC(p[1]) >> 4; c2 = DEC(p[1]) << 4 | DEC(p[2]) >> 2; c3 = DEC(p[2]) << 6 | DEC(p[3]); if (n >= 1) putc(c1, f); if (n >= 2) putc(c2, f); if (n >= 3) putc(c3, f); } /* fr: like read but stdio */ int fr(fd, buf, cnt) FILE *fd; char *buf; int cnt; { int c, i; for (i=0; i 2) { if ((in = fopen(argv[1], "r")) == (FILE *) NULL) { perror(argv[1]); xit(1); } argv++; argc--; } else in = stdin; if (argc != 2) { printf("Usage: uuencode infile outfile\n"); xit(2); } /* figure out the input file mode */ fstat(fileno(in), &sbuf); mode = sbuf.st_mode & 0777; printf("begin %o %s\n", mode, argv[1]); encode(in, stdout); printf("end\n"); xit(0); } /* * copy from in to out, encoding as you go along. */ encode(in, out) FILE *in; FILE *out; { char buf[80]; int i, n; for (;;) { /* 1 (up to) 45 character line */ n = fr(in, buf, 45); putc(ENC(n), out); for (i=0; i> 2; c2 = (*p << 4) & 060 | (p[1] >> 4) & 017; c3 = (p[1] << 2) & 074 | (p[2] >> 6) & 03; c4 = p[2] & 077; putc(ENC(c1), f); putc(ENC(c2), f); putc(ENC(c3), f); putc(ENC(c4), f); } /* fr: like read but stdio */ int fr(fd, buf, cnt) FILE *fd; char *buf; int cnt; { int c, i; for (i=0; i #undef putchar #undef getchar #undef NULL #undef EOF extern int _putchar(); extern char *CE, *VS, *SO, *SE, *CL, *AL, *CM; #define YMAX 23 #else #define YMAX 24 /* Maximum y coordinate starting at 0 */ /* Escape sequences. */ extern char *enter_string; /* String printed on entering mined */ extern char *rev_video; /* String for starting reverse video */ extern char *normal_video; /* String for leaving reverse video */ extern char *rev_scroll; /* String for reverse scrolling */ extern char *pos_string; /* Absolute cursor positioning */ #define X_PLUS ' ' /* To be added to x for cursor sequence */ #define Y_PLUS ' ' /* To be added to y for cursor sequence */ #endif UNIX #define XMAX 79 /* Maximum x coordinate starting at 0 */ #define SCREENMAX (YMAX - 1) /* Number of lines displayed */ #define XBREAK (XMAX - 2) /* Line shift at this coordinate */ #define SHIFT_SIZE 25 /* Number of chars to shift */ #define SHIFT_MARK '!' /* Char indicating that line continues*/ #define MAX_CHARS 1024 /* Maximum chars on one line */ /* LINE_START must be rounded up to the lowest SHIFT_SIZE */ #define LINE_START (((-MAX_CHARS - 1) / SHIFT_SIZE) * SHIFT_SIZE \ - SHIFT_SIZE) #define LINE_END (MAX_CHARS + 1) /* Highest x-coordinate for line */ #define LINE_LEN (XMAX + 1) /* Number of characters on line */ #define SCREEN_SIZE (XMAX * YMAX) /* Size of I/O buffering */ #define BLOCK_SIZE 1024 /* Return values of functions */ #define ERRORS -1 #define NO_LINE (ERRORS - 1) /* Must be < 0 */ #define FINE (ERRORS + 1) #define NO_INPUT (ERRORS + 2) #define STD_OUT 1 /* Filedescriptor for terminal */ #define FILE_LENGTH 14 /* Length of filename in minix */ #define MEMORY_SIZE (50 * 1024) /* Size of data space to malloc */ #define REPORT 2 /* Report change of lines on # lines */ /* * To avoid #define XXX 0 #define !XXX 1 an enum type is used for all flags used * in mined. */ typedef enum { /* General flags */ FALSE, TRUE, NOT_VALID, VALID, OFF, ON, /* Expression flags */ FORWARD, REVERSE, /* Yank flags */ SMALLER, BIGGER, SAME, EMPTY, NO_DELETE, DELETE, READ, WRITE } FLAG; /* * The Line structure. Each line entry contains a pointer to the next line, * a pointer to the previous line, a pointer to the text and an unsigned char * telling at which offset of the line printing should start (usually 0). */ struct Line { struct Line *next; struct Line *prev; char *text; unsigned char shift_count; }; typedef struct Line LINE; /* Dummy line indicator */ #define DUMMY 0x80 #define DUMMY_MASK 0x7F /* Expression definitions */ #define NO_MATCH 0 #define MATCH 1 #define REG_ERROR 2 #define BEGIN_LINE (2 * REG_ERROR) #define END_LINE (2 * BEGIN_LINE) /* * The regex structure. Status can be any of 0, BEGIN_LINE or REG_ERROR. In * the last case, the result.err_mess field is assigned. Start_ptr and end_ptr * point to the match found. For more details see the documentation file. */ struct regex { union { char *err_mess; int *expression; } result; char status; char *start_ptr; char *end_ptr; }; typedef struct regex REGEX; /* NULL definitions */ #define NIL_PTR ((char *) 0) #define NIL_LINE ((LINE *) 0) #define NIL_REG ((REGEX *) 0) #define NIL_INT ((int *) 0) /* * Forward declarations */ extern int nlines; /* Number of lines in file */ extern LINE *header; /* Head of line list */ extern LINE *tail; /* Last line in line list */ extern LINE *top_line; /* First line of screen */ extern LINE *bot_line; /* Last line of screen */ extern LINE *cur_line; /* Current line in use */ extern char *cur_text; /* Pointer to char on current line in use */ extern int last_y; /* Last y of screen. Usually SCREENMAX */ extern char screen[SCREEN_SIZE];/* Output buffer for "writes" and "reads" */ extern int x, y; /* x, y coordinates on screen */ extern FLAG modified; /* Set when file is modified */ extern FLAG stat_visible; /* Set if status_line is visible */ extern FLAG writable; /* Set if file cannot be written */ extern FLAG quit; /* Set when quit character is typed */ extern FLAG rpipe; /* Set if file should be read from stdin */ extern int input_fd; /* Fd for command input */ extern FLAG loading; /* Set if we're loading a file */ extern int out_count; /* Index in output buffer */ extern char file_name[LINE_LEN]; /* Name of file in use */ extern char text_buffer[MAX_CHARS]; /* Buffer for modifying text */ extern char blank_line[LINE_LEN]; /* Line filled with spaces */ extern char yank_file[]; /* Temp file for buffer */ extern FLAG yank_status; /* Status of yank_file */ extern long chars_saved; /* Nr of chars saved in buffer */ /* * Empty output buffer */ #define clear_buffer() (out_count = 0) /* * Print character on terminal */ #define putchar(c) (void) write_char(STD_OUT, (c)) /* * Ring bell on terminal */ #define ring_bell() putchar('\07') /* * Print string on terminal */ #define string_print(str) (void) writeline(STD_OUT, (str)) /* * Flush output buffer */ #define flush() (void) flush_buffer(STD_OUT) /* * Convert cnt to nearest tab position */ #define tab(cnt) (((cnt) + 8) & ~07) #define is_tab(c) ((c) == '\t') /* * Word defenitions */ #define white_space(c) ((c) == ' ' || (c) == '\t') #define alpha(c) ((c) != ' ' && (c) != '\t' && (c) != '\n') /* * Print line on terminal at offset 0 and clear tail of line */ #define line_print(line) put_line(line, 0, TRUE) /* * Move to coordinates and set textp. (Don't use address) */ #define move_to(nx, ny) move((nx), NIL_PTR, (ny)) /* * Move to coordinates on screen as indicated by textp. */ #define move_address(address) move(0, (address), y) /* * Functions handling status_line. ON means in reverse video. */ #define status_line(str1, str2) (void) bottom_line(ON, (str1), \ (str2), NIL_PTR, FALSE) #define error(str1, str2) (void) bottom_line(ON, (str1), \ (str2), NIL_PTR, FALSE) #define get_string(str1,str2, fl) bottom_line(ON, (str1), NIL_PTR, (str2), fl) #define clear_status() (void) bottom_line(OFF, NIL_PTR, NIL_PTR, \ NIL_PTR, FALSE) /* * Print info about current file and buffer. */ #define fstatus(mess, cnt) file_status((mess), (cnt), file_name, \ nlines, writable, modified) /* * Get real shift value. */ #define get_shift(cnt) ((cnt) & DUMMY_MASK) /* Forward declarations */ extern LINE *proceed(), *install_line(); extern LINE *match(), *line_insert(); extern char *alloc(), *num_out(), *basename(); #endif YMAX /* * Part one of the mined editor. */ /* * Author: Michiel Huisjes. * * 1. General remarks. * * Mined is a screen editor designed for the minix operating system. * It is meant to be used on files not larger than 50K and to be fast. * When mined starts up, it reads the file into its memory to minimize * disk access. The only time that disk access is needed is when certain * save, write or copy commands are given. * * Mined has the style of Emacs or Jove, that means that there are no modes. * Each character has its own entry in an 256 pointer to function array, * which is called when that character is typed. Only ASCII characters are * connected with a function that inserts that character at the current * location in the file. Two execptions are and which are * inserted as well. Note that the mapping between commands and functions * called is implicit in the table. Changing the mapping just implies * changing the pointers in this table. * * The display consists of SCREENMAX + 1 lines and XMAX + 1 characters. When * a line is larger (or gets larger during editing) than XBREAK characters, * the line is either shifted SHIFT_SIZE characters to the left (which means * that the first SHIFT_SIZE characters are not printed) or the end of the * line is marked with the SHIFT_MARK character and the rest of the line is * not printed. A line can never exceed MAX_CHARS characters. Mined will * always try to keep the cursor on the same line and same (relative) * x-coordinate if nothing changed. So if you scroll one line up, the cursor * stays on the same line, or when you move one line down, the cursor will * move to the same place on the line as it was on the previous. * Every character on the line is available for editing including the * linefeed at the the of the line. When the linefeed is deleted, the current * line and the next line are joined. The last character of the file (which * is always a linefeed) can never be deleted. * The bottomline (as indicated by YMAX + 1) is used as a status line during * editing. This line is usually blank or contains information mined needs * during editing. This information (or rather questions) is displayed in * reverse video. * * The terminal modes are changed completely. All signals like start/stop, * interrupt etc. are unset. The only signal that remains is the quit signal. * The quit signal (^\) is the general abort signal for mined. Typing a ^\ * during searching or when mined is asking for filenames, etc. will abort * the function and mined will return to the main loop. Sending a quit * signal during the main loop will abort the session (after confirmation) * and the file is not (!) saved. * The session will also be aborted when an unrecoverable error occurs. E.g * when there is no more memory available. If the file has been modified, * mined will ask if the file has to be saved or not. * If there is no more space left on the disk, mined will just give an error * message and continue. * * The number of system calls are minized. This is done to keep the editor * as fast as possible. I/O is done in SCREEN_SIZE reads/writes. Accumulated * output is also flushed at the end of each character typed. * * 2. Regular expressions * * Mined has a build in regular expression matcher, which is used for * searching and replace routines. A regular expression consists of a * sequence of: * * 1. A normal character matching that character. * 2. A . matching any character. * 3. A ^ matching the begin of a line. * 4. A $ (as last character of the pattern) mathing the end of a line. * 5. A \ matching . * 6. A number of characters enclosed in [] pairs matching any of these * characters. A list of characters can be indicated by a '-'. So * [a-z] matches any letter of the alphabet. If the first character * after the '[' is a '^' then the set is negated (matching none of * the characters). * A ']', '^' or '-' can be escaped by putting a '\' in front of it. * Of course this means that a \ must be represented by \\. * 7. If one of the expressions as described in 1-6 is followed by a * '*' than that expressions matches a sequence of 0 or more of * that expression. * * Parsing of regular expression is done in two phases. In the first phase * the expression is compiled into a more comprehensible form. In the second * phase the actual matching is done. For more details see 3.6. * * * 3. Implementation of mined. * * 3.1 Data structures. * * The main data structures are as follows. The whole file is kept in a * double linked list of lines. The LINE structure looks like this: * * typedef struct Line { * struct Line *next; * struct Line *prev; * char *text; * unsigned char shift_count; * } LINE; * * Each line entry contains a pointer to the next line, a pointer to the * previous line and a pointer to the text of that line. A special field * shift_count contains the number of shifts (in units of SHIFT_SIZE) * that is performed on that line. The total size of the structure is 7 * bytes so a file consisting of 1000 empty lines will waste a lot of * memory. A LINE structure is allocated for each line in the file. After * that the number of characters of the line is counted and sufficient * space is allocated to store them (including a linefeed and a '\0'). * The resulting address is assigned to the text field in the structure. * * A special structure is allocated and its address is assigned to the * variable header as well as the variable tail. The text field of this * structure is set to NIL_PTR. The tail->prev of this structure points * to the last LINE of the file and the header->next to the first LINE. * Other LINE *variables are top_line and bot_line which point to the * first line resp. the last line on the screen. * Two other variables are important as well. First the LINE *cur_line, * which points to the LINE currently in use and the char *cur_text, * which points to the character at which the cursor stands. * Whenever an ASCII character is typed, a new line is build with this * character inserted. Then the old data space (pointed to by * cur_line->text) is freed, data space for the new line is allocated and * assigned to cur_line->text. * * Two global variables called x and y represent the x and y coordinates * from the cursor. The global variable nlines contains the number of * lines in the file. Last_y indicates the maximum y coordinate of the * screen (which is usually SCREENMAX). * * A few strings must be initialized by hand before compiling mined. * These string are enter_string, which is printed upon entering mined, * rev_video (turn on reverse video), normal_video, rev_scroll (perform a * reverse scroll) and pos_string. The last string should hold the * absolute position string to be printed for cursor motion. The #define * X_PLUS and Y_PLUS should contain the characters to be added to the * coordinates x and y (both starting at 0) to finish cursor positioning. * * 3.2 Starting up. * * Mined can be called with or without argument and the function * load_file () is called with these arguments. load_file () checks * if the file exists if it can be read and if it is writable and * sets the writable flag accordingly. If the file can be read, * load_file () reads a line from the file and stores this line into * a structure by calling in„§Ø©Ŗ«¬­®Æ°±²³“µ¶·ø¹ŗ»¼½¾æĄĮĀĆÄÅĘĒČÉŹĖĢĶĪĻŠŃŅÓŌÕÖ×ŲŁŚstall_line () and line_insert () which * installs the line into the double linked list, until the end of the * file is reached. * Lines are read by the function get_line (), which buffers the * reading in blocks of SCREEN_SIZE. Load_file () also initializes the * LINE *variables described above. * * 3.3 Moving around. * * Several commands are implemented for moving through the file. * Moving up (UP), down (DN) left (LF) and right (RT) are done by the * arrow keys. Moving one line below the screen scrolls the screen one * line up. Moving one line above the screen scrolls the screen one line * down. The functions forward_scroll () and reverse_scroll () take care * of that. * Several other move functions exist: move to begin of line (BL), end of * line (EL) top of screen (HIGH), bottom of screen (LOW), top of file * (HO), end of file (EF), scroll one page down (PD), scroll one page up * (PU), scroll one line down (SD), scroll one line up (SU) and move to a * certain line number (GOTO). * Two functions called MN () and MP () each move one word further or * backwards. A word is a number of non-blanks seperated by a space, a * tab or a linefeed. * * 3.4 Modifying text. * * The modifying commands can be separated into two modes. The first * being inserting text, and the other deleting text. Two functions are * created for these purposes: insert () and delete (). Both are capable * of deleting or inserting large amounts of text as well as one * character. Insert () must be given the line and location at which * the text must be inserted. Is doesn't make any difference whether this * text contains linefeeds or not. Delete () must be given a pointer to * the start line, a pointer from where deleting should start on that * line and the same information about the end position. The last * character of the file will never be deleted. Delete () will make the * necessary changes to the screen after deleting, but insert () won't. * The functions for modifying text are: insert one char (S), insert a * file (file_insert (fd)), insert a linefeed and put cursor back to * end of line (LIB), delete character under the cursor (DCC), delete * before cursor (even linefeed) (DPC), delete next word (DNW), delete * previous word (DPC) and delete to end of line (if the cursor is at * a linefeed delete line) (DLN). * * 3.5 Yanking. * * A few utilities are provided for yanking pieces of text. The function * MA () marks the current position in the file. This is done by setting * LINE *mark_line and char *mark_text to the current position. Yanking * of text can be done in two modes. The first mode just copies the text * from the mark to the current position (or visa versa) into a buffer * (YA) and the second also deletes the text (DT). Both functions call * the function set_up () with the delete flag on or off. Set_up () * checks if the marked position is still a valid one (by using * check_mark () and legal ()), and then calls the function yank () with * a start and end position in the file. This function copies the text * into a scratch_file as indicated by the variable yank_file. This * scratch_file is made uniq by the function scratch_file (). At the end * of copying yank will (if necessary) delete the text. A global flag * called yank_status keeps track of the buffer (or file) status. It is * initialized on NOT_VALID and set to EMPTY (by set_up ()) or VALID (by * yank ()). Several things can be done with the buffer. It can be * inserted somewhere else in the file (PT) or it can be copied into * another file (WB), which will be prompted for. * * 3.6 Search and replace routines. * * Searching for strings and replacing strings are done by regular * expressions. For any expression the function compile () is called * with as argument the expression to compile. Compile () returns a * pointer to a structure which looks like this: * * typedef struct regex { * union { * char *err_mess; * int *expression; * } result; * char status; * char *start_ptr; * char *end_ptr; * } REGEX; * * If something went wrong during compiling (e.g. an illegal expression * was given), the function reg_error () is called, which sets the status * field to REG_ERROR and the err_mess field to the error message. If the * match must be anchored at the beginning of the line (end of line), the * status field is set to BEGIN_LINE (END_LINE). If none of these special * cases are true, the field is set to 0 and the function finished () is * called. Finished () allocates space to hold the compiled expression * and copies this expression into the expression field of the union * (bcopy ()). Matching is done by the routines match() and line_check(). * Match () takes as argument the REGEX *program, a pointer to the * startposition on the current line, and a flag indicating FORWARD or * REVERSE search. Match () checks out the whole file until a match is * found. If match is found it returns a pointer to the line in which the * match was found else it returns a NIL_LINE. Line_check () takes the * same arguments, but return either MATCH or NO_MATCH. * During checking, the start_ptr and end_ptr fields of the REGEX * structure are assigned to the start and end of the match. * Both functions try to find a match by walking through the line * character by character. For each possibility, the function * check_string () is called with as arguments the REGEX *program and the * string to search in. It starts walking through the expression until * the end of the expression or the end of the string is reached. * Whenever a * is encountered, this position of the string is marked, * the maximum number of matches are performed and the function star () * is called in order to try to find the longest match possible. Star () * takes as arguments the REGEX program, the current position of the * string, the marked position and the current position of the expression * Star () walks from the current position of the string back to the * marked position, and calls string_check () in order to find a match. * It returns MATCH or NO_MATCH, just as string_check () does. * Searching is now easy. Both search routines (forward (SF) and * backwards search (SR)) call search () with an apropiate message and a * flag indicating FORWARD or REVERSE search. Search () will get an * expression from the user by calling get_expression(). Get_expression() * returns a pointer to a REGEX structure or NIL_REG upon errors and * prompts for the expression. If no expression if given, the previous is * used instead. After that search will call match (), and if a match is * found, we can move to that place in the file by the functions find_x() * and find_y () which will find display the match on the screen. * Replacing can be done in two ways. A global replace (GR) or a line * replace (LR). Both functions call change () with a message an a flag * indicating global or line replacement. Change () will prompt for the * expression and for the replacement. Every & in the replacement pattern * means substitute the match instead. An & can be escaped by a \. When * a match is found, the function substitute () will perform the * substitution. * * 3.6 Miscellaneous commands. * * A few commands haven't be discussed yet. These are redraw the screen * (RD) fork a shell (SH), print file status (FS), write file to disc * (WT), insert a file at current position (IF), leave editor (XT) and * visit another file (VI). The last two functions will check if the file * has been modified. If it has, they will ask if you want to save the * file by calling ask_save (). * The function ESC () will repeat a command n times. It will prompt for * the number. Aborting the loop can be done by sending the ^\ signal. * * 3.7 Utility functions. * * Several functions exists for internal use. First allocation routines: * alloc (bytes) and newline () will return a pointer to free data space * if the given size. If there is no more memory available, the function * panic () is called. * Signal handling: The only signal that can be send to mined is the * SIGQUIT signal. This signal, functions as a general abort command. * Mined will abort if the signal is given during the main loop. The * function abort_mined () takes care of that. * Panic () is a function with as argument a error message. It will print * the message and the error number set by the kernel (errno) and will * ask if the file must be saved or not. It resets the terminal * (raw_mode ()) and exits. * String handling routines like copy_string(to, from), length_of(string) * and build_string (buffer, format, arg1, arg2, ...). The latter takes * a description of the string out out the format field and puts the * result in the buffer. (It works like printf (3), but then into a * string). The functions status_line (string1, string2), error (string1, * string2), clear_status () and bottom_line () all print information on * the status line. * Get_string (message, buffer) reads a string and getchar () reads one * character from the terminal. * Num_out ((long) number) prints the number into a 11 digit field * without leading zero's. It returns a pointer to the resulting string. * File_status () prints all file information on the status line. * Set_cursor (x, y) prints the string to put the cursor at coordinates * x and y. * Output is done by four functions: writeline(fd,string), clear_buffer() * write_char (fd, c) and flush_buffer (fd). Three defines are provided * to write on filedescriptor STD_OUT (terminal) which is used normally: * string_print (string), putchar (c) and flush (). All these functions * use the global I/O buffer screen and the global index for this array * called out_count. In this way I/O can be buffered, so that reads or * writes can be done in blocks of SCREEN_SIZE size. * The following functions all handle internal line maintenance. The * function proceed (start_line, count) returns the count'th line after * start_line. If count is negative, the count'th line before the * start_line is returned. If header or tail is encountered then that * will be returned. Display (x, y, start_line, count) displays count * lines starting at coordinates [x, y] and beginning at start_line. If * the header or tail is encountered, empty lines are displayed instead. * The function reset (head_line, ny) reset top_line, last_y, bot_line, * cur_line and y-coordinate. This is not a neat way to do the * maintenance, but it sure saves a lot of code. It is usually used in * combination with display (). * Put_line(line, offset, clear_line), prints a line (skipping characters * according to the line->shift_size field) until XBREAK - offset * characters are printed or a '\n' is encountered. If clear_line is * TRUE, spaces are printed until XBREAK - offset characters. * Line_print (line) is a #define from put_line (line, 0, TRUE). * Moving is done by the functions move_to (x, y), move_addres (address) * and move (x, adress, y). This function is the most important one in * mined. New_y must be between 0 and last_y, new_x can be about * anything, address must be a pointer to an character on the current * line (or y). Move_to () first adjust the y coordinate together with * cur_line. If an address is given, it finds the corresponding * x-coordinate. If an new x-coordinate was given, it will try to locate * the corresponding character. After that it sets the shift_count field * of cur_line to an apropiate number according to new_x. The only thing * left to do now is to assign the new values to cur_line, cur_text, x * and y. * * 4. Summary of commands. * * CURSOR MOTION * up-arrow Move cursor 1 line up. At top of screen, reverse scroll * down-arrow Move cursor 1 line down. At bottom, scroll forward. * left-arrow Move cursor 1 character left or to end of previous line * right-arrow Move cursor 1 character right or to start of next line * CTRL-A Move cursor to start of current line * CTRL-Z Move cursor to end of current line * CTRL-^ Move cursor to top of screen * CTRL-_ Move cursor to bottom of screen * CTRL-F Forward to start of next word (even to next line) * CTRL-B Backward to first character of previous word * * SCREEN MOTION * Home key Move cursor to first character of file * End key Move cursor to last character of file * PgUp Scroll backward 1 page. Bottom line becomes top line * PgD Scroll backward 1 page. Top line becomes bottom line * CTRL-D Scroll screen down one line (reverse scroll) * CTRL-U Scroll screen up one line (forward scroll) * * MODIFYING TEXT * ASCII char Self insert character at cursor * tab Insert tab at cursor * backspace Delete the previous char (left of cursor), even line feed * Del Delete the character under the cursor * CTRL-N Delete next word * CTRL-P Delete previous word * CTRL-O Insert line feed at cursor and back up 1 character * CTRL-T Delete tail of line (cursor to end); if empty, delete line * CTRL-@ Set the mark (remember the current location) * CTRL-K Delete text from the mark to current position save on file * CTRL-C Save the text from the mark to the current position * CTRL-Y Insert the contents of the save file at current position * CTRL-Q Insert the contents of the save file into a new file * CTRL-G Insert a file at the current position * * MISCELLANEOUS * CTRL-E Erase and redraw the screen * CTRL-V Visit file (read a new file); complain if old one changed * CTRL-W Write the current file back to the disk * numeric + Search forward (prompt for regular expression) * numeric - Search backward (prompt for regular expression) * numeric 5 Print the current status of the file * CTRL-R (Global) Replace str1 by str2 (prompts for each string) * CTRL-L (Line) Replace string1 by string2 * CTRL-S Fork off a shell and wait for it to finish * CTRL-X EXIT (prompt if file modified) * CTRL-] Go to a line. Prompts for linenumber * CTRL-\ Abort whatever editor was doing and start again * escape key Repeat a command count times; (prompts for count) */ /* ======================================================================== * * Utilities * * ======================================================================== */ #include "mined.h" #include "signal.h" #include "sgtty.h" #ifdef UNIX #include #else #include "errno.h" #endif UNIX extern int errno; /* * Print file status. */ FS() { fstatus(file_name[0] ? "" : "[buffer]", -1L); } /* * Visit (edit) another file. If the file has been modified, ask the user if * he wants to save it. */ VI() { char new_file[LINE_LEN]; /* Buffer to hold new file name */ if (modified == TRUE && ask_save() == ERRORS) return; /* Get new file name */ if (get_file("Visit file:", new_file) == ERRORS) return; /* Free old linked list, initialize global variables and load new file */ initialize(); #ifdef UNIX tputs(CL, 0, _putchar); #else string_print (enter_string); #endif UNIX load_file(new_file[0] == '\0' ? NIL_PTR : new_file); } /* * Write file in core to disc. */ WT() { register LINE *line; register long count = 0L; /* Nr of chars written */ char file[LINE_LEN]; /* Buffer for new file name */ int fd; /* Filedescriptor of file */ if (modified == FALSE) { error ("Write not necessary.", NIL_PTR); return FINE; } /* Check if file_name is valid and if file can be written */ if (file_name[0] == '\0' || writable == FALSE) { if (get_file("Enter file name:", file) != FINE) return ERRORS; copy_string(file_name, file); /* Save file name */ } if ((fd = creat(file_name, 0644)) < 0) { /* Empty file */ error("Cannot create ", file_name); writable = FALSE; return ERRORS; } else writable = TRUE; clear_buffer(); status_line("Writing ", file_name); for (line = header->next; line != tail; line = line->next) { if (line->shift_count & DUMMY) { if (line->next == tail && line->text[0] == '\n') continue; } if (writeline(fd, line->text) == ERRORS) { count = -1L; break; } count += (long) length_of(line->text); } if (count > 0L && flush_buffer(fd) == ERRORS) count = -1L; (void) close(fd); if (count == -1L) return ERRORS; modified = FALSE; rpipe = FALSE; /* File name is now assigned */ /* Display how many chars (and lines) were written */ fstatus("Wrote", count); return FINE; } /* * Call an interactive shell. */ SH() { register int w; int pid, status; switch (pid = fork()) { case -1: /* Error */ error("Cannot fork.", NIL_PTR); return; case 0: /* This is the child */ set_cursor(0, YMAX); putchar('\n'); flush(); raw_mode(OFF); if (rpipe) { /* Fix stdin */ close (0); if (open("/dev/tty", 0) < 0) exit (126); } execl("/bin/sh", "sh", "-i", (char *) 0); exit(127); /* Exit with 127 */ default : /* This is the parent */ signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); do { w = wait(&status); } while (w != -1 && w != pid); } raw_mode(ON); RD(); if ((status >> 8) == 127) /* Child died with 127 */ error("Cannot exec /bin/sh (possibly not enough memory)", NIL_PTR); else if ((status >> 8) == 126) error("Cannot open /dev/tty as fd #0", NIL_PTR); } /* * Proceed returns the count'th line after `line'. When count is negative * it returns the count'th line before `line'. When the next (previous) * line is the tail (header) indicating EOF (tof) it stops. */ LINE *proceed(line, count) register LINE *line; register int count; { if (count < 0) while (count++ < 0 && line != header) line = line->prev; else while (count-- > 0 && line != tail) line = line->next; return line; } /* * Show concatenation of s1 and s2 on the status line (bottom of screen) * If revfl is TRUE, turn on reverse video on both strings. Set stat_visible * only if bottom_line is visible. */ bottom_line(revfl, s1, s2, inbuf, statfl) FLAG revfl; char *s1, *s2; char *inbuf; FLAG statfl; { int ret = FINE; if (revfl == ON && stat_visible == TRUE) clear_status (); set_cursor(0, YMAX); if (revfl == ON) { /* Print rev. start sequence */ #ifdef UNIX tputs(SO, 0, _putchar); #else string_print(rev_video); #endif UNIX stat_visible = TRUE; } else /* Used as clear_status() */ stat_visible = FALSE; putchar(' '); if (s1 != NIL_PTR) string_print(s1); if (s2 != NIL_PTR) string_print(s2); putchar(' '); if (inbuf != NIL_PTR) ret = input(inbuf, statfl); /* Print normal video */ #ifdef UNIX tputs(SE, 0, _putchar); tputs(CE, 0, _putchar); #else string_print(normal_video); string_print(blank_line); /* Clear the rest of the line */ #endif UNIX if (inbuf != NIL_PTR) set_cursor(0, YMAX); else set_cursor(x, y); /* Set cursor back to old position */ flush(); /* Perform the actual write */ if (ret != FINE) clear_status(); return ret; } /* * Count_chars() count the number of chars that the line would occupy on the * screen. Counting starts at the real x-coordinate of the line. */ count_chars(line) LINE *line; { register int cnt = get_shift(line->shift_count) * -SHIFT_SIZE; register char *textp = line->text; /* Find begin of line on screen */ while (cnt < 0) { if (is_tab(*textp++)) cnt = tab(cnt); else cnt++; } /* Count number of chars left */ cnt = 0; while (*textp != '\n') { if (is_tab(*textp++)) cnt = tab(cnt); else cnt++; } return cnt; } /* * Move to coordinates nx, ny at screen. The caller must check that scrolling * is not needed. * If new_x is lower than 0 or higher than XBREAK, move_to() will check if * the line can be shifted. If it can it sets(or resets) the shift_count field * of the current line accordingly. * Move also sets cur_text to the right char. * If we're moving to the same x coordinate, try to move the the x-coordinate * used on the other previous call. */ move(new_x, new_address, new_y) register int new_x; int new_y; char *new_address; { register LINE *line = cur_line; /* For building new cur_line */ int shift = 0; /* How many shifts to make */ static int rel_x = 0; /* Remember relative x position */ int tx = x; char *find_address(); /* Check for illegal values */ if (new_y < 0 || new_y > last_y) return; /* Adjust y-coordinate and cur_line */ if (new_y < y) while (y != new_y) { y--; line = line->prev; } else while (y != new_y) { y++; line = line->next; } /* Set or unset relative x-coordinate */ if (new_address == NIL_PTR) { new_address = find_address(line, (new_x == x) ? rel_x : new_x , &tx); if (new_x != x) rel_x = tx; new_x = tx; } else rel_x = new_x = find_x(line, new_address); /* Adjust shift_count if new_x lower than 0 or higher than XBREAK */ if (new_x < 0 || new_x >= XBREAK) { if (new_x > XBREAK || (new_x == XBREAK && *new_address != '\n')) shift = (new_x - XBREAK) / SHIFT_SIZE + 1; else { shift = new_x / SHIFT_SIZE; if (new_x % SHIFT_SIZE) shift--; } if (shift != 0) { line->shift_count += shift; new_x = find_x(line, new_address); set_cursor(0, y); line_print(line); rel_x = new_x; } } /* Assign and position cursor */ x = new_x; cur_text = new_address; cur_line = line; set_cursor(x, y); } /* * Find_x() returns the x coordinate belonging to address. * (Tabs are expanded). */ find_x(line, address) LINE *line; char *address; { register char *textp = line->text; register int nx = get_shift(line->shift_count) * -SHIFT_SIZE; while (textp != address && *textp != '\0') { if (is_tab(*textp++)) /* Expand tabs */ nx = tab(nx); else nx++; } return nx; } /* * Find_address() returns the pointer in the line with offset x_coord. * (Tabs are expanded). */ char *find_address(line, x_coord, old_x) LINE *line; int x_coord; int *old_x; { register char *textp = line->text; register int tx = get_shift(line->shift_count) * -SHIFT_SIZE; while (tx < x_coord && *textp != '\n') { if (is_tab(*textp)) { if (*old_x - x_coord == 1 && tab(tx) > x_coord) break; /* Moving left over tab */ else tx = tab(tx); } else tx++; textp++; } *old_x = tx; return textp; } /* * Length_of() returns the number of characters int the string `string' * excluding the '\0'. */ length_of(string) register char *string; { register int count = 0; if (string != NIL_PTR) { while (*string++ != '\0') count++; } return count; } /* * Copy_string() copies the string `from' into the string `to'. `To' must be * long enough to hold `from'. */ copy_string(to, from) register char *to; register char *from; { while (*to++ = *from++) ; } /* * Reset assigns bot_line, top_line and cur_line according to `head_line' * which must be the first line of the screen, and an y-coordinate, * which will be the current y-coordinate (if it isn't larger than last_y) */ reset(head_line, screen_y) LINE *head_line; int screen_y; { register LINE *line; top_line = line = head_line; /* Search for bot_line (might be last line in file) */ for (last_y = 0; last_y < nlines - 1 && last_y < SCREENMAX && line->next != tail; last_y++) line = line->next; bot_line = line; y = (screen_y > last_y) ? last_y : screen_y; /* Set cur_line according to the new y value */ cur_line = proceed(top_line, y); } /* * Set cursor at coordinates x, y. */ set_cursor(nx, ny) int nx, ny; { #ifdef UNIX extern char *tgoto(); tputs(tgoto(CM, nx, ny), 0, _putchar); #else string_print(pos_string); putchar(X_PLUS + nx); putchar(Y_PLUS + YMAX - ny);/* Driver has (0,0) at lower left corner */ #endif UNIX } /* * Routine to open terminal when mined is used in a pipeline. */ open_device() { if ((input_fd = open("/dev/tty", 0)) < 0) panic("Cannot open /dev/tty for read"); } /* * Getchar() reads one character from the terminal. The character must be * masked with 0377 to avoid sign extension. */ getchar() { #ifdef UNIX return (_getchar() & 0177); #else char c; if (read(input_fd, &c, 1) != 1 && quit == FALSE) panic("Can't read one char from fd #0"); return c & 0377; #endif UNIX } /* * Display() shows count lines on the terminal starting at the given * coordinates. When the tail of the list is encountered it will fill the * rest of the screen with blank_line's. * When count is negative, a backwards print from `line' will be done. */ display(x_coord, y_coord, line, count) int x_coord, y_coord; register LINE *line; register int count; { set_cursor(x_coord, y_coord); /* Find new startline if count is negative */ if (count < 0) { line = proceed(line, count); count = -count; } /* Print the lines */ while (line != tail && count-- >= 0) { line_print(line); line = line->next; } /* Print the blank lines (if any) */ if (loading == FALSE) { while (count-- >= 0) { #ifdef UNIX tputs(CE, 0, _putchar); #else string_print(blank_line); #endif UNIX putchar('\n'); } } } /* * Write_char does a buffered output. */ write_char(fd, c) int fd; char c; { screen [out_count++] = c; if (out_count == SCREEN_SIZE) /* Flush on SCREEN_SIZE chars */ return flush_buffer(fd); return FINE; } /* * Writeline writes the given string on the given filedescriptor. */ writeline(fd, text) register int fd; register char *text; { while(*text) if (write_char(fd, *text++) == ERRORS) return ERRORS; return FINE; } /* * Put_line print the given line on the standard output. If offset is not zero * printing will start at that x-coordinate. If the FLAG clear_line is TRUE, * then (screen) line will be cleared when the end of the line has been * reached. */ put_line(line, offset, clear_line) LINE *line; /* Line to print */ int offset; /* Offset to start */ FLAG clear_line; /* Clear to eoln if TRUE */ { register char *textp = line->text; register int count = get_shift(line->shift_count) * -SHIFT_SIZE; int tab_count; /* Used in tab expansion */ /* Skip all chars as indicated by the offset and the shift_count field */ while (count < offset) { if (is_tab(*textp++)) count = tab(count); else count++; } while (*textp != '\n' && count < XBREAK) { if (is_tab(*textp)) { /* Expand tabs to spaces */ tab_count = tab(count); while (count < XBREAK && count < tab_count) { count++; putchar(' '); } textp++; } else { if (*textp >= '\01' && *textp <= '\037') { #ifdef UNIX tputs(SO, 0, _putchar); #else string_print (rev_video); #endif UNIX putchar(*textp++ + '\100'); #ifdef UNIX tputs(SE, 0, _putchar); #else string_print (normal_video); #endif UNIX } else putchar(*textp++); count++; } } /* If line is longer than XBREAK chars, print the shift_mark */ if (count == XBREAK && *textp != '\n') putchar(SHIFT_MARK); /* Clear the rest of the line is clear_line is TRUE */ if (clear_line == TRUE) { #ifdef UNIX tputs(CE, 0, _putchar); #else while (count++ <= XBREAK) putchar(' '); #endif UNIX putchar('\n'); } } /* * Flush the I/O buffer on filedescriptor fd. */ flush_buffer(fd) int fd; { if (out_count <= 0) /* There is nothing to flush */ return FINE; #ifdef UNIX if (fd == STD_OUT) { printf("%.*s", out_count, screen); _flush(); } else #endif UNIX if (write(fd, screen, out_count) != out_count) { bad_write(fd); return ERRORS; } clear_buffer(); /* Empty buffer */ return FINE; } /* * Bad_write() is called when a write failed. Notify the user. */ bad_write(fd) int fd; { if (fd == STD_OUT) /* Cannot write to terminal? */ exit(1); clear_buffer(); build_string(text_buffer, "Command aborted: %s (File incomplete)", (errno == ENOSPC || errno == -ENOSPC) ? "No space on device" : "Write error"); error(text_buffer, NIL_PTR); } /* * Catch the SIGQUIT signal (^\) send to mined. It turns on the quitflag. */ catch() { /* Reset the signal */ signal(SIGQUIT, catch); quit = TRUE; } /* * Abort_mined() will leave mined. Confirmation is asked first. */ abort_mined() { quit = FALSE; /* Ask for confirmation */ status_line("Really abort? ", NIL_PTR); if (getchar() != 'y') { clear_status(); return; } /* Reset terminal */ raw_mode(OFF); set_cursor(0, YMAX); putchar('\n'); flush(); #ifdef UNIX abort(); #else exit(1); #endif UNIX } #define UNDEF -1 /* * Set and reset tty into CBREAK or old mode according to argument `state'. It * also sets all signal characters (except for ^\) to UNDEF. ^\ is caught. */ raw_mode(state) FLAG state; { static struct sgttyb old_tty; static struct sgttyb new_tty; static struct tchars old_tchars; static struct tchars new_tchars = {UNDEF, '\034', UNDEF, UNDEF, UNDEF, UNDEF}; #ifdef UNIX int ldisc; #endif UNIX if (state == OFF) { ioctl(input_fd, TIOCSETP, &old_tty); ioctl(input_fd, TIOCSETC, &old_tchars); #ifdef UNIX ldisc = NTTYDISC; ioctl(input_fd, TIOCSETD, &ldisc); #endif UNIX return; } /* Save old tty settings */ ioctl(input_fd, TIOCGETC, &old_tchars); ioctl(input_fd, TIOCGETP, &old_tty); #ifdef UNIX ldisc = OTTYDISC; ioctl(input_fd, TIOCSETD, &ldisc); #endif UNIX /* Set tty to CBREAK mode */ ioctl(input_fd, TIOCGETP, &new_tty); new_tty.sg_flags |= CBREAK; new_tty.sg_flags &= ~ECHO; ioctl(input_fd, TIOCSETP, &new_tty); /* Unset signal chars */ ioctl(input_fd, TIOCSETC, &new_tchars); /* Only leaves you ^\ */ signal(SIGQUIT, catch); /* Which is caught */ } /* * Panic() is called with an error number and a message. It is called when * something unrecoverable has happened. * It writes the message to the terminal, resets the tty and exits. * Ask the user if he wants to save his file. */ panic(message) register char *message; { extern char yank_file[]; #ifdef UNIX tputs(CL, 0, _putchar); build_string(text_buffer, "%s\nError code %d\n", message, errno); #else build_string(text_buffer, "%s%s\nError code %d\n", enter_string, message, errno); #endif UNIX (void) write(STD_OUT, text_buffer, length_of(text_buffer)); if (loading == FALSE) XT(); /* Check if file can be saved */ else (void) unlink(yank_file); raw_mode(OFF); #ifdef UNIX abort(); #else exit(1); #endif UNIX } #ifndef lint typedef unsigned vir_bytes; #define POINTER_SIZE (sizeof(char *)) #define cast(x) ((vir_bytes) (x)) #define align(x, a) (((x) + (a - 1)) & ~(a - 1)) #define BUSY 1 #define succ(p) (* (char **) (p)) #define is_busy(p) (cast(p) & BUSY) #define set_busy(p) ((char *) (cast(p) | BUSY)) char *free_list; /* * Init_alloc() sets up the free list. The free list initially consists of * MEMORY_SIZE bytes. */ init_alloc() { register char *ptr, *top; extern char *sbrk(); /* Get data space for free list */ free_list = sbrk(POINTER_SIZE); if ((ptr = sbrk(MEMORY_SIZE)) < 0) panic("Bad memory allocation in startup"); top = sbrk(POINTER_SIZE); /* Set up list */ succ(free_list) = ptr; succ(ptr) = top; succ(top) = NIL_PTR; } /* * Allocate size bytes of memory. */ char *alloc(size) unsigned size; { register char *p = free_list; register char *next; char *new; unsigned len = align(size, POINTER_SIZE) + POINTER_SIZE; p = free_list; while ((next = succ(p)) != NIL_PTR) { if (is_busy(next)) /* Already in use */ p = (char *) (cast(next) & ~BUSY); else { while ((new = succ(next)) != NIL_PTR && !is_busy(new)) next = new; if (next - p >= len) { /* fits */ if ((new = p + len) < next) { /* too big */ succ(new) = next; succ(p) = set_busy(new); } else succ(p) = set_busy(next); free_list = p; return (p + POINTER_SIZE); } p = next; } } if (loading == TRUE) panic("File too big."); panic("Out of memory."); } free_space(p) register char *p; { p = (char *) (cast(p) - POINTER_SIZE); *(vir_bytes *) (p) &= ~BUSY; /* Pointer to free list should point to lowest address freed. */ if (free_list > p) free_list = p; } #else char *alloc(bytes) int bytes; { extern char *malloc(); return malloc((unsigned) bytes); } free_space(p) char *p; { free(p); } #endif lint /* ======================================================================== * * Main loops * * ======================================================================== */ /* The mapping between input codes and functions. */ extern UP(), DN(), LF(), RT(), MN(), MP(), GOTO(); extern SD(), SU(), PD(), PU(), HO(), EF(), BL(), EL(), HIGH(), LOW(); extern S(), LIB(), DPC(), DCC(), DLN(), DNW(), DPW(), CTRL(); extern XT(), WT(), VI(), RD(), SH(), I(), FS(), ESC(); extern SF(), SR(), LR(), GR(); extern MA(), YA(), DT(), PT(), WB(), IF(); #ifdef UNIX int (*key_map[128])() = { /* map ASCII characters to functions */ /* 000-017 */ MA, BL, MP, YA, SD, RD, MN, IF, DPC, S, S, DT, LR, S, DNW, LIB, /* 020-037 */ DPW, WB, GR, SH, DLN, SU, VI, WT, XT, PT, EL, ESC, I, GOTO, HIGH, LOW, /* 040-057 */ S, S, S, S, S, S, S, S, S, S, S, PD, S, PU, S, S, /* 060-077 */ S, S, DN, S, LF, FS, RT, S, UP, S, S, S, S, S, S, S, /* 100-117 */ S, S, S, CTRL, S, EF, SF, S, HO, S, S, S, S, S, S, S, /* 120-137 */ S, S, SR, S, S, S, S, S, S, S, S, S, S, S, S, S, /* 140-157 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, /* 160-177 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, DCC }; #else int (*key_map[256])() = { /* map ASCII characters to functions */ /* 000-017 */ I, BL, MP, YA, SD, RD, MN, IF, DPC, S, S, DT, LR, S, DNW, LIB, /* 020-037 */ DPW, WB, GR, SH, DLN, SU, VI, WT, XT, PT, EL, ESC, I, GOTO, HIGH, LOW, /* 040-057 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, /* 060-077 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, /* 100-117 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, /* 120-137 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, /* 140-157 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, /* 160-177 */ S, S, S, S, S, S, S, S, S, S, S, S, S, S, S, DCC, /* 200-217 */ I, I, I, I, I, I, I, I, I, SR, I, I, SF, I, I, I, /* 220-237 */ MA, I, I, I, I, I, I, I, I, I, I, CTRL, I, I, I, I, /* 240-257 */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 260-277 */ I, EF, DN, PD, LF, FS, RT, HO, UP, PU, I, I, I, I, I, I, /* 300-317 */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 320-337 */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 340-357 */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 360-377 */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I }; #endif UNIX int nlines; /* Number of lines in file */ LINE *header; /* Head of line list */ LINE *tail; /* Last line in line list */ LINE *cur_line; /* Current line in use */ LINE *top_line; /* First line of screen */ LINE *bot_line; /* Last line of screen */ char *cur_text; /* Current char on current line in use */ int last_y; /* Last y of screen. Usually SCREENMAX */ char screen[SCREEN_SIZE]; /* Output buffer for "writes" and "reads" */ int x, y; /* x, y coordinates on screen */ FLAG modified = FALSE; /* Set when file is modified */ FLAG stat_visible; /* Set if status_line is visible */ FLAG writable; /* Set if file cannot be written */ FLAG loading; /* Set if we are loading a file. */ FLAG quit = FALSE; /* Set when quit character is typed */ FLAG rpipe = FALSE; /* Set if file should be read from stdin */ int input_fd = 0; /* Fd for command input */ int out_count; /* Index in output buffer */ char file_name[LINE_LEN]; /* Name of file in use */ char text_buffer[MAX_CHARS]; /* Buffer for modifying text */ char blank_line[LINE_LEN]; /* Line filled with spaces */ /* Escape sequences. */ #ifdef UNIX char *CE, *VS, *SO, *SE, *CL, *AL, *CM; #else char *enter_string = "\033 8\033~0"; /* String printed on entering mined */ char *pos_string = "\033"; /* Absolute cursor position */ char *rev_scroll = "\033~1"; /* String for reverse scrolling */ char *rev_video = "\033z\160"; /* String for starting reverse video */ char *normal_video = "\033z\007"; /* String for leaving reverse video */ #endif UNIX /* * Yank variables. */ FLAG yank_status = NOT_VALID; /* Status of yank_file */ char yank_file[] = "/tmp/mined.XXXXXX"; long chars_saved; /* Nr of chars in buffer */ /* * Initialize is called when a another file is edited. It free's the allocated * space and sets modified back to FALSE and fixes the header/tail pointer. */ initialize() { register LINE *line, *next_line; /* Delete the whole list */ for (line = header->next; line != tail; line = next_line) { next_line = line->next; free_space(line->text); free_space(line); } /* header and tail should point to itself */ line->next = line->prev = line; x = y = 0; rpipe = modified = FALSE; } /* * Basename() finds the absolute name of the file out of a given path_name. */ char *basename(path) char *path; { register char *ptr = path; register char *last = NIL_PTR; while (*ptr != '\0') { if (*ptr == '/') last = ptr; ptr++; } if (last == NIL_PTR) return path; if (*(last + 1) == '\0') { /* E.g. /usr/tmp/pipo/ */ *last = '\0'; return basename(path);/* Try again */ } return last + 1; } /* * Load_file loads the file `file' into core. If file is a NIL_PTR or the file * couldn't be opened, just some initializations are done, and a line consisting * of a `\n' is installed. */ load_file(file) char *file; { register LINE *line = header; register int len; long nr_of_chars = 0L; int fd = -1; /* Filedescriptor for file */ nlines = 0; /* Zero lines to start with */ /* Open file */ writable = TRUE; /* Benefit of the doubt */ if (file == NIL_PTR) { if (rpipe == FALSE) status_line("No file.", NIL_PTR); else { fd = 0; file = "standard input"; } file_name[0] = '\0'; } else { copy_string(file_name, file); /* Save file name */ if (access(file, 0) < 0) /* Cannot access file. */ status_line("New file ", file); else if ((fd = open(file, 0)) < 0) status_line("Cannot open ", file); else if (access(file, 2) != 0) /* Set write flag */ writable = FALSE; } /* Read file */ loading = TRUE; /* Loading file, so set flag */ if (fd >= 0) { status_line("Reading ", file); while ((len = get_line(fd, text_buffer)) != ERRORS) { line = line_insert(line, text_buffer, len); nr_of_chars += (long) len; } if (nlines == 0) /* The file was empty! */ line = line_insert(line, "\n", 1); clear_buffer(); /* Clear output buffer */ cur_line = header->next; fstatus("Read", nr_of_chars); (void) close(fd); /* Close file */ } else /* Just install a "\n" */ (void) line_insert(line, "\n", 1); reset(header->next, 0); /* Initialize pointers */ /* Print screen */ display (0, 0, header->next, last_y); move_to (0, 0); flush(); /* Flush buffer */ loading = FALSE; /* Stop loading, reset flag */ } /* * Get_line reads one line from filedescriptor fd. If EOF is reached on fd, * get_line() returns ERRORS, else it returns the length of the string. */ get_line(fd, buffer) int fd; register char *buffer; { static char *last = NIL_PTR; static char *current = NIL_PTR; static int read_chars; register char *cur_pos = current; char *begin = buffer; do { if (cur_pos == last) { if ((read_chars = read(fd, screen, SCREEN_SIZE)) <= 0) break; last = &screen[read_chars]; cur_pos = screen; } if ((unsigned char) *cur_pos >= 0177 || *cur_pos == '\0') panic ("File contains non-ascii characters"); } while ((*buffer++ = *cur_pos++) != '\n'); current = cur_pos; if (read_chars <= 0) { if (buffer == begin) return ERRORS; if (*(buffer - 1) != '\n') if (loading == TRUE) /* Add '\n' to last line of file */ *buffer++ = '\n'; else { *buffer = '\0'; return NO_LINE; } } *buffer = '\0'; return buffer - begin; } /* * Install_line installs the buffer into a LINE structure It returns a pointer * to the allocated structure. */ LINE *install_line(buffer, length) char *buffer; int length; { register LINE *new_line = (LINE *) alloc(sizeof(LINE)); new_line->text = alloc(length + 1); new_line->shift_count = 0; copy_string(new_line->text, buffer); return new_line; } main(argc, argv) int argc; char *argv[]; { /* mined is the Monix editor for the IBM PC. */ register int index; /* Index in key table */ #ifdef UNIX get_term(); tputs(VS, 0, _putchar); tputs(CL, 0, _putchar); #else string_print(enter_string); /* Hello world */ for (index = 0; index < XMAX; index++) /* Fill blank_line with spaces*/ blank_line[index] = ' '; blank_line[XMAX] = '\0'; #endif UNIX if (!isatty(0)) { /* Reading from pipe */ if (argc != 1) { write(2, "Cannot find terminal.\n", 22); exit (1); } rpipe = TRUE; modified = TRUE; /* Set modified so he can write */ open_device(); } raw_mode(ON); /* Set tty to appropriate mode */ init_alloc(); header = tail = (LINE *) alloc(sizeof(LINE)); /* Make header of list*/ header->text = NIL_PTR; header->next = tail->prev = header; /* Load the file (if any) */ if (argc < 2) load_file(NIL_PTR); else { (void) get_file(NIL_PTR, argv[1]); /* Truncate filename */ load_file(argv[1]); } /* Main loop of the editor. */ for (;;) { index = getchar(); if (stat_visible == TRUE) clear_status(); if (quit == TRUE) abort_mined(); else { /* Call the function for this key */ (*key_map[index])(index); flush(); /* Flush output (if any) */ if (quit == TRUE) quit = FALSE; } } /* NOTREACHED */ } /* ======================================================================== * * Miscellaneous * * ======================================================================== */ /* * Redraw the screen */ RD() { /* Clear screen */ #ifdef UNIX tputs(VS, 0, _putchar); tputs(CL, 0, _putchar); #else string_print(enter_string); #endif UNIX /* Print first page */ display(0, 0, top_line, last_y); /* Clear last line */ set_cursor(0, YMAX); #ifdef UNIX tputs(CE, 0, _putchar); #else string_print(blank_line); #endif UNIX move_to(x, y); } /* * Ignore this keystroke. */ I() { } /* * Leave editor. If the file has changed, ask if the user wants to save it. */ XT() { if (modified == TRUE && ask_save() == ERRORS) return; raw_mode(OFF); set_cursor(0, YMAX); putchar('\n'); flush(); (void) unlink(yank_file); /* Might not be necessary */ exit(0); } /* * ESC() prompts for a count and wants a command after that. It repeats the * command count times. If a ^\ is given during repeating, stop looping and * return to main loop. */ ESC() { register int count; register int (*func)(); int index, number; extern int (*key_map[])(), I(); if ((index = get_number("Please enter repeat count.", &number)) == ERRORS) return; if ((func = key_map[index]) == I) { /* Function assigned? */ clear_status(); return; } count = number; while (count-- > 0 && quit == FALSE) { if (stat_visible == TRUE) clear_status(); (*func)(index); flush(); } if (quit == TRUE) /* Abort has been given */ error("Aborted", NIL_PTR); else clear_status(); } /* * Ask the user if he wants to save his file or not. */ ask_save() { register int c; status_line(file_name[0] ? basename(file_name) : "[buffer]" , " has been modified. Save? (y/n)"); while((c = getchar()) != 'y' && c != 'n' && quit == FALSE) { ring_bell(); flush(); } clear_status(); if (c == 'y') return WT(); if (c == 'n') return FINE; quit = FALSE; /* Abort character has been given */ return ERRORS; } /* * Line_number() finds the line number we're on. */ line_number() { register LINE *line = header->next; register int count = 1; while (line != cur_line) { count++; line = line->next; } return count; } /* * Display a line telling how many chars and lines the file contains. Also tell * whether the file is readonly and/or modified. */ file_status(message, count, file, lines, writefl, changed) char *message; register long count; /* Contains number of characters in file */ char *file; int lines; FLAG writefl, changed; { register LINE *line; char msg[LINE_LEN + 40];/* Buffer to hold line */ char yank_msg[LINE_LEN];/* Buffer for msg of yank_file */ if (count < 0) /* Not valid. Count chars in file */ for (line = header->next; line != tail; line = line->next) count += length_of(line->text); if (yank_status != NOT_VALID) /* Append buffer info */ build_string(yank_msg, " Buffer: %D char%s.", chars_saved, (chars_saved == 1L) ? "" : "s"); else yank_msg[0] = '\0'; build_string(msg, "%s %s%s%s %d line%s %D char%s.%s Line %d", message, (rpipe == TRUE && *message != '[') ? "standard input" : basename(file), (changed == TRUE) ? "*" : "", (writefl == FALSE) ? " (Readonly)" : "", lines, (lines == 1) ? "" : "s", count, (count == 1L) ? "" : "s", yank_msg, line_number()); if (length_of(msg) + 1 > LINE_LEN - 4) { msg[LINE_LEN - 4] = SHIFT_MARK; /* Overflow on status line */ msg[LINE_LEN - 3] = '\0'; } status_line(msg, NIL_PTR); /* Print the information */ } /* * Build_string() prints the arguments as described in fmt, into the buffer. * %s indicates an argument string, %d indicated an argument number. */ /* VARARGS */ build_string(buf, fmt, args) register char *buf, *fmt; int args; { int *argptr = &args; char *scanp; while (*fmt) { if (*fmt == '%') { fmt++; switch (*fmt++) { case 's' : scanp = (char *) *argptr; break; case 'd' : scanp = num_out((long) *argptr); break; case 'D' : scanp = num_out((long) *((long *) #ifdef UNIX argptr)); #else argptr++)); #endif UNIX break; default : scanp = ""; } while (*buf++ = *scanp++) ; buf--; argptr++; } else *buf++ = *fmt++; } *buf = '\0'; } /* * Output an (unsigned) long in a 10 digit field without leading zeros. * It returns a pointer to the first digit in the buffer. */ char *num_out(number) long number; { static char num_buf[11]; /* Buffer to build number */ register long digit; /* Next digit of number */ register long pow = 1000000000L; /* Highest ten power of long */ FLAG digit_seen = FALSE; int i; for (i = 0; i < 10; i++) { digit = number / pow; /* Get next digit */ if (digit == 0L && digit_seen == FALSE && i != 9) num_buf[i] = ' '; else { num_buf[i] = '0' + (char) digit; number -= digit * pow; /* Erase digit */ digit_seen = TRUE; } pow /= 10L; /* Get next digit */ } for (i = 0; num_buf[i] == ' '; i++) /* Skip leading spaces */ ; return (&num_buf[i]); } /* * Get_number() read a number from the terminal. The last character typed in is * returned. ERRORS is returned on a bad number. The resulting number is put * into the integer the arguments points to. */ get_number(message, result) char *message; int *result; { register int index; register int count = 0; status_line(message, NIL_PTR); index = getchar(); if (quit == FALSE && (index < '0' || index > '9')) { error("Bad count", NIL_PTR); return ERRORS; } /* Convert input to a decimal number */ while (index >= '0' && index <= '9' && quit == FALSE) { count *= 10; count += index - '0'; index = getchar(); } if (quit == TRUE) { clear_status(); return ERRORS; } *result = count; return index; } /* * Input() reads a string from the terminal. When the KILL character is typed, * it returns ERRORS. */ input(inbuf, clearfl) char *inbuf; FLAG clearfl; { register char *ptr; register char c; /* Character read */ ptr = inbuf; *ptr = '\0'; while (quit == FALSE) { flush(); switch (c = getchar()) { case '\b' : /* Erase previous char */ if (ptr > inbuf) { ptr--; #ifdef UNIX tputs(SE, 0, _putchar); #else string_print(normal_video); #endif UNIX if (is_tab(*ptr)) string_print(" \b\b\b \b\b"); else string_print(" \b\b \b"); #ifdef UNIX tputs(SO, 0, _putchar); #else string_print(rev_video); #endif UNIX string_print(" \b"); *ptr = '\0'; } else ring_bell(); break; case '\n' : /* End of input */ /* If inbuf is empty clear status_line */ return (ptr == inbuf && clearfl == TRUE) ? NO_INPUT :FINE; default : /* Only read ASCII chars */ if ((c >= ' ' && c <= '~') || c == '\t') { *ptr++ = c; *ptr = '\0'; if (c == '\t') string_print("^I"); else putchar(c); string_print(" \b"); } else ring_bell(); } } quit = FALSE; return ERRORS; } /* * Get_file() reads a filename from the terminal. Filenames longer than * FILE_LENGHT chars are truncated. */ get_file(message, file) char *message, *file; { char *ptr; int ret; if (message == NIL_PTR || (ret = get_string(message, file, TRUE)) == FINE) { if (length_of((ptr = basename(file))) > FILE_LENGTH) ptr[FILE_LENGTH] = '\0'; } return ret; } /* ======================================================================== * * UNIX I/O Routines * * ======================================================================== */ #ifdef UNIX #undef putchar _getchar() { char c; if (read(input_fd, &c, 1) != 1 && quit == FALSE) panic ("Cannot read 1 byte from input"); return c & 0177; } _flush() { (void) fflush(stdout); } _putchar(c) char c; { (void) write_char(STD_OUT, c); } get_term() { static char termbuf[50]; extern char *tgetstr(), *getenv(); char *loc = termbuf; char entry[1024]; if (tgetent(entry, getenv("TERM")) <= 0) { printf("Unknown terminal.\n"); exit(1); } AL = tgetstr("al", &loc); CE = tgetstr("ce", &loc); VS = tgetstr("vs", &loc); CL = tgetstr("cl", &loc); SO = tgetstr("so", &loc); SE = tgetstr("se", &loc); CM = tgetstr("cm", &loc); if (!CE || !SO || !SE || !CL || !AL || !CM) { printf("Sorry, no mined on this type of terminal\n"); exit(1); } } #endif UNIX /* * Part 2 of the mined editor. */ /* ======================================================================== * * Move Commands * * ======================================================================== */ #include "mined.h" /* * Move one line up. */ UP() { if (y == 0) { /* Top line of screen. Scroll one line */ (void) reverse_scroll(); move_to(x, y); } else /* Move to previous line */ move_to(x, y - 1); } /* * Move one line down. */ DN() { if (y == last_y) { /* Last line of screen. Scroll one line */ if (bot_line->next == tail && bot_line->text[0] != '\n') { dummy_line(); /* Create new empty line */ DN(); return; } else { (void) forward_scroll(); move_to(x, y); } } else /* Move to next line */ move_to(x, y + 1); } /* * Move left one position. */ LF() { if (x == 0 && get_shift(cur_line->shift_count) == 0) {/* Begin of line */ if (cur_line->prev != header) { UP(); /* Move one line up */ move_to(LINE_END, y); } } else move_to(x - 1, y); } /* * Move right one position. */ RT() { if (*cur_text == '\n') { if (cur_line->next != tail) { /* Last char of file */ DN(); /* Move one line down */ move_to(LINE_START, y); } } else move_to(x + 1, y); } /* * Move to coordinates [0, 0] on screen. */ HIGH() { move_to(0, 0); } /* * Move to coordinates [0, YMAX] on screen. */ LOW() { move_to(0, last_y); } /* * Move to begin of line. */ BL() { move_to(LINE_START, y); } /* * Move to end of line. */ EL() { move_to(LINE_END, y); } /* * GOTO() prompts for a linenumber and moves to that line. */ GOTO() { int number; LINE *line; if (get_number("Please enter line number.", &number) == ERRORS) return; if (number <= 0 || (line = proceed(header->next, number - 1)) == tail) error("Illegal line number: ", num_out((long) number)); else move_to(x, find_y(line)); } /* * Scroll forward one page or to eof, whatever comes first. (Bot_line becomes * top_line of display.) Try to leave the cursor on the same line. If this is * not possible, leave cursor on the line halfway the page. */ PD() { register int i; for (i = 0; i < SCREENMAX; i++) if (forward_scroll() == ERRORS) break; /* EOF reached */ if (y - i < 0) /* Line no longer on screen */ move_to(0, SCREENMAX >> 1); else move_to(0, y - i); } /* * Scroll backwards one page or to top of file, whatever comes first. (Top_line * becomes bot_line of display). The very bottom line (YMAX) is always blank. * Try to leave the cursor on the same line. If this is not possible, leave * cursor on the line halfway the page. */ PU() { register int i; for (i = 0; i < SCREENMAX; i++) if (reverse_scroll() == ERRORS) break; /* Top of file reached */ set_cursor(0, YMAX); /* Erase very bottom line */ #ifdef UNIX tputs(CE, 0, _putchar); #else string_print(blank_line); #endif UNIX if (y + i > SCREENMAX) /* line no longer on screen */ move_to(0, SCREENMAX >> 1); else move_to(0, y + i); } /* * Go to top of file, scrolling if possible, else redrawing screen. */ HO() { if (proceed(top_line, -SCREENMAX) == header) PU(); /* It fits. Let PU do it */ else { reset(header->next, 0);/* Reset top_line, etc. */ RD(); /* Display full page */ } move_to(LINE_START, 0); } /* * Go to last line of file, scrolling if possible, else redrawing screen */ EF() { if (tail->prev->text[0] != '\n') dummy_line(); if (proceed(bot_line, SCREENMAX) == tail) PD(); /* It fits. Let PD do it */ else { reset(proceed(tail->prev, -SCREENMAX), SCREENMAX); RD(); /* Display full page */ } move_to(LINE_START, last_y); } /* * Scroll one line up. Leave the cursor on the same line (if possible). */ SU() { if (top_line->prev == header) /* Top of file. Can't scroll */ return; (void) reverse_scroll(); set_cursor(0, YMAX); /* Erase very bottom line */ #ifdef UNIX tputs(CE, 0, _putchar); #else string_print(blank_line); #endif UNIX move_to(x, (y == SCREENMAX) ? SCREENMAX : y + 1); } /* * Scroll one line down. Leave the cursor on the same line (if possible). */ SD() { if (forward_scroll() != ERRORS) move_to(x, (y == 0) ? 0 : y - 1); else set_cursor(x, y); } /* * Perform a forward scroll. It returns ERRORS if we're at the last line of the * file. */ forward_scroll() { if (bot_line->next == tail) /* Last line of file. No dice */ return ERRORS; top_line = top_line->next; bot_line = bot_line->next; cur_line = cur_line->next; set_cursor(0, YMAX); line_print(bot_line); return FINE; } /* * Perform a backwards scroll. It returns ERRORS if we're at the first line * of the file. */ reverse_scroll() { if (top_line->prev == header) return ERRORS; /* Top of file. Can't scroll */ if (last_y != SCREENMAX) /* Reset last_y if necessary */ last_y++; else bot_line = bot_line->prev; /* Else adjust bot_line */ top_line = top_line->prev; cur_line = cur_line->prev; /* Perform the scroll */ set_cursor(0, 0); #ifdef UNIX tputs(AL, 0, _putchar); #else string_print(rev_scroll); #endif UNIX set_cursor(0, 0); line_print(top_line); return FINE; } /* * A word is defined as a number of non-blank characters separated by tabs * spaces or linefeeds. */ /* * MP() moves to the start of the previous word. A word is defined as a * number of non-blank characters separated by tabs spaces or linefeeds. */ MP() { move_previous_word(NO_DELETE); } move_previous_word(remove) FLAG remove; { register char *begin_line; register char *textp; char start_char = *cur_text; char *start_pos = cur_text; /* Fist check if we're at the beginning of line. */ if (cur_text == cur_line->text) { if (cur_line->prev == header) return; start_char = '\0'; } LF(); begin_line = cur_line->text; textp = cur_text; /* Check if we're in the middle of a word. */ if (!alpha(*textp) || !alpha(start_char)) { while (textp != begin_line && (white_space(*textp) || *textp == '\n')) textp--; } /* Now we're at the end of previous word. Skip non-blanks until a blank comes */ while (textp != begin_line && alpha(*textp)) textp--; /* Go to the next char if we're not at the beginning of the line */ if (textp != begin_line && *textp != '\n') textp++; /* Find the x-coordinate of this address, and move to it */ move_address(textp); if (remove == DELETE) delete(cur_line, textp, cur_line, start_pos); } /* * MN() moves to the start of the next word. A word is defined as a number of * non-blank characters separated by tabs spaces or linefeeds. Always keep in * mind that the pointer shouldn't pass the '\n'. */ MN() { move_next_word(NO_DELETE); } move_next_word(remove) FLAG remove; { register char *textp = cur_text; /* Move to the end of the current word. */ while (*textp != '\n' && alpha(*textp)) textp++; /* Skip all white spaces */ while (*textp != '\n' && white_space(*textp)) textp++; /* If we're deleting. delete the text in between */ if (remove == DELETE) { delete(cur_line, cur_text, cur_line, textp); return; } /* If we're at end of line. move to the first word on the next line. */ if (*textp == '\n' && cur_line->next != tail) { DN(); move_to(LINE_START, y); textp = cur_text; while (*textp != '\n' && white_space(*textp)) textp++; } move_address(textp); } /* ======================================================================== * * Modify Commands * * ======================================================================== */ /* * DCC deletes the character under the cursor. If this character is a '\n' the * current line is joined with the next one. * If this character is the only character of the line, the current line will * be deleted. */ DCC() { if (*cur_text == '\n') delete(cur_line,cur_text, cur_line->next,cur_line->next->text); else delete(cur_line, cur_text, cur_line, cur_text + 1); } /* * DPC deletes the character on the left side of the cursor. If the cursor is * at the beginning of the line, the last character if the previous line is *āäåęēčéźėģķīļšńņóōõö÷ųłśūüżž’ deleted. */ DPC() { if (x == 0 && cur_line->prev == header) return; /* Top of file */ LF(); /* Move one left */ DCC(); /* Delete character under cursor */ } /* * DLN deletes all characters until the end of the line. If the current * character is a '\n', then delete that char. */ DLN() { if (*cur_text == '\n') DCC(); else delete(cur_line, cur_text, cur_line, cur_text + length_of(cur_text) -1); } /* * DNW() deletes the next word (as described in MN()) */ DNW() { if (*cur_text == '\n') DCC(); else move_next_word(DELETE); } /* * DPW() deletes the next word (as described in MP()) */ DPW() { if (cur_text == cur_line->text) DPC(); else move_previous_word(DELETE); } /* * Insert character `character' at current location. */ S(character) register char character; { static char buffer[2]; buffer[0] = character; /* Insert the character */ if (insert(cur_line, cur_text, buffer) == ERRORS) return; /* Fix screen */ if (character == '\n') { set_cursor(0, y); if (y == SCREENMAX) { /* Can't use display */ line_print(cur_line); (void) forward_scroll(); } else { reset(top_line, y); /* Reset pointers */ display(0, y, cur_line, last_y - y); } move_to(0, (y == SCREENMAX) ? y : y + 1); } else if (x + 1 == XBREAK)/* If line must be shifted, just call move_to*/ move_to(x + 1, y); else { /* else display rest of line */ put_line(cur_line, x, FALSE); move_to(x + 1, y); } } /* * CTRL inserts a control-char at the current location. A message that this * function is called is displayed at the status line. */ CTRL() { register char ctrl; status_line("Enter control character.", NIL_PTR); if ((ctrl = getchar()) >= '\01' && ctrl <= '\037') { S(ctrl); /* Insert the char */ clear_status(); } else error ("Unknown control character", NIL_PTR); } /* * LIB insert a line at the current position and moves back to the end of * the previous line. */ LIB() { S('\n'); /* Insert the line */ UP(); /* Move one line up */ move_to(LINE_END, y); /* Move to end of this line */ } /* * Line_insert() inserts a new line with text pointed to by `string'. * It returns the address of the new line. */ LINE *line_insert(line, string, len) register LINE *line; char *string; int len; { register LINE *new_line; /* Allocate space for LINE structure and text */ new_line = install_line(string, len); /* Install the line into the double linked list */ new_line->prev = line; new_line->next = line->next; line->next = new_line; new_line->next->prev = new_line; /* Increment nlines */ nlines++; return new_line; } /* * Insert() insert the string `string' at the given line and location. */ insert(line, location, string) register LINE *line; char *location, *string; { register char *bufp = text_buffer; /* Buffer for building line */ register char *textp = line->text; if (length_of(textp) + length_of(string) >= MAX_CHARS) { error("Line too long", NIL_PTR); return ERRORS; } modified = TRUE; /* File has been modified */ /* Copy part of line until `location' has been reached */ while (textp != location) *bufp++ = *textp++; /* Insert string at this location */ while (*string != '\0') *bufp++ = *string++; *bufp = '\0'; if (*(string - 1) == '\n') /* Insert a new line */ (void) line_insert(line, location, length_of(location)); else /* Append last part of line */ copy_string(bufp, location); /* Install the new text in this line */ free_space(line->text); line->text = alloc(length_of(text_buffer) + 1); copy_string(line->text, text_buffer); return FINE; } /* * Line_delete() deletes the argument line out of the line list. The pointer to * the next line is returned. */ LINE *line_delete(line) register LINE *line; { register LINE *next_line = line->next; /* Delete the line */ line->prev->next = line->next; line->next->prev = line->prev; /* Free allocated space */ free_space(line->text); free_space(line); /* Decrement nlines */ nlines--; return next_line; } /* * Delete() deletes all the characters (including newlines) between the * startposition and endposition and fixes the screen accordingly. It * returns the number of lines deleted. */ delete(start_line, start_textp, end_line, end_textp) register LINE *start_line; LINE *end_line; char *start_textp, *end_textp; { register char *textp = start_line->text; register char *bufp = text_buffer; /* Storage for new line->text */ LINE *line; int line_cnt = 0; /* Nr of lines deleted */ int count = 0; int shift = 0; /* Used in shift calculation */ int nx = x; modified = TRUE; /* File has been modified */ /* Set up new line. Copy first part of start line until start_position. */ while (textp < start_textp) { *bufp++ = *textp++; count++; } /* Check if line doesn't exceed MAX_CHARS */ if (count + length_of(end_textp) >= MAX_CHARS) { error("Line too long", NIL_PTR); return; } /* Copy last part of end_line if end_line is not tail */ copy_string(bufp, (end_textp != NIL_PTR) ? end_textp : "\n"); /* Delete all lines between start and end_position (including end_line) */ line = start_line->next; while (line != end_line->next && line != tail) { line = line_delete(line); line_cnt++; } /* Check if last line of file should be deleted */ if (end_textp == NIL_PTR && length_of(start_line->text) == 1 && nlines > 1) { start_line = start_line->prev; (void) line_delete(start_line->next); line_cnt++; } else { /* Install new text */ free_space(start_line->text); start_line->text = alloc(length_of(text_buffer) + 1); copy_string(start_line->text, text_buffer); } /* Fix screen. First check if line is shifted. Perhaps we should shift it back*/ if (get_shift(start_line->shift_count)) { shift = (XBREAK - count_chars(start_line)) / SHIFT_SIZE; if (shift > 0) { /* Shift line `shift' back */ if (shift >= get_shift(start_line->shift_count)) start_line->shift_count = 0; else start_line->shift_count -= shift; nx += shift * SHIFT_SIZE;/* Reset x value */ } } if (line_cnt == 0) { /* Check if only one line changed */ if (shift > 0) { /* Reprint whole line */ set_cursor(0, y); line_print(start_line); } else { /* Just display last part of line */ set_cursor(x, y); put_line(start_line, x, TRUE); } move_to(nx, y); /* Reset cur_text */ return; } shift = last_y; /* Save value */ reset(top_line, y); display(0, y, start_line, shift - y); move_to((line_cnt == 1) ? nx : 0, y); } /* ======================================================================== * * Yank Commands * * ======================================================================== */ LINE *mark_line; /* For marking position. */ char *mark_text; int lines_saved; /* Nr of lines in buffer */ /* * PT() inserts the buffer at the current location. */ PT() { register int fd; /* File descriptor for buffer */ if ((fd = scratch_file(READ)) == ERRORS) error("Buffer is empty.", NIL_PTR); else { file_insert(fd, FALSE);/* Insert the buffer */ (void) close(fd); } } /* * IF() prompt for a filename and inserts the file at the current location * in the file. */ IF() { register int fd; /* File descriptor of file */ char name[LINE_LEN]; /* Buffer for file name */ /* Get the file name */ if (get_file("Get and insert file:", name) != FINE) return; if ((fd = open(name, 0)) < 0) error("Cannot open ", name); else { file_insert(fd, TRUE); /* Insert the file */ (void) close(fd); } } /* * File_insert() inserts a an opened file (as given by filedescriptor fd) * at the current location. */ file_insert(fd, old_pos) int fd; FLAG old_pos; { char line_buffer[MAX_CHARS]; /* Buffer for next line */ register LINE *line = cur_line; register int line_count = nlines; /* Nr of lines inserted */ LINE *page = cur_line; int ret = ERRORS; /* Get the first piece of text (might be ended with a '\n') from fd */ if (get_line(fd, line_buffer) == ERRORS) return; /* Empty file */ /* Insert this text at the current location. */ if (insert(line, cur_text, line_buffer) == ERRORS) return; /* Repeat getting lines (and inserting lines) until EOF is reached */ while ((ret = get_line(fd, line_buffer)) != ERRORS && ret != NO_LINE) line = line_insert(line, line_buffer, ret); if (ret == NO_LINE) { /* Last line read not ended by a '\n' */ line = line->next; (void) insert(line, line->text, line_buffer); } /* Calculate nr of lines added */ line_count = nlines - line_count; /* Fix the screen */ if (line_count == 0) { /* Only one line changed */ set_cursor(0, y); line_print(line); move_to((old_pos == TRUE) ? x : x + length_of(line_buffer), y); } else { /* Several lines changed */ reset(top_line, y); /* Reset pointers */ while (page != line && page != bot_line->next) page = page->next; if (page != bot_line->next || old_pos == TRUE) display(0, y, cur_line, SCREENMAX - y); if (old_pos == TRUE) move_to(x, y); else if (ret == NO_LINE) move_to(length_of(line_buffer), find_y(line)); else move_to(0, find_y(line->next)); } /* If nr of added line >= REPORT, print the count */ if (line_count >= REPORT) status_line(num_out((long) line_count), " lines added."); } /* * WB() writes the buffer (yank_file) into another file, which * is prompted for. */ WB() { register int new_fd; /* Filedescriptor to copy file */ int yank_fd; /* Filedescriptor to buffer */ register int cnt; /* Count check for read/write */ int ret; /* Error check for write */ char file[LINE_LEN]; /* Output file */ /* Checkout the buffer */ if ((yank_fd = scratch_file(READ)) == ERRORS) { error("Buffer is empty.", NIL_PTR); return; } /* Get file name */ if (get_file("Write buffer to file:", file) != FINE) return; /* Creat the new file */ if ((new_fd = creat(file, 0644)) < 0) { error("Cannot create ", file); return; } status_line("Writing ", file); /* Copy buffer into file */ while ((cnt = read(yank_fd, text_buffer, sizeof(text_buffer))) > 0) if (write(new_fd, text_buffer, cnt) != cnt) { bad_write(new_fd); ret = ERRORS; break; } /* Clean up open files and status_line */ (void) close(new_fd); (void) close(yank_fd); if (ret != ERRORS) /* Bad write */ file_status("Wrote", chars_saved, file, lines_saved, TRUE, FALSE); } /* * MA sets mark_line (mark_text) to the current line (text pointer). */ MA() { mark_line = cur_line; mark_text = cur_text; status_line("Mark set", NIL_PTR); } /* * YA() puts the text between the marked position and the current * in the buffer. */ YA() { set_up(NO_DELETE); } /* * DT() is essentially the same as YA(), but in DT() the text is deleted. */ DT() { set_up(DELETE); } /* * Set_up is an interface to the actual yank. It calls checkmark () to check * if the marked position is still valid. If it is, yank is called with the * arguments in the right order. */ set_up(remove) FLAG remove; /* DELETE if text should be deleted */ { FLAG checkmark(); switch (checkmark()) { case NOT_VALID : error("Mark not set.", NIL_PTR); return; case SMALLER : yank(mark_line, mark_text, cur_line, cur_text, remove); break; case BIGGER : yank(cur_line, cur_text, mark_line, mark_text, remove); break; case SAME : /* Ignore stupid behaviour */ yank_status = EMPTY; chars_saved = 0L; status_line("0 characters saved in buffer.", NIL_PTR); break; } } /* * Check_mark() checks if mark_line and mark_text are still valid pointers. If * they are it returns SMALLER if the marked position is before the current, * BIGGER if it isn't or SAME if somebody didn't get the point. * NOT_VALID is returned when mark_line and/or mark_text are no longer valid. * Legal() checks if mark_text is valid on the mark_line. */ FLAG checkmark() { register LINE *line; FLAG cur_seen = FALSE; /* Special case: check is mark_line and cur_line are the same. */ if (mark_line == cur_line) { if (mark_text == cur_text) /* Even same place */ return SAME; if (legal() == ERRORS) /* mark_text out of range */ return NOT_VALID; return (mark_text < cur_text) ? SMALLER : BIGGER; } /* Start looking for mark_line in the line structure */ for (line = header->next; line != tail; line = line->next) { if (line == cur_line) cur_seen = TRUE; else if (line == mark_line) break; } /* If we found mark_line (line != tail) check for legality of mark_text */ if (line == tail || legal() == ERRORS) return NOT_VALID; /* cur_seen is TRUE if cur_line is before mark_line */ return (cur_seen == TRUE) ? BIGGER : SMALLER; } /* * Legal() checks if mark_text is still a valid pointer. */ legal() { register char *textp = mark_line->text; /* Locate mark_text on mark_line */ while (textp != mark_text && *textp++ != '\0') ; return (*textp == '\0') ? ERRORS : FINE; } /* * Yank puts all the text between start_position and end_position into * the buffer. * The caller must check that the arguments to yank() are valid. (E.g. in * the right order) */ yank(start_line, start_textp, end_line, end_textp, remove) LINE *start_line, *end_line; char *start_textp, *end_textp; FLAG remove; /* DELETE if text should be deleted */ { register LINE *line = start_line; register char *textp = start_textp; int fd; /* Creat file to hold buffer */ if ((fd = scratch_file(WRITE)) == ERRORS) return; chars_saved = 0L; lines_saved = 0; status_line("Saving text.", NIL_PTR); /* Keep writing chars until the end_location is reached. */ while (textp != end_textp) { if (write_char(fd, *textp) == ERRORS) { (void) close(fd); return; } if (*textp++ == '\n') { /* Move to the next line */ line = line->next; textp = line->text; lines_saved++; } chars_saved++; } /* Flush the I/O buffer and close file */ if (flush_buffer(fd) == ERRORS) { (void) close(fd); return; } (void) close(fd); yank_status = VALID; /* * Check if the text should be deleted as well. If it should, the following * hack is used to save a lot of code. First move back to the start_position. * (This might be the location we're on now!) and them delete the text. * It might be a bit confusing the first time somebody uses it. * Delete() will fix the screen. */ if (remove == DELETE) { move_to(find_x(start_line, start_textp), find_y(start_line)); delete(start_line, start_textp, end_line, end_textp); } status_line(num_out(chars_saved), " characters saved in buffer."); } /* * Scratch_file() creates a uniq file in /usr/tmp. If the file couldn't * be created other combinations of files are tried until a maximum * of MAXTRAILS times. After MAXTRAILS times, an error message is given * and ERRORS is returned. */ #define MAXTRAILS 26 scratch_file(mode) FLAG mode; /* Can be READ or WRITE permission */ { static int trials = 0; /* Keep track of trails */ register char *y_ptr, *n_ptr; int fd; /* Filedescriptor to buffer */ /* If yank_status == NOT_VALID, scratch_file is called for the first time */ if (yank_status == NOT_VALID && mode == WRITE) { /* Create new file */ /* Generate file name. */ y_ptr = &yank_file[11]; n_ptr = num_out((long) getpid()); while ((*y_ptr = *n_ptr++) != '\0') y_ptr++; *y_ptr++ = 'a' + trials; *y_ptr = '\0'; /* Check file existence */ if (access(yank_file, 0) == 0 || (fd = creat(yank_file, 0644)) < 0) { if (trials++ >= MAXTRAILS) { error("Unable to creat scratchfile.", NIL_PTR); return ERRORS; } else return scratch_file(mode);/* Have another go */ } } else if ((mode == READ && (fd = open(yank_file, 0)) < 0) || (mode == WRITE && (fd = creat(yank_file, 0644)) < 0)) { yank_status = NOT_VALID; return ERRORS; } clear_buffer(); return fd; } /* ======================================================================== * * Search Routines * * ======================================================================== */ /* * A regular expression consists of a sequence of: * 1. A normal character matching that character. * 2. A . matching any character. * 3. A ^ matching the begin of a line. * 4. A $ (as last character of the pattern) mathing the end of a line. * 5. A \ matching . * 6. A number of characters enclosed in [] pairs matching any of these * characters. A list of characters can be indicated by a '-'. So * [a-z] matches any letter of the alphabet. If the first character * after the '[' is a '^' then the set is negated (matching none of * the characters). * A ']', '^' or '-' can be escaped by putting a '\' in front of it. * 7. If one of the expressions as described in 1-6 is followed by a * '*' than that expressions matches a sequence of 0 or more of * that expression. */ char typed_expression[LINE_LEN]; /* Holds previous expr. */ /* * SF searches forward for an expression. */ SF() { search("Search forward:", FORWARD); } /* * SF searches backwards for an expression. */ SR() { search("Search reverse:", REVERSE); } /* * Get_expression() prompts for an expression. If just a return is typed, the * old expression is used. If the expression changed, compile() is called and * the returning REGEX structure is returned. It returns NIL_REG upon error. * The save flag indicates whether the expression should be appended at the * message pointer. */ REGEX *get_expression(message) char *message; { static REGEX program; /* Program of expression */ char exp_buf[LINE_LEN]; /* Buffer for new expr. */ if (get_string(message, exp_buf, FALSE) == ERRORS) return NIL_REG; if (exp_buf[0] == '\0' && typed_expression[0] == '\0') { error("No previous expression.", NIL_PTR); return NIL_REG; } if (exp_buf[0] != '\0') { /* A new expr. is typed */ copy_string(typed_expression, exp_buf);/* Save expr. */ compile(exp_buf, &program); /* Compile new expression */ } if (program.status == REG_ERROR) { /* Error during compiling */ error(program.result.err_mess, NIL_PTR); return NIL_REG; } return &program; } /* * GR() a replaces all matches from the current position until the end * of the file. */ GR() { change("Global replace:", VALID); } /* * LR() replaces all matches on the current line. */ LR() { change("Line replace:", NOT_VALID); } /* * Change() prompts for an expression and a substitution pattern and changes * all matches of the expression into the substitution. change() start looking * for expressions at the current line and continues until the end of the file * if the FLAG file is VALID. */ change(message, file) char *message; /* Message to prompt for expression */ FLAG file; { char mess_buf[LINE_LEN]; /* Buffer to hold message */ char replacement[LINE_LEN]; /* Buffer to hold subst. pattern */ REGEX *program; /* Program resulting from compilation */ register LINE *line = cur_line; register char *textp; char *substitute(); long lines = 0L; /* Nr of lines on which subs occurred */ long subs = 0L; /* Nr of subs made */ int page = y; /* Index to check if line is on screen*/ /* Save message and get expression */ copy_string(mess_buf, message); if ((program = get_expression(mess_buf)) == NIL_REG) return; /* Get substitution pattern */ build_string(mess_buf, "%s %s by:", mess_buf, typed_expression); if (get_string(mess_buf, replacement, FALSE) == ERRORS) return; set_cursor(0, YMAX); flush(); /* Substitute until end of file */ do { if (line_check(program, line->text, FORWARD)) { lines++; /* Repeat sub. on this line as long as we find a match*/ do { subs++; /* Increment subs */ if ((textp = substitute(line, program,replacement)) == NIL_PTR) return; /* Line too long */ } while ((program->status & BEGIN_LINE) != BEGIN_LINE && (program->status & END_LINE) != END_LINE && line_check(program, textp, FORWARD)); /* Check to see if we can print the result */ if (page <= SCREENMAX) { set_cursor(0, page); line_print(line); } } if (page <= SCREENMAX) page++; line = line->next; } while (line != tail && file == VALID && quit == FALSE); copy_string(mess_buf, (quit == TRUE) ? "(Aborted) " : ""); /* Fix the status line */ if (subs == 0L && quit == FALSE) error("Pattern not found.", NIL_PTR); else if (lines >= REPORT || quit == TRUE) { build_string(mess_buf, "%s %D substitutions on %D lines.", mess_buf, subs, lines); status_line(mess_buf, NIL_PTR); } else if (file == NOT_VALID && subs >= REPORT) status_line(num_out(subs), " substitutions."); else clear_status(); move_to (x, y); } /* * Substitute() replaces the match on this line by the substitute pattern * as indicated by the program. Every '&' in the replacement is replaced by * the original match. A \ in the replacement escapes the next character. */ char *substitute(line, program, replacement) LINE *line; REGEX *program; char *replacement; /* Contains replacement pattern */ { register char *textp = text_buffer; register char *subp = replacement; char *linep = line->text; char *amp; modified = TRUE; /* Copy part of line until the beginning of the match */ while (linep != program->start_ptr) *textp++ = *linep++; /* * Replace the match by the substitution pattern. Each occurrence of '&' is * replaced by the original match. A \ escapes the next character. */ while (*subp != '\0' && textp < &text_buffer[MAX_CHARS]) { if (*subp == '&') { /* Replace the original match */ amp = program->start_ptr; while (amp < program->end_ptr && textp<&text_buffer[MAX_CHARS]) *textp++ = *amp++; subp++; } else { if (*subp == '\\' && *(subp + 1) != '\0') subp++; *textp++ = *subp++; } } /* Check for line length not exceeding MAX_CHARS */ if (length_of(text_buffer) + length_of(program->end_ptr) >= MAX_CHARS) { error("Substitution result: line too big", NIL_PTR); return NIL_PTR; } /* Append last part of line to the new build line */ copy_string(textp, program->end_ptr); /* Free old line and install new one */ free_space(line->text); line->text = alloc(length_of(text_buffer) + 1); copy_string(line->text, text_buffer); return(line->text + (textp - text_buffer)); } /* * Search() calls get_expression to fetch the expression. If this went well, * the function match() is called which returns the line with the next match. * If this line is the NIL_LINE, it means that a match could not be found. * Find_x() and find_y() display the right page on the screen, and return * the right coordinates for x and y. These coordinates are passed to move_to() */ search(message, method) char *message; FLAG method; { register REGEX *program; register LINE *match_line; /* Get the expression */ if ((program = get_expression(message)) == NIL_REG) return; set_cursor(0, YMAX); flush(); /* Find the match */ if ((match_line = match(program, cur_text, method)) == NIL_LINE) { if (quit == TRUE) status_line("Aborted", NIL_PTR); else status_line("Pattern not found.", NIL_PTR); return; } move(0, program->start_ptr, find_y(match_line)); clear_status(); } /* * find_y() checks if the matched line is on the current page. If it is, it * returns the new y coordinate, else it displays the correct page with the * matched line in the middle and returns the new y value; */ find_y(match_line) LINE *match_line; { register LINE *line; register int count = 0; /* Check if match_line is on the same page as currently displayed. */ for (line = top_line; line != match_line && line != bot_line->next; line = line->next) count++; if (line != bot_line->next) return count; /* Display new page, with match_line in center. */ if ((line = proceed(match_line, -(SCREENMAX >> 1))) == header) { /* Can't display in the middle. Make first line of file top_line */ count = 0; for (line = header->next; line != match_line; line = line->next) count++; line = header->next; } else /* New page is displayed. Set cursor to middle of page */ count = SCREENMAX >> 1; /* Reset pointers and redraw the screen */ reset(line, 0); RD(); return count; } /* Opcodes for characters */ #define NORMAL 0x0200 #define DOT 0x0400 #define EOLN 0x0800 #define STAR 0x1000 #define BRACKET 0x2000 #define NEGATE 0x0100 #define DONE 0x4000 /* Mask for opcodes and characters */ #define LOW_BYTE 0x00FF #define HIGH_BYTE 0xFF00 /* Previous is the contents of the previous address (ptr) points to */ #define previous(ptr) (*((ptr) - 1)) /* Buffer to store outcome of compilation */ int exp_buffer[BLOCK_SIZE]; /* Errors often used */ char *too_long = "Regular expression too long"; /* * Reg_error() is called by compile() is something went wrong. It set the * status of the structure to error, and assigns the error field of the union. */ #define reg_error(str) program->status = REG_ERROR, \ program->result.err_mess = (str) /* * Finished() is called when everything went right during compilation. It * allocates space for the expression, and copies the expression buffer into * this field. */ finished(program, last_exp) register REGEX *program; int *last_exp; { register int length = (last_exp - exp_buffer) * sizeof(int); /* Allocate space */ program->result.expression = (int *) alloc(length); /* Copy expression. (expression consists of ints!) */ bcopy(exp_buffer, program->result.expression, length); } /* * Bcopy copies `bytes' bytes from the `from' address into the `to' address. */ bcopy(from, to, bytes) register char *from, *to; register int bytes; { while (bytes--) *to++ = *from++; } /* * Compile compiles the pattern into a more comprehensible form and returns a * REGEX structure. If something went wrong, the status field of the structure * is set to REG_ERROR and an error message is set into the err_mess field of * the union. If all went well the expression is saved and the expression * pointer is set to the saved (and compiled) expression. */ compile(pattern, program) register char *pattern; /* Pointer to pattern */ REGEX *program; { register int *expression = exp_buffer; int *prev_char; /* Pointer to previous compiled atom */ int *acct_field; /* Pointer to last BRACKET start */ FLAG negate; /* Negate flag for BRACKET */ char low_char; /* Index for chars in BRACKET */ char c; /* Check for begin of line */ if (*pattern == '^') { program->status = BEGIN_LINE; pattern++; } else { program->status = 0; /* If the first character is a '*' we have to assign it here. */ if (*pattern == '*') { *expression++ = '*' + NORMAL; pattern++; } } for (; ;) { switch (c = *pattern++) { case '.' : *expression++ = DOT; break; case '$' : /* * Only means EOLN if it is the last char of the pattern */ if (*pattern == '\0') { *expression++ = EOLN | DONE; program->status |= END_LINE; finished(program, expression); return; } else *expression++ = NORMAL + '$'; break; case '\0' : *expression++ = DONE; finished(program, expression); return; case '\\' : /* If last char, it must! mean a normal '\' */ if (*pattern == '\0') *expression++ = NORMAL + '\\'; else *expression++ = NORMAL + *pattern++; break; case '*' : /* * If the previous expression was a [] find out the * begin of the list, and adjust the opcode. */ prev_char = expression - 1; if (*prev_char & BRACKET) *(expression - (*acct_field & LOW_BYTE))|= STAR; else *prev_char |= STAR; break; case '[' : /* * First field in expression gives information about * the list. * The opcode consists of BRACKET and if necessary * NEGATE to indicate that the list should be negated * and/or STAR to indicate a number of sequence of this * list. * The lower byte contains the length of the list. */ acct_field = expression++; if (*pattern == '^') { /* List must be negated */ pattern++; negate = TRUE; } else negate = FALSE; while (*pattern != ']') { if (*pattern == '\0') { reg_error("Missing ]"); return; } if (*pattern == '\\') pattern++; *expression++ = *pattern++; if (*pattern == '-') { /* Make list of chars */ low_char = previous(pattern); pattern++; /* Skip '-' */ if (low_char++ > *pattern) { reg_error("Bad range in [a-z]"); return; } /* Build list */ while (low_char <= *pattern) *expression++ = low_char++; pattern++; } if (expression >= &exp_buffer[BLOCK_SIZE]) { reg_error(too_long); return; } } pattern++; /* Skip ']' */ /* Assign length of list in acct field */ if ((*acct_field = (expression - acct_field)) == 1) { reg_error("Empty []"); return; } /* Assign negate and bracket field */ *acct_field |= BRACKET; if (negate == TRUE) *acct_field |= NEGATE; /* * Add BRACKET to opcode of last char in field because * a '*' may be following the list. */ previous(expression) |= BRACKET; break; default : *expression++ = c + NORMAL; } if (expression == &exp_buffer[BLOCK_SIZE]) { reg_error(too_long); return; } } /* NOTREACHED */ } /* * Match gets as argument the program, pointer to place in current line to * start from and the method to search for (either FORWARD or REVERSE). * Match() will look through the whole file until a match is found. * NIL_LINE is returned if no match could be found. */ LINE *match(program, string, method) REGEX *program; char *string; register FLAG method; { register LINE *line = cur_line; char old_char; /* For saving chars */ /* Corrupted program */ if (program->status == REG_ERROR) return NIL_LINE; /* Check part of text first */ if (!(program->status & BEGIN_LINE)) { if (method == FORWARD) { if (line_check(program, string + 1, method) == MATCH) return cur_line; /* Match found */ } else if (!(program->status & END_LINE)) { old_char = *string; /* Save char and */ *string = '\n'; /* Assign '\n' for line_check */ if (line_check(program, line->text, method) == MATCH) { *string = old_char; /* Restore char */ return cur_line; /* Found match */ } *string = old_char; /* No match, but restore char */ } } /* No match in last (or first) part of line. Check out rest of file */ do { line = (method == FORWARD) ? line->next : line->prev; if (line->text == NIL_PTR) /* Header/tail */ continue; if (line_check(program, line->text, method) == MATCH) return line; } while (line != cur_line && quit == FALSE); /* No match found. */ return NIL_LINE; } /* * Line_check() checks the line (or rather string) for a match. Method * indicates FORWARD or REVERSE search. It scans through the whole string * until a match is found, or the end of the string is reached. */ line_check(program, string, method) register REGEX *program; char *string; FLAG method; { register char *textp = string; /* Assign start_ptr field. We might find a match right away! */ program->start_ptr = textp; /* If the match must be anchored, just check the string. */ if (program->status & BEGIN_LINE) return check_string(program, string, NIL_INT); if (method == REVERSE) { /* First move to the end of the string */ for (textp = string; *textp != '\n'; textp++) ; /* Start checking string until the begin of the string is met */ while (textp >= string) { program->start_ptr = textp; if (check_string(program, textp--, NIL_INT)) return MATCH; } } else { /* Move through the string until the end of is found */ while (quit == FALSE && *textp != '\0') { program->start_ptr = textp; if (check_string(program, textp, NIL_INT)) return MATCH; if (*textp == '\n') break; textp++; } } return NO_MATCH; } /* * Check() checks of a match can be found in the given string. Whenever a STAR * is found during matching, then the begin position of the string is marked * and the maximum number of matches is performed. Then the function star() * is called which starts to finish the match from this position of the string * (and expression). Check() return MATCH for a match, NO_MATCH is the string * couldn't be matched or REG_ERROR for an illegal opcode in expression. */ check_string(program, string, expression) REGEX *program; register char *string; int *expression; { register int opcode; /* Holds opcode of next expr. atom */ char c; /* Char that must be matched */ char *mark; /* For marking position */ int star_fl; /* A star has been born */ if (expression == NIL_INT) expression = program->result.expression; /* Loop until end of string or end of expression */ while (quit == FALSE && !(*expression & DONE) && *string != '\0' && *string != '\n') { c = *expression & LOW_BYTE; /* Extract match char */ opcode = *expression & HIGH_BYTE; /* Extract opcode */ if (star_fl = (opcode & STAR)) { /* Check star occurrence */ opcode &= ~STAR; /* Strip opcode */ mark = string; /* Mark current position */ } expression++; /* Increment expr. */ switch (opcode) { case NORMAL : if (star_fl) while (*string++ == c) /* Skip all matches */ ; else if (*string++ != c) return NO_MATCH; break; case DOT : string++; if (star_fl) /* Skip to eoln */ while (*string != '\0' && *string++ != '\n') ; break; case NEGATE | BRACKET: case BRACKET : if (star_fl) while (in_list(expression, *string++, c, opcode) == MATCH) ; else if (in_list(expression, *string++, c, opcode) == NO_MATCH) return NO_MATCH; expression += c - 1; /* Add length of list */ break; default : panic("Corrupted program in check_string()"); } if (star_fl) return star(program, mark, string, expression); } if (*expression & DONE) { program->end_ptr = string; /* Match ends here */ /* * We might have found a match. The last thing to do is check * whether a '$' was given at the end of the expression, or * the match was found on a null string. (E.g. [a-z]* always * matches) unless a ^ or $ was included in the pattern. */ if ((*expression & EOLN) && *string != '\n' && *string != '\0') return NO_MATCH; if (string == program->start_ptr && !(program->status & BEGIN_LINE) && !(*expression & EOLN)) return NO_MATCH; return MATCH; } return NO_MATCH; } /* * Star() calls check_string() to find out the longest match possible. * It searches backwards until the (in check_string()) marked position * is reached, or a match is found. */ star(program, end_position, string, expression) REGEX *program; register char *end_position; register char *string; int *expression; { do { string--; if (check_string(program, string, expression)) return MATCH; } while (string != end_position); return NO_MATCH; } /* * In_list() checks if the given character is in the list of []. If it is * it returns MATCH. if it isn't it returns NO_MATCH. These returns values * are reversed when the NEGATE field in the opcode is present. */ in_list(list, c, list_length, opcode) register int *list; char c; register int list_length; int opcode; { if (c == '\0' || c == '\n') /* End of string, never matches */ return NO_MATCH; while (list_length-- > 1) { /* > 1, don't check acct_field */ if ((*list & LOW_BYTE) == c) return (opcode & NEGATE) ? NO_MATCH : MATCH; list++; } return (opcode & NEGATE) ? MATCH : NO_MATCH; } /* * Dummy_line() adds an empty line at the end of the file. This is sometimes * useful in combination with the EF and DN command in combination with the * Yank command set. */ dummy_line() { (void) line_insert(tail->prev, "\n", 1); tail->prev->shift_count = DUMMY; if (last_y != SCREENMAX) { last_y++; bot_line = bot_line->next; } } CFLAGS=-F -T. l=../../lib mined: mined1.s mined2.s mined.h @asld -i -o mined -T. $l/crtso.s mined1.s mined2.s $l/libc.a $l/end.s ...sh.hsh1.csh2.csh3.csh4.csh5.csh6.cmakefile/* -------- sh.h -------- */ /* * shell */ #define NULL 0 #define LINELIM 1000 #define NPUSH 8 /* limit to input nesting */ #define NOFILE 20 /* Number of open files */ #define NUFILE 10 /* Number of user-accessible files */ #define FDBASE 10 /* First file usable by Shell */ /* * values returned by wait */ #define WAITSIG(s) ((s)&0177) #define WAITVAL(s) (((s)>>8)&0377) #define WAITCORE(s) (((s)&0200)!=0) /* * library and system defintions */ typedef int xint; /* base type of jmp_buf, for broken compilers */ /* * shell components */ /* #include "area.h" */ /* #include "word.h" */ /* #include "io.h" */ /* #include "var.h" */ #define QUOTE 0200 #define NOBLOCK ((struct op *)NULL) #define NOWORD ((char *)NULL) #define NOWORDS ((char **)NULL) #define NOPIPE ((int *)NULL) /* * Description of a command or an operation on commands. * Might eventually use a union. */ struct op { int type; /* operation type, see below */ char **words; /* arguments to a command */ struct ioword **ioact; /* IO actions (eg, < > >>) */ struct op *left; struct op *right; char *str; /* identifier for case and for */ }; #define TCOM 1 /* command */ #define TPAREN 2 /* (c-list) */ #define TPIPE 3 /* a | b */ #define TLIST 4 /* a [&;] b */ #define TOR 5 /* || */ #define TAND 6 /* && */ #define TFOR 7 #define TDO 8 #define TCASE 9 #define TIF 10 #define TWHILE 11 #define TUNTIL 12 #define TELIF 13 #define TPAT 14 /* pattern in case */ #define TBRACE 15 /* {c-list} */ #define TASYNC 16 /* c & */ /* * actions determining the environment of a process */ #define BIT(i) (1<<(i)) #define FEXEC BIT(0) /* execute without forking */ /* * flags to control evaluation of words */ #define DOSUB 1 /* interpret $, `, and quotes */ #define DOBLANK 2 /* perform blank interpretation */ #define DOGLOB 4 /* interpret [?* */ #define DOKEY 8 /* move words with `=' to 2nd arg. list */ #define DOTRIM 16 /* trim resulting string */ #define DOALL (DOSUB|DOBLANK|DOGLOB|DOKEY|DOTRIM) Extern char **dolv; Extern int dolc; Extern int exstat; Extern char gflg; Extern int talking; /* interactive (talking-type wireless) */ Extern int execflg; Extern int multiline; /* \n changed to ; */ Extern struct op *outtree; /* result from parser */ Extern xint *failpt; Extern xint *errpt; struct brkcon { jmp_buf brkpt; struct brkcon *nextlev; } ; Extern struct brkcon *brklist; Extern int isbreak; /* * redirection */ struct ioword { short io_unit; /* unit affected */ short io_flag; /* action (below) */ union { char *io_name; /* file name */ struct block *io_here; /* here structure pointer */ } io_un; }; #define IOREAD 1 /* < */ #define IOHERE 2 /* << (here file) */ #define IOWRITE 4 /* > */ #define IOCAT 8 /* >> */ #define IOXHERE 16 /* ${}, ` in << */ #define IODUP 32 /* >&digit */ #define IOCLOSE 64 /* >&- */ #define IODEFAULT (-1) /* token for default IO unit */ Extern struct wdblock *wdlist; Extern struct wdblock *iolist; /* * parsing & execution environment */ extern struct env { char *linep; struct io *iobase; struct io *iop; xint *errpt; int iofd; struct env *oenv; } e; /* * flags: * -e: quit on error * -k: look for name=value everywhere on command line * -n: no execution * -t: exit after reading and executing one command * -v: echo as read * -x: trace * -u: unset variables net diagnostic */ extern char *flag; extern char *null; /* null value for variable */ extern int intr; /* interrupt pending */ Extern char *trap[NSIG]; Extern char ourtrap[NSIG]; Extern int trapset; /* trap pending */ extern int inword; /* defer traps and interrupts */ Extern int yynerrs; /* yacc */ Extern char line[LINELIM]; extern char *elinep; /* * other functions */ int (*inbuilt())(); /* find builtin command */ char *rexecve(); char *space(); char *getwd(); char *strsave(); char *evalstr(); char *putn(); char *itoa(); char *unquote(); struct var *lookup(); struct wdblock *add2args(); struct wdblock *glob(); char **makenv(); struct ioword *addio(); char **eval(); int setstatus(); int waitfor(); int onintr(); /* SIGINT handler */ /* * error handling */ void leave(); /* abort shell (or fail in subshell) */ void fail(); /* fail but return to process next command */ int sig(); /* default signal handler */ /* * library functions and system calls */ long lseek(); char *strncpy(); int strlen(); extern int errno; /* -------- var.h -------- */ struct var { char *value; char *name; struct var *next; char status; }; #define COPYV 1 /* flag to setval, suggesting copy */ #define RONLY 01 /* variable is read-only */ #define EXPORT 02 /* variable is to be exported */ #define GETCELL 04 /* name & value space was got with getcell */ Extern struct var *vlist; /* dictionary */ Extern struct var *homedir; /* home directory */ Extern struct var *prompt; /* main prompt */ Extern struct var *cprompt; /* continuation prompt */ Extern struct var *path; /* search path for commands */ Extern struct var *shell; /* shell to interpret command files */ Extern struct var *ifs; /* field separators */ struct var *lookup(/* char *s */); void setval(/* struct var *, char * */); void nameval(/* struct var *, char *val, *name */); void export(/* struct var * */); void ronly(/* struct var * */); int isassign(/* char *s */); int checkname(/* char *name */); int assign(/* char *s, int copyflag */); void putvlist(/* int key, int fd */); int eqname(/* char *n1, char *n2 */); /* -------- io.h -------- */ /* possible arguments to an IO function */ struct ioarg { char *aword; char **awordlist; int afile; /* file descriptor */ }; /* an input generator's state */ struct io { int (*iofn)(); struct ioarg arg; int peekc; char nlcount; /* for `'s */ char xchar; /* for `'s */ char task; /* reason for pushed IO */ }; Extern struct io iostack[NPUSH]; #define XOTHER 0 /* none of the below */ #define XDOLL 1 /* expanding ${} */ #define XGRAVE 2 /* expanding `'s */ #define XIO 4 /* file IO */ #define XHERE 0x80 /* Any of the above inside a here document */ #define XMASK 0x7f /* Get the actual task */ /* in substitution */ #define INSUB() ((e.iop->task&XMASK)==XGRAVE||(e.iop->task&XMASK)==XDOLL) /* * input generators for IO structure */ int nlchar(); int strchar(); int filechar(); int linechar(); int nextchar(); int gravechar(); int qgravechar(); int dolchar(); int wdchar(); /* * IO functions */ int getc(); int readc(); void unget(); void ioecho(); void prs(); void putc(); void prn(); void closef(); void closeall(); /* * IO control */ void pushio(/* struct ioarg arg, int (*gen)() */); int remap(); int openpipe(); void closepipe(); struct io *setbase(/* struct io * */); Extern struct ioarg temparg; /* temporary for PUSHIO */ #define PUSHIO(what,arg,gen) ((temparg.what = (arg)),pushio(temparg,(gen))) #define RUN(what,arg,gen) ((temparg.what = (arg)), run(temparg,(gen))) /* -------- word.h -------- */ #ifndef WORD_H #define WORD_H 1 struct wdblock { short w_bsize; short w_nword; /* bounds are arbitrary */ char *w_words[1]; }; struct wdblock *addword(); struct wdblock *newword(); char **getwords(); #endif /* -------- area.h -------- */ /* * storage allocation */ char *getcell(/* unsigned size */); void garbage(); void setarea(/* char *obj, int to */); void freearea(/* int area */); void freecell(/* char *obj */); Extern int areanum; /* current allocation area */ #define NEW(type) (type *)getcell(sizeof(type)) #define DELETE(obj) freecell((char *)obj) #define Extern extern #include "signal.h" #include "errno.h" #include "setjmp.h" #include "sh.h" /* -------- sh.c -------- */ /* * shell */ /* #include "sh.h" */ int intr; int inparse; char flags['z'-'a'+1]; char *flag = flags-'a'; char *elinep = line+sizeof(line)-5; char *null = ""; int inword =1; struct env e ={line, iostack, iostack-1, NULL, FDBASE, NULL}; extern char **environ; /* environment pointer */ /* * default shell, search rules */ char shellname[] = "/bin/sh"; char search[] = ":/bin:/usr/bin"; int (*qflag)() = SIG_IGN; main(argc, argv) int argc; register char **argv; { register int f; register char *s; int cflag; char *name, **ap; int (*iof)(); initarea(); if ((ap = environ) != NULL) { while (*ap) assign(*ap++, !COPYV); for (ap = environ; *ap;) export(lookup(*ap++)); } closeall(); areanum = 1; shell = lookup("SHELL"); if (shell->value == null) setval(shell, shellname); export(shell); homedir = lookup("HOME"); if (homedir->value == null) setval(homedir, "/"); export(homedir); setval(lookup("$"), itoa(getpid(), 5)); path = lookup("PATH"); if (path->value == null) setval(path, search); export(path); ifs = lookup("IFS"); if (ifs->value == null) setval(ifs, " \t\n"); prompt = lookup("PS1"); if (prompt->value == null) #ifndef UNIXSHELL setval(prompt, "$ "); #else setval(prompt, "% "); #endif if (geteuid() == 0) { setval(prompt, "# "); prompt->status &= ~EXPORT; } cprompt = lookup("PS2"); if (cprompt->value == null) setval(cprompt, "> "); iof = filechar; cflag = 0; name = *argv++; if (--argc >= 1) { if(argv[0][0] == '-' && argv[0][1] != '\0') { for (s = argv[0]+1; *s; s++) switch (*s) { case 'c': prompt->status &= ~EXPORT; cprompt->status &= ~EXPORT; setval(prompt, ""); setval(cprompt, ""); cflag = 1; if (--argc > 0) PUSHIO(aword, *++argv, iof = nlchar); break; case 'q': qflag = SIG_DFL; break; case 's': /* standard input */ break; case 't': prompt->status &= ~EXPORT; setval(prompt, ""); iof = linechar; break; case 'i': talking++; default: if (*s>='a' && *s<='z') flag[*s]++; } } else { argv--; argc++; } if (iof == filechar && --argc > 0) { setval(prompt, ""); setval(cprompt, ""); prompt->status &= ~EXPORT; cprompt->status &= ~EXPORT; if (newfile(*++argv)) exit(1); } } setdash(); if (e.iop < iostack) { PUSHIO(afile, 0, iof); if (isatty(0) && isatty(1) && !cflag) talking++; } signal(SIGQUIT, qflag); if (name[0] == '-') { talking++; if ((f = open("/etc/profile", 0)) >= 0) next(remap(f)); if ((f = open(".profile", 0)) >= 0) next(remap(f)); } if (talking) { signal(SIGTERM, sig); signal(SIGINT, SIG_IGN); } dolv = argv; dolc = argc; dolv[0] = name; if (dolc > 1) for (ap = ++argv; --argc > 0;) if (assign(*ap = *argv++, !COPYV)) dolc--; /* keyword */ else ap++; setval(lookup("#"), putn(dolc-1)); for (;;) { if (talking && e.iop <= iostack) prs(prompt->value); onecommand(); } } setdash() { register char *cp, c; char m['z'-'a'+1]; cp = m; for (c='a'; c<='z'; c++) if (flag[c]) *cp++ = c; *cp = 0; setval(lookup("-"), m); } newfile(s) register char *s; { register f; if (strcmp(s, "-") != 0) { f = open(s, 0); if (f < 0) { prs(s); err(": cannot open"); return(1); } } else f = 0; next(remap(f)); return(0); } onecommand() { register i; jmp_buf m1; inword++; while (e.oenv) quitenv(); freearea(areanum = 1); garbage(); wdlist = 0; iolist = 0; e.errpt = 0; e.linep = line; yynerrs = 0; multiline = 0; inparse = 1; if (talking) signal(SIGINT, onintr); if (setjmp(failpt = m1) || yyparse() || intr) { while (e.oenv) quitenv(); scraphere(); inparse = 0; intr = 0; return; } inparse = 0; inword = 0; if ((i = trapset) != 0) { trapset = 0; runtrap(i); } brklist = 0; intr = 0; execflg = 0; if (!flag['n']) { if (talking) signal(SIGINT, onintr); execute(outtree, NOPIPE, NOPIPE, 0); intr = 0; if (talking) signal(SIGINT, SIG_IGN); } } void fail() { longjmp(failpt, 1); /* NOTREACHED */ } void leave() { if (execflg) fail(); runtrap(0); sync(); exit(exstat); /* NOTREACHED */ } warn(s) register char *s; { if(*s) { prs(s); exstat = -1; } prs("\n"); if (flag['e']) leave(); } err(s) char *s; { warn(s); if (flag['n']) return; if (!talking) leave(); if (e.errpt) longjmp(e.errpt, 1); closeall(); e.iop = e.iobase = iostack; } newenv(f) { register struct env *ep; if (f) { quitenv(); return(1); } ep = (struct env *) space(sizeof(*ep)); if (ep == NULL) { while (e.oenv) quitenv(); fail(); } *ep = e; e.oenv = ep; e.errpt = errpt; return(0); } quitenv() { register struct env *ep; register fd; if ((ep = e.oenv) != NULL) { fd = e.iofd; e = *ep; /* should close `'d files */ DELETE(ep); while (--fd >= e.iofd) close(fd); } } /* * Is any character from s1 in s2? */ int anys(s1, s2) register char *s1, *s2; { while (*s1) if (any(*s1++, s2)) return(1); return(0); } /* * Is character c in s? */ int any(c, s) register int c; register char *s; { while (*s) if (*s++ == c) return(1); return(0); } char * putn(n) register n; { return(itoa(n, -1)); } char * itoa(u, n) register unsigned u; { register char *cp; static char s[20]; int m; m = 0; if (n < 0 && (int) u < 0) { m++; u = -u; } cp = s+sizeof(s); *--cp = 0; do { *--cp = u%10 + '0'; u /= 10; } while (--n > 0 || u); if (m) *--cp = '-'; return(cp); } next(f) { PUSHIO(afile, f, nextchar); } onintr() { signal(SIGINT, SIG_IGN); if (inparse) { prs("\n"); fail(); } intr++; } letter(c) register c; { return(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '_'); } digit(c) register c; { return(c >= '0' && c <= '9'); } letnum(c) register c; { return(letter(c) || digit(c)); } char * space(n) int n; { register char *cp; inword++; if ((cp = getcell(n)) == 0) err("out of string space"); inword--; return(cp); } char * strsave(s, a) register char *s; { register char *cp, *xp; if ((cp = space(strlen(s)+1)) != NULL) { setarea((char *)cp, a); for (xp = cp; (*xp++ = *s++) != '\0';) ; return(cp); } return(""); } /* * if inword is set, traps * are delayed, avoiding * having two people allocating * at once. */ xfree(s) register char *s; { inword++; DELETE(s); inword--; } /* * trap handling */ sig(i) register i; { if (inword == 0) { signal(i, SIG_IGN); runtrap(i); } else trapset = i; signal(i, sig); } runtrap(i) { char *trapstr; if ((trapstr = trap[i]) == NULL) return; if (i == 0) trap[i] = 0; RUN(aword, trapstr, nlchar); } /* -------- var.c -------- */ /* #include "sh.h" */ static char *findeq(); /* * Find the given name in the dictionary * and return its value. If the name was * not previously there, enter it now and * return a null value. */ struct var * lookup(n) register char *n; { register struct var *vp; register char *cp; register int c; static struct var dummy; if (digit(*n)) { dummy.name = n; for (c = 0; digit(*n) && c < 1000; n++) c = c*10 + *n-'0'; dummy.status = RONLY; dummy.value = c <= dolc? dolv[c]: null; return(&dummy); } for (vp = vlist; vp; vp = vp->next) if (eqname(vp->name, n)) return(vp); cp = findeq(n); vp = (struct var *)space(sizeof(*vp)); if (vp == 0 || (vp->name = space((int)(cp-n)+2)) == 0) { dummy.name = dummy.value = ""; return(&dummy); } for (cp = vp->name; (*cp = *n++) && *cp != '='; cp++) ; if (*cp == 0) *cp = '='; *++cp = 0; setarea((char *)vp, 0); setarea((char *)vp->name, 0); vp->value = null; vp->next = vlist; vp->status = GETCELL; vlist = vp; return(vp); } /* * give variable at `vp' the value `val'. */ void setval(vp, val) struct var *vp; char *val; { nameval(vp, val, (char *)NULL); } /* * if name is not NULL, it must be * a prefix of the space `val', * and end with `='. * this is all so that exporting * values is reasonably painless. */ void nameval(vp, val, name) regi !"ster struct var *vp; char *val, *name; { register char *cp, *xp; char *nv; int fl; if (vp->status & RONLY) { for (xp = vp->name; *xp && *xp != '=';) putc(*xp++); err(" is read-only"); return; } fl = 0; if (name == NULL) { xp = space(strlen(vp->name)+strlen(val)+2); if (xp == 0) return; /* make string: name=value */ setarea((char *)xp, 0); name = xp; for (cp = vp->name; (*xp = *cp++) && *xp!='='; xp++) ; if (*xp++ == 0) xp[-1] = '='; nv = xp; for (cp = val; (*xp++ = *cp++) != '\0';) ; val = nv; fl = GETCELL; } if (vp->status & GETCELL) xfree(vp->name); /* form new string `name=value' */ vp->name = name; vp->value = val; vp->status |= fl; } void export(vp) struct var *vp; { vp->status |= EXPORT; } void ronly(vp) struct var *vp; { if (letter(vp->name[0])) /* not an internal symbol ($# etc) */ vp->status |= RONLY; } int isassign(s) register char *s; { if (!letter(*s)) return(0); for (; *s != '='; s++) if (*s == 0 || !letnum(*s)) return(0); return(1); } int assign(s, cf) register char *s; int cf; { register char *cp; struct var *vp; if (!letter(*s)) return(0); for (cp = s; *cp != '='; cp++) if (*cp == 0 || !letnum(*cp)) return(0); vp = lookup(s); nameval(vp, ++cp, cf == COPYV? NULL: s); if (cf != COPYV) vp->status &= ~GETCELL; return(1); } int checkname(cp) register char *cp; { if (!letter(*cp++)) return(0); while (*cp) if (!letnum(*cp++)) return(0); return(1); } void putvlist(f, out) register int f, out; { register struct var *vp; for (vp = vlist; vp; vp = vp->next) if (vp->status & f && letter(*vp->name)) { if (vp->status & EXPORT) write(out, "export ", 7); if (vp->status & RONLY) write(out, "readonly ", 9); write(out, vp->name, (int)(findeq(vp->name) - vp->name)); write(out, "\n", 1); } } int eqname(n1, n2) register char *n1, *n2; { for (; *n1 != '=' && *n1 != 0; n1++) if (*n2++ != *n1) return(0); return(*n2 == 0 || *n2 == '='); } static char * findeq(cp) register char *cp; { while (*cp != '\0' && *cp != '=') cp++; return(cp); } /* -------- gmatch.c -------- */ /* * int gmatch(string, pattern) * char *string, *pattern; * * Match a pattern as in sh(1). */ #define NULL 0 #define CMASK 0377 #define QUOTE 0200 #define QMASK (CMASK&~QUOTE) #define NOT '!' /* might use ^ */ static char *cclass(); int gmatch(s, p) register char *s, *p; { register int sc, pc; if (s == NULL || p == NULL) return(0); while ((pc = *p++ & CMASK) != '\0') { sc = *s++ & QMASK; switch (pc) { case '[': if ((p = cclass(p, sc)) == NULL) return(0); break; case '?': if (sc == 0) return(0); break; case '*': s--; do { if (*p == '\0' || gmatch(s, p)) return(1); } while (*s++ != '\0'); return(0); default: if (sc != (pc&~QUOTE)) return(0); } } return(*s == 0); } static char * cclass(p, sub) register char *p; register int sub; { register int c, d, not, found; if ((not = *p == NOT) != 0) p++; found = not; do { if (*p == '\0') return(NULL); c = *p & CMASK; if (p[1] == '-' && p[2] != ']') { d = p[2] & CMASK; p++; } else d = c; if (c == sub || c <= sub && sub <= d) found = !not; } while (*++p != ']'); return(found? p+1: NULL); } /* -------- area.c -------- */ #define REGSIZE sizeof(struct region) #define GROWBY 256 #undef SHRINKBY 64 #define FREE 32767 #define BUSY 0 #define ALIGN (sizeof(int)-1) /* #include "area.h" */ #define NULL 0 struct region { struct region *next; int area; }; /* * All memory between (char *)areabot and (char *)(areatop+1) is * exclusively administered by the area management routines. * It is assumed that sbrk() and brk() manipulate the high end. */ static struct region *areabot; /* bottom of area */ static struct region *areatop; /* top of area */ static struct region *areanxt; /* starting point of scan */ char *sbrk(); char *brk(); initarea() { while ((int)sbrk(0) & ALIGN) sbrk(1); areabot = (struct region *)sbrk(REGSIZE); areabot->next = areabot; areabot->area = BUSY; areatop = areabot; areanxt = areabot; } char * getcell(nbytes) unsigned nbytes; { register int nregio; register struct region *p, *q; register i; if (nbytes == 0) abort(); /* silly and defeats the algorithm */ /* * round upwards and add administration area */ nregio = (nbytes+(REGSIZE-1))/REGSIZE + 1; for (p = areanxt;;) { if (p->area > areanum) { /* * merge free cells */ while ((q = p->next)->area > areanum) p->next = q->next; /* * exit loop if cell big enough */ if (q >= p + nregio) goto found; } p = p->next; if (p == areanxt) break; } i = nregio >= GROWBY ? nregio : GROWBY; p = (struct region *)sbrk(i * REGSIZE); if ((int)p == -1) return(NULL); p--; if (p != areatop) abort(); /* allocated areas are contiguous */ q = p + i; p->next = q; p->area = FREE; q->next = areabot; q->area = BUSY; areatop = q; found: /* * we found a FREE area big enough, pointed to by 'p', and up to 'q' */ areanxt = p + nregio; if (areanxt < q) { /* * split into requested area and rest */ if (areanxt+1 > q) abort(); /* insufficient space left for admin */ areanxt->next = q; areanxt->area = FREE; p->next = areanxt; } p->area = areanum; return((char *)(p+1)); } void freecell(cp) char *cp; { register struct region *p; if ((p = (struct region *)cp) != NULL) { p--; if (p < areanxt) areanxt = p; p->area = FREE; } } void freearea(a) register int a; { register struct region *p, *top; top = areatop; for (p = areabot; p != top; p = p->next) if (p->area >= a) p->area = FREE; } void setarea(cp,a) char *cp; int a; { register struct region *p; if ((p = (struct region *)cp) != NULL) (p-1)->area = a; } void garbage() { register struct region *p, *q, *top; top = areatop; for (p = areabot; p != top; p = p->next) { if (p->area > areanum) { while ((q = p->next)->area > areanum) p->next = q->next; areanxt = p; } } #ifdef SHRINKBY if (areatop >= q + SHRINKBY && q->area > areanum) { brk((char *)(q+1)); q->next = areabot; q->area = BUSY; areatop = q; } #endif } #define Extern extern #include "signal.h" #include "errno.h" #include "setjmp.h" #include "sh.h" /* -------- csyn.c -------- */ /* * shell: syntax (C version) */ typedef union { char *cp; char **wp; int i; struct op *o; } YYSTYPE; #define WORD 256 #define LOGAND 257 #define LOGOR 258 #define BREAK 259 #define IF 260 #define THEN 261 #define ELSE 262 #define ELIF 263 #define FI 264 #define CASE 265 #define ESAC 266 #define FOR 267 #define WHILE 268 #define UNTIL 269 #define DO 270 #define DONE 271 #define IN 272 #define YYERRCODE 300 /* flags to yylex */ #define CONTIN 01 /* skip new lines to complete command */ /* #include "sh.h" */ #define SYNTAXERR zzerr() static int startl = 1; static int peeksym = 0; static void zzerr(); static void word(); static char **copyw(); static struct op *block(), *namelist(), *list(), *newtp(); static struct op *pipeline(), *andor(), *command(); static struct op *nested(), *simple(), *c_list(); static struct op *dogroup(), *thenpart(), *casepart(), *caselist(); static struct op *elsepart(); static char **wordlist(), **pattern(); static void musthave(); static int yylex(); static struct ioword *io(); static struct ioword **copyio(); static char *tree(); static void diag(); static int nlseen; static int iounit = IODEFAULT; static struct op *tp; struct op *newtp(); static YYSTYPE yylval; int yyparse() { peeksym = 0; yynerrs = 0; outtree = c_list(); musthave('\n', 0); return(yynerrs!=0); } static struct op * pipeline(cf) int cf; { register struct op *t, *p; register int c; t = command(cf); if (t != NULL) { while ((c = yylex(0)) == '|') { if ((p = command(CONTIN)) == NULL) SYNTAXERR; if (t->type != TPAREN && t->type != TCOM) { /* shell statement */ t = block(TPAREN, t, NOBLOCK, NOWORDS); } t = block(TPIPE, t, p, NOWORDS); } peeksym = c; } return(t); } static struct op * andor() { register struct op *t, *p; register int c; t = pipeline(0); if (t != NULL) { while ((c = yylex(0)) == LOGAND || c == LOGOR) { if ((p = pipeline(CONTIN)) == NULL) SYNTAXERR; t = block(c == LOGAND? TAND: TOR, t, p, NOWORDS); } peeksym = c; } return(t); } static struct op * c_list() { register struct op *t, *p; register int c; t = andor(); if (t != NULL) { while ((c = yylex(0)) == ';' || c == '&' || multiline && c == '\n') { if (c == '&') t = block(TASYNC, t, NOBLOCK, NOWORDS); if ((p = andor()) == NULL) return(t); t = list(t, p); } peeksym = c; } return(t); } static int synio(cf) int cf; { register struct ioword *iop; register int i; register int c; if ((c = yylex(cf)) != '<' && c != '>') { peeksym = c; return(0); } i = yylval.i; musthave(WORD, 0); iop = io(iounit, i, yylval.cp); iounit = IODEFAULT; if (i & IOHERE) markhere(yylval.cp, iop); } static void musthave(c, cf) int c, cf; { if ((peeksym = yylex(cf)) != c) SYNTAXERR; peeksym = 0; } static struct op * simple() { register struct op *t; t = NULL; for (;;) { switch (peeksym = yylex(0)) { case '<': case '>': (void) synio(0); break; case WORD: if (t == NULL) { t = newtp(); t->type = TCOM; } peeksym = 0; word(yylval.cp); break; default: return(t); } } } static struct op * nested(type, mark) int type, mark; { register struct op *t; multiline++; t = c_list(); musthave(mark, 0); multiline--; return(block(type, t, NOBLOCK, NOWORDS)); } static struct op * command(cf) int cf; { register struct ioword *io; register struct op *t; struct wdblock *iosave; register int c; iosave = iolist; iolist = NULL; if (multiline) cf |= CONTIN; while (synio(cf)) cf = 0; switch (c = yylex(cf)) { default: peeksym = c; if ((t = simple()) == NULL) { if (iolist == NULL) return(NULL); t = newtp(); t->type = TCOM; } break; case '(': t = nested(TPAREN, ')'); break; case '{': t = nested(TBRACE, '}'); break; case FOR: t = newtp(); t->type = TFOR; musthave(WORD, 0); startl = 1; t->str = yylval.cp; multiline++; t->words = wordlist(); if ((c = yylex(0)) != '\n' && c != ';') SYNTAXERR; t->left = dogroup(0); multiline--; break; case WHILE: case UNTIL: multiline++; t = newtp(); t->type = c == WHILE? TWHILE: TUNTIL; t->left = c_list(); t->right = dogroup(1); t->words = NULL; multiline--; break; case CASE: t = newtp(); t->type = TCASE; musthave(WORD, 0); t->str = yylval.cp; startl++; multiline++; musthave(IN, CONTIN); startl++; t->left = caselist(); musthave(ESAC, 0); multiline--; break; case IF: multiline++; t = newtp(); t->type = TIF; t->left = c_list(); t->right = thenpart(); musthave(FI, 0); multiline--; break; } while (synio(0)) ; t = namelist(t); iolist = iosave; return(t); } static struct op * dogroup(onlydone) int onlydone; { register int c; register struct op *list; c = yylex(CONTIN); if (c == DONE && onlydone) return(NULL); if (c != DO) SYNTAXERR; list = c_list(); musthave(DONE, 0); return(list); } static struct op * thenpart() { register int c; register struct op *t; if ((c = yylex(0)) != THEN) { peeksym = c; return(NULL); } t = newtp(); t->type = 0; t->left = c_list(); if (t->left == NULL) SYNTAXERR; t->right = elsepart(); return(t); } static struct op * elsepart() { register int c; register struct op *t; switch (c = yylex(0)) { case ELSE: if ((t = c_list()) == NULL) SYNTAXERR; return(t); case ELIF: t = newtp(); t->type = TELIF; t->left = c_list(); t->right = thenpart(); return(t); default: peeksym = c; return(NULL); } } static struct op * caselist() { register struct op *t; register int c; t = NULL; while ((peeksym = yylex(CONTIN)) != ESAC) t = list(t, casepart()); return(t); } static struct op * casepart() { register struct op *t; register int c; t = newtp(); t->type = TPAT; t->words = pattern(); musthave(')', 0); t->left = c_list(); if ((peeksym = yylex(CONTIN)) != ESAC) musthave(BREAK, CONTIN); return(t); } static char ** pattern() { register int c, cf; cf = CONTIN; do { musthave(WORD, cf); word(yylval.cp); cf = 0; } while ((c = yylex(0)) == '|'); peeksym = c; word(NOWORD); return(copyw()); } static char ** wordlist() { register int c; if ((c = yylex(0)) != IN) { peeksym = c; return(NULL); } startl = 0; while ((c = yylex(0)) == WORD) word(yylval.cp); word(NOWORD); peeksym = c; return(copyw()); } /* * supporting functions */ static struct op * list(t1, t2) register struct op *t1, *t2; { if (t1 == NULL) return(t2); if (t2 == NULL) return(t1); return(block(TLIST, t1, t2, NOWORDS)); } static struct op * block(type, t1, t2, wp) struct op *t1, *t2; char **wp; { register struct op *t; t = newtp(); t->type = type; t->left = t1; t->right = t2; t->words = wp; return(t); } struct res { char *r_name; int r_val; } restab[] = { "for", FOR, "case", CASE, "esac", ESAC, "while", WHILE, "do", DO, "done", DONE, "if", IF, "in", IN, "then", THEN, "else", ELSE, "elif", ELIF, "until", UNTIL, "fi", FI, ";;", BREAK, "||", LOGOR, "&&", LOGAND, "{", '{', "}", '}', 0, }; rlookup(n) register char *n; { register struct res *rp; for (rp = restab; rp->r_name; rp++) if (strcmp(rp->r_name, n) == 0) return(rp->r_val); return(0); } static struct op * newtp() { register struct op *t; t = (struct op *)tree(sizeof(*t)); t->type = 0; t->words = NULL; t->ioact = NULL; t->left = NULL; t->right = NULL; t->str = NULL; return(t); } static struct op * namelist(t) register struct op *t; { if (iolist) { iolist = addword((char *)NULL, iolist); t->ioact = copyio(); } else t->ioact = NULL; if (t->type != TCOM) { if (t->type != TPAREN && t->ioact != NULL) { t = block(TPAREN, t, NOBLOCK, NOWORDS); t->ioact = t->left->ioact; t->left->ioact = NULL; } return(t); } word(NOWORD); t->words = copyw(); return(t); } static char ** copyw() { register char **wd; wd = getwords(wdlist); wdlist = 0; return(wd); } static void word(cp) char *cp; { wdlist = addword(cp, wdlist); } static struct ioword ** copyio() { re*,-./gister struct ioword **iop; iop = (struct ioword **) getwords(iolist); iolist = 0; return(iop); } static struct ioword * io(u, f, cp) char *cp; { register struct ioword *iop; iop = (struct ioword *) tree(sizeof(*iop)); iop->io_unit = u; iop->io_flag = f; iop->io_un.io_name = cp; iolist = addword((char *)iop, iolist); return(iop); } static void zzerr() { yyerror("syntax error"); } yyerror(s) char *s; { yynerrs++; if (talking) { if (multiline && nlseen) unget('\n'); multiline = 0; while (yylex(0) != '\n') ; } err(s); fail(); } static int yylex(cf) int cf; { register int c, c1; int atstart; if ((c = peeksym) > 0) { peeksym = 0; if (c == '\n') startl = 1; return(c); } nlseen = 0; e.linep = line; atstart = startl; startl = 0; yylval.i = 0; loop: while ((c = getc(0)) == ' ' || c == '\t') ; switch (c) { default: if (any(c, "0123456789")) { unget(c1 = getc(0)); if (c1 == '<' || c1 == '>') { iounit = c - '0'; goto loop; } *e.linep++ = c; c = c1; } break; case '#': while ((c = getc(0)) != 0 && c != '\n') ; unget(c); goto loop; case 0: return(c); case '$': *e.linep++ = c; if ((c = getc(0)) == '{') { if ((c = collect(c, '}')) != '\0') return(c); goto pack; } break; case '`': case '\'': case '"': if ((c = collect(c, c)) != '\0') return(c); goto pack; case '|': case '&': case ';': if ((c1 = dual(c)) != '\0') { startl = 1; return(c1); } startl = 1; return(c); case '^': startl = 1; return('|'); case '>': case '<': diag(c); return(c); case '\n': nlseen++; gethere(); startl = 1; if (multiline || cf & CONTIN) { if (talking && e.iop <= iostack) prs(cprompt->value); if (cf & CONTIN) goto loop; } return(c); case '(': case ')': startl = 1; return(c); } unget(c); pack: while ((c = getc(0)) != 0 && !any(c, "`$ '\"\t;&<>()|^\n")) if (e.linep >= elinep) err("word too long"); else *e.linep++ = c; unget(c); if(any(c, "\"'`$")) goto loop; *e.linep++ = '\0'; if (atstart && (c = rlookup(line))!=0) { startl = 1; return(c); } yylval.cp = strsave(line, areanum); return(WORD); } int collect(c, c1) register c, c1; { char s[2]; *e.linep++ = c; while ((c = getc(c1)) != c1) { if (c == 0) { unget(c); s[0] = c1; s[1] = 0; prs("no closing "); yyerror(s); return(YYERRCODE); } if (talking && c == '\n' && e.iop <= iostack) prs(cprompt->value); *e.linep++ = c; } *e.linep++ = c; return(0); } int dual(c) register c; { char s[3]; register char *cp = s; *cp++ = c; *cp++ = getc(0); *cp = 0; if ((c = rlookup(s)) == 0) unget(*--cp); return(c); } static void diag(ec) register int ec; { register int c; c = getc(0); if (c == '>' || c == '<') { if (c != ec) zzerr(); yylval.i = ec == '>'? IOWRITE|IOCAT: IOHERE; c = getc(0); } else yylval.i = ec == '>'? IOWRITE: IOREAD; if (c != '&' || yylval.i == IOHERE) unget(c); else yylval.i |= IODUP; } static char * tree(size) unsigned size; { register char *t; if ((t = getcell(size)) == NULL) { prs("command line too complicated\n"); fail(); /* NOTREACHED */ } return(t); } /* VARARGS1 */ /* ARGSUSED */ printf(s) /* yyparse calls it */ char *s; { } #define Extern extern #include "signal.h" #include "errno.h" #include "setjmp.h" #include "sh.h" /* -------- exec.c -------- */ /* #include "sh.h" */ /* * execute tree */ static char *signame[] = { "Signal 0", "Hangup", NULL, /* interrupt */ "Quit", "Illegal instruction", "Trace/BPT trap", "abort", "EMT trap", "Floating exception", "Killed", "Bus error", "Memory fault", "Bad system call", NULL, /* broken pipe */ "Alarm clock", "Terminated", }; #define NSIGNAL (sizeof(signame)/sizeof(signame[0])) static struct op *findcase(); static void brkset(); static void echo(); static int forkexec(); static int parent(); int execute(t, pin, pout, act) register struct op *t; int *pin, *pout; int act; { register struct op *t1; int i, pv[2], rv, child, a; char *cp, **wp, **wp2; struct var *vp; struct brkcon bc; if (t == NULL) return(0); rv = 0; a = areanum++; wp = (wp2 = t->words) != NULL? eval(wp2, DOALL): NULL; switch(t->type) { case TPAREN: case TCOM: rv = forkexec(t, pin, pout, act, wp, &child); if (child) { exstat = rv; leave(); } break; case TPIPE: if ((rv = openpipe(pv)) < 0) break; pv[0] = remap(pv[0]); pv[1] = remap(pv[1]); (void) execute(t->left, pin, pv, 0); rv = execute(t->right, pv, pout, 0); break; case TLIST: (void) execute(t->left, pin, pout, 0); rv = execute(t->right, pin, pout, 0); break; case TASYNC: i = parent(); if (i != 0) { if (i != -1) { if (pin != NULL) closepipe(pin); if (talking) { prs(putn(i)); prs("\n"); } } else rv = -1; setstatus(rv); } else { signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); if (talking) signal(SIGTERM, SIG_DFL); talking = 0; if (pin == NULL) { close(0); open("/dev/null", 0); } exit(execute(t->left, pin, pout, FEXEC)); } break; case TOR: case TAND: rv = execute(t->left, pin, pout, 0); if ((t1 = t->right)!=NULL && (rv == 0) == (t->type == TAND)) rv = execute(t1, pin, pout, 0); break; case TFOR: if (wp == NULL) { wp = dolv+1; if ((i = dolc-1) < 0) i = 0; } else i = -1; vp = lookup(t->str); while (setjmp(bc.brkpt)) if (isbreak) goto broken; brkset(&bc); for (t1 = t->left; i-- && *wp != NULL;) { setval(vp, *wp++); rv = execute(t1, pin, pout, 0); } brklist = brklist->nextlev; break; case TWHILE: case TUNTIL: while (setjmp(bc.brkpt)) if (isbreak) goto broken; brkset(&bc); t1 = t->left; while ((execute(t1, pin, pout, 0) == 0) == (t->type == TWHILE)) rv = execute(t->right, pin, pout, 0); brklist = brklist->nextlev; break; case TIF: case TELIF: rv = !execute(t->left, pin, pout, 0)? execute(t->right->left, pin, pout, 0): execute(t->right->right, pin, pout, 0); break; case TCASE: if ((cp = evalstr(t->str, DOSUB|DOTRIM)) == 0) cp = ""; if ((t1 = findcase(t->left, cp)) != NULL) rv = execute(t1, pin, pout, 0); break; case TBRACE: /* if (iopp = t->ioact) while (*iopp) if (iosetup(*iopp++, pin!=NULL, pout!=NULL)) { rv = -1; break; } */ if (rv >= 0 && (t1 = t->left)) rv = execute(t1, pin, pout, 0); break; } broken: t->words = wp2; isbreak = 0; freearea(areanum); areanum = a; if (intr) { closeall(); fail(); } return(rv); } static int forkexec(t, pin, pout, act, wp, pforked) register struct op *t; int *pin, *pout; int act; char **wp; int *pforked; { int i, rv, (*shcom)(); int doexec(); register int f; char *cp; struct ioword **iopp; int resetsig; resetsig = 0; *pforked = 0; shcom = NULL; rv = -1; /* system-detected error */ if (t->type == TCOM) { /* strip all initial assignments */ /* not correct wrt PATH=yyy command etc */ if (flag['x']) echo(wp); while ((cp = *wp++) != NULL && assign(cp, COPYV)) ; wp--; if (cp == NULL && t->ioact == NULL) return(setstatus(0)); else shcom = inbuilt(cp); } t->words = wp; f = act; if (shcom == NULL && (f & FEXEC) == 0) { i = parent(); if (i != 0) { if (i == -1) return(rv); if (pin != NULL) closepipe(pin); return(pout==NULL? setstatus(waitfor(i,0)): 0); } if (talking) { signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); resetsig = 1; } talking = 0; intr = 0; (*pforked)++; brklist = 0; execflg = 0; } #ifdef COMPIPE if ((pin != NULL || pout != NULL) && shcom != NULL && shcom != doexec) { err("piping to/from shell builtins not yet done"); return(-1); } #endif if (pin != NULL) { dup2(pin[0], 0); closepipe(pin); } if (pout != NULL) { dup2(pout[1], 1); closepipe(pout); } if ((iopp = t->ioact) != NULL) { if (shcom != NULL && shcom != doexec) { prs(cp); err(": cannot redirect shell command"); return(-1); } while (*iopp) if (iosetup(*iopp++, pin!=NULL, pout!=NULL)) return(rv); } if (shcom) return(setstatus((*shcom)(t))); /* should use FIOCEXCL */ for (i=FDBASE; itype == TPAREN) exit(execute(t->left, NOPIPE, NOPIPE, FEXEC)); if (resetsig) { signal(SIGINT, SIG_DFL); signal(SIGQUIT, SIG_DFL); } if (wp[0] == NULL) exit(0); cp = rexecve(wp[0], wp, makenv(wp)); prs(wp[0]); prs(": "); warn(cp); if (!execflg) trap[0] = NULL; leave(); /* NOTREACHED */ } /* * common actions when creating a new child */ static int parent() { register int i; i = fork(); if (i != 0) { if (i == -1) warn("try again"); setval(lookup("!"), putn(i)); } return(i); } /* * 0< 1> are ignored as required * within pipelines. */ iosetup(iop, pipein, pipeout) register struct ioword *iop; int pipein, pipeout; { register u; char *cp, *msg; if (iop->io_unit == IODEFAULT) /* take default */ iop->io_unit = iop->io_flag&(IOREAD|IOHERE)? 0: 1; if (pipein && iop->io_unit == 0) return(0); if (pipeout && iop->io_unit == 1) return(0); msg = iop->io_flag&(IOREAD|IOHERE)? "open": "create"; if ((iop->io_flag & IOHERE) == 0) { cp = iop->io_un.io_name; if ((cp = evalstr(cp, DOSUB|DOTRIM)) == NULL) return(1); } if (iop->io_flag & IODUP) { if (cp[1] || !digit(*cp) && *cp != '-') { prs(cp); err(": illegal >& argument"); return(1); } if (*cp == '-') iop->io_flag = IOCLOSE; iop->io_flag &= ~(IOREAD|IOWRITE); } switch (iop->io_flag) { case IOREAD: u = open(cp, 0); break; case IOHERE: case IOHERE|IOXHERE: u = herein(iop->io_un.io_here, iop->io_flag&IOXHERE); cp = "here file"; break; case IOWRITE|IOCAT: if ((u = open(cp, 1)) >= 0) { lseek(u, (long)0, 2); break; } case IOWRITE: u = creat(cp, 0666); break; case IODUP: u = dup2(*cp-'0', iop->io_unit); break; case IOCLOSE: close(iop->io_unit); return(0); } if (u < 0) { prs(cp); prs(": cannot "); warn(msg); return(1); } else { if (u != iop->io_unit) { dup2(u, iop->io_unit); close(u); } } return(0); } static void echo(wp) register char **wp; { register i; prs("+"); for (i=0; wp[i]; i++) { if (i) prs(" "); prs(wp[i]); } prs("\n"); } static struct op ** find1case(t, w) struct op *t; char *w; { register struct op *t1; struct op **tp; register char **wp, *cp; if (t == NULL) return(NULL); if (t->type == TLIST) { if ((tp = find1case(t->left, w)) != NULL) return(tp); t1 = t->right; /* TPAT */ } else t1 = t; for (wp = t1->words; *wp;) if ((cp = evalstr(*wp++, DOSUB)) && gmatch(w, cp)) return(&t1->left); return(NULL); } static struct op * findcase(t, w) struct op *t; char *w; { register struct op **tp; return((tp = find1case(t, w)) != NULL? *tp: NULL); } /* * Enter a new loop level (marked for break/continue). */ static void brkset(bc) struct brkcon *bc; { bc->nextlev = brklist; brklist = bc; } /* * Wait for the last process created. * Print a message for each process found * that was killed by a signal. * Ignore interrupt signals while waiting * unless `canintr' is true. */ int waitfor(lastpid, canintr) register int lastpid; int canintr; { register int pid, rv; int s; rv = 0; do { pid = wait(&s); if (pid == -1) { if (errno != EINTR || canintr) break; } else { if ((rv = WAITSIG(s)) != 0) { if (rv < NSIGNAL) { if (sign79:;<=>?@Aame[rv] != NULL) { if (pid != lastpid) { prn(pid); prs(": "); } prs(signame[rv]); } } else { if (pid != lastpid) { prn(pid); prs(": "); } prs("Signal "); prn(rv); prs(" "); } if (WAITCORE(s)) prs(" - core dumped"); prs("\n"); rv = -1; } else rv = WAITVAL(s); } /* Special patch for MINIX: sync before each command */ sync(); } while (pid != lastpid); return(rv); } int setstatus(s) register int s; { exstat = s; setval(lookup("?"), putn(s)); return(s); } /* * PATH-searching interface to execve. * If getenv("PATH") were kept up-to-date, * execvp might be used. */ char * rexecve(c, v, envp) char *c, **v, **envp; { register int i; register char *sp, *tp; int eacces = 0, asis = 0; extern int errno; sp = any('/', c)? "": path->value; asis = *sp == '\0'; while (asis || *sp != '\0') { asis = 0; tp = e.linep; for (; *sp != '\0'; tp++) if ((*tp = *sp++) == ':') { asis = *sp == '\0'; break; } if (tp != e.linep) *tp++ = '/'; for (i = 0; (*tp++ = c[i++]) != '\0';) ; execve(e.linep, v, envp); switch (errno) { case ENOEXEC: *v = e.linep; tp = *--v; *v = e.linep; execve("/bin/sh", v, envp); *v = tp; return("no Shell"); case ENOMEM: return("program too big"); case E2BIG: return("argument list too long"); case EACCES: eacces++; break; } } return(errno==ENOENT ? "not found" : "cannot execute"); } /* * Run the command produced by generator `f' * applied to stream `arg'. */ run(arg, f) struct ioarg arg; int (*f)(); { struct op *otree; struct wdblock *swdlist; struct wdblock *siolist; jmp_buf ev, rt; xint *ofail; int rv; areanum++; swdlist = wdlist; siolist = iolist; otree = outtree; ofail = failpt; rv = -1; if (newenv(setjmp(errpt = ev)) == 0) { wdlist = 0; iolist = 0; pushio(arg, f); e.iobase = e.iop; yynerrs = 0; if (setjmp(failpt = rt) == 0 && yyparse() == 0) rv = execute(outtree, NOPIPE, NOPIPE, 0); quitenv(); } wdlist = swdlist; iolist = siolist; failpt = ofail; outtree = otree; freearea(areanum--); return(rv); } /* -------- do.c -------- */ /* #include "sh.h" */ /* * built-in commands: doX */ static void rdexp(); static void badid(); static int brkcontin(); dolabel() { return(0); } dochdir(t) register struct op *t; { register char *cp, *er; if ((cp = t->words[1]) == NULL && (cp = homedir->value) == NULL) er = ": no home directory"; else if(chdir(cp) < 0) er = ": bad directory"; else return(0); prs(cp != NULL? cp: "cd"); err(er); return(1); } doshift(t) register struct op *t; { register n; n = t->words[1]? getn(t->words[1]): 1; if(dolc < n) { err("nothing to shift"); return(1); } dolv[n] = dolv[0]; dolv += n; dolc -= n; setval(lookup("#"), putn(dolc)); return(0); } /* * execute login and newgrp directly */ dologin(t) struct op *t; { register char *cp; if (talking) { signal(SIGINT, SIG_DFL); signal(SIGQUIT, SIG_DFL); } cp = rexecve(t->words[0], t->words, makenv(t->words)); prs(t->words[0]); prs(": "); err(cp); return(1); } doumask(t) register struct op *t; { register int i, n; register char *cp; if ((cp = t->words[1]) == NULL) { i = umask(0); umask(i); for (n=3*4; (n-=3) >= 0;) putc('0'+((i>>n)&07)); putc('\n'); } else { for (n=0; *cp>='0' && *cp<='9'; cp++) n = n*8 + (*cp-'0'); umask(n); } return(0); } doexec(t) register struct op *t; { register i; jmp_buf ex; xint *ofail; t->ioact = NULL; for(i = 0; (t->words[i]=t->words[i+1]) != NULL; i++) ; if (i == 0) return(1); execflg = 1; ofail = failpt; if (setjmp(failpt = ex) == 0) execute(t, NOPIPE, NOPIPE, FEXEC); failpt = ofail; execflg = 0; return(1); } dodot(t) struct op *t; { register i; register char *sp, *tp; char *cp; if ((cp = t->words[1]) == NULL) return(0); sp = any('/', cp)? ":": path->value; while (*sp) { tp = e.linep; while (*sp && (*tp = *sp++) != ':') tp++; if (tp != e.linep) *tp++ = '/'; for (i = 0; (*tp++ = cp[i++]) != '\0';) ; if ((i = open(e.linep, 0)) >= 0) { exstat = 0; next(remap(i)); return(exstat); } } prs(cp); err(": not found"); return(-1); } dowait(t) struct op *t; { register i; register char *cp; if ((cp = t->words[1]) != NULL) { i = getn(cp); if (i == 0) return(0); } else i = -1; if (talking) signal(SIGINT, onintr); setstatus(waitfor(i, 1)); if (talking) signal(SIGINT, SIG_IGN); return(0); } doread(t) struct op *t; { register char *cp, **wp; register nb; if (t->words[1] == NULL) { err("Usage: read name ..."); return(1); } for (wp = t->words+1; *wp; wp++) { for (cp = e.linep; cp < elinep-1; cp++) if ((nb = read(0, cp, sizeof(*cp))) != sizeof(*cp) || *cp == '\n' || wp[1] && any(*cp, ifs->value)) break; *cp = 0; if (nb <= 0) break; setval(lookup(*wp), e.linep); } return(nb <= 0); } doeval(t) register struct op *t; { int wdchar(); return(RUN(awordlist, t->words+1, wdchar)); } dotrap(t) register struct op *t; { register char *s; register n, i; if (t->words[1] == NULL) { for (i=0; iwords[2])!=NULL? s: t->words[1]); xfree(trap[n]); trap[n] = 0; if (s != NULL) { if ((i = strlen(s = t->words[1])) != 0) { trap[n] = strsave(s, 0); setsig(n, sig); } else setsig(n, SIG_IGN); } else setsig(n, (n == SIGINT || n == SIGQUIT) && talking? SIG_IGN: SIG_DFL); return(0); } getsig(s) char *s; { register int n; if ((n = getn(s)) < 0 || n >= NSIG) { err("trap: bad signal number"); n = 0; } return(n); } setsig(n, f) register n; int (*f)(); { if (n == 0) return; if (signal(n, SIG_IGN) != SIG_IGN || ourtrap[n]) { ourtrap[n] = 1; signal(n, f); } } getn(as) char *as; { register char *s; register n, m; s = as; m = 1; if (*s == '-') { m = -1; s++; } for (n = 0; digit(*s); s++) n = (n*10) + (*s-'0'); if (*s) { prs(as); err(": bad number"); } return(n*m); } dobreak(t) struct op *t; { return(brkcontin(t->words[1], 1)); } docontinue(t) struct op *t; { return(brkcontin(t->words[1], 0)); } static int brkcontin(cp, val) register char *cp; { register struct brkcon *bc; register nl; nl = cp == NULL? 1: getn(cp); if (nl <= 0) nl = 999; do { if ((bc = brklist) == NULL) break; brklist = bc->nextlev; } while (--nl); if (nl) { err("bad break/continue level"); return(1); } isbreak = val; longjmp(bc->brkpt, 1); /* NOTREACHED */ } doexit(t) struct op *t; { register char *cp; execflg = 0; if ((cp = t->words[1]) != NULL) exstat = getn(cp); leave(); } doexport(t) struct op *t; { rdexp(t->words+1, export, EXPORT); return(0); } doreadonly(t) struct op *t; { rdexp(t->words+1, ronly, RONLY); return(0); } static void rdexp(wp, f, key) register char **wp; void (*f)(); int key; { if (*wp != NULL) { for (; *wp != NULL; wp++) if (checkname(*wp)) (*f)(lookup(*wp)); else badid(*wp); } else putvlist(key, 1); } static void badid(s) register char *s; { prs(s); err(": bad identifier"); } doset(t) register struct op *t; { register struct var *vp; register char *cp; register n; if ((cp = t->words[1]) == NULL) { for (vp = vlist; vp; vp = vp->next) varput(vp->name, 1); return(0); } if (*cp == '-') { t->words++; if (*++cp == 0) flag['x'] = flag['v'] = 0; else for (; *cp; cp++) switch (*cp) { case 'e': if (!talking) flag['e']++; break; default: if (*cp>='a' && *cp<='z') flag[*cp]++; break; } setdash(); } if (t->words[1]) { t->words[0] = dolv[0]; for (n=1; t->words[n]; n++) setarea((char *)t->words[n], 0); dolc = n-1; dolv = t->words; setval(lookup("#"), putn(dolc)); setarea((char *)(dolv-1), 0); } return(0); } varput(s, out) register char *s; { if (letnum(*s)) { write(out, s, strlen(s)); write(out, "\n", 1); } } struct builtin { char *command; int (*fn)(); }; static struct builtin builtin[] = { ":", dolabel, "cd", dochdir, "shift", doshift, "exec", doexec, "wait", dowait, "read", doread, "eval", doeval, "trap", dotrap, "break", dobreak, "continue", docontinue, "exit", doexit, "export", doexport, "readonly", doreadonly, "set", doset, ".", dodot, "umask", doumask, "login", dologin, "newgrp", dologin, 0, }; int (*inbuilt(s))() register char *s; { register struct builtin *bp; for (bp = builtin; bp->command != NULL; bp++) if (strcmp(bp->command, s) == 0) return(bp->fn); return(NULL); } #define Extern extern #include "signal.h" #include "errno.h" #include "setjmp.h" #include "stat.h" #include "sh.h" /* -------- eval.c -------- */ /* #include "sh.h" */ /* #include "word.h" */ /* * ${} * `command` * blank interpretation * quoting * glob */ static char *blank(); static int grave(); static int expand(); static int dollar(); char ** eval(ap, f) register char **ap; { struct wdblock *wb; char **wp; jmp_buf ev; inword++; wp = NULL; wb = NULL; if (newenv(setjmp(errpt = ev)) == 0) { wb = addword((char *)0, wb); /* space for shell name, if command file */ while (expand(*ap++, &wb, f)) ; wb = addword((char *)0, wb); wp = getwords(wb) + 1; quitenv(); } else gflg = 1; inword--; return(gflg? NULL: wp); } /* * Make the exported environment from the exported * names in the dictionary. Keyword assignments * ought to be taken from wp (the list of words on the command line) * but aren't, yet. Until then: ARGSUSED */ char ** makenv(wp) char **wp; { register struct wdblock *wb; register struct var *vp; wb = NULL; for (vp = vlist; vp; vp = vp->next) if (vp->status & EXPORT) wb = addword(vp->name, wb); wb = addword((char *)0, wb); return(getwords(wb)); } char * evalstr(cp, f) register char *cp; int f; { struct wdblock *wb; inword++; wb = NULL; if (expand(cp, &wb, f)) { if (wb == NULL || wb->w_nword == 0 || (cp = wb->w_words[0]) == NULL) cp = ""; DELETE(wb); } else cp = NULL; inword--; return(cp); } static int expand(cp, wbp, f) register char *cp; register struct wdblock **wbp; { jmp_buf ev; gflg = 0; if (cp == NULL) return(0); if (!anys("$`'\"", cp) && !anys(ifs->value, cp) && ((f&DOGLOB)==0 || !anys("[*?", cp))) { cp = strsave(cp, areanum); if (f & DOTRIM) unquote(cp); *wbp = addword(cp, *wbp); return(1); } if (newenv(setjmp(errpt = ev)) == 0) { PUSHIO(aword, cp, strchar); e.iobase = e.iop; while ((cp = blank(f)) && gflg == 0) { e.linep = cp; cp = strsave(cp, areanum); if ((f&DOGLOB) == 0) { if (f & DOTRIM) unquote(cp); *wbp = addword(cp, *wbp); } else *wbp = glob(cp, *wbp); } quitenv(); } else gflg = 1; return(gflg == 0); } /* * Blank interpretation and quoting */ static char * blank(f) { register c, c1; register char *sp; sp = e.linep; loop: switch (c = subgetc('"', 0)) { case 0: if (sp == e.linep) return(0); *e.linep++ = 0; return(sp); default: if (f & DOBLANK && any(c, ifs->value)) goto loop; break; case '"': case '\'': if (INSUB()) break; for (c1 = c; (c = subgetc(c1, 1)) != c1;) { if (c == 0) break; if (c == '\'' || !any(c, "$`\"")) c |= QUOTE; *e.linep++ = c; } c = 0; } unget(c); for (;;) { c = subgetc('"', 0); if (c == 0 || f & DOBLANK && any(c, ifs->value) || !INSUB() && any(c, "\"'`")) { unget(c); if (any(c, "\"'`")) goto loop; break; } *e.linep++ = c; } *e.linep++ = 0; return(sp); } /* * Get characters, substituting for ` and $ */ int subgetc(ec, quoted) register char ec; int quoted; { register char c; again: c = getc(ec); if (!INSUB() && ec != '\'') { if (c == '`') { if (grave(quoted) == 0) return(0); e.iop->task = XGRAVE; goto again; } if (c == '$' && (c = dollar(quoted)) == 0) { e.iop->task = XDOLL; goto again; } } return(c); } /* * Prepare to generate the string returned by ${} substitution. */ static int dollar(quoted) int quoted; { int otask; struct io *oiop; char *dolp; register char *s, c, *cp; struct var *vp; c = readc(); s = e.linep; if (c != '{') { *e.linep++ = c; if (letter(c)) { while ((c = readc())!=0 && letnum(c)) if (e.linep < elinep) *e.linep++ = c; unget(c); } c = 0; } else { oiop = e.iop; otask = e.iop->task; e.iop->task = XOTHER; while ((c = subgetc('"', 0))!=0 && c!='}' && c!='\n') if (e.linep < elinep) *e.linep++ = c; if (oiop == e.iop) e.iop->task = otask; if (c != '}') { err("unclosed ${"); gflg++; return(c); } } if (e.linep >= elinep) { err("string in ${} too long"); gflg++; e.linep -= 10; } *e.linep = 0; if (*s) for (cp = s+1; *cp; cp++) if (any(*cp, "=-+?")) { c = *cp; *cp++ = 0; break; } if (s[1] == 0 && (*s == '*' || *s == '@')) { if (dolc > 1) { /* currently this does not distinguish $* and $@ */ /* should check dollar */ e.linep = s; PUSHIO(awordlist, dolv+1, dolchar); return(0); } else { /* trap the nasty ${=} */ s[0] = '1'; s[1] = 0; } } vp = lookup(s); if ((dolp = vp->value) == null) { switch (c) { case '=': if (digit(*s)) { err("cannot use ${...=...} with $n"); gflg++; break; } setval(vp, cp); dolp = vp->value; break; case '-': dolp = strsave(cp, areanum); break; case '?': if (*cp == 0) { prs("missing value for "); err(s); } else err(cp); gflg++; break; } } else if (c == '+') dolp = strsave(cp, areanum); if (flag['u'] && dolp == null) { prs("unset variable: "); err(s); gflg++; } e.linep = s; PUSHIO(aword, dolp, strchar); return(0); } /* * Run the command in `...` and read its output. */ static int grave(quoted) int quoted; { register char *cp; register int i; int pf[2]; for (cp = e.iop->arg.aword; *cp != '`'; cp++) if (*cp == 0) { err("no closing `"); return(0); } if (openpipe(pf) < 0) return(0); if ((i = fork()) == -1) { closepipe(pf); err("try again"); return(0); } if (i != 0) { e.iop->arg.aword = ++cp; close(pf[1]); PUSHIO(afile, remap(pf[0]), quoted? qgravechar: gravechar); return(1); } *cp = 0; /* allow trapped signals */ for (i=0; iarg.aword, 0); /* jrp debug */ scraphere(); freearea(areanum = 1); /* free old space */ e.oenv = NULL; e.iop = (e.iobase = iostack) - 1; unquote(cp); talking = 0; PUSHIO(aword, cp, nlchar); onecommand(); exit(1); } char * unquote(as) register char *as; { register char *s; if ((s = as) != NULL) while (*s) *s++ &= ~QUOTE; return(as); } /* -------- glob.c -------- */ /* #include "sh.h" */ #define DIRSIZ 14 struct direct { unsigned short d_ino; char d_name[DIRSIZ]; }; /* * glob */ #define scopy(x) strsave((x), areanum) #define BLKSIZ 512 #define NDENT ((BLKSIZ+sizeof(struct direct)-1)/sizeof(struct direct)) static struct wdblock *cl, *nl; static char spcl[] = "[?*"; static int xstrcmp(); static char *generate(); static int anyspcl(); struct wdblock * glob(cp, wb) char *cp; struct wdblock *wb; { register i; register char *pp; if (cp == 0) return(wb); i = 0; for (pp = cp; *pp; pp++) if (any(*pp, spcl)) i++; else if (!any(*pp & ~QUOTE, spcl)) *pp &= ~QUOTE; if (i != 0) { for (cl = addword(scopy(cp), (struct wdblock *)0); anyspcl(cl); cl = nl) { nl = newword(cl->w_nword*2); for(i=0; iw_nword; i++) { /* for each argument */ for (pp = cl->w_words[i]; *pp; pp++) if (any(*pp, spcl)) { globname(cl->w_words[i], pp); break; } if (*pp == '\0') nl = addword(scopy(cl->w_words[i]), nl); } for(i=0; iw_nword; i++) DELETE(cl->w_words[i]); DELETE(cl); } for(i=0; iw_nword; i++) unquote(cl->w_words[i]); glob0((char *)cl->w_words, cl->w_nword, sizeof(char *), xstrcmp); if (cl->w_nword) { for (i=0; iw_nword; i++) wb = addword(cl->w_words[i], wb); DELETE(cl); return(wb); } } wb = addword(unquote(cp), wb); return(wb); } globname(we, pp) char *we; register char *pp; { register char *np, *cp; char *name, *gp, *dp; int dn, j, n, k; struct direct ent[NDENT]; char dname[DIRSIZ+1]; struct stat dbuf; for (np = we; np != pp; pp--) if (pp[-1] == '/') break; for (dp = cp = space((int)(pp-np)+3); np < pp;) *cp++ = *np++; *cp++ = '.'; *cp = '\0'; for (gp = cp = space(strlen(pp)+1); *np && *np != '/';) *cp++ = *np++; *cp = '\0'; dn = open(dp, 0); if (dn < 0) { DELETE(dp); DELETE(gp); return; } dname[DIRSIZ] IKLMN= '\0'; while ((n = read(dn, (char *)ent, sizeof(ent))) >= sizeof(*ent)) { n /= sizeof(*ent); for (j=0; jw_words; for (i=0; iw_nword; i++) if (anys(spcl, *wd++)) return(1); return(0); } static int xstrcmp(p1, p2) char *p1, *p2; { return(strcmp(*(char **)p1, *(char **)p2)); } /* -------- word.c -------- */ /* #include "sh.h" */ /* #include "word.h" */ char *memcpy(); #define NSTART 16 /* default number of words to allow for initially */ struct wdblock * newword(nw) register nw; { register struct wdblock *wb; wb = (struct wdblock *) space(sizeof(*wb) + nw*sizeof(char *)); wb->w_bsize = nw; wb->w_nword = 0; return(wb); } struct wdblock * addword(wd, wb) char *wd; register struct wdblock *wb; { register struct wdblock *wb2; register nw; if (wb == NULL) wb = newword(NSTART); if ((nw = wb->w_nword) >= wb->w_bsize) { wb2 = newword(nw * 2); memcpy((char *)wb2->w_words, (char *)wb->w_words, nw*sizeof(char *)); wb2->w_nword = nw; DELETE(wb); wb = wb2; } wb->w_words[wb->w_nword++] = wd; return(wb); } char ** getwords(wb) register struct wdblock *wb; { register char **wd; register nb; if (wb == NULL) return(NULL); if (wb->w_nword == 0) { DELETE(wb); return(NULL); } wd = (char **) space(nb = sizeof(*wd) * wb->w_nword); memcpy((char *)wd, (char *)wb->w_words, nb); DELETE(wb); /* perhaps should done by caller */ return(wd); } int (*func)(); int globv; glob0(a0, a1, a2, a3) char *a0; unsigned a1; int a2; int (*a3)(); { func = a3; globv = a2; glob1(a0, a0 + a1 * a2); } glob1(base, lim) char *base, *lim; { register char *i, *j; int v2; char **k; char *lptr, *hptr; int c; unsigned n; v2 = globv; top: if ((n=(int)(lim-base)) <= v2) return; n = v2 * (n / (2*v2)); hptr = lptr = base+n; i = base; j = lim-v2; for(;;) { if (i < lptr) { if ((c = (*func)(i, lptr)) == 0) { glob2(i, lptr -= v2); continue; } if (c < 0) { i += v2; continue; } } begin: if (j > hptr) { if ((c = (*func)(hptr, j)) == 0) { glob2(hptr += v2, j); goto begin; } if (c > 0) { if (i == lptr) { glob3(i, hptr += v2, j); i = lptr += v2; goto begin; } glob2(i, j); j -= v2; i += v2; continue; } j -= v2; goto begin; } if (i == lptr) { if (lptr-base >= lim-hptr) { glob1(hptr+v2, lim); lim = lptr; } else { glob1(base, lptr); base = hptr+v2; } goto top; } glob3(j, lptr -= v2, i); j = hptr -= v2; } } glob2(i, j) char *i, *j; { register char *index1, *index2, c; int m; m = globv; index1 = i; index2 = j; do { c = *index1; *index1++ = *index2; *index2++ = c; } while(--m); } glob3(i, j, k) char *i, *j, *k; { register char *index1, *index2, *index3; int c; int m; m = globv; index1 = i; index2 = j; index3 = k; do { c = *index1; *index1++ = *index3; *index3++ = *index2; *index2++ = c; } while(--m); } #define Extern extern #include "signal.h" #include "errno.h" #include "setjmp.h" #include "sh.h" /* -------- io.c -------- */ /* #include "sh.h" */ /* * shell IO */ int getc(ec) register int ec; { register int c; if(e.linep > elinep) { while((c=readc()) != '\n' && c) ; err("input line too long"); gflg++; return(c); } c = readc(); if ((ec != '"') && (ec != '\'')) { if(c == '\\') { c = readc(); if (c == '\n' && ec != '\"') return(getc(ec)); c |= QUOTE; } } return(c); } void unget(c) { if (e.iop >= e.iobase) e.iop->peekc = c; } int readc() { register c; static int eofc; for (; e.iop >= e.iobase; e.iop--) if ((c = e.iop->peekc) != '\0') { e.iop->peekc = 0; return(c); } else if ((c = (*e.iop->iofn)(&e.iop->arg, e.iop)) != '\0') { if (c == -1) { e.iop++; continue; } if (e.iop == iostack) ioecho(c); return(c); } if (e.iop >= iostack || multiline && eofc++ < 3) return(0); leave(); /* NOTREACHED */ } void ioecho(c) char c; { if (flag['v']) write(2, &c, sizeof c); } void pushio(arg, fn) struct ioarg arg; int (*fn)(); { if (++e.iop >= &iostack[NPUSH]) { e.iop--; err("Shell input nested too deeply"); gflg++; return; } e.iop->iofn = fn; e.iop->arg = arg; e.iop->peekc = 0; e.iop->xchar = 0; e.iop->nlcount = 0; if (fn == filechar || fn == linechar || fn == nextchar) e.iop->task = XIO; else if (fn == gravechar || fn == qgravechar) e.iop->task = XGRAVE; else e.iop->task = XOTHER; } struct io * setbase(ip) struct io *ip; { register struct io *xp; xp = e.iobase; e.iobase = ip; return(xp); } /* * Input generating functions */ /* * Produce the characters of a string, then a newline, then EOF. */ int nlchar(ap) register struct ioarg *ap; { register int c; if (ap->aword == NULL) return(0); if ((c = *ap->aword++) == 0) { ap->aword = NULL; return('\n'); } return(c); } /* * Given a list of words, produce the characters * in them, with a space after each word. */ int wdchar(ap) register struct ioarg *ap; { register char c; register char **wl; if ((wl = ap->awordlist) == NULL) return(0); if (*wl != NULL) { if ((c = *(*wl)++) != 0) return(c & 0177); ap->awordlist++; return(' '); } ap->awordlist = NULL; return('\n'); } /* * Return the characters of a list of words, * producing a space between them. */ static int xxchar(), qqchar(); int dolchar(ap) register struct ioarg *ap; { register char *wp; if ((wp = *ap->awordlist++) != NULL) { PUSHIO(aword, wp, *ap->awordlist == NULL? qqchar: xxchar); return(-1); } return(0); } static int xxchar(ap) register struct ioarg *ap; { register int c; if (ap->aword == NULL) return(0); if ((c = *ap->aword++) == '\0') { ap->aword = NULL; return(' '); } return(c); } static int qqchar(ap) register struct ioarg *ap; { register int c; if (ap->aword == NULL || (c = *ap->aword++) == '\0') return(0); return(c); } /* * Produce the characters from a single word (string). */ int strchar(ap) register struct ioarg *ap; { register int c; if (ap->aword == 0 || (c = *ap->aword++) == 0) return(0); return(c); } /* * Return the characters from a file. */ int filechar(ap) register struct ioarg *ap; { register int i; char c; extern int errno; do { i = read(ap->afile, &c, sizeof(c)); } while (i < 0 && errno == EINTR); return(i == sizeof(c)? c&0177: (closef(ap->afile), 0)); } /* * Return the characters produced by a process (`...`). * Quote them if required, and remove any trailing newline characters. */ int gravechar(ap, iop) struct ioarg *ap; struct io *iop; { register int c; if ((c = qgravechar(ap, iop)&~QUOTE) == '\n') c = ' '; return(c); } int qgravechar(ap, iop) register struct ioarg *ap; struct io *iop; { register int c; if (iop->xchar) { if (iop->nlcount) { iop->nlcount--; return('\n'|QUOTE); } c = iop->xchar; iop->xchar = 0; } else if ((c = filechar(ap)) == '\n') { iop->nlcount = 1; while ((c = filechar(ap)) == '\n') iop->nlcount++; iop->xchar = c; if (c == 0) return(c); iop->nlcount--; c = '\n'; } return(c!=0? c|QUOTE: 0); } /* * Return a single command (usually the first line) from a file. */ int linechar(ap) register struct ioarg *ap; { register int c; if ((c = filechar(ap)) == '\n') { if (!multiline) { closef(ap->afile); ap->afile = -1; /* illegal value */ } } return(c); } /* * Return the next character from the command source, * prompting when required. */ int nextchar(ap) register struct ioarg *ap; { register int c; if ((c = filechar(ap)) != 0) return(c); if (talking && e.iop <= iostack+1) prs(prompt->value); return(0); } void prs(s) register char *s; { if (*s) write(2, s, strlen(s)); } void putc(c) char c; { write(2, &c, sizeof c); } void prn(u) unsigned u; { prs(itoa(u, 0)); } void closef(i) register i; { if (i > 2) close(i); } void closeall() { register u; for (u=NUFILE; u= 0 && fd < e.iofd); for (i=0; ih_tag = evalstr(s, DOSUB); if (h->h_tag == 0) return; h->h_iop = iop; h->h_iop->io_un.io_here = NULL; h->h_next = NULL; if (herelist == 0) herelist = h; else for (lh = herelist; lh!=NULL; lh = lh->h_next) if (lh->h_next == 0) { lh->h_next = h; break; } iop->io_flag |= IOHERE|IOXHERE; for (s = h->h_tag; *s; s++) if (*s & QUOTE) { iop->io_flag &= ~ IOXHERE; *s &= ~ QUOTE; } h->h_dosub = iop->io_flag & IOXHERE; } gethere() { register struct here *h; for (h = herelist; h != NULL; h = h->h_next) { h->h_iop->io_un.io_here = readhere(h->h_tag, h->h_dosub? 0: '\'', h->h_iop->io_flag & IOXHERE); } herelist = NULL; } static struct block * readhere(s, ec, nolit) register char *s; { register struct block *bp; register c; jmp_buf ev; bp = (struct block *) space(sizeof(*bp)); if (bp == 0) return(0); bp->b_linebuf = (char *)space(NCPB); if (bp->b_linebuf == 0) { /* jrp - should release bp here... */ return(0); } if (newenv(setjmp(errpt = ev)) == 0) { if (e.iop == iostack && e.iop->iofn == filechar) { pushio(e.iop->arg, filechar); e.iobase = e.iop; } /* jrp changes */ bp->b_linebuf[0] = 0; bp->b_next = bp->b_linebuf; bp->b_tmpfile[0] = 0; bp->b_fd = -1; for (;;) { while ((c = getc(ec)) != '\n' && c) { if (ec == '\'') c &= ~ QUOTE; if (savec(c, bp, nolit) == 0) { c = 0; break; } } savec(0, bp, nolit); if (strcmp(s, bp->b_linebuf) == 0 || c == 0) break; savec('\n', bp, nolit); } *bp->b_linebuf = 0; if (c == 0) { prs("here document `"); prs(s); err("' unclosed"); } quitenv(); } return(bp); } static savec(c, bp, nolit) register struct block *bp; { /* jrp - gutted routine completely, modified to use temp file. */ /* If the file is not open, see if a filename needs to be * created. If so, create one. Then create the file. */ char * lp; char * cp; static int inc; int len; if(bp->b_fd < 0) { if(bp->b_tmpfile[0] == 0) { /* Key this by the PID plus a tag... */ for (cp =VXYZ bp->b_tmpfile, lp = "/tmp/shtm"; (*cp = *lp++) != '\0'; cp++) ; inc = (inc + 1) % 100; lp = putn(getpid()*100 + inc); for (; (*cp = *lp++) != '\0'; cp++) ; } /* Create the file, then open it for * read/write access. After opening the * file, unlink it to it'll go away when * we're through using it. */ bp->b_fd = creat(bp->b_tmpfile, 0600); close(bp->b_fd); bp->b_fd = open(bp->b_tmpfile, 2); unlink(bp->b_tmpfile); if(bp->b_fd < 0) { return(0); } } /* Stuff the character into the line buffer. If it's a * newline, then insert it before the trailing null, write * out the line, and reset the line buffer. */ if(c == '\n') { bp->b_next[-1] = '\n'; bp->b_next[0] = '\0'; len = strlen(bp->b_linebuf); /* Write this out, unless the line ended * with a backslash... */ if((len > 1) && (bp->b_next[-2] != '\\')) { write_linebuf(bp, nolit); } return(1); } else { if(bp->b_next == &(bp->b_linebuf[NCPB - 1])) { prs("here: line buffer full\n"); return(0); } *(bp->b_next++) = c; return(1); } } write_linebuf(bp, nolit) struct block * bp; { char c; jmp_buf ev; if(nolit) { if (newenv(setjmp(errpt = ev)) == 0) { PUSHIO(aword, bp->b_linebuf, strchar); setbase(e.iop); e.iop->task |= XHERE; while ((c = subgetc(0, 0)) != 0) { c &= ~ QUOTE; write(bp->b_fd, &c, sizeof c); } quitenv(); } } else { write(bp->b_fd, bp->b_linebuf, strlen(bp->b_linebuf)); } /* Zap the line buffer for next time... */ bp->b_next = bp->b_linebuf; bp->b_linebuf[0] = 0; } herein(bp, xdoll) struct block *bp; { int ret_fd; if (bp == 0) return(-1); /* If we have a temp file, then rewind it to the beginning */ if(bp->b_fd < 0) { return(-1); } lseek(bp->b_fd, 0L, 0); /* Free up this block pointer, as we're * not going to need it anymore. */ xfree(bp->b_linebuf); xfree(bp); return(bp->b_fd); } scraphere() { struct here * h; struct here * nexth; struct block * bp; /* Close and unlink any files associated with * heres in progress, and free up all the * associated structures. */ h = herelist; while(h != NULL) { nexth = h->h_next; bp = (struct block *)h->h_iop->io_un.io_here; if(bp != NULL) { if(bp->b_fd >= 0) { close(bp->b_fd); } if(*bp->b_tmpfile) { unlink(bp->b_tmpfile); } xfree(bp->b_linebuf); xfree(bp); } xfree(h); h = nexth; } herelist = NULL; } char * memcpy(ato, from, nb) register char *ato, *from; register int nb; { register char *to; to = ato; while (--nb >= 0) *to++ = *from++; return(ato); } #define Extern #include #include #include #include "sh.h" CFLAGS=-F l=../../lib shobj = sh1.s sh2.s sh3.s sh4.s sh5.s sh6.s sh: $(shobj) sh.h @asld -i -o sh -T. $l/crtso.s $(shobj) $l/libc.a $l/end.s @chmem =8000 sh .h -rw-r--r-- 1 bin 60691 Sep 1 09:00 mined1.c -rw-r--r-- 1 bin 44850 Sep 1 09:00 mined2.c commands/sh: total 83 -rw-r--r-- 1 bin 162 Sep 1 09:00 makefile -rw-r--r-- 1 bin 7469 Sep 1 09:00 sh.h -rw-r--r-- 1 bin 14409 Sep 1 09:00 sh1.c -rw-r--r-- 1 bin 11481 Sep 1 09:00 sh2.c -rw-r--r-- 1 bin 16873 Sep 1 09:00 sh3.c -rw-r--r-- 1 bin 12265 Sep 1 09:00 sh4.c -rw-r--r-- 1 bin 10877 Sep 1 09:00 sh5.c -rw-r--r-- 1 bin 92 Sep 1 09:00 sh6.c E2’d--75511