]> err.no Git - sash/commitdiff
Imported Upstream version 2.1 upstream/2.1
authorTollef Fog Heen <tfheen@err.no>
Sun, 20 Apr 2014 06:13:08 +0000 (08:13 +0200)
committerTollef Fog Heen <tfheen@err.no>
Sun, 20 Apr 2014 06:13:08 +0000 (08:13 +0200)
13 files changed:
Makefile [new file with mode: 0644]
README [new file with mode: 0644]
cmd_dd.c [new file with mode: 0644]
cmd_ed.c [new file with mode: 0644]
cmd_grep.c [new file with mode: 0644]
cmd_gzip.c [new file with mode: 0644]
cmd_ls.c [new file with mode: 0644]
cmd_tar.c [new file with mode: 0644]
cmds.c [new file with mode: 0644]
sash.1 [new file with mode: 0644]
sash.c [new file with mode: 0644]
sash.h [new file with mode: 0644]
utils.c [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..b7f90d4
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,28 @@
+#
+# Makefile for sash
+#
+
+CFLAGS = -O3 -Wall -DHAVE_GZIP
+LDFLAGS = -static -s
+LIBS = -lz
+
+
+BINDIR = /bin
+MANDIR = /usr/man/man1
+
+
+OBJS = sash.o cmds.o cmd_dd.o cmd_ed.o cmd_grep.o cmd_ls.o cmd_tar.o \
+       cmd_gzip.o utils.o
+
+
+sash:  $(OBJS)
+       cc $(LDFLAGS) -o sash $(OBJS) $(LIBS)
+
+clean:
+       rm -f $(OBJS) sash
+
+install: sash
+       cp sash $(BINDIR)/sash
+       cp sash.1 $(MANDIR)/sash.1
+
+$(OBJS):       sash.h
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..64a2248
--- /dev/null
+++ b/README
@@ -0,0 +1,12 @@
+This is release 2.1 of sash, my stand-alone shell for Linux.
+
+The purpose of this program is to make replacing of shared libraries
+easy and safe.  It does this by firstly being linked statically, and
+secondly by including many of the standard utilities within itself.
+Read the sash.1 documentation for more details.
+
+Type "make install" to build and install the program and man page.
+
+David I. Bell
+dbell@canb.auug.org.au
+March 8, 1998
diff --git a/cmd_dd.c b/cmd_dd.c
new file mode 100644 (file)
index 0000000..dfaeae5
--- /dev/null
+++ b/cmd_dd.c
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 1998 by David I. Bell
+ * Permission is granted to use, distribute, or modify this source,
+ * provided that this copyright notice remains intact.
+ *
+ * The "dd" built-in command.
+ */
+
+#include "sash.h"
+
+
+#define        PAR_NONE        0
+#define        PAR_IF          1
+#define        PAR_OF          2
+#define        PAR_BS          3
+#define        PAR_COUNT       4
+#define        PAR_SEEK        5
+#define        PAR_SKIP        6
+
+
+typedef        struct {
+       char    *name;
+       int     value;
+} PARAM;
+
+
+static const PARAM     params[] =
+{
+       {"if",          PAR_IF},
+       {"of",          PAR_OF},
+       {"bs",          PAR_BS},
+       {"count",       PAR_COUNT},
+       {"seek",        PAR_SEEK},
+       {"skip",        PAR_SKIP},
+       {NULL,          PAR_NONE}
+};
+
+
+static long    getnum PROTO((const char * cp));
+
+
+void
+do_dd(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       const char *    str;
+       const PARAM *   par;
+       const char *    infile;
+       const char *    outfile;
+       char *          cp;
+       int             infd;
+       int             outfd;
+       int             incc;
+       int             outcc;
+       int             blocksize;
+       long            count;
+       long            seekval;
+       long            skipval;
+       long            intotal;
+       long            outtotal;
+       char *          buf;
+       char            localbuf[BUFSIZE];
+
+       infile = NULL;
+       outfile = NULL;
+       seekval = 0;
+       skipval = 0;
+       blocksize = 512;
+       count = 0x7fffffff;
+
+       while (--argc > 0) {
+               str = *++argv;
+               cp = strchr(str, '=');
+
+               if (cp == NULL) {
+                       fprintf(stderr, "Bad dd argument\n");
+
+                       return;
+               }
+
+               *cp++ = '\0';
+
+               for (par = params; par->name; par++) {
+                       if (strcmp(str, par->name) == 0)
+                               break;
+               }
+
+               switch (par->value) {
+                       case PAR_IF:
+                               if (infile) {
+                                       fprintf(stderr, "Multiple input files illegal\n");
+
+                                       return;
+                               }
+       
+                               infile = cp;
+                               break;
+
+                       case PAR_OF:
+                               if (outfile) {
+                                       fprintf(stderr, "Multiple output files illegal\n");
+
+                                       return;
+                               }
+
+                               outfile = cp;
+                               break;
+
+                       case PAR_BS:
+                               blocksize = getnum(cp);
+
+                               if (blocksize <= 0) {
+                                       fprintf(stderr, "Bad block size value\n");
+
+                                       return;
+                               }
+
+                               break;
+
+                       case PAR_COUNT:
+                               count = getnum(cp);
+
+                               if (count < 0) {
+                                       fprintf(stderr, "Bad count value\n");
+
+                                       return;
+                               }
+
+                               break;
+
+                       case PAR_SEEK:
+                               seekval = getnum(cp);
+
+                               if (seekval < 0) {
+                                       fprintf(stderr, "Bad seek value\n");
+
+                                       return;
+                               }
+
+                               break;
+
+                       case PAR_SKIP:
+                               skipval = getnum(cp);
+
+                               if (skipval < 0) {
+                                       fprintf(stderr, "Bad skip value\n");
+
+                                       return;
+                               }
+
+                               break;
+
+                       default:
+                               fprintf(stderr, "Unknown dd parameter\n");
+
+                               return;
+               }
+       }
+
+       if (infile == NULL) {
+               fprintf(stderr, "No input file specified\n");
+
+               return;
+       }
+
+       if (outfile == NULL) {
+               fprintf(stderr, "No output file specified\n");
+
+               return;
+       }
+
+       buf = localbuf;
+
+       if (blocksize > sizeof(localbuf)) {
+               buf = malloc(blocksize);
+
+               if (buf == NULL) {
+                       fprintf(stderr, "Cannot allocate buffer\n");
+
+                       return;
+               }
+       }
+
+       intotal = 0;
+       outtotal = 0;
+
+       infd = open(infile, 0);
+
+       if (infd < 0) {
+               perror(infile);
+
+               if (buf != localbuf)
+                       free(buf);
+
+               return;
+       }
+
+       outfd = creat(outfile, 0666);
+
+       if (outfd < 0) {
+               perror(outfile);
+               close(infd);
+
+               if (buf != localbuf)
+                       free(buf);
+
+               return;
+       }
+
+       if (skipval) {
+               if (lseek(infd, skipval * blocksize, 0) < 0) {
+                       while (skipval-- > 0) {
+                               incc = read(infd, buf, blocksize);
+
+                               if (incc < 0) {
+                                       perror(infile);
+                                       goto cleanup;
+                               }
+
+                               if (incc == 0) {
+                                       fprintf(stderr, "End of file while skipping\n");
+                                       goto cleanup;
+                               }
+                       }
+               }
+       }
+
+       if (seekval) {
+               if (lseek(outfd, seekval * blocksize, 0) < 0) {
+                       perror(outfile);
+                       goto cleanup;
+               }
+       }
+
+       while ((incc = read(infd, buf, blocksize)) > 0) {
+               intotal += incc;
+               cp = buf;
+
+               if (intflag) {
+                       fprintf(stderr, "Interrupted\n");
+                       goto cleanup;
+               }
+
+               while (incc > 0) {
+                       outcc = write(outfd, cp, incc);
+
+                       if (outcc < 0) {
+                               perror(outfile);
+                               goto cleanup;
+                       }
+
+                       outtotal += outcc;
+                       cp += outcc;
+                       incc -= outcc;
+               }
+       }
+
+       if (incc < 0)
+               perror(infile);
+
+cleanup:
+       close(infd);
+
+       if (close(outfd) < 0)
+               perror(outfile);
+
+       if (buf != localbuf)
+               free(buf);
+
+       printf("%ld+%d records in\n", intotal / blocksize,
+               (intotal % blocksize) != 0);
+
+       printf("%ld+%d records out\n", outtotal / blocksize,
+               (outtotal % blocksize) != 0);
+}
+
+
+/*
+ * Read a number with a possible multiplier.
+ * Returns -1 if the number format is illegal.
+ */
+static long
+getnum(cp)
+       const char *    cp;
+{
+       long    value;
+
+       if (!isdecimal(*cp))
+               return -1;
+
+       value = 0;
+
+       while (isdecimal(*cp))
+               value = value * 10 + *cp++ - '0';
+
+       switch (*cp++) {
+               case 'k':
+                       value *= 1024;
+                       break;
+
+               case 'b':
+                       value *= 512;
+                       break;
+
+               case 'w':
+                       value *= 2;
+                       break;
+
+               case '\0':
+                       return value;
+
+               default:
+                       return -1;
+       }
+
+       if (*cp)
+               return -1;
+
+       return value;
+}
+
+/* END CODE */
diff --git a/cmd_ed.c b/cmd_ed.c
new file mode 100644 (file)
index 0000000..c3e5223
--- /dev/null
+++ b/cmd_ed.c
@@ -0,0 +1,1377 @@
+/*
+ * Copyright (c) 1998 by David I. Bell
+ * Permission is granted to use, distribute, or modify this source,
+ * provided that this copyright notice remains intact.
+ *
+ * The "ed" built-in command (much simplified)
+ */
+
+#include "sash.h"
+
+#define        USERSIZE        1024    /* max line length typed in by user */
+#define        INITBUFSIZE     1024    /* initial buffer size */
+
+
+typedef        int     NUM;
+typedef        int     LEN;
+
+typedef        struct  LINE    LINE;
+struct LINE    {
+       LINE *  next;
+       LINE *  prev;
+       LEN     len;
+       char    data[1];
+};
+
+
+static LINE    lines;
+static LINE *  curline;
+static NUM     curnum;
+static NUM     lastnum;
+static NUM     marks[26];
+static BOOL    dirty;
+static char *  filename;
+static char    searchstring[USERSIZE];
+
+static char *  bufbase;
+static char *  bufptr;
+static LEN     bufused;
+static LEN     bufsize;
+
+
+static void    docommands PROTO((void));
+static void    subcommand PROTO((const char * cmd, NUM num1, NUM num2));
+
+static BOOL    getnum
+       PROTO((const char ** retcp, BOOL * rethavenum, NUM * retnum));
+
+static BOOL    setcurnum PROTO((NUM num));
+static BOOL    initedit PROTO((void));
+static void    termedit PROTO((void));
+static void    addlines PROTO((NUM num));
+static BOOL    insertline PROTO((NUM num, const char * data, LEN len));
+static BOOL    deletelines PROTO((NUM num1, NUM num2));
+static BOOL    printlines PROTO((NUM num1, NUM num2, BOOL expandflag));
+static BOOL    writelines PROTO((const char * file, NUM num1, NUM num2));
+static BOOL    readlines PROTO((const char * file, NUM num));
+static NUM     searchlines PROTO((const char * str, NUM num1, NUM num2));
+
+static LEN     findstring
+       PROTO((const LINE * lp, const char * str, LEN len, LEN offset));
+
+static LINE *  findline PROTO((NUM num));
+
+
+void
+do_ed(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       if (!initedit())
+               return;
+
+       if (argc > 1) {
+               filename = strdup(argv[1]);
+
+               if (filename == NULL) {
+                       fprintf(stderr, "No memory\n");
+                       termedit();
+
+                       return;
+               }
+
+               if (!readlines(filename, 1)) {
+                       termedit();
+
+                       return;
+               }
+
+               if (lastnum)
+                       setcurnum(1);
+
+               dirty = FALSE;
+       }
+
+       docommands();
+
+       termedit();
+}
+
+
+/*
+ * Read commands until we are told to stop.
+ */
+static void
+docommands()
+{
+       const char *    cp;
+       char *          endbuf;
+       char *          newname;
+       int             len;
+       NUM             num1;
+       NUM             num2;
+       BOOL            have1;
+       BOOL            have2;
+       char            buf[USERSIZE];
+
+       while (TRUE) {
+               intflag = FALSE;
+               printf(": ");
+               fflush(stdout);
+
+               if (fgets(buf, sizeof(buf), stdin) == NULL)
+                       return;
+
+               len = strlen(buf);
+
+               if (len == 0)
+                       return;
+
+               endbuf = &buf[len - 1];
+
+               if (*endbuf != '\n') {
+                       fprintf(stderr, "Command line too long\n");
+
+                       do {
+                               len = fgetc(stdin);
+                       } while ((len != EOF) && (len != '\n'));
+
+                       continue;
+               }
+
+               while ((endbuf > buf) && isblank(endbuf[-1]))
+                       endbuf--;
+
+               *endbuf = '\0';
+
+               cp = buf;
+
+               while (isblank(*cp))
+                       cp++;
+
+               have1 = FALSE;
+               have2 = FALSE;
+
+               if ((curnum == 0) && (lastnum > 0)) {
+                       curnum = 1;
+                       curline = lines.next;
+               }
+
+               if (!getnum(&cp, &have1, &num1))
+                       continue;
+
+               while (isblank(*cp))
+                       cp++;
+
+               if (*cp == ',') {
+                       cp++;
+
+                       if (!getnum(&cp, &have2, &num2))
+                               continue;
+
+                       if (!have1)
+                               num1 = 1;
+
+                       if (!have2)
+                               num2 = lastnum;
+
+                       have1 = TRUE;
+                       have2 = TRUE;
+               }
+
+               if (!have1)
+                       num1 = curnum;
+
+               if (!have2)
+                       num2 = num1;
+
+               switch (*cp++) {
+                       case 'a':
+                               addlines(num1 + 1);
+                               break;
+
+                       case 'c':
+                               deletelines(num1, num2);
+                               addlines(num1);
+                               break;
+
+                       case 'd':
+                               deletelines(num1, num2);
+                               break;
+
+                       case 'f':
+                               if (*cp && !isblank(*cp)) {
+                                       fprintf(stderr, "Bad file command\n");
+                                       break;
+                               }
+
+                               while (isblank(*cp))
+                                       cp++;
+
+                               if (*cp == '\0') {
+                                       if (filename)
+                                               printf("\"%s\"\n", filename);
+                                       else
+                                               printf("No filename\n");
+
+                                       break;
+                               }
+
+                               newname = strdup(cp);
+
+                               if (newname == NULL) {
+                                       fprintf(stderr, "No memory for filename\n");
+                                       break;
+                               }
+
+                               if (filename)
+                                       free(filename);
+
+                               filename = newname;
+                               break;
+
+                       case 'i':
+                               addlines(num1);
+                               break;
+
+                       case 'k':
+                               while (isblank(*cp))
+                                       cp++;
+
+                               if ((*cp < 'a') || (*cp > 'a') || cp[1]) {
+                                       fprintf(stderr, "Bad mark name\n");
+                                       break;
+                               }
+
+                               marks[*cp - 'a'] = num2;
+                               break;
+
+                       case 'l':
+                               printlines(num1, num2, TRUE);
+                               break;
+
+                       case 'p':
+                               printlines(num1, num2, FALSE);
+                               break;
+
+                       case 'q':
+                               while (isblank(*cp))
+                                       cp++;
+
+                               if (have1 || *cp) {
+                                       fprintf(stderr, "Bad quit command\n");
+                                       break;
+                               }
+
+                               if (!dirty)
+                                       return;
+
+                               printf("Really quit? ");
+                               fflush(stdout);
+
+                               buf[0] = '\0';
+                               fgets(buf, sizeof(buf), stdin);
+                               cp = buf;
+
+                               while (isblank(*cp))
+                                       cp++;
+
+                               if ((*cp == 'y') || (*cp == 'Y'))
+                                       return;
+
+                               break;
+
+                       case 'r':
+                               if (*cp && !isblank(*cp)) {
+                                       fprintf(stderr, "Bad read command\n");
+                                       break;
+                               }
+
+                               while (isblank(*cp))
+                                       cp++;
+
+                               if (*cp == '\0') {
+                                       fprintf(stderr, "No filename\n");
+                                       break;
+                               }
+
+                               if (!have1)
+                                       num1 = lastnum;
+
+                               if (readlines(cp, num1 + 1))
+                                       break;
+
+                               if (filename == NULL)
+                                       filename = strdup(cp);
+
+                               break;
+
+                       case 's':
+                               subcommand(cp, num1, num2);
+                               break;
+
+                       case 'w':
+                               if (*cp && !isblank(*cp)) {
+                                       fprintf(stderr, "Bad write command\n");
+                                       break;
+                               }
+
+                               while (isblank(*cp))
+                                       cp++;
+
+                               if (!have1) {
+                                       num1 = 1;
+                                       num2 = lastnum;
+                               }
+
+                               if (*cp == '\0')
+                                       cp = filename;
+
+                               if (cp == NULL) {
+                                       fprintf(stderr, "No file name specified\n");
+                                       break;
+                               }
+       
+                               writelines(cp, num1, num2);
+                               break;
+
+                       case 'z':
+                               switch (*cp) {
+                               case '-':
+                                       printlines(curnum-21, curnum, FALSE);
+                                       break;
+                               case '.':
+                                       printlines(curnum-11, curnum+10, FALSE);
+                                       break;
+                               default:
+                                       printlines(curnum, curnum+21, FALSE);
+                                       break;
+                               }
+                               break;
+
+                       case '.':
+                               if (have1) {
+                                       fprintf(stderr, "No arguments allowed\n");
+                                       break;
+                               }
+
+                               printlines(curnum, curnum, FALSE);
+                               break;
+       
+                       case '-':
+                               if (setcurnum(curnum - 1))
+                                       printlines(curnum, curnum, FALSE);
+
+                               break;
+
+                       case '=':
+                               printf("%d\n", num1);
+                               break;
+
+                       case '\0':
+                               if (have1) {
+                                       printlines(num2, num2, FALSE);
+                                       break;
+                               }
+
+                               if (setcurnum(curnum + 1))
+                                       printlines(curnum, curnum, FALSE);
+
+                               break;
+
+                       default:
+                               fprintf(stderr, "Unimplemented command\n");
+                               break;
+               }
+       }
+}
+
+
+/*
+ * Do the substitute command.
+ * The current line is set to the last substitution done.
+ */
+static void
+subcommand(cmd, num1, num2)
+       const char *    cmd;
+       NUM             num1;
+       NUM             num2;
+{
+       int     delim;
+       char *  cp;
+       char *  oldstr;
+       char *  newstr;
+       LEN     oldlen;
+       LEN     newlen;
+       LEN     deltalen;
+       LEN     offset;
+       LINE *  lp;
+       LINE *  nlp;
+       BOOL    globalflag;
+       BOOL    printflag;
+       BOOL    didsub;
+       BOOL    needprint;
+       char    buf[USERSIZE];
+
+       if ((num1 < 1) || (num2 > lastnum) || (num1 > num2)) {
+               fprintf(stderr, "Bad line range for substitute\n");
+
+               return;
+       }
+
+       globalflag = FALSE;
+       printflag = FALSE;
+       didsub = FALSE;
+       needprint = FALSE;
+
+       /*
+        * Copy the command so we can modify it.
+        */
+       strcpy(buf, cmd);
+       cp = buf;
+
+       if (isblank(*cp) || (*cp == '\0')) {
+               fprintf(stderr, "Bad delimiter for substitute\n");
+
+               return;
+       }
+
+       delim = *cp++;
+       oldstr = cp;
+
+       cp = strchr(cp, delim);
+
+       if (cp == NULL) {
+               fprintf(stderr, "Missing 2nd delimiter for substitute\n");
+
+               return;
+       }
+
+       *cp++ = '\0';
+
+       newstr = cp;
+       cp = strchr(cp, delim);
+
+       if (cp)
+               *cp++ = '\0';
+       else
+               cp = "";
+
+       while (*cp) switch (*cp++) {
+               case 'g':
+                       globalflag = TRUE;
+                       break;
+
+               case 'p':
+                       printflag = TRUE;
+                       break;
+
+               default:
+                       fprintf(stderr, "Unknown option for substitute\n");
+
+                       return;
+       }
+
+       if (*oldstr == '\0') {
+               if (searchstring[0] == '\0') {
+                       fprintf(stderr, "No previous search string\n");
+
+                       return;
+               }
+
+               oldstr = searchstring;
+       }
+
+       if (oldstr != searchstring)
+               strcpy(searchstring, oldstr);
+
+       lp = findline(num1);
+
+       if (lp == NULL)
+               return;
+
+       oldlen = strlen(oldstr);
+       newlen = strlen(newstr);
+       deltalen = newlen - oldlen;
+       offset = 0;
+       nlp = NULL;
+
+       while (num1 <= num2) {
+               offset = findstring(lp, oldstr, oldlen, offset);
+
+               if (offset < 0) {
+                       if (needprint) {
+                               printlines(num1, num1, FALSE);
+                               needprint = FALSE;
+                       }
+
+                       offset = 0;
+                       lp = lp->next;
+                       num1++;
+
+                       continue;
+               }
+
+               needprint = printflag;
+               didsub = TRUE;
+               dirty = TRUE;
+
+               /*
+                * If the replacement string is the same size or shorter
+                * than the old string, then the substitution is easy.
+                */
+               if (deltalen <= 0) {
+                       memcpy(&lp->data[offset], newstr, newlen);
+
+                       if (deltalen) {
+                               memcpy(&lp->data[offset + newlen],
+                                       &lp->data[offset + oldlen],
+                                       lp->len - offset - oldlen);
+
+                               lp->len += deltalen;
+                       }
+
+                       offset += newlen;
+
+                       if (globalflag)
+                               continue;
+
+                       if (needprint) {
+                               printlines(num1, num1, FALSE);
+                               needprint = FALSE;
+                       }
+
+                       lp = lp->next;
+                       num1++;
+
+                       continue;
+               }
+
+               /*
+                * The new string is larger, so allocate a new line
+                * structure and use that.  Link it in in place of
+                * the old line structure.
+                */
+               nlp = (LINE *) malloc(sizeof(LINE) + lp->len + deltalen);
+
+               if (nlp == NULL) {
+                       fprintf(stderr, "Cannot get memory for line\n");
+
+                       return;
+               }
+
+               nlp->len = lp->len + deltalen;
+
+               memcpy(nlp->data, lp->data, offset);
+
+               memcpy(&nlp->data[offset], newstr, newlen);
+
+               memcpy(&nlp->data[offset + newlen],
+                       &lp->data[offset + oldlen],
+                       lp->len - offset - oldlen);
+
+               nlp->next = lp->next;
+               nlp->prev = lp->prev;
+               nlp->prev->next = nlp;
+               nlp->next->prev = nlp;
+
+               if (curline == lp)
+                       curline = nlp;
+
+               free(lp);
+               lp = nlp;
+
+               offset += newlen;
+
+               if (globalflag)
+                       continue;
+
+               if (needprint) {
+                       printlines(num1, num1, FALSE);
+                       needprint = FALSE;
+               }
+
+               lp = lp->next;
+               num1++;
+       }
+
+       if (!didsub)
+               fprintf(stderr, "No substitutions found for \"%s\"\n", oldstr);
+}
+
+
+/*
+ * Search a line for the specified string starting at the specified
+ * offset in the line.  Returns the offset of the found string, or -1.
+ */
+static LEN
+findstring(lp, str, len, offset)
+       const LINE *    lp;
+       const char *    str;
+       LEN             len;
+       LEN             offset;
+{
+       LEN             left;
+       const char *    cp;
+       const char *    ncp;
+
+       cp = &lp->data[offset];
+       left = lp->len - offset;
+
+       while (left >= len) {
+               ncp = memchr(cp, *str, left);
+
+               if (ncp == NULL)
+                       return -1;
+
+               left -= (ncp - cp);
+
+               if (left < len)
+                       return -1;
+
+               cp = ncp;
+
+               if (memcmp(cp, str, len) == 0)
+                       return (cp - lp->data);
+
+               cp++;
+               left--;
+       }
+
+       return -1;
+}
+
+
+/*
+ * Add lines which are typed in by the user.
+ * The lines are inserted just before the specified line number.
+ * The lines are terminated by a line containing a single dot (ugly!),
+ * or by an end of file.
+ */
+static void
+addlines(num)
+       NUM     num;
+{
+       int     len;
+       char    buf[USERSIZE + 1];
+
+       while (fgets(buf, sizeof(buf), stdin)) {
+               if ((buf[0] == '.') && (buf[1] == '\n') && (buf[2] == '\0'))
+                       return;
+
+               len = strlen(buf);
+
+               if (len == 0)
+                       return;
+
+               if (buf[len - 1] != '\n') {
+                       fprintf(stderr, "Line too long\n");
+
+                       do {
+                               len = fgetc(stdin);
+                       } while ((len != EOF) && (len != '\n'));
+
+                       return;
+               }
+
+               if (!insertline(num++, buf, len))
+                       return;
+       }
+}
+
+
+/*
+ * Parse a line number argument if it is present.  This is a sum
+ * or difference of numbers, '.', '$', 'x, or a search string.
+ * Returns TRUE if successful (whether or not there was a number). 
+ * Returns FALSE if there was a parsing error, with a message output.
+ * Whether there was a number is returned indirectly, as is the number.
+ * The character pointer which stopped the scan is also returned.
+ */
+static BOOL
+getnum(retcp, rethavenum, retnum)
+       const char **   retcp;
+       BOOL *          rethavenum;
+       NUM *           retnum;
+{
+       const char *    cp;
+       char *          endstr;
+       char            str[USERSIZE];
+       BOOL            havenum;
+       NUM             value;
+       NUM             num;
+       NUM             sign;
+
+       cp = *retcp;
+       havenum = FALSE;
+       value = 0;
+       sign = 1;
+
+       while (TRUE) {
+               while (isblank(*cp))
+                       cp++;
+
+               switch (*cp) {
+                       case '.':
+                               havenum = TRUE;
+                               num = curnum;
+                               cp++;
+                               break;
+
+                       case '$':
+                               havenum = TRUE;
+                               num = lastnum;
+                               cp++;
+                               break;
+
+                       case '\'':
+                               cp++;
+
+                               if ((*cp < 'a') || (*cp > 'z')) {
+                                       fprintf(stderr, "Bad mark name\n");
+
+                                       return FALSE;
+                               }
+
+                               havenum = TRUE;
+                               num = marks[*cp++ - 'a'];
+                               break;
+
+                       case '/':
+                               strcpy(str, ++cp);
+                               endstr = strchr(str, '/');
+
+                               if (endstr) {
+                                       *endstr++ = '\0';
+                                       cp += (endstr - str);
+                               } else
+                                       cp = "";
+
+                               num = searchlines(str, curnum, lastnum);
+
+                               if (num == 0)
+                                       return FALSE;
+
+                               havenum = TRUE;
+                               break;
+
+                       default:
+                               if (!isdecimal(*cp)) {
+                                       *retcp = cp;
+                                       *rethavenum = havenum;
+                                       *retnum = value;
+
+                                       return TRUE;
+                               }
+
+                               num = 0;
+
+                               while (isdecimal(*cp))
+                                       num = num * 10 + *cp++ - '0';
+
+                               havenum = TRUE;
+                               break;
+               }
+
+               value += num * sign;
+
+               while (isblank(*cp))
+                       cp++;
+
+               switch (*cp) {
+                       case '-':
+                               sign = -1;
+                               cp++;
+                               break;
+                       case '+':
+                               sign = 1;
+                               cp++;
+                               break;
+
+                       default:
+                               *retcp = cp;
+                               *rethavenum = havenum;
+                               *retnum = value;
+
+                               return TRUE;
+               }
+       }
+}
+
+
+/*
+ * Initialize everything for editing.
+ */
+static BOOL
+initedit()
+{
+       int     i;
+
+       bufsize = INITBUFSIZE;
+       bufbase = malloc(bufsize);
+
+       if (bufbase == NULL) {
+               fprintf(stderr, "No memory for buffer\n");
+
+               return FALSE;
+       }
+
+       bufptr = bufbase;
+       bufused = 0;
+
+       lines.next = &lines;
+       lines.prev = &lines;
+
+       curline = NULL;
+       curnum = 0;
+       lastnum = 0;
+       dirty = FALSE;
+       filename = NULL;
+       searchstring[0] = '\0';
+
+       for (i = 0; i < 26; i++)
+               marks[i] = 0;
+
+       return TRUE;
+}
+
+
+/*
+ * Finish editing.
+ */
+static void
+termedit()
+{
+       if (bufbase)
+               free(bufbase);
+
+       bufbase = NULL;
+       bufptr = NULL;
+       bufsize = 0;
+       bufused = 0;
+
+       if (filename)
+               free(filename);
+
+       filename = NULL;
+
+       searchstring[0] = '\0';
+
+       if (lastnum)
+               deletelines(1, lastnum);
+
+       lastnum = 0;
+       curnum = 0;
+       curline = NULL;
+}
+
+
+/*
+ * Read lines from a file at the specified line number.
+ * Returns TRUE if the file was successfully read.
+ */
+static BOOL
+readlines(file, num)
+       const char *    file;
+       NUM             num;
+{
+       int     fd;
+       int     cc;
+       LEN     len;
+       LEN     linecount;
+       LEN     charcount;
+       char *  cp;
+
+       if ((num < 1) || (num > lastnum + 1)) {
+               fprintf(stderr, "Bad line for read\n");
+
+               return FALSE;
+       }
+
+       fd = open(file, 0);
+
+       if (fd < 0) {
+               perror(file);
+
+               return FALSE;
+       }
+
+       bufptr = bufbase;
+       bufused = 0;
+       linecount = 0;
+       charcount = 0;
+       cc = 0;
+
+       printf("\"%s\", ", file);
+       fflush(stdout);
+
+       do {
+               if (intflag) {
+                       printf("INTERRUPTED, ");
+                       bufused = 0;
+                       break;
+               }
+
+               cp = memchr(bufptr, '\n', bufused);
+
+               if (cp) {
+                       len = (cp - bufptr) + 1;
+
+                       if (!insertline(num, bufptr, len)) {
+                               close(fd);
+
+                               return FALSE;
+                       }
+
+                       bufptr += len;
+                       bufused -= len;
+                       charcount += len;
+                       linecount++;
+                       num++;
+
+                       continue;
+               }
+
+               if (bufptr != bufbase) {
+                       memcpy(bufbase, bufptr, bufused);
+                       bufptr = bufbase + bufused;
+               }
+
+               if (bufused >= bufsize) {
+                       len = (bufsize * 3) / 2;
+                       cp = realloc(bufbase, len);
+
+                       if (cp == NULL) {
+                               fprintf(stderr, "No memory for buffer\n");
+                               close(fd);
+
+                               return FALSE;
+                       }
+
+                       bufbase = cp;
+                       bufptr = bufbase + bufused;
+                       bufsize = len;
+               }
+
+               cc = read(fd, bufptr, bufsize - bufused);
+               bufused += cc;
+               bufptr = bufbase;
+
+       } while (cc > 0);
+
+       if (cc < 0) {
+               perror(file);
+               close(fd);
+
+               return FALSE;
+       }
+
+       if (bufused) {
+               if (!insertline(num, bufptr, bufused)) {
+                       close(fd);
+
+                       return -1;
+               }
+
+               linecount++;
+               charcount += bufused;
+       }
+
+       close(fd);
+
+       printf("%d lines%s, %d chars\n", linecount,
+               (bufused ? " (incomplete)" : ""), charcount);
+
+       return TRUE;
+}
+
+
+/*
+ * Write the specified lines out to the specified file.
+ * Returns TRUE if successful, or FALSE on an error with a message output.
+ */
+static BOOL
+writelines(file, num1, num2)
+       const char *    file;
+       NUM             num1;
+       NUM             num2;
+{
+       int     fd;
+       LINE *  lp;
+       LEN     linecount;
+       LEN     charcount;
+
+       if ((num1 < 1) || (num2 > lastnum) || (num1 > num2)) {
+               fprintf(stderr, "Bad line range for write\n");
+
+               return FALSE;
+       }
+
+       linecount = 0;
+       charcount = 0;
+
+       fd = creat(file, 0666);
+
+       if (fd < 0) {
+               perror(file);
+
+               return FALSE;
+       }
+
+       printf("\"%s\", ", file);
+       fflush(stdout);
+
+       lp = findline(num1);
+
+       if (lp == NULL) {
+               close(fd);
+
+               return FALSE;
+       }
+
+       while (num1++ <= num2) {
+               if (write(fd, lp->data, lp->len) != lp->len) {
+                       perror(file);
+                       close(fd);
+
+                       return FALSE;
+               }
+
+               charcount += lp->len;
+               linecount++;
+               lp = lp->next;
+       }
+
+       if (close(fd) < 0) {
+               perror(file);
+
+               return FALSE;
+       }
+
+       printf("%d lines, %d chars\n", linecount, charcount);
+
+       return TRUE;
+}
+
+
+/*
+ * Print lines in a specified range.
+ * The last line printed becomes the current line.
+ * If expandflag is TRUE, then the line is printed specially to
+ * show magic characters.
+ */
+static BOOL
+printlines(num1, num2, expandflag)
+       NUM     num1;
+       NUM     num2;
+       BOOL    expandflag;
+{
+       const LINE *            lp;
+       const unsigned char *   cp;
+       int                     ch;
+       LEN                     count;
+
+       if ((num1 < 1) || (num2 > lastnum) || (num1 > num2)) {
+               fprintf(stderr, "Bad line range for print\n");
+
+               return FALSE;
+       }
+
+       lp = findline(num1);
+
+       if (lp == NULL)
+               return FALSE;
+
+       while (!intflag && (num1 <= num2)) {
+               if (!expandflag) {
+                       write(STDOUT, lp->data, lp->len);
+                       setcurnum(num1++);
+                       lp = lp->next;
+
+                       continue;
+               }
+
+               /*
+                * Show control characters and characters with the
+                * high bit set specially.
+                */
+               cp = lp->data;
+               count = lp->len;
+
+               if ((count > 0) && (cp[count - 1] == '\n'))
+                       count--;
+
+               while (count-- > 0) {
+                       ch = *cp++;
+
+                       if (ch & 0x80) {
+                               fputs("M-", stdout);
+                               ch &= 0x7f;
+                       }
+
+                       if (ch < ' ') {
+                               fputc('^', stdout);
+                               ch += '@';
+                       }
+
+                       if (ch == 0x7f) {
+                               fputc('^', stdout);
+                               ch = '?';
+                       }
+
+                       fputc(ch, stdout);
+               }
+
+               fputs("$\n", stdout);
+
+               setcurnum(num1++);
+               lp = lp->next;
+       }
+
+       return TRUE;
+}
+
+
+/*
+ * Insert a new line with the specified text.
+ * The line is inserted so as to become the specified line,
+ * thus pushing any existing and further lines down one.
+ * The inserted line is also set to become the current line.
+ * Returns TRUE if successful.
+ */
+static BOOL
+insertline(num, data, len)
+       NUM             num;
+       const char *    data;
+       LEN             len;
+{
+       LINE *  newlp;
+       LINE *  lp;
+
+       if ((num < 1) || (num > lastnum + 1)) {
+               fprintf(stderr, "Inserting at bad line number\n");
+
+               return FALSE;
+       }
+
+       newlp = (LINE *) malloc(sizeof(LINE) + len - 1);
+
+       if (newlp == NULL)  {
+               fprintf(stderr, "Failed to allocate memory for line\n");
+
+               return FALSE;
+       }
+
+       memcpy(newlp->data, data, len);
+       newlp->len = len;
+
+       if (num > lastnum)
+               lp = &lines;
+       else {
+               lp = findline(num);
+
+               if (lp == NULL) {
+                       free((char *) newlp);
+
+                       return FALSE;
+               }
+       }
+
+       newlp->next = lp;
+       newlp->prev = lp->prev;
+       lp->prev->next = newlp;
+       lp->prev = newlp;
+
+       lastnum++;
+       dirty = TRUE;
+
+       return setcurnum(num);
+}
+
+
+/*
+ * Delete lines from the given range.
+ */
+static BOOL
+deletelines(num1, num2)
+       NUM     num1;
+       NUM     num2;
+{
+       LINE *  lp;
+       LINE *  nlp;
+       LINE *  plp;
+       NUM     count;
+
+       if ((num1 < 1) || (num2 > lastnum) || (num1 > num2)) {
+               fprintf(stderr, "Bad line numbers for delete\n");
+
+               return FALSE;
+       }
+
+       lp = findline(num1);
+
+       if (lp == NULL)
+               return FALSE;
+
+       if ((curnum >= num1) && (curnum <= num2)) {
+               if (num2 < lastnum)
+                       setcurnum(num2 + 1);
+               else if (num1 > 1)
+                       setcurnum(num1 - 1);
+               else
+                       curnum = 0;
+       }
+
+       count = num2 - num1 + 1;
+
+       if (curnum > num2)
+               curnum -= count;
+
+       lastnum -= count;
+
+       while (count-- > 0) {
+               nlp = lp->next;
+               plp = lp->prev;
+               plp->next = nlp;
+               nlp->prev = plp;
+               lp->next = NULL;
+               lp->prev = NULL;
+               lp->len = 0;
+               free(lp);
+               lp = nlp;
+       }
+
+       dirty = TRUE;
+
+       return TRUE;
+}
+
+
+/*
+ * Search for a line which contains the specified string.
+ * If the string is NULL, then the previously searched for string
+ * is used.  The currently searched for string is saved for future use.
+ * Returns the line number which matches, or 0 if there was no match
+ * with an error printed.
+ */
+static NUM
+searchlines(str, num1, num2)
+       const char *    str;
+       NUM             num1;
+       NUM             num2;
+{
+       const LINE *    lp;
+       int             len;
+
+       if ((num1 < 1) || (num2 > lastnum) || (num1 > num2)) {
+               fprintf(stderr, "Bad line numbers for search\n");
+
+               return 0;
+       }
+
+       if (*str == '\0') {
+               if (searchstring[0] == '\0') {
+                       fprintf(stderr, "No previous search string\n");
+
+                       return 0;
+               }
+
+               str = searchstring;
+       }
+
+       if (str != searchstring)
+               strcpy(searchstring, str);
+
+       len = strlen(str);
+
+       lp = findline(num1);
+
+       if (lp == NULL)
+               return 0;
+
+       while (num1 <= num2) {
+               if (findstring(lp, str, len, 0) >= 0)
+                       return num1;
+
+               num1++;
+               lp = lp->next;
+       }
+
+       fprintf(stderr, "Cannot find string \"%s\"\n", str);
+
+       return 0;
+}
+
+
+/*
+ * Return a pointer to the specified line number.
+ */
+static LINE *
+findline(num)
+       NUM     num;
+{
+       LINE *  lp;
+       NUM     lnum;
+
+       if ((num < 1) || (num > lastnum)) {
+               fprintf(stderr, "Line number %d does not exist\n", num);
+
+               return NULL;
+       }
+
+       if (curnum <= 0) {
+               curnum = 1;
+               curline = lines.next;
+       }
+
+       if (num == curnum)
+               return curline;
+
+       lp = curline;
+       lnum = curnum;
+
+       if (num < (curnum / 2)) {
+               lp = lines.next;
+               lnum = 1;
+       }
+       else if (num > ((curnum + lastnum) / 2)) {
+               lp = lines.prev;
+               lnum = lastnum;
+       }
+
+       while (lnum < num) {
+               lp = lp->next;
+               lnum++;
+       }
+
+       while (lnum > num) {
+               lp = lp->prev;
+               lnum--;
+       }
+
+       return lp;
+}
+
+
+/*
+ * Set the current line number.
+ * Returns TRUE if successful.
+ */
+static BOOL
+setcurnum(num)
+       NUM     num;
+{
+       LINE *  lp;
+
+       lp = findline(num);
+
+       if (lp == NULL)
+               return FALSE;
+
+       curnum = num;
+       curline = lp;
+
+       return TRUE;
+}
+
+/* END CODE */
diff --git a/cmd_grep.c b/cmd_grep.c
new file mode 100644 (file)
index 0000000..6f6e955
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 1998 by David I. Bell
+ * Permission is granted to use, distribute, or modify this source,
+ * provided that this copyright notice remains intact.
+ *
+ * The "grep" built-in command.
+ */
+
+#include <ctype.h>
+
+#include "sash.h"
+
+
+static BOOL    search
+       PROTO((const char * string, const char * word, BOOL ignorecase));
+
+
+void
+do_grep(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       FILE *          fp;
+       const char *    word;
+       const char *    name;
+       const char *    cp;
+       BOOL            tellname;
+       BOOL            ignorecase;
+       BOOL            tellline;
+       long            line;
+       char            buf[BUFSIZE];
+
+       ignorecase = FALSE;
+       tellline = FALSE;
+
+       argc--;
+       argv++;
+
+       if (**argv == '-') {
+               argc--;
+               cp = *argv++;
+
+               while (*++cp) switch (*cp) {
+                       case 'i':
+                               ignorecase = TRUE;
+                               break;
+
+                       case 'n':
+                               tellline = TRUE;
+                               break;
+
+                       default:
+                               fprintf(stderr, "Unknown option\n");
+
+                               return;
+               }
+       }
+
+       word = *argv++;
+       argc--;
+
+       tellname = (argc > 1);
+
+       while (argc-- > 0) {
+               name = *argv++;
+
+               fp = fopen(name, "r");
+
+               if (fp == NULL) {
+                       perror(name);
+
+                       continue;
+               }
+
+               line = 0;
+
+               while (fgets(buf, sizeof(buf), fp)) {
+                       if (intflag) {
+                               fclose(fp);
+
+                               return;
+                       }
+
+                       line++;
+
+                       cp = &buf[strlen(buf) - 1];
+
+                       if (*cp != '\n')
+                               fprintf(stderr, "%s: Line too long\n", name);
+
+                       if (search(buf, word, ignorecase)) {
+                               if (tellname)
+                                       printf("%s: ", name);
+
+                               if (tellline)
+                                       printf("%ld: ", line);
+
+                               fputs(buf, stdout);
+                       }
+               }
+
+               if (ferror(fp))
+                       perror(name);
+
+               fclose(fp);
+       }
+}
+
+
+/*
+ * See if the specified word is found in the specified string.
+ */
+static BOOL
+search(string, word, ignorecase)
+       const char *    string;
+       const char *    word;
+       BOOL            ignorecase;
+{
+       const char *    cp1;
+       const char *    cp2;
+       int             len;
+       int             lowfirst;
+       int             ch1;
+       int             ch2;
+
+       len = strlen(word);
+
+       if (!ignorecase) {
+               while (TRUE) {
+                       string = strchr(string, word[0]);
+
+                       if (string == NULL)
+                               return FALSE;
+
+                       if (memcmp(string, word, len) == 0)
+                               return TRUE;
+
+                       string++;
+               }
+       }
+
+       /*
+        * Here if we need to check case independence.
+        * Do the search by lower casing both strings.
+        */
+       lowfirst = *word;
+
+       if (isupper(lowfirst))
+               lowfirst = tolower(lowfirst);
+
+       while (TRUE) {
+               while (*string && (*string != lowfirst) &&
+                       (!isupper(*string) || (tolower(*string) != lowfirst)))
+               {
+                       string++;
+               }
+
+               if (*string == '\0')
+                       return FALSE;
+
+               cp1 = string;
+               cp2 = word;
+
+               do {
+                       if (*cp2 == '\0')
+                               return TRUE;
+
+                       ch1 = *cp1++;
+
+                       if (isupper(ch1))
+                               ch1 = tolower(ch1);
+
+                       ch2 = *cp2++;
+
+                       if (isupper(ch2))
+                               ch2 = tolower(ch2);
+
+               } while (ch1 == ch2);
+
+               string++;
+       }
+}
+
+/* END CODE */
diff --git a/cmd_gzip.c b/cmd_gzip.c
new file mode 100644 (file)
index 0000000..f123fd7
--- /dev/null
@@ -0,0 +1,648 @@
+/*
+ * Copyright (c) 1998 by David I. Bell
+ * Permission is granted to use, distribute, or modify this source,
+ * provided that this copyright notice remains intact.
+ *
+ * The "gzip" and "gunzip" built-in commands.
+ * These commands are optionally built into sash.
+ * This uses the zlib library by Jean-loup Gailly to compress and
+ * uncompress the files.
+ */
+
+#ifdef HAVE_GZIP
+
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <zlib.h>
+
+#include "sash.h"
+
+
+#define        GZ_EXT          ".gz"
+#define        TGZ_EXT         ".tgz"
+#define        Z_EXT           ".Z"
+#define        TAR_EXT         ".tar"
+#define        NO_EXT          ""
+
+
+/*
+ * Tables of conversions to make to file extensions.
+ */
+typedef        struct
+{
+       const char *    input;
+       const char *    output;
+} CONVERT;
+
+
+static const CONVERT   gzipConvertTable[] =
+{
+       {TAR_EXT,       TGZ_EXT},
+       {NO_EXT,        GZ_EXT}
+};
+
+
+static const CONVERT   gunzipConvertTable[] =
+{
+       {TGZ_EXT,       TAR_EXT},
+       {GZ_EXT,        NO_EXT},
+       {Z_EXT,         NO_EXT},
+       {NO_EXT,        NO_EXT}
+};
+
+
+/*
+ * Local routines to compress and uncompress files.
+ */
+static BOOL    gzip PROTO((const char * inputFile, const char * outputFile));
+static BOOL    gunzip PROTO((const char * inputFile, const char * outputFile));
+
+static const char * convertName
+       PROTO((const CONVERT * table, const char * inFile));
+
+
+void
+do_gzip(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       const char *    outPath;
+       const char *    inFile;
+       const char *    outFile;
+       int             i;
+
+       argc--;
+       argv++;
+
+       /*
+        * Look for the -o option if it is present.
+        * If present, it must be at the end of the command.
+        * Remember the output path and remove it if found.
+        */
+       outPath = NULL;
+
+       if ((argc >= 2) && (strcmp(argv[argc - 2], "-o") == 0) &&
+               (argv[argc - 1][0] != '-'))
+       {
+               argc -= 2;
+               outPath = argv[argc + 1];
+       }
+
+       /*
+        * Now make sure that there are no more options.
+        */
+       for (i = 0; i < argc; i++)
+       {
+               if (argv[i][0] == '-')
+               {
+                       if (strcmp(argv[i], "-o") == 0)
+                               fprintf(stderr, "Illegal use of -o\n");
+                       else
+                               fprintf(stderr, "Illegal option\n");
+
+                       return;
+               }
+       }
+       
+       /*
+        * If there is no output path specified, then compress each of
+        * the input files in place using their full paths.  The input
+        * file names are then deleted.
+        */
+       if (outPath == NULL)
+       {
+               while (!intflag && (argc-- > 0))
+               {
+                       inFile = *argv++;
+
+                       outFile = convertName(gzipConvertTable, inFile);
+
+                       /*
+                        * Try to compress the file.
+                        */
+                       if (!gzip(inFile, outFile))
+                               continue;
+
+                       /*
+                        * This was successful.
+                        * Try to delete the original file now.
+                        */
+                       if (unlink(inFile) < 0)
+                       {
+                               fprintf(stderr, "%s: %s\n", inFile,
+                                       "Compressed ok but unlink failed");
+                       }
+               }
+
+               return;
+       }
+
+       /*
+        * There is an output path specified.
+        * If it is not a directory, then either compress the single
+        * specified input file to the exactly specified output path,
+        * or else complain.
+        */
+       if (!isadir(outPath))
+       {
+               if (argc == 1)
+                       (void) gzip(*argv, outPath);
+               else
+                       fprintf(stderr, "Exactly one input file is required\n");
+
+               return;
+       }
+
+       /*
+        * There was an output directory specified.
+        * Compress each of the input files into the specified
+        * output directory, converting their extensions if possible.
+        */
+       while (!intflag && (argc-- > 0))
+       {
+               inFile = *argv++;
+
+               /*
+                * Strip the path off of the input file name to make
+                * the beginnings of the output file name.
+                */
+               outFile = strrchr(inFile, '/');
+
+               if (outFile)
+                       outFile++;
+               else
+                       outFile = inFile;
+
+               /*
+                * Convert the extension of the output file name if possible.
+                * If we can't, then that is ok.
+                */
+               outFile = convertName(gzipConvertTable, outFile);
+
+               /*
+                * Now build the output path name by prefixing it with
+                * the output directory.
+                */
+               outFile = buildname(outPath, outFile);
+
+               /*
+                * Compress the input file without deleting the input file.
+                */
+               (void) gzip(inFile, outFile);
+       }
+}
+
+
+void
+do_gunzip(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       const char *    outPath;
+       const char *    inFile;
+       const char *    outFile;
+       int             i;
+
+       argc--;
+       argv++;
+
+       /*
+        * Look for the -o option if it is present.
+        * If present, it must be at the end of the command.
+        * Remember the output path and remove it if found.
+        */
+       outPath = NULL;
+
+       if ((argc >= 2) && (strcmp(argv[argc - 2], "-o") == 0) &&
+               (argv[argc - 1][0] != '-'))
+       {
+               argc -= 2;
+               outPath = argv[argc + 1];
+       }
+
+       /*
+        * Now make sure that there are no more options.
+        */
+       for (i = 0; i < argc; i++)
+       {
+               if (argv[i][0] == '-')
+               {
+                       if (strcmp(argv[i], "-o") == 0)
+                               fprintf(stderr, "Illegal use of -o\n");
+                       else
+                               fprintf(stderr, "Illegal option\n");
+
+                       return;
+               }
+       }
+       
+       /*
+        * If there is no output path specified, then uncompress each of
+        * the input files in place using their full paths.  They must
+        * have one of the proper compression extensions which is converted.
+        * The input file names are then deleted.
+        */
+       if (outPath == NULL)
+       {
+               while (!intflag && (argc-- > 0))
+               {
+                       inFile = *argv++;
+
+                       outFile = convertName(gunzipConvertTable, inFile);
+
+                       if (inFile == outFile)
+                       {
+                               fprintf(stderr, "%s: %s\n", inFile,
+                                       "missing compression extension");
+
+                               continue;
+                       }
+
+                       /*
+                        * Try to uncompress the file.
+                        */
+                       if (!gunzip(inFile, outFile))
+                               continue;
+
+                       /*
+                        * This was successful.
+                        * Try to delete the original file now.
+                        */
+                       if (unlink(inFile) < 0)
+                       {
+                               fprintf(stderr, "%s: %s\n", inFile,
+                                       "Uncompressed ok but unlink failed");
+                       }
+               }
+
+               return;
+       }
+
+       /*
+        * There is an output path specified.
+        * If it is not a directory, then either uncompress the single
+        * specified input file to the exactly specified output path,
+        * or else complain.
+        */
+       if (!isadir(outPath))
+       {
+               if (argc == 1)
+                       (void) gunzip(*argv, outPath);
+               else
+                       fprintf(stderr, "Exactly one input file is required\n");
+
+               return;
+       }
+
+       /*
+        * There was an output directory specified.
+        * Uncompress each of the input files into the specified
+        * output directory, converting their extensions if possible.
+        */
+       while (!intflag && (argc-- > 0))
+       {
+               inFile = *argv++;
+
+               /*
+                * Strip the path off of the input file name to make
+                * the beginnings of the output file name.
+                */
+               outFile = strrchr(inFile, '/');
+
+               if (outFile)
+                       outFile++;
+               else
+                       outFile = inFile;
+
+               /*
+                * Convert the extension of the output file name if possible.
+                * If we can't, then that is ok.
+                */
+               outFile = convertName(gunzipConvertTable, outFile);
+
+               /*
+                * Now build the output path name by prefixing it with
+                * the output directory.
+                */
+               outFile = buildname(outPath, outFile);
+
+               /*
+                * Uncompress the input file without deleting the input file.
+                */
+               (void) gunzip(inFile, outFile);
+       }
+}
+
+
+/*
+ * Compress the specified input file to produce the output file.
+ * Returns TRUE if successful.
+ */
+static BOOL
+gzip(inputFileName, outputFileName)
+       const char *    inputFileName;
+       const char *    outputFileName;
+{
+       gzFile          outGZ;
+       int             inFD;
+       int             len;
+       int             err;
+       struct  stat    statbuf1;
+       struct  stat    statbuf2;
+       char            buf[BUFSIZE];
+
+       outGZ = NULL;
+       inFD = -1;
+
+       /*
+        * See if the output file is the same as the input file.
+        * If so, complain about it.
+        */
+       if (stat(inputFileName, &statbuf1) < 0) {
+               perror(inputFileName);
+
+               return FALSE;
+       }
+
+       if (stat(outputFileName, &statbuf2) < 0) {
+               statbuf2.st_ino = -1;
+               statbuf2.st_dev = -1;
+       }
+
+       if ((statbuf1.st_dev == statbuf2.st_dev) &&
+               (statbuf1.st_ino == statbuf2.st_ino))
+       {
+               fprintf(stderr,
+                       "Cannot compress file \"%s\" on top of itself\n",
+                       inputFileName);
+
+               return FALSE;
+       }
+
+       /*
+        * Open the input file.
+        */
+       inFD = open(inputFileName, O_RDONLY);
+
+       if (inFD < 0) {
+               perror(inputFileName);
+
+               goto failed;
+       }
+
+       /*
+        * Ask the zlib library to open the output file.
+        */
+       outGZ = gzopen(outputFileName, "wb9");
+
+       if (outGZ == NULL) {
+               fprintf(stderr, "%s: gzopen failed\n", outputFileName);
+
+               goto failed;
+       }
+
+       /*
+        * Read the uncompressed data from the input file and write
+        * the compressed data to the output file.
+        */
+       while ((len = read(inFD, buf, sizeof(buf))) > 0) {
+               if (gzwrite(outGZ, buf, len) != len) {
+                       fprintf(stderr, "%s: %s\n", inputFileName,
+                               gzerror(outGZ, &err));
+
+                       goto failed;
+               }
+
+               if (intflag)
+                       goto failed;
+       }
+
+       if (len < 0) {
+               perror(inputFileName);
+
+               goto failed;
+       }
+
+       /*
+        * All done, close the files.
+        */
+       if (close(inFD)) {
+               perror(inputFileName);
+
+               goto failed;
+       }
+
+       inFD = -1;
+
+       if (gzclose(outGZ) != Z_OK) {
+               fprintf(stderr, "%s: gzclose failed\n", outputFileName);
+
+               goto failed;
+       }
+
+       outGZ = NULL;
+
+       /*
+        * Success.
+        */
+       return TRUE;
+
+
+/*
+ * Here on an error, to clean up.
+ */
+failed:
+       if (inFD >= 0)
+               (void) close(inFD);
+
+       if (outGZ != NULL)
+               (void) gzclose(outGZ);
+
+       return FALSE;
+}
+
+
+/*
+ * Uncompress the input file to produce the output file.
+ * Returns TRUE if successful.
+ */
+static BOOL
+gunzip(inputFileName, outputFileName)
+       const char *    inputFileName;
+       const char *    outputFileName;
+{
+       gzFile          inGZ;
+       int             outFD;
+       int             len;
+       int             err;
+       struct  stat    statbuf1;
+       struct  stat    statbuf2;
+       char            buf[BUFSIZE];
+
+       inGZ = NULL;
+       outFD = -1;
+
+       /*
+        * See if the output file is the same as the input file.
+        * If so, complain about it.
+        */
+       if (stat(inputFileName, &statbuf1) < 0) {
+               perror(inputFileName);
+
+               return FALSE;
+       }
+
+       if (stat(outputFileName, &statbuf2) < 0) {
+               statbuf2.st_ino = -1;
+               statbuf2.st_dev = -1;
+       }
+
+       if ((statbuf1.st_dev == statbuf2.st_dev) &&
+               (statbuf1.st_ino == statbuf2.st_ino))
+       {
+               fprintf(stderr,
+                       "Cannot uncompress file \"%s\" on top of itself\n",
+                       inputFileName);
+
+               return FALSE;
+       }
+
+       /*
+        * Ask the zlib library to open the input file.
+        */
+       inGZ = gzopen(inputFileName, "rb");
+
+       if (inGZ == NULL) {
+               fprintf(stderr, "%s: gzopen failed\n", inputFileName);
+
+               return FALSE;
+       }
+
+       /*
+        * Create the output file.
+        */
+       outFD = open(outputFileName, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+
+       if (outFD < 0) {
+               perror(outputFileName);
+
+               goto failed;
+       }
+
+       /*
+        * Read the compressed data from the input file and write
+        * the uncompressed data extracted from it to the output file.
+        */
+       while ((len = gzread(inGZ, buf, sizeof(buf))) > 0) {
+               if (fullWrite(outFD, buf, len) < 0) {
+                       perror(outputFileName);
+
+                       goto failed;
+               }
+
+               if (intflag)
+                       goto failed;
+       }
+
+       if (len < 0) {
+               fprintf(stderr, "%s: %s\n", inputFileName,
+                       gzerror(inGZ, &err));
+
+               goto failed;
+       }
+
+       /*
+        * All done, close the files.
+        */
+       if (close(outFD)) {
+               perror(outputFileName);
+
+               goto failed;
+       }
+
+       outFD = -1;
+
+       if (gzclose(inGZ) != Z_OK) {
+               fprintf(stderr, "%s: gzclose failed\n", inputFileName);
+
+               goto failed;
+       }
+
+       inGZ = NULL;
+
+       /*
+        * Success.
+        */
+       return TRUE;
+
+
+/*
+ * Here on an error, to clean up.
+ */
+failed:
+       if (outFD >= 0)
+               (void) close(outFD);
+
+       if (inGZ != NULL)
+               (void) gzclose(inGZ);
+
+       return FALSE;
+}
+
+
+/*
+ * Convert an input file name to an output file name according to
+ * the specified convertion table.  The returned name points into a
+ * static buffer which is overwritten on each call.  The table must
+ * end in an entry which always specifies a successful conversion.
+ * If no conversion is done the original input file name pointer is
+ * returned.
+ */
+const char *
+convertName(table, inputFile)
+       const CONVERT * table;
+       const char *    inputFile;
+{
+       int             inputLength;
+       int             testLength;
+       static char     buf[PATHLEN];
+
+       inputLength = strlen(inputFile);
+
+       for (;;)
+       {
+               testLength = strlen(table->input);
+
+               if ((inputLength >= testLength) &&
+                       (memcmp(table->input,
+                               inputFile + inputLength - testLength,
+                               testLength) == 0))
+               {
+                       break;
+               }
+
+               table++;
+       }
+
+       /*
+        * If no conversion was done, return the original pointer.
+        */
+       if ((testLength == 0) && (table->output[0] == '\0'))
+               return inputFile;
+
+       /*
+        * Build the new name and return it.
+        */
+       memcpy(buf, inputFile, inputLength - testLength);
+
+       memcpy(buf + inputLength - testLength, table->output,
+               strlen(table->output) + 1);
+
+       return buf;
+}
+
+
+#endif
+
+/* END CODE */
diff --git a/cmd_ls.c b/cmd_ls.c
new file mode 100644 (file)
index 0000000..348b295
--- /dev/null
+++ b/cmd_ls.c
@@ -0,0 +1,302 @@
+/*
+ * Copyright (c) 1998 by David I. Bell
+ * Permission is granted to use, distribute, or modify this source,
+ * provided that this copyright notice remains intact.
+ *
+ * The "ls" built-in command.
+ */
+
+#include "sash.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <pwd.h>
+#include <grp.h>
+
+
+#define        LISTSIZE        256
+
+
+#ifdef S_ISLNK
+#define        LSTAT   lstat
+#else
+#define        LSTAT   stat
+#endif
+
+
+/*
+ * Flags for the LS command.
+ */
+#define        LSF_LONG        0x01
+#define        LSF_DIR         0x02
+#define        LSF_INODE       0x04
+#define        LSF_MULT        0x08
+
+
+static char ** list;
+static int     listsize;
+static int     listused;
+
+
+static void    lsfile
+       PROTO((const char * name, const struct stat * statbuf, int flags));
+
+
+void
+do_ls(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       const char *    cp;
+       const char *    name;
+       int             flags;
+       int             i;
+       DIR *           dirp;
+       BOOL            endslash;
+       char **         newlist;
+       struct dirent * dp;
+       char            fullname[PATHLEN];
+       struct  stat    statbuf;
+
+       static const char *     def[2] = {"-ls", "."};
+
+       if (listsize == 0) {
+               list = (char **) malloc(LISTSIZE * sizeof(char *));
+
+               if (list == NULL) {
+                       fprintf(stderr, "No memory for ls buffer\n");
+
+                       return;
+               }
+
+               listsize = LISTSIZE;
+       }
+
+       listused = 0;
+
+       flags = 0;
+
+       if ((argc > 1) && (argv[1][0] == '-'))
+       {
+               argc--;
+               cp = *(++argv) + 1;
+
+               while (*cp) switch (*cp++) {
+                       case 'l':       flags |= LSF_LONG; break;
+                       case 'd':       flags |= LSF_DIR; break;
+                       case 'i':       flags |= LSF_INODE; break;
+                       default:
+                               fprintf(stderr, "Unknown option -%c\n", cp[-1]);
+
+                               return;
+               }
+       }
+
+       if (argc <= 1) {
+               argc = 2;
+               argv = def;
+       }
+
+       if (argc > 2)
+               flags |= LSF_MULT;
+
+       while (argc-- > 1) {
+               name = *(++argv);
+               endslash = (*name && (name[strlen(name) - 1] == '/'));
+
+               if (LSTAT(name, &statbuf) < 0) {
+                       perror(name);
+
+                       continue;
+               }
+
+               if ((flags & LSF_DIR) || (!S_ISDIR(statbuf.st_mode))) {
+                       lsfile(name, &statbuf, flags);
+
+                       continue;
+               }
+
+               /*
+                * Do all the files in a directory.
+                */
+               dirp = opendir(name);
+
+               if (dirp == NULL) {
+                       perror(name);
+
+                       continue;
+               }
+
+               if (flags & LSF_MULT)
+                       printf("\n%s:\n", name);
+
+               while ((dp = readdir(dirp)) != NULL) {
+                       if (intflag)
+                               break;
+
+                       fullname[0] = '\0';
+
+                       if ((*name != '.') || (name[1] != '\0')) {
+                               strcpy(fullname, name);
+                               if (!endslash)
+                                       strcat(fullname, "/");
+                       }
+
+                       strcat(fullname, dp->d_name);
+
+                       if (listused >= listsize) {
+                               newlist = realloc(list,
+                                       ((sizeof(char **)) * (listsize + LISTSIZE)));
+
+                               if (newlist == NULL) {
+                                       fprintf(stderr, "No memory for ls buffer\n");
+                                       break;
+                               }
+
+                               list = newlist;
+                               listsize += LISTSIZE;
+                       }
+
+                       list[listused] = strdup(fullname);
+
+                       if (list[listused] == NULL) {
+                               fprintf(stderr, "No memory for filenames\n");
+                               break;
+                       }
+
+                       listused++;
+               }
+
+               closedir(dirp);
+
+               /*
+                * Sort the files.
+                */
+               qsort((void *) list, listused, sizeof(char *), namesort);
+
+               /*
+                * Now finally list the filenames.
+                */
+               for (i = 0; i < listused; i++) {
+                       name = list[i];
+
+                       if (LSTAT(name, &statbuf) < 0) {
+                               perror(name);
+                               free((char *) name);
+
+                               continue;
+                       }
+
+                       cp = strrchr(name, '/');
+
+                       if (cp)
+                               cp++;
+                       else
+                               cp = name;
+
+                       lsfile(cp, &statbuf, flags);
+
+                       free((char *) name);
+               }
+
+               listused = 0;
+       }
+}
+
+
+/*
+ * Do an LS of a particular file name according to the flags.
+ */
+static void
+lsfile(name, statbuf, flags)
+       const char *            name;
+       const struct stat *     statbuf;
+       int                     flags;
+{
+       char *          cp;
+       struct passwd * pwd;
+       struct group *  grp;
+       int             len;
+       char            buf[PATHLEN];
+       static  char    username[12];
+       static  int     userid;
+       static  BOOL    useridknown;
+       static  char    groupname[12];
+       static  int     groupid;
+       static  BOOL    groupidknown;
+
+       cp = buf;
+       *cp = '\0';
+
+       if (flags & LSF_INODE) {
+               sprintf(cp, "%5ld ", statbuf->st_ino);
+               cp += strlen(cp);
+       }
+
+       if (flags & LSF_LONG) {
+               strcpy(cp, modestring(statbuf->st_mode));
+               cp += strlen(cp);
+
+               sprintf(cp, "%3d ", statbuf->st_nlink);
+               cp += strlen(cp);
+
+               if (!useridknown || (statbuf->st_uid != userid)) {
+                       pwd = getpwuid(statbuf->st_uid);
+
+                       if (pwd)
+                               strcpy(username, pwd->pw_name);
+                       else
+                               sprintf(username, "%d", statbuf->st_uid);
+
+                       userid = statbuf->st_uid;
+                       useridknown = TRUE;
+               }
+
+               sprintf(cp, "%-8s ", username);
+               cp += strlen(cp);
+
+               if (!groupidknown || (statbuf->st_gid != groupid)) {
+                       grp = getgrgid(statbuf->st_gid);
+
+                       if (grp)
+                               strcpy(groupname, grp->gr_name);
+                       else
+                               sprintf(groupname, "%d", statbuf->st_gid);
+
+                       groupid = statbuf->st_gid;
+                       groupidknown = TRUE;
+               }
+
+               sprintf(cp, "%-8s ", groupname);
+               cp += strlen(cp);
+
+               if (S_ISBLK(statbuf->st_mode) || S_ISCHR(statbuf->st_mode))
+                       sprintf(cp, "%3d, %3d ", statbuf->st_rdev >> 8,
+                               statbuf->st_rdev & 0xff);
+               else
+                       sprintf(cp, "%8ld ", statbuf->st_size);
+
+               cp += strlen(cp);
+
+               sprintf(cp, " %-12s ", timestring(statbuf->st_mtime));
+       }
+
+       fputs(buf, stdout);
+       fputs(name, stdout);
+
+#ifdef S_ISLNK
+       if ((flags & LSF_LONG) && S_ISLNK(statbuf->st_mode)) {
+               len = readlink(name, buf, PATHLEN - 1);
+
+               if (len >= 0) {
+                       buf[len] = '\0';
+                       printf(" -> %s", buf);
+               }
+       }
+#endif
+
+       fputc('\n', stdout);
+}
+
+/* END CODE */
diff --git a/cmd_tar.c b/cmd_tar.c
new file mode 100644 (file)
index 0000000..69f50ac
--- /dev/null
+++ b/cmd_tar.c
@@ -0,0 +1,453 @@
+/*
+ * Copyright (c) 1998 by David I. Bell
+ * Permission is granted to use, distribute, or modify this source,
+ * provided that this copyright notice remains intact.
+ *
+ * The "tar" built-in command.
+ */
+
+#include "sash.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+
+/*
+ * Tar file format.
+ */
+#define TBLOCK 512
+#define NAMSIZ 100
+
+union hblock {
+       char dummy[TBLOCK];
+       struct header {
+               char name[NAMSIZ];
+               char mode[8];
+               char uid[8];
+               char gid[8];
+               char size[12];
+               char mtime[12];
+               char chksum[8];
+               char linkflag;
+               char linkname[NAMSIZ];
+               char extno[4];
+               char extotal[4];
+               char efsize[12];
+       } dbuf;
+} dblock;
+
+
+static BOOL    inheader;
+static BOOL    badheader;
+static BOOL    badwrite;
+static BOOL    extracting;
+static BOOL    warnedroot;
+static BOOL    eof;
+static BOOL    verbose;
+static long    datacc;
+static int     outfd;
+static char    outname[NAMSIZ];
+
+
+static void    doheader PROTO((const struct header * hp));
+static void    dodata PROTO((const char * cp, int count));
+static void    createpath PROTO((const char * name, int mode));
+static long    getoctal PROTO((const char * cp, int len));
+
+
+void
+do_tar(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       const char *    str;
+       const char *    devname;
+       const char *    cp;
+       int             devfd;
+       int             cc;
+       long            incc;
+       int             blocksize;
+       BOOL            createflag;
+       BOOL            listflag;
+       BOOL            fileflag;
+       char            buf[BUFSIZE];
+
+       if (argc < 2) {
+               fprintf(stderr, "Too few arguments for tar\n");
+
+               return;
+       }
+
+       createflag = FALSE;
+       extracting = FALSE;
+       listflag = FALSE;
+       fileflag = FALSE;
+       verbose = FALSE;
+       badwrite = FALSE;
+       badheader = FALSE;
+       warnedroot = FALSE;
+       eof = FALSE;
+       inheader = TRUE;
+       incc = 0;
+       datacc = 0;
+       outfd = -1;
+       blocksize = sizeof(buf);
+
+       for (str = argv[1]; *str; str++) {
+               switch (*str) {
+                       case 'f':       fileflag = TRUE; break;
+                       case 't':       listflag = TRUE; break;
+                       case 'x':       extracting = TRUE; break;
+                       case 'v':       verbose = TRUE; break;
+
+                       case 'c':
+                       case 'a':
+                               fprintf(stderr, "Writing is not supported\n");
+
+                               return;
+
+                       default:
+                               fprintf(stderr, "Unknown tar flag\n");
+
+                               return;
+               }
+       }
+
+       if (!fileflag) {
+               fprintf(stderr, "The 'f' flag must be specified\n");
+
+               return;
+       }
+
+       if (argc < 3) {
+               fprintf(stderr, "Missing input name\n");
+
+               return;
+       }
+
+       devname = argv[2];
+
+       if (extracting + listflag != 1) {
+               fprintf(stderr, "Exactly one of 'x' or 't' must be specified\n");
+
+               return;
+       }
+
+       devfd = open(devname, 0);
+
+       if (devfd < 0) {
+               perror(devname);
+
+               return;
+       }
+
+       cp = buf;
+
+       while (TRUE) {
+               if ((incc == 0) && !eof) {
+                       while (incc < blocksize) {
+                               cc = read(devfd, &buf[incc], blocksize - incc);
+
+                               if (cc < 0) {
+                                       perror(devname);
+                                       goto done;
+                               }
+
+                               if (cc == 0)
+                                       break;
+
+                               incc += cc;
+                       }
+
+                       cp = buf;
+               }
+
+               if (intflag) {
+                       if (extracting && (outfd >= 0))
+                               close(outfd);
+
+                       close(devfd);
+
+                       return;
+               }
+
+               if (inheader) {
+                       if ((incc == 0) || eof)
+                               goto done;
+
+                       if (incc < TBLOCK) {
+                               fprintf(stderr, "Short block for header\n");
+                               goto done;
+                       }
+
+                       doheader((struct header *) cp);
+
+                       cp += TBLOCK;
+                       incc -= TBLOCK;
+
+                       continue;
+               }
+
+               cc = incc;
+
+               if (cc > datacc)
+                       cc = datacc;
+
+               dodata(cp, cc);
+
+               if (cc % TBLOCK)
+                       cc += TBLOCK - (cc % TBLOCK);
+
+               cp += cc;
+               incc -= cc;
+       }
+
+done:
+       close(devfd);
+}
+
+
+static void
+doheader(hp)
+       const struct header *   hp;
+{
+       int             mode;
+       int             uid;
+       int             gid;
+       int             chksum;
+       long            size;
+       time_t          mtime;
+       const char *    name;
+       int             cc;
+       BOOL            hardlink;
+       BOOL            softlink;
+
+       /*
+        * If the block is completely empty, then this is the end of the
+        * archive file.  If the name is null, then just skip this header.
+        */
+       name = hp->name;
+
+       if (*name == '\0') {
+               for (cc = TBLOCK; cc > 0; cc--) {
+                       if (*name++)
+                               return;
+               }
+
+               eof = TRUE;
+
+               return;
+       }
+
+       mode = getoctal(hp->mode, sizeof(hp->mode));
+       uid = getoctal(hp->uid, sizeof(hp->uid));
+       gid = getoctal(hp->gid, sizeof(hp->gid));
+       size = getoctal(hp->size, sizeof(hp->size));
+       mtime = getoctal(hp->mtime, sizeof(hp->mtime));
+       chksum = getoctal(hp->chksum, sizeof(hp->chksum));
+
+       if ((mode < 0) || (uid < 0) || (gid < 0) || (size < 0)) {
+               if (!badheader)
+                       fprintf(stderr, "Bad tar header, skipping\n");
+               badheader = TRUE;
+
+               return;
+       }
+
+       badheader = FALSE;
+       badwrite = FALSE;
+
+       hardlink = ((hp->linkflag == 1) || (hp->linkflag == '1'));
+       softlink = ((hp->linkflag == 2) || (hp->linkflag == '2'));
+
+       if (name[strlen(name) - 1] == '/')
+               mode |= S_IFDIR;
+       else if ((mode & S_IFMT) == 0)
+               mode |= S_IFREG;
+
+       if (*name == '/') {
+               while (*name == '/')
+                       name++;
+
+               if (!warnedroot)
+                       fprintf(stderr, "Absolute paths detected, removing leading slashes\n");
+
+               warnedroot = TRUE;
+       }
+
+       if (!extracting) {
+               if (verbose)
+                       printf("%s %3d/%-d %9ld %s %s", modestring(mode),
+                               uid, gid, size, timestring(mtime), name);
+               else
+                       printf("%s", name);
+
+               if (hardlink)
+                       printf(" (link to \"%s\")", hp->linkname);
+               else if (softlink)
+                       printf(" (symlink to \"%s\")", hp->linkname);
+               else if (S_ISREG(mode)) {
+                       inheader = (size == 0);
+                       datacc = size;
+               }
+
+               printf("\n");
+
+               return;
+       }
+
+       if (verbose)
+               printf("x %s\n", name);
+
+
+       if (hardlink) {
+               if (link(hp->linkname, name) < 0)
+                       perror(name);
+
+               return;
+       }
+
+       if (softlink) {
+#ifdef S_ISLNK
+               if (symlink(hp->linkname, name) < 0)
+                       perror(name);
+#else
+               fprintf(stderr, "Cannot create symbolic links\n");
+#endif
+               return;
+       }
+
+       if (S_ISDIR(mode)) {
+               createpath(name, mode);
+
+               return;
+       }
+
+       createpath(name, 0777);
+
+       inheader = (size == 0);
+       datacc = size;
+
+       outfd = creat(name, mode);
+
+       if (outfd < 0) {
+               perror(name);
+               badwrite = TRUE;
+
+               return;
+       }
+
+       if (size == 0) {
+               close(outfd);
+               outfd = -1;
+       }
+}
+
+
+
+/*
+ * Handle a data block of some specified size.
+ */
+static void
+dodata(cp, count)
+       const char *    cp;
+       int             count;
+{
+       datacc -= count;
+
+       if (datacc <= 0)
+               inheader = TRUE;
+
+       if (badwrite || !extracting)
+               return;
+
+       if (fullWrite(outfd, cp, count) < 0) {
+               perror(outname);
+               close(outfd);
+               outfd = -1;
+               badwrite = TRUE;
+
+               return;
+       }
+
+       if (datacc <= 0) {
+               if (close(outfd))
+                       perror(outname);
+
+               outfd = -1;
+       }
+}
+
+
+/*
+ * Attempt to create the directories along the specified path, except for
+ * the final component.  The mode is given for the final directory only,
+ * while all previous ones get default protections.  Errors are not reported
+ * here, as failures to restore files can be reported later.
+ */
+static void
+createpath(name, mode)
+       const char *    name;
+       int             mode;
+{
+       char *  cp;
+       char *  cpold;
+       char    buf[NAMSIZ];
+
+       strcpy(buf, name);
+
+       cp = strchr(buf, '/');
+
+       while (cp) {
+               cpold = cp;
+               cp = strchr(cp + 1, '/');
+
+               *cpold = '\0';
+
+               if (mkdir(buf, cp ? 0777 : mode) == 0)
+                       printf("Directory \"%s\" created\n", buf);
+
+               *cpold = '/';
+       }
+}
+
+
+/*
+ * Read an octal value in a field of the specified width, with optional
+ * spaces on both sides of the number and with an optional null character
+ * at the end.  Returns -1 on an illegal format.
+ */
+static long
+getoctal(cp, len)
+       const char *    cp;
+       int             len;
+{
+       long    val;
+
+       while ((len > 0) && (*cp == ' ')) {
+               cp++;
+               len--;
+       }
+
+       if ((len == 0) || !isoctal(*cp))
+               return -1;
+
+       val = 0;
+
+       while ((len > 0) && isoctal(*cp)) {
+               val = val * 8 + *cp++ - '0';
+               len--;
+       }
+
+       while ((len > 0) && (*cp == ' ')) {
+               cp++;
+               len--;
+       }
+
+       if ((len > 0) && *cp)
+               return -1;
+
+       return val;
+}
+
+/* END CODE */
diff --git a/cmds.c b/cmds.c
new file mode 100644 (file)
index 0000000..f4a5e60
--- /dev/null
+++ b/cmds.c
@@ -0,0 +1,1061 @@
+/*
+ * Copyright (c) 1998 by David I. Bell
+ * Permission is granted to use, distribute, or modify this source,
+ * provided that this copyright notice remains intact.
+ *
+ * Most simple built-in commands are here.
+ */
+
+#include "sash.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mount.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+#include <utime.h>
+#include <errno.h>
+#include <linux/fs.h>
+
+
+void
+do_echo(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       BOOL    first;
+
+       first = TRUE;
+
+       while (argc-- > 1) {
+               if (!first)
+                       fputc(' ', stdout);
+
+               first = FALSE;
+               fputs(*++argv, stdout);
+       }
+
+       fputc('\n', stdout);
+}
+
+
+void
+do_pwd(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       char    buf[PATHLEN];
+
+       if (getcwd(buf, PATHLEN) == NULL) {
+               fprintf(stderr, "Cannot get current directory\n");
+
+               return;
+       }
+
+       printf("%s\n", buf);
+}
+
+
+void
+do_cd(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       const char *    path;
+
+       if (argc > 1)
+               path = argv[1];
+       else {
+               path = getenv("HOME");
+
+               if (path == NULL) {
+                       fprintf(stderr, "No HOME environment variable\n");
+
+                       return;
+               }
+       }
+
+       if (chdir(path) < 0)
+               perror(path);
+}
+
+
+void
+do_mkdir(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       while (argc-- > 1) {
+               if (mkdir(argv[1], 0777) < 0)
+                       perror(argv[1]);
+
+               argv++;
+       }
+}
+
+
+void
+do_mknod(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       const char *    cp;
+       int             mode;
+       int             major;
+       int             minor;
+
+       mode = 0666;
+
+       if (strcmp(argv[2], "b") == 0)
+               mode |= S_IFBLK;
+       else if (strcmp(argv[2], "c") == 0)
+               mode |= S_IFCHR;
+       else {
+               fprintf(stderr, "Bad device type\n");
+
+               return;
+       }
+
+       major = 0;
+       cp = argv[3];
+
+       while (isdecimal(*cp))
+               major = major * 10 + *cp++ - '0';
+
+       if (*cp || (major < 0) || (major > 255)) {
+               fprintf(stderr, "Bad major number\n");
+
+               return;
+       }
+
+       minor = 0;
+       cp = argv[4];
+
+       while (isdecimal(*cp))
+               minor = minor * 10 + *cp++ - '0';
+
+       if (*cp || (minor < 0) || (minor > 255)) {
+               fprintf(stderr, "Bad minor number\n");
+
+               return;
+       }
+
+       if (mknod(argv[1], mode, major * 256 + minor) < 0)
+               perror(argv[1]);
+}
+
+
+void
+do_rmdir(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       while (argc-- > 1) {
+               if (rmdir(argv[1]) < 0)
+                       perror(argv[1]);
+
+               argv++;
+       }
+}
+
+
+void
+do_sync(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       sync();
+}
+
+
+void
+do_rm(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       while (argc-- > 1) {
+               if (unlink(argv[1]) < 0)
+                       perror(argv[1]);
+
+               argv++;
+       }
+}
+
+
+void
+do_chmod(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       const char *    cp;
+       int             mode;
+
+       mode = 0;
+       cp = argv[1];
+
+       while (isoctal(*cp))
+               mode = mode * 8 + (*cp++ - '0');
+
+       if (*cp) {
+               fprintf(stderr, "Mode must be octal\n");
+
+               return;
+       }
+
+       argc--;
+       argv++;
+
+       while (argc-- > 1) {
+               if (chmod(argv[1], mode) < 0)
+                       perror(argv[1]);
+
+               argv++;
+       }
+}
+
+
+void
+do_chown(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       const char *    cp;
+       int             uid;
+       struct passwd * pwd;
+       struct stat     statbuf;
+
+       cp = argv[1];
+
+       if (isdecimal(*cp)) {
+               uid = 0;
+
+               while (isdecimal(*cp))
+                       uid = uid * 10 + (*cp++ - '0');
+
+               if (*cp) {
+                       fprintf(stderr, "Bad uid value\n");
+
+                       return;
+               }
+       } else {
+               pwd = getpwnam(cp);
+
+               if (pwd == NULL) {
+                       fprintf(stderr, "Unknown user name\n");
+
+                       return;
+               }
+
+               uid = pwd->pw_uid;
+       }
+
+       argc--;
+       argv++;
+
+       while (argc-- > 1) {
+               argv++;
+
+               if ((stat(*argv, &statbuf) < 0) ||
+                       (chown(*argv, uid, statbuf.st_gid) < 0))
+               {
+                       perror(*argv);
+               }
+       }
+}
+
+
+void
+do_chgrp(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       const char *    cp;
+       int             gid;
+       struct group *  grp;
+       struct stat     statbuf;
+
+       cp = argv[1];
+
+       if (isdecimal(*cp)) {
+               gid = 0;
+
+               while (isdecimal(*cp))
+                       gid = gid * 10 + (*cp++ - '0');
+
+               if (*cp) {
+                       fprintf(stderr, "Bad gid value\n");
+
+                       return;
+               }
+       } else {
+               grp = getgrnam(cp);
+
+               if (grp == NULL) {
+                       fprintf(stderr, "Unknown group name\n");
+
+                       return;
+               }
+
+               gid = grp->gr_gid;
+       }
+
+       argc--;
+       argv++;
+
+       while (argc-- > 1) {
+               argv++;
+
+               if ((stat(*argv, &statbuf) < 0) ||
+                       (chown(*argv, statbuf.st_uid, gid) < 0))
+               {
+                       perror(*argv);
+               }
+       }
+}
+
+
+void
+do_touch(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       const char *    name;
+       int             fd;
+       struct utimbuf  now;
+
+       time(&now.actime);
+       now.modtime = now.actime;
+
+       while (argc-- > 1) {
+               name = *(++argv);
+
+               fd = open(name, O_CREAT | O_WRONLY | O_EXCL, 0666);
+
+               if (fd >= 0) {
+                       close(fd);
+
+                       continue;
+               }
+
+               if (utime(name, &now) < 0)
+                       perror(name);
+       }
+}
+
+
+void
+do_mv(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       const char *    srcname;
+       const char *    destname;
+       const char *    lastarg;
+       BOOL            dirflag;
+
+       lastarg = argv[argc - 1];
+
+       dirflag = isadir(lastarg);
+
+       if ((argc > 3) && !dirflag) {
+               fprintf(stderr, "%s: not a directory\n", lastarg);
+
+               return;
+       }
+
+       while (!intflag && (argc-- > 2)) {
+               srcname = *(++argv);
+
+               if (access(srcname, 0) < 0) {
+                       perror(srcname);
+
+                       continue;
+               }
+
+               destname = lastarg;
+
+               if (dirflag)
+                       destname = buildname(destname, srcname);
+
+               if (rename(srcname, destname) >= 0)
+                       continue;
+
+               if (errno != EXDEV) {
+                       perror(destname);
+
+                       continue;
+               }
+
+               if (!copyfile(srcname, destname, TRUE))
+                       continue;
+
+               if (unlink(srcname) < 0)
+                       perror(srcname);
+       }
+}
+
+
+void
+do_ln(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       const char *    srcname;
+       const char *    destname;
+       const char *    lastarg;
+       BOOL            dirflag;
+
+       if (argv[1][0] == '-') {
+               if (strcmp(argv[1], "-s")) {
+                       fprintf(stderr, "Unknown option\n");
+
+                       return;
+               }
+
+               if (argc != 4) {
+                       fprintf(stderr, "Wrong number of arguments for symbolic link\n");
+
+                       return;
+               }
+
+#ifdef S_ISLNK
+               if (symlink(argv[2], argv[3]) < 0)
+                       perror(argv[3]);
+#else
+               fprintf(stderr, "Symbolic links are not allowed\n");
+#endif
+               return;
+       }
+
+       /*
+        * Here for normal hard links.
+        */
+       lastarg = argv[argc - 1];
+       dirflag = isadir(lastarg);
+
+       if ((argc > 3) && !dirflag) {
+               fprintf(stderr, "%s: not a directory\n", lastarg);
+
+               return;
+       }
+
+       while (argc-- > 2) {
+               srcname = *(++argv);
+
+               if (access(srcname, 0) < 0) {
+                       perror(srcname);
+
+                       continue;
+               }
+
+               destname = lastarg;
+
+               if (dirflag)
+                       destname = buildname(destname, srcname);
+
+               if (link(srcname, destname) < 0) {
+                       perror(destname);
+
+                       continue;
+               }
+       }
+}
+
+
+void
+do_cp(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       const char *    srcname;
+       const char *    destname;
+       const char *    lastarg;
+       BOOL            dirflag;
+
+       lastarg = argv[argc - 1];
+
+       dirflag = isadir(lastarg);
+
+       if ((argc > 3) && !dirflag) {
+               fprintf(stderr, "%s: not a directory\n", lastarg);
+
+               return;
+       }
+
+       while (!intflag && (argc-- > 2)) {
+               srcname = *argv++;
+               destname = lastarg;
+
+               if (dirflag)
+                       destname = buildname(destname, srcname);
+
+               (void) copyfile(srcname, destname, FALSE);
+       }
+}
+
+
+void
+do_mount(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       const char *    str;
+       const char *    type;
+       int             flags;
+
+       argc--;
+       argv++;
+       type = "ext2";
+       flags = MS_MGC_VAL;
+
+       while ((argc > 0) && (**argv == '-')) {
+               argc--;
+               str = *argv++ ;
+
+               while (*++str) switch (*str) {
+                       case 't':
+                               if ((argc <= 0) || (**argv == '-')) {
+                                       fprintf(stderr, "Missing file system type\n");
+
+                                       return;
+                               }
+
+                               type = *argv++;
+                               argc--;
+                               break;
+
+                       case 'r':
+                               flags |= MS_RDONLY;
+                               break;
+
+                       case 'm':
+                               flags |= MS_REMOUNT;
+                               break;
+
+                       default:
+                               fprintf(stderr, "Unknown option\n");
+
+                               return;
+               }
+       }
+
+       if (argc != 2) {
+               fprintf(stderr, "Wrong number of arguments for mount\n");
+
+               return;
+       }
+
+       if (mount(argv[0], argv[1], type, flags, 0) < 0)
+               perror("mount failed");
+}
+
+
+void
+do_umount(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       if (umount(argv[1]) < 0)
+               perror(argv[1]);
+}
+
+
+void
+do_cmp(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       int             fd1;
+       int             fd2;
+       int             cc1;
+       int             cc2;
+       long            pos;
+       const char *    bp1;
+       const char *    bp2;
+       char            buf1[BUFSIZE];
+       char            buf2[BUFSIZE];
+       struct  stat    statbuf1;
+       struct  stat    statbuf2;
+
+       if (stat(argv[1], &statbuf1) < 0) {
+               perror(argv[1]);
+
+               return;
+       }
+
+       if (stat(argv[2], &statbuf2) < 0) {
+               perror(argv[2]);
+
+               return;
+       }
+
+       if ((statbuf1.st_dev == statbuf2.st_dev) &&
+               (statbuf1.st_ino == statbuf2.st_ino))
+       {
+               printf("Files are links to each other\n");
+
+               return;
+       }
+
+       if (statbuf1.st_size != statbuf2.st_size) {
+               printf("Files are different sizes\n");
+
+               return;
+       }
+
+       fd1 = open(argv[1], O_RDONLY);
+
+       if (fd1 < 0) {
+               perror(argv[1]);
+
+               return;
+       }
+
+       fd2 = open(argv[2], O_RDONLY);
+
+       if (fd2 < 0) {
+               perror(argv[2]);
+               close(fd1);
+
+               return;
+       }
+
+       pos = 0;
+
+       while (TRUE) {
+               if (intflag)
+                       goto closefiles;
+
+               cc1 = read(fd1, buf1, sizeof(buf1));
+
+               if (cc1 < 0) {
+                       perror(argv[1]);
+                       goto closefiles;
+               }
+
+               cc2 = read(fd2, buf2, sizeof(buf2));
+
+               if (cc2 < 0) {
+                       perror(argv[2]);
+                       goto closefiles;
+               }
+
+               if ((cc1 == 0) && (cc2 == 0)) {
+                       printf("Files are identical\n");
+                       goto closefiles;
+               }
+
+               if (cc1 < cc2) {
+                       printf("First file is shorter than second\n");
+                       goto closefiles;
+               }
+
+               if (cc1 > cc2) {
+                       printf("Second file is shorter than first\n");
+                       goto closefiles;
+               }
+
+               if (memcmp(buf1, buf2, cc1) == 0) {
+                       pos += cc1;
+
+                       continue;
+               }
+
+               bp1 = buf1;
+               bp2 = buf2;
+               while (*bp1++ == *bp2++)
+                       pos++;
+
+               printf("Files differ at byte position %ld\n", pos);
+
+               goto closefiles;
+       }
+
+closefiles:
+       close(fd1);
+       close(fd2);
+}
+
+
+void
+do_more(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       FILE *          fp;
+       const char *    name;
+       int             ch;
+       int             line;
+       int             col;
+       int             pageLines;
+       int             pageColumns;
+       char            buf[80];
+
+       /*
+        * Get the width and height of the screen if it is set.
+        * If not, then default it.
+        */
+       pageLines = 0;
+       pageColumns = 0;
+
+       name = getenv("LINES");
+
+       if (name)
+               pageLines = atoi(name);
+
+       name = getenv("COLS");
+
+       if (name)
+               pageColumns = atoi(name);
+
+       if (pageLines <= 0)
+               pageLines = 24;
+
+       if (pageColumns <= 0)
+               pageColumns = 80;
+
+       /*
+        * OK, process each file.
+        */
+       while (argc-- > 1) {
+               name = *(++argv);
+
+               fp = fopen(name, "r");
+
+               if (fp == NULL) {
+                       perror(name);
+
+                       return;
+               }
+
+               printf("<< %s >>\n", name);
+               line = 1;
+               col = 0;
+
+               while (fp && ((ch = fgetc(fp)) != EOF)) {
+                       switch (ch) {
+                               case '\r':
+                                       col = 0;
+                                       break;
+
+                               case '\n':
+                                       line++;
+                                       col = 0;
+                                       break;
+
+                               case '\t':
+                                       col = ((col + 1) | 0x07) + 1;
+                                       break;
+
+                               case '\b':
+                                       if (col > 0)
+                                               col--;
+                                       break;
+
+                               default:
+                                       col++;
+                       }
+
+                       putchar(ch);
+
+                       if (col >= pageColumns) {
+                               col -= pageColumns;
+                               line++;
+                       }
+
+                       if (line < pageLines)
+                               continue;
+
+                       if (col > 0)
+                               putchar('\n');
+
+                       printf("--More--");
+                       fflush(stdout);
+
+                       if (intflag || (read(0, buf, sizeof(buf)) < 0)) {
+                               if (fp)
+                                       fclose(fp);
+
+                               return;
+                       }
+
+                       ch = buf[0];
+
+                       if (ch == ':')
+                               ch = buf[1];
+
+                       switch (ch) {
+                               case 'N':
+                               case 'n':
+                                       fclose(fp);
+                                       fp = NULL;
+                                       break;
+
+                               case 'Q':
+                               case 'q':
+                                       fclose(fp);
+
+                                       return;
+                       }
+
+                       col = 0;
+                       line = 1;
+               }
+
+               if (fp)
+                       fclose(fp);
+       }
+}
+
+
+void
+do_exit(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       exit(0);
+}
+
+
+void
+do_setenv(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       const char *    name;
+       const char *    value;
+       char *          str;
+
+       name = argv[1];
+       value = argv[2];
+
+       /*
+        * The value given to putenv must remain around, so we must malloc it.
+        * Note: memory is not reclaimed if the same variable is redefined.
+        */
+       str = malloc(strlen(name) + strlen(value) + 2);
+
+       if (str == NULL)
+       {
+               fprintf(stderr, "Cannot allocate memory\n");
+
+               return;
+       }
+
+       strcpy(str, name);
+       strcat(str, "=");
+       strcat(str, value);
+
+       putenv(str);
+}
+
+
+void
+do_printenv(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       const char **   env;
+       extern char **  environ;
+       int             len;
+
+       env = (const char **) environ;
+
+       if (argc == 1) {
+               while (*env)
+                       printf("%s\n", *env++);
+
+               return;
+       }
+
+       len = strlen(argv[1]);
+
+       while (*env) {
+               if ((strlen(*env) > len) && (env[0][len] == '=') &&
+                       (memcmp(argv[1], *env, len) == 0))
+               {
+                       printf("%s\n", &env[0][len+1]);
+
+                       return;
+               }
+               env++;
+       }
+}
+
+
+void
+do_umask(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       const char *    cp;
+       int             mask;
+
+       if (argc <= 1) {
+               mask = umask(0);
+               umask(mask);
+               printf("%03o\n", mask);
+
+               return;
+       }
+
+       mask = 0;
+       cp = argv[1];
+
+       while (isoctal(*cp))
+               mask = mask * 8 + *cp++ - '0';
+
+       if (*cp || (mask & ~0777)) {
+               fprintf(stderr, "Bad umask value\n");
+
+               return;
+       }
+
+       umask(mask);
+}
+
+
+void
+do_kill(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       const char *    cp;
+       int             sig;
+       int             pid;
+
+       sig = SIGTERM;
+
+       if (argv[1][0] == '-') {
+               cp = &argv[1][1];
+
+               if (strcmp(cp, "HUP") == 0)
+                       sig = SIGHUP;
+               else if (strcmp(cp, "INT") == 0)
+                       sig = SIGINT;
+               else if (strcmp(cp, "QUIT") == 0)
+                       sig = SIGQUIT;
+               else if (strcmp(cp, "KILL") == 0)
+                       sig = SIGKILL;
+               else {
+                       sig = 0;
+
+                       while (isdecimal(*cp))
+                               sig = sig * 10 + *cp++ - '0';
+
+                       if (*cp) {
+                               fprintf(stderr, "Unknown signal\n");
+
+                               return;
+                       }
+               }
+
+               argc--;
+               argv++;
+       }
+
+       while (argc-- > 1) {
+               cp = *++argv;
+               pid = 0;
+
+               while (isdecimal(*cp))
+                       pid = pid * 10 + *cp++ - '0';
+
+               if (*cp) {
+                       fprintf(stderr, "Non-numeric pid\n");
+
+                       return;
+               }
+
+               if (kill(pid, sig) < 0)
+                       perror(*argv);
+       }
+}
+
+
+void
+do_where(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       const char *    program;
+       const char *    dirName;
+       char *          path;
+       char *          endPath;
+       char *          fullPath;
+       BOOL            found;
+
+       found = FALSE;
+       program = argv[1];
+
+       if (strchr(program, '/') != NULL)
+       {
+               fprintf(stderr, "Program name cannot include a path\n");
+
+               return;
+       }
+
+       path = getenv("PATH");
+
+       fullPath = getchunk(strlen(path) + strlen(program) + 2);
+       path = chunkstrdup(path);
+
+       if ((path == NULL) || (fullPath == NULL))
+       {
+               fprintf(stderr, "Memory allocation failed\n");
+
+               return;
+       }
+
+       /*
+        * Check out each path to see if the program exists and is
+        * executable in that path.
+        */
+       for (; path; path = endPath)
+       {
+               /*
+                * Find the end of the next path and NULL terminate
+                * it if necessary.
+                */
+               endPath = strchr(path, ':');
+
+               if (endPath)
+                       *endPath++ = '\0';
+
+               /*
+                * Get the directory name, defaulting it to DOT if
+                * it is null.
+                */
+               dirName = path;
+
+               if (dirName == '\0')
+                       dirName = ".";
+
+               /*
+                * Construct the full path of the program.
+                */
+               strcpy(fullPath, dirName);
+               strcat(fullPath, "/");
+               strcat(fullPath, program);
+
+               /*
+                * See if the program exists and is executable.
+                */
+               if (access(fullPath, X_OK) < 0)
+               {
+                       if (errno != ENOENT)
+                               printf("%s: %s\n", fullPath, strerror(errno));
+
+                       continue;
+               }
+
+               printf("%s\n", fullPath);
+               found = TRUE;
+       }
+
+       if (!found)
+               printf("Program \"%s\" not found in PATH\n", program);
+}
+
+/* END CODE */
diff --git a/sash.1 b/sash.1
new file mode 100644 (file)
index 0000000..68214c4
--- /dev/null
+++ b/sash.1
@@ -0,0 +1,408 @@
+.TH SASH 1
+.SH NAME
+sash \- stand-alone shell with built-in commands
+.SH SYNOPSYS
+.B sash
+.SH DESCRIPTION
+The
+.B sash
+program is a stand-alone shell which is useful for recovering from certain
+types of system failures.  In particular, it was created in order to cope
+with the problem of missing shared libraries.
+You can also use
+.B sash
+to safely upgrade to new versions of the shared libraries.
+.PP
+.B Sash
+can execute external programs, as in any shell.  There are no restrictions
+on these commands, as the standard shell is used to execute them if there
+are any meta-characters in the command.
+.PP
+More importantly, however, is that many of the standard system commands
+are built-in to
+.BR sash .
+These built-in commands are:
+.PP
+.nf
+     -chgrp, -chmod, -chown, -cmp, -cp, -dd, -echo,
+     -ed, -grep, -gunzip, -gzip, -kill, -ln, -ls, -mkdir,
+     -mknod, -more, -mount, -mv, -printenv, -pwd, -rm,
+     -rmdir, -sync, -tar, -touch, -umount, -where
+.fi
+.PP
+These commands are generally similar to the standard programs with similar
+names.  However, they are simpler and cruder than the external programs,
+and so many of the options are not implemented.  The restrictions for each
+built-in command are described later.
+.PP
+The built-in commands which correspond to external programs begin with a
+dash character in order to distinguish them from the external programs.
+So typing "ls", for example, will attempt to run the real
+.B ls
+program.
+If "-ls" is typed, then the built-in command which mimics
+.B ls
+is called.
+.PP
+For the built-in commands, filenames are expanded so that asterisks,
+question marks, and characters inside of square brackets are recognised
+and are expanded.  However, no other command line processing is performed.
+This includes quoting of arguments, specifying of file redirection, and
+the specifying of a pipeline.
+.PP
+If an external program is non-existant or fails to run correctly, then
+the "alias" built-in command may be used to redefine the standard command
+so that it automatically runs the built-in command instead.  For example,
+the command "alias ls -ls" redefines "ls" to run the built-in command.
+This saves you the pain of having to remember to type the leading dash
+all of the time.
+.PP
+The "help" command will list all of the built-in commands in
+.B sash .
+If an argument is given, it will list only those built-in commands
+which contain the given argument as a sub-string.
+Each built-in command is described below in more detail.
+.PP
+.TP
+.B alias [name [command]]
+If
+.I name
+and
+.I command
+are provided, this defines an alias for a command
+with the specified name, which executes the specified command, with
+possible arguments.  If just
+.I name
+is provided, then the definition
+of the specified command alias is displayed.  If nothing is provided,
+then the definitions of all aliases are displayed.  When defining an
+alias, wildcards are not expanded.
+.TP
+.B cd [dirname]
+If
+.I dirname
+is provided, then the current directory is changed to the
+dirname.  If
+.I dirname
+is absent, then the current directory is changed
+to the user's home directory (value of the $HOME environment variable).
+.TP
+.B -chgrp gid filename ...
+Change the group id for the specified list of files.  The
+.I gid
+can
+either be a group name, or a decimal value.
+.TP
+.B -chmod mode filename ...
+Change the mode of the specified list of files.  The
+.I mode
+argument
+can only be an octal value.
+.TP
+.B -chown uid filename ...
+Change the owner id for the specified list of files.  The
+.I uid
+can
+either be a user name, or a decimal value.
+.TP
+.B -cmp filename1 filename2
+Determines whether or not the specified filenames have identical data.
+This says that the files are links to each other, are different sizes,
+differ at a particular byte number, or are identical.
+.TP
+.B -cp srcname ... destname
+Copies one or more files from the
+.I srcname
+to the
+.IR destname .
+If more
+than one srcname is given, or if destname is a directory, then all
+the srcnames are copied into the destname directory with the same
+names as the srcnames.
+.TP
+.B -dd if=name of=name [bs=n] [count=n] [skip=n] [seek=n]
+Copy data from one file to another with the specified parameters.
+The
+.I if
+and
+.I of
+arguments must be provided, so stdin and stdout cannot
+be specified.  The
+.I bs
+argument is the block size, and is a numeric
+value (which defaults to 512 bytes).
+.I Count
+is the number of blocks
+to be copied (which defaults to end of file for the input file).
+.I Skip
+is the number of blocks to ignore before copying (seek is used
+if possible, and the default is 0).
+.I Seek
+is the number of blocks to
+seek in the output file before writing (and defaults to 0).  Any of
+the numeric decimal values can have one or more trailing letters
+from the set 'kbw', which multiplies the value by 1024, 512, and 2
+respectively.  The command reports the number of full blocks read
+and written, and whether or not any partial block was read or written.
+.TP
+.B -echo [args] ...
+Echo the arguments to the -echo command.  Wildcards are expanded, so
+this is convenient to get a quick list of filenames in a directory.
+The output is always terminated with a newline.
+.TP
+.B -ed [filename]
+Edit the specified file using line-mode commands.  The following
+.B ed
+commands are provided: = c r w i a d p l s f k z and q.
+Line numbers can be constants, ".", "$", "'x", 
+.RI / string /
+and simple
+arithmetic combinations of these.  The substitute command and the
+search expression can only use literal strings.  There are some
+small differences in the way that some commands behave.
+.TP
+.B exec filename [args]
+Execute the specified program with the specified arguments.
+This replaces
+.B sash
+completely by the executed program.
+.TP
+.B exit
+Quit from
+.BR sash .
+.TP
+.B -grep [-in] word filename ...
+Display lines of the specified files which contain the given word.
+If only one filename is given, then only the matching lines are
+printed.  If multiple filenames are given, then the filenames are
+printed along with the matching lines.
+.I Word
+must be a single word,
+(ie, not a regular expression).  If -i is given, then case is
+ignored when doing the search.  If -n is given, then the line
+numbers of the matching lines are also printed.
+.TP
+.B -gunzip inputfilename ... [-o outputpath]
+Uncompress one or more files that had been compressed using the
+.I gzip
+or
+.I compress
+algorithms.
+If the -o option is not given,
+then each of the input file names must have one of the
+extensions ".gz", ".tgz", or ".Z",
+and those files will be replaced by the uncompressed versions of those files.
+The original files will be deleted after the output files have been
+successfully created.
+The uncompressed versions of the files have the same names as the original
+file names, except for a simple modification of their extensions.
+If an extension is ".tgz", then the extension is replaced by ".tar".
+Otherwise, the ".gz" or ".Z" extension is removed.
+.sp
+If the -o option is given, then the input files will not be deleted,
+and the uncompressed versions of the files will be created as specified
+by
+.IR outputpath .
+If the output path is a directory, then the uncompressed versions of the
+input files will be placed in that directory with their file names
+modified as described above, or with the same name if the input file name
+does not have one of the special extensions.
+If the output path is not a directory, then only one input file is allowed,
+and the uncompressed version of that input file is created as the output
+path exactly as specified.
+.TP
+.B -gzip inputfilename ... [-o outputpath]
+Compresses one or more files using the
+.I gzip
+algorithm.
+If the -o option is not given,
+then each of the input file names will be replaced by the compressed
+versions of those files,
+The original files will be deleted after the output files have been
+successfully created.
+The compressed versions of the files have the same names as the original
+file names, except for a simple modification of the extensions.
+If an extension is ".tar", then the extension is replaced by ".tgz".
+Otherwise, the ".gz" extension is added.
+.sp
+If the -o option is given, then the input files will not be deleted,
+and the compressed versions of the files will be created as specified
+by
+.IR outputpath .
+If the output path is a directory, then the compressed versions of the
+input files will be placed in that directory with their file names
+modified as described above.
+If the output path is not a directory, then only one input file is allowed,
+and the compressed version of that input file is created as the output
+path exactly as specified.
+.TP
+.B help
+Displays a list of built-in commands.
+.TP
+.B -kill [-signal] pid ...
+Sends the specified signal to the specified list of processes.
+.I Signal
+is a numberic value, or one of the special values HUP, INT,
+QUIT, or KILL.
+.TP
+.B -ln [-s] srcname ... destname
+Links one or more files from the
+.I srcname
+to the specified
+.IR destname .
+If there are
+multiple srcnames, or destname is a directory, then the link is
+put in the destname directory with the same name as the source name.
+The default links are hard links.  Using -s makes symbolic links.
+For symbolic links, only one srcname can be specified.
+.TP
+.B -ls [-lid] filename ...
+Display information about the specified filesnames, which may be
+directories.  The normal listing is simply a list of filenames,
+one per line.  The options available are -l, -i, and -d.  The -l
+option produces a long listing given the normal 'ls' information.
+The -i option also displays the inode numbers of the files.  The
+-d option displays information about a directory, instead of the
+files within it.
+.TP
+.B -mkdir dirname ...
+Creates the specified directories.  They are created with the
+default permissions.
+.TP
+.B -mknod filename type major minor
+Creates a special device node, either a character file or a block
+file.
+.I Filename
+is the name of the node.
+.I Type
+is either 'c' or 'd'.
+.I Major
+is the major device number.
+.I Minor
+is the minor device number.
+Both of these numbers are decimal.
+.TP
+.B -more filename ...
+Type out the contents of the specified filenames, one page at a
+time.  For each page displayed, you can type 'n' and a return to go
+to the next file, 'q' and a return to quit the command completely,
+or just a return to go to the next page.  The environment variables
+LINES and COLS can be used to set the page size.
+.TP
+.B -mount [-t type] [-r] [-m] devname dirname
+Mount a filesystem on a directory name.  The -t option specifies the
+type of filesystem being mounted, and defaults to "ext2".
+The -r option indicates to mount the filesystem read-only.
+The -m option indicates to remount an already mounted filesystem.
+.TP
+.B -mv srcname ... destname
+Moves one or more files from the
+.I srcname
+to the
+.IR destname .
+If multiple srcnames are given, or if destname is a directory, then
+the srcnames are copied into the destination directory with the
+same names as the srcnames.  Renames are attempted first, but if
+this fails because of the files being on different filesystems,
+then a copies and deletes are done instead.
+.TP
+.B -printenv [name]
+If
+.I name
+is not given, this prints out the values of all the current
+environment variables.  If
+.I name
+is given, then only that environment variable value is printed.
+.TP
+.B prompt [word] ...
+Sets the prompt string that is displayed before reading of a
+command.  A space is always added to the specified prompt.
+.TP
+.B -pwd
+Prints the current working directory.
+.TP
+.B quit
+Exits from
+.BR sash .
+.TP
+.B -rm filename ...
+Removes one or more files.
+.TP
+.B -rmdir dirname ...
+Removes one or more directories.  The directories must be empty
+for this to be successful.
+.TP
+.B setenv name value
+Set the value of an environment variable.
+.TP
+.B source filename
+Execute commands which are contained in the specified filename.
+.TP
+.B -sync
+Do a "sync" system call to force dirty blocks out to the disk.
+.TP
+.B -tar [xtv]f devname [filename] ...
+List or restore files from a tar archive.  This command can only
+read tar files, not create them.  The available options are xtvf.
+The f option must be specified, and accepts a device or file
+name argument which contains the tar archive.  If no filename is
+given, all files in the archive are listed or extracted.  Otherwise,
+only those files starting with the specified filenames are done.
+Leading slashes in the tar archive filenames are removed.
+.TP
+.B -touch filename ...
+Updates the modify times of the specifed files.  If a file does not
+exist, then it will be created with the default protection.
+.TP
+.B umask [mask]
+If
+.I mask
+is given, sets the "umask" value used for initializing the
+permissions of newly created files.  If
+.I mask
+is not given, then the
+current umask value is printed.  The mask is an octal value.
+.TP
+.B -umount filename
+Unmounts a file system.  The filename can either be the device name
+which is mounted, or else the directory name which the file system
+is mounted onto.
+.TP
+.B unalias name
+Remove the definition for the specified alias.
+.TP
+.B -where program
+Prints out all of paths defined by the PATH environment variable where the
+specified program exists.  If the program exists but cannot be executed,
+then the reason is also printed.
+.SH OPTIONS
+There are several command line options to
+.BR sash .
+The -c option executes the next argument as a command (including embedded
+spaces to separate the arguments of the command), and then exits.
+.PP
+The -p option takes the next argument as the prompt string to be used
+when prompting for commands.
+.PP
+The -q option makes
+.B sash
+quiet, which simply means that it doesn't print its introduction line
+when it starts.
+.SH WARNINGS
+.B Sash
+should obviously be linked statically, otherwise it's purpose is lost.
+.PP
+The system is still vulnerable to unrunnable shared versions of
+.B init
+and
+.B sh.
+.PP
+Several other system commands might be necessary for system recovery,
+but aren't built-in to
+.BR sash .
+.SH AUTHOR
+.nf
+David I. Bell
+dbell@canb.auug.org.au
+March 8, 1998
+.fi
diff --git a/sash.c b/sash.c
new file mode 100644 (file)
index 0000000..51cdb91
--- /dev/null
+++ b/sash.c
@@ -0,0 +1,924 @@
+/*
+ * Copyright (c) 1998 by David I. Bell
+ * Permission is granted to use, distribute, or modify this source,
+ * provided that this copyright notice remains intact.
+ *
+ * Stand-alone shell for system maintainance for Linux.
+ * This program should NOT be built using shared libraries.
+ */
+
+#include <wait.h>
+#include <signal.h>
+#include <errno.h>
+
+#include "sash.h"
+
+
+static const char * const      version = "2.1";
+
+
+typedef struct {
+       char *  name;
+       char *  usage;
+       void    (*func) PROTO((int argc, const char ** argv));
+       int     minargs;
+       int     maxargs;
+} CMDTAB;
+
+
+static const CMDTAB    cmdtab[] =
+{
+       {"alias",       "[name [command]]",     do_alias,
+       1,              MAXARGS},
+
+       {"cd",          "[dirname]",            do_cd,
+       1,              2},
+
+       {"-chgrp",      "gid filename ...",     do_chgrp,
+       3,              MAXARGS},
+
+       {"-chmod",      "mode filename ...",    do_chmod,
+       3,              MAXARGS},
+
+       {"-chown",      "uid filename ...",     do_chown,
+       3,              MAXARGS},
+
+       {"-cmp",                "filename1 filename2",  do_cmp,
+       3,              3},
+
+       {"-cp",         "srcname ... destname", do_cp,
+       3,              MAXARGS},
+
+       {"-dd",         "if=name of=name [bs=n] [count=n] [skip=n] [seek=n]", do_dd,
+       3,              MAXARGS},
+
+       {"-echo",       "[args] ...",           do_echo,
+       1,              MAXARGS},
+
+       {"-ed",         "[filename]",           do_ed,
+       1,              2},
+
+       {"exec",                "filename [args]",      do_exec,
+       2,              MAXARGS},
+
+       {"exit",                "",                     do_exit,
+       1,              1},
+
+       {"-grep",       "[-in] word filename ...",      do_grep,
+       3,              MAXARGS},
+
+#ifdef HAVE_GZIP
+       {"-gunzip",     "filename ... [-o outputPath]", do_gunzip,
+       2,              MAXARGS},
+
+       {"-gzip",       "filename ... [-o outputPath]", do_gzip,
+       2,              MAXARGS},
+#endif
+
+       {"help",                "[word]",               do_help,
+       1,              2},
+
+       {"-kill",       "[-sig] pid ...",       do_kill,
+       2,              MAXARGS},
+
+       {"-ln",         "[-s] srcname ... destname",    do_ln,
+       3,              MAXARGS},
+
+       {"-ls",         "[-lid] filename ...",  do_ls,
+       1,              MAXARGS},
+
+       {"-mkdir",      "dirname ...",          do_mkdir,
+       2,              MAXARGS},
+
+       {"-mknod",      "filename type major minor",    do_mknod,
+       5,              5},
+
+       {"-more",       "filename ...",         do_more,
+       2,              MAXARGS},
+
+       {"-mount",      "[-t type] [-r] [-m] devname dirname",  do_mount,
+       3,              MAXARGS},
+
+       {"-mv",         "srcname ... destname", do_mv,
+       3,              MAXARGS},
+
+       {"-printenv",   "[name]",               do_printenv,
+       1,              2},
+
+       {"prompt",      "string",               do_prompt,
+       2,              MAXARGS},
+
+       {"-pwd",                "",                     do_pwd,
+       1,              1},
+
+       {"quit",                "",                     do_exit,
+       1,              1},
+
+       {"-rm",         "filename ...",         do_rm,
+       2,              MAXARGS},
+
+       {"-rmdir",      "dirname ...",          do_rmdir,
+       2,              MAXARGS},
+
+       {"setenv",      "name value",           do_setenv,
+       3,              3},
+
+       {"source",      "filename",             do_source,
+       2,              2},
+
+       {"-sync",       "",                     do_sync,
+       1,              1},
+
+       {"-tar",                "[xtv]f devname filename ...",  do_tar,
+       2,              MAXARGS},
+
+       {"-touch",      "filename ...",         do_touch,
+       2,              MAXARGS},
+
+       {"umask",       "[mask]",               do_umask,
+       1,              2},
+
+       {"-umount",     "filename",             do_umount,
+       2,              2},
+
+       {"unalias",     "name",                 do_unalias,
+       2,              2},
+
+       {"-where",      "program",              do_where,
+       2,              2},
+
+       {NULL,          0,                      0,
+       0,              0}
+};
+
+
+typedef struct {
+       char *  name;
+       char *  value;
+} ALIAS;
+
+
+/*
+ * Local data.
+ */
+static ALIAS * aliastable;
+static int     aliascount;
+
+static FILE *  sourcefiles[MAXSOURCE];
+static int     sourcecount;
+
+static BOOL    intcrlf = TRUE;
+static char *  prompt;
+
+
+/*
+ * Local procedures.
+ */
+static void    catchint PROTO((int));
+static void    catchquit PROTO((int));
+static void    readfile PROTO((const char * name));
+static void    command PROTO((const char * cmd));
+static void    runcmd PROTO((const char * cmd, int argc, const char ** argv));
+static void    showprompt PROTO((void));
+static void    usage PROTO((void));
+static BOOL    trybuiltin PROTO((int argc, const char ** argv));
+static ALIAS * findalias PROTO((const char * name));
+
+
+/*
+ * Global interrupt flag.
+ */
+BOOL   intflag;
+
+
+int
+main(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       const char *    cp;
+       const char *    singleCommand;
+       BOOL            quietFlag;
+       char            buf[PATHLEN];
+
+       singleCommand = NULL;
+       quietFlag = FALSE;
+
+       /*
+        * Look for options.
+        */
+       argv++;
+       argc--;
+
+       while ((argc > 0) && (**argv == '-'))
+       {
+               cp = *argv++ + 1;
+               argc--;
+
+               while (*cp) switch (*cp++)
+               {
+                       case 'c':
+                               /*
+                                * Execute specified command.
+                                */
+                               if ((argc != 1) || singleCommand)
+                                       usage();
+
+                               singleCommand = *argv++;
+                               argc--;
+
+                               break;
+
+                       case 'p':
+                               /*
+                                * Set the prompt string.
+                                */
+                               if ((argc <= 0) || (**argv == '-'))
+                                       usage();
+
+                               if (prompt)
+                                       free(prompt);
+
+                               prompt = strdup(*argv++);
+                               argc--;
+
+                               break;
+
+                       case 'q':
+                               quietFlag = TRUE;
+                               break;
+
+                       case 'h':
+                       case '?':
+                               usage();
+                               break;
+
+                       default:
+                               fprintf(stderr, "Unknown option -%c\n", cp[-1]);
+
+                               return 1;
+               }
+       }
+
+       /*
+        * No more arguments are allowed.
+        */
+       if (argc > 0)
+               usage();
+
+       /*
+        * Default our path if it is not set.
+        */
+       if (getenv("PATH") == NULL)
+               putenv("PATH=/bin:/usr/bin:/sbin:/usr/sbin:/etc");
+
+       /*
+        * If we are to execute a single command, then do so and exit.
+        */
+       if (singleCommand)
+       {
+               command(singleCommand);
+
+               return 0;
+       }
+
+       /*
+        * Print a hello message unless we are told to be silent.
+        */
+       if (!quietFlag && isatty(STDIN))
+               printf("Stand-alone shell (version %s)\n", version);
+
+       signal(SIGINT, catchint);
+       signal(SIGQUIT, catchquit);
+
+       if (getenv("PATH") == NULL)
+               putenv("PATH=/bin:/usr/bin:/sbin:/usr/sbin:/etc");
+
+       cp = getenv("HOME");
+
+       if (cp) {
+               strcpy(buf, cp);
+               strcat(buf, "/");
+               strcat(buf, ".aliasrc");
+
+               if ((access(buf, 0) == 0) || (errno != ENOENT))
+                       readfile(buf);
+       }
+
+       readfile(NULL);
+
+       return 0;
+}
+
+
+/*
+ * Read commands from the specified file.
+ * A null name pointer indicates to read from stdin.
+ */
+static void
+readfile(name)
+       const char *    name;
+{
+       FILE *  fp;
+       int     cc;
+       BOOL    ttyflag;
+       char    buf[CMDLEN];
+
+       if (sourcecount >= MAXSOURCE) {
+               fprintf(stderr, "Too many source files\n");
+
+               return;
+       }
+
+       fp = stdin;
+
+       if (name) {
+               fp = fopen(name, "r");
+
+               if (fp == NULL) {
+                       perror(name);
+
+                       return;
+               }
+       }
+
+       sourcefiles[sourcecount++] = fp;
+
+       ttyflag = isatty(fileno(fp));
+
+       while (TRUE) {
+               if (ttyflag)
+                       showprompt();
+
+               if (intflag && !ttyflag && (fp != stdin)) {
+                       fclose(fp);
+                       sourcecount--;
+
+                       return;
+               }
+       
+               if (fgets(buf, CMDLEN - 1, fp) == NULL) {
+                       if (ferror(fp) && (errno == EINTR)) {
+                               clearerr(fp);
+
+                               continue;
+                       }
+
+                       break;
+               }
+
+               cc = strlen(buf);
+
+               if (buf[cc - 1] == '\n')
+                       cc--;
+
+               while ((cc > 0) && isblank(buf[cc - 1]))
+                       cc--;
+
+               buf[cc] = '\0';
+
+               command(buf);
+       }
+
+       if (ferror(fp)) {
+               perror("Reading command line");
+
+               if (fp == stdin)
+                       exit(1);
+       }
+
+       clearerr(fp);
+
+       if (fp != stdin)
+               fclose(fp);
+
+       sourcecount--;
+}
+
+
+/*
+ * Parse and execute one null-terminated command line string.
+ * This breaks the command line up into words, checks to see if the
+ * command is an alias, and expands wildcards.
+ */
+static void
+command(cmd)
+       const char *    cmd;
+{
+       char *          cp;
+       const ALIAS *   alias;
+       const char **   argv;
+       int             argc;
+       char            buf[CMDLEN];
+
+       intflag = FALSE;
+
+       freechunks();
+
+       while (isblank(*cmd))
+               cmd++;
+
+       if ((*cmd == '\0') || (*cmd == '#') || !makeargs(cmd, &argc, &argv))
+               return;
+
+       /*
+        * Search for the command in the alias table.
+        * If it is found, then replace the command name with
+        * the alias, and append any other arguments to it.
+        */
+       alias = findalias(argv[0]);
+
+       if (alias) {
+               cp = buf;
+               strcpy(cp, alias->value);
+               cp += strlen(cp);
+
+               while (--argc > 0) {
+                       *cp++ = ' ';
+                       strcat(cp, *++argv);
+                       cp += strlen(cp);
+               }
+
+               cmd = buf;
+
+               if (!makeargs(cmd, &argc, &argv))
+                       return;
+       }
+
+       /*
+        * Now look for the command in the builtin table, and execute
+        * the command if found.
+        */
+       if (trybuiltin(argc, argv))
+               return;
+
+       /*
+        * Not found, run the program along the PATH list.
+        */
+       runcmd(cmd, argc, argv);
+}
+
+
+/*
+ * Try to execute a built-in command.
+ * Returns TRUE if the command is a built in, whether or not the
+ * command succeeds.  Returns FALSE if this is not a built-in command.
+ */
+static BOOL
+trybuiltin(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       const CMDTAB *  cmdptr;
+       int             oac;
+       int             newargc;
+       int             matches;
+       int             i;
+       const char *    newargv[MAXARGS];
+       const char *    nametable[MAXARGS];
+
+       cmdptr = cmdtab - 1;
+
+       do {
+               cmdptr++;
+
+               if (cmdptr->name == NULL)
+                       return FALSE;
+
+       } while (strcmp(argv[0], cmdptr->name));
+
+       /*
+        * Give a usage string if the number of arguments is too large
+        * or too small.
+        */
+       if ((argc < cmdptr->minargs) || (argc > cmdptr->maxargs)) {
+               fprintf(stderr, "usage: %s %s\n",
+                       cmdptr->name, cmdptr->usage);
+
+               return TRUE;
+       }
+
+       /*
+        * Check here for several special commands which do not
+        * have wildcarding done for them.
+        */
+       if ((cmdptr->func == do_alias) || (cmdptr->func == do_prompt)) {
+               (*cmdptr->func)(argc, argv);
+
+               return TRUE;
+       }
+
+       /*
+        * Now for each command argument, see if it is a wildcard, and if
+        * so, replace the argument with the list of matching filenames.
+        */
+       newargv[0] = argv[0];
+       newargc = 1;
+       oac = 0;
+
+       while (++oac < argc) {
+               matches = expandwildcards(argv[oac], MAXARGS, nametable);
+
+               if (matches < 0)
+                       return TRUE;
+
+               if ((newargc + matches) >= MAXARGS) {
+                       fprintf(stderr, "Too many arguments\n");
+
+                       return TRUE;
+               }
+
+               if (matches == 0)
+                       newargv[newargc++] = argv[oac];
+
+               for (i = 0; i < matches; i++)
+                       newargv[newargc++] = nametable[i];
+       }
+
+       (*cmdptr->func)(newargc, newargv);
+
+       return TRUE;
+}
+
+
+/*
+ * Execute the specified command.
+ */
+static void
+runcmd(cmd, argc, argv)
+       const char *    cmd;
+       int             argc;
+       const char **   argv;
+{
+       const char *    cp;
+       BOOL            magic;
+       int             pid;
+       int             status;
+
+       magic = FALSE;
+
+       for (cp = cmd; *cp; cp++) {
+               if ((*cp >= 'a') && (*cp <= 'z'))
+                       continue;
+
+               if ((*cp >= 'A') && (*cp <= 'Z'))
+                       continue;
+
+               if (isdecimal(*cp))
+                       continue;
+
+               if (isblank(*cp))
+                       continue;
+
+               if ((*cp == '.') || (*cp == '/') || (*cp == '-') ||
+                       (*cp == '+') || (*cp == '=') || (*cp == '_') ||
+                       (*cp == ':') || (*cp == ','))
+               {
+                       continue;
+               }
+
+               magic = TRUE;
+       }
+
+       if (magic) {
+               system(cmd);
+
+               return;
+       }
+
+       /*
+        * No magic characters in the command, so do the fork and
+        * exec ourself.  If this fails with ENOEXEC, then run the
+        * shell anyway since it might be a shell script.
+        */
+       pid = fork();
+
+       if (pid < 0) {
+               perror("fork failed");
+
+               return;
+       }
+
+       if (pid) {
+               status = 0;
+               intcrlf = FALSE;
+
+               while (((pid = wait(&status)) < 0) && (errno == EINTR))
+                       ;
+
+               intcrlf = TRUE;
+
+               if ((status & 0xff) == 0)
+                       return;
+
+               fprintf(stderr, "pid %d: %s (signal %d)\n", pid,
+                       (status & 0x80) ? "core dumped" : "killed",
+                       status & 0x7f);
+
+               return;
+       }
+
+       /*
+        * We are the child, so run the program.
+        * First close any extra file descriptors we have opened.
+        */     
+       while (--sourcecount >= 0) {
+               if (sourcefiles[sourcecount] != stdin)
+                       fclose(sourcefiles[sourcecount]);
+       }
+
+       execvp(argv[0], (char **) argv);
+
+       if (errno == ENOEXEC) {
+               system(cmd);
+               exit(0);
+       }
+
+       perror(argv[0]);
+       exit(1);
+}
+
+
+void
+do_help(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       const CMDTAB *  cmdptr;
+       const char *    str;
+
+       str = NULL;
+
+       if (argc == 2)
+               str = argv[1];
+
+       for (cmdptr = cmdtab; cmdptr->name; cmdptr++) {
+               if ((str == NULL) || (strstr(cmdptr->name, str) != NULL))
+                       printf("%-10s %s\n", cmdptr->name, cmdptr->usage);
+       }
+}
+
+
+void
+do_alias(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       const char *    name;
+       char *  value;
+       ALIAS * alias;
+       int     count;
+       char    buf[CMDLEN];
+
+       if (argc < 2) {
+               count = aliascount;
+
+               for (alias = aliastable; count-- > 0; alias++)
+                       printf("%s\t%s\n", alias->name, alias->value);
+
+               return;
+       }
+
+       name = argv[1];
+
+       if (argc == 2) {
+               alias = findalias(name);
+
+               if (alias)
+                       printf("%s\n", alias->value);
+               else
+                       fprintf(stderr, "Alias \"%s\" is not defined\n", name);
+
+               return; 
+       }
+
+       if (strcmp(name, "alias") == 0) {
+               fprintf(stderr, "Cannot alias \"alias\"\n");
+
+               return;
+       }
+
+       if (!makestring(argc - 2, argv + 2, buf, CMDLEN))
+               return;
+
+       value = malloc(strlen(buf) + 1);
+
+       if (value == NULL) {
+               fprintf(stderr, "No memory for alias value\n");
+
+               return;
+       }
+
+       strcpy(value, buf);
+
+       alias = findalias(name);
+
+       if (alias) {
+               free(alias->value);
+               alias->value = value;
+
+               return;
+       }
+
+       if ((aliascount % ALIASALLOC) == 0) {
+               count = aliascount + ALIASALLOC;
+
+               if (aliastable)
+                       alias = (ALIAS *) realloc(aliastable,
+                               sizeof(ALIAS *) * count);
+               else
+                       alias = (ALIAS *) malloc(sizeof(ALIAS *) * count);
+
+               if (alias == NULL) {
+                       free(value);
+                       fprintf(stderr, "No memory for alias table\n");
+
+                       return;
+               }
+
+               aliastable = alias;
+       }
+
+       alias = &aliastable[aliascount];
+
+       alias->name = malloc(strlen(name) + 1);
+
+       if (alias->name == NULL) {
+               free(value);
+               fprintf(stderr, "No memory for alias name\n");
+
+               return;
+       }
+
+       strcpy(alias->name, name);
+       alias->value = value;
+       aliascount++;
+}
+
+
+/*
+ * Look up an alias name, and return a pointer to it.
+ * Returns NULL if the name does not exist.
+ */
+static ALIAS *
+findalias(name)
+       const char *    name;
+{
+       ALIAS * alias;
+       int     count;
+
+       count = aliascount;
+
+       for (alias = aliastable; count-- > 0; alias++) {
+               if (strcmp(name, alias->name) == 0)
+                       return alias;
+       }
+
+       return NULL;
+}
+
+
+void
+do_source(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       readfile(argv[1]);
+}
+
+
+void
+do_exec(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       const char *    name;
+
+       name = argv[1];
+
+       if (access(name, 4)) {
+               perror(name);
+
+               return;
+       }
+
+       while (--sourcecount >= 0) {
+               if (sourcefiles[sourcecount] != stdin)
+                       fclose(sourcefiles[sourcecount]);
+       }
+
+       argv[argc] = NULL;
+
+       execv(name, (char **) argv + 1);
+       exit(1);
+}
+
+
+void
+do_prompt(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       char *  cp;
+       char    buf[CMDLEN];
+
+       if (!makestring(argc - 1, argv + 1, buf, CMDLEN))
+               return;
+
+       cp = malloc(strlen(buf) + 2);
+
+       if (cp == NULL) {
+               fprintf(stderr, "No memory for prompt\n");
+
+               return;
+       }
+
+       strcpy(cp, buf);
+       strcat(cp, " ");
+
+       if (prompt)
+               free(prompt);
+
+       prompt = cp;
+}
+
+
+void
+do_unalias(argc, argv)
+       int             argc;
+       const char **   argv;
+{
+       ALIAS * alias;
+
+       while (--argc > 0) {
+               alias = findalias(*++argv);
+
+               if (alias == NULL)
+                       continue;
+
+               free(alias->name);
+               free(alias->value);
+               aliascount--;
+               alias->name = aliastable[aliascount].name;
+               alias->value = aliastable[aliascount].value;    
+       }
+}
+
+
+/*
+ * Display the prompt string.
+ */
+static void
+showprompt()
+{
+       const char *    cp;
+
+       cp = "> ";
+
+       if (prompt)
+               cp = prompt;
+
+       write(STDOUT, cp, strlen(cp));
+}      
+
+
+static void
+catchint(val)
+       int     val;
+{
+       signal(SIGINT, catchint);
+
+       intflag = TRUE;
+
+       if (intcrlf)
+               write(STDOUT, "\n", 1);
+}
+
+
+static void
+catchquit(val)
+       int     val;
+{
+       signal(SIGQUIT, catchquit);
+
+       intflag = TRUE;
+
+       if (intcrlf)
+               write(STDOUT, "\n", 1);
+}
+
+
+/*
+ * Print the usage information and quit.
+ */
+static void
+usage()
+{
+       fprintf(stderr, "Stand-alone shell (version %s)\n", version);
+       fprintf(stderr, "\n");
+       fprintf(stderr, "Usage: sash [-q] [-c command] [-p prompt]\n");
+
+       exit(1);
+}
+
+/* END CODE */
diff --git a/sash.h b/sash.h
new file mode 100644 (file)
index 0000000..ef55539
--- /dev/null
+++ b/sash.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 1998 by David I. Bell
+ * Permission is granted to use, distribute, or modify this source,
+ * provided that this copyright notice remains intact.
+ *
+ * Definitions for stand-alone shell for system maintainance for Linux.
+ */
+
+#ifndef        SASH_H
+#define        SASH_H
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <memory.h>
+#include <malloc.h>
+#include <time.h>
+#include <ctype.h>
+
+
+#define        PATHLEN         1024
+#define        CMDLEN          10240
+#define        MAXARGS         1000
+#define        ALIASALLOC      20
+#define        STDIN           0
+#define        STDOUT          1
+#define        MAXSOURCE       10
+#define        BUFSIZE         8192
+
+
+#ifndef        isblank
+#define        isblank(ch)     (((ch) == ' ') || ((ch) == '\t'))
+#endif
+
+#define        isquote(ch)     (((ch) == '"') || ((ch) == '\''))
+#define        isdecimal(ch)   (((ch) >= '0') && ((ch) <= '9'))
+#define        isoctal(ch)     (((ch) >= '0') && ((ch) <= '7'))
+
+
+typedef        int     BOOL;
+
+#define        FALSE   ((BOOL) 0)
+#define        TRUE    ((BOOL) 1)
+
+
+/*
+ * Macro to use function prototypes if this is an ANSI compiler.
+ */
+#ifdef __STDC__
+#define        PROTO(a)        a
+#else
+#define        const
+#define        PROTO(a)        ()
+#endif
+
+
+/*
+ * Built-in command functions.
+ */
+extern void    do_alias PROTO((int argc, const char ** argv));
+extern void    do_cd PROTO((int argc, const char ** argv));
+extern void    do_exec PROTO((int argc, const char ** argv));
+extern void    do_exit PROTO((int argc, const char ** argv));
+extern void    do_prompt PROTO((int argc, const char ** argv));
+extern void    do_source PROTO((int argc, const char ** argv));
+extern void    do_umask PROTO((int argc, const char ** argv));
+extern void    do_unalias PROTO((int argc, const char ** argv));
+extern void    do_help PROTO((int argc, const char ** argv));
+extern void    do_ln PROTO((int argc, const char ** argv));
+extern void    do_cp PROTO((int argc, const char ** argv));
+extern void    do_mv PROTO((int argc, const char ** argv));
+extern void    do_rm PROTO((int argc, const char ** argv));
+extern void    do_chmod PROTO((int argc, const char ** argv));
+extern void    do_mkdir PROTO((int argc, const char ** argv));
+extern void    do_rmdir PROTO((int argc, const char ** argv));
+extern void    do_mknod PROTO((int argc, const char ** argv));
+extern void    do_chown PROTO((int argc, const char ** argv));
+extern void    do_chgrp PROTO((int argc, const char ** argv));
+extern void    do_sync PROTO((int argc, const char ** argv));
+extern void    do_printenv PROTO((int argc, const char ** argv));
+extern void    do_more PROTO((int argc, const char ** argv));
+extern void    do_cmp PROTO((int argc, const char ** argv));
+extern void    do_touch PROTO((int argc, const char ** argv));
+extern void    do_ls PROTO((int argc, const char ** argv));
+extern void    do_dd PROTO((int argc, const char ** argv));
+extern void    do_tar PROTO((int argc, const char ** argv));
+extern void    do_mount PROTO((int argc, const char ** argv));
+extern void    do_umount PROTO((int argc, const char ** argv));
+extern void    do_setenv PROTO((int argc, const char ** argv));
+extern void    do_pwd PROTO((int argc, const char ** argv));
+extern void    do_echo PROTO((int argc, const char ** argv));
+extern void    do_kill PROTO((int argc, const char ** argv));
+extern void    do_grep PROTO((int argc, const char ** argv));
+extern void    do_ed PROTO((int argc, const char ** argv));
+extern void    do_gzip PROTO((int argc, const char ** argv));
+extern void    do_gunzip PROTO((int argc, const char ** argv));
+extern void    do_where PROTO((int argc, const char ** argv));
+
+
+/*
+ * Global utility routines.
+ */
+extern const char *    modestring PROTO((int mode));
+extern const char *    timestring PROTO((time_t timeval));
+extern BOOL            isadir PROTO((const char * name));
+extern int             namesort PROTO((const void * p1, const void * p2));
+extern char *          getchunk PROTO((int size));
+extern char *          chunkstrdup PROTO((const char *));
+extern void            freechunks PROTO((void));
+extern int             fullWrite PROTO((int fd, const char * buf, int len));
+extern BOOL            match PROTO((const char * text, const char * pattern));
+
+extern const char *    buildname
+       PROTO((const char * dirname, const char * filename));
+
+extern BOOL    makeargs
+       PROTO((const char * cmd, int * argcptr, const char *** argvptr));
+
+extern BOOL    copyfile
+       PROTO((const char * srcname, const char * destname, BOOL setmodes));
+
+extern BOOL    makestring
+       PROTO((int argc, const char ** argv, char * buf, int buflen));
+
+extern int     expandwildcards
+       PROTO((const char *name, int maxargc, const char * retargv[]));
+
+
+
+/*
+ * Global variable to indicate that an SIGINT occurred.
+ * This is used to stop processing.
+ */
+extern BOOL    intflag;
+
+#endif
+
+/* END CODE */
diff --git a/utils.c b/utils.c
new file mode 100644 (file)
index 0000000..faef571
--- /dev/null
+++ b/utils.c
@@ -0,0 +1,703 @@
+/*
+ * Copyright (c) 1998 by David I. Bell
+ * Permission is granted to use, distribute, or modify this source,
+ * provided that this copyright notice remains intact.
+ *
+ * Utility routines.
+ */
+
+#include "sash.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <utime.h>
+
+
+typedef        struct  chunk   CHUNK;
+#define        CHUNKINITSIZE   4
+
+struct chunk   {
+       CHUNK * next;
+       char    data[CHUNKINITSIZE];    /* actually of varying length */
+};
+
+
+static CHUNK * chunklist;
+
+
+
+/*
+ * Return the standard ls-like mode string from a file mode.
+ * This is static and so is overwritten on each call.
+ */
+const char *
+modestring(mode)
+       int     mode;
+{
+       static  char    buf[12];
+
+       strcpy(buf, "----------");
+
+       /*
+        * Fill in the file type.
+        */
+       if (S_ISDIR(mode))
+               buf[0] = 'd';
+       if (S_ISCHR(mode))
+               buf[0] = 'c';
+       if (S_ISBLK(mode))
+               buf[0] = 'b';
+       if (S_ISFIFO(mode))
+               buf[0] = 'p';
+#ifdef S_ISLNK
+       if (S_ISLNK(mode))
+               buf[0] = 'l';
+#endif
+#ifdef S_ISSOCK
+       if (S_ISSOCK(mode))
+               buf[0] = 's';
+#endif
+
+       /*
+        * Now fill in the normal file permissions.
+        */
+       if (mode & S_IRUSR)
+               buf[1] = 'r';
+       if (mode & S_IWUSR)
+               buf[2] = 'w';
+       if (mode & S_IXUSR)
+               buf[3] = 'x';
+       if (mode & S_IRGRP)
+               buf[4] = 'r';
+       if (mode & S_IWGRP)
+               buf[5] = 'w';
+       if (mode & S_IXGRP)
+               buf[6] = 'x';
+       if (mode & S_IROTH)
+               buf[7] = 'r';
+       if (mode & S_IWOTH)
+               buf[8] = 'w';
+       if (mode & S_IXOTH)
+               buf[9] = 'x';
+
+       /*
+        * Finally fill in magic stuff like suid and sticky text.
+        */
+       if (mode & S_ISUID)
+               buf[3] = ((mode & S_IXUSR) ? 's' : 'S');
+       if (mode & S_ISGID)
+               buf[6] = ((mode & S_IXGRP) ? 's' : 'S');
+       if (mode & S_ISVTX)
+               buf[9] = ((mode & S_IXOTH) ? 't' : 'T');
+
+       return buf;
+}
+
+
+/*
+ * Get the time to be used for a file.
+ * This is down to the minute for new files, but only the date for old files.
+ * The string is returned from a static buffer, and so is overwritten for
+ * each call.
+ */
+const char *
+timestring(timeval)
+       time_t  timeval;
+{
+       time_t          now;
+       char *          str;
+       static  char    buf[26];
+
+       time(&now);
+
+       str = ctime(&timeval);
+
+       strcpy(buf, &str[4]);
+       buf[12] = '\0';
+
+       if ((timeval > now) || (timeval < now - 365*24*60*60L)) {
+               strcpy(&buf[7], &str[20]);
+               buf[11] = '\0';
+       }
+
+       return buf;
+}
+
+
+/*
+ * Return TRUE if a filename is a directory.
+ * Nonexistant files return FALSE.
+ */
+BOOL
+isadir(name)
+       const char *    name;
+{
+       struct  stat    statbuf;
+
+       if (stat(name, &statbuf) < 0)
+               return FALSE;
+
+       return S_ISDIR(statbuf.st_mode);
+}
+
+
+/*
+ * Copy one file to another, while possibly preserving its modes, times,
+ * and modes.  Returns TRUE if successful, or FALSE on a failure with an
+ * error message output.  (Failure is not indicted if the attributes cannot
+ * be set.)
+ */
+BOOL
+copyfile(srcname, destname, setmodes)
+       const char *    srcname;
+       const char *    destname;
+       BOOL            setmodes;
+{
+       int             rfd;
+       int             wfd;
+       int             rcc;
+       char            buf[BUFSIZE];
+       struct  stat    statbuf1;
+       struct  stat    statbuf2;
+       struct  utimbuf times;
+       
+       if (stat(srcname, &statbuf1) < 0) {
+               perror(srcname);
+
+               return FALSE;
+       }
+
+       if (stat(destname, &statbuf2) < 0) {
+               statbuf2.st_ino = -1;
+               statbuf2.st_dev = -1;
+       }
+
+       if ((statbuf1.st_dev == statbuf2.st_dev) &&
+               (statbuf1.st_ino == statbuf2.st_ino))
+       {
+               fprintf(stderr, "Copying file \"%s\" to itself\n", srcname);
+
+               return FALSE;
+       }
+
+       rfd = open(srcname, O_RDONLY);
+
+       if (rfd < 0) {
+               perror(srcname);
+
+               return FALSE;
+       }
+
+       wfd = creat(destname, statbuf1.st_mode);
+
+       if (wfd < 0) {
+               perror(destname);
+               close(rfd);
+
+               return FALSE;
+       }
+
+       while ((rcc = read(rfd, buf, sizeof(buf))) > 0) {
+               if (intflag) {
+                       close(rfd);
+                       close(wfd);
+
+                       return FALSE;
+               }
+
+               if (fullWrite(wfd, buf, rcc) < 0)
+                       goto error_exit;
+       }
+
+       if (rcc < 0) {
+               perror(srcname);
+               goto error_exit;
+       }
+
+       (void) close(rfd);
+
+       if (close(wfd) < 0) {
+               perror(destname);
+
+               return FALSE;
+       }
+
+       if (setmodes) {
+               (void) chmod(destname, statbuf1.st_mode);
+
+               (void) chown(destname, statbuf1.st_uid, statbuf1.st_gid);
+
+               times.actime = statbuf1.st_atime;
+               times.modtime = statbuf1.st_mtime;
+
+               (void) utime(destname, &times);
+       }
+
+       return TRUE;
+
+
+error_exit:
+       close(rfd);
+       close(wfd);
+
+       return FALSE;
+}
+
+
+/*
+ * Build a path name from the specified directory name and file name.
+ * If the directory name is NULL, then the original filename is returned.
+ * The built path is in a static area, and is overwritten for each call.
+ */
+const char *
+buildname(dirname, filename)
+       const char *    dirname;
+       const char *    filename;
+{
+       const char *    cp;
+       static  char    buf[PATHLEN];
+
+       if ((dirname == NULL) || (*dirname == '\0'))
+               return filename;
+
+       cp = strrchr(filename, '/');
+
+       if (cp)
+               filename = cp + 1;
+
+       strcpy(buf, dirname);
+       strcat(buf, "/");
+       strcat(buf, filename);
+
+       return buf;
+}
+
+
+/*
+ * Expand the wildcards in a filename, if any.
+ * Returns an argument list with matching filenames in sorted order.
+ * The expanded names are stored in memory chunks which can later all
+ * be freed at once.  Returns zero if the name is not a wildcard, or
+ * returns the count of matched files if the name is a wildcard and
+ * there was at least one match, or returns -1 if either no filenames
+ * matched or too many filenames matched (with an error output).
+ */
+int
+expandwildcards(name, maxargc, retargv)
+       const char *    name;
+       int             maxargc;
+       const char *    retargv[];
+{
+       const char *    last;
+       const char *    cp1;
+       const char *    cp2;
+       const char *    cp3;
+       char *          str;
+       DIR *           dirp;
+       struct dirent * dp;
+       int             dirlen;
+       int             matches;
+       char            dirname[PATHLEN];
+
+       last = strrchr(name, '/');
+
+       if (last)
+               last++;
+       else
+               last = name;
+
+       cp1 = strchr(name, '*');
+       cp2 = strchr(name, '?');
+       cp3 = strchr(name, '[');
+
+       if ((cp1 == NULL) && (cp2 == NULL) && (cp3 == NULL))
+               return 0;
+
+       if ((cp1 && (cp1 < last)) || (cp2 && (cp2 < last)) ||
+               (cp3 && (cp3 < last)))
+       {
+               fprintf(stderr, "Wildcards only implemented for last filename component\n");
+
+               return -1;
+       }
+
+       dirname[0] = '.';
+       dirname[1] = '\0';
+
+       if (last != name) {
+               memcpy(dirname, name, last - name);
+               dirname[last - name - 1] = '\0';
+
+               if (dirname[0] == '\0') {
+                       dirname[0] = '/';
+                       dirname[1] = '\0';
+               }
+       }
+
+       dirp = opendir(dirname);
+
+       if (dirp == NULL) {
+               perror(dirname);
+
+               return -1;
+       }
+
+       dirlen = strlen(dirname);
+
+       if (last == name) {
+               dirlen = 0;
+               dirname[0] = '\0';
+       } else if (dirname[dirlen - 1] != '/') {
+               dirname[dirlen++] = '/';
+               dirname[dirlen] = '\0';
+       }
+
+       matches = 0;
+
+       while ((dp = readdir(dirp)) != NULL) {
+               if ((strcmp(dp->d_name, ".") == 0) ||
+                       (strcmp(dp->d_name, "..") == 0))
+               {
+                       continue;
+               }
+
+               if (!match(dp->d_name, last))
+                       continue;
+
+               if (matches >= maxargc) {
+                       fprintf(stderr, "Too many filename matches\n");
+                       closedir(dirp);
+
+                       return -1;
+               }
+
+               str = getchunk(dirlen + strlen(dp->d_name) + 1);
+
+               if (str == NULL) {
+                       fprintf(stderr, "No memory for filename\n");
+                       closedir(dirp);
+
+                       return -1;
+               }
+
+               if (dirlen)
+                       memcpy(str, dirname, dirlen);
+
+               strcpy(str + dirlen, dp->d_name);
+
+               retargv[matches++] = str;
+       }
+
+       closedir(dirp);
+
+       if (matches == 0) {
+               fprintf(stderr, "No matches\n");
+
+               return -1;
+       }
+
+       qsort((void *) retargv, matches, sizeof(char *), namesort);
+
+       return matches;
+}
+
+
+/*
+ * Sort routine for list of filenames.
+ */
+int
+namesort(p1, p2)
+       const void *    p1;
+       const void *    p2;
+{
+       const char **   s1;
+       const char **   s2;
+
+       s1 = (const char **) p1;
+       s2 = (const char **) p2;
+
+       return strcmp(*s1, *s2);
+}
+
+
+/*
+ * Routine to see if a text string is matched by a wildcard pattern.
+ * Returns TRUE if the text is matched, or FALSE if it is not matched
+ * or if the pattern is invalid.
+ *  *          matches zero or more characters
+ *  ?          matches a single character
+ *  [abc]      matches 'a', 'b' or 'c'
+ *  \c         quotes character c
+ *  Adapted from code written by Ingo Wilken.
+ */
+BOOL
+match(text, pattern)
+       const char *    text;
+       const char *    pattern;
+{
+       const char *    retrypat;
+       const char *    retrytxt;
+       int             ch;
+       BOOL            found;
+
+       retrypat = NULL;
+       retrytxt = NULL;
+
+       while (*text || *pattern) {
+               ch = *pattern++;
+
+               switch (ch) {
+                       case '*':  
+                               retrypat = pattern;
+                               retrytxt = text;
+                               break;
+
+                       case '[':  
+                               found = FALSE;
+
+                               while ((ch = *pattern++) != ']') {
+                                       if (ch == '\\')
+                                               ch = *pattern++;
+
+                                       if (ch == '\0')
+                                               return FALSE;
+
+                                       if (*text == ch)
+                                               found = TRUE;
+                               }
+
+                               if (!found) {
+                                       pattern = retrypat;
+                                       text = ++retrytxt;
+                               }
+
+                               /* fall into next case */
+
+                       case '?':  
+                               if (*text++ == '\0')
+                                       return FALSE;
+
+                               break;
+
+                       case '\\':  
+                               ch = *pattern++;
+
+                               if (ch == '\0')
+                                       return FALSE;
+
+                               /* fall into next case */
+
+                       default:        
+                               if (*text == ch) {
+                                       if (*text)
+                                               text++;
+                                       break;
+                               }
+
+                               if (*text) {
+                                       pattern = retrypat;
+                                       text = ++retrytxt;
+                                       break;
+                               }
+
+                               return FALSE;
+               }
+
+               if (pattern == NULL)
+                       return FALSE;
+       }
+
+       return TRUE;
+}
+
+
+/*
+ * Take a command string, and break it up into an argc, argv list.
+ * The returned argument list and strings are in static memory, and so
+ * are overwritten on each call.  The argument array is ended with an
+ * extra NULL pointer for convenience.  Returns TRUE if successful,
+ * or FALSE on an error with a message already output.
+ */
+BOOL
+makeargs(cmd, argcptr, argvptr)
+       const char *    cmd;
+       int *           argcptr;
+       const char ***  argvptr;
+{
+       char *                  cp;
+       int                     argc;
+       static char             strings[CMDLEN+1];
+       static const char *     argtable[MAXARGS+1];
+
+       /*
+        * Copy the command string and then break it apart
+        * into separate arguments.
+        */
+       strcpy(strings, cmd);
+       argc = 0;
+       cp = strings;
+
+       while (*cp) {
+               if (argc >= MAXARGS) {
+                       fprintf(stderr, "Too many arguments\n");
+
+                       return FALSE;
+               }
+
+               argtable[argc++] = (const char *) cp;
+
+               while (*cp && !isblank(*cp))
+                       cp++;
+
+               while (isblank(*cp))
+                       *cp++ = '\0';
+       }
+
+       argtable[argc] = NULL;
+
+       *argcptr = argc;
+       *argvptr = argtable;
+
+       return TRUE;
+}
+
+
+/*
+ * Make a NULL-terminated string out of an argc, argv pair.
+ * Returns TRUE if successful, or FALSE if the string is too long,
+ * with an error message given.  This does not handle spaces within
+ * arguments correctly.
+ */
+BOOL
+makestring(argc, argv, buf, buflen)
+       int             argc;
+       const char **   argv;
+       char *          buf;
+       int             buflen;
+{
+       int     len;
+
+       while (argc-- > 0) {
+               len = strlen(*argv);
+
+               if (len >= buflen) {
+                       fprintf(stderr, "Argument string too long\n");
+
+                       return FALSE;
+               }
+
+               strcpy(buf, *argv++);
+
+               buf += len;
+               buflen -= len;
+
+               if (argc)
+                       *buf++ = ' ';
+
+               buflen--; 
+       }
+
+       *buf = '\0';
+
+       return TRUE;
+}
+
+
+/*
+ * Allocate a chunk of memory (like malloc).
+ * The difference, though, is that the memory allocated is put on a
+ * list of chunks which can be freed all at one time.  You CAN NOT free
+ * an individual chunk.
+ */
+char *
+getchunk(size)
+       int     size;
+{
+       CHUNK * chunk;
+
+       if (size < CHUNKINITSIZE)
+               size = CHUNKINITSIZE;
+
+       chunk = (CHUNK *) malloc(size + sizeof(CHUNK) - CHUNKINITSIZE);
+
+       if (chunk == NULL)
+               return NULL;
+
+       chunk->next = chunklist;
+       chunklist = chunk;
+
+       return chunk->data;
+}
+
+
+/*
+ * Duplicate a string value using the chunk allocator.
+ * The returned string cannot be individually freed, but can only be freed
+ * with other strings when freechunks is called.  Returns NULL on failure.
+ */
+char *
+chunkstrdup(str)
+       const char *    str;
+{
+       int     len;
+       char *  newstr;
+
+       len = strlen(str) + 1;
+       newstr = getchunk(len);
+
+       if (newstr)
+               memcpy(newstr, str, len);
+
+       return newstr;
+}
+
+
+/*
+ * Free all chunks of memory that had been allocated since the last
+ * call to this routine.
+ */
+void
+freechunks()
+{
+       CHUNK * chunk;
+
+       while (chunklist) {
+               chunk = chunklist;
+               chunklist = chunk->next;
+               free((char *) chunk);
+       }
+}
+
+
+/*
+ * Write all of the supplied buffer out to a file.
+ * This does multiple writes as necessary.
+ * Returns the amount written, or -1 on an error.
+ */
+int
+fullWrite(fd, buf, len)
+       int             fd;
+       const char *    buf;
+       int             len;
+{
+       int     cc;
+       int     total;
+
+       total = 0;
+
+       while (len > 0) {
+               cc = write(fd, buf, len);
+
+               if (cc < 0)
+                       return -1;
+
+               buf += cc;
+               total+= cc;
+               len -= cc;
+       }
+
+       return total;
+}
+
+/* END CODE */