!***********************************************************************
!*
!*                Program to write an ICL VME 2900 tape
!*
!*            R.D. Eager   University of Kent   MCMLXXXIV
!*
!***********************************************************************
!
constantinteger  version = 1;           ! Major version number
constantinteger  edit    = 0;           ! Edit number within major version
!
!***********************************************************************
!*
!*          Constants
!*
!***********************************************************************
!
constantinteger  no = 0, yes = 1
constantinteger  defaultthreshold = 10
constantinteger  maxthreshold = 1000
constantinteger  sscharfiletype = 3;    ! Subsystem file type
constantinteger  ssdatafiletype = 4;    ! Subsystem file type
constantinteger  maxfile = 999;         ! Maximum number of files per tape
constantinteger  listchan = 80;         ! Channel for listing of files written
constantinteger  tapechan = 1
constantbyteinteger  nl = 10
constantbyteintegerarray  monthdays(1:11) = c 
31,28,31,30,31,30,31,31,30,31,30
constantstring (1) space char = " "
constantstring (1) snl = "
"
constantstring (6) data00 = "DATA00"
constantinteger  keymax = 6;            ! Number of parameter keywords
constantstring (9)array  keys(1:keymax) = c 
"TAPE",
"STARTFILE",
"LISTING",
"UPPER",
"THRESHOLD",
"VERSION"
!
!***********************************************************************
!*
!*          Record formats
!*
!***********************************************************************
!
recordformat  descf(integer  dr0,dr1 or  c 
                    longinteger  dr)
recordformat  fdf(integer  link,dsnum,byteinteger  status,accessroute,
                  valid action,cur state,mode of use,mode,file org,
                  dev code,rec type,flags,lm,rm,integer  asvar,arec,
                  recsize,minrec,maxrec,maxsize,lastrec,conad,currec,
                  cur,end,transfers,darecnum,cursize,datastart,
                  string (31) iden,integer  keydesc0,keydesc1,
                  recsizedesc0,recsizedesc1,byteinteger  f77flag,
                  f77form,f77access,f77status,integer  f77recl,f77nrec,
                  idaddr,byteinteger  f77blank,f77ufd,spare1,spare2)
recordformat  hf(integer  dataend,datastart,filesize,filetype,sum,
                 datetime,
                 (integer  spare1,spare2 or  c      { Character file }
                 integer  format,records or  c      { Data file }
                 integer  adir,count or  c          { Pdfile }
                 integer  pstart,spare3 or  c       { Old directory file }
                 integer  spare4,controlmode or  c  { Background control file }
                 integer  lda,ofm))                 { Object file }
recordformat  rf(integer  conad,filetype,datastart,dataend)
!
!
!***********************************************************************
!*
!*          Subsystem references
!*
!***********************************************************************
!
systemroutinespec  connect(string (31) file,integer  mode,hole,
                           prot,record (rf)name  r,integername  flag)
externalstringfunctionspec  date
systemroutinespec  define(integer  chan,string (31) iden,
                          integername  afd,flag)
systemroutinespec  disconnect(string (31) file,integername  flag)
systemstringfunctionspec  failuremessage(integer  mess)
systemroutinespec  fill(integer  length,from,filler)
externalintegerfunctionspec  instream
systemintegerfunctionspec  iocp(integer  ep,parm)
systemroutinespec  itoe(integer  ad,l)
systemstringfunctionspec  itos(integer  n)
systemintegermapspec  mapssfd(integer  dsnum)
systemroutinespec  move(integer  length,from,to)
externalintegerfunctionspec  outpos
externalintegerfunctionspec  outstream
externalroutinespec  prompt(string (255) s)
systemintegerfunctionspec  pstoi(string (63) s)
systemroutinespec  setfname(string (63) s)
externalroutinespec  set return code(integer  i)
externalstringfunctionspec  time
systemroutinespec  uctranslate(integer  ad,len)
!
!
!***********************************************************************
!*
!*          Magnetic tape interface routines
!*
!***********************************************************************
!
externalroutinespec  askmag(integer  channel,string (7) vol,
                            integername  flag)
