Logo Search packages:      
Sourcecode: sash version File versions  Download package

cmd_ar.c

/*
 * 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) 2002 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.
 */
00033 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 */

Generated by  Doxygen 1.6.0   Back to index