[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[win-pv-devel] [PATCH v2] Add a user mode library wrapper for XENIFACE IOCTLs



From: Rafal Wojdyla <omeg@xxxxxxxxxxxxxxxxxxxxxx>

Signed-off-by: Rafal Wojdyla <omeg@xxxxxxxxxxxxxxxxxxxxxx>
[fix compile warnings, update visual studio files]
Signed-off-by: Marek Marczykowski-Górecki <marmarek@xxxxxxxxxxxxxxxxxxxxxx>
---
This was posted before here:
https://lists.xenproject.org/archives/html/win-pv-devel/2015-11/msg00014.html

Back then I've raised a concern about code duplication caused by a
different API than libxc (having libxenvchan in mind). But two years
latter it looks like it isn't such a problem. libxenchan is the only
piece being effectively duplicated (at least in Qubes OS), and
everything else is really different anyway because of Linux/Windows
differences. So, I think it isn't an issue.

Also I've renamed XcEvtchnBindUnbound to XcEvtchnOpenUnbound, as
requested in review back then.

This has been tested with vs2017/WDK10 build for Windows 7 64bit, both
on Windows 7 and Windows 10. The patch assume "Add Windows 7 build
target" patches applied, but it should be easy to apply without them
too.
I've updated vs2015 files too, but don't have tools to test them (it
isn't possible to download free vs2015 anymore).

Changes in v2:
 - add xenproject to package depencencies in solution file
 - allow multiple concurrent accesses to xeniface device
 - drop Windows 7 targets
 - disable spectre warning
 - remove filter files
---
 include/xencontrol.h                 | 342 +++++++++++++
 src/xencontrol/xencontrol.c          | 919 +++++++++++++++++++++++++++++++++++
 src/xencontrol/xencontrol.rc         |  24 +
 src/xencontrol/xencontrol_private.h  |  49 ++
 vs2015/package/package.vcxproj       |   3 +
 vs2015/xencontrol/xencontrol.vcxproj |  67 +++
 vs2015/xeniface.sln                  |  26 +
 vs2017/package/package.vcxproj       |   3 +
 vs2017/xencontrol/xencontrol.vcxproj |  67 +++
 vs2017/xeniface.sln                  |  27 +
 10 files changed, 1527 insertions(+)
 create mode 100644 include/xencontrol.h
 create mode 100644 src/xencontrol/xencontrol.c
 create mode 100644 src/xencontrol/xencontrol.rc
 create mode 100644 src/xencontrol/xencontrol_private.h
 create mode 100644 vs2015/xencontrol/xencontrol.vcxproj
 create mode 100644 vs2017/xencontrol/xencontrol.vcxproj

diff --git a/include/xencontrol.h b/include/xencontrol.h
new file mode 100644
index 0000000..4560bc6
--- /dev/null
+++ b/include/xencontrol.h
@@ -0,0 +1,342 @@
+#ifndef _XENCONTROL_H_
+#define _XENCONTROL_H_
+
+#include <windows.h>
+#include <varargs.h>
+#include "xeniface_ioctls.h"
+
+#ifdef XENCONTROL_EXPORTS
+#    define XENCONTROL_API __declspec(dllexport)
+#else
+#    define XENCONTROL_API __declspec(dllimport)
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*! \typedef PXENCONTROL_CONTEXT
+    \brief Library handle representing a Xen Interface session
+*/
+struct _XENCONTROL_CONTEXT;
+typedef struct _XENCONTROL_CONTEXT *PXENCONTROL_CONTEXT;
+
+/*! \typedef XENCONTROL_LOG_LEVEL
+    \brief Log levels used by the library
+*/
+typedef enum
+_XENCONTROL_LOG_LEVEL {
+    XLL_ERROR = 1,
+    XLL_WARNING,
+    XLL_INFO,
+    XLL_DEBUG,
+    XLL_TRACE,
+} XENCONTROL_LOG_LEVEL;
+
+/*! \typedef XENCONTROL_LOGGER
+    \brief Callback for receiving diagnostic messages from the library
+*/
+typedef void
+XENCONTROL_LOGGER(
+    IN  XENCONTROL_LOG_LEVEL LogLevel,
+    IN  const CHAR *Function,
+    IN  const WCHAR *Message,
+    IN  va_list Args
+    );
+
+/*! \brief Register a callback for receiving library's diagnostic messages
+    \param Xc Xencontrol handle returned by XcOpen()
+    \param Logger Callback to register
+*/
+XENCONTROL_API
+void
+XcRegisterLogger(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  XENCONTROL_LOGGER *Logger
+    );
+
+/*! \brief Set log level threshold for library's diagnostic messages
+    \param Xc Xencontrol handle returned by XcOpen()
+    \param LogLevel Only messages with this level and above will be sent to 
the logger callback
+*/
+XENCONTROL_API
+void
+XcSetLogLevel(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  XENCONTROL_LOG_LEVEL LogLevel
+    );
+
+/*! \brief Open the Xen Interface device
+    \param Logger Callback for receiving library's diagnostic messages
+    \param Xc Xencontrol handle representing a Xen Interface session
+    \return Error code
+*/
+XENCONTROL_API
+DWORD
+XcOpen(
+    IN  XENCONTROL_LOGGER *Logger,
+    OUT PXENCONTROL_CONTEXT *Xc
+    );
+
+/*! \brief Close the Xen Interface device
+    \param Xc Xencontrol handle returned by XcOpen()
+*/
+XENCONTROL_API
+void
+XcClose(
+    IN  PXENCONTROL_CONTEXT Xc
+    );
+
+/*! \brief Open an unbound event channel
+    \param Xc Xencontrol handle returned by XcOpen()
+    \param RemoteDomain ID of a remote domain that will bind the channel
+    \param Event Handle to an event object that will receive event channel 
notifications
+    \param Mask Set to TRUE if the event channel should be initially masked
+    \param LocalPort Port number that is assigned to the event channel
+    \return Error code
+*/
+XENCONTROL_API
+DWORD
+XcEvtchnOpenUnbound(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  USHORT RemoteDomain,
+    IN  HANDLE Event,
+    IN  BOOL Mask,
+    OUT ULONG *LocalPort
+    );
+
+/*! \brief Open an event channel that was already bound by a remote domain
+    \param Xc Xencontrol handle returned by XcOpen()
+    \param RemoteDomain ID of a remote domain that has already bound the 
channel
+    \param RemotePort Port number that is assigned to the event channel in the 
\a RemoteDomain
+    \param Event Handle to an event that will receive event channel 
notifications
+    \param Mask Set to TRUE if the event object channel should be initially 
masked
+    \param LocalPort Port number that is assigned to the event channel
+    \return Error code
+*/
+XENCONTROL_API
+DWORD
+XcEvtchnBindInterdomain(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  USHORT RemoteDomain,
+    IN  ULONG RemotePort,
+    IN  HANDLE Event,
+    IN  BOOL Mask,
+    OUT ULONG *LocalPort
+    );
+
+/*! \brief Close an event channel
+    \param Xc Xencontrol handle returned by XcOpen()
+    \param LocalPort Port number that is assigned to the event channel
+    \return Error code
+*/
+XENCONTROL_API
+DWORD
+XcEvtchnClose(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  ULONG LocalPort
+    );
+
+/*! \brief Notify the remote end of an event channel
+    \param Xc Xencontrol handle returned by XcOpen()
+    \param LocalPort Port number that is assigned to the event channel
+    \return Error code
+*/
+XENCONTROL_API
+DWORD
+XcEvtchnNotify(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  ULONG LocalPort
+    );
+
+/*! \brief Unmask an event channel
+    \param Xc Xencontrol handle returned by XcOpen()
+    \param LocalPort Port number that is assigned to the event channel
+    \return Error code
+*/
+XENCONTROL_API
+DWORD
+XcEvtchnUnmask(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  ULONG LocalPort
+    );
+
+/*! \brief Grant a \a RemoteDomain permission to access local memory pages
+    \param Xc Xencontrol handle returned by XcOpen()
+    \param RemoteDomain ID of a remote domain that is being granted access
+    \param NumberPages Number of 4k pages to grant access to
+    \param NotifyOffset Offset of a byte in the granted region that will be 
set to 0 when the grant is revoked
+    \param NotifyPort Local port number of an open event channel that will be 
notified when the grant is revoked
+    \param Flags Grant options
+    \param Address Local user mode address of the granted memory region
+    \param References An array of Xen grant numbers for every granted page
+    \return Error code
+*/
+XENCONTROL_API
+DWORD
+XcGnttabPermitForeignAccess(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  USHORT RemoteDomain,
+    IN  ULONG NumberPages,
+    IN  ULONG NotifyOffset,
+    IN  ULONG NotifyPort,
+    IN  XENIFACE_GNTTAB_PAGE_FLAGS Flags,
+    OUT PVOID *Address,
+    OUT ULONG *References
+    );
+
+/*! \brief Revoke a foreign domain access to previously granted memory region
+    \param Xc Xencontrol handle returned by XcOpen()
+    \param Address Local user mode address of the granted memory region
+    \return Error code
+*/
+XENCONTROL_API
+DWORD
+XcGnttabRevokeForeignAccess(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  PVOID Address
+    );
+
+/*! \brief Map a foreign memory region into the current address space
+    \param Xc Xencontrol handle returned by XcOpen()
+    \param RemoteDomain ID of a remote domain that has granted access to the 
pages
+    \param NumberPages Number of 4k pages to map
+    \param References An array of Xen grant numbers for every granted page
+    \param NotifyOffset Offset of a byte in the mapped region that will be set 
to 0 when the region is unmapped
+    \param NotifyPort Local port number of an open event channel that will be 
notified when the region is unmapped
+    \param Flags Map options
+    \param Address Local user mode address of the mapped memory region
+    \return Error code
+*/
+XENCONTROL_API
+DWORD
+XcGnttabMapForeignPages(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  USHORT RemoteDomain,
+    IN  ULONG NumberPages,
+    IN  PULONG References,
+    IN  ULONG NotifyOffset,
+    IN  ULONG NotifyPort,
+    IN  XENIFACE_GNTTAB_PAGE_FLAGS Flags,
+    OUT PVOID *Address
+    );
+
+/*! \brief Unmap a foreign memory region from the current address space
+    \param Xc Xencontrol handle returned by XcOpen()
+    \param Address Local user mode address of the mapped memory region
+    \return Error code
+*/
+XENCONTROL_API
+DWORD
+XcGnttabUnmapForeignPages(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  PVOID Address
+    );
+
+/*! \brief Read a XenStore key
+    \param Xc Xencontrol handle returned by XcOpen()
+    \param Path Path to the key
+    \param cbValue Size of the \a Value buffer, in bytes
+    \param Value Buffer that receives the value
+    \return Error code
+*/
+XENCONTROL_API
+DWORD
+XcStoreRead(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  PCHAR Path,
+    IN  DWORD cbValue,
+    OUT CHAR *Value
+    );
+
+/*! \brief Write a value to a XenStore key
+    \param Xc Xencontrol handle returned by XcOpen()
+    \param Path Path to the key
+    \param Value Value to write
+    \return Error code
+*/
+XENCONTROL_API
+DWORD
+XcStoreWrite(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  PCHAR Path,
+    IN  PCHAR Value
+    );
+
+/*! \brief Enumerate all immediate child keys of a XenStore key
+    \param Xc Xencontrol handle returned by XcOpen()
+    \param Path Path to the key
+    \param cbOutput Size of the \a Output buffer, in bytes
+    \param Output Buffer that receives a NUL-separated child key names
+    \return Error code
+*/
+XENCONTROL_API
+DWORD
+XcStoreDirectory(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  PCHAR Path,
+    IN  DWORD cbOutput,
+    OUT CHAR *Output
+    );
+
+/*! \brief Remove a XenStore key
+    \param Xc Xencontrol handle returned by XcOpen()
+    \param Path Path to the key
+    \return Error code
+*/
+XENCONTROL_API
+DWORD
+XcStoreRemove(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  PCHAR Path
+    );
+
+/*! \brief Set permissions of a XenStore key
+    \param Xc Xencontrol handle returned by XcOpen()
+    \param Path Path to the key
+    \param Count Number of permissions
+    \param Permissions Array of permissions to set
+    \return Error code
+*/
+XENCONTROL_API
+DWORD
+XcStoreSetPermissions(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  PCHAR Path,
+    IN  ULONG Count,
+    IN  PXENIFACE_STORE_PERMISSION Permissions
+    );
+
+/*! \brief Add a XenStore key watch
+    \param Xc Xencontrol handle returned by XcOpen()
+    \param Path Path to the key to be watched
+    \param Event Handle to an event that will be signaled when the watch fires
+    \param Handle An opaque value representing the watch
+    \return Error code
+*/
+XENCONTROL_API
+DWORD
+XcStoreAddWatch(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  PCHAR Path,
+    IN  HANDLE Event,
+    OUT PVOID *Handle
+    );
+
+/*! \brief Remove a XenStore watch
+    \param Xc Xencontrol handle returned by XcOpen()
+    \param Handle Watch handle returned by XcStoreAddWatch()
+    \return Error code
+*/
+XENCONTROL_API
+DWORD
+XcStoreRemoveWatch(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  PVOID Handle
+    );
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _XENCONTROL_H_
diff --git a/src/xencontrol/xencontrol.c b/src/xencontrol/xencontrol.c
new file mode 100644
index 0000000..777fd29
--- /dev/null
+++ b/src/xencontrol/xencontrol.c
@@ -0,0 +1,919 @@
+#define INITGUID
+#include <windows.h>
+#include <winioctl.h>
+#include <setupapi.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "xencontrol.h"
+#include "xencontrol_private.h"
+
+BOOL APIENTRY
+DllMain(
+    IN  HMODULE Module,
+    IN  DWORD ReasonForCall,
+    IN  LPVOID Reserved
+)
+{
+    UNREFERENCED_PARAMETER(Module);
+    UNREFERENCED_PARAMETER(ReasonForCall);
+    UNREFERENCED_PARAMETER(Reserved);
+    return TRUE;
+}
+
+static void
+_Log(
+    IN  XENCONTROL_LOGGER *Logger,
+    IN  XENCONTROL_LOG_LEVEL LogLevel,
+    IN  XENCONTROL_LOG_LEVEL CurrentLogLevel,
+    IN  PCHAR Function,
+    IN  PWCHAR Format,
+    ...
+    )
+{
+    va_list Args;
+    DWORD LastError;
+
+    if (Logger == NULL)
+        return;
+
+    if (LogLevel > CurrentLogLevel)
+        return;
+
+    LastError = GetLastError();
+    va_start(Args, Format);
+    Logger(LogLevel, Function, Format, Args);
+    va_end(Args);
+    SetLastError(LastError);
+}
+
+static void
+_LogMultiSz(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  PCHAR Caller,
+    IN  XENCONTROL_LOG_LEVEL Level,
+    IN  PCHAR MultiSz
+    )
+{
+    PCHAR Ptr;
+    ULONG Len;
+
+    for (Ptr = MultiSz; *Ptr;) {
+        Len = (ULONG)strlen(Ptr);
+        _Log(Xc->Logger, Level, Xc->LogLevel, Caller, L"%S", Ptr);
+        Ptr += ((ptrdiff_t)Len + 1);
+    }
+}
+
+void
+XcRegisterLogger(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  XENCONTROL_LOGGER *Logger
+    )
+{
+    Xc->Logger = Logger;
+}
+
+void
+XcSetLogLevel(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  XENCONTROL_LOG_LEVEL LogLevel
+    )
+{
+    Xc->LogLevel = LogLevel;
+}
+
+DWORD
+XcOpen(
+    IN  XENCONTROL_LOGGER *Logger,
+    OUT PXENCONTROL_CONTEXT *Xc
+    )
+{
+    HDEVINFO DevInfo;
+    SP_DEVICE_INTERFACE_DATA InterfaceData;
+    SP_DEVICE_INTERFACE_DETAIL_DATA *DetailData = NULL;
+    DWORD BufferSize;
+    PXENCONTROL_CONTEXT Context;
+
+    Context = malloc(sizeof(*Context));
+    if (Context == NULL)
+        return ERROR_NOT_ENOUGH_MEMORY;
+
+    Context->Logger = Logger;
+    Context->LogLevel = XLL_INFO;
+    Context->RequestId = 1;
+    InitializeListHead(&Context->RequestList);
+    InitializeCriticalSection(&Context->RequestListLock);
+
+    DevInfo = SetupDiGetClassDevs(&GUID_INTERFACE_XENIFACE, 0, NULL, 
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
+    if (DevInfo == INVALID_HANDLE_VALUE) {
+        _Log(Logger, XLL_ERROR, Context->LogLevel, __FUNCTION__,
+             L"XENIFACE device class doesn't exist");
+        goto fail;
+    }
+
+    InterfaceData.cbSize = sizeof(InterfaceData);
+    if (!SetupDiEnumDeviceInterfaces(DevInfo, NULL, &GUID_INTERFACE_XENIFACE, 
0, &InterfaceData)) {
+        _Log(Logger, XLL_ERROR, Context->LogLevel, __FUNCTION__,
+             L"Failed to enumerate XENIFACE devices");
+        goto fail;
+    }
+
+    SetupDiGetDeviceInterfaceDetail(DevInfo, &InterfaceData, NULL, 0, 
&BufferSize, NULL);
+    if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+        _Log(Logger, XLL_ERROR, Context->LogLevel, __FUNCTION__,
+             L"Failed to get buffer size for device details");
+        goto fail;
+    }
+
+    // Using 'BufferSize' from failed function call
+#pragma warning(suppress: 6102)
+    DetailData = (SP_DEVICE_INTERFACE_DETAIL_DATA *)malloc(BufferSize);
+    if (!DetailData) {
+        SetLastError(ERROR_OUTOFMEMORY);
+        goto fail;
+    }
+
+    DetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
+
+    if (!SetupDiGetDeviceInterfaceDetail(DevInfo, &InterfaceData, DetailData, 
BufferSize, NULL, NULL)) {
+        _Log(Logger, XLL_ERROR, Context->LogLevel, __FUNCTION__,
+             L"Failed to get XENIFACE device path");
+        goto fail;
+    }
+
+    Context->XenIface = CreateFile(DetailData->DevicePath,
+                                   FILE_GENERIC_READ | FILE_GENERIC_WRITE,
+                                   FILE_SHARE_READ | FILE_SHARE_WRITE,
+                                   NULL,
+                                   OPEN_EXISTING,
+                                   FILE_ATTRIBUTE_NORMAL | 
FILE_FLAG_OVERLAPPED,
+                                   NULL);
+
+    if (Context->XenIface == INVALID_HANDLE_VALUE) {
+        _Log(Logger, XLL_ERROR, Context->LogLevel, __FUNCTION__,
+             L"Failed to open XENIFACE device, path: %s", 
DetailData->DevicePath);
+        goto fail;
+    }
+
+    _Log(Logger, XLL_ERROR, Context->LogLevel, __FUNCTION__,
+         L"XenIface handle: %p", Context->XenIface);
+
+    free(DetailData);
+    *Xc = Context;
+    return ERROR_SUCCESS;
+
+fail:
+    _Log(Logger, XLL_ERROR, Context->LogLevel, __FUNCTION__,
+         L"Error: 0x%x", GetLastError());
+
+    free(DetailData);
+    return GetLastError();
+}
+
+void
+XcClose(
+    IN  PXENCONTROL_CONTEXT Xc
+    )
+{
+    CloseHandle(Xc->XenIface);
+    DeleteCriticalSection(&Xc->RequestListLock);
+    free(Xc);
+}
+
+DWORD
+XcEvtchnOpenUnbound(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  USHORT RemoteDomain,
+    IN  HANDLE Event,
+    IN  BOOL Mask,
+    OUT ULONG *LocalPort
+    )
+{
+    XENIFACE_EVTCHN_BIND_UNBOUND_IN In;
+    XENIFACE_EVTCHN_BIND_UNBOUND_OUT Out;
+    DWORD Returned;
+    BOOL Success;
+
+    In.RemoteDomain = RemoteDomain;
+    In.Event = Event;
+    In.Mask = !!Mask;
+
+    Log(XLL_DEBUG, L"RemoteDomain: %d, Event: %p, Mask: %d", RemoteDomain, 
Event, Mask);
+    Success = DeviceIoControl(Xc->XenIface,
+                              IOCTL_XENIFACE_EVTCHN_BIND_UNBOUND,
+                              &In, sizeof(In),
+                              &Out, sizeof(Out),
+                              &Returned,
+                              NULL);
+
+    if (!Success) {
+        Log(XLL_ERROR, L"IOCTL_XENIFACE_EVTCHN_BIND_UNBOUND_PORT failed");
+        goto fail;
+    }
+
+    *LocalPort = Out.LocalPort;
+    Log(XLL_DEBUG, L"LocalPort: %lu", *LocalPort);
+
+    return ERROR_SUCCESS;
+
+fail:
+    Log(XLL_ERROR, L"Error: 0x%x", GetLastError());
+    return GetLastError();
+}
+
+DWORD
+XcEvtchnBindInterdomain(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  USHORT RemoteDomain,
+    IN  ULONG RemotePort,
+    IN  HANDLE Event,
+    IN  BOOL Mask,
+    OUT ULONG *LocalPort
+    )
+{
+    XENIFACE_EVTCHN_BIND_INTERDOMAIN_IN In;
+    XENIFACE_EVTCHN_BIND_INTERDOMAIN_OUT Out;
+    DWORD Returned;
+    BOOL Success;
+
+    In.RemoteDomain = RemoteDomain;
+    In.RemotePort = RemotePort;
+    In.Event = Event;
+    In.Mask = !!Mask;
+
+    Log(XLL_DEBUG, L"RemoteDomain: %d, RemotePort %lu, Event: %p, Mask: %d",
+        RemoteDomain, RemotePort, Event, Mask);
+    Success = DeviceIoControl(Xc->XenIface,
+                              IOCTL_XENIFACE_EVTCHN_BIND_INTERDOMAIN,
+                              &In, sizeof(In),
+                              &Out, sizeof(Out),
+                              &Returned,
+                              NULL);
+
+    if (!Success) {
+        Log(XLL_ERROR, L"IOCTL_XENIFACE_EVTCHN_BIND_INTERDOMAIN failed");
+        goto fail;
+    }
+
+    *LocalPort = Out.LocalPort;
+    Log(XLL_DEBUG, L"LocalPort: %lu", *LocalPort);
+
+    return ERROR_SUCCESS;
+
+fail:
+    Log(XLL_ERROR, L"Error: 0x%x", GetLastError());
+    return GetLastError();
+}
+
+DWORD
+XcEvtchnClose(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  ULONG LocalPort
+    )
+{
+    XENIFACE_EVTCHN_CLOSE_IN In;
+    DWORD Returned;
+    BOOL Success;
+
+    In.LocalPort = LocalPort;
+
+    Log(XLL_DEBUG, L"LocalPort: %lu", LocalPort);
+    Success = DeviceIoControl(Xc->XenIface,
+                              IOCTL_XENIFACE_EVTCHN_CLOSE,
+                              &In, sizeof(In),
+                              NULL, 0,
+                              &Returned,
+                              NULL);
+
+    if (!Success) {
+        Log(XLL_ERROR, L"IOCTL_XENIFACE_EVTCHN_CLOSE failed");
+        goto fail;
+    }
+
+    return ERROR_SUCCESS;
+
+fail:
+    Log(XLL_ERROR, L"Error: 0x%x", GetLastError());
+    return GetLastError();
+}
+
+DWORD
+XcEvtchnNotify(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  ULONG LocalPort
+    )
+{
+    XENIFACE_EVTCHN_NOTIFY_IN In;
+    DWORD Returned;
+    BOOL Success;
+
+    In.LocalPort = LocalPort;
+
+    Log(XLL_DEBUG, L"LocalPort: %lu", LocalPort);
+    Success = DeviceIoControl(Xc->XenIface,
+                              IOCTL_XENIFACE_EVTCHN_NOTIFY,
+                              &In, sizeof(In),
+                              NULL, 0,
+                              &Returned,
+                              NULL);
+
+    if (!Success) {
+        Log(XLL_ERROR, L"IOCTL_XENIFACE_EVTCHN_NOTIFY failed");
+        goto fail;
+    }
+
+    return ERROR_SUCCESS;
+
+fail:
+    Log(XLL_ERROR, L"Error: 0x%x", GetLastError());
+    return GetLastError();
+}
+
+DWORD
+XcEvtchnUnmask(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  ULONG LocalPort
+    )
+{
+    XENIFACE_EVTCHN_UNMASK_IN In;
+    DWORD Returned;
+    BOOL Success;
+
+    In.LocalPort = LocalPort;
+
+    Log(XLL_DEBUG, L"LocalPort: %lu", LocalPort);
+    Success = DeviceIoControl(Xc->XenIface,
+                              IOCTL_XENIFACE_EVTCHN_UNMASK,
+                              &In, sizeof(In),
+                              NULL, 0,
+                              &Returned,
+                              NULL);
+
+    if (!Success) {
+        Log(XLL_ERROR, L"IOCTL_XENIFACE_EVTCHN_UNMASK failed");
+        goto fail;
+    }
+
+    return ERROR_SUCCESS;
+
+fail:
+    Log(XLL_ERROR, L"Error: 0x%x", GetLastError());
+    return GetLastError();
+}
+
+static PXENCONTROL_GNTTAB_REQUEST
+FindRequest(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  PVOID Address
+    )
+{
+    PLIST_ENTRY Entry;
+    PXENCONTROL_GNTTAB_REQUEST ReturnRequest = NULL;
+
+    EnterCriticalSection(&Xc->RequestListLock);
+    Entry = Xc->RequestList.Flink;
+    while (Entry != &Xc->RequestList) {
+        PXENCONTROL_GNTTAB_REQUEST Request = CONTAINING_RECORD(Entry, 
XENCONTROL_GNTTAB_REQUEST, ListEntry);
+
+        if (Request->Address == Address) {
+            ReturnRequest = Request;
+            break;
+        }
+
+        Entry = Entry->Flink;
+    }
+    LeaveCriticalSection(&Xc->RequestListLock);
+
+    return ReturnRequest;
+}
+
+DWORD
+XcGnttabPermitForeignAccess(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  USHORT RemoteDomain,
+    IN  ULONG NumberPages,
+    IN  ULONG NotifyOffset,
+    IN  ULONG NotifyPort,
+    IN  XENIFACE_GNTTAB_PAGE_FLAGS Flags,
+    OUT PVOID *Address,
+    OUT ULONG *References
+    )
+{
+    XENIFACE_GNTTAB_PERMIT_FOREIGN_ACCESS_IN In;
+    XENIFACE_GNTTAB_PERMIT_FOREIGN_ACCESS_OUT *Out;
+    PXENCONTROL_GNTTAB_REQUEST Request;
+    DWORD Returned, Size;
+    BOOL Success;
+    DWORD Status;
+
+    // lock the whole operation to not generate duplicate IDs
+    EnterCriticalSection(&Xc->RequestListLock);
+
+    In.RequestId = Xc->RequestId;
+    In.RemoteDomain = RemoteDomain;
+    In.NumberPages = NumberPages;
+    In.NotifyOffset = NotifyOffset;
+    In.NotifyPort = NotifyPort;
+    In.Flags = Flags;
+
+    Size = (ULONG)FIELD_OFFSET(XENIFACE_GNTTAB_PERMIT_FOREIGN_ACCESS_OUT, 
References[NumberPages]);
+    Out = malloc(Size);
+    Request = malloc(sizeof(*Request));
+
+    Status = ERROR_OUTOFMEMORY;
+    if (!Request || !Out)
+        goto fail;
+
+    ZeroMemory(Request, sizeof(*Request));
+    Request->Id = In.RequestId;
+
+    Log(XLL_DEBUG, L"Id %lu, RemoteDomain: %d, NumberPages: %lu, NotifyOffset: 
0x%x, NotifyPort: %lu, Flags: 0x%x",
+        In.RequestId, RemoteDomain, NumberPages, NotifyOffset, NotifyPort, 
Flags);
+
+    Success = DeviceIoControl(Xc->XenIface,
+                              IOCTL_XENIFACE_GNTTAB_PERMIT_FOREIGN_ACCESS,
+                              &In, sizeof(In),
+                              Out, Size,
+                              &Returned,
+                              &Request->Overlapped);
+
+    Status = GetLastError();
+    // this IOCTL is expected to be pending on success
+    if (!Success) {
+        if (Status != ERROR_IO_PENDING) {
+            Log(XLL_ERROR, L"IOCTL_XENIFACE_GNTTAB_GRANT_PAGES failed");
+            goto fail;
+        }
+    } else {
+        Log(XLL_ERROR, L"IOCTL_XENIFACE_GNTTAB_GRANT_PAGES not pending");
+        Status = ERROR_UNIDENTIFIED_ERROR;
+        goto fail;
+    }
+
+    Request->Address = Out->Address;
+
+    InsertTailList(&Xc->RequestList, &Request->ListEntry);
+    Xc->RequestId++;
+    LeaveCriticalSection(&Xc->RequestListLock);
+
+    *Address = Out->Address;
+    memcpy(References, &Out->References, NumberPages * sizeof(ULONG));
+    Log(XLL_DEBUG, L"Address: %p", *Address);
+    for (ULONG i = 0; i < NumberPages; i++)
+        Log(XLL_DEBUG, L"Grant ref[%lu]: %lu", i, Out->References[i]);
+
+    free(Out);
+    return ERROR_SUCCESS;
+
+fail:
+    LeaveCriticalSection(&Xc->RequestListLock);
+    Log(XLL_ERROR, L"Error: 0x%x", Status);
+    free(Out);
+    free(Request);
+    return Status;
+}
+
+DWORD
+XcGnttabRevokeForeignAccess(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  PVOID Address
+    )
+{
+    XENIFACE_GNTTAB_REVOKE_FOREIGN_ACCESS_IN In;
+    PXENCONTROL_GNTTAB_REQUEST Request;
+    DWORD Returned;
+    BOOL Success;
+    DWORD Status;
+
+    Log(XLL_DEBUG, L"Address: %p", Address);
+
+    Status = ERROR_NOT_FOUND;
+    Request = FindRequest(Xc, Address);
+    if (!Request) {
+        Log(XLL_ERROR, L"Address %p not granted", Address);
+        goto fail;
+    }
+
+    In.RequestId = Request->Id;
+
+    Success = DeviceIoControl(Xc->XenIface,
+                              IOCTL_XENIFACE_GNTTAB_REVOKE_FOREIGN_ACCESS,
+                              &In, sizeof(In),
+                              NULL, 0,
+                              &Returned,
+                              NULL);
+
+    Status = GetLastError();
+    if (!Success) {
+        Log(XLL_ERROR, L"IOCTL_XENIFACE_GNTTAB_UNGRANT_PAGES failed");
+        goto fail;
+    }
+
+    EnterCriticalSection(&Xc->RequestListLock);
+    RemoveEntryList(&Request->ListEntry);
+    LeaveCriticalSection(&Xc->RequestListLock);
+    free(Request);
+
+    return Status;
+
+fail:
+    Log(XLL_ERROR, L"Error: %d 0x%x", Status, Status);
+    return Status;
+}
+
+DWORD
+XcGnttabMapForeignPages(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  USHORT RemoteDomain,
+    IN  ULONG NumberPages,
+    IN  PULONG References,
+    IN  ULONG NotifyOffset,
+    IN  ULONG NotifyPort,
+    IN  XENIFACE_GNTTAB_PAGE_FLAGS Flags,
+    OUT PVOID *Address
+    )
+{
+    XENIFACE_GNTTAB_MAP_FOREIGN_PAGES_IN *In;
+    XENIFACE_GNTTAB_MAP_FOREIGN_PAGES_OUT Out;
+    PXENCONTROL_GNTTAB_REQUEST Request;
+    DWORD Returned, Size;
+    BOOL Success;
+    DWORD Status;
+
+    // lock the whole operation to not generate duplicate IDs
+    EnterCriticalSection(&Xc->RequestListLock);
+
+    Status = ERROR_OUTOFMEMORY;
+    Size = (ULONG)FIELD_OFFSET(XENIFACE_GNTTAB_MAP_FOREIGN_PAGES_IN, 
References[NumberPages]);
+    In = malloc(Size);
+    Request = malloc(sizeof(*Request));
+    if (!In || !Request)
+        goto fail;
+
+    In->RequestId = Xc->RequestId;
+    In->RemoteDomain = RemoteDomain;
+    In->NumberPages = NumberPages;
+    In->NotifyOffset = NotifyOffset;
+    In->NotifyPort = NotifyPort;
+    In->Flags = Flags;
+    memcpy(&In->References, References, NumberPages * sizeof(ULONG));
+
+    ZeroMemory(Request, sizeof(*Request));
+    Request->Id = In->RequestId;
+
+    Log(XLL_DEBUG, L"Id %lu, RemoteDomain: %d, NumberPages: %lu, NotifyOffset: 
0x%x, NotifyPort: %lu, Flags: 0x%x",
+        In->RequestId, RemoteDomain, NumberPages, NotifyOffset, NotifyPort, 
Flags);
+
+    for (ULONG i = 0; i < NumberPages; i++)
+        Log(XLL_DEBUG, L"Grant ref[%lu]: %lu", i, References[i]);
+
+    Success = DeviceIoControl(Xc->XenIface,
+                              IOCTL_XENIFACE_GNTTAB_MAP_FOREIGN_PAGES,
+                              In, Size,
+                              &Out, sizeof(Out),
+                              &Returned,
+                              &Request->Overlapped);
+
+    Status = GetLastError();
+    // this IOCTL is expected to be pending on success
+    if (!Success) {
+        if (Status != ERROR_IO_PENDING) {
+            Log(XLL_ERROR, L"IOCTL_XENIFACE_GNTTAB_MAP_FOREIGN_PAGES failed");
+            goto fail;
+        }
+    } else {
+        Log(XLL_ERROR, L"IOCTL_XENIFACE_GNTTAB_MAP_FOREIGN_PAGES not pending");
+        Status = ERROR_UNIDENTIFIED_ERROR;
+        goto fail;
+    }
+
+    Request->Address = Out.Address;
+    InsertTailList(&Xc->RequestList, &Request->ListEntry);
+    Xc->RequestId++;
+    LeaveCriticalSection(&Xc->RequestListLock);
+
+    *Address = Out.Address;
+
+    Log(XLL_DEBUG, L"Address: %p", *Address);
+
+    free(In);
+    return ERROR_SUCCESS;
+
+fail:
+    LeaveCriticalSection(&Xc->RequestListLock);
+    Log(XLL_ERROR, L"Error: 0x%x", Status);
+    free(In);
+    free(Request);
+    return Status;
+}
+
+DWORD
+XcGnttabUnmapForeignPages(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  PVOID Address
+    )
+{
+    XENIFACE_GNTTAB_UNMAP_FOREIGN_PAGES_IN In;
+    PXENCONTROL_GNTTAB_REQUEST Request;
+    DWORD Returned;
+    BOOL Success;
+    DWORD Status;
+
+    Log(XLL_DEBUG, L"Address: %p", Address);
+
+    Status = ERROR_NOT_FOUND;
+    Request = FindRequest(Xc, Address);
+    if (!Request) {
+        Log(XLL_ERROR, L"Address %p not mapped", Address);
+        goto fail;
+    }
+
+    In.RequestId = Request->Id;
+
+    Success = DeviceIoControl(Xc->XenIface,
+                              IOCTL_XENIFACE_GNTTAB_UNMAP_FOREIGN_PAGES,
+                              &In, sizeof(In),
+                              NULL, 0,
+                              &Returned,
+                              NULL);
+
+    Status = GetLastError();
+    if (!Success) {
+        Log(XLL_ERROR, L"IOCTL_XENIFACE_GNTTAB_UNMAP_FOREIGN_PAGES failed");
+        goto fail;
+    }
+
+    EnterCriticalSection(&Xc->RequestListLock);
+    RemoveEntryList(&Request->ListEntry);
+    LeaveCriticalSection(&Xc->RequestListLock);
+    free(Request);
+
+    return Status;
+
+fail:
+    Log(XLL_ERROR, L"Error: 0x%x", Status);
+    return Status;
+}
+
+DWORD
+XcStoreRead(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  PSTR Path,
+    IN  DWORD cbValue,
+    OUT CHAR *Value
+    )
+{
+    DWORD Returned;
+    BOOL Success;
+
+    Log(XLL_DEBUG, L"Path: '%S'", Path);
+    Success = DeviceIoControl(Xc->XenIface,
+                              IOCTL_XENIFACE_STORE_READ,
+                              Path, (DWORD)strlen(Path) + 1,
+                              Value, cbValue,
+                              &Returned,
+                              NULL);
+
+    if (!Success) {
+        Log(XLL_ERROR, L"IOCTL_XENIFACE_STORE_READ failed");
+        goto fail;
+    }
+
+    Log(XLL_DEBUG, L"Value: '%S'", Value);
+
+    return ERROR_SUCCESS;
+
+fail:
+    Log(XLL_ERROR, L"Error: 0x%x", GetLastError());
+    return GetLastError();
+}
+
+DWORD
+XcStoreWrite(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  PCHAR Path,
+    IN  PCHAR Value
+    )
+{
+    PCHAR Buffer;
+    DWORD cbBuffer;
+    DWORD Returned;
+    BOOL Success;
+
+    cbBuffer = (DWORD)(strlen(Path) + 1 + strlen(Value) + 1 + 1);
+    Buffer = malloc(cbBuffer);
+    if (!Buffer) {
+        SetLastError(ERROR_OUTOFMEMORY);
+        goto fail;
+    }
+
+    ZeroMemory(Buffer, cbBuffer);
+    memcpy(Buffer, Path, strlen(Path));
+    memcpy(Buffer + strlen(Path) + 1, Value, strlen(Value));
+
+    Log(XLL_DEBUG, L"Path: '%S', Value: '%S'", Path, Value);
+    Success = DeviceIoControl(Xc->XenIface,
+                              IOCTL_XENIFACE_STORE_WRITE,
+                              Buffer, cbBuffer,
+                              NULL, 0,
+                              &Returned,
+                              NULL);
+
+    if (!Success) {
+        Log(XLL_ERROR, L"IOCTL_XENIFACE_STORE_WRITE failed");
+        goto fail;
+    }
+
+    free(Buffer);
+    return ERROR_SUCCESS;
+
+fail:
+    Log(XLL_ERROR, L"Error: 0x%x", GetLastError());
+    free(Buffer);
+    return GetLastError();
+}
+
+DWORD
+XcStoreDirectory(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  PCHAR Path,
+    IN  DWORD cbOutput,
+    OUT CHAR *Output
+    )
+{
+    DWORD Returned;
+    BOOL Success;
+
+    Log(XLL_DEBUG, L"Path: '%S'", Path);
+    Success = DeviceIoControl(Xc->XenIface,
+                              IOCTL_XENIFACE_STORE_DIRECTORY,
+                              Path, (DWORD)strlen(Path) + 1,
+                              Output, cbOutput,
+                              &Returned,
+                              NULL);
+
+    if (!Success) {
+        Log(XLL_ERROR, L"IOCTL_XENIFACE_STORE_DIRECTORY failed");
+        goto fail;
+    }
+
+    _LogMultiSz(Xc, __FUNCTION__, XLL_DEBUG, Output);
+
+    return ERROR_SUCCESS;
+
+fail:
+    Log(XLL_ERROR, L"Error: 0x%x", GetLastError());
+    return GetLastError();
+}
+
+DWORD
+XcStoreRemove(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  PCHAR Path
+    )
+{
+    DWORD Returned;
+    BOOL Success;
+
+    Log(XLL_DEBUG, L"Path: '%S'", Path);
+    Success = DeviceIoControl(Xc->XenIface,
+                              IOCTL_XENIFACE_STORE_REMOVE,
+                              Path, (DWORD)strlen(Path) + 1,
+                              NULL, 0,
+                              &Returned,
+                              NULL);
+
+    if (!Success) {
+        Log(XLL_ERROR, L"IOCTL_XENIFACE_STORE_REMOVE failed");
+        goto fail;
+    }
+
+    return ERROR_SUCCESS;
+
+fail:
+    Log(XLL_ERROR, L"Error: 0x%x", GetLastError());
+    return GetLastError();
+}
+
+DWORD
+XcStoreSetPermissions(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  PCHAR Path,
+    IN  ULONG Count,
+    IN  PXENIFACE_STORE_PERMISSION Permissions
+    )
+{
+    DWORD Returned, Size;
+    BOOL Success;
+    XENIFACE_STORE_SET_PERMISSIONS_IN *In = NULL;
+
+    Log(XLL_DEBUG, L"Path: '%S', Count: %lu", Path, Count);
+    for (ULONG i = 0; i < Count; i++)
+        Log(XLL_DEBUG, L"Domain: %d, Mask: 0x%x", Permissions[i].Domain, 
Permissions[i].Mask);
+
+    Size = (ULONG)FIELD_OFFSET(XENIFACE_STORE_SET_PERMISSIONS_IN, 
Permissions[Count]);
+    In = malloc(Size);
+    if (!In) {
+        SetLastError(ERROR_OUTOFMEMORY);
+        goto fail;
+    }
+
+    In->Path = Path;
+    In->PathLength = (DWORD)strlen(In->Path) + 1;
+    In->NumberPermissions = Count;
+    memcpy(&In->Permissions, Permissions, Count * 
sizeof(XENIFACE_STORE_PERMISSION));
+
+    Success = DeviceIoControl(Xc->XenIface,
+                              IOCTL_XENIFACE_STORE_SET_PERMISSIONS,
+                              In, Size,
+                              NULL, 0,
+                              &Returned,
+                              NULL);
+
+    if (!Success) {
+        Log(XLL_ERROR, L"IOCTL_XENIFACE_STORE_SET_PERMISSIONS failed");
+        goto fail;
+    }
+
+    free(In);
+    return ERROR_SUCCESS;
+
+fail:
+    Log(XLL_ERROR, L"Error: 0x%x", GetLastError());
+    free(In);
+    return GetLastError();
+}
+
+DWORD
+XcStoreAddWatch(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  PCHAR Path,
+    IN  HANDLE Event,
+    OUT PVOID *Handle
+    )
+{
+    DWORD Returned;
+    BOOL Success;
+    XENIFACE_STORE_ADD_WATCH_IN In;
+    XENIFACE_STORE_ADD_WATCH_OUT Out;
+
+    Log(XLL_DEBUG, L"Path: '%S', Event: %p", Path, Event);
+
+    In.Path = Path;
+    In.PathLength = (DWORD)strlen(Path) + 1;
+    In.Event = Event;
+    Success = DeviceIoControl(Xc->XenIface,
+                              IOCTL_XENIFACE_STORE_ADD_WATCH,
+                              &In, sizeof(In),
+                              &Out, sizeof(Out),
+                              &Returned,
+                              NULL);
+
+    if (!Success) {
+        Log(XLL_ERROR, L"IOCTL_XENIFACE_STORE_ADD_WATCH failed");
+        goto fail;
+    }
+
+    *Handle = Out.Context;
+
+    Log(XLL_DEBUG, L"Handle: %p", *Handle);
+
+    return ERROR_SUCCESS;
+
+fail:
+    Log(XLL_ERROR, L"Error: 0x%x", GetLastError());
+    return GetLastError();
+}
+
+DWORD
+XcStoreRemoveWatch(
+    IN  PXENCONTROL_CONTEXT Xc,
+    IN  PVOID Handle
+    )
+{
+    DWORD Returned;
+    BOOL Success;
+    XENIFACE_STORE_REMOVE_WATCH_IN In;
+
+    Log(XLL_DEBUG, L"Handle: %p", Handle);
+
+    In.Context = Handle;
+    Success = DeviceIoControl(Xc->XenIface,
+                              IOCTL_XENIFACE_STORE_REMOVE_WATCH,
+                              &In, sizeof(In),
+                              NULL, 0,
+                              &Returned,
+                              NULL);
+
+    if (!Success) {
+        Log(XLL_ERROR, L"IOCTL_XENIFACE_STORE_REMOVE_WATCH failed");
+        goto fail;
+    }
+
+    return ERROR_SUCCESS;
+
+fail:
+    Log(XLL_ERROR, L"Error: 0x%x", GetLastError());
+    return GetLastError();
+}
diff --git a/src/xencontrol/xencontrol.rc b/src/xencontrol/xencontrol.rc
new file mode 100644
index 0000000..6c33e84
--- /dev/null
+++ b/src/xencontrol/xencontrol.rc
@@ -0,0 +1,24 @@
+#include <windows.h>
+#include <ntverp.h>
+
+#undef VER_COMPANYNAME_STR
+#undef VER_PRODUCTNAME_STR
+#undef VER_PRODUCTVERSION
+#undef VER_PRODUCTVERSION_STR
+
+#include <version.h>
+
+#define VER_COMPANYNAME_STR         VENDOR_NAME_STR
+#define VER_LEGALCOPYRIGHT_STR      "Copyright (c) Invisible Things Lab"
+
+#define VER_PRODUCTNAME_STR         "XENIFACE"
+#define VER_PRODUCTVERSION          
MAJOR_VERSION,MINOR_VERSION,MICRO_VERSION,BUILD_NUMBER
+#define VER_PRODUCTVERSION_STR      MAJOR_VERSION_STR "." MINOR_VERSION_STR 
"." MICRO_VERSION_STR "." BUILD_NUMBER_STR
+
+#define VER_INTERNALNAME_STR        "XENCONTROL.DLL"
+#define VER_FILEDESCRIPTION_STR     "Xen interface user library"
+
+#define VER_FILETYPE                VFT_DLL
+#define VER_FILESUBTYPE             0
+
+#include <common.ver>
diff --git a/src/xencontrol/xencontrol_private.h 
b/src/xencontrol/xencontrol_private.h
new file mode 100644
index 0000000..685bcfa
--- /dev/null
+++ b/src/xencontrol/xencontrol_private.h
@@ -0,0 +1,49 @@
+#ifndef _XENCONTROL_PRIVATE_H_
+#define _XENCONTROL_PRIVATE_H_
+
+#include <windows.h>
+#include "xencontrol.h"
+
+#define Log(level, format, ...) \
+        _Log(Xc->Logger, level, Xc->LogLevel, __FUNCTION__, format, 
__VA_ARGS__)
+
+#define InitializeListHead(ListHead) ( \
+    (ListHead)->Flink = (ListHead)->Blink = (ListHead))
+
+#define InsertTailList(ListHead, Entry) { \
+    PLIST_ENTRY _EX_Blink; \
+    PLIST_ENTRY _EX_ListHead; \
+    _EX_ListHead = (ListHead); \
+    _EX_Blink = _EX_ListHead->Blink; \
+    (Entry)->Flink = _EX_ListHead; \
+    (Entry)->Blink = _EX_Blink; \
+    _EX_Blink->Flink = (Entry); \
+    _EX_ListHead->Blink = (Entry); \
+    }
+
+#define RemoveEntryList(Entry) { \
+    PLIST_ENTRY _EX_Blink; \
+    PLIST_ENTRY _EX_Flink; \
+    _EX_Flink = (Entry)->Flink; \
+    _EX_Blink = (Entry)->Blink; \
+    _EX_Blink->Flink = _EX_Flink; \
+    _EX_Flink->Blink = _EX_Blink; \
+    }
+
+typedef struct _XENCONTROL_CONTEXT {
+    HANDLE XenIface;
+    XENCONTROL_LOGGER *Logger;
+    XENCONTROL_LOG_LEVEL LogLevel;
+    ULONG RequestId;
+    LIST_ENTRY RequestList;
+    CRITICAL_SECTION RequestListLock;
+} XENCONTROL_CONTEXT, *PXENCONTROL_CONTEXT;
+
+typedef struct _XENCONTROL_GNTTAB_REQUEST {
+    LIST_ENTRY  ListEntry;
+    OVERLAPPED  Overlapped;
+    ULONG       Id;
+    PVOID       Address;
+} XENCONTROL_GNTTAB_REQUEST, *PXENCONTROL_GNTTAB_REQUEST;
+
+#endif // _XENCONTROL_PRIVATE_H_
diff --git a/vs2015/package/package.vcxproj b/vs2015/package/package.vcxproj
index 0b8c7d0..34b07aa 100644
--- a/vs2015/package/package.vcxproj
+++ b/vs2015/package/package.vcxproj
@@ -42,6 +42,9 @@
     <ProjectReference Include="..\xenagent\xenagent.vcxproj">
       <Project>{2E61D2CC-865E-442C-8C83-B8DAFD7BBD3B}</Project>
     </ProjectReference>
