xref: /csrg-svn/lib/libc/gen/getcwd.c (revision 68219)
1 /*
2  * Copyright (c) 1989, 1991, 1993, 1995
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * %sccs.include.redist.c%
6  */
7 
8 #if defined(LIBC_SCCS) && !defined(lint)
9 static char sccsid[] = "@(#)getcwd.c	8.3 (Berkeley) 02/02/95";
10 #endif /* LIBC_SCCS and not lint */
11 
12 #include <sys/param.h>
13 #include <sys/stat.h>
14 
15 #include <errno.h>
16 #include <dirent.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <unistd.h>
21 
22 #define	ISDOT(dp) \
23 	(dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || \
24 	    dp->d_name[1] == '.' && dp->d_name[2] == '\0'))
25 
26 char *
27 getcwd(pt, size)
28 	char *pt;
29 	size_t size;
30 {
31 	register struct dirent *dp;
32 	register DIR *dir;
33 	register dev_t dev;
34 	register ino_t ino;
35 	register int first;
36 	register char *bpt, *bup;
37 	struct stat s;
38 	dev_t root_dev;
39 	ino_t root_ino;
40 	size_t ptsize, pwdlen, upsize;
41 	int save_errno;
42 	char *ept, *eup, *pwd, *up;
43 
44 	/* Check $PWD -- if it's right, it's fast. */
45 	if ((pwd = getenv("PWD")) != NULL && pwd[0] == '/' && !stat(pwd, &s)) {
46 		dev = s.st_dev;
47 		ino = s.st_ino;
48 		if (!stat(".", &s) && dev == s.st_dev && ino == s.st_ino) {
49 			pwdlen = strlen(pwd);
50 			if (size != 0) {
51 				if (pwdlen + 1 > size) {
52 					errno = ERANGE;
53 					return (NULL);
54 				}
55 			} else if ((pt = malloc(pwdlen + 1)) == NULL)
56 				return (NULL);
57 			memmove(pt, pwd, pwdlen);
58 			pwd[pwdlen] = '\0';
59 			return (pt);
60 		}
61 	}
62 
63 	/*
64 	 * If no buffer specified by the user, allocate one as necessary.
65 	 * If a buffer is specified, the size has to be non-zero.  The path
66 	 * is built from the end of the buffer backwards.
67 	 */
68 	if (pt) {
69 		ptsize = 0;
70 		if (!size) {
71 			errno = EINVAL;
72 			return (NULL);
73 		}
74 		ept = pt + size;
75 	} else {
76 		if ((pt = malloc(ptsize = 1024 - 4)) == NULL)
77 			return (NULL);
78 		ept = pt + ptsize;
79 	}
80 	bpt = ept - 1;
81 	*bpt = '\0';
82 
83 	/*
84 	 * Allocate bytes (1024 - malloc space) for the string of "../"'s.
85 	 * Should always be enough (it's 340 levels).  If it's not, allocate
86 	 * as necessary.  Special case the first stat, it's ".", not "..".
87 	 */
88 	if ((up = malloc(upsize = 1024 - 4)) == NULL)
89 		goto err;
90 	eup = up + MAXPATHLEN;
91 	bup = up;
92 	up[0] = '.';
93 	up[1] = '\0';
94 
95 	/* Save root values, so know when to stop. */
96 	if (stat("/", &s))
97 		goto err;
98 	root_dev = s.st_dev;
99 	root_ino = s.st_ino;
100 
101 	errno = 0;			/* XXX readdir has no error return. */
102 
103 	for (first = 1;; first = 0) {
104 		/* Stat the current level. */
105 		if (lstat(up, &s))
106 			goto err;
107 
108 		/* Save current node values. */
109 		ino = s.st_ino;
110 		dev = s.st_dev;
111 
112 		/* Check for reaching root. */
113 		if (root_dev == dev && root_ino == ino) {
114 			*--bpt = '/';
115 			/*
116 			 * It's unclear that it's a requirement to copy the
117 			 * path to the beginning of the buffer, but it's always
118 			 * been that way and stuff would probably break.
119 			 */
120 			(void)bcopy(bpt, pt, ept - bpt);
121 			free(up);
122 			return (pt);
123 		}
124 
125 		/*
126 		 * Build pointer to the parent directory, allocating memory
127 		 * as necessary.  Max length is 3 for "../", the largest
128 		 * possible component name, plus a trailing NULL.
129 		 */
130 		if (bup + 3  + MAXNAMLEN + 1 >= eup) {
131 			if ((up = realloc(up, upsize *= 2)) == NULL)
132 				goto err;
133 			bup = up;
134 			eup = up + upsize;
135 		}
136 		*bup++ = '.';
137 		*bup++ = '.';
138 		*bup = '\0';
139 
140 		/* Open and stat parent directory. */
141 		if (!(dir = opendir(up)) || fstat(dirfd(dir), &s))
142 			goto err;
143 
144 		/* Add trailing slash for next directory. */
145 		*bup++ = '/';
146 
147 		/*
148 		 * If it's a mount point, have to stat each element because
149 		 * the inode number in the directory is for the entry in the
150 		 * parent directory, not the inode number of the mounted file.
151 		 */
152 		save_errno = 0;
153 		if (s.st_dev == dev) {
154 			for (;;) {
155 				if (!(dp = readdir(dir)))
156 					goto notfound;
157 				if (dp->d_fileno == ino)
158 					break;
159 			}
160 		} else
161 			for (;;) {
162 				if (!(dp = readdir(dir)))
163 					goto notfound;
164 				if (ISDOT(dp))
165 					continue;
166 				bcopy(dp->d_name, bup, dp->d_namlen + 1);
167 
168 				/* Save the first error for later. */
169 				if (lstat(up, &s)) {
170 					if (!save_errno)
171 						save_errno = errno;
172 					errno = 0;
173 					continue;
174 				}
175 				if (s.st_dev == dev && s.st_ino == ino)
176 					break;
177 			}
178 
179 		/*
180 		 * Check for length of the current name, preceding slash,
181 		 * leading slash.
182 		 */
183 		if (bpt - pt <= dp->d_namlen + (first ? 1 : 2)) {
184 			size_t len, off;
185 
186 			if (!ptsize) {
187 				errno = ERANGE;
188 				goto err;
189 			}
190 			off = bpt - pt;
191 			len = ept - bpt;
192 			if ((pt = realloc(pt, ptsize *= 2)) == NULL)
193 				goto err;
194 			bpt = pt + off;
195 			ept = pt + ptsize;
196 			(void)bcopy(bpt, ept - len, len);
197 			bpt = ept - len;
198 		}
199 		if (!first)
200 			*--bpt = '/';
201 		bpt -= dp->d_namlen;
202 		bcopy(dp->d_name, bpt, dp->d_namlen);
203 		(void)closedir(dir);
204 
205 		/* Truncate any file name. */
206 		*bup = '\0';
207 	}
208 
209 notfound:
210 	/*
211 	 * If readdir set errno, use it, not any saved error; otherwise,
212 	 * didn't find the current directory in its parent directory, set
213 	 * errno to ENOENT.
214 	 */
215 	if (!errno)
216 		errno = save_errno ? save_errno : ENOENT;
217 	/* FALLTHROUGH */
218 err:
219 	if (ptsize)
220 		free(pt);
221 	free(up);
222 	return (NULL);
223 }
224