]> code.bitgloo.com Git - clyne/stmos.git/commitdiff
wip forking, added sash source
authortcsullivan <tullivan99@gmail.com>
Thu, 18 Oct 2018 22:24:43 +0000 (18:24 -0400)
committertcsullivan <tullivan99@gmail.com>
Thu, 18 Oct 2018 22:24:43 +0000 (18:24 -0400)
25 files changed:
src/kernel/heap.c
src/kernel/heap.h
src/kernel/init.c
src/kernel/svc.c
src/kernel/task.c
src/sash/CHANGES [new file with mode: 0644]
src/sash/Makefile [new file with mode: 0644]
src/sash/README [new file with mode: 0644]
src/sash/cmd_ar.c [new file with mode: 0644]
src/sash/cmd_chattr.c [new file with mode: 0644]
src/sash/cmd_dd.c [new file with mode: 0644]
src/sash/cmd_ed.c [new file with mode: 0644]
src/sash/cmd_file.c [new file with mode: 0644]
src/sash/cmd_find.c [new file with mode: 0644]
src/sash/cmd_grep.c [new file with mode: 0644]
src/sash/cmd_gzip.c [new file with mode: 0644]
src/sash/cmd_ls.c [new file with mode: 0644]
src/sash/cmd_tar.c [new file with mode: 0644]
src/sash/cmds.c [new file with mode: 0644]
src/sash/dirent.h [new file with mode: 0644]
src/sash/sash.1 [new file with mode: 0644]
src/sash/sash.c [new file with mode: 0644]
src/sash/sash.h [new file with mode: 0644]
src/sash/utils.c [new file with mode: 0644]
src/user/user.c

index ff5dd9e876ccdea147de9aa642979fc4722d3971..123f7e62ad9af2c5bce8eea3441839823974821a 100644 (file)
 
 #define HEAP_ALIGN 4
 
-typedef struct {
-       uint32_t size;
-       void *next;
-} __attribute__ ((packed)) alloc_t;
-
 static alloc_t *free_blocks;
 static void *heap_end;
 
index c817e4d7a7385d266d98079513d4ded22601182e..82d057e032c298ee4743897eb196533a45227151 100644 (file)
 
 #include <stdint.h>
 
+typedef struct {
+       uint32_t size;
+       void *next;
+} __attribute__ ((packed)) alloc_t;
+
 /**
  * Initializes memory management of the given heap.
  * No overflow stuff is done, so... be careful.
index ca60c3ec37f92c349c4b7086482e192c24836490..474ef8a78a1e1c87b3570facb6fa5674b79e4996 100644 (file)
@@ -56,5 +56,5 @@ void init_idle(void)
        task_start(user_main, 4096);\r
 \r
        while (1)\r
-               delay(100);\r
+               delay(10);\r
 }\r
index 62324f73e2bd3a11cd063ceed6c60e6002e6ddfc..edf2b68f9f1f92d1a97ba35ad54a62c211f90f58 100644 (file)
@@ -26,6 +26,7 @@
 
 extern void gpio_svc(uint32_t *);
 extern void clock_svc(uint32_t *);
+extern void task_svc(uint32_t *);
 
 void svc_handler(uint32_t *args)
 {
@@ -41,6 +42,11 @@ void svc_handler(uint32_t *args)
                break;
        case 2:
                clock_svc(args);
+               break;
+       case 3:
+               task_svc(args);
+               asm("mov r0, %0" :: "r" (args[0]));
+               break;
        default:
                break;
        }
index 86ba766a24355a2259d61f9a3c0e30784e1ac08a..96051a932d4bda3b288d9e0396028eadea91c606 100644 (file)
 task_t *current, *prev;
 static uint8_t task_disable = 0;
 
+int task_fork(uint32_t sp);
+void task_svc(uint32_t *args)
+{
+       int result = task_fork(args[0]);
+       args[0] = result;
+}
+
 void task_hold(uint8_t hold)
 {
        if (hold != 0)
@@ -119,11 +126,12 @@ void task_init(void (*init)(void), uint16_t stackSize)
 
        // bit 0 - priv, bit 1 - psp/msp
        asm("\
+               isb; \
+               cpsie i; \
                mov r0, sp; \
                msr psp, r0; \
                mrs r0, control; \
                orr r0, r0, #3; \
-               cpsie i; \
                msr control, r0; \
        ");
 
@@ -140,31 +148,41 @@ void task_start(void (*task)(void), uint16_t stackSize)
        task_hold(0);
 }
 
-/*int fork_ret(void)
+int task_fork_ret(void)
 {
-       return 1;
+       return 0;
 }
 
-int fork(void)
+// Return 0 for child, non-zero for parent
+int task_fork(uint32_t sp)
 {
-       void (*pc)(void) = (void (*)(void))((uint32_t)fork_ret & ~(3));
        task_hold(1);
 
-       // duplicate task info
-       alloc_t *heapInfo = (alloc_t *)(current->stack - 2);
-       task_t *t = task_create(pc, heapInfo->size);
-       memcpy(t->stack, current->stack, heapInfo->size);
-       uint32_t *sp;
-       asm("mov %0, sp" : "=r" (sp));
-       t->sp = t->stack + (sp - current->stack);
+       // 1. Get a PC for the child
+       void (*pc)(void) = (void (*)(void))((uint32_t)task_fork_ret);
 
-       t->next = current->next;
-       current->next = t;
-       current = t;
+       // 2. Prepare child task
+       alloc_t *stackInfo = (alloc_t *)(((uint8_t *)current->stack)
+               - sizeof(alloc_t));
+       task_t *childTask = task_create(pc, stackInfo->size - 8);
+       for (uint32_t i = 0; i < (stackInfo->size - 8); i++)
+               childTask->stack[i] = current->stack[i];
+
+       //uint32_t *sp;
+       //asm("mov %0, sp" : "=r" (sp));
+       childTask->sp = (uint32_t *)((uint32_t)childTask->stack + (sp
+               - (uint32_t)current->stack));
+
+       // 3. Insert child into task chain
+       childTask->next = current->next;
+       current->next = childTask;
+
+       // 4. Re-enable scheduler, make change happen
        task_hold(0);
+
        SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
-       return 0;
-}*/
+       return 1;
+}
 
 __attribute__ ((naked))
 void PendSV_Handler(void) 
