+ if (default_handle != NULL)
+ fh_ref (default_handle);
+}
+\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. */
+ unsigned int unique_id; /* FH_REF_DATASET only. */
+ }
+ u;
+ enum fh_access access; /* Type of file access. */
+
+ /* Number of openers. */
+ size_t open_cnt;
+
+ /* Applicable only when open_cnt > 0. */
+ bool exclusive; /* No other openers allowed? */
+ const char *type; /* Human-readable type of file. */
+ void *aux; /* Owner's auxiliary data. */
+ };
+
+
+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 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,
+ otherwise a null pointer.
+
+ H's referent type must be one of the bits in MASK. The caller
+ must verify this ahead of time; we simply assert it here.
+
+ TYPE is the sort of file, e.g. "system file". Only one type
+ of access is allowed on a given file at a time for reading,
+ 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
+ other readers, but not writers; exclusive write access
+ precludes other writers, but not readers. A sharable read or
+ write lock precludes reader or writers, respectively, of a
+ different TYPE.
+
+ A lock may be associated with auxiliary data. See
+ fh_lock_get_aux and fh_lock_set_aux for more details. */
+struct fh_lock *
+fh_lock (struct file_handle *h, enum fh_referent mask UNUSED,
+ const char *type, enum fh_access access, bool exclusive)
+{
+ 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);
+
+ key = xmalloc (sizeof *key);
+
+ 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)
+ {
+ if ( 0 == compare_fh_locks (lock, key))
+ {
+ found_lock = true;
+ break;
+ }
+ }
+
+ if ( found_lock )
+ {
+ if (strcmp (lock->type, type))
+ {
+ if (access == FH_ACC_READ)
+ msg (SE, _("Can't read from %s as a %s because it is "
+ "already being read as a %s."),
+ fh_get_name (h), gettext (type), gettext (lock->type));
+ else
+ msg (SE, _("Can't write to %s as a %s because it is "
+ "already being written as a %s."),
+ fh_get_name (h), gettext (type), gettext (lock->type));
+ return NULL;
+ }
+ else if (exclusive || lock->exclusive)
+ {
+ msg (SE, _("Can't re-open %s as a %s."),
+ fh_get_name (h), gettext (type));
+ 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;
+ }
+ }
+
+ assert (found_lock);
+
+ return key;
+}
+
+/* Releases LOCK that was acquired with fh_lock.
+ Returns true if LOCK is still locked, because other clients
+ also had it locked.
+
+ Returns false if LOCK has now been destroyed. In this case
+ the caller must ensure that any auxiliary data associated with
+ LOCK is destroyed, to avoid a memory leak. The caller must
+ obtain a pointer to the auxiliary data, e.g. via
+ fh_lock_get_aux *before* calling fh_unlock (because it yields
+ undefined behavior to call fh_lock_get_aux on a destroyed
+ lock). */
+bool
+fh_unlock (struct fh_lock *lock)
+{
+ if (lock != NULL)
+ {
+ assert (lock->open_cnt > 0);
+ if (--lock->open_cnt == 0)
+ {
+ hmap_delete (&locks, &lock->node);
+ free_key (lock);
+ free (lock);
+ return false;
+ }
+ }
+ return true;
+}
+
+/* Returns auxiliary data for LOCK.
+
+ Auxiliary data is shared by every client that holds LOCK (for
+ an exclusive lock, this is a single client). To avoid leaks,
+ auxiliary data must be released before LOCK is destroyed. */
+void *
+fh_lock_get_aux (const struct fh_lock *lock)
+{
+ return lock->aux;
+}
+
+/* Sets the auxiliary data for LOCK to AUX. */
+void
+fh_lock_set_aux (struct fh_lock *lock, void *aux)
+{
+ lock->aux = aux;
+}
+
+/* Returns true if HANDLE is locked for the given type of ACCESS,
+ false otherwise. */
+bool
+fh_is_locked (const struct file_handle *handle, enum fh_access access)
+{
+ struct fh_lock key;
+ const struct fh_lock *k = NULL;
+ bool is_locked = false;
+ size_t hash ;
+
+ make_key (&key, handle, access);
+
+ 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;
+}
+
+/* Initializes the key fields in LOCK for looking up or inserting
+ handle H for the given kind of ACCESS. */
+static void
+make_key (struct fh_lock *lock, const struct file_handle *h,
+ enum fh_access access)
+{
+ lock->referent = fh_get_referent (h);
+ lock->access = access;
+ if (lock->referent == FH_REF_FILE)
+ 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. */
+static void
+free_key (struct fh_lock *lock)
+{
+ if (lock->referent == FH_REF_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
+compare_fh_locks (const struct fh_lock *a, const struct fh_lock *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)
+ 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 0;
+}
+
+/* Returns a hash value for LOCK. */
+static unsigned int
+hash_fh_lock (const struct fh_lock *lock)
+{
+ unsigned int basis;
+ if (lock->referent == FH_REF_FILE)
+ 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. Returns a null pointer if no information about the
+ file is available, perhaps because it does not exist. The
+ caller is responsible for freeing the structure with
+ fh_free_identity() when finished. */
+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;