1 /* hairy bits of Hurd file name lookup
2 Copyright (C) 1992-2024 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/>. */
20 #include <hurd/lookup.h>
21 #include <hurd/term.h>
22 #include <hurd/paths.h>
27 #include <eloop-threshold.h>
30 /* Translate the error from dir_lookup into the error the user sees. */
32 lookup_error (error_t error
)
38 /* These indicate that the server does not understand dir_lookup
39 at all. If it were a directory, it would, by definition. */
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
),
51 (file_t dir
, const char *name
,
52 int flags
, mode_t mode
,
53 retry_type
*do_retry
, string_t retry_name
,
55 enum retry_type doretry
,
57 int flags
, mode_t mode
,
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. */
72 if (file_name
[1] != '\0')
73 /* Remove leading slash when we have more than the slash. */
77 return lookup_error ((*lookup
) (startdir
, file_name
, flags
, mode
,
78 &doretry
, retryname
, result
));
80 error_t
reauthenticate (file_t unauth
)
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
,
90 err
= __io_reauthenticate (unauth
, ref
, MACH_MSG_TYPE_MAKE_SEND
);
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
);
99 lookup
= __dir_lookup
;
105 file_t startdir
= MACH_PORT_NULL
;
106 int dirport
= INIT_PORT_CWDIR
;
110 case FS_RETRY_REAUTH
:
111 if (err
= reauthenticate (*result
))
115 case FS_RETRY_NORMAL
:
116 if (nloops
++ >= __eloop_threshold ())
118 __mach_port_deallocate (__mach_task_self (), *result
);
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 undesirable, we can add a final check for that
148 one exception to our general translator-based rule. */
150 err
= __io_stat (*result
, &st
);
153 if (flags
& O_DIRECTORY
&& !S_ISDIR (st
.st_mode
))
155 if (S_ISLNK (st
.st_mode
))
157 else if (st
.st_mode
& (S_IPTRANS
|S_IATRANS
))
161 else if (st
.st_mode
& S_IPTRANS
)
165 mach_msg_type_number_t translen
= sizeof buf
;
166 err
= __file_get_translator (*result
,
169 && translen
> sizeof _HURD_SYMLINK
171 _HURD_SYMLINK
, sizeof _HURD_SYMLINK
))
178 /* We got a successful translation. Now apply any open-time
179 action flags we were passed. */
181 if (!err
&& (flags
& O_TRUNC
))
183 /* Asked to truncate the file. */
184 err
= __file_set_size (*result
, 0);
187 struct timespec atime
= { 0, UTIME_OMIT
};
188 struct timespec mtime
= { 0, UTIME_NOW
};
189 __file_utimens (*result
, atime
, mtime
);
195 __mach_port_deallocate (__mach_task_self (), *result
);
200 file_name
= retryname
;
203 case FS_RETRY_MAGICAL
:
204 switch (retryname
[0])
207 dirport
= INIT_PORT_CRDIR
;
208 if (*result
!= MACH_PORT_NULL
)
209 __mach_port_deallocate (__mach_task_self (), *result
);
210 if (nloops
++ >= __eloop_threshold ())
215 file_name
= &retryname
[1];
220 if (retryname
[1] == 'd' && retryname
[2] == '/')
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'))
237 if (! get_dtable_port
)
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
261 /* Do a normal retry on the remaining components. */
263 file_name
= end
+ 1; /* Skip the slash. */
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')
278 struct host_basic_info hostinfo
;
279 mach_msg_type_number_t hostinfocnt
= HOST_BASIC_INFO_COUNT
;
280 /* XXX want client's host */
281 if (err
= __host_info (__mach_host_self (), HOST_BASIC_INFO
,
282 (integer_t
*) &hostinfo
,
285 if (hostinfocnt
!= HOST_BASIC_INFO_COUNT
)
290 file_name
= _itoa (hostinfo
.cpu_subtype
, &retryname
[8], 10, 0);
292 file_name
= _itoa (hostinfo
.cpu_type
, file_name
, 10, 0);
293 if (file_name
< retryname
)
294 abort (); /* XXX write this right if this ever happens */
302 if (retryname
[1] == 't' && retryname
[2] == 'y')
303 switch (retryname
[3])
305 error_t
opentty (file_t
*result
)
308 error_t
ctty_open (file_t port
)
310 if (port
== MACH_PORT_NULL
)
311 return ENXIO
; /* No controlling terminal. */
312 return __termctty_open_terminal (port
,
316 err
= (*use_init_port
) (INIT_PORT_CTTYID
, &ctty_open
);
318 err
= reauthenticate (*result
);
323 err
= opentty (result
);
326 if (err
= opentty (&startdir
))
328 memmove (retryname
, &retryname
[4], strlen(retryname
+ 4) + 1);
338 if (retryname
[1] == 'i' && retryname
[2] == 'd'
339 && (retryname
[3] == '/' || retryname
[3] == 0))
341 char *p
, buf
[1024]; /* XXX */
343 p
= _itoa (__getpid (), &buf
[sizeof buf
], 10, 0);
344 len
= &buf
[sizeof buf
] - p
;
345 memcpy (buf
, p
, len
);
346 strncpy (buf
+ len
, &retryname
[3], sizeof buf
- len
- 1);
347 buf
[sizeof buf
- 1] = '\0';
348 strcpy (retryname
, buf
);
350 /* Do a normal retry on the remaining components. */
351 __mach_port_mod_refs (__mach_task_self (), lastdir
,
352 MACH_PORT_RIGHT_SEND
, 1);
354 file_name
= retryname
;
361 #endif /* !IS_IN (rtld) */
373 if (MACH_PORT_VALID (*result
) && *result
!= lastdir
)
375 if (MACH_PORT_VALID (lastdir
))
376 __mach_port_deallocate (__mach_task_self (), lastdir
);
379 __mach_port_mod_refs (__mach_task_self (), lastdir
,
380 MACH_PORT_RIGHT_SEND
, 1);
383 if (startdir
!= MACH_PORT_NULL
)
385 err
= lookup_op (startdir
);
386 __mach_port_deallocate (__mach_task_self (), startdir
);
387 startdir
= MACH_PORT_NULL
;
390 err
= (*use_init_port
) (dirport
, &lookup_op
);
394 if (MACH_PORT_VALID (lastdir
))
395 __mach_port_deallocate (__mach_task_self (), lastdir
);
399 weak_alias (__hurd_file_name_lookup_retry
, hurd_file_name_lookup_retry
)