doc: Improve description of extension subtype 5.
[pspp] / doc / dev / system-file-format.texi
index 89c35aab3f16dbe064f14dc94c3ced83ce285f60..ca7c668287634dde3cc9fe5df2b909a593323860 100644 (file)
@@ -1,3 +1,13 @@
+@c PSPP - a program for statistical analysis.
+@c Copyright (C) 2019 Free Software Foundation, Inc.
+@c Permission is granted to copy, distribute and/or modify this document
+@c under the terms of the GNU Free Documentation License, Version 1.3
+@c or any later version published by the Free Software Foundation;
+@c with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
+@c A copy of the license is included in the section entitled "GNU
+@c Free Documentation License".
+@c
+
 @node System File Format
 @appendix System File Format
 
@@ -6,7 +16,7 @@ that describes how they may be interpreted.  This chapter describes
 the format of a system file.
 
 System files use four data types: 8-bit characters, 32-bit integers,
-64-bit integers, 
+64-bit integers,
 and 64-bit floating points, called here @code{char}, @code{int32},
 @code{int64}, and
 @code{flt64}, respectively.  Data is not necessarily aligned on a word
@@ -30,7 +40,7 @@ files and translates as necessary.  PSPP also detects the
 floating-point format in use, as well as the endianness of IEEE 754
 floating-point numbers, and translates as needed.  However, only IEEE
 754 numbers with the same endianness as integer data in the same file
-has actually been observed in system files, and it is likely that
+have actually been observed in system files, and it is likely that
 other formats are obsolete or were never used.
 
 System files use a few floating point values for special purposes:
@@ -68,10 +78,80 @@ used for the dictionary and the data in the file, although it is
 possible to artificially synthesize files that use different encodings
 (@pxref{Character Encoding Record}).
 
-System files are divided into records, each of which begins with a
-4-byte record type, usually regarded as an @code{int32}.
+@menu
+* System File Record Structure::
+* File Header Record::
+* Variable Record::
+* Value Labels Records::
+* Document Record::
+* Machine Integer Info Record::
+* Machine Floating-Point Info Record::
+* Multiple Response Sets Records::
+* Extra Product Info Record::
+* Variable Display Parameter Record::
+* Long Variable Names Record::
+* Very Long String Record::
+* Character Encoding Record::
+* Long String Value Labels Record::
+* Long String Missing Values Record::
+* Data File and Variable Attributes Records::
+* Extended Number of Cases Record::
+* Other Informational Records::
+* Dictionary Termination Record::
+* Data Record::
+@end menu
+
+@node System File Record Structure
+@section System File Record Structure
+
+System files are divided into records with the following format:
+
+@example
+int32               type;
+char                data[];
+@end example
+
+This header does not identify the length of the @code{data} or any
+information about what it contains, so the system file reader must
+understand the format of @code{data} based on @code{type}.  However,
+records with type 7, called @dfn{extension records}, have a stricter
+format:
+
+@example
+int32               type;
+int32               subtype;
+int32               size;
+int32               count;
+char                data[size * count];
+@end example
+
+@table @code
+@item int32 rec_type;
+Record type.  Always set to 7.
+
+@item int32 subtype;
+Record subtype.  This value identifies a particular kind of extension
+record.
+
+@item int32 size;
+The size of each piece of data that follows the header, in bytes.
+Known extension records use 1, 4, or 8, for @code{char}, @code{int32},
+and @code{flt64} format data, respectively.
+
+@item int32 count;
+The number of pieces of data that follow the header.
+
+@item char data[size * count];
+Data, whose format and interpretation depend on the subtype.
+@end table
+
+An extension record contains exactly @code{size * count} bytes of
+data, which allows a reader that does not understand an extension
+record to skip it.  Extension records provide only nonessential
+information, so this allows for files written by newer software to
+preserve backward compatibility with older or less capable readers.
 
-The records must appear in the following order:
+Records in a system file must appear in the following order:
 
 @itemize @bullet
 @item
@@ -91,6 +171,11 @@ Document record, if present.
 Extension (type 7) records, in ascending numerical order of their
 subtypes.
 
+System files written by SPSS include at most one of each kind of
+extension record.  This is generally true of system files written by
+other software as well, with known exceptions noted below in the
+individual sections about each type of record.
+
 @item
 Dictionary termination record.
 
@@ -98,35 +183,19 @@ Dictionary termination record.
 Data record.
 @end itemize
 
-Each type of record is described separately below.
+We advise authors of programs that read system files to tolerate
+format variations.  Various kinds of misformatting and corruption have
+been observed in system files written by SPSS and other software
+alike.  In particular, because extension records provide nonessential
+information, it is generally better to ignore an extension record
+entirely than to refuse to read a system file.
 
