home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Geek Gadgets 1
/
ADE-1.bin
/
ade-dist
/
tar-1.11.8-src.tgz
/
tar.out
/
fsf
/
tar
/
src
/
extract.c
< prev
next >
Wrap
C/C++ Source or Header
|
1996-09-28
|
24KB
|
881 lines
/* Extract files from a tar archive.
Copyright (C) 1988, 1992, 1993, 1994 Free Software Foundation, Inc.
This file is part of GNU Tar.
GNU Tar is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
GNU Tar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNU Tar; see the file COPYING. If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
/* Extract files from a tar archive.
Written 19 Nov 1985 by John Gilmore. */
#include "system.h"
#include <time.h>
time_t time ();
/* We need the #define's even though we don't use them. */
#if NO_OPEN3
# include "open3.h"
#endif
/* Simulated 3-argument open for systems that don't have it */
#if EMUL_OPEN3
# include "open3.h"
#endif
#if HAVE_UTIME_H
# include <utime.h>
#else
struct utimbuf
{
long actime;
long modtime;
};
#endif
#include "tar.h"
extern union record *head; /* points to current tape header */
extern struct stat hstat; /* stat struct corresponding */
extern int head_standard; /* tape header is in ANSI format */
static void extract_sparse_file __P ((int, long *, long, char *));
static int make_dirs __P ((char *));
static time_t now = 0; /* current time */
static we_are_root = 0; /* true if our effective uid == 0 */
static int notumask = ~0; /* masks out bits user doesn't want */
#if 0
/* "Scratch" space to store the information about a sparse file before
writing the info into the header or extended header. */
struct sp_array *sparsearray;
/* Number of elts storable in the sparsearray. */
int sp_array_size = 10;
#endif
struct saved_dir_info
{
char *path;
int mode;
int atime;
int mtime;
uid_t uid;
gid_t gid;
struct saved_dir_info *next;
};
struct saved_dir_info *saved_dir_info_head;
/*--------------------------.
| Set up to extract files. |
`--------------------------*/
void
extr_init (void)
{
int ourmask;
now = time ((time_t *) 0);
if (geteuid () == 0)
we_are_root = 1;
/* We need to know our umask. But if flag_use_protection is set, leave
our kernel umask at 0, and our "notumask" at ~0. */
ourmask = umask (0); /* read it */
if (!flag_use_protection)
{
umask (ourmask); /* set it back how it was */
notumask = ~ourmask; /* make umask override permissions */
}
}
/*----------------------------------.
| Extract a file from the archive. |
`----------------------------------*/
void
extract_archive (void)
{
register char *data;
int fd, check, namelen, written, openflag;
long size;
struct utimbuf acc_upd_times;
register int skipcrud;
register int i;
#if 0
int sparse_ind = 0;
#endif
union record *exhdr;
struct saved_dir_info *tmp;
#if 0
int end_nulls;
#endif
#ifdef O_CTG
/* FIXME: Dirty kludge so the code compile on Masscomp's. I do not know
what longname is supposed to be. NULL makes it unused. */
char *longname = NULL;
#endif
#define CURRENT_FILE_NAME (skipcrud + current_file_name)
saverec (&head); /* make sure it sticks around */
userec (head); /* and go past it in the archive */
decode_header (head, &hstat, &head_standard, 1);
if (flag_confirm && !confirm ("extract", current_file_name))
{
if (head->header.isextended)
skip_extended_headers ();
skip_file ((long) hstat.st_size);
saverec ((union record **) 0);
return;
}
/* Print the record from `head' and `hstat'. */
if (flag_verbose)
print_header ();
/* Check for fully specified pathnames and other atrocities.
Note, we can't just make a pointer to the new file name, since
saverec() might move the header and adjust "head". We have to start
from "head" every time we want to touch the header record. */
skipcrud = 0;
while (!flag_absolute_names && CURRENT_FILE_NAME[0] == '/')
{
static int warned_once = 0;
skipcrud++; /* force relative path */
if (!warned_once++)
WARN ((0, 0, _("\
Removing leading / from absolute path names in the archive")));
}
switch (head->header.linkflag)
{
default:
WARN ((0, 0,
_("Unknown file type '%c' for %s, extracted as normal file"),
head->header.linkflag, CURRENT_FILE_NAME));
/* Fall through. */
/* JK - What we want to do if the file is sparse is loop through
the array of sparse structures in the header and read in and
translate the character strings representing 1) the offset at
which to write and 2) how many bytes to write into numbers,
which we store into the scratch array, "sparsearray". This
array makes our life easier the same way it did in creating the
tar file that had to deal with a sparse file.
After we read in the first five (at most) sparse structures, we
check to see if the file has an extended header, i.e., if more
sparse structures are needed to describe the contents of the new
file. If so, we read in the extended headers and continue to
store their contents into the sparsearray. */
case LF_SPARSE:
sp_array_size = 10;
sparsearray = (struct sp_array *) xmalloc (sp_array_size * sizeof (struct sp_array));
for (i = 0; i < SPARSE_IN_HDR; i++)
{
sparsearray[i].offset =
from_oct (1 + 12, head->header.sp[i].offset);
sparsearray[i].numbytes =
from_oct (1 + 12, head->header.sp[i].numbytes);
if (!sparsearray[i].numbytes)
break;
}
#if 0
end_nulls = from_oct (1 + 12, head->header.ending_blanks);
#endif
if (head->header.isextended)
{
/* Read in the list of extended headers and translate them into
the sparsearray as before. */
/* static */ int ind = SPARSE_IN_HDR;
while (1)
{
exhdr = findrec ();
for (i = 0; i < SPARSE_EXT_HDR; i++)
{
if (i + ind > sp_array_size - 1)
{
/* Realloc the scratch area since we've run out of
room. */
sp_array_size *= 2;
sparsearray = (struct sp_array *)
xrealloc (sparsearray,
sp_array_size * (sizeof (struct sp_array)));
}
/* Compare to 0, or use !(int)..., for Pyramid's dumb
compiler. */
if (exhdr->ext_hdr.sp[i].numbytes == 0)
break;
sparsearray[i + ind].offset =
from_oct (1 + 12, exhdr->ext_hdr.sp[i].offset);
sparsearray[i + ind].numbytes =
from_oct (1 + 12, exhdr->ext_hdr.sp[i].numbytes);
}
if (!exhdr->ext_hdr.isextended)
break;
else
{
ind += SPARSE_EXT_HDR;
userec (exhdr);
}
}
userec (exhdr);
}
/* Fall through. */
case LF_OLDNORMAL:
case LF_NORMAL:
case LF_CONTIG:
/* Appears to be a file. See if it's really a directory. */
namelen = strlen (CURRENT_FILE_NAME) - 1;
if (CURRENT_FILE_NAME[namelen] == '/')
goto really_dir;
/* FIXME, deal with protection issues. */
again_file:
openflag = (flag_keep ?
O_BINARY | O_NDELAY | O_WRONLY | O_CREAT | O_EXCL :
O_BINARY | O_NDELAY | O_WRONLY | O_CREAT | O_TRUNC)
| ((head->header.linkflag == LF_SPARSE) ? 0 : O_APPEND);
/* JK - The last | is a kludge to solve the problem the O_APPEND
flag causes with files we are trying to make sparse: when a file
is opened with O_APPEND, it writes to the last place that
something was written, thereby ignoring any lseeks that we have
done. We add this extra condition to make it able to lseek when
a file is sparse, i.e., we don't open the new file with this
flag. (Grump -- this bug caused me to waste a good deal of
time, I might add) */
if (flag_exstdout)
{
fd = 1;
goto extract_file;
}
#ifdef O_CTG
/* Contiguous files (on the Masscomp) have to specify the size in
the open call that creates them. */
if (head->header.linkflag == LF_CONTIG)
fd = open ((longname ? longname : head->header.name) + skipcrud,
openflag | O_CTG, hstat.st_mode, hstat.st_size);
else
#endif
{
#ifdef NO_OPEN3
/* On raw V7 we won't let them specify -k (flag_keep), but we just
bull ahead and create the files. */
fd = creat ((longname ? longname : head->header.name) + skipcrud,
hstat.st_mode);
#else
/* With 3-arg open(), we can do this up right. */
fd = open (CURRENT_FILE_NAME, openflag, hstat.st_mode);
#endif
}
if (fd < 0)
{
if (make_dirs (CURRENT_FILE_NAME))
goto again_file;
ERROR ((0, errno, _("Could not create file %s"), CURRENT_FILE_NAME));
if (head->header.isextended)
skip_extended_headers ();
skip_file ((long) hstat.st_size);
goto quit;
}
extract_file:
if (head->header.linkflag == LF_SPARSE)
{
char *name;
int namelen_bis;
/* Kludge alert. NAME is assigned to header.name because
during the extraction, the space that contains the header
will get scribbled on, and the name will get munged, so any
error messages that happen to contain the filename will look
REAL interesting unless we do this. */
namelen_bis = strlen (CURRENT_FILE_NAME) + 1;
name = (char *) xmalloc ((sizeof (char)) * namelen_bis);
memcpy (name, CURRENT_FILE_NAME, (size_t) namelen_bis);
size = hstat.st_size;
extract_sparse_file (fd, &size, hstat.st_size, name);
}
else
for (size = hstat.st_size;
size > 0;
size -= written)
{
#if 0
long offset, numbytes;
#endif
if (flag_multivol)
{
assign_string (&save_name, current_file_name);
save_totsize = hstat.st_size;
save_sizeleft = size;
}
/* Locate data, determine max length writeable, write it,
record that we have used the data, then check if the write
worked. */
data = findrec ()->charptr;
if (data == NULL)
{
/* Check it. */
ERROR ((0, 0, _("Unexpected EOF on archive file")));
break;
}
/* JK - If the file is sparse, use the sparsearray that we
created before to lseek into the new file the proper
amount, and to see how many bytes we want to write at that
position. */
#if 0
if (head->header.linkflag == LF_SPARSE)
{
off_t pos;
pos = lseek (fd, (off_t) sparsearray[sparse_ind].offset, 0);
fprintf (msg_file, _("%d at %d\n"), (int) pos, sparse_ind);
written = sparsearray[sparse_ind++].numbytes;
}
else
#endif
written = endofrecs ()->charptr - data;
if (written > size)
written = size;
errno = 0;
check = write (fd, data, (size_t) written);
/* The following is in violation of strict typing, since the
arg to userec should be a struct rec *. FIXME. */
userec ((union record *) (data + written - 1));
if (check == written)
continue;
/* Error in writing to file. Print it, skip to next file in
archive. */
if (check < 0)
ERROR ((0, errno, _("Could not write to file %s"),
CURRENT_FILE_NAME));
else
ERROR ((0, 0, _("Could only write %d of %d bytes to file %s"),
check, written, CURRENT_FILE_NAME));
skip_file ((long) (size - written));
break; /* still do the close, mod time, chmod, etc */
}
if (flag_multivol)
assign_string (&save_name, NULL);
/* If writing to stdout, don't try to do anything to the filename;
it doesn't exist, or we don't want to touch it anyway. */
if (flag_exstdout)
break;
#if 0
if (head->header.isextended)
{
register union record *exhdr;
register int i;
for (i = 0; i < 21; i++)
{
off_t offset;
if (!exhdr->ext_hdr.sp[i].numbytes)
break;
offset = from_oct (1 + 12, exhdr->ext_hdr.sp[i].offset);
written = from_oct (1 + 12, exhdr->ext_hdr.sp[i].numbytes);
lseek (fd, offset, 0);
check = write (fd, data, written);
if (check == written)
continue;
}
}
#endif
check = close (fd);
if (check < 0)
ERROR ((0, errno, _("Error while closing %s"), CURRENT_FILE_NAME));
set_filestat:
/* Set the modified time of the file.
Note that we set the accessed time to "now", which is really
"the time we started extracting files". unless flag_gnudump is
used, in which case .st_atime is used */
if (!flag_modified)
{
/* FIXME. If flag_gnudump should set ctime too, but how? */
if (flag_gnudump)
acc_upd_times.actime = hstat.st_atime;
else
acc_upd_times.actime = now; /* accessed now */
acc_upd_times.modtime = hstat.st_mtime; /* mod'd */
if (utime (CURRENT_FILE_NAME, &acc_upd_times) < 0)
ERROR ((0, errno,
_("Could not change access and modification times of %s"),
CURRENT_FILE_NAME));
}
/* We do the utime before the chmod because some versions of utime
are broken and trash the modes of the file. Since we then
change the mode anyway, we don't care. */
/* If we are root, set the owner and group of the extracted file.
This does what is wanted both on real Unix and on System V. If
we are running as a user, we extract as that user; if running as
root, we extract as the original owner. */
if (we_are_root || flag_do_chown)
if (chown (CURRENT_FILE_NAME, hstat.st_uid, hstat.st_gid) < 0)
ERROR ((0, errno, _("Cannot chown file %s to uid %d gid %d"),
CURRENT_FILE_NAME, hstat.st_uid, hstat.st_gid));
/* If '-k' is not set, open() or creat() could have saved the
permission bits from a previously created file, ignoring the
ones we specified. Even if -k is set, if the file has abnormal
mode bits, we must chmod since writing or chown() has probably
reset them.
If -k is set, we know *we* created this file, so the mode bits
were set by our open(). If the file is "normal", we skip the
chmod. This works because we did umask(0) if -p is set, so
umask will have left the specified mode alone. */
if ((!flag_keep)
|| (hstat.st_mode & (S_ISUID | S_ISGID | S_ISVTX)))
if (chmod (CURRENT_FILE_NAME, notumask & (int) hstat.st_mode) < 0)
ERROR ((0, errno, _("Cannot change mode of file %s to %lo"),
CURRENT_FILE_NAME, notumask & (int) hstat.st_mode));
quit:
break;
case LF_LINK:
again_link:
{
struct stat st1, st2;
check = link (current_link_name, CURRENT_FILE_NAME);
if (check == 0)
break;
if (make_dirs (CURRENT_FILE_NAME))
goto again_link;
if (flag_gnudump && errno == EEXIST)
break;
if (stat (current_link_name, &st1) == 0
&& stat (CURRENT_FILE_NAME, &st2) == 0
&& st1.st_dev == st2.st_dev
&& st1.st_ino == st2.st_ino)
break;
ERROR ((0, errno, _("Could not link %s to %s"),
CURRENT_FILE_NAME, current_link_name));
}
break;
#ifdef S_ISLNK
case LF_SYMLINK:
again_symlink:
check = symlink (current_link_name, CURRENT_FILE_NAME);
/* FIXME, don't worry uid, gid, etc. */
if (check == 0)
break;
if (make_dirs (CURRENT_FILE_NAME))
goto again_symlink;
ERROR ((0, errno, _("Could not create symlink to %s"),
current_link_name));
break;
#endif
#ifdef S_IFCHR
case LF_CHR:
hstat.st_mode |= S_IFCHR;
goto make_node;
#endif
#ifdef S_IFBLK
case LF_BLK:
hstat.st_mode |= S_IFBLK;
#endif
#if defined(S_IFCHR) || defined(S_IFBLK)
make_node:
check = mknod (CURRENT_FILE_NAME, (int) hstat.st_mode,
(int) hstat.st_rdev);
if (check != 0)
{
if (make_dirs (CURRENT_FILE_NAME))
goto make_node;
ERROR ((0, errno, _("Could not make %s"), CURRENT_FILE_NAME));
break;
};
goto set_filestat;
#endif
#ifdef S_ISFIFO
/* If local system doesn't support FIFOs, use default case. */
case LF_FIFO:
make_fifo:
check = mkfifo (CURRENT_FILE_NAME, (int) hstat.st_mode);
if (check != 0)
{
if (make_dirs (CURRENT_FILE_NAME))
goto make_fifo;
ERROR ((0, errno, _("Could not make %s"), CURRENT_FILE_NAME));
break;
};
goto set_filestat;
#endif
case LF_DIR:
case LF_DUMPDIR:
namelen = strlen (CURRENT_FILE_NAME) - 1;
really_dir:
/* Check for trailing /, and zap as many as we find. */
while (namelen && CURRENT_FILE_NAME[namelen] == '/')
CURRENT_FILE_NAME[namelen--] = '\0';
if (flag_gnudump)
{
/* Read the entry and delete files that aren't listed in the
archive. */
gnu_restore (skipcrud);
}
else if (head->header.linkflag == LF_DUMPDIR)
skip_file ((long) (hstat.st_size));
again_dir:
check = mkdir (CURRENT_FILE_NAME,
(we_are_root ? 0 : 0300) | (int) hstat.st_mode);
if (check != 0)
{
struct stat st1;
if (make_dirs (CURRENT_FILE_NAME))
goto again_dir;
/* If we're trying to create '.', let it be. */
if (CURRENT_FILE_NAME[namelen] == '.'
&& (namelen == 0 || CURRENT_FILE_NAME[namelen - 1] == '/'))
goto check_perms;
if (errno == EEXIST && stat (CURRENT_FILE_NAME, &st1) == 0
&& (S_ISDIR (st1.st_mode)))
break;
ERROR ((0, errno, _("Could not create directory %s"),
CURRENT_FILE_NAME));
break;
}
check_perms:
if (!we_are_root && 0300 != (0300 & (int) hstat.st_mode))
{
hstat.st_mode |= 0300;
WARN ((0, 0, _("Added write and execute permission to directory %s"),
CURRENT_FILE_NAME));
}
if (!flag_modified)
{
tmp = ((struct saved_dir_info *)
xmalloc (sizeof (struct saved_dir_info)));
tmp->path = xstrdup (CURRENT_FILE_NAME);
tmp->mode = hstat.st_mode;
tmp->atime = hstat.st_atime;
tmp->mtime = hstat.st_mtime;
tmp->uid = hstat.st_uid;
tmp->gid = hstat.st_gid;
tmp->next = saved_dir_info_head;
saved_dir_info_head = tmp;
}
else
{
/* This functions exactly as the code for set_filestat above. */
if (!flag_keep || (hstat.st_mode & (S_ISUID | S_ISGID | S_ISVTX)))
if (chmod (CURRENT_FILE_NAME, notumask & (int) hstat.st_mode) < 0)
ERROR ((0, errno, _("Cannot change mode of file %s to %lo"),
CURRENT_FILE_NAME, notumask & (int) hstat.st_mode));
}
break;
case LF_VOLHDR:
if (flag_verbose)
fprintf (stdlis, _("Reading %s\n"), current_file_name);
break;
case LF_NAMES:
extract_mangle ();
break;
case LF_MULTIVOL:
ERROR ((0, 0, _("\
Cannot extract `%s' -- file is continued from another volume"),
current_file_name));
skip_file ((long) hstat.st_size);
break;
case LF_LONGNAME:
case LF_LONGLINK:
ERROR ((0, 0, _("Visible long name error")));
skip_file ((long) hstat.st_size);
break;
}
/* We don't need to save it any longer. */
saverec (NULL); /* unsave it */
#undef CURRENT_FILE_NAME
}
/*------------------------------------------------------------------------.
| After a file/link/symlink/dir creation has failed, see if it's because |
| some required directory was not present, and if so, create all required |
| dirs. |
`------------------------------------------------------------------------*/
static int
make_dirs (char *pathname)
{
char *p; /* points into path */
int madeone = 0; /* did we do anything yet? */
int save_errno = errno; /* remember caller's errno */
int check;
if (errno != ENOENT)
return 0; /* not our problem */
for (p = strchr (pathname, '/'); p != NULL; p = strchr (p + 1, '/'))
{
/* Avoid mkdir of empty string, if leading or double '/'. */
if (p == pathname || p[-1] == '/')
continue;
/* Avoid mkdir where last part of path is '.'. */
if (p[-1] == '.' && (p == pathname + 1 || p[-2] == '/'))
continue;
*p = 0; /* truncate the path there */
check = mkdir (pathname, 0777); /* try to create it as a dir */
if (check == 0)
{
/* Fix ownership. */
if (we_are_root)
if (chown (pathname, hstat.st_uid, hstat.st_gid) < 0)
ERROR ((0, errno,
_("Cannot change owner of %s to uid %d gid %d"),
pathname, hstat.st_uid, hstat.st_gid));
pr_mkdir (pathname, p - pathname, notumask & 0777);
madeone++; /* remember if we made one */
*p = '/';
continue;
}
*p = '/';
if (errno == EEXIST
#ifdef MSDOS
/* Turbo C mkdir gives a funny errno. */
|| errno == EACCES
#endif
)
/* Directory already exists. */
continue;
/* Some other error in the mkdir. We return to the caller. */
break;
}
errno = save_errno; /* restore caller's errno */
return madeone; /* tell them to retry if we made one */
}
/*---.
| ? |
`---*/
static void
extract_sparse_file (int fd, long *sizeleft, long totalsize, char *name)
{
#if 0
register char *data;
#endif
union record *datarec;
int sparse_ind = 0;
int written, count;
/* FIXME: `datarec' might be used uninitialized in this function.
Reported by Bruno Haible. */
/* assuming sizeleft is initially totalsize */
while (*sizeleft > 0)
{
datarec = findrec ();
if (datarec == NULL)
{
ERROR ((0, 0, _("Unexpected EOF on archive file")));
return;
}
lseek (fd, sparsearray[sparse_ind].offset, 0);
written = sparsearray[sparse_ind++].numbytes;
while (written > RECORDSIZE)
{
count = write (fd, datarec->charptr, RECORDSIZE);
if (count < 0)
ERROR ((0, errno, _("Could not write to file %s"), name));
written -= count;
*sizeleft -= count;
userec (datarec);
datarec = findrec ();
}
count = write (fd, datarec->charptr, (size_t) written);
if (count < 0)
ERROR ((0, errno, _("Could not write to file %s"), name));
else if (count != written)
{
ERROR ((0, 0, _("Could only write %d of %d bytes to file %s"),
count, totalsize, name));
skip_file ((long) (*sizeleft));
}
written -= count;
*sizeleft -= count;
userec (datarec);
}
free (sparsearray);
#if 0
if (end_nulls)
{
register int i;
fprintf (stdlis, "%d\n", (int) end_nulls);
for (i = 0; i < end_nulls; i++)
write (fd, "\000", 1);
}
#endif
userec (datarec);
}
/*----------------------------------------------------------------.
| Set back the utime and mode for all the extracted directories. |
`----------------------------------------------------------------*/
void
restore_saved_dir_info (void)
{
struct utimbuf acc_upd_times;
while (saved_dir_info_head != NULL)
{
/* FIXME, if flag_gnudump should set ctime too, but how? */
if (flag_gnudump)
acc_upd_times.actime = saved_dir_info_head->atime;
else
acc_upd_times.actime = now; /* accessed now */
acc_upd_times.modtime = saved_dir_info_head->mtime; /* mod'd */
if (utime (saved_dir_info_head->path, &acc_upd_times) < 0)
ERROR ((0, errno,
_("Could not change access and modification times of %s"),
saved_dir_info_head->path));
/* If we are root, set the owner and group of the extracted file.
This does what is wanted both on real Unix and on System V. If
we are running as a user, we extract as that user; if running as
root, we extract as the original owner. */
if (we_are_root || flag_do_chown)
if (chown (saved_dir_info_head->path,
saved_dir_info_head->uid, saved_dir_info_head->gid) < 0)
ERROR ((0, errno, _("Cannot chown file %s to uid %d gid %d"),
saved_dir_info_head->path,
saved_dir_info_head->uid, saved_dir_info_head->gid));
if ((!flag_keep)
|| (saved_dir_info_head->mode & (S_ISUID | S_ISGID | S_ISVTX)))
if (chmod (saved_dir_info_head->path,
notumask & saved_dir_info_head->mode)
< 0)
ERROR ((0, errno, _("Cannot change mode of file %s to %lo"),
saved_dir_info_head->path,
notumask & saved_dir_info_head->mode));
saved_dir_info_head = saved_dir_info_head->next;
}
}