Relay-Version: version B 2.10 5/3/83; site utzoo.UUCP
Path: utzoo!mnetor!uunet!seismo!mcvax!diku!thorinn
From: thorinn@diku.UUCP (Lars Henrik Mathiesen)
Newsgroups: comp.unix.wizards
Subject: Re: symbolic links (and a path canonifier for csh)
Message-ID: <3326@diku.UUCP>
Date: Wed, 15-Jul-87 09:27:26 EDT
Article-I.D.: diku.3326
Posted: Wed Jul 15 09:27:26 1987
Date-Received: Sat, 18-Jul-87 04:42:11 EDT
References:  > <2211@bunker.UUCP> <1097@mtune.ATT.COM> <1101@mtune.ATT.COM> <6837@beta.UUCP> <6035@brl-smoke.ARPA>
Organization: DIKU, U of Copenhagen, DK
Lines: 193
Keywords: symbolic links

In article <6035@brl-smoke.ARPA> gwyn@brl-smoke.ARPA (Doug Gwyn ) writes:
>Surely some other approach fully compatible with hard links could have been
>found.

Excuse me, but how are symbolic links *to files* different from hard links?
For that matter, it seems to me that you'd get exactly the same problems if
you used hard links to directories. The only way to distinguish between
hard and symbolic links is lstat(2) (and an occasional ELOOP), isn't it?
(From this point of view it is a *feature* of symbolic links that their modes
aren't ever used.)
  The problem is that the use of symbolic links for directories is encouraged
(or at least not discouraged), which shows up the semantic problems much more.

But to solve the very real problem that csh(1) does not take symbolic links
into consideration when simplifying directory pathnames, I've written the
enclosed program for 4.3BSD. It is intended to be used with aliases as follows:
	alias cd    'chdir `cdfix    $cwd \!*`'
	alias pushd 'pushd `pushdfix $cwd \!*`'
and includes special case code to supply the default arguments if none are
given (which is why it must have two links). It runs faster than my old
solution (which was to do a 'cd `pwd`' after each chdir or pushd).
--
Lars Mathiesen, DIKU, U of Copenhagen, Denmark		..mcvax!diku!thorinn
Institute of Datalogy -- we're scientists, not engineers.

-------- cut here (cdfix.c AND pushdfix.c) ----------
/*
 * This is the source for both cdfix AND pushdfix, which should be hard links
 * to the same file. This goes for the source too.
 */

#include 
#ifdef DO_TILDE
#include 
#endif
#include 
#include 
#include 

int errno;
char *errpath, *progname;
char usage[] = "Usage: %s old-dir-path change-dir-path\n";

char *getenv();

panic(file, syscall, error)
    char *file, *syscall;
{
    char buf[BUFSIZ];

    sprintf(buf, "%s: %s: %s", progname, syscall, file);
    if (error)
	perror(buf, error);
    else
	fprintf(stderr, "%s\n", buf);
    printf("%s\n", errpath);
    exit(0);
}

#define LIM(x) (&(x)[MAXPATHLEN])

main(argc, argv)
    char **argv;
{
    static char head[MAXPATHLEN + 1], tail[MAXPATHLEN + 1];
    register char *headend, *tailbeg, *cp;
    struct stat stbuf;
    int loop = 0;
#ifdef DOTILDE
    char *name;
    struct passwd *pw;
#endif

    progname = argv[0];
    errpath = argv[1];
    if (argc != 3) {
	if (argc != 2)
	    fprintf(stderr, usage, progname);
	/*
	 * If this program is used to construct the argument
	 * to chdir or pushd in the csh, an empty argument is
	 * not equivalent to no argument; so we supply one that
	 * is equivalent. Assumes that two links are used, so
	 * that program name starts with 'p' for use with pushd.
	 */
	printf(*progname == 'p' ? "+1\n" : "~\n");
	exit(0);
    }

    /*
     * Check for the easy case.
     * This also lets +n arguments alone for pushd.
     */
    if (argv[2][0] != '.' && !index(argv[2], '/')) {
	printf(argv[2]);
	exit(0);
    }

    /*
     * Set up head and tail, assuming that the old path is OK.
     * Head contains a "canonical" path (no ., .. or superfluous /).
     * Head is normally NOT null-terminated!
     * Tail contains an non-canonical, absolute or relative path
     * to append on to head while keeping head canonical.
     */
    headend = head + strlen(argv[1]);
    strcpy(head, argv[1]);
    tailbeg = LIM(tail) - strlen(argv[2]);
    strcpy(tailbeg, argv[2]);

#ifdef DOTILDE
    /*
     * Attempt to get a home directory if necessary.
     * Normally this will be done by csh itself
     */
    if (tailbeg[0] == '~') {
	name = ++tailbeg;
	while (*tailbeg && *tailbeg != '/')
	    tailbeg++;
	while (*tailbeg == '/')
	    *tailbeg++ = '\0';
	if (*name == '\0')
	    cp = getenv("HOME");
	else
	    cp = (pw = getpwnam(name)) ? pw->pw_dir : NULL;
	if (cp) {
	    strcpy(head, cp);
	    headend = head + strlen(head);
	} else
	    panic(name - 1, "cannot substitute", 0);
    }
#endif

    while (tailbeg < LIM(tail)) {

	/* Consistency check */
	if (tailbeg[0] == '\0')
	    panic(head, "botch", 0);
	if (tailbeg[0] == '/') {
	    /* Absolute pathname */
	    head[0] = '/';
	    headend = head + 1;
	    tailbeg++;
	} else if (tailbeg[0] == '.' &&
		   (tailbeg[1] == '\0' || tailbeg[1] == '/'))
	    /* .  - just skip it */
	    tailbeg++;
	else if (tailbeg[0] == '.' && tailbeg[1] == '.' &&
		 (tailbeg[2] == '\0' || tailbeg[2] == '/')) {
	    /* .. */
	    if (headend == head + 1)
		/* This was /.. - skip it */
		tailbeg += 2;
	    else {
		/* Make head into string, then back up over last element */
		*headend = '\0';
		while (headend > &head[1] && *--headend != '/')
		    /* void */ ;
		/* See if head was a symbolic link */
		if (lstat(head, &stbuf) != 0)
		    panic(head, "lstat", errno);
		if ((stbuf.st_mode & S_IFMT) == S_IFLNK) {
		    /* Prepend the symbolic link to tail */
		    *--tailbeg = '/';
		    if ((tailbeg -= stbuf.st_size) < tail)
			panic(head, "tail length exceeded", 0);
		    if (++loop > 20)
			panic(head, "loop count exceeded", 0);
		    if (readlink(head, tailbeg, stbuf.st_size) != stbuf.st_size)
			panic(head, "readlink", errno);
		    /* Go back and check for absolute vs. relative */
		    continue;
		} else
		    /* Skip .. */
		    tailbeg += 2;
	    }
	} else {
	    /* copy element to head */
	    if (headend > &head[1])
		*headend++ = '/';
	    while (*tailbeg && *tailbeg != '/')
		*headend++ = *tailbeg++;
	    if (headend > LIM(head))
		panic(head, "path length exceeded", 0);
	}
	/* Remove redundant slashes */
	while(*tailbeg == '/')
	    tailbeg++;
    }
    *headend = '\0';
    printf("%s\n", head);
    exit(0);
}