/*
 * Program to fix web page links for CD-ROM (ISO-9660) or DOS (or UNIX)
 *
 *	Oct. 1997	Written for UNIX by Greg Ward Larson, SGI
 */

/********************************************************************

 This software has been tossed out into the public domain without
 warranty or support.  Use it in good health.  If you like it, share it
 with a friend.  If you don't like it, delete it with the satisfaction
 of knowing you'll never have to use it again.  I made every effort to
 ensure that it would not alter existing pages, only the new directory
 tree it creates.  If you delete your original pages without checking
 the new ones thoroughly, I think we both know who to blame.  If you
 use it in checking mode only, it doesn't create anything and therefore
 (all else being equal), should be harmless.  Still, I can't make any
 guarantee of safety or people will kill me and send their lawyers to
 loot my estate.  Caveat emptor, and that especially applies to things
 that are free.

 That said, if you find problems with this program or make improvements,
 I wouldn't mind hearing about it.  I don't promise to do anything, though,
 since I wrote this program for a single use and I don't expect I'll need
 it again real soon.  The same may be true for you, which is why we need
 to share these utilities when we create them, right?

 Bon apetit!
 -Greg Larson	<gregl@sgi.com>

*********************************************************************/

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>

char NULLDEV[] = "/dev/null";

char DFLTFILE[] = "index";

#define UNIX		0
#define ISO		1

#define HTML		0

char	suffix[][2][8] = {
	"html", "htm",
	"jpeg", "jpg",
	"tiff", "tif",
	"tar.Z", "trz",
	"ps.Z", "psz",
	"tar.gz", "tgz",
	"ps.gz", "pgz",
	"text", "txt",
	"jfif",	"jff",
	"", ""
};

char	anchor[][6] = {"HREF", "SRC", ""};
char	anchars[] = "HShs";	/* any case versions of first letters above */

char	isochar[128] = {		/* ISO character translation */
		'\0', '_', '_', '_', '_', '_', '_', '_',
		'_', '_', '_', '_', '_', '_', '_', '_',
		'_', '_', '_', '_', '_', '_', '_', '_',
		'_', '_', '_', '_', '_', '_', '_', '_',
		'_', '_', '_', '_', '_', '_', '_', '_',	/*  !"#$%&' */
		'_', '_', '_', '_', '_', '_', '_', '_',	/* ()*+,-./ */
		'0', '1', '2', '3', '4', '5', '6', '7',	/* 01234567 */
		'8', '9', '_', '_', '_', '_', '_', '_',	/* 89:;<=>? */
		'_', 'a', 'b', 'c', 'd', 'e', 'f', 'g',	/* @ABCDEFG */
		'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',	/* HIJKLMNO */
		'p', 'q', 'r', 's', 't', 'u', 'v', 'w',	/* PQRSTUVW */
		'x', 'y', 'z', '_', '_', '_', '_', '_',	/* XYZ[\]^_ */
		'_', 'a', 'b', 'c', 'd', 'e', 'f', 'g',	/* `abcdefg */
		'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',	/* hijklmno */
		'p', 'q', 'r', 's', 't', 'u', 'v', 'w',	/* pqrstuvw */
		'x', 'y', 'z', '_', '_', '_', '_', '_',	/* xyz{|}~  */
	};

#define ISOLEN		8
#define ISOSUF		3

typedef struct pmove {
	struct pmove	*next;		/* next in list */
	mode_t	mode;			/* file mode and type */
	ino_t	inode;			/* file inode number (link tracking) */
	dev_t	dev;			/* device for this file (ditto) */
	char	*newp;			/* new path name (NULL if none) */
	char	oldp[1];		/* old path name (extends struct) */
} PMOVE;			/* a page movement record */

#define isdir(m)	((m) && S_ISDIR(m))

PMOVE	*todolist = NULL, *donelist = NULL, *curpage = NULL;

#define pmdone(pm)	(pm->next = donelist, donelist = pm)
#define pmtodo(pm)	(pm->next = todolist, todolist = pm)

				/* flags for what we're doing */
