DataMuseum.dk

Presents historical artifacts from the history of:

Commodore CBM-900

This is an automatic "excavation" of a thematic subset of
artifacts from Datamuseum.dk's BitArchive.

See our Wiki for more about Commodore CBM-900

Excavated with: AutoArchaeologist - Free & Open Source Software.


top - download

⟦9adf6b938⟧ TextFile

    Length: 20180 (0x4ed4)
    Types: TextFile
    Notes: UNIX file
    Names: »cpdir.c«

Derivation

└─⟦f27320a65⟧ Bits:30001972 Commodore 900 hard disk image with partial source code
    └─⟦f4b8d8c84⟧ UNIX V7 Filesystem
        └─ ⟦this⟧ »cmd/cpdir.c« 

TextFile

/*
 * Cpdir. Copy hierarchies in a file system, preserving structure.
 *	  Define SLOW for 'block at a time copying' - not recommended tho.
 */
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <types.h>
#include <stat.h>
#include <dir.h>
#include <ctype.h>
#include <access.h>
#include <canon.h>

#define	CPBUFSIZ	(50*BUFSIZ)			/* copy buffer */
#define	SDSIZ		(sizeof(struct direct))
#define	MAXINT		32767
#define	SOURCE		0
#define	TARGET		1
#define	DEV0		((dev_t)0)
#define	INODE0		((ino_t)0)
#define	ROOTUID		0
#define	HASHSIZE	37

#define	hash(ino)	((ino)%HASHSIZE)

#define	not		!
#define	and		&&
#define	or		||
#define	TRUE		(0==0)
#define	FALSE		(not TRUE)

typedef	char		bool;
typedef	unsigned char	uchar;
typedef	unsigned int	uint;
typedef	unsigned short	ushort;
typedef	unsigned long	ulong;

typedef union {
	struct	direct	dbuf;
	char		cbuf[SDSIZ + 1];
} DIRBUF;
#define	db_ino		dbuf.d_ino
#define	db_name		dbuf.d_name

typedef struct link {
	ino_t		l_ino;
	dev_t		l_dev;
	dev_t		l_tdev;
	struct link	*l_next;
	uint		l_nlink;
	char		l_name[];
} LINK;

typedef struct str {
	struct str	*s_next;
	char		s_str[];
} STR;



/*
 * Global variables.
 */
extern	int	errno;
dev_t		dir2_dev;
ino_t		dir2_ino;
dev_t		src_dev;
dev_t		tgt_dev;
struct stat	srcstat;
struct stat	tgtstat;
LINK		*srctab[HASHSIZE];
LINK		*tgttab[HASHSIZE];
STR		*strtab[HASHSIZE];

bool	dflag;
bool	eflag;
bool	sflag;
bool	tflag;
bool	uflag;
bool	vflag;
bool	wflag;
bool	root;
bool	splitmsg;
bool	errprefix;
bool	dir1slash;
bool	dir2slash;

int	rlimit = MAXINT;
int	rlevel = 1;
int	exitval;
int	numsuppress;
int	broken;
int	srclinks;
int	tgtlinks;
int	srcsize;
int	tgtsize;
int	dir1len;
int	dir2len;

short	uid;
short	gid;

char	*dir1;
char	*dir2;
char	*target;
char	*source;

/*
 * Error message and other strings.
 */
char	*dot =		".";
char	*dotdot =	"..";
char	*fmt1 =		"%s\n";
char	*fmt2 =		"%s: %s\n";
char	*fmt3 =		"%s: %s%s\n";
char	*fmt4 =		"%s: %s%s%s\n";
char	*usage =	"usage: cpdir [options] dir1 dir2";
char	*devbound =	"crossing device boundary";
char	*writeerr =	"write error";
char	*readerr =	"read error";
char	*supressd =	"suppressed";
char	*linked =	"linked";
char	*notdir =	"not a directory";
char	*nomemory =	"out of memory";
char	*nopermit =	"permission denied";
char	*nomkdir =	"cannot make directory";
char	*nounlink =	"cannot unlink ";
char	*nolink =	"cannot link ";
char	*nocreate =	"cannot create";
char	*nofind =	"cannot find";
char	*noopen =	"cannot open";

