/* * Server-side request handling * * Copyright (C) 1998 Alexandre Julliard * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #include "wine/port.h" #include #include #include #ifdef HAVE_PWD_H #include #endif #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_SOCKET_H # include #endif #ifdef HAVE_SYS_WAIT_H # include #endif #ifdef HAVE_SYS_UIO_H #include #endif #ifdef HAVE_SYS_UN_H #include #endif #include #include "windef.h" #include "winbase.h" #include "wincon.h" #include "wine/library.h" #include "file.h" #include "handle.h" #include "thread.h" #include "process.h" #include "user.h" #define WANT_REQUEST_HANDLERS #include "request.h" /* Some versions of glibc don't define this */ #ifndef SCM_RIGHTS #define SCM_RIGHTS 1 #endif /* path names for server master Unix socket */ static const char * const server_socket_name = "socket"; /* name of the socket file */ static const char * const server_lock_name = "lock"; /* name of the server lock file */ struct master_socket { struct object obj; /* object header */ struct timeout_user *timeout; /* timeout on last process exit */ }; static void master_socket_dump( struct object *obj, int verbose ); static void master_socket_poll_event( struct object *obj, int event ); static const struct object_ops master_socket_ops = { sizeof(struct master_socket), /* size */ master_socket_dump, /* dump */ no_add_queue, /* add_queue */ NULL, /* remove_queue */ NULL, /* signaled */ NULL, /* satisfied */ no_get_fd, /* get_fd */ no_get_file_info, /* get_file_info */ no_destroy /* destroy */ }; static const struct fd_ops master_socket_fd_ops = { NULL, /* get_poll_events */ master_socket_poll_event, /* poll_event */ no_flush, /* flush */ no_get_file_info, /* get_file_info */ no_queue_async /* queue_async */ }; struct thread *current = NULL; /* thread handling the current request */ unsigned int global_error = 0; /* global error code for when no thread is current */ unsigned int server_start_ticks = 0; /* tick count offset from server startup */ static struct master_socket *master_socket; /* the master socket object */ /* socket communication static structures */ static struct iovec myiovec; static struct msghdr msghdr; #ifndef HAVE_MSGHDR_ACCRIGHTS struct cmsg_fd { int len; /* sizeof structure */ int level; /* SOL_SOCKET */ int type; /* SCM_RIGHTS */ int fd; /* fd to pass */ }; static struct cmsg_fd cmsg = { sizeof(cmsg), SOL_SOCKET, SCM_RIGHTS, -1 }; #endif /* HAVE_MSGHDR_ACCRIGHTS */ /* complain about a protocol error and terminate the client connection */ void fatal_protocol_error( struct thread *thread, const char *err, ... ) { va_list args; va_start( args, err ); fprintf( stderr, "Protocol error:%p: ", thread ); vfprintf( stderr, err, args ); va_end( args ); thread->exit_code = 1; kill_thread( thread, 1 ); } /* complain about a protocol error and terminate the client connection */ void fatal_protocol_perror( struct thread *thread, const char *err, ... ) { va_list args; va_start( args, err ); fprintf( stderr, "Protocol error:%p: ", thread ); vfprintf( stderr, err, args ); perror( " " ); va_end( args ); thread->exit_code = 1; kill_thread( thread, 1 ); } /* die on a fatal error */ void fatal_error( const char *err, ... ) { va_list args; va_start( args, err ); fprintf( stderr, "wineserver: " ); vfprintf( stderr, err, args ); va_end( args ); exit(1); } /* die on a fatal error */ void fatal_perror( const char *err, ... ) { va_list args; va_start( args, err ); fprintf( stderr, "wineserver: " ); vfprintf( stderr, err, args ); perror( " " ); va_end( args ); exit(1); } /* allocate the reply data */ void *set_reply_data_size( size_t size ) { assert( size <= get_reply_max_size() ); if (size && !(current->reply_data = mem_alloc( size ))) size = 0; current->reply_size = size; return current->reply_data; } /* write the remaining part of the reply */ void write_reply( struct thread *thread ) { int ret; if ((ret = write( thread->reply_fd, (char *)thread->reply_data + thread->reply_size - thread->reply_towrite, thread->reply_towrite )) >= 0) { if (!(thread->reply_towrite -= ret)) { free( thread->reply_data ); thread->reply_data = NULL; /* sent everything, can go back to waiting for requests */ change_select_fd( &thread->obj, thread->request_fd, POLLIN ); } return; } if (errno == EPIPE) kill_thread( thread, 0 ); /* normal death */ else if (errno != EWOULDBLOCK && errno != EAGAIN) fatal_protocol_perror( thread, "reply write" ); } /* send a reply to the current thread */ static void send_reply( union generic_reply *reply ) { int ret; if (!current->reply_size) { if ((ret = write( current->reply_fd, reply, sizeof(*reply) )) != sizeof(*reply)) goto error; } else { struct iovec vec[2]; vec[0].iov_base = (void *)reply; vec[0].iov_len = sizeof(*reply); vec[1].iov_base = current->reply_data; vec[1].iov_len = current->reply_size; if ((ret = writev( current->reply_fd, vec, 2 )) < sizeof(*reply)) goto error; if ((current->reply_towrite = current->reply_size - (ret - sizeof(*reply)))) { /* couldn't write it all, wait for POLLOUT */ change_select_fd( ¤t->obj, current->reply_fd, POLLOUT ); return; } } if (current->reply_data) { free( current->reply_data ); current->reply_data = NULL; } return; error: if (ret >= 0) fatal_protocol_error( current, "partial write %d\n", ret ); else if (errno == EPIPE) kill_thread( current, 0 ); /* normal death */ else fatal_protocol_perror( current, "reply write" ); } /* call a request handler */ static void call_req_handler( struct thread *thread ) { union generic_reply reply; enum request req = thread->req.request_header.req; 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 ); if (current) { reply.reply_header.error = current->error; reply.reply_header.reply_size = current->reply_size; if (debug_level) trace_reply( req, &reply ); send_reply( &reply ); } current = NULL; return; } fatal_protocol_error( current, "bad request %d\n", req ); } /* read a request from a thread */ void read_request( struct thread *thread ) { int ret; if (!thread->req_toread) /* no pending request */ { if ((ret = read( thread->obj.fd, &thread->req, sizeof(thread->req) )) != sizeof(thread->req)) goto error; if (!(thread->req_toread = thread->req.request_header.request_size)) { /* no data, handle request at once */ call_req_handler( thread ); return; } if (!(thread->req_data = malloc( thread->req_toread ))) fatal_protocol_error( thread, "no memory for %d bytes request\n", thread->req_toread ); } /* read the variable sized data */ for (;;) { ret = read( thread->obj.fd, ((char *)thread->req_data + thread->req.request_header.request_size - thread->req_toread), thread->req_toread ); if (ret <= 0) break; if (!(thread->req_toread -= ret)) { call_req_handler( thread ); free( thread->req_data ); thread->req_data = NULL; return; } } error: if (!ret) /* closed pipe */ kill_thread( thread, 0 ); else if (ret > 0) fatal_protocol_error( thread, "partial read %d\n", ret ); else if (errno != EWOULDBLOCK && errno != EAGAIN) fatal_protocol_perror( thread, "read" ); } /* receive a file descriptor on the process socket */ int receive_fd( struct process *process ) { struct send_fd data; int fd, ret; #ifdef HAVE_MSGHDR_ACCRIGHTS msghdr.msg_accrightslen = sizeof(int); msghdr.msg_accrights = (void *)&fd; #else /* HAVE_MSGHDR_ACCRIGHTS */ msghdr.msg_control = &cmsg; msghdr.msg_controllen = sizeof(cmsg); cmsg.fd = -1; #endif /* HAVE_MSGHDR_ACCRIGHTS */ myiovec.iov_base = (void *)&data; myiovec.iov_len = sizeof(data); ret = recvmsg( process->obj.fd, &msghdr, 0 ); #ifndef HAVE_MSGHDR_ACCRIGHTS fd = cmsg.fd; #endif if (ret == sizeof(data)) { struct thread *thread; if (data.tid) thread = get_thread_from_id( data.tid ); else thread = (struct thread *)grab_object( process->thread_list ); if (!thread || thread->process != process || thread->state == TERMINATED) { if (debug_level) fprintf( stderr, "%04x: *fd* %d <- %d bad thread id\n", data.tid, data.fd, fd ); close( fd ); } else { if (debug_level) fprintf( stderr, "%04x: *fd* %d <- %d\n", thread->id, data.fd, fd ); thread_add_inflight_fd( thread, data.fd, fd ); } if (thread) release_object( thread ); return 0; } if (ret >= 0) { if (ret > 0) fprintf( stderr, "Protocol error: process %p: partial recvmsg %d for fd\n", process, ret ); kill_process( process, NULL, 1 ); } else { if (errno != EWOULDBLOCK && errno != EAGAIN) { fprintf( stderr, "Protocol error: process %p: ", process ); perror( "recvmsg" ); kill_process( process, NULL, 1 ); } } return -1; } /* send an fd to a client */ int send_client_fd( struct process *process, int fd, obj_handle_t handle ) { int ret; if (debug_level) fprintf( stderr, "%04x: *fd* %p -> %d\n", current ? current->id : process->id, handle, fd ); #ifdef HAVE_MSGHDR_ACCRIGHTS msghdr.msg_accrightslen = sizeof(fd); msghdr.msg_accrights = (void *)&fd; #else /* HAVE_MSGHDR_ACCRIGHTS */ msghdr.msg_control = &cmsg; msghdr.msg_controllen = sizeof(cmsg); cmsg.fd = fd; #endif /* HAVE_MSGHDR_ACCRIGHTS */ myiovec.iov_base = (void *)&handle; myiovec.iov_len = sizeof(handle); ret = sendmsg( process->obj.fd, &msghdr, 0 ); if (ret == sizeof(handle)) return 0; if (ret >= 0) { fprintf( stderr, "Protocol error: process %p: partial sendmsg %d\n", process, ret ); kill_process( process, NULL, 1 ); } else if (errno == EPIPE) { kill_process( process, NULL, 0 ); } else { fprintf( stderr, "Protocol error: process %p: ", process ); perror( "sendmsg" ); kill_process( process, NULL, 1 ); } return -1; } /* get current tick count to return to client */ unsigned int get_tick_count(void) { struct timeval t; gettimeofday( &t, NULL ); return (t.tv_sec * 1000) + (t.tv_usec / 1000) - server_start_ticks; } static void master_socket_dump( struct object *obj, int verbose ) { struct master_socket *sock = (struct master_socket *)obj; assert( obj->ops == &master_socket_ops ); fprintf( stderr, "Master socket fd=%d\n", sock->obj.fd ); } /* handle a socket event */ static void master_socket_poll_event( struct object *obj, int event ) { struct master_socket *sock = (struct master_socket *)obj; assert( obj->ops == &master_socket_ops ); assert( sock == master_socket ); /* there is only one master socket */ if (event & (POLLERR | POLLHUP)) { /* this is not supposed to happen */ fprintf( stderr, "wineserver: Error on master socket\n" ); release_object( obj ); } else if (event & POLLIN) { struct sockaddr_un dummy; int len = sizeof(dummy); int client = accept( master_socket->obj.fd, (struct sockaddr *) &dummy, &len ); if (client == -1) return; if (sock->timeout) { remove_timeout_user( sock->timeout ); sock->timeout = NULL; } fcntl( client, F_SETFL, O_NONBLOCK ); create_process( client ); } } /* remove the socket upon exit */ static void socket_cleanup(void) { static int do_it_once; if (!do_it_once++) unlink( server_socket_name ); } /* create a directory and check its permissions */ static void create_dir( const char *name, struct stat *st ) { if (lstat( name, st ) == -1) { if (errno != ENOENT) fatal_perror( "lstat %s", name ); if (mkdir( name, 0700 ) == -1) fatal_perror( "mkdir %s", name ); if (lstat( name, st ) == -1) fatal_perror( "lstat %s", name ); } if (!S_ISDIR(st->st_mode)) fatal_error( "%s is not a directory\n", name ); if (st->st_uid != getuid()) fatal_error( "%s is not owned by you\n", name ); if (st->st_mode & 077) fatal_error( "%s must not be accessible by other users\n", name ); } /* create the server directory and chdir to it */ static void create_server_dir(void) { char *p, *server_dir; struct stat st, st2; if (!(server_dir = strdup( wine_get_server_dir() ))) fatal_error( "out of memory\n" ); /* first create the base directory if needed */ p = strrchr( server_dir, '/' ); *p = 0; create_dir( server_dir, &st ); /* now create the server directory */ *p = '/'; create_dir( server_dir, &st ); if (chdir( server_dir ) == -1) fatal_perror( "chdir %s", server_dir ); if (stat( ".", &st2 ) == -1) fatal_perror( "stat %s", server_dir ); if (st.st_dev != st2.st_dev || st.st_ino != st2.st_ino) fatal_error( "chdir did not end up in %s\n", server_dir ); free( server_dir ); } /* create the lock file and return its file descriptor */ static int create_server_lock(void) { struct stat st; int fd; if (lstat( server_lock_name, &st ) == -1) { if (errno != ENOENT) fatal_perror( "lstat %s/%s", wine_get_server_dir(), server_lock_name ); } else { if (!S_ISREG(st.st_mode)) fatal_error( "%s/%s is not a regular file\n", wine_get_server_dir(), server_lock_name ); } if ((fd = open( server_lock_name, O_CREAT|O_TRUNC|O_WRONLY, 0600 )) == -1) fatal_perror( "error creating %s/%s", wine_get_server_dir(), server_lock_name ); return fd; } /* wait for the server lock */ int wait_for_lock(void) { int fd, r; struct flock fl; create_server_dir(); fd = create_server_lock(); fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 1; r = fcntl( fd, F_SETLKW, &fl ); close(fd); return r; } /* kill the wine server holding the lock */ int kill_lock_owner( int sig ) { int fd, i, ret = 0; pid_t pid = 0; struct flock fl; create_server_dir(); fd = create_server_lock(); for (i = 0; i < 10; i++) { fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 1; if (fcntl( fd, F_GETLK, &fl ) == -1) goto done; if (fl.l_type != F_WRLCK) goto done; /* the file is not locked */ if (!pid) /* first time around */ { if (!(pid = fl.l_pid)) goto done; /* shouldn't happen */ if (sig == -1) { if (kill( pid, SIGINT ) == -1) goto done; kill( pid, SIGCONT ); ret = 1; } else /* just send the specified signal and return */ { ret = (kill( pid, sig ) != -1); goto done; } } else if (fl.l_pid != pid) goto done; /* no longer the same process */ sleep( 1 ); } /* waited long enough, now kill it */ kill( pid, SIGKILL ); done: close( fd ); return ret; } /* acquire the main server lock */ static void acquire_lock(void) { struct sockaddr_un addr; struct stat st; struct flock fl; int fd, slen, got_lock = 0; fd = create_server_lock(); fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 1; if (fcntl( fd, F_SETLK, &fl ) != -1) { /* check for crashed server */ if (stat( server_socket_name, &st ) != -1 && /* there is a leftover socket */ stat( "core", &st ) != -1 && st.st_size) /* and there is a non-empty core file */ { fprintf( stderr, "Warning: a previous instance of the wine server seems to have crashed.\n" "Please run 'gdb %s %s/core',\n" "type 'backtrace' at the gdb prompt and report the results. Thanks.\n\n", server_argv0, wine_get_server_dir() ); } unlink( server_socket_name ); /* we got the lock, we can safely remove the socket */ got_lock = 1; /* in that case we reuse fd without closing it, this ensures * that we hold the lock until the process exits */ } else { switch(errno) { case ENOLCK: break; case EACCES: /* check whether locks work at all on this file system */ if (fcntl( fd, F_GETLK, &fl ) == -1) break; /* fall through */ case EAGAIN: exit(2); /* we didn't get the lock, exit with special status */ default: fatal_perror( "fcntl %s/%s", wine_get_server_dir(), server_lock_name ); } /* it seems we can't use locks on this fs, so we will use the socket existence as lock */ close( fd ); } if ((fd = socket( AF_UNIX, SOCK_STREAM, 0 )) == -1) fatal_perror( "socket" ); addr.sun_family = AF_UNIX; strcpy( addr.sun_path, server_socket_name ); slen = sizeof(addr) - sizeof(addr.sun_path) + strlen(addr.sun_path) + 1; #ifdef HAVE_SOCKADDR_SUN_LEN addr.sun_len = slen; #endif if (bind( fd, (struct sockaddr *)&addr, slen ) == -1) { if ((errno == EEXIST) || (errno == EADDRINUSE)) { if (got_lock) fatal_error( "couldn't bind to the socket even though we hold the lock\n" ); exit(2); /* we didn't get the lock, exit with special status */ } fatal_perror( "bind" ); } atexit( socket_cleanup ); chmod( server_socket_name, 0600 ); /* make sure no other user can connect */ if (listen( fd, 5 ) == -1) fatal_perror( "listen" ); if (!(master_socket = alloc_fd_object( &master_socket_ops, &master_socket_fd_ops, fd ))) fatal_error( "out of memory\n" ); master_socket->timeout = NULL; set_select_events( &master_socket->obj, POLLIN ); } /* open the master server socket and start waiting for new clients */ void open_master_socket(void) { int pid, status, sync_pipe[2]; char dummy; /* make sure no request is larger than the maximum size */ assert( sizeof(union generic_request) == sizeof(struct request_max_size) ); assert( sizeof(union generic_reply) == sizeof(struct request_max_size) ); create_server_dir(); if (pipe( sync_pipe ) == -1) fatal_perror( "pipe" ); pid = fork(); switch( pid ) { case 0: /* child */ setsid(); close( sync_pipe[0] ); acquire_lock(); /* signal parent */ write( sync_pipe[1], &dummy, 1 ); close( sync_pipe[1] ); break; case -1: fatal_perror( "fork" ); break; default: /* parent */ close( sync_pipe[1] ); /* wait for child to signal us and then exit */ if (read( sync_pipe[0], &dummy, 1 ) == 1) _exit(0); /* child terminated, propagate exit status */ wait4( pid, &status, 0, NULL ); if (WIFEXITED(status)) _exit( WEXITSTATUS(status) ); _exit(1); } /* setup msghdr structure constant fields */ msghdr.msg_name = NULL; msghdr.msg_namelen = 0; msghdr.msg_iov = &myiovec; msghdr.msg_iovlen = 1; /* init startup ticks */ server_start_ticks = get_tick_count(); } /* master socket timer expiration handler */ static void close_socket_timeout( void *arg ) { master_socket->timeout = NULL; flush_registry(); /* if a new client is waiting, we keep on running */ if (check_select_events( master_socket->obj.fd, POLLIN )) return; if (debug_level) fprintf( stderr, "wineserver: exiting (pid=%ld)\n", (long) getpid() ); #ifdef DEBUG_OBJECTS /* shut down everything properly */ release_object( master_socket ); close_global_hooks(); close_global_handles(); close_registry(); close_atom_table(); #else exit(0); #endif } /* close the master socket and stop waiting for new clients */ void close_master_socket(void) { struct timeval when; if (master_socket_timeout == -1) return; /* just keep running forever */ if (master_socket_timeout) { gettimeofday( &when, 0 ); add_timeout( &when, master_socket_timeout * 1000 ); master_socket->timeout = add_timeout_user( &when, close_socket_timeout, NULL ); } else close_socket_timeout( NULL ); /* close it right away */ } /* lock/unlock the master socket to stop accepting new clients */ void lock_master_socket( int locked ) { set_select_events( &master_socket->obj, locked ? 0 : POLLIN ); }