diff --git a/server/object.c b/server/object.c
index b907af3e2bb7baf2d2a4bcd329abff812eacde04..2b86b13a713dc5c207c526e83fccfe8b1a144623 100644
--- a/server/object.c
+++ b/server/object.c
@@ -188,7 +188,7 @@ int no_get_file_info( struct object *obj, struct get_file_info_reply *info )
     return 0;
 }
 
-void default_select_event( int fd, int event, void *private )
+void default_select_event( int event, void *private )
 {
     struct object *obj = (struct object *)private;
     assert( obj );
diff --git a/server/object.h b/server/object.h
index 48c27991d022058065597fe4c2e964452ac046a7..a3f9d5315122c577d594da8caecbe9c8cae59f3e 100644
--- a/server/object.h
+++ b/server/object.h
@@ -74,7 +74,7 @@ extern int no_read_fd( struct object *obj );
 extern int no_write_fd( struct object *obj );
 extern int no_flush( struct object *obj );
 extern int no_get_file_info( struct object *obj, struct get_file_info_reply *info );
-extern void default_select_event( int fd, int event, void *private );
+extern void default_select_event( int event, void *private );
 
 /* request handlers */
 
@@ -84,7 +84,7 @@ struct thread;
 extern void fatal_protocol_error( const char *err, ... );
 extern void call_req_handler( struct thread *thread, enum request req,
                               void *data, int len, int fd );
-extern void call_timeout_handler( struct thread *thread );
+extern void call_timeout_handler( void *thread );
 extern void call_kill_handler( struct thread *thread, int exit_code );
 
 extern void trace_request( enum request req, void *data, int len, int fd );
@@ -103,25 +103,37 @@ extern void trace_reply( struct thread *thread, int type, int pass_fd,
 #define READ_EVENT    1
 #define WRITE_EVENT   2
 
-struct select_ops
+struct select_user
 {
-    void (*event)( int fd, int event, void *private );
-    void (*timeout)( int fd, void *private );
+    int    fd;                              /* user fd */
+    void (*func)(int event, void *private); /* callback function */
+    void  *private;                         /* callback private data */
 };
 
-extern int add_select_user( int fd, int events, const struct select_ops *ops, void *private );
-extern void remove_select_user( int fd );
-extern void set_select_timeout( int fd, struct timeval *when );
-extern void set_select_events( int fd, int events );
-extern void *get_select_private_data( const struct select_ops *ops, int fd );
+extern void register_select_user( struct select_user *user );
+extern void unregister_select_user( struct select_user *user );
+extern void set_select_events( struct select_user *user, int events );
+extern int check_select_events( struct select_user *user, int events );
 extern void select_loop(void);
 
+/* timeout functions */
+
+struct timeout_user;
+
+typedef void (*timeout_callback)( void *private );
+
+extern struct timeout_user *add_timeout_user( struct timeval *when,
+                                              timeout_callback func, void *private );
+extern void remove_timeout_user( struct timeout_user *user );
+extern void make_timeout( struct timeval *when, int timeout );
+
 /* socket functions */
 
-extern int add_client( int client_fd, struct thread *self );
-extern void remove_client( int client_fd, int exit_code );
-extern void set_timeout( int client_fd, struct timeval *when );
-extern int send_reply_v( int client_fd, int type, int pass_fd,
+struct client;
+
+extern struct client *add_client( int client_fd, struct thread *self );
+extern void remove_client( struct client *client, int exit_code );
+extern int send_reply_v( struct client *client, int type, int pass_fd,
                          struct iovec *vec, int veclen );
 
 /* mutex functions */
diff --git a/server/request.c b/server/request.c
index cb99777a29254486fc41252a4ce4afee86a8aaab..d8b6eabf3882ae8a7e2a027251067f644b148615 100644
--- a/server/request.c
+++ b/server/request.c
@@ -33,7 +33,7 @@ void fatal_protocol_error( const char *err, ... )
     fprintf( stderr, "Protocol error:%p: ", current );
     vfprintf( stderr, err, args );
     va_end( args );
-    remove_client( current->client_fd, -2 );
+    remove_client( current->client, -2 );
 }
 
 /* call a request handler */
@@ -68,10 +68,10 @@ void call_req_handler( struct thread *thread, enum request req,
     current = NULL;
 }
 
-/* handle a client timeout (unused for now) */
-void call_timeout_handler( struct thread *thread )
+/* handle a client timeout */
+void call_timeout_handler( void *thread )
 {
-    current = thread;
+    current = (struct thread *)thread;
     if (debug_level) trace_timeout();
     CLEAR_ERROR();
     thread_timeout();
diff --git a/server/select.c b/server/select.c
index 013ba715c49d0303f80ff3646af31c5dad7d7f1a..645f02abfd72af2dd30cb323ed4afaee0146807b 100644
--- a/server/select.c
+++ b/server/select.c
@@ -17,81 +17,82 @@
 
 #include "object.h"
 
-/* select user fd */
-struct user
+struct timeout_user
 {
-    struct timeval           when;    /* timeout expiry (absolute time) */
-    struct user             *next;    /* next in sorted timeout list */
-    struct user             *prev;    /* prev in sorted timeout list */
-    const struct select_ops *ops;   /* user operations list */
-    int                      fd;      /* user fd */
-    void                    *private; /* user private data */
+    struct timeout_user  *next;       /* next in sorted timeout list */
+    struct timeout_user  *prev;       /* prev in sorted timeout list */
+    struct timeval        when;       /* timeout expiry (absolute time) */
+    timeout_callback      callback;   /* callback function */
+    void                 *private;    /* callback private data */
 };
 
-static struct user *users[FD_SETSIZE];      /* users array */
+static struct select_user *users[FD_SETSIZE]; /* users array */
 static fd_set read_set, write_set;          /* current select sets */
 static int nb_users;                        /* current number of users */
 static int max_fd;                          /* max fd in use */
-static struct user *timeout_head;           /* sorted timeouts list head */
-static struct user *timeout_tail;           /* sorted timeouts list tail */
+static struct timeout_user *timeout_head;   /* sorted timeouts list head */
+static struct timeout_user *timeout_tail;   /* sorted timeouts list tail */
 
 
-/* add a user */
-int add_select_user( int fd, int events, const struct select_ops *ops, void *private )
+/* register a user */
+void register_select_user( struct select_user *user )
 {
     int flags;
-    struct user *user = malloc( sizeof(*user) );
-    if (!user) return -1;
-    assert( !users[fd] );
-
-    user->ops          = ops;
-    user->when.tv_sec  = 0;
-    user->when.tv_usec = 0;
-    user->fd           = fd;
-    user->private      = private;
-
-    flags = fcntl( fd, F_GETFL, 0 );
-    fcntl( fd, F_SETFL, flags | O_NONBLOCK );
-
-    users[fd] = user;
-    set_select_events( fd, events );
-    if (fd > max_fd) max_fd = fd;
+    assert( !users[user->fd] );
+
+    flags = fcntl( user->fd, F_GETFL, 0 );
+    fcntl( user->fd, F_SETFL, flags | O_NONBLOCK );
+
+    users[user->fd] = user;
+    if (user->fd > max_fd) max_fd = user->fd;
     nb_users++;
-    return fd;
 }
 
 /* remove a user */
-void remove_select_user( int fd )
+void unregister_select_user( struct select_user *user )
 {
-    struct user *user = users[fd];
-    assert( user );
+    assert( users[user->fd] == user );
 
-    set_select_timeout( fd, 0 );
-    set_select_events( fd, 0 );
-    users[fd] = NULL;
-    if (max_fd == fd) while (max_fd && !users[max_fd]) max_fd--;
+    FD_CLR( user->fd, &read_set );
+    FD_CLR( user->fd, &write_set );
+    users[user->fd] = NULL;
+    if (max_fd == user->fd) while (max_fd && !users[max_fd]) max_fd--;
     nb_users--;
-    free( user );
 }
 
-/* set a user timeout */
-void set_select_timeout( int fd, struct timeval *when )
+/* set the events that select waits for on this fd */
+void set_select_events( struct select_user *user, int events )
 {
-    struct user *user = users[fd];
-    struct user *pos;
-    assert( user );
+    assert( users[user->fd] == user );
+    if (events & READ_EVENT) FD_SET( user->fd, &read_set );
+    else FD_CLR( user->fd, &read_set );
+    if (events & WRITE_EVENT) FD_SET( user->fd, &write_set );
+    else FD_CLR( user->fd, &write_set );
+}
 
-    if (user->when.tv_sec || user->when.tv_usec)
-    {
-        /* there is already a timeout */
-        if (user->next) user->next->prev = user->prev;
-        else timeout_tail = user->prev;
-        if (user->prev) user->prev->next = user->next;
-        else timeout_head = user->next;
-        user->when.tv_sec = user->when.tv_usec = 0;
-    }
-    if (!when) return;  /* no timeout */
-    user->when = *when;
+/* check if events are pending */
+int check_select_events( struct select_user *user, int events )
+{
+    fd_set read_fds, write_fds;
+    struct timeval tv = { 0, 0 };
+
+    FD_ZERO( &read_fds );
+    FD_ZERO( &write_fds );
+    if (events & READ_EVENT) FD_SET( user->fd, &read_fds );
+    if (events & WRITE_EVENT) FD_SET( user->fd, &write_fds );
+    return select( user->fd + 1, &read_fds, &write_fds, NULL, &tv ) > 0;
+}
+
+/* add a timeout user */
+struct timeout_user *add_timeout_user( struct timeval *when, timeout_callback func, void *private )
+{
+    struct timeout_user *user;
+    struct timeout_user *pos;
+
+    if (!(user = mem_alloc( sizeof(*user) ))) return NULL;
+    user->when     = *when;
+    user->callback = func;
+    user->private  = private;
 
     /* Now insert it in the linked list */
 
@@ -117,24 +118,41 @@ void set_select_timeout( int fd, struct timeval *when )
         user->prev = timeout_tail;
         timeout_tail = user;
     }
+    return user;
 }
 
-/* set the events that select waits for on this fd */
-void set_select_events( int fd, int events )
+/* remove a timeout user */
+void remove_timeout_user( struct timeout_user *user )
 {
-    if (events & READ_EVENT) FD_SET( fd, &read_set );
-    else FD_CLR( fd, &read_set );
-    if (events & WRITE_EVENT) FD_SET( fd, &write_set );
-    else FD_CLR( fd, &write_set );
+    if (user->next) user->next->prev = user->prev;
+    else timeout_tail = user->prev;
+    if (user->prev) user->prev->next = user->next;
+    else timeout_head = user->next;
+    free( user );
 }
 
-/* get a user private data, checking the type */
-void *get_select_private_data( const struct select_ops *ops, int fd )
+/* make an absolute timeout value from a relative timeout in milliseconds */
+void make_timeout( struct timeval *when, int timeout )
 {
-    struct user *user = users[fd];
-    assert( user );
-    assert( user->ops == ops );
-    return user->private;
+    gettimeofday( when, 0 );
+    if (!timeout) return;
+    if ((when->tv_usec += (timeout % 1000) * 1000) >= 1000000)
+    {
+        when->tv_usec -= 1000000;
+        when->tv_sec++;
+    }
+    when->tv_sec += timeout / 1000;
+}
+
+/* handle an expired timeout */
+static void handle_timeout( struct timeout_user *user )
+{
+    if (user->next) user->next->prev = user->prev;
+    else timeout_tail = user->prev;
+    if (user->prev) user->prev->next = user->next;
+    else timeout_head = user->next;
+    user->callback( user->private );
+    free( user );
 }
 
 /* server main loop */
@@ -148,12 +166,6 @@ void select_loop(void)
     while (nb_users)
     {
         fd_set read = read_set, write = write_set;
-#if 0
-        printf( "select: " );
-        for (i = 0; i <= max_fd; i++) printf( "%c", FD_ISSET( i, &read_set ) ? 'r' :
-                                                    (FD_ISSET( i, &write_set ) ? 'w' : '-') );
-        printf( "\n" );
-#endif
         if (timeout_head)
         {
             struct timeval tv, now;
@@ -162,7 +174,7 @@ void select_loop(void)
                 ((timeout_head->when.tv_sec == now.tv_sec) &&
                  (timeout_head->when.tv_usec < now.tv_usec)))
             {
-                timeout_head->ops->timeout( timeout_head->fd, timeout_head->private );
+                handle_timeout( timeout_head );
                 continue;
             }
             tv.tv_sec = timeout_head->when.tv_sec - now.tv_sec;
@@ -171,10 +183,22 @@ void select_loop(void)
                 tv.tv_usec += 1000000;
                 tv.tv_sec--;
             }
+#if 0
+            printf( "select: " );
+            for (i = 0; i <= max_fd; i++) printf( "%c", FD_ISSET( i, &read_set ) ? 'r' :
+                                                  (FD_ISSET( i, &write_set ) ? 'w' : '-') );
+            printf( " timeout %d.%06d\n", tv.tv_sec, tv.tv_usec );
+#endif
             ret = select( max_fd + 1, &read, &write, NULL, &tv );
         }
         else  /* no timeout */
         {
+#if 0
+            printf( "select: " );
+            for (i = 0; i <= max_fd; i++) printf( "%c", FD_ISSET( i, &read_set ) ? 'r' :
+                                                  (FD_ISSET( i, &write_set ) ? 'w' : '-') );
+            printf( " no timeout\n" );
+#endif
             ret = select( max_fd + 1, &read, &write, NULL, NULL );
         }
 
@@ -194,7 +218,7 @@ void select_loop(void)
                called in an earlier pass of this loop might have removed 
                the current user ... */
             if (event && users[i]) 
-                users[i]->ops->event( i, event, users[i]->private );
+                users[i]->func( event, users[i]->private );
         }
     }
 }
