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 (*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 (*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
)
77 struct vfs_s_inode
*root
;
79 result
= mc_open (name
, O_RDONLY
);
81 message_2s (1, MSG_ERROR
, _("Couldn't open tar archive\n%s"), name
);
85 archive
->name
= g_strdup (name
);
86 mc_stat (name
, &(archive
->u
.tar
.tarstat
));
87 archive
->u
.tar
.fd
= -1;
89 /* Find out the method to handle this tar file */
90 size
= is_gunzipable (result
, &type
);
91 mc_lseek (result
, 0, SEEK_SET
);
95 s
= g_strconcat ( archive
->name
, decompress_extension (type
), NULL
);
96 result
= mc_open (s
, O_RDONLY
);
98 message_2s (1, MSG_ERROR
, _("Couldn't open tar archive\n%s"), s
);
104 archive
->u
.tar
.fd
= result
;
105 mode
= archive
->u
.tar
.tarstat
.st_mode
& 07777;
106 if (mode
& 0400) mode
|= 0100;
107 if (mode
& 0040) mode
|= 0010;
108 if (mode
& 0004) mode
|= 0001;
111 root
= vfs_s_new_inode (me
, archive
, &archive
->u
.tar
.tarstat
);
112 root
->st
.st_mode
= mode
;
113 root
->u
.tar
.data_offset
= -1;
115 root
->st
.st_dev
= MEDATA
->rdev
++;
117 vfs_s_add_dots (me
, root
, NULL
);
118 archive
->root
= root
;
123 static union record rec_buf
;
125 static union record
*
126 get_next_record (vfs_s_super
*archive
, int tard
)
130 n
= mc_read (tard
, rec_buf
.charptr
, RECORDSIZE
);
132 return NULL
; /* An error has occurred */
133 current_tar_position
+= RECORDSIZE
;
137 static void skip_n_records (vfs_s_super
*archive
, int tard
, int n
)
139 mc_lseek (tard
, n
* RECORDSIZE
, SEEK_CUR
);
140 current_tar_position
+= n
* RECORDSIZE
;
143 static void fill_stat_from_header (vfs
*me
, struct stat
*st
, union record
*header
)
145 st
->st_mode
= from_oct (8, header
->header
.mode
);
147 /* Adjust st->st_mode because there are tar-files with
148 * linkflag==LF_SYMLINK and S_ISLNK(mod)==0. I don't
149 * know about the other modes but I think I cause no new
150 * problem when I adjust them, too. -- Norbert.
152 if (header
->header
.linkflag
== LF_DIR
) {
153 st
->st_mode
|= S_IFDIR
;
154 } else if (header
->header
.linkflag
== LF_SYMLINK
) {
155 st
->st_mode
|= S_IFLNK
;
156 } else if (header
->header
.linkflag
== LF_CHR
) {
157 st
->st_mode
|= S_IFCHR
;
158 } else if (header
->header
.linkflag
== LF_BLK
) {
159 st
->st_mode
|= S_IFBLK
;
160 } else if (header
->header
.linkflag
== LF_FIFO
) {
161 st
->st_mode
|= S_IFIFO
;
163 st
->st_mode
|= S_IFREG
;
166 if (!strcmp (header
->header
.magic
, TMAGIC
)) {
167 st
->st_uid
= *header
->header
.uname
? finduid (header
->header
.uname
) :
168 from_oct (8, header
->header
.uid
);
169 st
->st_gid
= *header
->header
.gname
? findgid (header
->header
.gname
) :
170 from_oct (8, header
->header
.gid
);
171 switch (header
->header
.linkflag
) {
174 st
->st_rdev
= (from_oct (8, header
->header
.devmajor
) << 8) |
175 from_oct (8, header
->header
.devminor
);
177 } else { /* Old Unix tar */
178 st
->st_uid
= from_oct (8, header
->header
.uid
);
179 st
->st_gid
= from_oct (8, header
->header
.gid
);
181 st
->st_size
= hstat
.st_size
;
182 st
->st_mtime
= from_oct (1 + 12, header
->header
.mtime
);
183 st
->st_atime
= from_oct (1 + 12, header
->header
.atime
);
184 st
->st_ctime
= from_oct (1 + 12, header
->header
.ctime
);
195 * Return 1 for success, 0 if the checksum is bad, EOF on eof,
196 * 2 for a record full of zeros (EOF marker).
200 read_header (vfs
*me
, vfs_s_super
*archive
, int tard
)
203 register long sum
, signed_sum
, recsum
;
205 register union record
*header
;
206 static char *next_long_name
= NULL
, *next_long_link
= NULL
;
210 header
= get_next_record (archive
, tard
);
214 recsum
= from_oct (8, header
->header
.chksum
);
216 sum
= 0; signed_sum
= 0;
218 for (i
= sizeof (*header
); --i
>= 0;) {
220 * We can't use unsigned char here because of old compilers,
227 /* Adjust checksum to count the "chksum" field as blanks. */
228 for (i
= sizeof (header
->header
.chksum
); --i
>= 0;) {
229 sum
-= 0xFF & header
->header
.chksum
[i
];
230 signed_sum
-= (char) header
->header
.chksum
[i
];
232 sum
+= ' ' * sizeof header
->header
.chksum
;
233 signed_sum
+= ' ' * sizeof header
->header
.chksum
;
236 * This is a zeroed record...whole record is 0's except
237 * for the 8 blanks we faked for the checksum field.
240 return STATUS_EOFMARK
;
242 if (sum
!= recsum
&& signed_sum
!= recsum
)
243 return STATUS_BADCHECKSUM
;
246 * linkflag on BSDI tar (pax) always '\000'
248 if (header
->header
.linkflag
== '\000' &&
249 (i
= strlen(header
->header
.arch_name
)) &&
250 header
->header
.arch_name
[i
- 1] == '/')
251 header
->header
.linkflag
= LF_DIR
;
254 * Good record. Decode file size and return.
256 if (header
->header
.linkflag
== LF_LINK
|| header
->header
.linkflag
== LF_DIR
)
257 hstat
.st_size
= 0; /* Links 0 size on tape */
259 hstat
.st_size
= from_oct (1 + 12, header
->header
.size
);
261 header
->header
.arch_name
[NAMSIZ
- 1] = '\0';
262 if (header
->header
.linkflag
== LF_LONGNAME
263 || header
->header
.linkflag
== LF_LONGLINK
) {
268 longp
= ((header
->header
.linkflag
== LF_LONGNAME
)
274 bp
= *longp
= g_malloc (hstat
.st_size
);
276 for (size
= hstat
.st_size
;
279 data
= get_next_record (archive
, tard
)->charptr
;
281 message_1s (1, MSG_ERROR
, _("Unexpected EOF on archive file"));
282 return STATUS_BADCHECKSUM
;
284 written
= RECORDSIZE
;
288 bcopy (data
, bp
, written
);
292 if (hstat
.st_size
> 1)
293 bp
[hstat
.st_size
- 1] = 0; /* just to make sure */
298 struct vfs_s_entry
*entry
;
299 struct vfs_s_inode
*inode
, *parent
;
303 char *current_file_name
, *current_link_name
;
305 current_link_name
= (next_long_link
307 : g_strdup (header
->header
.arch_linkname
));
308 len
= strlen (current_link_name
);
309 if (len
> 1 && current_link_name
[len
- 1] == '/')
310 current_link_name
[len
- 1] = 0;
312 current_file_name
= (next_long_name
314 : g_strdup (header
->header
.arch_name
));
315 len
= strlen (current_file_name
);
316 if (current_file_name
[len
- 1] == '/') {
317 current_file_name
[len
- 1] = 0;
320 data_position
= current_tar_position
;
322 p
= strrchr (current_file_name
, '/');
324 p
= current_file_name
;
325 q
= current_file_name
+ len
; /* "" */
328 q
= current_file_name
;
331 parent
= vfs_s_find_inode (me
, archive
->root
, q
, LINK_NO_FOLLOW
, FL_MKDIR
);
332 if (parent
== NULL
) {
333 message_1s (1, MSG_ERROR
, _("Inconsistent tar archive"));
334 return STATUS_BADCHECKSUM
;
337 if (header
->header
.linkflag
== LF_LINK
) {
338 inode
= vfs_s_find_inode (me
, archive
->root
, current_link_name
, LINK_NO_FOLLOW
, 0);
340 message_1s (1, MSG_ERROR
, _("Inconsistent tar archive"));
342 entry
= vfs_s_new_entry(me
, p
, inode
);
343 vfs_s_insert_entry(me
, parent
, entry
);
344 g_free (current_link_name
);
349 fill_stat_from_header (me
, &st
, header
);
350 inode
= vfs_s_new_inode (me
, archive
, &st
);
352 inode
->u
.tar
.data_offset
= data_position
;
353 if (*current_link_name
) {
354 inode
->linkname
= current_link_name
;
355 } else if (current_link_name
!= next_long_link
) {
356 g_free (current_link_name
);
358 entry
= vfs_s_new_entry (me
, p
, inode
);
360 vfs_s_insert_entry (me
, parent
, entry
);
361 g_free (current_file_name
);
364 next_long_link
= next_long_name
= NULL
;
366 if (header
->header
.isextended
) {
367 while (get_next_record (archive
, tard
)->ext_hdr
.isextended
);
368 inode
->u
.tar
.data_offset
= current_tar_position
;
370 return STATUS_SUCCESS
;
375 * Main loop for reading an archive.
376 * Returns 0 on success, -1 on error.
378 static int open_archive (vfs
*me
, vfs_s_super
*archive
, char *name
, char *op
)
380 ReadStatus status
= STATUS_EOFMARK
; /* Initial status at start of archive */
381 ReadStatus prev_status
;
384 current_tar_position
= 0;
385 if ((tard
= tar_open_archive (me
, name
, archive
)) == -1) /* Open for reading */
389 prev_status
= status
;
390 status
= read_header (me
, archive
, tard
);
396 skip_n_records (archive
, tard
, (hstat
.st_size
+ RECORDSIZE
- 1) / RECORDSIZE
);
402 * If the previous header was good, tell them
403 * that we are skipping bad ones.
405 case STATUS_BADCHECKSUM
:
406 switch (prev_status
){
408 /* Error on first record */
410 message_2s (1, MSG_ERROR
, _("Hmm,...\n%s\ndoesn't look like a tar archive."), name
);
413 /* Error after header rec */
415 /* Error after error */
417 case STATUS_BADCHECKSUM
:
424 /* Record of zeroes */
426 status
= prev_status
; /* If error after 0's */
429 case STATUS_EOF
: /* End of archive */
437 static void *tar_super_check(vfs
*me
, char *archive_name
, char *op
)
439 static struct stat stat_buf
;
440 if (mc_stat (archive_name
, &stat_buf
))
445 static int tar_super_same(vfs
*me
, struct vfs_s_super
*parc
, char *archive_name
, char *op
, void *cookie
)
447 struct stat
*archive_stat
= cookie
; /* stat of main archive */
449 if (strcmp (parc
->name
, archive_name
)) return 0;
451 if (vfs_uid
&& (!(archive_stat
->st_mode
& 0004)))
452 if ((archive_stat
->st_gid
!= vfs_gid
) || !(archive_stat
->st_mode
& 0040))
453 if ((archive_stat
->st_uid
!= vfs_uid
) || !(archive_stat
->st_mode
& 0400))
455 /* Has the cached archive been changed on the disk? */
456 if (parc
->u
.tar
.tarstat
.st_mtime
< archive_stat
->st_mtime
) { /* Yes, reload! */
457 (*vfs_tarfs_ops
.free
) ((vfsid
) parc
);
458 vfs_rmstamp (&vfs_tarfs_ops
, (vfsid
) parc
, 0);
461 /* Hasn't been modified, give it a new timeout */
462 vfs_stamp (&vfs_tarfs_ops
, (vfsid
) parc
);
466 static int tar_read (void *fh
, char *buffer
, int count
)
468 off_t begin
= FH
->ino
->u
.tar
.data_offset
;
469 int fd
= FH_SUPER
->u
.tar
.fd
;
470 vfs
*me
= FH_SUPER
->me
;
472 if (mc_lseek (fd
, begin
+ FH
->pos
, SEEK_SET
) !=
473 begin
+ FH
->pos
) ERRNOR (EIO
, -1);
475 count
= MIN(count
, FH
->ino
->st
.st_size
- FH
->pos
);
477 if ((count
= mc_read (fd
, buffer
, count
)) == -1) ERRNOR (errno
, -1);
483 static int tar_ungetlocalcopy (vfs
*me
, char *path
, char *local
, int has_changed
)
485 /* We do just nothing. (We are read only and do not need to free local,
486 since it will be freed when tar archive will be freed */
487 /* We have to report error if file has changed */
488 ERRNOR (EROFS
, -has_changed
);
491 static int tar_fh_open (vfs
*me
, vfs_s_fh
*fh
, int flags
, int mode
)
493 if ((flags
& O_ACCMODE
) != O_RDONLY
) ERRNOR (EROFS
, -1);
497 static struct vfs_s_data tarfs_data
= {
503 NULL
, /* init_inode */
504 NULL
, /* free_inode */
505 NULL
, /* init_entry */
512 tar_fh_open
, /* fh_open */
515 vfs_s_find_entry_tree
,
522 NULL
, /* This is place of next pointer */