	subroutine cdopen (file_nam, chan, sblk, fsize, ierr)
C
C_TITLE	CDOPEN locates the specified CDROM file and returns file parameters
C
C_ARGS
	character*50	file_nam	!input - cdopen entry point; 
C					File specification, must include
C					file_name and ext, may include
C					device, directory and version;
C					ddcn:[dir1.dir2...]file_name.ext;v
	integer*4	chan		!return - cdopen entry point; 
C					The channel assigned to the CDROM
C					device by cdopen.
C					input - cdatt entry point;
C					The channel assigned by cdopen
C					which must be supplied when cdatt 
C					is called.
	integer*4	sblk		!return - cdopen entry point; 
C					The starting block of the file
C					just opened.  This value is
C					required input for calls to 
C					cdread.
	integer*4	fsize		!return - cdopen entry point; 
C					The size of the file in bytes.
	integer*4	ierr		!return - cdopen entry point;
C					Error return value,
C					0 = no error, -1 = fatal error
C					return - cdatt entry point;
C					Same as for cdopen entry point
C
C	The following arguments provide return data for the cdatt entry
C	point only.  They are not to be included in the cdopen call. 
C
	byte		abuf(2048)	!return - cdatt entry point; 
C					The user supplied buffer which
C					will contain the attribute record
C					on return.
	integer*4	att_blk		!return - cdatt entry point; 
C					The length in blocks of the
C					attribute record.
	integer*4	stid		!return - cdatt entry point;
C					The standard ID of the format
C					in which the CDROM was written.
C					   1  -  High Sierra
C					   2  -  ISO
C_VARS
	include '($syssrvnam)'		!FORTRAN system service definitions
	include '($iodef)'		!FORTRAN I/O definitions
	include '($ssdef)'		!FORTRAN system definitions
