[alt.sources] cktar - compare tar tape against file system

wht@n4hgf.Mt-Park.GA.US (Warren Tucker) (11/09/90)

Submitted-by: wht@n4hgf
Archive-name: cktar/part01

This program reads a tar input file and compares each
file on the volume with a matching file on disk.

If a file on the tar file has the same length as a file on the
disk, the files are compared byte for byte and the byte position
of the first nonmatching byte is printed.

If a file on the tar file has a different length from a file on
the disk, this fact is noted.

If the -U switch is present, UIDs must match.  If the -G switch is
present, gids must match.  If the -M switch is present, times of last
modification must match.

With -t or -v switch, no output is produced for matching files.
With -t, a title is printed for each file regardless of match.
With -v, this output is expanded to show all attributes of the file
on the tar file as well as the match status.

If you wish to have cktar read from stdin and do not wish to use
any of the available switches, use the '--' switch.

The -A switch acts like the tar A key, causing a leading '/' in
a tar input filename to be elided.

Exceptions are reported by codes appearing after filenames:
   A - stat access denied
   N - non-existent file
   U - uid difference
   G - gid difference
   T - time of last modification difference
   S - size difference
   C - comparison failed at position shown by decimal value after C
The 'S' and 'C' codes will never appear together, 'A' and 'N' will
never appear together or with any other codes, but any other mixture
of codes is possible.  Only the C code has a decimal file position
appended.

#!/bin/sh
# This is cktar, a shell archive (shar 3.46)
# made 11/09/1990 09:44 UTC by wht@n4hgf
# Source directory /u1/src/src
#
# existing files will NOT be overwritten unless -c is specified
#
# This shar contains:
# length  mode       name
# ------ ---------- ------------------------------------------
#  14450 -rw-r--r-- cktar.c
#
# ============= cktar.c ==============
if test -f 'cktar.c' -a X"$1" != X"-c"; then
	echo 'x - skipping cktar.c (File already exists)'
