From 580da246f54b25080e7a5b7d17a9aea49aaa0fef Mon Sep 17 00:00:00 2001
From: Alexandre Julliard <julliard@winehq.org>
Date: Wed, 12 Mar 2003 22:38:14 +0000
Subject: [PATCH] Added an inode object to keep track of all file descriptors
 open for a given file. Plugged a couple of potential file descriptor leaks.

---
 server/fd.c         | 233 ++++++++++++++++++++++++++++++++++++++++----
 server/file.c       |  62 ++++++------
 server/file.h       |   6 +-
 server/named_pipe.c |   4 +-
 server/pipe.c       |   2 +-
 server/process.c    |   2 +-
 server/request.c    |   2 +-
 server/serial.c     |  38 ++++----
 server/smb.c        |  25 +++--
 server/sock.c       |   4 +-
 server/thread.c     |   6 +-
 11 files changed, 289 insertions(+), 95 deletions(-)

diff --git a/server/fd.c b/server/fd.c
index 388c807e5f7..dc6f1601763 100644
--- a/server/fd.c
+++ b/server/fd.c
@@ -22,6 +22,7 @@
 #include "config.h"
 
 #include <assert.h>
+#include <fcntl.h>
 #include <signal.h>
 #include <stdio.h>
 #include <string.h>
@@ -29,6 +30,7 @@
 #ifdef HAVE_SYS_POLL_H
 #include <sys/poll.h>
 #endif
+#include <sys/stat.h>
 #include <sys/time.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -40,14 +42,25 @@
 #include "request.h"
 #include "console.h"
 
+/* file descriptor object */
+
+/* closed_fd is used to keep track of the unix fd belonging to a closed fd object */
+struct closed_fd
+{
+    struct closed_fd *next;   /* next fd in close list */
+    int               fd;     /* the unix file descriptor */
+};
+
 struct fd
 {
-    struct object        obj;        /* object header */
-    const struct fd_ops *fd_ops;     /* file descriptor operations */
-    struct object       *user;       /* object using this file descriptor */
-    int                  unix_fd;    /* unix file descriptor */
-    int                  poll_index; /* index of fd in poll array */
-    int                  mode;       /* file protection mode */
+    struct object        obj;         /* object header */
+    const struct fd_ops *fd_ops;      /* file descriptor operations */
+    struct inode        *inode;       /* inode that this fd belongs to */
+    struct list          inode_entry; /* entry in inode fd list */
+    struct closed_fd    *closed;      /* structure to store the unix fd at destroy time */
+    struct object       *user;        /* object using this file descriptor */
+    int                  unix_fd;     /* unix file descriptor */
+    int                  poll_index;  /* index of fd in poll array */
 };
 
 static void fd_dump( struct object *obj, int verbose );
@@ -65,6 +78,42 @@ static const struct object_ops fd_ops =
     fd_destroy                /* destroy */
 };
 
+/* inode object */
+
+struct inode
+{
+    struct object       obj;        /* object header */
+    struct list         entry;      /* inode hash list entry */
+    unsigned int        hash;       /* hashing code */
+    dev_t               dev;        /* device number */
+    ino_t               ino;        /* inode number */
+    struct list         open;       /* list of open file descriptors */
+    struct closed_fd   *closed;     /* list of file descriptors to close at destroy time */
+};
+
+static void inode_dump( struct object *obj, int verbose );
+static void inode_destroy( struct object *obj );
+
+static const struct object_ops inode_ops =
+{
+    sizeof(struct inode),     /* size */
+    inode_dump,               /* dump */
+    no_add_queue,             /* add_queue */
+    NULL,                     /* remove_queue */
+    NULL,                     /* signaled */
+    NULL,                     /* satisfied */
+    no_get_fd,                /* get_fd */
+    inode_destroy             /* destroy */
+};
+
+#define DUMP_LONG_LONG(val) do { \
+    if (sizeof(val) > sizeof(unsigned long) && (val) > ~0UL) \
+        fprintf( stderr, "%lx%08lx", (unsigned long)((val) >> 32), (unsigned long)(val) ); \
+    else \
+        fprintf( stderr, "%lx", (unsigned long)(val) ); \
+  } while (0)
+
+
 
 /****************************************************************/
 /* timeouts support */
