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