refactor to make more disk/device agnostic

This commit is contained in:
Zellyn Hunter 2017-03-17 22:26:15 -04:00
parent 5bd50043b9
commit 2d0d2773a4
13 changed files with 211 additions and 94 deletions

View File

@ -37,11 +37,7 @@ func runCat(args []string) error {
if len(args) < 1 || len(args) > 2 {
return fmt.Errorf("cat expects a disk image filename, and an optional subdirectory")
}
sd, err := disk.Open(args[0])
if err != nil {
return err
}
op, err := disk.OperatorFor(sd)
op, err := disk.Open(args[0])
if err != nil {
return err
}

View File

@ -38,11 +38,7 @@ func runDelete(args []string) error {
if len(args) != 2 {
return fmt.Errorf("delete expects a disk image filename, and a filename")
}
sd, err := disk.Open(args[0])
if err != nil {
return err
}
op, err := disk.OperatorFor(sd)
op, err := disk.Open(args[0])
if err != nil {
return err
}
@ -57,7 +53,7 @@ func runDelete(args []string) error {
if err != nil {
return err
}
_, err = sd.Write(f)
_, err = op.Write(f)
if err != nil {
return err
}

View File

@ -36,11 +36,7 @@ func runDump(args []string) error {
if len(args) != 2 {
return fmt.Errorf("dump expects a disk image filename, and a filename")
}
sd, err := disk.Open(args[0])
if err != nil {
return err
}
op, err := disk.OperatorFor(sd)
op, err := disk.Open(args[0])
if err != nil {
return err
}

View File

@ -68,11 +68,7 @@ func runMkhello(args []string) error {
if start < address {
return fmt.Errorf("start address %d (%04X) < load address %d (%04X)", start, start, address, address)
}
sd, err := disk.Open(args[0])
if err != nil {
return err
}
op, err := disk.OperatorFor(sd)
op, err := disk.Open(args[0])
if err != nil {
return err
}
@ -118,7 +114,7 @@ func runMkhello(args []string) error {
if err != nil {
return err
}
_, err = sd.Write(f)
_, err = op.Write(f)
if err != nil {
return err
}

View File

@ -42,11 +42,7 @@ func runPut(args []string) error {
if len(args) != 3 {
return fmt.Errorf("usage: put <disk image> <target filename> <source filename>")
}
sd, err := disk.Open(args[0])
if err != nil {
return err
}
op, err := disk.OperatorFor(sd)
op, err := disk.Open(args[0])
if err != nil {
return err
}
@ -76,7 +72,7 @@ func runPut(args []string) error {
if err != nil {
return err
}
_, err = sd.Write(f)
_, err = op.Write(f)
if err != nil {
return err
}

View File

@ -156,8 +156,8 @@ func (md MappedDisk) Write(w io.Writer) (n int, err error) {
return md.sectorDisk.Write(w)
}
// Open opens a disk image by filename.
func Open(filename string) (SectorDisk, error) {
// OpenDisk opens a disk image by filename.
func OpenDisk(filename string) (SectorDisk, error) {
ext := strings.ToLower(path.Ext(filename))
switch ext {
case ".dsk":
@ -165,3 +165,16 @@ func Open(filename string) (SectorDisk, error) {
}
return nil, fmt.Errorf("Unimplemented/unknown disk file extension %q", ext)
}
// Open opens a disk image by filename, returning an Operator.
func Open(filename string) (Operator, error) {
sd, err := OpenDisk(filename)
if err != nil {
return nil, err
}
op, err := OperatorForDisk(sd)
if err != nil {
return nil, err
}
return op, nil
}

View File

@ -1,10 +1,30 @@
// Copyright © 2016 Zellyn Hunter <zellyn@gmail.com>
// marshal.go contains helpers for marshaling sector structs to/from
// disk.
// disk and block structs to/from devices.
package disk
import "io"
// A ProDOS block.
type Block [512]byte
// BlockDevice is the interface used to read and write devices by
// logical block number.
type BlockDevice interface {
// ReadBlock reads a single block from the device. It always returns
// 512 byes.
ReadBlock(index uint16) (Block, error)
// WriteBlock writes a single block to a device. It expects exactly
// 512 bytes.
WriteBlock(index uint16, data Block) error
// Blocks returns the number of blocks on the device.
Blocks() uint16
// Write writes the device contents to the given Writer.
Write(io.Writer) (int, error)
}
// SectorSource is the interface for types that can marshal to sectors.
type SectorSource interface {
// ToSector marshals the sector struct to exactly 256 bytes.
@ -52,3 +72,44 @@ func MarshalLogicalSector(d LogicalSectorDisk, ss SectorSource) error {
}
return d.WriteLogicalSector(track, sector, bytes)
}
// BlockSource is the interface for types that can marshal to blocks.
type BlockSource interface {
// ToBlock marshals the block struct to exactly 512 bytes.
ToBlock() (Block, error)
// GetBlock returns the index that a block struct was loaded from.
GetBlock() uint16
}
// BlockSink is the interface for types that can unmarshal from blocks.
type BlockSink interface {
// FromBlock unmarshals the block struct from a Block. Input is
// expected to be exactly 512 bytes.
FromBlock(block Block) error
// SetBlock sets the index that a block struct was loaded from.
SetBlock(index uint16)
}
// UnmarshalBlock reads a block from a BlockDevice, and unmarshals it
// into a BlockSink, setting its index.
func UnmarshalBlock(d BlockDevice, bs BlockSink, index uint16) error {
block, err := d.ReadBlock(index)
if err != nil {
return err
}
if err := bs.FromBlock(block); err != nil {
return err
}
bs.SetBlock(index)
return nil
}
// MarshalBlock marshals a BlockSource to its block on a BlockDevice.
func MarshalBlock(d BlockDevice, bs BlockSource) error {
index := bs.GetBlock()
block, err := bs.ToBlock()
if err != nil {
return err
}
return d.WriteBlock(index, block)
}

View File

@ -9,6 +9,7 @@ package disk
import (
"errors"
"fmt"
"io"
"sort"
"strings"
)
@ -42,6 +43,8 @@ type Operator interface {
// is false, it returns with an error. Otherwise it returns true if
// an existing file was overwritten.
PutFile(fileInfo FileInfo, overwrite bool) (existed bool, err error)
// Write writes the underlying disk to the given writer.
Write(io.Writer) (int, error)
}
// FileInfo represents a file descriptor plus the content.
@ -51,40 +54,40 @@ type FileInfo struct {
StartAddress uint16
}
// operatorFactory is the type of functions that accept a SectorDisk,
// diskOperatorFactory is the type of functions that accept a SectorDisk,
// and may return an Operator interface to operate on it.
type operatorFactory func(SectorDisk) (Operator, error)
type diskOperatorFactory func(SectorDisk) (Operator, error)
// operatorFactories is the map of currently-registered operator
// factories.
var operatorFactories map[string]operatorFactory
// diskOperatorFactories is the map of currently-registered disk
// operator factories.
var diskOperatorFactories map[string]diskOperatorFactory
func init() {
operatorFactories = make(map[string]operatorFactory)
diskOperatorFactories = make(map[string]diskOperatorFactory)
}
// RegisterOperatorFactory registers an operator factory with the
// given name: a function that accepts a SectorDisk, and may return an
// Operator. It doesn't lock operatorFactories: it is expected to be
// called only from package `init` functions.
func RegisterOperatorFactory(name string, factory operatorFactory) {
operatorFactories[name] = factory
// RegisterDiskOperatorFactory registers a disk operator factory with
// the given name: a function that accepts a SectorDisk, and may
// return an Operator. It doesn't lock diskOperatorFactories: it is
// expected to be called only from package `init` functions.
func RegisterDiskOperatorFactory(name string, factory diskOperatorFactory) {
diskOperatorFactories[name] = factory
}
// OperatorFor returns an Operator for the given SectorDisk, if possible.
func OperatorFor(sd SectorDisk) (Operator, error) {
if len(operatorFactories) == 0 {
// OperatorForDisk returns an Operator for the given SectorDisk, if possible.
func OperatorForDisk(sd SectorDisk) (Operator, error) {
if len(diskOperatorFactories) == 0 {
return nil, errors.New("Cannot find an operator matching the given disk image (none registered)")
}
for _, factory := range operatorFactories {
for _, factory := range diskOperatorFactories {
if operator, err := factory(sd); err == nil {
return operator, nil
}
}
names := make([]string, 0, len(operatorFactories))
for name := range operatorFactories {
names := make([]string, 0, len(diskOperatorFactories))
for name := range diskOperatorFactories {
names = append(names, `"`+name+`"`)
}
sort.Strings(names)
return nil, fmt.Errorf("Cannot find an operator matching the given disk image (tried %s)", strings.Join(names, ", "))
return nil, fmt.Errorf("Cannot find a disk operator matching the given disk image (tried %s)", strings.Join(names, ", "))
}

View File

@ -7,6 +7,7 @@ package dos3
import (
"encoding/binary"
"fmt"
"io"
"strings"
"github.com/zellyn/diskii/lib/disk"
@ -669,6 +670,11 @@ func (o operator) PutFile(fileInfo disk.FileInfo, overwrite bool) (existed bool,
return false, fmt.Errorf("%s does not implement PutFile yet", operatorName)
}
// Write writes the underlying disk to the given writer.
func (o operator) Write(w io.Writer) (int, error) {
return o.lsd.Write(w)
}
// operatorFactory is the factory that returns dos3 operators given
// disk images.
func operatorFactory(sd disk.SectorDisk) (disk.Operator, error) {
@ -684,5 +690,5 @@ func operatorFactory(sd disk.SectorDisk) (disk.Operator, error) {
}
func init() {
disk.RegisterOperatorFactory(operatorName, operatorFactory)
disk.RegisterDiskOperatorFactory(operatorName, operatorFactory)
}

View File

@ -8,30 +8,14 @@ import (
"encoding/binary"
"fmt"
"io"
"github.com/zellyn/diskii/lib/disk"
)
// A single ProDOS block.
type Block [512]byte
// BlockDevice is the interface used to read and write devices by
// logical block number.
type BlockDevice interface {
// ReadBlock reads a single block from the device. It always returns
// 512 byes.
ReadBlock(index uint16) (Block, error)
// WriteBlock writes a single block to a device. It expects exactly
// 512 bytes.
WriteBlock(index uint16, data Block) error
// Blocks returns the number of blocks on the device.
Blocks() uint16
// Write writes the device contents to the given Writer.
Write(io.Writer) (int, error)
}
type VolumeBitMap []Block
type VolumeBitMap []disk.Block
func NewVolumeBitMap(blocks uint16) VolumeBitMap {
vbm := VolumeBitMap(make([]Block, (blocks+(512*8)-1)/(512*8)))
vbm := VolumeBitMap(make([]disk.Block, (blocks+(512*8)-1)/(512*8)))
for b := 0; b < int(blocks); b++ {
vbm.MarkUnused(uint16(b))
}
@ -69,9 +53,9 @@ func (vbm VolumeBitMap) IsFree(block uint16) bool {
}
// ReadVolumeBitMap
func ReadVolumeBitMap(bd BlockDevice, startBlock uint16) (VolumeBitMap, error) {
func ReadVolumeBitMap(bd disk.BlockDevice, startBlock uint16) (VolumeBitMap, error) {
blocks := bd.Blocks() / 4096
vbm := make([]Block, blocks)
vbm := make([]disk.Block, blocks)
for i := uint16(0); i < blocks; i++ {
block, err := bd.ReadBlock(startBlock + i)
if err != nil {
@ -84,7 +68,7 @@ func ReadVolumeBitMap(bd BlockDevice, startBlock uint16) (VolumeBitMap, error) {
// Write writes the Volume Bit Map to a block device, starting at the
// given block.
func (vbm VolumeBitMap) Write(bd BlockDevice, startBlock uint16) error {
func (vbm VolumeBitMap) Write(bd disk.BlockDevice, startBlock uint16) error {
for i, block := range vbm {
if err := bd.WriteBlock(startBlock+uint16(i), block); err != nil {
return fmt.Errorf("cannot write block %d of Volume Bit Map: %v", err)
@ -137,8 +121,8 @@ type VolumeDirectoryKeyBlock struct {
}
// ToBlock marshals the VolumeDirectoryKeyBlock to a Block of bytes.
func (vdkb VolumeDirectoryKeyBlock) ToBlock() Block {
var block Block
func (vdkb VolumeDirectoryKeyBlock) ToBlock() disk.Block {
var block disk.Block
binary.LittleEndian.PutUint16(block[0x0:0x2], vdkb.Prev)
binary.LittleEndian.PutUint16(block[0x2:0x4], vdkb.Next)
copyBytes(block[0x04:0x02b], vdkb.Header.toBytes())
@ -150,7 +134,7 @@ func (vdkb VolumeDirectoryKeyBlock) ToBlock() Block {
}
// FromBlock unmarshals a Block of bytes into a VolumeDirectoryKeyBlock.
func (vdkb *VolumeDirectoryKeyBlock) FromBlock(block Block) {
func (vdkb *VolumeDirectoryKeyBlock) FromBlock(block disk.Block) {
vdkb.Prev = binary.LittleEndian.Uint16(block[0x0:0x2])
vdkb.Next = binary.LittleEndian.Uint16(block[0x2:0x4])
vdkb.Header.fromBytes(block[0x04:0x2b])
@ -184,8 +168,8 @@ type VolumeDirectoryBlock struct {
}
// ToBlock marshals a VolumeDirectoryBlock to a Block of bytes.
func (vdb VolumeDirectoryBlock) ToBlock() Block {
var block Block
func (vdb VolumeDirectoryBlock) ToBlock() disk.Block {
var block disk.Block
binary.LittleEndian.PutUint16(block[0x0:0x2], vdb.Prev)
binary.LittleEndian.PutUint16(block[0x2:0x4], vdb.Next)
for i, desc := range vdb.Descriptors {
@ -196,7 +180,7 @@ func (vdb VolumeDirectoryBlock) ToBlock() Block {
}
// FromBlock unmarshals a Block of bytes into a VolumeDirectoryBlock.
func (vdb *VolumeDirectoryBlock) FromBlock(block Block) {
func (vdb *VolumeDirectoryBlock) FromBlock(block disk.Block) {
vdb.Prev = binary.LittleEndian.Uint16(block[0x0:0x2])
vdb.Next = binary.LittleEndian.Uint16(block[0x2:0x4])
for i := range vdb.Descriptors {
@ -360,7 +344,7 @@ func (fd FileDescriptor) Validate() (errors []error) {
// An index block contains 256 16-bit block numbers, pointing to other
// blocks. The LSBs are stored in the first half, MSBs in the second.
type IndexBlock Block
type IndexBlock disk.Block
// Get the blockNum'th block number from an index block.
func (i IndexBlock) Get(blockNum byte) uint16 {
@ -384,8 +368,8 @@ type SubdirectoryKeyBlock struct {
}
// ToBlock marshals the SubdirectoryKeyBlock to a Block of bytes.
func (skb SubdirectoryKeyBlock) ToBlock() Block {
var block Block
func (skb SubdirectoryKeyBlock) ToBlock() disk.Block {
var block disk.Block
binary.LittleEndian.PutUint16(block[0x0:0x2], skb.Prev)
binary.LittleEndian.PutUint16(block[0x2:0x4], skb.Next)
copyBytes(block[0x04:0x02b], skb.Header.toBytes())
@ -397,7 +381,7 @@ func (skb SubdirectoryKeyBlock) ToBlock() Block {
}
// FromBlock unmarshals a Block of bytes into a SubdirectoryKeyBlock.
func (skb *SubdirectoryKeyBlock) FromBlock(block Block) {
func (skb *SubdirectoryKeyBlock) FromBlock(block disk.Block) {
skb.Prev = binary.LittleEndian.Uint16(block[0x0:0x2])
skb.Next = binary.LittleEndian.Uint16(block[0x2:0x4])
skb.Header.fromBytes(block[0x04:0x2b])
@ -431,8 +415,8 @@ type SubdirectoryBlock struct {
}
// ToBlock marshals a SubdirectoryBlock to a Block of bytes.
func (sb SubdirectoryBlock) ToBlock() Block {
var block Block
func (sb SubdirectoryBlock) ToBlock() disk.Block {
var block disk.Block
binary.LittleEndian.PutUint16(block[0x0:0x2], sb.Prev)
binary.LittleEndian.PutUint16(block[0x2:0x4], sb.Next)
for i, desc := range sb.Descriptors {
@ -443,7 +427,7 @@ func (sb SubdirectoryBlock) ToBlock() Block {
}
// FromBlock unmarshals a Block of bytes into a SubdirectoryBlock.
func (sb *SubdirectoryBlock) FromBlock(block Block) {
func (sb *SubdirectoryBlock) FromBlock(block disk.Block) {
sb.Prev = binary.LittleEndian.Uint16(block[0x0:0x2])
sb.Next = binary.LittleEndian.Uint16(block[0x2:0x4])
for i := range sb.Descriptors {
@ -543,3 +527,66 @@ func copyBytes(dst, src []byte) int {
}
return copy(dst, src)
}
// operator is a disk.Operator - an interface for performing
// high-level operations on files and directories.
type operator struct {
dev disk.BlockDevice
}
var _ disk.Operator = operator{}
// operatorName is the keyword name for the operator that undestands
// prodos disks/devices.
const operatorName = "prodos"
// Name returns the name of the operator.
func (o operator) Name() string {
return operatorName
}
// HasSubdirs returns true if the underlying operating system on the
// disk allows subdirectories.
func (o operator) HasSubdirs() bool {
return true
}
// Catalog returns a catalog of disk entries. subdir should be empty
// for operating systems that do not support subdirectories.
func (o operator) Catalog(subdir string) ([]disk.Descriptor, error) {
return nil, fmt.Errorf("%s doesn't implement Catalog yet", operatorName)
/*
fds, _, err := ReadCatalog(o.dev)
if err != nil {
return nil, err
}
descs := make([]disk.Descriptor, 0, len(fds))
for _, fd := range fds {
descs = append(descs, fd.descriptor())
}
return descs, nil
*/
}
// GetFile retrieves a file by name.
func (o operator) GetFile(filename string) (disk.FileInfo, error) {
return disk.FileInfo{}, fmt.Errorf("%s doesn't implement GetFile yet", operatorName)
}
// Delete deletes a file by name. It returns true if the file was
// deleted, false if it didn't exist.
func (o operator) Delete(filename string) (bool, error) {
return false, fmt.Errorf("%s doesn't implement Delete yet", operatorName)
}
// PutFile writes a file by name. If the file exists and overwrite
// is false, it returns with an error. Otherwise it returns true if
// an existing file was overwritten.
func (o operator) PutFile(fileInfo disk.FileInfo, overwrite bool) (existed bool, err error) {
return false, fmt.Errorf("%s doesn't implement PutFile yet", operatorName)
}
// Write writes the underlying device blocks to the given writer.
func (o operator) Write(w io.Writer) (int, error) {
return o.dev.Write(w)
}

View File

@ -6,10 +6,11 @@ import (
"testing"
"github.com/kr/pretty"
"github.com/zellyn/diskii/lib/disk"
)
func randomBlock() Block {
var b1 Block
func randomBlock() disk.Block {
var b1 disk.Block
rand.Read(b1[:])
return b1
}

View File

@ -7,6 +7,7 @@ package supermon
import (
"encoding/binary"
"fmt"
"io"
"strconv"
"strings"
@ -783,6 +784,11 @@ func (o Operator) PutFile(fileInfo disk.FileInfo, overwrite bool) (existed bool,
return existed, nil
}
// Write writes the underlying disk to the given writer.
func (o Operator) Write(w io.Writer) (int, error) {
return o.SD.Write(w)
}
// operatorFactory is the factory that returns supermon operators
// given disk images.
func operatorFactory(sd disk.SectorDisk) (disk.Operator, error) {
@ -805,5 +811,5 @@ func operatorFactory(sd disk.SectorDisk) (disk.Operator, error) {
}
func init() {
disk.RegisterOperatorFactory(operatorName, operatorFactory)
disk.RegisterDiskOperatorFactory(operatorName, operatorFactory)
}

View File

@ -126,11 +126,11 @@ func TestReadSymbolTable(t *testing.T) {
// TestGetFile tests the retrieval of a file's contents, using the
// Operator interface.
func TestGetFile(t *testing.T) {
sd, err := disk.Open(testDisk)
sd, err := disk.OpenDisk(testDisk)
if err != nil {
t.Fatal(err)
}
op, err := disk.OperatorFor(sd)
op, err := disk.OperatorForDisk(sd)
if err != nil {
t.Fatal(err)
}
@ -213,11 +213,11 @@ func TestReadWriteSymbolTable(t *testing.T) {
// TestPutFile tests the creation of a file, using the Operator
// interface.
func TestPutFile(t *testing.T) {
sd, err := disk.Open(testDisk)
sd, err := disk.OpenDisk(testDisk)
if err != nil {
t.Fatal(err)
}
op, err := disk.OperatorFor(sd)
op, err := disk.OperatorForDisk(sd)
if err != nil {
t.Fatal(err)
}