Allow output files to overwrite input files (bug #21280). Thanks to
authorBen Pfaff <blp@gnu.org>
Sat, 3 Nov 2007 22:00:03 +0000 (22:00 +0000)
committerBen Pfaff <blp@gnu.org>
Sat, 3 Nov 2007 22:00:03 +0000 (22:00 +0000)
John Darrington for review.

39 files changed:
ChangeLog
Smake
src/data/ChangeLog
src/data/file-handle-def.c
src/data/file-handle-def.h
src/data/file-name.c
src/data/file-name.h
src/data/make-file.c
src/data/make-file.h
src/data/por-file-reader.c
src/data/por-file-writer.c
src/data/scratch-handle.h
src/data/scratch-reader.c
src/data/scratch-writer.c
src/data/sys-file-reader.c
src/data/sys-file-writer.c
src/language/data-io/ChangeLog
src/language/data-io/data-list.c
src/language/data-io/data-reader.c
src/language/data-io/data-writer.c
src/language/data-io/file-handle.q
src/language/data-io/get.c
src/language/data-io/inpt-pgm.c
src/language/data-io/print-space.c
src/language/data-io/print.c
src/language/dictionary/ChangeLog
src/language/dictionary/apply-dictionary.c
src/language/dictionary/sys-file-info.c
src/language/stats/ChangeLog
src/language/stats/aggregate.c
src/language/stats/correlations.q
src/language/stats/regression.q
src/ui/terminal/ChangeLog
src/ui/terminal/main.c
tests/ChangeLog
tests/automake.mk
tests/bugs/overwrite-input-file.sh
tests/bugs/overwrite-special-file.sh [new file with mode: 0755]
tests/command/erase.sh

index 5768f68bf0674435766fb9d669a82c7fa25917bf..696c526d66139571ebe4ed62073c3ac80bc66196 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2007-11-03  Ben Pfaff  <blp@gnu.org>
+
+       Allow output files to overwrite input files (bug #21280).
+
+       * Smake (GNULIB_MODULES): Add fatal-signal, tempname modules.
+
 2007-11-02  Ben Pfaff  <blp@gnu.org>
 
        * Smake (GNULIB_MODULES): Add isfinite, round modules.
diff --git a/Smake b/Smake
index aaf1ff90da4cf66ba579a0082e8a6db0aa473de0..43536f9de053fc7f7eb0e49dc127c699ab7a0d42 100644 (file)
--- a/Smake
+++ b/Smake
@@ -12,6 +12,7 @@ GNULIB_MODULES = \
        crypto/md4 \
        dirname \
        exit \
+       fatal-signal \
        fpieee \
        fprintf-posix \
        full-read \
@@ -61,6 +62,7 @@ GNULIB_MODULES = \
        strtol \
        strtoul \
        sys_stat \
+       tempname \
        tmpfile \
        trunc \
        unistd \
index 6f116701363c5c048e2d6b125e27ca85dd142647..0353c63888fcdb143a9e9ce8d6d7e38d64080d15 100644 (file)
@@ -1,3 +1,103 @@
+2007-11-03  Ben Pfaff  <blp@gnu.org>
+
+       Allow output files to overwrite input files (bug #21280).
+
+       * file-handle-def.c: Separate locking of files for input and for
+       output, to allow both to take place at once.  Also, distinguish a
+       file handle from the identity of the underlying file, because the
+       identity of a file changes over time and the file handle can't
+       represent two different identities.
+       (struct file_handle): Remove `next', `open_cnt', `deleted',
+       `type', `open_mode', `aux', `identity' members.  Change `id' from
+       char array to char *.  Add `ref_cnt' member.
+       (file_handle_from_ll) New function.
+       (file_handles) Removed.
+       (named_handles) New variable.
+       (fh_init) Initialize named_handles.
+       (fh_done) Remove name from all named_handles.
+       (free_handle) Updated for modified struct file_handle.
+       (unname_handle) New function.
+       (fh_ref) New function.
+       (fh_from_file_name) Removed.
+       (fh_unref) New function.
+       (fh_unname) New function.
+       (fh_from_id) Rewritten.
+       (create_handle) Updated for modified struct file_handle.
+       (fh_inline_file) Increment the handle's ref count.
+       (fh_create_file) Updated for modified struct file_handle.
+       (fh_create_scratch) Ditto.
+       (fh_free) Removed.
+       (mode_name) Removed.
+       (fh_open) Removed.
+       (fh_close) Removed.
+       (fh_is_open) Removed.
+       (fh_get_id) Updated for modified struct file_handle.
+       (fh_get_default_handle) Increment the handle's ref count.
+       (fh_set_default_handle) Handle ref counts.
+       (struct fh_lock) New structure.
+       (locks) New static var.
+       (fh_lock) New function.
+       (fh_unlock) New function.
+       (fh_lock_get_aux) New function.
+       (fh_lock_set_aux) New function.
+       (fh_is_locked) New function.
+       (make_key) New function.
+       (free_key) New function.
+       (compare_fh_locks) New function.
+       (hash_fh_lock) New function.
+
+       * file-handle-def.h (enum fh_access) New enum.
+
+       * file-name.c: Made file_identity the same in all supported
+       environments.
+       (struct file_identity): New `name' member.
+       (fn_get_identity): For a file that doesn't exist, get the
+       dev/inode of its directory plus its name.  If even the directory
+       doesn't exist, just use its name.  Merge the Windows
+       implementation into the Unix one.
+       (fn_compare_file_identities): Rewritten.  Merge the Windows
+       implementation into the Unix one.
+       (fn_hash_identity): New function.
+
+       * make-file.c (struct replace_file): New structure.
+       (all_files): New static var.
+       (replace_file_start): New function.
+       (replace_file_commit): New function.
+       (replace_file_abort): New function.
+       (free_replace_file): New function.
+       (unlink_replace_files): New function.
+
+       * por-file-reader.c (struct pfm_reader): Add `lock' member.
+       (close_reader): Unlock file.
+       (pfm_open_reader): Lock file.
+
+       * por-file-writer.c (struct pfm_writer): Add fh_lock, replace_file
+       members.
+       (pfm_open_writer): Lock file and prepare for its replacement.
+       (close_writer): Unlock file.
+
+       * scratch-handle.h (struct scratch_handle): Add unique_id so that
+       different generations of a scratch file can be distinguished.
+
+       * scratch-reader.c (scratch_reader_open): Verify that the file is
+       a scratch file.
+
+       * scratch-writer.c (struct scratch_writer): Add `lock' and `dict',
+       remove scratch_handle member.
+       (scratch_writer_open): Lock handle.  Prepare to replace handle
+       data, instead of doing it immediately.
+       (scratch_writer_casewriter_destroy): Replace handle data and
+       unlock handle.
+
+       * sys-file-reader.c (struct sfm_reader): Add `lock' member.
+       (sfm_open_reader): Lock file.
+       (close_reader): Unlock file.
+
+       * sys-file-writer.c (struct sfm_writer): Add fh_lock, replace_file
+       members.
+       (sfm_open_writer): Lock file and prepare for its replacement.
+       (close_writer): Unlock file.
+
 2007-11-02  Ben Pfaff  <blp@gnu.org>
 
        * data-out.c (output_number): Use isfinite (from C99) instead of
index 1dea25a5f0480b55a0c93e39e5aebc2d1ced2bf0..872559154469cceebbbf3272aab1702a319b80e8 100644 (file)
@@ -24,6 +24,8 @@
 #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 "gettext.h"
 #define _(msgid) gettext (msgid)
 
-/* (headers) */
-
 /* File handle. */
 struct file_handle
   {
-    struct file_handle *next;   /* Next in global list. */
-    int open_cnt;               /* 0=not open, otherwise # of openers. */
-    bool deleted;               /* Destroy handle when open_cnt goes to 0? */
-
-    char id[LONG_NAME_LEN + 1]; /* Identifier token; empty string if none. */
+    struct ll ll;               /* Element in global list. */
+    size_t ref_cnt;             /* Number of references. */
+    char *id;                   /* Identifier token, NULL if none. */
     char *name;                 /* User-friendly identifying name. */
-    const char *type;           /* If open, type of file. */
-    char open_mode[3];          /* "[rw][se]". */
-    void *aux;                  /* Aux data pointer for owner if any. */
     enum fh_referent referent;  /* What the file handle refers to. */
 
     /* FH_REF_FILE only. */
     char *file_name;           /* File name as provided by user. */
-    struct file_identity *identity; /* For checking file identity. */
     enum fh_mode mode;         /* File mode. */
 
     /* FH_REF_FILE and FH_REF_INLINE only. */
@@ -64,8 +58,14 @@ struct file_handle
     struct scratch_handle *sh;  /* Scratch file data. */
   };
 
-/* List of all handles. */
-static struct file_handle *file_handles;
+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;
 
 /* Default file handle for DATA LIST, REREAD, REPEATING DATA
    commands. */
@@ -75,92 +75,108 @@ static struct file_handle *default_handle;
 static struct file_handle *inline_file;
 
 static struct file_handle *create_handle (const char *id,
-                                          const char *name, enum fh_referent);
+                                          char *name, enum fh_referent);
+static void free_handle (struct file_handle *);
+static void unname_handle (struct file_handle *);
 
 /* File handle initialization routine. */
 void
 fh_init (void)
 {
-  inline_file = create_handle ("INLINE", "INLINE", FH_REF_INLINE);
+  ll_init (&named_handles);
+  inline_file = create_handle ("INLINE", xstrdup ("INLINE"), FH_REF_INLINE);
   inline_file->record_width = 80;
   inline_file->tab_width = 8;
 }
 
+/* Removes all named file handles from the global list. */
+void
+fh_done (void)
+{
+  while (!ll_is_empty (&named_handles))
+    unname_handle (file_handle_from_ll (ll_head (&named_handles)));
+}
+
 /* Free HANDLE and remove it from the global list. */
 static void
 free_handle (struct file_handle *handle)
 {
   /* Remove handle from global list. */
-  if (file_handles == handle)
-    file_handles = handle->next;
-  else
-    {
-      struct file_handle *iter = file_handles;
-      while (iter->next != handle)
-        iter = iter->next;
-      iter->next = handle->next;
-    }
+  if (handle->id != NULL)
+    ll_remove (&handle->ll);
 
   /* Free data. */
+  free (handle->id);
   free (handle->name);
   free (handle->file_name);
-  fn_free_identity (handle->identity);
   scratch_handle_destroy (handle->sh);
   free (handle);
 }
 
-/* Frees all the file handles. */
-void
-fh_done (void)
+/* Make HANDLE unnamed, so that it can no longer be referenced by
+   name.  The caller must hold a reference to HANDLE, which is
+   not affected by this function. */
+static void
+unname_handle (struct file_handle *handle)
 {
-  while (file_handles != NULL)
-    free_handle (file_handles);
+  assert (handle->id != NULL);
+  free (handle->id);
+  handle->id = NULL;
+  ll_remove (&handle->ll);
+
+  /* Drop the reference held by the named_handles table. */
+  fh_unref (handle);
 }
 
-/* Returns the handle with the given ID, or a null pointer if
-   there is none. */
+/* Increments HANDLE's reference count and returns HANDLE. */
 struct file_handle *
-fh_from_id (const char *id)
+fh_ref (struct file_handle *handle)
 {
-  struct file_handle *iter;
-
-  for (iter = file_handles; iter != NULL; iter = iter->next)
-    if (!iter->deleted && !strcasecmp (id, iter->id))
-      return iter;
-  return NULL;
+  assert (handle->ref_cnt > 0);
+  handle->ref_cnt++;
+  return handle;
 }
 
-/* Returns the handle for the file named FILE_NAME,
-   or a null pointer if none exists.
-   Different names for the same file (e.g. "x" and "./x") are
-   considered equivalent. */
-struct file_handle *
-fh_from_file_name (const char *file_name)
+/* Decrements HANDLE's reference count.
+   If the reference count drops to 0, HANDLE is destroyed. */
+void
+fh_unref (struct file_handle *handle)
 {
-  struct file_identity *identity;
-  struct file_handle *iter;
-
-  /* First check for a file with the same identity. */
-  identity = fn_get_identity (file_name);
-  if (identity != NULL)
+  if (handle != NULL)
     {
-      for (iter = file_handles; iter != NULL; iter = iter->next)
-        if (!iter->deleted
-            && iter->referent == FH_REF_FILE
-            && iter->identity != NULL
-            && !fn_compare_file_identities (identity, iter->identity))
-          {
-            fn_free_identity (identity);
-            return iter;
-          }
-      fn_free_identity (identity);
+      assert (handle->ref_cnt > 0);
+      if (--handle->ref_cnt == 0)
+        free_handle (handle);
     }
+}
+
+/* Make HANDLE unnamed, so that it can no longer be referenced by
+   name.  The caller must hold a reference to HANDLE, which is
+   not affected by this function.
+
+   This function ignores a null pointer as input.  It has no
+   effect on the inline handle, which is always named INLINE.*/
+void
+fh_unname (struct file_handle *handle)
+{
+  assert (handle->ref_cnt > 1);
+  if (handle != fh_inline_file () && handle->id != NULL)
+    unname_handle (handle);
+}
+
+/* Returns the handle with the given ID, or a null pointer if
+   there is none. */
+struct file_handle *
+fh_from_id (const char *id)
+{
+  struct file_handle *handle;
 
-  /* Then check for a file with the same name. */
-  for (iter = file_handles; iter != NULL; iter = iter->next)
-    if (!iter->deleted
-        && iter->referent == FH_REF_FILE && !strcmp (file_name, iter->file_name))
-      return iter;
+  ll_for_each (handle, struct file_handle, ll, &named_handles)
+    if (!strcasecmp (id, handle->id))
+      {
+        handle->ref_cnt++;
+        return handle;
+      }
 
   return NULL;
 }
@@ -172,20 +188,22 @@ fh_from_file_name (const char *file_name)
    The new handle is not fully initialized.  The caller is
    responsible for completing its initialization. */
 static struct file_handle *
-create_handle (const char *id, const char *handle_name,
-               enum fh_referent referent)
+create_handle (const char *id, char *handle_name, enum fh_referent referent)
 {
   struct file_handle *handle = xzalloc (sizeof *handle);
-  assert (id == NULL || fh_from_id (id) == NULL);
-  handle->next = file_handles;
-  handle->open_cnt = 0;
-  handle->deleted = false;
-  str_copy_trunc (handle->id, sizeof handle->id, id != NULL ? id : "");
-  handle->name = xstrdup (handle_name);
-  handle->type = NULL;
-  handle->aux = NULL;
+
+  handle->ref_cnt = 1;
+  handle->id = id != NULL ? xstrdup (id) : NULL;
+  handle->name = handle_name;
   handle->referent = referent;
-  file_handles = handle;
+
+  if (id != NULL)
+    {
+      assert (fh_from_id (id) == NULL);
+      ll_push_tail (&named_handles, &handle->ll);
+      handle->ref_cnt++;
+    }
+
   return handle;
 }
 
@@ -195,13 +213,14 @@ create_handle (const char *id, const char *handle_name,
 struct file_handle *
 fh_inline_file (void)
 {
+  fh_ref (inline_file);
   return inline_file;
 }
 
-/* Creates 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 unique among
+   existing file identifiers.  The new handle is associated with
+   file FILE_NAME and the given PROPERTIES. */
 struct file_handle *
 fh_create_file (const char *id, const char *file_name,
                 const struct fh_properties *properties)
@@ -209,12 +228,9 @@ fh_create_file (const char *id, const char *file_name,
   char *handle_name;
   struct file_handle *handle;
 
-  handle_name = id != NULL ? (char *) id : xasprintf ("\"%s\"", file_name);
+  handle_name = id != NULL ? xstrdup (id) : xasprintf ("\"%s\"", file_name);
   handle = create_handle (id, handle_name, FH_REF_FILE);
-  if (id == NULL)
-    free (handle_name);
   handle->file_name = xstrdup (file_name);
-  handle->identity = fn_get_identity (file_name);
   handle->mode = properties->mode;
   handle->record_width = properties->record_width;
   handle->tab_width = properties->tab_width;
@@ -228,8 +244,7 @@ struct file_handle *
 fh_create_scratch (const char *id)
 {
   struct file_handle *handle;
-  assert (id != NULL);
-  handle = create_handle (id, id, FH_REF_SCRATCH);
+  handle = create_handle (id, xstrdup (id), FH_REF_SCRATCH);
   handle->sh = NULL;
   return handle;
 }
@@ -243,142 +258,6 @@ fh_default_properties (void)
   return &default_properties;
 }
 
-/* Deletes FH from the global list of file handles.  Afterward,
-   attempts to search for it will fail.  Unless the file handle
-   is currently open, it will be destroyed; otherwise, it will be
-   destroyed later when it is closed.
-   Normally needed only if a file_handle needs to be re-assigned.
-   Otherwise, just let fh_done() destroy the handle. */
-void
-fh_free (struct file_handle *handle)
-{
-  if (handle == fh_inline_file () || handle == NULL || handle->deleted)
-    return;
-  handle->deleted = true;
-
-  if (handle == default_handle)
-    default_handle = fh_inline_file ();
-
-  if (handle->open_cnt == 0)
-    free_handle (handle);
-}
-
-/* Returns an English description of MODE,
-   which is in the format of the MODE argument to fh_open(). */
-static const char *
-mode_name (const char *mode)
-{
-  assert (mode != NULL);
-  assert (mode[0] == 'r' || mode[0] == 'w');
-
-  return mode[0] == 'r' ? "reading" : "writing";
-}
-
-/* Tries to open handle H with the given TYPE and MODE.
-
-   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 given
-   type of access is allowed on a given file handle at once.
-   If successful, a reference to TYPE is retained, so it should
-   probably be a string literal.
-
-   MODE combines the read or write mode with the sharing mode.
-   The first character is 'r' for read, 'w' for write.  The
-   second character is 's' to permit sharing, 'e' to require
-   exclusive access.
-
-   Returns the address of a void * that the caller can use for
-   data specific to the file handle if successful, or a null
-   pointer on failure.  For exclusive access modes the void *
-   will always be a null pointer at return.  In shared access
-   modes the void * will necessarily be null only if no other
-   sharers are active. */
-void **
-fh_open (struct file_handle *h, enum fh_referent mask UNUSED,
-         const char *type, const char *mode)
-{
-  assert (h != NULL);
-  assert ((fh_get_referent (h) & mask) != 0);
-  assert (type != NULL);
-  assert (mode != NULL);
-  assert (mode[0] == 'r' || mode[0] == 'w');
-  assert (mode[1] == 's' || mode[1] == 'e');
-  assert (mode[2] == '\0');
-
-  if (h->open_cnt != 0)
-    {
-      if (strcmp (h->type, type))
-        {
-          msg (SE, _("Can't open %s as a %s because it is "
-                     "already open as a %s."),
-               fh_get_name (h), type, h->type);
-          return NULL;
-        }
-      else if (strcmp (h->open_mode, mode))
-        {
-          msg (SE, _("Can't open %s as a %s for %s because it is "
-                     "already open for %s."),
-               fh_get_name (h), type, mode_name (mode),
-               mode_name (h->open_mode));
-          return NULL;
-        }
-      else if (h->open_mode[1] == 'e')
-        {
-          msg (SE, _("Can't re-open %s as a %s for %s."),
-               fh_get_name (h), type, mode_name (mode));
-          return NULL;
-        }
-    }
-  else
-    {
-      h->type = type;
-      strcpy (h->open_mode, mode);
-      assert (h->aux == NULL);
-    }
-  h->open_cnt++;
-
-  return &h->aux;
-}
-
-/* Closes file handle H, which must have been open for the
-   specified TYPE and MODE of access provided to fh_open().
-   Returns zero if the file is now closed, nonzero if it is still
-   open due to another reference.
-
-   After fh_close() returns zero for a handle, it is unsafe to
-   reference that file handle again in any way, because its
-   storage may have been freed. */
-int
-fh_close (struct file_handle *h, const char *type, const char *mode)
-{
-  assert (h != NULL);
-  assert (h->open_cnt > 0);
-  assert (type != NULL);
-  assert (!strcmp (type, h->type));
-  assert (mode != NULL);
-  assert (!strcmp (mode, h->open_mode));
-
-  if (--h->open_cnt == 0)
-    {
-      h->type = NULL;
-      h->aux = NULL;
-      if (h->deleted)
-        free_handle (h);
-      return 0;
-    }
-  return 1;
-}
-
-/* Is the file open?  BEGIN DATA...END DATA uses this to detect
-   whether the inline file is actually in use. */
-bool
-fh_is_open (const struct file_handle *handle)
-{
-  return handle->open_cnt > 0;
-}
-
 /* Returns the identifier that may be used in syntax to name the
    given HANDLE, which takes the form of a PSPP identifier.  If
    HANDLE has no identifier, returns a null pointer.
@@ -387,7 +266,7 @@ fh_is_open (const struct file_handle *handle)
 const char *
 fh_get_id (const struct file_handle *handle)
 {
-  return handle->id[0] != '\0' ? handle->id : NULL;
+  return handle->id;
 }
 
 /* Returns a user-friendly string to identify the given HANDLE.
@@ -446,7 +325,7 @@ fh_get_tab_width (const struct file_handle *handle)
 /* Returns the scratch file handle associated with HANDLE.
    Applicable to only FH_REF_SCRATCH files. */
 struct scratch_handle *
-fh_get_scratch_handle (struct file_handle *handle)
+fh_get_scratch_handle (const struct file_handle *handle)
 {
   assert (handle->referent == FH_REF_SCRATCH);
   return handle->sh;
@@ -465,7 +344,7 @@ fh_set_scratch_handle (struct file_handle *handle, struct scratch_handle *sh)
 struct file_handle *
 fh_get_default_handle (void)
 {
-  return default_handle ? default_handle : fh_inline_file ();
+  return default_handle ? fh_ref (default_handle) : fh_inline_file ();
 }
 
 /* Sets NEW_DEFAULT_HANDLE as the default handle. */
@@ -474,5 +353,235 @@ 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)));
+  if (default_handle != NULL)
+    fh_unref (default_handle);
   default_handle = new_default_handle;
+  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.
+
+   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 hash = hsh_hash_int ((lock->referent << 3) | lock->access);
+  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);
+  return hash;
 }
index 0a79309576e62a4cca91e0189f7d11d73f956aa5..79f0c52a5ef4f4271ea8864838dc935383b6f58c 100644 (file)
@@ -37,6 +37,13 @@ enum fh_mode
     FH_MODE_BINARY              /* Fixed-length records. */
   };
 
+/* Ways to access a file. */
+enum fh_access
+  {
+    FH_ACC_READ,                /* Read from it. */
+    FH_ACC_WRITE                /* Write to it. */
+  };
+
 /* Properties of a file handle. */
 struct fh_properties
   {
@@ -55,8 +62,10 @@ struct file_handle *fh_create_file (const char *handle_name,
 struct file_handle *fh_create_scratch (const char *handle_name);
 const struct fh_properties *fh_default_properties (void);
 
-/* Delete file handle from global list. */
-void fh_free (struct file_handle *);
+/* Reference management. */
+struct file_handle *fh_ref (struct file_handle *);
+void fh_unref (struct file_handle *);
+void fh_unname (struct file_handle *);
 
 /* Finding file handles. */
 struct file_handle *fh_from_id (const char *handle_name);
@@ -77,14 +86,16 @@ size_t fh_get_record_width (const struct file_handle *);
 size_t fh_get_tab_width (const struct file_handle *);
 
 /* Properties of FH_REF_SCRATCH file handles. */
-struct scratch_handle *fh_get_scratch_handle (struct file_handle *);
+struct scratch_handle *fh_get_scratch_handle (const struct file_handle *);
 void fh_set_scratch_handle (struct file_handle *, struct scratch_handle *);
 
-/* Opening and closing file handles. */
-void **fh_open (struct file_handle *, enum fh_referent mask,
-                const char *type, const char *mode);
-int fh_close (struct file_handle *, const char *type, const char *mode);
-bool fh_is_open (const struct file_handle *);
+/* Mutual exclusion for access . */
+struct fh_lock *fh_lock (struct file_handle *, enum fh_referent mask,
+                         const char *type, enum fh_access, bool exclusive);
+bool fh_unlock (struct fh_lock *);
+void *fh_lock_get_aux (const struct fh_lock *);
+void fh_lock_set_aux (struct fh_lock *, void *aux);
+bool fh_is_locked (const struct file_handle *, enum fh_access);
 
 /* Default file handle for DATA LIST, REREAD, REPEATING DATA
    commands. */
index 82279f77b1e2676d5c3e41915b60e734fde64dea..2fd79428e2409fc850e6c644503ad8e6e1778cc7 100644 (file)
@@ -30,8 +30,9 @@
 #include "dirname.h"
 #include "xmalloca.h"
 
-#include <libpspp/message.h>
 #include <data/settings.h>
+#include <libpspp/hash.h>
+#include <libpspp/message.h>
 #include <libpspp/str.h>
 #include <libpspp/verbose-msg.h>
 #include <libpspp/version.h>
@@ -348,12 +349,24 @@ create_stream (const char *fn, const char *mode, mode_t permissions)
   return stream;
 }
 
-#if !(defined _WIN32 || defined __WIN32__)
-/* A file's identity. */
+/* 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
 {
   dev_t device;               /* Device number. */
   ino_t inode;                /* Inode number. */
+  char *name;                 /* File name, where needed, otherwise NULL. */
 };
 
 /* Returns a pointer to a dynamically allocated structure whose
@@ -365,61 +378,41 @@ struct file_identity
 struct file_identity *
 fn_get_identity (const char *file_name)
 {
-  struct stat s;
+  struct file_identity *identity = xmalloc (sizeof *identity);
 
-  if (stat (file_name, &s) == 0)
+#if !(defined _WIN32 || defined __WIN32__)
+  struct stat s;
+  if (lstat (file_name, &s) == 0)
     {
-      struct file_identity *identity = xmalloc (sizeof *identity);
       identity->device = s.st_dev;
       identity->inode = s.st_ino;
-      return identity;
+      identity->name = NULL;
     }
   else
-    return NULL;
-}
-
-/* Frees IDENTITY obtained from fn_get_identity(). */
-void
-fn_free_identity (struct file_identity *identity)
-{
-  free (identity);
-}
-
-/* Compares A and B, returning a strcmp()-type result. */
-int
-fn_compare_file_identities (const struct file_identity *a,
-                            const struct file_identity *b)
-{
-  assert (a != NULL);
-  assert (b != NULL);
-  if (a->device != b->device)
-    return a->device < b->device ? -1 : 1;
-  else
-    return a->inode < b->inode ? -1 : a->inode > b->inode;
-}
+    {
+      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 */
-/* A file's identity. */
-struct file_identity
-{
-  char *normalized_file_name;  /* File's normalized name. */
-};
-
-/* 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
-   fn_free_identity() when finished. */
-struct file_identity *
-fn_get_identity (const char *file_name)
-{
-  struct file_identity *identity = xmalloc (sizeof *identity);
   char cname[PATH_MAX];
-
-  if (GetFullPathName (file_name, sizeof cname, cname, NULL))
-    identity->normalized_file_name = xstrdup (cname);
-  else
-    identity->normalized_file_name = xstrdup (file_name);
+  int ok = GetFullPathName (file_name, sizeof cname, cname, NULL);
+  identity->device = 0;
+  identity->inode = 0;
+  identity->name = xstrdup (ok ? cname : file_name);
+  str_lowercase (identity->file_name);
+#endif /* Windows */
 
   return identity;
 }
@@ -430,7 +423,7 @@ fn_free_identity (struct file_identity *identity)
 {
   if (identity != NULL)
     {
-      free (identity->normalized_file_name);
+      free (identity->name);
       free (identity);
     }
 }
@@ -440,6 +433,22 @@ int
 fn_compare_file_identities (const struct file_identity *a,
                             const struct file_identity *b)
 {
-  return strcasecmp (a->normalized_file_name, b->normalized_file_name);
+  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
+fn_hash_identity (const struct file_identity *identity)
+{
+  unsigned int hash = identity->device ^ identity->inode;
+  if (identity->name != NULL)
+    hash ^= hsh_hash_string (identity->name);
+  return hash;
 }
-#endif /* Windows */
index 5564280bf45a3b4b0404fd2a58692a8c3915fbf0..b4231d2f87178b841f85cf62d69a76e15b871b5e 100644 (file)
@@ -50,5 +50,6 @@ struct file_identity *fn_get_identity (const char *file_name);
 void fn_free_identity (struct file_identity *);
 int fn_compare_file_identities (const struct file_identity *,
                                 const struct file_identity *);
+unsigned int fn_hash_identity (const struct file_identity *);
 
 #endif /* file-name.h */
index 051905b821ad0fbd3ec5a8329c8e817be6550816..f8719dc67223d6cd1f44be998f49632ac5bdf0ce 100644 (file)
 
 #include <config.h>
 #include <assert.h>
+#include <fcntl.h>
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
 #include <stdio.h>
-#include "file-name.h"
-#include "make-file.h"
+
+#include <data/file-name.h>
+#include <data/make-file.h>
+#include <libpspp/ll.h>
 #include <libpspp/message.h>
 
+#include "fatal-signal.h"
+#include "tempname.h"
 #include "xalloc.h"
 
 #include "gettext.h"
@@ -105,7 +110,197 @@ make_unique_file_stream (FILE **fp, char **file_name)
 
   return 1;
 }
+\f
+struct replace_file
+  {
+    struct ll ll;
+    char *file_name;
+    char *tmp_name;
+  };
+
+static struct ll_list all_files = LL_INITIALIZER (all_files);
+
+static void free_replace_file (struct replace_file *);
+static void unlink_replace_files (void);
+
+struct replace_file *
+replace_file_start (const char *file_name, const char *mode,
+                    mode_t permissions, FILE **fp, char **tmp_name)
+{
+  static bool registered;
+  struct stat s;
+  struct replace_file *rf;
+  int fd;
+
+  /* If FILE_NAME represents a special file, write to it directly
+     instead of trying to replace it. */
+  if (stat (file_name, &s) == 0 && !S_ISREG (s.st_mode))
+    {
+      /* Open file descriptor. */
+      fd = open (file_name, O_WRONLY);
+      if (fd < 0)
+        {
+          msg (ME, _("Opening %s for writing: %s."),
+               file_name, strerror (errno));
+          return NULL;
+        }
+
+      /* Open file as stream. */
+      *fp = fdopen (fd, mode);
+      if (*fp == NULL)
+        {
+          msg (ME, _("Opening stream for %s: %s."),
+               file_name, strerror (errno));
+          close (fd);
+          return NULL;
+        }
+
+      rf = xmalloc (sizeof *rf);
+      rf->file_name = NULL;
+      rf->tmp_name = xstrdup (file_name);
+      if (tmp_name != NULL)
+        *tmp_name = rf->tmp_name;
+      return rf;
+    }
+
+  if (!registered)
+    {
+      at_fatal_signal (unlink_replace_files);
+      registered = true;
+    }
+  block_fatal_signals ();
+
+  rf = xmalloc (sizeof *rf);
+  rf->file_name = xstrdup (file_name);
+  for (;;)
+    {
+      /* Generate unique temporary file name. */
+      rf->tmp_name = xasprintf ("%s.tmpXXXXXX", file_name);
+      if (gen_tempname (rf->tmp_name, GT_NOCREATE) < 0)
+        {
+          msg (ME, _("Creating temporary file to replace %s: %s."),
+               rf->file_name, strerror (errno));
+          goto error;
+        }
+
+      /* Create file by that name. */
+      fd = open (rf->tmp_name, O_WRONLY | O_CREAT | O_EXCL, permissions);
+      if (fd >= 0)
+        break;
+      if (errno != EEXIST)
+        {
+          msg (ME, _("Creating temporary file %s: %s."),
+               rf->tmp_name, strerror (errno));
+          goto error;
+        }
+      free (rf->tmp_name);
+    }
+
+
+  /* Open file as stream. */
+  *fp = fdopen (fd, mode);
+  if (*fp == NULL)
+    {
+      msg (ME, _("Opening stream for temporary file %s: %s."),
+           rf->tmp_name, strerror (errno));
+      close (fd);
+      unlink (rf->tmp_name);
+      goto error;
+    }
+
+  /* Register file for deletion. */
+  ll_push_head (&all_files, &rf->ll);
+  unblock_fatal_signals ();
+
+  if (tmp_name != NULL)
+    *tmp_name = rf->tmp_name;
+
+  return rf;
+
+error:
+  unblock_fatal_signals ();
+  free_replace_file (rf);
+  *fp = NULL;
+  if (tmp_name != NULL)
+    *tmp_name = NULL;
+  return NULL;
+}
 
+bool
+replace_file_commit (struct replace_file *rf)
+{
+  bool ok = true;
 
+  if (rf->file_name != NULL)
+    {
+      int save_errno;
 
+      block_fatal_signals ();
+      ok = rename (rf->tmp_name, rf->file_name) == 0;
+      save_errno = errno;
+      ll_remove (&rf->ll);
+      unblock_fatal_signals ();
 
+      if (!ok)
+        msg (ME, _("Replacing %s by %s: %s."),
+             rf->tmp_name, rf->file_name, strerror (save_errno));
+    }
+  else
+    {
+      /* Special file: no temporary file to rename. */
+    }
+  free_replace_file (rf);
+
+  return ok;
+}
+
+bool
+replace_file_abort (struct replace_file *rf)
+{
+  bool ok = true;
+
+  if (rf->file_name != NULL)
+    {
+      int save_errno;
+
+      block_fatal_signals ();
+      ok = unlink (rf->tmp_name) == 0;
+      save_errno = errno;
+      ll_remove (&rf->ll);
+      unblock_fatal_signals ();
+
+      if (!ok)
+        msg (ME, _("Removing %s: %s."), rf->tmp_name, strerror (save_errno));
+    }
+  else
+    {
+      /* Special file: no temporary file to unlink. */
+    }
+  free_replace_file (rf);
+
+  return ok;
+}
+
+static void
+free_replace_file (struct replace_file *rf)
+{
+  free (rf->file_name);
+  free (rf->tmp_name);
+  free (rf);
+}
+
+static void
+unlink_replace_files (void)
+{
+  struct replace_file *rf;
+
+  block_fatal_signals ();
+  ll_for_each (rf, struct replace_file, ll, &all_files)
+    {
+      /* We don't free_replace_file(RF) because calling free is unsafe
+         from an asynchronous signal handler. */
+      unlink (rf->tmp_name);
+      fflush (stdout);
+    }
+  unblock_fatal_signals ();
+}
index ce56758ee87d2b9abc9d80f57e0958a636d30e5c..19d33ebe76bfc070b0f28aed4c11bc61c89b7111 100644 (file)
@@ -29,4 +29,31 @@ int make_temp_file (int *fd, char **file_name);
    responsible for freeing *FILE_NAME. */
 int make_unique_file_stream (FILE **fp, char **file_name) ;
 
+
+/* Prepares to atomically replace a (potentially) existing file
+   by a new file, by creating a temporary file with the given
+   PERMISSIONS bits in the same directory as *FILE_NAME.
+
+   Special files are an exception: they are not atomically
+   replaced but simply opened for writing.
+
+   If successful, stores the temporary file's name in *TMP_NAME
+   and a stream for it opened according to MODE (which should be
+   "w" or "wb") in *FP.  Returns a ticket that can be used to
+   commit or abort the file replacement.  If neither action has
+   yet been taken, program termination via signal will cause
+   *TMP_FILE to be unlinked.
+
+   The caller is responsible for closing *FP, but *TMP_NAME is
+   owned by the callee. */
+struct replace_file *replace_file_start (const char *file_name,
+                                         const char *mode, mode_t permissions,
+                                         FILE **fp, char **tmp_name);
+
+/* Commits or aborts the replacement of a (potentially) existing
+   file by a new file, using the ticket returned by
+   replace_file_start.  Returns success. */
+bool replace_file_commit (struct replace_file *);
+bool replace_file_abort (struct replace_file *);
+
 #endif /* make-file.h */
index 37ae23351a03fb332a4009381c6847cd30de2e40..668a8429d5839d9f8fb810009ef50d24d25eb169 100644 (file)
@@ -65,6 +65,7 @@ struct pfm_reader
     jmp_buf bail_out;           /* longjmp() target for error handling. */
 
     struct file_handle *fh;     /* File handle. */
+    struct fh_lock *lock;       /* Read lock for file. */
     FILE *file;                        /* File stream. */
     int line_length;            /* Number of characters so far on this line. */
     char cc;                   /* Current character. */
@@ -157,8 +158,8 @@ close_reader (struct pfm_reader *r)
       r->file = NULL;
     }
 
-  if (r->fh != NULL)
-    fh_close (r->fh, "portable file", "rs");
+  fh_unlock (r->lock);
+  fh_unref (r->fh);
 
   ok = r->ok;
   pool_destroy (r->pool);
@@ -241,15 +242,14 @@ pfm_open_reader (struct file_handle *fh, struct dictionary **dict,
   struct pfm_reader *volatile r = NULL;
 
   *dict = dict_create ();
-  if (!fh_open (fh, FH_REF_FILE, "portable file", "rs"))
-    goto error;
 
   /* Create and initialize reader. */
   pool = pool_create ();
   r = pool_alloc (pool, sizeof *r);
   r->pool = pool;
-  r->fh = fh;
-  r->file = fn_open (fh_get_file_name (r->fh), "rb");
+  r->fh = fh_ref (fh);
+  r->lock = NULL;
+  r->file = NULL;
   r->line_length = 0;
   r->weight_index = -1;
   r->trans = NULL;
@@ -257,11 +257,16 @@ pfm_open_reader (struct file_handle *fh, struct dictionary **dict,
   r->widths = NULL;
   r->value_cnt = 0;
   r->ok = true;
-
   if (setjmp (r->bail_out))
     goto error;
 
-  /* Check that file open succeeded. */
+  /* Lock file. */
+  r->lock = fh_lock (fh, FH_REF_FILE, "portable file", FH_ACC_READ, false);
+  if (r->lock == NULL)
+    goto error;
+
+  /* Open file. */
+  r->file = fn_open (fh_get_file_name (r->fh), "rb");
   if (r->file == NULL)
     {
       msg (ME, _("An error occurred while opening \"%s\" for reading "
index 111043a8a1e83e2b321ae586e18242892565de42..3d62078e6942635d00b83b727b03176f4e4a7096 100644 (file)
@@ -33,6 +33,7 @@
 #include <data/file-handle-def.h>
 #include <data/file-name.h>
 #include <data/format.h>
+#include <data/make-file.h>
 #include <data/missing-values.h>
 #include <data/short-names.h>
 #include <data/value-labels.h>
@@ -56,7 +57,9 @@
 struct pfm_writer
   {
     struct file_handle *fh;     /* File handle. */
+    struct fh_lock *lock;       /* Lock on file handle. */
     FILE *file;                        /* File stream. */
+    struct replace_file *rf;    /* Ticket for replacing output file. */
 
     int lc;                    /* Number of characters on this line so far. */
 
@@ -108,32 +111,14 @@ pfm_open_writer (struct file_handle *fh, struct dictionary *dict,
 {
   struct pfm_writer *w = NULL;
   mode_t mode;
-  FILE *file;
   size_t i;
 
-  /* Open file handle. */
-  if (!fh_open (fh, FH_REF_FILE, "portable file", "we"))
-    return NULL;
-
-  /* Create file. */
-  mode = S_IRUSR | S_IRGRP | S_IROTH;
-  if (opts.create_writeable)
-    mode |= S_IWUSR | S_IWGRP | S_IWOTH;
-  file = create_stream (fh_get_file_name (fh), "w", mode);
-  if (file == NULL)
-    {
-      fh_close (fh, "portable file", "we");
-      msg (ME, _("An error occurred while opening \"%s\" for writing "
-                 "as a portable file: %s."),
-           fh_get_file_name (fh), strerror (errno));
-      return NULL;
-    }
-
   /* Initialize data structures. */
   w = xmalloc (sizeof *w);
-  w->fh = fh;
-  w->file = file;
-
+  w->fh = fh_ref (fh);
+  w->lock = NULL;
+  w->file = NULL;
+  w->rf = NULL;
   w->lc = 0;
   w->var_cnt = 0;
   w->vars = NULL;
@@ -156,6 +141,24 @@ pfm_open_writer (struct file_handle *fh, struct dictionary *dict,
       w->digits = DBL_DIG;
     }
 
+  /* Lock file. */
+  w->lock = fh_lock (fh, FH_REF_FILE, "portable file", FH_ACC_WRITE, true);
+  if (w->lock == NULL)
+    goto error;
+
+  /* Create file. */
+  mode = S_IRUSR | S_IRGRP | S_IROTH;
+  if (opts.create_writeable)
+    mode |= S_IWUSR | S_IWGRP | S_IWOTH;
+  w->rf = replace_file_start (fh_get_file_name (fh), "w", mode,
+                              &w->file, NULL);
+  if (w->rf == NULL)
+    {
+      msg (ME, _("Error opening \"%s\" for writing as a portable file: %s."),
+           fh_get_file_name (fh), strerror (errno));
+      goto error;
+    }
+
   /* Write file header. */
   write_header (w);
   write_version_data (w);
@@ -165,12 +168,13 @@ pfm_open_writer (struct file_handle *fh, struct dictionary *dict,
     write_documents (w, dict);
   buf_write (w, "F", 1);
   if (ferror (w->file))
-    {
-      close_writer (w);
-      return NULL;
-    }
+    goto error;
   return casewriter_create (dict_get_next_value_idx (dict),
                             &por_file_casewriter_class, w);
+
+error:
+  close_writer (w);
+  return NULL;
 }
 \f
 /* Write NBYTES starting at BUF to the portable file represented by
@@ -491,9 +495,13 @@ close_writer (struct pfm_writer *w)
       if (!ok)
         msg (ME, _("An I/O error occurred writing portable file \"%s\"."),
              fh_get_file_name (w->fh));
+
+      if (ok ? !replace_file_commit (w->rf) : !replace_file_abort (w->rf))
+        ok = false;
     }
 
-  fh_close (w->fh, "portable file", "we");
+  fh_unlock (w->lock);
+  fh_unref (w->fh);
 
   free (w->vars);
   free (w);
index 1d638ef59b2bed5b895355d98a27aa74d65f74ed..c7775f4de995c0460221d3149ed700fa50f0abc1 100644 (file)
@@ -22,6 +22,7 @@
 /* A scratch file. */
 struct scratch_handle
   {
+    unsigned int unique_id;             /* Identifies this scratch file. */
     struct dictionary *dictionary;      /* Dictionary. */
     struct casereader *casereader;      /* Cases. */
   };
index d82486fee8a78dfc282db4eb17320538229489ae..3aa1768d562e1b7bac6441d86981c3b1bdbc1fdf 100644 (file)
 
 #include <config.h>
 
-#include "scratch-reader.h"
+#include <data/scratch-reader.h>
 
 #include <stdlib.h>
 
-#include "dictionary.h"
-#include "file-handle-def.h"
-#include "scratch-handle.h"
 #include <data/case.h>
 #include <data/casereader.h>
+#include <data/dictionary.h>
+#include <data/file-handle-def.h>
+#include <data/scratch-handle.h>
+#include <libpspp/assertion.h>
 #include <libpspp/message.h>
 
 #include "xalloc.h"
@@ -41,8 +42,11 @@ scratch_reader_open (struct file_handle *fh, struct dictionary **dict)
 {
   struct scratch_handle *sh;
 
-  if (!fh_open (fh, FH_REF_SCRATCH, "scratch file", "rs"))
-    return NULL;
+  /* We don't bother doing fh_lock or fh_ref on the file handle,
+     as there's no advantage in this case, and doing these would
+     require us to keep track of the "struct file_handle" and
+     "struct fh_lock" and undo our work later. */
+  assert (fh_get_referent (fh) == FH_REF_SCRATCH);
 
   sh = fh_get_scratch_handle (fh);
   if (sh == NULL || sh->casereader == NULL)
index 4e2929a24164590e0440ddb42e460c46fa95c53f..70a65ce9b67760ec585c9a54878174f4abc300bf 100644 (file)
 /* A scratch file writer. */
 struct scratch_writer
   {
-    struct scratch_handle *handle;      /* Underlying scratch handle. */
     struct file_handle *fh;             /* Underlying file handle. */
-    struct case_map *compactor;         /* Compacts into handle->dictionary. */
+    struct fh_lock *lock;               /* Exclusive access to file handle. */
+    struct dictionary *dict;            /* Dictionary for subwriter. */
+    struct case_map *compactor;         /* Compacts into dictionary. */
     struct casewriter *subwriter;       /* Data output. */
   };
 
@@ -53,47 +54,34 @@ struct casewriter *
 scratch_writer_open (struct file_handle *fh,
                      const struct dictionary *dictionary)
 {
-  struct scratch_handle *sh;
   struct scratch_writer *writer;
-  struct dictionary *scratch_dict;
-  struct case_map *compactor;
   struct casewriter *casewriter;
+  struct fh_lock *lock;
   size_t dict_value_cnt;
 
-  if (!fh_open (fh, FH_REF_SCRATCH, "scratch file", "we"))
+  /* Get exclusive write access to handle. */
+  lock = fh_lock (fh, FH_REF_SCRATCH, "scratch file", FH_ACC_WRITE, true);
+  if (lock == NULL)
     return NULL;
 
-  /* Destroy previous contents of handle. */
-  sh = fh_get_scratch_handle (fh);
-  if (sh != NULL)
-    scratch_handle_destroy (sh);
+  /* Create writer. */
+  writer = xmalloc (sizeof *writer);
+  writer->lock = lock;
+  writer->fh = fh_ref (fh);
 
-  /* Copy the dictionary and compact if needed. */
-  scratch_dict = dict_clone (dictionary);
-  dict_delete_scratch_vars (scratch_dict);
-  if (dict_count_values (scratch_dict, 0)
-      < dict_get_next_value_idx (scratch_dict))
+  writer->dict = dict_clone (dictionary);
+  dict_delete_scratch_vars (writer->dict);
+  if (dict_count_values (writer->dict, 0)
+      < dict_get_next_value_idx (writer->dict))
     {
-      compactor = case_map_to_compact_dict (scratch_dict, 0);
-      dict_compact_values (scratch_dict);
+      writer->compactor = case_map_to_compact_dict (writer->dict, 0);
+      dict_compact_values (writer->dict);
     }
   else
-    compactor = NULL;
-  dict_value_cnt = dict_get_next_value_idx (scratch_dict);
-
-  /* Create new contents. */
-  sh = xmalloc (sizeof *sh);
-  sh->dictionary = scratch_dict;
-  sh->casereader = NULL;
-
-  /* Create writer. */
-  writer = xmalloc (sizeof *writer);
-  writer->handle = sh;
-  writer->fh = fh;
-  writer->compactor = compactor;
+    writer->compactor = NULL;
+  dict_value_cnt = dict_get_next_value_idx (writer->dict);
   writer->subwriter = autopaging_writer_create (dict_value_cnt);
 
-  fh_set_scratch_handle (fh, sh);
   casewriter = casewriter_create (dict_value_cnt,
                                   &scratch_writer_casewriter_class, writer);
   taint_propagate (casewriter_get_taint (writer->subwriter),
@@ -122,11 +110,32 @@ scratch_writer_casewriter_write (struct casewriter *w UNUSED, void *writer_,
 static void
 scratch_writer_casewriter_destroy (struct casewriter *w UNUSED, void *writer_)
 {
+  static unsigned int next_unique_id = 0x12345678;
+
   struct scratch_writer *writer = writer_;
   struct casereader *reader = casewriter_make_reader (writer->subwriter);
   if (!casereader_error (reader))
-    writer->handle->casereader = reader;
-  fh_close (writer->fh, "scratch file", "we");
+    {
+      /* Destroy previous contents of handle. */
+      struct scratch_handle *sh = fh_get_scratch_handle (writer->fh);
+      if (sh != NULL)
+        scratch_handle_destroy (sh);
+
+      /* Create new contents. */
+      sh = xmalloc (sizeof *sh);
+      sh->unique_id = ++next_unique_id;
+      sh->dictionary = writer->dict;
+      sh->casereader = reader;
+      fh_set_scratch_handle (writer->fh, sh);
+    }
+  else
+    {
+      casereader_destroy (reader);
+      dict_destroy (writer->dict);
+    }
+
+  fh_unlock (writer->lock);
+  fh_unref (writer->fh);
   free (writer);
 }
 
index de9ae0d655f831e7097f696581bca5164ca8be8d..7c0312d2990e8953f519c507bb77b5bc1dd5b662 100644 (file)
@@ -66,6 +66,7 @@ struct sfm_reader
 
     /* File state. */
     struct file_handle *fh;     /* File handle. */
+    struct fh_lock *lock;       /* Mutual exclusion for file handle. */
     FILE *file;                 /* File stream. */
     bool error;                 /* I/O or corruption error? */
     size_t value_cnt;           /* Number of "union value"s in struct case. */
@@ -179,39 +180,38 @@ sfm_open_reader (struct file_handle *fh, struct dictionary **dict,
   int claimed_oct_cnt;
   int rec_type;
 
-  if (!fh_open (fh, FH_REF_FILE, "system file", "rs"))
-    return NULL;
-
   *dict = dict_create ();
 
   /* Create and initialize reader. */
   r = pool_create_container (struct sfm_reader, pool);
-  r->fh = fh;
-  r->file = fn_open (fh_get_file_name (fh), "rb");
+  r->fh = fh_ref (fh);
+  r->lock = NULL;
+  r->file = NULL;
   r->error = false;
   r->oct_cnt = 0;
   r->has_long_var_names = false;
   r->opcode_idx = sizeof r->opcodes;
 
+  r->lock = fh_lock (fh, FH_REF_FILE, "system file", FH_ACC_READ, false);
+  if (r->lock == NULL)
+    goto error;
+
+  r->file = fn_open (fh_get_file_name (fh), "rb");
+  if (r->file == NULL)
+    {
+      msg (ME, _("Error opening \"%s\" for reading as a system file: %s."),
+           fh_get_file_name (r->fh), strerror (errno));
+      goto error;
+    }
+
   /* Initialize info. */
   if (info == NULL)
     info = &local_info;
   memset (info, 0, sizeof *info);
 
   if (setjmp (r->bail_out))
-    {
-      close_reader (r);
-      dict_destroy (*dict);
-      *dict = NULL;
-      return NULL;
-    }
+    goto error;
 
-  if (r->file == NULL)
-    {
-      msg (ME, _("Error opening \"%s\" for reading as a system file: %s."),
-           fh_get_file_name (r->fh), strerror (errno));
-      longjmp (r->bail_out, 1);
-    }
 
   /* Read header. */
   read_header (r, *dict, &weight_idx, &claimed_oct_cnt, info);
@@ -305,6 +305,12 @@ sfm_open_reader (struct file_handle *fh, struct dictionary **dict,
     (NULL, r->value_cnt,
      r->case_cnt == -1 ? CASENUMBER_MAX: r->case_cnt,
                                        &sys_file_casereader_class, r);
+
+error:
+  close_reader (r);
+  dict_destroy (*dict);
+  *dict = NULL;
+  return NULL;
 }
 
 /* Closes a system file after we're done with it.
@@ -329,8 +335,8 @@ close_reader (struct sfm_reader *r)
       r->file = NULL;
     }
 
-  if (r->fh != NULL)
-    fh_close (r->fh, "system file", "rs");
+  fh_unlock (r->lock);
+  fh_unref (r->fh);
 
   error = r->error;
   pool_destroy (r->pool);
index a20b9fc1138244286834358df7ed09341a6f5bef..d25d5e8d62529606474901336cfdb079701c4527 100644 (file)
@@ -40,6 +40,7 @@
 #include <data/file-handle-def.h>
 #include <data/file-name.h>
 #include <data/format.h>
+#include <data/make-file.h>
 #include <data/missing-values.h>
 #include <data/settings.h>
 #include <data/short-names.h>
@@ -62,7 +63,9 @@
 struct sfm_writer
   {
     struct file_handle *fh;     /* File handle. */
+    struct fh_lock *lock;       /* Mutual exclusion for file. */
     FILE *file;                        /* File stream. */
+    struct replace_file *rf;    /* Ticket for replacing output file. */
 
     bool compress;             /* 1=compressed, 0=not compressed. */
     casenumber case_cnt;       /* Number of cases written so far. */
@@ -148,9 +151,8 @@ struct casewriter *
 sfm_open_writer (struct file_handle *fh, struct dictionary *d,
                  struct sfm_write_options opts)
 {
-  struct sfm_writer *w = NULL;
+  struct sfm_writer *w;
   mode_t mode;
-  FILE *file;
   int idx;
   int i;
 
@@ -162,27 +164,12 @@ sfm_open_writer (struct file_handle *fh, struct dictionary *d,
       opts.version = 3;
     }
 
-  /* Open file handle as an exclusive writer. */
-  if (!fh_open (fh, FH_REF_FILE, "system file", "we"))
-    return NULL;
-
-  /* Create the file on disk. */
-  mode = S_IRUSR | S_IRGRP | S_IROTH;
-  if (opts.create_writeable)
-    mode |= S_IWUSR | S_IWGRP | S_IWOTH;
-  file = create_stream (fh_get_file_name (fh), "w", mode);
-  if (file == NULL)
-    {
-      msg (ME, _("Error opening \"%s\" for writing as a system file: %s."),
-           fh_get_file_name (fh), strerror (errno));
-      fh_close (fh, "system file", "we");
-      return NULL;
-    }
-
   /* Create and initialize writer. */
   w = xmalloc (sizeof *w);
-  w->fh = fh;
-  w->file = file;
+  w->fh = fh_ref (fh);
+  w->lock = NULL;
+  w->file = NULL;
+  w->rf = NULL;
 
   w->compress = opts.compress;
   w->case_cnt = 0;
@@ -196,6 +183,24 @@ sfm_open_writer (struct file_handle *fh, struct dictionary *d,
   w->segment_cnt = sfm_dictionary_to_sfm_vars (d, &w->sfm_vars,
                                                &w->sfm_var_cnt);
 
+  /* Open file handle as an exclusive writer. */
+  w->lock = fh_lock (fh, FH_REF_FILE, "system file", FH_ACC_WRITE, true);
+  if (w->lock == NULL)
+    goto error;
+
+  /* Create the file on disk. */
+  mode = S_IRUSR | S_IRGRP | S_IROTH;
+  if (opts.create_writeable)
+    mode |= S_IWUSR | S_IWGRP | S_IWOTH;
+  w->rf = replace_file_start (fh_get_file_name (fh), "wb", mode,
+                              &w->file, NULL);
+  if (w->rf == NULL)
+    {
+      msg (ME, _("Error opening \"%s\" for writing as a system file: %s."),
+           fh_get_file_name (fh), strerror (errno));
+      goto error;
+    }
+
   /* Write the file header. */
   write_header (w, d);
 
@@ -239,6 +244,10 @@ sfm_open_writer (struct file_handle *fh, struct dictionary *d,
 
   return casewriter_create (dict_get_next_value_idx (d),
                             &sys_file_casewriter_class, w);
+
+error:
+  close_writer (w);
+  return NULL;
 }
 
 /* Returns value of X truncated to two least-significant digits. */
@@ -724,9 +733,13 @@ close_writer (struct sfm_writer *w)
       if (!ok)
         msg (ME, _("An I/O error occurred writing system file \"%s\"."),
              fh_get_file_name (w->fh));
+
+      if (ok ? !replace_file_commit (w->rf) : !replace_file_abort (w->rf))
+        ok = false;
     }
 
-  fh_close (w->fh, "system file", "we");
+  fh_unlock (w->lock);
+  fh_unref (w->fh);
 
   free (w->sfm_vars);
   free (w);
index 7982916a82747cb27cd07cec7d040c7b7c00f670..b58f9f7cd68ed1b7325f27e98ce2dae45a97c732 100644 (file)
@@ -1,3 +1,36 @@
+2007-11-03  Ben Pfaff  <blp@gnu.org>
+
+       Allow output files to overwrite input files (bug #21280).
+
+       * data-list.c (cmd_data_list): Manage file handle reference
+       counts.
+
+       * data-reader.c (struct dfm_reader): Add `lock' member.
+       (dfm_close_reader): Simplify, as reference counting is now
+       separate from locking.
+       (dfm_open_reader): Lock file.
+
+       * data-writer.c (struct dfm_writer): Add fh_lock, replace_file
+       members.
+       (dfm_open_writer): Lock file and prepare for replacement.
+       (dfm_close_writer): Unlock file and replace it.
+
+       * file-handle.q (cmd_close_file_handle): Use fh_unname.
+       (fh_parse): Don't distinguish existing handles for a given file
+       name from new ones.  Manage file handle reference counts.
+
+       * get.c (parse_read_command): Manage file handle reference counts.
+       (parse_write_command): Ditto.
+       (mtf_close_all_files): Ditto.
+
+       * inpt-pgm.c (cmd_reread): Manage file handle reference counts.
+
+       * print-space.c (cmd_print_space): Manage file handle reference
+       counts.
+
+       * print.c (internal_cmd_print): Manage file handle reference
+       counts.
+
 2007-11-03 John Darrington <john@darrington.wattle.id.au>
 
        * get.c: Add GET DATA command variant.
index 6ca829fc5ab7d75d5459c58d1d828436cc5d5c3c..3ee524d5b80001b7ad7e7c81eb3e0d206f5dd48b 100644 (file)
@@ -120,7 +120,7 @@ cmd_data_list (struct lexer *lexer, struct dataset *ds)
   struct dictionary *dict;
   struct data_list_pgm *dls;
   int table = -1;                /* Print table if nonzero, -1=undecided. */
-  struct file_handle *fh = fh_inline_file ();
+  struct file_handle *fh = NULL;
   struct pool *tmp_pool;
   bool ok;
 
@@ -143,6 +143,7 @@ cmd_data_list (struct lexer *lexer, struct dataset *ds)
       if (lex_match_id (lexer, "FILE"))
        {
          lex_match (lexer, '=');
+          fh_unref (fh);
          fh = fh_parse (lexer, FH_REF_FILE | FH_REF_INLINE);
          if (fh == NULL)
            goto error;
@@ -244,6 +245,8 @@ cmd_data_list (struct lexer *lexer, struct dataset *ds)
        }
     }
 
+  if (fh == NULL)
+    fh = fh_inline_file ();
   fh_set_default_handle (fh);
 
   if (dls->type == -1)
@@ -292,10 +295,12 @@ cmd_data_list (struct lexer *lexer, struct dataset *ds)
     }
 
   pool_destroy (tmp_pool);
+  fh_unref (fh);
 
   return CMD_SUCCESS;
 
  error:
+  fh_unref (fh);
   data_list_trns_free (dls);
   return CMD_CASCADING_FAILURE;
 }
