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