C
C_DESC	CDOPEN is part of a package of low level routines which give the
C	caller access to data written on a CDROM.  This package includes
C	CDOPEN, CDATT, CDREAD AND CDCLOSE.  CDOPEN must be called first to 
C	assign a channel to the CDROM reader and to find and open the 
C	requested file.  If the user desires the extended attribute record
C	(if it exists), he must make a call to CDATT IMMEDIATELY after a
C	successful call to CDOPEN FOR THAT SAME FILE.  NOTE: CDATT is not a
C	separate subroutine but is an entry point within the CDOPEN routine.
C	Once the CDROM file is opened, CDREAD can be used to read blocks of 
C	data.  CDCLOSE deassigns the channel to the CDROM reader which was 
C	assigned by CDOPEN in effect closing the file.
C
C	These routines work at the block level.  In particular, CDREAD 
C	returns in block size chunks.  It does not do any logical record 
C	handling.  The calling program must take care of any record 
C	deblocking.  The routines use a standard 512 byte block.  CDOPEN 
C	converts the file starting block to this convention if the volume 
C	was written using a different block size.  The number of bytes
C	returned by cdread is 512 times the number of blocks requested.
C
C	The routines recognize and can access both the ISO and High Sierra
C	CDROM formats.  This occurs automatically and is transparent to 
C	to the caller.  If the CDROM was not written in a recognized
C	format, CDOPEN will issue an error message and return to the caller
C	with an error condition - without opening the file.
C
C	The CDOPEN routine will use the value assigned to the PIC$CDROM 
C	logical name as defaults if it is defined.  The user will want to
C	assign a value to PIC$CDROM if the cdreader device on his system
C	is other than DUB0: or if he will be consistantly using a specific
C	directory on the CDROM disk.  Particularly if the device is not 
C	DUB0:, it will be desirable to have PIC$CDROM defined in the system 
C	logical name table.  Either a device, a directory or both may be 
C	assigned to the logical name.  The DCL command is
C
C		$DEFINE/SYS PIC$CDROM ddcu:[directory]
C
C	Examples:
C
C		$DEFINE/SYS PIC$CDROM DUA2:
C		$DEFINE PIC$CDROM DUB1:[MIRANDA]
C		$DEFINE PIC$CDROM [URANUS.C2678XXX]
C
C	Before running any programs built with this subroutine, the user
C	must make the CDROM disk volume available to the system.  
C
C		+ Insert the correct disk into the drive.  
C
C		+ Issue the DCL mount command with foreign qualifier
C			i.e. $MOUNT/FOREIGN DUB0:
C
C	CDOPEN locates the file specified by the caller.  Defaults for the 
C	device and directory are taken from the user defined logical name 
C	PIC$CDROM if it exists.  If not provided by PIC$CDROM, the device 
C	defaults to DUB0: and the directory to the highest level (root) 
C	directory.  Any user specified device or directory included in the 
C	file specification supercede the defaults.  After assigning a channel 
C	to the CDROM device, the routine searches down the directory tree to 
C	the requested level and then searches for the file name.  If the file 
C	is found, the assigned channel, the file starting block, the size of 
C	the file in bytes and an error value of 0 (sucessful completion) is 
C	returned to the calling program.  If any error condition is 
C	encountered, an appropriate message is issued and an error value of 
C	-1 is returned.
C
C	CDATT is a separate entry point in the CDOPEN routine.  It uses
C	information obtained by cdopen about the file but not returned to
C	the caller.  The entry point and argument list for cdatt can be
C	found on the last page of the cdopen subroutine listing.  The data 
C	in the user supplied attribute buffer and length variable should 
C	be valid if the error value is 0 on return.  If the extended 
C	attribute record does not exist or a read error occurs, the 
C	appropriate error message is issued and an error value 	of -1 is 
C	returned.  Information on the content and format of the extended 
C	attribute record can be found in the ISO standard 9660, "Information 
C	processing - Volume and file structure of CD-ROM for information 
C	interchange" (1988) in section 9.5 starting page 23.  An identifier
C	indicating whether the disk was written in ISO or High Sierra
C	format is also returned to the user from CDATT.
C
C_CALLS	The routine calls the subroutine
C
C		CDSTAND
C
C	which determines in which standard, ISO or HIGH SIERRA, the CDROM
C	was written.  An error return indicates an unacceptable standard.
C
C	The routine calls the PICS system subroutine
C
C		B2B
C
C	to copy byte strings.
C
C	The following VMS system routines are also used:
C
C		str$upcase
C		sys$assign
C		sys$qiow
C		lib$sys_trnlog
C
C_HIST	2Apr87, DMcMacken, ISD, U.S.G.S., Flagstaff, Original Version
C	5Dec89, MHolomany, ICDD, Swarthmore, PA - added variable
C		save_dir_blk for use in entry point CDATT since CDOPEN
C	        destroys the value CDATT needs.
C      27Mar90, B.H. Toby, Dept. of Mater. Sci, Univ. of Penn, device
C		specification expanded to allow up to 20 characters in
C		device name, tested with a clustered CDROM drive at 
C		Argonne Nat'l Lab.
C_END
C******************************************************************************
c
c	local variables
c
	byte		ibuf(6144)	!I/O buffer
	byte		dbuf(300)	!directory buffer
	integer*2	log_blk_sz	!disk logical block size
	integer*2	blk_fac		!disk blocking factor
	integer*4	log_blk		!disk logical block             
	integer*4	blk_len		!no bytes to read
	integer*2	iosb(4)		!I/O status block
	integer*4	status		!system call return status
	character*20	dev		!device name
	integer*4	mrk		!string pointer
	integer*4	mrk2		!string pointer
	character*50	file_up		!uppercase copy of file_nam
	character*50	dir_str1	!subdirectory string 1
	character*50	dir_str2	!subdirectory string 2
	character*50	direc		!directory string
	character*50	file		!file string
	integer*4	root_blk	!root directory location
	integer*4	dir_blk		!directory location pointer
	integer*4	save_dir_blk	!saved value of dir_blk used in
