2.6 RMS Example Program

The example program in this section uses RMS functions to maintain a simple employee file. The file is an indexed file with two keys: social security number and last name. The fields in the record are character strings defined in a structure with the tag record.

The records have the carriage-return attribute. Individual fields in each record are padded with blanks for two reasons. First, because RMS requires that the key fields be a fixed length and occur in a fixed position in each record, key fields must be padded in some way. The example program pads short fields; its use of the space character for padding is arbitrary. Second, the choice of blank padding (as opposed to null padding) allows the file to be printed or typed without conversion. Note that both the position and size of the key are attributes of the file, not of each I/O that gets done.

The program does not perform range or bounds checking. Only the error checking that shows the mapping of DEC C to RMS is performed. Any other errors are considered fatal.

The program is divided into the following sections:

To run this program, perform the following steps:

  1. Create a source file. The name of the source file in this example is RMSEXP.C. For more information about creating source files, see Chapter 1.

  2. Compile the source file with the following command:
    $ CC  RMSEXP<Return>
    

    For more information about the compiling process, see Chapter 1.

  3. Link the program with the following command:
    $ LINK RMSEXP <Return>
    

    For more information about the linking process, see Chapter 1.

  4. Because the program expects command-line arguments, it must be defined as a foreign command. You can do this with the following command line:
    $ RMSEXP :== $device:[directory]RMSEXP<Return>
    

    The identifier device is the logical or physical name of the device containing your directory; the identifier directory is the name of your directory. The device name must be preceded by the dollar sign ($) to be recognized as a foreign command by the DCL interpreter.

  5. Run the program using the following foreign command:
    $ RMSEXP  filename<Return>
    

The complete listing of the sample program follows. The listing is broken into sections and shown in Examples 2-1 through 2-9. Notes on each section are keyed to the numbers in the listing.

Example 2-1 shows the external data declarations and definitions.

Example 2-1 External Data Declarations and Definitions

/* This segment of RMSEXP.C contains external data        *
 * definitions.                                           */

 #include <rms.h>
#include <stdio.h>
#include <ssdef.h>
#include <string.h>
#include <stdlib.h>
#include <starlet.h>

 #define DEFAULT_FILE_EXT        ".dat"

#define RECORD_SIZE              (sizeof record)
#define SIZE_SSN                 15
#define SIZE_LNAME               25
#define SIZE_FNAME               25
#define SIZE_COMMENTS            15
#define KEY_SIZE                 \
(SIZE_SSN > SIZE_LNAME ? SIZE_SSN: SIZE_LNAME)

 struct FAB fab;
struct  RAB rab;
struct  XABKEY primary_key,alternate_key;

 struct
   {
      char     ssn[SIZE_SSN], last_name[SIZE_LNAME];
      char     first_name[SIZE_FNAME],
               comments[SIZE_COMMENTS];
   }  record;

 char response[BUFSIZ],*filename;

 int rms_status;

void open_file(void);
void type_options(void);
void pad_record(void);
void error_exit(char *);
void add_employee(void);
void delete_employee(void);
void type_employees(void);
void print_employees(void);
void update_employee(void);
void initialize(char *);

Key to Example 2-1:

  1. The <rms.h> header file defines the RMS data structures. The <stdio.h> header file contains the Standard I/O definitions. The <ssdef.h> header file contains the system services definitions.

  2. Preprocessor variables and macros are defined. A default file extension .DAT is defined.

    The sizes of the fields in the record are also defined. Some (such as the social security number field) are given a constant length. Others (such as the record size) are defined as macros; the size of the field is determined with the sizeof operator. DEC C evaluates constant expressions, such as KEY_SIZE, at compile time. No special code is necessary to calculate this value.

  3. Static storage for the RMS data structures is declared. The file access block, record access block, and extended attribute block types are defined by the <rms.h> header file. One extended attribute block is defined for the primary key and one is defined for the alternate key.

  4. The records in the file are defined using a structure with four fields of character arrays.

  5. The BUFSIZ constant is used to define the size of the array that will be used to buffer input from the terminal. The file-name variable is defined as a pointer to char.

  6. The variable rms_status is used to receive RMS return status information. After each function call, RMS returns status information as an integer. This return status is used to check for specific errors, end-of-file, or successful program execution.

