New mechanism to transfer file descriptors from client to server.

This commit is contained in:
Alexandre Julliard 2001-02-28 21:45:23 +00:00
parent 0ba5909038
commit f52424055f
11 changed files with 294 additions and 144 deletions

View File

@ -184,10 +184,14 @@ void FILE_SetDosError(void)
HANDLE FILE_DupUnixHandle( int fd, DWORD access )
{
HANDLE ret;
wine_server_send_fd( fd );
SERVER_START_REQ( alloc_file_handle )
{
req->access = access;
SERVER_CALL_FD( fd );
req->fd = fd;
SERVER_CALL();
ret = req->handle;
}
SERVER_END_REQ;

View File

@ -118,6 +118,12 @@ typedef struct
union debug_event_data info; /* event information */
} debug_event_t;
/* structure used in sending an fd from client to server */
struct send_fd
{
void *tid; /* thread id */
int fd; /* file descriptor on client-side */
};
/* Create a new process from the context of the parent */
struct new_process_request
@ -539,6 +545,7 @@ struct alloc_file_handle_request
{
REQUEST_HEADER; /* request header */
IN unsigned int access; /* wanted access rights */
IN int fd; /* file descriptor on the client side */
OUT handle_t handle; /* handle to the file */
};
@ -1592,7 +1599,7 @@ union generic_request
struct async_result_request async_result;
};
#define SERVER_PROTOCOL_VERSION 39
#define SERVER_PROTOCOL_VERSION 40
/* ### make_requests end ### */
/* Everything above this line is generated automatically by tools/make_requests */
@ -1611,10 +1618,10 @@ union generic_request
/* client communication functions */
extern unsigned int wine_server_call( union generic_request *req, size_t size );
extern unsigned int wine_server_call_fd( union generic_request *req, size_t size, int fd );
extern void server_protocol_error( const char *err, ... ) WINE_NORETURN;
extern void server_protocol_perror( const char *err ) WINE_NORETURN;
extern void wine_server_alloc_req( union generic_request *req, size_t size );
extern void wine_server_send_fd( int fd );
extern int wine_server_recv_fd( int handle, int cache );
extern const char *get_config_dir(void);
@ -1685,7 +1692,6 @@ struct __server_exception_frame
#define SERVER_CALL() (wine_server_call( &__req, sizeof(*req) ))
#define SERVER_CALL_ERR() (__server_call_err( &__req, sizeof(*req) ))
#define SERVER_CALL_FD(fd) (wine_server_call_fd( &__req, sizeof(*req), (fd) ))
extern int CLIENT_InitServer(void);

View File

