! File NMOUSE:IO_F ! FCB interface module for 1976/86 style filestores on 2MHz ether %option "-low-nocheck-nodiag" %include "mouse.inc" %include "2meg.inc" %ownrecord(ether buffer fm)%name req == nil %ownbytename b == nil %routine get buffer(%integer context) ! Acquire a message buffer if we haven't got one already, ! point REQ at it and B at the data part. %owninteger tag=0 req == acquire packet buffer %if req==nil req_context = context tag = tag+1; req_tag = tag req_status = 0; req_timeout = 100 req_ra = 0; req_rp = 0; req_lp = 0 req_bytes = 0; req_offset = def offset b == req_data(req_offset) %end %routine put buffer ! Return the ether message buffer we have been using back to ! the system pool, if we haven't done so already. %returnif req==nil discard packet buffer(req) req == nil b == nil %end %routine show %integer n b == req_data(req_offset) space phex2(req_context) n = req_bytes write(n,1); %returnif n<=0 space %while n>0 %cycle printsymbol(b); %exitif b=nl; b == b[1] %repeat %end %routine indirect request(%integer code) ! Send the request message REQ to the filestore multiplexor, and wait ! for the reply. Point REQ at it (the reply may or may not come in ! the same message buffer) and B at the data part. Check for errors. %ownrecord(mailbox fm)%name server==nil,punter req_code = code %if server==nil %start server == lookup mailbox("Filestore_Multiplexor") punter == create mailbox("",create semaphore("",0)) %finish req_system part_reply == punter {{printstring("IndReq -> "); phex2(req_code); show send message(req_system part,server) req == record(addr(receive message(punter))) {{printstring("IndReq <- "); phex2(req_status); show b == req_data(req_offset) %signal 3,5,req_code,ether errors(req_status) %if req_status<0 %end %routine direct request(%integer code) ! Send the request message REQ to the ether driver, and wait ! for the reply. Point REQ at it (the reply may or may not come in ! the same message buffer) and B at the data part. Check for errors. req_code = code {{printstring("DirReq -> "); phex2(req_code); show ether request(req) req == ether reply {{printstring("DirReq <- "); phex2(req_status); show b == req_data(req_offset) %signal 3,5,req_code,ether errors(req_status) %if req_status<0 %end %systemintegerfnspec stringdiff(%string(255)a,b) %owninteger fs num = 0 %ownstring(255) - file name = "", filestore = "" %routine check for error response ! Signal an event if the filestore response begins with a minus sign. ! In this case, the response looks like: ! ['-'],[error code digit],[space],[error message],[NL]. ! We use the [space] byte for the length of the error message string. %returnunless b='-' b[2] = req_bytes-4 {minus, code, space, NL} %signal 3,4,b[1]-'0',string(addr(b[2])) %end %routine command(%integer com,%string(255)param) ! Send a command to the filestore connected through CONTEXT, ! consisting of command letter COM, reference byte (i.e. user ! or transaction number) REF, the contents of PARAM (usually a ! filename), and NL. Then read the response, checking for error. %bytename p == length(param) get buffer(fs num) b = com; b[1] = '*'; b[1] = '0' %if com='L'; move(p,p[1],b[2]) b[p+2] = nl; req_bytes = p+3 indirect request(ether old write read) check for error response %end %routine parse file name(%string(*)%name in) ! IN is a canonical file name of the form :F:x:y ! where X is a filestore identifier and Y is the filestore-specific ! part of the file name, the :y may be absent. ! Put X and Y into the global variables FILESTORE and FILENAME. ! Set global variable FS NUM to the filestore's ether address plus ! the protocol type (0/1/2)<<8. ! Logical names are used to deduce filestore address and protocol variant ! from filestore name. To distinguish these logical names from ordinary ! ones, they start with a colon. Protocol variants are as follows: ! 0: 1976 style (as used by B and C) ! 1: 1986 style (as used by A and D) ! 2: 1976 without multi-block read capability (as used by Vax) ! Example logical names are :B==015, :A==114, and :V==272. %string(255)number %integer k,p,n %bytename f p = 1 n = 0 f == length(number) file name = "" fs num = 0 filestore = substring(in,4,length(in)) {take off the ":F:"} filename = "" %unless filestore -> filestore.(":").filename number = translate logical name(":".filestore) %while p<=f %cycle {read hex number, ignoring possible ':' character k = f[p]&127-'0' %if 0<=k<=9 %start n = n<<4+k %elseunless k=':'-'0' k = (k+'0')&95 %if 'A'<=k<='F' %start n = n<<4-'A'+k+10 %else n = -1; p = f {force error %finish %finish p = p+1 %repeat %if n=0 %or n&16_37f#n %start event_message = "Unknown filestore ".filestore %signal 3,5,n,event_message %finish fs num = n %end %integerfn gethex ! B points at an HDHEX number. Return that number as result, ! advancing B past it and its terminator. %integer n=0,k %cycle b == b[1]; k = b[-1]-'0' %result = n %if k<0 n = n<<4+k %repeat %end %integerfn copy out(%bytename dest,%integer max,offset) ! B points at the beginning of a successful filestore response, ! i.e. at the HDHEX length of a data block. Copy this data block, ! ignoring the first OFFSET bytes, but copy at most MAX bytes. ! Result is number of bytes actually copied. %integer amount amount = gethex-offset amount = max %if max>4) %and n = n&15 %if n>32; put(n+'0') %end %routine tup(%integer k) ! Put character K into REQ *backwards*, used to contruct the ! filestore protocol header on the front of data already in ! the buffer, without having to move the bulk of the data. b == b[-1]; b = k; req_bytes = req_bytes+1 %end %routine xehtup(%integer n) ! Put HDHEX number N into the buffer *backwards* tup(n&15) %and n = n>>4 %while n>32 tup(n+'0') %end %routine file read (%record(fcb fm)%name cb,%integer pos,amount,%bytename buf) ! Read arbitrary portion of file described by CB to BUF. %integer block = pos>>9, last = (pos+amount-1)>>9, offset = pos&511, blocks, i %returnif amount<=0 %signal 9,,,"End of file" %if pos+amount>cb_fl-cb_fs %while amount>0 %cycle blocks = last-block+1 {number of blocks left to read blocks = 16 %if blocks>16 {limit on multi-block read size per go blocks = 1 %if cb_b&16_300=16_200 {multi-block reads not supported} get buffer(cb_b) %if block=cb_d %start {in sequence: go for multi-block readsq put('X'); put(cb_c) put(blocks+'0') %if blocks>1 %else {otherwise use single-block random read put('R'); put(cb_c); puthex(block) blocks = 1 %finish put(nl); direct request(ether old write read) %cycle check for error response block = block+1; cb_d = block i = copy out(buf,amount,offset) offset = 0 buf == buf[i]; amount = amount-i blocks = blocks-1; %exitif blocks=0 get buffer(cb_b) direct request(ether old read) %repeat %repeat %end %routine file write (%record(fcb fm)%name cb,%integer pos,amount,%bytename buf) ! Write an arbitrary portion of file CB from BUF. This may involve reading ! from the file where part-blocks are involved. %integer block = pos>>9, last = (pos+amount-1)>>9, offset = pos&511, i,j,k %returnif amount<=0 ! Arrange for BLOCK to reach LAST only if the last block of the transfer is ! a part-block which needs to be read. So if the current transfer involves ! extending the file or if the end of the transfer is block-aligned, force ! LAST to an unreachable value. last = last+1 %if pos+amount>=cb_fl-cb_fs %or (pos+amount)&511=0 %unless offset=0 %start {Start of transfer is not block-aligned get buffer(cb_b) put('R'); put(cb_c); puthex(block); put(nl) direct request(ether old write read) check for error response i = gethex {B now points at start of data j = 512-offset {amount to copy j = amount %if amount512 req_bytes = i tup(nl); xehtup(i) tup(','); xehtup(block) tup(cb_c); tup('W') req_offset = addr(b)-addr(req_data(def offset)) req_context = cb_c direct request(ether old write read) check for error response block = block+1; cb_d = block pos = pos+j; buf == buf[j]; amount = amount-j %finish cb_fl = cb_fs+pos+amount %if cb_fl=cb_fl pos = (cb_p-cb_fs)&-512 {pos of start of relevant block amount = cb_fl-cb_fs-pos {amount left in file amount = 512 %if amount>512 {at most the buffer size delta = cb_fs+pos-cb_bs {scroll amount cb_fs = cb_fs-delta cb_fl = cb_fl-delta cb_p = cb_p-delta file read(cb,pos,amount,byte(cb_bs)) cb_l = cb_bl; cb_l = cb_fl %if cb_fl='0' %start get buffer(cb_b) req_code = ether old write read b = com; b[1] = i; b[2] = nl; req_bytes = 3 direct request(ether old write read) put buffer %finish dispose(record(cb_bs)) %unless cb_bs=0 dispose(cb) %end %routine SOP (%record(fcb fm)%name cb,%integer code,p1,p2,%bytename b) %switch sw(cbopclose:cbopread) ->sw(code) %if cbopclose<=code<=cbopread %signal 3,4,code,"Illegal FCB operation" sw(cbopflush): flushoutput(cb,p1); %return sw(cbopwrite): filewrite(cb,p1,p2,b); %return sw(cboprefresh): refreshinput(cb); %return sw(cbopread): fileread(cb,p1,p2,b); %return sw(cbopclose): close(cb,'K'); %return sw(cbopabort): close(cb,'H') %end %record(fcb fm)%map open(%integer mode,%string(*)%name originalname) ! Open the file specified in global variable FILENAME on the filestore ! specified by FS NUM, in the mode indicated by MODE. Allocate ! a control block and record the (canonical) name ORIGINALNAME in it, ! allocate a 512-byte buffer. Record in CB_A/B/C the filestore number, ! the ether context number, and the file transaction number. ! Use CB_D as the next sequential block number. %record(fcb fm)%name cb %integer size,com,p %routine read owner register {special for 1976 filestore} %integer i %routine semipartition(%integer s) %integer i %routine put(%integer k) byte(cb_fl) = k; cb_fl = cb_fl+1 %end get buffer(fs num) b = '\'; b[1] = 0; b[2] = s+'0'; b[3] = nl req_bytes = 4 indirect request(ether old write read) %returnif b='-' {too many partitions?} i = gethex %while i>0 %cycle %if b>' ' %start {within a directory name} put(b); b == b[1]; i = i-1 %else {between names: scan to next one} put(':'); put(nl) %cycle b == b[1]; i = i-1 %repeatuntil i<=0 %or b>' ' %finish %repeat b == req_data(req_offset) %end cb_c = '.' cb_bs = heapget(8192) cb_bl = cb_bs+8192 cb_fs = cb_bs cb_fl = cb_bs cb_p = cb_bs cb_l = cb_bs i = 0 %cycle semipartition(i); i = i+1 %repeatuntil b='-' %or i=16 cb_l = cb_fl %end ! Main part of OPEN %on 3 %start %if com='A' %start {openmod fails: try openout in case file not found com = 'T'; ->retry %finish %signal 3,3,event_extra,event_message %finish com = 'A' com = 'S' %if mode = fopopeni com = 'T' %if mode = fopopeno cb == newfcb(originalname) cb_a = fs num cb_b = -1 %if stringdiff(filename,"LOCAL:")=0 %and fs num&16_300=0 %and com='S' %start read owner register %result == cb %finish retry: command(com,filename) cb_b = req_context cb_c = b cb_bs = heapget(512); cb_bl = cb_bs+512 size = 0 %unless com='T' %start b == b[1]; b == b[1] %if b=',' size = gethex<<9 size = size-gethex %finish cb_fs = cb_bs; cb_fl = cb_fs+size %if com='S' %start cb_p = cb_bs; cb_l = cb_bs %elseif com='T' cb_p = cb_bs; cb_l = cb_bl %elseif mode=fopopena p = size&-512 %if p=size %start cb_fl = cb_bs; cb_fs = cb_fl-size cb_p = cb_bs; cb_l = cb_bl %else cb_p = cb_fs+p refreshinput(cb) cb_p = cb_fs+size; cb_l = cb_bl %finish %else {mode=fopopenm} cb_p = cb_fs refreshinput(cb) %unless size=0 cb_l = cb_bl %finish %result == cb %end %routine logout command('M',"") define logical name("current_user","") define logical name("default_directory","") %end %routine login(%string(*)%name p) %string(255)defdir %bytename d == length(defdir) %integer i %while filename -> (":").filename %cycle; %repeat {Trailing part only} logout %andreturnif filename="" command('L',filename.",".p) {Try to log on} filestore = ":F:".filestore defdir = filestore.":" command('F',"") {Establish default dir} d = d+copy out(d[d+1],255-d,0) i = d %while i>0 %cycle i = i-1; d = i %if d[i+1]=' ' %repeat d = d-5 %if fs num&16_300=16_200 {Vax: strip off "*.*;*"} d = d+1 %and d[d] = ':' %if ':'#d[d]#']' define logical name("default_directory",defdir) {current=default} define logical name(".",defdir) define logical name("current_filestore",filestore) define logical name("current_user",filename) %end %routine quote(%string(*)%name p) command('Q',p) %end %routine pass(%string(*)%name p) command('P',p) %end %routine credir %integer c = 'V' c = '[' %if fs num&16_300=0 command(c,filename) %end %routine delete command('D',filename) %end %routine finfo(%string(*)%name x) ! (This one is a candidate for getting rid of) %bytename l == length(x), f == length(filename) command('F',filename) l = copy out(l[1],255,0) %end %routine info(%string(*)%name x) ! Do a NINFO on the file in FILENAME, but if FILENAME ends ! with a ':', then instead do a FINFO 0 on that directory. %bytename l == length(x), f == length(filename) %integer c = 'N' c = 'F' %and f = f-1 %if f[f]=':' command(c,filename) l = copy out(l[1],255,0) %end %routine rename(%string(*)%name f2) %integer old fs num = fs num %string(255)oldfilename = filename parse filename(f2) %signal 3,4,0,"Cannot rename across filestores" %unless old fsnum = fs num %if fs num&16_300=0 %start %while filename -> (":").filename %cycle; %repeat %finish command('B',oldfilename.",".filename) %end %routine copy(%string(*)%name f2) %integer source num = fs num %string(255)sourcefilename = filename parse filename(f2) %signal 3,4,0,"Cannot copy across filestores" %unless source num = fs num command('O',sourcefilename.",".filename) %end %routine permit(%string(*)%name p) command('E',filename.",".p) %end %routine stamp(%string(*)%name d) command('C',filename.",".d) %end %routine time(%string(*)%name d) %bytename l == length(d) command('G',"") l = copy out(l[1],255,0) %end %routine special(%string(*)%name s) ! S typically begins with '[' or '^' or ']' for special ! commands NEWOWNER, NEWQUOTA, or CONTROL. S does not contain ! a user number digit, that is supplied automatically. ! In the case of CONTROL commands, the control code immediately ! follows the ']'. %integer code = charno(s,1) s = substring(s,2,length(s)) command(code,s) %end %externalrecord(fcbfm)%map FOP %alias "FOP_F" - (%integer code,%string(*)%name file,%name x) %switch sw(foplogout:fopoldfinfo) %record(fcbfm)%name cb == nil %integer fpc,spc parse filename(file) ->sw(code) %if foplogout <= code <= foptime %signal 3,4,code,"Unsupported file operation" sw(foplogout): logout; ->end sw(foplogin): login(x); ->end sw(fopquote): quote(x); ->end sw(foppass): pass(x); ->end sw(fopcredir): credir; ->end sw(fopdelete): delete; ->end sw(fopinfo): info(x); ->end sw(foprename): rename(x); ->end sw(fopcopy): copy(x); ->end sw(foppermit): permit(x); ->end sw(fopstamp): stamp(x); ->end sw(foptime): time(x); ->end sw(fopspecial): special(x); ->end sw(fopoldfinfo): finfo(x); ->end sw(fopopeni): sw(fopopeno): sw(fopopenm): sw(fopopena): cb == open(code,file) *lea sop,a0; *move.l a0,spc %if code=fopopeni %start *lea refresh input, a0; *move.l a0,fpc %else *lea flush output,a0; *move.l a0,fpc %finish cb_fastpc = fpc cb_pc = spc cb_gla = a4 end: %result == cb %end