diff --git a/lib/disk/ops.go b/lib/disk/ops.go index b2783b6..3267612 100644 --- a/lib/disk/ops.go +++ b/lib/disk/ops.go @@ -86,6 +86,10 @@ type Operator interface { // 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) + // WriteRaw writes raw contents of 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. + WriteRaw(filename string, contents []byte, overwrite bool) (existed bool, err error) } // FileInfo represents a file descriptor plus the content. diff --git a/lib/dos3/dos3.go b/lib/dos3/dos3.go index 05a00cd..e9285ed 100644 --- a/lib/dos3/dos3.go +++ b/lib/dos3/dos3.go @@ -657,6 +657,13 @@ func (o operator) Delete(filename string) (bool, error) { return false, fmt.Errorf("%s does not implement Delete yet", operatorName) } +// WriteRaw writes raw contents of 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. +func (o operator) WriteRaw(filename string, contents []byte, overwrite bool) (bool, error) { + return false, fmt.Errorf("%s does not implement WriteRaw yet", operatorName) +} + // operatorFactory is the factory that returns dos3 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 4d07c37..611a14d 100644 --- a/lib/supermon/supermon.go +++ b/lib/supermon/supermon.go @@ -48,6 +48,18 @@ func LoadSectorMap(sd disk.SectorDisk) (SectorMap, error) { return sm, nil } +// 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 +} + // Persist writes the current contenst of a sector map back back to // disk. func (sm SectorMap) Persist(sd disk.SectorDisk) error { @@ -175,19 +187,21 @@ func (sm SectorMap) Delete(file byte) { } } -// WriteFile writes the contents of a file. -func (sm SectorMap) WriteFile(sd disk.SectorDisk, file byte, contents []byte, overwrite bool) error { +// 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) { sectorsNeeded := (len(contents) + 255) / 256 cts := make([]byte, 256*sectorsNeeded) copy(cts, contents) existing := len(sm.SectorsForFile(file)) + existed := existing > 0 free := sm.FreeSectors() + existing if free < sectorsNeeded { - return errors.OutOfSpacef("file %d requires %d sectors, but only %d are available", file, sectorsNeeded, free) + return existed, errors.OutOfSpacef("file %d requires %d sectors, but only %d are available", file, sectorsNeeded, free) } - if existing > 0 { + if existed { if !overwrite { - return errors.FileExistsf("file %d already exists", file) + return existed, errors.FileExistsf("file %d already exists", file) } sm.Delete(file) } @@ -198,10 +212,10 @@ OUTER: for sector := byte(0); sector < sd.Sectors(); sector++ { if sm.FileForSector(track, sector) == FileFree { if err := sd.WritePhysicalSector(track, sector, cts[i*256:(i+1)*256]); err != nil { - return err + return existed, err } if err := sm.SetFileForSector(track, sector, file); err != nil { - return err + return existed, err } i++ if i == sectorsNeeded { @@ -211,9 +225,9 @@ OUTER: } } if err := sm.Persist(sd); err != nil { - return err + return existed, err } - return nil + return existed, nil } // Symbol represents a single Super-Mon symbol. @@ -381,10 +395,10 @@ func (sm SectorMap) WriteSymbolTable(sd disk.SectorDisk, st SymbolTable) error { symtbl1[offset+4] = six[5] copy(symtbl2[offset:offset+5], six) } - if err := sm.WriteFile(sd, 3, symtbl1, true); err != nil { + if _, err := sm.WriteFile(sd, 3, symtbl1, true); err != nil { return fmt.Errorf("unable to write first half of symbol table: %v", err) } - if err := sm.WriteFile(sd, 4, symtbl2, true); err != nil { + if _, err := sm.WriteFile(sd, 4, symtbl2, true); err != nil { return fmt.Errorf("unable to write first second of symbol table: %v", err) } return nil @@ -452,6 +466,9 @@ func (st SymbolTable) AddSymbol(name string, address uint16) error { pos = j } } + if pos == -1 { + return fmt.Errorf("symbol table full") + } for j, sym := range st { if addrHash(sym.Address) == hash && sym.Link == -1 { st[j].Link = pos @@ -473,14 +490,26 @@ func NameForFile(file byte, symbols []Symbol) string { return fmt.Sprintf("DF%02X", file) } +// 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 +} + // 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 - } + if addr := parseAddressFilename(filename); addr != 0 { + return addr, nil } for _, symbol := range st { @@ -495,6 +524,40 @@ func (st SymbolTable) FileForName(filename string) (byte, error) { return 0, errors.FileNotFoundf("filename %q not found", filename) } +// 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 +} + // operator is a disk.Operator - an interface for performing // high-level operations on files and directories. type operator struct { @@ -596,6 +659,43 @@ func (o operator) Delete(filename string) (bool, error) { return existed, nil } +// WriteRaw writes raw contents of 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. +func (o operator) WriteRaw(filename string, contents []byte, overwrite bool) (bool, error) { + numFile, namedFile, symbol, err := o.st.FilesForCompoundName(filename) + 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") + } + } + existed, err := o.sm.WriteFile(o.sd, numFile, contents, overwrite) + 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 +} + // operatorFactory is the factory that returns supermon operators // given disk images. func operatorFactory(sd disk.SectorDisk) (disk.Operator, error) {