@ -55,7 +55,8 @@ struct cmsg_fd
};
static void *boot_thread_id;
static sigset_t block_set; /* signals to block during server calls */
static int fd_socket; /* socket to exchange file descriptors with the server */
/* die on a fatal error; use only during initialization */
static void fatal_error( const char *err, ... ) WINE_NORETURN;
@ -158,47 +159,6 @@ static void send_request( union generic_request *request )
server_protocol_perror( "sendmsg" );
}
/***********************************************************************
* send_request_fd
*
* Send a request to the server, passing a file descriptor.
*/
static void send_request_fd( union generic_request *request, int fd )
{
#ifndef HAVE_MSGHDR_ACCRIGHTS
struct cmsg_fd cmsg;
#endif
struct msghdr msghdr;
struct iovec vec;
int ret;
vec.iov_base = (void *)request;
vec.iov_len = sizeof(*request);
msghdr.msg_name = NULL;
msghdr.msg_namelen = 0;
msghdr.msg_iov = &vec;
msghdr.msg_iovlen = 1;
#ifdef HAVE_MSGHDR_ACCRIGHTS
msghdr.msg_accrights = (void *)&fd;
msghdr.msg_accrightslen = sizeof(fd);
#else /* HAVE_MSGHDR_ACCRIGHTS */
cmsg.len = sizeof(cmsg);
cmsg.level = SOL_SOCKET;
cmsg.type = SCM_RIGHTS;
cmsg.fd = fd;
msghdr.msg_control = &cmsg;
msghdr.msg_controllen = sizeof(cmsg);
msghdr.msg_flags = 0;
#endif /* HAVE_MSGHDR_ACCRIGHTS */
if ((ret = sendmsg( NtCurrentTeb()->socket, &msghdr, 0 )) == sizeof(*request)) return;
if (ret >= 0) server_protocol_error( "partial write %d\n", ret );
if (errno == EPIPE) SYSDEPS_ExitThread(0);
server_protocol_perror( "sendmsg" );
}
/***********************************************************************
* wait_reply
*
@ -230,24 +190,64 @@ static void wait_reply( union generic_request *req )
*/
unsigned int wine_server_call( union generic_request *req, size_t size )
{
sigset_t old_set;
memset( (char *)req + size, 0, sizeof(*req) - size );
sigprocmask( SIG_BLOCK, &block_set, &old_set );
send_request( req );
wait_reply( req );
sigprocmask( SIG_SETMASK, &old_set, NULL );
return req->header.error;
}
/***********************************************************************
* wine_server_call_fd
* wine_server_send_fd
*
* Perform a server call, passing a file descriptor.
* Send a file descriptor to the server.
*/
unsigned int wine_server_call_fd( union generic_request *req, size_t size, int fd )
void wine_server_send_fd( int fd )
{
memset( (char *)req + size, 0, sizeof(*req) - size );
send_request_fd( req, fd );
wait_reply( req );
return req->header.error;
#ifndef HAVE_MSGHDR_ACCRIGHTS
struct cmsg_fd cmsg;
#endif
struct send_fd data;
struct msghdr msghdr;
struct iovec vec;
int ret;
vec.iov_base = (void *)&data;
vec.iov_len = sizeof(data);
msghdr.msg_name = NULL;
msghdr.msg_namelen = 0;
msghdr.msg_iov = &vec;
msghdr.msg_iovlen = 1;
#ifdef HAVE_MSGHDR_ACCRIGHTS
msghdr.msg_accrights = (void *)&fd;
msghdr.msg_accrightslen = sizeof(fd);
#else /* HAVE_MSGHDR_ACCRIGHTS */
cmsg.len = sizeof(cmsg);
cmsg.level = SOL_SOCKET;
cmsg.type = SCM_RIGHTS;
cmsg.fd = fd;
msghdr.msg_control = &cmsg;
msghdr.msg_controllen = sizeof(cmsg);
msghdr.msg_flags = 0;
#endif /* HAVE_MSGHDR_ACCRIGHTS */
data.tid = (void *)GetCurrentThreadId();
data.fd = fd;
for (;;)
{
if ((ret = sendmsg( fd_socket, &msghdr, 0 )) == sizeof(data)) return;
if (ret >= 0) server_protocol_error( "partial write %d\n", ret );
if (errno == EINTR) continue;
if (errno == EPIPE) SYSDEPS_ExitThread(0);
server_protocol_perror( "sendmsg" );
}
}
@ -563,6 +563,7 @@ int CLIENT_InitServer(void)
/* connect to the server */
fd = server_connect( oldcwd, serverdir );
fd_socket = dup(fd);
/* switch back to the starting directory */
if (oldcwd)
@ -570,6 +571,14 @@ int CLIENT_InitServer(void)
chdir( oldcwd );
free( oldcwd );
}
/* setup the signal mask */
sigemptyset( &block_set );
sigaddset( &block_set, SIGALRM );
sigaddset( &block_set, SIGIO );
sigaddset( &block_set, SIGINT );
sigaddset( &block_set, SIGHUP );
return fd;
}

View File