diff --git a/server/socket.c b/server/socket.c
index 7b2033d4bcf3ef4113eb45697ffcfaf8567bae51..98c1f6db20925e3f194f9c8bdecce94370bf6c46 100644
--- a/server/socket.c
+++ b/server/socket.c
@@ -37,13 +37,15 @@ enum state
 /* client structure */
 struct client
 {
-    enum state         state;        /* client state */
-    unsigned int       seq;          /* current sequence number */
-    struct header      head;         /* current msg header */
-    char              *data;         /* current msg data */
-    int                count;        /* bytes sent/received so far */
-    int                pass_fd;      /* fd to pass to and from the client */
-    struct thread     *self;         /* client thread (opaque pointer) */
+    enum state           state;      /* client state */
+    struct select_user   select;     /* select user */
+    unsigned int         seq;        /* current sequence number */
+    struct header        head;       /* current msg header */
+    char                *data;       /* current msg data */
+    int                  count;      /* bytes sent/received so far */
+    int                  pass_fd;    /* fd to pass to and from the client */
+    struct thread       *self;       /* client thread (opaque pointer) */
+    struct timeout_user *timeout;    /* current timeout (opaque pointer) */
 };
 
 
@@ -54,12 +56,12 @@ struct client
 
 
 /* signal a client protocol error */
