]> git.ipfire.org Git - thirdparty/tar.git/commitdiff
Fix data loss when acting as filter
authorPaul Eggert <eggert@cs.ucla.edu>
Sat, 3 Sep 2022 23:22:34 +0000 (18:22 -0500)
committerPaul Eggert <eggert@cs.ucla.edu>
Sat, 3 Sep 2022 23:23:11 +0000 (18:23 -0500)
This bug was introduced by the recent lseek-related changes.
* src/delete.c (delete_archive_members):
* src/update.c (update_archive):
Copy the member if acting as a filter, rather than lseeking over
it, which is possible if stdin is a regular file.
* src/list.c (skim_file, skim_member):
* src/sparse.c (sparse_skim_file):
New functions, for copying when a filter.
* src/list.c (skip_file): Remove; replaced with skim_file.
All callers changed.
(skip_member): Reimplement in terms of skim_member.
* src/sparse.c (sparse_skip_file):
Remove; replaced with sparse_skim_file.  All callers changed.
* src/update.c (acting_as_filter): New static var.
(update_archive): Set it; this is like delete.c.
* tests/delete01.at (deleting a member after a big one):
* tests/delete02.at (deleting a member from stdin archive):
Also test filter case.

src/common.h
src/delete.c
src/extract.c
src/list.c
src/sparse.c
src/update.c
tests/delete01.at
tests/delete02.at

index 241665249b39e0ff3ffc46f49604047bfdbb75b7..259655f9bff72e8dbb2a2d44c6377f8066661a76 100644 (file)
@@ -623,8 +623,9 @@ enum read_header read_header (union block **return_block,
                              struct tar_stat_info *info,
                              enum read_header_mode m);
 enum read_header tar_checksum (union block *header, bool silent);
-void skip_file (off_t size);
+void skim_file (off_t size, bool must_copy);
 void skip_member (void);
+void skim_member (bool must_copy);
 
 /* Module misc.c.  */
 
@@ -928,7 +929,7 @@ bool sparse_fixup_header (struct tar_stat_info *st);
 enum dump_status sparse_dump_file (int, struct tar_stat_info *st);
 enum dump_status sparse_extract_file (int fd, struct tar_stat_info *st,
                                      off_t *size);
-enum dump_status sparse_skip_file (struct tar_stat_info *st);
+enum dump_status sparse_skim_file (struct tar_stat_info *st, bool must_copy);
 bool sparse_diff_file (int, struct tar_stat_info *st);
 
 /* Module utf8.c */
index 3bee5c65f1a679d0ddd2f5e89235ffbdbb24076c..dd6bc213afe37f338bf071ad264712f8f21f2a57 100644 (file)
@@ -183,13 +183,13 @@ delete_archive_members (void)
        case HEADER_SUCCESS:
          if ((name = name_scan (current_stat_info.file_name)) == NULL)
            {
-             skip_member ();
+             skim_member (acting_as_filter);
              break;
            }
          name->found_count++;
          if (!ISFOUND (name))
            {
-             skip_member ();
+             skim_member (acting_as_filter);
              break;
            }
          FALLTHROUGH;
index 7696d5e462dea2f4c58d4b8460542c4edb325d6b..78de47f56000f4514aeb53d352be65743b01790e 100644 (file)
@@ -1320,7 +1320,7 @@ extract_file (char *file_name, int typeflag)
          }
       }
 
-  skip_file (size);
+  skim_file (size, false);
 
   mv_end ();
 
index 409917d254d9d2b034de8ccc46ae4cceb6d68120..cbc0e31e9b8cc5e327106dcd6b22501e1b6bb2f8 100644 (file)
@@ -416,7 +416,7 @@ read_header (union block **return_block, struct tar_stat_info *info,
   size_t next_long_name_blocks = 0;
   size_t next_long_link_blocks = 0;
   enum read_header status = HEADER_SUCCESS;
-  
+
   while (1)
     {
       header = find_next_block ();
@@ -1391,15 +1391,17 @@ print_for_mkdir (char *dirname, int length, mode_t mode)
     }
 }
 
-/* Skip over SIZE bytes of data in blocks in the archive.  */
+/* Skip over SIZE bytes of data in blocks in the archive.
+   This may involve copying the data.
+   If MUST_COPY, always copy instead of skipping.  */
 void
