mirror of
https://github.com/zellyn/diskii.git
synced 2024-09-11 03:54:51 +00:00
Implemented cat
command for dos3.3 and NakedOS
- Added a generic `Operator` registry and implemented Operators for dos3.3 and NakeOS/Super-Mon disks. - Currently Operators implement only the `Catalog` command.
This commit is contained in:
parent
c7cb3bb5ce
commit
f57c3a0c06
@ -1,6 +1,10 @@
|
||||
diskii
|
||||
======
|
||||
|
||||
**Note:** diskii is not stable yet! I don't expect to remove
|
||||
functionality, but I'm still experimenting with the command syntax and
|
||||
organization, so don't get too comfy with it yet.
|
||||
|
||||
diskii is a commandline tool for working with Apple II disk images.
|
||||
|
||||
It is also a library of code that can be used by other Go programs.
|
||||
|
61
cmd/cat.go
Normal file
61
cmd/cat.go
Normal file
@ -0,0 +1,61 @@
|
||||
// Copyright © 2016 Zellyn Hunter <zellyn@gmail.com>
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/zellyn/diskii/lib/disk"
|
||||
_ "github.com/zellyn/diskii/lib/dos33"
|
||||
_ "github.com/zellyn/diskii/lib/supermon"
|
||||
)
|
||||
|
||||
// catCmd represents the cat command, used to catalog a disk or
|
||||
// directory.
|
||||
var catCmd = &cobra.Command{
|
||||
Use: "cat",
|
||||
Short: "print a list of files",
|
||||
Long: `Catalog a disk or subdirectory.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := runCat(args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(-1)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(catCmd)
|
||||
}
|
||||
|
||||
// runCat performs the actual catalog logic.
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
subdir := ""
|
||||
if len(args) == 2 {
|
||||
if !op.HasSubdirs() {
|
||||
return fmt.Errorf("Disks of type %q cannot have subdirectories", op.Name())
|
||||
}
|
||||
subdir = args[1]
|
||||
}
|
||||
fds, err := op.Catalog(subdir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fd := range fds {
|
||||
fmt.Println(fd.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -32,6 +32,19 @@ decode - # read stdin`,
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
applesoftCmd.AddCommand(decodeCmd)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
|
||||
// Cobra supports Persistent Flags which will work for this command
|
||||
// and all subcommands, e.g.:
|
||||
// decodeCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||
|
||||
decodeCmd.Flags().Uint16VarP(&location, "location", "l", 0x801, "Starting program location in memory")
|
||||
decodeCmd.Flags().BoolVarP(&rawControlCodes, "raw", "r", false, "Print raw control codes (no escaping)")
|
||||
}
|
||||
|
||||
// runDecode performs the actual decode logic.
|
||||
func runDecode(args []string) error {
|
||||
if len(args) != 1 {
|
||||
@ -52,16 +65,3 @@ func runDecode(args []string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
applesoftCmd.AddCommand(decodeCmd)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
|
||||
// Cobra supports Persistent Flags which will work for this command
|
||||
// and all subcommands, e.g.:
|
||||
// decodeCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||
|
||||
decodeCmd.Flags().Uint16VarP(&location, "location", "l", 0x801, "Starting program location in memory")
|
||||
decodeCmd.Flags().BoolVarP(&rawControlCodes, "raw", "r", false, "Print raw control codes (no escaping)")
|
||||
}
|
||||
|
105
lib/disk/disk.go
105
lib/disk/disk.go
@ -6,7 +6,16 @@ package disk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
DOS33Tracks = 35 // Tracks per disk
|
||||
DOS33Sectors = 16 // Sectors per track
|
||||
// DOS33DiskBytes is the number of bytes on a DOS 3.3 disk.
|
||||
DOS33DiskBytes = 143360 // 35 tracks * 16 sectors * 256 bytes
|
||||
DOS33TrackBytes = 256 * DOS33Sectors // Bytes per track
|
||||
)
|
||||
|
||||
// Dos33LogicalToPhysicalSectorMap maps logical sector numbers to physical ones.
|
||||
@ -106,96 +115,22 @@ func (md MappedDisk) WriteLogicalSector(track byte, sector byte, data []byte) er
|
||||
return md.sectorDisk.WritePhysicalSector(track, physicalSector, data)
|
||||
}
|
||||
|
||||
// Sectors returns the number of sectors on the DSK image.
|
||||
// Sectors returns the number of sectors in the disk image.
|
||||
func (md MappedDisk) Sectors() byte {
|
||||
return md.sectorDisk.Sectors()
|
||||
}
|
||||
|
||||
// Tracks returns the number of tracks on the DSK image.
|
||||
// Tracks returns the number of tracks in the disk image.
|
||||
func (md MappedDisk) Tracks() byte {
|
||||
return md.sectorDisk.Tracks()
|
||||
}
|
||||
|
||||
const (
|
||||
DOS33Tracks = 35 // Tracks per disk
|
||||
DOS33Sectors = 16 // Sectors per track
|
||||
// DOS33DiskBytes is the number of bytes on a DOS 3.3 disk.
|
||||
DOS33DiskBytes = 143360 // 35 tracks * 16 sectors * 256 bytes
|
||||
DOS33TrackBytes = 256 * DOS33Sectors // Bytes per track
|
||||
)
|
||||
|
||||
// DSK represents a .dsk disk image.
|
||||
type DSK struct {
|
||||
data []byte // The actual data in the file
|
||||
sectors byte // Number of sectors per track
|
||||
physicalToStored []byte // Map of physical on-disk sector numbers to sectors in the disk image
|
||||
bytesPerTrack int // Number of bytes per track
|
||||
tracks byte // Number of tracks
|
||||
}
|
||||
|
||||
var _ SectorDisk = (*DSK)(nil)
|
||||
|
||||
// LoadDSK loads a .dsk image from a file.
|
||||
func LoadDSK(filename string) (DSK, error) {
|
||||
bb, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return DSK{}, err
|
||||
}
|
||||
// TODO(zellyn): handle 13-sector disks.
|
||||
if len(bb) != DOS33DiskBytes {
|
||||
return DSK{}, fmt.Errorf("Expected file %q to contain %d bytes, but got %d.", filename, DOS33DiskBytes, len(bb))
|
||||
}
|
||||
return DSK{
|
||||
data: bb,
|
||||
sectors: 16,
|
||||
physicalToStored: Dos33PhysicalToLogicalSectorMap,
|
||||
bytesPerTrack: 16 * 256,
|
||||
tracks: DOS33Tracks,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ReadPhysicalSector reads a single physical sector from the disk. It
|
||||
// always returns 256 byes.
|
||||
func (d DSK) ReadPhysicalSector(track byte, sector byte) ([]byte, error) {
|
||||
if track >= d.tracks {
|
||||
return nil, fmt.Errorf("ReadPhysicalSector expected track between 0 and %d; got %d", d.tracks-1, track)
|
||||
}
|
||||
if sector >= d.sectors {
|
||||
return nil, fmt.Errorf("ReadPhysicalSector expected sector between 0 and %d; got %d", d.sectors-1, sector)
|
||||
}
|
||||
|
||||
storedSector := d.physicalToStored[int(sector)]
|
||||
start := int(track)*d.bytesPerTrack + 256*int(storedSector)
|
||||
buf := make([]byte, 256)
|
||||
copy(buf, d.data[start:start+256])
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// WritePhysicalSector writes a single physical sector to a disk. It
|
||||
// expects exactly 256 bytes.
|
||||
func (d DSK) WritePhysicalSector(track byte, sector byte, data []byte) error {
|
||||
if track >= d.tracks {
|
||||
return fmt.Errorf("WritePhysicalSector expected track between 0 and %d; got %d", d.tracks-1, track)
|
||||
}
|
||||
if sector >= d.sectors {
|
||||
return fmt.Errorf("WritePhysicalSector expected sector between 0 and %d; got %d", d.sectors-1, sector)
|
||||
}
|
||||
if len(data) != 256 {
|
||||
return fmt.Errorf("WritePhysicalSector expects data of length 256; got %d", len(data))
|
||||
}
|
||||
|
||||
storedSector := d.physicalToStored[int(sector)]
|
||||
start := int(track)*d.bytesPerTrack + 256*int(storedSector)
|
||||
copy(d.data[start:start+256], data)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sectors returns the number of sectors on the DSK image.
|
||||
func (d DSK) Sectors() byte {
|
||||
return d.sectors
|
||||
}
|
||||
|
||||
// Tracks returns the number of tracks on the DSK image.
|
||||
func (d DSK) Tracks() byte {
|
||||
return d.tracks
|
||||
// Open opens a disk image by filename.
|
||||
func Open(filename string) (SectorDisk, error) {
|
||||
ext := strings.ToLower(path.Ext(filename))
|
||||
switch ext {
|
||||
case ".dsk":
|
||||
return LoadDSK(filename)
|
||||
}
|
||||
return nil, fmt.Errorf("Unimplemented/unknown disk file extension %q", ext)
|
||||
}
|
||||
|
86
lib/disk/dsk.go
Normal file
86
lib/disk/dsk.go
Normal file
@ -0,0 +1,86 @@
|
||||
// Copyright © 2016 Zellyn Hunter <zellyn@gmail.com>
|
||||
|
||||
// dsk.go contains logic for reading ".dsk" disk images.
|
||||
|
||||
package disk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// DSK represents a .dsk disk image.
|
||||
type DSK struct {
|
||||
data []byte // The actual data in the file
|
||||
sectors byte // Number of sectors per track
|
||||
physicalToStored []byte // Map of physical on-disk sector numbers to sectors in the disk image
|
||||
bytesPerTrack int // Number of bytes per track
|
||||
tracks byte // Number of tracks
|
||||
}
|
||||
|
||||
var _ SectorDisk = (*DSK)(nil)
|
||||
|
||||
// LoadDSK loads a .dsk image from a file.
|
||||
func LoadDSK(filename string) (DSK, error) {
|
||||
bb, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return DSK{}, err
|
||||
}
|
||||
// TODO(zellyn): handle 13-sector disks.
|
||||
if len(bb) != DOS33DiskBytes {
|
||||
return DSK{}, fmt.Errorf("Expected file %q to contain %d bytes, but got %d.", filename, DOS33DiskBytes, len(bb))
|
||||
}
|
||||
return DSK{
|
||||
data: bb,
|
||||
sectors: 16,
|
||||
physicalToStored: Dos33PhysicalToLogicalSectorMap,
|
||||
bytesPerTrack: 16 * 256,
|
||||
tracks: DOS33Tracks,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ReadPhysicalSector reads a single physical sector from the disk. It
|
||||
// always returns 256 byes.
|
||||
func (d DSK) ReadPhysicalSector(track byte, sector byte) ([]byte, error) {
|
||||
if track >= d.tracks {
|
||||
return nil, fmt.Errorf("ReadPhysicalSector expected track between 0 and %d; got %d", d.tracks-1, track)
|
||||
}
|
||||
if sector >= d.sectors {
|
||||
return nil, fmt.Errorf("ReadPhysicalSector expected sector between 0 and %d; got %d", d.sectors-1, sector)
|
||||
}
|
||||
|
||||
storedSector := d.physicalToStored[int(sector)]
|
||||
start := int(track)*d.bytesPerTrack + 256*int(storedSector)
|
||||
buf := make([]byte, 256)
|
||||
copy(buf, d.data[start:start+256])
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// WritePhysicalSector writes a single physical sector to a disk. It
|
||||
// expects exactly 256 bytes.
|
||||
func (d DSK) WritePhysicalSector(track byte, sector byte, data []byte) error {
|
||||
if track >= d.tracks {
|
||||
return fmt.Errorf("WritePhysicalSector expected track between 0 and %d; got %d", d.tracks-1, track)
|
||||
}
|
||||
if sector >= d.sectors {
|
||||
return fmt.Errorf("WritePhysicalSector expected sector between 0 and %d; got %d", d.sectors-1, sector)
|
||||
}
|
||||
if len(data) != 256 {
|
||||
return fmt.Errorf("WritePhysicalSector expects data of length 256; got %d", len(data))
|
||||
}
|
||||
|
||||
storedSector := d.physicalToStored[int(sector)]
|
||||
start := int(track)*d.bytesPerTrack + 256*int(storedSector)
|
||||
copy(d.data[start:start+256], data)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sectors returns the number of sectors on the DSK image.
|
||||
func (d DSK) Sectors() byte {
|
||||
return d.sectors
|
||||
}
|
||||
|
||||
// Tracks returns the number of tracks on the DSK image.
|
||||
func (d DSK) Tracks() byte {
|
||||
return d.tracks
|
||||
}
|
72
lib/disk/ops.go
Normal file
72
lib/disk/ops.go
Normal file
@ -0,0 +1,72 @@
|
||||
// Copyright © 2016 Zellyn Hunter <zellyn@gmail.com>
|
||||
|
||||
// ops.go contains the interfaces and helper functions for operating
|
||||
// on disk images logically: catalog, rename, delete, create files,
|
||||
// etc.
|
||||
|
||||
package disk
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Descriptor describes a file's characteristics.
|
||||
type Descriptor struct {
|
||||
Name string
|
||||
Sectors int
|
||||
Length int
|
||||
Locked bool
|
||||
}
|
||||
|
||||
// Operator is the interface that can operate on disks.
|
||||
type Operator interface {
|
||||
// Name returns the name of the operator.
|
||||
Name() string
|
||||
// HasSubdirs returns true if the underlying operating system on the
|
||||
// disk allows subdirectories.
|
||||
HasSubdirs() bool
|
||||
// Catalog returns a catalog of disk entries. subdir should be empty
|
||||
// for operating systems that do not support subdirectories.
|
||||
Catalog(subdir string) ([]Descriptor, error)
|
||||
}
|
||||
|
||||
// operatorFactory 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)
|
||||
|
||||
// operatorFactories is the map of currently-registered operator
|
||||
// factories.
|
||||
var operatorFactories map[string]operatorFactory
|
||||
|
||||
func init() {
|
||||
operatorFactories = make(map[string]operatorFactory)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// OperatorFor returns an Operator for the given SectorDisk, if possible.
|
||||
func OperatorFor(sd SectorDisk) (Operator, error) {
|
||||
if len(operatorFactories) == 0 {
|
||||
return nil, errors.New("Cannot find an operator matching the given disk image (none registered)")
|
||||
}
|
||||
for _, factory := range operatorFactories {
|
||||
if operator, err := factory(sd); err == nil {
|
||||
return operator, nil
|
||||
}
|
||||
}
|
||||
names := make([]string, 0, len(operatorFactories))
|
||||
for name := range operatorFactories {
|
||||
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, ", "))
|
||||
}
|
@ -454,3 +454,63 @@ func ReadCatalog(d disk.LogicalSectorDisk) (files, deleted []FileDesc, err error
|
||||
}
|
||||
return files, deleted, nil
|
||||
}
|
||||
|
||||
// operator is a disk.Operator - an interface for performing
|
||||
// high-level operations on files and directories.
|
||||
type operator struct {
|
||||
lsd disk.LogicalSectorDisk
|
||||
}
|
||||
|
||||
var _ disk.Operator = operator{}
|
||||
|
||||
// operatorName is the keyword name for the operator that undestands
|
||||
// dos33 disks.
|
||||
const operatorName = "dos33"
|
||||
|
||||
// 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 false
|
||||
}
|
||||
|
||||
// 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) {
|
||||
fds, _, err := ReadCatalog(o.lsd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
descs := make([]disk.Descriptor, 0, len(fds))
|
||||
for _, fd := range fds {
|
||||
descs = append(descs, disk.Descriptor{
|
||||
Name: fd.FilenameString(),
|
||||
Sectors: int(fd.SectorCount),
|
||||
Length: -1, // TODO(zellyn): read actual file length
|
||||
Locked: (fd.Filetype & FiletypeLocked) > 0,
|
||||
})
|
||||
}
|
||||
return descs, nil
|
||||
}
|
||||
|
||||
// operatorFactory is the factory that returns dos33 operators given
|
||||
// disk images.
|
||||
func operatorFactory(sd disk.SectorDisk) (disk.Operator, error) {
|
||||
lsd, err := disk.NewMappedDisk(sd, disk.Dos33LogicalToPhysicalSectorMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, _, err = ReadCatalog(lsd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot read catalog. Underlying error: %v", err)
|
||||
}
|
||||
return operator{lsd: lsd}, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
disk.RegisterOperatorFactory(operatorName, operatorFactory)
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ package supermon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/zellyn/diskii/lib/disk"
|
||||
)
|
||||
@ -211,3 +212,88 @@ func (st SymbolTable) SymbolsByAddress() map[uint16][]Symbol {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func FilenameString(file byte, symbols []Symbol) string {
|
||||
if len(symbols) > 0 {
|
||||
for _, symbol := range symbols {
|
||||
if strings.HasPrefix(symbol.Name, "F") {
|
||||
return symbol.Name
|
||||
}
|
||||
}
|
||||
return symbols[0].Name
|
||||
}
|
||||
return fmt.Sprintf("%02X", file)
|
||||
}
|
||||
|
||||
// operator is a disk.Operator - an interface for performing
|
||||
// high-level operations on files and directories.
|
||||
type operator struct {
|
||||
sd disk.SectorDisk
|
||||
sm SectorMap
|
||||
st SymbolTable
|
||||
symbols map[uint16][]Symbol
|
||||
}
|
||||
|
||||
var _ disk.Operator = operator{}
|
||||
|
||||
// operatorName is the keyword name for the operator that undestands
|
||||
// NakedOS/Super-Mon disks.
|
||||
const operatorName = "nakedos"
|
||||
|
||||
// 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 false
|
||||
}
|
||||
|
||||
// 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) {
|
||||
var descs []disk.Descriptor
|
||||
sectorsByFile := o.sm.SectorsByFile()
|
||||
for file := byte(1); file < FileReserved; file++ {
|
||||
l := len(sectorsByFile[file])
|
||||
if l == 0 {
|
||||
continue
|
||||
}
|
||||
fileAddr := uint16(0xDF00) + uint16(file)
|
||||
descs = append(descs, disk.Descriptor{
|
||||
Name: FilenameString(file, o.symbols[fileAddr]),
|
||||
Sectors: l,
|
||||
Length: l * 256,
|
||||
Locked: false,
|
||||
})
|
||||
}
|
||||
return descs, nil
|
||||
}
|
||||
|
||||
// operatorFactory is the factory that returns dos33 operators given
|
||||
// disk images.
|
||||
func operatorFactory(sd disk.SectorDisk) (disk.Operator, error) {
|
||||
sm, err := LoadSectorMap(sd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := sm.Verify(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
op := operator{sd: sd, sm: sm}
|
||||
|
||||
st, err := sm.ReadSymbolTable(sd)
|
||||
if err == nil {
|
||||
op.st = st
|
||||
op.symbols = st.SymbolsByAddress()
|
||||
}
|
||||
|
||||
return op, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
disk.RegisterOperatorFactory(operatorName, operatorFactory)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user