+    <ProjectReference Include="..\xencontrol\xencontrol.vcxproj">
+      <Project>{D386D8E9-D015-4AD2-A5C2-4F845A803FA2}</Project>
+    </ProjectReference>
   </ItemGroup>
   <ItemGroup>
     <FilesToPackage Include="$(DPINST_REDIST)\x86\dpinst.exe" 
Condition="'$(Platform)'=='Win32'" />
diff --git a/vs2015/xencontrol/xencontrol.vcxproj 
b/vs2015/xencontrol/xencontrol.vcxproj
new file mode 100644
index 0000000..d1be4ca
--- /dev/null
+++ b/vs2015/xencontrol/xencontrol.vcxproj
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="14.0" 
xmlns="http://schemas.microsoft.com/developer/msbuild/2003";>
+  <Import Project="..\configs.props" />
+  <PropertyGroup Label="PropertySheets">
+    <CharacterSet>Unicode</CharacterSet>
+    <PlatformToolset>WindowsApplicationForDrivers10.0</PlatformToolset>
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{D386D8E9-D015-4AD2-A5C2-4F845A803FA2}</ProjectGuid>
+  </PropertyGroup>
+  <Import Project="..\targets.props" />
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <PropertyGroup>
+    <IncludePath>$(IncludePath)</IncludePath>
+    <RunCodeAnalysis>true</RunCodeAnalysis>
+    <EnableInf2cat>false</EnableInf2cat>
+  </PropertyGroup>
+  <ItemDefinitionGroup>
+    <ClCompile>
+      
<AdditionalIncludeDirectories>$(SolutionDir)..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      
<PreprocessorDefinitions>WIN32;_WINDOWS;_USRDLL;XENCONTROL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <WarningLevel>EnableAllWarnings</WarningLevel>
+      
<DisableSpecificWarnings>4127;4711;4548;4820;4668;4255;5045;6001;6054;28196;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+      <MultiProcessorCompilation>true</MultiProcessorCompilation>
+      <EnablePREfast>true</EnablePREfast>
+      <ExceptionHandling>false</ExceptionHandling>
+      <TreatWarningAsError>true</TreatWarningAsError>
+      <RuntimeLibrary 
Condition="'$(UseDebugLibraries)'=='true'">MultiThreadedDebug</RuntimeLibrary>
+      <RuntimeLibrary 
Condition="'$(UseDebugLibraries)'=='false'">MultiThreaded</RuntimeLibrary>
+    </ClCompile>
+    <Link>
+      
<AdditionalDependencies>setupapi.lib;ws2_32.lib;shlwapi.lib;wtsapi32.lib;userenv.lib;version.lib;ntdll.lib;kernel32.lib;user32.lib;gdi32.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+    <ResourceCompile>
+      
<AdditionalIncludeDirectories>$(SolutionDir)..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+    </ResourceCompile>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Platform)'=='Win32'">
+    <ClCompile>
+      
<PreprocessorDefinitions>__i386__;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+    </ClCompile>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Platform)'=='x64'">
+    <ClCompile>
+      
<PreprocessorDefinitions>__x86_64__;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+    </ClCompile>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <FilesToPackage Include="$(TargetPath)" />
+    <FilesToPackage Include="$(OutDir)$(TargetName).pdb" />
+    <FilesToPackage Include="$(OutDir)$(TargetName).dll" />
+    <FilesToPackage Include="$(OutDir)$(TargetName).lib" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="..\..\src\xencontrol\xencontrol.c" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\..\include\xencontrol.h" />
+    <ClInclude Include="..\..\src\xencontrol\xencontrol_private.h" />
+  </ItemGroup>
+  <ItemGroup>
+    <ResourceCompile Include="..\..\src\xencontrol\xencontrol.rc" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+</Project>
diff --git a/vs2015/xeniface.sln b/vs2015/xeniface.sln
index 040f071..7f08c1c 100644
--- a/vs2015/xeniface.sln
+++ b/vs2015/xeniface.sln
@@ -15,6 +15,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = 
"package", "package\package.
                {2E61D2CC-865E-442C-8C83-B8DAFD7BBD3B} = 
{2E61D2CC-865E-442C-8C83-B8DAFD7BBD3B}
        EndProjectSection
 EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xencontrol", 
