--- 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;