Add (NOP) prodos factory and DiskBlockDevice

The prodos operator factory functions just return errors for now,
until Catalog is implemented.
This commit is contained in:
Zellyn Hunter 2017-03-22 22:27:27 -04:00
parent 118944b512
commit df80529449
5 changed files with 248 additions and 6 deletions

67
lib/disk/dev.go Normal file
View File

@ -0,0 +1,67 @@
// Copyright © 2017 Zellyn Hunter <zellyn@gmail.com>
// dev.go contains logic for reading ".po" disk images.
package disk
import (
"fmt"
"io"
"io/ioutil"
)
// Dev represents a .po disk image.
type Dev struct {
data []byte // The actual data in the file
blocks uint16 // Number of blocks
}
var _ BlockDevice = (*Dev)(nil)
// LoadDev loads a .po image from a file.
func LoadDev(filename string) (Dev, error) {
bb, err := ioutil.ReadFile(filename)
if err != nil {
return Dev{}, err
}
if len(bb)%512 != 0 {
return Dev{}, fmt.Errorf("expected file %q to contain a multiple of 512 bytes, but got %d", filename, len(bb))
}
return Dev{
data: bb,
blocks: uint16(len(bb) / 512),
}, nil
}
// Empty creates a .po image that is all zeros.
func EmptyDev(blocks uint16) Dev {
return Dev{
data: make([]byte, 512*int(blocks)),
blocks: blocks,
}
}
// ReadBlock reads a single block from the device. It always returns
// 512 byes.
func (d Dev) ReadBlock(index uint16) (Block, error) {
var b Block
copy(b[:], d.data[int(index)*512:int(index+1)*512])
return b, nil
}
// WriteBlock writes a single block to a device. It expects exactly
// 512 bytes.
func (d Dev) WriteBlock(index uint16, data Block) error {
copy(d.data[int(index)*512:int(index+1)*512], data[:])
return nil
}
// Blocks returns the number of blocks in the device.
func (d Dev) Blocks() uint16 {
return d.blocks
}
// Write writes the device contents to the given file.
func (d Dev) Write(w io.Writer) (n int, err error) {
return w.Write(d.data)
}

View File

