/*

Model more or less after macro32 swctl.
(Note because of cloned UCBs we must try to assign SWAn: first, then
  SWA0: and clone a unit if none found. If so, report the unit assigned
  in a message, a symbol, and a system logical. Use these logicals to
  keep track of disks assigned also. It's a convenient place to keep
  the information for use by the rest of the system.)

In the following, SWAn: can be SWA0:, the template unit if a specific
unit is not given.

SWCTL /setup     SWAn: disk:	! set up and add first path
      /add       SWAn: disk:	! add another disk
      /deassign  SWAn: disk:	! Deassign all disks, remove intercept
      /report	 SWAn: nla0:	! return stats on paths.
      /switch	 SWAn: pathnum	! switch to pathnum if disk is idle
      /idle	 SWAn: pathnum  ! make the current disk go idle (so we can
				!  force a path switch)
      /unidle	 SWAn: pathnum	! undoes /idle, restarts I/O on a disk path
! pathnum doesn't really matter for /idle or /unidle but needs to be there
      /hook:mbxname  SWAn: ipid ! "hook" the server (found by a logical)
				! to the SWAn: unit provided its IPID and
				! mailbox can be found
      /unhook	 SWAn:		! Unhook a server from SWAn: if any is hooked
      /second			! Use "2p" alias for the device, not the
				! device itself, so we can use with cases
				! of dk-du scsi<->mscp failover. (Needs to be
				! used in conjunction with /add to be sensible.)
      /pathchange SWAn: pathnum ! Idles the target, switches, and unidles in one
				! step. Thus it won't stall (as the 3 step way
				! would if on sys disk).

If no function is present, ignore the command and junk it.      

A logical present would hold the server name and the server mailbox
name. SWITCH$SERVER_MAILBOX and SWITCH$SERVER_PROCESS seem as good as
any for names. Require them to be in exec mode or higher to keep
folks from playing...

Not all these functions will be done here at once, just enough to try some
tests. The rest can be done at need.

Buffer formats:

ufunc	uswitch lo	uswitch hi	ulen		rest
1					dvc name len	dvc name	bash
2					dvc name len	dvc name	unbash
3	unit to sw to	idlfgs=0	switch, err if dvc bsy
3	"		idlfgs=1	save next irp, busy dvc
3	"		idlfgs=2	restart i/o, unbusying dvc, no switch
4	unit to rpt	?		statblk len	statblk data rpt stats
5	ucb of dvc			Report whether a device has a sw ucb
					intercepting it by returning its
					unit no, ucb, path blocks, etc. if
					present, 0 if none.
6	server ipid			mbx nm len	mbx name
					store server's mbx & ipid for swdriver]
					unit to talk to it
7	...				Clear swever info from a sw unit
8	...				Return octal 14747 (6631 decimal) if
					device is really a SW unit. Use
					for a sanity check. 
9					UCB of device			bash
10					UCB of device			unbash
11                                                      returns # of path in use
                                                        and pointer to pthptr
                                                        struct for SW unit that
                                                        is called.
                                                        Returns index of current
                                                        path, pthptr address for
                                                        principal path, and
                                                        pthptr addr for path now
                                                        in use in 1st 3 longs of
                                                        "rest"
12					Like 5, but does not need a UCB. Returns
					info on the device intercepted by the
					SW device to which the request was
					sent.

Note too that the DDTAB entry ddt$ps_mntver_2 contains a pointer to
an alternate entry (meant to be called from fork) which takes the
exact same buffer args as above. Its call sequence is

int swsetup(char* buffer, int bufsiz, UCB* swucb)

where swucb is the UCB address of the SW device UCB and the buffer and buffer
size are what would otherwise be passed as P1 and P2 in a $qio. Under
ufunc 3, note, idflgs values 0 or 2 do nothing in this routine. Status
returns directly from the call.

Note Further:

When doing a SETUP, a symbol SWCTL_SETUP_DVC will be set up to the
value of the last generated SW device so that it won't be necessary
for command procedures to try and figure it out externally.

The DCL symbol SW_DEVICE will be set to the value of the SW device
selected when a SWCTL/setup is done successfully. By using this
symbol for subsequent SWCTL/add commands it will be possible to
set up sets of devices easily.

*/
#pragma module SWCTL "X-1"
/*	.TITLE	SWCTL		- Control path switch code manually       */
/*	.IDENT	'X1.0'                                                    */
/*  									   */
/***************************************************************************/
/*                                                                         */
/*  COPYRIGHT (c) 1997 BY                                		   */
/*  DIGITAL EQUIPMENT CORPORATION, NASHUA. NEW HAMPSHIRE                   */
/*  ALL RIGHTS RESERVED.						   */
/* 									   */
/*  THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED AND COPIED  */
/*  ONLY IN  ACCORDANCE WITH  THE  TERMS  OF  SUCH  LICENSE  AND WITH THE  */
/*  INCLUSION OF THE ABOVE COPYRIGHT NOTICE. THIS SOFTWARE OR  ANY  OTHER  */
/*  COPIES THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY  */
/*  OTHER PERSON.  NO TITLE TO AND OWNERSHIP OF  THE  SOFTWARE IS  HEREBY  */
/*  TRANSFERRED.							   */
/* 									   */
/*  THE INFORMATION IN THIS SOFTWARE IS  SUBJECT TO CHANGE WITHOUT NOTICE  */
/*  AND  SHOULD  NOT  BE  CONSTRUED AS  A COMMITMENT BY DIGITAL EQUIPMENT  */
/*  CORPORATION.							   */
/* 									   */
/*  DIGITAL ASSUMES NO RESPONSIBILITY FOR THE USE  OR  RELIABILITY OF ITS  */
/*  SOFTWARE ON EQUIPMENT WHICH IS NOT SUPPLIED BY DIGITAL.		   */
/* 									   */
/*									   */
/***************************************************************************/
/*  									   */
/*++									   */
/* FACILITY:	SCSI							   */
/*  									   */
/* ABSTRACT:   	Sets up and controls multipath switching manually          */
/*		and reports various things about the switch facility.	   */
/*  									   */
/* ENVIRONMENT:	User mode						   */
/*  									   */
/* AUTHOR:  Glenn C. Everhart, 	CREATION DATE:  31-Mar-1997		   */
/*  									   */
/* MODIFIED BY:								   */
/*--  									   */



/* Include a bunch of definitions that are needed for doing driver
   internal kinds of things. */

/* Include files and define external constants */

#pragma nomember_alignment	/* Temporary fix */
#include <stdio.h>
#include <stat.h>
#include <stdlib.h>                                                          
#include <file.h>                
#include <dcdef.h>
#include <ddbdef.h>
#include <ddtdef.h>
#include <descrip.h>
#include <devdef.h>
#include <dvidef.h>
#include <iodef.h>
#include <jpidef.h>
#include <lib$routines.h>
#include <sbdef.h>
#include <smgdef.h>
#include <ssdef.h>
#include <starlet.h>
#include <str$routines.h>
#include <stsdef.h>
#include <ucbdef.h>

#pragma member_alignment	/* Temporary fix */
/* Define function prototypes for system routines */

