aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortcsullivan <tullivan99@gmail.com>2018-10-18 18:24:43 -0400
committertcsullivan <tullivan99@gmail.com>2018-10-18 18:24:43 -0400
commitf4149952ea4895356a3d4c0a9517893bb1e18cd2 (patch)
tree5620b42a4eb874a5b3a9679065c882066dca4f70
parent6b5dfe3c09195118226180470eada4a51d8ec922 (diff)
wip forking, added sash source
-rw-r--r--src/kernel/heap.c5
-rw-r--r--src/kernel/heap.h5
-rw-r--r--src/kernel/init.c2
-rw-r--r--src/kernel/svc.c6
-rw-r--r--src/kernel/task.c52
-rw-r--r--src/sash/CHANGES31
-rw-r--r--src/sash/Makefile69
-rw-r--r--src/sash/README17
-rw-r--r--src/sash/cmd_ar.c1019
-rw-r--r--src/sash/cmd_chattr.c265
-rw-r--r--src/sash/cmd_dd.c372
-rw-r--r--src/sash/cmd_ed.c1440
-rw-r--r--src/sash/cmd_file.c235
-rw-r--r--src/sash/cmd_find.c376
-rw-r--r--src/sash/cmd_grep.c197
-rw-r--r--src/sash/cmd_gzip.c698
-rw-r--r--src/sash/cmd_ls.c597
-rw-r--r--src/sash/cmd_tar.c1277
-rw-r--r--src/sash/cmds.c1562
-rw-r--r--src/sash/dirent.h10
-rw-r--r--src/sash/sash.1591
-rw-r--r--src/sash/sash.c1373
-rw-r--r--src/sash/sash.h169
-rw-r--r--src/sash/utils.c1144
-rw-r--r--src/user/user.c44
25 files changed, 11511 insertions, 45 deletions
diff --git a/src/kernel/heap.c b/src/kernel/heap.c
index ff5dd9e..123f7e6 100644
--- a/src/kernel/heap.c
+++ b/src/kernel/heap.c
@@ -22,11 +22,6 @@
#define HEAP_ALIGN 4
-typedef struct {
- uint32_t size;
- void *next;
-} __attribute__ ((packed)) alloc_t;
-
static alloc_t *free_blocks;
static void *heap_end;
diff --git a/src/kernel/heap.h b/src/kernel/heap.h
index c817e4d..82d057e 100644
--- a/src/kernel/heap.h
+++ b/src/kernel/heap.h
@@ -23,6 +23,11 @@
#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.
diff --git a/src/kernel/init.c b/src/kernel/init.c
index ca60c3e..474ef8a 100644
--- a/src/kernel/init.c
+++ b/src/kernel/init.c
@@ -56,5 +56,5 @@ void init_idle(void)
task_start(user_main, 4096);
while (1)
- delay(100);
+ delay(10);
}
diff --git a/src/kernel/svc.c b/src/kernel/svc.c
index 62324f7..edf2b68 100644
--- a/src/kernel/svc.c
+++ b/src/kernel/svc.c
@@ -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;
}
diff --git a/src/kernel/task.c b/src/kernel/task.c
index 86ba766..96051a9 100644
--- a/src/kernel/task.c
+++ b/src/kernel/task.c
@@ -26,6 +26,13 @@
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
index 0000000..c87d6d5
--- /dev/null
+++ b/src/sash/CHANGES
@@ -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
index 0000000..02a895a
--- /dev/null
+++ b/src/sash/Makefile
@@ -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
index 0000000..154f1f7
--- /dev/null
+++ b/src/sash/README
@@ -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
index 0000000..6c6181e
--- /dev/null
+++ b/src/sash/cmd_ar.c
@@ -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
index 0000000..df50c22
--- /dev/null
+++ b/src/sash/cmd_chattr.c
@@ -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
index 0000000..80338b6
--- /dev/null
+++ b/src/sash/cmd_dd.c
@@ -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
index 0000000..e935c0d
--- /dev/null
+++ b/src/sash/cmd_ed.c
@@ -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
index 0000000..caf2a30
--- /dev/null
+++ b/src/sash/cmd_file.c
@@ -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
index 0000000..fde9006
--- /dev/null
+++ b/src/sash/cmd_find.c
@@ -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
index 0000000..b9e0821
--- /dev/null
+++ b/src/sash/cmd_grep.c
@@ -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
index 0000000..6bcec11
--- /dev/null
+++ b/src/sash/cmd_gzip.c
@@ -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
index 0000000..41f31cc
--- /dev/null
+++ b/src/sash/cmd_ls.c
@@ -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
index 0000000..5dd5f21
--- /dev/null
+++ b/src/sash/cmd_tar.c
@@ -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
index 0000000..ae346b5
--- /dev/null
+++ b/src/sash/cmds.c
@@ -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
index 0000000..77db3b4
--- /dev/null
+++ b/src/sash/dirent.h
@@ -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
index 0000000..8c61b9b
--- /dev/null
+++ b/src/sash/sash.1
@@ -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
index 0000000..8c15711
--- /dev/null
+++ b/src/sash/sash.c
@@ -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
index 0000000..b0ed254
--- /dev/null
+++ b/src/sash/sash.h
@@ -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
index 0000000..f6bee83
--- /dev/null
+++ b/src/sash/utils.c
@@ -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 */
diff --git a/src/user/user.c b/src/user/user.c
index 0bb2607..7a55678 100644
--- a/src/user/user.c
+++ b/src/user/user.c
@@ -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);
+ }
+
}
}
+