PROGRAM unix;
 /* Copyright (c) 1979 David A. Butterfield and Daniel Weise */
 /* Modified 1982 by Alan E. Frisbie */
/* This program is (reputedly) able to read unix file structured
  disks under RT11. Once the compiler and rt11 weirdnesses are removed
  it migt become a utility to read unix disks on anything. Due to
  its vintage, it probably is suited to unix filestructures circa
  V7 Unix. */

/* N.b. The name "unix" is a trademark of AT&T (I think). Nevertheless,
  there has been large worldwide circulation of source code for various
  versions of unix V7 and earlier and later. Unix is (or at the stage of
  V7 was) not copyrighted but was treated as a trade secret. I believe
  the status of a trade secret that the whole world knows is that the
  information is public domain. Hence I believe Unix V7 is now public
  domain material. It remains a perfectly lousy OS for all that and should
  be used only by those who really LIKE to fix corrupted filestructures
  up. */

  CONST
    dirbit = 40000B;
    spbit = 20000B;
    large = 10000B;

  TYPE
    char13array = ARRAY [0..13] OF char;
    inode =
      RECORD
        flags: integer;
        nlinks: char;
        uid: char;
        gid: char;
        sizehi: char;
        sizelo: integer;
        blknos: ARRAY [0..7] OF integer;
        actime: ARRAY [0..1] OF integer;
        modtime: ARRAY [0..1] OF integer
      END;

    direntry =
      RECORD
        inodeno: integer;
        filename: ARRAY [0..13] OF char
      END;

    funny =
      RECORD
        CASE integer OF
          1:
            (inoderec: ARRAY [0..15] OF inode);
          2:
            (dirblockrec: ARRAY [0..31] OF direntry);
          3:
            (charrec: ARRAY [0..511] OF char);
          4:
            (ints: ARRAY [0..255] OF integer)
      END;

  VAR
    dev: text;

    cihave: integer; /* INODE NUMBER OF CURRENT DIRECTORY */
    icurd: inode; /* CONTAINS THE INODE OF THE CURRENT DIRECTORY */
    decnt: integer; /* NUMBER OF ENTRIES IN THE CURRENT DIRECTORY */

    datablock: funny;

    inodeblock: ARRAY [0..15] OF inode;
    inhave: integer; /* PHYSICAL BLOCK IN INODEBLOCK */

    dirblock: ARRAY [0..31] OF direntry;
    dihave: integer; /* PHYSICAL BLOCK IN DIRBLOCK */

    indblock: ARRAY [0..255] OF integer;
    ibhave: integer; /* PHYSICAL BLOCK IN INDBLOCK */

    dindblock: ARRAY [0..255] OF integer;
    dibhave: integer; /* PHYSICAL BLOCK IN DINDBLOCK */

    inbuff: ARRAY [1..80] OF char; /* THIS IS THE INPUT LINE */
    logname: ARRAY [1..8] OF char;
    logpass: ARRAY [1..8] OF char;

    loggedin: boolean;
    notdone: boolean;
    option: char;
    arg: integer;
    foo: char;

    nl: ARRAY [0..1] OF char;


  PROCEDURE seek(VAR f: text;
                 VAR x: funny;
                 blockno: integer);
    EXTERNAL;


  FUNCTION getblkno(rblock: integer;
                    sourceinode: inode): integer;

    VAR
      lindblockno, ldindblockno, physblock: integer;

    BEGIN
      IF (sourceinode.flags& large) = 0 THEN
        IF rblock < 8 THEN
          physblock := sourceinode.blknos[rblock]
        ELSE
          BEGIN
          writeln('ATTEMPT TO REFERENCE #', rblock, 'OF A SMALL FILE.');
          rblock := rblock DIV 0
          END
      ELSE
        BEGIN /* LARGE OR HUGE FILE */
        lindblockno := rblock DIV 256;
        IF lindblockno < 7 THEN
          BEGIN
          IF sourceinode.blknos[lindblockno] <> ibhave THEN
            BEGIN
            seek(dev, datablock, sourceinode.blknos[lindblockno]);
            indblock := datablock.ints;
            ibhave := sourceinode.blknos[lindblockno]
            END;
          physblock := indblock[rblock MOD 256]
          END
        ELSE
          BEGIN /* HUGE FILE WERE HERE IFF LINDBLOCKNO >= 7 */
          IF sourceinode.blknos[7] <> ibhave THEN
            BEGIN
            seek(dev, datablock, sourceinode.blknos[7]);
            indblock := datablock.ints;
            ibhave := sourceinode.blknos[7]
            END;
          lindblockno := lindblockno - 7;
          IF indblock[lindblockno] <> dibhave THEN
            BEGIN
            seek(dev, datablock, indblock[lindblockno]);
            dindblock := datablock.ints;
            dibhave := indblock[lindblockno]
            END;
          physblock := dindblock[rblock MOD 256]
          END
        END;
      getblkno := physblock
    END;


  FUNCTION geti(inodeno: integer): integer;

    VAR
      block: integer;

    BEGIN
      inodeno := inodeno - 1;
      geti := 17B& inodeno;
      block := inodeno DIV 16 + 2;
      IF block <> inhave THEN
        BEGIN
        seek(dev, datablock, block);
        inodeblock := datablock.inoderec;
        inhave := block
        END
    END;


  FUNCTION getd(dirnumber: integer): integer;

    VAR
      block: integer;

    BEGIN
      getd := 37B& dirnumber;
      block := getblkno(dirnumber DIV 32, icurd);
      IF block <> dihave THEN
        BEGIN
        seek(dev, datablock, block);
        dihave := block;
        dirblock := datablock.dirblockrec
        END
    END;


  PROCEDURE getfile(inodeno: integer);

    VAR
      hi: char;
      finptr, lo, size, block, i, j: integer;
      answer: ARRAY [1..14] OF char;
      insertcrs: boolean;

    BEGIN
      write('Should I add carriage returns? ');
      readln(answer);
      insertcrs := (answer[1] = 'y') OR (answer[1] = 'Y');
      finptr := geti(inodeno);
      hi := inodeblock[finptr].sizehi;
      lo := inodeblock[finptr].sizelo;