externalroutinespec  fskiptmmag(integer  chan,n,integername  flag)
externalroutinespec  readmag(integer  channel,ad,integername  len,flag)
externalroutinespec  rewindmag(integer  channel)
externalroutinespec  skipmag(integer  channel,n)
externalroutinespec  writemag(integer  channel,ad,len,integername  flag)
externalroutinespec  writetmmag(integer  chan,integername  flag)
externalroutinespec  unloadmag(integer  channel)
!
!
!***********************************************************************
!*
!*          Own variables
!*
!***********************************************************************
!
owninteger  blockno;                    ! Number of next block to be written
!
!
!***********************************************************************
!*
!*          Forward references
!*
!***********************************************************************
!
integerfunctionspec  matchstrings(stringname  a,string (255) b)
routinespec  write label1(string (4) type,string (16) name,
                          integer  fileno,generation,blocks)
routinespec  write label2(string (4) type,integer  blocksize,maxrec)
routinespec  write tape mark
string (6)functionspec  year and day
!
!
!***********************************************************************
!*
!*          Service routines
!*
!***********************************************************************
!
routine  clearstream(integer  chan)
! Clears  out  a  channel  definition, but does not give an error if the
! channel was not defined.
record (fdf)name  f
!
if  mapssfd(chan) # 0 then  start 
   f == record(mapssfd(chan))
   if  f_status = 0 then  start 
      mapssfd(chan) = 0
      f_dsnum = 0;                      ! Mark descriptor as free
   finish 
finish 
end ;   ! of clearstream
!
!-----------------------------------------------------------------------
!
routine  closestream(integer  chan)
! Private version of 'closestream'.  Does  not  give  an  error  if  the
! operation fails.
integer  flag
!
return  unless  instream # chan # outstream
flag = iocp(16,chan)
end ;   ! of closestream
!
!-----------------------------------------------------------------------
!
routine  fail(integer  n)
selectoutput(0)
printstring(snl."WRITEBTAPE fails -")
printstring(failuremessage(n))
closestream(listchan)
clearstream(listchan)
set return code(n)
stop 
end ;   ! of fail
!
!-----------------------------------------------------------------------
!
integerfunction  findkey(stringname  wksp,stringarrayname  keys,
                         integer  pmax)
integer  f,i
!
if  length(wksp) = 0 then  result  = -2;! Missing keyword
f = 0
for  i = 1,1,pmax cycle 
   if  matchstrings(wksp,keys(i)) = yes then  start 
      unless  f = 0 then  result  = -1
      f = i
   finish 
repeat 
result  = f
end ;   ! of findkey
!
!-----------------------------------------------------------------------
!
integerfunction  getpar(stringname  wksp,param,integername  parptr,parleng)
integer  c,inpr
!
inpr = 0
wksp = ""
!
cycle 
   parptr = parptr + 1
   if  parptr > parleng then  result  = -1
   c = charno(param,parptr)
   if  c = ',' or  c = '=' then  result  = c
   wksp = wksp.tostring(c)
repeat 
end ;   ! of getpar
!
!-----------------------------------------------------------------------
!
integerfunction  matchstrings(stringname  a,string (255) b)
integer  l
!
l = length(a)
if  length(b) < l then  result  = no
length(b) = l
if  a = b then  result  = yes else  result  = no
end ;   ! of matchstrings
!
!-----------------------------------------------------------------------
!
integerfunction  paramdecode(string (255) param,integer  pmax,
                               stringarrayname  keys,pars)
integer  i,pnum,pn,res,c,parptr,parleng
string (255) wksp
!
for  i = 1,1,pmax cycle 
   pars(i) = "";                        ! Initialise
repeat 
parptr = 0
pnum = 1
parleng = length(param)
!
cycle 
   c = getpar(wksp,param,parptr,parleng)
   res = 0
   if  c # '=' then  start 
      pn = pnum
   else 
      pn = findkey(wksp,keys,pmax)
      if  pn = 0 then  res = 322;       ! Unknown keyword
      if  pn = -1 then  res = 321;      ! Ambiguous keyword
      if  pn = -2 then  res = 325;      ! Missing keyword
      c = getpar(wksp,param,parptr,parleng)
      if  c = '=' then  res = 320;      ! Format error
   finish 
   if  pn > pmax then  res = 323;       ! Too many parameters
   if  res = 0 then  start 
      if  wksp # "" # pars(pn) then  res = 324
                                        ! Duplicated parameter
      pars(pn) = wksp
   finish 
   if  res # 0 then  result  = res
   if  c = -1 then  result  = 0;        ! Finished, all OK
   pnum = pnum + 1
repeat 
end ;   ! of paramdecode
!
!-----------------------------------------------------------------------
!
routine  readline(stringname  s)
integer  c
string (255) work1,work2
!
on  event  9 start 
   s = ".END"
   return 
finish 
!
s = ""
cycle 
   cycle 
      readsymbol(c)
      exit  if  c = nl
      s <- s.tostring(c)
   repeat 
   !
   s = work1.work2 while  s -> work1.(" ").work2
   !
   exit  unless  length(s) = 0
repeat 
uctranslate(addr(s)+1,length(s))
end ;   ! of readline
!
!-----------------------------------------------------------------------
!
routine  warn(string (255) s)
s <- "Warning - ".s
selectoutput(0)
printstring(s)
selectoutput(listchan)
printstring(s)
end ;   ! of warn
!
!-----------------------------------------------------------------------
!
routine  write block(integer  ad,len)
! Writes a block to the tape of length 'len', starting at 'ad'. Fills in
! the block sequence number and the block length (which together make up
! the  'block organisation data'), for which space should have been left
! at 'ad'.
integer  flag
!
move(4,addr(blockno),ad);               ! Block sequence number
halfinteger(ad+4) = len;                ! Block length
blockno = blockno + 1
!
writemag(tapechan,ad,len,flag)
if  flag # 0 then  start 
   unloadmag(tapechan)
   setfname("Tape write error")
   fail(233);                           ! General error
finish 
end ;   ! of write block
!
!-----------------------------------------------------------------------
!
routine  write body(integer  conad,threshold,maxrec,blocksize,upper,
                    string (31) input,integername  blocks,records)
integer  lastrec,start,len,i,rad,rlen,count,truncations,translate
record (descf) fdesc,tempdesc
record (hf)name  h
byteintegerarray  b(-7:blocksize-8)
!
! Set bound check inhibit, so that MODD will work sensibly
!
*cpsr _i
i = i!x'400'
*mpsr _i
!
h == record(conad)
start = conad + h_datastart
len = h_dataend - h_datastart
!
fdesc_dr0 = x'18000000'!len
fdesc_dr1 = start
!
records = 0
count = 0
truncations = 0
blocks = 0
truncations = 0
!
! Main copy loop
!
while  len > 0 cycle 
   translate = no
   if  h_filetype = sscharfiletype then  start 
      *lb   _nl;                        ! Character for scan
      *ld   _fdesc
      *jat  _11,<endoffile>;            ! Jump on zero descriptor length
      *swne _l =dr ;                    ! Scan for newline
      *jat  _11,<eof>;                  ! Jump on zero descriptor length
      *modd _1;                         ! Move past newline
   eof:
      *std  _tempdesc
      rlen = tempdesc_dr1 - fdesc_dr1 - 1
      len = len - rlen - 1
                                        ! Length of line, excluding newline
      rad = fdesc_dr1
      translate = yes
      *lsd  _tempdesc;                  ! Update descriptor to input file
      *st   _fdesc
   finish  else  c 
   if  h_format & x'ffff' = 1 then  start 
                                        ! Data file, fixed length records
      rad = fdesc_dr1
      rlen = h_format >> 16
      len = len - rlen
      fdesc_dr1 = fdesc_dr1 + rlen
   else ;                               ! Data file, variable length records
      rad = fdesc_dr1 + 2;              ! Omit record header
      rlen = halfinteger(fdesc_dr1) - 2;! Record length
      len = len - rlen - 2
      fdesc_dr1 = fdesc_dr1 + rlen + 2
   finish 
   !
   records = records + 1
   if  rlen > maxrec - 4 then  start 
      rlen = maxrec - 4
      truncations = truncations + 1
      if  truncations <= threshold then  start 
         warn("Record ".itos(records)." of file ".input." truncated".snl)
      finish 
   finish 
   !
   if  translate = yes and  rlen = 0 then  start 
      rad = addr(space char) + 1
      rlen = 1
   finish 
   !
   if  count + rlen + 4 > blocksize - 8 then  start 
      halfinteger(addr(b(-1))) = lastrec
      fill(blocksize-8-count,addr(b(1))+count,0)
                                        ! Pad rest of block with zeros
      write block(addr(b(-7)),blocksize)
      count = 0
      blocks = blocks + 1
   finish 
   !
   halfinteger(addr(b(1))+count) = rlen + 4
   halfinteger(addr(b(3))+count) = 0
   move(rlen,rad,addr(b(5))+count)
   if  translate = yes then  start 
      if  upper = yes then  uctranslate(addr(b(5))+count,rlen)
      itoe(addr(b(5))+count,rlen)
   finish 
   lastrec = count + 2
   count = count + rlen + 4
