X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=src%2Fcompare.c;h=98a953bf403aa524ead280729c106f1379fd1e35;hb=d659cbaccdc1f3279c49107cf15f15a639738529;hp=4ae3392878decfaa9728c292f3a0549656dfdc17;hpb=91b2d65e9dccae981f308a659d6c7bdd32285598;p=chaz%2Ftar diff --git a/src/compare.c b/src/compare.c index 4ae3392..98a953b 100644 --- a/src/compare.c +++ b/src/compare.c @@ -1,7 +1,7 @@ /* Diff files from a tar archive. Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001, - 2003 Free Software Foundation, Inc. + 2003, 2004, 2005 Free Software Foundation, Inc. Written by John Gilmore, on 1987-04-30. @@ -17,31 +17,20 @@ 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., - 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "system.h" - -#if HAVE_UTIME_H -# include -#else -struct utimbuf - { - long actime; - long modtime; - }; -#endif +#include #if HAVE_LINUX_FD_H # include #endif #include +#include #include "common.h" -#include "rmt.h" - -/* Spare space for messages, hopefully safe even after gettext. */ -#define MESSAGE_BUFFER_SIZE 100 +#include +#include /* Nonzero if we are verifying at the moment. */ bool now_verifying; @@ -56,18 +45,28 @@ static char *diff_buffer; void diff_init (void) { - diff_buffer = valloc (record_size); - if (!diff_buffer) - xalloc_die (); + void *ptr; + diff_buffer = page_aligned_alloc (&ptr, record_size); + if (listed_incremental_option) + read_directory_file (); } /* Sigh about something that differs by writing a MESSAGE to stdlis, given MESSAGE is nonzero. Also set the exit status if not already. */ -static void -report_difference (const char *message) +void +report_difference (struct tar_stat_info *st __attribute__ ((unused)), + const char *fmt, ...) { - if (message) - fprintf (stdlis, "%s: %s\n", quotearg_colon (current_stat_info.file_name), message); + if (fmt) + { + va_list ap; + + fprintf (stdlis, "%s: ", quotearg_colon (current_stat_info.file_name)); + va_start (ap, fmt); + vfprintf (stdlis, fmt, ap); + va_end (ap); + fprintf (stdlis, "\n"); + } if (exit_status == TAREXIT_SUCCESS) exit_status = TAREXIT_DIFFERS; @@ -75,41 +74,38 @@ report_difference (const char *message) /* Take a buffer returned by read_and_process and do nothing with it. */ static int -process_noop (size_t size, char *data) +process_noop (size_t size __attribute__ ((unused)), + char *data __attribute__ ((unused))) { - /* Yes, I know. SIZE and DATA are unused in this function. Some - compilers may even report it. That's OK, just relax! */ return 1; } static int process_rawdata (size_t bytes, char *buffer) { - ssize_t status = safe_read (diff_handle, diff_buffer, bytes); - char message[MESSAGE_BUFFER_SIZE]; + size_t status = safe_read (diff_handle, diff_buffer, bytes); if (status != bytes) { - if (status < 0) + if (status == SAFE_READ_ERROR) { read_error (current_stat_info.file_name); - report_difference (0); + report_difference (¤t_stat_info, NULL); } else { - sprintf (message, - ngettext ("Could only read %lu of %lu byte", - "Could only read %lu of %lu bytes", - bytes), - (unsigned long) status, (unsigned long) bytes); - report_difference (message); + report_difference (¤t_stat_info, + ngettext ("Could only read %lu of %lu byte", + "Could only read %lu of %lu bytes", + bytes), + (unsigned long) status, (unsigned long) bytes); } return 0; } if (memcmp (buffer, diff_buffer, bytes)) { - report_difference (_("Contents differ")); + report_difference (¤t_stat_info, _("Contents differ")); return 0; } @@ -125,7 +121,7 @@ process_dumpdir (size_t bytes, char *buffer) { if (memcmp (buffer, dumpdir_cursor, bytes)) { - report_difference (_("Contents differ")); + report_difference (¤t_stat_info, _("Contents differ")); return 0; } @@ -168,186 +164,290 @@ read_and_process (off_t size, int (*processor) (size_t, char *)) } } -/* JK Diff'ing a sparse file with its counterpart on the tar file is a - bit of a different story than a normal file. First, we must know what - areas of the file to skip through, i.e., we need to construct a - sparsearray, which will hold all the information we need. We must - compare small amounts of data at a time as we find it. */ +/* Call either stat or lstat over STAT_DATA, depending on + --dereference (-h), for a file which should exist. Diagnose any + problem. Return nonzero for success, zero otherwise. */ +static int +get_stat_data (char const *file_name, struct stat *stat_data) +{ + int status = deref_stat (dereference_option, file_name, stat_data); -/* FIXME: This does not look very solid to me, at first glance. Zero areas - are not checked, spurious sparse entries seemingly goes undetected, and - I'm not sure overall identical sparsity is verified. */ + if (status != 0) + { + if (errno == ENOENT) + stat_warn (file_name); + else + stat_error (file_name); + report_difference (¤t_stat_info, NULL); + return 0; + } + return 1; +} + + static void -diff_sparse_files (void) +diff_dir (void) { - off_t remaining_size = current_stat_info.stat.st_size; - char *buffer = xmalloc (BLOCKSIZE * sizeof (char)); - size_t buffer_size = BLOCKSIZE; - union block *data_block = 0; - int counter = 0; - int different = 0; - - if (! fill_in_sparse_array ()) - fatal_exit (); + struct stat stat_data; - while (remaining_size > 0) - { - ssize_t status; - size_t chunk_size; - off_t offset; + if (!get_stat_data (current_stat_info.file_name, &stat_data)) + return; -#if 0 - off_t amount_read = 0; -#endif + if (!S_ISDIR (stat_data.st_mode)) + report_difference (¤t_stat_info, _("File type differs")); + else if ((current_stat_info.stat.st_mode & MODE_ALL) != + (stat_data.st_mode & MODE_ALL)) + report_difference (¤t_stat_info, _("Mode differs")); +} - data_block = find_next_block (); - if (!data_block) - FATAL_ERROR ((0, 0, _("Unexpected EOF in archive"))); - chunk_size = sparsearray[counter].numbytes; - if (!chunk_size) - break; +static void +diff_file (void) +{ + struct stat stat_data; - offset = sparsearray[counter].offset; - if (lseek (diff_handle, offset, SEEK_SET) < 0) - { - seek_error_details (current_stat_info.file_name, offset); - report_difference (0); - } + if (!get_stat_data (current_stat_info.file_name, &stat_data)) + skip_member (); + else if (!S_ISREG (stat_data.st_mode)) + { + report_difference (¤t_stat_info, _("File type differs")); + skip_member (); + } + else + { + if ((current_stat_info.stat.st_mode & MODE_ALL) != + (stat_data.st_mode & MODE_ALL)) + report_difference (¤t_stat_info, _("Mode differs")); - /* Take care to not run out of room in our buffer. */ + if (!sys_compare_uid (&stat_data, ¤t_stat_info.stat)) + report_difference (¤t_stat_info, _("Uid differs")); + if (!sys_compare_gid (&stat_data, ¤t_stat_info.stat)) + report_difference (¤t_stat_info, _("Gid differs")); - while (buffer_size < chunk_size) + if (stat_data.st_mtime != current_stat_info.stat.st_mtime) + report_difference (¤t_stat_info, _("Mod time differs")); + if (current_header->header.typeflag != GNUTYPE_SPARSE && + stat_data.st_size != current_stat_info.stat.st_size) { - if (buffer_size * 2 < buffer_size) - xalloc_die (); - buffer_size *= 2; - buffer = xrealloc (buffer, buffer_size * sizeof (char)); + report_difference (¤t_stat_info, _("Size differs")); + skip_member (); } - - while (chunk_size > BLOCKSIZE) + else { - if (status = safe_read (diff_handle, buffer, BLOCKSIZE), - status != BLOCKSIZE) + diff_handle = open (current_stat_info.file_name, O_RDONLY | O_BINARY); + + if (diff_handle < 0) { - if (status < 0) - { - read_error (current_stat_info.file_name); - report_difference (0); - } + open_error (current_stat_info.file_name); + skip_member (); + report_difference (¤t_stat_info, NULL); + } + else + { + int status; + + if (current_stat_info.is_sparse) + sparse_diff_file (diff_handle, ¤t_stat_info); else { - char message[MESSAGE_BUFFER_SIZE]; - - sprintf (message, - ngettext ("Could only read %lu of %lu byte", - "Could only read %lu of %lu bytes", - chunk_size), - (unsigned long) status, (unsigned long) chunk_size); - report_difference (message); + if (multi_volume_option) + { + assign_string (&save_name, + current_stat_info.orig_file_name); + save_totsize = current_stat_info.stat.st_size; + /* save_sizeleft is set in read_and_process. */ + } + + read_and_process (current_stat_info.stat.st_size, + process_rawdata); + + if (multi_volume_option) + assign_string (&save_name, 0); } - break; - } - if (memcmp (buffer, data_block->buffer, BLOCKSIZE)) - { - different = 1; - break; - } + status = close (diff_handle); + if (status != 0) + close_error (current_stat_info.file_name); - chunk_size -= status; - remaining_size -= status; - set_next_block_after (data_block); - data_block = find_next_block (); - if (!data_block) - FATAL_ERROR ((0, 0, _("Unexpected EOF in archive"))); - } - if (status = safe_read (diff_handle, buffer, chunk_size), - status != chunk_size) - { - if (status < 0) - { - read_error (current_stat_info.file_name); - report_difference (0); - } - else - { - char message[MESSAGE_BUFFER_SIZE]; - - sprintf (message, - ngettext ("Could only read %lu of %lu byte", - "Could only read %lu of %lu bytes", - chunk_size), - (unsigned long) status, (unsigned long) chunk_size); - report_difference (message); + if (atime_preserve_option) + { + struct timespec ts[2]; + ts[0] = get_stat_atime (&stat_data); + ts[1] = get_stat_mtime (&stat_data); + if (utimens (current_stat_info.file_name, ts) != 0) + utime_error (current_stat_info.file_name); + } } - break; } + } +} - if (memcmp (buffer, data_block->buffer, chunk_size)) - { - different = 1; - break; - } -#if 0 - amount_read += chunk_size; - if (amount_read >= BLOCKSIZE) - { - amount_read = 0; - set_next_block_after (data_block); - data_block = find_next_block (); - if (!data_block) - FATAL_ERROR ((0, 0, _("Unexpected EOF in archive"))); - } +static void +diff_link (void) +{ + struct stat file_data; + struct stat link_data; + + if (get_stat_data (current_stat_info.file_name, &file_data) + && get_stat_data (current_stat_info.link_name, &link_data) + && !sys_compare_links (&file_data, &link_data)) + report_difference (¤t_stat_info, + _("Not linked to %s"), + quote (current_stat_info.link_name)); +} + +#ifdef HAVE_READLINK +static void +diff_symlink (void) +{ + size_t len = strlen (current_stat_info.link_name); + char *linkbuf = alloca (len + 1); + + int status = readlink (current_stat_info.file_name, linkbuf, len + 1); + + if (status < 0) + { + if (errno == ENOENT) + readlink_warn (current_stat_info.file_name); + else + readlink_error (current_stat_info.file_name); + report_difference (¤t_stat_info, NULL); + } + else if (status != len + || strncmp (current_stat_info.link_name, linkbuf, len) != 0) + report_difference (¤t_stat_info, _("Symlink differs")); +} #endif - set_next_block_after (data_block); - counter++; - remaining_size -= chunk_size; + +static void +diff_special (void) +{ + struct stat stat_data; + + /* FIXME: deal with umask. */ + + if (!get_stat_data (current_stat_info.file_name, &stat_data)) + return; + + if (current_header->header.typeflag == CHRTYPE + ? !S_ISCHR (stat_data.st_mode) + : current_header->header.typeflag == BLKTYPE + ? !S_ISBLK (stat_data.st_mode) + : /* current_header->header.typeflag == FIFOTYPE */ + !S_ISFIFO (stat_data.st_mode)) + { + report_difference (¤t_stat_info, _("File type differs")); + return; + } + + if ((current_header->header.typeflag == CHRTYPE + || current_header->header.typeflag == BLKTYPE) + && current_stat_info.stat.st_rdev != stat_data.st_rdev) + { + report_difference (¤t_stat_info, _("Device number differs")); + return; } -#if 0 - /* If the number of bytes read isn't the number of bytes supposedly in - the file, they're different. */ + if ((current_stat_info.stat.st_mode & MODE_ALL) != + (stat_data.st_mode & MODE_ALL)) + report_difference (¤t_stat_info, _("Mode differs")); +} - if (amount_read != current_stat_info.stat.st_size) - different = 1; -#endif +static void +diff_dumpdir (void) +{ + char *dumpdir_buffer = get_directory_contents (current_stat_info.file_name, + 0); + + if (multi_volume_option) + { + assign_string (&save_name, current_stat_info.orig_file_name); + save_totsize = current_stat_info.stat.st_size; + /* save_sizeleft is set in read_and_process. */ + } - set_next_block_after (data_block); - free (sparsearray); + if (dumpdir_buffer) + { + dumpdir_cursor = dumpdir_buffer; + read_and_process (current_stat_info.stat.st_size, process_dumpdir); + free (dumpdir_buffer); + } + else + read_and_process (current_stat_info.stat.st_size, process_noop); - if (different) - report_difference (_("Contents differ")); + if (multi_volume_option) + assign_string (&save_name, 0); } -/* Call either stat or lstat over STAT_DATA, depending on - --dereference (-h), for a file which should exist. Diagnose any - problem. Return nonzero for success, zero otherwise. */ -static int -get_stat_data (char const *file_name, struct stat *stat_data) +static void +diff_multivol (void) { - int status = deref_stat (dereference_option, file_name, stat_data); + struct stat stat_data; + int fd, status; + off_t offset; - if (status != 0) + if (current_stat_info.had_trailing_slash) { - if (errno == ENOENT) - stat_warn (file_name); - else - stat_error (file_name); - report_difference (0); - return 0; + diff_dir (); + return; } - return 1; + if (!get_stat_data (current_stat_info.file_name, &stat_data)) + return; + + if (!S_ISREG (stat_data.st_mode)) + { + report_difference (¤t_stat_info, _("File type differs")); + skip_member (); + return; + } + + offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset); + if (stat_data.st_size != current_stat_info.stat.st_size + offset) + { + report_difference (¤t_stat_info, _("Size differs")); + skip_member (); + return; + } + + fd = open (current_stat_info.file_name, O_RDONLY | O_BINARY); + + if (fd < 0) + { + open_error (current_stat_info.file_name); + report_difference (¤t_stat_info, NULL); + skip_member (); + return; + } + + if (lseek (fd, offset, SEEK_SET) < 0) + { + seek_error_details (current_stat_info.file_name, offset); + report_difference (¤t_stat_info, NULL); + return; + } + + if (multi_volume_option) + { + assign_string (&save_name, current_stat_info.orig_file_name); + save_totsize = stat_data.st_size; + /* save_sizeleft is set in read_and_process. */ + } + + read_and_process (current_stat_info.stat.st_size, process_rawdata); + + if (multi_volume_option) + assign_string (&save_name, 0); + + status = close (fd); + if (status != 0) + close_error (current_stat_info.file_name); } /* Diff a file against the archive. */ void diff_archive (void) { - struct stat stat_data; - int status; - struct utimbuf restore_times; set_next_block_after (current_header); decode_header (current_header, ¤t_stat_info, ¤t_format, 1); @@ -358,13 +458,13 @@ diff_archive (void) { if (now_verifying) fprintf (stdlis, _("Verify ")); - print_header (-1); + print_header (¤t_stat_info, -1); } switch (current_header->header.typeflag) { default: - ERROR ((0, 0, _("%s: Unknown file type '%c', diffed as normal file"), + ERROR ((0, 0, _("%s: Unknown file type `%c', diffed as normal file"), quotearg_colon (current_stat_info.file_name), current_header->header.typeflag)); /* Fall through. */ @@ -377,286 +477,54 @@ diff_archive (void) /* Appears to be a file. See if it's really a directory. */ if (current_stat_info.had_trailing_slash) - goto really_dir; - - if (!get_stat_data (current_stat_info.file_name, &stat_data)) - { - skip_member (); - goto quit; - } - - if (!S_ISREG (stat_data.st_mode)) - { - report_difference (_("File type differs")); - skip_member (); - goto quit; - } - - if ((current_stat_info.stat.st_mode & MODE_ALL) != (stat_data.st_mode & MODE_ALL)) - report_difference (_("Mode differs")); - -#if !MSDOS - /* stat() in djgpp's C library gives a constant number of 42 as the - uid and gid of a file. So, comparing an FTP'ed archive just after - unpack would fail on MSDOS. */ - if (stat_data.st_uid != current_stat_info.stat.st_uid) - report_difference (_("Uid differs")); - if (stat_data.st_gid != current_stat_info.stat.st_gid) - report_difference (_("Gid differs")); -#endif - - if (stat_data.st_mtime != current_stat_info.stat.st_mtime) - report_difference (_("Mod time differs")); - if (current_header->header.typeflag != GNUTYPE_SPARSE && - stat_data.st_size != current_stat_info.stat.st_size) - { - report_difference (_("Size differs")); - skip_member (); - goto quit; - } - - diff_handle = open (current_stat_info.file_name, O_RDONLY | O_BINARY); - - if (diff_handle < 0) - { - open_error (current_stat_info.file_name); - skip_member (); - report_difference (0); - goto quit; - } - - restore_times.actime = stat_data.st_atime; - restore_times.modtime = stat_data.st_mtime; - - /* Need to treat sparse files completely differently here. */ - - if (current_header->header.typeflag == GNUTYPE_SPARSE) - diff_sparse_files (); + diff_dir (); else - { - if (multi_volume_option) - { - assign_string (&save_name, current_stat_info.file_name); - save_totsize = current_stat_info.stat.st_size; - /* save_sizeleft is set in read_and_process. */ - } - - read_and_process (current_stat_info.stat.st_size, process_rawdata); - - if (multi_volume_option) - assign_string (&save_name, 0); - } - - status = close (diff_handle); - if (status != 0) - close_error (current_stat_info.file_name); - - if (atime_preserve_option) - utime (current_stat_info.file_name, &restore_times); - - quit: + diff_file (); break; -#if !MSDOS case LNKTYPE: - { - struct stat link_data; - - if (!get_stat_data (current_stat_info.file_name, &stat_data)) - break; - if (!get_stat_data (current_stat_info.link_name, &link_data)) - break; - - if (stat_data.st_dev != link_data.st_dev - || stat_data.st_ino != link_data.st_ino) - { - char *message = - xmalloc (MESSAGE_BUFFER_SIZE + 4 * strlen (current_stat_info.link_name)); - - sprintf (message, _("Not linked to %s"), - quote (current_stat_info.link_name)); - report_difference (message); - free (message); - break; - } - - break; - } -#endif /* not MSDOS */ + diff_link (); + break; #ifdef HAVE_READLINK case SYMTYPE: - { - size_t len = strlen (current_stat_info.link_name); - char *linkbuf = alloca (len + 1); - - status = readlink (current_stat_info.file_name, linkbuf, len + 1); - - if (status < 0) - { - if (errno == ENOENT) - readlink_warn (current_stat_info.file_name); - else - readlink_error (current_stat_info.file_name); - report_difference (0); - } - else if (status != len - || strncmp (current_stat_info.link_name, linkbuf, len) != 0) - report_difference (_("Symlink differs")); - - break; - } + diff_symlink (); + break; #endif case CHRTYPE: case BLKTYPE: case FIFOTYPE: - - /* FIXME: deal with umask. */ - - if (!get_stat_data (current_stat_info.file_name, &stat_data)) - break; - - if (current_header->header.typeflag == CHRTYPE - ? !S_ISCHR (stat_data.st_mode) - : current_header->header.typeflag == BLKTYPE - ? !S_ISBLK (stat_data.st_mode) - : /* current_header->header.typeflag == FIFOTYPE */ - !S_ISFIFO (stat_data.st_mode)) - { - report_difference (_("File type differs")); - break; - } - - if ((current_header->header.typeflag == CHRTYPE - || current_header->header.typeflag == BLKTYPE) - && current_stat_info.stat.st_rdev != stat_data.st_rdev) - { - report_difference (_("Device number differs")); - break; - } - - if ((current_stat_info.stat.st_mode & MODE_ALL) != (stat_data.st_mode & MODE_ALL)) - { - report_difference (_("Mode differs")); - break; - } - + diff_special (); break; case GNUTYPE_DUMPDIR: - { - char *dumpdir_buffer = get_directory_contents (current_stat_info.file_name, 0); - - if (multi_volume_option) - { - assign_string (&save_name, current_stat_info.file_name); - save_totsize = current_stat_info.stat.st_size; - /* save_sizeleft is set in read_and_process. */ - } - - if (dumpdir_buffer) - { - dumpdir_cursor = dumpdir_buffer; - read_and_process (current_stat_info.stat.st_size, process_dumpdir); - free (dumpdir_buffer); - } - else - read_and_process (current_stat_info.stat.st_size, process_noop); - - if (multi_volume_option) - assign_string (&save_name, 0); - /* Fall through. */ - } + diff_dumpdir (); + /* Fall through. */ case DIRTYPE: - really_dir: - if (!get_stat_data (current_stat_info.file_name, &stat_data)) - break; - - if (!S_ISDIR (stat_data.st_mode)) - { - report_difference (_("File type differs")); - break; - } - - if ((current_stat_info.stat.st_mode & MODE_ALL) != (stat_data.st_mode & MODE_ALL)) - { - report_difference (_("Mode differs")); - break; - } - + diff_dir (); break; case GNUTYPE_VOLHDR: break; case GNUTYPE_MULTIVOL: - { - off_t offset; - - if (current_stat_info.had_trailing_slash) - goto really_dir; - - if (!get_stat_data (current_stat_info.file_name, &stat_data)) - break; - - if (!S_ISREG (stat_data.st_mode)) - { - report_difference (_("File type differs")); - skip_member (); - break; - } - - offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset); - if (stat_data.st_size != current_stat_info.stat.st_size + offset) - { - report_difference (_("Size differs")); - skip_member (); - break; - } - - diff_handle = open (current_stat_info.file_name, O_RDONLY | O_BINARY); - - if (diff_handle < 0) - { - open_error (current_stat_info.file_name); - report_difference (0); - skip_member (); - break; - } - - if (lseek (diff_handle, offset, SEEK_SET) < 0) - { - seek_error_details (current_stat_info.file_name, offset); - report_difference (0); - break; - } - - if (multi_volume_option) - { - assign_string (&save_name, current_stat_info.file_name); - save_totsize = stat_data.st_size; - /* save_sizeleft is set in read_and_process. */ - } - - read_and_process (current_stat_info.stat.st_size, process_rawdata); - - if (multi_volume_option) - assign_string (&save_name, 0); - - status = close (diff_handle); - if (status != 0) - close_error (current_stat_info.file_name); - - break; - } + diff_multivol (); } } void verify_volume (void) { + if (removed_prefixes_p ()) + { + WARN((0, 0, + _("Archive contains file names with leading prefixes removed."))); + WARN((0, 0, + _("Verification may fail to locate original files."))); + } + if (!diff_buffer) diff_init (); @@ -718,6 +586,7 @@ verify_volume (void) do { counter++; + set_next_block_after (current_header); status = read_header (false); } while (status == HEADER_FAILURE); @@ -731,6 +600,8 @@ verify_volume (void) break; diff_archive (); + tar_stat_destroy (¤t_stat_info); + xheader_destroy (&extended_header); } access_mode = ACCESS_WRITE;