1 /******************************************************************************
2 * kernel.c
3 *
4 * Copyright (c) 2002-2005 K A Fraser
5 */
6
7 #include <xen/init.h>
8 #include <xen/lib.h>
9 #include <xen/errno.h>
10 #include <xen/param.h>
11 #include <xen/version.h>
12 #include <xen/sched.h>
13 #include <xen/paging.h>
14 #include <xen/guest_access.h>
15 #include <xen/hypercall.h>
16 #include <xen/hypfs.h>
17 #include <xsm/xsm.h>
18 #include <asm/current.h>
19 #include <public/version.h>
20
21 #ifdef CONFIG_COMPAT
22 #include <compat/version.h>
23
24 CHECK_build_id;
25 CHECK_compile_info;
26 CHECK_feature_info;
27 CHECK_varbuf;
28 #endif
29
30 enum system_state system_state = SYS_STATE_early_boot;
31
32 #ifdef CONFIG_HAS_DIT
33 bool __ro_after_init opt_dit = IS_ENABLED(CONFIG_DIT_DEFAULT);
34 boolean_param("dit", opt_dit);
35 #endif
36
37 static xen_commandline_t saved_cmdline;
38 static const char __initconst opt_builtin_cmdline[] = CONFIG_CMDLINE;
39 char __ro_after_init xen_cap_info[128];
40
assign_integer_param(const struct kernel_param * param,uint64_t val)41 static int assign_integer_param(const struct kernel_param *param, uint64_t val)
42 {
43 switch ( param->len )
44 {
45 case sizeof(uint8_t):
46 if ( val > UINT8_MAX && val < (uint64_t)INT8_MIN )
47 return -EOVERFLOW;
48 *(uint8_t *)param->par.var = val;
49 break;
50 case sizeof(uint16_t):
51 if ( val > UINT16_MAX && val < (uint64_t)INT16_MIN )
52 return -EOVERFLOW;
53 *(uint16_t *)param->par.var = val;
54 break;
55 case sizeof(uint32_t):
56 if ( val > UINT32_MAX && val < (uint64_t)INT32_MIN )
57 return -EOVERFLOW;
58 *(uint32_t *)param->par.var = val;
59 break;
60 case sizeof(uint64_t):
61 *(uint64_t *)param->par.var = val;
62 break;
63 default:
64 BUG();
65 }
66
67 return 0;
68 }
69
parse_params(const char * cmdline,const struct kernel_param * start,const struct kernel_param * end)70 static int __init parse_params(
71 const char *cmdline, const struct kernel_param *start,
72 const struct kernel_param *end)
73 {
74 char opt[MAX_PARAM_SIZE], *optval, *optkey, *q;
75 const char *p = cmdline, *key;
76 const struct kernel_param *param;
77 int rc, final_rc = 0;
78 bool bool_assert, found;
79
80 for ( ; ; )
81 {
82 /* Skip whitespace. */
83 while ( *p == ' ' )
84 p++;
85 if ( *p == '\0' )
86 break;
87
88 /* Grab the next whitespace-delimited option. */
89 q = optkey = opt;
90 while ( (*p != ' ') && (*p != '\0') )
91 {
92 if ( (q-opt) < (sizeof(opt)-1) ) /* avoid overflow */
93 *q++ = *p;
94 p++;
95 }
96 *q = '\0';
97
98 /* Search for value part of a key=value option. */
99 optval = strchr(opt, '=');
100 if ( optval != NULL )
101 {
102 *optval++ = '\0'; /* nul-terminate the option value */
103 q = strpbrk(opt, "([{<");
104 }
105 else
106 {
107 optval = q; /* default option value is empty string */
108 q = NULL;
109 }
110
111 /* Boolean parameters can be inverted with 'no-' prefix. */
112 key = optkey;
113 bool_assert = !!strncmp("no-", optkey, 3);
114 if ( !bool_assert )
115 optkey += 3;
116
117 rc = 0;
118 found = false;
119 for ( param = start; param < end; param++ )
120 {
121 int rctmp;
122 const char *s;
123
124 if ( strcmp(param->name, optkey) )
125 {
126 if ( param->type == OPT_CUSTOM && q &&
127 strlen(param->name) == q + 1 - opt &&
128 !strncmp(param->name, opt, q + 1 - opt) )
129 {
130 found = true;
131 optval[-1] = '=';
132 rctmp = param->par.func(q);
133 optval[-1] = '\0';
134 if ( !rc )
135 rc = rctmp;
136 }
137 continue;
138 }
139
140 rctmp = 0;
141 found = true;
142 switch ( param->type )
143 {
144 case OPT_STR:
145 strlcpy(param->par.var, optval, param->len);
146 break;
147 case OPT_UINT:
148 rctmp = assign_integer_param(
149 param,
150 simple_strtoll(optval, &s, 0));
151 if ( *s )
152 rctmp = -EINVAL;
153 break;
154 case OPT_BOOL:
155 rctmp = *optval ? parse_bool(optval, NULL) : 1;
156 if ( rctmp < 0 )
157 break;
158 if ( !rctmp )
159 bool_assert = !bool_assert;
160 rctmp = 0;
161 assign_integer_param(param, bool_assert);
162 break;
163 case OPT_SIZE:
164 rctmp = assign_integer_param(
165 param,
166 parse_size_and_unit(optval, &s));
167 if ( *s )
168 rctmp = -EINVAL;
169 break;
170 case OPT_CUSTOM:
171 rctmp = -EINVAL;
172 if ( !bool_assert )
173 {
174 if ( *optval )
175 break;
176 safe_strcpy(opt, "no");
177 optval = opt;
178 }
179 rctmp = param->par.func(optval);
180 break;
181 case OPT_IGNORE:
182 break;
183 default:
184 BUG();
185 break;
186 }
187
188 if ( !rc )
189 rc = rctmp;
190 }
191
192 if ( rc )
193 {
194 printk("parameter \"%s\" has invalid value \"%s\", rc=%d!\n",
195 key, optval, rc);
196 final_rc = rc;
197 }
198 if ( !found )
199 {
200 printk("parameter \"%s\" unknown!\n", key);
201 final_rc = -EINVAL;
202 }
203 }
204
205 return final_rc;
206 }
207
_cmdline_parse(const char * cmdline)208 static void __init _cmdline_parse(const char *cmdline)
209 {
210 parse_params(cmdline, __setup_start, __setup_end);
211 }
212
213 /**
214 * cmdline_parse -- parses the xen command line.
215 * If CONFIG_CMDLINE is set, it would be parsed prior to @cmdline.
216 * But if CONFIG_CMDLINE_OVERRIDE is set to y, @cmdline will be ignored.
217 */
cmdline_parse(const char * cmdline)218 void __init cmdline_parse(const char *cmdline)
219 {
220 if ( opt_builtin_cmdline[0] )
221 {
222 printk("Built-in command line: %s\n", opt_builtin_cmdline);
223 _cmdline_parse(opt_builtin_cmdline);
224 }
225
226 #ifndef CONFIG_CMDLINE_OVERRIDE
227 if ( cmdline == NULL )
228 return;
229
230 safe_strcpy(saved_cmdline, cmdline);
231 _cmdline_parse(cmdline);
232 #endif
233 }
234
parse_bool(const char * s,const char * e)235 int parse_bool(const char *s, const char *e)
236 {
237 size_t len = e ? ({ ASSERT(e >= s); e - s; }) : strlen(s);
238
239 switch ( len )
240 {
241 case 1:
242 if ( *s == '1' )
243 return 1;
244 if ( *s == '0' )
245 return 0;
246 break;
247
248 case 2:
249 if ( !strncmp("on", s, 2) )
250 return 1;
251 if ( !strncmp("no", s, 2) )
252 return 0;
253 break;
254
255 case 3:
256 if ( !strncmp("yes", s, 3) )
257 return 1;
258 if ( !strncmp("off", s, 3) )
259 return 0;
260 break;
261
262 case 4:
263 if ( !strncmp("true", s, 4) )
264 return 1;
265 break;
266
267 case 5:
268 if ( !strncmp("false", s, 5) )
269 return 0;
270 break;
271
272 case 6:
273 if ( !strncmp("enable", s, 6) )
274 return 1;
275 break;
276
277 case 7:
278 if ( !strncmp("disable", s, 7) )
279 return 0;
280 break;
281 }
282
283 return -1;
284 }
285
parse_boolean(const char * name,const char * s,const char * e)286 int parse_boolean(const char *name, const char *s, const char *e)
287 {
288 size_t slen, nlen;
289 bool has_neg_prefix = !strncmp(s, "no-", 3);
290
291 if ( has_neg_prefix )
292 s += 3;
293
294 slen = e ? ({ ASSERT(e >= s); e - s; }) : strlen(s);
295 nlen = strlen(name);
296
297 /* Does s now start with name? */
298 if ( slen < nlen || strncmp(s, name, nlen) )
299 return -1;
300
301 /* Exact, unadorned name? Result depends on the 'no-' prefix. */
302 if ( slen == nlen )
303 return !has_neg_prefix;
304
305 /* Inexact match with a 'no-' prefix? Not valid. */
306 if ( has_neg_prefix )
307 return -1;
308
309 /* =$SOMETHING? Defer to the regular boolean parsing. */
310 if ( s[nlen] == '=' )
311 {
312 int b = parse_bool(&s[nlen + 1], e);
313
314 if ( b >= 0 )
315 return b;
316
317 /* Not a boolean, but the name matched. Signal specially. */
318 return -2;
319 }
320
321 /* Unrecognised. Give up. */
322 return -1;
323 }
324
parse_signed_integer(const char * name,const char * s,const char * e,long long * val)325 int __init parse_signed_integer(const char *name, const char *s, const char *e,
326 long long *val)
327 {
328 size_t slen, nlen;
329 const char *str;
330 long long pval;
331
332 slen = e ? ({ ASSERT(e >= s); e - s; }) : strlen(s);
333 nlen = strlen(name);
334
335 if ( !e )
336 e = s + slen;
337
338 /* Check that this is the name we're looking for and a value was provided */
339 if ( slen <= nlen || strncmp(s, name, nlen) || s[nlen] != '=' )
340 return -1;
341
342 pval = simple_strtoll(&s[nlen + 1], &str, 10);
343
344 /* Number not recognised */
345 if ( str != e )
346 return -2;
347
348 *val = pval;
349
350 return 0;
351 }
352
cmdline_strcmp(const char * frag,const char * name)353 int cmdline_strcmp(const char *frag, const char *name)
354 {
355 for ( ; ; frag++, name++ )
356 {
357 unsigned char f = *frag, n = *name;
358 int res = f - n;
359
360 if ( res || n == '\0' )
361 {
362 /*
363 * NUL in 'name' matching a comma, colon, semicolon or equals in
364 * 'frag' implies success.
365 */
366 if ( n == '\0' && (f == ',' || f == ':' || f == ';' || f == '=') )
367 res = 0;
368
369 return res;
370 }
371 }
372 }
373
374 unsigned int tainted;
375
376 /**
377 * print_tainted - return a string to represent the kernel taint state.
378 *
379 * 'C' - Console output is synchronous.
380 * 'E' - An error (e.g. a machine check exceptions) has been injected.
381 * 'H' - HVM forced emulation prefix is permitted.
382 * 'I' - Platform is insecure (usually due to an errata on the platform).
383 * 'M' - Machine had a machine check experience.
384 * 'S' - Out of spec CPU (Incompatible features on one or more cores).
385 *
386 * The string is overwritten by the next call to print_taint().
387 */
print_tainted(char * str)388 char *print_tainted(char *str)
389 {
390 if ( tainted )
391 {
392 snprintf(str, TAINT_STRING_MAX_LEN, "Tainted: %c%c%c%c%c%c",
393 tainted & TAINT_MACHINE_INSECURE ? 'I' : ' ',
394 tainted & TAINT_MACHINE_CHECK ? 'M' : ' ',
395 tainted & TAINT_SYNC_CONSOLE ? 'C' : ' ',
396 tainted & TAINT_ERROR_INJECT ? 'E' : ' ',
397 tainted & TAINT_HVM_FEP ? 'H' : ' ',
398 tainted & TAINT_CPU_OUT_OF_SPEC ? 'S' : ' ');
399 }
400 else
401 {
402 snprintf(str, TAINT_STRING_MAX_LEN, "Not tainted");
403 }
404
405 return str;
406 }
407
add_taint(unsigned int taint)408 void add_taint(unsigned int taint)
409 {
410 tainted |= taint;
411 }
412
413 extern const initcall_t __initcall_start[], __presmp_initcall_end[],
414 __initcall_end[];
415
do_presmp_initcalls(void)416 void __init do_presmp_initcalls(void)
417 {
418 const initcall_t *call;
419 for ( call = __initcall_start; call < __presmp_initcall_end; call++ )
420 (*call)();
421 }
422
do_initcalls(void)423 void __init do_initcalls(void)
424 {
425 const initcall_t *call;
426 for ( call = __presmp_initcall_end; call < __initcall_end; call++ )
427 (*call)();
428 }
429
430 #ifdef CONFIG_HYPFS
431 static unsigned int __read_mostly major_version;
432 static unsigned int __read_mostly minor_version;
433
434 static HYPFS_DIR_INIT(buildinfo, "buildinfo");
435 static HYPFS_DIR_INIT(compileinfo, "compileinfo");
436 static HYPFS_DIR_INIT(version, "version");
437 static HYPFS_UINT_INIT(major, "major", major_version);
438 static HYPFS_UINT_INIT(minor, "minor", minor_version);
439 static HYPFS_STRING_INIT(changeset, "changeset");
440 static HYPFS_STRING_INIT(compiler, "compiler");
441 static HYPFS_STRING_INIT(compile_by, "compile_by");
442 static HYPFS_STRING_INIT(compile_date, "compile_date");
443 static HYPFS_STRING_INIT(compile_domain, "compile_domain");
444 static HYPFS_STRING_INIT(extra, "extra");
445
446 #ifdef CONFIG_HYPFS_CONFIG
447 static HYPFS_STRING_INIT(config, "config");
448 #endif
449
buildinfo_init(void)450 static int __init cf_check buildinfo_init(void)
451 {
452 hypfs_add_dir(&hypfs_root, &buildinfo, true);
453
454 hypfs_string_set_reference(&changeset, xen_changeset());
455 hypfs_add_leaf(&buildinfo, &changeset, true);
456
457 hypfs_add_dir(&buildinfo, &compileinfo, true);
458 hypfs_string_set_reference(&compiler, xen_compiler());
459 hypfs_string_set_reference(&compile_by, xen_compile_by());
460 hypfs_string_set_reference(&compile_date, xen_compile_date());
461 hypfs_string_set_reference(&compile_domain, xen_compile_domain());
462 hypfs_add_leaf(&compileinfo, &compiler, true);
463 hypfs_add_leaf(&compileinfo, &compile_by, true);
464 hypfs_add_leaf(&compileinfo, &compile_date, true);
465 hypfs_add_leaf(&compileinfo, &compile_domain, true);
466
467 major_version = xen_major_version();
468 minor_version = xen_minor_version();
469 hypfs_add_dir(&buildinfo, &version, true);
470 hypfs_string_set_reference(&extra, xen_extra_version());
471 hypfs_add_leaf(&version, &extra, true);
472 hypfs_add_leaf(&version, &major, true);
473 hypfs_add_leaf(&version, &minor, true);
474
475 #ifdef CONFIG_HYPFS_CONFIG
476 config.e.encoding = XEN_HYPFS_ENC_GZIP;
477 config.e.size = xen_config_data_size;
478 config.u.content = xen_config_data;
479 hypfs_add_leaf(&buildinfo, &config, true);
480 #endif
481
482 return 0;
483 }
484 __initcall(buildinfo_init);
485
486 static HYPFS_DIR_INIT(params, "params");
487
param_init(void)488 static int __init cf_check param_init(void)
489 {
490 struct param_hypfs *param;
491
492 hypfs_add_dir(&hypfs_root, ¶ms, true);
493
494 for ( param = __paramhypfs_start; param < __paramhypfs_end; param++ )
495 {
496 if ( param->init_leaf )
497 param->init_leaf(param);
498 else if ( param->hypfs.e.type == XEN_HYPFS_TYPE_STRING )
499 param->hypfs.e.size = strlen(param->hypfs.u.content) + 1;
500 hypfs_add_leaf(¶ms, ¶m->hypfs, true);
501 }
502
503 return 0;
504 }
505 __initcall(param_init);
506 #endif
507
xenver_varbuf_op(int cmd,XEN_GUEST_HANDLE_PARAM (void)arg)508 static long xenver_varbuf_op(int cmd, XEN_GUEST_HANDLE_PARAM(void) arg)
509 {
510 struct xen_varbuf user_str;
511 const char *str = NULL;
512 size_t sz;
513
514 switch ( cmd )
515 {
516 case XENVER_build_id:
517 str = xen_build_id;
518 sz = xen_build_id_len;
519 if ( !sz )
520 return -ENODATA;
521 goto have_len;
522
523 case XENVER_extraversion2:
524 str = xen_extra_version();
525 break;
526
527 case XENVER_changeset2:
528 str = xen_changeset();
529 break;
530
531 case XENVER_commandline2:
532 str = saved_cmdline;
533 break;
534
535 case XENVER_capabilities2:
536 str = xen_cap_info;
537 break;
538
539 default:
540 ASSERT_UNREACHABLE();
541 return -ENODATA;
542 }
543
544 sz = strlen(str);
545
546 have_len:
547 if ( sz > KB(64) ) /* Arbitrary limit. Avoid long-running operations. */
548 return -E2BIG;
549
550 if ( guest_handle_is_null(arg) ) /* Length request */
551 return sz;
552
553 if ( copy_from_guest(&user_str, arg, 1) )
554 return -EFAULT;
555
556 if ( sz > user_str.len )
557 return -ENOBUFS;
558
559 if ( copy_to_guest_offset(arg, offsetof(struct xen_varbuf, buf),
560 str, sz) )
561 return -EFAULT;
562
563 return sz;
564 }
565
do_xen_version(int cmd,XEN_GUEST_HANDLE_PARAM (void)arg)566 long do_xen_version(int cmd, XEN_GUEST_HANDLE_PARAM(void) arg)
567 {
568 bool deny = xsm_xen_version(XSM_OTHER, cmd);
569
570 switch ( cmd )
571 {
572 case XENVER_version:
573 return (xen_major_version() << 16) | xen_minor_version();
574
575 case XENVER_extraversion:
576 {
577 xen_extraversion_t extraversion;
578
579 memset(extraversion, 0, sizeof(extraversion));
580 safe_strcpy(extraversion, deny ? xen_deny() : xen_extra_version());
581 if ( copy_to_guest(arg, extraversion, ARRAY_SIZE(extraversion)) )
582 return -EFAULT;
583 return 0;
584 }
585
586 case XENVER_compile_info:
587 {
588 xen_compile_info_t info;
589
590 memset(&info, 0, sizeof(info));
591 safe_strcpy(info.compiler, deny ? xen_deny() : xen_compiler());
592 safe_strcpy(info.compile_by, deny ? xen_deny() : xen_compile_by());
593 safe_strcpy(info.compile_domain, deny ? xen_deny() : xen_compile_domain());
594 safe_strcpy(info.compile_date, deny ? xen_deny() : xen_compile_date());
595 if ( copy_to_guest(arg, &info, 1) )
596 return -EFAULT;
597 return 0;
598 }
599
600 case XENVER_capabilities:
601 {
602 xen_capabilities_info_t info;
603
604 memset(info, 0, sizeof(info));
605 if ( !deny )
606 safe_strcpy(info, xen_cap_info);
607
608 if ( copy_to_guest(arg, info, ARRAY_SIZE(info)) )
609 return -EFAULT;
610 return 0;
611 }
612
613 case XENVER_platform_parameters:
614 {
615 const struct vcpu *curr = current;
616
617 #ifdef CONFIG_COMPAT
618 if ( curr->hcall_compat )
619 {
620 compat_platform_parameters_t params = {
621 .virt_start = is_pv_vcpu(curr)
622 ? HYPERVISOR_COMPAT_VIRT_START(curr->domain)
623 : 0,
624 };
625
626 if ( copy_to_guest(arg, ¶ms, 1) )
627 return -EFAULT;
628 }
629 else
630 #endif
631 {
632 xen_platform_parameters_t params = {
633 /*
634 * Out of an abundance of caution, retain the useless return
635 * value for 64bit PV guests, but in release builds only.
636 *
637 * This is not expected to cause any problems, but if it does,
638 * the developer impacted will be the one best suited to fix
639 * the caller not to issue this hypercall.
640 */
641 .virt_start = !IS_ENABLED(CONFIG_DEBUG) && is_pv_vcpu(curr)
642 ? HYPERVISOR_VIRT_START
643 : 0,
644 };
645
646 if ( copy_to_guest(arg, ¶ms, 1) )
647 return -EFAULT;
648 }
649
650 return 0;
651 }
652
653 case XENVER_changeset:
654 {
655 xen_changeset_info_t chgset;
656
657 memset(chgset, 0, sizeof(chgset));
658 safe_strcpy(chgset, deny ? xen_deny() : xen_changeset());
659 if ( copy_to_guest(arg, chgset, ARRAY_SIZE(chgset)) )
660 return -EFAULT;
661 return 0;
662 }
663
664 case XENVER_get_features:
665 {
666 xen_feature_info_t fi;
667 struct domain *d = current->domain;
668
669 if ( copy_from_guest(&fi, arg, 1) )
670 return -EFAULT;
671
672 switch ( fi.submap_idx )
673 {
674 case 0:
675 fi.submap = (1U << XENFEAT_memory_op_vnode_supported) |
676 #ifdef CONFIG_X86
677 (1U << XENFEAT_vcpu_time_phys_area) |
678 #endif
679 (1U << XENFEAT_runstate_phys_area);
680 if ( VM_ASSIST(d, pae_extended_cr3) )
681 fi.submap |= (1U << XENFEAT_pae_pgdir_above_4gb);
682 if ( paging_mode_translate(d) )
683 fi.submap |=
684 (1U << XENFEAT_writable_page_tables) |
685 (1U << XENFEAT_auto_translated_physmap);
686 if ( is_hardware_domain(d) )
687 fi.submap |= 1U << XENFEAT_dom0;
688 #ifdef CONFIG_ARM
689 fi.submap |= (1U << XENFEAT_ARM_SMCCC_supported);
690 #endif
691 #ifdef CONFIG_X86
692 if ( is_pv_domain(d) )
693 fi.submap |= (1U << XENFEAT_mmu_pt_update_preserve_ad) |
694 (1U << XENFEAT_highmem_assist) |
695 (1U << XENFEAT_gnttab_map_avail_bits);
696 else
697 fi.submap |= (1U << XENFEAT_hvm_safe_pvclock) |
698 (1U << XENFEAT_hvm_callback_vector) |
699 (has_pirq(d) ? (1U << XENFEAT_hvm_pirqs) : 0);
700 fi.submap |= (1U << XENFEAT_dm_msix_all_writes);
701 #endif
702 if ( !paging_mode_translate(d) || is_domain_direct_mapped(d) )
703 fi.submap |= (1U << XENFEAT_direct_mapped);
704 else
705 fi.submap |= (1U << XENFEAT_not_direct_mapped);
706 break;
707 default:
708 return -EINVAL;
709 }
710
711 if ( __copy_to_guest(arg, &fi, 1) )
712 return -EFAULT;
713 return 0;
714 }
715
716 case XENVER_pagesize:
717 if ( deny )
718 return 0;
719 return (!guest_handle_is_null(arg) ? -EINVAL : PAGE_SIZE);
720
721 case XENVER_guest_handle:
722 {
723 xen_domain_handle_t hdl;
724
725 if ( deny )
726 memset(&hdl, 0, ARRAY_SIZE(hdl));
727
728 BUILD_BUG_ON(ARRAY_SIZE(current->domain->handle) != ARRAY_SIZE(hdl));
729
730 if ( copy_to_guest(arg, deny ? hdl : current->domain->handle,
731 ARRAY_SIZE(hdl) ) )
732 return -EFAULT;
733 return 0;
734 }
735
736 case XENVER_commandline:
737 {
738 size_t len = ARRAY_SIZE(saved_cmdline);
739
740 if ( deny )
741 len = strlen(xen_deny()) + 1;
742
743 if ( copy_to_guest(arg, deny ? xen_deny() : saved_cmdline, len) )
744 return -EFAULT;
745 return 0;
746 }
747
748 case XENVER_build_id:
749 case XENVER_extraversion2:
750 case XENVER_capabilities2:
751 case XENVER_changeset2:
752 case XENVER_commandline2:
753 if ( deny )
754 return -EPERM;
755 return xenver_varbuf_op(cmd, arg);
756 }
757
758 return -ENOSYS;
759 }
760
761 /*
762 * Local variables:
763 * mode: C
764 * c-file-style: "BSD"
765 * c-basic-offset: 4
766 * tab-width: 4
767 * indent-tabs-mode: nil
768 * End:
769 */
770