/* * 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 #include #include #include #include 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(); }