#include "system.h"
#include <time.h>
time_t time ();
#if HAVE_UTIME_H
# include <utime.h>
#else
struct utimbuf
{
long actime;
long modtime;
};
#endif
#include "common.h"
static time_t now;
static int we_are_root;
static mode_t newdir_umask;
static mode_t current_umask;
#if 0
struct sp_array *sparsearray;
int sp_array_size = 10;
#endif
struct delayed_set_stat
{
struct delayed_set_stat *next;
char *file_name;
struct stat stat_info;
};
static struct delayed_set_stat *delayed_set_stat_head;
void
extr_init (void)
{
now = time ((time_t *) 0);
we_are_root = geteuid () == 0;
newdir_umask = umask (0);
if (same_permissions_option)
current_umask = 0;
else
{
umask (newdir_umask);
current_umask = newdir_umask;
}
newdir_umask &= ~ MODE_WXUSR;
}
static void
set_mode (char *file_name, struct stat *stat_info)
{
if (!keep_old_files_option
|| (stat_info->st_mode & (S_ISUID | S_ISGID | S_ISVTX)))
if (chmod (file_name, ~current_umask & stat_info->st_mode) < 0)
ERROR ((0, errno, _("%s: Cannot change mode to %04lo"),
file_name,
(unsigned long) (~current_umask & stat_info->st_mode)));
}
static void
set_stat (char *file_name, struct stat *stat_info, int symlink_flag)
{
struct utimbuf utimbuf;
if (!symlink_flag)
{
if (!touch_option)
{
if (incremental_option)
utimbuf.actime = stat_info->st_atime;
else
utimbuf.actime = now;
utimbuf.modtime = stat_info->st_mtime;
if (utime (file_name, &utimbuf) < 0)
ERROR ((0, errno,
_("%s: Could not change access and modification times"),
file_name));
}
set_mode (file_name, stat_info);
}
if (we_are_root || same_owner_option)
{
#if HAVE_LCHOWN
if (symlink_flag)
{
if (lchown (file_name, stat_info->st_uid, stat_info->st_gid) < 0)
ERROR ((0, errno, _("%s: Cannot lchown to uid %lu gid %lu"),
file_name,
(unsigned long) stat_info->st_uid,
(unsigned long) stat_info->st_gid));
}
else
{
if (chown (file_name, stat_info->st_uid, stat_info->st_gid) < 0)
ERROR ((0, errno, _("%s: Cannot chown to uid %lu gid %lu"),
file_name,
(unsigned long) stat_info->st_uid,
(unsigned long) stat_info->st_gid));
}
#else
if (!symlink_flag)
if (chown (file_name, stat_info->st_uid, stat_info->st_gid) < 0)
ERROR ((0, errno, _("%s: Cannot chown to uid %lu gid %lu"),
file_name,
(unsigned long) stat_info->st_uid,
(unsigned long) stat_info->st_gid));
#endif/* not HAVE_LCHOWN */
if (!symlink_flag)
if (stat_info->st_mode & (S_ISUID | S_ISGID | S_ISVTX))
set_mode (file_name, stat_info);
}
}
static int
make_directories (char *file_name)
{
char *cursor;
int did_something = 0;
int saved_errno = errno;
int status;
for (cursor = strchr (file_name, '/');
cursor != NULL;
cursor = strchr (cursor + 1, '/'))
{
if (cursor == file_name || cursor[-1] == '/')
continue;
if (cursor[-1] == '.' && (cursor == file_name + 1 || cursor[-2] == '/'))
continue;
*cursor = '\0';
status = mkdir (file_name, ~newdir_umask & MODE_RWX);
if (status == 0)
{
if (we_are_root)
if (chown (file_name, current_stat.st_uid, current_stat.st_gid) < 0)
ERROR ((0, errno,
_("%s: Cannot change owner to uid %lu, gid %lu"),
file_name,
(unsigned long) current_stat.st_uid,
(unsigned long) current_stat.st_gid));
print_for_mkdir (file_name, cursor - file_name,
~newdir_umask & MODE_RWX);
did_something = 1;
*cursor = '/';
continue;
}
*cursor = '/';
if (errno == EEXIST
#if MSDOS
|| errno == EACCES
#endif
)
continue;
break;
}
errno = saved_errno;
return did_something;
}
static int
maybe_recoverable (char *file_name)
{
switch (errno)
{
case EEXIST:
if (keep_old_files_option)
return 0;
return remove_any_file (file_name, 0);
case ENOENT:
return make_directories (file_name);
default:
return 0;
}
}
static void
extract_sparse_file (int fd, off_t *sizeleft, off_t totalsize, char *name)
{
int sparse_ind = 0;
size_t written;
ssize_t count;
while (*sizeleft > 0)
{
union block *data_block = find_next_block ();
if (data_block == NULL)
{
ERROR ((0, 0, _("Unexpected EOF on archive file")));
return;
}
if (lseek (fd, sparsearray[sparse_ind].offset, SEEK_SET) < 0)
{
char buf[UINTMAX_STRSIZE_BOUND];
ERROR ((0, errno, _("%s: lseek error at byte %s"),
STRINGIFY_BIGINT (sparsearray[sparse_ind].offset, buf),
name));
return;
}
written = sparsearray[sparse_ind++].numbytes;
while (written > BLOCKSIZE)
{
count = full_write (fd, data_block->buffer, BLOCKSIZE);
if (count < 0)
ERROR ((0, errno, _("%s: Could not write to file"), name));
written -= count;
*sizeleft -= count;
set_next_block_after (data_block);
data_block = find_next_block ();
}
count = full_write (fd, data_block->buffer, written);
if (count < 0)
ERROR ((0, errno, _("%s: Could not write to file"), name));
else if (count != written)
{
char buf1[UINTMAX_STRSIZE_BOUND];
char buf2[UINTMAX_STRSIZE_BOUND];
ERROR ((0, 0, _("%s: Could only write %s of %s bytes"),
name,
STRINGIFY_BIGINT (totalsize - *sizeleft, buf1),
STRINGIFY_BIGINT (totalsize, buf2)));
skip_file (*sizeleft);
}
written -= count;
*sizeleft -= count;
set_next_block_after (data_block);
}
free (sparsearray);
}
void
extract_archive (void)
{
union block *data_block;
int fd;
int status;
ssize_t sstatus;
size_t name_length;
size_t written;
int openflag;
off_t size;
int skipcrud;
int counter;
char typeflag;
#if 0
int sparse_ind = 0;
#endif
union block *exhdr;
struct delayed_set_stat *data;
#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))
{
if (current_header->oldgnu_header.isextended)
skip_extended_headers ();
skip_file (current_stat.st_size);
return;
}
if (verbose_option)
print_header ();
skipcrud = 0;
while (!absolute_names_option && CURRENT_FILE_NAME[0] == '/')
{
static int warned_once = 0;
skipcrud++;
if (!warned_once)
{
warned_once = 1;
WARN ((0, 0, _("\
Removing leading `/' from absolute path names in the archive")));
}
}
if (backup_option && !to_stdout_option)
if (!maybe_backup_file (CURRENT_FILE_NAME, 0))
{
ERROR ((0, errno, _("%s: Was unable to backup this file"),
CURRENT_FILE_NAME));
if (current_header->oldgnu_header.isextended)
skip_extended_headers ();
skip_file (current_stat.st_size);
return;
}
typeflag = current_header->header.typeflag;
switch (typeflag)
{
case GNUTYPE_SPARSE:
sp_array_size = 10;
sparsearray = (struct sp_array *)
xmalloc (sp_array_size * sizeof (struct sp_array));
for (counter = 0; counter < SPARSES_IN_OLDGNU_HEADER; counter++)
{
sparsearray[counter].offset =
OFF_FROM_OCT (current_header->oldgnu_header.sp[counter].offset);
sparsearray[counter].numbytes =
SIZE_FROM_OCT (current_header->oldgnu_header.sp[counter].numbytes);
if (!sparsearray[counter].numbytes)
break;
}
if (current_header->oldgnu_header.isextended)
{
int ind = SPARSES_IN_OLDGNU_HEADER;
while (1)
{
exhdr = find_next_block ();
for (counter = 0; counter < SPARSES_IN_SPARSE_HEADER; counter++)
{
if (counter + ind > sp_array_size - 1)
{
sp_array_size *= 2;
sparsearray = (struct sp_array *)
xrealloc (sparsearray,
sp_array_size * (sizeof (struct sp_array)));
}
if (exhdr->sparse_header.sp[counter].numbytes == 0)
break;
sparsearray[counter + ind].offset =
OFF_FROM_OCT (exhdr->sparse_header.sp[counter].offset);
sparsearray[counter + ind].numbytes =
SIZE_FROM_OCT (exhdr->sparse_header.sp[counter].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) - 1;
if (CURRENT_FILE_NAME[name_length] == '/')
goto really_dir;
again_file:
openflag = (keep_old_files_option ?
O_BINARY | O_NDELAY | O_WRONLY | O_CREAT | O_EXCL :
O_BINARY | O_NDELAY | O_WRONLY | O_CREAT | O_TRUNC)
| ((typeflag == GNUTYPE_SPARSE) ? 0 : O_APPEND);
if (to_stdout_option)
{
fd = 1;
goto extract_file;
}
if (unlink_first_option)
remove_any_file (CURRENT_FILE_NAME, recursive_unlink_option);
#if O_CTG
if (typeflag == CONTTYPE)
fd = open (CURRENT_FILE_NAME, openflag | O_CTG,
current_stat.st_mode, current_stat.st_size);
else
fd = open (CURRENT_FILE_NAME, openflag, current_stat.st_mode);
#else
if (typeflag == CONTTYPE)
{
static int conttype_diagnosed = 0;
if (!conttype_diagnosed)
{
conttype_diagnosed = 1;
WARN ((0, 0, _("Extracting contiguous files as regular files")));
}
}
fd = open (CURRENT_FILE_NAME, openflag, current_stat.st_mode);
#endif
if (fd < 0)
{
if (maybe_recoverable (CURRENT_FILE_NAME))
goto again_file;
ERROR ((0, errno, _("%s: Could not create file"),
CURRENT_FILE_NAME));
if (current_header->oldgnu_header.isextended)
skip_extended_headers ();
skip_file (current_stat.st_size);
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 = (char *) 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);
}
else
for (size = current_stat.st_size;
size > 0;
size -= written)
{
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 == NULL)
{
ERROR ((0, 0, _("Unexpected EOF on archive file")));
break;
}
written = available_space_after (data_block);
if (written > size)
written = size;
errno = 0;
sstatus = full_write (fd, data_block->buffer, written);
set_next_block_after ((union block *)
(data_block->buffer + written - 1));
if (sstatus == written)
continue;
if (sstatus < 0)
ERROR ((0, errno, _("%s: Could not write to file"),
CURRENT_FILE_NAME));
else
ERROR ((0, 0, _("%s: Could only write %lu of %lu bytes"),
CURRENT_FILE_NAME,
(unsigned long) sstatus,
(unsigned long) written));
skip_file (size - written);
break;
}
if (multi_volume_option)
assign_string (&save_name, NULL);
if (to_stdout_option)
break;
status = close (fd);
if (status < 0)
{
ERROR ((0, errno, _("%s: Error while closing"), CURRENT_FILE_NAME));
if (backup_option)
undo_last_backup ();
}
set_stat (CURRENT_FILE_NAME, ¤t_stat, 0);
break;
case SYMTYPE:
if (to_stdout_option)
break;
#ifdef S_ISLNK
if (unlink_first_option)
remove_any_file (CURRENT_FILE_NAME, recursive_unlink_option);
while (status = symlink (current_link_name, CURRENT_FILE_NAME),
status != 0)
if (!maybe_recoverable (CURRENT_FILE_NAME))
break;
if (status == 0)
set_stat (CURRENT_FILE_NAME, ¤t_stat, 1);
else
{
ERROR ((0, errno, _("%s: Could not create symlink to `%s'"),
CURRENT_FILE_NAME, current_link_name));
if (backup_option)
undo_last_backup ();
}
break;
#else
{
static int warned_once = 0;
if (!warned_once)
{
warned_once = 1;
WARN ((0, 0, _("\
Attempting extraction of symbolic links as hard links")));
}
}
#endif
case LNKTYPE:
if (to_stdout_option)
break;
if (unlink_first_option)
remove_any_file (CURRENT_FILE_NAME, recursive_unlink_option);
again_link:
{
struct stat st1, st2;
status = link (current_link_name, CURRENT_FILE_NAME);
if (status == 0)
break;
if (maybe_recoverable (CURRENT_FILE_NAME))
goto again_link;
if (incremental_option && 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, _("%s: Could not link to `%s'"),
CURRENT_FILE_NAME, current_link_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 defined(S_IFCHR) || defined(S_IFBLK)
make_node:
if (to_stdout_option)
break;
if (unlink_first_option)
remove_any_file (CURRENT_FILE_NAME, recursive_unlink_option);
status = mknod (CURRENT_FILE_NAME, current_stat.st_mode,
current_stat.st_rdev);
if (status != 0)
{
if (maybe_recoverable (CURRENT_FILE_NAME))
goto make_node;
ERROR ((0, errno, _("%s: Could not make node"), CURRENT_FILE_NAME));
if (backup_option)
undo_last_backup ();
break;
};
set_stat (CURRENT_FILE_NAME, ¤t_stat, 0);
break;
#endif
#ifdef S_ISFIFO
case FIFOTYPE:
if (to_stdout_option)
break;
if (unlink_first_option)
remove_any_file (CURRENT_FILE_NAME, recursive_unlink_option);
while (status = mkfifo (CURRENT_FILE_NAME, current_stat.st_mode),
status != 0)
if (!maybe_recoverable (CURRENT_FILE_NAME))
break;
if (status == 0)
set_stat (CURRENT_FILE_NAME, ¤t_stat, 0);
else
{
ERROR ((0, errno, _("%s: Could not make fifo"), CURRENT_FILE_NAME));
if (backup_option)
undo_last_backup ();
}
break;
#endif
case DIRTYPE:
case GNUTYPE_DUMPDIR:
name_length = strlen (CURRENT_FILE_NAME) - 1;
really_dir:
while (name_length && CURRENT_FILE_NAME[name_length] == '/')
CURRENT_FILE_NAME[name_length--] = '\0';
if (incremental_option)
{
gnu_restore (skipcrud);
}
else if (typeflag == GNUTYPE_DUMPDIR)
skip_file (current_stat.st_size);
if (to_stdout_option)
break;
again_dir:
status = mkdir (CURRENT_FILE_NAME,
((we_are_root ? 0 : MODE_WXUSR)
| current_stat.st_mode));
if (status != 0)
{
if (errno == EEXIST)
{
struct stat st1;
int saved_errno = errno;
if (stat (CURRENT_FILE_NAME, &st1) == 0 && S_ISDIR (st1.st_mode))
goto check_perms;
errno = saved_errno;
}
if (maybe_recoverable (CURRENT_FILE_NAME))
goto again_dir;
if (CURRENT_FILE_NAME[name_length] == '.'
&& (name_length == 0
|| CURRENT_FILE_NAME[name_length - 1] == '/'))
goto check_perms;
ERROR ((0, errno, _("%s: Could not create directory"),
CURRENT_FILE_NAME));
if (backup_option)
undo_last_backup ();
break;
}
check_perms:
if (!we_are_root && MODE_WXUSR != (MODE_WXUSR & current_stat.st_mode))
{
current_stat.st_mode |= MODE_WXUSR;
WARN ((0, 0, _("Added write and execute permission to directory %s"),
CURRENT_FILE_NAME));
}
#if !MSDOS
if (touch_option)
set_mode (CURRENT_FILE_NAME, ¤t_stat);
else
{
data = ((struct delayed_set_stat *)
xmalloc (sizeof (struct delayed_set_stat)));
data->file_name = xstrdup (CURRENT_FILE_NAME);
data->stat_info = current_stat;
data->next = delayed_set_stat_head;
delayed_set_stat_head = data;
}
#endif
break;
case GNUTYPE_VOLHDR:
if (verbose_option)
fprintf (stdlis, _("Reading %s\n"), current_file_name);
break;
case GNUTYPE_NAMES:
extract_mangle ();
break;
case GNUTYPE_MULTIVOL:
ERROR ((0, 0, _("\
Cannot extract `%s' -- file is continued from another volume"),
current_file_name));
skip_file (current_stat.st_size);
if (backup_option)
undo_last_backup ();
break;
case GNUTYPE_LONGNAME:
case GNUTYPE_LONGLINK:
ERROR ((0, 0, _("Visible long name error")));
skip_file (current_stat.st_size);
if (backup_option)
undo_last_backup ();
break;
default:
WARN ((0, 0,
_("Unknown file type '%c' for %s, extracted as normal file"),
typeflag, CURRENT_FILE_NAME));
goto again_file;
}
#undef CURRENT_FILE_NAME
}
void
apply_delayed_set_stat (void)
{
struct delayed_set_stat *data;
while (delayed_set_stat_head != NULL)
{
data = delayed_set_stat_head;
delayed_set_stat_head = delayed_set_stat_head->next;
set_stat (data->file_name, &data->stat_info, 0);
free (data->file_name);
free (data);
}
}