Detect absence of rl_outstream and handle accordingly.
[pspp] / utilities / pspp-dump-sav.c
index a23ce6b42087d1cb5286d96024b5f8c790c6ab09..c6b5823660a140d3575d7d7559b47c87bf899f30 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
+   Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 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
 
 #include <ctype.h>
 #include <errno.h>
+#include <getopt.h>
 #include <inttypes.h>
+#include <limits.h>
 #include <stdio.h>
 #include <stdlib.h>
 
 #include "data/val-type.h"
+#include "libpspp/cast.h"
 #include "libpspp/compiler.h"
 #include "libpspp/float-format.h"
 #include "libpspp/integer-format.h"
 #include "gl/error.h"
 #include "gl/minmax.h"
 #include "gl/progname.h"
+#include "gl/version-etc.h"
 #include "gl/xalloc.h"
 
-#include "gettext.h"
-#define _(msgid) gettext (msgid)
-
 #define ID_MAX_LEN 64
 
 struct sfm_reader
@@ -64,6 +65,8 @@ static void read_machine_integer_info (struct sfm_reader *,
                                        size_t size, size_t count);
 static void read_machine_float_info (struct sfm_reader *,
                                      size_t size, size_t count);
+static void read_extra_product_info (struct sfm_reader *,
+                                     size_t size, size_t count);
 static void read_mrsets (struct sfm_reader *, size_t size, size_t count);
 static void read_display_parameters (struct sfm_reader *,
                                      size_t size, size_t count);
@@ -80,9 +83,11 @@ static void read_character_encoding (struct sfm_reader *r,
                                       size_t size, size_t count);
 static void read_long_string_value_labels (struct sfm_reader *r,
                                            size_t size, size_t count);
+static void read_long_string_missing_values (struct sfm_reader *r,
+                                             size_t size, size_t count);
 static void read_unknown_extension (struct sfm_reader *,
                                     size_t size, size_t count);
-static void read_compressed_data (struct sfm_reader *);
+static void read_compressed_data (struct sfm_reader *, int max_cases);
 
 static struct text_record *open_text_record (
   struct sfm_reader *, size_t size);
@@ -93,8 +98,9 @@ static char *text_tokenize (struct text_record *, int delimiter);
 static bool text_match (struct text_record *text, int c);
 static const char *text_parse_counted_string (struct text_record *);
 static size_t text_pos (const struct text_record *);
+static const char *text_get_all (const struct text_record *);
 
-static void usage (int exit_code);
+static void usage (void);
 static void sys_warn (struct sfm_reader *, const char *, ...)
      PRINTF_FORMAT (2, 3);
 static void sys_error (struct sfm_reader *, const char *, ...)
@@ -110,17 +116,58 @@ static void read_string (struct sfm_reader *, char *, size_t);
 static void skip_bytes (struct sfm_reader *, size_t);
 static void trim_spaces (char *);
 
+static void print_string (const char *s, size_t len);
+
 int
 main (int argc, char *argv[])
 {
+  int max_cases = 0;
   struct sfm_reader r;
   int i;
 
   set_program_name (argv[0]);
-  if (argc < 2)
-    usage (EXIT_FAILURE);
 
-  for (i = 1; i < argc; i++) 
+  for (;;)
+    {
+      static const struct option long_options[] =
+        {
+          { "data",    optional_argument, NULL, 'd' },
+          { "help",    no_argument,       NULL, 'h' },
+          { "version", no_argument,       NULL, 'v' },
+          { NULL,      0,                 NULL, 0 },
+        };
+
+      int c;
+
+      c = getopt_long (argc, argv, "d::hv", long_options, NULL);
+      if (c == -1)
+        break;
+
+      switch (c)
+        {
+        case 'd':
+          max_cases = optarg ? atoi (optarg) : INT_MAX;
+          break;
+
+        case 'v':
+          version_etc (stdout, "pspp-dump-sav", PACKAGE_NAME, PACKAGE_VERSION,
+                       "Ben Pfaff", "John Darrington", NULL_SENTINEL);
+          exit (EXIT_SUCCESS);
+
+        case 'h':
+          usage ();
+          exit (EXIT_SUCCESS);
+
+        default:
+          exit (EXIT_FAILURE);
+        }
+    }
+
+  if (optind == argc)
+    error (1, 0, "at least one non-option argument is required; "
+           "use --help for help");
+
+  for (i = optind; i < argc; i++)
     {
       int rec_type;
 
@@ -135,7 +182,7 @@ main (int argc, char *argv[])
       r.var_widths = 0;
       r.compressed = false;
 
-      if (argc > 2)
+      if (argc - optind > 1)
         printf ("Reading \"%s\":\n", r.file_name);
       
       read_header (&r);
@@ -152,7 +199,7 @@ main (int argc, char *argv[])
               break;
 
             case 4:
-              sys_error (&r, _("Misplaced type 4 record."));
+              sys_error (&r, "Misplaced type 4 record.");
 
             case 6:
               read_document_record (&r);
@@ -163,7 +210,7 @@ main (int argc, char *argv[])
               break;
 
             default:
-              sys_error (&r, _("Unrecognized record type %d."), rec_type);
+              sys_error (&r, "Unrecognized record type %d.", rec_type);
             }
         }
       printf ("%08llx: end-of-dictionary record "
@@ -171,8 +218,8 @@ main (int argc, char *argv[])
               (long long int) ftello (r.file),
               (long long int) ftello (r.file) + 4);
 
