`--level=0' option forces creating the level 0 dump, by truncating
the snapshot file if it exists.
+* Files removed during incremental dumps
+
+If a file or directory is removed while incremental dump is
+in progress, tar exact actions depend on whether this file
+was explicitly listed in the command line, or was gathered
+during file system scan.
+
+If the file was explicitly listed in the command line, tar
+issues error messages and exits with the code 2, meaning
+fatal error.
+
+Otherwise, if the file was gathered during the file system
+scan, tar issues a warning, saying "File removed before we read it",
+and sets the exit code to 1, which means "some files differ".
+If the --warning=no-file-removed option is given, no warning
+is issued and the exit code remains 0.
+
* Bugfixes
** Fix handling of hard link targets by -c --transform.
** Fix hard links recognition with -c --remove-files.
want a visual indication that @command{tar} is still running, but
don't want to see @option{--verbose} output. You can also instruct
@command{tar} to execute a list of actions on each checkpoint, see
-@option{--checklist-action} below. For a detailed description, see
+@option{--checkpoint-action} below. For a detailed description, see
@ref{checkpoints}.
@opsummary{checkpoint-action}
char *name; /* File name or globbing pattern */
size_t length; /* cached strlen (name) */
- int matching_flags; /* wildcard flags if name is a pattern */
-
+ int matching_flags; /* wildcard flags if name is a pattern */
+ bool cmdline; /* true if this name was given in the
+ command line */
+
int change_dir; /* Number of the directory to change to.
Set with the -C option. */
uintmax_t found_count; /* number of times a matching file has
bool file_dumpable_p (struct tar_stat_info *st);
void create_archive (void);
void pad_archive (off_t size_left);
-void dump_file (const char *st, int top_level, dev_t parent_device);
+void dump_file (const char *st, bool top_level, dev_t parent_device);
union block *start_header (struct tar_stat_info *st);
void finish_header (struct tar_stat_info *st, union block *header,
off_t block_ordinal);
void savedir_diag (char const *name);
void seek_diag_details (char const *name, off_t offset);
void stat_diag (char const *name);
+void file_removed_diag (const char *name, bool top_level,
+ void (*diagfn) (char const *name));
+void dir_removed_diag (char const *name, bool top_level,
+ void (*diagfn) (char const *name));
void write_error_details (char const *name, size_t status, size_t size);
void write_fatal (char const *name) __attribute__ ((noreturn));
void write_fatal_details (char const *name, ssize_t status, size_t size)
void name_term (void);
const char *name_next (int change_dirs);
void name_gather (void);
-struct name *addname (char const *string, int change_dir, struct name *parent);
+struct name *addname (char const *string, int change_dir,
+ bool cmdline, struct name *parent);
bool name_match (const char *name);
void names_notfound (void);
void collect_and_sort_names (void);
struct name *name_scan (const char *name);
-char *name_from_list (void);
+struct name const *name_from_list (void);
void blank_name_list (void);
char *new_name (const char *dir_name, const char *name);
size_t stripped_prefix_len (char const *file_name, size_t num);
\f
static void
dump_dir0 (char *directory,
- struct tar_stat_info *st, int top_level, dev_t parent_device)
+ struct tar_stat_info *st, bool top_level, dev_t parent_device)
{
dev_t our_device = st->stat.st_dev;
const char *tag_file_name;
}
strcpy (name_buf + name_len, entry);
if (!excluded_name (name_buf))
- dump_file (name_buf, 0, our_device);
+ dump_file (name_buf, false, our_device);
}
free (name_buf);
name_buf = xmalloc (name_size);
strcpy (name_buf, st->orig_file_name);
strcat (name_buf, tag_file_name);
- dump_file (name_buf, 0, our_device);
+ dump_file (name_buf, false, our_device);
free (name_buf);
break;
}
static bool
-dump_dir (int fd, struct tar_stat_info *st, int top_level, dev_t parent_device)
+dump_dir (int fd, struct tar_stat_info *st, bool top_level,
+ dev_t parent_device)
{
char *directory = fdsavedir (fd);
if (!directory)
void
create_archive (void)
{
- const char *p;
+ struct name const *p;
open_archive (ACCESS_WRITE);
buffer_write_global_xheader ();
collect_and_sort_names ();
while ((p = name_from_list ()) != NULL)
- if (!excluded_name (p))
- dump_file (p, -1, (dev_t) 0);
+ if (!excluded_name (p->name))
+ dump_file (p->name, p->cmdline, (dev_t) 0);
blank_name_list ();
while ((p = name_from_list ()) != NULL)
- if (!excluded_name (p))
+ if (!excluded_name (p->name))
{
- size_t plen = strlen (p);
+ size_t plen = strlen (p->name);
if (buffer_size <= plen)
{
while ((buffer_size *= 2) <= plen)
continue;
buffer = xrealloc (buffer, buffer_size);
}
- memcpy (buffer, p, plen);
+ memcpy (buffer, p->name, plen);
if (! ISSLASH (buffer[plen - 1]))
buffer[plen++] = DIRECTORY_SEPARATOR;
q = directory_contents (gnu_list_name->directory);
buffer = xrealloc (buffer, buffer_size);
}
strcpy (buffer + plen, q + 1);
- dump_file (buffer, -1, (dev_t) 0);
+ dump_file (buffer, false, (dev_t) 0);
}
q += qlen + 1;
}
}
else
{
- while ((p = name_next (1)) != NULL)
- if (!excluded_name (p))
- dump_file (p, 1, (dev_t) 0);
+ const char *name;
+ while ((name = name_next (1)) != NULL)
+ if (!excluded_name (name))
+ dump_file (name, true, (dev_t) 0);
}
write_eot ();
}
}
-
/* Dump a single file, recursing on directories. P is the file name
to dump. TOP_LEVEL tells whether this is a top-level call; zero
means no, positive means yes, and negative means the top level
static void
dump_file0 (struct tar_stat_info *st, const char *p,
- int top_level, dev_t parent_device)
+ bool top_level, dev_t parent_device)
{
union block *header;
char type;
if (deref_stat (dereference_option, p, &st->stat) != 0)
{
- stat_diag (p);
+ file_removed_diag (p, top_level, stat_diag);
return;
}
st->archive_file_size = original_size = st->stat.st_size;
: 0)));
if (fd < 0)
{
- if (!top_level && errno == ENOENT)
- WARNOPT (WARN_FILE_REMOVED,
- (0, 0, _("%s: File removed before we read it"),
- quotearg_colon (p)));
- else
- open_diag (p);
+ file_removed_diag (p, top_level, open_diag);
return;
}
}
: fstat (fd, &final_stat))
!= 0)
{
- stat_diag (p);
+ file_removed_diag (p, top_level, stat_diag);
ok = false;
}
}
size = readlink (p, buffer, linklen + 1);
if (size < 0)
{
- readlink_diag (p);
+ file_removed_diag (p, top_level, readlink_diag);
return;
}
buffer[size] = '\0';
}
void
-dump_file (const char *p, int top_level, dev_t parent_device)
+dump_file (const char *p, bool top_level, dev_t parent_device)
{
struct tar_stat_info st;
tar_stat_init (&st);
{
struct stat st;
if (deref_stat (dereference_option, p, &st) != 0)
- stat_diag (name);
+ {
+ if (errno != ENOENT)
+ stat_diag (directory->name);
+ /* else: should have been already reported */
+ }
else
directory->mtime = get_stat_mtime (&st);
}
if (one_file_system_option && device != stat_data->st_dev
/* ... except if it was explicitely given in the command line */
&& !is_individual_file (name_buffer))
+ /* FIXME:
+ WARNOPT (WARN_XDEV,
+ (0, 0,
+ _("%s: directory is on a different filesystem; not dumped"),
+ quotearg_colon (directory->name)));
+ */
directory->children = NO_CHILDREN;
else if (flag & PD_FORCE_CHILDREN)
{
if (deref_stat (dereference_option, name_buffer, &stat_data))
{
- stat_diag (name_buffer);
+ dir_removed_diag (name_buffer, false, stat_diag);
/* FIXME: used to be
children = CHANGED_CHILDREN;
but changed to: */
stat_error (name);
}
+void
+file_removed_diag (const char *name, bool top_level,
+ void (*diagfn) (char const *name))
+{
+ if (!top_level && errno == ENOENT)
+ WARNOPT (WARN_FILE_REMOVED,
+ (0, 0, _("%s: File removed before we read it"),
+ quotearg_colon (name)));
+ else
+ diagfn (name);
+}
+
+void
+dir_removed_diag (const char *name, bool top_level,
+ void (*diagfn) (char const *name))
+{
+ if (!top_level && errno == ENOENT)
+ WARNOPT (WARN_FILE_REMOVED,
+ (0, 0, _("%s: Directory removed before we read it"),
+ quotearg_colon (name)));
+ else
+ diagfn (name);
+}
+
void
write_fatal_details (char const *name, ssize_t status, size_t size)
{
buffer->matching_flags = matching_flags;
buffer->directory = NULL;
buffer->parent = NULL;
+ buffer->cmdline = true;
namelist = buffer;
nametail = &namelist->next;
}
else if (change_dir)
- addname (0, change_dir, NULL);
+ addname (0, change_dir, false, NULL);
}
else
{
change_dir = chdir_arg (xstrdup (ep->v.name));
if (ep)
- addname (ep->v.name, change_dir, NULL);
+ addname (ep->v.name, change_dir, true, NULL);
else
{
if (change_dir != change_dir0)
- addname (0, change_dir, NULL);
+ addname (NULL, change_dir, false, NULL);
break;
}
}
/* Add a name to the namelist. */
struct name *
-addname (char const *string, int change_dir, struct name *parent)
+addname (char const *string, int change_dir, bool cmdline, struct name *parent)
{
struct name *name = make_name (string);
name->change_dir = change_dir;
name->directory = NULL;
name->parent = parent;
+ name->cmdline = cmdline;
*nametail = name;
nametail = &name->next;
namebuf = xrealloc (namebuf, allocated_length + 1);
}
strcpy (namebuf + name_length, string + 1);
- np = addname (namebuf, change_dir, name);
+ np = addname (namebuf, change_dir, false, name);
if (!child_head)
child_head = np;
else
name_gather ();
if (!namelist)
- addname (".", 0, NULL);
+ addname (".", 0, false, NULL);
if (listed_incremental_option)
{
find and return all the non-found names in the namelist. */
struct name *gnu_list_name;
-char *
-name_from_list (void)
+struct name const *
+name_from_list ()
{
if (!gnu_list_name)
gnu_list_name = namelist;
{
gnu_list_name->found_count++;
chdir_do (gnu_list_name->change_dir);
- return gnu_list_name->name;
+ return gnu_list_name;
}
- return 0;
+ return NULL;
}
void
case 'K':
starting_file_option = true;
- addname (arg, 0, NULL);
+ addname (arg, 0, true, NULL);
break;
case ONE_FILE_SYSTEM_OPTION:
output_start = current_block->buffer;
{
- char *file_name;
-
- while ((file_name = name_from_list ()) != NULL)
+ struct name const *p;
+ while ((p = name_from_list ()) != NULL)
{
+ char *file_name = p->name;
if (excluded_name (file_name))
continue;
if (interactive_option && !confirm ("add", file_name))
extrac06.at\
extrac07.at\
extrac08.at\
+ filerem01.at\
+ filerem02.at\
gzip.at\
grow.at\
incremental.at\
--- /dev/null
+# Process this file with autom4te to create testsuite. -*- Autotest -*-
+
+# Test suite for GNU tar.
+# Copyright (C) 2009 Free Software Foundation, Inc.
+
+# 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 3, 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; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+AT_SETUP([file removed as we read it (ca. 22 seconds)])
+AT_KEYWORDS([create incremental filechange filerem filerem01])
+
+AT_TAR_CHECK([
+mkdir dir
+mkdir dir/sub
+genfile --file dir/file1
+genfile --file dir/sub/file2
+
+genfile --run --checkpoint=3 --unlink dir/file1 -- \
+ tar --blocking-factor=1 --checkpoint=1 --checkpoint-action='sleep=1' \
+ --checkpoint-action='echo' -c -f archive.tar \
+ --listed-incremental db -v dir >/dev/null
+],
+[0],
+[ignore],
+[tar: dir: Directory is new
+tar: dir/sub: Directory is new
+tar: dir/file1: File removed before we read it
+],[],[],[gnu, posix])
+
+# Timing information:
+#
+# For -Hgnu the above command line takes about 8 seconds to execute and
+# produces:
+#
+# tar: dir: Directory is new
+# tar: dir/sub: Directory is new
+# dir/
+# tar: Write checkpoint 1
+# tar: Write checkpoint 2
+# dir/sub/
+# tar: Write checkpoint 3
+# tar: Write checkpoint 4
+# dir/file1
+# tar: Write checkpoint 5
+# dir/sub/file2
+# tar: Write checkpoint 6
+# tar: Write checkpoint 7
+# tar: Write checkpoint 8
+#
+# For -Hposix the above command line takes about 14 seconds to execute and
+# produces:
+#
+# ./tar: dir: Directory is new
+# ./tar: dir/sub: Directory is new
+# dir/
+# ./tar: Write checkpoint 1
+# ./tar: Write checkpoint 2
+# ./tar: Write checkpoint 3
+# dir/sub/
+# ./tar: Write checkpoint 4
+# ./tar: Write checkpoint 5
+# ./tar: Write checkpoint 6
+# dir/file1
+# ./tar: Write checkpoint 7
+# ./tar: Write checkpoint 8
+# ./tar: Write checkpoint 9
+# dir/sub/file2
+# ./tar: Write checkpoint 10
+# ./tar: Write checkpoint 11
+# ./tar: Write checkpoint 12
+# ./tar: Write checkpoint 13
+# ./tar: Write checkpoint 14
+
+
+AT_CLEANUP
+
--- /dev/null
+# Process this file with autom4te to create testsuite. -*- Autotest -*-
+
+# Test suite for GNU tar.
+# Copyright (C) 2009 Free Software Foundation, Inc.
+
+# 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 3, 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. If not, see <http://www.gnu.org/licenses/>.
+
+# Description: see filerem01.at
+# This test case checks if the tar exit code is still 2 if a
+# file or directory disappears that is explicitly mentioned
+# in the command line.
+
+AT_SETUP([toplevel file removed (ca. 24 seconds)])
+AT_KEYWORDS([create incremental filechange filerem filerem02])
+
+AT_TAR_CHECK([
+mkdir dir
+mkdir dir/sub
+genfile --file dir/file1
+genfile --file dir/sub/file2
+mkdir dir2
+genfile --file dir2/file1
+
+genfile --run --checkpoint=3 --exec 'rm -rf dir2' -- \
+ tar --blocking-factor=1 --checkpoint=1 --checkpoint-action='sleep=1' \
+ --checkpoint-action='echo' -c -f archive.tar \
+ --listed-incremental db -v --warning=no-new-dir dir dir2 >/dev/null
+],
+[2],
+[ignore],
+[tar: dir2: Cannot stat: No such file or directory
+tar: dir2/file1: File removed before we read it
+tar: Exiting with failure status due to previous errors
+],[],[],[gnu, posix])
+
+# Timing information: see filerem01.at
+
+AT_CLEANUP
+
AT_TAR_CHECK([
genfile --file foo --length 50000k
genfile --file baz
-genfile --run 'tar -vcf bar foo baz' --checkpoint 10 --length 1024 \
- --append foo
+genfile --run --checkpoint 10 --length 1024 --append foo -- tar --checkpoint -vcf bar foo baz
],
[1],
[foo
m4_include([incr04.at])
m4_include([incr05.at])
m4_include([incr06.at])
+
+m4_include([filerem01.at])
+m4_include([filerem02.at])
+
m4_include([rename01.at])
m4_include([rename02.at])
m4_include([rename03.at])
AT_TAR_CHECK([
genfile --file foo --length 50000k
genfile --file baz
-genfile --run 'tar -vcf bar foo baz' --checkpoint 10 --length 49995k --truncate foo
+genfile --run --checkpoint 10 --length 49995k --truncate foo -- tar --checkpoint -vcf bar foo baz
echo Exit status: $?
echo separator
sleep 1