*_Useful Procedures and Functions_* _Reading file information_ It is useful to be able to check if a file exists, and read the information relating to that file. The following function will do this: DIM ctrl% 31,name% 127 X%=ctrl%:Y%=X%DIV256 REM Returns file type, file info in X%!... REM -------------------------------------- DEFFNfile(A$,A%):$name%=A$:?X%=name%:X%?1=name%DIV256:=(USR&FFDD)AND&FF Calling the function with FNfile(filename$,5) returns the file type as: 0: File does not exist 1: File exists 2: Directory exists The file's catalogue information is returned as: X%!2 - load address X%!6 - execution address X%!10 - file length X%?14 - file attributes X%!15 - file creation date _Finding out which filing system is selected_ This allows you to alter the behaviour of a program depending on the filing system selected. REM Get current filing system number REM -------------------------------- DEFFNfs:LOCAL A%,Y%,E%:=(USR&FFDA)AND&FF Some of the values the function returns are: 1 - 1200 baud tape 5 - Net 2 - 300 baud tape 6 - Teletext 3 - ROM 7 - IEEE 4 - Disk 8 - ADFS _Fast file handling_ This procedure allows you to read or write exactly the number of bytes you require from a file in one exchange with the file server. This can make network file handling much faster than using BGET or BPUT as each BGET or BPUT sends a whole network packet for just a single byte. It can also be faster than on disk as disk files are always buffered to 256 bytes and this us not necessarily the number you want. DIM ctrl% 31:X%=ctrl%:Y%=X%DIV256 REM General OSGBPB call REM ------------------- DEFPROCgbpb(A%,chn%,addr%,num%,ptr%) ?X%=chn%:X%!1=addr%:X%!5=num%:X%!9=ptr%:CALL &FFD1:ENDPROC The procedure is called as: PROCgbpb(action, handle, address, number, pointer) The 'action' parameter has the following effects: 1 - Write bytes to file using the supplied pointer 2 - Write bytes to file at the current PTR 3 - Read bytes from file using the supplied pointer 4 - Read bytes from file at the current PTR You can check whether you went past the end of the file by checking X%!5 after the call. This contains the number of bytes which were not transfered. This will be non-zero if you went outside the file. _Reading the current disk, directory and library name_ The following function will return the name of the currently selected disk, directory or library. DIM ctrl% 31,name% 127:X%=ctrl%:Y%=X%DIV256 REM Return current disk (5), directory (6) or library (7) name REM ---------------------------------------------------------- DEFFNgbpb(A%):X%!1=name%:CALL&FFD1:A%=name%+((1+?name%)AND((A%AND-2)=6)) A%?(1+?A%)=13:=$(A%+1) When calling the function: FNgbpb(5) returns the disk name FNgbpb(6) returns the current directory name FNgbpb(7) returns the current library name The NFS always returns the names padded with spaces - 16 characters for the disk name, and 10 characters for the directory and library name. _Cataloguing the currently selected directory_ The following code allows you to read the filenames in the currently selected directory. It will work with NFS, DFS and ADFS with no changes and will work with any other well-behaving filing system. Often programs supplied on disk offer the option to catalogue, but they fail to work on the network because they assume the filenames will be seven characters long, whereas then could be up to ten or more characters. The data block used to read the filenames needs to be long enough to receive the filenames read. Most filenames encountered on BBC systems are a maximum of 10 characters long, so data% needs to point to at least 12 bytes - ten characters for the filename, one byte for the name length and one byte for the terminator. However, there is nothing that specifies that filenames are a maximum of ten characters. Filenames can be anything up to 255 bytes long and it is entirely down the the filing system what length names it can use. For instance, DOS filing systems use up to twelve character filenames, viz "Filename/txt". DIM ctrl% 31:REM Need at least 13 bytes for OSGBPB DIM name% 31:REM Need at least 12 bytes for directory entries X%=ctrl%:Y%=X%DIV256 :REM Point to control block ret%=0 :REM Return value idx%=0 :REM Directory index, starts at zero REPEAT f$=FNgbpb8(idx%) :REM OSGBPB call to read directory entries idx%=X%!9 :REM Get directory index back IF f$<>"" THEN PRINT f$ :REM Directory entry returned UNTIL f$="" END : REM Returns entry in current directory, or null string if at end REM ------------------------------------------------------------ DEFFNgbpb8(ptr%):X%!1=name%:X%!5=1:X%!9=ptr%:A%=8:CALL&FFD1:IFX%!5=1:="" A%=name%+1:A%!(A%?-1)=&D20:A%?(INSTR($A%," ")-1)=13:=$A% This code simply prints the filenames, but it is a simple matter to change it so the filenames are read into an array or processed in another way. _Tree Walking_ OSGBPB 8 can 'walk' a directory tree, descending subdirectories and returning from them with the following code. DIM ctrl% 31,name% 31 X%=ctrl%:Y%=X%DIV256 :REM Point to control block DEFPROCScan LOCAL idx%,f$ REPEAT:f$=FNgbpb8(idx%):idx%=X%!9:IF f$<>"":PROCObject UNTIL f$="" ENDPROC : DEFPROCObject LOCAL type% type%=FNfile(f$,5) REM Process object f$ IFtype%=2:OSCLI "Dir "+f$:PROCScan:OSCLI "Dir ^" ENDPROC : REM Returns entry in current directory, or null string if at end REM ------------------------------------------------------------ DEFFNgbpb8(ptr%):X%!1=name%:X%!5=1:X%!9=ptr%:A%=8:CALL&FFD1:IFX%!5=1:="" A%=name%+1:A%!(A%?-1)=&D20:A%?(INSTR($A%," ")-1)=13:=$A% : REM Returns file type, file info in X%!... REM -------------------------------------- DEFFNfile(A$,A%):$name%=A$:?X%=name%:X%?1=name%DIV256:=(USR&FFDD)AND&FF Note that as 6502 BASIC only implements a 20-deep REPEAT stack, this will only descend to a maximum depth of 20 subdirectories before getting a "Too many REPEATs" error. To avoid this a program would have to maintain its own recursion stack. _Reading entries from all DFS directories_ OSGBPB 8 reads the directory entries from the current directory. DFS implements single-character 'sideways' directories. The following code will run on all filing systems, and on DFS will scan through all the DFS directories. DIM ctrl% 31:REM Need at least 13 bytes for OSGBPB DIM name% 31:REM Need at least 12 bytes for directory entries X%=ctrl%:Y%=X%DIV256 :REM Point to control block dfs_all%=TRUE :REM TRUE to do all DFS directories dir%=0 IF dfs_all% THEN IF FNfs=4 THEN dir%=ASC"!":REM Start at dir "!" REPEAT IF dir% THEN OSCLI "Dir """+STRING$(1+(dir%=34),CHR$dir%)+""" idx%=0 :REM Directory index, starts at zero REPEAT f$=FNgbpb8(idx%) :REM OSGBPB call to read directory entries idx%=X%!9 :REM Get directory index back IF f$<>"" THEN PROCname :REM Directory entry returned UNTIL f$="" IF dir%=95:dir%=0 :REM Loop for all directories IF dir% THEN dir%=dir%+1:IF dir%=ASC":" THEN dir%=dir%+1 :REM Skip ':' UNTIL dir%=0 END : REM Returns entry in current directory, or null string if at end REM ------------------------------------------------------------ DEFFNgbpb8(ptr%):X%!1=name%:X%!5=1:X%!9=ptr%:A%=8:CALL&FFD1:IFX%!5=1:="" A%=name%+1:A%!(A%?-1)=&D20:A%?(INSTR($A%," ")-1)=13:=$A% : REM Get current filing system number REM -------------------------------- DEFFNfs:LOCAL A%,Y%,E%:=(USR&FFDA)AND&FF : DEFPROCname IFdir%:IFdir%<>36:f$=CHR$dir%+"."+f$ :REM Prepend with non-"$" dir. char ...etc _Reading the full pathname_ The following functions read the full pathname for the current directory, the current library and the user's home directory. DIM ctrl% 31,name% 31:X%=ctrl%:Y%=X%DIV256 REM FNPath_Name - Return pathname with drive number or name if no number REM FNPath_Full - Returns full pathname with diskname REM The returned pathname can be used to select the directory with *DIR REM -------------------------------------------------------------------- : DEFFNPath_Name:A%=6 DEFFNPath_Full:A%=&106 LOCAL n$,p$:REPEAT:X%!1=name%:CALL &FFD1 ?(name%+2+?name%+?(name%+?name%+1))=13 n$=$(name%+2+?name%):*DIR ^ n$=LEFT$(n$,INSTR(n$+" "," ")-1) p$=n$+"."+p$:UNTILn$="$"ORn$="&" p$=LEFT$(p$,LENp$-1):OSCLI"DIR "+p$ IFA%=6:X%!1=name%:CALL &FFD1:?(name%+1+?name%)=13:n$=$(name%+1):IFn$<>"":=":"+n$+"."+p$ X%!1=name%:A%=5:CALL &FFD1 ?(name%+1+?name%)=13:n$=$(name%+1) n$=LEFT$(n$,INSTR(n$+" "," ")-1):=":"+n$+"."+p$ : : REM FNPath_Lib - Return library pathname REM ------------------------------------ : DEFFNPath_Lib LOCAL a$,b$:a$=FNPath_Name:*DIR % b$=FNPath_Name:OSCLI"DIR "+a$:=b$ : : REM FNPath_LibFull - Return full library pathname REM --------------------------------------------- : DEFFNPath_LibFull LOCAL a$,b$:a$=FNPath_Name:*DIR % b$=FNPath_Full:OSCLI"DIR "+a$:=b$ : : REM FNPath_URD - Return URD pathname REM -------------------------------- : DEFFNPath_URD LOCAL a$,b$:a$=FNPath_Name:*DIR & b$=FNPath_Name:OSCLI"DIR "+a$:=b$ : : REM FNPath_URDFull - Return full URD pathname REM ----------------------------------------- : DEFFNPath_URDFull LOCAL a$,b$:a$=FNPath_Name:*DIR & b$=FNPath_Full:OSCLI"DIR "+a$:=b$ This is an example of what would be returned by the functions: FNPath_Name would return :0.$.Progs.Temp FNPath_Full would return :ProgramDisk.$.Progs.Temp FNPath_Lib would return :1.$.Library FNPath_LibFull would return :UtilsDisk.$.Library FNPath_URD would return :0.$.User1 FNPath_URDFull would return :MyDisk.$.User1 These functions will work on NFS, ADFS and any well behaved filing system that recognises the '^' directory. They will not work on early Acorn file servers that do not recognise '^'. _File time and date_ File server and OSFILE functions read and write dates and times in an internal format. These functions and procedures provide a easy way of converting to and from the internal format. REM FNf_date - convert date into internal format REM FNf_time - convert time into internal format REM -------------------------------------------- DEFFNf_date(d%,m%,y%):y%=y%-1981:=d%+m%*256+(y%AND15)*4096+(y%DIV16)*32 DEFFNf_time(h%,m%,s%):=h%+m%*256+s%*65536 : REM PROCf_date - extract date from internal format REM PROCf_time - extract time from internal format REM ---------------------------------------------- DEFPROCf_date(d%):day%=d%AND31:month%=(d%AND&F00)DIV256 year%=(d%AND&F000)DIV4096+(d%AND&E0)/2+1981:ENDPROC DEFPROCf_time(t%):hour%=t%AND255:minute%=(t%AND&FF00)DIV256 second%=(t%AND&FF0000)DIV65536:ENDPROC _Communicating with the File Server_ These functions provide a convenient way of sending a file server operation to the file server. DIM ctrl% 127:X%=ctrl%:Y%=X%DIV256 REM NetFS_Op(function%, name$) - NetFS file server operation REM Returns file server return code - 0=ok REM -------------------------------------------------------- DEFFNNetFS_Op(A%,A$):!X%=0:X%?1=8+LEN A$:X%!3=A%:$(X%+7)=A$:A%=&14:CALL&FFF1:=X%?3 : REM NetFS_OpN(function%, arg%, offset%, name$) REM - NetFS file server operation with offset to supplied string REM Returns file server return code - 0=ok REM ------------------------------------------------------------- DEFFNNetFS_OpN(A%,T%,O%,A$):!X%=0:X%?1=O%+1+LENA$:X%!3=A%:X%?7=T%:$(X%+O%)=A$:A%=&14:CALL&FFF1:=X%?3 File server operations take a varying amount of data. Most operations take between none and three bytes of data plus an optional string parameter. Each function returns the file server return code. This will be zero if the call successfully completed. If NFS is not present, then the return code will be the supplied file server function code. NetFS_Op() takes a single string parameter. Most file server operations can be done with NetFS_Op() with arguments passed as characters prepended to the string parameter, as in the following example to read an object's creation date: IFFNNetFS_Op(18,CHR$1+filename$)=0 THEN cdate%=X%!5 NetFS_OpN() sends a file server command block to the current file server. function% is the file server function, arg% is the first argument for the file server call, offset% is the offset to where name$ should be stored in the control block. This will be following any other data placed in the control block. If no string is required, pass name$ as "" and offset% as the offset to the end of the control block. Any other parameters must be put in the control block before calling. The following example writes the creation date of a file: X%!8=cdate%:A%=FNNetFS_OpN(19,5,10,filename$) The following list shows how to call each file server function. Command line A%=FNNetFS_Op( 0, command$) Examine 0 A%=FNNetFS_Op( 3, CHR$0+CHR$ptr+CHR$num+filename$) numfound%=X%?4 cycle% =X%?5 load% =X%!16 exec% =X%!20 access%=X%?24 mdate% =X%!25 acc% =X%!27 AND &FFF aux% =X%?29 OR (16*X%?28) size% =X%!30 AND &FFFFFF X%?16=13:name$=$(X%+6) Examine 1 A%=FNNetFS_Op( 3, CHR$1+CHR$ptr+CHR$num+filename$) numfound%=X%?4 cycle%=X%?5 text% =X%+6 Examine 2 A%=FNNetFS_Op( 3, CHR$2+CHR$ptr+CHR$num+filename$) numfound%=X%?4 cycle%=X%?5 X%?17=13:name$=$(X%+6) Examine 3 A%=FNNetFS_Op( 3, CHR$3+CHR$ptr+CHR$num+filename$) numfound%=X%?4 cycle%=X%?5 text% =X%+6 Cat Header A%=FNNetFS_Op( 4, pathname$) header$=$(X%+4) Open A%=FNNetFS_Op( 6, CHR$ create+CHR$read+filename$) handle%=X%?4 Close A%=FNNetFS_Op( 7, CHR$handle) Read PTR A%=NetFS_Op(12, CHR$handle+CHR$0) ptr%=X%!4 AND &FFFFFF Read EXT A%=NetFS_Op(12, CHR$handle+CHR$1) ext%=X%!4 AND &FFFFFF Read Allocated A%=NetFS_Op(12, CHR$handle+CHR$2) size%=X%!4 AND &FFFFFF Write PTR X%?8=0:X%!9=size% A%=FNNetFS_OpN(13, handle, 12, "") Write EXT X%?8=1:X%!9=size% A%=FNNetFS_OpN(13, handle, 12, "") Read Disks A%=FNNetFS_Op(14, CHR$first+CHR$ number) numfound%=X%?4 drive%=X%?5 X%?22=13:drive$=$(X%+6) Read Users A%=FNNetFS_Op(15, CHR$first+CHR$number) numfound%=X%?4 stn% =X%!5 AND &FFFF user$=$(X%+7) priv%=X%?(8+LEN$(X%+7)) Read Time A%=FNNetFS_Op(16, "") date%=X%!4 time%=X%!6 Read EOF A%=FNNetFS_Op(17, CHR$handle) eof%=X%?4 Read CDate A%=FNNetFS(18, CHR$1+filename$) type% =X%?4 cdate%=X%!6 Read Addrs A%=FNNetFS(18, CHR$2+filename$) type%=X%?4 load%=X%!6 exec%=X%!10 Read Extent A%=FNNetFS(18, CHR$3+filename$) type%=X%?4 size%=X%!6 AND &FFFFFF Read Access A%=FNNetFS(18, CHR$4+filename$) type% =X%?4 access%=X%?6 public%=X%?7 Read Attrs A%=FNNetFS(18, CHR$5+filename$) type% =X%?4 access%=X%?6 cdate% =X%!7 Read Cycle A%=FNNetFS(18, CHR$6+filename$) cycle% =X%?18 public%=X%?17 X%?(7+X%?6)=13:dirname$=$(X%+7) Read SIN A%=FNNetFS(18, CHR$7+filename$) type% =X%?4 disknum%=X%?5 sin% =X%!6 sinFS =X%?10 Read Dates A%=FNNetFS(18, CHR$64+filename$) type% =X%?4 cdate%=X%!5 ctime%=X%!7 mdate%=X%!10 mtime%=X%!12 Read Accounts A%=FNNetFS(18, CHR$65+filename$) type%=X%?4 acc% =X%!5 AND &FFFF aux% =X%!7 AND &FFFF Write Info X%!8 =load% X%!12=exec% X%?16=attr% A%=FNNetFS_OpN(19, 1, 17, filename$) Write Load X%!8=load% A%=FNNetFS_OpN(19, 2, 12, filename$) Write Exec X%!8=exec% A%=FNNetFS_OpN(19, 3, 12, filename$) Write Attr A%=FNNetFS_Op(19, CHR$4+CHR$attr+filename$) Write Date X%!8=cdate% A%=FNNetFS_OpN(19, 5, 10, filename$) Write Date+Time X%!8 =cdate% X%!10=ctime% X%!13=mdate% X%!15=mtime% A%=FNNetFS_OpN(19,64,18,filename$) Delete A%=FNNetFS_Op(20, filename$) Read Env A%=NetFS_Op(21, "") X%?41=13:libname$=$(X%+31) X%?31=13:csdname$=$(X%+21) X%?11=13:diskname$=$(X%+5) Set Boot A%=FNNetFS_Op(22, CHR$option) Logoff A%=FNNetFS_Op(23, "") User Info A%=FNNetFS_Op(24, username$) priv%=X%?4 stn% =X%!5 AND &FFFF FS Version A%=FNNetFS_Op(25, "") ver$=$(X%+4) Read Free A%=FNNetFS_Op(26, discname$) free%=X%!4 AND &FFFFFF size%=X%!7 AND &FFFFFF Create Dir A%=FNNetFS_Op(27, CHR$ blocks+pathname$) Set Time X%!7=date% X%!9=time% A%=FNNetFS_OpN(28, date%, 12, "") WhoAmI A%=FNNetFS_Op(32, "") user$=$(X%+4) _Reading time and date from the fileserver_ A%=FNNetFS_Op(16,"") PROCf_date(X%!4):PROCf_time(X%!6) PRINT "It is now ";hour;":";minute%;":";second%;" on ";day%;"/";month%;"/";year% _Checking the file server type_ You can find out what type of file server you are using by asking for the file server version string. All SJ Research file server have "SJ" in the version string, and all MDFS servers have "MDFS" in the version string. The following code tests if the file server is an MDFS. A%=FNNetFS_Op(25,"") ver$=$(X%+4) mdfs%=INSTR(ver$,"MDFS")<>0 _Reading and Writing Network Station Information_ The Net_Info() function reads and sets various network filing system information, such as the file or printer server station and the client station number. DIM ctrl% 31:X%=ctrl%:Y%=X%DIV256 REM Net_Info(function%, data%) - Read/Set NetFS information REM Returns data read REM --------------------------------------- DEFFNNet_Info(A%,D%):?X%=A%:X%!1=D%:A%=&13:CALL&FFF1:=X%!1 Read file server station station%=FNNet_Info(0,0) Set file server station A%=FNNet_Info(1,station%) Read printer server station station%=FNNet_Info(2,0) Set printer server station A%=FNNet_Info(3,station%) Read protection mask mask%=FNNet_Info(4,0) Set protection mask A%=FNNet_Info(5,mask%) Read context context%=FNNet_Info(6,0) Set context A%=FNNet_Info(7,context%) Read station number station%=FNNet_Info(8,0) Read arguments args%=FNNet_Info(9,0) Read error info error%=FNNet_Info(10,0) The following are only implemented on ANFS. Read channel error occured on channel%=FNNet_Info(11,0) Read printer server name A%=FNNet_Info(12,0):X%?7=13:name$=$(X%+1) Set printer server name $(X%+1)=name$:A%=FNNet_Info(13,X%!1) Read space in NetPrint buffer space%=FNNet_Info(14,0) Read file server retries retries%=FNNet_Info(15,0) Write file server retries A%=FNNet_Info(16,retries%) Get network number thisnet%=FNNet_Info(17,0)