program ar11 (input,output,afil,xfil,bfil);
{$S- ;}
{$W- ;}
const
   { VMS definitiions }
   SS$_NORMAL = %x01;
   SS$_ENDOFFILE = %x00;
   CLI$K_GETCMD = %x01;
   IO$_WRITELBLK = %x20;
   IO$_READLBLK = %x21;
   IO$_REWIND = %x24;
   IO$_REWINDOFF = %x22;
   IO$_SKIPFILE = %x25;
   IO$_SKIPRECORD = %x26;
   IO$_SPACEFILE = %x02;
   IO$_SPACERECORD = %x09;
   IO$_WRITEOF = %x28;

   { ASCII character codes }
   TAB = %x09;
   NEWLINE = %x0A;
   ESC = %x1B;

   {Conversion of UNIX time in seconds to VMS time in 100-nanoseconds}
   TIME_DIFF = 10000000;
   { data structure sizes }
   LINELEN = 133;
   ARBUFSIZ = 26;
   BLOCK = 512;
   { Archiver Magic Number }
   ARMAG = %o177545;

type
   filename = packed array[1..14] of char;
   string63 = packed array[1..63] of char;
   string24 = packed array[1..24] of char;
   string4 = packed array[1..4]  of char;
   textline = packed array[1..LINELEN] of char;
   strdesc = packed record
                length: integer;
                addr: ^string63;
             end;
   timstr = packed record
               length: integer;
               addr: ^string24;
            end;
   systime = packed record
                time0,time4 : integer;
             end;
   clireqdesc = packed record
                   rqtype: integer;
                   fill1: integer;
                   rqdesc: strdesc;
                   fill2: array [1..3] of integer;
                end;
   ar_blk = array [1..BLOCK] of char;
   ar_head = packed record
                       name: array [1..14] of char;
                       date: integer;
                       uid,
                       gid: 0..255;
                       mode: packed set of 0..15;
                       size: integer;
             end;
   ar_ent = record
               case boolean of
                  FALSE: (chars: array [1..ARBUFSIZ] of char);
                  TRUE: (arent: ar_head);
            end;
   ar_mag = packed record
                      case Boolean of
                         FALSE: (chars: array [1..2] of char);
                         TRUE: (magic: 0..65535);
            end;

var
   xfil: text;
   afil: file of ar_blk;
   bfil: file of ar_blk;
   bflg: Boolean;
   arname: filename;
   arlen: integer;
   null: filename;
   flags: Boolean;
   line: textline;
   unix_base: string24;
   pr_time: timstr; {for storing converted ascii time strings}
   off_time: systime; {difference between seconds and 100-nanoseconds}
   base_diff, file_time: systime;
   buffer: ar_blk;
   arbuf: ar_ent;
   position: integer;
   getcmd: clireqdesc;
   wflg: boolean;
   vflg: boolean;
   tflg: boolean;
   xflg: boolean;
   i: integer;
   c: char;

value
   line := (LINELEN of ' ');
   buffer := (BLOCK of ' ');
   null := '_NLA0:        ';
   arname := 'CONT.A        ';
   arlen := 6;
   flags := TRUE;   {Initially reading flags from the command line}
   unix_base := '1-JAN-1970 00:00:00.00  ';
   off_time := (TIME_DIFF, 0);
   position := 1;
   bflg := FALSE;
   wflg := FALSE;
   vflg := FALSE;
   tflg := FALSE;
   xflg := FALSE;

   procedure prname(str: array[integer] of char);
   var
      i: integer;
   {
      Print a null terminated string.
   }
   begin {prname}
      i:=lower(str);
      while str[i] <> chr(0) do
         begin
            write(str[i]);
            i := i + 1;
         end;
   end; {prname}

   procedure sys$asctim(var timlen: integer; var timbuf: timstr;
                        var timadr: systime; cvtflg: integer); extern;

   procedure quaadd(var addend1, addend2, sum: systime); extern;

   procedure quamul(var multiplier, multiplicand, product: systime); extern;

   procedure vmstime(tapetime: integer; var cvttime: systime);
   var
      expnd: systime;
   {
      convert UNIX time to VMS time
   }
   begin {vmstime}
      expnd.time0 := tapetime;
      expnd.time4 := 0;
      quamul(off_time, expnd, cvttime);
      quaadd(cvttime, base_diff, cvttime);
   end; {vmstime}

   function unixtime(var filetime: systime): integer;
   var
      expnd: systime;
   begin {unixtime}
   end; {unixtime}

   procedure setupfile(var name: filename; len: integer; lognam: string4);
   type
      strdesc = record length: integer; addr: ^filename end;
   var
      fp: ^filename;
      fd: strdesc;

      function sys$crelog(%immed tblflg: integer; %stdescr lognam: string4;
                          var eqlnam: strdesc; %immed acmode: integer):
                                                        Boolean; extern;
   {
      Assign the passed filename to the passed 4-character logical name,
      to allow multiple file access from Pascal.
   }
   begin {setupfile}
      new(fp); fp^ := name;
      with fd do
         begin length := len; addr := fp end;
      if not sys$crelog(2,lognam,fd,0) then
         begin
         writeln(' Internal error: sys$crelog failed.');
         halt;
         end;
   end; {setupfile}

   function vmsfile(name: array[integer] of char; var fname: filename): integer;
   label
      1;
   var
      len,lastdot: integer;
      i,j: integer;
   {
      Convert the tape filename to a VMS filename.
      This proceeds by removing non-alphanumeric characters,
      and converting names with '/'s into equivalent directory
      names for VMS.
   }
   begin {vmsfile}
      i := 1;
      len := 0;
      lastdot := 0;
      while (name[i] <> chr(0)) and (i <= upper(name)) do
         case name[i] of
            '/':
               begin
                  i := i + 1;
                  len := 0;
               end;
            '.':
               begin
                  if lastdot <> 0 then
                     begin
                        for j := lastdot to len - 1 do
                           fname[j] := fname[j+1];
                        len := len - 1;
                     end;
                  lastdot := len + 1;
                  goto 1;
               end;
            ':','_','+','-':
               i := i+1;
         otherwise
