mirror of
https://github.com/zellyn/diskii.git
synced 2024-11-22 15:30:48 +00:00
Add stubbed-out delete, supermon symbol encoding
This commit is contained in:
parent
e6508a39b4
commit
0fdf6f05c4
68
cmd/delete.go
Normal file
68
cmd/delete.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// 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/dos3"
|
||||||
|
_ "github.com/zellyn/diskii/lib/supermon"
|
||||||
|
)
|
||||||
|
|
||||||
|
// deleteCmd represents the delete command, used to delete a file.
|
||||||
|
var deleteCmd = &cobra.Command{
|
||||||
|
Use: "delete",
|
||||||
|
Short: "delete a file",
|
||||||
|
Long: `Delete a file.
|
||||||
|
|
||||||
|
delete disk-image.dsk HELLO
|
||||||
|
`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if err := runDelete(args); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err.Error())
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RootCmd.AddCommand(deleteCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// runDelete performs the actual delete logic.
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
deleted, err := op.Delete(args[1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !deleted {
|
||||||
|
// TODO(zellyn): implement -f flag to not warn on nonexistence.
|
||||||
|
return fmt.Errorf("file %q not found", args[1])
|
||||||
|
}
|
||||||
|
f, err := os.Create(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = sd.Write(f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = f.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -6,6 +6,7 @@ package disk
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@ -49,6 +50,8 @@ type SectorDisk interface {
|
|||||||
Sectors() byte
|
Sectors() byte
|
||||||
// Tracks returns the number of tracks on the SectorDisk
|
// Tracks returns the number of tracks on the SectorDisk
|
||||||
Tracks() byte
|
Tracks() byte
|
||||||
|
// Write writes the disk contents to the given file.
|
||||||
|
Write(io.Writer) (int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type LogicalSectorDisk interface {
|
type LogicalSectorDisk interface {
|
||||||
@ -62,6 +65,8 @@ type LogicalSectorDisk interface {
|
|||||||
Sectors() byte
|
Sectors() byte
|
||||||
// Tracks returns the number of tracks on the SectorDisk
|
// Tracks returns the number of tracks on the SectorDisk
|
||||||
Tracks() byte
|
Tracks() byte
|
||||||
|
// Write writes the disk contents to the given file.
|
||||||
|
Write(io.Writer) (int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MappedDisk wraps a SectorDisk as a LogicalSectorDisk, handling the
|
// MappedDisk wraps a SectorDisk as a LogicalSectorDisk, handling the
|
||||||
@ -125,6 +130,11 @@ func (md MappedDisk) Tracks() byte {
|
|||||||
return md.sectorDisk.Tracks()
|
return md.sectorDisk.Tracks()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write writes the disk contents to the given file.
|
||||||
|
func (md MappedDisk) Write(w io.Writer) (n int, err error) {
|
||||||
|
return md.sectorDisk.Write(w)
|
||||||
|
}
|
||||||
|
|
||||||
// Open opens a disk image by filename.
|
// Open opens a disk image by filename.
|
||||||
func Open(filename string) (SectorDisk, error) {
|
func Open(filename string) (SectorDisk, error) {
|
||||||
ext := strings.ToLower(path.Ext(filename))
|
ext := strings.ToLower(path.Ext(filename))
|
||||||
|
@ -6,6 +6,7 @@ package disk
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -84,3 +85,8 @@ func (d DSK) Sectors() byte {
|
|||||||
func (d DSK) Tracks() byte {
|
func (d DSK) Tracks() byte {
|
||||||
return d.tracks
|
return d.tracks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write writes the disk contents to the given file.
|
||||||
|
func (d DSK) Write(w io.Writer) (n int, err error) {
|
||||||
|
return w.Write(d.data)
|
||||||
|
}
|
||||||
|
@ -83,6 +83,9 @@ type Operator interface {
|
|||||||
Catalog(subdir string) ([]Descriptor, error)
|
Catalog(subdir string) ([]Descriptor, error)
|
||||||
// GetFile retrieves a file by name.
|
// GetFile retrieves a file by name.
|
||||||
GetFile(filename string) (FileInfo, error)
|
GetFile(filename string) (FileInfo, error)
|
||||||
|
// Delete deletes a file by name. It returns true if the file was
|
||||||
|
// deleted, false if it didn't exist.
|
||||||
|
Delete(filename string) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileInfo represents a file descriptor plus the content.
|
// FileInfo represents a file descriptor plus the content.
|
||||||
|
@ -651,6 +651,12 @@ func (o operator) GetFile(filename string) (disk.FileInfo, error) {
|
|||||||
return disk.FileInfo{}, fmt.Errorf("%s does not yet implement `GetFile` for filetype %s", operatorName, errType)
|
return disk.FileInfo{}, fmt.Errorf("%s does not yet implement `GetFile` for filetype %s", operatorName, errType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 does not implement Delete yet", operatorName)
|
||||||
|
}
|
||||||
|
|
||||||
// operatorFactory is the factory that returns dos3 operators given
|
// operatorFactory is the factory that returns dos3 operators given
|
||||||
// disk images.
|
// disk images.
|
||||||
func operatorFactory(sd disk.SectorDisk) (disk.Operator, error) {
|
func operatorFactory(sd disk.SectorDisk) (disk.Operator, error) {
|
||||||
|
@ -4,9 +4,8 @@
|
|||||||
// structures of NakedOS/Super-Mon disks.
|
// structures of NakedOS/Super-Mon disks.
|
||||||
package supermon
|
package supermon
|
||||||
|
|
||||||
// TODO(zellyn): remove panics.
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -152,6 +151,44 @@ func decodeSymbol(five []byte, extra byte) string {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// encodeSymbol encodes a symbol name into the five+1 bytes used in a
|
||||||
|
// Super-Mon encoded symbol table entry. The returned byte array will
|
||||||
|
// always be six bytes long. If it can't be encoded, it returns an
|
||||||
|
// error.
|
||||||
|
func encodeSymbol(name string) (six []byte, err error) {
|
||||||
|
if len(name) > 9 {
|
||||||
|
return nil, fmt.Errorf("invalid Super-Mon symbol %q: too long", name)
|
||||||
|
}
|
||||||
|
if len(name) < 3 {
|
||||||
|
return nil, fmt.Errorf("invalid Super-Mon symbol %q: too short", name)
|
||||||
|
}
|
||||||
|
nm := []byte(strings.ToUpper(name))
|
||||||
|
value := uint64(0)
|
||||||
|
bits := 0
|
||||||
|
for i := len(nm) - 1; i >= 0; i-- {
|
||||||
|
ch := nm[i]
|
||||||
|
switch {
|
||||||
|
case 'A' <= ch && ch <= 'Z':
|
||||||
|
value = value<<5 + uint64(ch-'@')
|
||||||
|
bits += 5
|
||||||
|
case '0' <= ch && ch <= '4':
|
||||||
|
value = value<<6 + 0x1b + uint64(ch-'0')
|
||||||
|
bits += 6
|
||||||
|
case '5' <= ch && ch <= '9':
|
||||||
|
value = value<<6 + 0x3b + uint64(ch-'5')
|
||||||
|
bits += 6
|
||||||
|
}
|
||||||
|
if bits > 48 {
|
||||||
|
return nil, fmt.Errorf("invalid Super-Mon symbol %q: too long", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
eight := make([]byte, 8)
|
||||||
|
six = make([]byte, 6)
|
||||||
|
binary.LittleEndian.PutUint64(eight, value)
|
||||||
|
copy(six, eight)
|
||||||
|
return six, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SymbolTable represents an entire Super-Mon symbol table. It'll
|
// SymbolTable represents an entire Super-Mon symbol table. It'll
|
||||||
// always be 819 entries long, because it includes blanks.
|
// always be 819 entries long, because it includes blanks.
|
||||||
type SymbolTable []Symbol
|
type SymbolTable []Symbol
|
||||||
@ -192,7 +229,7 @@ func (sm SectorMap) ReadSymbolTable(sd disk.SectorDisk) (SymbolTable, error) {
|
|||||||
if (linkAddr-0xD000)%5 != 0 {
|
if (linkAddr-0xD000)%5 != 0 {
|
||||||
return nil, fmt.Errorf("Expected symbol table link address to 0xD000+5x; got 0x%04X", linkAddr)
|
return nil, fmt.Errorf("Expected symbol table link address to 0xD000+5x; got 0x%04X", linkAddr)
|
||||||
}
|
}
|
||||||
link = (int(linkAddr) - 0xD000) % 5
|
link = (int(linkAddr) - 0xD000) / 5
|
||||||
}
|
}
|
||||||
extra := symtbl1[i+4]
|
extra := symtbl1[i+4]
|
||||||
copy(five, symtbl2[i:i+5])
|
copy(five, symtbl2[i:i+5])
|
||||||
@ -205,11 +242,27 @@ func (sm SectorMap) ReadSymbolTable(sd disk.SectorDisk) (SymbolTable, error) {
|
|||||||
table = append(table, symbol)
|
table = append(table, symbol)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(zellyn): check link addresses.
|
for i, sym := range table {
|
||||||
|
if sym.Address != 0 && sym.Link != -1 {
|
||||||
|
if sym.Link == i {
|
||||||
|
return nil, fmt.Errorf("Symbol %q (0x%04X) links to itself", sym.Name, sym.Address)
|
||||||
|
}
|
||||||
|
linkSym := table[sym.Link]
|
||||||
|
if addrHash(sym.Address) != addrHash(linkSym.Address) {
|
||||||
|
return nil, fmt.Errorf("Symbol %q (0x%04X) with hash 0x%02X links to symbol %q (0x%04X) with hash 0x%02X",
|
||||||
|
sym.Name, sym.Address, addrHash(sym.Address), linkSym.Name, linkSym.Address, addrHash(linkSym.Address))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return table, nil
|
return table, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addrHash computes the SuperMon hash for an address.
|
||||||
|
func addrHash(addr uint16) byte {
|
||||||
|
return (byte(addr) ^ byte(addr>>8)) & 0x7f
|
||||||
|
}
|
||||||
|
|
||||||
// SymbolsByAddress returns a map of addresses to slices of symbols.
|
// SymbolsByAddress returns a map of addresses to slices of symbols.
|
||||||
func (st SymbolTable) SymbolsByAddress() map[uint16][]Symbol {
|
func (st SymbolTable) SymbolsByAddress() map[uint16][]Symbol {
|
||||||
result := map[uint16][]Symbol{}
|
result := map[uint16][]Symbol{}
|
||||||
@ -319,12 +372,21 @@ func (o operator) GetFile(filename string) (disk.FileInfo, error) {
|
|||||||
Length: len(data),
|
Length: len(data),
|
||||||
Locked: false,
|
Locked: false,
|
||||||
Type: disk.FiletypeBinary,
|
Type: disk.FiletypeBinary,
|
||||||
// TODO(zellyn): Set StartAddress if we know it.
|
|
||||||
}
|
}
|
||||||
return disk.FileInfo{
|
fi := disk.FileInfo{
|
||||||
Descriptor: desc,
|
Descriptor: desc,
|
||||||
Data: data,
|
Data: data,
|
||||||
}, nil
|
}
|
||||||
|
if file == 1 {
|
||||||
|
fi.StartAddress = 0x1800
|
||||||
|
}
|
||||||
|
return fi, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 does not implement Delete yet", operatorName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// operatorFactory is the factory that returns supermon operators
|
// operatorFactory is the factory that returns supermon operators
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
package supermon
|
package supermon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/zellyn/diskii/lib/disk"
|
"github.com/zellyn/diskii/lib/disk"
|
||||||
@ -137,3 +138,43 @@ No more; and by a sleep, to say we end
|
|||||||
t.Errorf("Incorrect result for GetFile(\"TOBE\"): want %q; got %q", want, got)
|
t.Errorf("Incorrect result for GetFile(\"TOBE\"): want %q; got %q", want, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestEncodeDecode tests encoding and decoding of Super-Mon symbol
|
||||||
|
// table entries.
|
||||||
|
func TestEncodeDecode(t *testing.T) {
|
||||||
|
testdata := []struct {
|
||||||
|
sym string
|
||||||
|
valid bool
|
||||||
|
}{
|
||||||
|
{"ABC", true},
|
||||||
|
{"abc", true},
|
||||||
|
{"ABCDEFGHI", true},
|
||||||
|
{"abcdefghi", true},
|
||||||
|
{"ABCDEF123", true},
|
||||||
|
{"abcdef123", true},
|
||||||
|
|
||||||
|
{"AB", false},
|
||||||
|
{"ab", false},
|
||||||
|
{"ABCDE1234", false},
|
||||||
|
{"abcde1234", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range testdata {
|
||||||
|
if !tt.valid {
|
||||||
|
if _, err := encodeSymbol(tt.sym); err == nil {
|
||||||
|
t.Errorf("Expected symbol %q to be invalid, but wansn't", tt.sym)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes, err := encodeSymbol(tt.sym)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error encoding symbol %q", tt.sym)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sym := decodeSymbol(bytes[:5], bytes[5])
|
||||||
|
if sym != strings.ToUpper(tt.sym) {
|
||||||
|
t.Errorf("Symbol %q encodes to %q", tt.sym, sym)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user