Initial implementation of NakeOS/Super-Mon GetFile

This commit is contained in:
Zellyn Hunter 2016-11-14 22:54:57 -05:00
parent d901a9a0ba
commit 2cf6d2d4a3
6 changed files with 148 additions and 24 deletions

View File

@ -35,7 +35,8 @@ Current disk operations supported:
| Feature | DOS 3.3 | NakedOS/Super-Mon |
| ------------- | ------------------ | ------------------ |
| catalog | :white_check_mark: | :white_check_mark: |
| ls | :white_check_mark: | :white_check_mark: |
| dump | :x: | :white_check_mark: |
### Installing/updating
Assuming you have Go installed, run `go get -u github.com/zellyn/diskii`

View File

@ -12,12 +12,13 @@ import (
_ "github.com/zellyn/diskii/lib/supermon"
)
// catCmd represents the cat command, used to catalog a disk or
// catalogCmd 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.`,
var catalogCmd = &cobra.Command{
Use: "catalog",
Aliases: []string{"cat", "ls"},
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())
@ -27,7 +28,7 @@ var catCmd = &cobra.Command{
}
func init() {
RootCmd.AddCommand(catCmd)
RootCmd.AddCommand(catalogCmd)
}
// runCat performs the actual catalog logic.

56
cmd/dump.go Normal file
View File

@ -0,0 +1,56 @@
// 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"
)
// dumpCmd represents the dump command, used to dump the raw contents
// of a file.
var dumpCmd = &cobra.Command{
Use: "dump",
Short: "dump the raw contents of a file",
Long: `Dump the raw contents of a file.
dump disk-image.dsk HELLO
`,
Run: func(cmd *cobra.Command, args []string) {
if err := runDump(args); err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(-1)
}
},
}
func init() {
RootCmd.AddCommand(dumpCmd)
}
// runDump performs the actual dump logic.
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)
if err != nil {
return err
}
file, err := op.GetFile(args[1])
if err != nil {
return err
}
// TODO(zellyn): allow writing to files
os.Stdout.Write(file.Data)
return nil
}

View File

@ -31,6 +31,14 @@ type Operator interface {
// Catalog returns a catalog of disk entries. subdir should be empty
// for operating systems that do not support subdirectories.
Catalog(subdir string) ([]Descriptor, error)
// GetFile retrieves a file by name.
GetFile(filename string) (FileInfo, error)
}
// FileInfo represents a file descriptor plus the content.
type FileInfo struct {
Descriptor Descriptor
Data []byte
}
// operatorFactory is the type of functions that accept a SectorDisk,

View File