"xencontrol\xencontrol.vcxproj", "{D386D8E9-D015-4AD2-A5C2-4F845A803FA2}"
+EndProject
 Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
                Windows 8 Debug|Win32 = Windows 8 Debug|Win32
@@ -123,6 +125,30 @@ Global
                {9B071A35-897C-477A-AEB7-95F77618A21D}.Windows 10 
Release|x64.ActiveCfg = Windows 10 Release|x64
                {9B071A35-897C-477A-AEB7-95F77618A21D}.Windows 10 
Release|x64.Build.0 = Windows 10 Release|x64
                {9B071A35-897C-477A-AEB7-95F77618A21D}.Windows 10 
Release|x64.Deploy.0 = Windows 10 Release|x64
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 8 
Debug|Win32.ActiveCfg = Windows 8 Debug|Win32
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 8 
Debug|Win32.Build.0 = Windows 8 Debug|Win32
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 8 
Debug|Win32.Deploy.0 = Windows 8 Debug|Win32
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 8 
Debug|x64.ActiveCfg = Windows 8 Debug|x64
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 8 
Debug|x64.Build.0 = Windows 8 Debug|x64
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 8 
Debug|x64.Deploy.0 = Windows 8 Debug|x64
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 8 
Release|Win32.ActiveCfg = Windows 8 Release|Win32
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 8 
Release|Win32.Build.0 = Windows 8 Release|Win32
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 8 
Release|Win32.Deploy.0 = Windows 8 Release|Win32
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 8 
Release|x64.ActiveCfg = Windows 8 Release|x64
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 8 
Release|x64.Build.0 = Windows 8 Release|x64
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 8 
Release|x64.Deploy.0 = Windows 8 Release|x64
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 10 
Debug|Win32.ActiveCfg = Windows 10 Debug|Win32
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 10 
Debug|Win32.Build.0 = Windows 10 Debug|Win32
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 10 
Debug|Win32.Deploy.0 = Windows 10 Debug|Win32
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 10 
Debug|x64.ActiveCfg = Windows 10 Debug|x64
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 10 
Debug|x64.Build.0 = Windows 10 Debug|x64
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 10 
Debug|x64.Deploy.0 = Windows 10 Debug|x64
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 10 
Release|Win32.ActiveCfg = Windows 10 Release|Win32
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 10 
Release|Win32.Build.0 = Windows 10 Release|Win32
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 10 
Release|Win32.Deploy.0 = Windows 10 Release|Win32
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 10 
Release|x64.ActiveCfg = Windows 10 Release|x64
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 10 
Release|x64.Build.0 = Windows 10 Release|x64
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 10 
Release|x64.Deploy.0 = Windows 10 Release|x64
        EndGlobalSection
        GlobalSection(SolutionProperties) = preSolution
                HideSolutionNode = FALSE