C					entry point CDATT
	integer*4	root_len	!root directory size
	integer*4	dir_len		!directory record length
	integer*4	dir_sz		!directory size
	integer*2	file_flg	!file flags
	integer*4	fid_len		!length of file identifier
	integer*4	ndx		!do loop index
	logical		found		!file found flag
	integer*4	att_len		!length of extended attribute record
	integer*4	nxt_blk		!next directory block pointer
	integer*2	num_sec		!number sectors in directory
	integer*2	i_sec		!directory sector index
	integer*2	sdx		!standard index
	integer*2	rb(2)		!root directory block indices
	integer*2	rl(2)		!root dirctory length indices
	integer*2	lbs(2)		!logical block size indices
	integer*2	db(2)		!directory block pointer indices
	integer*2	ds(2)		!dirctory size indices
	integer*2	ff(2)		!file flags indices
	character*50	default_str	!default device/directory string
	character*20	default_dev	!default device
	character*50	default_dir	!default directory
	integer*4	version_num	!file version number
	logical*2	version_flag	!flag whether user input file version
	character*9	cdrom_log	!device/directory logical name
	integer*4	tst_ver		!test version number from directory
	integer*4	tst_len		!length of file name to test
	logical*2	fnd_version	!found a version of file flag
	logical*2	no_ver		!directory file name has no version
	integer*4	sysout		!VMS sus$output unit number
	parameter	(sysout=6)
c
	data	rb /183, 159/
	data	rl /191, 167/
	data	lbs /137, 129/
	data	db /3, 3/
	data	ds /11, 11/
	data	ff /25, 26/
	data	cdrom_log /'PIC$CDROM'/
c
c	assume no errors
c
 	ierr = 0
c
c	determine device and directory defaults
c
	status = lib$sys_trnlog (cdrom_log,, default_str,,,)
	if (status .ne. SS$_NORMAL) then
c		write (sysout, 5000) status, cdrom_log, default_str
c5000	format (' status = ', i5 /
c	1	' cdrom_log = ', a /
c	2	' default_str = ', a)
		default_str = ' '
	endif
	mrk = index (default_str, ':')
	mrk2 = index (default_str, ']')
	if (mrk .eq. 0) then
		default_dev = 'DUB0:'
	else
		default_dev = default_str(1:mrk)
	endif
	if (mrk2 .eq. 0) then
		default_dir = ' '
	else
		default_dir = default_str(mrk+2:mrk2-1)
	endif
c
c	parse file name string
c
	call str$upcase (file_up, file_nam)
	mrk = index (file_up, ':')
	mrk2 = index (file_up, ']')
	if (mrk .eq. 0) then
		dev = default_dev
	else
		dev = file_up(1:mrk)
	endif
	if (mrk2 .ne. 0) then
		direc = file_up(mrk+2:mrk2-1)
		file = file_up(mrk2+1:50)
		if (direc .eq. 'ROOT') direc = ' '
	else
		direc = default_dir
		file = file_up(mrk+1:50)
	endif
c
c	test whether caller provided version number
c
	mrk = index (file, ';')
	if (mrk .eq. 0) then
		version_flag = .false.
	else
		version_flag = .true.
	endif
	version_num = 0
	tst_ver = 0
c
c	search for file
c
	chan = 0
	sblk = 0
	fsize = 0
c
c	open channel to device
c
	status = sys$assign (dev, chan,,)
	if (.not. status) then
		write (sysout, 7000)
7000	format (' **** CDOPEN - error assigning channel to device ****')
		WRITE (SYSOUT, 7010) DEV
7010	FORMAT(' DEV = "',A,'"')
		ierr = -1
		return
	endif
c
c	read volume descriptor block
c
	log_blk = 64
	blk_len = 2048
	status = sys$qiow (, %val(chan), %val(io$_readlblk),
	1				iosb,,, ibuf, %val(blk_len),
	2				%val(log_blk),,,)
	if (.not. status .or. iosb(1) .lt. 0) then
		write (sysout, 7001)
7001	format (' **** CDOPEN - error reading volume descriptor block ',
	1	'****')
		ierr = -1
		return
	endif
c
c	determine standard
c	get needed parameters
c
	call cdstand (ibuf, sdx, ierr)
	if (ierr .lt. 0) then
		write (sysout, 7005)
7005	format (' **** CDOPEN - invalid CDROM standard ****')
		return
	endif
	call b2b (ibuf(rb(sdx)), root_blk, 4)
	call b2b (ibuf(rl(sdx)), root_len, 4)
	call b2b (ibuf(lbs(sdx)), log_blk_sz, 2)
c                          
c	search directory tree
c	starting at root
c
	blk_fac = log_blk_sz/512
	log_blk = root_blk*blk_fac
	blk_len = root_len
	mrk2 = index(direc, ' ') - 1
	if (mrk2 .le. 0) then
		mrk = 0
		found = .true.
	else
		mrk = 1
	endif
	do while (mrk .ne. 0)
