MediumImpl.cpp revision 26d2a42f095ded346df2e41cc4837cb426b4753a
/* $Id$ */
/** @file
*
* VirtualBox COM class implementation
*/
/*
* Copyright (C) 2008 Sun Microsystems, Inc.
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* 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
* Clara, CA 95054 USA or visit http://www.sun.com if you need
* additional information or have any questions.
*/
#include "MediumImpl.h"
#include "VirtualBoxImpl.h"
#include "Logging.h"
////////////////////////////////////////////////////////////////////////////////
// MediumBase class
////////////////////////////////////////////////////////////////////////////////
// constructor / destructor
////////////////////////////////////////////////////////////////////////////////
// protected initializer/uninitializer for internal purposes only
////////////////////////////////////////////////////////////////////////////////
// IMedium properties
////////////////////////////////////////////////////////////////////////////////
{
if (!aId)
return E_POINTER;
AutoCaller autoCaller (this);
/* m.id is constant during life time, no need to lock */
return S_OK;
}
{
if (!aDescription)
return E_POINTER;
AutoCaller autoCaller (this);
AutoReadLock alock (this);
return S_OK;
}
{
if (!aDescription)
return E_INVALIDARG;
AutoCaller autoCaller (this);
AutoWriteLock alock (this);
/// @todo update m.description and save the global registry (and local
/// registries of portable VMs referring to this medium), this will also
/// require to add the mRegistered flag to data
return E_NOTIMPL;
}
{
if (!aState)
return E_POINTER;
AutoCaller autoCaller (this);
/* queryInfo() locks this for writing. */
AutoWriteLock alock (this);
switch (m.state)
{
case MediaState_Created:
case MediaState_Inaccessible:
case MediaState_LockedRead:
case MediaState_LockedWrite:
{
break;
}
default:
break;
}
return rc;
}
{
if (!aLocation)
return E_POINTER;
AutoCaller autoCaller (this);
AutoReadLock alock (this);
return S_OK;
}
{
if (!aLocation)
return E_INVALIDARG;
AutoCaller autoCaller (this);
AutoWriteLock alock (this);
/// @todo NEWMEDIA for file names, add the default extension if no extension
/// is present (using the information from the VD backend which also implies
/// that one more parameter should be passed to setLocation() requesting
/// that functionality since it is only allwed when called from this method
/// @todo NEWMEDIA rename the file and set m.location on success, then save
/// the global registry (and local registries of portable VMs referring to
/// this medium), this will also require to add the mRegistered flag to data
return E_NOTIMPL;
}
{
if (!aName)
return E_POINTER;
AutoCaller autoCaller (this);
AutoReadLock alock (this);
return S_OK;
}
{
if (!aSize)
return E_POINTER;
AutoCaller autoCaller (this);
AutoReadLock alock (this);
return S_OK;
}
{
if (!aLastAccessError)
return E_POINTER;
AutoCaller autoCaller (this);
AutoReadLock alock (this);
return S_OK;
}
{
return E_POINTER;
AutoCaller autoCaller (this);
AutoReadLock alock (this);
{
size_t i = 0;
{
}
}
return S_OK;
}
// IMedium methods
////////////////////////////////////////////////////////////////////////////////
{
return E_INVALIDARG;
return E_POINTER;
AutoCaller autoCaller (this);
AutoReadLock alock (this);
{
{
/* if the medium is attached to the machine in the current state, we
* return its ID as the first element of the array */
if (it->inCurState)
++ size;
if (size > 0)
{
size_t j = 0;
if (it->inCurState)
{
snapshotIds [j] = *jt;
}
}
break;
}
}
return S_OK;
}
/**
* @note @a aState may be NULL if the state value is not needed (only for
* in-process calls).
*/
{
AutoCaller autoCaller (this);
AutoWriteLock alock (this);
/* return the current state before */
if (aState)
switch (m.state)
{
case MediaState_Created:
case MediaState_Inaccessible:
case MediaState_LockedRead:
{
++ m.readers;
if (m.state == MediaState_Created)
m.accessibleInLock = true;
else if (m.state == MediaState_Inaccessible)
m.accessibleInLock = false;
break;
}
default:
{
rc = setStateError();
break;
}
}
return rc;
}
/**
* @note @a aState may be NULL if the state value is not needed (only for
* in-process calls).
*/
{
AutoCaller autoCaller (this);
AutoWriteLock alock (this);
switch (m.state)
{
case MediaState_LockedRead:
{
if (m.queryInfoSem == NIL_RTSEMEVENTMULTI)
{
-- m.readers;
/* Reset the state after the last reader */
if (m.readers == 0)
{
if (m.accessibleInLock)
m.state = MediaState_Created;
else
}
break;
}
/* otherwise, queryInfo() is in progress; fall through */
}
default:
{
tr ("Medium '%ls' is not locked for reading"),
m.locationFull.raw());
break;
}
}
/* return the current state after */
if (aState)
return rc;
}
/**
* @note @a aState may be NULL if the state value is not needed (only for
* in-process calls).
*/
{
AutoCaller autoCaller (this);
AutoWriteLock alock (this);
/* return the current state before */
if (aState)
switch (m.state)
{
case MediaState_Created:
case MediaState_Inaccessible:
{
if (m.state == MediaState_Created)
m.accessibleInLock = true;
else if (m.state == MediaState_Inaccessible)
m.accessibleInLock = false;
break;
}
default:
{
rc = setStateError();
break;
}
}
return rc;
}
/**
* @note @a aState may be NULL if the state value is not needed (only for
* in-process calls).
*/
{
AutoCaller autoCaller (this);
AutoWriteLock alock (this);
switch (m.state)
{
case MediaState_LockedWrite:
{
if (m.accessibleInLock)
m.state = MediaState_Created;
else
break;
}
default:
{
tr ("Medium '%ls' is not locked for writing"),
m.locationFull.raw());
break;
}
}
/* return the current state after */
if (aState)
return rc;
}
{
AutoMayUninitSpan mayUninitSpan (this);
if (mayUninitSpan.alreadyInProgress())
return S_OK;
/* unregisterWithVirtualBox() is assumed to always need a write mVirtualBox
* lock as it is intenede to modify its internal structires. Also, we want
* to unregister ourselves atomically after detecting that closure is
* possible to make sure that we don't do that after another thread has done
* VirtualBox::find*() but before it starts using us (provided that it holds
* a mVirtualBox lock of course). */
bool wasCreated = true;
switch (m.state)
{
case MediaState_NotCreated:
wasCreated = false;
break;
case MediaState_Created:
case MediaState_Inaccessible:
break;
default:
return setStateError();
}
tr ("Medium '%ls' is attached to %d virtual machines"),
/* perform extra media-dependent close checks */
if (wasCreated)
{
/* remove from the list of known media before performing actual
* uninitialization (to keep the media registry consistent on
* failure to do so) */
}
/* cause uninit() to happen on success */
return S_OK;
}
// public methods for internal purposes only
////////////////////////////////////////////////////////////////////////////////
/**
* Checks if the given change of \a aOldPath to \a aNewPath affects the location
* of this media and updates it if necessary to reflect the new location.
*
* @param aOldPath Old path (full).
* @param aNewPath New path (full).
*
* @note Locks this object for writing.
*/
{
AutoCaller autoCaller (this);
AutoWriteLock alock (this);
{
}
return S_OK;
}
/**
* Adds the given machine and optionally the snapshot to the list of the objects
* this image is attached to.
*
* @param aMachineId Machine ID.
* @param aSnapshotId Snapshot ID; when non-empty, adds a snapshot attachment.
*/
{
AutoCaller autoCaller (this);
AutoWriteLock alock (this);
switch (m.state)
{
case MediaState_Created:
case MediaState_Inaccessible:
case MediaState_LockedRead:
case MediaState_LockedWrite:
break;
default:
return setStateError();
}
{
return S_OK;
}
if (aSnapshotId.isEmpty())
{
/* sanity: no duplicate attachments */
it->inCurState = true;
return S_OK;
}
/* sanity: no duplicate attachments */
return S_OK;
}
/**
* Removes the given machine and optionally the snapshot from the list of the
* objects this image is attached to.
*
* @param aMachineId Machine ID.
* @param aSnapshotId Snapshot ID; when non-empty, removes the snapshot
* attachment.
*/
{
AutoCaller autoCaller (this);
AutoWriteLock alock (this);
if (aSnapshotId.isEmpty())
{
/* remove the current state attachment */
it->inCurState = false;
}
else
{
/* remove the snapshot attachment */
}
/* if the backref becomes empty, remove it */
return S_OK;
}
// protected methods
////////////////////////////////////////////////////////////////////////////////
/**
* Returns a short version of the location attribute.
*
* @note Must be called from under this object's read or write lock.
*/
{
return name;
}
/**
* Sets the value of m.location and calculates the value of m.locationFull.
*
* @param aLocation Path to the image file (can be relative to the
* VirtualBox home directory).
*
* @note Must be called from under this object's write lock.
*/
{
/* get the full file name */
if (RT_FAILURE (vrc))
tr ("Invalid image file location '%ls' (%Rrc)"),
m.locationFull = locationFull;
return S_OK;
}
/**
* Queries information from the image file.
*
* As a result of this call, the accessibility state and data members such as
* size and description will be updated with the current information.
*
* @note This method may block during a system I/O call that checks image file
* accessibility.
*
* @note Locks this object for writing.
*/
{
AutoWriteLock alock (this);
m.state == MediaState_Inaccessible ||
m.state == MediaState_LockedRead ||
m.state == MediaState_LockedWrite,
E_FAIL);
int vrc = VINF_SUCCESS;
/* check if a blocking queryInfo() call is in progress on some other thread,
* and wait for it to finish if so instead of querying data ourselves */
if (m.queryInfoSem != NIL_RTSEMEVENTMULTI)
{
++ m.queryInfoCallers;
-- m.queryInfoCallers;
if (m.queryInfoCallers == 0)
{
/* last waiting caller deletes the semaphore */
}
return S_OK;
}
/* lazily create a semaphore for possible callers */
bool tempStateSet = false;
if (m.state != MediaState_LockedRead &&
m.state != MediaState_LockedWrite)
{
/* Cause other methods to prevent any modifications before leaving the
* lock. Note that clients will never see this temporary state change
* directly since any COMGETTER(State) is (or will be) blocked until we
* finish and restore the actual state. This may be seen only through
* error messages reported by other methods. */
tempStateSet = true;
}
/* leave the lock before a blocking operation */
bool success = false;
/* get image file info */
{
if (RT_SUCCESS (vrc))
{
RTFileClose (file);
}
if (RT_FAILURE (vrc))
{
m.lastAccessError = Utf8StrFmt (
tr ("Could not access the image file '%ls' (%Rrc)"),
}
}
if (success)
m.lastAccessError.setNull();
/* inform other callers if there are any */
if (m.queryInfoCallers > 0)
{
}
else
{
/* delete the semaphore ourselves */
}
if (tempStateSet)
{
/* Set the proper state according to the result of the check */
if (success)
{
m.state = MediaState_Created;
}
else
{
LogWarningFunc (("'%ls' is not accessible ('%ls')\n",
}
}
else
{
/* we're locked, use a special field to store the result */
m.accessibleInLock = success;
}
return S_OK;
}
/**
* Sets the extended error info according to the current media state.
*
* @note Must be called from under this object's write or read lock.
*/
{
switch (m.state)
{
case MediaState_NotCreated:
{
tr ("Storage for the medium '%ls' is not created"),
m.locationFull.raw());
break;
}
case MediaState_Created:
{
tr ("Storage for the medium '%ls' is already created"),
m.locationFull.raw());
break;
}
case MediaState_LockedRead:
{
tr ("Medium '%ls' is locked for reading by another task"),
m.locationFull.raw());
break;
}
case MediaState_LockedWrite:
{
tr ("Medium '%ls' is locked for writing by another task"),
m.locationFull.raw());
break;
}
case MediaState_Inaccessible:
{
/* be in sync with Console::powerUpThread() */
if (!m.lastAccessError.isEmpty())
tr ("Medium '%ls' is not accessible. %ls"),
else
tr ("Medium '%ls' is not accessible"),
m.locationFull.raw());
break;
}
case MediaState_Creating:
{
tr ("Storage for the medium '%ls' is being created"),
break;
}
case MediaState_Deleting:
{
tr ("Storage for the medium '%ls' is being deleted"),
break;
}
default:
{
AssertFailed();
break;
}
}
return rc;
}
////////////////////////////////////////////////////////////////////////////////
// ImageMediumBase class
////////////////////////////////////////////////////////////////////////////////
/**
* Initializes the image medium object by opening an image file at the specified
* location.
*
* @param aVirtualBox Parent VirtualBox object.
* @param aLocation Path to the image file (can be relative to the
* VirtualBox home directory).
* @param aId UUID of the image.
*/
{
/* Enclose the state transition NotReady->InInit->Ready */
AutoInitSpan autoInitSpan (this);
/* share parent weakly */
/* register with parent early, since uninit() will unconditionally
* unregister on failure */
mVirtualBox->addDependentChild (this);
/* there must be a storage unit */
m.state = MediaState_Created;
/* get all the information about the medium from the file */
{
/* if the image file is not accessible, it's not acceptable for the
* newly opened media so convert this into an error */
if (!m.lastAccessError.isNull())
}
/* Confirm a successful initialization when it's the case */
return rc;
}
/**
* Initializes the image medium object by loading its data from the given
* settings node.
*
* Note that it is assumed that this method is called only for registered media.
*
* @param aVirtualBox Parent VirtualBox object.
* @param aImageNode Either <DVDImage> or <FloppyImage> settings node.
*/
{
/* Enclose the state transition NotReady->InInit->Ready */
AutoInitSpan autoInitSpan (this);
/* share parent weakly */
/* register with parent early, since uninit() will unconditionally
* unregister on failure */
mVirtualBox->addDependentChild (this);
/* see below why we don't call queryInfo() (and therefore treat the medium
* as inaccessible for now */
/* required */
/* required */
/* optional */
{
}
LogFlowThisFunc (("m.location='%ls', m.id={%RTuuid}\n",
/* Don't call queryInfo() for registered media to prevent the calling
* thread (i.e. the VirtualBox server startup thread) from an unexpected
* freeze but mark it as initially inaccessible instead. The vital UUID and
* location properties are read from the registry file above; to get the
* actual state and the rest of the data, the user will have to call
* COMGETTER(State).*/
/* Confirm a successful initialization when it's the case */
return rc;
}
/**
* Uninitializes the instance.
*
* Called either from FinalRelease() or by the parent when it gets destroyed.
*/
void ImageMediumBase::protectedUninit()
{
LogFlowThisFunc (("\n"));
/* Enclose the state transition Ready->InUninit->NotReady */
AutoUninitSpan autoUninitSpan (this);
if (autoUninitSpan.uninitDone())
return;
mVirtualBox->removeDependentChild (this);
}
// public methods for internal purposes only
////////////////////////////////////////////////////////////////////////////////
/**
* Saves image data by appending a new <Image> child node to the
* given <Images> parent node.
*
* @param aImagesNode <Images> node.
*
* @note Locks this object for reading.
*/
{
using namespace settings;
AutoCaller autoCaller (this);
AutoReadLock alock (this);
/* required */
/* required */
/* optional */
if (!m.description.isNull())
{
}
return S_OK;
}
////////////////////////////////////////////////////////////////////////////////
// DVDImage2 class
////////////////////////////////////////////////////////////////////////////////
/**
* @note Called from within this object's AutoMayUninitSpan and from under
* mVirtualBox write lock.
*/
{
return mVirtualBox->unregisterDVDImage (this);
}
////////////////////////////////////////////////////////////////////////////////
// FloppyImage2 class
////////////////////////////////////////////////////////////////////////////////
/**
* @note Called from within this object's AutoMayUninitSpan and from under
* mVirtualBox write lock.
*/
{
return mVirtualBox->unregisterFloppyImage (this);
}