Add support for reading and writing SPV files.
[pspp] / utilities / pspp-dump-sav.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include <ctype.h>
20 #include <errno.h>
21 #include <float.h>
22 #include <getopt.h>
23 #include <inttypes.h>
24 #include <limits.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27
28 #include "data/val-type.h"
29 #include "libpspp/cast.h"
30 #include "libpspp/compiler.h"
31 #include "libpspp/float-format.h"
32 #include "libpspp/integer-format.h"
33 #include "libpspp/misc.h"
34
35 #include "gl/error.h"
36 #include "gl/minmax.h"
37 #include "gl/progname.h"
38 #include "gl/version-etc.h"
39 #include "gl/xalloc.h"
40 #include "gl/xsize.h"
41
42 #define ID_MAX_LEN 64
43
44 enum compression
45   {
46     COMP_NONE,
47     COMP_SIMPLE,
48     COMP_ZLIB
49   };
50
51 struct sfm_reader
52   {
53     const char *file_name;
54     FILE *file;
55
56     int n_variable_records, n_variables;
57
58     int *var_widths;
59     size_t n_var_widths, allocated_var_widths;
60
61     enum integer_format integer_format;
62     enum float_format float_format;
63
64     enum compression compression;
65     double bias;
66   };
67
68 static void read_header (struct sfm_reader *);
69 static void read_variable_record (struct sfm_reader *);
70 static void read_value_label_record (struct sfm_reader *);
71 static void read_document_record (struct sfm_reader *);
72 static void read_extension_record (struct sfm_reader *);
73 static void read_machine_integer_info (struct sfm_reader *,
74                                        size_t size, size_t count);
75 static void read_machine_float_info (struct sfm_reader *,
76                                      size_t size, size_t count);
77 static void read_extra_product_info (struct sfm_reader *,
78                                      size_t size, size_t count);
79 static void read_mrsets (struct sfm_reader *, size_t size, size_t count);
80 static void read_display_parameters (struct sfm_reader *,
81                                      size_t size, size_t count);
82 static void read_long_var_name_map (struct sfm_reader *r,
83                                     size_t size, size_t count);
84 static void read_long_string_map (struct sfm_reader *r,
85                                   size_t size, size_t count);
86 static void read_datafile_attributes (struct sfm_reader *r,
87                                       size_t size, size_t count);
88 static void read_variable_attributes (struct sfm_reader *r,
89                                       size_t size, size_t count);
90 static void read_ncases64 (struct sfm_reader *, size_t size, size_t count);
91 static void read_character_encoding (struct sfm_reader *r,
92                                        size_t size, size_t count);
93 static void read_long_string_value_labels (struct sfm_reader *r,
94                                            size_t size, size_t count);
95 static void read_long_string_missing_values (struct sfm_reader *r,
96                                              size_t size, size_t count);
97 static void read_unknown_extension (struct sfm_reader *,
98                                     size_t size, size_t count);
99 static void read_simple_compressed_data (struct sfm_reader *, int max_cases);
100 static void read_zlib_compressed_data (struct sfm_reader *);
101
102 static struct text_record *open_text_record (
103   struct sfm_reader *, size_t size, size_t count);
104 static void close_text_record (struct text_record *);
105 static bool read_variable_to_value_pair (struct text_record *,
106                                          char **key, char **value);
107 static char *text_tokenize (struct text_record *, int delimiter);
108 static bool text_match (struct text_record *text, int c);
109 static const char *text_parse_counted_string (struct text_record *);
110 static size_t text_pos (const struct text_record *);
111 static const char *text_get_all (const struct text_record *);
112
113 static void usage (void);
114 static void sys_warn (struct sfm_reader *, const char *, ...)
115      PRINTF_FORMAT (2, 3);
116 static void sys_error (struct sfm_reader *, const char *, ...)
117      PRINTF_FORMAT (2, 3)
118      NO_RETURN;
119
120 static void read_bytes (struct sfm_reader *, void *, size_t);
121 static bool try_read_bytes (struct sfm_reader *, void *, size_t);
122 static int read_int (struct sfm_reader *);
123 static int64_t read_int64 (struct sfm_reader *);
124 static double read_float (struct sfm_reader *);
125 static void read_string (struct sfm_reader *, char *, size_t);
126 static void skip_bytes (struct sfm_reader *, size_t);
127 static void trim_spaces (char *);
128
129 static void print_string (const char *s, size_t len);
130
131 int
132 main (int argc, char *argv[])
133 {
134   int max_cases = 0;
135   struct sfm_reader r;
136   int i;
137
138   set_program_name (argv[0]);
139
140   for (;;)
141     {
142       static const struct option long_options[] =
143         {
144           { "data",    optional_argument, NULL, 'd' },
145           { "help",    no_argument,       NULL, 'h' },
146           { "version", no_argument,       NULL, 'v' },
147           { NULL,      0,                 NULL, 0 },
148         };
149
150       int c;
151
152       c = getopt_long (argc, argv, "d::hv", long_options, NULL);
153       if (c == -1)
154         break;
155
156       switch (c)
157         {
158         case 'd':
159           max_cases = optarg ? atoi (optarg) : INT_MAX;
160           break;
161
162         case 'v':
163           version_etc (stdout, "pspp-dump-sav", PACKAGE_NAME, PACKAGE_VERSION,
164                        "Ben Pfaff", "John Darrington", NULL_SENTINEL);
165           exit (EXIT_SUCCESS);
166
167         case 'h':
168           usage ();
169           exit (EXIT_SUCCESS);
170
171         default:
172           exit (EXIT_FAILURE);
173         }
174     }
175
176   if (optind == argc)
177     error (1, 0, "at least one non-option argument is required; "
178            "use --help for help");
179
180   for (i = optind; i < argc; i++)
181     {
182       int rec_type;
183
184       r.file_name = argv[i];
185       r.file = fopen (r.file_name, "rb");
186       if (r.file == NULL)
187         error (EXIT_FAILURE, errno, "error opening `%s'", r.file_name);
188       r.n_variable_records = 0;
189       r.n_variables = 0;
190       r.n_var_widths = 0;
191       r.allocated_var_widths = 0;
192       r.var_widths = 0;
193       r.compression = COMP_NONE;
194
195       if (argc - optind > 1)
196         printf ("Reading \"%s\":\n", r.file_name);
197
198       read_header (&r);
199       while ((rec_type = read_int (&r)) != 999)
200         {
201           switch (rec_type)
202             {
203             case 2:
204               read_variable_record (&r);
205               break;
206
207             case 3:
208               read_value_label_record (&r);
209               break;
210
211             case 4:
212               sys_error (&r, "Misplaced type 4 record.");
213
214             case 6:
215               read_document_record (&r);
216               break;
217
218             case 7:
219               read_extension_record (&r);
220               break;
221
222             default:
223               sys_error (&r, "Unrecognized record type %d.", rec_type);
224             }
225         }
226       printf ("%08llx: end-of-dictionary record "
227               "(first byte of data at %08llx)\n",
228               (long long int) ftello (r.file),
229               (long long int) ftello (r.file) + 4);
230
231       if (r.compression == COMP_SIMPLE)
232         {
233           if (max_cases > 0)
234             read_simple_compressed_data (&r, max_cases);
235         }
236       else if (r.compression == COMP_ZLIB)
237         read_zlib_compressed_data (&r);
238
239       fclose (r.file);
240     }
241
242   return 0;
243 }
244
245 static void
246 read_header (struct sfm_reader *r)
247 {
248   char rec_type[5];
249   char eye_catcher[61];
250   uint8_t raw_layout_code[4];
251   int32_t layout_code;
252   int32_t compressed;
253   int32_t weight_index;
254   int32_t ncases;
255   uint8_t raw_bias[8];
256   char creation_date[10];
257   char creation_time[9];
258   char file_label[65];
259   bool zmagic;
260
261   read_string (r, rec_type, sizeof rec_type);
262   read_string (r, eye_catcher, sizeof eye_catcher);
263
264   if (!strcmp ("$FL2", rec_type))
265     zmagic = false;
266   else if (!strcmp ("$FL3", rec_type))
267     zmagic = true;
268   else
269     sys_error (r, "This is not an SPSS system file.");
270
271   /* Identify integer format. */
272   read_bytes (r, raw_layout_code, sizeof raw_layout_code);
273   if ((!integer_identify (2, raw_layout_code, sizeof raw_layout_code,
274                           &r->integer_format)
275        && !integer_identify (3, raw_layout_code, sizeof raw_layout_code,
276                              &r->integer_format))
277       || (r->integer_format != INTEGER_MSB_FIRST
278           && r->integer_format != INTEGER_LSB_FIRST))
279     sys_error (r, "This is not an SPSS system file.");
280   layout_code = integer_get (r->integer_format,
281                              raw_layout_code, sizeof raw_layout_code);
282
283   read_int (r);                 /* Nominal case size (not actually useful). */
284   compressed = read_int (r);
285   weight_index = read_int (r);
286   ncases = read_int (r);
287
288   if (!zmagic)
289     {
290       if (compressed == 0)
291         r->compression = COMP_NONE;
292       else if (compressed == 1)
293         r->compression = COMP_SIMPLE;
294       else if (compressed != 0)
295         sys_error (r, "SAV file header has invalid compression value "
296                    "%"PRId32".", compressed);
297     }
298   else
299     {
300       if (compressed == 2)
301         r->compression = COMP_ZLIB;
302       else
303         sys_error (r, "ZSAV file header has invalid compression value "
304                    "%"PRId32".", compressed);
305     }
306
307   /* Identify floating-point format and obtain compression bias. */
308   read_bytes (r, raw_bias, sizeof raw_bias);
309   if (float_identify (100.0, raw_bias, sizeof raw_bias, &r->float_format) == 0)
310     {
311       sys_warn (r, "Compression bias is not the usual value of 100, or system "
312                 "file uses unrecognized floating-point format.");
313       if (r->integer_format == INTEGER_MSB_FIRST)
314         r->float_format = FLOAT_IEEE_DOUBLE_BE;
315       else
316         r->float_format = FLOAT_IEEE_DOUBLE_LE;
317     }
318   r->bias = float_get_double (r->float_format, raw_bias);
319
320   read_string (r, creation_date, sizeof creation_date);
321   read_string (r, creation_time, sizeof creation_time);
322   read_string (r, file_label, sizeof file_label);
323   trim_spaces (file_label);
324   skip_bytes (r, 3);
325
326   printf ("File header record:\n");
327   printf ("\t%17s: %s\n", "Product name", eye_catcher);
328   printf ("\t%17s: %"PRId32"\n", "Layout code", layout_code);
329   printf ("\t%17s: %"PRId32" (%s)\n", "Compressed",
330           compressed,
331           r->compression == COMP_NONE ? "no compression"
332           : r->compression == COMP_SIMPLE ? "simple compression"
333           : r->compression == COMP_ZLIB ? "ZLIB compression"
334           : "<error>");
335   printf ("\t%17s: %"PRId32"\n", "Weight index", weight_index);
336   printf ("\t%17s: %"PRId32"\n", "Number of cases", ncases);
337   printf ("\t%17s: %.*g\n", "Compression bias", DBL_DIG + 1, r->bias);
338   printf ("\t%17s: %s\n", "Creation date", creation_date);
339   printf ("\t%17s: %s\n", "Creation time", creation_time);
340   printf ("\t%17s: \"%s\"\n", "File label", file_label);
341 }
342
343 static const char *
344 format_name (int format)
345 {
346   switch ((format >> 16) & 0xff)
347     {
348     case 1: return "A";
349     case 2: return "AHEX";
350     case 3: return "COMMA";
351     case 4: return "DOLLAR";
352     case 5: return "F";
353     case 6: return "IB";
354     case 7: return "PIBHEX";
355     case 8: return "P";
356     case 9: return "PIB";
357     case 10: return "PK";
358     case 11: return "RB";
359     case 12: return "RBHEX";
360     case 15: return "Z";
361     case 16: return "N";
362     case 17: return "E";
363     case 20: return "DATE";
364     case 21: return "TIME";
365     case 22: return "DATETIME";
366     case 23: return "ADATE";
367     case 24: return "JDATE";
368     case 25: return "DTIME";
369     case 26: return "WKDAY";
370     case 27: return "MONTH";
371     case 28: return "MOYR";
372     case 29: return "QYR";
373     case 30: return "WKYR";
374     case 31: return "PCT";
375     case 32: return "DOT";
376     case 33: return "CCA";
377     case 34: return "CCB";
378     case 35: return "CCC";
379     case 36: return "CCD";
380     case 37: return "CCE";
381     case 38: return "EDATE";
382     case 39: return "SDATE";
383     case 40: return "MTIME";
384     case 41: return "YMDHMS";
385     default: return "invalid";
386     }
387 }
388
389 /* Reads a variable (type 2) record from R and adds the
390    corresponding variable to DICT.
391    Also skips past additional variable records for long string
392    variables. */
393 static void
394 read_variable_record (struct sfm_reader *r)
395 {
396   int width;
397   int has_variable_label;
398   int missing_value_code;
399   int print_format;
400   int write_format;
401   char name[9];
402
403   printf ("%08llx: variable record #%d\n",
404           (long long int) ftello (r->file), r->n_variable_records++);
405
406   width = read_int (r);
407   has_variable_label = read_int (r);
408   missing_value_code = read_int (r);
409   print_format = read_int (r);
410   write_format = read_int (r);
411   read_string (r, name, sizeof name);
412   name[strcspn (name, " ")] = '\0';
413
414   if (width >= 0)
415     r->n_variables++;
416
417   if (r->n_var_widths >= r->allocated_var_widths)
418     r->var_widths = x2nrealloc (r->var_widths, &r->allocated_var_widths,
419                                 sizeof *r->var_widths);
420   r->var_widths[r->n_var_widths++] = width;
421
422   printf ("\tWidth: %d (%s)\n",
423           width,
424           width > 0 ? "string"
425           : width == 0 ? "numeric"
426           : "long string continuation record");
427   printf ("\tVariable label: %d\n", has_variable_label);
428   printf ("\tMissing values code: %d (%s)\n", missing_value_code,
429           (missing_value_code == 0 ? "no missing values"
430            : missing_value_code == 1 ? "one missing value"
431            : missing_value_code == 2 ? "two missing values"
432            : missing_value_code == 3 ? "three missing values"
433            : missing_value_code == -2 ? "one missing value range"
434            : missing_value_code == -3 ? "one missing value, one range"
435            : "bad value"));
436   printf ("\tPrint format: %06x (%s%d.%d)\n",
437           print_format, format_name (print_format),
438           (print_format >> 8) & 0xff, print_format & 0xff);
439   printf ("\tWrite format: %06x (%s%d.%d)\n",
440           write_format, format_name (write_format),
441           (write_format >> 8) & 0xff, write_format & 0xff);
442   printf ("\tName: %s\n", name);
443
444   /* Get variable label, if any. */
445   if (has_variable_label != 0 && has_variable_label != 1)
446     sys_error (r, "Variable label indicator field is not 0 or 1.");
447   if (has_variable_label == 1)
448     {
449       long long int offset = ftello (r->file);
450       enum { MAX_LABEL_LEN = 65536 };
451
452       size_t len = read_int (r);
453       size_t read_len = MIN (MAX_LABEL_LEN, len);
454       char *label = xmalloc (read_len + 1);
455       read_string (r, label, read_len + 1);
456       printf("\t%08llx Variable label: \"%s\"\n", offset, label);
457       free (label);
458
459       /* Skip label padding up to multiple of 4 bytes. */
460       skip_bytes (r, ROUND_UP (len, 4) - len);
461     }
462
463   /* Set missing values. */
464   if (missing_value_code != 0)
465     {
466       int i;
467
468       printf ("\t%08llx Missing values:", (long long int) ftello (r->file));
469       if (!width)
470         {
471           if (missing_value_code < -3 || missing_value_code > 3
472               || missing_value_code == -1)
473             sys_error (r, "Numeric missing value indicator field is not "
474                        "-3, -2, 0, 1, 2, or 3.");
475           if (missing_value_code < 0)
476             {
477               double low = read_float (r);
478               double high = read_float (r);
479               printf (" %.*g...%.*g", DBL_DIG + 1, low, DBL_DIG + 1, high);
480               missing_value_code = -missing_value_code - 2;
481             }
482           for (i = 0; i < missing_value_code; i++)
483             printf (" %.*g", DBL_DIG + 1, read_float (r));
484         }
485       else if (width > 0)
486         {
487           if (missing_value_code < 1 || missing_value_code > 3)
488             sys_error (r, "String missing value indicator field is not "
489                        "0, 1, 2, or 3.");
490           for (i = 0; i < missing_value_code; i++)
491             {
492               char string[9];
493               read_string (r, string, sizeof string);
494               printf (" \"%s\"", string);
495             }
496         }
497       putchar ('\n');
498     }
499 }
500
501 static void
502 print_untyped_value (struct sfm_reader *r, char raw_value[8])
503 {
504   int n_printable;
505   double value;
506
507   value = float_get_double (r->float_format, raw_value);
508   for (n_printable = 0; n_printable < 8; n_printable++)
509     if (!isprint (raw_value[n_printable]))
510       break;
511
512   printf ("%.*g/\"%.*s\"", DBL_DIG + 1, value, n_printable, raw_value);
513 }
514
515 /* Reads value labels from sysfile R and inserts them into the
516    associated dictionary. */
517 static void
518 read_value_label_record (struct sfm_reader *r)
519 {
520   int label_cnt, var_cnt;
521   int i;
522
523   printf ("%08llx: value labels record\n", (long long int) ftello (r->file));
524
525   /* Read number of labels. */
526   label_cnt = read_int (r);
527   for (i = 0; i < label_cnt; i++)
528     {
529       char raw_value[8];
530       unsigned char label_len;
531       size_t padded_len;
532       char label[256];
533
534       read_bytes (r, raw_value, sizeof raw_value);
535
536       /* Read label length. */
537       read_bytes (r, &label_len, sizeof label_len);
538       padded_len = ROUND_UP (label_len + 1, 8);
539
540       /* Read label, padding. */
541       read_bytes (r, label, padded_len - 1);
542       label[label_len] = 0;
543
544       printf ("\t");
545       print_untyped_value (r, raw_value);
546       printf (": \"%s\"\n", label);
547     }
548
549   /* Now, read the type 4 record that has the list of variables
550      to which the value labels are to be applied. */
551
552   /* Read record type of type 4 record. */
553   if (read_int (r) != 4)
554     sys_error (r, "Variable index record (type 4) does not immediately "
555                "follow value label record (type 3) as it should.");
556
557   /* Read number of variables associated with value label from type 4
558      record. */
559   printf ("\t%08llx: apply to variables", (long long int) ftello (r->file));
560   var_cnt = read_int (r);
561   for (i = 0; i < var_cnt; i++)
562     printf (" #%d", read_int (r));
563   putchar ('\n');
564 }
565
566 static void
567 read_document_record (struct sfm_reader *r)
568 {
569   int n_lines;
570   int i;
571
572   printf ("%08llx: document record\n", (long long int) ftello (r->file));
573   n_lines = read_int (r);
574   printf ("\t%d lines of documents\n", n_lines);
575
576   for (i = 0; i < n_lines; i++)
577     {
578       char line[81];
579       printf ("\t%08llx: ", (long long int) ftello (r->file));
580       read_string (r, line, sizeof line);
581       trim_spaces (line);
582       printf ("line %d: \"%s\"\n", i, line);
583     }
584 }
585
586 static void
587 read_extension_record (struct sfm_reader *r)
588 {
589   long long int offset = ftello (r->file);
590   int subtype = read_int (r);
591   size_t size = read_int (r);
592   size_t count = read_int (r);
593   size_t bytes = size * count;
594
595   printf ("%08llx: Record 7, subtype %d, size=%zu, count=%zu\n",
596           offset, subtype, size, count);
597
598   switch (subtype)
599     {
600     case 3:
601       read_machine_integer_info (r, size, count);
602       return;
603
604     case 4:
605       read_machine_float_info (r, size, count);
606       return;
607
608     case 6:
609       /* DATE variable information.  We don't use it yet, but we
610          should. */
611       break;
612
613     case 7:
614     case 19:
615       read_mrsets (r, size, count);
616       return;
617
618     case 10:
619       read_extra_product_info (r, size, count);
620       return;
621
622     case 11:
623       read_display_parameters (r, size, count);
624       return;
625
626     case 13:
627       read_long_var_name_map (r, size, count);
628       return;
629
630     case 14:
631       read_long_string_map (r, size, count);
632       return;
633
634     case 16:
635       read_ncases64 (r, size, count);
636       return;
637
638     case 17:
639       read_datafile_attributes (r, size, count);
640       return;
641
642     case 18:
643       read_variable_attributes (r, size, count);
644       return;
645
646     case 20:
647       read_character_encoding (r, size, count);
648       return;
649
650     case 21:
651       read_long_string_value_labels (r, size, count);
652       return;
653
654     case 22:
655       read_long_string_missing_values (r, size, count);
656       return;
657
658     default:
659       sys_warn (r, "Unrecognized record type 7, subtype %d.", subtype);
660       read_unknown_extension (r, size, count);
661       return;
662     }
663
664   skip_bytes (r, bytes);
665 }
666
667 static void
668 read_machine_integer_info (struct sfm_reader *r, size_t size, size_t count)
669 {
670   long long int offset = ftello (r->file);
671   int version_major = read_int (r);
672   int version_minor = read_int (r);
673   int version_revision = read_int (r);
674   int machine_code = read_int (r);
675   int float_representation = read_int (r);
676   int compression_code = read_int (r);
677   int integer_representation = read_int (r);
678   int character_code = read_int (r);
679
680   printf ("%08llx: machine integer info\n", offset);
681   if (size != 4 || count != 8)
682     sys_error (r, "Bad size (%zu) or count (%zu) field on record type 7, "
683                "subtype 3.", size, count);
684
685   printf ("\tVersion: %d.%d.%d\n",
686           version_major, version_minor, version_revision);
687   printf ("\tMachine code: %d\n", machine_code);
688   printf ("\tFloating point representation: %d (%s)\n",
689           float_representation,
690           float_representation == 1 ? "IEEE 754"
691           : float_representation == 2 ? "IBM 370"
692           : float_representation == 3 ? "DEC VAX"
693           : "unknown");
694   printf ("\tCompression code: %d\n", compression_code);
695   printf ("\tEndianness: %d (%s)\n", integer_representation,
696           integer_representation == 1 ? "big"
697           : integer_representation == 2 ? "little" : "unknown");
698   printf ("\tCharacter code: %d\n", character_code);
699 }
700
701 /* Read record type 7, subtype 4. */
702 static void
703 read_machine_float_info (struct sfm_reader *r, size_t size, size_t count)
704 {
705   long long int offset = ftello (r->file);
706   double sysmis = read_float (r);
707   double highest = read_float (r);
708   double lowest = read_float (r);
709
710   printf ("%08llx: machine float info\n", offset);
711   if (size != 8 || count != 3)
712     sys_error (r, "Bad size (%zu) or count (%zu) on extension 4.",
713                size, count);
714
715   printf ("\tsysmis: %.*g (%a)\n", DBL_DIG + 1, sysmis, sysmis);
716   if (sysmis != SYSMIS)
717     sys_warn (r, "File specifies unexpected value %.*g (%a) as %s.",
718               DBL_DIG + 1, sysmis, sysmis, "SYSMIS");
719
720   printf ("\thighest: %.*g (%a)\n", DBL_DIG + 1, highest, highest);
721   if (highest != HIGHEST)
722     sys_warn (r, "File specifies unexpected value %.*g (%a) as %s.",
723               DBL_DIG + 1, highest, highest, "HIGHEST");
724
725   printf ("\tlowest: %.*g (%a)\n", DBL_DIG + 1, lowest, lowest);
726   if (lowest != LOWEST && lowest != SYSMIS)
727     sys_warn (r, "File specifies unexpected value %.*g (%a) as %s.",
728               DBL_DIG + 1, lowest, lowest, "LOWEST");
729 }
730
731 static void
732 read_extra_product_info (struct sfm_reader *r,
733                          size_t size, size_t count)
734 {
735   struct text_record *text;
736   const char *s;
737
738   printf ("%08llx: extra product info\n", (long long int) ftello (r->file));
739   text = open_text_record (r, size, count);
740   s = text_get_all (text);
741   print_string (s, strlen (s));
742   close_text_record (text);
743 }
744
745 /* Read record type 7, subtype 7. */
746 static void
747 read_mrsets (struct sfm_reader *r, size_t size, size_t count)
748 {
749   struct text_record *text;
750
751   printf ("%08llx: multiple response sets\n",
752           (long long int) ftello (r->file));
753   text = open_text_record (r, size, count);
754   for (;;)
755     {
756       const char *name;
757       enum { MRSET_MC, MRSET_MD } type;
758       bool cat_label_from_counted_values = false;
759       bool label_from_var_label = false;
760       const char *counted;
761       const char *label;
762       const char *variables;
763
764       while (text_match (text, '\n'))
765         continue;
766
767       name = text_tokenize (text, '=');
768       if (name == NULL)
769         break;
770
771       if (text_match (text, 'C'))
772         {
773           type = MRSET_MC;
774           counted = NULL;
775           if (!text_match (text, ' '))
776             {
777               sys_warn (r, "missing space following 'C' at offset %zu "
778                         "in mrsets record", text_pos (text));
779               break;
780             }
781         }
782       else if (text_match (text, 'D'))
783         {
784           type = MRSET_MD;
785         }
786       else if (text_match (text, 'E'))
787         {
788           char *number;
789
790           type = MRSET_MD;
791           cat_label_from_counted_values = true;
792
793           if (!text_match (text, ' '))
794             {
795               sys_warn (r, "Missing space following `%c' at offset %zu "
796                         "in MRSETS record", 'E', text_pos (text));
797               break;
798             }
799
800           number = text_tokenize (text, ' ');
801           if (!number)
802             sys_warn (r, "Missing label source value "
803                       "following `E' at offset %zu in MRSETS record",
804                       text_pos (text));
805           else if (!strcmp (number, "11"))
806             label_from_var_label = true;
807           else if (strcmp (number, "1"))
808             sys_warn (r, "Unexpected label source value `%s' "
809                       "following `E' at offset %zu in MRSETS record",
810                       number, text_pos (text));
811
812         }
813       else
814         {
815           sys_warn (r, "missing `C', `D', or `E' at offset %zu "
816                     "in mrsets record", text_pos (text));
817           break;
818         }
819
820       if (type == MRSET_MD)
821         {
822           counted = text_parse_counted_string (text);
823           if (counted == NULL)
824             break;
825         }
826
827       label = text_parse_counted_string (text);
828       if (label == NULL)
829         break;
830
831       variables = text_tokenize (text, '\n');
832
833       printf ("\t\"%s\": multiple %s set",
834               name, type == MRSET_MC ? "category" : "dichotomy");
835       if (counted != NULL)
836         printf (", counted value \"%s\"", counted);
837       if (cat_label_from_counted_values)
838         printf (", category labels from counted values");
839       if (label[0] != '\0')
840         printf (", label \"%s\"", label);
841       if (label_from_var_label)
842         printf (", label from variable label");
843       if (variables != NULL)
844         printf(", variables \"%s\"\n", variables);
845       else
846         printf(", no variables\n");
847     }
848   close_text_record (text);
849 }
850
851 /* Read record type 7, subtype 11. */
852 static void
853 read_display_parameters (struct sfm_reader *r, size_t size, size_t count)
854 {
855   size_t n_vars;
856   bool includes_width;
857   size_t i;
858
859   printf ("%08llx: variable display parameters\n",
860           (long long int) ftello (r->file));
861   if (size != 4)
862     {
863       sys_warn (r, "Bad size %zu on extension 11.", size);
864       skip_bytes (r, size * count);
865       return;
866     }
867
868   n_vars = r->n_variables;
869   if (count == 3 * n_vars)
870     includes_width = true;
871   else if (count == 2 * n_vars)
872     includes_width = false;
873   else
874     {
875       sys_warn (r, "Extension 11 has bad count %zu (for %zu variables.",
876                 count, n_vars);
877       skip_bytes (r, size * count);
878       return;
879     }
880
881   for (i = 0; i < n_vars; ++i)
882     {
883       int measure = read_int (r);
884       int width = includes_width ? read_int (r) : 0;
885       int align = read_int (r);
886
887       printf ("\tVar #%zu: measure=%d (%s)", i, measure,
888               (measure == 1 ? "nominal"
889                : measure == 2 ? "ordinal"
890                : measure == 3 ? "scale"
891                : "invalid"));
892       if (includes_width)
893         printf (", width=%d", width);
894       printf (", align=%d (%s)\n", align,
895               (align == 0 ? "left"
896                : align == 1 ? "right"
897                : align == 2 ? "centre"
898                : "invalid"));
899     }
900 }
901
902 /* Reads record type 7, subtype 13, which gives the long name
903    that corresponds to each short name.  */
904 static void
905 read_long_var_name_map (struct sfm_reader *r, size_t size, size_t count)
906 {
907   struct text_record *text;
908   char *var;
909   char *long_name;
910
911   printf ("%08llx: long variable names (short => long)\n",
912           (long long int) ftello (r->file));
913   text = open_text_record (r, size, count);
914   while (read_variable_to_value_pair (text, &var, &long_name))
915     printf ("\t%s => %s\n", var, long_name);
916   close_text_record (text);
917 }
918
919 /* Reads record type 7, subtype 14, which gives the real length
920    of each very long string.  Rearranges DICT accordingly. */
921 static void
922 read_long_string_map (struct sfm_reader *r, size_t size, size_t count)
923 {
924   struct text_record *text;
925   char *var;
926   char *length_s;
927
928   printf ("%08llx: very long strings (variable => length)\n",
929           (long long int) ftello (r->file));
930   text = open_text_record (r, size, count);
931   while (read_variable_to_value_pair (text, &var, &length_s))
932     printf ("\t%s => %d\n", var, atoi (length_s));
933   close_text_record (text);
934 }
935
936 static bool
937 read_attributes (struct sfm_reader *r, struct text_record *text,
938                  const char *variable)
939 {
940   const char *key;
941   int index;
942
943   for (;;)
944     {
945       key = text_tokenize (text, '(');
946       if (key == NULL)
947         return true;
948
949       for (index = 1; ; index++)
950         {
951           /* Parse the value. */
952           const char *value = text_tokenize (text, '\n');
953           if (value == NULL)
954             {
955               sys_warn (r, "%s: Error parsing attribute value %s[%d]",
956                         variable, key, index);
957               return false;
958             }
959           if (strlen (value) < 2
960               || value[0] != '\'' || value[strlen (value) - 1] != '\'')
961             sys_warn (r, "%s: Attribute value %s[%d] is not quoted: %s",
962                       variable, key, index, value);
963           else
964             printf ("\t%s: %s[%d] = \"%.*s\"\n",
965                     variable, key, index, (int) strlen (value) - 2, value + 1);
966
967           /* Was this the last value for this attribute? */
968           if (text_match (text, ')'))
969             break;
970         }
971
972       if (text_match (text, '/'))
973         return true;
974     }
975 }
976
977 /* Read extended number of cases record. */
978 static void
979 read_ncases64 (struct sfm_reader *r, size_t size, size_t count)
980 {
981   int64_t unknown, ncases64;
982
983   if (size != 8)
984     {
985       sys_warn (r, "Bad size %zu for extended number of cases.", size);
986       skip_bytes (r, size * count);
987       return;
988     }
989   if (count != 2)
990     {
991       sys_warn (r, "Bad count %zu for extended number of cases.", size);
992       skip_bytes (r, size * count);
993       return;
994     }
995   unknown = read_int64 (r);
996   ncases64 = read_int64 (r);
997   printf ("%08llx: extended number of cases: "
998           "unknown=%"PRId64", ncases64=%"PRId64"\n",
999           (long long int) ftello (r->file), unknown, ncases64);
1000 }
1001
1002 static void
1003 read_datafile_attributes (struct sfm_reader *r, size_t size, size_t count)
1004 {
1005   struct text_record *text;
1006
1007   printf ("%08llx: datafile attributes\n", (long long int) ftello (r->file));
1008   text = open_text_record (r, size, count);
1009   read_attributes (r, text, "datafile");
1010   close_text_record (text);
1011 }
1012
1013 static void
1014 read_character_encoding (struct sfm_reader *r, size_t size, size_t count)
1015 {
1016   long long int posn =  ftello (r->file);
1017   char *encoding = xcalloc (size, count + 1);
1018   read_string (r, encoding, count + 1);
1019
1020   printf ("%08llx: Character Encoding: %s\n", posn, encoding);
1021 }
1022
1023 static void
1024 read_long_string_value_labels (struct sfm_reader *r, size_t size, size_t count)
1025 {
1026   long long int start = ftello (r->file);
1027
1028   printf ("%08llx: long string value labels\n", start);
1029   while (ftello (r->file) - start < size * count)
1030     {
1031       long long posn = ftello (r->file);
1032       char var_name[ID_MAX_LEN + 1];
1033       int var_name_len;
1034       int n_values;
1035       int width;
1036       int i;
1037
1038       /* Read variable name. */
1039       var_name_len = read_int (r);
1040       if (var_name_len > ID_MAX_LEN)
1041         sys_error (r, "Variable name length in long string value label "
1042                    "record (%d) exceeds %d-byte limit.",
1043                    var_name_len, ID_MAX_LEN);
1044       read_string (r, var_name, var_name_len + 1);
1045
1046       /* Read width, number of values. */
1047       width = read_int (r);
1048       n_values = read_int (r);
1049
1050       printf ("\t%08llx: %s, width %d, %d values\n",
1051               posn, var_name, width, n_values);
1052
1053       /* Read values. */
1054       for (i = 0; i < n_values; i++)
1055         {
1056           char *value;
1057           int value_length;
1058
1059           char *label;
1060           int label_length;
1061
1062           posn = ftello (r->file);
1063
1064           /* Read value. */
1065           value_length = read_int (r);
1066           value = xmalloc (value_length + 1);
1067           read_string (r, value, value_length + 1);
1068
1069           /* Read label. */
1070           label_length = read_int (r);
1071           label = xmalloc (label_length + 1);
1072           read_string (r, label, label_length + 1);
1073
1074           printf ("\t\t%08llx: \"%s\" (%d bytes) => \"%s\" (%d bytes)\n",
1075                   posn, value, value_length, label, label_length);
1076
1077           free (value);
1078           free (label);
1079         }
1080     }
1081 }
1082
1083 static void
1084 read_long_string_missing_values (struct sfm_reader *r,
1085                                  size_t size, size_t count)
1086 {
1087   long long int start = ftello (r->file);
1088
1089   printf ("%08llx: long string missing values\n", start);
1090   while (ftello (r->file) - start < size * count)
1091     {
1092       long long posn = ftello (r->file);
1093       char var_name[ID_MAX_LEN + 1];
1094       uint8_t n_missing_values;
1095       int var_name_len;
1096       int i;
1097
1098       /* Read variable name. */
1099       var_name_len = read_int (r);
1100       if (var_name_len > ID_MAX_LEN)
1101         sys_error (r, "Variable name length in long string value label "
1102                    "record (%d) exceeds %d-byte limit.",
1103                    var_name_len, ID_MAX_LEN);
1104       read_string (r, var_name, var_name_len + 1);
1105
1106       /* Read number of values. */
1107       read_bytes (r, &n_missing_values, 1);
1108
1109       printf ("\t%08llx: %s, %d missing values:",
1110               posn, var_name, n_missing_values);
1111
1112       /* Read values. */
1113       for (i = 0; i < n_missing_values; i++)
1114         {
1115           char *value;
1116           int value_length;
1117
1118           posn = ftello (r->file);
1119
1120           /* Read value. */
1121           value_length = read_int (r);
1122           value = xmalloc (value_length + 1);
1123           read_string (r, value, value_length + 1);
1124
1125           printf (" \"%s\"", value);
1126
1127           free (value);
1128         }
1129       printf ("\n");
1130     }
1131 }
1132
1133 static void
1134 hex_dump (size_t offset, const void *buffer_, size_t buffer_size)
1135 {
1136   const uint8_t *buffer = buffer_;
1137
1138   while (buffer_size > 0)
1139     {
1140       size_t n = MIN (buffer_size, 16);
1141       size_t i;
1142
1143       printf ("%04zx", offset);
1144       for (i = 0; i < 16; i++)
1145         {
1146           if (i < n)
1147             printf ("%c%02x", i == 8 ? '-' : ' ', buffer[i]);
1148           else
1149             printf ("   ");
1150         }
1151
1152       printf (" |");
1153       for (i = 0; i < 16; i++)
1154         {
1155           unsigned char c = i < n ? buffer[i] : ' ';
1156           putchar (isprint (c) ? c : '.');
1157         }
1158       printf ("|\n");
1159
1160       offset += n;
1161       buffer += n;
1162       buffer_size -= n;
1163     }
1164 }
1165
1166 /* Reads and prints any type 7 record that we don't understand. */
1167 static void
1168 read_unknown_extension (struct sfm_reader *r, size_t size, size_t count)
1169 {
1170   unsigned char *buffer;
1171   size_t i;
1172
1173   if (size == 0 || count > 65536 / size)
1174     skip_bytes (r, size * count);
1175   else if (size != 1)
1176     {
1177       buffer = xmalloc (size);
1178       for (i = 0; i < count; i++)
1179         {
1180           read_bytes (r, buffer, size);
1181           hex_dump (i * size, buffer, size);
1182         }
1183       free (buffer);
1184     }
1185   else
1186     {
1187       buffer = xmalloc (count);
1188       read_bytes (r, buffer, count);
1189       print_string (CHAR_CAST (char *, buffer), count);
1190       free (buffer);
1191     }
1192 }
1193
1194 static void
1195 read_variable_attributes (struct sfm_reader *r, size_t size, size_t count)
1196 {
1197   struct text_record *text;
1198
1199   printf ("%08llx: variable attributes\n", (long long int) ftello (r->file));
1200   text = open_text_record (r, size, count);
1201   for (;;)
1202     {
1203       const char *variable = text_tokenize (text, ':');
1204       if (variable == NULL || !read_attributes (r, text, variable))
1205         break;
1206     }
1207   close_text_record (text);
1208 }
1209
1210 static void
1211 read_simple_compressed_data (struct sfm_reader *r, int max_cases)
1212 {
1213   enum { N_OPCODES = 8 };
1214   uint8_t opcodes[N_OPCODES];
1215   long long int opcode_ofs;
1216   int opcode_idx;
1217   int case_num;
1218   int i;
1219
1220   read_int (r);
1221   printf ("\n%08llx: compressed data:\n", (long long int) ftello (r->file));
1222
1223   opcode_idx = N_OPCODES;
1224   opcode_ofs = 0;
1225   case_num = 0;
1226   for (case_num = 0; case_num < max_cases; case_num++)
1227     {
1228       printf ("%08llx: case %d's uncompressible data begins\n",
1229               (long long int) ftello (r->file), case_num);
1230       for (i = 0; i < r->n_var_widths; )
1231         {
1232           int width = r->var_widths[i];
1233           char raw_value[8];
1234           int opcode;
1235
1236           if (opcode_idx >= N_OPCODES)
1237             {
1238               opcode_ofs = ftello (r->file);
1239               if (i == 0)
1240                 {
1241                   if (!try_read_bytes (r, opcodes, 8))
1242                     return;
1243                 }
1244               else
1245                 read_bytes (r, opcodes, 8);
1246               opcode_idx = 0;
1247             }
1248           opcode = opcodes[opcode_idx];
1249           printf ("%08llx: variable %d: opcode %d: ",
1250                   opcode_ofs + opcode_idx, i, opcode);
1251
1252           switch (opcode)
1253             {
1254             default:
1255               printf ("%.*g", DBL_DIG + 1, opcode - r->bias);
1256               if (width != 0)
1257                 printf (", but this is a string variable (width=%d)", width);
1258               printf ("\n");
1259               i++;
1260               break;
1261
1262             case 0:
1263               printf ("ignored padding\n");
1264               break;
1265
1266             case 252:
1267               printf ("end of data\n");
1268               return;
1269
1270             case 253:
1271               read_bytes (r, raw_value, 8);
1272               printf ("uncompressible data: ");
1273               print_untyped_value (r, raw_value);
1274               printf ("\n");
1275               i++;
1276               break;
1277
1278             case 254:
1279               printf ("spaces");
1280               if (width == 0)
1281                 printf (", but this is a numeric variable");
1282               printf ("\n");
1283               i++;
1284               break;
1285
1286             case 255:
1287               printf ("SYSMIS");
1288               if (width != 0)
1289                 printf (", but this is a string variable (width=%d)", width);
1290               printf ("\n");
1291               i++;
1292               break;
1293             }
1294
1295           opcode_idx++;
1296         }
1297     }
1298 }
1299
1300 static void
1301 read_zlib_compressed_data (struct sfm_reader *r)
1302 {
1303   long long int ofs;
1304   long long int this_ofs, next_ofs, next_len;
1305   long long int bias, zero;
1306   long long int expected_uncmp_ofs, expected_cmp_ofs;
1307   unsigned int block_size, n_blocks;
1308   unsigned int i;
1309
1310   read_int (r);
1311   ofs = ftello (r->file);
1312   printf ("\n%08llx: ZLIB compressed data header:\n", ofs);
1313
1314   this_ofs = read_int64 (r);
1315   next_ofs = read_int64 (r);
1316   next_len = read_int64 (r);
1317
1318   printf ("\tzheader_ofs: 0x%llx\n", this_ofs);
1319   if (this_ofs != ofs)
1320     printf ("\t\t(Expected 0x%llx.)\n", ofs);
1321   printf ("\tztrailer_ofs: 0x%llx\n", next_ofs);
1322   printf ("\tztrailer_len: %lld\n", next_len);
1323   if (next_len < 24 || next_len % 24)
1324     printf ("\t\t(Trailer length is not a positive multiple of 24.)\n");
1325
1326   printf ("\n%08llx: 0x%llx bytes of ZLIB compressed data\n",
1327           ofs + 8 * 3, next_ofs - (ofs + 8 * 3));
1328
1329   skip_bytes (r, next_ofs - (ofs + 8 * 3));
1330
1331   printf ("\n%08llx: ZLIB trailer fixed header:\n", next_ofs);
1332   bias = read_int64 (r);
1333   zero = read_int64 (r);
1334   block_size = read_int (r);
1335   n_blocks = read_int (r);
1336   printf ("\tbias: %lld\n", bias);
1337   printf ("\tzero: 0x%llx\n", zero);
1338   if (zero != 0)
1339     printf ("\t\t(Expected 0.)\n");
1340   printf ("\tblock_size: 0x%x\n", block_size);
1341   if (block_size != 0x3ff000)
1342     printf ("\t\t(Expected 0x3ff000.)\n");
1343   printf ("\tn_blocks: %u\n", n_blocks);
1344   if (n_blocks != next_len / 24 - 1)
1345     printf ("\t\t(Expected %llu.)\n", next_len / 24 - 1);
1346
1347   expected_uncmp_ofs = ofs;
1348   expected_cmp_ofs = ofs + 24;
1349   for (i = 0; i < n_blocks; i++)
1350     {
1351       long long int blockinfo_ofs = ftello (r->file);
1352       unsigned long long int uncompressed_ofs = read_int64 (r);
1353       unsigned long long int compressed_ofs = read_int64 (r);
1354       unsigned int uncompressed_size = read_int (r);
1355       unsigned int compressed_size = read_int (r);
1356
1357       printf ("\n%08llx: ZLIB block descriptor %d\n", blockinfo_ofs, i + 1);
1358
1359       printf ("\tuncompressed_ofs: 0x%llx\n", uncompressed_ofs);
1360       if (uncompressed_ofs != expected_uncmp_ofs)
1361         printf ("\t\t(Expected 0x%llx.)\n", ofs);
1362
1363       printf ("\tcompressed_ofs: 0x%llx\n", compressed_ofs);
1364       if (compressed_ofs != expected_cmp_ofs)
1365         printf ("\t\t(Expected 0x%llx.)\n", ofs + 24);
1366
1367       printf ("\tuncompressed_size: 0x%x\n", uncompressed_size);
1368       if (i < n_blocks - 1 && uncompressed_size != block_size)
1369         printf ("\t\t(Expected 0x%x.)\n", block_size);
1370
1371       printf ("\tcompressed_size: 0x%x\n", compressed_size);
1372       if (i == n_blocks - 1 && compressed_ofs + compressed_size != next_ofs)
1373         printf ("\t\t(This was expected to be 0x%llx.)\n",
1374                 next_ofs - compressed_size);
1375
1376       expected_uncmp_ofs += uncompressed_size;
1377       expected_cmp_ofs += compressed_size;
1378     }
1379 }
1380 \f
1381 /* Helpers for reading records that consist of structured text
1382    strings. */
1383
1384 /* State. */
1385 struct text_record
1386   {
1387     struct sfm_reader *reader;  /* Reader. */
1388     char *buffer;               /* Record contents. */
1389     size_t size;                /* Size of buffer. */
1390     size_t pos;                 /* Current position in buffer. */
1391   };
1392
1393 /* Reads SIZE * COUNT bytes into a text record for R,
1394    and returns the new text record. */
1395 static struct text_record *
1396 open_text_record (struct sfm_reader *r, size_t size, size_t count)
1397 {
1398   struct text_record *text = xmalloc (sizeof *text);
1399
1400   if (size_overflow_p (xsum (1, xtimes (size, count))))
1401     sys_error (r, "Extension record too large.");
1402
1403   size_t n_bytes = size * count;
1404   char *buffer = xmalloc (n_bytes + 1);
1405   read_bytes (r, buffer, n_bytes);
1406   buffer[size] = '\0';
1407   text->reader = r;
1408   text->buffer = buffer;
1409   text->size = n_bytes;
1410   text->pos = 0;
1411   return text;
1412 }
1413
1414 /* Closes TEXT and frees its storage.
1415    Not really needed, because the pool will free the text record anyway,
1416    but can be used to free it earlier. */
1417 static void
1418 close_text_record (struct text_record *text)
1419 {
1420   free (text->buffer);
1421   free (text);
1422 }
1423
1424 static char *
1425 text_tokenize (struct text_record *text, int delimiter)
1426 {
1427   size_t start = text->pos;
1428   while (text->pos < text->size
1429          && text->buffer[text->pos] != delimiter
1430          && text->buffer[text->pos] != '\0')
1431     text->pos++;
1432   if (start == text->pos)
1433     return NULL;
1434   text->buffer[text->pos++] = '\0';
1435   return &text->buffer[start];
1436 }
1437
1438 static bool
1439 text_match (struct text_record *text, int c)
1440 {
1441   if (text->pos < text->size && text->buffer[text->pos] == c)
1442     {
1443       text->pos++;
1444       return true;
1445     }
1446   else
1447     return false;
1448 }
1449
1450 /* Reads a integer value expressed in decimal, then a space, then a string that
1451    consists of exactly as many bytes as specified by the integer, then a space,
1452    from TEXT.  Returns the string, null-terminated, as a subset of TEXT's
1453    buffer (so the caller should not free the string). */
1454 static const char *
1455 text_parse_counted_string (struct text_record *text)
1456 {
1457   size_t start;
1458   size_t n;
1459   char *s;
1460
1461   start = text->pos;
1462   n = 0;
1463   while (isdigit ((unsigned char) text->buffer[text->pos]))
1464     n = (n * 10) + (text->buffer[text->pos++] - '0');
1465   if (start == text->pos)
1466     {
1467       sys_error (text->reader, "expecting digit at offset %zu in record",
1468                  text->pos);
1469       return NULL;
1470     }
1471
1472   if (!text_match (text, ' '))
1473     {
1474       sys_error (text->reader, "expecting space at offset %zu in record",
1475                  text->pos);
1476       return NULL;
1477     }
1478
1479   if (text->pos + n > text->size)
1480     {
1481       sys_error (text->reader, "%zu-byte string starting at offset %zu "
1482                  "exceeds record length %zu", n, text->pos, text->size);
1483       return NULL;
1484     }
1485
1486   s = &text->buffer[text->pos];
1487   if (s[n] != ' ')
1488     {
1489       sys_error (text->reader, "expecting space at offset %zu following "
1490                  "%zu-byte string", text->pos + n, n);
1491       return NULL;
1492     }
1493   s[n] = '\0';
1494   text->pos += n + 1;
1495   return s;
1496 }
1497
1498 /* Reads a variable=value pair from TEXT.
1499    Looks up the variable in DICT and stores it into *VAR.
1500    Stores a null-terminated value into *VALUE. */
1501 static bool
1502 read_variable_to_value_pair (struct text_record *text,
1503                              char **key, char **value)
1504 {
1505   *key = text_tokenize (text, '=');
1506   *value = text_tokenize (text, '\t');
1507   if (!*key || !*value)
1508     return false;
1509
1510   while (text->pos < text->size
1511          && (text->buffer[text->pos] == '\t'
1512              || text->buffer[text->pos] == '\0'))
1513     text->pos++;
1514   return true;
1515 }
1516
1517 /* Returns the current byte offset inside the TEXT's string. */
1518 static size_t
1519 text_pos (const struct text_record *text)
1520 {
1521   return text->pos;
1522 }
1523
1524 static const char *
1525 text_get_all (const struct text_record *text)
1526 {
1527   return text->buffer;
1528 }
1529 \f
1530 static void
1531 usage (void)
1532 {
1533   printf ("\
1534 %s, a utility for dissecting system files.\n\
1535 Usage: %s [OPTION]... SYSFILE...\n\
1536 where each SYSFILE is the name of a system file.\n\
1537 \n\
1538 Options:\n\
1539   --data[=MAXCASES]   print (up to MAXCASES cases of) compressed data\n\
1540   --help              display this help and exit\n\
1541   --version           output version information and exit\n",
1542           program_name, program_name);
1543 }
1544
1545 /* Displays a corruption message. */
1546 static void
1547 sys_msg (struct sfm_reader *r, const char *format, va_list args)
1548 {
1549   printf ("\"%s\" near offset 0x%llx: ",
1550           r->file_name, (long long int) ftello (r->file));
1551   vprintf (format, args);
1552   putchar ('\n');
1553 }
1554
1555 /* Displays a warning for the current file position. */
1556 static void
1557 sys_warn (struct sfm_reader *r, const char *format, ...)
1558 {
1559   va_list args;
1560
1561   va_start (args, format);
1562   sys_msg (r, format, args);
1563   va_end (args);
1564 }
1565
1566 /* Displays an error for the current file position,
1567    marks it as in an error state,
1568    and aborts reading it using longjmp. */
1569 static void
1570 sys_error (struct sfm_reader *r, const char *format, ...)
1571 {
1572   va_list args;
1573
1574   va_start (args, format);
1575   sys_msg (r, format, args);
1576   va_end (args);
1577
1578   exit (EXIT_FAILURE);
1579 }
1580 \f
1581 /* Reads BYTE_CNT bytes into BUF.
1582    Returns true if exactly BYTE_CNT bytes are successfully read.
1583    Aborts if an I/O error or a partial read occurs.
1584    If EOF_IS_OK, then an immediate end-of-file causes false to be
1585    returned; otherwise, immediate end-of-file causes an abort
1586    too. */
1587 static inline bool
1588 read_bytes_internal (struct sfm_reader *r, bool eof_is_ok,
1589                      void *buf, size_t byte_cnt)
1590 {
1591   size_t bytes_read = fread (buf, 1, byte_cnt, r->file);
1592   if (bytes_read == byte_cnt)
1593     return true;
1594   else if (ferror (r->file))
1595     sys_error (r, "System error: %s.", strerror (errno));
1596   else if (!eof_is_ok || bytes_read != 0)
1597     sys_error (r, "Unexpected end of file.");
1598   else
1599     return false;
1600 }
1601
1602 /* Reads BYTE_CNT into BUF.
1603    Aborts upon I/O error or if end-of-file is encountered. */
1604 static void
1605 read_bytes (struct sfm_reader *r, void *buf, size_t byte_cnt)
1606 {
1607   read_bytes_internal (r, false, buf, byte_cnt);
1608 }
1609
1610 /* Reads BYTE_CNT bytes into BUF.
1611    Returns true if exactly BYTE_CNT bytes are successfully read.
1612    Returns false if an immediate end-of-file is encountered.
1613    Aborts if an I/O error or a partial read occurs. */
1614 static bool
1615 try_read_bytes (struct sfm_reader *r, void *buf, size_t byte_cnt)
1616 {
1617   return read_bytes_internal (r, true, buf, byte_cnt);
1618 }
1619
1620 /* Reads a 32-bit signed integer from R and returns its value in
1621    host format. */
1622 static int
1623 read_int (struct sfm_reader *r)
1624 {
1625   uint8_t integer[4];
1626   read_bytes (r, integer, sizeof integer);
1627   return integer_get (r->integer_format, integer, sizeof integer);
1628 }
1629
1630 /* Reads a 64-bit signed integer from R and returns its value in
1631    host format. */
1632 static int64_t
1633 read_int64 (struct sfm_reader *r)
1634 {
1635   uint8_t integer[8];
1636   read_bytes (r, integer, sizeof integer);
1637   return integer_get (r->integer_format, integer, sizeof integer);
1638 }
1639
1640 /* Reads a 64-bit floating-point number from R and returns its
1641    value in host format. */
1642 static double
1643 read_float (struct sfm_reader *r)
1644 {
1645   uint8_t number[8];
1646   read_bytes (r, number, sizeof number);
1647   return float_get_double (r->float_format, number);
1648 }
1649
1650 /* Reads exactly SIZE - 1 bytes into BUFFER
1651    and stores a null byte into BUFFER[SIZE - 1]. */
1652 static void
1653 read_string (struct sfm_reader *r, char *buffer, size_t size)
1654 {
1655   assert (size > 0);
1656   read_bytes (r, buffer, size - 1);
1657   buffer[size - 1] = '\0';
1658 }
1659
1660 /* Skips BYTES bytes forward in R. */
1661 static void
1662 skip_bytes (struct sfm_reader *r, size_t bytes)
1663 {
1664   while (bytes > 0)
1665     {
1666       char buffer[1024];
1667       size_t chunk = MIN (sizeof buffer, bytes);
1668       read_bytes (r, buffer, chunk);
1669       bytes -= chunk;
1670     }
1671 }
1672
1673 static void
1674 trim_spaces (char *s)
1675 {
1676   char *end = strchr (s, '\0');
1677   while (end > s && end[-1] == ' ')
1678     end--;
1679   *end = '\0';
1680 }
1681
1682 static void
1683 print_string (const char *s, size_t len)
1684 {
1685   if (memchr (s, 0, len) == 0)
1686     {
1687       size_t i;
1688
1689       for (i = 0; i < len; i++)
1690         {
1691           unsigned char c = s[i];
1692
1693           if (c == '\\')
1694             printf ("\\\\");
1695           else if (c == '\n' || isprint (c))
1696             putchar (c);
1697           else
1698             printf ("\\%02x", c);
1699         }
1700       putchar ('\n');
1701     }
1702   else
1703     hex_dump (0, s, len);
1704 }