Fix volume bitmap not writing full blocks (#8)

This commit is contained in:
Terence Boldt 2022-03-06 05:29:33 -05:00 committed by GitHub
parent 62bb781fca
commit 402f39da91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 292 additions and 28 deletions

11
main.go
View File

@ -17,7 +17,7 @@ import (
"github.com/tjboldt/ProDOS-Utilities/prodos"
)
const version = "0.3.0"
const version = "0.3.1"
func main() {
var fileName string
@ -164,6 +164,15 @@ func main() {
}
defer file.Close()
prodos.DeleteFile(file, pathName)
case "dumpfile":
file, err := os.OpenFile(fileName, os.O_RDWR, 0755)
if err != nil {
fmt.Printf("Failed to open drive image %s:\n %s", fileName, err)
os.Exit(1)
}
defer file.Close()
fileEntry, err := prodos.GetFileEntry(file, pathName)
prodos.DumpFileEntry(fileEntry)
default:
fmt.Printf("Invalid command: %s\n\n", command)
flag.PrintDefaults()

View File

@ -160,3 +160,96 @@ func ConvertBasicToText(basic []byte) string {
}
}
}
// func ConvertTextToBasic(text string) ([]byte, error) {
// // convert line endings
// text = strings.Replace(text, "\r\n", "\n", -1)
// text = strings.Replace(text, "\r", "\n", -1)
// const startState = 0
// const lineNumberState = 1
// const tokenCheckState = 2
// const literalState = 3
// const stringState = 4
// const dataState = 5
// const endOfLineState = 6
// state := startState
// currentByte := 0x0801
// var lineNumberString string
// var tokenString string
// basicFile := new(bytes.Buffer)
// basicLine := new(bytes.Buffer)
// // parse character by character
// for _, c := range text {
// // skip initial whitespace and look for the start of a line number
// if state == startState {
// if c == ' ' {
// continue
// }
// if c >= '0' && c <= '9' {
// state = lineNumberState
// } else {
// return nil, errors.New("unexpected character before line number")
// }
// }
// // parse line number
// if state == lineNumberState {
// if c >= '0' && c <= '9' {
// lineNumberString += string(c)
// } else {
// lineNumber, err := strconv.ParseUint(lineNumberString, 10, 16)
// if err != nil {
// return nil, err
// }
// basicLine.WriteByte(byte(lineNumber % 256)) // low byte
// basicLine.WriteByte(byte(lineNumber / 256)) // high byte
// tokenString = ""
// tokenByte = 0
// state = tokenCheckState
// }
// }
// if state == tokenCheckState {
// // skip initial whitespace
// if c == ' ' && len(tokenString) == 0 {
// continue
// }
// // finish parsing token if
// if c == '\n' {
// state = endOfLineState
// } else if c == '"' {
// state = stringState
// }
// }
// }
// return basicFile.Bytes(), nil
// }
// func writeTokenOrBytes(parseString string, basicBytes []byte) bool {
// if len(parseString) == 0 {
// return false
// }
// upperToken := strings.ToUpper(parseString)
// for tokenByte, token := range tokens {
// if upperToken == token {
// return tokenByte
// }
// }
// if tokenByte > 0 {
// basicBytes.WriteByte(tokenByte)
// }
// return 0
// }

View File

