💾 Archived View for mirrors.apple2.org.za › archive › ground.icaen.uiowa.edu › Srcs › puffin.txt captured on 2024-06-16 at 14:27:58.

View Raw

More Information

⬅️ Previous capture (2023-01-29)

-=-=-=-=-=-=-

  (*$S+*)
  (*$V-*)
  PROGRAM puffin;
  CONST
  maxunit  =12; (* maximum number for a pascal unit *)
  maxdir   =105; (* maximum number of entries in a DOS diskette directory *)
   maxlink  =122; (* maximum number of entries in a track sector list *)
  didleng  =30; (* maximum length of a DOS file name *)
   pidleng  =23; (* maximum length of a Pascal file name *)
   sidleng  = 5; (* maximum length of a Pascal file name suffix, e.g. ".TEXT" *)
  sectsize =256; (* size of a DOS sector *)
  blocksize=512;
   pagesize =1024; (* size of a pascal text page *)
  maxbyte  =255;
  
   dirtrack    =17; (* track number where a DOS directory resides *)
  firstdirsect=15; (* first sector of a DOS directory *)
  
  TYPE
  byterange =0..maxbyte;
  sectrange =0..sectsize;
  dirrange  =0..maxdir;
  linkrange =0..maxlink;
   unitrange =0..maxunit;
  blockrange=0..blocksize;
  pagerange =0..pagesize;
  
  sectbuffer =PACKED ARRAY[byterange] OF byterange;
  blockbuffer=PACKED ARRAY[1..blocksize] OF byterange;
                     pagebuffer =PACKED ARRAY[1..pagesize] OF byterange;
  
  link=PACKED RECORD (* used to designate track/sector combinations *)
  tracknum:byterange;
  sectnum:byterange;
  END;
  tslist=(* track sector list *)
  RECORD
  continuation:link;
  list:PACKED ARRAY[1..maxlink] OF link;
  END;
  
  did=STRING[didleng];
  pid=STRING[pidleng];
  sid=STRING[sidleng];
  
  dosfilekinds= (* DOS file types *)
  (volinfo,unknown,dftext,dfinteger,applesoft,binary);
  pasfilekinds= (* some of the Pascal file types *)
               (textfile,fotofile,untyped);
  
  (* Pascal format for the information contained in a DOS directory entry *)
  dosdirentry=PACKED RECORD CASE dfkind:dosfilekinds OF
  volinfo: (* this is volume info *)
  (dunitnum:unitrange;
  dnumentries:dirrange);
  unknown,
  dftext,
  dfinteger,
  applesoft,
  binary:
  (file_tsl:link;     (* location of file's track-sector list*)
  locked:BOOLEAN;    (* designates whether file is locked *)
  name:did;
                                                          sectorcount:byterange); (* number of diskette sectors allocated *)
  END;
  
   dosdirectory=ARRAY[dirrange] OF dosdirentry;
  VAR
  dosdir:dosdirectory; (* current working DOS directory *)
   unitnum:unitrange;
  ioerror:INTEGER;
  ch:CHAR;
  
  FUNCTION readtrksec(unitnum:unitrange;
  trksec:link;VAR sb:sectbuffer;VAR ioerror:INTEGER):BOOLEAN;
  (* reads sector number 'trksec.sectnum' from tracknumber 'trksec.tracknum'
  on disk drive nunber 'unitnum' *)
  VAR
   block:blockbuffer;
  blocknum,offset:INTEGER;
  BEGIN
  WITH trksec DO 
  BEGIN
  (* compute half-block corresponding to desired sector  *)
  IF (sectnum IN [0,15]) THEN blocknum:=sectnum
  ELSE blocknum:=15-sectnum;
  IF (odd(blocknum)) THEN offset:=256
  ELSE offset:=0;
  (* now compte blocknum off set from track 0 *)
  blocknum:=(blocknum DIV 2)+8*tracknum;
  END; (* WITH trksec DO *)
  (*$I-*)
  unitread(unitnum,block,sizeof(block),blocknum);
  (*$I+*)
  ioerror:=ioresult;
  IF NOT (ioerror=0) THEN readtrksec:=FALSE
  ELSE BEGIN
               (* write into sector buffer *)
  moveleft(block[offset+1],sb,sizeof(sectbuffer));
  readtrksec:=TRUE;
  END; (* IF...THEN...ELSE *)
  END;
  
   FUNCTION writetrksec(unitnum:unitrange;
  trksec:link;VAR sb:sectbuffer;VAR ioerror:INTEGER):BOOLEAN;
   VAR
  blocknum,offset:INTEGER;
   block:blockbuffer;
  BEGIN
  (* see comments for 'readtrksec' *)
  WITH trksec DO 
  BEGIN
  (* compute half-block corresponding to desired sector  *)
  IF (sectnum IN [0,15]) THEN blocknum:=sectnum
  ELSE blocknum:=15-sectnum;
  IF (odd(blocknum)) THEN offset:=256
  ELSE offset:=0;
  (* now compte blocknum off set from track 0 *)
  blocknum:=(blocknum DIV 2)+8*tracknum;
  END; (* WITH trksec DO *)
  (*$I-*)
  unitread(unitnum,block,sizeof(block),blocknum);
  (*$I+*)
  ioerror:=ioresult;
  IF NOT (ioerror=0) THEN writetrksec:=FALSE
  ELSE BEGIN
  moveleft(sb,block[offset+1],sizeof(sectbuffer));
  (*$I-*)
  unitwrite(unitnum,block,sizeof(block));
  (*$I+*)
  ioerror:=ioresult;
  writetrksec:=ioerror=0;
  END;
  END;
  
            FUNCTION searchdir(target:did;VAR index:dirrange):BOOLEAN;
  VAR
  found:BOOLEAN;
  BEGIN
  found:=FALSE;
  index:=dosdir[0].dnumentries;
   WHILE NOT (found OR (index=0)) DO
  BEGIN
  found:=target=dosdir[index].name;
  index:=index-1;
  END;
   IF found THEN index:=index+1;
  searchdir:=found;
  END;
  
  FUNCTION stoi:INTEGER;
  VAR
  ch:CHAR;
   x:INTEGER;
  BEGIN
  x:=0;
  read(ch);
  WHILE ch IN ['0'..'9'] DO
  BEGIN
  x:=10*x+(ord(ch)-ord('0'));
    read(ch);
  END;
  writeln;
  stoi:=x;
  END;
  
  FUNCTION get_unit_num(VAR unitnum:unitrange):BOOLEAN;
  VAR
  un:INTEGER;
  BEGIN
  REPEAT
  writeln;
  writeln('Enter the unitnum number [4,5,9..12] of the disk drive containing');
  writeln('the DOS diskette to be cataloged. Enter 0 to escape.');
  writeln;
  write('>> ');
  un:=stoi;
  IF NOT (un IN [0,4,5,9..12]) THEN writeln(chr(7));
  UNTIL un IN [0,4,5,9..12];
  unitnum:=un;
  get_unit_num:=(un<>0);
  END;
  
  PROCEDURE capitalize(VAR line:STRING);
  CONST
  ordsmla=97;
  ordsmlz=122;
                shiftcase=32;
  VAR
  index:0..maxbyte;
  BEGIN
  FOR index:=1 TO length(line) DO
  IF line[index] IN [chr(ordsmla)..chr(ordsmlz)]
   THEN line[index]:=chr(ord(line[index])-shiftcase);
  END;
  
  FUNCTION getpasid(VAR name:pid):BOOLEAN;
  BEGIN
  writeln;
  writeln('Enter the name of the Pascal destination file,');
  writeln('or enter <RET> to exit:');
  writeln;
  write('>>');
  readln(name);
  IF (length(name)=0) THEN getpasid:=FALSE
  ELSE BEGIN
  capitalize(name);
  getpasid:=TRUE;
    END;
  END;
  
  FUNCTION getdosid(VAR name:did):BOOLEAN;
  BEGIN
  writeln;
  writeln('Enter the name of the DOS file to transfer,');
  writeln('or enter <RET> to exit:');
  writeln;
  write('>>');
  readln(name);
  IF (length(name)=0) THEN getdosid:=FALSE
  ELSE BEGIN
  capitalize(name);
  getdosid:=TRUE;
    END;
  END;
  
  PROCEDURE getfiletype(VAR suffix:sid;VAR filetype:pasfilekinds);
  BEGIN
  writeln;
  writeln('Transfer to a:');
  writeln;
  writeln('T)ext file, F)oto file, or D)ata (binary) file?');
          writeln;
  write('>> ');
   read(keyboard,ch);
  WHILE NOT (ch IN ['t','f','d','T','F','D']) DO
   BEGIN write(chr(7));read(keyboard,ch); END;
   writeln(ch);
   CASE ch OF
  'T','t':BEGIN suffix:='.TEXT';filetype:=textfile; END;
  'F','f':BEGIN suffix:='.FOTO';filetype:=fotofile; END;
  'D','d':BEGIN suffix:='';filetype:=untyped; END;
  END;
  END;
  
  PROCEDURE printmenu;
  CONST
  cleoln=29;
  BEGIN
  gotoxy(0,0);
  write(chr(cleoln),'C)atalog, D)isplay, T)ransfer, Q)uit?');
  END;
  
  PROCEDURE readcommand(VAR ch:CHAR);
  BEGIN
  read(keyboard,ch);
  WHILE NOT(ch IN ['C','c','D','d','T','t','Q','q']) DO
  BEGIN
  write(chr(7));
  read(keyboard,ch);
  END;
   writeln;
  END;
  
  PROCEDURE displayentry(de:dosdirentry);
  BEGIN
  WITH de DO
  BEGIN
  write(name,' ':(didleng-length(name)+1));
  CASE dfkind OF
  dftext:write('text':6);
  dfinteger:write('int':6);
  applesoft:write('soft':6);
  binary:write('bnry':6);
  unknown:write('unkn':6);
  END;
  IF locked THEN write('yes':8)
  ELSE write('no':8);
   write(sectorcount:9);
  writeln(filetsl.tracknum:6,'-',filetsl.sectnum:3);
  END;
  END;
  
  PROCEDURE displayheader;
  BEGIN
  write('File Name');
  write('Type':((didleng-length('file name'))+7));
  write('Locked':8);
  write('Sectors':9);
  writeln('TSL link':10);
  END;
  
  PROCEDURE displaydir;
  CONST
  cleos=11;
   esc=27;
  maxlines=21;
  VAR
  cumsectors:INTEGER;
  count:dirrange;
  
  BEGIN
   page(output);
  gotoxy(0,1);
  cumsectors:=0;
  IF dosdir[0].dnumentries=0 THEN writeln('The working directory is empty!')
  ELSE BEGIN
  displayheader;
  FOR count:=1 TO dosdir[0].dnumentries DO
  BEGIN
  displayentry(dosdir[count]);
  cumsectors:=cumsectors+dosdir[count].sectorcount;
  IF (count MOD maxlines)=0 THEN
  BEGIN
  write('Type <RET> to continue, <ESC> to stop ');
  read(keyboard,ch);
  IF ch=chr(esc) THEN exit(displaydir)
  ELSE BEGIN gotoxy(0,2);write(chr(cleos)); END;
  END;
  END;
      write(dosdir[0].dnumentries,' files on disk, ',cumsectors,' sectors in use');
  END;
  END;
  
            PROCEDURE catalog;
  CONST
  nextlink   = 1; (* relative byte 1 of directory sector is link to 
  next directory sector *)
  
  zerobase   =11; (* first byte of file info in a directory sector *)
  entrylength=35; (* DOS directory entries occupy 35 bytes *)
  mark      =maxbyte; (* directory entries which have been deleted are 'marked'
  in (relative) byte zero *)
   maxindex  =  7; (* maximum of 7 directory entries in a sector *)
   
  space= 32; (* ASCII space *)
  tilde=126; (* ASCII tilde *)
  TYPE
  indexrange=0..maxindex;
   entrybuffer=PACKED ARRAY[1..entrylength] OF byterange;
  
  VAR
  sectorindex:indexrange;
   entrybase:byterange;
  dir_link:link;
  dir_sector:sectbuffer;
  nextentry:entrybuffer;
  entrycount:dirrange;
  
  FUNCTION eodir(dirlink:link):BOOLEAN;
  BEGIN
  WITH dirlink DO
  eodir:=(sectnum=0) AND (tracknum=0);
  END;
  
  PROCEDURE fill_dir_entry(VAR de:dosdirentry;VAR eb:entrybuffer);
  CONST
  linkoffset = 1; (* relative byte zero for an entry gives the location of its
           track-sector list *)
  kindoffset = 3; (* relative byte 2 designates the file type of the entry *)
  nameoffset = 4; (* relative byte 3 is the beginning of the file name *)
  countoffset=34; (* relative byte 33 is the sector count (MOD sectsize) for
  the file *)
  lockbit   =128; (* locked files have the high bit of the file type byte set *)
  VAR
  j,kind:byterange;
   nonblank:0..didleng;
  BEGIN
  WITH de DO
  BEGIN
  filetsl.tracknum:=eb[linkoffset];
  filetsl.sectnum:=eb[linkoffset+1];
  kind:=eb[kindoffset];
  IF NOT ((kind MOD lockbit) IN [0,1,2,4]) THEN dfkind:=unknown
  ELSE CASE (kind MOD lockbit) OF
  0:dfkind:=dftext;
  1:dfkind:=dfinteger;
  2:dfkind:=applesoft;
  4:dfkind:=binary;
  END;
  IF ((kind DIV lockbit)=1) THEN locked:=TRUE
  ELSE locked:=FALSE;
  FOR j:=0 TO (didleng-1) DO
  BEGIN
  (* set the high bit low to get true ASCII *)
  eb[nameoffset+j]:=eb[nameoffset+j] MOD 128;
  (* eliminate any weird characters *)
                                                                           IF NOT (eb[nameoffset+j] IN [space..tilde]) THEN eb[nameoffset+j]:=space;
  END;
  (* find the leftmost trailing blank in the name field *)
  nonblank:=-scan(-didleng,<>' ',eb[nameoffset+didleng-1]);
  (* non_blank=0 if and only if no trailing blanks *)
  (* initialize the length of 'name' *)
  (*$R-*)
  name[0]:=chr(didleng-nonblank);
  (*$R+*)
  (* finally move in the name *)
  moveleft(eb[nameoffset],name[1],length(name));
  sectorcount:=eb[countoffset];
  END; (* WITH de DO *)
  END; (* filldirentry *)
  
  FUNCTION eodirsector(VAR index:indexrange;
  VAR dirsector:sectbuffer;VAR entrybase:byterange):BOOLEAN;
  VAR
  nofile:BOOLEAN;
  BEGIN
  nofile:=TRUE;
  WHILE (nofile AND (index<maxindex)) DO
  BEGIN
  index:=index+1;
  entrybase:=zerobase+(index-1)*entrylength;
  nofile:=(dirsector[entrybase] IN [0,mark]);
  END;
  eodirsector:=nofile;
  END;
  
  BEGIN (* catalog *)
  page(output);
  IF NOT getunitnum(unitnum) THEN exit(catalog);
  WITH dir_link DO
  BEGIN
  tracknum:=dirtrack;
                     sectnum:=firstdirsect;
  END;
  entrycount:=0;
  WHILE NOT eodir(dir_link) DO
  BEGIN
  IF NOT readtrksec(unitnum,dir_link,dir_sector,ioerror)
  THEN BEGIN writeln('ioerror ',ioerror,' reading directory');
  exit(catalog);
  END
  ELSE BEGIN
  sectorindex:=0;
  WHILE NOT eodirsector(sectorindex,dir_sector,entrybase) DO
  BEGIN
  moveleft(dir_sector[entrybase],nextentry,entrylength);
  entrycount:=entrycount+1;
  filldirentry(dosdir[entrycount],nextentry);
  END;
  END; (*IF...THEN...ELSE *)
  WITH dir_link DO
  BEGIN
  tracknum:=dir_sector[nextlink];
  sectnum:=dir_sector[nextlink+1];
  END;
  END;
  WITH dosdir[0] DO
  BEGIN
  dnumentries:=entrycount;
  dunitnum:=unitnum;
  END;
  displaydir;
  END; (* catalog *)
  
  (*$I DPTH2.1:TRANSFER.TEXT*)

PROCEDURE transfer;
  TYPE
  ffile=FILE;
  VAR
  dosname    :did;
  pasname    :pid;
  suffix     :sid;
  dirindex   :dirrange;
  linkindex  :linkrange;
  nextlink,
  nextnode   :link;
  nextsector :sectbuffer;
  currentnode:tslist;
  ioerror    :INTEGER;
  pasfile    :ffile;
  primpage,
  sparepage  :pagebuffer;
  pagepntr,
  sparepntr  :pagerange;
  relblock   :INTEGER;
  filetype   :pasfilekinds;
   fotoflag   :BOOLEAN; (* flag for shifts of size 'binaryoffset' *)
  
  PROCEDURE abortxfer(ioerror:INTEGER);
  BEGIN
  writeln;
  writeln('IO ERROR ',ioerror);
  writeln('EXITING TRANSFER');
  (*$I-*)
  close(pasfile,purge);
  (*$I+*)
  exit(transfer);
  END;
  
  FUNCTION openfile(name:pid;VAR f:ffile;VAR ioerror:INTEGER):BOOLEAN;
  BEGIN
  (*$I-*)
  rewrite(f,name);
  ioerror:=ioresult;
  (*$I+*)
  openfile:=ioerror=0;
  END;
  
  FUNCTION eolist(next:link):BOOLEAN;
  BEGIN
  WITH next DO
  eolist:=((tracknum=0) AND (sectnum=0));
  END;
  
  FUNCTION get_node(location:link;VAR listdata:tslist):BOOLEAN;
        CONST
  contoffset=  1; (* beginning of continuation link *)
  contleng  =  2; (* length of continuation info *)
  listoffset= 12; (* beginning of list of track sector links *)
  listleng  =244; (* length of list data *)
  VAR
  sb:sectbuffer;
  i:linkrange;
  BEGIN
  IF NOT (readtrksec(dosdir[0].dunitnum,location,sb,ioerror)) THEN get_node:=FALSE
  ELSE WITH listdata DO
  BEGIN
  continuation.tracknum:=sb[contoffset];
  continuation.sectnum:=sb[contoffset+1];
  FOR i:=1 TO maxlink DO
  BEGIN
  list[i].tracknum:=sb[listoffset+(i-1)*contleng];
  list[i].sectnum:=sb[listoffset+(i-1)*contleng+1];
  END;
  get_node:=TRUE;
  END;
  END;
  
  FUNCTION eonode(VAR linkindex:linkrange;VAR tslink:link):BOOLEAN;
  VAR
  emptysector:BOOLEAN;
  BEGIN
  emptysector:=TRUE;
  WHILE ((linkindex<maxlink) AND emptysector) DO
  BEGIN
  linkindex:=linkindex+1;
  WITH currentnode.list[linkindex] DO
  emptysector:=(tracknum=0) AND (sectnum=0)
  END;
  IF NOT emptysector THEN tslink:=currentnode.list[linkindex];
                    eonode:=emptysector;
  END;
  
  PROCEDURE writeblocks(VAR pb:pagebuffer;pbpntr:pagerange;
  blockcount:INTEGER;VAR relblock:INTEGER);
  BEGIN
  (******* note: pbpntr should be divisible by blocksize upon entry *)
  (*$I-*)
  pbpntr:=pbpntr-(blockcount*blocksize)+1;
  IF (blockwrite(pasfile,pb[pbpntr],blockcount,relblock)=blockcount)
  THEN relblock:=relblock+blockcount
  (*$I+*)
  ELSE abortxfer(ioerror);
  END;
  
  PROCEDURE stuff(VAR s:sectbuffer;VAR p:pagebuffer;
  VAR pagepntr:pagerange;filetype:pasfilekinds);
  PROCEDURE stufftext;
  CONST
  hibit=128;
  asciicr=13; (* ASCII carriage return *)
  space=32; (* ASCII space *)
  tilde=126; (* ASCII tilde *)
  VAR
  lengindex,nextnull:sectrange;
  leadindex,lagindex:pagerange;
  cr,null:CHAR;
  primfull,endofspare:BOOLEAN;
  BEGIN
  (* zero the high bits to get true ASCII *)
  FOR lengindex:=0 TO maxbyte DO
  BEGIN
  (* zero the high bit of s[lengindex] *)
  s[lengindex]:=s[lengindex] MOD hibit;
  (* eliminate weird characters by setting to null *)
        IF NOT (s[lengindex] IN [space..tilde,asciicr]) THEN s[lengindex]:=0;
  END;
  
  (* squeeze out the middle null characters *)
  null:=chr(0);
  lengindex:=sectsize;
  nextnull:=scan(lengindex,=null,s);
  WHILE (nextnull<lengindex) DO
  BEGIN
  moveleft(s[nextnull+1],s[nextnull],lengindex-nextnull-1);
  lengindex:=lengindex-1;
  nextnull:=scan(lengindex,=null,s);
  END;
  
  moveleft(s,sparepage[sparepntr+1],lengindex);
  sparepntr:=sparepntr+lengindex;
  endofspare:=FALSE;
  primfull:=FALSE;
  lagindex:=0;
  cr:=chr(asciicr);
  
  WHILE NOT (endofspare OR primfull) DO BEGIN
  leadindex:=scan((sparepntr-lagindex),=cr,sparepage[lagindex+1]);
   IF (leadindex=(sparepntr-lagindex)) THEN endofspare:=TRUE
  ELSE IF ((leadindex+pagepntr+1) > pagesize) THEN primfull:=TRUE
  ELSE BEGIN
  moveleft(sparepage[lagindex+1],primpage[pagepntr+1],leadindex+1);
  pagepntr:=pagepntr+leadindex+1;
  lagindex:=lagindex+leadindex+1;
  endofspare:=(lagindex=sparepntr);
  END;
   END;
  
  IF primfull THEN pagepntr:=pagesize;
      moveleft(sparepage[lagindex+1],sparepage,sparepntr-lagindex);
  sparepntr:=sparepntr-lagindex;
  
  END;(* stufftext *)
  
  PROCEDURE stuffoto;
  CONST
  fotoffset=4; (* four bytes of DOS address junk in the first sector *)
  BEGIN
  IF fotoflag (* first foto sector *)
  THEN BEGIN
  moveleft(s[fotoffset],primpage,sectsize-fotoffset);
  pagepntr:=sectsize-fotoffset;
  fotoflag:=FALSE;
  END
  ELSE BEGIN
  moveleft(sparepage,primpage[pagepntr+1],sparepntr);
  pagepntr:=sparepntr+pagepntr;
  sparepntr:=0;
  IF ((pagepntr+sectsize) <= pagesize) (* i.e., enough room for a sector *)
  THEN BEGIN
  moveleft(s,primpage[pagepntr+1],sectsize);
  pagepntr:=pagepntr+sectsize;
  END
  ELSE BEGIN
  (* move as much as possible into the primary page *)
  moveleft(s,primpage[pagepntr+1],pagesize-pagepntr);
  (* move the rest into the spare page *)
  (* begin by updating sparepntr *)
  sparepntr:=sectsize-(pagesize-pagepntr);
  moveleft(s[pagesize-pagepntr],sparepage,sparepntr);
  (* update pagepntr to end of page *)
       pagepntr:=pagesize;
  END;
  END;
  END;
  
  BEGIN
   IF (filetype=textfile) THEN stufftext
  ELSE IF (filetype=fotofile) THEN stuffoto
  ELSE BEGIN
  moveleft(s,p[pagepntr+1],sectsize);
  pagepntr:=pagepntr+sectsize;
    END;
  END;
  
  BEGIN
  page(output);
  IF NOT (getdosid(dosname)) THEN exit(transfer);
  WHILE NOT (searchdir(dosname,dirindex)) DO
  BEGIN
  writeln;
  writeln(dosname,' not in current dosdir');
  IF NOT (getdosid(dosname)) THEN exit(transfer);
     END;
    writeln;
  displayheader;
  displayentry(dosdir[dirindex]);
  nextnode:=dosdir[dirindex].file_tsl;
  
  getfiletype(suffix,filetype);
  IF NOT (getpasid(pasname)) THEN exit(transfer);
  WHILE NOT (openfile(concat(pasname,suffix),pasfile,ioerror)) DO
  BEGIN
  writeln;
  writeln('IO error ',ioerror,' opening ',concat(pasname,suffix));
  IF NOT (getpasid(pasname)) THEN exit(transfer);
   END;
  
  (* initalize the page buffers and associated pointers *)
  fillchar(primpage,pagesize,chr(0));
  fillchar(sparepage,pagesize,chr(0));
      pagepntr:=0;
  sparepntr:=0;
  relblock:=0;
  
  IF (filetype=fotofile) THEN fotoflag:=TRUE 
  ELSE IF (filetype=textfile) THEN 
  (* write two header blocks of nulls *)
  BEGIN
  relblock:=blockwrite(pasfile,primpage,1,relblock)+relblock;
  relblock:=blockwrite(pasfile,primpage,1,relblock)+relblock;
  END;
  
  WHILE NOT eolist(nextnode) DO
  IF NOT (get_node(nextnode,currentnode)) THEN abortxfer(ioerror)
  ELSE BEGIN
  linkindex:=0;
  WHILE NOT eonode(linkindex,nextlink) DO
  IF NOT (readtrksec(dosdir[0].dunitnum,nextlink,nextsector,ioerror))
   THEN abortxfer(ioerror)
  ELSE BEGIN
  stuff(nextsector,primpage,pagepntr,filetype);
   IF (pagepntr=pagesize) THEN
  BEGIN
  writeblocks(primpage,pagepntr,2,relblock);
  pagepntr:=0;
     fillchar(primpage,pagesize,chr(0));
  END;
  END;
  nextnode:=currentnode.continuation;
  END;
  
  (* pick up anything in the spare page *)
  moveleft(sparepage,primpage[pagepntr+1],sparepntr);
  pagepntr:=pagepntr+sparepntr;
                                                      (* if page is partially full it needs to be written *)
  IF (pagepntr>0) THEN (* note: pagepntr<pagesize will be true here *)
  BEGIN
  pagepntr:=blocksize*(1+(pagepntr DIV blocksize));
  writeblocks(primpage,pagepntr,(pagepntr DIV blocksize),relblock);
   END;
  
  IF ((filetype=textfile) AND odd(relblock)) THEN 
  BEGIN
  fillchar(primpage,pagesize,chr(0));
  relblock:=blockwrite(pasfile,primpage,1,relblock)+relblock;
  END;
  
  writeln;
  writeln(dosname,' transferred to ',concat(pasname,suffix));
  writeln(relblock,' blocks transferred');
  close(pasfile,lock);
  END;
  
                                                                                                                                                                                                                                                                                                                                                                                                                                                       
  
  BEGIN
  WITH dosdir[0] DO
  BEGIN dfkind:=volinfo; dnumentries:=0; dunitnum:=0; END;
  page(output);
  gotoxy(0,5);
  writeln('Welcome to PUFFIN!');
  REPEAT
  printmenu;
  readcommand(ch);
  CASE ch OF
  'c','C':catalog;
  'd','D':displaydir;
                  't','T':transfer;
  END;
  UNTIL ch IN ['Q','q'];
  END.