2016-11-09 04:37:15 +00:00
// Copyright © 2016 Zellyn Hunter <zellyn@gmail.com>
// Package supermon contains routines for working with the on-disk
// structures of NakedOS/Super-Mon disks.
package supermon
import (
2016-11-23 03:38:20 +00:00
"encoding/binary"
2016-11-09 04:37:15 +00:00
"fmt"
2017-03-18 02:26:15 +00:00
"io"
2016-11-15 03:54:57 +00:00
"strconv"
2016-11-13 21:59:38 +00:00
"strings"
2016-11-09 04:37:15 +00:00
"github.com/zellyn/diskii/lib/disk"
2016-11-28 01:23:31 +00:00
"github.com/zellyn/diskii/lib/errors"
2016-11-09 04:37:15 +00:00
)
const (
// FileIllegal (zero) is not allowed in the sector map.
FileIllegal = 0
// FileFree signifies unused space in the sector map.
FileFree = 0xff
// FileReserved signifies space used by NakedOS in the sector map.
FileReserved = 0xfe
)
// SectorMap is the list of sectors by file. It's always 560 bytes
// long (35 tracks * 16 sectors).
type SectorMap [ ] byte
// LoadSectorMap loads a NakedOS sector map.
func LoadSectorMap ( sd disk . SectorDisk ) ( SectorMap , error ) {
sm := SectorMap ( make ( [ ] byte , 560 ) )
2016-11-13 03:15:45 +00:00
sector09 , err := sd . ReadPhysicalSector ( 0 , 9 )
2016-11-09 04:37:15 +00:00
if err != nil {
return sm , err
}
2016-11-13 03:15:45 +00:00
sector0A , err := sd . ReadPhysicalSector ( 0 , 0xA )
2016-11-09 04:37:15 +00:00
if err != nil {
return sm , err
}
2016-11-13 03:15:45 +00:00
sector0B , err := sd . ReadPhysicalSector ( 0 , 0xB )
2016-11-09 04:37:15 +00:00
if err != nil {
return sm , err
}
copy ( sm [ 0 : 0x30 ] , sector09 [ 0xd0 : ] )
copy ( sm [ 0x30 : 0x130 ] , sector0A )
copy ( sm [ 0x130 : 0x230 ] , sector0B )
return sm , nil
}
2016-11-29 03:31:52 +00:00
// FirstFreeFile returns the first file number that isn't already
// used. It returns 0 if all are already used.
func ( sm SectorMap ) FirstFreeFile ( ) byte {
for file := byte ( 0x01 ) ; file < 0xfe ; file ++ {
sectors := sm . SectorsForFile ( file )
if len ( sectors ) == 0 {
return file
}
}
return 0
}
2016-11-28 01:23:31 +00:00
// Persist writes the current contenst of a sector map back back to
// disk.
func ( sm SectorMap ) Persist ( sd disk . SectorDisk ) error {
sector09 , err := sd . ReadPhysicalSector ( 0 , 9 )
if err != nil {
return err
}
copy ( sector09 [ 0xd0 : ] , sm [ 0 : 0x30 ] )
if err := sd . WritePhysicalSector ( 0 , 9 , sector09 ) ; err != nil {
return err
}
if err := sd . WritePhysicalSector ( 0 , 0xA , sm [ 0x30 : 0x130 ] ) ; err != nil {
return err
}
if err := sd . WritePhysicalSector ( 0 , 0xB , sm [ 0x130 : 0x230 ] ) ; err != nil {
return err
}
return nil
}
// FreeSectors returns the number of blocks free in a sector map.
func ( sm SectorMap ) FreeSectors ( ) int {
count := 0
for _ , file := range sm {
if file == FileFree {
count ++
}
}
return count
}
2016-11-13 03:15:45 +00:00
// Verify checks that we actually have a NakedOS disk.
func ( sm SectorMap ) Verify ( ) error {
for sector := byte ( 0 ) ; sector <= 0xB ; sector ++ {
if file := sm . FileForSector ( 0 , sector ) ; file != FileReserved {
return fmt . Errorf ( "Expected track 0, sectors 0-C to be reserved (0xFE), but got 0x%02X in sector %X" , file , sector )
}
}
for track := byte ( 0 ) ; track < 35 ; track ++ {
for sector := byte ( 0 ) ; sector < 16 ; sector ++ {
file := sm . FileForSector ( track , sector )
if file == FileIllegal {
return fmt . Errorf ( "Found illegal sector map value (%02X), in track %X sector %X" , FileIllegal , track , sector )
}
}
}
return nil
}
2016-11-15 03:54:57 +00:00
// FileForSector returns the file that owns the given track/sector, or
// zero if the track or sector is too high.
2016-11-09 04:37:15 +00:00
func ( sm SectorMap ) FileForSector ( track , sector byte ) byte {
if track >= 35 {
2016-11-15 03:54:57 +00:00
return FileIllegal
2016-11-09 04:37:15 +00:00
}
if sector >= 16 {
2016-11-15 03:54:57 +00:00
return FileIllegal
2016-11-09 04:37:15 +00:00
}
return sm [ int ( track ) * 16 + int ( sector ) ]
}
2016-11-29 02:15:15 +00:00
// SetFileForSector sets the file that owns the given track/sector, or
// returns an error if the track or sector is too high.
func ( sm SectorMap ) SetFileForSector ( track , sector , file byte ) error {
if track >= 35 {
return fmt . Errorf ( "track %d >34" , track )
}
if sector >= 16 {
return fmt . Errorf ( "sector %d >15" , sector )
}
if file == FileIllegal || file == FileFree || file == FileReserved {
return fmt . Errorf ( "illegal file number: 0x%0X" , file )
}
sm [ int ( track ) * 16 + int ( sector ) ] = file
return nil
}
2016-11-09 04:37:15 +00:00
// SectorsForFile returns the list of sectors that belong to the given
// file.
func ( sm SectorMap ) SectorsForFile ( file byte ) [ ] disk . TrackSector {
var result [ ] disk . TrackSector
for track := byte ( 0 ) ; track < 35 ; track ++ {
for sector := byte ( 0 ) ; sector < 16 ; sector ++ {
if file == sm . FileForSector ( track , sector ) {
result = append ( result , disk . TrackSector { Track : track , Sector : sector } )
}
}
}
return result
}
2016-11-13 03:15:45 +00:00
// SectorsByFile returns a map of file number to slice of sectors.
func ( sm SectorMap ) SectorsByFile ( ) map [ byte ] [ ] disk . TrackSector {
result := map [ byte ] [ ] disk . TrackSector { }
for file := byte ( 0x01 ) ; file < FileReserved ; file ++ {
sectors := sm . SectorsForFile ( file )
if len ( sectors ) > 0 {
result [ file ] = sectors
2016-11-09 04:37:15 +00:00
}
}
2016-11-13 03:15:45 +00:00
return result
}
2016-11-09 04:37:15 +00:00
2016-11-13 03:15:45 +00:00
// ReadFile reads the contents of a file.
2016-11-15 03:54:57 +00:00
func ( sm SectorMap ) ReadFile ( sd disk . SectorDisk , file byte ) ( [ ] byte , error ) {
2016-11-13 03:15:45 +00:00
var result [ ] byte
for _ , ts := range sm . SectorsForFile ( file ) {
bytes , err := sd . ReadPhysicalSector ( ts . Track , ts . Sector )
if err != nil {
2016-11-15 03:54:57 +00:00
return nil , err
2016-11-13 03:15:45 +00:00
}
result = append ( result , bytes ... )
}
2016-11-15 03:54:57 +00:00
return result , nil
2016-11-13 03:15:45 +00:00
}
2016-11-28 01:23:31 +00:00
// Delete deletes a file from the sector map. It does not persist the changes.
func ( sm SectorMap ) Delete ( file byte ) {
for i , f := range sm {
if f == file {
sm [ i ] = FileFree
}
}
}
2016-11-29 03:31:52 +00:00
// WriteFile writes the contents of a file. It returns true if the
// file already existed.
func ( sm SectorMap ) WriteFile ( sd disk . SectorDisk , file byte , contents [ ] byte , overwrite bool ) ( bool , error ) {
2016-11-28 01:23:31 +00:00
sectorsNeeded := ( len ( contents ) + 255 ) / 256
cts := make ( [ ] byte , 256 * sectorsNeeded )
copy ( cts , contents )
2016-11-28 03:31:51 +00:00
existing := len ( sm . SectorsForFile ( file ) )
2016-11-29 03:31:52 +00:00
existed := existing > 0
2016-11-28 03:31:51 +00:00
free := sm . FreeSectors ( ) + existing
2016-11-28 01:23:31 +00:00
if free < sectorsNeeded {
2016-11-29 03:31:52 +00:00
return existed , errors . OutOfSpacef ( "file %d requires %d sectors, but only %d are available" , file , sectorsNeeded , free )
2016-11-28 03:31:51 +00:00
}
2016-11-29 03:31:52 +00:00
if existed {
2016-11-28 03:31:51 +00:00
if ! overwrite {
2016-11-29 03:31:52 +00:00
return existed , errors . FileExistsf ( "file %d already exists" , file )
2016-11-28 03:31:51 +00:00
}
sm . Delete ( file )
2016-11-28 01:23:31 +00:00
}
i := 0
OUTER :
for track := byte ( 0 ) ; track < sd . Tracks ( ) ; track ++ {
for sector := byte ( 0 ) ; sector < sd . Sectors ( ) ; sector ++ {
2016-11-29 02:15:15 +00:00
if sm . FileForSector ( track , sector ) == FileFree {
2016-11-28 01:23:31 +00:00
if err := sd . WritePhysicalSector ( track , sector , cts [ i * 256 : ( i + 1 ) * 256 ] ) ; err != nil {
2016-11-29 03:31:52 +00:00
return existed , err
2016-11-28 01:23:31 +00:00
}
2016-11-29 02:15:15 +00:00
if err := sm . SetFileForSector ( track , sector , file ) ; err != nil {
2016-11-29 03:31:52 +00:00
return existed , err
2016-11-29 02:15:15 +00:00
}
2016-11-28 01:23:31 +00:00
i ++
if i == sectorsNeeded {
break OUTER
}
}
}
}
if err := sm . Persist ( sd ) ; err != nil {
2016-11-29 03:31:52 +00:00
return existed , err
2016-11-28 01:23:31 +00:00
}
2016-11-29 03:31:52 +00:00
return existed , nil
2016-11-28 01:23:31 +00:00
}
2016-11-13 03:15:45 +00:00
// Symbol represents a single Super-Mon symbol.
type Symbol struct {
// Address is the memory address the symbol points to, or 0 for an
// empty symbol table entry.
Address uint16
// Name is the name of the symbol.
Name string
// Link is the index of the next symbol in the symbol chain for this
// hash key, or -1 if none.
Link int
}
2016-12-01 03:29:42 +00:00
func ( s Symbol ) String ( ) string {
return fmt . Sprintf ( "{%s:%04X:%d}" , s . Name , s . Address , s . Link )
}
2016-11-13 03:15:45 +00:00
// decodeSymbol decodes a Super-Mon encoded symbol table entry,
// returning the string representation.
func decodeSymbol ( five [ ] byte , extra byte ) string {
result := ""
value := uint64 ( five [ 0 ] ) + uint64 ( five [ 1 ] ) << 8 + uint64 ( five [ 2 ] ) << 16 + uint64 ( five [ 3 ] ) << 24 + uint64 ( five [ 4 ] ) << 32 + uint64 ( extra ) << 40
for value & 0x1f > 0 {
if value & 0x1f < 27 {
result = result + string ( value & 0x1f + '@' )
value >>= 5
continue
}
if value & 0x20 == 0 {
result = result + string ( ( value & 0x1f ) - 0x1b + '0' )
} else {
result = result + string ( ( value & 0x1f ) - 0x1b + '5' )
}
value >>= 6
}
return result
}
2016-11-23 03:38:20 +00:00
// 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
2016-11-26 01:56:54 +00:00
// error. Empty strings are encoded as all zeros.
2016-11-23 03:38:20 +00:00
func encodeSymbol ( name string ) ( six [ ] byte , err error ) {
2016-11-26 01:56:54 +00:00
if name == "" {
six := make ( [ ] byte , 6 )
return six , nil
}
2016-11-23 03:38:20 +00:00
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
}
2016-11-13 03:15:45 +00:00
// SymbolTable represents an entire Super-Mon symbol table. It'll
// always be 819 entries long, because it includes blanks.
type SymbolTable [ ] Symbol
// ReadSymbolTable reads the symbol table from a disk. If there are
// problems with the symbol table (like it doesn't exist, or the link
// pointers are problematic), it'll return nil and an error.
func ( sm SectorMap ) ReadSymbolTable ( sd disk . SectorDisk ) ( SymbolTable , error ) {
table := make ( SymbolTable , 0 , 819 )
2016-11-15 03:54:57 +00:00
symtbl1 , err := sm . ReadFile ( sd , 3 )
if err != nil {
return nil , err
}
2016-11-13 03:15:45 +00:00
if len ( symtbl1 ) != 0x1000 {
return nil , fmt . Errorf ( "expected file FSYMTBL1(0x3) to be 0x1000 bytes long; got 0x%04X" , len ( symtbl1 ) )
}
2016-11-15 03:54:57 +00:00
symtbl2 , err := sm . ReadFile ( sd , 4 )
if err != nil {
return nil , err
}
2016-11-13 03:15:45 +00:00
if len ( symtbl2 ) != 0x1000 {
return nil , fmt . Errorf ( "expected file FSYMTBL1(0x4) to be 0x1000 bytes long; got 0x%04X" , len ( symtbl2 ) )
}
five := [ ] byte { 0 , 0 , 0 , 0 , 0 }
for i := 0 ; i < 0x0fff ; i += 5 {
address := uint16 ( symtbl1 [ i ] ) + uint16 ( symtbl1 [ i + 1 ] ) << 8
if address == 0 {
table = append ( table , Symbol { } )
continue
}
linkAddr := uint16 ( symtbl1 [ i + 2 ] ) + uint16 ( symtbl1 [ i + 3 ] ) << 8
link := - 1
if linkAddr != 0 {
if linkAddr < 0xD000 || linkAddr >= 0xDFFF {
return nil , fmt . Errorf ( "Expected symbol table link address between 0xD000 and 0xDFFE; got 0x%04X" , linkAddr )
2016-11-09 04:37:15 +00:00
}
2016-11-13 03:15:45 +00:00
if ( linkAddr - 0xD000 ) % 5 != 0 {
return nil , fmt . Errorf ( "Expected symbol table link address to 0xD000+5x; got 0x%04X" , linkAddr )
}
2016-11-23 03:38:20 +00:00
link = ( int ( linkAddr ) - 0xD000 ) / 5
2016-11-13 03:15:45 +00:00
}
extra := symtbl1 [ i + 4 ]
copy ( five , symtbl2 [ i : i + 5 ] )
name := decodeSymbol ( five , extra )
symbol := Symbol {
Address : address ,
Name : name ,
Link : link ,
2016-11-09 04:37:15 +00:00
}
2016-11-13 03:15:45 +00:00
table = append ( table , symbol )
2016-11-09 04:37:15 +00:00
}
2016-11-23 03:38:20 +00:00
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 ) )
}
}
}
2016-11-13 03:15:45 +00:00
return table , nil
2016-11-09 04:37:15 +00:00
}
2016-11-28 03:31:51 +00:00
// WriteSymbolTable writes a symbol table to a disk.
func ( sm SectorMap ) WriteSymbolTable ( sd disk . SectorDisk , st SymbolTable ) error {
symtbl1 := make ( [ ] byte , 0x1000 )
symtbl2 := make ( [ ] byte , 0x1000 )
for i , sym := range st {
offset := i * 5
linkAddr := 0
six , err := encodeSymbol ( sym . Name )
if err != nil {
return err
}
if sym . Link != - 1 {
linkAddr = sym . Link * 5 + 0xD000
}
symtbl1 [ offset ] = byte ( sym . Address % 256 )
symtbl1 [ offset + 1 ] = byte ( sym . Address >> 8 )
symtbl1 [ offset + 2 ] = byte ( linkAddr % 256 )
symtbl1 [ offset + 3 ] = byte ( linkAddr >> 8 )
symtbl1 [ offset + 4 ] = six [ 5 ]
copy ( symtbl2 [ offset : offset + 5 ] , six )
}
2016-11-29 03:31:52 +00:00
if _ , err := sm . WriteFile ( sd , 3 , symtbl1 , true ) ; err != nil {
2016-11-28 03:31:51 +00:00
return fmt . Errorf ( "unable to write first half of symbol table: %v" , err )
}
2016-11-29 03:31:52 +00:00
if _ , err := sm . WriteFile ( sd , 4 , symtbl2 , true ) ; err != nil {
2016-11-28 03:31:51 +00:00
return fmt . Errorf ( "unable to write first second of symbol table: %v" , err )
}
return nil
}
2016-11-23 03:38:20 +00:00
// addrHash computes the SuperMon hash for an address.
func addrHash ( addr uint16 ) byte {
return ( byte ( addr ) ^ byte ( addr >> 8 ) ) & 0x7f
}
2016-11-13 03:15:45 +00:00
// SymbolsByAddress returns a map of addresses to slices of symbols.
func ( st SymbolTable ) SymbolsByAddress ( ) map [ uint16 ] [ ] Symbol {
result := map [ uint16 ] [ ] Symbol { }
for _ , symbol := range st {
if symbol . Address != 0 {
result [ symbol . Address ] = append ( result [ symbol . Address ] , symbol )
2016-11-09 04:37:15 +00:00
}
}
return result
}
2016-11-13 21:59:38 +00:00
2016-12-01 03:29:42 +00:00
// SymbolsForAddress returns a slice of symbols for a given address.
func ( st SymbolTable ) SymbolsForAddress ( address uint16 ) [ ] Symbol {
result := [ ] Symbol { }
for _ , symbol := range st {
if symbol . Address == address {
result = append ( result , symbol )
}
}
return result
}
2016-12-02 03:51:56 +00:00
// ByName returns the address of the named symbol, or 0 if it's not in
// the symbol table.
func ( st SymbolTable ) ByName ( name string ) uint16 {
for _ , symbol := range st {
if strings . EqualFold ( name , symbol . Name ) {
return symbol . Address
}
}
return 0
}
2016-11-26 01:56:54 +00:00
// DeleteSymbol deletes an existing symbol. Returns true if the named
// symbol was found.
func ( st SymbolTable ) DeleteSymbol ( name string ) bool {
for i , sym := range st {
if strings . EqualFold ( name , sym . Name ) {
sym . Name = ""
sym . Address = 0
for j := range st {
if i == j {
continue
}
if st [ j ] . Link == i {
st [ j ] . Link = sym . Link
break
}
}
st [ i ] = sym
return true
}
}
return false
}
// AddSymbol adds a new symbol. If a symbol with the given name
// already exists with a different address, it deletes it first.
func ( st SymbolTable ) AddSymbol ( name string , address uint16 ) error {
if address == 0 {
2016-12-10 21:29:41 +00:00
return fmt . Errorf ( "cannot set symbol %q to address 0" , name )
2016-11-26 01:56:54 +00:00
}
hash := addrHash ( address )
pos := - 1
for j , sym := range st {
if strings . EqualFold ( name , sym . Name ) {
// If we can, simply update the address.
if addrHash ( sym . Address ) == hash {
st [ j ] . Address = address
return nil
}
st . DeleteSymbol ( name )
pos = j
break
}
if pos == - 1 && sym . Address == 0 {
pos = j
}
}
2016-11-29 03:31:52 +00:00
if pos == - 1 {
return fmt . Errorf ( "symbol table full" )
}
2016-11-26 01:56:54 +00:00
for j , sym := range st {
if addrHash ( sym . Address ) == hash && sym . Link == - 1 {
st [ j ] . Link = pos
break
}
}
st [ pos ] . Name = name
st [ pos ] . Address = address
st [ pos ] . Link = - 1
return nil
}
2016-11-15 03:54:57 +00:00
// NameForFile returns a string representation of a filename:
// either DFxx, or a symbol, if one exists for that value.
2016-12-01 03:29:42 +00:00
func NameForFile ( file byte , st SymbolTable ) string {
symbols := st . SymbolsForAddress ( 0xDF00 + uint16 ( file ) )
2016-11-13 21:59:38 +00:00
if len ( symbols ) > 0 {
2016-11-15 03:54:57 +00:00
return symbols [ 0 ] . Name
}
return fmt . Sprintf ( "DF%02X" , file )
}
2016-12-01 03:12:01 +00:00
// FullnameForFile returns a string representation of a filename:
// either DFxx, or a DFxx:symbol, if one exists for that value.
2016-12-01 03:29:42 +00:00
func FullnameForFile ( file byte , st SymbolTable ) string {
symbols := st . SymbolsForAddress ( 0xDF00 + uint16 ( file ) )
2016-12-01 03:12:01 +00:00
if len ( symbols ) > 0 {
return fmt . Sprintf ( "DF%02X:%s" , file , symbols [ 0 ] . Name )
}
return fmt . Sprintf ( "DF%02X" , file )
}
2016-11-29 03:31:52 +00:00
// parseAddressFilename parses filenames of the form DFxx and returns
// the xx part. Invalid filenames result in 0.
func parseAddressFilename ( filename string ) byte {
if addr , err := strconv . ParseUint ( filename , 16 , 16 ) ; err == nil {
if addr > 0xDF00 && addr < 0xDFFE {
return byte ( addr - 0xDF00 )
}
if addr > 0x00 && addr < 0xFE {
return byte ( addr )
}
}
return 0
}
2016-11-15 03:54:57 +00:00
// 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 ) {
2016-11-29 03:31:52 +00:00
if addr := parseAddressFilename ( filename ) ; addr != 0 {
return addr , nil
2016-11-15 03:54:57 +00:00
}
for _ , symbol := range st {
if strings . EqualFold ( symbol . Name , filename ) {
if symbol . Address > 0xDF00 && symbol . Address < 0xDFFE {
return byte ( symbol . Address - 0xDF00 ) , nil
2016-11-13 21:59:38 +00:00
}
2016-11-15 03:54:57 +00:00
break
2016-11-13 21:59:38 +00:00
}
}
2016-11-15 03:54:57 +00:00
2016-11-28 03:31:51 +00:00
return 0 , errors . FileNotFoundf ( "filename %q not found" , filename )
2016-11-13 21:59:38 +00:00
}
2016-12-02 03:51:56 +00:00
// ParseCompoundSymbol parses an address, symbol, or compound of both
// in the forms XXXX, symbolname, or XXXX:symbolname.
func ( st SymbolTable ) ParseCompoundSymbol ( name string ) ( address uint16 , symAddress uint16 , symbol string , err error ) {
if name == "" {
return 0 , 0 , "" , fmt . Errorf ( "expected symbol name, got %q" , name )
}
parts := strings . Split ( name , ":" )
if len ( parts ) > 2 {
return 0 , 0 , "" , fmt . Errorf ( "more than one colon in compound address:symbol: %q" , name )
}
if len ( parts ) == 1 {
// If there's a symbol by that name, use it.
if addr := st . ByName ( name ) ; addr != 0 {
return 0 , addr , name , nil
}
// If we can parse it as an address, do so.
if addr , err := strconv . ParseUint ( name , 16 , 16 ) ; err == nil {
return uint16 ( addr ) , 0 , "" , nil
}
// If it's a valid symbol name, assume that's what it is.
if _ , err := encodeSymbol ( name ) ; err != nil {
return 0 , 0 , name , nil
}
2016-12-10 21:29:41 +00:00
return 0 , 0 , "" , fmt . Errorf ( "%q is not a valid symbol name or address" , name )
2016-12-02 03:51:56 +00:00
}
if parts [ 0 ] == "" {
return 0 , 0 , "" , fmt . Errorf ( "empty address part of compound address:symbol: %q" , name )
}
if parts [ 1 ] == "" {
return 0 , 0 , "" , fmt . Errorf ( "empty symbol part of compound address:symbol: %q" , name )
}
// If we can parse it as an address, do so.
addr , err := strconv . ParseUint ( parts [ 0 ] , 16 , 16 )
if err != nil {
return 0 , 0 , "" , fmt . Errorf ( "error parsing address part of %q: %v" , name , err )
}
if _ , err := encodeSymbol ( parts [ 1 ] ) ; err != nil {
return 0 , 0 , name , err
}
return uint16 ( addr ) , st . ByName ( parts [ 1 ] ) , parts [ 1 ] , nil
}
2016-11-29 03:31:52 +00:00
// FilesForCompoundName parses a complex filename of the form DFxx,
// FILENAME, or DFxx:FILENAME, returning the file number before the
// colon, and the file name number after the colon, and the symbol
// name.
func ( st SymbolTable ) FilesForCompoundName ( filename string ) ( numFile byte , namedFile byte , symbol string , err error ) {
parts := strings . Split ( filename , ":" )
if len ( parts ) > 2 {
return 0 , 0 , "" , fmt . Errorf ( "more than one colon in compound filename: %q" , filename )
}
if len ( parts ) == 1 {
numFile = parseAddressFilename ( filename )
if numFile != 0 {
return numFile , 0 , "" , nil
}
file , err := st . FileForName ( filename )
if err != nil {
return 0 , 0 , filename , nil
}
return file , file , filename , nil
}
numFile = parseAddressFilename ( parts [ 0 ] )
if numFile == 0 {
return 0 , 0 , "" , fmt . Errorf ( "invalid file number: %q" , parts [ 0 ] )
}
if numFile2 := parseAddressFilename ( parts [ 1 ] ) ; numFile2 != 0 {
2016-12-10 21:29:41 +00:00
return 0 , 0 , "" , fmt . Errorf ( "cannot use valid file number (%q) as a filename" , parts [ 1 ] )
2016-11-29 03:31:52 +00:00
}
namedFile , err = st . FileForName ( parts [ 1 ] )
if err != nil {
return numFile , 0 , parts [ 1 ] , nil
}
return numFile , namedFile , parts [ 1 ] , nil
}
2016-12-10 04:08:47 +00:00
// Operator is a disk.Operator - an interface for performing
2016-11-13 21:59:38 +00:00
// high-level operations on files and directories.
2016-12-10 04:08:47 +00:00
type Operator struct {
SD disk . SectorDisk
SM SectorMap
ST SymbolTable
2016-11-13 21:59:38 +00:00
}
2016-12-10 04:08:47 +00:00
var _ disk . Operator = Operator { }
2016-11-13 21:59:38 +00:00
// operatorName is the keyword name for the operator that undestands
// NakedOS/Super-Mon disks.
const operatorName = "nakedos"
2016-12-10 04:08:47 +00:00
// Name returns the name of the Operator.
func ( o Operator ) Name ( ) string {
2016-11-13 21:59:38 +00:00
return operatorName
}
// HasSubdirs returns true if the underlying operating system on the
// disk allows subdirectories.
2016-12-10 04:08:47 +00:00
func ( o Operator ) HasSubdirs ( ) bool {
2016-11-13 21:59:38 +00:00
return false
}
// Catalog returns a catalog of disk entries. subdir should be empty
// for operating systems that do not support subdirectories.
2016-12-10 04:08:47 +00:00
func ( o Operator ) Catalog ( subdir string ) ( [ ] disk . Descriptor , error ) {
2016-11-13 21:59:38 +00:00
var descs [ ] disk . Descriptor
2016-12-10 04:08:47 +00:00
sectorsByFile := o . SM . SectorsByFile ( )
2016-11-13 21:59:38 +00:00
for file := byte ( 1 ) ; file < FileReserved ; file ++ {
l := len ( sectorsByFile [ file ] )
if l == 0 {
continue
}
descs = append ( descs , disk . Descriptor {
2016-12-10 04:08:47 +00:00
Name : NameForFile ( file , o . ST ) ,
Fullname : FullnameForFile ( file , o . ST ) ,
2016-12-01 03:12:01 +00:00
Sectors : l ,
Length : l * 256 ,
Locked : false ,
Type : disk . FiletypeBinary ,
2016-11-13 21:59:38 +00:00
} )
}
return descs , nil
}
2016-11-15 03:54:57 +00:00
// GetFile retrieves a file by name.
2016-12-10 04:08:47 +00:00
func ( o Operator ) GetFile ( filename string ) ( disk . FileInfo , error ) {
file , err := o . ST . FileForName ( filename )
2016-11-15 03:54:57 +00:00
if err != nil {
return disk . FileInfo { } , err
}
2016-12-10 04:08:47 +00:00
data , err := o . SM . ReadFile ( o . SD , file )
2016-11-15 03:54:57 +00:00
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 {
2016-12-10 04:08:47 +00:00
Name : NameForFile ( file , o . ST ) ,
2016-11-15 03:54:57 +00:00
Sectors : len ( data ) / 256 ,
Length : len ( data ) ,
Locked : false ,
2016-11-16 02:05:22 +00:00
Type : disk . FiletypeBinary ,
2016-11-15 03:54:57 +00:00
}
2016-11-23 03:38:20 +00:00
fi := disk . FileInfo {
2016-11-15 03:54:57 +00:00
Descriptor : desc ,
Data : data ,
2016-11-23 03:38:20 +00:00
}
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.
2016-12-10 04:08:47 +00:00
func ( o Operator ) Delete ( filename string ) ( bool , error ) {
file , err := o . ST . FileForName ( filename )
2016-11-28 03:31:51 +00:00
if err != nil {
return false , err
}
2016-12-10 04:08:47 +00:00
existed := len ( o . SM . SectorsForFile ( file ) ) > 0
o . SM . Delete ( file )
if err := o . SM . Persist ( o . SD ) ; err != nil {
2016-11-28 03:31:51 +00:00
return existed , err
}
2016-12-10 04:08:47 +00:00
if o . ST != nil {
changed := o . ST . DeleteSymbol ( filename )
2016-11-28 03:31:51 +00:00
if changed {
2016-12-10 04:08:47 +00:00
if err := o . SM . WriteSymbolTable ( o . SD , o . ST ) ; err != nil {
2016-11-28 03:31:51 +00:00
return existed , err
}
}
}
return existed , nil
2016-11-15 03:54:57 +00:00
}
2016-11-30 03:53:40 +00:00
// 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.
2016-12-10 04:08:47 +00:00
func ( o Operator ) PutFile ( fileInfo disk . FileInfo , overwrite bool ) ( existed bool , err error ) {
2016-11-30 03:53:40 +00:00
if fileInfo . Descriptor . Type != disk . FiletypeBinary {
return false , fmt . Errorf ( "%s: only binary file type supported" , operatorName )
}
if fileInfo . Descriptor . Length != len ( fileInfo . Data ) {
return false , fmt . Errorf ( "mismatch between FileInfo.Descriptor.Length (%d) and actual length of FileInfo.Data field (%d)" , fileInfo . Descriptor . Length , len ( fileInfo . Data ) )
}
2016-12-10 04:08:47 +00:00
numFile , namedFile , symbol , err := o . ST . FilesForCompoundName ( fileInfo . Descriptor . Name )
2016-11-29 03:31:52 +00:00
if err != nil {
return false , err
}
if symbol != "" {
2016-12-10 04:08:47 +00:00
if o . ST == nil {
2016-11-29 03:31:52 +00:00
return false , fmt . Errorf ( "cannot use symbolic names on disks without valid symbol tables in files 0x03 and 0x04" )
}
if _ , err := encodeSymbol ( symbol ) ; err != nil {
return false , err
}
}
if numFile == 0 {
2016-12-10 04:08:47 +00:00
numFile = o . SM . FirstFreeFile ( )
2016-11-29 03:31:52 +00:00
if numFile == 0 {
return false , fmt . Errorf ( "all files already used" )
}
}
2016-12-10 04:08:47 +00:00
existed , err = o . SM . WriteFile ( o . SD , numFile , fileInfo . Data , overwrite )
2016-11-29 03:31:52 +00:00
if err != nil {
return existed , err
}
if namedFile != numFile && symbol != "" {
2016-12-10 04:08:47 +00:00
if err := o . ST . AddSymbol ( symbol , 0xDF00 + uint16 ( numFile ) ) ; err != nil {
2016-11-29 03:31:52 +00:00
return existed , err
}
2016-12-10 04:08:47 +00:00
if err := o . SM . WriteSymbolTable ( o . SD , o . ST ) ; err != nil {
2016-11-29 03:31:52 +00:00
return existed , err
}
}
return existed , nil
}
2017-03-18 02:26:15 +00:00
// Write writes the underlying disk to the given writer.
func ( o Operator ) Write ( w io . Writer ) ( int , error ) {
return o . SD . Write ( w )
}
2016-11-17 02:42:18 +00:00
// operatorFactory is the factory that returns supermon operators
// given disk images.
2016-11-13 21:59:38 +00:00
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
}
2016-12-10 04:08:47 +00:00
op := Operator { SD : sd , SM : sm }
2016-11-13 21:59:38 +00:00
st , err := sm . ReadSymbolTable ( sd )
if err == nil {
2016-12-10 04:08:47 +00:00
op . ST = st
2016-11-13 21:59:38 +00:00
}
return op , nil
}
func init ( ) {
2017-03-18 02:26:15 +00:00
disk . RegisterDiskOperatorFactory ( operatorName , operatorFactory )
2016-11-13 21:59:38 +00:00
}