evax = 1
alpha=1
bigpage=1
addressbits=32
step2=1
	.IDENT	'V01h'
; Copyright 1993,1994 Glenn C. Everhart
; All rights reserved
;  Author: Glenn C. Everhart
;
; mods:
; 30/jun/1994 GCE - Change kernel mapping logic to use a bitmap instead
; so we can basically map everything (to within an ambiguity factor).
; Use a 2KB buffer bitmap, which covers 16000 file numbers and will do
; a pretty good job of rejecting the rest. Then we can turn on the logic
; to only look at mapped files and save taking a performance hit on
; anything else (to all intents & purposes). For the moment just make the
; bitmap space a 2048 byte block constant in size for simplicity. In a
; later version we may make it vary in size. Use of this will allow us
; to protect ANY number of files even if the ACE gets deleted on them
; all...
; 7/7/94 gce - Deallocate LDT only AFTER the dowait call...
; 7/8/94 gce -step2 conversion begun
;
;
real_pvt=0	;define to include code that on bit 2048 prevents opens on
		;assigned devices, privs or not.
.ntype	__,R31			;  set EVAX nonzero if R31 is a register
.if eq <__ & ^xF0> - ^x50
EVAX = 1
.iff
;EVAX = 0
.endc
	.if	df,evax
evax = 1
alpha=1
bigpage=1
addressbits=32
;					;... EVAX=1 -> Step1
.iif ndf WCB$W_NMAP, evax=2		;... EVAX=2 -> Step2 (ndf as of T2.0)
.iif ndf WCB$W_NMAP, step2=1		;... EVAX=2 -> Step2 (ndf as of T2.0)
	.endc
;x$$$dt=0
	.if	ndf,evax
	.macro .jsb_entry
; jsb entry
	.endm
        .macro  driver_data
        .PSECT  $$$105_PROLOGUE
        .endm
        .macro driver_code
        .PSECT  $$$115_DRIVER
        .endm
	.endc
; above for Alpha only.
;
; function: "Tricks" driver.
;	Implements FDT capture (based on code published on sigtapes and
;	info-vax for "standard" capture techniques) and implements file
;	marking and transparent daemon access on open and various other
;	times. Also throws in fragmentation avoider.
;
; the driver works by intercepting FDT entries of a host driver and
; adding its own ahead of them. The most complex intercept is the
; open one (accfilt and on). It decides the i/o is of interest and
; issues its own $qio to read the file ACL to look for our ACE (application
; ACE, flagged by my initials). If this is found or the file is in an
; internal "look at" list then some actions happen immediately:
; setting privs/identifiers/base priority/softlinks. If the "send to
; daemon" flag is set in the ace (1 bit of flags) the open daemon
; (jtdmn) is sent a message and we wait on EF 31 till the daemon sends
; a special knl ast back. Note our qio thread produces a normal
; kernel AST and that does a special kernel AST from which the
; daemon call may be done. Once back, we reissue the FDT calls lower
; on kernel stack. On completing this we flag the mainline may go on
; set efn 31 and our "waitfor" cell, and go on. Once the mainline resumes
; it can delete the LDT (local area stuff) if appropriate or shorten
; it and free most of the memory grabbed during open. The open daemon
; can signal to either cause the i/o to fail or to make it seem to
; succeed without actually doing the open by appropriate return codes.
;
; delete and extend daemons are basically similar (as a directory daemon
; would be) but since they work on all files they omit the kernel thread
; that does its own I/O.
;
; For softlinks, in addition to moving file opens there 'n'
; back, keep a device table to let us refer to devices by using some
; hi bits in file ID & RVN areas to refer to rel. vol of a softlink-set
; by editing dir files on the way by when opened to have these bogus
; file IDs. On create,  must check DID field & see if we have an LDT
; with a flag for that dir, so we'd again clobber create (& user chnl) to
; point at the other disk during time the file's open. That may let us
; produce the illusion that softlinked dirs really "are" on the current
; volume.
;
; We will support file moving, delete management, dynamic priority,
; privs & identifiers, daemon-based additional access controls, 
; (which might be file integrity tests & conditional softlinks),
; and space management eventually. Also we'll eventually support
; special action on directore read-in so the dirs' files get arbitrated
; by a daemon instead of directly read. That'll let them be treated as
; softlinks too without having to clutter the disk, (ever maybe)
; with junk file headers.
; (juicer has dir layout docs in its comments.)
;
; Initial version basically to support security & limited hsm stuff,
; not ALL softlink possibilities.
;
; For a follow-on, we will add support for removing file headers completely.
; Note that we can do a softlink to other files so long as we can tell
; that the file should be so linked. Having a bitmap to let us filter
; out uninteresting files, our open daemon can tell that. To handle requests
; other than open we need to catch access without io$m_access, io$_acpcontrol,
; and some other functions of io$_modify and maybe io$_delete (depends on
; policy decision...SHOULD a file "somewhere else" be deleted apart from
; its "home" location...I think not, so just fake success for that). Where
; these don't open a file, we do the softlink by resetting the IRP only,
; not the channel too. Thus no catching logic is needed to put the user
; channel back.
;  By catching I/O in this way we can read in a linked directory from
; "somewhere" and just tag those file IDs as being in our bitmap, and
; record for the daemon that those files are on device xxx:, then let
; the daemon return the correct device softlink info to let the file
; be accessed at its home. The directory would reside on the local
; disk. When a file got created in it, the directory would get a real
; entry. An inswap would need the "somewhere" entry to be removed and
; a real one added and the daemon's data telling it where the real file
; was reset. At each directory open for such a directory that was "really"
; somewhere else, though, a merge of the then-current remote directory
; and the local one would be needed, concurrently updating the daemon's
; database, so new files created on the other device would appear in
; the local file, but files really on the local disk would appear. There
; could be problems where the directory read was from the XQP with
; this, though, so we may want to just pull the directory in every time
; regardless. We get control ahead of the XQP, but must ensure all
; processing of this kind is in another process, as the XQP is not in
; an interruptable point for this stuff.
;
;  It is possible to have directory entries pointing at nonexistent
; file headers and file IDs and have the access daemon (that handles
; io$_access, io$_acpcontrol etc.) generate a softlink to some real
; file on the fly. If the nonexistent directory entries are flagged by,
; say, bit 8 of the RVN being set, it might be possible to reset the
; access to the desired one in kernel mode. If done by a daemon using
; some other flagging (a bitmap maybe? Or maybe use RVN 255?) then
; the daemon is responsible for setting the correct FID and device into
; the request and device can be stored in the daemon database. Thus
; an outswapped file could be completely offline, yet the directory
; entry could be "there", and in moments of not inswapping it might be
; left pointing at some scratch file header that would point at a zero
; length file, via dynamic softlink, so that directory entries and so
; on would succeed even though their info would be fairly useless.
; (It's possible to reset the EOF pointer of these to the right size
; and possibly reset the date before each access should we choose to
; do so; file would be zero size anyhow. If a daemon access is used,
; the extra overhead of resetting date every time and so on might not
; be too bad. It would mean that the outswapped file size and at least
; creation date would still be visible even if the file ID was actually
; useless.)
; A second release would be usefully able to perform these operations
; so that outswapped file headers could be purged away, yet directory
; operations would continue to be able to show the files. This would mean
; that the directory files would continue to be large, but the index
; file would not grow boundlessly. If such a directory were outswapped,
; it might be inswapped later,being able to outswap only if it had no
; current files. This is a little easier than softlinking directories
; since no merging is needed. Directory opens are usually done by
; RMS in exec mode, and this would make it easy to shove an outswapped
; directory back in, from our daemon, doing so before the XQP actually
; gets the request queued to move anything. We'd need extra data in
; a daemon database and possibly in a knl mode bitmap to flag what
; was a directory; an outswapped directory might need to be flagged
; with an otherwise illegal FID so we would have a sure and certain
; tag to use on it. Inswapping it would then have to replace the
; FID in the parent directory. The directory file format seems not to
; have any checksums, so this will be comparatively clean and simple.
; Flagging in this way would allow clear detection of directory files,
; though the daemon would need to back up the test with its own data
; so inadvertent bogus matches would just be allowed to continue. (One
; might coopt RVN 255 and RVN 254 for directories and files respectively
; where one wanted a simple tag that could be used to recognize swapped
; dirs and files.) This way one could get rid of file headers off a disk,
; and periodically trim off directory files, yet the directories would
; still apparently be there if anyone looked (and if a mode to open
; them were selected; if not, the directory file could just be pointed
; at some ordinary file and the open would NOT show another directory).
;   Normally you'd want to limit depth of opening old outswapped directories
; by telling the daemon how old a directory might be and still be opened
; (so a simple dir [...]*.* doesn't inswap everything unless that's really
; wanted). Since you'd be regulating at the granularity of directories
; filetype cuts might not show up, but directories would. Users could 
; reset directory creation dates with the FILE utility or similar if
; they wanted to keep these dates useful. A script to reset directory
; revision dates to the date of last file creation should be supplied
; to be run periodically, so that this information would be more
; useful; VMS normally doesn't maintain it.
;
; Glenn C. Everhart, November 1993
;
;vms$$v6=0	;add forvms v6 def'n
vms$v5=1
; define v5$picky also for SMP operation
v5$picky=1
	.SBTTL	EXTERNAL AND LOCAL DEFINITIONS

