From f4149952ea4895356a3d4c0a9517893bb1e18cd2 Mon Sep 17 00:00:00 2001 From: tcsullivan Date: Thu, 18 Oct 2018 18:24:43 -0400 Subject: [PATCH] wip forking, added sash source --- src/kernel/heap.c | 5 - src/kernel/heap.h | 5 + src/kernel/init.c | 2 +- src/kernel/svc.c | 6 + src/kernel/task.c | 52 +- src/sash/CHANGES | 31 + src/sash/Makefile | 69 ++ src/sash/README | 17 + src/sash/cmd_ar.c | 1019 +++++++++++++++++++++++++++ src/sash/cmd_chattr.c | 265 +++++++ src/sash/cmd_dd.c | 372 ++++++++++ src/sash/cmd_ed.c | 1440 +++++++++++++++++++++++++++++++++++++ src/sash/cmd_file.c | 235 +++++++ src/sash/cmd_find.c | 376 ++++++++++ src/sash/cmd_grep.c | 197 ++++++ src/sash/cmd_gzip.c | 698 ++++++++++++++++++ src/sash/cmd_ls.c | 597 ++++++++++++++++ src/sash/cmd_tar.c | 1277 +++++++++++++++++++++++++++++++++ src/sash/cmds.c | 1562 +++++++++++++++++++++++++++++++++++++++++ src/sash/dirent.h | 10 + src/sash/sash.1 | 591 ++++++++++++++++ src/sash/sash.c | 1373 ++++++++++++++++++++++++++++++++++++ src/sash/sash.h | 169 +++++ src/sash/utils.c | 1144 ++++++++++++++++++++++++++++++ src/user/user.c | 44 +- 25 files changed, 11511 insertions(+), 45 deletions(-) create mode 100644 src/sash/CHANGES create mode 100644 src/sash/Makefile create mode 100644 src/sash/README create mode 100644 src/sash/cmd_ar.c create mode 100644 src/sash/cmd_chattr.c create mode 100644 src/sash/cmd_dd.c create mode 100644 src/sash/cmd_ed.c create mode 100644 src/sash/cmd_file.c create mode 100644 src/sash/cmd_find.c create mode 100644 src/sash/cmd_grep.c create mode 100644 src/sash/cmd_gzip.c create mode 100644 src/sash/cmd_ls.c create mode 100644 src/sash/cmd_tar.c create mode 100644 src/sash/cmds.c create mode 100644 src/sash/dirent.h create mode 100644 src/sash/sash.1 create mode 100644 src/sash/sash.c create mode 100644 src/sash/sash.h create mode 100644 src/sash/utils.c 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 +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include + +/* + * These were used for old linux versions. + * #include + * #include + */ + +#include + + +#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 +#include +#include +#include + +#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 +#include +#include +#include + +#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 + +#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 +#include +#include + +#include "sash.h" + + +#define GZ_EXT ".gz" +#define TGZ_EXT ".tgz" +#define Z_EXT ".Z" +#define TAR_EXT ".tar" +#define NO_EXT "" + + +/* + * Tables of conversions to make to file extensions. + */ +typedef struct +{ + const char * input; + const char * output; +} CONVERT; + + +static const CONVERT gzipConvertTable[] = +{ + {TAR_EXT, TGZ_EXT}, + {NO_EXT, GZ_EXT} +}; + + +static const CONVERT gunzipConvertTable[] = +{ + {TGZ_EXT, TAR_EXT}, + {GZ_EXT, NO_EXT}, + {Z_EXT, NO_EXT}, + {NO_EXT, NO_EXT} +}; + + +/* + * Local routines to compress and uncompress files. + */ +static BOOL gzip(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 +#include +#include "dirent.h" +#include +#include + + +#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 +#include +#include +#include + + +/* + * 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 +#include +//#include +#include +#include +#include +#include +#include + +#if HAVE_LINUX_MOUNT +#include +#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 +#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 +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include + +#if __OpenBSD__ +#include +#endif + +#if __Linux__ +#include +#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 +#include +#include "dirent.h" +#include + + +/* + * 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, ×)); + } + + 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 -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)); } +int fork(void) +{ + int result; + asm("\ + mov r0, sp; \ + svc 3; \ + mov %0, r0; \ + " : "=r" (result)); + return result; +} + void user_main(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); + if (fork() == 0) { + while (1) { + gpio(GPIO_OUT, 5, 1); + user_delay(1000); + } + } else { + while (1) { + gpio(GPIO_OUT, 5, 0); + user_delay(500); + } + } } -void task1(void) -{ - user_delay(400); - task_start(task2, 1024); -} - -void task2(void) -{ - int state = 0; - user_delay(2500); - while (1) { - gpio(GPIO_OUT, 5, state ^= 1); - user_delay(500); - } -}