897d48ffb59bf26d832c1d069f4173ed1c0ee9b5
[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 SYSMIS."), sysmis);
587   printf ("\thighest: %g\n", highest);
588   if (highest != HIGHEST)
589     sys_warn (r, _("File specifies unexpected value %g as HIGHEST."), highest);
590   printf ("\tlowest: %g\n", lowest);
591   if (lowest != LOWEST)
592     sys_warn (r, _("File specifies unexpected value %g as LOWEST."), lowest);
593 }
594
595 /* Read record type 7, subtype 11. */
596 static void
597 read_display_parameters (struct sfm_reader *r, size_t size, size_t count)
598 {
599   size_t n_vars;
600   bool includes_width;
601   size_t i;
602
603   printf ("%08lx: variable display parameters\n", ftell (r->file));
604   if (size != 4)
605     {
606       sys_warn (r, _("Bad size %zu on extension 11."), size);
607       skip_bytes (r, size * count);
608       return;
609     }
610
611   n_vars = r->n_variables;
612   if (count == 3 * n_vars)
613     includes_width = true;
614   else if (count == 2 * n_vars)
615     includes_width = false;
616   else
617     {
618       sys_warn (r, _("Extension 11 has bad count %zu (for %zu variables)."),
619                 count, n_vars);
620       skip_bytes (r, size * count);
621       return;
622     }
623
624   for (i = 0; i < n_vars; ++i)
625     {
626       int measure = read_int (r);
627       int width = includes_width ? read_int (r) : 0;
628       int align = read_int (r);
629
630       printf ("\tVar #%zu: measure=%d (%s)", i, measure,
631               (measure == 1 ? "nominal"
632                : measure == 2 ? "ordinal"
633                : measure == 3 ? "scale"
634                : "invalid"));
635       if (includes_width)
636         printf (", width=%d", width);
637       printf (", align=%d (%s)\n", align,
638               (align == 0 ? "left"
639                : align == 1 ? "right"
640                : align == 2 ? "centre"
641                : "invalid"));
642     }
643 }
644
645 /* Reads record type 7, subtype 13, which gives the long name
646    that corresponds to each short name.  */
647 static void
648 read_long_var_name_map (struct sfm_reader *r, size_t size, size_t count)
649 {
650   struct text_record *text;
651   char *var;
652   char *long_name;
653
654   printf ("%08lx: long variable names (short => long)\n", ftell (r->file));
655   text = open_text_record (r, size * count);
656   while (read_variable_to_value_pair (text, &var, &long_name))
657     printf ("\t%s => %s\n", var, long_name);
658   close_text_record (text);
659 }
660
661 /* Reads record type 7, subtype 14, which gives the real length
662    of each very long string.  Rearranges DICT accordingly. */
663 static void
664 read_long_string_map (struct sfm_reader *r, size_t size, size_t count)
665 {
666   struct text_record *text;
667   char *var;
668   char *length_s;
669
670   printf ("%08lx: very long strings (variable => length)\n", ftell (r->file));
671   text = open_text_record (r, size * count);
672   while (read_variable_to_value_pair (text, &var, &length_s))
673     printf ("\t%s => %d\n", var, atoi (length_s));
674   close_text_record (text);
675 }
676
677 static bool
678 read_attributes (struct sfm_reader *r, struct text_record *text,
679                  const char *variable)
680 {
681   const char *key;
682   int index;
683
684   for (;;) 
685     {
686       key = text_tokenize (text, '(');
687       if (key == NULL)
688         return true;
689   
690       for (index = 1; ; index++)
691         {
692           /* Parse the value. */
693           const char *value = text_tokenize (text, '\n');
694           if (value == NULL) 
695             {
696               sys_warn (r, _("%s: Error parsing attribute value %s[%d]"),
697                         variable, key, index);
698               return false;
699             }
700           if (strlen (value) < 2
701               || value[0] != '\'' || value[strlen (value) - 1] != '\'')
702             sys_warn (r, _("%s: Attribute value %s[%d] is not quoted: %s"),
703                       variable, key, index, value);
704           else
705             printf ("\t%s: %s[%d] = \"%.*s\"\n",
706                     variable, key, index, (int) strlen (value) - 2, value + 1);
707
708           /* Was this the last value for this attribute? */
709           if (text_match (text, ')'))
710             break;
711         }
712
713       if (text_match (text, '/'))
714         return true; 
715     }
716 }
717
718 static void
719 read_datafile_attributes (struct sfm_reader *r, size_t size, size_t count) 
720 {
721   struct text_record *text;
722   
723   printf ("%08lx: datafile attributes\n", ftell (r->file));
724   text = open_text_record (r, size * count);
725   read_attributes (r, text, "datafile");
726   close_text_record (text);
727 }
728
729 static void
730 read_character_encoding (struct sfm_reader *r, size_t size, size_t count)
731 {
732   const unsigned long int posn =  ftell (r->file);
733   char *encoding = xcalloc (size, count + 1);
734   read_string (r, encoding, count + 1);
735
736   printf ("%08lx: Character Encoding: %s\n", posn, encoding);
737 }
738
739 static void
740 read_long_string_value_labels (struct sfm_reader *r, size_t size, size_t count)
741 {
742   const long start = ftell (r->file);
743
744   printf ("%08lx: long string value labels\n", start);
745   while (ftell (r->file) - start < size * count)
746     {
747       long posn = ftell (r->file);
748       char var_name[VAR_NAME_LEN + 1];
749       int var_name_len;
750       int n_values;
751       int width;
752       int i;
753
754       /* Read variable name. */
755       var_name_len = read_int (r);
756       if (var_name_len > VAR_NAME_LEN)
757         sys_error (r, _("Variable name length in long string value label "
758                         "record (%d) exceeds %d-byte limit."),
759                    var_name_len, VAR_NAME_LEN);
760       read_string (r, var_name, var_name_len + 1);
761
762       /* Read width, number of values. */
763       width = read_int (r);
764       n_values = read_int (r);
765
766       printf ("\t%08lx: %s, width %d, %d values\n",
767               posn, var_name, width, n_values);
768
769       /* Read values. */
770       for (i = 0; i < n_values; i++)
771         {
772           char *value;
773           int value_length;
774
775           char *label;
776           int label_length;
777
778           posn = ftell (r->file);
779
780           /* Read value. */
781           value_length = read_int (r);
782           value = xmalloc (value_length + 1);
783           read_string (r, value, value_length + 1);
784
785           /* Read label. */
786           label_length = read_int (r);
787           label = xmalloc (label_length + 1);
788           read_string (r, label, label_length + 1);
789
790           printf ("\t\t%08lx: \"%s\" (%d bytes) => \"%s\" (%d bytes)\n",
791                   posn, value, value_length, label, label_length);
792
793           free (value);
794           free (label);
795         }
796     }
797 }
798
799 static void
800 read_variable_attributes (struct sfm_reader *r, size_t size, size_t count) 
801 {
802   struct text_record *text;
803   
804   printf ("%08lx: variable attributes\n", ftell (r->file));
805   text = open_text_record (r, size * count);
806   for (;;) 
807     {
808       const char *variable = text_tokenize (text, ':');
809       if (variable == NULL || !read_attributes (r, text, variable))
810         break; 
811     }
812   close_text_record (text);
813 }
814 \f
815 /* Helpers for reading records that consist of structured text
816    strings. */
817
818 /* State. */
819 struct text_record
820   {
821     char *buffer;               /* Record contents. */
822     size_t size;                /* Size of buffer. */
823     size_t pos;                 /* Current position in buffer. */
824   };
825
826 /* Reads SIZE bytes into a text record for R,
827    and returns the new text record. */
828 static struct text_record *
829 open_text_record (struct sfm_reader *r, size_t size)
830 {
831   struct text_record *text = xmalloc (sizeof *text);
832   char *buffer = xmalloc (size + 1);
833   read_bytes (r, buffer, size);
834   text->buffer = buffer;
835   text->size = size;
836   text->pos = 0;
837   return text;
838 }
839
840 /* Closes TEXT and frees its storage.
841    Not really needed, because the pool will free the text record anyway,
842    but can be used to free it earlier. */
843 static void
844 close_text_record (struct text_record *text)
845 {
846   free (text->buffer);
847   free (text);
848 }
849
850 static char *
851 text_tokenize (struct text_record *text, int delimiter)
852 {
853   size_t start = text->pos;
854   while (text->pos < text->size
855          && text->buffer[text->pos] != delimiter
856          && text->buffer[text->pos] != '\0')
857     text->pos++;
858   if (text->pos == text->size)
859     return NULL;
860   text->buffer[text->pos++] = '\0';
861   return &text->buffer[start];
862 }
863
864 static bool
865 text_match (struct text_record *text, int c) 
866 {
867   if (text->pos < text->size && text->buffer[text->pos] == c) 
868     {
869       text->pos++;
870       return true;
871     }
872   else
873     return false;
874 }
875
876 /* Reads a variable=value pair from TEXT.
877    Looks up the variable in DICT and stores it into *VAR.
878    Stores a null-terminated value into *VALUE. */
879 static bool
880 read_variable_to_value_pair (struct text_record *text,
881                              char **key, char **value)
882 {
883   *key = text_tokenize (text, '=');
884   *value = text_tokenize (text, '\t');
885   if (!*key || !*value)
886     return false;
887
888   while (text->pos < text->size
889          && (text->buffer[text->pos] == '\t'
890              || text->buffer[text->pos] == '\0'))
891     text->pos++;
892   return true;
893 }
894 \f
895 static void
896 usage (int exit_code)
897 {
898   printf ("usage: %s SYSFILE...\n"
899           "where each SYSFILE is the name of a system file\n",
900           program_name);
901   exit (exit_code);
902 }
903
904 /* Displays a corruption message. */
905 static void
906 sys_msg (struct sfm_reader *r, const char *format, va_list args)
907 {
908   printf ("\"%s\" near offset 0x%lx: ",
909           r->file_name, (unsigned long) ftell (r->file));
910   vprintf (format, args);
911   putchar ('\n');
912 }
913
914 /* Displays a warning for the current file position. */
915 static void
916 sys_warn (struct sfm_reader *r, const char *format, ...)
917 {
918   va_list args;
919
920   va_start (args, format);
921   sys_msg (r, format, args);
922   va_end (args);
923 }
924
925 /* Displays an error for the current file position,
926    marks it as in an error state,
927    and aborts reading it using longjmp. */
928 static void
929 sys_error (struct sfm_reader *r, const char *format, ...)
930 {
931   va_list args;
932
933   va_start (args, format);
934   sys_msg (r, format, args);
935   va_end (args);
936
937   exit (EXIT_FAILURE);
938 }
939 \f
940 /* Reads BYTE_CNT bytes into BUF.
941    Returns true if exactly BYTE_CNT bytes are successfully read.
942    Aborts if an I/O error or a partial read occurs.
943    If EOF_IS_OK, then an immediate end-of-file causes false to be
944    returned; otherwise, immediate end-of-file causes an abort
945    too. */
946 static inline bool
947 read_bytes_internal (struct sfm_reader *r, bool eof_is_ok,
948                      void *buf, size_t byte_cnt)
949 {
950   size_t bytes_read = fread (buf, 1, byte_cnt, r->file);
951   if (bytes_read == byte_cnt)
952     return true;
953   else if (ferror (r->file))
954     sys_error (r, _("System error: %s."), strerror (errno));
955   else if (!eof_is_ok || bytes_read != 0)
956     sys_error (r, _("Unexpected end of file."));
957   else
958     return false;
959 }
960
961 /* Reads BYTE_CNT into BUF.
962    Aborts upon I/O error or if end-of-file is encountered. */
963 static void
964 read_bytes (struct sfm_reader *r, void *buf, size_t byte_cnt)
965 {
966   read_bytes_internal (r, false, buf, byte_cnt);
967 }
968
969 /* Reads a 32-bit signed integer from R and returns its value in
970    host format. */
971 static int
972 read_int (struct sfm_reader *r)
973 {
974   uint8_t integer[4];
975   read_bytes (r, integer, sizeof integer);
976   return integer_get (r->integer_format, integer, sizeof integer);
977 }
978
979 /* Reads a 64-bit floating-point number from R and returns its
980    value in host format. */
981 static double
982 read_float (struct sfm_reader *r)
983 {
984   uint8_t number[8];
985   read_bytes (r, number, sizeof number);
986   return float_get_double (r->float_format, number);
987 }
988
989 /* Reads exactly SIZE - 1 bytes into BUFFER
990    and stores a null byte into BUFFER[SIZE - 1]. */
991 static void
992 read_string (struct sfm_reader *r, char *buffer, size_t size)
993 {
994   assert (size > 0);
995   read_bytes (r, buffer, size - 1);
996   buffer[size - 1] = '\0';
997 }
998
999 /* Skips BYTES bytes forward in R. */
1000 static void
1001 skip_bytes (struct sfm_reader *r, size_t bytes)
1002 {
1003   while (bytes > 0)
1004     {
1005       char buffer[1024];
1006       size_t chunk = MIN (sizeof buffer, bytes);
1007       read_bytes (r, buffer, chunk);
1008       bytes -= chunk;
1009     }
1010 }
1011
1012 static void
1013 trim_spaces (char *s)
1014 {
1015   char *end = strchr (s, '\0');
1016   while (end > s && end[-1] == ' ')
1017     end--;
1018   *end = '\0';
1019 }