fbuf: New data structure for buffered file I/O.
[pspp] / src / data / por-file-reader.c
index 372d7682136f746110e4cc05d19c04ac30265824..7f6f7e6b73f2aa1e988ba987c2a26b2b7823c2e9 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 1997-9, 2000, 2006, 2009, 2010, 2011 Free Software Foundation, Inc.
+   Copyright (C) 1997-9, 2000, 2006, 2009, 2010, 2011, 2012, 2013, 2014, 2015 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
@@ -16,8 +16,6 @@
 
 #include <config.h>
 
-#include "data/por-file-reader.h"
-
 #include <ctype.h>
 #include <errno.h>
 #include <math.h>
@@ -27,6 +25,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 
+#include "data/any-reader.h"
 #include "data/casereader-provider.h"
 #include "data/casereader.h"
 #include "data/dictionary.h"
@@ -38,6 +37,7 @@
 #include "data/value-labels.h"
 #include "data/variable.h"
 #include "libpspp/compiler.h"
+#include "libpspp/i18n.h"
 #include "libpspp/message.h"
 #include "libpspp/misc.h"
 #include "libpspp/pool.h"
@@ -46,6 +46,7 @@
 #include "gl/intprops.h"
 #include "gl/minmax.h"
 #include "gl/xalloc.h"
