From 3e04045bb5159304f72536395ba17fe3a9825091 Mon Sep 17 00:00:00 2001 From: James Dingwall Date: Tue, 4 Mar 2014 21:41:16 +0000 Subject: [PATCH 01/30] Add some stubs for Windows xenstore support --- pyxs/client.py | 5 ++++- pyxs/connection.py | 27 +++++++++++++++++++++++---- pyxs/helpers.py | 34 ++++++++++++++++++++++++++++++++-- 3 files changed, 59 insertions(+), 7 deletions(-) diff --git a/pyxs/client.py b/pyxs/client.py index 3ff1f40..734f027 100644 --- a/pyxs/client.py +++ b/pyxs/client.py @@ -24,6 +24,7 @@ import threading import time import posixpath +import os from collections import deque from ._internal import Event, Packet, Op @@ -85,9 +86,11 @@ def __init__(self, unix_socket_path=None, socket_timeout=None, xen_bus_path=None, connection=None, transaction=None): if connection: self.connection = connection - elif unix_socket_path or not xen_bus_path: + elif os.name in ["posix"] and (unix_socket_path or not xen_bus_path): self.connection = UnixSocketConnection( unix_socket_path, socket_timeout=socket_timeout) + elif os.name in ["nt"]: + self.connection = XenBusConnectionWin() else: self.connection = XenBusConnection(xen_bus_path) diff --git a/pyxs/connection.py b/pyxs/connection.py index a112409..77c5d91 100644 --- a/pyxs/connection.py +++ b/pyxs/connection.py @@ -24,7 +24,7 @@ bytes, str = str, unicode from .exceptions import ConnectionError -from .helpers import writeall, readall +from .helpers import writeall, readall, osnmopen, osnmclose, osnmread from ._internal import Packet @@ -51,7 +51,7 @@ def disconnect(self, silent=True): return try: - os.close(self.fd) + osnmclose(self.fd) except OSError as e: if not silent: raise ConnectionError(e.args) @@ -105,7 +105,7 @@ def recv(self): # blocking unless any new data appears, so we have to check # size value, before reading. payload = ("" if size is 0 else - os.read(self.fd, size).decode("utf-8")) + osnmread(self.fd, size).decode("utf-8")) return Packet(op, payload, rq_id, tx_id) @@ -180,7 +180,26 @@ def connect(self): return try: - self.fd = os.open(self.path, os.O_RDWR) + self.fd = osnmopen(self.path, os.O_RDWR) except OSError as e: raise ConnectionError("Error while opening {0!r}: {1}" .format(self.path, e.args)) + + +class XenBusConnectionWin(FileDescriptorConnection): + def __init__(self): + # need to workout self.path using the magic windows code + pass + + def __copy__(self): + return self.__class__() + + def connect(self): + if self.fd: + return + + try: + self.fd = osnmopen(self.path) + except Exception as e: + raise ConnectionError("Error while opening {0!r}: {1}" + .format(self.path, e.args)) diff --git a/pyxs/helpers.py b/pyxs/helpers.py index d2c7485..c77a25e 100644 --- a/pyxs/helpers.py +++ b/pyxs/helpers.py @@ -31,6 +31,36 @@ for code, message in errno.errorcode.items()) +if os.name in ["posix"]: + def osnmopen(path, *args): + return os.open(path, *args) + + def osnmclose(fd): + os.close(fd) + + def osnmwrite(fd, data): + return os.write(fd, data) + + def osnmread(fd, length): + return os.read(fd, length) + +elif os.name in ["nt"]: + def osnmopen(path, *args): + pass + + def osnmclose(fd): + pass + + def osnmread(fd, length): + pass + + def osnmwrite(fd, data): + pass + +else: + raise NotImplemented("No operating system fd interface defined") + + def writeall(fd, data): """Writes a data string to the file descriptor. @@ -40,7 +70,7 @@ def writeall(fd, data): """ length = len(data) while length: - length -= os.write(fd, data[-length:]) + length -= osnmwrite(fd, data[-length:]) def readall(fd, length): @@ -51,7 +81,7 @@ def readall(fd, length): """ chunks = [] while length: - chunks.append(os.read(fd, length)) + chunks.append(osnmread(fd, length)) length -= len(chunks[-1]) else: return b"".join(chunks) From 39473b22ec68142b23d669ecff961b8e4eb0a877 Mon Sep 17 00:00:00 2001 From: James Dingwall Date: Wed, 5 Mar 2014 04:22:42 +0000 Subject: [PATCH 02/30] Implement osnmread, osnmwrite, osnmopen, osnmclose for os.name = ["nt"] --- pyxs/helpers.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/pyxs/helpers.py b/pyxs/helpers.py index c77a25e..97df491 100644 --- a/pyxs/helpers.py +++ b/pyxs/helpers.py @@ -45,17 +45,33 @@ def osnmread(fd, length): return os.read(fd, length) elif os.name in ["nt"]: + from win32file import CreateFile, CloseHandle, ReadFile, WriteFile + from win32file import FILE_GENERIC_READ, FILE_GENERIC_WRITE, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL + def osnmopen(path, *args): - pass + # CreateFile(path, FILE_GENERIC_READ|FILE_GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + # http://docs.activestate.com/activepython/2.7/pywin32/win32file__CreateFile_meth.html + # PyHANDLE = CreateFile(fileName, desiredAccess , shareMode , attributes , CreationDisposition , flagsAndAttributes , hTemplateFile ) + return CreateFile(path, FILE_GENERIC_READ|FILE_GENERIC_WRITE, 0, None, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, None) def osnmclose(fd): - pass + # http://docs.activestate.com/activepython/2.7/pywin32/win32file__CloseHandle_meth.html + # CloseHandle(handle) + CloseHandle(fd) def osnmread(fd, length): - pass + # ReadFile(handle, buf, 1024, &bytes_read, NULL) + # http://docs.activestate.com/activepython/2.7/pywin32/win32file__ReadFile_meth.html + # (int, string) = ReadFile(hFile, buffer/bufSize , ol ) + (lread, data) = ReadFile(fd, length, None) + return data def osnmwrite(fd, data): - pass + # WriteFile(handle, buf, sizeof(*msg) + msg->len, &bytes_written, NULL) + # http://docs.activestate.com/activepython/2.7/pywin32/win32file__WriteFile_meth.html + # int, int = WriteFile(hFile, data , ol ) + errCode, lwrite = WriteFile(fd, data, None) + return lwrite else: raise NotImplemented("No operating system fd interface defined") From 2fdda78be1ba80bd828f6f6ea48996a9cdfec5c2 Mon Sep 17 00:00:00 2001 From: James Dingwall Date: Wed, 5 Mar 2014 05:27:34 +0000 Subject: [PATCH 03/30] Use the Windows API to find the device path for the xenbus device. --- pyxs/connection.py | 182 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 179 insertions(+), 3 deletions(-) diff --git a/pyxs/connection.py b/pyxs/connection.py index 77c5d91..2068bf3 100644 --- a/pyxs/connection.py +++ b/pyxs/connection.py @@ -12,7 +12,7 @@ from __future__ import absolute_import, unicode_literals -__all__ = ["UnixSocketConnection", "XenBusConnection"] +__all__ = ["UnixSocketConnection", "XenBusConnection", "XenBusConnectionWin"] import errno import os @@ -20,6 +20,19 @@ import socket import sys +if os.name in ["nt"]: + import ctypes + from ctypes.wintypes import HANDLE + from ctypes.wintypes import BOOL + from ctypes.wintypes import HWND + from ctypes.wintypes import DWORD + from ctypes.wintypes import WORD + from ctypes.wintypes import LONG + from ctypes.wintypes import ULONG + from ctypes.wintypes import LPCSTR + from ctypes.wintypes import HKEY + from ctypes.wintypes import BYTE + if sys.version_info[0] is not 3: bytes, str = str, unicode @@ -148,6 +161,7 @@ def connect(self): else: self.fd = os.dup(sock.fileno()) + class XenBusConnection(FileDescriptorConnection): """XenStore connection through XenBus. @@ -188,12 +202,174 @@ def connect(self): class XenBusConnectionWin(FileDescriptorConnection): def __init__(self): - # need to workout self.path using the magic windows code - pass + # Determine self.path using some magic Windows code which is derived from + # http://pydoc.net/Python/pyserial/2.6/serial.tools.list_ports_windows/. + # The equivalent C from The GPLPV driver source is: +""" +DEFINE_GUID(GUID_XENBUS_IFACE, 0x14ce175a, 0x3ee2, 0x4fae, 0x92, 0x52, 0x0, 0xdb, 0xd8, 0x4f, 0x1, 0x8e); + +static char * +get_xen_interface_path() +{ + HDEVINFO handle; + SP_DEVICE_INTERFACE_DATA sdid; + SP_DEVICE_INTERFACE_DETAIL_DATA *sdidd; + DWORD buf_len; + char *path; + + handle = SetupDiGetClassDevs(&GUID_XENBUS_IFACE, 0, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + if (handle == INVALID_HANDLE_VALUE) + { + printf("SetupDiGetClassDevs failed\n"); + return NULL; + } + sdid.cbSize = sizeof(sdid); + if (!SetupDiEnumDeviceInterfaces(handle, NULL, &GUID_XENBUS_IFACE, 0, &sdid)) + { + printf("SetupDiEnumDeviceInterfaces failed\n"); + return NULL; + } + SetupDiGetDeviceInterfaceDetail(handle, &sdid, NULL, 0, &buf_len, NULL); + sdidd = malloc(buf_len); + sdidd->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + if (!SetupDiGetDeviceInterfaceDetail(handle, &sdid, sdidd, buf_len, NULL, NULL)) + { + printf("SetupDiGetDeviceInterfaceDetail failed\n"); + return NULL; + } + + path = malloc(strlen(sdidd->DevicePath) + 1); + StringCbCopyA(path, strlen(sdidd->DevicePath) + 1, sdidd->DevicePath); + free(sdidd); + + return path; +} +""" + DIGCF_PRESENT = 2 + DIGCF_DEVICEINTERFACE = 16 + NULL = None + ERROR_INSUFFICIENT_BUFFER = 122 + ERROR_NO_MORE_ITEMS = 259 + + HDEVINFO = ctypes.c_void_p + PDWORD = ctypes.POINTER(DWORD) + LPDWORD = ctypes.POINTER(DWORD) + + # Return code checkers + def ValidHandle(value, func, arguments): + if value == 0: + raise ctypes.WinError() + return value + + def ValidEnum(value, func, arguments): + if value != ERROR_NO_MORE_ITEMS: + raise ctypes.WinError() + return value + + def ValidSdid(value, func, arguments): + if value != ERROR_INSUFFICIENT_BUFFER: + raise ctypes.WinError() + return value + + # Some structures used by the Windows API + class GUID(ctypes.Structure): + _fields_ = [ + ('Data1', DWORD), + ('Data2', WORD), + ('Data3', WORD), + ('Data4', BYTE*8), + ] + + def __str__(self): + return "{%08x-%04x-%04x-%s-%s}" % ( + self.Data1, + self.Data2, + self.Data3, + ''.join(["%02x" % d for d in self.Data4[:2]]), + ''.join(["%02x" % d for d in self.Data4[2:]]), + ) + + class SP_DEVINFO_DATA(ctypes.Structure): + _fields_ = [ + ('cbSize', DWORD), + ('ClassGuid', GUID), + ('DevInst', DWORD), + ('Reserved', ULONG_PTR), + ] + + def __str__(self): + return "ClassGuid:%s DevInst:%s" % (self.ClassGuid, self.DevInst) + PSP_DEVINFO_DATA = ctypes.POINTER(SP_DEVINFO_DATA) + + class SP_DEVICE_INTERFACE_DATA(ctypes.Structure): + _fields_ = [ + ('cbSize', DWORD), + ('InterfaceClassGuid', GUID), + ('Flags', DWORD), + ('Reserved', ULONG_PTR), + ] + + def __str__(self): + return "InterfaceClassGuid:%s Flags:%s" % (self.InterfaceClassGuid, self.Flags) + PSP_DEVICE_INTERFACE_DATA = ctypes.POINTER(SP_DEVICE_INTERFACE_DATA) + + PSP_DEVICE_INTERFACE_DETAIL_DATA = ctypes.c_void_p + + # Import the Windows APIs + setupapi = ctypes.windll.LoadLibrary("setupapi") + + SetupDiGetClassDevs = setupapi.SetupDiGetClassDevsA + SetupDiGetClassDevs.argtypes = [ctypes.POINTER(GUID), PCTSTR, HWND, DWORD] + SetupDiGetClassDevs.restype = HDEVINFO + SetupDiGetClassDevs.errcheck = ValidHandle + + SetupDiEnumDeviceInterfaces = setupapi.SetupDiEnumDeviceInterfaces + SetupDiEnumDeviceInterfaces.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, ctypes.POINTER(GUID), DWORD, PSP_DEVICE_INTERFACE_DATA] + SetupDiEnumDeviceInterfaces.restype = BOOL + SetupDiEnumDeviceInterfaces.errcheck = ValidEnum + + SetupDiGetDeviceInterfaceDetail = setupapi.SetupDiGetDeviceInterfaceDetailA + SetupDiGetDeviceInterfaceDetail.argtypes = [HDEVINFO, PSP_DEVICE_INTERFACE_DATA, PSP_DEVICE_INTERFACE_DETAIL_DATA, DWORD, PDWORD, PSP_DEVINFO_DATA] + SetupDiGetDeviceInterfaceDetail.restype = BOOL + SetupDiGetDeviceInterfaceDetail.errcheck = ValidSdid + + SetupDiDestroyDeviceInfoList = setupapi.SetupDiDestroyDeviceInfoList + SetupDiDestroyDeviceInfoList.argtypes = [HDEVINFO] + SetupDiDestroyDeviceInfoList.restype = BOOL + + # Do stuff + GUID_XENBUS_IFACE = GUID(0x14ce175aL, 0x3ee2, 0x4fae, (BYTE*8)(0x92, 0x52, 0x0, 0xdb, 0xd8, 0x4f, 0x1, 0x8e)) + + handle = SetupDiGetClassDevs(ctypes.byref(GUID_XENBUS_IFACE), 0, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + + sdid = SP_DEVICE_INTERFACE_DATA() + sdid.cbSize = ctypes.sizeof(sdid) + SetupDiEnumDeviceInterfaces(handle, NULL, ctypes.byref(GUID_XENBUS_IFACE), 0, ctype.byref(sdid)) + + buf_len = DWORD() + SetupDiGetDeviceInterfaceDetail(handle, ctypes.byref(sdid), NULL, 0, ctypes.byref(buf_len), NULL); + + # We didn't know how big to make the structure until buf_len is assigned... + class SP_DEVICE_INTERFACE_DETAIL_DATA_A(ctypes.Structure): + _fields_ = [ + ('cbSize', DWORD), + ('DevicePath', CHAR*(buf_len.value - ctypes.sizeof(DWORD))), + ] + + def __str__(self): + return "DevicePath:%s" % (self.DevicePath,) + + sdidd = SP_DEVICE_INTERFACE_DETAIL_DATA_A() + sdidd.cbSize = ctypes.sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A); + self.path = ""+sdidd.DevicePath + + SetupDiDestroyDeviceInfoList(handle) + def __copy__(self): return self.__class__() + def connect(self): if self.fd: return From 62e33fe6f3733248d162ce24bd7a30bf82f75456 Mon Sep 17 00:00:00 2001 From: James Dingwall Date: Wed, 5 Mar 2014 07:54:14 +0000 Subject: [PATCH 04/30] Some little missing pieces and fixes. Successfully tested a xenstore list command!y --- pyxs/client.py | 2 +- pyxs/connection.py | 40 +++++++++++++++++++++------------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/pyxs/client.py b/pyxs/client.py index 734f027..4121e1b 100644 --- a/pyxs/client.py +++ b/pyxs/client.py @@ -28,7 +28,7 @@ from collections import deque from ._internal import Event, Packet, Op -from .connection import UnixSocketConnection, XenBusConnection +from .connection import UnixSocketConnection, XenBusConnection, XenBusConnectionWin from .exceptions import UnexpectedPacket, PyXSError from .helpers import validate_path, validate_watch_path, validate_perms, \ dict_merge, force_unicode, error diff --git a/pyxs/connection.py b/pyxs/connection.py index 2068bf3..da3a2fe 100644 --- a/pyxs/connection.py +++ b/pyxs/connection.py @@ -123,6 +123,10 @@ def recv(self): return Packet(op, payload, rq_id, tx_id) + def __del__(self): + self.disconnect() + + class UnixSocketConnection(FileDescriptorConnection): """XenStore connection through Unix domain socket. @@ -205,7 +209,7 @@ def __init__(self): # Determine self.path using some magic Windows code which is derived from # http://pydoc.net/Python/pyserial/2.6/serial.tools.list_ports_windows/. # The equivalent C from The GPLPV driver source is: -""" + """\ DEFINE_GUID(GUID_XENBUS_IFACE, 0x14ce175a, 0x3ee2, 0x4fae, 0x92, 0x52, 0x0, 0xdb, 0xd8, 0x4f, 0x1, 0x8e); static char * @@ -248,12 +252,16 @@ def __init__(self): DIGCF_PRESENT = 2 DIGCF_DEVICEINTERFACE = 16 NULL = None + ERROR_SUCCESS = 0 ERROR_INSUFFICIENT_BUFFER = 122 ERROR_NO_MORE_ITEMS = 259 HDEVINFO = ctypes.c_void_p + PCTSTR = ctypes.c_char_p + CHAR = ctypes.c_char PDWORD = ctypes.POINTER(DWORD) LPDWORD = ctypes.POINTER(DWORD) + PULONG = ctypes.POINTER(ULONG) # Return code checkers def ValidHandle(value, func, arguments): @@ -261,16 +269,6 @@ def ValidHandle(value, func, arguments): raise ctypes.WinError() return value - def ValidEnum(value, func, arguments): - if value != ERROR_NO_MORE_ITEMS: - raise ctypes.WinError() - return value - - def ValidSdid(value, func, arguments): - if value != ERROR_INSUFFICIENT_BUFFER: - raise ctypes.WinError() - return value - # Some structures used by the Windows API class GUID(ctypes.Structure): _fields_ = [ @@ -294,7 +292,7 @@ class SP_DEVINFO_DATA(ctypes.Structure): ('cbSize', DWORD), ('ClassGuid', GUID), ('DevInst', DWORD), - ('Reserved', ULONG_PTR), + ('Reserved', PULONG), ] def __str__(self): @@ -306,7 +304,7 @@ class SP_DEVICE_INTERFACE_DATA(ctypes.Structure): ('cbSize', DWORD), ('InterfaceClassGuid', GUID), ('Flags', DWORD), - ('Reserved', ULONG_PTR), + ('Reserved', PULONG), ] def __str__(self): @@ -326,12 +324,10 @@ def __str__(self): SetupDiEnumDeviceInterfaces = setupapi.SetupDiEnumDeviceInterfaces SetupDiEnumDeviceInterfaces.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, ctypes.POINTER(GUID), DWORD, PSP_DEVICE_INTERFACE_DATA] SetupDiEnumDeviceInterfaces.restype = BOOL - SetupDiEnumDeviceInterfaces.errcheck = ValidEnum SetupDiGetDeviceInterfaceDetail = setupapi.SetupDiGetDeviceInterfaceDetailA SetupDiGetDeviceInterfaceDetail.argtypes = [HDEVINFO, PSP_DEVICE_INTERFACE_DATA, PSP_DEVICE_INTERFACE_DETAIL_DATA, DWORD, PDWORD, PSP_DEVINFO_DATA] SetupDiGetDeviceInterfaceDetail.restype = BOOL - SetupDiGetDeviceInterfaceDetail.errcheck = ValidSdid SetupDiDestroyDeviceInfoList = setupapi.SetupDiDestroyDeviceInfoList SetupDiDestroyDeviceInfoList.argtypes = [HDEVINFO] @@ -340,14 +336,18 @@ def __str__(self): # Do stuff GUID_XENBUS_IFACE = GUID(0x14ce175aL, 0x3ee2, 0x4fae, (BYTE*8)(0x92, 0x52, 0x0, 0xdb, 0xd8, 0x4f, 0x1, 0x8e)) - handle = SetupDiGetClassDevs(ctypes.byref(GUID_XENBUS_IFACE), 0, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + handle = SetupDiGetClassDevs(ctypes.byref(GUID_XENBUS_IFACE), NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); sdid = SP_DEVICE_INTERFACE_DATA() sdid.cbSize = ctypes.sizeof(sdid) - SetupDiEnumDeviceInterfaces(handle, NULL, ctypes.byref(GUID_XENBUS_IFACE), 0, ctype.byref(sdid)) + if not SetupDiEnumDeviceInterfaces(handle, NULL, ctypes.byref(GUID_XENBUS_IFACE), 0, ctypes.byref(sdid)): + if ctypes.GetLastError() != ERROR_NO_MORE_ITEMS: + raise ctypes.WinError() buf_len = DWORD() - SetupDiGetDeviceInterfaceDetail(handle, ctypes.byref(sdid), NULL, 0, ctypes.byref(buf_len), NULL); + if not SetupDiGetDeviceInterfaceDetail(handle, ctypes.byref(sdid), NULL, 0, ctypes.byref(buf_len), NULL): + if ctypes.GetLastError() != ERROR_INSUFFICIENT_BUFFER: + raise ctypes.WinError() # We didn't know how big to make the structure until buf_len is assigned... class SP_DEVICE_INTERFACE_DETAIL_DATA_A(ctypes.Structure): @@ -360,7 +360,9 @@ def __str__(self): return "DevicePath:%s" % (self.DevicePath,) sdidd = SP_DEVICE_INTERFACE_DETAIL_DATA_A() - sdidd.cbSize = ctypes.sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A); + sdidd.cbSize = ctypes.sizeof(ctypes.POINTER(SP_DEVICE_INTERFACE_DETAIL_DATA_A)) + if not SetupDiGetDeviceInterfaceDetail(handle, ctypes.byref(sdid), ctypes.byref(sdidd), buf_len, NULL, NULL): + raise ctypes.WinError() self.path = ""+sdidd.DevicePath SetupDiDestroyDeviceInfoList(handle) From 33ca9ac7fbe328738da3c1389251d5840d9be3e0 Mon Sep 17 00:00:00 2001 From: James Dingwall Date: Wed, 12 Mar 2014 10:07:43 +0000 Subject: [PATCH 05/30] Add WindowsDriverError exception and use it when a failure to find the xenbus device occurs. --- pyxs/connection.py | 8 ++++---- pyxs/exceptions.py | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pyxs/connection.py b/pyxs/connection.py index 2068bf3..be9ce44 100644 --- a/pyxs/connection.py +++ b/pyxs/connection.py @@ -36,7 +36,7 @@ if sys.version_info[0] is not 3: bytes, str = str, unicode -from .exceptions import ConnectionError +from .exceptions import ConnectionError, WindowsDriverError from .helpers import writeall, readall, osnmopen, osnmclose, osnmread from ._internal import Packet @@ -258,17 +258,17 @@ def __init__(self): # Return code checkers def ValidHandle(value, func, arguments): if value == 0: - raise ctypes.WinError() + raise WindowsDriverError(str(ctypes.WinError())) return value def ValidEnum(value, func, arguments): if value != ERROR_NO_MORE_ITEMS: - raise ctypes.WinError() + raise WindowsDriverError(str(ctypes.WinError())) return value def ValidSdid(value, func, arguments): if value != ERROR_INSUFFICIENT_BUFFER: - raise ctypes.WinError() + raise WindowsDriverError(str(ctypes.WinError())) return value # Some structures used by the Windows API diff --git a/pyxs/exceptions.py b/pyxs/exceptions.py index 1c1ad77..6c1afdc 100644 --- a/pyxs/exceptions.py +++ b/pyxs/exceptions.py @@ -75,3 +75,6 @@ class UnexpectedPacket(ConnectionError): ``op = Op.READ`` the incoming packet is expected to have ``op = Op.READ`` as well. """ + +class WindowsDriverError(PyXSError): + """Windows specific error when using native API to locate xenbus device""" From 41ac0988679c25cc59159f39e668b24f8a66a9d5 Mon Sep 17 00:00:00 2001 From: James Dingwall Date: Wed, 12 Mar 2014 10:19:40 +0000 Subject: [PATCH 06/30] Don't provide the C code the work is derived from inline, reference by URL to the GPLPV mercurial tree. --- pyxs/connection.py | 43 ++----------------------------------------- 1 file changed, 2 insertions(+), 41 deletions(-) diff --git a/pyxs/connection.py b/pyxs/connection.py index eba4127..84ea861 100644 --- a/pyxs/connection.py +++ b/pyxs/connection.py @@ -208,47 +208,8 @@ class XenBusConnectionWin(FileDescriptorConnection): def __init__(self): # Determine self.path using some magic Windows code which is derived from # http://pydoc.net/Python/pyserial/2.6/serial.tools.list_ports_windows/. - # The equivalent C from The GPLPV driver source is: - """\ -DEFINE_GUID(GUID_XENBUS_IFACE, 0x14ce175a, 0x3ee2, 0x4fae, 0x92, 0x52, 0x0, 0xdb, 0xd8, 0x4f, 0x1, 0x8e); - -static char * -get_xen_interface_path() -{ - HDEVINFO handle; - SP_DEVICE_INTERFACE_DATA sdid; - SP_DEVICE_INTERFACE_DETAIL_DATA *sdidd; - DWORD buf_len; - char *path; - - handle = SetupDiGetClassDevs(&GUID_XENBUS_IFACE, 0, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); - if (handle == INVALID_HANDLE_VALUE) - { - printf("SetupDiGetClassDevs failed\n"); - return NULL; - } - sdid.cbSize = sizeof(sdid); - if (!SetupDiEnumDeviceInterfaces(handle, NULL, &GUID_XENBUS_IFACE, 0, &sdid)) - { - printf("SetupDiEnumDeviceInterfaces failed\n"); - return NULL; - } - SetupDiGetDeviceInterfaceDetail(handle, &sdid, NULL, 0, &buf_len, NULL); - sdidd = malloc(buf_len); - sdidd->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); - if (!SetupDiGetDeviceInterfaceDetail(handle, &sdid, sdidd, buf_len, NULL, NULL)) - { - printf("SetupDiGetDeviceInterfaceDetail failed\n"); - return NULL; - } - - path = malloc(strlen(sdidd->DevicePath) + 1); - StringCbCopyA(path, strlen(sdidd->DevicePath) + 1, sdidd->DevicePath); - free(sdidd); - - return path; -} -""" + # The equivalent C from The GPLPV driver source can be found in get_xen_interface_path() of shutdownmon. + # - http://xenbits.xensource.com/ext/win-pvdrivers/file/896402519f15/shutdownmon/shutdownmon.c DIGCF_PRESENT = 2 DIGCF_DEVICEINTERFACE = 16 NULL = None From 856010674434b31be9980b5aa7198a7d7be3bdeb Mon Sep 17 00:00:00 2001 From: James Dingwall Date: Wed, 12 Mar 2014 10:22:10 +0000 Subject: [PATCH 07/30] Remove __del__() --- pyxs/connection.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pyxs/connection.py b/pyxs/connection.py index 84ea861..c0c5ce2 100644 --- a/pyxs/connection.py +++ b/pyxs/connection.py @@ -123,10 +123,6 @@ def recv(self): return Packet(op, payload, rq_id, tx_id) - def __del__(self): - self.disconnect() - - class UnixSocketConnection(FileDescriptorConnection): """XenStore connection through Unix domain socket. From 778599c47de5669ead0c0fd26be7c501823c9701 Mon Sep 17 00:00:00 2001 From: James Dingwall Date: Tue, 24 Jun 2014 08:51:26 +0100 Subject: [PATCH 08/30] ctypes.POINTER() allocates memory which does not get reclaimed by gc to over time this leaks memory if multiple instances of the connection class get created. To avoid this once the device path has been discovered cache the value and resuse that for every future invocation. --- pyxs/connection.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/pyxs/connection.py b/pyxs/connection.py index c0c5ce2..e5e7afc 100644 --- a/pyxs/connection.py +++ b/pyxs/connection.py @@ -200,12 +200,27 @@ def connect(self): .format(self.path, e.args)) +_winDevicePath = None + class XenBusConnectionWin(FileDescriptorConnection): def __init__(self): + global _winDevicePath + + # Once the windows device path is learned once reuse it otherwise + # ctypes.POINTER() for the same structure leaks memory. Although + # this can be reclaimed with ctypes._reset_cache() this is poking + # at the internals of ctypes which doesn't seem to be a good idea. + + if _winDevicePath: + self.path = _winDevicePath + + return + # Determine self.path using some magic Windows code which is derived from # http://pydoc.net/Python/pyserial/2.6/serial.tools.list_ports_windows/. # The equivalent C from The GPLPV driver source can be found in get_xen_interface_path() of shutdownmon. # - http://xenbits.xensource.com/ext/win-pvdrivers/file/896402519f15/shutdownmon/shutdownmon.c + DIGCF_PRESENT = 2 DIGCF_DEVICEINTERFACE = 16 NULL = None @@ -244,6 +259,8 @@ def __str__(self): ''.join(["%02x" % d for d in self.Data4[2:]]), ) + PGUID = ctypes.POINTER(GUID) + class SP_DEVINFO_DATA(ctypes.Structure): _fields_ = [ ('cbSize', DWORD), @@ -254,6 +271,7 @@ class SP_DEVINFO_DATA(ctypes.Structure): def __str__(self): return "ClassGuid:%s DevInst:%s" % (self.ClassGuid, self.DevInst) + PSP_DEVINFO_DATA = ctypes.POINTER(SP_DEVINFO_DATA) class SP_DEVICE_INTERFACE_DATA(ctypes.Structure): @@ -266,20 +284,20 @@ class SP_DEVICE_INTERFACE_DATA(ctypes.Structure): def __str__(self): return "InterfaceClassGuid:%s Flags:%s" % (self.InterfaceClassGuid, self.Flags) - PSP_DEVICE_INTERFACE_DATA = ctypes.POINTER(SP_DEVICE_INTERFACE_DATA) + PSP_DEVICE_INTERFACE_DATA = ctypes.POINTER(SP_DEVICE_INTERFACE_DATA) PSP_DEVICE_INTERFACE_DETAIL_DATA = ctypes.c_void_p # Import the Windows APIs setupapi = ctypes.windll.LoadLibrary("setupapi") SetupDiGetClassDevs = setupapi.SetupDiGetClassDevsA - SetupDiGetClassDevs.argtypes = [ctypes.POINTER(GUID), PCTSTR, HWND, DWORD] + SetupDiGetClassDevs.argtypes = [PGUID, PCTSTR, HWND, DWORD] SetupDiGetClassDevs.restype = HDEVINFO SetupDiGetClassDevs.errcheck = ValidHandle SetupDiEnumDeviceInterfaces = setupapi.SetupDiEnumDeviceInterfaces - SetupDiEnumDeviceInterfaces.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, ctypes.POINTER(GUID), DWORD, PSP_DEVICE_INTERFACE_DATA] + SetupDiEnumDeviceInterfaces.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, PGUID, DWORD, PSP_DEVICE_INTERFACE_DATA] SetupDiEnumDeviceInterfaces.restype = BOOL SetupDiGetDeviceInterfaceDetail = setupapi.SetupDiGetDeviceInterfaceDetailA @@ -324,6 +342,8 @@ def __str__(self): SetupDiDestroyDeviceInfoList(handle) + _winDevicePath = self.path + def __copy__(self): return self.__class__() From 2a30b107cc62d88f0c7966d5dba611245fbd15cf Mon Sep 17 00:00:00 2001 From: James Dingwall Date: Thu, 26 Mar 2015 14:00:31 +0000 Subject: [PATCH 09/30] Avoid trailing NULL in value on write --- pyxs/client.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pyxs/client.py b/pyxs/client.py index 3ff1f40..b870fa8 100644 --- a/pyxs/client.py +++ b/pyxs/client.py @@ -121,7 +121,13 @@ def execute_command(self, op, *args, **kwargs): with self.tx_lock: kwargs["tx_id"] = self.tx_id # Forcing ``tx_id`` here. - self.connection.send(Packet(op, "".join(args), **kwargs)) + if op in [Op.WRITE]: + # xenstore will write the trailing \x00 of the value + # for writes so trim it for behaviour equivalent to + # xenstore-write + self.connection.send(Packet(op, "".join(args)[:-1], **kwargs)) + else: + self.connection.send(Packet(op, "".join(args), **kwargs)) # If we have any watched paths `XenStore` will send watch # events mixed with replies to other operations, so we loop From ab5a2b3e6b8c082d2c31449450df40ca1f96e03e Mon Sep 17 00:00:00 2001 From: Roy Jenkins Date: Mon, 11 May 2015 10:23:41 +0100 Subject: [PATCH 10/30] Add WMI support for Windows Server 2012 with Xen Project PV drivers --- pyxs/client.py | 20 ++++++------ pyxs/connection.py | 80 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 88 insertions(+), 12 deletions(-) diff --git a/pyxs/client.py b/pyxs/client.py index a066a19..a8318e6 100644 --- a/pyxs/client.py +++ b/pyxs/client.py @@ -28,7 +28,7 @@ from collections import deque from ._internal import Event, Packet, Op -from .connection import UnixSocketConnection, XenBusConnection, XenBusConnectionWin +from .connection import UnixSocketConnection, XenBusConnection, XenBusConnectionWin, XenBusConnectionWin2008 from .exceptions import UnexpectedPacket, PyXSError from .helpers import validate_path, validate_watch_path, validate_perms, \ dict_merge, force_unicode, error @@ -90,7 +90,13 @@ def __init__(self, unix_socket_path=None, socket_timeout=None, self.connection = UnixSocketConnection( unix_socket_path, socket_timeout=socket_timeout) elif os.name in ["nt"]: - self.connection = XenBusConnectionWin() + import wmi + c = wmi.WMI() + for system in c.Win32_OperatingSystem(): + if re.match('Microsoft Windows Server 2008.*', system.caption): + self.connection = XenBusConnectionWin2008() + else: + self.connection = XenBusConnectionWin() else: self.connection = XenBusConnection(xen_bus_path) @@ -124,13 +130,7 @@ def execute_command(self, op, *args, **kwargs): with self.tx_lock: kwargs["tx_id"] = self.tx_id # Forcing ``tx_id`` here. - if op in [Op.WRITE]: - # xenstore will write the trailing \x00 of the value - # for writes so trim it for behaviour equivalent to - # xenstore-write - self.connection.send(Packet(op, "".join(args)[:-1], **kwargs)) - else: - self.connection.send(Packet(op, "".join(args), **kwargs)) + self.connection.send(Packet(op, "".join(args), **kwargs)) # If we have any watched paths `XenStore` will send watch # events mixed with replies to other operations, so we loop @@ -222,7 +222,7 @@ def ls(self, path): :param str path: path to list. """ payload = self.execute_command(Op.DIRECTORY, path) - return [] if payload is "" else payload.split("\x00") + return [] if payload is "" else map(lambda x : x.split('/')[-1], payload.split("\x00")) def get_permissions(self, path): """Returns a list of permissions for a given `path`, see diff --git a/pyxs/connection.py b/pyxs/connection.py index e5e7afc..155d440 100644 --- a/pyxs/connection.py +++ b/pyxs/connection.py @@ -12,15 +12,19 @@ from __future__ import absolute_import, unicode_literals -__all__ = ["UnixSocketConnection", "XenBusConnection", "XenBusConnectionWin"] +__all__ = ["UnixSocketConnection", "XenBusConnection", "XenBusConnectionWin", "XenBusConnectionWin2008"] import errno import os import platform import socket import sys +from ._internal import Packet, Op +sys.coinit_flags = 0 +import pythoncom if os.name in ["nt"]: + import wmi import ctypes from ctypes.wintypes import HANDLE from ctypes.wintypes import BOOL @@ -32,6 +36,8 @@ from ctypes.wintypes import LPCSTR from ctypes.wintypes import HKEY from ctypes.wintypes import BYTE + sys.coinit_flags = 0 + import pythoncom if sys.version_info[0] is not 3: bytes, str = str, unicode @@ -200,9 +206,77 @@ def connect(self): .format(self.path, e.args)) -_winDevicePath = None +_wmiSession = None class XenBusConnectionWin(FileDescriptorConnection): + + session = None + response_packet = None + + + def __init__(self): + pass + + + def __copy__(self): + return self.__class__(self.path) + + + def connect(self): + global _wmiSession + + # Create a WMI Session + if not _wmiSession: + _wmiSession = wmi.WMI(moniker="//./root/wmi", find_classes=False) + + xenStoreBase = _wmiSession.XenProjectXenStoreBase()[0] + sessions = _wmiSession.query("select * from XenProjectXenStoreSession where InstanceName = 'Xen Interface\Session_PyxsSession_0'") + if len(sessions) <= 0: + session_name = "PyxsSession" + session_id = xenStoreBase.AddSession(Id=session_name)[0] + self.session = _wmiSession.query("select * from XenProjectXenStoreSession where SessionId = {id}".format(id=session_id)) + else: + self.session = sessions[0] + + + # Emulate sending the packet directly to the XenStore interface + # and store the result in response_packet + def send(self, packet): + + self.connect() + + remove_paths = lambda x : x.split('/')[-1] + + if packet.op == Op.READ: + #result = remove_paths(self.session.GetValue(packet.payload)[0]) + result = self.session.GetValue(packet.payload)[0] + elif packet.op == Op.WRITE: + payload = packet.payload.split('\x00', 1) + self.session.SetValue(payload[0], payload[1]) + result = "OK" + elif packet.op == Op.RM: + self.session.RemoveValue(packet.payload)[0] + result = "OK" + elif packet.op == Op.DIRECTORY: + #result = map(remove_paths, self.session.GetChildren(packet.payload)[0].childNodes) + result = self.session.GetChildren(packet.payload)[0].childNodes + result = "\x00".join(result) + else: + raise Exception("Unsupported XenStore Action ({x})".format(x=packet.op)) + self.response_packet = Packet(packet.op, result, packet.rq_id, packet.tx_id) + + + def recv(self): + return self.response_packet + + + def disconnect(self, silent=True): + self.session = None + + +_winDevicePath = None + +class XenBusConnectionWin2008(FileDescriptorConnection): def __init__(self): global _winDevicePath @@ -358,3 +432,5 @@ def connect(self): except Exception as e: raise ConnectionError("Error while opening {0!r}: {1}" .format(self.path, e.args)) + + From 1f391fdd3b3d84492968c358c32511c0bf4bc601 Mon Sep 17 00:00:00 2001 From: Roy Jenkins Date: Mon, 11 May 2015 14:05:26 +0100 Subject: [PATCH 11/30] Raise PyXSError properly --- pyxs/client.py | 9 +++++++-- pyxs/connection.py | 32 +++++++++++++++++++++++--------- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/pyxs/client.py b/pyxs/client.py index a8318e6..74326ae 100644 --- a/pyxs/client.py +++ b/pyxs/client.py @@ -108,8 +108,12 @@ def __init__(self, unix_socket_path=None, socket_timeout=None, self.tx_id = self.transaction_start() def __enter__(self): - self.connection.connect() - return self + try: + self.connection.connect() + except wmi.x_wmi: + raise PyXSError, None, sys.exc_info()[2] + return self + def __exit__(self, *exc_info): if not any(exc_info) and self.tx_id: @@ -454,3 +458,4 @@ def wait(self, sleep=None): if sleep is not None: time.sleep(sleep) + diff --git a/pyxs/connection.py b/pyxs/connection.py index 155d440..0338dcd 100644 --- a/pyxs/connection.py +++ b/pyxs/connection.py @@ -42,7 +42,7 @@ if sys.version_info[0] is not 3: bytes, str = str, unicode -from .exceptions import ConnectionError, WindowsDriverError +from .exceptions import ConnectionError, WindowsDriverError, PyXSError from .helpers import writeall, readall, osnmopen, osnmclose, osnmread from ._internal import Packet @@ -243,24 +243,39 @@ def connect(self): # and store the result in response_packet def send(self, packet): - self.connect() + try: + self.connect() + except wmi.x_wmi: + raise PyXSError, None, sys.exc_info()[2] remove_paths = lambda x : x.split('/')[-1] if packet.op == Op.READ: #result = remove_paths(self.session.GetValue(packet.payload)[0]) - result = self.session.GetValue(packet.payload)[0] + try: + result = self.session.GetValue(packet.payload)[0] + except wmi.x_wmi: + raise PyXSError, None, sys.exc_info()[2] elif packet.op == Op.WRITE: - payload = packet.payload.split('\x00', 1) - self.session.SetValue(payload[0], payload[1]) + try: + payload = packet.payload.split('\x00', 1) + self.session.SetValue(payload[0], payload[1]) + except wmi.x_wmi: + raise PyXSError, None, sys.exc_info()[2] result = "OK" elif packet.op == Op.RM: - self.session.RemoveValue(packet.payload)[0] + try: + self.session.RemoveValue(packet.payload)[0] + except wmi.x_wmi: + raise PyXSError, None, sys.exc_info()[2] result = "OK" elif packet.op == Op.DIRECTORY: #result = map(remove_paths, self.session.GetChildren(packet.payload)[0].childNodes) - result = self.session.GetChildren(packet.payload)[0].childNodes - result = "\x00".join(result) + try: + result = self.session.GetChildren(packet.payload)[0].childNodes + result = "\x00".join(result) + except wmi.x_wmi: + raise PyXSError, None, sys.exc_info()[2] else: raise Exception("Unsupported XenStore Action ({x})".format(x=packet.op)) self.response_packet = Packet(packet.op, result, packet.rq_id, packet.tx_id) @@ -433,4 +448,3 @@ def connect(self): raise ConnectionError("Error while opening {0!r}: {1}" .format(self.path, e.args)) - From c2b9c9f1de11f19b4b79b0830afff60246494175 Mon Sep 17 00:00:00 2001 From: Roy Jenkins Date: Tue, 12 May 2015 10:33:03 +0100 Subject: [PATCH 12/30] Don't call connect() uneccesarily --- pyxs/client.py | 9 ++------- pyxs/connection.py | 29 +++++++++++++++++++++-------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/pyxs/client.py b/pyxs/client.py index 74326ae..a8318e6 100644 --- a/pyxs/client.py +++ b/pyxs/client.py @@ -108,12 +108,8 @@ def __init__(self, unix_socket_path=None, socket_timeout=None, self.tx_id = self.transaction_start() def __enter__(self): - try: - self.connection.connect() - except wmi.x_wmi: - raise PyXSError, None, sys.exc_info()[2] - return self - + self.connection.connect() + return self def __exit__(self, *exc_info): if not any(exc_info) and self.tx_id: @@ -458,4 +454,3 @@ def wait(self, sleep=None): if sleep is not None: time.sleep(sleep) - diff --git a/pyxs/connection.py b/pyxs/connection.py index 0338dcd..dbf828d 100644 --- a/pyxs/connection.py +++ b/pyxs/connection.py @@ -222,29 +222,42 @@ def __copy__(self): return self.__class__(self.path) - def connect(self): + def connect(self, retry=False): global _wmiSession # Create a WMI Session - if not _wmiSession: - _wmiSession = wmi.WMI(moniker="//./root/wmi", find_classes=False) - - xenStoreBase = _wmiSession.XenProjectXenStoreBase()[0] - sessions = _wmiSession.query("select * from XenProjectXenStoreSession where InstanceName = 'Xen Interface\Session_PyxsSession_0'") + try: + if not _wmiSession: + _wmiSession = wmi.WMI(moniker="//./root/wmi", find_classes=False) + xenStoreBase = _wmiSession.XenProjectXenStoreBase()[0] + except wmi.x_wmi: + if not retry: + _wmiSession = None + self.connect(retry=True) + return + else: raise + + try: + sessions = _wmiSession.query("select * from XenProjectXenStoreSession where InstanceName = 'Xen Interface\Session_PyxsSession_0'") + except: + sessions = [] + if len(sessions) <= 0: session_name = "PyxsSession" session_id = xenStoreBase.AddSession(Id=session_name)[0] self.session = _wmiSession.query("select * from XenProjectXenStoreSession where SessionId = {id}".format(id=session_id)) else: - self.session = sessions[0] + self.session = sessions.pop() # Emulate sending the packet directly to the XenStore interface # and store the result in response_packet def send(self, packet): + global _wmiSession try: - self.connect() + if not _wmiSession: + self.connect() except wmi.x_wmi: raise PyXSError, None, sys.exc_info()[2] From 7a7bf551f4d993999dc2d0d2e0cb4661257eee4e Mon Sep 17 00:00:00 2001 From: Roy Jenkins Date: Tue, 12 May 2015 11:22:24 +0100 Subject: [PATCH 13/30] Stop session being created as a list --- pyxs/connection.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pyxs/connection.py b/pyxs/connection.py index dbf828d..457aeb9 100644 --- a/pyxs/connection.py +++ b/pyxs/connection.py @@ -14,6 +14,7 @@ __all__ = ["UnixSocketConnection", "XenBusConnection", "XenBusConnectionWin", "XenBusConnectionWin2008"] +import logging import errno import os import platform @@ -245,9 +246,9 @@ def connect(self, retry=False): if len(sessions) <= 0: session_name = "PyxsSession" session_id = xenStoreBase.AddSession(Id=session_name)[0] - self.session = _wmiSession.query("select * from XenProjectXenStoreSession where SessionId = {id}".format(id=session_id)) - else: - self.session = sessions.pop() + sessions = _wmiSession.query("select * from XenProjectXenStoreSession where SessionId = {id}".format(id=session_id)) + + self.session = sessions.pop() # Emulate sending the packet directly to the XenStore interface @@ -256,7 +257,7 @@ def send(self, packet): global _wmiSession try: - if not _wmiSession: + if not _wmiSession or not self.session: self.connect() except wmi.x_wmi: raise PyXSError, None, sys.exc_info()[2] From 36c0aa3f71390282a86b0ad9468cf1c10ffbccf3 Mon Sep 17 00:00:00 2001 From: Roy Jenkins Date: Wed, 13 May 2015 09:24:48 +0100 Subject: [PATCH 14/30] Never allow _wmiSession to be null --- pyxs/connection.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyxs/connection.py b/pyxs/connection.py index 457aeb9..572232a 100644 --- a/pyxs/connection.py +++ b/pyxs/connection.py @@ -228,12 +228,12 @@ def connect(self, retry=False): # Create a WMI Session try: - if not _wmiSession: + if not _wmiSession or retry: _wmiSession = wmi.WMI(moniker="//./root/wmi", find_classes=False) xenStoreBase = _wmiSession.XenProjectXenStoreBase()[0] except wmi.x_wmi: if not retry: - _wmiSession = None + #_wmiSession = None self.connect(retry=True) return else: raise @@ -461,4 +461,3 @@ def connect(self): except Exception as e: raise ConnectionError("Error while opening {0!r}: {1}" .format(self.path, e.args)) - From 7de63141935ad6c47f328be18a7477dba4b367ad Mon Sep 17 00:00:00 2001 From: Roy Jenkins Date: Thu, 14 May 2015 09:06:20 +0100 Subject: [PATCH 15/30] Retry Win32OperatingSystem WMI request if it fails --- pyxs/client.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pyxs/client.py b/pyxs/client.py index a8318e6..9148712 100644 --- a/pyxs/client.py +++ b/pyxs/client.py @@ -92,7 +92,16 @@ def __init__(self, unix_socket_path=None, socket_timeout=None, elif os.name in ["nt"]: import wmi c = wmi.WMI() - for system in c.Win32_OperatingSystem(): + try: + win32_os = c.Win32_OperatingSystem() + except wmi.x_wmi: + sleep(0.5) + try: + win32_os = c.Win32_OperatingSystem() + except wmi.x_wmi: + raise PyXSError() + + for system in win32_os: if re.match('Microsoft Windows Server 2008.*', system.caption): self.connection = XenBusConnectionWin2008() else: From 3f48756f751c036400f4ce129764e0f8f69241a7 Mon Sep 17 00:00:00 2001 From: Roy Jenkins Date: Thu, 14 May 2015 09:30:27 +0100 Subject: [PATCH 16/30] Add additional retries with half-second sleeps around WMI requests; they can be a little unreliable --- pyxs/client.py | 1 + pyxs/connection.py | 13 ++++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/pyxs/client.py b/pyxs/client.py index 9148712..72e0dc9 100644 --- a/pyxs/client.py +++ b/pyxs/client.py @@ -25,6 +25,7 @@ import time import posixpath import os +from time import sleep from collections import deque from ._internal import Event, Packet, Op diff --git a/pyxs/connection.py b/pyxs/connection.py index 572232a..62c0e34 100644 --- a/pyxs/connection.py +++ b/pyxs/connection.py @@ -20,6 +20,7 @@ import platform import socket import sys +from time import sleep from ._internal import Packet, Op sys.coinit_flags = 0 import pythoncom @@ -231,9 +232,9 @@ def connect(self, retry=False): if not _wmiSession or retry: _wmiSession = wmi.WMI(moniker="//./root/wmi", find_classes=False) xenStoreBase = _wmiSession.XenProjectXenStoreBase()[0] - except wmi.x_wmi: + except: # WMI can raise all sorts of exceptions if not retry: - #_wmiSession = None + sleep(0.5) self.connect(retry=True) return else: raise @@ -246,7 +247,13 @@ def connect(self, retry=False): if len(sessions) <= 0: session_name = "PyxsSession" session_id = xenStoreBase.AddSession(Id=session_name)[0] - sessions = _wmiSession.query("select * from XenProjectXenStoreSession where SessionId = {id}".format(id=session_id)) + try: + sessions = _wmiSession.query("select * from XenProjectXenStoreSession where SessionId = {id}".format(id=session_id)) + except: + sleep(0.5) + try: + sessions = _wmiSession.query("select * from XenProjectXenStoreSession where SessionId = {id}".format(id=session_id)) + except: raise self.session = sessions.pop() From 8fb255dde793d5229f5bc53d850edd3f81eb251e Mon Sep 17 00:00:00 2001 From: Roy Jenkins Date: Fri, 22 May 2015 08:48:26 +0100 Subject: [PATCH 17/30] Increase wait period on retry --- pyxs/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyxs/connection.py b/pyxs/connection.py index 62c0e34..47cbd3d 100644 --- a/pyxs/connection.py +++ b/pyxs/connection.py @@ -234,7 +234,7 @@ def connect(self, retry=False): xenStoreBase = _wmiSession.XenProjectXenStoreBase()[0] except: # WMI can raise all sorts of exceptions if not retry: - sleep(0.5) + sleep(5) self.connect(retry=True) return else: raise From d4411af3ded2dec2741458d9dd5e0f0ec63125da Mon Sep 17 00:00:00 2001 From: Roy Jenkins Date: Tue, 26 May 2015 09:05:09 +0100 Subject: [PATCH 18/30] Many more retries --- pyxs/connection.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pyxs/connection.py b/pyxs/connection.py index 47cbd3d..2cafc95 100644 --- a/pyxs/connection.py +++ b/pyxs/connection.py @@ -224,20 +224,21 @@ def __copy__(self): return self.__class__(self.path) - def connect(self, retry=False): + def connect(self, retry=0): global _wmiSession # Create a WMI Session try: - if not _wmiSession or retry: + if not _wmiSession or retry > 0: _wmiSession = wmi.WMI(moniker="//./root/wmi", find_classes=False) xenStoreBase = _wmiSession.XenProjectXenStoreBase()[0] except: # WMI can raise all sorts of exceptions - if not retry: + if retry > 20: sleep(5) - self.connect(retry=True) + self.connect(retry=(retry+1)) return - else: raise + else: + raise PyXSError, None, sys.exc_info()[2] try: sessions = _wmiSession.query("select * from XenProjectXenStoreSession where InstanceName = 'Xen Interface\Session_PyxsSession_0'") @@ -253,7 +254,8 @@ def connect(self, retry=False): sleep(0.5) try: sessions = _wmiSession.query("select * from XenProjectXenStoreSession where SessionId = {id}".format(id=session_id)) - except: raise + except: + raise PyXSError, None, sys.exc_info()[2] self.session = sessions.pop() From 819d2757acbbe396b60692434a2cb957e36c8294 Mon Sep 17 00:00:00 2001 From: Roy Jenkins Date: Wed, 27 May 2015 16:57:38 +0100 Subject: [PATCH 19/30] < not > --- pyxs/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyxs/connection.py b/pyxs/connection.py index 2cafc95..d92ef0f 100644 --- a/pyxs/connection.py +++ b/pyxs/connection.py @@ -233,7 +233,7 @@ def connect(self, retry=0): _wmiSession = wmi.WMI(moniker="//./root/wmi", find_classes=False) xenStoreBase = _wmiSession.XenProjectXenStoreBase()[0] except: # WMI can raise all sorts of exceptions - if retry > 20: + if retry < 20: sleep(5) self.connect(retry=(retry+1)) return From 0046ff3761637b86e68b4766f563f85586057a2f Mon Sep 17 00:00:00 2001 From: James Dingwall Date: Fri, 29 May 2015 15:54:43 +0100 Subject: [PATCH 20/30] except: is bad practice, don't do it --- pyxs/connection.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyxs/connection.py b/pyxs/connection.py index d92ef0f..8066b3e 100644 --- a/pyxs/connection.py +++ b/pyxs/connection.py @@ -232,7 +232,7 @@ def connect(self, retry=0): if not _wmiSession or retry > 0: _wmiSession = wmi.WMI(moniker="//./root/wmi", find_classes=False) xenStoreBase = _wmiSession.XenProjectXenStoreBase()[0] - except: # WMI can raise all sorts of exceptions + except Exception: # WMI can raise all sorts of exceptions if retry < 20: sleep(5) self.connect(retry=(retry+1)) @@ -242,7 +242,7 @@ def connect(self, retry=0): try: sessions = _wmiSession.query("select * from XenProjectXenStoreSession where InstanceName = 'Xen Interface\Session_PyxsSession_0'") - except: + except Exception: sessions = [] if len(sessions) <= 0: @@ -250,11 +250,11 @@ def connect(self, retry=0): session_id = xenStoreBase.AddSession(Id=session_name)[0] try: sessions = _wmiSession.query("select * from XenProjectXenStoreSession where SessionId = {id}".format(id=session_id)) - except: + except Exception: sleep(0.5) try: sessions = _wmiSession.query("select * from XenProjectXenStoreSession where SessionId = {id}".format(id=session_id)) - except: + except Exception: raise PyXSError, None, sys.exc_info()[2] self.session = sessions.pop() From 17ed8914d2aed9f2bee47b23c119f3d50171f77a Mon Sep 17 00:00:00 2001 From: James Dingwall Date: Fri, 29 May 2015 15:56:32 +0100 Subject: [PATCH 21/30] whitespace cleanup --- pyxs/client.py | 4 ++-- pyxs/connection.py | 13 ++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/pyxs/client.py b/pyxs/client.py index 72e0dc9..79db08b 100644 --- a/pyxs/client.py +++ b/pyxs/client.py @@ -94,11 +94,11 @@ def __init__(self, unix_socket_path=None, socket_timeout=None, import wmi c = wmi.WMI() try: - win32_os = c.Win32_OperatingSystem() + win32_os = c.Win32_OperatingSystem() except wmi.x_wmi: sleep(0.5) try: - win32_os = c.Win32_OperatingSystem() + win32_os = c.Win32_OperatingSystem() except wmi.x_wmi: raise PyXSError() diff --git a/pyxs/connection.py b/pyxs/connection.py index 8066b3e..c8df097 100644 --- a/pyxs/connection.py +++ b/pyxs/connection.py @@ -211,10 +211,9 @@ def connect(self): _wmiSession = None class XenBusConnectionWin(FileDescriptorConnection): - session = None response_packet = None - + def __init__(self): pass @@ -226,7 +225,7 @@ def __copy__(self): def connect(self, retry=0): global _wmiSession - + # Create a WMI Session try: if not _wmiSession or retry > 0: @@ -237,14 +236,14 @@ def connect(self, retry=0): sleep(5) self.connect(retry=(retry+1)) return - else: + else: raise PyXSError, None, sys.exc_info()[2] - + try: sessions = _wmiSession.query("select * from XenProjectXenStoreSession where InstanceName = 'Xen Interface\Session_PyxsSession_0'") except Exception: sessions = [] - + if len(sessions) <= 0: session_name = "PyxsSession" session_id = xenStoreBase.AddSession(Id=session_name)[0] @@ -261,7 +260,7 @@ def connect(self, retry=0): # Emulate sending the packet directly to the XenStore interface - # and store the result in response_packet + # and store the result in response_packet def send(self, packet): global _wmiSession From 2d96334da3b8a10562e42cdd0c4d0eb4715527de Mon Sep 17 00:00:00 2001 From: James Dingwall Date: Fri, 29 May 2015 16:19:37 +0100 Subject: [PATCH 22/30] Mark the xenbus connection according to the pv driver project not the windows version. --- pyxs/client.py | 6 +++--- pyxs/connection.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyxs/client.py b/pyxs/client.py index 79db08b..fbd1766 100644 --- a/pyxs/client.py +++ b/pyxs/client.py @@ -29,7 +29,7 @@ from collections import deque from ._internal import Event, Packet, Op -from .connection import UnixSocketConnection, XenBusConnection, XenBusConnectionWin, XenBusConnectionWin2008 +from .connection import UnixSocketConnection, XenBusConnection, XenBusConnectionWinWINPV, XenBusConnectionWinGPLPV from .exceptions import UnexpectedPacket, PyXSError from .helpers import validate_path, validate_watch_path, validate_perms, \ dict_merge, force_unicode, error @@ -104,9 +104,9 @@ def __init__(self, unix_socket_path=None, socket_timeout=None, for system in win32_os: if re.match('Microsoft Windows Server 2008.*', system.caption): - self.connection = XenBusConnectionWin2008() + self.connection = XenBusConnectionWinGPLPV() else: - self.connection = XenBusConnectionWin() + self.connection = XenBusConnectionWinWINPV() else: self.connection = XenBusConnection(xen_bus_path) diff --git a/pyxs/connection.py b/pyxs/connection.py index c8df097..a38a80e 100644 --- a/pyxs/connection.py +++ b/pyxs/connection.py @@ -12,7 +12,7 @@ from __future__ import absolute_import, unicode_literals -__all__ = ["UnixSocketConnection", "XenBusConnection", "XenBusConnectionWin", "XenBusConnectionWin2008"] +__all__ = ["UnixSocketConnection", "XenBusConnection", "XenBusConnectionWinWINPV", "XenBusConnectionWinGPLPV"] import logging import errno @@ -210,7 +210,7 @@ def connect(self): _wmiSession = None -class XenBusConnectionWin(FileDescriptorConnection): +class XenBusConnectionWinWINPV(FileDescriptorConnection): session = None response_packet = None @@ -313,7 +313,7 @@ def disconnect(self, silent=True): _winDevicePath = None -class XenBusConnectionWin2008(FileDescriptorConnection): +class XenBusConnectionWinGPLPV(FileDescriptorConnection): def __init__(self): global _winDevicePath From bd9f4b7b4c875fa99384fd93e1a15fab0156f0c2 Mon Sep 17 00:00:00 2001 From: James Dingwall Date: Fri, 29 May 2015 16:33:45 +0100 Subject: [PATCH 23/30] Try to decide on the windows xenbus connection class by looking for the project drivers rather than guess by operating system version. --- pyxs/client.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/pyxs/client.py b/pyxs/client.py index fbd1766..66cc851 100644 --- a/pyxs/client.py +++ b/pyxs/client.py @@ -91,22 +91,14 @@ def __init__(self, unix_socket_path=None, socket_timeout=None, self.connection = UnixSocketConnection( unix_socket_path, socket_timeout=socket_timeout) elif os.name in ["nt"]: + # There are two windows pv driver projects, examine wmi + # classes to try and determine which one we want to use. import wmi - c = wmi.WMI() try: - win32_os = c.Win32_OperatingSystem() - except wmi.x_wmi: - sleep(0.5) - try: - win32_os = c.Win32_OperatingSystem() - except wmi.x_wmi: - raise PyXSError() - - for system in win32_os: - if re.match('Microsoft Windows Server 2008.*', system.caption): - self.connection = XenBusConnectionWinGPLPV() - else: - self.connection = XenBusConnectionWinWINPV() + wmi.WMI(moniker="//./root/wmi", find_classes=False).XenProjectXenStoreBase() + self.connection = XenBusConnectionWinWINPV() + except AttributeError: + self.connection = XenBusConnectionWinGPLPV() else: self.connection = XenBusConnection(xen_bus_path) From b5d311f544d02206fd2bc741905b8e693825f70f Mon Sep 17 00:00:00 2001 From: James Dingwall Date: Fri, 29 May 2015 16:35:30 +0100 Subject: [PATCH 24/30] remove unused import. --- pyxs/client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyxs/client.py b/pyxs/client.py index 66cc851..25e4476 100644 --- a/pyxs/client.py +++ b/pyxs/client.py @@ -25,7 +25,6 @@ import time import posixpath import os -from time import sleep from collections import deque from ._internal import Event, Packet, Op From d82404e775301f35c6eb50a3ddf649e4571154be Mon Sep 17 00:00:00 2001 From: James Dingwall Date: Wed, 3 Jun 2015 21:54:00 +0100 Subject: [PATCH 25/30] remove unused pythoncom import --- pyxs/connection.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyxs/connection.py b/pyxs/connection.py index a38a80e..b4cb14a 100644 --- a/pyxs/connection.py +++ b/pyxs/connection.py @@ -23,7 +23,6 @@ from time import sleep from ._internal import Packet, Op sys.coinit_flags = 0 -import pythoncom if os.name in ["nt"]: import wmi @@ -39,7 +38,6 @@ from ctypes.wintypes import HKEY from ctypes.wintypes import BYTE sys.coinit_flags = 0 - import pythoncom if sys.version_info[0] is not 3: bytes, str = str, unicode From b8b12c14dfc1d67a6bce4d13740d9aecf4f2a951 Mon Sep 17 00:00:00 2001 From: James Dingwall Date: Fri, 12 Feb 2016 10:14:03 +0000 Subject: [PATCH 26/30] workaround for http://lists.xenproject.org/archives/html/win-pv-devel/2016-02/msg00005.html --- pyxs/_internal.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pyxs/_internal.py b/pyxs/_internal.py index f8fa8d5..edad126 100644 --- a/pyxs/_internal.py +++ b/pyxs/_internal.py @@ -13,6 +13,7 @@ __all__ = ["Event", "Op", "Packet"] +import os import struct from collections import namedtuple @@ -67,12 +68,17 @@ class Packet(namedtuple("_Packet", "op rq_id tx_id size payload")): def __new__(cls, op, payload, rq_id=None, tx_id=None): # Checking restrictions: - # a) payload is limited to 4096 bytes. - if len(payload) > 4096: - raise InvalidPayload(payload) - # b) operation requested is present in ``xsd_sockmsg_type``. + # a) operation requested is present in ``xsd_sockmsg_type``. if op not in Op: raise InvalidOperation(op) + # This is to code around http://lists.xenproject.org/archives/html/win-pv-devel/2016-02/msg00005.html + if os.name == "nt" and op == Op.READ and payload is None: + payload = "" + + # b) payload is limited to 4096 bytes. + if len(payload) > 4096: + raise InvalidPayload(payload) + return super(Packet, cls).__new__(cls, op, rq_id or 0, tx_id or 0, len(payload), payload) From fb83b1ac068647d7954b3bfced31ef45ea9a1843 Mon Sep 17 00:00:00 2001 From: James Dingwall Date: Mon, 11 Apr 2016 16:34:51 +0100 Subject: [PATCH 27/30] prefer "/dev/xen/xenbus" to "/proc/xen/xenbus" on Linux --- pyxs/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyxs/connection.py b/pyxs/connection.py index b4cb14a..d683a19 100644 --- a/pyxs/connection.py +++ b/pyxs/connection.py @@ -184,7 +184,7 @@ def __init__(self, path=None): system = platform.system() if system == "Linux": - path = "/proc/xen/xenbus" + path = "/dev/xen/xenbus" if os.path.exists("/dev/xen/xenbus") else "/proc/xen/xenbus" elif system == "NetBSD": path = "/kern/xen/xenbus" else: From 0446c8b33009346a0c45261970532b31cd926057 Mon Sep 17 00:00:00 2001 From: James Dingwall Date: Mon, 11 Apr 2016 16:39:18 +0100 Subject: [PATCH 28/30] prefer XenBusConnection over UnixSocketConnection on posix platforms --- pyxs/client.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pyxs/client.py b/pyxs/client.py index 25e4476..3d614b7 100644 --- a/pyxs/client.py +++ b/pyxs/client.py @@ -86,9 +86,12 @@ def __init__(self, unix_socket_path=None, socket_timeout=None, xen_bus_path=None, connection=None, transaction=None): if connection: self.connection = connection - elif os.name in ["posix"] and (unix_socket_path or not xen_bus_path): - self.connection = UnixSocketConnection( - unix_socket_path, socket_timeout=socket_timeout) + elif os.name in ["posix"]: + if xen_bus_path or not unix_socket_path: + self.connection = XenBusConnection(xen_bus_path) + else: + self.connection = UnixSocketConnection( + unix_socket_path, socket_timeout=socket_timeout) elif os.name in ["nt"]: # There are two windows pv driver projects, examine wmi # classes to try and determine which one we want to use. @@ -99,7 +102,7 @@ def __init__(self, unix_socket_path=None, socket_timeout=None, except AttributeError: self.connection = XenBusConnectionWinGPLPV() else: - self.connection = XenBusConnection(xen_bus_path) + raise ConnectionError("no connection to xenstore available") self.tx_id = 0 self.tx_lock = threading.Lock() From b68f04669aa28c032b08a9f675745a633e108051 Mon Sep 17 00:00:00 2001 From: James Dingwall Date: Sat, 28 Apr 2018 21:24:24 +0100 Subject: [PATCH 29/30] Rename DEBUG to CONTROL to match xen-4.9. Don't send an empty payload with CONTROL. --- pyxs/_internal.py | 2 +- pyxs/client.py | 2 +- tests.py | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pyxs/_internal.py b/pyxs/_internal.py index edad126..bfde2a9 100644 --- a/pyxs/_internal.py +++ b/pyxs/_internal.py @@ -22,7 +22,7 @@ #: Operations supported by XenStore. Operations = Op = namedtuple("Operations", [ - "DEBUG", + "CONTROL", "DIRECTORY", "READ", "GET_PERMS", diff --git a/pyxs/client.py b/pyxs/client.py index 3d614b7..f0c46d4 100644 --- a/pyxs/client.py +++ b/pyxs/client.py @@ -454,7 +454,7 @@ def wait(self, sleep=None): # Executing a noop, hopefuly we'll get some events queued # in the meantime. Note: I know it sucks, but it seems like # there's no other way ... - self.client.execute_command(Op.DEBUG, "") + self.client.execute_command(Op.CONTROL, "check") if sleep is not None: time.sleep(sleep) diff --git a/tests.py b/tests.py index af8f462..a02acdf 100644 --- a/tests.py +++ b/tests.py @@ -32,7 +32,7 @@ def test_packet(): # b) invalid payload -- maximum size exceeded. with pytest.raises(InvalidPayload): - Packet(Op.DEBUG, "hello" * 4096, 0) + Packet(Op.CONTROL, "hello" * 4096, 0) # Helpers. @@ -169,13 +169,13 @@ def test_client_execute_command(): # a) arguments contain invalid characters. with pytest.raises(ValueError): - c.execute_command(Op.DEBUG, "\x07foo") + c.execute_command(Op.CONTROL, "\x07foo") # b) command validator fails. - c.COMMAND_VALIDATORS[Op.DEBUG] = lambda *args: False + c.COMMAND_VALIDATORS[Op.CONTROL] = lambda *args: False with pytest.raises(ValueError): - c.execute_command(Op.DEBUG, "foo") - c.COMMAND_VALIDATORS.pop(Op.DEBUG) + c.execute_command(Op.CONTROL, "foo") + c.COMMAND_VALIDATORS.pop(Op.CONTROL) # c) ``Packet`` constructor fails. with pytest.raises(InvalidPayload): @@ -187,7 +187,7 @@ def test_client_execute_command(): _old_recv = c.connection.recv # e) XenStore returns a packet with invalid operation in the header. - c.connection.recv = lambda *args: Packet(Op.DEBUG, "boo") + c.connection.recv = lambda *args: Packet(Op.CONTROL, "boo") with pytest.raises(UnexpectedPacket): c.execute_command(Op.READ, "/foo/bar") c.connection.recv = _old_recv From b1bb2c4ea33c59d77fc84dea18a2a49e1d3122d0 Mon Sep 17 00:00:00 2001 From: JKDingwall Date: Mon, 30 Sep 2019 13:25:36 +0100 Subject: [PATCH 30/30] Use noop control packet while waiting The check operation essentially does an fsck of the xenstore tdb database which is expensive. A small xenstore patch can be used to introduce a noop control function and it is preferable to use that. No operation should be necessary here but the implementation of the wait() is wrong in this branch. The upstream release handles this correctly but the change would be quite intrusive. --- pyxs/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyxs/client.py b/pyxs/client.py index f0c46d4..929cb4d 100644 --- a/pyxs/client.py +++ b/pyxs/client.py @@ -454,7 +454,7 @@ def wait(self, sleep=None): # Executing a noop, hopefuly we'll get some events queued # in the meantime. Note: I know it sucks, but it seems like # there's no other way ... - self.client.execute_command(Op.CONTROL, "check") + self.client.execute_command(Op.CONTROL, "noop") if sleep is not None: time.sleep(sleep)