SnapshotImpl.cpp revision dd823bfb6b626445398cc207eeb5cad0abf767cf
2N/A * COM class implementation for Snapshot and SnapshotMachine in VBoxSVC. 2N/A * Copyright (C) 2006-2014 Oracle Corporation 2N/A * This file is part of VirtualBox Open Source Edition (OSE), as 2N/A * you can redistribute it and/or modify it under the terms of the GNU 2N/A * General Public License (GPL) as published by the Free Software 2N/A * Foundation, in version 2 as it comes in the "COPYING" file of the 2N/A * VirtualBox OSE distribution. VirtualBox OSE is distributed in the 2N/A * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. 2N/A// @todo these three includes are required for about one or two lines, try 2N/A// to remove them and put that code in shared code in MachineImplcpp 2N/A//////////////////////////////////////////////////////////////////////////////// 2N/A// Snapshot private data definition 2N/A//////////////////////////////////////////////////////////////////////////////// 2N/A /** weak VirtualBox parent */ 2N/A // pParent and llChildren are protected by the machine lock 2N/A//////////////////////////////////////////////////////////////////////////////// 2N/A// Constructor / destructor 2N/A//////////////////////////////////////////////////////////////////////////////// * Initializes the instance * @param aId id of the snapshot * @param aName name of the snapshot * @param aDescription name of the snapshot (NULL if no description) * @param aTimeStamp timestamp of the snapshot, in ms since 1970-01-01 UTC * @param aMachine machine associated with this snapshot * @param aParent parent snapshot (NULL if no parent) /* Enclose the state transition NotReady->InInit->Ready */ /* share parent weakly */ /* Confirm a successful initialization when it's the case */ * Uninitializes the instance and sets the ready flag to FALSE. * Called either from FinalRelease(), by the parent when it gets destroyed, * or by a third party when it decides this object is no more valid. * Since this manipulates the snapshots tree, the caller must hold the * machine lock in write mode (which protects the snapshots tree)! /* Enclose the state transition Ready->InUninit->NotReady */ m->
llChildren.
clear();
// this unsets all the ComPtrs and probably calls delete * Delete the current snapshot by removing it from the tree of snapshots * and reparenting its children. * After this, the caller must call uninit() on the snapshot. We can't call * that from here because if we do, the AutoUninitSpan waits forever for * the number of callers to become 0 (it is 1 because of the AutoCaller in here). * NOTE: this does NOT lock the snapshot, it is assumed that the machine state * (and the snapshots tree) is protected by the caller having requested the machine * lock in write mode AND the machine state must be DeletingSnapshot. // caller must have acquired the machine's write lock // the snapshot must have only one child when being deleted or no children at all // when we introduce clones later, deleting the snapshot will affect // the current and first snapshots of clones, if they are direct children // of this snapshot. So we will need to lock machines associated with // child snapshots as well and update mCurrentSnapshot and/or // mFirstSnapshot fields. /* we've changed the base of the current state so mark it as * modified as it no longer guaranteed to be its copy */ // no need to lock, snapshots tree is protected by machine lock // clear our own children list (since we reparented the children) * Internal helper that removes "this" from the list of children of its * parent. Used in uninit() and other places when reparenting is necessary. * The caller must hold the machine lock in write mode (which protects the snapshots tree)! //////////////////////////////////////////////////////////////////////////////// // ISnapshot public methods //////////////////////////////////////////////////////////////////////////////// * @note Locks this object for writing, then calls Machine::onSnapshotChange() * (see its lock requirements). // prohibit setting a UUID only as the machine name, or else it can // never be found by findMachine() alock.
release();
/* Important! (child->parent locks are forbidden) */ alock.
release();
/* Important! (child->parent locks are forbidden) */ // snapshots tree is protected by machine lock //////////////////////////////////////////////////////////////////////////////// // Snapshot public internal methods //////////////////////////////////////////////////////////////////////////////// * Returns the parent snapshot or NULL if there's none. Must have caller + locking! * Returns the first child snapshot or NULL if there's none. Must have caller + locking! * Must be called from under the object's lock! * Returns the depth in the snapshot tree for this snapshot. * @note takes the snapshot tree lock // snapshots tree is protected by machine lock * Returns the number of direct child snapshots, without grandchildren. // snapshots tree is protected by machine lock * Implementation method for getAllChildrenCount() so we request the * tree lock only once before recursing. Don't call directly. * Returns the number of child snapshots including all grandchildren. * Recurses into the snapshots tree. // snapshots tree is protected by machine lock * Returns the SnapshotMachine that this snapshot belongs to. * Caller must hold the snapshot's object lock! * Returns the UUID of this snapshot. * Caller must hold the snapshot's object lock! * Returns the name of this snapshot. * Caller must hold the snapshot's object lock! * Returns the time stamp of this snapshot. * Caller must hold the snapshot's object lock! * Searches for a snapshot with the given ID among children, grand-children, * etc. of this snapshot. This snapshot itself is also included in the search. * Caller must hold the machine lock (which protects the snapshots tree!) // no need to lock, uuid is const * Searches for a first snapshot with the given name among children, * grand-children, etc. of this snapshot. This snapshot itself is also included * Caller must hold the machine lock (which protects the snapshots tree!) * Internal implementation for Snapshot::updateSavedStatePaths (below). /* state file may be NULL (for offline snapshots) */ * Returns true if this snapshot or one of its children uses the given file, * whose path must be fully qualified, as its saved state. When invoked on a * machine's first snapshot, this can be used to check if a saved state file * is shared with any snapshots. * Caller must hold the machine lock, which protects the snapshots tree. * @param pSnapshotToIgnore If != NULL, this snapshot is ignored during the checks. return true;
// no need to recurse then // but otherwise we must check children * Checks if the specified path change affects the saved state file path of * this snapshot or any of its (grand-)children and updates it accordingly. * Intended to be called by Machine::openConfigLoader() only. * @param aOldPath old path (full) * @param aNewPath new path (full) * @note Locks the machine (for the snapshots tree) + this object + children for writing. // snapshots tree is protected by machine lock // call the implementation under the tree lock * Internal implementation for Snapshot::saveSnapshot (below). Caller has * requested the snapshots tree (machine) lock. // state file (only if this snapshot is online) // Use the heap to reduce the stack footprint. Each recursion needs // over 1K, and there can be VMs with deeply nested snapshots. The // stack can be quite small, especially with XPCOM. * Saves the given snapshot and all its children (unless \a aAttrsOnly is true). * It is assumed that the given node is empty (unless \a aAttrsOnly is true). * @param aNode <Snapshot> node to save the snapshot to. * @param aSnapshot Snapshot to save. * @param aAttrsOnly If true, only update user-changeable attrs. // snapshots tree is protected by machine lock * Part of the cleanup engine of Machine::Unregister(). * This recursively removes all medium attachments from the snapshot's machine * and returns the snapshot's saved state file name, if any, and then calls * uninit() on "this" itself. * This recurses into children first, so the given MediaList receives child * media first before their parents. If the caller wants to close all media, * they should go thru the list from the beginning to the end because media * cannot be closed if they have children. * This calls uninit() on itself, so the snapshots tree (beginning with a machine's pFirstSnapshot) becomes invalid after this. * It does not alter the main machine's snapshot pointers (pFirstSnapshot, pCurrentSnapshot). * Caller must hold the machine write lock (which protects the snapshots tree!) * @param writeLock Machine write lock, which can get released temporarily here. * @param cleanupMode Cleanup mode; see Machine::detachAllMedia(). * @param llMedia List of media returned to caller, depending on cleanupMode. // make a copy of the Guid for logging before we uninit ourselves // recurse into children first so that the child media appear on // the list first; this way caller can close the media from the // beginning to the end because parent media can't be closed if // make a copy of the children list since uninit() modifies it // now call detachAllMedia on the snapshot machine // report the saved state file if it's not on the list yet //////////////////////////////////////////////////////////////////////////////// // SnapshotMachine implementation //////////////////////////////////////////////////////////////////////////////// * Initializes the SnapshotMachine object when taking a snapshot. * @param aSessionMachine machine to take a snapshot from * @param aSnapshotId snapshot ID of this snapshot machine * @param aStateFilePath file where the execution state will be later saved * (or NULL for the offline snapshot) * @note The aSessionMachine must be locked for writing. /* Enclose the state transition NotReady->InInit->Ready */ /* memorize the primary Machine instance (i.e. not SessionMachine!) */ /* share the parent pointer */ /* take the pointer to Data to share */ /* take the pointer to UserData to share (our UserData must always be the * same as Machine's data) */ /* make a private copy of all other data (recent changes from SessionMachine) */ /* SSData is always unique for SnapshotMachine */ /* create copies of all shared folders (mHWData after attaching a copy * contains just references to original objects) */ /* associate hard disks with the snapshot * (Machine::uninitDataAndChildObjects() will deassociate at destruction) */ if (
pMedium)
// can be NULL for non-harddisk /* create copies of all storage controllers (mStorageControllerData * after attaching a copy contains just references to original objects) */ /* create all other child objects that will be immutable private copies */ /* create copies of all USB controllers (mUSBControllerData * after attaching a copy contains just references to original objects) */ /* Confirm a successful initialization when it's the case */ * Initializes the SnapshotMachine object when loading from the settings file. * @param aMachine machine the snapshot belongs to * @param aHWNode <Hardware> node * @param aHDAsNode <HardDiskAttachments> node * @param aSnapshotId snapshot ID of this snapshot machine * @param aStateFilePath file where the execution state is saved * (or NULL for the offline snapshot) * @note Doesn't lock anything. /* Enclose the state transition NotReady->InInit->Ready */ /* Don't need to lock aMachine when VirtualBox is starting up */ /* memorize the primary Machine instance (i.e. not SessionMachine!) */ /* share the parent pointer */ /* take the pointer to Data to share */ * take the pointer to UserData to share * (our UserData must always be the same as Machine's data) /* allocate private copies of all other data (will be loaded from settings) */ /* SSData is always unique for SnapshotMachine */ /* create all other child objects that will be immutable private copies */ /* load hardware and harddisk settings */ NULL,
/* puuidRegistry */ /* commit all changes made during the initialization */ i_commit();
/// @todo r=dj why do we need a commit in init?!? this is very expensive /// @todo r=klaus for some reason the settings loading logic backs up // the settings, and therefore a commit is needed. Should probably be changed. /* Confirm a successful initialization when it's the case */ * Uninitializes this SnapshotMachine object. /* Enclose the state transition Ready->InUninit->NotReady */ /* free the essential data structure last */ * Overrides VirtualBoxBase::lockHandle() in order to share the lock handle * with the primary Machine instance (mMachine) if it exists. //////////////////////////////////////////////////////////////////////////////// // SnapshotMachine public internal methods //////////////////////////////////////////////////////////////////////////////// * Called by the snapshot object associated with this SnapshotMachine when * snapshot data such as name or description is changed. * @warning Caller must hold no locks when calling this. /* Flag the machine as dirty or change won't get saved. We disable the * modification of the current state flag, cause this snapshot data isn't * related to the current state. */ SaveS_Force);
// we know we need saving, no need to check // save the global settings //////////////////////////////////////////////////////////////////////////////// // SessionMachine task records //////////////////////////////////////////////////////////////////////////////// * Abstract base class for SessionMachine::RestoreSnapshotTask and * SessionMachine::DeleteSnapshotTask. This is necessary since * RTThreadCreate cannot call a method as its thread function, so * instead we have it call the static SessionMachine::taskHandler, * which can then call the handler() method in here (implemented /** Restore snapshot state task */ /** Delete snapshot task */ * Static SessionMachine method that can get passed to RTThreadCreate to * have a thread started for a SnapshotTask. See SnapshotTask above. * This calls either RestoreSnapshotTask::handler() or DeleteSnapshotTask::handler(). // it's our responsibility to delete the task //////////////////////////////////////////////////////////////////////////////// // TakeSnapshot methods (SessionMachine and related tasks) //////////////////////////////////////////////////////////////////////////////// * Implementation for IInternalMachineControl::beginTakingSnapshot(). * Gets called indirectly from Console::TakeSnapshot, which creates a * progress object in the client and then starts a thread * (Console::fntTakeSnapshotWorker) which then calls this. * In other words, the asynchronous work for taking snapshots takes place * on the _client_ (in the Console). This is different from restoring * or deleting snapshots, which start threads on the server. * This does the server-side work of taking a snapshot: it creates differencing * images for all hard disks attached to the machine and then creates a * Snapshot object with a corresponding SnapshotMachine to save the VM settings. * The client's fntTakeSnapshotWorker() blocks while this takes place. * After this returns successfully, fntTakeSnapshotWorker() will begin * saving the machine state to the snapshot object and reconfigure the * When the console is done, it calls SessionMachine::EndTakingSnapshot(). * @note Locks mParent + this object for writing. * @param aInitiator in: The console on which Console::TakeSnapshot was called. * @param aName in: The name for the new snapshot. * @param aDescription in: A description for the new snapshot. * @param aConsoleProgress in: The console's (client's) progress object. * @param fTakingSnapshotOnline in: True if an online snapshot is being taken (i.e. machine is running). * @param aStateFilePath out: name of file in snapshots folder to which the console should write the VM state. tr(
"Cannot take another snapshot for machine '%s', because it exceeds the maximum snapshot depth limit. Please delete some earlier snapshot which you no longer need"),
/* save all current settings to ensure current changes are committed and * hard disks are fixed up */ // we can't have a machine XML rename pending at this point /* create an ID for the snapshot */ /* stateFilePath is null when the machine is not online nor saved */ // creating a new online snapshot: we need a fresh saved state file // taking an online snapshot from machine in "saved" state: then use existing state file // ensure the directory for the saved state file exists /* create a snapshot machine object */ /* create a snapshot object */ /* fill in the snapshot data */ /// @todo in the long run the progress object should be moved to // VBoxSVC to avoid trouble with monitoring the progress object state // when the process where it lives is terminating shortly after the // backup the media data so we can recover if things goes wrong along the day; // the matching commit() is in fixupMedia() during endSnapshot() /* Console::fntTakeSnapshotWorker and friends expects this. */ /* create new differencing hard disks and attach them to this machine */ 1,
// operation weight; must be the same as in Console::TakeSnapshot() // MUST NOT save the settings or the media registry here, because // this causes trouble with rolling back settings if the user cancels // taking the snapshot after the diff images have been created. // @todo r=dj what with the implicit diff that we created above? this is never cleaned up * Implementation for IInternalMachineControl::endTakingSnapshot(). * Called by the Console when it's done saving the VM state into the snapshot * (if online) and reconfiguring the hard disks. See BeginTakingSnapshot() above. * This also gets called if the console part of snapshotting failed after the * BeginTakingSnapshot() call, to clean up the server side. * @note Locks VirtualBox and this object for writing. * @param aSuccess Whether Console was successful with the client-side snapshot things. * Restore the state we had when BeginTakingSnapshot() was called, * Console::fntTakeSnapshotWorker restores its local copy when we return. * If the state was Running, then let Console::fntTakeSnapshotWorker do it // new snapshot becomes the current one /* memorize the first snapshot if necessary */ // snapshots change, so we know we need to save /* the machine was powered off or saved when taking a snapshot, so * reset the mCurrentStateModified flag */ /* delete all differencing hard disks created (this will also attach * their parents back by rolling back mMediaData) */ // delete the saved state file (it might have been already created) // no need to test for whether the saved state file is shared: an online // snapshot means that a new saved state file was created, which we must /* clear out the snapshot data */ /* machineLock has been released already */ //////////////////////////////////////////////////////////////////////////////// // RestoreSnapshot methods (SessionMachine and related tasks) //////////////////////////////////////////////////////////////////////////////// * Implementation for IInternalMachineControl::restoreSnapshot(). * Gets called from Console::RestoreSnapshot(), and that's basically the * only thing Console does. Restoring a snapshot happens entirely on the * server side since the machine cannot be running. * This creates a new thread that does the work and returns a progress * object to the client which is then returned to the caller of * Console::RestoreSnapshot(). * Actual work then takes place in RestoreSnapshotTask::handler(). * @note Locks this + children objects for writing! * @param aInitiator in: rhe console on which Console::RestoreSnapshot was called. * @param aSnapshot in: the snapshot to restore. * @param aMachineState in: client-side machine state. * @param aProgress out: progress object to monitor restore thread. // machine must not be running // create a progress object. The number of operations is: // 1 (preparing) + # of hard disks + 1 (if we need to copy the saved state file) */ LogFlowThisFunc((
"Going thru snapshot machine attachments to determine progress setup\n"));
++
ulTotalWeight;
// assume one MB weight for each differencing hard disk to manage Bstr(
tr(
"Restoring machine settings")).
raw(),
/* create and start the task on a separate thread (note that it will not * start working until we release alock) */ /* set the proper machine state (note: after creating a Task instance) */ /* return the progress to the caller */ /* return the new state to the caller */ * Worker method for the restore snapshot thread created by SessionMachine::RestoreSnapshot(). * This method gets called indirectly through SessionMachine::taskHandler() which then * calls RestoreSnapshotTask::handler(). * The RestoreSnapshotTask contains the progress object returned to the console by * SessionMachine::RestoreSnapshot, through which progress and results are reported. * @note Locks mParent + this object for writing. * @param aTask Task data. /* we might have been uninitialized because the session was accidentally * closed by the client, so don't assert */ tr(
"The session has been accidentally closed"));
/* Discard all current changes to mUserData (name, OSType etc.). * Note that the machine is powered off, so there is no need to inform /* Delete the saved state file if the machine was Saved prior to this // release the saved state file AFTER unsetting the member variable // so that releaseSavedStateFile() won't think it's still in use /* remember the timestamp of the snapshot we're restoring from */ /* copy all hardware data from the snapshot */ // restore the attachments from the snapshot /* release the locks before the potentially lengthy operation */ /* Note: on success, current (old) hard disks will be * the end. On failure, newly created implicit diffs will be * deleted by #rollback() at the end. */ /* should not have a saved state file associated at this point */ // online snapshot: then share the state file /* make the snapshot we restored from the current snapshot */ /* grab differencing hard disks from the old attachments that will * become unused and need to be auto-deleted */ /* while the hard disk is attached, the number of children or the * parent cannot change, so no lock */ /* we have already deleted the current state, so set the execution * state accordingly no matter of the delete snapshot result */ /* Paranoia: no one must have saved the settings in the mean time. If * it happens nevertheless we'll close our eyes and continue below. */ /* assign the timestamp from the snapshot */ // detach the current-state diffs that we detected above and build a list of // image files to delete _after_ saveSettings() // Normally we "detach" the medium by removing the attachment object // from the current machine data; saveSettings() below would then // compare the current machine data with the one in the backup // and actually call Medium::removeBackReference(). But that works only half // the time in our case so instead we force a detachment here: // remove from machine data // Remove it from the backup or else saveSettings will try to detach // it again and assert. The paranoia check avoids crashes (see // assert above) if this code is buggy and saves settings in the // then clean up backrefs // save machine settings, reset the modified flag and commit; // unconditionally add the parent registry. We do similar in SessionMachine::EndTakingSnapshot // (mParent->saveSettings()) // release the locks before updating registry and deleting image files // from here on we cannot roll back on failure any more // ignore errors here because we cannot roll back after saveSettings() above /* preserve existing error info */ /* undo all changes on failure */ /* restore the machine state */ /* set the result (this will try to fetch current error info on failure) */ //////////////////////////////////////////////////////////////////////////////// // DeleteSnapshot methods (SessionMachine and related tasks) //////////////////////////////////////////////////////////////////////////////// * Implementation for IInternalMachineControl::deleteSnapshot(). * Gets called from Console::DeleteSnapshot(), and that's basically the * only thing Console does initially. Deleting a snapshot happens entirely on * the server side if the machine is not running, and if it is running then * the individual merges are done via internal session callbacks. * This creates a new thread that does the work and returns a progress * object to the client which is then returned to the caller of * Console::DeleteSnapshot(). * Actual work then takes place in DeleteSnapshotTask::handler(). * @note Locks mParent + this + children objects for writing! /** @todo implement the "and all children" and "range" variants */ // be very picky about machine states tr(
"Invalid machine state: %s"),
tr(
"Snapshot '%s' of the machine '%s' cannot be deleted, because it has %d child snapshots, which is more than the one snapshot allowed for deletion"),
tr(
"Snapshot '%s' of the machine '%s' cannot be deleted, because it is the current snapshot and has one child snapshot"),
/* If the snapshot being deleted is the current one, ensure current * settings are committed and saved. // we can't have a machine XML rename pending at this point /* create a progress object. The number of operations is: * 1 (preparing) + 1 if the snapshot is online + # of normal hard disks LogFlowThisFunc((
"Going thru snapshot machine attachments to determine progress setup\n"));
// count normal hard disks and add their sizes to the weight // writethrough and shareable images are unaffected by snapshots, // so do nothing for them // normal or immutable media need attention /* create and start the task on a separate thread */ // the task might start running but will block on acquiring the machine's write lock // which we acquired above; once this function leaves, the task will be unblocked; // set the proper machine state here now (note: after creating a Task instance) /* return the progress to the caller */ /* return the new state to the caller */ * Helper struct for SessionMachine::deleteSnapshotHandler(). /** optional lock token, used only in case mpHD is not merged/deleted */ /* these are for reattaching the hard disk in case of a failure: */ * Worker method for the delete snapshot thread created by * SessionMachine::DeleteSnapshot(). This method gets called indirectly * through SessionMachine::taskHandler() which then calls * DeleteSnapshotTask::handler(). * The DeleteSnapshotTask contains the progress object returned to the console * by SessionMachine::DeleteSnapshot, through which progress and results are * SessionMachine::DeleteSnapshot() has set the machine state to * MachineState_DeletingSnapshot right after creating this task. Since we block * on the machine write lock at the beginning, once that has been acquired, we * can assume that the machine state is indeed that. * @note Locks the machine + the snapshot + the media tree for writing! * @param aTask Task data. /* we might have been uninitialized because the session was accidentally * closed by the client, so don't assert */ tr(
"The session has been accidentally closed"));
// once we have this lock, we know that SessionMachine::DeleteSnapshot() // has exited after setting the machine state to MachineState_DeletingSnapshot // no need to lock the snapshot machine since it is const by definition // save the snapshot ID (for callbacks) // Go thru the attachments of the snapshot machine (the media in here // point to the disk states _before_ the snapshot was taken, i.e. the // state we're restoring to; for each such medium, we will need to // merge it with its one and only child (the diff image holding the // changes written after the snapshot was taken). // writethrough, shareable and readonly images are // unaffected by snapshots, skip them // needs to be merged with child or deleted, check prerequisites // Look up the corresponding medium attachment in the currently // running VM. Any failure prevents a live merge. Could be made // a tad smarter by trying a few candidates, so that e.g. disks // which are simply moved to a different controller slot do not // prevent online merging in general. // no need to hold the lock any longer // For simplicity, prepareDeleteSnapshotMedium selects the merge // direction in the following way: we merge pHD onto its child // (forward merge), not the other way round, because that saves us // from unnecessarily shuffling around the attachments for the // machine that follows the snapshot (next snapshot or current // state), unless it's a base image. Backwards merges of the first // snapshot into the base image is essential, as it ensures that // when all snapshots are deleted the only remaining image is a // base image. Important e.g. for medium formats which do not have // a file representation such as iSCSI. // a couple paranoia checks for backward merges // parent is null -> this disk is a base hard disk: we will // then do a backward merge, i.e. merge its only child onto the // base disk. Here we need then to update the attachment that // refers to the child and have it point to the parent instead // minimal sanity checking // Adjust the backreferences, otherwise merging will assert. // Note that the medium attachment object stays associated // with the snapshot until the merge was successful. /*check available place on the storage*/ tr(
" Unable to merge storage '%s'. Can't get storage UID "),
/* store needed free space in multimap */ /* linking storage UID with snapshot path, it is a helper container (just for easy finding needed path) */ /* find all records in multimap with identical storage UID*/ /* find appropriate path by storage UID*/ /* get info about a storage */ tr(
" Unable to merge storage '%s'. Path to the storage wasn't found. "),
tr(
" Unable to merge storage '%s'. Can't get the storage size. "),
tr(
" Unable to merge storage '%s' - not enough free storage space. "),
// we can release the locks now since the machine state is MachineState_DeletingSnapshot /* Now we checked that we can successfully merge all normal hard disks * (unless a runtime error like end-of-disc happens). Now get rid of * the saved state (if present), as that will free some disk space. * The snapshot itself will be deleted as late as possible, so that * the user can repeat the delete operation if he runs out of disk * space or cancels the delete operation. */ // saveAllSnapshots() needs a machine lock, and the snapshots // tree is protected by the machine lock as well // machine will need saving now /// @todo NEWMEDIA turn the following errors into warnings because the /// snapshot itself has been already deleted (and interpret these /// warnings properly on the GUI side) /* no real merge needed, just updating state and delete * diff files if necessary */ /* Delete the differencing hard disk (has no children). Two * exceptions: if it's the last medium in the chain or if it's * a backward merge we don't want to handle due to complexity. * In both cases leave the image in place. If it's the first * exception the user can delete it later if he wants. */ /* No need to hold the lock any longer. */ // need to uninit the deleted medium // Put the medium merge information (MediumDeleteRec) where // SessionMachine::FinishOnlineMergeMedium can get at it. // This callback will arrive while onlineMergeMedium is // still executing, and there can't be two tasks. // online medium merge, in the direction decided earlier // normal medium merge, in the direction decided earlier // If the merge failed, we need to do our best to have a usable // VM configuration afterwards. The return code doesn't tell // whether the merge completed and so we have to check if the // source medium (diff images are always file based at the // moment) is still there or not. Be careful not to lose the // error code below, before the "Delayed failure exit". // Diff medium not backed by a file - cannot get status so // Source medium is still there, so merge failed early. // Source medium is gone. Assume the merge succeeded and // thus it's safe to remove the attachment. We use the // "Delayed failure exit" below. // need to change the medium attachment for backward merges // need to uninit the medium deleted by the merge // delete the no longer needed medium lock list, which // implicitly handled the unlocking // remove the medium attachment from the snapshot. For a backwards // merge the target attachment needs to be removed from the // snapshot, as the VM will take it over. For forward merges the // source medium attachment needs to be removed. // Search for old source attachment and replace with target. // There can be only one child snapshot in this case. // If no attachment is found do not change anything. Maybe // the source medium was not attached to the snapshot. // If this is an online deletion the attachment was updated // already to allow the VM continue execution immediately. // Needs a bit of special treatment due to this difference. // One attachment is merged, must save the settings // prevent calling cancelDeleteSnapshotMedium() for this attachment // Delayed failure exit when the merge cleanup failed but the // merge actually succeeded. // beginSnapshotDelete() needs the machine lock, and the snapshots // tree is protected by the machine lock as well // preserve existing error info so that the result can // be properly reported to the progress object below // un-prepare the remaining hard disks // whether we were successful or not, we need to set the machine // state and save the machine settings; // preserve existing error info so that the result can // be properly reported to the progress object below // restore the machine state that was saved when the // report the result (this will try to fetch current error info on failure) * Checks that this hard disk (part of a snapshot) may be deleted/merged and * performs necessary state changes. Must not be called for writethrough disks * This method is to be called prior to calling #deleteSnapshotMedium(). * If #deleteSnapshotMedium() is not called or fails, the state modifications * performed by this method must be undone by #cancelDeleteSnapshotMedium(). * @return COM status code * @param aHD Hard disk which is connected to the snapshot. * @param aMachineId UUID of machine this hard disk is attached to. * @param aSnapshotId UUID of snapshot this hard disk is attached to. May * be a zero UUID if no snapshot is applicable. * @param fOnlineMergePossible Flag whether an online merge is possible. * @param aVMMALockList Medium lock list for the medium attachment of this VM. * Only used if @a fOnlineMergePossible is @c true, and * must be non-NULL in this case. * @param aSource Source hard disk for merge (out). * @param aTarget Target hard disk for merge (out). * @param aMergeForward Merge direction decision (out). * @param aParentForTarget New parent if target needs to be reparented (out). * @param aChildrenToReparent MediumLockList with children which have to be * reparented to the target (out). * @param fNeedsOnlineMerge Whether this merge needs to be done online (out). * If this is set to @a true then the @a aVMMALockList * parameter has been modified and is returned as * @param aMediumLockList Where to store the created medium lock list (may * return NULL if no real merge is necessary). * @param aHDLockToken Where to store the write lock token for aHD, in case * it is not merged or deleted (out). * @note Caller must hold media tree lock for writing. This locks this object * and every medium object on the merge chain for writing. /* This technically is no merge, set those values nevertheless. * Helps with updating the medium attachments. */ /* special treatment of the last hard disk in the chain: */ /* lock only, to prevent any usage until the snapshot deletion /* the differencing hard disk w/o children will be deleted, protect it * from attaching to other VMs (this is why Deleting) */ /* not going multi-merge as it's too expensive */ tr(
"Hard disk '%s' has more than one child hard disk (%d)"),
/* the rest is a normal merge setup */ /* base hard disk, backward merge */ /* backward merge is too tricky, we'll just detach on snapshot * deletion, so lock only, to prevent any usage */ /* Determine best merge direction. */ /* Try to lock the newly constructed medium lock list. If it succeeds * this can be handled as an offline merge, i.e. without the need of * asking the VM to do the merging. Only continue with the online * merging preparation if applicable. */ /* Locking failed, this cannot be done as an offline merge. Try to * combine the locking information into the lock list of the medium * attachment in the running VM. If that fails or locking the * resulting lock list fails then the merge cannot be done online. * It can be repeated by the user when the VM is shut down. */ // could not update the lock, trigger cleanup below /* we will lock the children of the source for reparenting */ /* Cannot just call aChildrenToReparent->Lock(), as one of * the children is the one under which the current state of * the VM is located, and this means it is already locked * (for reading). Note that no special unlocking is needed, * because cancelMergeTo will unlock everything locked in * its context (using the unlock on destruction), and both * cancelDeleteSnapshotMedium (in case something fails) and * FinishOnlineMergeMedium re-define the read/write lock * state of everything which the VM need, search for the * UpdateLock method calls. */ tr(
"Cannot lock hard disk '%s' for a live merge"),
tr(
"Failed to construct lock list for a live merge of hard disk '%s'"),
// fix the VM's lock list if anything failed // blindly apply this, only needed for medium objects which // would be deleted as part of the merge tr(
"Cannot lock hard disk '%s' for an offline merge"),
* Cancels the deletion/merging of this hard disk (part of a snapshot). Undoes * what #prepareDeleteSnapshotMedium() did. Must be called if * #deleteSnapshotMedium() is not called or fails. * @param aHD Hard disk which is connected to the snapshot. * @param aSource Source hard disk for merge. * @param aChildrenToReparent Children to unlock. * @param fNeedsOnlineMerge Whether this merge needs to be done online. * @param aMediumLockList Medium locks to cancel. * @param aHDLockToken Optional write lock token for aHD. * @param aMachineId Machine id to attach the medium to. * @param aSnapshotId Snapshot id to attach the medium to. * @note Locks the medium tree and the hard disks in the chain for writing. // Online merge uses the medium lock list of the VM, so give // an empty list to cancelMergeTo so that it works as designed. // clean up the VM medium lock list ourselves // blindly apply this, only needed for medium objects which // would be deleted as part of the merge // reattach the source media to the snapshot * Perform an online merge of a hard disk, i.e. the equivalent of * Medium::mergeTo(), just for running VMs. If this fails you need to call * #cancelDeleteSnapshotMedium(). * @return COM status code * @param aMediumAttachment Identify where the disk is attached in the VM. * @param aSource Source hard disk for merge. * @param aTarget Target hard disk for merge. * @param aMergeForward Merge direction. * @param aParentForTarget New parent if target needs to be reparented. * @param aChildrenToReparent Medium lock list with children which have to be * reparented to the target. * @param aMediumLockList Where to store the created medium lock list (may * return NULL if no real merge is necessary). * @param aProgress Progress indicator. * @param pfNeedsMachineSaveSettings Whether the VM settings need to be saved (out). // Similar code appears in Medium::taskMergeHandle, so // if you make any changes below check whether they are applicable // in that context as well. /* Sanity check all hard disks in the chain. */ // In Medium::taskMergeHandler there is lots of consistency // checking which we cannot do here, as the state details are // impossible to get outside the Medium class. The locking should // have done the checks already. tr(
"Machine is not locked by a session (session state: %s)"),
// Must not hold any locks here, as this will call back to finish // updating the medium attachment, chain linking and state. // The callback mentioned above takes care of update the medium state * Implementation for IInternalMachineControl::finishOnlineMergeMedium(). * Gets called after the successful completion of an online merge from * Console::onlineMergeMedium(), which gets invoked indirectly above in * the call to IInternalSessionControl::onlineMergeMedium. * This updates the medium information and medium state so that the VM * can continue with the updated state of the medium chain. // all hard disks but the target were successfully deleted by // the merge; reparent target if necessary and uninitialize media // Declare this here to make sure the object does not get uninitialized // before this method completes. Would normally happen as halfway through // we delete the last reference to the no longer existing medium object. // first, unregister the target since it may become a base // hard disk which needs re-registration // then, reparent it and disconnect the deleted branch at // both ends (chain->parent() is source's parent) // disconnect the deleted branch at the elder end // Update parent UUIDs of the source's children, reparent them and // disconnect the deleted branch at the younger end // Fix the parent UUID of the images which needs to be moved to // underneath target. The running machine has the images opened, // but only for reading since the VM is paused. If anything fails // we must continue. The worst possible result is that the images // need manual fixing via VBoxManage to adjust the parent UUID. // The childen are still write locked, unlock them now and don't // rely on the destructor doing it very late. // obey {parent,child} lock order /* unregister and uninitialize all hard disks removed by the merge */ /* Create a real copy of the medium pointer, as the medium * lock deletion below would invalidate the referenced object. */ /* The target and all images not merged (readonly) are skipped */ /* now, uninitialize the deleted hard disk (note that * due to the Deleting state, uninit() will not touch * the parent-child relationship so we need to * uninitialize each disk individually) */ /* note that the operation initiator hard disk (which is * normally also the source hard disk) is a special case * -- there is one more caller added by Task to it which * we must release. Also, if we are in sync mode, the * caller may still hold an AutoCaller instance for it * and therefore we cannot uninit() it (it's therefore * the caller's responsibility) */ /* Delete the medium lock list entry, which also releases the * caller added by MergeChain before uninit() and updates the * iterator to point to the right place. */ /* Stop as soon as we reached the last medium affected by the merge. * The remaining images must be kept unchanged. */ /* Could be in principle folded into the previous loop, but let's keep * things simple. Update the medium locking to be the standard state: * all parent images locked for reading, just the last diff for writing. */ /* If this is a backwards merge of the only remaining snapshot (i.e. the * source has no children) then update the medium associated with the * attachment, as the previously associated one (source) is now deleted. * Without the immediate update the VM could not continue running. */