#include        acp_routines            /* ACP$ and ACP_STD$ routines */
#include        erl_routines            /* erl$ and erl_std$ routines */
#include        exe_routines
#include        ioc_routines
#include        sch_routines
#include        com_routines

#include        exe_routines            /* exe$ and exe_std$ routines */
#include        ioc_routines            /* ioc$ and ioc_std$ routines */
#include        ldr_routines            /* ldr$ and ldr_std$ routines */

/*#include      sch_routines            /* sch$ and sch_std$ routines */

/* Define various device driver macros */

#include        vms_drivers             /* Device driver support macros */

#include        vms_macros              /* Define bug_check and such */

#include        builtins                /* C builtin functions */
#include        string                  /* String rtns from "kernel CRTL" */
/* Define some useful types */

typedef unsigned short int WORD;        /* Define a WORD (16 bits) */
typedef unsigned char BYTE;             /* Define a BYTE (8 bits) */
typedef unsigned int UINT;              /* Usigned int (32 bits) */
typedef unsigned char uchar;

#define BYTE_OFFSET_MASK 0X1FFUL
#define RMS_EOF 98938 
#define TRUE 1
#define FALSE 0
#define DOUBLE_QUOTE 34
#define SINGLE_QUOTE 39
#define VAXBUFSIZ 512
	/* Note the number below must be at least as large as the
		number of paths permitted in SWdriver. */
#define MAXPATHS 16		/* Number of paths allowed for one SW unit*/
#define IO$V_FMODIFIERS 6

int CLI$PRESENT (long[]);
int CLI$GET_VALUE (long[], long[], long);
int CLI$DCL_PARSE(int, long(), long[], long[], long[]);
int CLI$DISPATCH(int);
int SYSVER_SHOW();
int SYSVER_REPLACE();
int SYSVER_WRITE();
int SYSVER_EXIT();
extern long swctl_cld(); 
long getswunit(void* namdsc, long* unit);
void report_dvc(UCB* hstucb);
void unbashnl();
void setpthchr();

/* Global cells */                            

/* Buffer that holds arguments for io$_format calls to swdriver */
    struct sff {
      long	         ufunc;	/* major function */
      unsigned short int uunit; /* Unit number used, or 65535 for find one*/
      unsigned short int dlyfgs;/* Delay flags, controls how switch is done */
      long		 ulen;	/* length of dvc name etc. */
      char		 urest[256]; /* Space for dvc name, report data etc. */
      } FMTBUF;

CCB* nlccb;  /* Global cells to hold CCB and UCB of channel to NLA0: */
UCB* nlucb;  /* Used by bashnl and unbashnl                          */
long k_arg[4];

/* The structure in which we save info about the entries for UCBs
   that are intercepted. For symmetry the main UCB info is also
   stored here in entry 1. */
/* We need this because it's what SWdriver will return */
typedef struct pthptr {
    DT_UCB *	ucb$l_hstucb;		/* UCB of path			*/
    void *	ucb$l_oldmv;		/* original mount ver entry	*/
    void *	ucb$l_oaltst;		/* original altstart entry	*/
    void *	ucb$l_qirp;		/* original pending_io entry	*/
    void *	ucb$l_cancl;		/* original cancel io entry	*/
    void *	ucb$l_aux;		/* original auxiliary io entry	*/
    long	ucb$l_enapth;		/* path enabled flag		*/
    uint64	ucb$q_pthtim;		/* time of last I/O		*/
    long	ucb$l_pthios;		/* Count of I/Os on this path	*/
    long	ucb$l_ptherr;		/* Count of errors on this path	*/
    } PTHPTR;    

PTHPTR* pptrs;  /* Use to point at a given path pointer */
char pthchr[8]; /* path characteristics */
/* Need this forward ref after the struct is defined */
int bashnl(CCB* inccb, UCB* inucb);

char prompt[] = "SWCTL> ";
long prompt_desc[2] = {(sizeof prompt)-1, &prompt};

char setup_qual[] = "SETUP";
long setup_desc[2] = {(sizeof setup_qual)-1, &setup_qual};

char add_qual[] = "ADD";
long add_desc[2] = {(sizeof add_qual)-1, &add_qual};

char second_qual[] = "SECOND";
long second_desc[2] = {(sizeof second_qual)-1, &second_qual};

char deas_qual[] = "DEASSIGN";
long deas_desc[2] = {(sizeof deas_qual)-1, &deas_qual};

char report_qual[] = "REPORT";
long report_desc[2] = {(sizeof report_qual)-1, &report_qual};

char switch_qual[] = "SWITCH";
long switch_desc[2] = {(sizeof switch_qual)-1, &switch_qual};

char pathchange_qual[] = "PATHCHANGE";
long pathchange_desc[2] = {(sizeof pathchange_qual)-1, &pathchange_qual};

char idle_qual[] = "IDLE";
long idle_desc[2] = {(sizeof idle_qual)-1, &idle_qual};

char unidle_qual[] = "UNIDLE";
long unidle_desc[2] = {(sizeof unidle_qual)-1, &unidle_qual};

char hook_qual[] = "HOOK";
long hook_desc[2] = {(sizeof hook_qual)-1, &hook_qual};

/* Unit will be the label used in the .CLD that gets the disk name we're
   setting up. We need to get the value of this to use in channel assigns
   also. */

long srchstat;	/* status from searchdev */
void * srchnmd;

char unit_qual[] = "UNIT";
long unit_desc[2] = {(sizeof unit_qual)-1, &unit_qual};

/* Descriptor for the SW unit, which can be SWA0: by default */
char swunit_qual[] = "SWUNIT";
long swunit_desc[2] = {(sizeof swunit_qual)-1, &swunit_qual};


int hdr_size,image_size,exit_flag;
unsigned long pc;
unsigned long mod_sys_ver = FALSE ;

/* Main routine */               
/*
  Abstract:
    This routine reads a command line and acts on it, performing various
    operations to set up multipath tuples, report on such tuples, or
    manually idle or switch paths. Since all paths have the same name
    there is no alternate naming used for different paths. They are
    just referred to by an index, 0 for the first path (which is the
    only one VMS thinks is in use) and 1-max (max=7 currently) for
    others. The reporting here only tells what the I/O counts and error
    counts are per path, which path is in use, and what the device name
    associated with a given SW unit is. The intent is that this utility
    be used only for rather knowledgeable users, and it requires privilege
    to work at all, so the thought is that it will be used in such cases
    mainly to set up multipath switching manually for older hardware, and
    not used heavily in normal operation apart from that. Therefore while
    the utility has been built to work, not a great deal of time has been
    spent tuning it for heavy day to day use. There are enough facilities
    here in the form of some DCL symbols to drive this with a DCL command
    file semi automatically if it is desired to do so.

   Inputs:
    No formal inputs
   Outputs:
    No formal outputs

*/

