/*
 *	$Source: /usr/src/mklink/RCS/mklink.c,v $
 *	$Author: kubitron $
 *	$Locker: kubitron $
 *	$Header: mklink.c,v 1.7 89/07/16 03:05:54 kubitron Locked $
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/dir.h>
#include <string.h>
#include <sys/param.h>
#include <sys/file.h>
#include <stdio.h>
#include <sys/errno.h>

#define MAXLEVEL 16

void buildlink();
char *normalize();
char *normalizestring();
void delete_start();
void createrel();
void usage();
void serror();

int errno;
static int nflag = 0;
static int vflag = 0;
static int Rflag = 0;
static int rflag = 0;
static int aflag = 0;
static int hflag = 0;
static int lflag = 0;
static int dflag = 0;
static int Fflag = 0;

static char srcbeg[MAXPATHLEN+1],srchead[MAXPATHLEN+1],destbeg[MAXPATHLEN+1];
static char relpath[MAXPATHLEN+MAXLEVEL*3+1],destdir[MAXPATHLEN+1];
static char *destshort;

main(argc,argv)
    int argc;
    char *argv[];
{
    char curwork[MAXPATHLEN+1],*relbeg;
    int count;
    char *c;
 
    while (--argc && (*++argv)[0] == '-') {
	while (*++(*argv)) {
	    switch (**argv) {
	    case 'R':		/* Recursive decent */
		Rflag++; break;
	    case 'n':		/* Tell but don't do */
		nflag++;	/* Fall into... */
	    case 'v':		/* verbose */
		vflag++; break;
	    case 'r':		/* create relative links */
		rflag++; break;
	    case 'a':		/* Copy links exactly */
		aflag++; break;
	    case 'l':		/* Make links to links--don't transfer them */
		lflag++; break;
	    case 'h':		/* Follow multiple links */
		hflag++; break;
	    case 'd':		/* Create directorys from links */
		dflag++; break;
	    case 'F':		/* Ignore RCS,SCCS,.o,.a,*~,#* */
		Fflag++; break;
	    default:
		usage();  /* doesn't return */
	    }
	}
    }
    if (argc != 1 && argc != 2) 
	usage(); /* doesn't return */
    *destbeg = *srcbeg = '\0';
    if (((*argv)[0] != '/') || (argc == 1) || ((*(argv+1))[0] != '/'))
	if (!getwd(curwork)) {
	    serror(curwork,1);
	    exit(1);
	}
    if ((*argv)[0] != '/') {
	strcpy(srcbeg,curwork);
	strcat(srcbeg,"/");
    }
    strcat(srcbeg,*argv);
    strcpy(srchead,normalize(srcbeg,0));   /* static source pointer */
    if (argc == 1)  {
	strcpy(destbeg,curwork);
	destshort = rindex(destbeg,'\0');
	strcat(destbeg,rindex(srcbeg,'/'));
    } else {
	if ((*(argv+1))[0] != '/') {
	    strcpy(destbeg,curwork);
	    strcat(destbeg,"/");
	}
	destshort = rindex(destbeg,'\0');
	strcat(destbeg,*(argv+1));
    }
    normalize(destbeg,0);
    strcpy(destdir,destbeg);
    c = rindex(destdir,'/');	/* Actual directory to write into */
    *c = '\0';
    relbeg = relpath;
    if (rflag) {
	for (count=MAXLEVEL; count; count--) {
	    strcpy(relbeg,"../");
	    relbeg += 3;
	}
	createrel(destbeg,srcbeg,relbeg);
    } else
	strcpy(relbeg,srcbeg);
    if (access(srchead,F_OK|R_OK)) {
	serror(srchead,1);
	exit(1);
    } else if (access(destdir,F_OK|W_OK)) {
	serror(destdir,1);
	exit(1);
    }
    buildlink(index(destbeg,0),index(srcbeg,0),relbeg,index(relbeg,0),0);
}
    