; 
; EXTERNAL SYMBOLS
; 
	.library /SYS$SHARE:LIB/

;	$ADPDEF				;DEFINE ADAPTER CONTROL BLOCK
	$CRBDEF				;DEFINE CHANNEL REQUEST BLOCK
	$DYNDEF ;define dynamic data types
	$DCDEF				;DEFINE DEVICE CLASS
	$DDBDEF				;DEFINE DEVICE DATA BLOCK
	$DEVDEF				;DEFINE DEVICE CHARACTERISTICS
	$DPTDEF				;DEFINE DRIVER PROLOGUE TABLE
	$EMBDEF				;DEFINE ERROR MESSAGE BUFFER
	$IDBDEF				;DEFINE INTERRUPT DATA BLOCK
	$IODEF				;DEFINE I/O FUNCTION CODES
	$DDTDEF				; DEFINE DISPATCH TBL...
	.if df,step2
	ddt$l_fdt=ddt$ps_fdt_2
	.endc
	$ptedef
	$vadef
	$IRPDEF				;DEFINE I/O REQUEST PACKET
	$irpedef
	$PRDEF				;DEFINE PROCESSOR REGISTERS
	$SSDEF				;DEFINE SYSTEM STATUS CODES
	$UCBDEF				;DEFINE UNIT CONTROL BLOCK
	.if	df,step2
	$fdt_contextdef
	.endc
	$sbdef	; system blk offsets
	$psldef
	$prdef
	$acldef
	$rsndef				;define resource numbers
	$acedef
	$VECDEF				;DEFINE INTERRUPT VECTOR BLOCK
	$pcbdef
	$statedef
	$jibdef
	$acbdef
	$vcbdef
	$arbdef
	$wcbdef
	$ccbdef
	$fcbdef
	$phddef
        $RABDEF                         ; RAB structure defs
        $RMSDEF                         ; RMS constants
; defs for acl hacking
	$fibdef
	$ipldef
	$atrdef
	.globl accfilt
	.globl	gceala
	.globl acllit
	.globl dowait
	.globl findldt
	.globl gceaba
	.globl	gceacl
	.globl	gcetpl
	.globl	getjtucb
	.globl	popout
	.globl	pors
	.globl	stp2bad
	.globl	v15a

p1=0	; first qio param
p2=4
p3=8
p4=12
p5=16
p6=20	;6th qio param offsets

	.IF	DF,VMS$V5	;VMS V5 + LATER ONLY
	$SPLCODDEF
	$cpudef
	.ENDC
; 
; UCB OFFSETS WHICH FOLLOW THE STANDARD UCB FIELDS
; 
	$DEFINI	UCB			;START OF UCB DEFINITIONS

;.=UCB$W_BCR+2				;BEGIN DEFINITIONS AT END OF UCB
.=UCB$K_LCL_DISK_LENGTH	;v4 def end of ucb
; USE THESE FIELDS TO HOLD OUR LOCAL DATA FOR VIRT DISK.
; Add our stuff at the end to ensure we don't mess some fields up that some
; areas of VMS may want.
; Leave thisfield first so we can know all diskswill have it at the
; same offset.
;
;
$def	ucb$l_hucbs	.blkl	1	;host ucb table
;
; Add other fields here if desired.
;
$def	ucb$l_exdmn	.blkl	1	;extend dmn pid
$def	ucb$l_exmbx	.blkl	1	;extend dmn mbx ucb
$def	ucb$l_deldmn	.blkl	1	;delete daemon pid
$def	ucb$l_delmbx	.blkl	1	;delete dmn mailbox ucb
;
;
$def	ucb$l_ctlflgs	.blkl	1		;flags to control modes
;
;
$def	ucb$l_prcvec	.blkl	1		;process local data tbl
$def	ucb$l_daemon	.blkl	1		;daemon pid for open daemon
$def	ucb$l_mbxucb	.blkl	1		;mailbox for input to daemon
$def	ucb$l_keycry	.blkl	2		;ucb resident "key" for ACEs
						;use as part of authenticator
						;for security-relevant fcns.
		;auth=f(file id, key, priv-info), match ace and computed
		;auth tag.
$def	ucb$l_cbtctr	.blkl	1		;how many extents
$def	ucb$l_cbtini	.blkl	1		;init for counter
; preceding 2 fields allow specifying of contig-best-try extents
; on every Nth extend, not every one. This should still help keep
; file extensions from preferentially picking up chaff
$def	ucb$JTcontfil	.blkb	80
$def	ucb$l_asten	.blkl	1		;ast enable mask store
;
$DEF	ucb$l_minxt	.blkl	1		;min. extent
$def	ucb$l_maxxt	.blkl	1		;max extent
$def	ucb$l_frac	.blkl	1		;fraction to extend by
$def	ucb$l_slop	.blkl	1		;slop blocks to leave free
; DDT intercept fields
; following must be contiguous.
$def    ucb$s_ppdbgn            ;add any more prepended stuff after this
$def    ucb$l_uniqid    .blkl   1       ;driver-unique ID, gets filled in
                                        ; by DPT address for easy following
                                        ; by SDA