main(){
    int fd;
    char c;              
    long actual_length;
    int i,n;
    int file_size,hdr_blkcnt, status;
    struct stat statbuf;
    char *p;
    char value[256];
    long value_desc[2] = {sizeof value, &value};
    short value_length = 0;
    char sw0val[6]="SWA0:";
    long sw0val_desc[2] = {sizeof sw0val, &sw0val};
    char swvalue[256];
    long swvalue_desc[2] = {sizeof swvalue, &swvalue};
    short swvalue_length = 0;
    char dskvalue[256];
    long dskvalue_desc[2] = {sizeof dskvalue, &dskvalue};
    short dskvalue_length = 0;
    char mbxnam[256];
    long mbxnam_desc[2] = {sizeof mbxnam, &mbxnam};
    int swunit;
    long chnl;
    short int iosb[4];
    long kargs1[3] = {2, (long)&swvalue_desc, (long)&swunit};
    int devchnl; /* channel to device, not sw device */
    int swchnl;  /* channel to sw device */
    long dviitm[6];
    char dvichr[256]; /* keep real device info here */
    long dvichrl;
    long dvichd[2];
    long dvisymd[2];
    char symnam[10]="SW_DEVICE";
    int k,kk,kkk,kkkk,kkkkk,kkkkkk; /* short term scratch cells */
    int ii;
    long setupcmd[132];
    char * morptr;
    long * lmoreptr;
    long secctr;
    long secflgs;
    long secnds[2];
    long nhosts;
    long indrct;
    long enab;
    long npath;
    uint64 * timptr;
    long hstdtap;                   /* Pointer to pthptr array in knl */
    UCB* hstucb;	            /* host device ucb */

/* fill in DVI buffer */
/* We use this to get the final device name */
    dviitm[0] = (DVI$_DEVNAM << 16) + 256;
    dviitm[1] = &dvichr;
    dviitm[2] = &dvichrl;
    dviitm[3] = 0;
    dviitm[4] = 0;
    dviitm[5] = 0;
    dvisymd[0] = 9 + (1<<24) + (DSC$K_DTYPE_T <<16);
    dvisymd[1] = &symnam;
/* Enter command/dispatch loop */

    exit_flag = FALSE;
    status = 0;
    while ((status != RMS_EOF) && (exit_flag == FALSE)) {     
        status = CLI$DCL_PARSE(0, swctl_cld, LIB$GET_FOREIGN, LIB$GET_FOREIGN, &prompt_desc);
        exit_flag = TRUE;
        if (status & 1) { 
/*	Set up the interface?	*/

/* We need the SW unit for everything, even if it's only swa0:, the
   template. */
          if (CLI$PRESENT(&swunit_desc) & 1) {
/* There'd better be a usable value as well. However, don't use it
   immediately since we really may not want it directly. */

            if (CLI$GET_VALUE(&swunit_desc, &swvalue_desc, &swvalue_length) & 1){
              swvalue[swvalue_length] = 0;
/* Now start checking for specific commands */
/* We'll start with /setup. Strictly speaking it could be avoided, just use
   /add, but this will make it clear that the intent is to start with a new
   and empty switching unit, and if we get one that isn't empty we can 
   complain, instead of (confusingly) adding another path to the unit. This
   stuff needs to be here for test mainly, in the absence of any hardware
   that can provide us sets of identically named devices, but it may be
   useful in manual setup situations sometime too. The setup of other paths
   will use the same routines as must be here for this stuff.

   Note that here we take a disk: name as the path to start with. We will
   allow multiples by using a /skip:n switch to tell our search routines
   to skip the first n matching names when looking for the device. That
   way we'll be able to handle 2p units and so on too, even though the
   VMS device name for all of them is in fact the same. */

                if (CLI$PRESENT(&setup_desc) & 1){
/* set up the first disk in a switch list */
/* first, be sure the "disk" argument exists. */
		  if (CLI$PRESENT(&unit_desc) & 1){
/* arg exists but we must ensure the value is there too. */
		    value_desc[0] = sizeof(value);
		    if (CLI$GET_VALUE(&unit_desc, &value_desc, &value_length) & 1){
		      value[value_length] = 0;
/* set up the descriptor */
		      value_desc[0] = value_length + (1<<24) + (DSC$K_DTYPE_T <<16);
		      chnl = 0;
		      k = (long)sys$assign(&value_desc, &chnl,0,0,0);
		      if (((k & 1) != 0) && (chnl != 0)){
/* Now we have a channel to the "disk", get one to the SW unit. Use an explicit
   unit number if it exists and we can, otherwise assign a new one. */
/* Use a kernel routine so we can get the info directly. */
		        swunit = 0;
		        swvalue_desc[0] = swvalue_length + (1<<24) + (DSC$K_DTYPE_T <<16);
		        k = sys$cmkrnl(&getswunit,&kargs1);
		        if (swunit != 0){
/* If the sw unit is explicit & nonzero, try to assign to it. If it 
   succeeds, use that unit. Otherwise assign a new SW unit and use that.
   Also detect unit in use if possible. Use ioc$searchdev for this
   from kernel mode so we don't try to create a device until we know we
   must. */
			  swchnl = 0;
		          swvalue_desc[0] = swvalue_length + (1<<24) + (DSC$K_DTYPE_T <<16);
		          k = (long)sys$assign(&swvalue_desc, &swchnl,0,0,0);
		          if (((k & 1) != 0)){
/* Ensure the driver really IS a swdriver unit */
			    setupcmd[0] = 8; /* check device is sw: */
			    kkkk = IO$_PHYSICAL + 128;
			    kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,0,0,0);
			    if (iosb[0] != 6635)sys$exit(SS$_UNSAFE);
			    setupcmd[0] = 1; /* bash device */
                            setupcmd[1] = 0;
			    if (CLI$PRESENT(&second_desc) & 1) setupcmd[1]=65536;
                            setupcmd[2] = value_length;
                            p = &setupcmd[3];
/* copy device name to the buffer now. */
                            memcpy(p,&value,value_length);
/* Send the command to swdriver now */
			    kkkk = IO$_PHYSICAL + 128;
			    kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,0,0,0);
			    if ((iosb[0] & 1) == 0)sys$exit(SS$_NONEXDRV);
		          }
		        } else {
/* If the SW unit did not exist, then create one by assigning to SWA0:
   so a new UCB will be cloned for us. */
			  swchnl = 0;
		          k = (long)sys$assign(&sw0val_desc, &swchnl, 0, 0, 0);
		          if (((k & 1) != 0)){
/* Ensure the driver really IS a swdriver unit */
			    setupcmd[0] = 8; /* check device is sw: */
			    kkkk = IO$_PHYSICAL + 128;
			    kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,0,0,0);
			    if (iosb[0] != 6635)sys$exit(SS$_UNSAFE);
			    setupcmd[0] = 1; /* bash device */
                            setupcmd[1] = 0;
			    if (CLI$PRESENT(&second_desc) & 1) setupcmd[1]=65536;
                            setupcmd[2] = value_length;
                            p = &setupcmd[3];
/* copy device name to the buffer now. */
                            memcpy(p,&value,value_length);
/* Send the command to swdriver now */
			    kkkk = IO$_PHYSICAL + 128;
			    kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,0,0,0);
			    if ((iosb[0] & 1) == 0)sys$exit(SS$_NONEXDRV);
/* At this point before tearing down the link we must save the SW unit number
   we got so subsequent commands can use it. Manually we can note what
   was used, but for larger scale use that will be inadequate. */
                            
			    dvichrl = 0;
                            kkk = sys$getdviw(1,swchnl,0,&dviitm,&iosb,0,0,0,0);
/* The final device name now should be in dvichr, length dvichrl. */
			    if (((kkk & 1) != 0) && dvichrl > 1){
			      dvichd[0] = dvichrl + (1<<24) + (DSC$K_DTYPE_T <<16);
			      dvichd[1] = &dvichr[0];
/* Now dvichd is a descriptor for all this */
			      ii = 2;
			      kkk = lib$set_symbol(&dvisymd,&dvichd,&ii);
		            }
		          }
                        }
                      }
                    }
                  }
                } /* setup_desc */


/* add is just like bash but requires the unit to already be initialized and adds
   another path to it. */
                if (CLI$PRESENT(&add_desc) & 1){
/* Add another disk to a switch list */
/* first, be sure the "disk" argument exists. */
		  if (CLI$PRESENT(&unit_desc) & 1){
/* arg exists but we must ensure the value is there too. */
		    value_desc[0] = sizeof(value);
		    if (CLI$GET_VALUE(&unit_desc, &value_desc, &value_length) & 1){
		      value[value_length] = 0;
/* set up the descriptor */
		      value_desc[0] = value_length + (1<<24) + (DSC$K_DTYPE_T <<16);
		      chnl = 0;
		      k = (long)sys$assign(&value_desc, &chnl,0,0,0);
		      if (((k & 1) != 0) && (chnl != 0)){
/* Now we have a channel to the "disk", get one to the SW unit. Use an explicit
   unit number if it exists and we can, otherwise assign a new one. */
/* Use a kernel routine so we can get the info directly. */
		        swunit = 0;
		        swvalue_desc[0] = swvalue_length + (1<<24) + (DSC$K_DTYPE_T <<16);
		        k = sys$cmkrnl(&getswunit,&kargs1);
		        if (swunit != 0){
/* If the sw unit is explicit & nonzero, try to assign to it. If it 
   succeeds, use that unit. Otherwise assign a new SW unit and use that.
   Also detect unit in use if possible. Use ioc$searchdev for this
   from kernel mode so we don't try to create a device until we know we
   must. */
			  swchnl = 0;
		          swvalue_desc[0] = swvalue_length + (1<<24) + (DSC$K_DTYPE_T <<16);
		          k = (long)sys$assign(&swvalue_desc, &swchnl,0,0,0);
		          if (((k & 1) != 0)){
/* Ensure the driver really IS a swdriver unit */
			    setupcmd[0] = 8; /* check device is sw: */
			    kkkk = IO$_PHYSICAL + 128;
			    kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,0,0,0);
			    if (iosb[0] != 6631)sys$exit(SS$_UNSAFE);
			    setupcmd[0] = 1; /* bash device */
                            setupcmd[1] = 65535;
			    if (CLI$PRESENT(&second_desc) & 1) setupcmd[1]=131071;
                            setupcmd[2] = value_length;
                            p = &setupcmd[3];
/* copy device name to the buffer now. */
                            memcpy(p,&value,value_length);
/* Send the command to swdriver now */
			    kkkk = IO$_PHYSICAL + 128;
			    kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,0,0,0);
			    if ((iosb[0] & 1) == 0)sys$exit(SS$_NONEXDRV);
		          }
		        } else {
/* If the SW unit did not exist, then create one by assigning to SWA0:
   so a new UCB will be cloned for us. */
			  swchnl = 0;
		          k = (long)sys$assign(&sw0val_desc, &swchnl, 0, 0, 0);
		          if (((k & 1) != 0)){
/* Ensure the driver really IS a swdriver unit */
			    setupcmd[0] = 8; /* check device is sw: */
			    kkkk = IO$_PHYSICAL + 128;
			    kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,0,0,0);
			    if (iosb[0] != 6631)sys$exit(SS$_UNSAFE);
			    setupcmd[0] = 1; /* bash device */
                            setupcmd[1] = 65535;
			    if (CLI$PRESENT(&second_desc) & 1) setupcmd[1]=131071;
                            setupcmd[2] = value_length;
                            p = &setupcmd[3];
/* copy device name to the buffer now. */
                            memcpy(p,&value,value_length);
/* Send the command to swdriver now */
			    kkkk = IO$_PHYSICAL + 128;
			    kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,0,0,0);
			    if ((iosb[0] & 1) == 0)sys$exit(SS$_NONEXDRV);
		          }
                        }
                      }
                    }
                  }
                } /* add_desc */