diff --git a/vs2017/package/package.vcxproj b/vs2017/package/package.vcxproj
index 764511b..f9fd507 100644
--- a/vs2017/package/package.vcxproj
+++ b/vs2017/package/package.vcxproj
@@ -42,6 +42,9 @@
     <ProjectReference Include="..\xenagent\xenagent.vcxproj">
       <Project>{2E61D2CC-865E-442C-8C83-B8DAFD7BBD3B}</Project>
     </ProjectReference>
+    <ProjectReference Include="..\xencontrol\xencontrol.vcxproj">
+      <Project>{D386D8E9-D015-4AD2-A5C2-4F845A803FA2}</Project>
+    </ProjectReference>
   </ItemGroup>
   <ItemGroup>
     <FilesToPackage Include="$(DPINST_REDIST)\x86\dpinst.exe" 
Condition="'$(Platform)'=='Win32'" />
diff --git a/vs2017/xencontrol/xencontrol.vcxproj 
b/vs2017/xencontrol/xencontrol.vcxproj
new file mode 100644
index 0000000..18f471c
--- /dev/null
+++ b/vs2017/xencontrol/xencontrol.vcxproj
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" 
xmlns="http://schemas.microsoft.com/developer/msbuild/2003";>
+  <Import Project="..\configs.props" />
+  <PropertyGroup Label="PropertySheets">
+    <CharacterSet>Unicode</CharacterSet>
+    <PlatformToolset>WindowsApplicationForDrivers10.0</PlatformToolset>
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{D386D8E9-D015-4AD2-A5C2-4F845A803FA2}</ProjectGuid>
+  </PropertyGroup>
+  <Import Project="..\targets.props" />
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <PropertyGroup>
+    <IncludePath>$(IncludePath)</IncludePath>
+    <RunCodeAnalysis>true</RunCodeAnalysis>
+    <EnableInf2cat>false</EnableInf2cat>
+  </PropertyGroup>
+  <ItemDefinitionGroup>
+    <ClCompile>
+      
<AdditionalIncludeDirectories>$(SolutionDir)..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      
<PreprocessorDefinitions>WIN32;_WINDOWS;_USRDLL;XENCONTROL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <WarningLevel>EnableAllWarnings</WarningLevel>
+      
<DisableSpecificWarnings>4127;4711;4548;4820;4668;4255;5045;6001;6054;28196;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+      <MultiProcessorCompilation>true</MultiProcessorCompilation>
+      <EnablePREfast>true</EnablePREfast>
+      <ExceptionHandling>false</ExceptionHandling>
+      <TreatWarningAsError>true</TreatWarningAsError>
+      <RuntimeLibrary 
Condition="'$(UseDebugLibraries)'=='true'">MultiThreadedDebug</RuntimeLibrary>
+      <RuntimeLibrary 
Condition="'$(UseDebugLibraries)'=='false'">MultiThreaded</RuntimeLibrary>
+    </ClCompile>
+    <Link>
+      
<AdditionalDependencies>setupapi.lib;ws2_32.lib;shlwapi.lib;wtsapi32.lib;userenv.lib;version.lib;ntdll.lib;kernel32.lib;user32.lib;gdi32.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+    <ResourceCompile>
+      
<AdditionalIncludeDirectories>$(SolutionDir)..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+    </ResourceCompile>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Platform)'=='Win32'">
+    <ClCompile>
+      
<PreprocessorDefinitions>__i386__;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+    </ClCompile>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Platform)'=='x64'">
+    <ClCompile>
+      
<PreprocessorDefinitions>__x86_64__;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+    </ClCompile>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <FilesToPackage Include="$(TargetPath)" />
+    <FilesToPackage Include="$(OutDir)$(TargetName).pdb" />
+    <FilesToPackage Include="$(OutDir)$(TargetName).dll" />
+    <FilesToPackage Include="$(OutDir)$(TargetName).lib" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="..\..\src\xencontrol\xencontrol.c" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\..\include\xencontrol.h" />
+    <ClInclude Include="..\..\src\xencontrol\xencontrol_private.h" />
+  </ItemGroup>
+  <ItemGroup>
+    <ResourceCompile Include="..\..\src\xencontrol\xencontrol.rc" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+</Project>
diff --git a/vs2017/xeniface.sln b/vs2017/xeniface.sln
index 040f071..1eb8156 100644
--- a/vs2017/xeniface.sln
+++ b/vs2017/xeniface.sln
@@ -8,11 +8,14 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = 
"xeniface", "xeniface\xenifa
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xenagent", 
"xenagent\xenagent.vcxproj", "{2E61D2CC-865E-442C-8C83-B8DAFD7BBD3B}"
 EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xencontrol", 
