update
[midnight-commander.git] / vfs / tar.c
bloba2e6943ebd404fa8769c0276d92f70e7a01c1e7d
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., 675 Mass Ave, Cambridge, MA 02139, USA. */
21 /* Namespace: vfs_tarfs_ops */
23 #include "xdirentry.h"
24 #include <errno.h>
25 #ifdef SCO_FLAVOR
26 #include <sys/timeb.h> /* alex: for struct timeb definition */
27 #endif /* SCO_FLAVOR */
28 #include <time.h>
30 #include "utilvfs.h"
32 #include "../src/dialog.h" /* For MSG_ERROR */
33 #include "tar.h"
34 #include "names.h"
36 #define isodigit(c) ( ((c) >= '0') && ((c) <= '7') )
38 * Quick and dirty octal conversion.
40 * Result is -1 if the field is invalid (all blank, or nonoctal).
42 static long from_oct (int digs, char *where)
44 register long value;
46 while (isspace (*where)) { /* Skip spaces */
47 where++;
48 if (--digs <= 0)
49 return -1; /* All blank field */
51 value = 0;
52 while (digs > 0 && isodigit (*where)) { /* Scan till nonoctal */
53 value = (value << 3) | (*where++ - '0');
54 --digs;
57 if (digs > 0 && *where && !isspace (*where))
58 return -1; /* Ended on non-space/nul */
60 return value;
63 static struct stat hstat; /* Stat struct corresponding */
65 static void tar_free_archive (vfs *me, vfs_s_super *archive)
67 if (archive->u.tar.fd != -1)
68 mc_close(archive->u.tar.fd);
71 /* As we open one archive at a time, it is safe to have this static */
72 static int current_tar_position = 0;
74 /* Returns fd of the open tar file */
75 static int tar_open_archive (vfs *me, char *name, vfs_s_super *archive)
77 int result, type;
78 long size;
79 mode_t mode;
80 struct vfs_s_inode *root;
82 result = mc_open (name, O_RDONLY);
83 if (result == -1) {
84 message_2s (1, MSG_ERROR, _("Couldn't open tar archive\n%s"), name);
85 ERRNOR (ENOENT, -1);
88 archive->name = g_strdup (name);
89 mc_stat (name, &(archive->u.tar.tarstat));
90 archive->u.tar.fd = -1;
92 /* Find out the method to handle this tar file */
93 size = is_gunzipable (result, &type);
94 mc_lseek (result, 0, SEEK_SET);
95 if (size > 0) {
96 char *s;
97 mc_close( result );
98 s = g_strconcat ( archive->name, decompress_extension (type), NULL );
99 result = mc_open (s, O_RDONLY);
100 if (result == -1)
101 message_2s (1, MSG_ERROR, _("Couldn't open tar archive\n%s"), s);
102 g_free(s);
103 if (result == -1)
104 ERRNOR (ENOENT, -1);
107 archive->u.tar.fd = result;
108 mode = archive->u.tar.tarstat.st_mode & 07777;
109 if (mode & 0400) mode |= 0100;
110 if (mode & 0040) mode |= 0010;
111 if (mode & 0004) mode |= 0001;
112 mode |= S_IFDIR;
114 root = vfs_s_new_inode (me, archive, &archive->u.tar.tarstat);
115 root->st.st_mode = mode;
116 root->u.tar.data_offset = -1;
117 root->st.st_nlink++;
118 root->st.st_dev = MEDATA->rdev++;
120 vfs_s_add_dots (me, root, NULL);
121 archive->root = root;
123 return result;
126 static union record rec_buf;
128 static union record *
129 get_next_record (vfs_s_super *archive, int tard)
131 int n;
133 n = mc_read (tard, rec_buf.charptr, RECORDSIZE);
134 if (n != RECORDSIZE)
135 return NULL; /* An error has occurred */
136 current_tar_position += RECORDSIZE;
137 return &rec_buf;
140 static void skip_n_records (vfs_s_super *archive, int tard, int n)
142 mc_lseek (tard, n * RECORDSIZE, SEEK_CUR);
143 current_tar_position += n * RECORDSIZE;
146 static void fill_stat_from_header (vfs *me, struct stat *st, union record *header)
148 st->st_mode = from_oct (8, header->header.mode);
150 /* Adjust st->st_mode because there are tar-files with
151 * linkflag==LF_SYMLINK and S_ISLNK(mod)==0. I don't
152 * know about the other modes but I think I cause no new
153 * problem when I adjust them, too. -- Norbert.
155 if (header->header.linkflag == LF_DIR) {
156 st->st_mode |= S_IFDIR;
157 } else if (header->header.linkflag == LF_SYMLINK) {
158 st->st_mode |= S_IFLNK;
159 } else if (header->header.linkflag == LF_CHR) {
160 st->st_mode |= S_IFCHR;
161 } else if (header->header.linkflag == LF_BLK) {
162 st->st_mode |= S_IFBLK;
163 } else if (header->header.linkflag == LF_FIFO) {
164 st->st_mode |= S_IFIFO;
165 } else
166 st->st_mode |= S_IFREG;
168 st->st_rdev = 0;
169 if (!strcmp (header->header.magic, TMAGIC)) {
170 st->st_uid = *header->header.uname ? finduid (header->header.uname) :
171 from_oct (8, header->header.uid);
172 st->st_gid = *header->header.gname ? findgid (header->header.gname) :
173 from_oct (8, header->header.gid);
174 switch (header->header.linkflag) {
175 case LF_BLK:
176 case LF_CHR:
177 st->st_rdev = (from_oct (8, header->header.devmajor) << 8) |
178 from_oct (8, header->header.devminor);
180 } else { /* Old Unix tar */
181 st->st_uid = from_oct (8, header->header.uid);
182 st->st_gid = from_oct (8, header->header.gid);
184 st->st_size = hstat.st_size;
185 st->st_mtime = from_oct (1 + 12, header->header.mtime);
186 st->st_atime = from_oct (1 + 12, header->header.atime);
187 st->st_ctime = from_oct (1 + 12, header->header.ctime);
191 typedef enum {
192 STATUS_BADCHECKSUM,
193 STATUS_SUCCESS,
194 STATUS_EOFMARK,
195 STATUS_EOF,
196 } ReadStatus;
198 * Return 1 for success, 0 if the checksum is bad, EOF on eof,
199 * 2 for a record full of zeros (EOF marker).
202 static ReadStatus
203 read_header (vfs *me, vfs_s_super *archive, int tard)
205 register int i;
206 register long sum, signed_sum, recsum;
207 register char *p;
208 register union record *header;
209 char **longp;
210 char *bp, *data;
211 int size, written;
212 static char *next_long_name = NULL, *next_long_link = NULL;
213 char *current_file_name, *current_link_name;
215 recurse:
217 header = get_next_record (archive, tard);
218 if (NULL == header)
219 return STATUS_EOF;
221 recsum = from_oct (8, header->header.chksum);
223 sum = 0; signed_sum = 0;
224 p = header->charptr;
225 for (i = sizeof (*header); --i >= 0;) {
227 * We can't use unsigned char here because of old compilers,
228 * e.g. V7.
230 signed_sum += *p;
231 sum += 0xFF & *p++;
234 /* Adjust checksum to count the "chksum" field as blanks. */
235 for (i = sizeof (header->header.chksum); --i >= 0;) {
236 sum -= 0xFF & header->header.chksum[i];
237 signed_sum -= (char) header->header.chksum[i];
239 sum += ' ' * sizeof header->header.chksum;
240 signed_sum += ' ' * sizeof header->header.chksum;
243 * This is a zeroed record...whole record is 0's except
244 * for the 8 blanks we faked for the checksum field.
246 if (sum == 8 * ' ')
247 return STATUS_EOFMARK;
249 if (sum != recsum && signed_sum != recsum)
250 return STATUS_BADCHECKSUM;
253 * linkflag on BSDI tar (pax) always '\000'
255 if (header->header.linkflag == '\000' &&
256 strlen(header->header.arch_name) &&
257 header->header.arch_name[strlen(header->header.arch_name) - 1] == '/')
258 header->header.linkflag = LF_DIR;
261 * Good record. Decode file size and return.
263 if (header->header.linkflag == LF_LINK || header->header.linkflag == LF_DIR)
264 hstat.st_size = 0; /* Links 0 size on tape */
265 else
266 hstat.st_size = from_oct (1 + 12, header->header.size);
268 header->header.arch_name[NAMSIZ - 1] = '\0';
269 if (header->header.linkflag == LF_LONGNAME
270 || header->header.linkflag == LF_LONGLINK) {
271 longp = ((header->header.linkflag == LF_LONGNAME)
272 ? &next_long_name
273 : &next_long_link);
275 if (*longp)
276 g_free (*longp);
277 bp = *longp = g_malloc (hstat.st_size);
279 for (size = hstat.st_size;
280 size > 0;
281 size -= written) {
282 data = get_next_record (archive, tard)->charptr;
283 if (data == NULL) {
284 message_1s (1, MSG_ERROR, _("Unexpected EOF on archive file"));
285 return STATUS_BADCHECKSUM;
287 written = RECORDSIZE;
288 if (written > size)
289 written = size;
291 bcopy (data, bp, written);
292 bp += written;
294 #if 0
295 if (hstat.st_size > 1)
296 bp [hstat.st_size - 1] = 0; /* just to make sure */
297 #endif
298 goto recurse;
299 } else {
300 struct stat st;
301 struct vfs_s_entry *entry;
302 struct vfs_s_inode *inode, *parent;
303 long data_position;
304 char *p, *q;
305 int len;
306 int isdir = 0;
308 current_file_name = (next_long_name
309 ? next_long_name
310 : g_strdup (header->header.arch_name));
311 len = strlen (current_file_name);
312 if (current_file_name[len - 1] == '/') {
313 current_file_name[len - 1] = 0;
314 isdir = 1;
317 current_link_name = (next_long_link
318 ? next_long_link
319 : g_strdup (header->header.arch_linkname));
320 len = strlen (current_link_name);
321 if (len && current_link_name [len - 1] == '/')
322 current_link_name[len - 1] = 0;
324 next_long_link = next_long_name = NULL;
326 data_position = current_tar_position;
328 p = strrchr (current_file_name, '/');
329 if (p == NULL) {
330 p = current_file_name;
331 q = current_file_name + strlen (current_file_name); /* "" */
332 } else {
333 *(p++) = 0;
334 q = current_file_name;
337 parent = vfs_s_find_inode (me, archive->root, q, LINK_NO_FOLLOW, FL_MKDIR);
338 if (parent == NULL) {
339 message_1s (1, MSG_ERROR, _("Inconsistent tar archive"));
340 return STATUS_BADCHECKSUM;
343 if (header->header.linkflag == LF_LINK) {
344 inode = vfs_s_find_inode (me, archive->root, current_link_name, LINK_NO_FOLLOW, 0);
345 if (inode == NULL) {
346 message_1s (1, MSG_ERROR, _("Inconsistent tar archive"));
347 } else {
348 entry = vfs_s_new_entry(me, p, inode);
349 vfs_s_insert_entry(me, parent, entry);
350 g_free (current_link_name);
351 goto done;
355 fill_stat_from_header (me, &st, header);
356 inode = vfs_s_new_inode (me, archive, &st);
358 inode->u.tar.data_offset = data_position;
359 if (*current_link_name)
360 inode->linkname = current_link_name;
361 entry = vfs_s_new_entry (me, p, inode);
363 vfs_s_insert_entry (me, parent, entry);
364 g_free (current_file_name);
366 done:
367 if (header->header.isextended) {
368 while (get_next_record (archive, tard)->ext_hdr.isextended);
369 inode->u.tar.data_offset = current_tar_position;
371 return STATUS_SUCCESS;
376 * Main loop for reading an archive.
377 * Returns 0 on success, -1 on error.
379 static int open_archive (vfs *me, vfs_s_super *archive, char *name, char *op)
381 ReadStatus status = STATUS_EOFMARK; /* Initial status at start of archive */
382 ReadStatus prev_status;
383 int tard;
385 current_tar_position = 0;
386 if ((tard = tar_open_archive (me, name, archive)) == -1) /* Open for reading */
387 return -1;
389 for (;;) {
390 prev_status = status;
391 status = read_header (me, archive, tard);
394 switch (status) {
396 case STATUS_SUCCESS:
397 skip_n_records (archive, tard, (hstat.st_size + RECORDSIZE - 1) / RECORDSIZE);
398 continue;
401 * Invalid header:
403 * If the previous header was good, tell them
404 * that we are skipping bad ones.
406 case STATUS_BADCHECKSUM:
407 switch (prev_status){
409 /* Error on first record */
410 case STATUS_EOFMARK:
411 message_2s (1, MSG_ERROR, _("Hmm,...\n%s\ndoesn't look like a tar archive."), name);
412 /* FALL THRU */
414 /* Error after header rec */
415 case STATUS_SUCCESS:
416 /* Error after error */
418 case STATUS_BADCHECKSUM:
419 return -1;
421 case STATUS_EOF:
422 return 0;
425 /* Record of zeroes */
426 case STATUS_EOFMARK:
427 status = prev_status; /* If error after 0's */
428 /* FALL THRU */
430 case STATUS_EOF: /* End of archive */
431 break;
433 break;
435 return 0;
438 void *tar_super_check(vfs *me, char *archive_name, char *op)
440 static struct stat stat_buf;
441 if (mc_stat (archive_name, &stat_buf))
442 return NULL;
443 return &stat_buf;
446 int tar_super_same(vfs *me, struct vfs_s_super *parc, char *archive_name, char *op, void *cookie)
448 struct stat *archive_stat = cookie; /* stat of main archive */
450 if (strcmp (parc->name, archive_name)) return 0;
452 if (vfs_uid && (!(archive_stat->st_mode & 0004)))
453 if ((archive_stat->st_gid != vfs_gid) || !(archive_stat->st_mode & 0040))
454 if ((archive_stat->st_uid != vfs_uid) || !(archive_stat->st_mode & 0400))
455 return 0;
456 /* Has the cached archive been changed on the disk? */
457 if (parc->u.tar.tarstat.st_mtime < archive_stat->st_mtime) { /* Yes, reload! */
458 (*vfs_tarfs_ops.free) ((vfsid) parc);
459 vfs_rmstamp (&vfs_tarfs_ops, (vfsid) parc, 0);
460 return 2;
462 /* Hasn't been modified, give it a new timeout */
463 vfs_stamp (&vfs_tarfs_ops, (vfsid) parc);
464 return 1;
467 static int tar_read (void *fh, char *buffer, int count)
469 off_t begin = FH->ino->u.tar.data_offset;
470 int fd = FH_SUPER->u.tar.fd;
471 vfs *me = FH_SUPER->me;
473 if (mc_lseek (fd, begin + FH->pos, SEEK_SET) !=
474 begin + FH->pos) ERRNOR (EIO, -1);
476 count = MIN(count, FH->ino->st.st_size - FH->pos);
478 if ((count = mc_read (fd, buffer, count)) == -1) ERRNOR (errno, -1);
480 FH->pos += count;
481 return count;
484 static void tar_ungetlocalcopy (vfs *me, char *path, char *local, int has_changed)
486 /* We do just nothing. (We are read only and do not need to free local,
487 since it will be freed when tar archive will be freed */
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);
493 return 0;
496 static struct vfs_s_data tarfs_data = {
497 NULL,
500 NULL, /* logfile */
502 NULL, /* init_inode */
503 NULL, /* free_inode */
504 NULL, /* init_entry */
506 tar_super_check,
507 tar_super_same,
508 open_archive,
509 tar_free_archive,
511 tar_fh_open, /* fh_open */
512 NULL, /* fh_close */
514 vfs_s_find_entry_tree,
515 NULL,
516 NULL
519 vfs vfs_tarfs_ops =
521 NULL, /* This is place of next pointer */
522 "TApe aRchiver decompressor",
523 0, /* flags */
524 "utar", /* prefix */
525 &tarfs_data,
526 0, /* errno */
527 NULL,
528 NULL,
529 vfs_s_fill_names,
530 NULL,
532 vfs_s_open,
533 vfs_s_close,
534 tar_read,
535 NULL,
537 vfs_s_opendir,
538 vfs_s_readdir,
539 vfs_s_closedir,
540 vfs_s_telldir,
541 vfs_s_seekdir,
543 vfs_s_stat,
544 vfs_s_lstat,
545 vfs_s_fstat,
547 NULL,
548 NULL,
549 NULL,
551 vfs_s_readlink,
552 NULL,
553 NULL,
554 NULL,
556 NULL,
557 vfs_s_chdir,
558 vfs_s_ferrno,
559 vfs_s_lseek,
560 NULL,
562 vfs_s_getid,
563 vfs_s_nothingisopen,
564 vfs_s_free,
566 vfs_s_getlocalcopy,
567 tar_ungetlocalcopy,
569 NULL, /* mkdir */
570 NULL,
571 NULL,
572 NULL
574 MMAPNULL