123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874 |
- /* Diff files from a tar archive.
- Copyright (C) 1988, 92, 93, 94, 96, 97 Free Software Foundation, Inc.
- Written by John Gilmore, on 1987-04-30.
- 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 2, 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.,
- 59 Place - Suite 330, Boston, MA 02111-1307, USA. */
- #include "system.h"
- #if HAVE_LINUX_FD_H
- # include <linux/fd.h>
- #endif
- #include "common.h"
- #include "rmt.h"
- /* Spare space for messages, hopefully safe even after gettext. */
- #define MESSAGE_BUFFER_SIZE 100
- /* Nonzero if we are verifying at the moment. */
- int now_verifying = 0;
- /* File descriptor for the file we are diffing. */
- static int diff_handle;
- /* Area for reading file contents into. */
- static char *diff_buffer = NULL;
- /*--------------------------------.
- | Initialize for a diff operation |
- `--------------------------------*/
- void
- diff_init (void)
- {
- diff_buffer = (char *) valloc ((unsigned) record_size);
- if (!diff_buffer)
- FATAL_ERROR ((0, 0,
- _("Could not allocate memory for diff buffer of %d bytes"),
- record_size));
- }
- /*------------------------------------------------------------------------.
- | Sigh about something that differs by writing a MESSAGE to stdlis, given |
- | MESSAGE is not NULL. Also set the exit status if not already. |
- `------------------------------------------------------------------------*/
- static void
- report_difference (const char *message)
- {
- if (message)
- fprintf (stdlis, "%s: %s\n", current_file_name, message);
- if (exit_status == TAREXIT_SUCCESS)
- exit_status = TAREXIT_DIFFERS;
- }
- /*-----------------------------------------------------------------------.
- | Takes a buffer returned by read_and_process and does nothing with it. |
- `-----------------------------------------------------------------------*/
- /* Yes, I know. SIZE and DATA are unused in this function. Some compilers
- may even report it. That's OK, just relax! */
- static int
- process_noop (long size, char *data)
- {
- return 1;
- }
- /*---.
- | ? |
- `---*/
- static int
- process_rawdata (long bytes, char *buffer)
- {
- int status = read (diff_handle, diff_buffer, (size_t) bytes);
- char message[MESSAGE_BUFFER_SIZE];
- if (status != bytes)
- {
- if (status < 0)
- {
- WARN ((0, errno, _("Cannot read %s"), current_file_name));
- report_difference (NULL);
- }
- else
- {
- sprintf (message, _("Could only read %d of %ld bytes"),
- status, bytes);
- report_difference (message);
- }
- return 0;
- }
- if (memcmp (buffer, diff_buffer, (size_t) bytes))
- {
- report_difference (_("Data differs"));
- return 0;
- }
- return 1;
- }
- /*---.
- | ? |
- `---*/
- /* Directory contents, only for GNUTYPE_DUMPDIR. */
- static char *dumpdir_cursor;
- static int
- process_dumpdir (long bytes, char *buffer)
- {
- if (memcmp (buffer, dumpdir_cursor, (size_t) bytes))
- {
- report_difference (_("Data differs"));
- return 0;
- }
- dumpdir_cursor += bytes;
- return 1;
- }
- /*------------------------------------------------------------------------.
- | Some other routine wants SIZE bytes in the archive. For each chunk of |
- | the archive, call PROCESSOR with the size of the chunk, and the address |
- | of the chunk it can work with. The PROCESSOR should return nonzero for |
- | success. It it return error once, continue skipping without calling |
- | PROCESSOR anymore. |
- `------------------------------------------------------------------------*/
- static void
- read_and_process (long size, int (*processor) (long, char *))
- {
- union block *data_block;
- long data_size;
- if (multi_volume_option)
- save_sizeleft = size;
- while (size)
- {
- data_block = find_next_block ();
- if (data_block == NULL)
- {
- ERROR ((0, 0, _("Unexpected EOF on archive file")));
- return;
- }
- data_size = available_space_after (data_block);
- if (data_size > size)
- data_size = size;
- if (!(*processor) (data_size, data_block->buffer))
- processor = process_noop;
- set_next_block_after ((union block *)
- (data_block->buffer + data_size - 1));
- size -= data_size;
- if (multi_volume_option)
- save_sizeleft -= data_size;
- }
- }
- /*---.
- | ? |
- `---*/
- /* JK This routine should be used more often than it is ... look into
- that. Anyhow, what it does is translate the sparse information on the
- header, and in any subsequent extended headers, into an array of
- structures with true numbers, as opposed to character strings. It
- simply makes our life much easier, doing so many comparisong and such.
- */
- static void
- fill_in_sparse_array (void)
- {
- int counter;
- /* Allocate space for our scratch space; it's initially 10 elements
- long, but can change in this routine if necessary. */
- sp_array_size = 10;
- sparsearray = (struct sp_array *) xmalloc (sp_array_size * sizeof (struct sp_array));
- /* There are at most five of these structures in the header itself;
- read these in first. */
- for (counter = 0; counter < SPARSES_IN_OLDGNU_HEADER; counter++)
- {
- /* Compare to 0, or use !(int)..., for Pyramid's dumb compiler. */
- if (current_header->oldgnu_header.sp[counter].numbytes == 0)
- break;
- sparsearray[counter].offset =
- from_oct (1 + 12, current_header->oldgnu_header.sp[counter].offset);
- sparsearray[counter].numbytes =
- from_oct (1 + 12, current_header->oldgnu_header.sp[counter].numbytes);
- }
- /* If the header's extended, we gotta read in exhdr's till we're done. */
- if (current_header->oldgnu_header.isextended)
- {
- /* How far into the sparsearray we are `so far'. */
- static int so_far_ind = SPARSES_IN_OLDGNU_HEADER;
- union block *exhdr;
- while (1)
- {
- exhdr = find_next_block ();
- for (counter = 0; counter < SPARSES_IN_SPARSE_HEADER; counter++)
- {
- if (counter + so_far_ind > sp_array_size - 1)
- {
- /* We just ran out of room in our scratch area -
- realloc it. */
- sp_array_size *= 2;
- sparsearray = (struct sp_array *)
- xrealloc (sparsearray,
- sp_array_size * sizeof (struct sp_array));
- }
- /* Convert the character strings into longs. */
- sparsearray[counter + so_far_ind].offset =
- from_oct (1 + 12, exhdr->sparse_header.sp[counter].offset);
- sparsearray[counter + so_far_ind].numbytes =
- from_oct (1 + 12, exhdr->sparse_header.sp[counter].numbytes);
- }
- /* If this is the last extended header for this file, we can
- stop. */
- if (!exhdr->sparse_header.isextended)
- break;
- so_far_ind += SPARSES_IN_SPARSE_HEADER;
- set_next_block_after (exhdr);
- }
- /* Be sure to skip past the last one. */
- set_next_block_after (exhdr);
- }
- }
- /*---.
- | ? |
- `---*/
- /* 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 contruct a
- sparsearray, which will hold all the information we need. We must
- compare small amounts of data at a time as we find it. */
- /* 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. */
- static void
- diff_sparse_files (int size_of_file)
- {
- int remaining_size = size_of_file;
- char *buffer = (char *) xmalloc (BLOCKSIZE * sizeof (char));
- int buffer_size = BLOCKSIZE;
- union block *data_block = NULL;
- int counter = 0;
- int different = 0;
- fill_in_sparse_array ();
- while (remaining_size > 0)
- {
- int status;
- long chunk_size;
- #if 0
- int amount_read = 0;
- #endif
- data_block = find_next_block ();
- chunk_size = sparsearray[counter].numbytes;
- if (!chunk_size)
- break;
- lseek (diff_handle, sparsearray[counter].offset, 0);
- /* Take care to not run out of room in our buffer. */
- while (buffer_size < chunk_size)
- {
- buffer_size *= 2;
- buffer = (char *) xrealloc (buffer, buffer_size * sizeof (char));
- }
- while (chunk_size > BLOCKSIZE)
- {
- if (status = read (diff_handle, buffer, BLOCKSIZE),
- status != BLOCKSIZE)
- {
- if (status < 0)
- {
- WARN ((0, errno, _("Cannot read %s"), current_file_name));
- report_difference (NULL);
- }
- else
- {
- char message[MESSAGE_BUFFER_SIZE];
- sprintf (message, _("Could only read %d of %ld bytes"),
- status, chunk_size);
- report_difference (message);
- }
- break;
- }
- if (memcmp (buffer, data_block->buffer, BLOCKSIZE))
- {
- different = 1;
- break;
- }
- chunk_size -= status;
- remaining_size -= status;
- set_next_block_after (data_block);
- data_block = find_next_block ();
- }
- if (status = read (diff_handle, buffer, (size_t) chunk_size),
- status != chunk_size)
- {
- if (status < 0)
- {
- WARN ((0, errno, _("Cannot read %s"), current_file_name));
- report_difference (NULL);
- }
- else
- {
- char message[MESSAGE_BUFFER_SIZE];
- sprintf (message, _("Could only read %d of %ld bytes"),
- status, chunk_size);
- report_difference (message);
- }
- break;
- }
- if (memcmp (buffer, data_block->buffer, (size_t) 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 ();
- }
- #endif
- set_next_block_after (data_block);
- counter++;
- remaining_size -= chunk_size;
- }
- #if 0
- /* If the number of bytes read isn't the number of bytes supposedly in
- the file, they're different. */
- if (amount_read != size_of_file)
- different = 1;
- #endif
- set_next_block_after (data_block);
- free (sparsearray);
- if (different)
- report_difference (_("Data differs"));
- }
- /*---------------------------------------------------------------------.
- | 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 (struct stat *stat_data)
- {
- int status = (dereference_option
- ? stat (current_file_name, stat_data)
- : lstat (current_file_name, stat_data));
- if (status < 0)
- {
- if (errno == ENOENT)
- report_difference (_("File does not exist"));
- else
- {
- ERROR ((0, errno, _("Cannot stat file %s"), current_file_name));
- report_difference (NULL);
- }
- #if 0
- skip_file ((long) current_stat.st_size);
- #endif
- return 0;
- }
- return 1;
- }
- /*----------------------------------.
- | Diff a file against the archive. |
- `----------------------------------*/
- void
- diff_archive (void)
- {
- struct stat stat_data;
- int name_length;
- int status;
- errno = EPIPE; /* FIXME: errno should be read-only */
- /* FIXME: remove perrors */
- set_next_block_after (current_header);
- decode_header (current_header, ¤t_stat, ¤t_format, 1);
- /* Print the block from `current_header' and `current_stat'. */
- if (verbose_option)
- {
- if (now_verifying)
- fprintf (stdlis, _("Verify "));
- print_header ();
- }
- switch (current_header->header.typeflag)
- {
- default:
- WARN ((0, 0, _("Unknown file type '%c' for %s, diffed as normal file"),
- current_header->header.typeflag, current_file_name));
- /* Fall through. */
- case AREGTYPE:
- case REGTYPE:
- case GNUTYPE_SPARSE:
- case CONTTYPE:
- /* Appears to be a file. See if it's really a directory. */
- name_length = strlen (current_file_name) - 1;
- if (current_file_name[name_length] == '/')
- goto really_dir;
- if (!get_stat_data (&stat_data))
- {
- if (current_header->oldgnu_header.isextended)
- skip_extended_headers ();
- skip_file ((long) current_stat.st_size);
- goto quit;
- }
- if (!S_ISREG (stat_data.st_mode))
- {
- report_difference (_("Not a regular file"));
- skip_file ((long) current_stat.st_size);
- goto quit;
- }
- stat_data.st_mode &= 07777;
- if (stat_data.st_mode != current_stat.st_mode)
- 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.st_uid)
- report_difference (_("Uid differs"));
- if (stat_data.st_gid != current_stat.st_gid)
- report_difference (_("Gid differs"));
- #endif
- if (stat_data.st_mtime != current_stat.st_mtime)
- report_difference (_("Mod time differs"));
- if (current_header->header.typeflag != GNUTYPE_SPARSE &&
- stat_data.st_size != current_stat.st_size)
- {
- report_difference (_("Size differs"));
- skip_file ((long) current_stat.st_size);
- goto quit;
- }
- diff_handle = open (current_file_name, O_NDELAY | O_RDONLY | O_BINARY);
- if (diff_handle < 0 && !absolute_names_option)
- {
- char *tmpbuf = xmalloc (strlen (current_file_name) + 2);
- *tmpbuf = '/';
- strcpy (tmpbuf + 1, current_file_name);
- diff_handle = open (tmpbuf, O_NDELAY | O_RDONLY);
- free (tmpbuf);
- }
- if (diff_handle < 0)
- {
- ERROR ((0, errno, _("Cannot open %s"), current_file_name));
- if (current_header->oldgnu_header.isextended)
- skip_extended_headers ();
- skip_file ((long) current_stat.st_size);
- report_difference (NULL);
- goto quit;
- }
- /* Need to treat sparse files completely differently here. */
- if (current_header->header.typeflag == GNUTYPE_SPARSE)
- diff_sparse_files (current_stat.st_size);
- else
- {
- if (multi_volume_option)
- {
- assign_string (&save_name, current_file_name);
- save_totsize = current_stat.st_size;
- /* save_sizeleft is set in read_and_process. */
- }
- read_and_process ((long) (current_stat.st_size), process_rawdata);
- if (multi_volume_option)
- assign_string (&save_name, NULL);
- }
- status = close (diff_handle);
- if (status < 0)
- ERROR ((0, errno, _("Error while closing %s"), current_file_name));
- quit:
- break;
- #if !MSDOS
- case LNKTYPE:
- {
- dev_t dev;
- ino_t ino;
- if (!get_stat_data (&stat_data))
- break;
- dev = stat_data.st_dev;
- ino = stat_data.st_ino;
- status = stat (current_link_name, &stat_data);
- if (status < 0)
- {
- if (errno == ENOENT)
- report_difference (_("Does not exist"));
- else
- {
- WARN ((0, errno, _("Cannot stat file %s"), current_file_name));
- report_difference (NULL);
- }
- break;
- }
- if (stat_data.st_dev != dev || stat_data.st_ino != ino)
- {
- char *message = (char *)
- xmalloc (MESSAGE_BUFFER_SIZE + strlen (current_link_name));
- sprintf (message, _("Not linked to %s"), current_link_name);
- report_difference (message);
- free (message);
- break;
- }
- break;
- }
- #endif /* not MSDOS */
- #ifdef S_ISLNK
- case SYMTYPE:
- {
- char linkbuf[NAME_FIELD_SIZE + 3]; /* FIXME: may be too short. */
- status = readlink (current_file_name, linkbuf, (sizeof linkbuf) - 1);
- if (status < 0)
- {
- if (errno == ENOENT)
- report_difference (_("No such file or directory"));
- else
- {
- WARN ((0, errno, _("Cannot read link %s"), current_file_name));
- report_difference (NULL);
- }
- break;
- }
- linkbuf[status] = '\0'; /* null-terminate it */
- if (strncmp (current_link_name, linkbuf, (size_t) status) != 0)
- report_difference (_("Symlink differs"));
- break;
- }
- #endif /* not S_ISLNK */
- #ifdef S_IFCHR
- case CHRTYPE:
- current_stat.st_mode |= S_IFCHR;
- goto check_node;
- #endif /* not S_IFCHR */
- #ifdef S_IFBLK
- /* If local system doesn't support block devices, use default case. */
- case BLKTYPE:
- current_stat.st_mode |= S_IFBLK;
- goto check_node;
- #endif /* not S_IFBLK */
- #ifdef S_ISFIFO
- /* If local system doesn't support FIFOs, use default case. */
- case FIFOTYPE:
- # ifdef S_IFIFO
- current_stat.st_mode |= S_IFIFO;
- # endif
- current_stat.st_rdev = 0; /* FIXME: do we need this? */
- goto check_node;
- #endif /* S_ISFIFO */
- check_node:
- /* FIXME: deal with umask. */
- if (!get_stat_data (&stat_data))
- break;
- if (current_stat.st_rdev != stat_data.st_rdev)
- {
- report_difference (_("Device numbers changed"));
- break;
- }
- if (
- #ifdef S_IFMT
- current_stat.st_mode != stat_data.st_mode
- #else
- /* POSIX lossage. */
- (current_stat.st_mode & 07777) != (stat_data.st_mode & 07777)
- #endif
- )
- {
- report_difference (_("Mode or device-type changed"));
- break;
- }
- break;
- case GNUTYPE_DUMPDIR:
- {
- char *dumpdir_buffer = get_directory_contents (current_file_name, 0);
- if (multi_volume_option)
- {
- assign_string (&save_name, current_file_name);
- save_totsize = current_stat.st_size;
- /* save_sizeleft is set in read_and_process. */
- }
- if (dumpdir_buffer)
- {
- dumpdir_cursor = dumpdir_buffer;
- read_and_process ((long) (current_stat.st_size), process_dumpdir);
- free (dumpdir_buffer);
- }
- else
- read_and_process ((long) (current_stat.st_size), process_noop);
- if (multi_volume_option)
- assign_string (&save_name, NULL);
- /* Fall through. */
- }
- case DIRTYPE:
- /* Check for trailing /. */
- name_length = strlen (current_file_name) - 1;
- really_dir:
- while (name_length && current_file_name[name_length] == '/')
- current_file_name[name_length--] = '\0'; /* zap / */
- if (!get_stat_data (&stat_data))
- break;
- if (!S_ISDIR (stat_data.st_mode))
- {
- report_difference (_("No longer a directory"));
- break;
- }
- if ((stat_data.st_mode & 07777) != (current_stat.st_mode & 07777))
- report_difference (_("Mode differs"));
- break;
- case GNUTYPE_VOLHDR:
- break;
- case GNUTYPE_MULTIVOL:
- {
- off_t offset;
- name_length = strlen (current_file_name) - 1;
- if (current_file_name[name_length] == '/')
- goto really_dir;
- if (!get_stat_data (&stat_data))
- break;
- if (!S_ISREG (stat_data.st_mode))
- {
- report_difference (_("Not a regular file"));
- skip_file ((long) current_stat.st_size);
- break;
- }
- stat_data.st_mode &= 07777;
- offset = from_oct (1 + 12, current_header->oldgnu_header.offset);
- if (stat_data.st_size != current_stat.st_size + offset)
- {
- report_difference (_("Size differs"));
- skip_file ((long) current_stat.st_size);
- break;
- }
- diff_handle = open (current_file_name, O_NDELAY | O_RDONLY | O_BINARY);
- if (diff_handle < 0)
- {
- WARN ((0, errno, _("Cannot open file %s"), current_file_name));
- report_difference (NULL);
- skip_file ((long) current_stat.st_size);
- break;
- }
- status = lseek (diff_handle, offset, 0);
- if (status != offset)
- {
- WARN ((0, errno, _("Cannot seek to %ld in file %s"),
- offset, current_file_name));
- report_difference (NULL);
- break;
- }
- if (multi_volume_option)
- {
- assign_string (&save_name, current_file_name);
- save_totsize = stat_data.st_size;
- /* save_sizeleft is set in read_and_process. */
- }
- read_and_process ((long) (current_stat.st_size), process_rawdata);
- if (multi_volume_option)
- assign_string (&save_name, NULL);
- status = close (diff_handle);
- if (status < 0)
- ERROR ((0, errno, _("Error while closing %s"), current_file_name));
- break;
- }
- }
- }
- /*---.
- | ? |
- `---*/
- void
- verify_volume (void)
- {
- if (!diff_buffer)
- diff_init ();
- /* Verifying an archive is meant to check if the physical media got it
- correctly, so try to defeat clever in-memory buffering pertaining to
- this particular media. On Linux, for example, the floppy drive would
- not even be accessed for the whole verification.
- The code was using fsync only when the ioctl is unavailable, but
- Marty Leisner says that the ioctl does not work when not preceded by
- fsync. So, until we know better, or maybe to please Marty, let's do it
- the unbelievable way :-). */
- #if HAVE_FSYNC
- fsync (archive);
- #endif
- #ifdef FDFLUSH
- ioctl (archive, FDFLUSH);
- #endif
- #ifdef MTIOCTOP
- {
- struct mtop operation;
- int status;
- operation.mt_op = MTBSF;
- operation.mt_count = 1;
- if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), status < 0)
- {
- if (errno != EIO
- || (status = rmtioctl (archive, MTIOCTOP, (char *) &operation),
- status < 0))
- {
- #endif
- if (rmtlseek (archive, 0L, 0) != 0)
- {
- /* Lseek failed. Try a different method. */
- WARN ((0, errno,
- _("Could not rewind archive file for verify")));
- return;
- }
- #ifdef MTIOCTOP
- }
- }
- }
- #endif
- access_mode = ACCESS_READ;
- now_verifying = 1;
- flush_read ();
- while (1)
- {
- enum read_header status = read_header ();
- if (status == HEADER_FAILURE)
- {
- int counter = 0;
- while (status == HEADER_FAILURE);
- {
- counter++;
- status = read_header ();
- }
- ERROR ((0, 0,
- _("VERIFY FAILURE: %d invalid header(s) detected"), counter));
- }
- if (status == HEADER_ZERO_BLOCK || status == HEADER_END_OF_FILE)
- break;
- diff_archive ();
- }
- access_mode = ACCESS_WRITE;
- now_verifying = 0;
- }
|