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