VirtualBox

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

Last change on this file since 90441 was 83155, checked in by vboxsync, 5 years ago

Main/glue/vboxapi.py: fix two typos: oXctp => oXcpt and CurXctpClass => CurXcptClass. The former causing python breakage for a user in the forums.

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette