Change "union value" to dynamically allocate long strings.
[pspp-builds.git] / src / data / por-file-writer.c
index aa8b5e0bc0139152dba4ae97dc546c1930429ee1..9ccc8fd713cd032774b8189e959b0e4725d0a7b8 100644 (file)
@@ -1,57 +1,67 @@
-/* PSPP - computes sample statistics.
-   Copyright (C) 1997-9, 2000, 2006 Free Software Foundation, Inc.
-   Written by Ben Pfaff <blp@gnu.org>.
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 1997-9, 2000, 2006, 2009 Free Software Foundation, Inc.
 
-   This program is free software; you can redistribute it and/or
-   modify it under the terms of the GNU General Public License as
-   published by the Free Software Foundation; either version 2 of the
-   License, or (at your option) any later version.
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
 
-   This program is distributed in the hope that it will be useful, but
-   WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-   General Public License for more details.
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-   02110-1301, USA. */
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
 #include <config.h>
 #include "por-file-writer.h"
-#include <libpspp/message.h>
+
 #include <ctype.h>
 #include <errno.h>
-#include <fcntl.h>
 #include <float.h>
 #include <math.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/stat.h>
 #include <time.h>
-#include <unistd.h>
-#include <libpspp/alloc.h>
-#include "case.h"
-#include "dictionary.h"
-#include <libpspp/message.h>
-#include "file-handle-def.h"
+
+#include <data/case.h>
+#include <data/casewriter-provider.h>
+#include <data/casewriter.h>
+#include <data/dictionary.h>
+#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>
+#include <data/variable.h>
+
 #include <libpspp/hash.h>
-#include <libpspp/magic.h>
+#include <libpspp/message.h>
 #include <libpspp/misc.h>
-#include "stat-macros.h"
 #include <libpspp/str.h>
-#include "value-labels.h"
-#include "variable.h"
 #include <libpspp/version.h>
 
+#include "minmax.h"
+#include "xalloc.h"
+
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
+#define N_(msgid) (msgid)
+
+/* Maximum width of a variable in a portable file. */
+#define MAX_POR_WIDTH 255
 
 /* Portable file writer. */
 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. */
 
@@ -62,25 +72,30 @@ struct pfm_writer
   };
 
 /* A variable to write to the portable file. */
-struct pfm_var 
+struct pfm_var
   {
     int width;                  /* 0=numeric, otherwise string var width. */
     int fv;                     /* Starting case index. */
   };
 
+static const struct casewriter_class por_file_casewriter_class;
+
+static bool close_writer (struct pfm_writer *);
 static void buf_write (struct pfm_writer *, const void *, size_t);
 static void write_header (struct pfm_writer *);
 static void write_version_data (struct pfm_writer *);
 static void write_variables (struct pfm_writer *, struct dictionary *);
 static void write_value_labels (struct pfm_writer *,
                                 const struct dictionary *);
+static void write_documents (struct pfm_writer *,
+                             const struct dictionary *);
 
 static void format_trig_double (long double, int base_10_precision, char[]);
 static char *format_trig_int (int, bool force_sign, char[]);
 
 /* Returns default options for writing a portable file. */
 struct pfm_write_options
