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