diff --git a/cmd/catalog.go b/cmd/catalog.go index 1820afd..9c177b9 100644 --- a/cmd/catalog.go +++ b/cmd/catalog.go @@ -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 } diff --git a/cmd/delete.go b/cmd/delete.go index a4df421..d7977fa 100644 --- a/cmd/delete.go +++ b/cmd/delete.go @@ -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 } diff --git a/cmd/dump.go b/cmd/dump.go index 78b56a9..fce649a 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -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 } diff --git a/cmd/nakedos.go b/cmd/nakedos.go index e4876c6..7fb1ed8 100644 --- a/cmd/nakedos.go +++ b/cmd/nakedos.go @@ -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 } diff --git a/cmd/put.go b/cmd/put.go index af6860b..f057273 100644 --- a/cmd/put.go +++ b/cmd/put.go @@ -42,11 +42,7 @@ func runPut(args []string) error { if len(args) != 3 { return fmt.Errorf("usage: put ") } - 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 } diff --git a/lib/disk/disk.go b/lib/disk/disk.go index 18f0143..7d5d222 100644 --- a/lib/disk/disk.go +++ b/lib/disk/disk.go @@ -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 +} diff --git a/lib/disk/marshal.go b/lib/disk/marshal.go index e6204d1..c472060 100644 --- a/lib/disk/marshal.go +++ b/lib/disk/marshal.go @@ -1,10 +1,30 @@ // Copyright © 2016 Zellyn Hunter // 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) +} diff --git a/lib/disk/ops.go b/lib/disk/ops.go index 2e82b23..eac9704 100644 --- a/lib/disk/ops.go +++ b/lib/disk/ops.go @@ -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, ", ")) } diff --git a/lib/dos3/dos3.go b/lib/dos3/dos3.go index 67293e6..a2fd121 100644 --- a/lib/dos3/dos3.go +++ b/lib/dos3/dos3.go @@ -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) } diff --git a/lib/prodos/prodos.go b/lib/prodos/prodos.go index 592956d..72caa51 100644 --- a/lib/prodos/prodos.go +++ b/lib/prodos/prodos.go @@ -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) +} diff --git a/lib/prodos/prodos_test.go b/lib/prodos/prodos_test.go index 3764416..a11e0c1 100644 --- a/lib/prodos/prodos_test.go +++ b/lib/prodos/prodos_test.go @@ -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 } diff --git a/lib/supermon/supermon.go b/lib/supermon/supermon.go index 226ebef..188c28d 100644 --- a/lib/supermon/supermon.go +++ b/lib/supermon/supermon.go @@ -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) } diff --git a/lib/supermon/supermon_test.go b/lib/supermon/supermon_test.go index a4c0a97..dd0c2de 100644 --- a/lib/supermon/supermon_test.go +++ b/lib/supermon/supermon_test.go @@ -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) }