-static void protocol_error( int client_fd, const char *err, ... )
+static void protocol_error( struct client *client, const char *err, ... )
 {
     va_list args;
 
     va_start( args, err );
-    fprintf( stderr, "Protocol error:%d: ", client_fd );
+    fprintf( stderr, "Protocol error:%d: ", client->select.fd );
     vfprintf( stderr, err, args );
     va_end( args );
 }
@@ -131,7 +133,7 @@ static void do_write( struct client *client, int client_fd )
     if (ret == -1)
     {
         if (errno != EPIPE) perror("sendmsg");
-        remove_client( client_fd, BROKEN_PIPE );
+        remove_client( client, BROKEN_PIPE );
         return;
     }
     if (client->pass_fd != -1)  /* We sent the fd, now we can close it */
@@ -147,7 +149,7 @@ static void do_write( struct client *client, int client_fd )
     client->count = 0;
     client->state = RUNNING;
     client->seq++;
-    set_select_events( client_fd, READ_EVENT );
+    set_select_events( &client->select, READ_EVENT );
 }
 
 
@@ -191,7 +193,7 @@ static void do_read( struct client *client, int client_fd )
         if (!client->data &&
             !(client->data = malloc(client->head.len-sizeof(client->head))))
         {
-            remove_client( client_fd, OUT_OF_MEMORY );
+            remove_client( client, OUT_OF_MEMORY );
             return;
         }
         vec.iov_base = client->data + client->count - sizeof(client->head);