#define C2ISOFN		01
#define C2UNIXFN	02
#define C2DOSNL		04
#define C2UNIXNL	010
#define CVERBOSE	020
#define CCHKONLY	040

int	cvtflags = 0;

char	*progname, *orig_url;

FILE	*infp, *outfp;

extern char	*localpath(), *relpath(), *findsuff();
extern FILE	*checkopen();
extern PMOVE	*pmalloc(), *getdone(), *getany(), *mkmove(), *getnew();


main(argc, argv)		/* check/rename web pages */
int	argc;
char	*argv[];
{
	int	i, n;

	progname = argv[0];
	for (i = 1; i < argc && argv[i][0] == '-'; i++)
		switch (argv[i][1]) {
		case 'u':		/* UNIX destination */
			cvtflags &= ~(C2DOSNL|C2ISOFN);
			cvtflags |= C2UNIXFN|C2UNIXNL;
			break;
		case 'd':		/* DOS destination */
			cvtflags &= ~C2UNIXNL;
			cvtflags |= C2DOSNL;
			/* fall through */
		case 'i':		/* ISO destination */
			cvtflags &= ~C2UNIXFN;
			cvtflags |= C2ISOFN;
			break;
		case 'v':		/* verbose */
			cvtflags |= CVERBOSE;
			break;
		case 'h':
			Help(stdout);
			exit(0);
		default:
			goto userr;
		}
	if (i > argc-2 | i < argc-3)
		goto userr;
					/* get starting URL */
	orig_url = argv[i];
	if (strncmp("http://", orig_url, 7))
		goto userr;
	for (n = strlen(orig_url); orig_url[n-1] == '/'; orig_url[--n] = '\0')
		;
	if (n < 8)
		goto userr;
					/* initialize lists */
	if (init(argv[i+1], argv[i+2]) < 0)
		goto userr;
					/* do all pages */
	while (donext())
		;
	exit(0);			/* done */
userr:
	Usage(stderr);
	exit(1);
}


Usage(fp)			/* print usage message to fp */
FILE	*fp;
{
	fprintf(fp,
	"Usage: %s [-u|-i|-d][-v] base_URL orig_dir/[html] [new_dir/[html]]\n",
			progname);
	fprintf(fp, "   Or: %s -h[elp]\n", progname);
}


Help(fp)			/* print helpful hints to fp */
register FILE	*fp;
{
	register int	i;

	fprintf(fp, "\nThis program prepares web pages for publication.\n\n");
	Usage(fp);
	fprintf(fp, "\nArguments to %s and their interpretation:\n", progname);
	fprintf(fp, "base_URL\tThe starting point for this website (beginning with \"http://\")\n");
	fprintf(fp, "orig_dir/[html]\tThe original directory [and front page] relative to base_URL\n");
	fprintf(fp, "\t\tIf [html] is left off, then we assume \"%s.%s\"\n",
			DFLTFILE, suffix[HTML][UNIX]);
	fprintf(fp, "newdir/[html]\tThe destination directory [and front page] relative to base_URL\n");
	fprintf(fp, "\t\tThis new directory must not exist.\n");
	fprintf(fp, "\t\tIf this argument is left off, %s just checks pages.\n", progname);
	fprintf(fp, "\nOptions to %s and their meanings:\n", progname);
	fprintf(fp, "\t-u\tConvert to UNIX file naming conventions and EOL\n");
	fprintf(fp, "\t-i\tConvert to ISO-9660 file naming conventions\n");
	fprintf(fp, "\t-d\tConvert to DOS file naming conventions and EOL\n");
	fprintf(fp, "\t-v\tVerbose reporting of progress to stdout\n");
	fprintf(fp, "\nAssumptions made by %s:\n", progname);
	fprintf(fp, "o Only move what front page references, directly and indirectly\n");
	fprintf(fp, "o Overhead references (above front page directory) are not moved\n");
	fprintf(fp, "o No non-printing, non-white characters are desired in pages\n");
	fprintf(fp, "o Symbolic links are followed and not treated specially\n");
	fprintf(fp, "o Hard links are made to destination whenever possible\n");
	fprintf(fp, "o Hard links are preserved among files\n");
	fprintf(fp, "\nThe following file suffixes are recognized:\n");
	fprintf(fp, "    ____UNIX____ISO____\n");
	for (i = 0; suffix[i][0][0]; i++)
		fprintf(fp, "\t%s\t%s\n", suffix[i][UNIX], suffix[i][ISO]);
	fputc('\n', fp);
}