1:
            begin
               len := len+1;
               fname[len] := name[i];
               i := i+1;
            end;
         end;
      if (lastdot = 0) and (len > 9) then
         len := 9;
      if lastdot > 0 then
         while len - lastdot > 3 do
            len := len - 1;
      vmsfile:=len;
   end; {vmsfile}

   procedure addbuf(c: char);
   {
      insert a character into the buffer.
      when the buffer reaches 512 characters, write it to the archive.
   }
   begin {addbuf}
      if position <= BLOCK then
         begin
            buffer[position] := c;
            position := position+1;
         end
      else
         begin
         writeln(' addbuf lost block position.');
         halt;
         end;
      if position > BLOCK then
         begin
            write (afil,buffer);
            position := 1;
         end;
   end; {addbuf}

   function getbuf: char;
   var
      i: integer;
   {
      get the next character from the buffer.
      when we run out of characters, read another block from the archive.
   }
   begin {getbuf}
      if position > BLOCK then
         begin
            read(afil, buffer);
            position := 1;
         end;
      getbuf := buffer[position];
      position := position+1;
   end; {getbuf}

   function getaf: Boolean;
   var
      magic: ar_mag;
   {
      Open an archive file and see if the Magic Number is correct.
   }
   begin {getaf}
      open(afil, OLD, FIXED);
      reset(afil);
      position := BLOCK + 1;
      magic.chars[1] := getbuf;
      magic.chars[2] := getbuf;
      if magic.magic = ARMAG then
         begin
            getaf := TRUE;
         end
      else
         begin
            close(afil);
            getaf := FALSE;
         end;
   end; {getaf}

   procedure fixlong;
   begin {fixlong}
      with arbuf do
         begin
         {swap words in size}
            c:=chars[25]; chars[25]:=chars[23]; chars[23]:=c;
            c:=chars[26]; chars[26]:=chars[24]; chars[24]:=c;
         {swap words in date}
            c:=chars[17]; chars[17]:=chars[15]; chars[15]:=c;
            c:=chars[18]; chars[18]:=chars[16]; chars[16]:=c;
         end;
   end; {fixlong}

   function getdir: Boolean;
   begin {getdir}
      if not eof(afil) then
         begin
            for i := 1 to ARBUFSIZ do
               arbuf.chars[i] := getbuf;
            fixlong;
            if (arbuf.chars[1] <> chr(0)) and (arbuf.arent.size > 0) then
               getdir := TRUE
            else
               getdir := FALSE;
         end
      else
         getdir := FALSE;
   end; {getdir}

   procedure typedir(v: boolean);
   var
      i,j,k: integer;
      param: integer;
      cvt_time: systime;
      tp: ^string24;
   {
      Print a archive directory.  v tells us whether to just print filenames
      or a whole lot of other junk about the file.
   }
   begin {typedir}
      new(tp);
      if v then {long form header}
       writeln('   mode    uid gid    size     date       time     name');
      if getaf then
         while getdir do
            with arbuf.arent do
               begin
                  if v then {long form listing}
                     begin
                        for k := 8 downto 0 do
                           if k in mode then
                              case k of
                              0,3,6:
                                 write('x');
                              1,4,7:
                                 write('w');
                              2,5,8:
                                 write('r');
                              end
                           else
                              write('-');
                        write(' ');
                        param := uid; write(param:3); write(' ');
                        param := gid; write(param:3);
                        write(' ');
                        write(size:8);
                        write(' ');
                        vmstime(date, cvt_time);
                        pr_time.addr := tp;  pr_time.length := 24;
                        sys$asctim(param, pr_time, cvt_time, 0);
                        write(pr_time.addr^:pr_time.length);
                        write(' ':24-pr_time.length+1);
                     end;
                  prname(name);
                  writeln;
                  if odd(size) then
                     size := size + 1;
                  while (size > 0) and not eof(afil) do
                     begin
                        size := size - 1;
                        c := getbuf;
                     end;
               end;
   end; {typedir}

   procedure xtractfiles(v: Boolean);
   label
      1,3;
   var
      tpos: integer;
      i,j: integer;
      param: integer;
      fname: filename;
      buf: ar_blk;
   begin {xtractfiles}
      if getaf then
         while getdir do
            with arbuf.arent do
                  begin
                     param := vmsfile(name, fname);
                     if v or wflg then
                        begin write('x '); prname(name);
                           write(' => ',fname:param);
                           if v then
                              writeln
                           else
                              begin
                                 write (' '); readln(c);
                                 {if eoln then goto 2;}
                                 case c of
                                    'x','X':
                                       goto 3;
                                    'y','Y':
                                    begin
                                       bflg := FALSE;
                                       goto 1;
                                    end;
                                    'b','B':
                                    begin
                                       bflg := TRUE;
                                       goto 1;
                                    end;
                                    otherwise
                                    begin
                                       for i := 1 to 6 do
                                          fname[i] := null[i];
                                       param := 6;
                                    end;
                                 end;
                              end;
                        end;
