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