$def    ucb$l_intcddt   .blkl   1       ; Our interceptor's DDT address if
                                        ; we are intercepted
$def    ucb$l_prevddt   .blkl   1       ; previous DDT address
$def    ucb$l_icsign    .blkl   1       ; unique pattern that identifies
                                        ; this as a DDT intercept block
; NOTE: Jon Pinkley suggests that the DDT size should be encoded in part of this
; unique ID so that incompatible future versions will be guarded against.
$def    ucb$s_ppdend
$def    ucb$a_vicddt    .blkb   ddt$k_length
                                        ; space for victim's DDT
			.blkl	4	;safety
$def	ucb$l_backlk	.blkl	1	;backlink to victim ucb
; Make the "unique magic number" depend on the DDT length, and on the
; length of the prepended material. If anything new is added, be sure that
; this magic number value changes.
magic=^xF013F000 + ddt$k_length + <256*<ucb$s_ppdend-ucb$s_ppdbgn-16>>
p.magic=^xF013F000 + ddt$k_length + <256*<ucb$s_ppdend-ucb$s_ppdbgn-16>>
	.iif ndf,f.nsiz,f.nsiz=2048
	.iif	ndf,f.nums,f.nums=16
	.iif	ndf,f.nsiz,f.nsiz=2048
ucb$l_fnums:	.blkw	f.nums	;store for file numbers to inspect whether
				;an ACE is there or not.
$DEF	UCB$L_JT_HOST_DESCR	.BLKL	2	;host dvc desc.
;
; Store copy of victim FDT table here for step 2 Alpha driver.
; assumes FDT table is 64+2 longs long
$def	ucb$l_myfdt	.blkl	70	;user FDT tbl copy + slop for safety
$def	ucb$l_oldfdt	.blkl	1	;fdt tbl of prior fdt chain
$def	ucb$l_vict	.blkl	1	;victim ucb, for unmung check
$def	ucb$l_mungd	.blkl	1	;munged flag, 1 if numg'd
$def	ucb$l_exempt	.blkl	4	;exempt PIDs
$DEF	UCB$K_JT_LEN	.BLKW	1	;LENGTH OF UCB
;UCB$K_JT_LEN=.				;LENGTH OF UCB

	$DEFEND	UCB			;END OF UCB DEFINITONS
; Define LDT offsets here.
ldt$l_fwd	=	0		;forward link. (LDTs are singly linked)
ldt$l_ccb	=	4		;CCB address so we can check ID
ldt$l_accmd	=	8		;accmd from user FIB (tells how open)
ldt$l_wprv	=	12		;working privs
ldt$l_aprv	=	20		;auth privs
ldt$l_bprio	=	28		;process base priority
ldt$l_prcstr	=	32		;pointer to per-process delblk count block
ldt$l_synch	=	36		;address of "iosb" block used to
					;end process waits & deallocated at
					;end of those waits.
ldt$l_iosb	=	40		;iosb for internal $qio
ldt$l_jtucb	=	48		;pointer to jt: ucb
ldt$l_fresiz	=	52		;length of LDT left since we will chop
					;off unused parts of ACE after we read
					;it to regain pool
; Keep chnucb in "permanent" part of LDT since it hangs around till close
; if we do a softlink. It will be zero unless there is a softlink so
; it acts as a flag to restore the channel, too.
ldt$l_chnucb	=	56		;original channel UCB address
ldt$l_softf	=	60		;flag if nonzero that we have softlink
ldt$l_ace	=	64		;start of our ACE, up to 256 bytes long
; chop off what's below here, as we need it no more after the file is open.
ldt$l_regs	=	320		;register save, r0 to r15
ldt$l_flgs	=	376		;slop storage for flags
ldt$l_parm	=	380		;storage for up to 6 params (6 longs)
ldt$l_fib	=	404		;FIB we use for OUR I/O
; 72 bytes max for our FIB
ldt$l_acl	=	476		;storage for ACL read-in; 512 bytes
ldt$l_itmlst	=	988		;item list to read the ACL all in if
					;we can.
ldt$l_aclsiz	=	1020		;size of the ACL on the file
ldt$l_rtnsts	=	1024		;status back from daemon
ldt$l_myfid	=	1032		;file id from read-acl call
ldt$l_mydid	=	1040		;dir id in user's fib
ldt$l_psl	=	1048		;psl of original i/o
ldt$l_fnd	=	1056		;filename desc of orig i/o (p2 arg)
					;2 longs
ldt$l_fndd	=	1064		;data area for filename (256 bytes)
ldt$l_fdtctx	=	1324		;save area for user's FDT context ptr
ldt$l_size	=       1332
ldt$k_clrsiz	=	1328		;allocate a little slop.

; ACE format:
;ace:	.byte	length
;	.byte	type = ace$c_info ;application ACE
;	.word	flags		;stuff like hidden, protected...
;	.long	info-flags	;use 1 bit to mean call the daemon
;	.ascii	/GCEV/		;my identifier
;	.blkb	data		;up to 244 bytes of data.

; data is a variable length list of stuff.
; Codes are as follows:
; 00 - nothing. Terminates list.
; 01 - starts "inspectme" record. Nothing more. We send FID from the LDT
;		in this case. This makes these real fast to forge.
; 02 - "moveme" record. Again we send FID from LDT and need nothing more.
;		We use info from the daemon to find the actual file based
;		on the file ID here.
; 03 - "bprio" record. Format:
;	03, prio, <long auth info>	;total 6 bytes
; 04 - "priv" record. Format:
;	04, <priv quadword> <auth quadword>	;total 17 bytes
; 05 - "ident" record, format:
;	05, <ident quadword> <auth quadword>	;total 17 bytes
; 06 - "softlink" record, format:
;	06, len, flgs, <file id to link to> <devicename> ;variable len
; 07 - "temporary" tag. Format:
;	07, len, <orig file id>, <sys time quadword when created> ;16 bytes
; flags for softlinks:
;	0 = normal
;	1 = softlink only on read, act like moveme record if r/w open
;	2 = directory file softlink, pass to daemon for special
;		handling so we can pull the dir in.
; more flags later as I think of them.
; more types as needed too.
;
; Few macros for long distance branches...
;
	.macro	beqlw	lbl,?lbl2
	bneq	lbl2
	brw	lbl
lbl2:
	.endm
	.macro	bneqw	lbl,?lbl2
	beql	lbl2
	brw	lbl
lbl2:
	.endm
	.macro	bleqw	lbl,?lbl2
	bgtr	lbl2
	brw	lbl
lbl2:
	.endm
	.macro	bgeqw	lbl,?lbl2
	blss	lbl2
	brw	lbl
lbl2:
	.endm
; allocate does not zero its result area.
; This macro makes it easy to zero an allocated area before using it.
; Leaves no side effects...just zeroes the area for "size" bytes
; starting at "addr".
	.macro	zapz	addr,size
	pushr	#^m<r0,r1,r2,r3,r4,r5>	;save regs from movc5
	movc5	#0,addr,#0,size,addr
	popr	#^m<r0,r1,r2,r3,r4,r5>	;save regs from movc5
	.endm

	driver_data
	.SBTTL Our FDT Filter Routines