diff --git a/src/sash/CHANGES b/src/sash/CHANGES
new file mode 100644 (file)
index 0000000..c87d6d5
--- /dev/null
@@ -0,0 +1,31 @@
+These are the major changes from version 3.7 to version 3.8:
+
+The Makefile has been updated for several distribution's standards.
+The ext2_fs include file location has been changed.
+Some compiler warnings were fixed.
+
+The -ls command has the -n option to print numeric user and group ids.
+
+The -n option might be needed in case the unsuppressable dynamic
+linking used to lookup the names fails.
+
+The -chroot, -pivot_root, and -losetup commands have been added.
+
+The exit status for commands has been implemented (such as -exit).
+Thanks to Tollef Fog Heen for the patches.
+
+
+These are the major changes from version 3.6 to version 3.7:
+
+A few bugs in the dd command have been fixed.
+
+
+These are the major changes from version 3.5 to version 3.6:
+
+The -mount and -umount commands have been modified to work for both
+Linux and BSD systems.  Thanks to Wilbern Cobb for the patches.
+
+The -e and -s options for the -mount command have been added.
+
+The -f option was added to the command line so that script files
+using sash can be executed.
diff --git a/src/sash/Makefile b/src/sash/Makefile
new file mode 100644 (file)
index 0000000..02a895a
--- /dev/null
@@ -0,0 +1,69 @@
+#
+# Makefile for sash
+#
+# The HAVE_GZIP definition adds the -gzip and -gunzip commands.
+# The HAVE_LINUX_ATTR definition adds the -chattr and -lsattr commands.
+# The HAVE_LINUX_CHROOT definition adds the -chroot command.
+# The HAVE_LINUX_PIVOT definition adds the -pivot_root command.
+# The HAVE_LINUX_LOSETUP definition adds the -losetup command.
+# The HAVE_LINUX_MOUNT definition makes -mount and -umount work on Linux.
+# The HAVE_BSD_MOUNT definition makes -mount and -umount work on BSD.
+# The MOUNT_TYPE definition sets the default file system type for -mount.
+#
+# Note that the linker may show warnings about 'statically linked
+# programs' requiring getpwnam, getpwuid, getgrnam and getgrgid.
+# This is unavoidable since those routines use dynamic libraries anyway.
+# Sash will still run, but if there are shared library problems then
+# the user might have to be be careful when using the -chown, -chgrp,
+# and -ls commands.
+#
+
+CC=arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb
+
+#HAVE_GZIP             = 1
+#HAVE_LINUX_ATTR       = 1
+#HAVE_LINUX_CHROOT     = 1
+#HAVE_LINUX_LOSETUP    = 1
+#HAVE_LINUX_PIVOT      = 1
+#HAVE_LINUX_MOUNT      = 1
+#HAVE_BSD_MOUNT                = 0
+MOUNT_TYPE             = '""'
+
+OPT = -O3 -ffreestanding
+
+CFLAGS = $(OPT) -Wall -Wmissing-prototypes \
+       -DMOUNT_TYPE=$(MOUNT_TYPE) #\
+       -DHAVE_GZIP=$(HAVE_GZIP) \
+       -DHAVE_LINUX_ATTR=$(HAVE_LINUX_ATTR) \
+       -DHAVE_LINUX_CHROOT=$(HAVE_LINUX_CHROOT) \
+       -DHAVE_LINUX_LOSETUP=$(HAVE_LINUX_LOSETUP) \
+       -DHAVE_LINUX_PIVOT=$(HAVE_LINUX_PIVOT) \
+       -DHAVE_LINUX_MOUNT=$(HAVE_LINUX_MOUNT) \
+       -DHAVE_BSD_MOUNT=$(HAVE_BSD_MOUNT) \
+
+#LDFLAGS = -static
+#LIBS = -lz
+
+
+DESTDIR =
+BINDIR = /bin
+MANDIR = /usr/man
+
+
+#OBJS = sash.o cmds.o cmd_dd.o cmd_ed.o cmd_grep.o cmd_ls.o cmd_tar.o \
+       cmd_gzip.o cmd_find.o cmd_file.o cmd_chattr.o cmd_ar.o utils.o
+OBJS = sash.o cmds.o cmd_ls.o utils.o
+
+
+sash:  $(OBJS)
+       $(CC) $(LDFLAGS) -o sash $(OBJS) $(LIBS)
+       arm-none-eabi-strip sash
+
+clean:
+       rm -f $(OBJS) sash
+
+install: sash
+       cp sash $(DESTDIR)/$(BINDIR)/sash
+       cp sash.1 $(DESTDIR)/$(MANDIR)/man1/sash.1
+
+$(OBJS):       sash.h
diff --git a/src/sash/README b/src/sash/README
new file mode 100644 (file)
index 0000000..154f1f7
--- /dev/null
@@ -0,0 +1,17 @@
+This is release 3.8 of sash, my stand-alone shell for Linux or other systems.
+
+The purpose of this program is to make system recovery possible in
+many cases where there are missing shared libraries or executables.
+It does this by firstly being linked statically, and secondly by
+including versions of 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.
+
+Several options in the Makefile can be changed to build sash for
+other UNIX-like systems.  In particular, dependencies on Linux file
+systems can be removed and the mount command can be configured.
+
+David I. Bell
+dbell@tip.net.au
+8 March 2014
diff --git a/src/sash/cmd_ar.c b/src/sash/cmd_ar.c
new file mode 100644 (file)
index 0000000..6c6181e
--- /dev/null
@@ -0,0 +1,1019 @@
+/*
+ * Original:
+ * Copyright (c) 1999 by Aaron R. Crane.
+ * Permission is granted to use, distribute, or modify this source,
+ * provided that this copyright notice remains intact.
+ *
+ * Modified:
+ * Copyright (c) 2014 by David I. Bell
+ * Permission is granted to use, distribute, or modify this source,
+ * provided that this copyright notice remains intact.
+ *
+ * The "ar" built-in command.
+ * This allows extraction and listing of ar files.
+ */
+
+#include <ar.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <limits.h>
+
+#include "sash.h"
+
+
+/*
+ * Structure to hold information about the archive file.
+ */
+typedef struct
+{
+       int     fd;             /* file reading archive from */
+       BOOL    eof;            /* end of file has been seen */
+       BOOL    rescan;         /* rescan the header just read */
+       char *  nameTable;      /* long name table */
+
+       /*
+        * Information about the current file read from the archive.
+        * This is extracted from the latest member header read.
+        */
+       char *  name;           /* current file name */
+       time_t  date;           /* date of file */
+       uid_t   uid;            /* user id */
+       gid_t   gid;            /* group id */
+       mode_t  mode;           /* file protection */
+       off_t   size;           /* file size */
+       int     pad;            /* padding to next header */
+} Archive;
+
+
+/*
+ * Local procedures.
+ */
+static void    initArchive(Archive * arch);
+static BOOL    openArchive(const char * name, Archive * arch);
+static void    closeArchive(Archive * arch);
+static BOOL    readSpecialMember(Archive * arch);
+static BOOL    readNormalMember(Archive * arch);
+static BOOL    readMember(Archive * arch, struct ar_hdr * hdr);
+static BOOL    skipMember(const Archive * arch);
+static BOOL    skipPadding(int fd, int pad);
+static BOOL    writeFile(const Archive * arch, int outfd);
+static int     createFile(const Archive * arch);
+static BOOL    canonicalize(Archive * arch, const struct ar_hdr * hdr);
+static void    listMember(const Archive * arch);
+static int     shortNameMatches44BSD(const char * name);
+static int     shortNameMatchesSysV(const char * name);
+
+static BOOL    wantMember(const Archive * arch, int n_names,
+                       const char ** names);
+
+static BOOL    getNumber(const char * s, unsigned base, int max_digits,
+                       unsigned long * ul);
+
+
+int
+do_ar(int argc, const char ** argv)
+{
+       const char *    options;
+       const char *    archiveName;
+       BOOL            doExtract;
+       BOOL            doTable;
+       BOOL            doPrint;
+       BOOL            verbose;
+       int             r;
+       Archive         arch;
+
+       r = 0;
+       verbose = FALSE;
+       doExtract = FALSE;
+       doTable = FALSE;
+       doPrint = FALSE;
+
+       if (argc < 3)
+       {
+               fprintf(stderr, "Too few arguments for ar\n");
+
+               return 1;
+       }
+
+       /*
+        * Get the option string and archive file name.
+        */
+       options = argv[1];
+       archiveName = argv[2];
+
+       /*
+        * Advance the arguments to the list of file names (if any).
+        */
+       argc -= 3;
+       argv += 3;
+
+       /*
+        * Parse the option characters.
+        */
+       for (; *options; options++)
+       {
+               switch (*options)
+               {
+               case 't':
+                       doTable = TRUE;
+                       break;
+
+               case 'x':
+                       doExtract = TRUE;
+                       break;
+
+               case 'p':
+                       doPrint = TRUE;
+                       break;
+
+               case 'v':
+                       verbose = TRUE;
+                       break;
+
+               case 'd': case 'm': case 'q': case 'r':
+                       fprintf(stderr, "Writing ar files is not supported\n");
+
+                       return 1;
+
+               default:
+                       fprintf(stderr, "Unknown ar flag: %c\n", *options);
+
+                       return 1;
+               }
+       }
+
+       if (doExtract + doTable + doPrint != 1)
+       {
+               fprintf(stderr,
+                       "Exactly one of 'x', 'p' or 't' must be specified\n");
+
+               return 1;
+       }
+
+       /*
+        * Open the archive file.
+        */
+       initArchive(&arch);
+
+       if (!openArchive(archiveName, &arch))
+               return 1;
+
+       /*
+        * Read the first special member of the archive.
+        */
+       if (!readSpecialMember(&arch))
+               return 1;
+
+       /*
+        * Read all of the normal members of the archive.
+        */
+       while (readNormalMember(&arch))
+       {
+               /*
+                * If this file is not wanted then skip it.
+                */
+               if (!wantMember(&arch, argc, argv))
+               {
+                       if (!skipMember(&arch))
+                               break;
+
+                       continue;
+               }
+
+               /*
+                * This file is wanted.
+                */
+               if (doTable)
+               {
+                       if (verbose)
+                               listMember(&arch);
+                       else
+                               puts(arch.name);
+
+                       if (!skipMember(&arch))
+                               break;
+               }
+               else if (doPrint)
+               {
+                       if (verbose)
+                       {
+                               /*
+                                * The verbose format makes me gag,
+                                * but 4.4BSD, GNU and even V7 all
+                                * have the same lossage.
+                                */
+                               printf("\n<%s>\n\n", arch.name);
+                               fflush(stdout);
+                       }
+
+                       if (!writeFile(&arch, STDOUT))
+                               break;
+               }
+               else if (doExtract)
+               {
+                       int     outfd;
+                       BOOL    success;
+
+                       if (verbose)
+                               printf("x - %s\n", arch.name);
+
+                       outfd = createFile(&arch);
+
+                       if (outfd == -1)
+                               break;
+
+                       success = writeFile(&arch, outfd);
+
+                       if (close(outfd) == -1)
+                       {
+                               fprintf(stderr, "Can't close %s: %s\n",
+                                       arch.name, strerror(errno));
+
+                               break;
+                       }
+
+                       if (!success)
+                       {
+                               r = 1;
+                               break;
+                       }
+               }
+               else
+               {
+                       fprintf(stderr, "Oops -- I don't know what to do\n");
+                       r = 1;
+                       break;
+               }
+       }
+
+       closeArchive(&arch);
+
+       return r;
+}
+
+
+/*
+ * Open the file for writing for the specified archive structure,
+ * setting its mode and owner if possible.  Returns the file handle
+ * of the opened file, or -1 on an error.
+ */
+static int
+createFile(const Archive * arch)
+{
+       int     fd;
+
+       fd = open(arch->name, O_WRONLY | O_CREAT | O_TRUNC, arch->mode);
+
+       if (fd == -1)
+       {
+               fprintf(stderr, "Can't create \"%s\": %s\n",
+                       arch->name, strerror(errno));
+
+               return -1;
+       }
+
+       /*
+        * Don't worry if these fail.  We have to do the fchmod() despite
+        * specifying the mode in the open() call, because that mode is
+        * munged by the umask.
+        */
+       checkStatus("fchmod", fchmod(fd, arch->mode));
+       checkStatus("fchown", fchown(fd, arch->uid, arch->gid));
+
+       return fd;
+}
+
+
+/*
+ * Return whether the current archive member is wanted.
+ * This means that the file name is contained in the specified list of
+ * file names, or else that the specified list of file names is empty.
+ */
+static BOOL
+wantMember(const Archive * arch, int n_names, const char ** name)
+{
+       int     i;
+
+       /*
+        * If there are no names then all archive members are wanted.
+        */
+       if (n_names == 0)
+               return TRUE;
+
+       /*
+        * See if the member file name is contained in the list.
+        */
+       for (i = 0; i < n_names; i++)
+       {
+               if (strcmp(arch->name, name[i]) == 0)
+                       return TRUE;
+       }
+
+       return FALSE;
+}
+
+
+/*
+ * Parse a number from the specified string in the specified base.
+ * The number is terminated by a null, space, or the specified number
+ * of digits.  The number is returned through the supplied pointer.
+ * Only non-negatives numbers are parsed.  Returns TRUE on success.
+ */
+static BOOL
+getNumber(const char * s, unsigned base, int max_digits, unsigned long * ul)
+{
+       const char *    p;
+       const char *    endp;
+       unsigned long   cutoff;
+       unsigned long   cutlim;
+
+       if (base < 2 || base > 10  ||  s == 0 || *s == 0  ||  ul == 0)
+               return FALSE;
+
+       cutoff = ULONG_MAX / (unsigned long) base;
+       cutlim = ULONG_MAX % (unsigned long) base;
+       *ul = 0;
+
+       endp = (max_digits >= 0) ? s + max_digits : 0;
+
+       for (p = s;  endp ? p < endp : 1;  p++)
+       {
+               unsigned d;
+
+               if (*p == 0 || *p == ' ')
+                       break;  /* end of string */
+
+               if (!isDecimal(*p))
+                       return FALSE; /* non-digit */
+
+               d = *p - '0';
+
+               if (d >= base)
+                       return FALSE; /* digit outside range for base */
+
+               if (*ul > cutoff || (*ul == cutoff && d > cutlim))
+                       return FALSE; /* overflow */
+
+               *ul *= base;
+               *ul += d;
+       }
+
+       if (p == s)
+               return FALSE;   /* nothing was converted */
+
+       if (*p && *p != ' ')
+               return FALSE;   /* trailing garbage */
+
+       return TRUE;
+}
+
+
+/*
+ * Initialise the specified Archive structure for use.
+ */
+static void
+initArchive(Archive * arch)
+{
+       arch->fd = -1;
+       arch->name = 0;
+       arch->nameTable = 0;
+       arch->eof = FALSE;
+       arch->rescan = FALSE;
+}
+
+
+/*
+ * Open the specified archive file name and read the header from it.
+ * The file handle is saved in the Archive structure for further use.
+ * Returns TRUE on success.
+ */
+static BOOL
+openArchive(const char * name, Archive * arch)
+{
+       unsigned char   buf[SARMAG];
+       ssize_t         cc;
+
+       arch->fd = open(name, O_RDONLY);
+
+       if (arch->fd == -1)
+       {
+               fprintf(stderr, "Can't open archive file %s: %s\n",
+                       name, strerror(errno));
+
+               return FALSE;
+       }
+
+       cc = read(arch->fd, buf, SARMAG);
+
+       if (cc == -1)
+       {
+               fprintf(stderr, "Error reading archive header: %s\n",
+                       strerror(errno));
+
+               goto close_and_out;
+       } 
+
+       if (cc != SARMAG)
+       {
+               fprintf(stderr, "Short read of archive header\n");
+
+               goto close_and_out;
+       }
+
+       if (memcmp(buf, ARMAG, SARMAG))
+       {
+               fprintf(stderr, "Invalid archive header\n");
+
+               goto close_and_out;
+       }
+
+       return TRUE;
+
+
+       /*
+        * Here on an error to clean up.
+        */
+close_and_out:
+       (void) close(arch->fd);
+       arch->fd = -1;
+
+       return FALSE;
+}
+
+
+/*
+ * Close the archive file.
+ */
+static void
+closeArchive(Archive * arch)
+{
+       free(arch->name);
+       arch->name = 0;
+
+       free(arch->nameTable);
+       arch->nameTable = 0;
+
+       if (arch->fd >= 0)
+               (void) close(arch->fd);
+
+       arch->fd = -1;
+}
+
+
+/*
+ * Read an archive member header into the specified structure.
+ * Returns TRUE on success.  On failure, the eof flag is set if
+ * the end of file had been reached.
+ */
+static BOOL
+readMember(Archive * arch, struct ar_hdr * hdr)
+{
+       ssize_t cc;
+
+       cc = read(arch->fd, hdr, sizeof(*hdr));
+
+       if (cc < 0)
+       {
+               fprintf(stderr, "Error reading member header: %s\n",
+                       strerror(errno));
+
+               return FALSE;
+       }
+
+       if (cc == 0)
+       {
+               arch->eof = TRUE;
+
+               return FALSE;
+       }
+
+       if (cc != sizeof(*hdr))
+       {
+               fprintf(stderr, "Short read of member header\n");
+
+               return FALSE;
+       }
+
+       if (memcmp(hdr->ar_fmag, ARFMAG, sizeof(hdr->ar_fmag)))
+       {
+               fprintf(stderr, "Invalid member header\n");
+
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+
+/*
+ * Check the member file name and see if it matches the 4.4 BSD
+ * syntax for long file names.  If so, return the number of characters
+ * of the actual long file name.  Returns -1 on an error.
+ */
+static int
+shortNameMatches44BSD(const char * name)
+{
+       const char *    p;
+       unsigned long   ul;
+
+       if (strncmp(name, "#1/", 3) != 0)
+               return -1;
+
+       if (!isDecimal(name[3]))
+               return -1;
+
+       for (p = name + 4;  *p;  p++)
+       {
+               if (!isDecimal(*p))
+                       break;
+       }
+
+       while (*p)
+       {
+               if (*p++ != ' ')
+                       return -1;
+       }
+
+       if (!getNumber(name + 3, 10, -1, &ul))
+               return -1;
+
+       if (ul == 0)            /* broken archive */
+               return -1;
+
+       return ul;
+}
+
+
+/*
+ * Check the member file name and see if it matches the SYS V syntax
+ * for long file names.  If so, return the number of characters of the
+ * actual long file name.  Returns -1 on an error.
+ */
+static int
+shortNameMatchesSysV(const char * name)
+{
+       const char *    p;
+       unsigned long   ul;
+
+       /* "^/(\d+) *$" */
+       if (name[0] != '/')
+               return -1;
+
+       if (!isDecimal(name[1]))
+               return -1;
+
+       for (p = name + 2;  *p;  p++)
+       {
+               if (!isDecimal(*p))
+                       break;
+       }
+
+       while (*p)
+       {
+               if (*p++ != ' ')
+                       return -1;
+       }
+
+       if (!getNumber(name + 1, 10, -1, &ul))
+               return -1;
+
+       return ul;
+}
+
+
+#define MEMB_NAME_ALLOC(n)                                     \
+       do                                                      \
+       {                                                       \
+               arch->name = malloc(n);                         \
+               if (!arch->name)                                \
+               {                                               \
+                       fprintf(stderr, "Out of memory\n");     \
+                       return FALSE;                           \
+               }                                               \
+       } while (0);
+
+
+/*
+ * Examine the archive structure that was read and fill in the
+ * current member data with the extracted information.  This handles
+ * various types of archive headers.  This can read data from the
+ * archive file to obtain a long file name.  Returns TRUE on success.
+ */
+static BOOL
+canonicalize(Archive * arch, const struct ar_hdr * hdr)
+{
+       char            buf[sizeof(hdr->ar_name) + 1];
+       int             n;
+       unsigned long   ul;
+       unsigned long   bsd_len;
+
+       bsd_len = 0;
+
+       free(arch->name);
+       arch->name = 0;
+
+       strncpy(buf, hdr->ar_name, sizeof(hdr->ar_name));
+       buf[sizeof(hdr->ar_name)] = 0;
+
+       /*
+        * 1. If shortname matches "^#1/(\d+) *$", then it's a 4.4BSD
+        *    longname.  Read a longname of $1 bytes from ARCH->fd, or
+        *    return FALSE if impossible.
+        */
+       if ((n = shortNameMatches44BSD(buf)) != -1)
+       {
+               /* N is the length of the longname */
+               ssize_t cc;
+
+               bsd_len = n;
+
+               MEMB_NAME_ALLOC(n + 1);
+
+               cc = read(arch->fd, arch->name, n);
+
+               if (cc == -1)
+               {
+                       fprintf(stderr, "Error reading longname: %s\n",
+                               strerror(errno));
+
+                       goto free_and_out;
+               }
+
+               if (cc != n)
+               {
+                       fprintf(stderr, "Unexpected end of file in longname\n");
+
+                       goto free_and_out;
+               }
+
+               arch->name[n] = 0;
+       }
+
+       /*
+        * 2. Otherwise, if shortname matches "^/(\d+) *$", then it's a SysV
+        *    longname.  Get the longname from the nameTable, or return FALSE
+        *    if there is none.
+        */
+       else if ((n = shortNameMatchesSysV(buf)) != -1)
+       {
+               /*
+                * N is the index of the longname
+                */
+               const char *    longname;
+               const char *    p;
+               size_t          len;
+
+               if (n >= strlen(arch->nameTable))
+               {
+                       fprintf(stderr, "Longname index too large\n");
+
+                       return FALSE;
+               }
+
+               longname = arch->nameTable + n;
+
+               p = strchr(longname, '/');
+
+               if (!p)
+               {
+                       fprintf(stderr, "Bad longname index\n");
+
+                       return FALSE;
+               }
+
+               if (p[1] != '\n')
+               {
+                       fprintf(stderr, "Malformed longname table\n");
+
+                       return FALSE;
+               }
+
+               len = p - longname;
+               MEMB_NAME_ALLOC(len + 1);
+               strncpy(arch->name, longname, len);
+               arch->name[len] = '\0';
+       }
+
+       /*
+        * 3. Otherwise, it's just a shortname.  If the shortname contains a
+        *    slash, then the name terminates before the slash; otherwise,
+        *    the name terminates at the first space, or at the end of the
+        *    field if there is none. */
+       else
+       {
+               const char *    p;
+               size_t          len;
+
+               p = strchr(buf, '/');
+
+               if (!p)
+                       p = strchr(buf, ' ');
+
+               if (p)
+                       len = p - buf;
+               else
+                       len = sizeof(hdr->ar_name);
+
+               MEMB_NAME_ALLOC(len + 1);
+               strncpy(arch->name, buf, len);
+               arch->name[len] = 0;
+       }
+
+       /*
+        * 4. Parse the remaining fields of the header.  Return FALSE if any
+        *    are missing or ill-formed.
+        */
+#define FIELD(AFIELD, MFIELD, base)                                    \
+       if (!getNumber(hdr->AFIELD, base, sizeof(hdr->AFIELD), &ul))    \
+       {                                                               \
+               fprintf(stderr, "Malformed archive member header\n");   \
+               goto free_and_out;                                      \
+       }                                                               \
+       arch->MFIELD = ul;
+
+       FIELD(ar_date, date, 10);
+       FIELD(ar_uid,  uid,  10);
+       FIELD(ar_gid,  gid,  10);
+       FIELD(ar_mode, mode,  8);
+       FIELD(ar_size, size, 10);
+#undef FIELD
+
+       /*
+        * 4a. Decide whether a pad byte will be present.u
+        *
+        * The 4.4BSD format is really broken and needs a whole pile of
+        * cruft to deal with it.  There are several cases:
+        *
+        * 1. Even namelen, even memberlen: no pad.
+        * 2. Even namelen, odd memberlen: pad.  Just like SysV.
+        * 3. Odd namelen, even memberlen: pad.  Cruft.
+        * 4. Odd namelen, odd memberlen: no pad.  Cruft.
+        *
+        * Essentially, whenever the namelen is odd, the naive determination
+        * of whether a pad is needed is reversed.
+        */
+       if (!bsd_len)
+               arch->pad = (arch->size % 2) ? 1 : 0;
+       else
+       {
+               arch->size -= bsd_len;
+               arch->pad = (arch->size % 2) ? 1 : 0;
+
+               if (bsd_len % 2)
+                       arch->pad = !arch->pad;
+       }
+
+       /*
+        * 5. Everything was successful.
+        */
+       return TRUE;
+
+
+       /*
+        * 5a. Error exit -- free memory.
+        */
+free_and_out:
+       free(arch->name);
+       arch->name = 0;
+
+       return FALSE;
+}
+
+
+/*
+ * Skip the padding character if required to position to the
+ * beginning of the next member header.  This is done if the
+ * padding value is nonzero.  Returns TRUE on success.
+ */
+static BOOL
+skipPadding(int fd, int pad)
+{
+       if (pad)
+       {
+               if (lseek(fd, 1, SEEK_CUR) == -1)
+               {
+                       fprintf(stderr, "Can't skip pad byte: %s\n",
+                               strerror(errno));
+
+                       return FALSE;
+               }
+       }
+
+       return TRUE;
+}
+
+
+/*
+ * Read the first member of the archive file and check whether it
+ * is a special one, and if so, handle it.  If the first member is
+ * a normal archive member, then set up to rescan it for the next
+ * readNormalMember call.  Returns TRUE on success.
+ */
+static BOOL
+readSpecialMember(Archive * arch)
+{
+       struct ar_hdr   hdr;
+
+       /*
+        * 1. Read a header H.  Fail if impossible.
+        */
+       if (!readMember(arch, &hdr))
+               return FALSE;
+
+       /*
+        * 2. If H is a symbol table, ditch it.
+        * Fail if impossible.
+        */
+       if ((strncmp(hdr.ar_name, "/ ", 2) == 0) ||
+               (strncmp(hdr.ar_name, "__.SYMTAB       ",
+                       sizeof(hdr.ar_name)) == 0))
+       {
+               if (!canonicalize(arch, &hdr))
+                       return FALSE;
+
+               return skipMember(arch);
+       }
+
+       /*
+        * 3. If H is a SysV longname table, read it into ARCH.
+        */
+       if (strncmp(hdr.ar_name, "//", 2) == 0)
+       {
+               unsigned long   len;
+               ssize_t         cc;
+
+               if (!getNumber(hdr.ar_size, 10, sizeof(hdr.ar_size), &len))
+               {
+                       fprintf(stderr, "Invalid name-table size\n");
+
+                       return FALSE;
+               }
+
+               arch->nameTable = malloc(len + 1);
+
+               if (!arch->nameTable)
+               {
+                       fprintf(stderr, "Out of memory\n");
+
+                       return FALSE;
+               }
+
+               cc = read(arch->fd, arch->nameTable, len);
+
+               if (cc == -1)
+               {
+                       fprintf(stderr, "Error reading name-table: %s\n",
+                               strerror(errno));
+
+                       return FALSE;
+               }
+
+               if (cc != (ssize_t) len)
+               {
+                       fprintf(stderr,
+                               "Unexpected end of file in name-table\n");
+
+                       return FALSE;
+               }
+
+               arch->nameTable[len] = 0;
+
+               return skipPadding(arch->fd, len % 2);
+       }
+
+       /*
+        * 4. We read a normal header.
+        * Canonicalize it, and mark it as needing rescanning.
+        */
+       arch->rescan = TRUE;
+
+       return canonicalize(arch, &hdr);
+}
+
+
+/*
+ * Read the next normal member of the archive file if possible.
+ * If the member is being rescanned, clear the rescan flag and
+ * return the header that was already read.  Returns TRUE on
+ * success.  On a failure, the eof flag will be set if end of
+ * file has been reached.
+ */
+static BOOL
+readNormalMember(Archive * arch)
+{
+       struct ar_hdr   hdr;
+
+       /*
+        * If we are rereading an old header then just clear the
+        * rescan flag and return success.
+        */
+       if (arch->rescan)
+       {
+               arch->rescan = FALSE;
+
+               return TRUE;
+       }
+
+       /*
+        * We need to read a new member header.
+        */
+       if (!readMember(arch, &hdr))
+               return FALSE;
+
+       return canonicalize(arch, &hdr);
+}
+
+
+/*
+ * Skip the current member of the archive so that we are positioned
+ * to tbe beginning of the next member's header (or end of file).
+ * Returns TRUE on success.
+ */
+static BOOL
+skipMember(const Archive * arch)
+{
+       if (lseek(arch->fd, arch->size, SEEK_CUR) == -1)
+       {
+               fprintf(stderr, "Can't skip past archive member: %s\n",
+                       strerror(errno));
+
+               return FALSE;
+       }
+
+       return skipPadding(arch->fd, arch->pad);
+}
+
+
+/*
+ * Copy all of the file data from the archive to the specified
+ * open file.  Returns TRUE on success.
+ */
+static BOOL
+writeFile(const Archive * arch, int outfd)
+{
+       char    buf[BUF_SIZE];
+       off_t   n;
+
+       n = arch->size;
+
+       while (n > 0)
+       {
+               ssize_t cc;
+
+               cc = read(arch->fd, buf, MIN(n, sizeof(buf)));
+
+               if (cc == -1)
+               {
+                       fprintf(stderr, "Error reading archive member: %s\n",
+                               strerror(errno));
+
+                       return FALSE;
+               }
+
+               if (cc == 0)
+               {
+                       fprintf(stderr, "Unexpected end of file\n");
+
+                       return FALSE;
+               }
+
+               if (fullWrite(outfd, buf, cc) < 0)
+               {
+                       fprintf(stderr, "Write error: %s\n", strerror(errno));
+
+                       return FALSE;
+               }
+
+               n -= cc;
+       }
+
+       if (!skipPadding(arch->fd, arch->pad))
+               return FALSE;
+
+       return TRUE;
+}
+
+
+/*
+ * Print one line listing the information about the specified archive member.
+ */
+static void
+listMember(const Archive * arch)
+{
+       printf("%s %6ld/%-6ld %8lu %s %s\n",
+              modeString(arch->mode) + 1,
+              (long) arch->uid,
+              (long) arch->gid,
+              (unsigned long) arch->size,
+              timeString(arch->date),
+              arch->name);
+}
+
+
+/* END CODE */
diff --git a/src/sash/cmd_chattr.c b/src/sash/cmd_chattr.c
new file mode 100644 (file)
index 0000000..df50c22
--- /dev/null
@@ -0,0 +1,265 @@
+/*
+ * Copyright (c) 2014 by David I. Bell
+ * Permission is granted to use, distribute, or modify this source,
+ * provided that this copyright notice remains intact.
+ *
+ * The "chattr" and "lsattr" built-in commands.
+ * These commands are optionally built into sash.
+ * They manipulate the important ext2 file system file attribute flags.
+ */
+
+#if    HAVE_LINUX_ATTR
+
+#include <sys/ioctl.h>
+#include <sys/types.h>
+
+/*
+ * These were used for old linux versions.
+ * #include <linux/fs.h>
+ * #include <linux/ext2_fs.h>
+ */
+
+#include <ext2fs/ext2_fs.h>
+
+
+#include "sash.h"
+
+
+/*
+ * The chattr command.
+ * This can turn on or off the immutable and append-only ext2 flags.
+ */
+int
+do_chattr(int argc, const char ** argv)
+{
+       const char *    fileName;
+       const char *    options;
+       int *           flagPointer;
+       int             offFlags;
+       int             onFlags;
+       int             oldFlags;
+       int             newFlags;
+       int             fd;
+       int             r;
+
+       r = 0;
+       argc--;
+       argv++;
+
+       /*
+        * Parse the options.
+        */
+       onFlags = 0;
+       offFlags = 0;
+
+       while ((**argv == '-') || (**argv == '+'))
+       {
+               options = *argv++;
+               argc--;
+
+               /*
+                * Point at the proper flags to be modified.
+                */
+               if (*options++ == '+')
+                       flagPointer = &onFlags;
+               else
+                       flagPointer = &offFlags;
+
+               /*
+                * Parse the flag characters.
+                */
+               while (*options)
+               {
+                       switch (*options++)
+                       {
+                               case 'i':
+                                       *flagPointer |= EXT2_IMMUTABLE_FL;
+                                       break;
+
+                               case 'a':
+                                       *flagPointer |= EXT2_APPEND_FL;
+                                       break;
+
+                               default:
+                                       fprintf(stderr, "Unknown flag '%c'\n",
+                                               options[-1]);
+
+                                       return 1;
+                       }
+               }
+       }
+
+       /*
+        * Make sure that the attributes are reasonable.
+        */
+       if ((onFlags == 0) && (offFlags == 0))
+       {
+               fprintf(stderr, "No attributes specified\n");
+
+               return 1;
+       }
+
+       if ((onFlags & offFlags) != 0)
+       {
+               fprintf(stderr, "Inconsistent attributes specified\n");
+
+               return 1;
+       }
+
+       /*
+        * Make sure there are some files to affect.
+        */
+       if (argc <= 0)
+       {
+               fprintf(stderr, "No files specified for setting attributes\n");
+
+               return 1;
+       }
+
+       /*
+        * Iterate over all of the file names.
+        */
+       while (argc-- > 0)
+       {
+               fileName = *argv++;
+
+               /*
+                * Open the file name.
+                */
+               fd = open(fileName, O_RDONLY | O_NONBLOCK);
+
+               if (fd < 0)
+               {
+                       perror(fileName);
+                       r = 1;
+                       continue;
+               }
+
+               /*
+                * Read the current ext2 flag bits.
+                */
+               if (ioctl(fd, EXT2_IOC_GETFLAGS, &oldFlags) < 0)
+               {
+                       perror(fileName);
+                       r = 1;
+                       (void) close(fd);
+
+                       continue;
+               }
+
+               /*
+                * Modify the flag bits as specified.
+                */
+               newFlags = oldFlags;
+               newFlags |= onFlags;
+               newFlags &= ~offFlags;
+
+               /*
+                * If the flags aren't being changed, then close this
+                * file and continue.
+                */
+               if (newFlags == oldFlags)
+               {
+                       (void) close(fd);
+
+                       continue;
+               }
+
+               /*
+                * Set the new ext2 flag bits.
+                */
+               if (ioctl(fd, EXT2_IOC_SETFLAGS, &newFlags) < 0)
+               {
+                       perror(fileName);
+                       r = 1;
+                       (void) close(fd);
+
+                       continue;
+               }
+
+               /*
+                * Close the file.
+                */
+               (void) close(fd);
+       }
+
+       return r;
+}
+
+
+/*
+ * The lsattr command.
+ * This lists the immutable and append-only ext2 flags.
+ */
+int
+do_lsattr(int argc, const char ** argv)
+{
+       const char *    fileName;
+       int             r;
+       int             fd;
+       int             status;
+       int             flags;
+       char            string[4];
+
+       r = 0;
+       argc--;
+       argv++;
+
+       /*
+        * Iterate over all of the file names.
+        */
+       while (argc-- > 0)
+       {
+               fileName = *argv++;
+
+               /*
+                * Open the file name.
+                */
+               fd = open(fileName, O_RDONLY | O_NONBLOCK);
+
+               if (fd < 0)
+               {
+                       perror(fileName);
+                       r = 1;
+                       continue;
+               }
+
+               /*
+                * Read the ext2 flag bits.
+                */
+               status = ioctl(fd, EXT2_IOC_GETFLAGS, &flags);
+
+               /*
+                * Close the file and check the status.
+                */
+               (void) close(fd);
+
+               if (status < 0)
+               {
+                       perror(fileName);
+                       r = 1;
+
+                       continue;
+               }
+
+               /*
+                * Build up the string according to the flags.
+                * This is 'i' for immutable, and 'a' for append only.
+                */
+               string[0] = ((flags & EXT2_IMMUTABLE_FL) ? 'i' : '-');
+               string[1] = ((flags & EXT2_APPEND_FL) ? 'a' : '-');
+               string[2] = '\0';
+
+               /*
+                * Print the flags and the file name.
+                */
+               printf("%s  %s\n", string, fileName);
+       }
+
+       return r;
+}
+
+#endif
+
+
+/* END CODE */
diff --git a/src/sash/cmd_dd.c b/src/sash/cmd_dd.c
new file mode 100644 (file)
index 0000000..80338b6
--- /dev/null
@@ -0,0 +1,372 @@
+/*
+ * Copyright (c) 2014 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
+{
+       const 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(const char * cp);
+
+
+int
+do_dd(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            inFull;
+       long            inPartial;
+       long            outFull;
+       long            outPartial;
+       char *          buf;
+       char            localBuf[BUF_SIZE];
+       int             r;
+
+       inFile = NULL;
+       outFile = NULL;
+       seekVal = 0;
+       skipVal = 0;
+       blockSize = 512;
+       count = -1;
+       r = 0;
+
+       while (--argc > 0)
+       {
+               str = *++argv;
+               cp = strchr(str, '=');
+
+               if (cp == NULL)
+               {
+                       fprintf(stderr, "Bad dd argument\n");
+
+                       return 1;
+               }
+
+               *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 1;
+                               }
+       
+                               inFile = cp;
+                               break;
+
+                       case PAR_OF:
+                               if (outFile)
+                               {
+                                       fprintf(stderr, "Multiple output files illegal\n");
+
+                                       return 1;
+                               }
+
+                               outFile = cp;
+                               break;
+
+                       case PAR_BS:
+                               blockSize = getNum(cp);
+
+                               if (blockSize <= 0)
+                               {
+                                       fprintf(stderr, "Bad block size value\n");
+
+                                       return 1;
+                               }
+
+                               break;
+
+                       case PAR_COUNT:
+                               count = getNum(cp);
+
+                               if (count < 0)
+                               {
+                                       fprintf(stderr, "Bad count value\n");
+
+                                       return 1;
+                               }
+
+                               break;
+
+                       case PAR_SEEK:
+                               seekVal = getNum(cp);
+
+                               if (seekVal < 0)
+                               {
+                                       fprintf(stderr, "Bad seek value\n");
+
+                                       return 1;
+                               }
+
+                               break;
+
+                       case PAR_SKIP:
+                               skipVal = getNum(cp);
+
+                               if (skipVal < 0)
+                               {
+                                       fprintf(stderr, "Bad skip value\n");
+
+                                       return 1;
+                               }
+
+                               break;
+
+                       default:
+                               fprintf(stderr, "Unknown dd parameter\n");
+
+                               return 1;
+               }
+       }
+
+       if (inFile == NULL)
+       {
+               fprintf(stderr, "No input file specified\n");
+
+               return 1;
+       }
+
+       if (outFile == NULL)
+       {
+               fprintf(stderr, "No output file specified\n");
+
+               return 1;
+       }
+
+       buf = localBuf;
+
+       if (blockSize > sizeof(localBuf))
+       {
+               buf = malloc(blockSize);
+
+               if (buf == NULL)
+               {
+                       fprintf(stderr, "Cannot allocate buffer\n");
+
+                       return 1;
+               }
+       }
+
+       inFull = 0;
+       inPartial = 0;
+       outFull = 0;
+       outPartial = 0;
+
+       inFd = open(inFile, 0);
+
+       if (inFd < 0)
+       {
+               perror(inFile);
+
+               if (buf != localBuf)
+                       free(buf);
+
+               return 1;
+       }
+
+       outFd = creat(outFile, 0666);
+
+       if (outFd < 0)
+       {
+               perror(outFile);
+               close(inFd);
+
+               if (buf != localBuf)
+                       free(buf);
+
+               return 1;
+       }
+
+       if (skipVal)
+       {
+               if (lseek(inFd, skipVal * blockSize, 0) < 0)
+               {
+                       while (skipVal-- > 0)
+                       {
+                               inCc = read(inFd, buf, blockSize);
+
+                               if (inCc < 0)
+                               {
+                                       perror(inFile);
+                                       r = 1;
+                                       goto cleanup;
+                               }
+
+                               if (inCc == 0)
+                               {
+                                       fprintf(stderr, "End of file while skipping\n");
+                                       r = 1;
+                                       goto cleanup;
+                               }
+                       }
+               }
+       }
+
+       if (seekVal)
+       {
+               if (lseek(outFd, seekVal * blockSize, 0) < 0)
+               {
+                       perror(outFile);
+                       r = 1;
+                       goto cleanup;
+               }
+       }
+
+       inCc = 0;
+
+       while (((count < 0) || (inFull + inPartial < count)) &&
+              (inCc = read(inFd, buf, blockSize)) > 0)
+       {
+               if (inCc < blockSize)
+                       inPartial++;
+               else
+                       inFull++;
+               cp = buf;
+
+               if (intFlag)
+               {
+                       fprintf(stderr, "Interrupted\n");
+                       r = 1;
+                       goto cleanup;
+               }
+
+               while (inCc > 0)
+               {
+                       outCc = write(outFd, cp, inCc);
+
+                       if (outCc < 0)
+                       {
+                               perror(outFile);
+                               r = 1;
+                               goto cleanup;
+                       }
+
+                       if (outCc < blockSize)
+                               outPartial++;
+                       else
+                               outFull++;
+                       cp += outCc;
+                       inCc -= outCc;
+               }
+       }
+
+       if (inCc < 0)
+               perror(inFile);
+
+cleanup:
+       close(inFd);
+
+       if (close(outFd) < 0)
+       {
+               perror(outFile);
+               r = 1;
+       }
+
+       if (buf != localBuf)
+               free(buf);
+
+       printf("%ld+%ld records in\n", inFull, inPartial);
+
+       printf("%ld+%ld records out\n", outFull, outPartial);
+
+       return r;
+}
+
+
+/*
+ * Read a number with a possible multiplier.
+ * Returns -1 if the number format is illegal.
+ */
+static long
+getNum(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/src/sash/cmd_ed.c b/src/sash/cmd_ed.c
new file mode 100644 (file)
index 0000000..e935c0d
--- /dev/null
@@ -0,0 +1,1440 @@
+/*
+ * Copyright (c) 2014 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        INITBUF_SIZE    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(void);
+static void    subCommand(const char * cmd, NUM num1, NUM num2);
+static BOOL    getNum(const char ** retcp, BOOL * retHaveNum, NUM * retNum);
+static BOOL    setCurNum(NUM num);
+static BOOL    initEdit(void);
+static void    termEdit(void);
+static void    addLines(NUM num);
+static BOOL    insertLine(NUM num, const char * data, LEN len);
+static BOOL    deleteLines(NUM num1, NUM num2);
+static BOOL    printLines(NUM num1, NUM num2, BOOL expandFlag);
+static BOOL    writeLines(const char * file, NUM num1, NUM num2);
+static BOOL    readLines(const char * file, NUM num);
+static NUM     searchLines(const char * str, NUM num1, NUM num2);
+static LINE *  findLine(NUM num);
+
+static LEN     findString
+       (const LINE * lp, const char * str, LEN len, LEN offset);
+
+
+int
+do_ed(int argc, const char ** argv)
+{
+       if (!initEdit())
+               return 1;
+
+       if (argc > 1)
+       {
+               fileName = strdup(argv[1]);
+
+               if (fileName == NULL)
+               {
+                       fprintf(stderr, "No memory\n");
+                       termEdit();
+
+                       return 1;
+               }
+
+               if (!readLines(fileName, 1))
+               {
+                       termEdit();
+
+                       return 1;
+               }
+
+               if (lastNum)
+                       setCurNum(1);
+
+               dirty = FALSE;
+       }
+
+       doCommands();
+
+       termEdit();
+       return 0;
+}
+
+
+/*
+ * Read commands until we are told to stop.
+ */
+static void
+doCommands(void)
+{
+       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 file name\n");
+
+                                       break;
+                               }
+
+                               newname = strdup(cp);
+
+                               if (newname == NULL)
+                               {
+                                       fprintf(stderr, "No memory for file name\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';
+
+                               if (fgets(buf, sizeof(buf), stdin) == NULL)
+                                       return;
+
+                               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 file name\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(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( 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)
+{
+       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(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(void)
+{
+       int     i;
+
+       bufSize = INITBUF_SIZE;
+       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(void)
+{
+       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(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(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(NUM num1, NUM num2, BOOL expandFlag)
+{
+       const LINE *    lp;
+       const 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)
+               {
+                       tryWrite(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++ & 0xff;
+
+                       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 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(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(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)
+{
+       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)
+{
+       LINE *  lp;
+
+       lp = findLine(num);
+
+       if (lp == NULL)
+               return FALSE;
+
+       curNum = num;
+       curLine = lp;
+
+       return TRUE;
+}
+
+/* END CODE */
diff --git a/src/sash/cmd_file.c b/src/sash/cmd_file.c
new file mode 100644 (file)
index 0000000..caf2a30
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2014 by David I. Bell
+ * Permission is granted to use, distribute, or modify this source,
+ * provided that this copyright notice remains intact.
+ *
+ * The "file" built-in command.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "sash.h"
+
+
+static const char *    checkFile(const char * name);
+
+
+int
+do_file(int argc, const char ** argv)
+{
+       const char *    name;
+       const char *    info;
+
+       argc--;
+       argv++;
+
+       while (argc-- > 0)
+       {
+               name = *argv++;
+
+               info = checkFile(name);
+
+               if (info == NULL)
+                       info = "No information available";
+
+               printf("%s: %s\n", name, info);
+       }
+
+       return 0;
+}
+
+
+/*
+ * Examine the specified file and return a static string which
+ * describes the file type.  Returns NULL on a failure.
+ */
+static const char *
+checkFile(const char * name)
+{
+       int             mode;
+       int             fd;
+       int             cc;
+       int             i;
+       int             ch;
+       int             badCount;
+       char *          cp;
+       struct  stat    statBuf;
+       char            data[8192];
+       static  char    info[1024];
+
+       cp = info;
+       *cp = '\0';
+
+       if (lstat(name, &statBuf) < 0)
+       {
+               if (errno == ENOENT)
+                       return "non-existent";
+
+               sprintf(cp, "stat failed: %s", strerror(errno));
+
+               return info;
+       }
+
+       /*
+        * Check the file type.
+        */
+       mode = statBuf.st_mode;
+
+       if (S_ISDIR(mode))
+               return "directory";
+
+       if (S_ISCHR(mode))
+               return "character device";
+
+       if (S_ISBLK(mode))
+               return "block device";
+
+       if (S_ISFIFO(mode))
+               return "named pipe";
+
+#ifdef S_ISLNK
+       if (S_ISLNK(mode))
+               return "symbolic link";
+#endif
+
+#ifdef S_ISSOCK
+       if (S_ISSOCK(mode))
+               return "socket";
+#endif
+
+       /*
+        * If the file is not a regular file mention that.
+        */
+       if (!S_ISREG(mode))
+       {
+               sprintf(cp, "unknown mode 0x%x, \n", mode);
+
+               cp += strlen(cp);
+       }
+
+       /*
+        * Check for an executable file.
+        */
+       if ((mode & (S_IEXEC | S_IXGRP | S_IXOTH)) != 0)
+       {
+               strcpy(cp, "executable, ");
+
+               cp += strlen(cp);
+       }
+
+       /*
+        * The file is a normal file.
+        * Open it if we can and read in the first block.
+        */
+       fd = open(name, O_RDONLY);
+
+       if (fd < 0)
+       {
+               sprintf(cp, "unreadable: %s", strerror(errno));
+
+               return info;
+       }
+
+       cc = read(fd, data, sizeof(data));
+
+       if (cc < 0)
+       {
+               sprintf(cp, "read error: %s", strerror(errno));
+
+               (void) close(fd);
+
+               return info;
+       }
+
+       (void) close(fd);
+
+       /*
+        * Check for an empty file.
+        */
+       if (cc == 0)
+       {
+               strcpy(cp, "empty file");
+
+               return info;
+       }
+
+       /*
+        * Check for a script file.
+        */
+       if ((cc > 2) && (data[0] == '#') && (data[1] == '!'))
+       {
+               char *  begin;
+               char *  end;
+
+               data[sizeof(data) - 1] = '\0';
+
+               begin = &data[2];
+
+               while (*begin == ' ')
+                       begin++;
+
+               end = begin;
+
+               while (*end && (*end != ' ') && (*end != '\n'))
+                       end++;
+
+               *end = '\0';
+
+               sprintf(cp, "script for \"%s\"", begin);
+
+               return info;
+       }
+
+       /*
+        * Check for special binary data types.
+        */
+       if ((data[0] == '\037') && (data[1] == '\235'))
+               return "compressed file";
+
+       if ((data[0] == '\037') && (data[1] == '\213'))
+               return "GZIP file";
+
+       if ((data[0] == '\177') && (memcmp(&data[1], "ELF", 3) == 0))
+       {
+               strcpy(cp, "ELF program");
+
+               return info;
+       }
+
+       /*
+        * Check for binary data.
+        */
+       badCount = 0;
+
+       for (i = 0; i < cc; i++)
+       {
+               ch = data[i];
+
+               if ((ch == '\n') || (ch == '\t'))
+                       continue;
+
+               if (isspace(ch) || isprint(ch))
+                       continue;
+
+               badCount++;
+       }
+
+       if (badCount != 0)
+       {
+               strcpy(cp, "binary");
+
+               return info;
+       }
+
+       /*
+        * It is just a text file.
+        */
+       strcpy(cp, "text file");
+
+       return info;
+}
+
+/* END CODE */
diff --git a/src/sash/cmd_find.c b/src/sash/cmd_find.c
new file mode 100644 (file)
index 0000000..fde9006
--- /dev/null
@@ -0,0 +1,376 @@
+/*
+ * Copyright (c) 2014 by David I. Bell
+ * Permission is granted to use, distribute, or modify this source,
+ * provided that this copyright notice remains intact.
+ *
+ * The "find" built-in command.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+
+#include "sash.h"
+
+
+#ifdef S_ISLNK
+#define        LSTAT   lstat
+#else
+#define        LSTAT   stat
+#endif
+
+
+#define        MAX_NAME_SIZE   (1024 * 10)
+
+
+/*
+ * Items that can be specified to restrict the output.
+ */
+static BOOL            xdevFlag;
+static dev_t           xdevDevice;
+static long            fileSize;
+static const char *    filePattern;
+static const char *    fileType;
+
+
+/*
+ * Recursive routine to examine the files in a directory.
+ */
+static void    examineDirectory(const char * path);
+static BOOL    testFile(const char * fullName, const struct stat * statBuf);
+
+
+
+/*
+ * Find files from the specified directory path.
+ * This is limited to just printing their file names.
+ */
+int
+do_find(int argc, const char ** argv)
+{
+       const char *    cp;
+       const char *    path;
+       struct stat     statBuf;
+
+       argc--;
+       argv++;
+
+       xdevFlag = FALSE;
+       fileType = NULL;
+       filePattern = NULL;
+       fileSize = 0;
+
+       if ((argc <= 0) || (**argv == '-'))
+       {
+               fprintf(stderr, "No path specified\n");
+
+               return 1;
+       }
+
+       path = *argv++;
+       argc--;
+
+       while (argc > 0)
+       {
+               argc--;
+               cp = *argv++;
+
+               if (strcmp(cp, "-xdev") == 0)
+                       xdevFlag = TRUE;
+               else if (strcmp(cp, "-type") == 0)
+               {
+                       if ((argc <= 0) || (**argv == '-'))
+                       {
+                               fprintf(stderr, "Missing type string\n");
+
+                               return 1;
+                       }
+
+                       argc--;
+                       fileType = *argv++;
+               }
+               else if (strcmp(cp, "-name") == 0)
+               {
+                       if ((argc <= 0) || (**argv == '-'))
+                       {
+                               fprintf(stderr, "Missing file name\n");
+
+                               return 1;
+                       }
+
+                       argc--;
+                       filePattern = *argv++;
+               }
+               else if (strcmp(cp, "-size") == 0)
+               {
+                       if ((argc <= 0) || (**argv == '-'))
+                       {
+                               fprintf(stderr, "Missing file size\n");
+
+                               return 1;
+                       }
+
+                       argc--;
+                       cp = *argv++;
+
+                       fileSize = 0;
+
+                       while (isDecimal(*cp))
+                               fileSize = fileSize * 10 + (*cp++ - '0');
+
+                       if (*cp || (fileSize < 0))
+                       {
+                               fprintf(stderr, "Bad file size specified\n");
+
+                               return 1;
+                       }
+               }
+               else
+               {
+                       if (*cp != '-')
+                               fprintf(stderr, "Missing dash in option\n");
+                       else
+                               fprintf(stderr, "Unknown option\n");
+
+                       return 1;
+               }
+       }
+
+       /*
+        * Get information about the path and make sure that it
+        * is a directory.
+        */
+       if (stat(path, &statBuf) < 0)
+       {
+               fprintf(stderr, "Cannot stat \"%s\": %s\n", path,
+                       strerror(errno));
+
+               return 1;
+       }
+
+       if (!S_ISDIR(statBuf.st_mode))
+       {
+               fprintf(stderr, "Path \"%s\" is not a directory\n", path);
+
+               return 1;
+       }
+
+       /*
+        * Remember the device that this directory is on in case we need it.
+        */
+       xdevDevice = statBuf.st_dev;
+
+       /*
+        * If the directory meets the specified criteria, then print it out.
+        */
+       if (testFile(path, &statBuf))
+               printf("%s\n", path);
+
+       /*
+        * Now examine the files in the directory.
+        */
+       examineDirectory(path);
+
+       return 0;
+}
+
+
+/*
+ * Recursive routine to examine the files in a directory.
+ */
+static void
+examineDirectory(const char * path)
+{
+       DIR *           dir;
+       BOOL            needSlash;
+       struct dirent * entry;
+       struct stat     statBuf;
+       char            fullName[MAX_NAME_SIZE];
+
+       /*
+        * Open the directory.
+        */
+       dir = opendir(path);
+
+       if (dir == NULL)
+       {
+               fprintf(stderr, "Cannot read directory \"%s\": %s\n",
+                       path, strerror(errno));
+
+               return;
+       }
+
+       /*
+        * See if a slash is needed.
+        */
+       needSlash = (*path && (path[strlen(path) - 1] != '/'));
+
+       /*
+        * Read all of the directory entries and check them,
+        * except for the current and parent directory entries.
+        */
+       while (!intFlag && ((entry = readdir(dir)) != NULL))
+       {
+               if ((strcmp(entry->d_name, ".") == 0) ||
+                       (strcmp(entry->d_name, "..") == 0))
+               {
+                       continue;
+               }
+
+               /*
+                * Build the full path name.
+                */
+               strcpy(fullName, path);
+
+               if (needSlash)
+                       strcat(fullName, "/");
+
+               strcat(fullName, entry->d_name);
+
+               /*
+                * Find out about this file.
+                */
+               if (LSTAT(fullName, &statBuf) < 0)
+               {
+                       fprintf(stderr, "Cannot stat \"%s\": %s\n",
+                               fullName, strerror(errno));
+
+                       continue;
+               }
+
+               /*
+                * If this file matches the criteria that was
+                * specified then print its name out.
+                */
+               if (testFile(fullName, &statBuf))
+                       printf("%s\n", fullName);
+
+               /*
+                * If this is a directory and we are allowed to cross
+                * mount points or the directory is still on the same
+                * device, then examine it's files too.
+                */
+               if (S_ISDIR(statBuf.st_mode) &&
+                       (!xdevFlag || (statBuf.st_dev == xdevDevice)))
+               {
+                       examineDirectory(fullName);
+               }
+       }
+
+       closedir(dir);
+}
+
+
+/*
+ * Test a file name having the specified status to see if it should
+ * be acted on.  Returns TRUE if the file name has been selected.
+ */
+static BOOL
+testFile(const char * fullName, const struct stat * statBuf)
+{
+       const char *    cp;
+       const char *    entryName;
+       BOOL            wantType;
+       int             mode;
+
+       mode = statBuf->st_mode;
+
+       /*
+        * Check the file type if it was specified.
+        */
+       if (fileType != NULL)
+       {
+               wantType = FALSE;
+
+               for (cp = fileType; *cp; cp++)
+               {
+                       switch (*cp)
+                       {
+                               case 'f':
+                                       if (S_ISREG(mode))
+                                               wantType = TRUE;
+                                       break;
+
+                               case 'd':
+                                       if (S_ISDIR(mode))
+                                               wantType = TRUE;
+
+                                       break;
+
+                               case 'p':
+                                       if (S_ISFIFO(mode))
+                                               wantType = TRUE;
+
+                                       break;
+
+                               case 'c':
+                                       if (S_ISCHR(mode))
+                                               wantType = TRUE;
+
+                                       break;
+
+                               case 'b':
+                                       if (S_ISBLK(mode))
+                                               wantType = TRUE;
+
+                                       break;
+
+                               case 's':
+                                       if (S_ISSOCK(mode))
+                                               wantType = TRUE;
+
+                                       break;
+
+#ifdef S_ISLNK
+                               case 'l':
+                                       if (S_ISLNK(mode))
+                                               wantType = TRUE;
+
+                                       break;
+#endif
+                               default:
+                                       break;
+                       }
+               }
+
+               if (!wantType)
+                       return FALSE;
+       }
+
+       /*
+        * Check the file size if it was specified.
+        * This check only lets regular files and directories through.
+        */
+       if (fileSize > 0)
+       {
+               if (!S_ISREG(mode) && !S_ISDIR(mode))
+                       return FALSE;
+
+               if (statBuf->st_size < fileSize)
+                       return FALSE;
+       }
+
+       /*
+        * Check the file name pattern if it was specified.
+        */
+       if (filePattern != NULL)
+       {
+               entryName = strrchr(fullName, '/');
+
+               if (entryName)
+                       entryName++;
+               else
+                       entryName = fullName;
+
+               if (!match(entryName, filePattern))
+                       return FALSE;
+       }
+
+       /*
+        * This file name is wanted.
+        */
+       return TRUE;
+}
+
+/* END CODE */
diff --git a/src/sash/cmd_grep.c b/src/sash/cmd_grep.c
new file mode 100644 (file)
index 0000000..b9e0821
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2014 by David I. Bell
+ * Permission is granted to use, distribute, or modify this source,
+ * provided that this copyright notice remains intact.
+ *
+ * The "grep" built-in command.
+ */
+
+#include <ctype.h>
+
+#include "sash.h"
+
+
+static BOOL    search
+       (const char * string, const char * word, BOOL ignoreCase);
+
+
+int
+do_grep(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[BUF_SIZE];
+       int             r;
+
+       r = 1;
+       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 1;
+               }
+       }
+
+       word = *argv++;
+       argc--;
+
+       tellName = (argc > 1);
+
+       while (argc-- > 0)
+       {
+               name = *argv++;
+
+               fp = fopen(name, "r");
+
+               if (fp == NULL)
+               {
+                       perror(name);
+                       r = 1;
+
+                       continue;
+               }
+
+               line = 0;
+
+               while (fgets(buf, sizeof(buf), fp))
+               {
+                       if (intFlag)
+                       {
+                               fclose(fp);
+
+                               return 1;
+                       }
+
+                       line++;
+
+                       cp = &buf[strlen(buf) - 1];
+
+                       if (*cp != '\n')
+                               fprintf(stderr, "%s: Line too long\n", name);
+
+                       if (search(buf, word, ignoreCase))
+                       {
+                               r = 0;
+                               if (tellName)
+                                       printf("%s: ", name);
+
+                               if (tellLine)
+                                       printf("%ld: ", line);
+
+                               fputs(buf, stdout);
+                       }
+               }
+
+               if (ferror(fp))
+                       perror(name);
+
+               fclose(fp);
+       }
+
+       return r;
+}
+
+
+/*
+ * See if the specified word is found in the specified string.
+ */
+static BOOL
+search(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/src/sash/cmd_gzip.c b/src/sash/cmd_gzip.c
new file mode 100644 (file)
index 0000000..6bcec11
--- /dev/null
@@ -0,0 +1,698 @@
+/*
+ * Copyright (c) 2014 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.
+ */
+
+#if    HAVE_GZIP
+
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <zlib.h>
+
+#include "sash.h"
+
+
+#define        GZ_EXT          ".gz"
+#define        TGZ_EXT         ".tgz"
+#define        Z_EXT           ".Z"
+#define        TAR_EXT         ".tar"
+#define        NO_EXT          ""
+
+
+/*
+ * Tables of conversions to make to file extensions.
+ */
+typedef        struct
+{
+       const char *    input;
+       const char *    output;
+} CONVERT;
+
+
+static const CONVERT   gzipConvertTable[] =
+{
+       {TAR_EXT,       TGZ_EXT},
+       {NO_EXT,        GZ_EXT}
+};
+
+
+static const CONVERT   gunzipConvertTable[] =
+{
+       {TGZ_EXT,       TAR_EXT},
+       {GZ_EXT,        NO_EXT},
+       {Z_EXT,         NO_EXT},
+       {NO_EXT,        NO_EXT}
+};
+
+
+/*
+ * Local routines to compress and uncompress files.
+ */
+static BOOL    gzip(const char * inputFile, const char * outputFile);
+static BOOL    gunzip(const char * inputFile, const char * outputFile);
+
+static const char * convertName
+       (const CONVERT * table, const char * inFile);
+
+
+int
+do_gzip(int argc, const char ** argv)
+{
+       const char *    outPath;
+       const char *    inFile;
+       const char *    outFile;
+       int             i;
+       int             r;
+
+       r = 0;
+       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 1;
+               }
+       }
+       
+       /*
+        * 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))
+                       {
+                               r = 1;
+
+                               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");
+
+                               r = 1;
+                       }
+               }
+
+               return r;
+       }
+
+       /*
+        * 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 (!isDirectory(outPath))
+       {
+               if (argc == 1)
+                       r = !gzip(*argv, outPath);
+               else
+               {
+                       fprintf(stderr, "Exactly one input file is required\n");
+                       r = 1;
+               }
+
+               return r;
+       }
+
+       /*
+        * 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.
+                */
+               if (!gzip(inFile, outFile))
+                       r = 1;
+       }
+
+       return r;
+}
+
+
+int
+do_gunzip(int argc, const char ** argv)
+{
+       const char *    outPath;
+       const char *    inFile;
+       const char *    outFile;
+       int             i;
+       int             r;
+
+       r = 0;
+       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 1;
+               }
+       }
+       
+       /*
+        * 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");
+                               r = 1;
+
+                               continue;
+                       }
+
+                       /*
+                        * Try to uncompress the file.
+                        */
+                       if (!gunzip(inFile, outFile))
+                       {
+                               r = 1;
+                               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");
+                               r = 1;
+                       }
+               }
+
+               return r;
+       }
+
+       /*
+        * There is an output path specified.
+        * If the output path is a device file then uncompress each of
+        * the input files to the device file.
+        */
+       if (isDevice(outPath))
+       {
+               while (!intFlag && (argc-- > 0))
+               {
+                       if (!gunzip(*argv++, outPath))
+                               r = 1;
+               }
+
+               return r;
+       }
+
+       /*
+        * If the output path is not a directory then either uncompress the
+        * single specified input file to the exactly specified output path,
+        * or else complain.
+        */
+       if (!isDirectory(outPath))
+       {
+               if (argc == 1)
+                       return !gunzip(*argv, outPath);
+               else
+                       fprintf(stderr, "Exactly one input file is required\n");
+
+               return 1;
+       }
+
+       /*
+        * 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.
+                */
+               if (!gunzip(inFile, outFile))
+                       r = 1;
+       }
+
+       return r;
+}
+
+
+/*
+ * Compress the specified input file to produce the output file.
+ * Returns TRUE if successful.
+ */
+static BOOL
+gzip(const char * inputFileName, const char * outputFileName)
+{
+       gzFile          outGZ;
+       int             inFD;
+       int             len;
+       int             err;
+       struct  stat    statBuf1;
+       struct  stat    statBuf2;
+       char            buf[BUF_SIZE];
+
+       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(const char * inputFileName, const char * outputFileName)
+{
+       gzFile          inGZ;
+       int             outFD;
+       int             len;
+       int             err;
+       struct  stat    statBuf1;
+       struct  stat    statBuf2;
+       char            buf[BUF_SIZE];
+
+       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.
+        */
+       if (isDevice(outputFileName))
+               outFD = open(outputFileName, O_WRONLY);
+       else
+               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(const CONVERT * table, const char * inputFile)
+{
+       int             inputLength;
+       int             testLength;
+       static char     buf[PATH_LEN];
+
+       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/src/sash/cmd_ls.c b/src/sash/cmd_ls.c
new file mode 100644 (file)
index 0000000..41f31cc
--- /dev/null
@@ -0,0 +1,597 @@
+/*
+ * Copyright (c) 2014 by David I. Bell
+ * Permission is granted to use, distribute, or modify this source,
+ * provided that this copyright notice remains intact.
+ *
+ * The "ls" built-in command.
+ */
+
+#include "sash.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "dirent.h"
+#include <pwd.h>
+#include <grp.h>
+
+
+#define        LISTSIZE        8192
+
+
+#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
+#define        LSF_FLAG        0x10
+#define        LSF_COLUMN      0x20
+#define        LSF_NUMERIC     0x40
+
+
+/*
+ * Data holding list of files.
+ */
+static char ** list;
+static int     listSize;
+static int     listUsed;
+
+/*
+ * Cached user and group name data.
+ */
+static char    userName[12];
+static int     userId;
+static BOOL    userIdKnown;
+static char    groupName[12];
+static int     groupId;
+static BOOL    groupIdKnown;
+
+
+/*
+ * Local procedures.
+ */
+static void    listFile(
+       const char *            name,
+       const struct stat *     statBuf,
+       int                     flags,
+       int                     width
+);
+
+static BOOL    addListName(const char * fileName);
+static void    listAllFiles(int flags, int displayWidth);
+static void    clearListNames(void);
+
+
+int
+do_ls(int argc, const char ** argv)
+{
+       const char *    cp;
+       const char *    name;
+       int             flags;
+       int             i;
+       int             displayWidth;
+       BOOL            endSlash;
+       DIR *           dirp;
+       struct dirent * dp;
+       char            fullName[PATH_LEN];
+       struct  stat    statBuf;
+       int             r;
+
+       static const char *     def[] = {"."};
+
+       /*
+        * Reset for a new listing run.
+        */
+       clearListNames();
+
+       userIdKnown = FALSE;
+       groupIdKnown = FALSE;
+
+       displayWidth = 0;
+       flags = 0;
+
+       /*
+        * Handle options.
+        */
+       argc--;
+       argv++;
+
+       while ((argc > 0) && (**argv == '-'))
+       {
+               cp = *argv++ + 1;
+               argc--;
+
+               while (*cp) switch (*cp++)
+               {
+                       case 'l':       flags |= LSF_LONG; break;
+                       case 'n':       flags |= LSF_NUMERIC; break;
+                       case 'd':       flags |= LSF_DIR; break;
+                       case 'i':       flags |= LSF_INODE; break;
+                       case 'F':       flags |= LSF_FLAG; break;
+                       case 'C':       flags |= LSF_COLUMN; break;
+
+                       default:
+                               fprintf(stderr, "Unknown option -%c\n", cp[-1]);
+
+                               return 1;
+               }
+       }
+
+       /*
+        * If long or numeric listing is specified then turn off column listing.
+        */
+       if (flags & (LSF_LONG | LSF_NUMERIC))
+               flags &= ~LSF_COLUMN;
+
+       /*
+        * If column listing is specified then calculate the maximum
+        * width available for the columns of file names.
+        * This is settable using the COLS environment variable.
+        */
+       if (flags & LSF_COLUMN)
+       {
+               name = getenv("COLS");
+
+               if (name)
+                       displayWidth = atoi(name);
+
+               if (displayWidth <= 0)
+                       displayWidth = 80;
+       }
+
+       /*
+        * If no arguments are given set up to show the current directory.
+        */
+       if (argc <= 0)
+       {
+               argc = 1;
+               argv = def;
+       }
+
+       if (argc > 1)
+               flags |= LSF_MULT;
+
+       /*
+        * Make one pass over the file names to collect together
+        * all of the files which are not directories.
+        * We will process them all as one list.
+        */
+       for (i = 0; i < argc; i++)
+       {
+               if ((flags & LSF_DIR) || !isDirectory(argv[i]))
+               {
+                       if (!addListName(argv[i]))
+                               return 1;
+               }
+       }
+
+       /*
+        * List those file names, and then clear the list.
+        */
+       listAllFiles(flags, displayWidth);
+       clearListNames();
+
+       /*
+        * If directories were being listed as themselves, then we are done.
+        */
+       if (flags & LSF_DIR)
+               return r;
+
+       /*
+        * Now iterate over the file names processing the directories.
+        */
+       while (!intFlag && (argc-- > 0))
+       {
+               name = *argv++;
+               endSlash = (*name && (name[strlen(name) - 1] == '/'));
+
+               if (LSTAT(name, &statBuf) < 0)
+               {
+                       perror(name);
+                       r = 1;
+
+                       continue;
+               }
+
+               /*
+                * If this file name is not a directory, then ignore it.
+                */
+               if (!S_ISDIR(statBuf.st_mode))
+                       continue;
+
+               /*
+                * Collect all the files in the directory.
+                */
+               dirp = opendir(name);
+
+               if (dirp == NULL)
+               {
+                       perror(name);
+
+                       continue;
+               }
+
+               if (flags & LSF_MULT)
+                       printf("\n%s:\n", name);
+
+               while (!intFlag && ((dp = readdir(dirp)) != NULL))
+               {
+                       fullName[0] = '\0';
+
+                       if ((*name != '.') || (name[1] != '\0'))
+                       {
+                               strcpy(fullName, name);
+
+                               if (!endSlash)
+                                       strcat(fullName, "/");
+                       }
+
+                       strcat(fullName, dp->d_name);
+
+                       /*
+                        * Save the file name in the list.
+                        */
+                       if (!addListName(fullName))
+                       {
+                               closedir(dirp);
+
+                               return 1;
+                       }
+               }
+
+               closedir(dirp);
+
+               /*
+                * List the files we collected in this directory,
+                * and then clear the list.
+                */
+               listAllFiles(flags, displayWidth);
+               clearListNames();
+       }
+
+       return r;
+}
+
+
+/*
+ * List all of the files in the current list of files.
+ * The files are displayed according to the specified flags,
+ * in the specified display width.
+ */
+static void
+listAllFiles(int flags, int displayWidth)
+{
+       const char *    name;
+       const char *    cp;
+       int             fileWidth;
+       int             column;
+       int             len;
+       int             i;
+       struct stat     statBuf;
+
+       /*
+        * Initialise width data until we need it.
+        */
+       fileWidth = 0;
+       column = 0;
+
+       /*
+        * Sort the files in the list.
+        */
+       qsort((void *) list, listUsed, sizeof(char *), nameSort);
+
+       /*
+        * If we are showing the files in columns then calculate the
+        * maximum width of all of the file names, taking into account
+        * various factors.
+        */
+       if (flags & LSF_COLUMN)
+       {
+               for (i = 0; i < listUsed; i++)
+               {
+                       len = strlen(list[i]);
+
+                       if (fileWidth < len)
+                               fileWidth = len;
+               }
+
+               if (flags & LSF_FLAG)
+                       fileWidth++;
+
+               if (flags & LSF_INODE)
+                       fileWidth += 8;
+
+               fileWidth += 2;
+       }
+
+       /*
+        * Now list the fileNames.
+        */
+       for (i = 0; i < listUsed; i++)
+       {
+               name = list[i];
+
+               if (LSTAT(name, &statBuf) < 0)
+               {
+                       perror(name);
+
+                       continue;
+               }
+
+               cp = strrchr(name, '/');
+
+               if (cp)
+                       cp++;
+               else
+                       cp = name;
+
+               /*
+                * List the file in the next column or at the end
+                * of a line depending on the width left.
+                */
+               if (column + fileWidth * 2 >= displayWidth)
+               {
+                       listFile(cp, &statBuf, flags, 0);
+                       column = 0;
+               }
+               else
+               {
+                       listFile(cp, &statBuf, flags, fileWidth);
+                       column += fileWidth;
+               }
+       }
+
+       /*
+        * Terminate the last file name if necessary.
+        */
+       if (column > 0)
+               fputc('\n', stdout);
+}
+
+
+/*
+ * Do a listing of a particular file name according to the flags.
+ * The output is shown within the specified width if it is nonzero,
+ * or on its own line if the width is zero.
+ */
+static void
+listFile(
+       const char *            name,
+       const struct stat *     statBuf,
+       int                     flags,
+       int                     width
+)
+{
+       char *          cp;
+       struct passwd * pwd;
+       struct group *  grp;
+       int             len;
+       int             mode;
+       int             flagChar;
+       int             usedWidth;
+       char            buf[PATH_LEN];
+
+       mode = statBuf->st_mode;
+
+       /*
+        * Initialise buffers for use.
+        */
+       cp = buf;
+       buf[0] = '\0';
+       flagChar = '\0';
+
+       /*
+        * Show the inode number if requested.
+        */
+       if (flags & LSF_INODE)
+       {
+               sprintf(cp, "%7ld ", statBuf->st_ino);
+               cp += strlen(cp);
+       }
+
+       /*
+        * Create the long or numeric status line if requested.
+        */
+       if (flags & (LSF_LONG | LSF_NUMERIC))
+       {
+               strcpy(cp, modeString(mode));
+               cp += strlen(cp);
+
+               sprintf(cp, "%3ld ", (long) statBuf->st_nlink);
+               cp += strlen(cp);
+
+               if (!userIdKnown || (statBuf->st_uid != userId))
+               {
+                       if (flags & LSF_NUMERIC)
+                               pwd = 0;
+                       else
+                               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))
+               {
+                       if (flags & LSF_NUMERIC)
+                               grp = 0;
+                       else
+                               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(mode) || S_ISCHR(mode))
+               {
+                       sprintf(cp, "%3lu, %3lu ",
+                               ((unsigned long) statBuf->st_rdev) >> 8,
+                               ((unsigned long) statBuf->st_rdev) & 0xff);
+               }
+               else
+                       sprintf(cp, "%8ld ", statBuf->st_size);
+
+               cp += strlen(cp);
+
+               sprintf(cp, " %-12s ", timeString(statBuf->st_mtime));
+       }
+
+       /*
+        * Set the special character if the file is a directory or
+        * symbolic link or executable and the display was requested.
+        */
+       if (flags & LSF_FLAG)
+       {
+               if (S_ISDIR(mode))
+                       flagChar = '/';
+#ifdef S_ISLNK
+               else if (S_ISLNK(mode))
+                       flagChar = '@';
+#endif
+               else if ((mode & 0111) != 0)
+                       flagChar = '*';
+       }
+
+       /*
+        * Print the status info followed by the file name.
+        */
+       fputs(buf, stdout);
+       fputs(name, stdout);
+
+       if (flagChar)
+               fputc(flagChar, stdout);
+
+       /*
+        * Calculate the width used so far.
+        */
+       usedWidth = strlen(buf) + strlen(name);
+
+       if (flagChar)
+               usedWidth++;
+
+       /*
+        * Show where a symbolic link points.
+        */
+#ifdef S_ISLNK
+       if ((flags & LSF_LONG) && S_ISLNK(mode))
+       {
+               len = readlink(name, buf, PATH_LEN - 1);
+
+               if (len >= 0)
+               {
+                       buf[len] = '\0';
+                       printf(" -> %s", buf);
+               }
+
+               usedWidth += strlen(buf) + 4;
+       }
+#endif
+
+       /*
+        * If no width was given then just end the line with a newline.
+        */
+       if (width == 0)
+       {
+               fputc('\n', stdout);
+
+               return;
+       }
+
+       /*
+        * There is a width given.
+        * Print as many spaces as it takes to reach that width.
+        */
+       while (usedWidth++ < width)
+               fputc(' ', stdout);
+}
+
+
+/*
+ * Save a file name to the end of the static list, reallocating if necessary.
+ * The file name is copied into allocated memory owned by the list.
+ * Returns TRUE on success.
+ */
+static BOOL
+addListName(const char * fileName)
+{
+       char ** newList;
+
+       /*
+        * Reallocate the list if necessary.
+        */
+       if (listUsed >= listSize)
+       {
+               newList = realloc(list,
+                       ((sizeof(char **)) * (listSize + LISTSIZE)));
+
+               if (newList == NULL)
+               {
+                       fprintf(stderr, "No memory for file name buffer\n");
+
+                       return FALSE;
+               }
+
+               list = newList;
+               listSize += LISTSIZE;
+       }
+
+       /*
+        * Copy the file name into the next entry.
+        */
+       list[listUsed] = strdup(fileName);
+
+       if (list[listUsed] == NULL)
+       {
+               fprintf(stderr, "No memory for file name\n");
+
+               return FALSE;
+       }
+
+       /*
+        * Increment the amount of space used.
+        */
+       listUsed++;
+
+       return TRUE;
+}
+
+
+/*
+ * Free all of the names from the list of file names.
+ */
+static void
+clearListNames(void)
+{
+       while (listUsed > 0)
+       {
+               listUsed--;
+
+               free(list[listUsed]);
+       }
+}
+
+/* END CODE */
diff --git a/src/sash/cmd_tar.c b/src/sash/cmd_tar.c
new file mode 100644 (file)
index 0000000..5dd5f21
--- /dev/null
@@ -0,0 +1,1277 @@
+/*
+ * Copyright (c) 2014 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.
+ * This allows creation, extraction, and listing of tar files.
+ */
+
+#include "sash.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+
+
+/*
+ * Tar file constants.
+ */
+#define TAR_BLOCK_SIZE 512
+#define TAR_NAME_SIZE  100
+
+
+/*
+ * The POSIX (and basic GNU) tar header format.
+ * This structure is always embedded in a TAR_BLOCK_SIZE sized block
+ * with zero padding.  We only process this information minimally.
+ */
+typedef struct
+{
+       char    name[TAR_NAME_SIZE];
+       char    mode[8];
+       char    uid[8];
+       char    gid[8];
+       char    size[12];
+       char    mtime[12];
+       char    checkSum[8];
+       char    typeFlag;
+       char    linkName[TAR_NAME_SIZE];
+       char    magic[6];
+       char    version[2];
+       char    uname[32];
+       char    gname[32];
+       char    devMajor[8];
+       char    devMinor[8];
+       char    prefix[155];
+} TarHeader;
+
+
+#define        TAR_MAGIC       "ustar"
+#define        TAR_VERSION     "00"
+
+#define        TAR_TYPE_REGULAR        '0'
+#define        TAR_TYPE_HARD_LINK      '1'
+#define        TAR_TYPE_SOFT_LINK      '2'
+
+
+/*
+ * Static data.
+ */
+static BOOL            listFlag;
+static BOOL            extractFlag;
+static BOOL            createFlag;
+static BOOL            verboseFlag;
+
+static BOOL            inHeader;
+static BOOL            badHeader;
+static BOOL            errorFlag;
+static BOOL            skipFileFlag;
+static BOOL            warnedRoot;
+static BOOL            eofFlag;
+static long            dataCc;
+static int             outFd;
+static char            outName[TAR_NAME_SIZE];
+
+
+/*
+ * Static data associated with the tar file.
+ */
+static const char *    tarName;
+static int             tarFd;
+static dev_t           tarDev;
+static ino_t           tarInode;
+
+
+/*
+ * Local procedures to restore files from a tar file.
+ */
+static BOOL    readTarFile(int fileCount, const char ** fileTable);
+static BOOL    readData(const char * cp, int count);
+static BOOL    createPath(const char * name, int mode);
+static long    getOctal(const char * cp, int len);
+
+static BOOL    readHeader(const TarHeader * hp,
+                       int fileCount, const char ** fileTable);
+
+
+/*
+ * Local procedures to save files into a tar file.
+ */
+static void    saveFile(const char * fileName, BOOL seeLinks);
+
+static void    saveRegularFile(const char * fileName,
+                       const struct stat * statbuf);
+
+static void    saveDirectory(const char * fileName,
+                       const struct stat * statbuf);
+
+static BOOL    wantFileName(const char * fileName,
+                       int fileCount, const char ** fileTable);
+
+static void    writeHeader(const char * fileName,
+                       const struct stat * statbuf);
+
+static BOOL    writeTarFile(int fileCount, const char ** fileTable);
+static void    writeTarBlock(const char * buf, int len);
+static BOOL    putOctal(char * cp, int len, long value);
+
+
+
+int
+do_tar(int argc, const char ** argv)
+{
+       const char *    options;
+       BOOL            successFlag;
+
+       argc--;
+       argv++;
+
+       if (argc < 2)
+       {
+               fprintf(stderr, "Too few arguments for tar\n");
+
+               return 1;
+       }
+
+       extractFlag = FALSE;
+       createFlag = FALSE;
+       listFlag = FALSE;
+       verboseFlag = FALSE;
+       tarName = NULL;
+       tarDev = 0;
+       tarInode = 0;
+       tarFd = -1;
+
+       /*
+        * Parse the options.
+        */
+       options = *argv++;
+       argc--;
+
+       for (; *options; options++)
+       {
+               switch (*options)
+               {
+                       case 'f':
+                               if (tarName != NULL)
+                               {
+                                       fprintf(stderr, "Only one 'f' option allowed\n");
+
+                                       return 1;
+                               }
+
+                               tarName = *argv++;
+                               argc--;
+
+                               break;
+
+                       case 't':
+                               listFlag = TRUE;
+                               break;
+
+                       case 'x':
+                               extractFlag = TRUE;
+                               break;
+
+                       case 'c':
+                               createFlag = TRUE;
+                               break;
+
+                       case 'v':
+                               verboseFlag = TRUE;
+                               break;
+
+                       default:
+                               fprintf(stderr, "Unknown tar flag '%c'\n", *options);
+
+                               return 1;
+               }
+       }
+
+       /*
+        * Validate the options.
+        */
+       if (extractFlag + listFlag + createFlag != 1)
+       {
+               fprintf(stderr, "Exactly one of 'c', 'x' or 't' must be specified\n");
+
+               return 1;
+       }
+
+       if (tarName == NULL)
+       {
+               fprintf(stderr, "The 'f' flag must be specified\n");
+
+               return 1;
+       }
+
+       /*
+        * Do the correct type of action supplying the rest of the
+        * command line arguments as the list of files to process.
+        */
+       if (createFlag)
+               successFlag = writeTarFile(argc, argv);
+       else
+               successFlag = readTarFile(argc, argv);
+
+       return !successFlag;
+}
+
+
+/*
+ * Read a tar file and extract or list the specified files within it.
+ * If the list is empty than all files are extracted or listed.
+ * Returns TRUE on success.
+ */
+static BOOL
+readTarFile(int fileCount, const char ** fileTable)
+{
+       const char *    cp;
+       BOOL            successFlag;
+       int             cc;
+       int             inCc;
+       int             blockSize;
+       char            buf[BUF_SIZE];
+
+       skipFileFlag = FALSE;
+       badHeader = FALSE;
+       warnedRoot = FALSE;
+       eofFlag = FALSE;
+       inHeader = TRUE;
+       successFlag = TRUE;
+
+       inCc = 0;
+       dataCc = 0;
+       outFd = -1;
+       blockSize = sizeof(buf);
+       cp = buf;
+
+       /*
+        * Open the tar file for reading.
+        */
+       tarFd = open(tarName, O_RDONLY);
+
+       if (tarFd < 0)
+       {
+               perror(tarName);
+
+               return FALSE;
+       }
+
+       /*
+        * Read blocks from the file until an end of file header block
+        * has been seen.  (A real end of file from a read is an error.)
+        */
+       while (!intFlag && !eofFlag)
+       {
+               /*
+                * Read the next block of data if necessary.
+                * This will be a large block if possible, which we will
+                * then process in the small tar blocks.
+                */
+               if (inCc <= 0)
+               {
+                       cp = buf;
+                       inCc = fullRead(tarFd, buf, blockSize);
+
+                       if (inCc < 0)
+                       {
+                               perror(tarName);
+                               successFlag = FALSE;
+
+                               goto done;
+                       }
+
+                       if (inCc == 0)
+                       {
+                               fprintf(stderr,
+                                       "Unexpected end of file from \"%s\"",
+                                       tarName);
+                               successFlag = FALSE;
+
+                               goto done;
+                       }
+               }
+
+               /*
+                * If we are expecting a header block then examine it.
+                */
+               if (inHeader)
+               {
+                       if (!readHeader((const TarHeader *) cp, fileCount, fileTable))
+                               successFlag = FALSE;
+
+                       cp += TAR_BLOCK_SIZE;
+                       inCc -= TAR_BLOCK_SIZE;
+
+                       continue;
+               }
+
+               /*
+                * We are currently handling the data for a file.
+                * Process the minimum of the amount of data we have available
+                * and the amount left to be processed for the file.
+                */
+               cc = inCc;
+
+               if (cc > dataCc)
+                       cc = dataCc;
+
+               if (!readData(cp, cc))
+                       successFlag = FALSE;
+
+               /*
+                * If the amount left isn't an exact multiple of the tar block
+                * size then round it up to the next block boundary since there
+                * is padding at the end of the file.
+                */
+               if (cc % TAR_BLOCK_SIZE)
+                       cc += TAR_BLOCK_SIZE - (cc % TAR_BLOCK_SIZE);
+
+               cp += cc;
+               inCc -= cc;
+       }
+
+       /*
+        * Check for an interrupt.
+        */
+       if (intFlag)
+       {
+               fprintf(stderr, "Interrupted - aborting\n");
+               successFlag = FALSE;
+       }
+
+done:
+       /*
+        * Close the tar file if needed.
+        */
+       if ((tarFd >= 0) && (close(tarFd) < 0))
+       {
+               perror(tarName);
+               successFlag = FALSE;
+       }
+
+       /*
+        * Close the output file if needed.
+        * This is only done here on a previous error and so no
+        * message is required on errors.
+        */
+       if (outFd >= 0)
+               (void) close(outFd);
+
+       return successFlag;
+}
+
+
+/*
+ * Examine the header block that was just read.
+ * This can specify the information for another file, or it can mark
+ * the end of the tar file.  Returns TRUE on success.
+ */
+static BOOL
+readHeader(const TarHeader * hp, int fileCount, const char ** fileTable)
+{
+       int             mode;
+       int             uid;
+       int             gid;
+       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 = TAR_BLOCK_SIZE; cc > 0; cc--)
+               {
+                       if (*name++)
+                               return TRUE;
+               }
+
+               eofFlag = TRUE;
+
+               return TRUE;
+       }
+
+       /*
+        * There is another file in the archive to examine.
+        * Extract the encoded information and check it.
+        */
+       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));
+
+       if ((mode < 0) || (uid < 0) || (gid < 0) || (size < 0))
+       {
+               if (!badHeader)
+                       fprintf(stderr, "Bad tar header, skipping\n");
+
+               badHeader = TRUE;
+
+               return FALSE;
+       }
+
+       badHeader = FALSE;
+       skipFileFlag = FALSE;
+
+       /*
+        * Check for the file modes.
+        */
+       hardLink = ((hp->typeFlag == TAR_TYPE_HARD_LINK) ||
+               (hp->typeFlag == TAR_TYPE_HARD_LINK - '0'));
+
+       softLink = ((hp->typeFlag == TAR_TYPE_SOFT_LINK) ||
+               (hp->typeFlag == TAR_TYPE_SOFT_LINK - '0'));
+
+       /*
+        * Check for a directory or a regular file.
+        */
+       if (name[strlen(name) - 1] == '/')
+               mode |= S_IFDIR;
+       else if ((mode & S_IFMT) == 0)
+               mode |= S_IFREG;
+
+       /*
+        * Check for absolute paths in the file.
+        * If we find any, then warn the user and make them relative.
+        */
+       if (*name == '/')
+       {
+               while (*name == '/')
+                       name++;
+
+               if (!warnedRoot)
+               {
+                       fprintf(stderr,
+                       "Absolute path detected, removing leading slashes\n");
+               }
+
+               warnedRoot = TRUE;
+       }
+
+       /*
+        * See if we want this file to be restored.
+        * If not, then set up to skip it.
+        */
+       if (!wantFileName(name, fileCount, fileTable))
+       {
+               if (!hardLink && !softLink && S_ISREG(mode))
+               {
+                       inHeader = (size == 0);
+                       dataCc = size;
+               }
+
+               skipFileFlag = TRUE;
+
+               return TRUE;
+       }
+
+       /*
+        * This file is to be handled.
+        * If we aren't extracting then just list information about the file.
+        */
+       if (!extractFlag)
+       {
+               if (verboseFlag)
+               {
+                       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 TRUE;
+       }
+
+       /*
+        * We really want to extract the file.
+        */
+       if (verboseFlag)
+               printf("x %s\n", name);
+
+       if (hardLink)
+       {
+               if (link(hp->linkName, name) < 0)
+               {
+                       perror(name);
+                       return FALSE;
+               }
+
+               return TRUE;
+       }
+
+       if (softLink)
+       {
+#ifdef S_ISLNK
+               if (symlink(hp->linkName, name) < 0)
+               {
+                       perror(name);
+
+                       return FALSE;
+               }
+
+               return TRUE;
+#else
+               fprintf(stderr, "Cannot create symbolic links\n");
+#endif
+               return FALSE;
+       }
+
+       /*
+        * If the file is a directory, then just create the path.
+        */
+       if (S_ISDIR(mode))
+               return createPath(name, mode);
+
+       /*
+        * There is a file to write.
+        * First create the path to it if necessary with a default permission.
+        */
+       if (!createPath(name, 0777))
+               return FALSE;
+
+       inHeader = (size == 0);
+       dataCc = size;
+
+       /*
+        * Start the output file.
+        */
+       outFd = open(name, O_WRONLY | O_CREAT | O_TRUNC, mode);
+
+       if (outFd < 0)
+       {
+               perror(name);
+               skipFileFlag = TRUE;
+
+               return FALSE;
+       }
+
+       /*
+        * If the file is empty, then that's all we need to do.
+        */
+       if (size == 0)
+       {
+               (void) close(outFd);
+               outFd = -1;
+       }
+
+       return TRUE;
+}
+
+
+/*
+ * Handle a data block of some specified size that was read.
+ * Returns TRUE on success.
+ */
+static BOOL
+readData(const char * cp, int count)
+{
+       /*
+        * Reduce the amount of data left in this file.
+        * If there is no more data left, then we need to read
+        * the header again.
+        */
+       dataCc -= count;
+
+       if (dataCc <= 0)
+               inHeader = TRUE;
+
+       /*
+        * If we aren't extracting files or this file is being
+        * skipped then do nothing more.
+        */
+       if (!extractFlag || skipFileFlag)
+               return TRUE;
+
+       /*
+        * Write the data to the output file.
+        */
+       if (fullWrite(outFd, cp, count) < 0)
+       {
+               perror(outName);
+               (void) close(outFd);
+               outFd = -1;
+               skipFileFlag = TRUE;
+
+               return FALSE;
+       }
+
+       /*
+        * If the write failed, close the file and disable further
+        * writes to this file.
+        */
+       if (dataCc <= 0)
+       {
+               if (close(outFd))
+                       perror(outName);
+
+               outFd = -1;
+
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+
+/*
+ * Write a tar file containing the specified files.
+ * Returns TRUE on success.
+ */
+static BOOL
+writeTarFile(int fileCount, const char ** fileTable)
+{
+       struct  stat    statbuf;
+       BOOL            successFlag;
+
+       successFlag = TRUE;
+       errorFlag = FALSE;
+
+       /*
+        * Make sure there is at least one file specified.
+        */
+       if (fileCount <= 0)
+       {
+               fprintf(stderr, "No files specified to be saved\n");
+
+               return FALSE;
+       }
+
+       /*
+        * Create the tar file for writing.
+        */
+       tarFd = open(tarName, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+
+       if (tarFd < 0)
+       {
+               perror(tarName);
+
+               return FALSE;
+       }
+
+       /*
+        * Get the device and inode of the tar file for checking later.
+        */
+       if (fstat(tarFd, &statbuf) < 0)
+       {
+               perror(tarName);
+               successFlag = FALSE;
+
+               goto done;
+       }
+
+       tarDev = statbuf.st_dev;
+       tarInode = statbuf.st_ino;
+
+       /*
+        * Append each file name into the archive file.
+        * Follow symbolic links for these top level file names.
+        */
+       while (!intFlag && !errorFlag && (fileCount-- > 0))
+       {
+               saveFile(*fileTable++, FALSE);
+       }
+
+       if (intFlag)
+       {
+               fprintf(stderr, "Interrupted - aborting archiving\n");
+               successFlag = FALSE;
+       }
+
+       /*
+        * Now write an empty block of zeroes to end the archive.
+        */
+       writeTarBlock("", 1);
+
+
+done:
+       /*
+        * Close the tar file and check for errors if it was opened.
+        */
+       if ((tarFd >= 0) && (close(tarFd) < 0))
+       {
+               perror(tarName);
+               successFlag = FALSE;
+       }
+
+       return successFlag;
+}
+
+
+/*
+ * Save one file into the tar file.
+ * If the file is a directory, then this will recursively save all of
+ * the files and directories within the directory.  The seeLinks
+ * flag indicates whether or not we want to see symbolic links as
+ * they really are, instead of blindly following them.
+ */
+static void
+saveFile(const char * fileName, BOOL seeLinks)
+{
+       int             status;
+       int             mode;
+       struct stat     statbuf;
+
+       if (verboseFlag)
+               printf("a %s\n", fileName);
+
+       /*
+        * Check that the file name will fit in the header.
+        */
+       if (strlen(fileName) >= TAR_NAME_SIZE)
+       {
+               fprintf(stderr, "%s: File name is too long\n", fileName);
+
+               return;
+       }
+
+       /*
+        * Find out about the file.
+        */
+#ifdef S_ISLNK
+       if (seeLinks)
+               status = lstat(fileName, &statbuf);
+       else
+#endif
+               status = stat(fileName, &statbuf);
+
+       if (status < 0)
+       {
+               perror(fileName);
+
+               return;
+       }
+
+       /*
+        * Make sure we aren't trying to save our file into itself.
+        */
+       if ((statbuf.st_dev == tarDev) && (statbuf.st_ino == tarInode))
+       {
+               fprintf(stderr, "Skipping saving of archive file itself\n");
+
+               return;
+       }
+
+       /*
+        * Check the type of file.
+        */
+       mode = statbuf.st_mode;
+
+       if (S_ISDIR(mode))
+       {
+               saveDirectory(fileName, &statbuf);
+
+               return;
+       }
+
+       if (S_ISREG(mode))
+       {
+               saveRegularFile(fileName, &statbuf);
+
+               return;
+       }
+
+       /*
+        * The file is a strange type of file, ignore it.
+        */
+       fprintf(stderr, "%s: not a directory or regular file\n", fileName);
+}
+
+
+/*
+ * Save a regular file to the tar file.
+ */
+static void
+saveRegularFile(const char * fileName, const struct stat * statbuf)
+{
+       BOOL            sawEof;
+       int             fileFd;
+       int             cc;
+       int             dataCount;
+       long            fullDataCount;
+       char            data[TAR_BLOCK_SIZE * 16];
+
+       /*
+        * Open the file for reading.
+        */
+       fileFd = open(fileName, O_RDONLY);
+
+       if (fileFd < 0)
+       {
+               perror(fileName);
+
+               return;
+       }
+
+       /*
+        * Write out the header for the file.
+        */
+       writeHeader(fileName, statbuf);
+
+       /*
+        * Write the data blocks of the file.
+        * We must be careful to write the amount of data that the stat
+        * buffer indicated, even if the file has changed size.  Otherwise
+        * the tar file will be incorrect.
+        */
+       fullDataCount = statbuf->st_size;
+       sawEof = FALSE;
+
+       while (!intFlag && (fullDataCount > 0))
+       {
+               /*
+                * Get the amount to write this iteration which is
+                * the minumum of the amount left to write and the
+                * buffer size.
+                */
+               dataCount = sizeof(data);
+
+               if (dataCount > fullDataCount)
+                       dataCount = (int) fullDataCount;
+
+               /*
+                * Read the data from the file if we haven't seen the
+                * end of file yet.
+                */
+               cc = 0;
+
+               if (!sawEof)
+               {
+                       cc = fullRead(fileFd, data, dataCount);
+
+                       if (cc < 0)
+                       {
+                               perror(fileName);
+
+                               (void) close(fileFd);
+                               errorFlag = TRUE;
+
+                               return;
+                       }
+
+                       /*
+                        * If the file ended too soon, complain and set
+                        * a flag so we will zero fill the rest of it.
+                        */
+                       if (cc < dataCount)
+                       {
+                               fprintf(stderr,
+                                       "%s: Short read - zero filling",
+                                       fileName);
+
+                               sawEof = TRUE;
+                       }
+               }
+
+               /*
+                * Zero fill the rest of the data if necessary.
+                */
+               if (cc < dataCount)
+                       memset(data + cc, 0, dataCount - cc);
+
+               /*
+                * Write the buffer to the TAR file.
+                */
+               writeTarBlock(data, dataCount);
+
+               fullDataCount -= dataCount;
+       }
+
+       /*
+        * Close the file.
+        */
+       if (close(fileFd) < 0)
+               fprintf(stderr, "%s: close: %s\n", fileName, strerror(errno));
+}
+
+
+/*
+ * Save a directory and all of its files to the tar file.
+ */
+static void
+saveDirectory(const char * dirName, const struct stat * statbuf)
+{
+       DIR *           dir;
+       struct dirent * entry;
+       BOOL            needSlash;
+       char            fullName[PATH_LEN];
+
+       /*
+        * Construct the directory name as used in the tar file by appending
+        * a slash character to it.
+        */
+       strcpy(fullName, dirName);
+       strcat(fullName, "/");
+
+       /*
+        * Write out the header for the directory entry.
+        */
+       writeHeader(fullName, statbuf);
+
+       /*
+        * Open the directory.
+        */
+       dir = opendir(dirName);
+
+       if (dir == NULL)
+       {
+               fprintf(stderr, "Cannot read directory \"%s\": %s\n",
+                       dirName, strerror(errno));
+
+               return;
+       }
+
+       /*
+        * See if a slash is needed.
+        */
+       needSlash = (*dirName && (dirName[strlen(dirName) - 1] != '/'));
+
+       /*
+        * Read all of the directory entries and check them,
+        * except for the current and parent directory entries.
+        */
+       while (!intFlag && !errorFlag && ((entry = readdir(dir)) != NULL))
+       {
+               if ((strcmp(entry->d_name, ".") == 0) ||
+                       (strcmp(entry->d_name, "..") == 0))
+               {
+                       continue;
+               }
+
+               /*
+                * Build the full path name to the file.
+                */
+               strcpy(fullName, dirName);
+
+               if (needSlash)
+                       strcat(fullName, "/");
+
+               strcat(fullName, entry->d_name);
+
+               /*
+                * Write this file to the tar file, noticing whether or not
+                * the file is a symbolic link.
+                */
+               saveFile(fullName, TRUE);
+       }
+
+       /*
+        * All done, close the directory.
+        */
+       closedir(dir);
+}
+
+
+/*
+ * Write a tar header for the specified file name and status.
+ * It is assumed that the file name fits.
+ */
+static void
+writeHeader(const char * fileName, const struct stat * statbuf)
+{
+       long                    checkSum;
+       const unsigned char *   cp;
+       int                     len;
+       TarHeader               header;
+
+       /*
+        * Zero the header block in preparation for filling it in.
+        */
+       memset((char *) &header, 0, sizeof(header));
+
+       /*
+        * Fill in the header.
+        */
+       strcpy(header.name, fileName);
+
+       strncpy(header.magic, TAR_MAGIC, sizeof(header.magic));
+       strncpy(header.version, TAR_VERSION, sizeof(header.version));
+
+       putOctal(header.mode, sizeof(header.mode), statbuf->st_mode & 0777);
+       putOctal(header.uid, sizeof(header.uid), statbuf->st_uid);
+       putOctal(header.gid, sizeof(header.gid), statbuf->st_gid);
+       putOctal(header.size, sizeof(header.size), statbuf->st_size);
+       putOctal(header.mtime, sizeof(header.mtime), statbuf->st_mtime);
+
+       header.typeFlag = TAR_TYPE_REGULAR;
+
+       /*
+        * Calculate and store the checksum.
+        * This is the sum of all of the bytes of the header,
+        * with the checksum field itself treated as blanks.
+        */
+       memset(header.checkSum, ' ', sizeof(header.checkSum));
+
+       cp = (const unsigned char *) &header;
+       len = sizeof(header);
+       checkSum = 0;
+
+       while (len-- > 0)
+               checkSum += *cp++;
+
+       putOctal(header.checkSum, sizeof(header.checkSum), checkSum);
+
+       /*
+        * Write the tar header.
+        */
+       writeTarBlock((const char *) &header, sizeof(header));
+}
+
+
+/*
+ * Write data to one or more blocks of the tar file.
+ * The data is always padded out to a multiple of TAR_BLOCK_SIZE.
+ * The errorFlag static variable is set on an error.
+ */
+static void
+writeTarBlock(const char * buf, int len)
+{
+       int     partialLength;
+       int     completeLength;
+       char    fullBlock[TAR_BLOCK_SIZE];
+
+       /*
+        * If we had a write error before, then do nothing more.
+        */
+       if (errorFlag)
+               return;
+
+       /*
+        * Get the amount of complete and partial blocks.
+        */
+       partialLength = len % TAR_BLOCK_SIZE;
+       completeLength = len - partialLength;
+
+       /*
+        * Write all of the complete blocks.
+        */
+       if ((completeLength > 0) && !fullWrite(tarFd, buf, completeLength))
+       {
+               perror(tarName);
+
+               errorFlag = TRUE;
+
+               return;
+       }
+
+       /*
+        * If there are no partial blocks left, we are done.
+        */
+       if (partialLength == 0)
+               return;
+
+       /*
+        * Copy the partial data into a complete block, and pad the rest
+        * of it with zeroes.
+        */
+       memcpy(fullBlock, buf + completeLength, partialLength);
+       memset(fullBlock + partialLength, 0, TAR_BLOCK_SIZE - partialLength);
+
+       /*
+        * Write the last complete block.
+        */
+       if (!fullWrite(tarFd, fullBlock, TAR_BLOCK_SIZE))
+       {
+               perror(tarName);
+
+               errorFlag = TRUE;
+       }
+}
+
+
+/*
+ * 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.
+ * Returns TRUE on success.
+ */
+static BOOL
+createPath(const char * name, int mode)
+{
+       char *  cp;
+       char *  cpOld;
+       char    buf[TAR_NAME_SIZE];
+
+       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 = '/';
+       }
+
+       return TRUE;
+}
+
+
+/*
+ * 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(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;
+}
+
+
+/*
+ * Put an octal string into the specified buffer.
+ * The number is zero and space padded and possibly null padded.
+ * Returns TRUE if successful.
+ */
+static BOOL
+putOctal(char * cp, int len, long value)
+{
+       int     tempLength;
+       char *  tempString;
+       char    tempBuffer[32];
+
+       /*
+        * Create a string of the specified length with an initial space,
+        * leading zeroes and the octal number, and a trailing null.
+        */
+       tempString = tempBuffer;
+
+       sprintf(tempString, " %0*lo", len - 2, value);
+
+       tempLength = strlen(tempString) + 1;
+
+       /*
+        * If the string is too large, suppress the leading space.
+        */
+       if (tempLength > len)
+       {
+               tempLength--;
+               tempString++;
+       }
+
+       /*
+        * If the string is still too large, suppress the trailing null.
+        */
+       if (tempLength > len)
+               tempLength--;
+
+       /*
+        * If the string is still too large, fail.
+        */
+       if (tempLength > len)
+               return FALSE;
+
+       /*
+        * Copy the string to the field.
+        */
+       memcpy(cp, tempString, len);
+
+       return TRUE;
+}
+
+
+/*
+ * See if the specified file name belongs to one of the specified list
+ * of path prefixes.  An empty list implies that all files are wanted.
+ * Returns TRUE if the file is selected.
+ */
+static BOOL
+wantFileName(const char * fileName, int fileCount, const char ** fileTable)
+{
+       const char *    pathName;
+       int             fileLength;
+       int             pathLength;
+
+       /*
+        * If there are no files in the list, then the file is wanted.
+        */
+       if (fileCount == 0)
+               return TRUE;
+
+       fileLength = strlen(fileName);
+
+       /*
+        * Check each of the test paths.
+        */
+       while (fileCount-- > 0)
+       {
+               pathName = *fileTable++;
+
+               pathLength = strlen(pathName);
+
+               if (fileLength < pathLength)
+                       continue;
+
+               if (memcmp(fileName, pathName, pathLength) != 0)
+                       continue;
+
+               if ((fileLength == pathLength) ||
+                       (fileName[pathLength] == '/'))
+               {
+                       return TRUE;
+               }
+       }
+
+       return FALSE;
+}
+
+/* END CODE */
diff --git a/src/sash/cmds.c b/src/sash/cmds.c
new file mode 100644 (file)
index 0000000..ae346b5
--- /dev/null
@@ -0,0 +1,1562 @@
+/*
+ * Copyright (c) 2014 by David I. Bell
+ * Permission is granted to use, distribute, or modify this source,
+ * provided that this copyright notice remains intact.
+ *
+ * Most simple built-in commands are here.
+ */
+
+#include "sash.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+//#include <sys/mount.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+#include <utime.h>
+#include <errno.h>
+
+#if    HAVE_LINUX_MOUNT
+#include <linux/fs.h>
+#endif
+
+/* Need to tell loop.h what the actual dev_t type is. */
+#undef dev_t
+#if defined(__alpha) || (defined(__sparc__) && defined(__arch64__))
+#define dev_t unsigned int
+#else
+#define dev_t unsigned short
+#endif
+//#include <linux/loop.h>
+#undef dev_t
+#define dev_t dev_t
+
+
+int
+do_echo(int argc, const char ** argv)
+{
+       BOOL    first;
+
+       first = TRUE;
+
+       while (argc-- > 1)
+       {
+               if (!first)
+                       fputc(' ', stdout);
+
+               first = FALSE;
+               fputs(*(++argv), stdout);
+       }
+
+       fputc('\n', stdout);
+
+       return 0;
+}
+
+
+int
+do_pwd(int argc, const char ** argv)
+{
+       char    buf[PATH_LEN];
+
+       if (getcwd(buf, PATH_LEN) == NULL)
+       {
+               fprintf(stderr, "Cannot get current directory\n");
+
+               return 1;
+       }
+
+       printf("%s\n", buf);
+
+       return 0;
+}
+
+
+int
+do_cd(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 1;
+               }
+       }
+
+       if (chdir(path) < 0)
+       {
+               perror(path);
+
+               return 1;
+       }
+
+       return 0;
+}
+
+
+int
+do_mkdir(int argc, const char ** argv)
+{
+       int r = 0;
+
+       while (argc-- > 1)
+       {
+               if (mkdir(argv[1], 0777) < 0)
+               {
+                       perror(argv[1]);
+                       r = 1;
+               }
+
+               argv++;
+       }
+
+       return r;
+}
+
+
+int
+do_mknod(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 1;
+       }
+
+       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 1;
+       }
+
+       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 1;
+       }
+
+       if (mknod(argv[1], mode, major * 256 + minor) < 0)
+       {
+               perror(argv[1]);
+
+               return 1;
+       }
+
+       return 0;
+}
+
+
+#if HAVE_LINUX_PIVOT
+
+int
+do_pivot_root(int argc, const char ** argv)
+{
+       if (pivot_root(argv[1], argv[2]) < 0)
+       {
+               perror("pivot_root");
+
+               return 1;
+       }
+
+       return 0;
+}
+
+#endif
+
+
+#if HAVE_LINUX_CHROOT
+
+int
+do_chroot(int argc, const char ** argv)
+{
+       if (chroot(argv[1]) < 0)
+       {
+               perror("chroot");
+
+               return 1;
+       }
+
+       return 0;
+}
+
+#endif
+
+
+int
+do_rmdir(int argc, const char ** argv)
+{
+       int r = 0;
+
+       while (argc-- > 1)
+       {
+               if (rmdir(argv[1]) < 0)
+               {
+                       perror(argv[1]);
+                       r = 1;
+               }
+
+               argv++;
+       }
+
+       return r;
+}
+
+
+int
+do_sync(int argc, const char ** argv)
+{
+       sync();
+
+       return 0;
+}
+
+
+int
+do_rm(int argc, const char ** argv)
+{
+       int r = 0;
+
+       while (argc-- > 1)
+       {
+               if (unlink(argv[1]) < 0)
+               {
+                       perror(argv[1]);
+                       r = 1;
+               }
+
+               argv++;
+       }
+
+       return r;
+}
+
+
+int
+do_chmod(int argc, const char ** argv)
+{
+       const char *    cp;
+       int             mode;
+       int             r;
+
+       r = 0;
+       mode = 0;
+       cp = argv[1];
+
+       while (isOctal(*cp))
+               mode = mode * 8 + (*cp++ - '0');
+
+       if (*cp)
+       {
+               fprintf(stderr, "Mode must be octal\n");
+
+               return 1;
+       }
+
+       argc--;
+       argv++;
+
+       while (argc-- > 1)
+       {
+               if (chmod(argv[1], mode) < 0)
+               {
+                       perror(argv[1]);
+                       r = 1;
+               }
+
+               argv++;
+       }
+
+       return r;
+}
+
+
+int
+do_chown(int argc, const char ** argv)
+{
+       const char *    cp;
+       int             uid;
+       struct passwd * pwd;
+       struct stat     statBuf;
+       int             r;
+
+       r = 0;
+       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 1;
+               }
+       }
+       else
+       {
+               pwd = getpwnam(cp);
+
+               if (pwd == NULL)
+               {
+                       fprintf(stderr, "Unknown user name\n");
+
+                       return 1;
+               }
+
+               uid = pwd->pw_uid;
+       }
+
+       argc--;
+       argv++;
+
+       while (argc-- > 1)
+       {
+               argv++;
+
+               if ((stat(*argv, &statBuf) < 0) ||
+                       (chown(*argv, uid, statBuf.st_gid) < 0))
+               {
+                       perror(*argv);
+                       r = 1;
+               }
+       }
+
+       return r;
+}
+
+
+int
+do_chgrp(int argc, const char ** argv)
+{
+       const char *    cp;
+       int             gid;
+       struct group *  grp;
+       struct stat     statBuf;
+       int             r;
+
+       r = 0;
+       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 1;
+               }
+       }
+       else
+       {
+               grp = getgrnam(cp);
+
+               if (grp == NULL)
+               {
+                       fprintf(stderr, "Unknown group name\n");
+
+                       return 1;
+               }
+
+               gid = grp->gr_gid;
+       }
+
+       argc--;
+       argv++;
+
+       while (argc-- > 1)
+       {
+               argv++;
+
+               if ((stat(*argv, &statBuf) < 0) ||
+                       (chown(*argv, statBuf.st_uid, gid) < 0))
+               {
+                       perror(*argv);
+                       r = 1;
+               }
+       }
+
+       return r;
+}
+
+
+int
+do_touch(int argc, const char ** argv)
+{
+       const char *    name;
+       int             fd;
+       struct utimbuf  now;
+       int             r;
+
+       r = 0;
+       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 (errno != EEXIST)
+               {
+                       perror(name);
+                       r = 1;
+
+                       continue;
+               }
+
+               if (utime(name, &now) < 0)
+               {
+                       perror(name);
+                       r = 1;
+               }
+       }
+
+       return r;
+}
+
+
+int
+do_mv(int argc, const char ** argv)
+{
+       const char *    srcName;
+       const char *    destName;
+       const char *    lastArg;
+       BOOL            dirFlag;
+       int             r;
+
+       r = 0;
+       lastArg = argv[argc - 1];
+
+       dirFlag = isDirectory(lastArg);
+
+       if ((argc > 3) && !dirFlag)
+       {
+               fprintf(stderr, "%s: not a directory\n", lastArg);
+
+               return 1;
+       }
+
+       while (!intFlag && (argc-- > 2))
+       {
+               srcName = *(++argv);
+
+               if (access(srcName, 0) < 0)
+               {
+                       perror(srcName);
+                       r = 1;
+
+                       continue;
+               }
+
+               destName = lastArg;
+
+               if (dirFlag)
+                       destName = buildName(destName, srcName);
+
+               if (rename(srcName, destName) >= 0)
+                       continue;
+
+               if (errno != EXDEV)
+               {
+                       perror(destName);
+                       r = 1;
+
+                       continue;
+               }
+
+               if (!copyFile(srcName, destName, TRUE))
+               {
+                       r = 1;
+
+                       continue;
+               }
+
+               if (unlink(srcName) < 0)
+               {
+                       perror(srcName);
+                       r = 1;
+               }
+       }
+
+       return r;
+}
+
+
+int
+do_ln(int argc, const char ** argv)
+{
+       const char *    srcName;
+       const char *    destName;
+       const char *    lastArg;
+       BOOL            dirFlag;
+       int             r;
+
+       r = 0;
+
+       if (argv[1][0] == '-')
+       {
+               if (strcmp(argv[1], "-s"))
+               {
+                       fprintf(stderr, "Unknown option\n");
+
+                       return 1;
+               }
+
+               if (argc != 4)
+               {
+                       fprintf(stderr, "Wrong number of arguments for symbolic link\n");
+
+                       return 1;
+               }
+
+#ifdef S_ISLNK
+               if (symlink(argv[2], argv[3]) < 0)
+               {
+                       perror(argv[3]);
+
+                       return 1;
+               }
+
+               return 0;
+#else
+               fprintf(stderr, "Symbolic links are not allowed\n");
+
+               return 1;
+#endif
+       }
+
+       /*
+        * Here for normal hard links.
+        */
+       lastArg = argv[argc - 1];
+       dirFlag = isDirectory(lastArg);
+
+       if ((argc > 3) && !dirFlag)
+       {
+               fprintf(stderr, "%s: not a directory\n", lastArg);
+
+               return 1;
+       }
+
+       while (argc-- > 2)
+       {
+               srcName = *(++argv);
+
+               if (access(srcName, 0) < 0)
+               {
+                       perror(srcName);
+                       r = 1;
+
+                       continue;
+               }
+
+               destName = lastArg;
+
+               if (dirFlag)
+                       destName = buildName(destName, srcName);
+
+               if (link(srcName, destName) < 0)
+               {
+                       perror(destName);
+                       r = 1;
+
+                       continue;
+               }
+       }
+
+       return r;
+}
+
+
+int
+do_cp(int argc, const char ** argv)
+{
+       const char *    srcName;
+       const char *    destName;
+       const char *    lastArg;
+       BOOL            dirFlag;
+       int             r;
+
+       r = 0;
+       lastArg = argv[argc - 1];
+
+       dirFlag = isDirectory(lastArg);
+
+       if ((argc > 3) && !dirFlag)
+       {
+               fprintf(stderr, "%s: not a directory\n", lastArg);
+
+               return 1;
+       }
+
+       while (!intFlag && (argc-- > 2))
+       {
+               srcName = *(++argv);
+               destName = lastArg;
+
+               if (dirFlag)
+                       destName = buildName(destName, srcName);
+
+               if (!copyFile(srcName, destName, FALSE))
+                       r = 1;
+       }
+
+       return r;
+}
+
+
+int
+do_mount(int argc, const char ** argv)
+{
+       const char *    str;
+       const char *    type;
+       int             flags;
+
+       argc--;
+       argv++;
+
+       type = MOUNT_TYPE;
+
+#if    HAVE_LINUX_MOUNT
+       flags = MS_MGC_VAL;
+#else
+       flags = 0;
+#endif
+
+       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 1;
+                               }
+
+                               type = *argv++;
+                               argc--;
+                               break;
+
+#if    HAVE_LINUX_MOUNT
+                       case 'r':
+                               flags |= MS_RDONLY;
+                               break;
+
+                       case 'm':
+                               flags |= MS_REMOUNT;
+                               break;
+
+                       case 's':
+                               flags |= MS_NOSUID;
+                               break;
+
+                       case 'e':
+                               flags |= MS_NOEXEC;
+                               break;
+
+#elif  HAVE_BSD_MOUNT
+                       case 'r':
+                               flags |= MNT_RDONLY;
+                               break;
+
+                       case 's':
+                               flags |= MNT_NOSUID;
+                               break;
+
+                       case 'e':
+                               flags |= MNT_NOEXEC;
+                               break;
+#endif
+                       default:
+                               fprintf(stderr, "Unknown option\n");
+
+                               return 1;
+               }
+       }
+
+       if (argc != 2)
+       {
+               fprintf(stderr, "Wrong number of arguments for mount\n");
+
+               return 1;
+       }
+
+#if    HAVE_LINUX_MOUNT
+
+       if (mount(argv[0], argv[1], type, flags, 0) < 0)
+       {
+               perror("mount failed");
+               return 1;
+       }
+#elif  HAVE_BSD_MOUNT
+       {
+               struct      ufs_args ufs;
+               struct      adosfs_args adosfs;
+               struct      iso_args iso;
+               struct      mfs_args mfs;
+               struct      msdosfs_args msdosfs;
+               void *      args;
+
+               if (!strcmp(type, "ffs") || !strcmp(type, "ufs"))
+               {
+                       ufs.fspec = (char*) argv[0];
+                       args = &ufs;
+               }
+               else if (!strcmp(type, "adosfs"))
+               {
+                       adosfs.fspec = (char*) argv[0];
+                       adosfs.uid = 0;
+                       adosfs.gid = 0;
+                       args = &adosfs;
+               }
+               else if (!strcmp(type, "cd9660"))
+               {
+                       iso.fspec = (char*) argv[0];
+                       args = &iso;
+               }
+               else if (!strcmp(type, "mfs"))
+               {
+                       mfs.fspec = (char*) argv[0];
+                       args = &mfs;
+               }
+               else if (!strcmp(type, "msdos"))
+               {
+                       msdosfs.fspec = (char*) argv[0];
+                       msdosfs.uid = 0;
+                       msdosfs.gid = 0;
+                       args = &msdosfs;
+               }
+               else
+               {
+                       fprintf(stderr, "Unknown filesystem type: %s", type);
+                       fprintf(stderr,
+                           "Supported: ffs ufs adosfs cd9660 mfs msdos\n");
+
+                       return 1;
+               }
+
+               if (mount(type, argv[1], flags, args) < 0)
+               {
+                       perror(argv[0]);
+
+                       return 1;
+               }
+       }
+#endif
+       return 0;
+}
+
+
+int
+do_umount(int argc, const char ** argv)
+{
+#if    HAVE_LINUX_MOUNT
+       if (umount(argv[1]) < 0)
+       {
+               perror(argv[1]);
+
+               return 1;
+       }
+#elif  HAVE_BSD_MOUNT
+       {
+               const char *    str;
+               int             flags = 0;
+
+               for (argc--, argv++;
+                   (argc > 0) && (**argv == '-');)
+               {
+                       argc--;
+                       str = *argv++;
+
+                       while (*++str)
+{
+                               switch (*str)
+                               {
+                                       case 'f':
+                                               flags = MNT_FORCE;
+                                               break;
+                               }
+                       }
+               }
+
+               if (unmount(argv[0], flags) < 0)
+               {
+                       perror(argv[0]);
+
+                       return 1;
+               }
+       }
+#endif
+       return 0;
+}
+
+
+int
+do_cmp(int argc, const char ** argv)
+{
+       int             fd1;
+       int             fd2;
+       int             cc1;
+       int             cc2;
+       long            pos;
+       const char *    bp1;
+       const char *    bp2;
+       char            buf1[BUF_SIZE];
+       char            buf2[BUF_SIZE];
+       struct  stat    statBuf1;
+       struct  stat    statBuf2;
+       int             r;
+
+       r = 0;
+
+       if (stat(argv[1], &statBuf1) < 0)
+       {
+               perror(argv[1]);
+
+               return 1;
+       }
+
+       if (stat(argv[2], &statBuf2) < 0)
+       {
+               perror(argv[2]);
+
+               return 1;
+       }
+
+       if ((statBuf1.st_dev == statBuf2.st_dev) &&
+               (statBuf1.st_ino == statBuf2.st_ino))
+       {
+               printf("Files are links to each other\n");
+
+               return 0;
+       }
+
+       if (statBuf1.st_size != statBuf2.st_size)
+       {
+               printf("Files are different sizes\n");
+
+               return 1;
+       }
+
+       fd1 = open(argv[1], O_RDONLY);
+
+       if (fd1 < 0)
+       {
+               perror(argv[1]);
+
+               return 1;
+       }
+
+       fd2 = open(argv[2], O_RDONLY);
+
+       if (fd2 < 0)
+       {
+               perror(argv[2]);
+               close(fd1);
+
+               return 1;
+       }
+
+       pos = 0;
+
+       while (TRUE)
+       {
+               if (intFlag)
+                       goto closefiles;
+
+               cc1 = read(fd1, buf1, sizeof(buf1));
+
+               if (cc1 < 0)
+               {
+                       perror(argv[1]);
+                       r = 1;
+                       goto closefiles;
+               }
+
+               cc2 = read(fd2, buf2, sizeof(buf2));
+
+               if (cc2 < 0)
+               {
+                       perror(argv[2]);
+                       r = 1;
+                       goto closefiles;
+               }
+
+               if ((cc1 == 0) && (cc2 == 0))
+               {
+                       printf("Files are identical\n");
+                       r = 0;
+                       goto closefiles;
+               }
+
+               if (cc1 < cc2)
+               {
+                       printf("First file is shorter than second\n");
+                       r = 1;
+                       goto closefiles;
+               }
+
+               if (cc1 > cc2)
+               {
+                       printf("Second file is shorter than first\n");
+                       r = 1;
+                       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);
+               r = 1;
+
+               goto closefiles;
+       }
+
+closefiles:
+       close(fd1);
+       close(fd2);
+
+       return r;
+}
+
+
+int
+do_more(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 1;
+               }
+
+               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 0;
+                       }
+
+                       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 0;
+                       }
+
+                       col = 0;
+                       line = 1;
+               }
+
+               if (fp)
+                       fclose(fp);
+       }
+
+       return 0;
+}
+
+
+int
+do_sum(int argc, const char ** argv)
+{
+       const char *    name;
+       int             fd;
+       int             cc;
+       int             ch;
+       int             i;
+       unsigned long   checksum;
+       char            buf[BUF_SIZE];
+       int             r;
+
+       argc--;
+       argv++;
+       r = 0;
+
+       while (argc-- > 0)
+       {
+               name = *argv++;
+
+               fd = open(name, O_RDONLY);
+
+               if (fd < 0)
+               {
+                       perror(name);
+                       r = 1;
+
+                       continue;
+               }
+
+               checksum = 0;
+
+               while ((cc = read(fd, buf, sizeof(buf))) > 0)
+               {
+                       for (i = 0; i < cc; i++)
+                       {
+                               ch = buf[i];
+
+                               if ((checksum & 0x01) != 0)
+                                       checksum = (checksum >> 1) + 0x8000;
+                               else
+                                       checksum = (checksum >> 1);
+
+                               checksum = (checksum + ch) & 0xffff;
+                       }
+               }
+
+               if (cc < 0)
+               {
+                       perror(name);
+                       r = 1;
+
+                       (void) close(fd);
+
+                       continue;
+               }
+
+               (void) close(fd);
+
+               printf("%05lu %s\n", checksum, name);
+       }
+
+       return r;
+}
+
+
+int
+do_exit(int argc, const char ** argv)
+{
+       int r = 0;
+
+       if (getpid() == 1)
+       {
+               fprintf(stderr, "You are the INIT process!\n");
+
+               return 1;
+       }
+
+       if (argc == 2)
+       {
+               r = atoi(argv[1]);
+       }
+
+       exit(r);
+
+       return 1;
+}
+
+
+int
+do_setenv(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 1;
+       }
+
+       strcpy(str, name);
+       strcat(str, "=");
+       strcat(str, value);
+
+       putenv(str);
+
+       return 0;
+}
+
+
+int
+do_printenv(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 0;
+       }
+
+       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 0;
+               }
+               env++;
+       }
+
+       return 0;
+}
+
+
+int
+do_umask(int argc, const char ** argv)
+{
+       const char *    cp;
+       int             mask;
+
+       if (argc <= 1)
+       {
+               mask = umask(0);
+               umask(mask);
+               printf("%03o\n", mask);
+
+               return 0;
+       }
+
+       mask = 0;
+       cp = argv[1];
+
+       while (isOctal(*cp))
+               mask = mask * 8 + *cp++ - '0';
+
+       if (*cp || (mask & ~0777))
+       {
+               fprintf(stderr, "Bad umask value\n");
+
+               return 1;
+       }
+
+       umask(mask);
+
+       return 0;
+}
+
+
+int
+do_kill(int argc, const char ** argv)
+{
+       const char *    cp;
+       int             sig;
+       int             pid;
+       int             r;
+
+       r = 0;
+       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 if (strcmp(cp, "STOP") == 0)
+                       sig = SIGSTOP;
+               else if (strcmp(cp, "CONT") == 0)
+                       sig = SIGCONT;
+               else if (strcmp(cp, "USR1") == 0)
+                       sig = SIGUSR1;
+               else if (strcmp(cp, "USR2") == 0)
+                       sig = SIGUSR2;
+               else if (strcmp(cp, "TERM") == 0)
+                       sig = SIGTERM;
+               else
+               {
+                       sig = 0;
+
+                       while (isDecimal(*cp))
+                               sig = sig * 10 + *cp++ - '0';
+
+                       if (*cp)
+                       {
+                               fprintf(stderr, "Unknown signal\n");
+
+                               return 1;
+                       }
+               }
+
+               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 1;
+               }
+
+               if (kill(pid, sig) < 0)
+               {
+                       perror(*argv);
+                       r = 1;
+               }
+       }
+
+       return r;
+}
+
+
+int
+do_where(int argc, const char ** argv)
+{
+       const char *    program;
+       const char *    dirName;
+       char *          path;
+       char *          endPath;
+       char *          fullPath;
+       BOOL            found;
+       int             r;
+
+       found = FALSE;
+       program = argv[1];
+
+       if (strchr(program, '/') != NULL)
+       {
+               fprintf(stderr, "Program name cannot include a path\n");
+
+               return 1;
+       }
+
+       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 1;
+       }
+
+       /*
+        * 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)
+                       {
+                               perror(fullPath);
+                               r = 1;
+                       }
+
+                       continue;
+               }
+
+               printf("%s\n", fullPath);
+               found = TRUE;
+       }
+
+       if (!found)
+       {
+               printf("Program \"%s\" not found in PATH\n", program);
+               r = 1;
+       }
+
+       return r;
+}
+
+#if HAVE_LINUX_LOSETUP
+
+int
+do_losetup(int argc, const char ** argv)
+{
+       int loopfd;
+       int targfd;
+       struct loop_info loopInfo;
+
+       if (!strcmp(argv[1], "-d"))
+       {
+               loopfd = open(argv[2], O_RDWR);
+
+               if (loopfd < 0)
+               {
+                       fprintf(stderr, "Error opening %s: %s\n", argv[2], 
+                               strerror(errno));
+
+                       return 1;
+               }
+
+               if (ioctl(loopfd, LOOP_CLR_FD, 0))
+               {
+                       fprintf(stderr, "Error unassociating device: %s\n", 
+                               strerror(errno));
+
+                       return 1;
+               }
+       }
+
+       loopfd = open(argv[1], O_RDWR);
+
+       if (loopfd < 0)
+       {
+               fprintf(stderr, "Error opening %s: %s\n", argv[1], 
+                       strerror(errno));
+
+               return 1;
+       }
+
+       targfd = open(argv[2], O_RDWR);
+
+       if (targfd < 0)
+       {
+               fprintf(stderr, "Error opening %s: %s\n", argv[2], 
+                       strerror(errno));
+
+               return 1;
+       }
+
+       if (ioctl(loopfd, LOOP_SET_FD, targfd))
+       {
+               fprintf(stderr, "Error setting up loopback device: %s\n", 
+                       strerror(errno));
+
+               return 1;
+       }
+
+       memset(&loopInfo, 0, sizeof(loopInfo));
+       strcpy(loopInfo.lo_name, argv[2]);
+
+       if (ioctl(loopfd, LOOP_SET_STATUS, &loopInfo))
+       {
+               fprintf(stderr, "Error setting up loopback device: %s\n", 
+                       strerror(errno));
+
+               return 1;
+       }
+
+       return 0;
+}
+
+#endif
+
+/* END CODE */
diff --git a/src/sash/dirent.h b/src/sash/dirent.h
new file mode 100644 (file)
index 0000000..77db3b4
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef DIRENT_H_
+#define DIRENT_H_
+
+typedef struct {} DIR;
+
+struct dirent {
+       char *d_name;
+};
+
+#endif // DIRENT_H_
diff --git a/src/sash/sash.1 b/src/sash/sash.1
new file mode 100644 (file)
index 0000000..8c61b9b
--- /dev/null
@@ -0,0 +1,591 @@
+.TH SASH 1
+.SH NAME
+sash \- stand-alone shell with built-in commands
+.SH SYNOPSYS
+.B sash [-c command] [-f fileName ] [-p prompt] [-q] [-a]
+.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 or important executables.
+.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 non-wildcard 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
+     -ar, -chattr, -chgrp, -chmod, -chown, -chroot, -cmp,
+     -cp, -dd, -echo, -ed, -grep, -file, -find, -gunzip,
+     -gzip, -kill, -losetup, -ln, -ls, -lsattr, -mkdir,
+     -mknod, -more, -mount, -mv, -pivot_root, -printenv, -pwd,
+     -rm, -rmdir, -sum, -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, file names are expanded so that asterisks,
+question marks, and characters inside of square brackets are recognised
+and are expanded.
+Arguments can be quoted using single quotes, double quotes, or backslashes.
+However, no other command line processing is performed.
+This includes 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.
+If many external programs will not run, then the "aliasall" command may
+be useful to create multiple aliases.
+.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.
+Arguments containing wildcards can be quoted in order to defer their
+expansion until the alias is invoked.
+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.
+.TP
+.B aliasall
+This defines aliases for all of the built-in commands that start with
+dashes to the corresponding names without the dashes.
+This may be useful when the system is so corrupted that no external
+programs may be executed at all.
+.TP
+.B -ar [txp][v] arfile [filename]...
+List or extract files from an ar archive.
+The arfile argument specifies a file name which contains the archive.
+If no additional filenames are specified, then all files in the archive are
+operated on.
+Otherwise, only those archive members which have the same name 
+as one of the additional filenames are operated on.
+Filenames which do not appear in the archive are ignored.
+Archives cannot be created or modified.
+The archiver correctly handles 4.0BSD archives,
+and understands both the SysV and 4.4BSD extensions for long file names.
+The extended pseudo-BSD formats are not supported;
+nor are the two antediluvian binary formats derived from V7 and earlier.
+(The GNU archiver normally creates archives in the 4.0BSD format with
+SysV extensions.)
+.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 -chattr [+i] [-i] [+a] [-a] fileName ...
+Change the attributes of the specified files on an ext2 or ext3 file system.
+Using a plus sign adds the specified attribute for the files.
+Using a minus sign removes the specified attributes for the files.
+The 'i' attribute makes a file immutable so that it cannot be changed.
+The 'a' attribute makes a file append-only.
+This command is only available on Linux.
+.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 -chroot path
+Changes  the  root  directory to that specified in
+.I path.
+This directory
+will be used for path  names  beginning with /. The root directory is
+inherited by all children of the current process.
+.TP
+.B -cmp fileName1 fileName2
+Determines whether or not the specified file names 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 a convenient way to get a quick list of file names 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 -file fileName ...
+Examine the specified files and print out their file type.
+This indicates whether the files are regular files or not,
+whether they contain printable text or shell scripts,
+are executables, or contain binary data.
+.TP
+.B -find dirName [-xdev] [-type chars] [-name pattern] [-size minSize]
+Find all files contained within the specified directory
+tree which meet all of the specified conditions.
+The -xdev option prevents crossing of mount points.
+The -name option specifies a wildcard pattern to match the last
+component of the file names.
+The -type option specifies that the files must have a type
+matching the specified list from the set: f d c b p s l.
+These represent regular files, directories, character devices,
+block devices, named pipes, sockets, and symbolic links.
+The -size option specifies that the files must be regular files or
+directories which contain at least the specified number of bytes.
+.TP
+.B -grep [-in] word fileName ...
+Display lines of the specified files which contain the given word.
+If only one file name is given, then only the matching lines are
+printed.  If multiple file names are given, then the file names 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 a regular file, then only one input file is allowed,
+and the uncompressed version of that input file is created as the output
+path exactly as specified.
+If the output path is a block or character device, then the uncompressed
+versions of the input files are concatenated to the device.
+.sp
+This command is only available if
+.B sash
+was compiled to use the gzip library.
+.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.
+.sp
+This command is only available if
+.B sash
+was compiled to use the gzip library.
+.TP
+.B help [word]
+Displays a list of built-in commands along with their usage strings.
+If a word is given,
+then just those commands whose name or usage contains the word is displayed.
+If a word is specified which exactly matches a built-in command name,
+then a short description of the command and its usage is given.
+.TP
+.B -kill [-signal] pid ...
+Sends the specified signal to the specified list of processes.
+.I Signal
+is a numeric value, or one of the special values HUP, INT,
+QUIT, KILL, TERM, STOP, CONT, USR1 or USR2.
+If no signal is specified then SIGTERM is used.
+.TP
+.B -losetup [-d] loopDev [file]
+Associates loopback devices with files on the system. If
+.I -d
+is not given,
+the loopback device
+.I loopDev
+is associated with
+.I file.
+If
+.I -d
+is given,
+.I loopDev
+is unassociated with the file it's currently configured for.
+.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 [-lidFC] fileName ...
+Display information about the specified list of file names.
+The normal listing is simply a list of file names, one per line.
+The options available are -l, -n, -i, -d, and -F.
+The -l option produces a long listing giving the normal 'ls' information.
+The -n option is like -l except that numeric user and group ids are shown.
+The -i option displays the inode numbers of the files.
+The -d option displays information about a directory, instead of the
+files within it.
+The -F option appends a slash or asterisk to the file name if the file
+is a directory or is executable.
+The -C option displays the file names in a multi-column format.
+The width of the output is calculated using the COLS environment variable.
+.TP
+.B -lsattr fileName ...
+Display attributes for the specified files on an ext2 or ext3 file system.
+The letter 'i' indicates that the file is immutable and cannot change.
+The letter 'a' indicates that the file is append-only.
+Dashes are shown where the attributes are not set.
+This command is only available on Linux.
+.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 file names, 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] [-s] [-e] [-m] devName dirName
+Mount a filesystem on a directory name.
+The -t option specifies the type of filesystem being mounted,
+and defaults to "ext3" for Linux and "ffs" for BSD.
+The -r option indicates to mount the filesystem read-only.
+The -s option indicates to mount the filesystem no-suid.
+The -e option indicates to mount the filesystem no-exec.
+The -m option indicates to remount an already mounted filesystem.
+The -m option is only available on Linux.
+.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 copies and deletes are done instead.
+.TP
+.B -pivot_root newRoot putOld
+Moves the root file system of the current process to the directory
+.I putOld
+and makes
+.I newRoot
+the  new root file system of the current process.
+.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 file name.
+.TP
+.B -sum fileName ...
+Calculates checksums for one or more files.
+This is the 16 bit checksum compatible with the BSD sum program.
+.TP
+.B -sync
+Do a "sync" system call to force dirty blocks out to the disk.
+.TP
+.B -tar [ctxv]f tarFileName [fileName] ...
+Create, list or extract files from a tar archive.
+The f option must be specified, and accepts a device or file name
+argument which contains the tar archive.
+When creating, at least one file name must be specified to be stored.
+If a file name is a directory, then all the files and directories
+within the directory are stored.
+Linked files and other special file types are not handled properly.
+When listing or extracting files, only those files starting with
+the specified file names are processed.
+If no file names are specified, then all files in the archive are processed.
+Leading slashes in the tar archive file names are always removed so that you
+might need to cd to "/" to restore files which had absolute paths.
+.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 [-f] fileName
+Unmounts a file system.  The file name can either be the device name
+which is mounted, or else the directory name which the file system
+is mounted onto.
+The -f option unmounts the filesystem even if it is being used.
+The -f option is only available on BSD.
+.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 .
+.PP
+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 -f option executes the commands contained in the file name specified
+by the next argument, and then exits.
+This feature can be used to create executable scripts for
+.B sash
+by starting the script file with a line similar to:
+.nf
+       #! /bin/sash -f
+.fi
+.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.
+This option is also implied if the -c or -f options are used.
+.PP
+The -a option creates aliases for the built-in commands so
+that they replace the corresponding standard commands.
+This is the same result as if the 'aliasall' command was used.
+.SH SYSTEM RECOVERY
+This section contains some useful information about using
+.B sash
+with
+.B lilo
+to perform system recovery in some situations.
+Similar concepts should exist for other boot loaders and operating systems.
+.PP
+When important shared libraries are being upgraded,
+it might be a good idea to have
+.B sash
+already running on a console by itself.
+Then if there is a problem with the shared libraries
+.B sash
+will be unaffected and you may be able to use it to fix the problem.
+.PP
+If a problem with the system shows up at boot time so that you cannot
+enter multi-user mode and log in,
+then you can first try booting into single-user mode by adding the
+.I single
+keyword after your kernel image name at the
+.B lilo
+prompt.
+If you manage to reach a shell prompt, then you can run
+.B sash
+from that shell (if necessary).
+One reason for doing this is that you might need to use the
+.B -mount
+command with the -m option to remount the root file system
+so that it can be modified.
+.PP
+If you cannot reach the shell in single-user mode,
+then you can try running sash directly as a replacement for the init process.
+This is done by adding the
+.I init=/bin/sash
+keyword after your kernel image name at the
+.B lilo
+prompt.
+When this is done, then the use of the
+.B aliasall
+command might be useful to reduce attempts to access the root file system
+when running commands.
+.PP
+If your root file system is so corrupted that you cannot get
+.B sash
+to run at all, then you will have to resort to a system recovery floppy.
+.SH WARNINGS
+.B Sash
+should obviously be linked statically, otherwise its purpose is lost.
+Note that even if the rest of the program is linked statically, the
+password and group lookup routines in the C library can still be dynamic.
+For that reason, if there are problems then it might be necessary to
+only use numeric ids for the -chown and -chgrp commands and to use
+the -n option instead of -l for the -ls command.
+.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@tip.net.au
+5 March 2014
+.fi
diff --git a/src/sash/sash.c b/src/sash/sash.c
new file mode 100644 (file)
index 0000000..8c15711
--- /dev/null
@@ -0,0 +1,1373 @@
+/*
+ * Copyright (c) 2014 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 <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <errno.h>
+
+#include "sash.h"
+
+
+static const char * const      version = "3.8";
+
+
+/*
+ * The special maximum argument value which means that there is
+ * no limit to the number of arguments on the command line.
+ */
+#define        INFINITE_ARGS   0x7fffffff
+
+
+/*
+ * One entry of the command table.
+ */
+typedef struct
+{
+       const char *    name;
+       int             (*func)(int argc, const char ** argv);
+       int             minArgs;
+       int             maxArgs;
+       const char *    description;
+       const char *    usage;
+} CommandEntry;
+
+
+/*
+ * The table of built-in commands.
+ * This is terminated wih an entry containing NULL values.
+ */
+static const CommandEntry      commandEntryTable[] =
+{
+       {
+               "alias",        do_alias,       1,      INFINITE_ARGS,
+               "Define a command alias",
+               "[name [command]]"
+       },
+
+       {
+               "aliasall",     do_aliasall,    1,      1,
+               "Define aliases for all of the build-in commands",
+               ""
+       },
+
+       {
+               "-ar",          do_ar,          3,      INFINITE_ARGS,
+               "Extract or list files from an AR file",
+               "[txp]v arFileName fileName ..."
+       },
+
+       {
+               "cd",           do_cd,          1,      2,
+               "Change current directory",
+               "[dirName]"
+       },
+
+#if    HAVE_LINUX_ATTR
+       {
+               "-chattr",      do_chattr,      3,      INFINITE_ARGS,
+               "Change ext2 file attributes",
+               "[+i] [-i] [+a] [-a] fileName ..."
+       },
+#endif
+
+       {
+               "-chgrp",       do_chgrp,       3,      INFINITE_ARGS,
+               "Change the group id of some files",
+               "gid fileName ..."
+       },
+
+       {
+               "-chmod",       do_chmod,       3,      INFINITE_ARGS,
+               "Change the protection of some files",
+               "mode fileName ..."
+       },
+
+       {
+               "-chown",       do_chown,       3,      INFINITE_ARGS,
+               "Change the owner id of some files",
+               "uid fileName ..."
+       },
+
+       {
+               "-cmp",         do_cmp,         3,      3,
+               "Compare two files for equality",
+               "fileName1 fileName2"
+       },
+
+       {
+               "-cp",          do_cp,          3,      INFINITE_ARGS,
+               "Copy files",
+               "srcName ... destName"
+       },
+
+#ifdef HAVE_LINUX_CHROOT
+       {
+               "-chroot",      do_chroot,      2,      2,
+               "change root file system",
+               "new_root_dir"
+       },
+#endif
+
+       {
+               "-dd",          do_dd,          3,      INFINITE_ARGS,
+               "Copy data between two files",
+               "if=name of=name [bs=n] [count=n] [skip=n] [seek=n]"
+       },
+
+       {
+               "-echo",        do_echo,        1,      INFINITE_ARGS,
+               "Echo the arguments",
+               "[args] ..."
+       },
+
+       {
+               "-ed",          do_ed,          1,      2,
+               "Edit a fileName using simple line mode commands",
+               "[fileName]"
+       },
+
+       {
+               "exec",         do_exec,        2,      INFINITE_ARGS,
+               "Execute another program in place of this sash process",
+               "fileName [args]"
+       },
+
+       {
+               "exit",         do_exit,        1,      2,
+               "Exit from sash",
+               "[exit value]"
+       },
+
+       {
+               "-file",        do_file,        1,      INFINITE_ARGS,
+               "Describe information about files",
+               "fileName ..."
+       },
+
+       {
+               "-find",        do_find,        2,      INFINITE_ARGS,
+               "Find files in a directory tree meeting some conditions",
+               "dirName [-xdev] [-type chars] [-name pattern] [-size minSize]"
+       },
+
+       {
+               "-grep",        do_grep,        3,      INFINITE_ARGS,
+               "Look for lines containing a word in some files",
+               "[-in] word fileName ..."
+       },
+
+#if    HAVE_GZIP
+       {
+               "-gunzip",      do_gunzip,      2,      INFINITE_ARGS,
+               "Uncompress files which were saved in GZIP or compress format",
+               "fileName ... [-o outputPath]"
+       },
+
+       {
+               "-gzip",        do_gzip,        2,      INFINITE_ARGS,
+               "Compress files into GZIP format",
+               "fileName ... [-o outputPath]"
+       },
+#endif
+
+       {
+               "help",         do_help,        1,      2,
+               "Print help about a command",
+               "[word]"
+       },
+
+       {
+               "-kill",        do_kill,        2,      INFINITE_ARGS,
+               "Send a signal to the specified process",
+               "[-sig] pid ..."
+       },
+
+#ifdef HAVE_LINUX_LOSETUP
+       {
+               "-losetup",     do_losetup,     3,      3,
+               "Associate a loopback device with a file",
+               "[-d] device\n       -losetup device filename"
+       },
+#endif
+
+       {
+               "-ln",          do_ln,          3,      INFINITE_ARGS,
+               "Link one fileName to another",
+               "[-s] srcName ... destName"
+       },
+
+       {
+               "-ls",          do_ls,          1,      INFINITE_ARGS,
+               "List information about files or directories",
+               "[-lidFC] fileName ..."
+       },
+
+#if    HAVE_LINUX_ATTR
+       {
+               "-lsattr",      do_lsattr,      2,      INFINITE_ARGS,
+               "List ext2 file attributes",
+               "fileName ..."
+       },
+#endif
+
+       {
+               "-mkdir",       do_mkdir,       2,      INFINITE_ARGS,
+               "Create a directory",
+               "dirName ..."
+       },
+
+       {
+               "-mknod",       do_mknod,       5,      5,
+               "Create a special type of file",
+               "fileName type major minor"
+       },
+
+       {
+               "-more",        do_more,        2,      INFINITE_ARGS,
+               "Type file contents page by page",
+               "fileName ..."
+       },
+
+       {
+               "-mount",       do_mount,       3,      INFINITE_ARGS,
+               "Mount or remount a filesystem on a directory",
+#if    HAVE_LINUX_MOUNT
+               "[-t type] [-r] [-s] [-e] [-m] devName dirName"
+#elif  HAVE_BSD_MOUNT
+               "[-t type] [-r] [-s] [-e] devName dirName"
+#else
+               "[-t type] devName dirName"
+#endif
+       },
+
+       {
+               "-mv",          do_mv,          3,      INFINITE_ARGS,
+               "Move or rename files",
+               "srcName ... destName"
+       },
+
+#ifdef HAVE_LINUX_PIVOT
+       {
+               "-pivot_root",  do_pivot_root,  3,      3,
+               "pivot the root file system",
+               "new_dir old_dir"
+       },
+#endif
+
+       {
+               "-printenv",    do_printenv,    1,      2,
+               "Print environment variables",
+               "[name]"
+       },
+
+       {
+               "prompt",       do_prompt,      2,      INFINITE_ARGS,
+               "Set the prompt string for sash",
+               "string"
+       },
+
+       {
+               "-pwd",         do_pwd,         1,      1,
+               "Print the current working directory",
+               ""
+       },
+
+       {
+               "quit",         do_exit,        1,      1,
+               "Exit from sash",
+               ""
+       },
+
+       {
+               "-rm",          do_rm,          2,      INFINITE_ARGS,
+               "Remove the specified files",
+               "fileName ..."
+       },
+
+       {
+               "-rmdir",       do_rmdir,       2,      INFINITE_ARGS,
+               "Remove the specified empty directories",
+               "dirName ..."
+       },
+
+       {
+               "setenv",       do_setenv,      3,      3,
+               "Set an environment variable value",
+               "name value"
+       },
+
+       {
+               "source",       do_source,      2,      2,
+               "Read commands from the specified file",
+               "fileName"
+       },
+
+       {
+               "-sum",         do_sum,         2,      INFINITE_ARGS,
+               "Calculate checksums of the specified files",
+               "fileName ..."
+       },
+
+       {
+               "-sync",        do_sync,        1,      1,
+               "Sync the disks to force cached data to them",
+               ""
+       },
+
+       {
+               "-tar",         do_tar,         2,      INFINITE_ARGS,
+               "Create, extract, or list files from a TAR file",
+               "[cxtv]f tarFileName fileName ..."
+       },
+
+       {
+               "-touch",       do_touch,       2,      INFINITE_ARGS,
+               "Update times or create the specified files",
+               "fileName ..."
+       },
+
+       {
+               "umask",        do_umask,       1,      2,
+               "Set the umask value for file protections",
+               "[mask]"
+       },
+
+       {
+#if    HAVE_BSD_MOUNT
+               "-umount",      do_umount,      2,      3,
+               "Unmount a filesystem",
+               "[-f] fileName"
+#else
+               "-umount",      do_umount,      2,      2,
+               "Unmount a filesystem",
+               "fileName"
+#endif
+       },
+
+       {
+               "unalias",      do_unalias,     2,      2,
+               "Remove a command alias",
+               "name"
+       },
+
+       {
+               "-where",       do_where,       2,      2,
+               "Type the location of a program",
+               "program"
+       },
+
+       {
+               NULL,           0,              0,      0,
+               NULL,
+               NULL
+       }
+};
+
+
+/*
+ * The definition of an command alias.
+ */
+typedef struct
+{
+       char *  name;
+       char *  value;
+} Alias;
+
+
+/*
+ * Local data.
+ */
+static Alias * aliasTable;
+static int     aliasCount;
+
+static FILE *  sourcefiles[MAX_SOURCE];
+static int     sourceCount;
+
+static BOOL    intCrlf = TRUE;
+static char *  prompt;
+
+
+/*
+ * Local procedures.
+ */
+static void    catchInt(int);
+static void    catchQuit(int);
+static int     readFile(const char * name);
+static int     command(const char * cmd);
+static BOOL    tryBuiltIn(const char * cmd);
+static int     runCmd(const char * cmd);
+static void    childProcess(const char * cmd);
+static void    showPrompt(void);
+static void    usage(void);
+static Alias * findAlias(const char * name);
+static void    expandVariable(char * name);
+
+
+/*
+ * Global interrupt flag.
+ */
+BOOL   intFlag;
+
+
+int
+main(int argc, const char ** argv)
+{
+       const char *    cp;
+       const char *    singleCommand;
+       const char *    commandFile;
+       BOOL            quietFlag;
+       BOOL            aliasFlag;
+        BOOL           interactiveFlag;
+       char            buf[PATH_LEN];
+
+       singleCommand = NULL;
+       commandFile = NULL;
+       quietFlag = FALSE;
+       aliasFlag = FALSE;
+        interactiveFlag = FALSE;
+
+       /*
+        * Look for options.
+        */
+       argv++;
+       argc--;
+
+       while ((argc > 0) && (**argv == '-'))
+       {
+               cp = *argv++ + 1;
+               argc--;
+
+               while (*cp) switch (*cp++)
+               {
+                        case '-':
+                                /*
+                                 * Ignore.  This is so that we can be
+                                 * run from login.
+                                 */
+                                break;
+
+                       case 'c':
+                               /*
+                                * Execute specified command.
+                                */
+                               if ((argc != 1) || singleCommand || interactiveFlag)
+                                       usage();
+
+                               singleCommand = *argv++;
+                               argc--;
+
+                               break;
+
+                       case 'f':
+                               /*
+                                * Execute commands from file.
+                                * This is used for sash script files.
+                                * The quiet flag is also set.
+                                */
+                               if ((argc != 1) || commandFile)
+                                       usage();
+
+                               quietFlag = TRUE;
+                               commandFile = *argv++;
+                               argc--;
+
+                               break;
+
+                       case 'i':
+                               /*
+                                * Be an interactive shell
+                                * ..is a no-op, but some contexts require this
+                                * ..interactiveFlag is to avoid -ic as a legacy
+                                */
+                                if (singleCommand)
+                                       usage();
+                                
+                                interactiveFlag = TRUE;
+                                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 'a':
+                               aliasFlag = 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 the alias flag is set then define all aliases.
+        */
+       if (aliasFlag)
+               do_aliasall(0, NULL);
+
+       /*
+        * If we are to execute a single command, then do so and exit.
+        */
+       if (singleCommand)
+       {
+               return command(singleCommand);
+       }
+
+       /*
+        * Print a hello message unless we are told to be silent.
+        */
+       if (!quietFlag && isatty(STDIN))
+       {
+               printf("Stand-alone shell (version %s)\n", version);
+
+               if (aliasFlag)
+                       printf("Built-in commands are aliased to standard commands\n");
+       }
+
+       signal(SIGINT, catchInt);
+       signal(SIGQUIT, catchQuit);
+
+       /*
+        * Execute the user's alias file if present.
+        */
+       cp = getenv("HOME");
+
+       if (cp)
+       {
+               strcpy(buf, cp);
+               strcat(buf, "/");
+               strcat(buf, ".aliasrc");
+
+               if ((access(buf, 0) == 0) || (errno != ENOENT))
+                       readFile(buf);
+       }
+
+       /*
+        * Read commands from stdin or from a command file.
+        */
+       return readFile(commandFile);
+
+}
+
+
+/*
+ * Read commands from the specified file.
+ * A null name pointer indicates to read from stdin.
+ */
+static int
+readFile(const char * name)
+{
+       FILE *  fp;
+       int     cc;
+       BOOL    ttyFlag;
+       char    buf[CMD_LEN];
+       int     r = 0;
+
+       if (sourceCount >= MAX_SOURCE)
+       {
+               fprintf(stderr, "Too many source files\n");
+
+               return 1;
+       }
+
+       fp = stdin;
+
+       if (name)
+       {
+               fp = fopen(name, "r");
+
+               if (fp == NULL)
+               {
+                       perror(name);
+
+                       return 1;
+               }
+       }
+
+       sourcefiles[sourceCount++] = fp;
+
+       ttyFlag = isatty(fileno(fp));
+
+       while (TRUE)
+       {
+               if (ttyFlag)
+                       showPrompt();
+
+               if (intFlag && !ttyFlag && (fp != stdin))
+               {
+                       fclose(fp);
+                       sourceCount--;
+
+                       return 1;
+               }
+       
+               if (fgets(buf, CMD_LEN - 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';
+
+               r = command(buf);
+       }
+
+       if (ferror(fp))
+       {
+               perror("Reading command line");
+
+               if (fp == stdin)
+                       exit(1);
+       }
+
+       clearerr(fp);
+
+       if (fp != stdin)
+               fclose(fp);
+
+       sourceCount--;
+
+       return r;
+}
+
+
+/*
+ * 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 int
+command(const char * cmd)
+{
+       const char *    endCmd;
+       const Alias *   alias;
+       char            newCommand[CMD_LEN];
+       char            cmdName[CMD_LEN];
+
+       /*
+        * Rest the interrupt flag and free any memory chunks that
+        * were allocated by the previous command.
+        */
+       intFlag = FALSE;
+
+       freeChunks();
+
+       /*
+        * Skip leading blanks.
+        */
+       while (isBlank(*cmd))
+               cmd++;
+
+       /*
+        * If the command is empty or is a comment then ignore it.
+        */
+       if ((*cmd == '\0') || (*cmd == '#'))
+               return 0;
+
+       /*
+        * Look for the end of the command name and then copy the
+        * command name to a buffer so we can null terminate it.
+        */
+       endCmd = cmd;
+
+       while (*endCmd && !isBlank(*endCmd))
+               endCmd++;
+
+       memcpy(cmdName, cmd, endCmd - cmd);
+
+       cmdName[endCmd - cmd] = '\0';
+
+       /*
+        * Search for the command name in the alias table.
+        * If it is found, then replace the command name with
+        * the alias value, and append the current command
+        * line arguments to that.
+        */
+       alias = findAlias(cmdName);
+
+       if (alias)
+       {
+               strcpy(newCommand, alias->value);
+               strcat(newCommand, endCmd);
+
+               cmd = newCommand;
+       }
+
+       /*
+        * Expand simple environment variables
+        */
+       while (strstr(cmd, "$(")) expandVariable((char *)cmd);
+
+       /*
+        * Now look for the command in the builtin table, and execute
+        * the command if found.
+        */
+       if (tryBuiltIn(cmd))
+               return 0; /* This is a blatant lie */
+
+       /*
+        * The command is not a built-in, so run the program along
+        * the PATH list.
+        */
+       return runCmd(cmd);
+}
+
+
+/*
+ * 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(const char * cmd)
+{
+       const char *            endCmd;
+       const CommandEntry *    entry;
+       int                     argc;
+       const char **           argv;
+       char                    cmdName[CMD_LEN];
+
+       /*
+        * Look for the end of the command name and then copy the
+        * command name to a buffer so we can null terminate it.
+        */
+       endCmd = cmd;
+
+       while (*endCmd && !isBlank(*endCmd))
+               endCmd++;
+
+       memcpy(cmdName, cmd, endCmd - cmd);
+
+       cmdName[endCmd - cmd] = '\0';
+
+       /*
+        * Search the command table looking for the command name.
+        */
+       for (entry = commandEntryTable; entry->name != NULL; entry++)
+       {
+               if (strcmp(entry->name, cmdName) == 0)
+                       break;
+       }
+
+       /*
+        * If the command is not a built-in, return indicating that.
+        */
+       if (entry->name == NULL)
+               return FALSE;
+
+       /*
+        * The command is a built-in.
+        * Break the command up into arguments and expand wildcards.
+        */
+       if (!makeArgs(cmd, &argc, &argv))
+               return TRUE;
+
+       /*
+        * Give a usage string if the number of arguments is too large
+        * or too small.
+        */
+       if ((argc < entry->minArgs) || (argc > entry->maxArgs))
+       {
+               fprintf(stderr, "usage: %s %s\n", entry->name, entry->usage);
+
+               return TRUE;
+       }
+
+       /*
+        * Call the built-in function with the argument list.
+        */
+       entry->func(argc, argv);
+
+       return TRUE;
+}
+
+
+/*
+ * Execute the specified command either by forking and executing
+ * the program ourself, or else by using the shell.  Returns the
+ * exit status, or -1 if the program cannot be executed at all.
+ */
+static int
+runCmd(const char * cmd)
+{
+       const char *    cp;
+       BOOL            magic;
+       pid_t           pid;
+       int             status;
+
+       /*
+        * Check the command for any magic shell characters
+        * except for quoting.
+        */
+       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 == ',') || (*cp == '\'') ||
+                       (*cp == '"'))
+               {
+                       continue;
+               }
+
+               magic = TRUE;
+       }
+
+       /*
+        * If there were any magic characters used then run the
+        * command using the shell.
+        */
+       if (magic)
+               return trySystem(cmd);
+
+       /*
+        * No magic characters were in the command, so we can do the fork
+        * and exec ourself.
+        */
+       pid = fork();
+
+       if (pid < 0)
+       {
+               perror("fork failed");
+
+               return -1;
+       }
+
+       /*
+        * If we are the child process, then go execute the program.
+        */
+       if (pid == 0)
+               childProcess(cmd);
+
+       /*
+        * We are the parent process.
+        * Wait for the child to complete.
+        */
+       status = 0;
+       intCrlf = FALSE;
+
+       while (((pid = waitpid(pid, &status, 0)) < 0) && (errno == EINTR))
+               ;
+
+       intCrlf = TRUE;
+
+       if (pid < 0)
+       {
+               fprintf(stderr, "Error from waitpid: %s", strerror(errno));
+
+               return -1;
+       }
+
+       if (WIFSIGNALED(status))
+       {
+               fprintf(stderr, "pid %ld: killed by signal %d\n",
+                       (long) pid, WTERMSIG(status));
+
+               return -1;
+       }
+
+       return WEXITSTATUS(status);
+}
+
+
+/*
+ * Here as the child process to try to execute the command.
+ * This is only called if there are no meta-characters in the command.
+ * This procedure never returns.
+ */
+static void
+childProcess(const char * cmd)
+{
+       const char **   argv;
+       int             argc;
+
+       /*
+        * Close any extra file descriptors we have opened.
+        */     
+       while (--sourceCount >= 0)
+       {
+               if (sourcefiles[sourceCount] != stdin)
+                       fclose(sourcefiles[sourceCount]);
+       }
+
+       /*
+        * Break the command line up into individual arguments.
+        * If this fails, then run the shell to execute the command.
+        */
+       if (!makeArgs(cmd, &argc, &argv))
+       {
+               int status = trySystem(cmd);
+
+               if (status == -1)
+                       exit(99);
+
+               exit(status);
+       }
+
+       /*
+        * Try to execute the program directly.
+        */
+       execvp(argv[0], (char **) argv);
+
+       /*
+        * The exec failed, so try to run the command using the shell
+        * in case it is a shell script.
+        */
+       if (errno == ENOEXEC)
+       {
+               int status = trySystem(cmd);
+
+               if (status == -1)
+                       exit(99);
+
+               exit(status);
+       }
+
+       /*
+        * There was something else wrong, complain and exit.
+        */
+       perror(argv[0]);
+       exit(1);
+}
+
+
+int
+do_help(int argc, const char ** argv)
+{
+       const CommandEntry *    entry;
+       const char *            str;
+
+       str = NULL;
+
+       if (argc == 2)
+               str = argv[1];
+
+       /*
+        * Check for an exact match, in which case describe the program.
+        */
+       if (str)
+       {
+               for (entry = commandEntryTable; entry->name; entry++)
+               {
+                       if (strcmp(str, entry->name) == 0)
+                       {
+                               printf("%s\n", entry->description);
+
+                               printf("usage: %s %s\n", entry->name,
+                                       entry->usage);
+
+                               return 0;
+                       }
+               }
+       }
+
+       /*
+        * Print short information about commands which contain the
+        * specified word.
+        */
+       for (entry = commandEntryTable; entry->name; entry++)
+       {
+               if ((str == NULL) || (strstr(entry->name, str) != NULL) ||
+                       (strstr(entry->usage, str) != NULL))
+               {
+                       printf("%-10s %s\n", entry->name, entry->usage);
+               }
+       }
+
+       return 0;
+}
+
+
+int
+do_alias(int argc, const char ** argv)
+{
+       const char *    name;
+       char *          value;
+       Alias *         alias;
+       int             count;
+       char            buf[CMD_LEN];
+
+       if (argc < 2)
+       {
+               count = aliasCount;
+
+               for (alias = aliasTable; count-- > 0; alias++)
+                       printf("%s\t%s\n", alias->name, alias->value);
+
+               return 0;
+       }
+
+       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 1;
+               }
+
+               return 0;       
+       }
+
+       if (strcmp(name, "alias") == 0)
+       {
+               fprintf(stderr, "Cannot alias \"alias\"\n");
+
+               return 1;
+       }
+
+       if (!makeString(argc - 2, argv + 2, buf, CMD_LEN))
+               return 1;
+
+       value = malloc(strlen(buf) + 1);
+
+       if (value == NULL)
+       {
+               fprintf(stderr, "No memory for alias value\n");
+
+               return 1;
+       }
+
+       strcpy(value, buf);
+
+       alias = findAlias(name);
+
+       if (alias)
+       {
+               free(alias->value);
+               alias->value = value;
+
+               return 0;
+       }
+
+       if ((aliasCount % ALIAS_ALLOC) == 0)
+       {
+               count = aliasCount + ALIAS_ALLOC;
+
+               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 1;
+               }
+
+               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 1;
+       }
+
+       strcpy(alias->name, name);
+       alias->value = value;
+       aliasCount++;
+
+       return 0;
+}
+
+
+/*
+ * Build aliases for all of the built-in commands which start with a dash,
+ * using the names without the dash.
+ */
+int
+do_aliasall(int argc, const char **argv)
+{
+       const CommandEntry *    entry;
+       const char *            name;
+       const char *            newArgv[4];
+
+       for (entry = commandEntryTable; entry->name; entry++)
+       {
+               name = entry->name;
+
+               if (*name != '-')
+                       continue;
+
+               newArgv[0] = "alias";
+               newArgv[1] = name + 1;
+               newArgv[2] = name;
+               newArgv[3] = NULL;
+
+               do_alias(3, newArgv);
+       }
+
+       return 0;
+}
+
+
+/*
+ * Look up an alias name, and return a pointer to it.
+ * Returns NULL if the name does not exist.
+ */
+static Alias *
+findAlias(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;
+}
+
+
+int
+do_source(int argc, const char ** argv)
+{
+       return readFile(argv[1]);
+}
+
+
+int
+do_exec(int argc, const char ** argv)
+{
+       const char *    name;
+
+       name = argv[1];
+
+       while (--sourceCount >= 0)
+       {
+               if (sourcefiles[sourceCount] != stdin)
+                       fclose(sourcefiles[sourceCount]);
+       }
+
+       argv[argc] = NULL;
+
+       execvp(name, (char **) argv + 1);
+       perror(name);
+
+       return 1;
+}
+
+
+int
+do_prompt(int argc, const char ** argv)
+{
+       char *  cp;
+       char    buf[CMD_LEN];
+
+       if (!makeString(argc - 1, argv + 1, buf, CMD_LEN))
+               return 1;
+
+       cp = malloc(strlen(buf) + 2);
+
+       if (cp == NULL)
+       {
+               fprintf(stderr, "No memory for prompt\n");
+
+               return 1;
+       }
+
+       strcpy(cp, buf);
+       strcat(cp, " ");
+
+       if (prompt)
+               free(prompt);
+
+       prompt = cp;
+
+       return 0;
+}
+
+
+int
+do_unalias(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;    
+       }
+
+       return 0;
+}
+
+
+/*
+ * Display the prompt string.
+ */
+static void
+showPrompt(void)
+{
+       const char *    cp;
+
+       cp = "> ";
+
+       if (prompt)
+               cp = prompt;
+
+       tryWrite(STDOUT, cp, strlen(cp));
+}      
+
+
+static void
+catchInt(int val)
+{
+       signal(SIGINT, catchInt);
+
+       intFlag = TRUE;
+
+       if (intCrlf)
+               tryWrite(STDOUT, "\n", 1);
+}
+
+
+static void
+catchQuit(int val)
+{
+       signal(SIGQUIT, catchQuit);
+
+       intFlag = TRUE;
+
+       if (intCrlf)
+               tryWrite(STDOUT, "\n", 1);
+}
+
+
+/*
+ * Print the usage information and quit.
+ */
+static void
+usage(void)
+{
+       fprintf(stderr, "Stand-alone shell (version %s)\n", version);
+       fprintf(stderr, "\n");
+       fprintf(stderr, "Usage: sash [-a] [-q] [-f fileName] [-c command] [-p prompt] [-i]\n");
+
+       exit(1);
+}
+
+
+/*
+ * Expand one environment variable: Syntax $(VAR)
+ */
+static void
+expandVariable(char * cmd)
+{
+       char    tmp[CMD_LEN];
+       char    *cp;
+       char    *ep;
+
+       strcpy(tmp, cmd);
+       cp = strstr(tmp, "$(");
+       if (cp) {
+               *cp++ = '\0';
+               strcpy(cmd, tmp);
+               ep = ++cp;
+               while (*ep && (*ep != ')')) ep++;
+               if (*ep == ')') *ep++ = '\0';
+               cp = getenv(cp);
+               if (cp) strcat(cmd, cp);
+               strcat(cmd, ep);
+       }
+       return;
+}
+
+/* END CODE */
diff --git a/src/sash/sash.h b/src/sash/sash.h
new file mode 100644 (file)
index 0000000..b0ed254
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2014 by David I. Bell
+ * Permission is granted to use, distribute, or modify this source,
+ * provided that this copyright notice remains intact.
+ *
+ * Definitions for stand-alone shell for system maintainance for Linux.
+ */
+
+#ifndef        SASH_H
+#define        SASH_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <memory.h>
+#include <time.h>
+#include <ctype.h>
+
+#if __OpenBSD__
+#include <sys/param.h>
+#endif
+
+#if __Linux__
+#include <malloc.h>
+#endif
+
+
+#define        PATH_LEN        1024
+#define        CMD_LEN         10240
+#define        ALIAS_ALLOC     20
+#define        EXPAND_ALLOC    1024
+#define        STDIN           0
+#define        STDOUT          1
+#define        MAX_SOURCE      10
+#define        BUF_SIZE        8192
+
+
+#define        isBlank(ch)     (((ch) == ' ') || ((ch) == '\t'))
+#define        isDecimal(ch)   (((ch) >= '0') && ((ch) <= '9'))
+#define        isOctal(ch)     (((ch) >= '0') && ((ch) <= '7'))
+#define        isWildCard(ch)  (((ch) == '*') || ((ch) == '?') || ((ch) == '['))
+
+#ifndef MAX
+#define MAX(x, y)      ((x) > (y) ? (x) : (y))
+#endif
+#ifndef MIN
+#define MIN(x, y)      ((x) < (y) ? (x) : (y))
+#endif
+
+typedef        int     BOOL;
+
+#define        FALSE   ((BOOL) 0)
+#define        TRUE    ((BOOL) 1)
+
+
+/*
+ * Built-in command functions.
+ */
+extern int     do_alias(int argc, const char ** argv);
+extern int     do_aliasall(int argc, const char ** argv);
+extern int     do_cd(int argc, const char ** argv);
+extern int     do_exec(int argc, const char ** argv);
+extern int     do_exit(int argc, const char ** argv);
+extern int     do_prompt(int argc, const char ** argv);
+extern int     do_source(int argc, const char ** argv);
+extern int     do_umask(int argc, const char ** argv);
+extern int     do_unalias(int argc, const char ** argv);
+extern int     do_help(int argc, const char ** argv);
+extern int     do_ln(int argc, const char ** argv);
+extern int     do_cp(int argc, const char ** argv);
+extern int     do_mv(int argc, const char ** argv);
+extern int     do_rm(int argc, const char ** argv);
+extern int     do_chmod(int argc, const char ** argv);
+extern int     do_mkdir(int argc, const char ** argv);
+extern int     do_rmdir(int argc, const char ** argv);
+extern int     do_mknod(int argc, const char ** argv);
+extern int     do_chown(int argc, const char ** argv);
+extern int     do_chgrp(int argc, const char ** argv);
+extern int     do_sum(int argc, const char ** argv);
+extern int     do_sync(int argc, const char ** argv);
+extern int     do_printenv(int argc, const char ** argv);
+extern int     do_more(int argc, const char ** argv);
+extern int     do_cmp(int argc, const char ** argv);
+extern int     do_touch(int argc, const char ** argv);
+extern int     do_ls(int argc, const char ** argv);
+extern int     do_dd(int argc, const char ** argv);
+extern int     do_tar(int argc, const char ** argv);
+extern int     do_ar(int argc, const char ** argv);
+extern int     do_mount(int argc, const char ** argv);
+extern int     do_umount(int argc, const char ** argv);
+extern int     do_setenv(int argc, const char ** argv);
+extern int     do_pwd(int argc, const char ** argv);
+extern int     do_echo(int argc, const char ** argv);
+extern int     do_kill(int argc, const char ** argv);
+extern int     do_grep(int argc, const char ** argv);
+extern int     do_file(int argc, const char ** argv);
+extern int     do_find(int argc, const char ** argv);
+extern int     do_ed(int argc, const char ** argv);
+extern int     do_where(int argc, const char ** argv);
+
+#if    HAVE_GZIP
+extern int     do_gzip(int argc, const char ** argv);
+extern int     do_gunzip(int argc, const char ** argv);
+#endif
+
+#if    HAVE_LINUX_ATTR
+extern int     do_lsattr(int argc, const char ** argv);
+extern int     do_chattr(int argc, const char ** argv);
+#endif
+
+#if    HAVE_LINUX_CHROOT
+extern int     do_chroot(int argc, const char ** argv);
+#endif
+
+#if    HAVE_LINUX_LOSETUP
+extern int     do_losetup(int argc, const char ** argv);
+#endif
+
+#if    HAVE_LINUX_PIVOT
+extern int     do_pivot_root(int argc, const char ** argv);
+extern  int    pivot_root(const char *new_root, const char *put_old);
+#endif
+
+
+/*
+ * Global utility routines.
+ */
+extern const char *    modeString(int mode);
+extern const char *    timeString(time_t timeVal);
+extern BOOL            isDirectory(const char * name);
+extern BOOL            isDevice(const char * name);
+extern int             nameSort(const void * p1, const void * p2);
+extern char *          getChunk(int size);
+extern char *          chunkstrdup(const char *);
+extern void            freeChunks(void);
+extern int             trySystem(const char * cmd);
+extern void            tryWrite(int fd, const char * buf, int len);
+extern int             fullWrite(int fd, const char * buf, int len);
+extern int             fullRead(int fd, char * buf, int len);
+extern void            checkStatus(const char * name, int status);
+extern BOOL            match(const char * text, const char * pattern);
+
+extern const char *    buildName
+       (const char * dirName, const char * fileName);
+
+extern BOOL    makeArgs
+       (const char * cmd, int * argcPtr, const char *** argvPtr);
+
+extern BOOL    copyFile
+       (const char * srcName, const char * destName, BOOL setModes);
+
+extern BOOL    makeString
+       (int argc, const char ** argv, char * buf, int bufLen);
+
+extern int     expandWildCards
+       (const char * fileNamePattern, const char *** retFileTable);
+
+
+/*
+ * Global variable to indicate that an SIGINT occurred.
+ * This is used to stop processing.
+ */
+extern BOOL    intFlag;
+
+#endif
+
+/* END CODE */
diff --git a/src/sash/utils.c b/src/sash/utils.c
new file mode 100644 (file)
index 0000000..f6bee83
--- /dev/null
@@ -0,0 +1,1144 @@
+/*
+ * Copyright (c) 2014 by David I. Bell
+ * Permission is granted to use, distribute, or modify this source,
+ * provided that this copyright notice remains intact.
+ *
+ * Utility routines.
+ */
+
+#include "sash.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "dirent.h"
+#include <utime.h>
+
+
+/*
+ * A chunk of data.
+ * Chunks contain data which is allocated as needed, but which is
+ * not freed until all of the data needs freeing, such as at
+ * the beginning of the next command.
+ */
+typedef        struct  chunk   CHUNK;
+#define        CHUNK_INIT_SIZE 4
+
+struct chunk
+{
+       CHUNK * next;
+       char    data[CHUNK_INIT_SIZE];  /* 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(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 string 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(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
+isDirectory(const char * name)
+{
+       struct  stat    statBuf;
+
+       if (stat(name, &statBuf) < 0)
+               return FALSE;
+
+       return S_ISDIR(statBuf.st_mode);
+}
+
+
+/*
+ * Return TRUE if a filename is a block or character device.
+ * Nonexistant files return FALSE.
+ */
+BOOL
+isDevice(const char * name)
+{
+       struct  stat    statBuf;
+
+       if (stat(name, &statBuf) < 0)
+               return FALSE;
+
+       return S_ISBLK(statBuf.st_mode) || S_ISCHR(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(
+       const char *    srcName,
+       const char *    destName,
+       BOOL            setModes
+)
+{
+       int             rfd;
+       int             wfd;
+       int             rcc;
+       char            buf[BUF_SIZE];
+       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;
+       }
+
+       checkStatus("close", close(rfd));
+
+       if (close(wfd) < 0)
+       {
+               perror(destName);
+
+               return FALSE;
+       }
+
+       if (setModes)
+       {
+               checkStatus("chmod", chmod(destName, statBuf1.st_mode));
+
+               checkStatus("chown", chown(destName, statBuf1.st_uid, statBuf1.st_gid));
+
+               times.actime = statBuf1.st_atime;
+               times.modtime = statBuf1.st_mtime;
+
+               checkStatus("utime", utime(destName, &times));
+       }
+
+       return TRUE;
+
+
+error_exit:
+       close(rfd);
+       close(wfd);
+
+       return FALSE;
+}
+
+
+/*
+ * Build a path name from the specified directory name and file name.
+ * If the directory name is NULL, then the original fileName is returned.
+ * The built path is in a static area, and is overwritten for each call.
+ */
+const char *
+buildName(const char * dirName, const char * fileName)
+{
+       const char *    cp;
+       static  char    buf[PATH_LEN];
+
+       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 wildcard pattern, 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.  The returned list is only valid until the next
+ * call or until the next command.  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 there was an allocation error.
+ */
+int
+expandWildCards(const char * fileNamePattern, const char *** retFileTable)
+{
+       const char *    last;
+       const char *    cp1;
+       const char *    cp2;
+       const char *    cp3;
+       char *          str;
+       DIR *           dirp;
+       struct dirent * dp;
+       int             dirLen;
+       int             newFileTableSize;
+       char **         newFileTable;
+       char            dirName[PATH_LEN];
+
+       static int      fileCount;
+       static int      fileTableSize;
+       static char **  fileTable;
+
+       /*
+        * Clear the return values until we know their final values.
+        */
+       fileCount = 0;
+       *retFileTable = NULL;
+
+       /*
+        * Scan the file name pattern for any wildcard characters.
+        */
+       cp1 = strchr(fileNamePattern, '*');
+       cp2 = strchr(fileNamePattern, '?');
+       cp3 = strchr(fileNamePattern, '[');
+
+       /*
+        * If there are no wildcard characters then return zero to
+        * indicate that there was actually no wildcard pattern.
+        */
+       if ((cp1 == NULL) && (cp2 == NULL) && (cp3 == NULL))
+               return 0;
+
+       /*
+        * There are wildcards in the specified filename.
+        * Get the last component of the file name.
+        */
+       last = strrchr(fileNamePattern, '/');
+
+       if (last)
+               last++;
+       else
+               last = fileNamePattern;
+
+       /*
+        * If any wildcards were found before the last filename component
+        * then return an error.
+        */
+       if ((cp1 && (cp1 < last)) || (cp2 && (cp2 < last)) ||
+               (cp3 && (cp3 < last)))
+       {
+               fprintf(stderr,
+               "Wildcards only implemented for last file name component\n");
+
+               return -1;
+       }
+
+       /*
+        * Assume at first that we are scanning the current directory.
+        */
+       dirName[0] = '.';
+       dirName[1] = '\0';
+
+       /*
+        * If there was a directory given as part of the file name then
+        * copy it and null terminate it.
+        */
+       if (last != fileNamePattern)
+       {
+               memcpy(dirName, fileNamePattern, last - fileNamePattern);
+               dirName[last - fileNamePattern - 1] = '\0';
+
+               if (dirName[0] == '\0')
+               {
+                       dirName[0] = '/';
+                       dirName[1] = '\0';
+               }
+       }
+
+       /*
+        * Open the directory containing the files to be checked.
+        */
+       dirp = opendir(dirName);
+
+       if (dirp == NULL)
+       {
+               perror(dirName);
+
+               return -1;
+       }
+
+       /*
+        * Prepare the directory name for use in making full path names.
+        */
+       dirLen = strlen(dirName);
+
+       if (last == fileNamePattern)
+       {
+               dirLen = 0;
+               dirName[0] = '\0';
+       }
+       else if (dirName[dirLen - 1] != '/')
+       {
+               dirName[dirLen++] = '/';
+               dirName[dirLen] = '\0';
+       }
+
+       /*
+        * Find all of the files in the directory and check them against
+        * the wildcard pattern.
+        */
+       while ((dp = readdir(dirp)) != NULL)
+       {
+               /*
+                * Skip the current and parent directories.
+                */
+               if ((strcmp(dp->d_name, ".") == 0) ||
+                       (strcmp(dp->d_name, "..") == 0))
+               {
+                       continue;
+               }
+
+               /*
+                * If the file name doesn't match the pattern then skip it.
+                */
+               if (!match(dp->d_name, last))
+                       continue;
+
+               /*
+                * This file name is selected.
+                * See if we need to reallocate the file name table.
+                */
+               if (fileCount >= fileTableSize)
+               {
+                       /*
+                        * Increment the file table size and reallocate it.
+                        */
+                       newFileTableSize = fileTableSize + EXPAND_ALLOC;
+
+                       newFileTable = (char **) realloc((char *) fileTable,
+                               (newFileTableSize * sizeof(char *)));
+
+                       if (newFileTable == NULL)
+                       {
+                               fprintf(stderr, "Cannot allocate file list\n");
+                               closedir(dirp);
+
+                               return -1;
+                       }
+
+                       fileTable = newFileTable;
+                       fileTableSize = newFileTableSize;
+               }
+
+               /*
+                * Allocate space for storing the file name in a chunk.
+                */
+               str = getChunk(dirLen + strlen(dp->d_name) + 1);
+
+               if (str == NULL)
+               {
+                       fprintf(stderr, "No memory for file name\n");
+                       closedir(dirp);
+
+                       return -1;
+               }
+
+               /*
+                * Save the file name in the chunk.
+                */
+               if (dirLen)
+                       memcpy(str, dirName, dirLen);
+
+               strcpy(str + dirLen, dp->d_name);
+
+               /*
+                * Save the allocated file name into the file table.
+                */
+               fileTable[fileCount++] = str;
+       }
+
+       /*
+        * Close the directory and check for any matches.
+        */
+       closedir(dirp);
+
+       if (fileCount == 0)
+       {
+               fprintf(stderr, "No matches\n");
+
+               return -1;
+       }
+
+       /*
+        * Sort the list of file names.
+        */
+       qsort((void *) fileTable, fileCount, sizeof(char *), nameSort);
+
+       /*
+        * Return the file list and count.
+        */
+       *retFileTable = (const char **) fileTable;
+
+       return fileCount;
+}
+
+
+/*
+ * Sort routine for list of fileNames.
+ */
+int
+nameSort(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(const char * text, const char * pattern)
+{
+       const char *    retryPat;
+       const char *    retryText;
+       int             ch;
+       BOOL            found;
+
+       retryPat = NULL;
+       retryText = NULL;
+
+       while (*text || *pattern)
+       {
+               ch = *pattern++;
+
+               switch (ch)
+               {
+                       case '*':  
+                               retryPat = pattern;
+                               retryText = 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 = ++retryText;
+                               }
+
+                               /* 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 = ++retryText;
+                                       break;
+                               }
+
+                               return FALSE;
+               }
+
+               if (pattern == NULL)
+                       return FALSE;
+       }
+
+       return TRUE;
+}
+
+
+/*
+ * Take a command string and break it up into an argc, argv list while
+ * handling quoting and wildcards.  The returned argument list and
+ * strings are in static memory, and so are overwritten on each call.
+ * The argument list is ended with a NULL pointer for convenience.
+ * Returns TRUE if successful, or FALSE on an error with a message
+ * already output.
+ */
+BOOL
+makeArgs(const char * cmd, int * retArgc, const char *** retArgv)
+{
+       const char *            argument;
+       char *                  cp;
+       char *                  cpOut;
+       char *                  newStrings;
+       const char **           fileTable;
+       const char **           newArgTable;
+       int                     newArgTableSize;
+       int                     fileCount;
+       int                     len;
+       int                     ch;
+       int                     quote;
+       BOOL                    quotedWildCards;
+       BOOL                    unquotedWildCards;
+
+       static int              stringsLength;
+       static char *           strings;
+       static int              argCount;
+       static int              argTableSize;
+       static const char **    argTable;
+
+       /*
+        * Clear the returned values until we know them.
+        */
+       argCount = 0;
+       *retArgc = 0;
+       *retArgv = NULL;
+
+       /*
+        * Copy the command string into a buffer that we can modify,
+        * reallocating it if necessary.
+        */
+       len = strlen(cmd) + 1;
+
+       if (len > stringsLength)
+       {
+               newStrings = realloc(strings, len);
+
+               if (newStrings == NULL)
+               {
+                       fprintf(stderr, "Cannot allocate string\n");
+
+                       return FALSE;
+               }
+
+               strings = newStrings;
+               stringsLength = len;
+       }
+
+       memcpy(strings, cmd, len);
+       cp = strings;
+
+       /*
+        * Keep parsing the command string as long as there are any
+        * arguments left.
+        */
+       while (*cp)
+       {
+               /*
+                * Save the beginning of this argument.
+                */
+               argument = cp;
+               cpOut = cp;
+
+               /*
+                * Reset quoting and wildcarding for this argument.
+                */
+               quote = '\0';
+               quotedWildCards = FALSE;
+               unquotedWildCards = FALSE;
+
+               /*
+                * Loop over the string collecting the next argument while
+                * looking for quoted strings or quoted characters, and
+                * remembering whether there are any wildcard characters
+                * in the argument.
+                */
+               while (*cp)
+               {
+                       ch = *cp++;
+
+                       /*
+                        * If we are not in a quote and we see a blank then
+                        * this argument is done.
+                        */
+                       if (isBlank(ch) && (quote == '\0'))
+                               break;
+
+                       /*
+                        * If we see a backslash then accept the next
+                        * character no matter what it is.
+                        */
+                       if (ch == '\\')
+                       {
+                               ch = *cp++;
+
+                               /*
+                                * Make sure there is a next character.
+                                */
+                               if (ch == '\0')
+                               {
+                                       fprintf(stderr,
+                                               "Bad quoted character\n");
+
+                                       return FALSE;
+                               }
+
+                               /*
+                                * Remember whether the quoted character
+                                * is a wildcard.
+                                */
+                               if (isWildCard(ch))
+                                       quotedWildCards = TRUE;
+
+                               *cpOut++ = ch;
+
+                               continue;
+                       }
+
+                       /*
+                        * If we see one of the wildcard characters then
+                        * remember whether it was seen inside or outside
+                        * of quotes.
+                        */
+                       if (isWildCard(ch))
+                       {
+                               if (quote)
+                                       quotedWildCards = TRUE;
+                               else
+                                       unquotedWildCards = TRUE;
+                       }
+
+                       /*
+                        * If we were in a quote and we saw the same quote
+                        * character again then the quote is done.
+                        */
+                       if (ch == quote)
+                       {
+                               quote = '\0';
+
+                               continue;
+                       }
+
+                       /*
+                        * If we weren't in a quote and we see either type
+                        * of quote character, then remember that we are
+                        * now inside of a quote.
+                        */
+                       if ((quote == '\0') && ((ch == '\'') || (ch == '"')))
+                       {
+                               quote = ch;
+
+                               continue;
+                       }
+
+                       /*
+                        * Store the character.
+                        */
+                       *cpOut++ = ch;
+               }
+
+               /*
+                * Make sure that quoting is terminated properly.
+                */
+               if (quote)
+               {
+                       fprintf(stderr, "Unmatched quote character\n");
+
+                       return FALSE;
+               }
+
+               /*
+                * Null terminate the argument if it had shrunk, and then
+                * skip over all blanks to the next argument, nulling them
+                * out too.
+                */
+               if (cp != cpOut)
+                       *cpOut = '\0';
+
+               while (isBlank(*cp))
+                       *cp++ = '\0';
+
+               /*
+                * If both quoted and unquoted wildcards were used then
+                * complain since we don't handle them properly.
+                */
+               if (quotedWildCards && unquotedWildCards)
+               {
+                       fprintf(stderr,
+                               "Cannot use quoted and unquoted wildcards\n");
+
+                       return FALSE;
+               }
+
+               /*
+                * Expand the argument into the matching filenames or accept
+                * it as is depending on whether there were any unquoted
+                * wildcard characters in it.
+                */
+               if (unquotedWildCards)
+               {
+                       /*
+                        * Expand the argument into the matching filenames.
+                        */
+                       fileCount = expandWildCards(argument, &fileTable);
+
+                       /*
+                        * Return an error if the wildcards failed to match.
+                        */
+                       if (fileCount < 0)
+                               return FALSE;
+
+                       if (fileCount == 0)
+                       {
+                               fprintf(stderr, "Wildcard expansion error\n");
+
+                               return FALSE;
+                       }
+               }
+               else
+               {
+                       /*
+                        * Set up to only store the argument itself.
+                        */
+                       fileTable = &argument;
+                       fileCount = 1;
+               }
+
+               /*
+                * Now reallocate the argument table to hold the file name.
+                */
+               if (argCount + fileCount >= argTableSize)
+               {
+                       newArgTableSize = argCount + fileCount + 1;
+
+                       newArgTable = (const char **) realloc(argTable,
+                               (sizeof(const char *) * newArgTableSize));
+
+                       if (newArgTable == NULL)
+                       {
+                               fprintf(stderr, "No memory for arg list\n");
+
+                               return FALSE;
+                       }
+
+                       argTable = newArgTable;
+                       argTableSize = newArgTableSize;
+               }
+
+               /*
+                * Copy the new arguments to the end of the old ones.
+                */
+               memcpy((void *) &argTable[argCount], (const void *) fileTable,
+                       (sizeof(const char **) * fileCount));
+
+               /*
+                * Add to the argument count.
+                */
+               argCount += fileCount;
+       }
+
+       /*
+        * Null terminate the argument list and return it.
+        */
+       argTable[argCount] = NULL;
+
+       *retArgc = argCount;
+       *retArgv = 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(
+       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(int size)
+{
+       CHUNK * chunk;
+
+       if (size < CHUNK_INIT_SIZE)
+               size = CHUNK_INIT_SIZE;
+
+       chunk = (CHUNK *) malloc(size + sizeof(CHUNK) - CHUNK_INIT_SIZE);
+
+       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(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(void)
+{
+       CHUNK * chunk;
+
+       while (chunkList)
+       {
+               chunk = chunkList;
+               chunkList = chunk->next;
+               free((char *) chunk);
+       }
+}
+
+
+/*
+ * Try writing data to the specified file descriptor.
+ * Only the first write error if any is printed.
+ * This is used when writing to STDOUT.
+ */
+void
+tryWrite(int fd, const char * cp, int len)
+{
+       static int failed = FALSE;
+
+       int status = fullWrite(fd, cp, len);
+
+       if ((status < 0) && !failed)
+       {
+               failed = TRUE;
+               perror("write");
+       }
+}
+
+
+/*
+ * 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(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;
+}
+
+
+/*
+ * Read all of the supplied buffer from a file.
+ * This does multiple reads as necessary.
+ * Returns the amount read, or -1 on an error.
+ * A short read is returned on an end of file.
+ */
+int
+fullRead(int fd, char * buf, int len)
+{
+       int     cc;
+       int     total;
+
+       total = 0;
+
+       while (len > 0)
+       {
+               cc = read(fd, buf, len);
+
+               if (cc < 0)
+                       return -1;
+
+               if (cc == 0)
+                       break;
+
+               buf += cc;
+               total+= cc;
+               len -= cc;
+       }
+
+       return total;
+}
+
+
+/*
+ * Call system for the specified command and print an error
+ * message if the execution fails.  The exit status of the
+ * command is returned.
+ */
+int
+trySystem(const char * cmd)
+{
+       int status;
+
+       status = system(cmd);
+
+       if (status == -1)
+               fprintf(stderr, "Error starting command: %s\n", cmd);
+
+       return status;
+}
+
+
+/*
+ * Check the status for the most recent system call and complain
+ * if its value is -1 which indicates it failed.
+ */
+void
+checkStatus(const char * name, int status)
+{
+       if (status == -1)
+               perror(name);
+}
+
+/* END CODE */
index 0bb26079fa3b046873571dc0d0b492ab559dcaf0..7a55678fc9202a4b493e5a02a3152e2d7bb8b220 100644 (file)
@@ -2,9 +2,6 @@
 #include "priv_gpio.h"
 #include <kernel/task.h>
 
-void task1(void);
-void task2(void);
-
 void user_delay(uint32_t ms)
 {
        register uint32_t r1 asm("r1") = ms;
@@ -16,29 +13,32 @@ void user_delay(uint32_t ms)
        " :: "r" (r1));
 }
 
-void user_main(void)
+int fork(void)
 {
-       gpio(GPIO_MODE, 5, OUTPUT);
-       task_start(task1, 512);
-
-       for (int i = 0; i < 8; i++) {
-               gpio(GPIO_OUT, 5, !(i & 1));
-               user_delay(200);
-       }
+       int result;
+       asm("\
+               mov r0, sp; \
+               svc 3; \
+               mov %0, r0; \
+       " : "=r" (result));
+       return result;
 }
 
-void task1(void)
+void user_main(void)
 {
-       user_delay(400);
-       task_start(task2, 1024);
-}
+       gpio(GPIO_MODE, 5, OUTPUT);
 
-void task2(void)
-{
-       int state = 0;
-       user_delay(2500);
-       while (1) {
-               gpio(GPIO_OUT, 5, state ^= 1);
-               user_delay(500);
+       if (fork() == 0) {
+               while (1) {
+                       gpio(GPIO_OUT, 5, 1);
+                       user_delay(1000);
+               }
+       } else {
+               while (1) {
+                       gpio(GPIO_OUT, 5, 0);
+                       user_delay(500);
+               }
+       
        }
 }
+