MISSING=INCLUDE/EXCLUDE works
[pspp] / src / data / file-handle-def.c
index 872559154469cceebbbf3272aab1702a319b80e8..09340833be4134d4125eefb96ec2ff1141884a11 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 1997-9, 2000, 2006 Free Software Foundation, Inc.
+   Copyright (C) 1997-9, 2000, 2006, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
 
 #include <config.h>
 
 
 #include <config.h>
 
-#include "file-handle-def.h"
+#include "data/file-handle-def.h"
 
 #include <assert.h>
 #include <errno.h>
 #include <stdlib.h>
 #include <string.h>
 
 
 #include <assert.h>
 #include <errno.h>
 #include <stdlib.h>
 #include <string.h>
 
-#include <libpspp/compiler.h>
-#include <libpspp/hash.h>
-#include <libpspp/ll.h>
-#include <libpspp/message.h>
-#include <libpspp/str.h>
-#include <data/file-name.h>
-#include <data/variable.h>
-#include <data/scratch-handle.h>
+#include "data/dataset.h"
+#include "data/variable.h"
+#include "libpspp/assertion.h"
+#include "libpspp/cast.h"
+#include "libpspp/compiler.h"
+#include "libpspp/hash-functions.h"
+#include "libpspp/hmap.h"
+#include "libpspp/i18n.h"
+#include "libpspp/message.h"
+#include "libpspp/str.h"
 
 
-#include "xalloc.h"
+#include <sys/stat.h>
+
+#include "gl/dirname.h"
+#include "gl/xalloc.h"
 
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
 
 
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
 
+#if defined _WIN32 || defined __WIN32__
+#define WIN32_LEAN_AND_MEAN  /* avoid including junk */
+#include <windows.h>
+#endif
+
 /* File handle. */
 struct file_handle
   {
 /* File handle. */
 struct file_handle
   {
-    struct ll ll;               /* Element in global list. */
+    struct hmap_node name_node; /* Element in named_handles hmap. */
     size_t ref_cnt;             /* Number of references. */
     char *id;                   /* Identifier token, NULL if none. */
     char *name;                 /* User-friendly identifying name. */
     size_t ref_cnt;             /* Number of references. */
     char *id;                   /* Identifier token, NULL if none. */
     char *name;                 /* User-friendly identifying name. */
@@ -48,24 +58,22 @@ struct file_handle
 
     /* FH_REF_FILE only. */
     char *file_name;           /* File name as provided by user. */
 
     /* FH_REF_FILE only. */
     char *file_name;           /* File name as provided by user. */
+    char *file_name_encoding;  /* The character encoding of file_name,
+                                  This is NOT the encoding of the file contents! */
     enum fh_mode mode;         /* File mode. */
     enum fh_mode mode;         /* File mode. */
+    enum fh_line_ends line_ends; /* Line ends for text files. */
 
     /* FH_REF_FILE and FH_REF_INLINE only. */
     size_t record_width;        /* Length of fixed-format records. */
     size_t tab_width;           /* Tab width, 0=do not expand tabs. */
 
     /* FH_REF_FILE and FH_REF_INLINE only. */
     size_t record_width;        /* Length of fixed-format records. */
     size_t tab_width;           /* Tab width, 0=do not expand tabs. */
+    char *encoding;             /* Charset for contents. */
 
 
-    /* FH_REF_SCRATCH only. */
-    struct scratch_handle *sh;  /* Scratch file data. */
+    /* FH_REF_DATASET only. */
+    struct dataset *ds;         /* Dataset. */
   };
 
   };
 
-static struct file_handle *
-file_handle_from_ll (struct ll *ll)
-{
-  return ll_data (ll, struct file_handle, ll);
-}
-
-/* List of all named handles. */
-static struct ll_list named_handles;
+/* All "struct file_handle"s with nonnull 'id' member. */
+static struct hmap named_handles = HMAP_INITIALIZER (named_handles);
 
 /* Default file handle for DATA LIST, REREAD, REPEATING DATA
    commands. */
 
 /* Default file handle for DATA LIST, REREAD, REPEATING DATA
    commands. */
