+ struct sfm_value_label *label = &record->labels[i];
+ unsigned char label_len;
+ size_t padded_len;
+
+ read_bytes (r, label->value, sizeof label->value);
+
+ /* Read label length. */
+ read_bytes (r, &label_len, sizeof label_len);
+ padded_len = ROUND_UP (label_len + 1, 8);
+
+ /* Read label, padding. */
+ label->label = pool_malloc (r->pool, padded_len + 1);
+ read_bytes (r, label->label, padded_len - 1);
+ label->label[label_len] = '\0';
+ }
+
+ /* Read record type of type 4 record. */
+ if (read_int (r) != 4)
+ sys_error (r, r->pos - 4,
+ _("Variable index record (type 4) does not immediately "
+ "follow value label record (type 3) as it should."));
+
+ /* Read number of variables associated with value label from type 4
+ record. */
+ record->n_vars = read_int (r);
+ if (record->n_vars < 1 || record->n_vars > n_vars)
+ sys_error (r, r->pos - 4,
+ _("Number of variables associated with a value label (%d) "
+ "is not between 1 and the number of variables (%zu)."),
+ record->n_vars, n_vars);
+ record->vars = pool_nmalloc (r->pool, record->n_vars, sizeof *record->vars);
+ for (i = 0; i < record->n_vars; i++)
+ record->vars[i] = read_int (r);
+}
+
+/* Reads a document record from R and returns it. */
+static struct sfm_document_record *
+read_document_record (struct sfm_reader *r)
+{
+ struct sfm_document_record *record;
+ int n_lines;
+
+ record = pool_malloc (r->pool, sizeof *record);
+ record->pos = r->pos;
+
+ n_lines = read_int (r);
+ if (n_lines <= 0 || n_lines >= INT_MAX / DOC_LINE_LENGTH)
+ sys_error (r, record->pos,
+ _("Number of document lines (%d) "
+ "must be greater than 0 and less than %d."),
+ n_lines, INT_MAX / DOC_LINE_LENGTH);
+
+ record->n_lines = n_lines;
+ record->documents = pool_malloc (r->pool, DOC_LINE_LENGTH * n_lines);
+ read_bytes (r, record->documents, DOC_LINE_LENGTH * n_lines);
+
+ return record;
+}
+
+static void
+read_extension_record_header (struct sfm_reader *r, int subtype,
+ struct sfm_extension_record *record)
+{
+ record->pos = r->pos;
+ record->size = read_int (r);
+ record->count = read_int (r);
+
+ /* Check that SIZE * COUNT + 1 doesn't overflow. Adding 1
+ allows an extra byte for a null terminator, used by some
+ extension processing routines. */
+ if (record->size != 0
+ && size_overflow_p (xsum (1, xtimes (record->count, record->size))))
+ sys_error (r, record->pos, "Record type 7 subtype %d too large.", subtype);
+}
+
+/* Reads an extension record from R into RECORD. */
+static struct sfm_extension_record *
+read_extension_record (struct sfm_reader *r, int subtype)
+{
+ struct extension_record_type
+ {
+ int subtype;
+ int size;
+ int count;
+ };
+
+ static const struct extension_record_type types[] =
+ {
+ /* Implemented record types. */
+ { EXT_INTEGER, 4, 8 },
+ { EXT_FLOAT, 8, 3 },
+ { EXT_MRSETS, 1, 0 },
+ { EXT_DISPLAY, 4, 0 },
+ { EXT_LONG_NAMES, 1, 0 },
+ { EXT_LONG_STRINGS, 1, 0 },
+ { EXT_NCASES, 8, 2 },
+ { EXT_FILE_ATTRS, 1, 0 },
+ { EXT_VAR_ATTRS, 1, 0 },
+ { EXT_MRSETS2, 1, 0 },
+ { EXT_ENCODING, 1, 0 },
+ { EXT_LONG_LABELS, 1, 0 },
+
+ /* Ignored record types. */
+ { EXT_VAR_SETS, 0, 0 },
+ { EXT_DATE, 0, 0 },
+ { EXT_DATA_ENTRY, 0, 0 },
+ };
+
+ const struct extension_record_type *type;
+ struct sfm_extension_record *record;
+ size_t n_bytes;
+
+ record = pool_malloc (r->pool, sizeof *record);
+ read_extension_record_header (r, subtype, record);
+ n_bytes = record->count * record->size;
+
+ for (type = types; type < &types[sizeof types / sizeof *types]; type++)
+ if (subtype == type->subtype)
+ {
+ if (type->size > 0 && record->size != type->size)
+ sys_warn (r, record->pos,
+ _("Record type 7, subtype %d has bad size %zu "
+ "(expected %d)."), subtype, record->size, type->size);
+ else if (type->count > 0 && record->count != type->count)
+ sys_warn (r, record->pos,
+ _("Record type 7, subtype %d has bad count %zu "
+ "(expected %d)."), subtype, record->count, type->count);
+ else if (type->count == 0 && type->size == 0)
+ {
+ /* Ignore this record. */
+ }
+ else
+ {
+ char *data = pool_malloc (r->pool, n_bytes + 1);
+ data[n_bytes] = '\0';
+
+ record->data = data;
+ read_bytes (r, record->data, n_bytes);
+ return record;
+ }
+
+ goto skip;
+ }
+
+ sys_warn (r, record->pos,
+ _("Unrecognized record type 7, subtype %d. Please send a "
+ "copy of this file, and the syntax which created it to %s."),
+ subtype, PACKAGE_BUGREPORT);
+
+skip:
+ skip_bytes (r, n_bytes);
+ return NULL;
+}
+
+static void
+skip_extension_record (struct sfm_reader *r, int subtype)
+{
+ struct sfm_extension_record record;
+
+ read_extension_record_header (r, subtype, &record);
+ skip_bytes (r, record.count * record.size);
+}
+
+static void
+parse_file_label (struct sfm_reader *r, const char *file_label,
+ struct dictionary *dict)
+{
+ char *utf8_file_label;
+ size_t file_label_len;
+
+ utf8_file_label = recode_string_pool ("UTF-8", dict_get_encoding (dict),
+ file_label, -1, r->pool);
+ file_label_len = strlen (utf8_file_label);
+ while (file_label_len > 0 && utf8_file_label[file_label_len - 1] == ' ')
+ file_label_len--;
+ utf8_file_label[file_label_len] = '\0';
+ dict_set_label (dict, utf8_file_label);
+}
+
+/* Reads a variable (type 2) record from R and adds the
+ corresponding variable to DICT.
+ Also skips past additional variable records for long string
+ variables. */
+static void
+parse_variable_records (struct sfm_reader *r, struct dictionary *dict,
+ struct sfm_var_record *var_recs, size_t n_var_recs)
+{
+ const char *dict_encoding = dict_get_encoding (dict);
+ struct sfm_var_record *rec;
+ int n_warnings = 0;