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"
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
}
// 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-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 {
return fmt . Errorf ( "cannot set symbol %q to address 0" )
}
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.
func NameForFile ( file byte , symbols [ ] Symbol ) string {
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-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-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 {
return 0 , 0 , "" , fmt . Errorf ( "cannot valid file number (%q) as a filename" )
}
namedFile , err = st . FileForName ( parts [ 1 ] )
if err != nil {
return numFile , 0 , parts [ 1 ] , nil
}
return numFile , namedFile , parts [ 1 ] , nil
}
2016-11-13 21:59:38 +00:00
// 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
}
2016-11-15 03:54:57 +00:00
fileAddr := 0xDF00 + uint16 ( file )
2016-11-13 21:59:38 +00:00
descs = append ( descs , disk . Descriptor {
2016-11-15 03:54:57 +00:00
Name : NameForFile ( file , o . symbols [ fileAddr ] ) ,
2016-11-13 21:59:38 +00:00
Sectors : l ,
Length : l * 256 ,
Locked : false ,
2016-11-17 02:43:36 +00:00
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.
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 ,
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.
func ( o operator ) Delete ( filename string ) ( bool , error ) {
2016-11-28 03:31:51 +00:00
file , err := o . st . FileForName ( filename )
if err != nil {
return false , err
}
existed := len ( o . sm . SectorsForFile ( file ) ) > 0
o . sm . Delete ( file )
if err := o . sm . Persist ( o . sd ) ; err != nil {
return existed , err
}
if o . st != nil {
changed := o . st . DeleteSymbol ( filename )
if changed {
if err := o . sm . WriteSymbolTable ( o . sd , o . st ) ; err != nil {
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-01 02:42:42 +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-01 02:42:42 +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 != "" {
if o . st == nil {
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 {
numFile = o . sm . FirstFreeFile ( )
if numFile == 0 {
return false , fmt . Errorf ( "all files already used" )
}
}
2016-11-30 03:53:40 +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 != "" {
if err := o . st . AddSymbol ( symbol , 0xDF + uint16 ( numFile ) ) ; err != nil {
return existed , err
}
if err := o . sm . WriteSymbolTable ( o . sd , o . st ) ; err != nil {
return existed , err
}
}
return existed , nil
}
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
}
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 )
}