@ -403,6 +403,12 @@ static int set_file_time( handle_t handle, time_t access_time, time_t write_time
if (!(file = get_file_obj( current->process, handle, GENERIC_WRITE )))
return 0;
if (!file->name)
{
set_error( STATUS_INVALID_HANDLE );
release_object( file );
return 0;
}
if (!access_time || !write_time)
{
struct stat st;
@ -453,19 +459,19 @@ DECL_HANDLER(create_file)
DECL_HANDLER(alloc_file_handle)
{
struct file *file;
int fd;
req->handle = 0;
if (current->pass_fd != -1)
if ((fd = thread_get_inflight_fd( current, req->fd )) == -1)
{
if ((file = create_file_for_fd( current->pass_fd, req->access,
FILE_SHARE_READ | FILE_SHARE_WRITE, 0 )))
{
req->handle = alloc_handle( current->process, file, req->access, 0 );
release_object( file );
}
current->pass_fd = -1;
set_error( STATUS_INVALID_HANDLE );
return;
}
if ((file = create_file_for_fd( fd, req->access, FILE_SHARE_READ | FILE_SHARE_WRITE, 0 )))
{
req->handle = alloc_handle( current->process, file, req->access, 0 );
release_object( file );
}
else set_error( STATUS_INVALID_PARAMETER );
}
/* get a Unix fd to access a file */

View File

@ -36,6 +36,7 @@ static int running_processes;
static void process_dump( struct object *obj, int verbose );
static int process_signaled( struct object *obj, struct thread *thread );
static void process_poll_event( struct object *obj, int event );
static void process_destroy( struct object *obj );
static const struct object_ops process_ops =
@ -47,7 +48,7 @@ static const struct object_ops process_ops =
process_signaled, /* signaled */
no_satisfied, /* satisfied */
NULL, /* get_poll_events */
NULL, /* poll_event */
process_poll_event, /* poll_event */
no_get_fd, /* get_fd */
no_flush, /* flush */
no_get_file_info, /* get_file_info */
@ -147,11 +148,7 @@ struct thread *create_process( int fd )
struct process *process;
struct thread *thread = NULL;
if (!(process = alloc_object( &process_ops, -1 )))
{
close( fd );
return NULL;
}
if (!(process = alloc_object( &process_ops, fd ))) return NULL;
process->next = NULL;
process->prev = NULL;
process->thread_list = NULL;
@ -183,8 +180,9 @@ struct thread *create_process( int fd )
if (!(process->init_event = create_event( NULL, 0, 1, 0 ))) goto error;
/* create the main thread */
if (!(thread = create_thread( fd, process ))) goto error;
if (!(thread = create_thread( dup(fd), process ))) goto error;
set_select_events( &process->obj, POLLIN ); /* start listening to events */
release_object( process );
return thread;
@ -311,6 +309,15 @@ static int process_signaled( struct object *obj, struct thread *thread )
}
static void process_poll_event( struct object *obj, int event )
{
struct process *process = (struct process *)obj;
assert( obj->ops == &process_ops );
if (event & (POLLERR | POLLHUP)) set_select_events( obj, -1 );
else if (event & POLLIN) receive_fd( process );
}
static void startup_info_destroy( struct object *obj )
{
struct startup_info *info = (struct startup_info *)obj;
@ -491,7 +498,7 @@ void resume_process( struct process *process )
}
/* kill a process on the spot */
static void kill_process( struct process *process, struct thread *skip, int exit_code )
void kill_process( struct process *process, struct thread *skip, int exit_code )
{
struct thread *thread = process->thread_list;
while (thread)

View File

@ -82,6 +82,7 @@ extern void remove_process_thread( struct process *process,
struct thread *thread );
extern void suspend_process( struct process *process );
extern void resume_process( struct process *process );
extern void kill_process( struct process *process, struct thread *skip, int exit_code );
extern void kill_debugged_processes( struct thread *debugger, int exit_code );
extern struct process_snapshot *process_snap( int *count );
extern struct module_snapshot *module_snap( struct process *process, int *count );

View File

@ -204,8 +204,6 @@ void send_reply( struct thread *thread, union generic_request *request )
{
int ret;
assert (thread->pass_fd == -1);
if (debug_level) trace_reply( thread, request );
request->header.error = thread->error;
@ -221,43 +219,68 @@ void send_reply( struct thread *thread, union generic_request *request )
}
}
/* read a message from a client that has something to say */
void read_request( struct thread *thread )
/* receive a file descriptor on the process socket */
int receive_fd( struct process *process )
{
union generic_request req;
int ret;
struct send_fd data;
int fd, ret;
#ifdef HAVE_MSGHDR_ACCRIGHTS
msghdr.msg_accrightslen = sizeof(int);
msghdr.msg_accrights = (void *)&thread->pass_fd;
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 */
assert( thread->pass_fd == -1 );
myiovec.iov_base = &data;
myiovec.iov_len = sizeof(data);
myiovec.iov_base = &req;
myiovec.iov_len = sizeof(req);
ret = recvmsg( thread->obj.fd, &msghdr, 0 );
ret = recvmsg( process->obj.fd, &msghdr, 0 );
#ifndef HAVE_MSGHDR_ACCRIGHTS
thread->pass_fd = cmsg.fd;
fd = cmsg.fd;
#endif
if (ret == sizeof(req))
if (ret == sizeof(data))
{
call_req_handler( thread, &req );
thread->pass_fd = -1;
return;
struct thread *thread = get_thread_from_id( data.tid );
if (!thread || thread->process != process)
{
if (debug_level)
fprintf( stderr, "%08x: *fd* %d <- %d bad thread id\n",
(unsigned int)data.tid, data.fd, fd );
close( fd );
}
else
{
if (debug_level)
fprintf( stderr, "%08x: *fd* %d <- %d\n",
(unsigned int)thread, data.fd, fd );
thread_add_inflight_fd( thread, data.fd, fd );
}
return 0;
}
if (!ret)
{
set_select_events( &process->obj, -1 ); /* stop waiting on it */
}
if (!ret) /* closed pipe */
kill_thread( thread, 0 );
else if (ret > 0)
fatal_protocol_error( thread, "partial recvmsg %d\n", ret );
else
fatal_protocol_perror( thread, "recvmsg" );
{
fprintf( stderr, "Protocol error: process %p: partial recvmsg %d for fd\n", process, ret );
kill_process( process, NULL, 1 );
}
else if (ret < 0)
{
if (errno != EWOULDBLOCK && errno != EAGAIN)
{
fprintf( stderr, "Protocol error: process %p: ", process );
perror( "recvmsg" );
kill_process( process, NULL, 1 );
}
}
return -1;
}
/* send the wakeup signal to a thread */
@ -280,7 +303,7 @@ int send_client_fd( struct thread *thread, int fd, handle_t handle )
int ret;
if (debug_level)
fprintf( stderr, "%08x: *fd* %d = %d\n", (unsigned int)thread, handle, fd );
fprintf( stderr, "%08x: *fd* %d -> %d\n", (unsigned int)thread, handle, fd );
#ifdef HAVE_MSGHDR_ACCRIGHTS
msghdr.msg_accrightslen = sizeof(fd);

View File

@ -31,7 +31,7 @@ extern void fatal_protocol_error( struct thread *thread, const char *err, ... );
extern void fatal_error( const char *err, ... ) WINE_NORETURN;
extern void fatal_perror( const char *err, ... ) WINE_NORETURN;
extern const char *get_config_dir(void);
extern void read_request( struct thread *thread );
extern int receive_fd( struct process *process );
extern int send_thread_wakeup( struct thread *thread, int signaled );
extern int send_client_fd( struct thread *thread, int fd, handle_t handle );
extern void send_reply( struct thread *thread, union generic_request *request );

View File

@ -61,7 +61,7 @@ struct thread_apc
static void dump_thread( struct object *obj, int verbose );
static int thread_signaled( struct object *obj, struct thread *thread );
extern void thread_poll_event( struct object *obj, int event );
static void thread_poll_event( struct object *obj, int event );
static void destroy_thread( struct object *obj );
static struct thread_apc *thread_dequeue_apc( struct thread *thread, int system_only );
@ -139,6 +139,43 @@ static int alloc_client_buffer( struct thread *thread )
return 0;
}
/* initialize the structure for a newly allocated thread */
inline static void init_thread_structure( struct thread *thread )
{
int i;
thread->unix_pid = 0; /* not known yet */
thread->context = NULL;
thread->teb = NULL;
thread->mutex = NULL;
thread->debug_ctx = NULL;
thread->debug_event = NULL;
thread->queue = NULL;
thread->info = NULL;
thread->wait = NULL;
thread->system_apc.head = NULL;
thread->system_apc.tail = NULL;
thread->user_apc.head = NULL;
thread->user_apc.tail = NULL;
thread->error = 0;
thread->request_fd = NULL;
thread->reply_fd = -1;
thread->wait_fd = -1;
thread->state = RUNNING;
thread->attached = 0;
thread->exit_code = 0;
thread->next = NULL;
thread->prev = NULL;
thread->priority = THREAD_PRIORITY_NORMAL;
thread->affinity = 1;
thread->suspend = 0;
thread->buffer = (void *)-1;
thread->last_req = REQ_get_thread_buffer;
for (i = 0; i < MAX_INFLIGHT_FDS; i++)
thread->inflight[i].server = thread->inflight[i].client = -1;
}
/* create a new thread */
struct thread *create_thread( int fd, struct process *process )
{
@ -149,36 +186,9 @@ struct thread *create_thread( int fd, struct process *process )
if (!(thread = alloc_object( &thread_ops, fd ))) return NULL;
thread->unix_pid = 0; /* not known yet */
thread->context = NULL;
thread->teb = NULL;
thread->mutex = NULL;
thread->debug_ctx = NULL;
thread->debug_event = NULL;
thread->queue = NULL;
thread->info = NULL;
thread->wait = NULL;
thread->system_apc.head = NULL;
thread->system_apc.tail = NULL;
thread->user_apc.head = NULL;
thread->user_apc.tail = NULL;
thread->error = 0;
thread->pass_fd = -1;
thread->request_fd = NULL;
thread->reply_fd = -1;
thread->wait_fd = -1;
thread->state = RUNNING;
thread->attached = 0;
thread->exit_code = 0;
thread->next = NULL;
thread->prev = NULL;
thread->priority = THREAD_PRIORITY_NORMAL;
thread->affinity = 1;
thread->suspend = 0;
thread->buffer = (void *)-1;
thread->last_req = REQ_get_thread_buffer;
thread->process = (struct process *)grab_object( process );
init_thread_structure( thread );
thread->process = (struct process *)grab_object( process );
if (!current) current = thread;
if (!booting_thread) /* first thread ever */
@ -190,7 +200,9 @@ struct thread *create_thread( int fd, struct process *process )
if ((thread->next = first_thread) != NULL) thread->next->prev = thread;
first_thread = thread;
#if 0
set_select_events( &thread->obj, POLLIN ); /* start listening to events */
#endif
if (!alloc_client_buffer( thread )) goto error;
return thread;
@ -200,13 +212,41 @@ struct thread *create_thread( int fd, struct process *process )
}
/* handle a client event */
void thread_poll_event( struct object *obj, int event )
static void thread_poll_event( struct object *obj, int event )
{
struct thread *thread = (struct thread *)obj;
assert( obj->ops == &thread_ops );
if (event & (POLLERR | POLLHUP)) kill_thread( thread, 0 );
#if 0
else if (event & POLLIN) read_request( thread );
#endif
}
/* cleanup everything that is no longer needed by a dead thread */
/* used by destroy_thread and kill_thread */
static void cleanup_thread( struct thread *thread )
{
int i;
struct thread_apc *apc;
while ((apc = thread_dequeue_apc( thread, 0 ))) free( apc );
if (thread->buffer != (void *)-1) munmap( thread->buffer, MAX_REQUEST_LENGTH );
if (thread->reply_fd != -1) close( thread->reply_fd );
if (thread->wait_fd != -1) close( thread->wait_fd );
if (thread->request_fd) release_object( thread->request_fd );
for (i = 0; i < MAX_INFLIGHT_FDS; i++)
{
if (thread->inflight[i].client != -1)
{
close( thread->inflight[i].server );
thread->inflight[i].client = thread->inflight[i].server = -1;
}
}
thread->buffer = (void *)-1;
thread->reply_fd = -1;
thread->wait_fd = -1;
thread->request_fd = NULL;
}
/* destroy a thread when its refcount is 0 */
@ -224,11 +264,7 @@ static void destroy_thread( struct object *obj )
while ((apc = thread_dequeue_apc( thread, 0 ))) free( apc );
if (thread->info) release_object( thread->info );
if (thread->queue) release_object( thread->queue );
if (thread->buffer != (void *)-1) munmap( thread->buffer, MAX_REQUEST_LENGTH );
if (thread->reply_fd != -1) close( thread->reply_fd );
if (thread->wait_fd != -1) close( thread->wait_fd );
if (thread->pass_fd != -1) close( thread->pass_fd );
if (thread->request_fd) release_object( thread->request_fd );
cleanup_thread( thread );
}
/* dump a thread on stdout for debugging purposes */
@ -599,6 +635,62 @@ static struct thread_apc *thread_dequeue_apc( struct thread *thread, int system_
return apc;
}
/* add an fd to the inflight list */
/* return list index, or -1 on error */
int thread_add_inflight_fd( struct thread *thread, int client, int server )
{
int i;
if (server == -1) return -1;
if (client == -1)
{
close( server );
return -1;
}
/* first check if we already have an entry for this fd */
for (i = 0; i < MAX_INFLIGHT_FDS; i++)
if (thread->inflight[i].client == client)
{
close( thread->inflight[i].server );
thread->inflight[i].server = server;
return i;
}
/* now find a free spot to store it */
for (i = 0; i < MAX_INFLIGHT_FDS; i++)
if (thread->inflight[i].client == -1)
{
thread->inflight[i].client = client;
thread->inflight[i].server = server;
return i;
}
return -1;
}
/* get an inflight fd and purge it from the list */
/* the fd must be closed when no longer used */
int thread_get_inflight_fd( struct thread *thread, int client )
{
int i, ret;
if (client == -1) return -1;
do
{
for (i = 0; i < MAX_INFLIGHT_FDS; i++)
{
if (thread->inflight[i].client == client)
{
ret = thread->inflight[i].server;
thread->inflight[i].server = thread->inflight[i].client = -1;
return ret;
}
}
} while (!receive_fd( thread->process )); /* in case it is still in the socket buffer */
return -1;
}
/* retrieve an LDT selector entry */
static void get_selector_entry( struct thread *thread, int entry,
unsigned int *base, unsigned int *limit,
@ -649,14 +741,7 @@ void kill_thread( struct thread *thread, int violent_death )
wake_up( &thread->obj, 0 );
detach_thread( thread, violent_death ? SIGTERM : 0 );
remove_select_user( &thread->obj );
release_object( thread->request_fd );
close( thread->reply_fd );
close( thread->wait_fd );
munmap( thread->buffer, MAX_REQUEST_LENGTH );
thread->request_fd = NULL;
thread->reply_fd = -1;
thread->wait_fd = -1;
thread->buffer = (void *)-1;
cleanup_thread( thread );
release_object( thread );
}

