summaryrefslogblamecommitdiffstats
path: root/c/src/libmisc/untar/untar.c
blob: 60f1550c611daa42dc35888bfaaacbc2950c55b8 (plain) (tree)


















                                                             
                     









































































































































































































































































































































































                                                                            
/* FIXME:
 *   1. Symbolic links are not created.
 *   2. Untar_FromMemory has printfs.
 *   3. Untar_FromMemory uses FILE *fp.
 *   4. How to determine end of archive?
 *
 *  Written by: Jake Janovetz <janovetz@tempest.ece.uiuc.edu>
 *
 *  The license and distribution terms for this file may be
 *  found in the file LICENSE in this distribution or at
 *  http://www.OARcorp.com/rtems/license.html.
 *
 *  $Id$
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "untar.h"


/**************************************************************************
 * TAR file format:
 *
 *   Offset   Length   Contents
 *     0    100 bytes  File name ('\0' terminated, 99 maxmum length)
 *   100      8 bytes  File mode (in octal ascii)
 *   108      8 bytes  User ID (in octal ascii)
 *   116      8 bytes  Group ID (in octal ascii)
 *   124     12 bytes  File size (s) (in octal ascii)
 *   136     12 bytes  Modify time (in octal ascii)
 *   148      8 bytes  Header checksum (in octal ascii)
 *   156      1 bytes  Link flag
 *   157    100 bytes  Linkname ('\0' terminated, 99 maxmum length)
 *   257      8 bytes  Magic ("ustar  \0")
 *   265     32 bytes  User name ('\0' terminated, 31 maxmum length)
 *   297     32 bytes  Group name ('\0' terminated, 31 maxmum length)
 *   329      8 bytes  Major device ID (in octal ascii)
 *   337      8 bytes  Minor device ID (in octal ascii)
 *   345    167 bytes  Padding
 *   512   (s+p)bytes  File contents (s+p) := (((s) + 511) & ~511),
 *                     round up to 512 bytes
 * 
 *   Checksum:
 *   int i, sum;
 *   char* header = tar_header_pointer;
 *   sum = 0;
 *   for(i = 0; i < 512; i++)
 *       sum += 0xFF & header[i];
 *************************************************************************/

#define LF_OLDNORMAL  '\0'     /* Normal disk file, Unix compatible */
#define LF_NORMAL     '0'      /* Normal disk file                  */
#define LF_LINK       '1'      /* Link to previously dumped file    */
#define LF_SYMLINK    '2'      /* Symbolic link                     */
#define LF_CHR        '3'      /* Character special file            */
#define LF_BLK        '4'      /* Block special file                */
#define LF_DIR        '5'      /* Directory                         */
#define LF_FIFO       '6'      /* FIFO special file                 */
#define LF_CONFIG     '7'      /* Contiguous file                   */

#define MAX_NAME_FIELD_SIZE      99

#define MIN(a,b)   ((a)>(b)?(b):(a))


/**************************************************************************
 * This converts octal ASCII number representations into an
 * unsigned long.  Only support 32-bit numbers for now.
 *************************************************************************/
static unsigned long
octal2ulong(char *octascii, int len)
{
   int           i;
   unsigned long num;
   unsigned long mult;

   num = 0;
   mult = 1;
   for (i=len-1; i>=0; i--)
   {
      if ((octascii[i] < '0') || (octascii[i] > '9'))
      {
         continue;
      }
      num  += mult*((unsigned long)(octascii[i] - '0'));
      mult *= 8;
   }
   return(num);
}


/**************************************************************************
 * Function: Untar_FromMemory                                             *
 **************************************************************************
 * Description:                                                           *
 *                                                                        *
 *    This is a simple subroutine used to rip links, directories, and     *
 *    files out of a block of memory.                                     *
 *                                                                        *
 *                                                                        *
 * Inputs:                                                                *
 *                                                                        *
 *    unsigned char *tar_buf    - Pointer to TAR buffer.                  *
 *    unsigned long size        - Length of TAR buffer.                   *
 *                                                                        *
 *                                                                        *
 * Output:                                                                *
 *                                                                        *
 *    int - UNTAR_SUCCESSFUL (0)    on successful completion.             *
 *          UNTAR_INVALID_CHECKSUM  for an invalid header checksum.       *
 *          UNTAR_INVALID_HEADER    for an invalid header.                *
 *                                                                        *
 **************************************************************************
 * Change History:                                                        *
 *  12/30/1998 - Creation (JWJ)                                           *
 *************************************************************************/
