Add error handling (#5)

* Add error handling

* Update to 0.3.0
This commit is contained in:
Terence Boldt 2022-03-04 18:08:33 -05:00 committed by GitHub
parent 30a221c177
commit 62bb781fca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 165 additions and 78 deletions

25
main.go
View File

@ -17,7 +17,7 @@ import (
"github.com/tjboldt/ProDOS-Utilities/prodos"
)
const version = "0.2.0"
const version = "0.3.0"
func main() {
var fileName string
@ -57,11 +57,18 @@ func main() {
}
defer file.Close()
pathName = strings.ToUpper(pathName)
volumeHeader, _, fileEntries := prodos.ReadDirectory(file, pathName)
volumeHeader, _, fileEntries, err := prodos.ReadDirectory(file, pathName)
if err != nil {
fmt.Printf("Error: %s", err)
}
if len(pathName) == 0 {
pathName = "/" + volumeHeader.VolumeName
}
volumeBitmap := prodos.ReadVolumeBitmap(file)
volumeBitmap, err := prodos.ReadVolumeBitmap(file)
if err != nil {
fmt.Printf("Failed to open drive image %s:\n %s", fileName, err)
os.Exit(1)
}
freeBlocks := prodos.GetFreeBlockCount(volumeBitmap, volumeHeader.TotalBlocks)
prodos.DumpDirectory(freeBlocks, volumeHeader.TotalBlocks, pathName, fileEntries)
case "get":
@ -109,7 +116,7 @@ func main() {
fmt.Printf("Failed to open input file %s: %s", inFileName, err)
os.Exit(1)
}
err = prodos.WriteFile(file, file, pathName, fileType, auxType, inFile)
err = prodos.WriteFile(file, pathName, fileType, auxType, inFile)
if err != nil {
fmt.Printf("Failed to write file %s: %s", pathName, err)
}
@ -121,7 +128,11 @@ func main() {
os.Exit(1)
}
defer file.Close()
block := prodos.ReadBlock(file, blockNumber)
block, err := prodos.ReadBlock(file, blockNumber)
if err != nil {
fmt.Printf("Failed to open drive image %s:\n %s", fileName, err)
os.Exit(1)
}
prodos.DumpBlock(block)
case "writeblock":
fmt.Printf("Writing block 0x%04X (%d):\n\n", blockNumber, blockNumber)
@ -144,7 +155,7 @@ func main() {
return
}
defer file.Close()
prodos.CreateVolume(file, file, volumeName, volumeSize)
prodos.CreateVolume(file, volumeName, volumeSize)
case "rm":
file, err := os.OpenFile(fileName, os.O_RDWR, 0755)
if err != nil {
@ -152,7 +163,7 @@ func main() {
os.Exit(1)
}
defer file.Close()
prodos.DeleteFile(file, file, pathName)
prodos.DeleteFile(file, pathName)
default:
fmt.Printf("Invalid command: %s\n\n", command)
flag.PrintDefaults()

View File

@ -12,8 +12,11 @@ import (
)
// ReadVolumeBitmap reads the volume bitmap from a ProDOS image
func ReadVolumeBitmap(reader io.ReaderAt) []byte {
headerBlock := ReadBlock(reader, 2)
func ReadVolumeBitmap(reader io.ReaderAt) ([]byte, error) {
headerBlock, err := ReadBlock(reader, 2)
if err != nil {
return nil, err
}
volumeHeader := parseVolumeHeader(headerBlock)
@ -31,14 +34,17 @@ func ReadVolumeBitmap(reader io.ReaderAt) []byte {
}
for i := 0; i < totalBitmapBlocks; i++ {
bitmapBlock := ReadBlock(reader, i+volumeHeader.BitmapStartBlock)
bitmapBlock, err := ReadBlock(reader, i+volumeHeader.BitmapStartBlock)
if err != nil {
return nil, err
}
for j := 0; j < 512 && i*512+j < totalBitmapBytes; j++ {
bitmap[i*512+j] = bitmapBlock[j]
}
}
return bitmap
return bitmap, nil
}
// GetFreeBlockCount gets the number of free blocks on a ProDOS image
@ -53,14 +59,17 @@ func GetFreeBlockCount(volumeBitmap []byte, totalBlocks int) int {
return freeBlockCount
}
func writeVolumeBitmap(writer io.WriterAt, reader io.ReaderAt, bitmap []byte) {
headerBlock := ReadBlock(reader, 2)
func writeVolumeBitmap(readerWriter ReaderWriterAt, bitmap []byte) error {
headerBlock, err := ReadBlock(readerWriter, 2)
if err != nil {
return err
}
volumeHeader := parseVolumeHeader(headerBlock)
for i := 0; i < len(bitmap)/512; i++ {
WriteBlock(writer, volumeHeader.BitmapStartBlock+i, bitmap[i*512:i*512+512])
WriteBlock(readerWriter, volumeHeader.BitmapStartBlock+i, bitmap[i*512:i*512+512])
}
return nil
}
func createVolumeBitmap(numberOfBlocks int) []byte {

View File

@ -12,15 +12,16 @@ import (
)
// ReadBlock reads a block from a ProDOS volume into a byte array
func ReadBlock(reader io.ReaderAt, block int) []byte {
func ReadBlock(reader io.ReaderAt, block int) ([]byte, error) {
buffer := make([]byte, 512)
reader.ReadAt(buffer, int64(block)*512)
_, err := reader.ReadAt(buffer, int64(block)*512)
return buffer
return buffer, err
}
// WriteBlock writes a block to a ProDOS volume from a byte array
func WriteBlock(writer io.WriterAt, block int, buffer []byte) {
writer.WriteAt(buffer, int64(block)*512)
func WriteBlock(writer io.WriterAt, block int, buffer []byte) error {
_, err := writer.WriteAt(buffer, int64(block)*512)
return err
}

View File

@ -74,8 +74,11 @@ type FileEntry struct {
// ReadDirectory reads the directory information from a specified path
// on a ProDOS image
func ReadDirectory(reader io.ReaderAt, path string) (VolumeHeader, DirectoryHeader, []FileEntry) {
buffer := ReadBlock(reader, 2)
func ReadDirectory(reader io.ReaderAt, path string) (VolumeHeader, DirectoryHeader, []FileEntry, error) {
buffer, err := ReadBlock(reader, 2)
if err != nil {
return VolumeHeader{}, DirectoryHeader{}, nil, err
}
volumeHeader := parseVolumeHeader(buffer)
@ -86,17 +89,25 @@ func ReadDirectory(reader io.ReaderAt, path string) (VolumeHeader, DirectoryHead
path = strings.ToUpper(path)
paths := strings.Split(path, "/")
directoryHeader, fileEntries := getFileEntriesInDirectory(reader, 2, 1, paths)
directoryHeader, fileEntries, err := getFileEntriesInDirectory(reader, 2, 1, paths)
if err != nil {
return VolumeHeader{}, DirectoryHeader{}, nil, err
}
return volumeHeader, directoryHeader, fileEntries
return volumeHeader, directoryHeader, fileEntries, nil
}
func getFreeFileEntryInDirectory(reader io.ReaderAt, directory string) (FileEntry, error) {
_, directoryHeader, _ := ReadDirectory(reader, directory)
_, directoryHeader, _, err := ReadDirectory(reader, directory)
if err != nil {
return FileEntry{}, err
}
//DumpDirectoryHeader(directoryHeader)
blockNumber := directoryHeader.StartingBlock
buffer := ReadBlock(reader, blockNumber)
buffer, err := ReadBlock(reader, blockNumber)
if err != nil {
return FileEntry{}, err
}
entryOffset := 43 // start at offset after header
entryNumber := 2 // header is essentially the first entry so start at 2
@ -109,7 +120,10 @@ func getFreeFileEntryInDirectory(reader io.ReaderAt, directory string) (FileEntr
return FileEntry{}, errors.New("No free file entries found")
}
// else read the next block in the directory
buffer = ReadBlock(reader, blockNumber)
buffer, err = ReadBlock(reader, blockNumber)
if err != nil {
return FileEntry{}, nil
}
entryOffset = 4
entryNumber = 1
}
@ -127,8 +141,11 @@ func getFreeFileEntryInDirectory(reader io.ReaderAt, directory string) (FileEntr
}
}
func getFileEntriesInDirectory(reader io.ReaderAt, blockNumber int, currentPath int, paths []string) (DirectoryHeader, []FileEntry) {
buffer := ReadBlock(reader, blockNumber)
func getFileEntriesInDirectory(reader io.ReaderAt, blockNumber int, currentPath int, paths []string) (DirectoryHeader, []FileEntry, error) {
buffer, err := ReadBlock(reader, blockNumber)
if err != nil {
return DirectoryHeader{}, nil, err
}
directoryHeader := parseDirectoryHeader(buffer, blockNumber)
@ -143,7 +160,7 @@ func getFileEntriesInDirectory(reader io.ReaderAt, blockNumber int, currentPath
if !matchedDirectory && (currentPath == len(paths)-1) {
// path not matched by last path part
return DirectoryHeader{}, nil
return DirectoryHeader{}, nil, errors.New("path not matched")
}
for {
@ -151,16 +168,19 @@ func getFileEntriesInDirectory(reader io.ReaderAt, blockNumber int, currentPath
entryOffset = 4
entryNumber = 1
if blockNumber == 0 {
return DirectoryHeader{}, nil
return DirectoryHeader{}, nil, nil
}
buffer, err = ReadBlock(reader, nextBlock)
if err != nil {
return DirectoryHeader{}, nil, err
}
buffer = ReadBlock(reader, nextBlock)
nextBlock = int(buffer[2]) + int(buffer[3])*256
}
fileEntry := parseFileEntry(buffer[entryOffset:entryOffset+40], blockNumber, entryOffset)
if fileEntry.StorageType != StorageDeleted {
if matchedDirectory && activeEntries == directoryHeader.ActiveFileCount {
return directoryHeader, fileEntries[0:activeEntries]
return directoryHeader, fileEntries[0:activeEntries], nil
}
if matchedDirectory {
fileEntries[activeEntries] = fileEntry
@ -298,8 +318,11 @@ func parseDirectoryHeader(buffer []byte, blockNumber int) DirectoryHeader {
return directoryEntry
}
func writeDirectoryHeader(writer io.WriterAt, reader io.ReaderAt, directoryHeader DirectoryHeader) {
buffer := ReadBlock(reader, directoryHeader.StartingBlock)
func writeDirectoryHeader(readerWriter ReaderWriterAt, directoryHeader DirectoryHeader) error {
buffer, err := ReadBlock(readerWriter, directoryHeader.StartingBlock)
if err != nil {
return err
}
buffer[0x00] = byte(directoryHeader.PreviousBlock & 0x00FF)
buffer[0x01] = byte(directoryHeader.PreviousBlock >> 8)
buffer[0x02] = byte(directoryHeader.NextBlock & 0x00FF)
@ -310,5 +333,7 @@ func writeDirectoryHeader(writer io.WriterAt, reader io.ReaderAt, directoryHeade
}
buffer[0x25] = byte(directoryHeader.ActiveFileCount & 0x00FF)
buffer[0x26] = byte(directoryHeader.ActiveFileCount >> 8)
WriteBlock(writer, directoryHeader.StartingBlock, buffer)
WriteBlock(readerWriter, directoryHeader.StartingBlock, buffer)
return nil
}

View File

@ -29,7 +29,10 @@ func LoadFile(reader io.ReaderAt, path string) ([]byte, error) {
buffer := make([]byte, fileEntry.EndOfFile)
for i := 0; i < len(blockList); i++ {
block := ReadBlock(reader, blockList[i])
block, err := ReadBlock(reader, blockList[i])
if err != nil {
return nil, err
}
for j := 0; j < 512 && i*512+j < fileEntry.EndOfFile; j++ {
buffer[i*512+j] = block[j]
}
@ -39,25 +42,28 @@ func LoadFile(reader io.ReaderAt, path string) ([]byte, error) {
}
// WriteFile writes a file to a ProDOS volume from a byte array
func WriteFile(writer io.WriterAt, reader io.ReaderAt, path string, fileType int, auxType int, buffer []byte) error {
func WriteFile(readerWriter ReaderWriterAt, path string, fileType int, auxType int, buffer []byte) error {
directory, fileName := GetDirectoryAndFileNameFromPath(path)
existingFileEntry, _ := getFileEntry(reader, path)
existingFileEntry, _ := getFileEntry(readerWriter, path)
if existingFileEntry.StorageType != StorageDeleted {
DeleteFile(writer, reader, path)
DeleteFile(readerWriter, path)
}
// get list of blocks to write file to
blockList := createBlockList(reader, len(buffer))
blockList, err := createBlockList(readerWriter, len(buffer))
if err != nil {
return err
}
// seedling file
if len(buffer) <= 0x200 {
WriteBlock(writer, blockList[0], buffer)
WriteBlock(readerWriter, blockList[0], buffer)
}
// sapling file needs index block
if len(buffer) > 0x200 && len(buffer) <= 0x20000 {
writeSaplingFile(writer, buffer, blockList)
writeSaplingFile(readerWriter, buffer, blockList)
}
// TODO: add tree file
@ -65,10 +71,10 @@ func WriteFile(writer io.WriterAt, reader io.ReaderAt, path string, fileType int
return errors.New("files > 128KB not supported yet")
}
updateVolumeBitmap(writer, reader, blockList)
updateVolumeBitmap(readerWriter, blockList)
// add file entry to directory
fileEntry, err := getFreeFileEntryInDirectory(reader, directory)
fileEntry, err := getFreeFileEntryInDirectory(readerWriter, directory)
if err != nil {
return err
}
@ -89,20 +95,23 @@ func WriteFile(writer io.WriterAt, reader io.ReaderAt, path string, fileType int
fileEntry.StorageType = StorageTree
}
writeFileEntry(writer, fileEntry)
writeFileEntry(readerWriter, fileEntry)
// increment file count
directoryHeaderBlock := ReadBlock(reader, fileEntry.HeaderPointer)
directoryHeaderBlock, err := ReadBlock(readerWriter, fileEntry.HeaderPointer)
if err != nil {
return err
}
directoryHeader := parseDirectoryHeader(directoryHeaderBlock, fileEntry.HeaderPointer)
directoryHeader.ActiveFileCount++
writeDirectoryHeader(writer, reader, directoryHeader)
writeDirectoryHeader(readerWriter, directoryHeader)
return nil
}
// DeleteFile deletes a file from a ProDOS volume
func DeleteFile(writer io.WriterAt, reader io.ReaderAt, path string) error {
fileEntry, err := getFileEntry(reader, path)
func DeleteFile(readerWriter ReaderWriterAt, path string) error {
fileEntry, err := getFileEntry(readerWriter, path)
if err != nil {
return errors.New("File not found")
}
@ -114,27 +123,33 @@ func DeleteFile(writer io.WriterAt, reader io.ReaderAt, path string) error {
}
// free the blocks
blocks, err := getBlocklist(reader, fileEntry)
blocks, err := getBlocklist(readerWriter, fileEntry)
if err != nil {
return err
}
volumeBitmap, err := ReadVolumeBitmap(readerWriter)
if err != nil {
return err
}
volumeBitmap := ReadVolumeBitmap(reader)
for i := 0; i < len(blocks); i++ {
freeBlockInVolumeBitmap(volumeBitmap, blocks[i])
}
writeVolumeBitmap(writer, reader, volumeBitmap)
writeVolumeBitmap(readerWriter, volumeBitmap)
// decrement the directory entry count
directoryBlock := ReadBlock(reader, fileEntry.HeaderPointer)
directoryBlock, err := ReadBlock(readerWriter, fileEntry.HeaderPointer)
if err != nil {
return err
}
directoryHeader := parseDirectoryHeader(directoryBlock, fileEntry.HeaderPointer)
directoryHeader.ActiveFileCount--
writeDirectoryHeader(writer, reader, directoryHeader)
writeDirectoryHeader(readerWriter, directoryHeader)
// zero out directory entry
fileEntry.StorageType = 0
fileEntry.FileName = ""
writeFileEntry(writer, fileEntry)
writeFileEntry(readerWriter, fileEntry)
return nil
}
@ -157,12 +172,16 @@ func GetDirectoryAndFileNameFromPath(path string) (string, string) {
return directory, fileName
}
func updateVolumeBitmap(writer io.WriterAt, reader io.ReaderAt, blockList []int) {
volumeBitmap := ReadVolumeBitmap(reader)
func updateVolumeBitmap(readerWriter ReaderWriterAt, blockList []int) error {
volumeBitmap, err := ReadVolumeBitmap(readerWriter)
if err != nil {
return err
}
for i := 0; i < len(blockList); i++ {
markBlockInVolumeBitmap(volumeBitmap, blockList[i])
}
writeVolumeBitmap(writer, reader, volumeBitmap)
writeVolumeBitmap(readerWriter, volumeBitmap)
return nil
}
func writeSaplingFile(writer io.WriterAt, buffer []byte, blockList []int) {
@ -206,17 +225,26 @@ func getBlocklist(reader io.ReaderAt, fileEntry FileEntry) ([]int, error) {
blocks[0] = fileEntry.KeyPointer
return blocks, nil
case StorageSapling:
index := ReadBlock(reader, fileEntry.KeyPointer)
index, err := ReadBlock(reader, fileEntry.KeyPointer)
if err != nil {
return nil, err
}
blocks[0] = fileEntry.KeyPointer
for i := 0; i < fileEntry.BlocksUsed-1; i++ {
blocks[i+1] = int(index[i]) + int(index[i+256])*256
}
return blocks, nil
case StorageTree:
masterIndex := ReadBlock(reader, fileEntry.KeyPointer)
masterIndex, err := ReadBlock(reader, fileEntry.KeyPointer)
if err != nil {
return nil, err
}
blocks[0] = fileEntry.KeyPointer
for i := 0; i < 128; i++ {
index := ReadBlock(reader, int(masterIndex[i])+int(masterIndex[i+256])*256)
index, err := ReadBlock(reader, int(masterIndex[i])+int(masterIndex[i+256])*256)
if err != nil {
return nil, err
}
for j := 0; j < 256 && i*256+j < fileEntry.BlocksUsed; j++ {
if (int(index[j]) + int(index[j+256])*256) == 0 {
return blocks, nil
@ -237,7 +265,10 @@ func getDataBlocklist(reader io.ReaderAt, fileEntry FileEntry) ([]int, error) {
return blocks, nil
case StorageSapling:
blocks := make([]int, fileEntry.BlocksUsed-1)
index := ReadBlock(reader, fileEntry.KeyPointer)
index, err := ReadBlock(reader, fileEntry.KeyPointer)
if err != nil {
return nil, err
}
for i := 0; i < fileEntry.BlocksUsed-1; i++ {
blocks[i] = int(index[i]) + int(index[i+256])*256
}
@ -247,7 +278,7 @@ func getDataBlocklist(reader io.ReaderAt, fileEntry FileEntry) ([]int, error) {
return nil, errors.New("Unsupported file storage type")
}
func createBlockList(reader io.ReaderAt, fileSize int) []int {
func createBlockList(reader io.ReaderAt, fileSize int) ([]int, error) {
numberOfBlocks := fileSize / 512
if fileSize%512 > 0 {
numberOfBlocks++
@ -265,15 +296,21 @@ func createBlockList(reader io.ReaderAt, fileSize int) []int {
numberOfBlocks++
}
}
volumeBitmap := ReadVolumeBitmap(reader)
volumeBitmap, err := ReadVolumeBitmap(reader)
if err != nil {
return nil, err
}
blockList := findFreeBlocks(volumeBitmap, numberOfBlocks)
return blockList
return blockList, nil
}
func getFileEntry(reader io.ReaderAt, path string) (FileEntry, error) {
directory, fileName := GetDirectoryAndFileNameFromPath(path)
_, _, fileEntries := ReadDirectory(reader, directory)
_, _, fileEntries, err := ReadDirectory(reader, directory)
if err != nil {
return FileEntry{}, err
}
if fileEntries == nil || len(fileEntries) == 0 {
return FileEntry{}, errors.New("File entry not found")

View File

@ -8,14 +8,13 @@ package prodos
import (
"fmt"
"io"
"strings"
"time"
)
// CreateVolume formats a new ProDOS volume including boot block,
// volume bitmap and empty directory
func CreateVolume(writer io.WriterAt, reader io.ReaderAt, volumeName string, numberOfBlocks int) {
func CreateVolume(readerWriter ReaderWriterAt, volumeName string, numberOfBlocks int) {
if numberOfBlocks > 65535 || numberOfBlocks < 64 {
return
}
@ -28,7 +27,7 @@ func CreateVolume(writer io.WriterAt, reader io.ReaderAt, volumeName string, num
blankBlock := make([]byte, 512)
for i := 0; i < numberOfBlocks; i++ {
WriteBlock(writer, i, blankBlock)
WriteBlock(readerWriter, i, blankBlock)
}
volumeHeader := [43]byte{}
@ -63,10 +62,10 @@ func CreateVolume(writer io.WriterAt, reader io.ReaderAt, volumeName string, num
volumeHeader[0x29] = byte(numberOfBlocks & 0xFF)
volumeHeader[0x2A] = byte(numberOfBlocks >> 8)
writer.WriteAt(volumeHeader[:], 1024)
readerWriter.WriteAt(volumeHeader[:], 1024)
// boot block 0
WriteBlock(writer, 0, getBootBlock())
WriteBlock(readerWriter, 0, getBootBlock())
// pointers to volume directory blocks
for i := 2; i < 6; i++ {
@ -83,12 +82,12 @@ func CreateVolume(writer io.WriterAt, reader io.ReaderAt, volumeName string, num
pointers[2] = byte(i + 1)
}
pointers[3] = 0x00
writer.WriteAt(pointers, int64(i*512))
readerWriter.WriteAt(pointers, int64(i*512))
}
// volume bit map starting at block 6
volumeBitmap := createVolumeBitmap(numberOfBlocks)
writeVolumeBitmap(writer, reader, volumeBitmap)
writeVolumeBitmap(readerWriter, volumeBitmap)
}
func getBootBlock() []byte {

View File

@ -21,9 +21,9 @@ func TestCreateVolume(t *testing.T) {
t.Run(testname, func(t *testing.T) {
file := NewMemoryFile(0x2000000)
CreateVolume(file, file, tt.wantVolumeName, tt.blocks)
CreateVolume(file, tt.wantVolumeName, tt.blocks)
volumeHeader, _, fileEntries := ReadDirectory(file, "")
volumeHeader, _, fileEntries, _ := ReadDirectory(file, "")
if volumeHeader.VolumeName != tt.wantVolumeName {
t.Errorf("got volume name %s, want %s", volumeHeader.VolumeName, tt.wantVolumeName)
}
@ -34,7 +34,7 @@ func TestCreateVolume(t *testing.T) {
t.Errorf("got files %d, want 0", len(fileEntries))
}
volumeBitmap := ReadVolumeBitmap(file)
volumeBitmap, _ := ReadVolumeBitmap(file)
freeBlockCount := GetFreeBlockCount(volumeBitmap, tt.blocks)
if freeBlockCount != tt.wantFreeBlocks {
t.Errorf("got free blocks: %d, want %d", freeBlockCount, tt.wantFreeBlocks)

View File

@ -12,6 +12,11 @@ type MemoryFile struct {
size int
}
type ReaderWriterAt interface {
ReadAt(data []byte, offset int64) (int, error)
WriteAt(data []byte, offset int64) (int, error)
}
// NewMemoryFile creates an in-memory file of the specified size in bytes
func NewMemoryFile(size int) *MemoryFile {
return &MemoryFile{make([]byte, size), size}