; These routines are edited from the JTdriver versions to call
; getJTucb, assuming they are called with R5 pointing at the patched
; driver's UCB.
; INPUTS:
; 
; 	R3	- IRP ADDRESS (I/O REQUEST PACKET)
; 	R4	- PCB ADDRESS (PROCESS CONTROL BLOCK)
; 	R5	- UCB ADDRESS (UNIT CONTROL BLOCK)
; 	R6	- CCB ADDRESS (CHANNEL CONTROL BLOCK)
; 	R7	- BIT NUMBER OF THE I/O FUNCTION CODE
; 	R8	- ADDRESS OF FDT TABLE ENTRY FOR THIS ROUTINE
; 	(AP)	- ADDRESS OF FIRST QIO PARAMETER
; Filter routines.
; These do the interesting stuff.
;
;AccFilt: Handles open (io$_access) requests.
; Operation:
; 1. Check that access is really OK here (not our own daemons, not
;	our own internal I/O, either dummy FID to call daemon at once
;	or not in our job, and that function has io$m_access bit
;	set if not a bogus fid (& relevant fcnmsk bit).
; 2. Store the I/O context (registers etc.) in a structure called
;	our Local Data Table (LDT)
; (note: skip 2-5 if dummy FID & just call daemon if needed)
; 3. Start an i/o thread to read the ACL in. Note we make mainline wait
;	via waitfor ef#31 and loop till our local data structure says
;	we got r0 return from USER'S i/o. Flag nodelete till done
;	unless it was set at start. Use per-process counter to do
;	the nodelete state right.
; 4. If our ACE is there (3rd long containing "GCEV") then store it in
;	our structure for later. Junk stuff we don't need any more.
; 5. ASTs (knl, -> sp. knl) of internal I/O get to skast state.
; 6. If ACE says to call daemon, or if ACL not all there and our ACE not
;	seen, call daemon (in latter case flagging to read ACL one ACE at
;	a time)
; 7. Either direct or from SKAST from daemon return, restore regs
;	& context and issue user i/o. Unblock mainline once we get
;	r0 status from that i/o (return approp. value) and undo
;	no-delete, no-suspend flagging of process. Free knl stuff if
;	no need for it, or leave it for delete FDT processing.
;
AccFilt: $driver_fdt_entry
; skip kernel channels
	bitb	#3,irp$b_rmod(r3)	;see if any nonknl bits are there
	bneq	1$			;if neq yes, ok to continue
2$:
	bsbw	pors
	ret
;	brw pors			;no, cannot munge knl packet
1$:
; Also check quotas like the DEC FDT routines do to ensure quotas are
; not going to be violated. No need to go further if so.
	movl	pcb$l_jib(r4),r1	;get the JIB
	.if	df,evax
	tstl	jib$l_filcnt(r1)	;got any files left?
	.iff
	tstw	jib$w_filcnt(r1)	;got any files left?
	.endc
	bleq	2$			;no, skip now.
;check device not mounted, mounted, shadowset part etc.
	bbs	#dev$v_dmt,ucb$l_devchar(r5),2$
	bbc	#dev$v_mnt,ucb$l_devchar(r5),2$
	bitl	#<DEV$M_SSM!DEV$M_SHD>,ucb$l_devchar2(r5)
	bneq	2$
	bbs	#dev$v_for,ucb$l_devchar(r5),2$
; Quotas and so on look OK. Can't economically check more here.
	pushr	#^m<r0,r5>
; original r5 now at 4(sp). Must get that to continue the ops.
	jsb	getJTucb		;find JTdriver ucb
	tstl	r0
	blss	509$
	popr	#^m<r0,r5>
	bsbw	popout
	ret
509$:
;	bgeqw	popout
	movl	r5,ucb$l_backlk(r0)	;save link'd ucb in ours too.
	movl	r0,r5			;point R5 at JT UCB
	bitl	#1,ucb$l_ctlflgs(r5)	;doing this filtering?
	bneq	1509$
	popr	#^m<r0,r5>
	bsbw	popout
	ret
1509$:
;	beqlw	popout
; Make sure this isn't one of OUR daemons
	cmpl	pcb$l_pid(r4),ucb$l_daemon(r5)	;open etc. daemon?
	bneq	2509$
3509$:	popr	#^m<r0,r5>
	bsbw	popout
	ret
2509$:
;	beqlw	popout
	cmpl	pcb$l_pid(r4),ucb$l_exdmn(r5)	;not extend daemon
	beqlw	3509$
	cmpl	pcb$l_pid(r4),ucb$l_deldmn(r5)
	beqlw	3509$			;not delete daemon
	cmpl	pcb$l_pid(r4),ucb$l_exempt(r5)	;exempted pid?
	beqlw	3509$
	cmpl	pcb$l_pid(r4),ucb$l_exempt+4(r5) ;exempted pid?
	beqlw	3509$
	cmpl	pcb$l_pid(r4),ucb$l_exempt+8(r5) ;exempted pid?
	beqlw	3509$
	cmpl	pcb$l_pid(r4),ucb$l_exempt+12(r5) ;exempted pid?
	beqlw	3509$
;make sure not a knl mode channel (leave the XQP channel alone!!!)
	cmpb	ccb$b_amod(r6),#1	;this the XQP's chnl?
; if less than 1 skip too...though that isn't supposed to happen
	bleqw	3509$			; if so scram NOW.
	bitl	#1024,ucb$l_ctlflgs(r5)	; checking for bogus FIDs?
	beql	3$			; if eql no
	.if	df,evax
	movl	irp$l_qio_p1(r3),r0	;get P1 param
	.iff
	movl	p1(ap),r0
	.endc
	beql	3$
	movl	4(r0),r0		;point at user FIB
	beql	3$			; skip if none there
; fid = fileno, fileseq, rvn, filenohi
; if filenohi .gt.128 and rvn .gt.128 as unsigned numbers then
; treat the operation here.
; Other h.o. bits are used to act as device switches here if this
; is selected. This requires of course that real volume sets
; be limited to maybe 32 volumes and that real maxfiles be less
; than the 24 bits' worth, for those volumes monitored here. Since
; this monitor is per disk, the function CAN just be disabled on large
; volume sets.
	bitb	#128,fib$w_fid+4(r0)	;rvn bit set?
	beql	3$
	bitb	#128,fib$w_fid+5(r0)	;hi fileno set?
	bneq	4$			;if so skip open funct. test
3$:
	.if	df,evax
	bitl	#<io$m_access>,irp$l_func(r3)	; see if this is really an OPEN
	.iff
	bitw	#<io$m_access>,irp$w_func(r3)	; see if this is really an OPEN
	.endc
	beqlw	3509$			;if not, scram
	brb	93$
4$:
; If here, we have a file id that appears fake and are flagging
; such. Arrange to call the daemon in that case.
; Note no LDT exists yet, so we'll do tests later, before issuing
; our own i/o, to test this.
93$:
; Ensure the file is not already open too, like DEC FDT routines do.
	tstl	ccb$l_wind(r6)		;if a window exists, open now
	bneqw	3509$			;so scram fast.
;
; Now ensure that this call is not in the same JOB as the daemon.
; (This lets the daemon spawn processes to do some work.)
	pushr	#^m<r6,r7,r8,r9,r10,r11>	;get some regs
	movl	ucb$l_daemon(r5),r10	;get the daemon PID
	bleq	5$
	movzwl	r10,r7			;get process index
; like code in FQdriver...
	movl	 g^sch$gl_pcbvec,r6	;get pcb vector
	movl	(r6)[r7],r8		;get a PCB address
	tstl	r8			;ensure a system addr
	bgeq	5$			;skip if not
	cmpl	r10,pcb$l_pid(r8)	;be sure this is the daemon process
	bneq	5$			;else skip
; ok, we for sure have the daemon's PCB now. See if the JIBs match
	cmpl	pcb$l_jib(r8),pcb$l_jib(r4)	;same JIB as daemon's?
	bneq	5$			;if not, don't skip out
	popr	#^m<r6,r7,r8,r9,r10,r11>	;get regs back now
48$:	brw	3509$			;then buzz off
5$:
	popr	#^m<r6,r7,r8,r9,r10,r11>	;get regs back now
; Ensure this is not our own internal IRP by checking vs the AST address in
; the IRP.
	movab	vcstp15,v15a
	cmpl	v15a,irp$l_ast(r3)	;our IRP should be skipped
	beqlw	3509$
;
; Add "keep private volumes really private" by seeing if the volume
; owner (ucb$l_pid) is nonzero and if it is, if it does not match
; irp$l_pid then fail this i/o.
	.if	df,real_pvt
	pushl	r0
	bitl	#2048,ucb$l_ctlflgs(r5)	;2048 bit means keep pvt dvc pvt
	beql	148$
	movl	ucb$l_backlk(r5),r0	;get original ucb
	tstl	ucb$l_pid(r5)		;device owned by a pid?
	beql	148$			;if eql no, skip out
	tstl	irp$l_pid(r3)		;can't check internal irps
	blss	148$
	cmpl	irp$l_pid(r3),ucb$l_pid(r5)	;this i/o from owner?
	beql	148$			;yah...let it by
; I/O from someone else. Return error...
	popl	r0
	popr	#^m<R0,R5>		;restore regs now
	movl	#ss$_nopriv,r0		;this is the error
	call_abortio
	ret
;	jmp	g^exe$abortio		;so stop the open HERE.
148$:
	popl	r0
	.endc
; if we want to check only files in our store, do the following...
; In some cases this will reduce overhead a LOT.
	bitl	#^x40000,ucb$l_ctlflgs(r5)	;check magic bit
	beql	50$
	pushr	#^m<r0,r1,r2,r3>	;need some regs
	.if	df,evax
	movl	irp$l_qio_p1(r5),r0	;get FIB desc
	.iff
	movl	p1(ap),r0
	.endc
	beql	47$
	movl	4(r0),r0		;get fib addr
	beql	47$
	movzwl	fib$w_fid(r0),r1	;get file number (check numbers
					; to save space)
	beql	47$			; look, don't skip, if no filenum
	.if	df,wd.lst
	movl	#f.nums,r2		; get size of store
	movab	ucb$l_fnums(r5),r3	; point at store
49$:	cmpw	(r3)+,r1		; same file number?
	beql	47$			; if so go ahead
	sobgtr	r2,49$
	.iff	;bitmap
	.iif df,x$$$dt,jsb g^ini$brk ;**************** debug ***************
	.iif	ndf,f.nums,f.nums=16
	.iif	ndf,f.nsiz,f.nsiz=2048
	movl	#f.nsiz,r2		; size of array
	movl	ucb$l_fnums(r5),r3	; get storage area
	beql	47$			; no bitmap means look
; r1 is file number...
	.iif	ndf,f.mask,f.mask=-16384 ;max bits to use in bitmap check
	bicl	#f.mask,r1		;clear extra bits
	ashl	#-3,r1,r2		;r2 gets byte offset into bitmap
	addl3	r3,r2,r0		;get the address
	bicl	#-8,r1			;isolate bit in byte now (0-7)
	bbs	r1,(r0),47$		;if the bit is zero, not here
					;if the bit is set, though, go fer it
	.endc
; fall thru...no match
	popr	#^m<r0,r1,r2,r3>
	popr	#^m<r0,r5>
	bsbw	popout
	ret
47$:
	popr	#^m<r0,r1,r2,r3>
50$:
; Looks like we need to deal with this IRP.
; First allocate some space to save the I/O context and find where this
; operation's LDT should be added.
; Do this from device IPL and save registers since we need them here.
	pushl	r0
	pushl	r1
	pushr	#^m<r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
	devicelock lockaddr=ucb$l_dlck(r5), -
	 lockipl=ucb$b_dipl(r5),preserve=YES
	jsb	findldt			;get our LDT if any. (normally none)
	tstl	r0			;did we find one ready?
; must reallocate if we found one...should never get one twice
	beql	55$			;if eql, good, no LDT. Grab one from pool.
;got an ldt. Free it up.
	pushl	r1
; point past this LDT so link is ok
	movl	ldt$l_fwd(r0),ldt$l_fwd(r1)	;remove this ldt from chain
; r0 = addr = ldt
	movl	ldt$l_fresiz(r0),r1	;get size
	jsb	g^exe$deanonpgdsiz	;free it
	popl	r1
;ok, now the bogus LDT is gone. Get a new one.
55$:	tstl	r1		;got a valid pointer?
	beqlw	2000$		;if not, skip out
	pushl	r1
	movl	#ldt$l_size,r1	;ldt size to get
	jsb	g^exe$alonpagvar	;go get pool
	popl	r1
	blbs	r0,56$		;if ok, go on
989$:	brw	2000$		;else skip out.
56$:
	movl	r2,(r1)		;point link at this one
	movl	r1,r9		;save copy here
	clrl	ldt$l_fwd(r2)	;zero our fwd pointer
	movl	#ldt$k_clrsiz,r10
	zapz	(r2),r10	;clear entire LDT out fassstt
; now wee have the LDT created. Set it up.
	movl	#ldt$l_size,ldt$l_fresiz(r2)	;set up the size to free
	movl	r6,ldt$l_ccb(r2)	;claim the LDT for us
	movl	r2,r11		;want the LDT less volatile
; Need to set up the process structure here. Since findldt doesn't
; return it, wee need to get it directly off the UCB.
	pushr	#^m<r2,r3>
	movl	ucb$l_prcvec(r5),r1	;start of ldt chain
	bgeq	999$		;lose if none
	movzwl	pcb$l_pid(r4),r2	;get index
	ashl	#5,r2,r2		;get tbl entry offset
; shift 5 so 32 bytes = 8 longs per entry
	addl3	r2,r1,r3		;point r3 at our slot
	movl	r3,r1			;let r1 return as link addr
999$:
	popr	#^m<r2,r3>
	movl	r1,ldt$l_prcstr(r11)	;set up pointer to process struct
	bgeq	989$
	addl2	#8,ldt$l_prcstr(r11)	;pass LDT base info to get to our counters
; allocate the synch structure we need now.
; (if we keep ldt allocated till wait falls thru and dealloc after
;  then we may be able to just use the ldt here though.)
	movl	#16,r1
	jsb	g^exe$alonpagvar
	blbs	r0,57$			;if all well, fine
;no aux struct so skip out
	clrl	(r9)		;clr pointer to ldt
	movl	r11,r0		;addr to free
	movl	ldt$l_fresiz(r11),r1	;size to free
	jsb	g^exe$deanonpgdsiz
	brw	2000$		;skip out
57$:
	movl	r2,ldt$l_synch(r11)	;save pointer to synch block
	clrq	(r2)			;set it initially null
	movl	r11,r1			;save ldt pointer
	movpsl	ldt$l_psl(r11)	;save original psl of request for later
	insv	#2,#psl$v_ipl,#psl$s_ipl,ldt$l_psl(r11) ;enforce ipl2
	movab	ldt$l_regs(r11),r0	;where to save regs
	popr	#^m<r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
	movl	r2,(r0)+
	movl	r3,(r0)+
	movl	r4,(r0)+
	movl	r5,(r0)+	;save all registers.
	movl	r6,(r0)+	;use movl since we don't know its
	movl	r7,(r0)+	;quadword aligned.
	movl	r8,(r0)+
	movl	r9,(r0)+
	movl	r10,(r0)+
	movl	r11,(r0)+	;save all registers
	pushr	#^m<r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
	movl	r1,r11		;r11 is again the LDT
	movl	irp$ps_fdt_context(r3),ldt$l_fdtctx(r11) ;save FDT context addr
; now fix up saved R5 to point at original intercepted ucb
	movl	ucb$l_backlk(r5),ldt$l_regs+12(r11)
	movab	ldt$l_parm(r11),r0	;save qio params
	.if	df,evax
	movl    irp$l_qio_p1(r3),(r0)+
	movl    irp$l_qio_p2(r3),(r0)+
	movl    irp$l_qio_p3(r3),(r0)+
	movl    irp$l_qio_p4(r3),(r0)+
	movl    irp$l_qio_p5(r3),(r0)
	.iff
	movl	p1(ap),(r0)+
	movl	p2(ap),(r0)+
	movl	p3(ap),(r0)+
	movl	p4(ap),(r0)+
	movl	p5(ap),(r0)+
	.endc
; get the params like user FIB stuff...
	.if	df,evax
	movl	irp$l_qio_p1(r3),r10	;fib desc.
	.iff
	movl	p1(ap),r10
	.endc
	movl	4(r10),r10		;point at fib ityself
	movl	fib$l_acctl(r10),ldt$l_accmd(r11)	;save "how open"
	.if	df,evax
	movl	pcb$l_prib(r4),ldt$l_bprio(r11)		;save base prio
	.iff
	movzbl	pcb$b_prib(r4),ldt$l_bprio(r11)		;save base prio
	.endc
; save file id, dir id from user call initially. Get file ID later after
; our i/o as a "better" number [should be the same].
	movl	fib$w_fid(r10),ldt$l_myfid(r11)
	movzwl	fib$w_fid+4(r10),ldt$l_myfid+4(r11)
	movl	fib$w_did(r10),ldt$l_mydid(r11)	;save dir id too
	movzwl	fib$w_did+4(r10),ldt$l_mydid+4(r11)
	movl	g^ctl$gl_phd,r9		;get proc. hdr
	movl	phd$q_privmsk(r9),ldt$l_wprv(r11)	;save working privs
	movl	phd$q_privmsk+4(r9),ldt$l_wprv+4(r11)	;save working privs
	movl	phd$q_authpriv(r9),ldt$l_aprv(r11)	;save auth privs
	movl	phd$q_authpriv+4(r9),ldt$l_aprv+4(r11)	;save auth privs
	movl	r5,ldt$l_jtucb(r11)		;save jt ucb here too
; set up template, blast it into the LDT for item list
	movab	ldt$l_acl(r11),gceaba		;acl buffer address
	movab	ldt$l_aclsiz(r11),gceala		;length of acl
	movab	gceacl,r9			;point at template now
	movab	ldt$l_itmlst(r11),r8
	pushr	#^m<r0,r1,r2,r3,r4,r5>	;don't let movc3 trash these
	movc3	#gcetpl,(r9),(r8)	;copy filled-in template to our
					;itemlist in ldt
	popr	#^m<r0,r1,r2,r3,r4,r5>
;fib desc still in r10
	movab	ldt$l_fib(r11),r9	;copy user fib
	.if	df,evax
	movl	@irp$l_qio_p1(r3),r8	;get size user has
	.iff
	movl	@p1(ap),r8
	.endc
	cmpl	r8,#64
	bleq	59$			;if ok branch
	movl	#64,r8
59$:
	pushr	#^m<r0,r1,r2,r3,r4,r5>	;don't let movc3 trash these
	movc3	r8,(r10),(r9)		;copy user FIB
	popr	#^m<r0,r1,r2,r3,r4,r5>
	bicl	#^xfff,fib$l_acctl(r9)	;no special open bits
;ensure fib has nothing special
	clrl	fib$l_aclctx(r9)	;no acl context
;
; An open might look up a filename so copy user desc too.
	.if	df,evax
	movl	irp$l_qio_p2(r3),r8	;get desc. pointer
	.iff
	movl	p2(ap),r8		;get desc pointer
	.endc
	beql	159$			;if no p2 arg, skip save
	movl	(r8),ldt$l_fnd(r11)	;copy user desc.
	cmpw	#255,ldt$l_fnd(r11)	;see if count to big
	bgeq	259$			;if geq all well
	movw	#255,ldt$l_fnd(r11)	;else chop off
259$:	movl	4(r8),r8		;point at user data now
	beql	159$
	pushr	#^m<r0,r1,r2,r3,r4,r5>	;don't let movc3 trash these
	movab	ldt$l_fndd(r11),r1	;our ldt data address
	movab	ldt$l_fndd(r11),ldt$l_fnd+4(r11) ;fill in data addr
	movzbl	ldt$l_fnd(r11),r0	;count to move
	beql	359$
	movc3	r0,(r8),(r1)		;copy filename string
359$:
	popr	#^m<r0,r1,r2,r3,r4,r5>
159$:
; Basically all set up now. Issue a $qio with an AST to point to the
; normal-knl-AST code and start waiting the mainline for ef #31 (the junk
; efn) and for the extra "iosb" area to get bumped. Block deletion of the
; process during this $qio by hand, counting this up and down per PROCESS.
	movl	ldt$l_prcstr(r11),r1	;get process data block

	deviceunlock lockaddr=ucb$l_dlck(r5),newipl=#ipl$_astdel,preserve=YES