The main function, shown in Example 2-2, controls the general flow of the program.

Example 2-2 Main Program Section

/* This segment of RMSEXP.C contains the main function    *
 * and controls the flow of the program.                  */

 main(int argc, char **argv)
{
    if (argc < 1 || argc > 2)
      printf("RMSEXP - incorrect number of arguments");
   else
      {

         printf("RMSEXP - Personnel Database \
                 Manipulation Example\n");

          filename = (argc == 2 ? *++argv : "personnel.dat");
          initialize(filename);
          open_file();


         for(;;)
            {
                printf("\nEnter option (A,D,P,T,U) or \
? for help :");

               gets(response);
               if (feof(stdin))
                  break;
               printf("\n\n");

                switch(response[0])
                  {
                     case 'a': case 'A':  add_employee();
                                          break;

                     case 'd': case 'D':  delete_employee();
                                          break;

                     case 'p': case 'P':  print_employees();
                                          break;

                     case 't': case 'T':  type_employees();
                                          break;

                     case 'u': case 'U':  update_employee();
                                          break;

                     default:             printf("RMSEXP - \
                                                  Unknown Operation.\n");

                     case '?': case '\0':
                                          type_options();
                  }
            }

          rms_status = sys$close(&fab);

          if (rms_status != RMS$_NORMAL)
                     error_exit("$CLOSE");
      }
}

Key to Example 2-2:

  1. The main function is entered with two parameters. The first is the number of arguments used to call the program; the second is a pointer to the first argument (file name).

  2. This statement checks that you used the correct number of arguments when invoking the program.

  3. If a file name is included in the command line to execute the program, that file name is used. If a file extension is not given, .DAT is the file extension. If no file name is specified, then the file name is PERSONNEL.DAT.

  4. The file access block, record access block, and extended attribute blocks are initialized.

  5. The file is opened using the RMS sys$open function.

  6. The program displays a menu and checks for end-of-file (the character Ctrl/Z).

  7. A switch statement and a set of case statements control the function to be called, which is determined by the response from the terminal.

  8. The program ends when Ctrl/Z is entered in response to the menu. At that time, the RMS sys$close function closes the employee file.

  9. The rms_status variable is checked for a return status of RMS$_NORMAL. If the file is not closed successfully, then the error-handling function terminates the program.

Example 2-3 shows the function that initializes the RMS data structures. See the RMS documentation for more information about the file access block, record access block, and extended attribute block structure members.

Example 2-3 Function Initializing RMS Data Structures

/* This segment of RMSEXP.C contains the function that    *
 * initializes the RMS data structures.                   */

void initialize(char *fn)
{

    fab = cc$rms_fab;               /* Initialize FAB     */
   fab.fab$b_bks =  4;
   fab.fab$l_dna =  DEFAULT_FILE_EXT;
   fab.fab$b_dns =  sizeof DEFAULT_FILE_EXT -1;
   fab.fab$b_fac =  FAB$M_DEL | FAB$M_GET |
                    FAB$M_PUT | FAB$M_UPD;
   fab.fab$l_fna =  fn;
   fab.fab$b_fns =  strlen(fn);
    fab.fab$l_fop = FAB$M_CIF;
   fab.fab$w_mrs =  RECORD_SIZE;
   fab.fab$b_org =  FAB$C_IDX;
    fab.fab$b_rat = FAB$M_CR;
   fab.fab$b_rfm =  FAB$C_FIX;
   fab.fab$b_shr =  FAB$M_NIL;
   fab.fab$l_xab =  &primary_key;


    rab = cc$rms_rab;              /* Initialize RAB      */

   rab.rab$l_fab =  &fab;

    primary_key = cc$rms_xabkey;   /* Initialize Primary  *
                                    *  Key XAB            */
   primary_key.xab$b_dtp = XAB$C_STG;
   primary_key.xab$b_flg = 0;
    primary_key.xab$w_pos0 = (char *) &record.ssn -
                             (char *) &record;
   primary_key.xab$b_ref =  0;
   primary_key.xab$b_siz0 = SIZE_SSN;
   primary_key.xab$l_nxt =  &alternate_key;
   primary_key.xab$l_knm =  "Employee Social Security \
Number       ";

    alternate_key = cc$rms_xabkey; /* Initialize Alternate *
                                   *  Key XAB             */
   alternate_key.xab$b_dtp = XAB$C_STG;
    alternate_key.xab$b_flg = XAB$M_DUP | XAB$M_CHG;
   alternate_key.xab$w_pos0 =  (char *) &record.last_name -
                               (char *) &record;
   alternate_key.xab$b_ref =  1;
   alternate_key.xab$b_siz0 =  SIZE_LNAME;
    alternate_key.xab$l_knm =  "Employee Last Name \
               ";
}

