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