/* deassign unconnects a SW unit */
                if (CLI$PRESENT(&deas_desc) & 1){
/* Deassign a switch path set */
/* first, be sure the "disk" argument exists. */
		  if (CLI$PRESENT(&unit_desc) & 1){
/* arg exists but we must ensure the value is there too. */
		    value_desc[0] = sizeof(value);
		    if (CLI$GET_VALUE(&unit_desc, &value_desc, &value_length) & 1){
		      value[value_length] = 0;
/* set up the descriptor */
		      value_desc[0] = value_length + (1<<24) + (DSC$K_DTYPE_T <<16);
		      chnl = 0;
		      k = (long)sys$assign(&value_desc, &chnl,0,0,0);
		      if (((k & 1) != 0) && (chnl != 0)){
/* Now we have a channel to the "disk", get one to the SW unit. Use an explicit
   unit number if it exists and we can, otherwise assign a new one. */
/* Use a kernel routine so we can get the info directly. */
		        swunit = 0;
		        swvalue_desc[0] = swvalue_length + (1<<24) + (DSC$K_DTYPE_T <<16);
		        k = sys$cmkrnl(&getswunit,&kargs1);
		        if (swunit != 0){
/* If the sw unit is explicit & nonzero, try to assign to it. If it 
   succeeds, use that unit. Otherwise assign a new SW unit and use that.
   Also detect unit in use if possible. Use ioc$searchdev for this
   from kernel mode so we don't try to create a device until we know we
   must. */
			  swchnl = 0;
		          swvalue_desc[0] = swvalue_length + (1<<24) + (DSC$K_DTYPE_T <<16);
		          k = (long)sys$assign(&swvalue_desc, &swchnl,0,0,0);
		          if (((k & 1) != 0)){
/* Ensure the driver really IS a swdriver unit */
			    setupcmd[0] = 8; /* check device is sw: */
			    kkkk = IO$_PHYSICAL + 128;
			    kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,0,0,0);
			    if (iosb[0] != 6631)sys$exit(SS$_UNSAFE);
			    setupcmd[0] = 2; /* unbash device */
                            setupcmd[1] = -1;
                            setupcmd[2] = value_length;
                            p = &setupcmd[3];
/* copy device name to the buffer now. */
                            memcpy(p,&value,value_length);
/* Send the command to swdriver now */
			    kkkk = IO$_PHYSICAL + 128;
			    kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,0,0,0);
			    if ((iosb[0] & 1) == 0)sys$exit(SS$_NONEXDRV);
		          }
		        } else {
/* If the SW unit did not exist, then create one by assigning to SWA0:
   so a new UCB will be cloned for us. */
			  swchnl = 0;
		          k = (long)sys$assign(&sw0val_desc, &swchnl, 0, 0, 0);
		          if (((k & 1) != 0)){
/* Ensure the driver really IS a swdriver unit */
			    setupcmd[0] = 8; /* check device is sw: */
			    kkkk = IO$_PHYSICAL + 128;
			    kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,0,0,0);
			    if (iosb[0] != 6631)sys$exit(SS$_UNSAFE);
			    setupcmd[0] = 2; /* unbash device */
                            setupcmd[1] = -1;
                            setupcmd[2] = value_length;
                            p = &setupcmd[3];
/* copy device name to the buffer now. */
                            memcpy(p,&value,value_length);
/* Send the command to swdriver now */
			    kkkk = IO$_PHYSICAL + 128;
			    kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,0,0,0);
			    if ((iosb[0] & 1) == 0)sys$exit(SS$_NONEXDRV);
		          }
                        }
                      }
                    }
                  }
                } /* deas_desc */

/* switch switches to a different path */
                if (CLI$PRESENT(&switch_desc) & 1){

/* In general we expect disk paths to all have the same names, so we
   can only use a simple path number to specify which path to connect. */
/* However, use the "disk" argument. We just want it to be a number. */

/* first, be sure the "disk" argument exists. */
		  if (CLI$PRESENT(&unit_desc) & 1){
/* arg exists but we must ensure the value is there too. */
		    value_desc[0] = sizeof(value);
		    if (CLI$GET_VALUE(&unit_desc, &value_desc, &value_length) & 1){
		      value[value_length] = 0;
/* set up the descriptor */
		      value_desc[0] = value_length + (1<<24) + (DSC$K_DTYPE_T <<16);
/* Now get the binary value */
/* Exit if first char is not a digit. */
		      if (!isdigit(value[0]))sys$exit(SS$_BADPARAM);
		      kkkkk = atoi(&value[0]);
/* Reject path number if too large. For now MAXPATHS will be treated as 16 here */
		      if (kkkkk > MAXPATHS)sys$exit(SS$_BADPARAM);
		      chnl = 0;
/* Now we need a channel to the SW device. */
/* Use a kernel routine so we can get the info directly. */
		        swunit = 0;
		        swvalue_desc[0] = swvalue_length + (1<<24) + (DSC$K_DTYPE_T <<16);
		        k = sys$cmkrnl(&getswunit,&kargs1);
		        if (swunit != 0){
/* If the sw unit is explicit & nonzero, try to assign to it. If it 
   succeeds, use that unit. Otherwise assign a new SW unit and use that.
   Also detect unit in use if possible. Use ioc$searchdev for this
   from kernel mode so we don't try to create a device until we know we
   must. */
			  swchnl = 0;
		          swvalue_desc[0] = swvalue_length + (1<<24) + (DSC$K_DTYPE_T <<16);
		          k = (long)sys$assign(&swvalue_desc, &swchnl,0,0,0);
		          if (((k & 1) != 0)){
/* Ensure the driver really IS a swdriver unit */
			    setupcmd[0] = 8; /* check device is sw: */
			    kkkk = IO$_PHYSICAL + 128;
			    kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,0,0,0);
			    if ((iosb[0] != 6635) && (iosb[0] != 6631))sys$exit(SS$_UNSAFE);
/* Now send the command */

			    setupcmd[0] = 3; /* switch path of device */
                            setupcmd[1] = kkkkk;
                            setupcmd[2] = value_length;
                            p = &setupcmd[3];
/* copy device name to the buffer now. */
                            memcpy(p,&value,value_length);
/* Send the command to swdriver now */
			    kkkk = IO$_PHYSICAL + 128;
			    kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,0,0,0);
			    if ((iosb[0] & 1) == 0)sys$exit(SS$_NONEXDRV);
/* Now get the unit number. iosb[2] will return this. */
			    setupcmd[0] = 8; /* check device is sw: */
			    kkkk = IO$_PHYSICAL + 128;
			    kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,0,0,0);
/* Underlying devices can become unhappy unless they get a SCSI start so
   send a packack to them. Do this by sending I/O to the SW channel via its
   start - I/O, to the now current channel. */
			    kkkk = IO$M_FMODIFIERS >> IO$V_FMODIFIERS;
/* Mask off extra cruft */
			    kkkk = kkkk & 0xFFE0;
			    kkkk = kkkk + iosb[2];
			    kkkk = (kkkk << IO$V_FMODIFIERS) + IO$_PACKACK;
/* We should not send an unsynch'd I/O to the device unless we know the
   switch succeeded, and in any case we don't know for sure in this function
   that the device hasn't suddenly become busy again either. Therefore do NOT
   send this extra I/O here. */
/*
			    k = sys$qiow(11,swchnl,kkkk,&iosb,0,0,0,0,0,0,0,0);
*/
		          }
		        } else {
/* In this case we need an existing SW unit. */
		          sys$exit(SS$_BADPARAM);
                        }
                    }
                  }
                } /* switch_desc */


/* idle causes the current path to become "busy" till unidled */
                if (CLI$PRESENT(&idle_desc) & 1){

/* In general we expect disk paths to all have the same names, so we
   can only use a simple path number to specify which path to connect. */
/* However, use the "disk" argument. We just want it to be a number. */

/* first, be sure the "disk" argument exists. */
		  if (CLI$PRESENT(&unit_desc) & 1){
/* arg exists but we must ensure the value is there too. */
		    value_desc[0] = sizeof(value);
		    if (CLI$GET_VALUE(&unit_desc, &value_desc, &value_length) & 1){
		      value[value_length] = 0;
/* set up the descriptor */
		      value_desc[0] = value_length + (1<<24) + (DSC$K_DTYPE_T <<16);
/* Now get the binary value */
/* Exit if first char is not a digit. */
		      if (!isdigit(value[0]))sys$exit(SS$_DEVCMDERR);
		      kkkkk = atoi(&value[0]);
/* Reject path number if too large. For now MAXPATHS will be treated as 16 here */
		      if (kkkkk > MAXPATHS)sys$exit(SS$_VECFULL);
		      chnl = 0;
/* Now we need a channel to the SW device. */
/* Use a kernel routine so we can get the info directly. */
		        swunit = 0;
		        swvalue_desc[0] = swvalue_length + (1<<24) + (DSC$K_DTYPE_T <<16);
		        k = sys$cmkrnl(&getswunit,&kargs1);
		        if (swunit != 0){
/* If the sw unit is explicit & nonzero, try to assign to it. If it 
   succeeds, use that unit. Otherwise assign a new SW unit and use that.
   Also detect unit in use if possible. Use ioc$searchdev for this
   from kernel mode so we don't try to create a device until we know we
   must. */
			  swchnl = 0;
		          swvalue_desc[0] = swvalue_length + (1<<24) + (DSC$K_DTYPE_T <<16);
		          k = (long)sys$assign(&swvalue_desc, &swchnl,0,0,0);
		          if (((k & 1) != 0)){
/* Ensure the driver really IS a swdriver unit */
			    setupcmd[0] = 8; /* check device is sw: */
			    kkkk = IO$_PHYSICAL + 128;
			    kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,0,0,0);
			    if ((iosb[0] != 6635) && (iosb[0] != 6631))sys$exit(SS$_UNSAFE);
/* Now send the command */

			    setupcmd[0] = 3; /* switch path of device */
                            setupcmd[1] = (kkkkk + (1<<16));
                            setupcmd[2] = value_length;
                            p = &setupcmd[3];
/* copy device name to the buffer now. */
                            memcpy(p,&value,value_length);
/* Send the command to swdriver now */
			    kkkk = IO$_PHYSICAL + 128;
			    kkk = sys$qio(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,31416,0,0);
			    if ((iosb[0] & 1) == 0)sys$exit(SS$_NONEXDRV);
		          }
		        } else {
/* In this case we need an existing SW unit. */
		          sys$exit(SS$_BADPARAM);
                        }
                    }
                  }
                } /* idle_desc */


