]>
Commit | Line | Data |
---|---|---|
726f6388 JA |
1 | This file is enable.def, from which is created enable.c. |
2 | It implements the builtin "enable" in Bash. | |
3 | ||
690150f9 | 4 | Copyright (C) 1987-2016 Free Software Foundation, Inc. |
726f6388 JA |
5 | |
6 | This file is part of GNU Bash, the Bourne Again SHell. | |
7 | ||
2e4498b3 CR |
8 | Bash is free software: you can redistribute it and/or modify |
9 | it under the terms of the GNU General Public License as published by | |
10 | the Free Software Foundation, either version 3 of the License, or | |
11 | (at your option) any later version. | |
12 | ||
13 | Bash is distributed in the hope that it will be useful, | |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | GNU General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU General Public License | |
19 | along with Bash. If not, see <http://www.gnu.org/licenses/>. | |
726f6388 JA |
20 | |
21 | $PRODUCES enable.c | |
22 | ||
23 | $BUILTIN enable | |
24 | $FUNCTION enable_builtin | |
d3ad40de | 25 | $SHORT_DOC enable [-a] [-dnps] [-f filename] [name ...] |
6a8fd0ed CR |
26 | Enable and disable shell builtins. |
27 | ||
9cbcc93b CR |
28 | Enables and disables builtin shell commands. Disabling allows you to |
29 | execute a disk command which has the same name as a shell builtin | |
30 | without using a full pathname. | |
31 | ||
32 | Options: | |
33 | -a print a list of builtins showing whether or not each is enabled | |
34 | -n disable each NAME or display a list of disabled builtins | |
35 | -p print the list of builtins in a reusable format | |
36 | -s print only the names of Posix `special' builtins | |
37 | ||
38 | Options controlling dynamic loading: | |
39 | -f Load builtin NAME from shared object FILENAME | |
40 | -d Remove a builtin loaded with -f | |
41 | ||
42 | Without options, each NAME is enabled. | |
43 | ||
44 | To use the `test' found in $PATH instead of the shell builtin | |
45 | version, type `enable -n test'. | |
6a8fd0ed CR |
46 | |
47 | Exit Status: | |
48 | Returns success unless NAME is not a shell builtin or an error occurs. | |
726f6388 JA |
49 | $END |
50 | ||
ccc6cda3 JA |
51 | #include <config.h> |
52 | ||
53 | #if defined (HAVE_UNISTD_H) | |
cce855bc JA |
54 | # ifdef _MINIX |
55 | # include <sys/types.h> | |
56 | # endif | |
ccc6cda3 JA |
57 | # include <unistd.h> |
58 | #endif | |
59 | ||
60 | #include <stdio.h> | |
61 | #include "../bashansi.h" | |
5e13499c CR |
62 | #include "../bashintl.h" |
63 | ||
726f6388 JA |
64 | #include "../shell.h" |
65 | #include "../builtins.h" | |
ccc6cda3 | 66 | #include "../flags.h" |
726f6388 | 67 | #include "common.h" |
ccc6cda3 | 68 | #include "bashgetopt.h" |
54a5fbe1 | 69 | #include "findcmd.h" |
ccc6cda3 | 70 | |
bb70624e JA |
71 | #if defined (PROGRAMMABLE_COMPLETION) |
72 | # include "../pcomplete.h" | |
73 | #endif | |
74 | ||
726f6388 JA |
75 | #define ENABLED 1 |
76 | #define DISABLED 2 | |
ccc6cda3 JA |
77 | #define SPECIAL 4 |
78 | ||
79 | #define AFLAG 0x01 | |
80 | #define DFLAG 0x02 | |
81 | #define FFLAG 0x04 | |
82 | #define NFLAG 0x08 | |
83 | #define PFLAG 0x10 | |
84 | #define SFLAG 0x20 | |
726f6388 | 85 | |
f73dda09 JA |
86 | #if defined (HAVE_DLOPEN) && defined (HAVE_DLSYM) |
87 | static int dyn_load_builtin __P((WORD_LIST *, int, char *)); | |
88 | #endif | |
89 | ||
90 | #if defined (HAVE_DLCLOSE) | |
91 | static int dyn_unload_builtin __P((char *)); | |
92 | static void delete_builtin __P((struct builtin *)); | |
93 | static int local_dlclose __P((void *)); | |
94 | #endif | |
95 | ||
54a5fbe1 CR |
96 | #define STRUCT_SUFFIX "_struct" |
97 | /* for now */ | |
98 | #define LOAD_SUFFIX "_builtin_load" | |
99 | #define UNLOAD_SUFFIX "_builtin_unload" | |
100 | ||
f73dda09 JA |
101 | static void list_some_builtins __P((int)); |
102 | static int enable_shell_command __P((char *, int)); | |
726f6388 JA |
103 | |
104 | /* Enable/disable shell commands present in LIST. If list is not specified, | |
105 | then print out a list of shell commands showing which are enabled and | |
106 | which are disabled. */ | |
ccc6cda3 | 107 | int |
726f6388 JA |
108 | enable_builtin (list) |
109 | WORD_LIST *list; | |
110 | { | |
ccc6cda3 JA |
111 | int result, flags; |
112 | int opt, filter; | |
113 | #if defined (HAVE_DLOPEN) && defined (HAVE_DLSYM) | |
114 | char *filename; | |
115 | #endif | |
726f6388 | 116 | |
ccc6cda3 JA |
117 | result = EXECUTION_SUCCESS; |
118 | flags = 0; | |
726f6388 | 119 | |
ccc6cda3 JA |
120 | reset_internal_getopt (); |
121 | while ((opt = internal_getopt (list, "adnpsf:")) != -1) | |
726f6388 | 122 | { |
ccc6cda3 | 123 | switch (opt) |
726f6388 | 124 | { |
ccc6cda3 JA |
125 | case 'a': |
126 | flags |= AFLAG; | |
127 | break; | |
128 | case 'n': | |
129 | flags |= NFLAG; | |
130 | break; | |
131 | case 'p': | |
132 | flags |= PFLAG; | |
133 | break; | |
134 | case 's': | |
135 | flags |= SFLAG; | |
136 | break; | |
137 | case 'f': | |
138 | #if defined (HAVE_DLOPEN) && defined (HAVE_DLSYM) | |
139 | flags |= FFLAG; | |
140 | filename = list_optarg; | |
141 | break; | |
142 | #else | |
5e13499c | 143 | builtin_error (_("dynamic loading not available")); |
ccc6cda3 JA |
144 | return (EX_USAGE); |
145 | #endif | |
146 | #if defined (HAVE_DLCLOSE) | |
147 | case 'd': | |
148 | flags |= DFLAG; | |
149 | break; | |
150 | #else | |
5e13499c | 151 | builtin_error (_("dynamic loading not available")); |
ccc6cda3 JA |
152 | return (EX_USAGE); |
153 | #endif /* HAVE_DLCLOSE */ | |
690150f9 | 154 | CASE_HELPOPT; |
ccc6cda3 JA |
155 | default: |
156 | builtin_usage (); | |
157 | return (EX_USAGE); | |
726f6388 JA |
158 | } |
159 | } | |
160 | ||
ccc6cda3 JA |
161 | list = loptend; |
162 | ||
163 | #if defined (RESTRICTED_SHELL) | |
164 | /* Restricted shells cannot load new builtins. */ | |
165 | if (restricted && (flags & (FFLAG|DFLAG))) | |
726f6388 | 166 | { |
7117c2d2 | 167 | sh_restricted ((char *)NULL); |
ccc6cda3 JA |
168 | return (EXECUTION_FAILURE); |
169 | } | |
170 | #endif | |
726f6388 | 171 | |
ccc6cda3 JA |
172 | if (list == 0 || (flags & PFLAG)) |
173 | { | |
174 | filter = (flags & AFLAG) ? (ENABLED | DISABLED) | |
175 | : (flags & NFLAG) ? DISABLED : ENABLED; | |
176 | ||
177 | if (flags & SFLAG) | |
178 | filter |= SPECIAL; | |
726f6388 JA |
179 | |
180 | list_some_builtins (filter); | |
181 | } | |
ccc6cda3 JA |
182 | #if defined (HAVE_DLOPEN) && defined (HAVE_DLSYM) |
183 | else if (flags & FFLAG) | |
184 | { | |
185 | filter = (flags & NFLAG) ? DISABLED : ENABLED; | |
186 | if (flags & SFLAG) | |
28ef6c31 | 187 | filter |= SPECIAL; |
ccc6cda3 JA |
188 | |
189 | result = dyn_load_builtin (list, filter, filename); | |
bb70624e JA |
190 | #if defined (PROGRAMMABLE_COMPLETION) |
191 | set_itemlist_dirty (&it_builtins); | |
192 | #endif | |
ccc6cda3 JA |
193 | } |
194 | #endif | |
195 | #if defined (HAVE_DLCLOSE) | |
196 | else if (flags & DFLAG) | |
197 | { | |
198 | while (list) | |
199 | { | |
200 | opt = dyn_unload_builtin (list->word->word); | |
201 | if (opt == EXECUTION_FAILURE) | |
202 | result = EXECUTION_FAILURE; | |
203 | list = list->next; | |
204 | } | |
bb70624e JA |
205 | #if defined (PROGRAMMABLE_COMPLETION) |
206 | set_itemlist_dirty (&it_builtins); | |
207 | #endif | |
ccc6cda3 JA |
208 | } |
209 | #endif | |
726f6388 JA |
210 | else |
211 | { | |
212 | while (list) | |
213 | { | |
ccc6cda3 | 214 | opt = enable_shell_command (list->word->word, flags & NFLAG); |
726f6388 | 215 | |
ccc6cda3 | 216 | if (opt == EXECUTION_FAILURE) |
726f6388 | 217 | { |
5e13499c | 218 | sh_notbuiltin (list->word->word); |
ccc6cda3 | 219 | result = EXECUTION_FAILURE; |
726f6388 JA |
220 | } |
221 | list = list->next; | |
222 | } | |
223 | } | |
ccc6cda3 | 224 | return (result); |
726f6388 JA |
225 | } |
226 | ||
227 | /* List some builtins. | |
228 | FILTER is a mask with two slots: ENABLED and DISABLED. */ | |
229 | static void | |
230 | list_some_builtins (filter) | |
231 | int filter; | |
232 | { | |
233 | register int i; | |
234 | ||
235 | for (i = 0; i < num_shell_builtins; i++) | |
236 | { | |
ccc6cda3 | 237 | if (shell_builtins[i].function == 0 || (shell_builtins[i].flags & BUILTIN_DELETED)) |
726f6388 JA |
238 | continue; |
239 | ||
ccc6cda3 JA |
240 | if ((filter & SPECIAL) && |
241 | (shell_builtins[i].flags & SPECIAL_BUILTIN) == 0) | |
242 | continue; | |
243 | ||
244 | if ((filter & ENABLED) && (shell_builtins[i].flags & BUILTIN_ENABLED)) | |
245 | printf ("enable %s\n", shell_builtins[i].name); | |
726f6388 JA |
246 | else if ((filter & DISABLED) && |
247 | ((shell_builtins[i].flags & BUILTIN_ENABLED) == 0)) | |
ccc6cda3 | 248 | printf ("enable -n %s\n", shell_builtins[i].name); |
726f6388 JA |
249 | } |
250 | } | |
251 | ||
252 | /* Enable the shell command NAME. If DISABLE_P is non-zero, then | |
253 | disable NAME instead. */ | |
254 | static int | |
255 | enable_shell_command (name, disable_p) | |
256 | char *name; | |
257 | int disable_p; | |
258 | { | |
ccc6cda3 | 259 | struct builtin *b; |
726f6388 | 260 | |
ccc6cda3 JA |
261 | b = builtin_address_internal (name, 1); |
262 | if (b == 0) | |
263 | return (EXECUTION_FAILURE); | |
264 | ||
265 | if (disable_p) | |
266 | b->flags &= ~BUILTIN_ENABLED; | |
7117c2d2 JA |
267 | #if defined (RESTRICTED_SHELL) |
268 | else if (restricted && ((b->flags & BUILTIN_ENABLED) == 0)) | |
269 | { | |
270 | sh_restricted ((char *)NULL); | |
271 | return (EXECUTION_FAILURE); | |
272 | } | |
273 | #endif | |
ccc6cda3 JA |
274 | else |
275 | b->flags |= BUILTIN_ENABLED; | |
276 | ||
bb70624e JA |
277 | #if defined (PROGRAMMABLE_COMPLETION) |
278 | set_itemlist_dirty (&it_enabled); | |
279 | set_itemlist_dirty (&it_disabled); | |
280 | #endif | |
281 | ||
ccc6cda3 JA |
282 | return (EXECUTION_SUCCESS); |
283 | } | |
284 | ||
285 | #if defined (HAVE_DLOPEN) && defined (HAVE_DLSYM) | |
d166f048 JA |
286 | |
287 | #if defined (HAVE_DLFCN_H) | |
288 | # include <dlfcn.h> | |
289 | #endif | |
ccc6cda3 JA |
290 | |
291 | static int | |
292 | dyn_load_builtin (list, flags, filename) | |
293 | WORD_LIST *list; | |
294 | int flags; | |
295 | char *filename; | |
296 | { | |
297 | WORD_LIST *l; | |
298 | void *handle; | |
299 | ||
54a5fbe1 CR |
300 | int total, size, new, replaced, r; |
301 | char *struct_name, *name, *funcname; | |
302 | sh_load_func_t *loadfunc; | |
ccc6cda3 | 303 | struct builtin **new_builtins, *b, *new_shell_builtins, *old_builtin; |
54a5fbe1 | 304 | char *loadables_path, *load_path; |
ccc6cda3 JA |
305 | |
306 | if (list == 0) | |
307 | return (EXECUTION_FAILURE); | |
308 | ||
309 | #ifndef RTLD_LAZY | |
310 | #define RTLD_LAZY 1 | |
311 | #endif | |
312 | ||
54a5fbe1 CR |
313 | handle = 0; |
314 | if (absolute_program (filename) == 0) | |
315 | { | |
316 | loadables_path = get_string_value ("BASH_LOADABLES_PATH"); | |
317 | if (loadables_path) | |
318 | { | |
319 | load_path = find_in_path (filename, loadables_path, FS_NODIRS|FS_EXEC_PREFERRED); | |
320 | if (load_path) | |
321 | { | |
ccc6cda3 | 322 | #if defined (_AIX) |
54a5fbe1 | 323 | handle = dlopen (load_path, RTLD_NOW|RTLD_GLOBAL); |
ccc6cda3 | 324 | #else |
54a5fbe1 CR |
325 | handle = dlopen (load_path, RTLD_LAZY); |
326 | #endif /* !_AIX */ | |
327 | free (load_path); | |
328 | } | |
329 | } | |
330 | } | |
331 | ||
332 | /* Fall back to current directory for now */ | |
333 | if (handle == 0) | |
334 | #if defined (_AIX) | |
335 | handle = dlopen (filename, RTLD_NOW|RTLD_GLOBAL); | |
336 | #else | |
337 | handle = dlopen (filename, RTLD_LAZY); | |
ccc6cda3 JA |
338 | #endif /* !_AIX */ |
339 | ||
340 | if (handle == 0) | |
726f6388 | 341 | { |
96f3fb66 CR |
342 | name = printable_filename (filename, 0); |
343 | builtin_error (_("cannot open shared object %s: %s"), name, dlerror ()); | |
344 | if (name != filename) | |
345 | free (name); | |
ccc6cda3 JA |
346 | return (EXECUTION_FAILURE); |
347 | } | |
726f6388 | 348 | |
ccc6cda3 JA |
349 | for (new = 0, l = list; l; l = l->next, new++) |
350 | ; | |
351 | new_builtins = (struct builtin **)xmalloc (new * sizeof (struct builtin *)); | |
726f6388 | 352 | |
ccc6cda3 JA |
353 | /* For each new builtin in the shared object, find it and its describing |
354 | structure. If this is overwriting an existing builtin, do so, otherwise | |
355 | save the loaded struct for creating the new list of builtins. */ | |
356 | for (replaced = new = 0; list; list = list->next) | |
357 | { | |
358 | name = list->word->word; | |
359 | ||
360 | size = strlen (name); | |
f73dda09 | 361 | struct_name = (char *)xmalloc (size + 8); |
ccc6cda3 | 362 | strcpy (struct_name, name); |
54a5fbe1 | 363 | strcpy (struct_name + size, STRUCT_SUFFIX); |
ccc6cda3 JA |
364 | |
365 | b = (struct builtin *)dlsym (handle, struct_name); | |
366 | if (b == 0) | |
367 | { | |
96f3fb66 | 368 | name = printable_filename (filename, 0); |
5e13499c | 369 | builtin_error (_("cannot find %s in shared object %s: %s"), |
96f3fb66 CR |
370 | struct_name, name, dlerror ()); |
371 | if (name != filename) | |
372 | free (name); | |
ccc6cda3 JA |
373 | free (struct_name); |
374 | continue; | |
726f6388 | 375 | } |
ccc6cda3 | 376 | |
54a5fbe1 CR |
377 | funcname = xrealloc (struct_name, size + sizeof (LOAD_SUFFIX) + 1); |
378 | strcpy (funcname, name); | |
379 | strcpy (funcname + size, LOAD_SUFFIX); | |
380 | ||
381 | loadfunc = (sh_load_func_t *)dlsym (handle, funcname); | |
382 | if (loadfunc) | |
383 | { | |
384 | r = (*loadfunc) (name); | |
385 | if (r == 0) | |
386 | { | |
690150f9 | 387 | builtin_error (_("load function for %s returns failure (%d): not loaded"), name, r); |
54a5fbe1 CR |
388 | free (funcname); |
389 | continue; | |
390 | } | |
391 | } | |
392 | free (funcname); | |
ccc6cda3 JA |
393 | |
394 | b->flags &= ~STATIC_BUILTIN; | |
395 | if (flags & SPECIAL) | |
396 | b->flags |= SPECIAL_BUILTIN; | |
397 | b->handle = handle; | |
398 | ||
399 | if (old_builtin = builtin_address_internal (name, 1)) | |
28ef6c31 JA |
400 | { |
401 | replaced++; | |
ccc6cda3 | 402 | FASTCOPY ((char *)b, (char *)old_builtin, sizeof (struct builtin)); |
28ef6c31 | 403 | } |
ccc6cda3 JA |
404 | else |
405 | new_builtins[new++] = b; | |
406 | } | |
407 | ||
408 | if (replaced == 0 && new == 0) | |
409 | { | |
410 | free (new_builtins); | |
411 | dlclose (handle); | |
412 | return (EXECUTION_FAILURE); | |
726f6388 | 413 | } |
ccc6cda3 JA |
414 | |
415 | if (new) | |
416 | { | |
417 | total = num_shell_builtins + new; | |
418 | size = (total + 1) * sizeof (struct builtin); | |
419 | ||
420 | new_shell_builtins = (struct builtin *)xmalloc (size); | |
421 | FASTCOPY ((char *)shell_builtins, (char *)new_shell_builtins, | |
422 | num_shell_builtins * sizeof (struct builtin)); | |
423 | for (replaced = 0; replaced < new; replaced++) | |
424 | FASTCOPY ((char *)new_builtins[replaced], | |
425 | (char *)&new_shell_builtins[num_shell_builtins + replaced], | |
426 | sizeof (struct builtin)); | |
427 | ||
428 | new_shell_builtins[total].name = (char *)0; | |
f73dda09 | 429 | new_shell_builtins[total].function = (sh_builtin_func_t *)0; |
ccc6cda3 JA |
430 | new_shell_builtins[total].flags = 0; |
431 | ||
432 | if (shell_builtins != static_shell_builtins) | |
433 | free (shell_builtins); | |
434 | ||
435 | shell_builtins = new_shell_builtins; | |
436 | num_shell_builtins = total; | |
437 | initialize_shell_builtins (); | |
438 | } | |
439 | ||
440 | free (new_builtins); | |
441 | return (EXECUTION_SUCCESS); | |
442 | } | |
443 | #endif | |
444 | ||
445 | #if defined (HAVE_DLCLOSE) | |
446 | static void | |
447 | delete_builtin (b) | |
448 | struct builtin *b; | |
449 | { | |
450 | int ind, size; | |
451 | struct builtin *new_shell_builtins; | |
452 | ||
453 | /* XXX - funky pointer arithmetic - XXX */ | |
d166f048 JA |
454 | #ifdef __STDC__ |
455 | ind = b - shell_builtins; | |
456 | #else | |
ccc6cda3 | 457 | ind = ((int)b - (int)shell_builtins) / sizeof (struct builtin); |
d166f048 | 458 | #endif |
ccc6cda3 JA |
459 | size = num_shell_builtins * sizeof (struct builtin); |
460 | new_shell_builtins = (struct builtin *)xmalloc (size); | |
461 | ||
462 | /* Copy shell_builtins[0]...shell_builtins[ind - 1] to new_shell_builtins */ | |
463 | if (ind) | |
464 | FASTCOPY ((char *)shell_builtins, (char *)new_shell_builtins, | |
465 | ind * sizeof (struct builtin)); | |
466 | /* Copy shell_builtins[ind+1]...shell_builtins[num_shell_builtins to | |
467 | new_shell_builtins, starting at ind. */ | |
468 | FASTCOPY ((char *)(&shell_builtins[ind+1]), | |
469 | (char *)(&new_shell_builtins[ind]), | |
470 | (num_shell_builtins - ind) * sizeof (struct builtin)); | |
471 | ||
472 | if (shell_builtins != static_shell_builtins) | |
473 | free (shell_builtins); | |
474 | ||
475 | /* The result is still sorted. */ | |
476 | num_shell_builtins--; | |
477 | shell_builtins = new_shell_builtins; | |
478 | } | |
479 | ||
b72432fd JA |
480 | /* Tenon's MachTen has a dlclose that doesn't return a value, so we |
481 | finesse it with a local wrapper. */ | |
482 | static int | |
483 | local_dlclose (handle) | |
484 | void *handle; | |
485 | { | |
486 | #if !defined (__MACHTEN__) | |
487 | return (dlclose (handle)); | |
488 | #else /* __MACHTEN__ */ | |
489 | dlclose (handle); | |
490 | return ((dlerror () != NULL) ? -1 : 0); | |
491 | #endif /* __MACHTEN__ */ | |
492 | } | |
493 | ||
ccc6cda3 JA |
494 | static int |
495 | dyn_unload_builtin (name) | |
496 | char *name; | |
497 | { | |
498 | struct builtin *b; | |
499 | void *handle; | |
54a5fbe1 CR |
500 | char *funcname; |
501 | sh_unload_func_t *unloadfunc; | |
502 | int ref, i, size; | |
ccc6cda3 JA |
503 | |
504 | b = builtin_address_internal (name, 1); | |
505 | if (b == 0) | |
506 | { | |
5e13499c | 507 | sh_notbuiltin (name); |
ccc6cda3 JA |
508 | return (EXECUTION_FAILURE); |
509 | } | |
510 | if (b->flags & STATIC_BUILTIN) | |
511 | { | |
5e13499c | 512 | builtin_error (_("%s: not dynamically loaded"), name); |
ccc6cda3 JA |
513 | return (EXECUTION_FAILURE); |
514 | } | |
515 | ||
516 | handle = (void *)b->handle; | |
517 | for (ref = i = 0; i < num_shell_builtins; i++) | |
518 | { | |
519 | if (shell_builtins[i].handle == b->handle) | |
520 | ref++; | |
521 | } | |
522 | ||
54a5fbe1 CR |
523 | /* Call any unload function */ |
524 | size = strlen (name); | |
525 | funcname = xmalloc (size + sizeof (UNLOAD_SUFFIX) + 1); | |
526 | strcpy (funcname, name); | |
527 | strcpy (funcname + size, UNLOAD_SUFFIX); | |
528 | ||
529 | unloadfunc = (sh_unload_func_t *)dlsym (handle, funcname); | |
530 | if (unloadfunc) | |
531 | (*unloadfunc) (name); /* void function */ | |
532 | free (funcname); | |
533 | ||
ccc6cda3 JA |
534 | /* Don't remove the shared object unless the reference count of builtins |
535 | using it drops to zero. */ | |
b72432fd | 536 | if (ref == 1 && local_dlclose (handle) != 0) |
ccc6cda3 | 537 | { |
5e13499c | 538 | builtin_error (_("%s: cannot delete: %s"), name, dlerror ()); |
ccc6cda3 JA |
539 | return (EXECUTION_FAILURE); |
540 | } | |
541 | ||
542 | /* Now remove this entry from the builtin table and reinitialize. */ | |
543 | delete_builtin (b); | |
544 | ||
545 | return (EXECUTION_SUCCESS); | |
726f6388 | 546 | } |
ccc6cda3 | 547 | #endif |