Reverted the use of bool in favour of gboolean
[midnight-commander.git] / vfs / tar.c
blob5a65d55f0556ae8dcb5430b3678e5bba3de8bfd1
1 /* Virtual File System: GNU Tar file system.
2 Copyright (C) 1995, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
3 2006, 2007 Free Software Foundation, Inc.
5 Written by: 1995 Jakub Jelinek
6 Rewritten by: 1998 Pavel Machek
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Library General Public License
10 as published by the Free Software Foundation; either version 2 of
11 the License, or (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU Library General Public License for more details.
18 You should have received a copy of the GNU Library General Public
19 License along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
22 /* Namespace: init_tarfs */
24 #include <config.h>
25 #include <sys/types.h>
26 #include <errno.h>
27 #include <ctype.h>
29 #ifdef hpux
30 /* major() and minor() macros (among other things) defined here for hpux */
31 #include <sys/mknod.h>
32 #endif
34 #include "../src/global.h"
35 #include "../src/tty.h" /* enable/disable interrupt key */
36 #include "../src/wtools.h" /* message() */
37 #include "../src/main.h" /* print_vfs_message */
38 #include "utilvfs.h"
39 #include "gc.h" /* vfs_rmstamp */
40 #include "xdirentry.h"
42 static struct vfs_class vfs_tarfs_ops;
44 enum {
45 TAR_UNKNOWN = 0,
46 TAR_V7,
47 TAR_USTAR,
48 TAR_POSIX,
49 TAR_GNU
53 * Header block on tape.
55 * I'm going to use traditional DP naming conventions here.
56 * A "block" is a big chunk of stuff that we do I/O on.
57 * A "record" is a piece of info that we care about.
58 * Typically many "record"s fit into a "block".
60 #define RECORDSIZE 512
61 #define NAMSIZ 100
62 #define PREFIX_SIZE 155
63 #define TUNMLEN 32
64 #define TGNMLEN 32
65 #define SPARSE_EXT_HDR 21
66 #define SPARSE_IN_HDR 4
68 struct sparse {
69 char offset[12];
70 char numbytes[12];
73 struct sp_array {
74 int offset;
75 int numbytes;
78 union record {
79 char charptr[RECORDSIZE];
80 struct header {
81 char arch_name[NAMSIZ];
82 char mode[8];
83 char uid[8];
84 char gid[8];
85 char size[12];
86 char mtime[12];
87 char chksum[8];
88 char linkflag;
89 char arch_linkname[NAMSIZ];
90 char magic[8];
91 char uname[TUNMLEN];
92 char gname[TGNMLEN];
93 char devmajor[8];
94 char devminor[8];
95 /* The following bytes of the tar header record were originally unused.
97 Archives following the ustar specification use almost all of those
98 bytes to support pathnames of 256 characters in length.
100 GNU tar archives use the "unused" space to support incremental
101 archives and sparse files. */
102 union unused {
103 char prefix[PREFIX_SIZE];
104 /* GNU extensions to the ustar (POSIX.1-1988) archive format. */
105 struct oldgnu {
106 char atime[12];
107 char ctime[12];
108 char offset[12];
109 char longnames[4];
110 char pad;
111 struct sparse sp[SPARSE_IN_HDR];
112 char isextended;
113 char realsize[12]; /* true size of the sparse file */
114 } oldgnu;
115 } unused;
116 } header;
117 struct extended_header {
118 struct sparse sp[21];
119 char isextended;
120 } ext_hdr;
123 /* The checksum field is filled with this while the checksum is computed. */
124 #define CHKBLANKS " " /* 8 blanks, no null */
126 /* The magic field is filled with this if uname and gname are valid. */
127 #define TMAGIC "ustar" /* ustar and a null */
128 #define OLDGNU_MAGIC "ustar " /* 7 chars and a null */
130 /* The linkflag defines the type of file */
131 #define LF_OLDNORMAL '\0' /* Normal disk file, Unix compat */
132 #define LF_NORMAL '0' /* Normal disk file */
133 #define LF_LINK '1' /* Link to previously dumped file */
134 #define LF_SYMLINK '2' /* Symbolic link */
135 #define LF_CHR '3' /* Character special file */
136 #define LF_BLK '4' /* Block special file */
137 #define LF_DIR '5' /* Directory */
138 #define LF_FIFO '6' /* FIFO special file */
139 #define LF_CONTIG '7' /* Contiguous file */
140 #define LF_EXTHDR 'x' /* pax Extended Header */
141 #define LF_GLOBAL_EXTHDR 'g' /* pax Global Extended Header */
142 /* Further link types may be defined later. */
144 /* Note that the standards committee allows only capital A through
145 capital Z for user-defined expansion. This means that defining something
146 as, say '8' is a *bad* idea. */
147 #define LF_DUMPDIR 'D' /* This is a dir entry that contains
148 the names of files that were in
149 the dir at the time the dump
150 was made */
151 #define LF_LONGLINK 'K' /* Identifies the NEXT file on the tape
152 as having a long linkname */
153 #define LF_LONGNAME 'L' /* Identifies the NEXT file on the tape
154 as having a long name. */
155 #define LF_MULTIVOL 'M' /* This is the continuation
156 of a file that began on another
157 volume */
158 #define LF_NAMES 'N' /* For storing filenames that didn't
159 fit in 100 characters */
160 #define LF_SPARSE 'S' /* This is for sparse files */
161 #define LF_VOLHDR 'V' /* This file is a tape/volume header */
162 /* Ignore it on extraction */
165 * Exit codes from the "tar" program
167 #define EX_SUCCESS 0 /* success! */
168 #define EX_ARGSBAD 1 /* invalid args */
169 #define EX_BADFILE 2 /* invalid filename */
170 #define EX_BADARCH 3 /* bad archive */
171 #define EX_SYSTEM 4 /* system gave unexpected error */
172 #define EX_BADVOL 5 /* Special error code means
173 Tape volume doesn't match the one
174 specified on the command line */
176 #define isodigit(c) ( ((c) >= '0') && ((c) <= '7') )
179 * Quick and dirty octal conversion.
181 * Result is -1 if the field is invalid (all blank, or nonoctal).
183 static long tar_from_oct (int digs, char *where)
185 register long value;
187 while (isspace ((unsigned char) *where)) { /* Skip spaces */
188 where++;
189 if (--digs <= 0)
190 return -1; /* All blank field */
192 value = 0;
193 while (digs > 0 && isodigit (*where)) { /* Scan till nonoctal */
194 value = (value << 3) | (*where++ - '0');
195 --digs;
198 if (digs > 0 && *where && !isspace ((unsigned char) *where))
199 return -1; /* Ended on non-space/nul */
201 return value;
204 static void tar_free_archive (struct vfs_class *me, struct vfs_s_super *archive)
206 (void) me;
208 if (archive->u.arch.fd != -1)
209 mc_close(archive->u.arch.fd);
212 /* As we open one archive at a time, it is safe to have this static */
213 static int current_tar_position = 0;
215 /* Returns fd of the open tar file */
216 static int
217 tar_open_archive_int (struct vfs_class *me, const char *name,
218 struct vfs_s_super *archive)
220 int result, type;
221 mode_t mode;
222 struct vfs_s_inode *root;
224 result = mc_open (name, O_RDONLY);
225 if (result == -1) {
226 message (D_ERROR, MSG_ERROR, _("Cannot open tar archive\n%s"), name);
227 ERRNOR (ENOENT, -1);
230 archive->name = g_strdup (name);
231 mc_stat (name, &(archive->u.arch.st));
232 archive->u.arch.fd = -1;
233 archive->u.arch.type = TAR_UNKNOWN;
235 /* Find out the method to handle this tar file */
236 type = get_compression_type (result);
237 mc_lseek (result, 0, SEEK_SET);
238 if (type != COMPRESSION_NONE) {
239 char *s;
240 mc_close (result);
241 s = g_strconcat (archive->name, decompress_extension (type), (char *) NULL);
242 result = mc_open (s, O_RDONLY);
243 if (result == -1)
244 message (D_ERROR, MSG_ERROR, _("Cannot open tar archive\n%s"), s);
245 g_free (s);
246 if (result == -1)
247 ERRNOR (ENOENT, -1);
250 archive->u.arch.fd = result;
251 mode = archive->u.arch.st.st_mode & 07777;
252 if (mode & 0400)
253 mode |= 0100;
254 if (mode & 0040)
255 mode |= 0010;
256 if (mode & 0004)
257 mode |= 0001;
258 mode |= S_IFDIR;
260 root = vfs_s_new_inode (me, archive, &archive->u.arch.st);
261 root->st.st_mode = mode;
262 root->data_offset = -1;
263 root->st.st_nlink++;
264 root->st.st_dev = MEDATA->rdev++;
266 archive->root = root;
268 return result;
271 static union record rec_buf;
273 static union record *
274 tar_get_next_record (struct vfs_s_super *archive, int tard)
276 int n;
278 (void) archive;
280 n = mc_read (tard, rec_buf.charptr, RECORDSIZE);
281 if (n != RECORDSIZE)
282 return NULL; /* An error has occurred */
283 current_tar_position += RECORDSIZE;
284 return &rec_buf;
287 static void tar_skip_n_records (struct vfs_s_super *archive, int tard, int n)
289 (void) archive;
291 mc_lseek (tard, n * RECORDSIZE, SEEK_CUR);
292 current_tar_position += n * RECORDSIZE;
295 static void
296 tar_fill_stat (struct vfs_s_super *archive, struct stat *st, union record *header,
297 size_t h_size)
299 st->st_mode = tar_from_oct (8, header->header.mode);
301 /* Adjust st->st_mode because there are tar-files with
302 * linkflag==LF_SYMLINK and S_ISLNK(mod)==0. I don't
303 * know about the other modes but I think I cause no new
304 * problem when I adjust them, too. -- Norbert.
306 if (header->header.linkflag == LF_DIR) {
307 st->st_mode |= S_IFDIR;
308 } else if (header->header.linkflag == LF_SYMLINK) {
309 st->st_mode |= S_IFLNK;
310 } else if (header->header.linkflag == LF_CHR) {
311 st->st_mode |= S_IFCHR;
312 } else if (header->header.linkflag == LF_BLK) {
313 st->st_mode |= S_IFBLK;
314 } else if (header->header.linkflag == LF_FIFO) {
315 st->st_mode |= S_IFIFO;
316 } else
317 st->st_mode |= S_IFREG;
319 st->st_rdev = 0;
320 switch (archive->u.arch.type) {
321 case TAR_USTAR:
322 case TAR_POSIX:
323 case TAR_GNU:
324 st->st_uid =
325 *header->header.uname ? vfs_finduid (header->header.
326 uname) : tar_from_oct (8,
327 header->
328 header.
329 uid);
330 st->st_gid =
331 *header->header.gname ? vfs_findgid (header->header.
332 gname) : tar_from_oct (8,
333 header->
334 header.
335 gid);
336 switch (header->header.linkflag) {
337 case LF_BLK:
338 case LF_CHR:
339 st->st_rdev =
340 (tar_from_oct (8, header->header.devmajor) << 8) |
341 tar_from_oct (8, header->header.devminor);
343 default:
344 st->st_uid = tar_from_oct (8, header->header.uid);
345 st->st_gid = tar_from_oct (8, header->header.gid);
347 st->st_size = h_size;
348 st->st_mtime = tar_from_oct (1 + 12, header->header.mtime);
349 st->st_atime = 0;
350 st->st_ctime = 0;
351 if (archive->u.arch.type == TAR_GNU) {
352 st->st_atime = tar_from_oct (1 + 12,
353 header->header.unused.oldgnu.atime);
354 st->st_ctime = tar_from_oct (1 + 12,
355 header->header.unused.oldgnu.ctime);
360 typedef enum {
361 STATUS_BADCHECKSUM,
362 STATUS_SUCCESS,
363 STATUS_EOFMARK,
364 STATUS_EOF
365 } ReadStatus;
367 * Return 1 for success, 0 if the checksum is bad, EOF on eof,
368 * 2 for a record full of zeros (EOF marker).
371 static ReadStatus
372 tar_read_header (struct vfs_class *me, struct vfs_s_super *archive,
373 int tard, size_t *h_size)
375 register int i;
376 register long sum, signed_sum, recsum;
377 register char *p;
378 register union record *header;
379 static char *next_long_name = NULL, *next_long_link = NULL;
381 recurse:
383 header = tar_get_next_record (archive, tard);
384 if (NULL == header)
385 return STATUS_EOF;
387 recsum = tar_from_oct (8, header->header.chksum);
389 sum = 0;
390 signed_sum = 0;
391 p = header->charptr;
392 for (i = sizeof (*header); --i >= 0;) {
394 * We can't use unsigned char here because of old compilers,
395 * e.g. V7.
397 signed_sum += *p;
398 sum += 0xFF & *p++;
401 /* Adjust checksum to count the "chksum" field as blanks. */
402 for (i = sizeof (header->header.chksum); --i >= 0;) {
403 sum -= 0xFF & header->header.chksum[i];
404 signed_sum -= (char) header->header.chksum[i];
406 sum += ' ' * sizeof header->header.chksum;
407 signed_sum += ' ' * sizeof header->header.chksum;
410 * This is a zeroed record...whole record is 0's except
411 * for the 8 blanks we faked for the checksum field.
413 if (sum == 8 * ' ')
414 return STATUS_EOFMARK;
416 if (sum != recsum && signed_sum != recsum)
417 return STATUS_BADCHECKSUM;
420 * Try to determine the archive format.
422 if (archive->u.arch.type == TAR_UNKNOWN) {
423 if (!strcmp (header->header.magic, TMAGIC)) {
424 if (header->header.linkflag == LF_GLOBAL_EXTHDR)
425 archive->u.arch.type = TAR_POSIX;
426 else
427 archive->u.arch.type = TAR_USTAR;
428 } else if (!strcmp (header->header.magic, OLDGNU_MAGIC)) {
429 archive->u.arch.type = TAR_GNU;
434 * linkflag on BSDI tar (pax) always '\000'
436 if (header->header.linkflag == '\000') {
437 if (header->header.arch_name[NAMSIZ - 1] != '\0')
438 i = NAMSIZ;
439 else
440 i = strlen (header->header.arch_name);
442 if (i && header->header.arch_name[i - 1] == '/')
443 header->header.linkflag = LF_DIR;
447 * Good record. Decode file size and return.
449 if (header->header.linkflag == LF_LINK
450 || header->header.linkflag == LF_DIR)
451 *h_size = 0; /* Links 0 size on tape */
452 else
453 *h_size = tar_from_oct (1 + 12, header->header.size);
456 * Skip over directory snapshot info records that
457 * are stored in incremental tar archives.
459 if (header->header.linkflag == LF_DUMPDIR) {
460 if (archive->u.arch.type == TAR_UNKNOWN)
461 archive->u.arch.type = TAR_GNU;
462 return STATUS_SUCCESS;
466 * Skip over pax extended header and global extended
467 * header records.
469 if (header->header.linkflag == LF_EXTHDR ||
470 header->header.linkflag == LF_GLOBAL_EXTHDR) {
471 if (archive->u.arch.type == TAR_UNKNOWN)
472 archive->u.arch.type = TAR_POSIX;
473 return STATUS_SUCCESS;
476 if (header->header.linkflag == LF_LONGNAME
477 || header->header.linkflag == LF_LONGLINK) {
478 char **longp;
479 char *bp, *data;
480 int size, written;
482 if (archive->u.arch.type == TAR_UNKNOWN)
483 archive->u.arch.type = TAR_GNU;
485 if (*h_size > MC_MAXPATHLEN) {
486 message (D_ERROR, MSG_ERROR, _("Inconsistent tar archive"));
487 return STATUS_BADCHECKSUM;
490 longp = ((header->header.linkflag == LF_LONGNAME)
491 ? &next_long_name : &next_long_link);
493 g_free (*longp);
494 bp = *longp = g_malloc (*h_size + 1);
496 for (size = *h_size; size > 0; size -= written) {
497 data = tar_get_next_record (archive, tard)->charptr;
498 if (data == NULL) {
499 g_free (*longp);
500 *longp = NULL;
501 message (D_ERROR, MSG_ERROR,
502 _("Unexpected EOF on archive file"));
503 return STATUS_BADCHECKSUM;
505 written = RECORDSIZE;
506 if (written > size)
507 written = size;
509 memcpy (bp, data, written);
510 bp += written;
513 if (bp - *longp == MC_MAXPATHLEN && bp[-1] != '\0') {
514 g_free (*longp);
515 *longp = NULL;
516 message (D_ERROR, MSG_ERROR, _("Inconsistent tar archive"));
517 return STATUS_BADCHECKSUM;
519 *bp = 0;
520 goto recurse;
521 } else {
522 struct stat st;
523 struct vfs_s_entry *entry;
524 struct vfs_s_inode *inode, *parent;
525 long data_position;
526 char *q;
527 int len;
528 char *current_file_name, *current_link_name;
530 current_link_name =
531 (next_long_link ? next_long_link :
532 g_strndup (header->header.arch_linkname, NAMSIZ));
533 len = strlen (current_link_name);
534 if (len > 1 && current_link_name[len - 1] == '/')
535 current_link_name[len - 1] = 0;
537 current_file_name = NULL;
538 switch (archive->u.arch.type) {
539 case TAR_USTAR:
540 case TAR_POSIX:
541 /* The ustar archive format supports pathnames of upto 256
542 * characters in length. This is achieved by concatenating
543 * the contents of the `prefix' and `arch_name' fields like
544 * this:
546 * prefix + path_separator + arch_name
548 * If the `prefix' field contains an empty string i.e. its
549 * first characters is '\0' the prefix field is ignored.
551 if (header->header.unused.prefix[0] != '\0') {
552 char *temp_name, *temp_prefix;
554 temp_name = g_strndup (header->header.arch_name, NAMSIZ);
555 temp_prefix = g_strndup (header->header.unused.prefix,
556 PREFIX_SIZE);
557 current_file_name = g_strconcat (temp_prefix, PATH_SEP_STR,
558 temp_name, (char *) NULL);
559 g_free (temp_name);
560 g_free (temp_prefix);
562 break;
563 case TAR_GNU:
564 if (next_long_name != NULL)
565 current_file_name = next_long_name;
566 break;
567 default:
568 break;
571 if (current_file_name == NULL)
572 current_file_name = g_strndup (header->header.arch_name, NAMSIZ);
574 canonicalize_pathname (current_file_name);
575 len = strlen (current_file_name);
577 data_position = current_tar_position;
579 p = strrchr (current_file_name, '/');
580 if (p == NULL) {
581 p = current_file_name;
582 q = current_file_name + len; /* "" */
583 } else {
584 *(p++) = 0;
585 q = current_file_name;
588 parent =
589 vfs_s_find_inode (me, archive, q, LINK_NO_FOLLOW, FL_MKDIR);
590 if (parent == NULL) {
591 message (D_ERROR, MSG_ERROR, _("Inconsistent tar archive"));
592 return STATUS_BADCHECKSUM;
595 if (header->header.linkflag == LF_LINK) {
596 inode =
597 vfs_s_find_inode (me, archive, current_link_name,
598 LINK_NO_FOLLOW, 0);
599 if (inode == NULL) {
600 message (D_ERROR, MSG_ERROR, _("Inconsistent tar archive"));
601 } else {
602 entry = vfs_s_new_entry (me, p, inode);
603 vfs_s_insert_entry (me, parent, entry);
604 g_free (current_link_name);
605 goto done;
609 tar_fill_stat (archive, &st, header, *h_size);
610 inode = vfs_s_new_inode (me, archive, &st);
612 inode->data_offset = data_position;
613 if (*current_link_name) {
614 inode->linkname = current_link_name;
615 } else if (current_link_name != next_long_link) {
616 g_free (current_link_name);
618 entry = vfs_s_new_entry (me, p, inode);
620 vfs_s_insert_entry (me, parent, entry);
621 g_free (current_file_name);
623 done:
624 next_long_link = next_long_name = NULL;
626 if (archive->u.arch.type == TAR_GNU &&
627 header->header.unused.oldgnu.isextended) {
628 while (tar_get_next_record (archive, tard)->ext_hdr.
629 isextended);
630 inode->data_offset = current_tar_position;
632 return STATUS_SUCCESS;
637 * Main loop for reading an archive.
638 * Returns 0 on success, -1 on error.
640 static int
641 tar_open_archive (struct vfs_class *me, struct vfs_s_super *archive,
642 const char *name, char *op)
644 /* Initial status at start of archive */
645 ReadStatus status = STATUS_EOFMARK;
646 ReadStatus prev_status;
647 int tard;
649 (void) op;
651 current_tar_position = 0;
652 /* Open for reading */
653 if ((tard = tar_open_archive_int (me, name, archive)) == -1)
654 return -1;
656 for (;;) {
657 size_t h_size;
659 prev_status = status;
660 status = tar_read_header (me, archive, tard, &h_size);
662 switch (status) {
664 case STATUS_SUCCESS:
665 tar_skip_n_records (archive, tard,
666 (h_size + RECORDSIZE -
667 1) / RECORDSIZE);
668 continue;
671 * Invalid header:
673 * If the previous header was good, tell them
674 * that we are skipping bad ones.
676 case STATUS_BADCHECKSUM:
677 switch (prev_status) {
679 /* Error on first record */
680 case STATUS_EOFMARK:
681 message (D_ERROR, MSG_ERROR,
683 ("Hmm,...\n%s\ndoesn't look like a tar archive."),
684 name);
685 /* FALL THRU */
687 /* Error after header rec */
688 case STATUS_SUCCESS:
689 /* Error after error */
691 case STATUS_BADCHECKSUM:
692 return -1;
694 case STATUS_EOF:
695 return 0;
698 /* Record of zeroes */
699 case STATUS_EOFMARK:
700 status = prev_status; /* If error after 0's */
701 /* FALL THRU */
703 case STATUS_EOF: /* End of archive */
704 break;
706 break;
708 return 0;
711 static void *
712 tar_super_check (struct vfs_class *me, const char *archive_name, char *op)
714 static struct stat stat_buf;
716 (void) me;
717 (void) op;
719 if (mc_stat (archive_name, &stat_buf))
720 return NULL;
721 return &stat_buf;
724 static int
725 tar_super_same (struct vfs_class *me, struct vfs_s_super *parc,
726 const char *archive_name, char *op, void *cookie)
728 struct stat *archive_stat = cookie; /* stat of main archive */
730 (void) me;
731 (void) op;
733 if (strcmp (parc->name, archive_name))
734 return 0;
736 /* Has the cached archive been changed on the disk? */
737 if (parc->u.arch.st.st_mtime < archive_stat->st_mtime) {
738 /* Yes, reload! */
739 (*vfs_tarfs_ops.free) ((vfsid) parc);
740 vfs_rmstamp (&vfs_tarfs_ops, (vfsid) parc);
741 return 2;
743 /* Hasn't been modified, give it a new timeout */
744 vfs_stamp (&vfs_tarfs_ops, (vfsid) parc);
745 return 1;
748 static ssize_t tar_read (void *fh, char *buffer, int count)
750 off_t begin = FH->ino->data_offset;
751 int fd = FH_SUPER->u.arch.fd;
752 struct vfs_class *me = FH_SUPER->me;
754 if (mc_lseek (fd, begin + FH->pos, SEEK_SET) !=
755 begin + FH->pos) ERRNOR (EIO, -1);
757 count = MIN(count, FH->ino->st.st_size - FH->pos);
759 if ((count = mc_read (fd, buffer, count)) == -1) ERRNOR (errno, -1);
761 FH->pos += count;
762 return count;
765 static int tar_fh_open (struct vfs_class *me, struct vfs_s_fh *fh, int flags, int mode)
767 (void) fh;
768 (void) mode;
770 if ((flags & O_ACCMODE) != O_RDONLY) ERRNOR (EROFS, -1);
771 return 0;
774 void
775 init_tarfs (void)
777 static struct vfs_s_subclass tarfs_subclass;
779 tarfs_subclass.flags = VFS_S_READONLY;
780 tarfs_subclass.archive_check = tar_super_check;
781 tarfs_subclass.archive_same = tar_super_same;
782 tarfs_subclass.open_archive = tar_open_archive;
783 tarfs_subclass.free_archive = tar_free_archive;
784 tarfs_subclass.fh_open = tar_fh_open;
786 vfs_s_init_class (&vfs_tarfs_ops, &tarfs_subclass);
787 vfs_tarfs_ops.name = "tarfs";
788 vfs_tarfs_ops.prefix = "utar";
789 vfs_tarfs_ops.read = tar_read;
790 vfs_tarfs_ops.setctl = NULL;
791 vfs_register_class (&vfs_tarfs_ops);