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

tar.cc

/*
 *    tardy - a tar post-processor
 *    Copyright (C) 1998, 1999, 2001-2004 Peter Miller;
 *    All rights reserved.
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 *
 * MANIFEST: functions to manipulate tar input
 */

#include <ac/errno.h>
#include <ac/string.h>

#include <error.h>
#include <tar/format.h>
#include <tar/input/tar.h>


tar_input_tar::~tar_input_tar()
{
    delete fp;
}


tar_input_tar::tar_input_tar(file_input *arg) :
    fp(arg),
    pos(0),
    state(0)
{
}


static int
all_zero(char *buf, long len)
{
    while (len > 0)
    {
      if (*buf++)
          return 0;
      --len;
    }
    return 1;
}


void
tar_input_tar::check_state(int a, int b)
{
    if (state != a && (b >= 0 && state != b))
      fatal("state %d: should be %d", state, a);
    if (state < 3 && (pos % TBLOCK))
      fatal("state %d: offset is %d", state, (int)(pos % TBLOCK));
}


int
tar_input_tar::read_data_strict(void *buffer, int maximum_length)
{
    if (fp < 0)
	::fatal("file not open (bug)");
    int result = 0;
    while (maximum_length > 0)
    {
      int nbytes = fp->read(buffer, maximum_length);
      if (nbytes == 0)
      {
          if (result == 0)
                  break;
          fatal
          (
                  "short read (asked %d, got %d)",
                  maximum_length + result,
                  result
          );
      }
      pos += nbytes;
      result += nbytes;
      maximum_length -= nbytes;
      buffer = (char *)buffer + nbytes;
    }
    return result;
}


int
tar_input_tar::read_data(void *buffer, int maximum_length)
{
    check_state(2, 3);
    state = 3;

    return read_data_strict(buffer, maximum_length);
}


void
tar_input_tar::read_data_padding()
{
    check_state(2, 3);
    state = 0;

    int n = pos % TBLOCK;
    if (n <= 0)
      return;
    char dummy[TBLOCK];
    n = TBLOCK - n;
    int nbytes = read_data_strict(dummy, n);
    if (nbytes < n)
      fatal("premature end of file (short padding read)");
}


int
tar_input_tar::read_header_longname(int size, tar_header &arg)
{
    //
    // Read the long name.
    //
    int size2 = (size + TBLOCK - 1) & -TBLOCK; 
    char *buffer = new char[size2];
    int nbytes = read_data_strict(buffer, size2);
    if (nbytes != size2)
      fatal("premature end of file (short name read)");
    while (size > 1 && buffer[size - 1] == 0)
      --size;
    rcstring long_file_name = rcstring(buffer, size);
    delete buffer;

    //
    // Read the next file header
    //
    state = 0;
    if (!read_header(arg))
      return 0;

    //
    // Replace the name with the long name we just read.
    //
    arg.name = long_file_name;

    //
    // Watch out for trailing slashes
    //
    while (arg.name[arg.name.length() - 1] == '/')
    {
      arg.name =
          rcstring
          (
            arg.name.to_c_string(),
            arg.name.length() - 1
          );
      arg.type = tar_header::type_directory;
      arg.size = 0;
      arg.link_count = 2;
    }
    return 1;
}


int
tar_input_tar::read_header_longlink(int size, tar_header &arg)
{
    //
    // Read the long name.
    //
    int size2 = (size + TBLOCK - 1) & -TBLOCK; 
    char *buffer = new char[size2];
    int nbytes = read_data_strict(buffer, size2);
    if (nbytes != size2)
      fatal("premature end of file (short name read)");
    while (size > 1 && buffer[size - 1] == 0)
      --size;
    rcstring long_link_name = rcstring(buffer, size);
    delete buffer;

    //
    // Read the next file header
    //
    state = 0;
    if (!read_header(arg))
      return 0;

    //
    // Replace the name with the long name we just read.
    //
    arg.linkname = long_link_name;
    return 1;
}


int
tar_input_tar::read_header_gzipped(int size, tar_header &arg)
{
    //
    // Read the name of the uncompressed file.
    //
    int size2 = (size + TBLOCK - 1) & -TBLOCK; 
    char *buffer = new char[size2];
    int nbytes = read_data_strict(buffer, size2);
    if (nbytes != size2)
      fatal("premature end of file (short name read)");
    while (size > 1 && buffer[size - 1] == 0)
      --size;
    rcstring long_file_name = rcstring(buffer, size);
    delete buffer;

    //
    // Read the next file header
    //
    state = 0;
    if (!read_header(arg))
      return 0;

    //
    // Replace the name with the name of the uncompressed file.
    //
    if (long_file_name.length() != 0)
      arg.name = long_file_name;
    arg.type = tar_header::type_normal_gzipped;
    return 1;
}