-pfm_writer_default_options (void) 
+pfm_writer_default_options (void)
 {
   struct pfm_write_options opts;
   opts.create_writeable = true;
@@ -92,80 +107,80 @@ pfm_writer_default_options (void)
 /* Writes the dictionary DICT to portable file HANDLE according
    to the given OPTS.  Returns nonzero only if successful.  DICT
    will not be modified, except to assign short names. */
-struct pfm_writer *
+struct casewriter *
 pfm_open_writer (struct file_handle *fh, struct dictionary *dict,
                  struct pfm_write_options opts)
 {
   struct pfm_writer *w = NULL;
   mode_t mode;
-  int fd;
   size_t i;
 
-  /* Create file. */
-  mode = S_IRUSR | S_IRGRP | S_IROTH;
-  if (opts.create_writeable)
-    mode |= S_IWUSR | S_IWGRP | S_IWOTH;
-  fd = open (fh_get_file_name (fh), O_WRONLY | O_CREAT | O_TRUNC, mode);
-  if (fd < 0) 
-    goto open_error;
-
-  /* Open file handle. */
-  if (!fh_open (fh, FH_REF_FILE, "portable file", "we"))
-    goto error;
-
   /* Initialize data structures. */
   w = xmalloc (sizeof *w);
-  w->fh = fh;
-  w->file = fdopen (fd, "w");
-  if (w->file == NULL) 
-    {
-      close (fd);
-      goto open_error;
-    }
-  
+  w->fh = fh_ref (fh);
+  w->lock = NULL;
+  w->file = NULL;
+  w->rf = NULL;
   w->lc = 0;
   w->var_cnt = 0;
   w->vars = NULL;
-  
+
   w->var_cnt = dict_get_var_cnt (dict);
   w->vars = xnmalloc (w->var_cnt, sizeof *w->vars);
-  for (i = 0; i < w->var_cnt; i++) 
+  for (i = 0; i < w->var_cnt; i++)
     {
       const struct variable *dv = dict_get_var (dict, i);
       struct pfm_var *pv = &w->vars[i];
-      pv->width = var_get_width (dv);
-      pv->fv = dv->fv;
+      pv->width = MIN (var_get_width (dv), MAX_POR_WIDTH);
+      pv->fv = var_get_case_index (dv);
     }
 
   w->digits = opts.digits;
-  if (w->digits < 1) 
+  if (w->digits < 1)
     {
       msg (ME, _("Invalid decimal digits count %d.  Treating as %d."),
            w->digits, DBL_DIG);
       w->digits = DBL_DIG;
     }
 
+  /* Lock file. */
+  /* TRANSLATORS: this fragment will be interpolated into
+     messages in fh_lock() that identify types of files. */
+  w->lock = fh_lock (fh, FH_REF_FILE, N_("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);
   write_variables (w, dict);
   write_value_labels (w, dict);
+  if (dict_get_document_line_cnt (dict) > 0)
+    write_documents (w, dict);
   buf_write (w, "F", 1);
-  if (pfm_write_error (w))
+  if (ferror (w->file))
     goto error;
-  return w;
+  return casewriter_create (dict_get_proto (dict),
+                            &por_file_casewriter_class, w);
 
- error:
-  pfm_close_writer (w);
+error:
+  close_writer (w);
   return NULL;
-
- open_error:
-  msg (ME, _("An error occurred while opening \"%s\" for writing "
-             "as a portable file: %s."),
-       fh_get_file_name (fh), strerror (errno));
-  goto error;
 }
-\f  
+\f
 /* Write NBYTES starting at BUF to the portable file represented by
    H.  Break lines properly every 80 characters.  */
 static void
@@ -180,7 +195,7 @@ buf_write (struct pfm_writer *w, const void *buf_, size_t nbytes)
   while (nbytes + w->lc >= 80)
     {
       size_t n = 80 - w->lc;
-      
+
       if (n)
         fwrite (buf, n, 1, w->file);
       fwrite ("\r\n", 2, 1, w->file);
@@ -201,7 +216,8 @@ write_float (struct pfm_writer *w, double d)
   char buffer[64];
   format_trig_double (d, floor (d) == d ? DBL_DIG : w->digits, buffer);
   buf_write (w, buffer, strlen (buffer));
-  buf_write (w, "/", 1);
+  if (d != SYSMIS)
+    buf_write (w, "/", 1);
 }
 
 /* Write N to the portable file as an integer field. */
@@ -238,7 +254,7 @@ write_header (struct pfm_writer *w)
 
   for (i = 0; i < 5; i++)
     buf_write (w, "ASCII SPSS PORT FILE                    ", 40);
-  
+
   buf_write (w, spss2ascii, 256);
   buf_write (w, "SPSSPORT", 8);
 }
@@ -259,9 +275,9 @@ write_version_data (struct pfm_writer *w)
       tm.tm_mday = 1;
       tmp = &tm;
     }
-  else 
+  else
     tmp = localtime (&t);
-    
+
   sprintf (date_str, "%04d%02d%02d",
            tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday);
   sprintf (time_str, "%02d%02d%02d", tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
@@ -278,25 +294,30 @@ write_version_data (struct pfm_writer *w)
   write_string (w, host_system);
 }
 
-/* Write format F to file H. */
+/* Write format F to file H.  The format is first resized to fit
+   a value of the given WIDTH, which is handy in case F
+   represents a string longer than 255 bytes and thus WIDTH is
+   truncated to 255 bytes.  */
 static void
-write_format (struct pfm_writer *w, const struct fmt_spec *f)
+write_format (struct pfm_writer *w, struct fmt_spec f, int width)
 {
-  write_int (w, fmt_to_io (f->type));
-  write_int (w, f->w);
-  write_int (w, f->d);
+  fmt_resize (&f, width);
+  write_int (w, fmt_to_io (f.type));
+  write_int (w, f.w);
+  write_int (w, f.d);
 }
 
 /* Write value V for variable VV to file H. */
 static void
-write_value (struct pfm_writer *w, union value *v, struct variable *vv)
+write_value (struct pfm_writer *w, const union value *v, struct variable *vv)
 {
   if (var_is_numeric (vv))
     write_float (w, v->f);
-  else 
+  else
     {
-      write_int (w, var_get_width (vv));
-      buf_write (w, v->s, var_get_width (vv)); 
+      int width = MIN (var_get_width (vv), MAX_POR_WIDTH);
+      write_int (w, width);
+      buf_write (w, value_str (v, width), width);
     }
 }
 
@@ -306,7 +327,13 @@ write_variables (struct pfm_writer *w, struct dictionary *dict)
 {
   int i;
 
-  dict_assign_short_names (dict);
+  short_names_assign (dict);
+
+  if (dict_get_weight (dict) != NULL) 
+    {
+      buf_write (w, "6", 1);
+      write_string (w, var_get_short_name (dict_get_weight (dict), 0));
+    }
   
   buf_write (w, "4", 1);
   write_int (w, dict_get_var_cnt (dict));
@@ -316,12 +343,13 @@ write_variables (struct pfm_writer *w, struct dictionary *dict)
     {
       struct variable *v = dict_get_var (dict, i);
       struct missing_values mv;
-      
+      int width = MIN (var_get_width (v), MAX_POR_WIDTH);
+
       buf_write (w, "7", 1);
-      write_int (w, var_get_width (v));
-      write_string (w, var_get_short_name (v));
-      write_format (w, var_get_print_format (v));
-      write_format (w, var_get_write_format (v));
+      write_int (w, width);
+      write_string (w, var_get_short_name (v, 0));
+      write_format (w, *var_get_print_format (v), width);
+      write_format (w, *var_get_write_format (v), width);
 
       /* Write missing values. */
       mv_copy (&mv, var_get_missing_values (v));
@@ -346,7 +374,7 @@ write_variables (struct pfm_writer *w, struct dictionary *dict)
               write_float (w, y);
             }
         }
-      while (mv_has_value (&mv)) 
+      while (mv_has_value (&mv))
         {
           union value value;
           mv_pop_value (&mv, &value);
@@ -354,8 +382,9 @@ write_variables (struct pfm_writer *w, struct dictionary *dict)
           write_value (w, &value, v);
         }
 
+      /* Write variable label. */
       if (var_get_label (v) != NULL)
-        { 
+        {
           buf_write (w, "C", 1);
           write_string (w, var_get_label (v));
         }
@@ -370,62 +399,90 @@ write_value_labels (struct pfm_writer *w, const struct dictionary *dict)
 
   for (i = 0; i < dict_get_var_cnt (dict); i++)
     {
-      struct val_labs_iterator *j;
       struct variable *v = dict_get_var (dict, i);
-      struct val_lab *vl;
+      const struct val_labs *val_labs = var_get_value_labels (v);
+      size_t n_labels = val_labs_count (val_labs);
+      const struct val_lab **labels;
 
-      if (!val_labs_count (v->val_labs))
+      if (n_labels == 0)
        continue;
 
       buf_write (w, "D", 1);
       write_int (w, 1);
-      write_string (w, var_get_short_name (v));
-      write_int (w, val_labs_count (v->val_labs));
+      write_string (w, var_get_short_name (v, 0));
+      write_int (w, val_labs_count (val_labs));
 
-      for (vl = val_labs_first_sorted (v->val_labs, &j); vl != NULL;
-           vl = val_labs_next (v->val_labs, &j)) 
+      n_labels = val_labs_count (val_labs);
+      labels = val_labs_sorted (val_labs);
+      for (i = 0; i < n_labels; i++)
         {
-          write_value (w, &vl->value, v);
-          write_string (w, vl->label);
+          const struct val_lab *vl = labels[i];
+          write_value (w, val_lab_get_value (vl), v);
+          write_string (w, val_lab_get_label (vl));
         }
+      free (labels);
     }
 }
 
-/* Writes case ELEM to the portable file represented by H. */
-int 
-pfm_write_case (struct pfm_writer *w, const struct ccase *c)
+/* Write documents in DICT to portable file W. */
+static void
+write_documents (struct pfm_writer *w, const struct dictionary *dict)
 {
+  size_t line_cnt = dict_get_document_line_cnt (dict);
+  struct string line = DS_EMPTY_INITIALIZER;
   int i;
 
-  if (ferror (w->file))
-    return 0;
-  
-  for (i = 0; i < w->var_cnt; i++)
+  buf_write (w, "E", 1);
+  write_int (w, line_cnt);
+  for (i = 0; i < line_cnt; i++)
     {
-      struct pfm_var *v = &w->vars[i];
-      
-      if (v->width == 0)
-        write_float (w, case_num (c, v->fv));
-      else
-       {
-         write_int (w, v->width);
-          buf_write (w, case_str (c, v->fv), v->width);
-       }
+      dict_get_document_line (dict, i, &line);
+      write_string (w, ds_cstr (&line));
     }
+  ds_destroy (&line);
+}
+
+/* Writes case C to the portable file represented by WRITER. */
+static void
+por_file_casewriter_write (struct casewriter *writer, void *w_,
+                           struct ccase *c)
+{
+  struct pfm_writer *w = w_;
+  int i;
 
-  return !pfm_write_error (w);
+  if (!ferror (w->file))
+    {
+      for (i = 0; i < w->var_cnt; i++)
+        {
+          struct pfm_var *v = &w->vars[i];
+
+          if (v->width == 0)
+            write_float (w, case_num_idx (c, v->fv));
+          else
+            {
+              write_int (w, v->width);
+              buf_write (w, case_str_idx (c, v->fv), v->width);
+            }
+        }
+    }
+  else
+    casewriter_force_error (writer);
+
+  case_unref (c);
 }
 
-bool
-pfm_write_error (const struct pfm_writer *w) 
+static void
+por_file_casewriter_destroy (struct casewriter *writer, void *w_)
 {
-  return ferror (w->file);
+  struct pfm_writer *w = w_;
+  if (!close_writer (w))
+    casewriter_force_error (writer);
 }
 
 /* Closes a portable file after we're done with it.
    Returns true if successful, false if an I/O error occurred. */
-bool
-pfm_close_writer (struct pfm_writer *w)
+static bool
+close_writer (struct pfm_writer *w)
 {
   bool ok;
 
@@ -439,17 +496,21 @@ pfm_close_writer (struct pfm_writer *w)
       memset (buf, 'Z', sizeof buf);
       buf_write (w, buf, w->lc >= 80 ? 80 : 80 - w->lc);
 
-      ok = !pfm_write_error (w);
-      if (fclose (w->file) == EOF) 
-        ok = false; 
+      ok = !ferror (w->file);
+      if (fclose (w->file) == EOF)
+        ok = false;
 
-      if (!ok) 
+      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);
 
@@ -472,14 +533,14 @@ pfm_close_writer (struct pfm_writer *w)
 
 /* This is floor(log30(2**31)), the minimum number of trigesimal
    digits that a `long int' can hold. */
-#define CHUNK_SIZE 6                    
+#define CHUNK_SIZE 6
 
 /* pow_tab[i] = pow (30, pow (2, i)) */
 static long double pow_tab[16];
 
 /* Initializes pow_tab[]. */
 static void
-init_pow_tab (void) 
+init_pow_tab (void)
 {
   static bool did_init = false;
   long double power;
@@ -721,7 +782,7 @@ format_trig_double (long double value, int base_10_precision, char output[])
      0...30**6, an invariant of the loop below. */
   errno = 0;
   base_2_sig = frexp (value, &base_2_exp);
-  if (errno != 0 || !finite (base_2_sig))
+  if (errno != 0 || !isfinite (base_2_sig))
     goto missing_value;
   if (base_2_exp == 0 && base_2_sig == 0.)
     goto zero;
@@ -841,3 +902,10 @@ format_trig_double (long double value, int base_10_precision, char output[])
   strcpy (output, "*.");
   return;
 }
+\f
+static const struct casewriter_class por_file_casewriter_class =
+  {
+    por_file_casewriter_write,
+    por_file_casewriter_destroy,
+    NULL,
+  };