-@menu
-* File Header Record::
-* Variable Record::
-* Value Labels Records::
-* Document Record::
-* Machine Integer Info Record::
-* Machine Floating-Point Info Record::
-* Multiple Response Sets Records::
-* Extra Product Info Record::
-* Variable Display Parameter Record::
-* Long Variable Names Record::
-* Very Long String Record::
-* Character Encoding Record::
-* Long String Value Labels Record::
-* Long String Missing Values Record::
-* Data File and Variable Attributes Records::
-* Extended Number of Cases Record::
-* Miscellaneous Informational Records::
-* Dictionary Termination Record::
-* Data Record::
-@end menu
+The following sections describe the known kinds of records.
 
 @node File Header Record
 @section File Header Record
 
-The file header is always the first record in the file.  It has the
-following format:
+A system file begins with the file header, with the following format:
 
 @example
 char                rec_type[4];
@@ -163,6 +232,12 @@ pspp 0.1.4 - sparc-sun-solaris2.5.2}.  The string is truncated if it
 would be longer than 60 characters; otherwise it is padded on the right
 with spaces.
 
+The product name field allow readers to behave differently based on
+quirks in the way that particular software writes system files.
+@xref{Value Labels Records}, for the detail of the quirk that the PSPP
+system file reader tolerates in files written by ReadStat, which has
+@code{https://github.com/WizardMac/ReadStat} in @code{prod_name}.
+
 @anchor{layout_code}
 @item int32 layout_code;
 Normally set to 2, although a few system files have been spotted in
@@ -173,11 +248,11 @@ file's integer endianness (@pxref{System File Format}).
 Number of data elements per case.  This is the number of variables,
 except that long string variables add extra data elements (one for every
 8 characters after the first 8).  However, string variables do not
-contribute to this value beyond the first 255 bytes.   Further, system
-files written by some systems set this value to -1.  In general, it is
+contribute to this value beyond the first 255 bytes.   Further, some
+software always writes -1 or 0 in this field.  In general, it is
 unsafe for systems reading system files to rely upon this value.
 
-@item int32 compressed;
+@item int32 compression;
 Set to 0 if the data in the file is not compressed, 1 if the data is
 compressed with simple bytecode compression, 2 if the data is ZLIB
 compressed.  This field has value 2 if and only if @code{rec_type} is
@@ -251,16 +326,20 @@ so readers should take care to parse dummy variable records in the
 same way as other variable records.
 
 @anchor{Dictionary Index}
-The @dfn{dictionary index} of a variable is its offset in the set of
+The @dfn{dictionary index} of a variable is a 1-based offset in the set of
 variable records, including dummy variable records for long string
-variables.  The first variable record has a dictionary index of 0, the
-second has a dictionary index of 1, and so on.
+variables.  The first variable record has a dictionary index of 1, the
+second has a dictionary index of 2, and so on.
 
 The system file format does not directly support string variables
 wider than 255 bytes.  Such very long string variables are represented
 by a number of narrower string variables.  @xref{Very Long String
 Record}, for details.
 
+A system file should contain at least one variable and thus at least
+one variable record, but system files have been observed in the wild
+without any variables (thus, no data either).
+
 @example
 int32               rec_type;
 int32               type;
@@ -315,12 +394,19 @@ the at-sign (@samp{@@}).  Subsequent characters may also be digits, octothorpes
 (@samp{#}), dollar signs (@samp{$}), underscores (@samp{_}), or full
 stops (@samp{.}).  The variable name is padded on the right with spaces.
 
+The @samp{name} fields should be unique within a system file.  System
+files written by SPSS that contain very long string variables with
+similar names sometimes contain duplicate names that are later
+eliminated by resolving the very long string names (@pxref{Very Long
+String Record}).  PSPP handles duplicates by assigning them new,
+unique names.
+
 @item int32 label_len;
 This field is present only if @code{has_var_label} is set to 1.  It is
 set to the length, in characters, of the variable label.  The
 documented maximum length varies from 120 to 255 based on SPSS
 version, but some files have been seen with longer labels.  PSPP
-accepts longer labels and truncates them to 255 bytes on input.
+accepts labels of any length.
 
 @item char label[];
 This field is present only if @code{has_var_label} is set to 1.  It has
@@ -344,6 +430,7 @@ in the range.  When a range plus a value are present, the third
 element denotes the additional discrete missing value.
 @end table
 
+@anchor{System File Output Formats}
 The @code{print} and @code{write} members of sysfile_variable are output
 formats coded into @code{int32} types.  The least-significant byte
 of the @code{int32} represents the number of decimal places, and the
@@ -437,6 +524,10 @@ Format types are defined as follows:
 @tab @code{EDATE}
 @item 39
 @tab @code{SDATE}
+@item 40
+@tab @code{MTIME}
+@item 41
+@tab @code{YMDHMS}
 @end multitable
 @end quotation
 
@@ -453,13 +544,21 @@ numeric and short string variables only.  Long string variables may
 have value labels, but their value labels are recorded using a
 different record type (@pxref{Long String Value Labels Record}).
 
+ReadStat (@pxref{File Header Record}) writes value labels that label a
+single value more than once.  In more detail, it emits value labels
+whose values are longer than string variables' widths, that are
+identical in the actual width of the variable, e.g.@: labels for
+values @code{ABC123} and @code{ABC456} for a string variable with
+width 3.  For files written by this software, PSPP ignores such
+labels.
+
 The value label record has the following format:
 
 @example
 int32               rec_type;
 int32               label_count;
 
-/* @r{Repeated @code{label_cnt} times}. */
+/* @r{Repeated @code{n_label} times}. */
 char                value[8];
 char                label_len;
 char                label[];
@@ -511,7 +610,7 @@ Number of variables that the associated value labels from the value
 label record are to be applied.
 
 @item int32 vars[];
-A list of dictionary indexes of variables to which to apply the value
+A list of 1-based dictionary indexes of variables to which to apply the value
 labels (@pxref{Dictionary Index}).  There are @code{var_count}
 elements.
 
@@ -534,7 +633,8 @@ char                lines[][80];
 Record type.  Always set to 6.
 
 @item int32 n_lines;
-Number of lines of documents present.
+Number of lines of documents present.  This should be greater than
+zero, but ReadStats writes system files with zero @code{n_lines}.
 
 @item char lines[][80];
 Document lines.  The number of elements is defined by @code{n_lines}.
@@ -679,13 +779,16 @@ Size of each piece of data in the data part, in bytes.  Always set to 8.
 Number of pieces of data in the data part.  Always set to 3.
 
 @item flt64 sysmis;
-The system missing value.
-
-@item flt64 highest;
-The value used for HIGHEST in missing values.
-
-@item flt64 lowest;
-The value used for LOWEST in missing values.
+@itemx flt64 highest;
+@itemx flt64 lowest;
+The system missing value, the value used for HIGHEST in missing
+values, and the value used for LOWEST in missing values, respectively.
+@xref{System File Format}, for more information.
+
+The SPSSWriter library in PHP, which identifies itself as @code{FOM
+SPSS 1.0.0} in the file header record @code{prod_name} field, writes
+unexpected values to these fields, but it uses the same values
+consistently throughout the rest of the file.
 @end table
 
 @node Multiple Response Sets Records
@@ -727,8 +830,8 @@ The size of each element in the @code{mrsets} member. Always set to 1.
 The total number of bytes in @code{mrsets}.
 
 @item char mrsets[];
-A series of multiple response sets, each of which consists of the
-following:
+Zero or more line feeds (byte 0x0a), followed by a series of multiple
+response sets, each of which consists of the following:
 
 @itemize @bullet
 @item
@@ -777,8 +880,15 @@ A space.
 The short names of the variables in the set, converted to lowercase,
 each separated from the previous by a single space.
 
+Even though a multiple response set must have at least two variables,
+some system files contain multiple response sets with no variables or
+one variable.  The source and meaning of these multiple response sets is
+unknown.  (Perhaps they arise from creating a multiple response set
+then deleting all the variables that it contains?)
+
 @item
-A line feed (byte 0x0a).
+One line feed (byte 0x0a).  Sometimes multiple, even hundreds, of line
+feeds are present.
 @end itemize
 @end table
 
@@ -1284,7 +1394,7 @@ The total number of bytes in @code{attributes}.
 @item char attributes[];
 The attributes, in a text-based format.
 
-In record type 17, this field contains a single attribute set.  An
+In record subtype 17, this field contains a single attribute set.  An
 attribute set is a sequence of one or more attributes concatenated
 together.  Each attribute consists of a name, which has the same
 syntax as a variable name, followed by, inside parentheses, a sequence
@@ -1296,13 +1406,17 @@ way to embed a line feed in a value.  There is no distinction between
 an attribute with a single value and an attribute array with one
 element.
 
-In record type 18, this field contains a sequence of one or more
+In record subtype 18, this field contains a sequence of one or more
 variable attribute sets.  If more than one variable attribute set is
 present, each one after the first is delimited from the previous by
 @code{/}.  Each variable attribute set consists of a long
 variable name,
 followed by @code{:}, followed by an attribute set with the same
-syntax as on record type 17.
+syntax as on record subtype 17.
+
+System files written by @code{Stata 14.1/-savespss- 1.77 by
+S.Radyakin} may include multiple records with subtype 18, one per
+variable that has variable attributes.
 
 The total length is @code{count} bytes.
 @end table
@@ -1321,10 +1435,10 @@ VARIABLE ATTRIBUTE VARIABLES=dummy ATTRIBUTE=bert('123').
 will contain a variable attribute record with the following contents:
 
 @example
-00000000  07 00 00 00 12 00 00 00  01 00 00 00 22 00 00 00  |............"...|
-00000010  64 75 6d 6d 79 3a 66 72  65 64 28 27 32 33 27 0a  |dummy:fred('23'.|
-00000020  27 33 34 27 0a 29 62 65  72 74 28 27 31 32 33 27  |'34'.)bert('123'|
-00000030  0a 29                                             |.)              |
+0000  07 00 00 00 12 00 00 00  01 00 00 00 22 00 00 00  |............"...|
+0010  64 75 6d 6d 79 3a 66 72  65 64 28 27 32 33 27 0a  |dummy:fred('23'.|
+0020  27 33 34 27 0a 29 62 65  72 74 28 27 31 32 33 27  |'34'.)bert('123'|
+0030  0a 29                                             |.)              |
 @end example
 
 @menu
@@ -1392,46 +1506,31 @@ same reason as @code{ncases} in the file header record, but this has
 not been observed in the wild.
 @end table
 
-@node Miscellaneous Informational Records
-@section Miscellaneous Informational Records
+@node Other Informational Records
+@section Other Informational Records
 
-Some specific types of miscellaneous informational records are
+This chapter documents many specific types of extension records are
 documented here, but others are known to exist.  PSPP ignores unknown
-miscellaneous informational records when reading system files.
+extension records when reading system files.
 
-@example
-/* @r{Header.} */
-int32               rec_type;
-int32               subtype;
-int32               size;
-int32               count;
-
-/* @r{Exactly @code{size * count} bytes of data.} */
-char                data[];
-@end example
+The following extension record subtypes have also been observed, with
+the following believed meanings:
 
-@table @code
-@item int32 rec_type;
-Record type.  Always set to 7.
-
-@item int32 subtype;
-Record subtype.  May take any value.  According to Aapi
-H@"am@"al@"ainen, value 5 indicates a set of grouped variables and 6
-indicates date info (probably related to USE).  Subtype 24 appears to
-contain XML that describes how data in the file should be displayed
-on-screen.
+@table @asis
+@item 5
+A named variable set for use in the GUI (according to Aapi
+H@"am@"al@"ainen).
 
-@item int32 size;
-Size of each piece of data in the data part.  Should have the value 1,
-4, or 8, for @code{char}, @code{int32}, and @code{flt64} format data,
-respectively.
+@item 6
+Date info, probably related to USE (according to Aapi H@"am@"al@"ainen).
 
-@item int32 count;
-Number of pieces of data in the data part.
+@item 12
+A UUID in the format described in RFC 4122.  Only two examples
+observed, both written by SPSS 13, and in each case the UUID contained
+both upper and lower case.
 
-@item char data[];
-Arbitrary data.  There must be @code{size} times @code{count} bytes of
-data.
+@item 24
+XML that describes how data in the file should be displayed on-screen.
 @end table
 
 @node Dictionary Termination Record
@@ -1488,9 +1587,10 @@ value @var{code} - @var{bias}, where
 variable @code{bias} from the file header.  For example,
 code 105 with bias 100.0 (the normal value) indicates a numeric variable
 of value 5.
-One file has been seen written by SPSS 14 that contained such a code
-in a @emph{string} field with the value 0 (after the bias is
-subtracted) as a way of encoding null bytes.
+
+A code of 0 (after subtracting the bias) in a string field encodes
+null bytes.  This is unusual, since a string field normally encodes
+text data, but it exists in real system files.
 
 @item 252
 End of file.  This code may or may not appear at the end of the data
@@ -1552,7 +1652,7 @@ The number of bytes in the ZLIB data trailer.  This and the previous
 field sum to the size of the system file in bytes.
 @end table
 
-The data header is followed by @code{(ztrailer_ofs - 24) / 24} ZLIB
+The data header is followed by @code{(ztrailer_len - 24) / 24} ZLIB
 compressed data blocks.  Each ZLIB compressed data block begins with a
 ZLIB header as specified in RFC@tie{}1950, e.g.@: hex bytes @code{78
 01} (the only header yet observed in practice).  Each block
@@ -1589,7 +1689,7 @@ been observed so far.
 
 @item int32 n_blocks;
 The number of ZLIB compressed data blocks, always exactly
-@code{(ztrailer_ofs - 24) / 24}.
+@code{(ztrailer_len - 24) / 24}.
 @end table
 
 The fixed header is followed by @code{n_blocks} 24-byte ZLIB data