; Before issuing our I/O, see if this is a bogus file id
; that we let by earlier and route it to the daemon if so, directly
; without reading the ACL.
; R5 should still be pointing at the JT unit here since we issue another
; $qio which handles getting it moved...
	bitl	#1024,ucb$l_ctlflgs(r5)	; checking for bogus FIDs?
	beqlw	103$			; if eql no
	.if	df,evax
	movl	irp$l_qio_p1(r3),r0	;get P1 param
	.iff
	movl	p1(ap),r0
	.endc
	beqlw	103$
	movl	4(r0),r0		;point at user FIB
	beqlw	103$			; skip if none there
; fid = fileno, fileseq, rvn, filenohi
; if filenohi .gt.128 and rvn .gt.128 as unsigned numbers then
; treat the operation here.
; Other h.o. bits are used to act as device switches here if this
; is selected. This requires of course that real volume sets
; be limited to maybe 32 volumes and that real maxfiles be less
; than the 24 bits' worth, for those volumes monitored here. Since
; this monitor is per disk, the function CAN just be disabled on large
; volume sets.
	bitb	#128,fib$w_fid+4(r0)	;rvn bit set?
	beql	105$
	bitb	#128,fib$w_fid+5(r0)	;hi fileno set?
	beql	105$			;if so skip open funct. test
