DISPLAY FILE LABEL: Display in a more natural way.
[pspp-builds.git] / tests / dissect-sysfile.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2007, 2008, 2009, 2010, 2011 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 ID_MAX_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 `%c' at offset %zu "
693                              "in MRSETS record"), 'E', 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[ID_MAX_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 > ID_MAX_LEN)
937         sys_error (r, _("Variable name length in long string value label "
938                         "record (%d) exceeds %d-byte limit."),
939                    var_name_len, ID_MAX_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   opcode_ofs = 0;
1084   case_num = 0;
1085   for (case_num = 0; ; case_num++)
1086     {
1087       printf ("%08llx: case %d's uncompressible data begins\n",
1088               (long long int) ftello (r->file), case_num);
1089       for (i = 0; i < r->n_var_widths; )
1090         {
1091           int width = r->var_widths[i];
1092           char raw_value[8];
1093           int opcode;
1094
1095           if (opcode_idx >= N_OPCODES)
1096             {
1097               opcode_ofs = ftello (r->file);
1098               if (i == 0)
1099                 {
1100                   if (!try_read_bytes (r, opcodes, 8))
1101                     return;
1102                 }
1103               else
1104                 read_bytes (r, opcodes, 8);
1105               opcode_idx = 0;
1106             }
1107           opcode = opcodes[opcode_idx];
1108           printf ("%08llx: variable %d: opcode %d: ",
1109                   opcode_ofs + opcode_idx, i, opcode);
1110
1111           switch (opcode)
1112             {
1113             default:
1114               printf ("%g", opcode - r->bias);
1115               if (width != 0)
1116                 printf (", but this is a string variable (width=%d)", width);
1117               printf ("\n");
1118               i++;
1119               break;
1120
1121             case 0:
1122               printf ("ignored padding\n");
1123               break;
1124
1125             case 252:
1126               printf ("end of data\n");
1127               return;
1128
1129             case 253:
1130               read_bytes (r, raw_value, 8);
1131               printf ("uncompressible data: ");
1132               print_untyped_value (r, raw_value);
1133               printf ("\n");
1134               i++;
1135               break;
1136
1137             case 254:
1138               printf ("spaces");
1139               if (width == 0)
1140                 printf (", but this is a numeric variable");
1141               printf ("\n");
1142               i++;
1143               break;
1144
1145             case 255:
1146               printf ("SYSMIS");
1147               if (width != 0)
1148                 printf (", but this is a string variable (width=%d)", width);
1149               printf ("\n");
1150               i++;
1151               break;
1152             }
1153
1154           opcode_idx++;
1155         }
1156     }
1157 }
1158 \f
1159 /* Helpers for reading records that consist of structured text
1160    strings. */
1161
1162 /* State. */
1163 struct text_record
1164   {
1165     struct sfm_reader *reader;  /* Reader. */
1166     char *buffer;               /* Record contents. */
1167     size_t size;                /* Size of buffer. */
1168     size_t pos;                 /* Current position in buffer. */
1169   };
1170
1171 /* Reads SIZE bytes into a text record for R,
1172    and returns the new text record. */
1173 static struct text_record *
1174 open_text_record (struct sfm_reader *r, size_t size)
1175 {
1176   struct text_record *text = xmalloc (sizeof *text);
1177   char *buffer = xmalloc (size + 1);
1178   read_bytes (r, buffer, size);
1179   buffer[size] = '\0';
1180   text->reader = r;
1181   text->buffer = buffer;
1182   text->size = size;
1183   text->pos = 0;
1184   return text;
1185 }
1186
1187 /* Closes TEXT and frees its storage.
1188    Not really needed, because the pool will free the text record anyway,
1189    but can be used to free it earlier. */
1190 static void
1191 close_text_record (struct text_record *text)
1192 {
1193   free (text->buffer);
1194   free (text);
1195 }
1196
1197 static char *
1198 text_tokenize (struct text_record *text, int delimiter)
1199 {
1200   size_t start = text->pos;
1201   while (text->pos < text->size
1202          && text->buffer[text->pos] != delimiter
1203          && text->buffer[text->pos] != '\0')
1204     text->pos++;
1205   if (start == text->pos)
1206     return NULL;
1207   text->buffer[text->pos++] = '\0';
1208   return &text->buffer[start];
1209 }
1210
1211 static bool
1212 text_match (struct text_record *text, int c) 
1213 {
1214   if (text->pos < text->size && text->buffer[text->pos] == c) 
1215     {
1216       text->pos++;
1217       return true;
1218     }
1219   else
1220     return false;
1221 }
1222
1223 /* Reads a integer value expressed in decimal, then a space, then a string that
1224    consists of exactly as many bytes as specified by the integer, then a space,
1225    from TEXT.  Returns the string, null-terminated, as a subset of TEXT's
1226    buffer (so the caller should not free the string). */
1227 static const char *
1228 text_parse_counted_string (struct text_record *text)
1229 {
1230   size_t start;
1231   size_t n;
1232   char *s;
1233
1234   start = text->pos;
1235   n = 0;
1236   while (isdigit ((unsigned char) text->buffer[text->pos]))
1237     n = (n * 10) + (text->buffer[text->pos++] - '0');
1238   if (start == text->pos)
1239     {
1240       sys_error (text->reader, "expecting digit at offset %zu in record",
1241                  text->pos);
1242       return NULL;
1243     }
1244
1245   if (!text_match (text, ' '))
1246     {
1247       sys_error (text->reader, "expecting space at offset %zu in record",
1248                  text->pos);
1249       return NULL;
1250     }
1251
1252   if (text->pos + n > text->size)
1253     {
1254       sys_error (text->reader, "%zu-byte string starting at offset %zu "
1255                  "exceeds record length %zu", n, text->pos, text->size);
1256       return NULL;
1257     }
1258
1259   s = &text->buffer[text->pos];
1260   if (s[n] != ' ')
1261     {
1262       sys_error (text->reader, "expecting space at offset %zu following "
1263                  "%zu-byte string", text->pos + n, n);
1264       return NULL;
1265     }
1266   s[n] = '\0';
1267   text->pos += n + 1;
1268   return s;
1269 }
1270
1271 /* Reads a variable=value pair from TEXT.
1272    Looks up the variable in DICT and stores it into *VAR.
1273    Stores a null-terminated value into *VALUE. */
1274 static bool
1275 read_variable_to_value_pair (struct text_record *text,
1276                              char **key, char **value)
1277 {
1278   *key = text_tokenize (text, '=');
1279   *value = text_tokenize (text, '\t');
1280   if (!*key || !*value)
1281     return false;
1282
1283   while (text->pos < text->size
1284          && (text->buffer[text->pos] == '\t'
1285              || text->buffer[text->pos] == '\0'))
1286     text->pos++;
1287   return true;
1288 }
1289
1290 /* Returns the current byte offset inside the TEXT's string. */
1291 static size_t
1292 text_pos (const struct text_record *text)
1293 {
1294   return text->pos;
1295 }
1296 \f
1297 static void
1298 usage (int exit_code)
1299 {
1300   printf ("usage: %s SYSFILE...\n"
1301           "where each SYSFILE is the name of a system file\n",
1302           program_name);
1303   exit (exit_code);
1304 }
1305
1306 /* Displays a corruption message. */
1307 static void
1308 sys_msg (struct sfm_reader *r, const char *format, va_list args)
1309 {
1310   printf ("\"%s\" near offset 0x%llx: ",
1311           r->file_name, (long long int) ftello (r->file));
1312   vprintf (format, args);
1313   putchar ('\n');
1314 }
1315
1316 /* Displays a warning for the current file position. */
1317 static void
1318 sys_warn (struct sfm_reader *r, const char *format, ...)
1319 {
1320   va_list args;
1321
1322   va_start (args, format);
1323   sys_msg (r, format, args);
1324   va_end (args);
1325 }
1326
1327 /* Displays an error for the current file position,
1328    marks it as in an error state,
1329    and aborts reading it using longjmp. */
1330 static void
1331 sys_error (struct sfm_reader *r, const char *format, ...)
1332 {
1333   va_list args;
1334
1335   va_start (args, format);
1336   sys_msg (r, format, args);
1337   va_end (args);
1338
1339   exit (EXIT_FAILURE);
1340 }
1341 \f
1342 /* Reads BYTE_CNT bytes into BUF.
1343    Returns true if exactly BYTE_CNT bytes are successfully read.
1344    Aborts if an I/O error or a partial read occurs.
1345    If EOF_IS_OK, then an immediate end-of-file causes false to be
1346    returned; otherwise, immediate end-of-file causes an abort
1347    too. */
1348 static inline bool
1349 read_bytes_internal (struct sfm_reader *r, bool eof_is_ok,
1350                      void *buf, size_t byte_cnt)
1351 {
1352   size_t bytes_read = fread (buf, 1, byte_cnt, r->file);
1353   if (bytes_read == byte_cnt)
1354     return true;
1355   else if (ferror (r->file))
1356     sys_error (r, _("System error: %s."), strerror (errno));
1357   else if (!eof_is_ok || bytes_read != 0)
1358     sys_error (r, _("Unexpected end of file."));
1359   else
1360     return false;
1361 }
1362
1363 /* Reads BYTE_CNT into BUF.
1364    Aborts upon I/O error or if end-of-file is encountered. */
1365 static void
1366 read_bytes (struct sfm_reader *r, void *buf, size_t byte_cnt)
1367 {
1368   read_bytes_internal (r, false, buf, byte_cnt);
1369 }
1370
1371 /* Reads BYTE_CNT bytes into BUF.
1372    Returns true if exactly BYTE_CNT bytes are successfully read.
1373    Returns false if an immediate end-of-file is encountered.
1374    Aborts if an I/O error or a partial read occurs. */
1375 static bool
1376 try_read_bytes (struct sfm_reader *r, void *buf, size_t byte_cnt)
1377 {
1378   return read_bytes_internal (r, true, buf, byte_cnt);
1379 }
1380
1381 /* Reads a 32-bit signed integer from R and returns its value in
1382    host format. */
1383 static int
1384 read_int (struct sfm_reader *r)
1385 {
1386   uint8_t integer[4];
1387   read_bytes (r, integer, sizeof integer);
1388   return integer_get (r->integer_format, integer, sizeof integer);
1389 }
1390
1391 /* Reads a 64-bit signed integer from R and returns its value in
1392    host format. */
1393 static int64_t
1394 read_int64 (struct sfm_reader *r)
1395 {
1396   uint8_t integer[8];
1397   read_bytes (r, integer, sizeof integer);
1398   return integer_get (r->integer_format, integer, sizeof integer);
1399 }
1400
1401 /* Reads a 64-bit floating-point number from R and returns its
1402    value in host format. */
1403 static double
1404 read_float (struct sfm_reader *r)
1405 {
1406   uint8_t number[8];
1407   read_bytes (r, number, sizeof number);
1408   return float_get_double (r->float_format, number);
1409 }
1410
1411 /* Reads exactly SIZE - 1 bytes into BUFFER
1412    and stores a null byte into BUFFER[SIZE - 1]. */
1413 static void
1414 read_string (struct sfm_reader *r, char *buffer, size_t size)
1415 {
1416   assert (size > 0);
1417   read_bytes (r, buffer, size - 1);
1418   buffer[size - 1] = '\0';
1419 }
1420
1421 /* Skips BYTES bytes forward in R. */
1422 static void
1423 skip_bytes (struct sfm_reader *r, size_t bytes)
1424 {
1425   while (bytes > 0)
1426     {
1427       char buffer[1024];
1428       size_t chunk = MIN (sizeof buffer, bytes);
1429       read_bytes (r, buffer, chunk);
1430       bytes -= chunk;
1431     }
1432 }
1433
1434 static void
1435 trim_spaces (char *s)
1436 {
1437   char *end = strchr (s, '\0');
1438   while (end > s && end[-1] == ' ')
1439     end--;
1440   *end = '\0';
1441 }