init(origh, newh)		/* initialize our process lists */
char	*origh, *newh;
{
	char	orig_dir[MAXPATHLEN], new_dir[MAXPATHLEN];
	char	orig_home[MAXNAMELEN], new_home[MAXNAMELEN];
	char	orig_path[MAXPATHLEN], new_path[MAXPATHLEN];
	register PMOVE	*pm;

	if (!getstart(orig_dir, orig_home, origh))
		return(-1);
	if (!strcmp(orig_dir, ".")) {		/* kill "." directory */
		orig_dir[0] = '\0';
		strcpy(orig_path, orig_home);
	} else
		sprintf(orig_path, "%s/%s", orig_dir, orig_home);
	if (newh != NULL) {
		if (!getstart(new_dir, new_home, newh))
			return(-1);
		pm = pmalloc(orig_dir, new_dir);
		if (!isdir(pm->mode)) {
			fprintf(stderr, "%s: %s: not a directory\n",
					progname, orig_dir);
			exit(1);
		}
		if (mkdir(new_dir, pm->mode) < 0) {
			fprintf(stderr, "%s: mkdir: ", progname);
			perror(new_dir);
			exit(2);
		}
		pmdone(pm);
		sprintf(new_path, "%s/%s", new_dir, new_home);
		pm = pmalloc(orig_path, new_path);
		pmtodo(pm);
	} else {
		cvtflags |= CCHKONLY;
		pm = pmalloc(orig_dir, NULL);
		pmdone(pm);
		pm = pmalloc(orig_path, NULL);
		pmtodo(pm);
	}
	return(0);
}


getstart(dir, hom, pth)		/* extract directory and home page file name */
register char	*dir, *hom, *pth;
{
	register char	*fn = NULL;
					/* copy full path into dir */
	while (*pth) {
		if ((*dir = *pth++) == '/')
			fn = dir;
		dir++;
	}
	if (fn == NULL)			/* make sure we got a directory */
		return(0);
	*fn++ = '\0';			/* separate out directory */
	if (*fn)			/* got filename also */
		strcpy(hom, fn);
	else {				/* else use default */
		for (fn = DFLTFILE; (*hom = *fn++); hom++)
			;
		*hom++ = '.';
		strcpy(hom, suffix[HTML][UNIX]);
	}
	return(1);
}


donext()			/* do next web page */
{
					/* get page from to do list */
	if (todolist == NULL)
		return(0);		/* return 0 if done */
	curpage = todolist;
	todolist = todolist->next;
	curpage->next = NULL;
					/* see if it can be linked */
	if (linkdone(curpage)) {
		pmdone(curpage);
		curpage = NULL;
		return(1);
	}
					/* open input file */
	if ((infp = fopen(curpage->oldp, "r")) == NULL) {
		fprintf(stderr, "%s: cannot open web page: ", progname);
		perror(curpage->oldp);
		exit(2);
	} else if (cvtflags & CVERBOSE)
		printf("Reading HTML from \"%s\"\n", curpage->oldp);
					/* open output file */
	if (curpage->newp == NULL)
		outfp = fopen(NULLDEV, "w");
	else if ((outfp = fopen(curpage->newp, "w")) == NULL) {
		fprintf(stderr, "%s: cannot open new page: ", progname);
		perror(curpage->newp);
		exit(2);
	} else if (cvtflags & CVERBOSE)
		printf("Writing HTML to \"%s\"\n", curpage->newp);
					/* process this page */
	docurrent();
					/* clean up */
	fclose(infp); infp = NULL;
	if (fclose(outfp) < 0) {
		fprintf(stderr, "%s: close error: ", progname);
		perror(curpage->newp);
		exit(2);
	}
	outfp = NULL;
	if (cvtflags & CVERBOSE)
		printf("Done processing \"%s\"\n", curpage->oldp);
	pmdone(curpage);
	curpage = NULL;
	return(1);
}