int
Untar_FromMemory(unsigned char *tar_buf, unsigned long size)
{
   FILE           *fp;
   char           *bufr;
   size_t         n;
   char           fname[100];
   char           linkname[100];
   int            sum;
   int            hdr_chksum;
   int            retval;
   unsigned long  ptr;
   unsigned long  i;
   unsigned long  nblocks;
   unsigned long  file_size;
   unsigned char  linkflag;


   ptr = 0;
   while (1)
   {
      if (ptr + 512 > size)
      {
         retval = UNTAR_SUCCESSFUL;
         break;
      }

      /* Read the header */
      bufr = &tar_buf[ptr];
      ptr += 512;
      if (strncmp(&bufr[257], "ustar  ", 7))
      {
         retval = UNTAR_SUCCESSFUL;
         break;
      }

      strncpy(fname, bufr, MAX_NAME_FIELD_SIZE);
      fname[MAX_NAME_FIELD_SIZE] = '\0';

      linkflag   = bufr[156];
      file_size  = octal2ulong(&bufr[124], 12);

      /******************************************************************
       * Compute the TAR checksum and check with the value in 
       * the archive.  The checksum is computed over the entire
       * header, but the checksum field is substituted with blanks.
       ******************************************************************/
      hdr_chksum = (int)octal2ulong(&bufr[148], 8);
      sum = 0;
      for (i=0; i<512; i++)
      {
         if ((i >= 148) && (i < 156))
         {
            sum += 0xff & ' ';
         }
         else
         {
            sum += 0xff & bufr[i];
         }
      }
      if (sum != hdr_chksum)
      {
         retval = UNTAR_INVALID_CHECKSUM;
         break;
      }


      /******************************************************************
       * We've decoded the header, now figure out what it contains and
       * do something with it.
       *****************************************************************/
      if (linkflag == LF_SYMLINK)
      {
         strncpy(linkname, &bufr[157], MAX_NAME_FIELD_SIZE);
         linkname[MAX_NAME_FIELD_SIZE] = '\0';
         /* symlink(fname, linkname); */
      }
      else if (linkflag == LF_NORMAL)
      {
         nblocks = (((file_size) + 511) & ~511) / 512;
         if ((fp = fopen(fname, "w")) == NULL)
         {
            printf("Untar failed to create file %s\n", fname);
            ptr += 512 * nblocks;
         }
         else
         {
            unsigned long sizeToGo = file_size;
            unsigned long len;

            /***************************************************************
             * Read out the data.  There are nblocks of data where nblocks
             * is the file_size rounded to the nearest 512-byte boundary.
             **************************************************************/
            for (i=0; i<nblocks; i++)
            {
               len = ((sizeToGo < 512L)?(sizeToGo):(512L));
               n = fwrite(&tar_buf[ptr], 1, len, fp);
               if (n != len)
               {
                  printf("Error during write\n");
                  break;
               }
               ptr += 512;
               sizeToGo -= n;
            }
            fclose(fp);
         }
      }
      else if (linkflag == LF_DIR)
      {
         mkdir(fname, S_IRWXU | S_IRWXG | S_IRWXO);
      }
   }

   return(retval);
}


/**************************************************************************
 * Function: Untar_FromFile                                               *
 **************************************************************************
 * Description:                                                           *
 *                                                                        *
 *    This is a simple subroutine used to rip links, directories, and     *
 *    files out of a TAR file.                                            *
 *                                                                        *
 *                                                                        *
 * Inputs:                                                                *
 *                                                                        *
 *    char *tar_name   - TAR filename.                                    *
 *                                                                        *
 *                                                                        *
 * Output:                                                                *
 *                                                                        *
 *    int - UNTAR_SUCCESSFUL (0)    on successful completion.             *
 *          UNTAR_INVALID_CHECKSUM  for an invalid header checksum.       *
 *          UNTAR_INVALID_HEADER    for an invalid header.                *
 *                                                                        *
 **************************************************************************
 * Change History:                                                        *
 *  12/30/1998 - Creation (JWJ)                                           *
 *************************************************************************/
int
Untar_FromFile(char *tar_name)
{
   int            fd;
   char           *bufr;
   size_t         n;
   char           fname[100];
   char           linkname[100];
   int            sum;
   int            hdr_chksum;
   int            retval;
   unsigned long  i;
   unsigned long  nblocks;
   unsigned long  size;
   unsigned char  linkflag;


   retval = UNTAR_SUCCESSFUL;
   bufr = (char *)malloc(512);
   if (bufr == NULL)
   {
      return(UNTAR_FAIL);
   }

   fd = open(tar_name, O_RDONLY);
   while (1)
   {
      /* Read the header */
      /* If the header read fails, we just consider it the end
         of the tarfile. */
      if ((n = read(fd, bufr, 512)) != 512)
      {
         break;
      }

      if (strncmp(&bufr[257], "ustar  ", 7))
      {
         break;
      }

      strncpy(fname, bufr, MAX_NAME_FIELD_SIZE);
      fname[MAX_NAME_FIELD_SIZE] = '\0';

      linkflag   = bufr[156];
      size       = octal2ulong(&bufr[124], 12);

      /******************************************************************
       * Compute the TAR checksum and check with the value in 
       * the archive.  The checksum is computed over the entire
       * header, but the checksum field is substituted with blanks.
       ******************************************************************/
      hdr_chksum = (int)octal2ulong(&bufr[148], 8);
      sum = 0;
      for (i=0; i<512; i++)
      {
         if ((i >= 148) && (i < 156))
         {
            sum += 0xff & ' ';
         }
         else
         {
            sum += 0xff & bufr[i];
         }
      }
      if (sum != hdr_chksum)
      {
         retval = UNTAR_INVALID_CHECKSUM;
         break;
      }


      /******************************************************************
       * We've decoded the header, now figure out what it contains and
       * do something with it.
       *****************************************************************/
      if (linkflag == LF_SYMLINK)
      {
         strncpy(linkname, &bufr[157], MAX_NAME_FIELD_SIZE);
         linkname[MAX_NAME_FIELD_SIZE] = '\0';
      }
      else if (linkflag == LF_NORMAL)
      {
         int out_fd;

         /******************************************************************
          * Read out the data.  There are nblocks of data where nblocks
          * is the size rounded to the nearest 512-byte boundary.
          *****************************************************************/
         nblocks = (((size) + 511) & ~511) / 512;

         if ((out_fd = creat(fname, 0644)) == -1)
         {
            for (i=0; i<nblocks; i++)
            {
               n = read(fd, bufr, 512);
            }
         }
         else
         {
            for (i=0; i<nblocks; i++)
            {
               n = read(fd, bufr, 512);
               n = MIN(n, size - i*512);
               write(out_fd, bufr, n);
            }
            close(out_fd);
         }
      }
      else if (linkflag == LF_DIR)
      {
         mkdir(fname, S_IRWXU | S_IRWXG | S_IRWXO);
      }
   }
   free(bufr);
   close(fd);

   return(retval);
}