xref: /netbsd/external/gpl2/xcvs/dist/lib/getcwd.c (revision 6550d01e)
1 /* Copyright (C) 1991,92,93,94,95,96,97,98,99,2004,2005 Free Software
2    Foundation, Inc.
3    This file is part of the GNU C Library.
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2, or (at your option)
8    any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License along
16    with this program; if not, write to the Free Software Foundation,
17    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
18 
19 #ifdef	HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22 
23 #if !_LIBC
24 # include "getcwd.h"
25 #endif
26 
27 #include <errno.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <stdbool.h>
31 #include <stddef.h>
32 
33 #include <fcntl.h> /* For AT_FDCWD on Solaris 9.  */
34 
35 #ifndef __set_errno
36 # define __set_errno(val) (errno = (val))
37 #endif
38 
39 #if HAVE_DIRENT_H || _LIBC
40 # include <dirent.h>
41 # ifndef _D_EXACT_NAMLEN
42 #  define _D_EXACT_NAMLEN(d) strlen ((d)->d_name)
43 # endif
44 #else
45 # define dirent direct
46 # if HAVE_SYS_NDIR_H
47 #  include <sys/ndir.h>
48 # endif
49 # if HAVE_SYS_DIR_H
50 #  include <sys/dir.h>
51 # endif
52 # if HAVE_NDIR_H
53 #  include <ndir.h>
54 # endif
55 #endif
56 #ifndef _D_EXACT_NAMLEN
57 # define _D_EXACT_NAMLEN(d) ((d)->d_namlen)
58 #endif
59 #ifndef _D_ALLOC_NAMLEN
60 # define _D_ALLOC_NAMLEN(d) (_D_EXACT_NAMLEN (d) + 1)
61 #endif
62 
63 #if HAVE_UNISTD_H || _LIBC
64 # include <unistd.h>
65 #endif
66 
67 #include <stdlib.h>
68 #include <string.h>
69 
70 #if _LIBC
71 # ifndef mempcpy
72 #  define mempcpy __mempcpy
73 # endif
74 #else
75 # include "mempcpy.h"
76 #endif
77 
78 #include <limits.h>
79 
80 #ifdef ENAMETOOLONG
81 # define is_ENAMETOOLONG(x) ((x) == ENAMETOOLONG)
82 #else
83 # define is_ENAMETOOLONG(x) 0
84 #endif
85 
86 #ifndef MAX
87 # define MAX(a, b) ((a) < (b) ? (b) : (a))
88 #endif
89 #ifndef MIN
90 # define MIN(a, b) ((a) < (b) ? (a) : (b))
91 #endif
92 
93 #ifndef PATH_MAX
94 # ifdef	MAXPATHLEN
95 #  define PATH_MAX MAXPATHLEN
96 # else
97 #  define PATH_MAX 1024
98 # endif
99 #endif
100 
101 #if D_INO_IN_DIRENT
102 # define MATCHING_INO(dp, ino) ((dp)->d_ino == (ino))
103 #else
104 # define MATCHING_INO(dp, ino) true
105 #endif
106 
107 #if !_LIBC
108 # define __getcwd getcwd
109 # define __lstat lstat
110 # define __closedir closedir
111 # define __opendir opendir
112 # define __readdir readdir
113 #endif
114 
115 /* Get the name of the current working directory, and put it in SIZE
116    bytes of BUF.  Returns NULL if the directory couldn't be determined or
117    SIZE was too small.  If successful, returns BUF.  In GNU, if BUF is
118    NULL, an array is allocated with `malloc'; the array is SIZE bytes long,
119    unless SIZE == 0, in which case it is as big as necessary.  */
120 
121 char *
122 __getcwd (char *buf, size_t size)
123 {
124   /* Lengths of big file name components and entire file names, and a
125      deep level of file name nesting.  These numbers are not upper
126      bounds; they are merely large values suitable for initial
127      allocations, designed to be large enough for most real-world
128      uses.  */
129   enum
130     {
131       BIG_FILE_NAME_COMPONENT_LENGTH = 255,
132       BIG_FILE_NAME_LENGTH = MIN (4095, PATH_MAX - 1),
133       DEEP_NESTING = 100
134     };
135 
136 #ifdef AT_FDCWD
137   int fd = AT_FDCWD;
138   bool fd_needs_closing = false;
139 #else
140   char dots[DEEP_NESTING * sizeof ".." + BIG_FILE_NAME_COMPONENT_LENGTH + 1];
141   char *dotlist = dots;
142   size_t dotsize = sizeof dots;
143   size_t dotlen = 0;
144 #endif
145   DIR *dirstream = NULL;
146   dev_t rootdev, thisdev;
147   ino_t rootino, thisino;
148   char *dir;
149   register char *dirp;
150   struct stat st;
151   size_t allocated = size;
152   size_t used;
153 
154 #if HAVE_PARTLY_WORKING_GETCWD && !defined AT_FDCWD
155   /* The system getcwd works, except it sometimes fails when it
156      shouldn't, setting errno to ERANGE, ENAMETOOLONG, or ENOENT.  If
157      AT_FDCWD is not defined, the algorithm below is O(N**2) and this
158      is much slower than the system getcwd (at least on GNU/Linux).
159      So trust the system getcwd's results unless they look
160      suspicious.  */
161 # undef getcwd
162   dir = getcwd (buf, size);
163   if (dir || (errno != ERANGE && !is_ENAMETOOLONG (errno) && errno != ENOENT))
164     return dir;
165 #endif
166 
167   if (size == 0)
168     {
169       if (buf != NULL)
170 	{
171 	  __set_errno (EINVAL);
172 	  return NULL;
173 	}
174 
175       allocated = BIG_FILE_NAME_LENGTH + 1;
176     }
177 
178   if (buf == NULL)
179     {
180       dir = malloc (allocated);
181       if (dir == NULL)
182 	return NULL;
183     }
184   else
185     dir = buf;
186 
187   dirp = dir + allocated;
188   *--dirp = '\0';
189 
190   if (__lstat (".", &st) < 0)
191     goto lose;
192   thisdev = st.st_dev;
193   thisino = st.st_ino;
194 
195   if (__lstat ("/", &st) < 0)
196     goto lose;
197   rootdev = st.st_dev;
198   rootino = st.st_ino;
199 
200   while (!(thisdev == rootdev && thisino == rootino))
201     {
202       struct dirent *d;
203       dev_t dotdev;
204       ino_t dotino;
205       bool mount_point;
206       int parent_status;
207 
208       /* Look at the parent directory.  */
209 #ifdef AT_FDCWD
210       fd = openat (fd, "..", O_RDONLY);
211       if (fd < 0)
212 	goto lose;
213       fd_needs_closing = true;
214       parent_status = fstat (fd, &st);
215 #else
216       dotlist[dotlen++] = '.';
217       dotlist[dotlen++] = '.';
218       dotlist[dotlen] = '\0';
219       parent_status = __lstat (dotlist, &st);
220 #endif
221       if (parent_status != 0)
222 	goto lose;
223 
224       if (dirstream && __closedir (dirstream) != 0)
225 	{
226 	  dirstream = NULL;
227 	  goto lose;
228 	}
229 
230       /* Figure out if this directory is a mount point.  */
231       dotdev = st.st_dev;
232       dotino = st.st_ino;
233       mount_point = dotdev != thisdev;
234 
235       /* Search for the last directory.  */
236 #ifdef AT_FDCWD
237       dirstream = fdopendir (fd);
238       if (dirstream == NULL)
239 	goto lose;
240       fd_needs_closing = false;
241 #else
242       dirstream = __opendir (dotlist);
243       if (dirstream == NULL)
244 	goto lose;
245       dotlist[dotlen++] = '/';
246 #endif
247       /* Clear errno to distinguish EOF from error if readdir returns
248 	 NULL.  */
249       __set_errno (0);
250       while ((d = __readdir (dirstream)) != NULL)
251 	{
252 	  if (d->d_name[0] == '.' &&
253 	      (d->d_name[1] == '\0' ||
254 	       (d->d_name[1] == '.' && d->d_name[2] == '\0')))
255 	    continue;
256 	  if (MATCHING_INO (d, thisino) || mount_point)
257 	    {
258 	      int entry_status;
259 #ifdef AT_FDCWD
260 	      entry_status = fstatat (fd, d->d_name, &st, AT_SYMLINK_NOFOLLOW);
261 #else
262 	      /* Compute size needed for this file name, or for the file
263 		 name ".." in the same directory, whichever is larger.
264 	         Room for ".." might be needed the next time through
265 		 the outer loop.  */
266 	      size_t name_alloc = _D_ALLOC_NAMLEN (d);
267 	      size_t filesize = dotlen + MAX (sizeof "..", name_alloc);
268 
269 	      if (filesize < dotlen)
270 		goto memory_exhausted;
271 
272 	      if (dotsize < filesize)
273 		{
274 		  /* My, what a deep directory tree you have, Grandma.  */
275 		  size_t newsize = MAX (filesize, dotsize * 2);
276 		  size_t i;
277 		  if (newsize < dotsize)
278 		    goto memory_exhausted;
279 		  if (dotlist != dots)
280 		    free (dotlist);
281 		  dotlist = malloc (newsize);
282 		  if (dotlist == NULL)
283 		    goto lose;
284 		  dotsize = newsize;
285 
286 		  i = 0;
287 		  do
288 		    {
289 		      dotlist[i++] = '.';
290 		      dotlist[i++] = '.';
291 		      dotlist[i++] = '/';
292 		    }
293 		  while (i < dotlen);
294 		}
295 
296 	      strcpy (dotlist + dotlen, d->d_name);
297 	      entry_status = __lstat (dotlist, &st);
298 #endif
299 	      /* We don't fail here if we cannot stat() a directory entry.
300 		 This can happen when (network) file systems fail.  If this
301 		 entry is in fact the one we are looking for we will find
302 		 out soon as we reach the end of the directory without
303 		 having found anything.  */
304 	      if (entry_status == 0 && S_ISDIR (st.st_mode)
305 		  && st.st_dev == thisdev && st.st_ino == thisino)
306 		break;
307 	    }
308 	}
309       if (d == NULL)
310 	{
311 	  if (errno == 0)
312 	    /* EOF on dirstream, which means that the current directory
313 	       has been removed.  */
314 	    __set_errno (ENOENT);
315 	  goto lose;
316 	}
317       else
318 	{
319 	  size_t dirroom = dirp - dir;
320 	  size_t namlen = _D_EXACT_NAMLEN (d);
321 
322 	  if (dirroom <= namlen)
323 	    {
324 	      if (size != 0)
325 		{
326 		  __set_errno (ERANGE);
327 		  goto lose;
328 		}
329 	      else
330 		{
331 		  char *tmp;
332 		  size_t oldsize = allocated;
333 
334 		  allocated += MAX (allocated, namlen);
335 		  if (allocated < oldsize
336 		      || ! (tmp = realloc (dir, allocated)))
337 		    goto memory_exhausted;
338 
339 		  /* Move current contents up to the end of the buffer.
340 		     This is guaranteed to be non-overlapping.  */
341 		  dirp = memcpy (tmp + allocated - (oldsize - dirroom),
342 				 tmp + dirroom,
343 				 oldsize - dirroom);
344 		  dir = tmp;
345 		}
346 	    }
347 	  dirp -= namlen;
348 	  memcpy (dirp, d->d_name, namlen);
349 	  *--dirp = '/';
350 	}
351 
352       thisdev = dotdev;
353       thisino = dotino;
354     }
355 
356   if (dirstream && __closedir (dirstream) != 0)
357     {
358       dirstream = NULL;
359       goto lose;
360     }
361 
362   if (dirp == &dir[allocated - 1])
363     *--dirp = '/';
364 
365 #ifndef AT_FDCWD
366   if (dotlist != dots)
367     free (dotlist);
368 #endif
369 
370   used = dir + allocated - dirp;
371   memmove (dir, dirp, used);
372 
373   if (buf == NULL && size == 0)
374     /* Ensure that the buffer is only as large as necessary.  */
375     buf = realloc (dir, used);
376 
377   if (buf == NULL)
378     /* Either buf was NULL all along, or `realloc' failed but
379        we still have the original string.  */
380     buf = dir;
381 
382   return buf;
383 
384  memory_exhausted:
385   __set_errno (ENOMEM);
386  lose:
387   {
388     int save = errno;
389     if (dirstream)
390       __closedir (dirstream);
391 #ifdef AT_FDCWD
392     if (fd_needs_closing)
393       close (fd);
394 #else
395     if (dotlist != dots)
396       free (dotlist);
397 #endif
398     if (buf == NULL)
399       free (dir);
400     __set_errno (save);
401   }
402   return NULL;
403 }
404 
405 #ifdef weak_alias
406 weak_alias (__getcwd, getcwd)
407 #endif
408