Print this page
14019 Allow more control over zone init exit actions
Portions contributed by: Joshua M. Clulow <jmc@joyent.com>
Portions contributed by: Andy Fiddaman <andy@omnios.org>
Reviewed by: C Fraire <cfraire@me.com>
Reviewed by: Gordon Ross <Gordon.W.Ross@gmail.com>
Approved by: Robert Mustacchi <rm@fingolfin.org>
| Split |
Close |
| Expand all |
| Collapse all |
--- old/usr/src/lib/libbrand/dtd/brand.dtd.1
+++ new/usr/src/lib/libbrand/dtd/brand.dtd.1
1 1 <?xml version='1.0' encoding='UTF-8' ?>
2 2
3 3 <!--
4 4 CDDL HEADER START
5 5
6 6 The contents of this file are subject to the terms of the
7 7 Common Development and Distribution License (the "License").
8 8 You may not use this file except in compliance with the License.
9 9
10 10 You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
11 11 or http://www.opensolaris.org/os/licensing.
12 12 See the License for the specific language governing permissions
13 13 and limitations under the License.
14 14
15 15 When distributing Covered Code, include this CDDL HEADER in each
16 16 file and include the License file at usr/src/OPENSOLARIS.LICENSE.
17 17 If applicable, add the following below this CDDL HEADER, with the
18 18 fields enclosed by brackets "[]" replaced with your own identifying
19 19 information: Portions Copyright [yyyy] [name of copyright owner]
20 20
21 21 CDDL HEADER END
22 22
23 23 Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
24 24 Copyright (c) 2011, Joyent, Inc. All rights reserved.
25 25
26 26 DO NOT EDIT THIS FILE.
27 27
28 28 Copyright 2014 Nexenta Systems, Inc. All rights reserved.
29 29 -->
30 30
31 31 <!--
32 32 verify_cfg
33 33
34 34 Identifies the program to be invoked by zonecfg to verify that the
35 35 zone's configuration is legal, and that all the configured devices,
36 36 attributes, etc. are legal for this brand.
37 37
38 38 The program is called with a single argument: the path to a file
39 39 containing a temporary config.xml file the zone. It should return 0
40 40 on success and non-0 on failure. Any detailed error messages should be
41 41 displayed to stderr.
42 42
43 43 It has no attributes.
44 44
45 45 -->
46 46 <!ELEMENT verify_cfg (#PCDATA) >
47 47 <!ATTLIST verify_cfg>
48 48 <!--
49 49 verify_adm
50 50
51 51 Identifies the program invoked by zoneadm to perform brand-specific
52 52 checks as to the viability of a zone on this specific machine.
53 53
54 54 The following replacements are performed:
55 55
56 56 %z Name of zone
57 57 %R Zonepath of zone
58 58 Additional arguments, if any, are appended.
59 59
60 60 The program should return 0 on success and non-0 on failure. Any
61 61 detailed error messages should be displayed to stderr.
62 62
63 63 It has no attributes.
64 64
65 65 -->
66 66 <!ELEMENT verify_adm (#PCDATA) >
67 67 <!ATTLIST verify_adm>
68 68
69 69 <!--
70 70 install
71 71
72 72 Identifies the program to invoke when installing a zone. The following
73 73 replacements are performed:
74 74
75 75 %z Name of zone
76 76 %R Zonepath of zone
77 77 Additional arguments, if any, are appended.
78 78
79 79 It has no attributes.
80 80 -->
81 81 <!ELEMENT install (#PCDATA) >
82 82 <!ATTLIST install>
83 83
84 84 <!--
85 85 installopts
86 86
87 87 Identifies the command-line options supported by the brand's
88 88 installation program, allowing zoneadm to parse the install line
89 89 properly.
90 90
91 91 It has no attributes.
92 92 -->
93 93 <!ELEMENT installopts (#PCDATA) >
94 94 <!ATTLIST installopts>
95 95
96 96 <!--
97 97 boot
98 98
99 99 This is a program which gets run by zoneadmd when a zone is booted.
100 100 The program will be invoked as the last step in the zone booting
101 101 process before the the first process is spawned inside the zone.
102 102
103 103 If this programs succeeds it should not generate any output.
104 104 If this program returns an error, any output generated by the
105 105 program will be sent to the zoneadmd message log.
106 106
107 107 The following replacements are performed:
108 108
109 109 %z Name of zone
110 110 %R Zonepath of zone
111 111 Additional arguments, if any, are appended.
112 112
113 113 It has no attributes.
114 114 -->
115 115 <!ELEMENT boot (#PCDATA) >
116 116 <!ATTLIST boot>
117 117
118 118 <!--
119 119 sysboot
120 120
121 121 This is a program that will be run by zoneadm during system boot for an
122 122 installed zone that won't automatically boot.
123 123
124 124 If the program succeeds, then it should not generate output.
125 125 If the program returns an error, then the output it generates will be
126 126 sent to the zones SMF service's message log.
127 127
128 128 The following replacements are performed:
129 129
130 130 %z Name of the target zone
131 131 %R Zonepath of the target zone
132 132 Additional arguments, if any, are appended.
133 133
134 134 This element has no attributes.
135 135 -->
136 136 <!ELEMENT sysboot (#PCDATA) >
137 137 <!ATTLIST sysboot>
138 138
139 139 <!--
140 140 halt
141 141
142 142 This is a program which gets run by zoneadmd when a zone is being
143 143 halted. This callback is provided to allow a brand to cleanup any
144 144 special configuration that was setup during boot.
145 145
146 146 This program will also be invoked by zoneadmd if any part of the zone
147 147 booting process fail, even if the booting process failed before the
148 148 brand boot program was invoked. It is also possible that if the zone
149 149 fails to halt after invoking this program, future attempts to halt the
150 150 zone will invoke this program again. So this program should be
151 151 designed to clean up any resources allocated to a zone but it should
152 152 also be able to gracefully handle the case where resources that it
153 153 expects to release are not actually allocated (or have been already
154 154 released.)
155 155
156 156 If this programs succeeds it should not generate any output. If this
157 157 program returns an error, any output generated by the program will be
158 158 sent to the zoneadmd message log.
159 159
160 160 The following replacements are performed:
161 161
162 162 %z Name of zone
163 163 %R Zonepath of zone
164 164 Additional arguments, if any, are appended.
165 165
166 166 It has no attributes.
167 167 -->
168 168 <!ELEMENT halt (#PCDATA) >
169 169 <!ATTLIST halt>
170 170
171 171 <!--
172 172 shutdown
173 173
174 174 This is a program which gets run by zoneadmd when a zone is being
175 175 shutdown gracefully. Currently only asynchronous mode is supported.
176 176
177 177 If this program succeeds it should not generate any output. If this
178 178 program returns an error, any output generated by the program will be
179 179 sent to the zoneadmd message log.
180 180
181 181 The following replacements are performed:
182 182
183 183 %z Name of zone
184 184 %R Zonepath of zone
185 185 Additional arguments, if any, are appended.
186 186
187 187 It has no attributes.
188 188 -->
189 189 <!ELEMENT shutdown (#PCDATA) >
190 190 <!ATTLIST shutdown>
191 191
192 192 <!--
193 193 modname
194 194
195 195 Path to the kernel module that implements the kernel-level
196 196 functionality of the brand.
197 197
198 198 It has no attributes.
199 199 -->
200 200 <!ELEMENT modname (#PCDATA) >
201 201 <!ATTLIST modname>
202 202
203 203 <!--
204 204 initname
|
↓ open down ↓ |
204 lines elided |
↑ open up ↑ |
205 205
206 206 Path to the initial executable that should be launched when booting a
207 207 branded zone.
208 208
209 209 It has no attributes.
210 210 -->
211 211 <!ELEMENT initname (#PCDATA) >
212 212 <!ATTLIST initname>
213 213
214 214 <!--
215 - restartinit
215 + restartinit, restartinit0 and restartinitreboot
216 216
217 - Boolean indicating that the program specified by the initname attr
218 - should be restarted, or not, if it exits. By default, the init program
219 - will be restarted if this attribute is not provided. Specifying false
220 - for this attr will prevent that.
217 + These three boolean elements control what action is taken when the
218 + program specified by the 'initname' element exits.
221 219
222 - It has no attributes.
220 + The default values are:
221 +
222 + restartinit: true
223 + restartinit0: false
224 + restartinitreboot: false
225 +
226 + If 'restartinit' is set to false then the init process will never be
227 + restarted and the zone will shut down once init exits. In this case, the
228 + other restartinit elements are ignored.
229 +
230 + When 'restartinit0' is set, init will only be restarted if it exited with
231 + an exit status of 0, otherwise the zone will shut down.
232 +
233 + If 'restartinitreboot' is set to true then whenever init should be
234 + restarted, based on the other restartinit elements, the zone will instead
235 + be rebooted.
236 +
237 + These have no attributes.
223 238 -->
224 239 <!ELEMENT restartinit (#PCDATA) >
225 240 <!ATTLIST restartinit>
241 +<!ELEMENT restartinit0 (#PCDATA) >
242 +<!ATTLIST restartinit0>
243 +<!ELEMENT restartinitreboot (#PCDATA) >
244 +<!ATTLIST restartinitreboot>
226 245
227 246 <!--
228 247 login_cmd
229 248
230 249 Path to the initial login binary that should be executed when
231 250 attempting to zlogin into a branded zone.
232 251
233 252 The following replacements are performed:
234 253
235 254 %Z Name of the current zone
236 255 %u User login name
237 256
238 257 It has no attributes.
239 258 -->
240 259 <!ELEMENT login_cmd (#PCDATA) >
241 260 <!ATTLIST login_cmd>
242 261
243 262 <!--
244 263 forcedlogin_cmd
245 264
246 265 Path to the initial login binary that should be executed when
247 266 attempting to zlogin into a branded zone without authentication.
248 267
249 268 The following replacements are performed:
250 269
251 270 %Z Name of the current zone
252 271 %u User login name
253 272
254 273 It has no attributes.
255 274 -->
256 275 <!ELEMENT forcedlogin_cmd (#PCDATA) >
257 276 <!ATTLIST forcedlogin_cmd>
258 277
259 278 <!--
260 279 user_cmd
261 280
262 281 Path to the binary that will translate a user name to a passwd(4) entry.
263 282
264 283 The following replacements are performed:
265 284
266 285 %u User login name
267 286
268 287 It has no attributes. The passwd(4) entry is used to determine $LOGNAME,
269 288 $HOME, and $SHELL for non-interactive "zlogin -l <user> <cmd>".
270 289 -->
271 290 <!ELEMENT user_cmd (#PCDATA) >
272 291 <!ATTLIST user_cmd>
273 292
274 293 <!--
275 294 attach
276 295
277 296 Path to a hook that will perform any necessary processing on
278 297 a zone to allow it to be attached. The zone will be in the "configured"
279 298 state when this hook is run. This hook is never called when the zone
280 299 is "force attached" (-F).
281 300
282 301 If this hook exits with a non-zero exit status, the attach operation
283 302 will fail.
284 303
285 304 The following replacements are performed:
286 305
287 306 %z Name of zone
288 307 %R Zonepath of zone
289 308 Additional arguments, if any, are appended.
290 309
291 310 If no hook is provided, the internal zoneadm attach code will be used.
292 311
293 312 It has no attributes.
294 313 -->
295 314 <!ELEMENT attach (#PCDATA) >
296 315 <!ATTLIST attach>
297 316
298 317 <!--
299 318 postattach
300 319
301 320 Path to a hook that will perform any necessary post-processing on
302 321 a zone after it has been attached. The zone will be in the "installed"
303 322 state when this hook is run. This hook is never called when the zone
304 323 is "force attached" (-F).
305 324
306 325 If this hook exits with a non-zero exit status, the attach operation
307 326 will fail and the zone state will be reset to "configured".
308 327
309 328 The following replacements are performed:
310 329
311 330 %z Name of zone
312 331 %R Zonepath of zone
313 332 Additional arguments, if any, are appended.
314 333
315 334 It has no attributes.
316 335 -->
317 336 <!ELEMENT postattach (#PCDATA) >
318 337 <!ATTLIST postattach>
319 338
320 339 <!--
321 340 postclone
322 341
323 342 Path to a hook that will perform any necessary post-processing on
324 343 a zone after it has been cloned. The zone will be in the "incomplete"
325 344 state when this hook is run.
326 345
327 346 If this hook exits with a non-zero exit status, the clone operation
328 347 will fail and the zone will be left in the "incomplete" state,
329 348 otherwise the state will be changed to the "installed" state.
330 349
331 350 The following replacements are performed:
332 351
333 352 %z Name of zone
334 353 %R Zonepath of zone
335 354 Additional arguments, if any, are appended.
336 355
337 356 It has no attributes.
338 357 -->
339 358 <!ELEMENT postclone (#PCDATA) >
340 359 <!ATTLIST postclone>
341 360
342 361 <!--
343 362 postinstall
344 363
345 364 Path to a script that will perform any necessary post-processing on
346 365 a zone after it has been freshly installed. This hook will run after the
347 366 install hook completes and the zone is in the installed state. The
348 367 additional arguments are the same as what is passed to the install hook.
349 368
350 369 The following replacements are performed:
351 370
352 371 %z Name of zone
353 372 %R Zonepath of zone
354 373 Additional arguments, if any, are appended.
355 374
356 375 It has no attributes.
357 376 -->
358 377 <!ELEMENT postinstall (#PCDATA) >
359 378 <!ATTLIST postinstall>
360 379
361 380 <!--
362 381 predetach
363 382
364 383 Path to a hook that will perform any necessary pre-processing on
365 384 a zone before it is detached. The zone will be in the "installed"
366 385 state when this hook is run.
367 386
368 387 It is possible that if the zone fails to detach after invoking this
369 388 hook, future attempts to detach the zone will invoke this hook again.
370 389 So this hook should be designed to gracefully handle the case where
371 390 it is run multiple times on the same zone. If this hook exits with
372 391 a non-zero exit status, the detach operation will fail.
373 392
374 393 This hook is most commonly used when there is pre-processing for detaching
375 394 a zone but the built-in detach support will be used for the actual
376 395 detach. Otherwise, if a detach hook is provided, then it can be used
377 396 to do both preprocessing as well as the actual detach.
378 397
379 398 The following replacements are performed:
380 399
381 400 %z Name of zone
382 401 %R Zonepath of zone
383 402 Additional arguments, if any, are appended.
384 403
385 404 It has no attributes.
386 405 -->
387 406 <!ELEMENT predetach (#PCDATA) >
388 407 <!ATTLIST predetach>
389 408
390 409 <!--
391 410 detach
392 411
393 412 Path to a hook that will perform any necessary processing on
394 413 a zone to allow it to be detached. The zone will be in the "installed"
395 414 state when this hook is run.
396 415
397 416 It is possible that if the zone fails to detach while running this
398 417 hook, future attempts to detach the zone will invoke this hook again.
399 418 So this hook should be designed to gracefully handle the case where
400 419 it is run multiple times on the same zone. If this hook exits with
401 420 a non-zero exit status, the detach operation will fail and the zone will
402 421 be left in the "installed" state, otherwise the state will be changed
403 422 to "configured".
404 423
405 424 The following replacements are performed:
406 425
407 426 %z Name of zone
408 427 %R Zonepath of zone
409 428 Additional arguments, if any, are appended.
410 429
411 430 If no hook is provided, the internal zoneadm detach code will be used.
412 431
413 432 It has no attributes.
414 433 -->
415 434 <!ELEMENT detach (#PCDATA) >
416 435 <!ATTLIST detach>
417 436
418 437 <!--
419 438 clone
420 439 Path to a hook that will perform any necessary processing on a zone to
421 440 allow it to be installed via cloning. Cloning is an alternative to
422 441 installing so this hook should result in the same effect for the zone.
423 442 The zone will be in the "incomplete" state when this hook is run.
424 443
425 444 If this hook exits with a non-zero exit status, the clone operation
426 445 will fail and the zone will be left in the "incomplete" state, otherwise
427 446 the state will be changed to "installed".
428 447
429 448 The following replacements are performed:
430 449
431 450 %z Name of zone
432 451 %R Zonepath of zone
433 452 1st arg name of source zone
434 453 Additional arguments, if any, are appended.
435 454
436 455 If no hook is provided, the internal zoneadm cloning code will be used.
437 456 -->
438 457 <!ELEMENT clone (#PCDATA) >
439 458 <!ATTLIST clone>
440 459
441 460 <!--
442 461 preuninstall
443 462
444 463 Path to a script that will perform any necessary pre-processing on
445 464 a zone before it is uninstalled. The zone will be in the "installed"
446 465 state when this hook is run.
447 466
448 467 It is possible that if the zone fails to uninstall after invoking this
449 468 hook, future attempts to uninstall the zone will invoke this hook
450 469 again. So this hook should be designed to gracefully handle the case
451 470 where it is run multiple times on the same zone. If this hook exits
452 471 with a non-zero exit status, the uninstall operation will fail.
453 472
454 473 The following replacements are performed:
455 474
456 475 %z Name of zone
457 476 %R Zonepath of zone
458 477 Additional arguments, if any, are appended.
459 478
460 479 It has no attributes.
461 480 -->
462 481 <!ELEMENT preuninstall (#PCDATA) >
463 482 <!ATTLIST preuninstall>
464 483
465 484 <!--
466 485 uninstall
467 486 Identifies the hook to invoke when uninstalling a zone. The zone will
468 487 be in the "incomplete" state when this hook is run.
469 488
470 489 If this hook exits with a non-zero exit status, the uninstall operation
471 490 will fail and the zone will be left in the "incomplete" state, otherwise
472 491 the state will be changed to "configured".
473 492
474 493 The following replacements are performed:
475 494
476 495 %z Name of zone
477 496 %R Zonepath of zone
478 497 Additional arguments, if any, are appended.
479 498
480 499 If no hook is provided, the internal zoneadm uninstall code will be used.
481 500 -->
482 501 <!ELEMENT uninstall (#PCDATA) >
483 502 <!ATTLIST uninstall>
484 503
485 504 <!--
486 505 presnap
487 506 Identifies the hook to invoke before snapshotting a zone using the
488 507 built-in ZFS clone support.
489 508
490 509 If this hook exits with a non-zero exit status, the snapshot operation
491 510 will fail and the zfs clone operation will fail.
492 511
493 512 The following replacements are performed:
494 513
495 514 %z Name of zone
496 515 %R Zonepath of zone
497 516 -->
498 517 <!ELEMENT presnap (#PCDATA) >
499 518 <!ATTLIST presnap>
500 519
501 520 <!--
502 521 postsnap
503 522 Identifies the hook to invoke after snapshotting a zone using the
504 523 built-in ZFS clone support.
505 524
506 525 If this hook exits with a non-zero exit status, the zfs clone operation
507 526 will fail.
508 527
509 528 The following replacements are performed:
510 529
511 530 %z Name of zone
512 531 %R Zonepath of zone
513 532 -->
514 533 <!ELEMENT postsnap (#PCDATA) >
515 534 <!ATTLIST postsnap>
516 535
517 536 <!--
518 537 validatesnap
519 538 Identifies the hook to invoke to validate a snapshot of a zone using the
520 539 built-in ZFS clone support. This will validate a snapshot that was
521 540 explicitly specified to the clone command when the user wants to
522 541 re-use a snapshot from an earlier clone operation.
523 542
524 543 If this hook exits with a non-zero exit status, the snapshot validation
525 544 operation will fail, meaning the zfs snapshot cannot be used to install
526 545 the zone.
527 546
528 547 The following replacements are performed:
529 548
530 549 %z Name of zone
531 550 %R Zonepath of zone
532 551 1st arg snapshot name
533 552 2nd arg snapshot path
534 553 -->
535 554 <!ELEMENT validatesnap (#PCDATA) >
536 555 <!ATTLIST validatesnap>
537 556
538 557 <!--
539 558 prestatechange
540 559 Identifies the hook to invoke before zoneadmd makes a state change.
541 560 If this hook exits with a non-zero exit status, the action failed
542 561 and no further state change activity will take place.
543 562
544 563 The following replacements are performed:
545 564
546 565 %z Name of zone
547 566 %R Zonepath of zone
548 567 1st arg integer representing current state of zone
549 568 2 - installed
550 569 3 - ready
551 570 4 - running
552 571 5 - shutting down
553 572 6 - down
554 573 7 - mounted
555 574 2nd arg integer representing transition command
556 575 0 - ready
557 576 1 - boot
558 577 4 - halt
559 578 3rd arg Alternate root (zonepath is mounted under this root)
560 579 empty string if zone not mounted under alternate root
561 580 -->
562 581 <!ELEMENT prestatechange (#PCDATA) >
563 582 <!ATTLIST prestatechange>
564 583
565 584 <!--
566 585 poststatechange
567 586 Identifies the hook to invoke after zoneadmd makes a successful state
568 587 change. If this hook exits with a non-zero exit status, the action failed
569 588 and zoneadmd treats the overall state change as failed, although
570 589 all of the actions up to running the hook will have taken place.
571 590
572 591 The following replacements are performed:
573 592
574 593 %z Name of zone
575 594 %R Zonepath of zone
576 595 See prestatechange comment for 1st, 2nd and 3rd argument values.
577 596 -->
578 597 <!ELEMENT poststatechange (#PCDATA) >
579 598 <!ATTLIST poststatechange>
580 599
581 600 <!--
582 601 query
583 602 Identifies a hook which can be called to get brand-specific information
584 603 about the zone. There is no specific place in zones where this is called,
585 604 calls within the zone infrastructure can be added as needed.
586 605
587 606 One example of the use of this hook is to query the implicit ZFS datasets
588 607 supported by the brand.
589 608
590 609 If this hook exits with a non-zero exit status, the query failed,
591 610 although in general, this hook shouldn't return non-zero.
592 611
593 612 The following replacements are performed:
594 613
595 614 %z Name of zone
596 615 %R Zonepath of zone
597 616 1st arg Arbitrary string which the hook can use to determine what
598 617 data to return. Brands implementing this hook should be
599 618 tolerant of arguments they don't support and simply do
600 619 nothing.
601 620 -->
602 621 <!ELEMENT query (#PCDATA) >
603 622 <!ATTLIST query>
604 623
605 624 <!--
606 625 privilege
607 626
608 627 Add a privilege to the default, prohibited, or required set for all
609 628 zones of this brand with ip-type matched. If a privilege is added
610 629 to the default set all zones of this brand with ip-type matched on
611 630 the system will inherit this privilege unless the privilege is
612 631 removed via limitpriv in zonecfg(1m). If a privilege is added to
613 632 the prohibited set it can not be added to any zones with ip-type
614 633 matched via limitpriv in zonecfg(1m). If a privilege is added to
615 634 the required set then all zones of this brand with ip-type matched
616 635 on the system will inherit this privilege and it can't be removed via
617 636 limitpriv in zonecfg(1m).
618 637
619 638 Its attributes are
|
↓ open down ↓ |
384 lines elided |
↑ open up ↑ |
620 639 set The name of the set the privilege should go into.
621 640 name The name of the privilege.
622 641 ip-type Optional, indicates that adding of the privilege to the
623 642 set only applies to certain IP types. Can be "shared" or
624 643 "exclusive". If it is not specified, the default value
625 644 "all" will be used, which means it is applicable regardless
626 645 the IP type.
627 646
628 647 -->
629 648 <!ELEMENT privilege (#PCDATA) >
630 -<!ATTLIST privilege set ( default | prohibited | required ) #REQUIRED
649 +<!ATTLIST privilege set ( default | prohibited | required ) #REQUIRED
631 650 name CDATA #REQUIRED
632 651 ip-type ( shared | exclusive ) "all" >
633 652
634 653 <!--
635 654 brand
636 655
637 656 The toplevel container for a brand configuration.
638 657
639 658 Its attributes are
640 659
641 660 name The name of the brand. This must match the name of the
642 661 directory in which the configuration file is stored.
643 662 -->
644 663
645 -<!ELEMENT brand (modname?, initname, restartinit?, login_cmd,
664 +<!ELEMENT brand (modname?, initname, restartinit?,
665 + restartinit0?, restartinitreboot?,
666 + login_cmd,
646 667 forcedlogin_cmd, user_cmd, install,
647 668 installopts?, boot?, sysboot?, halt?, shutdown?,
648 669 verify_cfg?, verify_adm?, postattach?, postclone?,
649 670 postinstall?, predetach?, attach?, detach?, clone?,
650 671 presnap?, postsnap?, validatesnap?,
651 672 preuninstall?, uninstall?,
652 673 prestatechange?, poststatechange?, query?,
653 674 privilege+)>
654 675
655 676 <!ATTLIST brand name CDATA #REQUIRED>
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX