mirror of
https://github.com/reactos/reactos.git
synced 2025-04-25 08:00:24 +00:00
Implement the first level of service dependants
svn path=/trunk/; revision=44679
This commit is contained in:
parent
92e5eddefb
commit
710b5b318b
11 changed files with 538 additions and 454 deletions
|
@ -9,158 +9,13 @@
|
|||
|
||||
#include "precomp.h"
|
||||
|
||||
|
||||
/*
|
||||
* Services which depend on the given service.
|
||||
* The return components depend on this service
|
||||
*/
|
||||
LPTSTR
|
||||
GetDependentServices(SC_HANDLE hService)
|
||||
{
|
||||
LPQUERY_SERVICE_CONFIG lpServiceConfig;
|
||||
LPTSTR lpStr = NULL;
|
||||
DWORD bytesNeeded;
|
||||
DWORD bytes;
|
||||
|
||||
if (!QueryServiceConfig(hService,
|
||||
NULL,
|
||||
0,
|
||||
&bytesNeeded) &&
|
||||
GetLastError() == ERROR_INSUFFICIENT_BUFFER)
|
||||
{
|
||||
lpServiceConfig = HeapAlloc(ProcessHeap,
|
||||
0,
|
||||
bytesNeeded);
|
||||
if (lpServiceConfig)
|
||||
{
|
||||
if (QueryServiceConfig(hService,
|
||||
lpServiceConfig,
|
||||
bytesNeeded,
|
||||
&bytesNeeded))
|
||||
{
|
||||
if (lpServiceConfig)
|
||||
{
|
||||
lpStr = lpServiceConfig->lpDependencies;
|
||||
bytes = 0;
|
||||
|
||||
while (TRUE)
|
||||
{
|
||||
bytes++;
|
||||
|
||||
if (!*lpStr && !*(lpStr + 1))
|
||||
{
|
||||
bytes++;
|
||||
break;
|
||||
}
|
||||
|
||||
lpStr++;
|
||||
}
|
||||
|
||||
bytes *= sizeof(TCHAR);
|
||||
lpStr = HeapAlloc(ProcessHeap,
|
||||
0,
|
||||
bytes);
|
||||
if (lpStr)
|
||||
{
|
||||
CopyMemory(lpStr,
|
||||
lpServiceConfig->lpDependencies,
|
||||
bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lpStr;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Services which the given service depends on (1st treeview)
|
||||
* The service depends on the return components
|
||||
*/
|
||||
LPENUM_SERVICE_STATUS
|
||||
GetServiceDependents(SC_HANDLE hService,
|
||||
LPDWORD lpdwCount)
|
||||
{
|
||||
LPENUM_SERVICE_STATUS lpDependencies;
|
||||
DWORD dwBytesNeeded;
|
||||
DWORD dwCount;
|
||||
|
||||
if (EnumDependentServices(hService,
|
||||
SERVICE_STATE_ALL,
|
||||
NULL,
|
||||
0,
|
||||
&dwBytesNeeded,
|
||||
&dwCount))
|
||||
{
|
||||
/* There are no dependent services */
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GetLastError() != ERROR_MORE_DATA)
|
||||
return NULL; /* Unexpected error */
|
||||
|
||||
lpDependencies = (LPENUM_SERVICE_STATUS)HeapAlloc(GetProcessHeap(),
|
||||
0,
|
||||
dwBytesNeeded);
|
||||
if (lpDependencies)
|
||||
{
|
||||
if (EnumDependentServices(hService,
|
||||
SERVICE_STATE_ALL,
|
||||
lpDependencies,
|
||||
dwBytesNeeded,
|
||||
&dwBytesNeeded,
|
||||
&dwCount))
|
||||
{
|
||||
*lpdwCount = dwCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
HeapFree(ProcessHeap,
|
||||
0,
|
||||
lpDependencies);
|
||||
|
||||
lpDependencies = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lpDependencies;
|
||||
|
||||
}
|
||||
|
||||
|
||||
BOOL
|
||||
HasDependentServices(SC_HANDLE hService)
|
||||
{
|
||||
DWORD dwBytesNeeded, dwCount;
|
||||
BOOL bRet = FALSE;
|
||||
|
||||
if (hService)
|
||||
{
|
||||
if (!EnumDependentServices(hService,
|
||||
SERVICE_STATE_ALL,
|
||||
NULL,
|
||||
0,
|
||||
&dwBytesNeeded,
|
||||
&dwCount))
|
||||
{
|
||||
if (GetLastError() == ERROR_MORE_DATA)
|
||||
bRet = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return bRet;
|
||||
}
|
||||
|
||||
//FIXME: reimplement this
|
||||
static BOOL
|
||||
DoInitDependsDialog(PSTOP_INFO pStopInfo,
|
||||
HWND hDlg)
|
||||
{
|
||||
LPENUM_SERVICE_STATUS lpDependencies;
|
||||
DWORD dwCount;
|
||||
//LPENUM_SERVICE_STATUS lpDependencies;
|
||||
//DWORD dwCount;
|
||||
LPTSTR lpPartialStr, lpStr;
|
||||
DWORD fullLen;
|
||||
HICON hIcon = NULL;
|
||||
|
@ -219,8 +74,8 @@ DoInitDependsDialog(PSTOP_INFO pStopInfo,
|
|||
lpPartialStr);
|
||||
}
|
||||
|
||||
/* Get the list of dependencies */
|
||||
lpDependencies = GetServiceDependents(pStopInfo->hMainService, &dwCount);
|
||||
/* Get the list of dependencies
|
||||
GetServiceDependents(pStopInfo->hMainService, &dwCount);
|
||||
if (lpDependencies)
|
||||
{
|
||||
LPENUM_SERVICE_STATUS lpEnumServiceStatus;
|
||||
|
@ -230,7 +85,7 @@ DoInitDependsDialog(PSTOP_INFO pStopInfo,
|
|||
{
|
||||
lpEnumServiceStatus = &lpDependencies[i];
|
||||
|
||||
/* Add the service to the listbox */
|
||||
Add the service to the listbox
|
||||
SendDlgItemMessage(hDlg,
|
||||
IDC_STOP_DEPENDS_LB,
|
||||
LB_ADDSTRING,
|
||||
|
@ -241,7 +96,7 @@ DoInitDependsDialog(PSTOP_INFO pStopInfo,
|
|||
HeapFree(ProcessHeap,
|
||||
0,
|
||||
lpDependencies);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
return bRet;
|
||||
|
|
180
reactos/base/applications/mscutils/servman/dependencies_tv1.c
Normal file
180
reactos/base/applications/mscutils/servman/dependencies_tv1.c
Normal file
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* PROJECT: ReactOS Services
|
||||
* LICENSE: GPL - See COPYING in the top level directory
|
||||
* FILE: base/applications/mscutils/servman/tv1_dependencies.c
|
||||
* PURPOSE: Helper functions for service dependents
|
||||
* COPYRIGHT: Copyright 2009 Ged Murphy <gedmurphy@reactos.org>
|
||||
*
|
||||
*/
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
|
||||
LPTSTR
|
||||
TV1_GetDependants(PSERVICEPROPSHEET pDlgInfo,
|
||||
SC_HANDLE hService)
|
||||
{
|
||||
LPQUERY_SERVICE_CONFIG lpServiceConfig;
|
||||
LPTSTR lpStr = NULL;
|
||||
DWORD bytesNeeded;
|
||||
DWORD bytes;
|
||||
|
||||
/* Get the info for this service */
|
||||
if (!QueryServiceConfig(hService,
|
||||
NULL,
|
||||
0,
|
||||
&bytesNeeded) &&
|
||||
GetLastError() == ERROR_INSUFFICIENT_BUFFER)
|
||||
{
|
||||
lpServiceConfig = HeapAlloc(ProcessHeap,
|
||||
0,
|
||||
bytesNeeded);
|
||||
if (lpServiceConfig)
|
||||
{
|
||||
if (QueryServiceConfig(hService,
|
||||
lpServiceConfig,
|
||||
bytesNeeded,
|
||||
&bytesNeeded))
|
||||
{
|
||||
/* Does this service have any dependencies? */
|
||||
if (lpServiceConfig->lpDependencies &&
|
||||
*lpServiceConfig->lpDependencies != '\0')
|
||||
{
|
||||
lpStr = lpServiceConfig->lpDependencies;
|
||||
bytes = 0;
|
||||
|
||||
/* Work out how many bytes we need to hold the list */
|
||||
while (TRUE)
|
||||
{
|
||||
bytes++;
|
||||
|
||||
if (!*lpStr && !*(lpStr + 1))
|
||||
{
|
||||
bytes++;
|
||||
break;
|
||||
}
|
||||
|
||||
lpStr++;
|
||||
}
|
||||
|
||||
/* Allocate and copy the list */
|
||||
bytes *= sizeof(TCHAR);
|
||||
lpStr = HeapAlloc(ProcessHeap,
|
||||
0,
|
||||
bytes);
|
||||
if (lpStr)
|
||||
{
|
||||
CopyMemory(lpStr,
|
||||
lpServiceConfig->lpDependencies,
|
||||
bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HeapFree(GetProcessHeap(),
|
||||
0,
|
||||
lpServiceConfig);
|
||||
}
|
||||
}
|
||||
|
||||
return lpStr;
|
||||
}
|
||||
|
||||
static VOID
|
||||
TV1_AddDependantsToTree(PSERVICEPROPSHEET pDlgInfo,
|
||||
HTREEITEM hParent,
|
||||
SC_HANDLE hService)
|
||||
{
|
||||
LPQUERY_SERVICE_CONFIG lpServiceConfig;
|
||||
LPTSTR lpDependants;
|
||||
LPTSTR lpStr;
|
||||
LPTSTR lpNoDepends;
|
||||
BOOL bHasChildren;
|
||||
|
||||
/* Get a list of service dependents */
|
||||
lpDependants = TV1_GetDependants(pDlgInfo, hService);
|
||||
if (lpDependants)
|
||||
{
|
||||
lpStr = lpDependants;
|
||||
|
||||
/* Make sure this isn't the end of the list */
|
||||
while (*lpStr)
|
||||
{
|
||||
/* Get the info for this service */
|
||||
lpServiceConfig = GetServiceConfig(lpStr);
|
||||
if (lpServiceConfig)
|
||||
{
|
||||
/* Does this item need a +/- box? */
|
||||
bHasChildren = lpServiceConfig->lpDependencies ? TRUE : FALSE;
|
||||
|
||||
/* Add it */
|
||||
AddItemToTreeView(pDlgInfo->hDependsTreeView1,
|
||||
hParent,
|
||||
lpServiceConfig->lpDisplayName,
|
||||
lpStr,
|
||||
lpServiceConfig->dwServiceType,
|
||||
bHasChildren);
|
||||
|
||||
HeapFree(GetProcessHeap(),
|
||||
0,
|
||||
lpServiceConfig);
|
||||
}
|
||||
|
||||
/* Move to the end of the string */
|
||||
while (*lpStr++)
|
||||
;
|
||||
}
|
||||
|
||||
HeapFree(GetProcessHeap(),
|
||||
0,
|
||||
lpDependants);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* If there is no parent, set the tree to 'no dependencies' */
|
||||
if (!hParent)
|
||||
{
|
||||
/* Load the 'No dependencies' string */
|
||||
AllocAndLoadString(&lpNoDepends, hInstance, IDS_NO_DEPENDS);
|
||||
|
||||
AddItemToTreeView(pDlgInfo->hDependsTreeView1,
|
||||
NULL,
|
||||
lpNoDepends,
|
||||
NULL,
|
||||
0,
|
||||
FALSE);
|
||||
|
||||
HeapFree(ProcessHeap,
|
||||
0,
|
||||
lpNoDepends);
|
||||
|
||||
/* Disable the window */
|
||||
EnableWindow(pDlgInfo->hDependsTreeView1, FALSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BOOL
|
||||
TV1_Initialize(PSERVICEPROPSHEET pDlgInfo,
|
||||
SC_HANDLE hService)
|
||||
{
|
||||
BOOL bRet = FALSE;
|
||||
|
||||
/* Accociate the imagelist with TV1 */
|
||||
pDlgInfo->hDependsTreeView1 = GetDlgItem(pDlgInfo->hDependsWnd, IDC_DEPEND_TREE1);
|
||||
if (!pDlgInfo->hDependsTreeView1)
|
||||
{
|
||||
ImageList_Destroy(pDlgInfo->hDependsImageList);
|
||||
pDlgInfo->hDependsImageList = NULL;
|
||||
return FALSE;
|
||||
}
|
||||
(void)TreeView_SetImageList(pDlgInfo->hDependsTreeView1,
|
||||
pDlgInfo->hDependsImageList,
|
||||
TVSIL_NORMAL);
|
||||
|
||||
/* Set the first items in the control */
|
||||
TV1_AddDependantsToTree(pDlgInfo, NULL, hService);
|
||||
|
||||
return bRet;
|
||||
}
|
184
reactos/base/applications/mscutils/servman/dependencies_tv2.c
Normal file
184
reactos/base/applications/mscutils/servman/dependencies_tv2.c
Normal file
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
* PROJECT: ReactOS Services
|
||||
* LICENSE: GPL - See COPYING in the top level directory
|
||||
* FILE: base/applications/mscutils/servman/tv2_dependencies.c
|
||||
* PURPOSE: Helper functions for service dependents
|
||||
* COPYRIGHT: Copyright 2009 Ged Murphy <gedmurphy@reactos.org>
|
||||
*
|
||||
*/
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
|
||||
static BOOL
|
||||
HasDependantServices(LPWSTR lpServiceName)
|
||||
{
|
||||
HANDLE hSCManager;
|
||||
HANDLE hService;
|
||||
DWORD dwBytesNeeded, dwCount;
|
||||
BOOL bRet = FALSE;
|
||||
|
||||
hSCManager = OpenSCManager(NULL,
|
||||
NULL,
|
||||
SC_MANAGER_ALL_ACCESS);
|
||||
if (hSCManager)
|
||||
{
|
||||
hService = OpenService(hSCManager,
|
||||
lpServiceName,
|
||||
SERVICE_QUERY_STATUS | SERVICE_ENUMERATE_DEPENDENTS);
|
||||
if (hService)
|
||||
{
|
||||
/* Does this have any dependencies? */
|
||||
if (!EnumDependentServices(hService,
|
||||
SERVICE_STATE_ALL,
|
||||
NULL,
|
||||
0,
|
||||
&dwBytesNeeded,
|
||||
&dwCount))
|
||||
{
|
||||
if (GetLastError() == ERROR_MORE_DATA)
|
||||
{
|
||||
/* It does, return TRUE */
|
||||
bRet = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CloseServiceHandle(hSCManager);
|
||||
}
|
||||
|
||||
return bRet;
|
||||
}
|
||||
|
||||
|
||||
static LPENUM_SERVICE_STATUS
|
||||
TV2_GetDependants(SC_HANDLE hService,
|
||||
LPDWORD lpdwCount)
|
||||
{
|
||||
LPENUM_SERVICE_STATUS lpDependencies;
|
||||
DWORD dwBytesNeeded;
|
||||
DWORD dwCount;
|
||||
|
||||
/* Does this have any dependencies? */
|
||||
if (EnumDependentServices(hService,
|
||||
SERVICE_STATE_ALL,
|
||||
NULL,
|
||||
0,
|
||||
&dwBytesNeeded,
|
||||
&dwCount))
|
||||
{
|
||||
/* There are no dependent services */
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GetLastError() != ERROR_MORE_DATA)
|
||||
return NULL; /* Unexpected error */
|
||||
|
||||
lpDependencies = (LPENUM_SERVICE_STATUS)HeapAlloc(GetProcessHeap(),
|
||||
0,
|
||||
dwBytesNeeded);
|
||||
if (lpDependencies)
|
||||
{
|
||||
/* Get the list of dependents */
|
||||
if (EnumDependentServices(hService,
|
||||
SERVICE_STATE_ALL,
|
||||
lpDependencies,
|
||||
dwBytesNeeded,
|
||||
&dwBytesNeeded,
|
||||
&dwCount))
|
||||
{
|
||||
/* Set the count */
|
||||
*lpdwCount = dwCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
HeapFree(ProcessHeap,
|
||||
0,
|
||||
lpDependencies);
|
||||
|
||||
lpDependencies = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lpDependencies;
|
||||
}
|
||||
|
||||
|
||||
static VOID
|
||||
TV2_AddDependantsToTree(PSERVICEPROPSHEET pDlgInfo,
|
||||
HTREEITEM hParent,
|
||||
SC_HANDLE hService)
|
||||
{
|
||||
LPENUM_SERVICE_STATUS lpServiceStatus;
|
||||
LPTSTR lpNoDepends;
|
||||
DWORD count, i;
|
||||
BOOL bHasChildren;
|
||||
|
||||
/* Get a list of service dependents */
|
||||
lpServiceStatus = TV2_GetDependants(hService, &count);
|
||||
if (lpServiceStatus)
|
||||
{
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
/* Does this item need a +/- box? */
|
||||
bHasChildren = HasDependantServices(lpServiceStatus[i].lpServiceName);
|
||||
|
||||
/* Add it */
|
||||
AddItemToTreeView(pDlgInfo->hDependsTreeView2,
|
||||
hParent,
|
||||
lpServiceStatus[i].lpDisplayName,
|
||||
lpServiceStatus[i].lpServiceName,
|
||||
lpServiceStatus[i].ServiceStatus.dwServiceType,
|
||||
bHasChildren);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* If there is no parent, set the tree to 'no dependencies' */
|
||||
if (!hParent)
|
||||
{
|
||||
/* Load the 'No dependencies' string */
|
||||
AllocAndLoadString(&lpNoDepends, hInstance, IDS_NO_DEPENDS);
|
||||
|
||||
AddItemToTreeView(pDlgInfo->hDependsTreeView2,
|
||||
NULL,
|
||||
lpNoDepends,
|
||||
NULL,
|
||||
0,
|
||||
FALSE);
|
||||
|
||||
HeapFree(ProcessHeap,
|
||||
0,
|
||||
lpNoDepends);
|
||||
|
||||
/* Disable the window */
|
||||
EnableWindow(pDlgInfo->hDependsTreeView2, FALSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOL
|
||||
TV2_Initialize(PSERVICEPROPSHEET pDlgInfo,
|
||||
SC_HANDLE hService)
|
||||
{
|
||||
BOOL bRet = FALSE;
|
||||
|
||||
/* Accociate the imagelist with TV2 */
|
||||
pDlgInfo->hDependsTreeView2 = GetDlgItem(pDlgInfo->hDependsWnd, IDC_DEPEND_TREE2);
|
||||
if (!pDlgInfo->hDependsTreeView2)
|
||||
{
|
||||
ImageList_Destroy(pDlgInfo->hDependsImageList);
|
||||
pDlgInfo->hDependsImageList = NULL;
|
||||
return FALSE;
|
||||
}
|
||||
(void)TreeView_SetImageList(pDlgInfo->hDependsTreeView2,
|
||||
pDlgInfo->hDependsImageList,
|
||||
TVSIL_NORMAL);
|
||||
|
||||
/* Set the first items in the control */
|
||||
TV2_AddDependantsToTree(pDlgInfo, NULL, hService);
|
||||
|
||||
return bRet;
|
||||
}
|
|
@ -106,8 +106,8 @@ CAPTION "Dependencies"
|
|||
FONT 8, "MS Shell Dlg",0,0
|
||||
STYLE DS_SHELLFONT | WS_BORDER | WS_VISIBLE | WS_DLGFRAME | WS_SYSMENU | WS_THICKFRAME | WS_GROUP | WS_TABSTOP
|
||||
BEGIN
|
||||
CONTROL "", IDC_DEPEND_TREE1, "SysTreeView32" ,0x50010007, 8, 70, 236, 68, 0x00000200
|
||||
CONTROL "", IDC_DEPEND_TREE2, "SysTreeView32", 0x50010007, 8, 151, 234, 67, 0x00000200
|
||||
CONTROL "", IDC_DEPEND_TREE1, "SysTreeView32" , WS_BORDER | WS_CHILDWINDOW | WS_VISIBLE | WS_TABSTOP | TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_DISABLEDRAGDROP, 8, 70, 236, 68
|
||||
CONTROL "", IDC_DEPEND_TREE2, "SysTreeView32", WS_BORDER | WS_CHILDWINDOW | WS_VISIBLE | WS_TABSTOP | TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_DISABLEDRAGDROP, 8, 151, 234, 67
|
||||
LTEXT "Some services depend on other services, system drivers and load order groups. If a system component is stopped or it is not running properly, dependant services can be affected.", IDC_STATIC,8, 7, 238, 26
|
||||
LTEXT "This service depends on the following components", IDC_STATIC, 8, 57, 236, 9
|
||||
LTEXT "", IDC_DEPEND_SERVICE, 8, 38, 236, 13
|
||||
|
|
|
@ -1,20 +1,15 @@
|
|||
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
|
||||
<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type='win32'
|
||||
name='Microsoft.Windows.Common-Controls'
|
||||
version='6.0.0.0'
|
||||
processorArchitecture='X86'
|
||||
publicKeyToken='6595b64144ccf1df'
|
||||
language='*' />
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level='highestAvailable' uiAccess='false' />
|
||||
<requestedExecutionLevel level='asInvoker' uiAccess='false' />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type='win32' name='Microsoft.VC90.CRT' version='9.0.21022.8' processorArchitecture='x86' publicKeyToken='1fc8b3b9a1e18e3b' />
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
</assembly>
|
||||
|
|
|
@ -105,11 +105,6 @@ BOOL RefreshServiceList(PMAIN_WND_INFO Info);
|
|||
BOOL UpdateServiceStatus(ENUM_SERVICE_STATUS_PROCESS* pService);
|
||||
BOOL GetServiceList(PMAIN_WND_INFO Info, DWORD *NumServices);
|
||||
|
||||
/* dependencies */
|
||||
LPENUM_SERVICE_STATUS GetServiceDependents(SC_HANDLE hService, LPDWORD lpdwCount);
|
||||
BOOL HasDependentServices(SC_HANDLE hService);
|
||||
INT_PTR CALLBACK StopDependsDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
|
||||
LPTSTR GetDependentServices(SC_HANDLE hService);
|
||||
|
||||
/* propsheet.c */
|
||||
typedef struct _SERVICEPROPSHEET
|
||||
|
@ -117,8 +112,25 @@ typedef struct _SERVICEPROPSHEET
|
|||
PMAIN_WND_INFO Info;
|
||||
ENUM_SERVICE_STATUS_PROCESS *pService;
|
||||
HIMAGELIST hDependsImageList;
|
||||
HWND hDependsWnd;
|
||||
HWND hDependsTreeView1;
|
||||
HWND hDependsTreeView2;
|
||||
} SERVICEPROPSHEET, *PSERVICEPROPSHEET;
|
||||
|
||||
|
||||
HTREEITEM AddItemToTreeView(HWND hTreeView, HTREEITEM hRoot, LPTSTR lpDisplayName, LPTSTR lpServiceName, ULONG serviceType, BOOL bHasChildren);
|
||||
|
||||
/* dependencies */
|
||||
//LPENUM_SERVICE_STATUS GetServiceDependents(SC_HANDLE hService, LPDWORD lpdwCount);
|
||||
INT_PTR CALLBACK StopDependsDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
|
||||
//LPTSTR GetDependentServices(SC_HANDLE hService);
|
||||
|
||||
/* tv1_dependencies */
|
||||
BOOL TV1_Initialize(PSERVICEPROPSHEET pDlgInfo, SC_HANDLE hService);
|
||||
|
||||
/* tv2_dependencies */
|
||||
BOOL TV2_Initialize(PSERVICEPROPSHEET pDlgInfo, SC_HANDLE hService);
|
||||
|
||||
LONG APIENTRY OpenPropSheet(PMAIN_WND_INFO Info);
|
||||
|
||||
/* propsheet window procs */
|
||||
|
|
|
@ -10,11 +10,13 @@
|
|||
#include "precomp.h"
|
||||
|
||||
|
||||
static HTREEITEM
|
||||
HTREEITEM
|
||||
AddItemToTreeView(HWND hTreeView,
|
||||
HTREEITEM hRoot,
|
||||
LPTSTR lpLabel,
|
||||
ULONG serviceType)
|
||||
LPTSTR lpDisplayName,
|
||||
LPTSTR lpServiceName,
|
||||
ULONG serviceType,
|
||||
BOOL bHasChildren)
|
||||
{
|
||||
TV_ITEM tvi;
|
||||
TV_INSERTSTRUCT tvins;
|
||||
|
@ -22,9 +24,10 @@ AddItemToTreeView(HWND hTreeView,
|
|||
ZeroMemory(&tvi, sizeof(tvi));
|
||||
ZeroMemory(&tvins, sizeof(tvins));
|
||||
|
||||
tvi.mask = TVIF_TEXT | TVIF_SELECTEDIMAGE | TVIF_IMAGE;
|
||||
tvi.pszText = lpLabel;
|
||||
tvi.cchTextMax = lstrlen(lpLabel);
|
||||
tvi.mask = TVIF_TEXT | TVIF_SELECTEDIMAGE | TVIF_IMAGE | TVIF_CHILDREN;
|
||||
tvi.pszText = lpDisplayName;
|
||||
tvi.cchTextMax = _tcslen(lpDisplayName);
|
||||
tvi.cChildren = bHasChildren; //I_CHILDRENCALLBACK;
|
||||
|
||||
if (serviceType == SERVICE_WIN32_OWN_PROCESS ||
|
||||
serviceType == SERVICE_WIN32_SHARE_PROCESS)
|
||||
|
@ -50,221 +53,56 @@ AddItemToTreeView(HWND hTreeView,
|
|||
return TreeView_InsertItem(hTreeView, &tvins);
|
||||
}
|
||||
|
||||
#if 0
|
||||
static VOID
|
||||
AddServiceDependency(PSERVICEPROPSHEET dlgInfo,
|
||||
HWND hTreeView,
|
||||
SC_HANDLE hSCManager,
|
||||
LPTSTR lpServiceName,
|
||||
HTREEITEM hParent,
|
||||
HWND hwndDlg)
|
||||
/*
|
||||
static BOOL
|
||||
TreeView_GetItemText(HWND hTreeView,
|
||||
HTREEITEM hItem,
|
||||
LPTSTR lpBuffer,
|
||||
DWORD cbBuffer)
|
||||
{
|
||||
LPQUERY_SERVICE_CONFIG lpServiceConfig;
|
||||
SC_HANDLE hService;
|
||||
HTREEITEM hChild;
|
||||
LPTSTR lpStr;
|
||||
LPTSTR lpNoDepends;
|
||||
TVITEM tv = {0};
|
||||
|
||||
hService = OpenService(hSCManager,
|
||||
lpServiceName,
|
||||
SERVICE_QUERY_CONFIG);
|
||||
if (hService)
|
||||
{
|
||||
|
||||
lpStr = GetDependentServices(hService);
|
||||
if (lpStr)
|
||||
{
|
||||
while (*lpStr)
|
||||
{
|
||||
hChild = AddItemToTreeView(hTreeView,
|
||||
hParent,
|
||||
lpServiceConfig->lpDisplayName,
|
||||
lpServiceConfig->dwServiceType);
|
||||
|
||||
|
||||
AddServiceDependency(dlgInfo,
|
||||
hTreeView,
|
||||
hSCManager,
|
||||
lpStr,
|
||||
hChild,
|
||||
hwndDlg);
|
||||
|
||||
while (*lpStr++)
|
||||
;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (TreeView_GetCount(hTreeView) == 0)
|
||||
{
|
||||
if (AllocAndLoadString(&lpNoDepends, hInstance, IDS_NO_DEPENDS))
|
||||
{
|
||||
lpStr = lpNoDepends;
|
||||
}
|
||||
|
||||
AddItemToTreeView(hTreeView,
|
||||
hParent,
|
||||
lpStr,
|
||||
0);
|
||||
|
||||
HeapFree(ProcessHeap,
|
||||
0,
|
||||
lpNoDepends);
|
||||
|
||||
EnableWindow(hTreeView, FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
HeapFree(ProcessHeap,
|
||||
0,
|
||||
lpStr);
|
||||
|
||||
CloseServiceHandle(hService);
|
||||
}
|
||||
tv.mask = TVIF_TEXT | TVIF_HANDLE;
|
||||
tv.hItem = hItem;
|
||||
tv.pszText = lpBuffer;
|
||||
tv.cchTextMax = (int)cbBuffer;
|
||||
|
||||
return TreeView_GetItem(hTreeView, &tv);
|
||||
}
|
||||
#endif
|
||||
*/
|
||||
|
||||
static VOID
|
||||
AddServiceDependent(HWND hTreeView,
|
||||
HTREEITEM hParent,
|
||||
SC_HANDLE hSCManager,
|
||||
LPTSTR lpServiceName,
|
||||
LPTSTR lpDisplayName,
|
||||
DWORD dwServiceType)
|
||||
static BOOL
|
||||
InitDependPage(PSERVICEPROPSHEET pDlgInfo)
|
||||
{
|
||||
LPENUM_SERVICE_STATUS lpServiceStatus;
|
||||
SC_HANDLE hChildService;
|
||||
HTREEITEM hChildNode;
|
||||
DWORD count;
|
||||
INT i;
|
||||
|
||||
|
||||
hChildNode = AddItemToTreeView(hTreeView,
|
||||
hParent,
|
||||
lpDisplayName,
|
||||
dwServiceType);
|
||||
|
||||
hChildService = OpenService(hSCManager,
|
||||
lpServiceName,
|
||||
SERVICE_QUERY_STATUS | SERVICE_ENUMERATE_DEPENDENTS);
|
||||
if (hChildService)
|
||||
{
|
||||
lpServiceStatus = GetServiceDependents(hChildService, &count);
|
||||
if (lpServiceStatus)
|
||||
{
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
AddServiceDependent(hTreeView,
|
||||
hChildNode,
|
||||
hSCManager,
|
||||
lpServiceStatus[i].lpServiceName,
|
||||
lpServiceStatus[i].lpDisplayName,
|
||||
lpServiceStatus[i].ServiceStatus.dwServiceType);
|
||||
}
|
||||
|
||||
HeapFree(ProcessHeap,
|
||||
0,
|
||||
lpServiceStatus);
|
||||
}
|
||||
|
||||
CloseServiceHandle(hChildService);
|
||||
}
|
||||
}
|
||||
|
||||
static VOID
|
||||
SetServiceDependents(HWND hTreeView,
|
||||
SC_HANDLE hSCManager,
|
||||
SC_HANDLE hService)
|
||||
{
|
||||
LPENUM_SERVICE_STATUS lpServiceStatus;
|
||||
LPTSTR lpNoDepends;
|
||||
DWORD count, i;
|
||||
|
||||
lpServiceStatus = GetServiceDependents(hService, &count);
|
||||
if (lpServiceStatus)
|
||||
{
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
AddServiceDependent(hTreeView,
|
||||
NULL,
|
||||
hSCManager,
|
||||
lpServiceStatus[i].lpServiceName,
|
||||
lpServiceStatus[i].lpDisplayName,
|
||||
lpServiceStatus[i].ServiceStatus.dwServiceType);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AllocAndLoadString(&lpNoDepends, hInstance, IDS_NO_DEPENDS);
|
||||
|
||||
AddItemToTreeView(hTreeView,
|
||||
NULL,
|
||||
lpNoDepends,
|
||||
0);
|
||||
|
||||
HeapFree(ProcessHeap,
|
||||
0,
|
||||
lpNoDepends);
|
||||
|
||||
EnableWindow(hTreeView, FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
static VOID
|
||||
SetDependentServices(SC_HANDLE hService)
|
||||
{
|
||||
}
|
||||
|
||||
static VOID
|
||||
InitDependPage(PSERVICEPROPSHEET dlgInfo,
|
||||
HWND hwndDlg)
|
||||
{
|
||||
HWND hTreeView1, hTreeView2;
|
||||
SC_HANDLE hSCManager;
|
||||
SC_HANDLE hService;
|
||||
BOOL bRet = FALSE;
|
||||
|
||||
dlgInfo->hDependsImageList = InitImageList(IDI_NODEPENDS,
|
||||
IDI_DRIVER,
|
||||
GetSystemMetrics(SM_CXSMICON),
|
||||
GetSystemMetrics(SM_CXSMICON),
|
||||
IMAGE_ICON);
|
||||
|
||||
|
||||
hTreeView1 = GetDlgItem(hwndDlg, IDC_DEPEND_TREE1);
|
||||
if (!hTreeView1)
|
||||
return;
|
||||
|
||||
(void)TreeView_SetImageList(hTreeView1,
|
||||
dlgInfo->hDependsImageList,
|
||||
TVSIL_NORMAL);
|
||||
|
||||
hTreeView2 = GetDlgItem(hwndDlg, IDC_DEPEND_TREE2);
|
||||
if (!hTreeView2)
|
||||
return;
|
||||
|
||||
(void)TreeView_SetImageList(hTreeView2,
|
||||
dlgInfo->hDependsImageList,
|
||||
TVSIL_NORMAL);
|
||||
/* Initialize the image list */
|
||||
pDlgInfo->hDependsImageList = InitImageList(IDI_NODEPENDS,
|
||||
IDI_DRIVER,
|
||||
GetSystemMetrics(SM_CXSMICON),
|
||||
GetSystemMetrics(SM_CXSMICON),
|
||||
IMAGE_ICON);
|
||||
|
||||
/* Set the first items in each tree view */
|
||||
hSCManager = OpenSCManager(NULL,
|
||||
NULL,
|
||||
SC_MANAGER_ALL_ACCESS);
|
||||
if (hSCManager)
|
||||
{
|
||||
hService = OpenService(hSCManager,
|
||||
dlgInfo->pService->lpServiceName,
|
||||
pDlgInfo->pService->lpServiceName,
|
||||
SERVICE_QUERY_STATUS | SERVICE_ENUMERATE_DEPENDENTS | SERVICE_QUERY_CONFIG);
|
||||
if (hService)
|
||||
{
|
||||
/* Set the first tree view */
|
||||
SetServiceDependents(hTreeView1,
|
||||
hSCManager,
|
||||
hService);
|
||||
TV1_Initialize(pDlgInfo, hService);
|
||||
|
||||
/* Set the second tree view */
|
||||
SetDependentServices(hService);
|
||||
TV2_Initialize(pDlgInfo, hService);
|
||||
|
||||
bRet = TRUE;
|
||||
|
||||
CloseServiceHandle(hService);
|
||||
}
|
||||
|
@ -272,10 +110,10 @@ InitDependPage(PSERVICEPROPSHEET dlgInfo,
|
|||
CloseServiceHandle(hSCManager);
|
||||
}
|
||||
|
||||
return bRet;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Dependancies Property dialog callback.
|
||||
* Controls messages to the Dependancies dialog
|
||||
|
@ -286,12 +124,12 @@ DependenciesPageProc(HWND hwndDlg,
|
|||
WPARAM wParam,
|
||||
LPARAM lParam)
|
||||
{
|
||||
PSERVICEPROPSHEET dlgInfo;
|
||||
PSERVICEPROPSHEET pDlgInfo;
|
||||
|
||||
/* Get the window context */
|
||||
dlgInfo = (PSERVICEPROPSHEET)GetWindowLongPtr(hwndDlg,
|
||||
GWLP_USERDATA);
|
||||
if (dlgInfo == NULL && uMsg != WM_INITDIALOG)
|
||||
pDlgInfo = (PSERVICEPROPSHEET)GetWindowLongPtr(hwndDlg,
|
||||
GWLP_USERDATA);
|
||||
if (pDlgInfo == NULL && uMsg != WM_INITDIALOG)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
@ -300,24 +138,48 @@ DependenciesPageProc(HWND hwndDlg,
|
|||
{
|
||||
case WM_INITDIALOG:
|
||||
{
|
||||
dlgInfo = (PSERVICEPROPSHEET)(((LPPROPSHEETPAGE)lParam)->lParam);
|
||||
if (dlgInfo != NULL)
|
||||
pDlgInfo = (PSERVICEPROPSHEET)(((LPPROPSHEETPAGE)lParam)->lParam);
|
||||
if (pDlgInfo != NULL)
|
||||
{
|
||||
SetWindowLongPtr(hwndDlg,
|
||||
GWLP_USERDATA,
|
||||
(LONG_PTR)dlgInfo);
|
||||
(LONG_PTR)pDlgInfo);
|
||||
|
||||
InitDependPage(dlgInfo, hwndDlg);
|
||||
pDlgInfo->hDependsWnd = hwndDlg;
|
||||
|
||||
InitDependPage(pDlgInfo);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_NOTIFY:
|
||||
{
|
||||
switch (((LPNMHDR)lParam)->code)
|
||||
{
|
||||
case TVN_ITEMEXPANDING:
|
||||
{
|
||||
LPNMTREEVIEW lpnmtv = (LPNMTREEVIEW)lParam;
|
||||
|
||||
if (lpnmtv->action == TVE_EXPAND)
|
||||
{
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_COMMAND:
|
||||
switch(LOWORD(wParam))
|
||||
{
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_DESTROY:
|
||||
if (pDlgInfo->hDependsImageList)
|
||||
ImageList_Destroy(pDlgInfo->hDependsImageList);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
|
|
|
@ -30,63 +30,54 @@ GetSelectedService(PMAIN_WND_INFO Info)
|
|||
LPQUERY_SERVICE_CONFIG
|
||||
GetServiceConfig(LPTSTR lpServiceName)
|
||||
{
|
||||
SC_HANDLE hSCManager = NULL;
|
||||
SC_HANDLE hSc = NULL;
|
||||
LPQUERY_SERVICE_CONFIG pServiceConfig = NULL;
|
||||
DWORD BytesNeeded = 0;
|
||||
LPQUERY_SERVICE_CONFIG lpServiceConfig = NULL;
|
||||
SC_HANDLE hSCManager;
|
||||
SC_HANDLE hService;
|
||||
DWORD dwBytesNeeded;
|
||||
|
||||
hSCManager = OpenSCManager(NULL,
|
||||
NULL,
|
||||
SC_MANAGER_ENUMERATE_SERVICE);
|
||||
if (hSCManager == NULL)
|
||||
SC_MANAGER_ALL_ACCESS);
|
||||
if (hSCManager)
|
||||
{
|
||||
GetError();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
hSc = OpenService(hSCManager,
|
||||
lpServiceName,
|
||||
SERVICE_QUERY_CONFIG);
|
||||
if (hSc == NULL)
|
||||
{
|
||||
GetError();
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!QueryServiceConfig(hSc,
|
||||
pServiceConfig,
|
||||
0,
|
||||
&BytesNeeded))
|
||||
{
|
||||
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
|
||||
hService = OpenService(hSCManager,
|
||||
lpServiceName,
|
||||
SERVICE_QUERY_STATUS | SERVICE_ENUMERATE_DEPENDENTS | SERVICE_QUERY_CONFIG);
|
||||
if (hService)
|
||||
{
|
||||
pServiceConfig = (LPQUERY_SERVICE_CONFIG) HeapAlloc(ProcessHeap,
|
||||
0,
|
||||
BytesNeeded);
|
||||
if (pServiceConfig == NULL)
|
||||
goto cleanup;
|
||||
|
||||
if (!QueryServiceConfig(hSc,
|
||||
pServiceConfig,
|
||||
BytesNeeded,
|
||||
&BytesNeeded))
|
||||
if (!QueryServiceConfig(hService,
|
||||
NULL,
|
||||
0,
|
||||
&dwBytesNeeded))
|
||||
{
|
||||
HeapFree(ProcessHeap,
|
||||
0,
|
||||
pServiceConfig);
|
||||
|
||||
pServiceConfig = NULL;
|
||||
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
|
||||
{
|
||||
lpServiceConfig = (LPQUERY_SERVICE_CONFIG)HeapAlloc(GetProcessHeap(),
|
||||
0,
|
||||
dwBytesNeeded);
|
||||
if (lpServiceConfig)
|
||||
{
|
||||
if (!QueryServiceConfig(hService,
|
||||
lpServiceConfig,
|
||||
dwBytesNeeded,
|
||||
&dwBytesNeeded))
|
||||
{
|
||||
HeapFree(GetProcessHeap(),
|
||||
0,
|
||||
lpServiceConfig);
|
||||
lpServiceConfig = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CloseServiceHandle(hService);
|
||||
}
|
||||
|
||||
CloseServiceHandle(hSCManager);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (hSCManager != NULL)
|
||||
CloseServiceHandle(hSCManager);
|
||||
if (hSc != NULL)
|
||||
CloseServiceHandle(hSc);
|
||||
|
||||
return pServiceConfig;
|
||||
return lpServiceConfig;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
<file>create.c</file>
|
||||
<file>delete.c</file>
|
||||
<file>dependencies.c</file>
|
||||
<file>dependencies_tv1.c</file>
|
||||
<file>dependencies_tv2.c</file>
|
||||
<file>export.c</file>
|
||||
<file>listview.c</file>
|
||||
<file>mainwnd.c</file>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#define REACTOS_STR_FILE_DESCRIPTION "ReactOS Service Manager\0"
|
||||
#define REACTOS_STR_INTERNAL_NAME "services\0"
|
||||
#define REACTOS_STR_ORIGINAL_FILENAME "servman.exe\0"
|
||||
#include <reactos/version.rc>
|
||||
//#include <reactos/version.rc>
|
||||
|
||||
1 24 DISCARDABLE "manifest.xml"
|
||||
|
||||
|
|
|
@ -71,11 +71,14 @@ static BOOL
|
|||
StopDependentServices(PSTOP_INFO pStopInfo,
|
||||
SC_HANDLE hService)
|
||||
{
|
||||
LPENUM_SERVICE_STATUS lpDependencies;
|
||||
SC_HANDLE hDepService;
|
||||
DWORD dwCount;
|
||||
//LPENUM_SERVICE_STATUS lpDependencies;
|
||||
//SC_HANDLE hDepService;
|
||||
//DWORD dwCount;
|
||||
BOOL bRet = FALSE;
|
||||
|
||||
MessageBox(NULL, L"Rewrite StopDependentServices", NULL, 0);
|
||||
/*
|
||||
|
||||
lpDependencies = GetServiceDependents(hService, &dwCount);
|
||||
if (lpDependencies)
|
||||
{
|
||||
|
@ -106,7 +109,7 @@ StopDependentServices(PSTOP_INFO pStopInfo,
|
|||
HeapFree(GetProcessHeap(),
|
||||
0,
|
||||
lpDependencies);
|
||||
}
|
||||
}*/
|
||||
|
||||
return bRet;
|
||||
}
|
||||
|
@ -116,14 +119,34 @@ BOOL
|
|||
DoStop(PMAIN_WND_INFO pInfo)
|
||||
{
|
||||
STOP_INFO stopInfo;
|
||||
SC_HANDLE hSCManager;
|
||||
SC_HANDLE hService;
|
||||
//SC_HANDLE hSCManager;
|
||||
SC_HANDLE hService = NULL;
|
||||
BOOL bRet = FALSE;
|
||||
|
||||
if (pInfo)
|
||||
{
|
||||
stopInfo.pInfo = pInfo;
|
||||
//stopInfo.pInfo = pInfo;
|
||||
|
||||
if (TRUE /*HasDependentServices(pInfo->pCurrentService->lpServiceName)*/)
|
||||
{
|
||||
INT ret = DialogBoxParam(hInstance,
|
||||
MAKEINTRESOURCE(IDD_DLG_DEPEND_STOP),
|
||||
pInfo->hMainWnd,
|
||||
StopDependsDialogProc,
|
||||
(LPARAM)&stopInfo);
|
||||
if (ret == IDOK)
|
||||
{
|
||||
if (StopDependentServices(&stopInfo, hService))
|
||||
{
|
||||
bRet = StopService(&stopInfo, hService);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bRet = StopService(&stopInfo, hService);
|
||||
}
|
||||
/*
|
||||
hSCManager = OpenSCManager(NULL,
|
||||
NULL,
|
||||
SC_MANAGER_ALL_ACCESS);
|
||||
|
@ -137,31 +160,11 @@ DoStop(PMAIN_WND_INFO pInfo)
|
|||
stopInfo.hSCManager = hSCManager;
|
||||
stopInfo.hMainService = hService;
|
||||
|
||||
if (HasDependentServices(hService))
|
||||
{
|
||||
INT ret = DialogBoxParam(hInstance,
|
||||
MAKEINTRESOURCE(IDD_DLG_DEPEND_STOP),
|
||||
pInfo->hMainWnd,
|
||||
StopDependsDialogProc,
|
||||
(LPARAM)&stopInfo);
|
||||
if (ret == IDOK)
|
||||
{
|
||||
if (StopDependentServices(&stopInfo, hService))
|
||||
{
|
||||
bRet = StopService(&stopInfo, hService);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bRet = StopService(&stopInfo, hService);
|
||||
}
|
||||
|
||||
CloseServiceHandle(hService);
|
||||
}
|
||||
|
||||
CloseServiceHandle(hSCManager);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
return bRet;
|
||||
|
|
Loading…
Reference in a new issue