]> granicus.if.org Git - sudo/commitdiff
getcwd(3) from OpenBSD for those without it.
authorTodd C. Miller <Todd.Miller@courtesan.com>
Mon, 7 Sep 1998 02:55:14 +0000 (02:55 +0000)
committerTodd C. Miller <Todd.Miller@courtesan.com>
Mon, 7 Sep 1998 02:55:14 +0000 (02:55 +0000)
getcwd.c

index b9a7f5f6205f49dcf3d871d7c7c64a431b1cf671..d1f87f84773ab1f5208ddbe87e25bbd237569e3c 100644 (file)
--- a/getcwd.c
+++ b/getcwd.c
@@ -1,45 +1,50 @@
 /*
- *  CU sudo version 1.5.6
+ * Copyright (c) 1989, 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
  *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 1, or (at your option)
- *  any later version.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
  *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- *
- *  Please send bugs, changes, problems to sudo-bugs@courtesan.com
- *
- *******************************************************************
- *
- *  This module contains getcwd(3) for those systems that lack it.
- *  getcwd(3) returns a pointer to the current working dir.  It uses
- *  path as a copy-out parameter and malloc(3)s space if path is NULL.
- *  This implementation of getcwd(3) restricts len(path) to be < MAXPATHLEN.
- *
- *  Todd C. Miller (millert@colorado.edu) Fri Jun  3 18:32:19 MDT 1994
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
  */
 
-#ifndef lint
-static char rcsid[] = "$Id$";
-#endif /* lint */
+#if defined(LIBC_SCCS) && !defined(lint)
+static char rcsid[] = "$OpenBSD: getcwd.c,v 1.5 1998/08/14 21:39:26 deraadt Exp $";
+#endif /* LIBC_SCCS and not lint */
 
 #include "config.h"
 
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#include <errno.h>
 #include <stdio.h>
 #ifdef STDC_HEADERS
 #include <stdlib.h>
 #endif /* STDC_HEADERS */
-#ifdef HAVE_UNISTD_H
-#include <unistd.h>
-#endif /* HAVE_UNISTD_H */
 #ifdef HAVE_STRING_H
 #include <string.h>
 #endif /* HAVE_STRING_H */
@@ -47,118 +52,218 @@ static char rcsid[] = "$Id$";
 #include <strings.h>
 #endif /* HAVE_STRINGS_H */
 #if defined(HAVE_MALLOC_H) && !defined(STDC_HEADERS)
-#  include <malloc.h>
+#include <malloc.h>   
 #endif /* HAVE_MALLOC_H && !STDC_HEADERS */
-#include <errno.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <sys/param.h>
-#include <sys/in.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif /* HAVE_UNISTD_H */
+#if HAVE_DIRENT_H
+#  include <dirent.h>
+#  define NAMLEN(dirent) strlen((dirent)->d_name)
+#else
+#  define dirent direct
+#  define NAMLEN(dirent) (dirent)->d_namlen
+#  if HAVE_SYS_NDIR_H
+#    include <sys/ndir.h>
+#  endif
+#  if HAVE_SYS_DIR_H
+#    include <sys/dir.h>
+#  endif
+#  if HAVE_NDIR_H
+#    include <ndir.h>
+#  endif
+#endif
 
-#include <pathnames.h>
 #include "compat.h"
 
