/*     
 * $RCSfile: fixRelPaths.c,v $
 *
 * x-kernel v3.3
 *
 * Copyright (c) 1993,1991,1990,1996  Arizona Board of Regents
 *
 * $Log: fixRelPaths.c,v $
 * Revision 1.2  1996/01/27 00:06:33  slm
 * Updated copyright and version.
 *
 * Revision 1.1  1995/07/29  03:06:35  slm
 * Initial revision
 *
 * Revision 1.4.1.1.1.1  1994/11/14  15:42:19  hkaram
 * New branch
 *
 * Revision 1.4.1.1  1994/07/18  23:45:12  menze
 * More solaris fixes
 *
 * Revision 1.4  1994/04/20  23:46:47  gordon
 * changed rindex to strrchr for Solaris
 *
 * Revision 1.3  1994/02/05  00:09:34  menze
 *   [ 1994/01/28          menze ]
 *   removeSourcePathComponent now avoids stat'ing empty string for cwd
 *
 * Revision 1.2  1994/01/27  21:54:55  menze
 *   [ 1994/01/03          menze ]
 *   Fixed a problem with the test for input buffer overflow
 *   Fixed test for whether a directory component indicates the current or
 *   parent directory.
 *
 * Revision 1.1  1993/12/14  00:46:16  menze
 * Initial revision
 */

/* 
 * This program takes a single parameter naming a directory
 * (sourceDir) relative to the program's current directory.  The
 * program then reads a stream of filenames relative to sourceDir and
 * rewrites them as relative to the current directory.
 *
 * In the simplest case, this merely involves slapping the sourceDir in
 * front of each filename.  Exceptions:
 *
 *	Absolute path names are unaltered
 *
 *	Path names with leading '..' have as many of the leading '..'s
 *	stripped off (and sourceDir modified accordingly) as possible
 *	before prepending the sourceDir.  This gets a little messy,
 * 	which is why this isn't a 1 line awk script.
 *
 * Usage:  pgm sourcePath
 *
 */

#include <stdio.h>

#include <string.h>
#ifndef X_SOLARIS
#include <strings.h>
#endif /* X_SOLARIS */

#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>

#define BUFSIZE	512

struct {
    char	*str;
    enum { UP_T, DOWN_T, ROOT_T } type;
    enum { REM_UNKNOWN=0, REM_YES, REM_NO } removable;
} comps[BUFSIZE];


char	*pgm;
char	*sourcePath;
int	sourcePathLen;
int	trailing;
int	last;
int	parsed;


static void
initSourcePath( p )
    char	*p;
{
    sourcePath = p;
    sourcePathLen = strlen(p);
    parsed = 0;
}


static void
resetSourcePath()
{
    if ( parsed ) {
	trailing = last;
    }
}


static void
parseSourcePath()
{
    int	i = 0;

    while(*sourcePath) {
	if ( i == BUFSIZE ) {
	    fprintf(stderr, "%s: sourcePath too long\n", pgm);
	    exit(1);
	}
	if ( *sourcePath == '/' ) {
	    comps[i].type = ROOT_T;
	    comps[i].str = "/";
	    i++;
	    sourcePath++;
	} else if ( ! strncmp(sourcePath, "./", 2) ) {
	    sourcePath += 2;
	} else if ( sourcePath[0] == '.' && sourcePath[1] == 0 ) {
	    sourcePath += 1;
	} else if ( ! strncmp(sourcePath, "../", 3) ) {
	    comps[i].type = UP_T;
	    comps[i].str = "..";
	    i++;
	    sourcePath += 3;
	} else if ( ! strncmp(sourcePath, "..", 2) &&
		    strlen(sourcePath) == 2 ) {
	    comps[i].type = UP_T;
	    comps[i].str = "..";
	    i++;
	    sourcePath += 2;
	} else {
	    comps[i].type = DOWN_T;
	    comps[i].str = sourcePath;
	    while (*sourcePath && *sourcePath != '/') {
		sourcePath++;
	    }
	    if ( *sourcePath ) {
		*sourcePath++ = 0;
	    }
	    i++;
	}
	while(*sourcePath == '/')
	  sourcePath++;
    }
    trailing = last = i - 1;
    parsed = 1;
}