Key to Example 2-3:

  1. The data structure variable cc$rms_fab initializes the file access block with default values. Some members have no default values; they must be initialized. Such members include the file-name string address and size. Other members can be initialized to override the default values.

  2. This statement initializes the file-processing options member with the create-if option. A file is created if one does not exist.

  3. This statement initializes the record attributes member with the carriage-return control attribute. Records are terminated with a carriage return/line feed when they are printed on the printer or displayed at the terminal.

  4. The data structure variable cc$rms_rab initializes the record access block with the default values. In this case, the only member that must be initialized is the rab$l_fab member, which associates a file access block with a record access block.

  5. The data structure variable cc$rms_xabkey initializes an extended attribute block for one key of an indexed file.

  6. The position of the key is specified by subtracting the offset of the member from the base of the structure.

  7. A separate extended attribute block is initialized for the alternate key.

  8. This statement specifies that more than one alternate key can contain the same value (XAB$M_DUP), and that the value of the alternate key can be changed (XAB$M_CHG).

  9. The key-name member is padded with blanks because it is a fixed-length, 32-character field.

Example 2-4 shows the internal functions for the program.

Example 2-4 Internal Functions

/* This segment of RMSEXP.C contains the functions that   *
 * control the data manipulation of the program.          */

void open_file(void)
{
    rms_status = sys$create(&fab);
   if (rms_status != RMS$_NORMAL &&
       rms_status != RMS$_CREATED)
      error_exit("$OPEN");

   if (rms_status == RMS$_CREATED)
      printf("[Created new data file.]\n");

    rms_status = sys$connect(&rab);
   if (rms_status != RMS$_NORMAL)
      error_exit("$CONNECT");
}

 void type_options(void)
{
   printf("Enter one of the following:\n\n");
   printf("A     Add an employee.\n");
   printf("D     Delete an employee specified by SSN.\n");
   printf("P     Print employee(s) by ascending SSN on \
line printer.\n");


   printf("T     Type employee(s) by ascending last name \
on terminal.\n");
   printf("U     Update employee specified by SSN.\n\n");
   printf("?     Type this text.\n");
   printf("^Z    Exit this program.\n\n");
}

 void pad_record(void)
{
   int      i;

   for(i = strlen(record.ssn); i < SIZE_SSN; i++)
      record.ssn[i] = ' ';
   for(i = strlen(record.last_name); i < SIZE_LNAME; i++)
      record.last_name[i] = ' ';
   for(i = strlen(record.first_name); i < SIZE_FNAME; i++)
      record.first_name[i] = ' ';
   for(i = strlen(record.comments);i < SIZE_COMMENTS; i++)
      record.comments[i] = ' ';
}

/* This subroutine is the fatal error-handling routine.   */

 void error_exit(char *operation)
{
   printf("RMSEXP - file %s failed (%s)\n",
           operation, filename);
   exit(rms_status);
}

Key to Example 2-4:

  1. The open_file function uses the RMS sys$create function to create the file, giving the address of the file access block as an argument. The function returns status information to the rms_status variable.

  2. The RMS sys$connect function associates the record access block with the file access block.

  3. The type_options function, called from the main function, prints help information. Once the help information is displayed, control returns to the main function, which processes the response that is typed at the terminal.

  4. For each field in the record, the pad_record function fills the remaining bytes in the field with blanks.

  5. This function handles fatal errors. It prints the function that caused the error, returns an OpenVMS error code (if appropriate), and exits the program.