docurrent()			/* process the current web page from infp */
{
#define INCOMMAND	1
#define INCOMMENT	2
#define INQUOTE		4
	int	state = 0, funnychars = 0, lineno = 0;
	char	buf[1024];
	register int	c, i;
	int	n;

	while (fgets(buf, sizeof(buf), infp) != NULL) {
		lineno++;
		for (i = 0; (c=buf[i]); i++) {
			switch(c) {
			case '<':
				state |= INCOMMAND;
				break;
			case '"':
				if (state & INCOMMAND)
					state ^= INQUOTE;
				break;
			case '!':
				if ((state & (INCOMMAND|INQUOTE)) == INCOMMAND)
					state |= INCOMMENT;
				break;
			case '>':
				if (!(state & INQUOTE))
					state &= ~(INCOMMAND|INCOMMENT);
				break;
			case '\r':
				if (cvtflags & C2UNIXNL) {
					if (!buf[i+1])
						fputc('\n', outfp);
				} else
					break;
				continue;
			case '\n':
				if (cvtflags & C2DOSNL &&
						(i == 0 || buf[i-1] != '\r'))
					fputc('\r', outfp);
				break;
			default:
				if (state==INCOMMAND &&
						(i == 0 || buf[i-1] == '<'
							|| isspace(buf[i-1]))
						&& strchr(anchars, c) != NULL
						&& (n = processlink(buf+i))) {
					i += n-1;
					continue;
				}
				if (!isascii(c) ||
						!(isprint(c) || isspace(c))) {
					funnychars++;
					continue;
				}
				break;
			}
			putc(c, outfp);
		}
		if (state & INQUOTE) {
			fprintf(stderr,
		"%s: warning - unbalanced quote at line %d in \"%s\"\n",
					progname, lineno, curpage->oldp);
			state &= ~INQUOTE;
		}
	}
	if (funnychars)
		fprintf(stderr,
		"%s: warning - skipped %d non-printing characters in \"%s\"\n",
				progname, funnychars, curpage->oldp);
#undef INCOMMAND
#undef INCOMMENT
#undef INQUOTE
}


#if 0
matchiso(isfn, name)		/* see if name matches isfn using isochar */
register char	*isfn, *name;
{
	while (*isfn == isochar[*name++])
		if (!*isfn++)
			return(1);
	return(0);
}
#endif


matchkey(key, s)		/* see if a string matches a upper-case key */
register char	*key, *s;
{
	while (*key)
		if (toupper(*s++) != *key++)
			return(0);
	return(1);
}


