diff --git a/vfs/ChangeLog b/vfs/ChangeLog index 95a873c3d..c8a40966e 100644 --- a/vfs/ChangeLog +++ b/vfs/ChangeLog @@ -1,3 +1,24 @@ +2006-04-17 Pavel Tsekov + + * tar.c (PREFIX_SIZE): New macro definition. + (union unused): New union which describes better the trailing bytes + of the tar record header. + (union record): Do not assume that all tar archives follow the GNU + tar archive format. + (tar_open_archive_int): Initialize the variable holding the type fo the + archive format. + (tar_read_header): Try to determine the archive format using the + `magic' field of the tar record header. Use the `linkflag' field + as a fallback. + Add support for pathnames of upto 256 characters as stored in ustar + archives. + Do not access GNU specific fields of the tar record header if the + archive is not a GNU one. + (tar_fill_stat): Pass pointer to vfs_s_super instead of pointer ot + vfs_class as the first argument. + Do not access GNU specific fields of the tar record header if the + archive is not a GNU one. + 2006-04-14 Pavel Tsekov * tar.c (OLDGNU_MAGIC): New macro definition. diff --git a/vfs/tar.c b/vfs/tar.c index 60d8241d0..6adeafedd 100644 --- a/vfs/tar.c +++ b/vfs/tar.c @@ -40,6 +40,14 @@ static struct vfs_class vfs_tarfs_ops; +enum { + TAR_UNKNOWN = 0, + TAR_V7, + TAR_USTAR, + TAR_POSIX, + TAR_GNU +}; + /* * Header block on tape. * @@ -50,6 +58,7 @@ static struct vfs_class vfs_tarfs_ops; */ #define RECORDSIZE 512 #define NAMSIZ 100 +#define PREFIX_SIZE 155 #define TUNMLEN 32 #define TGNMLEN 32 #define SPARSE_EXT_HDR 21 @@ -82,18 +91,27 @@ union record { char gname[TGNMLEN]; char devmajor[8]; char devminor[8]; - /* these following fields were added by JF for gnu */ - /* and are NOT standard */ - char atime[12]; - char ctime[12]; - char offset[12]; - char longnames[4]; - char pad; - struct sparse sp[SPARSE_IN_HDR]; - char isextended; - char realsize[12]; /* true size of the sparse file */ - /* char ending_blanks[12];*//* number of nulls at the - end of the file, if any */ + /* The following bytes of the tar header record were originally unused. + + Archives following the ustar specification use almost all of those + bytes to support pathnames of 256 characters in length. + + GNU tar archives use the "unused" space to support incremental + archives and sparse files. */ + union unused { + char prefix[PREFIX_SIZE]; + /* GNU extensions to the ustar (POSIX.1-1988) archive format. */ + struct oldgnu { + char atime[12]; + char ctime[12]; + char offset[12]; + char longnames[4]; + char pad; + struct sparse sp[SPARSE_IN_HDR]; + char isextended; + char realsize[12]; /* true size of the sparse file */ + } oldgnu; + } unused; } header; struct extended_header { struct sparse sp[21]; @@ -223,6 +241,7 @@ tar_open_archive_int (struct vfs_class *me, const char *name, archive->name = g_strdup (name); mc_stat (name, &(archive->u.arch.st)); archive->u.arch.fd = -1; + archive->u.arch.type = TAR_UNKNOWN; /* Find out the method to handle this tar file */ type = get_compression_type (result); @@ -285,15 +304,9 @@ static void tar_skip_n_records (struct vfs_s_super *archive, int tard, int n) } static void -tar_fill_stat (struct vfs_class *me, struct stat *st, union record *header, +tar_fill_stat (struct vfs_s_super *archive, struct stat *st, union record *header, size_t h_size) { - int is_gnu; - - (void) me; - - is_gnu = !strcmp (header->header.magic, OLDGNU_MAGIC); - st->st_mode = tar_from_oct (8, header->header.mode); /* Adjust st->st_mode because there are tar-files with @@ -315,7 +328,10 @@ tar_fill_stat (struct vfs_class *me, struct stat *st, union record *header, st->st_mode |= S_IFREG; st->st_rdev = 0; - if (!strcmp (header->header.magic, TMAGIC) || is_gnu != 0) { + switch (archive->u.arch.type) { + case TAR_USTAR: + case TAR_POSIX: + case TAR_GNU: st->st_uid = *header->header.uname ? vfs_finduid (header->header. uname) : tar_from_oct (8, @@ -335,14 +351,20 @@ tar_fill_stat (struct vfs_class *me, struct stat *st, union record *header, (tar_from_oct (8, header->header.devmajor) << 8) | tar_from_oct (8, header->header.devminor); } - } else { /* Old Unix tar */ + default: st->st_uid = tar_from_oct (8, header->header.uid); st->st_gid = tar_from_oct (8, header->header.gid); } st->st_size = h_size; st->st_mtime = tar_from_oct (1 + 12, header->header.mtime); - st->st_atime = tar_from_oct (1 + 12, header->header.atime); - st->st_ctime = tar_from_oct (1 + 12, header->header.ctime); + st->st_atime = 0; + st->st_ctime = 0; + if (archive->u.arch.type == TAR_GNU) { + st->st_atime = tar_from_oct (1 + 12, + header->header.unused.oldgnu.atime); + st->st_ctime = tar_from_oct (1 + 12, + header->header.unused.oldgnu.ctime); + } } @@ -405,6 +427,20 @@ tar_read_header (struct vfs_class *me, struct vfs_s_super *archive, if (sum != recsum && signed_sum != recsum) return STATUS_BADCHECKSUM; + /* + * Try to determine the archive format. + */ + if (archive->u.arch.type == TAR_UNKNOWN) { + if (!strcmp (header->header.magic, TMAGIC)) { + if (header->header.linkflag == LF_GLOBAL_EXTHDR) + archive->u.arch.type = TAR_POSIX; + else + archive->u.arch.type = TAR_USTAR; + } else if (!strcmp (header->header.magic, OLDGNU_MAGIC)) { + archive->u.arch.type = TAR_GNU; + } + } + /* * linkflag on BSDI tar (pax) always '\000' */ @@ -431,16 +467,22 @@ tar_read_header (struct vfs_class *me, struct vfs_s_super *archive, * Skip over directory snapshot info records that * are stored in incremental tar archives. */ - if (header->header.linkflag == LF_DUMPDIR) + if (header->header.linkflag == LF_DUMPDIR) { + if (archive->u.arch.type == TAR_UNKNOWN) + archive->u.arch.type = TAR_GNU; return STATUS_SUCCESS; + } /* * Skip over pax extended header and global extended * header records. */ if (header->header.linkflag == LF_EXTHDR || - header->header.linkflag == LF_GLOBAL_EXTHDR) + header->header.linkflag == LF_GLOBAL_EXTHDR) { + if (archive->u.arch.type == TAR_UNKNOWN) + archive->u.arch.type = TAR_POSIX; return STATUS_SUCCESS; + } if (header->header.linkflag == LF_LONGNAME || header->header.linkflag == LF_LONGLINK) { @@ -448,6 +490,9 @@ tar_read_header (struct vfs_class *me, struct vfs_s_super *archive, char *bp, *data; int size, written; + if (archive->u.arch.type == TAR_UNKNOWN) + archive->u.arch.type = TAR_GNU; + if (*h_size > MC_MAXPATHLEN) { message (1, MSG_ERROR, _("Inconsistent tar archive")); return STATUS_BADCHECKSUM; @@ -500,9 +545,43 @@ tar_read_header (struct vfs_class *me, struct vfs_s_super *archive, if (len > 1 && current_link_name[len - 1] == '/') current_link_name[len - 1] = 0; - current_file_name = - (next_long_name ? next_long_name : - g_strndup (header->header.arch_name, NAMSIZ)); + current_file_name = NULL; + switch (archive->u.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 `arch_name' fields like + * this: + * + * prefix + path_separator + arch_name + * + * If the `prefix' field contains an empty string i.e. its + * first characters is '\0' the prefix field is ignored. + */ + if (header->header.unused.prefix[0] != '\0') { + char *temp_name, *temp_prefix; + + temp_name = g_strndup (header->header.arch_name, NAMSIZ); + temp_prefix = g_strndup (header->header.unused.prefix, + PREFIX_SIZE); + 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) + current_file_name = g_strndup (header->header.arch_name, NAMSIZ); + canonicalize_pathname (current_file_name); len = strlen (current_file_name); @@ -538,7 +617,7 @@ tar_read_header (struct vfs_class *me, struct vfs_s_super *archive, } } - tar_fill_stat (me, &st, header, *h_size); + tar_fill_stat (archive, &st, header, *h_size); inode = vfs_s_new_inode (me, archive, &st); inode->data_offset = data_position; @@ -555,7 +634,8 @@ tar_read_header (struct vfs_class *me, struct vfs_s_super *archive, done: next_long_link = next_long_name = NULL; - if (header->header.isextended) { + if (archive->u.arch.type == TAR_GNU && + header->header.unused.oldgnu.isextended) { while (tar_get_next_record (archive, tard)->ext_hdr. isextended); inode->data_offset = current_tar_position;