/*
 * Functions returning non_int.
 */
long	lseek();
char	*tmalloc();
char	*realloc();
char	*rindex();
char	*concat();
char	*parent();
bool	suppress();
bool	isslash();
bool	dirchecks();
bool	linkattempt();
bool	tgtunlink();
LINK	*linklocate();
LINK	*linkinstall();




main(ac, av)
int ac;
char *av[];
{
	ac = 0;
	aarghh(av);
	init();
	cpdir();
	report();
	return (exitval);
}

aarghh(av)
register char *av[];
{
	register char *cp;

	for (;;) {
		if ((cp = *++av) == NULL) {
			errprint(fmt1, usage);
			exit(1);
		}
		if (*cp++ != '-')
			break;
		while (*cp)
			switch (*cp++) {
			case 'd':
				dflag = TRUE;
				break;
			case 'e':
				eflag = TRUE;
				break;
			case 'r':
				if (not isdigit(*cp)) {
					rlimit = 1;
					break;
				}
				*av = cp;
				rlimit = new_atoi(av);
				cp = *av;
				break;
			case 's':
				sflag = TRUE;
				strinstall(cp);
				++numsuppress;
				goto NEXTARG;
				break;
			case 't':
				tflag = wflag = eflag = TRUE;
				break;
			case 'u':
				uflag = TRUE;
				break;
			case 'v':
				vflag = wflag = TRUE;
				break;
			default:
				errprint(fmt1, usage);
				exit(1);
			}
		NEXTARG:;
	}

	if ((dir1=*av++) == NULL  or  (dir2=*av++) == NULL  or  *av != NULL) {
		errprint(fmt1, usage);
		exit(1);
	}
	return;
}

/*
 * Actions: Initialize certain flags and the name buffers source and target.
 * Verify dir1 and dir2 permissions. Dir2 is made if necessary.
 * Side Effects: On return srcstat contains the status of dir1. If dir2
 * exists tgtstat contains its status. In the case that tflag is set and dir2
 * does not exist, tgt_dev and tgtstat.st_dev are the device of target's
 * parent, the device that target would be created on except for tflag.
 */
init()
{
	struct stat outstat;
	struct stat errstat;

	uid = getuid();
	gid = getgid();
	root = (uid == ROOTUID);
	if (root)
		umask(0);

	errprefix = not isatty(fileno(stdin));
	fstat(fileno(stdout), &outstat);
	fstat(fileno(stderr), &errstat);
	splitmsg = (outstat.st_ino != errstat.st_ino)
		or (outstat.st_dev != errstat.st_dev);

	dir1len = strlen(dir1);
	srcsize = (dir1len + 1);
	source = tmalloc(srcsize);
	strcpy(source, dir1);
	dir1slash = isslash(dir1);

	dir2len = strlen(dir2);
	tgtsize = dir2len + 1;
	target = tmalloc(tgtsize);
	strcpy(target, dir2);
	dir2slash = isslash(dir2);

	/*
	 * We must check boundary conditions before we call dirchecks().
	 * These are: source exists and is a directory, and if target does not
	 * exist that we have write and search permission on its parent and
	 * tgt_dev contains the device number of the parent.
	 */
	if (stat(dir1, &srcstat) < 0) {
		errprint(fmt2, dir1, nofind);
		exit(1);
	}
	if ((srcstat.st_mode & S_IFMT) != S_IFDIR) {
		errprint(fmt2, dir1, notdir);
		exit(1);
	}
	if (access(dir2, 0) < 0) {
		register char *cp;
		cp = parent(dir2);
		if (stat(cp, &tgtstat) < 0) {
			errprint(fmt2, dir2, "cannot find parent");
			exit(1);
		}
		if (access(cp, AWRITE|ASRCH) < 0) {
			errprint(fmt2, dir2, nomkdir);
			exit(1);
		}
		tgt_dev = tgtstat.st_dev;
	}
	if (not dirchecks())
		exit(1);

	src_dev = srcstat.st_dev;
	dir2_dev = tgt_dev = tgtstat.st_dev;
	if (access(target, 0) == 0)
		dir2_ino = tgtstat.st_ino;
	return;
}


