From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- .../Frontends/VBoxManage/VBoxManageSnapshot.cpp | 670 +++++++++++++++++++++ 1 file changed, 670 insertions(+) create mode 100644 src/VBox/Frontends/VBoxManage/VBoxManageSnapshot.cpp (limited to 'src/VBox/Frontends/VBoxManage/VBoxManageSnapshot.cpp') diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageSnapshot.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageSnapshot.cpp new file mode 100644 index 00000000..69598c44 --- /dev/null +++ b/src/VBox/Frontends/VBoxManage/VBoxManageSnapshot.cpp @@ -0,0 +1,670 @@ +/* $Id: VBoxManageSnapshot.cpp $ */ +/** @file + * VBoxManage - The 'snapshot' command. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "VBoxManage.h" +using namespace com; + +DECLARE_TRANSLATION_CONTEXT(Snapshot); + +/** + * Helper function used with "VBoxManage snapshot ... dump". Gets called to find the + * snapshot in the machine's snapshot tree that uses a particular diff image child of + * a medium. + * Horribly inefficient since we keep re-querying the snapshots tree for each image, + * but this is for quick debugging only. + * @param pMedium + * @param pThisSnapshot + * @param pCurrentSnapshot + * @param uMediumLevel + * @param uSnapshotLevel + * @return + */ +bool FindAndPrintSnapshotUsingMedium(ComPtr &pMedium, + ComPtr &pThisSnapshot, + ComPtr &pCurrentSnapshot, + uint32_t uMediumLevel, + uint32_t uSnapshotLevel) +{ + HRESULT hrc; + + do + { + // get snapshot machine so we can figure out which diff image this created + ComPtr pSnapshotMachine; + CHECK_ERROR_BREAK(pThisSnapshot, COMGETTER(Machine)(pSnapshotMachine.asOutParam())); + + // get media attachments + SafeIfaceArray aAttachments; + CHECK_ERROR_BREAK(pSnapshotMachine, COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(aAttachments))); + + for (uint32_t i = 0; + i < aAttachments.size(); + ++i) + { + ComPtr pAttach(aAttachments[i]); + DeviceType_T type; + CHECK_ERROR_BREAK(pAttach, COMGETTER(Type)(&type)); + if (type == DeviceType_HardDisk) + { + ComPtr pMediumInSnapshot; + CHECK_ERROR_BREAK(pAttach, COMGETTER(Medium)(pMediumInSnapshot.asOutParam())); + + if (pMediumInSnapshot == pMedium) + { + // get snapshot name + Bstr bstrSnapshotName; + CHECK_ERROR_BREAK(pThisSnapshot, COMGETTER(Name)(bstrSnapshotName.asOutParam())); + + RTPrintf("%*s \"%ls\"%s\n", + 50 + uSnapshotLevel * 2, "", // indent + bstrSnapshotName.raw(), + (pThisSnapshot == pCurrentSnapshot) ? " (CURSNAP)" : ""); + return true; // found + } + } + } + + // not found: then recurse into child snapshots + SafeIfaceArray aSnapshots; + CHECK_ERROR_BREAK(pThisSnapshot, COMGETTER(Children)(ComSafeArrayAsOutParam(aSnapshots))); + + for (uint32_t i = 0; + i < aSnapshots.size(); + ++i) + { + ComPtr pChild(aSnapshots[i]); + if (FindAndPrintSnapshotUsingMedium(pMedium, + pChild, + pCurrentSnapshot, + uMediumLevel, + uSnapshotLevel + 1)) + // found: + break; + } + } while (0); + + return false; +} + +/** + * Helper function used with "VBoxManage snapshot ... dump". Called from DumpSnapshot() + * for each hard disk attachment found in a virtual machine. This then writes out the + * root (base) medium for that hard disk attachment and recurses into the children + * tree of that medium, correlating it with the snapshots of the machine. + * @param pCurrentStateMedium constant, the medium listed in the current machine data (latest diff image). + * @param pMedium variant, initially the base medium, then a child of the base medium when recursing. + * @param pRootSnapshot constant, the root snapshot of the machine, if any; this then looks into the child snapshots. + * @param pCurrentSnapshot constant, the machine's current snapshot (so we can mark it in the output). + * @param uLevel variant, the recursion level for output indentation. + */ +void DumpMediumWithChildren(ComPtr &pCurrentStateMedium, + ComPtr &pMedium, + ComPtr &pRootSnapshot, + ComPtr &pCurrentSnapshot, + uint32_t uLevel) +{ + HRESULT hrc; + do + { + // print this medium + Bstr bstrMediumName; + CHECK_ERROR_BREAK(pMedium, COMGETTER(Name)(bstrMediumName.asOutParam())); + RTPrintf("%*s \"%ls\"%s\n", + uLevel * 2, "", // indent + bstrMediumName.raw(), + (pCurrentStateMedium == pMedium) ? " (CURSTATE)" : ""); + + // find and print the snapshot that uses this particular medium (diff image) + FindAndPrintSnapshotUsingMedium(pMedium, pRootSnapshot, pCurrentSnapshot, uLevel, 0); + + // recurse into children + SafeIfaceArray aChildren; + CHECK_ERROR_BREAK(pMedium, COMGETTER(Children)(ComSafeArrayAsOutParam(aChildren))); + for (uint32_t i = 0; + i < aChildren.size(); + ++i) + { + ComPtr pChild(aChildren[i]); + DumpMediumWithChildren(pCurrentStateMedium, pChild, pRootSnapshot, pCurrentSnapshot, uLevel + 1); + } + } while (0); +} + + +/** + * Handles the 'snapshot myvm list' sub-command. + * @returns Exit code. + * @param pArgs The handler argument package. + * @param pMachine Reference to the VM (locked) we're operating on. + */ +static RTEXITCODE handleSnapshotList(HandlerArg *pArgs, ComPtr &pMachine) +{ + static const RTGETOPTDEF g_aOptions[] = + { + { "--details", 'D', RTGETOPT_REQ_NOTHING }, + { "--machinereadable", 'M', RTGETOPT_REQ_NOTHING }, + }; + + VMINFO_DETAILS enmDetails = VMINFO_STANDARD; + + int c; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, pArgs->argc, pArgs->argv, g_aOptions, RT_ELEMENTS(g_aOptions), 2 /*iArg*/, 0 /*fFlags*/); + while ((c = RTGetOpt(&GetState, &ValueUnion))) + { + switch (c) + { + case 'D': enmDetails = VMINFO_FULL; break; + case 'M': enmDetails = VMINFO_MACHINEREADABLE; break; + default: return errorGetOpt(c, &ValueUnion); + } + } + + ComPtr pSnapshot; + HRESULT hrc = pMachine->FindSnapshot(Bstr().raw(), pSnapshot.asOutParam()); + if (FAILED(hrc)) + { + RTPrintf(Snapshot::tr("This machine does not have any snapshots\n")); + return RTEXITCODE_FAILURE; + } + if (pSnapshot) + { + ComPtr pCurrentSnapshot; + CHECK_ERROR2I_RET(pMachine, COMGETTER(CurrentSnapshot)(pCurrentSnapshot.asOutParam()), RTEXITCODE_FAILURE); + hrc = showSnapshots(pSnapshot, pCurrentSnapshot, enmDetails); + if (FAILED(hrc)) + return RTEXITCODE_FAILURE; + } + return RTEXITCODE_SUCCESS; +} + +/** + * Implementation for "VBoxManage snapshot ... dump". This goes thru the machine's + * medium attachments and calls DumpMediumWithChildren() for each hard disk medium found, + * which then dumps the parent/child tree of that medium together with the corresponding + * snapshots. + * @param pMachine Machine to dump snapshots for. + */ +void DumpSnapshot(ComPtr &pMachine) +{ + HRESULT hrc; + + do + { + // get root snapshot + ComPtr pSnapshot; + CHECK_ERROR_BREAK(pMachine, FindSnapshot(Bstr("").raw(), pSnapshot.asOutParam())); + + // get current snapshot + ComPtr pCurrentSnapshot; + CHECK_ERROR_BREAK(pMachine, COMGETTER(CurrentSnapshot)(pCurrentSnapshot.asOutParam())); + + // get media attachments + SafeIfaceArray aAttachments; + CHECK_ERROR_BREAK(pMachine, COMGETTER(MediumAttachments)(ComSafeArrayAsOutParam(aAttachments))); + for (uint32_t i = 0; + i < aAttachments.size(); + ++i) + { + ComPtr pAttach(aAttachments[i]); + DeviceType_T type; + CHECK_ERROR_BREAK(pAttach, COMGETTER(Type)(&type)); + if (type == DeviceType_HardDisk) + { + ComPtr pCurrentStateMedium; + CHECK_ERROR_BREAK(pAttach, COMGETTER(Medium)(pCurrentStateMedium.asOutParam())); + + ComPtr pBaseMedium; + CHECK_ERROR_BREAK(pCurrentStateMedium, COMGETTER(Base)(pBaseMedium.asOutParam())); + + Bstr bstrBaseMediumName; + CHECK_ERROR_BREAK(pBaseMedium, COMGETTER(Name)(bstrBaseMediumName.asOutParam())); + + RTPrintf(Snapshot::tr("[%RI32] Images and snapshots for medium \"%ls\"\n"), i, bstrBaseMediumName.raw()); + + DumpMediumWithChildren(pCurrentStateMedium, + pBaseMedium, + pSnapshot, + pCurrentSnapshot, + 0); + } + } + } while (0); +} + +typedef enum SnapshotUniqueFlags +{ + SnapshotUniqueFlags_Null = 0, + SnapshotUniqueFlags_Number = RT_BIT(1), + SnapshotUniqueFlags_Timestamp = RT_BIT(2), + SnapshotUniqueFlags_Space = RT_BIT(16), + SnapshotUniqueFlags_Force = RT_BIT(30) +} SnapshotUniqueFlags; + +static int parseSnapshotUniqueFlags(const char *psz, SnapshotUniqueFlags *pUnique) +{ + int vrc = VINF_SUCCESS; + unsigned uUnique = 0; + while (psz && *psz && RT_SUCCESS(vrc)) + { + size_t len; + const char *pszComma = strchr(psz, ','); + if (pszComma) + len = pszComma - psz; + else + len = strlen(psz); + if (len > 0) + { + if (!RTStrNICmp(psz, "number", len)) + uUnique |= SnapshotUniqueFlags_Number; + else if (!RTStrNICmp(psz, "timestamp", len)) + uUnique |= SnapshotUniqueFlags_Timestamp; + else if (!RTStrNICmp(psz, "space", len)) + uUnique |= SnapshotUniqueFlags_Space; + else if (!RTStrNICmp(psz, "force", len)) + uUnique |= SnapshotUniqueFlags_Force; + else + vrc = VERR_PARSE_ERROR; + } + if (pszComma) + psz += len + 1; + else + psz += len; + } + + if (RT_SUCCESS(vrc)) + *pUnique = (SnapshotUniqueFlags)uUnique; + return vrc; +} + +/** + * Implementation for all VBoxManage snapshot ... subcommands. + * @param a + * @return + */ +RTEXITCODE handleSnapshot(HandlerArg *a) +{ + HRESULT hrc; + +/** @todo r=bird: sub-standard command line parsing here! + * + * 'VBoxManage snapshot empty take --help' takes a snapshot rather than display + * help as you would expect. + * + */ + + /* we need at least a VM and a command */ + if (a->argc < 2) + return errorSyntax(Snapshot::tr("Not enough parameters")); + + /* the first argument must be the VM */ + Bstr bstrMachine(a->argv[0]); + ComPtr pMachine; + CHECK_ERROR(a->virtualBox, FindMachine(bstrMachine.raw(), + pMachine.asOutParam())); + if (!pMachine) + return RTEXITCODE_FAILURE; + + /* we have to open a session for this task (new or shared) */ + CHECK_ERROR_RET(pMachine, LockMachine(a->session, LockType_Shared), RTEXITCODE_FAILURE); + do + { + /* replace the (read-only) IMachine object by a writable one */ + ComPtr sessionMachine; + CHECK_ERROR_BREAK(a->session, COMGETTER(Machine)(sessionMachine.asOutParam())); + + /* switch based on the command */ + bool fDelete = false, + fRestore = false, + fRestoreCurrent = false; + + if (!strcmp(a->argv[1], "take")) + { + setCurrentSubcommand(HELP_SCOPE_SNAPSHOT_TAKE); + + /* there must be a name */ + if (a->argc < 3) + { + errorSyntax(Snapshot::tr("Missing snapshot name")); + hrc = E_FAIL; + break; + } + Bstr name(a->argv[2]); + + /* parse the optional arguments */ + Bstr desc; + bool fPause = true; /* default is NO live snapshot */ + SnapshotUniqueFlags enmUnique = SnapshotUniqueFlags_Null; + static const RTGETOPTDEF s_aTakeOptions[] = + { + { "--description", 'd', RTGETOPT_REQ_STRING }, + { "-description", 'd', RTGETOPT_REQ_STRING }, + { "-desc", 'd', RTGETOPT_REQ_STRING }, + { "--pause", 'p', RTGETOPT_REQ_NOTHING }, + { "--live", 'l', RTGETOPT_REQ_NOTHING }, + { "--uniquename", 'u', RTGETOPT_REQ_STRING } + }; + RTGETOPTSTATE GetOptState; + RTGetOptInit(&GetOptState, a->argc, a->argv, s_aTakeOptions, RT_ELEMENTS(s_aTakeOptions), + 3, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + int ch; + RTGETOPTUNION Value; + int vrc; + while ( SUCCEEDED(hrc) + && (ch = RTGetOpt(&GetOptState, &Value))) + { + switch (ch) + { + case 'p': + fPause = true; + break; + + case 'l': + fPause = false; + break; + + case 'd': + desc = Value.psz; + break; + + case 'u': + vrc = parseSnapshotUniqueFlags(Value.psz, &enmUnique); + if (RT_FAILURE(vrc)) + return errorArgument(Snapshot::tr("Invalid unique name description '%s'"), Value.psz); + break; + + default: + errorGetOpt(ch, &Value); + hrc = E_FAIL; + break; + } + } + if (FAILED(hrc)) + break; + + if (enmUnique & (SnapshotUniqueFlags_Number | SnapshotUniqueFlags_Timestamp)) + { + ComPtr pSnapshot; + hrc = sessionMachine->FindSnapshot(name.raw(), + pSnapshot.asOutParam()); + if (SUCCEEDED(hrc) || (enmUnique & SnapshotUniqueFlags_Force)) + { + /* there is a duplicate, need to create a unique name */ + uint32_t count = 0; + RTTIMESPEC now; + + if (enmUnique & SnapshotUniqueFlags_Number) + { + if (enmUnique & SnapshotUniqueFlags_Force) + count = 1; + else + count = 2; + RTTimeSpecSetNano(&now, 0); /* Shut up MSC */ + } + else + RTTimeNow(&now); + + while (count < 500) + { + Utf8Str suffix; + if (enmUnique & SnapshotUniqueFlags_Number) + suffix = Utf8StrFmt("%u", count); + else + { + RTTIMESPEC nowplus = now; + RTTimeSpecAddSeconds(&nowplus, count); + RTTIME stamp; + RTTimeExplode(&stamp, &nowplus); + suffix = Utf8StrFmt("%04u-%02u-%02uT%02u:%02u:%02uZ", stamp.i32Year, stamp.u8Month, stamp.u8MonthDay, stamp.u8Hour, stamp.u8Minute, stamp.u8Second); + } + Bstr tryName = name; + if (enmUnique & SnapshotUniqueFlags_Space) + tryName = BstrFmt("%ls %s", name.raw(), suffix.c_str()); + else + tryName = BstrFmt("%ls%s", name.raw(), suffix.c_str()); + count++; + hrc = sessionMachine->FindSnapshot(tryName.raw(), + pSnapshot.asOutParam()); + if (FAILED(hrc)) + { + name = tryName; + break; + } + } + if (SUCCEEDED(hrc)) + { + errorArgument(Snapshot::tr("Failed to generate a unique snapshot name")); + hrc = E_FAIL; + break; + } + } + hrc = S_OK; + } + + ComPtr progress; + Bstr snapId; + CHECK_ERROR_BREAK(sessionMachine, TakeSnapshot(name.raw(), desc.raw(), + fPause, snapId.asOutParam(), + progress.asOutParam())); + + hrc = showProgress(progress); + if (SUCCEEDED(hrc)) + RTPrintf(Snapshot::tr("Snapshot taken. UUID: %ls\n"), snapId.raw()); + else + CHECK_PROGRESS_ERROR(progress, (Snapshot::tr("Failed to take snapshot"))); + } + else if ( (fDelete = !strcmp(a->argv[1], "delete")) + || (fRestore = !strcmp(a->argv[1], "restore")) + || (fRestoreCurrent = !strcmp(a->argv[1], "restorecurrent")) + ) + { + setCurrentSubcommand(fDelete ? HELP_SCOPE_SNAPSHOT_DELETE + : fRestore ? HELP_SCOPE_SNAPSHOT_RESTORE + : HELP_SCOPE_SNAPSHOT_RESTORECURRENT); + + if (fRestoreCurrent) + { + if (a->argc > 2) + { + errorSyntax(Snapshot::tr("Too many arguments")); + hrc = E_FAIL; + break; + } + } + /* exactly one parameter: snapshot name */ + else if (a->argc != 3) + { + errorSyntax(Snapshot::tr("Expecting snapshot name only")); + hrc = E_FAIL; + break; + } + + ComPtr pSnapshot; + + if (fRestoreCurrent) + { + CHECK_ERROR_BREAK(sessionMachine, COMGETTER(CurrentSnapshot)(pSnapshot.asOutParam())); + if (pSnapshot.isNull()) + { + RTPrintf(Snapshot::tr("This machine does not have any snapshots\n")); + return RTEXITCODE_FAILURE; + } + } + else + { + // restore or delete snapshot: then resolve cmd line argument to snapshot instance + CHECK_ERROR_BREAK(sessionMachine, FindSnapshot(Bstr(a->argv[2]).raw(), + pSnapshot.asOutParam())); + } + + Bstr bstrSnapGuid; + CHECK_ERROR_BREAK(pSnapshot, COMGETTER(Id)(bstrSnapGuid.asOutParam())); + + Bstr bstrSnapName; + CHECK_ERROR_BREAK(pSnapshot, COMGETTER(Name)(bstrSnapName.asOutParam())); + + ComPtr pProgress; + + RTPrintf(Snapshot::tr("%s snapshot '%ls' (%ls)\n"), + fDelete ? Snapshot::tr("Deleting") : Snapshot::tr("Restoring"), bstrSnapName.raw(), bstrSnapGuid.raw()); + + if (fDelete) + { + CHECK_ERROR_BREAK(sessionMachine, DeleteSnapshot(bstrSnapGuid.raw(), + pProgress.asOutParam())); + } + else + { + // restore or restore current + CHECK_ERROR_BREAK(sessionMachine, RestoreSnapshot(pSnapshot, pProgress.asOutParam())); + } + + hrc = showProgress(pProgress); + CHECK_PROGRESS_ERROR(pProgress, (Snapshot::tr("Snapshot operation failed"))); + } + else if (!strcmp(a->argv[1], "edit")) + { + setCurrentSubcommand(HELP_SCOPE_SNAPSHOT_EDIT); + if (a->argc < 3) + { + errorSyntax(Snapshot::tr("Missing snapshot name")); + hrc = E_FAIL; + break; + } + + /* Parse the optional arguments, allowing more freedom than the + * synopsis explains. Can rename multiple snapshots and so on. */ + ComPtr pSnapshot; + static const RTGETOPTDEF s_aEditOptions[] = + { + { "--current", 'c', RTGETOPT_REQ_NOTHING }, + { "-current", 'c', RTGETOPT_REQ_NOTHING }, + { "--name", 'n', RTGETOPT_REQ_STRING }, + { "-name", 'n', RTGETOPT_REQ_STRING }, + { "-newname", 'n', RTGETOPT_REQ_STRING }, + { "--description", 'd', RTGETOPT_REQ_STRING }, + { "-description", 'd', RTGETOPT_REQ_STRING }, + { "-desc", 'd', RTGETOPT_REQ_STRING } + }; + RTGETOPTSTATE GetOptState; + RTGetOptInit(&GetOptState, a->argc, a->argv, s_aEditOptions, RT_ELEMENTS(s_aEditOptions), + 2, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + int ch; + RTGETOPTUNION Value; + while ( SUCCEEDED(hrc) + && (ch = RTGetOpt(&GetOptState, &Value))) + { + switch (ch) + { + case 'c': + CHECK_ERROR_BREAK(sessionMachine, COMGETTER(CurrentSnapshot)(pSnapshot.asOutParam())); + if (pSnapshot.isNull()) + { + RTPrintf(Snapshot::tr("This machine does not have any snapshots\n")); + return RTEXITCODE_FAILURE; + } + break; + + case 'n': + CHECK_ERROR_BREAK(pSnapshot, COMSETTER(Name)(Bstr(Value.psz).raw())); + break; + + case 'd': + CHECK_ERROR_BREAK(pSnapshot, COMSETTER(Description)(Bstr(Value.psz).raw())); + break; + + case VINF_GETOPT_NOT_OPTION: + CHECK_ERROR_BREAK(sessionMachine, FindSnapshot(Bstr(Value.psz).raw(), pSnapshot.asOutParam())); + break; + + default: + errorGetOpt(ch, &Value); + hrc = E_FAIL; + break; + } + } + + if (FAILED(hrc)) + break; + } + else if (!strcmp(a->argv[1], "showvminfo")) + { + setCurrentSubcommand(HELP_SCOPE_SNAPSHOT_SHOWVMINFO); + + /* exactly one parameter: snapshot name */ + if (a->argc != 3) + { + errorSyntax(Snapshot::tr("Expecting snapshot name only")); + hrc = E_FAIL; + break; + } + + ComPtr pSnapshot; + + CHECK_ERROR_BREAK(sessionMachine, FindSnapshot(Bstr(a->argv[2]).raw(), + pSnapshot.asOutParam())); + + /* get the machine of the given snapshot */ + ComPtr pMachine2; + pSnapshot->COMGETTER(Machine)(pMachine2.asOutParam()); + showVMInfo(a->virtualBox, pMachine2, NULL, VMINFO_NONE); + } + else if (!strcmp(a->argv[1], "list")) + { + setCurrentSubcommand(HELP_SCOPE_SNAPSHOT_LIST); + hrc = handleSnapshotList(a, sessionMachine) == RTEXITCODE_SUCCESS ? S_OK : E_FAIL; + } + else if (!strcmp(a->argv[1], "dump")) // undocumented parameter to debug snapshot info + DumpSnapshot(sessionMachine); + else + { + errorSyntax(Snapshot::tr("Invalid parameter '%s'"), a->argv[1]); + hrc = E_FAIL; + } + } while (0); + + a->session->UnlockMachine(); + + return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} -- cgit v1.2.3