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

source code of glibc/sysdeps/mach/hurd/getcwd.c