9efcf1a5ebcb69261126f56305970b58dc7d2e99
[platform/upstream/glibc.git] / sysdeps / mach / hurd / getcwd.c
1 /* Copyright (C) 1991, 92, 93, 94, 95, 96, 97, 98 Free Software Foundation, Inc.
2    This file is part of the GNU C Library.
3
4    The GNU C Library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Lesser General Public
6    License as published by the Free Software Foundation; either
7    version 2.1 of the License, or (at your option) any later version.
8
9    The GNU C Library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Lesser General Public License for more details.
13
14    You should have received a copy of the GNU Lesser General Public
15    License along with the GNU C Library; if not, write to the Free
16    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
17    02111-1307 USA.  */
18
19 #include <errno.h>
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <hurd.h>
23 #include <hurd/port.h>
24 #include <dirent.h>
25 #include <unistd.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <stdio.h>
29 #include <fcntl.h>
30
31
32 /* Get the canonical absolute name of the given directory port, and put it
33    in SIZE bytes of BUF.  Returns NULL if the directory couldn't be
34    determined or SIZE was too small.  If successful, returns BUF.  In GNU,
35    if BUF is NULL, an array is allocated with `malloc'; the array is SIZE
36    bytes long, unless SIZE <= 0, in which case it is as big as necessary.
37    If our root directory cannot be reached, the result will not begin with
38    a slash to indicate that it is relative to some unknown root directory.  */
39
40 char *
41 _hurd_canonicalize_directory_name_internal (file_t thisdir,
42                                             char *buf,
43                                             size_t size)
44 {
45   error_t err;
46   mach_port_t rootid, thisid, rootdevid, thisdevid;
47   ino_t rootino, thisino;
48   char *file_name;
49   register char *file_namep;
50   file_t parent;
51   char *dirbuf = NULL;
52   unsigned int dirbufsize = 0;
53
54   inline void cleanup (void)
55     {
56       if (parent != thisdir)
57         __mach_port_deallocate (__mach_task_self (), parent);
58
59       __mach_port_deallocate (__mach_task_self (), thisid);
60       __mach_port_deallocate (__mach_task_self (), thisdevid);
61       __mach_port_deallocate (__mach_task_self (), rootid);
62       __mach_port_deallocate (__mach_task_self (), rootdevid);
63
64       if (dirbuf != NULL)
65         __vm_deallocate (__mach_task_self (),
66                          (vm_address_t) dirbuf, dirbufsize);
67     }
68
69
70   if (size == 0)
71     {
72       if (buf != NULL)
73         {
74           errno = EINVAL;
75           return NULL;
76         }
77
78       size = FILENAME_MAX * 4 + 1;      /* Good starting guess.  */
79     }
80
81   if (buf != NULL)
82     file_name = buf;
83   else
84     {
85       file_name = malloc (size);
86       if (file_name == NULL)
87         return NULL;
88     }
89
90   file_namep = file_name + size;
91   *--file_namep = '\0';
92
93   /* Get a port to our root directory and get its identity.  */
94
95   if (err = __USEPORT (CRDIR, __io_identity (port,
96                                              &rootid, &rootdevid, &rootino)))
97     return __hurd_fail (err), NULL;
98   __mach_port_deallocate (__mach_task_self (), rootdevid);
99
100   /* Stat the port to the directory of interest.  */
101
102   if (err = __io_identity (thisdir, &thisid, &thisdevid, &thisino))
103     {
104       __mach_port_deallocate (__mach_task_self (), rootid);
105       return __hurd_fail (err), NULL;
106     }
107
108   parent = thisdir;
109   while (thisid != rootid)
110     {
111       /* PARENT is a port to the directory we are currently on;
112          THISID, THISDEV, and THISINO are its identity.
113          Look in its parent (..) for a file with the same file number.  */
114
115       struct dirent *d;
116       mach_port_t dotid, dotdevid;
117       ino_t dotino;
118       int mount_point;
119       file_t newp;
120       char *dirdata;
121       unsigned int dirdatasize;
122       int direntry, nentries;
123
124
125       /* Look at the parent directory.  */
126       newp = __file_name_lookup_under (parent, "..", O_READ, 0);
127       if (newp == MACH_PORT_NULL)
128         goto lose;
129       if (parent != thisdir)
130         __mach_port_deallocate (__mach_task_self (), parent);
131       parent = newp;
132
133       /* Get this directory's identity and figure out if it's a mount
134          point.  */
135       if (err = __io_identity (parent, &dotid, &dotdevid, &dotino))
136         goto errlose;
137       mount_point = dotdevid != thisdevid;
138
139       if (thisid == dotid)
140         {
141           /* `..' == `.' but it is not our root directory.  */
142           __mach_port_deallocate (__mach_task_self (), dotid);
143           __mach_port_deallocate (__mach_task_self (), dotdevid);
144           break;
145         }
146
147       /* Search for the last directory.  */
148       direntry = 0;
149       dirdata = dirbuf;
150       dirdatasize = dirbufsize;
151       while (!(err = __dir_readdir (parent, &dirdata, &dirdatasize,
152                                     direntry, -1, 0, &nentries)) &&
153              nentries != 0)
154         {
155           /* We have a block of directory entries.  */
156
157           unsigned int offset;
158
159           direntry += nentries;
160
161           if (dirdata != dirbuf)
162             {
163               /* The data was passed out of line, so our old buffer is no
164                  longer useful.  Deallocate the old buffer and reset our
165                  information for the new buffer.  */
166               __vm_deallocate (__mach_task_self (),
167                                (vm_address_t) dirbuf, dirbufsize);
168               dirbuf = dirdata;
169               dirbufsize = round_page (dirdatasize);
170             }
171
172           /* Iterate over the returned directory entries, looking for one
173              whose file number is THISINO.  */
174
175           offset = 0;
176           while (offset < dirdatasize)
177             {
178               d = (struct dirent *) &dirdata[offset];
179               offset += d->d_reclen;
180
181               /* Ignore `.' and `..'.  */
182               if (d->d_name[0] == '.' &&
183                   (d->d_namlen == 1 ||
184                    (d->d_namlen == 2 && d->d_name[1] == '.')))
185                 continue;
186
187               if (mount_point || d->d_ino == thisino)
188                 {
189                   file_t try = __file_name_lookup_under (parent, d->d_name,
190                                                          O_NOLINK, 0);
191                   file_t id, devid;
192                   ino_t fileno;
193                   if (try == MACH_PORT_NULL)
194                     goto lose;
195                   err = __io_identity (try, &id, &devid, &fileno);
196                   __mach_port_deallocate (__mach_task_self (), try);
197                   if (err)
198                     goto inner_errlose;
199                   __mach_port_deallocate (__mach_task_self (), id);
200                   __mach_port_deallocate (__mach_task_self (), devid);
201                   if (id == thisid)
202                     goto found;
203                 }
204             }
205         }
206
207       if (err)
208         {
209         inner_errlose:          /* Goto ERRLOSE: after cleaning up.  */
210           __mach_port_deallocate (__mach_task_self (), dotid);
211           __mach_port_deallocate (__mach_task_self (), dotdevid);
212           goto errlose;
213         }
214       else if (nentries == 0)
215         {
216           /* We got to the end of the directory without finding anything!
217              We are in a directory that has been unlinked, or something is
218              broken.  */
219           err = ENOENT;
220           goto inner_errlose;
221         }
222       else
223       found:
224         {
225           /* Prepend the directory name just discovered.  */
226
227           if (file_namep - file_name < d->d_namlen + 1)
228             {
229               if (buf != NULL)
230                 {
231                   errno = ERANGE;
232                   return NULL;
233                 }
234               else
235                 {
236                   size *= 2;
237                   buf = realloc (file_name, size);
238                   if (buf == NULL)
239                     {
240                       free (file_name);
241                       return NULL;
242                     }
243                   file_namep = &buf[file_namep - file_name + size / 2];
244                   file_name = buf;
245                   /* Move current contents up to the end of the buffer.
246                      This is guaranteed to be non-overlapping.  */
247                   memcpy (file_namep, file_namep - size / 2,
248                           file_name + size - file_namep);
249                 }
250             }
251           file_namep -= d->d_namlen;
252           (void) memcpy (file_namep, d->d_name, d->d_namlen);
253           *--file_namep = '/';
254         }
255
256       /* The next iteration will find the name of the directory we
257          just searched through.  */
258       __mach_port_deallocate (__mach_task_self (), thisid);
259       __mach_port_deallocate (__mach_task_self (), thisdevid);
260       thisid = dotid;
261       thisdevid = dotdevid;
262       thisino = dotino;
263     }
264
265   if (file_namep == &file_name[size - 1])
266     /* We found nothing and got all the way to the root.
267        So the root is our current directory.  */
268     *--file_namep = '/';
269
270   if (thisid != rootid)
271     /* We did not get to our root directory. The returned name should
272        not begin with a slash.  */
273     ++file_namep;
274
275   memmove (file_name, file_namep, file_name + size - file_namep);
276   cleanup ();
277   return file_name;
278
279  errlose:
280   /* Set errno.  */
281   (void) __hurd_fail (err);
282  lose:
283   cleanup ();
284   return NULL;
285 }
286
287 char *
288 __canonicalize_directory_name_internal (thisdir, buf, size)
289      const char *thisdir;
290      char *buf;
291      size_t size;
292 {
293   char *result;
294   file_t port = __file_name_lookup (thisdir, 0, 0);
295   if (port == MACH_PORT_NULL)
296     return NULL;
297   result = _hurd_canonicalize_directory_name_internal (port, buf, size);
298   __mach_port_deallocate (__mach_task_self (), port);
299   return result;
300 }
301 \f
302 /* Get the pathname of the current working directory, and put it in SIZE
303    bytes of BUF.  Returns NULL if the directory couldn't be determined or
304    SIZE was too small.  If successful, returns BUF.  In GNU, if BUF is
305    NULL, an array is allocated with `malloc'; the array is SIZE bytes long,
306    unless SIZE <= 0, in which case it is as big as necessary.  */
307 char *
308 __getcwd (char *buf, size_t size)
309 {
310   char *cwd =
311     __USEPORT (CWDIR,
312                _hurd_canonicalize_directory_name_internal (port,
313                                                            buf, size));
314   if (cwd && cwd[0] != '/')
315     {
316       /* `cwd' is an unknown root directory.  */
317       if (buf == NULL)
318           free (cwd);
319       return __hurd_fail (EGRATUITOUS), NULL;
320     }
321   return cwd;
322 }
323 weak_alias (__getcwd, getcwd)