index e2b76e7ef37d9a43044476c2d9498bf4400afaaa..6113bf744c92b112adf5f89a7fc763832894671c 100644 (file)
@@ -55,6 +55,7 @@ enum dfm_reader_flags
 struct dfm_reader
   {
     struct file_handle *fh;     /* File handle. */
+    struct fh_lock *lock;       /* Mutual exclusion lock for file. */
     struct msg_locator where;   /* Current location in data file. */
     struct string line;         /* Current line. */
     struct string scratch;      /* Extra line buffer. */
@@ -69,24 +70,18 @@ struct dfm_reader
 void
 dfm_close_reader (struct dfm_reader *r)
 {
-  int still_open;
-  bool is_inline;
-  char *file_name;
-
   if (r == NULL)
     return;
 
-  is_inline = r->fh == fh_inline_file ();
-  file_name = is_inline ? NULL : xstrdup (fh_get_file_name (r->fh));
-  still_open = fh_close (r->fh, "data file", "rs");
-  if (still_open)
+  if (fh_unlock (r->lock))
     {
-      free (file_name);
+      /* File is still locked by another client. */
       return;
     }
 
-  if (!is_inline)
-    fn_close (file_name, r->file);
+  /* This was the last client, so close the underlying file. */
+  if (fh_get_referent (r->fh) != FH_REF_INLINE)
+    fn_close (fh_get_file_name (r->fh), r->file);
   else
     {
       /* Skip any remaining data on the inline file. */
@@ -98,10 +93,10 @@ dfm_close_reader (struct dfm_reader *r)
         }
     }
 
+  fh_unref (r->fh);
   ds_destroy (&r->line);
   ds_destroy (&r->scratch);
   free (r);
-  free (file_name);
 }
 
 /* Opens the file designated by file handle FH for reading as a
@@ -113,22 +108,26 @@ struct dfm_reader *
 dfm_open_reader (struct file_handle *fh, struct lexer *lexer)
 {
   struct dfm_reader *r;
-  void **rp;
+  struct fh_lock *lock;
 
-  rp = fh_open (fh, FH_REF_FILE | FH_REF_INLINE, "data file", "rs");
-  if (rp == NULL)
+  lock = fh_lock (fh, FH_REF_FILE | FH_REF_INLINE, "data file",
+                  FH_ACC_READ, false);
+  if (lock == NULL)
     return NULL;
-  if (*rp != NULL)
-    return *rp;
+
+  r = fh_lock_get_aux (lock);
+  if (r != NULL)
+    return r;
 
   r = xmalloc (sizeof *r);
-  r->fh = fh;
-  r->lexer = lexer ;
+  r->fh = fh_ref (fh);
+  r->lock = lock;
+  r->lexer = lexer;
   ds_init_empty (&r->line);
   ds_init_empty (&r->scratch);
   r->flags = DFM_ADVANCE;
   r->eof_cnt = 0;
-  if (fh != fh_inline_file ())
+  if (fh_get_referent (fh) != FH_REF_INLINE)
     {
       r->where.file_name = fh_get_file_name (fh);
       r->where.line_number = 0;
@@ -137,12 +136,13 @@ dfm_open_reader (struct file_handle *fh, struct lexer *lexer)
         {
           msg (ME, _("Could not open \"%s\" for reading as a data file: %s."),
                fh_get_file_name (r->fh), strerror (errno));
-          fh_close (fh,"data file", "rs");
+          fh_unlock (r->lock);
+          fh_unref (fh);
           free (r);
           return NULL;
         }
     }
-  *rp = r;
+  fh_lock_set_aux (lock, r);
 
   return r;
 }
@@ -430,7 +430,7 @@ cmd_begin_data (struct lexer *lexer, struct dataset *ds)
   struct dfm_reader *r;
   bool ok;
 
-  if (!fh_is_open (fh_inline_file ()))
+  if (!fh_is_locked (fh_inline_file (), FH_ACC_READ))
     {
       msg (SE, _("This command is not valid here since the current "
                  "input program does not access the inline file."));
index 55b6737b1351ac3fa4e6cbd01a1f96ced717f95b..b1b955c85478c5f5be2407f7e43b9b33690409ed 100644 (file)
 #include <assert.h>
 #include <errno.h>
 #include <stdlib.h>
+#include <sys/stat.h>
 
 #include <data/file-name.h>
+#include <data/make-file.h>
 #include <language/data-io/file-handle.h>
 #include <libpspp/assertion.h>
 #include <libpspp/message.h>
@@ -38,7 +40,9 @@
 struct dfm_writer
   {
     struct file_handle *fh;     /* File handle. */
