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