mirror of
https://github.com/tjboldt/ProDOS-Utilities.git
synced 2024-12-27 05:29:49 +00:00
Fix volume bitmap not writing full blocks (#8)
This commit is contained in:
parent
62bb781fca
commit
402f39da91
11
main.go
11
main.go
@ -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()
|
||||
|
@ -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
|
||||
// }
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
64
prodos/file_test.go
Normal 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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -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
|
||||
// +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|
||||
|
Loading…
Reference in New Issue
Block a user