@ -8,6 +8,7 @@
package prodos
import (
"fmt"
"io"
)
@ -64,11 +65,35 @@ func writeVolumeBitmap(readerWriter ReaderWriterAt, bitmap []byte) error {
if err != nil {
return err
}
volumeHeader := parseVolumeHeader(headerBlock)
for i := 0; i < len(bitmap)/512; i++ {
WriteBlock(readerWriter, volumeHeader.BitmapStartBlock+i, bitmap[i*512:i*512+512])
volumeHeader := parseVolumeHeader(headerBlock)
totalBitmapBytes := volumeHeader.TotalBlocks / 8
if volumeHeader.TotalBlocks%8 > 0 {
totalBitmapBytes++
}
totalBitmapBlocks := totalBitmapBytes / 512
if totalBitmapBytes%512 > 0 {
totalBitmapBlocks++
}
for i := 0; i < totalBitmapBlocks; i++ {
bitmapBlock, err := ReadBlock(readerWriter, i+volumeHeader.BitmapStartBlock)
if err != nil {
return err
}
for j := 0; j < 512 && i*512+j < totalBitmapBytes; j++ {
bitmapBlock[j] = bitmap[i*512+j]
}
err = WriteBlock(readerWriter, volumeHeader.BitmapStartBlock+i, bitmapBlock)
if err != nil {
return err
}
}
return nil
}
@ -122,6 +147,10 @@ func findFreeBlocks(volumeBitmap []byte, numberOfBlocks int) []int {
blocks[blocksFound] = i
blocksFound++
if blocksFound == numberOfBlocks {
for i := 0; i < len(blocks); i++ {
fmt.Printf("%04X ", blocks[i])
}
fmt.Printf("\n")
return blocks
}
}

View File

@ -53,3 +53,32 @@ func TestCheckFreeBlockInVolumeBitmap(t *testing.T) {
})
}
}
func TestMarkBlockInVolumeBitmap(t *testing.T) {
var tests = []struct {
blocks int
want bool
}{
{0, false}, // boot block
{1, false}, // SOS boot block
{2, false}, // volume root
{21, false}, // end of volume bitmap
{22, true}, // beginning of free space
{999, false}, // end of volume bitmap
{8192, true}, // more free space
{65534, true}, // last free block
{65535, false}, // can't use last block because volume size is 0xFFFF, not 0x10000
}
for _, tt := range tests {
testname := fmt.Sprintf("%d", tt.blocks)
t.Run(testname, func(t *testing.T) {
volumeBitMap := createVolumeBitmap(65535)
markBlockInVolumeBitmap(volumeBitMap, 999)
ans := checkFreeBlockInVolumeBitmap(volumeBitMap, tt.blocks)
if ans != tt.want {
t.Errorf("got %t, want %t", ans, tt.want)
}
})
}
}

View File

