ProDOS-Utilities/prodos/file.go

443 lines
12 KiB
Go
Raw Normal View History

// Copyright Terence J. Boldt (c)2021-2023
// 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
2021-06-06 12:00:20 +00:00
package prodos
import (
2021-06-30 04:04:59 +00:00
"errors"
"fmt"
"io"
2021-06-06 12:00:20 +00:00
"strings"
2021-06-26 01:15:20 +00:00
"time"
2021-06-06 12:00:20 +00:00
)
// 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)
2021-06-30 04:04:59 +00:00
if err != nil {
return nil, err
}
2021-06-12 02:43:35 +00:00
blockList, err := getDataBlocklist(reader, fileEntry)
2021-07-02 10:54:25 +00:00
if err != nil {
return nil, err
}
2021-06-12 02:43:35 +00:00
buffer := make([]byte, fileEntry.EndOfFile)
2024-03-11 13:05:23 +00:00
for i := uint16(0); i < uint16(len(blockList)); i++ {
block, err := ReadBlock(reader, blockList[i])
if err != nil {
return nil, err
}
2024-03-11 13:05:23 +00:00
for j := uint16(0); j < 512 && uint32(i)*512+uint32(j) < fileEntry.EndOfFile; j++ {
2021-06-12 02:43:35 +00:00
buffer[i*512+j] = block[j]
}
}
2021-06-30 04:04:59 +00:00
return buffer, nil
2021-06-12 02:43:35 +00:00
}
2022-01-23 22:30:18 +00:00
// WriteFile writes a file to a ProDOS volume from a byte array
2024-03-11 13:05:23 +00:00
func WriteFile(readerWriter ReaderWriterAt, path string, fileType uint8, auxType uint16, createdTime time.Time, modifiedTime time.Time, buffer []byte) error {
2021-06-26 01:15:20 +00:00
directory, fileName := GetDirectoryAndFileNameFromPath(path)
2023-01-22 15:01:57 +00:00
if len(fileName) > 15 {
return errors.New("filename too long")
}
existingFileEntry, _ := GetFileEntry(readerWriter, path)
2021-06-30 12:22:08 +00:00
if existingFileEntry.StorageType != StorageDeleted {
2023-01-22 17:02:09 +00:00
return errors.New(("file already exists"))
2021-06-30 12:22:08 +00:00
}
2021-06-26 01:15:20 +00:00
// get list of blocks to write file to
2024-03-11 13:05:23 +00:00
blockList, err := createBlockList(readerWriter, uint32(len(buffer)))
if err != nil {
return err
}
2021-06-26 01:15:20 +00:00
// seedling file
if len(buffer) <= 0x200 {
writeSeedlingFile(readerWriter, buffer, blockList)
2021-06-26 01:15:20 +00:00
}
// sapling file needs index block
if len(buffer) > 0x200 && len(buffer) <= 0x20000 {
writeSaplingFile(readerWriter, buffer, blockList)
2021-06-26 01:15:20 +00:00
}
// tree file needs master index and index blocks
if len(buffer) > 0x20000 && len(buffer) <= 0x1000000 {
writeTreeFile(readerWriter, buffer, blockList)
}
if len(buffer) > 0x1000000 {
return errors.New("files > 16MB not supported by ProDOS")
2021-06-30 12:22:08 +00:00
}
2021-06-26 01:15:20 +00:00
updateVolumeBitmap(readerWriter, blockList)
2021-06-30 04:04:59 +00:00
2021-06-26 01:15:20 +00:00
// add file entry to directory
fileEntry, err := getFreeFileEntryInDirectory(readerWriter, directory)
2021-07-02 10:54:25 +00:00
if err != nil {
return err
}
2021-06-26 01:15:20 +00:00
fileEntry.FileName = fileName
2024-03-11 13:05:23 +00:00
fileEntry.BlocksUsed = uint16(len(blockList))
fileEntry.CreationTime = createdTime
fileEntry.ModifiedTime = modifiedTime
2021-06-27 11:53:50 +00:00
fileEntry.AuxType = auxType
2024-03-11 13:05:23 +00:00
fileEntry.EndOfFile = uint32(len(buffer))
2021-06-27 11:53:50 +00:00
fileEntry.FileType = fileType
2021-06-26 01:15:20 +00:00
fileEntry.KeyPointer = blockList[0]
2023-01-22 15:01:57 +00:00
fileEntry.Version = 0x24
fileEntry.MinVersion = 0x00
2021-06-30 04:04:59 +00:00
fileEntry.Access = 0b11100011
2021-06-30 12:22:08 +00:00
if len(blockList) == 1 {
fileEntry.StorageType = StorageSeedling
} else if len(blockList) <= 257 {
fileEntry.StorageType = StorageSapling
} else {
fileEntry.StorageType = StorageTree
}
writeFileEntry(readerWriter, fileEntry)
2021-06-30 12:22:08 +00:00
2023-01-19 05:30:17 +00:00
return incrementFileCount(readerWriter, fileEntry)
}
func incrementFileCount(readerWriter ReaderWriterAt, fileEntry FileEntry) error {
directoryHeaderBlock, err := ReadBlock(readerWriter, fileEntry.HeaderPointer)
if err != nil {
return err
}
2021-06-30 12:22:08 +00:00
directoryHeader := parseDirectoryHeader(directoryHeaderBlock, fileEntry.HeaderPointer)
directoryHeader.ActiveFileCount++
writeDirectoryHeader(readerWriter, directoryHeader)
2021-06-30 12:22:08 +00:00
return nil
}
2022-01-23 22:30:18 +00:00
// DeleteFile deletes a file from a ProDOS volume
func DeleteFile(readerWriter ReaderWriterAt, path string) error {
fileEntry, err := GetFileEntry(readerWriter, path)
2023-01-22 17:02:09 +00:00
// DumpFileEntry(fileEntry)
// oldDirectoryBlock, _ := ReadBlock(readerWriter, fileEntry.DirectoryBlock)
// DumpBlock(oldDirectoryBlock)
2021-06-30 12:22:08 +00:00
if err != nil {
return errors.New("file not found")
2021-06-30 12:22:08 +00:00
}
if fileEntry.StorageType == StorageDeleted {
return errors.New("file already deleted")
2021-06-30 12:22:08 +00:00
}
2021-07-02 11:58:06 +00:00
if fileEntry.StorageType == StorageDirectory {
return errors.New("directory deletion not supported")
2021-07-02 11:58:06 +00:00
}
2021-06-30 12:22:08 +00:00
// free the blocks
blocks, err := getAllBlockList(readerWriter, fileEntry)
if err != nil {
return err
}
2023-01-22 15:01:57 +00:00
volumeBitmap, err := ReadVolumeBitmap(readerWriter)
2021-07-02 10:54:25 +00:00
if err != nil {
return err
}
2021-06-30 12:22:08 +00:00
for i := 0; i < len(blocks); i++ {
freeBlockInVolumeBitmap(volumeBitmap, blocks[i])
}
writeVolumeBitmap(readerWriter, volumeBitmap)
2021-06-30 12:22:08 +00:00
// decrement the directory entry count
directoryBlock, err := ReadBlock(readerWriter, fileEntry.HeaderPointer)
if err != nil {
return err
}
2021-06-30 12:22:08 +00:00
directoryHeader := parseDirectoryHeader(directoryBlock, fileEntry.HeaderPointer)
2021-06-26 01:15:20 +00:00
2021-06-30 12:22:08 +00:00
directoryHeader.ActiveFileCount--
writeDirectoryHeader(readerWriter, directoryHeader)
2021-06-30 12:22:08 +00:00
// zero out directory entry
fileEntry.StorageType = 0
fileEntry.FileName = ""
writeFileEntry(readerWriter, fileEntry)
2021-06-30 04:04:59 +00:00
return nil
2021-06-26 01:15:20 +00:00
}
2023-01-22 17:02:09 +00:00
// FileExists return true if the file exists
func FileExists(reader io.ReaderAt, path string) (bool, error) {
fileEntry, _ := GetFileEntry(reader, path)
return fileEntry.StorageType != StorageDeleted, nil
}
2022-01-23 22:30:18 +00:00
// GetDirectoryAndFileNameFromPath gets the directory and filename from a path
2021-07-02 10:54:25 +00:00
func GetDirectoryAndFileNameFromPath(path string) (string, string) {
path = strings.ToUpper(path)
paths := strings.Split(path, "/")
var directoryBuilder strings.Builder
for i := 1; i < len(paths)-1; i++ {
directoryBuilder.WriteString("/")
directoryBuilder.WriteString(paths[i])
}
directory := directoryBuilder.String()
fileName := paths[len(paths)-1]
return directory, fileName
}
2024-03-11 13:05:23 +00:00
func updateVolumeBitmap(readerWriter ReaderWriterAt, blockList []uint16) error {
volumeBitmap, err := ReadVolumeBitmap(readerWriter)
if err != nil {
fmt.Printf("%s", err)
return err
}
2024-03-11 13:05:23 +00:00
for i := uint16(0); i < uint16(len(blockList)); i++ {
markBlockInVolumeBitmap(volumeBitmap, blockList[i])
}
return writeVolumeBitmap(readerWriter, volumeBitmap)
}
2024-03-11 13:05:23 +00:00
func writeSeedlingFile(writer io.WriterAt, buffer []byte, blockList []uint16) {
WriteBlock(writer, blockList[0], buffer)
}
2024-03-11 13:05:23 +00:00
func writeSaplingFile(writer io.WriterAt, buffer []byte, blockList []uint16) {
// 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)
} else {
indexBuffer[i] = 0
indexBuffer[i+256] = 0
}
}
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++
}
}
}
2024-03-11 13:05:23 +00:00
func writeTreeFile(writer io.WriterAt, buffer []byte, blockList []uint16) {
// write master index block with pointers to index blocks
indexBuffer := make([]byte, 512)
numberOfIndexBlocks := len(blockList)/256 + 1
if len(blockList)%256 == 0 {
numberOfIndexBlocks--
}
for i := 0; i < 256; i++ {
if i < numberOfIndexBlocks {
indexBuffer[i] = byte(blockList[i+1] & 0x00FF)
indexBuffer[i+256] = byte(blockList[i+1] >> 8)
} else {
indexBuffer[i] = 0
indexBuffer[i+256] = 0
}
}
WriteBlock(writer, blockList[0], indexBuffer)
numberOfIndexBlocks++
// write index blocks
for i := 0; i < len(blockList)/256+1; i++ {
for j := 0; j < 256; j++ {
if i*256+j < len(blockList)-numberOfIndexBlocks {
indexBuffer[j] = byte(blockList[i*256+numberOfIndexBlocks+j] & 0x00FF)
indexBuffer[j+256] = byte(blockList[i*256+j+2] >> 8)
} else {
indexBuffer[j] = 0
indexBuffer[j+256] = 0
}
}
WriteBlock(writer, blockList[i+1], indexBuffer)
}
// write all data blocks
blockBuffer := make([]byte, 512)
blockPointer := 0
blockIndexNumber := numberOfIndexBlocks
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++
}
}
}
2024-03-11 13:05:23 +00:00
func getDataBlocklist(reader io.ReaderAt, fileEntry FileEntry) ([]uint16, error) {
return getBlocklist(reader, fileEntry, true)
}
2024-03-11 13:05:23 +00:00
func getAllBlockList(reader io.ReaderAt, fileEntry FileEntry) ([]uint16, error) {
return getBlocklist(reader, fileEntry, false)
}
2021-07-02 10:54:25 +00:00
// Returns all blocks, including index blocks
2024-03-11 13:05:23 +00:00
func getBlocklist(reader io.ReaderAt, fileEntry FileEntry, dataOnly bool) ([]uint16, error) {
blocks := make([]uint16, fileEntry.BlocksUsed)
2021-06-12 02:43:35 +00:00
switch fileEntry.StorageType {
case StorageSeedling:
blocks[0] = fileEntry.KeyPointer
2021-07-02 10:54:25 +00:00
return blocks, nil
2021-06-12 02:43:35 +00:00
case StorageSapling:
index, err := ReadBlock(reader, fileEntry.KeyPointer)
if err != nil {
return nil, err
}
2024-03-11 13:05:23 +00:00
blockOffset := uint16(1)
2023-01-22 15:01:57 +00:00
blocks[0] = fileEntry.KeyPointer
2024-03-11 13:05:23 +00:00
for i := uint16(0); i < fileEntry.BlocksUsed-1; i++ {
blocks[i+blockOffset] = uint16(index[i]) + uint16(index[i+256])*256
2021-06-12 02:43:35 +00:00
}
2023-01-22 15:01:57 +00:00
if dataOnly {
return blocks[1:], nil
}
2021-07-02 10:54:25 +00:00
return blocks, nil
2021-06-12 02:43:35 +00:00
case StorageTree:
2023-01-22 15:01:57 +00:00
// this is actually too large
2024-03-11 13:05:23 +00:00
dataBlocks := make([]uint16, fileEntry.BlocksUsed)
2023-01-22 15:01:57 +00:00
// this is also actually too large
numberOfIndexBlocks := fileEntry.BlocksUsed/256 + 2
2024-03-11 13:05:23 +00:00
indexBlocks := make([]uint16, numberOfIndexBlocks)
masterIndex, err := ReadBlock(reader, fileEntry.KeyPointer)
if err != nil {
return nil, err
}
2023-01-22 15:01:57 +00:00
numberOfDataBlocks := 0
indexBlocks[0] = fileEntry.KeyPointer
indexBlockCount := 1
2024-03-11 13:05:23 +00:00
for i := uint16(0); i < 128; i++ {
indexBlock := uint16(masterIndex[i]) + uint16(masterIndex[i+256])*256
if indexBlock == 0 {
break
}
indexBlocks[indexBlockCount] = indexBlock
indexBlockCount++
index, err := ReadBlock(reader, indexBlock)
if err != nil {
return nil, err
}
2024-03-11 13:05:23 +00:00
for j := uint16(0); j < 256 && i*256+j < fileEntry.BlocksUsed; j++ {
if (uint16(index[j]) + uint16(index[j+256])*256) == 0 {
break
2021-07-02 10:54:25 +00:00
}
2023-01-22 15:01:57 +00:00
numberOfDataBlocks++
2024-03-11 13:05:23 +00:00
dataBlocks[i*256+j] = uint16(index[j]) + uint16(index[j+256])*256
2021-06-12 02:43:35 +00:00
}
}
if dataOnly {
return dataBlocks, nil
2021-07-02 10:54:25 +00:00
}
2023-01-22 15:01:57 +00:00
blocks = append(indexBlocks[0:numberOfIndexBlocks], dataBlocks[0:numberOfDataBlocks]...)
return blocks, nil
2021-07-02 10:54:25 +00:00
}
return nil, errors.New("unsupported file storage type")
2021-06-12 02:43:35 +00:00
}
2024-03-11 13:05:23 +00:00
func createBlockList(reader io.ReaderAt, fileSize uint32) ([]uint16, error) {
numberOfBlocks := uint16(fileSize / 512)
2021-06-26 01:15:20 +00:00
if fileSize%512 > 0 {
numberOfBlocks++
}
2021-06-26 01:15:20 +00:00
if fileSize > 0x200 && fileSize <= 0x20000 {
numberOfBlocks++ // add index block
}
2022-12-30 11:12:41 +00:00
if fileSize > 0x20000 && fileSize <= 0x1000000 {
// add index blocks for each 256 blocks
numberOfBlocks += numberOfBlocks / 256
2021-06-26 01:15:20 +00:00
// add index block for any remaining blocks
if numberOfBlocks%256 > 0 {
2021-06-26 01:15:20 +00:00
numberOfBlocks++
}
// add master index block
numberOfBlocks++
2021-06-06 12:00:20 +00:00
}
if fileSize > 0x1000000 {
return nil, errors.New("file size too large")
}
volumeBitmap, err := ReadVolumeBitmap(reader)
if err != nil {
return nil, err
}
2021-06-29 02:26:20 +00:00
blockList := findFreeBlocks(volumeBitmap, numberOfBlocks)
2021-06-06 12:00:20 +00:00
2023-01-22 15:01:57 +00:00
return blockList[0:numberOfBlocks], nil
2021-06-26 01:15:20 +00:00
}
2022-12-31 14:14:27 +00:00
// GetFileEntry returns a file entry for the given path
func GetFileEntry(reader io.ReaderAt, path string) (FileEntry, error) {
2021-06-26 01:15:20 +00:00
directory, fileName := GetDirectoryAndFileNameFromPath(path)
_, _, fileEntries, err := ReadDirectory(reader, directory)
if err != nil {
return FileEntry{}, err
}
2021-06-06 12:00:20 +00:00
2021-06-30 12:22:08 +00:00
if fileEntries == nil || len(fileEntries) == 0 {
return FileEntry{}, errors.New("file entry not found")
2021-06-06 12:00:20 +00:00
}
var fileEntry FileEntry
for i := 0; i < len(fileEntries); i++ {
if fileEntries[i].FileName == fileName {
fileEntry = fileEntries[i]
}
}
2021-06-30 12:22:08 +00:00
if fileEntry.StorageType == StorageDeleted {
return FileEntry{}, errors.New("file not found")
2021-06-30 12:22:08 +00:00
}
2021-06-30 04:04:59 +00:00
return fileEntry, nil
2021-06-12 02:43:35 +00:00
}