[ROSTESTS]: advapi32_apitest: Add a test to extensively test services command-line...
authorHermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
Fri, 2 Dec 2016 20:01:29 +0000 (20:01 +0000)
committerHermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
Fri, 2 Dec 2016 20:01:29 +0000 (20:01 +0000)
[ADVAPI32]: Correctly set up both the ANSI and UNICODE service command-line arguments. Adapted from a patch by Thomas Faber. Thanks!

[SERVICES]
- Correctly pack the service command-line arguments in the control packet structure. In particular, the offsets stored in the vector are relative to the beginning of the vector (and not relative to the previous offset ^^). Improve comments...
- Fix the definition of the SCM_CONTROL_PACKET control packet structure to make it Win2k3-compatible, so that we can use Win2k3' advapi32.dll or services.exe on ReactOS and vice-versa.

CORE-9235 CORE-9838

svn path=/trunk/; revision=73413

reactos/base/system/services/database.c
reactos/dll/win32/advapi32/service/sctrl.c
reactos/sdk/include/reactos/services/services.h
rostests/apitests/advapi32/CMakeLists.txt
rostests/apitests/advapi32/ServiceArgs.c [new file with mode: 0644]
rostests/apitests/advapi32/testlist.c

index 6a54248..5a6f210 100644 (file)
@@ -1216,17 +1216,17 @@ ScmSendStartCommand(PSERVICE Service,
                     DWORD argc,
                     LPWSTR* argv)
 {
                     DWORD argc,
                     LPWSTR* argv)
 {
+    DWORD dwError = ERROR_SUCCESS;
     PSCM_CONTROL_PACKET ControlPacket;
     SCM_REPLY_PACKET ReplyPacket;
     DWORD PacketSize;
     PSCM_CONTROL_PACKET ControlPacket;
     SCM_REPLY_PACKET ReplyPacket;
     DWORD PacketSize;
-    PWSTR Ptr;
-    DWORD dwWriteCount = 0;
-    DWORD dwReadCount = 0;
-    DWORD dwError = ERROR_SUCCESS;
     DWORD i;
     DWORD i;
+    PWSTR Ptr;
     PWSTR *pOffPtr;
     PWSTR pArgPtr;
     BOOL bResult;
     PWSTR *pOffPtr;
     PWSTR pArgPtr;
     BOOL bResult;
+    DWORD dwWriteCount = 0;
+    DWORD dwReadCount = 0;
 #ifdef USE_ASYNCHRONOUS_IO
     OVERLAPPED Overlapped = {0};
 #endif
 #ifdef USE_ASYNCHRONOUS_IO
     OVERLAPPED Overlapped = {0};
 #endif
@@ -1234,26 +1234,32 @@ ScmSendStartCommand(PSERVICE Service,
     DPRINT("ScmSendStartCommand() called\n");
 
     /* Calculate the total length of the start command line */
     DPRINT("ScmSendStartCommand() called\n");
 
     /* Calculate the total length of the start command line */
-    PacketSize = sizeof(SCM_CONTROL_PACKET) +
-                 (DWORD)((wcslen(Service->lpServiceName) + 1) * sizeof(WCHAR));
+    PacketSize = sizeof(SCM_CONTROL_PACKET);
+    PacketSize += (DWORD)((wcslen(Service->lpServiceName) + 1) * sizeof(WCHAR));
 
 
-    /* Calculate the required packet size for the start arguments */
+    /*
+     * Calculate the required packet size for the start argument vector 'argv',
+     * composed of the list of pointer offsets, followed by UNICODE strings.
+     * The strings are stored continuously after the vector of offsets, with
+     * the offsets being relative to the beginning of the vector, as in the
+     * following layout (with N == argc):
+     *     [argOff(0)]...[argOff(N-1)][str(0)]...[str(N-1)] .
+     */
     if (argc > 0 && argv != NULL)
     {
     if (argc > 0 && argv != NULL)
     {
-        PacketSize = ALIGN_UP(PacketSize, LPWSTR);
+        PacketSize = ALIGN_UP(PacketSize, PWSTR);
+        PacketSize += (argc * sizeof(PWSTR));
 
         DPRINT("Argc: %lu\n", argc);
         for (i = 0; i < argc; i++)
         {
             DPRINT("Argv[%lu]: %S\n", i, argv[i]);
 
         DPRINT("Argc: %lu\n", argc);
         for (i = 0; i < argc; i++)
         {
             DPRINT("Argv[%lu]: %S\n", i, argv[i]);
-            PacketSize += (DWORD)((wcslen(argv[i]) + 1) * sizeof(WCHAR) + sizeof(PWSTR));
+            PacketSize += (DWORD)((wcslen(argv[i]) + 1) * sizeof(WCHAR));
         }
     }
 
     /* Allocate a control packet */
         }
     }
 
     /* Allocate a control packet */
-    ControlPacket = HeapAlloc(GetProcessHeap(),
-                              HEAP_ZERO_MEMORY,
-                              PacketSize);
+    ControlPacket = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, PacketSize);
     if (ControlPacket == NULL)
         return ERROR_NOT_ENOUGH_MEMORY;
 
     if (ControlPacket == NULL)
         return ERROR_NOT_ENOUGH_MEMORY;
 
@@ -1262,22 +1268,23 @@ ScmSendStartCommand(PSERVICE Service,
                                ? SERVICE_CONTROL_START_OWN
                                : SERVICE_CONTROL_START_SHARE;
     ControlPacket->hServiceStatus = (SERVICE_STATUS_HANDLE)Service;
                                ? SERVICE_CONTROL_START_OWN
                                : SERVICE_CONTROL_START_SHARE;
     ControlPacket->hServiceStatus = (SERVICE_STATUS_HANDLE)Service;
-    ControlPacket->dwServiceNameOffset = sizeof(SCM_CONTROL_PACKET);
 
 
-    Ptr = (PWSTR)((PBYTE)ControlPacket + ControlPacket->dwServiceNameOffset);
+    /* Copy the start command line */
+    ControlPacket->dwServiceNameOffset = sizeof(SCM_CONTROL_PACKET);
+    Ptr = (PWSTR)((ULONG_PTR)ControlPacket + ControlPacket->dwServiceNameOffset);
     wcscpy(Ptr, Service->lpServiceName);
 
     wcscpy(Ptr, Service->lpServiceName);
 
-    ControlPacket->dwArgumentsCount = 0;
+    ControlPacket->dwArgumentsCount  = 0;
     ControlPacket->dwArgumentsOffset = 0;
 
     ControlPacket->dwArgumentsOffset = 0;
 
-    /* Copy argument list */
+    /* Copy the argument vector */
     if (argc > 0 && argv != NULL)
     {
         Ptr += wcslen(Service->lpServiceName) + 1;
         pOffPtr = (PWSTR*)ALIGN_UP_POINTER(Ptr, PWSTR);
         pArgPtr = (PWSTR)((ULONG_PTR)pOffPtr + argc * sizeof(PWSTR));
 
     if (argc > 0 && argv != NULL)
     {
         Ptr += wcslen(Service->lpServiceName) + 1;
         pOffPtr = (PWSTR*)ALIGN_UP_POINTER(Ptr, PWSTR);
         pArgPtr = (PWSTR)((ULONG_PTR)pOffPtr + argc * sizeof(PWSTR));
 
-        ControlPacket->dwArgumentsCount = argc;
+        ControlPacket->dwArgumentsCount  = argc;
         ControlPacket->dwArgumentsOffset = (DWORD)((ULONG_PTR)pOffPtr - (ULONG_PTR)ControlPacket);
 
         DPRINT("dwArgumentsCount: %lu\n", ControlPacket->dwArgumentsCount);
         ControlPacket->dwArgumentsOffset = (DWORD)((ULONG_PTR)pOffPtr - (ULONG_PTR)ControlPacket);
 
         DPRINT("dwArgumentsCount: %lu\n", ControlPacket->dwArgumentsCount);
@@ -1285,12 +1292,10 @@ ScmSendStartCommand(PSERVICE Service,
 
         for (i = 0; i < argc; i++)
         {
 
         for (i = 0; i < argc; i++)
         {
-             wcscpy(pArgPtr, argv[i]);
-             *pOffPtr = (PWSTR)((ULONG_PTR)pArgPtr - (ULONG_PTR)pOffPtr);
-             DPRINT("offset: %p\n", *pOffPtr);
-
-             pArgPtr += wcslen(argv[i]) + 1;
-             pOffPtr++;
+            wcscpy(pArgPtr, argv[i]);
+            pOffPtr[i] = (PWSTR)((ULONG_PTR)pArgPtr - (ULONG_PTR)pOffPtr);
+            DPRINT("offset[%lu]: %p\n", i, pOffPtr[i]);
+            pArgPtr += wcslen(argv[i]) + 1;
         }
     }
 
         }
     }
 
index b253ec7..9859657 100644 (file)
@@ -278,58 +278,88 @@ ScConnectControlPipe(HANDLE *hPipe)
 }
 
 
 }
 
 
+/*
+ * Ansi/Unicode argument layout of the vector passed to a service at startup,
+ * depending on the different versions of Windows considered:
+ *
+ * - XP/2003:
+ *   [argv array of pointers][parameter 1][parameter 2]...[service name]
+ *
+ * - Vista:
+ *   [argv array of pointers][align to 8 bytes]
+ *   [parameter 1][parameter 2]...[service name]
+ *
+ * - Win7/8:
+ *   [argv array of pointers][service name]
+ *   [parameter 1][align to 4 bytes][parameter 2][align to 4 bytes]...
+ *
+ * Space for parameters and service name is always enough to store
+ * both the Ansi and the Unicode versions including NULL terminator.
+ */
+
 static DWORD
 ScBuildUnicodeArgsVector(PSCM_CONTROL_PACKET ControlPacket,
                          LPDWORD lpArgCount,
                          LPWSTR **lpArgVector)
 {
 static DWORD
 ScBuildUnicodeArgsVector(PSCM_CONTROL_PACKET ControlPacket,
                          LPDWORD lpArgCount,
                          LPWSTR **lpArgVector)
 {
-    LPWSTR *lpVector;
-    LPWSTR *lpArg;
-    LPWSTR pszServiceName;
+    PWSTR *lpVector;
+    PWSTR pszServiceName;
     DWORD cbServiceName;
     DWORD cbServiceName;
+    DWORD cbArguments;
     DWORD cbTotal;
     DWORD i;
 
     if (ControlPacket == NULL || lpArgCount == NULL || lpArgVector == NULL)
         return ERROR_INVALID_PARAMETER;
 
     DWORD cbTotal;
     DWORD i;
 
     if (ControlPacket == NULL || lpArgCount == NULL || lpArgVector == NULL)
         return ERROR_INVALID_PARAMETER;
 
-    *lpArgCount = 0;
+    *lpArgCount  = 0;
     *lpArgVector = NULL;
 
     *lpArgVector = NULL;
 
-    pszServiceName = (PWSTR)((PBYTE)ControlPacket + ControlPacket->dwServiceNameOffset);
-    cbServiceName = lstrlenW(pszServiceName) * sizeof(WCHAR) + sizeof(UNICODE_NULL);
+    /* Retrieve and count the start command line (NULL-terminated) */
+    pszServiceName = (PWSTR)((ULONG_PTR)ControlPacket + ControlPacket->dwServiceNameOffset);
+    cbServiceName  = lstrlenW(pszServiceName) * sizeof(WCHAR) + sizeof(UNICODE_NULL);
 
 
-    cbTotal = cbServiceName + sizeof(LPWSTR);
+    /*
+     * The total size of the argument vector is equal to the entry for
+     * the service name, plus the size of the original argument vector.
+     */
+    cbTotal = sizeof(PWSTR) + cbServiceName;
     if (ControlPacket->dwArgumentsCount > 0)
     if (ControlPacket->dwArgumentsCount > 0)
-        cbTotal += ControlPacket->dwSize - ControlPacket->dwArgumentsOffset;
+        cbArguments = ControlPacket->dwSize - ControlPacket->dwArgumentsOffset;
+    else
+        cbArguments = 0;
+    cbTotal += cbArguments;
 
 
-    lpVector = HeapAlloc(GetProcessHeap(),
-                         HEAP_ZERO_MEMORY,
-                         cbTotal);
+    /* Allocate the new argument vector */
+    lpVector = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbTotal);
     if (lpVector == NULL)
     if (lpVector == NULL)
-        return ERROR_OUTOFMEMORY;
-
-    lpArg = lpVector;
-    *lpArg = (LPWSTR)(lpArg + 1);
-    lpArg++;
+        return ERROR_NOT_ENOUGH_MEMORY;
 
 
-    memcpy(lpArg, pszServiceName, cbServiceName);
-    lpArg = (LPWSTR*)((ULONG_PTR)lpArg + cbServiceName);
+    /*
+     * The first argument is reserved for the service name, which
+     * will be appended to the end of the argument string list.
+     */
 
 
+    /* Copy the remaining arguments */
     if (ControlPacket->dwArgumentsCount > 0)
     {
     if (ControlPacket->dwArgumentsCount > 0)
     {
-        memcpy(lpArg,
-               ((PBYTE)ControlPacket + ControlPacket->dwArgumentsOffset),
-               ControlPacket->dwSize - ControlPacket->dwArgumentsOffset);
+        memcpy(&lpVector[1],
+               (PWSTR)((ULONG_PTR)ControlPacket + ControlPacket->dwArgumentsOffset),
+               cbArguments);
 
         for (i = 0; i < ControlPacket->dwArgumentsCount; i++)
         {
 
         for (i = 0; i < ControlPacket->dwArgumentsCount; i++)
         {
-            *lpArg = (LPWSTR)((ULONG_PTR)lpArg + (ULONG_PTR)*lpArg);
-            lpArg++;
+            lpVector[i + 1] = (PWSTR)((ULONG_PTR)&lpVector[1] + (ULONG_PTR)lpVector[i + 1]);
+            TRACE("Unicode lpVector[%lu] = '%ls'\n", i + 1, lpVector[i + 1]);
         }
     }
 
         }
     }
 
-    *lpArgCount = ControlPacket->dwArgumentsCount + 1;
+    /* Now copy the service name */
+    lpVector[0] = (PWSTR)((ULONG_PTR)&lpVector[1] + cbArguments);
+    memcpy(lpVector[0], pszServiceName, cbServiceName);
+    TRACE("Unicode lpVector[%lu] = '%ls'\n", 0, lpVector[0]);
+
+    *lpArgCount  = ControlPacket->dwArgumentsCount + 1;
     *lpArgVector = lpVector;
 
     return ERROR_SUCCESS;
     *lpArgVector = lpVector;
 
     return ERROR_SUCCESS;
@@ -341,101 +371,48 @@ ScBuildAnsiArgsVector(PSCM_CONTROL_PACKET ControlPacket,
                       LPDWORD lpArgCount,
                       LPSTR **lpArgVector)
 {
                       LPDWORD lpArgCount,
                       LPSTR **lpArgVector)
 {
-    LPSTR *lpVector;
-    LPSTR *lpPtr;
-    LPWSTR lpUnicodeString;
-    LPWSTR pszServiceName;
-    LPSTR lpAnsiString;
-    DWORD cbServiceName;
-    DWORD dwVectorSize;
-    DWORD dwUnicodeSize;
-    DWORD dwAnsiSize = 0;
-    DWORD dwAnsiNameSize = 0;
-    DWORD i;
+    DWORD dwError;
+    NTSTATUS Status;
+    DWORD ArgCount, i;
+    PWSTR *lpVectorW;
+    PSTR  *lpVectorA;
+    UNICODE_STRING UnicodeString;
+    ANSI_STRING AnsiString;
 
     if (ControlPacket == NULL || lpArgCount == NULL || lpArgVector == NULL)
         return ERROR_INVALID_PARAMETER;
 
 
     if (ControlPacket == NULL || lpArgCount == NULL || lpArgVector == NULL)
         return ERROR_INVALID_PARAMETER;
 
-    *lpArgCount = 0;
+    *lpArgCount  = 0;
     *lpArgVector = NULL;
 
     *lpArgVector = NULL;
 
-    pszServiceName = (PWSTR)((PBYTE)ControlPacket + ControlPacket->dwServiceNameOffset);
-    cbServiceName = lstrlenW(pszServiceName) * sizeof(WCHAR) + sizeof(UNICODE_NULL);
-
-    dwAnsiNameSize = WideCharToMultiByte(CP_ACP,
-                                         0,
-                                         pszServiceName,
-                                         cbServiceName,
-                                         NULL,
-                                         0,
-                                         NULL,
-                                         NULL);
-
-    dwVectorSize = ControlPacket->dwArgumentsCount * sizeof(LPWSTR);
-    if (ControlPacket->dwArgumentsCount > 0)
-    {
-        lpUnicodeString = (LPWSTR)((PBYTE)ControlPacket +
-                                   ControlPacket->dwArgumentsOffset +
-                                   dwVectorSize);
-        dwUnicodeSize = (ControlPacket->dwSize -
-                         ControlPacket->dwArgumentsOffset -
-                         dwVectorSize) / sizeof(WCHAR);
-
-        dwAnsiSize = WideCharToMultiByte(CP_ACP,
-                                         0,
-                                         lpUnicodeString,
-                                         dwUnicodeSize,
-                                         NULL,
-                                         0,
-                                         NULL,
-                                         NULL);
-    }
-
-    dwVectorSize += sizeof(LPWSTR);
-
-    lpVector = HeapAlloc(GetProcessHeap(),
-                         HEAP_ZERO_MEMORY,
-                         dwVectorSize + dwAnsiNameSize + dwAnsiSize);
-    if (lpVector == NULL)
-        return ERROR_OUTOFMEMORY;
-
-    lpPtr = (LPSTR*)lpVector;
-    lpAnsiString = (LPSTR)((ULONG_PTR)lpVector + dwVectorSize);
-
-    WideCharToMultiByte(CP_ACP,
-                        0,
-                        pszServiceName,
-                        cbServiceName,
-                        lpAnsiString,
-                        dwAnsiNameSize,
-                        NULL,
-                        NULL);
+    /* Build the UNICODE arguments vector */
+    dwError = ScBuildUnicodeArgsVector(ControlPacket, &ArgCount, &lpVectorW);
+    if (dwError != ERROR_SUCCESS)
+        return dwError;
 
 
-    if (ControlPacket->dwArgumentsCount > 0)
+    /* Convert the vector to ANSI in place */
+    lpVectorA = (PSTR*)lpVectorW;
+    for (i = 0; i < ArgCount; i++)
     {
     {
-        lpAnsiString = (LPSTR)((ULONG_PTR)lpAnsiString + dwAnsiNameSize);
-
-        WideCharToMultiByte(CP_ACP,
-                            0,
-                            lpUnicodeString,
-                            dwUnicodeSize,
-                            lpAnsiString,
-                            dwAnsiSize,
-                            NULL,
-                            NULL);
-    }
+        RtlInitUnicodeString(&UnicodeString, lpVectorW[i]);
+        RtlInitEmptyAnsiString(&AnsiString, lpVectorA[i], UnicodeString.MaximumLength);
+        Status = RtlUnicodeStringToAnsiString(&AnsiString, &UnicodeString, FALSE);
+        if (!NT_SUCCESS(Status))
+        {
+            /* Failed to convert to ANSI; free the allocated vector and return */
+            dwError = RtlNtStatusToDosError(Status);
+            HeapFree(GetProcessHeap(), 0, lpVectorW);
+            return dwError;
+        }
 
 
-    lpAnsiString = (LPSTR)((ULONG_PTR)lpVector + dwVectorSize);
-    for (i = 0; i < ControlPacket->dwArgumentsCount + 1; i++)
-    {
-        *lpPtr = lpAnsiString;
+        /* NULL-terminate the string */
+        AnsiString.Buffer[AnsiString.Length / sizeof(CHAR)] = ANSI_NULL;
 
 
-        lpPtr++;
-        lpAnsiString += (strlen(lpAnsiString) + 1);
+        TRACE("Ansi lpVector[%lu] = '%s'\n", i, lpVectorA[i]);
     }
 
     }
 
-    *lpArgCount = ControlPacket->dwArgumentsCount + 1;
-    *lpArgVector = lpVector;
+    *lpArgCount  = ArgCount;
+    *lpArgVector = lpVectorA;
 
     return ERROR_SUCCESS;
 }
 
     return ERROR_SUCCESS;
 }
index 7147c43..b6f9d17 100644 (file)
@@ -29,9 +29,9 @@ typedef struct _SCM_CONTROL_PACKET
 {
     DWORD dwSize;
     DWORD dwControl;
 {
     DWORD dwSize;
     DWORD dwControl;
+    DWORD dwArgumentsCount;
     SERVICE_STATUS_HANDLE hServiceStatus;
     DWORD dwServiceNameOffset;
     SERVICE_STATUS_HANDLE hServiceStatus;
     DWORD dwServiceNameOffset;
-    DWORD dwArgumentsCount;
     DWORD dwArgumentsOffset;
 } SCM_CONTROL_PACKET, *PSCM_CONTROL_PACKET;
 
     DWORD dwArgumentsOffset;
 } SCM_CONTROL_PACKET, *PSCM_CONTROL_PACKET;
 
index affaee6..ce7caf1 100644 (file)
@@ -13,6 +13,7 @@ list(APPEND SOURCE
     RegQueryValueExW.c
     RtlEncryptMemory.c
     SaferIdentifyLevel.c
     RegQueryValueExW.c
     RtlEncryptMemory.c
     SaferIdentifyLevel.c
+    ServiceArgs.c
     testlist.c)
 
 add_executable(advapi32_apitest ${SOURCE})
     testlist.c)
 
 add_executable(advapi32_apitest ${SOURCE})
