macro and text revision (-mdoc version 3)
[unix-history] / usr / src / lib / libc / gen / getcwd.c
index 3e2c41f..681fb1b 100644 (file)
 /*
 /*
- * Copyright (c) 1980 Regents of the University of California.
- * All rights reserved.  The Berkeley software License Agreement
- * specifies the terms and conditions for redistribution.
+ * Copyright (c) 1989, 1991 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * %sccs.include.redist.c%
  */
 
 #if defined(LIBC_SCCS) && !defined(lint)
  */
 
 #if defined(LIBC_SCCS) && !defined(lint)
-static char sccsid[] = "@(#)getcwd.c   5.2 (Berkeley) %G%";
-#endif LIBC_SCCS and not lint
+static char sccsid[] = "@(#)getcwd.c   5.11 (Berkeley) %G%";
+#endif /* LIBC_SCCS and not lint */
 
 
-/*
- * getwd() returns the pathname of the current working directory. On error
- * an error message is copied to pathname and null pointer is returned.
- */
 #include <sys/param.h>
 #include <sys/stat.h>
 #include <sys/param.h>
 #include <sys/stat.h>
-#include <sys/dir.h>
+#include <errno.h>
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
 
 
-#define GETWDERR(s)    strcpy(pathname, (s));
-
-char *strcpy();
-static int pathsize;                   /* pathname length */
+#define        ISDOT(dp) \
+       (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || \
+           dp->d_name[1] == '.' && dp->d_name[2] == '\0'))
 
 char *
 
 char *
-getwd(pathname)
-       char *pathname;
+getcwd(pt, size)
+       char *pt;
+       size_t size;
 {
 {
-       char pathbuf[MAXPATHLEN];               /* temporary pathname buffer */
-       char *pnptr = &pathbuf[(sizeof pathbuf)-1]; /* pathname pointer */
-       char curdir[MAXPATHLEN];        /* current directory buffer */
-       char *dptr = curdir;            /* directory pointer */
-       char *prepend();                /* prepend dirname to pathname */
-       dev_t cdev, rdev;               /* current & root device number */
-       ino_t cino, rino;               /* current & root inode number */
-       DIR *dirp;                      /* directory stream */
-       struct direct *dir;             /* directory entry struct */
-       struct stat d, dd;              /* file status struct */
-
-       pathsize = 0;
-       *pnptr = '\0';
-       if (stat("/", &d) < 0) {
-               GETWDERR("getwd: can't stat /");
-               return (NULL);
-       }
-       rdev = d.st_dev;
-       rino = d.st_ino;
-       strcpy(dptr, "./");
-       dptr += 2;
-       if (stat(curdir, &d) < 0) {
-               GETWDERR("getwd: can't stat .");
-               return (NULL);
+       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((char *)NULL);
+               }
+               ept = pt + size;
+       } else {
+               if (!(pt = (char *)malloc(ptsize = 1024 - 4)))
+                       return((char *)NULL);
+               ept = pt + ptsize;
        }
        }
-       for (;;) {
-               if (d.st_ino == rino && d.st_dev == rdev)
-                       break;          /* reached root directory */
-               cino = d.st_ino;
-               cdev = d.st_dev;
-               strcpy(dptr, "../");
-               dptr += 3;
-               if ((dirp = opendir(curdir)) == NULL) {
-                       GETWDERR("getwd: can't open ..");
-                       return (NULL);
+       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 = (char *)malloc(upsize = 1024 - 4)))
+               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);
                }
                }
-               fstat(dirp->dd_fd, &d);
-               if (cdev == d.st_dev) {
-                       if (cino == d.st_ino) {
-                               /* reached root directory */
-                               closedir(dirp);
-                               break;
+
+               /*
+                * 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 = (char *)realloc(up, upsize *= 2)))
+                               goto err;
+                       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;
                        }
                        }
-                       do {
-                               if ((dir = readdir(dirp)) == NULL) {
-                                       closedir(dirp);
-                                       GETWDERR("getwd: read error in ..");
-                                       return (NULL);
-                               }
-                       } while (dir->d_ino != cino);
                } else
                } else
-                       do {
-                               if ((dir = readdir(dirp)) == NULL) {
-                                       closedir(dirp);
-                                       GETWDERR("getwd: read error in ..");
-                                       return (NULL);
+                       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;
                                }
                                }
-                               strcpy(dptr, dir->d_name);
-                               lstat(curdir, &dd);
-                       } while(dd.st_ino != cino || dd.st_dev != cdev);
-               closedir(dirp);
-               pnptr = prepend("/", prepend(dir->d_name, pnptr));
+                               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 = (char *)realloc(pt, ptsize *= 2)))
+                               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';
        }
        }
-       if (*pnptr == '\0')             /* current dir == root dir */
-               strcpy(pathname, "/");
-       else
-               strcpy(pathname, pnptr);
-       return (pathname);
-}
 
 
-/*
- * prepend() tacks a directory name onto the front of a pathname.
- */
-static char *
-prepend(dirname, pathname)
-       register char *dirname;
-       register char *pathname;
-{
-       register int i;                 /* directory name size counter */
-
-       for (i = 0; *dirname != '\0'; i++, dirname++)
-               continue;
-       if ((pathsize += i) < MAXPATHLEN)
-               while (i-- > 0)
-                       *--pathname = *--dirname;
-       return (pathname);
+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((char *)NULL);
 }
 }