void buildlink (destend,srcend,relsrcbeg,relsrcend,level)
    char *destend,*srcend,*relsrcbeg,*relsrcend;
    int level;
{
    struct stat statbuf;
    DIR *curdir;
    struct direct *subfile;
    char linkbuf[MAXPATHLEN+1],linkrelbuf[MAXPATHLEN+1];
    char *linkpath,*tailbuf,*extbuf,tmpch;
    int len,ignore;

    if (lstat(srcbeg,&statbuf)) {
	serror(srcbeg,0);
	return;
    }

    if (Fflag) {
	ignore = 0;
	if (!(tailbuf = rindex(srcbeg,'/')))
	    tailbuf = srcbeg;
	else
	    tailbuf++;
	if (!strcmp(tailbuf,"SCCS") || !strcmp(tailbuf,"RCS") ||
	    tailbuf[0] == '#' || tailbuf[strlen(tailbuf)-1] == '~')
	    ignore++;
	else if (extbuf = rindex(tailbuf,'.')) {
	    extbuf++;
	    if (!strcmp(extbuf,"o") || !strcmp(extbuf,"a") ||
		!strcmp(extbuf,"bak"))
		ignore++;
	} else if (extbuf = rindex(tailbuf,','))
	    if (!strcmp(extbuf,",v"))
		ignore++;
	if (ignore) {
	    if (vflag)
		printf("mklink -- warning: ignoring %s\n",srcbeg);
	    return;
	}
    }
		
    switch (statbuf.st_mode & S_IFMT) {
    case S_IFLNK:
	if (lflag)
	    goto dolink;
	if ((len = readlink(srcbeg,linkpath=linkrelbuf,MAXPATHLEN+1)) < 0) {
	    serror(srcbeg,0);
	    return;
	}
	linkrelbuf[len] = '\0';	/* Null terminate this sucker */
	if (!aflag) {
	    linkbuf[0] = '\0';
	    if (linkrelbuf[0] != '/') {	/* relative link */
		strcpy(linkbuf,srcbeg);
		if (linkpath = rindex(linkbuf,'/'))
		    *++linkpath = '\0';	/* lop off link name */
	    }
	    strcat(linkbuf,linkrelbuf);
	    normalize(linkpath = linkbuf,1);
	    if (!strncmp(srchead,linkbuf,len = strlen(srchead)) &&
		((tmpch = linkbuf[len]) == '/' || !tmpch)) {
		/*
		 * link is within the source.  Make it relative
		 * (within transfered body).
		 */
		createrel(srcbeg,linkbuf,linkpath=linkrelbuf);
	    }
	    else if (rflag)
		/*
		 * Making relative links.... (relative to destination)
		 */
		createrel(destbeg,linkbuf,linkpath=linkrelbuf);
	}
	if (!nflag && symlink(linkpath,destbeg)) {
	    serror(destbeg,0);
	    return;
	}
	if (vflag)
	    printf("     link: %s --> %s\n",destbeg,linkpath);
	break;

    case S_IFDIR:
	if (Rflag) 
	    if (level < MAXLEVEL) {
		if (!nflag && mkdir(destbeg,0777)) {
		    if (errno != EEXIST) {
			serror(destbeg,1);
			return;
		    } else
			serror(destbeg,0);
		}
		if (vflag)
		    printf("directory: %s\n",destbeg);
		level++;
		if (*relsrcbeg != '/')
		    relsrcbeg -= 3;	/* if relative, add another "../" */
		if (!(curdir = opendir(srcbeg))) {
		    serror(srcbeg,1);
		    return;
		}
		*destend++ = '/';
		*srcend++ = '/';
		*relsrcend++ = '/';
		readdir(curdir);	/* skip . and .. */
		readdir(curdir);
		while (subfile = readdir(curdir)) 
		    if (subfile->d_namlen) {
			strcpy(destend,subfile->d_name);
			strcpy(srcend,subfile->d_name);
			strcpy(relsrcend,subfile->d_name);
			buildlink(index(destend,0),index(srcend,0),
				  relsrcbeg,index(relsrcend,0),level);
		    }
		closedir(curdir);
		break;
	    } else {
		printf(stderr,"Maximum directory depth exceeded--linking %s\n",destbeg);
	    }
    default:
    dolink:
	if (!nflag && symlink(relsrcbeg,destbeg)) {
	    serror(destbeg,0);
	    return;
	}
	if (vflag)
	    printf("     link: %s --> %s\n",destbeg,relsrcbeg);
	break;
    }
    return;
}

/*
 * Create a relative path name for "path" relative to "cwd".
 * Note that directories must be normalized first.
 */
void createrel(cwd,path,buffer)
    char *cwd,*path,*buffer;
{
    char *lcwdnam,*lpathnam,*ccwd,*cpath;

    *buffer = '\0';
    lcwdnam = ccwd = cwd;
    lpathnam = cpath = path;
    while (*ccwd && *cpath && *ccwd == *cpath) 
	if (*ccwd == '/') {
	    lcwdnam = ccwd++;
	    lpathnam = cpath++;
	} else {
	    ccwd++;
	    cpath++;
	}
    if ((!*ccwd || *ccwd == '/') && (!*cpath || *cpath == '/')) {
	lcwdnam = ccwd;
	lpathnam = cpath;
    }
    if (*lcwdnam)
	while (lcwdnam = index(++lcwdnam,'/'))
	    strcat(buffer,"../");
    if (lpathnam)
	strcat(buffer,lpathnam+1);
}