int
processlink(s)			/* see if this is a link we can process */
char	*s;
{
	int	i, n;
	register char	*cp, *cp2;
	char	*subfile;
	PMOVE	*pm;
	char	url[MAXPATHLEN];
				/* find keyword */
	for (i = 0; anchor[i][0]; i++)
		if (matchkey(anchor[i], s))
			break;
	if (!(n = strlen(anchor[i])))
		return(0);
				/* find reference */
	for (cp = s+n; *cp && isspace(*cp); cp++)
		;
	if (*cp != '=')
		return(0);
	for (cp++; *cp && isspace(*cp); cp++)
		;
	if ((n = *cp == '"'))	/* quoted? */
		cp++;
	cp2 = url;		/* extract URL */
	while (*cp) {
		if (n ? *cp == '"' : isspace(*cp) || *cp == '>')
			break;
		*cp2++ = *cp++;
	}
	if (cp2 == url)
		return(0);
	*cp2 = '\0';
	n += cp - s;		/* tricky length -- note boolean value of n */
				/* separate subfile index, if any */
	if ((subfile = strchr(url, '#')) != NULL)
		*subfile++ = '\0';
				/* check for internal ref. or remote URL */
	if (!*url || (cp2 = localpath(url)) == NULL) {
		fprintf(outfp, "%s=\"%s", anchor[i], url);
		goto finish;
	}
	cp = findsuff(cp2);	/* check suffix */
	if (*cp++ == '.' && toupper(*cp++) == 'H' &&
			toupper(*cp++) == 'T' && toupper(*cp++) == 'M') {
		if (toupper(*cp) == 'L') cp++;
		if (*cp)
			cp = NULL;	/* suffix not html */
	} else
		cp = NULL;		/* not html */
	pm = curpage;		/* using as flag */
	if (!*cp2 || !strncmp(cp2, "../", 3))
		goto overhead;
				/* see if we've logged it already */
	if ((pm = getany(cp2)) == NULL) {
		pm = mkmove(cp2);	/* pick name and allocate */
		if (pm == NULL)
			goto overhead;	/* above top directory */
		if (pm->mode == 0)
			pmdone(pm);	/* missing file entry */
		else if (cp != NULL)
			pmtodo(pm);	/* put HTML on to do list */
		else {
			cplink(pm);	/* copy/link other files */
			pmdone(pm);
		}
	}
	if (pm->mode == 0)	/* flag missing pages */
		fprintf(stderr, "%s: %s: warning - dangling link \"%s\"\n",
				progname, curpage->oldp, pm->oldp);
				/* output relative path */
	fprintf(outfp, "%s=\"%s", anchor[i], relpath(curpage->newp, pm->newp));
	goto finish;

overhead:			/* overhead link case */
	fprintf(stderr, "%s: %s: warning - overhead link \"%s\" left as ",
			progname, curpage->oldp, cp2);
	if (pm == NULL) {
		fprintf(stderr, "\"%s/%s\"\n", orig_url, cp2);
		fprintf(outfp, "%s=\"%s/%s", anchor[i], orig_url, cp2);
	} else {
		fprintf(stderr, "\"%s\"\n", url);
		fprintf(outfp, "%s=\"%s", anchor[i], url);
	}
finish:
	if (subfile != NULL)	/* print subfile index, if any */
		fprintf(outfp, "#%s", subfile);
	fputc('"', outfp);	/* close quote */
	return(n);
}


islocal(s)			/* check for local path vs. network URL */
register char	*s;
{
	if (!*s) return(0);
	do {
		if (*s == ':') return(0);
		if (*s == '/') return(1);
	} while (*++s);
	return(1);
}


killparents(pth)		/* remove "../" occurrences from pth */
char	*pth;
{
	register char	*cp, *cp2;
	int	npar = 0;
				/* eliminate "./" occurrences first! */
	cp2 = pth;
	while ((cp = strstr(cp2, "./")) != NULL) {
		cp2 = cp + 2;
		if (cp > pth && cp[-1] != '/')
			continue;
		while ((*cp++ = *cp2++))
			;
		cp2 = pth;
	}
	cp2 = pth;
	while ((cp = strstr(cp2, "../")) != NULL) {
		cp2 = cp + 3;
		if (cp == pth)
			return(-1);	/* path begins with parent! */
		if (*--cp != '/')
			continue;
		while (cp > pth && cp[-1] != '/')
			cp--;
		npar++;
		while ((*cp++ = *cp2++))
			;
		cp2 = pth;
	}
	return(*pth ? npar : -1);
}


