Add putallrecursive

This commit is contained in:
Terence Boldt 2023-01-22 10:01:57 -05:00
parent 1722e7b23a
commit 286964d3ee
7 changed files with 242 additions and 55 deletions

24
main.go
View File

@ -32,7 +32,7 @@ func main() {
var auxType int var auxType int
flag.StringVar(&fileName, "d", "", "A ProDOS format drive image") flag.StringVar(&fileName, "d", "", "A ProDOS format drive image")
flag.StringVar(&pathName, "p", "", "Path name in ProDOS drive image (default is root of volume)") flag.StringVar(&pathName, "p", "", "Path name in ProDOS drive image (default is root of volume)")
flag.StringVar(&command, "c", "ls", "Command to execute: ls, get, put, rm, mkdir, readblock, writeblock, create, putall") flag.StringVar(&command, "c", "ls", "Command to execute: ls, get, put, rm, mkdir, readblock, writeblock, create, putall, putallrecursive")
flag.StringVar(&outFileName, "o", "", "Name of file to write") flag.StringVar(&outFileName, "o", "", "Name of file to write")
flag.StringVar(&inFileName, "i", "", "Name of file to read") flag.StringVar(&inFileName, "i", "", "Name of file to read")
flag.IntVar(&volumeSize, "s", 65535, "Number of blocks to create the volume with (default 65535, 64 to 65535, 0x0040 to 0xFFFF hex input accepted)") flag.IntVar(&volumeSize, "s", 65535, "Number of blocks to create the volume with (default 65535, 64 to 65535, 0x0040 to 0xFFFF hex input accepted)")
@ -62,13 +62,17 @@ func main() {
case "create": case "create":
create(fileName, volumeName, volumeSize) create(fileName, volumeName, volumeSize)
case "putall": case "putall":
putall(fileName, inFileName) putall(fileName, inFileName, pathName, false)
case "putallrecursive":
putall(fileName, inFileName, pathName, true)
case "rm": case "rm":
rm(fileName, pathName) rm(fileName, pathName)
case "mkdir": case "mkdir":
mkdir(fileName, pathName) mkdir(fileName, pathName)
case "dumpfile": case "dumpfile":
dumpFile(fileName, pathName) dumpFile(fileName, pathName)
case "dumpdirectory":
dumpDirectory(fileName, pathName)
default: default:
fmt.Printf("Invalid command: %s\n\n", command) fmt.Printf("Invalid command: %s\n\n", command)
flag.PrintDefaults() flag.PrintDefaults()
@ -88,6 +92,18 @@ func dumpFile(fileName string, pathName string) {
prodos.DumpFileEntry(fileEntry) prodos.DumpFileEntry(fileEntry)
} }
func dumpDirectory(fileName string, pathName string) {
checkPathName(pathName)
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()
_, directoryheader, _, err := prodos.ReadDirectory(file, pathName)
prodos.DumpDirectoryHeader(directoryheader)
}
func mkdir(fileName string, pathName string) { func mkdir(fileName string, pathName string) {
checkPathName(pathName) checkPathName(pathName)
file, err := os.OpenFile(fileName, os.O_RDWR, 0755) file, err := os.OpenFile(fileName, os.O_RDWR, 0755)
@ -114,7 +130,7 @@ func rm(fileName string, pathName string) {
prodos.DeleteFile(file, pathName) prodos.DeleteFile(file, pathName)
} }
func putall(fileName string, inFileName string) { func putall(fileName string, inFileName string, pathName string, recursive bool) {
if len(inFileName) == 0 { if len(inFileName) == 0 {
inFileName = "." inFileName = "."
} }
@ -124,7 +140,7 @@ func putall(fileName string, inFileName string) {
os.Exit(1) os.Exit(1)
} }
defer file.Close() defer file.Close()
err = prodos.AddFilesFromHostDirectory(file, inFileName) err = prodos.AddFilesFromHostDirectory(file, inFileName, pathName, recursive)
if err != nil { if err != nil {
fmt.Printf("failed to add host files: %s\n", err) fmt.Printf("failed to add host files: %s\n", err)
os.Exit(1) os.Exit(1)

View File

@ -131,7 +131,6 @@ func createVolumeBitmap(numberOfBlocks int) []byte {
markBlockInVolumeBitmap(volumeBitmap, i) markBlockInVolumeBitmap(volumeBitmap, i)
} }
} }
//DumpBlock(volumeBitmap)
return volumeBitmap return volumeBitmap
} }

