+ if (default_handle != NULL)
+ fh_ref (default_handle);
+}
+\f
+/* Information about a file handle's readers or writers. */
+struct fh_lock
+ {
+ /* 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. */
+ }
+ 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. */
+ };
+
+/* 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 int compare_fh_locks (const void *, const void *, const void *);
+static unsigned int hash_fh_lock (const void *, const void *);
+
+/* 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, *lock;
+ void **lockp;
+
+ 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);
+
+ make_key (&key, h, access);
+ lockp = hsh_probe (locks, &key);
+ if (*lockp == NULL)
+ {
+ lock = *lockp = xmalloc (sizeof *lock);
+ *lock = key;
+ lock->open_cnt = 1;
+ lock->exclusive = exclusive;
+ lock->type = type;
+ lock->aux = NULL;
+ }
+ else
+ {
+ free_key (&key);
+
+ lock = *lockp;
+ 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++;
+ }
+
+ return lock;
+}
+
+/* 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)
+ {
+ hsh_delete (locks, lock);
+ 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;
+ bool is_locked;
+
+ make_key (&key, handle, access);
+ is_locked = hsh_find (locks, &key) != NULL;
+ 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 = 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;
+ }
+}
+
+/* Frees the key fields in LOCK. */
+static void
+free_key (struct fh_lock *lock)
+{
+ if (lock->referent == FH_REF_FILE)
+ fn_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 void *a_, const void *b_, const void *aux UNUSED)
+{
+ 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)
+ return fn_compare_file_identities (a->u.file, b->u.file);
+ else if (a->referent == FH_REF_SCRATCH)
+ 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 void *lock_, const void *aux UNUSED)
+{
+ const struct fh_lock *lock = lock_;
+ unsigned int basis;
+ if (lock->referent == FH_REF_FILE)
+ basis = fn_hash_identity (lock->u.file);
+ else if (lock->referent == FH_REF_SCRATCH)
+ basis = lock->u.unique_id;
+ else
+ basis = 0;
+ return hash_int ((lock->referent << 3) | lock->access, basis);