char *normalize(buffer,parentdir)
    char *buffer;
{
    char *point;
    char tmpbuf[MAXPATHLEN+1];
    char *c;

    strcat(buffer,"/");		/* makes matching easier */

    /*
     * Remove null (//) and '.' (/./) components
     */
    point = buffer;
    while (point = index(point,'/')) {
	if (point[1] == '/')
	    delete_start(point,1);
	else if (point[1] == '.' && point[2] == '/')
	    delete_start(point,2);
	else
	    point++;
    }
    c = rindex(buffer,'/');
    *c = '\0';
    if (!(point = rindex(buffer,'/')) || (point == buffer))
	return(buffer);
    
    tmpbuf[0] = '\0';
    if (parentdir || mklink_chdir(buffer)) {
	if (!strcmp(point,"/.."))
	    return(normalizestring(buffer)); /* problems? */
	*point = '\0';
	if (mklink_chdir(buffer)) {
	    *point = '/';
	    return(normalizestring(buffer)); /* problems */
	} else {
	    strcpy(tmpbuf,point+1);
	    mklink_getwd(buffer);
	    strcat(buffer,"/");
	    strcat(buffer,tmpbuf);
	    return(buffer);
	}
    } else {
	mklink_getwd(buffer);
	return(buffer);
    }
}

static char chdirbuf[MAXPATHLEN+1],getdirbuf[MAXPATHLEN+1];
static newdirflag = 1;

mklink_chdir(buffer)
    char *buffer;
{
    if (!(strcmp(buffer,chdirbuf)))
	return(0);
    else {
	strcpy(chdirbuf,buffer);
	newdirflag = 1;
	return(chdir(buffer));
    }
}

mklink_getwd(buffer)
    char *buffer;
{
    if (newdirflag) {
	getwd(getdirbuf);
	newdirflag = 0;
    }

    strcpy(buffer,getdirbuf);
}
	
	

char *normalizestring(buffer)
    char *buffer;
{
    char *point,*cpoint;
    int flag;
    
    strcat(buffer,"/");		/* makes matching easier */

    /*
     * Remove null (//) and '.' (/./) components
     */
    point = buffer;
    while (point = index(point,'/')) {
	if (point[1] == '/')
	    delete_start(point,1);
	else if (point[1] == '.' && point[2] == '/')
	    delete_start(point,2);
	else
	    point++;
    }
    /*
     * Remove extraneous /../ components
     */

    buffer[strlen(buffer)-1] = '\0';	/* kill off trailing slash */

    do {
	flag = 0;
	if (point = index(buffer,'/'))
	    for (;;) {
		if (strncmp(point,"/../",4) &&
		        (cpoint = index(point+1,'/')) &&
		        (!strncmp(cpoint,"/../",4) || !strcmp(cpoint,"/.."))) {
		    if (cpoint = index(cpoint+1,'/')) {
			delete_start(point+1,cpoint-point);
			flag++;
		    } else {
			*point = '\0';
			break;
		    }	
		} else if (!(point = index(point+1,'/')))
		    break;
	    }
    } while (flag);
    
    return(buffer);
}

/*
 * Delete the first n characters from string in place.
 */

void delete_start(string,n)
    char *string;
    int n;
{
    int len = strlen(string);
    if (n >= len)
	*string = '\0';
    else
	bcopy(string+n,string,len-n+1);
}

void usage()
{
    printf("usage: mklink [-RanrvhF] source [dest]\n");
    printf("	   -R:	Recursive descent\n");
    printf("	   -a:  Transfer links without modification \n");
    printf("	   -n:	Don't actually do anything (implies -v)\n");
    printf("	   -r:	Make link farm relative to source\n");
    printf("	   -v:	Verbose\n");
    printf("	   -l:	Make links to links.  Don't transfer them\n");
    printf("	   -F:	Ignore RCS, SCCS, *.o, *.a, *~, and #* files \n");
    exit(1);
}

void serror(string,lev)
    char *string;
    int lev;
{
    fprintf(stderr,"mklink -- ");
    fprintf(stderr,lev?"ERROR: ":"warning: ");
    perror(string);
}