diff --git a/rostests/apitests/advapi32/ServiceArgs.c b/rostests/apitests/advapi32/ServiceArgs.c
new file mode 100644 (file)
index 0000000..38d19aa
--- /dev/null
@@ -0,0 +1,475 @@
+/*
+ * PROJECT:         ReactOS api tests
+ * LICENSE:         LGPLv2.1+ - See COPYING.LIB in the top level directory
+ * PURPOSE:         Test for service arguments
+ * PROGRAMMER:      Jacek Caban for CodeWeavers
+ *                  Thomas Faber <thomas.faber@reactos.org>
+ */
+
+#include <apitest.h>
+#include <winnls.h>
+#include <winsvc.h>
+#include <strsafe.h>
+
+static char **argv;
+static int argc;
+
+static HANDLE pipe_handle = INVALID_HANDLE_VALUE;
+static char service_nameA[100];
+static WCHAR service_nameW[100];
+static WCHAR named_pipe_name[100];
+
+/* Test process global variables */
+static SC_HANDLE scm_handle;
+static SERVICE_STATUS_HANDLE service_handle;
+
+static void send_msg(const char *type, const char *msg)
+{
+    DWORD written = 0;
+    char buf[512];
+
+    StringCbPrintfA(buf, sizeof(buf), "%s:%s", type, msg);
+    WriteFile(pipe_handle, buf, strlen(buf)+1, &written, NULL);
+}
+
+static inline void service_trace(const char *msg, ...)
+{
+    va_list valist;
+    char buf[512];
+
+    va_start(valist, msg);
+    StringCbVPrintfA(buf, sizeof(buf), msg, valist);
+    va_end(valist);
+
+    send_msg("TRACE", buf);
+}
+
+static void service_ok(int cnd, const char *msg, ...)
+{
+   va_list valist;
+   char buf[512];
+
+    va_start(valist, msg);
+    StringCbVPrintfA(buf, sizeof(buf), msg, valist);
+    va_end(valist);
+
+    send_msg(cnd ? "OK" : "FAIL", buf);
+}
+
+static VOID WINAPI service_handler(DWORD ctrl)
+{
+    SERVICE_STATUS status;
+
+    status.dwServiceType             = SERVICE_WIN32;
+    status.dwControlsAccepted        = SERVICE_ACCEPT_STOP;
+    status.dwWin32ExitCode           = 0;
+    status.dwServiceSpecificExitCode = 0;
+    status.dwCheckPoint              = 0;
+    status.dwWaitHint                = 0;
+
+    switch(ctrl)
+    {
+    case SERVICE_CONTROL_STOP:
+        status.dwCurrentState     = SERVICE_STOP_PENDING;
+        status.dwControlsAccepted = 0;
+        SetServiceStatus(service_handle, &status);
+    default:
+        status.dwCurrentState = SERVICE_RUNNING;
+        SetServiceStatus(service_handle, &status);
+    }
+}
+
+static void service_main_common(void)
+{
+    SERVICE_STATUS status;
+    BOOL res;
+
+    service_handle = RegisterServiceCtrlHandlerW(service_nameW, service_handler);
+    service_ok(service_handle != NULL, "RegisterServiceCtrlHandler failed: %lu\n", GetLastError());
+    if (!service_handle)
+        return;
+
+    status.dwServiceType             = SERVICE_WIN32;
+    status.dwCurrentState            = SERVICE_RUNNING;
+    status.dwControlsAccepted        = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
+    status.dwWin32ExitCode           = 0;
+    status.dwServiceSpecificExitCode = 0;
+    status.dwCheckPoint              = 0;
+    status.dwWaitHint                = 10000;
+    res = SetServiceStatus(service_handle, &status);
+    service_ok(res, "SetServiceStatus(SERVICE_RUNNING) failed: %lu", GetLastError());
+
+    Sleep(100);
+
+    status.dwCurrentState     = SERVICE_STOPPED;
+    status.dwControlsAccepted = 0;
+    res = SetServiceStatus(service_handle, &status);
+    service_ok(res, "SetServiceStatus(SERVICE_STOPPED) failed: %lu", GetLastError());
+}
+
+/*
+ * A/W argument layout XP/2003:
+ * [argv array of pointers][parameter 1][parameter 2]...[service name]
+ *
+ * A/W argument layout Vista:
+ * [argv array of pointers][align to 8 bytes][parameter 1][parameter 2]...[service name]
+ *
+ * A/W argument layout Win7/8:
+ * [argv array of pointers][service name]
+ * [parameter 1][align to 4 bytes][parameter 2][align to 4 bytes]...
+ *
+ * Space for parameters and service name is always enough to store
+ * the WCHAR version including null terminator.
+ */
+
+static void WINAPI service_mainA(DWORD service_argc, char **service_argv)
+{
+    int i;
+    char *next_arg;
+    char *next_arg_aligned;
+
+    service_ok(service_argc == argc - 3, "service_argc = %d, expected %d", service_argc, argc - 3);
+    if (service_argc == argc - 3)
+    {
+        service_ok(!strcmp(service_argv[0], service_nameA), "service_argv[0] = %s, expected %s", service_argv[0], service_nameA);
+        service_ok(service_argv[0] == (char *)&service_argv[service_argc] ||
+                   service_argv[1] == (char *)&service_argv[service_argc] ||
+                   service_argv[1] == (char *)(((ULONG_PTR)&service_argv[service_argc] + 7) & ~7), "service_argv[0] = %p, [1] = %p, expected one of them to be %p", service_argv[0], service_argv[1], &service_argv[service_argc]);
+        //service_trace("service_argv[0] = %p", service_argv[0]);
+        next_arg_aligned = next_arg = NULL;
+        for (i = 1; i < service_argc; i++)
+        {
+            //service_trace("service_argv[%d] = %p", i, service_argv[i]);
+            service_ok(!strcmp(service_argv[i], argv[i + 3]), "service_argv[%d] = %s, expected %s", i, service_argv[i], argv[i + 3]);
+            service_ok(next_arg == NULL ||
+                       service_argv[i] == next_arg ||
+                       service_argv[i] == next_arg_aligned, "service_argv[%d] = %p, expected %p or %p", i, service_argv[i], next_arg, next_arg_aligned);
+            next_arg = service_argv[i];
+            next_arg += 2 * (strlen(next_arg) + 1);
+            next_arg_aligned = (char *)(((ULONG_PTR)next_arg + 3) & ~3);
+        }
+    }
+
+    service_main_common();
+}
+
+static void WINAPI service_mainW(DWORD service_argc, WCHAR **service_argv)
+{
+    int i;
+    WCHAR argW[32];
+    WCHAR *next_arg;
+    WCHAR *next_arg_aligned;
+
+    service_ok(service_argc == argc - 3, "service_argc = %d, expected %d", service_argc, argc - 3);
+    if (service_argc == argc - 3)
+    {
+        service_ok(!wcscmp(service_argv[0], service_nameW), "service_argv[0] = %ls, expected %ls", service_argv[0], service_nameW);
+        service_ok(service_argv[0] == (WCHAR *)&service_argv[service_argc] ||
+                   service_argv[1] == (WCHAR *)&service_argv[service_argc] ||
+                   service_argv[1] == (WCHAR *)(((ULONG_PTR)&service_argv[service_argc] + 7) & ~7), "service_argv[0] = %p, [1] = %p, expected one of them to be %p", service_argv[0], service_argv[1], &service_argv[service_argc]);
+        //service_trace("service_argv[0] = %p", service_argv[0]);
+        next_arg_aligned = next_arg = NULL;
+        for (i = 1; i < service_argc; i++)
+        {
+            //service_trace("service_argv[%d] = %p", i, service_argv[i]);
+            MultiByteToWideChar(CP_ACP, 0, argv[i + 3], -1, argW, _countof(argW));
+            service_ok(!wcscmp(service_argv[i], argW), "service_argv[%d] = %ls, expected %ls", i, service_argv[i], argW);
+            service_ok(next_arg == NULL ||
+                       service_argv[i] == next_arg ||
+                       service_argv[i] == next_arg_aligned, "service_argv[%d] = %p, expected %p or %p", i, service_argv[i], next_arg, next_arg_aligned);
+            next_arg = service_argv[i];
+            next_arg += wcslen(next_arg) + 1;
+            next_arg_aligned = (WCHAR *)(((ULONG_PTR)next_arg + 3) & ~3);
+        }
+    }
+
+    service_main_common();
+}
+
+static void service_process(BOOLEAN unicode)
+{
+    BOOL res;
+
+    SERVICE_TABLE_ENTRYA servtblA[] =
+    {
+        { service_nameA, service_mainA },
+        { NULL, NULL }
+    };
+    SERVICE_TABLE_ENTRYW servtblW[] =
+    {
+        { service_nameW, service_mainW },
+        { NULL, NULL }
+    };
+
+    res = WaitNamedPipeW(named_pipe_name, NMPWAIT_USE_DEFAULT_WAIT);
+    if (!res)
+        return;
+
+    pipe_handle = CreateFileW(named_pipe_name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+    if (pipe_handle == INVALID_HANDLE_VALUE)
+        return;
+
+    //service_trace("Starting...");
+    if (unicode)
+    {
+        res = StartServiceCtrlDispatcherW(servtblW);
+        service_ok(res, "StartServiceCtrlDispatcherW failed: %lu\n", GetLastError());
+    }
+    else
+    {
+        res = StartServiceCtrlDispatcherA(servtblA);
+        service_ok(res, "StartServiceCtrlDispatcherA failed: %lu\n", GetLastError());
+    }
+
+    CloseHandle(pipe_handle);
+}
+
+static SC_HANDLE register_service(PCWSTR extra_args)
+{
+    WCHAR service_cmd[MAX_PATH+150];
+    SC_HANDLE service;
+
+    GetModuleFileNameW(NULL, service_cmd, MAX_PATH);
+
+    StringCbCatW(service_cmd, sizeof(service_cmd), L" ServiceArgs ");
+    StringCbCatW(service_cmd, sizeof(service_cmd), service_nameW);
+    StringCbCatW(service_cmd, sizeof(service_cmd), extra_args);
+
+    trace("service_cmd \"%ls\"\n", service_cmd);
+
+    service = CreateServiceW(scm_handle, service_nameW, service_nameW, GENERIC_ALL,
+                             SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE,
+                             service_cmd, NULL, NULL, NULL, NULL, NULL);
+    if (!service && GetLastError() == ERROR_ACCESS_DENIED)
+    {
+        skip("Not enough access right to create service\n");
+        return NULL;
+    }
+
+    ok(service != NULL, "CreateService failed: %lu\n", GetLastError());
+    return service;
+}
+
+static DWORD WINAPI pipe_thread(void *arg)
+{
+    char buf[512];
+    DWORD read;
+    BOOL res;
+
+    res = ConnectNamedPipe(pipe_handle, NULL);
+    ok(res || GetLastError() == ERROR_PIPE_CONNECTED, "ConnectNamedPipe failed: %lu\n", GetLastError());
+
+    while (1)
+    {
+        res = ReadFile(pipe_handle, buf, sizeof(buf), &read, NULL);
+        if (!res)
+        {
+            ok(GetLastError() == ERROR_BROKEN_PIPE || GetLastError() == ERROR_INVALID_HANDLE,
+               "ReadFile failed: %lu\n", GetLastError());
+            break;
+        }
+
+        if (!strncmp(buf, "TRACE:", 6))
+        {
+            trace("service trace: %s\n", buf+6);
+        }
+        else if (!strncmp(buf, "OK:", 3))
+        {
+            ok(1, "service: %s\n", buf+3);
+        }
+        else if (!strncmp(buf, "FAIL:", 5))
+        {
+            ok(0, "service: %s\n", buf+5);
+        }
+        else
+        {
+            ok(0, "malformed service message: %s\n", buf);
+        }
+    }
+
+    DisconnectNamedPipe(pipe_handle);
+    //trace("pipe disconnected\n");
+    return 0;
+}
+
+static void test_startA(SC_HANDLE service_handle, int service_argc, const char **service_argv)
+{
+    SERVICE_STATUS status;
+    BOOL res;
+
+    res = StartServiceA(service_handle, service_argc, service_argv);
+    ok(res, "StartService failed: %lu\n", GetLastError());
+    if (!res)
+        return;
+
+    do
+    {
+        Sleep(100);
+        ZeroMemory(&status, sizeof(status));
+        res = QueryServiceStatus(service_handle, &status);
+    } while (res && status.dwCurrentState != SERVICE_STOPPED);
+    ok(res, "QueryServiceStatus failed: %lu\n", GetLastError());
+    ok(status.dwCurrentState == SERVICE_STOPPED, "status.dwCurrentState = %lx\n", status.dwCurrentState);
+}
+
+static void test_startW(SC_HANDLE service_handle, int service_argc, const WCHAR **service_argv)
+{
+    SERVICE_STATUS status;
+    BOOL res;
+
+    res = StartServiceW(service_handle, service_argc, service_argv);
+    ok(res, "StartService failed: %lu\n", GetLastError());
+    if (!res)
+        return;
+
+    do
+    {
+        Sleep(100);
+        ZeroMemory(&status, sizeof(status));
+        res = QueryServiceStatus(service_handle, &status);
+    } while (res && status.dwCurrentState != SERVICE_STOPPED);
+    ok(res, "QueryServiceStatus failed: %lu\n", GetLastError());
+    ok(status.dwCurrentState == SERVICE_STOPPED, "status.dwCurrentState = %lx\n", status.dwCurrentState);
+}
+
+static void test_runner(BOOLEAN unicode, PCWSTR extra_args, int service_argc, void *service_argv)
+{
+    HANDLE thread;
+    SC_HANDLE service_handle;
+    BOOL res;
+
+    StringCbPrintfW(service_nameW, sizeof(service_nameW), L"WineTestService%lu", GetTickCount());
+    WideCharToMultiByte(CP_ACP, 0, service_nameW, -1, service_nameA, _countof(service_nameA), NULL, NULL);
+    //trace("service_name: %ls\n", service_nameW);
+    StringCbPrintfW(named_pipe_name, sizeof(named_pipe_name), L"\\\\.\\pipe\\%ls_pipe", service_nameW);
+
+    pipe_handle = CreateNamedPipeW(named_pipe_name, PIPE_ACCESS_INBOUND,
+                                   PIPE_TYPE_MESSAGE|PIPE_READMODE_MESSAGE|PIPE_WAIT, 10, 2048, 2048, 10000, NULL);
+    ok(pipe_handle != INVALID_HANDLE_VALUE, "CreateNamedPipe failed: %lu\n", GetLastError());
+    if (pipe_handle == INVALID_HANDLE_VALUE)
+        return;
+
+    thread = CreateThread(NULL, 0, pipe_thread, NULL, 0, NULL);
+    ok(thread != NULL, "CreateThread failed: %lu\n", GetLastError());
+    if (!thread)
+        return;
+
+    service_handle = register_service(extra_args);
+    if (!service_handle)
+        return;
+
+    //trace("starting...\n");
+
+    if (unicode)
+        test_startW(service_handle, service_argc, service_argv);
+    else
+        test_startA(service_handle, service_argc, service_argv);
+
+    res = DeleteService(service_handle);
+    ok(res, "DeleteService failed: %lu\n", GetLastError());
+
+    CloseServiceHandle(service_handle);
+
+    ok(WaitForSingleObject(thread, 10000) == WAIT_OBJECT_0, "Timeout waiting for thread\n");
+    CloseHandle(thread);
+    CloseHandle(pipe_handle);
+}
+
+START_TEST(ServiceArgs)
+{
+    argc = winetest_get_mainargs(&argv);
+
+    scm_handle = OpenSCManagerW(NULL, NULL, GENERIC_ALL);
+    ok(scm_handle != NULL, "OpenSCManager failed: %lu\n", GetLastError());
+    if (!scm_handle)
+    {
+        skip("Failed to open service control manager. Skipping test\n");
+        return;
+    }
+
+    if (argc < 3)
+    {
+        char *service_argvA[10];
+        WCHAR *service_argvW[10];
+
+        test_runner(FALSE, L" A", 0, NULL);
+        test_runner(FALSE, L" W", 0, NULL);
+        test_runner(TRUE, L" A", 0, NULL);
+        test_runner(TRUE, L" W", 0, NULL);
+
+        service_argvA[0] = "x";
+        service_argvW[0] = L"x";
+        test_runner(FALSE, L" A x", 1, service_argvA);
+        test_runner(FALSE, L" W x", 1, service_argvA);
+        test_runner(TRUE, L" A x", 1, service_argvW);
+        test_runner(TRUE, L" W x", 1, service_argvW);
+
+        service_argvA[1] = "y";
+        service_argvW[1] = L"y";
+        test_runner(FALSE, L" A x y", 2, service_argvA);
+        test_runner(FALSE, L" W x y", 2, service_argvA);
+        test_runner(TRUE, L" A x y", 2, service_argvW);
+        test_runner(TRUE, L" W x y", 2, service_argvW);
+
+        service_argvA[0] = "ab";
+        service_argvW[0] = L"ab";
+        test_runner(FALSE, L" A ab y", 2, service_argvA);
+        test_runner(FALSE, L" W ab y", 2, service_argvA);
+        test_runner(TRUE, L" A ab y", 2, service_argvW);
+        test_runner(TRUE, L" W ab y", 2, service_argvW);
+
+        service_argvA[0] = "abc";
+        service_argvW[0] = L"abc";
+        test_runner(FALSE, L" A abc y", 2, service_argvA);
+        test_runner(FALSE, L" W abc y", 2, service_argvA);
+        test_runner(TRUE, L" A abc y", 2, service_argvW);
+        test_runner(TRUE, L" W abc y", 2, service_argvW);
+
+        service_argvA[0] = "abcd";
+        service_argvW[0] = L"abcd";
+        test_runner(FALSE, L" A abcd y", 2, service_argvA);
+        test_runner(FALSE, L" W abcd y", 2, service_argvA);
+        test_runner(TRUE, L" A abcd y", 2, service_argvW);
+        test_runner(TRUE, L" W abcd y", 2, service_argvW);
+
+        service_argvA[0] = "abcde";
+        service_argvW[0] = L"abcde";
+        test_runner(FALSE, L" A abcde y", 2, service_argvA);
+        test_runner(FALSE, L" W abcde y", 2, service_argvA);
+        test_runner(TRUE, L" A abcde y", 2, service_argvW);
+        test_runner(TRUE, L" W abcde y", 2, service_argvW);
+
+        service_argvA[0] = "abcdef";
+        service_argvW[0] = L"abcdef";
+        test_runner(FALSE, L" A abcdef y", 2, service_argvA);
+        test_runner(FALSE, L" W abcdef y", 2, service_argvA);
+        test_runner(TRUE, L" A abcdef y", 2, service_argvW);
+        test_runner(TRUE, L" W abcdef y", 2, service_argvW);
+
+        service_argvA[0] = "abcdefg";
+        service_argvW[0] = L"abcdefg";
+        test_runner(FALSE, L" A abcdefg y", 2, service_argvA);
+        test_runner(FALSE, L" W abcdefg y", 2, service_argvA);
+        test_runner(TRUE, L" A abcdefg y", 2, service_argvW);
+        test_runner(TRUE, L" W abcdefg y", 2, service_argvW);
+
+        service_argvA[0] = "";
+        service_argvW[0] = L"";
+        test_runner(FALSE, L" A \"\" y", 2, service_argvA);
+        test_runner(FALSE, L" W \"\" y", 2, service_argvA);
+        test_runner(TRUE, L" A \"\" y", 2, service_argvW);
+        test_runner(TRUE, L" W \"\" y", 2, service_argvW);
+    }
+    else
+    {
+        strcpy(service_nameA, argv[2]);
+        MultiByteToWideChar(CP_ACP, 0, service_nameA, -1, service_nameW, _countof(service_nameW));
+        StringCbPrintfW(named_pipe_name, sizeof(named_pipe_name), L"\\\\.\\pipe\\%ls_pipe", service_nameW);
+        if (!strcmp(argv[3], "A"))
+            service_process(FALSE);
+        else
+            service_process(TRUE);
+    }
+
+    CloseServiceHandle(scm_handle);
+}
index 84fd087..cbc98ac 100644 (file)
@@ -16,6 +16,7 @@ extern void func_RegQueryInfoKey(void);
 extern void func_RegQueryValueExW(void);
 extern void func_RtlEncryptMemory(void);
 extern void func_SaferIdentifyLevel(void);
 extern void func_RegQueryValueExW(void);
 extern void func_RtlEncryptMemory(void);
 extern void func_SaferIdentifyLevel(void);
+extern void func_ServiceArgs(void);
 
 const struct test winetest_testlist[] =
 {
 
 const struct test winetest_testlist[] =
 {
@@ -32,7 +33,7 @@ const struct test winetest_testlist[] =
     { "RegQueryValueExW", func_RegQueryValueExW },
     { "RtlEncryptMemory", func_RtlEncryptMemory },
     { "SaferIdentifyLevel", func_SaferIdentifyLevel },
     { "RegQueryValueExW", func_RegQueryValueExW },
     { "RtlEncryptMemory", func_RtlEncryptMemory },
     { "SaferIdentifyLevel", func_SaferIdentifyLevel },
-
+    { "ServiceArgs", func_ServiceArgs },
     { 0, 0 }
 };
 
     { 0, 0 }
 };