+    struct fh_lock *lock;       /* Exclusive access to file. */
     FILE *file;                 /* Associated file. */
+    struct replace_file *rf;    /* Atomic file replacement support. */
   };
 
 /* Opens a file handle for writing as a data file. */
@@ -46,31 +50,33 @@ struct dfm_writer *
 dfm_open_writer (struct file_handle *fh)
 {
   struct dfm_writer *w;
-  void **aux;
+  struct fh_lock *lock;
 
-  aux = fh_open (fh, FH_REF_FILE, "data file", "ws");
-  if (aux == NULL)
+  lock = fh_lock (fh, FH_REF_FILE, "data file", FH_ACC_WRITE, false);
+  if (lock == NULL)
     return NULL;
-  if (*aux != NULL)
-    return *aux;
 
-  w = *aux = xmalloc (sizeof *w);
-  w->fh = fh;
-  w->file = fn_open (fh_get_file_name (w->fh), "wb");
-
-  if (w->file == NULL)
+  w = fh_lock_get_aux (lock);
+  if (w != NULL)
+    return w;
+
+  w = xmalloc (sizeof *w);
+  w->fh = fh_ref (fh);
+  w->lock = lock;
+  w->rf = replace_file_start (fh_get_file_name (w->fh), "wb",
+                              (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP
+                               | S_IROTH | S_IWOTH), &w->file, NULL);
+  if (w->rf == NULL)
     {
       msg (ME, _("An error occurred while opening \"%s\" for writing "
                  "as a data file: %s."),
            fh_get_file_name (w->fh), strerror (errno));
-      goto error;
+      dfm_close_writer (w);
+      return NULL;
     }
+  fh_lock_set_aux (lock, w);
 
   return w;
-
- error:
-  dfm_close_writer (w);
-  return NULL;
 }
 
 /* Returns false if an I/O error occurred on WRITER, true otherwise. */
