#include <system.h>
#include <quotearg.h>
#include <utimens.h>
#include <errno.h>
#include <xgetcwd.h>
#include "common.h"
#ifdef __APPLE__
#include <libgen.h>
#include <sys/queue.h>
#include <copyfile.h>
struct copyfile_list_entry_t {
char *src;
char *dst;
char *tmp;
LIST_ENTRY(copyfile_list_entry_t) link;
} *cle;
extern LIST_HEAD(copyfile_list_t, copyfile_list_entry_t) copyfile_list;
#endif
#ifdef __APPLE__
#include <sys/mount.h>
#endif
static bool 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;
dev_t dev;
ino_t ino;
mode_t mode;
uid_t uid;
gid_t gid;
struct timespec atime;
struct timespec mtime;
size_t file_name_len;
mode_t invert_permissions;
enum permstatus permstatus;
bool after_links;
char file_name[1];
};
static struct delayed_set_stat *delayed_set_stat_head;
struct delayed_link
{
struct delayed_link *next;
dev_t dev;
ino_t ino;
struct timespec mtime;
bool is_symlink;
uid_t uid;
gid_t gid;
struct string_list *sources;
char target[1];
};
static struct delayed_link *delayed_link_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;
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 *cur_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 & ~ (0 < same_owner_option ? S_IRWXU : MODE_RWX)))
&& typeflag != DIRTYPE
&& typeflag != GNUTYPE_DUMPDIR)
return;
}
else if (! invert_permissions)
return;
else
{
struct stat st;
if (! cur_info)
{
if (stat (file_name, &st) != 0)
{
stat_error (file_name);
return;
}
cur_info = &st;
}
mode = cur_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, struct timespec t)
{
if (t.tv_sec <= 0)
WARN ((0, 0, _("%s: implausibly old time stamp %s"),
file_name, tartime (t, true)));
else if (timespec_cmp (volume_start_time, t) < 0)
{
struct timespec now;
gettime (&now);
if (timespec_cmp (now, t) < 0)
{
char buf[TIMESPEC_STRSIZE_BOUND];
struct timespec diff;
diff.tv_sec = t.tv_sec - now.tv_sec;
diff.tv_nsec = t.tv_nsec - now.tv_nsec;
if (diff.tv_nsec < 0)
{
diff.tv_nsec += BILLION;
diff.tv_sec--;
}
WARN ((0, 0, _("%s: time stamp %s is %s s in the future"),
file_name, tartime (t, true), code_timespec (diff, buf)));
}
}
}
static void
set_stat (char const *file_name,
struct tar_stat_info const *st,
struct stat const *cur_info,
mode_t invert_permissions, enum permstatus permstatus,
char typeflag)
{
if (typeflag != SYMTYPE)
{
if (! touch_option && permstatus != INTERDIR_PERMSTATUS)
{
struct timespec ts[2];
if (incremental_option)
ts[0] = st->atime;
else
ts[0] = start_time;
ts[1] = st->mtime;
if (utimens (file_name, ts) != 0)
utime_error (file_name);
else
{
check_time (file_name, ts[0]);
check_time (file_name, ts[1]);
}
}
}
if (0 < same_owner_option && permstatus != INTERDIR_PERMSTATUS)
{
int chown_result = 1;
if (typeflag == SYMTYPE)
{
#if HAVE_LCHOWN
chown_result = lchown (file_name, st->stat.st_uid, st->stat.st_gid);
#endif
}
else
{
chown_result = chown (file_name, st->stat.st_uid, st->stat.st_gid);
}
if (chown_result == 0)
{
if (cur_info
&& cur_info->st_mode & S_IXUGO
&& cur_info->st_mode & (S_ISUID | S_ISGID))
cur_info = NULL;
}
else if (chown_result < 0)
chown_error_details (file_name,
st->stat.st_uid, st->stat.st_gid);
}
if (typeflag != SYMTYPE)
set_mode (file_name, &st->stat, cur_info,
invert_permissions, permstatus, typeflag);
}
static void
delay_set_stat (char const *file_name, struct tar_stat_info const *st,
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->next = delayed_set_stat_head;
data->dev = st->stat.st_dev;
data->ino = st->stat.st_ino;
data->mode = st->stat.st_mode;
data->uid = st->stat.st_uid;
data->gid = st->stat.st_gid;
data->atime = st->atime;
data->mtime = st->mtime;
data->file_name_len = file_name_len;
data->invert_permissions = invert_permissions;
data->permstatus = permstatus;
data->after_links = 0;
strcpy (data->file_name, file_name);
delayed_set_stat_head = data;
}
static void
repair_delayed_set_stat (char const *dir,
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->dev = current_stat_info.stat.st_dev;
data->ino = current_stat_info.stat.st_ino;
data->mode = current_stat_info.stat.st_mode;
data->uid = current_stat_info.stat.st_uid;
data->gid = current_stat_info.stat.st_gid;
data->atime = current_stat_info.atime;
data->mtime = current_stat_info.mtime;
data->invert_permissions =
((current_stat_info.stat.st_mode ^ st.st_mode)
& MODE_RWX & ~ current_umask);
data->permstatus = ARCHIVED_PERMSTATUS;
return;
}
}
ERROR ((0, 0, _("%s: Unexpected inconsistency when making directory"),
quotearg_colon (dir)));
}
#if HAVE_QUARANTINE
void
apply_qtn(int fd)
{
int stat_ok;
struct stat sb;
int qstatus;
if (archive_qtn_file != NULL) {
stat_ok = (fstat(fd, &sb) == 0);
if (stat_ok) fchmod(fd, sb.st_mode | S_IWUSR);
qstatus = qtn_file_apply_to_fd(archive_qtn_file, fd);
if (stat_ok) fchmod(fd, sb.st_mode);
if (qstatus) {
warnx("qtn_file_apply_to_fd: %s", qtn_error(qstatus));
}
}
}
void
apply_qtn_to_path(char *path)
{
int stat_ok;
struct stat sb;
int qstatus;
if (archive_qtn_file != NULL) {
stat_ok = (stat(path, &sb) == 0);
if (stat_ok) chmod(path, sb.st_mode | S_IWUSR);
qstatus = qtn_file_apply_to_path(archive_qtn_file, path);
if (stat_ok) chmod(path, sb.st_mode);
if (qstatus) {
warnx("qtn_file_apply_to_path: %s", qtn_error(qstatus));
}
}
}
#else
void apply_qtn(int fd) {}
void apply_qtn_to_path(char *path) {}
#endif
static int
make_directories (char *file_name)
{
char *cursor0 = file_name + FILE_SYSTEM_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)
{
apply_qtn_to_path(file_name);
delay_set_stat (file_name,
¤t_stat_info,
invert_permissions, INTERDIR_PERMSTATUS);
print_for_mkdir (file_name, cursor - file_name, mode);
did_something = 1;
*cursor = '/';
continue;
}
*cursor = '/';
if (errno == EEXIST)
continue;
else if ((errno == ENOSYS
|| ERRNO_IS_EACCES)
&& access (file_name, W_OK) == 0)
continue;
break;
}
return did_something;
}
static bool
file_newer_p (const char *file_name, struct tar_stat_info *tar_stat)
{
struct stat st;
if (stat (file_name, &st))
{
stat_warn (file_name);
return errno != ENOENT;
}
if (!S_ISDIR (st.st_mode)
&& tar_timespec_cmp (tar_stat->mtime, get_stat_mtime (&st)) <= 0)
{
return true;
}
return false;
}
static int
maybe_recoverable (char *file_name, int *interdir_made)
{
int e = errno;
if (*interdir_made)
return 0;
switch (errno)
{
case EEXIST:
switch (old_files_option)
{
case KEEP_OLD_FILES:
return 0;
case KEEP_NEWER_FILES:
if (file_newer_p (file_name, ¤t_stat_info))
{
errno = e;
return 0;
}
case DEFAULT_OLD_FILES:
case NO_OVERWRITE_DIR_OLD_FILES:
case OVERWRITE_OLD_FILES:
{
int r = remove_any_file (file_name, ORDINARY_REMOVE_OPTION);
errno = EEXIST;
return r;
}
case UNLINK_FIRST_OLD_FILES:
break;
}
case ENOENT:
if (! make_directories (file_name))
{
errno = ENOENT;
return 0;
}
*interdir_made = 1;
return 1;
default:
return 0;
}
}
static void
apply_nonancestor_delayed_set_stat (char const *file_name, bool after_links)
{
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 *cur_info = 0;
check_for_renamed_directories |= data->after_links;
if (after_links < data->after_links
|| (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)
{
cur_info = &st;
if (stat (data->file_name, &st) != 0)
{
stat_error (data->file_name);
skip_this_one = 1;
}
else if (! (st.st_dev == data->dev && st.st_ino == data->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)
{
struct tar_stat_info st;
st.stat.st_mode = data->mode;
st.stat.st_uid = data->uid;
st.stat.st_gid = data->gid;
st.atime = data->atime;
st.mtime = data->mtime;
set_stat (data->file_name, &st, cur_info,
data->invert_permissions, data->permstatus, DIRTYPE);
}
delayed_set_stat_head = data->next;
free (data);
}
}
static int
extract_dir (char *file_name, int typeflag)
{
int status;
mode_t mode;
int interdir_made = 0;
if (one_file_system_option && root_device == 0)
{
struct stat st;
char *dir = xgetcwd ();
if (deref_stat (true, dir, &st))
stat_diag (dir);
else
root_device = st.st_dev;
free (dir);
}
if (incremental_option)
purge_directory (file_name);
else if (typeflag == GNUTYPE_DUMPDIR)
skip_member ();
mode = current_stat_info.stat.st_mode | (we_are_root ? 0 : MODE_WXUSR);
if (0 < same_owner_option || current_stat_info.stat.st_mode & ~ MODE_RWX)
mode &= S_IRWXU;
while ((status = mkdir (file_name, mode)))
{
if (errno == EEXIST
&& (interdir_made
|| old_files_option == DEFAULT_OLD_FILES
|| old_files_option == OVERWRITE_OLD_FILES))
{
struct stat st;
if (stat (file_name, &st) == 0)
{
if (interdir_made)
{
repair_delayed_set_stat (file_name, &st);
return 0;
}
if (S_ISDIR (st.st_mode))
{
mode = st.st_mode;
break;
}
}
errno = EEXIST;
}
if (maybe_recoverable (file_name, &interdir_made))
continue;
if (errno != EEXIST)
{
mkdir_error (file_name);
return 1;
}
break;
}
if (status == 0
|| old_files_option == DEFAULT_OLD_FILES
|| old_files_option == OVERWRITE_OLD_FILES)
{
apply_qtn_to_path(file_name);
if (status == 0)
delay_set_stat (file_name, ¤t_stat_info,
((mode ^ current_stat_info.stat.st_mode)
& MODE_RWX & ~ current_umask),
ARCHIVED_PERMSTATUS);
else
delay_set_stat (file_name, ¤t_stat_info,
0,
UNKNOWN_PERMSTATUS);
}
return status;
}
static int
open_output_file (char *file_name, int typeflag, mode_t mode)
{
int fd;
int openflag = (O_WRONLY | O_BINARY | O_CREAT
| (old_files_option == OVERWRITE_OLD_FILES
? O_TRUNC
: O_EXCL));
#if O_CTG
if (typeflag == CONTTYPE)
fd = open (file_name, openflag | O_CTG, mode, current_stat_info.stat.st_size);
else
fd = open (file_name, openflag, mode);
#else
if (typeflag == CONTTYPE)
{
static int conttype_diagnosed;
#ifdef __APPLE__
#endif
if (!conttype_diagnosed)
{
conttype_diagnosed = 1;
WARN ((0, 0, _("Extracting contiguous files as regular files")));
}
}
fd = open (file_name, openflag, mode);
#endif
return fd;
}
static int
extract_file (char *file_name, int typeflag)
{
int fd;
off_t size;
union block *data_block;
int status;
size_t count;
size_t written;
int interdir_made = 0;
mode_t mode = current_stat_info.stat.st_mode & MODE_RWX & ~ current_umask;
mode_t invert_permissions =
0 < same_owner_option ? mode & (S_IRWXG | S_IRWXO) : 0;
if (to_stdout_option)
fd = STDOUT_FILENO;
else if (to_command_option)
{
fd = sys_exec_command (file_name, 'f', ¤t_stat_info);
if (fd < 0)
{
skip_member ();
return 0;
}
}
else
{
do
fd = open_output_file (file_name, typeflag, mode ^ invert_permissions);
while (fd < 0 && maybe_recoverable (file_name, &interdir_made));
if (fd < 0)
{
skip_member ();
open_error (file_name);
return 1;
}
}
#ifdef __APPLE__
if (!current_stat_info.is_sparse) {
fstore_t fst;
fst.fst_flags = 0;
fst.fst_posmode = F_PEOFPOSMODE;
fst.fst_offset = 0;
fst.fst_length = current_stat_info.stat.st_size;
(void)fcntl(fd, F_PREALLOCATE, &fst);
}
#endif
apply_qtn(fd);
mv_begin (¤t_stat_info);
if (current_stat_info.is_sparse)
sparse_extract_file (fd, ¤t_stat_info, &size);
else
for (size = current_stat_info.stat.st_size; size > 0; )
{
mv_size_left (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 -= written;
set_next_block_after ((union block *)
(data_block->buffer + written - 1));
if (count != written)
{
if (!to_command_option)
write_error_details (file_name, count, written);
break;
}
}
skip_file (size);
mv_end ();
if (to_stdout_option)
return 0;
status = close (fd);
if (status < 0)
close_error (file_name);
if (to_command_option)
sys_wait_command ();
else
set_stat (file_name, ¤t_stat_info, NULL, invert_permissions,
(old_files_option == OVERWRITE_OLD_FILES ?
UNKNOWN_PERMSTATUS : ARCHIVED_PERMSTATUS),
typeflag);
return status;
}
static int
create_placeholder_file (char *file_name, bool is_symlink, int *interdir_made)
{
int fd;
struct stat st;
while ((fd = open (file_name, O_WRONLY | O_CREAT | O_EXCL, 0)) < 0)
if (! maybe_recoverable (file_name, interdir_made))
break;
if (fd < 0)
open_error (file_name);
else if (fstat (fd, &st) != 0)
{
stat_error (file_name);
close (fd);
}
else if (close (fd) != 0)
close_error (file_name);
else
{
struct delayed_set_stat *h;
struct delayed_link *p =
xmalloc (offsetof (struct delayed_link, target)
+ strlen (current_stat_info.link_name)
+ 1);
p->next = delayed_link_head;
delayed_link_head = p;
p->dev = st.st_dev;
p->ino = st.st_ino;
p->mtime = get_stat_mtime (&st);
p->is_symlink = is_symlink;
if (is_symlink)
{
p->uid = current_stat_info.stat.st_uid;
p->gid = current_stat_info.stat.st_gid;
}
p->sources = xmalloc (offsetof (struct string_list, string)
+ strlen (file_name) + 1);
p->sources->next = 0;
strcpy (p->sources->string, file_name);
strcpy (p->target, current_stat_info.link_name);
h = delayed_set_stat_head;
if (h && ! h->after_links
&& strncmp (file_name, h->file_name, h->file_name_len) == 0
&& ISSLASH (file_name[h->file_name_len])
&& (last_component (file_name) == file_name + h->file_name_len + 1))
{
do
{
h->after_links = 1;
if (stat (h->file_name, &st) != 0)
stat_error (h->file_name);
else
{
h->dev = st.st_dev;
h->ino = st.st_ino;
}
}
while ((h = h->next) && ! h->after_links);
}
return 0;
}
return -1;
}
static int
extract_link (char *file_name, int typeflag)
{
int interdir_made = 0;
char const *link_name;
transform_member_name (¤t_stat_info.link_name, xform_link);
link_name = current_stat_info.link_name;
if (! absolute_names_option && contains_dot_dot (link_name))
return create_placeholder_file (file_name, false, &interdir_made);
do
{
struct stat st1, st2;
int e;
int status = link (link_name, file_name);
e = errno;
if (status == 0)
{
struct delayed_link *ds = delayed_link_head;
if (ds && lstat (link_name, &st1) == 0)
for (; ds; ds = ds->next)
if (ds->dev == st1.st_dev
&& ds->ino == st1.st_ino
&& timespec_cmp (ds->mtime, get_stat_mtime (&st1)) == 0)
{
struct string_list *p = xmalloc (offsetof (struct string_list, string)
+ strlen (file_name) + 1);
strcpy (p->string, file_name);
p->next = ds->sources;
ds->sources = p;
break;
}
return 0;
}
else if ((e == EEXIST && strcmp (link_name, file_name) == 0)
|| (lstat (link_name, &st1) == 0
&& lstat (file_name, &st2) == 0
&& st1.st_dev == st2.st_dev
&& st1.st_ino == st2.st_ino))
return 0;
errno = e;
}
while (maybe_recoverable (file_name, &interdir_made));
if (!(incremental_option && errno == EEXIST))
{
link_error (link_name, file_name);
return 1;
}
return 0;
}
static int
extract_symlink (char *file_name, int typeflag)
{
#ifdef HAVE_SYMLINK
int status;
int interdir_made = 0;
transform_member_name (¤t_stat_info.link_name, xform_symlink);
if (! absolute_names_option
&& (IS_ABSOLUTE_FILE_NAME (current_stat_info.link_name)
|| contains_dot_dot (current_stat_info.link_name)))
return create_placeholder_file (file_name, true, &interdir_made);
while ((status = symlink (current_stat_info.link_name, file_name)))
if (!maybe_recoverable (file_name, &interdir_made))
break;
if (status == 0)
set_stat (file_name, ¤t_stat_info, NULL, 0, 0, SYMTYPE);
else
symlink_error (current_stat_info.link_name, file_name);
return status;
#else
static int warned_once;
if (!warned_once)
{
warned_once = 1;
WARN ((0, 0, _("Attempting extraction of symbolic links as hard links")));
}
return extract_link (file_name, typeflag);
#endif
}
#if S_IFCHR || S_IFBLK
static int
extract_node (char *file_name, int typeflag)
{
int status;
int interdir_made = 0;
mode_t mode = current_stat_info.stat.st_mode & ~ current_umask;
mode_t invert_permissions =
0 < same_owner_option ? mode & (S_IRWXG | S_IRWXO) : 0;
do
status = mknod (file_name, mode ^ invert_permissions,
current_stat_info.stat.st_rdev);
while (status && maybe_recoverable (file_name, &interdir_made));
if (status != 0)
mknod_error (file_name);
else
set_stat (file_name, ¤t_stat_info, NULL, invert_permissions,
ARCHIVED_PERMSTATUS, typeflag);
return status;
}
#endif
#if HAVE_MKFIFO || defined mkfifo
static int
extract_fifo (char *file_name, int typeflag)
{
int status;
int interdir_made = 0;
mode_t mode = current_stat_info.stat.st_mode & ~ current_umask;
mode_t invert_permissions =
0 < same_owner_option ? mode & (S_IRWXG | S_IRWXO) : 0;
while ((status = mkfifo (file_name, mode)) != 0)
if (!maybe_recoverable (file_name, &interdir_made))
break;
if (status == 0)
set_stat (file_name, ¤t_stat_info, NULL, invert_permissions,
ARCHIVED_PERMSTATUS, typeflag);
else
mkfifo_error (file_name);
return status;
}
#endif
static int
extract_volhdr (char *file_name, int typeflag)
{
if (verbose_option)
fprintf (stdlis, _("Reading %s\n"), quote (current_stat_info.file_name));
skip_member ();
return 0;
}
static int
extract_failure (char *file_name, int typeflag)
{
return 1;
}
typedef int (*tar_extractor_t) (char *file_name, int typeflag);
static int
prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun)
{
int rc = 1;
if (EXTRACT_OVER_PIPE)
rc = 0;
switch (typeflag)
{
case GNUTYPE_SPARSE:
*fun = extract_file;
rc = 1;
break;
case AREGTYPE:
case REGTYPE:
case CONTTYPE:
if (current_stat_info.had_trailing_slash)
*fun = extract_dir;
else
{
*fun = extract_file;
rc = 1;
}
break;
case SYMTYPE:
*fun = extract_symlink;
break;
case LNKTYPE:
*fun = extract_link;
break;
#if S_IFCHR
case CHRTYPE:
current_stat_info.stat.st_mode |= S_IFCHR;
*fun = extract_node;
break;
#endif
#if S_IFBLK
case BLKTYPE:
current_stat_info.stat.st_mode |= S_IFBLK;
*fun = extract_node;
break;
#endif
#if HAVE_MKFIFO || defined mkfifo
case FIFOTYPE:
*fun = extract_fifo;
break;
#endif
case DIRTYPE:
case GNUTYPE_DUMPDIR:
*fun = extract_dir;
if (current_stat_info.is_dumpdir)
delay_directory_restore_option = true;
break;
case GNUTYPE_VOLHDR:
*fun = extract_volhdr;
break;
case GNUTYPE_MULTIVOL:
ERROR ((0, 0,
_("%s: Cannot extract -- file is continued from another volume"),
quotearg_colon (current_stat_info.file_name)));
*fun = extract_failure;
break;
case GNUTYPE_LONGNAME:
case GNUTYPE_LONGLINK:
ERROR ((0, 0, _("Unexpected long name header")));
*fun = extract_failure;
break;
default:
WARN ((0, 0,
_("%s: Unknown file type `%c', extracted as normal file"),
quotearg_colon (file_name), typeflag));
*fun = extract_file;
}
if (rc == 0)
return 0;
switch (old_files_option)
{
case UNLINK_FIRST_OLD_FILES:
if (!remove_any_file (file_name,
recursive_unlink_option ? RECURSIVE_REMOVE_OPTION
: ORDINARY_REMOVE_OPTION)
&& errno && errno != ENOENT)
{
unlink_error (file_name);
return 0;
}
break;
case KEEP_NEWER_FILES:
if (file_newer_p (file_name, ¤t_stat_info))
{
WARN ((0, 0, _("Current %s is newer or same age"),
quote (file_name)));
return 0;
}
break;
default:
break;
}
return 1;
}
void
extract_archive (void)
{
char typeflag;
tar_extractor_t fun;
#ifdef __APPLE__
struct copyfile_list_entry_t *cle = NULL;
#endif
set_next_block_after (current_header);
decode_header (current_header, ¤t_stat_info, ¤t_format, 1);
if (!current_stat_info.file_name[0]
|| (interactive_option
&& !confirm ("extract", current_stat_info.file_name)))
{
skip_member ();
return;
}
if (verbose_option)
print_header (¤t_stat_info, -1);
if (!delay_directory_restore_option)
apply_nonancestor_delayed_set_stat (current_stat_info.file_name, 0);
if (backup_option)
if (!maybe_backup_file (current_stat_info.file_name, 0))
{
int e = errno;
ERROR ((0, e, _("%s: Was unable to backup this file"),
quotearg_colon (current_stat_info.file_name)));
skip_member ();
return;
}
typeflag = sparse_member_p (¤t_stat_info) ?
GNUTYPE_SPARSE : current_header->header.typeflag;
if (prepare_to_extract (current_stat_info.file_name, typeflag, &fun))
{
#ifdef __APPLE__
if (strncmp(basename(current_stat_info.file_name), "._", 2) == 0) {
if ((cle = calloc(1, sizeof(struct copyfile_list_entry_t))) == NULL)
goto err;
if ((cle->src = strdup(current_stat_info.file_name)) == NULL)
goto err;
if (asprintf(&cle->tmp, "%s.XXX", current_stat_info.file_name) == -1)
goto err;
if (mktemp(cle->tmp) == NULL)
goto err;
if (asprintf(&cle->dst, "%s/%s", dirname(current_stat_info.file_name), basename(current_stat_info.file_name) + 2) != -1)
LIST_INSERT_HEAD(©file_list, cle, link);
else {
err:
if (cle->src) free(cle->src);
if (cle->dst) free(cle->dst);
if (cle->tmp) free(cle->tmp);
if (cle) {
free(cle);
cle = NULL;
}
}
}
#endif
if (fun && (*fun) (cle ? cle->tmp : current_stat_info.file_name, typeflag)
&& backup_option)
undo_last_backup ();
}
else
skip_member ();
}
static void
apply_delayed_links (void)
{
struct delayed_link *ds;
for (ds = delayed_link_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
&& timespec_cmp (get_stat_mtime (&st), ds->mtime) == 0)
{
if (unlink (source) != 0)
unlink_error (source);
else if (valid_source && link (valid_source, source) == 0)
;
else if (!ds->is_symlink)
{
if (link (ds->target, source) != 0)
link_error (ds->target, source);
}
else if (symlink (ds->target, source) != 0)
symlink_error (ds->target, source);
else
{
struct tar_stat_info st1;
st1.stat.st_uid = ds->uid;
st1.stat.st_gid = ds->gid;
set_stat (source, &st1, NULL, 0, 0, SYMTYPE);
valid_source = source;
}
}
}
for (sources = ds->sources; sources; )
{
struct string_list *next = sources->next;
free (sources);
sources = next;
}
{
struct delayed_link *next = ds->next;
free (ds);
ds = next;
}
}
delayed_link_head = 0;
}
void
extract_finish (void)
{
apply_nonancestor_delayed_set_stat ("", 0);
apply_delayed_links ();
apply_nonancestor_delayed_set_stat ("", 1);
}
bool
rename_directory (char *src, char *dst)
{
if (rename (src, dst))
{
int e = errno;
switch (e)
{
case ENOENT:
if (make_directories (dst))
{
if (rename (src, dst) == 0)
return true;
e = errno;
}
break;
case EXDEV:
default:
break;
}
ERROR ((0, e, _("Cannot rename %s to %s"),
quote_n (0, src),
quote_n (1, dst)));
return false;
}
return true;
}
void
fatal_exit (void)
{
extract_finish ();
error (TAREXIT_FAILURE, 0, _("Error is not recoverable: exiting now"));
abort ();
}
void
xalloc_die (void)
{
error (0, 0, "%s", _("memory exhausted"));
fatal_exit ();
}