From 2cf6d2d4a399cc4e062460f48fc88fda0eadc032 Mon Sep 17 00:00:00 2001 From: Zellyn Hunter Date: Mon, 14 Nov 2016 22:54:57 -0500 Subject: [PATCH] Initial implementation of NakeOS/Super-Mon GetFile --- README.md | 3 +- cmd/{cat.go => catalog.go} | 13 +++--- cmd/dump.go | 56 +++++++++++++++++++++++++ lib/disk/ops.go | 8 ++++ lib/dos33/dos33.go | 6 +++ lib/supermon/supermon.go | 86 ++++++++++++++++++++++++++++++-------- 6 files changed, 148 insertions(+), 24 deletions(-) rename cmd/{cat.go => catalog.go} (79%) create mode 100644 cmd/dump.go diff --git a/README.md b/README.md index e6c738d..3eede52 100644 --- a/README.md +++ b/README.md @@ -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` diff --git a/cmd/cat.go b/cmd/catalog.go similarity index 79% rename from cmd/cat.go rename to cmd/catalog.go index 73235d8..955d349 100644 --- a/cmd/cat.go +++ b/cmd/catalog.go @@ -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. diff --git a/cmd/dump.go b/cmd/dump.go new file mode 100644 index 0000000..255c79a --- /dev/null +++ b/cmd/dump.go @@ -0,0 +1,56 @@ +// Copyright © 2016 Zellyn Hunter + +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 +} diff --git a/lib/disk/ops.go b/lib/disk/ops.go index ff94df1..a7ed3c2 100644 --- a/lib/disk/ops.go +++ b/lib/disk/ops.go @@ -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, diff --git a/lib/dos33/dos33.go b/lib/dos33/dos33.go index a930ac2..c75b092 100644 --- a/lib/dos33/dos33.go +++ b/lib/dos33/dos33.go @@ -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) { diff --git a/lib/supermon/supermon.go b/lib/supermon/supermon.go index f1ede6d..74a20bf 100644 --- a/lib/supermon/supermon.go +++ b/lib/supermon/supermon.go @@ -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) {