MachineImpl.cpp revision 66e325646531048327d57d0292dc291e04c9b943
* This file is part of VirtualBox Open Source Edition (OSE), as * you can redistribute it and/or modify it under the terms of the GNU * General Public License (GPL) as published by the Free Software * Foundation, in version 2 as it comes in the "COPYING" file of the * VirtualBox OSE distribution. VirtualBox OSE is distributed in the * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * additional information or have any questions. #
else /* !RT_OS_WINDOWS */#
endif /* !RT_OS_WINDOWS *////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// * @note The template is NOT completely valid according to VBOX_XML_SCHEMA * (when loading a newly created settings file, validation will be turned off) * Progress callback handler for lengthy operations * (corresponds to the FNRTPROGRESS typedef). * @param uPercentage Completetion precentage (0-100). * @param pvUser Pointer to the Progress instance. /* update the progress object */ ///////////////////////////////////////////////////////////////////////////// // Machine::Data structure ///////////////////////////////////////////////////////////////////////////// /* mUuid is initialized in Machine::init() */ ///////////////////////////////////////////////////////////////////////////// // Machine::UserData structure ///////////////////////////////////////////////////////////////////////////// /* default values for a newly created machine */ /* mName, mOSTypeId, mSnapshotFolder, mSnapshotFolderFull are initialized in ///////////////////////////////////////////////////////////////////////////// // Machine::HWData structure ///////////////////////////////////////////////////////////////////////////// /* default values for a newly created machine */ /* default boot order: floppy - DVD - HDD */ /* Make copies to speed up comparison */ ///////////////////////////////////////////////////////////////////////////// // Machine::HDData structure ///////////////////////////////////////////////////////////////////////////// /* default values for a newly created machine */ /* Make copies to speed up comparison */ ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // constructor / destructor ///////////////////////////////////////////////////////////////////////////// * Initializes the instance. * @param aParent Associated parent object * @param aConfigFile Local file system path to the VM settings file (can * be relative to the VirtualBox config directory). * @param aMode Init_New, Init_Existing or Init_Registered * @param aName name for the machine when aMode is Init_New * @param aNameSync |TRUE| to automatically sync settings dir and file * name with the machine name. |FALSE| is used for legacy * machines where the file name is specified by the * user and should never change. Used only in Init_New * mode (ignored otherwise). * @param aId UUID of the machine. Required for aMode==Init_Registered * and optional for aMode==Init_New. Used for consistency * check when aMode is Init_Registered; must match UUID * stored in the settings file. Used for predefining the * UUID of a VM when aMode is Init_New. * @return Success indicator. if not S_OK, the machine object is invalid /* Enclose the state transition NotReady->InInit->Ready */ /* share the parent weakly */ /* register with parent early, since uninit() will unconditionally * unregister on failure */ /* allocate the essential machine data structure (the rest will be * allocated later by initDataAndChildObjects() */ /* memorize the config file name (as provided) */ /* get the full file name */ tr (
"Invalid settings file name: '%ls' (%Vrc)"),
/* store the supplied UUID (will be used to check for UUID consistency /* lock the settings file */ /* check for the file existence */ tr (
"Invalid settings file name: '%ls' (%Vrc)"),
/* set to true now to cause uninit() to call * uninitDataAndChildObjects() on failure */ /* create the machine UUID */ /* memorize the provided new machine's name */ /* initialize the default snapshots folder * (note: depends on the name value set above!) */ /* commit all changes made during the initialization */ /* Confirm a successful initialization when it's the case */ * Initializes the registered machine by loading the settings file. * This method is separated from #init() in order to make it possible to * retry the operation after VirtualBox startup instead of refusing to * startup the whole VirtualBox server in case if the settings file of some * registered VM is invalid or inaccessible. * @note Must be always called from this object's write lock * (unless called from #init() that doesn't need any locking). * @note Locks the mUSBController method for writing. * @note Subclasses must not call this method. /* Temporarily reset the registered flag in order to let setters * potentially called from loadSettings() succeed (isMutable() used in * all setters will return FALSE for a Machine instance if mRegistered /* Restore the registered flag (even on failure) */ /* Set mAccessible to TRUE only if we successfully locked and loaded /* commit all changes made during loading the settings file */ /* VirtualBox will not call trySetRegistered(), so * inform the USB proxy about all attached USB filters */ /* If the machine is registered, then, instead of returning a * failure, we mark it as inaccessible and set the result to * success to give it a try later */ /* fetch the current error info */ LogWarning ((
"Machine {%Vuuid} is inaccessible! [%ls]\n",
/* rollback all changes */ /* uninitialize the common part to make sure all data is reset to * default (null) values */ * Uninitializes the instance. * Called either from FinalRelease() or by the parent when it gets destroyed. * @note The caller of this method must make sure that this object * a) doesn't have active callers on the current thread and b) is not locked * by the current thread; otherwise uninit() will hang either a) due to * AutoUninitSpan waiting for a number of calls to drop to zero or b) due to * a dead-lock caused by this thread waiting for all callers on the other * threads are are done but preventing them from doing so by holding a lock. /* Enclose the state transition Ready->InUninit->NotReady */ /* Enter this object lock because there may be a SessionMachine instance * somewhere around, that shares our data and lock but doesn't use our * addCaller()/removeCaller(), and it may be also accessing the same data * members. mParent lock is necessary as well because of * SessionMachine::uninit(), etc. /* Theoretically, this can only happen if the VirtualBox server has been * terminated while there were clients running that owned open direct * sessions. Since in this case we are definitely called by * VirtualBox::uninit(), we may be sure that SessionMachine::uninit() * won't happen on the client watcher thread (because it does * VirtualBox::addCaller() for the duration of the * SessionMachine::checkForDeath() call, so that VirtualBox::uninit() * cannot happen until the VirtualBox caller is released). This is * important, because SessionMachine::uninit() cannot correctly operate * after we return from this method (it expects the Machine instance is * still valid). We'll call it ourselves below. "the direct session is still open!\n",
/* set machine state using SessionMachine reimplementation */ * Uninitialize SessionMachine using public uninit() to indicate * an unexpected uninitialization. /* SessionMachine::uninit() must set mSession.mMachine to null */ /* the lock is no more necessary (SessionMachine is uninitialized) */ /* make sure the configuration is unlocked */ /* free the essential data structure last */ ///////////////////////////////////////////////////////////////////////////// /* mParent is constant during life time, no need to lock */ /* try to initialize the VM once more if not accessible */ tr (
"Machine name cannot be empty"));
/* look up the object by Id to check it is valid */ tr (
"Invalid RAM size: %lu MB (must be in range [%lu, %lu] MB)"),
tr (
"Invalid VRAM size: %lu MB (must be in range [%lu, %lu] MB)"),
/** @todo this method should not be public */ /** @todo this method should not be public */ tr (
"Invalid memory balloon size: %lu MB (must be in range [%lu, %lu] MB)"),
/** @todo this method should not be public */ /** @todo this method should not be public */ /* make sure monitor count is a sensible number */ tr (
"Invalid monitor count: %lu (must be in range [%lu, %lu])"),
/* mBIOSSettings is constant during life time, no need to lock */ /** @todo check validity! */ /** @todo check validity! */ * 1. Allow to change the name of the snapshot folder containing snapshots * 2. Rename the folder on disk instead of just changing the property * value (to be smart and not to leave garbage). Note that it cannot be * done here because the change may be rolled back. Thus, the right * place is #saveSettings(). tr (
"The snapshot folder of a machine with snapshots cannot " "be changed (please discard all snapshots first)"));
/* the default snapshots folder is 'Snapshots' in the machine dir */ /* the default snapshots folder is {UUID}, for backwards * compatibility and to resolve conflicts */ tr (
"Invalid snapshot folder: '%ls' (%Vrc)"),
/* Note: The GUI depends on this method returning E_NOTIMPL with no * extended error info to indicate that USB is simply not available * (w/o treting it as a failure), for example, as in OSE */ /* Note: The GUI depends on this method returning E_NOTIMPL with no * extended error info to indicate that SATA is simply not available * (w/o treting it as a failure), for example, as in OSE */ * if we're ready and isConfigLocked() is FALSE then it means * that no config file exists yet, so always return TRUE * Note: for machines with no snapshots, we always return FALSE * (mData->mCurrentStateModified will be TRUE in this case, for historical ///////////////////////////////////////////////////////////////////////////// tr (
"Invalid boot position: %lu (must be in range [1, %lu])"),
tr (
"Booting from USB devices is not currently supported"));
tr (
"Invalid boot position: %lu (must be in range [1, %lu])"),
/* The device property is not used for SATA yet. Thus it is always zero. */ tr (
"Invalid device number: %l (must be always 0)"),
* @todo: r=aeichner make max port count a system property. tr (
"Invalid channel number: %l (must be in range [%lu, %lu])"),
/* Validate input for IDE drives. */ tr (
"Invalid device number: %l (must be in range [%lu, %lu])"),
/* The first device is assigned to the CD/DVD drive. */ tr (
"Invalid device number: %l (must be %lu)"),
tr (
"Invalid channel number: %l (must be in range [%lu, %lu])"),
/* VirtualBox::getHardDisk() need read lock */ tr (
"Cannot attach hard disks to an unregistered machine"));
/* see if the device on the controller is already busy */ tr (
"Hard disk '%ls' is already attached to device slot %d on " /* find a hard disk by UUID */ /* create an attachment object early to let it check argiuments */ tr (
"Cannot attach the differencing hard disk '%ls'"),
* increase readers to protect from unregistration * until rollback()/commit() is done Log3 ((
"A: %ls associated with %Vuuid\n",
/* determine what the hard disk is already attached to */ /* attached to some VM in its current state */ * attached to us, either in the backed up list of the * attachments or in the current one; the former is ok * (reattachment takes place within the same * "transaction") the latter is an error so check for it "currently attached to device slot %d on channel %d " "of bus %d of this machine"),
* dirty = false to indicate we didn't set machineId * and prevent it from being reset in DetachHardDisk() /* attached to other VM */ "currently attached to a machine with " * here we go when the HardDiskType_Normal * is attached to some VM (probably to this one, too) * at some particular snapshot, so we can create a diff * increase readers to protect from unregistration * until rollback()/commit() is done /* note: diff images are actually created only in commit() */ tr (
"No hard disk attached to device slot %d on channel %d of bus %d"),
/* decrease readers increased in AttachHardDisk() */ /* deassociate from this machine */ /* deassociate from this machine */ /* decrease readers increased in AttachHardDisk() */ * we cannot use erase (it) below because backup() above will create * a copy of the list and make this copy active, but the iterator * still refers to the original and is not valid for a copy * note: Non-dirty hard disks are actually deassociated * and diff images are deleted only in commit() tr (
"No hard disk attached to device slot %d on channel %d of bus %d"),
* @note Locks this object for reading. /* serialize file access (prevent writes) */ /* start with nothing found */ /* if we're ready and isConfigLocked() is FALSE then it means * that no config file exists yet, so return shortly */ /* load the settings file (we don't reuse the existing handle but * request a new one to allow for concurrent multithreaded reads) */ /* if we're supposed to return the first one */ /* did we find the key we're looking for? */ /* is there another item? */ /* else it's the last one, arguments are already NULL */ /* Here we are when a) there are no items at all or b) there are items * but none of them equals to the requested non-NULL key. b) is an * error as well as a) if the key is non-NULL. When the key is NULL * (which is the case only when there are no items), we just fall * through to return NULLs and S_OK. */ tr (
"Could not find the extra data key '%ls'"),
aKey);
* @note Locks this object for reading. /* serialize file access (prevent writes) */ /* start with nothing found */ /* if we're ready and isConfigLocked() is FALSE then it means * that no config file exists yet, so return shortly */ /* load the settings file (we don't reuse the existing handle but * request a new one to allow for concurrent multithreaded reads) */ /* check if the key exists */ * @note Locks mParent for writing + this object for writing. /* VirtualBox::onExtraDataCanChange() and saveSettings() need mParent * lock (saveSettings() needs a write one). This object's write lock is * also necessary to serialize file access (prevent concurrent reads and /* If we're ready and isConfigLocked() is FALSE then it means that no * config file exists yet, so call saveSettings() to create one. */ /* load the settings file */ /* When no key is found, oldVal is null */ /* ask for permission from all listeners */ tr (
"Could not set extra data because someone refused " "the requested change of '%ls' to '%ls'%s%ls"),
/* an old value does for sure exist here (XML schema * guarantees that "value" may not absent in the * <ExtraDataItem> element) */ /* save settings on success */ /* fire a notification */ /* saveSettings() needs mParent lock */ /* the settings file path may never be null */ /* save all VM data excluding snapshots */ /* saveSettings() needs mParent lock */ /* the settings file path may never be null */ /* perform backup only when there was auto-conversion */ /* save all VM data excluding snapshots */ * during this rollback, the session will be notified if data has tr (
"Cannot delete settings of a registered machine"));
/* delete the settings only when the file actually exists */ tr (
"Could not delete the settings file '%ls' (%Vrc)"),
/* delete the Logs folder, nothing important should be left * there (we don't check for errors because the user might have * some private files there that we don't want to delete) */ /* Delete all VBox.log[.N] files from the Logs folder * (this must be in sync with the rotation logic in * Console::powerUpThread()). Also, delete the VBox.png[.N] * files that may have been created by the GUI. */ for (
int i =
3; i >= 0; i--)
/* delete the Snapshots folder, nothing important should be left * there (we don't check for errors because the user might have * some private files there that we don't want to delete) */ /* delete the directory that contains the settings file, but only * if it matches the VM name (i.e. a structure created by default in * prepareSaveSettings()) */ /// @todo (dmik) don't forget to set // mData->mCurrentStateModified to FALSE tr (
"Shared folder named '%ls' already exists"),
aName);
tr (
"Shared folder host path '%ls' is not accessible"),
aHostPath);
/* inform the direct session if any */ /* inform the direct session if any */ tr (
"Machine session is not open (session state: %d)"),
/* ignore calls made after #OnSessionEnd() is called */ tr (
"Machine session is not open (session state: %d)"),
/* ignore calls made after #OnSessionEnd() is called */ // public methods for internal purposes ///////////////////////////////////////////////////////////////////////////// * Returns the session machine object associated with the this machine. * The returned session machine is null if no direct session is currently open. * @note locks this object for reading. /* return null for inaccessible machines */ * Saves the registry entry of this machine to the given configuration node. * @param aEntryNode Node to save the registry entry to. * @note locks this object for reading. /* settings file name (possibly, relative) */ * Calculates the absolute path of the given path taking the directory of * the machine settings file as the current directory. * @param aPath path to calculate the absolute path for * @param aResult where to put the result (used only on success, * so can be the same Utf8Str instance as passed as \a aPath) * @return VirtualBox result * @note Locks this object for reading. * Tries to calculate the relative path of the given absolute path using the * directory of the machine settings file as the base directory. * @param aPath absolute path to calculate the relative path for * @param aResult where to put the result (used only when it's possible to * make a relative path from the given absolute path; * otherwise left untouched) * @note Locks this object for reading. /* when assigning, we create a separate Utf8Str instance because both * aPath and aResult can point to the same memory location when this * func is called (if we just do aResult = aPath, aResult will be freed * first, and since its the same as aPath, an attempt to copy garbage * Returns the full path to the machine's log folder in the * \a aLogFolder argument. /* Log folder is <Machines>/<VM_Name>/Logs */ /* Log folder is <Machines>/<VM_SnapshotFolder>/Logs */ * Returns @c true if the given DVD image is attached to this machine either * in the current state or in any of the snapshots. * @param aId Image ID to check. * @param aUsage Type of the check. * @note Locks this object + DVD object for reading. /* answer 'not attached' if the VM is limited */ /* take the session machine when appropriate */ /* first, check the current state */ /* loop over the backed up (permanent) and current (temporary) DVD data */ for (
unsigned i = 0; i <
ELEMENTS (d); ++ i)
/* then, check snapshots if any */ * Returns @c true if the given Floppy image is attached to this machine either * in the current state or in any of the snapshots. * @param aId Image ID to check. * @param aUsage Type of the check. * @note Locks this object + Floppy object for reading. /* answer 'not attached' if the VM is limited */ /* take the session machine when appropriate */ /* first, check the current state */ /* loop over the backed up (permanent) and current (temporary) Floppy data */ for (
unsigned i = 0; i <
ELEMENTS (d); ++ i)
/* then, check snapshots if any */ * @note Locks mParent and this object for writing, * calls the client process (outside the lock). /* We need VirtualBox lock because of Progress::notifyComplete() */ tr (
"A session for the machine '%ls' is currently open " /* This machine is awaiting for a spawning session to be opened, so * reject any other open attempts from processes other than one * started by #openRemoteSession(). */ tr (
"An unexpected process (PID=0x%08X) has tried to open a direct " "session with the machine named '%ls', while only a process " "started by OpenRemoteSession (PID=0x%08X) is allowed"),
/* create a SessionMachine object */ * Set the session state to Spawning to protect against subsequent * attempts to open a session and to unregister the machine after * Leave the lock before calling the client process -- it will call * because the state is Spawning, so that openRemotesession() and * openExistingSession() calls will fail. This method, called before we * enter the lock again, will fail because of the wrong PID. * Note that mData->mSession.mRemoteControls accessed outside * the lock may not be modified when state is Spawning, so it's safe. /* The failure may w/o any error info (from RPC), so provide one */ tr (
"Failed to assign the machine to the session"));
/* complete the remote session initialization */ /* get the console from the direct session */ /* assign machine & console to the remote sesion */ * after openRemoteSession(), the first and the only * entry in remoteControls is that remote session /* The failure may w/o any error info (from RPC), so provide one */ tr (
"Failed to assign the machine to the remote session"));
/* enter the lock again */ /* Restore the session state */ /* finalize spawning amyway (this is why we don't return on errors above) */ /* Note that the progress object is finalized later */ /* We don't reset mSession.mPid and mType here because both are * necessary for SessionMachine::uninit() to reap the child process /* Remove the remote control from the list on failure * and reset session state to Closed. */ /* memorize PID of the directly opened session */ /* memorize the direct session control and cache IUnknown for it */ /* associate the SessionMachine with this Machine */ /* request an IUnknown pointer early from the remote party for later * identity checks (it will be internally cached within mDirectControl /* finalize the progress after setting the state, for consistency */ /* uninitialize the created session machine on failure */ * @note Locks this object for writing, calls the client process tr (
"A session for the machine '%ls' is currently open " "(or being opened or closed)"),
/* get the path to the executable */ /* clone the current environment */ /* put new variables to the environment * (ignore empty variable names here since RTEnv API * intentionally doesn't do that) */ if (*p ==
'\n' && (p ==
newEnvStr || *(p -
1) !=
'\\'))
#
ifdef RT_OS_DARWIN /* Avoid Lanuch Services confusing this with the selector by using a helper app. */#
ifdef RT_OS_WINDOWS /** @todo drop this once the RTProcCreate bug has been fixed */#
ifdef RT_OS_DARWIN /* Avoid Lanuch Services confusing this with the selector by using a helper app. */#
ifdef RT_OS_WINDOWS /** @todo drop this once the RTProcCreate bug has been fixed */ tr (
"Invalid session type: '%ls'"),
aType);
tr (
"Could not launch a process for the machine '%ls' (%Vrc)"),
* Note that we don't leave the lock here before calling the client, * because it doesn't need to call us back if called with a NULL argument. * Leaving the lock herer is dangerous because we didn't prepare the * launch data yet, but the client we've just started may happen to be * too fast and call openSession() that will fail (because of PID, etc.), * so that the Machine will never get out of the Spawning session state. /* inform the session that it will be a remote one */ /* restore the session state */ /* The failure may w/o any error info (from RPC), so provide one */ tr (
"Failed to assign the machine to the session"));
/* attach launch data to the machine */ * @note Locks this object for writing, calls the client process tr (
"The machine '%ls' does not have an open session"),
* Get the console from the direct session (note that we don't leave the * lock here because GetRemoteConsole must not call us back). /* The failure may w/o any error info (from RPC), so provide one */ tr (
"Failed to get a console object from the direct session"));
* Leave the lock before calling the client process. It's safe here * since the only thing to do after we get the lock again is to add * the remote control to the list (which doesn't directly influence /* attach the remote session to the machine */ /* The failure may w/o any error info (from RPC), so provide one */ tr (
"Failed to assign the machine to the session"));
/* need to revalidate the state after entering the lock again */ tr (
"The machine '%ls' does not have an open session"),
/* store the control in the list */ * Checks that the registered flag of the machine can be set according to * the argument and sets it. On success, commits and saves all settings. * @note When this machine is inaccessible, the only valid value for \a * aRegistered is FALSE (i.e. unregister the machine) because unregistered * inaccessible machines are not currently supported. Note that unregistering * an inaccessible machine will \b uninitialize this machine object. Therefore, * the caller must make sure there are no active Machine::addCaller() calls * on the current thread because this will block Machine::uninit(). * @note Must be called from mParent's write lock. Locks this object and /* wait for state dependants to drop to zero */ /* A special case: the machine is not accessible. */ /* inaccessible machines can only be unregistered */ /* Uninitialize ourselves here because currently there may be no * unregistered that are inaccessible (this state combination is not * supported). Note releasing the caller and leaving the lock before tr (
"The machine '%ls' with UUID {%s} is already registered"),
tr (
"Cannot unregister the machine '%ls' because it " "is in the Saved state"),
tr (
"Cannot unregister the machine '%ls' because it " tr (
"Cannot unregister the machine '%ls' because it has an " tr (
"Cannot unregister the machine '%ls' because it " "has %d hard disks attached"),
/* Ensure the settings are saved. If we are going to be registered and * isConfigLocked() is FALSE then it means that no config file exists yet, * Increases the number of objects dependent on the machine state or on the * registered state. Guarantees that these two states will not change at least * until #releaseStateDependency() is called. * Depending on the @a aDepType value, additional state checks may be made. * These checks will set extended error info on failure. See * #checkStateDependency() for more info. * If this method returns a failure, the dependency is not added and the caller * is not allowed to rely on any particular machine state or registration state * value and may return the failed result code to the upper level. * @param aDepType Dependency type to add. * @param aState Current machine state (NULL if not interested). * @param aRegistered Current registered state (NULL if not interested). * @note Locks this object for reading. /* ensureNoStateDependencies() is waiting for state dependencies to * drop to zero so don't add more. It may make sense to wait a bit * and retry before reporting an error (since the pending state * transition should be really quick) but let's just assert for * now to see if it ever happens on practice. */ tr (
"Machine state change is in progress. " "Please retry the operation later."));
* Decreases the number of objects dependent on the machine state. * Must always complete the #addStateDependency() call after the state * dependency is no more necessary. /* stateLockHandle() is the same handle that is used by AutoCaller * so lock it in advance to avoid two mutex requests in a raw */ /* releaseStateDependency() w/o addStateDependency()? */);
/* inform ensureNoStateDependencies() that there are no more deps */ ///////////////////////////////////////////////////////////////////////////// * Performs machine state checks based on the @a aDepType value. If a check * fails, this method will set extended error info, otherwise it will return * S_OK. It is supposed, that on failure, the caller will immedieately return * the return value of this method to the upper level. * When @a aDepType is AnyStateDep, this method always returns S_OK. * When @a aDepType is MutableStateDep, this method returns S_OK only if the * current state of this machine object allows to change settings of the * machine (i.e. the machine is not registered, or registered but not running * and not saved). It is useful to call this method from Machine setters * before performing any change. * When @a aDepType is MutableOrSavedStateDep, this method behaves the same * as for MutableStateDep except that if the machine is saved, S_OK is also * returned. This is useful in setters which allow changing machine * properties when it is in the saved state. * @param aDepType Dependency type to check. * @note Non Machine based classes should use #addStateDependency() and * #releaseStateDependency() methods or the smart AutoStateDependency * @note This method must be called from under this object's read or write tr (
"The machine is not mutable (state is %d)"),
tr (
"The machine is not mutable (state is %d)"),
* Helper to initialize all associated child objects and allocate data * This method must be called as a part of the object's initialization procedure * (usually done in the #init() method). * @note Must be called only from #init() or from #registeredInit(). /* allocate data structures */ /* initialize mOSTypeId */ /* create associated BIOS settings object */ /* create an associated VRDPServer object (default is disabled) */ /* create an associated DVD drive object */ /* create an associated floppy drive object */ /* create associated serial port objects */ /* create associated parallel port objects */ /* create the audio adapter object (always present, default is disabled) */ /* create the USB controller object (always present, default is disabled) */ /* create the SATA controller object (always present, default is disabled) */ /* create associated network adapter objects */ * Helper to uninitialize all associated child objects and to free all data * This method must be called as a part of the object's uninitialization * procedure (usually done in the #uninit() method). * @note Must be called only from #uninit() or from #registeredInit(). /* uninit all children using addDependentChild()/removeDependentChild() * in their init()/uninit() methods */ /* tell all our other child objects we've been uninitialized */ /* Deassociate hard disks (only when a real Machine or a SnapshotMachine * instance is uninitialized; SessionMachine instances refer to real * Machine hard disks). This is necessary for a clean re-initialization of * the VM after successfully re-checking the accessibility state. Note * that in case of normal Machine or SnapshotMachine uninitialization (as * a result of unregistering or discarding the snapshot), outdated hard * disk attachments will already be uninitialized and deleted, so this * code will not affect them. */ /* reset some important fields of mData */ /* free data structures (the essential mData structure is not freed here * since it may be still in use) */ * Makes sure that there are no machine state dependants. If necessary, waits * for the number of dependants to drop to zero. Must be called from under this * object's write lock which will be released while waiting. * @param aLock This object's write lock. * @warning To be used only in methods that change the machine state! /* Wait for all state dependants if necessary */ /* lazy semaphore creation */ /* reset the semaphore before waiting, the last dependant will signal * Helper to change the machine state. * @note Locks this object for writing. /* wait for state dependants to drop to zero */ * Searches for a shared folder with the given logical name * in the collection of shared folders. * @param aName logical name of the shared folder * @param aSharedFolder where to return the found object * @param aSetError whether to set the error info if the folder is * S_OK when found or E_INVALIDARG when not found * must be called from under the object's lock! * Loads all the VM settings by walking down the <Machine> node. * @param aRegistered true when the machine is being loaded on VirtualBox * @note This method is intended to be called only from init(), so it assumes * all machine data fields have appropriate default values when it is called. * @note Doesn't lock any objects. /* no concurrent file access is possible in init() so open by handle */ /* If the stored UUID is not empty, it means the registered machine * is being loaded. Compare the loaded UUID with the stored one taken * from the global registry. */ tr (
"Machine UUID {%Vuuid} in '%ls' doesn't match its " "UUID {%s} in the registry file '%ls'"),
/* nameSync (optional, default is true) */ /* Description (optional, default is null) */ /* look up the object by Id to check it is valid */ /* stateFile (optional) */ tr (
"Invalid saved state file path: '%ls' (%Vrc)"),
* currentSnapshot ID (optional) * Note that due to XML Schema constaraints, this attribute, when * present, will guaranteedly refer to an existing snapshot /* snapshotFolder (optional) */ /* currentStateModified (optional, default is true) */ /* lastStateChange (optional, defaults to now) */ /* aborted (optional, default is false) */ * note: all mUserData members must be assigned prior this point because * we need to commit changes in order to let mUserData be shared by all * snapshot machine instances. /* Snapshot node (optional) */ /* read all snapshots recursively */ /* Hardware node (required) */ /* HardDiskAttachments node (required) */ * NOTE: the assignment below must be the last thing to do, * otherwise it will be not possible to change the settings * somewehere in the code above because all setters will be * blocked by checkStateDependency (MutableStateDep). /* set the machine state to Aborted or Saved when appropriate */ /* no need to use setMachineState() during init() */ /* no need to use setMachineState() during init() */ /* we assume that error info is set by the thrower */ * Recursively loads all snapshots starting from the given. * @param aNode <Snapshot> node. * @param aCurSnapshotId Current snapshot ID from the settings file. * @param aParentSnapshot Parent snapshot. /* create a snapshot machine object */ tr (
"Invalid saved state file path: '%ls' (%Vrc)"),
/* Hardware node (required) */ /* HardDiskAttachments node (required) */ /* initialize the snapshot machine */ /* create a snapshot object */ /* initialize the snapshot */ /* memorize the first snapshot if necessary */ /* memorize the current snapshot when appropriate */ /* Snapshots node (optional) */ * @param aNode <Hardware> node. /* CPU node (currently not required) */ /* default value in case the node is not there */ /* PAE (optional, default is false) */ /* Memory node (required) */ /* Boot node (required) */ /* reset all boot order positions to NoDevice */ /* position (required) */ /* position unicity is guaranteed by XML Schema */ /* Display node (required) */ /* Network node (required) */ /* we assume that all network adapters are initially disabled /* slot number (required) */ /* slot unicity is guaranteed by XML Schema */ /* Serial node (required) */ /* slot number (required) */ /* slot unicity is guaranteed by XML Schema */ /* Parallel node (optional) */ /* slot number (required) */ /* slot unicity is guaranteed by XML Schema */ /* Shared folders (required) */ /* folder logical name (required) */ /* folder host path (required) */ /* Clipboard node (required) */ /* Guest node (required) */ /* optional, defaults to 0 */ /* optional, defaults to 0 */ * @param aNode <HardDiskAttachments> node. * @param aRegistered true when the machine is being loaded on VirtualBox * startup, or when a snapshot is being loaded (wchich * currently can happen on startup only) * @param aSnapshotId pointer to the snapshot ID if this is a snapshot machine /* when the machine is being loaded (opened) from a file, it cannot * have hard disks attached (this should not happen normally, * because we don't allow to attach hard disks to an unregistered tr (
"Unregistered machine '%ls' cannot have hard disks attached " "(found %d hard disk attachments)"),
/* hardDisk uuid (required) */ /* bus (controller) type (required) */ /* find a hard disk by UUID */ tr (
"Hard disk '%ls' with UUID {%s} is already " "attached to a machine with UUID {%s} (see '%ls')"),
tr (
"Immutable hard disk '%ls' with UUID {%s} cannot be " "directly attached to a machine (see '%ls')"),
/* associate the hard disk with this machine */ /* associate the hard disk with the given snapshot ID */ * Searches for a <Snapshot> node for the given snapshot. * If the search is successful, \a aSnapshotNode will contain the found node. * In this case, \a aSnapshotsNode can be NULL meaning the found node is a * direct child of \a aMachineNode. * If the search fails, a failure is returned and both \a aSnapshotsNode and * \a aSnapshotNode are set to 0. * @param aSnapshot Snapshot to search for. * @param aMachineNode <Machine> node to start from. * @param aSnapshotsNode <Snapshots> node containing the found <Snapshot> node * (may be NULL if the caller is not interested). * @param aSnapshotNode Found <Snapshot> node. // build the full uuid path (from the top parent to the given snapshot) /* proceed to the nested <Snapshots> node */ /* pass over to the outer loop */ /* the next uuid is not found, no need to continue... */ // we must always succesfully find the node * Returns the snapshot with the given UUID or fails of no such snapshot. * @param aId snapshot UUID to find (empty UUID refers the first snapshot) * @param aSnapshot where to return the found snapshot * @param aSetError true to set extended error info on failure tr (
"This machine does not have any snapshots"));
tr (
"Could not find a snapshot with UUID {%s}"),
* Returns the snapshot with the given name or fails of no such snapshot. * @param aName snapshot name to find * @param aSnapshot where to return the found snapshot * @param aSetError true to set extended error info on failure tr (
"This machine does not have any snapshots"));
tr (
"Could not find a snapshot named '%ls'"),
aName);
* Searches for an attachment that contains the given hard disk. * The hard disk must be associated with some VM and can be optionally * associated with some snapshot. If the attachment is stored in the snapshot * (i.e. the hard disk is associated with some snapshot), @a aSnapshot * will point to a non-null object on output. * @param aHd hard disk to search an attachment for * @param aMachine where to store the hard disk's machine (can be NULL) * @param aSnapshot where to store the hard disk's snapshot (can be NULL) * @param aHda where to store the hard disk's attachment (can be NULL) * It is assumed that the machine where the attachment is found, * is already placed to the Discarding state, when this method is called. * The object returned in @a aHda is the attachment from the snapshot * machine if the hard disk is associated with the snapshot, not from the * primary machine object returned returned in @a aMachine. * Helper for #saveSettings. Cares about renaming the settings directory and * file if the machine name was changed and about creating a new settings file * if this is a new machine. * @note Must be never called directly but only from #saveSettings(). * @param aRenamed receives |true| if the name was changed and the settings * file was renamed as a result, or |false| otherwise. The * value makes sense only on success. * @param aNew receives |true| if a virgin settings file was created. /* Note: tecnhically, mParent needs to be locked only when the machine is * registered (see prepareSaveSettings() for details) but we don't * currently differentiate it in callers of saveSettings() so we don't * make difference here too. */ /* if we're ready and isConfigLocked() is FALSE then it means * that no config file exists yet (we will create a virgin one) */ /* attempt to rename the settings file if machine name is changed */ /* unlock the old config file */ /* first, rename the directory if it matches the machine name */ /* new dir and old dir cannot be equal here because of 'if' * above and because name != newName */ /* perform real rename only if the machine is not new */ tr (
"Could not rename the directory '%s' to '%s' " "to save the settings file (%Vrc)"),
/* then try to rename the settings file itself */ /* get the path to old settings file in renamed directory */ /* perform real rename only if the machine is not new */ tr (
"Could not rename the settings file '%s' to '%s' " /* update mConfigFileFull amd mConfigFile */ /* try to get the relative path for mConfigFile */ /* last, try to update the global settings with the new path */ /* revert to old values */ /* update the snapshot folder */ /* update the saved state file path */ /* Update saved state file paths of all online snapshots. * Note that saveSettings() will recognize name change * and will save all snapshots in this case. */ /* silently try to rename everything back */ /* lock the config again */ /* create a virgin config file */ /* ensure the settings directory exists */ tr (
"Could not create a directory '%s' " "to save the settings file (%Vrc)"),
/* Note: open flags must correlate with RTFileOpen() in lockConfig() */ tr (
"Could not create the settings file '%s' (%Vrc)"),
/* we do not close the file to simulate lockConfig() */ * Saves machine data, user data and hardware data. * @param aMarkCurStateAsModified * If true (default), mData->mCurrentStateModified will be set to * what #isReallyModified() returns prior to saving settings to a file, * otherwise the current value of mData->mCurrentStateModified will be * @param aInformCallbacksAnyway * If true, callbacks will be informed even if #isReallyModified() * returns false. This is necessary for cases when we change machine data * diectly, not through the backup()/commit() mechanism. * @note Must be called from under mParent write lock (sometimes needed by * #prepareSaveSettings()) and this object's write lock. Locks children for * writing. There is one exception when mParent is unused and therefore may * be left unlocked: if this machine is an unregistered one. /* Note: tecnhically, mParent needs to be locked only when the machine is * registered (see prepareSaveSettings() for details) but we don't * currently differentiate it in callers of saveSettings() so we don't * make difference here too. */ /// @todo (dmik) I guess we should lock all our child objects here // (such as mVRDPServer etc.) to ensure they are not changed // until completely saved to disk and committed /// @todo (dmik) also, we need to delegate saving child objects' settings // to objects themselves to ensure operations 'commit + save changes' // are atomic (amd done from the object's lock so that nobody can change // settings again until completely saved). * We ignore changes to user data when setting mCurrentStateModified * because the current state will not differ from the current snapshot * if only user data has been changed (user data is shared by all /* First, prepare to save settings. It will will care about renaming the * settings directory and file if the machine name was changed and about * creating a new settings file if this is a new machine. */ /* this object is locked for writing to prevent concurrent reads and writes */ /* The newly created settings file is incomplete therefore we turn off * validation. The rest is like in loadSettingsTree_ForUpdate().*/ false /* aCatchLoadErrors */,
false /* aAddDefaults */);
/* ask to save all snapshots when the machine name was changed since * it may affect saved state file paths for online snapshots (see * #openConfigLoader() for details) */ /* commit before saving, since it may change settings * (for example, perform fixup of lazy hard disk changes) */ /* include hard disk changes to the modified flag */ /* nameSync (optional, default is true) */ /* Description node (optional) */ /* stateFile (optional) */ /* try to make the file name relative to the settings file dir */ /* currentSnapshot ID (optional) */ /* snapshotFolder (optional) */ /// @todo use the Bstr::NullOrEmpty constant and setValueOr /* currentStateModified (optional, default is true) */ /* set the aborted attribute when appropriate, defaults to false */ /* Hardware node (required) */ /* first, delete the entire node if exists */ /* HardDiskAttachments node (required) */ /* first, delete the entire node if exists */ /* update all snapshots if requested */ /* save the settings on success */ /* we assume that error info is set by the thrower */ /* backup arbitrary data item to cause #isModified() to still return * true in case of any error */ /* Fire the data change event, even on failure (since we've already * committed all data). This is done only for SessionMachines because * mutable Machine instances are always not registered (i.e. private * to the client process that creates them) and thus don't need to * Wrapper for #saveSnapshotSettingsWorker() that opens the settings file * and locates the <Machine> node in there. See #saveSnapshotSettingsWorker() * @param aSnapshot Snapshot to operate on * @param aOpFlags Operation to perform, one of SaveSS_NoOp, SaveSS_AddOp * or SaveSS_UpdateAttrsOp possibly combined with * SaveSS_UpdateCurrentId. * @note Locks this object for writing + other child objects. /* This object's write lock is also necessary to serialize file access * (prevent concurrent reads and writes) */ /* load the settings file */ /* save settings on success */ * Performs the specified operation on the given snapshot * in the settings file represented by \a aMachineNode. * If \a aOpFlags = SaveSS_UpdateAllOp, \a aSnapshot can be NULL to indicate * that the whole tree of the snapshots should be updated in <Machine>. * One particular case is when the last (and the only) snapshot should be * removed (it is so when both mCurrentSnapshot and mFirstSnapshot are NULL). * \a aOp may be just SaveSS_UpdateCurrentId if only the currentSnapshot * attribute of <Machine> needs to be updated. * @param aMachineNode <Machine> node in the opened settings file. * @param aSnapshot Snapshot to operate on. * @param aOpFlags Operation to perform, one of SaveSS_NoOp, SaveSS_AddOp * or SaveSS_UpdateAttrsOp possibly combined with * SaveSS_UpdateCurrentId. * @note Must be called with this object locked for writing. /* quick path: recreate the whole tree of the snapshots */ /* first, delete the entire root snapshot node if it exists */ /* second, if we have any snapshots left, substitute aSnapshot * with the first snapshot to recreate the whole tree, otherwise /* when a new snapshot is added, this means diffs were created * save the current hard disk attachments */ /* If we have one or more attachments then we definitely * created diffs for them and associated new diffs with * current settngs. So, since we don't use saveSettings(), * we need to inform callbacks manually. */ /* update currentSnapshot when appropriate */ * 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 updatge user-changeable attrs. /* timeStamp (required) */ /* Description node (optional) */ /* stateFile (optional) */ /* try to make the file name relative to the settings file dir */ * Saves the VM hardware configuration. It is assumed that the * @param aNode <Hardware> node to save the VM hardware confguration to. /* PAE (optional, default is false) */ /* skip, this is allowed for <Order> nodes * when loading, the default value NoDevice will remain */ /* VRDP settings (optional) */ /* DVD drive (required) */ /* Flooppy drive (required) */ /* USB Controller (required) */ /* SATA Controller (required) */ /* Network adapters (required) */ * Saves the hard disk confguration. * It is assumed that the given node is empty. * @param aNode <HardDiskAttachments> node to save the hard disk confguration to. * Saves machine state settings as defined by aFlags * @param aFlags Combination of SaveSTS_* flags. * @note Locks objects for writing. /* This object's write lock is also necessary to serialize file access * (prevent concurrent reads and writes) */ /* load the settings file */ /* try to make the file name relative to the settings file dir */ /* set the aborted attribute when appropriate, defaults to false */ /* save settings on success */ * Cleans up all differencing hard disks based on immutable hard disks. /// @todo (dmik) no error handling for now // (need async error reporting for this) * Fixes up lazy hard disk attachments by creating or deleting differencing * hard disks when machine settings are being committed. * Must be called only from #commit(). * changes are being committed, * perform actual diff image creation, deletion etc. /* take a copy of backed up attachments (will modify it) */ /* list of new diffs created */ /* go through current attachments */ * not dirty, therefore was either attached before backing up * or doesn't need any fixup (already fixed up); try to locate * this hard disk among backed up attachments and remove from /* dirty, determine what to do */ /* decrease readers increased in AttachHardDisk() */ /* indicate we need a diff (indirect attachment) */ /* reset the dirty flag */ /* reset the dirty flag */ /* decrease readers increased in AttachHardDisk() */ /* indicate we need a diff (indirect attachment) */ /* search for the most recent base among snapshots */ * see whether any previously attached hard disk has the * the currently attached one (Normal or Independent) as * matched dev and ctl (i.e. attached to the same place) * will win and immediately stop the search; otherwise * the first attachment that matched the hd only will * not an exact match; ensure there is no exact match * among other current attachments referring the same * root (to prevent this attachmend from reusing the * hard disk of the other attachment that will later * give the exact match or already gave it before) * the exact match, either non-dirty or dirty * one refers the same root: in both cases * we cannot reuse the hard disk, so break /* found either one or another, reuse the diff */ /* was not attached, need a diff */ * find the most recent diff based on the currently * attached root (Normal hard disk) among snapshots * matched dev and ctl (i.e. attached to the same place) * will win and immediately stop the search; otherwise * the first attachment that matched the hd only will /* the most recent diff has been found, use as a base */ Log3 ((
"FC: %ls: recent found %ls\n",
/* create a new diff for the hard disk being indirectly attached */ /* update the attachment and reset the dirty flag */ Log3 ((
"FC: %ls: diff created %ls\n",
/* delete diffs we created */ * unregisterDiffHardDisk() is supposed to delete and uninit * the differencing hard disk /* too bad if we fail here, but nothing to do, just continue */ /* the best is to rollback the changes... */ Log3 ((
"FC: ROLLED BACK\n"));
* go through the rest of old attachments and delete diffs * or deassociate hard disks from machines (they will become detached) * unregisterDiffHardDisk() is supposed to delete and uninit * the differencing hard disk * too bad if we fail here, but nothing to do, just continue * (the last rc will be returned to the caller though) /* deassociate from this machine */ /* commit all the changes */ Log3 ((
"FC: COMMITTED\n"));
* changes are being rolled back, * go trhough all current attachments and fix up dirty ones * the way it is done in DetachHardDisk() /* decrease readers increased in AttachHardDisk() */ /* deassociate from this machine */ /* deassociate from this machine */ /* decrease readers increased in AttachHardDisk() */ /* rollback all the changes */ Log3 ((
"FR: ROLLED BACK\n"));
* Creates differencing hard disks for all normal hard disks * and replaces attachments to refer to created disks. * Used when taking a snapshot or when discarding the current state. * @param aSnapshotId ID of the snapshot being taken * or NULL if the current state is being discarded * @param aFolder folder where to create diff. hard disks * @param aProgress progress object to run (must contain at least as * many operations left as the number of VDIs attached) * @param aOnline whether the machine is online (i.e., when the EMT * thread is paused, OR when current hard disks are * marked as busy for some other reason) * The progress object is not marked as completed, neither on success * nor on failure. This is a responsibility of the caller. * @note Locks mParent + this object for writing /* accessing mParent methods below needs mParent lock */ // first pass: check accessibility before performing changes (
"Invalid hard disk type %d\n",
hd->
type()),
tr (
"Hard disk '%ls' is not accessible (%ls)"),
// second pass: perform changes // clear busy flag if the VM is online tr (
"Preserving immutable hard disk '%ls'"),
// create a copy of the independent diff // decrease readers (hd is no more used for reading in any case) // checked in the first pass tr (
"Creating a differencing hard disk for '%ls'"),
// create a new diff for the image being attached // if online, hd must keep a reader referece // associate the snapshot id with the old hard disk // add the new attachment // if online, newHd must be marked as busy // set busy flag back if the VM is online // replace the whole list of attachments with the new one // delete those diffs we've just created // unregisterDiffHardDisk() is supposed to delete and uninit // the differencing hard disk * Deletes differencing hard disks created by createSnapshotDiffs() in case * if snapshot creation was failed. * @param aSnapshot failed snapshot * @note Locks mParent + this object for writing. /* accessing mParent methods below needs mParent lock */ /* short cut: check whether attachments are all the same */ /* must not have children */ /* deassociate the old hard disk from the given snapshot's ID */ /* unregisterDiffHardDisk() is supposed to delete and uninit * the differencing hard disk */ /* restore the whole list of attachments from the failed snapshot */ * Helper to lock the machine configuration for write access. * @return S_OK or E_FAIL and sets error info on failure * @note Doesn't lock anything (must be called from this object's lock) /* open the associated config file */ tr (
"Could not lock the settings file '%ls' (%Vrc)"),
* Helper to unlock the machine configuration from write access * @note Doesn't lock anything. * @note Not thread safe (must be called from this object's lock). /** @todo flush the directory. */ * Returns true if the settings file is located in the directory named exactly * as the machine. This will be true if the machine settings structure was * created by default in #openConfigLoader(). * @param aSettingsDir if not NULL, the full machine settings file directory * name will be assigned there. * @note Doesn't lock anything. * @note Not thread safe (must be called from this object's lock). /* if we don't rename anything on name change, return false shorlty */ * @note Locks objects for reading! * @note This method doesn't check (ignores) actual changes to mHDData. * Use mHDData.mHDAttachmentsChanged right after #commit() instead. * @param aIgnoreUserData |true| to ignore changes to mUserData * @note Locks objects for reading! * Discards all changes to machine settings. * @param aNotify whether to notify the direct session about changes or not /* check for changes in own data */ /* check for changes in child objects */ /* inform the direct session about changes */ * Commits all the changes to machine settings. * Note that when committing fails at some stage, it still continues * until the end. So, all data will either be actually committed or rolled * back (for failed cases) and the returned result code will describe the * first failure encountered. However, #isModified() will still return true * in case of failure, to indicade that settings in memory and on disk are * use safe commit to ensure Snapshot machines (that share mUserData) * will still refer to a valid memory location /* attach new data to the primary machine and reshare it */ * backup arbitrary data item to cause #isModified() to still return * true in case of any error * Copies all the hardware data from the given machine. * This method must be called from under this object's lock. * This method doesn't call #commit(), so all data remains backed up // create copies of all shared folders (mHWData after attiching a copy // contains just references to original objects) ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// /** Task structure for asynchronous VM operations */ /** Take snapshot task */ /** Discard snapshot task */ /** Discard current state task */ //////////////////////////////////////////////////////////////////////////////// /* set the proper type to indicate we're the SessionMachine instance */ * @note Must be called only by Machine::openSession() from its own write lock. /* Enclose the state transition NotReady->InInit->Ready */ /* create the interprocess semaphore */ (
"Cannot create IPC mutex '%ls', err=%d\n",
(
"Cannot create IPC mutex '%s', arc=%ld\n",
tr (
"Cannot create IPC semaphore. Most likely your host kernel lacks " "support for SysV IPC. Check the host kernel configuration for " /* set the initial value to 1 */ /* memorize the peer Machine */ /* share the parent pointer */ /* take the pointers to data to share */ /* create another VRDPServer object that will be mutable */ /* create another DVD drive object that will be mutable */ /* create another floppy drive object that will be mutable */ /* create another audio adapter object that will be mutable */ /* create a list of serial ports that will be mutable */ /* create a list of parallel ports that will be mutable */ /* create another USB controller object that will be mutable */ /* create another SATA controller object that will be mutable */ /* create a list of network adapters that will be mutable */ /* Confirm a successful initialization when it's the case */ * Uninitializes this session object. If the reason is other than * Uninit::Unexpected, then this method MUST be called from #checkForDeath(). * @param aReason uninitialization reason * @note Locks mParent + this object for writing. * Strongly reference ourselves to prevent this object deletion after * reference and call the destructor). Important: this must be done before * accessing any members (and before AutoUninitSpan that does it as well). * This self reference will be released as the very last step on return. /* Enclose the state transition Ready->InUninit->NotReady */ /* We've been called by init() because it's failed. It's not really * necessary (nor it's safe) to perform the regular uninit sequense * below, the following is enough. /* We need to lock this object in uninit() because the lock is shared * with mPeer (as well as data we modify below). mParent->addProcessToReap() * and others need mParent lock. */ /* reset the state to Aborted */ /* release all captured USB devices */ /* Console::captureUSBDevices() is called in the VM process only after * setting the machine state to Starting or Restoring. * Console::detachAllUSBDevices() will be called upon successful * termination. So, we need to release USB devices only if there was * an abnormal termination of a running VM. * This is identical to SessionMachine::DetachAllUSBDevices except * for the aAbnormal argument. */ #
endif /* VBOX_WITH_USB */ /* mType is not null when this machine's process has been started by * VirtualBox::OpenRemoteSession(), therefore it is our child. We * need to queue the PID to reap the process (and avoid zombies on /* Uninitialization didn't come from #checkForDeath(), so tell the * client watcher thread to update the set of machines that have open /* uninitialize all remote controls */ * An expected uninitialization can come only from #checkForDeath(). * Otherwise it means that something's got really wrong (for examlple, * the Session implementation has released the VirtualBox reference * before it triggered #OnSessionEnd(), or before releasing IPC semaphore, * etc). However, it's also possible, that the client releases the IPC * semaphore correctly (i.e. before it releases the VirtualBox reference), * but but the VirtualBox release event comes first to the server process. * This case is practically possible, so we should not assert on an * unexpected uninit, just log a warning. /* this must be null here (see #OnSessionEnd()) */ /* remove the association between the peer machine and this session machine */ /* reset the rest of session data */ /* close the interprocess semaphore before leaving the shared lock */ /* free the essential data structure last */ /* leave the shared lock before setting the below two to NULL */ // util::Lockable interface //////////////////////////////////////////////////////////////////////////////// * Overrides VirtualBoxBase::lockHandle() in order to share the lock handle * with the primary Machine instance (mPeer). // IInternalMachineControl methods //////////////////////////////////////////////////////////////////////////////// * @note Locks the same as #setMachineState() does. * @note Locks this object for reading. * Goes through the USB filters of the given machine to see if the given * device matches any filter or not. * @note Locks the same as USBController::hasMatchingFilter() does. * @note Locks the same as Host::captureUSBDevice() does. /* if captureDeviceForVM() fails, it must have set extended error info */ * @note Locks the same as Host::detachUSBDevice() does. * Inserts all machine filters to the USB proxy service and then calls * Host::autoCaptureUSBDevices(). * Called by Console from the VM process upon VM startup. * @note Locks what called methods lock. * Removes all machine filters from the USB proxy service and then calls * Host::detachAllUSBDevices(). * Called by Console from the VM process upon normal VM termination or by * SessionMachine::uninit() upon abnormal VM termination (from under the * @note Locks what called methods lock. * @note Locks mParent + this object for writing. * We don't assert below because it might happen that a non-direct session * informs us it is closed right after we've been uninitialized -- it's ok. /* get IInternalSessionControl interface */ /* Progress::init() needs mParent lock */ /* The direct session is being normally closed by the client process * ----------------------------------------------------------------- */ /* go to the closing state (essential for all open*Session() calls and * for #checkForDeath()) */ /* set direct control to NULL to release the remote instance */ * Create the progress object the client will use to wait until * #checkForDeath() is called to uninitialize this session object * after it releases the IPC semaphore. Bstr (
tr (
"Closing session")),
FALSE /* aCancelable */);
/* the remote session is being normally closed */ * @note Locks mParent + this object for writing. /* mParent->addProgress() needs mParent lock */ /* memorize the progress ID and add it to the global collection */ /* stateFilePath is null when the machine is not running */ /* fill in the snapshot data */ /* set the state to Saving (this is expected by Console::SaveState()) */ * @note Locks mParent + this objects for writing. /* endSavingState() need mParent lock */ * on success, set the state to Saved; * on failure, set the state to the state we had when BeginSavingState() was * called (this is expected by Console::SaveState() and * Console::saveStateThread()) * @note Locks this objects for writing. tr (
"Invalid saved state file path: '%ls' (%Vrc)"),
/* The below setMachineState() will detect the state transition and will * update the settings file */ * @note Locks mParent + this objects for writing. /* Progress::init() needs mParent lock */ * save all current settings to ensure current changes are committed * and hard disks are fixed up /* check that there are no Writethrough hard disks attached */ tr (
"Cannot take a snapshot when there is a Writethrough hard " /* create an ID for the snapshot */ /* stateFilePath is null when the machine is not online nor saved */ /* ensure the directory for the saved state file exists */ tr (
"Could not create a directory '%s' to save the " /* create a snapshot machine object */ * create a server-side progress object (it will be descriptionless * when we need to combine it with the VM-side progress, i.e. when we're * taking a snapshot online). The number of operations is: * 1 (preparing) + # of VDIs + 1 (if the state is saved so we need to copy it) /* create a combined server-side progress object when necessary */ /* create a snapshot object */ * create and start the task on a separate thread * (note that it will not start working until we release alock) /* fill in the snapshot data */ /* set the state to Saving (this is expected by Console::TakeSnapshot()) */ * @note Locks mParent + this objects for writing. /* Lock mParent because of endTakingSnapshot() */ * set the state to the state we had when BeginTakingSnapshot() was called * (this is expected by Console::TakeSnapshot() and * Console::saveStateThread()) * @note Locks mParent + this + children objects for writing! /* Progress::init() needs mParent lock */ tr (
"Cannot discard the snapshot '%ls' because it is the first " "snapshot of the machine '%ls' and it has more than one " * If the snapshot being discarded is the current one, ensure current * settings are committed and saved. * create a progress object. The number of operations is: * 1 (preparing) + # of VDIs Bstr (
tr (
"Preparing to discard snapshot")));
/* create and start the task on a separate thread */ /* set the proper machine state (note: after creating a Task instance) */ /* return the progress to the caller */ /* return the new state to the caller */ * @note Locks mParent + this + children objects for writing! /* Progress::init() needs mParent lock */ tr (
"Could not discard the current state of the machine '%ls' " "because it doesn't have any snapshots"),
* create a progress object. The number of operations is: * 1 (preparing) + # of VDIs + 1 (if we need to copy the saved state file) Bstr (
tr (
"Discarding current machine state")),
Bstr (
tr (
"Preparing to discard current state")));
/* create and start the task on a separate thread */ /* set the proper machine state (note: after creating a Task instance) */ /* return the progress to the caller */ /* return the new state to the caller */ * @note Locks mParent + other objects for writing! /* Progress::init() needs mParent lock */ tr (
"Could not discard the current state of the machine '%ls' " "because it doesn't have any snapshots"),
* create a progress object. The number of operations is: * 1 (preparing) + # of VDIs in the current snapshot + * # of VDIs in the previous snapshot + * 1 (if we need to copy the saved state file of the previous snapshot) * or (if there is no previous snapshot): * 1 (preparing) + # of VDIs in the current snapshot * 2 + * 1 (if we need to copy the saved state file of the current snapshot) Bstr (
tr (
"Discarding current machine snapshot and state")),
Bstr (
tr (
"Preparing to discard current snapshot and state")));
/* create and start the task on a separate thread */ /* set the proper machine state (note: after creating a Task instance) */ /* return the progress to the caller */ /* return the new state to the caller */ // public methods only for internal purposes ///////////////////////////////////////////////////////////////////////////// * Called from the client watcher thread to check for unexpected client * @note On Win32 and on OS/2, this method is called only when we've got the * mutex (i.e. the client has either died or terminated normally). This * method always returns true. * @note On Linux, the method returns true if the client process has * terminated abnormally (and/or the session has been uninitialized) and * false if it is still alive. * @note Locks this object for writing. * Enclose autoCaller with a block because calling uninit() * from under it will deadlock. * return true if not ready, to cause the client watcher to exclude * the corresponding session from watching * Determine the reason of death: if the session state is Closing here, * everything is fine. Otherwise it means that the client did not call * OnSessionEnd() before it released the IPC semaphore. * This may happen either because the client process has abnormally * terminated, or because it simply forgot to call ISession::Close() * before exiting. We threat the latter also as an abnormal termination * (see Session::uninit() for details). /* release the IPC mutex */ /* release the IPC mutex */ /* the semaphore is signaled, meaning the session is terminated */ * @note Locks this object for reading. /* ignore notifications sent after #OnSessionEnd() is called */ * @note Locks this object for reading. /* ignore notifications sent after #OnSessionEnd() is called */ * @note Locks this object for reading. /* ignore notifications sent after #OnSessionEnd() is called */ * @note Locks this object for reading. /* ignore notifications sent after #OnSessionEnd() is called */ * @note Locks this object for reading. /* ignore notifications sent after #OnSessionEnd() is called */ * @note Locks this object for reading. /* ignore notifications sent after #OnSessionEnd() is called */ * @note Locks this object for reading. /* ignore notifications sent after #OnSessionEnd() is called */ * @note Locks this object for reading. /* ignore notifications sent after #OnSessionEnd() is called */ * Returns @c true if this machine's USB controller reports it has a matching * filter for the given USB device and @c false otherwise. * @note Locks this object for reading. /* silently return if not ready -- this method may be called after the * direct machine session has been called */ * @note The calls shall hold no locks. Will temporarily lock this object for reading. /* This notification may happen after the machine object has been * uninitialized (the session was closed), so don't assert. */ /* fail on notifications sent after #OnSessionEnd() is called, it is * expected by the caller */ /* No locks should be held at this point. */ * @note The calls shall hold no locks. Will temporarily lock this object for reading. /* This notification may happen after the machine object has been * uninitialized (the session was closed), so don't assert. */ /* fail on notifications sent after #OnSessionEnd() is called, it is * expected by the caller */ /* No locks should be held at this point. */ ///////////////////////////////////////////////////////////////////////////// * Helper method to finalize saving the state. * @note Must be called from under this object's lock. * @param aSuccess TRUE if the snapshot has been taken successfully * @note Locks mParent + this objects for writing. /* mParent->removeProgress() and saveSettings() need mParent lock */ /* save all VM settings */ /* delete the saved state file (it might have been already created) */ /* remove the completed progress object */ /* clear out the temporary saved state data */ * Helper method to finalize taking a snapshot. * Gets called only from #EndTakingSnapshot() that is expected to * be called by the VM process when it finishes *all* the tasks related to * taking a snapshot, either scucessfully or unsuccessfilly. * @param aSuccess TRUE if the snapshot has been taken successfully * @note Locks mParent + this objects for writing. /* Progress object uninitialization needs mParent lock */ /* the server progress must be completed on success */ /* memorize the first snapshot if necessary */ * the machine was powered off or saved when taking a snapshot, * so reset the mCurrentStateModified flag /* wait for the completion of the server progress (diff VDI creation) */ /// @todo (dmik) later, we will definitely want to cancel it instead // (when the cancel function is implemented) * delete all differencing VDIs created * (this will attach their parents back) /* continue cleanup on error */ /* delete the saved state file (it might have been already created) */ /* clear out the snapshot data */ /* uninitialize the combined progress (to remove it from the VBox collection) */ * Take snapshot task handler. * Must be called only by TakeSnapshotTask::handler()! * The sole purpose of this task is to asynchronously create differencing VDIs * and copy the saved state file (when necessary). The VM process will wait * for this task to complete using the mSnapshotData.mServerProgress * @note Locks mParent + this objects for writing. * we might have been uninitialized because the session was * accidentally closed by the client, so don't assert /* endTakingSnapshot() needs mParent lock */ /* create new differencing hard disks and attach them to this machine */ Bstr (
tr (
"Copying the execution state")));
* We can safely leave the lock here: * mMachineState is MachineState_Saving here /* copy the state file */ tr (
"Could not copy the state file '%ls' to '%ls' (%Vrc)"),
* we have to call endTakingSnapshot() here if the snapshot was taken * offline, because the VM process will not do it in this case /* finalize the progress after setting the state, for consistency */ * Discard snapshot task handler. * Must be called only by DiscardSnapshotTask::handler()! * When aTask.subTask is true, the associated progress object is left * uncompleted on success. On failure, the progress is marked as completed * regardless of this parameter. * @note Locks mParent + this + child objects for writing! * 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"));
/* Progress::notifyComplete() et al., saveSettings() need mParent lock. * Also safely lock the snapshot stuff in the direction parent->child */ /* no need to lock the snapshot machine since it is const by definiton */ /* save the snapshot ID (for callbacks) */ LogFlowThisFunc ((
"Check hard disk accessibility and affected machines...\n"));
tr (
"One or more hard disks belonging to other machines are " "based on the hard disk '%ls' stored in the snapshot '%ls'"),
tr (
"Normal hard disk '%ls' stored in the snapshot '%ls' " "has more than one child hard disk (%d)"),
tr (
"Hard disk '%ls' stored in the snapshot '%ls' is not " /* reset the busy flag of all previous hard disks */ tr (
"Discarding changes to immutable hard disk '%ls'"),
/* clear the busy flag before unregistering */ * unregisterDiffHardDisk() is supposed to delete and uninit * the differencing hard disk * merge this image to all its children tr (
"Merging changes to normal hard disk '%ls' to children"),
// if (it != sm->mHDData->mHDAttachments.begin()) // rc = setError (E_FAIL, "Simulated failure"); * normal vdi has the only child or none * (checked in the first pass) tr (
"Detaching normal hard disk '%ls'"),
/* just deassociate the normal image from this machine */ /* clear the busy flag */ tr (
"Preserving changes to normal hard disk '%ls'"),
/* must be the same machine (checked in the first pass) */ /* merge the child to this basic image */ /* reset the snapshot Id */ /* replace the child image in the appropriate place */ /* preserve existing error info */ /* clear the busy flag on the rest of hard disks */ * we have to try to discard the snapshot even if merging failed * because some images might have been already merged (and deleted) /* It is important to uninitialize and delete all snapshot's hard * disk attachments as they are no longer valid -- otherwise the * code in Machine::uninitDataAndChildObjects() will mistakenly * perform hard disk deassociation. */ // when we introduce clones later, discarding 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. /* currently, the parent snapshot must refer to the same machine */ /* mark the current state as modified */ * the first snapshot must have only one child when discarded, // if we implement some warning mechanism later, we'll have // to return a warning if the state file path cannot be deleted /* restore the merge error if any (ErrorInfo will be restored /* preserve existing error info */ /* restore the machine state */ * save settings anyway, since we've already changed the current true /* aInformCallbacksAnyway */);
/* set the result (this will try to fetch current error info on failure) */ * Discard current state task handler. * Must be called only by DiscardCurrentStateTask::handler()! * @note Locks mParent + this object for writing. * 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"));
/* Progress::notifyComplete() et al., saveSettings() need mParent lock */ * discard all current changes to mUserData (name, OSType etc.) * (note that the machine is powered off, so there is no need * to inform the direct session) * discard the saved state file if the machine was Saved prior * the "discard current snapshot and state" task is in action, * the current snapshot is not the last one. * Discard the current snapshot first. * the progress can be completed by a subtask only if there was /* remember the timestamp of the snapshot we're restoring from */ /* copy all hardware data from the current snapshot */ /* restore the attachmends from the snapshot */ /* here we can still safely rollback, so do it */ /* preserve existing error info */ * either from #saveSettings() or directly at the end /* should not have a saved state file associated at this point */ Bstr (
tr (
"Restoring the execution state")));
/* copy the state file */ tr (
"Could not copy the state file '%s' to '%s' (%Vrc)"),
* discard the current snapshot and state task is in action, * the current snapshot is the last one. * Discard the current snapshot after discarding the current state. /* commit changes to fixup hard disks before discarding */ * the progress can be completed by a subtask only if there * we've committed already, so inform callbacks anyway to ensure * they don't miss some change * we have already discarded the current state, so set the * execution state accordingly no matter of the discard snapshot result /* assign the timestamp from the snapshot */ /* mark the current state as not modified */ /* save all settings and commit */ /* preserve existing error info */ /* restore the machine state */ * save all settings and commit if still modified (there is no way to * rollback properly). Note that isModified() will return true after * copyFrom(). Also save the settings if requested by the subtask. true /* aInformCallbacksAnyway */);
/* set the result (this will try to fetch current error info on failure) */ * Helper to change the machine state (reimplementation). * @note Locks this object for writing. (
"oldMachineState=%d, aMachineState=%d\n",
/* detect some state transitions */ * the EMT thread is about to start, so mark attached HDDs as busy * and all its ancestors as being in use * the EMT thread stopped, so mark attached HDDs as no more busy * and remove the in-use flag from all its ancestors * delete the saved state file once the machine has finished * restoring from it (note that Console sets the state from * Restoring to Saved if the VM couldn't restore successfully, * to give the user an ability to fix an error and retry -- * we keep the saved state file in this case) * delete the saved state after Console::DiscardSavedState() is called * or if the VM process (owning a direct VM session) crashed while the // Not sure that deleting the saved state file just because of the // client death before it attempted to restore the VM is a good // thing. But when it crashes we need to go to the Aborted state // which cannot have the saved state file associated... The only // way to fix this is to make the Aborted condition not a VM state // but a bool flag: i.e., when a crash occurs, set it to true and // change the state to PoweredOff or Saved depending on the * set the current state modified flag to indicate that the * current state is no more identical to the state in the /* redirect to the underlying peer machine */ /* the machine has stopped execution * (or the saved state file was adopted) */ /* the saved state file was adopted */ * clear differencing hard disks based on immutable hard disks * once we've been shut down for any reason * Sends the current machine state value to the VM process. * @note Locks this object for reading, then calls a client process. /* directControl may be already set to NULL here in #OnSessionEnd() * called too early by the direct session process while there is still * some operation (like discarding the snapshot) in progress. The client * process in this case is waiting inside Session::close() for the * "end session" process object to complete, while #uninit() called by * #checkForDeath() on the Watcher thread is waiting for the pending * operation to complete. For now, we accept this inconsitent behavior * and simply do nothing here. */ // it's our responsibility to delete the task ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// /* set the proper type to indicate we're the SnapshotMachine instance */ * 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 attiching a copy * contains just references to original objects) /* create all other child objects that will be immutable private copies */ /* Confirm a successful initialization when it's the case */ * Initializes the SnapshotMachine object when loading from the settings file. * @param aMachine machine the snapshot belngs 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 */ /* 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 */ /* commit all changes made during the initialization */ /* 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 */ // util::Lockable interface //////////////////////////////////////////////////////////////////////////////// * Overrides VirtualBoxBase::lockHandle() in order to share the lock handle * with the primary Machine instance (mPeer). // public methods only for internal purposes //////////////////////////////////////////////////////////////////////////////// * Called by the snapshot object associated with this SnapshotMachine when * snapshot data such as name or description is changed. * @note Locks this object for writing.