-      if (r.compressed)
-        read_compressed_data (&r);
+      if (r.compressed && max_cases > 0)
+        read_compressed_data (&r, max_cases);
 
       fclose (r.file);
     }
@@ -187,7 +234,6 @@ read_header (struct sfm_reader *r)
   char eye_catcher[61];
   uint8_t raw_layout_code[4];
   int32_t layout_code;
-  int32_t nominal_case_size;
   int32_t compressed;
   int32_t weight_index;
   int32_t ncases;
@@ -200,7 +246,7 @@ read_header (struct sfm_reader *r)
   read_string (r, eye_catcher, sizeof eye_catcher);
 
   if (strcmp ("$FL2", rec_type) != 0)
-    sys_error (r, _("This is not an SPSS system file."));
+    sys_error (r, "This is not an SPSS system file.");
 
   /* Identify integer format. */
   read_bytes (r, raw_layout_code, sizeof raw_layout_code);
@@ -210,11 +256,11 @@ read_header (struct sfm_reader *r)
                              &r->integer_format))
       || (r->integer_format != INTEGER_MSB_FIRST
           && r->integer_format != INTEGER_LSB_FIRST))
-    sys_error (r, _("This is not an SPSS system file."));
+    sys_error (r, "This is not an SPSS system file.");
   layout_code = integer_get (r->integer_format,
                              raw_layout_code, sizeof raw_layout_code);
 
-  nominal_case_size = read_int (r);
+  read_int (r);                 /* Nominal case size (not actually useful). */
   compressed = read_int (r);
   weight_index = read_int (r);
   ncases = read_int (r);
