getcwd.c revision 1573
11638Srgrimes/*
21638Srgrimes * Copyright (c) 1989, 1991, 1993
31638Srgrimes *	The Regents of the University of California.  All rights reserved.
41638Srgrimes *
51638Srgrimes * Redistribution and use in source and binary forms, with or without
61638Srgrimes * modification, are permitted provided that the following conditions
71638Srgrimes * are met:
81638Srgrimes * 1. Redistributions of source code must retain the above copyright
91638Srgrimes *    notice, this list of conditions and the following disclaimer.
101638Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
111638Srgrimes *    notice, this list of conditions and the following disclaimer in the
121638Srgrimes *    documentation and/or other materials provided with the distribution.
131638Srgrimes * 3. All advertising materials mentioning features or use of this software
141638Srgrimes *    must display the following acknowledgement:
151638Srgrimes *	This product includes software developed by the University of
161638Srgrimes *	California, Berkeley and its contributors.
171638Srgrimes * 4. Neither the name of the University nor the names of its contributors
181638Srgrimes *    may be used to endorse or promote products derived from this software
191638Srgrimes *    without specific prior written permission.
201638Srgrimes *
211638Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
221638Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
231638Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
241638Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
251638Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
261638Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
271638Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
281638Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
291638Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
301638Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
311638Srgrimes * SUCH DAMAGE.
321638Srgrimes */
3350476Speter
341638Srgrimes#if defined(LIBC_SCCS) && !defined(lint)
351638Srgrimesstatic char sccsid[] = "@(#)getcwd.c	8.1 (Berkeley) 6/4/93";
361638Srgrimes#endif /* LIBC_SCCS and not lint */
3779538Sru
381638Srgrimes#include <sys/param.h>
391638Srgrimes#include <sys/stat.h>
401638Srgrimes
411638Srgrimes#include <errno.h>
421638Srgrimes#include <dirent.h>
43131530Sru#include <stdio.h>
44131530Sru#include <stdlib.h>
451638Srgrimes#include <string.h>
461638Srgrimes#include <unistd.h>
471638Srgrimes
48131530Sru#define	ISDOT(dp) \
49131530Sru	(dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || \
501638Srgrimes	    dp->d_name[1] == '.' && dp->d_name[2] == '\0'))
511638Srgrimes
521638Srgrimeschar *
531638Srgrimesgetcwd(pt, size)
541638Srgrimes	char *pt;
551638Srgrimes	size_t size;
561638Srgrimes{
571638Srgrimes	register struct dirent *dp;
581638Srgrimes	register DIR *dir;
591638Srgrimes	register dev_t dev;
601638Srgrimes	register ino_t ino;
611638Srgrimes	register int first;
62131530Sru	register char *bpt, *bup;
63131530Sru	struct stat s;
641638Srgrimes	dev_t root_dev;
651638Srgrimes	ino_t root_ino;
661638Srgrimes	size_t ptsize, upsize;
671638Srgrimes	int save_errno;
681638Srgrimes	char *ept, *eup, *up;
691638Srgrimes
70131530Sru	/*
71131530Sru	 * If no buffer specified by the user, allocate one as necessary.
721638Srgrimes	 * If a buffer is specified, the size has to be non-zero.  The path
731638Srgrimes	 * is built from the end of the buffer backwards.
741638Srgrimes	 */
751638Srgrimes	if (pt) {
761638Srgrimes		ptsize = 0;
771638Srgrimes		if (!size) {
781638Srgrimes			errno = EINVAL;
791638Srgrimes			return (NULL);
801638Srgrimes		}
811638Srgrimes		ept = pt + size;
821638Srgrimes	} else {
831638Srgrimes		if ((pt = malloc(ptsize = 1024 - 4)) == NULL)
841638Srgrimes			return (NULL);
851638Srgrimes		ept = pt + ptsize;
861638Srgrimes	}
871638Srgrimes	bpt = ept - 1;
881638Srgrimes	*bpt = '\0';
891638Srgrimes
901638Srgrimes	/*
911638Srgrimes	 * Allocate bytes (1024 - malloc space) for the string of "../"'s.
921638Srgrimes	 * Should always be enough (it's 340 levels).  If it's not, allocate
931638Srgrimes	 * as necessary.  Special * case the first stat, it's ".", not "..".
941638Srgrimes	 */
951638Srgrimes	if ((up = malloc(upsize = 1024 - 4)) == NULL)
961638Srgrimes		goto err;
971638Srgrimes	eup = up + MAXPATHLEN;
98131530Sru	bup = up;
99131530Sru	up[0] = '.';
1001638Srgrimes	up[1] = '\0';
1011638Srgrimes
1021638Srgrimes	/* Save root values, so know when to stop. */
1031638Srgrimes	if (stat("/", &s))
104131530Sru		goto err;
105131530Sru	root_dev = s.st_dev;
1061638Srgrimes	root_ino = s.st_ino;
107131530Sru
108131530Sru	errno = 0;			/* XXX readdir has no error return. */
1091638Srgrimes
1101638Srgrimes	for (first = 1;; first = 0) {
1111638Srgrimes		/* Stat the current level. */
1121638Srgrimes		if (lstat(up, &s))
1131638Srgrimes			goto err;
114131530Sru
115131530Sru		/* Save current node values. */
1161638Srgrimes		ino = s.st_ino;
1171638Srgrimes		dev = s.st_dev;
1181638Srgrimes
119131530Sru		/* Check for reaching root. */
120131530Sru		if (root_dev == dev && root_ino == ino) {
1211638Srgrimes			*--bpt = '/';
1221638Srgrimes			/*
1231638Srgrimes			 * It's unclear that it's a requirement to copy the
1241638Srgrimes			 * path to the beginning of the buffer, but it's always
1251638Srgrimes			 * been that way and stuff would probably break.
1261638Srgrimes			 */
1271638Srgrimes			(void)bcopy(bpt, pt, ept - bpt);
1281638Srgrimes			free(up);
1291638Srgrimes			return (pt);
1301638Srgrimes		}
1311638Srgrimes
1321638Srgrimes		/*
1331638Srgrimes		 * Build pointer to the parent directory, allocating memory
1341638Srgrimes		 * as necessary.  Max length is 3 for "../", the largest
1351638Srgrimes		 * possible component name, plus a trailing NULL.
1361638Srgrimes		 */
1371638Srgrimes		if (bup + 3  + MAXNAMLEN + 1 >= eup) {
1381638Srgrimes			if ((up = realloc(up, upsize *= 2)) == NULL)
1391638Srgrimes				goto err;
140131530Sru			bup = up;
141131530Sru			eup = up + upsize;
1421638Srgrimes		}
1431638Srgrimes		*bup++ = '.';
1441638Srgrimes		*bup++ = '.';
14520920Swosch		*bup = '\0';
14620920Swosch
14720920Swosch		/* Open and stat parent directory. */
14820920Swosch		if (!(dir = opendir(up)) || fstat(dirfd(dir), &s))
14920920Swosch			goto err;
15020920Swosch
1511638Srgrimes		/* Add trailing slash for next directory. */
1521638Srgrimes		*bup++ = '/';
15320243Smpp
15420243Smpp		/*
1551638Srgrimes		 * If it's a mount point, have to stat each element because
1561638Srgrimes		 * the inode number in the directory is for the entry in the
1571638Srgrimes		 * parent directory, not the inode number of the mounted file.
1581638Srgrimes		 */
1591638Srgrimes		save_errno = 0;
1601638Srgrimes		if (s.st_dev == dev) {
1611638Srgrimes			for (;;) {
1621638Srgrimes				if (!(dp = readdir(dir)))
163					goto notfound;
164				if (dp->d_fileno == ino)
165					break;
166			}
167		} else
168			for (;;) {
169				if (!(dp = readdir(dir)))
170					goto notfound;
171				if (ISDOT(dp))
172					continue;
173				bcopy(dp->d_name, bup, dp->d_namlen + 1);
174
175				/* Save the first error for later. */
176				if (lstat(up, &s)) {
177					if (!save_errno)
178						save_errno = errno;
179					errno = 0;
180					continue;
181				}
182				if (s.st_dev == dev && s.st_ino == ino)
183					break;
184			}
185
186		/*
187		 * Check for length of the current name, preceding slash,
188		 * leading slash.
189		 */
190		if (bpt - pt <= dp->d_namlen + (first ? 1 : 2)) {
191			size_t len, off;
192
193			if (!ptsize) {
194				errno = ERANGE;
195				goto err;
196			}
197			off = bpt - pt;
198			len = ept - bpt;
199			if ((pt = realloc(pt, ptsize *= 2)) == NULL)
200				goto err;
201			bpt = pt + off;
202			ept = pt + ptsize;
203			(void)bcopy(bpt, ept - len, len);
204			bpt = ept - len;
205		}
206		if (!first)
207			*--bpt = '/';
208		bpt -= dp->d_namlen;
209		bcopy(dp->d_name, bpt, dp->d_namlen);
210		(void)closedir(dir);
211
212		/* Truncate any file name. */
213		*bup = '\0';
214	}
215
216notfound:
217	/*
218	 * If readdir set errno, use it, not any saved error; otherwise,
219	 * didn't find the current directory in its parent directory, set
220	 * errno to ENOENT.
221	 */
222	if (!errno)
223		errno = save_errno ? save_errno : ENOENT;
224	/* FALLTHROUGH */
225err:
226	if (ptsize)
227		free(pt);
228	free(up);
229	return (NULL);
230}
231