Added a separate device object to keep track of inodes that are on the
same device.

diff --git a/server/fd.c b/server/fd.c
index ccf7b81..cd08432 100644
--- a/server/fd.c
+++ b/server/fd.c
@@ -165,14 +165,43 @@
     fd_destroy                /* destroy */
 };
 
+/* device object */
+
+#define DEVICE_HASH_SIZE 7
+#define INODE_HASH_SIZE 17
+
+struct device
+{
+    struct object       obj;        /* object header */
+    struct list         entry;      /* entry in device hash list */
+    dev_t               dev;        /* device number */
+    struct list         inode_hash[INODE_HASH_SIZE];  /* inodes hash table */
+};
+
+static void device_dump( struct object *obj, int verbose );
+static void device_destroy( struct object *obj );
+
+static const struct object_ops device_ops =
+{
+    sizeof(struct device),    /* size */
+    device_dump,              /* dump */
+    no_add_queue,             /* add_queue */
+    NULL,                     /* remove_queue */
+    NULL,                     /* signaled */
+    NULL,                     /* satisfied */
+    no_signal,                /* signal */
+    no_get_fd,                /* get_fd */
+    no_close_handle,          /* close_handle */
+    device_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 */
+    struct device      *device;     /* device containing this inode */
     ino_t               ino;        /* inode number */
     struct list         open;       /* list of open file descriptors */
     struct list         locks;      /* list of file locks */
@@ -544,12 +573,56 @@
 
 
 /****************************************************************/
+/* device functions */
+
+static struct list device_hash[DEVICE_HASH_SIZE];
+
+/* retrieve the device object for a given fd, creating it if needed */
+static struct device *get_device( dev_t dev )
+{
+    struct device *device;
+    unsigned int i, hash = dev % DEVICE_HASH_SIZE;
+
+    if (device_hash[hash].next)
+    {
+        LIST_FOR_EACH_ENTRY( device, &device_hash[hash], struct device, entry )
+            if (device->dev == dev) return (struct device *)grab_object( device );
+    }
+    else list_init( &device_hash[hash] );
+
+    /* not found, create it */
+    if ((device = alloc_object( &device_ops )))
+    {
+        device->dev = dev;
+        for (i = 0; i < INODE_HASH_SIZE; i++) list_init( &device->inode_hash[i] );
+        list_add_head( &device_hash[hash], &device->entry );
+    }
+    return device;
+}
+
+static void device_dump( struct object *obj, int verbose )
+{
+    struct device *device = (struct device *)obj;
+    fprintf( stderr, "Device dev=" );
+    DUMP_LONG_LONG( device->dev );
+    fprintf( stderr, "\n" );
+}
+
+static void device_destroy( struct object *obj )
+{
+    struct device *device = (struct device *)obj;
+    unsigned int i;
+
+    for (i = 0; i < INODE_HASH_SIZE; i++)
+        assert( list_empty(&device->inode_hash[i]) );
+
+    list_remove( &device->entry );  /* remove it from the hash table */
+}
+
+
+/****************************************************************/
 /* inode functions */
 
-#define HASH_SIZE 37
-
-static struct list inode_hash[HASH_SIZE];
-
 /* close all pending file descriptors in the closed list */
 static void inode_close_pending( struct inode *inode )
 {
@@ -574,13 +647,10 @@
     }
 }
 
-
 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=" );
+    fprintf( stderr, "Inode device=%p ino=", inode->device );
     DUMP_LONG_LONG( inode->ino );
     fprintf( stderr, "\n" );
 }
@@ -604,7 +674,7 @@
         {
             /* make sure it is still the same file */
             struct stat st;
-            if (!stat( fd->unlink, &st ) && st.st_dev == inode->dev && st.st_ino == inode->ino)
+            if (!stat( fd->unlink, &st ) && st.st_dev == inode->device->dev && st.st_ino == inode->ino)
             {
                 if (S_ISDIR(st.st_mode)) rmdir( fd->unlink );
                 else unlink( fd->unlink );
@@ -612,37 +682,39 @@
         }
         free( fd );
     }
+    release_object( inode->device );
 }
 
 /* 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 device *device;
     struct inode *inode;
-    unsigned int hash = (dev ^ ino) % HASH_SIZE;
+    unsigned int hash = ino % INODE_HASH_SIZE;
 
-    if (inode_hash[hash].next)
+    if (!(device = get_device( dev ))) return NULL;
+
+    LIST_FOR_EACH_ENTRY( inode, &device->inode_hash[hash], struct inode, entry )
     {
-        LIST_FOR_EACH( ptr, &inode_hash[hash] )
+        if (inode->ino == ino)
         {
-            inode = LIST_ENTRY( ptr, struct inode, entry );
-            if (inode->dev == dev && inode->ino == ino)
-                return (struct inode *)grab_object( inode );
+            release_object( device );
+            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->device = device;
         inode->ino    = ino;
         list_init( &inode->open );
         list_init( &inode->locks );
         list_init( &inode->closed );
-        list_add_head( &inode_hash[hash], &inode->entry );
+        list_add_head( &device->inode_hash[hash], &inode->entry );
     }
+    else release_object( device );
+
     return inode;
 }