/*
 * Assume: Source is a directory we can read and search. Srcstat contains its
 * status. Target is a directory we can write and search. Tgtstat contains its
 * status. Src_dev and tgt_dev are the devices of the parents of source and
 * target. If tflag is on target may not exist. In this case tgtstat.st_dev
 * contains the device where target would have been created if tflag were off.
 */
cpdir()
{
	register int	n;
	register int	fd;
	register DIRBUF	*dbp;
	DIRBUF	dirbuf;
	long	address;
	dev_t	sdev;
	dev_t	tdev;
	struct	stat	locsrcstat;
	struct	stat	loctgtstat;

	/*
	 * Check for circular copy.
	 */
	if (srcstat.st_ino == dir2_ino
	and srcstat.st_dev == dir2_dev) {
		if (wflag)
			printf(fmt2, source,
			"not copied to avoid circular copy");
		return;
	}

	/*
	 * Open source, and prepare dirbuf.
	 */
	if ((fd = open(source, 0)) < 0) {
		errprint(fmt2, source, noopen);
		return;
	}
	dbp = &dirbuf;
	dbp->cbuf[SDSIZ] = '\0';

	/*
	 * Save srcstat, src_dev, tgtstat, tgt_dev to restore before exit.
	 */
	locsrcstat = srcstat;
	loctgtstat = tgtstat;
	tdev = tgt_dev;
	sdev = src_dev;

	/*
	 * Detect the crossing of device boundaries.
	 */
	if (tgt_dev != tgtstat.st_dev) {
		if (wflag)
			printf(fmt2, target, devbound);
		tgt_dev = tgtstat.st_dev;
	}
	if (src_dev != srcstat.st_dev) {
		if (wflag)
			printf(fmt2, source, devbound);
		src_dev = srcstat.st_dev;
	}

	/*
	 * Loop through the directory source.
	 */
	while ((n = read(fd, dbp->cbuf, SDSIZ)) > 0) {
		if (n != SDSIZ) {
			errprint(fmt2, source, readerr);
			close(fd);
			goto OUT;
		}

		canino(dbp->db_ino);
		if (dbp->db_ino == INODE0
		or  strcmp(dbp->db_name, dot) == 0
		or  strcmp(dbp->db_name, dotdot) == 0)
			continue;

		grow(dbp->db_name);
		if (sflag  and  suppress()) {
			if (vflag)
				printf(fmt2, source, supressd);
			shrink();
			continue;
		}
		if (stat(source, &srcstat) < 0) {
			errprint(fmt2, source, nofind);
			shrink();
			continue;
		}

		switch (srcstat.st_mode & S_IFMT) {
		default:
			errprint(fmt2, source, "unknown file type");
			shrink();
			continue;
		case S_IFCHR:
		case S_IFBLK:
			cpnode();
			shrink();
			continue;
		case S_IFPIP:
			srcstat.st_rdev = DEV0;
			cpnode();
			shrink();
			continue;
		case S_IFREG:
			cpfile();
			shrink();
			continue;
		case S_IFDIR:
			if (rlevel == rlimit) {
				if (vflag)
					printf(fmt2, source, supressd);
				shrink();
				continue;
			}
			if (not dirchecks()) {
				shrink();
				continue;
			}

			address = lseek(fd, 0L, 1);
			close(fd);
			++rlevel;
			cpdir();
			--rlevel;
			shrink();
			if ((fd = open(source, 0)) < 0) {
				errprint(fmt2, source,
				"cannot reopen, copy incomplete");
				goto OUT;
			}
			lseek(fd, address, 0);
			continue;
		}
	}
	close(fd);

	OUT:
	if (vflag)
		printf(fmt1, target);
	srcstat = locsrcstat;
	tgtstat = loctgtstat;
	tgt_dev = tdev;
	src_dev = sdev;
	adjust();
	return;
}