@@ -202,7 +204,7 @@ static void do_read( struct client *client, int client_fd )
     if (ret == -1)
     {
         perror("recvmsg");
-        remove_client( client_fd, BROKEN_PIPE );
+        remove_client( client, BROKEN_PIPE );
         return;
     }
 #ifndef HAVE_MSGHDR_ACCRIGHTS
@@ -216,7 +218,7 @@ static void do_read( struct client *client, int client_fd )
     }
     else if (!ret)  /* closed pipe */
     {
-        remove_client( client_fd, BROKEN_PIPE );
+        remove_client( client, BROKEN_PIPE );
         return;
     }
 
@@ -231,17 +233,17 @@ static void do_read( struct client *client, int client_fd )
     /* sanity checks */
     if (client->head.seq != client->seq)
     {
-        protocol_error( client_fd, "bad sequence %08x instead of %08x\n",
+        protocol_error( client, "bad sequence %08x instead of %08x\n",
                         client->head.seq, client->seq );
-        remove_client( client_fd, PROTOCOL_ERROR );
+        remove_client( client, PROTOCOL_ERROR );
         return;
     }
     if ((client->head.len < sizeof(client->head)) ||
         (client->head.len > MAX_MSG_LENGTH + sizeof(client->head)))
     {
-        protocol_error( client_fd, "bad header length %08x\n",
+        protocol_error( client, "bad header length %08x\n",
                         client->head.len );
-        remove_client( client_fd, PROTOCOL_ERROR );
+        remove_client( client, PROTOCOL_ERROR );
         return;
     }
 
@@ -270,66 +272,51 @@ static void do_read( struct client *client, int client_fd )
     }
 }
 