char *
localpath(url)			/* determine if URL is local to us */
char	*url;
{
	static char	locp[MAXPATHLEN];
	register int	n;

	n = strlen(orig_url);				/* check full URL */
	if (!strncmp(url, orig_url, n) && url[n] == '/')
		strcpy(locp, url+n+1);
	else if (islocal(url)) {			/* get local name */
		strcpy(locp, curpage->oldp);
		for (n = strlen(locp); n > 0 && locp[n-1] != '/'; n--)
			;
		strcpy(locp+n, url);
	} else
		return(NULL);
	n = strlen(locp);
	if (n == 0 || locp[n-1] == '/') {	/* use default page */
		strcpy(locp+n, DFLTFILE);
		n += strlen(locp+n);
		locp[n++] = '.';
		if (cvtflags & C2UNIXFN)
			strcpy(locp+n, suffix[HTML][ISO]);
		else
			strcpy(locp+n, suffix[HTML][UNIX]);
	}
	killparents(locp);			/* eliminate "../" */
	return(locp);
}


char *
relpath(curp, pth)		/* from full path, find shortest route */
char	*curp, *pth;
{
	static char	relp[MAXPATHLEN];
	int	npar;
	register char	*cp1, *cp2;

	if (curp == NULL || pth == NULL)
		return("NOPATH");
					/* skip common prefix */
	for (cp1 = curp, cp2 = pth; *cp1 && *cp1 == *cp2; cp1++, cp2++)
		;
	while (cp1 > curp && cp1[-1] != '/')
		cp1--;
	while (cp2 > pth && cp2[-1] != '/')
		cp2--;
					/* count uncommon components in curp */
	for (npar = 0; *cp1; cp1++)
		npar += *cp1 == '/';
					/* prepend this many parents */
	for (cp1 = relp; npar--; ) {
		*cp1++ = '.'; *cp1++ = '.'; *cp1++ = '/';
	}
					/* the rest is where we left cp2 */
	strcpy(cp1, cp2);
	return(relp);
}


PMOVE *
pmalloc(op, np)			/* allocate and assign new PMOVE structure */
char	*op, *np;
{
	register PMOVE	*p; int n, n1;
	struct stat	stbuf;

	if (stat(*op ? op : ".", &stbuf) < 0) {	/* stat original */
		fprintf(stderr, "%s: ", progname);
		if (curpage != NULL)
			fprintf(stderr, "%s: ", curpage->oldp);
		perror(*op ? op : ".");
		memset((char *)&stbuf, 0, sizeof(stbuf));
	}
	n1 = n = sizeof(PMOVE) + strlen(op);	/* allocate struct */
	if (np!=NULL) n += strlen(np)+1;
	if ((p=(PMOVE *)malloc(n)) == NULL) {
		fprintf(stderr, "%s: out of memory\n", progname);
		exit(2);
	}
	strcpy(p->oldp, op);			/* copy names */
	if (np!=NULL) p->newp = strcpy((char *)p+n1, np);
	else p->newp = NULL;
	p->mode = stbuf.st_mode;		/* record mode, etc. */
	p->dev = stbuf.st_dev;
	if (!isdir(stbuf.st_mode) && stbuf.st_nlink > 1)
		p->inode = stbuf.st_ino;
	else
		p->inode = 0;
	p->next = NULL;
	return(p);
}


char *
findsuff(pn)		/* find file suffix, return '.' position or end */
register char	*pn;
{
	register char	*sf = NULL;
	register int	needsuff = 1;

	while (*pn) {
		if (*pn == '.') {		/* potential suffix */
			if (needsuff) {
				sf = pn;
				needsuff = 0;
			}
		} else if (*pn == '/') {	/* new directory */
			sf = NULL;
			needsuff = 1;
		} else if (sf != NULL) {	/* else check suffix */
			if (*pn == '#')		/* don't go into index */
				break;
			needsuff |= isdigit(*pn);	/* avoid digits */
		}
		pn++;
	}
	return(sf==NULL ? pn : sf);
}


incrfname(c0)		/* increment number in file name */
register char	*c0;
{
	while (*c0 == '9') *c0-- = '0';
	if (*c0 <= '0' || *c0 > '9') *c0 = '1';
	else (*c0)++;
}