@@ -75,16 +83,26 @@ static struct file_handle *default_handle;
 static struct file_handle *inline_file;
 
 static struct file_handle *create_handle (const char *id,
 static struct file_handle *inline_file;
 
 static struct file_handle *create_handle (const char *id,
-                                          char *name, enum fh_referent);
+                                          char *name, enum fh_referent,
+                                          const char *encoding);
 static void free_handle (struct file_handle *);
 static void unname_handle (struct file_handle *);
 
 static void free_handle (struct file_handle *);
 static void unname_handle (struct file_handle *);
 
+/* Hash table of all active locks. */
+static struct hmap locks = HMAP_INITIALIZER (locks);
+
+static struct file_identity *fh_get_identity (const struct file_handle *);
+static void fh_free_identity (struct file_identity *);
+static int fh_compare_file_identities (const struct file_identity *,
+                                const struct file_identity *);
+static unsigned int fh_hash_identity (const struct file_identity *);
+
 /* File handle initialization routine. */
 void
 fh_init (void)
 {
 /* File handle initialization routine. */
 void
 fh_init (void)
 {
-  ll_init (&named_handles);
-  inline_file = create_handle ("INLINE", xstrdup ("INLINE"), FH_REF_INLINE);
+  inline_file = create_handle ("INLINE", xstrdup ("INLINE"), FH_REF_INLINE,
+                               "Auto");
   inline_file->record_width = 80;
   inline_file->tab_width = 8;
 }
   inline_file->record_width = 80;
   inline_file->tab_width = 8;
 }
@@ -93,23 +111,32 @@ fh_init (void)
 void
 fh_done (void)
 {
 void
 fh_done (void)
 {
-  while (!ll_is_empty (&named_handles))
-    unname_handle (file_handle_from_ll (ll_head (&named_handles)));
+  struct file_handle *handle, *next;
+
+  HMAP_FOR_EACH_SAFE (handle, next,
+                      struct file_handle, name_node, &named_handles)
+    unname_handle (handle);
+
+  free_handle (inline_file);
 }
 
 /* Free HANDLE and remove it from the global list. */
 static void
 free_handle (struct file_handle *handle)
 {
 }
 
 /* Free HANDLE and remove it from the global list. */
 static void
 free_handle (struct file_handle *handle)
 {
+  if (handle == NULL)
+    return;
+
   /* Remove handle from global list. */
   if (handle->id != NULL)
   /* Remove handle from global list. */
   if (handle->id != NULL)
-    ll_remove (&handle->ll);
+    hmap_delete (&named_handles, &handle->name_node);
 
   /* Free data. */
   free (handle->id);
   free (handle->name);
   free (handle->file_name);
 
   /* Free data. */
   free (handle->id);
   free (handle->name);
   free (handle->file_name);
-  scratch_handle_destroy (handle->sh);
+  free (handle->file_name_encoding);
+  free (handle->encoding);
   free (handle);
 }
 
   free (handle);
 }
 
@@ -122,7 +149,7 @@ unname_handle (struct file_handle *handle)
   assert (handle->id != NULL);
   free (handle->id);
   handle->id = NULL;
   assert (handle->id != NULL);
   free (handle->id);
   handle->id = NULL;
-  ll_remove (&handle->ll);
+  hmap_delete (&named_handles, &handle->name_node);
 
   /* Drop the reference held by the named_handles table. */
   fh_unref (handle);
 
   /* Drop the reference held by the named_handles table. */
   fh_unref (handle);
