1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 1996,2008 Oracle.  All rights reserved.
5 */
6/*
7 * Copyright (c) 1989, 1991, 1993
8 *	The Regents of the University of California.  All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 * 3. Neither the name of the University nor the names of its contributors
19 *    may be used to endorse or promote products derived from this software
20 *    without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
33 *
34 * $Id: getcwd.c,v 12.10 2008/01/08 20:58:07 bostic Exp $
35 */
36
37#include "db_config.h"
38
39#include "db_int.h"
40
41#ifdef HAVE_SYSTEM_INCLUDE_FILES
42#if HAVE_DIRENT_H
43# include <dirent.h>
44# define NAMLEN(dirent) strlen((dirent)->d_name)
45#else
46# define dirent direct
47# define NAMLEN(dirent) (dirent)->d_namlen
48# if HAVE_SYS_NDIR_H
49#  include <sys/ndir.h>
50# endif
51# if HAVE_SYS_DIR_H
52#  include <sys/dir.h>
53# endif
54# if HAVE_NDIR_H
55#  include <ndir.h>
56# endif
57#endif
58#endif
59
60#define	ISDOT(dp) \
61	(dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || \
62	    (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
63
64#ifndef dirfd
65#define	dirfd(dirp)     ((dirp)->dd_fd)
66#endif
67
68/*
69 * getcwd --
70 *	Get the current working directory.
71 *
72 * PUBLIC: #ifndef HAVE_GETCWD
73 * PUBLIC: char *getcwd __P((char *, size_t));
74 * PUBLIC: #endif
75 */
76char *
77getcwd(pt, size)
78	char *pt;
79	size_t size;
80{
81	register struct dirent *dp;
82	register DIR *dir;
83	register dev_t dev;
84	register ino_t ino;
85	register int first;
86	register char *bpt, *bup;
87	struct stat s;
88	dev_t root_dev;
89	ino_t root_ino;
90	size_t ptsize, upsize;
91	int ret, save_errno;
92	char *ept, *eup, *up;
93
94	/*
95	 * If no buffer specified by the user, allocate one as necessary.
96	 * If a buffer is specified, the size has to be non-zero.  The path
97	 * is built from the end of the buffer backwards.
98	 */
99	if (pt) {
100		ptsize = 0;
101		if (!size) {
102			__os_set_errno(EINVAL);
103			return (NULL);
104		}
105		if (size == 1) {
106			__os_set_errno(ERANGE);
107			return (NULL);
108		}
109		ept = pt + size;
110	} else {
111		if ((ret =
112		    __os_malloc(NULL, ptsize = 1024 - 4, &pt)) != 0) {
113			__os_set_errno(ret);
114			return (NULL);
115		}
116		ept = pt + ptsize;
117	}
118	bpt = ept - 1;
119	*bpt = '\0';
120
121	/*
122	 * Allocate bytes (1024 - malloc space) for the string of "../"'s.
123	 * Should always be enough (it's 340 levels).  If it's not, allocate
124	 * as necessary.  Special case the first stat, it's ".", not "..".
125	 */
126	if ((ret = __os_malloc(NULL, upsize = 1024 - 4, &up)) != 0)
127		goto err;
128	eup = up + 1024;
129	bup = up;
130	up[0] = '.';
131	up[1] = '\0';
132
133	/* Save root values, so know when to stop. */
134	if (stat("/", &s))
135		goto err;
136	root_dev = s.st_dev;
137	root_ino = s.st_ino;
138
139	__os_set_errno(0);		/* XXX readdir has no error return. */
140
141	for (first = 1;; first = 0) {
142		/* Stat the current level. */
143		if (lstat(up, &s))
144			goto err;
145
146		/* Save current node values. */
147		ino = s.st_ino;
148		dev = s.st_dev;
149
150		/* Check for reaching root. */
151		if (root_dev == dev && root_ino == ino) {
152			*--bpt = PATH_SEPARATOR[0];
153			/*
154			 * It's unclear that it's a requirement to copy the
155			 * path to the beginning of the buffer, but it's always
156			 * been that way and stuff would probably break.
157			 */
158			bcopy(bpt, pt, ept - bpt);
159			__os_free(NULL, up);
160			return (pt);
161		}
162
163		/*
164		 * Build pointer to the parent directory, allocating memory
165		 * as necessary.  Max length is 3 for "../", the largest
166		 * possible component name, plus a trailing NULL.
167		 */
168		if (bup + 3  + MAXNAMLEN + 1 >= eup) {
169			if (__os_realloc(NULL, upsize *= 2, &up) != 0)
170				goto err;
171			bup = up;
172			eup = up + upsize;
173		}
174		*bup++ = '.';
175		*bup++ = '.';
176		*bup = '\0';
177
178		/* Open and stat parent directory. */
179		if (!(dir = opendir(up)) || fstat(dirfd(dir), &s))
180			goto err;
181
182		/* Add trailing slash for next directory. */
183		*bup++ = PATH_SEPARATOR[0];
184
185		/*
186		 * If it's a mount point, have to stat each element because
187		 * the inode number in the directory is for the entry in the
188		 * parent directory, not the inode number of the mounted file.
189		 */
190		save_errno = 0;
191		if (s.st_dev == dev) {
192			for (;;) {
193				if (!(dp = readdir(dir)))
194					goto notfound;
195				if (dp->d_fileno == ino)
196					break;
197			}
198		} else
199			for (;;) {
200				if (!(dp = readdir(dir)))
201					goto notfound;
202				if (ISDOT(dp))
203					continue;
204				bcopy(dp->d_name, bup, dp->d_namlen + 1);
205
206				/* Save the first error for later. */
207				if (lstat(up, &s)) {
208					if (save_errno == 0)
209						save_errno = __os_get_errno();
210					__os_set_errno(0);
211					continue;
212				}
213				if (s.st_dev == dev && s.st_ino == ino)
214					break;
215			}
216
217		/*
218		 * Check for length of the current name, preceding slash,
219		 * leading slash.
220		 */
221		if (bpt - pt < dp->d_namlen + (first ? 1 : 2)) {
222			size_t len, off;
223
224			if (!ptsize) {
225				__os_set_errno(ERANGE);
226				goto err;
227			}
228			off = bpt - pt;
229			len = ept - bpt;
230			if (__os_realloc(NULL, ptsize *= 2, &pt) != 0)
231				goto err;
232			bpt = pt + off;
233			ept = pt + ptsize;
234			bcopy(bpt, ept - len, len);
235			bpt = ept - len;
236		}
237		if (!first)
238			*--bpt = PATH_SEPARATOR[0];
239		bpt -= dp->d_namlen;
240		bcopy(dp->d_name, bpt, dp->d_namlen);
241		(void)closedir(dir);
242
243		/* Truncate any file name. */
244		*bup = '\0';
245	}
246
247notfound:
248	/*
249	 * If readdir set errno, use it, not any saved error; otherwise,
250	 * didn't find the current directory in its parent directory, set
251	 * errno to ENOENT.
252	 */
253	if (__os_get_errno_ret_zero() == 0)
254		__os_set_errno(save_errno == 0 ? ENOENT : save_errno);
255	/* FALLTHROUGH */
256err:
257	if (ptsize)
258		__os_free(NULL, pt);
259	__os_free(NULL, up);
260	return (NULL);
261}
262