          <<< NOTED::DISK$NOTES7:[NOTES$LIBRARY_7OF4]HACKERS.NOTE;1 >>>
                               -< ** Hackers ** >-
================================================================================
Note 1773.1  Hack to tell RMS to flush dirty (DFW=Deferred write) buffer  1 of 3
EPS::VANDENHEUVEL "Things that make you think, Hmm" 198 lines  20-JUL-1995 01:06
                          -< RMS_FLUSH_DFW_BUFFERS.C >-
--------------------------------------------------------------------------------
/*
** RMS_FLUSH_DFW_BUFFERS.C, Hein van den Heuvel, Digital, June 1995
** 
** This program will cause dirty RMS buffers (for which the writes were
** deferred using the FOP=DFW option) to be written to the disk for 
** selected devices.
** Running this program just before a Backup/Ignore=interlock will
** create a more reliable backup despite that innore=interlock.
** (Ofcourse it would be _really_ nice if backup knew how to do this trick,
** but some argue backup should have not favor RMS over say RDB. 
** The real solution for these and similar issues is probably a registry
** of quiesent action routines to be called by backup and such, 
** Running this program on a regular, say hourly, basis will make
** a system less vulnerable to data loss in the event of catastrofic failure.
** 
** The program works by using GETLKI to find RMS bucket locks held in PW 
** (protected write) mode as those are indicative of a deferred write buffer.
** ENQueing an incompatible lock request to any such lock, will generate
** an RMS internal blocking AST in the process holding the lock to which
** RMS will react by writting out the buffer and releasing the lock.
** This will happen transparently to any user program running.
**
** The flush is done synchroneously, a lock at a time, in this version.
** This can ofcourse be improved to allow multiple concurrent outstanding 
** lock request. If that improvement is made, you might want to limit the
** total number of outstanding IOs at the various levels: Per system,
** Per disk, and perhaps even per process. Be sure to guard aqainst ENQLM
** and ASTLM deplation when going Async.
** Further potentional enhancement: keep cache of file locks (enqlm based).
**
** Hope this helps someone out there,
** Hein van den Heuvel, July 1995.
**
*/

#include <dcdef>
/* #include <dvsdef> does not exist, hand coded defines follow */
#define DVS$_DEVCLASS 1
#define DVS$_DEVTYPE    2
#define SS$_NOMOREDEV 2648
#include <ssdef>
#include <prvdef>
#include <dvidef>
#include <lkidef>
#include <lckdef>
#include <string>
#include <stdio>
#define MAXDEVICE  200
#define MAXDEVNAMLEN  16
#define MAXDEVLOCKNAM 13
#define EFN 1

typedef struct { short len, cod; void *address; short *retlen; } item;
typedef struct { int len; void *address; } desc;

int	sys$getlkiw(), sys$enqw(), sys$deq(), sys$getdvi(), str$upcase();
int	jiggle_lock(), sys$device_scan(), sys$setprv(), sys$cmexec();

