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