"xencontrol\xencontrol.vcxproj", "{D386D8E9-D015-4AD2-A5C2-4F845A803FA2}"
+EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "package", 
"package\package.vcxproj", "{9B071A35-897C-477A-AEB7-95F77618A21D}"
        ProjectSection(ProjectDependencies) = postProject
                {22166290-65D8-49D2-BB88-33201797C7D8} = 
{22166290-65D8-49D2-BB88-33201797C7D8}
                {85C731AD-2EA2-4049-A542-D2D38EDE938C} = 
{85C731AD-2EA2-4049-A542-D2D38EDE938C}
                {2E61D2CC-865E-442C-8C83-B8DAFD7BBD3B} = 
{2E61D2CC-865E-442C-8C83-B8DAFD7BBD3B}
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2} = 
{D386D8E9-D015-4AD2-A5C2-4F845A803FA2}
        EndProjectSection
 EndProject
 Global
@@ -123,6 +126,30 @@ Global
                {9B071A35-897C-477A-AEB7-95F77618A21D}.Windows 10 
Release|x64.ActiveCfg = Windows 10 Release|x64
                {9B071A35-897C-477A-AEB7-95F77618A21D}.Windows 10 
Release|x64.Build.0 = Windows 10 Release|x64
                {9B071A35-897C-477A-AEB7-95F77618A21D}.Windows 10 
