getcwd.c.patch   [plain text]


--- getcwd.c.orig	2006-01-19 18:35:45.000000000 -0800
+++ getcwd.c	2006-02-08 17:53:41.000000000 -0800
@@ -54,7 +54,81 @@
 	(dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || \
 	    (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
 
-extern int __getcwd(char *, size_t);
+/*
+ * If __getcwd() ever becomes a syscall, we can remove this workaround.
+ * The problem here is that fcntl() assumes a buffer of size MAXPATHLEN,
+ * if size is less than MAXPATHLEN, we need to use a temporary buffer
+ * and see if it fits.  We also have to assume that open() or fcntl()
+ * don't fail with errno=ERANGE.
+ */
+static inline int
+__getcwd(char *buf, size_t size)
+{
+	int fd, err, save;
+	struct stat dot, pt;
+	char *b;
+
+	if ((fd = open(".", O_RDONLY)) < 0)
+		return -1;
+	if (fstat(fd, &dot) < 0) {
+		save = errno;
+		close(fd);
+		errno = save;
+		return -1;
+	}
+	/* check that the device and inode are non-zero, otherwise punt */
+	if (dot.st_dev == 0 || dot.st_ino == 0) {
+		close(fd);
+		errno = EINVAL;
+		return -1;
+	}
+	if (size < MAXPATHLEN) {
+		/* the hard case; allocate a buffer of size MAXPATHLEN to use */
+		b = (char *)alloca(MAXPATHLEN);
+		if (b == NULL) {
+			close(fd);
+			errno = ENOMEM; /* make sure it isn't ERANGE */
+			return -1;
+		}
+	} else
+		b = buf;
+
+	err = fcntl(fd, F_GETPATH, b);
+	if (err) {
+		save = errno;
+		close(fd);
+		errno = save;
+		return err;
+	}
+	close(fd);
+	/*
+	 * now double-check that the path returned by fcntl() has the same
+	 * device and inode number as '.'.
+	 */
+	if (stat(b, &pt) < 0)
+		return -1;
+	/*
+	 * Since dot.st_dev and dot.st_ino are non-zero, we don't need to
+	 * separately test for pt.st_dev and pt.st_ino being non-zero, because
+	 * they have to match
+	 */
+	if (dot.st_dev != pt.st_dev || dot.st_ino != pt.st_ino) {
+		errno = EINVAL;
+		return -1;
+	}
+	/*
+	 * For the case where we allocated a buffer, check that it can fit
+	 * in the real buffer, and copy it over.
+	 */
+	if (size < MAXPATHLEN) {
+		if (strlen(b) >= size) {
+			errno = ERANGE;
+			return -1;
+		}
+		strcpy(buf, b);
+	}
+	return 0;
+}
 
 char *
 getcwd(pt, size)
@@ -91,31 +165,23 @@
 		}
 		ept = pt + size;
 	} else {
-		if ((pt = malloc(ptsize = 1024 - 4)) == NULL)
+		if ((pt = malloc(ptsize = MAXPATHLEN)) == NULL)
 			return (NULL);
 		ept = pt + ptsize;
 	}
 	if (__getcwd(pt, ept - pt) == 0) {
-		if (*pt != '/') {
-			bpt = pt;
-			ept = pt + strlen(pt) - 1;
-			while (bpt < ept) {
-				c = *bpt;
-				*bpt++ = *ept;
-				*ept-- = c;
-			}
-		}
 		return (pt);
-	}
+	} else if (errno == ERANGE) /* failed because buffer too small */
+		return NULL;
 	bpt = ept - 1;
 	*bpt = '\0';
 
 	/*
-	 * Allocate bytes (1024 - malloc space) for the string of "../"'s.
+	 * Allocate bytes MAXPATHLEN) 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)
+	if ((up = malloc(upsize = MAXPATHLEN)) == NULL)
 		goto err;
 	eup = up + MAXPATHLEN;
 	bup = up;