(extract_archive): Rewritten

This commit is contained in:
Sergey Poznyakoff
2005-02-04 00:03:59 +00:00
parent 59f314a2c4
commit c8764440d9

View File

@@ -602,19 +602,435 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_symlinks)
}
}
void
extract_dir (char *file_name, int typeflag)
{
int status;
mode_t mode;
int interdir_made = 0;
if (incremental_option)
/* Read the entry and delete files that aren't listed in the archive. */
purge_directory (file_name);
else if (typeflag == GNUTYPE_DUMPDIR)
skip_member ();
mode = (current_stat_info.stat.st_mode | (we_are_root ? 0 : MODE_WXUSR)) & MODE_RWX;
status = prepare_to_extract (file_name);
if (status == 0)
return;
if (status > 0)
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;
}
if (S_ISDIR (st.st_mode))
{
mode = st.st_mode & ~ current_umask;
break;
}
}
errno = EEXIST;
}
if (maybe_recoverable (file_name, &interdir_made))
continue;
if (errno != EEXIST)
{
mkdir_error (file_name);
if (backup_option)
undo_last_backup ();
return;
}
break;
}
if (status == 0
|| old_files_option == DEFAULT_OLD_FILES
|| old_files_option == OVERWRITE_OLD_FILES)
delay_set_stat (file_name, &current_stat_info.stat,
MODE_RWX & (mode ^ current_stat_info.stat.st_mode),
(status == 0
? ARCHIVED_PERMSTATUS
: UNKNOWN_PERMSTATUS));
}
static int
open_output_file (char *file_name, int typeflag)
{
int fd;
int openflag = (O_WRONLY | O_BINARY | O_CREAT
| (old_files_option == OVERWRITE_OLD_FILES
? O_TRUNC
: O_EXCL));
mode_t mode = current_stat_info.stat.st_mode & MODE_RWX & ~ current_umask;
#if O_CTG
/* Contiguous files (on the Masscomp) have to specify the size in
the open call that creates them. */
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 /* not O_CTG */
if (typeflag == CONTTYPE)
{
static int conttype_diagnosed;
if (!conttype_diagnosed)
{
conttype_diagnosed = 1;
WARN ((0, 0, _("Extracting contiguous files as regular files")));
}
}
fd = open (file_name, openflag, mode);
#endif /* not O_CTG */
return fd;
}
static void
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;
/* FIXME: deal with protection issues. */
do
{
if (to_stdout_option)
fd = STDOUT_FILENO;
else
{
if (! prepare_to_extract (file_name))
{
skip_member ();
if (backup_option)
undo_last_backup ();
return;
}
fd = open_output_file (file_name, typeflag);
}
}
while (fd < 0 && maybe_recoverable (file_name, &interdir_made));
if (fd < 0)
{
open_error (file_name);
skip_member ();
if (backup_option)
undo_last_backup ();
return;
}
if (current_stat_info.is_sparse)
sparse_extract_file (fd, &current_stat_info, &size);
else
for (size = current_stat_info.stat.st_size; size > 0; )
{
if (multi_volume_option)
{
assign_string (&save_name, current_stat_info.file_name);
save_totsize = current_stat_info.stat.st_size;
save_sizeleft = size;
}
/* Locate data, determine max length writeable, write it,
block that we have used the data, then check if the write
worked. */
data_block = find_next_block ();
if (! data_block)
{
ERROR ((0, 0, _("Unexpected EOF in archive")));
break; /* FIXME: What happens, then? */
}
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 (file_name, count, written);
break;
}
}
skip_file (size);
if (multi_volume_option)
assign_string (&save_name, 0);
/* 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 (to_stdout_option)
return;
status = close (fd);
if (status < 0)
{
close_error (file_name);
if (backup_option)
undo_last_backup ();
}
set_stat (file_name, &current_stat_info.stat, 0, 0,
(old_files_option == OVERWRITE_OLD_FILES
? UNKNOWN_PERMSTATUS
: ARCHIVED_PERMSTATUS),
typeflag);
}
static void
extract_link (char *file_name)
{
char const *link_name;
int interdir_made = 0;
if (!prepare_to_extract (file_name))
return;
do
{
struct stat st1, st2;
int e;
link_name = safer_name_suffix (current_stat_info.link_name, true);
int status = link (link_name, file_name);
e = errno;
if (status == 0)
{
struct delayed_symlink *ds = delayed_symlink_head;
if (ds && lstat (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 (file_name) + 1);
strcpy (p->string, file_name);
p->next = ds->sources;
ds->sources = p;
break;
}
return;
}
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;
errno = e;
}
while (maybe_recoverable (file_name, &interdir_made));
if (!(incremental_option && errno == EEXIST))
{
link_error (link_name, file_name);
if (backup_option)
undo_last_backup ();
}
}
static void
extract_symlink (char *file_name)
{
#ifdef HAVE_SYMLINK
int status, fd;
int interdir_made = 0;
if (! prepare_to_extract (file_name))
return;
if (absolute_names_option
|| ! (IS_ABSOLUTE_FILE_NAME (current_stat_info.link_name)
|| contains_dot_dot (current_stat_info.link_name)))
{
while (status = symlink (current_stat_info.link_name, file_name),
status != 0)
if (!maybe_recoverable (file_name, &interdir_made))
break;
if (status == 0)
set_stat (file_name, &current_stat_info.stat, 0, 0, 0, SYMTYPE);
else
symlink_error (current_stat_info.link_name, file_name);
}
else
{
/* This symbolic link is potentially dangerous. Don't
create it now; instead, create a placeholder file, which
will be replaced after other extraction is done. */
struct stat st;
while ((fd = open (file_name, O_WRONLY | O_CREAT | O_EXCL, 0)) < 0)
if (! maybe_recoverable (file_name, &interdir_made))
break;
status = -1;
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_symlink *p = xmalloc (offsetof (struct delayed_symlink, target)
+ strlen (current_stat_info.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_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_symlinks
&& strncmp (file_name, h->file_name, h->file_name_len) == 0
&& ISSLASH (file_name[h->file_name_len])
&& (base_name (file_name) == 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 ();
#else
static int warned_once;
if (!warned_once)
{
warned_once = 1;
WARN ((0, 0, _("Attempting extraction of symbolic links as hard links")));
}
extract_link (file_name);
#endif
}
#if S_IFCHR || S_IFBLK
static void
extract_node (char *file_name, int typeflag)
{
int status;
int interdir_made = 0;
do
{
if (! prepare_to_extract (file_name))
return;
status = mknod (file_name, current_stat_info.stat.st_mode,
current_stat_info.stat.st_rdev);
}
while (status && maybe_recoverable (file_name, &interdir_made));
if (status != 0)
{
mknod_error (file_name);
if (backup_option)
undo_last_backup ();
}
else
set_stat (file_name, &current_stat_info.stat, 0, 0, ARCHIVED_PERMSTATUS, typeflag);
}
#endif
#if HAVE_MKFIFO || defined mkfifo
static void
extract_fifo (char *file_name, int typeflag)
{
int status;
int interdir_made = 0;
if (! prepare_to_extract (file_name))
return;
while ((status = mkfifo (file_name, current_stat_info.stat.st_mode)))
if (!maybe_recoverable (file_name, &interdir_made))
break;
if (status == 0)
set_stat (file_name, &current_stat_info.stat, NULL, 0,
ARCHIVED_PERMSTATUS, typeflag);
else
{
mkfifo_error (file_name);
if (backup_option)
undo_last_backup ();
}
}
#endif
/* Extract a file from the archive. */
void
extract_archive (void)
{
union block *data_block;
int fd;
int status;
size_t count;
size_t written;
int openflag;
mode_t mode;
off_t size;
int interdir_made = 0;
char typeflag;
char *file_name;
@@ -666,9 +1082,7 @@ extract_archive (void)
switch (typeflag)
{
case GNUTYPE_SPARSE:
/* Fall through. */
case GNUTYPE_SPARSE: /* FIXME: Shouldn't we call extract_file at once? */
case AREGTYPE:
case REGTYPE:
case CONTTYPE:
@@ -677,422 +1091,42 @@ extract_archive (void)
suffix means a directory. */
if (current_stat_info.had_trailing_slash)
goto really_dir;
/* FIXME: deal with protection issues. */
again_file:
openflag = (O_WRONLY | O_BINARY | O_CREAT
| (old_files_option == OVERWRITE_OLD_FILES
? O_TRUNC
: O_EXCL));
mode = current_stat_info.stat.st_mode & MODE_RWX & ~ current_umask;
if (to_stdout_option)
{
fd = STDOUT_FILENO;
goto extract_file;
}
if (! prepare_to_extract (file_name))
{
skip_member ();
if (backup_option)
undo_last_backup ();
break;
}
#if O_CTG
/* Contiguous files (on the Masscomp) have to specify the size in
the open call that creates them. */
if (typeflag == CONTTYPE)
fd = open (file_name, openflag | O_CTG, mode, current_stat_info.stat.st_size);
extract_dir (file_name, typeflag);
else
fd = open (file_name, openflag, mode);
#else /* not O_CTG */
if (typeflag == CONTTYPE)
{
static int conttype_diagnosed;
if (!conttype_diagnosed)
{
conttype_diagnosed = 1;
WARN ((0, 0, _("Extracting contiguous files as regular files")));
}
}
fd = open (file_name, openflag, mode);
#endif /* not O_CTG */
if (fd < 0)
{
if (maybe_recoverable (file_name, &interdir_made))
goto again_file;
open_error (file_name);
skip_member ();
if (backup_option)
undo_last_backup ();
break;
}
extract_file:
if (current_stat_info.is_sparse)
{
sparse_extract_file (fd, &current_stat_info, &size);
}
else
for (size = current_stat_info.stat.st_size; size > 0; )
{
if (multi_volume_option)
{
assign_string (&save_name, current_stat_info.file_name);
save_totsize = current_stat_info.stat.st_size;
save_sizeleft = size;
}
/* Locate data, determine max length writeable, write it,
block that we have used the data, then check if the write
worked. */
data_block = find_next_block ();
if (! data_block)
{
ERROR ((0, 0, _("Unexpected EOF in archive")));
break; /* FIXME: What happens, then? */
}
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 (file_name, count, written);
break;
}
}
skip_file (size);
if (multi_volume_option)
assign_string (&save_name, 0);
/* 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 (to_stdout_option)
break;
status = close (fd);
if (status < 0)
{
close_error (file_name);
if (backup_option)
undo_last_backup ();
}
set_stat (file_name, &current_stat_info.stat, 0, 0,
(old_files_option == OVERWRITE_OLD_FILES
? UNKNOWN_PERMSTATUS
: ARCHIVED_PERMSTATUS),
typeflag);
extract_file (file_name, typeflag);
break;
case SYMTYPE:
#ifdef HAVE_SYMLINK
if (! prepare_to_extract (file_name))
break;
if (absolute_names_option
|| ! (IS_ABSOLUTE_FILE_NAME (current_stat_info.link_name)
|| contains_dot_dot (current_stat_info.link_name)))
{
while (status = symlink (current_stat_info.link_name, file_name),
status != 0)
if (!maybe_recoverable (file_name, &interdir_made))
break;
if (status == 0)
set_stat (file_name, &current_stat_info.stat, 0, 0, 0, SYMTYPE);
else
symlink_error (current_stat_info.link_name, file_name);
}
else
{
/* This symbolic link is potentially dangerous. Don't
create it now; instead, create a placeholder file, which
will be replaced after other extraction is done. */
struct stat st;
while (fd = open (file_name, O_WRONLY | O_CREAT | O_EXCL, 0),
fd < 0)
if (! maybe_recoverable (file_name, &interdir_made))
break;
status = -1;
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_symlink *p =
xmalloc (offsetof (struct delayed_symlink, target)
+ strlen (current_stat_info.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_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_symlinks
&& strncmp (file_name, h->file_name, h->file_name_len) == 0
&& ISSLASH (file_name[h->file_name_len])
&& (base_name (file_name)
== 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 ();
extract_symlink (file_name);
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;
/* Fall through. */
#endif
case LNKTYPE:
if (! prepare_to_extract (file_name))
break;
again_link:
{
char const *link_name = safer_name_suffix (current_stat_info.link_name,
true);
struct stat st1, st2;
int e;
/* MSDOS does not implement links. However, djgpp's link() actually
copies the file. */
status = link (link_name, file_name);
e = errno;
if (status == 0)
{
struct delayed_symlink *ds = delayed_symlink_head;
if (ds && lstat (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 (file_name) + 1);
strcpy (p->string, file_name);
p->next = ds->sources;
ds->sources = p;
break;
}
break;
}
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))
break;
errno = e;
if (maybe_recoverable (file_name, &interdir_made))
goto again_link;
if (incremental_option && errno == EEXIST)
break;
link_error (link_name, file_name);
if (backup_option)
undo_last_backup ();
}
extract_link (file_name);
break;
#if S_IFCHR
case CHRTYPE:
current_stat_info.stat.st_mode |= S_IFCHR;
goto make_node;
extract_node (file_name, typeflag);
break;
#endif
#if S_IFBLK
case BLKTYPE:
current_stat_info.stat.st_mode |= S_IFBLK;
#endif
#if S_IFCHR || S_IFBLK
make_node:
if (! prepare_to_extract (file_name))
break;
status = mknod (file_name, current_stat_info.stat.st_mode,
current_stat_info.stat.st_rdev);
if (status != 0)
{
if (maybe_recoverable (file_name, &interdir_made))
goto make_node;
mknod_error (file_name);
if (backup_option)
undo_last_backup ();
break;
};
set_stat (file_name, &current_stat_info.stat, 0, 0,
ARCHIVED_PERMSTATUS, typeflag);
extract_node (file_name, typeflag);
break;
#endif
#if HAVE_MKFIFO || defined mkfifo
case FIFOTYPE:
if (! prepare_to_extract (file_name))
break;
while (status = mkfifo (file_name, current_stat_info.stat.st_mode),
status != 0)
if (!maybe_recoverable (file_name, &interdir_made))
break;
if (status == 0)
set_stat (file_name, &current_stat_info.stat, NULL, 0,
ARCHIVED_PERMSTATUS, typeflag);
else
{
mkfifo_error (file_name);
if (backup_option)
undo_last_backup ();
}
extract_fifo (file_name, typeflag);
break;
#endif
case DIRTYPE:
case GNUTYPE_DUMPDIR:
really_dir:
if (incremental_option)
{
/* Read the entry and delete files that aren't listed in the
archive. */
purge_directory (file_name);
}
else if (typeflag == GNUTYPE_DUMPDIR)
skip_member ();
mode = ((current_stat_info.stat.st_mode
| (we_are_root ? 0 : MODE_WXUSR))
& MODE_RWX);
status = prepare_to_extract (file_name);
if (status == 0)
break;
if (status < 0)
goto directory_exists;
again_dir:
status = mkdir (file_name, mode);
if (status != 0)
{
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);
break;
}
if (S_ISDIR (st.st_mode))
{
mode = st.st_mode & ~ current_umask;
goto directory_exists;
}
}
errno = EEXIST;
}
if (maybe_recoverable (file_name, &interdir_made))
goto again_dir;
if (errno != EEXIST)
{
mkdir_error (file_name);
if (backup_option)
undo_last_backup ();
break;
}
}
directory_exists:
if (status == 0
|| old_files_option == DEFAULT_OLD_FILES
|| old_files_option == OVERWRITE_OLD_FILES)
delay_set_stat (file_name, &current_stat_info.stat,
MODE_RWX & (mode ^ current_stat_info.stat.st_mode),
(status == 0
? ARCHIVED_PERMSTATUS
: UNKNOWN_PERMSTATUS));
extract_dir (file_name, typeflag);
break;
case GNUTYPE_VOLHDR:
@@ -1115,7 +1149,7 @@ extract_archive (void)
case GNUTYPE_LONGNAME:
case GNUTYPE_LONGLINK:
ERROR ((0, 0, _("Visible long name error")));
ERROR ((0, 0, _("Unexpected long name header")));
skip_member ();
if (backup_option)
undo_last_backup ();
@@ -1125,7 +1159,7 @@ extract_archive (void)
WARN ((0, 0,
_("%s: Unknown file type '%c', extracted as normal file"),
quotearg_colon (file_name), typeflag));
goto again_file;
extract_file (file_name, typeflag);
}
}