From f4149952ea4895356a3d4c0a9517893bb1e18cd2 Mon Sep 17 00:00:00 2001 From: tcsullivan Date: Thu, 18 Oct 2018 18:24:43 -0400 Subject: wip forking, added sash source --- src/sash/cmd_ar.c | 1019 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1019 insertions(+) create mode 100644 src/sash/cmd_ar.c (limited to 'src/sash/cmd_ar.c') diff --git a/src/sash/cmd_ar.c b/src/sash/cmd_ar.c new file mode 100644 index 0000000..6c6181e --- /dev/null +++ b/src/sash/cmd_ar.c @@ -0,0 +1,1019 @@ +/* + * Original: + * Copyright (c) 1999 by Aaron R. Crane. + * Permission is granted to use, distribute, or modify this source, + * provided that this copyright notice remains intact. + * + * Modified: + * Copyright (c) 2014 by David I. Bell + * Permission is granted to use, distribute, or modify this source, + * provided that this copyright notice remains intact. + * + * The "ar" built-in command. + * This allows extraction and listing of ar files. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sash.h" + + +/* + * Structure to hold information about the archive file. + */ +typedef struct +{ + int fd; /* file reading archive from */ + BOOL eof; /* end of file has been seen */ + BOOL rescan; /* rescan the header just read */ + char * nameTable; /* long name table */ + + /* + * Information about the current file read from the archive. + * This is extracted from the latest member header read. + */ + char * name; /* current file name */ + time_t date; /* date of file */ + uid_t uid; /* user id */ + gid_t gid; /* group id */ + mode_t mode; /* file protection */ + off_t size; /* file size */ + int pad; /* padding to next header */ +} Archive; + + +/* + * Local procedures. + */ +static void initArchive(Archive * arch); +static BOOL openArchive(const char * name, Archive * arch); +static void closeArchive(Archive * arch); +static BOOL readSpecialMember(Archive * arch); +static BOOL readNormalMember(Archive * arch); +static BOOL readMember(Archive * arch, struct ar_hdr * hdr); +static BOOL skipMember(const Archive * arch); +static BOOL skipPadding(int fd, int pad); +static BOOL writeFile(const Archive * arch, int outfd); +static int createFile(const Archive * arch); +static BOOL canonicalize(Archive * arch, const struct ar_hdr * hdr); +static void listMember(const Archive * arch); +static int shortNameMatches44BSD(const char * name); +static int shortNameMatchesSysV(const char * name); + +static BOOL wantMember(const Archive * arch, int n_names, + const char ** names); + +static BOOL getNumber(const char * s, unsigned base, int max_digits, + unsigned long * ul); + + +int +do_ar(int argc, const char ** argv) +{ + const char * options; + const char * archiveName; + BOOL doExtract; + BOOL doTable; + BOOL doPrint; + BOOL verbose; + int r; + Archive arch; + + r = 0; + verbose = FALSE; + doExtract = FALSE; + doTable = FALSE; + doPrint = FALSE; + + if (argc < 3) + { + fprintf(stderr, "Too few arguments for ar\n"); + + return 1; + } + + /* + * Get the option string and archive file name. + */ + options = argv[1]; + archiveName = argv[2]; + + /* + * Advance the arguments to the list of file names (if any). + */ + argc -= 3; + argv += 3; + + /* + * Parse the option characters. + */ + for (; *options; options++) + { + switch (*options) + { + case 't': + doTable = TRUE; + break; + + case 'x': + doExtract = TRUE; + break; + + case 'p': + doPrint = TRUE; + break; + + case 'v': + verbose = TRUE; + break; + + case 'd': case 'm': case 'q': case 'r': + fprintf(stderr, "Writing ar files is not supported\n"); + + return 1; + + default: + fprintf(stderr, "Unknown ar flag: %c\n", *options); + + return 1; + } + } + + if (doExtract + doTable + doPrint != 1) + { + fprintf(stderr, + "Exactly one of 'x', 'p' or 't' must be specified\n"); + + return 1; + } + + /* + * Open the archive file. + */ + initArchive(&arch); + + if (!openArchive(archiveName, &arch)) + return 1; + + /* + * Read the first special member of the archive. + */ + if (!readSpecialMember(&arch)) + return 1; + + /* + * Read all of the normal members of the archive. + */ + while (readNormalMember(&arch)) + { + /* + * If this file is not wanted then skip it. + */ + if (!wantMember(&arch, argc, argv)) + { + if (!skipMember(&arch)) + break; + + continue; + } + + /* + * This file is wanted. + */ + if (doTable) + { + if (verbose) + listMember(&arch); + else + puts(arch.name); + + if (!skipMember(&arch)) + break; + } + else if (doPrint) + { + if (verbose) + { + /* + * The verbose format makes me gag, + * but 4.4BSD, GNU and even V7 all + * have the same lossage. + */ + printf("\n<%s>\n\n", arch.name); + fflush(stdout); + } + + if (!writeFile(&arch, STDOUT)) + break; + } + else if (doExtract) + { + int outfd; + BOOL success; + + if (verbose) + printf("x - %s\n", arch.name); + + outfd = createFile(&arch); + + if (outfd == -1) + break; + + success = writeFile(&arch, outfd); + + if (close(outfd) == -1) + { + fprintf(stderr, "Can't close %s: %s\n", + arch.name, strerror(errno)); + + break; + } + + if (!success) + { + r = 1; + break; + } + } + else + { + fprintf(stderr, "Oops -- I don't know what to do\n"); + r = 1; + break; + } + } + + closeArchive(&arch); + + return r; +} + + +/* + * Open the file for writing for the specified archive structure, + * setting its mode and owner if possible. Returns the file handle + * of the opened file, or -1 on an error. + */ +static int +createFile(const Archive * arch) +{ + int fd; + + fd = open(arch->name, O_WRONLY | O_CREAT | O_TRUNC, arch->mode); + + if (fd == -1) + { + fprintf(stderr, "Can't create \"%s\": %s\n", + arch->name, strerror(errno)); + + return -1; + } + + /* + * Don't worry if these fail. We have to do the fchmod() despite + * specifying the mode in the open() call, because that mode is + * munged by the umask. + */ + checkStatus("fchmod", fchmod(fd, arch->mode)); + checkStatus("fchown", fchown(fd, arch->uid, arch->gid)); + + return fd; +} + + +/* + * Return whether the current archive member is wanted. + * This means that the file name is contained in the specified list of + * file names, or else that the specified list of file names is empty. + */ +static BOOL +wantMember(const Archive * arch, int n_names, const char ** name) +{ + int i; + + /* + * If there are no names then all archive members are wanted. + */ + if (n_names == 0) + return TRUE; + + /* + * See if the member file name is contained in the list. + */ + for (i = 0; i < n_names; i++) + { + if (strcmp(arch->name, name[i]) == 0) + return TRUE; + } + + return FALSE; +} + + +/* + * Parse a number from the specified string in the specified base. + * The number is terminated by a null, space, or the specified number + * of digits. The number is returned through the supplied pointer. + * Only non-negatives numbers are parsed. Returns TRUE on success. + */ +static BOOL +getNumber(const char * s, unsigned base, int max_digits, unsigned long * ul) +{ + const char * p; + const char * endp; + unsigned long cutoff; + unsigned long cutlim; + + if (base < 2 || base > 10 || s == 0 || *s == 0 || ul == 0) + return FALSE; + + cutoff = ULONG_MAX / (unsigned long) base; + cutlim = ULONG_MAX % (unsigned long) base; + *ul = 0; + + endp = (max_digits >= 0) ? s + max_digits : 0; + + for (p = s; endp ? p < endp : 1; p++) + { + unsigned d; + + if (*p == 0 || *p == ' ') + break; /* end of string */ + + if (!isDecimal(*p)) + return FALSE; /* non-digit */ + + d = *p - '0'; + + if (d >= base) + return FALSE; /* digit outside range for base */ + + if (*ul > cutoff || (*ul == cutoff && d > cutlim)) + return FALSE; /* overflow */ + + *ul *= base; + *ul += d; + } + + if (p == s) + return FALSE; /* nothing was converted */ + + if (*p && *p != ' ') + return FALSE; /* trailing garbage */ + + return TRUE; +} + + +/* + * Initialise the specified Archive structure for use. + */ +static void +initArchive(Archive * arch) +{ + arch->fd = -1; + arch->name = 0; + arch->nameTable = 0; + arch->eof = FALSE; + arch->rescan = FALSE; +} + + +/* + * Open the specified archive file name and read the header from it. + * The file handle is saved in the Archive structure for further use. + * Returns TRUE on success. + */ +static BOOL +openArchive(const char * name, Archive * arch) +{ + unsigned char buf[SARMAG]; + ssize_t cc; + + arch->fd = open(name, O_RDONLY); + + if (arch->fd == -1) + { + fprintf(stderr, "Can't open archive file %s: %s\n", + name, strerror(errno)); + + return FALSE; + } + + cc = read(arch->fd, buf, SARMAG); + + if (cc == -1) + { + fprintf(stderr, "Error reading archive header: %s\n", + strerror(errno)); + + goto close_and_out; + } + + if (cc != SARMAG) + { + fprintf(stderr, "Short read of archive header\n"); + + goto close_and_out; + } + + if (memcmp(buf, ARMAG, SARMAG)) + { + fprintf(stderr, "Invalid archive header\n"); + + goto close_and_out; + } + + return TRUE; + + + /* + * Here on an error to clean up. + */ +close_and_out: + (void) close(arch->fd); + arch->fd = -1; + + return FALSE; +} + + +/* + * Close the archive file. + */ +static void +closeArchive(Archive * arch) +{ + free(arch->name); + arch->name = 0; + + free(arch->nameTable); + arch->nameTable = 0; + + if (arch->fd >= 0) + (void) close(arch->fd); + + arch->fd = -1; +} + + +/* + * Read an archive member header into the specified structure. + * Returns TRUE on success. On failure, the eof flag is set if + * the end of file had been reached. + */ +static BOOL +readMember(Archive * arch, struct ar_hdr * hdr) +{ + ssize_t cc; + + cc = read(arch->fd, hdr, sizeof(*hdr)); + + if (cc < 0) + { + fprintf(stderr, "Error reading member header: %s\n", + strerror(errno)); + + return FALSE; + } + + if (cc == 0) + { + arch->eof = TRUE; + + return FALSE; + } + + if (cc != sizeof(*hdr)) + { + fprintf(stderr, "Short read of member header\n"); + + return FALSE; + } + + if (memcmp(hdr->ar_fmag, ARFMAG, sizeof(hdr->ar_fmag))) + { + fprintf(stderr, "Invalid member header\n"); + + return FALSE; + } + + return TRUE; +} + + +/* + * Check the member file name and see if it matches the 4.4 BSD + * syntax for long file names. If so, return the number of characters + * of the actual long file name. Returns -1 on an error. + */ +static int +shortNameMatches44BSD(const char * name) +{ + const char * p; + unsigned long ul; + + if (strncmp(name, "#1/", 3) != 0) + return -1; + + if (!isDecimal(name[3])) + return -1; + + for (p = name + 4; *p; p++) + { + if (!isDecimal(*p)) + break; + } + + while (*p) + { + if (*p++ != ' ') + return -1; + } + + if (!getNumber(name + 3, 10, -1, &ul)) + return -1; + + if (ul == 0) /* broken archive */ + return -1; + + return ul; +} + + +/* + * Check the member file name and see if it matches the SYS V syntax + * for long file names. If so, return the number of characters of the + * actual long file name. Returns -1 on an error. + */ +static int +shortNameMatchesSysV(const char * name) +{ + const char * p; + unsigned long ul; + + /* "^/(\d+) *$" */ + if (name[0] != '/') + return -1; + + if (!isDecimal(name[1])) + return -1; + + for (p = name + 2; *p; p++) + { + if (!isDecimal(*p)) + break; + } + + while (*p) + { + if (*p++ != ' ') + return -1; + } + + if (!getNumber(name + 1, 10, -1, &ul)) + return -1; + + return ul; +} + + +#define MEMB_NAME_ALLOC(n) \ + do \ + { \ + arch->name = malloc(n); \ + if (!arch->name) \ + { \ + fprintf(stderr, "Out of memory\n"); \ + return FALSE; \ + } \ + } while (0); + + +/* + * Examine the archive structure that was read and fill in the + * current member data with the extracted information. This handles + * various types of archive headers. This can read data from the + * archive file to obtain a long file name. Returns TRUE on success. + */ +static BOOL +canonicalize(Archive * arch, const struct ar_hdr * hdr) +{ + char buf[sizeof(hdr->ar_name) + 1]; + int n; + unsigned long ul; + unsigned long bsd_len; + + bsd_len = 0; + + free(arch->name); + arch->name = 0; + + strncpy(buf, hdr->ar_name, sizeof(hdr->ar_name)); + buf[sizeof(hdr->ar_name)] = 0; + + /* + * 1. If shortname matches "^#1/(\d+) *$", then it's a 4.4BSD + * longname. Read a longname of $1 bytes from ARCH->fd, or + * return FALSE if impossible. + */ + if ((n = shortNameMatches44BSD(buf)) != -1) + { + /* N is the length of the longname */ + ssize_t cc; + + bsd_len = n; + + MEMB_NAME_ALLOC(n + 1); + + cc = read(arch->fd, arch->name, n); + + if (cc == -1) + { + fprintf(stderr, "Error reading longname: %s\n", + strerror(errno)); + + goto free_and_out; + } + + if (cc != n) + { + fprintf(stderr, "Unexpected end of file in longname\n"); + + goto free_and_out; + } + + arch->name[n] = 0; + } + + /* + * 2. Otherwise, if shortname matches "^/(\d+) *$", then it's a SysV + * longname. Get the longname from the nameTable, or return FALSE + * if there is none. + */ + else if ((n = shortNameMatchesSysV(buf)) != -1) + { + /* + * N is the index of the longname + */ + const char * longname; + const char * p; + size_t len; + + if (n >= strlen(arch->nameTable)) + { + fprintf(stderr, "Longname index too large\n"); + + return FALSE; + } + + longname = arch->nameTable + n; + + p = strchr(longname, '/'); + + if (!p) + { + fprintf(stderr, "Bad longname index\n"); + + return FALSE; + } + + if (p[1] != '\n') + { + fprintf(stderr, "Malformed longname table\n"); + + return FALSE; + } + + len = p - longname; + MEMB_NAME_ALLOC(len + 1); + strncpy(arch->name, longname, len); + arch->name[len] = '\0'; + } + + /* + * 3. Otherwise, it's just a shortname. If the shortname contains a + * slash, then the name terminates before the slash; otherwise, + * the name terminates at the first space, or at the end of the + * field if there is none. */ + else + { + const char * p; + size_t len; + + p = strchr(buf, '/'); + + if (!p) + p = strchr(buf, ' '); + + if (p) + len = p - buf; + else + len = sizeof(hdr->ar_name); + + MEMB_NAME_ALLOC(len + 1); + strncpy(arch->name, buf, len); + arch->name[len] = 0; + } + + /* + * 4. Parse the remaining fields of the header. Return FALSE if any + * are missing or ill-formed. + */ +#define FIELD(AFIELD, MFIELD, base) \ + if (!getNumber(hdr->AFIELD, base, sizeof(hdr->AFIELD), &ul)) \ + { \ + fprintf(stderr, "Malformed archive member header\n"); \ + goto free_and_out; \ + } \ + arch->MFIELD = ul; + + FIELD(ar_date, date, 10); + FIELD(ar_uid, uid, 10); + FIELD(ar_gid, gid, 10); + FIELD(ar_mode, mode, 8); + FIELD(ar_size, size, 10); +#undef FIELD + + /* + * 4a. Decide whether a pad byte will be present.u + * + * The 4.4BSD format is really broken and needs a whole pile of + * cruft to deal with it. There are several cases: + * + * 1. Even namelen, even memberlen: no pad. + * 2. Even namelen, odd memberlen: pad. Just like SysV. + * 3. Odd namelen, even memberlen: pad. Cruft. + * 4. Odd namelen, odd memberlen: no pad. Cruft. + * + * Essentially, whenever the namelen is odd, the naive determination + * of whether a pad is needed is reversed. + */ + if (!bsd_len) + arch->pad = (arch->size % 2) ? 1 : 0; + else + { + arch->size -= bsd_len; + arch->pad = (arch->size % 2) ? 1 : 0; + + if (bsd_len % 2) + arch->pad = !arch->pad; + } + + /* + * 5. Everything was successful. + */ + return TRUE; + + + /* + * 5a. Error exit -- free memory. + */ +free_and_out: + free(arch->name); + arch->name = 0; + + return FALSE; +} + + +/* + * Skip the padding character if required to position to the + * beginning of the next member header. This is done if the + * padding value is nonzero. Returns TRUE on success. + */ +static BOOL +skipPadding(int fd, int pad) +{ + if (pad) + { + if (lseek(fd, 1, SEEK_CUR) == -1) + { + fprintf(stderr, "Can't skip pad byte: %s\n", + strerror(errno)); + + return FALSE; + } + } + + return TRUE; +} + + +/* + * Read the first member of the archive file and check whether it + * is a special one, and if so, handle it. If the first member is + * a normal archive member, then set up to rescan it for the next + * readNormalMember call. Returns TRUE on success. + */ +static BOOL +readSpecialMember(Archive * arch) +{ + struct ar_hdr hdr; + + /* + * 1. Read a header H. Fail if impossible. + */ + if (!readMember(arch, &hdr)) + return FALSE; + + /* + * 2. If H is a symbol table, ditch it. + * Fail if impossible. + */ + if ((strncmp(hdr.ar_name, "/ ", 2) == 0) || + (strncmp(hdr.ar_name, "__.SYMTAB ", + sizeof(hdr.ar_name)) == 0)) + { + if (!canonicalize(arch, &hdr)) + return FALSE; + + return skipMember(arch); + } + + /* + * 3. If H is a SysV longname table, read it into ARCH. + */ + if (strncmp(hdr.ar_name, "//", 2) == 0) + { + unsigned long len; + ssize_t cc; + + if (!getNumber(hdr.ar_size, 10, sizeof(hdr.ar_size), &len)) + { + fprintf(stderr, "Invalid name-table size\n"); + + return FALSE; + } + + arch->nameTable = malloc(len + 1); + + if (!arch->nameTable) + { + fprintf(stderr, "Out of memory\n"); + + return FALSE; + } + + cc = read(arch->fd, arch->nameTable, len); + + if (cc == -1) + { + fprintf(stderr, "Error reading name-table: %s\n", + strerror(errno)); + + return FALSE; + } + + if (cc != (ssize_t) len) + { + fprintf(stderr, + "Unexpected end of file in name-table\n"); + + return FALSE; + } + + arch->nameTable[len] = 0; + + return skipPadding(arch->fd, len % 2); + } + + /* + * 4. We read a normal header. + * Canonicalize it, and mark it as needing rescanning. + */ + arch->rescan = TRUE; + + return canonicalize(arch, &hdr); +} + + +/* + * Read the next normal member of the archive file if possible. + * If the member is being rescanned, clear the rescan flag and + * return the header that was already read. Returns TRUE on + * success. On a failure, the eof flag will be set if end of + * file has been reached. + */ +static BOOL +readNormalMember(Archive * arch) +{ + struct ar_hdr hdr; + + /* + * If we are rereading an old header then just clear the + * rescan flag and return success. + */ + if (arch->rescan) + { + arch->rescan = FALSE; + + return TRUE; + } + + /* + * We need to read a new member header. + */ + if (!readMember(arch, &hdr)) + return FALSE; + + return canonicalize(arch, &hdr); +} + + +/* + * Skip the current member of the archive so that we are positioned + * to tbe beginning of the next member's header (or end of file). + * Returns TRUE on success. + */ +static BOOL +skipMember(const Archive * arch) +{ + if (lseek(arch->fd, arch->size, SEEK_CUR) == -1) + { + fprintf(stderr, "Can't skip past archive member: %s\n", + strerror(errno)); + + return FALSE; + } + + return skipPadding(arch->fd, arch->pad); +} + + +/* + * Copy all of the file data from the archive to the specified + * open file. Returns TRUE on success. + */ +static BOOL +writeFile(const Archive * arch, int outfd) +{ + char buf[BUF_SIZE]; + off_t n; + + n = arch->size; + + while (n > 0) + { + ssize_t cc; + + cc = read(arch->fd, buf, MIN(n, sizeof(buf))); + + if (cc == -1) + { + fprintf(stderr, "Error reading archive member: %s\n", + strerror(errno)); + + return FALSE; + } + + if (cc == 0) + { + fprintf(stderr, "Unexpected end of file\n"); + + return FALSE; + } + + if (fullWrite(outfd, buf, cc) < 0) + { + fprintf(stderr, "Write error: %s\n", strerror(errno)); + + return FALSE; + } + + n -= cc; + } + + if (!skipPadding(arch->fd, arch->pad)) + return FALSE; + + return TRUE; +} + + +/* + * Print one line listing the information about the specified archive member. + */ +static void +listMember(const Archive * arch) +{ + printf("%s %6ld/%-6ld %8lu %s %s\n", + modeString(arch->mode) + 1, + (long) arch->uid, + (long) arch->gid, + (unsigned long) arch->size, + timeString(arch->date), + arch->name); +} + + +/* END CODE */ -- cgit v1.2.3