/* unidle causes the current path to become "unbusy" if it was idled before */
                if (CLI$PRESENT(&unidle_desc) & 1){

/* In general we expect disk paths to all have the same names, so we
   can only use a simple path number to specify which path to connect. */
/* However, use the "disk" argument. We just want it to be a number. */

/* first, be sure the "disk" argument exists. */
		  if (CLI$PRESENT(&unit_desc) & 1){
/* arg exists but we must ensure the value is there too. */
		    value_desc[0] = sizeof(value);
		    if (CLI$GET_VALUE(&unit_desc, &value_desc, &value_length) & 1){
		      value[value_length] = 0;
/* set up the descriptor */
		      value_desc[0] = value_length + (1<<24) + (DSC$K_DTYPE_T <<16);
/* Now get the binary value */
/* Exit if first char is not a digit. */
		      if (!isdigit(value[0]))sys$exit(SS$_DEVCMDERR);
		      kkkkk = atoi(&value[0]);
/* Reject path number if too large. For now MAXPATHS will be treated as 16 here */
		      if (kkkkk > MAXPATHS)sys$exit(SS$_VECFULL);
		      chnl = 0;
/* Now we need a channel to the SW device. */
/* Use a kernel routine so we can get the info directly. */
		        swunit = 0;
		        swvalue_desc[0] = swvalue_length + (1<<24) + (DSC$K_DTYPE_T <<16);
		        k = sys$cmkrnl(&getswunit,&kargs1);
		        if (swunit != 0){
/* If the sw unit is explicit & nonzero, try to assign to it. If it 
   succeeds, use that unit. Otherwise assign a new SW unit and use that.
   Also detect unit in use if possible. Use ioc$searchdev for this
   from kernel mode so we don't try to create a device until we know we
   must. */
			  swchnl = 0;
		          swvalue_desc[0] = swvalue_length + (1<<24) + (DSC$K_DTYPE_T <<16);
		          k = (long)sys$assign(&swvalue_desc, &swchnl,0,0,0);
		          if (((k & 1) != 0)){
/* Ensure the driver really IS a swdriver unit */
			    setupcmd[0] = 8; /* check device is sw: */
			    kkkk = IO$_PHYSICAL + 128;
			    kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,0,0,0);
			    if ((iosb[0] != 6635) && (iosb[0] != 6631))sys$exit(SS$_UNSAFE);
/* Now send the command */

			    setupcmd[0] = 3; /* switch path of device */
                            setupcmd[1] = (kkkkk + (2<<16));
                            setupcmd[2] = value_length;
                            p = &setupcmd[3];
/* copy device name to the buffer now. */
                            memcpy(p,&value,value_length);
/* Send the command to swdriver now */
			    kkkk = IO$_PHYSICAL + 128;
			    kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,31416,0,0);
			    if ((iosb[0] & 1) == 0)sys$exit(SS$_NONEXDRV);
		          }
		        } else {
/* In this case we need an existing SW unit. */
		          sys$exit(SS$_BADPARAM);
                        }
                    }
                  }
                } /* unidle_desc */


/* hook passes server info to the SW device */
                if (CLI$PRESENT(&hook_desc) & 1){

/* We use the "disk" argument to carry the server's IPID */

/* first, be sure the "disk" argument exists. */
		  if (CLI$PRESENT(&unit_desc) & 1){
/* arg exists but we must ensure the value is there too. */
		    value_desc[0] = sizeof(value);
		    if (CLI$GET_VALUE(&unit_desc, &value_desc, &value_length) & 1){
		      value[value_length] = 0;
/* set up the descriptor */
		      value_desc[0] = value_length + (1<<24) + (DSC$K_DTYPE_T <<16);
/* Now get the binary value */
		      if (!isxdigit(value[0]))sys$exit(SS$_BADPARAM);
		      kkkkk = strtol(&value[0], (char**)NULL, 16);
		      chnl = 0;
/* Now we need a channel to the SW device. */
/* Use a kernel routine so we can get the info directly. */
		        swunit = 0;
		        swvalue_desc[0] = swvalue_length + (1<<24) + (DSC$K_DTYPE_T <<16);
		        k = sys$cmkrnl(&getswunit,&kargs1);
		        if (swunit != 0){
/* Require a real sw dvc here, not template. */
			  swchnl = 0;
		          swvalue_desc[0] = swvalue_length + (1<<24) + (DSC$K_DTYPE_T <<16);
		          k = (long)sys$assign(&swvalue_desc, &swchnl,0,0,0);
		          if (((k & 1) != 0)){
/* Ensure the driver really IS a swdriver unit */
			    setupcmd[0] = 8; /* check device is sw: */
			    kkkk = IO$_PHYSICAL + 128;
			    kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,0,0,0);
			    if ((iosb[0] != 6635) && (iosb[0] != 6631))sys$exit(SS$_UNSAFE);
/* Get mailbox name now */
		            if (CLI$GET_VALUE(&hook_desc, &value_desc, &value_length) & 1){
		              value[value_length] = 0;
/* set up the descriptor */
		              value_desc[0] = value_length + (1<<24) + (DSC$K_DTYPE_T <<16);

/* Now send the command */

			      setupcmd[0] = 6; /* switch path of device */
                              setupcmd[1] = kkkkk;
                              setupcmd[2] = value_length;
                              p = &setupcmd[3];
/* copy device name to the buffer now. */
                              memcpy(p,&value,value_length);
/* Send the command to swdriver now */
			      kkkk = IO$_PHYSICAL + 128;
			      kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,0,0,0);
			      if ((iosb[0] & 1) == 0)sys$exit(SS$_NONEXDRV);
		            }
		          }
		        } else {
/* In this case we need an existing SW unit. */
		          sys$exit(SS$_BADPARAM);
                        }
                    }
                  }
                } /* hook_desc */


