diskm8/shell.go

1864 lines
40 KiB
Go
Raw Permalink Normal View History

2018-01-20 00:05:04 +00:00
package main
import (
"bufio"
"fmt"
"io/ioutil"
"runtime/debug"
"strings"
"time"
"path/filepath"
"sort"
"os"
"regexp"
"strconv"
"errors"
"github.com/chzyer/readline"
"github.com/paleotronic/diskm8/disk"
"github.com/paleotronic/diskm8/loggy"
"github.com/paleotronic/diskm8/panic"
)
const MAXVOL = 8
var commandList map[string]*shellCommand
var commandVolumes [MAXVOL]*disk.DSKWrapper
var commandTarget int = -1
var commandPath [MAXVOL]string
2018-01-20 00:05:04 +00:00
func mountDsk(dsk *disk.DSKWrapper) (int, error) {
var fr []int
for i, d := range commandVolumes {
if d == nil {
fr = append(fr, i)
} else if dsk.Filename == d.Filename {
return i, nil
}
}
if len(fr) == 0 {
return -1, errors.New("No free slots")
}
commandVolumes[fr[0]] = dsk
return fr[0], nil
}
func smartSplit(line string) (string, []string) {
var out []string
var inqq bool
var lastEscape bool
var chunk string
add := func() {
if chunk != "" {
out = append(out, chunk)
chunk = ""
}
}
for _, ch := range line {
switch {
case ch == '"':
inqq = !inqq
add()
case ch == ' ':
if inqq || lastEscape {
chunk += string(ch)
} else {
add()
}
lastEscape = false
case ch == '\\' && !inqq:
lastEscape = true
default:
chunk += string(ch)
}
}
add()
if len(out) == 0 {
return "", out
}
return out[0], out[1:]
}
func getPrompt(wp [MAXVOL]string, t int) string {
2018-01-20 00:05:04 +00:00
if t == -1 || commandVolumes[t] == nil {
return fmt.Sprintf("dsk:%d:%s:%s> ", 0, "<no mount>", wp)
}
dsk := commandVolumes[t]
if dsk != nil {
return fmt.Sprintf("dsk:%d:%s:%s> ", t, filepath.Base(dsk.Filename), wp[t])
2018-01-20 00:05:04 +00:00
}
return "dsk> "
}
type shellCommand struct {
Name string
Description string
MinArgs, MaxArgs int
Code func(args []string) int
NeedsMount bool
Context shellCommandContext
Text []string
}
type shellCommandContext int
const (
sccNone shellCommandContext = 1 << iota
sccLocal
sccDiskFile
sccCommand
sccReportName
sccAnyFile = sccDiskFile | sccLocal
sccAny = sccAnyFile | sccCommand
)
type shellCompleter struct {
}
func hasPrefix(str []rune, prefix []rune) bool {
if len(prefix) > len(str) {
return false
}
for i := 0; i < len(prefix); i++ {
if str[i] != prefix[i] {
return false
}
}
return true
}
func (sc *shellCompleter) Do(line []rune, pos int) ([][]rune, int) {
prefix := ""
chunk := ""
for _, ch := range line {
if ch == ' ' {
prefix = chunk
break
} else {
chunk += string(ch)
}
}
chunk = ""
cprefix := ""
var lastEscape bool
for i := 0; i < pos; i++ {
ch := line[i]
switch {
case ch == '\\':
lastEscape = true
case ch == ' ' && !lastEscape:
cprefix = chunk
chunk = ""
lastEscape = false
default:
chunk += string(ch)
}
}
if chunk != "" {
cprefix = chunk
}
var context shellCommandContext = sccNone
cmd, match := commandList[prefix]
if match {
context = cmd.Context
} else {
context = sccCommand
}
var items [][]rune
switch context {
case sccCommand:
for k, _ := range commandList {
items = append(items, []rune(k))
}
case sccDiskFile:
if commandTarget == -1 || commandVolumes[commandTarget] == nil {
return [][]rune(nil), 0
}
fullpath, _ := filepath.Abs(commandVolumes[commandTarget].Filename)
info, err := analyze(0, fullpath)
if err != nil {
return [][]rune(nil), 0
}
for _, f := range info.Files {
items = append(items, []rune(f.Filename))
}
case sccLocal:
files, err := filepath.Glob(cprefix + "*")
//fmt.Println(err)
if err != nil {
return items, 0
}
for _, v := range files {
items = append(items, []rune(v))
//fmt.Println("thing:", v)
}
}
if len(items) == 0 {
return [][]rune(nil), 0
}
//fmt.Printf("Context = %d, CPrefix=%s, Items=%v\n", context, cprefix, items)
var filt [][]rune
for _, v := range items {
if hasPrefix(v, []rune(cprefix)) {
filt = append(filt, shellEscape(v[len(cprefix):]))
}
}
return filt, len(cprefix)
}
func shellEscape(str []rune) []rune {
out := make([]rune, 0)
for _, v := range str {
if v == ' ' {
out = append(out, '\\')
}
out = append(out, v)
}
return out
}
func init() {
commandList = map[string]*shellCommand{
"mount": &shellCommand{
Name: "mount",
Description: "Mount a disk image",
MinArgs: 1,
MaxArgs: 1,
Code: shellMount,
NeedsMount: false,
Context: sccLocal,
Text: []string{
"mount <diskfile>",
"",
"Mounts disk and switches to the new slot",
},
},
2018-07-19 01:39:11 +00:00
"setvolume": &shellCommand{
Name: "setvolume",
Description: "Sets the ProDOS volume name",
MinArgs: 1,
MaxArgs: 1,
Code: shellVolumeName,
NeedsMount: true,
Context: sccNone,
Text: []string{
"setvolume <volume name>",
"",
"Set ProDOS volume name. Truncated to 15 chars if too long.",
},
},
2018-01-20 00:05:04 +00:00
"unmount": &shellCommand{
Name: "unmount",
Description: "unmount disk image",
MinArgs: 0,
MaxArgs: 1,
Code: shellUnmount,
NeedsMount: true,
Context: sccLocal,
Text: []string{
"unmount <slot>",
"",
"Unmount the disk in the specified slot (or current slot)",
},
},
"extract": &shellCommand{
Name: "extract",
Description: "extract file from disk image",
MinArgs: 1,
MaxArgs: -1,
Code: shellExtract,
NeedsMount: true,
Context: sccDiskFile,
Text: []string{
"extract <filename|pattern>",
"",
"Extracts files from current disk",
},
},
"help": &shellCommand{
Name: "help",
Description: "Shows this help",
MinArgs: 0,
MaxArgs: 1,
Code: shellHelp,
NeedsMount: false,
Context: sccCommand,
Text: []string{
"help <command>",
"",
"Display specific help for command or list of commands",
},
},
"info": &shellCommand{
Name: "info",
Description: "Information about the current disk",
MinArgs: -1,
MaxArgs: -1,
Code: shellInfo,
NeedsMount: true,
Context: sccNone,
Text: []string{
"info",
"",
"Display information on current disk",
},
},
"analyze": &shellCommand{
Name: "analyze",
Description: "Process disk using diskm8 analytics",
MinArgs: -1,
MaxArgs: -1,
Code: shellAnalyze,
NeedsMount: true,
Context: sccNone,
Text: []string{
"analyze",
"",
"Display detailed diskm8 information on current disk",
},
},
"quit": &shellCommand{
Name: "quit",
Description: "Leave this place",
MinArgs: -1,
MaxArgs: -1,
Code: shellQuit,
NeedsMount: false,
Context: sccNone,
},
"prefix": &shellCommand{
Name: "prefix",
Description: "Change volume path",
MinArgs: 0,
MaxArgs: 1,
Code: shellPath,
NeedsMount: true,
Context: sccDiskFile,
Text: []string{
"prefix [<path>]",
"",
"Change disk working directory.",
},
},
2018-01-20 00:05:04 +00:00
"cat": &shellCommand{
Name: "cat",
Description: "Display file information",
MinArgs: 0,
MaxArgs: 1,
Code: shellCat,
NeedsMount: true,
Context: sccNone,
Text: []string{
"cat [<pattern>]",
"",
"List files on current disk (can use wildcards).",
},
},
"mkdir": &shellCommand{
Name: "mkdir",
Description: "Create a directory on disk",
MinArgs: 1,
MaxArgs: 1,
Code: shellMkdir,
NeedsMount: true,
Context: sccDiskFile,
Text: []string{
"mkdir <directory>",
"",
"Create directory on current disk (if supported)",
},
},
"put": &shellCommand{
Name: "put",
Description: "Copy local file to disk (with optional target dir)",
2018-01-20 00:05:04 +00:00
MinArgs: 1,
MaxArgs: 2,
2018-01-20 00:05:04 +00:00
Code: shellPut,
NeedsMount: true,
Context: sccLocal,
Text: []string{
"put <local file> [<target dir>]",
2018-01-20 00:05:04 +00:00
"",
"Write local file to current disk",
},
},
"delete": &shellCommand{
Name: "delete",
Description: "Remove file from disk",
MinArgs: 1,
MaxArgs: 1,
Code: shellDelete,
NeedsMount: true,
Context: sccDiskFile,
Text: []string{
"delete <filename>",
"",
"Delete file from current disk",
},
},
"ingest": &shellCommand{
Name: "ingest",
Description: "Ingest directory containing disks (or single disk) into system",
MinArgs: 1,
MaxArgs: 1,
Code: shellIngest,
NeedsMount: false,
Context: sccLocal,
Text: []string{
"ingest <disk name>",
"",
"Catalog diskfile into diskm8 database.",
},
},
"lock": &shellCommand{
Name: "lock",
Description: "Lock file on the disk",
MinArgs: 1,
MaxArgs: 1,
Code: shellLock,
NeedsMount: true,
Context: sccDiskFile,
Text: []string{
"lock <diskfile>",
"",
"Make file on disk read-only",
},
},
"unlock": &shellCommand{
Name: "unlock",
Description: "Unlock file on the disk",
MinArgs: 1,
MaxArgs: 1,
Code: shellUnlock,
NeedsMount: true,
Context: sccDiskFile,
Text: []string{
"unlock <diskfile>",
"",
"Make file on disk writable",
},
},
"ls": &shellCommand{
Name: "ls",
Description: "List local files",
MinArgs: 0,
MaxArgs: 999,
Code: shellListFiles,
NeedsMount: false,
Context: sccLocal,
Text: []string{
"ls <pattern>",
"",
"List local files",
},
},
"cd": &shellCommand{
Name: "cd",
Description: "Change local path",
MinArgs: 0,
MaxArgs: 1,
Code: shellCd,
NeedsMount: false,
Context: sccLocal,
Text: []string{
"cd <path>",
"",
"Change local directory",
},
},
"disks": &shellCommand{
Name: "disks",
Description: "List mounted volumes",
MinArgs: 0,
MaxArgs: 0,
Code: shellDisks,
NeedsMount: false,
Context: sccNone,
Text: []string{
"disks",
"",
"List all mounted volumes",
},
},
"target": &shellCommand{
Name: "target",
Description: "Select mounted volume as default",
MinArgs: 1,
MaxArgs: 1,
Code: shellPrefix,
NeedsMount: false,
Context: sccNone,
Text: []string{
"target <slot>",
"",
"Select slot as default for commands",
},
},
"copy": &shellCommand{
Name: "copy",
Description: "Copy files from one volume to another",
MinArgs: 2,
MaxArgs: 999,
Code: shellD2DCopy,
NeedsMount: false,
Context: sccDiskFile,
Text: []string{
"copy [<slot>:]<pattern> <slot>:[<path>]",
"",
"Copy files from one mounted disk to another.",
"Example:",
"copy 0:*.system 1:",
},
},
"move": &shellCommand{
Name: "move",
Description: "Move files from one volume to another",
MinArgs: 2,
MaxArgs: 999,
Code: shellD2DCopy,
NeedsMount: false,
Context: sccDiskFile,
Text: []string{
"move [<slot>:]<pattern> <slot>:[<path>]",
"",
"Move files from one mounted disk to another.",
"Example:",
"move 0:*.system 1:",
},
},
"rename": &shellCommand{
Name: "rename",
Description: "Rename a file on the disk",
MinArgs: 2,
MaxArgs: 2,
Code: shellRename,
NeedsMount: true,
Context: sccDiskFile,
Text: []string{
"rename <filename> <new filename>",
"",
"Rename a file on a disk.",
},
},
"report": &shellCommand{
Name: "report",
Description: "Run a report",
MinArgs: 1,
MaxArgs: 999,
Code: shellReport,
NeedsMount: false,
Context: sccDiskFile,
Text: []string{
"report <name> [<path>]",
"",
"Reports:",
"as-dupes Active sector dupes report (-as-dupes at command line)",
"file-dupes File dupes report (-file-dupes at command line)",
"whole-dupes Whole disk dupes report (-whole-dupes at command line)",
},
},
"search": &shellCommand{
Name: "search",
Description: "Run a search",
MinArgs: 1,
MaxArgs: 999,
Code: shellSearch,
NeedsMount: false,
Context: sccDiskFile,
Text: []string{
"search <type> [<path>]",
"",
"Searches:",
"filename Search by filename",
"text Search for files containing tex",
"hash Search for files with hash",
},
},
"quarantine": &shellCommand{
Name: "quarantine",
Description: "Like report, but allow moving dupes to a backup folder",
MinArgs: 1,
MaxArgs: 999,
Code: shellQuarantine,
NeedsMount: false,
Context: sccDiskFile,
Text: []string{
"quarantine <name> [<path>]",
"",
"Scans:",
"as-dupes Active sector dupes report (-as-dupes at command line)",
"file-dupes File dupes report (-file-dupes at command line)",
"whole-dupes Whole disk dupes report (-whole-dupes at command line)",
},
},
}
}
func shellProcess(line string) int {
line = strings.TrimSpace(line)
verb, args := smartSplit(line)
if verb != "" {
verb = strings.ToLower(verb)
command, ok := commandList[verb]
if ok {
fmt.Println()
var cok = true
if command.MinArgs != -1 {
if len(args) < command.MinArgs {
os.Stderr.WriteString(fmt.Sprintf("%s expects at least %d arguments\n", verb, command.MinArgs))
cok = false
}
}
if command.MaxArgs != -1 {
if len(args) > command.MaxArgs {
os.Stderr.WriteString(fmt.Sprintf("%s expects at most %d arguments\n", verb, command.MaxArgs))
cok = false
}
}
if command.NeedsMount {
if commandTarget == -1 || commandVolumes[commandTarget] == nil {
os.Stderr.WriteString(fmt.Sprintf("%s only works on mounted disks\n", verb))
cok = false
}
}
if cok {
r := command.Code(args)
fmt.Println()
return r
} else {
return -1
}
} else {
os.Stderr.WriteString(fmt.Sprintf("Unrecognized command: %s\n", verb))
return -1
}
}
return 0
}
func shellDo(dsk *disk.DSKWrapper) {
//commandVolumes = dsk
//commandPath[commandTarget] := ""
2018-01-20 00:05:04 +00:00
ac := &shellCompleter{}
rl, err := readline.NewEx(&readline.Config{
Prompt: getPrompt(commandPath, commandTarget),
HistoryFile: binpath() + "/.shell_history",
DisableAutoSaveHistory: false,
AutoComplete: ac,
})
if err != nil {
//fmt.Println("Error rl:", err)
os.Exit(2)
}
defer rl.Close()
running := true
for running {
line, err := rl.Readline()
if err != nil {
//fmt.Println("Error:", err)
break
}
r := shellProcess(line)
if r == 999 {
//fmt.Println("exit 999")
return
}
rl.SetPrompt(getPrompt(commandPath, commandTarget))
}
}
func shellPath(args []string) int {
path := ""
if len(args) > 0 {
path = args[0]
}
if formatIn(commandVolumes[commandTarget].Format.ID, []disk.DiskFormatID{disk.DF_PRODOS, disk.DF_PRODOS_800KB, disk.DF_PRODOS_400KB, disk.DF_PRODOS_CUSTOM}) {
_, _, _, e := commandVolumes[commandTarget].PRODOSFindDirBlocks(2, path)
if e == nil {
commandPath[commandTarget] = path
if path == "" {
path = "/"
}
fmt.Printf("Switched to directory %s\r\n", path)
} else {
fmt.Println("No such directory")
}
} else {
fmt.Println("Not supported on this filesystem")
}
return 0
}
2018-01-20 00:05:04 +00:00
func shellMount(args []string) int {
if len(args) != 1 {
fmt.Println("mount expects a diskfile")
return -1
}
dsk, err := disk.NewDSKWrapper(defNibbler, args[0])
if err != nil {
os.Stderr.WriteString("Error:" + err.Error() + "\n")
return -1
}
slotid, err := mountDsk(dsk)
if err != nil {
os.Stderr.WriteString("Error:" + err.Error() + "\n")
return -1
}
commandTarget = slotid
os.Stderr.WriteString(fmt.Sprintf("mount disk in slot %d\n", slotid))
return 0
}
func shellUnmount(args []string) int {
if len(args) > 0 {
if shellPrefix(args) == -1 {
return -1
}
}
if commandVolumes[commandTarget] != nil {
commandVolumes[commandTarget] = nil
commandPath[commandTarget] = ""
2018-01-20 00:05:04 +00:00
os.Stderr.WriteString("Unmounted volume\n")
}
return 0
}
func shellHelp(args []string) int {
if len(args) == 0 {
keys := make([]string, 0)
for k, _ := range commandList {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
info := commandList[k]
fmt.Printf("%-10s %s\n", info.Name, info.Description)
}
} else {
command := strings.ToLower(args[0])
if details, ok := commandList[command]; ok {
if details.Text != nil {
for _, l := range details.Text {
fmt.Println(l)
}
} else {
os.Stderr.WriteString("No help available for " + command)
}
} else {
os.Stderr.WriteString("No help available for " + command)
}
}
return 0
}
func shellInfo(args []string) int {
fullpath, _ := filepath.Abs(commandVolumes[commandTarget].Filename)
fmt.Printf("Disk path : %s\n", fullpath)
fmt.Printf("Disk type : %s\n", commandVolumes[commandTarget].Format.String())
fmt.Printf("Sector Order: %s\n", commandVolumes[commandTarget].Layout.String())
fmt.Printf("Size : %d bytes\n", len(commandVolumes[commandTarget].Data))
return 0
}
func shellQuit(args []string) int {
return 999
}
func shellCat(args []string) int {
fullpath, _ := filepath.Abs(commandVolumes[commandTarget].Filename)
info, err := analyze(0, fullpath)
if err != nil {
return -1
}
bs := 256
2018-07-19 01:39:11 +00:00
volumename := "no-name"
2018-01-20 00:05:04 +00:00
if info.FormatID.ID == disk.DF_PASCAL || info.FormatID.ID == disk.DF_PRODOS ||
info.FormatID.ID == disk.DF_PRODOS_800KB || info.FormatID.ID == disk.DF_PRODOS_400KB ||
info.FormatID.ID == disk.DF_PRODOS_CUSTOM {
bs = 512
2018-07-19 01:39:11 +00:00
vdh, err := commandVolumes[commandTarget].PRODOSGetVDH(2)
if err == nil {
volumename = vdh.GetVolumeName()
}
2018-01-20 00:05:04 +00:00
}
pattern := "*"
if len(args) > 0 {
pattern = args[0]
}
files, _ := globDisk(commandTarget, pattern)
2018-07-19 01:39:11 +00:00
fmt.Printf("Volume Name is %s\n\n", volumename)
2018-01-20 00:05:04 +00:00
fmt.Printf("%-33s %6s %2s %-23s %s\n", "NAME", "BLOCKS", "RO", "KIND", "ADDITONAL")
for _, f := range files {
add := ""
locked := " "
if f.LoadAddress != 0 {
add = fmt.Sprintf("(A$%.4X)", f.LoadAddress)
}
if f.Locked {
locked = "Y"
}
fmt.Printf("%-33s %6d %2s %-23s %s\n", f.Filename, (f.Size/bs)+1, locked, f.Type, add)
}
free := 0
used := 0
for _, v := range info.Bitmap {
if v {
used++
} else {
free++
}
}
fmt.Printf("\nUSED: %-20d FREE: %-20d\n", used, free)
return 0
}
func shellCd(args []string) int {
if len(args) > 0 {
err := os.Chdir(args[0])
if err != nil {
os.Stderr.WriteString("Change directory failed: " + err.Error() + "\n")
return -1
}
}
wd, _ := os.Getwd()
os.Stderr.WriteString("Working directory is now " + wd + "\n")
return 0
}
func shellListFiles(args []string) int {
bs := 256
if len(args) == 0 {
wd, _ := os.Getwd()
args = append(args, wd+"/*.*")
}
for _, a := range args {
files, err := filepath.Glob(a)
if err != nil {
os.Stderr.WriteString("Error reading path " + a + ": " + err.Error() + "\n")
continue
}
fmt.Printf("%6s %2s %-23s %s\n", "BLOCKS", "RO", "KIND", "NAME")
for _, f := range files {
locked := " "
fi, _ := os.Stat(f)
if fi.Mode().Perm()&0100 != 0100 {
locked = "Y"
}
fmt.Printf("%6d %2s %-23s %s\n", (int(fi.Size())/bs)+1, locked, "Local file", fi.Name())
}
}
return 0
}
func shellAnalyze(args []string) int {
fullpath, _ := filepath.Abs(commandVolumes[commandTarget].Filename)
info, err := analyze(0, fullpath)
if err != nil {
return -1
}
fmt.Printf("Format: %s\n", info.FormatID)
fmt.Printf("Tracks: %d, Sectors: %d\n", info.Tracks, info.Sectors)
return 0
}
func shellExtract(args []string) int {
fullpath, _ := filepath.Abs(commandVolumes[commandTarget].Filename)
_, err := analyze(0, fullpath)
if err != nil {
return 1
}
fmt.Println("Extract:", args[0])
files, _ := globDisk(commandTarget, args[0])
for _, f := range files {
err := ExtractFile(fullpath, f, true, true)
if err == nil {
fmt.Println("OK")
} else {
fmt.Println("FAILED")
return -1
}
}
return 0
}
func formatIn(f disk.DiskFormatID, list []disk.DiskFormatID) bool {
for _, v := range list {
if v == f {
return true
}
}
return false
}
func fts() string {
t := time.Now()
return fmt.Sprintf(
"%.4d%.2d%.2d%.2d%.2d%.2d",
t.Year(), t.Month(), t.Day(),
t.Hour(), t.Minute(), t.Second(),
)
}
func backupFile(path string) error {
data, err := ioutil.ReadFile(path)
if err != nil {
return err
}
path = strings.Replace(path, ":", "", -1)
path = strings.Replace(path, "\\", "/", -1)
bpath := binpath() + "/backup/" + path + "." + fts()
os.MkdirAll(filepath.Dir(bpath), 0755)
f, err := os.Create(bpath)
if err != nil {
return err
}
f.Write(data)
f.Close()
os.Stderr.WriteString("Backed up disk to: " + bpath + "\n")
return nil
}
func saveDisk(dsk *disk.DSKWrapper, path string) error {
backupFile(path)
f, e := os.Create(path)
if e != nil {
return e
}
defer f.Close()
f.Write(dsk.Data)
fmt.Println("Updated disk " + path)
return nil
}
func shellMkdir(args []string) int {
fullpath, _ := filepath.Abs(commandVolumes[commandTarget].Filename)
_, err := analyze(0, fullpath)
if err != nil {
return 1
}
path := ""
name := args[0]
if strings.Contains(name, "/") {
path = filepath.Dir(name)
name = filepath.Base(name)
}
if formatIn(commandVolumes[commandTarget].Format.ID, []disk.DiskFormatID{disk.DF_PRODOS, disk.DF_PRODOS_800KB, disk.DF_PRODOS_400KB, disk.DF_PRODOS_CUSTOM}) {
e := commandVolumes[commandTarget].PRODOSCreateDirectory(path, name)
if e != nil {
fmt.Println(e)
return -1
}
saveDisk(commandVolumes[commandTarget], fullpath)
} else {
fmt.Println("Do not support Mkdir on " + commandVolumes[commandTarget].Format.String() + " currently.")
return 0
}
return 0
}
2018-07-19 01:39:11 +00:00
func shellVolumeName(args []string) int {
fullpath, _ := filepath.Abs(commandVolumes[commandTarget].Filename)
_, err := analyze(0, fullpath)
if err != nil {
return 1
}
name := strings.ToUpper(args[0])
if formatIn(commandVolumes[commandTarget].Format.ID, []disk.DiskFormatID{disk.DF_PRODOS, disk.DF_PRODOS_800KB, disk.DF_PRODOS_400KB, disk.DF_PRODOS_CUSTOM}) {
vdh, err := commandVolumes[commandTarget].PRODOSGetVDH(2)
if err != nil {
fmt.Printf("Failed to get Volume Directory Header: %v\n", err)
return -1
}
vdh.SetVolumeName(name)
commandVolumes[commandTarget].PRODOSSetVDH(2, vdh)
fmt.Printf("Volume name is now %s\n", vdh.GetVolumeName())
saveDisk(commandVolumes[commandTarget], fullpath)
} else {
fmt.Println("Do not support setvolume on " + commandVolumes[commandTarget].Format.String() + ".")
return 0
}
return 0
}
2018-01-20 00:05:04 +00:00
func isASCII(in []byte) bool {
for _, v := range in {
if v > 128 {
return false
}
}
return true
}
func shellPut(args []string) int {
fullpath, _ := filepath.Abs(commandVolumes[commandTarget].Filename)
_, err := analyze(0, fullpath)
if err != nil {
return 1
}
2018-05-12 01:00:57 +00:00
parts := strings.Split(args[0], ",")
data, err := ioutil.ReadFile(parts[0])
2018-01-20 00:05:04 +00:00
if err != nil {
return -1
}
2018-05-12 01:00:57 +00:00
addr := int64(0x0801)
name := filepath.Base(args[0])
reTrailAddr := regexp.MustCompile("(?i)^([^,]+)([,]A(([$]|0x)[0-9a-f]+))?([,]L(([$]|0x)[0-9a-f]+))?$")
if reTrailAddr.MatchString(name) {
m := reTrailAddr.FindAllStringSubmatch(name, -1)
name = m[0][1]
saddr := m[0][3]
slen := m[0][6]
if saddr != "" {
if strings.HasPrefix(saddr, "$") {
saddr = "0x" + saddr[1:]
}
addr, _ = strconv.ParseInt(saddr, 0, 32)
}
if slen != "" {