/* * Copyright (c) 1989, 1991, 1993, 1995 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Jan-Simon Pendry. * * %sccs.include.redist.c% */ #if defined(LIBC_SCCS) && !defined(lint) static char sccsid[] = "@(#)getcwd.c 8.5 (Berkeley) 02/07/95"; #endif /* LIBC_SCCS and not lint */ #include #include #include #include #include #include #include #include #include static char *getcwd_physical __P((char *, size_t)); #define ISDOT(dp) \ (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || \ dp->d_name[1] == '.' && dp->d_name[2] == '\0')) char * getcwd(pt, size) char *pt; size_t size; { char *pwd; size_t pwdlen; dev_t dev; ino_t ino; struct stat s; /* Check $PWD -- if it's right, it's fast. */ if ((pwd = getenv("PWD")) != NULL && pwd[0] == '/' && !stat(pwd, &s)) { dev = s.st_dev; ino = s.st_ino; if (!stat(".", &s) && dev == s.st_dev && ino == s.st_ino) { pwdlen = strlen(pwd); if (size != 0) { if (pwdlen + 1 > size) { errno = ERANGE; return (NULL); } } else if ((pt = malloc(pwdlen + 1)) == NULL) return (NULL); memmove(pt, pwd, pwdlen); pt[pwdlen] = '\0'; return (pt); } } return (getcwd_physical(pt, size)); } /* * char *realpath(const char *path, char resolved_path[MAXPATHLEN]); * * Find the real name of path, by removing all ".", ".." and symlink * components. Returns (resolved) on success, or (NULL) on failure, * in which case the path which caused trouble is left in (resolved). */ char * realpath(path, resolved) const char *path; char *resolved; { struct stat sb; int fd, n, rootd, serrno; char *p, *q, wbuf[MAXPATHLEN]; /* Save the starting point. */ if ((fd = open(".", O_RDONLY)) < 0) { (void)strcpy(resolved, "."); return (NULL); } /* * Find the dirname and basename from the path to be resolved. * Change directory to the dirname component. * lstat the basename part. * if it is a symlink, read in the value and loop. * if it is a directory, then change to that directory. * get the current directory name and append the basename. */ (void)strncpy(resolved, path, MAXPATHLEN - 1); resolved[MAXPATHLEN - 1] = '\0'; loop: q = strrchr(resolved, '/'); if (q != NULL) { p = q + 1; if (q == resolved) q = "/"; else { do { --q; } while (q > resolved && *q == '/'); q[1] = '\0'; q = resolved; } if (chdir(q) < 0) goto err1; } else p = resolved; /* Deal with the last component. */ if (lstat(p, &sb) == 0) { if (S_ISLNK(sb.st_mode)) { n = readlink(p, resolved, MAXPATHLEN); if (n < 0) goto err1; resolved[n] = '\0'; goto loop; } if (S_ISDIR(sb.st_mode)) { if (chdir(p) < 0) goto err1; p = ""; } } /* * Save the last component name and get the full pathname of * the current directory. */ (void)strcpy(wbuf, p); /* * Call the inernal internal version of getcwd which * does a physical search rather than using the $PWD short-cut */ if (getcwd_physical(resolved, MAXPATHLEN) == 0) goto err1; /* * Join the two strings together, ensuring that the right thing * happens if the last component is empty, or the dirname is root. */ if (resolved[0] == '/' && resolved[1] == '\0') rootd = 1; else rootd = 0; if (*wbuf) { if (strlen(resolved) + strlen(wbuf) + rootd + 1 > MAXPATHLEN) { errno = ENAMETOOLONG; goto err1; } if (rootd == 0) (void)strcat(resolved, "/"); (void)strcat(resolved, wbuf); } /* Go back to where we came from. */ if (fchdir(fd) < 0) { serrno = errno; goto err2; } /* It's okay if the close fails, what's an fd more or less? */ (void)close(fd); return (resolved); err1: serrno = errno; (void)fchdir(fd); err2: (void)close(fd); errno = serrno; return (NULL); } static char * getcwd_physical(pt, size) char *pt; size_t size; { register struct dirent *dp; register DIR *dir; register dev_t dev; register ino_t ino; register int first; register char *bpt, *bup; struct stat s; dev_t root_dev; ino_t root_ino; size_t ptsize, upsize; int save_errno; char *ept, *eup, *up; /* * If no buffer specified by the user, allocate one as necessary. * If a buffer is specified, the size has to be non-zero. The path * is built from the end of the buffer backwards. */ if (pt) { ptsize = 0; if (!size) { errno = EINVAL; return (NULL); } ept = pt + size; } else { if ((pt = malloc(ptsize = 1024 - 4)) == NULL) return (NULL); ept = pt + ptsize; } bpt = ept - 1; *bpt = '\0'; /* * Allocate bytes (1024 - malloc space) for the string of "../"'s. * Should always be enough (it's 340 levels). If it's not, allocate * as necessary. Special case the first stat, it's ".", not "..". */ if ((up = malloc(upsize = 1024 - 4)) == NULL) goto err; eup = up + MAXPATHLEN; bup = up; up[0] = '.'; up[1] = '\0'; /* Save root values, so know when to stop. */ if (stat("/", &s)) goto err; root_dev = s.st_dev; root_ino = s.st_ino; errno = 0; /* XXX readdir has no error return. */ for (first = 1;; first = 0) { /* Stat the current level. */ if (lstat(up, &s)) goto err; /* Save current node values. */ ino = s.st_ino; dev = s.st_dev; /* Check for reaching root. */ if (root_dev == dev && root_ino == ino) { *--bpt = '/'; /* * It's unclear that it's a requirement to copy the * path to the beginning of the buffer, but it's always * been that way and stuff would probably break. */ (void)bcopy(bpt, pt, ept - bpt); free(up); return (pt); } /* * Build pointer to the parent directory, allocating memory * as necessary. Max length is 3 for "../", the largest * possible component name, plus a trailing NULL. */ if (bup + 3 + MAXNAMLEN + 1 >= eup) { if ((up = realloc(up, upsize *= 2)) == NULL) goto err; bup = up; eup = up + upsize; } *bup++ = '.'; *bup++ = '.'; *bup = '\0'; /* Open and stat parent directory. */ if (!(dir = opendir(up)) || fstat(dirfd(dir), &s)) goto err; /* Add trailing slash for next directory. */ *bup++ = '/'; /* * If it's a mount point, have to stat each element because * the inode number in the directory is for the entry in the * parent directory, not the inode number of the mounted file. */ save_errno = 0; if (s.st_dev == dev) { for (;;) { if (!(dp = readdir(dir))) goto notfound; if (dp->d_fileno == ino) break; } } else for (;;) { if (!(dp = readdir(dir))) goto notfound; if (ISDOT(dp)) continue; bcopy(dp->d_name, bup, dp->d_namlen + 1); /* Save the first error for later. */ if (lstat(up, &s)) { if (!save_errno) save_errno = errno; errno = 0; continue; } if (s.st_dev == dev && s.st_ino == ino) break; } /* * Check for length of the current name, preceding slash, * leading slash. */ if (bpt - pt <= dp->d_namlen + (first ? 1 : 2)) { size_t len, off; if (!ptsize) { errno = ERANGE; goto err; } off = bpt - pt; len = ept - bpt; if ((pt = realloc(pt, ptsize *= 2)) == NULL) goto err; bpt = pt + off; ept = pt + ptsize; (void)bcopy(bpt, ept - len, len); bpt = ept - len; } if (!first) *--bpt = '/'; bpt -= dp->d_namlen; bcopy(dp->d_name, bpt, dp->d_namlen); (void)closedir(dir); /* Truncate any file name. */ *bup = '\0'; } notfound: /* * If readdir set errno, use it, not any saved error; otherwise, * didn't find the current directory in its parent directory, set * errno to ENOENT. */ if (!errno) errno = save_errno ? save_errno : ENOENT; /* FALLTHROUGH */ err: if (ptsize) free(pt); free(up); return (NULL); }