From 7e623c530fd079f90f117fc18b06adccc54a200c Mon Sep 17 00:00:00 2001 From: Terence Boldt Date: Wed, 9 Jun 2021 08:23:18 -0400 Subject: [PATCH] Add volume creation --- main.go | 46 ++++++++++++--- prodos/bitmap.go | 77 ++++++++++++++++++++++++- prodos/bitmap_test.go | 28 +++++++++ prodos/block.go | 22 +++++++ prodos/format.go | 130 ++++++++++++++++++++++++++++++++++++++++++ prodos/readblock.go | 13 ----- prodos/time.go | 3 - prodos/writeBlock.go | 15 ----- 8 files changed, 293 insertions(+), 41 deletions(-) create mode 100644 prodos/bitmap_test.go create mode 100644 prodos/block.go create mode 100644 prodos/format.go delete mode 100644 prodos/readblock.go delete mode 100644 prodos/writeBlock.go diff --git a/main.go b/main.go index c8aa727..1eb32d0 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,6 @@ package main import ( "flag" "fmt" - "io/fs" "os" "github.com/tjboldt/ProDOS-Utilities/prodos" @@ -14,36 +13,69 @@ func main() { var pathName string var command string var outFileName string + var blockNumber int + var volumeSize int + var volumeName string flag.StringVar(&fileName, "driveimage", "", "A ProDOS format drive image") flag.StringVar(&pathName, "path", "", "Path name in ProDOS drive image") - flag.StringVar(&command, "command", "ls", "Command to execute: ls, get, put, volumebitmap") + flag.StringVar(&command, "command", "ls", "Command to execute: ls, get, put, volumebitmap, readblock, writeblock, create") flag.StringVar(&outFileName, "outfile", "export.bin", "Name of file to write") + flag.IntVar(&volumeSize, "volumesize", 65535, "Number of blocks to create the volume with") + flag.StringVar(&volumeName, "volumename", "NO.NAME", "Specifiy a name for the volume from 1 to 15 characters") + flag.IntVar(&blockNumber, "block", 0, "A block number to read/write from 0 to 65535") flag.Parse() if len(fileName) == 0 { fmt.Printf("Missing driveimage. Run with --help for more info.\n") os.Exit(1) } - file, err := os.OpenFile(fileName, os.O_RDWR, 0755) - if err != nil { - os.Exit(1) - } switch command { case "ls": + file, err := os.OpenFile(fileName, os.O_RDWR, 0755) + if err != nil { + os.Exit(1) + } volumeHeader, fileEntries := prodos.ReadDirectory(file, pathName) prodos.DumpDirectory(volumeHeader, fileEntries) case "volumebitmap": + file, err := os.OpenFile(fileName, os.O_RDWR, 0755) + if err != nil { + os.Exit(1) + } volumeBitmap := prodos.ReadVolumeBitmap(file) prodos.DumpBlock(volumeBitmap) case "get": + file, err := os.OpenFile(fileName, os.O_RDWR, 0755) + if err != nil { + os.Exit(1) + } if len(pathName) == 0 { fmt.Println("Missing pathname") os.Exit(1) } getFile := prodos.LoadFile(file, pathName) - os.WriteFile(outFileName, getFile, fs.FileMode(os.O_RDWR)) + outFile, err := os.Create(outFileName) + if err != nil { + os.Exit(1) + } + outFile.Write(getFile) + case "readblock": + file, err := os.OpenFile(fileName, os.O_RDWR, 0755) + if err != nil { + os.Exit(1) + } + block := prodos.ReadBlock(file, blockNumber) + outFile, err := os.Create(outFileName) + if err != nil { + os.Exit(1) + } + outFile.Write(block) + case "create": + //fmt.Println("Create volume") + prodos.CreateVolume(fileName, volumeName, volumeSize) default: + fmt.Printf("Command %s not handle\n", command) os.Exit(1) } } diff --git a/prodos/bitmap.go b/prodos/bitmap.go index 1734128..c8c21fb 100644 --- a/prodos/bitmap.go +++ b/prodos/bitmap.go @@ -1,6 +1,8 @@ package prodos -import "os" +import ( + "os" +) func ReadVolumeBitmap(file *os.File) []byte { headerBlock := ReadBlock(file, 2) @@ -27,11 +29,80 @@ func WriteVolumeBitmap(file *os.File, bitmap []byte) { volumeHeader := parseVolumeHeader(headerBlock) - for i := 0; i < len(bitmap)/512/8; i++ { - WriteBlock(file, volumeHeader.BitmapStartBlock+1, bitmap[i*512:i*512+513]) + for i := 0; i < len(bitmap)/512; i++ { + WriteBlock(file, volumeHeader.BitmapStartBlock+i, bitmap[i*512:i*512+512]) } } +func CreateVolumeBitmap(numberOfBlocks int) []byte { + volumeBitmapBlocks := numberOfBlocks / 512 / 8 + if volumeBitmapBlocks*8*512 < numberOfBlocks { + volumeBitmapBlocks++ + } + + // set all 1's to show blocks available... + volumeBitmap := make([]byte, volumeBitmapBlocks*512) + for i := 0; i < len(volumeBitmap); i++ { + volumeBitmap[i] = 0xFF + } + + // boot blocks + MarkBlockInVolumeBitmap(volumeBitmap, 0) + MarkBlockInVolumeBitmap(volumeBitmap, 1) + + // root directory + MarkBlockInVolumeBitmap(volumeBitmap, 2) + MarkBlockInVolumeBitmap(volumeBitmap, 3) + MarkBlockInVolumeBitmap(volumeBitmap, 4) + MarkBlockInVolumeBitmap(volumeBitmap, 5) + + // volume bitmap blocks + for i := 0; i < volumeBitmapBlocks; i++ { + MarkBlockInVolumeBitmap(volumeBitmap, 6+i) + } + + // blocks beyond the volume + totalBlocksInBitmap := volumeBitmapBlocks * 512 * 8 + blocksBeyondEnd := totalBlocksInBitmap - numberOfBlocks + if blocksBeyondEnd > 0 { + for i := totalBlocksInBitmap - blocksBeyondEnd; i < totalBlocksInBitmap; i++ { + MarkBlockInVolumeBitmap(volumeBitmap, i) + } + } + //DumpBlock(volumeBitmap) + + return volumeBitmap +} + func FindFreeBlocks(numberOfBlocks int) []int { return nil } + +func MarkBlockInVolumeBitmap(volumeBitmap []byte, blockNumber int) { + bitToChange := blockNumber % 8 + byteToChange := blockNumber / 8 + + byteToWrite := 0b11111111 + + switch bitToChange { + case 0: + byteToWrite = 0b01111111 + case 1: + byteToWrite = 0b10111111 + case 2: + byteToWrite = 0b11011111 + case 3: + byteToWrite = 0b11101111 + case 4: + byteToWrite = 0b11110111 + case 5: + byteToWrite = 0b11111011 + case 6: + byteToWrite = 0b11111101 + case 7: + byteToWrite = 0b11111110 + } + + //fmt.Printf("blockNumber: $%04X byteToWrite: 0b%08b volumeBitmap: $%02X byteToChange: $%04X\n", blockNumber, byteToWrite, volumeBitmap[byteToChange], byteToChange) + volumeBitmap[byteToChange] &= byte(byteToWrite) +} diff --git a/prodos/bitmap_test.go b/prodos/bitmap_test.go new file mode 100644 index 0000000..32f6d94 --- /dev/null +++ b/prodos/bitmap_test.go @@ -0,0 +1,28 @@ +package prodos + +import ( + "fmt" + "testing" +) + +func TestCreateVolumeBitmap(t *testing.T) { + var tests = []struct { + blocks int + want int + }{ + {65536, 8192}, + {65533, 8192}, + {140, 512}, + } + + for _, tt := range tests { + testname := fmt.Sprintf("%d", tt.blocks) + t.Run(testname, func(t *testing.T) { + volumeBitMap := CreateVolumeBitmap(tt.blocks) + ans := len(volumeBitMap) + if ans != tt.want { + t.Errorf("got %d, want %d", ans, tt.want) + } + }) + } +} diff --git a/prodos/block.go b/prodos/block.go new file mode 100644 index 0000000..3405a20 --- /dev/null +++ b/prodos/block.go @@ -0,0 +1,22 @@ +package prodos + +import ( + "os" +) + +func ReadBlock(file *os.File, block int) []byte { + buffer := make([]byte, 512) + + file.ReadAt(buffer, int64(block)*512) + + return buffer +} + +func WriteBlock(file *os.File, block int, buffer []byte) { + WriteBlockNoSync(file, block, buffer) + file.Sync() +} + +func WriteBlockNoSync(file *os.File, block int, buffer []byte) { + file.WriteAt(buffer, int64(block)*512) +} diff --git a/prodos/format.go b/prodos/format.go new file mode 100644 index 0000000..c65c1c6 --- /dev/null +++ b/prodos/format.go @@ -0,0 +1,130 @@ +package prodos + +import ( + "fmt" + "os" + "strings" + "time" +) + +func CreateVolume(fileName string, volumeName string, numberOfBlocks int) { + if numberOfBlocks > 65535 || numberOfBlocks < 64 { + return + } + volumeName = strings.ToUpper(volumeName) + volumeNameLen := len(volumeName) + if volumeNameLen == 0 || volumeNameLen > 15 { + fmt.Println("bad name length") + return + } + file, err := os.Create(fileName) + if err != nil { + fmt.Printf("failed to create file: %s\n", err) + return + } + + blankBlock := make([]byte, 512) + for i := 0; i < numberOfBlocks; i++ { + WriteBlockNoSync(file, i, blankBlock) + } + + volumeHeader := [43]byte{} + + // volume name + volumeHeader[0x04] = 0xF0 + byte(volumeNameLen) + + for i := 0; i < volumeNameLen; i++ { + volumeHeader[i+0x05] = volumeName[i] + } + + //creation date + creationDate := DateTimeToProDOS(time.Now()) + + for i := 0; i < len(creationDate); i++ { + volumeHeader[0x1C+i] = creationDate[i] + } + + // access + volumeHeader[0x22] = 0xE3 + + // entry length + volumeHeader[0x23] = 0x27 + + // entries per block + volumeHeader[0x24] = 0x0D + + // volume bitmap pointer + volumeHeader[0x27] = 0x06 + + // number of blocks + volumeHeader[0x29] = byte(numberOfBlocks & 0xFF) + volumeHeader[0x2A] = byte(numberOfBlocks >> 8) + + file.WriteAt(volumeHeader[:], 1024) + file.Sync() + + // boot block 0 + WriteBlock(file, 0, GetBootBlock()) + + // pointers to volume directory blocks + for i := 2; i < 6; i++ { + pointers := make([]byte, 4) + if i == 2 { + pointers[0] = 0x00 // 0x00 indicates start block + } else { + pointers[0] = byte(i - 1) + } + pointers[1] = 0x00 + if i == 5 { + pointers[2] = 0x00 // 0x00 indicated end block + } else { + pointers[2] = byte(i + 1) + } + pointers[3] = 0x00 + file.WriteAt(pointers, int64(i*512)) + } + + // volume bit map starting at block 6 + volumeBitmap := CreateVolumeBitmap(numberOfBlocks) + WriteVolumeBitmap(file, volumeBitmap) + + file.Close() +} + +func GetBootBlock() []byte { + bootBlock := []byte{ + 0x01, 0x38, 0xb0, 0x03, 0x4c, 0x1c, 0x09, 0x78, 0x86, 0x43, 0xc9, 0x03, 0x08, 0x8a, 0x29, 0x70, + 0x4a, 0x4a, 0x4a, 0x4a, 0x09, 0xc0, 0x85, 0x49, 0xa0, 0xff, 0x84, 0x48, 0x28, 0xc8, 0xb1, 0x48, + 0xd0, 0x3a, 0xb0, 0x0e, 0xa9, 0x03, 0x8d, 0x00, 0x08, 0xe6, 0x3d, 0xa5, 0x49, 0x48, 0xa9, 0x5b, + 0x48, 0x60, 0x85, 0x40, 0x85, 0x48, 0xa0, 0x5e, 0xb1, 0x48, 0x99, 0x94, 0x09, 0xc8, 0xc0, 0xeb, + 0xd0, 0xf6, 0xa2, 0x06, 0xbc, 0x32, 0x09, 0xbd, 0x39, 0x09, 0x99, 0xf2, 0x09, 0xbd, 0x40, 0x09, + 0x9d, 0x7f, 0x0a, 0xca, 0x10, 0xee, 0xa9, 0x09, 0x85, 0x49, 0xa9, 0x86, 0xa0, 0x00, 0xc9, 0xf9, + 0xb0, 0x2f, 0x85, 0x48, 0x84, 0x60, 0x84, 0x4a, 0x84, 0x4c, 0x84, 0x4e, 0x84, 0x47, 0xc8, 0x84, + 0x42, 0xc8, 0x84, 0x46, 0xa9, 0x0c, 0x85, 0x61, 0x85, 0x4b, 0x20, 0x27, 0x09, 0xb0, 0x66, 0xe6, + 0x61, 0xe6, 0x61, 0xe6, 0x46, 0xa5, 0x46, 0xc9, 0x06, 0x90, 0xef, 0xad, 0x00, 0x0c, 0x0d, 0x01, + 0x0c, 0xd0, 0x52, 0xa9, 0x04, 0xd0, 0x02, 0xa5, 0x4a, 0x18, 0x6d, 0x23, 0x0c, 0xa8, 0x90, 0x0d, + 0xe6, 0x4b, 0xa5, 0x4b, 0x4a, 0xb0, 0x06, 0xc9, 0x0a, 0xf0, 0x71, 0xa0, 0x04, 0x84, 0x4a, 0xad, + 0x20, 0x09, 0x29, 0x0f, 0xa8, 0xb1, 0x4a, 0xd9, 0x20, 0x09, 0xd0, 0xdb, 0x88, 0x10, 0xf6, 0xa0, + 0x16, 0xb1, 0x4a, 0x4a, 0x6d, 0x1f, 0x09, 0x8d, 0x1f, 0x09, 0xa0, 0x11, 0xb1, 0x4a, 0x85, 0x46, + 0xc8, 0xb1, 0x4a, 0x85, 0x47, 0xa9, 0x00, 0x85, 0x4a, 0xa0, 0x1e, 0x84, 0x4b, 0x84, 0x61, 0xc8, + 0x84, 0x4d, 0x20, 0x27, 0x09, 0xb0, 0x35, 0xe6, 0x61, 0xe6, 0x61, 0xa4, 0x4e, 0xe6, 0x4e, 0xb1, + 0x4a, 0x85, 0x46, 0xb1, 0x4c, 0x85, 0x47, 0x11, 0x4a, 0xd0, 0x18, 0xa2, 0x01, 0xa9, 0x00, 0xa8, + 0x91, 0x60, 0xc8, 0xd0, 0xfb, 0xe6, 0x61, 0xea, 0xea, 0xca, 0x10, 0xf4, 0xce, 0x1f, 0x09, 0xf0, + 0x07, 0xd0, 0xd8, 0xce, 0x1f, 0x09, 0xd0, 0xca, 0x58, 0x4c, 0x00, 0x20, 0x4c, 0x47, 0x09, 0x02, + 0x26, 0x50, 0x52, 0x4f, 0x44, 0x4f, 0x53, 0xa5, 0x60, 0x85, 0x44, 0xa5, 0x61, 0x85, 0x45, 0x6c, + 0x48, 0x00, 0x08, 0x1e, 0x24, 0x3f, 0x45, 0x47, 0x76, 0xf4, 0xd7, 0xd1, 0xb6, 0x4b, 0xb4, 0xac, + 0xa6, 0x2b, 0x18, 0x60, 0x4c, 0xbc, 0x09, 0x20, 0x58, 0xfc, 0xa0, 0x14, 0xb9, 0x58, 0x09, 0x99, + 0xb1, 0x05, 0x88, 0x10, 0xf7, 0x4c, 0x55, 0x09, 0xd5, 0xce, 0xc1, 0xc2, 0xcc, 0xc5, 0xa0, 0xd4, + 0xcf, 0xa0, 0xcc, 0xcf, 0xc1, 0xc4, 0xa0, 0xd0, 0xd2, 0xcf, 0xc4, 0xcf, 0xd3, 0xa5, 0x53, 0x29, + 0x03, 0x2a, 0x05, 0x2b, 0xaa, 0xbd, 0x80, 0xc0, 0xa9, 0x2c, 0xa2, 0x11, 0xca, 0xd0, 0xfd, 0xe9, + 0x01, 0xd0, 0xf7, 0xa6, 0x2b, 0x60, 0xa5, 0x46, 0x29, 0x07, 0xc9, 0x04, 0x29, 0x03, 0x08, 0x0a, + 0x28, 0x2a, 0x85, 0x3d, 0xa5, 0x47, 0x4a, 0xa5, 0x46, 0x6a, 0x4a, 0x4a, 0x85, 0x41, 0x0a, 0x85, + 0x51, 0xa5, 0x45, 0x85, 0x27, 0xa6, 0x2b, 0xbd, 0x89, 0xc0, 0x20, 0xbc, 0x09, 0xe6, 0x27, 0xe6, + 0x3d, 0xe6, 0x3d, 0xb0, 0x03, 0x20, 0xbc, 0x09, 0xbc, 0x88, 0xc0, 0x60, 0xa5, 0x40, 0x0a, 0x85, + 0x53, 0xa9, 0x00, 0x85, 0x54, 0xa5, 0x53, 0x85, 0x50, 0x38, 0xe5, 0x51, 0xf0, 0x14, 0xb0, 0x04, + 0xe6, 0x53, 0x90, 0x02, 0xc6, 0x53, 0x38, 0x20, 0x6d, 0x09, 0xa5, 0x50, 0x18, 0x20, 0x6f, 0x09, + 0xd0, 0xe3, 0xa0, 0x7f, 0x84, 0x52, 0x08, 0x28, 0x38, 0xc6, 0x52, 0xf0, 0xce, 0x18, 0x08, 0x88, + 0xf0, 0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + } + return bootBlock +} diff --git a/prodos/readblock.go b/prodos/readblock.go deleted file mode 100644 index 034dda7..0000000 --- a/prodos/readblock.go +++ /dev/null @@ -1,13 +0,0 @@ -package prodos - -import ( - "os" -) - -func ReadBlock(file *os.File, block int) []byte { - buffer := make([]byte, 512) - - file.ReadAt(buffer, int64(block)*512) - - return buffer -} diff --git a/prodos/time.go b/prodos/time.go index 199fce8..f731969 100644 --- a/prodos/time.go +++ b/prodos/time.go @@ -1,7 +1,6 @@ package prodos import ( - "fmt" "time" ) @@ -21,8 +20,6 @@ TIME: | hour | | minute | */ func DateTimeToProDOS(dateTime time.Time) []byte { - fmt.Printf("Sending date/time...\n") - year := dateTime.Year() % 100 month := dateTime.Month() day := dateTime.Day() diff --git a/prodos/writeBlock.go b/prodos/writeBlock.go deleted file mode 100644 index cb187ec..0000000 --- a/prodos/writeBlock.go +++ /dev/null @@ -1,15 +0,0 @@ -package prodos - -import ( - "fmt" - "os" -) - -func WriteBlock(file *os.File, block int, buffer []byte) { - - fmt.Printf("Write block %d\n", block) - - file.WriteAt(buffer, int64(block)*512) - file.Sync() - fmt.Printf("Write block completed\n") -}