Example 2-5 shows the function that adds a record to the file. This function is called when 'a' or 'A' is entered in response to the menu.

Example 2-5 Utility Function: Adding Records

/* This segment of RMSEXP.C contains the function that    *
 * adds a record to the file.                             */

void add_employee(void)
{
    do
      {
         printf("(ADD)   Enter Social Security Number:");

         gets(response);

      }
   while(strlen(response) == 0);

   strncpy(record.ssn,response,SIZE_SSN);

   do
      {
         printf("(ADD)    Enter Last Name:");

         gets(response);
      }
   while(strlen(response) == 0);

   strncpy(record.last_name,response,SIZE_LNAME);

   do
      {
         printf("(ADD)    Enter First Name:");

         gets(response);
      }
   while(strlen(response) == 0);

   strncpy(record.first_name,response,SIZE_FNAME);

   do
      {
         printf("(ADD)    Enter Comments:");

         gets(response);
      }
   while(strlen(response) == 0);

   strncpy(record.comments,response,SIZE_COMMENTS);

    pad_record();

    rab.rab$b_rac = RAB$C_KEY;
   rab.rab$l_rbf =  (char *) &record;
   rab.rab$w_rsz =  RECORD_SIZE;

    rms_status = sys$put(&rab);
    if (rms_status != RMS$_NORMAL && rms_status !=
                     RMS$_DUP && rms_status != RMS$_OK_DUP)
      error_exit("$PUT");
   else
      if (rms_status == RMS$_NORMAL || rms_status ==
                                                 RMS$_OK_DUP)
         printf("[Record added successfully.]\n");
      else
         printf("RMSEXP - Existing employee with same SSN, \
not added.\n");
}

Key to Example 2-5:

  1. A series of do loops controls the input of information. For each field in the record, a prompt is displayed. The response is buffered and the field is copied to the structure.

  2. When all fields have been entered, the pad_record function pads each field with blanks.

  3. Three members in the record access block are initialized before writing the record. The record access member (rab$b_ rac) is initialized for keyed access. The record buffer and size members (rab$l_rbf and rab$w_rsz) are initialized with the address and size of the record to be written.

  4. The RMS sys$put function writes the record to the file.

  5. The rms_status variable is checked. If the return status is normal, or if the record has a duplicate key value and duplicates are allowed, the function prints a message stating that the record was added to the file. Any other return value is treated as a fatal error causing error_exit to be called.

Example 2-6 shows the function that deletes records. This function is called when 'd' or 'D' is entered in response to the menu.

Example 2-6 Utility Function: Deleting Records

/* This segment of RMSEXP.C contains the function that    *
 * deletes a record from the file.                        */

void delete_employee(void)
{
   int i;
    do
      {
         printf("(DELETE) Enter Social Security Number   ");
         gets(response);
         i =  strlen(response);
      }
   while(i == 0);

    while(i < SIZE_SSN)
      response[i++] = ' ';

    rab.rab$b_krf = 0;
   rab.rab$l_kbf =  response;
   rab.rab$b_ksz =  SIZE_SSN;
   rab.rab$b_rac =  RAB$C_KEY;

    rms_status = sys$find(&rab);

    if (rms_status != RMS$_NORMAL && rms_status != RMS$_RNF)
      error_exit("$FIND");
   else
      if (rms_status == RMS$_RNF)
         printf("RMSEXP - specified employee does not \
exist.\n");

      else
         {
             rms_status = sys$delete(&rab);
            if (rms_status != RMS$_NORMAL)
               error_exit("$DELETE");
         }
}

Key to Example 2-6:

  1. A do loop prompts you to type a social security number at the terminal and places the response in the response buffer.

  2. The social security number is padded with blanks.

  3. Some members in the record access block must be initialized before the program can locate the record. Here, the key of reference (0 specifies the primary key), the location and size of the search string (this is the address of the response buffer and its size), and the type of record access (in this case, keyed access) are given.

  4. The RMS sys$find function locates the record specified by the social security number entered from the terminal.

  5. The program checks the rms_status variable for the values RMS$_NORMAL and RMS$_RNF (record not found). A message is displayed if the record cannot be found. Any other error is a fatal error.

  6. The RMS sys$delete function deletes the record. The return status is checked only for success.

Example 2-7 shows the function that displays the employee file at the terminal. This function is called from the main function when 't' or 'T' is entered in response to the menu.

Example 2-7 Utility Function: Typing the File

/* This segment of RMSEXP.C contains the function that    *
 * displays a single record at the terminal.              */

void type_employees(void)
{
    int number_employees;

    rab.rab$b_krf = 1;

    rms_status = sys$rewind(&rab);
   if (rms_status != RMS$_NORMAL)
      error_exit("$REWIND");

    printf("\n\nEmployees (Sorted by Last Name)\n\n");
   printf("Last Name      First Name          SSN      \
          Comments\n");

   printf("---------      ----------          ---------\
          --------\n\n");
    rab.rab$b_rac = RAB$C_SEQ;
   rab.rab$l_ubf =  (char *) &record;
   rab.rab$w_usz =  RECORD_SIZE;

    for(number_employees = 0; ; number_employees++)
      {
         rms_status =  sys$get(&rab);
         if (rms_status != RMS$_NORMAL && rms_status !=
                                          RMS$_EOF)
            error_exit("$GET");
         else
            if (rms_status == RMS$_EOF)
               break;

         printf("%.*s%.*s%.*s%.*s\n",
                 SIZE_LNAME, record.last_name,
                 SIZE_FNAME, record.first_name,
                 SIZE_SSN, record.ssn,
                 SIZE_COMMENTS, record.comments);
      }

    if (number_employees)
      printf("\nTotal number of employees = %d.\n",
                number_employees);
   else
      printf("[Data file is empty.]\n");
}

Key to Example 2-7:

  1. A running total of the number of records in the file is kept in the number_employees variable.

  2. The key of reference is changed to the alternate key so that the employees are displayed in alphabetical order by last name.

  3. The file is positioned to the beginning of the first record according to the new key of reference, and the return status of the sys$rewind function is checked for success.

  4. A heading is displayed.

  5. Sequential record access is specified, and the location and size of the record is given.

  6. A for loop controls the following operations:

  7. This if statement checks for records in the file. The result is a display of the number of records or a message indicating that the file is empty.

Example 2-8 shows the function that prints the file on the printer. This function is called by the main function when 'p' or 'P' is entered in response to the menu.

Example 2-8 Utility Function: Printing the File

/* This segment of RMSEXP.C contains the function that    *
 * prints the file.                                       */

void print_employees(void)
{
   int  number_employees;
   FILE *fp;

    fp = fopen("personnel.lis", "w", "rat=cr",
                       "rfm=var", "fop=spl");
   if (fp == NULL)
      {
         perror("RMSEXP - failed opening listing \
file");

         exit(SS$_NORMAL);
      }

    rab.rab$b_krf = 0;

    rms_status = sys$rewind(&rab);
   if (rms_status != RMS$_NORMAL)
      error_exit("$REWIND");

    fprintf(fp,"\n\nEmployees (Sorted by SSN)\n\n");
   fprintf(fp,"Last Name      First Name       SSN      \
     Comments\n");

   fprintf(fp,"---------      ----------       ---------\
     --------\n\n");

    rab.rab$b_rac = RAB$C_SEQ;
   rab.rab$l_ubf =  (char *) &record;
   rab.rab$w_usz =  RECORD_SIZE;

    for(number_employees = 0; ; number_employees++)
      {
         rms_status =  sys$get(&rab);
         if (rms_status != RMS$_NORMAL &&
             rms_status != RMS$_EOF)
            error_exit("$GET");
         else
            if (rms_status == RMS$_EOF)
               break;

         fprintf(fp, "%.*s%.*s%.*s%.*s",
                 SIZE_LNAME,record.last_name,
                 SIZE_FNAME,record.first_name,
                 SIZE_SSN,record.ssn,
                 SIZE_COMMENTS,record.comments);
      }
    if (number_employees)
      fprintf(fp, "Total number of employees = %d.\n",
              number_employees);
   else
      fprintf(fp,"[Data file is empty.]\n");

    fclose(fp);
      printf("[Listing file\"personnel.lis\"spooled to \
SYS$PRINT.]\n");
}

