#include "system.h"
#include <quotearg.h>
#if HAVE_UTIME_H
# include <utime.h>
#else
struct utimbuf
{
long actime;
long modtime;
};
#endif
#include "common.h"
int we_are_root;
static mode_t newdir_umask;
static mode_t current_umask;
enum permstatus
{
UNKNOWN_PERMSTATUS,
ARCHIVED_PERMSTATUS,
INTERDIR_PERMSTATUS
};
struct delayed_set_stat
{
struct delayed_set_stat *next;
struct stat stat_info;
size_t file_name_len;
mode_t invert_permissions;
enum permstatus permstatus;
bool after_symlinks;
char file_name[1];
};
static struct delayed_set_stat *delayed_set_stat_head;
struct delayed_symlink
{
struct delayed_symlink *next;
dev_t dev;
ino_t ino;
time_t mtime;
uid_t uid;
gid_t gid;
struct string_list *sources;
char target[1];
};
static struct delayed_symlink *delayed_symlink_head;
struct string_list
{
struct string_list *next;
char string[1];
};
void
extr_init (void)
{
we_are_root = geteuid () == 0;
same_permissions_option += we_are_root;
same_owner_option += we_are_root;
xalloc_fail_func = extract_finish;
newdir_umask = umask (0);
if (0 < same_permissions_option)
current_umask = 0;
else
{
umask (newdir_umask);
current_umask = newdir_umask;
}
}
static void
set_mode (char const *file_name, struct stat const *stat_info,
struct stat const *current_stat_info,
mode_t invert_permissions, enum permstatus permstatus,
char typeflag)
{
mode_t mode;
if (0 < same_permissions_option
&& permstatus != INTERDIR_PERMSTATUS)
{
mode = stat_info->st_mode;
if (permstatus == ARCHIVED_PERMSTATUS
&& ! (mode & ~ MODE_RWX)
&& typeflag != DIRTYPE
&& typeflag != GNUTYPE_DUMPDIR)
return;
}
else if (! invert_permissions)
return;
else
{
struct stat st;
if (! current_stat_info)
{
if (stat (file_name, &st) != 0)
{
stat_error (file_name);
return;
}
current_stat_info = &st;
}
mode = current_stat_info->st_mode ^ invert_permissions;
}
if (chmod (file_name, mode) != 0)
chmod_error_details (file_name, mode);
}
static void
check_time (char const *file_name, time_t t)
{
time_t now;
if (start_time < t && (now = time (0)) < t)
WARN ((0, 0, _("%s: time stamp %s is %lu s in the future"),
file_name, tartime (t), (unsigned long) (t - now)));
}
static void
set_stat (char const *file_name, struct stat const *stat_info,
struct stat const *current_stat_info,
mode_t invert_permissions, enum permstatus permstatus,
char typeflag)
{
struct utimbuf utimbuf;
if (typeflag != SYMTYPE)
{
if (! touch_option && permstatus != INTERDIR_PERMSTATUS)
{
if (incremental_option)
utimbuf.actime = stat_info->st_atime;
else
utimbuf.actime = start_time;
utimbuf.modtime = stat_info->st_mtime;
if (utime (file_name, &utimbuf) < 0)
utime_error (file_name);
else
{
check_time (file_name, stat_info->st_atime);
check_time (file_name, stat_info->st_mtime);
}
}
set_mode (file_name, stat_info, current_stat_info,
invert_permissions, permstatus, typeflag);
}
if (0 < same_owner_option && permstatus != INTERDIR_PERMSTATUS)
{
if (typeflag == SYMTYPE)
{
#if HAVE_LCHOWN
if (lchown (file_name, stat_info->st_uid, stat_info->st_gid) < 0)
chown_error_details (file_name,
stat_info->st_uid, stat_info->st_gid);
#endif
}
else
{
if (chown (file_name, stat_info->st_uid, stat_info->st_gid) < 0)
chown_error_details (file_name,
stat_info->st_uid, stat_info->st_gid);
if (stat_info->st_mode & (S_ISUID | S_ISGID | S_ISVTX))
set_mode (file_name, stat_info, 0,
invert_permissions, permstatus, typeflag);
}
}
}
static void
delay_set_stat (char const *file_name, struct stat const *stat_info,
mode_t invert_permissions, enum permstatus permstatus)
{
size_t file_name_len = strlen (file_name);
struct delayed_set_stat *data =
xmalloc (offsetof (struct delayed_set_stat, file_name)
+ file_name_len + 1);
data->file_name_len = file_name_len;
strcpy (data->file_name, file_name);
data->invert_permissions = invert_permissions;
data->permstatus = permstatus;
data->after_symlinks = 0;
data->stat_info = *stat_info;
data->next = delayed_set_stat_head;
delayed_set_stat_head = data;
}
static void
repair_delayed_set_stat (char const *dir_name,
struct stat const *dir_stat_info)
{
struct delayed_set_stat *data;
for (data = delayed_set_stat_head; data; data = data->next)
{
struct stat st;
if (stat (data->file_name, &st) != 0)
{
stat_error (data->file_name);
return;
}
if (st.st_dev == dir_stat_info->st_dev
&& st.st_ino == dir_stat_info->st_ino)
{
data->stat_info = current_stat;
data->invert_permissions = (MODE_RWX
& (current_stat.st_mode ^ st.st_mode));
data->permstatus = ARCHIVED_PERMSTATUS;
return;
}
}
ERROR ((0, 0, _("%s: Unexpected inconsistency when making directory"),
quotearg_colon (dir_name)));
}
static int
make_directories (char *file_name)
{
char *cursor0 = file_name + FILESYSTEM_PREFIX_LEN (file_name);
char *cursor;
int did_something = 0;
int mode;
int invert_permissions;
int status;
for (cursor = cursor0; *cursor; cursor++)
{
if (! ISSLASH (*cursor))
continue;
if (cursor == cursor0 || ISSLASH (cursor[-1]))
continue;
if (cursor[-1] == '.'
&& (cursor == cursor0 + 1 || ISSLASH (cursor[-2])
|| (cursor[-2] == '.'
&& (cursor == cursor0 + 2 || ISSLASH (cursor[-3])))))
continue;
*cursor = '\0';
mode = MODE_RWX & ~ newdir_umask;
invert_permissions = we_are_root ? 0 : MODE_WXUSR & ~ mode;
status = mkdir (file_name, mode ^ invert_permissions);
if (status == 0)
{
delay_set_stat (file_name,
¤t_stat ,
invert_permissions, INTERDIR_PERMSTATUS);
print_for_mkdir (file_name, cursor - file_name, mode);
did_something = 1;
*cursor = '/';
continue;
}
*cursor = '/';
if (errno == EEXIST
#if MSDOS
|| errno == EACCES
#endif
)
continue;
break;
}
return did_something;
}
static int
prepare_to_extract (char const *file_name)
{
if (to_stdout_option)
return 0;
if (old_files_option == UNLINK_FIRST_OLD_FILES
&& !remove_any_file (file_name, recursive_unlink_option)
&& errno && errno != ENOENT)
{
unlink_error (file_name);
return 0;
}
return 1;
}
static int
maybe_recoverable (char *file_name, int *interdir_made)
{
if (*interdir_made)
return 0;
switch (errno)
{
case EEXIST:
switch (old_files_option)
{
default:
return 0;
case DEFAULT_OLD_FILES:
case OVERWRITE_OLD_DIRS:
case OVERWRITE_OLD_FILES:
{
int r = remove_any_file (file_name, 0);
errno = EEXIST;
return r;
}
}
case ENOENT:
if (! make_directories (file_name))
{
errno = ENOENT;
return 0;
}
*interdir_made = 1;
return 1;
default:
return 0;
}
}
static void
extract_sparse_file (int fd, off_t *sizeleft, off_t totalsize, char *name)
{
int sparse_ind = 0;
while (*sizeleft > 0)
{
size_t written;
size_t count;
union block *data_block = find_next_block ();
if (! data_block)
{
ERROR ((0, 0, _("Unexpected EOF in archive")));
return;
}
if (lseek (fd, sparsearray[sparse_ind].offset, SEEK_SET) < 0)
{
seek_error_details (name, sparsearray[sparse_ind].offset);
return;
}
written = sparsearray[sparse_ind++].numbytes;
while (written > BLOCKSIZE)
{
count = full_write (fd, data_block->buffer, BLOCKSIZE);
written -= count;
*sizeleft -= count;
if (count != BLOCKSIZE)
{
write_error_details (name, count, BLOCKSIZE);
return;
}
set_next_block_after (data_block);
data_block = find_next_block ();
if (! data_block)
{
ERROR ((0, 0, _("Unexpected EOF in archive")));
return;
}
}
count = full_write (fd, data_block->buffer, written);
*sizeleft -= count;
if (count != written)
{
write_error_details (name, count, written);
return;
}
set_next_block_after (data_block);
}
}
static void
apply_nonancestor_delayed_set_stat (char const *file_name, bool after_symlinks)
{
size_t file_name_len = strlen (file_name);
bool check_for_renamed_directories = 0;
while (delayed_set_stat_head)
{
struct delayed_set_stat *data = delayed_set_stat_head;
bool skip_this_one = 0;
struct stat st;
struct stat const *current_stat_info = 0;
check_for_renamed_directories |= data->after_symlinks;
if (after_symlinks < data->after_symlinks
|| (data->file_name_len < file_name_len
&& file_name[data->file_name_len]
&& (ISSLASH (file_name[data->file_name_len])
|| ISSLASH (file_name[data->file_name_len - 1]))
&& memcmp (file_name, data->file_name, data->file_name_len) == 0))
break;
if (check_for_renamed_directories)
{
current_stat_info = &st;
if (stat (data->file_name, &st) != 0)
{
stat_error (data->file_name);
skip_this_one = 1;
}
else if (! (st.st_dev == data->stat_info.st_dev
&& (st.st_ino == data->stat_info.st_ino)))
{
ERROR ((0, 0,
_("%s: Directory renamed before its status could be extracted"),
quotearg_colon (data->file_name)));
skip_this_one = 1;
}
}
if (! skip_this_one)
set_stat (data->file_name, &data->stat_info, current_stat_info,
data->invert_permissions, data->permstatus, DIRTYPE);
delayed_set_stat_head = data->next;
free (data);
}
}
void
extract_archive (void)
{
union block *data_block;
int fd;
int status;
size_t count;
size_t name_length;
size_t written;
int openflag;
mode_t mode;
off_t size;
size_t skipcrud;
int counter;
int interdir_made = 0;
char typeflag;
union block *exhdr;
#define CURRENT_FILE_NAME (skipcrud + current_file_name)
set_next_block_after (current_header);
decode_header (current_header, ¤t_stat, ¤t_format, 1);
if (interactive_option && !confirm ("extract", current_file_name))
{
skip_member ();
return;
}
if (verbose_option)
print_header ();
skipcrud = 0;
if (! absolute_names_option)
{
if (contains_dot_dot (CURRENT_FILE_NAME))
{
ERROR ((0, 0, _("%s: Member name contains `..'"),
quotearg_colon (CURRENT_FILE_NAME)));
skip_member ();
return;
}
skipcrud = FILESYSTEM_PREFIX_LEN (current_file_name);
while (ISSLASH (CURRENT_FILE_NAME[0]))
skipcrud++;
if (skipcrud)
{
static int warned_once;
if (!warned_once)
{
warned_once = 1;
WARN ((0, 0, _("Removing leading `%.*s' from member names"),
(int) skipcrud, current_file_name));
}
}
}
apply_nonancestor_delayed_set_stat (CURRENT_FILE_NAME, 0);
if (backup_option && !to_stdout_option)
if (!maybe_backup_file (CURRENT_FILE_NAME, 0))
{
int e = errno;
ERROR ((0, e, _("%s: Was unable to backup this file"),
quotearg_colon (CURRENT_FILE_NAME)));
skip_member ();
return;
}
typeflag = current_header->header.typeflag;
switch (typeflag)
{
case GNUTYPE_SPARSE:
sp_array_size = 10;
sparsearray =
xmalloc (sp_array_size * sizeof (struct sp_array));
for (counter = 0; counter < SPARSES_IN_OLDGNU_HEADER; counter++)
{
struct sparse const *s = ¤t_header->oldgnu_header.sp[counter];
sparsearray[counter].offset = OFF_FROM_HEADER (s->offset);
sparsearray[counter].numbytes = SIZE_FROM_HEADER (s->numbytes);
if (!sparsearray[counter].numbytes)
break;
}
if (current_header->oldgnu_header.isextended)
{
int ind = SPARSES_IN_OLDGNU_HEADER;
while (1)
{
exhdr = find_next_block ();
if (! exhdr)
{
ERROR ((0, 0, _("Unexpected EOF in archive")));
return;
}
for (counter = 0; counter < SPARSES_IN_SPARSE_HEADER; counter++)
{
struct sparse const *s = &exhdr->sparse_header.sp[counter];
if (counter + ind > sp_array_size - 1)
{
sp_array_size *= 2;
sparsearray =
xrealloc (sparsearray,
sp_array_size * sizeof (struct sp_array));
}
if (s->numbytes[0] == 0)
break;
sparsearray[counter + ind].offset =
OFF_FROM_HEADER (s->offset);
sparsearray[counter + ind].numbytes =
SIZE_FROM_HEADER (s->numbytes);
}
if (!exhdr->sparse_header.isextended)
break;
else
{
ind += SPARSES_IN_SPARSE_HEADER;
set_next_block_after (exhdr);
}
}
set_next_block_after (exhdr);
}
case AREGTYPE:
case REGTYPE:
case CONTTYPE:
name_length = strlen (CURRENT_FILE_NAME);
if (FILESYSTEM_PREFIX_LEN (CURRENT_FILE_NAME) < name_length
&& CURRENT_FILE_NAME[name_length - 1] == '/')
goto really_dir;
again_file:
openflag = (O_WRONLY | O_BINARY | O_CREAT
| (old_files_option == OVERWRITE_OLD_FILES
? O_TRUNC
: O_EXCL));
mode = current_stat.st_mode & MODE_RWX & ~ current_umask;
if (to_stdout_option)
{
fd = STDOUT_FILENO;
goto extract_file;
}
if (! prepare_to_extract (CURRENT_FILE_NAME))
{
skip_member ();
if (backup_option)
undo_last_backup ();
break;
}
#if O_CTG
if (typeflag == CONTTYPE)
fd = open (CURRENT_FILE_NAME, openflag | O_CTG,
mode, current_stat.st_size);
else
fd = open (CURRENT_FILE_NAME, openflag, mode);
#else
if (typeflag == CONTTYPE)
{
static int conttype_diagnosed;
if (!conttype_diagnosed)
{
conttype_diagnosed = 1;
WARN ((0, 0, _("Extracting contiguous files as regular files")));
}
}
fd = open (CURRENT_FILE_NAME, openflag, mode);
#endif
if (fd < 0)
{
if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made))
goto again_file;
open_error (CURRENT_FILE_NAME);
skip_member ();
if (backup_option)
undo_last_backup ();
break;
}
extract_file:
if (typeflag == GNUTYPE_SPARSE)
{
char *name;
size_t name_length_bis;
name_length_bis = strlen (CURRENT_FILE_NAME) + 1;
name = xmalloc (name_length_bis);
memcpy (name, CURRENT_FILE_NAME, name_length_bis);
size = current_stat.st_size;
extract_sparse_file (fd, &size, current_stat.st_size, name);
free (sparsearray);
}
else
for (size = current_stat.st_size; size > 0; )
{
if (multi_volume_option)
{
assign_string (&save_name, current_file_name);
save_totsize = current_stat.st_size;
save_sizeleft = size;
}
data_block = find_next_block ();
if (! data_block)
{
ERROR ((0, 0, _("Unexpected EOF in archive")));
break;
}
written = available_space_after (data_block);
if (written > size)
written = size;
errno = 0;
count = full_write (fd, data_block->buffer, written);
size -= count;
set_next_block_after ((union block *)
(data_block->buffer + written - 1));
if (count != written)
{
write_error_details (CURRENT_FILE_NAME, count, written);
break;
}
}
skip_file (size);
if (multi_volume_option)
assign_string (&save_name, 0);
if (to_stdout_option)
break;
status = close (fd);
if (status < 0)
{
close_error (CURRENT_FILE_NAME);
if (backup_option)
undo_last_backup ();
}
set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, 0,
(old_files_option == OVERWRITE_OLD_FILES
? UNKNOWN_PERMSTATUS
: ARCHIVED_PERMSTATUS),
typeflag);
break;
case SYMTYPE:
#ifdef HAVE_SYMLINK
if (! prepare_to_extract (CURRENT_FILE_NAME))
break;
if (absolute_names_option
|| ! (ISSLASH (current_link_name
[FILESYSTEM_PREFIX_LEN (current_link_name)])
|| contains_dot_dot (current_link_name)))
{
while (status = symlink (current_link_name, CURRENT_FILE_NAME),
status != 0)
if (!maybe_recoverable (CURRENT_FILE_NAME, &interdir_made))
break;
if (status == 0)
set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, 0, 0, SYMTYPE);
else
symlink_error (current_link_name, CURRENT_FILE_NAME);
}
else
{
struct stat st;
while (fd = open (CURRENT_FILE_NAME, O_WRONLY | O_CREAT | O_EXCL, 0),
fd < 0)
if (! maybe_recoverable (CURRENT_FILE_NAME, &interdir_made))
break;
status = -1;
if (fd < 0)
open_error (CURRENT_FILE_NAME);
else if (fstat (fd, &st) != 0)
{
stat_error (CURRENT_FILE_NAME);
close (fd);
}
else if (close (fd) != 0)
close_error (CURRENT_FILE_NAME);
else
{
struct delayed_set_stat *h;
struct delayed_symlink *p =
xmalloc (offsetof (struct delayed_symlink, target)
+ strlen (current_link_name) + 1);
p->next = delayed_symlink_head;
delayed_symlink_head = p;
p->dev = st.st_dev;
p->ino = st.st_ino;
p->mtime = st.st_mtime;
p->uid = current_stat.st_uid;
p->gid = current_stat.st_gid;
p->sources = xmalloc (offsetof (struct string_list, string)
+ strlen (CURRENT_FILE_NAME) + 1);
p->sources->next = 0;
strcpy (p->sources->string, CURRENT_FILE_NAME);
strcpy (p->target, current_link_name);
h = delayed_set_stat_head;
if (h && ! h->after_symlinks
&& strncmp (CURRENT_FILE_NAME, h->file_name, h->file_name_len) == 0
&& ISSLASH (CURRENT_FILE_NAME[h->file_name_len])
&& (base_name (CURRENT_FILE_NAME)
== CURRENT_FILE_NAME + h->file_name_len + 1))
{
do
{
h->after_symlinks = 1;
if (stat (h->file_name, &st) != 0)
stat_error (h->file_name);
else
{
h->stat_info.st_dev = st.st_dev;
h->stat_info.st_ino = st.st_ino;
}
}
while ((h = h->next) && ! h->after_symlinks);
}
status = 0;
}
}
if (status != 0 && backup_option)
undo_last_backup ();
break;
#else
{
static int warned_once;
if (!warned_once)
{
warned_once = 1;
WARN ((0, 0,
_("Attempting extraction of symbolic links as hard links")));
}
}
typeflag = LNKTYPE;
#endif
case LNKTYPE:
if (! prepare_to_extract (CURRENT_FILE_NAME))
break;
again_link:
{
struct stat st1, st2;
int e;
status = link (current_link_name, CURRENT_FILE_NAME);
if (status == 0)
{
struct delayed_symlink *ds = delayed_symlink_head;
if (ds && stat (current_link_name, &st1) == 0)
for (; ds; ds = ds->next)
if (ds->dev == st1.st_dev
&& ds->ino == st1.st_ino
&& ds->mtime == st1.st_mtime)
{
struct string_list *p =
xmalloc (offsetof (struct string_list, string)
+ strlen (CURRENT_FILE_NAME) + 1);
strcpy (p->string, CURRENT_FILE_NAME);
p->next = ds->sources;
ds->sources = p;
break;
}
break;
}
if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made))
goto again_link;
if (incremental_option && errno == EEXIST)
break;
e = errno;
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;
link_error (current_link_name, CURRENT_FILE_NAME);
if (backup_option)
undo_last_backup ();
}
break;
#if S_IFCHR
case CHRTYPE:
current_stat.st_mode |= S_IFCHR;
goto make_node;
#endif
#if S_IFBLK
case BLKTYPE:
current_stat.st_mode |= S_IFBLK;
#endif
#if S_IFCHR || S_IFBLK
make_node:
if (! prepare_to_extract (CURRENT_FILE_NAME))
break;
status = mknod (CURRENT_FILE_NAME, current_stat.st_mode,
current_stat.st_rdev);
if (status != 0)
{
if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made))
goto make_node;
mknod_error (CURRENT_FILE_NAME);
if (backup_option)
undo_last_backup ();
break;
};
set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, 0,
ARCHIVED_PERMSTATUS, typeflag);
break;
#endif
#if HAVE_MKFIFO || defined mkfifo
case FIFOTYPE:
if (! prepare_to_extract (CURRENT_FILE_NAME))
break;
while (status = mkfifo (CURRENT_FILE_NAME, current_stat.st_mode),
status != 0)
if (!maybe_recoverable (CURRENT_FILE_NAME, &interdir_made))
break;
if (status == 0)
set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, 0,
ARCHIVED_PERMSTATUS, typeflag);
else
{
mkfifo_error (CURRENT_FILE_NAME);
if (backup_option)
undo_last_backup ();
}
break;
#endif
case DIRTYPE:
case GNUTYPE_DUMPDIR:
name_length = strlen (CURRENT_FILE_NAME);
really_dir:
while (FILESYSTEM_PREFIX_LEN (CURRENT_FILE_NAME) < name_length
&& CURRENT_FILE_NAME[name_length - 1] == '/')
name_length--;
CURRENT_FILE_NAME[name_length] = '\0';
if (incremental_option)
{
gnu_restore (skipcrud);
}
else if (typeflag == GNUTYPE_DUMPDIR)
skip_member ();
if (! prepare_to_extract (CURRENT_FILE_NAME))
break;
mode = ((current_stat.st_mode
| (we_are_root ? 0 : MODE_WXUSR))
& MODE_RWX);
again_dir:
status = mkdir (CURRENT_FILE_NAME, mode);
if (status != 0)
{
if (errno == EEXIST
&& (interdir_made
|| old_files_option == OVERWRITE_OLD_DIRS
|| old_files_option == OVERWRITE_OLD_FILES))
{
struct stat st;
if (stat (CURRENT_FILE_NAME, &st) == 0)
{
if (interdir_made)
{
repair_delayed_set_stat (CURRENT_FILE_NAME, &st);
break;
}
if (S_ISDIR (st.st_mode))
{
mode = st.st_mode & ~ current_umask;
goto directory_exists;
}
}
errno = EEXIST;
}
if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made))
goto again_dir;
if (errno != EEXIST)
{
mkdir_error (CURRENT_FILE_NAME);
if (backup_option)
undo_last_backup ();
break;
}
}
directory_exists:
if (status == 0
|| old_files_option == OVERWRITE_OLD_DIRS
|| old_files_option == OVERWRITE_OLD_FILES)
delay_set_stat (CURRENT_FILE_NAME, ¤t_stat,
MODE_RWX & (mode ^ current_stat.st_mode),
(status == 0
? ARCHIVED_PERMSTATUS
: UNKNOWN_PERMSTATUS));
break;
case GNUTYPE_VOLHDR:
if (verbose_option)
fprintf (stdlis, _("Reading %s\n"), quote (current_file_name));
break;
case GNUTYPE_NAMES:
extract_mangle ();
break;
case GNUTYPE_MULTIVOL:
ERROR ((0, 0,
_("%s: Cannot extract -- file is continued from another volume"),
quotearg_colon (current_file_name)));
skip_member ();
if (backup_option)
undo_last_backup ();
break;
case GNUTYPE_LONGNAME:
case GNUTYPE_LONGLINK:
ERROR ((0, 0, _("Visible long name error")));
skip_member ();
if (backup_option)
undo_last_backup ();
break;
default:
WARN ((0, 0,
_("%s: Unknown file type '%c', extracted as normal file"),
quotearg_colon (CURRENT_FILE_NAME), typeflag));
goto again_file;
}
#undef CURRENT_FILE_NAME
}
static void
apply_delayed_symlinks (void)
{
struct delayed_symlink *ds;
for (ds = delayed_symlink_head; ds; )
{
struct string_list *sources = ds->sources;
char const *valid_source = 0;
for (sources = ds->sources; sources; sources = sources->next)
{
char const *source = sources->string;
struct stat st;
if (lstat (source, &st) == 0
&& st.st_dev == ds->dev
&& st.st_ino == ds->ino
&& st.st_mtime == ds->mtime)
{
if (unlink (source) != 0)
unlink_error (source);
else if (valid_source && link (valid_source, source) == 0)
;
else if (symlink (ds->target, source) != 0)
symlink_error (ds->target, source);
else
{
valid_source = source;
st.st_uid = ds->uid;
st.st_gid = ds->gid;
set_stat (source, &st, 0, 0, 0, SYMTYPE);
}
}
}
for (sources = ds->sources; sources; )
{
struct string_list *next = sources->next;
free (sources);
sources = next;
}
{
struct delayed_symlink *next = ds->next;
free (ds);
ds = next;
}
}
delayed_symlink_head = 0;
}
void
extract_finish (void)
{
apply_nonancestor_delayed_set_stat ("", 0);
apply_delayed_symlinks ();
apply_nonancestor_delayed_set_stat ("", 1);
}
void
fatal_exit (void)
{
extract_finish ();
error (TAREXIT_FAILURE, 0, _("Error is not recoverable: exiting now"));
abort ();
}