; If here, we have a file id that appears fake and are flagging
; such. Arrange to call the daemon in that case.
; Note no LDT exists yet, so we'll do tests later, before issuing
; our own i/o, to test this.
; LDT pointer in R11 here.
	movl	r11,r0
	movl	r5,r1
	popr	#^m<r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
	movl	r0,r11
	movl	r1,r0
	popl	r1
	popl	r1	;leave r0 alone
; now stack is clean except of <r0,r5> push
	popl	r5		;get original r5 back
	tstl	(sp)+		;& remove saved r0
; Now replace regs on stack, but we do leave R11 pointing at LDT.
; Since R11 is scratch for FDT routines, this is ok. Other
; stacked regs in r2-r10 range get left alone but we
; continue with r5 = JT UCB (stacked r5=victim ucb).
	pushr	#^m<r3,r4,r5,r6,r7,r8,r9,r10,r11>
	movl	r0,r5		;set r5 to jt ucb
	pushr	#^m<r0,r1,r2>	;now stack is same as
				;after push of r0-r11
; Note fake FID handling not defined yet.
	brw	afakfid		;go handle fake fids
105$:
103$:
; (r1) = count up/down our knl threads
; 4(r1) = disable delete counter
	tstl	4(r1)			;is del inhibited now?
	bgtr	61$
	incl	(r1)			;count knl thread up here.
	bitl	#<pcb$m_nodelet!pcb$m_nosuspend>,pcb$l_sts(r4)	;is delete inhibited now?
	bneq	160$			;if so leave it alone