@@ -225,9 +271,8 @@ read_header (struct sfm_reader *r)
   read_bytes (r, raw_bias, sizeof raw_bias);
   if (float_identify (100.0, raw_bias, sizeof raw_bias, &r->float_format) == 0)
     {
-      sys_warn (r, _("Compression bias is not the usual "
-                     "value of 100, or system file uses unrecognized "
-                     "floating-point format."));
+      sys_warn (r, "Compression bias is not the usual value of 100, or system "
+                "file uses unrecognized floating-point format.");
       if (r->integer_format == INTEGER_MSB_FIRST)
         r->float_format = FLOAT_IEEE_DOUBLE_BE;
       else
@@ -354,7 +399,7 @@ read_variable_record (struct sfm_reader *r)
 
   /* Get variable label, if any. */
   if (has_variable_label != 0 && has_variable_label != 1)
-    sys_error (r, _("Variable label indicator field is not 0 or 1."));
+    sys_error (r, "Variable label indicator field is not 0 or 1.");
   if (has_variable_label == 1)
     {
       long long int offset = ftello (r->file);
@@ -385,8 +430,8 @@ read_variable_record (struct sfm_reader *r)
         {
           if (missing_value_code < -3 || missing_value_code > 3
               || missing_value_code == -1)
-            sys_error (r, _("Numeric missing value indicator field is not "
-                            "-3, -2, 0, 1, 2, or 3."));
+            sys_error (r, "Numeric missing value indicator field is not "
+                       "-3, -2, 0, 1, 2, or 3.");
           if (missing_value_code < 0)
             {
               double low = read_float (r);
@@ -400,8 +445,8 @@ read_variable_record (struct sfm_reader *r)
       else if (width > 0)
         {
           if (missing_value_code < 1 || missing_value_code > 3)
-            sys_error (r, _("String missing value indicator field is not "
-                            "0, 1, 2, or 3."));
+            sys_error (r, "String missing value indicator field is not "
+                       "0, 1, 2, or 3.");
           for (i = 0; i < missing_value_code; i++)
             {
               char string[9];
@@ -420,7 +465,7 @@ print_untyped_value (struct sfm_reader *r, char raw_value[8])
   double value;
 
   value = float_get_double (r->float_format, raw_value);
-  for (n_printable = 0; n_printable < sizeof raw_value; n_printable++)
+  for (n_printable = 0; n_printable < 8; n_printable++)
     if (!isprint (raw_value[n_printable]))
       break;
 
@@ -466,8 +511,8 @@ read_value_label_record (struct sfm_reader *r)
 
   /* Read record type of type 4 record. */
   if (read_int (r) != 4)
-    sys_error (r, _("Variable index record (type 4) does not immediately "
-                    "follow value label record (type 3) as it should."));
+    sys_error (r, "Variable index record (type 4) does not immediately "
+               "follow value label record (type 3) as it should.");
 
   /* Read number of variables associated with value label from type 4
      record. */
@@ -536,6 +581,10 @@ read_extension_record (struct sfm_reader *r)
       read_mrsets (r, size, count);
       return;
 
+    case 10:
+      read_extra_product_info (r, size, count);
+      return;
+
     case 11:
       read_display_parameters (r, size, count);
       return;
@@ -568,8 +617,12 @@ read_extension_record (struct sfm_reader *r)
       read_long_string_value_labels (r, size, count);
       return;
 
+    case 22:
+      read_long_string_missing_values (r, size, count);
+      return;
+
     default:
-      sys_warn (r, _("Unrecognized record type 7, subtype %d."), subtype);
+      sys_warn (r, "Unrecognized record type 7, subtype %d.", subtype);
       read_unknown_extension (r, size, count);
       return;
     }
@@ -592,9 +645,8 @@ read_machine_integer_info (struct sfm_reader *r, size_t size, size_t count)
 
   printf ("%08llx: machine integer info\n", offset);
   if (size != 4 || count != 8)
-    sys_error (r, _("Bad size (%zu) or count (%zu) field on record type 7, "
-                    "subtype 3."),
-                size, count);
+    sys_error (r, "Bad size (%zu) or count (%zu) field on record type 7, "
+               "subtype 3.", size, count);
 
   printf ("\tVersion: %d.%d.%d\n",
           version_major, version_minor, version_revision);
@@ -623,23 +675,37 @@ read_machine_float_info (struct sfm_reader *r, size_t size, size_t count)
 
   printf ("%08llx: machine float info\n", offset);
   if (size != 8 || count != 3)
-    sys_error (r, _("Bad size (%zu) or count (%zu) on extension 4."),
+    sys_error (r, "Bad size (%zu) or count (%zu) on extension 4.",
                size, count);
 
-  printf ("\tsysmis: %g\n", sysmis);
+  printf ("\tsysmis: %g (%a)\n", sysmis, sysmis);
   if (sysmis != SYSMIS)
-    sys_warn (r, _("File specifies unexpected value %g as %s."),
-              sysmis, "SYSMIS");
+    sys_warn (r, "File specifies unexpected value %g (%a) as %s.",
+              sysmis, sysmis, "SYSMIS");
 
-  printf ("\thighest: %g\n", highest);
+  printf ("\thighest: %g (%a)\n", highest, highest);
   if (highest != HIGHEST)
-    sys_warn (r, _("File specifies unexpected value %g as %s."),
-              highest, "HIGHEST");
+    sys_warn (r, "File specifies unexpected value %g (%a) as %s.",
+              highest, highest, "HIGHEST");
 
-  printf ("\tlowest: %g\n", lowest);
-  if (lowest != LOWEST)
-    sys_warn (r, _("File specifies unexpected value %g as %s."),
-              lowest, "LOWEST");
+  printf ("\tlowest: %g (%a)\n", lowest, lowest);
+  if (lowest != LOWEST && lowest != SYSMIS)
+    sys_warn (r, "File specifies unexpected value %g (%a) as %s.",
+              lowest, lowest, "LOWEST");
+}
+
+static void
+read_extra_product_info (struct sfm_reader *r,
+                         size_t size, size_t count)
+{
+  struct text_record *text;
+  const char *s;
+
+  printf ("%08llx: extra product info\n", (long long int) ftello (r->file));
+  text = open_text_record (r, size * count);
+  s = text_get_all (text);
+  print_string (s, strlen (s));
+  close_text_record (text);
 }
 
 /* Read record type 7, subtype 7. */
@@ -689,8 +755,8 @@ read_mrsets (struct sfm_reader *r, size_t size, size_t count)
 
           if (!text_match (text, ' '))
             {
-              sys_warn (r, _("Missing space following `%c' at offset %zu "
-                             "in MRSETS record"), 'E', text_pos (text));
+              sys_warn (r, "Missing space following `%c' at offset %zu "
+                        "in MRSETS record", 'E', text_pos (text));
               break;
             }
 
@@ -698,8 +764,8 @@ read_mrsets (struct sfm_reader *r, size_t size, size_t count)
           if (!strcmp (number, "11"))
             label_from_var_label = true;
           else if (strcmp (number, "1"))
-            sys_warn (r, _("Unexpected label source value `%s' "
-                           "following `E' at offset %zu in MRSETS record"),
+            sys_warn (r, "Unexpected label source value `%s' "
+                      "following `E' at offset %zu in MRSETS record",
                       number, text_pos (text));
 
         }
@@ -756,7 +822,7 @@ read_display_parameters (struct sfm_reader *r, size_t size, size_t count)
           (long long int) ftello (r->file));
   if (size != 4)
     {
-      sys_warn (r, _("Bad size %zu on extension 11."), size);
+      sys_warn (r, "Bad size %zu on extension 11.", size);
       skip_bytes (r, size * count);
       return;
     }
@@ -768,7 +834,7 @@ read_display_parameters (struct sfm_reader *r, size_t size, size_t count)
     includes_width = false;
   else
     {
-      sys_warn (r, _("Extension 11 has bad count %zu (for %zu variables)."),
+      sys_warn (r, "Extension 11 has bad count %zu (for %zu variables.",
                 count, n_vars);
       skip_bytes (r, size * count);
       return;
@@ -848,13 +914,13 @@ read_attributes (struct sfm_reader *r, struct text_record *text,
           const char *value = text_tokenize (text, '\n');
           if (value == NULL) 
             {
-              sys_warn (r, _("%s: Error parsing attribute value %s[%d]"),
+              sys_warn (r, "%s: Error parsing attribute value %s[%d]",
                         variable, key, index);
               return false;
             }
           if (strlen (value) < 2
               || value[0] != '\'' || value[strlen (value) - 1] != '\'')
-            sys_warn (r, _("%s: Attribute value %s[%d] is not quoted: %s"),
+            sys_warn (r, "%s: Attribute value %s[%d] is not quoted: %s",
                       variable, key, index, value);
           else
             printf ("\t%s: %s[%d] = \"%.*s\"\n",
@@ -878,13 +944,13 @@ read_ncases64 (struct sfm_reader *r, size_t size, size_t count)
 
   if (size != 8)
     {
-      sys_warn (r, _("Bad size %zu for extended number of cases."), size);
+      sys_warn (r, "Bad size %zu for extended number of cases.", size);
       skip_bytes (r, size * count);
       return;
     }
   if (count != 2)
     {
-      sys_warn (r, _("Bad count %zu for extended number of cases."), size);
+      sys_warn (r, "Bad count %zu for extended number of cases.", size);
       skip_bytes (r, size * count);
       return;
     }
@@ -934,8 +1000,8 @@ read_long_string_value_labels (struct sfm_reader *r, size_t size, size_t count)
       /* Read variable name. */
       var_name_len = read_int (r);
       if (var_name_len > ID_MAX_LEN)
-        sys_error (r, _("Variable name length in long string value label "
-                        "record (%d) exceeds %d-byte limit."),
+        sys_error (r, "Variable name length in long string value label "
+                   "record (%d) exceeds %d-byte limit.",
                    var_name_len, ID_MAX_LEN);
       read_string (r, var_name, var_name_len + 1);
 
@@ -976,6 +1042,56 @@ read_long_string_value_labels (struct sfm_reader *r, size_t size, size_t count)
     }
 }
 
+static void
+read_long_string_missing_values (struct sfm_reader *r,
+                                 size_t size, size_t count)
+{
+  long long int start = ftello (r->file);
+
+  printf ("%08llx: long string missing values\n", start);
+  while (ftello (r->file) - start < size * count)
+    {
+      long long posn = ftello (r->file);
+      char var_name[ID_MAX_LEN + 1];
+      uint8_t n_missing_values;
+      int var_name_len;
+      int i;
+
+      /* Read variable name. */
+      var_name_len = read_int (r);
+      if (var_name_len > ID_MAX_LEN)
+        sys_error (r, "Variable name length in long string value label "
+                   "record (%d) exceeds %d-byte limit.",
+                   var_name_len, ID_MAX_LEN);
+      read_string (r, var_name, var_name_len + 1);
+
+      /* Read number of values. */
+      read_bytes (r, &n_missing_values, 1);
+
+      printf ("\t%08llx: %s, %d missing values:",
+              posn, var_name, n_missing_values);
+
+      /* Read values. */
+      for (i = 0; i < n_missing_values; i++)
+       {
+          char *value;
+          int value_length;
+
+          posn = ftello (r->file);
+
+          /* Read value. */
+          value_length = read_int (r);
+          value = xmalloc (value_length + 1);
+          read_string (r, value, value_length + 1);
+
+          printf (" \"%s\"", value);
+
+          free (value);
+       }
+      printf ("\n");
+    }
+}
+
 static void
 hex_dump (size_t offset, const void *buffer_, size_t buffer_size)
 {
@@ -1032,20 +1148,7 @@ read_unknown_extension (struct sfm_reader *r, size_t size, size_t count)
     {
       buffer = xmalloc (count);
       read_bytes (r, buffer, count);
-      if (memchr (buffer, 0, count) == 0)
-        for (i = 0; i < count; i++)
-          {
-            unsigned char c = buffer[i];
-
-            if (c == '\\')
-              printf ("\\\\");
-            else if (c == '\n' || isprint (c))
-              putchar (c);
-            else
-              printf ("\\%02x", c);
-          }
-      else
-        hex_dump (0, buffer, count);
+      print_string (CHAR_CAST (char *, buffer), count);
       free (buffer);
     }
 }
@@ -1067,7 +1170,7 @@ read_variable_attributes (struct sfm_reader *r, size_t size, size_t count)
 }
 
 static void
-read_compressed_data (struct sfm_reader *r)
+read_compressed_data (struct sfm_reader *r, int max_cases)
 {
   enum { N_OPCODES = 8 };
   uint8_t opcodes[N_OPCODES];
@@ -1082,7 +1185,7 @@ read_compressed_data (struct sfm_reader *r)
   opcode_idx = N_OPCODES;
   opcode_ofs = 0;
   case_num = 0;
-  for (case_num = 0; ; case_num++)
+  for (case_num = 0; case_num < max_cases; case_num++)
     {
       printf ("%08llx: case %d's uncompressible data begins\n",
               (long long int) ftello (r->file), case_num);
@@ -1293,14 +1396,26 @@ text_pos (const struct text_record *text)
 {
   return text->pos;
 }
+
+static const char *
+text_get_all (const struct text_record *text)
+{
+  return text->buffer;
+}
 \f
 static void
-usage (int exit_code)
+usage (void)
 {
-  printf ("usage: %s SYSFILE...\n"
-          "where each SYSFILE is the name of a system file\n",
-          program_name);
-  exit (exit_code);
+  printf ("\
+%s, a utility for dissecting system files.\n\
+Usage: %s [OPTION]... SYSFILE...\n\
+where each SYSFILE is the name of a system file.\n\
+\n\
+Options:\n\
+  --data[=MAXCASES]   print (up to MAXCASES cases of) compressed data\n\
+  --help              display this help and exit\n\
+  --version           output version information and exit\n",
+          program_name, program_name);
 }
 
 /* Displays a corruption message. */
@@ -1353,9 +1468,9 @@ read_bytes_internal (struct sfm_reader *r, bool eof_is_ok,
   if (bytes_read == byte_cnt)
     return true;
   else if (ferror (r->file))
-    sys_error (r, _("System error: %s."), strerror (errno));
+    sys_error (r, "System error: %s.", strerror (errno));
   else if (!eof_is_ok || bytes_read != 0)
-    sys_error (r, _("Unexpected end of file."));
+    sys_error (r, "Unexpected end of file.");
   else
     return false;
 }
@@ -1439,3 +1554,27 @@ trim_spaces (char *s)
     end--;
   *end = '\0';
 }
+
+static void
+print_string (const char *s, size_t len)
+{
+  if (memchr (s, 0, len) == 0)
+    {
+      size_t i;
+
+      for (i = 0; i < len; i++)
+        {
+          unsigned char c = s[i];
+
+          if (c == '\\')
+            printf ("\\\\");
+          else if (c == '\n' || isprint (c))
+            putchar (c);
+          else
+            printf ("\\%02x", c);
+        }
+      putchar ('\n');
+    }
+  else
+    hex_dump (0, s, len);
+}