diff --git a/main.go b/main.go index b96a30b..5055a40 100644 --- a/main.go +++ b/main.go @@ -1,3 +1,11 @@ +// 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. + +// This file provides a command line utility to read, write and delete +// files and directories on a ProDOS drive image as well as format +// new volumes + package main import ( @@ -101,7 +109,7 @@ func main() { fmt.Printf("Failed to open input file %s: %s", inFileName, err) os.Exit(1) } - err = prodos.WriteFile(file, pathName, fileType, auxType, inFile) + err = prodos.WriteFile(file, file, pathName, fileType, auxType, inFile) if err != nil { fmt.Printf("Failed to write file %s: %s", pathName, err) } @@ -136,7 +144,7 @@ func main() { return } defer file.Close() - prodos.CreateVolume(file, volumeName, volumeSize) + prodos.CreateVolume(file, file, volumeName, volumeSize) case "rm": file, err := os.OpenFile(fileName, os.O_RDWR, 0755) if err != nil { @@ -144,7 +152,7 @@ func main() { os.Exit(1) } defer file.Close() - prodos.DeleteFile(file, pathName) + prodos.DeleteFile(file, file, pathName) default: fmt.Printf("Invalid command: %s\n\n", command) flag.PrintDefaults() diff --git a/prodos/basic.go b/prodos/basic.go index c8bffa7..fa5f1d9 100644 --- a/prodos/basic.go +++ b/prodos/basic.go @@ -1,3 +1,9 @@ +// 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. + +// This file provides conversion between BASIC and text + package prodos import ( diff --git a/prodos/bitmap.go b/prodos/bitmap.go index c8e0a5d..fd46d08 100644 --- a/prodos/bitmap.go +++ b/prodos/bitmap.go @@ -1,11 +1,18 @@ +// 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. + +// This file provides access to volum bitmap on +// a ProDOS drive image + package prodos import ( - "os" + "io" ) -func ReadVolumeBitmap(file *os.File) []byte { - headerBlock := ReadBlock(file, 2) +func ReadVolumeBitmap(reader io.ReaderAt) []byte { + headerBlock := ReadBlock(reader, 2) volumeHeader := parseVolumeHeader(headerBlock) @@ -23,7 +30,7 @@ func ReadVolumeBitmap(file *os.File) []byte { } for i := 0; i < totalBitmapBlocks; i++ { - bitmapBlock := ReadBlock(file, i+volumeHeader.BitmapStartBlock) + bitmapBlock := ReadBlock(reader, i+volumeHeader.BitmapStartBlock) for j := 0; j < 512 && i*512+j < totalBitmapBytes; j++ { bitmap[i*512+j] = bitmapBlock[j] @@ -33,13 +40,13 @@ func ReadVolumeBitmap(file *os.File) []byte { return bitmap } -func writeVolumeBitmap(file *os.File, bitmap []byte) { - headerBlock := ReadBlock(file, 2) +func writeVolumeBitmap(writer io.WriterAt, reader io.ReaderAt, bitmap []byte) { + headerBlock := ReadBlock(reader, 2) volumeHeader := parseVolumeHeader(headerBlock) for i := 0; i < len(bitmap)/512; i++ { - WriteBlock(file, volumeHeader.BitmapStartBlock+i, bitmap[i*512:i*512+512]) + WriteBlock(writer, volumeHeader.BitmapStartBlock+i, bitmap[i*512:i*512+512]) } } diff --git a/prodos/block.go b/prodos/block.go index 3405a20..10fe2ea 100644 --- a/prodos/block.go +++ b/prodos/block.go @@ -1,22 +1,24 @@ +// 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. + +// This file provides access to read and write +// blocks on a ProDOS drive image + package prodos import ( - "os" + "io" ) -func ReadBlock(file *os.File, block int) []byte { +func ReadBlock(reader io.ReaderAt, block int) []byte { buffer := make([]byte, 512) - file.ReadAt(buffer, int64(block)*512) + reader.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) +func WriteBlock(writer io.WriterAt, block int, buffer []byte) { + writer.WriteAt(buffer, int64(block)*512) } diff --git a/prodos/directory.go b/prodos/directory.go index e00049c..7b250bd 100644 --- a/prodos/directory.go +++ b/prodos/directory.go @@ -1,9 +1,16 @@ +// 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. + +// This file provides access to read, write, delete +// fand parse directories on a ProDOS drive image + package prodos import ( "errors" "fmt" - "os" + "io" "strings" "time" ) @@ -56,8 +63,8 @@ type FileEntry struct { DirectoryOffset int } -func ReadDirectory(file *os.File, path string) (VolumeHeader, DirectoryHeader, []FileEntry) { - buffer := ReadBlock(file, 2) +func ReadDirectory(reader io.ReaderAt, path string) (VolumeHeader, DirectoryHeader, []FileEntry) { + buffer := ReadBlock(reader, 2) volumeHeader := parseVolumeHeader(buffer) @@ -68,16 +75,16 @@ func ReadDirectory(file *os.File, path string) (VolumeHeader, DirectoryHeader, [ path = strings.ToUpper(path) paths := strings.Split(path, "/") - directoryHeader, fileEntries := getFileEntriesInDirectory(file, 2, 1, paths) + directoryHeader, fileEntries := getFileEntriesInDirectory(reader, 2, 1, paths) return volumeHeader, directoryHeader, fileEntries } -func getFreeFileEntryInDirectory(file *os.File, directory string) (FileEntry, error) { - _, directoryHeader, _ := ReadDirectory(file, directory) +func getFreeFileEntryInDirectory(reader io.ReaderAt, directory string) (FileEntry, error) { + _, directoryHeader, _ := ReadDirectory(reader, directory) //DumpDirectoryHeader(directoryHeader) blockNumber := directoryHeader.StartingBlock - buffer := ReadBlock(file, blockNumber) + buffer := ReadBlock(reader, blockNumber) entryOffset := 43 // start at offset after header entryNumber := 2 // header is essentially the first entry so start at 2 @@ -91,7 +98,7 @@ func getFreeFileEntryInDirectory(file *os.File, directory string) (FileEntry, er return FileEntry{}, errors.New("No free file entries found") } // else read the next block in the directory - buffer = ReadBlock(file, blockNumber) + buffer = ReadBlock(reader, blockNumber) entryOffset = 4 entryNumber = 1 } @@ -109,8 +116,8 @@ func getFreeFileEntryInDirectory(file *os.File, directory string) (FileEntry, er } } -func getFileEntriesInDirectory(file *os.File, blockNumber int, currentPath int, paths []string) (DirectoryHeader, []FileEntry) { - buffer := ReadBlock(file, blockNumber) +func getFileEntriesInDirectory(reader io.ReaderAt, blockNumber int, currentPath int, paths []string) (DirectoryHeader, []FileEntry) { + buffer := ReadBlock(reader, blockNumber) directoryHeader := parseDirectoryHeader(buffer, blockNumber) @@ -135,7 +142,7 @@ func getFileEntriesInDirectory(file *os.File, blockNumber int, currentPath int, if blockNumber == 0 { return DirectoryHeader{}, nil } - buffer = ReadBlock(file, nextBlock) + buffer = ReadBlock(reader, nextBlock) nextBlock = int(buffer[2]) + int(buffer[3])*256 } fileEntry := parseFileEntry(buffer[entryOffset:entryOffset+40], blockNumber, entryOffset) @@ -147,7 +154,7 @@ func getFileEntriesInDirectory(file *os.File, blockNumber int, currentPath int, if matchedDirectory { fileEntries[activeEntries] = fileEntry } else if !matchedDirectory && fileEntry.FileType == 15 && paths[currentPath+1] == fileEntry.FileName { - return getFileEntriesInDirectory(file, fileEntry.KeyPointer, currentPath+1, paths) + return getFileEntriesInDirectory(reader, fileEntry.KeyPointer, currentPath+1, paths) } activeEntries++ } @@ -194,7 +201,7 @@ func parseFileEntry(buffer []byte, blockNumber int, entryOffset int) FileEntry { return fileEntry } -func writeFileEntry(file *os.File, fileEntry FileEntry) { +func writeFileEntry(writer io.WriterAt, fileEntry FileEntry) { buffer := make([]byte, 39) buffer[0] = byte(fileEntry.StorageType)<<4 + byte(len(fileEntry.FileName)) for i := 0; i < len(fileEntry.FileName); i++ { @@ -224,7 +231,7 @@ func writeFileEntry(file *os.File, fileEntry FileEntry) { buffer[0x25] = byte(fileEntry.HeaderPointer & 0x00FF) buffer[0x26] = byte(fileEntry.HeaderPointer >> 8) - _, err := file.WriteAt(buffer, int64(fileEntry.DirectoryBlock*512+fileEntry.DirectoryOffset)) + _, err := writer.WriteAt(buffer, int64(fileEntry.DirectoryBlock*512+fileEntry.DirectoryOffset)) if err != nil { } @@ -280,8 +287,8 @@ func parseDirectoryHeader(buffer []byte, blockNumber int) DirectoryHeader { return directoryEntry } -func writeDirectoryHeader(file *os.File, directoryHeader DirectoryHeader) { - buffer := ReadBlock(file, directoryHeader.StartingBlock) +func writeDirectoryHeader(writer io.WriterAt, reader io.ReaderAt, directoryHeader DirectoryHeader) { + buffer := ReadBlock(reader, directoryHeader.StartingBlock) buffer[0x00] = byte(directoryHeader.PreviousBlock & 0x00FF) buffer[0x01] = byte(directoryHeader.PreviousBlock >> 8) buffer[0x02] = byte(directoryHeader.NextBlock & 0x00FF) @@ -292,5 +299,5 @@ func writeDirectoryHeader(file *os.File, directoryHeader DirectoryHeader) { } buffer[0x25] = byte(directoryHeader.ActiveFileCount & 0x00FF) buffer[0x26] = byte(directoryHeader.ActiveFileCount >> 8) - WriteBlock(file, directoryHeader.StartingBlock, buffer) + WriteBlock(writer, directoryHeader.StartingBlock, buffer) } diff --git a/prodos/doc.go b/prodos/doc.go new file mode 100644 index 0000000..eadf65d --- /dev/null +++ b/prodos/doc.go @@ -0,0 +1,11 @@ +// 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 provides access to read/write/delete/list files and + directories on a ProDOS order drive images. + +*/ + +package prodos diff --git a/prodos/file.go b/prodos/file.go index af2c623..940e001 100644 --- a/prodos/file.go +++ b/prodos/file.go @@ -1,19 +1,27 @@ +// 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. + +// This file provides access to read, write and delete +// files on a ProDOS drive image + package prodos import ( "errors" - "os" + "io" "strings" "time" ) -func LoadFile(file *os.File, path string) ([]byte, error) { - fileEntry, err := getFileEntry(file, path) +// 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) if err != nil { return nil, err } - blockList, err := getDataBlocklist(file, fileEntry) + blockList, err := getDataBlocklist(reader, fileEntry) if err != nil { return nil, err } @@ -21,7 +29,7 @@ func LoadFile(file *os.File, path string) ([]byte, error) { buffer := make([]byte, fileEntry.EndOfFile) for i := 0; i < len(blockList); i++ { - block := ReadBlock(file, blockList[i]) + block := ReadBlock(reader, blockList[i]) for j := 0; j < 512 && i*512+j < fileEntry.EndOfFile; j++ { buffer[i*512+j] = block[j] } @@ -30,25 +38,25 @@ func LoadFile(file *os.File, path string) ([]byte, error) { return buffer, nil } -func WriteFile(file *os.File, path string, fileType int, auxType int, buffer []byte) error { +func WriteFile(writer io.WriterAt, reader io.ReaderAt, path string, fileType int, auxType int, buffer []byte) error { directory, fileName := GetDirectoryAndFileNameFromPath(path) - existingFileEntry, _ := getFileEntry(file, path) + existingFileEntry, _ := getFileEntry(reader, path) if existingFileEntry.StorageType != StorageDeleted { - DeleteFile(file, path) + DeleteFile(writer, reader, path) } // get list of blocks to write file to - blockList := createBlockList(file, len(buffer)) + blockList := createBlockList(reader, len(buffer)) // seedling file if len(buffer) <= 0x200 { - WriteBlock(file, blockList[0], buffer) + WriteBlock(writer, blockList[0], buffer) } // sapling file needs index block if len(buffer) > 0x200 && len(buffer) <= 0x20000 { - writeSaplingFile(file, buffer, blockList) + writeSaplingFile(writer, buffer, blockList) } // TODO: add tree file @@ -56,10 +64,10 @@ func WriteFile(file *os.File, path string, fileType int, auxType int, buffer []b return errors.New("Files > 128KB not supported yet.") } - updateVolumeBitmap(file, blockList) + updateVolumeBitmap(writer, reader, blockList) // add file entry to directory - fileEntry, err := getFreeFileEntryInDirectory(file, directory) + fileEntry, err := getFreeFileEntryInDirectory(reader, directory) if err != nil { return err } @@ -80,59 +88,19 @@ func WriteFile(file *os.File, path string, fileType int, auxType int, buffer []b fileEntry.StorageType = StorageTree } - writeFileEntry(file, fileEntry) + writeFileEntry(writer, fileEntry) // increment file count - directoryHeaderBlock := ReadBlock(file, fileEntry.HeaderPointer) + directoryHeaderBlock := ReadBlock(reader, fileEntry.HeaderPointer) directoryHeader := parseDirectoryHeader(directoryHeaderBlock, fileEntry.HeaderPointer) directoryHeader.ActiveFileCount++ - writeDirectoryHeader(file, directoryHeader) + writeDirectoryHeader(writer, reader, directoryHeader) return nil } -func updateVolumeBitmap(file *os.File, blockList []int) { - volumeBitmap := ReadVolumeBitmap(file) - for i := 0; i < len(blockList); i++ { - markBlockInVolumeBitmap(volumeBitmap, blockList[i]) - } - writeVolumeBitmap(file, volumeBitmap) -} - -func writeSaplingFile(file *os.File, buffer []byte, blockList []int) { - // write index block with pointers to data blocks - indexBuffer := make([]byte, 512) - for i := 0; i < 256; i++ { - if i < len(blockList)-1 { - indexBuffer[i] = byte(blockList[i+1] & 0x00FF) - indexBuffer[i+256] = byte(blockList[i+1] >> 8) - } - } - WriteBlock(file, blockList[0], indexBuffer) - - // write all data blocks - blockBuffer := make([]byte, 512) - blockPointer := 0 - blockIndexNumber := 1 - for i := 0; i < len(buffer); i++ { - blockBuffer[blockPointer] = buffer[i] - if blockPointer == 511 { - WriteBlock(file, blockList[blockIndexNumber], blockBuffer) - blockPointer = 0 - blockIndexNumber++ - } else if i == len(buffer)-1 { - for j := blockPointer; j < 512; j++ { - blockBuffer[j] = 0 - } - WriteBlock(file, blockList[blockIndexNumber], blockBuffer) - } else { - blockPointer++ - } - } -} - -func DeleteFile(file *os.File, path string) error { - fileEntry, err := getFileEntry(file, path) +func DeleteFile(writer io.WriterAt, reader io.ReaderAt, path string) error { + fileEntry, err := getFileEntry(reader, path) if err != nil { return errors.New("File not found") } @@ -144,27 +112,27 @@ func DeleteFile(file *os.File, path string) error { } // free the blocks - blocks, err := getBlocklist(file, fileEntry) + blocks, err := getBlocklist(reader, fileEntry) if err != nil { return err } - volumeBitmap := ReadVolumeBitmap(file) + volumeBitmap := ReadVolumeBitmap(reader) for i := 0; i < len(blocks); i++ { freeBlockInVolumeBitmap(volumeBitmap, blocks[i]) } - writeVolumeBitmap(file, volumeBitmap) + writeVolumeBitmap(writer, reader, volumeBitmap) // decrement the directory entry count - directoryBlock := ReadBlock(file, fileEntry.HeaderPointer) + directoryBlock := ReadBlock(reader, fileEntry.HeaderPointer) directoryHeader := parseDirectoryHeader(directoryBlock, fileEntry.HeaderPointer) directoryHeader.ActiveFileCount-- - writeDirectoryHeader(file, directoryHeader) + writeDirectoryHeader(writer, reader, directoryHeader) // zero out directory entry fileEntry.StorageType = 0 fileEntry.FileName = "" - writeFileEntry(file, fileEntry) + writeFileEntry(writer, fileEntry) return nil } @@ -186,8 +154,48 @@ func GetDirectoryAndFileNameFromPath(path string) (string, string) { return directory, fileName } +func updateVolumeBitmap(writer io.WriterAt, reader io.ReaderAt, blockList []int) { + volumeBitmap := ReadVolumeBitmap(reader) + for i := 0; i < len(blockList); i++ { + markBlockInVolumeBitmap(volumeBitmap, blockList[i]) + } + writeVolumeBitmap(writer, reader, volumeBitmap) +} + +func writeSaplingFile(writer io.WriterAt, buffer []byte, blockList []int) { + // write index block with pointers to data blocks + indexBuffer := make([]byte, 512) + for i := 0; i < 256; i++ { + if i < len(blockList)-1 { + indexBuffer[i] = byte(blockList[i+1] & 0x00FF) + indexBuffer[i+256] = byte(blockList[i+1] >> 8) + } + } + WriteBlock(writer, blockList[0], indexBuffer) + + // write all data blocks + blockBuffer := make([]byte, 512) + blockPointer := 0 + blockIndexNumber := 1 + for i := 0; i < len(buffer); i++ { + blockBuffer[blockPointer] = buffer[i] + if blockPointer == 511 { + WriteBlock(writer, blockList[blockIndexNumber], blockBuffer) + blockPointer = 0 + blockIndexNumber++ + } else if i == len(buffer)-1 { + for j := blockPointer; j < 512; j++ { + blockBuffer[j] = 0 + } + WriteBlock(writer, blockList[blockIndexNumber], blockBuffer) + } else { + blockPointer++ + } + } +} + // Returns all blocks, including index blocks -func getBlocklist(file *os.File, fileEntry FileEntry) ([]int, error) { +func getBlocklist(reader io.ReaderAt, fileEntry FileEntry) ([]int, error) { blocks := make([]int, fileEntry.BlocksUsed) switch fileEntry.StorageType { @@ -195,17 +203,17 @@ func getBlocklist(file *os.File, fileEntry FileEntry) ([]int, error) { blocks[0] = fileEntry.KeyPointer return blocks, nil case StorageSapling: - index := ReadBlock(file, fileEntry.KeyPointer) + index := ReadBlock(reader, fileEntry.KeyPointer) 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(file, fileEntry.KeyPointer) + masterIndex := ReadBlock(reader, fileEntry.KeyPointer) blocks[0] = fileEntry.KeyPointer for i := 0; i < 128; i++ { - index := ReadBlock(file, int(masterIndex[i])+int(masterIndex[i+256])*256) + index := ReadBlock(reader, int(masterIndex[i])+int(masterIndex[i+256])*256) for j := 0; j < 256 && i*256+j < fileEntry.BlocksUsed; j++ { if (int(index[j]) + int(index[j+256])*256) == 0 { return blocks, nil @@ -215,10 +223,10 @@ func getBlocklist(file *os.File, fileEntry FileEntry) ([]int, error) { } } - return nil, errors.New("Unsupported file storage type") + return nil, errors.New("unsupported file storage type") } -func getDataBlocklist(file *os.File, fileEntry FileEntry) ([]int, error) { +func getDataBlocklist(reader io.ReaderAt, fileEntry FileEntry) ([]int, error) { switch fileEntry.StorageType { case StorageSeedling: blocks := make([]int, 1) @@ -226,7 +234,7 @@ func getDataBlocklist(file *os.File, fileEntry FileEntry) ([]int, error) { return blocks, nil case StorageSapling: blocks := make([]int, fileEntry.BlocksUsed-1) - index := ReadBlock(file, fileEntry.KeyPointer) + index := ReadBlock(reader, fileEntry.KeyPointer) for i := 0; i < fileEntry.BlocksUsed-1; i++ { blocks[i] = int(index[i]) + int(index[i+256])*256 } @@ -236,7 +244,7 @@ func getDataBlocklist(file *os.File, fileEntry FileEntry) ([]int, error) { return nil, errors.New("Unsupported file storage type") } -func createBlockList(file *os.File, fileSize int) []int { +func createBlockList(reader io.ReaderAt, fileSize int) []int { numberOfBlocks := fileSize / 512 if fileSize%512 > 0 { numberOfBlocks++ @@ -254,15 +262,15 @@ func createBlockList(file *os.File, fileSize int) []int { numberOfBlocks++ } } - volumeBitmap := ReadVolumeBitmap(file) + volumeBitmap := ReadVolumeBitmap(reader) blockList := findFreeBlocks(volumeBitmap, numberOfBlocks) return blockList } -func getFileEntry(file *os.File, path string) (FileEntry, error) { +func getFileEntry(reader io.ReaderAt, path string) (FileEntry, error) { directory, fileName := GetDirectoryAndFileNameFromPath(path) - _, _, fileEntries := ReadDirectory(file, directory) + _, _, fileEntries := ReadDirectory(reader, directory) 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 10829ae..5d79af4 100644 --- a/prodos/format.go +++ b/prodos/format.go @@ -1,13 +1,21 @@ +// 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. + +// This file provides access to format a ProDOS drive image + package prodos import ( "fmt" - "os" + "io" "strings" "time" ) -func CreateVolume(file *os.File, volumeName string, numberOfBlocks int) { +// 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) { if numberOfBlocks > 65535 || numberOfBlocks < 64 { return } @@ -20,7 +28,7 @@ func CreateVolume(file *os.File, volumeName string, numberOfBlocks int) { blankBlock := make([]byte, 512) for i := 0; i < numberOfBlocks; i++ { - WriteBlockNoSync(file, i, blankBlock) + WriteBlock(writer, i, blankBlock) } volumeHeader := [43]byte{} @@ -55,11 +63,10 @@ func CreateVolume(file *os.File, volumeName string, numberOfBlocks int) { volumeHeader[0x29] = byte(numberOfBlocks & 0xFF) volumeHeader[0x2A] = byte(numberOfBlocks >> 8) - file.WriteAt(volumeHeader[:], 1024) - file.Sync() + writer.WriteAt(volumeHeader[:], 1024) // boot block 0 - WriteBlock(file, 0, getBootBlock()) + WriteBlock(writer, 0, getBootBlock()) // pointers to volume directory blocks for i := 2; i < 6; i++ { @@ -76,12 +83,12 @@ func CreateVolume(file *os.File, volumeName string, numberOfBlocks int) { pointers[2] = byte(i + 1) } pointers[3] = 0x00 - file.WriteAt(pointers, int64(i*512)) + writer.WriteAt(pointers, int64(i*512)) } // volume bit map starting at block 6 volumeBitmap := createVolumeBitmap(numberOfBlocks) - writeVolumeBitmap(file, volumeBitmap) + writeVolumeBitmap(writer, reader, volumeBitmap) } func getBootBlock() []byte { diff --git a/prodos/format_test.go b/prodos/format_test.go index dd4f69f..27ca64a 100644 --- a/prodos/format_test.go +++ b/prodos/format_test.go @@ -2,7 +2,6 @@ package prodos import ( "fmt" - "os" "testing" ) @@ -20,17 +19,9 @@ func TestCreateVolume(t *testing.T) { for _, tt := range tests { testname := fmt.Sprintf("%d", tt.blocks) t.Run(testname, func(t *testing.T) { - fileName := os.TempDir() + "/test-volume.hdv" - defer os.Remove(fileName) - file, err := os.Create(fileName) - if err != nil { - t.Errorf("failed to create file: %s\n", err) - return - } + file := NewMemoryFile(0x2000000) - defer file.Close() - - CreateVolume(file, tt.wantVolumeName, tt.blocks) + CreateVolume(file, file, tt.wantVolumeName, tt.blocks) volumeHeader, _, fileEntries := ReadDirectory(file, "") if volumeHeader.VolumeName != tt.wantVolumeName { diff --git a/prodos/memfile.go b/prodos/memfile.go new file mode 100644 index 0000000..726115d --- /dev/null +++ b/prodos/memfile.go @@ -0,0 +1,26 @@ +// 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. + +// This file provides a file in memory + +package prodos + +type MemoryFile struct { + data []byte + size int +} + +func NewMemoryFile(size int) *MemoryFile { + return &MemoryFile{make([]byte, size), size} +} + +func (memoryFile *MemoryFile) WriteAt(data []byte, offset int64) (int, error) { + copy(memoryFile.data[int(offset):], data) + return len(data), nil +} + +func (memoryFile *MemoryFile) ReadAt(data []byte, offset int64) (int, error) { + copy(data, memoryFile.data[int(offset):]) + return len(data), nil +} diff --git a/prodos/text.go b/prodos/text.go index bb2f87b..1adbef1 100644 --- a/prodos/text.go +++ b/prodos/text.go @@ -1,3 +1,10 @@ +// 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. + +// This file provides text dumps for directories +// and blocks + package prodos import ( diff --git a/prodos/time.go b/prodos/time.go index 9803d44..cc34af8 100644 --- a/prodos/time.go +++ b/prodos/time.go @@ -1,3 +1,9 @@ +// 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. + +// This file provides conversion to and from ProDOS time format + package prodos import ( diff --git a/readme.go b/readme.go index f889f38..fc42870 100644 --- a/readme.go +++ b/readme.go @@ -1,3 +1,9 @@ +// 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. + +// This file provides readme for command line utility + package main import "fmt" @@ -8,7 +14,7 @@ func printReadme() { fmt.Println(` MIT License -Copyright (c) 2021 Terence Boldt +Copyright (c)2021-2022 Terence Boldt Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal