OpenVMS Source-Code Demos

RMS_C_UG_EXAMPLE

//===============================================================================================================================
// file  :	RMS_C_UG_EXAMPLE.C
// title :	RMSEXP.C				(c) 2005 by HP
// source:	HP C User's Guide for OpenVMS Systems
//		Section 2.6 - RMS Example Program
//		http://h41379.www4.hpe.com/commercial/c/docs/5492profile_010.html
// edit  :	Neil Rieck (2015-06-08)
// ver who    when     what
// --- ---    -------- ----------------------------------------------------------------------------------------------------------
// 100 DEC    19900101	1. original effort for VMS on VAX
// 101 Compaq 19980101	1. republished by new owner of OpenVMS
// 102 HP     20020101	1. republished by new owner of OpenVMS
// 103 NSR    20150608	1. copied from the manuals then annotated for developer use
//			2. commented out two lines (see: xab$l_knm) which prevent this program from running a second time on this
//			   platform (HP C V7.3-010 on OpenVMS Alpha V8.4). This problem could be fixed by creating/using key-name
//			   buffers then using those rather than constants. Since the RMS manuals claim key-name is optional I'll
//			   just leave both xab$l_knm locations initialized to NULL					bf_103.3
//===============================================================================================================================
#define __NEW_STARLET 1									// recommended for Alpha and Itanium
//
//	include the usual stuff
//
#include <rms.h>
#include <stdio.h>
#include <ssdef.h>
#include <string.h>
#include <stdlib.h>
#include <starlet.h>
//
//	need some definitions
//
#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)
//
//	need some variables
//
struct  FAB	fab;			// file access block
struct  RAB	rab; 			// record access block
struct  XABKEY	primary_key,		// extended access block (for) Keys
		alternate_key;
//-----------------------------------------------------------------------------
//	record layout for file: personnel.dat
//
//	key#0 info:
//		CHANGES=no, DUPLICATES=no, TYPE=string
//		SEG0_LENGTH=15, SEG0_POSITION=0 (not segmented)
//		name = "Employee Social Security Number"
//		variable: ssn[SIZE_SSN]
//	key#1 info:
//		CHANGES=yes, DUPLICATES=yes, TYPE=string
//		SEG0_LENGTH=25, SEG0_POSITION=15 (not segmented)
//		name = "Employee Last Name"
//		variable: last_name[SIZE_LNAME]
//	notes: why did this author employ size constants here? See pad_record() for more details.
//------------------------------------------------------------------------------
struct {
      char    ssn[SIZE_SSN];		// key#0 (primary)
      char    last_name[SIZE_LNAME];	// key#1 (alternate)
      char    first_name[SIZE_FNAME];
      char    comments[SIZE_COMMENTS];
}  record;
//
char  response[BUFSIZ], *filename;	//
//
int  rms_status;
//
//	forward declarations
//
void open_file(void);
void type_options(void); 		// display help
void pad_record(void);			// fill fields with spaces
void error_exit(char *);
void add_employee(void);
void delete_employee(void);
void type_employees(void);		// fetch in alternate-key order
void print_employees(void); 		// fetch in primary-key order
void update_employee(void);
void initialize(char *);

//==================================================================================
//	main (every program has one)
//	notes: startup is modal
//		mode1: run from the DCL commandline like so: "$run RMSEXP"
//      	mode2: create/use a foreign symbol to pass a command-line argument
//			RMSEXP :== $[path]RMSEXP.exe
//			RMSEXP my-unique-filename.dat
//==================================================================================
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(;;) {						// loop forever
               printf("\nEnter option (A,D,P,T,U) or ? for help :");
               gets(response);					// get keyboard (unprotected)
               if (feof(stdin))					// it ctrl-z
                  break; 					// then exit loop
               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();	// display help
               }
         }
         rms_status = sys$close(&fab);
         if (rms_status != RMS$_NORMAL)
		error_exit("$CLOSE");
      }
}

//
// initialize RMS data structures (fab, rab, and xab)
//