-/* handle a client timeout */
-static void client_timeout( int client_fd, void *private )
-{
-    struct client *client = (struct client *)private;
-    set_select_timeout( client_fd, 0 );  /* Remove the timeout */
-    call_timeout_handler( client->self );
-}
-
 /* handle a client event */
-static void client_event( int client_fd, int event, void *private )
+static void client_event( int event, void *private )
 {
     struct client *client = (struct client *)private;
-    if (event & WRITE_EVENT)
-        do_write( client, client_fd );
-    if (event & READ_EVENT)
-        do_read( client, client_fd );
+    if (event & WRITE_EVENT) do_write( client, client->select.fd );
+    if (event & READ_EVENT) do_read( client, client->select.fd );
 }
 
-static const struct select_ops client_ops =
-{
-    client_event,
-    client_timeout
-};
 
 /*******************************************************************/
 /* server-side exported functions                                  */
 
 /* add a client */
-int add_client( int client_fd, struct thread *self )
+struct client *add_client( int fd, struct thread *self )
 {
-    struct client *client = malloc( sizeof(*client) );
-    if (!client) return -1;
+    struct client *client = mem_alloc( sizeof(*client) );
+    if (!client) return NULL;
 
     client->state                = RUNNING;
+    client->select.fd            = fd;
+    client->select.func          = client_event;
+    client->select.private       = client;
     client->seq                  = 0;
     client->head.len             = 0;
     client->head.type            = 0;
     client->count                = 0;
     client->data                 = NULL;
     client->self                 = self;
+    client->timeout              = NULL;
     client->pass_fd              = -1;
-
-    if (add_select_user( client_fd, READ_EVENT, &client_ops, client ) == -1)
-    {
-        free( client );
-        return -1;
-    }
-    return client_fd;
+    register_select_user( &client->select );
+    set_select_events( &client->select, READ_EVENT );
+    return client;
 }
 
 /* remove a client */
-void remove_client( int client_fd, int exit_code )
+void remove_client( struct client *client, int exit_code )
 {
-    struct client *client = (struct client *)get_select_private_data( &client_ops, client_fd );
     assert( client );
 
     call_kill_handler( client->self, exit_code );
 
-    remove_select_user( client_fd );
-    close( client_fd );
+    if (client->timeout) remove_timeout_user( client->timeout );
+    unregister_select_user( &client->select );
+    close( client->select.fd );
 
     /* Purge messages */
     if (client->data) free( client->data );
@@ -338,13 +325,12 @@ void remove_client( int client_fd, int exit_code )
 }
 
 /* send a reply to a client */
-int send_reply_v( int client_fd, int type, int pass_fd,
+int send_reply_v( struct client *client, int type, int pass_fd,
                   struct iovec *vec, int veclen )
 {
     int i;
     unsigned int len;
     char *p;
-    struct client *client = (struct client *)get_select_private_data( &client_ops, client_fd );
 
     assert( client );
     assert( client->state == WAITING );
@@ -369,6 +355,6 @@ int send_reply_v( int client_fd, int type, int pass_fd,
     }
 
     client->state = READING;
-    set_select_events( client_fd, WRITE_EVENT );
+    set_select_events( &client->select, WRITE_EVENT );
     return 0;
 }