VirtualBox

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

Last change on this file since 106061 was 106061, checked in by vboxsync, 4 days 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 106061 2024-09-16 14:03:52Z vboxsync $
3# pylint: disable=import-error -- for cross-platform Win32 imports
4# pylint: disable=unused-import
5# pylint: disable=protected-access -- for XPCOM _xpcom member
6"""
7VirtualBox Python API Glue.
8"""
9
10__copyright__ = \
11"""
12Copyright (C) 2009-2024 Oracle and/or its affiliates.
13
14This file is part of VirtualBox base platform packages, as
15available from https://www.virtualbox.org.
16
17This program is free software; you can redistribute it and/or
18modify it under the terms of the GNU General Public License
19as published by the Free Software Foundation, in version 3 of the
20License.
21
22This program is distributed in the hope that it will be useful, but
23WITHOUT ANY WARRANTY; without even the implied warranty of
24MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
25General Public License for more details.
26
27You should have received a copy of the GNU General Public License
28along with this program; if not, see <https://www.gnu.org/licenses>.
29
30The contents of this file may alternatively be used under the terms
31of the Common Development and Distribution License Version 1.0
32(CDDL), a copy of it is provided in the "COPYING.CDDL" file included
33in the VirtualBox distribution, in which case the provisions of the
34CDDL are applicable instead of those of the GPL.
35
36You may elect to license modified versions of this file under the
37terms and conditions of either the GPL or the CDDL or both.
38
39SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
40"""
41__version__ = "$Revision: 106061 $"
42
43
44# Note! To set Python bitness on OSX use 'export VERSIONER_PYTHON_PREFER_32_BIT=yes'
45
46
47# Standard Python imports.
48import os
49import sys
50import traceback
51
52
53if sys.version_info >= (3, 0):
54 xrange = range # pylint: disable=invalid-name
55 long = int # pylint: disable=invalid-name
56
57#
58# Globals, environment and sys.path changes.
59#
60import platform
61g_sVBoxBinDir = os.environ.get("VBOX_PROGRAM_PATH", None)
62g_sVBoxSdkDir = os.environ.get("VBOX_SDK_PATH", None)
63
64if g_sVBoxBinDir is None:
65 if platform.system() == 'Darwin':
66 g_sVBoxBinDir = '/Applications/VirtualBox.app/Contents/MacOS'
67 else: # Will be set by the installer
68 g_sVBoxBinDir = "%VBOX_INSTALL_PATH%"
69else:
70 g_sVBoxBinDir = os.path.abspath(g_sVBoxBinDir)
71
72if g_sVBoxSdkDir is None:
73 if platform.system() == 'Darwin':
74 g_sVBoxSdkDir = '/Applications/VirtualBox.app/Contents/MacOS/sdk'
75 else: # Will be set by the installer
76 g_sVBoxSdkDir = "%VBOX_SDK_PATH%"
77else:
78 g_sVBoxSdkDir = os.path.abspath(g_sVBoxSdkDir)
79
80os.environ["VBOX_PROGRAM_PATH"] = g_sVBoxBinDir
81os.environ["VBOX_SDK_PATH"] = g_sVBoxSdkDir
82sys.path.append(g_sVBoxBinDir)
83
84
85#
86# Import the generated VirtualBox constants.
87#
88from .VirtualBox_constants import VirtualBoxReflectionInfo
89
90
91class PerfCollector(object):
92 """ This class provides a wrapper over IPerformanceCollector in order to
93 get more 'pythonic' interface.
94
95 To begin collection of metrics use setup() method.
96
97 To get collected data use query() method.
98
99 It is possible to disable metric collection without changing collection
100 parameters with disable() method. The enable() method resumes metric
101 collection.
102 """
103
104 def __init__(self, mgr, vbox):
105 """ Initializes the instance.
106
107 """
108 self.mgr = mgr
109 self.isMscom = mgr.type == 'MSCOM'
110 self.collector = vbox.performanceCollector
111
112 def setup(self, names, objects, period, nsamples):
113 """ Discards all previously collected values for the specified
114 metrics, sets the period of collection and the number of retained
115 samples, enables collection.
116 """
117 self.collector.setupMetrics(names, objects, period, nsamples)
118
119 def enable(self, names, objects):
120 """ Resumes metric collection for the specified metrics.
121 """
122 self.collector.enableMetrics(names, objects)
123
124 def disable(self, names, objects):
125 """ Suspends metric collection for the specified metrics.
126 """
127 self.collector.disableMetrics(names, objects)
128
129 def query(self, names, objects):
130 """ Retrieves collected metric values as well as some auxiliary
131 information. Returns an array of dictionaries, one dictionary per
132 metric. Each dictionary contains the following entries:
133 'name': metric name
134 'object': managed object this metric associated with
135 'unit': unit of measurement
136 'scale': divide 'values' by this number to get float numbers
137 'values': collected data
138 'values_as_string': pre-processed values ready for 'print' statement
139 """
140 # Get around the problem with input arrays returned in output
141 # parameters (see #3953) for MSCOM.
142 if self.isMscom:
143 (values, names, objects, names_out, objects_out, units, scales, _sequence_numbers,
144 indices, lengths) = self.collector.queryMetricsData(names, objects)
145 else:
146 (values, names_out, objects_out, units, scales, _sequence_numbers,
147 indices, lengths) = self.collector.queryMetricsData(names, objects)
148 out = []
149 for i, _ in enumerate(names_out):
150 scale = int(scales[i])
151 if scale != 1:
152 fmt = '%.2f%s'
153 else:
154 fmt = '%d %s'
155 out.append({
156 'name': str(names_out[i]),
157 'object': str(objects_out[i]),
158 'unit': str(units[i]),
159 'scale': scale,
160 'values': [int(values[j]) for j in xrange(int(indices[i]), int(indices[i]) + int(lengths[i]))],
161 'values_as_string': '[' + ', '.join([fmt % (int(values[j]) / scale, units[i]) for j in
162 xrange(int(indices[i]), int(indices[i]) + int(lengths[i]))]) + ']'
163 })
164 return out
165
166
167#
168# Attribute hacks.
169#
170def comifyName(name):
171 return name[0].capitalize() + name[1:]
172
173
174## This is for saving the original DispatchBaseClass __getattr__ and __setattr__
175# method references.
176_g_dCOMForward = {}
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) # pylint: disable=assignment-from-none
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 isinstance(oAttr, 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 self.oHandle = None;
450
451 # Setup client impersonation in COM calls.
452 try:
453 pythoncom.CoInitializeSecurity(None,
454 None,
455 None,
456 pythoncom.RPC_C_AUTHN_LEVEL_DEFAULT,
457 pythoncom.RPC_C_IMP_LEVEL_IMPERSONATE,
458 None,
459 pythoncom.EOAC_NONE,
460 None)
461 except:
462 _, oXcpt, _ = sys.exc_info();
463 if isinstance(oXcpt, pythoncom.com_error) and self.xcptGetStatus(oXcpt) == -2147417831: # RPC_E_TOO_LATE
464 print("Warning: CoInitializeSecurity was already called");
465 else:
466 print("Warning: CoInitializeSecurity failed: ", oXcpt);
467
468 # Remember this thread ID and get its handle so we can wait on it in waitForEvents().
469 self.tid = GetCurrentThreadId()
470 pid = GetCurrentProcess()
471 self.aoHandles = [DuplicateHandle(pid, GetCurrentThread(), pid, 0, 0, DUPLICATE_SAME_ACCESS),] # type: list[PyHANDLE]
472
473 # Hack the COM dispatcher base class so we can modify method and
474 # attribute names to match those in xpcom.
475 if 'setattr' not in _g_dCOMForward:
476 _g_dCOMForward['getattr'] = DispatchBaseClass.__dict__['__getattr__'] # before setattr which we test for.
477 _g_dCOMForward['setattr'] = DispatchBaseClass.__dict__['__setattr__']
478 setattr(DispatchBaseClass, '__getattr__', _CustomGetAttr)
479 setattr(DispatchBaseClass, '__setattr__', _CustomSetAttr)
480
481 # Hack the exception base class so the users doesn't need to check for
482 # XPCOM or COM and do different things.
483 ## @todo
484
485 #
486 # Make sure the gencache is correct (we don't quite follow the COM
487 # versioning rules).
488 #
489 self.flushGenPyCache(win32com.client.gencache)
490 win32com.client.gencache.EnsureDispatch('VirtualBox.Session')
491 win32com.client.gencache.EnsureDispatch('VirtualBox.VirtualBox')
492 win32com.client.gencache.EnsureDispatch('VirtualBox.VirtualBoxClient')
493
494 self.oClient = None ##< instance of client used to support lifetime of VBoxSDS
495 self.oIntCv = threading.Condition()
496 self.fInterrupted = False
497
498 _ = dParams
499
500 def flushGenPyCache(self, oGenCache):
501 """
502 Flushes VBox related files in the win32com gen_py cache.
503
504 This is necessary since we don't follow the typelib versioning rules
505 that everyeone else seems to subscribe to.
506 """
507 #
508 # The EnsureModule method have broken validation code, it doesn't take
509 # typelib module directories into account. So we brute force them here.
510 # (It's possible the directory approach is from some older pywin
511 # version or the result of runnig makepy or gencache manually, but we
512 # need to cover it as well.)
513 #
514 sName = oGenCache.GetGeneratedFileName(self.VBOX_TLB_GUID, self.VBOX_TLB_LCID,
515 self.VBOX_TLB_MAJOR, self.VBOX_TLB_MINOR)
516 sGenPath = oGenCache.GetGeneratePath()
517 if len(sName) > 36 and len(sGenPath) > 5:
518 sTypelibPath = os.path.join(sGenPath, sName)
519 if os.path.isdir(sTypelibPath):
520 import shutil
521 shutil.rmtree(sTypelibPath, ignore_errors=True)
522
523 #
524 # Ensure that our typelib is valid.
525 #
526 return oGenCache.EnsureModule(self.VBOX_TLB_GUID, self.VBOX_TLB_LCID, self.VBOX_TLB_MAJOR, self.VBOX_TLB_MINOR)
527
528 def getSessionObject(self):
529 import win32com
530 from win32com.client import Dispatch
531 return win32com.client.Dispatch("VirtualBox.Session")
532
533 def getVirtualBox(self):
534 # Caching self.oClient is the trick for SDS. It allows to keep the
535 # VBoxSDS in the memory until the end of PlatformMSCOM lifetme.
536 if self.oClient is None:
537 import win32com
538 from win32com.client import Dispatch
539 self.oClient = win32com.client.Dispatch("VirtualBox.VirtualBoxClient")
540 return self.oClient.virtualBox
541
542 def getType(self):
543 return 'MSCOM'
544
545 def getArray(self, oInterface, sAttrib):
546 return oInterface.__getattr__(sAttrib) # pylint: disable=unnecessary-dunder-call
547
548 def setArray(self, oInterface, sAttrib, aoArray):
549 #
550 # HACK ALERT!
551 #
552 # With pywin32 build 218, we're seeing type mismatch errors here for
553 # IGuestSession::environmentChanges (safearray of BSTRs). The Dispatch
554 # object (_oleobj_) seems to get some type conversion wrong and COM
555 # gets upset. So, we redo some of the dispatcher work here, picking
556 # the missing type information from the getter.
557 #
558 oOleObj = getattr(oInterface, '_oleobj_')
559 aPropMapGet = getattr(oInterface, '_prop_map_get_')
560 aPropMapPut = getattr(oInterface, '_prop_map_put_')
561 sComAttrib = sAttrib if sAttrib in aPropMapGet else comifyName(sAttrib)
562 try:
563 aArgs, _aDefaultArgs = aPropMapPut[sComAttrib]
564 aGetArgs = aPropMapGet[sComAttrib]
565 except KeyError: # fallback.
566 return oInterface.__setattr__(sAttrib, aoArray) # pylint: disable=unnecessary-dunder-call
567
568 import pythoncom
569 oOleObj.InvokeTypes(aArgs[0], # dispid
570 aArgs[1], # LCID
571 aArgs[2], # DISPATCH_PROPERTYPUT
572 (pythoncom.VT_HRESULT, 0), # retType - or void?
573 (aGetArgs[2],), # argTypes - trick: we get the type from the getter.
574 aoArray,) # The array
575 return True
576
577 def initPerThread(self):
578 import pythoncom
579 pythoncom.CoInitializeEx(0)
580
581 def deinitPerThread(self):
582 import pythoncom
583 pythoncom.CoUninitialize()
584
585 def createListener(self, oImplClass, dArgs):
586 _ = oImplClass; _ = dArgs;
587 raise Exception('no active listeners on Windows as PyGatewayBase::QueryInterface() '
588 'returns new gateway objects all the time, thus breaking EventQueue '
589 'assumptions about the listener interface pointer being constants between calls ')
590
591 def waitForEvents(self, cMsTimeout):
592 from win32api import GetCurrentThreadId
593 from win32event import INFINITE
594 from win32event import MsgWaitForMultipleObjects, QS_ALLINPUT, WAIT_TIMEOUT, WAIT_OBJECT_0
595 from pythoncom import PumpWaitingMessages
596 import types
597
598 if not isinstance(cMsTimeout, int):
599 raise TypeError("The timeout argument is not an integer")
600 if self.tid != GetCurrentThreadId():
601 raise Exception("wait for events from the same thread you inited!")
602
603 if cMsTimeout < 0:
604 cMsTimeout = INFINITE
605 rc = MsgWaitForMultipleObjects(self.aoHandles, 0, cMsTimeout, QS_ALLINPUT)
606 if WAIT_OBJECT_0 <= rc < WAIT_OBJECT_0 + len(self.aoHandles):
607 # is it possible?
608 rc = 2
609 elif rc == WAIT_OBJECT_0 + len(self.aoHandles):
610 # Waiting messages
611 PumpWaitingMessages()
612 rc = 0
613 else:
614 # Timeout
615 rc = 1
616
617 # check for interruption
618 self.oIntCv.acquire()
619 if self.fInterrupted:
620 self.fInterrupted = False
621 rc = 1
622 self.oIntCv.release()
623
624 return rc
625
626 def interruptWaitEvents(self):
627 """
628 Basically a python implementation of NativeEventQueue::postEvent().
629
630 The magic value must be in sync with the C++ implementation or this
631 won't work.
632
633 Note that because of this method we cannot easily make use of a
634 non-visible Window to handle the message like we would like to do.
635 """
636 from win32api import PostThreadMessage
637 from win32con import WM_USER
638
639 self.oIntCv.acquire()
640 self.fInterrupted = True
641 self.oIntCv.release()
642 try:
643 PostThreadMessage(self.tid, WM_USER, None, 0xf241b819)
644 except:
645 return False
646 return True
647
648 def deinit(self):
649 for oHandle in self.aoHandles:
650 if oHandle is not None:
651 oHandle.Close();
652 self.oHandle = None;
653
654 del self.oClient;
655 self.oClient = None;
656
657 # This non-sense doesn't pair up with any pythoncom.CoInitialize[Ex].
658 # See @bugref{9037}.
659 #import pythoncom
660 #pythoncom.CoUninitialize()
661
662 def queryInterface(self, oIUnknown, sClassName):
663 from win32com.client import CastTo
664 return CastTo(oIUnknown, sClassName)
665
666 def xcptGetStatus(self, oXcpt):
667 # The DISP_E_EXCEPTION + excptinfo fun needs checking up, only
668 # empirical info on it so far.
669 hrXcpt = oXcpt.hresult
670 if hrXcpt == self.winerror.DISP_E_EXCEPTION:
671 try:
672 hrXcpt = oXcpt.excepinfo[5]
673 except:
674 pass
675 return hrXcpt
676
677 def xcptIsDeadInterface(self, oXcpt):
678 return self.xcptGetStatus(oXcpt) in [
679 0x800706ba, -2147023174, # RPC_S_SERVER_UNAVAILABLE.
680 0x800706be, -2147023170, # RPC_S_CALL_FAILED.
681 0x800706bf, -2147023169, # RPC_S_CALL_FAILED_DNE.
682 0x80010108, -2147417848, # RPC_E_DISCONNECTED.
683 0x800706b5, -2147023179, # RPC_S_UNKNOWN_IF
684 ]
685
686 def xcptGetMessage(self, oXcpt):
687 if hasattr(oXcpt, 'excepinfo'):
688 try:
689 if len(oXcpt.excepinfo) >= 3:
690 sRet = oXcpt.excepinfo[2]
691 if len(sRet) > 0:
692 return sRet[0:]
693 except:
694 pass
695 if hasattr(oXcpt, 'strerror'):
696 try:
697 sRet = oXcpt.strerror
698 if len(sRet) > 0:
699 return sRet
700 except:
701 pass
702 return None
703
704 def xcptGetBaseXcpt(self):
705 import pythoncom
706
707 return pythoncom.com_error
708
709 def xcptSetupConstants(self, oDst):
710 import winerror
711
712 oDst = self.xcptCopyErrorConstants(oDst, winerror)
713
714 # XPCOM compatability constants.
715 oDst.NS_OK = oDst.S_OK
716 oDst.NS_ERROR_FAILURE = oDst.E_FAIL
717 oDst.NS_ERROR_ABORT = oDst.E_ABORT
718 oDst.NS_ERROR_NULL_POINTER = oDst.E_POINTER
719 oDst.NS_ERROR_NO_INTERFACE = oDst.E_NOINTERFACE
720 oDst.NS_ERROR_INVALID_ARG = oDst.E_INVALIDARG
721 oDst.NS_ERROR_OUT_OF_MEMORY = oDst.E_OUTOFMEMORY
722 oDst.NS_ERROR_NOT_IMPLEMENTED = oDst.E_NOTIMPL
723 oDst.NS_ERROR_UNEXPECTED = oDst.E_UNEXPECTED
724 return oDst
725
726
727class PlatformXPCOM(PlatformBase):
728 """
729 Platform specific code for XPCOM.
730 """
731
732 def __init__(self, dParams):
733 PlatformBase.__init__(self, dParams)
734 sys.path.append(g_sVBoxSdkDir + '/bindings/xpcom/python/')
735 import xpcom.vboxxpcom
736 import xpcom
737 import xpcom.components
738 _ = dParams
739
740 def getSessionObject(self):
741 import xpcom.components
742 return xpcom.components.classes["@virtualbox.org/Session;1"].createInstance()
743
744 def getVirtualBox(self):
745 import xpcom.components
746 client = xpcom.components.classes["@virtualbox.org/VirtualBoxClient;1"].createInstance()
747 return client.virtualBox
748
749 def getType(self):
750 return 'XPCOM'
751
752 def getArray(self, oInterface, sAttrib):
753 return oInterface.__getattr__('get' + comifyName(sAttrib))() # pylint: disable=unnecessary-dunder-call
754
755 def setArray(self, oInterface, sAttrib, aoArray):
756 return oInterface.__getattr__('set' + comifyName(sAttrib))(aoArray) # pylint: disable=unnecessary-dunder-call
757
758 def initPerThread(self):
759 import xpcom
760 xpcom._xpcom.AttachThread()
761
762 def deinitPerThread(self):
763 import xpcom
764 xpcom._xpcom.DetachThread()
765
766 def createListener(self, oImplClass, dArgs):
767 notDocumentedDict = {}
768 notDocumentedDict['BaseClass'] = oImplClass
769 notDocumentedDict['dArgs'] = dArgs
770 sEval = ""
771 sEval += "import xpcom.components\n"
772 sEval += "class ListenerImpl(BaseClass):\n"
773 sEval += " _com_interfaces_ = xpcom.components.interfaces.IEventListener\n"
774 sEval += " def __init__(self): BaseClass.__init__(self, dArgs)\n"
775 sEval += "result = ListenerImpl()\n"
776 exec(sEval, notDocumentedDict, notDocumentedDict) # pylint: disable=exec-used
777 return notDocumentedDict['result']
778
779 def waitForEvents(self, cMsTimeout):
780 import xpcom
781 return xpcom._xpcom.WaitForEvents(cMsTimeout)
782
783 def interruptWaitEvents(self):
784 import xpcom
785 return xpcom._xpcom.InterruptWait()
786
787 def deinit(self):
788 import xpcom
789 xpcom._xpcom.DeinitCOM()
790
791 def queryInterface(self, oIUnknown, sClassName):
792 import xpcom.components
793 return oIUnknown.queryInterface(getattr(xpcom.components.interfaces, sClassName))
794
795 def xcptGetStatus(self, oXcpt):
796 return oXcpt.errno
797
798 def xcptIsDeadInterface(self, oXcpt):
799 return self.xcptGetStatus(oXcpt) in [
800 0x80004004, -2147467260, # NS_ERROR_ABORT
801 0x800706be, -2147023170, # NS_ERROR_CALL_FAILED (RPC_S_CALL_FAILED)
802 ]
803
804 def xcptGetMessage(self, oXcpt):
805 if hasattr(oXcpt, 'msg'):
806 try:
807 sRet = oXcpt.msg
808 if len(sRet) > 0:
809 return sRet
810 except:
811 pass
812 return None
813
814 def xcptGetBaseXcpt(self):
815 import xpcom
816 return xpcom.Exception
817
818 def xcptSetupConstants(self, oDst):
819 import xpcom
820 oDst = self.xcptCopyErrorConstants(oDst, xpcom.nsError)
821
822 # COM compatability constants.
823 oDst.E_ACCESSDENIED = -2147024891 # see VBox/com/defs.h
824 oDst.S_OK = oDst.NS_OK
825 oDst.E_FAIL = oDst.NS_ERROR_FAILURE
826 oDst.E_ABORT = oDst.NS_ERROR_ABORT
827 oDst.E_POINTER = oDst.NS_ERROR_NULL_POINTER
828 oDst.E_NOINTERFACE = oDst.NS_ERROR_NO_INTERFACE
829 oDst.E_INVALIDARG = oDst.NS_ERROR_INVALID_ARG
830 oDst.E_OUTOFMEMORY = oDst.NS_ERROR_OUT_OF_MEMORY
831 oDst.E_NOTIMPL = oDst.NS_ERROR_NOT_IMPLEMENTED
832 oDst.E_UNEXPECTED = oDst.NS_ERROR_UNEXPECTED
833 oDst.DISP_E_EXCEPTION = -2147352567 # For COM compatability only.
834 return oDst
835
836
837class PlatformWEBSERVICE(PlatformBase):
838 """
839 VirtualBox Web Services API specific code.
840 """
841
842 def __init__(self, dParams):
843 PlatformBase.__init__(self, dParams)
844 # Import web services stuff. Fix the sys.path the first time.
845 sWebServLib = os.path.join(g_sVBoxSdkDir, 'bindings', 'webservice', 'python', 'lib')
846 if sWebServLib not in sys.path:
847 sys.path.append(sWebServLib)
848 import VirtualBox_wrappers
849 from VirtualBox_wrappers import IWebsessionManager2
850
851 # Initialize instance variables from parameters.
852 if dParams is not None:
853 self.user = dParams.get("user", "")
854 self.password = dParams.get("password", "")
855 self.url = dParams.get("url", "")
856 else:
857 self.user = ""
858 self.password = ""
859 self.url = None
860 self.vbox = None
861 self.wsmgr = None
862
863 #
864 # Base class overrides.
865 #
866
867 def getSessionObject(self):
868 return self.wsmgr.getSessionObject(self.vbox)
869
870 def getVirtualBox(self):
871 return self.connect(self.url, self.user, self.password)
872
873 def getType(self):
874 return 'WEBSERVICE'
875
876 def isRemote(self):
877 """ Returns True if remote VBox host, False if local. """
878 return True
879
880 def getArray(self, oInterface, sAttrib):
881 return oInterface.__getattr__(sAttrib) # pylint: disable=unnecessary-dunder-call
882
883 def setArray(self, oInterface, sAttrib, aoArray):
884 return oInterface.__setattr__(sAttrib, aoArray) # pylint: disable=unnecessary-dunder-call
885
886 def waitForEvents(self, _timeout):
887 # Webservices cannot do that yet
888 return 2
889
890 def interruptWaitEvents(self):
891 # Webservices cannot do that yet
892 return False
893
894 def deinit(self):
895 try:
896 self.disconnect()
897 except:
898 pass
899
900 def queryInterface(self, oIUnknown, sClassName):
901 notDocumentedDict = {}
902 notDocumentedDict['oIUnknown'] = oIUnknown
903 sEval = ""
904 sEval += "from VirtualBox_wrappers import " + sClassName + "\n"
905 sEval += "result = " + sClassName + "(oIUnknown.mgr, oIUnknown.handle)\n"
906 # wrong, need to test if class indeed implements this interface
907 exec(sEval, notDocumentedDict, notDocumentedDict) # pylint: disable=exec-used
908 return notDocumentedDict['result']
909
910 #
911 # Web service specific methods.
912 #
913
914 def connect(self, url, user, passwd):
915 if self.vbox is not None:
916 self.disconnect()
917 from VirtualBox_wrappers import IWebsessionManager2
918
919 if url is None:
920 url = ""
921 self.url = url
922 if user is None:
923 user = ""
924 self.user = user
925 if passwd is None:
926 passwd = ""
927 self.password = passwd
928 self.wsmgr = IWebsessionManager2(self.url)
929 self.vbox = self.wsmgr.logon(self.user, self.password)
930 if not self.vbox.handle:
931 raise Exception("cannot connect to '" + self.url + "' as '" + self.user + "'")
932 return self.vbox
933
934 def disconnect(self):
935 if self.vbox is not None and self.wsmgr is not None:
936 self.wsmgr.logoff(self.vbox)
937 self.vbox = None
938 self.wsmgr = None
939
940
941## The current (last) exception class.
942# This is reinitalized whenever VirtualBoxManager is called, so it will hold
943# the reference to the error exception class for the last platform/style that
944# was used. Most clients does not talk to multiple VBox instance on different
945# platforms at the same time, so this should be sufficent for most uses and
946# be way simpler to use than VirtualBoxManager::oXcptClass.
947g_oCurXcptClass = None
948
949
950class VirtualBoxManager(object):
951 """
952 VirtualBox API manager class.
953
954 The API users will have to instantiate this. If no parameters are given,
955 it will default to interface with the VirtualBox running on the local
956 machine. sStyle can be None (default), MSCOM, XPCOM or WEBSERVICES. Most
957 users will either be specifying None or WEBSERVICES.
958
959 The dPlatformParams is an optional dictionary for passing parameters to the
960 WEBSERVICE backend.
961 """
962
963 class Statuses(object):
964 def __init__(self):
965 pass
966
967 def __init__(self, sStyle=None, dPlatformParams=None):
968
969 # Deprecation warning for older Python stuff (< Python 3.x).
970 if sys.version_info.major < 3:
971 print("\nWarning: Running VirtualBox with Python %d.%d is marked as being deprecated.\n" \
972 "Please upgrade your Python installation to avoid breakage.\n" \
973 % (sys.version_info.major, sys.version_info.minor))
974
975 if sStyle is None:
976 if sys.platform == 'win32':
977 sStyle = "MSCOM"
978 else:
979 sStyle = "XPCOM"
980 if sStyle == 'XPCOM':
981 self.platform = PlatformXPCOM(dPlatformParams)
982 elif sStyle == 'MSCOM':
983 self.platform = PlatformMSCOM(dPlatformParams)
984 elif sStyle == 'WEBSERVICE':
985 self.platform = PlatformWEBSERVICE(dPlatformParams)
986 else:
987 raise Exception('Unknown sStyle=%s' % (sStyle,))
988 self.style = sStyle
989 self.type = self.platform.getType()
990 self.remote = self.platform.isRemote()
991 ## VirtualBox API constants (for webservices, enums are symbolic).
992 self.constants = VirtualBoxReflectionInfo(sStyle == "WEBSERVICE")
993
994 ## Status constants.
995 self.statuses = self.platform.xcptSetupConstants(VirtualBoxManager.Statuses())
996 ## @todo Add VBOX_E_XXX to statuses? They're already in constants...
997 ## Dictionary for errToString, built on demand.
998 self._dErrorValToName = None
999
1000 ## Dictionary for resolving enum values to names, two levels of dictionaries.
1001 ## First level is indexed by enum name, the next by value.
1002 self._ddEnumValueToName = {};
1003
1004 ## The exception class for the selected platform.
1005 self.oXcptClass = self.platform.xcptGetBaseXcpt()
1006 global g_oCurXcptClass
1007 g_oCurXcptClass = self.oXcptClass
1008
1009 # Get the virtualbox singleton.
1010 try:
1011 self.platform.getVirtualBox()
1012 except NameError:
1013 print("Installation problem: check that appropriate libs in place")
1014 traceback.print_exc()
1015 raise
1016 except Exception:
1017 _, e, _ = sys.exc_info()
1018 print("init exception: ", e)
1019 traceback.print_exc()
1020
1021 def __del__(self):
1022 self.deinit()
1023
1024 def getPythonApiRevision(self):
1025 """
1026 Returns a Python API revision number.
1027 This will be incremented when features are added to this file.
1028 """
1029 return 3
1030
1031 @property
1032 def mgr(self):
1033 """
1034 This used to be an attribute referring to a session manager class with
1035 only one method called getSessionObject. It moved into this class.
1036 """
1037 return self
1038
1039 #
1040 # Wrappers for self.platform methods.
1041 #
1042 def getVirtualBox(self):
1043 """ See PlatformBase::getVirtualBox(). """
1044 return self.platform.getVirtualBox()
1045
1046 def getSessionObject(self, oIVBox = None):
1047 """ See PlatformBase::getSessionObject(). """
1048 # ignore parameter which was never needed
1049 _ = oIVBox
1050 return self.platform.getSessionObject()
1051
1052 def getArray(self, oInterface, sAttrib):
1053 """ See PlatformBase::getArray(). """
1054 return self.platform.getArray(oInterface, sAttrib)
1055
1056 def setArray(self, oInterface, sAttrib, aoArray):
1057 """ See PlatformBase::setArray(). """
1058 return self.platform.setArray(oInterface, sAttrib, aoArray)
1059
1060 def createListener(self, oImplClass, dArgs=None):
1061 """ See PlatformBase::createListener(). """
1062 return self.platform.createListener(oImplClass, dArgs)
1063
1064 def waitForEvents(self, cMsTimeout):
1065 """ See PlatformBase::waitForEvents(). """
1066 return self.platform.waitForEvents(cMsTimeout)
1067
1068 def interruptWaitEvents(self):
1069 """ See PlatformBase::interruptWaitEvents(). """
1070 return self.platform.interruptWaitEvents()
1071
1072 def queryInterface(self, oIUnknown, sClassName):
1073 """ See PlatformBase::queryInterface(). """
1074 return self.platform.queryInterface(oIUnknown, sClassName)
1075
1076 #
1077 # Init and uninit.
1078 #
1079 def initPerThread(self):
1080 """ See PlatformBase::deinitPerThread(). """
1081 self.platform.initPerThread()
1082
1083 def deinitPerThread(self):
1084 """ See PlatformBase::deinitPerThread(). """
1085 return self.platform.deinitPerThread()
1086
1087 def deinit(self):
1088 """
1089 For unitializing the manager.
1090 Do not access it after calling this method.
1091 """
1092 if hasattr(self, "platform") and self.platform is not None:
1093 self.platform.deinit()
1094 self.platform = None
1095 return True
1096
1097 #
1098 # Utility methods.
1099 #
1100 def openMachineSession(self, oIMachine, fPermitSharing=True):
1101 """
1102 Attempts to open the a session to the machine.
1103 Returns a session object on success.
1104 Raises exception on failure.
1105 """
1106 oSession = self.getSessionObject()
1107 if fPermitSharing:
1108 eType = self.constants.LockType_Shared
1109 else:
1110 eType = self.constants.LockType_Write
1111 oIMachine.lockMachine(oSession, eType)
1112 return oSession
1113
1114 def closeMachineSession(self, oSession):
1115 """
1116 Closes a session opened by openMachineSession.
1117 Ignores None parameters.
1118 """
1119 if oSession is not None:
1120 oSession.unlockMachine()
1121 return True
1122
1123 def getPerfCollector(self, oIVBox):
1124 """
1125 Returns a helper class (PerfCollector) for accessing performance
1126 collector goodies. See PerfCollector for details.
1127 """
1128 return PerfCollector(self, oIVBox)
1129
1130 def getBinDir(self):
1131 """
1132 Returns the VirtualBox binary directory.
1133 """
1134 return g_sVBoxBinDir
1135
1136 def getSdkDir(self):
1137 """
1138 Returns the VirtualBox SDK directory.
1139 """
1140 return g_sVBoxSdkDir
1141
1142 def getEnumValueName(self, sEnumTypeNm, oEnumValue, fTypePrefix = False):
1143 """
1144 Returns the name (string) for the corresponding enum value.
1145 """
1146 # Cache lookup:
1147 dValueNames = self._ddEnumValueToName.get(sEnumTypeNm);
1148 if dValueNames is not None:
1149 sValueName = dValueNames.get(oEnumValue);
1150 if sValueName:
1151 return sValueName if not fTypePrefix else '%s_%s' % (sEnumTypeNm, sValueName);
1152 else:
1153 # Cache miss. Build the reverse lookup dictionary and add it to the cache:
1154 dNamedValues = self.constants.all_values(sEnumTypeNm);
1155 if len(dNamedValues) > 0:
1156
1157 dValueNames = {};
1158 for sName in dNamedValues:
1159 dValueNames[dNamedValues[sName]] = sName;
1160 self._ddEnumValueToName[sEnumTypeNm] = dValueNames;
1161
1162 # Lookup the value:
1163 sValueName = dValueNames.get(oEnumValue);
1164 if sValueName:
1165 return sValueName if not fTypePrefix else '%s_%s' % (sEnumTypeNm, sValueName);
1166
1167 # Fallback:
1168 return '%s_Unknown_%s' % (sEnumTypeNm, oEnumValue);
1169
1170 #
1171 # Error code utilities.
1172 #
1173 ## @todo port to webservices!
1174 def xcptGetStatus(self, oXcpt=None):
1175 """
1176 Gets the status code from an exception. If the exception parameter
1177 isn't specified, the current exception is examined.
1178 """
1179 if oXcpt is None:
1180 oXcpt = sys.exc_info()[1]
1181 return self.platform.xcptGetStatus(oXcpt)
1182
1183 def xcptIsDeadInterface(self, oXcpt=None):
1184 """
1185 Returns True if the exception indicates that the interface is dead,
1186 False if not. If the exception parameter isn't specified, the current
1187 exception is examined.
1188 """
1189 if oXcpt is None:
1190 oXcpt = sys.exc_info()[1]
1191 return self.platform.xcptIsDeadInterface(oXcpt)
1192
1193 def xcptIsOurXcptKind(self, oXcpt=None):
1194 """
1195 Checks if the exception is one that could come from the VBox API. If
1196 the exception parameter isn't specified, the current exception is
1197 examined.
1198 """
1199 if self.oXcptClass is None: # @todo find the exception class for web services!
1200 return False
1201 if oXcpt is None:
1202 oXcpt = sys.exc_info()[1]
1203 return isinstance(oXcpt, self.oXcptClass)
1204
1205 def xcptIsEqual(self, oXcpt, hrStatus):
1206 """
1207 Checks if the exception oXcpt is equal to the COM/XPCOM status code
1208 hrStatus.
1209
1210 The oXcpt parameter can be any kind of object, we'll just return True
1211 if it doesn't behave like a our exception class. If it's None, we'll
1212 query the current exception and examine that.
1213
1214 Will not raise any exception as long as hrStatus and self are not bad.
1215 """
1216 if oXcpt is None:
1217 oXcpt = sys.exc_info()[1]
1218 return self.platform.xcptIsEqual(oXcpt, hrStatus)
1219
1220 def xcptIsNotEqual(self, oXcpt, hrStatus):
1221 """
1222 Negated xcptIsEqual.
1223 """
1224 return not self.xcptIsEqual(oXcpt, hrStatus)
1225
1226 def xcptToString(self, hrStatusOrXcpt=None):
1227 """
1228 Converts the specified COM status code, or the status code of the
1229 specified exception, to a C constant string. If the parameter isn't
1230 specified (is None), the current exception is examined.
1231 """
1232
1233 # Deal with exceptions.
1234 if hrStatusOrXcpt is None or self.xcptIsOurXcptKind(hrStatusOrXcpt):
1235 hrStatus = self.xcptGetStatus(hrStatusOrXcpt)
1236 else:
1237 hrStatus = hrStatusOrXcpt
1238
1239 # Build the dictionary on demand.
1240 if self._dErrorValToName is None:
1241 dErrorValToName = {}
1242 for sKey in dir(self.statuses):
1243 if sKey[0].isupper():
1244 oValue = getattr(self.statuses, sKey)
1245 if isinstance(oValue, (int, long)):
1246 dErrorValToName[int(oValue)] = sKey
1247 # Always prefer the COM names (see aliasing in platform specific code):
1248 for sKey in ('S_OK', 'E_FAIL', 'E_ABORT', 'E_POINTER', 'E_NOINTERFACE', 'E_INVALIDARG',
1249 'E_OUTOFMEMORY', 'E_NOTIMPL', 'E_UNEXPECTED',):
1250 oValue = getattr(self.statuses, sKey, None)
1251 if oValue is not None:
1252 dErrorValToName[oValue] = sKey
1253 self._dErrorValToName = dErrorValToName
1254
1255 # Do the lookup, falling back on formatting the status number.
1256 try:
1257 sStr = self._dErrorValToName[int(hrStatus)]
1258 except KeyError:
1259 hrLong = long(hrStatus)
1260 sStr = '%#x (%d)' % (hrLong & 0xffffffff, hrLong)
1261 return sStr
1262
1263 def xcptGetMessage(self, oXcpt=None):
1264 """
1265 Returns the best error message found in the COM-like exception. If the
1266 exception parameter isn't specified, the current exception is examined.
1267 """
1268 if oXcpt is None:
1269 oXcpt = sys.exc_info()[1]
1270 sRet = self.platform.xcptGetMessage(oXcpt)
1271 if sRet is None:
1272 sRet = self.xcptToString(oXcpt)
1273 return sRet
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