Release|x64.Deploy.0 = Windows 10 Release|x64
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 8 
Debug|Win32.ActiveCfg = Windows 8 Debug|Win32
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 8 
Debug|Win32.Build.0 = Windows 8 Debug|Win32
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 8 
Debug|Win32.Deploy.0 = Windows 8 Debug|Win32
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 8 
Debug|x64.ActiveCfg = Windows 8 Debug|x64
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 8 
Debug|x64.Build.0 = Windows 8 Debug|x64
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 8 
Debug|x64.Deploy.0 = Windows 8 Debug|x64
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 8 
Release|Win32.ActiveCfg = Windows 8 Release|Win32
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 8 
Release|Win32.Build.0 = Windows 8 Release|Win32
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 8 
Release|Win32.Deploy.0 = Windows 8 Release|Win32
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 8 
Release|x64.ActiveCfg = Windows 8 Release|x64
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 8 
Release|x64.Build.0 = Windows 8 Release|x64
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 8 
Release|x64.Deploy.0 = Windows 8 Release|x64
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 10 
Debug|Win32.ActiveCfg = Windows 10 Debug|Win32
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 10 
Debug|Win32.Build.0 = Windows 10 Debug|Win32
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 10 
Debug|Win32.Deploy.0 = Windows 10 Debug|Win32
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 10 
Debug|x64.ActiveCfg = Windows 10 Debug|x64
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 10 
Debug|x64.Build.0 = Windows 10 Debug|x64
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 10 
Debug|x64.Deploy.0 = Windows 10 Debug|x64
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 10 
Release|Win32.ActiveCfg = Windows 10 Release|Win32
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 10 
Release|Win32.Build.0 = Windows 10 Release|Win32
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 10 
Release|Win32.Deploy.0 = Windows 10 Release|Win32
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 10 
Release|x64.ActiveCfg = Windows 10 Release|x64
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 10 
Release|x64.Build.0 = Windows 10 Release|x64
+               {D386D8E9-D015-4AD2-A5C2-4F845A803FA2}.Windows 10 
Release|x64.Deploy.0 = Windows 10 Release|x64
        EndGlobalSection
        GlobalSection(SolutionProperties) = preSolution
                HideSolutionNode = FALSE
-- 
2.13.6


_______________________________________________
win-pv-devel mailing list
win-pv-devel@xxxxxxxxxxxxxxxxxxxx
https://lists.xenproject.org/mailman/listinfo/win-pv-devel

 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.