mirror of
https://github.com/reactos/reactos.git
synced 2025-02-24 17:34:57 +00:00
sc.exe now supports basic starting, stopping, creation and deletion of services.
Still early days and very bare bones, so not including into build yet svn path=/trunk/; revision=19000
This commit is contained in:
parent
8886eb415a
commit
cfb2293f95
8 changed files with 117 additions and 84 deletions
|
@ -16,14 +16,26 @@
|
||||||
* control, continue, interrogate, pause, stop
|
* control, continue, interrogate, pause, stop
|
||||||
*/
|
*/
|
||||||
|
|
||||||
BOOL Control(DWORD Control, TCHAR **Args)
|
BOOL Control(DWORD Control, LPCTSTR ServiceName, LPCTSTR *Args)
|
||||||
{
|
{
|
||||||
SC_HANDLE hSc;
|
SC_HANDLE hSc;
|
||||||
SERVICE_STATUS Status;
|
SERVICE_STATUS Status;
|
||||||
LPCTSTR ServiceName = *Args;
|
|
||||||
|
/* testing */
|
||||||
|
_tprintf(_T("service to control - %s\n\n"), ServiceName);
|
||||||
|
_tprintf(_T("command - %lu\n\n"), Control);
|
||||||
|
_tprintf(_T("Arguments :\n"));
|
||||||
|
while (*Args)
|
||||||
|
{
|
||||||
|
printf("%s\n", *Args);
|
||||||
|
Args++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
hSc = OpenService(hSCManager, ServiceName, DELETE);
|
hSc = OpenService(hSCManager, ServiceName,
|
||||||
|
SERVICE_INTERROGATE | SERVICE_PAUSE_CONTINUE |
|
||||||
|
SERVICE_STOP | SERVICE_USER_DEFINED_CONTROL |
|
||||||
|
SERVICE_QUERY_STATUS);
|
||||||
|
|
||||||
if (hSc == NULL)
|
if (hSc == NULL)
|
||||||
{
|
{
|
||||||
|
@ -40,6 +52,9 @@ BOOL Control(DWORD Control, TCHAR **Args)
|
||||||
}
|
}
|
||||||
|
|
||||||
CloseServiceHandle(hSc);
|
CloseServiceHandle(hSc);
|
||||||
|
|
||||||
|
/* print the status information */
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,12 +11,14 @@
|
||||||
|
|
||||||
#include "sc.h"
|
#include "sc.h"
|
||||||
|
|
||||||
BOOL Create(TCHAR **Args)
|
BOOL Create(LPCTSTR ServiceName, LPCTSTR *ServiceArgs)
|
||||||
{
|
{
|
||||||
SC_HANDLE hSc;
|
SC_HANDLE hSc;
|
||||||
LPCTSTR ServiceName = *Args;
|
LPCTSTR BinaryPathName = *++ServiceArgs;
|
||||||
LPCTSTR BinaryPathName = *++Args;
|
LPCTSTR *Options = ++ServiceArgs;
|
||||||
LPCTSTR *Options = (LPCTSTR *)++Args;
|
|
||||||
|
if ((! ServiceName) || (! BinaryPathName))
|
||||||
|
return CreateUsage();
|
||||||
|
|
||||||
/* testing */
|
/* testing */
|
||||||
printf("service to create - %s\n", ServiceName);
|
printf("service to create - %s\n", ServiceName);
|
||||||
|
@ -28,7 +30,6 @@ BOOL Create(TCHAR **Args)
|
||||||
Options++;
|
Options++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
hSc = CreateService(hSCManager,
|
hSc = CreateService(hSCManager,
|
||||||
ServiceName,
|
ServiceName,
|
||||||
ServiceName,
|
ServiceName,
|
||||||
|
|
|
@ -11,10 +11,9 @@
|
||||||
|
|
||||||
#include "sc.h"
|
#include "sc.h"
|
||||||
|
|
||||||
BOOL Delete(TCHAR **Args)
|
BOOL Delete(LPCTSTR ServiceName)
|
||||||
{
|
{
|
||||||
SC_HANDLE hSc;
|
SC_HANDLE hSc;
|
||||||
LPCTSTR ServiceName = *Args;
|
|
||||||
|
|
||||||
/* testing */
|
/* testing */
|
||||||
printf("service to delete - %s\n\n", ServiceName);
|
printf("service to delete - %s\n\n", ServiceName);
|
||||||
|
|
|
@ -12,32 +12,31 @@
|
||||||
#include "sc.h"
|
#include "sc.h"
|
||||||
|
|
||||||
/* local function decs */
|
/* local function decs */
|
||||||
VOID PrintService(ENUM_SERVICE_STATUS_PROCESS *pSStatus, BOOL bExtended);
|
VOID PrintService(BOOL bExtended);
|
||||||
INT EnumServices(DWORD ServiceType, DWORD ServiceState);
|
INT EnumServices(DWORD ServiceType, DWORD ServiceState);
|
||||||
|
|
||||||
/* global variables */
|
/* global variables */
|
||||||
static ENUM_SERVICE_STATUS_PROCESS *pServiceStatus = NULL;
|
static ENUM_SERVICE_STATUS_PROCESS *pServiceStatus = NULL;
|
||||||
|
|
||||||
BOOL
|
|
||||||
Query(TCHAR **Args, BOOL bExtended)
|
BOOL Query(LPCTSTR ServiceName, LPCTSTR *ServiceArgs, BOOL bExtended)
|
||||||
{
|
{
|
||||||
|
|
||||||
LPCTSTR ServiceName = *Args;
|
if (! ServiceName)
|
||||||
|
|
||||||
if (! *Args)
|
|
||||||
{
|
{
|
||||||
printf("Service name %s\n", ServiceName); // test
|
/* display all running services and drivers */
|
||||||
|
_tprintf(_T("No service name, displaying all services\n")); // test
|
||||||
|
|
||||||
/* get default values */
|
/* get default values */
|
||||||
EnumServices(SERVICE_WIN32, SERVICE_ACTIVE);
|
EnumServices(SERVICE_WIN32, SERVICE_ACTIVE);
|
||||||
|
|
||||||
/* print default values */
|
/* print default values */
|
||||||
PrintService(pServiceStatus, bExtended);
|
PrintService(bExtended);
|
||||||
}
|
}
|
||||||
|
else if (_tcsicmp(ServiceName, _T("type=")) == 0)
|
||||||
if (_tcsicmp(Args[0], _T("type=")) == 0)
|
|
||||||
{
|
{
|
||||||
TCHAR *Type = *++Args;
|
LPCTSTR Type = *ServiceArgs;
|
||||||
|
|
||||||
_tprintf(_T("got type\narg = %s\n"), Type); // test
|
_tprintf(_T("got type\narg = %s\n"), Type); // test
|
||||||
if (_tcsicmp(Type, _T("driver")) == 0)
|
if (_tcsicmp(Type, _T("driver")) == 0)
|
||||||
EnumServices(SERVICE_DRIVER, SERVICE_STATE_ALL);
|
EnumServices(SERVICE_DRIVER, SERVICE_STATE_ALL);
|
||||||
|
@ -51,11 +50,11 @@ Query(TCHAR **Args, BOOL bExtended)
|
||||||
_tprintf(_T("Must be \"driver\" or \"service\" or \"all\"\n"));
|
_tprintf(_T("Must be \"driver\" or \"service\" or \"all\"\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
PrintService(pServiceStatus, bExtended);
|
PrintService(bExtended);
|
||||||
}
|
}
|
||||||
else if(_tcsicmp(Args[0], _T("state=")) == 0)
|
else if(_tcsicmp(ServiceName, _T("state=")) == 0)
|
||||||
{
|
{
|
||||||
TCHAR *State = *++Args;
|
LPCTSTR State = *ServiceArgs;
|
||||||
|
|
||||||
if (_tcsicmp(State, _T("active")) == 0)
|
if (_tcsicmp(State, _T("active")) == 0)
|
||||||
EnumServices(SERVICE_DRIVER|SERVICE_WIN32, SERVICE_ACTIVE);
|
EnumServices(SERVICE_DRIVER|SERVICE_WIN32, SERVICE_ACTIVE);
|
||||||
|
@ -69,27 +68,27 @@ Query(TCHAR **Args, BOOL bExtended)
|
||||||
_tprintf(_T("Must be \"active\" or \"inactive\" or \"all\"\n"));
|
_tprintf(_T("Must be \"active\" or \"inactive\" or \"all\"\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
PrintService(pServiceStatus, bExtended);
|
PrintService(bExtended);
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
else if(_tcsicmp(ServiceName, _T("bufsize=")))
|
||||||
|
|
||||||
|
else if(_tcsicmp(ServiceName, _T("ri=")))
|
||||||
|
|
||||||
|
else if(_tcsicmp(ServiceName, _T("group=")))
|
||||||
|
*/
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
printf("no args\n"); // test
|
/* print only the service requested */
|
||||||
|
printf("Service name %s\n", ServiceName); // test
|
||||||
|
|
||||||
/* get default values */
|
/* get default values */
|
||||||
EnumServices(SERVICE_WIN32, SERVICE_ACTIVE);
|
EnumServices(SERVICE_WIN32, SERVICE_ACTIVE);
|
||||||
|
|
||||||
/* print default values */
|
/* print default values */
|
||||||
PrintService(pServiceStatus, bExtended);
|
PrintService(bExtended);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
else if(_tcsicmp(Args[0], _T("bufsize=")))
|
|
||||||
|
|
||||||
else if(_tcsicmp(Args[0], _T("ri=")))
|
|
||||||
|
|
||||||
else if(_tcsicmp(Args[0], _T("group=")))
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -100,6 +99,8 @@ INT EnumServices(DWORD ServiceType, DWORD ServiceState)
|
||||||
DWORD BytesNeeded = 0;
|
DWORD BytesNeeded = 0;
|
||||||
DWORD NumServices = 0;
|
DWORD NumServices = 0;
|
||||||
DWORD ResumeHandle = 0;
|
DWORD ResumeHandle = 0;
|
||||||
|
|
||||||
|
// hSc = OpenService(hSCManager, ServiceName, SERVICE_QUERY_STATUS);
|
||||||
|
|
||||||
/* determine required buffer size */
|
/* determine required buffer size */
|
||||||
if (! EnumServicesStatusEx(hSCManager,
|
if (! EnumServicesStatusEx(hSCManager,
|
||||||
|
@ -147,8 +148,7 @@ INT EnumServices(DWORD ServiceType, DWORD ServiceState)
|
||||||
|
|
||||||
|
|
||||||
VOID
|
VOID
|
||||||
PrintService(ENUM_SERVICE_STATUS_PROCESS *pServiceStatus,
|
PrintService(BOOL bExtended)
|
||||||
BOOL bExtended)
|
|
||||||
{
|
{
|
||||||
_tprintf(_T("SERVICE_NAME: %s\n"), pServiceStatus->lpServiceName);
|
_tprintf(_T("SERVICE_NAME: %s\n"), pServiceStatus->lpServiceName);
|
||||||
_tprintf(_T("DISPLAY_NAME: %s\n"), pServiceStatus->lpDisplayName);
|
_tprintf(_T("DISPLAY_NAME: %s\n"), pServiceStatus->lpDisplayName);
|
||||||
|
|
|
@ -44,8 +44,14 @@ DWORD ReportLastError(VOID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
INT ScControl(LPTSTR MachineName, LPCTSTR Command, TCHAR **Args)
|
INT ScControl(LPTSTR MachineName, // remote machine name
|
||||||
|
LPCTSTR Command, // sc command
|
||||||
|
LPCTSTR ServiceName, // name of service
|
||||||
|
LPCTSTR *ServiceArgs, // any options
|
||||||
|
DWORD ArgCount) // argument counter
|
||||||
{
|
{
|
||||||
|
/* count trailing arguments */
|
||||||
|
ArgCount -= 3;
|
||||||
|
|
||||||
if (MachineName)
|
if (MachineName)
|
||||||
{
|
{
|
||||||
|
@ -61,66 +67,66 @@ INT ScControl(LPTSTR MachineName, LPCTSTR Command, TCHAR **Args)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* emurate command */
|
||||||
if (_tcsicmp(Command, _T("query")) == 0)
|
if (_tcsicmp(Command, _T("query")) == 0)
|
||||||
Query(Args, FALSE);
|
Query(ServiceName, ServiceArgs, FALSE);
|
||||||
|
|
||||||
else if (_tcsicmp(Command, _T("queryex")) == 0)
|
else if (_tcsicmp(Command, _T("queryex")) == 0)
|
||||||
Query(Args, TRUE);
|
Query(ServiceName, ServiceArgs, TRUE);
|
||||||
|
|
||||||
else if (_tcsicmp(Command, _T("start")) == 0)
|
else if (_tcsicmp(Command, _T("start")) == 0)
|
||||||
{
|
{
|
||||||
if (*Args)
|
if (ServiceName)
|
||||||
Start(0, Args);
|
Start(ServiceName, ServiceArgs, ArgCount);
|
||||||
else
|
else
|
||||||
StartUsage();
|
StartUsage();
|
||||||
}
|
}
|
||||||
else if (_tcsicmp(Command, _T("pause")) == 0)
|
else if (_tcsicmp(Command, _T("pause")) == 0)
|
||||||
{
|
{
|
||||||
if (*Args)
|
if (ServiceName)
|
||||||
Control(SERVICE_CONTROL_PAUSE, Args);
|
Control(SERVICE_CONTROL_PAUSE, ServiceName, ServiceArgs);
|
||||||
else
|
else
|
||||||
PauseUsage();
|
PauseUsage();
|
||||||
}
|
}
|
||||||
else if (_tcsicmp(Command, _T("interrogate")) == 0)
|
else if (_tcsicmp(Command, _T("interrogate")) == 0)
|
||||||
{
|
{
|
||||||
if (*Args)
|
if (ServiceName)
|
||||||
Control(SERVICE_CONTROL_INTERROGATE, Args);
|
Control(SERVICE_CONTROL_INTERROGATE, ServiceName, ServiceArgs);
|
||||||
else
|
else
|
||||||
InterrogateUsage();
|
InterrogateUsage();
|
||||||
}
|
}
|
||||||
else if (_tcsicmp(Command, _T("stop")) == 0)
|
else if (_tcsicmp(Command, _T("stop")) == 0)
|
||||||
{
|
{
|
||||||
if (*Args)
|
if (ServiceName)
|
||||||
Control(SERVICE_CONTROL_STOP, Args);
|
Control(SERVICE_CONTROL_STOP, ServiceName, ServiceArgs);
|
||||||
else
|
else
|
||||||
StopUsage();
|
StopUsage();
|
||||||
}
|
}
|
||||||
else if (_tcsicmp(Command, _T("continue")) == 0)
|
else if (_tcsicmp(Command, _T("continue")) == 0)
|
||||||
{
|
{
|
||||||
if (*Args)
|
if (ServiceName)
|
||||||
Control(SERVICE_CONTROL_CONTINUE, Args);
|
Control(SERVICE_CONTROL_CONTINUE, ServiceName, ServiceArgs);
|
||||||
else
|
else
|
||||||
ContinueUsage();
|
ContinueUsage();
|
||||||
}
|
}
|
||||||
else if (_tcsicmp(Command, _T("delete")) == 0)
|
else if (_tcsicmp(Command, _T("delete")) == 0)
|
||||||
{
|
{
|
||||||
if (*Args)
|
if (ServiceName)
|
||||||
Delete(Args);
|
Delete(ServiceName);
|
||||||
else
|
else
|
||||||
DeleteUsage();
|
DeleteUsage();
|
||||||
}
|
}
|
||||||
else if (_tcsicmp(Command, _T("create")) == 0)
|
else if (_tcsicmp(Command, _T("create")) == 0)
|
||||||
{
|
{
|
||||||
if (*Args)
|
if (*ServiceArgs)
|
||||||
Create(Args);
|
Create(ServiceName, ServiceArgs);
|
||||||
else
|
else
|
||||||
CreateUsage();
|
CreateUsage();
|
||||||
}
|
}
|
||||||
else if (_tcsicmp(Command, _T("control")) == 0)
|
else if (_tcsicmp(Command, _T("control")) == 0)
|
||||||
{
|
{
|
||||||
if (*Args)
|
if (ServiceName)
|
||||||
Control((DWORD)NULL, ++Args);
|
Control((DWORD)NULL, ServiceName, ServiceArgs);
|
||||||
else
|
else
|
||||||
ContinueUsage();
|
ContinueUsage();
|
||||||
}
|
}
|
||||||
|
@ -130,10 +136,9 @@ INT ScControl(LPTSTR MachineName, LPCTSTR Command, TCHAR **Args)
|
||||||
|
|
||||||
int _tmain(DWORD argc, LPCTSTR argv[])
|
int _tmain(DWORD argc, LPCTSTR argv[])
|
||||||
{
|
{
|
||||||
LPTSTR MachineName = NULL; // remote machine
|
LPTSTR MachineName = NULL; // remote machine
|
||||||
LPCTSTR Command = argv[1]; // sc command
|
LPCTSTR Command = NULL; // sc command
|
||||||
TCHAR **Args = NULL; // rest of args
|
LPCTSTR ServiceName = NULL; // Name of service
|
||||||
|
|
||||||
|
|
||||||
if (argc < 2)
|
if (argc < 2)
|
||||||
return MainUsage();
|
return MainUsage();
|
||||||
|
@ -146,13 +151,16 @@ int _tmain(DWORD argc, LPCTSTR argv[])
|
||||||
|
|
||||||
_tcscpy(MachineName, argv[1]);
|
_tcscpy(MachineName, argv[1]);
|
||||||
Command = argv[2];
|
Command = argv[2];
|
||||||
Args = (TCHAR **)&argv[3];
|
if (argc > 3)
|
||||||
return ScControl(MachineName, Command, Args);
|
ServiceName = argv[3];
|
||||||
|
return ScControl(MachineName, Command, ServiceName, &argv[4], argc);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Args = (TCHAR **)&argv[2];
|
Command = argv[1];
|
||||||
return ScControl(MachineName, Command, Args);
|
if (argc > 2)
|
||||||
|
ServiceName = argv[2];
|
||||||
|
return ScControl(MachineName, Command, ServiceName, &argv[3], argc);
|
||||||
}
|
}
|
||||||
|
|
||||||
return MainUsage();
|
return MainUsage();
|
||||||
|
|
|
@ -7,11 +7,11 @@ extern SC_HANDLE hSCManager; // declared in sc.c
|
||||||
//#define DBG
|
//#define DBG
|
||||||
|
|
||||||
/* control functions */
|
/* control functions */
|
||||||
BOOL Query(TCHAR **Args, BOOL bExtended);
|
BOOL Query(LPCTSTR ServiceName, LPCTSTR *ServiceArgs, BOOL bExtended);
|
||||||
BOOL Start(INT ArgCount, TCHAR **Args);
|
BOOL Start(LPCTSTR ServiceName, LPCTSTR *ServiceArgs, INT ArgCount);
|
||||||
BOOL Create(TCHAR **Args);
|
BOOL Create(LPCTSTR ServiceName, LPCTSTR *ServiceArgs);
|
||||||
BOOL Delete(TCHAR **Args);
|
BOOL Delete(LPCTSTR ServiceName);
|
||||||
BOOL Control(DWORD Control, TCHAR **Args);
|
BOOL Control(DWORD Control, LPCTSTR ServiceName, LPCTSTR *Args);
|
||||||
|
|
||||||
/* print and error functions */
|
/* print and error functions */
|
||||||
DWORD ReportLastError(VOID);
|
DWORD ReportLastError(VOID);
|
||||||
|
@ -25,3 +25,5 @@ INT ContinueUsage(VOID);
|
||||||
INT StopUsage(VOID);
|
INT StopUsage(VOID);
|
||||||
INT ConfigUsage(VOID);
|
INT ConfigUsage(VOID);
|
||||||
INT DescriptionUsage(VOID);
|
INT DescriptionUsage(VOID);
|
||||||
|
INT DeleteUsage(VOID);
|
||||||
|
INT CreateUsage(VOID);
|
||||||
|
|
|
@ -11,14 +11,11 @@
|
||||||
|
|
||||||
#include "sc.h"
|
#include "sc.h"
|
||||||
|
|
||||||
BOOL Start(INT ArgCount, TCHAR **Args)
|
BOOL Start(LPCTSTR ServiceName, LPCTSTR *ServiceArgs, INT ArgCount)
|
||||||
{
|
{
|
||||||
SC_HANDLE hSc;
|
SC_HANDLE hSc;
|
||||||
SERVICE_STATUS_PROCESS ServiceStatus;
|
SERVICE_STATUS_PROCESS ServiceStatus;
|
||||||
LPCTSTR ServiceName = *Args++;
|
|
||||||
LPCTSTR *ServiceArgs = (LPCTSTR *)Args;
|
|
||||||
DWORD BytesNeeded;
|
DWORD BytesNeeded;
|
||||||
|
|
||||||
|
|
||||||
/* testing */
|
/* testing */
|
||||||
_tprintf(_T("service to start - %s\n\n"), ServiceName);
|
_tprintf(_T("service to start - %s\n\n"), ServiceName);
|
||||||
|
@ -28,8 +25,7 @@ BOOL Start(INT ArgCount, TCHAR **Args)
|
||||||
printf("%s\n", *ServiceArgs);
|
printf("%s\n", *ServiceArgs);
|
||||||
ServiceArgs++;
|
ServiceArgs++;
|
||||||
}
|
}
|
||||||
if (! *ServiceArgs)
|
|
||||||
ServiceArgs = NULL;
|
|
||||||
|
|
||||||
/* get a handle to the service requested for starting */
|
/* get a handle to the service requested for starting */
|
||||||
hSc = OpenService(hSCManager, ServiceName, SERVICE_ALL_ACCESS);
|
hSc = OpenService(hSCManager, ServiceName, SERVICE_ALL_ACCESS);
|
||||||
|
|
|
@ -142,13 +142,25 @@ INT DeleteUsage(VOID)
|
||||||
|
|
||||||
INT CreateUsage(VOID)
|
INT CreateUsage(VOID)
|
||||||
{
|
{
|
||||||
_tprintf(_T("DESCRIPTION:\n")
|
_tprintf(_T("Creates a service entry in the registry and Service Database.\n")
|
||||||
_T("Creates a service entry in the registry and Service Database.\n")
|
_T("SYNTAX:\n")
|
||||||
_T(" If the service is running, or another process has an\n")
|
_T("sc create [service name] [binPath= ] <option1> <option2>...\n")
|
||||||
_T(" open handle to the service, the service is simply marked\n")
|
_T("CREATE OPTIONS:\n")
|
||||||
_T(" for deletion.\n")
|
_T("NOTE: The option name includes the equal sign.\n")
|
||||||
_T("USAGE:\n")
|
_T(" type= <own|share|interact|kernel|filesys|rec>\n")
|
||||||
_T(" sc <server> delete [service name]\n"));
|
_T(" (default = own)\n")
|
||||||
|
_T(" start= <boot|system|auto|demand|disabled>\n")
|
||||||
|
_T(" (default = demand)\n")
|
||||||
|
_T(" error= <normal|severe|critical|ignore>\n")
|
||||||
|
_T(" (default = normal)\n")
|
||||||
|
_T(" binPath= <BinaryPathName>\n")
|
||||||
|
_T(" group= <LoadOrderGroup>\n")
|
||||||
|
_T(" tag= <yes|no>\n")
|
||||||
|
_T(" depend= <Dependencies(separated by / (forward slash))>\n")
|
||||||
|
_T(" obj= <AccountName|ObjectName>\n")
|
||||||
|
_T(" (default = LocalSystem)\n")
|
||||||
|
_T(" DisplayName= <display name>\n")
|
||||||
|
_T(" password= <password>\n"));
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue