This section discusses the C code of the SSFF implementation, and makes pertinent comments relating to the maintainability of this source. Also discussed is the issue of signal file portability across different machines and how this is achieved.
The file ssff.h contains the definitions of the data structures used in the SSFF system. The full definition of the header structure is:
struct SSFF_Header
{
/* the header variables */
TSSFF_Generic
*Machine,
*Vars;
float
Start_Time,
Record_Freq;
/* columns info */
int Num_Recs;
TSSFF_Column
*Cols;
/* internal vars go here */
int
Format, Machine_ID;
FILE *File;
int One_Rec_Size;
char *File_Data;
long Data_Seek_Pos;
/* ESPS vars if we are using ESPS as well */
#ifdef USE_ESPS
struct
{
struct header *Hdr;
struct fea_data *Rec;
/
ESPS_Vars;
#endif
}; |
The structures SSFF_Column and SSFF_Generic were discussed in the section called Reading the Header Structure.
The Machine field holds a pointer to a single generic variable. Space for this variable is malloc()ed by an internal function. The data field of this generic variable holds the name of the machine that the data is native to. The field Machine_ID holds an integer code for the machine type. Each machine is given a unique number by the routine Establish_Machine() in the file machine.c. This file contains all code that relates to data portability issues between machines. At the top of machine.c is a static array which associates each computer architecture supported with a unique number. If the machine indicated in the header of the data file is not supported, then Establish_Machine() prints out an error message and terminates.
The Vars pointer, as previously discussed, points to the first generic variable structure in the singly-linked list of such items. Start_Time and Record_Freq hold the indicated values for the particular file.
Num_Recs holds a copy of the argument passed to either SSFF_Read_Open() or SSFF_Write_Open() and indicates the size of the buffer that must be provided for reading or writing data. This buffer can be accessed and manipulated by the programmer. Cols contains a pointer to the start of the singly-linked list of field definitions relating to the structure of the store data in the file. Note that each SSFF_Column structure in the list contains a pointer to the record buffer (field Data) which denotes where the data for that field is read from or written to.
One_Rec_Size holds the size of one record in bytes. Thus, the size of the record buffer in memory is: One_Rec_Size * Num_Recs.
Format holds the type of the file being read or written to, FORMAT_SSFF or FORMAT_ESPS.
Data_Seek_Pos contains the seek position that is to be used by SSFF_Seek() if random access is desired. The value is the one returned by the standard C function ftell() when the file is positioned just after the header information and just before the data.
Providing machine independence for binary data files means dealing with the different binary numerical formats used on various computer architectures. With this in mind, the SSFF routines achieve independence by having two routines to process raw binary data, implemented in the file machine.c.
void Write_Convert_Data( int Machine_ID, char *Data,
struct SSFF_Column *Col ) |
This function converts binary representations from the host format, into the format that the data is to be written in. (For example, it is possible to write a program that creates native VAX signal files on a SUN SPARCstation.) This routine is called just before the data is written out to disk. Note that the generic variable MACHINE is used to determine which format is required. Machine_ID contains the number (one of the MACH_ set) indicating the format in which the data is required. The macro HOST_MACHINE contains the number indicating the current host.
Clearly, if the data that is to be written is supposed to be in the native format for the machine that is currently running the program, then no conversions have to be done. Hence, the source code for this function starts with:
/* return if trivial */
if (Machine_ID == HOST_MACHINE)
return; |
The argument Data points to the binary representation of the data stored in the field pointed to by Col. The length of the storage for this field is:
Length = Col->Width * Col->Size; |
The switch statement:
switch( Machine_ID )
{ |
has as many cases as host machines supported. Consider the case construct for the SUN SPARCstation code, which converts from SPARC format to IBM-PC format:
case MACH_IBMPC:
switch( Col->Type )
{
case SSFF_SHORT:
{
int HLen = Length / 2;
char Val;
for (loop = 0; loop < HLen; loop++, Data += 2)
{
Val = Data[0];
Data[0] = Data[1];
Data[1] = Val;
/
/
break;
case SSFF_LONG:
case SSFF_FLOAT:
{
int QLen = Length / 4;
char Val;
for (loop = 0; loop < QLen; loop++, Data += 4)
{
Val = Data[0];
Data[0] = Data[3];
Data[3] = Val;
Val = Data[1];
Data[1] = Data[2];
Data[2] = Val;
/
/
break;
case SSFF_DOUBLE:
fprintf(stderr,
"Convert_Data:: SPARC to IBM-PC double not supported...\n");
exit(1);
break;
/ |
Conversions of double elements from the SPARCstation to the PC are not supported. Conversion of short elements is simple --- reverse the byte order. The situation with long and float elements is the same, except four bytes must be reversed instead of two.
The first step with supporting conversion to or from another machine is to know how the data changes from one format to the other. In most cases it is a simple matter of shuffling the byte order.
void Read_Convert_Data( int Machine_ID, char *Data,
struct SSFF_Column *Col ) |
This function is called immediately after raw binary data has been read from the file. This routine converts the data from the format that it was saved in (host machine Machine_ID) to the native format for the current host.
This function is generally identical to Write_Convert_Data() as most conversions simply require a byte order reversal. The two functions are kept separate, though, to cater for more complicated cases where shuffling the bytes is not enough.
The native SSFF routines handle data stored in the SSFF format. This section documents the important routines, and explains how the data is handled. The file containing the source code is ssff.c.
The SSFF Write_Open() and Read_Open() routines make heavy use of the Parse_Var_Line() function. This function parses the generic variable definitions and the field definitions, and stores the appropriate information in the SSFF_Header structure that identifies a particular file. Special code is required for the three special variables that do not need a type: Start_Time, Record_Freq and Machine. Only one line is considered each time. If it is a special variable, then the data is read and the appropriate fields of the SSFF_Header structure are filled in. If the line indicates a new field or a new generic, then space for the item is malloc'd and it is joined to the end of the appropriate linked list.
The function Have_All_Entries() returns TRUE if a header contains valid information for the Machine and Record_Freq fields, and also has at least one field for storing or reading data. Compute_Column_Sizes() looks at each defined field in turn, allocates enough memory for each (taking the maximum number of records to be read or written each time into account), and sets the field One_Rec_Size to the correct value for the file format. Note that there are effectively two record buffers. The first is associated with each field, each has its own allocated space for storing data. The second is associated with the file as a whole. Data must be copied from each field's space into the file's data record before it is written, and to each field's space after it is read. The functions SSFF_Write() and SSFF_Read() deal with this internally.
The SSFF_Write_Open() function must allocate memory for the SSFF_Header structure before it can do anything else. The header variables are then cleared, and each variable and column definition in Col_List and Var_List are interpreted by Parse_Var_Line(). This routine stores the identification string, as defined by the macro ID_STRING in ssff.h to be "SSFF -- (c) SHLRC", at the very start of the output file. An SSFF file is identified by the presence of this string at the start of the header information. The generic and column definitions are then written out to the file in ascii. The header terminates with END_STRING, defined as a series of dashes.
If Have_All_Entries() is successful, the data for each column buffer and the file buffer is allocated using Compute_Column_Sizes() and the machine number is retrieved using Establish_Machine().
SSFF_Read_Open() works in a similar fashion, except the data for the variable and column definitions is read from the file. The string ID_STRING is looked for at the start of the file to make sure that the data stored is in SSFF format.
SSFF_Write() writes a number of binary records to the output file. The data is copied from each field buffer to the record buffer, and Write_Convert_Data() is called to make sure that the output is in the correct format. SSFF_Read() works in a similar fashion -- it calls Read_Convert_Data() to get each field's data into the correct format after the data has been read from disk.
SSFF_Seek() uses the ftell() position saved by SSFF_Write_Open() or SSFF_Read_Open(), combined with the One_Rec_Size field of SSFF_Header to fseek() to the correct record. SSFF_Close() frees up all record buffers, and closes the file.
If USE_ESPS is defined in ssff.h, then the ESPS support code will be incorporated into the SSFF routines. This is effectively a fall back option. If the file is not found to be in SSFF format, then the ESPS routines are tried.
The file my_esps.c contains the bulk of the code necessary to provide ESPS support. The routines in this file are responsible for transparently mapping the ESPS header and data structures onto the appropriate SSFF ones. This way, ESPS files can be handled identically to SSFF files.
int ESPS_Write_Open( struct SSFF_Header *Hdr, char *Var_List[] ) |
This function is called by SSFF_Read_Open() if the file is not in SSFF format. It opens an ESPS file, and translates the Var_List information into ESPS format. Note that the SSFF_Header structure includes fields to store the pointer to the ESPS header, and the ESPS record buffer.
The ESPS function add_fea_fld() must be called for each column. The column list does not need to be passed to this function because SSFF_Write_Open() has already handled it (the Hdr pointer contains the field Cols which allows the column list to be accessed, as discussed before). The add_fea_fld() routine allocates an ESPS field in the file. The special variables are added, along with the generics, using the ESPS functions add_genhd_d(), add_genhd_c(), add_genhd_i. The ESPS header is then written to disk using write_header().
int ESPS_Read_Open( struct SSFF_Header *Hdr, int Num_Recs, char *Filename ) |
Opens an ESPS file for reading, and interprets the header data. The list of ESPS fields is retrieved via the FEA header, and the information is converted into a string to allow Parse_Var_Line() to interpret it. A similar process makes the generics available to the SSFF header. Note that the ESPS variable start_time is converted to Start_Time, and record_freq is converted to Record_Freq.
Note that this function is called by the SSFF_Read_Open() routine if the file is not in SSFF format.
int ESPS_Write( struct SSFF_Header *Hdr, int Num_Recs ) |
This routine is called by SSFF_Write() if the file has been opened by ESPS_Write_Open(). It uses the ESPS function put_fea_rec() to write the data from the ESPS record buffer to the disk. Note that the data must be copied from each field into the buffer.
int ESPS Read( struct SSFF_Header *Hdr, int Num_Recs ) |
This is called by SSFF_Read() if the file was opened by ESPS_Read_Open(). It uses get_fea_rec() to read data from the ESPS file into the record buffer. The data must then be copied into the buffer for each field.
It is possible to extend SSFF in two ways:
Add support for data conversions from and to more machines. This is quite simple to do. It involves adding code to the machine.c file to allow for the extra machine architectures.
Add support for more file formats. Support for further file formats can be added in a similar way to how the ESPS support is implemented. The code to do this must be capable of translating the header and field data from the new format into the structure expected by the SSFF routines.