61$:	incl	4(r1)			;bump inhibit counter once more
	bisl	#<pcb$m_nodelet!pcb$m_nosuspend>,pcb$l_sts(r4)	;inhib del
160$:
	movl	ldt$l_synch(r11),r10	;point r10 at the synch block
; the $qio will return with all regs except r0,r1
; First have to get the channel number from the CCB address which was passed
; in R6 so we can use the channel for OUR $qio.
;
; This is system dependent.
	.if	df,evax	;evax defined for alpha
	movl	r6,r12		;deduced from sysqioreq src
	subl2   g^ctl$ga_ccb_table,r12  ;subtract base address
	ashl	#-5,r12,r12     ;divide by ccb$k_length = 32
;	assume ccb$k_length eq 32
	incl	r12		;1-based
	pushl	r13
	bicl3	#^c<^x0000f000>,r12,r13	;r13 -> hi 4 bits
	bicl2	#^xf000,r12	;get low 12 bits masked off
	ashl	#4,r12,r12	;shift 12 up
	ashl	#-12,r13,r0	;shift the 4 down
	bisl	r0,r12		;merge
	movzwl	r12,r12		;ensure h.o. bits off
	popl	r13		;restore borrowed reg
; r12 is now channel
	movl	r12,r8
	.iff	;vax vers
	movl	r6,r8
	subl2	g^ctl$gl_ccbbase,r8	;form -chnl
	mnegl	r8,r8
	movzwl	r8,r8			;r12 should be channel now
	.endc
;	.iif	df,x$$$dt,jsb g^ini$brk
;	.iif	df,x$$$dt,cmpl	r6,r6
; now issue the $qio
; form descriptor for fib on stack
	pushl	r11		;be VERY sure we keep valid ldt ptr
	subl	#16,sp		;get 4 longs
	movl	sp,r10		;descriptor is len, addr
	movl	#64,(r10)
	movab	ldt$l_fib(r11),4(r10)	;(r10) is descriptor of fib now.
	movl	#gcetpl,8(r10)	;length of itemlist
	movab	ldt$l_itmlst(r11),12(r10)	;now have descriptor for itmlst
;begin the $qio here
	clrl	-(sp)		;p6
	movl	12(r10),-(sp)	;p5
	clrl	-(sp)		;p4
	clrl	-(sp)		;p3
; Where user open involves a dir lookup, we might need one too.
; Therefore supply the filename he used if we found one.
	tstl	ldt$l_fndd(r11)	;got a p2?
	bneq	459$		;if so fill in
	clrl	-(sp)		;p2 zero if none here
	brb	559$
459$:	pushab	ldt$l_fnd(r11)	;p2 as our copy of user p2 in knl space
559$:
	movab	(r10),-(sp)	;p1
	movl	r11,-(sp)	;ast parm = LDT address
	movab	vcstp15,-(sp)	;ast address = vcstp15 (step 1.5 of thread)
	movl	ldt$l_synch(r11),-(sp)	;iosb = synch + 8
	addl2	#8,(sp)		;this gives us a way to getthe status for debug
	tstl	(sp)		;ensure negative
	blss	659$
	clrl	(sp)		;if synch addr illegal use 0
659$:
;	clrl	-(sp)		;no iosb
	movl	#io$_access,-(sp)	;function
	movl	r8,-(sp)	;channel number
	movl	#31,-(sp)	;junk event flag
	movl	g^ctl$gl_pcb,r4		;point at our PCB just in case
	.if	df,evax
	calls	#12,g^sys$qio	;do the i/o
	.iff
; force curr, prev mode to knl (=0) before issuing the request here.
	movpsl	-(sp)	;force prev knl mode too
	bicl	#<psl$m_prvmod+psl$m_curmod>,(sp)
	insv	#0,#psl$v_ipl,#psl$s_ipl,(sp) ;sys services like ipl 0 on vax
	pushab	158$
	rei		;continues at 158$ with stack clear
158$:
	.if	ndf,ee$qq
	calls	#12,g^exe$qio	;do the i/o (kernel entry!!!)
	.iff
	calls	#12,g^sys$qio	;do the i/o (kernel entry!!!)
	.endc
	.endc
	setipl	ipl=#2,environ=UNIPROCESSOR	;back to astdel
	addl2	#16,sp		;clean the stack.
	popl	r11
; r11 should still be the LDT address, R10 the synch block address.
;	.iif	df,x$$$dt,jsb g^ini$brk ;*********************debug***************
;	blbc	r0,500$		;if the I/O failed, we lose. Try to just issue
	blbs	r0,3500$
	brw	500$
3500$:
				;the user's i/o.
	.if	ndf,evax
	movl	ldt$l_psl(r11),-(sp)	;get to original I/O PSL
	jsb	301$
	brb	302$
301$:	rei
302$:
	.endc
; if ldt got deallocated before here, could be disaster.
	movl	ldt$l_synch(r11),r10	;status blk address
	jsb	dowait		;await done
	movl	r10,r0		;r0 status from user's I/O here usually now
; Now that dowait is done we can free the LDT with no fear of having it
; deallocated out from under dowait.
	setipl ipl=#2,environ=UNIPROCESSOR
	pushl	r0
