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); }