libvirt/src/vbox/vbox_MSCOMGlue.c
Daniel P. Berrangé fc920f704c src: convert all code to use virsocket.h
There are a large number of different header files that
are related to the sockets APIs. The virsocket.h header
includes all of the relevant headers for Windows and UNIX
in one convenient place. If virsocketaddr.h is already
included, then there's no need for virsocket.h

Reviewed-by: Pavel Hrdina <phrdina@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
2020-01-29 14:51:40 +00:00

780 lines
20 KiB
C

/*
* vbox_MSCOMGlue.c: glue to the MSCOM based VirtualBox API
*
* Copyright (C) 2013 Red Hat, Inc.
* Copyright (C) 2010-2011 Matthias Bolte <matthias.bolte@googlemail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*
*/
#include <config.h>
#define nsCID CLSID
#include "internal.h"
#include "viralloc.h"
#include "virlog.h"
#include "virerror.h"
#include "virstring.h"
#include "virutil.h"
#include "virsocket.h"
#include "vbox_MSCOMGlue.h"
#define VIR_FROM_THIS VIR_FROM_VBOX
VIR_LOG_INIT("vbox.vbox_MSCOMGlue");
#define VBOX_REGKEY_ORACLE "Software\\Oracle\\VirtualBox"
#define VBOX_REGKEY_SUN "Software\\Sun\\xVM VirtualBox"
#define IVIRTUALBOX_IID_STR_v2_2 "779264f4-65ed-48ed-be39-518ca549e296"
#define ISESSION_IID_STR_v2_2 "12F4DCDB-12B2-4ec1-B7CD-DDD9F6C5BF4D"
typedef struct _VBOXXPCOMC_v1 VBOXXPCOMC_v1;
typedef struct _VBOXXPCOMC_v2 VBOXXPCOMC_v2;
struct _VBOXXPCOMC_v1 {
unsigned cb;
unsigned uVersion;
unsigned int (*pfnGetVersion)(void);
void (*pfnComInitialize)(IVirtualBox **virtualBox, ISession **session);
void (*pfnComUninitialize)(void);
void (*pfnComUnallocMem)(void *pv);
void (*pfnUtf16Free)(PRUnichar *pwszString);
void (*pfnUtf8Free)(char *pszString);
int (*pfnUtf16ToUtf8)(const PRUnichar *pwszString, char **ppszString);
int (*pfnUtf8ToUtf16)(const char *pszString, PRUnichar **ppwszString);
unsigned uEndVersion;
};
struct _VBOXXPCOMC_v2 {
unsigned cb;
unsigned uVersion;
unsigned int (*pfnGetVersion)(void);
void (*pfnComInitialize)(const char *pszVirtualBoxIID,
IVirtualBox **ppVirtualBox,
const char *pszSessionIID,
ISession **ppSession);
void (*pfnComUninitialize)(void);
void (*pfnComUnallocMem)(void *pv);
void (*pfnUtf16Free)(PRUnichar *pwszString);
void (*pfnUtf8Free)(char *pszString);
int (*pfnUtf16ToUtf8)(const PRUnichar *pwszString, char **ppszString);
int (*pfnUtf8ToUtf16)(const char *pszString, PRUnichar **ppwszString);
void (*pfnGetEventQueue)(nsIEventQueue **eventQueue);
unsigned uEndVersion;
};
PFNVBOXGETXPCOMCFUNCTIONS g_pfnGetFunctions = NULL;
static unsigned long vboxVersion;
static IVirtualBox *vboxVirtualBox;
static ISession *vboxSession;
/*
* nsISupports dummy implementation
*/
static nsresult __stdcall
vboxSupports_QueryInterface(nsISupports *pThis G_GNUC_UNUSED,
const nsID *iid G_GNUC_UNUSED,
void **resultp G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
static nsresult __stdcall
vboxSupports_AddRef(nsISupports *pThis G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
static nsresult __stdcall
vboxSupports_Release(nsISupports *pThis G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
static nsresult __stdcall
vboxSupports_GetTypeInfoCount(nsISupports *pThis G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
static nsresult __stdcall
vboxSupports_GetTypeInfo(nsISupports *pThis G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
static nsresult __stdcall
vboxSupports_GetIDsOfNames(nsISupports *pThis G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
static nsresult __stdcall
vboxSupports_Invoke(nsISupports *pThis G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
/*
* nsIEventTarget dummy implementation
*/
static nsresult __stdcall
vboxEventTarget_PostEvent(nsIEventTarget *pThis G_GNUC_UNUSED,
PLEvent *aEvent G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
static nsresult __stdcall
vboxEventTarget_IsOnCurrentThread(nsIEventTarget *pThis G_GNUC_UNUSED,
PRBool *_retval G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
/*
* nsIEventQueue dummy implementation
*/
static nsresult __stdcall
vboxEventQueue_InitEvent(nsIEventQueue *pThis G_GNUC_UNUSED,
PLEvent *aEvent G_GNUC_UNUSED,
void *owner G_GNUC_UNUSED,
PLHandleEventProc handler G_GNUC_UNUSED,
PLDestroyEventProc destructor G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
static nsresult __stdcall
vboxEventQueue_PostSynchronousEvent(nsIEventQueue *pThis G_GNUC_UNUSED,
PLEvent *aEvent G_GNUC_UNUSED,
void **aResult G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
static nsresult __stdcall
vboxEventQueue_PendingEvents(nsIEventQueue *pThis G_GNUC_UNUSED,
PRBool *_retval G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
static nsresult __stdcall
vboxEventQueue_ProcessPendingEvents(nsIEventQueue *pThis G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
static nsresult __stdcall
vboxEventQueue_EventLoop(nsIEventQueue *pThis G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
static nsresult __stdcall
vboxEventQueue_EventAvailable(nsIEventQueue *pThis G_GNUC_UNUSED,
PRBool *aResult G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
static nsresult __stdcall
vboxEventQueue_GetEvent(nsIEventQueue *pThis G_GNUC_UNUSED,
PLEvent **_retval G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
static nsresult __stdcall
vboxEventQueue_HandleEvent(nsIEventQueue *pThis G_GNUC_UNUSED,
PLEvent *aEvent G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
static nsresult __stdcall
vboxEventQueue_WaitForEvent(nsIEventQueue *pThis G_GNUC_UNUSED,
PLEvent **_retval G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
static PRInt32 __stdcall
vboxEventQueue_GetEventQueueSelectFD(nsIEventQueue *pThis G_GNUC_UNUSED)
{
return -1;
}
static nsresult __stdcall
vboxEventQueue_Init(nsIEventQueue *pThis G_GNUC_UNUSED,
PRBool aNative G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
static nsresult __stdcall
vboxEventQueue_InitFromPRThread(nsIEventQueue *pThis G_GNUC_UNUSED,
PRThread *thread G_GNUC_UNUSED,
PRBool aNative G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
static nsresult __stdcall
vboxEventQueue_InitFromPLQueue(nsIEventQueue *pThis G_GNUC_UNUSED,
PLEventQueue *aQueue G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
static nsresult __stdcall
vboxEventQueue_EnterMonitor(nsIEventQueue *pThis G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
static nsresult __stdcall
vboxEventQueue_ExitMonitor(nsIEventQueue *pThis G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
static nsresult __stdcall
vboxEventQueue_RevokeEvents(nsIEventQueue *pThis G_GNUC_UNUSED,
void *owner G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
static nsresult __stdcall
vboxEventQueue_GetPLEventQueue(nsIEventQueue *pThis G_GNUC_UNUSED,
PLEventQueue **_retval G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
static nsresult __stdcall
vboxEventQueue_IsQueueNative(nsIEventQueue *pThis G_GNUC_UNUSED,
PRBool *_retval G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
static nsresult __stdcall
vboxEventQueue_StopAcceptingEvents(nsIEventQueue *pThis G_GNUC_UNUSED)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
static struct nsIEventQueue_vtbl vboxEventQueueVtbl = {
{
{
vboxSupports_QueryInterface,
vboxSupports_AddRef,
vboxSupports_Release,
vboxSupports_GetTypeInfoCount,
vboxSupports_GetTypeInfo,
vboxSupports_GetIDsOfNames,
vboxSupports_Invoke
},
vboxEventTarget_PostEvent,
vboxEventTarget_IsOnCurrentThread
},
vboxEventQueue_InitEvent,
vboxEventQueue_PostSynchronousEvent,
vboxEventQueue_PendingEvents,
vboxEventQueue_ProcessPendingEvents,
vboxEventQueue_EventLoop,
vboxEventQueue_EventAvailable,
vboxEventQueue_GetEvent,
vboxEventQueue_HandleEvent,
vboxEventQueue_WaitForEvent,
vboxEventQueue_GetEventQueueSelectFD,
vboxEventQueue_Init,
vboxEventQueue_InitFromPRThread,
vboxEventQueue_InitFromPLQueue,
vboxEventQueue_EnterMonitor,
vboxEventQueue_ExitMonitor,
vboxEventQueue_RevokeEvents,
vboxEventQueue_GetPLEventQueue,
vboxEventQueue_IsQueueNative,
vboxEventQueue_StopAcceptingEvents,
};
static nsIEventQueue vboxEventQueue = {
&vboxEventQueueVtbl
};
static char *
vboxLookupRegistryValue(HKEY key, const char *keyName, const char *valueName)
{
LONG status;
DWORD type;
DWORD length;
char *value = NULL;
status = RegQueryValueEx(key, valueName, NULL, &type, NULL, &length);
if (status != ERROR_SUCCESS) {
VIR_ERROR(_("Could not query registry value '%s\\%s'"),
keyName, valueName);
return NULL;
}
if (type != REG_SZ) {
VIR_ERROR(_("Registry value '%s\\%s' has unexpected type"),
keyName, valueName);
return NULL;
}
if (length < 2) {
VIR_ERROR(_("Registry value '%s\\%s' is too short"),
keyName, valueName);
return NULL;
}
/* +1 for the null-terminator if it's missing */
if (VIR_ALLOC_N(value, length + 1) < 0)
return NULL;
status = RegQueryValueEx(key, valueName, NULL, NULL, (LPBYTE)value, &length);
if (status != ERROR_SUCCESS) {
VIR_FREE(value);
VIR_ERROR(_("Could not query registry value '%s\\%s'"),
keyName, valueName);
return NULL;
}
if (value[length - 1] != '\0')
value[length] = '\0';
return value;
}
static int
vboxLookupVersionInRegistry(void)
{
int result = -1;
const char *keyName = VBOX_REGKEY_ORACLE;
LONG status;
HKEY key;
char *value = NULL;
status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, KEY_READ, &key);
if (status != ERROR_SUCCESS) {
keyName = VBOX_REGKEY_SUN;
status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, KEY_READ, &key);
if (status != ERROR_SUCCESS) {
/* Both keys aren't there, or we cannot open them. In general this
* indicates that VirtualBox is not installed, so we just silently
* fail here making vboxRegister() register the dummy driver. */
return -1;
}
}
/* The registry key layout changed around version 4.0.8. Before the version
* number was in the Version key, now the Version key can contain %VER% and
* the actual version number is in the VersionExt key then. */
value = vboxLookupRegistryValue(key, keyName, "Version");
if (value == NULL)
goto cleanup;
if (STREQ(value, "%VER%")) {
VIR_FREE(value);
value = vboxLookupRegistryValue(key, keyName, "VersionExt");
if (value == NULL)
goto cleanup;
}
if (virParseVersionString(value, &vboxVersion, false) < 0) {
VIR_ERROR(_("Could not parse version number from '%s'"), value);
goto cleanup;
}
result = 0;
cleanup:
VIR_FREE(value);
RegCloseKey(key);
return result;
}
static unsigned int
vboxGetVersion(void)
{
return vboxVersion;
}
static void
vboxComUnallocMem(void *pv)
{
SysFreeString(pv);
}
static void
vboxUtf16Free(PRUnichar *pwszString)
{
SysFreeString(pwszString);
}
static void
vboxUtf8Free(char *pszString)
{
VIR_FREE(pszString);
}
static int
vboxUtf16ToUtf8(const PRUnichar *pwszString, char **ppszString)
{
int length = WideCharToMultiByte(CP_UTF8, 0, pwszString, -1, NULL, 0,
NULL, NULL);
if (length < 1)
return -1;
if (VIR_ALLOC_N(*ppszString, length) < 0)
return -1;
return WideCharToMultiByte(CP_UTF8, 0, pwszString, -1, *ppszString,
length, NULL, NULL);
}
static int
vboxUtf8ToUtf16(const char *pszString, PRUnichar **ppwszString)
{
int length = MultiByteToWideChar(CP_UTF8, 0, pszString, -1, NULL, 0);
if (length < 1)
return -1;
*ppwszString = SysAllocStringLen(NULL, length);
if (*ppwszString == NULL)
return -1;
return MultiByteToWideChar(CP_UTF8, 0, pszString, -1, *ppwszString, length);
}
static void
vboxGetEventQueue(nsIEventQueue **eventQueue)
{
*eventQueue = &vboxEventQueue;
}
static void
vboxComInitialize_v2(const char *pszVirtualBoxIID, IVirtualBox **ppVirtualBox,
const char *pszSessionIID, ISession **ppSession)
{
int result = -1;
HRESULT hrc;
IID virtualBoxIID;
IID sessionIID;
char *mbsVirtualBoxIID = NULL;
char *mbsSessionIID = NULL;
PRUnichar *wcsVirtualBoxIID = NULL;
PRUnichar *wcsSessionIID = NULL;
*ppVirtualBox = NULL;
*ppSession = NULL;
CoInitialize(NULL);
mbsVirtualBoxIID = g_strdup_printf("{%s}", pszVirtualBoxIID);
mbsSessionIID = g_strdup_printf("{%s}", pszSessionIID)
goto cleanup;
if (vboxUtf8ToUtf16(mbsVirtualBoxIID, &wcsVirtualBoxIID) < 0 ||
vboxUtf8ToUtf16(mbsSessionIID, &wcsSessionIID) < 0) {
goto cleanup;
}
hrc = IIDFromString(wcsVirtualBoxIID, &virtualBoxIID);
if (FAILED(hrc)) {
VIR_ERROR(_("Could not parse IID from '%s', rc = 0x%08x"),
pszVirtualBoxIID, (unsigned int)hrc);
goto cleanup;
}
hrc = IIDFromString(wcsSessionIID, &sessionIID);
if (FAILED(hrc)) {
VIR_ERROR(_("Could not parse IID from '%s', rc = 0x%08x"),
pszVirtualBoxIID, (unsigned int)hrc);
goto cleanup;
}
hrc = CoCreateInstance(&CLSID_VirtualBox, NULL, CLSCTX_LOCAL_SERVER,
&virtualBoxIID, (void**)&vboxVirtualBox);
if (FAILED(hrc)) {
VIR_ERROR(_("Could not create VirtualBox instance, rc = 0x%08x"),
(unsigned int)hrc);
goto cleanup;
}
hrc = CoCreateInstance(&CLSID_Session, NULL, CLSCTX_INPROC_SERVER,
&sessionIID, (void**)&vboxSession);
if (FAILED(hrc)) {
VIR_ERROR(_("Could not create Session instance, rc = 0x%08x"),
(unsigned int)hrc);
goto cleanup;
}
*ppVirtualBox = vboxVirtualBox;
*ppSession = vboxSession;
result = 0;
cleanup:
if (result < 0) {
if (vboxVirtualBox != NULL) {
vboxVirtualBox->vtbl->nsisupports.Release((nsISupports *)vboxVirtualBox);
vboxVirtualBox = NULL;
}
if (vboxSession != NULL) {
vboxSession->vtbl->nsisupports.Release((nsISupports *)vboxSession);
vboxSession = NULL;
}
}
vboxUtf16Free(wcsVirtualBoxIID);
vboxUtf16Free(wcsSessionIID);
}
static void
vboxComInitialize_v1(IVirtualBox **ppVirtualBox, ISession **ppSession)
{
vboxComInitialize_v2(IVIRTUALBOX_IID_STR_v2_2, ppVirtualBox,
ISESSION_IID_STR_v2_2, ppSession);
}
static void
vboxComUninitialize(void)
{
if (vboxVirtualBox != NULL) {
vboxVirtualBox->vtbl->nsisupports.Release((nsISupports *)vboxVirtualBox);
vboxVirtualBox = NULL;
}
if (vboxSession != NULL) {
vboxSession->vtbl->nsisupports.Release((nsISupports *)vboxSession);
vboxSession = NULL;
}
CoUninitialize();
}
static VBOXXPCOMC_v1 vboxXPCOMC_v1 = {
sizeof(VBOXXPCOMC_v1), /* cb */
0x00010000U, /* uVersion */
vboxGetVersion, /* pfnGetVersion */
vboxComInitialize_v1, /* pfnComInitialize */
vboxComUninitialize, /* pfnComUninitialize */
vboxComUnallocMem, /* pfnComUnallocMem */
vboxUtf16Free, /* pfnUtf16Free */
vboxUtf8Free, /* pfnUtf8Free */
vboxUtf16ToUtf8, /* pfnUtf16ToUtf8 */
vboxUtf8ToUtf16, /* pfnUtf8ToUtf16 */
0x00010000U /* uEndVersion */
};
static VBOXXPCOMC_v2 vboxXPCOMC_v2 = {
sizeof(VBOXXPCOMC_v2), /* cb */
0x00020000U, /* uVersion */
vboxGetVersion, /* pfnGetVersion */
vboxComInitialize_v2, /* pfnComInitialize */
vboxComUninitialize, /* pfnComUninitialize */
vboxComUnallocMem, /* pfnComUnallocMem */
vboxUtf16Free, /* pfnUtf16Free */
vboxUtf8Free, /* pfnUtf8Free */
vboxUtf16ToUtf8, /* pfnUtf16ToUtf8 */
vboxUtf8ToUtf16, /* pfnUtf8ToUtf16 */
vboxGetEventQueue, /* pfnGetEventQueue */
0x00020000U /* uEndVersion */
};
static PCVBOXXPCOM
vboxGetFunctions(unsigned int version)
{
if (version == 0x00010000U) {
return (PCVBOXXPCOM)&vboxXPCOMC_v1;
} else if (version == 0x00020000U) {
return (PCVBOXXPCOM)&vboxXPCOMC_v2;
} else {
return NULL;
}
}
int
VBoxCGlueInit(unsigned int *version)
{
if (vboxLookupVersionInRegistry() < 0)
return -1;
*version = vboxGetVersion();
g_pfnGetFunctions = vboxGetFunctions;
return 0;
}
void
VBoxCGlueTerm(void)
{
}
/*
* In MSCOM an array is represented by a SAFEARRAY pointer. To access the items
* in the array the SafeArrayAccessData function is used to lock the array and
* get its contents. When the items aren't needed anymore the
* SafeArrayUnaccessData function is used to unlock the array. The pointer
* retuned by SafeArrayAccessData function becomes invalid. Finally the
* SafeArrayDestroy function is called to destroy the array, it also releases
* or frees all items in the array according to their type.
*/
typedef HRESULT __stdcall (*SafeArrayGetter)(void *self, SAFEARRAY **array);
typedef HRESULT __stdcall (*SafeArrayGetterWithPtrArg)(void *self, void *arg, SAFEARRAY **array);
typedef HRESULT __stdcall (*SafeArrayGetterWithUintArg)(void *self, PRUint32 arg, SAFEARRAY **array);
static nsresult
vboxArrayGetHelper(vboxArray *array, HRESULT hrc, SAFEARRAY *safeArray)
{
void **items = NULL;
array->items = NULL;
array->count = 0;
array->handle = NULL;
if (FAILED(hrc))
return hrc;
hrc = SafeArrayAccessData(safeArray, (void **)&items);
if (FAILED(hrc)) {
SafeArrayDestroy(safeArray);
return hrc;
}
array->items = items;
array->count = safeArray->rgsabound[0].cElements;
array->handle = safeArray;
return hrc;
}
/*
* Call the getter with self as first argument and fill the array with the
* returned items.
*/
nsresult
vboxArrayGet(vboxArray *array, void *self, void *getter)
{
HRESULT hrc;
SAFEARRAY *safeArray = NULL;
hrc = ((SafeArrayGetter)getter)(self, &safeArray);
return vboxArrayGetHelper(array, hrc, safeArray);
}
/*
* Call the getter with self as first argument and arg as second argument
* and fill the array with the returned items.
*/
nsresult
vboxArrayGetWithPtrArg(vboxArray *array, void *self, void *getter, void *arg)
{
HRESULT hrc;
SAFEARRAY *safeArray = NULL;
hrc = ((SafeArrayGetterWithPtrArg)getter)(self, arg, &safeArray);
return vboxArrayGetHelper(array, hrc, safeArray);
}
/*
* Call the getter with self as first argument and arg as second argument
* and fill the array with the returned items.
*/
nsresult
vboxArrayGetWithUintArg(vboxArray *array, void *self, void *getter, PRUint32 arg)
{
HRESULT hrc;
SAFEARRAY *safeArray = NULL;
hrc = ((SafeArrayGetterWithUintArg)getter)(self, arg, &safeArray);
return vboxArrayGetHelper(array, hrc, safeArray);
}
/*
* Release all items in the array and reset it.
*
* SafeArrayDestroy is aware of the item's type and calls release or free
* for each item according to its type. Therefore, vboxArrayUnalloc and
* vboxArrayRelease are the same for MSCOM.
*/
void
vboxArrayRelease(vboxArray *array)
{
if (array->handle == NULL)
return;
SafeArrayUnaccessData(array->handle);
SafeArrayDestroy(array->handle);
array->items = NULL;
array->count = 0;
array->handle = NULL;
}