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