Add scratch file handles.
[pspp-builds.git] / src / pfm-write.c
index a4b80bd079321d4b669809ec6cbd7e66d8c273f7..132216d5cbe6ff2a58fc4072d004c0d369eab706 100644 (file)
 #include "error.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 "alloc.h"
 #include "case.h"
 #include "dictionary.h"
 #include "hash.h"
 #include "magic.h"
 #include "misc.h"
+#include "stat-macros.h"
 #include "str.h"
 #include "value-labels.h"
 #include "var.h"
 #include "version.h"
 
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
 #include "debug-print.h"
 
 /* Portable file writer. */
@@ -52,6 +59,8 @@ struct pfm_writer
 
     size_t var_cnt;             /* Number of variables. */
     struct pfm_var *vars;       /* Variables. */
+
+    int digits;                 /* Digits of precision. */
   };
 
 /* A variable to write to the portable file. */
@@ -70,38 +79,57 @@ static int write_value_labels (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[]);
 
-/* Writes the dictionary DICT to portable file HANDLE.  Returns
-   nonzero only if successful.  DICT will not be modified, except
-   to assign short names. */
+/* Returns default options for writing a portable file. */
+struct pfm_write_options
+pfm_writer_default_options (void) 
+{
+  struct pfm_write_options opts;
+  opts.create_writeable = true;
+  opts.type = PFM_COMM;
+  opts.digits = DBL_DIG;
+  return opts;
+}
+
+/* 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 *
-pfm_open_writer (struct file_handle *fh, struct dictionary *dict)
+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;
 
-  if (!fh_open (fh, "portable file", "we"))
+  /* Create file. */
+  mode = S_IRUSR | S_IRGRP | S_IROTH;
+  if (opts.create_writeable)
+    mode |= S_IWUSR | S_IWGRP | S_IWOTH;
+  fd = open (fh_get_filename (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;
-  
-  /* Open the physical disk file. */
+
+  /* Initialize data structures. */
   w = xmalloc (sizeof *w);
   w->fh = fh;
-  w->file = fopen (handle_get_filename (fh), "wb");
+  w->file = fdopen (fd, "w");
+  if (w->file == NULL) 
+    {
+      close (fd);
+      goto open_error;
+    }
+  
   w->lc = 0;
   w->var_cnt = 0;
   w->vars = NULL;
   
-  /* Check that file create succeeded. */
-  if (w->file == NULL)
-    {
-      msg (ME, _("An error occurred while opening \"%s\" for writing "
-          "as a portable file: %s."),
-           handle_get_filename (fh), strerror (errno));
-      err_cond_fail ();
-      goto error;
-    }
-  
   w->var_cnt = dict_get_var_cnt (dict);
-  w->vars = xmalloc (sizeof *w->vars * w->var_cnt);
+  w->vars = xnmalloc (w->var_cnt, sizeof *w->vars);
   for (i = 0; i < w->var_cnt; i++) 
     {
       const struct variable *dv = dict_get_var (dict, i);
@@ -110,6 +138,14 @@ pfm_open_writer (struct file_handle *fh, struct dictionary *dict)
       pv->fv = dv->fv;
     }
 
+  w->digits = opts.digits;
+  if (w->digits < 1) 
+    {
+      msg (ME, _("Invalid decimal digits count %d.  Treating as %d."),
+           w->digits, DBL_DIG);
+      w->digits = DBL_DIG;
+    }
+
   /* Write file header. */
   if (!write_header (w)
       || !write_version_data (w)
@@ -120,9 +156,16 @@ pfm_open_writer (struct file_handle *fh, struct dictionary *dict)
 
   return w;
 
-error:
+ error:
   pfm_close_writer (w);
   return NULL;
+
+ open_error:
+  msg (ME, _("An error occurred while opening \"%s\" for writing "
+             "as a portable file: %s."),
+       fh_get_filename (fh), strerror (errno));
+  err_cond_fail ();
+  goto error;
 }
 \f  
 /* Write NBYTES starting at BUF to the portable file represented by
@@ -156,7 +199,7 @@ buf_write (struct pfm_writer *w, const void *buf_, size_t nbytes)
 
  error:
   msg (ME, _("%s: Writing portable file: %s."),
-       handle_get_filename (w->fh), strerror (errno));
+       fh_get_filename (w->fh), strerror (errno));
   return 0;
 }
 
@@ -166,7 +209,7 @@ static int
 write_float (struct pfm_writer *w, double d)
 {
   char buffer[64];
-  format_trig_double (d, DBL_DIG, buffer);
+  format_trig_double (d, floor (d) == d ? DBL_DIG : w->digits, buffer);
   return buf_write (w, buffer, strlen (buffer)) && buf_write (w, "/", 1);
 }
 
@@ -203,7 +246,7 @@ write_header (struct pfm_writer *w)
   {
     /* PORTME: Translation table from SPSS character code to this
        computer's native character code (which is probably ASCII). */
-    static const unsigned char spss2ascii[256] =
+    static const char spss2ascii[256] =
       {
        "0000000000000000000000000000000000000000000000000000000000000000"
        "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ."
@@ -295,25 +338,43 @@ write_variables (struct pfm_writer *w, struct dictionary *dict)
 
   for (i = 0; i < dict_get_var_cnt (dict); i++)
     {
-      static const char *miss_types[MISSING_COUNT] =
-       {
-         "", "8", "88", "888", "B ", "9", "A", "B 8", "98", "A8",
-       };
-
-      const char *m;
-      int j;
-
       struct variable *v = dict_get_var (dict, i);
+      struct missing_values mv;
       
       if (!buf_write (w, "7", 1) || !write_int (w, v->width)
          || !write_string (w, v->short_name)
          || !write_format (w, &v->print) || !write_format (w, &v->write))
        return 0;
 
-      for (m = miss_types[v->miss_type], j = 0; j < (int) strlen (m); j++)
-       if ((m[j] != ' ' && !buf_write (w, &m[j], 1))
-           || !write_value (w, &v->missing[j], v))
-         return 0;
+      /* Write missing values. */
+      mv_copy (&mv, &v->miss);
+      while (mv_has_range (&mv))
+        {
+          double x, y;
+          mv_pop_range (&mv, &x, &y);
+          if (x == LOWEST)
+            {
+              if (!buf_write (w, "9", 1) || !write_float (w, y))
+                return 0;
+            }
+          else if (y == HIGHEST)
+            {
+              if (!buf_write (w, "A", 1) || !write_float (w, y))
+                return 0;
+            }
+          else {
+            if (!buf_write (w, "B", 1) || !write_float (w, x)
+                || !write_float (w, y))
+              return 0; 
+          }
+        }
+      while (mv_has_value (&mv)) 
+        {
+          union value value;
+          mv_pop_value (&mv, &value);
+          if (!buf_write (w, "8", 1) || !write_value (w, &value, v))
+            return 0; 
+        }
 
       if (v->label && (!buf_write (w, "C", 1) || !write_string (w, v->label)))
        return 0;
@@ -359,7 +420,7 @@ write_value_labels (struct pfm_writer *w, const struct dictionary *dict)
 /* Writes case ELEM to the portable file represented by H.  Returns
    success. */
 int 
-pfm_write_case (struct pfm_writer *w, struct ccase *c)
+pfm_write_case (struct pfm_writer *w, const struct ccase *c)
 {
   int i;
   
@@ -390,8 +451,6 @@ pfm_close_writer (struct pfm_writer *w)
   if (w == NULL)
     return;
 
-  fh_close (w->fh, "portable file", "we");
-  
   if (w->file != NULL)
     {
       char buf[80];
@@ -405,14 +464,24 @@ pfm_close_writer (struct pfm_writer *w)
 
       if (fclose (w->file) == EOF)
         msg (ME, _("%s: Closing portable file: %s."),
-             handle_get_filename (w->fh), strerror (errno));
+             fh_get_filename (w->fh), strerror (errno));
     }
 
+  fh_close (w->fh, "portable file", "we");
+  
   free (w->vars);
   free (w);
 }
 \f
-/* Base-30 conversion. */
+/* Base-30 conversion.
+
+   Portable files represent numbers in base-30 format, so we need
+   to be able to convert real and integer number to that base.
+   Older versions of PSPP used libgmp to do so, but this added a
+   big library dependency to do just one thing.  Now we do it
+   ourselves internally.
+
+   Important fact: base 30 is called "trigesimal". */
 
 /* Conversion base. */
 #define BASE 30                         /* As an integer. */
@@ -422,30 +491,36 @@ pfm_close_writer (struct pfm_writer *w)
    digits that a `long int' can hold. */
 #define CHUNK_SIZE 6                    
 
-/* Yields the square of X. */
-#define Q(X) ((X) * (X))
+/* pow_tab[i] = pow (30, pow (2, i)) */
+static long double pow_tab[16];
+
+/* Initializes pow_tab[]. */
+static void
+init_pow_tab (void) 
+{
+  static bool did_init = false;
+  long double power;
+  size_t i;
+
+  /* Only initialize once. */
+  if (did_init)
+    return;
+  did_init = true;
+
+  /* Set each element of pow_tab[] until we run out of numerical
+     range. */
+  i = 0;
+  for (power = 30.0L; power < DBL_MAX; power *= power)
+    {
+      assert (i < sizeof pow_tab / sizeof *pow_tab);
+      pow_tab[i++] = power;
+    }
+}
 
 /* Returns 30**EXPONENT, for 0 <= EXPONENT <= log30(DBL_MAX). */
 static long double
 pow30_nonnegative (int exponent)
 {
-  /* pow_tab[i] = pow (30, pow (2, i)) */
-  static const long double pow_tab[] =
-    {
-      LDBASE,
-      Q (LDBASE),
-      Q (Q (LDBASE)),
-      Q (Q (Q (LDBASE))),
-      Q (Q (Q (Q (LDBASE)))),
-      Q (Q (Q (Q (Q (LDBASE))))),
-      Q (Q (Q (Q (Q (Q (LDBASE)))))),
-      Q (Q (Q (Q (Q (Q (Q (LDBASE))))))),
-      Q (Q (Q (Q (Q (Q (Q (Q (LDBASE)))))))),
-      Q (Q (Q (Q (Q (Q (Q (Q (Q (LDBASE))))))))),
-      Q (Q (Q (Q (Q (Q (Q (Q (Q (Q (LDBASE)))))))))),
-      Q (Q (Q (Q (Q (Q (Q (Q (Q (Q (Q (LDBASE))))))))))),
-    };
-
   long double power;
   int i;
 
@@ -638,6 +713,8 @@ format_trig_double (long double value, int base_10_precision, char output[])
   /* Number of trigesimal places left to write into BUFFER. */
   int trigs_to_output;
 
+  init_pow_tab ();
+
   /* Handle special cases. */
   if (value == SYSMIS)
     goto missing_value;
@@ -717,6 +794,8 @@ format_trig_double (long double value, int base_10_precision, char output[])
      required base-30 precision as 2/3 of the base-10 precision
      (log30(10) = .68). */
   assert (base_10_precision > 0);
+  if (base_10_precision > LDBL_DIG)
+    base_10_precision = LDBL_DIG;
   base_30_precision = DIV_RND_UP (base_10_precision * 2, 3);
   if (trig_cnt > base_30_precision)
     {