]>
Commit | Line | Data |
---|---|---|
5afdca00 UD |
1 | /* Linuxthreads - a simple clone()-based implementation of Posix */ |
2 | /* threads for Linux. */ | |
3 | /* Copyright (C) 1996 Xavier Leroy (Xavier.Leroy@inria.fr) */ | |
4 | /* */ | |
5 | /* This program is free software; you can redistribute it and/or */ | |
6 | /* modify it under the terms of the GNU Library General Public License */ | |
7 | /* as published by the Free Software Foundation; either version 2 */ | |
8 | /* of the License, or (at your option) any later version. */ | |
9 | /* */ | |
10 | /* This program 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 */ | |
13 | /* GNU Library General Public License for more details. */ | |
14 | ||
a9cb398f UD |
15 | #ifndef _INTERNALS_H |
16 | #define _INTERNALS_H 1 | |
17 | ||
5afdca00 UD |
18 | /* Internal data structures */ |
19 | ||
20 | /* Includes */ | |
21 | ||
4959e310 | 22 | #include <limits.h> |
5afdca00 UD |
23 | #include <setjmp.h> |
24 | #include <signal.h> | |
25 | #include <unistd.h> | |
26 | #include <sys/types.h> | |
4360eafd | 27 | #include <bits/libc-tsd.h> /* for _LIBC_TSD_KEY_N */ |
5afdca00 | 28 | |
b43b13ac UD |
29 | #include <resolv.h> /* for per-thread resolver context */ |
30 | ||
31 | ||
5afdca00 | 32 | #include "pt-machine.h" |
883c331a | 33 | #include "semaphore.h" |
a9cb398f | 34 | #include "../linuxthreads_db/thread_dbP.h" |
5afdca00 | 35 | |
00a2f9aa UD |
36 | #ifndef THREAD_GETMEM |
37 | # define THREAD_GETMEM(descr, member) descr->member | |
38 | #endif | |
75311719 UD |
39 | #ifndef THREAD_GETMEM_NC |
40 | # define THREAD_GETMEM_NC(descr, member) descr->member | |
41 | #endif | |
00a2f9aa UD |
42 | #ifndef THREAD_SETMEM |
43 | # define THREAD_SETMEM(descr, member, value) descr->member = (value) | |
44 | #endif | |
75311719 UD |
45 | #ifndef THREAD_SETMEM_NC |
46 | # define THREAD_SETMEM_NC(descr, member, value) descr->member = (value) | |
47 | #endif | |
00a2f9aa | 48 | |
5afdca00 UD |
49 | /* Arguments passed to thread creation routine */ |
50 | ||
51 | struct pthread_start_args { | |
52 | void * (*start_routine)(void *); /* function to run */ | |
53 | void * arg; /* its argument */ | |
54 | sigset_t mask; /* initial signal mask for thread */ | |
55 | int schedpolicy; /* initial scheduling policy (if any) */ | |
56 | struct sched_param schedparam; /* initial scheduling parameters (if any) */ | |
57 | }; | |
58 | ||
59 | ||
60 | /* We keep thread specific data in a special data structure, a two-level | |
61 | array. The top-level array contains pointers to dynamically allocated | |
62 | arrays of a certain number of data pointers. So we can implement a | |
63 | sparse array. Each dynamic second-level array has | |
64 | PTHREAD_KEY_2NDLEVEL_SIZE | |
65 | entries. This value shouldn't be too large. */ | |
66 | #define PTHREAD_KEY_2NDLEVEL_SIZE 32 | |
67 | ||
68 | /* We need to address PTHREAD_KEYS_MAX key with PTHREAD_KEY_2NDLEVEL_SIZE | |
69 | keys in each subarray. */ | |
70 | #define PTHREAD_KEY_1STLEVEL_SIZE \ | |
71 | ((PTHREAD_KEYS_MAX + PTHREAD_KEY_2NDLEVEL_SIZE - 1) \ | |
72 | / PTHREAD_KEY_2NDLEVEL_SIZE) | |
73 | ||
d790bc34 UD |
74 | typedef void (*destr_function)(void *); |
75 | ||
76 | struct pthread_key_struct { | |
77 | int in_use; /* already allocated? */ | |
78 | destr_function destr; /* destruction routine */ | |
79 | }; | |
80 | ||
5afdca00 | 81 | |
ef5d6645 UD |
82 | #define PTHREAD_START_ARGS_INITIALIZER(fct) \ |
83 | { (void *(*) (void *)) fct, NULL, {{0, }}, 0, { 0 } } | |
5afdca00 UD |
84 | |
85 | /* The type of thread descriptors */ | |
86 | ||
87 | typedef struct _pthread_descr_struct * pthread_descr; | |
88 | ||
1d2fc9b3 UD |
89 | /* Callback interface for removing the thread from waiting on an |
90 | object if it is cancelled while waiting or about to wait. | |
91 | This hold a pointer to the object, and a pointer to a function | |
92 | which ``extricates'' the thread from its enqueued state. | |
93 | The function takes two arguments: pointer to the wait object, | |
94 | and a pointer to the thread. It returns 1 if an extrication | |
95 | actually occured, and hence the thread must also be signalled. | |
96 | It returns 0 if the thread had already been extricated. */ | |
97 | ||
98 | typedef struct _pthread_extricate_struct { | |
99 | void *pu_object; | |
100 | int (*pu_extricate_func)(void *, pthread_descr); | |
101 | } pthread_extricate_if; | |
102 | ||
103 | /* Atomic counter made possible by compare_and_swap */ | |
104 | ||
105 | struct pthread_atomic { | |
106 | long p_count; | |
107 | int p_spinlock; | |
108 | }; | |
109 | ||
ce75c139 UD |
110 | /* Context info for read write locks. The pthread_rwlock_info structure |
111 | is information about a lock that has been read-locked by the thread | |
112 | in whose list this structure appears. The pthread_rwlock_context | |
113 | is embedded in the thread context and contains a pointer to the | |
114 | head of the list of lock info structures, as well as a count of | |
115 | read locks that are untracked, because no info structure could be | |
116 | allocated for them. */ | |
117 | ||
118 | struct _pthread_rwlock_t; | |
119 | ||
120 | typedef struct _pthread_rwlock_info { | |
121 | struct _pthread_rwlock_info *pr_next; | |
122 | struct _pthread_rwlock_t *pr_lock; | |
123 | int pr_lock_count; | |
124 | } pthread_readlock_info; | |
125 | ||
5afdca00 UD |
126 | struct _pthread_descr_struct { |
127 | pthread_descr p_nextlive, p_prevlive; | |
128 | /* Double chaining of active threads */ | |
129 | pthread_descr p_nextwaiting; /* Next element in the queue holding the thr */ | |
6a1db4ff | 130 | pthread_descr p_nextlock; /* can be on a queue and waiting on a lock */ |
5afdca00 UD |
131 | pthread_t p_tid; /* Thread identifier */ |
132 | int p_pid; /* PID of Unix process */ | |
133 | int p_priority; /* Thread priority (== 0 if not realtime) */ | |
d8d914df | 134 | pthread_spinlock_t * p_lock; /* Spinlock for synchronized accesses */ |
5afdca00 UD |
135 | int p_signal; /* last signal received */ |
136 | sigjmp_buf * p_signal_jmp; /* where to siglongjmp on a signal or NULL */ | |
137 | sigjmp_buf * p_cancel_jmp; /* where to siglongjmp on a cancel or NULL */ | |
138 | char p_terminated; /* true if terminated e.g. by pthread_exit */ | |
139 | char p_detached; /* true if detached */ | |
140 | char p_exited; /* true if the assoc. process terminated */ | |
141 | void * p_retval; /* placeholder for return value */ | |
142 | int p_retcode; /* placeholder for return code */ | |
143 | pthread_descr p_joining; /* thread joining on that thread or NULL */ | |
144 | struct _pthread_cleanup_buffer * p_cleanup; /* cleanup functions */ | |
145 | char p_cancelstate; /* cancellation state */ | |
146 | char p_canceltype; /* cancellation type (deferred/async) */ | |
147 | char p_canceled; /* cancellation request pending */ | |
148 | int * p_errnop; /* pointer to used errno variable */ | |
149 | int p_errno; /* error returned by last system call */ | |
150 | int * p_h_errnop; /* pointer to used h_errno variable */ | |
151 | int p_h_errno; /* error returned by last netdb function */ | |
3387a425 UD |
152 | char * p_in_sighandler; /* stack address of sighandler, or NULL */ |
153 | char p_sigwaiting; /* true if a sigwait() is in progress */ | |
5afdca00 UD |
154 | struct pthread_start_args p_start_args; /* arguments for thread creation */ |
155 | void ** p_specific[PTHREAD_KEY_1STLEVEL_SIZE]; /* thread-specific data */ | |
156 | void * p_libc_specific[_LIBC_TSD_KEY_N]; /* thread-specific data for libc */ | |
fdacb17d | 157 | int p_userstack; /* nonzero if the user provided the stack */ |
5d409851 UD |
158 | void *p_guardaddr; /* address of guard area or NULL */ |
159 | size_t p_guardsize; /* size of guard area */ | |
00a2f9aa UD |
160 | pthread_descr p_self; /* Pointer to this structure */ |
161 | int p_nr; /* Index of descriptor in __pthread_handles */ | |
a9cb398f UD |
162 | int p_report_events; /* Nonzero if events must be reported. */ |
163 | td_eventbuf_t p_eventbuf; /* Data for event. */ | |
d569d333 UD |
164 | struct pthread_atomic p_resume_count; /* number of times restart() was |
165 | called on thread */ | |
166 | char p_woken_by_cancel; /* cancellation performed wakeup */ | |
167 | pthread_extricate_if *p_extricate; /* See above */ | |
ce75c139 UD |
168 | pthread_readlock_info *p_readlock_list; /* List of readlock info structs */ |
169 | pthread_readlock_info *p_readlock_free; /* Free list of structs */ | |
170 | int p_untracked_readlock_count; /* Readlocks not tracked by list */ | |
b43b13ac UD |
171 | struct __res_state *p_resp; /* Pointer to resolver state */ |
172 | struct __res_state p_res; /* per-thread resolver state */ | |
173 | /* New elements must be added at the end. */ | |
76a16b8f UD |
174 | } __attribute__ ((aligned(32))); /* We need to align the structure so that |
175 | doubles are aligned properly. This is 8 | |
176 | bytes on MIPS and 16 bytes on MIPS64. | |
177 | 32 bytes might give better cache | |
178 | utilization. */ | |
179 | ||
5afdca00 UD |
180 | |
181 | /* The type of thread handles. */ | |
182 | ||
183 | typedef struct pthread_handle_struct * pthread_handle; | |
184 | ||
185 | struct pthread_handle_struct { | |
d8d914df | 186 | pthread_spinlock_t h_lock; /* Fast lock for sychronized access */ |
5afdca00 | 187 | pthread_descr h_descr; /* Thread descriptor or NULL if invalid */ |
3387a425 | 188 | char * h_bottom; /* Lowest address in the stack thread */ |
5afdca00 UD |
189 | }; |
190 | ||
191 | /* The type of messages sent to the thread manager thread */ | |
192 | ||
193 | struct pthread_request { | |
194 | pthread_descr req_thread; /* Thread doing the request */ | |
195 | enum { /* Request kind */ | |
3387a425 UD |
196 | REQ_CREATE, REQ_FREE, REQ_PROCESS_EXIT, REQ_MAIN_THREAD_EXIT, |
197 | REQ_POST, REQ_DEBUG | |
5afdca00 UD |
198 | } req_kind; |
199 | union { /* Arguments for request */ | |
200 | struct { /* For REQ_CREATE: */ | |
201 | const pthread_attr_t * attr; /* thread attributes */ | |
202 | void * (*fn)(void *); /* start function */ | |
203 | void * arg; /* argument to start function */ | |
204 | sigset_t mask; /* signal mask */ | |
205 | } create; | |
206 | struct { /* For REQ_FREE: */ | |
fdacb17d | 207 | pthread_t thread_id; /* identifier of thread to free */ |
5afdca00 UD |
208 | } free; |
209 | struct { /* For REQ_PROCESS_EXIT: */ | |
210 | int code; /* exit status */ | |
211 | } exit; | |
3387a425 | 212 | void * post; /* For REQ_POST: the semaphore */ |
5afdca00 UD |
213 | } req_args; |
214 | }; | |
215 | ||
216 | ||
217 | /* Signals used for suspend/restart and for cancellation notification. */ | |
218 | ||
5afdca00 UD |
219 | extern int __pthread_sig_restart; |
220 | extern int __pthread_sig_cancel; | |
ddbf7fef | 221 | |
b92ad8d6 UD |
222 | /* Signal used for interfacing with gdb */ |
223 | ||
224 | extern int __pthread_sig_debug; | |
225 | ||
5afdca00 UD |
226 | /* Global array of thread handles, used for validating a thread id |
227 | and retrieving the corresponding thread descriptor. Also used for | |
228 | mapping the available stack segments. */ | |
229 | ||
230 | extern struct pthread_handle_struct __pthread_handles[PTHREAD_THREADS_MAX]; | |
231 | ||
232 | /* Descriptor of the initial thread */ | |
233 | ||
234 | extern struct _pthread_descr_struct __pthread_initial_thread; | |
235 | ||
236 | /* Descriptor of the manager thread */ | |
237 | ||
238 | extern struct _pthread_descr_struct __pthread_manager_thread; | |
239 | ||
240 | /* Descriptor of the main thread */ | |
241 | ||
242 | extern pthread_descr __pthread_main_thread; | |
243 | ||
244 | /* Limit between the stack of the initial thread (above) and the | |
245 | stacks of other threads (below). Aligned on a STACK_SIZE boundary. | |
246 | Initially 0, meaning that the current thread is (by definition) | |
247 | the initial thread. */ | |
248 | ||
249 | extern char *__pthread_initial_thread_bos; | |
250 | ||
3387a425 UD |
251 | /* Indicate whether at least one thread has a user-defined stack (if 1), |
252 | or all threads have stacks supplied by LinuxThreads (if 0). */ | |
253 | ||
254 | extern int __pthread_nonstandard_stacks; | |
255 | ||
5afdca00 | 256 | /* File descriptor for sending requests to the thread manager. |
fdacb17d | 257 | Initially -1, meaning that __pthread_initialize_manager must be called. */ |
5afdca00 UD |
258 | |
259 | extern int __pthread_manager_request; | |
260 | ||
261 | /* Other end of the pipe for sending requests to the thread manager. */ | |
262 | ||
263 | extern int __pthread_manager_reader; | |
264 | ||
265 | /* Limits of the thread manager stack. */ | |
266 | ||
267 | extern char *__pthread_manager_thread_bos; | |
268 | extern char *__pthread_manager_thread_tos; | |
269 | ||
270 | /* Pending request for a process-wide exit */ | |
271 | ||
272 | extern int __pthread_exit_requested, __pthread_exit_code; | |
273 | ||
3387a425 UD |
274 | /* Set to 1 by gdb if we're debugging */ |
275 | ||
276 | extern volatile int __pthread_threads_debug; | |
277 | ||
a9cb398f UD |
278 | /* Globally enabled events. */ |
279 | extern volatile td_thr_events_t __pthread_threads_events; | |
280 | ||
ab86fbb1 UD |
281 | /* Pointer to descriptor of thread with last event. */ |
282 | extern volatile pthread_descr __pthread_last_event; | |
283 | ||
5afdca00 UD |
284 | /* Return the handle corresponding to a thread id */ |
285 | ||
286 | static inline pthread_handle thread_handle(pthread_t id) | |
287 | { | |
288 | return &__pthread_handles[id % PTHREAD_THREADS_MAX]; | |
289 | } | |
290 | ||
291 | /* Validate a thread handle. Must have acquired h->h_spinlock before. */ | |
292 | ||
293 | static inline int invalid_handle(pthread_handle h, pthread_t id) | |
294 | { | |
295 | return h->h_descr == NULL || h->h_descr->p_tid != id; | |
296 | } | |
297 | ||
298 | /* Fill in defaults left unspecified by pt-machine.h. */ | |
299 | ||
300 | /* The page size we can get from the system. This should likely not be | |
301 | changed by the machine file but, you never know. */ | |
302 | #ifndef PAGE_SIZE | |
303 | #define PAGE_SIZE (sysconf (_SC_PAGE_SIZE)) | |
304 | #endif | |
305 | ||
306 | /* The max size of the thread stack segments. If the default | |
307 | THREAD_SELF implementation is used, this must be a power of two and | |
308 | a multiple of PAGE_SIZE. */ | |
309 | #ifndef STACK_SIZE | |
310 | #define STACK_SIZE (2 * 1024 * 1024) | |
311 | #endif | |
312 | ||
313 | /* The initial size of the thread stack. Must be a multiple of PAGE_SIZE. */ | |
314 | #ifndef INITIAL_STACK_SIZE | |
315 | #define INITIAL_STACK_SIZE (4 * PAGE_SIZE) | |
316 | #endif | |
317 | ||
318 | /* Size of the thread manager stack. The "- 32" avoids wasting space | |
319 | with some malloc() implementations. */ | |
320 | #ifndef THREAD_MANAGER_STACK_SIZE | |
321 | #define THREAD_MANAGER_STACK_SIZE (2 * PAGE_SIZE - 32) | |
322 | #endif | |
323 | ||
324 | /* The base of the "array" of thread stacks. The array will grow down from | |
325 | here. Defaults to the calculated bottom of the initial application | |
326 | stack. */ | |
327 | #ifndef THREAD_STACK_START_ADDRESS | |
328 | #define THREAD_STACK_START_ADDRESS __pthread_initial_thread_bos | |
329 | #endif | |
330 | ||
331 | /* Get some notion of the current stack. Need not be exactly the top | |
332 | of the stack, just something somewhere in the current frame. */ | |
333 | #ifndef CURRENT_STACK_FRAME | |
334 | #define CURRENT_STACK_FRAME ({ char __csf; &__csf; }) | |
335 | #endif | |
336 | ||
337 | /* Recover thread descriptor for the current thread */ | |
338 | ||
3387a425 UD |
339 | extern pthread_descr __pthread_find_self (void) __attribute__ ((const)); |
340 | ||
5afdca00 UD |
341 | static inline pthread_descr thread_self (void) __attribute__ ((const)); |
342 | static inline pthread_descr thread_self (void) | |
343 | { | |
344 | #ifdef THREAD_SELF | |
345 | return THREAD_SELF; | |
346 | #else | |
347 | char *sp = CURRENT_STACK_FRAME; | |
348 | if (sp >= __pthread_initial_thread_bos) | |
349 | return &__pthread_initial_thread; | |
350 | else if (sp >= __pthread_manager_thread_bos | |
351 | && sp < __pthread_manager_thread_tos) | |
352 | return &__pthread_manager_thread; | |
3387a425 UD |
353 | else if (__pthread_nonstandard_stacks) |
354 | return __pthread_find_self(); | |
5afdca00 UD |
355 | else |
356 | return (pthread_descr)(((unsigned long)sp | (STACK_SIZE-1))+1) - 1; | |
357 | #endif | |
358 | } | |
359 | ||
de262537 | 360 | /* If MEMORY_BARRIER isn't defined in pt-machine.h, assume the architecture |
a5a6f926 UD |
361 | doesn't need a memory barrier instruction (e.g. Intel x86). Some |
362 | architectures distinguish between normal/read and write barriers. */ | |
de262537 UD |
363 | |
364 | #ifndef MEMORY_BARRIER | |
365 | #define MEMORY_BARRIER() | |
366 | #endif | |
a5a6f926 UD |
367 | #ifndef WRITE_MEMORY_BARRIER |
368 | #define WRITE_MEMORY_BARRIER() MEMORY_BARRIER() | |
369 | #endif | |
de262537 | 370 | |
8619129f UD |
371 | /* Max number of times we must spin on a spinlock calling sched_yield(). |
372 | After MAX_SPIN_COUNT iterations, we put the calling thread to sleep. */ | |
373 | ||
374 | #ifndef MAX_SPIN_COUNT | |
375 | #define MAX_SPIN_COUNT 50 | |
376 | #endif | |
377 | ||
378 | /* Duration of sleep (in nanoseconds) when we can't acquire a spinlock | |
379 | after MAX_SPIN_COUNT iterations of sched_yield(). | |
380 | With the 2.0 and 2.1 kernels, this MUST BE > 2ms. | |
381 | (Otherwise the kernel does busy-waiting for realtime threads, | |
382 | giving other threads no chance to run.) */ | |
383 | ||
384 | #ifndef SPIN_SLEEP_DURATION | |
385 | #define SPIN_SLEEP_DURATION 2000001 | |
386 | #endif | |
387 | ||
5afdca00 UD |
388 | /* Debugging */ |
389 | ||
390 | #ifdef DEBUG | |
391 | #include <assert.h> | |
392 | #define ASSERT assert | |
393 | #define MSG __pthread_message | |
394 | #else | |
395 | #define ASSERT(x) | |
396 | #define MSG(msg,arg...) | |
397 | #endif | |
398 | ||
399 | /* Internal global functions */ | |
400 | ||
401 | void __pthread_destroy_specifics(void); | |
402 | void __pthread_perform_cleanup(void); | |
3387a425 UD |
403 | int __pthread_initialize_manager(void); |
404 | void __pthread_message(char * fmt, ...); | |
5afdca00 | 405 | int __pthread_manager(void *reqfd); |
20bdb31b | 406 | int __pthread_manager_event(void *reqfd); |
5afdca00 UD |
407 | void __pthread_manager_sighandler(int sig); |
408 | void __pthread_reset_main_thread(void); | |
383052e9 | 409 | void __pthread_reset_pthread_once(void); |
5afdca00 | 410 | void __fresetlockfiles(void); |
bf7997b6 | 411 | void __pthread_manager_adjust_prio(int thread_prio); |
1d2fc9b3 | 412 | void __pthread_set_own_extricate_if(pthread_descr self, pthread_extricate_if *peif); |
5afdca00 | 413 | |
a9b5d2ee | 414 | extern int __pthread_attr_setguardsize (pthread_attr_t *__attr, |
d8cf93f4 UD |
415 | size_t __guardsize); |
416 | extern int __pthread_attr_getguardsize (const pthread_attr_t *__attr, | |
417 | size_t *__guardsize); | |
a9b5d2ee | 418 | extern int __pthread_attr_setstackaddr (pthread_attr_t *__attr, |
d8cf93f4 UD |
419 | void *__stackaddr); |
420 | extern int __pthread_attr_getstackaddr (const pthread_attr_t *__attr, | |
421 | void **__stackaddr); | |
a9b5d2ee | 422 | extern int __pthread_attr_setstacksize (pthread_attr_t *__attr, |
d8cf93f4 UD |
423 | size_t __stacksize); |
424 | extern int __pthread_attr_getstacksize (const pthread_attr_t *__attr, | |
425 | size_t *__stacksize); | |
426 | extern int __pthread_getconcurrency (void); | |
427 | extern int __pthread_setconcurrency (int __level); | |
428 | extern int __pthread_mutexattr_gettype (const pthread_mutexattr_t *__attr, | |
429 | int *__kind); | |
430 | extern void __pthread_kill_other_threads_np (void); | |
8632b240 | 431 | |
1d2fc9b3 UD |
432 | void __pthread_restart_old(pthread_descr th); |
433 | void __pthread_suspend_old(pthread_descr self); | |
434 | ||
435 | void __pthread_restart_new(pthread_descr th); | |
436 | void __pthread_suspend_new(pthread_descr self); | |
437 | ||
438 | void __pthread_wait_for_restart_signal(pthread_descr self); | |
439 | ||
440 | void __pthread_init_condvar(int rt_sig_available); | |
441 | ||
442 | /* Global pointers to old or new suspend functions */ | |
443 | ||
444 | extern void (*__pthread_restart)(pthread_descr); | |
445 | extern void (*__pthread_suspend)(pthread_descr); | |
446 | ||
5afdca00 UD |
447 | /* Prototypes for the function without cancelation support when the |
448 | normal version has it. */ | |
449 | extern int __libc_close (int fd); | |
450 | extern int __libc_nanosleep (const struct timespec *requested_time, | |
451 | struct timespec *remaining); | |
5afdca00 | 452 | extern pid_t __libc_waitpid (pid_t pid, int *stat_loc, int options); |
883c331a UD |
453 | |
454 | /* Prototypes for some of the new semaphore functions. */ | |
455 | extern int __new_sem_post (sem_t * sem); | |
a9cb398f UD |
456 | |
457 | /* The functions called the signal events. */ | |
458 | extern void __linuxthreads_create_event (void); | |
459 | extern void __linuxthreads_death_event (void); | |
460 | extern void __linuxthreads_reap_event (void); | |
461 | ||
462 | #endif /* internals.h */ |