@@ -126,28 +132,27 @@ dfm_put_record (struct dfm_writer *w, const char *rec, size_t len)
 bool
 dfm_close_writer (struct dfm_writer *w)
 {
-  char *file_name;
   bool ok;
 
   if (w == NULL)
     return true;
-  file_name = xstrdup (fh_get_name (w->fh));
-  if (fh_close (w->fh, "data file", "ws"))
-    {
-      free (file_name);
-      return true;
-    }
+  if (fh_unlock (w->lock))
+    return true;
 
   ok = true;
   if (w->file != NULL)
     {
+      const char *file_name = fh_get_file_name (w->fh);
       ok = !dfm_write_error (w) && !fn_close (file_name, w->file);
 
       if (!ok)
         msg (ME, _("I/O error occurred writing data file \"%s\"."), file_name);
+
+      if (ok ? !replace_file_commit (w->rf) : !replace_file_abort (w->rf))
+        ok = false;
     }
+  fh_unref (w->fh);
   free (w);
-  free (file_name);
 
   return ok;
 }
index 71cc961f7227d4e1646a59d098fff9c4a87162d0..d53017735259eeec6b6cb2635fa4dd5734e9f17e 100644 (file)
@@ -133,8 +133,7 @@ cmd_close_file_handle (struct lexer *lexer, struct dataset *ds UNUSED)
   if (handle == NULL)
     return CMD_CASCADING_FAILURE;
 
