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