repeat 
!
endoffile:
!
if  count # 0 then  start ;             ! Write last incomplete block
   halfinteger(addr(b(-1))) = lastrec
   fill(blocksize-8-count,addr(b(1))+count,0)
                                        ! Pad rest of block with zeros
   write block(addr(b(-7)),blocksize)
   blocks = blocks + 1
finish 
!
if  truncations > threshold then  start 
   warn("Total of ".itos(truncations)." records truncated in file ".input.snl)
finish 
end ;   ! of write body
!
!-----------------------------------------------------------------------
!
routine  write file(integer  conad,fileno,threshold,generation,upper,
                            string (31) input,output,integername  records)
! Writes the file 'input' (connected at 'conad') to the tape, giving  it
! the name 'output'.
integer  maxrec,nblocks,blocksize
record (hf)name  h
!
write label1("HDR1",output,fileno,generation,0)
h == record(conad)
if  h_filetype = sscharfiletype then  start 
   maxrec = 256 + 4;                    ! Allow for record header
else 
   maxrec = h_format >> 16 + 4
finish 
blocksize = maxrec
blocksize = 4096 if  blocksize < 4096;  ! Minimum blocksize
blocksize = blocksize + 8;              ! Allow for block organisation data
!
write label2("HDR2",blocksize,maxrec)
write tape mark
write body(conad,threshold,maxrec,blocksize,upper,input,nblocks,records)
if  nblocks = 0 then  warn("File ".input." is empty".snl)
write tape mark
write label1("EOF1",output,fileno,generation,nblocks)
write label2("EOF2",blocksize,maxrec)
write tape mark
end ;   ! of write file
!
!-----------------------------------------------------------------------
!
routine  write label1(string (4) type,string (16) name,integer  fileno,
                      generation,blocks)
! Writes  a first file header or end of file label (HDR1 or EOF1) to the
! tape.
string (31) s
byteintegerarray  b(-5:80)
!
move(4,addr(type)+1,addr(b(1)))
fill(17,addr(b(5)),' ')
move(length(name),addr(name)+1,addr(b(5)))
                                        ! File identifier (VME 2900 filename)
move(6,addr(data00)+1,addr(b(22)));     ! File set identifier
s = "0001";                             ! File section number
move(4,addr(s)+1,addr(b(28)))
s = itos(fileno);                       ! File sequence number
s = "0".s while  length(s) < 4
move(4,addr(s)+1,addr(b(32)))
s = itos(generation)
s = "0".s while  length(s) < 4
move(4,addr(s)+1,addr(b(36)))
s = "01";                               ! Version number
move(2,addr(s)+1,addr(b(40)))
s = year and day;                       ! Creation date
move(6,addr(s)+1,addr(b(42)))
move(6,addr(s)+1,addr(b(48)));          ! Expiration date
b(54) = ' ';                            ! Accessibility - all
s = itos(blocks)
s = "0".s while  length(s) < 6
move(6,addr(s)+1,addr(b(55)))
fill(20,addr(b(61)),' ');               ! System code/reserved
!
itoe(addr(b(1)),80)
write block(addr(b(-5)),86)
end ;   ! of write label1
!
!-----------------------------------------------------------------------
!
routine  write label2(string (4) type,integer  blocksize,maxrec)
! Writes a second file header or end of file label (HDR2 or EOF2) to the
! tape.
string (31) s
byteintegerarray  b(-5:80)
!
move(4,addr(type)+1,addr(b(1)))
b(5) = 'V';                             ! Variable length records
s = itos(blocksize)
s = "0".s while  length(s) < 5
move(5,addr(s)+1,addr(b(6)))
s = itos(maxrec)
s = "0".s while  length(s) < 5
move(5,addr(s)+1,addr(b(11)))
fill(35,addr(b(16)),' ')
b(51) = '0'
b(52) = '8'
fill(28,addr(b(53)),' ')
!
itoe(addr(b(1)),80)
!
write block(addr(b(-5)),86)
end ;   ! of write label2
!
!-----------------------------------------------------------------------
!
routine  write tape mark
! Writes one tape mark to the tape.
integer  flag
!
blockno = blockno + 1
!
writetmmag(tapechan,flag)
if  flag # 0 then  start 
   unloadmag(tapechan)
   setfname("Tape write error")
   fail(233);                           ! General error