/*
 * Assume source names the file to be copied, srcstat contains its status,
 * and target is the file to copy to. Target may or may not exist.
 */
cpfile()
{
#ifndef	SLOW
 	register char *ip;
#endif
	register int n;
	register int i;
	register int fd1;
	register int fd2;
	register size_t size;
#ifndef	SLOW
	register char *wp;				/* write pointer */
	register int wflag;				/* write flag */
	static char buf[CPBUFSIZ];			/* copy buffer */
#else
	static char buf[BUFSIZ];
#endif

	if (access(source, AREAD) < 0) {
		errprint(fmt2, source, noopen);
		return;
	}

	if (uflag  and  stat(target, &tgtstat) == 0)
		if (srcstat.st_mtime <= tgtstat.st_mtime) {
			if (vflag)
				printf(fmt2, target, "no update");
			return;
		}

	if (not tgtunlink())
		return;
	if (srcstat.st_nlink > 1)
		if (linkattempt())
			return;

	if (tflag) {
		if (vflag)
			printf(fmt1, target);
		return;
	}

	if ((fd1 = open(source, 0)) < 0) {
		errprint(fmt2, source, noopen);
		return;
	}
	if ((fd2 = creat(target, 0)) < 0) {
		close(fd1);
		errprint(fmt2, target, nocreate);
		return;
	}

	size = srcstat.st_size;
#ifndef	SLOW
	while ((n = read(fd1, buf, sizeof(buf))) > 0) {
#else
	while ((n = read(fd1, buf, BUFSIZ)) > 0) {
#endif
		/*
		 * Check for blocks of zeroes (holes in a sparse file).
		 * However, a block of zeroes at the end of a file must be
		 * written so the file has correct length.
		 */
#ifndef	SLOW
		wp = ip = buf;
		size -= n;
		while ((n-BUFSIZ) > 0 || ((n-BUFSIZ) == 0 && size != 0)) {
			n -= BUFSIZ;
			wflag = FALSE;
			ip = wp;
			for (i = 0; i < BUFSIZ; ++i)
				if (*ip++ != '\0') {
					wflag = TRUE;
					break;
				}
			if (wflag) {
				if (write(fd2, wp, BUFSIZ) < BUFSIZ) {
					errprint(fmt2, target, writeerr);
					close(fd1);
					close(fd2);
					return (FALSE);
				}
			} else {
				lseek(fd2, (long)BUFSIZ, 1);
			}
			wp += BUFSIZ;
		}
		if (write(fd2, wp, n) < n) {
			errprint(fmt2, target, writeerr);
			close(fd1);
			close(fd2);
			return (FALSE);
		}
#else
		if ((size -= n) == (size_t)0)
			goto WRITE;
		for (i = 0; i < n; ++i)
			if (buf[i] != '\0')
				goto WRITE;
		lseek(fd2, (long)BUFSIZ, 1);
		continue;

		WRITE:
		if (write(fd2, buf, n) < n) {
			errprint(fmt2, target, writeerr);
			close(fd1);
			close(fd2);
			return (FALSE);
		}
#endif
	}
	close(fd1);
	close(fd2);
	adjust();

	if (n < 0)
		errprint(fmt2, source, readerr);
	else if (vflag)
		printf(fmt1, target);
	return;
}

/*
 * Copy special nodes and named pipes.
 */
cpnode()
{
	if (not root) {
		if (vflag)
			printf(fmt2, source, "not the super-user");
		return;
	}
	if (not tgtunlink())
		return;
	if (srcstat.st_nlink > 1)
		if (linkattempt())
			return;
	if (not tflag) {
		if (mknod(target, srcstat.st_mode, srcstat.st_rdev) < 0) {
			errprint(fmt2, target, "cannot make node");
			return;
		}
		adjust();
	}
	if (vflag)
		printf(fmt2, target, "copied node");
	return;
}


/*
 * Grow each of target and source by appending '/' and cp.
 */
grow(cp)
register char *cp;
{
	register int a;
	register int b;

	a = strlen(cp) + 2;

	b = strlen(source);
	if (a+b > srcsize) {
		if ((source = realloc(source, a+b)) == NULL) {
			errprint(fmt1, nomemory);
			exit(1);
		}
		srcsize = a+b;
	}
	if (rlevel > 1  or  not dir1slash)
		source[b++] = '/';
	strcpy(source+b, cp);

	b = strlen(target);
	if (a+b > tgtsize) {
		if ((target = realloc(target, a+b)) == NULL) {
			errprint(fmt1, nomemory);
			exit(1);
		}
		tgtsize = a+b;
	}
	if (rlevel > 1  or  not dir2slash)
		target[b++] = '/';
	strcpy(target+b, cp);
}

/*
 * Shrink source and target down by the last pathname component.
 */
shrink()
{
	if (rlevel == 1  and  dir1slash)
		source[dir1len] = '\0';
	else
		*rindex(source, '/') = '\0';
	if (rlevel == 1  and  dir2slash)
		target[dir2len] = '\0';
	else
		*rindex(target, '/') = '\0';
	return;
}


/*
 * Returns a value equal to atoi(*cpp). Positive decimal integers only.
 * Leaves *cpp pointing to the character that terminated the digit string.
 */
new_atoi(cpp)
char **cpp;
{
	register int sum = 0;
	register int c;
	register char *cp;

	sum = 0;
	cp = *cpp;
	while (isdigit(c = *cp++)) {
		sum *= 10;
		sum -= '0';
		sum += c;
	}
	*cpp = cp-1;
	return (sum);
}

errprint(arg)
char *arg[];
{
	register char *format;

	format = (errprefix) ? "cpdir: %r" : "%r";
	fprintf(stderr, format, &arg);
	if (wflag  and  splitmsg)
		printf(format, &arg);
	if (not eflag)
		exit(1);
	exitval = 1;
	return;
}

/*
 * Interface to malloc to check for bad returns.
 */
char *
tmalloc(n)
int n;
{
	register char *cp;
	if ((cp = malloc(n)) == NULL) {
		errprint(fmt1, nomemory);
		exit(1);
	}
	return (cp);
}

/*
 * Assume: source is a dir, srcstat has its status, we have write and search
 * permission on target's parent directory, tgt_dev is the device of target's
 * parent.
 * Actions: Check permissions on source. Check permissions on target if it
 * exists. If non-extant and tflag is off, make it.
 * Side Effects: On return, if target exists, tgtstat contains its status. If
 * it does not exist, tgtstat.st_dev is the device it would have been created
 * on if tflag were off.
 * Return TRUE if everything Aok, FALSE otherwise.
 */
bool
dirchecks()
{
	register int n;
	static int status;

	if (access(source, AREAD | ASRCH) < 0) {
		errprint(fmt2, source, nopermit);
		return (FALSE);
	}

	if (stat(target, &tgtstat) == 0) {
		if ((tgtstat.st_mode & S_IFMT) != S_IFDIR) {
			errprint(fmt3, source, "target is ", notdir);
			return (FALSE);
		}
		if (access(target, AWRITE | ASRCH) < 0) {
			errprint(fmt2, target, nopermit);
			return (FALSE);
		}
		return (TRUE);
	}

	if (tflag) {
		tgtstat.st_dev = tgt_dev;
		return (TRUE);
	}

	if ((n = fork()) < 0) {
		errprint(fmt2, target, nomkdir);
		return (FALSE);
	}
	if (n == 0) {
		close(2);
		umask(077);
		execl("/bin/mkdir", "cpdir", target, NULL);
		exit(1);
	}
	while (wait(&status) != n)
		;
	if (status != 0) {
		errprint(fmt2, target, nomkdir);
		return (FALSE);
	}
	if (stat(target, &tgtstat) < 0) {
		errprint(fmt2, target, "made but cannot stat");
		return (FALSE);
	}
	return (TRUE);
}

strinstall(cp)
register char *cp;
{
	register STR *sp;
	register int n;

	n = strhash(cp);
	for (sp = strtab[n]; sp != NULL; sp = sp->s_next)
		if (strcmp(cp, sp->s_str) == 0)
			return;
	sp = (STR *) tmalloc(sizeof(STR) + strlen(cp) + 1);
	sp->s_next = strtab[n];
	strtab[n] = sp;
	strcpy(sp->s_str, cp);
	return;
}

bool
suppress()
{
	register char *cp;
	register STR *sp;
	register STR **spp;

	cp = source + dir1len;
	if (not dir1slash)
		++cp;
	spp = strtab + strhash(cp);
	if ((sp = *spp) == NULL)
		return (FALSE);
	while (sp != NULL) {
		if (strcmp(cp, sp->s_str) == 0) {
			*spp = sp->s_next;
			free(sp);
			if (--numsuppress == 0)
				sflag = FALSE;
			return (TRUE);
		}
		spp = &(sp->s_next);
		sp = *spp;
	}
	return (FALSE);
}

strhash(cp)
register uchar *cp;
{
	register uint sum = 0;
	for (sum = 0; *cp != '\0'; sum += *cp++)
		;
	return (sum % HASHSIZE);
}

LINK *
linklocate(flag)
int flag;
{
	register LINK *lp;
	register struct stat *stp;

	if (flag == SOURCE) {
		stp = &srcstat;
		lp = srctab[hash(stp->st_ino)];
	}
	else {
		stp = &tgtstat;
		lp = tgttab[hash(stp->st_ino)];
	}

	for ( ; lp != NULL; lp = lp->l_next) {
		if (lp->l_ino != stp->st_ino)
			continue;
		if (lp->l_dev != stp->st_dev)
			continue;
		--(lp->l_nlink);
		return (lp);
	}
	return (NULL);
}

LINK *
linkinstall(flag)
int flag;
{
	register LINK **lpp;
	register LINK *lp;
	register struct stat *stp;

	if (flag == SOURCE) {
		++srclinks;
		stp = &srcstat;
		lpp = hash(stp->st_ino) + srctab;
	}
	else {
		++tgtlinks;
		stp = &tgtstat;
		lpp = hash(stp->st_ino) + tgttab;
	}

	lp = (LINK *) tmalloc(sizeof(LINK) + strlen(target) - dir2len + 1);
	if (!dir2slash)
		strcpy(lp->l_name, target + dir2len + 1);
	else
		strcpy(lp->l_name, target + dir2len);
	lp->l_dev = stp->st_dev;
	lp->l_ino = stp->st_ino;
	lp->l_tdev = tgt_dev;
	lp->l_nlink = stp->st_nlink - 1;
	lp->l_next = *lpp;
	*lpp = lp;
	return (lp);
}

linkpurge(flag, lp)
int flag;
register LINK *lp;
{
	register LINK *lp1;
	register LINK **lpp;

	if (flag == SOURCE) {
		--srclinks;
		lpp = hash(srcstat.st_ino) + srctab;
	}
	else {
		--tgtlinks;
		lpp = hash(tgtstat.st_ino) + tgttab;
	}

	for (lp1 = *lpp; lp1 != NULL; *lpp = lp1, lp1 = lp1->l_next) {
		if (lp1 != lp)
			continue;
		*lpp = lp1->l_next;
		free((char *)lp1);
		return;
	}
	return;
}

bool
linkattempt()
{
	register LINK *lp;
	register bool ret;
	register char *cp;

	if ((lp = linklocate(SOURCE)) == NULL) {
		linkinstall(SOURCE);
		return (FALSE);
	}
	cp = concat(dir2, lp->l_name);

	if (tflag) {
		if (tgt_dev != lp->l_tdev) {
			errprint(fmt4, target, nolink, " to ", cp);
			++broken;
			ret = FALSE;
		}
		else {
			if (vflag)
				printf(fmt1, target);
			ret = TRUE;
		}
	}
	else if (link(cp, target) == 0) {
		if (vflag)
			printf(fmt1, target);
		ret = TRUE;
	}
	else {
		errprint(fmt4, target, nolink, " to ", cp);
		++broken;
		ret = FALSE;
	}

	if (lp->l_nlink == 0)
		linkpurge(SOURCE, lp);
	return (ret);
}

bool
tgtunlink()
{
	register LINK *lp;

	if (stat(target, &tgtstat) < 0)
		return (TRUE);
	if ((tgtstat.st_mode & S_IFMT) == S_IFDIR) {
		errprint(fmt3, target, nounlink, "directory");
		return (FALSE);
	}
	else if ((tgtstat.st_mode & S_IFMT) != (srcstat.st_mode & S_IFMT)) {
		errprint(fmt3, source, "file type mismatch with ", target);
		return (FALSE);
	}

	if (not tflag)
		if (unlink(target) < 0) {
			errprint(fmt2, target, nounlink);
			return (FALSE);
		}
	if (tflag)
		printf(fmt2, target, "unlinked");

	if ((lp = linklocate(TARGET)) != NULL) {
		if (lp->l_nlink == 0)
			linkpurge(TARGET, lp);
	}
	else if (tgtstat.st_nlink > 1)
		linkinstall(TARGET);
	return (TRUE);
}

report()
{
	register LINK *lp;
	register LINK **lpp;
	static char *fmtlinks = "%s external links into hierarchy %s:\n";

	if (not wflag)
		return;
	if (broken)
		printf("%d internal link%s broken\n", broken,
			(broken == 1) ? "" : "s");
	if (srclinks) {
		printf(fmtlinks, "missed", dir1);
		for (lpp = srctab; lpp < srctab + HASHSIZE; ++lpp)
			for (lp = *lpp; lp != NULL; lp = lp->l_next)
				printf("\t%d\t%s (inode %d)\n", lp->l_nlink,
					concat(dir1, lp->l_name), lp->l_ino);
	}
	if (tgtlinks) {
		printf(fmtlinks, "broken", target);
		for (lpp = tgttab; lpp < tgttab + HASHSIZE; ++lpp)
			for (lp = *lpp; lp != NULL; lp = lp->l_next)
				printf("\t%d\t%s (inode %d)\n", lp->l_nlink,
					concat(dir2, lp->l_name), lp->l_ino);
	}
	return;
}

/*
 * Chown, chmod, and chdate target. Assume srcstat has status of source,
 * and tgtstat has status of target if it exists.
 */
adjust()
{
	time_t date[2];

	if (tflag)
		return;
	if (root)
		chown(target, srcstat.st_uid, srcstat.st_gid);
	chmod(target, srcstat.st_mode & (root ? 07777 : 06777));
	if (dflag) {
		time(&date[0]);
		date[1] = srcstat.st_mtime;
		utime(target, date);
	}
	return;
}

/*
 * Concatenate pieces of pathnames, a and b. If a is not "/" a '/' char
 * is placed between the names. Previous return is freed.
 */
char *
concat(a, b)
register char *a;
char *b;
{
	static char *ret;
	register int a1;
	register char *rp;

	if (ret != NULL)
		free(ret);
	a1 = strlen(a);
	rp = ret = tmalloc(a1 + strlen(b) + 2);
	strcpy(rp, a);
	rp += a1;
	if (not isslash(ret))
		*rp++ = '/';
	strcpy(rp, b);
	return (ret);
}

char *
parent(cp)
register char *cp;
{
	static char *ret;
	register char *cp0;

	if (ret != NULL)
		free(ret);
	if ((cp0 = rindex(cp, '/')) == NULL)
		return (dot);
	ret = tmalloc(cp0 - cp + 1);
	strncpy(ret, cp, cp0 - cp);
	return (ret);
}

bool
isslash(cp)
register char *cp;
{
	register int c;
	while ((c = *cp++) != '\0')
		if (c != '/')
			return (FALSE);
	return (TRUE);
}