macro and text revision (-mdoc version 3)
[unix-history] / usr / src / lib / libc / gen / getcwd.c
index 6cfdbcb..681fb1b 100644 (file)
-/*     @(#)getcwd.c    4.5     (Berkeley)      %G%     */
-
 /*
 /*
- * Getwd
+ * Copyright (c) 1989, 1991 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * %sccs.include.redist.c%
  */
  */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+static char sccsid[] = "@(#)getcwd.c   5.11 (Berkeley) %G%";
+#endif /* LIBC_SCCS and not lint */
+
 #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        dot     "."
-#define        dotdot  ".."
+#define        ISDOT(dp) \
+       (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || \
+           dp->d_name[1] == '.' && dp->d_name[2] == '\0'))
 
 
-#define        prexit(s)       { strcpy(np, s); return (NULL); }
+char *
+getcwd(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;
 
 
-static char *name;
+       /*
+        * 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;
+       }
+       bpt = ept - 1;
+       *bpt = '\0';
 
 
-static DIR *file;
-static int off;
-static struct stat d, dd;
-static struct direct *dir;
+       /*
+        * 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';
 
 
-char *
-getwd(np)
-       char *np;
-{
-       dev_t rdev;
-       ino_t rino;
-
-       off = -1;
-       *np++ = '/';
-       name = np;
-       stat("/", &d);
-       rdev = d.st_dev;
-       rino = d.st_ino;
-       for (;;) {
-               stat(dot, &d);
-               if (d.st_ino==rino && d.st_dev==rdev)
-                       goto done;
-               if ((file = opendir(dotdot)) == NULL)
-                       prexit("getwd: cannot open ..");
-               fstat(file->dd_fd, &dd);
-               if (chdir(dotdot) < 0)
-                       prexit("getwd: cannot chdir to ..");
-               if (d.st_dev == dd.st_dev) {
-                       if(d.st_ino == dd.st_ino)
-                               goto done;
-                       do
-                               if ((dir = readdir(file)) == NULL)
-                                       prexit("getwd: read error in ..");
-                       while (dir->d_ino != d.st_ino);
+       /* 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 = (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;
+                       }
                } else
                } else
-                       do {
-                               if ((dir = readdir(file)) == NULL)
-                                       prexit("getwd: read error in ..");
-                               stat(dir->d_name, &dd);
-                       } while(dd.st_ino != d.st_ino || dd.st_dev != d.st_dev);
-               closedir(file);
-               cat();
+                       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 = (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';
        }
        }
-done:
-       name--;
-       if (chdir(name) < 0)
-               prexit("getwd: can't change back");
-       return (name);
-}
 
 
-cat()
-{
-       register i, j;
-
-       i = -1;
-       while (dir->d_name[++i] != 0);
-       if ((off+i+2) > 1024-1)
-               return;
-       for(j=off+1; j>=0; --j)
-               name[j+i+1] = name[j];
-       if (off >= 0)
-               name[i] = '/';
-       off=i+off+1;
-       name[off] = 0;
-       for(--i; i>=0; --i)
-               name[i] = dir->d_name[i];
+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);
 }
 }