/* @(#)extract.c 1.52 02/05/02 Copyright 1985 J. Schilling */
#ifndef lint
static char sccsid[] =
"@(#)extract.c 1.52 02/05/02 Copyright 1985 J. Schilling";
#endif
/*
* extract files from archive
*
* Copyright (c) 1985 J. Schilling
*/
/*
* This program 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.
*
* This program 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 this program; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <mconfig.h>
#include <stdio.h>
#include <standard.h>
#include "star.h"
#include "props.h"
#include "table.h"
#include <dirdefs.h> /*XXX Wegen S_IFLNK */
#include <unixstd.h>
#include <strdefs.h>
#include <schily.h>
#ifdef JOS
# include <error.h>
# define mkdir makedir
#else
# include <errno.h>
# define EMISSDIR ENOENT
#endif
#include "dirtime.h"
#include "starsubs.h"
extern FILE *vpr;
extern char *listfile;
extern int bufsize;
extern char *bigptr;
extern BOOL havepat;
extern BOOL prblockno;
extern BOOL nflag;
extern BOOL interactive;
extern BOOL nodir;
extern BOOL nospec;
extern BOOL xdir;
extern BOOL uncond;
extern BOOL keep_old;
extern BOOL refresh_old;
extern BOOL abs_path;
extern BOOL nowarn;
extern BOOL force_hole;
extern BOOL to_stdout;
extern BOOL remove_first;
extern BOOL copylinks;
extern BOOL hardlinks;
extern BOOL symlinks;
extern BOOL dometa;
EXPORT void extract __PR((char *vhname));
EXPORT BOOL newer __PR((FINFO * info));
LOCAL BOOL same_symlink __PR((FINFO * info));
LOCAL BOOL create_dirs __PR((char* name));
LOCAL BOOL make_dir __PR((FINFO * info));
LOCAL BOOL make_link __PR((FINFO * info));
LOCAL BOOL make_symlink __PR((FINFO * info));
LOCAL BOOL emul_symlink __PR((FINFO * info));
LOCAL BOOL emul_link __PR((FINFO * info));
LOCAL BOOL make_copy __PR((FINFO * info, BOOL do_symlink));
LOCAL int copy_file __PR((char *from, char *to, BOOL do_symlink));
LOCAL BOOL make_fifo __PR((FINFO * info));
LOCAL BOOL make_special __PR((FINFO * info));
LOCAL BOOL get_file __PR((FINFO * info));
LOCAL int void_func __PR((void *vp, char* p, int amount));
EXPORT BOOL void_file __PR((FINFO * info));
EXPORT int xt_file __PR((FINFO * info,
int (*)(void *, char *, int),
void *arg, int amt, char* text));
EXPORT void skip_slash __PR((FINFO * info));
EXPORT void
extract(vhname)
char *vhname;
{
FINFO finfo;
TCB tb;
char name[PATH_MAX+1];
register TCB *ptb = &tb;
fillbytes((char *)&finfo, sizeof(finfo), '\0');
finfo.f_tcb = ptb;
for (;;) {
if (get_tcb(ptb) == EOF)
break;
finfo.f_name = name;
if (tcb_to_info(ptb, &finfo) == EOF)
return;
if (prblockno)
(void)tblocks(); /* set curblockno */
if (is_volhdr(&finfo)) {
if (!get_volhdr(&finfo, vhname)) {
excomerrno(EX_BAD,
"Volume Header '%s' does not match '%s'.\n",
finfo.f_name, vhname);
}
void_file(&finfo);
continue;
}
if (!abs_path && /* XXX VVV siehe skip_slash() */
(finfo.f_name[0] == '/'/* || finfo.f_lname[0] == '/'*/))
skip_slash(&finfo);
if (listfile) {
if (!hash_lookup(finfo.f_name)) {
void_file(&finfo);
continue;
}
} else if (havepat && !match(finfo.f_name)) {
void_file(&finfo);
continue;
}
if (!is_file(&finfo) && to_stdout) {
void_file(&finfo);
continue;
}
if (is_special(&finfo) && nospec) {
xstats.s_isspecial++;
errmsgno(EX_BAD, "'%s' is not a file. Not created.\n",
finfo.f_name) ;
continue;
}
if (newer(&finfo) && !(xdir && is_dir(&finfo))) {
void_file(&finfo);
continue;
}
if (is_symlink(&finfo) && same_symlink(&finfo)) {
continue;
}
if (interactive && !ia_change(ptb, &finfo)) {
if (!nflag)
fprintf(vpr, "Skipping ...\n");
void_file(&finfo);
continue;
}
vprint(&finfo);
if (remove_first) {
/*
* With keep_old we do not come here.
*/
(void)remove_file(finfo.f_name, TRUE);
}
if (is_dir(&finfo)) {
if (!make_dir(&finfo))
continue;
} else if (is_link(&finfo)) {
if (!make_link(&finfo))
continue;
} else if (is_symlink(&finfo)) {
if (!make_symlink(&finfo))
continue;
} else if (is_special(&finfo)) {
if (is_door(&finfo)) {
if (!nowarn) {
errmsgno(EX_BAD,
"WARNING: Extracting door '%s' as plain file.\n",
finfo.f_name);
}
if (!get_file(&finfo))
continue;
} else if (is_fifo(&finfo)) {
if (!make_fifo(&finfo))
continue;
} else {
if (!make_special(&finfo))
continue;
}
} else if (is_meta(&finfo)) {
void_file(&finfo);
} else if (!get_file(&finfo))
continue;
if (!to_stdout)
setmodes(&finfo);
}
dirtimes("", (struct timeval *)0);
}
EXPORT BOOL
newer(info)
FINFO *info;
{
FINFO cinfo;
if (uncond)
return (FALSE);
if (!getinfo(info->f_name, &cinfo)) {
if (refresh_old) {
errmsgno(EX_BAD, "file '%s' does not exists.\n", info->f_name);
return (TRUE);
}
return (FALSE);
}
if (keep_old) {
if (!nowarn)
errmsgno(EX_BAD, "file '%s' exists.\n", info->f_name);
return (TRUE);
}
/*
* XXX nsec beachten wenn im Archiv!
*/
if (cinfo.f_mtime >= info->f_mtime) {
isnewer:
if (!nowarn)
errmsgno(EX_BAD, "current '%s' newer.\n", info->f_name);
return (TRUE);
} else if ((cinfo.f_mtime % 2) == 0 && (cinfo.f_mtime + 1) == info->f_mtime) {
/*
* The DOS FAT filestem does only support a time granularity
* of 2 seconds. So we need to be a bit more generous.
* XXX We should be able to test the filesytem type.
*/
goto isnewer;
}
return (FALSE);
}
LOCAL BOOL
same_symlink(info)
FINFO *info;
{
FINFO finfo;
char lname[PATH_MAX+1];
TCB tb;
finfo.f_lname = lname;
finfo.f_lnamelen = 0;
if (uncond || !getinfo(info->f_name, &finfo))
return (FALSE);
/*
* Bei symlinks gehen nicht: lchmod lchtime & teilweise lchown
*/
#ifdef S_IFLNK
if (!is_symlink(&finfo)) /* File on disk */
return (FALSE);
fillbytes(&tb, sizeof(TCB), '\0');
info_to_tcb(&finfo, &tb); /* XXX ist das noch nötig ??? */
/* z.Zt. wegen linkflag/uname/gname */
if (read_symlink(info->f_name, &finfo, &tb)) {
if (streql(info->f_lname, finfo.f_lname)) {
if (!nowarn)
errmsgno(EX_BAD, "current '%s' is same symlink.\n",
info->f_name);
return (TRUE);
}
}
#ifdef XXX
/*
* XXX nsec beachten wenn im Archiv!
*/
if (finfo.f_mtime >= info->f_mtime) {
if (!nowarn)
errmsgno(EX_BAD, "current '%s' newer.\n", info->f_name);
return (TRUE);
}
#endif /* XXX*/
#endif
return (FALSE);
}
LOCAL BOOL
create_dirs(name)
register char *name;
{
register char *np;
register char *dp;
int err;
if (nodir) {
errmsgno(EX_BAD, "Directories not created.\n");
return (FALSE);
}
np = dp = name;
do {
if (*np == '/')
dp = np;
} while (*np++);
if (dp == name)
return TRUE;
*dp = '\0';
if (access(name, 0) < 0) {
if (mkdir(name, 0777) < 0) {
if (!create_dirs(name) || mkdir(name, 0777) < 0) {
err = geterrno();
if ((err == EACCES || err == EEXIST))
goto exists;
*dp = '/';
return FALSE;
}
}
} else {
FINFO dinfo;
exists:
if (getinfo(name, &dinfo) && !is_dir(&dinfo) &&
remove_file(name, FALSE) && mkdir(name, 0777) < 0) {
if (!create_dirs(name) || mkdir(name, 0777) < 0) {
*dp = '/';
return FALSE;
}
}
}
*dp = '/';
return TRUE;
}
LOCAL BOOL
make_dir(info)
FINFO *info;
{
FINFO dinfo;
int err;
if (dometa)
return (TRUE);
if (create_dirs(info->f_name)) {
if (getinfo(info->f_name, &dinfo) && is_dir(&dinfo))
return (TRUE);
if (uncond)
unlink(info->f_name);
if (mkdir(info->f_name, 0777) >= 0)
return (TRUE);
err = geterrno();
if ((err == EACCES || err == EEXIST) &&
remove_file(info->f_name, FALSE)) {
if (mkdir(info->f_name, 0777) >= 0)
return (TRUE);
}
}
xstats.s_openerrs++;
errmsg("Cannot make dir '%s'.\n", info->f_name);
return FALSE;
}
LOCAL BOOL
make_link(info)
FINFO *info;
{
int err;
if (dometa)
return (TRUE);
if (copylinks)
return (make_copy(info, FALSE));
else if (hardlinks)
return (emul_link(info));
#ifdef HAVE_LINK
if (uncond)
unlink(info->f_name);
if (link(info->f_lname, info->f_name) >= 0)
return (TRUE);
err = geterrno();
if (create_dirs(info->f_name)) {
if (link(info->f_lname, info->f_name) >= 0)
return (TRUE);
err = geterrno();
}
if ((err == EACCES || err == EEXIST) &&
remove_file(info->f_name, FALSE)) {
if (link(info->f_lname, info->f_name) >= 0)
return (TRUE);
}
xstats.s_openerrs++;
errmsg("Cannot link '%s' to '%s'.\n", info->f_name, info->f_lname);
return(FALSE);
#else /* HAVE_LINK */
xstats.s_isspecial++;
errmsgno(EX_BAD, "Not supported. Cannot link '%s' to '%s'.\n",
info->f_name, info->f_lname);
return (FALSE);
#endif /* HAVE_LINK */
}
LOCAL BOOL
make_symlink(info)
FINFO *info;
{
int err;
if (dometa)
return (TRUE);
if (copylinks)
return (make_copy(info, TRUE));
else if (symlinks)
return (emul_symlink(info));
#ifdef S_IFLNK
if (uncond)
unlink(info->f_name);
if (sxsymlink(info) >= 0)
return (TRUE);
err = geterrno();
if (create_dirs(info->f_name)) {
if (sxsymlink(info) >= 0)
return (TRUE);
err = geterrno();
}
if ((err == EACCES || err == EEXIST) &&
remove_file(info->f_name, FALSE)) {
if (sxsymlink(info) >= 0)
return (TRUE);
}
xstats.s_openerrs++;
errmsg("Cannot create symbolic link '%s' to '%s'.\n",
info->f_name, info->f_lname);
return (FALSE);
#else /* S_IFLNK */
xstats.s_isspecial++;
errmsgno(EX_BAD, "Not supported. Cannot create symbolic link '%s' to '%s'.\n",
info->f_name, info->f_lname);
return (FALSE);
#endif /* S_IFLNK */
}
LOCAL BOOL
emul_symlink(info)
FINFO *info;
{
errmsgno(EX_BAD, "Option -symlinks not yet implemented.\n");
errmsgno(EX_BAD, "Cannot create symbolic link '%s' to '%s'.\n",
info->f_name, info->f_lname);
return (FALSE);
}
LOCAL BOOL
emul_link(info)
FINFO *info;
{
errmsgno(EX_BAD, "Option -hardlinks not yet implemented.\n");
errmsgno(EX_BAD, "Cannot link '%s' to '%s'.\n", info->f_name, info->f_lname);
#ifdef HAVE_LINK
return (FALSE);
#else
return (FALSE);
#endif /* S_IFLNK */
}
LOCAL BOOL
make_copy(info, do_symlink)
FINFO *info;
BOOL do_symlink;
{
int ret;
int err;
if (uncond)
unlink(info->f_name);
if ((ret = copy_file(info->f_lname, info->f_name, do_symlink)) >= 0)
return (TRUE);
err = geterrno();
if (ret != -2 && create_dirs(info->f_name)) {
if (copy_file(info->f_lname, info->f_name, do_symlink) >= 0)
return (TRUE);
err = geterrno();
}
if ((err == EACCES || err == EEXIST || err == EISDIR) &&
remove_file(info->f_name, FALSE)) {
if (copy_file(info->f_lname, info->f_name, do_symlink) >= 0)
return (TRUE);
}
xstats.s_openerrs++;
errmsg("Cannot create link copy '%s' from '%s'.\n",
info->f_name, info->f_lname);
return(FALSE);
}
LOCAL int
copy_file(from, to, do_symlink)
char *from;
char *to;
BOOL do_symlink;
{
FINFO finfo;
FILE *fin;
FILE *fout;
int cnt = -1;
char buf[8192];
char nbuf[PATH_MAX+1];
/*
* When tar archives hard links, both names (from/to) are relative to
* the current directory. With symlinks this does not work. Symlinks
* are always evaluated relative to the directory they reside in.
* For this reason, we cannot simply open the from/to files if we
* like to emulate a symbolic link. To emulate the behavior of a
* symbolic link, we concat the the directory part of the 'to' name
* (which is the path that becomes the sombolic link) to the complete
* 'from' name (which is the path the symbolic linkc pints to) in case
* the 'from' name is a relative path name.
*/
if (do_symlink && from[0] != '/') {
char *p = strrchr(to, '/');
int len;
if (p) {
len = p - to + 1;
strncpy(nbuf, to, len);
if ((len + strlen(from)) > PATH_MAX) {
xstats.s_toolong++;
errmsgno(EX_BAD,
"Name too long. Cannot copy from '%s'.\n", from);
return (-2);
}
strcpy(&nbuf[len], from);
from = nbuf;
}
}
if (!getinfo(from, &finfo)) {
xstats.s_staterrs++;
errmsg("Cannot stat '%s'.\n", from);
return (-2);
}
if (!is_file(&finfo)) {
errmsgno(EX_BAD, "Not a file. Cannot copy from '%s'.\n", from);
seterrno(EINVAL);
return (-2);
}
if ((fin = fileopen(from, "rub")) == 0) {
errmsg("Cannot open '%s'.\n", from);
} else {
if ((fout = fileopen(to, "wtcub")) == 0) {
/* errmsg("Cannot create '%s'.\n", to);*/
return (-1);
} else {
while ((cnt = ffileread(fin, buf, sizeof(buf))) > 0)
ffilewrite(fout, buf, cnt);
fclose(fout);
}
fclose(fin);
}
return (cnt);
}
LOCAL BOOL
make_fifo(info)
FINFO *info;
{
int mode;
int err;
if (dometa)
return (TRUE);
#ifdef HAVE_MKFIFO
mode = info->f_mode | info->f_type;
if (uncond)
unlink(info->f_name);
if (mkfifo(info->f_name, mode) >= 0)
return (TRUE);
err = geterrno();
if (create_dirs(info->f_name)) {
if (mkfifo(info->f_name, mode) >= 0)
return (TRUE);
err = geterrno();
}
if ((err == EACCES || err == EEXIST) &&
remove_file(info->f_name, FALSE)) {
if (mkfifo(info->f_name, mode) >= 0)
return (TRUE);
}
xstats.s_openerrs++;
errmsg("Cannot make fifo '%s'.\n", info->f_name);
return (FALSE);
#else
#ifdef HAVE_MKNOD
return (make_special(info));
#endif
xstats.s_isspecial++;
errmsgno(EX_BAD, "Not supported. Cannot make fifo '%s'.\n",
info->f_name);
return (FALSE);
#endif
}
LOCAL BOOL
make_special(info)
FINFO *info;
{
int mode;
int dev;
int err;
if (dometa)
return (TRUE);
#ifdef HAVE_MKNOD
mode = info->f_mode | info->f_type;
dev = info->f_rdev;
if (uncond)
unlink(info->f_name);
if (mknod(info->f_name, mode, dev) >= 0)
return (TRUE);
err = geterrno();
if (create_dirs(info->f_name)) {
if (mknod(info->f_name, mode, dev) >= 0)
return (TRUE);
err = geterrno();
}
if ((err == EACCES || err == EEXIST) &&
remove_file(info->f_name, FALSE)) {
if (mknod(info->f_name, mode, dev) >= 0)
return (TRUE);
}
xstats.s_openerrs++;
errmsg("Cannot make %s '%s'.\n",
is_fifo(info)?"fifo":"special",
info->f_name);
return (FALSE);
#else
xstats.s_isspecial++;
errmsgno(EX_BAD, "Not supported. Cannot make %s '%s'.\n",
is_fifo(info)?"fifo":"special",
info->f_name);
return (FALSE);
#endif
}
LOCAL BOOL
get_file(info)
FINFO *info;
{
FILE *f;
int err;
int ret;
if (dometa)
return (TRUE);
if (to_stdout) {
f = stdout;
} else if ((f = fileopen(info->f_name, "wctub")) == (FILE *)NULL) {
err = geterrno();
if (err == EMISSDIR && create_dirs(info->f_name))
return get_file(info);
if ((err == EACCES || err == EEXIST || err == EISDIR) &&
remove_file(info->f_name, FALSE)) {
return get_file(info);
}
xstats.s_openerrs++;
errmsg("Cannot create '%s'.\n", info->f_name);
void_file(info);
return (FALSE);
}
file_raise(f, FALSE);
if (is_sparse(info)) {
ret = get_sparse(f, info);
} else if (force_hole) {
ret = get_forced_hole(f, info);
} else {
ret = xt_file(info, (int(*)__PR((void *, char *, int)))ffilewrite,
f, 0, "writing");
}
if (ret < 0) {
snulltimes(info->f_name, info);
die(EX_BAD);
}
if (!to_stdout) {
#ifdef HAVE_FSYNC
int cnt;
#endif
if (ret == FALSE)
xstats.s_rwerrs--; /* Compensate overshoot below */
if (fflush(f) != 0)
ret = FALSE;
#ifdef HAVE_FSYNC
err = 0;
cnt = 0;
do {
if (fsync(fdown(f)) != 0)
err = geterrno();
if (err == EINVAL)
err = 0;
} while (err == EINTR && ++cnt < 10);
if (err != 0)
ret = FALSE;
#endif
if (fclose(f) != 0)
ret = FALSE;
if (ret == FALSE) {
xstats.s_rwerrs++;
snulltimes(info->f_name, info);
}
}
return (ret);
}
/* ARGSUSED */
LOCAL int
void_func(vp, p, amount)
void *vp;
char *p;
int amount;
{
return (amount);
}
EXPORT BOOL
void_file(info)
FINFO *info;
{
int ret;
/*
* handle botch in gnu sparse file definitions
*/
if (props.pr_flags & PR_GNU_SPARSE_BUG)
if (gnu_skip_extended(info->f_tcb) < 0)
die(EX_BAD);
ret = xt_file(info, void_func, 0, 0, "void");
if (ret < 0)
die(EX_BAD);
return (ret);
}
EXPORT int
xt_file(info, func, arg, amt, text)
FINFO *info;
int (*func) __PR((void *, char *, int));
void *arg;
int amt;
char *text;
{
register int amount; /* XXX ??? */
register off_t size;
register int tasize;
BOOL ret = TRUE;
size = info->f_rsize;
if (amt == 0)
amt = bufsize;
while (size > 0) {
/*
* Replace TBLOCK by 1 for cpio.
*/
amount = buf_rwait(TBLOCK);
if (amount < TBLOCK) {
errmsgno(EX_BAD, "Tar file too small (amount: %d bytes).\n", amount);
errmsgno(EX_BAD, "Unexpected EOF on input.\n");
return (-1);
}
amount = (amount / TBLOCK) * TBLOCK;
amount = min(size, amount);
amount = min(amount, amt);
tasize = tarsize(amount);
if ((*func)(arg, bigptr, amount) != amount) {
ret = FALSE;
xstats.s_rwerrs++;
errmsg("Error %s '%s'.\n", text, info->f_name);
/* func -> void_func() setzen ???? */
}
size -= amount;
buf_rwake(tasize);
}
return (ret);
}
EXPORT void
skip_slash(info)
FINFO *info;
{
static BOOL warned = FALSE;
if (!warned && !nowarn) {
errmsgno(EX_BAD, "WARNING: skipping leading '/' on filenames.\n");
warned = TRUE;
}
/* XXX
* XXX ACHTUNG: ia_change kann es nötig machen, den String umzukopieren
* XXX denn sonst ist die Länge des Speicherplatzes unbestimmt!
*
* XXX ACHTUNG: mir ist noch unklar, ob es richtig ist, auch in jedem
* XXX Fall Führende slashes vom Linknamen zu entfernen.
* XXX Bei Hard-Link ist das sicher richtig und ergibt sich auch
* XXX automatisch, wenn man nur vor dem Aufruf von skip_slash()
* XXX auf f_name[0] == '/' abfragt.
*/
while (info->f_name[0] == '/')
info->f_name++;
/*
* Don't strip leading '/' from targets of symlinks.
*/
if (is_symlink(info))
return;
while (info->f_lname[0] == '/')
info->f_lname++;
}
syntax highlighted by Code2HTML, v. 0.9.1