SAMPLE RESPONSE FILE CODE Introduction The sample code in this package shows how CID response files can be read and parsed, and how response file processing is used in a CID installation program. The samples are organized like this: o Low-level file and parsing APIs. These routines handle the basic file and parsing services needed to use response files. They can be used to build higher-level services. These routines make only minimal assumptions about the nature of the response files. The routines can be modified for another set of assumptions, but for this example the assumptions are - Blank lines and lines in which the first non-blank character is '*' or ';' (asterisk or semicolon) are comment lines. Their contents are ignored. - Response file data lines consist of a _keyword_ and a _value_, seperated by an equal ('=') sign. The _keyword_ begins with the first non-blank character in the line and ends with the last non-blank character before the equal sign. Blank characters and equal signs are not valid in a keyword. The _value_ begins with the first non-blank character to the right of the equal sign and ends with the last non-blank character in the line. There can be only one keyword/value pair per line. - _Values_ can be of two types: _strings_ and _lists_. A _string_ is any character sequence to the right of the equal sign. The only illegal string is a single left parenthesis '('. A _list_ is a sequence of keyword/value pairs. The start of a list is indicated by a single left parenthesis to the right of the equal sign. The end of a list is indicated by a right parenthesis on a line by itself. The following is an example of a keyword with a _list_ value: MyList = ( ListValue.1 = orange ListValue.2 = red MyImbeddedList = ( ImbeddedListValue.1 = Cabbage ImbeddedListValue.2 = Sprouts ImneddedListValue.3 = Kale ) ListValue.3 = green ) These low-level APIs are discussed in detail under "RFIO Routines" below. [Introduction] 1 o Higher-level APIs for processing response files. These routines use the low-level APIs to provide a simple interface for application programs. They make further assumptions. Again, the routines can be modified to change these assumptions. - The keyword 'include' (in any case) has special meaning. When it is found in a response file, the value (a _string_) is the name of another response file that is to be immediately opened and processed. If the response file A.RSP contains the lines keywordA1 = value1 keywordA2 = value2 include = B.RSP keywordA3 = value3 and the response file B.RSP contains the lines keywordB1 = XXX keywordB2 = ZZZ then the high-level APIs will process the response files as if they were a single file containing keywordA1 = value1 keywordA2 = value2 keywordA3 = value3 keywordB1 = XXX keywordB2 = ZZZ If a fully-qualified path is specified for the _value_, then that file is used. If a relative path is specified, it is searched for on The current working directory Each path in the current environment PATH variable Each path in the current enviornment DPATH variable - Errors are to be logged to a certain log file from a certain message file. The error handling assumptions are discussed in more detail under "Error Handling" below. The high-level APIs are discussed in detail under "HLRFIO Routines" below. o Sample applications. Three complete programs that use the APIs discussed above are provided. They are - SAMPLE1. This is a very simple application that creates a file to put Response File information into the process environment. - SAMPLE2. This is a more complicated example that enforces some common Response File rules and deals with _list_ type values. - SAMPLE3. This example deals with Named Lists, a technique that allows response file (as defined above) to handle variable-length tables. [Introduction] 2 o A complete CID installation program, written in REXX. This program is based on working code, and can be used as a template for a CID install program. Unlike the samples discussed above, however, it will probably not run as-is on your workstation. It uses several programs that are not included with this package of samples. For the most part, you would need to write your own versions of these programs anyway. Two of the programs are included in this package for reference. The are - GETBOOTD. This little program returns an integer that represents the drive from which the workstation was last booted. - CHKLVL. This program checks the release level of programs that record their release information in ibmlvl.ini. o A make file. [Introduction] 3 HLRFIO Routines The high-level Response File APIs provide a simple interface to response files that meet the assumptions listed above. Many response file programs need only two of the routines -- RFOpen() and RFGetNextKeywordInFile(). The routines are all in module HLRFIO.C (and HLRFIO.OBJ) and are collectively referred to as the "HLRFIO routines." The HLRFIO routines fall into three categories: - File handling routines RFOpen() opens a response file and readies it for processing. RFClose() closes the response file. Normally it is not necessary to call this routine. - Response File parsing routines RFGetNextKeyword() returns the next keyword/value pair. RFGetNextKwdInList() returns the next keyword/value pair from a _list_ type value. - List handling routine RFCopyList() makes a memory copy of a _list_ value. To use any of the HLRFIO routines in your program, include the header file hlrfio.h and link your program with HLRFIO.OBJ. UINT RFOpen(char *pszFilename) This routine opens the file pszFilename as a response file. It returns 0 (RFH0) for success, 3 (RFHERR) for failure. Use this routine to open the first or 'main' response file. Subsequent opens of included response file are done under the covers and do not require your any action by your program. Example: rc = RFOpen("My.rsp"); if (rc == RFH0) UINT RFClose(void) This function closes a response file. RFClose is not required unless the caller wants to free resources (memory and file handles) in use by rfio. A call to RFOpen automatically closes down any previously open Response File, so there is no need to call RFClose() between Response Files. This function always returns RFH0. [HLRFIO Routines] 4 UINT RFGetNextKeyword(char **ppszKeyword, char **ppszValue, UINT *puiType) This function stores pointers to the next keyword and value, and stores an indicator of the type of value (string or list) (RFHLIST and RFHSTRING respectively). Do not issue a free() against *ppxzKeyword or *ppszValue. If you need a copy of the keyword and/or value, make one. The values will be erased and the space they take up will be freed on the next call to RFGetNextKeyword(). This function returns RF0 as long as everything is OK, RFEOF when the last keyword and value have already been returned, or RFERR when a fatal error has occured. Example: char *keyword, *value; unsigned int type; while (RFGetNextKeyword(&keyword, &value, &type) == RFH0) UINT RFGetNextKwdInList(char **ppszStart, char **ppszKeyword, char **ppszValue, UINT *puiType) This functions parses out the keywords and values from inside a _list_ value. The variable *ppszStart keeps track of where you are within the list. Before the first call, set *ppszStart to the address of the list; on subsequent calls, the value will be automatically updated. Otherwise, this call works like RFGetNextKeyword, returning the next keyword, value and type. Like RFGetNextKeyword, it returns RF0 as long as everything is OK, RFEOF when the last keyword and value have already been returned, or RFERR when a fatal error has occured. Example: char *keyword, *value; char *listPosition, *listKeyword, *listValue; char *listCopy; unsigned int type, listType; rc = RFGetNextKeyword(&keyword, &value, &type); if (rc == RFH0) if (type = RFHLIST) { listPosition = listCopy; while (RFGetNextKwdInList(&listPosition, &listKeyword, &listValue, &listType); < process the keyword/value pair from the list> } [HLRFIO Routines] 5 VOID *RFCopyList(void *from) This routine makes a copy of the _list_ value pointed to by "from". The copy is persistant. The memory it takes up can be released by issuing a free() on the value returned from this function. Take care that the "from" value is really a _list_ type value. The routine makes a copy of everything from "from" to the next occurance of two adjacent null characters. The reason for having this routine is that _values_ returned from RFGetNextKeyword() are not permanent. The memory they occupy is freed on the next call to RFGetNextKeyword or RFGetNextKwdInList. So if we want to manipulate the list, we need to make our own private copy. Example: char *keyword, *value; char *listPosition, *listKeyword, *listValue; char *listCopy; unsigned int type, listType; rc = RFGetNextKeyword(&keyword, &value, &type); if (rc == RFH0) if (type = RFHLIST) { listCopy = RHCopyList(value); // Make private copy of list listPosition = listCopy; while (RFGetNextKwdInList(&listPosition, &listKeyword, &listValue, &listType); < process the keyword/value pair from the list> free(listCopy); // Free space used for private copy } void *RFMergeLists(void *List1, void *List2) This routine merges two _list_ type values into a single list. The two lists passed to this routine should be persistant copies. The resulting list is also persistant. It may be freed with a free() call when no longer needed. When the lists are merged, the routine assumes that List2 contains the most current information. All the contents of List2 are copied to the merged list, then, for any keywords in List1 that do not also appear in List2, the keyword and value are copied to the merged list. Imbedded lists are not merged recursively. Any list value in List2 will replace the corresponding imbedded list from List1. [HLRFIO Routines] 6 RFIO Routines The low-level Response File APIs provide a more flexible but more complicated way to manipulate Response Files. Their main purpose is to provide building blocks for higher level code like the HLRFIO routines. These routines are all in module RFIO.C and are referred to as the RFIO Routines below. Because HLRFIO uses the RFIO routines, if you need to change any of the assumptions about response file structure you will probably need to make changes to the RFIO routines. The RFIO Routines fall into three catagories: - File Open and Close Routines RF_Open_File() opens and returns a handle for a response file. RF_Close_FIle() closes a response file and returns the previous handle, if any. - Routines for handling included response files RF_Create_Root_Ise() creates a structure used to control included response file. It is called once, before the first RF_Open_File(). RF_Get_Current_File_Name() returns the name of the current active response file. RF_Get_Current_RFHANDLE() returns the handle of the current active response file. - Sequential File I/O routines RF_Get_Next_Kwd_In_File() extracts the next keyword/ value pair from the current file. RF_Get_Next_Kwd_In_List() extracts the next keyword/ value pair from a _list_ type value. RF_Get_Next_Wanted_Keyword_In_File() extracts the next occurance of a particular keyword or keywords in the file. To use any of the RFIO routines in a program, include the header file rfioe.h and link to RFIO.OBJ. RFHANDLE RF_Open_File ( char *filename, // The name of the resp file RFISP root_isp // Pointer to the root of the // include stack int *DosRc // Where to put DOS rtrn code ); [RFIO Routines] 7 RF_Open_File returns a handle that can be used with the other RF commands on success, or (RFHANDLE)NULL when there is an error. In the case of an error, errno is put at *DosRc; DosRc is unchanged if the operation is successfull. filename points to an asciiz string containing the path and name of the response file. RF_Open_File does not presume a particular file suffix, so you have to give the whole thing i.e. \sd\myrf.rsp RF_Open_File first tries DosFindFirst on the file spec. If it is not found, and the filespec is relative, then each unit of DPATH is searched. If still not found, each unit of PATH is searched. The file name is allowed to be ambiguous, but only the first occurance found is returned. root_isp is a pointer to a structure of type RFIS, which is used to handle the imbedding of included files. A call to RF_Open_File preserves the handle of the file previously opened by RF_Open_File when it returns the new handle. RF_Close_File returns the previous handle, it there was one. So RF_Open and RF_Close can be used together to painlessly process imbedded references to other files. RF_Open_File checks for include file loops. If an open is issued for file that is already in the include stack, it will be ignored; the current RFHANDLE will be returned with no error indication. The question arises what should the caller do if she is trying to open an imbed file and NULL (file not found) is returned. Normally, this will be a fatal error. If the caller wants to recover and resume processing, however, she can call RF_Get_Current_RFHANDLE() to get the handle of the file that included the imbed; then resume processing. RFHANDLE RF_Close_File ( RFHANDLE rfh // Value returned from open RFISP root_isp // Pointer to root of include // stack ); RF_Close_File closes the response file. rfh is the RFHANDLE to be close. Close looks in the include stack, and closes the file associated with rfh and all files below it on the stack (normally, rfh will probably be at the bottom of the stack). root_isp is a pointer to the root entry of the include stack. RF_Close_File returns the RFHANDLE that was in effect when rfh was opened, or (RFHANDLE)NULL if rfh was the first file opened. RFISP RF_Create_Root_ISE (void); This routine creates the root imbed (a.k.a. include) stack entry. A user of RFIO calls it exactly once, before the exection of the first RF_Open_File call. It returns a value passed to all subsequent Open and Close calls. On an out-of-memory condition, it returns (RFISP)NULL. [RFIO Routines] 8 char *RF_Get_Current_File_Name( RFISP root_isp; ); This routine returns a pointer to the full name by which the currently active response file was found. You need to free() the string after you use it. If there is no response file currently open, or if there is an out-of-memory condition, NULL is returned. char *RF_Get_Current_RFHANDLE( RFISP root_isp; ); This routine returns the RFHANDLE of the currently active response file. If there is no response file currently open, NULL is returned. int RF_Get_Next_Kwd_In_File ( RFHANDLE rfh, // Value returned from open char **kwp, // Where to put the pointer // to the key word. char **valp, // Where to put the pointer // to the value. int *valtypep // Where to put the type of // value ); // (0 = string; 1 = list) This function reads the next response file entry, parses it, creates a place to put the pieces, and puts the pieces there. It returns pointers to the keyword string and the value string; both the strings are null-terminated. Comment lines and blank lines are skipped. In the case of a list, a pointer to the whole list is returned. rfh is the handle returned from the RF_Open_File call. kwp is the address of a character pointer where the routine will put the address of the keyword string. valp is the address of a character pointer where the routine will put the address of the value string. valtypep is a pointer to an integer where the routine will put the type of value returned. 0 if a simple value string, or 1 if a list. Returns RF0 to indicate success RFOOS to indicate an out-of-memory condition RFEOF to indicate end-of-file RFERR if their is an I/O error or other DOS error. The OS error code is stuffed at *valtypep. RFSYNTAX to indicate a Response File syntax error You can get the file name with a call to RF_Get_Current_File_Name(). [RFIO Routines] 9 The offending line at *valp, and the line number at *type. int RF_Get_Next_Kwd_In_List( char **start, // val returned from // RF_Get_Next_Kwd_In_File() char **kwp, // Where to put the pointer // to the key word. char **valp, // Where to put the pointer // to the value. int *valtypep // Where to put the type of // value ); // (0 = string; 1 = list) This is like RF_Get_Next_Kwd_In_File() except that it works on lists in memory. Instead of an RFHANDLE, it takes a double char pointer that keeps track of where it is in the list. It is used to sequentially parse list values returned from RF_Get_Next_Kwd_In_File(). start is a pointer to a char pointer. On the first invocation, the caller must set it to the valp parameter returned by RF_Get_Next_Kwd_In_File(). On subsequent calls, it is a value set by this routine. The caller should not modify this value after setting it to valp. Note that nested lists are supported. This routine returns the same values as RF_Get_Next_Keyword_In_List. When RFEOF is returned, it really means that end-of-list has been reached. int RF_Get_Next_Wanted_Kwd_In_File( RFHANDLE rfh, // Value returned from open char **kwdlist,// A pointer to an array of // character pointers. Each // element of the array // points to a string that // contains the name // of a keyword of interest. char **kwp, // Where to put the pointer // to the key word. char **valp, // Where to put the pointer // to the value. int *valtypep // Where to put the type of // value ); // (0 = string; 1 = list) This routine works just lie RF_Get_Next_Kwd_In_File() except that you can restrict the keywords returned to those you specify. The keywords to be returned are pointed to by kwdlist. This variable should point to a vector of pointers to strings that contains the names of keywords of interest. The strings must be asciiz; the last element in the vector of pointers must be NULL. Here's an example: [RFIO Routines] 10 char *kwdvector[] = { "kwd1", "keyword2", "kword3", NULL } while (RF_Get_Next_Wanted_Kwd_In_File(rfh, kwdvector, kwp, valp, typep) != RFEOF) { free(kwp); free(valp); } This routine returnes the same values as RF_Get_Next_Wanted_Kwd_In_File. RFEOF means the last matching keyword has already been returned. Error Handling The part of this sample code that is least likely to suit your needs is the error handling. The code that handles errors is in a seperate module, CIDLOG.C. The interface to the error handling routine is fairly standard. The call is CID_Log(MessageNumber, ...) where a variable number (0 to 10) pointers to asciiz strings can be passed as optional parameters. But message numbers and the behavior of the error handling routine will probably have to change for your particular application. The message numbers are defined in hlrfio.h. Suggested message text is in respfile.msg (source in respfile.txt). Sample Code Files in This Package The following files of sample code are included. makefile A make file for all the code in this package. When run, it generates respfile.msg sample1.exe sample2.exe sample3.exe [Files in This Package] 11 getbootd.exe chklvl.exe respfile.msg An OS/2 message file containing the error messages emitted by the Response File APIs. respfile.txt Source for respfile.msg cidlog.c Source for the error logging routine. cidlog.h Header file for the error logging routine. findfile.c Source for the routine that searches for response files across the PATH and DPATH. findfile.h Corresponding header file. hlrfio.c Source for the high-level Response File APIs hlrfio.h Corresponding header file. rfio.c Source for the low-level Response File APIs rfio.h Corresponding header file. sample1.c A simple example using the high level APIs to process a response file. When compiled and linked, it is a working program. sample1.def Linker def file for sample1 sample2.c A more complex example. Also a working program. sample2.def Linker def file for sample2 sample3.c A more complex example yet. Also a working program. sample3.def Linker def file for sample3 sampinst.cmd A REXX command file that implements a full CID install getbootd.c A program to find out what drive was last booted from. getboot.def chklvl.c A program to find out the installed level of a package. chklvl.def test.rsp Test response file to be used with the sample programs. [Files in This Package] 12