;
; Now we can deallocate either the whole LDT or the part below the
; ACE.
; Rather than fiddle, leave the whole ACE buffer there, chopping
; off after it.
; LDT is pointed at by R11 here.
; Note we have our regs back because fdtlop etc. saves all in its
; entry mask. Thus the regs are original qio regs. For findldt we
; need r5=JT unit UCB though, so get that.
	movl	ldt$l_regs+12(r11),r5	;original ucb
	jsb	getjtucb	;find JT UCB again
	tstl	r0		;lose if we cannot
	beql	85$		;
	movl	r0,r5		;now r5=JT ucb
	cmpl	ldt$l_ace+8(r11),acllit	;this our ACE?
	beql	80$			;if eql we must keep the ace till close
; clean all out
	jsb	findldt		;find where this LDT is
; On return r1 is previous LDT (which we need) and r0 is
; LDT address (must be same as R11).
	cmpl	r0,r11
	bneq	85$		;if we get bad LDT, don't mess
; r1 is prev LDT, r11 is this one.
	movl	ldt$l_fwd(r11),ldt$l_fwd(r1)	;collapse this LDT out of chain
;now deallocate this LDT & go
	movl	ldt$l_fresiz(r11),r1		;length to free
	movl	r11,r0			;addr to free
	jsb	g^exe$deanonpgdsiz	;free it
	brw	85$
80$:
; shorten by reallocate/copy/delete
	jsb	findldt
	cmpl	r0,r11
	bneq	85$		;if we get bad LDT, don't mess
	movl	r1,r10		;move prev-ldt addr to r10...keep from harm
	movl	#ldt$l_regs,r1	;length to allocate
	jsb	g^exe$alonpagvar
	blbc	r0,85$		;leave LDT alone if we can't grab less
	movl	r2,r9		;new addr save
	movc3	#ldt$l_regs,(r11),(r9)	;copy 1st part of LDT
; now free the old LDT after we move linkage.
	movl	#ldt$l_regs,ldt$l_fresiz(r9)	;set size as less
	movl	r9,ldt$l_fwd(r10)	;point prev. LDT at this.
	movl	ldt$l_fresiz(r11),r1
	movl	r11,r0		;dealloc old ldt
	jsb	g^exe$deanonpgdsiz	;free it
; now old LDT should be free so we're done.
85$:
	popl	r0
; get all the saved regs off the stack
	popr	#^m<r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
	popl	r1
	popl	r1		;leave r0 alone but clean stack
	brw	510$
500$:
	movl	r11,r0		;save LDT pointer in r0
; (need LDT at stp2bad)
	popr	#^m<r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
	popl	r1
	popl	r1		;leave r0 alone
;stp2bad preserves all regs via save/restore.
	pushl	r11
	bsbw	stp2bad		;go try & resume i/o
	setipl ipl=#2,environ=UNIPROCESSOR
; R11 can be scratched in fdt code since it gets restored on exit from
; the system service.
	popl	r11
	movl	#ss$_accvio,r0	;set generic error
;
; free the ldt now to keep it clean.
	pushr	#^m<r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
; Now we can deallocate either the whole LDT or the part below the
; ACE.
; Rather than fiddle, leave the whole ACE buffer there, chopping
; off after it.
; LDT is pointed at by R11 here.
; Note we have our regs back because fdtlop etc. saves all in its
; entry mask. Thus the regs are original qio regs. For findldt we
; need r5=JT unit UCB though, so get that.
	movl	ldt$l_regs+12(r11),r5	;original ucb
	jsb	getjtucb	;find JT UCB again
	tstl	r0		;lose if we cannot
	beql	3085$		;
	movl	r0,r5		;now r5=JT ucb
	cmpl	ldt$l_ace+8(r11),acllit	;this our ACE?
	beql	3080$			;if eql we must keep the ace till close
; clean all out
	jsb	findldt		;find where this LDT is
; On return r1 is previous LDT (which we need) and r0 is
; LDT address (must be same as R11).
	cmpl	r0,r11
	bneq	3085$		;if we get bad LDT, don't mess
; r1 is prev LDT, r11 is this one.
	movl	ldt$l_fwd(r11),ldt$l_fwd(r1)	;collapse this LDT out of chain
;now deallocate this LDT & go
	movl	ldt$l_fresiz(r11),r1		;length to free
	movl	r11,r0			;addr to free
	jsb	g^exe$deanonpgdsiz	;free it
	brw	3085$
3080$:
; shorten by reallocate/copy/delete
	jsb	findldt
	cmpl	r0,r11
	bneq	3085$		;if we get bad LDT, don't mess
	movl	r1,r10		;move prev-ldt addr to r10...keep from harm
	movl	#ldt$l_regs,r1	;length to allocate
	jsb	g^exe$alonpagvar
	blbc	r0,3085$		;leave LDT alone if we can't grab less
	movl	r2,r9		;new addr save
	movc3	#ldt$l_regs,(r11),(r9)	;copy 1st part of LDT
; now free the old LDT after we move linkage.
	movl	#ldt$l_regs,ldt$l_fresiz(r9)	;set size as less
	movl	r9,ldt$l_fwd(r10)	;point prev. LDT at this.
	movl	ldt$l_fresiz(r11),r1
	movl	r11,r0		;dealloc old ldt
	jsb	g^exe$deanonpgdsiz	;free it
; now old LDT should be free so we're done.
3085$:
	popr	#^m<r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
510$:
	pushl	r0		;ensure waits run
	movl	#31,-(sp)	;junk event flag
	calls	#1,g^sys$setef
	popl	r0		;get status back
; Must now flush the pushr of <r0,r5> off stack.
	popl	r5		;get saved <r0,r5> r5 = ucb pointer
	tstl	(sp)+		;fix stack, leave r0 alone
; For step2 we must store r0 in fdt_context structure, which is
; located by IRP$PS_FDT_CONTEXT in the IRP for the original user
; IRP; this returns the intermediate return status to the user.
; Return dd$_fdt_compl in r0 at this point in step2, and real status
; in the fdt_context area at FDT_CONTEXT$L_QIO_STATUS and
; perhaps FDT_CONTEXT$L_QIO_R1_VALUE as needed.
; This needs to be gathered from the user's I/O appropriately too.
; Note that the LDT has the cell ldt$l_fdtctx which is the address
; of the user's FDT context area. That should be used since the IRP
; is invalid by the time we get here. Our pointer is not.
	movl	ldt$l_fdtctx(r11),r1	;get fdt context area
	movl	r0,fdt_context$l_qio_status(r1)	;save status
	movl	#ss$_fdt_compl,r0	;normal fdt donesignal
	setipl ipl=#0,environ=UNIPROCESSOR
	ret
;	jmp	g^exe$qioreturn	;do intermediate exit.

2000$:
	deviceunlock lockaddr=ucb$l_dlck(r5),newipl=#ipl$_astdel,preserve=YES
	popr	#^m<r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
	popl	r1
	popl	r0
	popr	#^m<r0,r5>
	bsbw	popout
	ret
;	brw	popout		;leave
; handling for fake FIDs, not yet implemented so just return.
afakfid:
	popr	#^m<r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>
	bsbw	popout
	ret
jt_end:
	.END