-skip_file (off_t size)
+skim_file (off_t size, bool must_copy)
 {
   union block *x;
 
   /* FIXME: Make sure mv_begin_read is always called before it */
 
-  if (seekable_archive)
+  if (seekable_archive && !must_copy)
     {
       off_t nblk = seek_archive (size);
       if (nblk >= 0)
@@ -1426,6 +1428,14 @@ skip_file (off_t size)
    NOTE: Current header must be decoded before calling this function. */
 void
 skip_member (void)
+{
+  skim_member (false);
+}
+
+/* Skip the current member in the archive.
+   If MUST_COPY, always copy instead of skipping.  */
+void
+skim_member (bool must_copy)
 {
   if (!current_stat_info.skipped)
     {
@@ -1435,9 +1445,9 @@ skip_member (void)
       mv_begin_read (&current_stat_info);
 
       if (current_stat_info.is_sparse)
-       sparse_skip_file (&current_stat_info);
+       sparse_skim_file (&current_stat_info, must_copy);
       else if (save_typeflag != DIRTYPE)
-       skip_file (current_stat_info.stat.st_size);
+       skim_file (current_stat_info.stat.st_size, must_copy);
 
       mv_end ();
     }
index 0b9f250f0eb44fa4bfa81b46dd952035b48ff8e4..767793b09a982632011c3aaf8123d9aa4b0f7bf8 100644 (file)
@@ -586,7 +586,7 @@ sparse_extract_file (int fd, struct tar_stat_info *st, off_t *size)
 }
 
 enum dump_status
-sparse_skip_file (struct tar_stat_info *st)
+sparse_skim_file (struct tar_stat_info *st, bool must_copy)
 {
   bool rc = true;
   struct tar_sparse_file file;
@@ -598,7 +598,7 @@ sparse_skip_file (struct tar_stat_info *st)
   file.fd = -1;
 
   rc = tar_sparse_decode_header (&file);
-  skip_file (file.stat_info->archive_file_size - file.dumped_size);
+  skim_file (file.stat_info->archive_file_size - file.dumped_size, must_copy);
   return (tar_sparse_done (&file) && rc) ? dump_status_ok : dump_status_short;
 }
 
@@ -721,7 +721,7 @@ sparse_diff_file (int fd, struct tar_stat_info *st)
     }
 
   if (!rc)
-    skip_file (file.stat_info->archive_file_size - file.dumped_size);
+    skim_file (file.stat_info->archive_file_size - file.dumped_size, false);
   mv_end ();
 
   tar_sparse_done (&file);
index eece03659e3315901b1321cb16377472ac392953..5424e2cee484597201ce857a66013860840cf8be 100644 (file)
@@ -42,6 +42,8 @@ bool time_to_start_writing;
    first part of the record.  */
 char *output_start;
 
+static bool acting_as_filter;
+
 /* Catenate file FILE_NAME to the archive without creating a header for it.
    It had better be a tar file or the archive is screwed.  */
 static void
@@ -110,6 +112,7 @@ update_archive (void)
 
   name_gather ();
   open_archive (ACCESS_UPDATE);
+  acting_as_filter = strcmp (archive_name_array[0], "-") == 0;
   xheader_forbid_global ();
 
   while (!found_end)
@@ -166,7 +169,7 @@ update_archive (void)
                  }
              }
 
-           skip_member ();
+           skim_member (acting_as_filter);
            break;
          }
 
index f323975ee172e40e4523a3f0db685127f3c71c22..251b54974f76b8433baaf9a1fb7100030732b30e 100644 (file)
@@ -27,8 +27,10 @@ AT_TAR_CHECK([
 genfile -l 50000 --file file1
 genfile -l 1024 --file file2
 tar cf archive file1 file2
+tar -f - --delete file2 <archive >archout
+tar tf archout
 tar f archive --delete file2
-tar tf archive],
+cmp archive archout],
 [0],
 [file1
 ])
index 694c85d7b309bc6d5b2d450c0ed31f8d1526224a..a21cd6d4576f3af3dd8d62edfb4d57d013f4064a 100644 (file)
@@ -31,7 +31,9 @@ tar cf archive 1 2 3
 tar tf archive
 cat archive | tar f - --delete 2 > archive2
 echo separator
-tar tf archive2],
+tar tf archive2
+tar f - --delete 2 < archive > archive3
+cmp archive2 archive3],
 [0],
 [1
 2