--- /dev/null
+#
+# 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
--- /dev/null
+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
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+.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
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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, ×);
+ }
+
+ 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 */