Key to Example 2-8:

  1. This function creates a sequential file with carriage- return carriage-control, variable-length records. It spools the file to the printer when the file is closed. The file is created using the standard I/O library function fopen, which associates the file with the file pointer, fp.

  2. The key of reference for the indexed file is the primary key.

  3. The RMS sys$rewind function positions the file at the first record. The return status is checked for success.

  4. A heading is written to the sequential file using the standard I/O library function fprintf.

  5. The record access, user buffer address, and user buffer size members of the record access block are initialized for keyed access to the record located in the record structure.

  6. A for loop controls the following operations:

  7. The number_employees counter is checked. If it is 0, a message is printed indicating that the file is empty. If it is not 0, the total is printed at the bottom of the listing.

  8. The sequential file is closed. Since it has the spl record attribute, the file is automatically spooled to the printer. The function displays a message at the terminal stating that the file was successfully spooled.

Example 2-9 shows the function that updates the file. This function is called by the main function when 'u' or 'U' is entered in response to the menu.

Example 2-9 Utility Function: Updating the File

/* This segment of RMSEXP.C contains the function that    *
 * updates the file.                                      */

void update_employee(void)
{
   int i;
    do
      {
         printf("(UPDATE) Enter Social Security Number\
      ");

         gets(response);
         i =  strlen(response);
      }
   while(i == 0);

    while(i < SIZE_SSN)
      response[i++] = ' ';

    rab.rab$b_krf = 0;
   rab.rab$l_kbf =  response;
   rab.rab$b_ksz =  SIZE_SSN;
   rab.rab$b_rac =  RAB$C_KEY;
   rab.rab$l_ubf =  (char *) &record;
   rab.rab$w_usz =  RECORD_SIZE;

    rms_status = sys$get(&rab);


   if (rms_status != RMS$_NORMAL && rms_status != RMS$_RNF)
      error_exit("$GET");
   else
      if (rms_status == RMS$_RNF)
         printf("RMSEXP - specified employee does not \
exist.\n");

       else
         {
            printf("Enter the new data or RETURN to leave \
data unmodified.\n\n");

            printf("Last Name:");
            gets(response);
            if (strlen(response))
               strncpy(record.last_name, response,
                       SIZE_LNAME);

            printf("First Name:");
            gets(response);
            if (strlen(response))
               strncpy(record.first_name, response,
                       SIZE_FNAME);

            printf("Comments:");
            gets(response);
            if (strlen(response))
               strncpy(record.comments, response,
                       SIZE_COMMENTS);

             pad_record();

             rms_status = sys$update(&rab);
            if (rms_status != RMS$_NORMAL)
               error_exit("$UPDATE");

            printf("[Record has been successfully \
updated.]\n");
         }
}

Key to Example 2-9:

  1. A do loop prompts for the social security number and places the response in the response buffer.

  2. The response is padded with blanks so that it will correspond to the field in the file.

  3. Some of the members in the record access block are initialized for the operation. The primary key is specified as the key of reference, the location and size of the key value are given, keyed access is specified, and the location and size of the record are given.

  4. The RMS sys$get function locates the record and places it in the record structure. The function checks the rms_status value for RMS$_NORMAL and RMS$_RNF (record not found). If the record is not found, a message is displayed. If the record is found, the program prints instructions for updating the record.

  5. If you press the Return key, the record is placed in the record structure unchanged. If you make a change to the record, the new information is placed in the record structure.

  6. The fields in the record are padded with blanks.

  7. The RMS sys$update function rewrites the record. The program then checks that the update operation was successful. Any error causes the program to call the fatal error-handling routine.


Previous Page | Next Page | Table of Contents | Index