/* following assembly language computes
   size by subtracting 1 from bytecount (to 32 bits),
   then shifting to divide by 512 to get # blocks. */
    /*$C
        MOVB HI(6),%0
        BIC #^O177400,%0
        MOV LO(6),%1
        SUB #1,%1
        SBC %0
        ASHC #-9.,%0
        MOV %1,SIZE(6)
    */
      FOR i := 0 TO size DO
        BEGIN
        block := getblkno(i, inodeblock[finptr]);
        IF block <> 0 THEN
          BEGIN
          seek(dev, datablock, block);
          IF insertcrs THEN
            FOR j := 0 TO 511 DO
              IF datablock.charrec[j] = chr(12B) THEN
                write(chr(15B), chr(12B))
              ELSE
                write(datablock.charrec[j])
          ELSE
            write(datablock.charrec)
          END
        ELSE
          writeln(nl, 'BLOCK ', i, ' IS EMPTY')
        END
    END;


  PROCEDURE movefile(inodeno: integer);

    VAR
      output: text;
      filename: ARRAY [1..14] OF char;
      hi: char;
      finptr, lo, size, block, i, j: integer;
      insertcrs: boolean;

    BEGIN
      write('Write to file: ');
      readln(filename);
      rewrite(output, filename);
      write('Should I add carriage returns? ');
      readln(filename);
      insertcrs := (filename[1] = 'y') OR (filename[1] = 'Y');
      finptr := geti(inodeno);
      hi := inodeblock[finptr].sizehi;
      lo := inodeblock[finptr].sizelo;
/* following assembly language computes
   size by subtracting 1 from bytecount (to 32 bits),
   then shifting to divide by 512 to get # blocks. */
    /*$C
        MOVB HI(6),%0
        BIC #^O177400,%0
        MOV LO(6),%1
        SUB #1,%1
        SBC %0
        ASHC #-9.,%0
        MOV %1,SIZE(6)
    */
      FOR i := 0 TO size DO
        BEGIN
        block := getblkno(i, inodeblock[finptr]);
        seek(dev, datablock, block);
        IF insertcrs THEN
          FOR j := 0 TO 511 DO
            IF datablock.charrec[j] = chr(12B) THEN
              write(output, chr(15B), chr(12B))
            ELSE
              write(output, datablock.charrec[j])
        ELSE
          write(output, datablock.charrec)
        END;
      close(output)
    END;


  PROCEDURE writef(VAR name: char13array);

    VAR
      i: integer;

    BEGIN
      FOR i := 0 TO 13 DO
        BEGIN
        IF ord(name[i]) = 0 THEN
          write(' ')
        ELSE
          write(name[i])
        END
    END;


  PROCEDURE getcurd(ciwant: integer);

    VAR
      hi: char;
      finptr, lo: integer;

    BEGIN
      IF ciwant <> cihave THEN
        BEGIN
        finptr := geti(ciwant);
        IF (inodeblock[finptr].flags& dirbit) = 0 THEN
          writeln('Not a directory')
        ELSE
          BEGIN
          cihave := ciwant;
          icurd := inodeblock[finptr]
          END
        END;
      hi := icurd.sizehi;
      lo := icurd.sizelo;