@ -117,7 +117,7 @@ func getFreeFileEntryInDirectory(reader io.ReaderAt, directory string) (FileEntr
// if we ran out of blocks in the directory, return empty
// TODO: expand the directory to add more entries
if blockNumber == 0 {
return FileEntry{}, errors.New("No free file entries found")
return FileEntry{}, errors.New("no free file entries found")
}
// else read the next block in the directory
buffer, err = ReadBlock(reader, blockNumber)

View File

@ -9,6 +9,7 @@ package prodos
import (
"errors"
"fmt"
"io"
"strings"
"time"
@ -16,7 +17,7 @@ import (
// LoadFile loads in a file from a ProDOS volume into a byte array
func LoadFile(reader io.ReaderAt, path string) ([]byte, error) {
fileEntry, err := getFileEntry(reader, path)
fileEntry, err := GetFileEntry(reader, path)
if err != nil {
return nil, err
}
@ -45,7 +46,7 @@ func LoadFile(reader io.ReaderAt, path string) ([]byte, error) {
func WriteFile(readerWriter ReaderWriterAt, path string, fileType int, auxType int, buffer []byte) error {
directory, fileName := GetDirectoryAndFileNameFromPath(path)
existingFileEntry, _ := getFileEntry(readerWriter, path)
existingFileEntry, _ := GetFileEntry(readerWriter, path)
if existingFileEntry.StorageType != StorageDeleted {
DeleteFile(readerWriter, path)
}
@ -56,6 +57,11 @@ func WriteFile(readerWriter ReaderWriterAt, path string, fileType int, auxType i
return err
}
for i := 0; i < len(blockList); i++ {
fmt.Printf("%04X ", blockList[i])
}
fmt.Printf("\n")
// seedling file
if len(buffer) <= 0x200 {
WriteBlock(readerWriter, blockList[0], buffer)
@ -66,9 +72,13 @@ func WriteFile(readerWriter ReaderWriterAt, path string, fileType int, auxType i
writeSaplingFile(readerWriter, buffer, blockList)
}
// TODO: add tree file
if len(buffer) > 0x20000 {
return errors.New("files > 128KB not supported yet")
// tree file needs master index and index blocks
if len(buffer) > 0x20000 && len(buffer) <= 0x1000000 {
writeTreeFile(readerWriter, buffer, blockList)
}
if len(buffer) > 0x1000000 {
return errors.New("files > 16MB not supported by ProDOS")
}
updateVolumeBitmap(readerWriter, blockList)
@ -111,15 +121,15 @@ func WriteFile(readerWriter ReaderWriterAt, path string, fileType int, auxType i
// DeleteFile deletes a file from a ProDOS volume
func DeleteFile(readerWriter ReaderWriterAt, path string) error {
fileEntry, err := getFileEntry(readerWriter, path)
fileEntry, err := GetFileEntry(readerWriter, path)
if err != nil {
return errors.New("File not found")
return errors.New("file not found")
}
if fileEntry.StorageType == StorageDeleted {
return errors.New("File already deleted")
return errors.New("file already deleted")
}
if fileEntry.StorageType == StorageDirectory {
return errors.New("Directory deletion not supported")
return errors.New("directory deletion not supported")
}
// free the blocks
@ -173,15 +183,20 @@ func GetDirectoryAndFileNameFromPath(path string) (string, string) {
}
func updateVolumeBitmap(readerWriter ReaderWriterAt, blockList []int) error {
for i := 0; i < len(blockList); i++ {
fmt.Printf("%04X ", blockList[i])
}
fmt.Printf("\n")
volumeBitmap, err := ReadVolumeBitmap(readerWriter)
if err != nil {
fmt.Printf("%s", err)
return err
}
for i := 0; i < len(blockList); i++ {
markBlockInVolumeBitmap(volumeBitmap, blockList[i])
}
writeVolumeBitmap(readerWriter, volumeBitmap)
return nil
return writeVolumeBitmap(readerWriter, volumeBitmap)
}
func writeSaplingFile(writer io.WriterAt, buffer []byte, blockList []int) {
@ -216,6 +231,10 @@ func writeSaplingFile(writer io.WriterAt, buffer []byte, blockList []int) {
}
}
func writeTreeFile(writer io.WriterAt, buffer []byte, blockList []int) {
}
// Returns all blocks, including index blocks
func getBlocklist(reader io.ReaderAt, fileEntry FileEntry) ([]int, error) {
blocks := make([]int, fileEntry.BlocksUsed)
@ -273,39 +292,60 @@ func getDataBlocklist(reader io.ReaderAt, fileEntry FileEntry) ([]int, error) {
blocks[i] = int(index[i]) + int(index[i+256])*256
}
return blocks, nil
// case StorageTree:
// blocks := make([]int, fileEntry.BlocksUsed-fileEntry.BlocksUsed/256-1)
// masterIndex := ReadBlock(reader, fileEntry.KeyPointer)
// for i := 0; i < 128; i++ {
// blockNumber := 0
// blocks[j] = int(index[i]) + int(index[i+256])*256
// }
}
return nil, errors.New("Unsupported file storage type")
return nil, errors.New("unsupported file storage type")
}
func createBlockList(reader io.ReaderAt, fileSize int) ([]int, error) {
numberOfBlocks := fileSize / 512
//fmt.Printf("Number of blocks %d\n", numberOfBlocks)
if fileSize%512 > 0 {
//fmt.Printf("Adding block for partial usage\n")
numberOfBlocks++
}
if fileSize > 0x200 && fileSize <= 0x20000 {
//fmt.Printf("Adding index block for sapling file\n")
numberOfBlocks++ // add index block
}
if fileSize > 0x20000 {
// add master index block
numberOfBlocks++
// add index blocks for each 128 blocks
numberOfBlocks += numberOfBlocks / 128
if fileSize > 0x20000 && fileSize < 0x1000000 {
//fmt.Printf("Tree file\n")
// add index blocks for each 256 blocks
numberOfBlocks += numberOfBlocks / 256
// add index block for any remaining blocks
if numberOfBlocks%128 > 0 {
if numberOfBlocks%256 > 0 {
numberOfBlocks++
}
// add master index block
numberOfBlocks++
}
if fileSize > 0x1000000 {
return nil, errors.New("file size too large")
}
volumeBitmap, err := ReadVolumeBitmap(reader)
if err != nil {
return nil, err
}
//fmt.Printf("findFreeBlocks %d\n", numberOfBlocks)
blockList := findFreeBlocks(volumeBitmap, numberOfBlocks)
return blockList, nil
}
func getFileEntry(reader io.ReaderAt, path string) (FileEntry, error) {
func GetFileEntry(reader io.ReaderAt, path string) (FileEntry, error) {
directory, fileName := GetDirectoryAndFileNameFromPath(path)
_, _, fileEntries, err := ReadDirectory(reader, directory)
if err != nil {
@ -313,7 +353,7 @@ func getFileEntry(reader io.ReaderAt, path string) (FileEntry, error) {
}
if fileEntries == nil || len(fileEntries) == 0 {
return FileEntry{}, errors.New("File entry not found")
return FileEntry{}, errors.New("file entry not found")
}
var fileEntry FileEntry
@ -325,7 +365,7 @@ func getFileEntry(reader io.ReaderAt, path string) (FileEntry, error) {
}
if fileEntry.StorageType == StorageDeleted {
return FileEntry{}, errors.New("File not found")
return FileEntry{}, errors.New("file not found")
}
return fileEntry, nil

64
prodos/file_test.go Normal file
View File

@ -0,0 +1,64 @@
// Copyright Terence J. Boldt (c)2021-2022
// Use of this source code is governed by an MIT
// license that can be found in the LICENSE file.
package prodos
import (
"fmt"
"testing"
)
func TestCreatBlocklist(t *testing.T) {
var tests = []struct {
fileSize int
wantBlocks int
}{
{1, 1},
{512, 1},
{513, 3},
{2048, 5},
{2049, 6},
{17128, 35},
}
virtualDisk := NewMemoryFile(0x2000000)
CreateVolume(virtualDisk, "VIRTUAL.DISK", 0xFFFE)
for _, tt := range tests {
testname := fmt.Sprintf("%d", tt.fileSize)
t.Run(testname, func(t *testing.T) {
blockList, err := createBlockList(virtualDisk, tt.fileSize)
if err != nil {
t.Error("got error, want nil")
}
if len(blockList) != tt.wantBlocks {
t.Errorf("got %d blocks, want %d", len(blockList), tt.wantBlocks)
}
})
}
}
func TestUpdateVolumeBitmap(t *testing.T) {
blockList := []int{10, 11, 12, 100, 120}
virtualDisk := NewMemoryFile(0x2000000)
CreateVolume(virtualDisk, "VIRTUAL.DISK", 0xFFFE)
updateVolumeBitmap(virtualDisk, blockList)
for _, tt := range blockList {
testname := fmt.Sprintf("%d", tt)
t.Run(testname, func(t *testing.T) {
volumeBitmap, err := ReadVolumeBitmap(virtualDisk)
if err != nil {
t.Error("got error, want nil")
}
free := checkFreeBlockInVolumeBitmap(volumeBitmap, tt)
if free {
t.Errorf("got true, want false")
}
})
}
}

View File

@ -11,14 +11,14 @@ import (
)
// DateTimeToProDOS converts Time to ProDOS date time
// 49041 ($BF91) 49040 ($BF90)
// 49041 ($BF91) 49040 ($BF90)
//
// 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
// +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
// DATE: | year | month | day |
// +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
//
// 49043 ($BF93) 49042 ($BF92)
// 49043 ($BF93) 49042 ($BF92)
//
// 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
// +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+