From: Tollef Fog Heen Date: Sun, 20 Apr 2014 06:13:08 +0000 (+0200) Subject: Imported Upstream version 3.4 X-Git-Tag: upstream/3.4^0 X-Git-Url: https://err.no/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=77b0329e189a9653fa571e801c1d1e5e64e751bd;p=sash Imported Upstream version 3.4 --- diff --git a/Makefile b/Makefile index b7f90d4..4a94167 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,11 @@ # # Makefile for sash # +# The HAVE_GZIP definition adds the -gzip and -gunzip commands. +# The HAVE_EXT2 definition adds the -chattr and -lsattr comamnds. +# -CFLAGS = -O3 -Wall -DHAVE_GZIP +CFLAGS = -O3 -Wall -Wmissing-prototypes -DHAVE_GZIP -DHAVE_EXT2 LDFLAGS = -static -s LIBS = -lz @@ -12,11 +15,11 @@ MANDIR = /usr/man/man1 OBJS = sash.o cmds.o cmd_dd.o cmd_ed.o cmd_grep.o cmd_ls.o cmd_tar.o \ - cmd_gzip.o utils.o + cmd_gzip.o cmd_find.o cmd_file.o cmd_chattr.o cmd_ar.o utils.o sash: $(OBJS) - cc $(LDFLAGS) -o sash $(OBJS) $(LIBS) + $(CC) $(LDFLAGS) -o sash $(OBJS) $(LIBS) clean: rm -f $(OBJS) sash diff --git a/README b/README index 64a2248..88137cd 100644 --- a/README +++ b/README @@ -1,12 +1,21 @@ -This is release 2.1 of sash, my stand-alone shell for Linux. +This is release 3.4 of sash, my stand-alone shell for Linux. -The purpose of this program is to make replacing of shared libraries -easy and safe. It does this by firstly being linked statically, and -secondly by including many of the standard utilities within itself. +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. +This version has the "-ar" built-in command for extracting files +from archive files. This is used for unpacking Debian packages. +Thanks to Aaron R. Crane for donating the code for this command +(highly modified by me). + Type "make install" to build and install the program and man page. +Some warning messages may appear when compiling cmds.c under Linux from +the mount.h and fs.h include files. These warnings can be ignored. + David I. Bell dbell@canb.auug.org.au -March 8, 1998 +2 October 1999 diff --git a/cmd_ar.c b/cmd_ar.c new file mode 100644 index 0000000..0653ed5 --- /dev/null +++ b/cmd_ar.c @@ -0,0 +1,1011 @@ +/* + * 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) 1999 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 */ + unsigned 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); + + +void +do_ar(int argc, const char ** argv) +{ + const char * options; + const char * archiveName; + BOOL doExtract; + BOOL doTable; + BOOL doPrint; + BOOL verbose; + Archive arch; + + verbose = FALSE; + doExtract = FALSE; + doTable = FALSE; + doPrint = FALSE; + + if (argc < 3) + { + fprintf(stderr, "Too few arguments for ar\n"); + + return; + } + + /* + * 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; + + default: + fprintf(stderr, "Unknown ar flag: %c\n", *options); + + return; + } + } + + if (doExtract + doTable + doPrint != 1) + { + fprintf(stderr, + "Exactly one of 'x', 'p' or 't' must be specified\n"); + + return; + } + + /* + * Open the archive file. + */ + initArchive(&arch); + + if (!openArchive(archiveName, &arch)) + return; + + /* + * Read the first special member of the archive. + */ + if (!readSpecialMember(&arch)) + return; + + /* + * 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) + break; + } + else + { + fprintf(stderr, "Oops -- I don't know what to do\n"); + break; + } + } + + closeArchive(&arch); +} + + +/* + * 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. + */ + (void) fchmod(fd, arch->mode); + (void) 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) +{ + unsigned 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/cmd_chattr.c b/cmd_chattr.c new file mode 100644 index 0000000..f2d98d1 --- /dev/null +++ b/cmd_chattr.c @@ -0,0 +1,248 @@ +/* + * Copyright (c) 1999 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. + */ + +#ifdef HAVE_EXT2 + +#include +#include +#include + +#include "sash.h" + + +/* + * The chattr command. + * This can turn on or off the immutable and append-only ext2 flags. + */ +void +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; + + 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; + } + } + } + + /* + * Make sure that the attributes are reasonable. + */ + if ((onFlags == 0) && (offFlags == 0)) + { + fprintf(stderr, "No attributes specified\n"); + + return; + } + + if ((onFlags & offFlags) != 0) + { + fprintf(stderr, "Inconsistent attributes specified\n"); + + return; + } + + /* + * Make sure there are some files to affect. + */ + if (argc <= 0) + { + fprintf(stderr, "No files specified for setting attributes\n"); + + return; + } + + /* + * 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); + + continue; + } + + /* + * Read the current ext2 flag bits. + */ + if (ioctl(fd, EXT2_IOC_GETFLAGS, &oldFlags) < 0) + { + perror(fileName); + + (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); + + (void) close(fd); + + continue; + } + + /* + * Close the file. + */ + (void) close(fd); + } +} + + +/* + * The lsattr command. + * This lists the immutable and append-only ext2 flags. + */ +void +do_lsattr(int argc, const char ** argv) +{ + const char * fileName; + int fd; + int status; + int flags; + char string[4]; + + 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); + + 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); + + 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); + } +} + +#endif + + +/* END CODE */ diff --git a/cmd_dd.c b/cmd_dd.c index dfaeae5..f910261 100644 --- a/cmd_dd.c +++ b/cmd_dd.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998 by David I. Bell + * Copyright (c) 1999 by David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. * @@ -18,9 +18,10 @@ #define PAR_SKIP 6 -typedef struct { - char *name; - int value; +typedef struct +{ + const char * name; + int value; } PARAM; @@ -36,44 +37,44 @@ static const PARAM params[] = }; -static long getnum PROTO((const char * cp)); +static long getNum(const char * cp); void -do_dd(argc, argv) - int argc; - const char ** argv; +do_dd(int argc, const char ** argv) { const char * str; const PARAM * par; - const char * infile; - const char * outfile; + const char * inFile; + const char * outFile; char * cp; - int infd; - int outfd; - int incc; - int outcc; - int blocksize; + int inFd; + int outFd; + int inCc; + int outCc; + int blockSize; long count; - long seekval; - long skipval; + long seekVal; + long skipVal; long intotal; - long outtotal; + long outTotal; char * buf; - char localbuf[BUFSIZE]; + char localBuf[BUF_SIZE]; - infile = NULL; - outfile = NULL; - seekval = 0; - skipval = 0; - blocksize = 512; + inFile = NULL; + outFile = NULL; + seekVal = 0; + skipVal = 0; + blockSize = 512; count = 0x7fffffff; - while (--argc > 0) { + while (--argc > 0) + { str = *++argv; cp = strchr(str, '='); - if (cp == NULL) { + if (cp == NULL) + { fprintf(stderr, "Bad dd argument\n"); return; @@ -81,36 +82,41 @@ do_dd(argc, argv) *cp++ = '\0'; - for (par = params; par->name; par++) { + for (par = params; par->name; par++) + { if (strcmp(str, par->name) == 0) break; } - switch (par->value) { + switch (par->value) + { case PAR_IF: - if (infile) { + if (inFile) + { fprintf(stderr, "Multiple input files illegal\n"); return; } - infile = cp; + inFile = cp; break; case PAR_OF: - if (outfile) { + if (outFile) + { fprintf(stderr, "Multiple output files illegal\n"); return; } - outfile = cp; + outFile = cp; break; case PAR_BS: - blocksize = getnum(cp); + blockSize = getNum(cp); - if (blocksize <= 0) { + if (blockSize <= 0) + { fprintf(stderr, "Bad block size value\n"); return; @@ -119,9 +125,10 @@ do_dd(argc, argv) break; case PAR_COUNT: - count = getnum(cp); + count = getNum(cp); - if (count < 0) { + if (count < 0) + { fprintf(stderr, "Bad count value\n"); return; @@ -130,9 +137,10 @@ do_dd(argc, argv) break; case PAR_SEEK: - seekval = getnum(cp); + seekVal = getNum(cp); - if (seekval < 0) { + if (seekVal < 0) + { fprintf(stderr, "Bad seek value\n"); return; @@ -141,9 +149,10 @@ do_dd(argc, argv) break; case PAR_SKIP: - skipval = getnum(cp); + skipVal = getNum(cp); - if (skipval < 0) { + if (skipVal < 0) + { fprintf(stderr, "Bad skip value\n"); return; @@ -158,24 +167,28 @@ do_dd(argc, argv) } } - if (infile == NULL) { + if (inFile == NULL) + { fprintf(stderr, "No input file specified\n"); return; } - if (outfile == NULL) { + if (outFile == NULL) + { fprintf(stderr, "No output file specified\n"); return; } - buf = localbuf; + buf = localBuf; - if (blocksize > sizeof(localbuf)) { - buf = malloc(blocksize); + if (blockSize > sizeof(localBuf)) + { + buf = malloc(blockSize); - if (buf == NULL) { + if (buf == NULL) + { fprintf(stderr, "Cannot allocate buffer\n"); return; @@ -183,42 +196,49 @@ do_dd(argc, argv) } intotal = 0; - outtotal = 0; + outTotal = 0; - infd = open(infile, 0); + inFd = open(inFile, 0); - if (infd < 0) { - perror(infile); + if (inFd < 0) + { + perror(inFile); - if (buf != localbuf) + if (buf != localBuf) free(buf); return; } - outfd = creat(outfile, 0666); + outFd = creat(outFile, 0666); - if (outfd < 0) { - perror(outfile); - close(infd); + if (outFd < 0) + { + perror(outFile); + close(inFd); - if (buf != localbuf) + if (buf != localBuf) free(buf); return; } - if (skipval) { - if (lseek(infd, skipval * blocksize, 0) < 0) { - while (skipval-- > 0) { - incc = read(infd, buf, blocksize); - - if (incc < 0) { - perror(infile); + if (skipVal) + { + if (lseek(inFd, skipVal * blockSize, 0) < 0) + { + while (skipVal-- > 0) + { + inCc = read(inFd, buf, blockSize); + + if (inCc < 0) + { + perror(inFile); goto cleanup; } - if (incc == 0) { + if (inCc == 0) + { fprintf(stderr, "End of file while skipping\n"); goto cleanup; } @@ -226,53 +246,60 @@ do_dd(argc, argv) } } - if (seekval) { - if (lseek(outfd, seekval * blocksize, 0) < 0) { - perror(outfile); + if (seekVal) + { + if (lseek(outFd, seekVal * blockSize, 0) < 0) + { + perror(outFile); + goto cleanup; } } - while ((incc = read(infd, buf, blocksize)) > 0) { - intotal += incc; + while ((inCc = read(inFd, buf, blockSize)) > 0) + { + intotal += inCc; cp = buf; - if (intflag) { + if (intFlag) + { fprintf(stderr, "Interrupted\n"); goto cleanup; } - while (incc > 0) { - outcc = write(outfd, cp, incc); + while (inCc > 0) + { + outCc = write(outFd, cp, inCc); - if (outcc < 0) { - perror(outfile); + if (outCc < 0) + { + perror(outFile); goto cleanup; } - outtotal += outcc; - cp += outcc; - incc -= outcc; + outTotal += outCc; + cp += outCc; + inCc -= outCc; } } - if (incc < 0) - perror(infile); + if (inCc < 0) + perror(inFile); cleanup: - close(infd); + close(inFd); - if (close(outfd) < 0) - perror(outfile); + if (close(outFd) < 0) + perror(outFile); - if (buf != localbuf) + if (buf != localBuf) free(buf); - printf("%ld+%d records in\n", intotal / blocksize, - (intotal % blocksize) != 0); + printf("%ld+%d records in\n", intotal / blockSize, + (intotal % blockSize) != 0); - printf("%ld+%d records out\n", outtotal / blocksize, - (outtotal % blocksize) != 0); + printf("%ld+%d records out\n", outTotal / blockSize, + (outTotal % blockSize) != 0); } @@ -281,20 +308,20 @@ cleanup: * Returns -1 if the number format is illegal. */ static long -getnum(cp) - const char * cp; +getNum(const char * cp) { long value; - if (!isdecimal(*cp)) + if (!isDecimal(*cp)) return -1; value = 0; - while (isdecimal(*cp)) + while (isDecimal(*cp)) value = value * 10 + *cp++ - '0'; - switch (*cp++) { + switch (*cp++) + { case 'k': value *= 1024; break; diff --git a/cmd_ed.c b/cmd_ed.c index c3e5223..9b4b383 100644 --- a/cmd_ed.c +++ b/cmd_ed.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998 by David I. Bell + * Copyright (c) 1999 by David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. * @@ -9,14 +9,16 @@ #include "sash.h" #define USERSIZE 1024 /* max line length typed in by user */ -#define INITBUFSIZE 1024 /* initial buffer size */ +#define INITBUF_SIZE 1024 /* initial buffer size */ typedef int NUM; typedef int LEN; typedef struct LINE LINE; -struct LINE { + +struct LINE +{ LINE * next; LINE * prev; LEN len; @@ -25,76 +27,73 @@ struct LINE { static LINE lines; -static LINE * curline; -static NUM curnum; -static NUM lastnum; +static LINE * curLine; +static NUM curNum; +static NUM lastNum; static NUM marks[26]; static BOOL dirty; -static char * filename; -static char searchstring[USERSIZE]; - -static char * bufbase; -static char * bufptr; -static LEN bufused; -static LEN bufsize; - - -static void docommands PROTO((void)); -static void subcommand PROTO((const char * cmd, NUM num1, NUM num2)); - -static BOOL getnum - PROTO((const char ** retcp, BOOL * rethavenum, NUM * retnum)); - -static BOOL setcurnum PROTO((NUM num)); -static BOOL initedit PROTO((void)); -static void termedit PROTO((void)); -static void addlines PROTO((NUM num)); -static BOOL insertline PROTO((NUM num, const char * data, LEN len)); -static BOOL deletelines PROTO((NUM num1, NUM num2)); -static BOOL printlines PROTO((NUM num1, NUM num2, BOOL expandflag)); -static BOOL writelines PROTO((const char * file, NUM num1, NUM num2)); -static BOOL readlines PROTO((const char * file, NUM num)); -static NUM searchlines PROTO((const char * str, NUM num1, NUM num2)); - -static LEN findstring - PROTO((const LINE * lp, const char * str, LEN len, LEN offset)); - -static LINE * findline PROTO((NUM num)); +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); void -do_ed(argc, argv) - int argc; - const char ** argv; +do_ed(int argc, const char ** argv) { - if (!initedit()) + if (!initEdit()) return; - if (argc > 1) { - filename = strdup(argv[1]); + if (argc > 1) + { + fileName = strdup(argv[1]); - if (filename == NULL) { + if (fileName == NULL) + { fprintf(stderr, "No memory\n"); - termedit(); + termEdit(); return; } - if (!readlines(filename, 1)) { - termedit(); + if (!readLines(fileName, 1)) + { + termEdit(); return; } - if (lastnum) - setcurnum(1); + if (lastNum) + setCurNum(1); dirty = FALSE; } - docommands(); + doCommands(); - termedit(); + termEdit(); } @@ -102,7 +101,7 @@ do_ed(argc, argv) * Read commands until we are told to stop. */ static void -docommands() +doCommands(void) { const char * cp; char * endbuf; @@ -114,8 +113,9 @@ docommands() BOOL have2; char buf[USERSIZE]; - while (TRUE) { - intflag = FALSE; + while (TRUE) + { + intFlag = FALSE; printf(": "); fflush(stdout); @@ -129,116 +129,126 @@ docommands() endbuf = &buf[len - 1]; - if (*endbuf != '\n') { + if (*endbuf != '\n') + { fprintf(stderr, "Command line too long\n"); - do { + do + { len = fgetc(stdin); - } while ((len != EOF) && (len != '\n')); + } + while ((len != EOF) && (len != '\n')); continue; } - while ((endbuf > buf) && isblank(endbuf[-1])) + while ((endbuf > buf) && isBlank(endbuf[-1])) endbuf--; *endbuf = '\0'; cp = buf; - while (isblank(*cp)) + while (isBlank(*cp)) cp++; have1 = FALSE; have2 = FALSE; - if ((curnum == 0) && (lastnum > 0)) { - curnum = 1; - curline = lines.next; + if ((curNum == 0) && (lastNum > 0)) + { + curNum = 1; + curLine = lines.next; } - if (!getnum(&cp, &have1, &num1)) + if (!getNum(&cp, &have1, &num1)) continue; - while (isblank(*cp)) + while (isBlank(*cp)) cp++; - if (*cp == ',') { + if (*cp == ',') + { cp++; - if (!getnum(&cp, &have2, &num2)) + if (!getNum(&cp, &have2, &num2)) continue; if (!have1) num1 = 1; if (!have2) - num2 = lastnum; + num2 = lastNum; have1 = TRUE; have2 = TRUE; } if (!have1) - num1 = curnum; + num1 = curNum; if (!have2) num2 = num1; - switch (*cp++) { + switch (*cp++) + { case 'a': - addlines(num1 + 1); + addLines(num1 + 1); break; case 'c': - deletelines(num1, num2); - addlines(num1); + deleteLines(num1, num2); + addLines(num1); break; case 'd': - deletelines(num1, num2); + deleteLines(num1, num2); break; case 'f': - if (*cp && !isblank(*cp)) { + if (*cp && !isBlank(*cp)) + { fprintf(stderr, "Bad file command\n"); break; } - while (isblank(*cp)) + while (isBlank(*cp)) cp++; - if (*cp == '\0') { - if (filename) - printf("\"%s\"\n", filename); + if (*cp == '\0') + { + if (fileName) + printf("\"%s\"\n", fileName); else - printf("No filename\n"); + printf("No file name\n"); break; } newname = strdup(cp); - if (newname == NULL) { - fprintf(stderr, "No memory for filename\n"); + if (newname == NULL) + { + fprintf(stderr, "No memory for file name\n"); break; } - if (filename) - free(filename); + if (fileName) + free(fileName); - filename = newname; + fileName = newname; break; case 'i': - addlines(num1); + addLines(num1); break; case 'k': - while (isblank(*cp)) + while (isBlank(*cp)) cp++; - if ((*cp < 'a') || (*cp > 'a') || cp[1]) { + if ((*cp < 'a') || (*cp > 'a') || cp[1]) + { fprintf(stderr, "Bad mark name\n"); break; } @@ -247,18 +257,19 @@ docommands() break; case 'l': - printlines(num1, num2, TRUE); + printLines(num1, num2, TRUE); break; case 'p': - printlines(num1, num2, FALSE); + printLines(num1, num2, FALSE); break; case 'q': - while (isblank(*cp)) + while (isBlank(*cp)) cp++; - if (have1 || *cp) { + if (have1 || *cp) + { fprintf(stderr, "Bad quit command\n"); break; } @@ -273,7 +284,7 @@ docommands() fgets(buf, sizeof(buf), stdin); cp = buf; - while (isblank(*cp)) + while (isBlank(*cp)) cp++; if ((*cp == 'y') || (*cp == 'Y')) @@ -282,85 +293,91 @@ docommands() break; case 'r': - if (*cp && !isblank(*cp)) { + if (*cp && !isBlank(*cp)) + { fprintf(stderr, "Bad read command\n"); break; } - while (isblank(*cp)) + while (isBlank(*cp)) cp++; - if (*cp == '\0') { - fprintf(stderr, "No filename\n"); + if (*cp == '\0') + { + fprintf(stderr, "No file name\n"); break; } if (!have1) - num1 = lastnum; + num1 = lastNum; - if (readlines(cp, num1 + 1)) + if (readLines(cp, num1 + 1)) break; - if (filename == NULL) - filename = strdup(cp); + if (fileName == NULL) + fileName = strdup(cp); break; case 's': - subcommand(cp, num1, num2); + subCommand(cp, num1, num2); break; case 'w': - if (*cp && !isblank(*cp)) { + if (*cp && !isBlank(*cp)) + { fprintf(stderr, "Bad write command\n"); break; } - while (isblank(*cp)) + while (isBlank(*cp)) cp++; if (!have1) { num1 = 1; - num2 = lastnum; + num2 = lastNum; } if (*cp == '\0') - cp = filename; + cp = fileName; - if (cp == NULL) { + if (cp == NULL) + { fprintf(stderr, "No file name specified\n"); break; } - writelines(cp, num1, num2); + writeLines(cp, num1, num2); break; case 'z': - switch (*cp) { + switch (*cp) + { case '-': - printlines(curnum-21, curnum, FALSE); + printLines(curNum-21, curNum, FALSE); break; case '.': - printlines(curnum-11, curnum+10, FALSE); + printLines(curNum-11, curNum+10, FALSE); break; default: - printlines(curnum, curnum+21, FALSE); + printLines(curNum, curNum+21, FALSE); break; } break; case '.': - if (have1) { + if (have1) + { fprintf(stderr, "No arguments allowed\n"); break; } - printlines(curnum, curnum, FALSE); + printLines(curNum, curNum, FALSE); break; case '-': - if (setcurnum(curnum - 1)) - printlines(curnum, curnum, FALSE); + if (setCurNum(curNum - 1)) + printLines(curNum, curNum, FALSE); break; @@ -369,13 +386,14 @@ docommands() break; case '\0': - if (have1) { - printlines(num2, num2, FALSE); + if (have1) + { + printLines(num2, num2, FALSE); break; } - if (setcurnum(curnum + 1)) - printlines(curnum, curnum, FALSE); + if (setCurNum(curNum + 1)) + printLines(curNum, curNum, FALSE); break; @@ -392,37 +410,35 @@ docommands() * The current line is set to the last substitution done. */ static void -subcommand(cmd, num1, num2) - const char * cmd; - NUM num1; - NUM num2; +subCommand(const char * cmd, NUM num1, NUM num2) { int delim; char * cp; - char * oldstr; - char * newstr; - LEN oldlen; - LEN newlen; - LEN deltalen; + char * oldStr; + char * newStr; + LEN oldLen; + LEN newLen; + LEN deltaLen; LEN offset; LINE * lp; LINE * nlp; - BOOL globalflag; - BOOL printflag; - BOOL didsub; - BOOL needprint; + BOOL globalFlag; + BOOL printFlag; + BOOL didSub; + BOOL needPrint; char buf[USERSIZE]; - if ((num1 < 1) || (num2 > lastnum) || (num1 > num2)) { + if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) + { fprintf(stderr, "Bad line range for substitute\n"); return; } - globalflag = FALSE; - printflag = FALSE; - didsub = FALSE; - needprint = FALSE; + globalFlag = FALSE; + printFlag = FALSE; + didSub = FALSE; + needPrint = FALSE; /* * Copy the command so we can modify it. @@ -430,18 +446,20 @@ subcommand(cmd, num1, num2) strcpy(buf, cmd); cp = buf; - if (isblank(*cp) || (*cp == '\0')) { + if (isBlank(*cp) || (*cp == '\0')) + { fprintf(stderr, "Bad delimiter for substitute\n"); return; } delim = *cp++; - oldstr = cp; + oldStr = cp; cp = strchr(cp, delim); - if (cp == NULL) { + if (cp == NULL) + { fprintf(stderr, "Missing 2nd delimiter for substitute\n"); return; @@ -449,7 +467,7 @@ subcommand(cmd, num1, num2) *cp++ = '\0'; - newstr = cp; + newStr = cp; cp = strchr(cp, delim); if (cp) @@ -457,13 +475,14 @@ subcommand(cmd, num1, num2) else cp = ""; - while (*cp) switch (*cp++) { + while (*cp) switch (*cp++) + { case 'g': - globalflag = TRUE; + globalFlag = TRUE; break; case 'p': - printflag = TRUE; + printFlag = TRUE; break; default: @@ -472,37 +491,42 @@ subcommand(cmd, num1, num2) return; } - if (*oldstr == '\0') { - if (searchstring[0] == '\0') { + if (*oldStr == '\0') + { + if (searchString[0] == '\0') + { fprintf(stderr, "No previous search string\n"); return; } - oldstr = searchstring; + oldStr = searchString; } - if (oldstr != searchstring) - strcpy(searchstring, oldstr); + if (oldStr != searchString) + strcpy(searchString, oldStr); - lp = findline(num1); + lp = findLine(num1); if (lp == NULL) return; - oldlen = strlen(oldstr); - newlen = strlen(newstr); - deltalen = newlen - oldlen; + oldLen = strlen(oldStr); + newLen = strlen(newStr); + deltaLen = newLen - oldLen; offset = 0; nlp = NULL; - while (num1 <= num2) { - offset = findstring(lp, oldstr, oldlen, offset); + while (num1 <= num2) + { + offset = findString(lp, oldStr, oldLen, offset); - if (offset < 0) { - if (needprint) { - printlines(num1, num1, FALSE); - needprint = FALSE; + if (offset < 0) + { + if (needPrint) + { + printLines(num1, num1, FALSE); + needPrint = FALSE; } offset = 0; @@ -512,33 +536,36 @@ subcommand(cmd, num1, num2) continue; } - needprint = printflag; - didsub = TRUE; + 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 <= 0) + { + memcpy(&lp->data[offset], newStr, newLen); - if (deltalen) { - memcpy(&lp->data[offset + newlen], - &lp->data[offset + oldlen], - lp->len - offset - oldlen); + if (deltaLen) + { + memcpy(&lp->data[offset + newLen], + &lp->data[offset + oldLen], + lp->len - offset - oldLen); - lp->len += deltalen; + lp->len += deltaLen; } - offset += newlen; + offset += newLen; - if (globalflag) + if (globalFlag) continue; - if (needprint) { - printlines(num1, num1, FALSE); - needprint = FALSE; + if (needPrint) + { + printLines(num1, num1, FALSE); + needPrint = FALSE; } lp = lp->next; @@ -552,51 +579,53 @@ subcommand(cmd, num1, num2) * structure and use that. Link it in in place of * the old line structure. */ - nlp = (LINE *) malloc(sizeof(LINE) + lp->len + deltalen); + nlp = (LINE *) malloc(sizeof(LINE) + lp->len + deltaLen); - if (nlp == NULL) { + if (nlp == NULL) + { fprintf(stderr, "Cannot get memory for line\n"); return; } - nlp->len = lp->len + deltalen; + nlp->len = lp->len + deltaLen; memcpy(nlp->data, lp->data, offset); - memcpy(&nlp->data[offset], newstr, newlen); + memcpy(&nlp->data[offset], newStr, newLen); - memcpy(&nlp->data[offset + newlen], - &lp->data[offset + oldlen], - lp->len - offset - oldlen); + 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; + if (curLine == lp) + curLine = nlp; free(lp); lp = nlp; - offset += newlen; + offset += newLen; - if (globalflag) + if (globalFlag) continue; - if (needprint) { - printlines(num1, num1, FALSE); - needprint = FALSE; + if (needPrint) + { + printLines(num1, num1, FALSE); + needPrint = FALSE; } lp = lp->next; num1++; } - if (!didsub) - fprintf(stderr, "No substitutions found for \"%s\"\n", oldstr); + if (!didSub) + fprintf(stderr, "No substitutions found for \"%s\"\n", oldStr); } @@ -605,11 +634,7 @@ subcommand(cmd, num1, num2) * offset in the line. Returns the offset of the found string, or -1. */ static LEN -findstring(lp, str, len, offset) - const LINE * lp; - const char * str; - LEN len; - LEN offset; +findString( const LINE * lp, const char * str, LEN len, LEN offset) { LEN left; const char * cp; @@ -618,7 +643,8 @@ findstring(lp, str, len, offset) cp = &lp->data[offset]; left = lp->len - offset; - while (left >= len) { + while (left >= len) + { ncp = memchr(cp, *str, left); if (ncp == NULL) @@ -649,13 +675,13 @@ findstring(lp, str, len, offset) * or by an end of file. */ static void -addlines(num) - NUM num; +addLines(NUM num) { int len; char buf[USERSIZE + 1]; - while (fgets(buf, sizeof(buf), stdin)) { + while (fgets(buf, sizeof(buf), stdin)) + { if ((buf[0] == '.') && (buf[1] == '\n') && (buf[2] == '\0')) return; @@ -664,17 +690,20 @@ addlines(num) if (len == 0) return; - if (buf[len - 1] != '\n') { + if (buf[len - 1] != '\n') + { fprintf(stderr, "Line too long\n"); - do { + do + { len = fgetc(stdin); - } while ((len != EOF) && (len != '\n')); + } + while ((len != EOF) && (len != '\n')); return; } - if (!insertline(num++, buf, len)) + if (!insertLine(num++, buf, len)) return; } } @@ -689,100 +718,105 @@ addlines(num) * The character pointer which stopped the scan is also returned. */ static BOOL -getnum(retcp, rethavenum, retnum) - const char ** retcp; - BOOL * rethavenum; - NUM * retnum; +getNum(const char ** retcp, BOOL * retHaveNum, NUM * retNum) { const char * cp; - char * endstr; + char * endStr; char str[USERSIZE]; - BOOL havenum; + BOOL haveNum; NUM value; NUM num; NUM sign; cp = *retcp; - havenum = FALSE; + haveNum = FALSE; value = 0; sign = 1; - while (TRUE) { - while (isblank(*cp)) + while (TRUE) + { + while (isBlank(*cp)) cp++; - switch (*cp) { + switch (*cp) + { case '.': - havenum = TRUE; - num = curnum; + haveNum = TRUE; + num = curNum; cp++; break; case '$': - havenum = TRUE; - num = lastnum; + haveNum = TRUE; + num = lastNum; cp++; break; case '\'': cp++; - if ((*cp < 'a') || (*cp > 'z')) { + if ((*cp < 'a') || (*cp > 'z')) + { fprintf(stderr, "Bad mark name\n"); return FALSE; } - havenum = TRUE; + haveNum = TRUE; num = marks[*cp++ - 'a']; break; case '/': strcpy(str, ++cp); - endstr = strchr(str, '/'); + endStr = strchr(str, '/'); - if (endstr) { - *endstr++ = '\0'; - cp += (endstr - str); - } else + if (endStr) + { + *endStr++ = '\0'; + cp += (endStr - str); + } + else cp = ""; - num = searchlines(str, curnum, lastnum); + num = searchLines(str, curNum, lastNum); if (num == 0) return FALSE; - havenum = TRUE; + haveNum = TRUE; break; default: - if (!isdecimal(*cp)) { + if (!isDecimal(*cp)) + { *retcp = cp; - *rethavenum = havenum; - *retnum = value; + *retHaveNum = haveNum; + *retNum = value; return TRUE; } num = 0; - while (isdecimal(*cp)) + while (isDecimal(*cp)) num = num * 10 + *cp++ - '0'; - havenum = TRUE; + haveNum = TRUE; break; } value += num * sign; - while (isblank(*cp)) + while (isBlank(*cp)) cp++; - switch (*cp) { + switch (*cp) + { case '-': sign = -1; cp++; break; + case '+': sign = 1; cp++; @@ -790,8 +824,8 @@ getnum(retcp, rethavenum, retnum) default: *retcp = cp; - *rethavenum = havenum; - *retnum = value; + *retHaveNum = haveNum; + *retNum = value; return TRUE; } @@ -803,31 +837,32 @@ getnum(retcp, rethavenum, retnum) * Initialize everything for editing. */ static BOOL -initedit() +initEdit(void) { int i; - bufsize = INITBUFSIZE; - bufbase = malloc(bufsize); + bufSize = INITBUF_SIZE; + bufBase = malloc(bufSize); - if (bufbase == NULL) { + if (bufBase == NULL) + { fprintf(stderr, "No memory for buffer\n"); return FALSE; } - bufptr = bufbase; - bufused = 0; + bufPtr = bufBase; + bufUsed = 0; lines.next = &lines; lines.prev = &lines; - curline = NULL; - curnum = 0; - lastnum = 0; + curLine = NULL; + curNum = 0; + lastNum = 0; dirty = FALSE; - filename = NULL; - searchstring[0] = '\0'; + fileName = NULL; + searchString[0] = '\0'; for (i = 0; i < 26; i++) marks[i] = 0; @@ -840,29 +875,29 @@ initedit() * Finish editing. */ static void -termedit() +termEdit(void) { - if (bufbase) - free(bufbase); + if (bufBase) + free(bufBase); - bufbase = NULL; - bufptr = NULL; - bufsize = 0; - bufused = 0; + bufBase = NULL; + bufPtr = NULL; + bufSize = 0; + bufUsed = 0; - if (filename) - free(filename); + if (fileName) + free(fileName); - filename = NULL; + fileName = NULL; - searchstring[0] = '\0'; + searchString[0] = '\0'; - if (lastnum) - deletelines(1, lastnum); + if (lastNum) + deleteLines(1, lastNum); - lastnum = 0; - curnum = 0; - curline = NULL; + lastNum = 0; + curNum = 0; + curLine = NULL; } @@ -871,18 +906,17 @@ termedit() * Returns TRUE if the file was successfully read. */ static BOOL -readlines(file, num) - const char * file; - NUM num; +readLines(const char * file, NUM num) { int fd; int cc; LEN len; - LEN linecount; - LEN charcount; + LEN lineCount; + LEN charCount; char * cp; - if ((num < 1) || (num > lastnum + 1)) { + if ((num < 1) || (num > lastNum + 1)) + { fprintf(stderr, "Bad line for read\n"); return FALSE; @@ -890,97 +924,109 @@ readlines(file, num) fd = open(file, 0); - if (fd < 0) { + if (fd < 0) + { perror(file); return FALSE; } - bufptr = bufbase; - bufused = 0; - linecount = 0; - charcount = 0; + bufPtr = bufBase; + bufUsed = 0; + lineCount = 0; + charCount = 0; cc = 0; printf("\"%s\", ", file); fflush(stdout); - do { - if (intflag) { + do + { + if (intFlag) + { printf("INTERRUPTED, "); - bufused = 0; + bufUsed = 0; break; } - cp = memchr(bufptr, '\n', bufused); + cp = memchr(bufPtr, '\n', bufUsed); - if (cp) { - len = (cp - bufptr) + 1; + if (cp) + { + len = (cp - bufPtr) + 1; - if (!insertline(num, bufptr, len)) { + if (!insertLine(num, bufPtr, len)) + { close(fd); return FALSE; } - bufptr += len; - bufused -= len; - charcount += len; - linecount++; + bufPtr += len; + bufUsed -= len; + charCount += len; + lineCount++; num++; continue; } - if (bufptr != bufbase) { - memcpy(bufbase, bufptr, bufused); - bufptr = bufbase + bufused; + if (bufPtr != bufBase) + { + memcpy(bufBase, bufPtr, bufUsed); + bufPtr = bufBase + bufUsed; } - if (bufused >= bufsize) { - len = (bufsize * 3) / 2; - cp = realloc(bufbase, len); + if (bufUsed >= bufSize) + { + len = (bufSize * 3) / 2; + cp = realloc(bufBase, len); - if (cp == NULL) { + if (cp == NULL) + { fprintf(stderr, "No memory for buffer\n"); close(fd); return FALSE; } - bufbase = cp; - bufptr = bufbase + bufused; - bufsize = len; + bufBase = cp; + bufPtr = bufBase + bufUsed; + bufSize = len; } - cc = read(fd, bufptr, bufsize - bufused); - bufused += cc; - bufptr = bufbase; + cc = read(fd, bufPtr, bufSize - bufUsed); + bufUsed += cc; + bufPtr = bufBase; - } while (cc > 0); + } + while (cc > 0); - if (cc < 0) { + if (cc < 0) + { perror(file); close(fd); return FALSE; } - if (bufused) { - if (!insertline(num, bufptr, bufused)) { + if (bufUsed) + { + if (!insertLine(num, bufPtr, bufUsed)) + { close(fd); return -1; } - linecount++; - charcount += bufused; + lineCount++; + charCount += bufUsed; } close(fd); - printf("%d lines%s, %d chars\n", linecount, - (bufused ? " (incomplete)" : ""), charcount); + printf("%d lines%s, %d chars\n", lineCount, + (bufUsed ? " (incomplete)" : ""), charCount); return TRUE; } @@ -991,24 +1037,22 @@ readlines(file, num) * Returns TRUE if successful, or FALSE on an error with a message output. */ static BOOL -writelines(file, num1, num2) - const char * file; - NUM num1; - NUM num2; +writeLines(const char * file, NUM num1, NUM num2) { int fd; LINE * lp; - LEN linecount; - LEN charcount; + LEN lineCount; + LEN charCount; - if ((num1 < 1) || (num2 > lastnum) || (num1 > num2)) { + if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) + { fprintf(stderr, "Bad line range for write\n"); return FALSE; } - linecount = 0; - charcount = 0; + lineCount = 0; + charCount = 0; fd = creat(file, 0666); @@ -1021,34 +1065,38 @@ writelines(file, num1, num2) printf("\"%s\", ", file); fflush(stdout); - lp = findline(num1); + lp = findLine(num1); - if (lp == NULL) { + if (lp == NULL) + { close(fd); return FALSE; } - while (num1++ <= num2) { - if (write(fd, lp->data, lp->len) != lp->len) { + while (num1++ <= num2) + { + if (write(fd, lp->data, lp->len) != lp->len) + { perror(file); close(fd); return FALSE; } - charcount += lp->len; - linecount++; + charCount += lp->len; + lineCount++; lp = lp->next; } - if (close(fd) < 0) { + if (close(fd) < 0) + { perror(file); return FALSE; } - printf("%d lines, %d chars\n", linecount, charcount); + printf("%d lines, %d chars\n", lineCount, charCount); return TRUE; } @@ -1057,35 +1105,35 @@ writelines(file, num1, num2) /* * 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 + * If expandFlag is TRUE, then the line is printed specially to * show magic characters. */ static BOOL -printlines(num1, num2, expandflag) - NUM num1; - NUM num2; - BOOL expandflag; +printLines(NUM num1, NUM num2, BOOL expandFlag) { const LINE * lp; const unsigned char * cp; int ch; LEN count; - if ((num1 < 1) || (num2 > lastnum) || (num1 > num2)) { + if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) + { fprintf(stderr, "Bad line range for print\n"); return FALSE; } - lp = findline(num1); + lp = findLine(num1); if (lp == NULL) return FALSE; - while (!intflag && (num1 <= num2)) { - if (!expandflag) { + while (!intFlag && (num1 <= num2)) + { + if (!expandFlag) + { write(STDOUT, lp->data, lp->len); - setcurnum(num1++); + setCurNum(num1++); lp = lp->next; continue; @@ -1101,20 +1149,24 @@ printlines(num1, num2, expandflag) if ((count > 0) && (cp[count - 1] == '\n')) count--; - while (count-- > 0) { + while (count-- > 0) + { ch = *cp++; - if (ch & 0x80) { + if (ch & 0x80) + { fputs("M-", stdout); ch &= 0x7f; } - if (ch < ' ') { + if (ch < ' ') + { fputc('^', stdout); ch += '@'; } - if (ch == 0x7f) { + if (ch == 0x7f) + { fputc('^', stdout); ch = '?'; } @@ -1124,7 +1176,7 @@ printlines(num1, num2, expandflag) fputs("$\n", stdout); - setcurnum(num1++); + setCurNum(num1++); lp = lp->next; } @@ -1140,52 +1192,53 @@ printlines(num1, num2, expandflag) * Returns TRUE if successful. */ static BOOL -insertline(num, data, len) - NUM num; - const char * data; - LEN len; +insertLine(NUM num, const char * data, LEN len) { - LINE * newlp; + LINE * newLp; LINE * lp; - if ((num < 1) || (num > lastnum + 1)) { + if ((num < 1) || (num > lastNum + 1)) + { fprintf(stderr, "Inserting at bad line number\n"); return FALSE; } - newlp = (LINE *) malloc(sizeof(LINE) + len - 1); + newLp = (LINE *) malloc(sizeof(LINE) + len - 1); - if (newlp == NULL) { + if (newLp == NULL) + { fprintf(stderr, "Failed to allocate memory for line\n"); return FALSE; } - memcpy(newlp->data, data, len); - newlp->len = len; + memcpy(newLp->data, data, len); + newLp->len = len; - if (num > lastnum) + if (num > lastNum) lp = &lines; - else { - lp = findline(num); + else + { + lp = findLine(num); - if (lp == NULL) { - free((char *) newlp); + if (lp == NULL) + { + free((char *) newLp); return FALSE; } } - newlp->next = lp; - newlp->prev = lp->prev; - lp->prev->next = newlp; - lp->prev = newlp; + newLp->next = lp; + newLp->prev = lp->prev; + lp->prev->next = newLp; + lp->prev = newLp; - lastnum++; + lastNum++; dirty = TRUE; - return setcurnum(num); + return setCurNum(num); } @@ -1193,43 +1246,44 @@ insertline(num, data, len) * Delete lines from the given range. */ static BOOL -deletelines(num1, num2) - NUM num1; - NUM num2; +deleteLines(NUM num1, NUM num2) { LINE * lp; LINE * nlp; LINE * plp; NUM count; - if ((num1 < 1) || (num2 > lastnum) || (num1 > num2)) { + if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) + { fprintf(stderr, "Bad line numbers for delete\n"); return FALSE; } - lp = findline(num1); + lp = findLine(num1); if (lp == NULL) return FALSE; - if ((curnum >= num1) && (curnum <= num2)) { - if (num2 < lastnum) - setcurnum(num2 + 1); + if ((curNum >= num1) && (curNum <= num2)) + { + if (num2 < lastNum) + setCurNum(num2 + 1); else if (num1 > 1) - setcurnum(num1 - 1); + setCurNum(num1 - 1); else - curnum = 0; + curNum = 0; } count = num2 - num1 + 1; - if (curnum > num2) - curnum -= count; + if (curNum > num2) + curNum -= count; - lastnum -= count; + lastNum -= count; - while (count-- > 0) { + while (count-- > 0) + { nlp = lp->next; plp = lp->prev; plp->next = nlp; @@ -1255,42 +1309,43 @@ deletelines(num1, num2) * with an error printed. */ static NUM -searchlines(str, num1, num2) - const char * str; - NUM num1; - NUM num2; +searchLines(const char * str, NUM num1, NUM num2) { const LINE * lp; int len; - if ((num1 < 1) || (num2 > lastnum) || (num1 > num2)) { + if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) + { fprintf(stderr, "Bad line numbers for search\n"); return 0; } - if (*str == '\0') { - if (searchstring[0] == '\0') { + if (*str == '\0') + { + if (searchString[0] == '\0') + { fprintf(stderr, "No previous search string\n"); return 0; } - str = searchstring; + str = searchString; } - if (str != searchstring) - strcpy(searchstring, str); + if (str != searchString) + strcpy(searchString, str); len = strlen(str); - lp = findline(num1); + lp = findLine(num1); if (lp == NULL) return 0; - while (num1 <= num2) { - if (findstring(lp, str, len, 0) >= 0) + while (num1 <= num2) + { + if (findString(lp, str, len, 0) >= 0) return num1; num1++; @@ -1307,44 +1362,49 @@ searchlines(str, num1, num2) * Return a pointer to the specified line number. */ static LINE * -findline(num) - NUM num; +findLine(NUM num) { LINE * lp; NUM lnum; - if ((num < 1) || (num > lastnum)) { + 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 (curNum <= 0) + { + curNum = 1; + curLine = lines.next; } - if (num == curnum) - return curline; + if (num == curNum) + return curLine; - lp = curline; - lnum = curnum; + lp = curLine; + lnum = curNum; - if (num < (curnum / 2)) { + if (num < (curNum / 2)) + { lp = lines.next; lnum = 1; } - else if (num > ((curnum + lastnum) / 2)) { + else if (num > ((curNum + lastNum) / 2)) + { lp = lines.prev; - lnum = lastnum; + lnum = lastNum; } - while (lnum < num) { + while (lnum < num) + { lp = lp->next; lnum++; } - while (lnum > num) { + while (lnum > num) + { lp = lp->prev; lnum--; } @@ -1358,18 +1418,17 @@ findline(num) * Returns TRUE if successful. */ static BOOL -setcurnum(num) - NUM num; +setCurNum(NUM num) { LINE * lp; - lp = findline(num); + lp = findLine(num); if (lp == NULL) return FALSE; - curnum = num; - curline = lp; + curNum = num; + curLine = lp; return TRUE; } diff --git a/cmd_file.c b/cmd_file.c new file mode 100644 index 0000000..486254d --- /dev/null +++ b/cmd_file.c @@ -0,0 +1,233 @@ +/* + * Copyright (c) 1999 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); + + +void +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); + } +} + + +/* + * 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/cmd_find.c b/cmd_find.c new file mode 100644 index 0000000..63b77b3 --- /dev/null +++ b/cmd_find.c @@ -0,0 +1,374 @@ +/* + * Copyright (c) 1999 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. + */ +void +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; + } + + 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; + } + + argc--; + fileType = *argv++; + } + else if (strcmp(cp, "-name") == 0) + { + if ((argc <= 0) || (**argv == '-')) + { + fprintf(stderr, "Missing file name\n"); + + return; + } + + argc--; + filePattern = *argv++; + } + else if (strcmp(cp, "-size") == 0) + { + if ((argc <= 0) || (**argv == '-')) + { + fprintf(stderr, "Missing file size\n"); + + return; + } + + 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; + } + } + else + { + if (*cp != '-') + fprintf(stderr, "Missing dash in option\n"); + else + fprintf(stderr, "Unknown option\n"); + + return; + } + } + + /* + * 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; + } + + if (!S_ISDIR(statBuf.st_mode)) + { + fprintf(stderr, "Path \"%s\" is not a directory\n", path); + + return; + } + + /* + * 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); +} + + +/* + * 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/cmd_grep.c b/cmd_grep.c index 6f6e955..1f10eb2 100644 --- a/cmd_grep.c +++ b/cmd_grep.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998 by David I. Bell + * Copyright (c) 1999 by David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. * @@ -12,41 +12,41 @@ static BOOL search - PROTO((const char * string, const char * word, BOOL ignorecase)); + (const char * string, const char * word, BOOL ignoreCase); void -do_grep(argc, argv) - int argc; - const char ** argv; +do_grep(int argc, const char ** argv) { FILE * fp; const char * word; const char * name; const char * cp; - BOOL tellname; - BOOL ignorecase; - BOOL tellline; + BOOL tellName; + BOOL ignoreCase; + BOOL tellLine; long line; - char buf[BUFSIZE]; + char buf[BUF_SIZE]; - ignorecase = FALSE; - tellline = FALSE; + ignoreCase = FALSE; + tellLine = FALSE; argc--; argv++; - if (**argv == '-') { + if (**argv == '-') + { argc--; cp = *argv++; - while (*++cp) switch (*cp) { + while (*++cp) switch (*cp) + { case 'i': - ignorecase = TRUE; + ignoreCase = TRUE; break; case 'n': - tellline = TRUE; + tellLine = TRUE; break; default: @@ -59,14 +59,16 @@ do_grep(argc, argv) word = *argv++; argc--; - tellname = (argc > 1); + tellName = (argc > 1); - while (argc-- > 0) { + while (argc-- > 0) + { name = *argv++; fp = fopen(name, "r"); - if (fp == NULL) { + if (fp == NULL) + { perror(name); continue; @@ -74,8 +76,10 @@ do_grep(argc, argv) line = 0; - while (fgets(buf, sizeof(buf), fp)) { - if (intflag) { + while (fgets(buf, sizeof(buf), fp)) + { + if (intFlag) + { fclose(fp); return; @@ -88,11 +92,12 @@ do_grep(argc, argv) if (*cp != '\n') fprintf(stderr, "%s: Line too long\n", name); - if (search(buf, word, ignorecase)) { - if (tellname) + if (search(buf, word, ignoreCase)) + { + if (tellName) printf("%s: ", name); - if (tellline) + if (tellLine) printf("%ld: ", line); fputs(buf, stdout); @@ -111,22 +116,21 @@ do_grep(argc, argv) * See if the specified word is found in the specified string. */ static BOOL -search(string, word, ignorecase) - const char * string; - const char * word; - BOOL ignorecase; +search(const char * string, const char * word, BOOL ignoreCase) { const char * cp1; const char * cp2; int len; - int lowfirst; + int lowFirst; int ch1; int ch2; len = strlen(word); - if (!ignorecase) { - while (TRUE) { + if (!ignoreCase) + { + while (TRUE) + { string = strchr(string, word[0]); if (string == NULL) @@ -143,14 +147,15 @@ search(string, word, ignorecase) * Here if we need to check case independence. * Do the search by lower casing both strings. */ - lowfirst = *word; + lowFirst = *word; - if (isupper(lowfirst)) - lowfirst = tolower(lowfirst); + if (isupper(lowFirst)) + lowFirst = tolower(lowFirst); - while (TRUE) { - while (*string && (*string != lowfirst) && - (!isupper(*string) || (tolower(*string) != lowfirst))) + while (TRUE) + { + while (*string && (*string != lowFirst) && + (!isupper(*string) || (tolower(*string) != lowFirst))) { string++; } @@ -161,7 +166,8 @@ search(string, word, ignorecase) cp1 = string; cp2 = word; - do { + do + { if (*cp2 == '\0') return TRUE; @@ -175,7 +181,8 @@ search(string, word, ignorecase) if (isupper(ch2)) ch2 = tolower(ch2); - } while (ch1 == ch2); + } + while (ch1 == ch2); string++; } diff --git a/cmd_gzip.c b/cmd_gzip.c index f123fd7..617984d 100644 --- a/cmd_gzip.c +++ b/cmd_gzip.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998 by David I. Bell + * Copyright (c) 1999 by David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. * @@ -55,17 +55,15 @@ static const CONVERT gunzipConvertTable[] = /* * Local routines to compress and uncompress files. */ -static BOOL gzip PROTO((const char * inputFile, const char * outputFile)); -static BOOL gunzip PROTO((const char * inputFile, const char * outputFile)); +static BOOL gzip(const char * inputFile, const char * outputFile); +static BOOL gunzip(const char * inputFile, const char * outputFile); static const char * convertName - PROTO((const CONVERT * table, const char * inFile)); + (const CONVERT * table, const char * inFile); void -do_gzip(argc, argv) - int argc; - const char ** argv; +do_gzip(int argc, const char ** argv) { const char * outPath; const char * inFile; @@ -112,7 +110,7 @@ do_gzip(argc, argv) */ if (outPath == NULL) { - while (!intflag && (argc-- > 0)) + while (!intFlag && (argc-- > 0)) { inFile = *argv++; @@ -144,7 +142,7 @@ do_gzip(argc, argv) * specified input file to the exactly specified output path, * or else complain. */ - if (!isadir(outPath)) + if (!isDirectory(outPath)) { if (argc == 1) (void) gzip(*argv, outPath); @@ -159,7 +157,7 @@ do_gzip(argc, argv) * Compress each of the input files into the specified * output directory, converting their extensions if possible. */ - while (!intflag && (argc-- > 0)) + while (!intFlag && (argc-- > 0)) { inFile = *argv++; @@ -184,7 +182,7 @@ do_gzip(argc, argv) * Now build the output path name by prefixing it with * the output directory. */ - outFile = buildname(outPath, outFile); + outFile = buildName(outPath, outFile); /* * Compress the input file without deleting the input file. @@ -195,9 +193,7 @@ do_gzip(argc, argv) void -do_gunzip(argc, argv) - int argc; - const char ** argv; +do_gunzip(int argc, const char ** argv) { const char * outPath; const char * inFile; @@ -245,7 +241,7 @@ do_gunzip(argc, argv) */ if (outPath == NULL) { - while (!intflag && (argc-- > 0)) + while (!intFlag && (argc-- > 0)) { inFile = *argv++; @@ -281,11 +277,23 @@ do_gunzip(argc, argv) /* * There is an output path specified. - * If it is not a directory, then either uncompress the single - * specified input file to the exactly specified output path, + * 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)) + (void) gunzip(*argv++, outPath); + + return; + } + + /* + * 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 (!isadir(outPath)) + if (!isDirectory(outPath)) { if (argc == 1) (void) gunzip(*argv, outPath); @@ -300,7 +308,7 @@ do_gunzip(argc, argv) * Uncompress each of the input files into the specified * output directory, converting their extensions if possible. */ - while (!intflag && (argc-- > 0)) + while (!intFlag && (argc-- > 0)) { inFile = *argv++; @@ -325,7 +333,7 @@ do_gunzip(argc, argv) * Now build the output path name by prefixing it with * the output directory. */ - outFile = buildname(outPath, outFile); + outFile = buildName(outPath, outFile); /* * Uncompress the input file without deleting the input file. @@ -340,17 +348,15 @@ do_gunzip(argc, argv) * Returns TRUE if successful. */ static BOOL -gzip(inputFileName, outputFileName) - const char * inputFileName; - const char * outputFileName; +gzip(const char * inputFileName, const char * outputFileName) { gzFile outGZ; int inFD; int len; int err; - struct stat statbuf1; - struct stat statbuf2; - char buf[BUFSIZE]; + struct stat statBuf1; + struct stat statBuf2; + char buf[BUF_SIZE]; outGZ = NULL; inFD = -1; @@ -359,19 +365,21 @@ gzip(inputFileName, outputFileName) * See if the output file is the same as the input file. * If so, complain about it. */ - if (stat(inputFileName, &statbuf1) < 0) { + if (stat(inputFileName, &statBuf1) < 0) + { perror(inputFileName); return FALSE; } - if (stat(outputFileName, &statbuf2) < 0) { - statbuf2.st_ino = -1; - statbuf2.st_dev = -1; + 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)) + if ((statBuf1.st_dev == statBuf2.st_dev) && + (statBuf1.st_ino == statBuf2.st_ino)) { fprintf(stderr, "Cannot compress file \"%s\" on top of itself\n", @@ -385,7 +393,8 @@ gzip(inputFileName, outputFileName) */ inFD = open(inputFileName, O_RDONLY); - if (inFD < 0) { + if (inFD < 0) + { perror(inputFileName); goto failed; @@ -396,7 +405,8 @@ gzip(inputFileName, outputFileName) */ outGZ = gzopen(outputFileName, "wb9"); - if (outGZ == NULL) { + if (outGZ == NULL) + { fprintf(stderr, "%s: gzopen failed\n", outputFileName); goto failed; @@ -406,19 +416,22 @@ gzip(inputFileName, outputFileName) * 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) { + 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) + if (intFlag) goto failed; } - if (len < 0) { + if (len < 0) + { perror(inputFileName); goto failed; @@ -427,7 +440,8 @@ gzip(inputFileName, outputFileName) /* * All done, close the files. */ - if (close(inFD)) { + if (close(inFD)) + { perror(inputFileName); goto failed; @@ -435,7 +449,8 @@ gzip(inputFileName, outputFileName) inFD = -1; - if (gzclose(outGZ) != Z_OK) { + if (gzclose(outGZ) != Z_OK) + { fprintf(stderr, "%s: gzclose failed\n", outputFileName); goto failed; @@ -468,17 +483,15 @@ failed: * Returns TRUE if successful. */ static BOOL -gunzip(inputFileName, outputFileName) - const char * inputFileName; - const char * outputFileName; +gunzip(const char * inputFileName, const char * outputFileName) { gzFile inGZ; int outFD; int len; int err; - struct stat statbuf1; - struct stat statbuf2; - char buf[BUFSIZE]; + struct stat statBuf1; + struct stat statBuf2; + char buf[BUF_SIZE]; inGZ = NULL; outFD = -1; @@ -487,19 +500,21 @@ gunzip(inputFileName, outputFileName) * See if the output file is the same as the input file. * If so, complain about it. */ - if (stat(inputFileName, &statbuf1) < 0) { + if (stat(inputFileName, &statBuf1) < 0) + { perror(inputFileName); return FALSE; } - if (stat(outputFileName, &statbuf2) < 0) { - statbuf2.st_ino = -1; - statbuf2.st_dev = -1; + 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)) + if ((statBuf1.st_dev == statBuf2.st_dev) && + (statBuf1.st_ino == statBuf2.st_ino)) { fprintf(stderr, "Cannot uncompress file \"%s\" on top of itself\n", @@ -513,7 +528,8 @@ gunzip(inputFileName, outputFileName) */ inGZ = gzopen(inputFileName, "rb"); - if (inGZ == NULL) { + if (inGZ == NULL) + { fprintf(stderr, "%s: gzopen failed\n", inputFileName); return FALSE; @@ -522,9 +538,13 @@ gunzip(inputFileName, outputFileName) /* * Create the output file. */ - outFD = open(outputFileName, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (isDevice(outputFileName)) + outFD = open(outputFileName, O_WRONLY); + else + outFD = open(outputFileName, O_WRONLY|O_CREAT|O_TRUNC, 0666); - if (outFD < 0) { + if (outFD < 0) + { perror(outputFileName); goto failed; @@ -534,18 +554,21 @@ gunzip(inputFileName, outputFileName) * 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) { + while ((len = gzread(inGZ, buf, sizeof(buf))) > 0) + { + if (fullWrite(outFD, buf, len) < 0) + { perror(outputFileName); goto failed; } - if (intflag) + if (intFlag) goto failed; } - if (len < 0) { + if (len < 0) + { fprintf(stderr, "%s: %s\n", inputFileName, gzerror(inGZ, &err)); @@ -555,7 +578,8 @@ gunzip(inputFileName, outputFileName) /* * All done, close the files. */ - if (close(outFD)) { + if (close(outFD)) + { perror(outputFileName); goto failed; @@ -563,7 +587,8 @@ gunzip(inputFileName, outputFileName) outFD = -1; - if (gzclose(inGZ) != Z_OK) { + if (gzclose(inGZ) != Z_OK) + { fprintf(stderr, "%s: gzclose failed\n", inputFileName); goto failed; @@ -600,13 +625,11 @@ failed: * returned. */ const char * -convertName(table, inputFile) - const CONVERT * table; - const char * inputFile; +convertName(const CONVERT * table, const char * inputFile) { int inputLength; int testLength; - static char buf[PATHLEN]; + static char buf[PATH_LEN]; inputLength = strlen(inputFile); diff --git a/cmd_ls.c b/cmd_ls.c index 348b295..ab8d8db 100644 --- a/cmd_ls.c +++ b/cmd_ls.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998 by David I. Bell + * Copyright (c) 1999 by David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. * @@ -15,7 +15,7 @@ #include -#define LISTSIZE 256 +#define LISTSIZE 8192 #ifdef S_ISLNK @@ -32,60 +32,76 @@ #define LSF_DIR 0x02 #define LSF_INODE 0x04 #define LSF_MULT 0x08 +#define LSF_FLAG 0x10 +#define LSF_COLUMN 0x20 +/* + * Data holding list of files. + */ static char ** list; -static int listsize; -static int listused; +static int listSize; +static int listUsed; -static void lsfile - PROTO((const char * name, const struct stat * statbuf, int flags)); +/* + * 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); void -do_ls(argc, argv) - int argc; - const char ** argv; +do_ls(int argc, const char ** argv) { const char * cp; const char * name; int flags; int i; + int displayWidth; + BOOL endSlash; DIR * dirp; - BOOL endslash; - char ** newlist; struct dirent * dp; - char fullname[PATHLEN]; - struct stat statbuf; - - static const char * def[2] = {"-ls", "."}; + char fullName[PATH_LEN]; + struct stat statBuf; - if (listsize == 0) { - list = (char **) malloc(LISTSIZE * sizeof(char *)); + static const char * def[] = {"."}; - if (list == NULL) { - fprintf(stderr, "No memory for ls buffer\n"); - - return; - } - - listsize = LISTSIZE; - } - - listused = 0; + /* + * Reset for a new listing run. + */ + clearListNames(); + displayWidth = 0; flags = 0; - if ((argc > 1) && (argv[1][0] == '-')) + /* + * Handle options. + */ + argc--; + argv++; + + while ((argc > 0) && (**argv == '-')) { + cp = *argv++ + 1; argc--; - cp = *(++argv) + 1; - while (*cp) switch (*cp++) { + while (*cp) switch (*cp++) + { case 'l': flags |= LSF_LONG; 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]); @@ -93,36 +109,94 @@ do_ls(argc, argv) } } - if (argc <= 1) { - argc = 2; + /* + * If long listing is specified then turn off column listing. + */ + if (flags & LSF_LONG) + 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 > 2) + if (argc > 1) flags |= LSF_MULT; - while (argc-- > 1) { - name = *(++argv); - endslash = (*name && (name[strlen(name) - 1] == '/')); + /* + * 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; + } + } + + /* + * 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; + + /* + * 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) { + if (LSTAT(name, &statBuf) < 0) + { perror(name); continue; } - if ((flags & LSF_DIR) || (!S_ISDIR(statbuf.st_mode))) { - lsfile(name, &statbuf, flags); - + /* + * If this file name is not a directory, then ignore it. + */ + if (!S_ISDIR(statBuf.st_mode)) continue; - } /* - * Do all the files in a directory. + * Collect all the files in the directory. */ dirp = opendir(name); - if (dirp == NULL) { + if (dirp == NULL) + { perror(name); continue; @@ -131,172 +205,374 @@ do_ls(argc, argv) if (flags & LSF_MULT) printf("\n%s:\n", name); - while ((dp = readdir(dirp)) != NULL) { - if (intflag) - break; + while (!intFlag && ((dp = readdir(dirp)) != NULL)) + { + fullName[0] = '\0'; - fullname[0] = '\0'; + if ((*name != '.') || (name[1] != '\0')) + { + strcpy(fullName, name); - if ((*name != '.') || (name[1] != '\0')) { - strcpy(fullname, name); - if (!endslash) - strcat(fullname, "/"); + if (!endSlash) + strcat(fullName, "/"); } - strcat(fullname, dp->d_name); + strcat(fullName, dp->d_name); - if (listused >= listsize) { - newlist = realloc(list, - ((sizeof(char **)) * (listsize + LISTSIZE))); + /* + * Save the file name in the list. + */ + if (!addListName(fullName)) + { + closedir(dirp); - if (newlist == NULL) { - fprintf(stderr, "No memory for ls buffer\n"); - break; - } - - list = newlist; - listsize += LISTSIZE; - } - - list[listused] = strdup(fullname); - - if (list[listused] == NULL) { - fprintf(stderr, "No memory for filenames\n"); - break; + return; } - - listused++; } closedir(dirp); /* - * Sort the files. + * List the files we collected in this directory, + * and then clear the list. */ - qsort((void *) list, listused, sizeof(char *), namesort); + listAllFiles(flags, displayWidth); + clearListNames(); + } +} - /* - * Now finally list the filenames. - */ - for (i = 0; i < listused; i++) { - name = list[i]; - if (LSTAT(name, &statbuf) < 0) { - perror(name); - free((char *) name); +/* + * 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++; - continue; - } + if (flags & LSF_INODE) + fileWidth += 8; - cp = strrchr(name, '/'); + fileWidth += 2; + } - if (cp) - cp++; - else - cp = name; + /* + * Now list the fileNames. + */ + for (i = 0; i < listUsed; i++) + { + name = list[i]; - lsfile(cp, &statbuf, flags); + if (LSTAT(name, &statBuf) < 0) + { + perror(name); - free((char *) name); + continue; } - listused = 0; + 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 an LS of a particular file name according to the flags. + * 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 -lsfile(name, statbuf, flags) - const char * name; - const struct stat * statbuf; - int flags; +listFile( + const char * name, + const struct stat * statBuf, + int flags, + int width +) { char * cp; struct passwd * pwd; struct group * grp; int len; - char buf[PATHLEN]; - static char username[12]; - static int userid; - static BOOL useridknown; - static char groupname[12]; - static int groupid; - static BOOL groupidknown; - + int mode; + int flagChar; + int usedWidth; + char buf[PATH_LEN]; + static char userName[12]; + static int userId; + static BOOL userIdKnown; + static char groupName[12]; + static int groupId; + static BOOL groupIdKnown; + + mode = statBuf->st_mode; + + /* + * Initialise buffers for use. + */ cp = buf; - *cp = '\0'; + buf[0] = '\0'; + flagChar = '\0'; - if (flags & LSF_INODE) { - sprintf(cp, "%5ld ", statbuf->st_ino); + /* + * Show the inode number if requested. + */ + if (flags & LSF_INODE) + { + sprintf(cp, "%7ld ", statBuf->st_ino); cp += strlen(cp); } - if (flags & LSF_LONG) { - strcpy(cp, modestring(statbuf->st_mode)); + /* + * Create the long status line if requested. + */ + if (flags & LSF_LONG) + { + strcpy(cp, modeString(mode)); cp += strlen(cp); - sprintf(cp, "%3d ", statbuf->st_nlink); + sprintf(cp, "%3d ", statBuf->st_nlink); cp += strlen(cp); - if (!useridknown || (statbuf->st_uid != userid)) { - pwd = getpwuid(statbuf->st_uid); + if (!userIdKnown || (statBuf->st_uid != userId)) + { + pwd = getpwuid(statBuf->st_uid); if (pwd) - strcpy(username, pwd->pw_name); + strcpy(userName, pwd->pw_name); else - sprintf(username, "%d", statbuf->st_uid); + sprintf(userName, "%d", statBuf->st_uid); - userid = statbuf->st_uid; - useridknown = TRUE; + userId = statBuf->st_uid; + userIdKnown = TRUE; } - sprintf(cp, "%-8s ", username); + sprintf(cp, "%-8s ", userName); cp += strlen(cp); - if (!groupidknown || (statbuf->st_gid != groupid)) { - grp = getgrgid(statbuf->st_gid); + if (!groupIdKnown || (statBuf->st_gid != groupId)) + { + grp = getgrgid(statBuf->st_gid); if (grp) - strcpy(groupname, grp->gr_name); + strcpy(groupName, grp->gr_name); else - sprintf(groupname, "%d", statbuf->st_gid); + sprintf(groupName, "%d", statBuf->st_gid); - groupid = statbuf->st_gid; - groupidknown = TRUE; + groupId = statBuf->st_gid; + groupIdKnown = TRUE; } - sprintf(cp, "%-8s ", groupname); + sprintf(cp, "%-8s ", groupName); cp += strlen(cp); - if (S_ISBLK(statbuf->st_mode) || S_ISCHR(statbuf->st_mode)) - sprintf(cp, "%3d, %3d ", statbuf->st_rdev >> 8, - statbuf->st_rdev & 0xff); + 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); + sprintf(cp, "%8ld ", statBuf->st_size); cp += strlen(cp); - sprintf(cp, " %-12s ", timestring(statbuf->st_mtime)); + 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(statbuf->st_mode)) { - len = readlink(name, buf, PATHLEN - 1); + if ((flags & LSF_LONG) && S_ISLNK(mode)) + { + len = readlink(name, buf, PATH_LEN - 1); - if (len >= 0) { + if (len >= 0) + { buf[len] = '\0'; printf(" -> %s", buf); } + + usedWidth += strlen(buf) + 4; } #endif - fputc('\n', stdout); + /* + * 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/cmd_tar.c b/cmd_tar.c index 69f50ac..c2f240a 100644 --- a/cmd_tar.c +++ b/cmd_tar.c @@ -1,226 +1,370 @@ /* - * Copyright (c) 1998 by David I. Bell + * Copyright (c) 1999 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 format. + * Tar file constants. */ -#define TBLOCK 512 -#define NAMSIZ 100 - -union hblock { - char dummy[TBLOCK]; - struct header { - char name[NAMSIZ]; - char mode[8]; - char uid[8]; - char gid[8]; - char size[12]; - char mtime[12]; - char chksum[8]; - char linkflag; - char linkname[NAMSIZ]; - char extno[4]; - char extotal[4]; - char efsize[12]; - } dbuf; -} dblock; - - -static BOOL inheader; -static BOOL badheader; -static BOOL badwrite; -static BOOL extracting; -static BOOL warnedroot; -static BOOL eof; -static BOOL verbose; -static long datacc; -static int outfd; -static char outname[NAMSIZ]; - - -static void doheader PROTO((const struct header * hp)); -static void dodata PROTO((const char * cp, int count)); -static void createpath PROTO((const char * name, int mode)); -static long getoctal PROTO((const char * cp, int len)); +#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 void readTarFile(int fileCount, const char ** fileTable); +static void readData(const char * cp, int count); +static void createPath(const char * name, int mode); +static long getOctal(const char * cp, int len); + +static void 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 void writeTarFile(int fileCount, const char ** fileTable); +static void writeTarBlock(const char * buf, int len); +static BOOL putOctal(char * cp, int len, long value); + void -do_tar(argc, argv) - int argc; - const char ** argv; +do_tar(int argc, const char ** argv) { - const char * str; - const char * devname; - const char * cp; - int devfd; - int cc; - long incc; - int blocksize; - BOOL createflag; - BOOL listflag; - BOOL fileflag; - char buf[BUFSIZE]; - - if (argc < 2) { + const char * options; + + argc--; + argv++; + + if (argc < 2) + { fprintf(stderr, "Too few arguments for tar\n"); return; } - createflag = FALSE; - extracting = FALSE; - listflag = FALSE; - fileflag = FALSE; - verbose = FALSE; - badwrite = FALSE; - badheader = FALSE; - warnedroot = FALSE; - eof = FALSE; - inheader = TRUE; - incc = 0; - datacc = 0; - outfd = -1; - blocksize = sizeof(buf); - - for (str = argv[1]; *str; str++) { - switch (*str) { - case 'f': fileflag = TRUE; break; - case 't': listflag = TRUE; break; - case 'x': extracting = TRUE; break; - case 'v': verbose = TRUE; break; + 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; + } + + tarName = *argv++; + argc--; + + break; + + case 't': + listFlag = TRUE; + break; + + case 'x': + extractFlag = TRUE; + break; case 'c': - case 'a': - fprintf(stderr, "Writing is not supported\n"); + createFlag = TRUE; + break; - return; + case 'v': + verboseFlag = TRUE; + break; default: - fprintf(stderr, "Unknown tar flag\n"); + fprintf(stderr, "Unknown tar flag '%c'\n", *options); return; } } - if (!fileflag) { - fprintf(stderr, "The 'f' flag must be specified\n"); + /* + * Validate the options. + */ + if (extractFlag + listFlag + createFlag != 1) + { + fprintf(stderr, "Exactly one of 'c', 'x' or 't' must be specified\n"); return; } - if (argc < 3) { - fprintf(stderr, "Missing input name\n"); + if (tarName == NULL) + { + fprintf(stderr, "The 'f' flag must be specified\n"); return; } - devname = argv[2]; + /* + * Do the correct type of action supplying the rest of the + * command line arguments as the list of files to process. + */ + if (createFlag) + writeTarFile(argc, argv); + else + readTarFile(argc, argv); +} - if (extracting + listflag != 1) { - fprintf(stderr, "Exactly one of 'x' or 't' must be specified\n"); - return; - } +/* + * 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. + */ +static void +readTarFile(int fileCount, const char ** fileTable) +{ + const char * cp; + int cc; + int inCc; + int blockSize; + char buf[BUF_SIZE]; + + skipFileFlag = FALSE; + badHeader = FALSE; + warnedRoot = FALSE; + eofFlag = FALSE; + inHeader = TRUE; + inCc = 0; + dataCc = 0; + outFd = -1; + blockSize = sizeof(buf); + cp = buf; - devfd = open(devname, 0); + /* + * Open the tar file for reading. + */ + tarFd = open(tarName, O_RDONLY); - if (devfd < 0) { - perror(devname); + if (tarFd < 0) + { + perror(tarName); return; } - cp = buf; - - while (TRUE) { - if ((incc == 0) && !eof) { - while (incc < blocksize) { - cc = read(devfd, &buf[incc], blocksize - incc); - - if (cc < 0) { - perror(devname); - goto done; - } - - if (cc == 0) - break; - - incc += cc; - } - + /* + * 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; - } - - if (intflag) { - if (extracting && (outfd >= 0)) - close(outfd); + inCc = fullRead(tarFd, buf, blockSize); - close(devfd); + if (inCc < 0) + { + perror(tarName); - return; - } - - if (inheader) { - if ((incc == 0) || eof) goto done; + } + + if (inCc == 0) + { + fprintf(stderr, + "Unexpected end of file from \"%s\"", + tarName); - if (incc < TBLOCK) { - fprintf(stderr, "Short block for header\n"); goto done; } + } - doheader((struct header *) cp); + /* + * If we are expecting a header block then examine it. + */ + if (inHeader) + { + readHeader((const TarHeader *) cp, fileCount, fileTable); - cp += TBLOCK; - incc -= TBLOCK; + cp += TAR_BLOCK_SIZE; + inCc -= TAR_BLOCK_SIZE; continue; } - cc = incc; + /* + * 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 (cc > dataCc) + cc = dataCc; - dodata(cp, cc); + readData(cp, cc); - if (cc % TBLOCK) - cc += TBLOCK - (cc % TBLOCK); + /* + * 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; + inCc -= cc; } + /* + * Check for an interrupt. + */ + if (intFlag) + fprintf(stderr, "Interrupted - aborting\n"); + + done: - close(devfd); + /* + * Close the tar file if needed. + */ + if ((tarFd >= 0) && (close(tarFd) < 0)) + perror(tarName); + + /* + * 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); } +/* + * 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. + */ static void -doheader(hp) - const struct header * hp; +readHeader(const TarHeader * hp, int fileCount, const char ** fileTable) { int mode; int uid; int gid; - int chksum; + int checkSum; long size; time_t mtime; const char * name; int cc; - BOOL hardlink; - BOOL softlink; + BOOL hardLink; + BOOL softLink; /* * If the block is completely empty, then this is the end of the @@ -228,67 +372,117 @@ doheader(hp) */ name = hp->name; - if (*name == '\0') { - for (cc = TBLOCK; cc > 0; cc--) { + if (*name == '\0') + { + for (cc = TAR_BLOCK_SIZE; cc > 0; cc--) + { if (*name++) return; } - eof = TRUE; + eofFlag = TRUE; return; } - mode = getoctal(hp->mode, sizeof(hp->mode)); - uid = getoctal(hp->uid, sizeof(hp->uid)); - gid = getoctal(hp->gid, sizeof(hp->gid)); - size = getoctal(hp->size, sizeof(hp->size)); - mtime = getoctal(hp->mtime, sizeof(hp->mtime)); - chksum = getoctal(hp->chksum, sizeof(hp->chksum)); - - if ((mode < 0) || (uid < 0) || (gid < 0) || (size < 0)) { - if (!badheader) + /* + * 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)); + checkSum = getOctal(hp->checkSum, sizeof(hp->checkSum)); + + if ((mode < 0) || (uid < 0) || (gid < 0) || (size < 0)) + { + if (!badHeader) fprintf(stderr, "Bad tar header, skipping\n"); - badheader = TRUE; + + badHeader = TRUE; return; } - badheader = FALSE; - badwrite = FALSE; + badHeader = FALSE; + skipFileFlag = FALSE; + + /* + * Check for the file modes. + */ + hardLink = ((hp->typeFlag == TAR_TYPE_HARD_LINK) || + (hp->typeFlag == TAR_TYPE_HARD_LINK - '0')); - hardlink = ((hp->linkflag == 1) || (hp->linkflag == '1')); - softlink = ((hp->linkflag == 2) || (hp->linkflag == '2')); + 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; - if (*name == '/') { + /* + * 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 paths detected, removing leading slashes\n"); + if (!warnedRoot) + { + fprintf(stderr, + "Absolute path detected, removing leading slashes\n"); + } - warnedroot = TRUE; + warnedRoot = TRUE; } - if (!extracting) { - if (verbose) - printf("%s %3d/%-d %9ld %s %s", modestring(mode), - uid, gid, size, timestring(mtime), name); + /* + * 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; + } + + /* + * 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; + 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"); @@ -296,20 +490,24 @@ doheader(hp) return; } - if (verbose) + /* + * We really want to extract the file. + */ + if (verboseFlag) printf("x %s\n", name); - - if (hardlink) { - if (link(hp->linkname, name) < 0) + if (hardLink) + { + if (link(hp->linkName, name) < 0) perror(name); return; } - if (softlink) { + if (softLink) + { #ifdef S_ISLNK - if (symlink(hp->linkname, name) < 0) + if (symlink(hp->linkName, name) < 0) perror(name); #else fprintf(stderr, "Cannot create symbolic links\n"); @@ -317,64 +515,548 @@ doheader(hp) return; } - if (S_ISDIR(mode)) { - createpath(name, mode); + /* + * If the file is a directory, then just create the path. + */ + if (S_ISDIR(mode)) + { + createPath(name, mode); return; } - createpath(name, 0777); + /* + * There is a file to write. + * First create the path to it if necessary with a default permission. + */ + createPath(name, 0777); - inheader = (size == 0); - datacc = size; + inHeader = (size == 0); + dataCc = size; - outfd = creat(name, mode); + /* + * Start the output file. + */ + outFd = open(name, O_WRONLY | O_CREAT | O_TRUNC, mode); - if (outfd < 0) { + if (outFd < 0) + { perror(name); - badwrite = TRUE; + skipFileFlag = TRUE; return; } - if (size == 0) { - close(outfd); - outfd = -1; + /* + * If the file is empty, then that's all we need to do. + */ + if (size == 0) + { + (void) close(outFd); + outFd = -1; } } +/* + * Handle a data block of some specified size that was read. + */ +static void +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; + + /* + * Write the data to the output file. + */ + if (fullWrite(outFd, cp, count) < 0) + { + perror(outName); + (void) close(outFd); + outFd = -1; + skipFileFlag = TRUE; + + return; + } + + /* + * If the write failed, close the file and disable further + * writes to this file. + */ + if (dataCc <= 0) + { + if (close(outFd)) + perror(outName); + + outFd = -1; + } +} + /* - * Handle a data block of some specified size. + * Write a tar file containing the specified files. */ static void -dodata(cp, count) - const char * cp; - int count; +writeTarFile(int fileCount, const char ** fileTable) { - datacc -= count; + struct stat statbuf; + + errorFlag = FALSE; - if (datacc <= 0) - inheader = TRUE; + /* + * Make sure there is at least one file specified. + */ + if (fileCount <= 0) + { + fprintf(stderr, "No files specified to be saved\n"); - if (badwrite || !extracting) return; + } - if (fullWrite(outfd, cp, count) < 0) { - perror(outname); - close(outfd); - outfd = -1; - badwrite = TRUE; + /* + * Create the tar file for writing. + */ + tarFd = open(tarName, O_WRONLY | O_CREAT | O_TRUNC, 0666); + + if (tarFd < 0) + { + perror(tarName); return; } - if (datacc <= 0) { - if (close(outfd)) - perror(outname); + /* + * Get the device and inode of the tar file for checking later. + */ + if (fstat(tarFd, &statbuf) < 0) + { + perror(tarName); + + goto done; + } - outfd = -1; + 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"); + + /* + * 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); +} + + +/* + * 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; } } @@ -386,28 +1068,27 @@ dodata(cp, count) * here, as failures to restore files can be reported later. */ static void -createpath(name, mode) - const char * name; - int mode; +createPath(const char * name, int mode) { char * cp; - char * cpold; - char buf[NAMSIZ]; + char * cpOld; + char buf[TAR_NAME_SIZE]; strcpy(buf, name); cp = strchr(buf, '/'); - while (cp) { - cpold = cp; + while (cp) + { + cpOld = cp; cp = strchr(cp + 1, '/'); - *cpold = '\0'; + *cpOld = '\0'; if (mkdir(buf, cp ? 0777 : mode) == 0) printf("Directory \"%s\" created\n", buf); - *cpold = '/'; + *cpOld = '/'; } } @@ -418,28 +1099,29 @@ createpath(name, mode) * at the end. Returns -1 on an illegal format. */ static long -getoctal(cp, len) - const char * cp; - int len; +getOctal(const char * cp, int len) { long val; - while ((len > 0) && (*cp == ' ')) { + while ((len > 0) && (*cp == ' ')) + { cp++; len--; } - if ((len == 0) || !isoctal(*cp)) + if ((len == 0) || !isOctal(*cp)) return -1; val = 0; - while ((len > 0) && isoctal(*cp)) { + while ((len > 0) && isOctal(*cp)) + { val = val * 8 + *cp++ - '0'; len--; } - while ((len > 0) && (*cp == ' ')) { + while ((len > 0) && (*cp == ' ')) + { cp++; len--; } @@ -450,4 +1132,102 @@ getoctal(cp, len) 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/cmds.c b/cmds.c index f4a5e60..6f3250d 100644 --- a/cmds.c +++ b/cmds.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998 by David I. Bell + * Copyright (c) 1999 by David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. * @@ -20,20 +20,19 @@ void -do_echo(argc, argv) - int argc; - const char ** argv; +do_echo(int argc, const char ** argv) { BOOL first; first = TRUE; - while (argc-- > 1) { + while (argc-- > 1) + { if (!first) fputc(' ', stdout); first = FALSE; - fputs(*++argv, stdout); + fputs(*(++argv), stdout); } fputc('\n', stdout); @@ -41,13 +40,12 @@ do_echo(argc, argv) void -do_pwd(argc, argv) - int argc; - const char ** argv; +do_pwd(int argc, const char ** argv) { - char buf[PATHLEN]; + char buf[PATH_LEN]; - if (getcwd(buf, PATHLEN) == NULL) { + if (getcwd(buf, PATH_LEN) == NULL) + { fprintf(stderr, "Cannot get current directory\n"); return; @@ -58,18 +56,18 @@ do_pwd(argc, argv) void -do_cd(argc, argv) - int argc; - const char ** argv; +do_cd(int argc, const char ** argv) { const char * path; if (argc > 1) path = argv[1]; - else { + else + { path = getenv("HOME"); - if (path == NULL) { + if (path == NULL) + { fprintf(stderr, "No HOME environment variable\n"); return; @@ -82,11 +80,10 @@ do_cd(argc, argv) void -do_mkdir(argc, argv) - int argc; - const char ** argv; +do_mkdir(int argc, const char ** argv) { - while (argc-- > 1) { + while (argc-- > 1) + { if (mkdir(argv[1], 0777) < 0) perror(argv[1]); @@ -96,9 +93,7 @@ do_mkdir(argc, argv) void -do_mknod(argc, argv) - int argc; - const char ** argv; +do_mknod(int argc, const char ** argv) { const char * cp; int mode; @@ -111,7 +106,8 @@ do_mknod(argc, argv) mode |= S_IFBLK; else if (strcmp(argv[2], "c") == 0) mode |= S_IFCHR; - else { + else + { fprintf(stderr, "Bad device type\n"); return; @@ -120,10 +116,11 @@ do_mknod(argc, argv) major = 0; cp = argv[3]; - while (isdecimal(*cp)) + while (isDecimal(*cp)) major = major * 10 + *cp++ - '0'; - if (*cp || (major < 0) || (major > 255)) { + if (*cp || (major < 0) || (major > 255)) + { fprintf(stderr, "Bad major number\n"); return; @@ -132,10 +129,11 @@ do_mknod(argc, argv) minor = 0; cp = argv[4]; - while (isdecimal(*cp)) + while (isDecimal(*cp)) minor = minor * 10 + *cp++ - '0'; - if (*cp || (minor < 0) || (minor > 255)) { + if (*cp || (minor < 0) || (minor > 255)) + { fprintf(stderr, "Bad minor number\n"); return; @@ -147,11 +145,10 @@ do_mknod(argc, argv) void -do_rmdir(argc, argv) - int argc; - const char ** argv; +do_rmdir(int argc, const char ** argv) { - while (argc-- > 1) { + while (argc-- > 1) + { if (rmdir(argv[1]) < 0) perror(argv[1]); @@ -161,20 +158,17 @@ do_rmdir(argc, argv) void -do_sync(argc, argv) - int argc; - const char ** argv; +do_sync(int argc, const char ** argv) { sync(); } void -do_rm(argc, argv) - int argc; - const char ** argv; +do_rm(int argc, const char ** argv) { - while (argc-- > 1) { + while (argc-- > 1) + { if (unlink(argv[1]) < 0) perror(argv[1]); @@ -184,9 +178,7 @@ do_rm(argc, argv) void -do_chmod(argc, argv) - int argc; - const char ** argv; +do_chmod(int argc, const char ** argv) { const char * cp; int mode; @@ -194,10 +186,11 @@ do_chmod(argc, argv) mode = 0; cp = argv[1]; - while (isoctal(*cp)) + while (isOctal(*cp)) mode = mode * 8 + (*cp++ - '0'); - if (*cp) { + if (*cp) + { fprintf(stderr, "Mode must be octal\n"); return; @@ -206,7 +199,8 @@ do_chmod(argc, argv) argc--; argv++; - while (argc-- > 1) { + while (argc-- > 1) + { if (chmod(argv[1], mode) < 0) perror(argv[1]); @@ -216,24 +210,24 @@ do_chmod(argc, argv) void -do_chown(argc, argv) - int argc; - const char ** argv; +do_chown(int argc, const char ** argv) { const char * cp; int uid; struct passwd * pwd; - struct stat statbuf; + struct stat statBuf; cp = argv[1]; - if (isdecimal(*cp)) { + if (isDecimal(*cp)) + { uid = 0; - while (isdecimal(*cp)) + while (isDecimal(*cp)) uid = uid * 10 + (*cp++ - '0'); - if (*cp) { + if (*cp) + { fprintf(stderr, "Bad uid value\n"); return; @@ -241,7 +235,8 @@ do_chown(argc, argv) } else { pwd = getpwnam(cp); - if (pwd == NULL) { + if (pwd == NULL) + { fprintf(stderr, "Unknown user name\n"); return; @@ -253,11 +248,12 @@ do_chown(argc, argv) argc--; argv++; - while (argc-- > 1) { + while (argc-- > 1) + { argv++; - if ((stat(*argv, &statbuf) < 0) || - (chown(*argv, uid, statbuf.st_gid) < 0)) + if ((stat(*argv, &statBuf) < 0) || + (chown(*argv, uid, statBuf.st_gid) < 0)) { perror(*argv); } @@ -266,32 +262,35 @@ do_chown(argc, argv) void -do_chgrp(argc, argv) - int argc; - const char ** argv; +do_chgrp(int argc, const char ** argv) { const char * cp; int gid; struct group * grp; - struct stat statbuf; + struct stat statBuf; cp = argv[1]; - if (isdecimal(*cp)) { + if (isDecimal(*cp)) + { gid = 0; - while (isdecimal(*cp)) + while (isDecimal(*cp)) gid = gid * 10 + (*cp++ - '0'); - if (*cp) { + if (*cp) + { fprintf(stderr, "Bad gid value\n"); return; } - } else { + } + else + { grp = getgrnam(cp); - if (grp == NULL) { + if (grp == NULL) + { fprintf(stderr, "Unknown group name\n"); return; @@ -303,11 +302,12 @@ do_chgrp(argc, argv) argc--; argv++; - while (argc-- > 1) { + while (argc-- > 1) + { argv++; - if ((stat(*argv, &statbuf) < 0) || - (chown(*argv, statbuf.st_uid, gid) < 0)) + if ((stat(*argv, &statBuf) < 0) || + (chown(*argv, statBuf.st_uid, gid) < 0)) { perror(*argv); } @@ -316,9 +316,7 @@ do_chgrp(argc, argv) void -do_touch(argc, argv) - int argc; - const char ** argv; +do_touch(int argc, const char ** argv) { const char * name; int fd; @@ -327,12 +325,14 @@ do_touch(argc, argv) time(&now.actime); now.modtime = now.actime; - while (argc-- > 1) { + while (argc-- > 1) + { name = *(++argv); fd = open(name, O_CREAT | O_WRONLY | O_EXCL, 0666); - if (fd >= 0) { + if (fd >= 0) + { close(fd); continue; @@ -345,75 +345,78 @@ do_touch(argc, argv) void -do_mv(argc, argv) - int argc; - const char ** argv; +do_mv(int argc, const char ** argv) { - const char * srcname; - const char * destname; - const char * lastarg; - BOOL dirflag; + const char * srcName; + const char * destName; + const char * lastArg; + BOOL dirFlag; - lastarg = argv[argc - 1]; + lastArg = argv[argc - 1]; - dirflag = isadir(lastarg); + dirFlag = isDirectory(lastArg); - if ((argc > 3) && !dirflag) { - fprintf(stderr, "%s: not a directory\n", lastarg); + if ((argc > 3) && !dirFlag) + { + fprintf(stderr, "%s: not a directory\n", lastArg); return; } - while (!intflag && (argc-- > 2)) { - srcname = *(++argv); + while (!intFlag && (argc-- > 2)) + { + srcName = *(++argv); - if (access(srcname, 0) < 0) { - perror(srcname); + if (access(srcName, 0) < 0) + { + perror(srcName); continue; } - destname = lastarg; + destName = lastArg; - if (dirflag) - destname = buildname(destname, srcname); + if (dirFlag) + destName = buildName(destName, srcName); - if (rename(srcname, destname) >= 0) + if (rename(srcName, destName) >= 0) continue; - if (errno != EXDEV) { - perror(destname); + if (errno != EXDEV) + { + perror(destName); continue; } - if (!copyfile(srcname, destname, TRUE)) + if (!copyFile(srcName, destName, TRUE)) continue; - if (unlink(srcname) < 0) - perror(srcname); + if (unlink(srcName) < 0) + perror(srcName); } } void -do_ln(argc, argv) - int argc; - const char ** argv; +do_ln(int argc, const char ** argv) { - const char * srcname; - const char * destname; - const char * lastarg; - BOOL dirflag; + const char * srcName; + const char * destName; + const char * lastArg; + BOOL dirFlag; - if (argv[1][0] == '-') { - if (strcmp(argv[1], "-s")) { + if (argv[1][0] == '-') + { + if (strcmp(argv[1], "-s")) + { fprintf(stderr, "Unknown option\n"); return; } - if (argc != 4) { + if (argc != 4) + { fprintf(stderr, "Wrong number of arguments for symbolic link\n"); return; @@ -431,31 +434,35 @@ do_ln(argc, argv) /* * Here for normal hard links. */ - lastarg = argv[argc - 1]; - dirflag = isadir(lastarg); + lastArg = argv[argc - 1]; + dirFlag = isDirectory(lastArg); - if ((argc > 3) && !dirflag) { - fprintf(stderr, "%s: not a directory\n", lastarg); + if ((argc > 3) && !dirFlag) + { + fprintf(stderr, "%s: not a directory\n", lastArg); return; } - while (argc-- > 2) { - srcname = *(++argv); + while (argc-- > 2) + { + srcName = *(++argv); - if (access(srcname, 0) < 0) { - perror(srcname); + if (access(srcName, 0) < 0) + { + perror(srcName); continue; } - destname = lastarg; + destName = lastArg; - if (dirflag) - destname = buildname(destname, srcname); + if (dirFlag) + destName = buildName(destName, srcName); - if (link(srcname, destname) < 0) { - perror(destname); + if (link(srcName, destName) < 0) + { + perror(destName); continue; } @@ -464,41 +471,39 @@ do_ln(argc, argv) void -do_cp(argc, argv) - int argc; - const char ** argv; +do_cp(int argc, const char ** argv) { - const char * srcname; - const char * destname; - const char * lastarg; - BOOL dirflag; + const char * srcName; + const char * destName; + const char * lastArg; + BOOL dirFlag; - lastarg = argv[argc - 1]; + lastArg = argv[argc - 1]; - dirflag = isadir(lastarg); + dirFlag = isDirectory(lastArg); - if ((argc > 3) && !dirflag) { - fprintf(stderr, "%s: not a directory\n", lastarg); + if ((argc > 3) && !dirFlag) + { + fprintf(stderr, "%s: not a directory\n", lastArg); return; } - while (!intflag && (argc-- > 2)) { - srcname = *argv++; - destname = lastarg; + while (!intFlag && (argc-- > 2)) + { + srcName = *(++argv); + destName = lastArg; - if (dirflag) - destname = buildname(destname, srcname); + if (dirFlag) + destName = buildName(destName, srcName); - (void) copyfile(srcname, destname, FALSE); + (void) copyFile(srcName, destName, FALSE); } } void -do_mount(argc, argv) - int argc; - const char ** argv; +do_mount(int argc, const char ** argv) { const char * str; const char * type; @@ -509,13 +514,16 @@ do_mount(argc, argv) type = "ext2"; flags = MS_MGC_VAL; - while ((argc > 0) && (**argv == '-')) { + while ((argc > 0) && (**argv == '-')) + { argc--; - str = *argv++ ; + str = *argv++; - while (*++str) switch (*str) { + while (*++str) switch (*str) + { case 't': - if ((argc <= 0) || (**argv == '-')) { + if ((argc <= 0) || (**argv == '-')) + { fprintf(stderr, "Missing file system type\n"); return; @@ -540,7 +548,8 @@ do_mount(argc, argv) } } - if (argc != 2) { + if (argc != 2) + { fprintf(stderr, "Wrong number of arguments for mount\n"); return; @@ -552,9 +561,7 @@ do_mount(argc, argv) void -do_umount(argc, argv) - int argc; - const char ** argv; +do_umount(int argc, const char ** argv) { if (umount(argv[1]) < 0) perror(argv[1]); @@ -562,9 +569,7 @@ do_umount(argc, argv) void -do_cmp(argc, argv) - int argc; - const char ** argv; +do_cmp(int argc, const char ** argv) { int fd1; int fd2; @@ -573,32 +578,35 @@ do_cmp(argc, argv) long pos; const char * bp1; const char * bp2; - char buf1[BUFSIZE]; - char buf2[BUFSIZE]; - struct stat statbuf1; - struct stat statbuf2; + char buf1[BUF_SIZE]; + char buf2[BUF_SIZE]; + struct stat statBuf1; + struct stat statBuf2; - if (stat(argv[1], &statbuf1) < 0) { + if (stat(argv[1], &statBuf1) < 0) + { perror(argv[1]); return; } - if (stat(argv[2], &statbuf2) < 0) { + if (stat(argv[2], &statBuf2) < 0) + { perror(argv[2]); return; } - if ((statbuf1.st_dev == statbuf2.st_dev) && - (statbuf1.st_ino == statbuf2.st_ino)) + if ((statBuf1.st_dev == statBuf2.st_dev) && + (statBuf1.st_ino == statBuf2.st_ino)) { printf("Files are links to each other\n"); return; } - if (statbuf1.st_size != statbuf2.st_size) { + if (statBuf1.st_size != statBuf2.st_size) + { printf("Files are different sizes\n"); return; @@ -606,7 +614,8 @@ do_cmp(argc, argv) fd1 = open(argv[1], O_RDONLY); - if (fd1 < 0) { + if (fd1 < 0) + { perror(argv[1]); return; @@ -614,7 +623,8 @@ do_cmp(argc, argv) fd2 = open(argv[2], O_RDONLY); - if (fd2 < 0) { + if (fd2 < 0) + { perror(argv[2]); close(fd1); @@ -623,40 +633,47 @@ do_cmp(argc, argv) pos = 0; - while (TRUE) { - if (intflag) + while (TRUE) + { + if (intFlag) goto closefiles; cc1 = read(fd1, buf1, sizeof(buf1)); - if (cc1 < 0) { + if (cc1 < 0) + { perror(argv[1]); goto closefiles; } cc2 = read(fd2, buf2, sizeof(buf2)); - if (cc2 < 0) { + if (cc2 < 0) + { perror(argv[2]); goto closefiles; } - if ((cc1 == 0) && (cc2 == 0)) { + if ((cc1 == 0) && (cc2 == 0)) + { printf("Files are identical\n"); goto closefiles; } - if (cc1 < cc2) { + if (cc1 < cc2) + { printf("First file is shorter than second\n"); goto closefiles; } - if (cc1 > cc2) { + if (cc1 > cc2) + { printf("Second file is shorter than first\n"); goto closefiles; } - if (memcmp(buf1, buf2, cc1) == 0) { + if (memcmp(buf1, buf2, cc1) == 0) + { pos += cc1; continue; @@ -664,6 +681,7 @@ do_cmp(argc, argv) bp1 = buf1; bp2 = buf2; + while (*bp1++ == *bp2++) pos++; @@ -679,9 +697,7 @@ closefiles: void -do_more(argc, argv) - int argc; - const char ** argv; +do_more(int argc, const char ** argv) { FILE * fp; const char * name; @@ -718,12 +734,14 @@ do_more(argc, argv) /* * OK, process each file. */ - while (argc-- > 1) { + while (argc-- > 1) + { name = *(++argv); fp = fopen(name, "r"); - if (fp == NULL) { + if (fp == NULL) + { perror(name); return; @@ -733,8 +751,10 @@ do_more(argc, argv) line = 1; col = 0; - while (fp && ((ch = fgetc(fp)) != EOF)) { - switch (ch) { + while (fp && ((ch = fgetc(fp)) != EOF)) + { + switch (ch) + { case '\r': col = 0; break; @@ -759,7 +779,8 @@ do_more(argc, argv) putchar(ch); - if (col >= pageColumns) { + if (col >= pageColumns) + { col -= pageColumns; line++; } @@ -773,7 +794,8 @@ do_more(argc, argv) printf("--More--"); fflush(stdout); - if (intflag || (read(0, buf, sizeof(buf)) < 0)) { + if (intFlag || (read(0, buf, sizeof(buf)) < 0)) + { if (fp) fclose(fp); @@ -785,7 +807,8 @@ do_more(argc, argv) if (ch == ':') ch = buf[1]; - switch (ch) { + switch (ch) + { case 'N': case 'n': fclose(fp); @@ -810,18 +833,81 @@ do_more(argc, argv) void -do_exit(argc, argv) - int argc; - const char ** argv; +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]; + + argc--; + argv++; + + while (argc-- > 0) + { + name = *argv++; + + fd = open(name, O_RDONLY); + + if (fd < 0) + { + perror(name); + + 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); + + (void) close(fd); + + continue; + } + + (void) close(fd); + + printf("%05lu %s\n", checksum, name); + } +} + + +void +do_exit(int argc, const char ** argv) +{ + if (getpid() == 1) + { + fprintf(stderr, "You are the INIT process!\n"); + + return; + } + exit(0); } void -do_setenv(argc, argv) - int argc; - const char ** argv; +do_setenv(int argc, const char ** argv) { const char * name; const char * value; @@ -852,9 +938,7 @@ do_setenv(argc, argv) void -do_printenv(argc, argv) - int argc; - const char ** argv; +do_printenv(int argc, const char ** argv) { const char ** env; extern char ** environ; @@ -862,7 +946,8 @@ do_printenv(argc, argv) env = (const char **) environ; - if (argc == 1) { + if (argc == 1) + { while (*env) printf("%s\n", *env++); @@ -871,7 +956,8 @@ do_printenv(argc, argv) len = strlen(argv[1]); - while (*env) { + while (*env) + { if ((strlen(*env) > len) && (env[0][len] == '=') && (memcmp(argv[1], *env, len) == 0)) { @@ -885,14 +971,13 @@ do_printenv(argc, argv) void -do_umask(argc, argv) - int argc; - const char ** argv; +do_umask(int argc, const char ** argv) { const char * cp; int mask; - if (argc <= 1) { + if (argc <= 1) + { mask = umask(0); umask(mask); printf("%03o\n", mask); @@ -903,10 +988,11 @@ do_umask(argc, argv) mask = 0; cp = argv[1]; - while (isoctal(*cp)) + while (isOctal(*cp)) mask = mask * 8 + *cp++ - '0'; - if (*cp || (mask & ~0777)) { + if (*cp || (mask & ~0777)) + { fprintf(stderr, "Bad umask value\n"); return; @@ -917,9 +1003,7 @@ do_umask(argc, argv) void -do_kill(argc, argv) - int argc; - const char ** argv; +do_kill(int argc, const char ** argv) { const char * cp; int sig; @@ -927,7 +1011,8 @@ do_kill(argc, argv) sig = SIGTERM; - if (argv[1][0] == '-') { + if (argv[1][0] == '-') + { cp = &argv[1][1]; if (strcmp(cp, "HUP") == 0) @@ -938,13 +1023,25 @@ do_kill(argc, argv) sig = SIGQUIT; else if (strcmp(cp, "KILL") == 0) sig = SIGKILL; - else { + 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)) + while (isDecimal(*cp)) sig = sig * 10 + *cp++ - '0'; - if (*cp) { + if (*cp) + { fprintf(stderr, "Unknown signal\n"); return; @@ -955,14 +1052,16 @@ do_kill(argc, argv) argv++; } - while (argc-- > 1) { - cp = *++argv; + while (argc-- > 1) + { + cp = *(++argv); pid = 0; - while (isdecimal(*cp)) + while (isDecimal(*cp)) pid = pid * 10 + *cp++ - '0'; - if (*cp) { + if (*cp) + { fprintf(stderr, "Non-numeric pid\n"); return; @@ -975,9 +1074,7 @@ do_kill(argc, argv) void -do_where(argc, argv) - int argc; - const char ** argv; +do_where(int argc, const char ** argv) { const char * program; const char * dirName; @@ -998,7 +1095,7 @@ do_where(argc, argv) path = getenv("PATH"); - fullPath = getchunk(strlen(path) + strlen(program) + 2); + fullPath = getChunk(strlen(path) + strlen(program) + 2); path = chunkstrdup(path); if ((path == NULL) || (fullPath == NULL)) diff --git a/sash.1 b/sash.1 index 68214c4..23e8587 100644 --- a/sash.1 +++ b/sash.1 @@ -2,32 +2,31 @@ .SH NAME sash \- stand-alone shell with built-in commands .SH SYNOPSYS -.B sash +.B sash [-c command] [-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. -You can also use -.B sash -to safely upgrade to new versions of the shared libraries. +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 meta-characters in the command. +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 +More importantly, however, +is that many of the standard system commands are built-in to .BR sash . These built-in commands are: .PP .nf - -chgrp, -chmod, -chown, -cmp, -cp, -dd, -echo, - -ed, -grep, -gunzip, -gzip, -kill, -ln, -ls, -mkdir, - -mknod, -more, -mount, -mv, -printenv, -pwd, -rm, - -rmdir, -sync, -tar, -touch, -umount, -where + -ar, -chattr, -chgrp, -chmod, -chown, -cmp, -cp, + -dd, -echo, -ed, -grep, -file, -find, -gunzip, + -gzip, -kill, -ln, -ls, -lsattr, -mkdir, -mknod, + -more, -mount, -mv, -printenv, -pwd, -rm, -rmdir, + -sum, -sync, -tar, -touch, -umount, -where .fi .PP These commands are generally similar to the standard programs with similar @@ -44,11 +43,12 @@ If "-ls" is typed, then the built-in command which mimics .B ls is called. .PP -For the built-in commands, filenames are expanded so that asterisks, +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. However, no other command line processing is performed. -This includes quoting of arguments, specifying of file redirection, and -the specifying of a pipeline. +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 @@ -56,6 +56,8 @@ 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 . @@ -69,56 +71,86 @@ If .I name and .I command -are provided, this defines an alias for a command -with the specified name, which executes the specified command, with -possible arguments. If just +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. When defining an -alias, wildcards are not expanded. -.TP -.B cd [dirname] +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 +.I dirName is provided, then the current directory is changed to the -dirname. If -.I dirname +dirName. If +.I dirName is absent, then the current directory is changed to the user's home directory (value of the $HOME environment variable). .TP -.B -chgrp gid filename ... +.B -chattr [+i] [-i] [+a] [-a] fileName ... +Change the attributes of the specified files on the ext2 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. +.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 ... +.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 ... +.B -chown uid fileName ... Change the owner id for the specified list of files. The .I uid can either be a user name, or a decimal value. .TP -.B -cmp filename1 filename2 -Determines whether or not the specified filenames have identical data. +.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 +.B -cp srcName ... destName Copies one or more files from the -.I srcname +.I srcName to the -.IR destname . +.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. +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. @@ -146,11 +178,11 @@ respectively. The command reports the number of full blocks read and written, and whether or not any partial block was read or written. .TP .B -echo [args] ... -Echo the arguments to the -echo command. Wildcards are expanded, so -this is convenient to get a quick list of filenames in a directory. +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] +.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. @@ -161,7 +193,7 @@ 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] +.B exec fileName [args] Execute the specified program with the specified arguments. This replaces .B sash @@ -171,10 +203,29 @@ completely by the executed program. Quit from .BR sash . .TP -.B -grep [-in] word filename ... +.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 filename is given, then only the matching lines are -printed. If multiple filenames are given, then the filenames are +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, @@ -182,7 +233,7 @@ must be a single word, 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] +.B -gunzip inputFileName ... [-o outputPath] Uncompress one or more files that had been compressed using the .I gzip or @@ -202,16 +253,18 @@ Otherwise, the ".gz" or ".Z" extension is removed. 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 . +.IR outputPath . If the output path is a directory, then the uncompressed versions of the input files will be placed in that directory with their file names modified as described above, or with the same name if the input file name does not have one of the special extensions. -If the output path is not a directory, then only one input file is allowed, +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. .TP -.B -gzip inputfilename ... [-o outputpath] +.B -gzip inputFileName ... [-o outputPath] Compresses one or more files using the .I gzip algorithm. @@ -228,7 +281,7 @@ Otherwise, the ".gz" extension is added. 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 . +.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. @@ -236,40 +289,55 @@ If the output path is not a directory, then only one input file is allowed, and the compressed version of that input file is created as the output path exactly as specified. .TP -.B help -Displays a list of built-in commands. +.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 numberic value, or one of the special values HUP, INT, -QUIT, or KILL. +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 -ln [-s] srcname ... destname +.B -ln [-s] srcName ... destName Links one or more files from the -.I srcname +.I srcName to the specified -.IR destname . +.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. +multiple srcNames, or destName is a directory, then the link is +put in the destName directory with the same name as the source name. The default links are hard links. Using -s makes symbolic links. -For symbolic links, only one srcname can be specified. -.TP -.B -ls [-lid] filename ... -Display information about the specified filesnames, which may be -directories. The normal listing is simply a list of filenames, -one per line. The options available are -l, -i, and -d. The -l -option produces a long listing given the normal 'ls' information. -The -i option also displays the inode numbers of the files. The --d option displays information about a directory, instead of the +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, -i, -d, and -F. +The -l option produces a long listing giving the normal 'ls' information. +The -i option displays the inode numbers of the files. +The -d option displays information about a directory, instead of the files within it. -.TP -.B -mkdir dirname ... +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 the ext2 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. +.TP +.B -mkdir dirName ... Creates the specified directories. They are created with the default permissions. .TP -.B -mknod filename type major minor +.B -mknod fileName type major minor Creates a special device node, either a character file or a block file. .I Filename @@ -282,29 +350,29 @@ is the major device number. is the minor device number. Both of these numbers are decimal. .TP -.B -more filename ... -Type out the contents of the specified filenames, one page at a +.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] [-m] devname dirname +.B -mount [-t type] [-r] [-m] devName dirName Mount a filesystem on a directory name. The -t option specifies the type of filesystem being mounted, and defaults to "ext2". The -r option indicates to mount the filesystem read-only. The -m option indicates to remount an already mounted filesystem. .TP -.B -mv srcname ... destname +.B -mv srcName ... destName Moves one or more files from the -.I srcname +.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 +.IR destName . +If multiple srcNames are given, or if destName is a directory, then +the srcNames are copied into the destination directory with the +same names as the srcNames. Renames are attempted first, but if this fails because of the files being on different filesystems, -then a copies and deletes are done instead. +then copies and deletes are done instead. .TP .B -printenv [name] If @@ -325,32 +393,41 @@ Prints the current working directory. Exits from .BR sash . .TP -.B -rm filename ... +.B -rm fileName ... Removes one or more files. .TP -.B -rmdir dirname ... +.B -rmdir dirName ... Removes one or more directories. The directories must be empty for this to be successful. .TP .B setenv name value Set the value of an environment variable. .TP -.B source filename -Execute commands which are contained in the specified filename. +.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 [xtv]f devname [filename] ... -List or restore files from a tar archive. This command can only -read tar files, not create them. The available options are xtvf. -The f option must be specified, and accepts a device or file -name argument which contains the tar archive. If no filename is -given, all files in the archive are listed or extracted. Otherwise, -only those files starting with the specified filenames are done. -Leading slashes in the tar archive filenames are removed. -.TP -.B -touch filename ... +.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 @@ -363,8 +440,8 @@ permissions of newly created files. If is not given, then the current umask value is printed. The mask is an octal value. .TP -.B -umount filename -Unmounts a file system. The filename can either be the device name +.B -umount 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. .TP @@ -388,14 +465,58 @@ The -q option makes .B sash quiet, which simply means that it doesn't print its introduction line when it starts. +.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. +.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 it's purpose is lost. -.PP -The system is still vulnerable to unrunnable shared versions of -.B init -and -.B sh. +should obviously be linked statically, otherwise its purpose is lost. .PP Several other system commands might be necessary for system recovery, but aren't built-in to @@ -404,5 +525,5 @@ but aren't built-in to .nf David I. Bell dbell@canb.auug.org.au -March 8, 1998 +2 October 1999 .fi diff --git a/sash.c b/sash.c index 51cdb91..3cde006 100644 --- a/sash.c +++ b/sash.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998 by David I. Bell + * Copyright (c) 1999 by David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. * @@ -7,202 +7,390 @@ * This program should NOT be built using shared libraries. */ -#include +#include +#include #include #include #include "sash.h" -static const char * const version = "2.1"; +static const char * const version = "3.4"; -typedef struct { - char * name; - char * usage; - void (*func) PROTO((int argc, const char ** argv)); - int minargs; - int maxargs; -} CMDTAB; +/* + * 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 -static const CMDTAB cmdtab[] = +/* + * One entry of the command table. + */ +typedef struct { - {"alias", "[name [command]]", do_alias, - 1, MAXARGS}, + const char * name; + void (*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", "[dirname]", do_cd, - 1, 2}, + { + "cd", do_cd, 1, 2, + "Change current directory", + "[dirName]" + }, - {"-chgrp", "gid filename ...", do_chgrp, - 3, MAXARGS}, +#ifdef HAVE_EXT2 + { + "-chattr", do_chattr, 3, INFINITE_ARGS, + "Change ext2 file attributes", + "[+i] [-i] [+a] [-a] fileName ..." + }, +#endif - {"-chmod", "mode filename ...", do_chmod, - 3, MAXARGS}, + { + "-chgrp", do_chgrp, 3, INFINITE_ARGS, + "Change the group id of some files", + "gid fileName ..." + }, - {"-chown", "uid filename ...", do_chown, - 3, MAXARGS}, + { + "-chmod", do_chmod, 3, INFINITE_ARGS, + "Change the protection of some files", + "mode fileName ..." + }, - {"-cmp", "filename1 filename2", do_cmp, - 3, 3}, + { + "-chown", do_chown, 3, INFINITE_ARGS, + "Change the owner id of some files", + "uid fileName ..." + }, - {"-cp", "srcname ... destname", do_cp, - 3, MAXARGS}, + { + "-cmp", do_cmp, 3, 3, + "Compare two files for equality", + "fileName1 fileName2" + }, - {"-dd", "if=name of=name [bs=n] [count=n] [skip=n] [seek=n]", do_dd, - 3, MAXARGS}, + { + "-cp", do_cp, 3, INFINITE_ARGS, + "Copy files", + "srcName ... destName" + }, + + { + "-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]" + }, - {"-echo", "[args] ...", do_echo, - 1, MAXARGS}, + { + "exec", do_exec, 2, INFINITE_ARGS, + "Execute another program in place of this sash process", + "fileName [args]" + }, - {"-ed", "[filename]", do_ed, - 1, 2}, + { + "exit", do_exit, 1, 1, + "Exit from sash", + "" + }, - {"exec", "filename [args]", do_exec, - 2, MAXARGS}, + { + "-file", do_file, 1, INFINITE_ARGS, + "Describe information about files", + "fileName ..." + }, - {"exit", "", do_exit, - 1, 1}, + { + "-find", do_find, 2, INFINITE_ARGS, + "Find files in a directory tree meeting some conditions", + "dirName [-xdev] [-type chars] [-name pattern] [-size minSize]" + }, - {"-grep", "[-in] word filename ...", do_grep, - 3, MAXARGS}, + { + "-grep", do_grep, 3, INFINITE_ARGS, + "Look for lines containing a word in some files", + "[-in] word fileName ..." + }, #ifdef HAVE_GZIP - {"-gunzip", "filename ... [-o outputPath]", do_gunzip, - 2, MAXARGS}, + { + "-gunzip", do_gunzip, 2, INFINITE_ARGS, + "Uncompress files which were saved in GZIP or compress format", + "fileName ... [-o outputPath]" + }, - {"-gzip", "filename ... [-o outputPath]", do_gzip, - 2, MAXARGS}, + { + "-gzip", do_gzip, 2, INFINITE_ARGS, + "Compress files into GZIP format", + "fileName ... [-o outputPath]" + }, #endif - {"help", "[word]", do_help, - 1, 2}, + { + "help", do_help, 1, 2, + "Print help about a command", + "[word]" + }, - {"-kill", "[-sig] pid ...", do_kill, - 2, MAXARGS}, + { + "-kill", do_kill, 2, INFINITE_ARGS, + "Send a signal to the specified process", + "[-sig] pid ..." + }, + + { + "-ln", do_ln, 3, INFINITE_ARGS, + "Link one fileName to another", + "[-s] srcName ... destName" + }, - {"-ln", "[-s] srcname ... destname", do_ln, - 3, MAXARGS}, + { + "-ls", do_ls, 1, INFINITE_ARGS, + "List information about files or directories", + "[-lidFC] fileName ..." + }, + +#ifdef HAVE_EXT2 + { + "-lsattr", do_lsattr, 2, INFINITE_ARGS, + "List ext2 file attributes", + "fileName ..." + }, +#endif - {"-ls", "[-lid] filename ...", do_ls, - 1, MAXARGS}, + { + "-mkdir", do_mkdir, 2, INFINITE_ARGS, + "Create a directory", + "dirName ..." + }, - {"-mkdir", "dirname ...", do_mkdir, - 2, MAXARGS}, + { + "-mknod", do_mknod, 5, 5, + "Create a special type of file", + "fileName type major minor" + }, - {"-mknod", "filename type major minor", do_mknod, - 5, 5}, + { + "-more", do_more, 2, INFINITE_ARGS, + "Type file contents page by page", + "fileName ..." + }, - {"-more", "filename ...", do_more, - 2, MAXARGS}, + { + "-mount", do_mount, 3, INFINITE_ARGS, + "Mount or remount a filesystem on a directory", + "[-t type] [-r] [-m] devName dirName" + }, - {"-mount", "[-t type] [-r] [-m] devname dirname", do_mount, - 3, MAXARGS}, + { + "-mv", do_mv, 3, INFINITE_ARGS, + "Move or rename files", + "srcName ... destName" + }, - {"-mv", "srcname ... destname", do_mv, - 3, MAXARGS}, + { + "-printenv", do_printenv, 1, 2, + "Print environment variables", + "[name]" + }, - {"-printenv", "[name]", do_printenv, - 1, 2}, + { + "prompt", do_prompt, 2, INFINITE_ARGS, + "Set the prompt string for sash", + "string" + }, - {"prompt", "string", do_prompt, - 2, MAXARGS}, + { + "-pwd", do_pwd, 1, 1, + "Print the current working directory", + "" + }, - {"-pwd", "", do_pwd, - 1, 1}, + { + "quit", do_exit, 1, 1, + "Exit from sash", + "" + }, - {"quit", "", do_exit, - 1, 1}, + { + "-rm", do_rm, 2, INFINITE_ARGS, + "Remove the specified files", + "fileName ..." + }, - {"-rm", "filename ...", do_rm, - 2, MAXARGS}, + { + "-rmdir", do_rmdir, 2, INFINITE_ARGS, + "Remove the specified empty directories", + "dirName ..." + }, - {"-rmdir", "dirname ...", do_rmdir, - 2, MAXARGS}, + { + "setenv", do_setenv, 3, 3, + "Set an environment variable value", + "name value" + }, - {"setenv", "name value", do_setenv, - 3, 3}, + { + "source", do_source, 2, 2, + "Read commands from the specified file", + "fileName" + }, - {"source", "filename", do_source, - 2, 2}, + { + "-sum", do_sum, 2, INFINITE_ARGS, + "Calculate checksums of the specified files", + "fileName ..." + }, - {"-sync", "", do_sync, - 1, 1}, + { + "-sync", do_sync, 1, 1, + "Sync the disks to force cached data to them", + "" + }, - {"-tar", "[xtv]f devname filename ...", do_tar, - 2, MAXARGS}, + { + "-tar", do_tar, 2, INFINITE_ARGS, + "Create, extract, or list files from a TAR file", + "[cxtv]f tarFileName fileName ..." + }, - {"-touch", "filename ...", do_touch, - 2, MAXARGS}, + { + "-touch", do_touch, 2, INFINITE_ARGS, + "Update times or create the specified files", + "fileName ..." + }, - {"umask", "[mask]", do_umask, - 1, 2}, + { + "umask", do_umask, 1, 2, + "Set the umask value for file protections", + "[mask]" + }, - {"-umount", "filename", do_umount, - 2, 2}, + { + "-umount", do_umount, 2, 2, + "Unmount a filesystem", + "fileName" + }, - {"unalias", "name", do_unalias, - 2, 2}, + { + "unalias", do_unalias, 2, 2, + "Remove a command alias", + "name" + }, - {"-where", "program", do_where, - 2, 2}, + { + "-where", do_where, 2, 2, + "Type the location of a program", + "program" + }, - {NULL, 0, 0, - 0, 0} + { + NULL, 0, 0, 0, + NULL, + NULL + } }; -typedef struct { +/* + * The definition of an command alias. + */ +typedef struct +{ char * name; char * value; -} ALIAS; +} Alias; /* * Local data. */ -static ALIAS * aliastable; -static int aliascount; +static Alias * aliasTable; +static int aliasCount; -static FILE * sourcefiles[MAXSOURCE]; -static int sourcecount; +static FILE * sourcefiles[MAX_SOURCE]; +static int sourceCount; -static BOOL intcrlf = TRUE; +static BOOL intCrlf = TRUE; static char * prompt; /* * Local procedures. */ -static void catchint PROTO((int)); -static void catchquit PROTO((int)); -static void readfile PROTO((const char * name)); -static void command PROTO((const char * cmd)); -static void runcmd PROTO((const char * cmd, int argc, const char ** argv)); -static void showprompt PROTO((void)); -static void usage PROTO((void)); -static BOOL trybuiltin PROTO((int argc, const char ** argv)); -static ALIAS * findalias PROTO((const char * name)); +static void catchInt(int); +static void catchQuit(int); +static void readFile(const char * name); +static void command(const char * cmd); +static BOOL tryBuiltIn(const char * cmd); +static void runCmd(const char * cmd); +static void childProcess(const char * cmd); +static void showPrompt(void); +static void usage(void); +static Alias * findAlias(const char * name); /* * Global interrupt flag. */ -BOOL intflag; +BOOL intFlag; int -main(argc, argv) - int argc; - const char ** argv; +main(int argc, const char ** argv) { const char * cp; const char * singleCommand; BOOL quietFlag; - char buf[PATHLEN]; + BOOL aliasFlag; + char buf[PATH_LEN]; singleCommand = NULL; quietFlag = FALSE; + aliasFlag = FALSE; /* * Look for options. @@ -248,6 +436,10 @@ main(argc, argv) quietFlag = TRUE; break; + case 'a': + aliasFlag = TRUE; + break; + case 'h': case '?': usage(); @@ -272,6 +464,12 @@ main(argc, argv) 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. */ @@ -286,26 +484,35 @@ main(argc, argv) * Print a hello message unless we are told to be silent. */ if (!quietFlag && isatty(STDIN)) + { printf("Stand-alone shell (version %s)\n", version); - signal(SIGINT, catchint); - signal(SIGQUIT, catchquit); + if (aliasFlag) + printf("Built-in commands are aliased to standard commands\n"); + } - if (getenv("PATH") == NULL) - putenv("PATH=/bin:/usr/bin:/sbin:/usr/sbin:/etc"); + signal(SIGINT, catchInt); + signal(SIGQUIT, catchQuit); + /* + * Execute the user's alias file if present. + */ cp = getenv("HOME"); - if (cp) { + if (cp) + { strcpy(buf, cp); strcat(buf, "/"); strcat(buf, ".aliasrc"); if ((access(buf, 0) == 0) || (errno != ENOENT)) - readfile(buf); + readFile(buf); } - readfile(NULL); + /* + * Read commands from stdin. + */ + readFile(NULL); return 0; } @@ -316,15 +523,15 @@ main(argc, argv) * A null name pointer indicates to read from stdin. */ static void -readfile(name) - const char * name; +readFile(const char * name) { FILE * fp; int cc; - BOOL ttyflag; - char buf[CMDLEN]; + BOOL ttyFlag; + char buf[CMD_LEN]; - if (sourcecount >= MAXSOURCE) { + if (sourceCount >= MAX_SOURCE) + { fprintf(stderr, "Too many source files\n"); return; @@ -332,33 +539,39 @@ readfile(name) fp = stdin; - if (name) { + if (name) + { fp = fopen(name, "r"); - if (fp == NULL) { + if (fp == NULL) + { perror(name); return; } } - sourcefiles[sourcecount++] = fp; + sourcefiles[sourceCount++] = fp; - ttyflag = isatty(fileno(fp)); + ttyFlag = isatty(fileno(fp)); - while (TRUE) { - if (ttyflag) - showprompt(); + while (TRUE) + { + if (ttyFlag) + showPrompt(); - if (intflag && !ttyflag && (fp != stdin)) { + if (intFlag && !ttyFlag && (fp != stdin)) + { fclose(fp); - sourcecount--; + sourceCount--; return; } - if (fgets(buf, CMDLEN - 1, fp) == NULL) { - if (ferror(fp) && (errno == EINTR)) { + if (fgets(buf, CMD_LEN - 1, fp) == NULL) + { + if (ferror(fp) && (errno == EINTR)) + { clearerr(fp); continue; @@ -372,7 +585,7 @@ readfile(name) if (buf[cc - 1] == '\n') cc--; - while ((cc > 0) && isblank(buf[cc - 1])) + while ((cc > 0) && isBlank(buf[cc - 1])) cc--; buf[cc] = '\0'; @@ -380,7 +593,8 @@ readfile(name) command(buf); } - if (ferror(fp)) { + if (ferror(fp)) + { perror("Reading command line"); if (fp == stdin) @@ -392,7 +606,7 @@ readfile(name) if (fp != stdin) fclose(fp); - sourcecount--; + sourceCount--; } @@ -402,60 +616,74 @@ readfile(name) * command is an alias, and expands wildcards. */ static void -command(cmd) - const char * cmd; +command(const char * cmd) { - char * cp; - const ALIAS * alias; - const char ** argv; - int argc; - char buf[CMDLEN]; + const char * endCmd; + const Alias * alias; + char newCommand[CMD_LEN]; + char cmdName[CMD_LEN]; - intflag = FALSE; + /* + * Rest the interrupt flag and free any memory chunks that + * were allocated by the previous command. + */ + intFlag = FALSE; - freechunks(); + freeChunks(); - while (isblank(*cmd)) + /* + * Skip leading blanks. + */ + while (isBlank(*cmd)) cmd++; - if ((*cmd == '\0') || (*cmd == '#') || !makeargs(cmd, &argc, &argv)) + /* + * If the command is empty or is a comment then ignore it. + */ + if ((*cmd == '\0') || (*cmd == '#')) return; /* - * Search for the command in the alias table. - * If it is found, then replace the command name with - * the alias, and append any other arguments to it. + * Look for the end of the command name and then copy the + * command name to a buffer so we can null terminate it. */ - alias = findalias(argv[0]); + endCmd = cmd; - if (alias) { - cp = buf; - strcpy(cp, alias->value); - cp += strlen(cp); + while (*endCmd && !isBlank(*endCmd)) + endCmd++; - while (--argc > 0) { - *cp++ = ' '; - strcat(cp, *++argv); - cp += strlen(cp); - } + memcpy(cmdName, cmd, endCmd - cmd); - cmd = buf; + cmdName[endCmd - cmd] = '\0'; - if (!makeargs(cmd, &argc, &argv)) - return; + /* + * 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; } /* * Now look for the command in the builtin table, and execute * the command if found. */ - if (trybuiltin(argc, argv)) + if (tryBuiltIn(cmd)) return; /* - * Not found, run the program along the PATH list. + * The command is not a built-in, so run the program along + * the PATH list. */ - runcmd(cmd, argc, argv); + runCmd(cmd); } @@ -465,114 +693,105 @@ command(cmd) * command succeeds. Returns FALSE if this is not a built-in command. */ static BOOL -trybuiltin(argc, argv) - int argc; - const char ** argv; +tryBuiltIn(const char * cmd) { - const CMDTAB * cmdptr; - int oac; - int newargc; - int matches; - int i; - const char * newargv[MAXARGS]; - const char * nametable[MAXARGS]; + const char * endCmd; + const CommandEntry * entry; + int argc; + const char ** argv; + char cmdName[CMD_LEN]; - cmdptr = cmdtab - 1; + /* + * 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; - do { - cmdptr++; + while (*endCmd && !isBlank(*endCmd)) + endCmd++; - if (cmdptr->name == NULL) - return FALSE; + memcpy(cmdName, cmd, endCmd - cmd); - } while (strcmp(argv[0], cmdptr->name)); + cmdName[endCmd - cmd] = '\0'; /* - * Give a usage string if the number of arguments is too large - * or too small. + * Search the command table looking for the command name. */ - if ((argc < cmdptr->minargs) || (argc > cmdptr->maxargs)) { - fprintf(stderr, "usage: %s %s\n", - cmdptr->name, cmdptr->usage); - - return TRUE; + for (entry = commandEntryTable; entry->name != NULL; entry++) + { + if (strcmp(entry->name, cmdName) == 0) + break; } /* - * Check here for several special commands which do not - * have wildcarding done for them. + * If the command is not a built-in, return indicating that. */ - if ((cmdptr->func == do_alias) || (cmdptr->func == do_prompt)) { - (*cmdptr->func)(argc, argv); + 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; - } /* - * Now for each command argument, see if it is a wildcard, and if - * so, replace the argument with the list of matching filenames. + * Give a usage string if the number of arguments is too large + * or too small. */ - newargv[0] = argv[0]; - newargc = 1; - oac = 0; - - while (++oac < argc) { - matches = expandwildcards(argv[oac], MAXARGS, nametable); - - if (matches < 0) - return TRUE; - - if ((newargc + matches) >= MAXARGS) { - fprintf(stderr, "Too many arguments\n"); - - return TRUE; - } - - if (matches == 0) - newargv[newargc++] = argv[oac]; + if ((argc < entry->minArgs) || (argc > entry->maxArgs)) + { + fprintf(stderr, "usage: %s %s\n", entry->name, entry->usage); - for (i = 0; i < matches; i++) - newargv[newargc++] = nametable[i]; + return TRUE; } - (*cmdptr->func)(newargc, newargv); + /* + * Call the built-in function with the argument list. + */ + entry->func(argc, argv); return TRUE; } /* - * Execute the specified command. + * Execute the specified command either by forking and executing + * the program ourself, or else by using the shell. */ static void -runcmd(cmd, argc, argv) - const char * cmd; - int argc; - const char ** argv; +runCmd(const char * cmd) { const char * cp; BOOL magic; - int pid; + pid_t pid; int status; + /* + * Check the command for any magic shell characters + * except for quoting. + */ magic = FALSE; - for (cp = cmd; *cp; cp++) { + for (cp = cmd; *cp; cp++) + { if ((*cp >= 'a') && (*cp <= 'z')) continue; if ((*cp >= 'A') && (*cp <= 'Z')) continue; - if (isdecimal(*cp)) + if (isDecimal(*cp)) continue; - if (isblank(*cp)) + if (isBlank(*cp)) continue; if ((*cp == '.') || (*cp == '/') || (*cp == '-') || (*cp == '+') || (*cp == '=') || (*cp == '_') || - (*cp == ':') || (*cp == ',')) + (*cp == ':') || (*cp == ',') || (*cp == '\'') || + (*cp == '"')) { continue; } @@ -580,100 +799,175 @@ runcmd(cmd, argc, argv) magic = TRUE; } - if (magic) { + /* + * If there were any magic characters used then run the + * command using the shell. + */ + if (magic) + { system(cmd); return; } /* - * No magic characters in the command, so do the fork and - * exec ourself. If this fails with ENOEXEC, then run the - * shell anyway since it might be a shell script. + * No magic characters were in the command, so we can do the fork + * and exec ourself. */ pid = fork(); - if (pid < 0) { + if (pid < 0) + { perror("fork failed"); return; } - if (pid) { - status = 0; - intcrlf = FALSE; + /* + * If we are the child process, then go execute the program. + */ + if (pid == 0) + childProcess(cmd); - while (((pid = wait(&status)) < 0) && (errno == EINTR)) - ; + /* + * We are the parent process. + * Wait for the child to complete. + */ + status = 0; + intCrlf = FALSE; - intcrlf = TRUE; + while (((pid = waitpid(pid, &status, 0)) < 0) && (errno == EINTR)) + ; - if ((status & 0xff) == 0) - return; + intCrlf = TRUE; - fprintf(stderr, "pid %d: %s (signal %d)\n", pid, - (status & 0x80) ? "core dumped" : "killed", - status & 0x7f); + if (pid < 0) + { + fprintf(stderr, "Error from waitpid: %s", strerror(errno)); return; } + if (WIFSIGNALED(status)) + { + fprintf(stderr, "pid %ld: killed by signal %d\n", + (long) pid, WTERMSIG(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; + /* - * We are the child, so run the program. - * First close any extra file descriptors we have opened. + * Close any extra file descriptors we have opened. */ - while (--sourcecount >= 0) { - if (sourcefiles[sourcecount] != stdin) - fclose(sourcefiles[sourcecount]); + 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)) + { + system(cmd); + exit(0); + } + + /* + * Try to execute the program directly. + */ execvp(argv[0], (char **) argv); - if (errno == ENOEXEC) { + /* + * The exec failed, so try to run the command using the shell + * in case it is a shell script. + */ + if (errno == ENOEXEC) + { system(cmd); exit(0); } + /* + * There was something else wrong, complain and exit. + */ perror(argv[0]); exit(1); } void -do_help(argc, argv) - int argc; - const char ** argv; +do_help(int argc, const char ** argv) { - const CMDTAB * cmdptr; - const char * str; + const CommandEntry * entry; + const char * str; str = NULL; if (argc == 2) str = argv[1]; - for (cmdptr = cmdtab; cmdptr->name; cmdptr++) { - if ((str == NULL) || (strstr(cmdptr->name, str) != NULL)) - printf("%-10s %s\n", cmdptr->name, cmdptr->usage); + /* + * 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; + } + } + } + + /* + * 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); + } } } void -do_alias(argc, argv) - int argc; - const char ** argv; +do_alias(int argc, const char ** argv) { const char * name; - char * value; - ALIAS * alias; - int count; - char buf[CMDLEN]; + char * value; + Alias * alias; + int count; + char buf[CMD_LEN]; - if (argc < 2) { - count = aliascount; + if (argc < 2) + { + count = aliasCount; - for (alias = aliastable; count-- > 0; alias++) + for (alias = aliasTable; count-- > 0; alias++) printf("%s\t%s\n", alias->name, alias->value); return; @@ -681,8 +975,9 @@ do_alias(argc, argv) name = argv[1]; - if (argc == 2) { - alias = findalias(name); + if (argc == 2) + { + alias = findAlias(name); if (alias) printf("%s\n", alias->value); @@ -692,18 +987,20 @@ do_alias(argc, argv) return; } - if (strcmp(name, "alias") == 0) { + if (strcmp(name, "alias") == 0) + { fprintf(stderr, "Cannot alias \"alias\"\n"); return; } - if (!makestring(argc - 2, argv + 2, buf, CMDLEN)) + if (!makeString(argc - 2, argv + 2, buf, CMD_LEN)) return; value = malloc(strlen(buf) + 1); - if (value == NULL) { + if (value == NULL) + { fprintf(stderr, "No memory for alias value\n"); return; @@ -711,39 +1008,45 @@ do_alias(argc, argv) strcpy(value, buf); - alias = findalias(name); + alias = findAlias(name); - if (alias) { + if (alias) + { free(alias->value); alias->value = value; return; } - if ((aliascount % ALIASALLOC) == 0) { - count = aliascount + ALIASALLOC; + if ((aliasCount % ALIAS_ALLOC) == 0) + { + count = aliasCount + ALIAS_ALLOC; - if (aliastable) - alias = (ALIAS *) realloc(aliastable, - sizeof(ALIAS *) * count); + if (aliasTable) + { + alias = (Alias *) realloc(aliasTable, + sizeof(Alias) * count); + } else - alias = (ALIAS *) malloc(sizeof(ALIAS *) * count); + alias = (Alias *) malloc(sizeof(Alias) * count); - if (alias == NULL) { + if (alias == NULL) + { free(value); fprintf(stderr, "No memory for alias table\n"); return; } - aliastable = alias; + aliasTable = alias; } - alias = &aliastable[aliascount]; + alias = &aliasTable[aliasCount]; alias->name = malloc(strlen(name) + 1); - if (alias->name == NULL) { + if (alias->name == NULL) + { free(value); fprintf(stderr, "No memory for alias name\n"); @@ -752,7 +1055,35 @@ do_alias(argc, argv) strcpy(alias->name, name); alias->value = value; - aliascount++; + aliasCount++; +} + + +/* + * Build aliases for all of the built-in commands which start with a dash, + * using the names without the dash. + */ +void +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); + } } @@ -760,16 +1091,16 @@ do_alias(argc, argv) * Look up an alias name, and return a pointer to it. * Returns NULL if the name does not exist. */ -static ALIAS * -findalias(name) - const char * name; +static Alias * +findAlias(const char * name) { - ALIAS * alias; + Alias * alias; int count; - count = aliascount; + count = aliasCount; - for (alias = aliastable; count-- > 0; alias++) { + for (alias = aliasTable; count-- > 0; alias++) + { if (strcmp(name, alias->name) == 0) return alias; } @@ -779,55 +1110,52 @@ findalias(name) void -do_source(argc, argv) - int argc; - const char ** argv; +do_source(int argc, const char ** argv) { - readfile(argv[1]); + readFile(argv[1]); } void -do_exec(argc, argv) - int argc; - const char ** argv; +do_exec(int argc, const char ** argv) { const char * name; name = argv[1]; - if (access(name, 4)) { + if (access(name, 4)) + { perror(name); return; } - while (--sourcecount >= 0) { - if (sourcefiles[sourcecount] != stdin) - fclose(sourcefiles[sourcecount]); + while (--sourceCount >= 0) + { + if (sourcefiles[sourceCount] != stdin) + fclose(sourcefiles[sourceCount]); } argv[argc] = NULL; - execv(name, (char **) argv + 1); + execvp(name, (char **) argv + 1); exit(1); } void -do_prompt(argc, argv) - int argc; - const char ** argv; +do_prompt(int argc, const char ** argv) { char * cp; - char buf[CMDLEN]; + char buf[CMD_LEN]; - if (!makestring(argc - 1, argv + 1, buf, CMDLEN)) + if (!makeString(argc - 1, argv + 1, buf, CMD_LEN)) return; cp = malloc(strlen(buf) + 2); - if (cp == NULL) { + if (cp == NULL) + { fprintf(stderr, "No memory for prompt\n"); return; @@ -844,23 +1172,22 @@ do_prompt(argc, argv) void -do_unalias(argc, argv) - int argc; - const char ** argv; +do_unalias(int argc, const char ** argv) { - ALIAS * alias; + Alias * alias; - while (--argc > 0) { - alias = findalias(*++argv); + 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; + aliasCount--; + alias->name = aliasTable[aliasCount].name; + alias->value = aliasTable[aliasCount].value; } } @@ -869,7 +1196,7 @@ do_unalias(argc, argv) * Display the prompt string. */ static void -showprompt() +showPrompt(void) { const char * cp; @@ -883,27 +1210,25 @@ showprompt() static void -catchint(val) - int val; +catchInt(int val) { - signal(SIGINT, catchint); + signal(SIGINT, catchInt); - intflag = TRUE; + intFlag = TRUE; - if (intcrlf) + if (intCrlf) write(STDOUT, "\n", 1); } static void -catchquit(val) - int val; +catchQuit(int val) { - signal(SIGQUIT, catchquit); + signal(SIGQUIT, catchQuit); - intflag = TRUE; + intFlag = TRUE; - if (intcrlf) + if (intCrlf) write(STDOUT, "\n", 1); } @@ -912,11 +1237,11 @@ catchquit(val) * Print the usage information and quit. */ static void -usage() +usage(void) { fprintf(stderr, "Stand-alone shell (version %s)\n", version); fprintf(stderr, "\n"); - fprintf(stderr, "Usage: sash [-q] [-c command] [-p prompt]\n"); + fprintf(stderr, "Usage: sash [-a] [-q] [-c command] [-p prompt]\n"); exit(1); } diff --git a/sash.h b/sash.h index ef55539..93fe99a 100644 --- a/sash.h +++ b/sash.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998 by David I. Bell + * Copyright (c) 1999 by David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. * @@ -21,24 +21,27 @@ #include -#define PATHLEN 1024 -#define CMDLEN 10240 -#define MAXARGS 1000 -#define ALIASALLOC 20 +#define PATH_LEN 1024 +#define CMD_LEN 10240 +#define ALIAS_ALLOC 20 +#define EXPAND_ALLOC 1024 #define STDIN 0 #define STDOUT 1 -#define MAXSOURCE 10 -#define BUFSIZE 8192 +#define MAX_SOURCE 10 +#define BUF_SIZE 8192 -#ifndef isblank -#define isblank(ch) (((ch) == ' ') || ((ch) == '\t')) -#endif - -#define isquote(ch) (((ch) == '"') || ((ch) == '\'')) -#define isdecimal(ch) (((ch) >= '0') && ((ch) <= '9')) -#define isoctal(ch) (((ch) >= '0') && ((ch) <= '7')) +#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; @@ -47,94 +50,97 @@ typedef int BOOL; /* - * Macro to use function prototypes if this is an ANSI compiler. + * Built-in command functions. */ -#ifdef __STDC__ -#define PROTO(a) a -#else -#define const -#define PROTO(a) () +extern void do_alias(int argc, const char ** argv); +extern void do_aliasall(int argc, const char ** argv); +extern void do_cd(int argc, const char ** argv); +extern void do_exec(int argc, const char ** argv); +extern void do_exit(int argc, const char ** argv); +extern void do_prompt(int argc, const char ** argv); +extern void do_source(int argc, const char ** argv); +extern void do_umask(int argc, const char ** argv); +extern void do_unalias(int argc, const char ** argv); +extern void do_help(int argc, const char ** argv); +extern void do_ln(int argc, const char ** argv); +extern void do_cp(int argc, const char ** argv); +extern void do_mv(int argc, const char ** argv); +extern void do_rm(int argc, const char ** argv); +extern void do_chmod(int argc, const char ** argv); +extern void do_mkdir(int argc, const char ** argv); +extern void do_rmdir(int argc, const char ** argv); +extern void do_mknod(int argc, const char ** argv); +extern void do_chown(int argc, const char ** argv); +extern void do_chgrp(int argc, const char ** argv); +extern void do_sum(int argc, const char ** argv); +extern void do_sync(int argc, const char ** argv); +extern void do_printenv(int argc, const char ** argv); +extern void do_more(int argc, const char ** argv); +extern void do_cmp(int argc, const char ** argv); +extern void do_touch(int argc, const char ** argv); +extern void do_ls(int argc, const char ** argv); +extern void do_dd(int argc, const char ** argv); +extern void do_tar(int argc, const char ** argv); +extern void do_ar(int argc, const char ** argv); +extern void do_mount(int argc, const char ** argv); +extern void do_umount(int argc, const char ** argv); +extern void do_setenv(int argc, const char ** argv); +extern void do_pwd(int argc, const char ** argv); +extern void do_echo(int argc, const char ** argv); +extern void do_kill(int argc, const char ** argv); +extern void do_grep(int argc, const char ** argv); +extern void do_file(int argc, const char ** argv); +extern void do_find(int argc, const char ** argv); +extern void do_ed(int argc, const char ** argv); +extern void do_where(int argc, const char ** argv); + +#ifdef HAVE_GZIP +extern void do_gzip(int argc, const char ** argv); +extern void do_gunzip(int argc, const char ** argv); #endif - -/* - * Built-in command functions. - */ -extern void do_alias PROTO((int argc, const char ** argv)); -extern void do_cd PROTO((int argc, const char ** argv)); -extern void do_exec PROTO((int argc, const char ** argv)); -extern void do_exit PROTO((int argc, const char ** argv)); -extern void do_prompt PROTO((int argc, const char ** argv)); -extern void do_source PROTO((int argc, const char ** argv)); -extern void do_umask PROTO((int argc, const char ** argv)); -extern void do_unalias PROTO((int argc, const char ** argv)); -extern void do_help PROTO((int argc, const char ** argv)); -extern void do_ln PROTO((int argc, const char ** argv)); -extern void do_cp PROTO((int argc, const char ** argv)); -extern void do_mv PROTO((int argc, const char ** argv)); -extern void do_rm PROTO((int argc, const char ** argv)); -extern void do_chmod PROTO((int argc, const char ** argv)); -extern void do_mkdir PROTO((int argc, const char ** argv)); -extern void do_rmdir PROTO((int argc, const char ** argv)); -extern void do_mknod PROTO((int argc, const char ** argv)); -extern void do_chown PROTO((int argc, const char ** argv)); -extern void do_chgrp PROTO((int argc, const char ** argv)); -extern void do_sync PROTO((int argc, const char ** argv)); -extern void do_printenv PROTO((int argc, const char ** argv)); -extern void do_more PROTO((int argc, const char ** argv)); -extern void do_cmp PROTO((int argc, const char ** argv)); -extern void do_touch PROTO((int argc, const char ** argv)); -extern void do_ls PROTO((int argc, const char ** argv)); -extern void do_dd PROTO((int argc, const char ** argv)); -extern void do_tar PROTO((int argc, const char ** argv)); -extern void do_mount PROTO((int argc, const char ** argv)); -extern void do_umount PROTO((int argc, const char ** argv)); -extern void do_setenv PROTO((int argc, const char ** argv)); -extern void do_pwd PROTO((int argc, const char ** argv)); -extern void do_echo PROTO((int argc, const char ** argv)); -extern void do_kill PROTO((int argc, const char ** argv)); -extern void do_grep PROTO((int argc, const char ** argv)); -extern void do_ed PROTO((int argc, const char ** argv)); -extern void do_gzip PROTO((int argc, const char ** argv)); -extern void do_gunzip PROTO((int argc, const char ** argv)); -extern void do_where PROTO((int argc, const char ** argv)); +#ifdef HAVE_EXT2 +extern void do_lsattr(int argc, const char ** argv); +extern void do_chattr(int argc, const char ** argv); +#endif /* * Global utility routines. */ -extern const char * modestring PROTO((int mode)); -extern const char * timestring PROTO((time_t timeval)); -extern BOOL isadir PROTO((const char * name)); -extern int namesort PROTO((const void * p1, const void * p2)); -extern char * getchunk PROTO((int size)); -extern char * chunkstrdup PROTO((const char *)); -extern void freechunks PROTO((void)); -extern int fullWrite PROTO((int fd, const char * buf, int len)); -extern BOOL match PROTO((const char * text, const char * pattern)); - -extern const char * buildname - PROTO((const char * dirname, const char * filename)); +extern 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 fullWrite(int fd, const char * buf, int len); +extern int fullRead(int fd, char * buf, int len); +extern BOOL match(const char * text, const char * pattern); -extern BOOL makeargs - PROTO((const char * cmd, int * argcptr, const char *** argvptr)); +extern const char * buildName + (const char * dirName, const char * fileName); -extern BOOL copyfile - PROTO((const char * srcname, const char * destname, BOOL setmodes)); +extern BOOL makeArgs + (const char * cmd, int * argcPtr, const char *** argvPtr); -extern BOOL makestring - PROTO((int argc, const char ** argv, char * buf, int buflen)); +extern BOOL copyFile + (const char * srcName, const char * destName, BOOL setModes); -extern int expandwildcards - PROTO((const char *name, int maxargc, const char * retargv[])); +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; +extern BOOL intFlag; #endif diff --git a/utils.c b/utils.c index faef571..d128122 100644 --- a/utils.c +++ b/utils.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998 by David I. Bell + * Copyright (c) 1999 by David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. * @@ -14,16 +14,23 @@ #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 CHUNKINITSIZE 4 +#define CHUNK_INIT_SIZE 4 -struct chunk { +struct chunk +{ CHUNK * next; - char data[CHUNKINITSIZE]; /* actually of varying length */ + char data[CHUNK_INIT_SIZE]; /* actually of varying length */ }; -static CHUNK * chunklist; +static CHUNK * chunkList; @@ -32,8 +39,7 @@ static CHUNK * chunklist; * This is static and so is overwritten on each call. */ const char * -modestring(mode) - int mode; +modeString(int mode) { static char buf[12]; @@ -96,14 +102,13 @@ modestring(mode) /* - * Get the time to be used for a file. + * 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(timeval) - time_t timeval; +timeString(time_t timeVal) { time_t now; char * str; @@ -111,12 +116,13 @@ timestring(timeval) time(&now); - str = ctime(&timeval); + str = ctime(&timeVal); strcpy(buf, &str[4]); buf[12] = '\0'; - if ((timeval > now) || (timeval < now - 365*24*60*60L)) { + if ((timeVal > now) || (timeVal < now - 365*24*60*60L)) + { strcpy(&buf[7], &str[20]); buf[11] = '\0'; } @@ -126,19 +132,34 @@ timestring(timeval) /* - * Return TRUE if a filename is a directory. + * 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 -isadir(name) - const char * name; +isDevice(const char * name) { - struct stat statbuf; + struct stat statBuf; - if (stat(name, &statbuf) < 0) + if (stat(name, &statBuf) < 0) return FALSE; - return S_ISDIR(statbuf.st_mode); + return S_ISBLK(statBuf.st_mode) || S_ISCHR(statBuf.st_mode); } @@ -149,57 +170,64 @@ isadir(name) * be set.) */ BOOL -copyfile(srcname, destname, setmodes) - const char * srcname; - const char * destname; - BOOL setmodes; +copyFile( + const char * srcName, + const char * destName, + BOOL setModes +) { int rfd; int wfd; int rcc; - char buf[BUFSIZE]; - struct stat statbuf1; - struct stat statbuf2; + char buf[BUF_SIZE]; + struct stat statBuf1; + struct stat statBuf2; struct utimbuf times; - if (stat(srcname, &statbuf1) < 0) { - perror(srcname); + if (stat(srcName, &statBuf1) < 0) + { + perror(srcName); return FALSE; } - if (stat(destname, &statbuf2) < 0) { - statbuf2.st_ino = -1; - statbuf2.st_dev = -1; + 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)) + if ((statBuf1.st_dev == statBuf2.st_dev) && + (statBuf1.st_ino == statBuf2.st_ino)) { - fprintf(stderr, "Copying file \"%s\" to itself\n", srcname); + fprintf(stderr, "Copying file \"%s\" to itself\n", srcName); return FALSE; } - rfd = open(srcname, O_RDONLY); + rfd = open(srcName, O_RDONLY); - if (rfd < 0) { - perror(srcname); + if (rfd < 0) + { + perror(srcName); return FALSE; } - wfd = creat(destname, statbuf1.st_mode); + wfd = creat(destName, statBuf1.st_mode); - if (wfd < 0) { - perror(destname); + if (wfd < 0) + { + perror(destName); close(rfd); return FALSE; } - while ((rcc = read(rfd, buf, sizeof(buf))) > 0) { - if (intflag) { + while ((rcc = read(rfd, buf, sizeof(buf))) > 0) + { + if (intFlag) + { close(rfd); close(wfd); @@ -210,28 +238,31 @@ copyfile(srcname, destname, setmodes) goto error_exit; } - if (rcc < 0) { - perror(srcname); + if (rcc < 0) + { + perror(srcName); goto error_exit; } (void) close(rfd); - if (close(wfd) < 0) { - perror(destname); + if (close(wfd) < 0) + { + perror(destName); return FALSE; } - if (setmodes) { - (void) chmod(destname, statbuf1.st_mode); + if (setModes) + { + (void) chmod(destName, statBuf1.st_mode); - (void) chown(destname, statbuf1.st_uid, statbuf1.st_gid); + (void) chown(destName, statBuf1.st_uid, statBuf1.st_gid); - times.actime = statbuf1.st_atime; - times.modtime = statbuf1.st_mtime; + times.actime = statBuf1.st_atime; + times.modtime = statBuf1.st_mtime; - (void) utime(destname, ×); + (void) utime(destName, ×); } return TRUE; @@ -247,47 +278,43 @@ error_exit: /* * Build a path name from the specified directory name and file name. - * If the directory name is NULL, then the original filename is returned. + * If the directory name is NULL, then the original fileName is returned. * The built path is in a static area, and is overwritten for each call. */ const char * -buildname(dirname, filename) - const char * dirname; - const char * filename; +buildName(const char * dirName, const char * fileName) { const char * cp; - static char buf[PATHLEN]; + static char buf[PATH_LEN]; - if ((dirname == NULL) || (*dirname == '\0')) - return filename; + if ((dirName == NULL) || (*dirName == '\0')) + return fileName; - cp = strrchr(filename, '/'); + cp = strrchr(fileName, '/'); if (cp) - filename = cp + 1; + fileName = cp + 1; - strcpy(buf, dirname); + strcpy(buf, dirName); strcat(buf, "/"); - strcat(buf, filename); + strcat(buf, fileName); return buf; } /* - * Expand the wildcards in a filename, if any. - * Returns an argument list with matching filenames in sorted order. + * 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. Returns zero if the name is not a wildcard, or - * returns the count of matched files if the name is a wildcard and - * there was at least one match, or returns -1 if either no filenames - * matched or too many filenames matched (with an error output). + * 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(name, maxargc, retargv) - const char * name; - int maxargc; - const char * retargv[]; +expandWildCards(const char * fileNamePattern, const char *** retFileTable) { const char * last; const char * cp1; @@ -296,120 +323,214 @@ expandwildcards(name, maxargc, retargv) char * str; DIR * dirp; struct dirent * dp; - int dirlen; - int matches; - char dirname[PATHLEN]; + int dirLen; + int newFileTableSize; + char ** newFileTable; + char dirName[PATH_LEN]; - last = strrchr(name, '/'); + static int fileCount; + static int fileTableSize; + static char ** fileTable; - if (last) - last++; - else - last = name; + /* + * Clear the return values until we know their final values. + */ + fileCount = 0; + *retFileTable = NULL; - cp1 = strchr(name, '*'); - cp2 = strchr(name, '?'); - cp3 = strchr(name, '['); + /* + * 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 filename component\n"); + fprintf(stderr, + "Wildcards only implemented for last file name component\n"); return -1; } - dirname[0] = '.'; - dirname[1] = '\0'; + /* + * Assume at first that we are scanning the current directory. + */ + dirName[0] = '.'; + dirName[1] = '\0'; - if (last != name) { - memcpy(dirname, name, last - name); - dirname[last - name - 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'; + if (dirName[0] == '\0') + { + dirName[0] = '/'; + dirName[1] = '\0'; } } - dirp = opendir(dirname); + /* + * Open the directory containing the files to be checked. + */ + dirp = opendir(dirName); - if (dirp == NULL) { - perror(dirname); + if (dirp == NULL) + { + perror(dirName); return -1; } - dirlen = strlen(dirname); + /* + * Prepare the directory name for use in making full path names. + */ + dirLen = strlen(dirName); - if (last == name) { - dirlen = 0; - dirname[0] = '\0'; - } else if (dirname[dirlen - 1] != '/') { - dirname[dirlen++] = '/'; - dirname[dirlen] = '\0'; + if (last == fileNamePattern) + { + dirLen = 0; + dirName[0] = '\0'; + } + else if (dirName[dirLen - 1] != '/') + { + dirName[dirLen++] = '/'; + dirName[dirLen] = '\0'; } - matches = 0; - - while ((dp = readdir(dirp)) != NULL) { + /* + * 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; - if (matches >= maxargc) { - fprintf(stderr, "Too many filename matches\n"); - closedir(dirp); + /* + * 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; - return -1; + 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; } - str = getchunk(dirlen + strlen(dp->d_name) + 1); + /* + * 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 filename\n"); + if (str == NULL) + { + fprintf(stderr, "No memory for file name\n"); closedir(dirp); return -1; } - if (dirlen) - memcpy(str, dirname, dirlen); + /* + * Save the file name in the chunk. + */ + if (dirLen) + memcpy(str, dirName, dirLen); - strcpy(str + dirlen, dp->d_name); + strcpy(str + dirLen, dp->d_name); - retargv[matches++] = str; + /* + * Save the allocated file name into the file table. + */ + fileTable[fileCount++] = str; } + /* + * Close the directory and check for any matches. + */ closedir(dirp); - if (matches == 0) { + if (fileCount == 0) + { fprintf(stderr, "No matches\n"); return -1; } - qsort((void *) retargv, matches, sizeof(char *), namesort); + /* + * Sort the list of file names. + */ + qsort((void *) fileTable, fileCount, sizeof(char *), nameSort); + + /* + * Return the file list and count. + */ + *retFileTable = (const char **) fileTable; - return matches; + return fileCount; } /* - * Sort routine for list of filenames. + * Sort routine for list of fileNames. */ int -namesort(p1, p2) - const void * p1; - const void * p2; +nameSort(const void * p1, const void * p2) { const char ** s1; const char ** s2; @@ -432,31 +553,32 @@ namesort(p1, p2) * Adapted from code written by Ingo Wilken. */ BOOL -match(text, pattern) - const char * text; - const char * pattern; +match(const char * text, const char * pattern) { - const char * retrypat; - const char * retrytxt; + const char * retryPat; + const char * retryText; int ch; BOOL found; - retrypat = NULL; - retrytxt = NULL; + retryPat = NULL; + retryText = NULL; - while (*text || *pattern) { + while (*text || *pattern) + { ch = *pattern++; - switch (ch) { + switch (ch) + { case '*': - retrypat = pattern; - retrytxt = text; + retryPat = pattern; + retryText = text; break; case '[': found = FALSE; - while ((ch = *pattern++) != ']') { + while ((ch = *pattern++) != ']') + { if (ch == '\\') ch = *pattern++; @@ -467,9 +589,10 @@ match(text, pattern) found = TRUE; } - if (!found) { - pattern = retrypat; - text = ++retrytxt; + if (!found) + { + pattern = retryPat; + text = ++retryText; } /* fall into next case */ @@ -489,15 +612,17 @@ match(text, pattern) /* fall into next case */ default: - if (*text == ch) { + if (*text == ch) + { if (*text) text++; break; } - if (*text) { - pattern = retrypat; - text = ++retrytxt; + if (*text) + { + pattern = retryPat; + text = ++retryText; break; } @@ -513,51 +638,283 @@ match(text, pattern) /* - * Take a command string, and break it up into an argc, argv list. - * The returned argument list and strings are in static memory, and so - * are overwritten on each call. The argument array is ended with an - * extra NULL pointer for convenience. Returns TRUE if successful, - * or FALSE on an error with a message already output. + * 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(cmd, argcptr, argvptr) - const char * cmd; - int * argcptr; - const char *** argvptr; +makeArgs(const char * cmd, int * retArgc, const char *** retArgv) { + const char * argument; char * cp; - int argc; - static char strings[CMDLEN+1]; - static const char * argtable[MAXARGS+1]; + 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; /* - * Copy the command string and then break it apart - * into separate arguments. + * Clear the returned values until we know them. */ - strcpy(strings, cmd); - argc = 0; - cp = strings; + 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; - while (*cp) { - if (argc >= MAXARGS) { - fprintf(stderr, "Too many arguments\n"); + if (len > stringsLength) + { + newStrings = realloc(strings, len); + + if (newStrings == NULL) + { + fprintf(stderr, "Cannot allocate string\n"); return FALSE; } - argtable[argc++] = (const char *) cp; + 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; + } - while (*cp && !isblank(*cp)) - cp++; + /* + * 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)) + 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; } - argtable[argc] = NULL; + /* + * Null terminate the argument list and return it. + */ + argTable[argCount] = NULL; - *argcptr = argc; - *argvptr = argtable; + *retArgc = argCount; + *retArgv = argTable; return TRUE; } @@ -570,18 +927,21 @@ makeargs(cmd, argcptr, argvptr) * arguments correctly. */ BOOL -makestring(argc, argv, buf, buflen) - int argc; - const char ** argv; - char * buf; - int buflen; +makeString( + int argc, + const char ** argv, + char * buf, + int bufLen +) { int len; - while (argc-- > 0) { + while (argc-- > 0) + { len = strlen(*argv); - if (len >= buflen) { + if (len >= bufLen) + { fprintf(stderr, "Argument string too long\n"); return FALSE; @@ -590,12 +950,12 @@ makestring(argc, argv, buf, buflen) strcpy(buf, *argv++); buf += len; - buflen -= len; + bufLen -= len; if (argc) *buf++ = ' '; - buflen--; + bufLen--; } *buf = '\0'; @@ -611,21 +971,20 @@ makestring(argc, argv, buf, buflen) * an individual chunk. */ char * -getchunk(size) - int size; +getChunk(int size) { CHUNK * chunk; - if (size < CHUNKINITSIZE) - size = CHUNKINITSIZE; + if (size < CHUNK_INIT_SIZE) + size = CHUNK_INIT_SIZE; - chunk = (CHUNK *) malloc(size + sizeof(CHUNK) - CHUNKINITSIZE); + chunk = (CHUNK *) malloc(size + sizeof(CHUNK) - CHUNK_INIT_SIZE); if (chunk == NULL) return NULL; - chunk->next = chunklist; - chunklist = chunk; + chunk->next = chunkList; + chunkList = chunk; return chunk->data; } @@ -634,22 +993,21 @@ getchunk(size) /* * 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. + * with other strings when freeChunks is called. Returns NULL on failure. */ char * -chunkstrdup(str) - const char * str; +chunkstrdup(const char * str) { int len; - char * newstr; + char * newStr; len = strlen(str) + 1; - newstr = getchunk(len); + newStr = getChunk(len); - if (newstr) - memcpy(newstr, str, len); + if (newStr) + memcpy(newStr, str, len); - return newstr; + return newStr; } @@ -658,13 +1016,14 @@ chunkstrdup(str) * call to this routine. */ void -freechunks() +freeChunks(void) { CHUNK * chunk; - while (chunklist) { - chunk = chunklist; - chunklist = chunk->next; + while (chunkList) + { + chunk = chunkList; + chunkList = chunk->next; free((char *) chunk); } } @@ -676,17 +1035,15 @@ freechunks() * Returns the amount written, or -1 on an error. */ int -fullWrite(fd, buf, len) - int fd; - const char * buf; - int len; +fullWrite(int fd, const char * buf, int len) { int cc; int total; total = 0; - while (len > 0) { + while (len > 0) + { cc = write(fd, buf, len); if (cc < 0) @@ -700,4 +1057,37 @@ fullWrite(fd, buf, len) 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; +} + /* END CODE */