From 62bb781fca5213e7f072b8cc8edadc85fef822a6 Mon Sep 17 00:00:00 2001 From: Terence Boldt Date: Fri, 4 Mar 2022 18:08:33 -0500 Subject: [PATCH] Add error handling (#5) * Add error handling * Update to 0.3.0 --- main.go | 25 ++++++++--- prodos/bitmap.go | 25 +++++++---- prodos/block.go | 11 ++--- prodos/directory.go | 59 ++++++++++++++++++-------- prodos/file.go | 99 +++++++++++++++++++++++++++++-------------- prodos/format.go | 13 +++--- prodos/format_test.go | 6 +-- prodos/memfile.go | 5 +++ 8 files changed, 165 insertions(+), 78 deletions(-) diff --git a/main.go b/main.go index 0cd50b8..dc9ca20 100644 --- a/main.go +++ b/main.go @@ -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() diff --git a/prodos/bitmap.go b/prodos/bitmap.go index 54ad262..bf0fc61 100644 --- a/prodos/bitmap.go +++ b/prodos/bitmap.go @@ -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 { diff --git a/prodos/block.go b/prodos/block.go index 7707bd0..6fbf4df 100644 --- a/prodos/block.go +++ b/prodos/block.go @@ -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 } diff --git a/prodos/directory.go b/prodos/directory.go index f75ab4e..17ff3a5 100644 --- a/prodos/directory.go +++ b/prodos/directory.go @@ -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 } diff --git a/prodos/file.go b/prodos/file.go index c9ef880..c439cdb 100644 --- a/prodos/file.go +++ b/prodos/file.go @@ -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") diff --git a/prodos/format.go b/prodos/format.go index 5d79af4..7880a06 100644 --- a/prodos/format.go +++ b/prodos/format.go @@ -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 { diff --git a/prodos/format_test.go b/prodos/format_test.go index 27ca64a..e2458c0 100644 --- a/prodos/format_test.go +++ b/prodos/format_test.go @@ -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) diff --git a/prodos/memfile.go b/prodos/memfile.go index f7c1354..08521e8 100644 --- a/prodos/memfile.go +++ b/prodos/memfile.go @@ -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}