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 +#include #include #include #include @@ -29,6 +30,7 @@ #ifdef HAVE_SYS_POLL_H #include #endif +#include #include #include #include @@ -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 ))) { - 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 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, ¤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;