Added an inode object to keep track of all file descriptors open for a
given file.
Plugged a couple of potential file descriptor leaks.
diff --git a/server/fd.c b/server/fd.c
index 388c807..dc6f160 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 @@
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 @@
}
}
+
+/****************************************************************/
+/* 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 @@
}
}
-/* 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 @@
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 2544bba..ba853e9 100644
--- a/server/file.c
+++ b/server/file.c
@@ -149,7 +149,7 @@
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 @@
{
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 @@
(!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;
}
- file->name = name;
- file->next = file_hash[hash];
- file_hash[hash] = file;
+ /* refuse to open a directory */
+ if (S_ISDIR(mode))
+ {
+ set_error( STATUS_ACCESS_DENIED );
+ release_object( file );
+ return NULL;
+ }
return file;
- file_error:
- file_set_error();
error:
- if (fd != -1) close( fd );
free( name );
return NULL;
}
@@ -241,29 +246,20 @@
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 55fc3a4..ff59f37 100644
--- a/server/file.h
+++ b/server/file.h
@@ -42,7 +42,10 @@
/* 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 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 b1e2e66..05cdda7 100644
--- a/server/named_pipe.c
+++ b/server/named_pipe.c
@@ -361,8 +361,8 @@
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 3e01ee9..52580fc 100644
--- a/server/pipe.c
+++ b/server/pipe.c
@@ -85,7 +85,7 @@
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 9f26b8d..f26b62a 100644
--- a/server/process.c
+++ b/server/process.c
@@ -302,7 +302,7 @@
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 a47827b..a926f5c 100644
--- a/server/request.c
+++ b/server/request.c
@@ -696,7 +696,7 @@
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 2f9130b..3831b7e 100644
--- a/server/serial.c
+++ b/server/serial.c
@@ -150,25 +150,27 @@
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 ef94dd5..f31cebe 100644
--- a/server/smb.c
+++ b/server/smb.c
@@ -167,22 +167,21 @@
return;
}
- smb = alloc_object( &smb_ops );
- if (smb)
+ if (!(smb = alloc_object( &smb_ops )))
{
- 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 );
+ 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 )))
+ {
+ reply->handle = alloc_handle( current->process, smb, GENERIC_READ, 0);
+ }
+ release_object( smb );
}
DECL_HANDLER(get_smb_info)
diff --git a/server/sock.c b/server/sock.c
index 51d7ec7..6161d23 100644
--- a/server/sock.c
+++ b/server/sock.c
@@ -616,7 +616,7 @@
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 @@
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 5d41a4a..3db8b1e 100644
--- a/server/thread.c
+++ b/server/thread.c
@@ -170,7 +170,7 @@
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 @@
fatal_protocol_error( current, "bad wait fd\n" );
goto error;
}
- current->reply_fd = alloc_fd( &thread_fd_ops, reply_fd, ¤t->obj );
- current->wait_fd = alloc_fd( &thread_fd_ops, wait_fd, ¤t->obj );
+ current->reply_fd = create_anonymous_fd( &thread_fd_ops, reply_fd, ¤t->obj );
+ current->wait_fd = create_anonymous_fd( &thread_fd_ops, wait_fd, ¤t->obj );
if (!current->reply_fd || !current->wait_fd) return;
current->unix_pid = req->unix_pid;