int
tar_input_tar::read_header(tar_header &arg)
{
    check_state(0);
    state = 1;

    /*
     * read a block of input
     */
    char block[TBLOCK];
    int nbytes = read_data_strict(block, TBLOCK);
    if (!nbytes)
      return 0;
    if (nbytes != TBLOCK)
      fatal("premature end of file (short header read)");
    if (all_zero(block, TBLOCK))
      return 0;

    /*
     * Build a pointer to the on-tape header block structure.
     * Use this to asist decoding the header block.
     */
    header_ty *hp = (header_ty *)block;

    /*
     * chew over header fields
     */
    int hchksum = hp->chksum_get();
    if (hchksum < 0)
      fp->fatal("corrupted checksum field");
    int cs2 = hp->calculate_checksum();
    if (hchksum != cs2)
    {
      hp->dump();
      fp->fatal
      (
          "checksum does not match (calculated 0%o, file has 0%o)",
          cs2,
          hchksum
      );
    }

    arg = tar_header(); /* zero out everything */

    /*
     * Give all files a unique inode number.
     *
     * Let the writer figure out (from the linkname) if they should
     * be the same.  FIXME: use a symtab and get the inode_number
     * and link_count more correct.
     */
    static long ino;
    arg.inode_number = ++ino;

    arg.name = hp->name_get();

    int hsize = hp->size_get();
    if (hsize < 0)
      fp->fatal("%s: corrupted size field", arg.name.to_c_string());
    arg.size = hsize;

    switch (hp->linkflag_get())
    {
    case LF_OLDNORMAL:
    case LF_NORMAL:
      arg.type = tar_header::type_normal;
      /*
       * Working out that it is a directory is
       * interesting: you can't rely on the mode bits,
       * and it shows as a normal file.  The clue is
       * that the name will end with a slash.
       */
      while (arg.name[arg.name.length() - 1] == '/')
      {
          arg.name = rcstring(arg.name.to_c_string(), arg.name.length() - 1);
          arg.type = tar_header::type_directory;
          arg.size = 0;
          arg.link_count = 2;
      }
      break;

    case LF_CONTIG:
      arg.type = tar_header::type_normal_contiguous;
      break;

    case LF_GZIPPED:
      return read_header_gzipped(hsize, arg);

    case LF_LINK:
      arg.linkname = hp->linkname_get();
      arg.type = tar_header::type_link_hard;
      arg.size = 0;
      arg.link_count = 2;
      break;

    case LF_SYMLINK:
      arg.linkname = hp->linkname_get();
      arg.type = tar_header::type_link_symbolic;
      arg.size = 0;
      break;

    case LF_CHR:
      arg.type = tar_header::type_device_character;
      arg.size = 0;
      break;

    case LF_BLK:
      arg.type = tar_header::type_device_block;
      arg.size = 0;
      break;

    case LF_DIR:
      while (arg.name[arg.name.length() - 1] == '/')
          arg.name = rcstring(arg.name.to_c_string(), arg.name.length() - 1);
      arg.type = tar_header::type_directory;
      arg.size = 0;
      arg.link_count = 2;
      break;

    case LF_FIFO:
      arg.type = tar_header::type_fifo;
      arg.size = 0;
      break;

    case LF_LONGNAME:
      return read_header_longname(hsize, arg);

    case LF_LONGLINK:
      return read_header_longlink(hsize, arg);

    default:
      fp->fatal("file type ``%c'' unknown", hp->linkflag_get());
      break;
    }

    int hmode = hp->mode_get();
    if (hmode < 0)
      fatal("\"%s\" corrupted mode field", arg.name.to_c_string());
    arg.mode = hmode;

    int huid = hp->uid_get();
    if (huid < 0)
      fatal("\"%s\" corrupted uid field", arg.name.to_c_string());
    arg.user_id = huid;

    int hgid = hp->gid_get();
    if (hgid < 0)
      fatal("\"%s\" corrupted gid field", arg.name.to_c_string());
    arg.group_id = hgid;

    int hmtime = hp->mtime_get();
    if (hmtime < 0)
    {
      /*
       * usually because of a file time
       * before 1970 or after 2038
       */
      if (errno != ERANGE)
          fatal("\"%s\" corrupted mtime field", arg.name.to_c_string());
      warning("\"%s\" mtime field out of range", arg.name.to_c_string());

      time_t now;
      time(&now);
      hmtime = now;
    }
    arg.mtime = hmtime;

    if (0 == memcmp(hp->magic, TMAGIC, sizeof(hp->magic)))
    {
      arg.user_name = hp->uname_get();
      arg.group_name = hp->gname_get();
      arg.device_major = hp->devmajor_get();
      arg.device_minor = hp->devminor_get();
    }
    else
    {
      arg.user_name = "nobody";
      arg.group_name = "nogroup";
      arg.device_major = 0;
      arg.device_minor = 0;
    }

    /*
     * say that we found something
     */
    return 1;
}


void
tar_input_tar::read_header_padding()
{
    check_state(1);
    state = 2;
}


const char *
tar_input_tar::filename()
    const
{
    return fp->filename();
}

Generated by  Doxygen 1.6.0   Back to index