From: François Pinard Date: Wed, 16 Nov 1994 02:42:05 +0000 (+0000) Subject: Initial revision X-Git-Url: https://git.dogcows.com/gitweb?a=commitdiff_plain;h=8f297653455cc601a6480476f1570eccc16ba3d4;p=chaz%2Ftar Initial revision --- diff --git a/src/diffarch.c b/src/diffarch.c new file mode 100644 index 0000000..de05ae6 --- /dev/null +++ b/src/diffarch.c @@ -0,0 +1,702 @@ +/* Diff files from a tar archive. + Copyright (C) 1988 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. */ + +/* + * Diff files from a tar archive. + * + * Written 30 April 1987 by John Gilmore, ihnp4!hoptoad!gnu. + */ + +#include +#include +#ifndef STDC_HEADERS +extern int errno; +#endif +#include + +#ifdef BSD42 +#include +#else +#ifndef V7 +#include +#endif +#endif + +#ifndef NO_MTIO +#include +#include +#endif + +#include "tar.h" +#include "port.h" +#include "rmt.h" + +#ifndef S_ISLNK +#define lstat stat +#endif + +extern char *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, head->header.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(head->header.name)-1; + if (head->header.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", + head->header.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(head->header.name, O_NDELAY|O_RDONLY|O_BINARY); + + if (diff_fd < 0 && !f_absolute_paths) { + char tmpbuf[NAMSIZ+2]; + + tmpbuf[0]='/'; + strcpy(&tmpbuf[1],head->header.name); + diff_fd=open(tmpbuf, O_NDELAY|O_RDONLY); + } + if (diff_fd < 0) { + msg_perror("cannot open %s",head->header.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",head->header.name); + + quit: + break; + +#ifndef __MSDOS__ + case LF_LINK: + if(do_stat(&filestat)) + break; + dev = filestat.st_dev; + ino = filestat.st_ino; + err = stat(head->header.linkname, &filestat); + if (err < 0) { + if (errno==ENOENT) { + fprintf(msg_file, "%s: does not exist\n",head->header.name); + } else { + msg_perror("cannot stat file %s",head->header.name); + } + different++; + break; + } + if(filestat.st_dev!=dev || filestat.st_ino!=ino) { + fprintf(msg_file, "%s not linked to %s\n",head->header.name,head->header.linkname); + break; + } + break; +#endif + +#ifdef S_ISLNK + case LF_SYMLINK: + { + char linkbuf[NAMSIZ+3]; + check = readlink(head->header.name, linkbuf, + (sizeof linkbuf)-1); + + if (check < 0) { + if (errno == ENOENT) { + fprintf(msg_file, + "%s: no such file or directory\n", + head->header.name); + } else { + msg_perror("cannot read link %s",head->header.name); + } + different++; + break; + } + + linkbuf[check] = '\0'; /* Null-terminate it */ + if (strncmp(head->header.linkname, linkbuf, check) != 0) { + fprintf(msg_file, "%s: symlink differs\n", + head->header.linkname); + 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", head->header.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", head->header.name); + different++; + break; + } + break; + + case LF_DUMPDIR: + data=diff_dir=get_dir_contents(head->header.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(head->header.name)-1; + really_dir: + while (namelen && head->header.name[namelen] == '/') + head->header.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",head->header.name); + different++; + break; + } + if((filestat.st_mode&07777) != (hstat.st_mode&07777)) + sigh("mode"); + break; + + case LF_VOLHDR: + break; + + case LF_MULTIVOL: + namelen = strlen(head->header.name)-1; + if (head->header.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", + head->header.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(head->header.name, O_NDELAY|O_RDONLY|O_BINARY); + + if (diff_fd < 0) { + msg_perror("cannot open file %s",head->header.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,head->header.name); + different++; + break; + } + + wantbytes((long)(hstat.st_size),compare_chunk); + + check = close(diff_fd); + if (check < 0) { + msg_perror("Error while closing %s",head->header.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",head->header.name); + } else { + fprintf(msg_file,"%s: could only read %d of %d bytes\n",head->header.name,err,bytes); + } + different++; + return -1; + } + if(bcmp(buffer,diff_buf,bytes)) { + fprintf(msg_file, "%s: data differs\n",head->header.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",head->header.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", + head->header.name, what); +} + +void +verify_volume() +{ + int status; +#ifdef MTIOCTOP + struct mtop t; + int er; +#endif + + 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(head->header.name, statp) : lstat(head->header.name, statp); + if (err < 0) { + if (errno==ENOENT) { + fprintf(msg_file, "%s: does not exist\n",head->header.name); + } else + msg_perror("can't stat file %s",head->header.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 *) 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 *) 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", head->header.name); + else + fprintf(msg_file, "%s: could only read %d of %d bytes\n", + 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", head->header.name); + else + fprintf(msg_file, "%s: could only read %d of %d bytes\n", + 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", head->header.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 *) 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 *) + 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); + } +}