/* Take (# bytes / 16) as decnt value */
    /*$C
        MOVB HI(6),%0
        MOV LO(6),%1
        ASHC #-4,%0
        MOV %1,DECNT(5)         ; ASSUME NO MORE THAN 32767 ENTRIES
    */
    END;


  PROCEDURE dirlist;

    VAR
      hi: char;
      lo, finode, finptr, dirptr, dircnt, size: integer;

    BEGIN
      writeln(' inode  filename     type  blocks');
      FOR dircnt := 0 TO decnt - 1 DO
        BEGIN
        dirptr := getd(dircnt);
        finode := dirblock[dirptr].inodeno;
        IF finode <> 0 THEN
          BEGIN
          write(finode: 6, '  ');
          writef(dirblock[dirptr].filename);
          write(' ');
          finptr := geti(finode);
          IF (inodeblock[finptr].flags AND dirbit) <> 0 THEN
            write('d')
          ELSE
            write(' ');
          IF (inodeblock[finptr].flags AND spbit) <> 0 THEN
            write('s')
          ELSE
            write(' ');
          hi := inodeblock[finptr].sizehi;
          lo := inodeblock[finptr].sizelo;
/* following assembly language computes
   size by subtracting 1 from bytecount (to 32 bits),
   then shifting to divide by 512 to get # blocks. */
            /*$C
            MOVB HI(6),%0
            BIC #^O177400,%0
            MOV LO(6),%1
            SUB #1,%1
            SBC %0
            ASHC #-9.,%0
            MOV %1,SIZE(6)
            */
          writeln(size + 1: 4)
          END
        END
    END;


  BEGIN
/* sets special (no-echo?) mode on terminal */
    /*$C
    BIS #^O40000,@#^O44
    */
    nl[0] := chr(15B);
    nl[1] := chr(12B);
    writeln(chr(14B), 'Program to read Unix disks.');
    write('Unix disk drive: ');
    readln(logname);
    IF ord(logname[1]) > 96 THEN
      logname[1] := chr(ord(logname[1]) - 32);
    IF ord(logname[2]) > 96 THEN
      logname[2] := chr(ord(logname[2]) - 32);
    notdone := true;
    cihave := 0;
    inhave := 0;
    dihave := 0;
    ibhave := 0;
    dibhave := 0;
/*  RESET(DEV,'DX1:');
*/
    reset(dev, logname);
    getcurd(1);
    WHILE notdone DO
      BEGIN
      write('% ');
      read(option);
      CASE option OF
        '?':		/* Type instructions */
          BEGIN
          readln(foo);
          writeln('q = quit', nl, 'd = l = directory listing', nl,
                  'c = change directory', nl, 't = type file', nl,
                  'm = move to RT11 file', nl,
                  '    "c", "t", & "m" are followed by the inode number', nl,
                  '    of the directory or file.', nl)
          END;

        'q', 'Q':		/* Quit to monitor */
          BEGIN
          readln(foo); /* Get rid of trailing CR */
          notdone := false;
          END;

        'd', 'D', 'l', 'L':	/* List Directory */
          BEGIN
          readln(foo);
          dirlist
          END;

        'c', 'C':		/* Change working directory */
          BEGIN
          readln(arg);
          getcurd(arg)
          END;

        't', 'T':		/* Type contents of Unix file */
          BEGIN
          readln(arg);
          getfile(arg)
          END;

        'm', 'M':		/* Move Unix file to RT-11 file */
          BEGIN
          readln(arg);
          movefile(arg)
          END;

        ' ':		/* Catch null lines */
          BEGIN
          writeln('Illegal command -- type ? for help')
          END;

        ELSE
          BEGIN
          readln(foo);
          writeln('Illegal command -- type ? for help')
          END
        END
      END;

    close(dev)
  END.