-#ifndef STDC_HEADERS
-#ifndef __GNUC__               /* gcc has its own malloc */
-extern char *malloc    __P((size_t));
-#endif /* __GNUC__ */
-extern char *strcpy    __P((char *, const char *));
-extern int   strlen    __P((const char *));
-extern char *getwd     __P((char *));
-extern char *fgets     __P((char *, int, FILE *));
-#endif /* !STDC_HEADERS */
+#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;
+{
+       struct dirent *dp;
+       DIR *dir = NULL;
+       dev_t dev;
+       ino_t ino;
+       int first;
+       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;
 
-/*
- * Since we can't count on this being defined...
- */
-extern int errno;
+       /*
+        * 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';
 
-/******************************************************************
- *
- *  getcwd()
- *
- *  getcwd() returns a pointer to the current working dir.  It uses
- *  path as a copy-out parameter and malloc(3)s space if path is NULL.
- */
+       /* Save root values, so know when to stop. */
+       if (stat("/", &s))
+               goto err;
+       root_dev = s.st_dev;
+       root_ino = s.st_ino;
 
-char * getcwd(path, psize)
-    char * path;                               /* path to copy into */
-    size_t psize;                              /* size of path */
-{
-    char buf[MAXPATHLEN+1];                    /* +1 for the newline */
-    size_t blen;                               /* length of buf */
-    FILE * pwd;                                        /* stream for "pwd" process */
-    int fd[2];                                 /* for pipe(2) */
-    pid_t pid;                                 /* pid of "pwd" process */
-    int status;                                        /* status from wait(2) */
-
-    /* sanity check */
-    if (path && psize == 0) {
-       errno = EINVAL;
-       return(NULL);
-    }
-
-    /*
-     * open a pipe to pwd and read a line
-     */
-    if (pipe(fd) < 0)
-       return(NULL);
-    switch ((pid = fork())) {
-       case -1:
-           /* fork failed */
-           (void) close(fd[0]);
-           (void) close(fd[1]);
-           return(NULL);
-       case 0:
-           /* in child */
-           (void) dup2(fd[0], 0);
-           (void) dup2(fd[1], 1);
-           (void) close(fd[0]);
-           (void) close(fd[1]);
-           execl(_PATH_PWD, "pwd", NULL);
-           _exit(-1);          /* should not happen */
-    }
-
-    /* in parent */
-    if ((pwd = fdopen(fd[0], "r")) == NULL) {
-       (void) close(fd[0]);
-       (void) close(fd[1]);
-       return(NULL);
-    }
-
-    if (!fgets(buf, sizeof(buf), pwd)) {
-       errno = EACCES;                         /* what an assumption... */
-       pclose(pwd); 
-       return(NULL);
-    }
-
-    /* wait for the pipe to close */
-    while (wait(&status) != pid)
-       ;
-
-    blen = strlen(buf);
-    if (buf[blen - 1] == '\n')
-       buf[--blen] = '\0';                     /* remove newline */
-    else if (blen >= MAXPATHLEN) {
-       errno = ENAMETOOLONG;                   /* only possible w/o newline */
-       return(NULL);
-    }
-
-    /* sanity check */
-    if (path && psize < blen + 1) {
-       errno = ERANGE;
-       return(NULL);
-    }
-
-    if (path == NULL) {
-       if (!(path = (char *) malloc(MAXPATHLEN))) {
-           errno = ENOMEM;
-           return(NULL);
+       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.
+                        */
+                       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) {
+                       char *nup;
+
+                       if ((nup = realloc(up, upsize *= 2)) == NULL)
+                               goto err;
+                       up = nup;
+                       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, NAMLEN(dp) + 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 <= NAMLEN(dp) + (first ? 1 : 2)) {
+                       size_t len, off;
+                       char *npt;
+
+                       if (!ptsize) {
+                               errno = ERANGE;
+                               goto err;
+                       }
+                       off = bpt - pt;
+                       len = ept - bpt;
+                       if ((npt = realloc(pt, ptsize *= 2)) == NULL)
+                               goto err;
+                       pt = npt;
+                       bpt = pt + off;
+                       ept = pt + ptsize;
+                       bcopy(bpt, ept - len, len);
+                       bpt = ept - len;
+               }
+               if (!first)
+                       *--bpt = '/';
+               bpt -= NAMLEN(dp);
+               bcopy(dp->d_name, bpt, NAMLEN(dp));
+               (void)closedir(dir);
+
+               /* Truncate any file name. */
+               *bup = '\0';
        }
-    }
 
-    (void) strcpy(path, buf);
-    return(path);
+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);
+       if (up)
+               free(up);
+       if (dir)
+               (void)closedir(dir);
+       return (NULL);
 }