@ -497,6 +497,12 @@ func (o operator) Catalog(subdir string) ([]disk.Descriptor, error) {
return descs, nil
}
// GetFile retrieves a file by name.
func (o operator) GetFile(filename string) (disk.FileInfo, error) {
// TODO(zellyn): Implement GetFile
return disk.FileInfo{}, fmt.Errorf("%s does not yet implement `GetFile`", operatorName)
}
// operatorFactory is the factory that returns dos33 operators given
// disk images.
func operatorFactory(sd disk.SectorDisk) (disk.Operator, error) {

View File

@ -8,6 +8,7 @@ package supermon
import (
"fmt"
"strconv"
"strings"
"github.com/zellyn/diskii/lib/disk"
@ -67,13 +68,14 @@ func (sm SectorMap) Verify() error {
return nil
}
// FileForSector returns the file that owns the given track/sector.
// FileForSector returns the file that owns the given track/sector, or
// zero if the track or sector is too high.
func (sm SectorMap) FileForSector(track, sector byte) byte {
if track >= 35 {
panic(fmt.Sprintf("FileForSector called with track=%d > 34", track))
return FileIllegal
}
if sector >= 16 {
panic(fmt.Sprintf("FileForSector called with sector=%d > 15", sector))
return FileIllegal
}
return sm[int(track)*16+int(sector)]
}
@ -105,16 +107,16 @@ func (sm SectorMap) SectorsByFile() map[byte][]disk.TrackSector {
}
// ReadFile reads the contents of a file.
func (sm SectorMap) ReadFile(sd disk.SectorDisk, file byte) []byte {
func (sm SectorMap) ReadFile(sd disk.SectorDisk, file byte) ([]byte, error) {
var result []byte
for _, ts := range sm.SectorsForFile(file) {
bytes, err := sd.ReadPhysicalSector(ts.Track, ts.Sector)
if err != nil {
panic(err.Error())
return nil, err
}
result = append(result, bytes...)
}
return result
return result, nil
}
// Symbol represents a single Super-Mon symbol.
@ -159,11 +161,17 @@ type SymbolTable []Symbol
// pointers are problematic), it'll return nil and an error.
func (sm SectorMap) ReadSymbolTable(sd disk.SectorDisk) (SymbolTable, error) {
table := make(SymbolTable, 0, 819)
symtbl1 := sm.ReadFile(sd, 3)
symtbl1, err := sm.ReadFile(sd, 3)
if err != nil {
return nil, err
}
if len(symtbl1) != 0x1000 {
return nil, fmt.Errorf("expected file FSYMTBL1(0x3) to be 0x1000 bytes long; got 0x%04X", len(symtbl1))
}
symtbl2 := sm.ReadFile(sd, 4)
symtbl2, err := sm.ReadFile(sd, 4)
if err != nil {
return nil, err
}
if len(symtbl2) != 0x1000 {
return nil, fmt.Errorf("expected file FSYMTBL1(0x4) to be 0x1000 bytes long; got 0x%04X", len(symtbl2))
}
@ -213,16 +221,35 @@ func (st SymbolTable) SymbolsByAddress() map[uint16][]Symbol {
return result
}
func FilenameString(file byte, symbols []Symbol) string {
// NameForFile returns a string representation of a filename:
// either DFxx, or a symbol, if one exists for that value.
func NameForFile(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)
return fmt.Sprintf("DF%02X", file)
}
// FileForName returns a byte file number for a representation of a
// filename: either DFxx, or a symbol, if one exists with the given
// name and points to a DFxx address.
func (st SymbolTable) FileForName(filename string) (byte, error) {
if addr, err := strconv.ParseUint(filename, 16, 16); err == nil {
if addr > 0xDF00 && addr < 0xDFFE {
return byte(addr - 0xDF00), nil
}
}
for _, symbol := range st {
if strings.EqualFold(symbol.Name, filename) {
if symbol.Address > 0xDF00 && symbol.Address < 0xDFFE {
return byte(symbol.Address - 0xDF00), nil
}
break
}
}
return 0, fmt.Errorf("invalid filename: %q", filename)
}
// operator is a disk.Operator - an interface for performing
@ -261,9 +288,9 @@ func (o operator) Catalog(subdir string) ([]disk.Descriptor, error) {
if l == 0 {
continue
}
fileAddr := uint16(0xDF00) + uint16(file)
fileAddr := 0xDF00 + uint16(file)
descs = append(descs, disk.Descriptor{
Name: FilenameString(file, o.symbols[fileAddr]),
Name: NameForFile(file, o.symbols[fileAddr]),
Sectors: l,
Length: l * 256,
Locked: false,
@ -272,6 +299,31 @@ func (o operator) Catalog(subdir string) ([]disk.Descriptor, error) {
return descs, nil
}
// GetFile retrieves a file by name.
func (o operator) GetFile(filename string) (disk.FileInfo, error) {
file, err := o.st.FileForName(filename)
if err != nil {
return disk.FileInfo{}, err
}
data, err := o.sm.ReadFile(o.sd, file)
if err != nil {
return disk.FileInfo{}, fmt.Errorf("error reading file DF%02x: %v", file, err)
}
if len(data) == 0 {
return disk.FileInfo{}, fmt.Errorf("file DF%02x not fount", file)
}
desc := disk.Descriptor{
Name: NameForFile(file, o.symbols[0xDF00+uint16(file)]),
Sectors: len(data) / 256,
Length: len(data),
Locked: false,
}
return disk.FileInfo{
Descriptor: desc,
Data: data,
}, nil
}
// operatorFactory is the factory that returns dos33 operators given
// disk images.
func operatorFactory(sd disk.SectorDisk) (disk.Operator, error) {