c
c	determine name of directory we want on this level
c
		mrk = index (direc, '.')     
		if (mrk .eq. 0) then
			dir_str1 = direc
		else
			dir_str1 = direc(1:mrk-1)
	 		direc = direc(mrk+1:50)
		endif
		nxt_blk = log_blk
		num_sec = (blk_len + 2047)/2048
		blk_len = 2048
		i_sec = 0
		found = .false.
		do while (i_sec .lt. num_sec .and. .not. found)
		i_sec = i_sec + 1
c
c	read directory block
c
		status = sys$qiow (, %val(chan),
	1				%val(io$_readlblk), iosb,,, 
	2				ibuf, %val(blk_len),
	3				%val(nxt_blk),,,)
		if (.not. status .or. iosb(1) .lt. 0) then
			write (sysout, 7002)
7002	format (' **** COPEN - error reading directory block ****')
			ierr = -1
			return
		endif
c
c	scan directory entries on level
c
		if (i_sec .eq. 1) then
			mrk2 = ibuf(1) + 1
			mrk2 = ibuf(mrk2) + mrk2
		else
			mrk2 = 1
		endif
		dir_str2 = ' '
	   	do while (mrk2 .lt. blk_len .and.
	1				ibuf(mrk2) .ne. 0 .and.
	2				dir_str1 .ne. dir_str2)
c
c	copy entry to directory buffer
c
			dir_len = ibuf(mrk2)
			call b2b (ibuf(mrk2), dbuf, dir_len)
			call b2b (dbuf(db(sdx)), dir_blk, 4)
			call b2b (dbuf(ds(sdx)), dir_sz, 4)
			call b2b (dbuf(ff(sdx)), file_flg, 2)
c
c	construct directory name string from entry
c
			if (btest(file_flg, 1)) then
				fid_len = dbuf(33)
				dir_str2 = ' '
				call b2b (dbuf(34), %ref(dir_str2), fid_len)
 			endif
c
c	save pointer in case this is it
c	point to next directory entry
c
			log_blk = dir_blk*blk_fac
			mrk2 = mrk2 + dir_len
		enddo
c
c	set values depending on whether we found it or not
c
		if (dir_str1 .eq. dir_str2) then
			blk_len = dir_sz
			found = .true.
		else
			found = .false.
		endif
		nxt_blk = nxt_blk + 4
		enddo
		if (.not. found) mrk = 0
	enddo
c
c	finished search of directories
c	now get file (if possible)
c
	if (found) then
c
c	open bottom directory
c
   	    found = .false.
	    fnd_version = .false.
	    num_sec = (blk_len + 2047)/2048
	    blk_len = 2048
	    i_sec = 0
	    do while (i_sec .lt. num_sec .and. .not. found)
		i_sec = i_sec + 1
		status = sys$qiow (, %val(chan), %val(io$_readlblk),
	1				iosb,,, ibuf, %val(blk_len),
	2				%val(log_blk),,,)
 		if (.not. status .or. iosb(1) .lt. 0) then
			write (sysout, 7002)
			ierr = -1
			return
		endif
c
c	search directory for file name
c
		if (i_sec .eq. 1) then
			mrk2 = ibuf(1) + 1
			mrk2 = ibuf(mrk2) + mrk2
		else
			mrk2 = 1
		endif
		dir_str2 = ' '
	   	do while (mrk2 .lt. blk_len .and.
	1				ibuf(mrk2) .ne. 0 .and.
	2				.not. found)
c
c	copy entry into directory buffer
c
			dir_len = ibuf(mrk2)
			call b2b (ibuf(mrk2), dbuf, dir_len)
			call b2b (dbuf(db(sdx)), dir_blk, 4)
			call b2b (dbuf(ds(sdx)), dir_sz, 4)
			call b2b (dbuf(ff(sdx)), file_flg, 2)
c
c	construct file name string from entry
c
			fid_len = dbuf(33)
			dir_str2 = ' '
			call b2b (dbuf(34), %ref(dir_str2), fid_len)
