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