2 Virtual File System: GNU Tar file system.
4 Copyright (C) 1995-2018
5 Free Software Foundation, Inc.
10 Slava Zanko <slavazanko@gmail.com>, 2013
12 This file is part of the Midnight Commander.
14 The Midnight Commander is free software: you can redistribute it
15 and/or modify it under the terms of the GNU General Public License as
16 published by the Free Software Foundation, either version 3 of the License,
17 or (at your option) any later version.
19 The Midnight Commander is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 GNU General Public License for more details.
24 You should have received a copy of the GNU General Public License
25 along with this program. If not, see <http://www.gnu.org/licenses/>.
30 * \brief Source: Virtual File System: GNU Tar file system
31 * \author Jakub Jelinek
32 * \author Pavel Machek
35 * Namespace: init_tarfs
39 #include <sys/types.h>
44 /* major() and minor() macros (among other things) defined here for hpux */
45 #include <sys/mknod.h>
48 #include "lib/global.h"
50 #include "lib/unixcompat.h" /* makedev() */
51 #include "lib/widget.h" /* message() */
53 #include "lib/vfs/vfs.h"
54 #include "lib/vfs/utilvfs.h"
55 #include "lib/vfs/xdirentry.h"
56 #include "lib/vfs/gc.h" /* vfs_rmstamp */
60 /*** global variables ****************************************************************************/
62 /*** file scope macro definitions ****************************************************************/
65 * Header block on tape.
67 * I'm going to use traditional DP naming conventions here.
68 * A "block" is a big chunk of stuff that we do I/O on.
69 * A "record" is a piece of info that we care about.
70 * Typically many "record"s fit into a "block".
72 #define RECORDSIZE 512
74 #define PREFIX_SIZE 155
77 #define SPARSE_EXT_HDR 21
78 #define SPARSE_IN_HDR 4
80 /* The checksum field is filled with this while the checksum is computed. */
81 #define CHKBLANKS " " /* 8 blanks, no null */
83 /* The magic field is filled with this if uname and gname are valid. */
84 #define TMAGIC "ustar" /* ustar and a null */
85 #define OLDGNU_MAGIC "ustar " /* 7 chars and a null */
87 /* The linkflag defines the type of file */
88 #define LF_OLDNORMAL '\0' /* Normal disk file, Unix compat */
89 #define LF_NORMAL '0' /* Normal disk file */
90 #define LF_LINK '1' /* Link to previously dumped file */
91 #define LF_SYMLINK '2' /* Symbolic link */
92 #define LF_CHR '3' /* Character special file */
93 #define LF_BLK '4' /* Block special file */
94 #define LF_DIR '5' /* Directory */
95 #define LF_FIFO '6' /* FIFO special file */
96 #define LF_CONTIG '7' /* Contiguous file */
97 #define LF_EXTHDR 'x' /* pax Extended Header */
98 #define LF_GLOBAL_EXTHDR 'g' /* pax Global Extended Header */
99 /* Further link types may be defined later. */
101 /* Note that the standards committee allows only capital A through
102 capital Z for user-defined expansion. This means that defining something
103 as, say '8' is a *bad* idea. */
104 #define LF_DUMPDIR 'D' /* This is a dir entry that contains
105 the names of files that were in
106 the dir at the time the dump
108 #define LF_LONGLINK 'K' /* Identifies the NEXT file on the tape
109 as having a long linkname */
110 #define LF_LONGNAME 'L' /* Identifies the NEXT file on the tape
111 as having a long name. */
112 #define LF_MULTIVOL 'M' /* This is the continuation
113 of a file that began on another
115 #define LF_NAMES 'N' /* For storing filenames that didn't
116 fit in 100 characters */
117 #define LF_SPARSE 'S' /* This is for sparse files */
118 #define LF_VOLHDR 'V' /* This file is a tape/volume header */
119 /* Ignore it on extraction */
122 * Exit codes from the "tar" program
124 #define EX_SUCCESS 0 /* success! */
125 #define EX_ARGSBAD 1 /* invalid args */
126 #define EX_BADFILE 2 /* invalid filename */
127 #define EX_BADARCH 3 /* bad archive */
128 #define EX_SYSTEM 4 /* system gave unexpected error */
129 #define EX_BADVOL 5 /* Special error code means
130 Tape volume doesn't match the one
131 specified on the command line */
133 #define isodigit(c) ( ((c) >= '0') && ((c) <= '7') )
135 /*** file scope type declarations ****************************************************************/
148 /* cppcheck-suppress unusedStructMember */
150 /* cppcheck-suppress unusedStructMember */
156 char charptr
[RECORDSIZE
];
159 char arch_name
[NAMSIZ
];
167 char arch_linkname
[NAMSIZ
];
173 /* The following bytes of the tar header record were originally unused.
175 Archives following the ustar specification use almost all of those
176 bytes to support pathnames of 256 characters in length.
178 GNU tar archives use the "unused" space to support incremental
179 archives and sparse files. */
182 char prefix
[PREFIX_SIZE
];
183 /* GNU extensions to the ustar (POSIX.1-1988) archive format. */
188 /* cppcheck-suppress unusedStructMember */
190 /* cppcheck-suppress unusedStructMember */
192 /* cppcheck-suppress unusedStructMember */
194 struct sparse sp
[SPARSE_IN_HDR
];
196 /* cppcheck-suppress unusedStructMember */
197 char realsize
[12]; /* true size of the sparse file */
201 struct extended_header
203 struct sparse sp
[21];
220 int type
; /* Type of the archive */
223 /*** file scope variables ************************************************************************/
225 static struct vfs_s_subclass tarfs_subclass
;
226 static struct vfs_class
*vfs_tarfs_ops
= (struct vfs_class
*) &tarfs_subclass
;
228 /* As we open one archive at a time, it is safe to have this static */
229 static off_t current_tar_position
= 0;
231 static union record rec_buf
;
233 /*** file scope functions ************************************************************************/
234 /* --------------------------------------------------------------------------------------------- */
236 * Quick and dirty octal conversion.
238 * Result is -1 if the field is invalid (all blank, or nonoctal).
241 tar_from_oct (int digs
, const char *where
)
245 while (isspace ((unsigned char) *where
))
249 return -1; /* All blank field */
252 while (digs
> 0 && isodigit (*where
))
253 { /* Scan till nonoctal */
254 value
= (value
<< 3) | (*where
++ - '0');
258 if (digs
> 0 && *where
&& !isspace ((unsigned char) *where
))
259 return -1; /* Ended on non-space/nul */
264 /* --------------------------------------------------------------------------------------------- */
267 tar_free_archive (struct vfs_class
*me
, struct vfs_s_super
*archive
)
271 if (archive
->data
!= NULL
)
273 tar_super_data_t
*arch
= (tar_super_data_t
*) archive
->data
;
277 g_free (archive
->data
);
281 /* --------------------------------------------------------------------------------------------- */
283 /* Returns fd of the open tar file */
285 tar_open_archive_int (struct vfs_class
*me
, const vfs_path_t
* vpath
, struct vfs_s_super
*archive
)
288 tar_super_data_t
*arch
;
290 struct vfs_s_inode
*root
;
292 result
= mc_open (vpath
, O_RDONLY
);
295 message (D_ERROR
, MSG_ERROR
, _("Cannot open tar archive\n%s"), vfs_path_as_str (vpath
));
299 archive
->name
= g_strdup (vfs_path_as_str (vpath
));
300 archive
->data
= g_new (tar_super_data_t
, 1);
301 arch
= (tar_super_data_t
*) archive
->data
;
302 mc_stat (vpath
, &arch
->st
);
304 arch
->type
= TAR_UNKNOWN
;
306 /* Find out the method to handle this tar file */
307 type
= get_compression_type (result
, archive
->name
);
308 if (type
== COMPRESSION_NONE
)
309 mc_lseek (result
, 0, SEEK_SET
);
313 vfs_path_t
*tmp_vpath
;
316 s
= g_strconcat (archive
->name
, decompress_extension (type
), (char *) NULL
);
317 tmp_vpath
= vfs_path_from_str_flags (s
, VPF_NO_CANON
);
318 result
= mc_open (tmp_vpath
, O_RDONLY
);
319 vfs_path_free (tmp_vpath
);
321 message (D_ERROR
, MSG_ERROR
, _("Cannot open tar archive\n%s"), s
);
325 MC_PTR_FREE (archive
->name
);
331 mode
= arch
->st
.st_mode
& 07777;
340 root
= vfs_s_new_inode (me
, archive
, &arch
->st
);
341 root
->st
.st_mode
= mode
;
342 root
->data_offset
= -1;
344 root
->st
.st_dev
= MEDATA
->rdev
++;
346 archive
->root
= root
;
351 /* --------------------------------------------------------------------------------------------- */
353 static union record
*
354 tar_get_next_record (struct vfs_s_super
*archive
, int tard
)
360 n
= mc_read (tard
, rec_buf
.charptr
, sizeof (rec_buf
.charptr
));
361 if (n
!= sizeof (rec_buf
.charptr
))
362 return NULL
; /* An error has occurred */
363 current_tar_position
+= sizeof (rec_buf
.charptr
);
367 /* --------------------------------------------------------------------------------------------- */
370 tar_skip_n_records (struct vfs_s_super
*archive
, int tard
, size_t n
)
374 mc_lseek (tard
, n
* sizeof (rec_buf
.charptr
), SEEK_CUR
);
375 current_tar_position
+= n
* sizeof (rec_buf
.charptr
);
378 /* --------------------------------------------------------------------------------------------- */
381 tar_checksum (const union record
*header
)
387 const char *p
= header
->charptr
;
389 recsum
= tar_from_oct (8, header
->header
.chksum
);
391 for (i
= sizeof (*header
); --i
>= 0;)
394 * We can't use unsigned char here because of old compilers,
401 /* Adjust checksum to count the "chksum" field as blanks. */
402 for (i
= sizeof (header
->header
.chksum
); --i
>= 0;)
404 sum
-= 0xFF & header
->header
.chksum
[i
];
405 signed_sum
-= (char) header
->header
.chksum
[i
];
408 sum
+= ' ' * sizeof (header
->header
.chksum
);
409 signed_sum
+= ' ' * sizeof (header
->header
.chksum
);
412 * This is a zeroed record...whole record is 0's except
413 * for the 8 blanks we faked for the checksum field.
416 return STATUS_EOFMARK
;
418 if (sum
!= recsum
&& signed_sum
!= recsum
)
419 return STATUS_BADCHECKSUM
;
421 return STATUS_SUCCESS
;
424 /* --------------------------------------------------------------------------------------------- */
427 tar_fill_stat (struct vfs_s_super
*archive
, struct stat
*st
, union record
*header
, size_t h_size
)
429 tar_super_data_t
*arch
= (tar_super_data_t
*) archive
->data
;
431 st
->st_mode
= tar_from_oct (8, header
->header
.mode
);
433 /* Adjust st->st_mode because there are tar-files with
434 * linkflag==LF_SYMLINK and S_ISLNK(mod)==0. I don't
435 * know about the other modes but I think I cause no new
436 * problem when I adjust them, too. -- Norbert.
438 if (header
->header
.linkflag
== LF_DIR
|| header
->header
.linkflag
== LF_DUMPDIR
)
439 st
->st_mode
|= S_IFDIR
;
440 else if (header
->header
.linkflag
== LF_SYMLINK
)
441 st
->st_mode
|= S_IFLNK
;
442 else if (header
->header
.linkflag
== LF_CHR
)
443 st
->st_mode
|= S_IFCHR
;
444 else if (header
->header
.linkflag
== LF_BLK
)
445 st
->st_mode
|= S_IFBLK
;
446 else if (header
->header
.linkflag
== LF_FIFO
)
447 st
->st_mode
|= S_IFIFO
;
449 st
->st_mode
|= S_IFREG
;
452 #ifdef HAVE_STRUCT_STAT_ST_RDEV
462 st
->st_uid
= *header
->header
.uname
463 ? vfs_finduid (header
->header
.uname
)
464 : tar_from_oct (8, header
->header
.uid
);
465 st
->st_gid
= *header
->header
.gname
466 ? vfs_findgid (header
->header
.gname
)
467 : tar_from_oct (8,header
->header
.gid
);
470 switch (header
->header
.linkflag
)
474 #ifdef HAVE_STRUCT_STAT_ST_RDEV
476 makedev (tar_from_oct (8, header
->header
.devmajor
),
477 tar_from_oct (8, header
->header
.devminor
));
486 st
->st_uid
= tar_from_oct (8, header
->header
.uid
);
487 st
->st_gid
= tar_from_oct (8, header
->header
.gid
);
491 st
->st_size
= h_size
;
492 #ifdef HAVE_STRUCT_STAT_ST_MTIM
493 st
->st_atim
.tv_nsec
= st
->st_mtim
.tv_nsec
= st
->st_ctim
.tv_nsec
= 0;
495 st
->st_mtime
= tar_from_oct (1 + 12, header
->header
.mtime
);
498 if (arch
->type
== TAR_GNU
)
500 st
->st_atime
= tar_from_oct (1 + 12, header
->header
.unused
.oldgnu
.atime
);
501 st
->st_ctime
= tar_from_oct (1 + 12, header
->header
.unused
.oldgnu
.ctime
);
504 #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
505 st
->st_blksize
= 8 * 1024; /* FIXME */
507 vfs_adjust_stat (st
);
510 /* --------------------------------------------------------------------------------------------- */
512 * Return 1 for success, 0 if the checksum is bad, EOF on eof,
513 * 2 for a record full of zeros (EOF marker).
517 tar_read_header (struct vfs_class
*me
, struct vfs_s_super
*archive
, int tard
, size_t * h_size
)
519 tar_super_data_t
*arch
= (tar_super_data_t
*) archive
->data
;
520 ReadStatus checksum_status
;
521 union record
*header
;
522 static char *next_long_name
= NULL
, *next_long_link
= NULL
;
526 header
= tar_get_next_record (archive
, tard
);
530 checksum_status
= tar_checksum (header
);
531 if (checksum_status
!= STATUS_SUCCESS
)
532 return checksum_status
;
535 * Try to determine the archive format.
537 if (arch
->type
== TAR_UNKNOWN
)
539 if (strcmp (header
->header
.magic
, TMAGIC
) == 0)
541 if (header
->header
.linkflag
== LF_GLOBAL_EXTHDR
)
542 arch
->type
= TAR_POSIX
;
544 arch
->type
= TAR_USTAR
;
546 else if (strcmp (header
->header
.magic
, OLDGNU_MAGIC
) == 0)
547 arch
->type
= TAR_GNU
;
551 * linkflag on BSDI tar (pax) always '\000'
553 if (header
->header
.linkflag
== '\000')
557 if (header
->header
.arch_name
[NAMSIZ
- 1] != '\0')
560 len
= strlen (header
->header
.arch_name
);
562 if (len
!= 0 && IS_PATH_SEP (header
->header
.arch_name
[len
- 1]))
563 header
->header
.linkflag
= LF_DIR
;
567 * Good record. Decode file size and return.
569 if (header
->header
.linkflag
== LF_LINK
|| header
->header
.linkflag
== LF_DIR
)
570 *h_size
= 0; /* Links 0 size on tape */
572 *h_size
= tar_from_oct (1 + 12, header
->header
.size
);
574 if (header
->header
.linkflag
== LF_DUMPDIR
)
576 if (arch
->type
== TAR_UNKNOWN
)
577 arch
->type
= TAR_GNU
;
581 * Skip over pax extended header and global extended
584 if (header
->header
.linkflag
== LF_EXTHDR
|| header
->header
.linkflag
== LF_GLOBAL_EXTHDR
)
586 if (arch
->type
== TAR_UNKNOWN
)
587 arch
->type
= TAR_POSIX
;
588 return STATUS_SUCCESS
;
591 if (header
->header
.linkflag
== LF_LONGNAME
|| header
->header
.linkflag
== LF_LONGLINK
)
598 if (arch
->type
== TAR_UNKNOWN
)
599 arch
->type
= TAR_GNU
;
601 if (*h_size
> MC_MAXPATHLEN
)
603 message (D_ERROR
, MSG_ERROR
, _("Inconsistent tar archive"));
604 return STATUS_BADCHECKSUM
;
607 longp
= ((header
->header
.linkflag
== LF_LONGNAME
) ? &next_long_name
: &next_long_link
);
610 bp
= *longp
= g_malloc (*h_size
+ 1);
612 for (size
= *h_size
; size
> 0; size
-= written
)
614 data
= tar_get_next_record (archive
, tard
)->charptr
;
617 MC_PTR_FREE (*longp
);
618 message (D_ERROR
, MSG_ERROR
, _("Unexpected EOF on archive file"));
619 return STATUS_BADCHECKSUM
;
621 written
= RECORDSIZE
;
622 if ((off_t
) written
> size
)
623 written
= (size_t) size
;
625 memcpy (bp
, data
, written
);
629 if (bp
- *longp
== MC_MAXPATHLEN
&& bp
[-1] != '\0')
631 MC_PTR_FREE (*longp
);
632 message (D_ERROR
, MSG_ERROR
, _("Inconsistent tar archive"));
633 return STATUS_BADCHECKSUM
;
641 struct vfs_s_entry
*entry
;
642 struct vfs_s_inode
*inode
= NULL
, *parent
;
646 char *current_file_name
, *current_link_name
;
649 (next_long_link
? next_long_link
: g_strndup (header
->header
.arch_linkname
, NAMSIZ
));
650 len
= strlen (current_link_name
);
651 if (len
> 1 && IS_PATH_SEP (current_link_name
[len
- 1]))
652 current_link_name
[len
- 1] = '\0';
654 current_file_name
= NULL
;
659 /* The ustar archive format supports pathnames of upto 256
660 * characters in length. This is achieved by concatenating
661 * the contents of the 'prefix' and 'arch_name' fields like
664 * prefix + path_separator + arch_name
666 * If the 'prefix' field contains an empty string i.e. its
667 * first characters is '\0' the prefix field is ignored.
669 if (header
->header
.unused
.prefix
[0] != '\0')
671 char *temp_name
, *temp_prefix
;
673 temp_name
= g_strndup (header
->header
.arch_name
, NAMSIZ
);
674 temp_prefix
= g_strndup (header
->header
.unused
.prefix
, PREFIX_SIZE
);
675 current_file_name
= g_strconcat (temp_prefix
, PATH_SEP_STR
,
676 temp_name
, (char *) NULL
);
678 g_free (temp_prefix
);
682 if (next_long_name
!= NULL
)
683 current_file_name
= next_long_name
;
689 if (current_file_name
== NULL
)
691 if (next_long_name
!= NULL
)
692 current_file_name
= g_strdup (next_long_name
);
694 current_file_name
= g_strndup (header
->header
.arch_name
, NAMSIZ
);
697 canonicalize_pathname (current_file_name
);
698 len
= strlen (current_file_name
);
700 data_position
= current_tar_position
;
702 p
= strrchr (current_file_name
, PATH_SEP
);
705 p
= current_file_name
;
706 q
= current_file_name
+ len
; /* "" */
711 q
= current_file_name
;
714 parent
= vfs_s_find_inode (me
, archive
, q
, LINK_NO_FOLLOW
, FL_MKDIR
);
717 message (D_ERROR
, MSG_ERROR
, _("Inconsistent tar archive"));
718 return STATUS_BADCHECKSUM
;
721 if (header
->header
.linkflag
== LF_LINK
)
723 inode
= vfs_s_find_inode (me
, archive
, current_link_name
, LINK_NO_FOLLOW
, FL_NONE
);
726 message (D_ERROR
, MSG_ERROR
, _("Inconsistent tar archive"));
730 entry
= vfs_s_new_entry (me
, p
, inode
);
731 vfs_s_insert_entry (me
, parent
, entry
);
732 g_free (current_link_name
);
737 tar_fill_stat (archive
, &st
, header
, *h_size
);
738 if (S_ISDIR (st
.st_mode
))
740 entry
= MEDATA
->find_entry (me
, parent
, p
, LINK_NO_FOLLOW
, FL_NONE
);
744 inode
= vfs_s_new_inode (me
, archive
, &st
);
746 inode
->data_offset
= data_position
;
747 if (*current_link_name
)
749 inode
->linkname
= current_link_name
;
751 else if (current_link_name
!= next_long_link
)
753 g_free (current_link_name
);
755 entry
= vfs_s_new_entry (me
, p
, inode
);
757 vfs_s_insert_entry (me
, parent
, entry
);
758 g_free (current_file_name
);
761 next_long_link
= next_long_name
= NULL
;
763 if (arch
->type
== TAR_GNU
&& header
->header
.unused
.oldgnu
.isextended
)
765 while (tar_get_next_record (archive
, tard
)->ext_hdr
.isextended
!= 0)
769 inode
->data_offset
= current_tar_position
;
771 return STATUS_SUCCESS
;
775 /* --------------------------------------------------------------------------------------------- */
777 * Main loop for reading an archive.
778 * Returns 0 on success, -1 on error.
781 tar_open_archive (struct vfs_s_super
*archive
, const vfs_path_t
* vpath
,
782 const vfs_path_element_t
* vpath_element
)
784 /* Initial status at start of archive */
785 ReadStatus status
= STATUS_EOFMARK
;
788 current_tar_position
= 0;
789 /* Open for reading */
790 tard
= tar_open_archive_int (vpath_element
->class, vpath
, archive
);
797 ReadStatus prev_status
= status
;
799 status
= tar_read_header (vpath_element
->class, archive
, tard
, &h_size
);
804 tar_skip_n_records (archive
, tard
, (h_size
+ RECORDSIZE
- 1) / RECORDSIZE
);
810 * If the previous header was good, tell them
811 * that we are skipping bad ones.
813 case STATUS_BADCHECKSUM
:
816 /* Error on first record */
819 message (D_ERROR
, MSG_ERROR
, _("%s\ndoesn't look like a tar archive."),
820 vfs_path_as_str (vpath
));
823 /* Error after header rec */
826 /* Error after error */
828 case STATUS_BADCHECKSUM
:
838 /* Record of zeroes */
839 case STATUS_EOFMARK
: /* If error after 0's */
842 case STATUS_EOF
: /* End of archive */
852 /* --------------------------------------------------------------------------------------------- */
855 tar_super_check (const vfs_path_t
* vpath
)
857 static struct stat stat_buf
;
860 stat_result
= mc_stat (vpath
, &stat_buf
);
862 return (stat_result
!= 0) ? NULL
: &stat_buf
;
865 /* --------------------------------------------------------------------------------------------- */
868 tar_super_same (const vfs_path_element_t
* vpath_element
, struct vfs_s_super
*parc
,
869 const vfs_path_t
* vpath
, void *cookie
)
871 struct stat
*archive_stat
= cookie
; /* stat of main archive */
873 (void) vpath_element
;
875 if (strcmp (parc
->name
, vfs_path_as_str (vpath
)) != 0)
878 /* Has the cached archive been changed on the disk? */
879 if (((tar_super_data_t
*) parc
->data
)->st
.st_mtime
< archive_stat
->st_mtime
)
882 vfs_tarfs_ops
->free ((vfsid
) parc
);
883 vfs_rmstamp (vfs_tarfs_ops
, (vfsid
) parc
);
886 /* Hasn't been modified, give it a new timeout */
887 vfs_stamp (vfs_tarfs_ops
, (vfsid
) parc
);
891 /* --------------------------------------------------------------------------------------------- */
894 tar_read (void *fh
, char *buffer
, size_t count
)
896 off_t begin
= FH
->ino
->data_offset
;
897 int fd
= ((tar_super_data_t
*) FH_SUPER
->data
)->fd
;
898 struct vfs_class
*me
= FH_SUPER
->me
;
901 if (mc_lseek (fd
, begin
+ FH
->pos
, SEEK_SET
) != begin
+ FH
->pos
)
904 count
= MIN (count
, (size_t) (FH
->ino
->st
.st_size
- FH
->pos
));
906 res
= mc_read (fd
, buffer
, count
);
914 /* --------------------------------------------------------------------------------------------- */
917 tar_fh_open (struct vfs_class
*me
, vfs_file_handler_t
* fh
, int flags
, mode_t mode
)
922 if ((flags
& O_ACCMODE
) != O_RDONLY
)
927 /* --------------------------------------------------------------------------------------------- */
928 /*** public functions ****************************************************************************/
929 /* --------------------------------------------------------------------------------------------- */
934 tarfs_subclass
.flags
= VFS_S_READONLY
; /* FIXME: tarfs used own temp files */
935 tarfs_subclass
.archive_check
= tar_super_check
;
936 tarfs_subclass
.archive_same
= tar_super_same
;
937 tarfs_subclass
.open_archive
= tar_open_archive
;
938 tarfs_subclass
.free_archive
= tar_free_archive
;
939 tarfs_subclass
.fh_open
= tar_fh_open
;
941 vfs_s_init_class (&tarfs_subclass
);
942 vfs_tarfs_ops
->name
= "tarfs";
943 vfs_tarfs_ops
->prefix
= "utar";
944 vfs_tarfs_ops
->read
= tar_read
;
945 vfs_tarfs_ops
->setctl
= NULL
;
946 vfs_register_class (vfs_tarfs_ops
);
949 /* --------------------------------------------------------------------------------------------- */