c
c	separate file name and version number if necessary
c
			if (version_flag) then
				tst_len = 50
			else
				tst_len = index (dir_str2, ';') - 1
				if (tst_len .le. 0) then
					tst_len = 50
					no_ver = .true.
				else
					no_ver = .false.
					mrk = index (dir_str2, ' ') - 1
					if (mrk .lt. 0) mrk = 50
					read (dir_str2((tst_len+2):mrk), 
	1				    6000) tst_ver
6000	format (i3)
				endif
			endif
c
c	point to next directory entry
c
			mrk2 = mrk2 + dir_len
c
c	set flags and/or return values
c	save value of dir_blk in save_dir_blk for use in CDATT
c
			if (file .eq. dir_str2(:tst_len)) then
				if (version_flag .or. no_ver .or.
	1			    tst_ver .gt. version_num) then
					att_len = dbuf(2)
					sblk = blk_fac *
	1					(dir_blk + att_len)
					save_dir_blk = dir_blk

			     		fsize = dir_sz
					if (version_flag) then
				     	    found = .true.
					else
					    fnd_version = .true.
					endif
				endif
			endif
		enddo
		log_blk = log_blk + 4
	    enddo
	else
	    tst_len = index (direc, ' ') - 1
	    if (tst_len .lt. 0) tst_len = 50
		if (tst_len .gt. 0) then
		    write (sysout, 7003) dev, direc(:tst_len)
7003	format (' **** CDOPEN - directory not found ****'/
	1	x, a, ':[', a, ']')
		else
		    write (sysout, 7008) dev
7008	format (' **** CDOPEN - directory not found ****'/
	1	x, a, ':[ROOT]')
		endif
	    ierr = -1
	endif
c
c	tell user that file was not found
c
	if (fnd_version) found = .true.
	if (.not. found) then
		tst_len = index (direc, ' ') - 1
		if (tst_len .lt. 0) tst_len = 50
		    if (tst_len .gt. 0) then
			write (sysout, 7006) dev, direc(:tst_len), file
7006	format (' **** CDOPEN - file not found ****'/ 
	1	 x, a, ':[', a, ']', a)
		    else
			write (sysout, 7009) dev, file
7009	format (' **** CDOPEN - file not found ****'/ 
	1	 x, a, ':[ROOT]', a)
		    endif
		ierr = -1
	endif
c
c	go back to caller
c
	return
c
c	entry to obtain extended attribute record
c
	entry cdatt (chan, abuf, att_blk, stid, ierr)
c
	ierr = 0
	if (att_len .eq. 0) then
		write (sysout, 7007)
7007	format (' **** CDOPEN - there is no extended attribute record ',
	1	'****')
		ierr = -1
		return
	endif
	log_blk = blk_fac*save_dir_blk
	blk_len = 512*blk_fac*att_len
	att_blk = att_len
	stid = sdx
	status = sys$qiow (, %val(chan), %val(io$_readlblk),
	1		iosb,,, abuf, %val(blk_len),
	2		%val(log_blk),,,)
	if (.not. status .or. iosb(1) .lt. 0) then
		write (sysout, 7004)
7004	format (' **** CDOPEN - error reading extended attribute ',
	1	'record ****')
		ierr = -1
	endif
c
	return
c
	end
	subroutine cdstand (ibuf, sdx, ierr)
C_TITLE	CDSTAND determines the CDROM format standard
C_ARGS
	byte		ibuf(6144)	!input, volume descriptor buffer
	integer*2	sdx		!return, standard index
	integer*4	ierr		!return, error value
C_VARS	none
C_DESC	This routine determines whether the cdrom being accessed is
C	in ISO or High Sierra format.  If the routine is unable to
C	determine the standard, a negative error value is returned.
C_HIST	3Aug88, DMcMacken, ISD, USGS, Flagstaff, Original Version
C_END
C******************************************************************************
c
c	local variables
c
	logical*2	isoflg		!flag indicating ISO standard
	logical*2	hisflg		!High Sierra standard flag
	integer*2	ndx		!character counter for loop
	byte		isostd(5)	!ISO identifying string
	byte		hisstd(5)	!High Sierra identifying string
c
	data	isostd /'C', 'D', '0', '0', '1'/
	data	hisstd /'C', 'D', 'R', 'O', 'M'/
c
c	assume there will be no error
c
	ierr = 0
c
c	determine the standard
c
	isoflg = .true.
	hisflg = .false.
	ndx = 1
	do while (isoflg .and. ndx .le. 5)
		if (ibuf(ndx + 1) .ne. isostd(ndx)) isoflg = .false.
		ndx = ndx + 1
	enddo