+#include "gl/xmemdup0.h"
 
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
@@ -64,10 +65,13 @@ static const char portable_to_local[256] =
 /* Portable file reader. */
 struct pfm_reader
   {
+    struct any_reader any_reader;
     struct pool *pool;          /* All the portable file state. */
 
     jmp_buf bail_out;           /* longjmp() target for error handling. */
 
+    struct dictionary *dict;
+    struct any_read_info info;
     struct file_handle *fh;     /* File handle. */
     struct fh_lock *lock;       /* Read lock for file. */
     FILE *file;                        /* File stream. */
@@ -82,6 +86,13 @@ struct pfm_reader
 
 static const struct casereader_class por_file_casereader_class;
 
+static struct pfm_reader *
+pfm_reader_cast (const struct any_reader *r_)
+{
+  assert (r_->klass == &por_file_reader_class);
+  return UP_CAST (r_, struct pfm_reader, any_reader);
+}
+
 static void
 error (struct pfm_reader *r, const char *msg,...)
      PRINTF_FORMAT (2, 3)
@@ -150,15 +161,16 @@ warning (struct pfm_reader *r, const char *msg, ...)
 /* Close and destroy R.
    Returns false if an error was detected on R, true otherwise. */
 static bool
-close_reader (struct pfm_reader *r)
+pfm_close (struct any_reader *r_)
 {
+  struct pfm_reader *r = pfm_reader_cast (r_);
   bool ok;
-  if (r == NULL)
-    return true;
 
+  dict_destroy (r->dict);
+  any_read_info_destroy (&r->info);
   if (r->file)
     {
-      if (fn_close (fh_get_file_name (r->fh), r->file) == EOF)
+      if (fn_close (r->fh, r->file) == EOF)
         {
           msg (ME, _("Error closing portable file `%s': %s."),
                fh_get_file_name (r->fh), strerror (errno));
@@ -181,7 +193,7 @@ static void
 por_file_casereader_destroy (struct casereader *reader, void *r_)
 {
   struct pfm_reader *r = r_;
-  if (!close_reader (r))
+  if (!pfm_close (&r->any_reader))
     casereader_force_error (reader);
 }
 
@@ -235,7 +247,7 @@ match (struct pfm_reader *r, int c)
 }
 
 static void read_header (struct pfm_reader *);
-static void read_version_data (struct pfm_reader *, struct pfm_read_info *);
+static void read_version_data (struct pfm_reader *, struct any_read_info *);
 static void read_variables (struct pfm_reader *, struct dictionary *);
 static void read_value_label (struct pfm_reader *, struct dictionary *);
 static void read_documents (struct pfm_reader *, struct dictionary *);
@@ -243,18 +255,18 @@ static void read_documents (struct pfm_reader *, struct dictionary *);
 /* Reads the dictionary from file with handle H, and returns it in a
    dictionary structure.  This dictionary may be modified in order to
    rename, reorder, and delete variables, etc. */
-struct casereader *
-pfm_open_reader (struct file_handle *fh, struct dictionary **dict,
-                 struct pfm_read_info *info)
+static struct any_reader *
+pfm_open (struct file_handle *fh)
 {
   struct pool *volatile pool = NULL;
   struct pfm_reader *volatile r = NULL;
 
-  *dict = dict_create ();
-
   /* Create and initialize reader. */
   pool = pool_create ();
   r = pool_alloc (pool, sizeof *r);
+  r->any_reader.klass = &por_file_reader_class;
+  r->dict = dict_create (get_default_encoding ());
+  memset (&r->info, 0, sizeof r->info);
   r->pool = pool;
   r->fh = fh_ref (fh);
   r->lock = NULL;
@@ -276,7 +288,7 @@ pfm_open_reader (struct file_handle *fh, struct dictionary **dict,
     goto error;
 
   /* Open file. */
-  r->file = fn_open (fh_get_file_name (r->fh), "rb");
+  r->file = fn_fopen (r->fh, "rb");
   if (r->file == NULL)
     {
       msg (ME, _("An error occurred while opening `%s' for reading "
@@ -287,31 +299,47 @@ pfm_open_reader (struct file_handle *fh, struct dictionary **dict,
 
   /* Read header, version, date info, product id, variables. */
   read_header (r);
-  read_version_data (r, info);
-  read_variables (r, *dict);
+  read_version_data (r, &r->info);
+  read_variables (r, r->dict);
 
   /* Read value labels. */
   while (match (r, 'D'))
-    read_value_label (r, *dict);
+    read_value_label (r, r->dict);
 
   /* Read documents. */
   if (match (r, 'E'))
-    read_documents (r, *dict);
+    read_documents (r, r->dict);
 
   /* Check that we've made it to the data. */
   if (!match (r, 'F'))
     error (r, _("Data record expected."));
 
-  r->proto = caseproto_ref_pool (dict_get_proto (*dict), r->pool);
-  return casereader_create_sequential (NULL, r->proto, CASENUMBER_MAX,
-                                       &por_file_casereader_class, r);
+  r->proto = caseproto_ref_pool (dict_get_proto (r->dict), r->pool);
+  return &r->any_reader;
 
  error:
-  close_reader (r);
-  dict_destroy (*dict);
-  *dict = NULL;
+  pfm_close (&r->any_reader);
   return NULL;
 }
+
+static struct casereader *
+pfm_decode (struct any_reader *r_, const char *encoding UNUSED,
+            struct dictionary **dictp, struct any_read_info *info)
+{
+  struct pfm_reader *r = pfm_reader_cast (r_);
+
+  *dictp = r->dict;
+  r->dict = NULL;
+
+  if (info)
+    {
+      *info = r->info;
+      memset (&r->info, 0, sizeof r->info);
+    }
+
+  return casereader_create_sequential (NULL, r->proto, CASENUMBER_MAX,
+                                       &por_file_casereader_class, r);
+}
 \f
 /* Returns the value of base-30 digit C,
    or -1 if C is not a base-30 digit. */
@@ -535,11 +563,11 @@ read_header (struct pfm_reader *r)
 /* Reads the version and date info record, as well as product and
    subproduct identification records if present. */
 static void
-read_version_data (struct pfm_reader *r, struct pfm_read_info *info)
+read_version_data (struct pfm_reader *r, struct any_read_info *info)
 {
   static const char empty_string[] = "";
   char *date, *time;
-  const char *product, *author, *subproduct;
+  const char *product, *subproduct;
   int i;
 
   /* Read file. */
@@ -548,7 +576,11 @@ read_version_data (struct pfm_reader *r, struct pfm_read_info *info)
   date = read_pool_string (r);
   time = read_pool_string (r);
   product = match (r, '1') ? read_pool_string (r) : empty_string;
-  author = match (r, '2') ? read_pool_string (r) : empty_string;
+  if (match (r, '2'))
+    {
+      /* Skip "author" field. */
+      read_pool_string (r);
+    }
   subproduct = match (r, '3') ? read_pool_string (r) : empty_string;
 
   /* Validate file. */
@@ -560,16 +592,25 @@ read_version_data (struct pfm_reader *r, struct pfm_read_info *info)
   /* Save file info. */
   if (info != NULL)
     {
+      memset (info, 0, sizeof *info);
+
+      info->float_format = FLOAT_NATIVE_DOUBLE;
+      info->integer_format = INTEGER_NATIVE;
+      info->compression = ANY_COMP_NONE;
+      info->case_cnt = -1;
+
       /* Date. */
+      info->creation_date = xmalloc (11);
       for (i = 0; i < 8; i++)
         {
           static const int map[] = {6, 7, 8, 9, 3, 4, 0, 1};
           info->creation_date[map[i]] = date[i];
         }
       info->creation_date[2] = info->creation_date[5] = ' ';
-      info->creation_date[10] = 0;
+      info->creation_date[10] = '\0';
 
       /* Time. */
+      info->creation_time = xmalloc (9);
       for (i = 0; i < 6; i++)
         {
           static const int map[] = {0, 1, 3, 4, 6, 7};
@@ -579,8 +620,8 @@ read_version_data (struct pfm_reader *r, struct pfm_read_info *info)
       info->creation_time[8] = 0;
 
       /* Product. */
-      str_copy_trunc (info->product, sizeof info->product, product);
-      str_copy_trunc (info->subproduct, sizeof info->subproduct, subproduct);
+      info->product = xstrdup (product);
+      info->product_ext = xstrdup (subproduct);
     }
 }
 
@@ -652,8 +693,8 @@ read_variables (struct pfm_reader *r, struct dictionary *dict)
   if (r->var_cnt <= 0)
     error (r, _("Invalid number of variables %d."), r->var_cnt);
 
-  /* Purpose of this value is unknown.  It is typically 161. */
-  read_int (r);
+  if (match (r, '5'))
+    read_int (r);
 
   if (match (r, '6'))
     {
@@ -745,7 +786,7 @@ read_variables (struct pfm_reader *r, struct dictionary *dict)
         {
           char label[256];
           read_string (r, label);
-          var_set_label (v, label, NULL, false); /* XXX */
+          var_set_label (v, label); /* XXX */
         }
     }
 
@@ -881,9 +922,9 @@ por_file_casereader_read (struct casereader *reader, void *r_)
   return c;
 }
 
-/* Returns true if FILE is an SPSS portable file,
-   false otherwise. */
-bool
+/* Detects whether FILE is an SPSS portable file.  Returns 1 if so, 0 if not,
+   and a negative errno value if there is an error reading FILE. */
+static int
 pfm_detect (FILE *file)
 {
   unsigned char header[464];
@@ -897,7 +938,7 @@ pfm_detect (FILE *file)
     {
       int c = getc (file);
       if (c == EOF || raw_cnt++ > 512)
-        return false;
+        return ferror (file) ? -errno : 0;
       else if (c == '\n')
         {
           while (line_len < 80 && cooked_cnt < sizeof header)
@@ -924,9 +965,9 @@ pfm_detect (FILE *file)
 
   for (i = 0; i < 8; i++)
     if (trans[header[i + 456]] != "SPSSPORT"[i])
-      return false;
+      return 0;
 
-  return true;
+  return 1;
 }
 
 static const struct casereader_class por_file_casereader_class =
@@ -936,3 +977,13 @@ static const struct casereader_class por_file_casereader_class =
     NULL,
     NULL,
   };
+
+const struct any_reader_class por_file_reader_class =
+  {
+    N_("SPSS Portable File"),
+    pfm_detect,
+    pfm_open,
+    pfm_close,
+    pfm_decode,
+    NULL,                       /* get_strings */
+  };