Add file deletion

This commit is contained in:
Terence Boldt 2021-06-11 22:43:35 -04:00
parent 7e623c530f
commit a04603f1e2
7 changed files with 228 additions and 68 deletions

8
.gitignore vendored
View File

@ -13,3 +13,11 @@
# Dependency directories (remove the comment below to include it)
# vendor/
# Project specific
*.hdv
*.bin
*.xxd
*.swp
.DS_Store
ProDOS-Utilities

View File

@ -2,15 +2,19 @@
This project is just starting but is intended to be both a command line tool and library to provide access to ProDOS based hard drive images. It is written in Go to be cross platform (Linux, Windows, macOS etc.). Functionality, naming and parameters are subject to change without notice. This project was started so I would be able to automate writing the firmware file into the drive image for one of my other projects [Apple2-IO-RPi](https://github.com/tjboldt/Apple2-IO-RPi).
## Current command line functionality
1. Export files up to 128KB (only seedling and sapling files are supported)
1. Export files
2. List any directory
3. Display volume bitmap
4. Create new volume
5. Delete file
## Current library functionality
1. Read block
2. Write block
3. Read file (up to 128KB)
4. Read volume bitmap
5. Write volume bitmap
6. Get list of file entries from any path
7. Get volume header
3. Read file
4. Delete file
5. Create new volume
6. Read volume bitmap
7. Write volume bitmap
8. Get list of file entries from any path
9. Get volume header

11
main.go
View File

@ -18,7 +18,7 @@ func main() {
var volumeName string
flag.StringVar(&fileName, "driveimage", "", "A ProDOS format drive image")
flag.StringVar(&pathName, "path", "", "Path name in ProDOS drive image")
flag.StringVar(&command, "command", "ls", "Command to execute: ls, get, put, volumebitmap, readblock, writeblock, create")
flag.StringVar(&command, "command", "ls", "Command to execute: ls, get, put, volumebitmap, readblock, writeblock, createvolume, delete")
flag.StringVar(&outFileName, "outfile", "export.bin", "Name of file to write")
flag.IntVar(&volumeSize, "volumesize", 65535, "Number of blocks to create the volume with")
flag.StringVar(&volumeName, "volumename", "NO.NAME", "Specifiy a name for the volume from 1 to 15 characters")
@ -71,9 +71,14 @@ func main() {
os.Exit(1)
}
outFile.Write(block)
case "create":
//fmt.Println("Create volume")
case "createvolume":
prodos.CreateVolume(fileName, volumeName, volumeSize)
case "delete":
file, err := os.OpenFile(fileName, os.O_RDWR, 0755)
if err != nil {
os.Exit(1)
}
prodos.DeleteFile(file, pathName)
default:
fmt.Printf("Command %s not handle\n", command)
os.Exit(1)

View File

@ -82,27 +82,55 @@ func MarkBlockInVolumeBitmap(volumeBitmap []byte, blockNumber int) {
bitToChange := blockNumber % 8
byteToChange := blockNumber / 8
byteToWrite := 0b11111111
byteToAnd := 0b11111111
switch bitToChange {
case 0:
byteToWrite = 0b01111111
byteToAnd = 0b01111111
case 1:
byteToWrite = 0b10111111
byteToAnd = 0b10111111
case 2:
byteToWrite = 0b11011111
byteToAnd = 0b11011111
case 3:
byteToWrite = 0b11101111
byteToAnd = 0b11101111
case 4:
byteToWrite = 0b11110111
byteToAnd = 0b11110111
case 5:
byteToWrite = 0b11111011
byteToAnd = 0b11111011
case 6:
byteToWrite = 0b11111101
byteToAnd = 0b11111101
case 7:
byteToWrite = 0b11111110
byteToAnd = 0b11111110
}
//fmt.Printf("blockNumber: $%04X byteToWrite: 0b%08b volumeBitmap: $%02X byteToChange: $%04X\n", blockNumber, byteToWrite, volumeBitmap[byteToChange], byteToChange)
volumeBitmap[byteToChange] &= byte(byteToWrite)
volumeBitmap[byteToChange] &= byte(byteToAnd)
}
func FreeBlockInVolumeBitmap(volumeBitmap []byte, blockNumber int) {
bitToChange := blockNumber % 8
byteToChange := blockNumber / 8
byteToOr := 0b00000000
switch bitToChange {
case 0:
byteToOr = 0b10000000
case 1:
byteToOr = 0b01000000
case 2:
byteToOr = 0b00100000
case 3:
byteToOr = 0b00010000
case 4:
byteToOr = 0b00001000
case 5:
byteToOr = 0b00000100
case 6:
byteToOr = 0b00000010
case 7:
byteToOr = 0b00000001
}
volumeBitmap[byteToChange] |= byte(byteToOr)
}

View File

@ -16,6 +16,8 @@ type VolumeHeader struct {
NextBlock int
EntryLength int
EntriesPerBlock int
MinVersion int
Version int
}
type DirectoryHeader struct {
@ -34,16 +36,21 @@ const (
)
type FileEntry struct {
StorageType int
FileName string
FileType int
CreationTime time.Time
StartingBlock int
BlocksUsed int
EndOfFile int
Access int
AuxType int
ModifiedTime time.Time
StorageType int
FileName string
FileType int
CreationTime time.Time
KeyPointer int
Version int
MinVersion int
BlocksUsed int
EndOfFile int
Access int
AuxType int
ModifiedTime time.Time
HeaderPointer int
DirectoryBlock int
DirectoryOffset int
}
func ReadDirectory(file *os.File, path string) (VolumeHeader, []FileEntry) {
@ -95,14 +102,14 @@ func getFileEntriesInDirectory(file *os.File, blockNumber int, currentPath int,
buffer = ReadBlock(file, nextBlock)
nextBlock = int(buffer[2]) + int(buffer[3])*256
}
fileEntry := parseFileEntry(buffer[entryOffset : entryOffset+40])
fileEntry := parseFileEntry(buffer[entryOffset:entryOffset+40], blockNumber, entryOffset)
//DumpFileEntry(fileEntry)
if fileEntry.StorageType != StorageDeleted {
if matchedDirectory {
fileEntries[activeEntries] = fileEntry
} else if !matchedDirectory && fileEntry.FileType == 15 && paths[currentPath+1] == fileEntry.FileName {
return getFileEntriesInDirectory(file, fileEntry.StartingBlock, currentPath+1, paths)
return getFileEntriesInDirectory(file, fileEntry.KeyPointer, currentPath+1, paths)
}
activeEntries++
if matchedDirectory && activeEntries == directoryHeader.ActiveFileCount {
@ -115,7 +122,7 @@ func getFileEntriesInDirectory(file *os.File, blockNumber int, currentPath int,
}
}
func parseFileEntry(buffer []byte) FileEntry {
func parseFileEntry(buffer []byte, blockNumber int, entryOffset int) FileEntry {
storageType := int(buffer[0] >> 4)
fileNameLength := int(buffer[0] & 15)
fileName := string(buffer[1 : fileNameLength+1])
@ -124,26 +131,70 @@ func parseFileEntry(buffer []byte) FileEntry {
blocksUsed := int(buffer[19]) + int(buffer[20])*256
endOfFile := int(buffer[21]) + int(buffer[22])*256 + int(buffer[23])*65536
creationTime := DateTimeFromProDOS(buffer[24:28])
version := int(buffer[28])
minVersion := int(buffer[29])
access := int(buffer[30])
auxType := int(buffer[31]) + int(buffer[32])*256
modifiedTime := DateTimeFromProDOS((buffer[33:37]))
headerPointer := int(buffer[0x25]) + int(buffer[0x26])*256
fileEntry := FileEntry{
StorageType: storageType,
FileName: fileName,
FileType: fileType,
CreationTime: creationTime,
StartingBlock: startingBlock,
BlocksUsed: blocksUsed,
EndOfFile: endOfFile,
Access: access,
AuxType: auxType,
ModifiedTime: modifiedTime,
StorageType: storageType,
FileName: fileName,
FileType: fileType,
CreationTime: creationTime,
Version: version,
MinVersion: minVersion,
KeyPointer: startingBlock,
BlocksUsed: blocksUsed,
EndOfFile: endOfFile,
Access: access,
AuxType: auxType,
ModifiedTime: modifiedTime,
HeaderPointer: headerPointer,
DirectoryBlock: blockNumber,
DirectoryOffset: entryOffset,
}
return fileEntry
}
func writeFileEntry(file *os.File, fileEntry FileEntry) {
buffer := make([]byte, 39)
buffer[0] = byte(fileEntry.StorageType)<<4 + byte(len(fileEntry.FileName))
for i := 0; i < len(fileEntry.FileName); i++ {
buffer[i+1] = fileEntry.FileName[i]
}
buffer[0x10] = byte(fileEntry.FileType)
buffer[0x11] = byte(fileEntry.KeyPointer & 0xFF)
buffer[0x12] = byte(fileEntry.KeyPointer >> 8)
buffer[0x13] = byte(fileEntry.BlocksUsed & 0xFF)
buffer[0x14] = byte(fileEntry.BlocksUsed >> 8)
buffer[0x15] = byte(fileEntry.EndOfFile & 0x0000FF)
buffer[0x16] = byte(fileEntry.EndOfFile & 0x00FF00 >> 8)
buffer[0x17] = byte(fileEntry.EndOfFile & 0xFF0000 >> 16)
creationTime := DateTimeToProDOS(fileEntry.CreationTime)
for i := 0; i < 4; i++ {
buffer[0x18+i] = creationTime[i]
}
buffer[0x1C] = byte(fileEntry.Version)
buffer[0x1D] = byte(fileEntry.MinVersion)
buffer[0x1E] = byte(fileEntry.Access)
buffer[0x1F] = byte(fileEntry.AuxType & 0x00FF)
buffer[0x20] = byte(fileEntry.AuxType >> 8)
modifiedTime := DateTimeToProDOS(fileEntry.CreationTime)
for i := 0; i < 4; i++ {
buffer[0x21+i] = modifiedTime[i]
}
buffer[0x25] = byte(fileEntry.HeaderPointer & 0x00FF)
buffer[0x26] = byte(fileEntry.HeaderPointer >> 8)
_, err := file.WriteAt(buffer, int64(fileEntry.DirectoryBlock*512+fileEntry.DirectoryOffset))
if err != nil {
}
}
func parseVolumeHeader(buffer []byte) VolumeHeader {
nextBlock := int(buffer[2]) + int(buffer[3])*256
filenameLength := buffer[4] & 15
@ -170,15 +221,17 @@ func parseVolumeHeader(buffer []byte) VolumeHeader {
NextBlock: nextBlock,
EntriesPerBlock: entriesPerBlock,
EntryLength: entryLength,
MinVersion: minVersion,
Version: version,
}
return volumeHeader
}
func parseDirectoryHeader(buffer []byte) DirectoryHeader {
nextBlock := int(buffer[2]) + int(buffer[3])*256
filenameLength := buffer[4] & 15
name := string(buffer[5 : filenameLength+5])
fileCount := int(buffer[37]) + int(buffer[38])*256
nextBlock := int(buffer[0x02]) + int(buffer[0x03])*256
filenameLength := buffer[0x04] & 15
name := string(buffer[0x05 : filenameLength+0x05])
fileCount := int(buffer[0x25]) + int(buffer[0x26])*256
directoryEntry := DirectoryHeader{
NextBlock: nextBlock,
@ -188,3 +241,16 @@ func parseDirectoryHeader(buffer []byte) DirectoryHeader {
return directoryEntry
}
func writeDirectoryHeader(file *os.File, directoryHeader DirectoryHeader, blockNumber int) {
buffer := ReadBlock(file, blockNumber)
buffer[0x02] = byte(directoryHeader.NextBlock & 0x00FF)
buffer[0x03] = byte(directoryHeader.NextBlock >> 8)
buffer[0x04] = buffer[0x04] | byte(len(directoryHeader.Name))
for i := 0; i < len(directoryHeader.Name); i++ {
buffer[0x05+i] = directoryHeader.Name[i]
}
buffer[0x25] = byte(directoryHeader.ActiveFileCount & 0x00FF)
buffer[0x26] = byte(directoryHeader.ActiveFileCount >> 8)
file.WriteAt(buffer, int64(blockNumber*512))
}

View File

@ -6,6 +6,47 @@ import (
)
func LoadFile(file *os.File, path string) []byte {
fileEntry := GetFileEntry(file, path)
blockList := GetBlocklist(file, fileEntry)
buffer := make([]byte, fileEntry.EndOfFile)
for i := 0; i < len(blockList); i++ {
block := ReadBlock(file, blockList[i])
for j := 0; j < 512 && i*512+j < fileEntry.EndOfFile; j++ {
buffer[i*512+j] = block[j]
}
}
return buffer
}
func GetBlocklist(file *os.File, fileEntry FileEntry) []int {
blocks := make([]int, fileEntry.BlocksUsed)
switch fileEntry.StorageType {
case StorageSeedling:
blocks[0] = fileEntry.KeyPointer
case StorageSapling:
index := ReadBlock(file, fileEntry.KeyPointer)
for i := 0; i < fileEntry.BlocksUsed-1; i++ {
blocks[i] = int(index[i]) + int(index[i+256])*256
}
case StorageTree:
masterIndex := ReadBlock(file, fileEntry.KeyPointer)
for i := 0; i < 128; i++ {
index := ReadBlock(file, int(masterIndex[i])+int(masterIndex[i+256])*256)
for j := 0; j < 256 && i*256+j < fileEntry.BlocksUsed; j++ {
blocks[i*256+j] = int(index[j]) + int(index[j+256])*256
}
}
}
return blocks
}
func GetFileEntry(file *os.File, path string) FileEntry {
path = strings.ToUpper(path)
paths := strings.Split(path, "/")
@ -22,7 +63,7 @@ func LoadFile(file *os.File, path string) []byte {
_, fileEntries := ReadDirectory(file, directory)
if fileEntries == nil {
return nil
return FileEntry{}
}
var fileEntry FileEntry
@ -33,22 +74,29 @@ func LoadFile(file *os.File, path string) []byte {
}
}
switch fileEntry.StorageType {
case StorageSeedling:
return ReadBlock(file, fileEntry.StartingBlock)[0:fileEntry.EndOfFile]
case StorageSapling:
index := ReadBlock(file, fileEntry.StartingBlock)
buffer := make([]byte, fileEntry.EndOfFile)
for i := 0; i < 512 && index[i] > 0; i++ {
chunk := ReadBlock(file, int(index[i])+int(index[i+256])*256)
for j := i * 512; j < fileEntry.EndOfFile && j < i*512+512; j++ {
buffer[j] = chunk[j-i*512]
}
}
return buffer
case StorageTree:
// add tree file support later
return nil
}
return nil
return fileEntry
}
func DeleteFile(file *os.File, path string) {
fileEntry := GetFileEntry(file, path)
// free the blocks
blocks := GetBlocklist(file, fileEntry)
volumeBitmap := ReadVolumeBitmap(file)
for i := 0; i < len(blocks); i++ {
FreeBlockInVolumeBitmap(volumeBitmap, blocks[i])
}
WriteVolumeBitmap(file, volumeBitmap)
// zero out directory entry
fileEntry.StorageType = 0
fileEntry.FileName = ""
writeFileEntry(file, fileEntry)
// decrement the directory entry count
directoryBlock := ReadBlock(file, fileEntry.HeaderPointer)
directoryHeader := parseDirectoryHeader(directoryBlock)
directoryHeader.ActiveFileCount--
writeDirectoryHeader(file, directoryHeader, fileEntry.HeaderPointer)
}

View File

@ -85,9 +85,10 @@ func DumpFileEntry(fileEntry FileEntry) {
fmt.Printf("AuxType: %04X\n", fileEntry.AuxType)
fmt.Printf("EOF: %06X\n", fileEntry.EndOfFile)
fmt.Printf("Blocks used: %04X\n", fileEntry.BlocksUsed)
fmt.Printf("Starting block: %04X\n", fileEntry.StartingBlock)
fmt.Printf("Starting block: %04X\n", fileEntry.KeyPointer)
fmt.Printf("File type: %02X\n", fileEntry.FileType)
fmt.Printf("Storage type: %02X\n", fileEntry.StorageType)
fmt.Printf("Header pointer: %04X\n", fileEntry.HeaderPointer)
fmt.Printf("\n")
}
@ -95,8 +96,8 @@ func DumpVolumeHeader(volumeHeader VolumeHeader) {
fmt.Printf("Next block: %d\n", volumeHeader.NextBlock)
fmt.Printf("Volume name: %s\n", volumeHeader.VolumeName)
fmt.Printf("Creation time: %d-%s-%d %02d:%02d\n", volumeHeader.CreationTime.Year(), volumeHeader.CreationTime.Month(), volumeHeader.CreationTime.Day(), volumeHeader.CreationTime.Hour(), volumeHeader.CreationTime.Minute())
// fmt.Printf("ProDOS version (should be 0): %d\n", volumeHeader.Version)
// fmt.Printf("ProDOS mininum version (should be 0): %d\n", minVersion)
fmt.Printf("ProDOS version (should be 0): %d\n", volumeHeader.Version)
fmt.Printf("ProDOS mininum version (should be 0): %d\n", volumeHeader.MinVersion)
fmt.Printf("Entry length (should be 39): %d\n", volumeHeader.EntryLength)
fmt.Printf("Entries per block (should be 13): %d\n", volumeHeader.EntriesPerBlock)
fmt.Printf("File count: %d\n", volumeHeader.ActiveFileCount)