@ -166,15 +166,125 @@ func OpenDisk(filename string) (SectorDisk, error) {
return nil, fmt.Errorf("Unimplemented/unknown disk file extension %q", ext)
}
// OpenDev opens a device image by filename.
func OpenDev(filename string) (BlockDevice, error) {
ext := strings.ToLower(path.Ext(filename))
switch ext {
case ".po":
return LoadDev(filename)
}
return nil, fmt.Errorf("Unimplemented/unknown device 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
if err == nil {
var op Operator
op, err = OperatorForDisk(sd)
if err == nil {
return op, nil
}
}
op, err := OperatorForDisk(sd)
if err != nil {
return nil, err
dev, err2 := OpenDev(filename)
if err2 == nil {
var op Operator
op, err2 = OperatorForDevice(dev)
if err2 != nil {
return nil, err2
}
return op, nil
}
return op, nil
return nil, err
}
type DiskBlockDevice struct {
lsd LogicalSectorDisk
blocks uint16
}
// BlockDeviceFromSectorDisk creates a ProDOS block device from a
// SectorDisk. It reads maps ProDOS to physical sectors.
func BlockDeviceFromSectorDisk(sd SectorDisk) (BlockDevice, error) {
lsd, err := NewMappedDisk(sd, ProDOSLogicalToPhysicalSectorMap)
if err != nil {
return nil, err
}
return DiskBlockDevice{
lsd: lsd,
blocks: uint16(lsd.Tracks()) / 2 * uint16(lsd.Sectors()),
}, nil
}
// ReadBlock reads a single block from the device. It always returns
// 512 byes.
func (dbv DiskBlockDevice) ReadBlock(index uint16) (Block, error) {
var b Block
if index >= dbv.blocks {
return b, fmt.Errorf("device has %d blocks; tried to read block %d (index=%d)", dbv.blocks, index+1, index)
}
i := int(index) * 2
sectors := int(dbv.lsd.Sectors())
track0 := i / sectors
sector0 := i % sectors
sector1 := sector0 + 1
track1 := track0
if sector1 == sectors {
sector1 = 0
track1++
}
b0, err := dbv.lsd.ReadLogicalSector(byte(track0), byte(sector0))
if err != nil {
return b, fmt.Errorf("error reading first half of block %d (t:%d s:%d): %v", index, track0, sector0, err)
}
b1, err := dbv.lsd.ReadLogicalSector(byte(track1), byte(sector1))
if err != nil {
return b, fmt.Errorf("error reading second half of block %d (t:%d s:%d): %v", index, track1, sector1, err)
}
copy(b[:256], b0)
copy(b[256:], b1)
return b, nil
}
// WriteBlock writes a single block to a device. It expects exactly
// 512 bytes.
func (dbv DiskBlockDevice) WriteBlock(index uint16, data Block) error {
if index >= dbv.blocks {
return fmt.Errorf("device has %d blocks; tried to read block %d (index=%d)", dbv.blocks, index+1, index)
}
i := int(index) * 2
sectors := int(dbv.lsd.Sectors())
track0 := i / sectors
sector0 := i % sectors
sector1 := sector0 + 1
track1 := track0
if sector1 == sectors {
sector1 = 0
track1++
}
if err := dbv.lsd.WriteLogicalSector(byte(track0), byte(sector0), data[:256]); err != nil {
return fmt.Errorf("error writing first half of block %d (t:%d s:%d): %v", index, track0, sector0, err)
}
if err := dbv.lsd.WriteLogicalSector(byte(track1), byte(sector1), data[256:]); err != nil {
return fmt.Errorf("error writing second half of block %d (t:%d s:%d): %v", index, track1, sector1, err)
}
return nil
}
// Blocks returns the number of blocks on the device.
func (dbv DiskBlockDevice) Blocks() uint16 {
return dbv.blocks
}
// Write writes the device contents to the given Writer.
func (dbv DiskBlockDevice) Write(w io.Writer) (int, error) {
return dbv.lsd.Write(w)
}

View File

@ -91,3 +91,41 @@ func OperatorForDisk(sd SectorDisk) (Operator, error) {
sort.Strings(names)
return nil, fmt.Errorf("Cannot find a disk operator matching the given disk image (tried %s)", strings.Join(names, ", "))
}
// deviceOperatorFactory is the type of functions that accept a BlockDevice,
// and may return an Operator interface to operate on it.
type deviceOperatorFactory func(BlockDevice) (Operator, error)
// deviceOperatorFactories is the map of currently-registered device
// operator factories.
var deviceOperatorFactories map[string]deviceOperatorFactory
func init() {
deviceOperatorFactories = make(map[string]deviceOperatorFactory)
}
// RegisterDeviceOperatorFactory registers a device operator factory with
// the given name: a function that accepts a BlockDevice, and may
// return an Operator. It doesn't lock deviceOperatorFactories: it is
// expected to be called only from package `init` functions.
func RegisterDeviceOperatorFactory(name string, factory deviceOperatorFactory) {
deviceOperatorFactories[name] = factory
}
// OperatorForDevice returns an Operator for the given BlockDevice, if possible.
func OperatorForDevice(sd BlockDevice) (Operator, error) {
if len(deviceOperatorFactories) == 0 {
return nil, errors.New("Cannot find an operator matching the given device image (none registered)")
}
for _, factory := range deviceOperatorFactories {
if operator, err := factory(sd); err == nil {
return operator, nil
}
}
names := make([]string, 0, len(deviceOperatorFactories))
for name := range deviceOperatorFactories {
names = append(names, `"`+name+`"`)
}
sort.Strings(names)
return nil, fmt.Errorf("Cannot find a device operator matching the given device image (tried %s)", strings.Join(names, ", "))
}

View File

@ -590,3 +590,29 @@ func (o operator) PutFile(fileInfo disk.FileInfo, overwrite bool) (existed bool,
func (o operator) Write(w io.Writer) (int, error) {
return o.dev.Write(w)
}
// deviceOperatorFactory is the factory that returns prodos operators
// given device images.
func deviceOperatorFactory(bd disk.BlockDevice) (disk.Operator, error) {
op := operator{dev: bd}
_, err := op.Catalog("")
if err != nil {
return nil, fmt.Errorf("Cannot read catalog. Underlying error: %v", err)
}
return op, nil
}
// diskOperatorFactory is the factory that returns dos3 operators
// given disk images.
func diskOperatorFactory(sd disk.SectorDisk) (disk.Operator, error) {
bd, err := disk.BlockDeviceFromSectorDisk(sd)
if err != nil {
return nil, err
}
return deviceOperatorFactory(bd)
}
func init() {
disk.RegisterDeviceOperatorFactory(operatorName, deviceOperatorFactory)
disk.RegisterDiskOperatorFactory(operatorName, diskOperatorFactory)
}

View File

@ -7,6 +7,7 @@ import (
// Import disk operator factories for DOS3 and Super-Mon
_ "github.com/zellyn/diskii/lib/dos3"
_ "github.com/zellyn/diskii/lib/prodos"
_ "github.com/zellyn/diskii/lib/supermon"
)