summaryrefslogtreecommitdiffstats
path: root/src/vfs/tar/tar.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 17:44:12 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 17:44:12 +0000
commit8ccb487c21368a7fdc8c7c72315325bf0aa06147 (patch)
treeb2056fae01d325924508a41731edfbd4c3cddd23 /src/vfs/tar/tar.c
parentInitial commit. (diff)
downloadmc-8ccb487c21368a7fdc8c7c72315325bf0aa06147.tar.xz
mc-8ccb487c21368a7fdc8c7c72315325bf0aa06147.zip
Adding upstream version 3:4.8.29.upstream/3%4.8.29upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/vfs/tar/tar.c')
-rw-r--r--src/vfs/tar/tar.c1010
1 files changed, 1010 insertions, 0 deletions
diff --git a/src/vfs/tar/tar.c b/src/vfs/tar/tar.c
new file mode 100644
index 0000000..dd98226
--- /dev/null
+++ b/src/vfs/tar/tar.c
@@ -0,0 +1,1010 @@
+/*
+ Virtual File System: GNU Tar file system.
+
+ Copyright (C) 1995-2022
+ Free Software Foundation, Inc.
+
+ Written by:
+ Jakub Jelinek, 1995
+ Pavel Machek, 1998
+ Slava Zanko <slavazanko@gmail.com>, 2013
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file
+ * \brief Source: Virtual File System: GNU Tar file system
+ * \author Jakub Jelinek
+ * \author Pavel Machek
+ * \date 1995, 1998
+ *
+ * Namespace: init_tarfs
+ */
+
+#include <config.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <ctype.h>
+
+#ifdef hpux
+/* major() and minor() macros (among other things) defined here for hpux */
+#include <sys/mknod.h>
+#endif
+
+#include "lib/global.h"
+#include "lib/util.h"
+#include "lib/unixcompat.h" /* makedev() */
+#include "lib/widget.h" /* message() */
+
+#include "lib/vfs/vfs.h"
+#include "lib/vfs/utilvfs.h"
+#include "lib/vfs/xdirentry.h"
+#include "lib/vfs/gc.h" /* vfs_rmstamp */
+
+#include "tar.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define TAR_SUPER(super) ((tar_super_t *) (super))
+
+
+/* tar Header Block, from POSIX 1003.1-1990. */
+
+/* The magic field is filled with this if uname and gname are valid. */
+#define TMAGIC "ustar" /* ustar and a null */
+
+#define XHDTYPE 'x' /* Extended header referring to the next file in the archive */
+#define XGLTYPE 'g' /* Global extended header */
+
+/* Values used in typeflag field. */
+#define LNKTYPE '1' /* link */
+#define SYMTYPE '2' /* symbolic link */
+#define CHRTYPE '3' /* character special */
+#define BLKTYPE '4' /* block special */
+#define DIRTYPE '5' /* directory */
+#define FIFOTYPE '6' /* FIFO special */
+
+
+/* tar Header Block, GNU extensions. */
+
+/* *BEWARE* *BEWARE* *BEWARE* that the following information is still
+ boiling, and may change. Even if the OLDGNU format description should be
+ accurate, the so-called GNU format is not yet fully decided. It is
+ surely meant to use only extensions allowed by POSIX, but the sketch
+ below repeats some ugliness from the OLDGNU format, which should rather
+ go away. Sparse files should be saved in such a way that they do *not*
+ require two passes at archive creation time. Huge files get some POSIX
+ fields to overflow, alternate solutions have to be sought for this. */
+
+
+/* Sparse files are not supported in POSIX ustar format. For sparse files
+ with a POSIX header, a GNU extra header is provided which holds overall
+ sparse information and a few sparse descriptors. When an old GNU header
+ replaces both the POSIX header and the GNU extra header, it holds some
+ sparse descriptors too. Whether POSIX or not, if more sparse descriptors
+ are still needed, they are put into as many successive sparse headers as
+ necessary. The following constants tell how many sparse descriptors fit
+ in each kind of header able to hold them. */
+
+#define SPARSES_IN_EXTRA_HEADER 16
+#define SPARSES_IN_OLDGNU_HEADER 4
+#define SPARSES_IN_SPARSE_HEADER 21
+
+/* OLDGNU_MAGIC uses both magic and version fields, which are contiguous.
+ Found in an archive, it indicates an old GNU header format, which will be
+ hopefully become obsolescent. With OLDGNU_MAGIC, uname and gname are
+ valid, though the header is not truly POSIX conforming. */
+#define OLDGNU_MAGIC "ustar " /* 7 chars and a null */
+
+/* The standards committee allows only capital A through capital Z for user-defined expansion. */
+
+/* This is a dir entry that contains the names of files that were in the
+ dir at the time the dump was made. */
+#define GNUTYPE_DUMPDIR 'D'
+
+/* Identifies the *next* file on the tape as having a long linkname. */
+#define GNUTYPE_LONGLINK 'K'
+
+/* Identifies the *next* file on the tape as having a long name. */
+#define GNUTYPE_LONGNAME 'L'
+
+
+/* tar Header Block, overall structure. */
+
+/* tar files are made in basic blocks of this size. */
+#define BLOCKSIZE 512
+
+
+#define isodigit(c) ( ((c) >= '0') && ((c) <= '7') )
+
+/*** file scope type declarations ****************************************************************/
+
+/* *INDENT-OFF* */
+
+/* POSIX header */
+struct posix_header
+{ /* byte offset */
+ char name[100]; /* 0 */
+ char mode[8]; /* 100 */
+ char uid[8]; /* 108 */
+ char gid[8]; /* 116 */
+ char size[12]; /* 124 */
+ char mtime[12]; /* 136 */
+ char chksum[8]; /* 148 */
+ char typeflag; /* 156 */
+ char linkname[100]; /* 157 */
+ char magic[6]; /* 257 */
+ char version[2]; /* 263 */
+ char uname[32]; /* 265 */
+ char gname[32]; /* 297 */
+ char devmajor[8]; /* 329 */
+ char devminor[8]; /* 337 */
+ char prefix[155]; /* 345 */
+ /* 500 */
+};
+
+/* Descriptor for a single file hole */
+struct sparse
+{ /* byte offset */
+ /* cppcheck-suppress unusedStructMember */
+ char offset[12]; /* 0 */
+ /* cppcheck-suppress unusedStructMember */
+ char numbytes[12]; /* 12 */
+ /* 24 */
+};
+
+/* The GNU extra header contains some information GNU tar needs, but not
+ foreseen in POSIX header format. It is only used after a POSIX header
+ (and never with old GNU headers), and immediately follows this POSIX
+ header, when typeflag is a letter rather than a digit, so signaling a GNU
+ extension. */
+struct extra_header
+{ /* byte offset */
+ char atime[12]; /* 0 */
+ char ctime[12]; /* 12 */
+ char offset[12]; /* 24 */
+ char realsize[12]; /* 36 */
+ char longnames[4]; /* 48 */
+ char unused_pad1[68]; /* 52 */
+ struct sparse sp[SPARSES_IN_EXTRA_HEADER];
+ /* 120 */
+ char isextended; /* 504 */
+ /* 505 */
+};
+
+/* Extension header for sparse files, used immediately after the GNU extra
+ header, and used only if all sparse information cannot fit into that
+ extra header. There might even be many such extension headers, one after
+ the other, until all sparse information has been recorded. */
+struct sparse_header
+{ /* byte offset */
+ struct sparse sp[SPARSES_IN_SPARSE_HEADER];
+ /* 0 */
+ char isextended; /* 504 */
+ /* 505 */
+};
+
+/* The old GNU format header conflicts with POSIX format in such a way that
+ POSIX archives may fool old GNU tar's, and POSIX tar's might well be
+ fooled by old GNU tar archives. An old GNU format header uses the space
+ used by the prefix field in a POSIX header, and cumulates information
+ normally found in a GNU extra header. With an old GNU tar header, we
+ never see any POSIX header nor GNU extra header. Supplementary sparse
+ headers are allowed, however. */
+struct oldgnu_header
+{ /* byte offset */
+ char unused_pad1[345]; /* 0 */
+ char atime[12]; /* 345 */
+ char ctime[12]; /* 357 */
+ char offset[12]; /* 369 */
+ char longnames[4]; /* 381 */
+ char unused_pad2; /* 385 */
+ struct sparse sp[SPARSES_IN_OLDGNU_HEADER];
+ /* 386 */
+ char isextended; /* 482 */
+ char realsize[12]; /* 483 */
+ /* 495 */
+};
+
+/* *INDENT-ON* */
+
+/* tar Header Block, overall structure */
+union block
+{
+ char buffer[BLOCKSIZE];
+ struct posix_header header;
+ struct extra_header extra_header;
+ struct oldgnu_header oldgnu_header;
+ struct sparse_header sparse_header;
+};
+
+enum archive_format
+{
+ TAR_UNKNOWN = 0,
+ TAR_V7,
+ TAR_USTAR,
+ TAR_POSIX,
+ TAR_GNU
+};
+
+typedef enum
+{
+ STATUS_BADCHECKSUM,
+ STATUS_SUCCESS,
+ STATUS_EOFMARK,
+ STATUS_EOF
+} ReadStatus;
+
+typedef struct
+{
+ struct vfs_s_super base; /* base class */
+
+ int fd;
+ struct stat st;
+ enum archive_format type; /* Type of the archive */
+} tar_super_t;
+
+/*** file scope variables ************************************************************************/
+
+static struct vfs_s_subclass tarfs_subclass;
+static struct vfs_class *vfs_tarfs_ops = VFS_CLASS (&tarfs_subclass);
+
+/* As we open one archive at a time, it is safe to have this static */
+static off_t current_tar_position = 0;
+
+static union block block_buf;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Quick and dirty octal conversion.
+ *
+ * Result is -1 if the field is invalid (all blank, or nonoctal).
+ */
+static long
+tar_from_oct (int digs, const char *where)
+{
+ long value;
+
+ while (isspace ((unsigned char) *where))
+ { /* Skip spaces */
+ where++;
+ if (--digs <= 0)
+ return -1; /* All blank field */
+ }
+ value = 0;
+ while (digs > 0 && isodigit (*where))
+ { /* Scan till nonoctal */
+ value = (value << 3) | (*where++ - '0');
+ --digs;
+ }
+
+ if (digs > 0 && *where && !isspace ((unsigned char) *where))
+ return -1; /* Ended on non-space/nul */
+
+ return value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_super *
+tar_new_archive (struct vfs_class *me)
+{
+ tar_super_t *arch;
+
+ arch = g_new0 (tar_super_t, 1);
+ arch->base.me = me;
+ arch->fd = -1;
+ arch->type = TAR_UNKNOWN;
+
+ return VFS_SUPER (arch);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tar_free_archive (struct vfs_class *me, struct vfs_s_super *archive)
+{
+ tar_super_t *arch = TAR_SUPER (archive);
+
+ (void) me;
+
+ if (arch->fd != -1)
+ {
+ mc_close (arch->fd);
+ arch->fd = -1;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Returns fd of the open tar file */
+static int
+tar_open_archive_int (struct vfs_class *me, const vfs_path_t * vpath, struct vfs_s_super *archive)
+{
+ int result, type;
+ tar_super_t *arch;
+ mode_t mode;
+ struct vfs_s_inode *root;
+
+ result = mc_open (vpath, O_RDONLY);
+ if (result == -1)
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot open tar archive\n%s"), vfs_path_as_str (vpath));
+ ERRNOR (ENOENT, -1);
+ }
+
+ archive->name = g_strdup (vfs_path_as_str (vpath));
+ arch = TAR_SUPER (archive);
+ mc_stat (vpath, &arch->st);
+
+ /* Find out the method to handle this tar file */
+ type = get_compression_type (result, archive->name);
+ if (type == COMPRESSION_NONE)
+ mc_lseek (result, 0, SEEK_SET);
+ else
+ {
+ char *s;
+ vfs_path_t *tmp_vpath;
+
+ mc_close (result);
+ s = g_strconcat (archive->name, decompress_extension (type), (char *) NULL);
+ tmp_vpath = vfs_path_from_str_flags (s, VPF_NO_CANON);
+ result = mc_open (tmp_vpath, O_RDONLY);
+ vfs_path_free (tmp_vpath, TRUE);
+ if (result == -1)
+ message (D_ERROR, MSG_ERROR, _("Cannot open tar archive\n%s"), s);
+ g_free (s);
+ if (result == -1)
+ {
+ MC_PTR_FREE (archive->name);
+ ERRNOR (ENOENT, -1);
+ }
+ }
+
+ arch->fd = result;
+ mode = arch->st.st_mode & 07777;
+ if (mode & 0400)
+ mode |= 0100;
+ if (mode & 0040)
+ mode |= 0010;
+ if (mode & 0004)
+ mode |= 0001;
+ mode |= S_IFDIR;
+
+ root = vfs_s_new_inode (me, archive, &arch->st);
+ root->st.st_mode = mode;
+ root->data_offset = -1;
+ root->st.st_nlink++;
+ root->st.st_dev = VFS_SUBCLASS (me)->rdev++;
+
+ archive->root = root;
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static union block *
+tar_get_next_block (struct vfs_s_super *archive, int tard)
+{
+ int n;
+
+ (void) archive;
+
+ n = mc_read (tard, block_buf.buffer, sizeof (block_buf.buffer));
+ if (n != sizeof (block_buf.buffer))
+ return NULL; /* An error has occurred */
+ current_tar_position += sizeof (block_buf.buffer);
+ return &block_buf;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tar_skip_n_records (struct vfs_s_super *archive, int tard, size_t n)
+{
+ (void) archive;
+
+ mc_lseek (tard, n * sizeof (block_buf.buffer), SEEK_CUR);
+ current_tar_position += n * sizeof (block_buf.buffer);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ReadStatus
+tar_checksum (const union block *header)
+{
+ long recsum;
+ long signed_sum = 0;
+ long sum = 0;
+ int i;
+ const char *p = header->buffer;
+
+ recsum = tar_from_oct (8, header->header.chksum);
+
+ for (i = sizeof (*header); --i >= 0;)
+ {
+ /*
+ * We can't use unsigned char here because of old compilers,
+ * e.g. V7.
+ */
+ signed_sum += *p;
+ sum += 0xFF & *p++;
+ }
+
+ /* Adjust checksum to count the "chksum" field as blanks. */
+ for (i = sizeof (header->header.chksum); --i >= 0;)
+ {
+ sum -= 0xFF & header->header.chksum[i];
+ signed_sum -= (char) header->header.chksum[i];
+ }
+
+ sum += ' ' * sizeof (header->header.chksum);
+ signed_sum += ' ' * sizeof (header->header.chksum);
+
+ /*
+ * This is a zeroed block... whole block is 0's except
+ * for the 8 blanks we faked for the checksum field.
+ */
+ if (sum == 8 * ' ')
+ return STATUS_EOFMARK;
+
+ if (sum != recsum && signed_sum != recsum)
+ return STATUS_BADCHECKSUM;
+
+ return STATUS_SUCCESS;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static size_t
+tar_decode_header (union block *header, tar_super_t * arch)
+{
+ size_t size;
+
+ /*
+ * Try to determine the archive format.
+ */
+ if (arch->type == TAR_UNKNOWN)
+ {
+ if (strcmp (header->header.magic, TMAGIC) == 0)
+ {
+ if (header->header.typeflag == XGLTYPE)
+ arch->type = TAR_POSIX;
+ else
+ arch->type = TAR_USTAR;
+ }
+ else if (strcmp (header->header.magic, OLDGNU_MAGIC) == 0)
+ arch->type = TAR_GNU;
+ }
+
+ /*
+ * typeflag on BSDI tar (pax) always '\000'
+ */
+ if (header->header.typeflag == '\000')
+ {
+ size_t len;
+
+ if (header->header.name[sizeof (header->header.name) - 1] != '\0')
+ len = sizeof (header->header.name);
+ else
+ len = strlen (header->header.name);
+
+ if (len != 0 && IS_PATH_SEP (header->header.name[len - 1]))
+ header->header.typeflag = DIRTYPE;
+ }
+
+ /*
+ * Good block. Decode file size and return.
+ */
+ if (header->header.typeflag == LNKTYPE || header->header.typeflag == DIRTYPE)
+ size = 0; /* Links 0 size on tape */
+ else
+ size = tar_from_oct (1 + 12, header->header.size);
+
+ if (header->header.typeflag == GNUTYPE_DUMPDIR)
+ if (arch->type == TAR_UNKNOWN)
+ arch->type = TAR_GNU;
+
+ return size;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tar_fill_stat (struct vfs_s_super *archive, struct stat *st, union block *header, size_t h_size)
+{
+ tar_super_t *arch = TAR_SUPER (archive);
+
+ st->st_mode = tar_from_oct (8, header->header.mode);
+
+ /* Adjust st->st_mode because there are tar-files with
+ * typeflag==SYMTYPE and S_ISLNK(mod)==0. I don't
+ * know about the other modes but I think I cause no new
+ * problem when I adjust them, too. -- Norbert.
+ */
+ if (header->header.typeflag == DIRTYPE || header->header.typeflag == GNUTYPE_DUMPDIR)
+ st->st_mode |= S_IFDIR;
+ else if (header->header.typeflag == SYMTYPE)
+ st->st_mode |= S_IFLNK;
+ else if (header->header.typeflag == CHRTYPE)
+ st->st_mode |= S_IFCHR;
+ else if (header->header.typeflag == BLKTYPE)
+ st->st_mode |= S_IFBLK;
+ else if (header->header.typeflag == FIFOTYPE)
+ st->st_mode |= S_IFIFO;
+ else
+ st->st_mode |= S_IFREG;
+
+ st->st_dev = 0;
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ st->st_rdev = 0;
+#endif
+
+ switch (arch->type)
+ {
+ case TAR_USTAR:
+ case TAR_POSIX:
+ case TAR_GNU:
+ /* *INDENT-OFF* */
+ st->st_uid = *header->header.uname != '\0'
+ ? (uid_t) vfs_finduid (header->header.uname)
+ : tar_from_oct (8, header->header.uid);
+ st->st_gid = *header->header.gname != '\0'
+ ? (gid_t) vfs_findgid (header->header.gname)
+ : tar_from_oct (8,header->header.gid);
+ /* *INDENT-ON* */
+
+ switch (header->header.typeflag)
+ {
+ case BLKTYPE:
+ case CHRTYPE:
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ st->st_rdev =
+ makedev (tar_from_oct (8, header->header.devmajor),
+ tar_from_oct (8, header->header.devminor));
+#endif
+ break;
+ default:
+ break;
+ }
+ break;
+
+ default:
+ st->st_uid = tar_from_oct (8, header->header.uid);
+ st->st_gid = tar_from_oct (8, header->header.gid);
+ break;
+ }
+
+ st->st_size = h_size;
+#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ st->st_atim.tv_nsec = st->st_mtim.tv_nsec = st->st_ctim.tv_nsec = 0;
+#endif
+ st->st_mtime = tar_from_oct (1 + 12, header->header.mtime);
+ st->st_atime = 0;
+ st->st_ctime = 0;
+ if (arch->type == TAR_GNU)
+ {
+ st->st_atime = tar_from_oct (1 + 12, header->oldgnu_header.atime);
+ st->st_ctime = tar_from_oct (1 + 12, header->oldgnu_header.ctime);
+ }
+
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ st->st_blksize = 8 * 1024; /* FIXME */
+#endif
+ vfs_adjust_stat (st);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Return 1 for success, 0 if the checksum is bad, EOF on eof,
+ * 2 for a block full of zeros (EOF marker).
+ *
+ */
+static ReadStatus
+tar_read_header (struct vfs_class *me, struct vfs_s_super *archive, int tard, size_t * h_size)
+{
+ tar_super_t *arch = TAR_SUPER (archive);
+ ReadStatus checksum_status;
+ union block *header;
+ static char *next_long_name = NULL, *next_long_link = NULL;
+
+ while (TRUE)
+ {
+ header = tar_get_next_block (archive, tard);
+ if (header == NULL)
+ return STATUS_EOF;
+
+ checksum_status = tar_checksum (header);
+ if (checksum_status != STATUS_SUCCESS)
+ return checksum_status;
+
+ *h_size = tar_decode_header (header, arch);
+
+ /* Skip over pax extended header and global extended header records. */
+ if (header->header.typeflag == XHDTYPE || header->header.typeflag == XGLTYPE)
+ {
+ if (arch->type == TAR_UNKNOWN)
+ arch->type = TAR_POSIX;
+ return STATUS_SUCCESS;
+ }
+
+ if (header->header.typeflag == GNUTYPE_LONGNAME
+ || header->header.typeflag == GNUTYPE_LONGLINK)
+ {
+ char **longp;
+ char *bp, *data;
+ off_t size;
+ size_t written;
+
+ if (arch->type == TAR_UNKNOWN)
+ arch->type = TAR_GNU;
+
+ if (*h_size > MC_MAXPATHLEN)
+ {
+ message (D_ERROR, MSG_ERROR, _("Inconsistent tar archive"));
+ return STATUS_BADCHECKSUM;
+ }
+
+ longp = header->header.typeflag == GNUTYPE_LONGNAME ? &next_long_name : &next_long_link;
+
+ g_free (*longp);
+ bp = *longp = g_malloc (*h_size + 1);
+
+ for (size = *h_size; size > 0; size -= written)
+ {
+ data = tar_get_next_block (archive, tard)->buffer;
+ if (data == NULL)
+ {
+ MC_PTR_FREE (*longp);
+ message (D_ERROR, MSG_ERROR, _("Unexpected EOF on archive file"));
+ return STATUS_BADCHECKSUM;
+ }
+ written = BLOCKSIZE;
+ if ((off_t) written > size)
+ written = (size_t) size;
+
+ memcpy (bp, data, written);
+ bp += written;
+ }
+
+ if (bp - *longp == MC_MAXPATHLEN && bp[-1] != '\0')
+ {
+ MC_PTR_FREE (*longp);
+ message (D_ERROR, MSG_ERROR, _("Inconsistent tar archive"));
+ return STATUS_BADCHECKSUM;
+ }
+
+ *bp = '\0';
+ }
+ else
+ break;
+ }
+
+ {
+ struct stat st;
+ struct vfs_s_entry *entry;
+ struct vfs_s_inode *inode = NULL, *parent;
+ off_t data_position;
+ char *p, *q;
+ size_t len;
+ char *current_file_name, *current_link_name;
+
+ current_link_name =
+ next_long_link != NULL ? next_long_link : g_strndup (header->header.linkname,
+ sizeof (header->header.linkname));
+ len = strlen (current_link_name);
+ if (len > 1 && IS_PATH_SEP (current_link_name[len - 1]))
+ current_link_name[len - 1] = '\0';
+
+ current_file_name = NULL;
+ switch (arch->type)
+ {
+ case TAR_USTAR:
+ case TAR_POSIX:
+ /* The ustar archive format supports pathnames of upto 256
+ * characters in length. This is achieved by concatenating
+ * the contents of the 'prefix' and 'name' fields like
+ * this:
+ *
+ * prefix + path_separator + name
+ *
+ * If the 'prefix' field contains an empty string i.e. its
+ * first characters is '\0' the prefix field is ignored.
+ */
+ if (header->header.prefix[0] != '\0')
+ {
+ char *temp_name, *temp_prefix;
+
+ temp_name = g_strndup (header->header.name, sizeof (header->header.name));
+ temp_prefix = g_strndup (header->header.prefix, sizeof (header->header.prefix));
+ current_file_name = g_strconcat (temp_prefix, PATH_SEP_STR,
+ temp_name, (char *) NULL);
+ g_free (temp_name);
+ g_free (temp_prefix);
+ }
+ break;
+ case TAR_GNU:
+ if (next_long_name != NULL)
+ current_file_name = next_long_name;
+ break;
+ default:
+ break;
+ }
+
+ if (current_file_name == NULL)
+ {
+ if (next_long_name != NULL)
+ current_file_name = g_strdup (next_long_name);
+ else
+ current_file_name = g_strndup (header->header.name, sizeof (header->header.name));
+ }
+
+ canonicalize_pathname (current_file_name);
+ len = strlen (current_file_name);
+
+ data_position = current_tar_position;
+
+ p = strrchr (current_file_name, PATH_SEP);
+ if (p == NULL)
+ {
+ p = current_file_name;
+ q = current_file_name + len; /* "" */
+ }
+ else
+ {
+ *(p++) = '\0';
+ q = current_file_name;
+ }
+
+ parent = vfs_s_find_inode (me, archive, q, LINK_NO_FOLLOW, FL_MKDIR);
+ if (parent == NULL)
+ {
+ message (D_ERROR, MSG_ERROR, _("Inconsistent tar archive"));
+ return STATUS_BADCHECKSUM;
+ }
+
+ if (header->header.typeflag == LNKTYPE)
+ {
+ inode = vfs_s_find_inode (me, archive, current_link_name, LINK_NO_FOLLOW, FL_NONE);
+ if (inode == NULL)
+ message (D_ERROR, MSG_ERROR, _("Inconsistent tar archive"));
+ else
+ {
+ entry = vfs_s_new_entry (me, p, inode);
+ vfs_s_insert_entry (me, parent, entry);
+ g_free (current_link_name);
+ goto done;
+ }
+ }
+
+ memset (&st, 0, sizeof (st));
+ tar_fill_stat (archive, &st, header, *h_size);
+
+ if (S_ISDIR (st.st_mode))
+ {
+ entry = VFS_SUBCLASS (me)->find_entry (me, parent, p, LINK_NO_FOLLOW, FL_NONE);
+ if (entry != NULL)
+ goto done;
+ }
+
+ inode = vfs_s_new_inode (me, archive, &st);
+ inode->data_offset = data_position;
+
+ if (*current_link_name != '\0')
+ inode->linkname = current_link_name;
+ else if (current_link_name != next_long_link)
+ g_free (current_link_name);
+
+ entry = vfs_s_new_entry (me, p, inode);
+ vfs_s_insert_entry (me, parent, entry);
+ g_free (current_file_name);
+
+ done:
+ next_long_link = next_long_name = NULL;
+
+ if (arch->type == TAR_GNU && header->oldgnu_header.isextended)
+ {
+ while (tar_get_next_block (archive, tard)->sparse_header.isextended != 0)
+ ;
+
+ if (inode != NULL)
+ inode->data_offset = current_tar_position;
+ }
+ return STATUS_SUCCESS;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Main loop for reading an archive.
+ * Returns 0 on success, -1 on error.
+ */
+static int
+tar_open_archive (struct vfs_s_super *archive, const vfs_path_t * vpath,
+ const vfs_path_element_t * vpath_element)
+{
+ /* Initial status at start of archive */
+ ReadStatus status = STATUS_EOFMARK;
+ int tard;
+
+ current_tar_position = 0;
+ /* Open for reading */
+ tard = tar_open_archive_int (vpath_element->class, vpath, archive);
+ if (tard == -1)
+ return -1;
+
+ while (TRUE)
+ {
+ size_t h_size = 0;
+ ReadStatus prev_status = status;
+
+ status = tar_read_header (vpath_element->class, archive, tard, &h_size);
+
+ switch (status)
+ {
+ case STATUS_SUCCESS:
+ tar_skip_n_records (archive, tard, (h_size + BLOCKSIZE - 1) / BLOCKSIZE);
+ continue;
+
+ /*
+ * Invalid header:
+ *
+ * If the previous header was good, tell them
+ * that we are skipping bad ones.
+ */
+ case STATUS_BADCHECKSUM:
+ switch (prev_status)
+ {
+ /* Error on first block */
+ case STATUS_EOFMARK:
+ {
+ message (D_ERROR, MSG_ERROR, _("%s\ndoesn't look like a tar archive."),
+ vfs_path_as_str (vpath));
+ MC_FALLTHROUGH;
+
+ /* Error after header rec */
+ }
+ case STATUS_SUCCESS:
+ /* Error after error */
+
+ case STATUS_BADCHECKSUM:
+ return -1;
+
+ case STATUS_EOF:
+ return 0;
+
+ default:
+ break;
+ }
+ MC_FALLTHROUGH;
+
+ /* Record of zeroes */
+ case STATUS_EOFMARK: /* If error after 0's */
+ MC_FALLTHROUGH;
+ /* exit from loop */
+ case STATUS_EOF: /* End of archive */
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+tar_super_check (const vfs_path_t * vpath)
+{
+ static struct stat stat_buf;
+ int stat_result;
+
+ stat_result = mc_stat (vpath, &stat_buf);
+
+ return (stat_result != 0) ? NULL : &stat_buf;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+tar_super_same (const vfs_path_element_t * vpath_element, struct vfs_s_super *parc,
+ const vfs_path_t * vpath, void *cookie)
+{
+ struct stat *archive_stat = cookie; /* stat of main archive */
+
+ (void) vpath_element;
+
+ if (strcmp (parc->name, vfs_path_as_str (vpath)) != 0)
+ return 0;
+
+ /* Has the cached archive been changed on the disk? */
+ if (parc != NULL && TAR_SUPER (parc)->st.st_mtime < archive_stat->st_mtime)
+ {
+ /* Yes, reload! */
+ vfs_tarfs_ops->free ((vfsid) parc);
+ vfs_rmstamp (vfs_tarfs_ops, (vfsid) parc);
+ return 2;
+ }
+ /* Hasn't been modified, give it a new timeout */
+ vfs_stamp (vfs_tarfs_ops, (vfsid) parc);
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+tar_read (void *fh, char *buffer, size_t count)
+{
+ struct vfs_class *me = VFS_FILE_HANDLER_SUPER (fh)->me;
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+ off_t begin = file->ino->data_offset;
+ int fd = TAR_SUPER (VFS_FILE_HANDLER_SUPER (fh))->fd;
+ ssize_t res;
+
+ if (mc_lseek (fd, begin + file->pos, SEEK_SET) != begin + file->pos)
+ ERRNOR (EIO, -1);
+
+ count = MIN (count, (size_t) (file->ino->st.st_size - file->pos));
+
+ res = mc_read (fd, buffer, count);
+ if (res == -1)
+ ERRNOR (errno, -1);
+
+ file->pos += res;
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+tar_fh_open (struct vfs_class *me, vfs_file_handler_t * fh, int flags, mode_t mode)
+{
+ (void) fh;
+ (void) mode;
+
+ if ((flags & O_ACCMODE) != O_RDONLY)
+ ERRNOR (EROFS, -1);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_init_tarfs (void)
+{
+ /* FIXME: tarfs used own temp files */
+ vfs_init_subclass (&tarfs_subclass, "tarfs", VFSF_READONLY, "utar");
+ vfs_tarfs_ops->read = tar_read;
+ vfs_tarfs_ops->setctl = NULL;
+ tarfs_subclass.archive_check = tar_super_check;
+ tarfs_subclass.archive_same = tar_super_same;
+ tarfs_subclass.new_archive = tar_new_archive;
+ tarfs_subclass.open_archive = tar_open_archive;
+ tarfs_subclass.free_archive = tar_free_archive;
+ tarfs_subclass.fh_open = tar_fh_open;
+ vfs_register_class (vfs_tarfs_ops);
+}
+
+/* --------------------------------------------------------------------------------------------- */