finish 
end ;   ! of write tape mark
!
!-----------------------------------------------------------------------
!
routine  write vol1 label(string (6) vol)
! Writes the main volume header label (VOL1) to the tape.
integer  i
byteintegerarray  b(-5:80)
!
i = m'VOL1'
move(4,addr(i),addr(b(1)))
move(6,addr(vol)+1,addr(b(5)))
b(i) = ' ' for  i = 11,1,79
b(80) = '2';                            ! Indicates 2900 standard version
!
itoe(addr(b(1)),80)
write block(addr(b(-5)),86)
end ;   ! of write vol1 label
!
!-----------------------------------------------------------------------
!
string (6)function  year and day
! Yields the year and day within the year as a  6-character  string,  in
! the form ' yyddd'.
integer  month,day,year
string (3) res
string (3) ds
string (8) dt
!
dt = date
res = substring(dt,7,8);                ! Year
year = pstoi(res)
month = pstoi(substring(dt,4,5));       ! Month
day = pstoi(substring(dt,1,2));         ! Day
if  year//4*4 = year and  month > 2 then  day = day + 1
!
while  month > 1 cycle 
   month = month - 1
   day = day + monthdays(month)
repeat 
!
ds = itos(day)
ds = "0".ds while  length(ds) < 3
!
result  = " ".res.ds
end ;   ! of year and day
!
!
!***********************************************************************
!*
!*          W R I T E B T A P E
!*
!***********************************************************************
!
externalroutine  writebtape(string (255) parms)
integer  flag,startfile,fileno,failures,threshold,records,afd,i,c,upper
integer  generation,len
string (63) input,output,name
string (255) s,work
stringname  vol,fs,out,us,ts,vs
record (rf) rr
record (hf)name  h
byteintegerarray  b(-5:80)
string (255)array  options(1:keymax)
!
if  parms = "?" then  start 
   printstring("Parameters are:  ")
   for  i = 1,1,keymax cycle 
      printstring(keys(i))
      printsymbol(',') unless  i = keymax
   repeat 
   newline
   set return code(0)
   return 
finish 
!
flag = paramdecode(parms,keymax,keys,options)
-> err if  flag # 0
vol == options(1)
fs == options(2)
out == options(3)
us == options(4)
ts == options(5)
vs == options(6)
!
if  vol = "" then  start 
   flag = 263;                          ! Wrong number of parameters
   -> err
finish 
unless  1 <= length(vol) <= 6 then  start 
   setfname(keys(1))
   flag = 326;                          ! Invalid value for TAPE parameter
   -> err
