1
0
mirror of https://github.com/livingcomputermuseum/pdp7-unix.git synced 2026-02-11 02:40:46 +00:00
Files
livingcomputermuseum.pdp7-unix/tools/mkfs7
2016-03-10 10:22:40 +10:00

372 lines
11 KiB
Perl
Executable File

#!/usr/bin/perl
#
# mkfs7: Make a PDP-7 filesystem image for SimH
#
# (c) 2016 Warren Toomey, GPL3
#
use strict;
use warnings;
use Data::Dumper;
# Constants
use constant NUMBLOCKS => 8000; # Number of blocks on a surface
use constant WORDSPERBLK => 64; # 64 words per block
use constant NUMINODEBLKS => 710; # Blocks 1 to 710 for i-nodes
use constant FIRSTINODEBLK => 1; # First i-node block number
use constant INODESIZE => 12; # Size of an i-node
use constant INODESPERBLK => WORDSPERBLK/INODESIZE;
use constant DIRENTSIZE => 8; # Size of an directory entry
use constant DIRENTSPERBLK => WORDSPERBLK/DIRENTSIZE;
use constant ROOT_UID => -1; # Native root user-id
use constant MAXINT => 0777777; # Biggest unsigned integer
use constant DD_INUM => 2; # I-number of the dd directory
# i-node field offsets
use constant I_FLAGS => 0;
use constant I_DISKPS => 1;
use constant I_UID => 8;
use constant I_NLKS => 9;
use constant I_SIZE => 10;
use constant I_UNIQ => 11;
use constant I_NUMBLKS => 7; # Seven block pointers in i-node
# i-node flag masks
use constant I_USED => 0400000;
use constant I_LARGE => 0200000;
use constant I_SPECIAL => 0000040;
use constant I_DIRECTORY => 0000020;
use constant I_OWNERREAD => 0000010;
use constant I_OWNERWRITE => 0000004;
use constant I_WORLDREAD => 0000010;
use constant I_WORLDWRITE => 0000004;
# Directory field offsets
use constant D_INUM => 0;
use constant D_NAME => 1;
use constant D_UNIQ => 5;
use constant D_NUMWORDS => 8; # Eight words in a direntry
# Globals
my $debug=0;
my @Block; # Array of blocks and words in each block
my $nextblknum= NUMINODEBLKS+1; # Next free block number
my $nextinum= 1; # i-num 0 is never used
my @Dirstack; # Stack of directories. Each value is a ref
# to a [ blocknum, offset, inum ] array which
# is the next free position in the directory
# Debug printing
sub dprint {
print(@_) if ($debug);
}
sub dprintf {
printf(@_) if ($debug);
}
# Given a size in words, allocate and return a set of block numbers
# for the entity
sub allocate_blocks {
my $numwords= shift;
my @blklist;
my $numblocks= int( ($numwords+WORDSPERBLK-1) / WORDSPERBLK );
die("Not enough blocks\n") if (($nextblknum+$numblocks) >= NUMBLOCKS);
foreach my $b (1 .. $numblocks) {
push(@blklist, $nextblknum++);
}
dprint("Allocated blocks for size $numwords: $blklist[0] .. $blklist[-1]\n");
return(@blklist);
}
# Allocate and return either the specified i-node or the next
# available one if there is no argument
sub allocate_inode {
my $inum= shift;
return($nextinum++) if (!defined($inum));
die("i-num $inum already allocated\n") if ($inum< $nextinum);
$nextinum= $inum+1;
return($inum);
}
# Given a list of block numbers, allocate a set of indirect blocks
# and install block pointers into the indirect blocks. Return the
# list of indirect block numbers.
sub build_indirect_blocks {
my @blklist= @_;
my $blkcount= @blklist;
# Divide the number of data blocks by WORDSPERBLK and round up, so
# we know how many indirect blocks to allocate.
my $indcount= int(($blkcount+WORDSPERBLK-1)/WORDSPERBLK);
# Get enough indirect blocks
my @indlist= allocate_blocks($indcount);
# Now fill in the pointers
my $indblock= $indlist[0];
my $offset= 0;
foreach my $datablock (@blklist) {
$Block[$indblock][$offset++]= $datablock;
if ($offset == WORDSPERBLK) {
$offset=0; $indblock++;
}
}
# Return the indirect block numbers
dprint("Built indirect blocks $indlist[0] .. $indlist[-1]\n");
return(@indlist);
}
# Return blocknumber and offset for a specific i-node
sub get_inode_block_offset {
my $inum= shift;
my $blocknum= FIRSTINODEBLK + int($inum/INODESPERBLK);
my $offset= INODESIZE * ($inum%INODESPERBLK);
dprint("inum $inum => block $blocknum offset $offset\n");
return($blocknum, $offset);
}
# Given an i-node number (possibly undef), permission, filetype, uid, size
# and up to seven direct or indirect block numbers, fill in the given i-node
# with the data. If the i-node number is undef, allocate an i-node number.
# Return the i-node number used.
sub fill_inode {
my ($inum, $perms, $filetype, $uid, $size, @blklist)= @_;
die("Too many blocks\n") if (@blklist>7);
# Deal with the root user-id
$uid= MAXINT if ($uid==ROOT_UID);
# Calculate the block number and word offset for this
$inum= allocate_inode() if (!defined($inum));
my ($blocknum, $offset)= get_inode_block_offset($inum);
# Fill in the easy fields
$Block[$blocknum][$offset+I_UID]= $uid;
$Block[$blocknum][$offset+I_SIZE]= $size;
$Block[$blocknum][$offset+I_NLKS]= 1;
my $i= $offset;
foreach my $datablocknum (@blklist) {
$Block[$blocknum][$i+I_DISKPS]= $datablocknum;
}
# Deal with the flags and see if it's a large file
my $flags = $perms | $filetype | I_USED;
$flags |= I_LARGE if ($size> WORDSPERBLK * I_NUMBLKS);
$Block[$blocknum][$offset+I_FLAGS]= $flags;
dprintf("fill inum %d: flags %06o uid %06o size %d\n", $inum, $flags, $uid, $size);
return($inum);
}
# Convert an ASCII string into an array of 18-bit word values
# where two characters are packed into each word. Put NUL in
# if the string has an odd number of characters. Return the array
sub ascii2words {
my $str = shift;
my @words;
for ( my $i = 0 ; $i < length($str) ; $i += 2 ) {
my $c1 = substr( $str, $i, 1 ) || "\0";
my $c2 = substr( $str, $i + 1, 1 ) || "\0";
push( @words, ( ord($c1) << 9 ) | ord($c2) );
}
return (@words);
}
# Add an extra block to an i-node. NOTE: for now, we don't change the size
# in the i-node.
sub add_block_to_inode {
my ($blknum, $inum) = @_;
my ($iblock, $offset)= get_inode_block_offset($inum);
foreach my $i (1 .. I_NUMBLKS) {
next if ($Block[$iblock][$offset+$i]!=0); # Skip in-use blocks
$Block[$iblock][$offset+$i]= $blknum;
return;
}
die("Unable to add extra block to i-node $inum\n");
dprint("Added block $blknum to i-node $inum\n");
}
# Add a name and an i-node number to the current directory in the
# directory stack.
sub add_direntry {
my ($name, $inum)= @_;
dprint("Adding $name inode $inum to current directory\n");
# Get the block and offset to the next empty slot in the directory
my $dirref= $Dirstack[-1];
if (!defined($dirref)) {
print("Empty dirstack, we must be building the root dir\n");
return;
}
my $blocknum= $dirref->[0];
my $offset= $dirref->[1];
# Convert the name into four words
my @wlist= ascii2words($name);
# Fill in the directory entry
$Block[$blocknum][$offset+D_INUM]= $inum;
$Block[$blocknum][$offset+D_NAME]= pop(@wlist);
$Block[$blocknum][$offset+D_NAME+1]= pop(@wlist);
$Block[$blocknum][$offset+D_NAME+2]= pop(@wlist);
$Block[$blocknum][$offset+D_NAME+3]= pop(@wlist);
# Move up to the next position in the directory.
$dirref->[1] += D_NUMWORDS;
# If we have filled the directory up, allocate another block to it
if ($dirref->[1] == WORDSPERBLK) {
my $nextblock= allocate_blocks(WORDSPERBLK);
$dirref->[0]= $nextblock;
$dirref->[1]= 0;
# And add this new block to the directory's i-node
add_block_to_inode($nextblock, $dirref->[2]);
}
}
# Given a name, perms, a user-id and an optional i-node number, make a
# directory. Link it to the previous directory in the directory stack.
# Allocate blocks and i-nodes for it. Add a "dd" entry as well.
sub make_dir {
my ($dirname, $perms, $uid, $inum)= @_;
# Get an i-node number or validate the one we got
$inum= allocate_inode($inum);
# Get a block for this directory
my $dirblock= allocate_blocks(WORDSPERBLK);
# Add this to the previous directory
# and fill the i-node with the details
add_direntry($dirname, $inum);
fill_inode($inum, $perms, I_DIRECTORY, $uid, 0, $dirblock);
# Make this the top directory on the dirstack
dprint("Pushing dir block $dirblock inum $inum to dirstack\n");
push(@Dirstack, [$dirblock, 0, $inum]);
# Add a "dd" entry to this directory
add_direntry("dd", DD_INUM);
dprintf("Made directory %s perms %06o uid %d in i-node %d\n\n",
$dirname, $perms, $uid, $inum);
}
# Read a word from a file in paper tape binary format.
# Return -1 on EOF
sub read_word {
my $FH = shift;
# Convert three bytes into one 18-bit word
return(-1) if ( read( $FH, my $three, 3 ) != 3 ); # Not enough bytes read
my ( $b1, $b2, $b3 ) = unpack( "CCC", $three );
return ((($b1 & 077) << 12 ) |
(($b2 & 077) << 6 ) |
($b3 & 077));
}
# Given a filename, perms, user-id and an external file, add a file to the
# filesystem. Add an entry to this file in the current directory on
# the dirstack.
sub add_file {
my ($name, $perms, $uid, $extfile) = @_;
dprintf("Adding file %s perms %06o uid %d extfile %s\n",
$name, $perms, $uid, $extfile);
# Open the external file
open(my $IN, "<", $extfile) || die("Can't open $extfile: $!\n");
# Determine if this is ASCII or binary
my $isbinary=0;
my $c = getc($IN);
seek($IN, 0, 0);
$isbinary=1 if ((ord($c) & 0300) == 0200);
# Read the whole file into a buffer, converting from ASCII or sixbit encoding
my @buf;
my $size;
while (1) {
if ( $isbinary ) {
# Convert three bytes into one 18-bit word
my $result = read_word($IN);
last if ($result == -1);
$buf[$size++] = $result;
}
else {
# Convert two ASCII characters into one 18-bit word
my $c1 = getc($IN);
last if ( !defined($c1) ); # No character, leave the loop
my $word = ord($c1) << 9;
my $c2 = getc($IN);
$word |= ord($c2) if (defined($c2));
$buf[$size++] = $word;
}
}
# Allocate enough blocks for the file
my @blklist= allocate_blocks($size);
# If it's too big, allocate indirect blocks
my $large=0;
my @indblocks;
if (@blklist > I_NUMBLKS) {
$large=1;
@indblocks= build_indirect_blocks(@blklist);
}
# Allocate and fill in the i-node
my $inum= allocate_inode();
if ($large) {
fill_inode($inum, $perms, 0, $uid, $size, @indblocks);
} else {
fill_inode($inum, $perms, 0, $uid, $size, @blklist);
}
# and add the entry in the directory
add_direntry($name, $inum);
dprint("Done adding file $name as inum $inum\n\n");
}
# Given a name, perms, uid and i-number
# add a special file to the filesystem
sub add_special {
my ($name, $perms, $uid, $inum) = @_;
# Allocate and fill in the i-node
my $inum= allocate_inode($inum);
fill_inode($inum, $perms, I_SPECIAL, $uid, 0);
# Add the entry in the directory
add_direntry($name, $inum);
dprint("Done adding special file $name inum $inum\n\n");
}
### MAIN PROGRAM
$debug=1;
make_dir("dd", 014, -1, 2);
make_dir("system", 014, -1, 3);
add_file("init", 014, -1, "proto");
pop(@Dirstack);
add_special("ttyin", 014, -1, 6);
add_special("keyboard", 014, -1, 7);
add_special("pptin", 014, -1, 8);
add_special("ttyout", 014, -1, 10);
add_special("display", 014, -1, 11);
add_special("pptout", 014, -1, 12);
add_file("as", 014, -1, "b.c");
add_file("a7out", 014, -1, "a7out");
add_file("oflow", 014, -1, "a7out");