Fix assertion for proper Huffman merge pattern: 0 == 1 modulo 1.
[pspp] / src / sfm-write.c
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
3    Written by Ben Pfaff <blp@gnu.org>.
4
5    This program is free software; you can redistribute it and/or
6    modify it under the terms of the GNU General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18    02111-1307, USA. */
19
20 #include <config.h>
21 #include "sfm-write.h"
22 #include "sfmP.h"
23 #include "error.h"
24 #include <stdlib.h>
25 #include <ctype.h>
26 #include <errno.h>
27 #include <time.h>
28 #if HAVE_UNISTD_H
29 #include <unistd.h>     /* Required by SunOS4. */
30 #endif
31 #include "alloc.h"
32 #include "case.h"
33 #include "dictionary.h"
34 #include "error.h"
35 #include "file-handle.h"
36 #include "getline.h"
37 #include "hash.h"
38 #include "magic.h"
39 #include "misc.h"
40 #include "str.h"
41 #include "value-labels.h"
42 #include "var.h"
43 #include "version.h"
44
45 #include "debug-print.h"
46
47 /* Compression bias used by PSPP.  Values between (1 -
48    COMPRESSION_BIAS) and (251 - COMPRESSION_BIAS) inclusive can be
49    compressed. */
50 #define COMPRESSION_BIAS 100
51
52 /* System file writer. */
53 struct sfm_writer
54   {
55     struct file_handle *fh;     /* File handle. */
56     FILE *file;                 /* File stream. */
57
58     int needs_translation;      /* 0=use fast path, 1=translation needed. */
59     int compress;               /* 1=compressed, 0=not compressed. */
60     int case_cnt;               /* Number of cases written so far. */
61     size_t flt64_cnt;           /* Number of flt64 elements in case. */
62
63     /* Compression buffering. */
64     flt64 *buf;                 /* Buffered data. */
65     flt64 *end;                 /* Buffer end. */
66     flt64 *ptr;                 /* Current location in buffer. */
67     unsigned char *x;           /* Location in current instruction octet. */
68     unsigned char *y;           /* End of instruction octet. */
69
70     /* Variables. */
71     struct sfm_var *vars;       /* Variables. */
72     size_t var_cnt;             /* Number of variables. */
73   };
74
75 /* A variable in a system file. */
76 struct sfm_var 
77   {
78     int width;                  /* 0=numeric, otherwise string width. */
79     int fv;                     /* Index into case. */
80     size_t flt64_cnt;           /* Number of flt64 elements. */
81   };
82
83 static char *append_string_max (char *, const char *, const char *);
84 static int write_header (struct sfm_writer *, const struct dictionary *);
85 static int buf_write (struct sfm_writer *, const void *, size_t);
86 static int write_variable (struct sfm_writer *, struct variable *);
87 static int write_value_labels (struct sfm_writer *,
88                                struct variable *, int idx);
89 static int write_rec_7_34 (struct sfm_writer *);
90 static int write_documents (struct sfm_writer *, const struct dictionary *);
91 static int does_dict_need_translation (const struct dictionary *);
92
93 static inline int
94 var_flt64_cnt (const struct variable *v) 
95 {
96   return v->type == NUMERIC ? 1 : DIV_RND_UP (v->width, sizeof (flt64));
97 }
98
99 /* Opens the system file designated by file handle FH for writing
100    cases from dictionary D.  If COMPRESS is nonzero, the
101    system file will be compressed.
102
103    No reference to D is retained, so it may be modified or
104    destroyed at will after this function returns. */
105 struct sfm_writer *
106 sfm_open_writer (struct file_handle *fh,
107                  const struct dictionary *d, int compress)
108 {
109   struct sfm_writer *w = NULL;
110   int idx;
111   int i;
112
113   if (!fh_open (fh, "system file", "we"))
114     goto error;
115
116   /* Create and initialize writer. */
117   w = xmalloc (sizeof *w);
118   w->fh = fh;
119   w->file = fopen (handle_get_filename (fh), "wb");
120
121   w->needs_translation = does_dict_need_translation (d);
122   w->compress = compress;
123   w->case_cnt = 0;
124   w->flt64_cnt = 0;
125
126   w->buf = w->end = w->ptr = NULL;
127   w->x = w->y = NULL;
128
129   w->var_cnt = dict_get_var_cnt (d);
130   w->vars = xmalloc (sizeof *w->vars * w->var_cnt);
131   for (i = 0; i < w->var_cnt; i++) 
132     {
133       const struct variable *dv = dict_get_var (d, i);
134       struct sfm_var *sv = &w->vars[i];
135       sv->width = dv->width;
136       sv->fv = dv->fv;
137       sv->flt64_cnt = var_flt64_cnt (dv);
138     }
139
140   /* Check that file create succeeded. */
141   if (w->file == NULL)
142     {
143       msg (ME, _("Error opening \"%s\" for writing "
144                  "as a system file: %s."),
145            handle_get_filename (w->fh), strerror (errno));
146       err_cond_fail ();
147       goto error;
148     }
149
150   /* Write the file header. */
151   if (!write_header (w, d))
152     goto error;
153
154   /* Write basic variable info. */
155   for (i = 0; i < dict_get_var_cnt (d); i++)
156     write_variable (w, dict_get_var (d, i));
157
158   /* Write out value labels. */
159   for (idx = i = 0; i < dict_get_var_cnt (d); i++)
160     {
161       struct variable *v = dict_get_var (d, i);
162
163       if (!write_value_labels (w, v, idx))
164         goto error;
165       idx += var_flt64_cnt (v);
166     }
167
168   if (dict_get_documents (d) != NULL && !write_documents (w, d))
169     goto error;
170   if (!write_rec_7_34 (w))
171     goto error;
172
173   /* Write record 999. */
174   {
175     struct
176       {
177         int32 rec_type P;
178         int32 filler P;
179       }
180     rec_999;
181
182     rec_999.rec_type = 999;
183     rec_999.filler = 0;
184
185     if (!buf_write (w, &rec_999, sizeof rec_999))
186       goto error;
187   }
188
189   if (w->compress) 
190     {
191       w->buf = xmalloc (sizeof *w->buf * 128);
192       w->ptr = w->buf;
193       w->end = &w->buf[128];
194       w->x = (unsigned char *) w->ptr++;
195       w->y = (unsigned char *) w->ptr;
196     }
197   
198   return w;
199
200  error:
201   sfm_close_writer (w);
202   return NULL;
203 }
204
205 static int
206 does_dict_need_translation (const struct dictionary *d)
207 {
208   size_t case_idx;
209   size_t i;
210
211   case_idx = 0;
212   for (i = 0; i < dict_get_var_cnt (d); i++) 
213     {
214       struct variable *v = dict_get_var (d, i);
215       if (v->fv != case_idx)
216         return 0;
217       case_idx += v->nv;
218     }
219   return 1;
220 }
221
222 /* Returns value of X truncated to two least-significant digits. */
223 static int
224 rerange (int x)
225 {
226   if (x < 0)
227     x = -x;
228   if (x >= 100)
229     x %= 100;
230   return x;
231 }
232
233 /* Write the sysfile_header header to system file W. */
234 static int
235 write_header (struct sfm_writer *w, const struct dictionary *d)
236 {
237   struct sysfile_header hdr;
238   char *p;
239   int i;
240
241   time_t t;
242
243   memcpy (hdr.rec_type, "$FL2", 4);
244
245   p = stpcpy (hdr.prod_name, "@(#) SPSS DATA FILE ");
246   p = append_string_max (p, version, &hdr.prod_name[60]);
247   p = append_string_max (p, " - ", &hdr.prod_name[60]);
248   p = append_string_max (p, host_system, &hdr.prod_name[60]);
249   memset (p, ' ', &hdr.prod_name[60] - p);
250
251   hdr.layout_code = 2;
252
253   w->flt64_cnt = 0;
254   for (i = 0; i < dict_get_var_cnt (d); i++)
255     w->flt64_cnt += var_flt64_cnt (dict_get_var (d, i));
256   hdr.case_size = w->flt64_cnt;
257
258   hdr.compress = w->compress;
259
260   if (dict_get_weight (d) != NULL)
261     {
262       struct variable *weight_var;
263       int recalc_weight_idx = 1;
264       int i;
265
266       weight_var = dict_get_weight (d);
267       for (i = 0; ; i++) 
268         {
269           struct variable *v = dict_get_var (d, i);
270           if (v == weight_var)
271             break;
272           recalc_weight_idx += var_flt64_cnt (v);
273         }
274       hdr.weight_idx = recalc_weight_idx;
275     }
276   else
277     hdr.weight_idx = 0;
278
279   hdr.case_cnt = -1;
280   hdr.bias = COMPRESSION_BIAS;
281
282   if (time (&t) == (time_t) -1)
283     {
284       memcpy (hdr.creation_date, "01 Jan 70", 9);
285       memcpy (hdr.creation_time, "00:00:00", 8);
286     }
287   else
288     {
289       static const char *month_name[12] =
290         {
291           "Jan", "Feb", "Mar", "Apr", "May", "Jun",
292           "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
293         };
294       struct tm *tmp = localtime (&t);
295       int day = rerange (tmp->tm_mday);
296       int mon = rerange (tmp->tm_mon + 1);
297       int year = rerange (tmp->tm_year);
298       int hour = rerange (tmp->tm_hour + 1);
299       int min = rerange (tmp->tm_min + 1);
300       int sec = rerange (tmp->tm_sec + 1);
301       char buf[10];
302
303       sprintf (buf, "%02d %s %02d", day, month_name[mon - 1], year);
304       memcpy (hdr.creation_date, buf, sizeof hdr.creation_date);
305       sprintf (buf, "%02d:%02d:%02d", hour - 1, min - 1, sec - 1);
306       memcpy (hdr.creation_time, buf, sizeof hdr.creation_time);
307     }
308   
309   {
310     const char *label = dict_get_label (d);
311     if (label == NULL)
312       label = "";
313
314     st_bare_pad_copy (hdr.file_label, label, sizeof hdr.file_label); 
315   }
316   
317   memset (hdr.padding, 0, sizeof hdr.padding);
318
319   if (!buf_write (w, &hdr, sizeof hdr))
320     return 0;
321   return 1;
322 }
323
324 /* Translates format spec from internal form in SRC to system file
325    format in DEST. */
326 static inline void
327 write_format_spec (struct fmt_spec *src, int32 *dest)
328 {
329   *dest = (formats[src->type].spss << 16) | (src->w << 8) | src->d;
330 }
331
332 /* Write the variable record(s) for primary variable P and secondary
333    variable S to system file W. */
334 static int
335 write_variable (struct sfm_writer *w, struct variable *v)
336 {
337   struct sysfile_variable sv;
338
339   /* Missing values. */
340   flt64 m[3];           /* Missing value values. */
341   int nm;               /* Number of missing values, possibly negative. */
342
343   sv.rec_type = 2;
344   sv.type = v->width;
345   sv.has_var_label = (v->label != NULL);
346
347   switch (v->miss_type)
348     {
349     case MISSING_NONE:
350       nm = 0;
351       break;
352     case MISSING_1:
353     case MISSING_2:
354     case MISSING_3:
355       for (nm = 0; nm < v->miss_type; nm++)
356         m[nm] = v->missing[nm].f;
357       break;
358     case MISSING_RANGE:
359       m[0] = v->missing[0].f;
360       m[1] = v->missing[1].f;
361       nm = -2;
362       break;
363     case MISSING_LOW:
364       m[0] = second_lowest_flt64;
365       m[1] = v->missing[0].f;
366       nm = -2;
367       break;
368     case MISSING_HIGH:
369       m[0] = v->missing[0].f;
370       m[1] = FLT64_MAX;
371       nm = -2;
372       break;
373     case MISSING_RANGE_1:
374       m[0] = v->missing[0].f;
375       m[1] = v->missing[1].f;
376       m[2] = v->missing[2].f;
377       nm = -3;
378       break;
379     case MISSING_LOW_1:
380       m[0] = second_lowest_flt64;
381       m[1] = v->missing[0].f;
382       m[2] = v->missing[1].f;
383       nm = -3;
384       break;
385     case MISSING_HIGH_1:
386       m[0] = v->missing[0].f;
387       m[1] = second_lowest_flt64;
388       m[2] = v->missing[1].f;
389       nm = -3;
390       break;
391     default:
392       assert (0);
393       abort ();
394     }
395
396   sv.n_missing_values = nm;
397   write_format_spec (&v->print, &sv.print);
398   write_format_spec (&v->write, &sv.write);
399   memcpy (sv.name, v->name, strlen (v->name));
400   memset (&sv.name[strlen (v->name)], ' ', 8 - strlen (v->name));
401   if (!buf_write (w, &sv, sizeof sv))
402     return 0;
403
404   if (v->label)
405     {
406       struct label
407         {
408           int32 label_len P;
409           char label[255] P;
410         }
411       l;
412
413       int ext_len;
414
415       l.label_len = min (strlen (v->label), 255);
416       ext_len = ROUND_UP (l.label_len, sizeof l.label_len);
417       memcpy (l.label, v->label, l.label_len);
418       memset (&l.label[l.label_len], ' ', ext_len - l.label_len);
419
420       if (!buf_write (w, &l, offsetof (struct label, label) + ext_len))
421         return 0;
422     }
423
424   if (nm && !buf_write (w, m, sizeof *m * nm))
425     return 0;
426
427   if (v->type == ALPHA && v->width > (int) sizeof (flt64))
428     {
429       int i;
430       int pad_count;
431
432       sv.type = -1;
433       sv.has_var_label = 0;
434       sv.n_missing_values = 0;
435       memset (&sv.print, 0, sizeof sv.print);
436       memset (&sv.write, 0, sizeof sv.write);
437       memset (&sv.name, 0, sizeof sv.name);
438
439       pad_count = DIV_RND_UP (v->width, (int) sizeof (flt64)) - 1;
440       for (i = 0; i < pad_count; i++)
441         if (!buf_write (w, &sv, sizeof sv))
442           return 0;
443     }
444
445   return 1;
446 }
447
448 /* Writes the value labels for variable V having system file variable
449    index IDX to system file W.  Returns
450    nonzero only if successful. */
451 static int
452 write_value_labels (struct sfm_writer *w, struct variable *v, int idx)
453 {
454   struct value_label_rec
455     {
456       int32 rec_type P;
457       int32 n_labels P;
458       flt64 labels[1] P;
459     };
460
461   struct var_idx_rec
462     {
463       int32 rec_type P;
464       int32 n_vars P;
465       int32 vars[1] P;
466     };
467
468   struct val_labs_iterator *i;
469   struct value_label_rec *vlr;
470   struct var_idx_rec vir;
471   struct val_lab *vl;
472   size_t vlr_size;
473   flt64 *loc;
474
475   if (!val_labs_count (v->val_labs))
476     return 1;
477
478   /* Pass 1: Count bytes. */
479   vlr_size = (sizeof (struct value_label_rec)
480               + sizeof (flt64) * (val_labs_count (v->val_labs) - 1));
481   for (vl = val_labs_first (v->val_labs, &i); vl != NULL;
482        vl = val_labs_next (v->val_labs, &i))
483     vlr_size += ROUND_UP (strlen (vl->label) + 1, sizeof (flt64));
484
485   /* Pass 2: Copy bytes. */
486   vlr = xmalloc (vlr_size);
487   vlr->rec_type = 3;
488   vlr->n_labels = val_labs_count (v->val_labs);
489   loc = vlr->labels;
490   for (vl = val_labs_first_sorted (v->val_labs, &i); vl != NULL;
491        vl = val_labs_next (v->val_labs, &i))
492     {
493       size_t len = strlen (vl->label);
494
495       *loc++ = vl->value.f;
496       *(unsigned char *) loc = len;
497       memcpy (&((unsigned char *) loc)[1], vl->label, len);
498       memset (&((unsigned char *) loc)[1 + len], ' ',
499               REM_RND_UP (len + 1, sizeof (flt64)));
500       loc += DIV_RND_UP (len + 1, sizeof (flt64));
501     }
502   
503   if (!buf_write (w, vlr, vlr_size))
504     {
505       free (vlr);
506       return 0;
507     }
508   free (vlr);
509
510   vir.rec_type = 4;
511   vir.n_vars = 1;
512   vir.vars[0] = idx + 1;
513   if (!buf_write (w, &vir, sizeof vir))
514     return 0;
515
516   return 1;
517 }
518
519 /* Writes record type 6, document record. */
520 static int
521 write_documents (struct sfm_writer *w, const struct dictionary *d)
522 {
523   struct
524     {
525       int32 rec_type P;         /* Always 6. */
526       int32 n_lines P;          /* Number of lines of documents. */
527     }
528   rec_6;
529
530   const char *documents;
531   size_t n_lines;
532
533   documents = dict_get_documents (d);
534   n_lines = strlen (documents) / 80;
535
536   rec_6.rec_type = 6;
537   rec_6.n_lines = n_lines;
538   if (!buf_write (w, &rec_6, sizeof rec_6))
539     return 0;
540   if (!buf_write (w, documents, 80 * n_lines))
541     return 0;
542
543   return 1;
544 }
545
546 /* Writes record type 7, subtypes 3 and 4. */
547 static int
548 write_rec_7_34 (struct sfm_writer *w)
549 {
550   struct
551     {
552       int32 rec_type_3 P;
553       int32 subtype_3 P;
554       int32 data_type_3 P;
555       int32 n_elem_3 P;
556       int32 elem_3[8] P;
557       int32 rec_type_4 P;
558       int32 subtype_4 P;
559       int32 data_type_4 P;
560       int32 n_elem_4 P;
561       flt64 elem_4[3] P;
562     }
563   rec_7;
564
565   /* Components of the version number, from major to minor. */
566   int version_component[3];
567   
568   /* Used to step through the version string. */
569   char *p;
570
571   /* Parses the version string, which is assumed to be of the form
572      #.#x, where each # is a string of digits, and x is a single
573      letter. */
574   version_component[0] = strtol (bare_version, &p, 10);
575   if (*p == '.')
576     p++;
577   version_component[1] = strtol (bare_version, &p, 10);
578   version_component[2] = (isalpha ((unsigned char) *p)
579                           ? tolower ((unsigned char) *p) - 'a' : 0);
580     
581   rec_7.rec_type_3 = 7;
582   rec_7.subtype_3 = 3;
583   rec_7.data_type_3 = sizeof (int32);
584   rec_7.n_elem_3 = 8;
585   rec_7.elem_3[0] = version_component[0];
586   rec_7.elem_3[1] = version_component[1];
587   rec_7.elem_3[2] = version_component[2];
588   rec_7.elem_3[3] = -1;
589
590   /* PORTME: 1=IEEE754, 2=IBM 370, 3=DEC VAX E. */
591 #ifdef FPREP_IEEE754
592   rec_7.elem_3[4] = 1;
593 #endif
594
595   rec_7.elem_3[5] = 1;
596
597   /* PORTME: 1=big-endian, 2=little-endian. */
598 #if WORDS_BIGENDIAN
599   rec_7.elem_3[6] = 1;
600 #else
601   rec_7.elem_3[6] = 2;
602 #endif
603
604   /* PORTME: 1=EBCDIC, 2=7-bit ASCII, 3=8-bit ASCII, 4=DEC Kanji. */
605   rec_7.elem_3[7] = 2;
606
607   rec_7.rec_type_4 = 7;
608   rec_7.subtype_4 = 4;
609   rec_7.data_type_4 = sizeof (flt64);
610   rec_7.n_elem_4 = 3;
611   rec_7.elem_4[0] = -FLT64_MAX;
612   rec_7.elem_4[1] = FLT64_MAX;
613   rec_7.elem_4[2] = second_lowest_flt64;
614
615   if (!buf_write (w, &rec_7, sizeof rec_7))
616     return 0;
617   return 1;
618 }
619
620 /* Write NBYTES starting at BUF to the system file represented by
621    H. */
622 static int
623 buf_write (struct sfm_writer *w, const void *buf, size_t nbytes)
624 {
625   assert (buf != NULL);
626   if (fwrite (buf, nbytes, 1, w->file) != 1)
627     {
628       msg (ME, _("%s: Writing system file: %s."),
629            handle_get_filename (w->fh), strerror (errno));
630       return 0;
631     }
632   return 1;
633 }
634
635 /* Copies string DEST to SRC with the proviso that DEST does not reach
636    byte END; no null terminator is copied.  Returns a pointer to the
637    byte after the last byte copied. */
638 static char *
639 append_string_max (char *dest, const char *src, const char *end)
640 {
641   int nbytes = min (end - dest, (int) strlen (src));
642   memcpy (dest, src, nbytes);
643   return dest + nbytes;
644 }
645
646 /* Makes certain that the compression buffer of H has room for another
647    element.  If there's not room, pads out the current instruction
648    octet with zero and dumps out the buffer. */
649 static inline int
650 ensure_buf_space (struct sfm_writer *w)
651 {
652   if (w->ptr >= w->end)
653     {
654       memset (w->x, 0, w->y - w->x);
655       w->x = w->y;
656       w->ptr = w->buf;
657       if (!buf_write (w, w->buf, sizeof *w->buf * 128))
658         return 0;
659     }
660   return 1;
661 }
662
663 static void write_compressed_data (struct sfm_writer *w, const flt64 *elem);
664
665 /* Writes case C to system file W.
666    Returns nonzero if successful. */
667 int
668 sfm_write_case (struct sfm_writer *w, struct ccase *c)
669 {
670   w->case_cnt++;
671
672   if (!w->needs_translation && !w->compress
673       && sizeof (flt64) == sizeof (union value)) 
674     {
675       /* Fast path: external and internal representations are the
676          same and the dictionary is properly ordered.  Write
677          directly to file. */
678       buf_write (w, case_data_all (c), sizeof (union value) * w->flt64_cnt);
679     }
680   else 
681     {
682       /* Slow path: internal and external representations differ.
683          Write into a bounce buffer, then write to W. */
684       flt64 *bounce;
685       flt64 *bounce_cur;
686       size_t bounce_size;
687       size_t i;
688
689       bounce_size = sizeof *bounce * w->flt64_cnt;
690       bounce = bounce_cur = local_alloc (bounce_size);
691
692       for (i = 0; i < w->var_cnt; i++) 
693         {
694           struct sfm_var *v = &w->vars[i];
695
696           if (v->width == 0) 
697             *bounce_cur = case_num (c, v->fv);
698           else 
699             memcpy (bounce_cur, case_data (c, v->fv)->s, v->width);
700           bounce_cur += v->flt64_cnt;
701         }
702
703       if (!w->compress)
704         buf_write (w, bounce, bounce_size);
705       else
706         write_compressed_data (w, bounce);
707
708       local_free (bounce); 
709     }
710   
711   return 1;
712 }
713
714 static void
715 put_instruction (struct sfm_writer *w, unsigned char instruction) 
716 {
717   if (w->x >= w->y)
718     {
719       if (!ensure_buf_space (w))
720         return;
721       w->x = (unsigned char *) w->ptr++;
722       w->y = (unsigned char *) w->ptr;
723     }
724   *w->x++ = instruction;
725 }
726
727 static void
728 put_element (struct sfm_writer *w, const flt64 *elem) 
729 {
730   if (!ensure_buf_space (w))
731     return;
732   memcpy (w->ptr++, elem, sizeof *elem);
733 }
734
735 static void
736 write_compressed_data (struct sfm_writer *w, const flt64 *elem) 
737 {
738   size_t i;
739
740   for (i = 0; i < w->var_cnt; i++)
741     {
742       struct sfm_var *v = &w->vars[i];
743
744       if (v->width == 0) 
745         {
746           if (*elem == -FLT64_MAX)
747             put_instruction (w, 255);
748           else if (*elem >= 1 - COMPRESSION_BIAS
749                    && *elem <= 251 - COMPRESSION_BIAS
750                    && *elem == (int) *elem) 
751             put_instruction (w, (int) *elem + COMPRESSION_BIAS);
752           else
753             {
754               put_instruction (w, 253);
755               put_element (w, elem);
756             }
757           elem++;
758         }
759       else 
760         {
761           size_t j;
762           
763           for (j = 0; j < v->flt64_cnt; j++, elem++) 
764             {
765               if (!memcmp (elem, "        ", sizeof (flt64)))
766                 put_instruction (w, 254);
767               else 
768                 {
769                   put_instruction (w, 253);
770                   put_element (w, elem);
771                 }
772             }
773         }
774     }
775 }
776
777 /* Closes a system file after we're done with it. */
778 void
779 sfm_close_writer (struct sfm_writer *w)
780 {
781   if (w == NULL)
782     return;
783
784   fh_close (w->fh, "system file", "we");
785   
786   if (w->file != NULL) 
787     {
788       /* Flush buffer. */
789       if (w->buf != NULL && w->ptr > w->buf)
790         {
791           memset (w->x, 0, w->y - w->x);
792           buf_write (w, w->buf, (w->ptr - w->buf) * sizeof *w->buf);
793         }
794
795       /* Seek back to the beginning and update the number of cases.
796          This is just a courtesy to later readers, so there's no need
797          to check return values or report errors. */
798       if (!fseek (w->file, offsetof (struct sysfile_header, case_cnt), SEEK_SET))
799         {
800           int32 case_cnt = w->case_cnt;
801
802           /* I don't really care about the return value: it doesn't
803              matter whether this data is written. */
804           fwrite (&case_cnt, sizeof case_cnt, 1, w->file);
805         }
806
807       if (fclose (w->file) == EOF)
808         msg (ME, _("%s: Closing system file: %s."),
809              handle_get_filename (w->fh), strerror (errno));
810     }
811
812   free (w->buf);
813   free (w->vars);
814   free (w);
815 }