1:
                     if bflg then
                        begin
                           setupfile(fname, param, 'BFIL');
                           open(bfil, 512, NEW, FIXED);
                           rewrite(bfil);
                           while size > 0 do
                              begin
                                 if size > BLOCK then
                                    j := BLOCK
                                 else
                                    begin
                                       if odd(size) then
                                          size := size + 1;
                                       j := size;
                                    end;
                                 for i := 1 to j do
                                    buf[i] := getbuf;
                                 if j < BLOCK then
                                    for i := j+1 to BLOCK do
                                       buf[i] := chr(0);
                                 write(bfil,buf);
                                 size := size - j;
                              end;
                           close(bfil);
                        end
                     else
                        begin
                           setupfile(fname, param, 'XFIL');
                           open(xfil, NEW);
                           rewrite(xfil);
                           tpos := 0;
                           for i := size downto 1 do
                              begin
                                 c := getbuf;
                                 if (c = chr(NEWLINE)) then
                                    begin
                                       writeln(xfil);
                                       tpos := 0;
                                    end
                                 else
                                    begin
                                       if tpos > LINELEN then
                                          begin
                                             writeln(xfil);
                                             tpos := 1;
                                          end
                                       else
                                          tpos := tpos + 1;
                                       write(xfil,c);
                                    end;
                              end;
                           if odd(size) then
                              c := getbuf;
                           close(xfil);
                        end;
                  end;
3:
   end; {xtractfiles}

   procedure init;
   var
      fp: ^string24;
      fd: timstr;

   procedure sys$bintim(var timbuf: timstr; var timaddr: systime); extern;

   procedure sys$cli(var reqdesc: clireqdesc); extern;

   begin {init}
   {get command line}
      getcmd.rqtype := CLI$K_GETCMD;
      sys$cli(getcmd);
   {get UNIX base time}
      new(fp); fp^ := unix_base;
      fd.length := 24;
      fd.addr := fp;
      sys$bintim(fd, base_diff);
   end; {init}

begin {ar11}
   init;
{  writeln(getcmd.rqdesc.addr^:getcmd.rqdesc.length); }
   i := 1;
   with getcmd.rqdesc do
   while i <= length do
      begin
         if flags then
            case addr^[i] of
            't','T':
               tflg := TRUE;
            'x','X':
               xflg := TRUE;
            'v','V':
               vflg := TRUE;
            'w','W':
               wflg := TRUE;
            'b','B':
               bflg := TRUE;
            ' ':
               begin
                  arlen := 0;
                  flags := FALSE; {start looking for file names}
               end;
            end
         else
            begin
               arlen := arlen + 1;
               arname[arlen] := addr^[i];
            end;
         i := i+1;
      end;
   setupfile(arname,arlen,'AFIL');
   if tflg then
      begin
         if wflg or xflg or bflg then
            writeln('Bad usage.')
         else
            typedir(vflg);
      end
   else
   if xflg then
      begin
         if tflg then
            writeln('Bad usage.')
         else
            xtractfiles(vflg);
      end;
   writeln('End');
end. {ar11}
