+
+/* System file writer casewriter class. */
+static const struct casewriter_class sys_file_casewriter_class =
+ {
+ sys_file_casewriter_write,
+ sys_file_casewriter_destroy,
+ NULL,
+ };
+\f
+/* Writes case C to system file W, without compressing it. */
+static void
+write_case_uncompressed (struct sfm_writer *w, const struct ccase *c)
+{
+ size_t i;
+
+ for (i = 0; i < w->sfm_var_cnt; i++)
+ {
+ struct sfm_var *v = &w->sfm_vars[i];
+
+ if (v->var_width == 0)
+ write_float (w, case_num_idx (c, v->case_index));
+ else
+ {
+ write_bytes (w, case_str_idx (c, v->case_index) + v->offset,
+ v->segment_width);
+ write_spaces (w, v->padding);
+ }
+ }
+}
+
+/* Writes case C to system file W, with compression. */
+static void
+write_case_compressed (struct sfm_writer *w, const struct ccase *c)
+{
+ size_t i;
+
+ for (i = 0; i < w->sfm_var_cnt; i++)
+ {
+ struct sfm_var *v = &w->sfm_vars[i];
+
+ if (v->var_width == 0)
+ {
+ double d = case_num_idx (c, v->case_index);
+ if (d == SYSMIS)
+ put_cmp_opcode (w, 255);
+ else if (d >= 1 - COMPRESSION_BIAS
+ && d <= 251 - COMPRESSION_BIAS
+ && d == (int) d)
+ put_cmp_opcode (w, (int) d + COMPRESSION_BIAS);
+ else
+ put_cmp_number (w, d);
+ }
+ else
+ {
+ int offset = v->offset;
+ int width, padding;
+
+ /* This code properly deals with a width that is not a
+ multiple of 8, by ensuring that the final partial
+ oct (8 byte unit) is treated as padded with spaces
+ on the right. */
+ for (width = v->segment_width; width > 0; width -= 8, offset += 8)
+ {
+ const void *data = case_str_idx (c, v->case_index) + offset;
+ int chunk_size = MIN (width, 8);
+ if (!memcmp (data, " ", chunk_size))
+ put_cmp_opcode (w, 254);
+ else
+ put_cmp_string (w, data, chunk_size);
+ }
+
+ /* This code deals properly with padding that is not a
+ multiple of 8 bytes, by discarding the remainder,
+ which was already effectively padded with spaces in
+ the previous loop. (Note that v->width + v->padding
+ is always a multiple of 8.) */
+ for (padding = v->padding / 8; padding > 0; padding--)
+ put_cmp_opcode (w, 254);
+ }
+ }
+}
+
+static bool
+start_zstream (struct sfm_writer *w)
+{
+ int error;
+
+ error = deflateInit (&w->zstream, 1);
+ if (error != Z_OK)
+ {
+ msg (ME, _("Failed to initialize ZLIB for compression (%s)."),
+ w->zstream.msg);
+ return false;
+ }
+ return true;
+}
+
+static void
+finish_zstream (struct sfm_writer *w)
+{
+ struct zblock *block;
+ int error;
+
+ assert (w->zstream.total_in <= ZBLOCK_SIZE);
+
+ w->zstream.next_in = NULL;
+ w->zstream.avail_in = 0;
+ do
+ {
+ uint8_t buf[4096];
+
+ w->zstream.next_out = buf;
+ w->zstream.avail_out = sizeof buf;
+ error = deflate (&w->zstream, Z_FINISH);
+ write_bytes (w, buf, w->zstream.next_out - buf);
+ }
+ while (error == Z_OK);
+
+ if (error != Z_STREAM_END)
+ msg (ME, _("Failed to complete ZLIB stream compression (%s)."),
+ w->zstream.msg);
+
+ if (w->n_blocks >= w->allocated_blocks)
+ w->blocks = x2nrealloc (w->blocks, &w->allocated_blocks,
+ sizeof *w->blocks);
+ block = &w->blocks[w->n_blocks++];
+ block->uncompressed_size = w->zstream.total_in;
+ block->compressed_size = w->zstream.total_out;
+ deflateEnd (&w->zstream);
+}
+
+static void
+write_zlib (struct sfm_writer *w, const void *data_, unsigned int n)
+{
+ const uint8_t *data = data_;
+
+ while (n > 0)
+ {
+ unsigned int chunk;
+
+ if (w->zstream.total_in >= ZBLOCK_SIZE)
+ {
+ finish_zstream (w);
+ start_zstream (w);
+ }
+
+ chunk = MIN (n, ZBLOCK_SIZE - w->zstream.total_in);
+
+ w->zstream.next_in = CONST_CAST (uint8_t *, data);
+ w->zstream.avail_in = chunk;
+ do
+ {
+ uint8_t buf[4096];
+ int error;
+
+ w->zstream.next_out = buf;
+ w->zstream.avail_out = sizeof buf;
+ error = deflate (&w->zstream, Z_NO_FLUSH);
+ write_bytes (w, buf, w->zstream.next_out - buf);
+ if (error != Z_OK)
+ {
+ msg (ME, _("ZLIB stream compression failed (%s)."),
+ w->zstream.msg);
+ return;
+ }
+ }
+ while (w->zstream.avail_in > 0 || w->zstream.avail_out == 0);
+ data += chunk;
+ n -= chunk;
+ }
+}
+
+static void
+write_ztrailer (struct sfm_writer *w)
+{
+ long long int uncompressed_ofs;
+ long long int compressed_ofs;
+ const struct zblock *block;
+
+ write_int64 (w, -COMPRESSION_BIAS);
+ write_int64 (w, 0);
+ write_int (w, ZBLOCK_SIZE);
+ write_int (w, w->n_blocks);
+
+ uncompressed_ofs = w->zstart;
+ compressed_ofs = w->zstart + 24;
+ for (block = w->blocks; block < &w->blocks[w->n_blocks]; block++)
+ {
+ write_int64 (w, uncompressed_ofs);
+ write_int64 (w, compressed_ofs);
+ write_int (w, block->uncompressed_size);
+ write_int (w, block->compressed_size);
+
+ uncompressed_ofs += block->uncompressed_size;
+ compressed_ofs += block->compressed_size;
+ }
+
+ if (!fseeko (w->file, w->zstart + 8, SEEK_SET))
+ {
+ write_int64 (w, compressed_ofs);
+ write_int64 (w, 24 + (w->n_blocks * 24));
+ }
+ else
+ msg (ME, _("%s: Seek failed (%s)."),
+ fh_get_file_name (w->fh), strerror (errno));
+}
+
+/* Flushes buffered compressed opcodes and data to W. */
+static void
+flush_compressed (struct sfm_writer *w)
+{
+ if (w->n_opcodes)
+ {
+ unsigned int n = 8 * (1 + w->n_elements);
+ if (w->compression == ANY_COMP_SIMPLE)
+ write_bytes (w, w->cbuf, n);
+ else
+ write_zlib (w, w->cbuf, n);
+
+ w->n_opcodes = w->n_elements = 0;
+ memset (w->cbuf[0], 0, 8);
+ }
+}
+
+/* Appends OPCODE to the buffered set of compression opcodes in
+ W. Flushes the compression buffer beforehand if necessary. */
+static void
+put_cmp_opcode (struct sfm_writer *w, uint8_t opcode)
+{
+ if (w->n_opcodes >= 8)
+ flush_compressed (w);
+
+ w->cbuf[0][w->n_opcodes++] = opcode;
+}
+
+/* Appends NUMBER to the buffered compression data in W. */
+static void
+put_cmp_number (struct sfm_writer *w, double number)
+{
+ put_cmp_opcode (w, 253);
+ convert_double_to_output_format (number, w->cbuf[++w->n_elements]);
+}
+
+/* Appends SIZE bytes of DATA to the buffered compression data in
+ W, followed by enough spaces to pad the output data to exactly
+ 8 bytes (thus, SIZE must be no greater than 8). */
+static void
+put_cmp_string (struct sfm_writer *w, const void *data, size_t size)
+{
+ assert (size <= 8);
+
+ put_cmp_opcode (w, 253);
+ w->n_elements++;
+ memset (w->cbuf[w->n_elements], w->space, 8);
+ memcpy (w->cbuf[w->n_elements], data, size);
+}
+\f
+/* Writes 32-bit integer X to the output file for writer W. */
+static void
+write_int (struct sfm_writer *w, int32_t x)
+{
+ write_bytes (w, &x, sizeof x);
+}
+
+/* Writes 64-bit integer X to the output file for writer W. */
+static void
+write_int64 (struct sfm_writer *w, int64_t x)
+{
+ write_bytes (w, &x, sizeof x);
+}
+
+/* Converts NATIVE to the 64-bit format used in output files in
+ OUTPUT. */
+static inline void
+convert_double_to_output_format (double native, uint8_t output[8])
+{
+ /* If "double" is not a 64-bit type, then convert it to a
+ 64-bit type. Otherwise just copy it. */
+ if (FLOAT_NATIVE_DOUBLE != FLOAT_NATIVE_64_BIT)
+ float_convert (FLOAT_NATIVE_DOUBLE, &native, FLOAT_NATIVE_64_BIT, output);
+ else
+ memcpy (output, &native, sizeof native);
+}
+
+/* Writes floating-point number X to the output file for writer
+ W. */
+static void
+write_float (struct sfm_writer *w, double x)
+{
+ uint8_t output[8];
+ convert_double_to_output_format (x, output);
+ write_bytes (w, output, sizeof output);
+}
+
+/* Writes contents of VALUE with the given WIDTH to W, padding
+ with zeros to a multiple of 8 bytes.
+ To avoid a branch, and because we don't actually need to
+ support it, WIDTH must be no bigger than 8. */
+static void
+write_value (struct sfm_writer *w, const union value *value, int width)
+{
+ assert (width <= 8);
+ if (width == 0)
+ write_float (w, value->f);
+ else
+ {
+ write_bytes (w, value_str (value, width), width);
+ write_zeros (w, 8 - width);
+ }
+}
+
+/* Writes null-terminated STRING in a field of the given WIDTH to W. If STRING
+ is longer than WIDTH, it is truncated; if STRING is shorter than WIDTH, it
+ is padded on the right with spaces. */
+static void
+write_string (struct sfm_writer *w, const char *string, size_t width)
+{
+ size_t data_bytes = MIN (strlen (string), width);
+ size_t pad_bytes = width - data_bytes;
+ write_bytes (w, string, data_bytes);
+ while (pad_bytes-- > 0)
+ putc (w->space, w->file);
+}
+
+/* Recodes null-terminated UTF-8 encoded STRING into ENCODING, and writes the
+ recoded version in a field of the given WIDTH to W. The string is truncated
+ or padded on the right with spaces to exactly WIDTH bytes. */
+static void
+write_utf8_string (struct sfm_writer *w, const char *encoding,
+ const char *string, size_t width)
+{
+ char *s = recode_string (encoding, "UTF-8", string, -1);
+ write_string (w, s, width);
+ free (s);
+}
+
+/* Writes a record with type 7, subtype SUBTYPE that contains CONTENT recoded
+ from UTF-8 encoded into ENCODING. */
+static void
+write_utf8_record (struct sfm_writer *w, const char *encoding,
+ const struct string *content, int subtype)
+{
+ struct substring s;
+
+ s = recode_substring_pool (encoding, "UTF-8", ds_ss (content), NULL);
+ write_string_record (w, s, subtype);
+ ss_dealloc (&s);
+}
+
+/* Writes a record with type 7, subtype SUBTYPE that contains the string
+ CONTENT. */
+static void
+write_string_record (struct sfm_writer *w,
+ const struct substring content, int subtype)
+{
+ write_int (w, 7);
+ write_int (w, subtype);
+ write_int (w, 1);
+ write_int (w, ss_length (content));
+ write_bytes (w, ss_data (content), ss_length (content));
+}
+
+/* Writes SIZE bytes of DATA to W's output file. */
+static void
+write_bytes (struct sfm_writer *w, const void *data, size_t size)
+{
+ fwrite (data, 1, size, w->file);
+}
+
+/* Writes N zeros to W's output file. */
+static void
+write_zeros (struct sfm_writer *w, size_t n)
+{
+ while (n-- > 0)
+ putc (0, w->file);
+}
+
+/* Writes N spaces to W's output file. */
+static void
+write_spaces (struct sfm_writer *w, size_t n)
+{
+ while (n-- > 0)
+ putc (w->space, w->file);
+}