newpath(op, np)		/* assign new path, creating directories as needed */
char	*op, *np;
{
	register char	*cp1, *cp2;
	PMOVE	*p;
	char	origd[MAXPATHLEN];
					/* identify old directory */
	for (cp1 = op, cp2 = origd; (*cp2++ = *cp1++); )
		;
	while (cp2 > origd && *--cp2 != '/')
		cp1--;
	op = cp1;			/* point to original file name */
	*cp2 = '\0';
	if ((p = getdone(origd)) == NULL) {	/* need new directory */
		p = mkmove(origd);		/* mutually recursive */
		if (p == NULL)
			return(0);
		pmdone(p);			/* add to done list */
	}				/* copy new directory path */
	for (cp1 = p->newp, cp2 = np; (*cp2 = *cp1++); cp2++)
		;
	*cp2++ = '/';
	cp1 = op;			/* rename file as indicated */
	if (cvtflags & C2UNIXFN) {
		while ((*cp2++ = *cp1) && *cp1++ != '.')
			;
		if (*cp1)		/* check suffix */
			if (!strcmp(cp1, suffix[HTML][ISO]))
				strcpy(cp2, suffix[HTML][UNIX]);
			else
				strcpy(cp2, cp1);
	} else if (cvtflags & C2ISOFN) {
		char	*nm = cp2, *suf;
		register int	i;
		/* Check character set.
		 * Check length.
		 * Check suffix.
		 * Make sure new file name is unique.
		 */
		suf = findsuff(cp1);
		while (*cp1) {			/* map filename */
			if (cp1 == suf)
				*cp2++ = '.';
			else if (cp1 > suf)
				*cp2++ = *cp1;
			else if (cp2 < nm+ISOLEN)
				*cp2++ = isochar[*cp1 & 0x7f];
			cp1++;
		}
		*cp2 = '\0';
		suf = findsuff(nm);
		if (suf == nm+ISOLEN && nm[ISOLEN-1] == '_')
			nm[ISOLEN-1] = '0';	/* don't generate trailing _ */
		if (*suf) {			/* map extension */
			for (i = 0; suffix[i][0][0]; i++)
				if (!strcmp(suf+1, suffix[i][UNIX])) {
					strcpy(suf+1, suffix[i][ISO]);
					break;
				}
			if (!suffix[i][0][0]) {	/* didn't match standard one */
				for (i = 1; i <= ISOSUF; i++)
					suf[i] = isochar[suf[i] & 0x7f];
				suf[ISOSUF+1] = '\0';
			}
		}
		while (getnew(np) != NULL) {
			if (suf-nm < ISOLEN) {
				for (i = ISOSUF+1; i--; )
					nm[ISOLEN+i] = suf[i];
				while (suf-nm < ISOLEN)
					*suf++ = '0';
			}
			incrfname(suf-1);
		}
	} else
		strcpy(cp2, cp1);
	return(1);
}


PMOVE *
mkmove(oldname)		/* determine new name and allocate PMOVE struct */
char	*oldname;
{
	register PMOVE	*p;
	char	newname[MAXPATHLEN], *cp;
	register int	n;

	if (cvtflags & CCHKONLY)
		return(pmalloc(oldname, NULL));
	n = strlen(oldname);
	while (n > 0 && oldname[n-1] == '/')
		oldname[--n] = '\0';
	if (n == 0 || !newpath(oldname, newname))
		return(NULL);
	p = pmalloc(oldname, newname);			/* allocate PMOVE */
							/* create if dir. */
	if (isdir(p->mode)) {
		while (cvtflags & C2ISOFN && *(cp = findsuff(p->newp))) {
			*cp = '_';		/* no extension */
			while (cp > p->newp && cp[-1] != '/')
				cp--;
			cp[ISOLEN] = '\0';	/* may harm uniqueness */
		}
		if (mkdir(p->newp, p->mode) < 0) {
			fprintf(stderr, "%s: %s: mkdir: ",
					progname, curpage->oldp);
			perror(p->newp);
			exit(2);
		} else if (cvtflags & CVERBOSE)
			printf("Created directory \"%s\"\n", p->newp);
	}
	return(p);
}


