1 /* Virtual File System: GNU Tar file system.
2 Copyright (C) 1995 The Free Software Foundation
4 Written by: 1995 Jakub Jelinek
5 Rewritten by: 1998 Pavel Machek
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public License
9 as published by the Free Software Foundation; either version 2 of
10 the License, or (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU Library General Public License for more details.
17 You should have received a copy of the GNU Library General Public
18 License along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
21 /* Namespace: vfs_tarfs_ops */
27 #include "xdirentry.h"
29 #include "../src/dialog.h" /* For MSG_ERROR */
33 #define isodigit(c) ( ((c) >= '0') && ((c) <= '7') )
35 * Quick and dirty octal conversion.
37 * Result is -1 if the field is invalid (all blank, or nonoctal).
39 static long from_oct (int digs
, char *where
)
43 while (isspace ((unsigned char) *where
)) { /* Skip spaces */
46 return -1; /* All blank field */
49 while (digs
> 0 && isodigit (*where
)) { /* Scan till nonoctal */
50 value
= (value
<< 3) | (*where
++ - '0');
54 if (digs
> 0 && *where
&& !isspace ((unsigned char) *where
))
55 return -1; /* Ended on non-space/nul */
60 static struct stat hstat
; /* Stat struct corresponding */
62 static void tar_free_archive (vfs
*me
, vfs_s_super
*archive
)
64 if (archive
->u
.tar
.fd
!= -1)
65 mc_close(archive
->u
.tar
.fd
);
68 /* As we open one archive at a time, it is safe to have this static */
69 static int current_tar_position
= 0;
71 /* Returns fd of the open tar file */
72 static int tar_open_archive (vfs
*me
, char *name
, vfs_s_super
*archive
)
76 struct vfs_s_inode
*root
;
78 result
= mc_open (name
, O_RDONLY
);
80 message_2s (1, MSG_ERROR
, _("Couldn't open tar archive\n%s"), name
);
84 archive
->name
= g_strdup (name
);
85 mc_stat (name
, &(archive
->u
.tar
.tarstat
));
86 archive
->u
.tar
.fd
= -1;
88 /* Find out the method to handle this tar file */
89 type
= get_compression_type (result
);
90 mc_lseek (result
, 0, SEEK_SET
);
91 if (type
!= COMPRESSION_NONE
) {
94 s
= g_strconcat ( archive
->name
, decompress_extension (type
), NULL
);
95 result
= mc_open (s
, O_RDONLY
);
97 message_2s (1, MSG_ERROR
, _("Couldn't open tar archive\n%s"), s
);
103 archive
->u
.tar
.fd
= result
;
104 mode
= archive
->u
.tar
.tarstat
.st_mode
& 07777;
105 if (mode
& 0400) mode
|= 0100;
106 if (mode
& 0040) mode
|= 0010;
107 if (mode
& 0004) mode
|= 0001;
110 root
= vfs_s_new_inode (me
, archive
, &archive
->u
.tar
.tarstat
);
111 root
->st
.st_mode
= mode
;
112 root
->u
.tar
.data_offset
= -1;
114 root
->st
.st_dev
= MEDATA
->rdev
++;
116 vfs_s_add_dots (me
, root
, NULL
);
117 archive
->root
= root
;
122 static union record rec_buf
;
124 static union record
*
125 get_next_record (vfs_s_super
*archive
, int tard
)
129 n
= mc_read (tard
, rec_buf
.charptr
, RECORDSIZE
);
131 return NULL
; /* An error has occurred */
132 current_tar_position
+= RECORDSIZE
;
136 static void skip_n_records (vfs_s_super
*archive
, int tard
, int n
)
138 mc_lseek (tard
, n
* RECORDSIZE
, SEEK_CUR
);
139 current_tar_position
+= n
* RECORDSIZE
;
142 static void fill_stat_from_header (vfs
*me
, struct stat
*st
, union record
*header
)
144 st
->st_mode
= from_oct (8, header
->header
.mode
);
146 /* Adjust st->st_mode because there are tar-files with
147 * linkflag==LF_SYMLINK and S_ISLNK(mod)==0. I don't
148 * know about the other modes but I think I cause no new
149 * problem when I adjust them, too. -- Norbert.
151 if (header
->header
.linkflag
== LF_DIR
) {
152 st
->st_mode
|= S_IFDIR
;
153 } else if (header
->header
.linkflag
== LF_SYMLINK
) {
154 st
->st_mode
|= S_IFLNK
;
155 } else if (header
->header
.linkflag
== LF_CHR
) {
156 st
->st_mode
|= S_IFCHR
;
157 } else if (header
->header
.linkflag
== LF_BLK
) {
158 st
->st_mode
|= S_IFBLK
;
159 } else if (header
->header
.linkflag
== LF_FIFO
) {
160 st
->st_mode
|= S_IFIFO
;
162 st
->st_mode
|= S_IFREG
;
165 if (!strcmp (header
->header
.magic
, TMAGIC
)) {
166 st
->st_uid
= *header
->header
.uname
? finduid (header
->header
.uname
) :
167 from_oct (8, header
->header
.uid
);
168 st
->st_gid
= *header
->header
.gname
? findgid (header
->header
.gname
) :
169 from_oct (8, header
->header
.gid
);
170 switch (header
->header
.linkflag
) {
173 st
->st_rdev
= (from_oct (8, header
->header
.devmajor
) << 8) |
174 from_oct (8, header
->header
.devminor
);
176 } else { /* Old Unix tar */
177 st
->st_uid
= from_oct (8, header
->header
.uid
);
178 st
->st_gid
= from_oct (8, header
->header
.gid
);
180 st
->st_size
= hstat
.st_size
;
181 st
->st_mtime
= from_oct (1 + 12, header
->header
.mtime
);
182 st
->st_atime
= from_oct (1 + 12, header
->header
.atime
);
183 st
->st_ctime
= from_oct (1 + 12, header
->header
.ctime
);
194 * Return 1 for success, 0 if the checksum is bad, EOF on eof,
195 * 2 for a record full of zeros (EOF marker).
199 read_header (vfs
*me
, vfs_s_super
*archive
, int tard
)
202 register long sum
, signed_sum
, recsum
;
204 register union record
*header
;
205 static char *next_long_name
= NULL
, *next_long_link
= NULL
;
209 header
= get_next_record (archive
, tard
);
213 recsum
= from_oct (8, header
->header
.chksum
);
215 sum
= 0; signed_sum
= 0;
217 for (i
= sizeof (*header
); --i
>= 0;) {
219 * We can't use unsigned char here because of old compilers,
226 /* Adjust checksum to count the "chksum" field as blanks. */
227 for (i
= sizeof (header
->header
.chksum
); --i
>= 0;) {
228 sum
-= 0xFF & header
->header
.chksum
[i
];
229 signed_sum
-= (char) header
->header
.chksum
[i
];
231 sum
+= ' ' * sizeof header
->header
.chksum
;
232 signed_sum
+= ' ' * sizeof header
->header
.chksum
;
235 * This is a zeroed record...whole record is 0's except
236 * for the 8 blanks we faked for the checksum field.
239 return STATUS_EOFMARK
;
241 if (sum
!= recsum
&& signed_sum
!= recsum
)
242 return STATUS_BADCHECKSUM
;
245 * linkflag on BSDI tar (pax) always '\000'
247 if (header
->header
.linkflag
== '\000' &&
248 (i
= strlen(header
->header
.arch_name
)) &&
249 header
->header
.arch_name
[i
- 1] == '/')
250 header
->header
.linkflag
= LF_DIR
;
253 * Good record. Decode file size and return.
255 if (header
->header
.linkflag
== LF_LINK
|| header
->header
.linkflag
== LF_DIR
)
256 hstat
.st_size
= 0; /* Links 0 size on tape */
258 hstat
.st_size
= from_oct (1 + 12, header
->header
.size
);
260 header
->header
.arch_name
[NAMSIZ
- 1] = '\0';
261 if (header
->header
.linkflag
== LF_LONGNAME
262 || header
->header
.linkflag
== LF_LONGLINK
) {
267 longp
= ((header
->header
.linkflag
== LF_LONGNAME
)
273 bp
= *longp
= g_malloc (hstat
.st_size
);
275 for (size
= hstat
.st_size
;
278 data
= get_next_record (archive
, tard
)->charptr
;
280 message_1s (1, MSG_ERROR
, _("Unexpected EOF on archive file"));
281 return STATUS_BADCHECKSUM
;
283 written
= RECORDSIZE
;
287 memcpy (bp
, data
, written
);
291 if (hstat
.st_size
> 1)
292 bp
[hstat
.st_size
- 1] = 0; /* just to make sure */
297 struct vfs_s_entry
*entry
;
298 struct vfs_s_inode
*inode
, *parent
;
302 char *current_file_name
, *current_link_name
;
304 current_link_name
= (next_long_link
306 : g_strdup (header
->header
.arch_linkname
));
307 len
= strlen (current_link_name
);
308 if (len
> 1 && current_link_name
[len
- 1] == '/')
309 current_link_name
[len
- 1] = 0;
311 current_file_name
= (next_long_name
313 : g_strdup (header
->header
.arch_name
));
314 len
= strlen (current_file_name
);
315 if (current_file_name
[len
- 1] == '/') {
316 current_file_name
[len
- 1] = 0;
319 data_position
= current_tar_position
;
321 p
= strrchr (current_file_name
, '/');
323 p
= current_file_name
;
324 q
= current_file_name
+ len
; /* "" */
327 q
= current_file_name
;
330 parent
= vfs_s_find_inode (me
, archive
->root
, q
, LINK_NO_FOLLOW
, FL_MKDIR
);
331 if (parent
== NULL
) {
332 message_1s (1, MSG_ERROR
, _("Inconsistent tar archive"));
333 return STATUS_BADCHECKSUM
;
336 if (header
->header
.linkflag
== LF_LINK
) {
337 inode
= vfs_s_find_inode (me
, archive
->root
, current_link_name
, LINK_NO_FOLLOW
, 0);
339 message_1s (1, MSG_ERROR
, _("Inconsistent tar archive"));
341 entry
= vfs_s_new_entry(me
, p
, inode
);
342 vfs_s_insert_entry(me
, parent
, entry
);
343 g_free (current_link_name
);
348 fill_stat_from_header (me
, &st
, header
);
349 inode
= vfs_s_new_inode (me
, archive
, &st
);
351 inode
->u
.tar
.data_offset
= data_position
;
352 if (*current_link_name
) {
353 inode
->linkname
= current_link_name
;
354 } else if (current_link_name
!= next_long_link
) {
355 g_free (current_link_name
);
357 entry
= vfs_s_new_entry (me
, p
, inode
);
359 vfs_s_insert_entry (me
, parent
, entry
);
360 g_free (current_file_name
);
363 next_long_link
= next_long_name
= NULL
;
365 if (header
->header
.isextended
) {
366 while (get_next_record (archive
, tard
)->ext_hdr
.isextended
);
367 inode
->u
.tar
.data_offset
= current_tar_position
;
369 return STATUS_SUCCESS
;
374 * Main loop for reading an archive.
375 * Returns 0 on success, -1 on error.
377 static int open_archive (vfs
*me
, vfs_s_super
*archive
, char *name
, char *op
)
379 ReadStatus status
= STATUS_EOFMARK
; /* Initial status at start of archive */
380 ReadStatus prev_status
;
383 current_tar_position
= 0;
384 if ((tard
= tar_open_archive (me
, name
, archive
)) == -1) /* Open for reading */
388 prev_status
= status
;
389 status
= read_header (me
, archive
, tard
);
395 skip_n_records (archive
, tard
, (hstat
.st_size
+ RECORDSIZE
- 1) / RECORDSIZE
);
401 * If the previous header was good, tell them
402 * that we are skipping bad ones.
404 case STATUS_BADCHECKSUM
:
405 switch (prev_status
){
407 /* Error on first record */
409 message_2s (1, MSG_ERROR
, _("Hmm,...\n%s\ndoesn't look like a tar archive."), name
);
412 /* Error after header rec */
414 /* Error after error */
416 case STATUS_BADCHECKSUM
:
423 /* Record of zeroes */
425 status
= prev_status
; /* If error after 0's */
428 case STATUS_EOF
: /* End of archive */
436 static void *tar_super_check(vfs
*me
, char *archive_name
, char *op
)
438 static struct stat stat_buf
;
439 if (mc_stat (archive_name
, &stat_buf
))
444 static int tar_super_same(vfs
*me
, struct vfs_s_super
*parc
, char *archive_name
, char *op
, void *cookie
)
446 struct stat
*archive_stat
= cookie
; /* stat of main archive */
448 if (strcmp (parc
->name
, archive_name
)) return 0;
450 if (vfs_uid
&& (!(archive_stat
->st_mode
& 0004)))
451 if ((archive_stat
->st_gid
!= vfs_gid
) || !(archive_stat
->st_mode
& 0040))
452 if ((archive_stat
->st_uid
!= vfs_uid
) || !(archive_stat
->st_mode
& 0400))
454 /* Has the cached archive been changed on the disk? */
455 if (parc
->u
.tar
.tarstat
.st_mtime
< archive_stat
->st_mtime
) { /* Yes, reload! */
456 (*vfs_tarfs_ops
.free
) ((vfsid
) parc
);
457 vfs_rmstamp (&vfs_tarfs_ops
, (vfsid
) parc
, 0);
460 /* Hasn't been modified, give it a new timeout */
461 vfs_stamp (&vfs_tarfs_ops
, (vfsid
) parc
);
465 static int tar_read (void *fh
, char *buffer
, int count
)
467 off_t begin
= FH
->ino
->u
.tar
.data_offset
;
468 int fd
= FH_SUPER
->u
.tar
.fd
;
469 vfs
*me
= FH_SUPER
->me
;
471 if (mc_lseek (fd
, begin
+ FH
->pos
, SEEK_SET
) !=
472 begin
+ FH
->pos
) ERRNOR (EIO
, -1);
474 count
= MIN(count
, FH
->ino
->st
.st_size
- FH
->pos
);
476 if ((count
= mc_read (fd
, buffer
, count
)) == -1) ERRNOR (errno
, -1);
482 static int tar_ungetlocalcopy (vfs
*me
, char *path
, char *local
, int has_changed
)
484 /* We do just nothing. (We are read only and do not need to free local,
485 since it will be freed when tar archive will be freed */
486 /* We have to report error if file has changed */
487 ERRNOR (EROFS
, -has_changed
);
490 static int tar_fh_open (vfs
*me
, vfs_s_fh
*fh
, int flags
, int mode
)
492 if ((flags
& O_ACCMODE
) != O_RDONLY
) ERRNOR (EROFS
, -1);
496 static struct vfs_s_data tarfs_data
= {
502 NULL
, /* init_inode */
503 NULL
, /* free_inode */
504 NULL
, /* init_entry */
511 tar_fh_open
, /* fh_open */
514 vfs_s_find_entry_tree
,
521 NULL
, /* This is place of next pointer */