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