main(argc, argv)
int	argc;
char	*argv[];
{

    int	    stat, i, parent, grand_parent, mount_count;
    int	    device=DC$_DISK, device_count = 0, wildcard=0;
    short   retlen, l;
    int	    privs[] = { PRV$M_WORLD | PRV$M_CMEXEC , 0}, context[] = {0,0};
    char    label[16];
#pragma nostandard /* Using address of variable where constant is standard */

    struct  {   char	state_rqmode, state_grmode, fill[2];} lki_state;
    struct  {   int	vbn, fill[7] ;} resnam;
    struct  {	int	rms; unsigned short fid_num, fid_seq, fid_rvn;
		char	devlocknam[22] ;} parent_resnam;
    desc    devnam_desc, search_devnam_desc, resnam_desc = { 4, &resnam }, 
	    parent_resnam_desc = { 0, &parent_resnam }, *search_devnam=0;
    item    device_items[] = {   4, DVS$_DEVCLASS,  &device, 0, 0,0,0,0};
    item    getlki_items[] = {	31, LKI$_RESNAM,    &resnam, &retlen,
				 4, LKI$_PARENT,    &parent, 0, 
				 3, LKI$_STATE,     &lki_state, 0, 0,0,0,0};
    item    parent_items[] = {	31, LKI$_RESNAM,    &parent_resnam, &retlen,
				 4, LKI$_PARENT,    &grand_parent, 0, 0,0,0,0};
    item    getdvi_items[] = {  MAXDEVLOCKNAM, DVI$_DEVLOCKNAM, 0, &retlen,
				12, DVI$_VOLNAM,    &label, 0,
				 4, DVI$_MOUNTCNT,  &mount_count, 0, 0,0,0,0};
    int	    getlki_args[] = {7, EFN, (int) &wildcard, (int) &getlki_items,0,0,0,0};
    int	    parent_args[] = {7, EFN, (int) &parent, (int) &parent_items,0,0,0,0};
    int	    jiggle_args[] = {2, (int) &parent_resnam_desc, (int) &resnam_desc};
    char    devnam[MAXDEVICE][MAXDEVNAMLEN];
    char    devlocknam[MAXDEVICE][MAXDEVLOCKNAM+1];
#pragma standard

    /*
    ** First get some temporary privs for the GETLKI in EXEC mode later on.
    */
    stat = sys$setprv ( 1, privs, 0, 0);
    if (stat != SS$_NORMAL) return (stat & -2);
    
    /*
    ** Now see if we need to look at all devices of just those specified
    ** by the (wildcarded) specification on the command line.
    */
    if (argc > 1) {
        search_devnam_desc.len = strlen(argv[1]);
        search_devnam_desc.address = argv[1];
        search_devnam = &search_devnam_desc;
        str$upcase (&search_devnam_desc, &search_devnam_desc);
	printf ("Selected disks/labels are:\n");
    }

    /*
    ** Build two arrays with DISK device names and their lock names.
    */
    label[12]=0;
    for (i=0;  i<MAXDEVICE; ) {
	devnam_desc.len = MAXDEVNAMLEN;
	devnam_desc.address = &devnam[i][0];
	stat = sys$device_scan (&devnam_desc, &devnam_desc, 
	    search_devnam, device_items, context);
	if (stat & 1) {
	    devnam[i][devnam_desc.len]=0;    /* Null terminate */
	    getdvi_items[0].address = devlocknam[i];
	    stat = sys$getdvi (EFN,0,&devnam_desc,getdvi_items,0,0,0,0);
	    if (stat != SS$_NORMAL) return stat;
	    if (mount_count) {
		devlocknam[i][retlen] = 0;
		if (search_devnam) printf ("%20.s  %s\n", devnam[i], label);
		i++;
		}
	    }
	else {
	    if (stat == SS$_NOMOREDEV) {
		if (search_devnam && (i==0)) printf (" * NONE *\n");
		device_count = i;
		break;
		}
	    else return stat;
	    }
	}

    /*
    ** Main loop. Get a lock, any lock. Find out wether it is held
    ** in PW mode, has a resource name length of 4 and a parent lock
    */
    stat = sys$cmexec (&sys$getlkiw, &getlki_args);
    while (stat & 1) {
	if ((lki_state.state_grmode == LCK$K_PWMODE) &&
	    (parent!=0) && (retlen == 4) ) {
	    /*
	    ** found likely deferred buffer lock, to be sure,
	    ** go for the parent which should be an RMS file lock.
	    */
	    stat = sys$cmexec (&sys$getlkiw, &parent_args);
	    if ((stat & 1) && (grand_parent==0) && 
		(parent_resnam.rms == 'RMS$')) {
		parent_resnam_desc.len = retlen;
		/*
		** Found a deferred write buffer for sure!
		** Let's see if it is in the device name table,
		*/
		for (i=0; i < device_count; i++) {
		  if (!strcmp (devlocknam[i], parent_resnam.devlocknam)) {
		    printf ("VBN %08X, File (%d,%d,%d) on %s\n", resnam.vbn, 
			    parent_resnam.fid_num, parent_resnam.fid_seq,
			    parent_resnam.fid_rvn, &devnam[i][1]);
		    /*
		    ** Now go exec to jiggle the lock to get RMS to write.
		    */
		    stat = sys$cmexec (&jiggle_lock, &jiggle_args);

		    } /* In selected device table? */
		  }   /* Device table loop */
		}     /* RMS$ ? */
	    }         /* PW ? */
	if (stat &1) stat = sys$cmexec (&sys$getlkiw, &getlki_args);	
	} /* stat ? */
    if (stat == SS$_NOMORELOCK) stat = SS$_NORMAL;
    return stat;
}

int jiggle_lock(void *file_resnam_descriptor, void *vbn_resnam_descriptor)
/*
** This function is called in EXEC mode, 
** Bucket locks are sublocks to the file locks, must get file lock first.
*/
{
    int stat;
    struct  { short condition_value, reserved; long lockid, valblk[4];} file;
    struct  { short condition_value, reserved; long lockid, valblk[4];} vbn;

    stat = sys$enqw (EFN, LCK$K_NLMODE, &file, (LCK$M_VALBLK | LCK$M_SYSTEM), 
	       file_resnam_descriptor, 0, 0, 0, 0, 0, 0);
    if (stat&1) stat = sys$enqw (EFN, LCK$K_EXMODE, &vbn, (LCK$M_VALBLK | 
	    LCK$M_SYSTEM), vbn_resnam_descriptor, file.lockid, 0, 0, 0, 0, 0);
    if (stat&1) stat = sys$deq (vbn.lockid, &vbn.valblk, 0, 0);
    if (stat&1) stat = sys$deq (file.lockid, &vbn.valblk, 0, 0);
    return stat;
}
