freebsd-dev/gnu/usr.bin/tar/diffarch.c
Bruce Evans ec491764da Quick fix for attempts to free non-malloc()ed memory. The variables
current_file_name and current_link_name sometimes point into the
middle of malloc()ed memory and sometimes point to alloca()ed memory,
but free() is sometimes called on them.  This seems to be harmless
for the usual tar operations, but it is usually fatal for `tar -W'.
E.g., for `cd /etc; tar Wcf /tmp/foo rc', at the start of
verify_volume(), current_file_name points to alloca()ed memory, and
tar attempts to free it.
2001-02-18 01:06:13 +00:00

765 lines
16 KiB
C

/* Diff files from a tar archive.
Copyright (C) 1988, 1992, 1993 Free Software Foundation
This file is part of GNU Tar.
GNU Tar 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.
GNU Tar 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 GNU Tar; see the file COPYING. If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
/* $FreeBSD$ */
/*
* Diff files from a tar archive.
*
* Written 30 April 1987 by John Gilmore, ihnp4!hoptoad!gnu.
*/
#include <stdio.h>
#include <errno.h>
#ifndef STDC_HEADERS
extern int errno;
#endif
#include <sys/types.h>
#ifdef BSD42
#include <sys/file.h>
#else
#ifndef V7
#include <fcntl.h>
#endif
#endif
#ifdef HAVE_SYS_MTIO_H
#include <sys/ioctl.h>
#include <sys/mtio.h>
#endif
#include "tar.h"
#include "port.h"
#include "rmt.h"
#ifndef S_ISLNK
#define lstat stat
#endif
extern void *valloc ();
extern union record *head; /* Points to current tape header */
extern struct stat hstat; /* Stat struct corresponding */
extern int head_standard; /* Tape header is in ANSI format */
void decode_header ();
void diff_sparse_files ();
void fill_in_sparse_array ();
void fl_read ();
long from_oct ();
int do_stat ();
extern void print_header ();
int read_header ();
void saverec ();
void sigh ();
extern void skip_file ();
extern void skip_extended_headers ();
int wantbytes ();
extern FILE *msg_file;
int now_verifying = 0; /* Are we verifying at the moment? */
int diff_fd; /* Descriptor of file we're diffing */
char *diff_buf = 0; /* Pointer to area for reading
file contents into */
char *diff_dir; /* Directory contents for LF_DUMPDIR */
int different = 0;
/*struct sp_array *sparsearray;
int sp_ar_size = 10;*/
/*
* Initialize for a diff operation
*/
void
diff_init ()
{
/*NOSTRICT*/
diff_buf = (char *) valloc ((unsigned) blocksize);
if (!diff_buf)
{
msg ("could not allocate memory for diff buffer of %d bytes",
blocksize);
exit (EX_ARGSBAD);
}
}
/*
* Diff a file against the archive.
*/
void
diff_archive ()
{
register char *data;
int check, namelen;
int err;
long offset;
struct stat filestat;
int compare_chunk ();
int compare_dir ();
int no_op ();
#ifndef __MSDOS__
dev_t dev;
ino_t ino;
#endif
char *get_dir_contents ();
long from_oct ();
errno = EPIPE; /* FIXME, remove perrors */
saverec (&head); /* Make sure it sticks around */
userec (head); /* And go past it in the archive */
decode_header (head, &hstat, &head_standard, 1); /* Snarf fields */
/* Print the record from 'head' and 'hstat' */
if (f_verbose)
{
if (now_verifying)
fprintf (msg_file, "Verify ");
print_header ();
}
switch (head->header.linkflag)
{
default:
msg ("Unknown file type '%c' for %s, diffed as normal file",
head->header.linkflag, current_file_name);
/* FALL THRU */
case LF_OLDNORMAL:
case LF_NORMAL:
case LF_SPARSE:
case LF_CONTIG:
/*
* Appears to be a file.
* See if it's really a directory.
*/
namelen = strlen (current_file_name) - 1;
if (current_file_name[namelen] == '/')
goto really_dir;
if (do_stat (&filestat))
{
if (head->header.isextended)
skip_extended_headers ();
skip_file ((long) hstat.st_size);
different++;
goto quit;
}
if (!S_ISREG (filestat.st_mode))
{
fprintf (msg_file, "%s: not a regular file\n",
current_file_name);
skip_file ((long) hstat.st_size);
different++;
goto quit;
}
filestat.st_mode &= 07777;
if (filestat.st_mode != hstat.st_mode)
sigh ("mode");
if (filestat.st_uid != hstat.st_uid)
sigh ("uid");
if (filestat.st_gid != hstat.st_gid)
sigh ("gid");
if (filestat.st_mtime != hstat.st_mtime)
sigh ("mod time");
if (head->header.linkflag != LF_SPARSE &&
filestat.st_size != hstat.st_size)
{
sigh ("size");
skip_file ((long) hstat.st_size);
goto quit;
}
diff_fd = open (current_file_name, O_NDELAY | O_RDONLY | O_BINARY);
if (diff_fd < 0 && !f_absolute_paths)
{
char tmpbuf[NAMSIZ + 2];
tmpbuf[0] = '/';
strcpy (&tmpbuf[1], current_file_name);
diff_fd = open (tmpbuf, O_NDELAY | O_RDONLY);
}
if (diff_fd < 0)
{
msg_perror ("cannot open %s", current_file_name);
if (head->header.isextended)
skip_extended_headers ();
skip_file ((long) hstat.st_size);
different++;
goto quit;
}
/*
* Need to treat sparse files completely differently here.
*/
if (head->header.linkflag == LF_SPARSE)
diff_sparse_files (hstat.st_size);
else
wantbytes ((long) (hstat.st_size), compare_chunk);
check = close (diff_fd);
if (check < 0)
msg_perror ("Error while closing %s", current_file_name);
quit:
break;
#ifndef __MSDOS__
case LF_LINK:
if (do_stat (&filestat))
break;
dev = filestat.st_dev;
ino = filestat.st_ino;
err = stat (current_link_name, &filestat);
if (err < 0)
{
if (errno == ENOENT)
{
fprintf (msg_file, "%s: does not exist\n", current_file_name);
}
else
{
msg_perror ("cannot stat file %s", current_file_name);
}
different++;
break;
}
if (filestat.st_dev != dev || filestat.st_ino != ino)
{
fprintf (msg_file, "%s not linked to %s\n", current_file_name, current_link_name);
break;
}
break;
#endif
#ifdef S_ISLNK
case LF_SYMLINK:
{
char linkbuf[NAMSIZ + 3];
check = readlink (current_file_name, linkbuf,
(sizeof linkbuf) - 1);
if (check < 0)
{
if (errno == ENOENT)
{
fprintf (msg_file,
"%s: no such file or directory\n",
current_file_name);
}
else
{
msg_perror ("cannot read link %s", current_file_name);
}
different++;
break;
}
linkbuf[check] = '\0'; /* Null-terminate it */
if (strncmp (current_link_name, linkbuf, check) != 0)
{
fprintf (msg_file, "%s: symlink differs\n",
current_link_name);
different++;
}
}
break;
#endif
#ifdef S_IFCHR
case LF_CHR:
hstat.st_mode |= S_IFCHR;
goto check_node;
#endif
#ifdef S_IFBLK
/* If local system doesn't support block devices, use default case */
case LF_BLK:
hstat.st_mode |= S_IFBLK;
goto check_node;
#endif
#ifdef S_ISFIFO
/* If local system doesn't support FIFOs, use default case */
case LF_FIFO:
#ifdef S_IFIFO
hstat.st_mode |= S_IFIFO;
#endif
hstat.st_rdev = 0; /* FIXME, do we need this? */
goto check_node;
#endif
check_node:
/* FIXME, deal with umask */
if (do_stat (&filestat))
break;
if (hstat.st_rdev != filestat.st_rdev)
{
fprintf (msg_file, "%s: device numbers changed\n", current_file_name);
different++;
break;
}
#ifdef S_IFMT
if (hstat.st_mode != filestat.st_mode)
#else /* POSIX lossage */
if ((hstat.st_mode & 07777) != (filestat.st_mode & 07777))
#endif
{
fprintf (msg_file, "%s: mode or device-type changed\n", current_file_name);
different++;
break;
}
break;
case LF_DUMPDIR:
data = diff_dir = get_dir_contents (current_file_name, 0);
if (data)
{
wantbytes ((long) (hstat.st_size), compare_dir);
free (data);
}
else
wantbytes ((long) (hstat.st_size), no_op);
/* FALL THROUGH */
case LF_DIR:
/* Check for trailing / */
namelen = strlen (current_file_name) - 1;
really_dir:
while (namelen && current_file_name[namelen] == '/')
current_file_name[namelen--] = '\0'; /* Zap / */
if (do_stat (&filestat))
break;
if (!S_ISDIR (filestat.st_mode))
{
fprintf (msg_file, "%s is no longer a directory\n", current_file_name);
different++;
break;
}
if ((filestat.st_mode & 07777) != (hstat.st_mode & 07777))
sigh ("mode");
break;
case LF_VOLHDR:
break;
case LF_MULTIVOL:
namelen = strlen (current_file_name) - 1;
if (current_file_name[namelen] == '/')
goto really_dir;
if (do_stat (&filestat))
break;
if (!S_ISREG (filestat.st_mode))
{
fprintf (msg_file, "%s: not a regular file\n",
current_file_name);
skip_file ((long) hstat.st_size);
different++;
break;
}
filestat.st_mode &= 07777;
offset = from_oct (1 + 12, head->header.offset);
if (filestat.st_size != hstat.st_size + offset)
{
sigh ("size");
skip_file ((long) hstat.st_size);
different++;
break;
}
diff_fd = open (current_file_name, O_NDELAY | O_RDONLY | O_BINARY);
if (diff_fd < 0)
{
msg_perror ("cannot open file %s", current_file_name);
skip_file ((long) hstat.st_size);
different++;
break;
}
err = lseek (diff_fd, offset, 0);
if (err != offset)
{
msg_perror ("cannot seek to %ld in file %s", offset, current_file_name);
different++;
break;
}
wantbytes ((long) (hstat.st_size), compare_chunk);
check = close (diff_fd);
if (check < 0)
{
msg_perror ("Error while closing %s", current_file_name);
}
break;
}
/* We don't need to save it any longer. */
saverec ((union record **) 0);/* Unsave it */
}
int
compare_chunk (bytes, buffer)
long bytes;
char *buffer;
{
int err;
err = read (diff_fd, diff_buf, bytes);
if (err != bytes)
{
if (err < 0)
{
msg_perror ("can't read %s", current_file_name);
}
else
{
fprintf (msg_file, "%s: could only read %d of %ld bytes\n",
current_file_name, err, bytes);
}
different++;
return -1;
}
if (bcmp (buffer, diff_buf, bytes))
{
fprintf (msg_file, "%s: data differs\n", current_file_name);
different++;
return -1;
}
return 0;
}
int
compare_dir (bytes, buffer)
long bytes;
char *buffer;
{
if (bcmp (buffer, diff_dir, bytes))
{
fprintf (msg_file, "%s: data differs\n", current_file_name);
different++;
return -1;
}
diff_dir += bytes;
return 0;
}
/*
* Sigh about something that differs.
*/
void
sigh (what)
char *what;
{
fprintf (msg_file, "%s: %s differs\n",
current_file_name, what);
}
void
verify_volume ()
{
int status;
#ifdef MTIOCTOP
struct mtop t;
int er;
#endif
current_file_name = NULL;
current_link_name = NULL;
if (!diff_buf)
diff_init ();
#ifdef MTIOCTOP
t.mt_op = MTBSF;
t.mt_count = 1;
if ((er = rmtioctl (archive, MTIOCTOP, &t)) < 0)
{
if (errno != EIO || (er = rmtioctl (archive, MTIOCTOP, &t)) < 0)
{
#endif
if (rmtlseek (archive, 0L, 0) != 0)
{
/* Lseek failed. Try a different method */
msg_perror ("Couldn't rewind archive file for verify");
return;
}
#ifdef MTIOCTOP
}
}
#endif
ar_reading = 1;
now_verifying = 1;
fl_read ();
for (;;)
{
status = read_header ();
if (status == 0)
{
unsigned n;
n = 0;
do
{
n++;
status = read_header ();
}
while (status == 0);
msg ("VERIFY FAILURE: %d invalid header%s detected!", n, n == 1 ? "" : "s");
}
if (status == 2 || status == EOF)
break;
diff_archive ();
}
ar_reading = 0;
now_verifying = 0;
}
int
do_stat (statp)
struct stat *statp;
{
int err;
err = f_follow_links ? stat (current_file_name, statp) : lstat (current_file_name, statp);
if (err < 0)
{
if (errno == ENOENT)
{
fprintf (msg_file, "%s: does not exist\n", current_file_name);
}
else
msg_perror ("can't stat file %s", current_file_name);
/* skip_file((long)hstat.st_size);
different++;*/
return 1;
}
else
return 0;
}
/*
* 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.
*/
void
diff_sparse_files (filesize)
int filesize;
{
int sparse_ind = 0;
char *buf;
int buf_size = RECORDSIZE;
union record *datarec;
int err;
long numbytes;
/* int amt_read = 0;*/
int size = filesize;
buf = (char *) ck_malloc (buf_size * sizeof (char));
fill_in_sparse_array ();
while (size > 0)
{
datarec = findrec ();
if (!sparsearray[sparse_ind].numbytes)
break;
/*
* 'numbytes' is nicer to write than
* 'sparsearray[sparse_ind].numbytes' all the time ...
*/
numbytes = sparsearray[sparse_ind].numbytes;
lseek (diff_fd, sparsearray[sparse_ind].offset, 0);
/*
* take care to not run out of room in our buffer
*/
while (buf_size < numbytes)
{
buf = (char *) ck_realloc (buf, buf_size * 2 * sizeof (char));
buf_size *= 2;
}
while (numbytes > RECORDSIZE)
{
if ((err = read (diff_fd, buf, RECORDSIZE)) != RECORDSIZE)
{
if (err < 0)
msg_perror ("can't read %s", current_file_name);
else
fprintf (msg_file, "%s: could only read %d of %ld bytes\n",
current_file_name, err, numbytes);
break;
}
if (bcmp (buf, datarec->charptr, RECORDSIZE))
{
different++;
break;
}
numbytes -= err;
size -= err;
userec (datarec);
datarec = findrec ();
}
if ((err = read (diff_fd, buf, numbytes)) != numbytes)
{
if (err < 0)
msg_perror ("can't read %s", current_file_name);
else
fprintf (msg_file, "%s: could only read %d of %ld bytes\n",
current_file_name, err, numbytes);
break;
}
if (bcmp (buf, datarec->charptr, numbytes))
{
different++;
break;
}
/* amt_read += numbytes;
if (amt_read >= RECORDSIZE) {
amt_read = 0;
userec(datarec);
datarec = findrec();
}*/
userec (datarec);
sparse_ind++;
size -= numbytes;
}
/*
* if the number of bytes read isn't the
* number of bytes supposedly in the file,
* they're different
*/
/* if (amt_read != filesize)
different++;*/
userec (datarec);
free (sparsearray);
if (different)
fprintf (msg_file, "%s: data differs\n", current_file_name);
}
/*
* 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.
*/
void
fill_in_sparse_array ()
{
int ind;
/*
* 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 *) ck_malloc (sp_array_size * sizeof (struct sp_array));
/*
* there are at most five of these structures in the header
* itself; read these in first
*/
for (ind = 0; ind < SPARSE_IN_HDR; ind++)
{
if (!head->header.sp[ind].numbytes)
break;
sparsearray[ind].offset =
from_oct (1 + 12, head->header.sp[ind].offset);
sparsearray[ind].numbytes =
from_oct (1 + 12, head->header.sp[ind].numbytes);
}
/*
* if the header's extended, we gotta read in exhdr's till
* we're done
*/
if (head->header.isextended)
{
/* how far into the sparsearray we are 'so far' */
static int so_far_ind = SPARSE_IN_HDR;
union record *exhdr;
for (;;)
{
exhdr = findrec ();
for (ind = 0; ind < SPARSE_EXT_HDR; ind++)
{
if (ind + so_far_ind > sp_array_size - 1)
{
/*
* we just ran out of room in our
* scratch area - realloc it
*/
sparsearray = (struct sp_array *)
ck_realloc (sparsearray,
sp_array_size * 2 * sizeof (struct sp_array));
sp_array_size *= 2;
}
/*
* convert the character strings into longs
*/
sparsearray[ind + so_far_ind].offset =
from_oct (1 + 12, exhdr->ext_hdr.sp[ind].offset);
sparsearray[ind + so_far_ind].numbytes =
from_oct (1 + 12, exhdr->ext_hdr.sp[ind].numbytes);
}
/*
* if this is the last extended header for this
* file, we can stop
*/
if (!exhdr->ext_hdr.isextended)
break;
else
{
so_far_ind += SPARSE_EXT_HDR;
userec (exhdr);
}
}
/* be sure to skip past the last one */
userec (exhdr);
}
}