>From e88e9efe1b4f8d5c3d5ecc44327c6db268c23b29 Mon Sep 17 00:00:00 2001 From: Daniel Santos Date: Thu, 19 Jan 2012 04:01:01 -0600 Subject: Hack for in-process wine-server (a.k.a., "Dirty speed hack") MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="------------1.7.3.4" This is a multi-part message in MIME format. --------------1.7.3.4 Content-Type: text/plain; charset=UTF-8; format=fixed Content-Transfer-Encoding: 8bit --- dlls/ntdll/server.c | 154 ++++++++++++++++++++++++++++++++++++++++++++------- server/Makefile.in | 32 ++++++++++- server/fd.c | 21 +++++++ server/main.c | 17 +++++- server/object.h | 11 ++++ server/request.c | 131 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 343 insertions(+), 23 deletions(-) --------------1.7.3.4 Content-Type: text/x-patch; name="0001-Hack-for-in-process-wine-server-a.k.a.-Dirty-speed-hac.txt" Content-Transfer-Encoding: 8bit Content-Disposition: attachment; filename="0001-Hack-for-in-process-wine-server-a.k.a.-Dirty-speed-hac.txt" diff --git a/dlls/ntdll/server.c b/dlls/ntdll/server.c index 8a01d22..752e2f7 100644 --- a/dlls/ntdll/server.c +++ b/dlls/ntdll/server.c @@ -74,6 +74,36 @@ WINE_DEFAULT_DEBUG_CHANNEL(server); +/************************************************************************************************** + * In-Process Server (a.k.a., "dirty speed hack") Laundry List + * + * - libwineserver isn't built when --with-wine-tools is specified (and it should be, even though + * we don't want the wineserver executable). The make targets (currently in server/Makefile.in) + * should probably be moved to libs/libwineserver/Makefile.in or other autotools code modofied so + * it's built, even when the wineserver isn't. + * - If PE is 32 bit, it must run a 32 bit wineserver and visa versa. This can create problems if + * the WINEPREFIX is a different arch (32 vs 64). The solution is probably to thunk. + * - Examine/resolve signal handling differences between client & server code. + * - How should the wineserver thread behave when the main program terminates? Either way, catch + * it's termination at least properly shutdown the in-process wineserver. At current, auxiliary + * processes stick around and have to be manually killed after the main process/wineserver + * terminates. + * - (probably fixed on gnu/linux) Audit synchronization issues in server code. + * - Test (and fix) server syncrhonization on all polling implementations (server/fd.c) + * - Use more portable synchronization mechanism in new server code (currently, we're just directly + * including pthread.h and using a pthread mutex). + * - Get rid of copy & paste structs in request.c (__server_iovec, __SERVER_MAX_DATA and + * __server_request_info) + * - Are there ever any server calls made in a thread prior to the init_thread call? If so, they + * will be broken. + * - Get rid of all fatal_{,p}error calls made outside of initialization. + * - Properly document new functions & data. + * + * Potential enhancements + * - Add at least one alternate server calling convention to reduce memcpy calls. + * - Tweak in-proc server call to copy less data (if possible without the above). + *************************************************************************************************/ + /* Some versions of glibc don't define this */ #ifndef SCM_RIGHTS #define SCM_RIGHTS 1 @@ -102,6 +132,8 @@ static const enum cpu_type client_cpu = CPU_ARM; unsigned int server_cpus = 0; int is_wow64 = FALSE; +static void *libwineserver_handle; +void (*call_req_handler_inproc)(void *req) = 0; timeout_t server_start_time = 0; /* time of server startup */ @@ -281,16 +313,46 @@ static inline unsigned int wait_reply( struct __server_request_info *req ) unsigned int wine_server_call( void *req_ptr ) { struct __server_request_info * const req = req_ptr; - sigset_t old_set; - unsigned int ret; - pthread_sigmask( SIG_BLOCK, &server_block_set, &old_set ); - ret = send_request( req ); - if (!ret) ret = wait_reply( req ); - pthread_sigmask( SIG_SETMASK, &old_set, NULL ); - return ret; + if(call_req_handler_inproc && req->u.req.request_header.req != REQ_init_thread) + { + call_req_handler_inproc(req); + return req->u.reply.reply_header.error; + } + else + { + sigset_t old_set; + unsigned int ret; + + pthread_sigmask( SIG_BLOCK, &server_block_set, &old_set ); + ret = send_request( req ); + if (!ret) ret = wait_reply( req ); + pthread_sigmask( SIG_SETMASK, &old_set, NULL ); + return ret; + } } +#if 0 + unsigned int wine_server_call_pipe( void *req_ptr ) + { + struct __server_request_info * const req = req_ptr; + sigset_t old_set; + unsigned int ret; + + pthread_sigmask( SIG_BLOCK, &server_block_set, &old_set ); + ret = send_request( req ); + if (!ret) ret = wait_reply( req ); + pthread_sigmask( SIG_SETMASK, &old_set, NULL ); + return ret; + } + + unsigned int wine_server_call_inproc( void *req_ptr ) + { + struct __server_request_info * const req = req_ptr; + call_req_handler_inproc(req); + return req->u.reply.reply_header.error; + } +#endif /*********************************************************************** * server_enter_uninterrupted_section @@ -686,6 +748,11 @@ int server_pipe( int fd[2] ) return ret; } +/* Theory: start_server should only get called by one wine process (hopefully) + * so we don't have to screw with not loading the libwineserver.so in each + * process (therefore, screwing everythign up) + */ + /*********************************************************************** * start_server @@ -695,27 +762,69 @@ int server_pipe( int fd[2] ) static void start_server(void) { static int started; /* we only try once */ - char *argv[3]; + static char *argv[4]; static char wineserver[] = "server/wineserver"; static char debug[] = "-d"; + static char foreground[] = "-f"; if (!started) { - int status; - int pid = fork(); - if (pid == -1) fatal_perror( "fork" ); - if (!pid) + if(!getenv("DIRTYNASTYSPEEDHACK")) { + int status; + int pid = fork(); + if (pid == -1) fatal_perror( "fork" ); + if (!pid) + { + argv[0] = wineserver; + argv[1] = TRACE_ON(server) ? debug : NULL; + argv[2] = NULL; + wine_exec_wine_binary( argv[0], argv, getenv("WINESERVER") ); + fatal_error( "could not exec wineserver\n" ); + } + waitpid( pid, &status, 0 ); + status = WIFEXITED(status) ? WEXITSTATUS(status) : 1; + if (status == 2) return; /* server lock held by someone else, will retry later */ + if (status) exit(status); /* server failed */ + } + else + { + char errmsg[1024]; + void *(*wineserver_start_inproc)(void *); + pthread_t wineserver_main_thread; + struct timespec wait_time = {0, 500}; + void *wineserver_ret; + argv[0] = wineserver; - argv[1] = TRACE_ON(server) ? debug : NULL; - argv[2] = NULL; - wine_exec_wine_binary( argv[0], argv, getenv("WINESERVER") ); - fatal_error( "could not exec wineserver\n" ); + argv[1] = foreground; + argv[2] = TRACE_ON(server) ? debug : NULL; + argv[3] = NULL; + + fprintf(stderr, "Loading libwineserver...\n"); + + if(!(libwineserver_handle = wine_dlopen("libwineserver.so", RTLD_NOW, errmsg, sizeof(errmsg)))) { + fatal_error("could not load wineserver lib: %s", errmsg); + } + + if(!(wineserver_start_inproc = wine_dlsym(libwineserver_handle, "wineserver_start_inproc", errmsg, sizeof(errmsg)))) { + fatal_error("failed to find libwineserver entry point: %s", errmsg); + } + + if(!(call_req_handler_inproc = wine_dlsym(libwineserver_handle, "call_req_handler_inproc", errmsg, sizeof(errmsg)))) { + fatal_error("failed to find symbol call_req_handler_inproc in libwineserver: %s", errmsg); + } + + if(pthread_create(&wineserver_main_thread, NULL, wineserver_start_inproc, argv)) { + fatal_perror("pthread_create failed"); + } + + /* FIXME: needs better mechanism to make sure wineserver started (this just waits + * 500 mS and if the thread doesn't terminate, presumes all is good). */ + if(!pthread_timedjoin_np(wineserver_main_thread, &wineserver_ret, &wait_time)) { + fatal_error("wineserver thread exited :("); + } + fprintf(stderr, "libwineserver loaded\n"); } - waitpid( pid, &status, 0 ); - status = WIFEXITED(status) ? WEXITSTATUS(status) : 1; - if (status == 2) return; /* server lock held by someone else, will retry later */ - if (status) exit(status); /* server failed */ started = 1; } } @@ -1095,6 +1204,8 @@ size_t server_init_thread( void *entry_point ) req->wait_fd = ntdll_get_thread_data()->wait_fd[1]; req->debug_level = (TRACE_ON(server) != 0); req->cpu = client_cpu; + /* call_req_handler_inproc in server doesn't know how to handle this call properly, so it + * should always be called via the conventional call mechanism. */ ret = wine_server_call( req ); NtCurrentTeb()->ClientId.UniqueProcess = ULongToHandle(reply->pid); NtCurrentTeb()->ClientId.UniqueThread = ULongToHandle(reply->tid); @@ -1103,6 +1214,9 @@ size_t server_init_thread( void *entry_point ) server_cpus = reply->all_cpus; } SERVER_END_REQ; + /* If we've loaded the wineserver locally, then enable the wine_server_call_inproc for all + * future requests in this process. */ + /* if(call_req_handler_inproc) wine_server_call = wine_server_call_inproc; */ is_wow64 = !is_win64 && (server_cpus & (1 << CPU_x86_64)) != 0; ntdll_get_thread_data()->wow64_redir = is_wow64; diff --git a/server/Makefile.in b/server/Makefile.in index a2f1a52..03ceb79 100644 --- a/server/Makefile.in +++ b/server/Makefile.in @@ -1,5 +1,13 @@ DEFS = -D__WINESRC__ EXTRALIBS = @LIBPOLL@ +VERSION = 1.0 +SOVERSION = 1 +MODULE = libwineserver.$(LIBEXT) +SONAME = libwineserver.so.$(SOVERSION) +CFLAGS = @CFLAGS@ -fPIC + +SO_INSTALLDIRS = $(DESTDIR)$(libdir) + C_SRCS = \ async.c \ @@ -53,28 +61,48 @@ EXTRA_MANPAGES = wineserver.de.man wineserver.fr.man INSTALLDIRS = \ $(DESTDIR)$(bindir) \ + $(DESTDIR)$(libdir) \ $(DESTDIR)$(mandir)/man$(prog_manext) \ $(DESTDIR)$(mandir)/de.UTF-8/man$(prog_manext) \ $(DESTDIR)$(mandir)/fr.UTF-8/man$(prog_manext) -all: $(PROGRAMS) +all: $(PROGRAMS) $(MODULE) @MAKE_RULES@ wineserver: $(OBJS) $(CC) -o $@ $(OBJS) $(LIBWINE) $(LIBPORT) $(LDFLAGS) $(LIBS) $(LDRPATH_LOCAL) +libwineserver.so.$(VERSION): $(OBJS) $(VERSCRIPT) Makefile.in + $(LDSHARED) $(OBJS) $(EXTRALIBS) $(LDFLAGS) $(LIBS) -o $@ + +libwineserver.so.$(SOVERSION): libwineserver.so.$(VERSION) + $(RM) $@ && $(LN_S) libwineserver.so.$(VERSION) $@ + +libwineserver.so: libwineserver.so.$(SOVERSION) + $(RM) $@ && $(LN_S) libwineserver.so.$(SOVERSION) $@ + wineserver-installed: $(OBJS) $(CC) -o $@ $(OBJS) $(LIBWINE) $(LIBPORT) $(LDFLAGS) $(LIBS) $(LDRPATH_INSTALL) -install install-lib:: wineserver-installed $(DESTDIR)$(bindir) install-man-pages +install:: wineserver-installed $(DESTDIR)$(bindir) install-lib install-man-pages $(INSTALL_PROGRAM) wineserver-installed $(DESTDIR)$(bindir)/wineserver +install-lib:: libwineserver.so.$(VERSION) $(DESTDIR)$(libdir) + $(INSTALL_PROGRAM) libwineserver.so.$(VERSION) $(DESTDIR)$(libdir) + cd $(DESTDIR)$(libdir) \ + && $(RM) libwineserver.so libwineserver.so.$(SOVERSION) \ + && $(LN_S) libwineserver.so.$(VERSION) libwineserver.so.$(SOVERSION) \ + && $(LN_S) libwineserver.so.$(VERSION) libwineserver.so + install-man-pages:: $(EXTRA_MANPAGES) $(INSTALLDIRS) $(INSTALL_DATA) wineserver.de.man $(DESTDIR)$(mandir)/de.UTF-8/man$(prog_manext)/wineserver.$(prog_manext) $(INSTALL_DATA) wineserver.fr.man $(DESTDIR)$(mandir)/fr.UTF-8/man$(prog_manext)/wineserver.$(prog_manext) uninstall:: $(RM) $(DESTDIR)$(bindir)/wineserver + $(RM) $(DESTDIR)$(libdir)/libwineserver.so.$(VERSION) + $(RM) $(DESTDIR)$(libdir)/libwineserver.so.$(SOVERSION) + $(RM) $(DESTDIR)$(libdir)/libwineserver.so $(RM) $(DESTDIR)$(mandir)/de.UTF-8/man$(prog_manext)/wineserver.$(prog_manext) $(RM) $(DESTDIR)$(mandir)/fr.UTF-8/man$(prog_manext)/wineserver.$(prog_manext) diff --git a/server/fd.c b/server/fd.c index a8b3a5f..513197e 100644 --- a/server/fd.c +++ b/server/fd.c @@ -502,6 +502,19 @@ static inline void remove_epoll_user( struct fd *fd, int user ) } } +/* Obtain lock on wineserver_mutex, but only if we're running in-process */ +static void inline wineserver_lock() +{ + if(wineserver_is_inproc && pthread_mutex_lock(&wineserver_mutex)) + perror("wineserver: pthread_mutex_lock"); +} + +static void inline wineserver_unlock() +{ + if(wineserver_is_inproc && pthread_mutex_unlock(&wineserver_mutex)) + perror("wineserver: pthread_mutex_unlock"); +} + static inline void main_loop_epoll(void) { int i, ret, timeout; @@ -521,7 +534,9 @@ static inline void main_loop_epoll(void) if (!active_users) break; /* last user removed by a timeout */ if (epoll_fd == -1) break; /* an error occurred with epoll */ + wineserver_unlock(); ret = epoll_wait( epoll_fd, events, sizeof(events)/sizeof(events[0]), timeout ); + wineserver_lock(); set_current_time(); /* put the events into the pollfd array first, like poll does */ @@ -626,6 +641,7 @@ static inline void main_loop_epoll(void) if (!active_users) break; /* last user removed by a timeout */ if (kqueue_fd == -1) break; /* an error occurred with kqueue */ + wineserver_unlock(); if (timeout != -1) { struct timespec ts; @@ -635,6 +651,7 @@ static inline void main_loop_epoll(void) ret = kevent( kqueue_fd, NULL, 0, events, sizeof(events)/sizeof(events[0]), &ts ); } else ret = kevent( kqueue_fd, NULL, 0, events, sizeof(events)/sizeof(events[0]), NULL ); + wineserver_lock(); set_current_time(); @@ -730,6 +747,7 @@ static inline void main_loop_epoll(void) if (!active_users) break; /* last user removed by a timeout */ if (port_fd == -1) break; /* an error occurred with event completion */ + wineserver_unlock(); if (timeout != -1) { struct timespec ts; @@ -739,6 +757,7 @@ static inline void main_loop_epoll(void) ret = port_getn( port_fd, events, sizeof(events)/sizeof(events[0]), &nget, &ts ); } else ret = port_getn( port_fd, events, sizeof(events)/sizeof(events[0]), &nget, NULL ); + wineserver_lock(); if (ret == -1) break; /* an error occurred with event completion */ @@ -889,7 +908,9 @@ void main_loop(void) if (!active_users) break; /* last user removed by a timeout */ + wineserver_unlock(); ret = poll( pollfd, nb_users, timeout ); + wineserver_lock(); set_current_time(); if (ret > 0) diff --git a/server/main.c b/server/main.c index 2d841e8..72d92e0 100644 --- a/server/main.c +++ b/server/main.c @@ -41,6 +41,7 @@ /* command-line options */ int debug_level = 0; int foreground = 0; +int wineserver_is_inproc = 0; timeout_t master_socket_timeout = 3 * -TICKS_PER_SEC; /* master socket timeout, default is 3 seconds */ const char *server_argv0; @@ -125,7 +126,7 @@ static void sigterm_handler( int signum ) exit(1); /* make sure atexit functions get called */ } -int main( int argc, char *argv[] ) +static int wineserver_start( int argc, char *argv[] ) { setvbuf( stderr, NULL, _IOLBF, 0 ); parse_args( argc, argv ); @@ -148,3 +149,17 @@ int main( int argc, char *argv[] ) main_loop(); return 0; } + +int main( int argc, char *argv[] ) +{ + return wineserver_start(argc, argv); +} + +void *wineserver_start_inproc(char *argv[]) +{ + int argc = 0; + while (argv[argc]) ++argc; + wineserver_is_inproc = 1; + wineserver_start(argc, argv); + return 0; +} diff --git a/server/object.h b/server/object.h index a8cb327..51de21a 100644 --- a/server/object.h +++ b/server/object.h @@ -25,10 +25,21 @@ #include #endif +#ifdef HAVE_PTHREAD_H +#include +#else +/* FIXME */ +#endif + #include #include "wine/server_protocol.h" #include "wine/list.h" +/* FIXME: need more portable locking mechanism */ + +extern pthread_mutex_t wineserver_mutex; /* Synchronize access to wineserver data between threads */ +extern int wineserver_is_inproc; + #define DEBUG_OBJECTS /* kernel objects */ diff --git a/server/request.c b/server/request.c index 05c7464..bc27451 100644 --- a/server/request.c +++ b/server/request.c @@ -51,6 +51,7 @@ #ifdef HAVE_POLL_H #include #endif +#include #include "ntstatus.h" #define WIN32_NO_STATUS @@ -126,6 +127,8 @@ int config_dir_fd = -1; /* file descriptor for the config dir */ static struct master_socket *master_socket; /* the master socket object */ static struct timeout_user *master_timeout; +pthread_mutex_t wineserver_mutex; /* keep calls from in-process wine app serialized with out-of-process calls */ + /* complain about a protocol error and terminate the client connection */ void fatal_protocol_error( struct thread *thread, const char *err, ... ) { @@ -254,12 +257,130 @@ static void send_reply( union generic_reply *reply ) fatal_protocol_perror( current, "reply write" ); } +/* FIXME: copy & paste from include/server.h. Can we just include that header? */ +struct __server_iovec +{ + const void *ptr; + data_size_t size; +}; + +#define __SERVER_MAX_DATA 5 + +struct __server_request_info +{ + union + { + union generic_request req; /* request structure */ + union generic_reply reply; /* reply structure */ + } u; + unsigned int data_count; /* count of request data pointers */ + void *reply_data; /* reply data pointer */ + struct __server_iovec data[__SERVER_MAX_DATA]; /* request variable size data */ +}; + +static inline void call_req_handler_common( struct thread *thread, enum request req, + union generic_reply *reply ) +{ + current = thread; + current->reply_size = 0; + clear_error(); + memset( reply, 0, sizeof(reply) ); + + if (debug_level) trace_request(); + + if (req < REQ_NB_REQUESTS) + req_handlers[req]( ¤t->req, &reply ); + else + set_error( STATUS_NOT_IMPLEMENTED ); +} + +void call_req_handler_inproc( void * req_ptr ) { + struct __server_request_info *req = (struct __server_request_info *)req_ptr; + union generic_reply reply; + struct thread *thread; + enum request reqnum; + size_t max_reply_size = req->u.req.request_header.reply_size; + char *p; + size_t i; + + if(pthread_mutex_lock(&wineserver_mutex)) { + fatal_perror("call_req_handler_inproc: pthread_mutex_lock"); + } + + thread = get_thread_from_id(GetCurrentThreadId()); + if(!thread) thread = get_thread_from_pid(getpid()); + if(!thread) fatal_perror("call_req_handler_inproc: failed to get thread object"); + + grab_object(thread); + + memcpy(&(thread->req), req, sizeof(thread->req)); + p = (char *)malloc(req->u.req.request_header.request_size); + if(!p) fatal_perror("malloc"); + thread->req_data = p; + for(i = 0; i < req->data_count; ++i) { + memcpy(p, req->data[i].ptr, req->data[i].size); + p += req->data[i].size; + } + + reqnum = thread->req.request_header.req; + +#if 0 + call_req_handler_common(thread, reqnum, &reply); +#endif + current = thread; + current->reply_size = 0; + clear_error(); + memset( &reply, 0, sizeof(reply) ); + + if (debug_level) trace_request(); + + if (reqnum < REQ_NB_REQUESTS) + req_handlers[reqnum]( ¤t->req, &reply ); + else + set_error( STATUS_NOT_IMPLEMENTED ); + + /* copy back the reply over the request union/struct (but not the whole __server_request_info) */ + + free (thread->req_data); + thread->req_data = NULL; + + if(current) { + memcpy(req, &reply, sizeof(reply)); + req->u.reply.reply_header.error = current->error; + req->u.reply.reply_header.reply_size = current->reply_size; + + if (debug_level) trace_reply( reqnum, &req->u.reply ); + + if(req->reply_data) { + /* FIXME: clean this up */ + if(current->reply_size > max_reply_size) { + fatal_error("reply buffer too small, supplied %u bytes, requires %u\n", + max_reply_size, current->reply_size); + } + memcpy(req->reply_data, current->reply_data, current->reply_size); + free(current->reply_data); + } + + current->reply_data = NULL; + current = NULL; + } + + release_object( thread ); + + if(pthread_mutex_unlock(&wineserver_mutex)) { + perror("call_req_handler_inproc: pthread_mutex_unlock"); + } +} + /* call a request handler */ static void call_req_handler( struct thread *thread ) { union generic_reply reply; enum request req = thread->req.request_header.req; +#if 0 + call_req_handler_common(thread, req, &reply); +#endif current = thread; current->reply_size = 0; clear_error(); @@ -758,6 +879,11 @@ void open_master_socket(void) assert( sizeof(union generic_request) == sizeof(struct request_max_size) ); assert( sizeof(union generic_reply) == sizeof(struct request_max_size) ); + /* init mutex for serializing in-process and out-of-process requests */ + if(pthread_mutex_init(&wineserver_mutex, NULL)) { + fatal_perror("pthread_mutex_init failed"); + } + /* make sure the stdio fds are open */ fd = open( "/dev/null", O_RDWR ); while (fd >= 0 && fd <= 2) fd = dup( fd ); @@ -840,5 +966,10 @@ void close_master_socket( timeout_t timeout ) if (master_timeout) /* cancel previous timeout */ remove_timeout_user( master_timeout ); + if(pthread_mutex_destroy(&wineserver_mutex)) { + perror("pthread_mutex_destroy"); + } + memset(&wineserver_mutex, 0, sizeof(wineserver_mutex)); + master_timeout = add_timeout_user( timeout, close_socket_timeout, NULL ); } --------------1.7.3.4--