View File

@ -33,6 +33,7 @@ type VolumeHeader struct {
type DirectoryHeader struct { type DirectoryHeader struct {
PreviousBlock int PreviousBlock int
NextBlock int NextBlock int
IsSubDirectory bool
Name string Name string
CreationTime time.Time CreationTime time.Time
Version int Version int
@ -64,19 +65,20 @@ const (
// FileEntry from ProDOS // FileEntry from ProDOS
type FileEntry struct { type FileEntry struct {
StorageType int StorageType int
FileName string FileName string
FileType int FileType int
CreationTime time.Time CreationTime time.Time
KeyPointer int KeyPointer int
Version int Version int
MinVersion int MinVersion int
BlocksUsed int BlocksUsed int
EndOfFile int EndOfFile int
Access int Access int
AuxType int AuxType int
ModifiedTime time.Time ModifiedTime time.Time
HeaderPointer int HeaderPointer int
DirectoryBlock int DirectoryBlock int
DirectoryOffset int DirectoryOffset int
} }
@ -95,6 +97,11 @@ func ReadDirectory(reader io.ReaderAt, path string) (VolumeHeader, DirectoryHead
path = fmt.Sprintf("/%s", volumeHeader.VolumeName) path = fmt.Sprintf("/%s", volumeHeader.VolumeName)
} }
// add volume name if not full path
if !strings.HasPrefix(path, "/") {
path = fmt.Sprintf("/%s/%s", volumeHeader.VolumeName, path)
}
path = strings.ToUpper(path) path = strings.ToUpper(path)
paths := strings.Split(path, "/") paths := strings.Split(path, "/")
@ -106,12 +113,23 @@ func ReadDirectory(reader io.ReaderAt, path string) (VolumeHeader, DirectoryHead
return volumeHeader, directoryHeader, fileEntries, nil return volumeHeader, directoryHeader, fileEntries, nil
} }
// CreateDirectory creates a directory information of a specified path
// on a ProDOS image
func CreateDirectory(readerWriter ReaderWriterAt, path string) error { func CreateDirectory(readerWriter ReaderWriterAt, path string) error {
if len(path) == 0 {
return errors.New("cannot create directory with path")
}
// add volume name if not full path
path, err := makeFullPath(path, readerWriter)
if err != nil {
return err
}
parentPath, newDirectory := GetDirectoryAndFileNameFromPath(path) parentPath, newDirectory := GetDirectoryAndFileNameFromPath(path)
existingFileEntry, _ := GetFileEntry(readerWriter, path) existingFileEntry, _ := GetFileEntry(readerWriter, path)
if existingFileEntry.StorageType != StorageDeleted { if existingFileEntry.StorageType != StorageDeleted {
//DeleteFile(readerWriter, path)
return errors.New("directory already exists") return errors.New("directory already exists")
} }
@ -140,6 +158,8 @@ func CreateDirectory(readerWriter ReaderWriterAt, path string) error {
fileEntry.KeyPointer = blockList[0] fileEntry.KeyPointer = blockList[0]
fileEntry.Access = 0b11100011 fileEntry.Access = 0b11100011
fileEntry.StorageType = StorageDirectory fileEntry.StorageType = StorageDirectory
fileEntry.Version = 0x24
fileEntry.MinVersion = 0x00
writeFileEntry(readerWriter, fileEntry) writeFileEntry(readerWriter, fileEntry)
@ -152,6 +172,7 @@ func CreateDirectory(readerWriter ReaderWriterAt, path string) error {
directoryEntry := DirectoryHeader{ directoryEntry := DirectoryHeader{
PreviousBlock: 0, PreviousBlock: 0,
NextBlock: 0, NextBlock: 0,
IsSubDirectory: true,
Name: newDirectory, Name: newDirectory,
CreationTime: time.Now(), CreationTime: time.Now(),
Version: 0x24, Version: 0x24,
@ -162,7 +183,7 @@ func CreateDirectory(readerWriter ReaderWriterAt, path string) error {
ActiveFileCount: 0, ActiveFileCount: 0,
StartingBlock: blockList[0], StartingBlock: blockList[0],
ParentBlock: fileEntry.DirectoryBlock, ParentBlock: fileEntry.DirectoryBlock,
ParentEntry: fileEntry.DirectoryOffset, ParentEntry: (fileEntry.DirectoryOffset - 0x04) / 0x27,
ParentEntryLength: 0x27, ParentEntryLength: 0x27,
} }
@ -175,14 +196,26 @@ func CreateDirectory(readerWriter ReaderWriterAt, path string) error {
return nil return nil
} }
func getFreeFileEntryInDirectory(reader io.ReaderAt, directory string) (FileEntry, error) { func makeFullPath(path string, reader io.ReaderAt) (string, error) {
_, directoryHeader, _, err := ReadDirectory(reader, directory) if !strings.HasPrefix(path, "/") {
buffer, err := ReadBlock(reader, 0x0002)
if err != nil {
return "", err
}
volumeHeader := parseVolumeHeader(buffer)
path = fmt.Sprintf("/%s/%s", volumeHeader.VolumeName, path)
}
return path, nil
}
func getFreeFileEntryInDirectory(readerWriter ReaderWriterAt, directory string) (FileEntry, error) {
_, directoryHeader, _, err := ReadDirectory(readerWriter, directory)
if err != nil { if err != nil {
return FileEntry{}, err return FileEntry{}, err
} }
//DumpDirectoryHeader(directoryHeader)
blockNumber := directoryHeader.StartingBlock blockNumber := directoryHeader.StartingBlock
buffer, err := ReadBlock(reader, blockNumber) buffer, err := ReadBlock(readerWriter, blockNumber)
if err != nil { if err != nil {
return FileEntry{}, err return FileEntry{}, err
} }
@ -191,21 +224,28 @@ func getFreeFileEntryInDirectory(reader io.ReaderAt, directory string) (FileEntr
for { for {
if entryNumber > 13 { if entryNumber > 13 {
blockNumber = int(buffer[2]) + int(buffer[3])*256 nextBlockNumber := int(buffer[2]) + int(buffer[3])*256
// if we ran out of blocks in the directory, return empty // if we ran out of blocks in the directory, expand directory or fail
// TODO: expand the directory to add more entries if nextBlockNumber == 0 {
if blockNumber == 0 { if !directoryHeader.IsSubDirectory {
return FileEntry{}, errors.New("no free file entries found") return FileEntry{}, errors.New("no free file entries found")
}
nextBlockNumber, err = expandDirectory(readerWriter, nextBlockNumber, buffer, blockNumber, directoryHeader)
if err != nil {
return FileEntry{}, err
}
} }
blockNumber = nextBlockNumber
// else read the next block in the directory // else read the next block in the directory
buffer, err = ReadBlock(reader, blockNumber) buffer, err = ReadBlock(readerWriter, blockNumber)
if err != nil { if err != nil {
return FileEntry{}, nil return FileEntry{}, nil
} }
entryOffset = 4 entryOffset = 4
entryNumber = 1 entryNumber = 1
} }
fileEntry := parseFileEntry(buffer[entryOffset:entryOffset+40], blockNumber, entryOffset) fileEntry := parseFileEntry(buffer[entryOffset:entryOffset+0x28], blockNumber, entryOffset)
if fileEntry.StorageType == StorageDeleted { if fileEntry.StorageType == StorageDeleted {
fileEntry.DirectoryBlock = blockNumber fileEntry.DirectoryBlock = blockNumber
@ -219,6 +259,51 @@ func getFreeFileEntryInDirectory(reader io.ReaderAt, directory string) (FileEntr
} }
} }
func expandDirectory(readerWriter ReaderWriterAt, nextBlockNumber int, buffer []byte, blockNumber int, directoryHeader DirectoryHeader) (int, error) {
volumeBitMap, err := ReadVolumeBitmap(readerWriter)
if err != nil {
errString := fmt.Sprintf("failed to get volume bitmap to expand directory: %s", err)
return 0, errors.New(errString)
}
blockList := findFreeBlocks(volumeBitMap, 1)
if len(blockList) != 1 {
return 0, errors.New("failed to get free block to expand directory")
}
nextBlockNumber = blockList[0]
buffer[0x02] = byte(nextBlockNumber & 0x00FF)
buffer[0x03] = byte(nextBlockNumber >> 8)
WriteBlock(readerWriter, blockNumber, buffer)
if err != nil {
errString := fmt.Sprintf("failed to write block to expand directory: %s", err)
return 0, errors.New(errString)
}
buffer = make([]byte, 0x200)
buffer[0x00] = byte(blockNumber & 0x00FF)
buffer[0x01] = byte(blockNumber >> 8)
err = WriteBlock(readerWriter, nextBlockNumber, buffer)
if err != nil {
errString := fmt.Sprintf("failed to write new block to expand directory: %s", err)
return 0, errors.New(errString)
}
updateVolumeBitmap(readerWriter, blockList)
buffer, err = ReadBlock(readerWriter, directoryHeader.ParentBlock)
if err != nil {
errString := fmt.Sprintf("failed to read parent block to expand directory: %s", err)
return 0, errors.New(errString)
}
directoryEntryOffset := directoryHeader.ParentEntry*directoryHeader.EntryLength + 0x04
directoryFileEntry := parseFileEntry(buffer[directoryEntryOffset:directoryEntryOffset+0x28], directoryHeader.ParentBlock, directoryHeader.ParentEntry*directoryHeader.EntryLength+0x04)
directoryFileEntry.BlocksUsed++
directoryFileEntry.EndOfFile += 0x200
writeFileEntry(readerWriter, directoryFileEntry)
return nextBlockNumber, nil
}
func getFileEntriesInDirectory(reader io.ReaderAt, blockNumber int, currentPath int, paths []string) (DirectoryHeader, []FileEntry, error) { func getFileEntriesInDirectory(reader io.ReaderAt, blockNumber int, currentPath int, paths []string) (DirectoryHeader, []FileEntry, error) {
buffer, err := ReadBlock(reader, blockNumber) buffer, err := ReadBlock(reader, blockNumber)
if err != nil { if err != nil {
@ -333,13 +418,14 @@ func writeFileEntry(writer io.WriterAt, fileEntry FileEntry) {
buffer[0x1E] = byte(fileEntry.Access) buffer[0x1E] = byte(fileEntry.Access)
buffer[0x1F] = byte(fileEntry.AuxType & 0x00FF) buffer[0x1F] = byte(fileEntry.AuxType & 0x00FF)
buffer[0x20] = byte(fileEntry.AuxType >> 8) buffer[0x20] = byte(fileEntry.AuxType >> 8)
modifiedTime := DateTimeToProDOS(fileEntry.CreationTime) modifiedTime := DateTimeToProDOS(fileEntry.ModifiedTime)
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
buffer[0x21+i] = modifiedTime[i] buffer[0x21+i] = modifiedTime[i]
} }
buffer[0x25] = byte(fileEntry.HeaderPointer & 0x00FF) buffer[0x25] = byte(fileEntry.HeaderPointer & 0x00FF)
buffer[0x26] = byte(fileEntry.HeaderPointer >> 8) buffer[0x26] = byte(fileEntry.HeaderPointer >> 8)
//fmt.Printf("Writing file entry at block: %04X offset: %04X\n", fileEntry.DirectoryBlock, fileEntry.DirectoryOffset)
_, err := writer.WriteAt(buffer, int64(fileEntry.DirectoryBlock*512+fileEntry.DirectoryOffset)) _, err := writer.WriteAt(buffer, int64(fileEntry.DirectoryBlock*512+fileEntry.DirectoryOffset))
if err != nil { if err != nil {
@ -381,7 +467,8 @@ func parseVolumeHeader(buffer []byte) VolumeHeader {
func parseDirectoryHeader(buffer []byte, blockNumber int) DirectoryHeader { func parseDirectoryHeader(buffer []byte, blockNumber int) DirectoryHeader {
previousBlock := int(buffer[0x00]) + int(buffer[0x01])*256 previousBlock := int(buffer[0x00]) + int(buffer[0x01])*256
nextBlock := int(buffer[0x02]) + int(buffer[0x03])*256 nextBlock := int(buffer[0x02]) + int(buffer[0x03])*256
filenameLength := buffer[0x04] & 15 isSubDirectory := (buffer[0x04] & 0xF0) == 0xE0
filenameLength := buffer[0x04] & 0x0F
name := string(buffer[0x05 : filenameLength+0x05]) name := string(buffer[0x05 : filenameLength+0x05])
creationTime := DateTimeFromProDOS(buffer[0x1C:0x20]) creationTime := DateTimeFromProDOS(buffer[0x1C:0x20])
version := int(buffer[0x20]) version := int(buffer[0x20])
@ -398,6 +485,7 @@ func parseDirectoryHeader(buffer []byte, blockNumber int) DirectoryHeader {
PreviousBlock: previousBlock, PreviousBlock: previousBlock,
NextBlock: nextBlock, NextBlock: nextBlock,
StartingBlock: blockNumber, StartingBlock: blockNumber,
IsSubDirectory: isSubDirectory,
Name: name, Name: name,
CreationTime: creationTime, CreationTime: creationTime,
Version: version, Version: version,
@ -415,6 +503,7 @@ func parseDirectoryHeader(buffer []byte, blockNumber int) DirectoryHeader {
} }
func writeDirectoryHeader(readerWriter ReaderWriterAt, directoryHeader DirectoryHeader) error { func writeDirectoryHeader(readerWriter ReaderWriterAt, directoryHeader DirectoryHeader) error {
// Reading back the block preserves values including reserved fields
buffer, err := ReadBlock(readerWriter, directoryHeader.StartingBlock) buffer, err := ReadBlock(readerWriter, directoryHeader.StartingBlock)
if err != nil { if err != nil {
return err return err
@ -423,6 +512,11 @@ func writeDirectoryHeader(readerWriter ReaderWriterAt, directoryHeader Directory
buffer[0x01] = byte(directoryHeader.PreviousBlock >> 8) buffer[0x01] = byte(directoryHeader.PreviousBlock >> 8)
buffer[0x02] = byte(directoryHeader.NextBlock & 0x00FF) buffer[0x02] = byte(directoryHeader.NextBlock & 0x00FF)
buffer[0x03] = byte(directoryHeader.NextBlock >> 8) buffer[0x03] = byte(directoryHeader.NextBlock >> 8)
if directoryHeader.IsSubDirectory {
buffer[0x04] = 0xE0
} else {
buffer[0x04] = 0xF0
}
buffer[0x04] = buffer[0x04] | byte(len(directoryHeader.Name)) buffer[0x04] = buffer[0x04] | byte(len(directoryHeader.Name))
for i := 0; i < len(directoryHeader.Name); i++ { for i := 0; i < len(directoryHeader.Name); i++ {
buffer[0x05+i] = directoryHeader.Name[i] buffer[0x05+i] = directoryHeader.Name[i]
@ -431,6 +525,16 @@ func writeDirectoryHeader(readerWriter ReaderWriterAt, directoryHeader Directory
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
buffer[0x1C+i] = creationTime[i] buffer[0x1C+i] = creationTime[i]
} }
// Without these reserved bytes, reading the directory causes I/O ERROR
buffer[0x14] = 0x75
buffer[0x15] = byte(directoryHeader.Version)
buffer[0x16] = byte(directoryHeader.MinVersion)
buffer[0x17] = 0xC3
buffer[0x18] = 0x0D
buffer[0x19] = 0x27
buffer[0x1A] = 0x00
buffer[0x1B] = 0x00
buffer[0x20] = byte(directoryHeader.Version) buffer[0x20] = byte(directoryHeader.Version)
buffer[0x21] = byte(directoryHeader.MinVersion) buffer[0x21] = byte(directoryHeader.MinVersion)
buffer[0x22] = byte(directoryHeader.Access) buffer[0x22] = byte(directoryHeader.Access)

View File

@ -46,6 +46,10 @@ func LoadFile(reader io.ReaderAt, path string) ([]byte, error) {
func WriteFile(readerWriter ReaderWriterAt, path string, fileType int, auxType int, createdTime time.Time, modifiedTime time.Time, buffer []byte) error { func WriteFile(readerWriter ReaderWriterAt, path string, fileType int, auxType int, createdTime time.Time, modifiedTime time.Time, buffer []byte) error {
directory, fileName := GetDirectoryAndFileNameFromPath(path) directory, fileName := GetDirectoryAndFileNameFromPath(path)
if len(fileName) > 15 {
return errors.New("filename too long")
}
existingFileEntry, _ := GetFileEntry(readerWriter, path) existingFileEntry, _ := GetFileEntry(readerWriter, path)
if existingFileEntry.StorageType != StorageDeleted { if existingFileEntry.StorageType != StorageDeleted {
DeleteFile(readerWriter, path) DeleteFile(readerWriter, path)
@ -91,6 +95,8 @@ func WriteFile(readerWriter ReaderWriterAt, path string, fileType int, auxType i
fileEntry.EndOfFile = len(buffer) fileEntry.EndOfFile = len(buffer)
fileEntry.FileType = fileType fileEntry.FileType = fileType
fileEntry.KeyPointer = blockList[0] fileEntry.KeyPointer = blockList[0]
fileEntry.Version = 0x24
fileEntry.MinVersion = 0x00
fileEntry.Access = 0b11100011 fileEntry.Access = 0b11100011
if len(blockList) == 1 { if len(blockList) == 1 {
fileEntry.StorageType = StorageSeedling fileEntry.StorageType = StorageSeedling
@ -135,6 +141,7 @@ func DeleteFile(readerWriter ReaderWriterAt, path string) error {
if err != nil { if err != nil {
return err return err
} }
volumeBitmap, err := ReadVolumeBitmap(readerWriter) volumeBitmap, err := ReadVolumeBitmap(readerWriter)
if err != nil { if err != nil {
return err return err
@ -307,26 +314,27 @@ func getBlocklist(reader io.ReaderAt, fileEntry FileEntry, dataOnly bool) ([]int
if err != nil { if err != nil {
return nil, err return nil, err
} }
blockOffset := 0 blockOffset := 1
if !dataOnly { blocks[0] = fileEntry.KeyPointer
blocks[0] = fileEntry.KeyPointer
blockOffset = 1
}
for i := 0; i < fileEntry.BlocksUsed-1; i++ { for i := 0; i < fileEntry.BlocksUsed-1; i++ {
blocks[i+blockOffset] = int(index[i]) + int(index[i+256])*256 blocks[i+blockOffset] = int(index[i]) + int(index[i+256])*256
} }
if dataOnly {
return blocks[1:], nil
}
return blocks, nil return blocks, nil
case StorageTree: case StorageTree:
// this is actually too large
dataBlocks := make([]int, fileEntry.BlocksUsed) dataBlocks := make([]int, fileEntry.BlocksUsed)
numberOfIndexBlocks := fileEntry.BlocksUsed/256 + 1 // this is also actually too large
if fileEntry.BlocksUsed%256 != 0 { numberOfIndexBlocks := fileEntry.BlocksUsed/256 + 2
numberOfIndexBlocks++
}
indexBlocks := make([]int, numberOfIndexBlocks) indexBlocks := make([]int, numberOfIndexBlocks)
masterIndex, err := ReadBlock(reader, fileEntry.KeyPointer) masterIndex, err := ReadBlock(reader, fileEntry.KeyPointer)
if err != nil { if err != nil {
return nil, err return nil, err
} }
numberOfDataBlocks := 0
indexBlocks[0] = fileEntry.KeyPointer indexBlocks[0] = fileEntry.KeyPointer
indexBlockCount := 1 indexBlockCount := 1
@ -345,6 +353,7 @@ func getBlocklist(reader io.ReaderAt, fileEntry FileEntry, dataOnly bool) ([]int
if (int(index[j]) + int(index[j+256])*256) == 0 { if (int(index[j]) + int(index[j+256])*256) == 0 {
break break
} }
numberOfDataBlocks++
dataBlocks[i*256+j] = int(index[j]) + int(index[j+256])*256 dataBlocks[i*256+j] = int(index[j]) + int(index[j+256])*256
} }
} }
@ -353,7 +362,7 @@ func getBlocklist(reader io.ReaderAt, fileEntry FileEntry, dataOnly bool) ([]int
return dataBlocks, nil return dataBlocks, nil
} }
blocks = append(indexBlocks, dataBlocks...) blocks = append(indexBlocks[0:numberOfIndexBlocks], dataBlocks[0:numberOfDataBlocks]...)
return blocks, nil return blocks, nil
} }
@ -392,7 +401,7 @@ func createBlockList(reader io.ReaderAt, fileSize int) ([]int, error) {
blockList := findFreeBlocks(volumeBitmap, numberOfBlocks) blockList := findFreeBlocks(volumeBitmap, numberOfBlocks)
return blockList, nil return blockList[0:numberOfBlocks], nil
} }
// GetFileEntry returns a file entry for the given path // GetFileEntry returns a file entry for the given path

View File

@ -9,7 +9,7 @@ import (
"testing" "testing"
) )
func TestCreatBlocklist(t *testing.T) { func TestCreateBlocklist(t *testing.T) {
var tests = []struct { var tests = []struct {
fileSize int fileSize int
wantBlocks int wantBlocks int

View File

@ -8,6 +8,7 @@ package prodos
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
@ -19,7 +20,15 @@ import (
// from the specified host directory // from the specified host directory
func AddFilesFromHostDirectory( func AddFilesFromHostDirectory(
readerWriter ReaderWriterAt, readerWriter ReaderWriterAt,
directory string) error { directory string,
path string,
recursive bool) error {
path, err := makeFullPath(path, readerWriter)
if !strings.HasSuffix(path, "/") {
path = path + "/"
}
files, err := os.ReadDir(directory) files, err := os.ReadDir(directory)
if err != nil { if err != nil {
@ -33,37 +42,67 @@ func AddFilesFromHostDirectory(
} }
if file.Name()[0] != '.' && !file.IsDir() && info.Size() > 0 && info.Size() <= 0x1000000 { if file.Name()[0] != '.' && !file.IsDir() && info.Size() > 0 && info.Size() <= 0x1000000 {
err = WriteFileFromFile(readerWriter, "", 0, 0, info.ModTime(), filepath.Join(directory, file.Name())) err = WriteFileFromFile(readerWriter, path, 0, 0, info.ModTime(), filepath.Join(directory, file.Name()))
if err != nil { if err != nil {
return err return err
} }
} }
if file.Name()[0] != '.' && recursive && file.IsDir() {
newPath := file.Name()
if len(newPath) > 15 {
newPath = newPath[0:15]
}
newFullPath := strings.ToUpper(path + newPath)
newHostDirectory := filepath.Join(directory, file.Name())
CreateDirectory(readerWriter, newFullPath)
AddFilesFromHostDirectory(readerWriter, newHostDirectory, newFullPath+"/", recursive)
}
} }
return nil return nil
} }
// WriteFileFromFile writes a file to a ProDOS volume from a host file // WriteFileFromFile writes a file to a ProDOS volume from a host file
func WriteFileFromFile(readerWriter ReaderWriterAt, pathName string, fileType int, auxType int, modifiedTime time.Time, inFileName string) error { func WriteFileFromFile(
fmt.Printf("WriteFileFromFile: %s\n", inFileName) readerWriter ReaderWriterAt,
pathName string,
fileType int,
auxType int,
modifiedTime time.Time,
inFileName string) error {
inFile, err := os.ReadFile(inFileName) inFile, err := os.ReadFile(inFileName)
if err != nil { if err != nil {
fmt.Println("failed to read file") errString := fmt.Sprintf("write from file failed: %s", err)
return err return errors.New(errString)
} }
if auxType == 0 && fileType == 0 { if auxType == 0 && fileType == 0 {
auxType, fileType, inFile, err = convertFileByType(inFileName, inFile) auxType, fileType, inFile, err = convertFileByType(inFileName, inFile)
if err != nil { if err != nil {
fmt.Println("failed to convert file") errString := fmt.Sprintf("failed to convert file: %s", err)
return err return errors.New(errString)
} }
} }
trimExtensions := false
if len(pathName) == 0 { if len(pathName) == 0 {
_, pathName = filepath.Split(inFileName) _, pathName = filepath.Split(inFileName)
pathName = strings.ToUpper(pathName) pathName = strings.ToUpper(pathName)
trimExtensions = true
}
if strings.HasSuffix(pathName, "/") {
trimExtensions = true
_, fileName := filepath.Split(inFileName)
pathName = strings.ToUpper(pathName + fileName)
}
if trimExtensions {
ext := filepath.Ext(pathName) ext := filepath.Ext(pathName)
if len(ext) > 0 { if len(ext) > 0 {
switch ext { switch ext {
case ".SYS", ".TXT", ".BAS", ".BIN": case ".SYS", ".TXT", ".BAS", ".BIN":
@ -72,6 +111,13 @@ func WriteFileFromFile(readerWriter ReaderWriterAt, pathName string, fileType in
} }
} }
paths := strings.SplitAfter(pathName, "/")
if len(paths[len(paths)-1]) > 15 {
paths[len(paths)-1] = paths[len(paths)-1][0:15]
pathName = strings.Join(paths, "")
}
fmt.Printf("Source: %s Destination: %s\n", inFileName, pathName)
return WriteFile(readerWriter, pathName, fileType, auxType, time.Now(), modifiedTime, inFile) return WriteFile(readerWriter, pathName, fileType, auxType, time.Now(), modifiedTime, inFile)
} }

View File

@ -100,6 +100,8 @@ func DumpFileEntry(fileEntry FileEntry) {
fmt.Printf("Storage type: %02X\n", fileEntry.StorageType) fmt.Printf("Storage type: %02X\n", fileEntry.StorageType)
fmt.Printf("Header pointer: %04X\n", fileEntry.HeaderPointer) fmt.Printf("Header pointer: %04X\n", fileEntry.HeaderPointer)
fmt.Printf("Access: %04X\n", fileEntry.Access) fmt.Printf("Access: %04X\n", fileEntry.Access)
fmt.Printf("Directory block: %04X\n", fileEntry.DirectoryBlock)
fmt.Printf("Directory offset: %04X\n", fileEntry.DirectoryOffset)
fmt.Printf("\n") fmt.Printf("\n")
} }
@ -119,11 +121,22 @@ func DumpVolumeHeader(volumeHeader VolumeHeader) {
// DumpDirectoryHeader dumps the directory header as text // DumpDirectoryHeader dumps the directory header as text
func DumpDirectoryHeader(directoryHeader DirectoryHeader) { func DumpDirectoryHeader(directoryHeader DirectoryHeader) {
fmt.Printf("Name: %s\n", directoryHeader.Name)
fmt.Printf("File count: %d\n", directoryHeader.ActiveFileCount)
fmt.Printf("Starting block: %04X\n", directoryHeader.StartingBlock) fmt.Printf("Starting block: %04X\n", directoryHeader.StartingBlock)
fmt.Printf("Previous block: %04X\n", directoryHeader.PreviousBlock) fmt.Printf("Previous block: %04X\n", directoryHeader.PreviousBlock)
fmt.Printf("Next block: %04X\n", directoryHeader.NextBlock) fmt.Printf("Next block: %04X\n", directoryHeader.NextBlock)
fmt.Printf("Is subdirectory: %t\n", directoryHeader.IsSubDirectory)
fmt.Printf("Name: %s\n", directoryHeader.Name)
fmt.Printf("Creation time: %s\n", TimeToString(directoryHeader.CreationTime))
fmt.Printf("Version: %02X\n", directoryHeader.Version)
fmt.Printf("MinVersion: %02X\n", directoryHeader.MinVersion)
fmt.Printf("Access: %02X\n", directoryHeader.Access)
fmt.Printf("Entry length: %02X\n", directoryHeader.EntryLength)
fmt.Printf("Entries per block: %02X\n", directoryHeader.EntriesPerBlock)
fmt.Printf("File count: %d\n", directoryHeader.ActiveFileCount)
fmt.Printf("Active file count: %04X\n", directoryHeader.ActiveFileCount)
fmt.Printf("Parent block: %04X\n", directoryHeader.ParentBlock)
fmt.Printf("Parent entry: %02X\n", directoryHeader.ParentEntry)
fmt.Printf("Parent entry length: %02X\n", directoryHeader.ParentEntryLength)
} }
// DumpBlock dumps the block as hexadecimal and text // DumpBlock dumps the block as hexadecimal and text