| /* |
| * Copyright 2012 Jacek Caban for CodeWeavers |
| * |
| * 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, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
| */ |
| |
| #include <windows.h> |
| #include <stdio.h> |
| |
| #include "wine/test.h" |
| |
| static SERVICE_STATUS_HANDLE (WINAPI *pRegisterServiceCtrlHandlerExA)(LPCSTR,LPHANDLER_FUNCTION_EX,LPVOID); |
| |
| static HANDLE pipe_handle = INVALID_HANDLE_VALUE; |
| static char service_name[100], named_pipe_name[100]; |
| static SERVICE_STATUS_HANDLE service_handle; |
| |
| /* Service process global variables */ |
| static HANDLE service_stop_event; |
| |
| static void send_msg(const char *type, const char *msg) |
| { |
| DWORD written = 0; |
| char buf[512]; |
| |
| sprintf(buf, "%s:%s", type, msg); |
| WriteFile(pipe_handle, buf, strlen(buf)+1, &written, NULL); |
| } |
| |
| static inline void service_trace(const char *msg) |
| { |
| send_msg("TRACE", msg); |
| } |
| |
| static inline void service_event(const char *event) |
| { |
| send_msg("EVENT", event); |
| } |
| |
| static void service_ok(int cnd, const char *msg, ...) |
| { |
| va_list valist; |
| char buf[512]; |
| |
| va_start(valist, msg); |
| vsprintf(buf, msg, valist); |
| va_end(valist); |
| |
| send_msg(cnd ? "OK" : "FAIL", buf); |
| } |
| |
| static DWORD WINAPI service_handler(DWORD ctrl, DWORD event_type, void *event_data, void *context) |
| { |
| 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: |
| case SERVICE_CONTROL_SHUTDOWN: |
| service_event("STOP"); |
| status.dwCurrentState = SERVICE_STOP_PENDING; |
| status.dwControlsAccepted = 0; |
| SetServiceStatus(service_handle, &status); |
| SetEvent(service_stop_event); |
| return NO_ERROR; |
| default: |
| status.dwCurrentState = SERVICE_RUNNING; |
| SetServiceStatus( service_handle, &status ); |
| return NO_ERROR; |
| } |
| } |
| |
| static void WINAPI service_main(DWORD argc, char **argv) |
| { |
| SERVICE_STATUS status; |
| BOOL res; |
| |
| service_handle = pRegisterServiceCtrlHandlerExA(service_name, service_handler, NULL); |
| service_ok(service_handle != NULL, "RegisterServiceCtrlHandlerEx failed: %u\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: %u", GetLastError()); |
| |
| service_event("RUNNING"); |
| |
| WaitForSingleObject(service_stop_event, INFINITE); |
| |
| status.dwCurrentState = SERVICE_STOPPED; |
| status.dwControlsAccepted = 0; |
| res = SetServiceStatus(service_handle, &status); |
| service_ok(res, "SetServiceStatus(SERVICE_STOPPED) failed: %u", GetLastError()); |
| } |
| |
| static void service_process(void) |
| { |
| BOOL res; |
| |
| static const SERVICE_TABLE_ENTRYA servtbl[] = { |
| {service_name, service_main}, |
| {NULL, NULL} |
| }; |
| |
| res = WaitNamedPipeA(named_pipe_name, NMPWAIT_USE_DEFAULT_WAIT); |
| if(!res) |
| return; |
| |
| pipe_handle = CreateFileA(named_pipe_name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); |
| if(pipe_handle == INVALID_HANDLE_VALUE) |
| return; |
| |
| service_trace("Starting..."); |
| |
| service_stop_event = CreateEventA(NULL, TRUE, FALSE, NULL); |
| service_ok(service_stop_event != NULL, "Could not create event: %u\n", GetLastError()); |
| if(!service_stop_event) |
| return; |
| |
| res = StartServiceCtrlDispatcherA(servtbl); |
| service_ok(res, "StartServiceCtrlDispatcher failed: %u\n", GetLastError()); |
| |
| CloseHandle(service_stop_event); |
| } |
| |
| /* Test process global variables */ |
| static SC_HANDLE scm_handle; |
| |
| static char current_event[32]; |
| static HANDLE event_handle = INVALID_HANDLE_VALUE; |
| static CRITICAL_SECTION event_cs; |
| |
| static SC_HANDLE register_service(void) |
| { |
| char service_cmd[MAX_PATH+150], *ptr; |
| SC_HANDLE service; |
| |
| ptr = service_cmd + GetModuleFileNameA(NULL, service_cmd, MAX_PATH); |
| |
| /* If the file doesn't exist, assume we're using built-in exe and append .so to the path */ |
| if(GetFileAttributesA(service_cmd) == INVALID_FILE_ATTRIBUTES) { |
| strcpy(ptr, ".so"); |
| ptr += 3; |
| } |
| |
| strcpy(ptr, " service "); |
| ptr += strlen(ptr); |
| strcpy(ptr, service_name); |
| |
| trace("service_cmd \"%s\"\n", service_cmd); |
| |
| service = CreateServiceA(scm_handle, service_name, service_name, 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: %u\n", GetLastError()); |
| return service; |
| } |
| |
| static void expect_event(const char *event_name) |
| { |
| char evt[32]; |
| DWORD res; |
| |
| trace("waiting for %s\n", event_name); |
| |
| res = WaitForSingleObject(event_handle, 30000); |
| ok(res == WAIT_OBJECT_0, "WaitForSingleObject failed: %u\n", res); |
| if(res != WAIT_OBJECT_0) |
| return; |
| |
| EnterCriticalSection(&event_cs); |
| strcpy(evt, current_event); |
| *current_event = 0; |
| LeaveCriticalSection(&event_cs); |
| |
| ok(!strcmp(evt, event_name), "Unexpected event: %s, expected %s\n", evt, event_name); |
| } |
| |
| static DWORD WINAPI pipe_thread(void *arg) |
| { |
| char buf[257], *ptr; |
| DWORD read; |
| BOOL res; |
| |
| res = ConnectNamedPipe(pipe_handle, NULL); |
| ok(res || GetLastError() == ERROR_PIPE_CONNECTED, "ConnectNamedPipe failed: %u\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: %u\n", GetLastError()); |
| break; |
| } |
| |
| for(ptr = buf; ptr < buf+read; ptr += strlen(ptr)+1) { |
| if(!strncmp(ptr, "TRACE:", 6)) { |
| trace("service trace: %s\n", ptr+6); |
| }else if(!strncmp(ptr, "OK:", 3)) { |
| ok(1, "service: %s\n", ptr+3); |
| }else if(!strncmp(ptr, "FAIL:", 3)) { |
| ok(0, "service: %s\n", ptr+5); |
| }else if(!strncmp(ptr, "EVENT:", 6)) { |
| trace("service event: %s\n", ptr+6); |
| EnterCriticalSection(&event_cs); |
| ok(!current_event[0], "event %s still queued\n", current_event); |
| strcpy(current_event, ptr+6); |
| LeaveCriticalSection(&event_cs); |
| SetEvent(event_handle); |
| }else { |
| ok(0, "malformed service message: %s\n", ptr); |
| } |
| } |
| } |
| |
| DisconnectNamedPipe(pipe_handle); |
| trace("pipe disconnected\n"); |
| return 0; |
| } |
| |
| static void test_process(void) |
| { |
| SC_HANDLE service_handle; |
| SERVICE_STATUS status; |
| HANDLE thread; |
| BOOL res; |
| |
| sprintf(service_name, "WineTestService%d", GetTickCount()); |
| trace("service_name: %s\n", service_name); |
| sprintf(named_pipe_name, "\\\\.\\pipe\\%s_pipe", service_name); |
| |
| pipe_handle = CreateNamedPipeA(named_pipe_name, PIPE_ACCESS_INBOUND, |
| PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT, 10, 2048, 2048, 10000, NULL); |
| ok(pipe_handle != INVALID_HANDLE_VALUE, "CreateNamedPipe failed: %u\n", GetLastError()); |
| if(pipe_handle == INVALID_HANDLE_VALUE) |
| return; |
| |
| InitializeCriticalSection(&event_cs); |
| event_handle = CreateEventA(NULL, FALSE, FALSE, NULL); |
| ok(event_handle != INVALID_HANDLE_VALUE, "CreateEvent failed: %u\n", GetLastError()); |
| if(event_handle == INVALID_HANDLE_VALUE) |
| return; |
| |
| thread = CreateThread(NULL, 0, pipe_thread, NULL, 0, NULL); |
| ok(thread != NULL, "CreateThread failed: %u\n", GetLastError()); |
| if(!thread) |
| return; |
| |
| service_handle = register_service(); |
| if(!service_handle) |
| return; |
| |
| trace("starting...\n"); |
| res = StartServiceA(service_handle, 0, NULL); |
| ok(res, "StartService failed: %u\n", GetLastError()); |
| if(!res) |
| return; |
| expect_event("RUNNING"); |
| |
| res = ControlService(service_handle, SERVICE_CONTROL_STOP, &status); |
| ok(res, "ControlService failed: %u\n", GetLastError()); |
| expect_event("STOP"); |
| |
| res = DeleteService(service_handle); |
| ok(res, "DeleteService failed: %u\n", GetLastError()); |
| |
| CloseServiceHandle(service_handle); |
| } |
| |
| START_TEST(service) |
| { |
| char **argv; |
| int argc; |
| |
| pRegisterServiceCtrlHandlerExA = (void*)GetProcAddress(GetModuleHandleA("advapi32.dll"), "RegisterServiceCtrlHandlerExA"); |
| if(!pRegisterServiceCtrlHandlerExA) { |
| win_skip("RegisterServiceCtrlHandlerExA not available, skipping tests\n"); |
| return; |
| } |
| |
| argc = winetest_get_mainargs(&argv); |
| |
| scm_handle = OpenSCManagerA(NULL, NULL, GENERIC_ALL); |
| ok(scm_handle != NULL, "OpenSCManager failed: %u\n", GetLastError()); |
| |
| if(argc < 3) { |
| test_process(); |
| }else { |
| strcpy(service_name, argv[2]); |
| sprintf(named_pipe_name, "\\\\.\\pipe\\%s_pipe", service_name); |
| |
| service_process(); |
| } |
| |
| CloseHandle(event_handle); |
| CloseHandle(pipe_handle); |
| CloseServiceHandle(scm_handle); |
| } |