libio: Remove unused pragma weak on vtable
[glibc.git] / hurd / lookup-retry.c
blob99c981046f19944eed551d7fb85c66f52ee71531
1 /* hairy bits of Hurd file name lookup
2 Copyright (C) 1992-2023 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
10 The GNU C Library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; if not, see
17 <https://www.gnu.org/licenses/>. */
19 #include <hurd.h>
20 #include <hurd/lookup.h>
21 #include <hurd/term.h>
22 #include <hurd/paths.h>
23 #include <limits.h>
24 #include <fcntl.h>
25 #include <string.h>
26 #include <_itoa.h>
27 #include <eloop-threshold.h>
28 #include <unistd.h>
30 /* Translate the error from dir_lookup into the error the user sees. */
31 static inline error_t
32 lookup_error (error_t error)
34 switch (error)
36 case EOPNOTSUPP:
37 case MIG_BAD_ID:
38 /* These indicate that the server does not understand dir_lookup
39 at all. If it were a directory, it would, by definition. */
40 return ENOTDIR;
41 default:
42 return error;
46 error_t
47 __hurd_file_name_lookup_retry (error_t (*use_init_port)
48 (int which, error_t (*operate) (file_t)),
49 file_t (*get_dtable_port) (int fd),
50 error_t (*lookup)
51 (file_t dir, const char *name,
52 int flags, mode_t mode,
53 retry_type *do_retry, string_t retry_name,
54 mach_port_t *result),
55 enum retry_type doretry,
56 char retryname[1024],
57 int flags, mode_t mode,
58 file_t *result)
60 error_t err;
61 char *file_name;
62 int nloops;
63 file_t lastdir = MACH_PORT_NULL;
65 error_t lookup_op (file_t startdir)
67 if (file_name[0] == '/' && file_name[1] != '\0')
69 while (file_name[1] == '/')
70 /* Remove double leading slash. */
71 file_name++;
72 if (file_name[1] != '\0')
73 /* Remove leading slash when we have more than the slash. */
74 file_name++;
77 return lookup_error ((*lookup) (startdir, file_name, flags, mode,
78 &doretry, retryname, result));
80 error_t reauthenticate (file_t unauth)
82 error_t err;
83 mach_port_t ref = __mach_reply_port ();
84 error_t reauth (auth_t auth)
86 return __auth_user_authenticate (auth, ref,
87 MACH_MSG_TYPE_MAKE_SEND,
88 result);
90 err = __io_reauthenticate (unauth, ref, MACH_MSG_TYPE_MAKE_SEND);
91 if (! err)
92 err = (*use_init_port) (INIT_PORT_AUTH, &reauth);
93 __mach_port_destroy (__mach_task_self (), ref);
94 __mach_port_deallocate (__mach_task_self (), unauth);
95 return err;
98 if (! lookup)
99 lookup = __dir_lookup;
101 nloops = 0;
102 err = 0;
105 file_t startdir = MACH_PORT_NULL;
106 int dirport = INIT_PORT_CWDIR;
108 switch (doretry)
110 case FS_RETRY_REAUTH:
111 if (err = reauthenticate (*result))
112 goto out;
113 /* Fall through. */
115 case FS_RETRY_NORMAL:
116 if (nloops++ >= __eloop_threshold ())
118 __mach_port_deallocate (__mach_task_self (), *result);
119 err = ELOOP;
120 goto out;
123 /* An empty RETRYNAME indicates we have the final port. */
124 if (retryname[0] == '\0'
125 /* If reauth'd, we must do one more retry on "" to give the new
126 translator a chance to make a new port for us. */
127 && doretry == FS_RETRY_NORMAL)
129 if (flags & O_NOFOLLOW)
131 /* In Linux, O_NOFOLLOW means to reject symlinks. If we
132 did an O_NOLINK lookup above and io_stat here to check
133 for S_IFLNK only, a translator like firmlink could easily
134 spoof this check by not showing S_IFLNK, but in fact
135 redirecting the lookup to some other name
136 (i.e. opening the very same holes a symlink would).
138 Instead we do an O_NOTRANS lookup above, and stat the
139 underlying node: if it has a translator set, and its
140 owner is not root (st_uid 0) then we reject it.
141 Since the motivation for this feature is security, and
142 that security presumes we trust the containing
143 directory, this check approximates the security of
144 refusing symlinks while accepting mount points.
145 Note that we actually permit something Linux doesn't:
146 we follow root-owned symlinks; if that is deemed
147 undesireable, we can add a final check for that
148 one exception to our general translator-based rule. */
149 struct stat64 st;
150 err = __io_stat (*result, &st);
151 if (!err)
153 if (flags & O_DIRECTORY && !S_ISDIR (st.st_mode))
154 err = ENOTDIR;
155 if (S_ISLNK (st.st_mode))
156 err = ELOOP;
157 else if (st.st_mode & (S_IPTRANS|S_IATRANS))
159 if (st.st_uid != 0)
160 err = ELOOP;
161 else if (st.st_mode & S_IPTRANS)
163 char buf[1024];
164 char *trans = buf;
165 mach_msg_type_number_t translen = sizeof buf;
166 err = __file_get_translator (*result,
167 &trans, &translen);
168 if (!err
169 && translen > sizeof _HURD_SYMLINK
170 && !memcmp (trans,
171 _HURD_SYMLINK, sizeof _HURD_SYMLINK))
172 err = ELOOP;
178 /* We got a successful translation. Now apply any open-time
179 action flags we were passed. */
180 #if !IS_IN (rtld)
181 if (!err && (flags & O_TRUNC))
183 /* Asked to truncate the file. */
184 err = __file_set_size (*result, 0);
185 if (!err)
187 struct timespec atime = { 0, UTIME_OMIT };
188 struct timespec mtime = { 0, UTIME_NOW };
189 __file_utimens (*result, atime, mtime);
192 #endif
194 if (err)
195 __mach_port_deallocate (__mach_task_self (), *result);
196 goto out;
199 startdir = *result;
200 file_name = retryname;
201 break;
203 case FS_RETRY_MAGICAL:
204 switch (retryname[0])
206 case '/':
207 dirport = INIT_PORT_CRDIR;
208 if (*result != MACH_PORT_NULL)
209 __mach_port_deallocate (__mach_task_self (), *result);
210 if (nloops++ >= __eloop_threshold ())
212 err = ELOOP;
213 goto out;
215 file_name = &retryname[1];
216 break;
218 #if !IS_IN (rtld)
219 case 'f':
220 if (retryname[1] == 'd' && retryname[2] == '/')
222 int fd;
223 char *end;
224 int save = errno;
225 errno = 0;
226 fd = (int) __strtoul_internal (&retryname[3], &end, 10, 0);
227 if (end == NULL || errno /* Malformed number. */
228 /* Check for excess text after the number. A slash
229 is valid; it ends the component. Anything else
230 does not name a numeric file descriptor. */
231 || (*end != '/' && *end != '\0'))
233 errno = save;
234 err = ENOENT;
235 goto out;
237 if (! get_dtable_port)
238 err = EGRATUITOUS;
239 else
241 *result = (*get_dtable_port) (fd);
242 if (*result == MACH_PORT_NULL)
244 /* If the name was a proper number, but the file
245 descriptor does not exist, we return EBADF instead
246 of ENOENT. */
247 err = errno;
248 errno = save;
251 errno = save;
252 if (err)
253 goto out;
254 if (*end == '\0')
256 err = 0;
257 goto out;
259 else
261 /* Do a normal retry on the remaining components. */
262 startdir = *result;
263 file_name = end + 1; /* Skip the slash. */
264 break;
267 else
268 goto bad_magic;
269 break;
271 case 'm':
272 if (retryname[1] == 'a' && retryname[2] == 'c'
273 && retryname[3] == 'h' && retryname[4] == 't'
274 && retryname[5] == 'y' && retryname[6] == 'p'
275 && retryname[7] == 'e')
277 error_t err;
278 struct host_basic_info hostinfo;
279 mach_msg_type_number_t hostinfocnt = HOST_BASIC_INFO_COUNT;
280 char *p;
281 /* XXX want client's host */
282 if (err = __host_info (__mach_host_self (), HOST_BASIC_INFO,
283 (integer_t *) &hostinfo,
284 &hostinfocnt))
285 goto out;
286 if (hostinfocnt != HOST_BASIC_INFO_COUNT)
288 err = EGRATUITOUS;
289 goto out;
291 p = _itoa (hostinfo.cpu_subtype, &retryname[8], 10, 0);
292 *--p = '/';
293 p = _itoa (hostinfo.cpu_type, &retryname[8], 10, 0);
294 if (p < retryname)
295 abort (); /* XXX write this right if this ever happens */
296 if (p > retryname)
297 memmove (retryname, p, strlen(p) + 1);
298 startdir = *result;
300 else
301 goto bad_magic;
302 break;
304 case 't':
305 if (retryname[1] == 't' && retryname[2] == 'y')
306 switch (retryname[3])
308 error_t opentty (file_t *result)
310 error_t err;
311 error_t ctty_open (file_t port)
313 if (port == MACH_PORT_NULL)
314 return ENXIO; /* No controlling terminal. */
315 return __termctty_open_terminal (port,
316 flags,
317 result);
319 err = (*use_init_port) (INIT_PORT_CTTYID, &ctty_open);
320 if (! err)
321 err = reauthenticate (*result);
322 return err;
325 case '\0':
326 err = opentty (result);
327 goto out;
328 case '/':
329 if (err = opentty (&startdir))
330 goto out;
331 memmove (retryname, &retryname[4], strlen(retryname + 4) + 1);
332 break;
333 default:
334 goto bad_magic;
336 else
337 goto bad_magic;
338 break;
340 case 'p':
341 if (retryname[1] == 'i' && retryname[2] == 'd'
342 && (retryname[3] == '/' || retryname[3] == 0))
344 char *p, buf[1024]; /* XXX */
345 size_t len;
346 p = _itoa (__getpid (), &buf[sizeof buf], 10, 0);
347 len = &buf[sizeof buf] - p;
348 memcpy (buf, p, len);
349 strncpy (buf + len, &retryname[3], sizeof buf - len - 1);
350 buf[sizeof buf - 1] = '\0';
351 strcpy (retryname, buf);
353 /* Do a normal retry on the remaining components. */
354 __mach_port_mod_refs (__mach_task_self (), lastdir,
355 MACH_PORT_RIGHT_SEND, 1);
356 startdir = lastdir;
357 file_name = retryname;
359 else
360 goto bad_magic;
361 break;
363 bad_magic:
364 #endif /* !IS_IN (rtld) */
365 default:
366 err = EGRATUITOUS;
367 goto out;
369 break;
371 default:
372 err = EGRATUITOUS;
373 goto out;
376 if (MACH_PORT_VALID (*result) && *result != lastdir)
378 if (MACH_PORT_VALID (lastdir))
379 __mach_port_deallocate (__mach_task_self (), lastdir);
381 lastdir = *result;
382 __mach_port_mod_refs (__mach_task_self (), lastdir,
383 MACH_PORT_RIGHT_SEND, 1);
386 if (startdir != MACH_PORT_NULL)
388 err = lookup_op (startdir);
389 __mach_port_deallocate (__mach_task_self (), startdir);
390 startdir = MACH_PORT_NULL;
392 else
393 err = (*use_init_port) (dirport, &lookup_op);
394 } while (! err);
396 out:
397 if (MACH_PORT_VALID (lastdir))
398 __mach_port_deallocate (__mach_task_self (), lastdir);
400 return err;
402 weak_alias (__hurd_file_name_lookup_retry, hurd_file_name_lookup_retry)