void initialize(char *fn) {
   fab		 = cc$rms_fab;					// Initialize FAB (be a good citizen)
   //
   //	now make changes to the just-initialized fab
   //
   fab.fab$b_bks = 4;						// blocksize:		4
   fab.fab$l_dna = DEFAULT_FILE_EXT;				// default name addr:	.ext
   fab.fab$b_dns = sizeof DEFAULT_FILE_EXT -1;			// default name len:	whatever
   fab.fab$b_fac =	FAB$M_DEL |				// intend to:		DELETE
			FAB$M_GET | 				//			GET (read)
			FAB$M_PUT |				//			PUT (write)
			FAB$M_UPD; 				//			UPDATE
   fab.fab$l_fna = fn;						// file name addr:	filename.ext
   fab.fab$b_fns = strlen(fn);					// file name length:	whatever
   fab.fab$l_fop = FAB$M_CIF;					// file operation:	create if (not found)
   fab.fab$w_mrs = RECORD_SIZE;					// maximum record size:	whatever
   fab.fab$b_org = FAB$C_IDX;					// organization:	indexed
   fab.fab$b_rat = FAB$M_CR;					// record-attribute:	<CR>
   fab.fab$b_rfm = FAB$C_FIX;					// format:		fixed
   fab.fab$b_shr = FAB$M_NIL;					// share:		none
   fab.fab$l_xab = &primary_key;				// indicate our primary key
   //
   rab = cc$rms_rab;              				// Initialize RAB (be a good citizen)
   //
   //	now make changes to the just initialized rab
   //
   rab.rab$l_fab = &fab;					// now point our rab at our fab
   //
   primary_key = cc$rms_xabkey;					// Initialize XAB for our key#0
   //
   //	now make changes to the just initialized xab
   //
   primary_key.xab$b_dtp = XAB$C_STG;				// key data type:	string
   primary_key.xab$b_flg = 0;					// key options:		all set to NO
   primary_key.xab$w_pos0 = (char *) &record.ssn -		// start of segment0:	offset from start of record
                            (char *) &record;
   primary_key.xab$b_ref = 0;					// reference:		this is key #0
   primary_key.xab$b_siz0= SIZE_SSN;				// length of segment0:	whatever
   primary_key.xab$l_nxt = &alternate_key;			// next xab address:
// primary_key.xab$l_knm = "Employee Social Security Number";	// key name						bf_103.3
   //
   alternate_key = cc$rms_xabkey;				// Initialize XAB for our key#1
   //
   //	now make changes to the just initialized xab (this one is chained to the previous xab)
   //
   alternate_key.xab$b_dtp = XAB$C_STG;				// key data type:	string
   alternate_key.xab$b_flg = XAB$M_DUP | XAB$M_CHG; 		// key options:		DUP + CHANGE
   alternate_key.xab$w_pos0 = (char *) &record.last_name - 	// start of segment0:	offset from start of record
                              (char *) &record;
   alternate_key.xab$b_ref = 1;					// reference:		this is key #1
   alternate_key.xab$b_siz0= SIZE_LNAME;			// length of segment0:	whatever
// alternate_key.xab$l_knm =  "Employee Last Name";		//							bf_103.3
}

//
//	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");
}

//
//	type_options (display help)
//

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");
}

//
//	pad_record()
//
// Okay, so what is going on here?
//
// 1) Think ISAM. Remember that "indexed RMS technology" was originally created to support COBOL so you must always be thinking
//    of COBOL's PIC clause. (In DEC-BASIC you use a MAP declaration to do the same thing). For this reason, RMS was implemented
//    using "fixed-length mapped strings" which are not the same as "fixed-length VMS strings implemented by descriptor"
// 2) C employs ASCIZ (null terminated) strings. Even though this program is just an RMS demo found in the C User's Manual, it
//    would have been better if the original author would have created a mechanism to move data smoothly "between ASCIZ strings"
//    in C and "fixed-length mapped strings" in RMS (perhaps a MOVE_TO_RMS routine)
// 3) the original author used strncpy() to move c string data to the record fields which means NULLs follow the NULL terminator
//    (provided the string is not filled in which case no NULL would be found! Oops!)
//    This routine locates the terminating NULL then begins padding with spaces starting with the character after the NULL.
//
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);
}

//
//	add_employee
//
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");
}

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

   while(i < SIZE_SSN)						//
      response[i++] = ' '; 					// zap the NULL terminator placed by C (ASCIZ) then pad

   rab.rab$b_krf = 0;						// we want to use key#0 (primary)
   rab.rab$l_kbf = response;					// key buf addr:	whatever
   rab.rab$b_ksz = SIZE_SSN; 					// key buf size:	whatever
   rab.rab$b_rac = RAB$C_KEY; 					// record access:	lookup-by-key

   rms_status = sys$find(&rab);					// do it

   if (rms_status != RMS$_NORMAL && rms_status != RMS$_RNF)	// if error other than record-not-found
      error_exit("$FIND"); 					// then exit through here
   else
      if (rms_status == RMS$_RNF) 				// if record-not-found
         printf("RMSEXP - specified employee does not exist.\n");
      else {
            rms_status = sys$delete(&rab);			// delete the locked record
            if (rms_status != RMS$_NORMAL)			// if no success
               error_exit("$DELETE");				// then exit though here
      }
}

//
//	type_employees (to the terminal)
//

void type_employees(void) {
   int  number_employees;
   //
   rab.rab$b_krf = 1;						// we want to use key#1 (first alternate)
   //
   rms_status = sys$rewind(&rab);				// move to BOF
   if (rms_status != RMS$_NORMAL) 				// if an error
      error_exit("$REWIND");					// then exit thru here
   //
   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;					// read sequentially
   rab.rab$l_ubf = (char *) &record;				// user buffer addr:	whatever
   rab.rab$w_usz = RECORD_SIZE;					// user buffer size:	whatever
   //
   for(number_employees = 0; ; number_employees++) {
	rms_status = sys$get(&rab);
	if (rms_status != RMS$_NORMAL &&			// if and error other than EOF
	    rms_status != RMS$_EOF	)			//
		error_exit("$GET"); 				// then exit thru here
        else 							//
            if (rms_status == RMS$_EOF)				// if 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)					// if <> 0
      printf("\nTotal number of employees = %d.\n", number_employees);
   else 							// else
      printf("[Data file is empty.]\n");			//
}

//
//	print_employees (to the printer)
//

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;						// we want to use key#0 (primary)
   //
   rms_status = sys$rewind(&rab);				// rewind to BOF
   if (rms_status != RMS$_NORMAL)				// if something went wrong
      error_exit("$REWIND"); 					// then exit trhough here
   //
   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; 					// sequential
   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 &&			// if an error other than EOF
             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");
}

//
//	update_employee
//
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++] = ' ';					// zap the NULL terminator placed by C (ASCIZ) then pad

   rab.rab$b_krf = 0;						// we want to use key#0 (primary)
   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 &&	// if an error other than record not found
	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");
         }
}