VirtualBox

source: vbox/trunk/src/VBox/Main/glue/vboxapi.py@ 98103

Last change on this file since 98103 was 98103, checked in by vboxsync, 17 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 43.3 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: vboxapi.py 98103 2023-01-17 14:15:46Z vboxsync $
3"""
4VirtualBox Python API Glue.
5"""
6
7__copyright__ = \
8"""
9Copyright (C) 2009-2023 Oracle and/or its affiliates.
10
11This file is part of VirtualBox base platform packages, as
12available from https://www.virtualbox.org.
13
14This program is free software; you can redistribute it and/or
15modify it under the terms of the GNU General Public License
16as published by the Free Software Foundation, in version 3 of the
17License.
18
19This program is distributed in the hope that it will be useful, but
20WITHOUT ANY WARRANTY; without even the implied warranty of
21MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22General Public License for more details.
23
24You should have received a copy of the GNU General Public License
25along with this program; if not, see <https://www.gnu.org/licenses>.
26
27The contents of this file may alternatively be used under the terms
28of the Common Development and Distribution License Version 1.0
29(CDDL), a copy of it is provided in the "COPYING.CDDL" file included
30in the VirtualBox distribution, in which case the provisions of the
31CDDL are applicable instead of those of the GPL.
32
33You may elect to license modified versions of this file under the
34terms and conditions of either the GPL or the CDDL or both.
35
36SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
37"""
38__version__ = "$Revision: 98103 $"
39
40
41# Note! To set Python bitness on OSX use 'export VERSIONER_PYTHON_PREFER_32_BIT=yes'
42
43
44# Standard Python imports.
45import os
46import sys
47import traceback
48
49
50if sys.version_info >= (3, 0):
51 xrange = range
52 long = int
53
54#
55# Globals, environment and sys.path changes.
56#
57import platform
58VBoxBinDir = os.environ.get("VBOX_PROGRAM_PATH", None)
59VBoxSdkDir = os.environ.get("VBOX_SDK_PATH", None)
60
61if VBoxBinDir is None:
62 if platform.system() == 'Darwin':
63 VBoxBinDir = '/Applications/VirtualBox.app/Contents/MacOS'
64 else: # Will be set by the installer
65 VBoxBinDir = "%VBOX_INSTALL_PATH%"
66else:
67 VBoxBinDir = os.path.abspath(VBoxBinDir)
68
69if VBoxSdkDir is None:
70 if platform.system() == 'Darwin':
71 VBoxSdkDir = '/Applications/VirtualBox.app/Contents/MacOS/sdk'
72 else: # Will be set by the installer
73 VBoxSdkDir = "%VBOX_SDK_PATH%"
74else:
75 VBoxSdkDir = os.path.abspath(VBoxSdkDir)
76
77os.environ["VBOX_PROGRAM_PATH"] = VBoxBinDir
78os.environ["VBOX_SDK_PATH"] = VBoxSdkDir
79sys.path.append(VBoxBinDir)
80
81
82#
83# Import the generated VirtualBox constants.
84#
85from .VirtualBox_constants import VirtualBoxReflectionInfo
86
87
88class PerfCollector(object):
89 """ This class provides a wrapper over IPerformanceCollector in order to
90 get more 'pythonic' interface.
91
92 To begin collection of metrics use setup() method.
93
94 To get collected data use query() method.
95
96 It is possible to disable metric collection without changing collection
97 parameters with disable() method. The enable() method resumes metric
98 collection.
99 """
100
101 def __init__(self, mgr, vbox):
102 """ Initializes the instance.
103
104 """
105 self.mgr = mgr
106 self.isMscom = (mgr.type == 'MSCOM')
107 self.collector = vbox.performanceCollector
108
109 def setup(self, names, objects, period, nsamples):
110 """ Discards all previously collected values for the specified
111 metrics, sets the period of collection and the number of retained
112 samples, enables collection.
113 """
114 self.collector.setupMetrics(names, objects, period, nsamples)
115
116 def enable(self, names, objects):
117 """ Resumes metric collection for the specified metrics.
118 """
119 self.collector.enableMetrics(names, objects)
120
121 def disable(self, names, objects):
122 """ Suspends metric collection for the specified metrics.
123 """
124 self.collector.disableMetrics(names, objects)
125
126 def query(self, names, objects):
127 """ Retrieves collected metric values as well as some auxiliary
128 information. Returns an array of dictionaries, one dictionary per
129 metric. Each dictionary contains the following entries:
130 'name': metric name
131 'object': managed object this metric associated with
132 'unit': unit of measurement
133 'scale': divide 'values' by this number to get float numbers
134 'values': collected data
135 'values_as_string': pre-processed values ready for 'print' statement
136 """
137 # Get around the problem with input arrays returned in output
138 # parameters (see #3953) for MSCOM.
139 if self.isMscom:
140 (values, names, objects, names_out, objects_out, units, scales, sequence_numbers,
141 indices, lengths) = self.collector.queryMetricsData(names, objects)
142 else:
143 (values, names_out, objects_out, units, scales, sequence_numbers,
144 indices, lengths) = self.collector.queryMetricsData(names, objects)
145 out = []
146 for i in xrange(0, len(names_out)):
147 scale = int(scales[i])
148 if scale != 1:
149 fmt = '%.2f%s'
150 else:
151 fmt = '%d %s'
152 out.append({
153 'name': str(names_out[i]),
154 'object': str(objects_out[i]),
155 'unit': str(units[i]),
156 'scale': scale,
157 'values': [int(values[j]) for j in xrange(int(indices[i]), int(indices[i]) + int(lengths[i]))],
158 'values_as_string': '[' + ', '.join([fmt % (int(values[j]) / scale, units[i]) for j in
159 xrange(int(indices[i]), int(indices[i]) + int(lengths[i]))]) + ']'
160 })
161 return out
162
163
164#
165# Attribute hacks.
166#
167def ComifyName(name):
168 return name[0].capitalize() + name[1:]
169
170
171## This is for saving the original DispatchBaseClass __getattr__ and __setattr__
172# method references.
173_g_dCOMForward = {
174 'getattr': None,
175 'setattr': None,
176}
177
178
179def _CustomGetAttr(self, sAttr):
180 """ Our getattr replacement for DispatchBaseClass. """
181 # Fastpath.
182 oRet = self.__class__.__dict__.get(sAttr)
183 if oRet is not None:
184 return oRet
185
186 # Try case-insensitivity workaround for class attributes (COM methods).
187 sAttrLower = sAttr.lower()
188 for k in list(self.__class__.__dict__.keys()):
189 if k.lower() == sAttrLower:
190 setattr(self.__class__, sAttr, self.__class__.__dict__[k])
191 return getattr(self, k)
192
193 # Slow path.
194 try:
195 return _g_dCOMForward['getattr'](self, ComifyName(sAttr))
196 except AttributeError:
197 return _g_dCOMForward['getattr'](self, sAttr)
198
199
200def _CustomSetAttr(self, sAttr, oValue):
201 """ Our setattr replacement for DispatchBaseClass. """
202 try:
203 return _g_dCOMForward['setattr'](self, ComifyName(sAttr), oValue)
204 except AttributeError:
205 return _g_dCOMForward['setattr'](self, sAttr, oValue)
206
207
208class PlatformBase(object):
209 """
210 Base class for the platform specific code.
211 """
212
213 def __init__(self, aoParams):
214 _ = aoParams
215
216 def getVirtualBox(self):
217 """
218 Gets a the IVirtualBox singleton.
219 """
220 return None
221
222 def getSessionObject(self):
223 """
224 Get a session object that can be used for opening machine sessions.
225
226 The oIVBox parameter is an getVirtualBox() return value, i.e. an
227 IVirtualBox reference.
228
229 See also openMachineSession.
230 """
231 return None
232
233 def getType(self):
234 """ Returns the platform type (class name sans 'Platform'). """
235 return None
236
237 def isRemote(self):
238 """
239 Returns True if remote (web services) and False if local (COM/XPCOM).
240 """
241 return False
242
243 def getArray(self, oInterface, sAttrib):
244 """
245 Retrives the value of the array attribute 'sAttrib' from
246 interface 'oInterface'.
247
248 This is for hiding platform specific differences in attributes
249 returning arrays.
250 """
251 _ = oInterface
252 _ = sAttrib
253 return None
254
255 def setArray(self, oInterface, sAttrib, aoArray):
256 """
257 Sets the value (aoArray) of the array attribute 'sAttrib' in
258 interface 'oInterface'.
259
260 This is for hiding platform specific differences in attributes
261 setting arrays.
262 """
263 _ = oInterface
264 _ = sAttrib
265 _ = aoArray
266 return None
267
268 def initPerThread(self):
269 """
270 Does backend specific initialization for the calling thread.
271 """
272 return True
273
274 def deinitPerThread(self):
275 """
276 Does backend specific uninitialization for the calling thread.
277 """
278 return True
279
280 def createListener(self, oImplClass, dArgs):
281 """
282 Instantiates and wraps an active event listener class so it can be
283 passed to an event source for registration.
284
285 oImplClass is a class (type, not instance) which implements
286 IEventListener.
287
288 dArgs is a dictionary with string indexed variables. This may be
289 modified by the method to pass platform specific parameters. Can
290 be None.
291
292 This currently only works on XPCOM. COM support is not possible due to
293 shortcuts taken in the COM bridge code, which is not under our control.
294 Use passive listeners for COM and web services.
295 """
296 _ = oImplClass
297 _ = dArgs
298 raise Exception("No active listeners for this platform")
299
300 def waitForEvents(self, cMsTimeout):
301 """
302 Wait for events to arrive and process them.
303
304 The timeout (cMsTimeout) is in milliseconds for how long to wait for
305 events to arrive. A negative value means waiting for ever, while 0
306 does not wait at all.
307
308 Returns 0 if events was processed.
309 Returns 1 if timed out or interrupted in some way.
310 Returns 2 on error (like not supported for web services).
311
312 Raises an exception if the calling thread is not the main thread (the one
313 that initialized VirtualBoxManager) or if the time isn't an integer.
314 """
315 _ = cMsTimeout
316 return 2
317
318 def interruptWaitEvents(self):
319 """
320 Interrupt a waitForEvents call.
321 This is normally called from a worker thread to wake up the main thread.
322
323 Returns True on success, False on failure.
324 """
325 return False
326
327 def deinit(self):
328 """
329 Unitializes the platform specific backend.
330 """
331 return None
332
333 def queryInterface(self, oIUnknown, sClassName):
334 """
335 IUnknown::QueryInterface wrapper.
336
337 oIUnknown is who to ask.
338 sClassName is the name of the interface we're asking for.
339 """
340 return None
341
342 #
343 # Error (exception) access methods.
344 #
345
346 def xcptGetStatus(self, oXcpt):
347 """
348 Returns the COM status code from the VBox API given exception.
349 """
350 return None
351
352 def xcptIsDeadInterface(self, oXcpt):
353 """
354 Returns True if the exception indicates that the interface is dead, False if not.
355 """
356 return False
357
358 def xcptIsEqual(self, oXcpt, hrStatus):
359 """
360 Checks if the exception oXcpt is equal to the COM/XPCOM status code
361 hrStatus.
362
363 The oXcpt parameter can be any kind of object, we'll just return True
364 if it doesn't behave like a our exception class.
365
366 Will not raise any exception as long as hrStatus and self are not bad.
367 """
368 try:
369 hrXcpt = self.xcptGetStatus(oXcpt)
370 except AttributeError:
371 return False
372 if hrXcpt == hrStatus:
373 return True
374
375 # Fudge for 32-bit signed int conversion.
376 if 0x7fffffff < hrStatus <= 0xffffffff and hrXcpt < 0:
377 if (hrStatus - 0x100000000) == hrXcpt:
378 return True
379 return False
380
381 def xcptGetMessage(self, oXcpt):
382 """
383 Returns the best error message found in the COM-like exception.
384 Returns None to fall back on xcptToString.
385 Raises exception if oXcpt isn't our kind of exception object.
386 """
387 return None
388
389 def xcptGetBaseXcpt(self):
390 """
391 Returns the base exception class.
392 """
393 return None
394
395 def xcptSetupConstants(self, oDst):
396 """
397 Copy/whatever all error constants onto oDst.
398 """
399 return oDst
400
401 @staticmethod
402 def xcptCopyErrorConstants(oDst, oSrc):
403 """
404 Copy everything that looks like error constants from oDst to oSrc.
405 """
406 for sAttr in dir(oSrc):
407 if sAttr[0].isupper() and (sAttr[1].isupper() or sAttr[1] == '_'):
408 oAttr = getattr(oSrc, sAttr)
409 if type(oAttr) is int:
410 setattr(oDst, sAttr, oAttr)
411 return oDst
412
413
414class PlatformMSCOM(PlatformBase):
415 """
416 Platform specific code for MS COM.
417 """
418
419 ## @name VirtualBox COM Typelib definitions (should be generate)
420 #
421 # @remarks Must be updated when the corresponding VirtualBox.xidl bits
422 # are changed. Fortunately this isn't very often.
423 # @{
424 VBOX_TLB_GUID = '{D7569351-1750-46F0-936E-BD127D5BC264}'
425 VBOX_TLB_LCID = 0
426 VBOX_TLB_MAJOR = 1
427 VBOX_TLB_MINOR = 3
428 ## @}
429
430 def __init__(self, dParams):
431 PlatformBase.__init__(self, dParams)
432
433 #
434 # Since the code runs on all platforms, we have to do a lot of
435 # importing here instead of at the top of the file where it's normally located.
436 #
437 from win32com import universal
438 from win32com.client import gencache, DispatchBaseClass
439 from win32com.client import constants, getevents
440 import win32com
441 import pythoncom
442 import win32api
443 import winerror
444 from win32con import DUPLICATE_SAME_ACCESS
445 from win32api import GetCurrentThread, GetCurrentThreadId, DuplicateHandle, GetCurrentProcess
446 import threading
447
448 self.winerror = winerror
449
450 # Setup client impersonation in COM calls.
451 try:
452 pythoncom.CoInitializeSecurity(None,
453 None,
454 None,
455 pythoncom.RPC_C_AUTHN_LEVEL_DEFAULT,
456 pythoncom.RPC_C_IMP_LEVEL_IMPERSONATE,
457 None,
458 pythoncom.EOAC_NONE,
459 None)
460 except:
461 _, oXcpt, _ = sys.exc_info();
462 if isinstance(oXcpt, pythoncom.com_error) and self.xcptGetStatus(oXcpt) == -2147417831: # RPC_E_TOO_LATE
463 print("Warning: CoInitializeSecurity was already called");
464 else:
465 print("Warning: CoInitializeSecurity failed: ", oXcpt);
466
467 # Remember this thread ID and get its handle so we can wait on it in waitForEvents().
468 self.tid = GetCurrentThreadId()
469 pid = GetCurrentProcess()
470 self.aoHandles = [DuplicateHandle(pid, GetCurrentThread(), pid, 0, 0, DUPLICATE_SAME_ACCESS),] # type: list[PyHANDLE]
471
472 # Hack the COM dispatcher base class so we can modify method and
473 # attribute names to match those in xpcom.
474 if _g_dCOMForward['setattr'] is None:
475 _g_dCOMForward['getattr'] = DispatchBaseClass.__dict__['__getattr__']
476 _g_dCOMForward['setattr'] = DispatchBaseClass.__dict__['__setattr__']
477 setattr(DispatchBaseClass, '__getattr__', _CustomGetAttr)
478 setattr(DispatchBaseClass, '__setattr__', _CustomSetAttr)
479
480 # Hack the exception base class so the users doesn't need to check for
481 # XPCOM or COM and do different things.
482 ## @todo
483
484 #
485 # Make sure the gencache is correct (we don't quite follow the COM
486 # versioning rules).
487 #
488 self.flushGenPyCache(win32com.client.gencache)
489 win32com.client.gencache.EnsureDispatch('VirtualBox.Session')
490 win32com.client.gencache.EnsureDispatch('VirtualBox.VirtualBox')
491 win32com.client.gencache.EnsureDispatch('VirtualBox.VirtualBoxClient')
492
493 self.oClient = None ##< instance of client used to support lifetime of VBoxSDS
494 self.oIntCv = threading.Condition()
495 self.fInterrupted = False
496
497 _ = dParams
498
499 def flushGenPyCache(self, oGenCache):
500 """
501 Flushes VBox related files in the win32com gen_py cache.
502
503 This is necessary since we don't follow the typelib versioning rules
504 that everyeone else seems to subscribe to.
505 """
506 #
507 # The EnsureModule method have broken validation code, it doesn't take
508 # typelib module directories into account. So we brute force them here.
509 # (It's possible the directory approach is from some older pywin
510 # version or the result of runnig makepy or gencache manually, but we
511 # need to cover it as well.)
512 #
513 sName = oGenCache.GetGeneratedFileName(self.VBOX_TLB_GUID, self.VBOX_TLB_LCID,
514 self.VBOX_TLB_MAJOR, self.VBOX_TLB_MINOR)
515 sGenPath = oGenCache.GetGeneratePath()
516 if len(sName) > 36 and len(sGenPath) > 5:
517 sTypelibPath = os.path.join(sGenPath, sName)
518 if os.path.isdir(sTypelibPath):
519 import shutil
520 shutil.rmtree(sTypelibPath, ignore_errors=True)
521
522 #
523 # Ensure that our typelib is valid.
524 #
525 return oGenCache.EnsureModule(self.VBOX_TLB_GUID, self.VBOX_TLB_LCID, self.VBOX_TLB_MAJOR, self.VBOX_TLB_MINOR)
526
527 def getSessionObject(self):
528 import win32com
529 from win32com.client import Dispatch
530 return win32com.client.Dispatch("VirtualBox.Session")
531
532 def getVirtualBox(self):
533 # Caching self.oClient is the trick for SDS. It allows to keep the
534 # VBoxSDS in the memory until the end of PlatformMSCOM lifetme.
535 if self.oClient is None:
536 import win32com
537 from win32com.client import Dispatch
538 self.oClient = win32com.client.Dispatch("VirtualBox.VirtualBoxClient")
539 return self.oClient.virtualBox
540
541 def getType(self):
542 return 'MSCOM'
543
544 def getArray(self, oInterface, sAttrib):
545 return oInterface.__getattr__(sAttrib)
546
547 def setArray(self, oInterface, sAttrib, aoArray):
548 #
549 # HACK ALERT!
550 #
551 # With pywin32 build 218, we're seeing type mismatch errors here for
552 # IGuestSession::environmentChanges (safearray of BSTRs). The Dispatch
553 # object (_oleobj_) seems to get some type conversion wrong and COM
554 # gets upset. So, we redo some of the dispatcher work here, picking
555 # the missing type information from the getter.
556 #
557 oOleObj = getattr(oInterface, '_oleobj_')
558 aPropMapGet = getattr(oInterface, '_prop_map_get_')
559 aPropMapPut = getattr(oInterface, '_prop_map_put_')
560 sComAttrib = sAttrib if sAttrib in aPropMapGet else ComifyName(sAttrib)
561 try:
562 aArgs, aDefaultArgs = aPropMapPut[sComAttrib]
563 aGetArgs = aPropMapGet[sComAttrib]
564 except KeyError: # fallback.
565 return oInterface.__setattr__(sAttrib, aoArray)
566
567 import pythoncom
568 oOleObj.InvokeTypes(aArgs[0], # dispid
569 aArgs[1], # LCID
570 aArgs[2], # DISPATCH_PROPERTYPUT
571 (pythoncom.VT_HRESULT, 0), # retType - or void?
572 (aGetArgs[2],), # argTypes - trick: we get the type from the getter.
573 aoArray,) # The array
574
575 def initPerThread(self):
576 import pythoncom
577 pythoncom.CoInitializeEx(0)
578
579 def deinitPerThread(self):
580 import pythoncom
581 pythoncom.CoUninitialize()
582
583 def createListener(self, oImplClass, dArgs):
584 if True:
585 raise Exception('no active listeners on Windows as PyGatewayBase::QueryInterface() '
586 'returns new gateway objects all the time, thus breaking EventQueue '
587 'assumptions about the listener interface pointer being constants between calls ')
588 # Did this code ever really work?
589 d = {}
590 d['BaseClass'] = oImplClass
591 d['dArgs'] = dArgs
592 d['tlb_guid'] = PlatformMSCOM.VBOX_TLB_GUID
593 d['tlb_major'] = PlatformMSCOM.VBOX_TLB_MAJOR
594 d['tlb_minor'] = PlatformMSCOM.VBOX_TLB_MINOR
595 str_ = ""
596 str_ += "import win32com.server.util\n"
597 str_ += "import pythoncom\n"
598
599 str_ += "class ListenerImpl(BaseClass):\n"
600 str_ += " _com_interfaces_ = ['IEventListener']\n"
601 str_ += " _typelib_guid_ = tlb_guid\n"
602 str_ += " _typelib_version_ = tlb_major, tlb_minor\n"
603 str_ += " _reg_clsctx_ = pythoncom.CLSCTX_INPROC_SERVER\n"
604 # Maybe we'd better implement Dynamic invoke policy, to be more flexible here
605 str_ += " _reg_policy_spec_ = 'win32com.server.policy.EventHandlerPolicy'\n"
606
607 # capitalized version of listener method
608 str_ += " HandleEvent=BaseClass.handleEvent\n"
609 str_ += " def __init__(self): BaseClass.__init__(self, dArgs)\n"
610 str_ += "result = win32com.server.util.wrap(ListenerImpl())\n"
611 exec(str_, d, d)
612 return d['result']
613
614 def waitForEvents(self, timeout):
615 from win32api import GetCurrentThreadId
616 from win32event import INFINITE
617 from win32event import MsgWaitForMultipleObjects, QS_ALLINPUT, WAIT_TIMEOUT, WAIT_OBJECT_0
618 from pythoncom import PumpWaitingMessages
619 import types
620
621 if not isinstance(timeout, int):
622 raise TypeError("The timeout argument is not an integer")
623 if self.tid != GetCurrentThreadId():
624 raise Exception("wait for events from the same thread you inited!")
625
626 if timeout < 0:
627 cMsTimeout = INFINITE
628 else:
629 cMsTimeout = timeout
630 rc = MsgWaitForMultipleObjects(self.aoHandles, 0, cMsTimeout, QS_ALLINPUT)
631 if WAIT_OBJECT_0 <= rc < WAIT_OBJECT_0 + len(self.aoHandles):
632 # is it possible?
633 rc = 2
634 elif rc == WAIT_OBJECT_0 + len(self.aoHandles):
635 # Waiting messages
636 PumpWaitingMessages()
637 rc = 0
638 else:
639 # Timeout
640 rc = 1
641
642 # check for interruption
643 self.oIntCv.acquire()
644 if self.fInterrupted:
645 self.fInterrupted = False
646 rc = 1
647 self.oIntCv.release()
648
649 return rc
650
651 def interruptWaitEvents(self):
652 """
653 Basically a python implementation of NativeEventQueue::postEvent().
654
655 The magic value must be in sync with the C++ implementation or this
656 won't work.
657
658 Note that because of this method we cannot easily make use of a
659 non-visible Window to handle the message like we would like to do.
660 """
661 from win32api import PostThreadMessage
662 from win32con import WM_USER
663
664 self.oIntCv.acquire()
665 self.fInterrupted = True
666 self.oIntCv.release()
667 try:
668 PostThreadMessage(self.tid, WM_USER, None, 0xf241b819)
669 except:
670 return False
671 return True
672
673 def deinit(self):
674 for oHandle in self.aoHandles:
675 if oHandle is not None:
676 oHandle.Close();
677 self.oHandle = None;
678
679 del self.oClient;
680 self.oClient = None;
681
682 # This non-sense doesn't pair up with any pythoncom.CoInitialize[Ex].
683 # See @bugref{9037}.
684 #import pythoncom
685 #pythoncom.CoUninitialize()
686
687 def queryInterface(self, oIUnknown, sClassName):
688 from win32com.client import CastTo
689 return CastTo(oIUnknown, sClassName)
690
691 def xcptGetStatus(self, oXcpt):
692 # The DISP_E_EXCEPTION + excptinfo fun needs checking up, only
693 # empirical info on it so far.
694 hrXcpt = oXcpt.hresult
695 if hrXcpt == self.winerror.DISP_E_EXCEPTION:
696 try:
697 hrXcpt = oXcpt.excepinfo[5]
698 except:
699 pass
700 return hrXcpt
701
702 def xcptIsDeadInterface(self, oXcpt):
703 return self.xcptGetStatus(oXcpt) in [
704 0x800706ba, -2147023174, # RPC_S_SERVER_UNAVAILABLE.
705 0x800706be, -2147023170, # RPC_S_CALL_FAILED.
706 0x800706bf, -2147023169, # RPC_S_CALL_FAILED_DNE.
707 0x80010108, -2147417848, # RPC_E_DISCONNECTED.
708 0x800706b5, -2147023179, # RPC_S_UNKNOWN_IF
709 ]
710
711 def xcptGetMessage(self, oXcpt):
712 if hasattr(oXcpt, 'excepinfo'):
713 try:
714 if len(oXcpt.excepinfo) >= 3:
715 sRet = oXcpt.excepinfo[2]
716 if len(sRet) > 0:
717 return sRet[0:]
718 except:
719 pass
720 if hasattr(oXcpt, 'strerror'):
721 try:
722 sRet = oXcpt.strerror
723 if len(sRet) > 0:
724 return sRet
725 except:
726 pass
727 return None
728
729 def xcptGetBaseXcpt(self):
730 import pythoncom
731
732 return pythoncom.com_error
733
734 def xcptSetupConstants(self, oDst):
735 import winerror
736
737 oDst = self.xcptCopyErrorConstants(oDst, winerror)
738
739 # XPCOM compatability constants.
740 oDst.NS_OK = oDst.S_OK
741 oDst.NS_ERROR_FAILURE = oDst.E_FAIL
742 oDst.NS_ERROR_ABORT = oDst.E_ABORT
743 oDst.NS_ERROR_NULL_POINTER = oDst.E_POINTER
744 oDst.NS_ERROR_NO_INTERFACE = oDst.E_NOINTERFACE
745 oDst.NS_ERROR_INVALID_ARG = oDst.E_INVALIDARG
746 oDst.NS_ERROR_OUT_OF_MEMORY = oDst.E_OUTOFMEMORY
747 oDst.NS_ERROR_NOT_IMPLEMENTED = oDst.E_NOTIMPL
748 oDst.NS_ERROR_UNEXPECTED = oDst.E_UNEXPECTED
749 return oDst
750
751
752class PlatformXPCOM(PlatformBase):
753 """
754 Platform specific code for XPCOM.
755 """
756
757 def __init__(self, dParams):
758 PlatformBase.__init__(self, dParams)
759 sys.path.append(VBoxSdkDir + '/bindings/xpcom/python/')
760 import xpcom.vboxxpcom
761 import xpcom
762 import xpcom.components
763 _ = dParams
764
765 def getSessionObject(self):
766 import xpcom.components
767 return xpcom.components.classes["@virtualbox.org/Session;1"].createInstance()
768
769 def getVirtualBox(self):
770 import xpcom.components
771 client = xpcom.components.classes["@virtualbox.org/VirtualBoxClient;1"].createInstance()
772 return client.virtualBox
773
774 def getType(self):
775 return 'XPCOM'
776
777 def getArray(self, oInterface, sAttrib):
778 return oInterface.__getattr__('get' + ComifyName(sAttrib))()
779
780 def setArray(self, oInterface, sAttrib, aoArray):
781 return oInterface.__getattr__('set' + ComifyName(sAttrib))(aoArray)
782
783 def initPerThread(self):
784 import xpcom
785 xpcom._xpcom.AttachThread()
786
787 def deinitPerThread(self):
788 import xpcom
789 xpcom._xpcom.DetachThread()
790
791 def createListener(self, oImplClass, dArgs):
792 d = {}
793 d['BaseClass'] = oImplClass
794 d['dArgs'] = dArgs
795 str = ""
796 str += "import xpcom.components\n"
797 str += "class ListenerImpl(BaseClass):\n"
798 str += " _com_interfaces_ = xpcom.components.interfaces.IEventListener\n"
799 str += " def __init__(self): BaseClass.__init__(self, dArgs)\n"
800 str += "result = ListenerImpl()\n"
801 exec(str, d, d)
802 return d['result']
803
804 def waitForEvents(self, timeout):
805 import xpcom
806 return xpcom._xpcom.WaitForEvents(timeout)
807
808 def interruptWaitEvents(self):
809 import xpcom
810 return xpcom._xpcom.InterruptWait()
811
812 def deinit(self):
813 import xpcom
814 xpcom._xpcom.DeinitCOM()
815
816 def queryInterface(self, oIUnknown, sClassName):
817 import xpcom.components
818 return oIUnknown.queryInterface(getattr(xpcom.components.interfaces, sClassName))
819
820 def xcptGetStatus(self, oXcpt):
821 return oXcpt.errno
822
823 def xcptIsDeadInterface(self, oXcpt):
824 return self.xcptGetStatus(oXcpt) in [
825 0x80004004, -2147467260, # NS_ERROR_ABORT
826 0x800706be, -2147023170, # NS_ERROR_CALL_FAILED (RPC_S_CALL_FAILED)
827 ]
828
829 def xcptGetMessage(self, oXcpt):
830 if hasattr(oXcpt, 'msg'):
831 try:
832 sRet = oXcpt.msg
833 if len(sRet) > 0:
834 return sRet
835 except:
836 pass
837 return None
838
839 def xcptGetBaseXcpt(self):
840 import xpcom
841 return xpcom.Exception
842
843 def xcptSetupConstants(self, oDst):
844 import xpcom
845 oDst = self.xcptCopyErrorConstants(oDst, xpcom.nsError)
846
847 # COM compatability constants.
848 oDst.E_ACCESSDENIED = -2147024891 # see VBox/com/defs.h
849 oDst.S_OK = oDst.NS_OK
850 oDst.E_FAIL = oDst.NS_ERROR_FAILURE
851 oDst.E_ABORT = oDst.NS_ERROR_ABORT
852 oDst.E_POINTER = oDst.NS_ERROR_NULL_POINTER
853 oDst.E_NOINTERFACE = oDst.NS_ERROR_NO_INTERFACE
854 oDst.E_INVALIDARG = oDst.NS_ERROR_INVALID_ARG
855 oDst.E_OUTOFMEMORY = oDst.NS_ERROR_OUT_OF_MEMORY
856 oDst.E_NOTIMPL = oDst.NS_ERROR_NOT_IMPLEMENTED
857 oDst.E_UNEXPECTED = oDst.NS_ERROR_UNEXPECTED
858 oDst.DISP_E_EXCEPTION = -2147352567 # For COM compatability only.
859 return oDst
860
861
862class PlatformWEBSERVICE(PlatformBase):
863 """
864 VirtualBox Web Services API specific code.
865 """
866
867 def __init__(self, dParams):
868 PlatformBase.__init__(self, dParams)
869 # Import web services stuff. Fix the sys.path the first time.
870 sWebServLib = os.path.join(VBoxSdkDir, 'bindings', 'webservice', 'python', 'lib')
871 if sWebServLib not in sys.path:
872 sys.path.append(sWebServLib)
873 import VirtualBox_wrappers
874 from VirtualBox_wrappers import IWebsessionManager2
875
876 # Initialize instance variables from parameters.
877 if dParams is not None:
878 self.user = dParams.get("user", "")
879 self.password = dParams.get("password", "")
880 self.url = dParams.get("url", "")
881 else:
882 self.user = ""
883 self.password = ""
884 self.url = None
885 self.vbox = None
886 self.wsmgr = None
887
888 #
889 # Base class overrides.
890 #
891
892 def getSessionObject(self):
893 return self.wsmgr.getSessionObject(self.vbox)
894
895 def getVirtualBox(self):
896 return self.connect(self.url, self.user, self.password)
897
898 def getType(self):
899 return 'WEBSERVICE'
900
901 def isRemote(self):
902 """ Returns True if remote VBox host, False if local. """
903 return True
904
905 def getArray(self, oInterface, sAttrib):
906 return oInterface.__getattr__(sAttrib)
907
908 def setArray(self, oInterface, sAttrib, aoArray):
909 return oInterface.__setattr__(sAttrib, aoArray)
910
911 def waitForEvents(self, timeout):
912 # Webservices cannot do that yet
913 return 2
914
915 def interruptWaitEvents(self, timeout):
916 # Webservices cannot do that yet
917 return False
918
919 def deinit(self):
920 try:
921 self.disconnect()
922 except:
923 pass
924
925 def queryInterface(self, oIUnknown, sClassName):
926 d = {}
927 d['oIUnknown'] = oIUnknown
928 str = ""
929 str += "from VirtualBox_wrappers import " + sClassName + "\n"
930 str += "result = " + sClassName + "(oIUnknown.mgr, oIUnknown.handle)\n"
931 # wrong, need to test if class indeed implements this interface
932 exec(str, d, d)
933 return d['result']
934
935 #
936 # Web service specific methods.
937 #
938
939 def connect(self, url, user, passwd):
940 if self.vbox is not None:
941 self.disconnect()
942 from VirtualBox_wrappers import IWebsessionManager2
943
944 if url is None:
945 url = ""
946 self.url = url
947 if user is None:
948 user = ""
949 self.user = user
950 if passwd is None:
951 passwd = ""
952 self.password = passwd
953 self.wsmgr = IWebsessionManager2(self.url)
954 self.vbox = self.wsmgr.logon(self.user, self.password)
955 if not self.vbox.handle:
956 raise Exception("cannot connect to '" + self.url + "' as '" + self.user + "'")
957 return self.vbox
958
959 def disconnect(self):
960 if self.vbox is not None and self.wsmgr is not None:
961 self.wsmgr.logoff(self.vbox)
962 self.vbox = None
963 self.wsmgr = None
964
965
966## The current (last) exception class.
967# This is reinitalized whenever VirtualBoxManager is called, so it will hold
968# the reference to the error exception class for the last platform/style that
969# was used. Most clients does talk to multiple VBox instance on different
970# platforms at the same time, so this should be sufficent for most uses and
971# be way simpler to use than VirtualBoxManager::oXcptClass.
972CurXcptClass = None
973
974
975class VirtualBoxManager(object):
976 """
977 VirtualBox API manager class.
978
979 The API users will have to instantiate this. If no parameters are given,
980 it will default to interface with the VirtualBox running on the local
981 machine. sStyle can be None (default), MSCOM, XPCOM or WEBSERVICES. Most
982 users will either be specifying None or WEBSERVICES.
983
984 The dPlatformParams is an optional dictionary for passing parameters to the
985 WEBSERVICE backend.
986 """
987
988 class Statuses(object):
989 def __init__(self):
990 pass
991
992 def __init__(self, sStyle=None, dPlatformParams=None):
993 if sStyle is None:
994 if sys.platform == 'win32':
995 sStyle = "MSCOM"
996 else:
997 sStyle = "XPCOM"
998 if sStyle == 'XPCOM':
999 self.platform = PlatformXPCOM(dPlatformParams)
1000 elif sStyle == 'MSCOM':
1001 self.platform = PlatformMSCOM(dPlatformParams)
1002 elif sStyle == 'WEBSERVICE':
1003 self.platform = PlatformWEBSERVICE(dPlatformParams)
1004 else:
1005 raise Exception('Unknown sStyle=%s' % (sStyle,))
1006 self.style = sStyle
1007 self.type = self.platform.getType()
1008 self.remote = self.platform.isRemote()
1009 ## VirtualBox API constants (for webservices, enums are symbolic).
1010 self.constants = VirtualBoxReflectionInfo(sStyle == "WEBSERVICE")
1011
1012 ## Status constants.
1013 self.statuses = self.platform.xcptSetupConstants(VirtualBoxManager.Statuses())
1014 ## @todo Add VBOX_E_XXX to statuses? They're already in constants...
1015 ## Dictionary for errToString, built on demand.
1016 self._dErrorValToName = None
1017
1018 ## Dictionary for resolving enum values to names, two levels of dictionaries.
1019 ## First level is indexed by enum name, the next by value.
1020 self._ddEnumValueToName = {};
1021
1022 ## The exception class for the selected platform.
1023 self.oXcptClass = self.platform.xcptGetBaseXcpt()
1024 global CurXcptClass
1025 CurXcptClass = self.oXcptClass
1026
1027 # Get the virtualbox singleton.
1028 try:
1029 vbox = self.platform.getVirtualBox()
1030 except NameError:
1031 print("Installation problem: check that appropriate libs in place")
1032 traceback.print_exc()
1033 raise
1034 except Exception:
1035 _, e, _ = sys.exc_info()
1036 print("init exception: ", e)
1037 traceback.print_exc()
1038
1039 def __del__(self):
1040 self.deinit()
1041
1042 def getPythonApiRevision(self):
1043 """
1044 Returns a Python API revision number.
1045 This will be incremented when features are added to this file.
1046 """
1047 return 3
1048
1049 @property
1050 def mgr(self):
1051 """
1052 This used to be an attribute referring to a session manager class with
1053 only one method called getSessionObject. It moved into this class.
1054 """
1055 return self
1056
1057 #
1058 # Wrappers for self.platform methods.
1059 #
1060 def getVirtualBox(self):
1061 """ See PlatformBase::getVirtualBox(). """
1062 return self.platform.getVirtualBox()
1063
1064 def getSessionObject(self, oIVBox = None):
1065 """ See PlatformBase::getSessionObject(). """
1066 # ignore parameter which was never needed
1067 _ = oIVBox
1068 return self.platform.getSessionObject()
1069
1070 def getArray(self, oInterface, sAttrib):
1071 """ See PlatformBase::getArray(). """
1072 return self.platform.getArray(oInterface, sAttrib)
1073
1074 def setArray(self, oInterface, sAttrib, aoArray):
1075 """ See PlatformBase::setArray(). """
1076 return self.platform.setArray(oInterface, sAttrib, aoArray)
1077
1078 def createListener(self, oImplClass, dArgs=None):
1079 """ See PlatformBase::createListener(). """
1080 return self.platform.createListener(oImplClass, dArgs)
1081
1082 def waitForEvents(self, cMsTimeout):
1083 """ See PlatformBase::waitForEvents(). """
1084 return self.platform.waitForEvents(cMsTimeout)
1085
1086 def interruptWaitEvents(self):
1087 """ See PlatformBase::interruptWaitEvents(). """
1088 return self.platform.interruptWaitEvents()
1089
1090 def queryInterface(self, oIUnknown, sClassName):
1091 """ See PlatformBase::queryInterface(). """
1092 return self.platform.queryInterface(oIUnknown, sClassName)
1093
1094 #
1095 # Init and uninit.
1096 #
1097 def initPerThread(self):
1098 """ See PlatformBase::deinitPerThread(). """
1099 self.platform.initPerThread()
1100
1101 def deinitPerThread(self):
1102 """ See PlatformBase::deinitPerThread(). """
1103 return self.platform.deinitPerThread()
1104
1105 def deinit(self):
1106 """
1107 For unitializing the manager.
1108 Do not access it after calling this method.
1109 """
1110 if hasattr(self, "platform") and self.platform is not None:
1111 self.platform.deinit()
1112 self.platform = None
1113 return True
1114
1115 #
1116 # Utility methods.
1117 #
1118 def openMachineSession(self, oIMachine, fPermitSharing=True):
1119 """
1120 Attempts to open the a session to the machine.
1121 Returns a session object on success.
1122 Raises exception on failure.
1123 """
1124 oSession = self.getSessionObject()
1125 if fPermitSharing:
1126 eType = self.constants.LockType_Shared
1127 else:
1128 eType = self.constants.LockType_Write
1129 oIMachine.lockMachine(oSession, eType)
1130 return oSession
1131
1132 def closeMachineSession(self, oSession):
1133 """
1134 Closes a session opened by openMachineSession.
1135 Ignores None parameters.
1136 """
1137 if oSession is not None:
1138 oSession.unlockMachine()
1139 return True
1140
1141 def getPerfCollector(self, oIVBox):
1142 """
1143 Returns a helper class (PerfCollector) for accessing performance
1144 collector goodies. See PerfCollector for details.
1145 """
1146 return PerfCollector(self, oIVBox)
1147
1148 def getBinDir(self):
1149 """
1150 Returns the VirtualBox binary directory.
1151 """
1152 global VBoxBinDir
1153 return VBoxBinDir
1154
1155 def getSdkDir(self):
1156 """
1157 Returns the VirtualBox SDK directory.
1158 """
1159 global VBoxSdkDir
1160 return VBoxSdkDir
1161
1162 def getEnumValueName(self, sEnumTypeNm, oEnumValue, fTypePrefix = False):
1163 """
1164 Returns the name (string) for the corresponding enum value.
1165 """
1166 # Cache lookup:
1167 dValueNames = self._ddEnumValueToName.get(sEnumTypeNm);
1168 if dValueNames is not None:
1169 sValueName = dValueNames.get(oEnumValue);
1170 if sValueName:
1171 return sValueName if not fTypePrefix else '%s_%s' % (sEnumTypeNm, sValueName);
1172 else:
1173 # Cache miss. Build the reverse lookup dictionary and add it to the cache:
1174 dNamedValues = self.constants.all_values(sEnumTypeNm);
1175 if len(dNamedValues) > 0:
1176
1177 dValueNames = dict();
1178 for sName in dNamedValues:
1179 dValueNames[dNamedValues[sName]] = sName;
1180 self._ddEnumValueToName[sEnumTypeNm] = dValueNames;
1181
1182 # Lookup the value:
1183 sValueName = dValueNames.get(oEnumValue);
1184 if sValueName:
1185 return sValueName if not fTypePrefix else '%s_%s' % (sEnumTypeNm, sValueName);
1186
1187 # Fallback:
1188 return '%s_Unknown_%s' % (sEnumTypeNm, oEnumValue);
1189
1190 #
1191 # Error code utilities.
1192 #
1193 ## @todo port to webservices!
1194 def xcptGetStatus(self, oXcpt=None):
1195 """
1196 Gets the status code from an exception. If the exception parameter
1197 isn't specified, the current exception is examined.
1198 """
1199 if oXcpt is None:
1200 oXcpt = sys.exc_info()[1]
1201 return self.platform.xcptGetStatus(oXcpt)
1202
1203 def xcptIsDeadInterface(self, oXcpt=None):
1204 """
1205 Returns True if the exception indicates that the interface is dead,
1206 False if not. If the exception parameter isn't specified, the current
1207 exception is examined.
1208 """
1209 if oXcpt is None:
1210 oXcpt = sys.exc_info()[1]
1211 return self.platform.xcptIsDeadInterface(oXcpt)
1212
1213 def xcptIsOurXcptKind(self, oXcpt=None):
1214 """
1215 Checks if the exception is one that could come from the VBox API. If
1216 the exception parameter isn't specified, the current exception is
1217 examined.
1218 """
1219 if self.oXcptClass is None: # @todo find the exception class for web services!
1220 return False
1221 if oXcpt is None:
1222 oXcpt = sys.exc_info()[1]
1223 return isinstance(oXcpt, self.oXcptClass)
1224
1225 def xcptIsEqual(self, oXcpt, hrStatus):
1226 """
1227 Checks if the exception oXcpt is equal to the COM/XPCOM status code
1228 hrStatus.
1229
1230 The oXcpt parameter can be any kind of object, we'll just return True
1231 if it doesn't behave like a our exception class. If it's None, we'll
1232 query the current exception and examine that.
1233
1234 Will not raise any exception as long as hrStatus and self are not bad.
1235 """
1236 if oXcpt is None:
1237 oXcpt = sys.exc_info()[1]
1238 return self.platform.xcptIsEqual(oXcpt, hrStatus)
1239
1240 def xcptIsNotEqual(self, oXcpt, hrStatus):
1241 """
1242 Negated xcptIsEqual.
1243 """
1244 return not self.xcptIsEqual(oXcpt, hrStatus)
1245
1246 def xcptToString(self, hrStatusOrXcpt=None):
1247 """
1248 Converts the specified COM status code, or the status code of the
1249 specified exception, to a C constant string. If the parameter isn't
1250 specified (is None), the current exception is examined.
1251 """
1252
1253 # Deal with exceptions.
1254 if hrStatusOrXcpt is None or self.xcptIsOurXcptKind(hrStatusOrXcpt):
1255 hrStatus = self.xcptGetStatus(hrStatusOrXcpt)
1256 else:
1257 hrStatus = hrStatusOrXcpt
1258
1259 # Build the dictionary on demand.
1260 if self._dErrorValToName is None:
1261 dErrorValToName = dict()
1262 for sKey in dir(self.statuses):
1263 if sKey[0].isupper():
1264 oValue = getattr(self.statuses, sKey)
1265 if isinstance(oValue, (int, long)):
1266 dErrorValToName[int(oValue)] = sKey
1267 # Always prefer the COM names (see aliasing in platform specific code):
1268 for sKey in ('S_OK', 'E_FAIL', 'E_ABORT', 'E_POINTER', 'E_NOINTERFACE', 'E_INVALIDARG',
1269 'E_OUTOFMEMORY', 'E_NOTIMPL', 'E_UNEXPECTED',):
1270 oValue = getattr(self.statuses, sKey, None)
1271 if oValue is not None:
1272 dErrorValToName[oValue] = sKey
1273 self._dErrorValToName = dErrorValToName
1274
1275 # Do the lookup, falling back on formatting the status number.
1276 try:
1277 sStr = self._dErrorValToName[int(hrStatus)]
1278 except KeyError:
1279 hrLong = long(hrStatus)
1280 sStr = '%#x (%d)' % (hrLong & 0xffffffff, hrLong)
1281 return sStr
1282
1283 def xcptGetMessage(self, oXcpt=None):
1284 """
1285 Returns the best error message found in the COM-like exception. If the
1286 exception parameter isn't specified, the current exception is examined.
1287 """
1288 if oXcpt is None:
1289 oXcpt = sys.exc_info()[1]
1290 sRet = self.platform.xcptGetMessage(oXcpt)
1291 if sRet is None:
1292 sRet = self.xcptToString(oXcpt)
1293 return sRet
1294
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use