-  fh_free (handle);
-
+  fh_unname (handle);
   return CMD_SUCCESS;
 }
 
@@ -158,7 +157,10 @@ referent_name (enum fh_referent referent)
 /* Parses a file handle name, which may be a file name as a string
    or a file handle name as an identifier.  The allowed types of
    file handle are restricted to those in REFERENT_MASK.  Returns
-   the file handle when successful, a null pointer on failure. */
+   the file handle when successful, a null pointer on failure.
+
+   The caller is responsible for fh_unref()'ing the returned
+   file handle when it is no longer needed. */
 struct file_handle *
 fh_parse (struct lexer *lexer, enum fh_referent referent_mask)
 {
@@ -177,8 +179,6 @@ fh_parse (struct lexer *lexer, enum fh_referent referent_mask)
       handle = NULL;
       if (lex_token (lexer) == T_ID)
         handle = fh_from_id (lex_tokid (lexer));
-      if (handle == NULL)
-        handle = fh_from_file_name (ds_cstr (lex_tokstr (lexer)));
       if (handle == NULL)
         {
           if (lex_token (lexer) != T_ID || lex_tokid (lexer)[0] != '#' || get_syntax () != ENHANCED)
@@ -194,6 +194,7 @@ fh_parse (struct lexer *lexer, enum fh_referent referent_mask)
     {
       msg (SE, _("Handle for %s not allowed here."),
            referent_name (fh_get_referent (handle)));
+      fh_unref (handle);
       return NULL;
     }
 
index b861aca52ffef7df758d3eabfc506718795081eb..d22a4e50ddd93911a64b514ea61bc76fa4eb0692 100644 (file)
@@ -88,6 +88,7 @@ parse_read_command (struct lexer *lexer, struct dataset *ds, enum reader_command
        {
          lex_match (lexer, '=');
 
+          fh_unref (fh);
          fh = fh_parse (lexer, FH_REF_FILE | FH_REF_SCRATCH);
          if (fh == NULL)
             goto error;
@@ -140,9 +141,11 @@ parse_read_command (struct lexer *lexer, struct dataset *ds, enum reader_command
 
   proc_set_active_file (ds, reader, dict);
 
+  fh_unref (fh);
   return CMD_SUCCESS;
 
  error:
+  fh_unref (fh);
   casereader_destroy (reader);
   if (dict != NULL)
     dict_destroy (dict);
@@ -368,9 +371,11 @@ parse_write_command (struct lexer *lexer, struct dataset *ds,
                                            map);
   dict_destroy (dict);
 
+  fh_unref (handle);
   return writer;
 
  error:
+  fh_unref (handle);
   casewriter_destroy (writer);
   dict_destroy (dict);
   case_map_destroy (map);
@@ -1089,6 +1094,7 @@ mtf_close_all_files (struct mtf_proc *mtf)
 
   ll_for_each_preremove (file, struct mtf_file, ll, &mtf->files)
     {
+      fh_unref (file->handle);
       casereader_destroy (file->reader);
       free (file->by);
       dict_destroy (file->dict);
index 844fe2fe75d04efde0c84741fbbc09477509ac26..b8a31eaefd935cf42fea0db5dda2ce8888740ee7 100644 (file)
@@ -296,6 +296,7 @@ cmd_reread (struct lexer *lexer, struct dataset *ds)
       else if (lex_match_id (lexer, "FILE"))
        {
          lex_match (lexer, '=');
+          fh_unref (fh);
           fh = fh_parse (lexer, FH_REF_FILE | FH_REF_INLINE);
          if (fh == NULL)
            {
@@ -316,6 +317,7 @@ cmd_reread (struct lexer *lexer, struct dataset *ds)
   t->column = e;
   add_transformation (ds, reread_trns_proc, reread_trns_free, t);
 
+  fh_unref (fh);
   return CMD_SUCCESS;
 }
 
index 39773ec0843d98588dd8c11401201e21a2f3983c..c3a2e09a3d93f6f56de75da1a12346ee2329a8b7 100644 (file)
@@ -82,6 +82,7 @@ cmd_print_space (struct lexer *lexer, struct dataset *ds)
       writer = dfm_open_writer (handle);
       if (writer == NULL)
         {
+          fh_unref (handle);
           expr_free (expr);
           return CMD_FAILURE;
         }
@@ -95,6 +96,7 @@ cmd_print_space (struct lexer *lexer, struct dataset *ds)
 
   add_transformation (ds,
                      print_space_trns_proc, print_space_trns_free, trns);
+  fh_unref (handle);
   return CMD_SUCCESS;
 }
 
index 8ec63c4d4f2431a392bae04446c01516fee85563..86d69ea997ed338c630e350c1812e82f1445409e 100644 (file)
@@ -204,11 +204,13 @@ internal_cmd_print (struct lexer *lexer, struct dataset *ds,
   add_transformation (ds, print_trns_proc, print_trns_free, trns);
 
   pool_destroy (tmp_pool);
+  fh_unref (fh);
 
   return CMD_SUCCESS;
 
  error:
   print_trns_free (trns);
+  fh_unref (fh);
   return CMD_FAILURE;
 }
 \f
index fde939f99d60ae2f010d731f54f64d2f0fca8097..31c762f57976dfb5fec76393a834c97b1b648480 100644 (file)
@@ -1,3 +1,13 @@
+2007-11-03  Ben Pfaff  <blp@gnu.org>
+
+       Allow output files to overwrite input files (bug #21280).
+
+       * apply-dictionary.c (cmd_apply_dictionary): Manage file handle
+       reference counts.
+
+       * sys-file-info.c (cmd_sysfile_info): Manage file handle reference
+       counts.
+
 2007-08-12  Ben Pfaff  <blp@gnu.org>
 
        Output variable measurement level, alignment, and display width as
index 0b724044ae077a4a73ed58374355c59dd80f3b14..ff4d7853c3a5e8b6487fafb40e111e8dd624d17e 100644 (file)
@@ -50,13 +50,15 @@ cmd_apply_dictionary (struct lexer *lexer, struct dataset *ds)
 
   lex_match_id (lexer, "FROM");
   lex_match (lexer, '=');
+
   handle = fh_parse (lexer, FH_REF_FILE | FH_REF_SCRATCH);
   if (!handle)
     return CMD_FAILURE;
-
   reader = any_reader_open (handle, &dict);
+  fh_unref (handle);
   if (dict == NULL)
     return CMD_FAILURE;
+
   casereader_destroy (reader);
 
   for (i = 0; i < dict_get_var_cnt (dict); i++)
index 2369b99ec809d92679b21e9c69ae186ea346e101..fb953c94e97712704ed9f0bbadd07c6fa638e311 100644 (file)
@@ -99,7 +99,10 @@ cmd_sysfile_info (struct lexer *lexer, struct dataset *ds UNUSED)
 
   reader = sfm_open_reader (h, &d, &info);
   if (!reader)
-    return CMD_FAILURE;
+    {
+      fh_unref (h);
+      return CMD_FAILURE;
+    }
   casereader_destroy (reader);
 
   t = tab_create (2, 10, 0);
@@ -184,6 +187,7 @@ cmd_sysfile_info (struct lexer *lexer, struct dataset *ds UNUSED)
 
   dict_destroy (d);
 
+  fh_unref (h);
   return lex_end_of_command (lexer);
 }
 \f
index 146640f3a32fb8258f24eeadf5ffaf57f9b64e11..21c28c7911c7bed1a35d90be39e0cee4bd9b0df4 100644 (file)
@@ -1,3 +1,16 @@
+2007-11-03  Ben Pfaff  <blp@gnu.org>
+
+       Allow output files to overwrite input files (bug #21280).
+
+       * aggregate.c (cmd_aggregate): Manage file handle reference
+       counts.
+
+       * correlations.q (internal_cmd_frequencies): Ditto.
+       (cor_custom_matrix): Ditto.
+
+       * regression.q (regression_custom_export): Ditto.
+       (cmd_regression): Ditto.
+
 2007-10-12  Ben Pfaff  <blp@gnu.org>
 
        * flip.c (flip_file): No need to conditionally substitute for
index c9d68d35da70867fccfaf92f0b7f5a1b08322f86..18d78f238ae1f3038168cad0538d119c8903a0c5 100644 (file)
@@ -330,6 +330,7 @@ cmd_aggregate (struct lexer *lexer, struct dataset *ds)
     }
 
   agr_destroy (&agr);
+  fh_unref (out_file);
   return CMD_SUCCESS;
 
 error:
@@ -337,6 +338,7 @@ error:
     proc_commit (ds);
   casewriter_destroy (output);
   agr_destroy (&agr);
+  fh_unref (out_file);
   return CMD_CASCADING_FAILURE;
 }
 
index 540024dbd1f6ee15c98a9deca208b5060b450978..5d543098ec986b8737a8c0cd30e198d7cdea0ce9 100644 (file)
@@ -77,9 +77,13 @@ internal_cmd_correlations (struct lexer *lexer, struct dataset *ds)
   matrix_file = NULL;
 
   if (!parse_correlations (lexer, ds, &cmd, NULL))
-    return CMD_FAILURE;
+    {
+      fh_unref (matrix_file);
+      return CMD_FAILURE;
+    }
 
   free_correlations (&cmd);
+  fh_unref (matrix_file);
 
   return CMD_SUCCESS;
 }
@@ -141,6 +145,7 @@ cor_custom_matrix (struct lexer *lexer, struct dataset *ds UNUSED, struct cmd_co
     matrix_file = NULL;
   else
     {
+      fh_unref (matrix_file);
       matrix_file = fh_parse (lexer, FH_REF_FILE);
       if (matrix_file == NULL)
         return 0;
index 515081a4810997fe623de70ed4b769af3442ceae..e3b3f5ee9338b37ff671c55bce0cb83063129881 100644 (file)
@@ -930,6 +930,7 @@ regression_custom_export (struct lexer *lexer, struct dataset *ds UNUSED,
     model_file = NULL;
   else
     {
+      fh_unref (model_file);
       model_file = fh_parse (lexer, FH_REF_FILE);
       if (model_file == NULL)
        return 0;
@@ -950,8 +951,12 @@ cmd_regression (struct lexer *lexer, struct dataset *ds)
   bool ok;
   size_t i;
 
+  model_file = NULL;
   if (!parse_regression (lexer, ds, &cmd, NULL))
-    return CMD_FAILURE;
+    {
+      fh_unref (model_file);
+      return CMD_FAILURE;
+    }
 
   models = xnmalloc (cmd.n_dependent, sizeof *models);
   for (i = 0; i < cmd.n_dependent; i++)
@@ -970,6 +975,7 @@ cmd_regression (struct lexer *lexer, struct dataset *ds)
   free (v_variables);
   free (models);
   free_regression (&cmd);
+  fh_unref (model_file);
 
   return ok ? CMD_SUCCESS : CMD_FAILURE;
 }
index c626af0aae1cbebfed7a56913aec517fdbb79e3a..b4d329da37537be45e6db74d045ba759586eb881 100644 (file)
@@ -1,3 +1,12 @@
+2007-11-03  Ben Pfaff  <blp@gnu.org>
+
+       Allow output files to overwrite input files (bug #21280).
+
+       * main.c (main): Use at_fatal_signal instead of calling signal
+       directly.
+       (interrupt_handler) Removed.
+       (terminate) Rename clean_up.  Don't call exit directly.
+
 2007-09-25  Ben Pfaff  <blp@gnu.org>
 
        Patch #6210: implement ability to resize output device parameters
index e3d3dbd7df5d7aaf92daa164ef566eea5dc8abd9..2f80cb57a3b06e5ad32e3071c0748895c87f9480 100644 (file)
@@ -53,6 +53,7 @@
 #include <ui/terminal/read-line.h>
 #include <ui/terminal/terminal.h>
 
+#include "fatal-signal.h"
 #include "progname.h"
 
 #include "gettext.h"
@@ -61,7 +62,7 @@
 
 static void i18n_init (void);
 static void fpu_init (void);
-static void terminate (bool success) NO_RETURN;
+static void clean_up (void);
 
 /* If a segfault happens, issue a message to that effect and halt */
 void bug_handler(int sig);
@@ -84,7 +85,7 @@ main (int argc, char **argv)
   signal (SIGABRT, bug_handler);
   signal (SIGSEGV, bug_handler);
   signal (SIGFPE, bug_handler);
-  signal (SIGINT, interrupt_handler);
+  at_fatal_signal (clean_up);
 
   i18n_init ();
   fpu_init ();
@@ -137,7 +138,8 @@ main (int argc, char **argv)
         }
     }
 
-  terminate (!any_errors ());
+  clean_up ();
+  return any_errors ();
 }
 \f
 static void
@@ -188,17 +190,9 @@ bug_handler(int sig)
     }
 }
 
-void
-interrupt_handler(int sig UNUSED)
-{
-  terminate (false);
-}
-
-
-/* Terminate PSPP.  SUCCESS should be true to exit successfully,
-   false to exit as a failure.  */
+/* Clean up PSPP in preparation for termination.  */
 static void
-terminate (bool success)
+clean_up (void)
 {
   static bool terminating = false;
   if (!terminating)
@@ -214,10 +208,8 @@ terminate (bool success)
       destroy_source_stream (the_source_stream);
       prompt_done ();
       readln_uninitialize ();
-
       outp_done ();
       msg_ui_done ();
       fmt_done ();
     }
-  exit (success ? EXIT_SUCCESS : EXIT_FAILURE);
 }
index 555e24cf373a409205fbe8b54d7da45ac665b55c..776e7a4f381ac9c8c4c0312d13d0cebceda620e4 100644 (file)
@@ -1,3 +1,16 @@
+2007-11-03  Ben Pfaff  <blp@gnu.org>
+
+       Allow output files to overwrite input files (bug #21280).
+
+       * automake.mk: Add new file.
+
+       * bugs/overwrite-input-file.sh: Rewrite to make sure that we can
+       overwrite input files safely.
+
+       * bugs/overwrite-special-file.sh: New test.
+
+       * command/erase.sh: Fix "activity" message.
+
 2007-11-03  John Darrington <john@darrington.wattle.id.au>
 
        * Book1.gnm.unzipped command/get-data-gnm.sh: New test and data
index b673bef33f91ccccc0af21d4e966d6914f5795c2..dcec8da65d9d8775fc1b2e08194609c8de953da7 100644 (file)
@@ -108,6 +108,7 @@ dist_TESTS = \
        tests/bugs/match-files-scratch.sh \
        tests/bugs/multipass.sh \
        tests/bugs/overwrite-input-file.sh \
+       tests/bugs/overwrite-special-file.sh \
        tests/bugs/random.sh \
        tests/bugs/signals.sh \
        tests/bugs/t-test-with-temp.sh \
index 56d569cb17dffc2478a5dafcba8ee51f419eb83c..185de8b482b5c92493869e198740a9dd3708a88e 100755 (executable)
@@ -1,9 +1,8 @@
 #!/bin/sh
 
-# This program tests for a bug that caused SAVE to the file currently
-# being read with GET to truncate the save file to zero length, and
-# similarly for IMPORT/EXPORT.
-
+# This program tests that simultaneous input and output to a single
+# file properly coexist, with the output atomically replacing the
+# input if successful.
 
 TEMPDIR=/tmp/pspp-tst-$$
 TESTFILE=$TEMPDIR/`basename $0`.sps
@@ -55,33 +54,35 @@ mkdir -p $TEMPDIR
 
 cd $TEMPDIR
 
-activity="create program 1"
-cat > $TESTFILE <<EOF
-DATA LIST /X 1.
-BEGIN DATA.
+activity="create data file"
+cat > foo.data <<EOF
 1
 2
 3
 4
 5
-END DATA.
+EOF
+if [ $? -ne 0 ] ; then no_result ; fi
 
+activity="create program 1"
+cat > $TESTFILE <<EOF
+DATA LIST FILE='foo.data' /X 1.
 SAVE OUTFILE='foo.sav'.
 EXPORT OUTFILE='foo.por'.
 EOF
 if [ $? -ne 0 ] ; then no_result ; fi
 
-
 activity="run program 1"
 $SUPERVISOR $PSPP --testing-mode $TESTFILE
 if [ $? -ne 0 ] ; then no_result ; fi
 
-
 activity="check and save copy of output files"
 # Check that the files are nonzero length.
+test -s foo.data || fail
 test -s foo.sav || fail
 test -s foo.por || fail
 # Save copies of them.
+cp foo.data foo.data.backup || fail
 cp foo.sav foo.sav.backup || fail
 cp foo.por foo.por.backup || fail
 
@@ -89,37 +90,104 @@ cp foo.por foo.por.backup || fail
 activity="create program 2"
 cat > $TESTFILE <<EOF
 GET 'foo.sav'.
-SAVE OUTFILE='foo.sav'.
+COMPUTE Y = X + 1.
+XSAVE OUTFILE='foo.sav'.
+XEXPORT OUTFILE='foo.por'.
+PRINT OUTFILE='foo.data'.
+HOST kill -TERM \$PPID
 EOF
 if [ $? -ne 0 ] ; then no_result ; fi
 
-
 activity="run program 2"
-$SUPERVISOR $PSPP --testing-mode $TESTFILE -e /dev/null
-# This should have failed with an error message.
-if [ $? -eq 0 ] ; then no_result ; fi
+{ $SUPERVISOR $PSPP --testing-mode $TESTFILE -e /dev/null; } >/dev/null 2>&1
+# PSPP should have terminated with a signal.  POSIX requires that the exit
+# status of a process terminated by a signal be greater than 128.
+if [ $? -le 128 ] ; then no_result ; fi
+
+activity="check for remaining temporary files"
+if test -e *.tmp*; then fail; fi
+
+activity="compare output 1"
+cmp foo.sav foo.sav.backup
+if [ $? -ne 0 ] ; then fail ; fi
+
+activity="compare output 2"
+cmp foo.por foo.por.backup
+if [ $? -ne 0 ] ; then fail ; fi
+
+activity="compare output 3"
+cmp foo.data foo.data.backup
+if [ $? -ne 0 ] ; then fail ; fi
 
 
 activity="create program 3"
 cat > $TESTFILE <<EOF
+DATA LIST NOTABLE LIST FILE='foo.data'/X.
+COMPUTE Y = X + 1.
+PRINT OUTFILE='foo.data'/X Y.
+EXECUTE.
+
+GET 'foo.sav'.
+COMPUTE Y = X + 2.
+SAVE OUTFILE='foo.sav'.
+
 IMPORT 'foo.por'.
+COMPUTE Y = X + 3.
 EXPORT OUTFILE='foo.por'.
 EOF
 if [ $? -ne 0 ] ; then no_result ; fi
 
-
 activity="run program 3"
 $SUPERVISOR $PSPP --testing-mode $TESTFILE -e /dev/null
-# This should have failed with an error message.
-if [ $? -eq 0 ] ; then no_result ; fi
+if [ $? -ne 0 ] ; then no_result ; fi
 
+activity="check for remaining temporary files"
+if test -e *.tmp*; then fail; fi
 
-activity="compare output 1"
-cmp foo.sav foo.sav.backup
-if [ $? -ne 0 ] ; then fail ; fi
+activity="create program 4"
+cat > $TESTFILE <<EOF
+DATA LIST LIST NOTABLE FILE='foo.data'/X Y.
+LIST.
 
-activity="compare output 2"
-cmp foo.por foo.por.backup
+GET 'foo.sav'.
+LIST.
+
+IMPORT 'foo.por'.
+LIST.
+EOF
+if [ $? -ne 0 ] ; then no_result ; fi
+
+activity="run program 4"
+$SUPERVISOR $PSPP --testing-mode $TESTFILE -e /dev/null
+if [ $? -ne 0 ] ; then no_result ; fi
+
+activity="compare output"
+diff -b -B pspp.list - << EOF
+       X        Y
+-------- --------
+    1.00     2.00
+    2.00     3.00
+    3.00     4.00
+    4.00     5.00
+    5.00     6.00
+
+X        Y
+- --------
+1     3.00
+2     4.00
+3     5.00
+4     6.00
+5     7.00
+
+X        Y
+- --------
+1     4.00
+2     5.00
+3     6.00
+4     7.00
+5     8.00
+EOF
 if [ $? -ne 0 ] ; then fail ; fi
 
+
 pass;
diff --git a/tests/bugs/overwrite-special-file.sh b/tests/bugs/overwrite-special-file.sh
new file mode 100755 (executable)
index 0000000..bdc2dc1
--- /dev/null
@@ -0,0 +1,99 @@
+#!/bin/sh
+
+# This program tests that simultaneous input and output to a single
+# file properly coexist, with the output atomically replacing the
+# input if successful.
+
+TEMPDIR=/tmp/pspp-tst-$$
+TESTFILE=$TEMPDIR/`basename $0`.sps
+
+# ensure that top_builddir  are absolute
+if [ -z "$top_builddir" ] ; then top_builddir=. ; fi
+if [ -z "$top_srcdir" ] ; then top_srcdir=. ; fi
+top_builddir=`cd $top_builddir; pwd`
+PSPP=$top_builddir/src/ui/terminal/pspp
+
+# ensure that top_srcdir is absolute
+top_srcdir=`cd $top_srcdir; pwd`
+
+STAT_CONFIG_PATH=$top_srcdir/config
+export STAT_CONFIG_PATH
+
+
+cleanup()
+{
+     cd /
+     rm -rf $TEMPDIR
+}
+
+
+fail()
+{
+    echo $activity
+    echo FAILED
+    cleanup;
+    exit 1;
+}
+
+
+no_result()
+{
+    echo $activity
+    echo NO RESULT;
+    cleanup;
+    exit 2;
+}
+
+pass()
+{
+    cleanup;
+    exit 0;
+}
+
+mkdir -p $TEMPDIR
+
+cd $TEMPDIR
+
+# This test only works on systems that have a /dev/null device...
+test -c /dev/null || no_result
+
+# ...and that are able to make a symbolic link to it...
+ln -s /dev/null foo.out || no_result
+
+# ...that is still /dev/null.
+test -c /dev/null || no_result
+
+activity="create program 1"
+cat > $TESTFILE <<EOF
+DATA LIST /x 1.
+BEGIN DATA.
+1
+2
+3
+4
+5
+END DATA.
+PRINT OUTFILE='foo.out'/x.
+PRINT OUTFILE='foo2.out'/x.
+EXECUTE.
+EOF
+if [ $? -ne 0 ] ; then no_result ; fi
+
+activity="run program 1"
+$SUPERVISOR $PSPP --testing-mode $TESTFILE
+if [ $? -ne 0 ] ; then no_result ; fi
+
+activity="check that foo2.out was created"
+diff -b -B foo2.out - << EOF
+ 1
+ 2
+ 3
+ 4
+ 5
+EOF
+if [ $? -ne 0 ] ; then fail ; fi
+
+activity="check that foo.out is unchanged"
+test -c /dev/null || fail
+
+pass;
index c16ed3dc7213830fc94203dc2e2d4f670ca334f3..0599bed315e666cafc21b71650d4996d753a25f0 100755 (executable)
@@ -90,7 +90,7 @@ EOF
 if [ $? -ne 0 ] ; then no_result ; fi
 
 
-activity="run prog 1"
+activity="run prog 2"
 $SUPERVISOR $PSPP --testing-mode $TESTFILE
 if [ $? -ne 0 ] ; then fail ; fi