Allocate buffer large enough not only if size == 0, but also for size < 0.
[kopensolaris-gnu/glibc.git] / sysdeps / unix / sysv / linux / getcwd.c
1 /* Determine current working directory.  Linux version.
2    Copyright (C) 1997, 1998, 1999 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4    Contributed by Ulrich Drepper <drepper@cygnus.com>, 1997.
5
6    The GNU C Library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Library General Public License as
8    published by the Free Software Foundation; either version 2 of the
9    License, or (at your option) any later version.
10
11    The GNU C Library is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Library General Public License for more details.
15
16    You should have received a copy of the GNU Library General Public
17    License along with the GNU C Library; see the file COPYING.LIB.  If not,
18    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19    Boston, MA 02111-1307, USA.  */
20
21 #include <assert.h>
22 #include <errno.h>
23 #include <limits.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26
27 #include <sysdep.h>
28 #include <sys/syscall.h>
29
30 #include "kernel-features.h"
31
32
33 #if __ASSUME_GETCWD_SYSCALL > 0
34 /* Kernel 2.1.92 introduced a third way to get the current working
35    directory: a syscall.  We've got to be careful that even when
36    compiling under 2.1.92+ the libc still runs under older kernels. */
37 extern int __syscall_getcwd (char *buf, unsigned long size);
38 # define no_syscall_getcwd 0
39 # define have_new_dcache 1
40 /* This is a trick since we don't define generic_getcwd.  */
41 # define generic_getcwd getcwd
42 #else
43 /* The "proc" filesystem provides an easy method to retrieve the value.
44    For each process, the corresponding directory contains a symbolic link
45    named `cwd'.  Reading the content of this link immediate gives us the
46    information.  But we have to take care for systems which do not have
47    the proc filesystem mounted.  Use the POSIX implementation in this case.  */
48 static char *generic_getcwd (char *buf, size_t size) internal_function;
49
50 # if __NR_getcwd
51 /* Kernel 2.1.92 introduced a third way to get the current working
52    directory: a syscall.  We've got to be careful that even when
53    compiling under 2.1.92+ the libc still runs under older kernels. */
54 extern int __syscall_getcwd (char *buf, unsigned long size);
55 static int no_syscall_getcwd;
56 static int have_new_dcache;
57 # else
58 #  define no_syscall_getcwd 1
59 static int have_new_dcache = 1;
60 # endif
61 #endif
62
63 char *
64 __getcwd (char *buf, size_t size)
65 {
66   int save_errno;
67   char *path;
68   int n;
69   char *result;
70   size_t alloc_size = size;
71
72   if (no_syscall_getcwd && !have_new_dcache)
73     return generic_getcwd (buf, size);
74
75   if (size <= 0)
76     {
77       if (buf != NULL)
78         {
79           __set_errno (EINVAL);
80           return NULL;
81         }
82
83       alloc_size = PATH_MAX;
84     }
85
86   if (buf != NULL)
87     path = buf;
88   else
89     {
90       path = malloc (alloc_size);
91       if (path == NULL)
92         return NULL;
93     }
94
95   save_errno = errno;
96
97 #if defined __NR_getcwd || __LINUX_GETCWD_SYSCALL > 0
98   if (!no_syscall_getcwd)
99     {
100       int retval;
101
102       retval = INLINE_SYSCALL (getcwd, 2, path, alloc_size);
103       if (retval >= 0)
104         {
105           if (buf == NULL)
106             {
107               buf = realloc (path, (size_t) retval);
108               if (buf == NULL)
109                 /* `realloc' failed but we still have the original string.  */
110                 buf = path;
111             }
112           return buf;
113         }
114
115 # if __ASSUME_GETCWD_SYSCALL
116       /* It should never happen that the `getcwd' syscall failed because
117          the buffer is too small if we allocated the buffer outself.  */
118       assert (errno != ERANGE || buf != NULL);
119
120       if (buf == NULL)
121         free (path);
122
123       return NULL;
124 # else
125       if (errno == ENOSYS)
126         {
127            no_syscall_getcwd = 1;
128            have_new_dcache = 1; /* Now we will try the /proc method.  */
129         }
130       else if (errno != ERANGE || buf != NULL)
131         {
132           if (buf == NULL)
133             free (path);
134           return NULL;
135         }
136
137       __set_errno (save_errno);
138 # endif
139     }
140 #endif
141
142   n = __readlink ("/proc/self/cwd", path, alloc_size - 1);
143   if (n != -1)
144     {
145       if (path[0] == '/')
146         {
147           if (n >= alloc_size - 1)
148             {
149               if (buf == NULL)
150                 free (path);
151               return NULL;
152             }
153
154           path[n] = '\0';
155           if (buf == NULL)
156             {
157               buf = realloc (path, (size_t) n + 1);
158               if (buf == NULL)
159                 /* `relloc' failed but we still have the original string.  */
160                 buf = path;
161             }
162           return buf;
163         }
164 #ifndef have_new_dcache
165       else
166         have_new_dcache = 0;
167 #endif
168     }
169
170 #if __ASSUME_GETCWD_SYSCALL == 0
171   /* Set to have_new_dcache only if error indicates that proc doesn't
172      exist.  */
173   if (errno != EACCES && errno != ENAMETOOLONG)
174     have_new_dcache = 0;
175 #endif
176
177   /* Something went wrong.  Restore the error number and use the generic
178      version.  */
179   __set_errno (save_errno);
180
181   /* Don't put restrictions on the length of the path unless the user does.  */
182   if (size <= 0)
183     {
184       free (path);
185       path = NULL;
186     }
187
188   result = generic_getcwd (path, size);
189
190   if (result == NULL && buf == NULL && size > 0)
191     free (path);
192
193   return result;
194 }
195 weak_alias (__getcwd, getcwd)
196
197 #if __ASSUME_GETCWD_SYSCALL == 0
198 /* Get the code for the generic version.  */
199 # define GETCWD_RETURN_TYPE     static char * internal_function
200 # define __getcwd               generic_getcwd
201 # include <sysdeps/posix/getcwd.c>
202 #endif