finish 
for  i = 1,1,length(vol) cycle 
   c = charno(vol,i)
   unless  'A' <= c <= 'Z' or  c 
           (i # 1 and  ('0' <= c <= '9')) then  start 
      setfname(keys(1))
      flag = 326;                       ! Invalid value for TAPE parameter
      -> err
   finish 
repeat 
vol = vol." " while  length(vol) < 6
!
if  fs # "" then  start ;               ! Starting file specified
   startfile = pstoi(fs)
   unless  1 <= startfile <= maxfile then  start 
      setfname(keys(2))
      flag = 326;                       ! Invalid value for STARTFILE parameter
      -> err
   finish 
finish  else  startfile = 1
!
out = "T#LIST" if  out = ""
!
if  us # "" then  start ;               ! Upper case option specified
   if  matchstrings(us,"NO") = no then  start 
      if  matchstrings(us,"YES") = yes then  start 
         upper = yes
      else 
         setfname(keys(4))
         flag = 326;                    ! Invalid value for UPPER parameter
         -> err
      finish 
   else 
      upper = no
   finish 
finish  else  upper = no
!
if  ts # "" then  start ;               ! Error threshold specified
   threshold = pstoi(ts)
   unless  1 <= threshold <= maxthreshold then  start 
      setfname(keys(5))
      flag = 326;                       ! Invalid value for THRESHOLD parameter
      -> err
   finish 
finish  else  threshold = defaultthreshold
!
if  vs # "" then  start 
   if  matchstrings(vs,"NO") = no then  start 
      if  matchstrings(vs,"YES") = yes then  start 
         printstring("Version: E".itos(version).".".itos(edit).snl)
      else 
         setfname(keys(6))
         flag = 326;                    ! Invalid value for VERSION parameter
         -> err
      finish 
   finish 
finish 
!
askmag(tapechan,vol."*",flag)
if  flag # 0 then  start 
   setfname("Failed to claim tape ".vol)
   flag = 233;                          ! General error
   -> err
finish 
!
rewindmag(tapechan)
fileno = startfile - 1
if  startfile = 1 then  start 
   blockno = 0
   write vol1 label(vol)
else 
   fskiptmmag(tapechan,fileno*3,flag);  ! Position after last required file
   if  flag = 1 then  start 
      setfname("Too few files already on tape")
      flag = 233;                       ! General error
      -> uerr
   finish 
   if  flag # 0 then  start 
      setfname("Tape read error")
      flag = 233;                       ! General error
      -> uerr
   finish 
   !
   skipmag(tapechan,-2);                ! Position before last EOF2
   len = 86;                            ! Length of EOF2
   readmag(tapechan,addr(b(-5)),len,flag)
   if  flag # 0 or  len # 86 then  start 
      setfname("Error reading last EOF2 label")
      flag = 233;                       ! General error
      -> uerr
   finish 
   move(4,addr(b(-5)),addr(blockno))
   blockno = blockno + 2;               ! Number of next block to write
   skipmag(tapechan,1);                 ! Skip over tape mark
finish 
!
define(listchan,out,afd,flag)
-> err if  flag # 0
selectoutput(listchan)
newlines(2)
printstring("    ICL VME 2900 tape ".vol." written at ".time." on ".date)
newlines(2)
printstring("      File       EMAS filename                   VME 2900 filename         ".c 
            "Records".snl)
newlines(2)
!
failures = 0
cycle 
   prompt("File: ")
   readline(s)
   exit  if  s = ".END"
   unless  s -> input.(",").output then  start 
      input = s
      output = s
   finish 
   if  output -> output.("(").work.(")") then  start 
      generation = pstoi(work)
   else 
      generation = 1
   finish 
   unless  1 <= generation <= 4095 then  start 
      warn("Invalid generation number for output file '".output."' - 1 assumed".snl)
      generation = 1
   finish 
   if  length(output) > 16 then  start 
      warn("VME2900 filename '".output."' truncated to 16 characters".snl)
      length(output) = 16
   finish 
   !
   connect(input,1,0,0,rr,flag)
   if  flag = 0 then  start 
      h == record(rr_conad)
      unless  h_filetype = sscharfiletype or  c 
              (h_filetype = ssdatafiletype and  h_format & x'ffff' # 3) then  start 
         flag = 267;                    ! Invalid filetype
         setfname(input)
      finish 
   finish 
   if  flag # 0 then  start 
      warn(failuremessage(flag))
      failures = failures + 1
      continue 
   finish 
   fileno = fileno + 1
   !
   write file(rr_conad,fileno,threshold,generation,upper,input,output,records)
   !
   write(fileno,8)
   spaces(8)
   name = input
   if  name -> work.(".").name then  start ; finish 
   printstring(name)
   spaces(49-outpos)
   printstring(output."(".itos(generation).")")
   spaces(74-outpos)
   write(records,6)
   newline
   disconnect(input,flag)
repeat 
!
write tape mark if  fileno = 0
write tape mark
!
newline
selectoutput(0)
closestream(listchan)
clearstream(listchan)
if  failures # 0 then  start 
   printstring(itos(failures)." file")
   printsymbol('s') if  failures # 1
   printstring(" failed to copy".snl)
finish 
printstring("Tape written".snl)
unloadmag(tapechan)
set return code(-failures)
return 
!
uerr:
unloadmag(tapechan)
!
err:
fail(flag)
end ;   ! of writebtape
endoffile