PMOVE *
getnew(np)		/* find PMOVE struct with same new path */
char	*np;
{
	register PMOVE	*p;

	if (curpage != NULL && !strcmp(curpage->newp, np))
		return(curpage);
	for (p = donelist; p != NULL; p = p->next)
		if (!strcmp(p->newp, np))
			return(p);
	for (p = todolist; p != NULL; p = p->next)
		if (!strcmp(p->newp, np))
			return(p);
	return(NULL);
}


PMOVE *
getdone(op)		/* find finished PMOVE with this original path */
char	*op;
{
	register PMOVE	*p;

	for (p = donelist; p != NULL; p = p->next)
		if (!strcmp(p->oldp, op))
			return(p);
	return(NULL);
}


PMOVE *
getany(op)		/* find any PMOVE struct with this original path */
char	*op;
{
	register PMOVE	*p;

	if ((p = getdone(op)) != NULL)
		return(p);
	if (curpage != NULL && !strcmp(curpage->oldp, op))
		return(curpage);
	for (p = todolist; p != NULL; p = p->next)
		if (!strcmp(p->oldp, op))
			return(p);
	return(NULL);
}


linkdone(pm)			/* see if a page can be linked */
register PMOVE	*pm;
{
	register PMOVE	*pl;
					/* first, is it even linkable? */
	if (pm->newp == NULL || pm->inode == 0)
		return(0);
					/* OK, see if we can find it */
	for (pl = donelist; pl != NULL; pl = pl->next)
		if (pl->inode == pm->inode && pl->dev == pm->dev)
			break;
	if (pl == NULL || pl->newp == NULL)
		return(0);		/* not found */
	if (link(pl->newp, pm->newp) < 0) {
		fprintf(stderr, "%s: %s: cannot link: ", progname, pl->newp);
		perror(pm->newp);
		exit(2);
	} else if (cvtflags & CVERBOSE)
		printf("Linked \"%s\" to \"%s\"\n", pl->newp, pm->newp);
	return(1);
}


cplink(pm)		/* copy or link a file to its new location */
register PMOVE	*pm;
{
	if (pm->newp == NULL)
		return;
	errno = 0;
	if (link(pm->oldp, pm->newp) < 0)
		if (errno == EXDEV) {
			if (!linkdone(pm))
				cpfile(pm->oldp, pm->newp);
		} else {
			fprintf(stderr, "%s: %s: cannot link: ",
					progname, pm->oldp);
			perror(pm->newp);
			exit(2);
		}
	else if (cvtflags & CVERBOSE)
		printf("Linked \"%s\" to \"%s\"\n", pm->oldp, pm->newp);
}


cpfile(oldf, newf)	/* copy file contents */
char	*oldf, *newf;
{
	int	infd, outfd, n;
	char	buf[BUFSIZ];

	if ((infd = open(oldf, O_RDONLY)) < 0) {
		fprintf(stderr, "%s: cannot open for reading: ", progname);
		perror(oldf);
		exit(2);
	}
	if ((outfd = open(newf, O_WRONLY|O_CREAT|O_EXCL, 0666)) < 0) {
		fprintf(stderr, "%s: cannot open for writing: ", progname);
		perror(newf);
		exit(2);
	}
	while ((n = read(infd, buf, BUFSIZ)) > 0)
		if (write(outfd, buf, n) != n) {
			fprintf(stderr, "%s: error writing: ", progname);
			perror(newf);
			exit(2);
		}
	if (n < 0) {
		fprintf(stderr, "%s: error reading: ", progname);
		perror(oldf);
		exit(2);
	}
	close(infd);
	close(outfd);
	if (cvtflags & CVERBOSE)
		printf("Copied \"%s\" to \"%s\"\n", oldf, newf);
}