@@ -132,6 +159,8 @@ unname_handle (struct file_handle *handle)
 struct file_handle *
 fh_ref (struct file_handle *handle)
 {
 struct file_handle *
 fh_ref (struct file_handle *handle)
 {
+  if (handle == fh_inline_file ())
+    return handle;
   assert (handle->ref_cnt > 0);
   handle->ref_cnt++;
   return handle;
   assert (handle->ref_cnt > 0);
   handle->ref_cnt++;
   return handle;
@@ -144,6 +173,8 @@ fh_unref (struct file_handle *handle)
 {
   if (handle != NULL)
     {
 {
   if (handle != NULL)
     {
+      if (handle == fh_inline_file ())
+        return;
       assert (handle->ref_cnt > 0);
       if (--handle->ref_cnt == 0)
         free_handle (handle);
       assert (handle->ref_cnt > 0);
       if (--handle->ref_cnt == 0)
         free_handle (handle);
@@ -171,11 +202,11 @@ fh_from_id (const char *id)
 {
   struct file_handle *handle;
 
 {
   struct file_handle *handle;
 
-  ll_for_each (handle, struct file_handle, ll, &named_handles)
-    if (!strcasecmp (id, handle->id))
+  HMAP_FOR_EACH_WITH_HASH (handle, struct file_handle, name_node,
+                           utf8_hash_case_string (id, 0), &named_handles)
+    if (!utf8_strcasecmp (id, handle->id))
       {
       {
-        handle->ref_cnt++;
-        return handle;
+       return fh_ref (handle);
       }
 
   return NULL;
       }
 
   return NULL;
@@ -188,20 +219,21 @@ fh_from_id (const char *id)
    The new handle is not fully initialized.  The caller is
    responsible for completing its initialization. */
 static struct file_handle *
    The new handle is not fully initialized.  The caller is
    responsible for completing its initialization. */
 static struct file_handle *
-create_handle (const char *id, char *handle_name, enum fh_referent referent)
+create_handle (const char *id, char *handle_name, enum fh_referent referent,
+               const char *encoding)
 {
 {
-  struct file_handle *handle = xzalloc (sizeof *handle);
+  struct file_handle *handle = XZALLOC (struct file_handle);
 
   handle->ref_cnt = 1;
 
   handle->ref_cnt = 1;
-  handle->id = id != NULL ? xstrdup (id) : NULL;
+  handle->id = xstrdup_if_nonnull (id);
   handle->name = handle_name;
   handle->referent = referent;
   handle->name = handle_name;
   handle->referent = referent;
+  handle->encoding = xstrdup (encoding);
 
   if (id != NULL)
     {
 
   if (id != NULL)
     {
-      assert (fh_from_id (id) == NULL);
-      ll_push_tail (&named_handles, &handle->ll);
-      handle->ref_cnt++;
+      hmap_insert (&named_handles, &handle->name_node,
+                   utf8_hash_case_string (handle->id, 0));
     }
 
   return handle;
     }
 
   return handle;
@@ -213,25 +245,26 @@ create_handle (const char *id, char *handle_name, enum fh_referent referent)
 struct file_handle *
 fh_inline_file (void)
 {
 struct file_handle *
 fh_inline_file (void)
 {
-  fh_ref (inline_file);
   return inline_file;
 }
 
   return inline_file;
 }
 
-/* Creates and returns a new file handle with the given ID, which
-   may be null.  If it is non-null, it must be unique among
-   existing file identifiers.  The new handle is associated with
-   file FILE_NAME and the given PROPERTIES. */
+/* Creates and returns a new file handle with the given ID, which may be null.
+   If it is non-null, it must be a UTF-8 encoded string that is unique among
+   existing file identifiers.  The new handle is associated with file FILE_NAME
+   and the given PROPERTIES. */
 struct file_handle *
 struct file_handle *
-fh_create_file (const char *id, const char *file_name,
+fh_create_file (const char *id, const char *file_name, const char *file_name_encoding,
                 const struct fh_properties *properties)
 {
   char *handle_name;
   struct file_handle *handle;
 
                 const struct fh_properties *properties)
 {
   char *handle_name;
   struct file_handle *handle;
 
-  handle_name = id != NULL ? xstrdup (id) : xasprintf ("\"%s\"", file_name);
-  handle = create_handle (id, handle_name, FH_REF_FILE);
+  handle_name = id != NULL ? xstrdup (id) : xasprintf ("`%s'", file_name);
+  handle = create_handle (id, handle_name, FH_REF_FILE, properties->encoding);
   handle->file_name = xstrdup (file_name);
   handle->file_name = xstrdup (file_name);
+  handle->file_name_encoding = xstrdup_if_nonnull (file_name_encoding);
   handle->mode = properties->mode;
   handle->mode = properties->mode;
+  handle->line_ends = properties->line_ends;
   handle->record_width = properties->record_width;
   handle->tab_width = properties->tab_width;
   return handle;
   handle->record_width = properties->record_width;
   handle->tab_width = properties->tab_width;
   return handle;
@@ -239,13 +272,19 @@ fh_create_file (const char *id, const char *file_name,
 
 /* Creates a new file handle with the given ID, which must be
    unique among existing file identifiers.  The new handle is
 
 /* Creates a new file handle with the given ID, which must be
    unique among existing file identifiers.  The new handle is
-   associated with a scratch file (initially empty). */
+   associated with a dataset file (initially empty). */
 struct file_handle *
 struct file_handle *
-fh_create_scratch (const char *id)
+fh_create_dataset (struct dataset *ds)
 {
 {
+  const char *name;
   struct file_handle *handle;
   struct file_handle *handle;
-  handle = create_handle (id, xstrdup (id), FH_REF_SCRATCH);
-  handle->sh = NULL;
+
+  name = dataset_name (ds);
+  if (name[0] == '\0')
+    name = _("active dataset");
+
+  handle = create_handle (NULL, xstrdup (name), FH_REF_DATASET, C_ENCODING);
+  handle->ds = ds;
   return handle;
 }
 
   return handle;
 }
 
@@ -253,8 +292,14 @@ fh_create_scratch (const char *id)
 const struct fh_properties *
 fh_default_properties (void)
 {
 const struct fh_properties *
 fh_default_properties (void)
 {
+#if defined _WIN32 || defined __WIN32__
+#define DEFAULT_LINE_ENDS FH_END_CRLF
+#else
+#define DEFAULT_LINE_ENDS FH_END_LF
+#endif
+
   static const struct fh_properties default_properties
   static const struct fh_properties default_properties
-    = {FH_MODE_TEXT, 1024, 4};
+    = {FH_MODE_TEXT, DEFAULT_LINE_ENDS, 1024, 4, (char *) "Auto"};
   return &default_properties;
 }
 
   return &default_properties;
 }
 
@@ -296,6 +341,16 @@ fh_get_file_name (const struct file_handle *handle)
   return handle->file_name;
 }
 
   return handle->file_name;
 }
 
+
+/* Returns the character encoding of the name of the file associated with HANDLE. */
+const char *
+fh_get_file_name_encoding (const struct file_handle *handle)
+{
+  assert (handle->referent == FH_REF_FILE);
+  return handle->file_name_encoding;
+}
+
+
 /* Returns the mode of HANDLE. */
 enum fh_mode
 fh_get_mode (const struct file_handle *handle)
 /* Returns the mode of HANDLE. */
 enum fh_mode
 fh_get_mode (const struct file_handle *handle)
@@ -304,6 +359,15 @@ fh_get_mode (const struct file_handle *handle)
   return handle->mode;
 }
 
   return handle->mode;
 }
 
+/* Returns the line ends of HANDLE, which must be a handle associated with a
+   file. */
+enum fh_line_ends
+fh_get_line_ends (const struct file_handle *handle)
+{
+  assert (handle->referent == FH_REF_FILE);
+  return handle->line_ends;
+}
+
 /* Returns the width of a logical record on HANDLE. */
 size_t
 fh_get_record_width (const struct file_handle *handle)
 /* Returns the width of a logical record on HANDLE. */
 size_t
 fh_get_record_width (const struct file_handle *handle)
@@ -322,29 +386,61 @@ fh_get_tab_width (const struct file_handle *handle)
   return handle->tab_width;
 }
 
   return handle->tab_width;
 }
 
-/* Returns the scratch file handle associated with HANDLE.
-   Applicable to only FH_REF_SCRATCH files. */
-struct scratch_handle *
-fh_get_scratch_handle (const struct file_handle *handle)
+/* Returns the encoding of characters read from HANDLE. */
+const char *
+fh_get_encoding (const struct file_handle *handle)
 {
 {
-  assert (handle->referent == FH_REF_SCRATCH);
-  return handle->sh;
+  return handle->encoding;
 }
 
 }
 
-/* Sets SH to be the scratch file handle associated with HANDLE.
-   Applicable to only FH_REF_SCRATCH files. */
-void
-fh_set_scratch_handle (struct file_handle *handle, struct scratch_handle *sh)
+/* Returns true if A and B refer to the same file or dataset, false
+   otherwise. */
+bool
+fh_equal (const struct file_handle *a, const struct file_handle *b)
 {
 {
-  assert (handle->referent == FH_REF_SCRATCH);
-  handle->sh = sh;
+  if (a->referent != b->referent)
+    return false;
+
+  switch (a->referent)
+    {
+    case FH_REF_FILE:
+      {
+        struct file_identity *a_id = fh_get_identity (a);
+        struct file_identity *b_id = fh_get_identity (b);
+
+        int cmp = fh_compare_file_identities (a_id, b_id);
+
+        fh_free_identity (a_id);
+        fh_free_identity (b_id);
+
+        return cmp == 0;
+      }
+
+    case FH_REF_INLINE:
+      return true;
+
+    case FH_REF_DATASET:
+      return a->ds == b->ds;
+
+    default:
+      NOT_REACHED ();
+    }
+}
+
+/* Returns the dataset handle associated with HANDLE.
+   Applicable to only FH_REF_DATASET files. */
+struct dataset *
+fh_get_dataset (const struct file_handle *handle)
+{
+  assert (handle->referent == FH_REF_DATASET);
+  return handle->ds;
 }
 
 /* Returns the current default handle. */
 struct file_handle *
 fh_get_default_handle (void)
 {
 }
 
 /* Returns the current default handle. */
 struct file_handle *
 fh_get_default_handle (void)
 {
-  return default_handle ? fh_ref (default_handle) : fh_inline_file ();
+  return default_handle ? default_handle : fh_inline_file ();
 }
 
 /* Sets NEW_DEFAULT_HANDLE as the default handle. */
 }
 
 /* Sets NEW_DEFAULT_HANDLE as the default handle. */
@@ -353,22 +449,24 @@ fh_set_default_handle (struct file_handle *new_default_handle)
 {
   assert (new_default_handle == NULL
           || (new_default_handle->referent & (FH_REF_INLINE | FH_REF_FILE)));
 {
   assert (new_default_handle == NULL
           || (new_default_handle->referent & (FH_REF_INLINE | FH_REF_FILE)));
-  if (default_handle != NULL)
+  if (default_handle != NULL && default_handle != inline_file)
     fh_unref (default_handle);
   default_handle = new_default_handle;
   if (default_handle != NULL)
     fh_unref (default_handle);
   default_handle = new_default_handle;
   if (default_handle != NULL)
-    fh_ref (default_handle);
+    default_handle = fh_ref (default_handle);
 }
 \f
 /* Information about a file handle's readers or writers. */
 struct fh_lock
   {
 }
 \f
 /* Information about a file handle's readers or writers. */
 struct fh_lock
   {
+    struct hmap_node node;      /* hmap_node member. */
+
     /* Hash key. */
     enum fh_referent referent;  /* Type of underlying file. */
     union
       {
         struct file_identity *file; /* FH_REF_FILE only. */
     /* Hash key. */
     enum fh_referent referent;  /* Type of underlying file. */
     union
       {
         struct file_identity *file; /* FH_REF_FILE only. */
-        unsigned int unique_id;    /* FH_REF_SCRATCH only. */
+        unsigned int unique_id;    /* FH_REF_DATASET only. */
       }
     u;
     enum fh_access access;      /* Type of file access. */
       }
     u;
     enum fh_access access;      /* Type of file access. */
@@ -382,14 +480,12 @@ struct fh_lock
     void *aux;                  /* Owner's auxiliary data. */
   };
 
     void *aux;                  /* Owner's auxiliary data. */
   };
 
-/* Hash table of all active locks. */
-static struct hsh_table *locks;
 
 static void make_key (struct fh_lock *, const struct file_handle *,
                       enum fh_access);
 static void free_key (struct fh_lock *);
 
 static void make_key (struct fh_lock *, const struct file_handle *,
                       enum fh_access);
 static void free_key (struct fh_lock *);
-static int compare_fh_locks (const void *, const void *, const void *);
-static unsigned int hash_fh_lock (const void *, const void *);
+static int compare_fh_locks (const struct fh_lock *a, const struct fh_lock *b);
+static unsigned int hash_fh_lock (const struct fh_lock *lock);
 
 /* Tries to lock handle H for the given kind of ACCESS and TYPE
    of file.  Returns a pointer to a struct fh_lock if successful,
 
 /* Tries to lock handle H for the given kind of ACCESS and TYPE
    of file.  Returns a pointer to a struct fh_lock if successful,
@@ -403,6 +499,10 @@ static unsigned int hash_fh_lock (const void *, const void *);
    and similarly for writing.  If successful, a reference to TYPE
    is retained, so it should probably be a string literal.
 
    and similarly for writing.  If successful, a reference to TYPE
    is retained, so it should probably be a string literal.
 
+   TYPE should be marked with N_() in the caller: that is, the
+   caller should not translate it with gettext, but fh_lock will
+   do so.
+
    ACCESS specifies whether the lock is for reading or writing.
    EXCLUSIVE is true to require exclusive access, false to allow
    sharing with other accessors.  Exclusive read access precludes
    ACCESS specifies whether the lock is for reading or writing.
    EXCLUSIVE is true to require exclusive access, false to allow
    sharing with other accessors.  Exclusive read access precludes
@@ -417,31 +517,36 @@ struct fh_lock *
 fh_lock (struct file_handle *h, enum fh_referent mask UNUSED,
          const char *type, enum fh_access access, bool exclusive)
 {
 fh_lock (struct file_handle *h, enum fh_referent mask UNUSED,
          const char *type, enum fh_access access, bool exclusive)
 {
-  struct fh_lock key, *lock;
-  void **lockp;
+  struct fh_lock *key = NULL;
+  size_t hash ;
+  struct fh_lock *lock = NULL;
+  bool found_lock = false;
 
   assert ((fh_get_referent (h) & mask) != 0);
   assert (access == FH_ACC_READ || access == FH_ACC_WRITE);
 
 
   assert ((fh_get_referent (h) & mask) != 0);
   assert (access == FH_ACC_READ || access == FH_ACC_WRITE);
 
-  if (locks == NULL)
-    locks = hsh_create (0, compare_fh_locks, hash_fh_lock, NULL, NULL);
+  key = xmalloc (sizeof *key);
 
 
-  make_key (&key, h, access);
-  lockp = hsh_probe (locks, &key);
-  if (*lockp == NULL)
+  make_key (key, h, access);
+
+  key->open_cnt = 1;
+  key->exclusive = exclusive;
+  key->type = type;
+  key->aux = NULL;
+
+  hash = hash_fh_lock (key);
+
+  HMAP_FOR_EACH_WITH_HASH (lock, struct fh_lock, node, hash, &locks)
     {
     {
-      lock = *lockp = xmalloc (sizeof *lock);
-      *lock = key;
-      lock->open_cnt = 1;
-      lock->exclusive = exclusive;
-      lock->type = type;
-      lock->aux = NULL;
+      if (0 == compare_fh_locks (lock, key))
+       {
+         found_lock = true;
+         break;
+       }
     }
     }
-  else
-    {
-      free_key (&key);
 
 
-      lock = *lockp;
+  if (found_lock)
+    {
       if (strcmp (lock->type, type))
         {
           if (access == FH_ACC_READ)
       if (strcmp (lock->type, type))
         {
           if (access == FH_ACC_READ)
@@ -461,9 +566,27 @@ fh_lock (struct file_handle *h, enum fh_referent mask UNUSED,
           return NULL;
         }
       lock->open_cnt++;
           return NULL;
         }
       lock->open_cnt++;
+
+      free_key (key);
+      free (key);
+
+      return lock;
+    }
+
+  hmap_insert (&locks, &key->node, hash);
+  found_lock = false;
+  HMAP_FOR_EACH_WITH_HASH (lock, struct fh_lock, node, hash, &locks)
+    {
+      if (0 == compare_fh_locks (lock, key))
+       {
+         found_lock = true;
+         break;
+       }
     }
 
     }
 
-  return lock;
+  assert (found_lock);
+
+  return key;
 }
 
 /* Releases LOCK that was acquired with fh_lock.
 }
 
 /* Releases LOCK that was acquired with fh_lock.
@@ -485,7 +608,7 @@ fh_unlock (struct fh_lock *lock)
       assert (lock->open_cnt > 0);
       if (--lock->open_cnt == 0)
         {
       assert (lock->open_cnt > 0);
       if (--lock->open_cnt == 0)
         {
-          hsh_delete (locks, lock);
+         hmap_delete (&locks, &lock->node);
           free_key (lock);
           free (lock);
           return false;
           free_key (lock);
           free (lock);
           return false;
@@ -518,10 +641,24 @@ bool
 fh_is_locked (const struct file_handle *handle, enum fh_access access)
 {
   struct fh_lock key;
 fh_is_locked (const struct file_handle *handle, enum fh_access access)
 {
   struct fh_lock key;
-  bool is_locked;
+  const struct fh_lock *k = NULL;
+  bool is_locked = false;
+  size_t hash ;
 
   make_key (&key, handle, access);
 
   make_key (&key, handle, access);
-  is_locked = hsh_find (locks, &key) != NULL;
+
+  hash = hash_fh_lock (&key);
+
+
+  HMAP_FOR_EACH_WITH_HASH (k, struct fh_lock, node, hash, &locks)
+    {
+      if (0 == compare_fh_locks (k, &key))
+       {
+         is_locked = true;
+         break;
+       }
+    }
+
   free_key (&key);
 
   return is_locked;
   free_key (&key);
 
   return is_locked;
@@ -536,12 +673,9 @@ make_key (struct fh_lock *lock, const struct file_handle *h,
   lock->referent = fh_get_referent (h);
   lock->access = access;
   if (lock->referent == FH_REF_FILE)
   lock->referent = fh_get_referent (h);
   lock->access = access;
   if (lock->referent == FH_REF_FILE)
-    lock->u.file = fn_get_identity (fh_get_file_name (h));
-  else if (lock->referent == FH_REF_SCRATCH)
-    {
-      struct scratch_handle *sh = fh_get_scratch_handle (h);
-      lock->u.unique_id = sh != NULL ? sh->unique_id : 0;
-    }
+    lock->u.file = fh_get_identity (h);
+  else if (lock->referent == FH_REF_DATASET)
+    lock->u.unique_id = dataset_seqno (fh_get_dataset (h));
 }
 
 /* Frees the key fields in LOCK. */
 }
 
 /* Frees the key fields in LOCK. */
@@ -549,24 +683,21 @@ static void
 free_key (struct fh_lock *lock)
 {
   if (lock->referent == FH_REF_FILE)
 free_key (struct fh_lock *lock)
 {
   if (lock->referent == FH_REF_FILE)
-    fn_free_identity (lock->u.file);
+    fh_free_identity (lock->u.file);
 }
 
 /* Compares the key fields in struct fh_lock objects A and B and
    returns a strcmp()-type result. */
 static int
 }
 
 /* Compares the key fields in struct fh_lock objects A and B and
    returns a strcmp()-type result. */
 static int
-compare_fh_locks (const void *a_, const void *b_, const void *aux UNUSED)
+compare_fh_locks (const struct fh_lock *a, const struct fh_lock *b)
 {
 {
-  const struct fh_lock *a = a_;
-  const struct fh_lock *b = b_;
-
   if (a->referent != b->referent)
     return a->referent < b->referent ? -1 : 1;
   else if (a->access != b->access)
     return a->access < b->access ? -1 : 1;
   else if (a->referent == FH_REF_FILE)
   if (a->referent != b->referent)
     return a->referent < b->referent ? -1 : 1;
   else if (a->access != b->access)
     return a->access < b->access ? -1 : 1;
   else if (a->referent == FH_REF_FILE)
-    return fn_compare_file_identities (a->u.file, b->u.file);
-  else if (a->referent == FH_REF_SCRATCH)
+    return fh_compare_file_identities (a->u.file, b->u.file);
+  else if (a->referent == FH_REF_DATASET)
     return (a->u.unique_id < b->u.unique_id ? -1
             : a->u.unique_id > b->u.unique_id);
   else
     return (a->u.unique_id < b->u.unique_id ? -1
             : a->u.unique_id > b->u.unique_id);
   else
@@ -575,13 +706,153 @@ compare_fh_locks (const void *a_, const void *b_, const void *aux UNUSED)
 
 /* Returns a hash value for LOCK. */
 static unsigned int
 
 /* Returns a hash value for LOCK. */
 static unsigned int
-hash_fh_lock (const void *lock_, const void *aux UNUSED)
+hash_fh_lock (const struct fh_lock *lock)
 {
 {
-  const struct fh_lock *lock = lock_;
-  unsigned int hash = hsh_hash_int ((lock->referent << 3) | lock->access);
+  unsigned int basis;
   if (lock->referent == FH_REF_FILE)
   if (lock->referent == FH_REF_FILE)
-    hash ^= fn_hash_identity (lock->u.file);
-  else if (lock->referent == FH_REF_SCRATCH)
-    hash ^= hsh_hash_int (lock->u.unique_id);
+    basis = fh_hash_identity (lock->u.file);
+  else if (lock->referent == FH_REF_DATASET)
+    basis = lock->u.unique_id;
+  else
+    basis = 0;
+  return hash_int ((lock->referent << 3) | lock->access, basis);
+}
+
+\f
+
+
+
+
+/* A file's identity:
+
+   - For a file that exists, this is its device and inode.
+
+   - For a file that does not exist, but which has a directory
+     name that exists, this is the device and inode of the
+     directory, plus the file's base name.
+
+   - For a file that does not exist and has a nonexistent
+     directory, this is the file name.
+
+   Windows doesn't have inode numbers, so we just use the name
+   there. */
+struct file_identity
+{
+  unsigned long long device;               /* Device number. */
+  unsigned long long inode;                /* Inode number. */
+  char *name;                 /* File name, where needed, otherwise NULL. */
+};
+
+/* Returns a pointer to a dynamically allocated structure whose
+   value can be used to tell whether two files are actually the
+   same file.  The caller is responsible for freeing the structure with
+   fh_free_identity() when finished. */
+static struct file_identity *
+fh_get_identity (const struct file_handle *fh)
+{
+  struct file_identity *identity = xmalloc (sizeof *identity);
+
+  const char *file_name = fh_get_file_name (fh);
+
+#if !(defined _WIN32 || defined __WIN32__)
+  struct stat s;
+  if (lstat (file_name, &s) == 0)
+    {
+      identity->device = s.st_dev;
+      identity->inode = s.st_ino;
+      identity->name = NULL;
+    }
+  else
+    {
+      char *dir = dir_name (file_name);
+      if (last_component (file_name) != NULL && stat (dir, &s) == 0)
+        {
+          identity->device = s.st_dev;
+          identity->inode = s.st_ino;
+          identity->name = base_name (file_name);
+        }
+      else
+        {
+          identity->device = 0;
+          identity->inode = 0;
+          identity->name = xstrdup (file_name);
+        }
+      free (dir);
+    }
+#else /* Windows */
+  bool ok = false;
+  HANDLE h = CreateFile (file_name, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
+  if (h != INVALID_HANDLE_VALUE)
+  {
+    BY_HANDLE_FILE_INFORMATION fi;
+    ok = GetFileInformationByHandle (h, &fi);
+    if (ok)
+      {
+       identity->device = fi.dwVolumeSerialNumber;
+       identity->inode = fi.nFileIndexHigh;
+       identity->inode <<= (sizeof fi.nFileIndexLow) * CHAR_BIT;
+       identity->inode |= fi.nFileIndexLow;
+       identity->name = 0;
+      }
+    CloseHandle (h);
+  }
+
+  if (!ok)
+    {
+      identity->device = 0;
+      identity->inode = 0;
+
+      size_t bufsize;
+      size_t pathlen = 255;
+      char *cname = NULL;
+      do
+      {
+       bufsize = pathlen;
+       cname = xrealloc (cname, bufsize);
+       pathlen = GetFullPathName (file_name, bufsize, cname, NULL);
+      }
+      while (pathlen > bufsize);
+      identity->name = xstrdup (cname);
+      free (cname);
+      str_lowercase (identity->name);
+    }
+#endif /* Windows */
+
+  return identity;
+}
+
+/* Frees IDENTITY obtained from fh_get_identity(). */
+void
+fh_free_identity (struct file_identity *identity)
+{
+  if (identity != NULL)
+    {
+      free (identity->name);
+      free (identity);
+    }
+}
+
+/* Compares A and B, returning a strcmp()-type result. */
+int
+fh_compare_file_identities (const struct file_identity *a,
+                            const struct file_identity *b)
+{
+  if (a->device != b->device)
+    return a->device < b->device ? -1 : 1;
+  else if (a->inode != b->inode)
+    return a->inode < b->inode ? -1 : 1;
+  else if (a->name != NULL)
+    return b->name != NULL ? strcmp (a->name, b->name) : 1;
+  else
+    return b->name != NULL ? -1 : 0;
+}
+
+/* Returns a hash value for IDENTITY. */
+unsigned int
+fh_hash_identity (const struct file_identity *identity)
+{
+  unsigned int hash = hash_int (identity->device, identity->inode);
+  if (identity->name != NULL)
+    hash = hash_string (identity->name, hash);
   return hash;
 }
   return hash;
 }