View File

@ -36,6 +36,14 @@ struct apc_queue
struct thread_apc *tail;
};
/* descriptor for fds currently in flight from client to server */
struct inflight_fd
{
int client; /* fd on the client side (or -1 if entry is free) */
int server; /* fd on the server side */
};
#define MAX_INFLIGHT_FDS 16 /* max number of fds in flight per thread */
struct thread
{
struct object obj; /* object header */
@ -52,9 +60,9 @@ struct thread
struct thread_wait *wait; /* current wait condition if sleeping */
struct apc_queue system_apc; /* queue of system async procedure calls */
struct apc_queue user_apc; /* queue of user async procedure calls */
struct inflight_fd inflight[MAX_INFLIGHT_FDS]; /* fds currently in flight */
unsigned int error; /* current error code */
struct object *request_fd; /* fd for receiving client requests */
int pass_fd; /* fd to pass to the client */
int reply_fd; /* fd to send a reply to a client */
int wait_fd; /* fd to use to wake a sleeping client */
enum run_state state; /* running state */
@ -96,6 +104,8 @@ extern void wake_up( struct object *obj, int max );
extern int thread_queue_apc( struct thread *thread, struct object *owner, void *func,
enum apc_type type, int system, int nb_args, ... );
extern void thread_cancel_apc( struct thread *thread, struct object *owner, int system );
extern int thread_add_inflight_fd( struct thread *thread, int client, int server );
extern int thread_get_inflight_fd( struct thread *thread, int client );
extern struct thread_snapshot *thread_snap( int *count );
/* ptrace functions */

View File

@ -670,7 +670,8 @@ static void dump_create_file_reply( const struct create_file_request *req )
static void dump_alloc_file_handle_request( const struct alloc_file_handle_request *req )
{
fprintf( stderr, " access=%08x", req->access );
fprintf( stderr, " access=%08x,", req->access );
fprintf( stderr, " fd=%d", req->fd );
}
static void dump_alloc_file_handle_reply( const struct alloc_file_handle_request *req )
@ -1889,11 +1890,9 @@ void trace_request( struct thread *thread, const union generic_request *request
fprintf( stderr, "%08x: %s(", (unsigned int)thread, req_names[req] );
cur_pos = 0;
req_dumpers[req]( request );
fprintf( stderr, " )\n" );
}
else
fprintf( stderr, "%08x: %d(", (unsigned int)thread, req );
if (thread->pass_fd != -1) fprintf( stderr, " ) fd=%d\n", thread->pass_fd );
else fprintf( stderr, " )\n" );
else fprintf( stderr, "%08x: %d(???)\n", (unsigned int)thread, req );
}
void trace_reply( struct thread *thread, const union generic_request *request )