@@ -311,21 +360,104 @@ void main_loop(void)
     }
 }
 
+
+/****************************************************************/
+/* inode functions */
+
+#define HASH_SIZE 37
+
+static struct list inode_hash[HASH_SIZE];
+
+
+static void inode_dump( struct object *obj, int verbose )
+{
+    struct inode *inode = (struct inode *)obj;
+    fprintf( stderr, "Inode dev=" );
+    DUMP_LONG_LONG( inode->dev );
+    fprintf( stderr, " ino=" );
+    DUMP_LONG_LONG( inode->ino );
+    fprintf( stderr, "\n" );
+}
+
+static void inode_destroy( struct object *obj )
+{
+    struct inode *inode = (struct inode *)obj;
+
+    assert( !list_head(&inode->open) );
+
+    list_remove( &inode->entry );
+    while (inode->closed)
+    {
+        struct closed_fd *fd = inode->closed;
+        inode->closed = fd->next;
+        close( fd->fd );
+        free( fd );
+    }
+}
+
+/* retrieve the inode object for a given fd, creating it if needed */
+static struct inode *get_inode( dev_t dev, ino_t ino )
+{
+    struct list *ptr;
+    struct inode *inode;
+    unsigned int hash = (dev ^ ino) % HASH_SIZE;
+
+    if (inode_hash[hash].next)
+    {
+        LIST_FOR_EACH( ptr, &inode_hash[hash] )
+        {
+            inode = LIST_ENTRY( ptr, struct inode, entry );
+            if (inode->dev == dev && inode->ino == ino)
+                return (struct inode *)grab_object( inode );
+        }
+    }
+    else list_init( &inode_hash[hash] );
+
+    /* not found, create it */
+    if ((inode = alloc_object( &inode_ops )))
+    {
+        inode->hash   = hash;
+        inode->dev    = dev;
+        inode->ino    = ino;
+        inode->closed = NULL;
+        list_init( &inode->open );
+        list_add_head( &inode_hash[hash], &inode->entry );
+    }
+    return inode;
+}
+
+/* add fd to the indoe list of file descriptors to close */
+static void inode_add_closed_fd( struct inode *inode, struct closed_fd *fd )
+{
+    fd->next = inode->closed;
+    inode->closed = fd;
+}
+
+
 /****************************************************************/
 /* file descriptor functions */
 
 static void fd_dump( struct object *obj, int verbose )
 {
     struct fd *fd = (struct fd *)obj;
-    fprintf( stderr, "Fd unix_fd=%d mode=%06o user=%p\n", fd->unix_fd, fd->mode, fd->user );
+    fprintf( stderr, "Fd unix_fd=%d user=%p\n", fd->unix_fd, fd->user );
 }
 
 static void fd_destroy( struct object *obj )
 {
     struct fd *fd = (struct fd *)obj;
 
+    list_remove( &fd->inode_entry );
     if (fd->poll_index != -1) remove_poll_user( fd, fd->poll_index );
-    close( fd->unix_fd );
+    if (fd->inode)
+    {
+        inode_add_closed_fd( fd->inode, fd->closed );
+        release_object( fd->inode );
+    }
+    else  /* no inode, close it right away */
+    {
+        if (fd->unix_fd != -1) close( fd->unix_fd );
+    }
 }
 
 /* set the events that select waits for on this fd */
@@ -346,24 +478,22 @@ void set_fd_events( struct fd *fd, int events )
     }
 }
 