/* Report should report statistics of all device paths available. */
                if (CLI$PRESENT(&report_desc) & 1){
/* first, be sure the "disk" argument exists. */

/* Get chnl to the SW unit */
/* Use a kernel routine so we can get the info directly. */
		        swunit = 0;
		        swvalue_desc[0] = swvalue_length + (1<<24) + (DSC$K_DTYPE_T <<16);
		        k = sys$cmkrnl(&getswunit,&kargs1);
		        if (swunit != 0){
/* If the sw unit is explicit & nonzero, try to assign to it. If it 
   succeeds, use that unit. Otherwise assign a new SW unit and use that.
   Also detect unit in use if possible. Use ioc$searchdev for this
   from kernel mode so we don't try to create a device until we know we
   must. */
			  swchnl = 0;
		          swvalue_desc[0] = swvalue_length + (1<<24) + (DSC$K_DTYPE_T <<16);
		          k = (long)sys$assign(&swvalue_desc, &swchnl,0,0,0);
		          if (((k & 1) != 0)){
/* Ensure the driver really IS a swdriver unit */
			    setupcmd[0] = 8; /* check device is sw: */
			    kkkk = IO$_PHYSICAL + 128;
			    kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,0,0,0);
			    if ((iosb[0] != 6631) && (iosb[0] != 6635))sys$exit(SS$_UNSAFE);
/* Retrieve information about the device we have bashed */
			    setupcmd[0] = 12; /* Get bash information */
			    kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,0,0,0);
/* No need for anything but the master UCB, but setupcmd[4] points to the
   PTHPTR structure for the path */
                            pptrs=(PTHPTR*)setupcmd[4];
                            pthchr[0]=0;
			    hstucb = (UCB*)setupcmd[9];
/* Now find and report the device name of our switched devices (main path) */
			    report_dvc(hstucb);
/* Now get our statistics */
			    setupcmd[0] = 4; /* report */
                            setupcmd[1] = 65535;
                            setupcmd[2] = value_length;
                            p = &setupcmd[3];
/* copy device name to the buffer now. */
                            memcpy(p,&value,value_length);
/* Send the command to swdriver now */
			    kkkk = IO$_PHYSICAL + 128;
			    kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,0,0,0);
			    if ((iosb[0] & 1) == 0)sys$exit(SS$_NONEXDRV);
/* Now the buffer has returned and we have info to report. Do so. */
			    nhosts = setupcmd[3];
                            k = setupcmd[4]; /* size of pthptr structure */
                            indrct = setupcmd[5];
                            printf("There are %d paths, current is %d\n",nhosts,indrct);
                            morptr = (char*)&setupcmd[8];
                            for (kk = 0; kk < nhosts; kk++){
                              sys$cmkrnl(&setpthchr,0);
                              printf(" Path %d, type: %s\n",kk,pthchr);
                              lmoreptr = (long*)morptr;
                              kkkk = *lmoreptr++;
                              kkk = *lmoreptr;
                              morptr = morptr + 4 * sizeof(long);
                              printf(" I/O count: %d,  Err count: %d\n",kkk,kkkk);
                              pptrs = pptrs + 1;
                            }
		          }
		        } else {
/* If the SW unit did not exist, then find what to use by using device 
   info instead. If THAT isn't intercepted, give up. */
		          sys$exit(SS$_BADPARAM);
                        }
                } /* report_desc */

/* switch switches to a different path */
                if (CLI$PRESENT(&pathchange_desc) & 1){

/* In general we expect disk paths to all have the same names, so we
   can only use a simple path number to specify which path to connect. */
/* However, use the "disk" argument. We just want it to be a number. */

/* first, be sure the "disk" argument exists. */
		  if (CLI$PRESENT(&unit_desc) & 1){
/* arg exists but we must ensure the value is there too. */
		    value_desc[0] = sizeof(value);
		    if (CLI$GET_VALUE(&unit_desc, &value_desc, &value_length) & 1){
		      value[value_length] = 0;
/* set up the descriptor */
		      value_desc[0] = value_length + (1<<24) + (DSC$K_DTYPE_T <<16);
/* Now get the binary value */
/* Exit if first char is not a digit. */
		      if (!isdigit(value[0]))sys$exit(SS$_BADPARAM);
		      kkkkk = atoi(&value[0]);
/* Reject path number if too large. For now MAXPATHS will be treated as 16 here */
		      if (kkkkk > MAXPATHS)sys$exit(SS$_BADPARAM);
		      chnl = 0;
/* Now we need a channel to the SW device. */
/* Use a kernel routine so we can get the info directly. */
		        swunit = 0;
		        swvalue_desc[0] = swvalue_length + (1<<24) + (DSC$K_DTYPE_T <<16);
		        k = sys$cmkrnl(&getswunit,&kargs1);
		        if (swunit != 0){
/* If the sw unit is explicit & nonzero, try to assign to it. If it 
   succeeds, use that unit. Otherwise assign a new SW unit and use that.
   Also detect unit in use if possible. Use ioc$searchdev for this
   from kernel mode so we don't try to create a device until we know we
   must. */
			  swchnl = 0;
		          swvalue_desc[0] = swvalue_length + (1<<24) + (DSC$K_DTYPE_T <<16);
		          k = (long)sys$assign(&swvalue_desc, &swchnl,0,0,0);
		          if (((k & 1) != 0)){
/* Ensure the driver really IS a swdriver unit */
			    setupcmd[0] = 8; /* check device is sw: */
			    kkkk = IO$_PHYSICAL + 128;
			    kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,0,0,0);
			    if ((iosb[0] != 6635) && (iosb[0] != 6631))sys$exit(SS$_UNSAFE);
/* now idle the unit */
			    setupcmd[0] = 3; /* switch path of device */
                            setupcmd[1] = (kkkkk + (1<<16));
                            setupcmd[2] = value_length;
                            p = &setupcmd[3];
/* copy device name to the buffer now. */
                            memcpy(p,&value,value_length);
/* Send the command to swdriver now */
			    kkkk = IO$_PHYSICAL + 128;
			    kkk = sys$qio(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,31416,0,0);
			    if ((iosb[0] & 1) == 0)sys$exit(SS$_NONEXDRV);
/* Now send the switch  command */
			    secctr = 0;
                            secflgs=0;
/* 40000000 hex should be 1.0 in .flg4 notation */
			    secnds[0] = 1073741900; /* 1.0+ in floating */
			    do {
			      secctr++;
			      setupcmd[0] = 3; /* switch path of device */
                              setupcmd[1] = kkkkk;
                              setupcmd[2] = value_length;
                              p = &setupcmd[3];
/* copy device name to the buffer now. */
                              memcpy(p,&value,value_length);
/* Send the command to swdriver now */
			      kkkk = IO$_PHYSICAL + 128;
			      kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,0,0,0);
/* Wait a bit before trying to switch paths to let I/O drain */
/* Don't do this more than 60 times. */
			      if ((iosb[0] & 1) == 0)lib$wait(&secnds,&secflgs);
			    } while (((iosb[0] & 1) == 0) && (secctr < 60));
			    if((iosb[0] & 1) != 0){
/* Now get the unit number. iosb[2] will return this. */
			      setupcmd[0] = 8; /* check device is sw: */
			      kkkk = IO$_PHYSICAL + 128;
			      kkk = sys$qiow(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,0,0,0);
/* Underlying devices can become unhappy unless they get a SCSI start so
   send a packack to them. Do this by sending I/O to the SW channel via its
   start - I/O, to the now current channel. */
			      kkkk = IO$M_FMODIFIERS >> IO$V_FMODIFIERS;
/* Mask off extra cruft */
			      kkkk = kkkk & 0xFFE0;
			      kkkk = kkkk + iosb[2];
			      kkkk = (kkkk << IO$V_FMODIFIERS) + IO$_PACKACK;
			      k = sys$qiow(11,swchnl,kkkk,&iosb,0,0,0,0,0,0,0,0);
			    }
/* now unidle the device */
			    setupcmd[0] = 3; /* switch path of device */
                            setupcmd[1] = (kkkkk + (2<<16));
                            setupcmd[2] = value_length;
                            p = &setupcmd[3];
/* copy device name to the buffer now. */
                            memcpy(p,&value,value_length);
/* Send the command to swdriver now */
			    kkkk = IO$_PHYSICAL + 128;
			    kkk = sys$qio(1,swchnl,kkkk,&iosb,0,0,&setupcmd[0],528,0,31416,0,0);
			    if ((iosb[0] & 1) == 0)sys$exit(SS$_NONEXDRV);
		          }
		        } else {
/* In this case we need an existing SW unit. */
		          sys$exit(SS$_BADPARAM);
                        }
                    }
                  }
                } /* pathchange_desc */



            }
          }
        }
    }

} /* end of main */

