VirtualBox

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

Last change on this file since 103030 was 103030, checked in by vboxsync, 11 months ago

Main/Python: SCM fix, take 2. bugref:10579

  • 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 103030 2024-01-24 16:15:36Z 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-2023 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: 103030 $"
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 raise AttributeError
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 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 'getattr' not in _g_dCOMForward:
476 _g_dCOMForward['getattr'] = DispatchBaseClass.__dict__['__getattr__']
477 setattr(DispatchBaseClass, '__getattr__', _CustomGetAttr)
478
479 if 'setattr' not in _g_dCOMForward:
480 _g_dCOMForward['setattr'] = DispatchBaseClass.__dict__['__setattr__']
481 setattr(DispatchBaseClass, '__setattr__', _CustomSetAttr)
482
483 # Hack the exception base class so the users doesn't need to check for
484 # XPCOM or COM and do different things.
485 ## @todo
486
487 #
488 # Make sure the gencache is correct (we don't quite follow the COM
489 # versioning rules).
490 #
491 self.flushGenPyCache(win32com.client.gencache)
492 win32com.client.gencache.EnsureDispatch('VirtualBox.Session')
493 win32com.client.gencache.EnsureDispatch('VirtualBox.VirtualBox')
494 win32com.client.gencache.EnsureDispatch('VirtualBox.VirtualBoxClient')
495
496 self.oClient = None ##< instance of client used to support lifetime of VBoxSDS
497 self.oIntCv = threading.Condition()
498 self.fInterrupted = False
499
500 _ = dParams
501
502 def flushGenPyCache(self, oGenCache):
503 """
504 Flushes VBox related files in the win32com gen_py cache.
505
506 This is necessary since we don't follow the typelib versioning rules
507 that everyeone else seems to subscribe to.
508 """
509 #
510 # The EnsureModule method have broken validation code, it doesn't take
511 # typelib module directories into account. So we brute force them here.
512 # (It's possible the directory approach is from some older pywin
513 # version or the result of runnig makepy or gencache manually, but we
514 # need to cover it as well.)
515 #
516 sName = oGenCache.GetGeneratedFileName(self.VBOX_TLB_GUID, self.VBOX_TLB_LCID,
517 self.VBOX_TLB_MAJOR, self.VBOX_TLB_MINOR)
518 sGenPath = oGenCache.GetGeneratePath()
519 if len(sName) > 36 and len(sGenPath) > 5:
520 sTypelibPath = os.path.join(sGenPath, sName)
521 if os.path.isdir(sTypelibPath):
522 import shutil
523 shutil.rmtree(sTypelibPath, ignore_errors=True)
524
525 #
526 # Ensure that our typelib is valid.
527 #
528 return oGenCache.EnsureModule(self.VBOX_TLB_GUID, self.VBOX_TLB_LCID, self.VBOX_TLB_MAJOR, self.VBOX_TLB_MINOR)
529
530 def getSessionObject(self):
531 import win32com
532 from win32com.client import Dispatch
533 return win32com.client.Dispatch("VirtualBox.Session")
534
535 def getVirtualBox(self):
536 # Caching self.oClient is the trick for SDS. It allows to keep the
537 # VBoxSDS in the memory until the end of PlatformMSCOM lifetme.
538 if self.oClient is None:
539 import win32com
540 from win32com.client import Dispatch
541 self.oClient = win32com.client.Dispatch("VirtualBox.VirtualBoxClient")
542 return self.oClient.virtualBox
543
544 def getType(self):
545 return 'MSCOM'
546
547 def getArray(self, oInterface, sAttrib):
548 return getattr(oInterface, sAttrib)
549
550 def setArray(self, oInterface, sAttrib, aoArray):
551 #
552 # HACK ALERT!
553 #
554 # With pywin32 build 218, we're seeing type mismatch errors here for
555 # IGuestSession::environmentChanges (safearray of BSTRs). The Dispatch
556 # object (_oleobj_) seems to get some type conversion wrong and COM
557 # gets upset. So, we redo some of the dispatcher work here, picking
558 # the missing type information from the getter.
559 #
560 oOleObj = getattr(oInterface, '_oleobj_')
561 aPropMapGet = getattr(oInterface, '_prop_map_get_')
562 aPropMapPut = getattr(oInterface, '_prop_map_put_')
563 sComAttrib = sAttrib if sAttrib in aPropMapGet else comifyName(sAttrib)
564 try:
565 aArgs, _aDefaultArgs = aPropMapPut[sComAttrib]
566 aGetArgs = aPropMapGet[sComAttrib]
567 except KeyError: # fallback.
568 return setattr(oInterface, sAttrib, aoArray)
569
570 import pythoncom
571 oOleObj.InvokeTypes(aArgs[0], # dispid
572 aArgs[1], # LCID
573 aArgs[2], # DISPATCH_PROPERTYPUT
574 (pythoncom.VT_HRESULT, 0), # retType - or void?
575 (aGetArgs[2],), # argTypes - trick: we get the type from the getter.
576 aoArray,) # The array
577 return True
578
579 def initPerThread(self):
580 import pythoncom
581 pythoncom.CoInitializeEx(0)
582
583 def deinitPerThread(self):
584 import pythoncom
585 pythoncom.CoUninitialize()
586
587 def createListener(self, oImplClass, dArgs):
588 raise Exception('no active listeners on Windows as PyGatewayBase::QueryInterface() '
589 'returns new gateway objects all the time, thus breaking EventQueue '
590 'assumptions about the listener interface pointer being constants between calls ')
591
592 def waitForEvents(self, cMsTimeout):
593 from win32api import GetCurrentThreadId
594 from win32event import INFINITE
595 from win32event import MsgWaitForMultipleObjects, QS_ALLINPUT, WAIT_TIMEOUT, WAIT_OBJECT_0
596 from pythoncom import PumpWaitingMessages
597 import types
598
599 if not isinstance(cMsTimeout, int):
600 raise TypeError("The timeout argument is not an integer")
601 if self.tid != GetCurrentThreadId():
602 raise Exception("wait for events from the same thread you inited!")
603
604 if cMsTimeout < 0:
605 cMsTimeout = INFINITE
606 rc = MsgWaitForMultipleObjects(self.aoHandles, 0, cMsTimeout, QS_ALLINPUT)
607 if WAIT_OBJECT_0 <= rc < WAIT_OBJECT_0 + len(self.aoHandles):
608 # is it possible?
609 rc = 2
610 elif rc == WAIT_OBJECT_0 + len(self.aoHandles):
611 # Waiting messages
612 PumpWaitingMessages()
613 rc = 0
614 else:
615 # Timeout
616 rc = 1
617
618 # check for interruption
619 self.oIntCv.acquire()
620 if self.fInterrupted:
621 self.fInterrupted = False
622 rc = 1
623 self.oIntCv.release()
624
625 return rc
626
627 def interruptWaitEvents(self):
628 """
629 Basically a python implementation of NativeEventQueue::postEvent().
630
631 The magic value must be in sync with the C++ implementation or this
632 won't work.
633
634 Note that because of this method we cannot easily make use of a
635 non-visible Window to handle the message like we would like to do.
636 """
637 from win32api import PostThreadMessage
638 from win32con import WM_USER
639
640 self.oIntCv.acquire()
641 self.fInterrupted = True
642 self.oIntCv.release()
643 try:
644 PostThreadMessage(self.tid, WM_USER, None, 0xf241b819)
645 except:
646 return False
647 return True
648
649 def deinit(self):
650 for oHandle in self.aoHandles:
651 if oHandle is not None:
652 oHandle.Close();
653 self.oHandle = None;
654
655 del self.oClient;
656 self.oClient = None;
657
658 # This non-sense doesn't pair up with any pythoncom.CoInitialize[Ex].
659 # See @bugref{9037}.
660 #import pythoncom
661 #pythoncom.CoUninitialize()
662
663 def queryInterface(self, oIUnknown, sClassName):
664 from win32com.client import CastTo
665 return CastTo(oIUnknown, sClassName)
666
667 def xcptGetStatus(self, oXcpt):
668 # The DISP_E_EXCEPTION + excptinfo fun needs checking up, only
669 # empirical info on it so far.
670 hrXcpt = oXcpt.hresult
671 if hrXcpt == self.winerror.DISP_E_EXCEPTION:
672 try:
673 hrXcpt = oXcpt.excepinfo[5]
674 except:
675 pass
676 return hrXcpt
677
678 def xcptIsDeadInterface(self, oXcpt):
679 return self.xcptGetStatus(oXcpt) in [
680 0x800706ba, -2147023174, # RPC_S_SERVER_UNAVAILABLE.
681 0x800706be, -2147023170, # RPC_S_CALL_FAILED.
682 0x800706bf, -2147023169, # RPC_S_CALL_FAILED_DNE.
683 0x80010108, -2147417848, # RPC_E_DISCONNECTED.
684 0x800706b5, -2147023179, # RPC_S_UNKNOWN_IF
685 ]
686
687 def xcptGetMessage(self, oXcpt):
688 if hasattr(oXcpt, 'excepinfo'):
689 try:
690 if len(oXcpt.excepinfo) >= 3:
691 sRet = oXcpt.excepinfo[2]
692 if len(sRet) > 0:
693 return sRet[0:]
694 except:
695 pass
696 if hasattr(oXcpt, 'strerror'):
697 try:
698 sRet = oXcpt.strerror
699 if len(sRet) > 0:
700 return sRet
701 except:
702 pass
703 return None
704
705 def xcptGetBaseXcpt(self):
706 import pythoncom
707
708 return pythoncom.com_error
709
710 def xcptSetupConstants(self, oDst):
711 import winerror
712
713 oDst = self.xcptCopyErrorConstants(oDst, winerror)
714
715 # XPCOM compatability constants.
716 oDst.NS_OK = oDst.S_OK
717 oDst.NS_ERROR_FAILURE = oDst.E_FAIL
718 oDst.NS_ERROR_ABORT = oDst.E_ABORT
719 oDst.NS_ERROR_NULL_POINTER = oDst.E_POINTER
720 oDst.NS_ERROR_NO_INTERFACE = oDst.E_NOINTERFACE
721 oDst.NS_ERROR_INVALID_ARG = oDst.E_INVALIDARG
722 oDst.NS_ERROR_OUT_OF_MEMORY = oDst.E_OUTOFMEMORY
723 oDst.NS_ERROR_NOT_IMPLEMENTED = oDst.E_NOTIMPL
724 oDst.NS_ERROR_UNEXPECTED = oDst.E_UNEXPECTED
725 return oDst
726
727
728class PlatformXPCOM(PlatformBase):
729 """
730 Platform specific code for XPCOM.
731 """
732
733 def __init__(self, dParams):
734 PlatformBase.__init__(self, dParams)
735 sys.path.append(g_sVBoxSdkDir + '/bindings/xpcom/python/')
736 import xpcom.vboxxpcom
737 import xpcom
738 import xpcom.components
739 _ = dParams
740
741 def getSessionObject(self):
742 import xpcom.components
743 return xpcom.components.classes["@virtualbox.org/Session;1"].createInstance()
744
745 def getVirtualBox(self):
746 import xpcom.components
747 client = xpcom.components.classes["@virtualbox.org/VirtualBoxClient;1"].createInstance()
748 return client.virtualBox
749
750 def getType(self):
751 return 'XPCOM'
752
753 def getArray(self, oInterface, sAttrib):
754 return getattr(oInterface, 'get' + comifyName(sAttrib));
755
756 def setArray(self, oInterface, sAttrib, aoArray):
757 return setattr(oInterface, 'set' + comifyName(sAttrib), aoArray)
758
759 def initPerThread(self):
760 import xpcom
761 xpcom._xpcom.AttachThread()
762
763 def deinitPerThread(self):
764 import xpcom
765 xpcom._xpcom.DetachThread()
766
767 def createListener(self, oImplClass, dArgs):
768 notDocumentedDict = {}
769 notDocumentedDict['BaseClass'] = oImplClass
770 notDocumentedDict['dArgs'] = dArgs
771 sEval = ""
772 sEval += "import xpcom.components\n"
773 sEval += "class ListenerImpl(BaseClass):\n"
774 sEval += " _com_interfaces_ = xpcom.components.interfaces.IEventListener\n"
775 sEval += " def __init__(self): BaseClass.__init__(self, dArgs)\n"
776 sEval += "result = ListenerImpl()\n"
777 exec(sEval, notDocumentedDict, notDocumentedDict) # pylint: disable=exec-used
778 return notDocumentedDict['result']
779
780 def waitForEvents(self, cMsTimeout):
781 import xpcom
782 return xpcom._xpcom.WaitForEvents(cMsTimeout)
783
784 def interruptWaitEvents(self):
785 import xpcom
786 return xpcom._xpcom.InterruptWait()
787
788 def deinit(self):
789 import xpcom
790 xpcom._xpcom.DeinitCOM()
791
792 def queryInterface(self, oIUnknown, sClassName):
793 import xpcom.components
794 return oIUnknown.queryInterface(getattr(xpcom.components.interfaces, sClassName))
795
796 def xcptGetStatus(self, oXcpt):
797 return oXcpt.errno
798
799 def xcptIsDeadInterface(self, oXcpt):
800 return self.xcptGetStatus(oXcpt) in [
801 0x80004004, -2147467260, # NS_ERROR_ABORT
802 0x800706be, -2147023170, # NS_ERROR_CALL_FAILED (RPC_S_CALL_FAILED)
803 ]
804
805 def xcptGetMessage(self, oXcpt):
806 if hasattr(oXcpt, 'msg'):
807 try:
808 sRet = oXcpt.msg
809 if len(sRet) > 0:
810 return sRet
811 except:
812 pass
813 return None
814
815 def xcptGetBaseXcpt(self):
816 import xpcom
817 return xpcom.Exception
818
819 def xcptSetupConstants(self, oDst):
820 import xpcom
821 oDst = self.xcptCopyErrorConstants(oDst, xpcom.nsError)
822
823 # COM compatability constants.
824 oDst.E_ACCESSDENIED = -2147024891 # see VBox/com/defs.h
825 oDst.S_OK = oDst.NS_OK
826 oDst.E_FAIL = oDst.NS_ERROR_FAILURE
827 oDst.E_ABORT = oDst.NS_ERROR_ABORT
828 oDst.E_POINTER = oDst.NS_ERROR_NULL_POINTER
829 oDst.E_NOINTERFACE = oDst.NS_ERROR_NO_INTERFACE
830 oDst.E_INVALIDARG = oDst.NS_ERROR_INVALID_ARG
831 oDst.E_OUTOFMEMORY = oDst.NS_ERROR_OUT_OF_MEMORY
832 oDst.E_NOTIMPL = oDst.NS_ERROR_NOT_IMPLEMENTED
833 oDst.E_UNEXPECTED = oDst.NS_ERROR_UNEXPECTED
834 oDst.DISP_E_EXCEPTION = -2147352567 # For COM compatability only.
835 return oDst
836
837
838class PlatformWEBSERVICE(PlatformBase):
839 """
840 VirtualBox Web Services API specific code.
841 """
842
843 def __init__(self, dParams):
844 PlatformBase.__init__(self, dParams)
845 # Import web services stuff. Fix the sys.path the first time.
846 sWebServLib = os.path.join(g_sVBoxSdkDir, 'bindings', 'webservice', 'python', 'lib')
847 if sWebServLib not in sys.path:
848 sys.path.append(sWebServLib)
849 import VirtualBox_wrappers
850 from VirtualBox_wrappers import IWebsessionManager2
851
852 # Initialize instance variables from parameters.
853 if dParams is not None:
854 self.user = dParams.get("user", "")
855 self.password = dParams.get("password", "")
856 self.url = dParams.get("url", "")
857 else:
858 self.user = ""
859 self.password = ""
860 self.url = None
861 self.vbox = None
862 self.wsmgr = None
863
864 #
865 # Base class overrides.
866 #
867
868 def getSessionObject(self):
869 return self.wsmgr.getSessionObject(self.vbox)
870
871 def getVirtualBox(self):
872 return self.connect(self.url, self.user, self.password)
873
874 def getType(self):
875 return 'WEBSERVICE'
876
877 def isRemote(self):
878 """ Returns True if remote VBox host, False if local. """
879 return True
880
881 def getArray(self, oInterface, sAttrib):
882 return getattr(oInterface, sAttrib)
883
884 def setArray(self, oInterface, sAttrib, aoArray):
885 return setattr(oInterface, sAttrib, aoArray)
886
887 def waitForEvents(self, _timeout):
888 # Webservices cannot do that yet
889 return 2
890
891 def interruptWaitEvents(self):
892 # Webservices cannot do that yet
893 return False
894
895 def deinit(self):
896 try:
897 self.disconnect()
898 except:
899 pass
900
901 def queryInterface(self, oIUnknown, sClassName):
902 notDocumentedDict = {}
903 notDocumentedDict['oIUnknown'] = oIUnknown
904 sEval = ""
905 sEval += "from VirtualBox_wrappers import " + sClassName + "\n"
906 sEval += "result = " + sClassName + "(oIUnknown.mgr, oIUnknown.handle)\n"
907 # wrong, need to test if class indeed implements this interface
908 exec(sEval, notDocumentedDict, notDocumentedDict) # pylint: disable=exec-used
909 return notDocumentedDict['result']
910
911 #
912 # Web service specific methods.
913 #
914
915 def connect(self, url, user, passwd):
916 if self.vbox is not None:
917 self.disconnect()
918 from VirtualBox_wrappers import IWebsessionManager2
919
920 if url is None:
921 url = ""
922 self.url = url
923 if user is None:
924 user = ""
925 self.user = user
926 if passwd is None:
927 passwd = ""
928 self.password = passwd
929 self.wsmgr = IWebsessionManager2(self.url)
930 self.vbox = self.wsmgr.logon(self.user, self.password)
931 if not self.vbox.handle:
932 raise Exception("cannot connect to '" + self.url + "' as '" + self.user + "'")
933 return self.vbox
934
935 def disconnect(self):
936 if self.vbox is not None and self.wsmgr is not None:
937 self.wsmgr.logoff(self.vbox)
938 self.vbox = None
939 self.wsmgr = None
940
941
942## The current (last) exception class.
943# This is reinitalized whenever VirtualBoxManager is called, so it will hold
944# the reference to the error exception class for the last platform/style that
945# was used. Most clients does talk to multiple VBox instance on different
946# platforms at the same time, so this should be sufficent for most uses and
947# be way simpler to use than VirtualBoxManager::oXcptClass.
948g_curXcptClass = None
949
950
951class VirtualBoxManager(object):
952 """
953 VirtualBox API manager class.
954
955 The API users will have to instantiate this. If no parameters are given,
956 it will default to interface with the VirtualBox running on the local
957 machine. sStyle can be None (default), MSCOM, XPCOM or WEBSERVICES. Most
958 users will either be specifying None or WEBSERVICES.
959
960 The dPlatformParams is an optional dictionary for passing parameters to the
961 WEBSERVICE backend.
962 """
963
964 class Statuses(object):
965 def __init__(self):
966 pass
967
968 def __init__(self, sStyle=None, dPlatformParams=None):
969
970 # Deprecation warning for older Python stuff (< Python 3.x).
971 if sys.version_info.major < 3:
972 print("\nWarning: Running VirtualBox with Python %d.%d is marked as being deprecated.\n" \
973 "Please upgrade your Python installation to avoid breakage.\n" \
974 % (sys.version_info.major, sys.version_info.minor))
975
976 if sStyle is None:
977 if sys.platform == 'win32':
978 sStyle = "MSCOM"
979 else:
980 sStyle = "XPCOM"
981 if sStyle == 'XPCOM':
982 self.platform = PlatformXPCOM(dPlatformParams)
983 elif sStyle == 'MSCOM':
984 self.platform = PlatformMSCOM(dPlatformParams)
985 elif sStyle == 'WEBSERVICE':
986 self.platform = PlatformWEBSERVICE(dPlatformParams)
987 else:
988 raise Exception('Unknown sStyle=%s' % (sStyle,))
989 self.style = sStyle
990 self.type = self.platform.getType()
991 self.remote = self.platform.isRemote()
992 ## VirtualBox API constants (for webservices, enums are symbolic).
993 self.constants = VirtualBoxReflectionInfo(sStyle == "WEBSERVICE")
994
995 ## Status constants.
996 self.statuses = self.platform.xcptSetupConstants(VirtualBoxManager.Statuses())
997 ## @todo Add VBOX_E_XXX to statuses? They're already in constants...
998 ## Dictionary for errToString, built on demand.
999 self._dErrorValToName = None
1000
1001 ## Dictionary for resolving enum values to names, two levels of dictionaries.
1002 ## First level is indexed by enum name, the next by value.
1003 self._ddEnumValueToName = {};
1004
1005 ## The exception class for the selected platform.
1006 self.oXcptClass = self.platform.xcptGetBaseXcpt()
1007 global g_curXcptClass
1008 g_curXcptClass = self.oXcptClass
1009
1010 # Get the virtualbox singleton.
1011 try:
1012 self.platform.getVirtualBox()
1013 except NameError:
1014 print("Installation problem: check that appropriate libs in place")
1015 traceback.print_exc()
1016 raise
1017 except Exception:
1018 _, e, _ = sys.exc_info()
1019 print("init exception: ", e)
1020 traceback.print_exc()
1021
1022 def __del__(self):
1023 self.deinit()
1024
1025 def getPythonApiRevision(self):
1026 """
1027 Returns a Python API revision number.
1028 This will be incremented when features are added to this file.
1029 """
1030 return 3
1031
1032 @property
1033 def mgr(self):
1034 """
1035 This used to be an attribute referring to a session manager class with
1036 only one method called getSessionObject. It moved into this class.
1037 """
1038 return self
1039
1040 #
1041 # Wrappers for self.platform methods.
1042 #
1043 def getVirtualBox(self):
1044 """ See PlatformBase::getVirtualBox(). """
1045 return self.platform.getVirtualBox()
1046
1047 def getSessionObject(self, oIVBox = None):
1048 """ See PlatformBase::getSessionObject(). """
1049 # ignore parameter which was never needed
1050 _ = oIVBox
1051 return self.platform.getSessionObject()
1052
1053 def getArray(self, oInterface, sAttrib):
1054 """ See PlatformBase::getArray(). """
1055 return self.platform.getArray(oInterface, sAttrib)
1056
1057 def setArray(self, oInterface, sAttrib, aoArray):
1058 """ See PlatformBase::setArray(). """
1059 return self.platform.setArray(oInterface, sAttrib, aoArray)
1060
1061 def createListener(self, oImplClass, dArgs=None):
1062 """ See PlatformBase::createListener(). """
1063 return self.platform.createListener(oImplClass, dArgs)
1064
1065 def waitForEvents(self, cMsTimeout):
1066 """ See PlatformBase::waitForEvents(). """
1067 return self.platform.waitForEvents(cMsTimeout)
1068
1069 def interruptWaitEvents(self):
1070 """ See PlatformBase::interruptWaitEvents(). """
1071 return self.platform.interruptWaitEvents()
1072
1073 def queryInterface(self, oIUnknown, sClassName):
1074 """ See PlatformBase::queryInterface(). """
1075 return self.platform.queryInterface(oIUnknown, sClassName)
1076
1077 #
1078 # Init and uninit.
1079 #
1080 def initPerThread(self):
1081 """ See PlatformBase::deinitPerThread(). """
1082 self.platform.initPerThread()
1083
1084 def deinitPerThread(self):
1085 """ See PlatformBase::deinitPerThread(). """
1086 return self.platform.deinitPerThread()
1087
1088 def deinit(self):
1089 """
1090 For unitializing the manager.
1091 Do not access it after calling this method.
1092 """
1093 if hasattr(self, "platform") and self.platform is not None:
1094 self.platform.deinit()
1095 self.platform = None
1096 return True
1097
1098 #
1099 # Utility methods.
1100 #
1101 def openMachineSession(self, oIMachine, fPermitSharing=True):
1102 """
1103 Attempts to open the a session to the machine.
1104 Returns a session object on success.
1105 Raises exception on failure.
1106 """
1107 oSession = self.getSessionObject()
1108 if fPermitSharing:
1109 eType = self.constants.LockType_Shared
1110 else:
1111 eType = self.constants.LockType_Write
1112 oIMachine.lockMachine(oSession, eType)
1113 return oSession
1114
1115 def closeMachineSession(self, oSession):
1116 """
1117 Closes a session opened by openMachineSession.
1118 Ignores None parameters.
1119 """
1120 if oSession is not None:
1121 oSession.unlockMachine()
1122 return True
1123
1124 def getPerfCollector(self, oIVBox):
1125 """
1126 Returns a helper class (PerfCollector) for accessing performance
1127 collector goodies. See PerfCollector for details.
1128 """
1129 return PerfCollector(self, oIVBox)
1130
1131 def getBinDir(self):
1132 """
1133 Returns the VirtualBox binary directory.
1134 """
1135 return g_sVBoxBinDir
1136
1137 def getSdkDir(self):
1138 """
1139 Returns the VirtualBox SDK directory.
1140 """
1141 return g_sVBoxSdkDir
1142
1143 def getEnumValueName(self, sEnumTypeNm, oEnumValue, fTypePrefix = False):
1144 """
1145 Returns the name (string) for the corresponding enum value.
1146 """
1147 # Cache lookup:
1148 dValueNames = self._ddEnumValueToName.get(sEnumTypeNm);
1149 if dValueNames is not None:
1150 sValueName = dValueNames.get(oEnumValue);
1151 if sValueName:
1152 return sValueName if not fTypePrefix else '%s_%s' % (sEnumTypeNm, sValueName);
1153 else:
1154 # Cache miss. Build the reverse lookup dictionary and add it to the cache:
1155 dNamedValues = self.constants.all_values(sEnumTypeNm);
1156 if len(dNamedValues) > 0:
1157
1158 dValueNames = {};
1159 for sName in dNamedValues:
1160 dValueNames[dNamedValues[sName]] = sName;
1161 self._ddEnumValueToName[sEnumTypeNm] = dValueNames;
1162
1163 # Lookup the value:
1164 sValueName = dValueNames.get(oEnumValue);
1165 if sValueName:
1166 return sValueName if not fTypePrefix else '%s_%s' % (sEnumTypeNm, sValueName);
1167
1168 # Fallback:
1169 return '%s_Unknown_%s' % (sEnumTypeNm, oEnumValue);
1170
1171 #
1172 # Error code utilities.
1173 #
1174 ## @todo port to webservices!
1175 def xcptGetStatus(self, oXcpt=None):
1176 """
1177 Gets the status code from an exception. If the exception parameter
1178 isn't specified, the current exception is examined.
1179 """
1180 if oXcpt is None:
1181 oXcpt = sys.exc_info()[1]
1182 return self.platform.xcptGetStatus(oXcpt)
1183
1184 def xcptIsDeadInterface(self, oXcpt=None):
1185 """
1186 Returns True if the exception indicates that the interface is dead,
1187 False if not. If the exception parameter isn't specified, the current
1188 exception is examined.
1189 """
1190 if oXcpt is None:
1191 oXcpt = sys.exc_info()[1]
1192 return self.platform.xcptIsDeadInterface(oXcpt)
1193
1194 def xcptIsOurXcptKind(self, oXcpt=None):
1195 """
1196 Checks if the exception is one that could come from the VBox API. If
1197 the exception parameter isn't specified, the current exception is
1198 examined.
1199 """
1200 if self.oXcptClass is None: # @todo find the exception class for web services!
1201 return False
1202 if oXcpt is None:
1203 oXcpt = sys.exc_info()[1]
1204 return isinstance(oXcpt, self.oXcptClass)
1205
1206 def xcptIsEqual(self, oXcpt, hrStatus):
1207 """
1208 Checks if the exception oXcpt is equal to the COM/XPCOM status code
1209 hrStatus.
1210
1211 The oXcpt parameter can be any kind of object, we'll just return True
1212 if it doesn't behave like a our exception class. If it's None, we'll
1213 query the current exception and examine that.
1214
1215 Will not raise any exception as long as hrStatus and self are not bad.
1216 """
1217 if oXcpt is None:
1218 oXcpt = sys.exc_info()[1]
1219 return self.platform.xcptIsEqual(oXcpt, hrStatus)
1220
1221 def xcptIsNotEqual(self, oXcpt, hrStatus):
1222 """
1223 Negated xcptIsEqual.
1224 """
1225 return not self.xcptIsEqual(oXcpt, hrStatus)
1226
1227 def xcptToString(self, hrStatusOrXcpt=None):
1228 """
1229 Converts the specified COM status code, or the status code of the
1230 specified exception, to a C constant string. If the parameter isn't
1231 specified (is None), the current exception is examined.
1232 """
1233
1234 # Deal with exceptions.
1235 if hrStatusOrXcpt is None or self.xcptIsOurXcptKind(hrStatusOrXcpt):
1236 hrStatus = self.xcptGetStatus(hrStatusOrXcpt)
1237 else:
1238 hrStatus = hrStatusOrXcpt
1239
1240 # Build the dictionary on demand.
1241 if self._dErrorValToName is None:
1242 dErrorValToName = {}
1243 for sKey in dir(self.statuses):
1244 if sKey[0].isupper():
1245 oValue = getattr(self.statuses, sKey)
1246 if isinstance(oValue, (int, long)):
1247 dErrorValToName[int(oValue)] = sKey
1248 # Always prefer the COM names (see aliasing in platform specific code):
1249 for sKey in ('S_OK', 'E_FAIL', 'E_ABORT', 'E_POINTER', 'E_NOINTERFACE', 'E_INVALIDARG',
1250 'E_OUTOFMEMORY', 'E_NOTIMPL', 'E_UNEXPECTED',):
1251 oValue = getattr(self.statuses, sKey, None)
1252 if oValue is not None:
1253 dErrorValToName[oValue] = sKey
1254 self._dErrorValToName = dErrorValToName
1255
1256 # Do the lookup, falling back on formatting the status number.
1257 try:
1258 sStr = self._dErrorValToName[int(hrStatus)]
1259 except KeyError:
1260 hrLong = long(hrStatus)
1261 sStr = '%#x (%d)' % (hrLong & 0xffffffff, hrLong)
1262 return sStr
1263
1264 def xcptGetMessage(self, oXcpt=None):
1265 """
1266 Returns the best error message found in the COM-like exception. If the
1267 exception parameter isn't specified, the current exception is examined.
1268 """
1269 if oXcpt is None:
1270 oXcpt = sys.exc_info()[1]
1271 sRet = self.platform.xcptGetMessage(oXcpt)
1272 if sRet is None:
1273 sRet = self.xcptToString(oXcpt)
1274 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