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 | |
39 | char * |
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 | } |
277 | strong_alias (__hurd_canonicalize_directory_name_internal, _hurd_canonicalize_directory_name_internal) |
278 | |
279 | char * |
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. */ |
297 | char * |
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 | } |
306 | libc_hidden_def (__getcwd) |
307 | weak_alias (__getcwd, getcwd) |
308 | |