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