c
c	if not ISO try High Sierra
c
	if (.not. isoflg) then
		hisflg = .true.
		ndx = 1
		do while (hisflg .and. ndx .le. 5)
			if (ibuf(ndx + 9) .ne. hisstd(ndx)) hisflg = .false.
			ndx = ndx + 1
		enddo
	endif
c
c	set standard index
c
	if (isoflg) then
		sdx = 2
	else if (hisflg) then
		sdx = 1
	else
		sdx = 0
		ierr = -1
	endif
c
	return
	end
	subroutine cdread (chan, sblk, vblk, nblk, ibuf, ierr)
C
C_TITLE	CDREAD reads nblk's from vblk of CDROM file open by CDOPEN
C
C_ARGS
	integer*4	chan		!input - channel number of device
	integer*4	sblk		!input - starting block of file
	integer*4	vblk		!input - requested virtual block no.
	integer*4	nblk		!input - number of blocks to transfer
	byte		ibuf(4096)	!return - data in user supplied buffer
	integer*4	ierr		!return - error return
C					0 = successful, -1 = failure
C
C_VARS
	include '($syssrvnam)'		!FORTRAN system service defintions
	include '($iodef)'		!FORTRAN I/O definitions
C
C_DESC	This routine calls a VMS system service to read  logical blocks
C	from a CDROM file.  The file must first have been opened by a 
C	call to CDOPEN to assign the channel (chan) and to provide the
C	starting block (sblk) of the file.  The user must supply the
C	virtual block number (vblk - sequential block number from beginning
C	of file) and the number of blocks (nblk - 512 byte blocks) to
C	read.  The user must also supply a buffer (ibuf) large enough to
C	hold the blocks requested.  If the read is successful a value of
C	zero (0) is returned in the variable ierr; otherwise a -1 is
C	returned.
C
C_HIST	6Apr87, DMcMacken, ISD, U.S.G.S., Flagstaff, Original Verson
C
C_END
C******************************************************************************
c
c	local variables
c
	integer*4	log_blk		!logical block to read
	integer*4	blk_len		!number of bytes to read
	integer*4	status		!system call return status
	integer*2	iosb(4)		!I/O status block
	integer*4	sysout		!VMS sys$output logical unit
	parameter	(sysout=6)
c
c	assume no errors
c
	ierr = 0
c
c	compute QIO parameters
c
	log_blk = sblk + vblk - 1
    	blk_len = 512*nblk
c
c	get block for caller
c
	status = sys$qiow (, %val(chan), %val(io$_readlblk), iosb,,,
	1		ibuf, %val(blk_len), %val(log_blk),,,)
	if (.not. status .or. iosb(1) .lt. 0) then
		write (sysout, 7000)
7000	format (' **** CDREAD - error reading CDROM ****')
		ierr = -1
	endif
c
	return
	end
	subroutine cdclose (chan, ierr)
C
C_TITLE	CDCLOSE closes (deassigns) the channel opened by CDOPEN
C
C_ARG
	integer*4	chan		!input, channel assigned by CDOPEN
	integer*4	ierr		!return, error return
C					0 = successful, -1 = fatal error
C_VARS
	include '($syssrvnam)'		!FORTRAN system service definitions
C_DESC	This routine calls a VMS system routine to deassign the channel
C	speccified by the caller.  This is equivalent to closing the file
C	associated with the channel "chan."
C
C_HIST	6Apr87, DMcMacken, ISD, U.S.G.S., Flagstaff, Original Version
C_END
C******************************************************************************
c
c	local variables
c
	integer*4	status		!system service return status
	integer*4	sysout		!VMS sys$output unit number
	parameter	(sysout=6)
c
	ierr = 0
	status = sys$dassgn (%val(chan))
	if (.not. status) then
		write (sysout, 7000)
7000	format (' **** CDCLOSE - error deassigning channel ****')
		ierr = -1
	endif
c
	return
	end
C---------------------------------------------------------------------------
	subroutine b2b (in,out,nbyte)
	integer nbyte,i
	byte in(*),out(*)
	do i=1,nbyte
	  out(i) = in(i)
	enddo
	return
	end
