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