/* * RPCSS named pipe server * * Copyright (C) 2002 Greg Turner * * 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 #include "rpcss.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(ole); static HANDLE np_server_end; static HANDLE np_server_work_event; static CRITICAL_SECTION np_server_cs; static LONG srv_thread_count; static BOOL server_live; LONG RPCSS_SrvThreadCount(void) { return srv_thread_count; } BOOL RPCSS_UnBecomePipeServer(void) { BOOL rslt = TRUE; DWORD wait_result; HANDLE master_mutex = RPCSS_GetMasterMutex(); WINE_TRACE("\n"); wait_result = WaitForSingleObject(master_mutex, MASTER_MUTEX_TIMEOUT); switch (wait_result) { case WAIT_ABANDONED: /* ? */ case WAIT_OBJECT_0: /* we have ownership */ break; case WAIT_FAILED: case WAIT_TIMEOUT: default: WINE_ERR("This should never happen: couldn't enter mutex.\n"); /* this is totally unacceptable. no graceful out exists */ assert(FALSE); } /* now that we have the master mutex, we can safely stop listening on the pipe. Before we proceed, we do a final check that it's OK to shut down to ensure atomicity */ if (!RPCSS_ReadyToDie()) rslt = FALSE; else { WINE_TRACE("shutting down pipe.\n"); server_live = FALSE; if (!CloseHandle(np_server_end)) WINE_WARN("Failed to close named pipe.\n"); if (!CloseHandle(np_server_work_event)) WINE_WARN("Failed to close the event handle.\n"); DeleteCriticalSection(&np_server_cs); } if (!ReleaseMutex(master_mutex)) WINE_ERR("Unable to leave master mutex!??\n"); return rslt; } static void RPCSS_ServerProcessRANMessage(PRPCSS_NP_MESSAGE pMsg, PRPCSS_NP_REPLY pReply) { WINE_TRACE("\n"); /* we do absolutely nothing, but on the server end, the lazy timeout is reset as a result of our connection. */ RPCSS_SetMaxLazyTimeout(pMsg->message.ranmsg.timeout); RPCSS_SetLazyTimeRemaining(RPCSS_GetMaxLazyTimeout()); pReply->as_uint = 0; } static void RPCSS_ServerProcessREGISTEREPMessage(PRPCSS_NP_MESSAGE pMsg, PRPCSS_NP_REPLY pReply, char *vardata) { WINE_TRACE("\n"); RPCSS_RegisterRpcEndpoints( pMsg->message.registerepmsg.iface, pMsg->message.registerepmsg.object_count, pMsg->message.registerepmsg.binding_count, pMsg->message.registerepmsg.no_replace, vardata, pMsg->vardata_payload_size ); /* no reply */ pReply->as_uint = 0; } static void RPCSS_ServerProcessUNREGISTEREPMessage(PRPCSS_NP_MESSAGE pMsg, PRPCSS_NP_REPLY pReply, char *vardata) { WINE_TRACE("\n"); RPCSS_UnregisterRpcEndpoints( pMsg->message.unregisterepmsg.iface, pMsg->message.unregisterepmsg.object_count, pMsg->message.unregisterepmsg.binding_count, vardata, pMsg->vardata_payload_size ); /* no reply */ pReply->as_uint = 0; } static void RPCSS_ServerProcessRESOLVEEPMessage(PRPCSS_NP_MESSAGE pMsg, PRPCSS_NP_REPLY pReply, char *vardata) { WINE_TRACE("\n"); /* for now, reply is placed into *pReply.as_string, on success, by RPCSS_ResolveRpcEndpoints */ ZeroMemory(pReply->as_string, MAX_RPCSS_NP_REPLY_STRING_LEN); RPCSS_ResolveRpcEndpoints( pMsg->message.resolveepmsg.iface, pMsg->message.resolveepmsg.object, vardata, pReply->as_string ); } static void RPCSS_ServerProcessMessage(PRPCSS_NP_MESSAGE pMsg, PRPCSS_NP_REPLY pReply, char *vardata) { WINE_TRACE("\n"); switch (pMsg->message_type) { case RPCSS_NP_MESSAGE_TYPEID_RANMSG: RPCSS_ServerProcessRANMessage(pMsg, pReply); break; case RPCSS_NP_MESSAGE_TYPEID_REGISTEREPMSG: RPCSS_ServerProcessREGISTEREPMessage(pMsg, pReply, vardata); break; case RPCSS_NP_MESSAGE_TYPEID_UNREGISTEREPMSG: RPCSS_ServerProcessUNREGISTEREPMessage(pMsg, pReply, vardata); break; case RPCSS_NP_MESSAGE_TYPEID_RESOLVEEPMSG: RPCSS_ServerProcessRESOLVEEPMessage(pMsg, pReply, vardata); break; default: WINE_ERR("Message type unknown!! No action taken.\n"); } } /* each message gets its own thread. this is it. */ static VOID HandlerThread(LPVOID lpvPipeHandle) { RPCSS_NP_MESSAGE msg, vardata_payload_msg; char *c, *vardata = NULL; RPCSS_NP_REPLY reply; DWORD bytesread, written; BOOL success, had_payload = FALSE; HANDLE mypipe; mypipe = (HANDLE) lpvPipeHandle; WINE_TRACE("mypipe: %p\n", mypipe); success = ReadFile( mypipe, /* pipe handle */ (char *) &msg, /* message buffer */ sizeof(RPCSS_NP_MESSAGE), /* message buffer size */ &bytesread, /* receives number of bytes read */ NULL /* not overlapped */ ); if (msg.vardata_payload_size) { had_payload = TRUE; /* this fudge space allows us not to worry about exceeding the buffer space on the last read */ vardata = LocalAlloc(LPTR, (msg.vardata_payload_size) + VARDATA_PAYLOAD_BYTES); if (!vardata) { WINE_ERR("vardata memory allocation failure.\n"); success = FALSE; } else { for ( c = vardata; (c - vardata) < msg.vardata_payload_size; c += VARDATA_PAYLOAD_BYTES) { success = ReadFile( mypipe, (char *) &vardata_payload_msg, sizeof(RPCSS_NP_MESSAGE), &bytesread, NULL ); if ( (!success) || (bytesread != sizeof(RPCSS_NP_MESSAGE)) || (vardata_payload_msg.message_type != RPCSS_NP_MESSAGE_TYPEID_VARDATAPAYLOADMSG) ) { WINE_ERR("vardata payload read failure! (s=%s,br=%d,mt=%u,mt_exp=%u\n", success ? "TRUE" : "FALSE", bytesread, vardata_payload_msg.message_type, RPCSS_NP_MESSAGE_TYPEID_VARDATAPAYLOADMSG); success = FALSE; break; } CopyMemory(c, vardata_payload_msg.message.vardatapayloadmsg.payload, VARDATA_PAYLOAD_BYTES); WINE_TRACE("payload read.\n"); } } } if (success && (bytesread == sizeof(RPCSS_NP_MESSAGE))) { WINE_TRACE("read success.\n"); /* process the message and send a reply, serializing requests. */ EnterCriticalSection(&np_server_cs); WINE_TRACE("processing message.\n"); RPCSS_ServerProcessMessage(&msg, &reply, vardata); LeaveCriticalSection(&np_server_cs); if (had_payload) LocalFree(vardata); WINE_TRACE("message processed, sending reply....\n"); success = WriteFile( mypipe, /* pipe handle */ (char *) &reply, /* reply buffer */ sizeof(RPCSS_NP_REPLY), /* reply buffer size */ &written, /* receives number of bytes written */ NULL /* not overlapped */ ); if ( (!success) || (written != sizeof(RPCSS_NP_REPLY)) ) WINE_WARN("Message reply failed. (success=%d, br=%d)\n", success, written); else WINE_TRACE("Reply sent successfully.\n"); } else WINE_WARN("Message receipt failed.\n"); FlushFileBuffers(mypipe); DisconnectNamedPipe(mypipe); CloseHandle(mypipe); InterlockedDecrement(&srv_thread_count); } static VOID NPMainWorkThread(LPVOID ignored) { BOOL connected; HANDLE hthread, master_mutex = RPCSS_GetMasterMutex(); DWORD threadid, wait_result; WINE_TRACE("\n"); while (server_live) { connected = ConnectNamedPipe(np_server_end, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED); if (connected) { /* is "work" the act of connecting pipes, or the act of serving requests successfully? for now I will make it the former. */ if (!SetEvent(np_server_work_event)) WINE_WARN("failed to signal np_server_work_event.\n"); /* Create a thread for this client. */ InterlockedIncrement(&srv_thread_count); hthread = CreateThread( NULL, /* no security attribute */ 0, /* default stack size */ (LPTHREAD_START_ROUTINE) HandlerThread, (LPVOID) np_server_end, /* thread parameter */ 0, /* not suspended */ &threadid /* returns thread ID (not used) */ ); if (hthread) { WINE_TRACE("Spawned handler thread: %p\n", hthread); CloseHandle(hthread); /* for safety's sake, hold the mutex while we switch the pipe */ wait_result = WaitForSingleObject(master_mutex, MASTER_MUTEX_TIMEOUT); switch (wait_result) { case WAIT_ABANDONED: /* ? */ case WAIT_OBJECT_0: /* we have ownership */ break; case WAIT_FAILED: case WAIT_TIMEOUT: default: /* huh? */ wait_result = WAIT_FAILED; } if (wait_result == WAIT_FAILED) { WINE_ERR("Couldn't enter master mutex. Expect prolems.\n"); } else { /* now create a new named pipe instance to listen on */ np_server_end = CreateNamedPipe( NAME_RPCSS_NAMED_PIPE, /* pipe name */ PIPE_ACCESS_DUPLEX, /* pipe open mode */ PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, /* pipe-specific modes */ PIPE_UNLIMITED_INSTANCES, /* maximum instances */ sizeof(RPCSS_NP_REPLY), /* output buffer size */ sizeof(RPCSS_NP_MESSAGE), /* input buffer size */ 2000, /* time-out interval */ NULL /* SD */ ); if (np_server_end == INVALID_HANDLE_VALUE) { WINE_ERR("Failed to recreate named pipe!\n"); /* not sure what to do? */ assert(FALSE); } if (!ReleaseMutex(master_mutex)) WINE_ERR("Uh oh. Couldn't leave master mutex. Expect deadlock.\n"); } } else { WINE_ERR("Failed to spawn handler thread!\n"); DisconnectNamedPipe(np_server_end); InterlockedDecrement(&srv_thread_count); } } } WINE_TRACE("Server thread shutdown.\n"); } static HANDLE RPCSS_NPConnect(void) { HANDLE the_pipe = NULL; DWORD dwmode, wait_result; HANDLE master_mutex = RPCSS_GetMasterMutex(); WINE_TRACE("\n"); while (TRUE) { wait_result = WaitForSingleObject(master_mutex, MASTER_MUTEX_TIMEOUT); switch (wait_result) { case WAIT_ABANDONED: case WAIT_OBJECT_0: break; case WAIT_FAILED: case WAIT_TIMEOUT: default: WINE_ERR("This should never happen: couldn't enter mutex.\n"); return NULL; } /* try to open the client side of the named pipe. */ the_pipe = CreateFileA( NAME_RPCSS_NAMED_PIPE, /* pipe name */ GENERIC_READ | GENERIC_WRITE, /* r/w access */ 0, /* no sharing */ NULL, /* no security attributes */ OPEN_EXISTING, /* open an existing pipe */ 0, /* default attributes */ NULL /* no template file */ ); if (the_pipe != INVALID_HANDLE_VALUE) break; if (GetLastError() != ERROR_PIPE_BUSY) { WINE_WARN("Unable to open named pipe %s (assuming unavailable).\n", wine_dbgstr_a(NAME_RPCSS_NAMED_PIPE)); the_pipe = NULL; break; } WINE_WARN("Named pipe busy (will wait)\n"); if (!ReleaseMutex(master_mutex)) WINE_ERR("Failed to release master mutex. Expect deadlock.\n"); /* wait for the named pipe. We are only willing to wait only 5 seconds. It should be available /very/ soon. */ if (! WaitNamedPipeA(NAME_RPCSS_NAMED_PIPE, MASTER_MUTEX_WAITNAMEDPIPE_TIMEOUT)) { WINE_ERR("Named pipe unavailable after waiting. Something is probably wrong.\n"); return NULL; } } if (the_pipe) { dwmode = PIPE_READMODE_MESSAGE; /* SetNamedPipeHandleState not implemented ATM, but still seems to work somehow. */ if (! SetNamedPipeHandleState(the_pipe, &dwmode, NULL, NULL)) WINE_WARN("Failed to set pipe handle state\n"); } if (!ReleaseMutex(master_mutex)) WINE_ERR("Uh oh, failed to leave the RPC Master Mutex!\n"); return the_pipe; } static BOOL RPCSS_SendReceiveNPMsg(HANDLE np, PRPCSS_NP_MESSAGE msg, PRPCSS_NP_REPLY reply) { DWORD count; WINE_TRACE("(np == %p, msg == %p, reply == %p)\n", np, msg, reply); if (! WriteFile(np, msg, sizeof(RPCSS_NP_MESSAGE), &count, NULL)) { WINE_ERR("write failed.\n"); return FALSE; } if (count != sizeof(RPCSS_NP_MESSAGE)) { WINE_ERR("write count mismatch.\n"); return FALSE; } if (! ReadFile(np, reply, sizeof(RPCSS_NP_REPLY), &count, NULL)) { WINE_ERR("read failed.\n"); return FALSE; } if (count != sizeof(RPCSS_NP_REPLY)) { WINE_ERR("read count mismatch, got %d.\n", count); return FALSE; } /* message execution was successful */ return TRUE; } BOOL RPCSS_BecomePipeServer(void) { RPCSS_NP_MESSAGE msg; RPCSS_NP_REPLY reply; BOOL rslt = TRUE; HANDLE client_handle, hthread, master_mutex = RPCSS_GetMasterMutex(); DWORD threadid, wait_result; WINE_TRACE("\n"); wait_result = WaitForSingleObject(master_mutex, MASTER_MUTEX_TIMEOUT); switch (wait_result) { case WAIT_ABANDONED: /* ? */ case WAIT_OBJECT_0: /* we have ownership */ break; case WAIT_FAILED: case WAIT_TIMEOUT: default: WINE_ERR("Couldn't enter master mutex.\n"); return FALSE; } /* now we have the master mutex. during this time we will * * o check if an rpcss already listens on the pipe. If so, * we will tell it we were invoked, which will cause the * other end to update its timeouts. After, we just return * false. * * o otherwise, we establish the pipe for ourselves and get * ready to listen on it */ if ((client_handle = RPCSS_NPConnect()) != NULL) { msg.message_type = RPCSS_NP_MESSAGE_TYPEID_RANMSG; msg.message.ranmsg.timeout = RPCSS_GetMaxLazyTimeout(); msg.vardata_payload_size = 0; if (!RPCSS_SendReceiveNPMsg(client_handle, &msg, &reply)) WINE_ERR("Something is amiss: RPC_SendReceive failed.\n"); rslt = FALSE; } if (rslt) { np_server_work_event = CreateEventA(NULL, FALSE, FALSE, "RpcNpServerWorkEvent"); if (np_server_work_event == NULL) { /* dunno what we can do then */ WINE_ERR("Unable to create the np_server_work_event\n"); assert(FALSE); } InitializeCriticalSection(&np_server_cs); np_server_end = CreateNamedPipe( NAME_RPCSS_NAMED_PIPE, /* pipe name */ PIPE_ACCESS_DUPLEX, /* pipe open mode */ PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, /* pipe-specific modes */ PIPE_UNLIMITED_INSTANCES, /* maximum number of instances */ sizeof(RPCSS_NP_REPLY), /* output buffer size */ sizeof(RPCSS_NP_MESSAGE), /* input buffer size */ 2000, /* time-out interval */ NULL /* SD */ ); if (np_server_end == INVALID_HANDLE_VALUE) { WINE_ERR("Failed to create named pipe!\n"); DeleteCriticalSection(&np_server_cs); if (!CloseHandle(np_server_work_event)) /* we will leak the handle... */ WINE_WARN("Failed to close np_server_work_event handle!\n"); np_server_work_event = NULL; np_server_end = NULL; rslt = FALSE; } } server_live = rslt; if (rslt) { /* OK, now spawn the (single) server thread */ hthread = CreateThread( NULL, /* no security attribute */ 0, /* default stack size */ (LPTHREAD_START_ROUTINE) NPMainWorkThread, NULL, /* thread parameter */ 0, /* not suspended */ &threadid /* returns thread ID (not used) */ ); if (hthread) { WINE_TRACE("Created server thread.\n"); CloseHandle(hthread); } else { WINE_ERR("Serious error: unable to create server thread!\n"); if (!CloseHandle(np_server_work_event)) /* we will leak the handle... */ WINE_WARN("Failed to close np_server_work_event handle!\n"); if (!CloseHandle(np_server_end)) /* we will leak the handle... */ WINE_WARN("Unable to close named pipe handle!\n"); DeleteCriticalSection(&np_server_cs); np_server_end = NULL; np_server_work_event = NULL; rslt = FALSE; server_live = FALSE; } } if (!ReleaseMutex(master_mutex)) WINE_ERR("Unable to leave master mutex!??\n"); return rslt; } BOOL RPCSS_NPDoWork(void) { DWORD waitresult = WaitForSingleObject(np_server_work_event, 1000); if (waitresult == WAIT_TIMEOUT) return FALSE; if (waitresult == WAIT_OBJECT_0) return TRUE; return FALSE; }