OpenVMS Source Code Demos

sys_file_io_demo.bas

1000	%title	"sys_file_io_demo_xxx.BAS"					!
	%ident                      "version_100.3"				! <<<---+---
	declare string constant k_version = "100.3"			,	! <<<---+					&
				k_program = "sys_file_io_demo"			!
	!========================================================================================================================
	! Title   : sys_file_io_demo_xxx.bas
	! Author  : Neil S. Rieck ( https://neilrieck.net/ )
	! Purpose : Do file i/o the hard way so we will have a little more control over the EOF in the last block
	!
	! Overview: The need for this demo program began when we were trying to do file uploads to OpenVMS from a browser via an
	! interface program written in BASIC. Our technique could not replicate what binary-FTP does. For example, if you FTP any
	! image file (gif, jpg, png, etc.) into OpenVMS, the resultant file will have a somewhat strange set of file attributes
	! as these two example DCL command examples demonstrate:
	!------------------------------------------------------------------------------------------------------------------------
	! Example #1
	! ==========
	!  $dir/full  skynet-prototype-cpu.jpg
	!
	!	Directory CSMIS$ROOT3:[DVLP._BASIC_DEMO]
	!
	!	skynet-prototype-cpu.jpg;1                File ID:  (13274,19,0)
	!	Size:           46/48         Owner:    [SYSTEM]
	!	Created:    31-MAY-2010 10:50:29.90
	!	Revised:    31-MAY-2010 10:50:30.40 (1)
	!	Expires:    <None specified>
	!	Backup:     <No backup recorded>
	!	Effective:  <None specified>
	!	Recording:  <None specified>
	!	Accessed:   <None specified>
	!	Attributes: <None specified>
	!	Modified:   <None specified>
	!	Linkcount:  1
	!	File organization:  Sequential
	!	Shelved state:      Online
	!	Caching attribute:  Writethrough
	!	File attributes:    Allocation: 48, Extend: 0, Global buffer count: 0, No version limit
	!	Record format:      Fixed length 512 byte records                                            <<<---***   NOTE
	!	Record attributes:  None
	!	RMS attributes:     None
	!	Journaling enabled: None
	!	File protection:    System:RWED, Owner:RWED, Group:RWED, World:RWED
	!	Access Cntrl List:  None
	!	Client attributes:  None
	!
	!	Total of 1 file, 46/48 blocks.
	!------------------------------------------------------------------------------------------------------------------------
	! Example #2
	! ==========
	!  $ana/rms   skynet-prototype-cpu.jpg
	!
	!	Check RMS File Integrity                      3-JUN-2010 08:50:45.49   Page 1
	!	CSMIS$ROOT3:[DVLP._BASIC_DEMO]skynet-prototype-cpu.jpg;1
	!
	!	FILE HEADER
	!
	!	        File Spec: CSMIS$ROOT3:[DVLP._BASIC_DEMO]skynet-prototype-cpu.jpg;1
	!	        File ID: (13274,19,0)
	!	        Owner UIC: [SYSTEM]
	!	        Protection:  System: RWED, Owner: RWED, Group: RWED, World: RWED
	!	        Creation Date:   31-MAY-2010 10:50:29.90
	!	        Revision Date:   31-MAY-2010 10:50:30.40, Number: 1
	!	        Expiration Date: none specified
	!	        Backup Date:     none posted
	!	        Contiguity Options:  none
	!	        Performance Options: none
	!	        Reliability Options: none
	!	        Journaling Enabled:  none
	!
	!	RMS FILE ATTRIBUTES
	!
	!	        File Organization: sequential
	!	        Record Format: fixed
	!	        Record Attributes:
	!	        Maximum Record Size: 512
	!	        Longest Record: 512
	!	        Blocks Allocated: 48, Default Extend Size: 0
	!	        End-of-File VBN: 46, Offset: %X'0005'                                                ???????????? Huh?
	!	        File Monitoring: disabled
	!	        Global Buffer Count  pre-V8.3:          0
	!	        Global Buffer Count post-V8.3:          0
	!	        Global Buffer Flags post-V8.3:       none
	!
	!	The analysis uncovered NO errors.
	!------------------------------------------------------------------------------------------------------------------------
	! Observations:
	!
	! Notice that the $DIRECTORY command returned:
	!	"Record format: Fixed length 512 byte records"
	! ...whilst the $ANALYZE command indicates than an EOF exists at byte position 5 in block 46. How can the last block
	! contain a partial record when "FIXED LENGTH 512 BYTE" was specified?
	!
	! If you attempt to store a file like this using only BASIC statements, you will end up with a file containing 46 "full"
	! blocks (BASIC's run-time libraries enforce full with "FIXED LENGTH 512"). When you perform a $DIFF test between the two
	! data files, you will notice that there are data differences in the last block. Using $DUMP will show you that the FTP'd
	! file ends in a string of "00" (a.k.a. <nul>) characters whilst the BASIC-created file ends in a string of "20" (a.k.a.
	! <sp>) characters.
	!
	! So now you modify your BASIC program so the last block ends in string of "00" (a.k.a. <nul>) characters then perform the
	! $DIFF test again and the test appears to pass. ($ANALYZE still shows structural differences)
	!
	! Now FTP the BASIC-produced data file back to Windows. It will be a little bit longer than the original file but will
	! appear to work. But is this guaranteed to work with every file including Excel spread sheets and MS power points?
	! Maybe, maybe not.
	!
	! Possible Solutions:
	! 1) Rewrite your interface program in "C" where we will have more control because we are closer to the metal. Of course
	!    this will only be possible if you have a "C" Compiler License -AND- your shop allows other languages. (suprisingly,
	!    not everyone can maintain a "C" program)
	! 2) In BASIC, create a file that is opened with RECORDSIZE 512, RECORDTYPE NONE, ORGANIZATION SEQUENTIAL VARIABLE, then
	!    make sure all your records are 512 byte strings except the last one. Close the file then do a systyem call to change
	!    the RMS file attributes from variable to fixed.
	! 3) In BASIC, create a file that is opened without a MAP string. the you will need to use "MOVE TO" followed by "PUT".
	!    This did not work but maybe I have not stumbled on the correct syntax.
	! 4) In BASIC, create a file that is opened without a MAP string. the you will need to use "PRINT #" with 512 byte
	!    variable strings everytime except in the last print. This did seem to work but threw an error upon CLOSE
	! 5) Have BASIC call VMS system library routines like SYS$OPEN and SYS$PUT to write the data using 512 byte records. On
	!    the last record, reduce the record size to write out a smaller amount. THIS WORKS. <<<
	!
	! History :
	! Ver Who When   What
	! --- --- ------ --------------------------------------------------------------------------------------------------------
	! 100 NSR 100601 1. started original program
	!     NSR 100602 2. more work
	!     NSR 100603 3. more work
	!========================================================================================================================
	option type=explicit							! cuz tricks are for kids
	!
	!	import definitions from the system library
	!
	%include "starlet"          %from %library "sys$library:basic$starlet"	! system services
	%include "$ssdef"           %from %library "sys$library:basic$starlet"	! ss$
	%include "$rmsdef"          %from %library "sys$library:basic$starlet"	! rms$
	%include "$fabdef"          %from %library "sys$library:basic$starlet"	! fab$
	%include "$rabdef"          %from %library "sys$library:basic$starlet"	! rab$
	!
	!	program variables + constants
	!
	declare string constant dq		= '34'C				! <double quote>
	declare string constant cr_lf		= cr + lf			! <cr> + <lf>
	declare string constant k_test_file	= "sys_file_io_demo_hack.dat"	!
	!
	map(db512) string data_512 = 512					! data buffer
	!
	declare FABDEF		my_fab						! define a fab (file access block)
	declare RABDEF		my_rab						! define a rab (record access block)
	declare long		debug%					,	&
				junk%					,	&
				junk2%					,	&
				new_data_file_flag% 			,	&
				weekday%				,	&
				i%					,	&
				j%					,	&
				k%					,	&
				handler_error%				,	&
				record_num%				,	&
				rc%					,	&
		string		junk$					,	&
				temp$					,	&
				out$					,	&
		word		chan%					,	&
				record_size_w%					!
	!
	!========================================================================================================================
	!	main
	!========================================================================================================================
	main:
	debug% = 3								!
	print string$(len(k_program +"_"+ k_version), asc("="))
	print k_program +"_"+ k_version						!
	print string$(len(k_program +"_"+ k_version), asc("="))			! what will the optimizer do with this?
	!
	!	delete test file?
	!
	print "OK to delete file '"+ k_test_file +"'? (y/N )";			!
	input junk$								!
	select left$(edit$(junk$,32+2),1)					!
	    case "Y"								!
		print "-i-deleting file: "+ k_test_file				!
		when error in							!
		    while 1							!
			kill k_test_file					!
		    next 							!
		use								!
		end when							!
		new_data_file_flag% = 1						!
	end select								!
	print "=================================================="
	!
	!	use BASIC to create test file with data?
	!
	print "set up menu:"							!
	print "1. use BASIC to create and populate a testfile"			!
	print "   (necessary for testing the sys$open and sys$get routines)"	!
	print "2. use BASIC to create an empty testfile"			!
	print "   (necessary for testing the sys$put and sys$write routines)"	!
	print "3. do nothing"							!
	print "   (everything will be done with system calls)"			!
	input "choice? (1-3, default=3) ";junk$					!
	junk$ = left$(edit$(junk$,32+2),1)					!
	select junk$								!
	    case "1" to "2"							!
		when error in							!
		    print "-i-open"						!
		    open k_test_file for output as #99				&
			,organization sequential fixed				&
			,recordtype none					&
			,map db512						!
		    new_data_file_flag% = 1					!
		    goto skip_basic_puts	if junk$ <> "1"			!
		    junk% = 0							!
		    while junk% < 9						!
			junk% = junk% + 1					!
			data_512 = "record: "+ str$(junk%) +" written with BASIC"
			print "-i-put"						!
			put #99							!
		    next							!
		    new_data_file_flag% = 0					!
		    !
		    skip_basic_puts:						!
		    close #99							!
		use								!
		    print "-i-error: "+ str$(err)				!
		end when							!
	end select								!
	print "=================================================="
	!
	!========================================================================================================================
	!	open the data file (create it if it doesn't exist)
	!========================================================================================================================
	print "-i-entering sys$ area of the demo"				!
	!
	!	set up the FAB (file access block)
	!
	map(fn255) string file_name$ = 255					! fielname buffer (room for 255 characters)
	file_name$ = k_test_file						!
	!
	record_size_w%		= 512						! or len(data_512)
	!
	my_fab::fab$b_bid	= fab$c_bid					! block id (FAB)
	my_fab::fab$b_bln	= fab$c_bln					! FAB block len (default)
!~~~	my_fab::fab$b_rfm	= fab$c_udf					x record format: undefined record type
	my_fab::fab$b_rfm	= fab$c_fix					! record format: fixed
	my_fab::fab$l_alq	= 1						! initial allocation quantity
	my_fab::fab$l_fna	= loc( file_name$)				! file name address
	my_fab::fab$b_fns	= len( edit$(file_name$, 2))			! file name size
	my_fab::fab$w_mrs	= record_size_w%				! maximum record size
	my_fab::fab$b_fac	=						! file access:			&
				fab$m_bro	or				! block or record operations	&
				fab$m_upd	or				! can update			&
				fab$m_get	or				! can get			&
				fab$m_put					! can put
!~~~	my_fab::fab$b_shr = fab$m_upi						x share: user process interlock (disable RMS
!~~~										x	locking)
!~~~	my_fab::fab$l_fop = fab$m_ufo						x file operation: user file open (RMS is limited
!~~~										x	to $create or $open)
	!
	!	set up the RAB (file access block)
	!
	!	references:
	!	1. "Guide to OpenVMS File Applications"
	!		http://www.openvms.compaq.com/doc/731final/4506/4506pro_027.html
	!	2. "OpenVMS Record Management Services Reference Manual"
	!		http://www.openvms.compaq.com/doc/731final/4523/4523pro_011.html
	!
	my_rab::rab$b_bid = rab$c_bid						! this block is a RAB
	my_rab::rab$b_bln = rab$c_bln						! this structure is a traditional 32-bit RAB
	my_rab::rab$l_fab = loc(my_fab)						! address of my FAB
	my_rab::rab$b_rac = RAB$C_SEQ						! sequential
	my_rab::rab$l_rop = RAB$M_WBH						! write behind
	my_rab::rab$l_rbf = loc(data_512) 					! record buffer address (to be written)
	my_rab::rab$w_rsz = record_size_w%					! record size           (to be written)
	my_rab::rab$l_ubf = loc(data_512) 					! user buffer addr (applies to $GET only)
	my_rab::rab$w_usz = record_size_w%					! user buffer size (applies to $GET only)

	!=======================================================================
	!	sys$open / sys$create
	!=======================================================================
	print "-i-calling sys$open" if debug% >= 1				!
	rc% = sys$open ( my_fab )						! open the file
	if (rc% and 7%) <> 1% then						! if error...
	    if debug% >=1 then							!
		print "-e-sys$open rc: ";str$(rc%)				!
		print "-i-so creating data file"				!
		print "-i-calling sys$create"					!
	    end if								!
	    rc% = sys$create ( my_fab )						! attempt a create
	    if (rc% and 7%) <> 1% then						! if create error
		print "-e-sys$create rc: "+ str$(rc%)				!
		goto fini_rc							! adios...
	    else								! if create was successful
		print "-i-sys$create rc: ";str$(rc%)	if debug% >= 1		!
		new_data_file_flag% = 1						! we need to initialize the data <<<<<<<<<<
	    end if								!
	else									! else no error...
	    print "-i-sys$open rc: ";str$(rc%)	if debug% >= 1			! if open was successful
	end if									!
	!
	chan% = my_fab::fab$l_stv						! remember i/o channel number

	!=======================================================================
	!	sys$connect (to RAB)
	!=======================================================================
	rc% = sys$connect( my_rab )
	if (rc% and 7%) <> 1% then						! if error...
	    print "-e-sys$connect rc: ";str$(rc%)				!
	    print "-i-rab status code : ";my_rab::RAB$L_STS			!
	    print "-i-rab status value: ";my_rab::RAB$L_STV			!
	else									! else no error...
	    print "-i-sys$connect rc: ";str$(rc%)	if debug% >= 1		! if $connect was successful
	end if									!
	!
	if  new_data_file_flag% = 1 then					! if we need to initialize the data <<<<<<<<<<
	    new_data_file_flag% = 0						! clear one-time flag
	    input "write some data to the new file? (y/N)"; junk$		!
	    select left$(edit$(junk$,32+2),1)					!
		case "Y"							!
		case else							!
		    goto skip_write						!
	    end select								!
	    !
	    junk% = 0
	    do_write:
	    junk% = junk% + 1
	    data_512 = "record: "+ str$(junk%) +" written with OpenVMS sys$put"
	    rc% = sys$put( my_rab )						!
	    if (rc% and 7%) <> 1% then						!
		print "-e-sys$put rc: ";str$(rc%)				!
	    else								!
		print "-i-sys$put rc: ";str$(rc%)				!
		goto do_write	if junk% < 3					!
	    end if								!
	    !
	    junk% = junk% + 1	   						!
	    data_512 = "record: "+ str$(junk%) +" written with OpenVMS sys$write"
	    junk% = len(edit$(data_512,128))					! how much space do we "need"
	    print "-i-hack: attempting to write a block shorter than "+ str$(record_size_w%)
	    my_rab::rab$w_rsz = junk%						! only need to tweak rab$w_rsz
!~~~	    my_rab::rab$w_usz = junk%						x
	    rc% = sys$write( my_rab )						! sys$write pays closer attention to the RAB
	    if (rc% and 7%) <> 1% then						!
		print "-e-sys$write rc: ";str$(rc%)				!
	    else								!
		print "-i-sys$write rc: ";str$(rc%)				!
	    end if								!
	    !
	    !	restore initial rab value(s)
	    !
	    my_rab::rab$w_rsz = record_size_w%					! only need to restore rab$w_rsz
!~~~	    my_rab::rab$w_usz = record_size_w%					x
	end if									!
	skip_write:

	!=======================================================================
	!	sys$rewind
	!=======================================================================
	rc% = sys$rewind ( my_rab )						!
	if (rc% and 7%) <> 1% then						! if error...
	    print "-e-sys$rewind rc: ";str$(rc%)				!
	else									!
	    print "-i-sys$rewind rc: ";str$(rc%)	if debug% >= 1		! if $rewind was successful
	end if									!
	!=======================================================================
	!	sys$get
	!=======================================================================
	get_more:								!
        rc% = sys$get ( my_rab )						!
	if (rc% and 7%) <> 1% then						! if error...
	    print "-e-sys$get rc: ";str$(rc%)					!
	else									!
	    print "-i-sys$get rc: ";str$(rc%)	if debug% >= 1			! if $get was successful
	    print "-i-data: "+ edit$(data_512,128)				!
	    goto get_more							!
	end if									!
	!=======================================================================
	!	sys$disconnect
	!=======================================================================
	rc% = sys$disconnect ( my_rab )
	if (rc% and 7%) <> 1% then						! if error...
	    print "-e-sys$disconnect rc: ";str$(rc%)				!
	else									!
	    print "-i-sys$disconnect rc: ";str$(rc%)	if debug% >= 1		! if $disconnect was successful
	end if									!
	!=======================================================================
	!	sys$close
	!=======================================================================
	rc% = sys$close ( my_fab )						!
	if (rc% and 7%) <> 1% then						! if error...
	    print "-e-sys$close rc: ";str$(rc%)					!
	else									!
	    print "-i-sys$close rc: ";str$(rc%)	if debug% >= 1			! if $close was successful
	end if									!

	!
	goto fini
	!========================================================================================================================
	!	That's all folks...
	!========================================================================================================================
	fini_rc:								!
	goto fini_common							!
	!
	fini:
	rc% = 1									! assume success ("-s-")
	!
	fini_common:								!
	end program rc%								! return code to caller
	!------------------------------------------------------------------------------------------------------------------------

Back to Home
Neil Rieck
Waterloo, Ontario, Canada.