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