else
echo 'x - extracting cktar.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'cktar.c' &&
X/* CHK=0xC1F1 */
X/*+-------------------------------------------------------------------------
X	cktar.c - read a tar tape and compare against file system
X	wht@n4hgf.Mt-Park.GA.US
X--------------------------------------------------------------------------*/
X/*+:EDITS:*/
X/*:11-08-1990-16:34-wht-creation */
X
X#include <stdio.h>
X#include <ctype.h>
X#ifdef BSD
X#include <sys/errno.h>
X#include <sys/time.h>
X#define memcmp(s1,s2,c) bcmp(s2,s1,c)
X#else
X#include <errno.h>
X#include <time.h>
X#include <memory.h>
X#endif
X#include <sys/types.h>
X#include <sys/stat.h>
X
X#if defined(min)
X#undef min
X#endif
X#define min(a,b) ((a < b) ? a : b)
X
Xextern int errno;
X
X#define TBLOCK 512
X#define NBLOCK 20
X#define NAMSIZ 100
Xtypedef union tar_block
X{
X	char d[TBLOCK];					/* data */
X	struct header					/* header */
X	{
X		char name[NAMSIZ];
X		char mode[8];
X		char uid[8];
X		char gid[8];
X		char size[12];
X		char mtime[12];
X		char chksum[8];
X		char linkflag;
X		char linkname[NAMSIZ];
X		char extno[4];
X		char extotal[4];
X		char efsize[12];
X	} h;
X} TARBLOCK;
X
Xchar *tar_fname = "(stdin)";
XFILE *fptar;
Xint Tsw = 0;
Xint Vsw = 0;
Xint Asw = 0;
Xint Usw = 0;
Xint Gsw = 0;
Xint Msw = 0;
Xchar _tarbuf[TBLOCK * NBLOCK];
Xlong now;
X
Xchar *usage_text[] =
X{
X"usage: cktar [-t] [-v] [-A] [-U] [-G] [-M] [-f tar_input_file] [--]",
X"This program reads a tar input file and compares each",
X"file on the volume with a matching file on disk.",
X"",
X"If a file on the tar file has the same length as a file on the",
X"disk, the files are compared byte for byte and the byte position",
X"of the first nonmatching byte is printed.",
X"",
X"If a file on the tar file has a different length from a file on",
X"the disk, this fact is noted.",
X"",
X"If the -U switch is present, UIDs must match.  If the -G switch is",
X"present, gids must match.  If the -M switch is present, times of last",
X"modification must match.",
X"",
X"With -t or -v switch, no output is produced for matching files.",
X"With -t, a title is printed for each file regardless of match.",
X"With -v, this output is expanded to show all attributes of the file",
X"on the tar file as well as the match status.",
X"",
X"If you wish to have cktar read from stdin and do not wish to use",
X"any of the available switches, use the '--' switch.",
X"",
X"The -A switch acts like the tar A key, causing a leading '/' in",
X"a tar input filename to be elided.",
X"",
X"Exceptions are reported by codes appearing after filenames:",
X"   A - stat access denied",
X"   N - non-existent file",
X"   U - uid difference",
X"   G - gid difference",
X"   T - time of last modification difference",
X"   S - size difference",
X"   C - comparison failed at position shown by decimal value after C",
X"The 'S' and 'C' codes will never appear together, 'A' and 'N' will",
X"never appear together or with any other codes, but any other mixture",
X"of codes is possible.  Only the C code has a decimal file position",
X"appended.",
X"",
X(char *)0
X};
X
X/*+-------------------------------------------------------------------------
X	usage()
X--------------------------------------------------------------------------*/
Xvoid
Xusage()
X{
Xchar **t = usage_text;
X
X	while(*t)
X	{
X		fputs(*t,stderr);
X		fputs("\n",stderr);
X		t++;
X	}
X	exit(1);
X}	/* end of usage */
X
X/*+-----------------------------------------------------------------------
X	mode_map(mode) - build drwxrwxrwx string
X------------------------------------------------------------------------*/
Xchar *
Xmode_map(mode,mode_str)
Xunsigned short mode;
X{
Xregister unsigned ftype = mode & S_IFMT;
Xstatic char result[12];
Xregister char *rtn = result;
X
X	/*          drwxrwxrwx */
X	/*          0123456789 */
X	strcpy(rtn,"----------");
X
X	switch(ftype)
X	{
X		case S_IFIFO:	*rtn = 'p'; break; /* FIFO (named pipe) */
X		case S_IFDIR:	*rtn = 'd'; break; /* directory */
X		case S_IFCHR:	*rtn = 'c'; break; /* character special */
X		case S_IFBLK:	*rtn = 'b'; break; /* block special */
X		case 0:
X		case S_IFREG:	*rtn = '-'; break; /* regular */
X
X#if defined(BSD)
X		case S_IFLNK:	*rtn = 'l'; break; /* symbolic link */
X		case S_IFSOCK:	*rtn = 's'; break; /* socket */
X#endif
X#if defined (M_XENIX) || defined(M_UNIX)
X		case S_IFNAM:						/* name space entry */
X			if(mode & S_INSEM)				/* semaphore */
X			{
X				*rtn = 's';
X				break;
X			}
X			if(mode & S_INSHD)				/* shared memory */
X			{
X				*rtn = 'm';
X				break;
X			}
X#endif
X
X		default:		*rtn = '?'; break;	/* ??? */
X	}
X
X	if(mode & 000400) *(rtn + 1) = 'r';
X	if(mode & 000200) *(rtn + 2) = 'w';
X	if(mode & 000100) *(rtn + 3) = 'x';
X	if(mode & 004000) *(rtn + 3) = 's';
X	if(mode & 000040) *(rtn + 4) = 'r';
X	if(mode & 000020) *(rtn + 5) = 'w';
X	if(mode & 000010) *(rtn + 6) = 'x';
X	if(mode & 002000) *(rtn + 6) = 's';
X	if(mode & 000004) *(rtn + 7) = 'r';
X	if(mode & 000002) *(rtn + 8) = 'w';
X	if(mode & 000001) *(rtn + 9) = 'x';
X	if(mode & 001000) *(rtn + 9) = 't';
X
X	return(rtn);
X
X}	/* end of mode_map */
X
X/*+-----------------------------------------------------------------------
X	char *epoch_secs_to_str(epoch_secs)
X------------------------------------------------------------------------*/
Xchar *
Xepoch_secs_to_str(epoch_secs)
Xlong epoch_secs;
X{
Xstatic char buf[64];
Xstruct tm *tod;
Xchar *month_name_list = "JanFebMarAprMayJunJulAugSepOctNovDec";
X
X	tod = localtime(&epoch_secs);
X
X	if((now - epoch_secs) > (86400L * 365/2))	/* if older than 6 months */
X	{
X		sprintf(buf,"%02d %-3.3s %04d ",
X			tod->tm_mday,month_name_list + (tod->tm_mon * 3),
X			tod->tm_year + 1900);
X	}
X	else
X	{
X		sprintf(buf,"%02d %-3.3s %02d:%02d",
X			tod->tm_mday,month_name_list + (tod->tm_mon * 3),
X			tod->tm_hour,tod->tm_min);
X	}
X
X	return(buf);
X}	/* end of epoch_secs_to_str */
X
X/*+-------------------------------------------------------------------------
X	report_tarblock(t)
X--------------------------------------------------------------------------*/
Xvoid
Xreport_tarblock(t)
Xregister TARBLOCK *t;
X{
Xlong tmode;
Xlong tsize;
Xlong tuid;
Xlong tgid;
Xlong tmtime;
X
X	if(Vsw)
X	{
X		sscanf(t->h.size,"%lo",&tsize);
X		sscanf(t->h.mode,"%lo",&tmode);
X		sscanf(t->h.uid,"%lo",&tuid);
X		sscanf(t->h.gid,"%lo",&tgid);
X		sscanf(t->h.mtime,"%lo",&tmtime);
X
X		printf("%s ",mode_map((unsigned short)tmode));
X		printf("%4d/%04d ",(unsigned short)tuid,
X			(unsigned short)tgid);
X		printf("%8ld ",tsize);
X		printf("%s ",epoch_secs_to_str(tmtime));
X	}
X	printf("%s ",t->h.name + Asw);
X
X}	/* end of report_tarblock */
X
X/*+-------------------------------------------------------------------------
X	cktar(fpt,t)
X--------------------------------------------------------------------------*/
Xvoid
Xcktar(fpt,t)
XFILE *fpt;
Xregister TARBLOCK *t;
X{
Xchar tb[TBLOCK];	/* tar file data block */
Xlong tmode;			/* tar file mode (as in st_mode) */
Xlong tsize;			/* tar file version of file size */
Xlong tuid;			/* tar file version of uid */
Xlong tgid;			/* tar file version of gid */
Xlong tmtime;		/* tar file version of last mod time */
Xchar db[TBLOCK];	/* disk file data block */
XFILE *fpd = (FILE *)0;
Xchar *dname;		/* ptr to file name with -A (Asw) considerations */
Xstruct stat dstat;	/* disk file stat() result */
Xint report = 0;		/* true if report_tarblock() needed */
Xint do_compare = 1;	/* true if we should continue to compare tape to disk */
Xint len;			/* really a scratch int, often length of read() */
Xlong blks;			/* tape blocks for this file, decremented as we go */
Xlong fpos = 0;		/* file position */
Xint thiscount;		/* TBLOCK or residue (partial block size) */
Xchar *tbp;			/* hack pointer to tape buf for final mismatch calc */
Xchar *dbp;			/* hack pointer to disk buf for final mismatch calc */
Xchar exceptions[32]; /* accumulator for comparison failures */
X
X	exceptions[0] = 0;
X
X	/*
X	 * if the tar "file" is a link and -t, say so
X	 */
X	if(t->h.linkflag)
X	{
X		if(Tsw)
X		{
X			report_tarblock(t);
X			fputs("link->",stdout);
X			fputs(t->h.linkname,stdout);
X		}
X		return;
X	}
X
X#ifdef BSD
X	sscanf(t->h.mode,"%lo",&tmode);
X	if((t->h.mode & S_IFMT) && ((t->h.mode & S_IFMT) != S_IFREG)
X	{
X		if(Tsw)
X		{
X			report_tarblock(t);
X			fputs("not regular file\n",stdout);
X		}
X		return;
X	}
X#endif
X
X	/*
X	 * get the dope on the tar file for gross level comparison
X	 */
X	sscanf(t->h.size,"%lo",&tsize);
X	sscanf(t->h.uid,"%lo",&tuid);
X	sscanf(t->h.gid,"%lo",&tgid);
X	sscanf(t->h.mtime,"%lo",&tmtime);
X
X	/*
X	 * figger out how many data blocks for this file
X	 */
X	blks = tsize / TBLOCK;
X	if(tsize - (blks * TBLOCK))
X		blks++;
X
X	/*
X	 * get file name with consideration for a possible -A switch
X	 * then, stat() the file; if we cannot stat the file, mark
X	 * it in the exceptions string and then do little else
X	 * other than space over the tape file
X	 */
X	dname = t->h.name + Asw;
X	if(stat(dname,&dstat))
X	{
X		if(errno = EACCES)
X			strcat(exceptions,"A");	/* stat access denied */
X		else
X			strcat(exceptions,"N");	/* non-existent file */
X		report = 1;
X		do_compare = 0;
X	}
X
X	/*
X	 * perform uid, gid and mtime comparisons; even if any
X	 * or all of them fail, we'll still do a file data comparison
X	 * provided the tape and data files have the same length
X	 */
X	if(do_compare && Usw && (dstat.st_uid != (unsigned short)tsize))
X	{
X		strcat(exceptions,"U");	/* uid difference */
X		report = 1;
X	}
X	if(do_compare && Gsw && (dstat.st_gid != (unsigned short)tsize))
X	{
X		strcat(exceptions,"G");	/* gid difference */
X		report = 1;
X	}
X	if(do_compare && Msw && (dstat.st_mtime != tmtime))
X	{
X		strcat(exceptions,"T");	/* time of last modification different */
X		report = 1;
X	}
X
X	/*
X	 * if tape and disk files don't have the same length,
X	 * omit data comparison
X	 */
X	if(do_compare && (dstat.st_size != tsize))
X	{
X		strcat(exceptions,"S");	/* size difference */
X		report = 1;
X		do_compare = 0;
X	}
X
X	/*
X	 * if we can't open the disk file, data comparison unlikely
X	 */
X	if(do_compare && (access(dname,4) || !(fpd = fopen(dname,"r"))))
X	{
X		strcat(exceptions,"R");	/* read access denied */
X		report = 1;
X		do_compare = 0;
X	}
X
X	/*
X	 * tape advance and possible data comparison loop
X	 */
X	while(blks)
X	{
X		if((len = fread(tb,1,sizeof(tb),fpt)) != sizeof(tb))
X		{
X			if(len < 0)
X				perror(tar_fname);
X			else
X				fprintf(stderr,"%s: short read expecting data (%d bytes)\n",
X					tar_fname,len);
X			exit(1);
X		}
X		if(do_compare)
X		{
X			thiscount = min(TBLOCK,(int)(tsize - fpos));
X			if((len = fread(db,1,thiscount,fpd)) != thiscount)
X			{
X				if(len < 0)
X					perror(dname);
X				else
X				{
X					fprintf(stderr,
X						"%s: short read expecting %d bytes, got %d\n",
X						dname,thiscount,len);
X				}
X				exit(1);
X			}
X			/*
X			 * compare full tape block with full disk block or
X			 * valid portion of tape block with the last 1-511 bytes
X			 * of a disk file
X			 *
X			 * first do the (supposedly) fastest compare available
X			 * and if the compare fails, plod through slowly
X			 * to calculate the proper file position where the
X			 * compare failed
X			 */
X			if(memcmp(tb,db,thiscount))
X			{
X				len = 0;
X				tbp = tb;
X				dbp = db;
Xprintf(">>thiscount=%d\n",thiscount);
Xprintf("*tb=%02x *db=%02x\n",*tbp,*dbp);
X				while(len < thiscount)
X				{
X					if(*tbp != *dbp)
X						break;
X					len++,tbp++,dbp++;
X				}
X				sprintf(tb,"C %ld",fpos + len); /* use tape buf for scratch */
X				strcat(exceptions,tb); /* byte compare failure position */
X				report = 1;
X				do_compare = 0;
X			}
X		}					/* end of do_compare==TRUE tape<->disk comparison */
X		fpos += TBLOCK;
X		blks--;	/* leaving this at bottom in case we ever want current
X				 * block number in the loop
X				 */
X	}						/* end of while(blks) loop */
X
X	if(fpd)
X		fclose(fpd);
X
X	if(Tsw || report)
X	{
X		report_tarblock(t);
X		if(report)
X			fputs(exceptions,stdout);
X		fputs("\n",stdout);
X	}
X
X}	/* end of cktar */
X
X/*+-------------------------------------------------------------------------
X	getopt(argc,argv,opts) - Thank you, Henry!
X--------------------------------------------------------------------------*/
X#ifdef BSD
X#define strchr index	/* for BSD */
Xextern int strcmp();
Xextern char *strchr();
X
X#define NULL	(char *)0
X#define EOF	(-1)
X#define ERR(s,c)	if(opterr)\
X{ \
X	extern int strlen(),write(); \
X	char errbuf[2]; \
X	errbuf[0] = c; errbuf[1] = '\n'; \
X	(void) write(2,argv[0],(unsigned)strlen(argv[0])); \
X	(void) write(2,s,(unsigned)strlen(s)); \
X	(void) write(2,errbuf,2); \
X}
Xint opterr = 1;
Xint optind = 1;
Xint optopt;
Xchar *optarg;
Xint
Xgetopt(argc,argv,opts)
Xint argc;
Xchar **argv,*opts;
X{
X	static int sp = 1;
X	register int c;
X	register char *cp;
X
X	if(sp == 1)
X	{
X		if(optind >= argc || argv[optind][0] != '-' || argv[optind][1] == '\0')
X			return(EOF);
X		else if(!strcmp(argv[optind],"--"))
X		{
X			optind++;
X			return(EOF);
X		}
X	}
X	optopt = c = argv[optind][sp];
X	if(c == ':' || (cp=strchr(opts,c)) == NULL)
X	{
X		ERR(": unknown option, -",c);
X		if(argv[optind][++sp] == '\0')
X		{
X			optind++;
X			sp = 1;
X		}
X		return('?');
X	}
X	if(*++cp == ':')
X	{
X		if(argv[optind][sp+1] != '\0')
X			optarg = &argv[optind++][sp+1];
X		else if(++optind >= argc)
X		{
X			ERR(": argument missing for -",c);
X			sp = 1;
X			return('?');
X		}
X		else
X			optarg = argv[optind++];
X		sp = 1;
X	}
X	else 
X	{
X		if(argv[optind][++sp] == '\0')
X		{
X			sp = 1;
X			optind++;
X		}
X		optarg = NULL;
X	}
X	return(c);
X}	/* end of getopt */
X#endif /* BSD */
X
X/*+-------------------------------------------------------------------------
X	main(argc,argv)
X--------------------------------------------------------------------------*/
Xmain(argc,argv)
Xint argc;
Xchar **argv;
X{
Xint iargv;
Xint itmp;
Xint errflg = 0;
Xint rdlen;
XTARBLOCK tblk;
Xextern char *optarg;
Xextern int optind;
X
X	if(argc == 1)
X		usage();
X
X	time(&now);
X
X	fptar = stdin;
X	setvbuf(fptar,_tarbuf,_IOFBF,sizeof(_tarbuf));
X
X	while((itmp = getopt(argc,argv,"tvAUGMf:")) != -1)
X	{
X		switch(itmp)
X		{
X			case 't':
X				Tsw = 1;
X				break;
X			case 'v':
X				Vsw = 1;
X				break;
X			case 'A':
X				Asw = 1;
X				break;
X			case 'U':
X				Usw = 1;
X				break;
X			case 'G':
X				Gsw = 1;
X				break;
X			case 'M':
X				Msw = 1;
X				break;
X			case 'f':
X				tar_fname = optarg;
X				if(fptar = fopen(tar_fname,"r"))
X					setvbuf(fptar,_tarbuf,_IOFBF,sizeof(_tarbuf));
X				break;
X			case '?':
X				errflg++;
X		}
X	}
X
X	if(!fptar)
X	{
X		perror((tar_fname && *tar_fname) ? tar_fname : "(null)");
X		exit(1);
X	}
X
X	if(errflg)
X		usage();
X
X	while((rdlen = fread((char *)&tblk,1,sizeof(tblk),fptar)) == sizeof(tblk))
X	{
X		if(!tblk.h.name[0])
X		{
X			rdlen = 0;
X			break;
X		}
X		cktar(fptar,&tblk);
X	}
X
X	if(rdlen)
X	{
X		if(rdlen < 0)
X			perror(tar_fname);
X		else
X		{
X			fprintf(stderr,"%s: short read expecting header (%d bytes)\n",
X				tar_fname,rdlen);
X		}
X	}
X
X	exit(0);
X
X}	/* end of main */
X
X/* vi: set tabstop=4 shiftwidth=4: */
X/* end of cktar.c */
SHAR_EOF
chmod 0644 cktar.c ||
echo 'restore of cktar.c failed'
Wc_c="`wc -c < 'cktar.c'`"
test 14451 -eq "$Wc_c" ||
	echo 'cktar.c: original size 14451, current size' "$Wc_c"
fi
exit 0
 
----------------------------------------------------------------------------
Warren Tucker                     emory!n4hgf!wht or wht@n4hgf.Mt-Park.GA.US
"I was 35 years old before I knew a pie was meant to be eaten." - Moe Howard