The big work in progress nothing works commit
parent
cf1667f0bf
commit
ba1ad8f13a
@ -0,0 +1,9 @@
|
||||
package commands
|
||||
|
||||
const fakePath = "/bin:/usr/bin"
|
||||
const fakePipeBuf = "4096"
|
||||
const trampSuccess = "tramp_exit_status 0\n"
|
||||
const trampFailure = "tramp_exit_status 1\n"
|
||||
const lsExecutable = "/bin/ls"
|
||||
const testExecutable = "/bin/test"
|
||||
const statExecutable = "/bin/stat"
|
@ -0,0 +1,45 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type EchoCommand struct{}
|
||||
|
||||
func (c *EchoCommand) Execute(shell *Shell, args []string) error {
|
||||
if len(args) > 1 {
|
||||
if "\"`uname -sr`\"" == args[0]+" "+args[1] {
|
||||
log.Println("Tramp requested uname, returning a fake one: " + fakeUname)
|
||||
_, err := shell.WriteOutput(
|
||||
[]byte(
|
||||
fmt.Sprintf(
|
||||
"\"%s\"\n%s\n",
|
||||
fakeUname,
|
||||
"tramp_exit_status 0",
|
||||
),
|
||||
),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
if "\"`tty`\"" == args[0] {
|
||||
log.Println("Telling tramp that it's dealing with a tty")
|
||||
_, err := shell.WriteOutputString("\"/dev/pts/0\"\n" + trampSuccess)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
if args[0] == "~root" {
|
||||
log.Println("Telling tramp root's home directory")
|
||||
_, err := shell.WriteOutputString("/root")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, err := shell.WriteOutput([]byte(strings.Join(args, " ") + "\n"))
|
||||
return err
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package commands
|
||||
|
||||
import "strings"
|
||||
|
||||
func AssignmentsToMap(args []string) map[string]string {
|
||||
var assignments = make(map[string]string)
|
||||
for _, arg := range args {
|
||||
split := strings.Split(arg, "=")
|
||||
if len(split) > 1 {
|
||||
assignments[split[0]] = split[1]
|
||||
}
|
||||
}
|
||||
|
||||
return assignments
|
||||
}
|
||||
|
||||
func ApplyEnvIfPresent(shell *Shell, comm *Command) {
|
||||
var args []string
|
||||
args = append(args, comm.Name)
|
||||
args = append(args, comm.Arguments...)
|
||||
|
||||
assignments := AssignmentsToMap(args)
|
||||
if ps1, ok := assignments["PS1"]; ok {
|
||||
shell.SetPrompt(ps1)
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package commands
|
||||
|
||||
type GetConfCommand struct{}
|
||||
|
||||
func (c *GetConfCommand) Execute(shell *Shell, args []string) error {
|
||||
if len(args) > 0 {
|
||||
switch args[0] {
|
||||
case "PATH":
|
||||
shell.WriteOutputString(fakePath + "\n" + trampSuccess)
|
||||
case "PIPE_BUF":
|
||||
shell.WriteOutputString(fakePipeBuf + "\n" + trampSuccess)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"git.snorba.art/hugo/nssh/storage"
|
||||
)
|
||||
|
||||
type CommandHandler struct {
|
||||
shell *Shell
|
||||
filestore storage.Filestore
|
||||
}
|
||||
|
||||
func NewCommandHandler(shell *Shell, filestore storage.Filestore) *CommandHandler {
|
||||
return &CommandHandler{
|
||||
shell: shell,
|
||||
filestore: filestore,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *CommandHandler) Handle(comm *Command) error {
|
||||
if strings.Contains(comm.Name, "=") {
|
||||
ApplyEnvIfPresent(h.shell, comm)
|
||||
return nil
|
||||
}
|
||||
|
||||
switch comm.Name {
|
||||
case "exec":
|
||||
ApplyEnvIfPresent(h.shell, comm)
|
||||
case "(cd":
|
||||
h.shell.WriteOutput([]byte("tramp_exit_status 0\n"))
|
||||
case "echo":
|
||||
(&EchoCommand{}).Execute(h.shell, comm.Arguments)
|
||||
case "(echo":
|
||||
if strings.Join(comm.Arguments, " ") == "foo ; echo bar)" {
|
||||
log.Println("Handling tramp's foobar test")
|
||||
h.shell.WriteOutputString("foo\nbar\n")
|
||||
}
|
||||
case "stty":
|
||||
return (&SttyCommand{}).Execute(h.shell, comm.Arguments)
|
||||
case "set":
|
||||
log.Println("Ignoring \"set\" command")
|
||||
case "locale":
|
||||
if len(comm.Arguments) > 0 {
|
||||
if comm.Arguments[0] == "-a" {
|
||||
locales := []string{"C", "C.UTF-8", "POSIX", "en_US.utf8"}
|
||||
log.Println("Tramp requested locale, returning fake values: ", locales)
|
||||
h.shell.WriteOutputString(
|
||||
strings.Join(locales, "\n") + "\n",
|
||||
)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("Ignoring \"locale\" command with unsupported parameters: ", comm.Arguments)
|
||||
case "getconf":
|
||||
return (&GetConfCommand{}).Execute(h.shell, comm.Arguments)
|
||||
case "test", "/bin/test":
|
||||
return (&TestCommand{filestore: h.filestore}).Execute(h.shell, comm.Arguments)
|
||||
case "while":
|
||||
if len(comm.Arguments) > 6 {
|
||||
var err error
|
||||
|
||||
log.Printf("Pointing tramp to executable for \"%s\"", comm.Arguments[6])
|
||||
|
||||
switch comm.Arguments[6] {
|
||||
case "$d/ls":
|
||||
_, err = h.shell.WriteOutputString("tramp_executable " + lsExecutable)
|
||||
case "$d/test":
|
||||
_, err = h.shell.WriteOutputString("tramp_executable " + testExecutable)
|
||||
case "$d/stat":
|
||||
_, err = h.shell.WriteOutputString("tramp_executable " + statExecutable)
|
||||
}
|
||||
return err
|
||||
}
|
||||
case "ls", "/bin/ls":
|
||||
if len(comm.Arguments) > 2 &&
|
||||
comm.Arguments[1] == "-al" &&
|
||||
comm.Arguments[2] == "/dev/null" {
|
||||
_, err := h.shell.WriteOutputString(
|
||||
fmt.Sprintf(
|
||||
"crw-rw-rw- 1 root root 1, 3 May 5 00:05 /dev/null\n%s",
|
||||
trampSuccess,
|
||||
),
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if len(comm.Arguments) > 1 && comm.Arguments[0] == "-lnd" {
|
||||
_, err := h.shell.WriteOutputString(
|
||||
fmt.Sprintf(
|
||||
"drwxrwxr-x 3 1000 1000 28 Apr 28 14:04 %s\n%s",
|
||||
comm.Arguments[1],
|
||||
trampSuccess,
|
||||
),
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// should handle "--color=never --help 2>&1 | grep -iq busybox" so that we
|
||||
// can pretend to have busybox ls. The assumption is that this is easier
|
||||
// to emulate than the --dired flag of ls
|
||||
if len(comm.Arguments) > 7 &&
|
||||
comm.Arguments[4] == "grep" &&
|
||||
comm.Arguments[6] == "busybox" {
|
||||
_, err := h.shell.WriteOutputString(trampSuccess)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := h.shell.WriteOutputString(trampFailure)
|
||||
return err
|
||||
case "mkdir":
|
||||
return (&MkdirCommand{filestore: h.filestore}).Execute(comm.Arguments)
|
||||
default:
|
||||
log.Printf("Error: Received unexpected command %s", comm.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"git.snorba.art/hugo/nssh/storage"
|
||||
)
|
||||
|
||||
type MkdirCommand struct {
|
||||
filestore storage.Filestore
|
||||
}
|
||||
|
||||
func (c *MkdirCommand) Execute(arguments []string) error {
|
||||
for _, dir := range arguments {
|
||||
if len(dir) > 0 && dir[0] != '-' {
|
||||
var currentPath string
|
||||
for _, part := range strings.Split(dir, "/") {
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
currentPath += "/" + part
|
||||
if exists, _ := c.filestore.DirectoryExists(currentPath); !exists {
|
||||
err := c.filestore.MakeDirectory(currentPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -0,0 +1,363 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/google/shlex"
|
||||
"github.com/pkg/term/termios"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type Shell struct {
|
||||
pty *os.File
|
||||
tty *os.File
|
||||
prompt []byte
|
||||
|
||||
readData []byte
|
||||
readIndex int
|
||||
winsize *Winsize
|
||||
}
|
||||
|
||||
type Command struct {
|
||||
Name string
|
||||
Arguments []string
|
||||
}
|
||||
|
||||
func (s *Shell) GetPty() *os.File {
|
||||
return s.pty
|
||||
}
|
||||
|
||||
func (s *Shell) Attach(conn io.ReadWriteCloser) error {
|
||||
pty, tty, err := termios.Pty()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.pty = pty
|
||||
s.tty = tty
|
||||
|
||||
if s.winsize != nil {
|
||||
SetWinsize(s.pty.Fd(), s.winsize)
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer s.pty.Close()
|
||||
io.Copy(s.pty, conn)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer conn.Close()
|
||||
io.Copy(conn, s.pty)
|
||||
}()
|
||||
|
||||
_, err = s.tty.Write(s.prompt)
|
||||
return err
|
||||
}
|
||||
|
||||
func NewShell(prompt string) *Shell {
|
||||
return &Shell{
|
||||
prompt: []byte(prompt),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Shell) SetWinsize(w, h uint32) {
|
||||
s.winsize = &Winsize{Width: uint16(w), Height: uint16(h)}
|
||||
|
||||
if s.pty != nil {
|
||||
SetWinsize(s.pty.Fd(), s.winsize)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Shell) Close() (error, error) {
|
||||
return s.pty.Close(), s.tty.Close()
|
||||
}
|
||||
|
||||
var ErrEmptyCommand error = errors.New("Empty command line")
|
||||
|
||||
type State struct {
|
||||
Variables map[string]string
|
||||
Commands map[string]CommandExecutor
|
||||
}
|
||||
|
||||
type Statement interface {
|
||||
Words() []Word
|
||||
Statements() []Statement
|
||||
AddStatement(Statement)
|
||||
Evaluate(state State, output io.Writer) (exitcode uint8)
|
||||
}
|
||||
|
||||
type BaseStatement struct {
|
||||
words []Word
|
||||
statements []Statement
|
||||
}
|
||||
|
||||
func (s *BaseStatement) Words() []Word {
|
||||
return s.words
|
||||
}
|
||||
|
||||
func (s *BaseStatement) Statements() []Statement {
|
||||
return s.statements
|
||||
}
|
||||
|
||||
func (s *BaseStatement) AddStatement(statement Statement) {
|
||||
s.statements = append(s.statements, statement)
|
||||
}
|
||||
|
||||
func (s *BaseStatement) AddWord(word Word) {
|
||||
s.words = append(s.words, word)
|
||||
}
|
||||
|
||||
type Word string
|
||||
|
||||
func (w Word) Statements() []Statement {
|
||||
return []Statement{}
|
||||
}
|
||||
|
||||
func (w Word) AddStatement(statement Statement) {}
|
||||
|
||||
func (w Word) AddWord(word Word) {}
|
||||
|
||||
type ParserParameters interface {
|
||||
MakeStatement() Statement
|
||||
ShouldLeave(Word, rune) bool
|
||||
Supports(rune) bool
|
||||
SubParsers() []Parser
|
||||
}
|
||||
|
||||
type Parser interface {
|
||||
Parse(*bufio.Reader) (Statement, error)
|
||||
Parameters() ParserParameters
|
||||
}
|
||||
|
||||
type BaseParser struct {
|
||||
parameters ParserParameters
|
||||
}
|
||||
|
||||
func (p *BaseParser) Parameters() ParserParameters {
|
||||
return p.parameters
|
||||
}
|
||||
|
||||
type BaseParameters struct {
|
||||
EntryMarkers []rune
|
||||
subParsers []Parser
|
||||
}
|
||||
|
||||
func (p *BaseParameters) SubParsers() []Parser {
|
||||
return p.subParsers
|
||||
}
|
||||
|
||||
func (p *BaseParameters) Supports(r rune) bool {
|
||||
for _, char := range p.EntryMarkers {
|
||||
if char == r {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *BaseParameters) MakeStatement() Statement {
|
||||
return &BaseStatement{}
|
||||
}
|
||||
|
||||
func (p *BaseParameters) ShouldLeave(w Word, r rune) bool {
|
||||
if r == '\n' || r == ';' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (p *BaseParser) Parse(r *bufio.Reader) (Statement, error) {
|
||||
var currentWord Word = ""
|
||||
statement := p.Parameters().MakeStatement()
|
||||
for {
|
||||
char, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return statement, err
|
||||
}
|
||||
|
||||
if char == ' ' || char == '\t' {
|
||||
statement.AddWord(currentWord)
|
||||
currentWord = ""
|
||||
}
|
||||
|
||||
if p.Parameters().ShouldLeave(currentWord, char) {
|
||||
return statement, nil
|
||||
}
|
||||
|
||||
var matchedParser bool
|
||||
|
||||
for _, parser := range p.Parameters().SubParsers() {
|
||||
if parser.Parameters().Supports(char) {
|
||||
matchedParser = true
|
||||
nestedStatement, err := parser.Parse(r)
|
||||
|
||||
if word, ok := nestedStatement.(Word); ok {
|
||||
statement.AddWord(word)
|
||||
} else {
|
||||
statement.AddStatement(nestedStatement)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return statement, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !matchedParser {
|
||||
currentWord += Word(char)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Shell) ReadCommand() (*Command, error) {
|
||||
var currentLine string
|
||||
var backslash bool
|
||||
var insideDoubleQuote bool
|
||||
var insideSingleQuote bool
|
||||
var maybeHeredoc bool
|
||||
var insideHeredoc bool
|
||||
var heredocMarker string
|
||||
var heredocMarkerComplete bool
|
||||
var heredocCurrentWord string
|
||||
|
||||
reader := bufio.NewReader(s.tty)
|
||||
|
||||
GatherCommand:
|
||||
for {
|
||||
char, _, err := reader.ReadRune()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if insideHeredoc {
|
||||
currentLine += string(char)
|
||||
|
||||
if !heredocMarkerComplete && char == '<' {
|
||||
insideHeredoc = false
|
||||
continue
|
||||
}
|
||||
|
||||
if char == '\n' {
|
||||
log.Printf("Heredoc current word: \"%s\"", heredocCurrentWord)
|
||||
if heredocCurrentWord == heredocMarker {
|
||||
log.Println("exiting heredoc")
|
||||
insideHeredoc = false
|
||||
heredocMarkerComplete = false
|
||||
break GatherCommand
|
||||
}
|
||||
|
||||
if !heredocMarkerComplete {
|
||||
log.Println("Encountered heredoc marker: " + heredocCurrentWord)
|
||||
heredocMarker = strings.Trim(heredocCurrentWord, "'")
|
||||
heredocMarkerComplete = true
|
||||
}
|
||||
|
||||
heredocCurrentWord = ""
|
||||
continue
|
||||
}
|
||||
|
||||
heredocCurrentWord += string(char)
|
||||
continue
|
||||
}
|
||||
|
||||
if backslash {
|
||||
if !insideDoubleQuote && char == '\n' {
|
||||
currentLine += string(" ")
|
||||
continue
|
||||
}
|
||||
|
||||
currentLine += "\\" + string(char)
|
||||
backslash = false
|
||||
continue
|
||||
}
|
||||
|
||||
switch rune(char) {
|
||||
case '<':
|
||||
currentLine += string(char)
|
||||
if maybeHeredoc {
|
||||
maybeHeredoc = false
|
||||
insideHeredoc = true
|
||||
continue
|
||||
}
|
||||
|
||||
maybeHeredoc = true
|
||||
continue
|
||||
case '\\':
|
||||
backslash = true
|
||||
continue
|
||||
case '"':
|
||||
insideDoubleQuote = !insideDoubleQuote
|
||||
currentLine += string(char)
|
||||
case '\'':
|
||||
insideSingleQuote = !insideSingleQuote
|
||||
currentLine += string(char)
|
||||
case '\n':
|
||||
if insideSingleQuote || insideDoubleQuote {
|
||||
currentLine += string(char)
|
||||
continue
|
||||
}
|
||||
|
||||
break GatherCommand
|
||||
default:
|
||||
currentLine += string(char)
|
||||
}
|
||||
}
|
||||
|
||||
commandLine, err := shlex.Split(currentLine)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(commandLine) == 0 {
|
||||
return nil, ErrEmptyCommand
|
||||
}
|
||||
|
||||
comm := &Command{Name: commandLine[0]}
|
||||
if len(commandLine) > 1 {
|
||||
comm.Arguments = commandLine[1:]
|
||||
}
|
||||
|
||||
return comm, nil
|
||||
}
|
||||
|
||||
func (s *Shell) SetPrompt(prompt string) {
|
||||
log.Printf("Changing prompt to \"%s\"", prompt)
|
||||
s.prompt = []byte(prompt)
|
||||
}
|
||||
|
||||
func (s *Shell) WritePrompt() error {
|
||||
_, err := s.tty.Write(s.prompt)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Shell) WriteOutput(output []byte) (int, error) {
|
||||
return s.tty.Write(output)
|
||||
}
|
||||
|
||||
func (s *Shell) WriteOutputString(output string) (int, error) {
|
||||
return s.WriteOutput([]byte(output))
|
||||
}
|
||||
|
||||
// Winsize stores the Height and Width of a terminal.
|
||||
type Winsize struct {
|
||||
Height uint16
|
||||
Width uint16
|
||||
x uint16 // unused
|
||||
y uint16 // unused
|
||||
}
|
||||
|
||||
// SetWinsize sets the size of the given pty.
|
||||
func SetWinsize(fd uintptr, winsize *Winsize) {
|
||||
log.Printf("window resize %dx%d", winsize.Width, winsize.Height)
|
||||
unix.Syscall(
|
||||
unix.SYS_IOCTL,
|
||||
fd,
|
||||
uintptr(unix.TIOCSWINSZ), uintptr(unsafe.Pointer(winsize)),
|
||||
)
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package shell
|
||||
|
||||
type Block struct {
|
||||
BaseToken
|
||||
}
|
||||
|
||||
type DollarBlock struct {
|
||||
BaseToken
|
||||
}
|
||||
|
||||
type BlockParameters struct{}
|
||||
type DollarBlockParameters struct {
|
||||
BlockParameters
|
||||
}
|
||||
|
||||
func (p *BlockParameters) Enter(i *CharIterator) error {
|
||||
return i.Next()
|
||||
}
|
||||
|
||||
func (p *DollarBlockParameters) SubParsers() []Parser {
|
||||
return []Parser{
|
||||
&BaseParser{
|
||||
parameters: &WordParameters{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *DollarBlockParameters) MakeToken() Token {
|
||||
return &DollarBlock{}
|
||||
}
|
||||
|
||||
func (p *BlockParameters) SubParsers() []Parser {
|
||||
return []Parser{
|
||||
&BaseParser{
|
||||
parameters: &StatementParameters{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *BlockParameters) Supports(charsBefore []rune, r rune) bool {
|
||||
if r == '{' {
|
||||
return len(charsBefore) == 0 ||
|
||||
countBackslashSuffixes(charsBefore)%2 == 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *BlockParameters) ShouldLeave(charsBefore []rune, r rune) bool {
|
||||
if r == '}' {
|
||||
return len(charsBefore) == 0 ||
|
||||
countBackslashSuffixes(charsBefore)%2 == 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *BlockParameters) MakeToken() Token {
|
||||
return &Block{}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Echo struct{}
|
||||
|
||||
func (e *Echo) Name() string {
|
||||
return "echo"
|
||||
}
|
||||
|
||||
func (e *Echo) Execute(arguments []string, _ io.Reader, stdout io.Writer, stderr io.Writer) uint8 {
|
||||
_, err := stdout.Write([]byte(strings.Join(arguments, " ")))
|
||||
if err != nil {
|
||||
stderr.Write([]byte(err.Error()))
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package shell
|
||||
|
||||
import "io"
|
||||
|
||||
type CommandExecutor interface {
|
||||
Name() string
|
||||
Execute(arguments []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) (exitcode uint8)
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
package shell
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
)
|
||||
|
||||
type DollarParameters struct {
|
||||
ignoreParams *IgnoreFirstCharParameters
|
||||
}
|
||||
|
||||
func (p *DollarParameters) SubParsers() []Parser {
|
||||
if p.ignoreParams == nil {
|
||||
p.ignoreParams = &IgnoreFirstCharParameters{}
|
||||
}
|
||||
|
||||
return []Parser{
|
||||
&BaseParser{
|
||||
parameters: p.ignoreParams,
|
||||
},
|
||||
&BaseParser{
|
||||
parameters: &WordParameters{},
|
||||
},
|
||||
&BaseParser{
|
||||
parameters: &SubshellParameters{},
|
||||
},
|
||||
&BaseParser{
|
||||
parameters: &DollarBlockParameters{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *DollarParameters) Enter(_ *CharIterator) error {
|
||||
if p.ignoreParams != nil {
|
||||
p.ignoreParams.Reset()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *DollarParameters) Supports(charsBefore []rune, r rune) bool {
|
||||
if r == '$' {
|
||||
return len(charsBefore) == 0 ||
|
||||
countBackslashSuffixes(charsBefore)%2 == 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *DollarParameters) ShouldLeave(charsBefore []rune, r rune) bool {
|
||||
if isWhitespace(r) || r == ';' {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *DollarParameters) Leave(i *CharIterator) error {
|
||||
return i.Previous()
|
||||
}
|
||||
|
||||
func (p *DollarParameters) MakeToken() Token {
|
||||
return &Dollar{}
|
||||
}
|
||||
|
||||
type Dollar struct {
|
||||
BaseToken
|
||||
}
|
||||
|
||||
func (d *Dollar) Expand(state State, stdin io.Reader, stderr io.Writer) ([]string, uint8) {
|
||||
if len(d.tokens) > 1 {
|
||||
logstr := "Unexpected tokens after dollar sign: " + d.tokens[1].String()
|
||||
log.Println(logstr)
|
||||
stderr.Write([]byte(logstr))
|
||||
return []string{""}, 2
|
||||
}
|
||||
|
||||
if word, ok := d.tokens[0].(*Word); ok {
|
||||
return []string{state.Variable(word.String())}, 0
|
||||
} else if block, ok := d.tokens[0].(*DollarBlock); ok && len(block.Tokens()) > 0 {
|
||||
|
||||
if word, ok := block.Tokens()[0].(*Word); ok {
|
||||
return []string{state.Variable(word.String())}, 0
|
||||
} else {
|
||||
log.Println("Whaat")
|
||||
}
|
||||
} else {
|
||||
logstr := "Unexpected tokens after dollar sign: " + d.String()
|
||||
log.Println(logstr)
|
||||
stderr.Write([]byte(logstr))
|
||||
return []string{""}, 2
|
||||
}
|
||||
|
||||
return []string{""}, 1
|
||||
}
|
||||
|
||||
type IgnoreFirstCharParameters struct {
|
||||
entered bool
|
||||
}
|
||||
|
||||
func (p *IgnoreFirstCharParameters) Reset() {
|
||||
p.entered = false
|
||||
}
|
||||
|
||||
func (p *IgnoreFirstCharParameters) Enter(_ *CharIterator) error {
|
||||
p.entered = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *IgnoreFirstCharParameters) SubParsers() []Parser {
|
||||
return []Parser{
|
||||
&BaseParser{
|
||||
parameters: &CharParserParameters{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *IgnoreFirstCharParameters) MakeToken() Token {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *IgnoreFirstCharParameters) Supports(_ []rune, r rune) bool {
|
||||
if !p.entered {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *IgnoreFirstCharParameters) ShouldLeave(_ []rune, r rune) bool {
|
||||
return true
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package shell
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type DollarTest struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (t *DollarTest) TestParse() {
|
||||
parser := &BaseParser{&DollarParameters{}}
|
||||
reader := bufio.NewReader(strings.NewReader("$variable"))
|
||||
|
||||
iterator, err := NewCharIterator(reader)
|
||||
t.NoError(err)
|
||||
|
||||
token, err := parser.Parse(iterator, &CharCollection{})
|
||||
t.ErrorIs(err, io.EOF)
|
||||
|
||||
t.IsType(&Dollar{}, token)
|
||||
t.IsType(&Word{}, token.Tokens()[0])
|
||||
|
||||
t.Equal(token.Tokens()[0].String(), "variable")
|
||||
}
|
||||
|
||||
func (t *DollarTest) TestExpand() {
|
||||
parser := &BaseParser{&DollarParameters{}}
|
||||
reader := bufio.NewReader(strings.NewReader("$variable $hey"))
|
||||
|
||||
iterator, err := NewCharIterator(reader)
|
||||
t.NoError(err)
|
||||
|
||||
token, err := parser.Parse(iterator, &CharCollection{})
|
||||
|
||||
t.IsType(&Dollar{}, token)
|
||||
t.IsType(&Word{}, token.Tokens()[0])
|
||||
|
||||
state := NewShellState()
|
||||
state.SetVariable("variable", "Test")
|
||||
|
||||
expanded, exit := token.(Expandable).Expand(state, nil, nil)
|
||||
t.Equal(uint8(0), exit)
|
||||
t.Equal(expanded[0], "Test")
|
||||
}
|
||||
|
||||
func TestDollarTest(t *testing.T) {
|
||||
suite.Run(t, new(DollarTest))
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package shell
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type LineParameters struct{}
|
||||
|
||||
func (p *LineParameters) SubParsers() []Parser {
|
||||
return []Parser{
|
||||
&BaseParser{
|
||||
parameters: &StatementParameters{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *LineParameters) Supports(charsBefore []rune, r rune) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *LineParameters) ShouldLeave(charsBefore []rune, r rune) bool {
|
||||
if r == '\n' {
|
||||
return countBackslashSuffixes(charsBefore)%2 == 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *LineParameters) MakeToken() Token {
|
||||
return &Line{}
|
||||
}
|
||||
|
||||
type Line struct {
|
||||
BaseToken
|
||||
}
|
||||
|
||||
func (l *Line) Evaluate(state State, stdin io.Reader, stdout io.Writer, stderr io.Writer) uint8 {
|
||||
var retval uint8 = 0
|
||||
|
||||
for _, token := range l.Tokens() {
|
||||
if eval, ok := token.(Evalable); ok {
|
||||
retval = eval.Evaluate(state, stdin, stdout, stderr)
|
||||
} else {
|
||||
stderr.Write([]byte(
|
||||
fmt.Sprintf(
|
||||
"shell: Syntax error near unexpected token: %s",
|
||||
token.String(),
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
return retval
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
package shell
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type Parser interface {
|
||||
Parse(*CharIterator, *CharCollection) (Token, error)
|
||||
Parameters() ParserParameters
|
||||
}
|
||||
|
||||
type BaseParser struct {
|
||||
parameters ParserParameters
|
||||
}
|
||||
|
||||
func (p *BaseParser) Parameters() ParserParameters {
|
||||
return p.parameters
|
||||
}
|
||||
|
||||
type CharIterator struct {
|
||||
reader *bufio.Reader
|
||||
currentChar rune
|
||||
lastChar rune
|
||||
}
|
||||
|
||||
func NewCharIterator(reader *bufio.Reader) (*CharIterator, error) {
|
||||
char, _, err := reader.ReadRune()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &CharIterator{
|
||||
reader: reader,
|
||||
currentChar: char,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (i *CharIterator) Next() error {
|
||||
var err error
|
||||
|
||||
i.lastChar = i.currentChar
|
||||
i.currentChar, _, err = i.reader.ReadRune()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *CharIterator) Previous() error {
|
||||
if i.lastChar == -1 {
|
||||
return errors.New("Chariterator can only go back once")
|
||||
}
|
||||
|
||||
err := i.reader.UnreadRune()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i.currentChar = i.lastChar
|
||||
i.lastChar = -1
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
type CharCollection struct {
|
||||
chars []rune
|
||||
}
|
||||
|
||||
func (c *CharCollection) Append(r rune) {
|
||||
c.chars = append(c.chars, r)
|
||||
}
|
||||
|
||||
func (c *CharCollection) Chars() []rune {
|
||||
return c.chars
|
||||
}
|
||||
|
||||
func (i CharIterator) Current() rune {
|
||||
return i.currentChar
|
||||
}
|
||||
|
||||
func (p *BaseParser) Parse(i *CharIterator, charsBefore *CharCollection) (Token, error) {
|
||||
token := p.Parameters().MakeToken()
|
||||
if enter, ok := p.parameters.(Enterable); ok {
|
||||
err := enter.Enter(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
parsers := p.Parameters().SubParsers()
|
||||
|
||||
ParseLoop:
|
||||
for {
|
||||
char := i.Current()
|
||||
|
||||
if p.Parameters().ShouldLeave(charsBefore.Chars(), char) {
|
||||
if leave, ok := p.Parameters().(Leavable); ok {
|
||||
err := leave.Leave(i)
|
||||
if err != nil {
|
||||
return token, err
|
||||
}
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
var matchedParser bool
|
||||
for _, parser := range parsers {
|
||||
if parser.Parameters().Supports(charsBefore.Chars(), char) {
|
||||
matchedParser = true
|
||||
|
||||
nestedToken, err := parser.Parse(i, charsBefore)
|
||||
|
||||
if token != nil {
|
||||
token.AddToken(nestedToken)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return token, err
|
||||
}
|
||||
|
||||
charsBefore.Append(char)
|
||||
err = i.Next()
|
||||
if err != nil {
|
||||
return token, err
|
||||
}
|
||||
|
||||
continue ParseLoop
|
||||
}
|
||||
}
|
||||
|
||||
if !matchedParser {
|
||||
return token, fmt.Errorf(
|
||||
"Parser encountered unsupported token or char. Parser: %s full text: %s -\n Char: %s",
|
||||
reflect.TypeOf(*&p.parameters).String(),
|
||||
string(charsBefore.Chars()),
|
||||
string(char),
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package shell
|
||||
|
||||
type ParserParameters interface {
|
||||
MakeToken() Token
|
||||
ShouldLeave(charsBefore []rune, r rune) bool
|
||||
Supports(charsBefore []rune, r rune) bool
|
||||
SubParsers() []Parser
|
||||
}
|
||||
|
||||
type Enterable interface {
|
||||
Enter(*CharIterator) error
|
||||
}
|
||||
|
||||
type Resetable interface {
|
||||
Reset()
|
||||
}
|
||||
|
||||
type Leavable interface {
|
||||
Leave(*CharIterator) error
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package shell
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type CharIteratorTest struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (t *CharIteratorTest) TestNextAndBacktrack() {
|
||||
charIterator, err := NewCharIterator(bufio.NewReader(strings.NewReader("abcdefg")))
|
||||
t.NoError(err)
|
||||
|
||||
t.Equal(string('a'), string(charIterator.Current()))
|
||||
|
||||
err = charIterator.Next()
|
||||
t.NoError(err)
|
||||
|
||||
t.Equal(string('b'), string(charIterator.Current()))
|
||||
|
||||
err = charIterator.Previous()
|
||||
t.NoError(err)
|
||||
|
||||
t.Equal(string('a'), string(charIterator.Current()))
|
||||
|
||||
err = charIterator.Previous()
|
||||
t.Error(err)
|
||||
t.Equal("Chariterator can only go back once", err.Error())
|
||||
|
||||
err = charIterator.Next()
|
||||
t.NoError(err)
|
||||
|
||||
t.Equal(string('b'), string(charIterator.Current()))
|
||||
|
||||
}
|
||||
|
||||
func TestCharIteratorTest(t *testing.T) {
|
||||
suite.Run(t, new(CharIteratorTest))
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package shell
|
||||
|
||||
type Pipe struct {
|
||||
BaseToken
|
||||
}
|
||||
|
||||
type PipeParameters struct {
|
||||
StatementParameters
|
||||
}
|
||||
|
||||
func (p *PipeParameters) SubParsers() []Parser {
|
||||
return []Parser{
|
||||
&BaseParser{
|
||||
parameters: &StatementParameters{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PipeParameters) Supports(charsBefore []rune, r rune) bool {
|
||||
if r == '|' {
|
||||
return len(charsBefore) == 0 ||
|
||||
countBackslashSuffixes(charsBefore)%2 == 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *PipeParameters) MakeToken() Token {
|
||||
return &Pipe{}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package shell
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type QuoteParameters struct{}
|
||||
|
||||
func (p *QuoteParameters) SubParsers() []Parser {
|
||||
return []Parser{
|
||||
&BaseParser{
|
||||
parameters: &DollarParameters{},
|
||||
},
|
||||
&BaseParser{
|
||||
parameters: &CharParserParameters{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *QuoteParameters) Supports(charsBefore []rune, r rune) bool {
|
||||
if r == '"' {
|
||||
return len(charsBefore) == 0 ||
|
||||
countBackslashSuffixes(charsBefore)%2 == 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *QuoteParameters) ShouldLeave(charsBefore []rune, r rune) bool {
|
||||
if r == '"' {
|
||||
return len(charsBefore) == 0 ||
|
||||
countBackslashSuffixes(charsBefore)%2 == 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *QuoteParameters) MakeToken() Token {
|
||||
return &Quote{}
|
||||
}
|
||||
|
||||
type Quote struct {
|
||||
BaseToken
|
||||
}
|
||||
|
||||
func (q *Quote) Expand(state State, stdin io.Reader, stderr io.Writer) ([]string, uint8) {
|
||||
var retcode uint8
|
||||
var str string
|
||||
|
||||
for _, token := range q.Tokens() {
|
||||
if expand, ok := token.(Expandable); ok {
|
||||
log.Println(expand)
|
||||
var expansion []string
|
||||
expansion, retcode = expand.Expand(state, stdin, stderr)
|
||||
|
||||
str += strings.Join(expansion, " ")
|
||||
continue
|
||||
}
|
||||
|
||||
str += token.String()
|
||||
}
|
||||
|
||||
return []string{strings.Trim(str, "'")}, retcode
|
||||
}
|
||||
|
||||
type SingleQuoteParameters struct {
|
||||
QuoteParameters
|
||||
}
|
||||
|
||||
type SingleQuote struct {
|
||||
BaseToken
|
||||
}
|
||||
|
||||
func (p *SingleQuoteParameters) Supports(charsBefore []rune, r rune) bool {
|
||||
if r == '\'' {
|
||||
return len(charsBefore) == 0 ||
|
||||
countBackslashSuffixes(charsBefore)%2 == 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *SingleQuoteParameters) ShouldLeave(charsBefore []rune, r rune) bool {
|
||||
if r == '\'' {
|
||||
return len(charsBefore) == 0 ||
|
||||
countBackslashSuffixes(charsBefore)%2 == 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package shell
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.snorba.art/hugo/nssh/commands/shell/builtins"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type QuotesTest struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (t *QuotesTest) TestParse() {
|
||||
expectedContents := fmt.Sprintf("'%s'", "word1 word2 word3\n word4")
|
||||
|
||||
parser := &BaseParser{&QuoteParameters{}}
|
||||
reader := bufio.NewReader(strings.NewReader(expectedContents))
|
||||
|
||||
iterator, err := NewCharIterator(reader)
|
||||
t.NoError(err)
|
||||
|
||||
token, err := parser.Parse(iterator, &CharCollection{})
|
||||
t.ErrorIs(err, io.EOF)
|
||||
|
||||
t.Equal(expectedContents, token.String())
|
||||
|
||||
expanded, _ := token.(Expandable).Expand(nil, nil, nil)
|
||||
t.Equal("word1 word2 word3\n word4", strings.Join(expanded, " "))
|
||||
}
|
||||
|
||||
func (t *QuotesTest) TestExpandVariables() {
|
||||
parser := &BaseParser{&QuoteParameters{}}
|
||||
reader := bufio.NewReader(
|
||||
strings.NewReader("This is a $variable ${variable2} $variable"),
|
||||
)
|
||||
|
||||
state := NewShellState()
|
||||
state.SetVariable("variable", "supergood")
|
||||
state.SetVariable("variable2", "thing")
|
||||
|
||||
echo := &builtins.Echo{}
|
||||
state.SetCommand(echo.Name(), echo)
|
||||
|
||||
iterator, err := NewCharIterator(reader)
|
||||
t.NoError(err)
|
||||
|
||||
token, err := parser.Parse(iterator, &CharCollection{})
|
||||
t.ErrorIs(err, io.EOF)
|
||||
|
||||
expanded, _ := token.(Expandable).Expand(state, nil, nil)
|
||||
t.Equal("This is a supergood thing supergood", expanded[0])
|
||||
}
|
||||
|
||||
func TestQuotesTest(t *testing.T) {
|
||||
suite.Run(t, new(QuotesTest))
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package shell
|
||||
|
||||
import (
|
||||
"bufio"
|
@ -0,0 +1,112 @@
|
||||
package shell
|
||||
|
||||
type State interface {
|
||||
Variable(string) string
|
||||
Command(string) CommandExecutor
|
||||
Env(string) string
|
||||
|
||||
Pushd(string) error
|
||||
Popd() error
|
||||
Cwd() string
|
||||
|
||||
SetVariable(string, string)
|
||||
SetCommand(string, CommandExecutor)
|
||||
SetEnv(string, string)
|
||||
|
||||
Clone() State
|
||||
}
|
||||
|
||||
func NewShellState() *ShellState {
|
||||
return &ShellState{
|
||||
variables: map[string]string{},
|
||||
commands: map[string]CommandExecutor{},
|
||||
env: map[string]string{},
|
||||
dirstack: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
type ShellState struct {
|
||||
variables map[string]string
|
||||
commands map[string]CommandExecutor
|
||||
env map[string]string
|
||||
dirstack []string
|
||||
}
|
||||
|
||||
func (s *ShellState) Variable(n string) string {
|
||||
if v, ok := s.variables[n]; ok {
|
||||
return v
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *ShellState) Command(n string) CommandExecutor {
|
||||
if v, ok := s.commands[n]; ok {
|
||||
return v
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ShellState) Env(n string) string {
|
||||
if v, ok := s.env[n]; ok {
|
||||
return v
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *ShellState) SetVariable(n string, v string) {
|
||||
s.variables[n] = v
|
||||
}
|
||||
|
||||
func (s *ShellState) SetCommand(n string, e CommandExecutor) {
|
||||
s.commands[n] = e
|
||||
}
|
||||
|
||||
func (s *ShellState) SetEnv(n string, v string) {
|
||||
s.env[n] = v
|
||||
}
|
||||
|
||||
func (s *ShellState) Pushd(dir string) error {
|
||||
s.dirstack = append([]string{dir}, s.dirstack...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ShellState) Popd() error {
|
||||
if len(s.dirstack) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
s.dirstack = s.dirstack[1:]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ShellState) Cwd() string {
|
||||
if len(s.dirstack) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return s.dirstack[0]
|
||||
}
|
||||
|
||||
func (s *ShellState) Clone() State {
|
||||
state := NewShellState()
|
||||
|
||||
for k, v := range s.commands {
|
||||
state.SetCommand(k, v)
|
||||
}
|
||||
|
||||
for k, v := range s.variables {
|
||||
state.SetVariable(k, v)
|
||||
}
|
||||
|
||||
for k, v := range s.env {
|
||||
state.SetEnv(k, v)
|
||||
}
|
||||
|
||||
state.dirstack = make([]string, len(s.dirstack))
|
||||
copy(s.dirstack, state.dirstack)
|
||||
|
||||
return state
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package shell
|
||||
|
||||
type Statement struct {
|
||||
BaseToken
|
||||
}
|
||||
|
||||
type StatementParameters struct{}
|
||||
|
||||
func (p *StatementParameters) SubParsers() []Parser {
|
||||
return []Parser{
|
||||
&BaseParser{
|
||||
parameters: &QuoteParameters{},
|
||||
},
|
||||
&BaseParser{
|
||||
parameters: &SingleQuoteParameters{},
|
||||
},
|
||||
&BaseParser{
|
||||
parameters: &WordParameters{},
|
||||
},
|
||||
&BaseParser{
|
||||
parameters: &DollarParameters{},
|
||||
},
|
||||
&BaseParser{
|
||||
parameters: &PipeParameters{},
|
||||
},
|
||||
&BaseParser{
|
||||
parameters: &SubshellParameters{},
|
||||
},
|
||||
&BaseParser{
|
||||
parameters: &BlockParameters{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *StatementParameters) Supports(charsBefore []rune, r rune) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *StatementParameters) MakeToken() Token {
|
||||
return &Statement{}
|
||||
}
|
||||
|
||||
func (p *StatementParameters) ShouldLeave(charsBefore []rune, r rune) bool {
|
||||
if r == '\n' || r == ';' {
|
||||
return countBackslashSuffixes(charsBefore)%2 == 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package shell
|
||||
|
||||
type SubshellParameters struct{}
|
||||
|
||||
func (p *SubshellParameters) SubParsers() []Parser {
|
||||
return []Parser{
|
||||
&BaseParser{
|
||||
parameters: &StatementParameters{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *SubshellParameters) Supports(charsBefore []rune, r rune) bool {
|
||||
if r == '(' {
|
||||
return len(charsBefore) == 0 ||
|
||||
countBackslashSuffixes(charsBefore)%2 == 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *SubshellParameters) ShouldLeave(charsBefore []rune, r rune) bool {
|
||||
if r == ')' {
|
||||
return len(charsBefore) == 0 ||
|
||||
countBackslashSuffixes(charsBefore)%2 == 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *SubshellParameters) MakeToken() Token {
|
||||
return &Subshell{}
|
||||
}
|
||||
|
||||
type Subshell struct {
|
||||
BaseToken
|
||||
}
|
||||
|
||||
type Backtick struct {
|
||||
Subshell
|
||||
}
|
||||
|
||||
type BacktickParameters struct {
|
||||
SubshellParameters
|
||||
}
|
||||
|
||||
func (p *BacktickParameters) Supports(charsBefore []rune, r rune) bool {
|
||||
if r == '`' {
|
||||
return len(charsBefore) == 0 ||
|
||||
countBackslashSuffixes(charsBefore)%2 == 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *BacktickParameters) ShouldLeave(charsBefore []rune, r rune) bool {
|
||||
if r == '`' {
|
||||
return len(charsBefore) == 0 ||
|
||||
countBackslashSuffixes(charsBefore)%2 == 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package shell
|
||||
|
||||
import "io"
|
||||
|
||||
type Token interface {
|
||||
Tokens() []Token
|
||||
AddToken(Token)
|
||||
String() string
|
||||
}
|
||||
|
||||
type Expandable interface {
|
||||
Expand(state State, stdin io.Reader, stderr io.Writer) ([]string, uint8)
|
||||
}
|
||||
|
||||
type Evalable interface {
|
||||
Evaluate(state State, stdin io.Reader, stdout io.Writer, stderr io.Writer) uint8
|
||||
}
|
||||
|
||||
type BaseToken struct {
|
||||
tokens []Token
|
||||
}
|
||||
|
||||
func (t *BaseToken) Tokens() []Token {
|
||||
return t.tokens
|
||||
}
|
||||
|
||||
func (t *BaseToken) AddToken(token Token) {
|
||||
if token != nil {
|
||||
t.tokens = append(t.tokens, token)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *BaseToken) String() string {
|
||||
var str string
|
||||
|
||||
for _, token := range t.Tokens() {
|
||||
str += token.String()
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package shell
|
||||
|
||||
func countBackslashSuffixes(chars []rune) int {
|
||||
backslashCount := 0
|
||||
for i := len(chars) - 1; i > 0; i-- {
|
||||
if chars[i] == '\\' {
|
||||
backslashCount += 1
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
return backslashCount
|
||||
}
|
||||
|
||||
func isWhitespace(r rune) bool {
|
||||
return r == ' ' || r == '\t' || r == '\n'
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package shell
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type UtilTest struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (t *UtilTest) TestCountBackslashSuffixes() {
|
||||
three := []rune{'a', '\\', 'b', '\\', '\\', '\\'}
|
||||
t.Equal(3, countBackslashSuffixes(three))
|
||||
|
||||
six := []rune{'a', '\\', 'b', '\\', '\\', '\\', '\\', '\\', '\\'}
|
||||
t.Equal(6, countBackslashSuffixes(six))
|
||||
|
||||
zero := []rune{'a', '\\', 'b', '\\', '\\', '\\', 'c'}
|
||||
t.Equal(0, countBackslashSuffixes(zero))
|
||||
}
|
||||
|
||||
func TestUtilTest(t *testing.T) {
|
||||
suite.Run(t, new(UtilTest))
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package shell
|
||||
|
||||
type WhitespaceParameters struct{}
|
||||
|
||||
func (p *WhitespaceParameters) SubParsers() []Parser {
|
||||
return []Parser{
|
||||
&BaseParser{
|
||||
parameters: &WhitespaceParameters{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *WhitespaceParameters) Supports(charsBefore []rune, r rune) bool {
|
||||
return isWhitespace(r)
|
||||
}
|
||||
|
||||
func (p *WhitespaceParameters) ShouldLeave(charsBefore []rune, r rune) bool {
|
||||
return !p.Supports(charsBefore, r)
|
||||
}
|
||||
|
||||
func (p *WhitespaceParameters) MakeToken() Token {
|
||||
return &Whitespace{}
|
||||
}
|
||||
|
||||
type Whitespace struct {
|
||||
tokens []Token
|
||||
}
|
||||
|
||||
func (q *Whitespace) AddToken(s Token) {
|
||||
q.tokens = append(q.tokens, s)
|
||||
}
|
||||
|
||||
func (q *Whitespace) Tokens() []Token {
|
||||
return q.tokens
|
||||
}
|
||||
|
||||
func (q *Whitespace) String() string {
|
||||
var str string
|
||||
|
||||
for _, t := range q.Tokens() {
|
||||
str += t.String()
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
package shell
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func NewWord(wordS string) *Word {
|
||||
word := new(Word)
|
||||
|
||||
for _, c := range wordS {
|
||||
word.AddToken(Char(c))
|
||||
}
|
||||
|
||||
return word
|
||||
}
|
||||
|
||||
type Word struct {
|
||||
chars []Char
|
||||
consecutiveBackslashes int
|
||||
}
|
||||
|
||||
func (w *Word) Tokens() []Token {
|
||||
return []Token{}
|
||||
}
|
||||
|
||||
func (w *Word) AddToken(token Token) {
|
||||
if char, ok := token.(Char); ok {
|
||||
if rune(char) == '\\' {
|
||||
w.consecutiveBackslashes += 1
|
||||
} else if w.consecutiveBackslashes%2 == 1 {
|
||||
if len(w.chars) > 0 {
|
||||
w.chars = w.chars[:len(w.chars)-1]
|
||||
} else {
|
||||
log.Println(
|
||||
"Parse error: funky shit is happening, a word with 0" +
|
||||
" chars was expected to contain a backslash",
|
||||
)
|
||||
}
|
||||
|
||||
w.consecutiveBackslashes = 0
|
||||
}
|
||||
|
||||
w.chars = append(w.chars, char)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Word) Expand(_ State, _ io.Reader, _ io.Writer) ([]string, uint8) {
|
||||
var str string
|
||||
for _, char := range w.chars {
|
||||
str += string(char)
|
||||
}
|
||||
|
||||
return []string{str}, 0
|
||||
}
|
||||
|
||||
func (w *Word) String() string {
|
||||
str, _ := w.Expand(nil, nil, nil)
|
||||
|
||||
return strings.Join(str, "")
|
||||
}
|
||||
|
||||
type WordParameters struct{}
|
||||
|
||||
func (p *WordParameters) MakeToken() Token {
|
||||
return new(Word)
|
||||
}
|
||||
|
||||
var wordRegexp = regexp.MustCompile("[0-9a-zA-Z_.~/:\\\\=-]")
|
||||
|
||||
func (p *WordParameters) Supports(charsBefore []rune, r rune) bool {
|
||||
return wordRegexp.MatchString(string(r))
|
||||
}
|
||||
|
||||
func (p *WordParameters) ShouldLeave(charsBefore []rune, r rune) bool {
|
||||
if !p.Supports(charsBefore, r) {
|
||||
return countBackslashSuffixes(charsBefore)%2 == 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *WordParameters) Leave(i *CharIterator) error {
|
||||
return i.Previous()
|
||||
}
|
||||
|
||||
func (p *WordParameters) SubParsers() []Parser {
|
||||
return []Parser{
|
||||
&BaseParser{
|
||||
parameters: &CharParserParameters{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type Char rune
|
||||
|
||||
func (c Char) Tokens() []Token {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Char) AddToken(_ Token) {}
|
||||
|
||||
func (c Char) String() string {
|
||||
return string(c)
|
||||
}
|
||||
|
||||
type CharParserParameters struct {
|
||||
currentRune rune
|
||||
}
|
||||
|
||||
func (p *CharParserParameters) MakeToken() Token {
|
||||
return Char(p.currentRune)
|
||||
}
|
||||
|
||||
func (p *CharParserParameters) Supports(charsBefore []rune, r rune) bool {
|
||||
p.currentRune = r
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *CharParserParameters) ShouldLeave(charsBefore []rune, r rune) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *CharParserParameters) SubParsers() []Parser {
|
||||
return nil
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
package shell
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type WordTest struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (t *WordTest) TestSupports() {
|
||||
p := &WordParameters{}
|
||||
|
||||
noChars := []rune{}
|
||||
t.True(p.Supports(noChars, 'a'))
|
||||
t.True(p.Supports(noChars, '-'))
|
||||
t.True(p.Supports(noChars, 'A'))
|
||||
t.True(p.Supports(noChars, '0'))
|
||||
t.True(p.Supports(noChars, '.'))
|
||||
}
|
||||
|
||||
func (t *WordTest) TestShouldLeave() {
|
||||
p := &WordParameters{}
|
||||
|
||||
noChars := []rune{}
|
||||
t.True(p.ShouldLeave(noChars, ' '))
|
||||
t.True(p.ShouldLeave(noChars, '\t'))
|
||||
t.True(p.ShouldLeave(noChars, '\n'))
|
||||
}
|
||||
func (t *WordTest) TestAddToken() {
|
||||
word := NewWord("")
|
||||
|
||||
word.AddToken(Char('a'))
|
||||
|
||||
t.Equal("a", word.String())
|
||||
}
|
||||
|
||||
func (t *WordTest) TestParse() {
|
||||
parser := &BaseParser{&WordParameters{}}
|
||||
reader := bufio.NewReader(strings.NewReader("word1 word2 word\\ 3"))
|
||||
|
||||
iterator, err := NewCharIterator(reader)
|
||||
t.NoError(err)
|
||||
|
||||
token, err := parser.Parse(iterator, &CharCollection{})
|
||||
t.NoError(err)
|
||||
t.Equal("word1", token.String())
|
||||
|
||||
iterator.Next()
|
||||
token, err = parser.Parse(iterator, &CharCollection{})
|
||||
t.NoError(err)
|
||||
t.Equal("", token.String())
|
||||
|
||||
iterator.Next()
|
||||
iterator.Next()
|
||||
token, err = parser.Parse(iterator, &CharCollection{})
|
||||
t.NoError(err)
|
||||
t.Equal("word2", token.String())
|
||||
|
||||
iterator.Next()
|
||||
iterator.Next()
|
||||
token, err = parser.Parse(iterator, &CharCollection{})
|
||||
t.ErrorIs(err, io.EOF)
|
||||
t.Equal("word 3", token.String())
|
||||
}
|
||||
|
||||
func (t *WordTest) TestParseBackslash() {
|
||||
parser := &BaseParser{&WordParameters{}}
|
||||
reader := bufio.NewReader(strings.NewReader("w\\\\\\\\\\o=rd1\\ \\$wo\\\\rd2\\\\\\ word\\ 3"))
|
||||
|
||||
iterator, err := NewCharIterator(reader)
|
||||
t.NoError(err)
|
||||
|
||||
token, err := parser.Parse(iterator, &CharCollection{})
|
||||
t.ErrorIs(err, io.EOF)
|
||||
t.Equal("w\\\\\\\\o=rd1 $wo\\\\rd2\\\\ word 3", token.String())
|
||||
}
|
||||
|
||||
func TestWordTest(t *testing.T) {
|
||||
suite.Run(t, new(WordTest))
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func Control(c rune) rune {
|
||||
return c & 0x1f
|
||||
}
|
||||
|
||||
func sttyCharFromDesc(c string) (rune, error) {
|
||||
if len(c) > 2 || len(c) < 1 {
|
||||
return 'i', errors.New(
|
||||
"stty char description has unsupported length: " +
|
||||
strconv.Itoa(len(c)),
|
||||
)
|
||||
}
|
||||
|
||||
if rune(c[0]) == '^' {
|
||||
return Control(rune(c[1])), nil
|
||||
}
|
||||
|
||||
return rune(c[0]), nil
|
||||
}
|
||||
|
||||
const fakeUname = "Linux 5.16.19-76051619-generic"
|
||||
|
||||
type CommandExecutor interface {
|
||||
Execute(shell *Shell, args []string) error
|
||||
}
|
||||
|
||||
type SttyCommand struct{}
|
||||
|
||||
func (c *SttyCommand) Execute(shell *Shell, args []string) error {
|
||||
// Tramp uses:
|
||||
// -inlcr: don't translate newline to carriage return
|
||||
// -onlcr: don't translate newline to carriage return-newline
|
||||
// -echo: echo input characters
|
||||
// kill CHAR: CHAR will erase the current line
|
||||
// erase CHAR: CHAR will erase the last character typed
|
||||
// icanon: enable special charactersq
|
||||
|
||||
fd := int(shell.GetPty().Fd())
|
||||
const ioctlReadTermios = unix.TCGETS // Linux
|
||||
const ioctlWriteTermios = unix.TCSETS // Linux
|
||||
|
||||
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
newState := *termios
|
||||
|
||||
var skipNext bool
|
||||
for i, arg := range args {
|
||||
if skipNext {
|
||||
skipNext = false
|
||||
continue
|
||||
}
|
||||
|
||||
var disable bool
|
||||
if arg[0] == '-' {
|
||||
disable = true
|
||||
arg = arg[1:]
|
||||
}
|
||||
|
||||
switch arg {
|
||||
case "onlcr":
|
||||
if disable {
|
||||
newState.Iflag &^= unix.ONLCR
|
||||
continue
|
||||
}
|
||||
|
||||
newState.Iflag |= unix.ONLCR
|
||||
case "inlcr":
|
||||
if disable {
|
||||
newState.Iflag &^= unix.INLCR
|
||||
continue
|
||||
}
|
||||
|
||||
newState.Iflag |= unix.INLCR
|
||||
case "echo":
|
||||
if disable {
|
||||
newState.Lflag &^= unix.ECHO
|
||||
continue
|
||||
}
|
||||
|
||||
newState.Lflag |= unix.ECHO
|
||||
case "kill":
|
||||
skipNext = true
|
||||
|
||||
char, err := sttyCharFromDesc(args[i+1])
|
||||
if err != nil {
|
||||
log.Printf(
|
||||
"Warning: Not applying unsupported character description for stty kill: %s",
|
||||
args[i+1],
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
newState.Cc[unix.VKILL] = uint8(char)
|
||||
case "erase":
|
||||
skipNext = true
|
||||
|
||||
char, err := sttyCharFromDesc(args[i+1])
|
||||
if err != nil {
|
||||
log.Printf(
|
||||
"Warning: Not applying unsupported character description for stty erase: %s",
|
||||
args[i+1],
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
newState.Cc[unix.VERASE] = uint8(char)
|
||||
case "icanon":
|
||||
if disable {
|
||||
newState.Lflag &^= unix.ICANON
|
||||
continue
|
||||
}
|
||||
|
||||
newState.Lflag |= unix.ICANON
|
||||
|
||||
case "tab0":
|
||||
newState.Oflag = (newState.Oflag & ^uint32(unix.TABDLY)) | unix.TAB0
|
||||
case "iutf8":
|
||||
if disable {
|
||||
newState.Iflag &^= unix.IUTF8
|
||||
continue
|
||||
}
|
||||
|
||||
newState.Iflag |= unix.IUTF8
|
||||
|
||||
default:
|
||||
log.Printf(
|
||||
"Warning, tramp requested unexpected stty option %s",
|
||||
arg,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, &newState); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"git.snorba.art/hugo/nssh/storage"
|
||||
)
|
||||
|
||||
type TestCommand struct {
|
||||
filestore storage.Filestore
|
||||
}
|
||||
|
||||
var fakeDirs = map[string]bool{
|
||||
"/bin": true,
|
||||
"/usr/bin": true,
|
||||
"/sbin": true,
|
||||
}
|
||||
|
||||
func (c *TestCommand) Execute(shell *Shell, args []string) error {
|
||||
if len(args) > 0 && args[0] == "0" {
|
||||
_, err := shell.WriteOutputString(trampSuccess)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(args) > 1 && args[0] == "-d" {
|
||||
log.Printf("Checking for existance of directory %s", args[1])
|
||||
var exists bool
|
||||
var err error
|
||||
|
||||
if answer, ok := fakeDirs[args[1]]; ok {
|
||||
exists = answer
|
||||
} else {
|
||||
exists, err = c.filestore.DirectoryExists(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if exists {
|
||||
log.Printf("Directory %s exists", args[1])
|
||||
_, err := shell.WriteOutputString(trampSuccess)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = shell.WriteOutputString(trampFailure)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(args) > 1 && (args[0] == "-e" || args[0] == "-r") {
|
||||
log.Printf("Checking for existance of file %s", args[1])
|
||||
// Tramp sometimes throws in a sentence "this file does not exist", my
|
||||
// guess is it does this to check if the test command is working
|
||||
// properly. This catches that and makes the test command fail
|
||||
// appropriately.
|
||||
if len(args) > 5 && args[2] == "this" && args[5] == "not" {
|
||||
_, err := shell.WriteOutputString(trampFailure)
|
||||
return err
|
||||
}
|
||||
|
||||
if exists, _ := c.filestore.Exists(args[1]); exists {
|
||||
log.Printf("File %s exists", args[1])
|
||||
_, err := shell.WriteOutputString(trampSuccess)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := shell.WriteOutputString(trampFailure)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,324 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type CommandHandler struct {
|
||||
shell *Shell
|
||||
filestore Filestore
|
||||
}
|
||||
|
||||
func NewCommandHandler(shell *Shell, filestore Filestore) *CommandHandler {
|
||||
return &CommandHandler{
|
||||
shell: shell,
|
||||
filestore: filestore,
|
||||
}
|
||||
}
|
||||
|
||||
func Control(c rune) rune {
|
||||
return c & 0x1f
|
||||
}
|
||||
|
||||
func sttyCharFromDesc(c string) (rune, error) {
|
||||
if len(c) > 2 || len(c) < 1 {
|
||||
return 'i', errors.New(
|
||||
"stty char description has unsupported length: " +
|
||||
strconv.Itoa(len(c)),
|
||||
)
|
||||
}
|
||||
|
||||
if rune(c[0]) == '^' {
|
||||
return Control(rune(c[1])), nil
|
||||
}
|
||||
|
||||
return rune(c[0]), nil
|
||||
}
|
||||
|
||||
const fakeUname = "Linux 5.16.19-76051619-generic"
|
||||
|
||||
type CommandExecutor interface {
|
||||
Execute(shell *Shell, args []string) error
|
||||
}
|
||||
|
||||
type SttyCommand struct{}
|
||||
|
||||
func (c *SttyCommand) Execute(shell *Shell, args []string) error {
|
||||
// Tramp uses:
|
||||
// -inlcr: don't translate newline to carriage return
|
||||
// -onlcr: don't translate newline to carriage return-newline
|
||||
// -echo: echo input characters
|
||||
// kill CHAR: CHAR will erase the current line
|
||||
// erase CHAR: CHAR will erase the last character typed
|
||||
// icanon: enable special charactersq
|
||||
|
||||
fd := int(shell.GetPty().Fd())
|
||||
const ioctlReadTermios = unix.TCGETS // Linux
|
||||
const ioctlWriteTermios = unix.TCSETS // Linux
|
||||
|
||||
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
newState := *termios
|
||||
|
||||
var skipNext bool
|
||||
for i, arg := range args {
|
||||
if skipNext {
|
||||
skipNext = false
|
||||
continue
|
||||
}
|
||||
|
||||
var disable bool
|
||||
if arg[0] == '-' {
|
||||
disable = true
|
||||
arg = arg[1:]
|
||||
}
|
||||
|
||||
switch arg {
|
||||
case "onlcr":
|
||||
if disable {
|
||||
newState.Iflag &^= unix.ONLCR
|
||||
continue
|
||||
}
|
||||
|
||||
newState.Iflag |= unix.ONLCR
|
||||
case "inlcr":
|
||||
if disable {
|
||||
newState.Iflag &^= unix.INLCR
|
||||
continue
|
||||
}
|
||||
|
||||
newState.Iflag |= unix.INLCR
|
||||
case "echo":
|
||||
if disable {
|
||||
newState.Lflag &^= unix.ECHO
|
||||
continue
|
||||
}
|
||||
|
||||
newState.Lflag |= unix.ECHO
|
||||
case "kill":
|
||||
skipNext = true
|
||||
|
||||
char, err := sttyCharFromDesc(args[i+1])
|
||||
if err != nil {
|
||||
log.Printf(
|
||||
"Warning: Not applying unsupported character description for stty kill: %s",
|
||||
args[i+1],
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
newState.Cc[unix.VKILL] = uint8(char)
|
||||
case "erase":
|
||||
skipNext = true
|
||||
|
||||
char, err := sttyCharFromDesc(args[i+1])
|
||||
if err != nil {
|
||||
log.Printf(
|
||||
"Warning: Not applying unsupported character description for stty erase: %s",
|
||||
args[i+1],
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
newState.Cc[unix.VERASE] = uint8(char)
|
||||
case "icanon":
|
||||
if disable {
|
||||
newState.Lflag &^= unix.ICANON
|
||||
continue
|
||||
}
|
||||
|
||||
newState.Lflag |= unix.ICANON
|
||||
|
||||
case "tab0":
|
||||
newState.Oflag = (newState.Oflag & ^uint32(unix.TABDLY)) | unix.TAB0
|
||||
case "iutf8":
|
||||
if disable {
|
||||
newState.Iflag &^= unix.IUTF8
|
||||
continue
|
||||
}
|
||||
|
||||
newState.Iflag |= unix.IUTF8
|
||||
|
||||
default:
|
||||
log.Printf(
|
||||
"Warning, tramp requested unexpected stty option %s",
|
||||
arg,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, &newState); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type EchoCommand struct{}
|
||||
|
||||
func (c *EchoCommand) Execute(shell *Shell, args []string) error {
|
||||
if len(args) > 1 {
|
||||
if "\"`uname -sr`\"" == args[0]+" "+args[1] {
|
||||
log.Println("Tramp requested uname, returning a fake one: " + fakeUname)
|
||||
_, err := shell.WriteOutput(
|
||||
[]byte(
|
||||
fmt.Sprintf(
|
||||
"\"%s\"\n%s\n",
|
||||
fakeUname,
|
||||
"tramp_exit_status 0",
|
||||
),
|
||||
),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
if "\"`tty`\"" == args[0] {
|
||||
log.Println("Telling tramp that it's dealing with a tty")
|
||||
_, err := shell.WriteOutputString("\"/dev/pts/0\"\n" + trampSuccess)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
if args[0] == "~root" {
|
||||
log.Println("Telling tramp root's home directory")
|
||||
_, err := shell.WriteOutputString("/root")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, err := shell.WriteOutput([]byte(strings.Join(args, " ") + "\n"))
|
||||
return err
|
||||
}
|
||||
|
||||
func AssignmentsToMap(args []string) map[string]string {
|
||||
var assignments = make(map[string]string)
|
||||
for _, arg := range args {
|
||||
split := strings.Split(arg, "=")
|
||||
if len(split) > 1 {
|
||||
assignments[split[0]] = split[1]
|
||||
}
|
||||
}
|
||||
|
||||
return assignments
|
||||
}
|
||||
|
||||
func ApplyEnvIfPresent(shell *Shell, comm *Command) {
|
||||
var args []string
|
||||
args = append(args, comm.Name)
|
||||
args = append(args, comm.Arguments...)
|
||||
|
||||
assignments := AssignmentsToMap(args)
|
||||
if ps1, ok := assignments["PS1"]; ok {
|
||||
shell.SetPrompt(ps1)
|
||||
}
|
||||
}
|
||||
|
||||
type TestCommand struct {
|
||||
filestore Filestore
|
||||
}
|
||||
|
||||
func (c *TestCommand) Execute(shell *Shell, args []string) error {
|
||||
if len(args) > 0 && args[0] == "0" {
|
||||
_, err := shell.WriteOutputString(trampSuccess)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(args) > 1 && args[0] == "-d" {
|
||||
log.Printf("Checking for existance of directory %s", args[1])
|
||||
exists, err := c.filestore.Exists(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if exists {
|
||||
_, err := shell.WriteOutputString(trampSuccess)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = shell.WriteOutputString(trampFailure)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(args) > 1 && args[0] == "-r" {
|
||||
_, err := shell.WriteOutputString(trampSuccess)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const fakePath = "/bin:/usr/bin"
|
||||
const fakePipeBuf = "4096"
|
||||
const trampSuccess = "tramp_exit_status 0\n"
|
||||
const trampFailure = "tramp_exit_status 1\n"
|
||||
|
||||
type GetConfCommand struct{}
|
||||
|
||||
func (c *GetConfCommand) Execute(shell *Shell, args []string) error {
|
||||
if len(args) > 0 {
|
||||
switch args[0] {
|
||||
case "PATH":
|
||||
shell.WriteOutputString(fakePath + "\n" + trampSuccess)
|
||||
case "PIPE_BUF":
|
||||
shell.WriteOutputString(fakePipeBuf + "\n" + trampSuccess)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *CommandHandler) Handle(comm *Command) error {
|
||||
if strings.Contains(comm.Name, "=") {
|
||||
ApplyEnvIfPresent(h.shell, comm)
|
||||
return nil
|
||||
}
|
||||
|
||||
switch comm.Name {
|
||||
case "exec":
|
||||
ApplyEnvIfPresent(h.shell, comm)
|
||||
case "(cd":
|
||||
h.shell.WriteOutput([]byte("tramp_exit_status 0\n"))
|
||||
case "echo":
|
||||
(&EchoCommand{}).Execute(h.shell, comm.Arguments)
|
||||
case "(echo":
|
||||
if strings.Join(comm.Arguments, " ") == "foo ; echo bar)" {
|
||||
log.Println("Handling tramp's foobar test")
|
||||
h.shell.WriteOutputString("foo\nbar\n")
|
||||
}
|
||||
case "stty":
|
||||
return (&SttyCommand{}).Execute(h.shell, comm.Arguments)
|
||||
case "set":
|
||||
log.Println("Ignoring \"set\" command")
|
||||
case "locale":
|
||||
if len(comm.Arguments) > 0 {
|
||||
if comm.Arguments[0] == "-a" {
|
||||
locales := []string{"C", "C.UTF-8", "POSIX", "en_US.utf8"}
|
||||
log.Println("Tramp requested locale, returning fake values: ", locales)
|
||||
h.shell.WriteOutputString(
|
||||
strings.Join(locales, "\n") + "\n",
|
||||
)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("Ignoring \"locale\" command with unsupported parameters: ", comm.Arguments)
|
||||
case "getconf":
|
||||
return (&GetConfCommand{}).Execute(h.shell, comm.Arguments)
|
||||
case "test":
|
||||
return (&TestCommand{filestore: h.filestore}).Execute(h.shell, comm.Arguments)
|
||||
default:
|
||||
log.Printf("Error: Received unexpected command %s", comm.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,289 +0,0 @@
|
||||
2022/05/05 11:04:59 Private Key generated
|
||||
2022/05/05 11:05:06 logged in with key SHA256:e+GGc8xMdIe6nXdohM3LR8s2H4moA8zffv8CP5WjB8M
|
||||
2022/05/05 11:05:06 received request of type "pty-req"
|
||||
2022/05/05 11:05:06 window resize 106x42
|
||||
2022/05/05 11:05:06 pty-req 'xterm-256color'
|
||||
2022/05/05 11:05:06 received request of type "env"
|
||||
2022/05/05 11:05:06 received request of type "env"
|
||||
2022/05/05 11:05:06 received request of type "env"
|
||||
2022/05/05 11:05:06 received request of type "env"
|
||||
2022/05/05 11:05:06 received request of type "env"
|
||||
2022/05/05 11:05:06 received request of type "env"
|
||||
2022/05/05 11:05:06 received request of type "env"
|
||||
2022/05/05 11:05:06 received request of type "env"
|
||||
2022/05/05 11:05:06 received request of type "env"
|
||||
2022/05/05 11:05:06 received request of type "env"
|
||||
2022/05/05 11:05:06 received request of type "env"
|
||||
2022/05/05 11:05:06 received request of type "env"
|
||||
2022/05/05 11:05:06 received request of type "env"
|
||||
2022/05/05 11:05:06 received request of type "shell"
|
||||
2022/05/05 11:05:09 command: "ls" []
|
||||
2022/05/05 11:05:09 Error: Received unexpected command ls
|
||||
2022/05/05 11:05:26 command: "test" [-d /var/www]
|
||||
2022/05/05 11:05:26 Checking for existance of directory /var/www
|
||||
2022/05/05 11:06:36 command: "locale" []
|
||||
2022/05/05 11:06:36 Ignoring "locale" command with unsupported parameters: []
|
||||
2022/05/05 11:06:39 command: "locale" [-a]
|
||||
2022/05/05 11:06:39 Tramp requested locale, returning fake values: [C C.UTF-8 POSIX en_US.utf8]
|
||||
2022/05/05 11:07:01 command: "exec" []
|
||||
2022/05/05 11:07:08 command: "PS1=hallo" []
|
||||
2022/05/05 11:07:08 Changing prompt to "hallo"
|
||||
2022/05/05 11:07:21 command: "exec" [PS1=heey]
|
||||
2022/05/05 11:07:21 Changing prompt to "heey"
|
||||
2022/05/05 11:07:35 command: "exec" [HAAi=DOEI PS1=heeaaaa]
|
||||
2022/05/05 11:07:35 Changing prompt to "heeaaaa"
|
||||
2022/05/05 11:08:57 command: "echo" [heeeeey]
|
||||
2022/05/05 11:09:06 Received EOF, closing TTY
|
||||
2022/05/05 11:09:06 logged in with key SHA256:e+GGc8xMdIe6nXdohM3LR8s2H4moA8zffv8CP5WjB8M
|
||||
2022/05/05 11:09:06 received request of type "pty-req"
|
||||
2022/05/05 11:09:06 window resize 106x42
|
||||
2022/05/05 11:09:06 pty-req 'xterm-256color'
|
||||
2022/05/05 11:09:06 received request of type "env"
|
||||
2022/05/05 11:09:06 received request of type "env"
|
||||
2022/05/05 11:09:06 received request of type "env"
|
||||
2022/05/05 11:09:06 received request of type "env"
|
||||
2022/05/05 11:09:06 received request of type "env"
|
||||
2022/05/05 11:09:06 received request of type "env"
|
||||
2022/05/05 11:09:06 received request of type "env"
|
||||
2022/05/05 11:09:06 received request of type "env"
|
||||
2022/05/05 11:09:06 received request of type "env"
|
||||
2022/05/05 11:09:06 received request of type "env"
|
||||
2022/05/05 11:09:06 received request of type "env"
|
||||
2022/05/05 11:09:06 received request of type "env"
|
||||
2022/05/05 11:09:06 received request of type "env"
|
||||
2022/05/05 11:09:06 received request of type "shell"
|
||||
2022/05/05 11:09:11 command: "echo" [heey]
|
||||
2022/05/05 11:09:16 command: "echo" [
|
||||
|
||||
|
||||
faiwiefjawiejfwae
|
||||
ajeifwajef
|
||||
|
||||
|
||||
]
|
||||
2022/05/05 11:09:38 Heredoc current word: "HAAAAAA"
|
||||
2022/05/05 11:09:38 Encountered heredoc marker: HAAAAAA
|
||||
2022/05/05 11:09:40 Heredoc current word: "fijwoiajfawoie"
|
||||
2022/05/05 11:09:40 Heredoc current word: "afjwaoeifj"
|
||||
2022/05/05 11:09:41 Heredoc current word: "awfjoiwjfe"
|
||||
2022/05/05 11:09:41 Heredoc current word: ""
|
||||
2022/05/05 11:09:42 Heredoc current word: "jawif;eijaw"
|
||||
2022/05/05 11:09:42 Heredoc current word: ""
|
||||
2022/05/05 11:09:49 Heredoc current word: "HAAAAAAA"
|
||||
2022/05/05 11:09:59 Heredoc current word: "HAAAAAA"
|
||||
2022/05/05 11:09:59 exiting heredoc
|
||||
2022/05/05 11:09:59 command: "while" [read -r heey; do hahahaha; done <<HAAAAAA fijwoiajfawoie afjwaoeifj awfjoiwjfe jawif;eijaw HAAAAAAA HAAAAAA]
|
||||
2022/05/05 11:09:59 Error: Received unexpected command while
|
||||
2022/05/05 11:10:24 Received EOF, closing TTY
|
||||
2022/05/05 11:11:22 logged in with key SHA256:e+GGc8xMdIe6nXdohM3LR8s2H4moA8zffv8CP5WjB8M
|
||||
2022/05/05 11:11:22 received request of type "pty-req"
|
||||
2022/05/05 11:11:22 window resize 106x42
|
||||
2022/05/05 11:11:22 pty-req 'xterm-256color'
|
||||
2022/05/05 11:11:22 received request of type "env"
|
||||
2022/05/05 11:11:22 received request of type "env"
|
||||
2022/05/05 11:11:22 received request of type "env"
|
||||
2022/05/05 11:11:22 received request of type "env"
|
||||
2022/05/05 11:11:22 received request of type "env"
|
||||
2022/05/05 11:11:22 received request of type "env"
|
||||
2022/05/05 11:11:22 received request of type "env"
|
||||
2022/05/05 11:11:22 received request of type "env"
|
||||
2022/05/05 11:11:22 received request of type "env"
|
||||
2022/05/05 11:11:22 received request of type "env"
|
||||
2022/05/05 11:11:22 received request of type "env"
|
||||
2022/05/05 11:11:22 received request of type "env"
|
||||
2022/05/05 11:11:22 received request of type "env"
|
||||
2022/05/05 11:11:22 received request of type "shell"
|
||||
2022/05/05 11:11:25 command: "stty" [-echo]
|
||||
2022/05/05 11:11:29 command: "fjieawif;waiejf;iajwf;awijef;aiwjf;awijef;oaiwejf;awiej;ijw" []
|
||||
2022/05/05 11:11:29 Error: Received unexpected command fjieawif;waiejf;iajwf;awijef;aiwjf;awijef;oaiwejf;awiej;ijw
|
||||
2022/05/05 11:11:34 Received EOF, closing TTY
|
||||
2022/05/05 12:18:04 logged in with key SHA256:e+GGc8xMdIe6nXdohM3LR8s2H4moA8zffv8CP5WjB8M
|
||||
2022/05/05 12:18:04 received request of type "pty-req"
|
||||
2022/05/05 12:18:04 window resize 0x0
|
||||
2022/05/05 12:18:04 pty-req 'dumb'
|
||||
2022/05/05 12:18:04 received request of type "env"
|
||||
2022/05/05 12:18:04 received request of type "env"
|
||||
2022/05/05 12:18:04 received request of type "env"
|
||||
2022/05/05 12:18:04 received request of type "env"
|
||||
2022/05/05 12:18:04 received request of type "env"
|
||||
2022/05/05 12:18:04 received request of type "env"
|
||||
2022/05/05 12:18:04 received request of type "env"
|
||||
2022/05/05 12:18:04 received request of type "env"
|
||||
2022/05/05 12:18:04 received request of type "env"
|
||||
2022/05/05 12:18:04 received request of type "env"
|
||||
2022/05/05 12:18:04 received request of type "env"
|
||||
2022/05/05 12:18:04 received request of type "env"
|
||||
2022/05/05 12:18:04 received request of type "env"
|
||||
2022/05/05 12:18:04 received request of type "env"
|
||||
2022/05/05 12:18:04 received request of type "shell"
|
||||
2022/05/05 12:18:04 command: "exec" [env TERM=dumb INSIDE_EMACS=27.2,tramp:2.4.5.27.2 ENV= HISTFILE=~/.tramp_history PROMPT_COMMAND= PS1=#$ PS2= PS3= /bin/sh]
|
||||
2022/05/05 12:18:04 Changing prompt to "#$ "
|
||||
2022/05/05 12:18:04 command: "(cd" [~/) 2>/dev/null; echo tramp_exit_status $?]
|
||||
2022/05/05 12:18:04 command: "set" [+o vi +o emacs]
|
||||
2022/05/05 12:18:04 Ignoring "set" command
|
||||
2022/05/05 12:18:04 command: "stty" [-inlcr -onlcr -echo kill ^U erase ^H]
|
||||
2022/05/05 12:18:04 command: "echo" [foo]
|
||||
2022/05/05 12:18:04 command: "PS1=///eac57010bddba5dae79f08f3cd43067c#$" [PS2= PS3= PROMPT_COMMAND=]
|
||||
2022/05/05 12:18:04 Changing prompt to "///eac57010bddba5dae79f08f3cd43067c#$"
|
||||
2022/05/05 12:18:04 command: "echo" ["`uname -sr`" 2>/dev/null; echo tramp_exit_status $?]
|
||||
2022/05/05 12:18:04 Tramp requested uname, returning a fake one: Linux 5.16.19-76051619-generic
|
||||
2022/05/05 12:18:04 command: "(echo" [foo ; echo bar)]
|
||||
2022/05/05 12:18:04 Handling tramp's foobar test
|
||||
2022/05/05 12:18:04 command: "PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/local/bin:/local/freeware/bin:/local/gnu/bin:/usr/freeware/bin:/usr/pkg/bin:/usr/contrib/bin:/opt/bin:/opt/sbin:/opt/local/bin" [&& export PATH]
|
||||
2022/05/05 12:18:04 command: "mesg" [n 2>/dev/null; biff n 2>/dev/null]
|
||||
2022/05/05 12:18:04 Error: Received unexpected command mesg
|
||||
2022/05/05 12:18:04 command: "stty" [tab0]
|
||||
2022/05/05 12:18:04 command: "stty" [iutf8 2>/dev/null]
|
||||
2022/05/05 12:18:04 Warning, tramp requested unexpected stty option 2>/dev/null
|
||||
2022/05/05 12:18:04 command: "echo" ["`tty`" 2>/dev/null; echo tramp_exit_status $?]
|
||||
2022/05/05 12:18:04 Telling tramp that it's dealing with a tty
|
||||
2022/05/05 12:18:04 Heredoc current word: "'cf8110e8e028188d140944ad469ceb8f'"
|
||||
2022/05/05 12:18:04 Encountered heredoc marker: 'cf8110e8e028188d140944ad469ceb8f'
|
||||
2022/05/05 12:18:04 Heredoc current word: "LC_ALL en_US.utf8"
|
||||
2022/05/05 12:18:04 Heredoc current word: "ENV ''"
|
||||
2022/05/05 12:18:04 Heredoc current word: "TMOUT 0"
|
||||
2022/05/05 12:18:04 Heredoc current word: "LC_CTYPE ''"
|
||||
2022/05/05 12:18:04 Heredoc current word: "PAGER cat"
|
||||
2022/05/05 12:18:04 Heredoc current word: "cf8110e8e028188d140944ad469ceb8f"
|
||||
2022/05/05 12:18:04 exiting heredoc
|
||||
2022/05/05 12:18:04 command: "while" [read var val; do export $var=$val; done <<cf8110e8e028188d140944ad469ceb8f LC_ALL en_US.utf8 ENV TMOUT 0 LC_CTYPE PAGER cat cf8110e8e028188d140944ad469ceb8f]
|
||||
2022/05/05 12:18:04 Error: Received unexpected command while
|
||||
2022/05/05 12:18:04 command: "unset" [CDPATH HISTORY MAIL MAILCHECK MAILPATH autocorrect correct]
|
||||
2022/05/05 12:18:04 Error: Received unexpected command unset
|
||||
2022/05/05 12:18:04 command: "test" [-e / 2>/dev/null; echo tramp_exit_status $?]
|
||||
2022/05/05 12:18:04 command: "/bin/test" [-e / 2>/dev/null; echo tramp_exit_status $?]
|
||||
2022/05/05 12:18:04 Error: Received unexpected command /bin/test
|
||||
2022/05/05 12:18:04 command: "/usr/bin/test" [-e / 2>/dev/null; echo tramp_exit_status $?]
|
||||
2022/05/05 12:18:04 Error: Received unexpected command /usr/bin/test
|
||||
2022/05/05 12:18:04 Heredoc current word: "'cf8110e8e028188d140944ad469ceb8f'"
|
||||
2022/05/05 12:18:04 Encountered heredoc marker: 'cf8110e8e028188d140944ad469ceb8f'
|
||||
2022/05/05 12:18:04 Heredoc current word: "/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/sbin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/sbin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/local/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/local/sbin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/local/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/local/freeware/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/local/gnu/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/freeware/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/pkg/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/contrib/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/opt/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/opt/sbin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/opt/local/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "cf8110e8e028188d140944ad469ceb8f"
|
||||
2022/05/05 12:18:04 exiting heredoc
|
||||
2022/05/05 12:18:04 command: "while" [read d; do if test -x $d/ls && test -f $d/ls; then echo tramp_executable $d/ls; break; fi; done <<cf8110e8e028188d140944ad469ceb8f /bin /usr/bin /sbin /usr/sbin /usr/local/bin /usr/local/sbin /local/bin /local/freeware/bin /local/gnu/bin /usr/freeware/bin /usr/pkg/bin /usr/contrib/bin /opt/bin /opt/sbin /opt/local/bin cf8110e8e028188d140944ad469ceb8f]
|
||||
2022/05/05 12:18:04 Error: Received unexpected command while
|
||||
2022/05/05 12:18:04 Heredoc current word: "'cf8110e8e028188d140944ad469ceb8f'"
|
||||
2022/05/05 12:18:04 Encountered heredoc marker: 'cf8110e8e028188d140944ad469ceb8f'
|
||||
2022/05/05 12:18:04 Heredoc current word: "/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/sbin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/sbin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/local/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/local/sbin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/local/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/local/freeware/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/local/gnu/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/freeware/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/pkg/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/contrib/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/opt/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/opt/sbin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/opt/local/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "cf8110e8e028188d140944ad469ceb8f"
|
||||
2022/05/05 12:18:04 exiting heredoc
|
||||
2022/05/05 12:18:04 command: "while" [read d; do if test -x $d/gnuls && test -f $d/gnuls; then echo tramp_executable $d/gnuls; break; fi; done <<cf8110e8e028188d140944ad469ceb8f /bin /usr/bin /sbin /usr/sbin /usr/local/bin /usr/local/sbin /local/bin /local/freeware/bin /local/gnu/bin /usr/freeware/bin /usr/pkg/bin /usr/contrib/bin /opt/bin /opt/sbin /opt/local/bin cf8110e8e028188d140944ad469ceb8f]
|
||||
2022/05/05 12:18:04 Error: Received unexpected command while
|
||||
2022/05/05 12:18:04 Heredoc current word: "'cf8110e8e028188d140944ad469ceb8f'"
|
||||
2022/05/05 12:18:04 Encountered heredoc marker: 'cf8110e8e028188d140944ad469ceb8f'
|
||||
2022/05/05 12:18:04 Heredoc current word: "/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/sbin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/sbin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/local/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/local/sbin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/local/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/local/freeware/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/local/gnu/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/freeware/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/pkg/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/contrib/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/opt/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/opt/sbin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/opt/local/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "cf8110e8e028188d140944ad469ceb8f"
|
||||
2022/05/05 12:18:04 exiting heredoc
|
||||
2022/05/05 12:18:04 command: "while" [read d; do if test -x $d/gls && test -f $d/gls; then echo tramp_executable $d/gls; break; fi; done <<cf8110e8e028188d140944ad469ceb8f /bin /usr/bin /sbin /usr/sbin /usr/local/bin /usr/local/sbin /local/bin /local/freeware/bin /local/gnu/bin /usr/freeware/bin /usr/pkg/bin /usr/contrib/bin /opt/bin /opt/sbin /opt/local/bin cf8110e8e028188d140944ad469ceb8f]
|
||||
2022/05/05 12:18:04 Error: Received unexpected command while
|
||||
2022/05/05 12:18:04 command: "test" [-e / 2>/dev/null; echo tramp_exit_status $?]
|
||||
2022/05/05 12:18:04 command: "/bin/test" [-e / 2>/dev/null; echo tramp_exit_status $?]
|
||||
2022/05/05 12:18:04 Error: Received unexpected command /bin/test
|
||||
2022/05/05 12:18:04 command: "/usr/bin/test" [-e / 2>/dev/null; echo tramp_exit_status $?]
|
||||
2022/05/05 12:18:04 Error: Received unexpected command /usr/bin/test
|
||||
2022/05/05 12:18:04 Heredoc current word: "'cf8110e8e028188d140944ad469ceb8f'"
|
||||
2022/05/05 12:18:04 Encountered heredoc marker: 'cf8110e8e028188d140944ad469ceb8f'
|
||||
2022/05/05 12:18:04 Heredoc current word: "/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/sbin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/sbin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/local/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/local/sbin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/local/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/local/freeware/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/local/gnu/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/freeware/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/pkg/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/contrib/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/opt/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/opt/sbin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/opt/local/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "cf8110e8e028188d140944ad469ceb8f"
|
||||
2022/05/05 12:18:04 exiting heredoc
|
||||
2022/05/05 12:18:04 command: "while" [read d; do if test -x $d/ls && test -f $d/ls; then echo tramp_executable $d/ls; break; fi; done <<cf8110e8e028188d140944ad469ceb8f /bin /usr/bin /sbin /usr/sbin /usr/local/bin /usr/local/sbin /local/bin /local/freeware/bin /local/gnu/bin /usr/freeware/bin /usr/pkg/bin /usr/contrib/bin /opt/bin /opt/sbin /opt/local/bin cf8110e8e028188d140944ad469ceb8f]
|
||||
2022/05/05 12:18:04 Error: Received unexpected command while
|
||||
2022/05/05 12:18:04 Heredoc current word: "'cf8110e8e028188d140944ad469ceb8f'"
|
||||
2022/05/05 12:18:04 Encountered heredoc marker: 'cf8110e8e028188d140944ad469ceb8f'
|
||||
2022/05/05 12:18:04 Heredoc current word: "/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/sbin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/sbin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/local/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/local/sbin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/local/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/local/freeware/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/local/gnu/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/freeware/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/pkg/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/contrib/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/opt/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/opt/sbin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/opt/local/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "cf8110e8e028188d140944ad469ceb8f"
|
||||
2022/05/05 12:18:04 exiting heredoc
|
||||
2022/05/05 12:18:04 command: "while" [read d; do if test -x $d/gnuls && test -f $d/gnuls; then echo tramp_executable $d/gnuls; break; fi; done <<cf8110e8e028188d140944ad469ceb8f /bin /usr/bin /sbin /usr/sbin /usr/local/bin /usr/local/sbin /local/bin /local/freeware/bin /local/gnu/bin /usr/freeware/bin /usr/pkg/bin /usr/contrib/bin /opt/bin /opt/sbin /opt/local/bin cf8110e8e028188d140944ad469ceb8f]
|
||||
2022/05/05 12:18:04 Error: Received unexpected command while
|
||||
2022/05/05 12:18:04 Heredoc current word: "'cf8110e8e028188d140944ad469ceb8f'"
|
||||
2022/05/05 12:18:04 Encountered heredoc marker: 'cf8110e8e028188d140944ad469ceb8f'
|
||||
2022/05/05 12:18:04 Heredoc current word: "/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/sbin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/sbin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/local/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/local/sbin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/local/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/local/freeware/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/local/gnu/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/freeware/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/pkg/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/usr/contrib/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/opt/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/opt/sbin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "/opt/local/bin"
|
||||
2022/05/05 12:18:04 Heredoc current word: "cf8110e8e028188d140944ad469ceb8f"
|
||||
2022/05/05 12:18:04 exiting heredoc
|
||||
2022/05/05 12:18:04 command: "while" [read d; do if test -x $d/gls && test -f $d/gls; then echo tramp_executable $d/gls; break; fi; done <<cf8110e8e028188d140944ad469ceb8f /bin /usr/bin /sbin /usr/sbin /usr/local/bin /usr/local/sbin /local/bin /local/freeware/bin /local/gnu/bin /usr/freeware/bin /usr/pkg/bin /usr/contrib/bin /opt/bin /opt/sbin /opt/local/bin cf8110e8e028188d140944ad469ceb8f]
|
||||
2022/05/05 12:18:04 Error: Received unexpected command while
|
||||
2022/05/05 12:18:04 command: "test" [-r /hallo 2>/dev/null; echo tramp_exit_status $?]
|
@ -0,0 +1,18 @@
|
||||
package storage
|
||||
|
||||
import "io"
|
||||
|
||||
type File struct {
|
||||
Name string `json:"name"`
|
||||
AbsolutePath string `json:"absolute_path"`
|
||||
Contents io.Reader `json:"-"`
|
||||
}
|
||||
|
||||
type Filestore interface {
|
||||
Get(name string) (*File, error)
|
||||
Set(file *File) error
|
||||
Delete(file *File) error
|
||||
Exists(name string) (bool, error)
|
||||
MakeDirectory(name string) error
|
||||
DirectoryExists(name string) (bool, error)
|
||||
}
|
Loading…
Reference in New Issue