Hello François, On Thursday, March 13, 2014 14:43:55 François Ouellet wrote: > When dumping a file which is being actively updated by an application > (in our case it was an outlook pst file on our samba server), safe_read() > can sometimes return 0. > > When it happens, sparse_dump_region() goes into an infinite loop. > > I don't know what the proper fix would be. I just fixed it on our server > with this:
thanks for clear report! You can try the attached patch if you wanted (but it has the same effects as yours one, just the message is little bit different). Reproducer: $ truncate -s 10M file $ tar cSf archive file $ truncate -s 5M file $ tar df archive Could we please apply at least something like the patch attached? Looking at the code, I don't like duplications. I was thinking about safe_read/blocking_read wrapper with sth. like 'bool may_eof' argument (and others) to make it possible to deal with errors at one place (stopped once I realized that it will be somehow bigger code change). Would you be interested in such patch? Pavel
>From c008273c7488e47dbf942ec79e83e95d34ff873f Mon Sep 17 00:00:00 2001 From: Pavel Raiskup <prais...@redhat.com> Date: Sun, 16 Mar 2014 22:26:33 +0100 Subject: [PATCH] tar: fix infinite loop in --diff for sparse files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When sparse file got truncated, tar went into infinite loop (reliably when the file ended by hole originally, unlikely when file ended with data). Fixed also potential loop (in similar fashion) in sparse files dumping. Original bugreport: http://lists.gnu.org/archive/html/bug-tar/2014-03/msg00052.html Thanks François Ouellet * src/sparse.c (check_sparse_region): Return false when safe_read returns 0. Also print the error on correct position in file. (check_data_region): Report error when safe_read returns 0. (sparse_dump_region): Fail if file got truncated. * THANKS: Adjust. --- THANKS | 1 + src/sparse.c | 48 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/THANKS b/THANKS index b4c5427..e74f71c 100644 --- a/THANKS +++ b/THANKS @@ -175,6 +175,7 @@ Fabio d'Alessi c...@civ.bio.unipd.it Frank Heckenbach fr...@g-n-u.de Frank Koenen koe...@lidp.com Franz-Werner Gergen ger...@edvulx.mpi-stuttgart.mpg.de +François Ouellet fou...@gmail.com François Pinard pin...@iro.umontreal.ca Fritz Elfert fr...@fsun.triltsch.de George Chyu gsc...@ccgate.dp.beckman.com diff --git a/src/sparse.c b/src/sparse.c index 6a97676..53c1868 100644 --- a/src/sparse.c +++ b/src/sparse.c @@ -301,6 +301,7 @@ sparse_dump_region (struct tar_sparse_file *file, size_t i) { union block *blk; off_t bytes_left = file->stat_info->sparse_map[i].numbytes; + const char *file_name = file->stat_info->orig_file_name; if (!lseek_or_error (file, file->stat_info->sparse_map[i].offset)) return false; @@ -314,13 +315,23 @@ sparse_dump_region (struct tar_sparse_file *file, size_t i) bytes_read = safe_read (file->fd, blk->buffer, bufsize); if (bytes_read == SAFE_READ_ERROR) { - read_diag_details (file->stat_info->orig_file_name, + read_diag_details (file_name, (file->stat_info->sparse_map[i].offset + file->stat_info->sparse_map[i].numbytes - bytes_left), bufsize); return false; } + if (bytes_read == 0) + { + char buf[UINTMAX_STRSIZE_BOUND]; + FATAL_ERROR ((0, 0, + ngettext ("%s: File shrank by %s byte", + "%s: File shrank by %s bytes", + bytes_left), + quotearg_colon (file_name), + offtostr (bytes_left, buf))); + } memset (blk->buffer + bytes_read, 0, BLOCKSIZE - bytes_read); bytes_left -= bytes_read; @@ -475,33 +486,37 @@ sparse_skip_file (struct tar_stat_info *st) static bool check_sparse_region (struct tar_sparse_file *file, off_t beg, off_t end) { - if (!lseek_or_error (file, beg)) + off_t offset = beg; + + if (!lseek_or_error (file, offset)) return false; - while (beg < end) + while (offset < end) { size_t bytes_read; - size_t rdsize = BLOCKSIZE < end - beg ? BLOCKSIZE : end - beg; + size_t rdsize = BLOCKSIZE < end - offset ? BLOCKSIZE : end - offset; char diff_buffer[BLOCKSIZE]; bytes_read = safe_read (file->fd, diff_buffer, rdsize); if (bytes_read == SAFE_READ_ERROR) { read_diag_details (file->stat_info->orig_file_name, - beg, - rdsize); - return false; - } - if (!zero_block_p (diff_buffer, bytes_read)) - { - char begbuf[INT_BUFSIZE_BOUND (off_t)]; - report_difference (file->stat_info, - _("File fragment at %s is not a hole"), - offtostr (beg, begbuf)); + offset, rdsize); return false; } - beg += bytes_read; + if (bytes_read == 0 + || !zero_block_p (diff_buffer, bytes_read)) + { + char begbuf[INT_BUFSIZE_BOUND (off_t)]; + const char *msg = bytes_read ? _("File fragment at %s is not a hole") + : _("Hole starting at %s is truncated"); + + report_difference (file->stat_info, msg, offtostr (beg, begbuf)); + return false; + } + + offset += bytes_read; } return true; } @@ -542,7 +557,8 @@ check_data_region (struct tar_sparse_file *file, size_t i) file->dumped_size += bytes_read; size_left -= bytes_read; mv_size_left (file->stat_info->archive_file_size - file->dumped_size); - if (memcmp (blk->buffer, diff_buffer, rdsize)) + if (bytes_read == 0 + || memcmp (blk->buffer, diff_buffer, bytes_read)) { report_difference (file->stat_info, _("Contents differ")); return false; -- 1.8.5.3