summaryrefslogtreecommitdiffstats
path: root/src/VBox/Main/glue/vboxapi.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Main/glue/vboxapi.py')
-rwxr-xr-xsrc/VBox/Main/glue/vboxapi.py1294
1 files changed, 1294 insertions, 0 deletions
diff --git a/src/VBox/Main/glue/vboxapi.py b/src/VBox/Main/glue/vboxapi.py
new file mode 100755
index 00000000..fe78ce62
--- /dev/null
+++ b/src/VBox/Main/glue/vboxapi.py
@@ -0,0 +1,1294 @@
+# -*- coding: utf-8 -*-
+# $Id: vboxapi.py $
+"""
+VirtualBox Python API Glue.
+"""
+
+__copyright__ = \
+"""
+Copyright (C) 2009-2023 Oracle and/or its affiliates.
+
+This file is part of VirtualBox base platform packages, as
+available from https://www.virtualbox.org.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, in version 3 of the
+License.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, see <https://www.gnu.org/licenses>.
+
+The contents of this file may alternatively be used under the terms
+of the Common Development and Distribution License Version 1.0
+(CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+in the VirtualBox distribution, in which case the provisions of the
+CDDL are applicable instead of those of the GPL.
+
+You may elect to license modified versions of this file under the
+terms and conditions of either the GPL or the CDDL or both.
+
+SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+"""
+__version__ = "$Revision: 155244 $"
+
+
+# Note! To set Python bitness on OSX use 'export VERSIONER_PYTHON_PREFER_32_BIT=yes'
+
+
+# Standard Python imports.
+import os
+import sys
+import traceback
+
+
+if sys.version_info >= (3, 0):
+ xrange = range
+ long = int
+
+#
+# Globals, environment and sys.path changes.
+#
+import platform
+VBoxBinDir = os.environ.get("VBOX_PROGRAM_PATH", None)
+VBoxSdkDir = os.environ.get("VBOX_SDK_PATH", None)
+
+if VBoxBinDir is None:
+ if platform.system() == 'Darwin':
+ VBoxBinDir = '/Applications/VirtualBox.app/Contents/MacOS'
+ else: # Will be set by the installer
+ VBoxBinDir = "%VBOX_INSTALL_PATH%"
+else:
+ VBoxBinDir = os.path.abspath(VBoxBinDir)
+
+if VBoxSdkDir is None:
+ if platform.system() == 'Darwin':
+ VBoxSdkDir = '/Applications/VirtualBox.app/Contents/MacOS/sdk'
+ else: # Will be set by the installer
+ VBoxSdkDir = "%VBOX_SDK_PATH%"
+else:
+ VBoxSdkDir = os.path.abspath(VBoxSdkDir)
+
+os.environ["VBOX_PROGRAM_PATH"] = VBoxBinDir
+os.environ["VBOX_SDK_PATH"] = VBoxSdkDir
+sys.path.append(VBoxBinDir)
+
+
+#
+# Import the generated VirtualBox constants.
+#
+from .VirtualBox_constants import VirtualBoxReflectionInfo
+
+
+class PerfCollector(object):
+ """ This class provides a wrapper over IPerformanceCollector in order to
+ get more 'pythonic' interface.
+
+ To begin collection of metrics use setup() method.
+
+ To get collected data use query() method.
+
+ It is possible to disable metric collection without changing collection
+ parameters with disable() method. The enable() method resumes metric
+ collection.
+ """
+
+ def __init__(self, mgr, vbox):
+ """ Initializes the instance.
+
+ """
+ self.mgr = mgr
+ self.isMscom = (mgr.type == 'MSCOM')
+ self.collector = vbox.performanceCollector
+
+ def setup(self, names, objects, period, nsamples):
+ """ Discards all previously collected values for the specified
+ metrics, sets the period of collection and the number of retained
+ samples, enables collection.
+ """
+ self.collector.setupMetrics(names, objects, period, nsamples)
+
+ def enable(self, names, objects):
+ """ Resumes metric collection for the specified metrics.
+ """
+ self.collector.enableMetrics(names, objects)
+
+ def disable(self, names, objects):
+ """ Suspends metric collection for the specified metrics.
+ """
+ self.collector.disableMetrics(names, objects)
+
+ def query(self, names, objects):
+ """ Retrieves collected metric values as well as some auxiliary
+ information. Returns an array of dictionaries, one dictionary per
+ metric. Each dictionary contains the following entries:
+ 'name': metric name
+ 'object': managed object this metric associated with
+ 'unit': unit of measurement
+ 'scale': divide 'values' by this number to get float numbers
+ 'values': collected data
+ 'values_as_string': pre-processed values ready for 'print' statement
+ """
+ # Get around the problem with input arrays returned in output
+ # parameters (see #3953) for MSCOM.
+ if self.isMscom:
+ (values, names, objects, names_out, objects_out, units, scales, sequence_numbers,
+ indices, lengths) = self.collector.queryMetricsData(names, objects)
+ else:
+ (values, names_out, objects_out, units, scales, sequence_numbers,
+ indices, lengths) = self.collector.queryMetricsData(names, objects)
+ out = []
+ for i in xrange(0, len(names_out)):
+ scale = int(scales[i])
+ if scale != 1:
+ fmt = '%.2f%s'
+ else:
+ fmt = '%d %s'
+ out.append({
+ 'name': str(names_out[i]),
+ 'object': str(objects_out[i]),
+ 'unit': str(units[i]),
+ 'scale': scale,
+ 'values': [int(values[j]) for j in xrange(int(indices[i]), int(indices[i]) + int(lengths[i]))],
+ 'values_as_string': '[' + ', '.join([fmt % (int(values[j]) / scale, units[i]) for j in
+ xrange(int(indices[i]), int(indices[i]) + int(lengths[i]))]) + ']'
+ })
+ return out
+
+
+#
+# Attribute hacks.
+#
+def ComifyName(name):
+ return name[0].capitalize() + name[1:]
+
+
+## This is for saving the original DispatchBaseClass __getattr__ and __setattr__
+# method references.
+_g_dCOMForward = {
+ 'getattr': None,
+ 'setattr': None,
+}
+
+
+def _CustomGetAttr(self, sAttr):
+ """ Our getattr replacement for DispatchBaseClass. """
+ # Fastpath.
+ oRet = self.__class__.__dict__.get(sAttr)
+ if oRet is not None:
+ return oRet
+
+ # Try case-insensitivity workaround for class attributes (COM methods).
+ sAttrLower = sAttr.lower()
+ for k in list(self.__class__.__dict__.keys()):
+ if k.lower() == sAttrLower:
+ setattr(self.__class__, sAttr, self.__class__.__dict__[k])
+ return getattr(self, k)
+
+ # Slow path.
+ try:
+ return _g_dCOMForward['getattr'](self, ComifyName(sAttr))
+ except AttributeError:
+ return _g_dCOMForward['getattr'](self, sAttr)
+
+
+def _CustomSetAttr(self, sAttr, oValue):
+ """ Our setattr replacement for DispatchBaseClass. """
+ try:
+ return _g_dCOMForward['setattr'](self, ComifyName(sAttr), oValue)
+ except AttributeError:
+ return _g_dCOMForward['setattr'](self, sAttr, oValue)
+
+
+class PlatformBase(object):
+ """
+ Base class for the platform specific code.
+ """
+
+ def __init__(self, aoParams):
+ _ = aoParams
+
+ def getVirtualBox(self):
+ """
+ Gets a the IVirtualBox singleton.
+ """
+ return None
+
+ def getSessionObject(self):
+ """
+ Get a session object that can be used for opening machine sessions.
+
+ The oIVBox parameter is an getVirtualBox() return value, i.e. an
+ IVirtualBox reference.
+
+ See also openMachineSession.
+ """
+ return None
+
+ def getType(self):
+ """ Returns the platform type (class name sans 'Platform'). """
+ return None
+
+ def isRemote(self):
+ """
+ Returns True if remote (web services) and False if local (COM/XPCOM).
+ """
+ return False
+
+ def getArray(self, oInterface, sAttrib):
+ """
+ Retrives the value of the array attribute 'sAttrib' from
+ interface 'oInterface'.
+
+ This is for hiding platform specific differences in attributes
+ returning arrays.
+ """
+ _ = oInterface
+ _ = sAttrib
+ return None
+
+ def setArray(self, oInterface, sAttrib, aoArray):
+ """
+ Sets the value (aoArray) of the array attribute 'sAttrib' in
+ interface 'oInterface'.
+
+ This is for hiding platform specific differences in attributes
+ setting arrays.
+ """
+ _ = oInterface
+ _ = sAttrib
+ _ = aoArray
+ return None
+
+ def initPerThread(self):
+ """
+ Does backend specific initialization for the calling thread.
+ """
+ return True
+
+ def deinitPerThread(self):
+ """
+ Does backend specific uninitialization for the calling thread.
+ """
+ return True
+
+ def createListener(self, oImplClass, dArgs):
+ """
+ Instantiates and wraps an active event listener class so it can be
+ passed to an event source for registration.
+
+ oImplClass is a class (type, not instance) which implements
+ IEventListener.
+
+ dArgs is a dictionary with string indexed variables. This may be
+ modified by the method to pass platform specific parameters. Can
+ be None.
+
+ This currently only works on XPCOM. COM support is not possible due to
+ shortcuts taken in the COM bridge code, which is not under our control.
+ Use passive listeners for COM and web services.
+ """
+ _ = oImplClass
+ _ = dArgs
+ raise Exception("No active listeners for this platform")
+
+ def waitForEvents(self, cMsTimeout):
+ """
+ Wait for events to arrive and process them.
+
+ The timeout (cMsTimeout) is in milliseconds for how long to wait for
+ events to arrive. A negative value means waiting for ever, while 0
+ does not wait at all.
+
+ Returns 0 if events was processed.
+ Returns 1 if timed out or interrupted in some way.
+ Returns 2 on error (like not supported for web services).
+
+ Raises an exception if the calling thread is not the main thread (the one
+ that initialized VirtualBoxManager) or if the time isn't an integer.
+ """
+ _ = cMsTimeout
+ return 2
+
+ def interruptWaitEvents(self):
+ """
+ Interrupt a waitForEvents call.
+ This is normally called from a worker thread to wake up the main thread.
+
+ Returns True on success, False on failure.
+ """
+ return False
+
+ def deinit(self):
+ """
+ Unitializes the platform specific backend.
+ """
+ return None
+
+ def queryInterface(self, oIUnknown, sClassName):
+ """
+ IUnknown::QueryInterface wrapper.
+
+ oIUnknown is who to ask.
+ sClassName is the name of the interface we're asking for.
+ """
+ return None
+
+ #
+ # Error (exception) access methods.
+ #
+
+ def xcptGetStatus(self, oXcpt):
+ """
+ Returns the COM status code from the VBox API given exception.
+ """
+ return None
+
+ def xcptIsDeadInterface(self, oXcpt):
+ """
+ Returns True if the exception indicates that the interface is dead, False if not.
+ """
+ return False
+
+ def xcptIsEqual(self, oXcpt, hrStatus):
+ """
+ Checks if the exception oXcpt is equal to the COM/XPCOM status code
+ hrStatus.
+
+ The oXcpt parameter can be any kind of object, we'll just return True
+ if it doesn't behave like a our exception class.
+
+ Will not raise any exception as long as hrStatus and self are not bad.
+ """
+ try:
+ hrXcpt = self.xcptGetStatus(oXcpt)
+ except AttributeError:
+ return False
+ if hrXcpt == hrStatus:
+ return True
+
+ # Fudge for 32-bit signed int conversion.
+ if 0x7fffffff < hrStatus <= 0xffffffff and hrXcpt < 0:
+ if (hrStatus - 0x100000000) == hrXcpt:
+ return True
+ return False
+
+ def xcptGetMessage(self, oXcpt):
+ """
+ Returns the best error message found in the COM-like exception.
+ Returns None to fall back on xcptToString.
+ Raises exception if oXcpt isn't our kind of exception object.
+ """
+ return None
+
+ def xcptGetBaseXcpt(self):
+ """
+ Returns the base exception class.
+ """
+ return None
+
+ def xcptSetupConstants(self, oDst):
+ """
+ Copy/whatever all error constants onto oDst.
+ """
+ return oDst
+
+ @staticmethod
+ def xcptCopyErrorConstants(oDst, oSrc):
+ """
+ Copy everything that looks like error constants from oDst to oSrc.
+ """
+ for sAttr in dir(oSrc):
+ if sAttr[0].isupper() and (sAttr[1].isupper() or sAttr[1] == '_'):
+ oAttr = getattr(oSrc, sAttr)
+ if type(oAttr) is int:
+ setattr(oDst, sAttr, oAttr)
+ return oDst
+
+
+class PlatformMSCOM(PlatformBase):
+ """
+ Platform specific code for MS COM.
+ """
+
+ ## @name VirtualBox COM Typelib definitions (should be generate)
+ #
+ # @remarks Must be updated when the corresponding VirtualBox.xidl bits
+ # are changed. Fortunately this isn't very often.
+ # @{
+ VBOX_TLB_GUID = '{D7569351-1750-46F0-936E-BD127D5BC264}'
+ VBOX_TLB_LCID = 0
+ VBOX_TLB_MAJOR = 1
+ VBOX_TLB_MINOR = 3
+ ## @}
+
+ def __init__(self, dParams):
+ PlatformBase.__init__(self, dParams)
+
+ #
+ # Since the code runs on all platforms, we have to do a lot of
+ # importing here instead of at the top of the file where it's normally located.
+ #
+ from win32com import universal
+ from win32com.client import gencache, DispatchBaseClass
+ from win32com.client import constants, getevents
+ import win32com
+ import pythoncom
+ import win32api
+ import winerror
+ from win32con import DUPLICATE_SAME_ACCESS
+ from win32api import GetCurrentThread, GetCurrentThreadId, DuplicateHandle, GetCurrentProcess
+ import threading
+
+ self.winerror = winerror
+
+ # Setup client impersonation in COM calls.
+ try:
+ pythoncom.CoInitializeSecurity(None,
+ None,
+ None,
+ pythoncom.RPC_C_AUTHN_LEVEL_DEFAULT,
+ pythoncom.RPC_C_IMP_LEVEL_IMPERSONATE,
+ None,
+ pythoncom.EOAC_NONE,
+ None)
+ except:
+ _, oXcpt, _ = sys.exc_info();
+ if isinstance(oXcpt, pythoncom.com_error) and self.xcptGetStatus(oXcpt) == -2147417831: # RPC_E_TOO_LATE
+ print("Warning: CoInitializeSecurity was already called");
+ else:
+ print("Warning: CoInitializeSecurity failed: ", oXcpt);
+
+ # Remember this thread ID and get its handle so we can wait on it in waitForEvents().
+ self.tid = GetCurrentThreadId()
+ pid = GetCurrentProcess()
+ self.aoHandles = [DuplicateHandle(pid, GetCurrentThread(), pid, 0, 0, DUPLICATE_SAME_ACCESS),] # type: list[PyHANDLE]
+
+ # Hack the COM dispatcher base class so we can modify method and
+ # attribute names to match those in xpcom.
+ if _g_dCOMForward['setattr'] is None:
+ _g_dCOMForward['getattr'] = DispatchBaseClass.__dict__['__getattr__']
+ _g_dCOMForward['setattr'] = DispatchBaseClass.__dict__['__setattr__']
+ setattr(DispatchBaseClass, '__getattr__', _CustomGetAttr)
+ setattr(DispatchBaseClass, '__setattr__', _CustomSetAttr)
+
+ # Hack the exception base class so the users doesn't need to check for
+ # XPCOM or COM and do different things.
+ ## @todo
+
+ #
+ # Make sure the gencache is correct (we don't quite follow the COM
+ # versioning rules).
+ #
+ self.flushGenPyCache(win32com.client.gencache)
+ win32com.client.gencache.EnsureDispatch('VirtualBox.Session')
+ win32com.client.gencache.EnsureDispatch('VirtualBox.VirtualBox')
+ win32com.client.gencache.EnsureDispatch('VirtualBox.VirtualBoxClient')
+
+ self.oClient = None ##< instance of client used to support lifetime of VBoxSDS
+ self.oIntCv = threading.Condition()
+ self.fInterrupted = False
+
+ _ = dParams
+
+ def flushGenPyCache(self, oGenCache):
+ """
+ Flushes VBox related files in the win32com gen_py cache.
+
+ This is necessary since we don't follow the typelib versioning rules
+ that everyeone else seems to subscribe to.
+ """
+ #
+ # The EnsureModule method have broken validation code, it doesn't take
+ # typelib module directories into account. So we brute force them here.
+ # (It's possible the directory approach is from some older pywin
+ # version or the result of runnig makepy or gencache manually, but we
+ # need to cover it as well.)
+ #
+ sName = oGenCache.GetGeneratedFileName(self.VBOX_TLB_GUID, self.VBOX_TLB_LCID,
+ self.VBOX_TLB_MAJOR, self.VBOX_TLB_MINOR)
+ sGenPath = oGenCache.GetGeneratePath()
+ if len(sName) > 36 and len(sGenPath) > 5:
+ sTypelibPath = os.path.join(sGenPath, sName)
+ if os.path.isdir(sTypelibPath):
+ import shutil
+ shutil.rmtree(sTypelibPath, ignore_errors=True)
+
+ #
+ # Ensure that our typelib is valid.
+ #
+ return oGenCache.EnsureModule(self.VBOX_TLB_GUID, self.VBOX_TLB_LCID, self.VBOX_TLB_MAJOR, self.VBOX_TLB_MINOR)
+
+ def getSessionObject(self):
+ import win32com
+ from win32com.client import Dispatch
+ return win32com.client.Dispatch("VirtualBox.Session")
+
+ def getVirtualBox(self):
+ # Caching self.oClient is the trick for SDS. It allows to keep the
+ # VBoxSDS in the memory until the end of PlatformMSCOM lifetme.
+ if self.oClient is None:
+ import win32com
+ from win32com.client import Dispatch
+ self.oClient = win32com.client.Dispatch("VirtualBox.VirtualBoxClient")
+ return self.oClient.virtualBox
+
+ def getType(self):
+ return 'MSCOM'
+
+ def getArray(self, oInterface, sAttrib):
+ return oInterface.__getattr__(sAttrib)
+
+ def setArray(self, oInterface, sAttrib, aoArray):
+ #
+ # HACK ALERT!
+ #
+ # With pywin32 build 218, we're seeing type mismatch errors here for
+ # IGuestSession::environmentChanges (safearray of BSTRs). The Dispatch
+ # object (_oleobj_) seems to get some type conversion wrong and COM
+ # gets upset. So, we redo some of the dispatcher work here, picking
+ # the missing type information from the getter.
+ #
+ oOleObj = getattr(oInterface, '_oleobj_')
+ aPropMapGet = getattr(oInterface, '_prop_map_get_')
+ aPropMapPut = getattr(oInterface, '_prop_map_put_')
+ sComAttrib = sAttrib if sAttrib in aPropMapGet else ComifyName(sAttrib)
+ try:
+ aArgs, aDefaultArgs = aPropMapPut[sComAttrib]
+ aGetArgs = aPropMapGet[sComAttrib]
+ except KeyError: # fallback.
+ return oInterface.__setattr__(sAttrib, aoArray)
+
+ import pythoncom
+ oOleObj.InvokeTypes(aArgs[0], # dispid
+ aArgs[1], # LCID
+ aArgs[2], # DISPATCH_PROPERTYPUT
+ (pythoncom.VT_HRESULT, 0), # retType - or void?
+ (aGetArgs[2],), # argTypes - trick: we get the type from the getter.
+ aoArray,) # The array
+
+ def initPerThread(self):
+ import pythoncom
+ pythoncom.CoInitializeEx(0)
+
+ def deinitPerThread(self):
+ import pythoncom
+ pythoncom.CoUninitialize()
+
+ def createListener(self, oImplClass, dArgs):
+ if True:
+ raise Exception('no active listeners on Windows as PyGatewayBase::QueryInterface() '
+ 'returns new gateway objects all the time, thus breaking EventQueue '
+ 'assumptions about the listener interface pointer being constants between calls ')
+ # Did this code ever really work?
+ d = {}
+ d['BaseClass'] = oImplClass
+ d['dArgs'] = dArgs
+ d['tlb_guid'] = PlatformMSCOM.VBOX_TLB_GUID
+ d['tlb_major'] = PlatformMSCOM.VBOX_TLB_MAJOR
+ d['tlb_minor'] = PlatformMSCOM.VBOX_TLB_MINOR
+ str_ = ""
+ str_ += "import win32com.server.util\n"
+ str_ += "import pythoncom\n"
+
+ str_ += "class ListenerImpl(BaseClass):\n"
+ str_ += " _com_interfaces_ = ['IEventListener']\n"
+ str_ += " _typelib_guid_ = tlb_guid\n"
+ str_ += " _typelib_version_ = tlb_major, tlb_minor\n"
+ str_ += " _reg_clsctx_ = pythoncom.CLSCTX_INPROC_SERVER\n"
+ # Maybe we'd better implement Dynamic invoke policy, to be more flexible here
+ str_ += " _reg_policy_spec_ = 'win32com.server.policy.EventHandlerPolicy'\n"
+
+ # capitalized version of listener method
+ str_ += " HandleEvent=BaseClass.handleEvent\n"
+ str_ += " def __init__(self): BaseClass.__init__(self, dArgs)\n"
+ str_ += "result = win32com.server.util.wrap(ListenerImpl())\n"
+ exec(str_, d, d)
+ return d['result']
+
+ def waitForEvents(self, timeout):
+ from win32api import GetCurrentThreadId
+ from win32event import INFINITE
+ from win32event import MsgWaitForMultipleObjects, QS_ALLINPUT, WAIT_TIMEOUT, WAIT_OBJECT_0
+ from pythoncom import PumpWaitingMessages
+ import types
+
+ if not isinstance(timeout, int):
+ raise TypeError("The timeout argument is not an integer")
+ if self.tid != GetCurrentThreadId():
+ raise Exception("wait for events from the same thread you inited!")
+
+ if timeout < 0:
+ cMsTimeout = INFINITE
+ else:
+ cMsTimeout = timeout
+ rc = MsgWaitForMultipleObjects(self.aoHandles, 0, cMsTimeout, QS_ALLINPUT)
+ if WAIT_OBJECT_0 <= rc < WAIT_OBJECT_0 + len(self.aoHandles):
+ # is it possible?
+ rc = 2
+ elif rc == WAIT_OBJECT_0 + len(self.aoHandles):
+ # Waiting messages
+ PumpWaitingMessages()
+ rc = 0
+ else:
+ # Timeout
+ rc = 1
+
+ # check for interruption
+ self.oIntCv.acquire()
+ if self.fInterrupted:
+ self.fInterrupted = False
+ rc = 1
+ self.oIntCv.release()
+
+ return rc
+
+ def interruptWaitEvents(self):
+ """
+ Basically a python implementation of NativeEventQueue::postEvent().
+
+ The magic value must be in sync with the C++ implementation or this
+ won't work.
+
+ Note that because of this method we cannot easily make use of a
+ non-visible Window to handle the message like we would like to do.
+ """
+ from win32api import PostThreadMessage
+ from win32con import WM_USER
+
+ self.oIntCv.acquire()
+ self.fInterrupted = True
+ self.oIntCv.release()
+ try:
+ PostThreadMessage(self.tid, WM_USER, None, 0xf241b819)
+ except:
+ return False
+ return True
+
+ def deinit(self):
+ for oHandle in self.aoHandles:
+ if oHandle is not None:
+ oHandle.Close();
+ self.oHandle = None;
+
+ del self.oClient;
+ self.oClient = None;
+
+ # This non-sense doesn't pair up with any pythoncom.CoInitialize[Ex].
+ # See @bugref{9037}.
+ #import pythoncom
+ #pythoncom.CoUninitialize()
+
+ def queryInterface(self, oIUnknown, sClassName):
+ from win32com.client import CastTo
+ return CastTo(oIUnknown, sClassName)
+
+ def xcptGetStatus(self, oXcpt):
+ # The DISP_E_EXCEPTION + excptinfo fun needs checking up, only
+ # empirical info on it so far.
+ hrXcpt = oXcpt.hresult
+ if hrXcpt == self.winerror.DISP_E_EXCEPTION:
+ try:
+ hrXcpt = oXcpt.excepinfo[5]
+ except:
+ pass
+ return hrXcpt
+
+ def xcptIsDeadInterface(self, oXcpt):
+ return self.xcptGetStatus(oXcpt) in [
+ 0x800706ba, -2147023174, # RPC_S_SERVER_UNAVAILABLE.
+ 0x800706be, -2147023170, # RPC_S_CALL_FAILED.
+ 0x800706bf, -2147023169, # RPC_S_CALL_FAILED_DNE.
+ 0x80010108, -2147417848, # RPC_E_DISCONNECTED.
+ 0x800706b5, -2147023179, # RPC_S_UNKNOWN_IF
+ ]
+
+ def xcptGetMessage(self, oXcpt):
+ if hasattr(oXcpt, 'excepinfo'):
+ try:
+ if len(oXcpt.excepinfo) >= 3:
+ sRet = oXcpt.excepinfo[2]
+ if len(sRet) > 0:
+ return sRet[0:]
+ except:
+ pass
+ if hasattr(oXcpt, 'strerror'):
+ try:
+ sRet = oXcpt.strerror
+ if len(sRet) > 0:
+ return sRet
+ except:
+ pass
+ return None
+
+ def xcptGetBaseXcpt(self):
+ import pythoncom
+
+ return pythoncom.com_error
+
+ def xcptSetupConstants(self, oDst):
+ import winerror
+
+ oDst = self.xcptCopyErrorConstants(oDst, winerror)
+
+ # XPCOM compatability constants.
+ oDst.NS_OK = oDst.S_OK
+ oDst.NS_ERROR_FAILURE = oDst.E_FAIL
+ oDst.NS_ERROR_ABORT = oDst.E_ABORT
+ oDst.NS_ERROR_NULL_POINTER = oDst.E_POINTER
+ oDst.NS_ERROR_NO_INTERFACE = oDst.E_NOINTERFACE
+ oDst.NS_ERROR_INVALID_ARG = oDst.E_INVALIDARG
+ oDst.NS_ERROR_OUT_OF_MEMORY = oDst.E_OUTOFMEMORY
+ oDst.NS_ERROR_NOT_IMPLEMENTED = oDst.E_NOTIMPL
+ oDst.NS_ERROR_UNEXPECTED = oDst.E_UNEXPECTED
+ return oDst
+
+
+class PlatformXPCOM(PlatformBase):
+ """
+ Platform specific code for XPCOM.
+ """
+
+ def __init__(self, dParams):
+ PlatformBase.__init__(self, dParams)
+ sys.path.append(VBoxSdkDir + '/bindings/xpcom/python/')
+ import xpcom.vboxxpcom
+ import xpcom
+ import xpcom.components
+ _ = dParams
+
+ def getSessionObject(self):
+ import xpcom.components
+ return xpcom.components.classes["@virtualbox.org/Session;1"].createInstance()
+
+ def getVirtualBox(self):
+ import xpcom.components
+ client = xpcom.components.classes["@virtualbox.org/VirtualBoxClient;1"].createInstance()
+ return client.virtualBox
+
+ def getType(self):
+ return 'XPCOM'
+
+ def getArray(self, oInterface, sAttrib):
+ return oInterface.__getattr__('get' + ComifyName(sAttrib))()
+
+ def setArray(self, oInterface, sAttrib, aoArray):
+ return oInterface.__getattr__('set' + ComifyName(sAttrib))(aoArray)
+
+ def initPerThread(self):
+ import xpcom
+ xpcom._xpcom.AttachThread()
+
+ def deinitPerThread(self):
+ import xpcom
+ xpcom._xpcom.DetachThread()
+
+ def createListener(self, oImplClass, dArgs):
+ d = {}
+ d['BaseClass'] = oImplClass
+ d['dArgs'] = dArgs
+ str = ""
+ str += "import xpcom.components\n"
+ str += "class ListenerImpl(BaseClass):\n"
+ str += " _com_interfaces_ = xpcom.components.interfaces.IEventListener\n"
+ str += " def __init__(self): BaseClass.__init__(self, dArgs)\n"
+ str += "result = ListenerImpl()\n"
+ exec(str, d, d)
+ return d['result']
+
+ def waitForEvents(self, timeout):
+ import xpcom
+ return xpcom._xpcom.WaitForEvents(timeout)
+
+ def interruptWaitEvents(self):
+ import xpcom
+ return xpcom._xpcom.InterruptWait()
+
+ def deinit(self):
+ import xpcom
+ xpcom._xpcom.DeinitCOM()
+
+ def queryInterface(self, oIUnknown, sClassName):
+ import xpcom.components
+ return oIUnknown.queryInterface(getattr(xpcom.components.interfaces, sClassName))
+
+ def xcptGetStatus(self, oXcpt):
+ return oXcpt.errno
+
+ def xcptIsDeadInterface(self, oXcpt):
+ return self.xcptGetStatus(oXcpt) in [
+ 0x80004004, -2147467260, # NS_ERROR_ABORT
+ 0x800706be, -2147023170, # NS_ERROR_CALL_FAILED (RPC_S_CALL_FAILED)
+ ]
+
+ def xcptGetMessage(self, oXcpt):
+ if hasattr(oXcpt, 'msg'):
+ try:
+ sRet = oXcpt.msg
+ if len(sRet) > 0:
+ return sRet
+ except:
+ pass
+ return None
+
+ def xcptGetBaseXcpt(self):
+ import xpcom
+ return xpcom.Exception
+
+ def xcptSetupConstants(self, oDst):
+ import xpcom
+ oDst = self.xcptCopyErrorConstants(oDst, xpcom.nsError)
+
+ # COM compatability constants.
+ oDst.E_ACCESSDENIED = -2147024891 # see VBox/com/defs.h
+ oDst.S_OK = oDst.NS_OK
+ oDst.E_FAIL = oDst.NS_ERROR_FAILURE
+ oDst.E_ABORT = oDst.NS_ERROR_ABORT
+ oDst.E_POINTER = oDst.NS_ERROR_NULL_POINTER
+ oDst.E_NOINTERFACE = oDst.NS_ERROR_NO_INTERFACE
+ oDst.E_INVALIDARG = oDst.NS_ERROR_INVALID_ARG
+ oDst.E_OUTOFMEMORY = oDst.NS_ERROR_OUT_OF_MEMORY
+ oDst.E_NOTIMPL = oDst.NS_ERROR_NOT_IMPLEMENTED
+ oDst.E_UNEXPECTED = oDst.NS_ERROR_UNEXPECTED
+ oDst.DISP_E_EXCEPTION = -2147352567 # For COM compatability only.
+ return oDst
+
+
+class PlatformWEBSERVICE(PlatformBase):
+ """
+ VirtualBox Web Services API specific code.
+ """
+
+ def __init__(self, dParams):
+ PlatformBase.__init__(self, dParams)
+ # Import web services stuff. Fix the sys.path the first time.
+ sWebServLib = os.path.join(VBoxSdkDir, 'bindings', 'webservice', 'python', 'lib')
+ if sWebServLib not in sys.path:
+ sys.path.append(sWebServLib)
+ import VirtualBox_wrappers
+ from VirtualBox_wrappers import IWebsessionManager2
+
+ # Initialize instance variables from parameters.
+ if dParams is not None:
+ self.user = dParams.get("user", "")
+ self.password = dParams.get("password", "")
+ self.url = dParams.get("url", "")
+ else:
+ self.user = ""
+ self.password = ""
+ self.url = None
+ self.vbox = None
+ self.wsmgr = None
+
+ #
+ # Base class overrides.
+ #
+
+ def getSessionObject(self):
+ return self.wsmgr.getSessionObject(self.vbox)
+
+ def getVirtualBox(self):
+ return self.connect(self.url, self.user, self.password)
+
+ def getType(self):
+ return 'WEBSERVICE'
+
+ def isRemote(self):
+ """ Returns True if remote VBox host, False if local. """
+ return True
+
+ def getArray(self, oInterface, sAttrib):
+ return oInterface.__getattr__(sAttrib)
+
+ def setArray(self, oInterface, sAttrib, aoArray):
+ return oInterface.__setattr__(sAttrib, aoArray)
+
+ def waitForEvents(self, timeout):
+ # Webservices cannot do that yet
+ return 2
+
+ def interruptWaitEvents(self, timeout):
+ # Webservices cannot do that yet
+ return False
+
+ def deinit(self):
+ try:
+ self.disconnect()
+ except:
+ pass
+
+ def queryInterface(self, oIUnknown, sClassName):
+ d = {}
+ d['oIUnknown'] = oIUnknown
+ str = ""
+ str += "from VirtualBox_wrappers import " + sClassName + "\n"
+ str += "result = " + sClassName + "(oIUnknown.mgr, oIUnknown.handle)\n"
+ # wrong, need to test if class indeed implements this interface
+ exec(str, d, d)
+ return d['result']
+
+ #
+ # Web service specific methods.
+ #
+
+ def connect(self, url, user, passwd):
+ if self.vbox is not None:
+ self.disconnect()
+ from VirtualBox_wrappers import IWebsessionManager2
+
+ if url is None:
+ url = ""
+ self.url = url
+ if user is None:
+ user = ""
+ self.user = user
+ if passwd is None:
+ passwd = ""
+ self.password = passwd
+ self.wsmgr = IWebsessionManager2(self.url)
+ self.vbox = self.wsmgr.logon(self.user, self.password)
+ if not self.vbox.handle:
+ raise Exception("cannot connect to '" + self.url + "' as '" + self.user + "'")
+ return self.vbox
+
+ def disconnect(self):
+ if self.vbox is not None and self.wsmgr is not None:
+ self.wsmgr.logoff(self.vbox)
+ self.vbox = None
+ self.wsmgr = None
+
+
+## The current (last) exception class.
+# This is reinitalized whenever VirtualBoxManager is called, so it will hold
+# the reference to the error exception class for the last platform/style that
+# was used. Most clients does talk to multiple VBox instance on different
+# platforms at the same time, so this should be sufficent for most uses and
+# be way simpler to use than VirtualBoxManager::oXcptClass.
+CurXcptClass = None
+
+
+class VirtualBoxManager(object):
+ """
+ VirtualBox API manager class.
+
+ The API users will have to instantiate this. If no parameters are given,
+ it will default to interface with the VirtualBox running on the local
+ machine. sStyle can be None (default), MSCOM, XPCOM or WEBSERVICES. Most
+ users will either be specifying None or WEBSERVICES.
+
+ The dPlatformParams is an optional dictionary for passing parameters to the
+ WEBSERVICE backend.
+ """
+
+ class Statuses(object):
+ def __init__(self):
+ pass
+
+ def __init__(self, sStyle=None, dPlatformParams=None):
+ if sStyle is None:
+ if sys.platform == 'win32':
+ sStyle = "MSCOM"
+ else:
+ sStyle = "XPCOM"
+ if sStyle == 'XPCOM':
+ self.platform = PlatformXPCOM(dPlatformParams)
+ elif sStyle == 'MSCOM':
+ self.platform = PlatformMSCOM(dPlatformParams)
+ elif sStyle == 'WEBSERVICE':
+ self.platform = PlatformWEBSERVICE(dPlatformParams)
+ else:
+ raise Exception('Unknown sStyle=%s' % (sStyle,))
+ self.style = sStyle
+ self.type = self.platform.getType()
+ self.remote = self.platform.isRemote()
+ ## VirtualBox API constants (for webservices, enums are symbolic).
+ self.constants = VirtualBoxReflectionInfo(sStyle == "WEBSERVICE")
+
+ ## Status constants.
+ self.statuses = self.platform.xcptSetupConstants(VirtualBoxManager.Statuses())
+ ## @todo Add VBOX_E_XXX to statuses? They're already in constants...
+ ## Dictionary for errToString, built on demand.
+ self._dErrorValToName = None
+
+ ## Dictionary for resolving enum values to names, two levels of dictionaries.
+ ## First level is indexed by enum name, the next by value.
+ self._ddEnumValueToName = {};
+
+ ## The exception class for the selected platform.
+ self.oXcptClass = self.platform.xcptGetBaseXcpt()
+ global CurXcptClass
+ CurXcptClass = self.oXcptClass
+
+ # Get the virtualbox singleton.
+ try:
+ vbox = self.platform.getVirtualBox()
+ except NameError:
+ print("Installation problem: check that appropriate libs in place")
+ traceback.print_exc()
+ raise
+ except Exception:
+ _, e, _ = sys.exc_info()
+ print("init exception: ", e)
+ traceback.print_exc()
+
+ def __del__(self):
+ self.deinit()
+
+ def getPythonApiRevision(self):
+ """
+ Returns a Python API revision number.
+ This will be incremented when features are added to this file.
+ """
+ return 3
+
+ @property
+ def mgr(self):
+ """
+ This used to be an attribute referring to a session manager class with
+ only one method called getSessionObject. It moved into this class.
+ """
+ return self
+
+ #
+ # Wrappers for self.platform methods.
+ #
+ def getVirtualBox(self):
+ """ See PlatformBase::getVirtualBox(). """
+ return self.platform.getVirtualBox()
+
+ def getSessionObject(self, oIVBox = None):
+ """ See PlatformBase::getSessionObject(). """
+ # ignore parameter which was never needed
+ _ = oIVBox
+ return self.platform.getSessionObject()
+
+ def getArray(self, oInterface, sAttrib):
+ """ See PlatformBase::getArray(). """
+ return self.platform.getArray(oInterface, sAttrib)
+
+ def setArray(self, oInterface, sAttrib, aoArray):
+ """ See PlatformBase::setArray(). """
+ return self.platform.setArray(oInterface, sAttrib, aoArray)
+
+ def createListener(self, oImplClass, dArgs=None):
+ """ See PlatformBase::createListener(). """
+ return self.platform.createListener(oImplClass, dArgs)
+
+ def waitForEvents(self, cMsTimeout):
+ """ See PlatformBase::waitForEvents(). """
+ return self.platform.waitForEvents(cMsTimeout)
+
+ def interruptWaitEvents(self):
+ """ See PlatformBase::interruptWaitEvents(). """
+ return self.platform.interruptWaitEvents()
+
+ def queryInterface(self, oIUnknown, sClassName):
+ """ See PlatformBase::queryInterface(). """
+ return self.platform.queryInterface(oIUnknown, sClassName)
+
+ #
+ # Init and uninit.
+ #
+ def initPerThread(self):
+ """ See PlatformBase::deinitPerThread(). """
+ self.platform.initPerThread()
+
+ def deinitPerThread(self):
+ """ See PlatformBase::deinitPerThread(). """
+ return self.platform.deinitPerThread()
+
+ def deinit(self):
+ """
+ For unitializing the manager.
+ Do not access it after calling this method.
+ """
+ if hasattr(self, "platform") and self.platform is not None:
+ self.platform.deinit()
+ self.platform = None
+ return True
+
+ #
+ # Utility methods.
+ #
+ def openMachineSession(self, oIMachine, fPermitSharing=True):
+ """
+ Attempts to open the a session to the machine.
+ Returns a session object on success.
+ Raises exception on failure.
+ """
+ oSession = self.getSessionObject()
+ if fPermitSharing:
+ eType = self.constants.LockType_Shared
+ else:
+ eType = self.constants.LockType_Write
+ oIMachine.lockMachine(oSession, eType)
+ return oSession
+
+ def closeMachineSession(self, oSession):
+ """
+ Closes a session opened by openMachineSession.
+ Ignores None parameters.
+ """
+ if oSession is not None:
+ oSession.unlockMachine()
+ return True
+
+ def getPerfCollector(self, oIVBox):
+ """
+ Returns a helper class (PerfCollector) for accessing performance
+ collector goodies. See PerfCollector for details.
+ """
+ return PerfCollector(self, oIVBox)
+
+ def getBinDir(self):
+ """
+ Returns the VirtualBox binary directory.
+ """
+ global VBoxBinDir
+ return VBoxBinDir
+
+ def getSdkDir(self):
+ """
+ Returns the VirtualBox SDK directory.
+ """
+ global VBoxSdkDir
+ return VBoxSdkDir
+
+ def getEnumValueName(self, sEnumTypeNm, oEnumValue, fTypePrefix = False):
+ """
+ Returns the name (string) for the corresponding enum value.
+ """
+ # Cache lookup:
+ dValueNames = self._ddEnumValueToName.get(sEnumTypeNm);
+ if dValueNames is not None:
+ sValueName = dValueNames.get(oEnumValue);
+ if sValueName:
+ return sValueName if not fTypePrefix else '%s_%s' % (sEnumTypeNm, sValueName);
+ else:
+ # Cache miss. Build the reverse lookup dictionary and add it to the cache:
+ dNamedValues = self.constants.all_values(sEnumTypeNm);
+ if len(dNamedValues) > 0:
+
+ dValueNames = dict();
+ for sName in dNamedValues:
+ dValueNames[dNamedValues[sName]] = sName;
+ self._ddEnumValueToName[sEnumTypeNm] = dValueNames;
+
+ # Lookup the value:
+ sValueName = dValueNames.get(oEnumValue);
+ if sValueName:
+ return sValueName if not fTypePrefix else '%s_%s' % (sEnumTypeNm, sValueName);
+
+ # Fallback:
+ return '%s_Unknown_%s' % (sEnumTypeNm, oEnumValue);
+
+ #
+ # Error code utilities.
+ #
+ ## @todo port to webservices!
+ def xcptGetStatus(self, oXcpt=None):
+ """
+ Gets the status code from an exception. If the exception parameter
+ isn't specified, the current exception is examined.
+ """
+ if oXcpt is None:
+ oXcpt = sys.exc_info()[1]
+ return self.platform.xcptGetStatus(oXcpt)
+
+ def xcptIsDeadInterface(self, oXcpt=None):
+ """
+ Returns True if the exception indicates that the interface is dead,
+ False if not. If the exception parameter isn't specified, the current
+ exception is examined.
+ """
+ if oXcpt is None:
+ oXcpt = sys.exc_info()[1]
+ return self.platform.xcptIsDeadInterface(oXcpt)
+
+ def xcptIsOurXcptKind(self, oXcpt=None):
+ """
+ Checks if the exception is one that could come from the VBox API. If
+ the exception parameter isn't specified, the current exception is
+ examined.
+ """
+ if self.oXcptClass is None: # @todo find the exception class for web services!
+ return False
+ if oXcpt is None:
+ oXcpt = sys.exc_info()[1]
+ return isinstance(oXcpt, self.oXcptClass)
+
+ def xcptIsEqual(self, oXcpt, hrStatus):
+ """
+ Checks if the exception oXcpt is equal to the COM/XPCOM status code
+ hrStatus.
+
+ The oXcpt parameter can be any kind of object, we'll just return True
+ if it doesn't behave like a our exception class. If it's None, we'll
+ query the current exception and examine that.
+
+ Will not raise any exception as long as hrStatus and self are not bad.
+ """
+ if oXcpt is None:
+ oXcpt = sys.exc_info()[1]
+ return self.platform.xcptIsEqual(oXcpt, hrStatus)
+
+ def xcptIsNotEqual(self, oXcpt, hrStatus):
+ """
+ Negated xcptIsEqual.
+ """
+ return not self.xcptIsEqual(oXcpt, hrStatus)
+
+ def xcptToString(self, hrStatusOrXcpt=None):
+ """
+ Converts the specified COM status code, or the status code of the
+ specified exception, to a C constant string. If the parameter isn't
+ specified (is None), the current exception is examined.
+ """
+
+ # Deal with exceptions.
+ if hrStatusOrXcpt is None or self.xcptIsOurXcptKind(hrStatusOrXcpt):
+ hrStatus = self.xcptGetStatus(hrStatusOrXcpt)
+ else:
+ hrStatus = hrStatusOrXcpt
+
+ # Build the dictionary on demand.
+ if self._dErrorValToName is None:
+ dErrorValToName = dict()
+ for sKey in dir(self.statuses):
+ if sKey[0].isupper():
+ oValue = getattr(self.statuses, sKey)
+ if isinstance(oValue, (int, long)):
+ dErrorValToName[int(oValue)] = sKey
+ # Always prefer the COM names (see aliasing in platform specific code):
+ for sKey in ('S_OK', 'E_FAIL', 'E_ABORT', 'E_POINTER', 'E_NOINTERFACE', 'E_INVALIDARG',
+ 'E_OUTOFMEMORY', 'E_NOTIMPL', 'E_UNEXPECTED',):
+ oValue = getattr(self.statuses, sKey, None)
+ if oValue is not None:
+ dErrorValToName[oValue] = sKey
+ self._dErrorValToName = dErrorValToName
+
+ # Do the lookup, falling back on formatting the status number.
+ try:
+ sStr = self._dErrorValToName[int(hrStatus)]
+ except KeyError:
+ hrLong = long(hrStatus)
+ sStr = '%#x (%d)' % (hrLong & 0xffffffff, hrLong)
+ return sStr
+
+ def xcptGetMessage(self, oXcpt=None):
+ """
+ Returns the best error message found in the COM-like exception. If the
+ exception parameter isn't specified, the current exception is examined.
+ """
+ if oXcpt is None:
+ oXcpt = sys.exc_info()[1]
+ sRet = self.platform.xcptGetMessage(oXcpt)
+ if sRet is None:
+ sRet = self.xcptToString(oXcpt)
+ return sRet
+