1 /*
2 * Copyright 2009-2017 Citrix Ltd and other contributors
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published
6 * by the Free Software Foundation; version 2.1 only. with the special
7 * exception on linking described in file LICENSE.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Lesser General Public License for more details.
13 */
14
15 #include <fcntl.h>
16 #include <inttypes.h>
17 #include <limits.h>
18 #include <stdlib.h>
19 #include <sys/stat.h>
20 #include <sys/types.h>
21 #include <sys/utsname.h>
22 #include <time.h>
23 #include <unistd.h>
24
25 #include <libxl.h>
26 #include <libxl_utils.h>
27 #include <libxlutil.h>
28
29 #include "xl.h"
30 #include "xl_utils.h"
31 #include "xl_parse.h"
32
33 static int fd_lock = -1;
34
35 #ifndef LIBXL_HAVE_NO_SUSPEND_RESUME
suspend_domain(uint32_t domid)36 static void suspend_domain(uint32_t domid)
37 {
38 libxl_domain_suspend_only(ctx, domid, NULL);
39 }
40
resume_domain(uint32_t domid)41 static void resume_domain(uint32_t domid)
42 {
43 libxl_domain_resume(ctx, domid, 1, NULL);
44 }
45
main_suspend(int argc,char ** argv)46 int main_suspend(int argc, char **argv)
47 {
48 int opt;
49
50 SWITCH_FOREACH_OPT(opt, "", NULL, "suspend", 1) {
51 /* No options */
52 }
53
54 suspend_domain(find_domain(argv[optind]));
55
56 return EXIT_SUCCESS;
57 }
58
main_resume(int argc,char ** argv)59 int main_resume(int argc, char **argv)
60 {
61 int opt;
62
63 SWITCH_FOREACH_OPT(opt, "", NULL, "resume", 1) {
64 /* No options */
65 }
66
67 resume_domain(find_domain(argv[optind]));
68
69 return EXIT_SUCCESS;
70 }
71 #endif
72
pause_domain(uint32_t domid)73 static void pause_domain(uint32_t domid)
74 {
75 libxl_domain_pause(ctx, domid, NULL);
76 }
77
unpause_domain(uint32_t domid)78 static void unpause_domain(uint32_t domid)
79 {
80 libxl_domain_unpause(ctx, domid, NULL);
81 }
82
destroy_domain(uint32_t domid,int force)83 static void destroy_domain(uint32_t domid, int force)
84 {
85 int rc;
86
87 if (domid == 0 && !force) {
88 fprintf(stderr, "Not destroying domain 0; use -f to force.\n"
89 "This can only be done when using a disaggregated "
90 "hardware domain and toolstack.\n\n");
91 exit(EXIT_FAILURE);
92 }
93 rc = libxl_domain_destroy(ctx, domid, 0);
94 if (rc) { fprintf(stderr,"destroy failed (rc=%d)\n",rc); exit(EXIT_FAILURE); }
95 }
96
main_pause(int argc,char ** argv)97 int main_pause(int argc, char **argv)
98 {
99 int opt;
100
101 SWITCH_FOREACH_OPT(opt, "", NULL, "pause", 1) {
102 /* No options */
103 }
104
105 pause_domain(find_domain(argv[optind]));
106
107 return EXIT_SUCCESS;
108 }
109
main_unpause(int argc,char ** argv)110 int main_unpause(int argc, char **argv)
111 {
112 int opt;
113
114 SWITCH_FOREACH_OPT(opt, "", NULL, "unpause", 1) {
115 /* No options */
116 }
117
118 unpause_domain(find_domain(argv[optind]));
119
120 return EXIT_SUCCESS;
121 }
122
main_destroy(int argc,char ** argv)123 int main_destroy(int argc, char **argv)
124 {
125 int opt;
126 int force = 0;
127
128 SWITCH_FOREACH_OPT(opt, "f", NULL, "destroy", 1) {
129 case 'f':
130 force = 1;
131 break;
132 }
133
134 destroy_domain(find_domain(argv[optind]), force);
135 return EXIT_SUCCESS;
136 }
137
reboot_domain(uint32_t domid,libxl_evgen_domain_death ** deathw,libxl_ev_user for_user,int fallback_trigger)138 static void reboot_domain(uint32_t domid, libxl_evgen_domain_death **deathw,
139 libxl_ev_user for_user, int fallback_trigger)
140 {
141 int rc;
142
143 fprintf(stderr, "Rebooting domain %u\n", domid);
144 rc = libxl_domain_reboot(ctx, domid, NULL);
145 if (rc == ERROR_NOPARAVIRT) {
146 if (fallback_trigger) {
147 fprintf(stderr, "PV control interface not available:"
148 " sending ACPI reset button event.\n");
149 rc = libxl_send_trigger(ctx, domid, LIBXL_TRIGGER_RESET, 0, NULL);
150 } else {
151 fprintf(stderr, "PV control interface not available:"
152 " external graceful reboot not possible.\n");
153 fprintf(stderr, "Use \"-F\" to fallback to ACPI reset event.\n");
154 }
155 }
156 if (rc) {
157 fprintf(stderr,"reboot failed (rc=%d)\n",rc);exit(EXIT_FAILURE);
158 }
159
160 if (deathw) {
161 rc = libxl_evenable_domain_death(ctx, domid, for_user, deathw);
162 if (rc) {
163 fprintf(stderr,"wait for death failed (evgen, rc=%d)\n",rc);
164 exit(EXIT_FAILURE);
165 }
166 }
167 }
168
shutdown_domain(uint32_t domid,libxl_evgen_domain_death ** deathw,libxl_ev_user for_user,int fallback_trigger)169 static void shutdown_domain(uint32_t domid,
170 libxl_evgen_domain_death **deathw,
171 libxl_ev_user for_user,
172 int fallback_trigger)
173 {
174 int rc;
175
176 fprintf(stderr, "Shutting down domain %u\n", domid);
177 rc = libxl_domain_shutdown(ctx, domid, NULL);
178 if (rc == ERROR_NOPARAVIRT) {
179 if (fallback_trigger) {
180 fprintf(stderr, "PV control interface not available:"
181 " sending ACPI power button event.\n");
182 rc = libxl_send_trigger(ctx, domid, LIBXL_TRIGGER_POWER, 0, NULL);
183 } else {
184 fprintf(stderr, "PV control interface not available:"
185 " external graceful shutdown not possible.\n");
186 fprintf(stderr, "Use \"-F\" to fallback to ACPI power event.\n");
187 }
188 }
189
190 if (rc) {
191 fprintf(stderr,"shutdown failed (rc=%d)\n",rc);exit(EXIT_FAILURE);
192 }
193
194 if (deathw) {
195 rc = libxl_evenable_domain_death(ctx, domid, for_user, deathw);
196 if (rc) {
197 fprintf(stderr,"wait for death failed (evgen, rc=%d)\n",rc);
198 exit(EXIT_FAILURE);
199 }
200 }
201 }
202
wait_for_domain_deaths(libxl_evgen_domain_death ** deathws,int nr,int wait_for_shutdown_or_death)203 static void wait_for_domain_deaths(libxl_evgen_domain_death **deathws, int nr,
204 int wait_for_shutdown_or_death)
205 {
206 int rc, count = 0;
207 LOG("Waiting for %d domains", nr);
208 while(1 && count < nr) {
209 libxl_event *event;
210 rc = libxl_event_wait(ctx, &event, LIBXL_EVENTMASK_ALL, 0,0);
211 if (rc) {
212 LOG("Failed to get event, quitting (rc=%d)", rc);
213 exit(EXIT_FAILURE);
214 }
215
216 switch (event->type) {
217 case LIBXL_EVENT_TYPE_DOMAIN_DEATH:
218 LOG("Domain %d has been destroyed", event->domid);
219 libxl_evdisable_domain_death(ctx, deathws[event->for_user]);
220 count++;
221 break;
222 case LIBXL_EVENT_TYPE_DOMAIN_SHUTDOWN:
223 LOG("Domain %d has been shut down, reason code %d",
224 event->domid, event->u.domain_shutdown.shutdown_reason);
225 if (wait_for_shutdown_or_death) {
226 libxl_evdisable_domain_death(ctx, deathws[event->for_user]);
227 count++;
228 } else {
229 LOG("Domain %d continue waiting for death", event->domid);
230 }
231 break;
232 default:
233 LOG("Unexpected event type %d", event->type);
234 break;
235 }
236 libxl_event_free(ctx, event);
237 }
238 }
239
main_shutdown_or_reboot(int do_reboot,int argc,char ** argv)240 static int main_shutdown_or_reboot(int do_reboot, int argc, char **argv)
241 {
242 const char *what = do_reboot ? "reboot" : "shutdown";
243 void (*fn)(uint32_t domid,
244 libxl_evgen_domain_death **, libxl_ev_user, int) =
245 do_reboot ? &reboot_domain : &shutdown_domain;
246 int opt, i, nb_domain;
247 int wait_for_it = 0, all = 0, nrdeathws = 0;
248 int fallback_trigger = 0;
249 static struct option opts[] = {
250 {"all", 0, 0, 'a'},
251 {"wait", 0, 0, 'w'},
252 COMMON_LONG_OPTS
253 };
254
255 SWITCH_FOREACH_OPT(opt, "awF", opts, what, 0) {
256 case 'a':
257 all = 1;
258 break;
259 case 'w':
260 wait_for_it++;
261 break;
262 case 'F':
263 fallback_trigger = 1;
264 break;
265 }
266
267 if (!argv[optind] && !all) {
268 fprintf(stderr, "You must specify -a or a domain id.\n\n");
269 return EXIT_FAILURE;
270 }
271
272 if (all) {
273 libxl_dominfo *dominfo;
274 libxl_evgen_domain_death **deathws = NULL;
275 if (!(dominfo = libxl_list_domain(ctx, &nb_domain))) {
276 fprintf(stderr, "libxl_list_domain failed.\n");
277 return EXIT_FAILURE;
278 }
279
280 if (wait_for_it)
281 deathws = calloc(nb_domain, sizeof(*deathws));
282
283 for (i = 0; i<nb_domain; i++) {
284 if (dominfo[i].domid == 0 || dominfo[i].never_stop)
285 continue;
286 fn(dominfo[i].domid, deathws ? &deathws[i] : NULL, i,
287 fallback_trigger);
288 nrdeathws++;
289 }
290
291 if (deathws) {
292 wait_for_domain_deaths(deathws, nrdeathws, wait_for_it == 1);
293 free(deathws);
294 }
295
296 libxl_dominfo_list_free(dominfo, nb_domain);
297 } else {
298 libxl_evgen_domain_death *deathw = NULL;
299 uint32_t domid = find_domain(argv[optind]);
300
301 fn(domid, wait_for_it ? &deathw : NULL, 0, fallback_trigger);
302
303 if (wait_for_it)
304 wait_for_domain_deaths(&deathw, 1, wait_for_it == 1);
305 }
306
307
308 return EXIT_SUCCESS;
309 }
310
main_shutdown(int argc,char ** argv)311 int main_shutdown(int argc, char **argv)
312 {
313 return main_shutdown_or_reboot(0, argc, argv);
314 }
315
main_reboot(int argc,char ** argv)316 int main_reboot(int argc, char **argv)
317 {
318 return main_shutdown_or_reboot(1, argc, argv);
319 }
320
evdisable_disk_ejects(libxl_evgen_disk_eject ** diskws,int num_disks)321 static void evdisable_disk_ejects(libxl_evgen_disk_eject **diskws,
322 int num_disks)
323 {
324 int i;
325
326 for (i = 0; i < num_disks; i++) {
327 if (diskws[i])
328 libxl_evdisable_disk_eject(ctx, diskws[i]);
329 diskws[i] = NULL;
330 }
331 }
332
domain_wait_event(uint32_t domid,libxl_event ** event_r)333 static int domain_wait_event(uint32_t domid, libxl_event **event_r)
334 {
335 int ret;
336 for (;;) {
337 ret = libxl_event_wait(ctx, event_r, LIBXL_EVENTMASK_ALL, 0,0);
338 if (ret) {
339 LOG("Domain %u, failed to get event, quitting (rc=%d)", domid, ret);
340 return ret;
341 }
342 if ((*event_r)->domid != domid) {
343 char *evstr = libxl_event_to_json(ctx, *event_r);
344 LOG("INTERNAL PROBLEM - ignoring unexpected event for"
345 " domain %d (expected %d): event=%s",
346 (*event_r)->domid, domid, evstr);
347 free(evstr);
348 libxl_event_free(ctx, *event_r);
349 continue;
350 }
351 return ret;
352 }
353 }
354
355 /*
356 * Returns false if memory can't be freed, but also if we encounter errors.
357 * Returns true in case there is already, or we manage to free it, enough
358 * memory, but also if autoballoon is false.
359 */
freemem(uint32_t domid,libxl_domain_config * d_config)360 static bool freemem(uint32_t domid, libxl_domain_config *d_config)
361 {
362 int rc;
363 double credit = 30;
364 uint64_t need_memkb, free_memkb;
365
366 if (!autoballoon)
367 return true;
368
369 rc = libxl_domain_need_memory(ctx, d_config, domid, &need_memkb);
370 if (rc < 0)
371 return false;
372
373 for (;;) {
374 time_t start;
375
376 rc = libxl_get_free_memory(ctx, &free_memkb);
377 if (rc < 0)
378 return false;
379
380 if (free_memkb >= need_memkb)
381 return true;
382
383 if (credit <= 0)
384 return false;
385
386 rc = libxl_set_memory_target(ctx, 0, free_memkb - need_memkb, 1, 0);
387 if (rc < 0)
388 return false;
389
390 /* wait until dom0 reaches its target, as long as we are making
391 * progress */
392 start = time(NULL);
393 rc = libxl_wait_for_memory_target(ctx, 0, 10);
394 if (rc < 0)
395 return false;
396
397 credit -= difftime(time(NULL), start);
398 }
399 }
400
reload_domain_config(uint32_t domid,libxl_domain_config * d_config)401 static void reload_domain_config(uint32_t domid,
402 libxl_domain_config *d_config)
403 {
404 int rc;
405 uint8_t *t_data;
406 int ret, t_len;
407 libxl_domain_config d_config_new;
408
409 /* In case user has used "config-update" to store a new config
410 * file.
411 */
412 ret = libxl_userdata_retrieve(ctx, domid, "xl", &t_data, &t_len);
413 if (ret && errno != ENOENT) {
414 LOG("\"xl\" configuration found but failed to load\n");
415 }
416 if (t_len > 0) {
417 LOG("\"xl\" configuration found, using it\n");
418 libxl_domain_config_dispose(d_config);
419 libxl_domain_config_init(d_config);
420 parse_config_data("<updated>", (const char *)t_data,
421 t_len, d_config);
422 free(t_data);
423 libxl_userdata_unlink(ctx, domid, "xl");
424 return;
425 }
426
427 libxl_domain_config_init(&d_config_new);
428 rc = libxl_retrieve_domain_configuration(ctx, domid, &d_config_new,
429 NULL);
430 if (rc) {
431 LOG("failed to retrieve guest configuration (rc=%d). "
432 "reusing old configuration", rc);
433 libxl_domain_config_dispose(&d_config_new);
434 } else {
435 libxl_domain_config_dispose(d_config);
436 /* Steal allocations */
437 memcpy(d_config, &d_config_new, sizeof(libxl_domain_config));
438 }
439 }
440
441 /* Can update r_domid if domain is destroyed */
handle_domain_death(uint32_t * r_domid,libxl_event * event,libxl_domain_config * d_config)442 static domain_restart_type handle_domain_death(uint32_t *r_domid,
443 libxl_event *event,
444 libxl_domain_config *d_config)
445 {
446 domain_restart_type restart = DOMAIN_RESTART_NONE;
447 libxl_action_on_shutdown action;
448
449 switch (event->u.domain_shutdown.shutdown_reason) {
450 case LIBXL_SHUTDOWN_REASON_POWEROFF:
451 action = d_config->on_poweroff;
452 break;
453 case LIBXL_SHUTDOWN_REASON_REBOOT:
454 action = d_config->on_reboot;
455 break;
456 case LIBXL_SHUTDOWN_REASON_SUSPEND:
457 LOG("Domain has suspended.");
458 return DOMAIN_RESTART_SUSPENDED;
459 case LIBXL_SHUTDOWN_REASON_CRASH:
460 action = d_config->on_crash;
461 break;
462 case LIBXL_SHUTDOWN_REASON_WATCHDOG:
463 action = d_config->on_watchdog;
464 break;
465 case LIBXL_SHUTDOWN_REASON_SOFT_RESET:
466 action = d_config->on_soft_reset;
467 break;
468 default:
469 LOG("Unknown shutdown reason code %d. Destroying domain.",
470 event->u.domain_shutdown.shutdown_reason);
471 action = LIBXL_ACTION_ON_SHUTDOWN_DESTROY;
472 }
473
474 LOG("Action for shutdown reason code %d is %s",
475 event->u.domain_shutdown.shutdown_reason,
476 get_action_on_shutdown_name(action));
477
478 if (action == LIBXL_ACTION_ON_SHUTDOWN_COREDUMP_DESTROY || action == LIBXL_ACTION_ON_SHUTDOWN_COREDUMP_RESTART) {
479 char *corefile;
480 int rc;
481
482 xasprintf(&corefile, XEN_DUMP_DIR "/%s", d_config->c_info.name);
483 LOG("dumping core to %s", corefile);
484 rc = libxl_domain_core_dump(ctx, *r_domid, corefile, NULL);
485 if (rc) LOG("core dump failed (rc=%d).", rc);
486 free(corefile);
487 /* No point crying over spilled milk, continue on failure. */
488
489 if (action == LIBXL_ACTION_ON_SHUTDOWN_COREDUMP_DESTROY)
490 action = LIBXL_ACTION_ON_SHUTDOWN_DESTROY;
491 else
492 action = LIBXL_ACTION_ON_SHUTDOWN_RESTART;
493 }
494
495 switch (action) {
496 case LIBXL_ACTION_ON_SHUTDOWN_PRESERVE:
497 break;
498
499 case LIBXL_ACTION_ON_SHUTDOWN_RESTART_RENAME:
500 reload_domain_config(*r_domid, d_config);
501 restart = DOMAIN_RESTART_RENAME;
502 break;
503
504 case LIBXL_ACTION_ON_SHUTDOWN_RESTART:
505 reload_domain_config(*r_domid, d_config);
506 restart = DOMAIN_RESTART_NORMAL;
507 /* fall-through */
508 case LIBXL_ACTION_ON_SHUTDOWN_DESTROY:
509 LOG("Domain %d needs to be cleaned up: destroying the domain",
510 *r_domid);
511 libxl_domain_destroy(ctx, *r_domid, 0);
512 *r_domid = INVALID_DOMID;
513 break;
514
515 case LIBXL_ACTION_ON_SHUTDOWN_SOFT_RESET:
516 reload_domain_config(*r_domid, d_config);
517 restart = DOMAIN_RESTART_SOFT_RESET;
518 break;
519
520 case LIBXL_ACTION_ON_SHUTDOWN_COREDUMP_DESTROY:
521 case LIBXL_ACTION_ON_SHUTDOWN_COREDUMP_RESTART:
522 /* Already handled these above. */
523 abort();
524 }
525
526 return restart;
527 }
528
529 /* Preserve a copy of a domain under a new name. Updates *r_domid */
preserve_domain(uint32_t * r_domid,libxl_event * event,libxl_domain_config * d_config)530 static int preserve_domain(uint32_t *r_domid, libxl_event *event,
531 libxl_domain_config *d_config)
532 {
533 time_t now;
534 struct tm tm;
535 char strtime[24];
536
537 libxl_uuid new_uuid;
538
539 int rc;
540
541 now = time(NULL);
542 if (now == ((time_t) -1)) {
543 LOG("Failed to get current time for domain rename");
544 return 0;
545 }
546
547 tzset();
548 if (gmtime_r(&now, &tm) == NULL) {
549 LOG("Failed to convert time to UTC");
550 return 0;
551 }
552
553 if (!strftime(&strtime[0], sizeof(strtime), "-%Y%m%dT%H%MZ", &tm)) {
554 LOG("Failed to format time as a string");
555 return 0;
556 }
557
558 libxl_uuid_generate(&new_uuid);
559
560 LOG("Preserving domain %u %s with suffix%s",
561 *r_domid, d_config->c_info.name, strtime);
562 rc = libxl_domain_preserve(ctx, *r_domid, &d_config->c_info,
563 strtime, new_uuid);
564
565 /*
566 * Although the domain still exists it is no longer the one we are
567 * concerned with.
568 */
569 *r_domid = INVALID_DOMID;
570
571 return rc == 0 ? 1 : 0;
572 }
573
console_child_report(xlchildnum child)574 static void console_child_report(xlchildnum child)
575 {
576 if (xl_child_pid(child))
577 child_report(child);
578 }
579
vncviewer(uint32_t domid,int autopass)580 static int vncviewer(uint32_t domid, int autopass)
581 {
582 libxl_vncviewer_exec(ctx, domid, autopass);
583 fprintf(stderr, "Unable to execute vncviewer\n");
584 return 1;
585 }
586
autoconnect_vncviewer(uint32_t domid,int autopass)587 static void autoconnect_vncviewer(uint32_t domid, int autopass)
588 {
589 console_child_report(child_vncviewer);
590
591 pid_t pid = xl_fork(child_vncviewer, "vncviewer child");
592 if (pid)
593 return;
594
595 postfork();
596
597 sleep(1);
598 vncviewer(domid, autopass);
599 _exit(EXIT_FAILURE);
600 }
601
acquire_lock(void)602 static int acquire_lock(void)
603 {
604 int rc;
605 struct flock fl;
606
607 /* lock already acquired */
608 if (fd_lock >= 0)
609 return ERROR_INVAL;
610
611 fl.l_type = F_WRLCK;
612 fl.l_whence = SEEK_SET;
613 fl.l_start = 0;
614 fl.l_len = 0;
615 fd_lock = open(lockfile, O_WRONLY|O_CREAT, S_IWUSR);
616 if (fd_lock < 0) {
617 fprintf(stderr, "cannot open the lockfile %s errno=%d\n", lockfile, errno);
618 return ERROR_FAIL;
619 }
620 if (fcntl(fd_lock, F_SETFD, FD_CLOEXEC) < 0) {
621 close(fd_lock);
622 fprintf(stderr, "cannot set cloexec to lockfile %s errno=%d\n", lockfile, errno);
623 return ERROR_FAIL;
624 }
625 get_lock:
626 rc = fcntl(fd_lock, F_SETLKW, &fl);
627 if (rc < 0 && errno == EINTR)
628 goto get_lock;
629 if (rc < 0) {
630 fprintf(stderr, "cannot acquire lock %s errno=%d\n", lockfile, errno);
631 rc = ERROR_FAIL;
632 } else
633 rc = 0;
634 return rc;
635 }
636
release_lock(void)637 static int release_lock(void)
638 {
639 int rc;
640 struct flock fl;
641
642 /* lock not acquired */
643 if (fd_lock < 0)
644 return ERROR_INVAL;
645
646 release_lock:
647 fl.l_type = F_UNLCK;
648 fl.l_whence = SEEK_SET;
649 fl.l_start = 0;
650 fl.l_len = 0;
651
652 rc = fcntl(fd_lock, F_SETLKW, &fl);
653 if (rc < 0 && errno == EINTR)
654 goto release_lock;
655 if (rc < 0) {
656 fprintf(stderr, "cannot release lock %s, errno=%d\n", lockfile, errno);
657 rc = ERROR_FAIL;
658 } else
659 rc = 0;
660 close(fd_lock);
661 fd_lock = -1;
662
663 return rc;
664 }
665
666
autoconnect_console(libxl_ctx * ctx_ignored,libxl_event * ev,void * priv)667 static void autoconnect_console(libxl_ctx *ctx_ignored,
668 libxl_event *ev, void *priv)
669 {
670 uint32_t bldomid = ev->domid;
671 int notify_fd = *(int*)priv; /* write end of the notification pipe */
672
673 libxl_event_free(ctx, ev);
674
675 console_child_report(child_console);
676
677 pid_t pid = xl_fork(child_console, "console child");
678 if (pid)
679 return;
680
681 postfork();
682
683 sleep(1);
684 libxl_primary_console_exec(ctx, bldomid, notify_fd, NULL);
685 /* Do not return. xl continued in child process */
686 perror("xl: unable to exec console client");
687 _exit(1);
688 }
689
create_domain(struct domain_create * dom_info)690 int create_domain(struct domain_create *dom_info)
691 {
692 uint32_t domid = INVALID_DOMID;
693
694 libxl_domain_config d_config;
695
696 int debug = dom_info->debug;
697 int daemonize = dom_info->daemonize;
698 int monitor = dom_info->monitor;
699 int paused = dom_info->paused;
700 int vncautopass = dom_info->vncautopass;
701 const char *config_file = dom_info->config_file;
702 const char *extra_config = dom_info->extra_config;
703 const char *restore_file = dom_info->restore_file;
704 const char *config_source = NULL;
705 const char *restore_source = NULL;
706 int migrate_fd = dom_info->migrate_fd;
707 bool config_in_json;
708
709 int i;
710 int need_daemon = daemonize;
711 int ret, rc;
712 libxl_evgen_domain_death *deathw = NULL;
713 libxl_evgen_disk_eject **diskws = NULL; /* one per disk */
714 unsigned int num_diskws = 0;
715 void *config_data = 0;
716 int config_len = 0;
717 int restore_fd = -1;
718 int restore_fd_to_close = -1;
719 int send_back_fd = -1;
720 const libxl_asyncprogress_how *autoconnect_console_how;
721 int notify_pipe[2] = { -1, -1 };
722 struct save_file_header hdr;
723 uint32_t domid_soft_reset = INVALID_DOMID;
724
725 int restoring = (restore_file || (migrate_fd >= 0));
726
727 libxl_domain_config_init(&d_config);
728
729 if (restoring) {
730 uint8_t *optdata_begin = 0;
731 const uint8_t *optdata_here = 0;
732 union { uint32_t u32; char b[4]; } u32buf;
733 uint32_t badflags;
734
735 if (migrate_fd >= 0) {
736 restore_source = "<incoming migration stream>";
737 restore_fd = migrate_fd;
738 send_back_fd = dom_info->send_back_fd;
739 } else {
740 restore_source = restore_file;
741 restore_fd = open(restore_file, O_RDONLY);
742 if (restore_fd == -1) {
743 fprintf(stderr, "Can't open restore file: %s\n", strerror(errno));
744 return ERROR_INVAL;
745 }
746 restore_fd_to_close = restore_fd;
747 rc = libxl_fd_set_cloexec(ctx, restore_fd, 1);
748 if (rc) return rc;
749 }
750
751 CHK_ERRNOVAL(libxl_read_exactly(
752 ctx, restore_fd, &hdr, sizeof(hdr),
753 restore_source, "header"));
754 if (memcmp(hdr.magic, savefileheader_magic, sizeof(hdr.magic))) {
755 fprintf(stderr, "File has wrong magic number -"
756 " corrupt or for a different tool?\n");
757 return ERROR_INVAL;
758 }
759 if (hdr.byteorder != SAVEFILE_BYTEORDER_VALUE) {
760 fprintf(stderr, "File has wrong byte order\n");
761 return ERROR_INVAL;
762 }
763 fprintf(stderr, "Loading new save file %s"
764 " (new xl fmt info"
765 " 0x%"PRIx32"/0x%"PRIx32"/%"PRIu32")\n",
766 restore_source, hdr.mandatory_flags, hdr.optional_flags,
767 hdr.optional_data_len);
768
769 badflags = hdr.mandatory_flags & ~XL_MANDATORY_FLAG_ALL;
770 if (badflags) {
771 fprintf(stderr, "Savefile has mandatory flag(s) 0x%"PRIx32" "
772 "which are not supported; need newer xl\n",
773 badflags);
774 return ERROR_INVAL;
775 }
776 if (hdr.optional_data_len) {
777 optdata_begin = xmalloc(hdr.optional_data_len);
778 CHK_ERRNOVAL(libxl_read_exactly(
779 ctx, restore_fd, optdata_begin,
780 hdr.optional_data_len, restore_source,
781 "optdata"));
782 }
783
784 #define OPTDATA_LEFT (hdr.optional_data_len - (optdata_here - optdata_begin))
785 #define WITH_OPTDATA(amt, body) \
786 if (OPTDATA_LEFT < (amt)) { \
787 fprintf(stderr, "Savefile truncated.\n"); \
788 return ERROR_INVAL; \
789 } else { \
790 body; \
791 optdata_here += (amt); \
792 }
793
794 optdata_here = optdata_begin;
795
796 if (OPTDATA_LEFT) {
797 fprintf(stderr, " Savefile contains xl domain config%s\n",
798 !!(hdr.mandatory_flags & XL_MANDATORY_FLAG_JSON)
799 ? " in JSON format" : "");
800 WITH_OPTDATA(4, {
801 memcpy(u32buf.b, optdata_here, 4);
802 config_len = u32buf.u32;
803 });
804 WITH_OPTDATA(config_len, {
805 config_data = xmalloc(config_len);
806 memcpy(config_data, optdata_here, config_len);
807 });
808 }
809
810 }
811
812 if (config_file) {
813 free(config_data); config_data = 0;
814 /* /dev/null represents special case (read config. from command line) */
815 if (!strcmp(config_file, "/dev/null")) {
816 config_len = 0;
817 } else {
818 ret = libxl_read_file_contents(ctx, config_file,
819 &config_data, &config_len);
820 if (ret) { fprintf(stderr, "Failed to read config file: %s: %s\n",
821 config_file, strerror(errno)); return ERROR_FAIL; }
822 }
823 if (!restoring && extra_config && strlen(extra_config)) {
824 if (config_len > INT_MAX - (strlen(extra_config) + 2 + 1)) {
825 fprintf(stderr, "Failed to attach extra configuration\n");
826 return ERROR_FAIL;
827 }
828 /* allocate space for the extra config plus two EOLs plus \0 */
829 config_data = xrealloc(config_data, config_len
830 + strlen(extra_config) + 2 + 1);
831 config_len += sprintf(config_data + config_len, "\n%s\n",
832 extra_config);
833 }
834 config_source=config_file;
835 config_in_json = false;
836 } else {
837 if (!config_data) {
838 fprintf(stderr, "Config file not specified and"
839 " none in save file\n");
840 return ERROR_INVAL;
841 }
842 config_source = "<saved>";
843 config_in_json = !!(hdr.mandatory_flags & XL_MANDATORY_FLAG_JSON);
844 }
845
846 if (!dom_info->quiet)
847 fprintf(stderr, "Parsing config from %s\n", config_source);
848
849 if (config_in_json) {
850 libxl_domain_config_from_json(ctx, &d_config,
851 (const char *)config_data);
852 } else {
853 parse_config_data(config_source, config_data, config_len, &d_config);
854 }
855
856 if (!dom_info->ignore_global_affinity_masks) {
857 libxl_domain_build_info *b_info = &d_config.b_info;
858
859 /* It is possible that no hard affinity is specified in config file.
860 * Generate hard affinity maps now if we care about those.
861 */
862 if (b_info->num_vcpu_hard_affinity == 0 &&
863 (!libxl_bitmap_is_full(&global_vm_affinity_mask) ||
864 (b_info->type == LIBXL_DOMAIN_TYPE_PV &&
865 !libxl_bitmap_is_full(&global_pv_affinity_mask)) ||
866 (b_info->type != LIBXL_DOMAIN_TYPE_PV &&
867 !libxl_bitmap_is_full(&global_hvm_affinity_mask))
868 )) {
869 b_info->num_vcpu_hard_affinity = b_info->max_vcpus;
870 b_info->vcpu_hard_affinity =
871 xmalloc(b_info->max_vcpus * sizeof(libxl_bitmap));
872
873 for (i = 0; i < b_info->num_vcpu_hard_affinity; i++) {
874 libxl_bitmap *m = &b_info->vcpu_hard_affinity[i];
875 libxl_bitmap_init(m);
876 libxl_cpu_bitmap_alloc(ctx, m, 0);
877 libxl_bitmap_set_any(m);
878 }
879 }
880
881 apply_global_affinity_masks(b_info->type,
882 b_info->vcpu_hard_affinity,
883 b_info->num_vcpu_hard_affinity);
884 }
885
886 if (migrate_fd >= 0) {
887 if (d_config.c_info.name) {
888 /* when we receive a domain we get its name from the config
889 * file; and we receive it to a temporary name */
890 assert(!common_domname);
891
892 common_domname = d_config.c_info.name;
893 d_config.c_info.name = 0; /* steals allocation from config */
894
895 xasprintf(&d_config.c_info.name, "%s--incoming", common_domname);
896 *dom_info->migration_domname_r = strdup(d_config.c_info.name);
897 }
898 }
899
900 if (debug || dryrun_only) {
901 FILE *cfg_print_fh = (debug && !dryrun_only) ? stderr : stdout;
902 if (default_output_format == OUTPUT_FORMAT_SXP) {
903 printf_info_sexp(-1, &d_config, cfg_print_fh);
904 } else {
905 char *json = libxl_domain_config_to_json(ctx, &d_config);
906 if (!json) {
907 fprintf(stderr,
908 "Failed to convert domain configuration to JSON\n");
909 exit(1);
910 }
911 fputs(json, cfg_print_fh);
912 free(json);
913 flush_stream(cfg_print_fh);
914 }
915 }
916
917
918 ret = 0;
919 if (dryrun_only)
920 goto out;
921
922 start:
923 assert(domid == INVALID_DOMID);
924
925 if (autoballoon) {
926 rc = acquire_lock();
927 if (rc < 0)
928 goto error_out;
929 }
930
931 if (domid_soft_reset == INVALID_DOMID) {
932 if (!freemem(domid, &d_config)) {
933 fprintf(stderr, "failed to free memory for the domain\n");
934 ret = ERROR_FAIL;
935 goto error_out;
936 }
937 }
938
939 libxl_asyncprogress_how autoconnect_console_how_buf;
940 if ( dom_info->console_autoconnect ) {
941 if (libxl_pipe(ctx, notify_pipe)) {
942 ret = ERROR_FAIL;
943 goto error_out;
944 }
945 autoconnect_console_how_buf.callback = autoconnect_console;
946 autoconnect_console_how_buf.for_callback = ¬ify_pipe[1];
947 autoconnect_console_how = &autoconnect_console_how_buf;
948 }else{
949 autoconnect_console_how = 0;
950 }
951
952 if (!libxl_domid_valid_guest(d_config.c_info.domid))
953 d_config.c_info.domid = domid_policy;
954
955 if ( restoring ) {
956 libxl_domain_restore_params params;
957
958 libxl_domain_restore_params_init(¶ms);
959
960 params.checkpointed_stream = dom_info->checkpointed_stream;
961 params.stream_version =
962 (hdr.mandatory_flags & XL_MANDATORY_FLAG_STREAMv2) ? 2 : 1;
963 params.colo_proxy_script = dom_info->colo_proxy_script;
964 libxl_defbool_set(¶ms.userspace_colo_proxy,
965 dom_info->userspace_colo_proxy);
966
967 ret = libxl_domain_create_restore(ctx, &d_config,
968 &domid, restore_fd,
969 send_back_fd, ¶ms,
970 0, autoconnect_console_how);
971
972 libxl_domain_restore_params_dispose(¶ms);
973
974 /*
975 * On subsequent reboot etc we should create the domain, not
976 * restore/migrate-receive it again.
977 */
978 restoring = 0;
979 } else if (domid_soft_reset != INVALID_DOMID) {
980 /* Do soft reset. */
981 ret = libxl_domain_soft_reset(ctx, &d_config, domid_soft_reset,
982 0, autoconnect_console_how);
983 domid = domid_soft_reset;
984 domid_soft_reset = INVALID_DOMID;
985 } else {
986 ret = libxl_domain_create_new(ctx, &d_config, &domid,
987 0, autoconnect_console_how);
988 }
989 if ( ret )
990 goto error_out;
991
992 if (autoballoon)
993 release_lock();
994
995 if (restore_fd_to_close >= 0) {
996 if (close(restore_fd_to_close))
997 fprintf(stderr, "Failed to close restoring file, fd %d, errno %d\n",
998 restore_fd_to_close, errno);
999 restore_fd_to_close = -1;
1000 }
1001
1002 if (autoconnect_console_how) {
1003 char buf[1];
1004 int r;
1005
1006 /* Try to get notification from xenconsole. Just move on if
1007 * error occurs -- it's only minor annoyance if console
1008 * doesn't show up.
1009 */
1010 do {
1011 r = read(notify_pipe[0], buf, 1);
1012 } while (r == -1 && errno == EINTR);
1013
1014 if (r == -1)
1015 fprintf(stderr,
1016 "Failed to get notification from xenconsole: %s\n",
1017 strerror(errno));
1018 else if (r == 0)
1019 fprintf(stderr, "Got EOF from xenconsole notification fd\n");
1020 else if (r == 1 && buf[0] != 0x00)
1021 fprintf(stderr, "Got unexpected response from xenconsole: %#x\n",
1022 buf[0]);
1023
1024 close(notify_pipe[0]);
1025 close(notify_pipe[1]);
1026 notify_pipe[0] = notify_pipe[1] = -1;
1027 }
1028
1029 if (!paused)
1030 libxl_domain_unpause(ctx, domid, NULL);
1031
1032 ret = domid; /* caller gets success in parent */
1033 if (!daemonize && !monitor)
1034 goto out;
1035
1036 if (dom_info->vnc)
1037 autoconnect_vncviewer(domid, vncautopass);
1038
1039 if (need_daemon) {
1040 char *name;
1041
1042 xasprintf(&name, "xl-%s", d_config.c_info.name);
1043 ret = do_daemonize(name, NULL);
1044 free(name);
1045 if (ret) {
1046 ret = (ret == 1) ? domid : ret;
1047 goto out;
1048 }
1049 need_daemon = 0;
1050 }
1051 LOG("Waiting for domain %s (domid %u) to die [pid %ld]",
1052 d_config.c_info.name, domid, (long)getpid());
1053
1054 ret = libxl_evenable_domain_death(ctx, domid, 0, &deathw);
1055 if (ret) goto out;
1056
1057 if (!diskws) {
1058 diskws = xmalloc(sizeof(*diskws) * d_config.num_disks);
1059 for (i = 0; i < d_config.num_disks; i++)
1060 diskws[i] = NULL;
1061 num_diskws = d_config.num_disks;
1062 }
1063 for (i = 0; i < num_diskws; i++) {
1064 if (d_config.disks[i].removable) {
1065 ret = libxl_evenable_disk_eject(ctx, domid, d_config.disks[i].vdev,
1066 0, &diskws[i]);
1067 if (ret) goto out;
1068 }
1069 }
1070 while (1) {
1071 libxl_evgen_domain_death *deathw2 = NULL;
1072 libxl_event *event;
1073 ret = domain_wait_event(domid, &event);
1074 if (ret) goto out;
1075
1076 switch (event->type) {
1077
1078 case LIBXL_EVENT_TYPE_DOMAIN_SHUTDOWN:
1079 LOG("Domain %u has shut down, reason code %d 0x%x", domid,
1080 event->u.domain_shutdown.shutdown_reason,
1081 event->u.domain_shutdown.shutdown_reason);
1082 switch (handle_domain_death(&domid, event, &d_config)) {
1083 case DOMAIN_RESTART_SOFT_RESET:
1084 domid_soft_reset = domid;
1085 domid = INVALID_DOMID;
1086 /* fall through */
1087 case DOMAIN_RESTART_RENAME:
1088 if (domid_soft_reset == INVALID_DOMID &&
1089 !preserve_domain(&domid, event, &d_config)) {
1090 libxl_event_free(ctx, event);
1091 /* If we fail then exit leaving the old domain in place. */
1092 ret = -1;
1093 goto out;
1094 }
1095
1096 /* Otherwise fall through and restart. */
1097 case DOMAIN_RESTART_NORMAL:
1098 libxl_event_free(ctx, event);
1099 libxl_evdisable_domain_death(ctx, deathw);
1100 deathw = NULL;
1101 evdisable_disk_ejects(diskws, num_diskws);
1102 free(diskws);
1103 diskws = NULL;
1104 num_diskws = 0;
1105 /* discard any other events which may have been generated */
1106 while (!(ret = libxl_event_check(ctx, &event,
1107 LIBXL_EVENTMASK_ALL, 0,0))) {
1108 libxl_event_free(ctx, event);
1109 }
1110 if (ret != ERROR_NOT_READY) {
1111 LOG("warning, libxl_event_check (cleanup) failed (rc=%d)",
1112 ret);
1113 }
1114
1115 /*
1116 * Do not attempt to reconnect if we come round again due to a
1117 * guest reboot -- the stdin/out will be disconnected by then.
1118 */
1119 dom_info->console_autoconnect = 0;
1120
1121 /* Some settings only make sense on first boot. */
1122 paused = 0;
1123 if (common_domname
1124 && strcmp(d_config.c_info.name, common_domname)) {
1125 d_config.c_info.name = strdup(common_domname);
1126 }
1127
1128 /*
1129 * XXX FIXME: If this sleep is not there then domain
1130 * re-creation fails sometimes.
1131 */
1132 LOG("Done. Rebooting now");
1133 sleep(2);
1134 goto start;
1135
1136 case DOMAIN_RESTART_NONE:
1137 LOG("Done. Exiting now");
1138 libxl_event_free(ctx, event);
1139 ret = 0;
1140 goto out;
1141
1142 case DOMAIN_RESTART_SUSPENDED:
1143 LOG("Continue waiting for domain %u", domid);
1144 /*
1145 * Enable a new event before disabling the old. This ensures
1146 * the xenstore watch remains active. Otherwise it'll fire
1147 * immediately on re-registration and find our suspended domain.
1148 */
1149 ret = libxl_evenable_domain_death(ctx, domid, 0, &deathw2);
1150 if (ret) goto out;
1151 libxl_evdisable_domain_death(ctx, deathw);
1152 deathw = deathw2;
1153 deathw2 = NULL;
1154 break;
1155
1156 default:
1157 abort();
1158 }
1159 break;
1160
1161 case LIBXL_EVENT_TYPE_DOMAIN_DEATH:
1162 LOG("Domain %u has been destroyed.", domid);
1163 libxl_event_free(ctx, event);
1164 ret = 0;
1165 goto out;
1166
1167 case LIBXL_EVENT_TYPE_DISK_EJECT:
1168 /* XXX what is this for? */
1169 libxl_cdrom_insert(ctx, domid, &event->u.disk_eject.disk, NULL);
1170 break;
1171
1172 default:;
1173 char *evstr = libxl_event_to_json(ctx, event);
1174 LOG("warning, got unexpected event type %d, event=%s",
1175 event->type, evstr);
1176 free(evstr);
1177 }
1178
1179 libxl_event_free(ctx, event);
1180 }
1181
1182 error_out:
1183 if (autoballoon)
1184 release_lock();
1185 if (libxl_domid_valid_guest(domid)) {
1186 libxl_domain_destroy(ctx, domid, 0);
1187 domid = INVALID_DOMID;
1188 }
1189
1190 out:
1191 if (restore_fd_to_close >= 0) {
1192 if (close(restore_fd_to_close))
1193 fprintf(stderr, "Failed to close restoring file, fd %d, errno %d\n",
1194 restore_fd_to_close, errno);
1195 restore_fd_to_close = -1;
1196 }
1197
1198 if (logfile != 2)
1199 close(logfile);
1200
1201 libxl_domain_config_dispose(&d_config);
1202
1203 free(config_data);
1204
1205 console_child_report(child_console);
1206
1207 if (deathw)
1208 libxl_evdisable_domain_death(ctx, deathw);
1209 if (diskws) {
1210 evdisable_disk_ejects(diskws, d_config.num_disks);
1211 free(diskws);
1212 }
1213
1214 /*
1215 * If we have daemonized then do not return to the caller -- this has
1216 * already happened in the parent.
1217 */
1218 if ( daemonize && !need_daemon )
1219 exit(ret);
1220
1221 return ret;
1222 }
1223
main_create(int argc,char ** argv)1224 int main_create(int argc, char **argv)
1225 {
1226 struct domain_create dom_info = {
1227 /* Command-line options */
1228 .config_file = NULL,
1229 .console_autoconnect = 0,
1230 .debug = 0,
1231 .daemonize = 1,
1232 .ignore_global_affinity_masks = 0,
1233 .monitor = 1,
1234 .paused = 0,
1235 .quiet = 0,
1236 .vnc = 0,
1237 .vncautopass = 0,
1238
1239 /* Extra configuration file settings */
1240 .extra_config = NULL,
1241
1242 /* FDs, initialize to invalid */
1243 .migrate_fd = -1,
1244 .send_back_fd = -1,
1245 };
1246 int opt, rc;
1247 static const struct option opts[] = {
1248 {"defconfig", 1, 0, 'f'},
1249 {"dryrun", 0, 0, 'n'},
1250 {"ignore-global-affinity-masks", 0, 0, 'i'},
1251 {"quiet", 0, 0, 'q'},
1252 {"vncviewer", 0, 0, 'V'},
1253 {"vncviewer-autopass", 0, 0, 'A'},
1254 COMMON_LONG_OPTS
1255 };
1256
1257 if (argv[1] && argv[1][0] != '-' && !strchr(argv[1], '=')) {
1258 dom_info.config_file = argv[1];
1259 argc--; argv++;
1260 }
1261
1262 SWITCH_FOREACH_OPT(opt, "AFVcdef:inpq", opts, "create", 0) {
1263 case 'A':
1264 dom_info.vnc = dom_info.vncautopass = 1;
1265 break;
1266 case 'F':
1267 dom_info.daemonize = 0;
1268 break;
1269 case 'V':
1270 dom_info.vnc = 1;
1271 break;
1272 case 'c':
1273 dom_info.console_autoconnect = 1;
1274 break;
1275 case 'd':
1276 dom_info.debug = 1;
1277 break;
1278 case 'e':
1279 dom_info.daemonize = 0;
1280 dom_info.monitor = 0;
1281 break;
1282 case 'f':
1283 dom_info.config_file = optarg;
1284 break;
1285 case 'i':
1286 dom_info.ignore_global_affinity_masks = 1;
1287 break;
1288 case 'n':
1289 dryrun_only = 1;
1290 break;
1291 case 'p':
1292 dom_info.paused = 1;
1293 break;
1294 case 'q':
1295 dom_info.quiet = 1;
1296 break;
1297 }
1298
1299 for (; optind < argc; optind++) {
1300 if (strchr(argv[optind], '=') != NULL) {
1301 string_realloc_append(&dom_info.extra_config, argv[optind]);
1302 string_realloc_append(&dom_info.extra_config, "\n");
1303 } else if (!dom_info.config_file) {
1304 dom_info.config_file = argv[optind];
1305 } else {
1306 help("create");
1307 free(dom_info.extra_config);
1308 return 2;
1309 }
1310 }
1311
1312 rc = create_domain(&dom_info);
1313 if (rc < 0) {
1314 free(dom_info.extra_config);
1315 return -rc;
1316 }
1317
1318 free(dom_info.extra_config);
1319 return 0;
1320 }
1321
1322 #ifdef LIBXL_HAVE_DT_OVERLAY
main_dt_overlay(int argc,char ** argv)1323 int main_dt_overlay(int argc, char **argv)
1324 {
1325 const char *overlay_config_file = NULL;
1326 void *overlay_dtb = NULL;
1327 int rc;
1328 uint8_t op;
1329 int overlay_dtb_size = 0;
1330 uint32_t domain_id = 0;
1331 bool domain_op = false;
1332
1333 if (argc < 2) {
1334 help("dt-overlay");
1335 return EXIT_FAILURE;
1336 }
1337
1338 if (strcmp(argv[optind], "add") == 0)
1339 op = LIBXL_DT_OVERLAY_ADD;
1340 else if (strcmp(argv[optind], "remove") == 0)
1341 op = LIBXL_DT_OVERLAY_REMOVE;
1342 else if (strcmp(argv[optind], "attach") == 0) {
1343 op = LIBXL_DT_OVERLAY_DOMAIN_ATTACH;
1344 domain_op = true;
1345 } else {
1346 fprintf(stderr, "Invalid dt overlay operation\n");
1347 return EXIT_FAILURE;
1348 }
1349
1350 overlay_config_file = argv[optind+1];
1351
1352 if (domain_op) {
1353 if (argc <= optind + 2) {
1354 fprintf(stderr, "Missing domain ID\n");
1355 help("dt-overlay");
1356 return EXIT_FAILURE;
1357 } else {
1358 domain_id = find_domain(argv[optind+2]);
1359 }
1360 }
1361
1362 if (overlay_config_file) {
1363 rc = libxl_read_file_contents(ctx, overlay_config_file,
1364 &overlay_dtb, &overlay_dtb_size);
1365
1366 if (rc) {
1367 fprintf(stderr, "failed to read the overlay device tree file %s\n",
1368 overlay_config_file);
1369 free(overlay_dtb);
1370 return EXIT_FAILURE;
1371 }
1372 } else {
1373 fprintf(stderr, "overlay dtbo file not provided\n");
1374 return EXIT_FAILURE;
1375 }
1376
1377 if (!domain_op)
1378 rc = libxl_dt_overlay(ctx, overlay_dtb, overlay_dtb_size, op);
1379 else
1380 rc = libxl_dt_overlay_domain(ctx, domain_id, overlay_dtb,
1381 overlay_dtb_size, op);
1382
1383 free(overlay_dtb);
1384
1385 if (rc)
1386 return EXIT_FAILURE;
1387
1388 return rc;
1389 }
1390 #endif
1391
1392 /*
1393 * Local variables:
1394 * mode: C
1395 * c-basic-offset: 4
1396 * indent-tabs-mode: nil
1397 * End:
1398 */
1399