-/* allocate an fd object */
-/* if the function fails the unix fd is closed */
-struct fd *alloc_fd( const struct fd_ops *fd_user_ops, int unix_fd, struct object *user )
+/* allocate an fd object, without setting the unix fd yet */
+struct fd *alloc_fd( const struct fd_ops *fd_user_ops, struct object *user )
 {
     struct fd *fd = alloc_object( &fd_ops );
 
-    if (!fd)
-    {
-        close( unix_fd );
-        return NULL;
-    }
+    if (!fd) return NULL;
+
     fd->fd_ops     = fd_user_ops;
     fd->user       = user;
-    fd->unix_fd    = unix_fd;
+    fd->inode      = NULL;
+    fd->closed     = NULL;
+    fd->unix_fd    = -1;
     fd->poll_index = -1;
-    fd->mode       = 0;
+    list_init( &fd->inode_entry );
 
-    if ((unix_fd != -1) && ((fd->poll_index = add_poll_user( fd )) == -1))
+    if ((fd->poll_index = add_poll_user( fd )) == -1)
     {
         release_object( fd );
         return NULL;
@@ -371,6 +501,71 @@ struct fd *alloc_fd( const struct fd_ops *fd_user_ops, int unix_fd, struct objec
     return fd;
 }
 
+/* open() wrapper using a struct fd */
+/* the fd must have been created with alloc_fd */
+/* on error the fd object is released */
+struct fd *open_fd( struct fd *fd, const char *name, int flags, int *mode )
+{
+    struct stat st;
+    struct closed_fd *closed_fd;
+
+    assert( fd->unix_fd == -1 );
+
+    if (!(closed_fd = mem_alloc( sizeof(*closed_fd) )))
+    {
+        release_object( fd );
+        return NULL;
+    }
+    if ((fd->unix_fd = open( name, flags, *mode )) == -1)
+    {
+        file_set_error();
+        release_object( fd );
+        free( closed_fd );
+        return NULL;
+    }
+    closed_fd->fd = fd->unix_fd;
+    fstat( fd->unix_fd, &st );
+    *mode = st.st_mode;
+
+    if (S_ISREG(st.st_mode))  /* only bother with an inode for normal files */
+    {
+        struct inode *inode = get_inode( st.st_dev, st.st_ino );
+
+        if (!inode)
+        {
+            /* we can close the fd because there are no others open on the same file,
+             * otherwise we wouldn't have failed to allocate a new inode
+             */
+            release_object( fd );
+            free( closed_fd );
+            return NULL;
+        }
+        fd->inode = inode;
+        fd->closed = closed_fd;
+        list_add_head( &inode->open, &fd->inode_entry );
+    }
+    else
+    {
+        free( closed_fd );
+    }
+    return fd;
+}
+
+/* create an fd for an anonymous file */
+/* if the function fails the unix fd is closed */
+struct fd *create_anonymous_fd( const struct fd_ops *fd_user_ops, int unix_fd, struct object *user )
+{
+    struct fd *fd = alloc_fd( fd_user_ops, user );
+
+    if (fd)
+    {
+        fd->unix_fd = unix_fd;
+        return fd;
+    }
+    close( unix_fd );
+    return NULL;
+}
+
 /* retrieve the object that is using an fd */
 void *get_fd_user( struct fd *fd )
 {
diff --git a/server/file.c b/server/file.c
index 2544bbaf019..ba853e9fe2d 100644
--- a/server/file.c
+++ b/server/file.c
@@ -149,7 +149,7 @@ static struct file *create_file_for_fd( int fd, unsigned int access, unsigned in
             init_async_queue (&file->read_q);
             init_async_queue (&file->write_q);
         }
-        if (!(file->fd = alloc_fd( &file_fd_ops, fd, &file->obj )))
+        if (!(file->fd = create_anonymous_fd( &file_fd_ops, fd, &file->obj )))
         {
             release_object( file );
             return NULL;
@@ -165,9 +165,7 @@ static struct file *create_file( const char *nameptr, size_t len, unsigned int a
 {
     struct file *file;
     int hash, flags;
-    struct stat st;
     char *name;
-    int fd = -1;
     mode_t mode;
 
     if (!(name = mem_alloc( len + 1 ))) return NULL;
@@ -200,31 +198,38 @@ static struct file *create_file( const char *nameptr, size_t len, unsigned int a
         (!strcasecmp( name + len - 4, ".exe" ) || !strcasecmp( name + len - 4, ".com" )))
         mode |= 0111;
 
-    /* FIXME: should set error to STATUS_OBJECT_NAME_COLLISION if file existed before */
-    if ((fd = open( name, flags | O_NONBLOCK | O_LARGEFILE, mode )) == -1 )
-        goto file_error;
-    /* refuse to open a directory */
-    if (fstat( fd, &st ) == -1) goto file_error;
-    if (S_ISDIR(st.st_mode))
+    if (!(file = alloc_object( &file_ops ))) goto error;
+
+    file->access     = access;
+    file->flags      = attrs;
+    file->sharing    = sharing;
+    file->drive_type = drive_type;
+    file->name       = name;
+    file->next       = file_hash[hash];
+    file_hash[hash]  = file;
+    if (file->flags & FILE_FLAG_OVERLAPPED)
     {
-        set_error( STATUS_ACCESS_DENIED );
-        goto error;
+        init_async_queue (&file->read_q);
+        init_async_queue (&file->write_q);
     }
 
-    if (!(file = create_file_for_fd( fd, access, sharing, attrs, drive_type )))
+    /* FIXME: should set error to STATUS_OBJECT_NAME_COLLISION if file existed before */
+    if (!(file->fd = alloc_fd( &file_fd_ops, &file->obj )) ||
+        !(file->fd = open_fd( file->fd, name, flags | O_NONBLOCK | O_LARGEFILE, &mode )))
     {
-        free( name );
+        release_object( file );
+        return NULL;
+    }
+    /* refuse to open a directory */
+    if (S_ISDIR(mode))
+    {
+        set_error( STATUS_ACCESS_DENIED );
+        release_object( file );
         return NULL;
     }
-    file->name = name;
-    file->next = file_hash[hash];
-    file_hash[hash] = file;
     return file;
 
- file_error:
-    file_set_error();
  error:
-    if (fd != -1) close( fd );
     free( name );
     return NULL;
 }
@@ -241,29 +246,20 @@ int get_file_drive_type( struct file *file )
     return file->drive_type;
 }
 
-/* Create an anonymous Unix file */
-int create_anonymous_file(void)
+/* create a temp file for anonymous mappings */
+struct file *create_temp_file( int access )
 {
-    char tmpfn[21];
+    char tmpfn[16];
     int fd;
 
-    sprintf(tmpfn,"/tmp/anonmap.XXXXXX");
+    sprintf( tmpfn, "anonmap.XXXXXX" );  /* create it in the server directory */
     fd = mkstemp(tmpfn);
     if (fd == -1)
     {
         file_set_error();
-        return -1;
+        return NULL;
     }
     unlink( tmpfn );
-    return fd;
-}
-
-/* Create a temp file for anonymous mappings */
-struct file *create_temp_file( int access )
-{
-    int fd;
-
-    if ((fd = create_anonymous_file()) == -1) return NULL;
     return create_file_for_fd( fd, access, 0, 0, DRIVE_FIXED );
 }
 
diff --git a/server/file.h b/server/file.h
index 55fc3a46e0f..ff59f37de73 100644
--- a/server/file.h
+++ b/server/file.h
@@ -42,7 +42,10 @@ struct fd_ops
 
 /* file descriptor functions */
 
-extern struct fd *alloc_fd( const struct fd_ops *fd_ops, int unix_fd, struct object *user );
+extern struct fd *alloc_fd( const struct fd_ops *fd_user_ops, struct object *user );
+extern struct fd *open_fd( struct fd *fd, const char *name, int flags, int *mode );
+extern struct fd *create_anonymous_fd( const struct fd_ops *fd_user_ops,
+                                       int unix_fd, struct object *user );
 extern void *get_fd_user( struct fd *fd );
 extern int get_unix_fd( struct fd *fd );
 extern void fd_poll_event( struct fd *fd, int event );
@@ -85,7 +88,6 @@ extern int get_file_unix_fd( struct file *file );
 extern int is_same_file( struct file *file1, struct file *file2 );
 extern int get_file_drive_type( struct file *file );
 extern int grow_file( struct file *file, int size_high, int size_low );
-extern int create_anonymous_file(void);
 extern struct file *create_temp_file( int access );
 extern void file_set_error(void);
 
diff --git a/server/named_pipe.c b/server/named_pipe.c
index b1e2e66d772..05cdda78de7 100644
--- a/server/named_pipe.c
+++ b/server/named_pipe.c
@@ -361,8 +361,8 @@ DECL_HANDLER(open_named_pipe)
 
         if(!socketpair(PF_UNIX, SOCK_STREAM, 0, fds))
         {
-            user->fd = alloc_fd( &pipe_user_fd_ops, fds[1], &user->obj );
-            partner->fd = alloc_fd( &pipe_user_fd_ops, fds[0], &partner->obj );
+            user->fd = create_anonymous_fd( &pipe_user_fd_ops, fds[1], &user->obj );
+            partner->fd = create_anonymous_fd( &pipe_user_fd_ops, fds[0], &partner->obj );
             if (user->fd && partner->fd)
             {
                 notify_waiter(partner,STATUS_SUCCESS);
diff --git a/server/pipe.c b/server/pipe.c
index 3e01ee9ae2b..52580fc721e 100644
--- a/server/pipe.c
+++ b/server/pipe.c
@@ -85,7 +85,7 @@ static struct pipe *create_pipe_side( int fd, int side )
         pipe->other  = NULL;
         pipe->side   = side;
     }
-    if (!(pipe->fd = alloc_fd( &pipe_fd_ops, fd, &pipe->obj )))
+    if (!(pipe->fd = create_anonymous_fd( &pipe_fd_ops, fd, &pipe->obj )))
     {
         release_object( pipe );
         return NULL;
diff --git a/server/process.c b/server/process.c
index 9f26b8d43ba..f26b62a47aa 100644
--- a/server/process.c
+++ b/server/process.c
@@ -302,7 +302,7 @@ struct thread *create_process( int fd )
     first_process = process;
 
     if (!(process->id = alloc_ptid( process ))) goto error;
-    if (!(process->msg_fd = alloc_fd( &process_fd_ops, fd, &process->obj ))) goto error;
+    if (!(process->msg_fd = create_anonymous_fd( &process_fd_ops, fd, &process->obj ))) goto error;
 
     /* create the main thread */
     if (pipe( request_pipe ) == -1)
diff --git a/server/request.c b/server/request.c
index a47827bd4d4..a926f5c96a4 100644
--- a/server/request.c
+++ b/server/request.c
@@ -696,7 +696,7 @@ static void acquire_lock(void)
     if (listen( fd, 5 ) == -1) fatal_perror( "listen" );
 
     if (!(master_socket = alloc_object( &master_socket_ops )) ||
-        !(master_socket->fd = alloc_fd( &master_socket_fd_ops, fd, &master_socket->obj )))
+        !(master_socket->fd = create_anonymous_fd( &master_socket_fd_ops, fd, &master_socket->obj )))
         fatal_error( "out of memory\n" );
     master_socket->timeout = NULL;
     set_fd_events( master_socket->fd, POLLIN );
diff --git a/server/serial.c b/server/serial.c
index 2f9130b089d..3831b7e2240 100644
--- a/server/serial.c
+++ b/server/serial.c
@@ -150,25 +150,27 @@ static struct serial *create_serial( const char *nameptr, size_t len, unsigned i
        if(0>fcntl(fd, F_SETFL, 0))
            perror("fcntl");
 
-    if ((serial = alloc_object( &serial_ops )))
+    if (!(serial = alloc_object( &serial_ops )))
     {
-        serial->attrib       = attributes;
-        serial->access       = access;
-        serial->readinterval = 0;
-        serial->readmult     = 0;
-        serial->readconst    = 0;
-        serial->writemult    = 0;
-        serial->writeconst   = 0;
-        serial->eventmask    = 0;
-        serial->commerror    = 0;
-        init_async_queue(&serial->read_q);
-        init_async_queue(&serial->write_q);
-        init_async_queue(&serial->wait_q);
-        if (!(serial->fd = alloc_fd( &serial_fd_ops, fd, &serial->obj )))
-        {
-            release_object( serial );
-            return NULL;
-        }
+        close( fd );
+        return NULL;
+    }
+    serial->attrib       = attributes;
+    serial->access       = access;
+    serial->readinterval = 0;
+    serial->readmult     = 0;
+    serial->readconst    = 0;
+    serial->writemult    = 0;
+    serial->writeconst   = 0;
+    serial->eventmask    = 0;
+    serial->commerror    = 0;
+    init_async_queue(&serial->read_q);
+    init_async_queue(&serial->write_q);
+    init_async_queue(&serial->wait_q);
+    if (!(serial->fd = create_anonymous_fd( &serial_fd_ops, fd, &serial->obj )))
+    {
+        release_object( serial );
+        return NULL;
     }
     return serial;
 }
diff --git a/server/smb.c b/server/smb.c
index ef94dd5f6e1..f31cebe7820 100644
--- a/server/smb.c
+++ b/server/smb.c
@@ -167,22 +167,21 @@ DECL_HANDLER(create_smb)
         return;
     }
 
-    smb = alloc_object( &smb_ops );
-    if (smb)
+    if (!(smb = alloc_object( &smb_ops )))
+    {
+        close( fd );
+        return;
+    }
+    smb->tree_id = req->tree_id;
+    smb->user_id = req->user_id;
+    smb->dialect = req->dialect;
+    smb->file_id = req->file_id;
+    smb->offset = 0;
+    if ((smb->fd = create_anonymous_fd( &smb_fd_ops, fd, &smb->obj )))
     {
-        smb->tree_id = req->tree_id;
-        smb->user_id = req->user_id;
-        smb->dialect = req->dialect;
-        smb->file_id = req->file_id;
-        smb->offset = 0;
-        if (!(smb->fd = alloc_fd( &smb_fd_ops, fd, &smb->obj )))
-        {
-            release_object( smb );
-            return;
-        }
         reply->handle = alloc_handle( current->process, smb, GENERIC_READ, 0);
-        release_object( smb );
     }
+    release_object( smb );
 }
 
 DECL_HANDLER(get_smb_info)
diff --git a/server/sock.c b/server/sock.c
index 51d7ec70f1b..6161d23bd03 100644
--- a/server/sock.c
+++ b/server/sock.c
@@ -616,7 +616,7 @@ static struct object *create_socket( int family, int type, int protocol, unsigne
     sock->message = 0;
     sock->wparam  = 0;
     sock->deferred = NULL;
-    if (!(sock->fd = alloc_fd( &sock_fd_ops, sockfd, &sock->obj )))
+    if (!(sock->fd = create_anonymous_fd( &sock_fd_ops, sockfd, &sock->obj )))
     {
         release_object( sock );
         return NULL;
@@ -686,7 +686,7 @@ static struct sock *accept_socket( obj_handle_t handle )
         if (sock->event) acceptsock->event = (struct event *)grab_object( sock->event );
         acceptsock->flags = sock->flags;
         acceptsock->deferred = 0;
-        if (!(acceptsock->fd = alloc_fd( &sock_fd_ops, acceptfd, &acceptsock->obj )))
+        if (!(acceptsock->fd = create_anonymous_fd( &sock_fd_ops, acceptfd, &acceptsock->obj )))
         {
             release_object( acceptsock );
             release_object( sock );
diff --git a/server/thread.c b/server/thread.c
index 5d41a4a8ab5..3db8b1e1120 100644
--- a/server/thread.c
+++ b/server/thread.c
@@ -170,7 +170,7 @@ struct thread *create_thread( int fd, struct process *process )
         release_object( thread );
         return NULL;
     }
-    if (!(thread->request_fd = alloc_fd( &thread_fd_ops, fd, &thread->obj )))
+    if (!(thread->request_fd = create_anonymous_fd( &thread_fd_ops, fd, &thread->obj )))
     {
         release_object( thread );
         return NULL;
@@ -840,8 +840,8 @@ DECL_HANDLER(init_thread)
         fatal_protocol_error( current, "bad wait fd\n" );
         goto error;
     }
-    current->reply_fd = alloc_fd( &thread_fd_ops, reply_fd, &current->obj );
-    current->wait_fd  = alloc_fd( &thread_fd_ops, wait_fd, &current->obj );
+    current->reply_fd = create_anonymous_fd( &thread_fd_ops, reply_fd, &current->obj );
+    current->wait_fd  = create_anonymous_fd( &thread_fd_ops, wait_fd, &current->obj );
     if (!current->reply_fd || !current->wait_fd) return;
 
     current->unix_pid = req->unix_pid;
-- 
GitLab