/* 
 * The filename being modified has a "../" as it's first component, so
 * the new filename looks something like "d1/d2/../da/db".  We want to
 * know if it's OK to remove the innermost component of the dirpath
 * (d2 here) and compress the filename to "d1/da/db".  The question
 * boils down to "Do d1/ and d1/d2/.. reference the same thing?"  
 */
static int
removeSourcePathComponent()
{
    if ( ! parsed ) {
	parseSourcePath();
    }
    if ( trailing < 0 || comps[trailing].type != DOWN_T ) {
	return 0;
    }
    /* 
     * We cache the results for each component.
     */
    if ( comps[trailing].removable == REM_UNKNOWN ) {
	int	i;
	char	filename[BUFSIZE];
	struct stat	sb1, sb2;

	/* 
	 * First stat the parent directory of the component we want to
	 * remove. 
	 */
	filename[0] = 0;
	for ( i=0; i < trailing; i++ ) {
	    strcat(filename, comps[i].str);
	    if ( comps[i].type != ROOT_T )
	      strcat(filename, "/");
	}
	if ( filename[0] == 0 ) {
	    strcpy(filename, "./");
	}
	if ( stat(filename, &sb1) ) {
	    fprintf(stderr, "%s: %s: can not stat: ", pgm, filename);
	    perror(0);
	    return 0;
	}
	/* 
	 * Now stat "dir-component/.." ... if they're the same, this
	 * component can be removed.
	 */
	strcat(filename, comps[i].str);
	strcat(filename, "/..");
	if ( stat(filename, &sb2) ) {
	    fprintf(stderr, "%s: %s: can not stat: ", pgm, filename);
	    perror(0);
	    return 0;
	}
	comps[i].removable = (sb1.st_ino == sb2.st_ino &&
			      sb1.st_dev == sb2.st_dev ) ? REM_YES : REM_NO;
    }
    if ( comps[trailing].removable == REM_NO ) {
	return 0;
    }
    /* assert(comps[trailing].removable == REM_YES); */
    trailing--;
    return 1;
}


static void
printSourcePath()
{
    int	i;

    if ( parsed ) {
	for ( i=0; i <= trailing; i++ ) {
	    printf("%s%s", comps[i].str, comps[i].type == ROOT_T ? "" : "/");
	}
    } else {
	printf(sourcePath);
	if ( sourcePath[sourcePathLen - 1] != '/' ) {
	    putchar('/');
	}
    } 
}


static void
usage()
{
    fprintf(stderr, "usage: %s sourceDir\n", pgm);
    exit(2);
}


int
main( argc, argv ) 
    int		argc;
    char	**argv;
{
    char	buf[BUFSIZE];
    char	*p;
    int		changes;

    pgm = strrchr(argv[0], '/');
    if ( pgm ) {
	pgm++;
    } else {
	pgm = argv[0];
    }
    if ( argc < 2 ) usage();

    initSourcePath(argv[1]);
    buf[BUFSIZE - 2] = 0;
    while ( fgets(buf, BUFSIZE, stdin) ) {
	if ( buf[BUFSIZE - 2] ) {
	    /* 
	     * fgets might have thrown something away
	     */
	    fprintf(stderr, "%s: line too long\n", pgm);
	    buf[BUFSIZE - 2] = 0;
	    continue;
	}
	if ( buf[0] == '/' ) {
	    printf(buf);
	    continue;
	}
	p = buf;
	resetSourcePath();
	do {
	    changes = 0;
	    if ( ! strncmp(p, "./", 2) ) {
		p += 2;
		changes = 1;
	    }
	    if ( ! strncmp(p, "../", 3) ) {
		if ( removeSourcePathComponent() ) {
		    p += 3;
		    changes = 1;
		}
	    }
	} while (changes);
	printSourcePath();
	printf(p);
    }
    return 0;
}