/* getswunit */
/* Function: Call ioc_std$searchdev to find out if a particular named SW
   device is there, and if so return its unit number. If it is not 
   findable, return zero (which is the template unit number) so we can
   assign a channel to the device and get the new UCB set up. By using
   this protocol, we ensure that if someone gives a specific SW unit
   (for example, SWA4:) and it is available, we can use it. */
/* Inputs: Descriptor of device name */
/* Output: unit number of the unit, or zero if it does not exist. */
long getswunit(void* namdsc, long* unit){
  UCB* pucb;
  DDB* pddb;
  SB* psb;
  int k;
  *unit = 0;
  srchnmd = namdsc;
  k = IOC_STD$SEARCHDEV(namdsc, &pucb, &pddb, &psb);
  srchstat = k;	/* Put the searchdev status where we can see it in user mode */
  if ((k & 1) != 0){
    *unit = pucb->ucb$w_unit;
  }
  return (k);
}

/* Report_dvc */
/* Purpose:
   Find the actual device name and print it given the UCB of the
   device. This is used in /report to show the actual name of the
   disk device, not just the SW device name, so that a user can have
   some idea what he is seeing.

   Input: hstucb - UCB address of disk

   Output: prints the device name
*/
void report_dvc(UCB* hstucb){
char nlnam[6]="NLA0:";
long nldsc[2]={5,&nlnam};
long k,kk,kkk,kkkk,kkkkk;
long dviitm[6];
long ddviitm[6];
long dvcnml;
char dvcnam[68];
long iosb[2];
int nlchnl;

    dviitm[0] = 64 + (DVI$_ALLDEVNAM << 16);
    dviitm[1] = &dvcnam[0];
    dviitm[2] = &dvcnml;
    dviitm[3] = 0;
    dviitm[4] = 0;
    nlchnl=0;
    if((long)hstucb >= 0)return;
/* Assign a nl: channel to use later */
    k = sys$assign(&nldsc,&nlchnl,0,0,0);
    if (nlchnl == 0)return;
/* setupcmd[3] returns path index in use */
    k_arg[0]=2;
    k_arg[1]=nlchnl;
    k_arg[2]=(long)&nlccb; /* ccb returns in nlccb */
    kkkkk = sys$cmkrnl(&ioc$chan_to_ccb,&k_arg[0]);
    if (nlccb != 0){
      k_arg[0] = 2; /* Call local kernel mode stuff to fill in channel */
      k_arg[1] = nlccb; /* Send in address of NL ccb */
      k_arg[2] = (long)hstucb; /* Pass address of struct SWdriver rtns */
      kkkkk = sys$cmkrnl(&bashnl,&k_arg[0]);
      if ((kkkkk & 1) != 0){
/* Now we have a channel to the disk we point to, so use $getdvi to get the
   device name (and save it!) and issue an INQUIRY to see if it is what
   we need, if it is a SCSI disk. */
        k = sys$getdviw(1,nlchnl,0,&dviitm,&iosb,0,0,0);
        kkkkk=sys$cmkrnl(&unbashnl,0);
        kk = sys$dassgn(nlchnl);
/* We now have the device name so print it */
      }        
      dvcnam[dvcnml] = 0;      
      printf(" Devices named %s Switching Statistics\n",dvcnam);
      return;
    }
}

/* bashnl */
/* Resets a channel to some other device. Use to give "quiet" channels to
   disks that won't interfere with mount etc. */
/* Useful only for local disks really 

   Inputs: inccb - CCB pointer to channel to the NLA0: device
	(expected to have been externally assigned)
           inpptr - pthptr structure pointing to a chosen device path

   Output:
           Changes the channel to point to the desired device.
           Returns success status if the device is a disk. Otherwise
           returns error.

   NOTE: Does not alter reference counts. Should be used only where
         the access needed must not interfere with normal device access
         (in particular, allocation, mount, etc.)
*/   
int bashnl(CCB* inccb, UCB* inucb){
  UCB* hucb;
  int stat;
  stat = 1;
/* Ensure that crude validity holds */
/* pthptr better be in system space */
  if ((long)inucb >= 0 )return(16);
/* inccb better look like a legal ccb address */
  if ((int)inccb == 0 || ((int)inccb > 0 && (int)inccb < 8192))return(32);
  hucb = inucb;
  nlucb = inccb->ccb$l_ucb;
/* Disk means it looks promising. */
  if (hucb->ucb$b_devclass == DC$_DISK){
    inccb->ccb$l_ucb = hucb;
  } else {
    stat = 8;
  }
  return (stat);
}

/* unbashnl */
/* Purpose: undo the changing of a channel to the NL device to a
   channel to disk. */
/*
   Inputs: nlccb and nlucb as set by bashnl
   Outputs: none
*/

void unbashnl() {
   nlccb->ccb$l_ucb = nlucb;
   return;
}
/* setpthchr */
/* Abstract: */
/* Uses global pptrs as PTHPTR structure pointer and is expected to
   set the pthchr char array with flags about what the nature of the
   path is. */
/* Environment:
   Kernel mode, IPL 0
*/
void setpthchr(){
   UCB* myucb;
   DDB* myddb;
   int k,kk;
   myucb=(UCB*)pptrs->ucb$l_hstucb;
   for (k=0; k < 7; k++){
     pthchr[k] = 0;
   }
/* Only report enabled paths */
   if ((long)myucb >= 0) return;
   if (pptrs->ucb$l_enapth <= 0)return;
   if ((myucb->ucb$l_devchar2 & DEV$M_MSCP) != 0){
     pthchr[0]='M';
     pthchr[1]='S';
     pthchr[2]='C';
     pthchr[3]='P';
     return;
   }
   myddb = myucb->ucb$l_ddb;
   if ((long)myddb >= 0)return;
   if ((char)myddb->ddb$l_port_id != 0){
     pthchr[0]='P';
     pthchr[1]='K';
     pthchr[2]=(char)myddb->ddb$l_port_id;
     return;
   } else {
     pthchr[0] = myddb->ddb$t_name[1];
     pthchr[1] = myddb->ddb$t_name[2];
     pthchr[2] = myddb->ddb$t_name[3];
     return;
   }
}
