Introduction SDS is a library of calls, written in C but with interfaces to Fortran and C++, whose primary job is to describe arbitrarily structured data. As well as describing existing collections of data, the SDS interface provides ways of organising existing data and of requesting allocated memory for data which only exists as description. At the moment [and perhaps forever] SDS can describe data struc- tures that it cannot help generate: in particular data with size and type information embedded in them which are generated by spe- cial purpose hardware readout systems. Terms An Sds is a managed collection of objects and their descriptions. The objects may exist purely as descriptions or as arrays of one or more instantiations, and their storage locations may be dif- ferent. Datasets exist in two forms: a 'prototyping' stage when descriptions of objects are being added to the dataset: these ob- jects may or may not have memory allocated for them; and an 'as- sembled' stage when all memory that should be allocated has been. When assembled, the dataset is "closed" and objects may not be added or removed, but full access is given to manipulate the data themselves; in the proto state the data cannot be modified but the of the dataset may be. Here, the term refers to a object, which may range from 'primi- tive' types such as integer, float and character to complex data types such as C structures and structures with embedded size and type information. Examples of objects are: int i; int iarray[5]; struct foo { int a[20]; char adesc[32]; double asize; } bar[20]; struct Module { int Type; int DataSize; short Data[DataSize]; } Module[NumberOfModules]; This last case shows an array of objects with embedded size in- formation. Using a simple naming convention described more fully below, SDS can apply such descriptions to real data and present the user with the size information and object addresses. Data objects managed by a dataset may be stored with the SDS headers or may be marked 'disjoint': that is, their storage is not with the headers. In the latter case, SDS may be informed of the storage location by the programmer (having retrieved this in- formation from, for instance, a database) or the knowledge may be explicitly stored with the header so that loading or attaching to the object may be done automatically. Sds uses a number of : that is, opaque values acting as pointers into system tables which describe each of the components; they are analagous to file descriptors. The allows access to type information containing : - A type code, indicating if the described data is 'primitive' (eg SDS_INT, SDS_DOUBLE), 'primitive' with special interpretation (eg SDS_UNIX_TIME, which is in fact SDS_LONG but can be inter- preted in a special fashion), or compound - referring to a struc- ture. The typeH is the type code for such a compound data type, and accesses a hierarchy of type codes which finally resolve into primitives. - For each type code, a multiplicity. - For each type code, a definition name. Each object is associated with a typeH, and subobjects used to describe compuond objects may also need type handles. Thus, when Sds describes the C structure: struct Fred { int aint; float bfloat[20]; } Albert[3]; the typeH will access the information: - one integer, name aint - twenty floats, name bfloat Both of these names are referred to here as names. The name 'Fred' is also a definition name, but in Sds it is replaced by the typeH. By contrast, the name 'Albert' is an name, which is not part of the type description and is accordingly managed through the (see below). To formalise data structures which cannot be easily described by C, in particular when size and type information is embedded in the data themselves, naming conventions are used. A structure described by Sds (in pseudo-code) as : struct Fred { float scale_factor; short data_count; char data[MULTIPLICITY_ONE]; int checksum; } Albert[MULTIPLICITY_TWO]; can be scanned by Sds to pick up the multiplicity of the 'data' arrays in each element of the 'Albert' array from embedded infor- mation. Here, the strategy is to look for an array called 'data' with undetermined length after an [integer,short,byte] field called 'data_count' is encountered. Sds can then scan the described data and, if required, store pointer and multiplicity arrays with the header for subsequent fast access. Typically, the total storage size will determine the value of Albert's multipli- city. The (storeH) allows access to information detailing where an ob- ject or subobject is stored: - The storage name, for example a filename or the name of a shared memory partition. - the name of the host which controls the storage - an indicator of the storage type - SDS_FILE, SDS_SHARED_MEM, SDS_DATABASE etc. The sdsH allows access to dataset information: number of objects, name and timestamp of dataset and object information such as mul- tiplicities, structures and addresses; the sdsP refers to da- tasets whose contents - number and type of objects - may be changed. Many queries and manipulations may be asked of proto-sds datasets, but they will not respond to queries about the placing of objects they manage: this is to provide protection against ac- quiring addresses that may change due to Sds internals or copying of objects. Such protection will be given if decent programming practice is followed but inappropriate use of pointers whose validity is not guaranteed will cause problems. In general, the transformation from a proto dataset to an object-accessible da- taset should be followed by calls re-establishing the addresses of any objects used. The recordH and objectH allow access to object information (alignment, size, name, multiplicity, instantiation names and in- directly their associated typeH and storeH). The objectI points to the position of an object within a dataset: user objects start at number 1 [object 0 is the dataset directo- ry, and should not be directly accessed by the user]. In general, sds calls will return a long integer; a return of zero indicates an error or warning which may be investigated with the error han- dling routines detailed below. All handles are invalid if 0. NOTE: In what follows, 'Level 1' calls are those of immediate usefullness for basic use of SDS, whereas Level 2 are of secon- dary interest but still intended for the user. Internal calls are not described. int call_succeeded = sds_init(); Initialise the Sds system tables. Required. Has effect only on the first call. int call_succeeded = sds_reinit_enable(); Where a system may require a second initialisation, for instance a realtime system after partial failure, calling sds_reinit_enable() will allow a subsequent call to sds_init() to take effect. Note that only the first subsequent sds_init() will have effect; after that the normal behaviour returns. int call_succeeded = sds_fprint_def(FILE *stream, objectH); Print the structural definition of an object (ie names, types and structure levels) to the named output stream. int call_succeeded = sds_fprint_object(FILE *stream, objectH); Print the data contents of an object to the named output stream. An attempt is made to give reasonable format. int version = sds_version(); Returns a floating point version number of the form 2.3 sdsH = sds_open(storeH) Gets access to an existing dataset. You may now query it about what is there and where it is, but you do not yet have access to the data.... sdsH = sds_attach(storeH, int access_mechanism); Attach to a dataset; ie you are getting access to the actual stuff, through mechanisms such as local shared memory, mapping disk, or reflective memory links. sdsP = sds_create(char *name) Gets a new dataset prototype ready for filling with records/objects. The name given is the dataset name, which must be unique for the datasets currently accessed by a process. objectI = sds_add_oh(sdsP, objectH); Add an object to a dataset prototype; returns the object_index of the object within the dataset (Note that an object may be re- gistered in more than one dataset, in which case their indices are in general different.) int call_succeeded = sds_delete_oh(sdsP, objectH); Delete an object from a dataset prototype. sdsH = sds_make_public(sdsP, storeH); This call creates the Sds described by the input prototype; the dataset is now in principio accessible by other processes (although system permissions may regulate this). The call thus implies that some copying will be done: at least the Sds headers and any objects declared in memory must be copied to the new storage area. Data objects marked 'disjoint' will not be copied. The sdsH returned now allows access to all data pointers, but further manipulation of the dataset structure is illegal. sdsH = sds_make_private(sdsP); This call is similar to sds_make_public() in that a transform is made between a proto dataset whose structure may be changed to one where data may be manipulated but structure remains constant. In this case however objects in process memory remain there, and proto objects without allocated memory will have process storage created. sdsP = sds_protoize(sdsH); Moves a dataset to the prototype state so that the dataset stuc- ture may be changed. Attempts to obtain object addresses from sdsP or sdsH are now invalid. It cannot be assumed that objects whose addresses were found from the generating sdsH before proti- sation (yuk) will remain in these locations after further dataset manipulation. sdsH = sds_duplicate(sdsH, storeH); Duplicates the dataset sdsH to new storage. sdsH = sds_deep_duplicate(sdsH, storeH); Duplicates the dataset sdsH to new storage, including all 'dis- joint' objects. int call_succeeded = sds_discard_all(sdsH); Discard all system descriptive information about a dataset. int call_succeeded = sds_destroy_all(sdsH); Discard all system descriptive information about a dataset and the objects in it, having first free'd any Sds-system allocated memory in the objects. typeH = sds_make_th(struct typelist *tl); storeH = sds_make_sh(struct storeage *st); objectH = sds_new_object(th,int number,char *names[]); int call_succeeded = sds_oh_instantiations(objectH, storeH[], char *instatiation_names[]); int call_succeeded = sds_oh_struct_storage(objectH, storeH[], char *name = NULL); oh = sds_copy(storeH, objectI, pointer = NULL); Having open'd the dataset, you may now *copy* some or all of the objects (including architecture conversion if necessary). Space will be allocated unless 'pointer' has a non-null value, in which case data will be loaded starting at the pointer position: caveat programmer. objectH = sds_ohfromindex(sdsH, objectI); objectH = sds_ohfromname(sdsH, name); Find the appropriate object handle from the index or name of an object within a dataset. objectH = sds_ohlikename(sdsH, part_name,int occurrence); Find the occurence'th instance of an object within a dataset whose name contains 'part_name' as a substring. void *ohptr = sds_ohptr(objectH); Find the pointer to the data acessed through objectH. Note: this call will give an error if the objectH was found by querying a proto-dataset: default behaviour is to print out the error stack and stop. char *ohname = sds_ohname(objectH); Find the name of the data acessed through objectH. int multiplicity = sds_ohmult(objectH); Find the multiplicity of the data acessed through objectH - ie how many of each element - the array size. int sizeof = sds_ohsizeof(objectH); Find the size in bytes of one element of an object. The size could be undetermined; sds_error is then set to SDS_SIZE_UNDETERMINED and the error level set to SDS_WARNING; the size returned is 0. int sizeof = sds_thsizeof(th); Find the size in bytes of the data structure accessed by the type_handle. The size could be undetermined; sds_error is then set to SDS_SIZE_UNDETERMINED and the error level set to SDS_WARNING; the size returned is 0. int call_succeeded = sds_ohtstamp(int tstamp); Sets the time stamp of an object. int tstamp = sds_get_ohtstamp(objectH); Returns the time stamp of an object. int call_succeeded = sds_discard(objectH); Discard all system descriptive information about an object. int call_succeeded = sds_destroy(objectH); Free any Sds-system allocated memory in the object, then sds_discard() all system information. int descriptor_handle = sds_desc_handle(objectH); Get a handle to refer to subsequent analysis calls on an object. There are two main types of object analysis: linear and hierarch- ical. In each case repeated calls to the analysis routines re- turn information about the analysed objects. In linear analysis, a sequence of statements about the primitive elements that make up the object is made - eg 10 floats, 23 int, 120 char, 10 floats, 23 int, 120 char...... This is effected by sequential calls to int level = sds_linear(descH, struct level_description **ld.sp); In hierarchical elements, a series of index numbers refering to an array of descriptor structures is returned from int level = sds_hierarch(descH, struct level_description **ld); each of which give: The start address of the field Its name Its type_code The number of elements in the array (or 1). Its size in bytes. When no more fields remain for analysis, 0 is returned and the warning SDS_DONE_ANALYSIS is set. int call_succeeded = sds_close_dh(descH); Clean up all system stuff created for an analysis. Use of descH before it is reallocated with sds_desc_handle() will cause an er- ror; however sds_close_dh() is called automatically when an analysis sequence is run completion. Re-closing an already closed descH has no effect. int level = sds_find_level(descH, field_name, struct level_description **ld); Makes calls to sds_hierarch() until a match is made to the field named field_name. recordH = sds_begin_record(char *name); sds_record_entry(recordH, handle, int number, void *ptr, char *name); sds_begin_sub_record(rh, name) sds_end_sub_record(rh) sds_end_and_declare(recordH, sdsH); sds_destroy_record_def(recordH, int destroy_object); sds_print_record_def(recordH); void sds_stop_if_error(char *comment); Really crude. If there has been an Sds error, it prints out the error stack and does exit(1); otherwise it is quiet. int level = sds_push_error(int errcode, int errlevel, char *err- string); Push a new error onto the stack. We assume that errstring is zero terminated, and check to see if it is NULL. Returns the 'level' of the error in the stack. Filename and line information is added by the preprocessor from the defines __LINE__ and __FILE__. int error_code = sds_last_error(); Returns the last sds_error, which is 0 if no problems found. int error_code = sds_last_warning(); Returns the last sds_error at SDS_WARNING level, which is 0 if no problems found. void sds_perror(char *comment); Print out the top level comment, any filled parts of the error stack, and the last operating system error if there was one. void sds_output_proginfo(int truefalse); Default 0 == Do not print out line and file information. void sds_exit_on_fatal(int truefalse); Default 1 == An SDS_FATAL error causes exit(truefalse & 0xff); void sds_output_errors(int level); Default 0 == Do not output errors as they are registered; this is when you want to wait for a final catastrophe and then use sds_perror(), or when you may be able to take corrective action; in most programs sds_output_errors(SDS_FATAL) will be appropri- ate. sds_clear_errors(); Start with a clean slate. int error_code = sds_pop_error(int errlevel, char *errstring); void printoutprog(int i); Print out program info for the i'th entry on the stack void printout(int i); Print out the i'th entry on the stack Currently, error levels are: SDS_WARNING SDS_ERROR SDS_FATAL