You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2015 lines
61 KiB

  1. /*
  2. * virsh-snapshot.c: Commands to manage domain snapshot
  3. *
  4. * Copyright (C) 2005-2019 Red Hat, Inc.
  5. *
  6. * This library is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 2.1 of the License, or (at your option) any later version.
  10. *
  11. * This library is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public
  17. * License along with this library. If not, see
  18. * <http://www.gnu.org/licenses/>.
  19. */
  20. #include <config.h>
  21. #include "virsh-snapshot.h"
  22. #include <assert.h>
  23. #include <libxml/parser.h>
  24. #include <libxml/tree.h>
  25. #include <libxml/xpath.h>
  26. #include <libxml/xmlsave.h>
  27. #include "internal.h"
  28. #include "virbuffer.h"
  29. #include "viralloc.h"
  30. #include "virfile.h"
  31. #include "virsh-util.h"
  32. #include "virstring.h"
  33. #include "virxml.h"
  34. #include "conf/virdomainsnapshotobjlist.h"
  35. #include "vsh-table.h"
  36. /* Helper for snapshot-create and snapshot-create-as */
  37. static bool
  38. virshSnapshotCreate(vshControl *ctl, virDomainPtr dom, const char *buffer,
  39. unsigned int flags, const char *from)
  40. {
  41. bool ret = false;
  42. virDomainSnapshotPtr snapshot;
  43. bool halt = false;
  44. const char *name = NULL;
  45. snapshot = virDomainSnapshotCreateXML(dom, buffer, flags);
  46. /* If no source file but validate was not recognized, try again without
  47. * that flag. */
  48. if (!snapshot && last_error->code == VIR_ERR_NO_SUPPORT && !from) {
  49. flags &= ~VIR_DOMAIN_SNAPSHOT_CREATE_VALIDATE;
  50. snapshot = virDomainSnapshotCreateXML(dom, buffer, flags);
  51. }
  52. /* Emulate --halt on older servers. */
  53. if (!snapshot && last_error->code == VIR_ERR_INVALID_ARG &&
  54. (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT)) {
  55. int persistent;
  56. vshResetLibvirtError();
  57. persistent = virDomainIsPersistent(dom);
  58. if (persistent < 0) {
  59. vshReportError(ctl);
  60. goto cleanup;
  61. }
  62. if (!persistent) {
  63. vshError(ctl, "%s",
  64. _("cannot halt after snapshot of transient domain"));
  65. goto cleanup;
  66. }
  67. if (virDomainIsActive(dom) == 1)
  68. halt = true;
  69. flags &= ~VIR_DOMAIN_SNAPSHOT_CREATE_HALT;
  70. snapshot = virDomainSnapshotCreateXML(dom, buffer, flags);
  71. }
  72. if (snapshot == NULL)
  73. goto cleanup;
  74. if (halt && virDomainDestroy(dom) < 0) {
  75. vshReportError(ctl);
  76. goto cleanup;
  77. }
  78. name = virDomainSnapshotGetName(snapshot);
  79. if (!name) {
  80. vshError(ctl, "%s", _("Could not get snapshot name"));
  81. goto cleanup;
  82. }
  83. if (from)
  84. vshPrintExtra(ctl, _("Domain snapshot %s created from '%s'"), name, from);
  85. else
  86. vshPrintExtra(ctl, _("Domain snapshot %s created"), name);
  87. ret = true;
  88. cleanup:
  89. virshDomainSnapshotFree(snapshot);
  90. return ret;
  91. }
  92. /*
  93. * "snapshot-create" command
  94. */
  95. static const vshCmdInfo info_snapshot_create[] = {
  96. {.name = "help",
  97. .data = N_("Create a snapshot from XML")
  98. },
  99. {.name = "desc",
  100. .data = N_("Create a snapshot (disk and RAM) from XML")
  101. },
  102. {.name = NULL}
  103. };
  104. static const vshCmdOptDef opts_snapshot_create[] = {
  105. VIRSH_COMMON_OPT_DOMAIN_FULL(0),
  106. {.name = "xmlfile",
  107. .type = VSH_OT_STRING,
  108. .help = N_("domain snapshot XML")
  109. },
  110. {.name = "redefine",
  111. .type = VSH_OT_BOOL,
  112. .help = N_("redefine metadata for existing snapshot")
  113. },
  114. VIRSH_COMMON_OPT_CURRENT(N_("with redefine, set current snapshot")),
  115. {.name = "no-metadata",
  116. .type = VSH_OT_BOOL,
  117. .help = N_("take snapshot but create no metadata")
  118. },
  119. {.name = "halt",
  120. .type = VSH_OT_BOOL,
  121. .help = N_("halt domain after snapshot is created")
  122. },
  123. {.name = "disk-only",
  124. .type = VSH_OT_BOOL,
  125. .help = N_("capture disk state but not vm state")
  126. },
  127. {.name = "reuse-external",
  128. .type = VSH_OT_BOOL,
  129. .help = N_("reuse any existing external files")
  130. },
  131. {.name = "quiesce",
  132. .type = VSH_OT_BOOL,
  133. .help = N_("quiesce guest's file systems")
  134. },
  135. {.name = "atomic",
  136. .type = VSH_OT_BOOL,
  137. .help = N_("require atomic operation")
  138. },
  139. VIRSH_COMMON_OPT_LIVE(N_("take a live snapshot")),
  140. {.name = "validate",
  141. .type = VSH_OT_BOOL,
  142. .help = N_("validate the XML against the schema"),
  143. },
  144. {.name = NULL}
  145. };
  146. static bool
  147. cmdSnapshotCreate(vshControl *ctl, const vshCmd *cmd)
  148. {
  149. virDomainPtr dom = NULL;
  150. bool ret = false;
  151. const char *from = NULL;
  152. char *buffer = NULL;
  153. unsigned int flags = 0;
  154. if (vshCommandOptBool(cmd, "redefine"))
  155. flags |= VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE;
  156. if (vshCommandOptBool(cmd, "current"))
  157. flags |= VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT;
  158. if (vshCommandOptBool(cmd, "no-metadata"))
  159. flags |= VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA;
  160. if (vshCommandOptBool(cmd, "halt"))
  161. flags |= VIR_DOMAIN_SNAPSHOT_CREATE_HALT;
  162. if (vshCommandOptBool(cmd, "disk-only"))
  163. flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY;
  164. if (vshCommandOptBool(cmd, "reuse-external"))
  165. flags |= VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT;
  166. if (vshCommandOptBool(cmd, "quiesce"))
  167. flags |= VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE;
  168. if (vshCommandOptBool(cmd, "atomic"))
  169. flags |= VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC;
  170. if (vshCommandOptBool(cmd, "live"))
  171. flags |= VIR_DOMAIN_SNAPSHOT_CREATE_LIVE;
  172. if (vshCommandOptBool(cmd, "validate"))
  173. flags |= VIR_DOMAIN_SNAPSHOT_CREATE_VALIDATE;
  174. if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
  175. goto cleanup;
  176. if (vshCommandOptStringReq(ctl, cmd, "xmlfile", &from) < 0)
  177. goto cleanup;
  178. if (!from) {
  179. buffer = g_strdup("<domainsnapshot/>");
  180. } else {
  181. if (virFileReadAll(from, VSH_MAX_XML_FILE, &buffer) < 0) {
  182. vshSaveLibvirtError();
  183. goto cleanup;
  184. }
  185. }
  186. ret = virshSnapshotCreate(ctl, dom, buffer, flags, from);
  187. cleanup:
  188. VIR_FREE(buffer);
  189. virshDomainFree(dom);
  190. return ret;
  191. }
  192. /*
  193. * "snapshot-create-as" command
  194. */
  195. static int
  196. virshParseSnapshotMemspec(vshControl *ctl, virBufferPtr buf, const char *str)
  197. {
  198. int ret = -1;
  199. const char *snapshot = NULL;
  200. const char *file = NULL;
  201. char **array = NULL;
  202. int narray;
  203. size_t i;
  204. if (!str)
  205. return 0;
  206. narray = vshStringToArray(str, &array);
  207. if (narray < 0)
  208. goto cleanup;
  209. for (i = 0; i < narray; i++) {
  210. if (!snapshot && STRPREFIX(array[i], "snapshot="))
  211. snapshot = array[i] + strlen("snapshot=");
  212. else if (!file && STRPREFIX(array[i], "file="))
  213. file = array[i] + strlen("file=");
  214. else if (!file && *array[i] == '/')
  215. file = array[i];
  216. else
  217. goto cleanup;
  218. }
  219. virBufferAddLit(buf, "<memory");
  220. virBufferEscapeString(buf, " snapshot='%s'", snapshot);
  221. virBufferEscapeString(buf, " file='%s'", file);
  222. virBufferAddLit(buf, "/>\n");
  223. ret = 0;
  224. cleanup:
  225. if (ret < 0)
  226. vshError(ctl, _("unable to parse memspec: %s"), str);
  227. virStringListFree(array);
  228. return ret;
  229. }
  230. static int
  231. virshParseSnapshotDiskspec(vshControl *ctl, virBufferPtr buf, const char *str)
  232. {
  233. int ret = -1;
  234. const char *name = NULL;
  235. const char *snapshot = NULL;
  236. const char *driver = NULL;
  237. const char *stype = NULL;
  238. const char *file = NULL;
  239. char **array = NULL;
  240. int narray;
  241. size_t i;
  242. bool isFile = true;
  243. narray = vshStringToArray(str, &array);
  244. if (narray <= 0)
  245. goto cleanup;
  246. name = array[0];
  247. for (i = 1; i < narray; i++) {
  248. if (!snapshot && STRPREFIX(array[i], "snapshot="))
  249. snapshot = array[i] + strlen("snapshot=");
  250. else if (!driver && STRPREFIX(array[i], "driver="))
  251. driver = array[i] + strlen("driver=");
  252. else if (!stype && STRPREFIX(array[i], "stype="))
  253. stype = array[i] + strlen("stype=");
  254. else if (!file && STRPREFIX(array[i], "file="))
  255. file = array[i] + strlen("file=");
  256. else
  257. goto cleanup;
  258. }
  259. virBufferEscapeString(buf, "<disk name='%s'", name);
  260. if (snapshot)
  261. virBufferAsprintf(buf, " snapshot='%s'", snapshot);
  262. if (stype) {
  263. if (STREQ(stype, "block")) {
  264. isFile = false;
  265. } else if (STRNEQ(stype, "file")) {
  266. vshError(ctl, _("Unknown storage type: '%s'"), stype);
  267. goto cleanup;
  268. }
  269. virBufferAsprintf(buf, " type='%s'", stype);
  270. }
  271. if (driver || file) {
  272. virBufferAddLit(buf, ">\n");
  273. virBufferAdjustIndent(buf, 2);
  274. if (driver)
  275. virBufferAsprintf(buf, "<driver type='%s'/>\n", driver);
  276. if (file) {
  277. if (isFile)
  278. virBufferEscapeString(buf, "<source file='%s'/>\n", file);
  279. else
  280. virBufferEscapeString(buf, "<source dev='%s'/>\n", file);
  281. }
  282. virBufferAdjustIndent(buf, -2);
  283. virBufferAddLit(buf, "</disk>\n");
  284. } else {
  285. virBufferAddLit(buf, "/>\n");
  286. }
  287. ret = 0;
  288. cleanup:
  289. if (ret < 0)
  290. vshError(ctl, _("unable to parse diskspec: %s"), str);
  291. virStringListFree(array);
  292. return ret;
  293. }
  294. static const vshCmdInfo info_snapshot_create_as[] = {
  295. {.name = "help",
  296. .data = N_("Create a snapshot from a set of args")
  297. },
  298. {.name = "desc",
  299. .data = N_("Create a snapshot (disk and RAM) from arguments")
  300. },
  301. {.name = NULL}
  302. };
  303. static const vshCmdOptDef opts_snapshot_create_as[] = {
  304. VIRSH_COMMON_OPT_DOMAIN_FULL(0),
  305. {.name = "name",
  306. .type = VSH_OT_STRING,
  307. .help = N_("name of snapshot")
  308. },
  309. {.name = "description",
  310. .type = VSH_OT_STRING,
  311. .help = N_("description of snapshot")
  312. },
  313. {.name = "print-xml",
  314. .type = VSH_OT_BOOL,
  315. .help = N_("print XML document rather than create")
  316. },
  317. {.name = "no-metadata",
  318. .type = VSH_OT_BOOL,
  319. .help = N_("take snapshot but create no metadata")
  320. },
  321. {.name = "halt",
  322. .type = VSH_OT_BOOL,
  323. .help = N_("halt domain after snapshot is created")
  324. },
  325. {.name = "disk-only",
  326. .type = VSH_OT_BOOL,
  327. .help = N_("capture disk state but not vm state")
  328. },
  329. {.name = "reuse-external",
  330. .type = VSH_OT_BOOL,
  331. .help = N_("reuse any existing external files")
  332. },
  333. {.name = "quiesce",
  334. .type = VSH_OT_BOOL,
  335. .help = N_("quiesce guest's file systems")
  336. },
  337. {.name = "atomic",
  338. .type = VSH_OT_BOOL,
  339. .help = N_("require atomic operation")
  340. },
  341. VIRSH_COMMON_OPT_LIVE(N_("take a live snapshot")),
  342. {.name = "memspec",
  343. .type = VSH_OT_STRING,
  344. .flags = VSH_OFLAG_REQ_OPT,
  345. .help = N_("memory attributes: [file=]name[,snapshot=type]")
  346. },
  347. {.name = "diskspec",
  348. .type = VSH_OT_ARGV,
  349. .help = N_("disk attributes: disk[,snapshot=type][,driver=type][,stype=type][,file=name]")
  350. },
  351. {.name = NULL}
  352. };
  353. static bool
  354. cmdSnapshotCreateAs(vshControl *ctl, const vshCmd *cmd)
  355. {
  356. virDomainPtr dom = NULL;
  357. bool ret = false;
  358. char *buffer = NULL;
  359. const char *name = NULL;
  360. const char *desc = NULL;
  361. const char *memspec = NULL;
  362. virBuffer buf = VIR_BUFFER_INITIALIZER;
  363. unsigned int flags = VIR_DOMAIN_SNAPSHOT_CREATE_VALIDATE;
  364. const vshCmdOpt *opt = NULL;
  365. if (vshCommandOptBool(cmd, "no-metadata"))
  366. flags |= VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA;
  367. if (vshCommandOptBool(cmd, "halt"))
  368. flags |= VIR_DOMAIN_SNAPSHOT_CREATE_HALT;
  369. if (vshCommandOptBool(cmd, "disk-only"))
  370. flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY;
  371. if (vshCommandOptBool(cmd, "reuse-external"))
  372. flags |= VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT;
  373. if (vshCommandOptBool(cmd, "quiesce"))
  374. flags |= VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE;
  375. if (vshCommandOptBool(cmd, "atomic"))
  376. flags |= VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC;
  377. if (vshCommandOptBool(cmd, "live"))
  378. flags |= VIR_DOMAIN_SNAPSHOT_CREATE_LIVE;
  379. if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
  380. return false;
  381. if (vshCommandOptStringReq(ctl, cmd, "name", &name) < 0 ||
  382. vshCommandOptStringReq(ctl, cmd, "description", &desc) < 0)
  383. goto cleanup;
  384. virBufferAddLit(&buf, "<domainsnapshot>\n");
  385. virBufferAdjustIndent(&buf, 2);
  386. virBufferEscapeString(&buf, "<name>%s</name>\n", name);
  387. virBufferEscapeString(&buf, "<description>%s</description>\n", desc);
  388. if (vshCommandOptStringReq(ctl, cmd, "memspec", &memspec) < 0)
  389. goto cleanup;
  390. if (memspec && virshParseSnapshotMemspec(ctl, &buf, memspec) < 0)
  391. goto cleanup;
  392. if (vshCommandOptBool(cmd, "diskspec")) {
  393. virBufferAddLit(&buf, "<disks>\n");
  394. virBufferAdjustIndent(&buf, 2);
  395. while ((opt = vshCommandOptArgv(ctl, cmd, opt))) {
  396. if (virshParseSnapshotDiskspec(ctl, &buf, opt->data) < 0)
  397. goto cleanup;
  398. }
  399. virBufferAdjustIndent(&buf, -2);
  400. virBufferAddLit(&buf, "</disks>\n");
  401. }
  402. virBufferAdjustIndent(&buf, -2);
  403. virBufferAddLit(&buf, "</domainsnapshot>\n");
  404. buffer = virBufferContentAndReset(&buf);
  405. if (vshCommandOptBool(cmd, "print-xml")) {
  406. vshPrint(ctl, "%s\n", buffer);
  407. ret = true;
  408. goto cleanup;
  409. }
  410. ret = virshSnapshotCreate(ctl, dom, buffer, flags, NULL);
  411. cleanup:
  412. virBufferFreeAndReset(&buf);
  413. VIR_FREE(buffer);
  414. virshDomainFree(dom);
  415. return ret;
  416. }
  417. /* Helper for resolving {--current | --ARG name} into a snapshot
  418. * belonging to DOM. If EXCLUSIVE, fail if both --current and arg are
  419. * present. On success, populate *SNAP and *NAME, before returning 0.
  420. * On failure, return -1 after issuing an error message. */
  421. static int
  422. virshLookupSnapshot(vshControl *ctl, const vshCmd *cmd,
  423. const char *arg, bool exclusive, virDomainPtr dom,
  424. virDomainSnapshotPtr *snap, const char **name)
  425. {
  426. bool current = vshCommandOptBool(cmd, "current");
  427. const char *snapname = NULL;
  428. if (vshCommandOptStringReq(ctl, cmd, arg, &snapname) < 0)
  429. return -1;
  430. if (exclusive && current && snapname) {
  431. vshError(ctl, _("--%s and --current are mutually exclusive"), arg);
  432. return -1;
  433. }
  434. if (snapname) {
  435. *snap = virDomainSnapshotLookupByName(dom, snapname, 0);
  436. } else if (current) {
  437. *snap = virDomainSnapshotCurrent(dom, 0);
  438. } else {
  439. vshError(ctl, _("--%s or --current is required"), arg);
  440. return -1;
  441. }
  442. if (!*snap) {
  443. vshReportError(ctl);
  444. return -1;
  445. }
  446. *name = virDomainSnapshotGetName(*snap);
  447. return 0;
  448. }
  449. /*
  450. * "snapshot-edit" command
  451. */
  452. static const vshCmdInfo info_snapshot_edit[] = {
  453. {.name = "help",
  454. .data = N_("edit XML for a snapshot")
  455. },
  456. {.name = "desc",
  457. .data = N_("Edit the domain snapshot XML for a named snapshot")
  458. },
  459. {.name = NULL}
  460. };
  461. static const vshCmdOptDef opts_snapshot_edit[] = {
  462. VIRSH_COMMON_OPT_DOMAIN_FULL(0),
  463. {.name = "snapshotname",
  464. .type = VSH_OT_STRING,
  465. .help = N_("snapshot name"),
  466. .completer = virshSnapshotNameCompleter,
  467. },
  468. VIRSH_COMMON_OPT_CURRENT(N_("also set edited snapshot as current")),
  469. {.name = "rename",
  470. .type = VSH_OT_BOOL,
  471. .help = N_("allow renaming an existing snapshot")
  472. },
  473. {.name = "clone",
  474. .type = VSH_OT_BOOL,
  475. .help = N_("allow cloning to new name")
  476. },
  477. {.name = NULL}
  478. };
  479. static bool
  480. cmdSnapshotEdit(vshControl *ctl, const vshCmd *cmd)
  481. {
  482. virDomainPtr dom = NULL;
  483. virDomainSnapshotPtr snapshot = NULL;
  484. virDomainSnapshotPtr edited = NULL;
  485. const char *name = NULL;
  486. const char *edited_name;
  487. bool ret = false;
  488. unsigned int getxml_flags = VIR_DOMAIN_XML_SECURE;
  489. unsigned int define_flags = VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE;
  490. bool rename_okay = vshCommandOptBool(cmd, "rename");
  491. bool clone_okay = vshCommandOptBool(cmd, "clone");
  492. VSH_EXCLUSIVE_OPTIONS_EXPR("rename", rename_okay, "clone", clone_okay)
  493. if (vshCommandOptBool(cmd, "current") &&
  494. vshCommandOptBool(cmd, "snapshotname"))
  495. define_flags |= VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT;
  496. if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
  497. return false;
  498. if (virshLookupSnapshot(ctl, cmd, "snapshotname", false, dom,
  499. &snapshot, &name) < 0)
  500. goto cleanup;
  501. #define EDIT_GET_XML \
  502. virDomainSnapshotGetXMLDesc(snapshot, getxml_flags)
  503. #define EDIT_NOT_CHANGED \
  504. do { \
  505. /* Depending on flags, we re-edit even if XML is unchanged. */ \
  506. if (!(define_flags & VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT)) { \
  507. vshPrintExtra(ctl, \
  508. _("Snapshot %s XML configuration not changed.\n"), \
  509. name); \
  510. ret = true; \
  511. goto edit_cleanup; \
  512. } \
  513. } while (0)
  514. #define EDIT_DEFINE \
  515. (strstr(doc, "<state>disk-snapshot</state>") ? \
  516. define_flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY : 0), \
  517. edited = virDomainSnapshotCreateXML(dom, doc_edited, define_flags)
  518. #include "virsh-edit.c"
  519. edited_name = virDomainSnapshotGetName(edited);
  520. if (STREQ(name, edited_name)) {
  521. vshPrintExtra(ctl, _("Snapshot %s edited.\n"), name);
  522. } else if (clone_okay) {
  523. vshPrintExtra(ctl, _("Snapshot %s cloned to %s.\n"), name,
  524. edited_name);
  525. } else {
  526. unsigned int delete_flags;
  527. delete_flags = VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY;
  528. if (virDomainSnapshotDelete(rename_okay ? snapshot : edited,
  529. delete_flags) < 0) {
  530. vshReportError(ctl);
  531. vshError(ctl, _("Failed to clean up %s"),
  532. rename_okay ? name : edited_name);
  533. goto cleanup;
  534. }
  535. if (!rename_okay) {
  536. vshError(ctl, _("Must use --rename or --clone to change %s to %s"),
  537. name, edited_name);
  538. goto cleanup;
  539. }
  540. }
  541. ret = true;
  542. cleanup:
  543. if (!ret && name)
  544. vshError(ctl, _("Failed to update %s"), name);
  545. virshDomainSnapshotFree(edited);
  546. virshDomainSnapshotFree(snapshot);
  547. virshDomainFree(dom);
  548. return ret;
  549. }
  550. /*
  551. * "snapshot-current" command
  552. */
  553. static const vshCmdInfo info_snapshot_current[] = {
  554. {.name = "help",
  555. .data = N_("Get or set the current snapshot")
  556. },
  557. {.name = "desc",
  558. .data = N_("Get or set the current snapshot")
  559. },
  560. {.name = NULL}
  561. };
  562. static const vshCmdOptDef opts_snapshot_current[] = {
  563. VIRSH_COMMON_OPT_DOMAIN_FULL(0),
  564. {.name = "name",
  565. .type = VSH_OT_BOOL,
  566. .help = N_("list the name, rather than the full xml")
  567. },
  568. {.name = "security-info",
  569. .type = VSH_OT_BOOL,
  570. .help = N_("include security sensitive information in XML dump")
  571. },
  572. {.name = "snapshotname",
  573. .type = VSH_OT_STRING,
  574. .help = N_("name of existing snapshot to make current"),
  575. .completer = virshSnapshotNameCompleter,
  576. },
  577. {.name = NULL}
  578. };
  579. static bool
  580. cmdSnapshotCurrent(vshControl *ctl, const vshCmd *cmd)
  581. {
  582. virDomainPtr dom = NULL;
  583. bool ret = false;
  584. int current;
  585. virDomainSnapshotPtr snapshot = NULL;
  586. char *xml = NULL;
  587. const char *snapshotname = NULL;
  588. unsigned int flags = 0;
  589. const char *domname;
  590. if (vshCommandOptBool(cmd, "security-info"))
  591. flags |= VIR_DOMAIN_XML_SECURE;
  592. VSH_EXCLUSIVE_OPTIONS("name", "snapshotname");
  593. if (!(dom = virshCommandOptDomain(ctl, cmd, &domname)))
  594. return false;
  595. if (vshCommandOptStringReq(ctl, cmd, "snapshotname", &snapshotname) < 0)
  596. goto cleanup;
  597. if (snapshotname) {
  598. virDomainSnapshotPtr snapshot2 = NULL;
  599. flags = (VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE |
  600. VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT);
  601. if (!(snapshot = virDomainSnapshotLookupByName(dom, snapshotname, 0)))
  602. goto cleanup;
  603. xml = virDomainSnapshotGetXMLDesc(snapshot, VIR_DOMAIN_XML_SECURE);
  604. if (!xml)
  605. goto cleanup;
  606. /* strstr is safe here, since xml came from libvirt API and not user */
  607. if (strstr(xml, "<state>disk-snapshot</state>"))
  608. flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY;
  609. if (!(snapshot2 = virDomainSnapshotCreateXML(dom, xml, flags)))
  610. goto cleanup;
  611. virshDomainSnapshotFree(snapshot2);
  612. vshPrintExtra(ctl, _("Snapshot %s set as current"), snapshotname);
  613. ret = true;
  614. goto cleanup;
  615. }
  616. if ((current = virDomainHasCurrentSnapshot(dom, 0)) < 0)
  617. goto cleanup;
  618. if (!current) {
  619. vshError(ctl, _("domain '%s' has no current snapshot"), domname);
  620. goto cleanup;
  621. } else {
  622. if (!(snapshot = virDomainSnapshotCurrent(dom, 0)))
  623. goto cleanup;
  624. if (vshCommandOptBool(cmd, "name")) {
  625. const char *name;
  626. if (!(name = virDomainSnapshotGetName(snapshot)))
  627. goto cleanup;
  628. vshPrint(ctl, "%s", name);
  629. } else {
  630. if (!(xml = virDomainSnapshotGetXMLDesc(snapshot, flags)))
  631. goto cleanup;
  632. vshPrint(ctl, "%s", xml);
  633. }
  634. }
  635. ret = true;
  636. cleanup:
  637. if (!ret)
  638. vshReportError(ctl);
  639. VIR_FREE(xml);
  640. virshDomainSnapshotFree(snapshot);
  641. virshDomainFree(dom);
  642. return ret;
  643. }
  644. /* Helper function to get the name of a snapshot's parent. Caller
  645. * must free the result. Returns 0 on success (including when it was
  646. * proven no parent exists), and -1 on failure with error reported
  647. * (such as no snapshot support or domain deleted in meantime). */
  648. static int
  649. virshGetSnapshotParent(vshControl *ctl, virDomainSnapshotPtr snapshot,
  650. char **parent_name)
  651. {
  652. virDomainSnapshotPtr parent = NULL;
  653. char *xml = NULL;
  654. xmlDocPtr xmldoc = NULL;
  655. xmlXPathContextPtr ctxt = NULL;
  656. int ret = -1;
  657. virshControlPtr priv = ctl->privData;
  658. *parent_name = NULL;
  659. /* Try new API, since it is faster. */
  660. if (!priv->useSnapshotOld) {
  661. parent = virDomainSnapshotGetParent(snapshot, 0);
  662. if (parent) {
  663. /* API works, and virDomainSnapshotGetName will succeed */
  664. *parent_name = g_strdup(virDomainSnapshotGetName(parent));
  665. ret = 0;
  666. goto cleanup;
  667. }
  668. if (last_error->code == VIR_ERR_NO_DOMAIN_SNAPSHOT) {
  669. /* API works, and we found a root with no parent */
  670. ret = 0;
  671. goto cleanup;
  672. }
  673. /* API didn't work, fall back to XML scraping. */
  674. priv->useSnapshotOld = true;
  675. }
  676. xml = virDomainSnapshotGetXMLDesc(snapshot, 0);
  677. if (!xml)
  678. goto cleanup;
  679. xmldoc = virXMLParseStringCtxt(xml, _("(domain_snapshot)"), &ctxt);
  680. if (!xmldoc)
  681. goto cleanup;
  682. *parent_name = virXPathString("string(/domainsnapshot/parent/name)", ctxt);
  683. ret = 0;
  684. cleanup:
  685. if (ret < 0) {
  686. vshReportError(ctl);
  687. vshError(ctl, "%s", _("unable to determine if snapshot has parent"));
  688. } else {
  689. vshResetLibvirtError();
  690. }
  691. virshDomainSnapshotFree(parent);
  692. xmlXPathFreeContext(ctxt);
  693. xmlFreeDoc(xmldoc);
  694. VIR_FREE(xml);
  695. return ret;
  696. }
  697. /* Helper function to filter snapshots according to status and
  698. * location portion of flags. Returns 0 if filter excluded snapshot,
  699. * 1 if snapshot is okay (or if snapshot is already NULL), and -1 on
  700. * failure, with error already reported. */
  701. static int
  702. virshSnapshotFilter(vshControl *ctl, virDomainSnapshotPtr snapshot,
  703. unsigned int flags)
  704. {
  705. char *xml = NULL;
  706. xmlDocPtr xmldoc = NULL;
  707. xmlXPathContextPtr ctxt = NULL;
  708. int ret = -1;
  709. char *state = NULL;
  710. if (!snapshot)
  711. return 1;
  712. xml = virDomainSnapshotGetXMLDesc(snapshot, 0);
  713. if (!xml)
  714. goto cleanup;
  715. xmldoc = virXMLParseStringCtxt(xml, _("(domain_snapshot)"), &ctxt);
  716. if (!xmldoc)
  717. goto cleanup;
  718. /* Libvirt 1.0.1 and newer never call this function, because the
  719. * filtering is already supported by the listing functions. Older
  720. * libvirt lacked /domainsnapshot/memory, but was also limited in
  721. * the types of snapshots it could create: if state was disk-only,
  722. * the snapshot is external; all other snapshots are internal. */
  723. state = virXPathString("string(/domainsnapshot/state)", ctxt);
  724. if (!state) {
  725. vshError(ctl, "%s", _("unable to perform snapshot filtering"));
  726. goto cleanup;
  727. }
  728. if (STREQ(state, "disk-snapshot")) {
  729. ret = ((flags & (VIR_DOMAIN_SNAPSHOT_LIST_DISK_ONLY |
  730. VIR_DOMAIN_SNAPSHOT_LIST_EXTERNAL)) ==
  731. (VIR_DOMAIN_SNAPSHOT_LIST_DISK_ONLY |
  732. VIR_DOMAIN_SNAPSHOT_LIST_EXTERNAL));
  733. } else {
  734. if (!(flags & VIR_DOMAIN_SNAPSHOT_LIST_INTERNAL))
  735. ret = 0;
  736. else if (STREQ(state, "shutoff"))
  737. ret = !!(flags & VIR_DOMAIN_SNAPSHOT_LIST_INACTIVE);
  738. else
  739. ret = !!(flags & VIR_DOMAIN_SNAPSHOT_LIST_ACTIVE);
  740. }
  741. cleanup:
  742. VIR_FREE(state);
  743. xmlXPathFreeContext(ctxt);
  744. xmlFreeDoc(xmldoc);
  745. VIR_FREE(xml);
  746. return ret;
  747. }
  748. /*
  749. * "snapshot-info" command
  750. */
  751. static const vshCmdInfo info_snapshot_info[] = {
  752. {.name = "help",
  753. .data = N_("snapshot information")
  754. },
  755. {.name = "desc",
  756. .data = N_("Returns basic information about a snapshot.")
  757. },
  758. {.name = NULL}
  759. };
  760. static const vshCmdOptDef opts_snapshot_info[] = {
  761. VIRSH_COMMON_OPT_DOMAIN_FULL(0),
  762. {.name = "snapshotname",
  763. .type = VSH_OT_STRING,
  764. .help = N_("snapshot name"),
  765. .completer = virshSnapshotNameCompleter,
  766. },
  767. VIRSH_COMMON_OPT_CURRENT(N_("info on current snapshot")),
  768. {.name = NULL}
  769. };
  770. static bool
  771. cmdSnapshotInfo(vshControl *ctl, const vshCmd *cmd)
  772. {
  773. virDomainPtr dom;
  774. virDomainSnapshotPtr snapshot = NULL;
  775. const char *name;
  776. char *doc = NULL;
  777. xmlDocPtr xmldoc = NULL;
  778. xmlXPathContextPtr ctxt = NULL;
  779. char *state = NULL;
  780. int external;
  781. char *parent = NULL;
  782. bool ret = false;
  783. int count;
  784. unsigned int flags;
  785. int current;
  786. int metadata;
  787. virshControlPtr priv = ctl->privData;
  788. dom = virshCommandOptDomain(ctl, cmd, NULL);
  789. if (dom == NULL)
  790. return false;
  791. if (virshLookupSnapshot(ctl, cmd, "snapshotname", true, dom,
  792. &snapshot, &name) < 0)
  793. goto cleanup;
  794. vshPrint(ctl, "%-15s %s\n", _("Name:"), name);
  795. vshPrint(ctl, "%-15s %s\n", _("Domain:"), virDomainGetName(dom));
  796. /* Determine if snapshot is current; this is useful enough that we
  797. * attempt a fallback. */
  798. current = virDomainSnapshotIsCurrent(snapshot, 0);
  799. if (current < 0) {
  800. virDomainSnapshotPtr other = virDomainSnapshotCurrent(dom, 0);
  801. vshResetLibvirtError();
  802. current = 0;
  803. if (other) {
  804. if (STREQ(name, virDomainSnapshotGetName(other)))
  805. current = 1;
  806. virshDomainSnapshotFree(other);
  807. }
  808. }
  809. vshPrint(ctl, "%-15s %s\n", _("Current:"),
  810. current > 0 ? _("yes") : _("no"));
  811. /* Get the XML configuration of the snapshot to determine the
  812. * state of the machine at the time of the snapshot. */
  813. doc = virDomainSnapshotGetXMLDesc(snapshot, 0);
  814. if (!doc)
  815. goto cleanup;
  816. xmldoc = virXMLParseStringCtxt(doc, _("(domain_snapshot)"), &ctxt);
  817. if (!xmldoc)
  818. goto cleanup;
  819. state = virXPathString("string(/domainsnapshot/state)", ctxt);
  820. if (!state) {
  821. vshError(ctl, "%s",
  822. _("unexpected problem reading snapshot xml"));
  823. goto cleanup;
  824. }
  825. vshPrint(ctl, "%-15s %s\n", _("State:"), state);
  826. /* In addition to state, location is useful. If the snapshot has
  827. * a <memory> element, then the existence of snapshot='external'
  828. * prior to <domain> is the deciding factor; for snapshots
  829. * created prior to 1.0.1, a state of disk-only is the only
  830. * external snapshot. */
  831. switch (virXPathBoolean("boolean(/domainsnapshot/memory)", ctxt)) {
  832. case 1:
  833. external = virXPathBoolean("boolean(/domainsnapshot/memory[@snapshot='external'] "
  834. "| /domainsnapshot/disks/disk[@snapshot='external'])",
  835. ctxt);
  836. break;
  837. case 0:
  838. external = STREQ(state, "disk-snapshot");
  839. break;
  840. default:
  841. external = -1;
  842. break;
  843. }
  844. if (external < 0) {
  845. vshError(ctl, "%s",
  846. _("unexpected problem reading snapshot xml"));
  847. goto cleanup;
  848. }
  849. vshPrint(ctl, "%-15s %s\n", _("Location:"),
  850. external ? _("external") : _("internal"));
  851. /* Since we already have the XML, there's no need to call
  852. * virDomainSnapshotGetParent */
  853. parent = virXPathString("string(/domainsnapshot/parent/name)", ctxt);
  854. vshPrint(ctl, "%-15s %s\n", _("Parent:"), NULLSTR_MINUS(parent));
  855. /* Children, Descendants. After this point, the fallback to
  856. * compute children is too expensive, so we gracefully quit if the
  857. * APIs don't exist. */
  858. if (priv->useSnapshotOld) {
  859. ret = true;
  860. goto cleanup;
  861. }
  862. flags = 0;
  863. count = virDomainSnapshotNumChildren(snapshot, flags);
  864. if (count < 0) {
  865. if (last_error->code == VIR_ERR_NO_SUPPORT) {
  866. vshResetLibvirtError();
  867. ret = true;
  868. }
  869. goto cleanup;
  870. }
  871. vshPrint(ctl, "%-15s %d\n", _("Children:"), count);
  872. flags = VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS;
  873. count = virDomainSnapshotNumChildren(snapshot, flags);
  874. if (count < 0)
  875. goto cleanup;
  876. vshPrint(ctl, "%-15s %d\n", _("Descendants:"), count);
  877. /* Metadata; the fallback here relies on the fact that metadata
  878. * used to have an all-or-nothing effect on snapshot count. */
  879. metadata = virDomainSnapshotHasMetadata(snapshot, 0);
  880. if (metadata < 0) {
  881. metadata = virDomainSnapshotNum(dom,
  882. VIR_DOMAIN_SNAPSHOT_LIST_METADATA);
  883. vshResetLibvirtError();
  884. }
  885. if (metadata >= 0)
  886. vshPrint(ctl, "%-15s %s\n", _("Metadata:"),
  887. metadata ? _("yes") : _("no"));
  888. ret = true;
  889. cleanup:
  890. VIR_FREE(state);
  891. xmlXPathFreeContext(ctxt);
  892. xmlFreeDoc(xmldoc);
  893. VIR_FREE(doc);
  894. VIR_FREE(parent);
  895. virshDomainSnapshotFree(snapshot);
  896. virshDomainFree(dom);
  897. return ret;
  898. }
  899. /* Helpers for collecting a list of snapshots. */
  900. struct virshSnap {
  901. virDomainSnapshotPtr snap;
  902. char *parent;
  903. };
  904. struct virshSnapshotList {
  905. struct virshSnap *snaps;
  906. int nsnaps;
  907. };
  908. typedef struct virshSnapshotList *virshSnapshotListPtr;
  909. static void
  910. virshSnapshotListFree(virshSnapshotListPtr snaplist)
  911. {
  912. size_t i;
  913. if (!snaplist)
  914. return;
  915. if (snaplist->snaps) {
  916. for (i = 0; i < snaplist->nsnaps; i++) {
  917. virshDomainSnapshotFree(snaplist->snaps[i].snap);
  918. VIR_FREE(snaplist->snaps[i].parent);
  919. }
  920. VIR_FREE(snaplist->snaps);
  921. }
  922. VIR_FREE(snaplist);
  923. }
  924. static int
  925. virshSnapSorter(const void *a, const void *b)
  926. {
  927. const struct virshSnap *sa = a;
  928. const struct virshSnap *sb = b;
  929. if (sa->snap && !sb->snap)
  930. return -1;
  931. if (!sa->snap)
  932. return sb->snap != NULL;
  933. return vshStrcasecmp(virDomainSnapshotGetName(sa->snap),
  934. virDomainSnapshotGetName(sb->snap));
  935. }
  936. /* Compute a list of snapshots from DOM. If FROM is provided, the
  937. * list is limited to descendants of the given snapshot. If FLAGS is
  938. * given, the list is filtered. If TREE is specified, then all but
  939. * FROM or the roots will also have parent information. */
  940. static virshSnapshotListPtr
  941. virshSnapshotListCollect(vshControl *ctl, virDomainPtr dom,
  942. virDomainSnapshotPtr from,
  943. unsigned int orig_flags, bool tree)
  944. {
  945. size_t i;
  946. char **names = NULL;
  947. int count = -1;
  948. bool descendants = false;
  949. bool roots = false;
  950. virDomainSnapshotPtr *snaps;
  951. virshSnapshotListPtr snaplist = vshMalloc(ctl, sizeof(*snaplist));
  952. virshSnapshotListPtr ret = NULL;
  953. const char *fromname = NULL;
  954. int start_index = -1;
  955. int deleted = 0;
  956. bool filter_fallback = false;
  957. unsigned int flags = orig_flags;
  958. virshControlPtr priv = ctl->privData;
  959. /* Try the interface available in 0.9.13 and newer. */
  960. if (!priv->useSnapshotOld) {
  961. if (from)
  962. count = virDomainSnapshotListAllChildren(from, &snaps, flags);
  963. else
  964. count = virDomainListAllSnapshots(dom, &snaps, flags);
  965. /* If we failed because of flags added in 1.0.1, we can do
  966. * fallback filtering. */
  967. if (count < 0 && last_error->code == VIR_ERR_INVALID_ARG &&
  968. flags & (VIR_DOMAIN_SNAPSHOT_FILTERS_STATUS |
  969. VIR_DOMAIN_SNAPSHOT_FILTERS_LOCATION)) {
  970. flags &= ~(VIR_DOMAIN_SNAPSHOT_FILTERS_STATUS |
  971. VIR_DOMAIN_SNAPSHOT_FILTERS_LOCATION);
  972. vshResetLibvirtError();
  973. filter_fallback = true;
  974. if (from)
  975. count = virDomainSnapshotListAllChildren(from, &snaps, flags);
  976. else
  977. count = virDomainListAllSnapshots(dom, &snaps, flags);
  978. }
  979. }
  980. if (count >= 0) {
  981. /* When mixing --from and --tree, we also want a copy of from
  982. * in the list, but with no parent for that one entry. */
  983. snaplist->snaps = vshCalloc(ctl, count + (tree && from),
  984. sizeof(*snaplist->snaps));
  985. snaplist->nsnaps = count;
  986. for (i = 0; i < count; i++)
  987. snaplist->snaps[i].snap = snaps[i];
  988. VIR_FREE(snaps);
  989. if (tree) {
  990. for (i = 0; i < count; i++) {
  991. if (virshGetSnapshotParent(ctl, snaplist->snaps[i].snap,
  992. &snaplist->snaps[i].parent) < 0)
  993. goto cleanup;
  994. }
  995. if (from) {
  996. snaplist->snaps[snaplist->nsnaps++].snap = from;
  997. virDomainSnapshotRef(from);
  998. }
  999. }
  1000. goto success;
  1001. }
  1002. /* Assume that if we got this far, then the --no-leaves and
  1003. * --no-metadata flags were not supported. Disable groups that
  1004. * have no impact. */
  1005. /* XXX should we emulate --no-leaves? */
  1006. if (flags & VIR_DOMAIN_SNAPSHOT_LIST_NO_LEAVES &&
  1007. flags & VIR_DOMAIN_SNAPSHOT_LIST_LEAVES)
  1008. flags &= ~(VIR_DOMAIN_SNAPSHOT_LIST_NO_LEAVES |
  1009. VIR_DOMAIN_SNAPSHOT_LIST_LEAVES);
  1010. if (flags & VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA &&
  1011. flags & VIR_DOMAIN_SNAPSHOT_LIST_METADATA)
  1012. flags &= ~(VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA |
  1013. VIR_DOMAIN_SNAPSHOT_LIST_METADATA);
  1014. if (flags & VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA) {
  1015. /* We can emulate --no-metadata if --metadata was supported,
  1016. * since it was an all-or-none attribute on old servers. */
  1017. count = virDomainSnapshotNum(dom,
  1018. VIR_DOMAIN_SNAPSHOT_LIST_METADATA);
  1019. if (count < 0)
  1020. goto cleanup;
  1021. if (count > 0)
  1022. return snaplist;
  1023. flags &= ~VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA;
  1024. }
  1025. if (flags & (VIR_DOMAIN_SNAPSHOT_FILTERS_STATUS |
  1026. VIR_DOMAIN_SNAPSHOT_FILTERS_LOCATION)) {
  1027. flags &= ~(VIR_DOMAIN_SNAPSHOT_FILTERS_STATUS |
  1028. VIR_DOMAIN_SNAPSHOT_FILTERS_LOCATION);
  1029. filter_fallback = true;
  1030. }
  1031. /* This uses the interfaces available in 0.8.0-0.9.6
  1032. * (virDomainSnapshotListNames, global list only) and in
  1033. * 0.9.7-0.9.12 (addition of virDomainSnapshotListChildrenNames
  1034. * for child listing, and new flags), as follows, with [*] by the
  1035. * combinations that need parent info (either for filtering
  1036. * purposes or for the resulting tree listing):
  1037. * old new
  1038. * list global as-is global as-is
  1039. * list --roots *global + filter global + flags
  1040. * list --from *global + filter child as-is
  1041. * list --from --descendants *global + filter child + flags
  1042. * list --tree *global as-is *global as-is
  1043. * list --tree --from *global + filter *child + flags
  1044. *
  1045. * Additionally, when --tree and --from are both used, from is
  1046. * added to the final list as the only element without a parent.
  1047. * Otherwise, --from does not appear in the final list.
  1048. */
  1049. if (from) {
  1050. fromname = virDomainSnapshotGetName(from);
  1051. if (!fromname) {
  1052. vshError(ctl, "%s", _("Could not get snapshot name"));
  1053. goto cleanup;
  1054. }
  1055. descendants = (flags & VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS) || tree;
  1056. if (tree)
  1057. flags |= VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS;
  1058. /* Determine if we can use the new child listing API. */
  1059. if (priv->useSnapshotOld ||
  1060. ((count = virDomainSnapshotNumChildren(from, flags)) < 0 &&
  1061. last_error->code == VIR_ERR_NO_SUPPORT)) {
  1062. /* We can emulate --from. */
  1063. /* XXX can we also emulate --leaves? */
  1064. vshResetLibvirtError();
  1065. priv->useSnapshotOld = true;
  1066. flags &= ~VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS;
  1067. goto global;
  1068. }
  1069. if (tree && count >= 0)
  1070. count++;
  1071. } else {
  1072. global:
  1073. /* Global listing (including fallback when --from failed with
  1074. * child listing). */
  1075. count = virDomainSnapshotNum(dom, flags);
  1076. /* Fall back to simulation if --roots was unsupported. */
  1077. /* XXX can we also emulate --leaves? */
  1078. if (!from && count < 0 && last_error->code == VIR_ERR_INVALID_ARG &&
  1079. (flags & VIR_DOMAIN_SNAPSHOT_LIST_ROOTS)) {
  1080. vshResetLibvirtError();
  1081. roots = true;
  1082. flags &= ~VIR_DOMAIN_SNAPSHOT_LIST_ROOTS;
  1083. count = virDomainSnapshotNum(dom, flags);
  1084. }
  1085. }
  1086. if (count < 0) {
  1087. if (!last_error)
  1088. vshError(ctl, _("failed to collect snapshot list"));
  1089. goto cleanup;
  1090. }
  1091. if (!count)
  1092. goto success;
  1093. names = vshCalloc(ctl, sizeof(*names), count);
  1094. /* Now that we have a count, collect the list. */
  1095. if (from && !priv->useSnapshotOld) {
  1096. if (tree) {
  1097. count = virDomainSnapshotListChildrenNames(from, names + 1,
  1098. count - 1, flags);
  1099. if (count >= 0) {
  1100. count++;
  1101. names[0] = g_strdup(fromname);
  1102. }
  1103. } else {
  1104. count = virDomainSnapshotListChildrenNames(from, names,
  1105. count, flags);
  1106. }
  1107. } else {
  1108. count = virDomainSnapshotListNames(dom, names, count, flags);
  1109. }
  1110. if (count < 0)
  1111. goto cleanup;
  1112. snaplist->snaps = vshCalloc(ctl, sizeof(*snaplist->snaps), count);
  1113. snaplist->nsnaps = count;
  1114. for (i = 0; i < count; i++) {
  1115. snaplist->snaps[i].snap = virDomainSnapshotLookupByName(dom,
  1116. names[i], 0);
  1117. if (!snaplist->snaps[i].snap)
  1118. goto cleanup;
  1119. }
  1120. /* Collect parents when needed. With the new API, --tree and
  1121. * --from together put from as the first element without a parent;
  1122. * with the old API we still need to do a post-process filtering
  1123. * based on all parent information. */
  1124. if (tree || (from && priv->useSnapshotOld) || roots) {
  1125. for (i = (from && !priv->useSnapshotOld); i < count; i++) {
  1126. if (from && priv->useSnapshotOld && STREQ(names[i], fromname)) {
  1127. start_index = i;
  1128. if (tree)
  1129. continue;
  1130. }
  1131. if (virshGetSnapshotParent(ctl, snaplist->snaps[i].snap,
  1132. &snaplist->snaps[i].parent) < 0)
  1133. goto cleanup;
  1134. if ((from && ((tree && !snaplist->snaps[i].parent) ||
  1135. (!descendants &&
  1136. STRNEQ_NULLABLE(fromname,
  1137. snaplist->snaps[i].parent)))) ||
  1138. (roots && snaplist->snaps[i].parent)) {
  1139. virshDomainSnapshotFree(snaplist->snaps[i].snap);
  1140. snaplist->snaps[i].snap = NULL;
  1141. VIR_FREE(snaplist->snaps[i].parent);
  1142. deleted++;
  1143. }
  1144. }
  1145. }
  1146. if (tree)
  1147. goto success;
  1148. if (priv->useSnapshotOld && descendants) {
  1149. bool changed = false;
  1150. bool remaining = false;
  1151. /* Make multiple passes over the list - first pass finds
  1152. * direct children and NULLs out all roots and from, remaining
  1153. * passes NULL out any undecided entry whose parent is not
  1154. * still in list. We mark known descendants by clearing
  1155. * snaps[i].parents. Sorry, this is O(n^3) - hope your
  1156. * hierarchy isn't huge. XXX Is it worth making O(n^2 log n)
  1157. * by using qsort and bsearch? */
  1158. if (start_index < 0) {
  1159. vshError(ctl, _("snapshot %s disappeared from list"), fromname);
  1160. goto cleanup;
  1161. }
  1162. for (i = 0; i < count; i++) {
  1163. if (i == start_index || !snaplist->snaps[i].parent) {
  1164. VIR_FREE(names[i]);
  1165. virshDomainSnapshotFree(snaplist->snaps[i].snap);
  1166. snaplist->snaps[i].snap = NULL;
  1167. VIR_FREE(snaplist->snaps[i].parent);
  1168. deleted++;
  1169. } else if (STREQ(snaplist->snaps[i].parent, fromname)) {
  1170. VIR_FREE(snaplist->snaps[i].parent);
  1171. changed = true;
  1172. } else {
  1173. remaining = true;
  1174. }
  1175. }
  1176. if (!changed) {
  1177. ret = vshMalloc(ctl, sizeof(*snaplist));
  1178. goto cleanup;
  1179. }
  1180. while (changed && remaining) {
  1181. changed = remaining = false;
  1182. for (i = 0; i < count; i++) {
  1183. bool found_parent = false;
  1184. size_t j;
  1185. if (!names[i] || !snaplist->snaps[i].parent)
  1186. continue;
  1187. for (j = 0; j < count; j++) {
  1188. if (!names[j] || i == j)
  1189. continue;
  1190. if (STREQ(snaplist->snaps[i].parent, names[j])) {
  1191. found_parent = true;
  1192. if (!snaplist->snaps[j].parent)
  1193. VIR_FREE(snaplist->snaps[i].parent);
  1194. else
  1195. remaining = true;
  1196. break;
  1197. }
  1198. }
  1199. if (!found_parent) {
  1200. changed = true;
  1201. VIR_FREE(names[i]);
  1202. virshDomainSnapshotFree(snaplist->snaps[i].snap);
  1203. snaplist->snaps[i].snap = NULL;
  1204. VIR_FREE(snaplist->snaps[i].parent);
  1205. deleted++;
  1206. }
  1207. }
  1208. }
  1209. }
  1210. success:
  1211. if (filter_fallback) {
  1212. /* Older API didn't filter on status or location, but the
  1213. * information is available in domain XML. */
  1214. if (!(orig_flags & VIR_DOMAIN_SNAPSHOT_FILTERS_STATUS))
  1215. orig_flags |= VIR_DOMAIN_SNAPSHOT_FILTERS_STATUS;
  1216. if (!(orig_flags & VIR_DOMAIN_SNAPSHOT_FILTERS_LOCATION))
  1217. orig_flags |= VIR_DOMAIN_SNAPSHOT_FILTERS_LOCATION;
  1218. for (i = 0; i < snaplist->nsnaps; i++) {
  1219. switch (virshSnapshotFilter(ctl, snaplist->snaps[i].snap,
  1220. orig_flags)) {
  1221. case 1:
  1222. break;
  1223. case 0:
  1224. virshDomainSnapshotFree(snaplist->snaps[i].snap);
  1225. snaplist->snaps[i].snap = NULL;
  1226. VIR_FREE(snaplist->snaps[i].parent);
  1227. deleted++;
  1228. break;
  1229. default:
  1230. goto cleanup;
  1231. }
  1232. }
  1233. }
  1234. if (!(orig_flags & VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL))
  1235. qsort(snaplist->snaps, snaplist->nsnaps, sizeof(*snaplist->snaps),
  1236. virshSnapSorter);
  1237. snaplist->nsnaps -= deleted;
  1238. ret = g_steal_pointer(&snaplist);
  1239. cleanup:
  1240. virshSnapshotListFree(snaplist);
  1241. if (names && count > 0)
  1242. for (i = 0; i < count; i++)
  1243. VIR_FREE(names[i]);
  1244. VIR_FREE(names);
  1245. return ret;
  1246. }
  1247. static const char *
  1248. virshSnapshotListLookup(int id, bool parent, void *opaque)
  1249. {
  1250. virshSnapshotListPtr snaplist = opaque;
  1251. if (parent)
  1252. return snaplist->snaps[id].parent;
  1253. return virDomainSnapshotGetName(snaplist->snaps[id].snap);
  1254. }
  1255. /*
  1256. * "snapshot-list" command
  1257. */
  1258. static const vshCmdInfo info_snapshot_list[] = {
  1259. {.name = "help",
  1260. .data = N_("List snapshots for a domain")
  1261. },
  1262. {.name = "desc",
  1263. .data = N_("Snapshot List")
  1264. },
  1265. {.name = NULL}
  1266. };
  1267. static const vshCmdOptDef opts_snapshot_list[] = {
  1268. VIRSH_COMMON_OPT_DOMAIN_FULL(0),
  1269. {.name = "parent",
  1270. .type = VSH_OT_BOOL,
  1271. .help = N_("add a column showing parent snapshot")
  1272. },
  1273. {.name = "roots",
  1274. .type = VSH_OT_BOOL,
  1275. .help = N_("list only snapshots without parents")
  1276. },
  1277. {.name = "leaves",
  1278. .type = VSH_OT_BOOL,
  1279. .help = N_("list only snapshots without children")
  1280. },
  1281. {.name = "no-leaves",
  1282. .type = VSH_OT_BOOL,
  1283. .help = N_("list only snapshots that are not leaves (with children)")
  1284. },
  1285. {.name = "metadata",
  1286. .type = VSH_OT_BOOL,
  1287. .help = N_("list only snapshots that have metadata that would prevent undefine")
  1288. },
  1289. {.name = "no-metadata",
  1290. .type = VSH_OT_BOOL,
  1291. .help = N_("list only snapshots that have no metadata managed by libvirt")
  1292. },
  1293. {.name = "inactive",
  1294. .type = VSH_OT_BOOL,
  1295. .help = N_("filter by snapshots taken while inactive")
  1296. },
  1297. {.name = "active",
  1298. .type = VSH_OT_BOOL,
  1299. .help = N_("filter by snapshots taken while active (full system snapshots)")
  1300. },
  1301. {.name = "disk-only",
  1302. .type = VSH_OT_BOOL,
  1303. .help = N_("filter by disk-only snapshots")
  1304. },
  1305. {.name = "internal",
  1306. .type = VSH_OT_BOOL,
  1307. .help = N_("filter by internal snapshots")
  1308. },
  1309. {.name = "external",
  1310. .type = VSH_OT_BOOL,
  1311. .help = N_("filter by external snapshots")
  1312. },
  1313. {.name = "tree",
  1314. .type = VSH_OT_BOOL,
  1315. .help = N_("list snapshots in a tree")
  1316. },
  1317. {.name = "from",
  1318. .type = VSH_OT_STRING,
  1319. .help = N_("limit list to children of given snapshot")
  1320. },
  1321. VIRSH_COMMON_OPT_CURRENT(N_("limit list to children of current snapshot")),
  1322. {.name = "descendants",
  1323. .type = VSH_OT_BOOL,
  1324. .help = N_("with --from, list all descendants")
  1325. },
  1326. {.name = "name",
  1327. .type = VSH_OT_BOOL,
  1328. .help = N_("list snapshot names only")
  1329. },
  1330. {.name = "topological",
  1331. .type = VSH_OT_BOOL,
  1332. .help = N_("sort list topologically rather than by name"),
  1333. },
  1334. {.name = NULL}
  1335. };
  1336. static bool
  1337. cmdSnapshotList(vshControl *ctl, const vshCmd *cmd)
  1338. {
  1339. virDomainPtr dom = NULL;
  1340. bool ret = false;
  1341. unsigned int flags = 0;
  1342. size_t i;
  1343. xmlDocPtr xml = NULL;
  1344. xmlXPathContextPtr ctxt = NULL;
  1345. char *doc = NULL;
  1346. virDomainSnapshotPtr snapshot = NULL;
  1347. char *state = NULL;
  1348. long long creation_longlong;
  1349. g_autoptr(GDateTime) then = NULL;
  1350. g_autofree gchar *thenstr = NULL;
  1351. bool tree = vshCommandOptBool(cmd, "tree");
  1352. bool name = vshCommandOptBool(cmd, "name");
  1353. bool from = vshCommandOptBool(cmd, "from");
  1354. bool parent = vshCommandOptBool(cmd, "parent");
  1355. bool roots = vshCommandOptBool(cmd, "roots");
  1356. bool current = vshCommandOptBool(cmd, "current");
  1357. const char *from_snap = NULL;
  1358. char *parent_snap = NULL;
  1359. virDomainSnapshotPtr start = NULL;
  1360. virshSnapshotListPtr snaplist = NULL;
  1361. vshTablePtr table = NULL;
  1362. VSH_EXCLUSIVE_OPTIONS_VAR(tree, name);
  1363. VSH_EXCLUSIVE_OPTIONS_VAR(parent, roots);
  1364. VSH_EXCLUSIVE_OPTIONS_VAR(parent, tree);
  1365. VSH_EXCLUSIVE_OPTIONS_VAR(roots, tree);
  1366. VSH_EXCLUSIVE_OPTIONS_VAR(roots, from);
  1367. VSH_EXCLUSIVE_OPTIONS_VAR(roots, current);
  1368. #define FILTER(option, flag) \
  1369. do { \
  1370. if (vshCommandOptBool(cmd, option)) { \
  1371. if (tree) { \
  1372. vshError(ctl, \
  1373. _("--%s and --tree are mutually exclusive"), \
  1374. option); \
  1375. return false; \
  1376. } \
  1377. flags |= VIR_DOMAIN_SNAPSHOT_LIST_ ## flag; \
  1378. } \
  1379. } while (0)
  1380. FILTER("leaves", LEAVES);
  1381. FILTER("no-leaves", NO_LEAVES);
  1382. FILTER("inactive", INACTIVE);
  1383. FILTER("active", ACTIVE);
  1384. FILTER("disk-only", DISK_ONLY);
  1385. FILTER("internal", INTERNAL);
  1386. FILTER("external", EXTERNAL);
  1387. #undef FILTER
  1388. if (vshCommandOptBool(cmd, "topological"))
  1389. flags |= VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL;
  1390. if (roots)
  1391. flags |= VIR_DOMAIN_SNAPSHOT_LIST_ROOTS;
  1392. if (vshCommandOptBool(cmd, "metadata"))
  1393. flags |= VIR_DOMAIN_SNAPSHOT_LIST_METADATA;
  1394. if (vshCommandOptBool(cmd, "no-metadata"))
  1395. flags |= VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA;
  1396. if (vshCommandOptBool(cmd, "descendants")) {
  1397. if (!from && !current) {
  1398. vshError(ctl, "%s",
  1399. _("--descendants requires either --from or --current"));
  1400. return false;
  1401. }
  1402. flags |= VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS;
  1403. }
  1404. if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
  1405. return false;
  1406. if ((from || current) &&
  1407. virshLookupSnapshot(ctl, cmd, "from", true, dom, &start, &from_snap) < 0)
  1408. goto cleanup;
  1409. if (!(snaplist = virshSnapshotListCollect(ctl, dom, start, flags, tree)))
  1410. goto cleanup;
  1411. if (!tree && !name) {
  1412. if (parent)
  1413. table = vshTableNew(_("Name"), _("Creation Time"), _("State"), _("Parent"), NULL);
  1414. else
  1415. table = vshTableNew(_("Name"), _("Creation Time"), _("State"), NULL);
  1416. if (!table)
  1417. goto cleanup;
  1418. }
  1419. if (tree) {
  1420. for (i = 0; i < snaplist->nsnaps; i++) {
  1421. if (!snaplist->snaps[i].parent &&
  1422. vshTreePrint(ctl, virshSnapshotListLookup, snaplist,
  1423. snaplist->nsnaps, i) < 0)
  1424. goto cleanup;
  1425. }
  1426. ret = true;
  1427. goto cleanup;
  1428. }
  1429. for (i = 0; i < snaplist->nsnaps; i++) {
  1430. const char *snap_name;
  1431. /* free up memory from previous iterations of the loop */
  1432. VIR_FREE(parent_snap);
  1433. VIR_FREE(state);
  1434. xmlXPathFreeContext(ctxt);
  1435. xmlFreeDoc(xml);
  1436. VIR_FREE(doc);
  1437. snapshot = snaplist->snaps[i].snap;
  1438. snap_name = virDomainSnapshotGetName(snapshot);
  1439. assert(snap_name);
  1440. if (name) {
  1441. /* just print the snapshot name */
  1442. vshPrint(ctl, "%s\n", snap_name);
  1443. continue;
  1444. }
  1445. if (!(doc = virDomainSnapshotGetXMLDesc(snapshot, 0)))
  1446. continue;
  1447. if (!(xml = virXMLParseStringCtxt(doc, _("(domain_snapshot)"), &ctxt)))
  1448. continue;
  1449. if (parent)
  1450. parent_snap = virXPathString("string(/domainsnapshot/parent/name)",
  1451. ctxt);
  1452. if (!(state = virXPathString("string(/domainsnapshot/state)", ctxt)))
  1453. continue;
  1454. if (virXPathLongLong("string(/domainsnapshot/creationTime)", ctxt,
  1455. &creation_longlong) < 0)
  1456. continue;
  1457. then = g_date_time_new_from_unix_local(creation_longlong);
  1458. thenstr = g_date_time_format(then, "%Y-%m-%d %H:%M:%S %z");
  1459. if (parent) {
  1460. if (vshTableRowAppend(table, snap_name, thenstr, state,
  1461. NULLSTR_EMPTY(parent_snap),
  1462. NULL) < 0)
  1463. goto cleanup;
  1464. } else {
  1465. if (vshTableRowAppend(table, snap_name, thenstr, state,
  1466. NULL) < 0)
  1467. goto cleanup;
  1468. }
  1469. }
  1470. if (table)
  1471. vshTablePrintToStdout(table, ctl);
  1472. ret = true;
  1473. cleanup:
  1474. /* this frees up memory from the last iteration of the loop */
  1475. virshSnapshotListFree(snaplist);
  1476. VIR_FREE(parent_snap);
  1477. VIR_FREE(state);
  1478. virshDomainSnapshotFree(start);
  1479. xmlXPathFreeContext(ctxt);
  1480. xmlFreeDoc(xml);
  1481. VIR_FREE(doc);
  1482. virshDomainFree(dom);
  1483. vshTableFree(table);
  1484. return ret;
  1485. }
  1486. /*
  1487. * "snapshot-dumpxml" command
  1488. */
  1489. static const vshCmdInfo info_snapshot_dumpxml[] = {
  1490. {.name = "help",
  1491. .data = N_("Dump XML for a domain snapshot")
  1492. },
  1493. {.name = "desc",
  1494. .data = N_("Snapshot Dump XML")
  1495. },
  1496. {.name = NULL}
  1497. };
  1498. static const vshCmdOptDef opts_snapshot_dumpxml[] = {
  1499. VIRSH_COMMON_OPT_DOMAIN_FULL(0),
  1500. {.name = "snapshotname",
  1501. .type = VSH_OT_DATA,
  1502. .flags = VSH_OFLAG_REQ,
  1503. .help = N_("snapshot name"),
  1504. .completer = virshSnapshotNameCompleter,
  1505. },
  1506. {.name = "security-info",
  1507. .type = VSH_OT_BOOL,
  1508. .help = N_("include security sensitive information in XML dump")
  1509. },
  1510. {.name = NULL}
  1511. };
  1512. static bool
  1513. cmdSnapshotDumpXML(vshControl *ctl, const vshCmd *cmd)
  1514. {
  1515. virDomainPtr dom = NULL;
  1516. bool ret = false;
  1517. const char *name = NULL;
  1518. virDomainSnapshotPtr snapshot = NULL;
  1519. char *xml = NULL;
  1520. unsigned int flags = 0;
  1521. if (vshCommandOptBool(cmd, "security-info"))
  1522. flags |= VIR_DOMAIN_XML_SECURE;
  1523. if (vshCommandOptStringReq(ctl, cmd, "snapshotname", &name) < 0)
  1524. return false;
  1525. if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
  1526. return false;
  1527. if (!(snapshot = virDomainSnapshotLookupByName(dom, name, 0)))
  1528. goto cleanup;
  1529. if (!(xml = virDomainSnapshotGetXMLDesc(snapshot, flags)))
  1530. goto cleanup;
  1531. vshPrint(ctl, "%s", xml);
  1532. ret = true;
  1533. cleanup:
  1534. VIR_FREE(xml);
  1535. virshDomainSnapshotFree(snapshot);
  1536. virshDomainFree(dom);
  1537. return ret;
  1538. }
  1539. /*
  1540. * "snapshot-parent" command
  1541. */
  1542. static const vshCmdInfo info_snapshot_parent[] = {
  1543. {.name = "help",
  1544. .data = N_("Get the name of the parent of a snapshot")
  1545. },
  1546. {.name = "desc",
  1547. .data = N_("Extract the snapshot's parent, if any")
  1548. },
  1549. {.name = NULL}
  1550. };
  1551. static const vshCmdOptDef opts_snapshot_parent[] = {
  1552. VIRSH_COMMON_OPT_DOMAIN_FULL(0),
  1553. {.name = "snapshotname",
  1554. .type = VSH_OT_STRING,
  1555. .help = N_("find parent of snapshot name"),
  1556. .completer = virshSnapshotNameCompleter,
  1557. },
  1558. VIRSH_COMMON_OPT_CURRENT(N_("find parent of current snapshot")),
  1559. {.name = NULL}
  1560. };
  1561. static bool
  1562. cmdSnapshotParent(vshControl *ctl, const vshCmd *cmd)
  1563. {
  1564. virDomainPtr dom = NULL;
  1565. bool ret = false;
  1566. const char *name = NULL;
  1567. virDomainSnapshotPtr snapshot = NULL;
  1568. char *parent = NULL;
  1569. dom = virshCommandOptDomain(ctl, cmd, NULL);
  1570. if (dom == NULL)
  1571. goto cleanup;
  1572. if (virshLookupSnapshot(ctl, cmd, "snapshotname", true, dom,
  1573. &snapshot, &name) < 0)
  1574. goto cleanup;
  1575. if (virshGetSnapshotParent(ctl, snapshot, &parent) < 0)
  1576. goto cleanup;
  1577. if (!parent) {
  1578. vshError(ctl, _("snapshot '%s' has no parent"), name);
  1579. goto cleanup;
  1580. }
  1581. vshPrint(ctl, "%s", parent);
  1582. ret = true;
  1583. cleanup:
  1584. VIR_FREE(parent);
  1585. virshDomainSnapshotFree(snapshot);
  1586. virshDomainFree(dom);
  1587. return ret;
  1588. }
  1589. /*
  1590. * "snapshot-revert" command
  1591. */
  1592. static const vshCmdInfo info_snapshot_revert[] = {
  1593. {.name = "help",
  1594. .data = N_("Revert a domain to a snapshot")
  1595. },
  1596. {.name = "desc",
  1597. .data = N_("Revert domain to snapshot")
  1598. },
  1599. {.name = NULL}
  1600. };
  1601. static const vshCmdOptDef opts_snapshot_revert[] = {
  1602. VIRSH_COMMON_OPT_DOMAIN_FULL(0),
  1603. {.name = "snapshotname",
  1604. .type = VSH_OT_STRING,
  1605. .help = N_("snapshot name"),
  1606. .completer = virshSnapshotNameCompleter,
  1607. },
  1608. VIRSH_COMMON_OPT_CURRENT(N_("revert to current snapshot")),
  1609. {.name = "running",
  1610. .type = VSH_OT_BOOL,
  1611. .help = N_("after reverting, change state to running")
  1612. },
  1613. {.name = "paused",
  1614. .type = VSH_OT_BOOL,
  1615. .help = N_("after reverting, change state to paused")
  1616. },
  1617. {.name = "force",
  1618. .type = VSH_OT_BOOL,
  1619. .help = N_("try harder on risky reverts")
  1620. },
  1621. {.name = NULL}
  1622. };
  1623. static bool
  1624. cmdDomainSnapshotRevert(vshControl *ctl, const vshCmd *cmd)
  1625. {
  1626. virDomainPtr dom = NULL;
  1627. bool ret = false;
  1628. const char *name = NULL;
  1629. virDomainSnapshotPtr snapshot = NULL;
  1630. unsigned int flags = 0;
  1631. bool force = false;
  1632. int result;
  1633. if (vshCommandOptBool(cmd, "running"))
  1634. flags |= VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING;
  1635. if (vshCommandOptBool(cmd, "paused"))
  1636. flags |= VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED;
  1637. /* We want virsh snapshot-revert --force to work even when talking
  1638. * to older servers that did the unsafe revert by default but
  1639. * reject the flag, so we probe without the flag, and only use it
  1640. * when the error says it will make a difference. */
  1641. if (vshCommandOptBool(cmd, "force"))
  1642. force = true;
  1643. dom = virshCommandOptDomain(ctl, cmd, NULL);
  1644. if (dom == NULL)
  1645. goto cleanup;
  1646. if (virshLookupSnapshot(ctl, cmd, "snapshotname", true, dom,
  1647. &snapshot, &name) < 0)
  1648. goto cleanup;
  1649. result = virDomainRevertToSnapshot(snapshot, flags);
  1650. if (result < 0 && force &&
  1651. last_error->code == VIR_ERR_SNAPSHOT_REVERT_RISKY) {
  1652. flags |= VIR_DOMAIN_SNAPSHOT_REVERT_FORCE;
  1653. vshResetLibvirtError();
  1654. result = virDomainRevertToSnapshot(snapshot, flags);
  1655. }
  1656. if (result < 0)
  1657. goto cleanup;
  1658. ret = true;
  1659. cleanup:
  1660. virshDomainSnapshotFree(snapshot);
  1661. virshDomainFree(dom);
  1662. return ret;
  1663. }
  1664. /*
  1665. * "snapshot-delete" command
  1666. */
  1667. static const vshCmdInfo info_snapshot_delete[] = {
  1668. {.name = "help",
  1669. .data = N_("Delete a domain snapshot")
  1670. },
  1671. {.name = "desc",
  1672. .data = N_("Snapshot Delete")
  1673. },
  1674. {.name = NULL}
  1675. };
  1676. static const vshCmdOptDef opts_snapshot_delete[] = {
  1677. VIRSH_COMMON_OPT_DOMAIN_FULL(0),
  1678. {.name = "snapshotname",
  1679. .type = VSH_OT_STRING,
  1680. .help = N_("snapshot name"),
  1681. .completer = virshSnapshotNameCompleter,
  1682. },
  1683. VIRSH_COMMON_OPT_CURRENT(N_("delete current snapshot")),
  1684. {.name = "children",
  1685. .type = VSH_OT_BOOL,
  1686. .help = N_("delete snapshot and all children")
  1687. },
  1688. {.name = "children-only",
  1689. .type = VSH_OT_BOOL,
  1690. .help = N_("delete children but not snapshot")
  1691. },
  1692. {.name = "metadata",
  1693. .type = VSH_OT_BOOL,
  1694. .help = N_("delete only libvirt metadata, leaving snapshot contents behind")
  1695. },
  1696. {.name = NULL}
  1697. };
  1698. static bool
  1699. cmdSnapshotDelete(vshControl *ctl, const vshCmd *cmd)
  1700. {
  1701. virDomainPtr dom = NULL;
  1702. bool ret = false;
  1703. const char *name = NULL;
  1704. virDomainSnapshotPtr snapshot = NULL;
  1705. unsigned int flags = 0;
  1706. dom = virshCommandOptDomain(ctl, cmd, NULL);
  1707. if (dom == NULL)
  1708. goto cleanup;
  1709. if (virshLookupSnapshot(ctl, cmd, "snapshotname", true, dom,
  1710. &snapshot, &name) < 0)
  1711. goto cleanup;
  1712. if (vshCommandOptBool(cmd, "children"))
  1713. flags |= VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN;
  1714. if (vshCommandOptBool(cmd, "children-only"))
  1715. flags |= VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY;
  1716. if (vshCommandOptBool(cmd, "metadata"))
  1717. flags |= VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY;
  1718. /* XXX If we wanted, we could emulate DELETE_CHILDREN_ONLY even on
  1719. * older servers that reject the flag, by manually computing the
  1720. * list of descendants. But that's a lot of code to maintain. */
  1721. if (virDomainSnapshotDelete(snapshot, flags) == 0) {
  1722. if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY)
  1723. vshPrintExtra(ctl, _("Domain snapshot %s children deleted\n"), name);
  1724. else
  1725. vshPrintExtra(ctl, _("Domain snapshot %s deleted\n"), name);
  1726. } else {
  1727. vshError(ctl, _("Failed to delete snapshot %s"), name);
  1728. goto cleanup;
  1729. }
  1730. ret = true;
  1731. cleanup:
  1732. virshDomainSnapshotFree(snapshot);
  1733. virshDomainFree(dom);
  1734. return ret;
  1735. }
  1736. const vshCmdDef snapshotCmds[] = {
  1737. {.name = "snapshot-create",
  1738. .handler = cmdSnapshotCreate,
  1739. .opts = opts_snapshot_create,
  1740. .info = info_snapshot_create,
  1741. .flags = 0
  1742. },
  1743. {.name = "snapshot-create-as",
  1744. .handler = cmdSnapshotCreateAs,
  1745. .opts = opts_snapshot_create_as,
  1746. .info = info_snapshot_create_as,
  1747. .flags = 0
  1748. },
  1749. {.name = "snapshot-current",
  1750. .handler = cmdSnapshotCurrent,
  1751. .opts = opts_snapshot_current,
  1752. .info = info_snapshot_current,
  1753. .flags = 0
  1754. },
  1755. {.name = "snapshot-delete",
  1756. .handler = cmdSnapshotDelete,
  1757. .opts = opts_snapshot_delete,
  1758. .info = info_snapshot_delete,
  1759. .flags = 0
  1760. },
  1761. {.name = "snapshot-dumpxml",
  1762. .handler = cmdSnapshotDumpXML,
  1763. .opts = opts_snapshot_dumpxml,
  1764. .info = info_snapshot_dumpxml,
  1765. .flags = 0
  1766. },
  1767. {.name = "snapshot-edit",
  1768. .handler = cmdSnapshotEdit,
  1769. .opts = opts_snapshot_edit,
  1770. .info = info_snapshot_edit,
  1771. .flags = 0
  1772. },
  1773. {.name = "snapshot-info",
  1774. .handler = cmdSnapshotInfo,
  1775. .opts = opts_snapshot_info,
  1776. .info = info_snapshot_info,
  1777. .flags = 0
  1778. },
  1779. {.name = "snapshot-list",
  1780. .handler = cmdSnapshotList,
  1781. .opts = opts_snapshot_list,
  1782. .info = info_snapshot_list,
  1783. .flags = 0
  1784. },
  1785. {.name = "snapshot-parent",
  1786. .handler = cmdSnapshotParent,
  1787. .opts = opts_snapshot_parent,
  1788. .info = info_snapshot_parent,
  1789. .flags = 0
  1790. },
  1791. {.name = "snapshot-revert",
  1792. .handler = cmdDomainSnapshotRevert,
  1793. .opts = opts_snapshot_revert,
  1794. .info = info_snapshot_revert,
  1795. .flags = 0
  1796. },
  1797. {.name = NULL}
  1798. };