From 18fb0008ec4aa4d0997dc1da96e3648a122106cd Mon Sep 17 00:00:00 2001 From: davy wybiral Date: Mon, 9 Jul 2018 18:32:48 -0500 Subject: [PATCH] Full duplex (#30) * duplex rewrite * fix CLI usage bug * fix race * redesign api * update README.md --- Gopkg.lock | 10 +- README.md | 30 +- cmd/hookah/main.go | 129 ++++-- examples/certstream/main.go | 22 +- examples/customproto/main.go | 49 +- hookah.go | 172 ++------ internal/app/app.go | 130 ++++++ internal/app/config.go | 9 + internal/app/update.go | 11 + internal/protocols/exec.go | 39 ++ internal/protocols/file.go | 59 +++ internal/protocols/listen.go | 102 +++++ internal/protocols/protocols.go | 4 + internal/protocols/serial.go | 41 ++ internal/protocols/stdio.go | 27 ++ internal/protocols/tcp.go | 16 + internal/protocols/tcplisten.go | 10 + internal/protocols/unix.go | 16 + internal/protocols/unixlisten.go | 10 + internal/protocols/ws.go | 73 +++ .../output => internal/protocols}/wslisten.go | 55 ++- pkg/chreader/chreader.go | 40 -- pkg/flagslice/flagslice.go | 17 + pkg/input/file.go | 12 - pkg/input/http.go | 29 -- pkg/input/httplisten.go | 55 --- pkg/input/input.go | 13 - pkg/input/listen.go | 63 --- pkg/input/serial.go | 26 -- pkg/input/stdin.go | 12 - pkg/input/tcp.go | 12 - pkg/input/tcplisten.go | 11 - pkg/input/udplisten.go | 16 - pkg/input/udpmulticast.go | 26 -- pkg/input/unix.go | 12 - pkg/input/unixlisten.go | 11 - pkg/input/ws.go | 76 ---- pkg/input/wslisten.go | 67 --- pkg/node/node.go | 17 + pkg/output/file.go | 13 - pkg/output/http.go | 56 --- pkg/output/httplisten.go | 53 --- pkg/output/listen.go | 59 --- pkg/output/output.go | 13 - pkg/output/serial.go | 26 -- pkg/output/stdout.go | 17 - pkg/output/tcp.go | 12 - pkg/output/tcplisten.go | 11 - pkg/output/udp.go | 12 - pkg/output/unix.go | 12 - pkg/output/unixlisten.go | 11 - pkg/output/ws.go | 66 --- vendor/github.com/google/shlex/COPYING | 202 +++++++++ vendor/github.com/google/shlex/README | 2 + vendor/github.com/google/shlex/shlex.go | 417 ++++++++++++++++++ vendor/golang.org/x/sys/unix/syscall_bsd.go | 8 +- .../x/sys/unix/syscall_dragonfly.go | 2 +- .../golang.org/x/sys/unix/syscall_freebsd.go | 2 +- vendor/golang.org/x/sys/unix/syscall_linux.go | 75 +++- .../golang.org/x/sys/unix/syscall_solaris.go | 8 +- vendor/golang.org/x/sys/unix/syscall_unix.go | 4 +- .../x/sys/unix/zsyscall_linux_ppc64.go | 24 +- .../x/sys/unix/zsyscall_linux_ppc64le.go | 24 +- .../golang.org/x/sys/unix/ztypes_linux_386.go | 8 + .../x/sys/unix/ztypes_linux_amd64.go | 8 + .../golang.org/x/sys/unix/ztypes_linux_arm.go | 8 + .../x/sys/unix/ztypes_linux_arm64.go | 8 + .../x/sys/unix/ztypes_linux_mips.go | 8 + .../x/sys/unix/ztypes_linux_mips64.go | 8 + .../x/sys/unix/ztypes_linux_mips64le.go | 8 + .../x/sys/unix/ztypes_linux_mipsle.go | 8 + .../x/sys/unix/ztypes_linux_ppc64.go | 8 + .../x/sys/unix/ztypes_linux_ppc64le.go | 8 + .../x/sys/unix/ztypes_linux_s390x.go | 8 + 74 files changed, 1636 insertions(+), 1110 deletions(-) create mode 100644 internal/app/app.go create mode 100644 internal/app/config.go create mode 100644 internal/app/update.go create mode 100644 internal/protocols/exec.go create mode 100644 internal/protocols/file.go create mode 100644 internal/protocols/listen.go create mode 100644 internal/protocols/protocols.go create mode 100644 internal/protocols/serial.go create mode 100644 internal/protocols/stdio.go create mode 100644 internal/protocols/tcp.go create mode 100644 internal/protocols/tcplisten.go create mode 100644 internal/protocols/unix.go create mode 100644 internal/protocols/unixlisten.go create mode 100644 internal/protocols/ws.go rename {pkg/output => internal/protocols}/wslisten.go (53%) delete mode 100644 pkg/chreader/chreader.go create mode 100644 pkg/flagslice/flagslice.go delete mode 100644 pkg/input/file.go delete mode 100644 pkg/input/http.go delete mode 100644 pkg/input/httplisten.go delete mode 100644 pkg/input/input.go delete mode 100644 pkg/input/listen.go delete mode 100644 pkg/input/serial.go delete mode 100644 pkg/input/stdin.go delete mode 100644 pkg/input/tcp.go delete mode 100644 pkg/input/tcplisten.go delete mode 100644 pkg/input/udplisten.go delete mode 100644 pkg/input/udpmulticast.go delete mode 100644 pkg/input/unix.go delete mode 100644 pkg/input/unixlisten.go delete mode 100644 pkg/input/ws.go delete mode 100644 pkg/input/wslisten.go create mode 100644 pkg/node/node.go delete mode 100644 pkg/output/file.go delete mode 100644 pkg/output/http.go delete mode 100644 pkg/output/httplisten.go delete mode 100644 pkg/output/listen.go delete mode 100644 pkg/output/output.go delete mode 100644 pkg/output/serial.go delete mode 100644 pkg/output/stdout.go delete mode 100644 pkg/output/tcp.go delete mode 100644 pkg/output/tcplisten.go delete mode 100644 pkg/output/udp.go delete mode 100644 pkg/output/unix.go delete mode 100644 pkg/output/unixlisten.go delete mode 100644 pkg/output/ws.go create mode 100644 vendor/github.com/google/shlex/COPYING create mode 100644 vendor/github.com/google/shlex/README create mode 100644 vendor/github.com/google/shlex/shlex.go diff --git a/Gopkg.lock b/Gopkg.lock index 17d7abb..c19a8b0 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1,6 +1,12 @@ # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. +[[projects]] + branch = "master" + name = "github.com/google/shlex" + packages = ["."] + revision = "6f45313302b9c56850fc17f99e40caebce98c716" + [[projects]] name = "github.com/gorilla/websocket" packages = ["."] @@ -17,11 +23,11 @@ branch = "master" name = "golang.org/x/sys" packages = ["unix"] - revision = "151529c776cdc58ddbe7963ba9af779f3577b419" + revision = "1b2967e3c290b7c545b3db0deeda16e9be4f98a2" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "739d61bcca4d6b2fd034651407206afb25510c29c9e6b0471e18b7058d4e0e26" + inputs-digest = "235336fd703a055cea7253348fc9808efcd6efc68c81dbe8c9946e58906d6f7a" solver-name = "gps-cdcl" solver-version = 1 diff --git a/README.md b/README.md index b7bbe26..e2e85b1 100644 --- a/README.md +++ b/README.md @@ -14,29 +14,39 @@ go build github.com/wybiral/hookah/cmd/hookah ``` # Usage instructions (CLI) -The hookah command allows you to specify an input source -i and an output destination -o. -Any data that's fed into the input will be piped to the output. +The hookah command allows you to pipe data betweem various sources/destinations. +By default pipes are full duplex but can be limited to input/output-only mode. + +For details run `hookah -h` ## Examples -Pipe from stdin to a new TCP listener on port 8080: +Pipe from stdin/stdout to a new TCP listener on port 8080: +``` +hookah stdio tcp-listen://localhost:8080 +``` +Note: this is the same even if you ommit the `stdio` part because hookah will +assume stdio is indended when only one node (tcp-listen in this case) is used. + +Pipe from an existing TCP listener on port 8080 to a new WebSocket listener on +port 8081: ``` -hookah -o tcp-listen://localhost:8080 +hookah tcp-listen://localhost:8080 ws-listen://localhost:8081 ``` -Pipe from an existing TCP listener on port 8080 to a new HTTP listener on port 8081: +Pipe from a new Unix domain socket listener to a TCP client on port 8080: ``` -hookah -i tcp://localhost:8080 -o http-listen://localhost:8081 +hookah unix-listen://path/to/sock tcp://localhost:8080 ``` -Pipe from a new Unix domain socket listener to stdout: +Pipe only the input from a TCP client to the output of another TCP client: ``` -hookah -i unix-listen://path/to/sock +hookah -i tcp://:8080 -o tcp://:8081 ``` -Pipe from a new HTTP listener on port 8080 to an existing Unix domain socket: +Fan-out the input from a TCP listener to the output of multiple TCP clients: ``` -hookah -i http-listen://localhost:8080 -o unix://path/to/sock +hookah -i tcp-listen://:8080 -o tcp://:8081 -o tcp://:8082 -o tcp://:8083 ``` # Usage instructions (Go package) diff --git a/cmd/hookah/main.go b/cmd/hookah/main.go index acf9da5..f6cb913 100644 --- a/cmd/hookah/main.go +++ b/cmd/hookah/main.go @@ -2,65 +2,116 @@ package main import ( + "errors" "flag" "fmt" - "io" - "log" "os" "os/signal" "github.com/wybiral/hookah" + "github.com/wybiral/hookah/internal/app" + "github.com/wybiral/hookah/pkg/flagslice" ) func main() { // Create hookah API instance h := hookah.New() flag.Usage = func() { - fmt.Print("NAME:\n") - fmt.Print(" hookah\n\n") - fmt.Print("USAGE:\n") - fmt.Print(" hookah -i input -o output\n\n") - fmt.Print("VERSION:\n") - fmt.Printf(" %s\n\n", hookah.Version) - fmt.Print("INPUTS:\n") - for _, reg := range h.ListInputs() { - fmt.Printf(" %s\n", reg.Usage) - } - fmt.Print("\n") - fmt.Print("OUTPUTS:\n") - for _, reg := range h.ListOutputs() { - fmt.Printf(" %s\n", reg.Usage) - } - fmt.Print("\n") + usage(h) os.Exit(0) } // Parse flags - var inOpts string - flag.StringVar(&inOpts, "i", "stdin", "Stream input") - var outOpts string - flag.StringVar(&outOpts, "o", "stdout", "Stream output") + var opts, rOpts, wOpts flagslice.FlagSlice + flag.Var(&rOpts, "i", "input node (readonly)") + flag.Var(&wOpts, "o", "output node (writeonly)") flag.Parse() - // Setup input stream - r, err := h.NewInput(inOpts) + opts = flag.Args() + // Run and report errors + err := run(h, opts, rOpts, wOpts) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + os.Exit(0) +} + +func run(h *hookah.API, opts, rOpts, wOpts []string) error { + a := app.New(nil) + // Closing this App instance will close all the nodes being created + defer a.Close() + // Add bidirectional nodes + err := addNodes(h, a, opts, true, true) + if err != nil { + return err + } + // Add reader (input) nodes + err = addNodes(h, a, rOpts, true, false) if err != nil { - log.Fatal(err) + return err } - defer r.Close() - // Setup output stream - w, err := h.NewOutput(outOpts) + // Add writer (output) nodes + err = addNodes(h, a, wOpts, false, true) if err != nil { - log.Fatal(err) + return err + } + // No nodes, show usage + if len(a.Nodes) == 0 { + flag.Usage() + return nil + } + // Only one node, link to stdio + if len(a.Nodes) == 1 { + n, err := h.NewNode("stdio") + if err != nil { + return err + } + a.AddNode(n) } - defer w.Close() - // Listen for interrupt to close gracefully - ch := make(chan os.Signal, 1) - signal.Notify(ch, os.Interrupt) + if len(a.Readers) == 0 { + return errors.New("no input nodes") + } + if len(a.Writers) == 0 { + return errors.New("no output nodes") + } + // Handle CTRL+C by sending a Quit signal + interrupt := make(chan os.Signal, 1) + signal.Notify(interrupt, os.Interrupt) go func() { - <-ch - r.Close() - w.Close() - os.Exit(1) + <-interrupt + a.Quit <- nil }() - // Copy all of in to out - io.Copy(w, r) + return a.Run() +} + +// Add nodes to the app from opt strings and r/w status +func addNodes(h *hookah.API, a *app.App, opts []string, r, w bool) error { + for _, opt := range opts { + n, err := h.NewNode(opt) + if err != nil { + return err + } + if !r { + n.R = nil + } + if !w { + n.W = nil + } + a.AddNode(n) + } + return nil +} + +// Print CLI usage info +func usage(h *hookah.API) { + fmt.Print("NAME:\n") + fmt.Print(" hookah\n\n") + fmt.Print("USAGE:\n") + fmt.Print(" hookah node [node] -i in_node -o out_node\n\n") + fmt.Print("VERSION:\n") + fmt.Printf(" %s\n\n", hookah.Version) + fmt.Print("PROTOCOLS:\n") + for _, p := range h.ListProtocols() { + fmt.Printf(" %s\n", p.Usage) + } + fmt.Print("\n") } diff --git a/examples/certstream/main.go b/examples/certstream/main.go index a38956a..ba790f5 100644 --- a/examples/certstream/main.go +++ b/examples/certstream/main.go @@ -1,16 +1,16 @@ -// Example of using hookah to create an input stream from the CertStream +// Example of using hookah to create an input node from the CertStream // WebSocket API (https://certstream.calidog.io/). // The cert updates are filtered to remove heartbeat messages and processed by // restricting the JSON fields and adding indentation. -// These updates are then written to stdout. +// These updates are then written to stdout node. package main import ( "encoding/json" - "io" "log" "github.com/wybiral/hookah" + "github.com/wybiral/hookah/pkg/node" ) // CertStream JSON struct @@ -32,28 +32,26 @@ type certUpdate struct { func main() { // Create hookah API instance h := hookah.New() - // Create hookah input (certstream WebSocket API) - r, err := h.NewInput("wss://certstream.calidog.io") + // Create hookah node (certstream WebSocket API) + r, err := h.NewNode("wss://certstream.calidog.io") if err != nil { log.Fatal(err) } - defer r.Close() - // Create hookah output (stdout) - w, err := h.NewOutput("stdout") + // Create hookah node (stdout) + w, err := h.NewNode("stdout") if err != nil { log.Fatal(err) } - defer w.Close() // Start stream stream(w, r) } // Copy from reader to writer // Drops heartbeat messages, restricts fields, and formats JSON -func stream(w io.Writer, r io.Reader) { +func stream(w, r *node.Node) { var u certUpdate - d := json.NewDecoder(r) - e := json.NewEncoder(w) + d := json.NewDecoder(r.R) + e := json.NewEncoder(w.W) e.SetIndent("", " ") for { err := d.Decode(&u) diff --git a/examples/customproto/main.go b/examples/customproto/main.go index 2009196..28faf6b 100644 --- a/examples/customproto/main.go +++ b/examples/customproto/main.go @@ -1,6 +1,5 @@ -// Example of using hookah with a custom input protocol. In this case the input -// protocol is named numbers:// and it can accept "odd" or "even" as the -// argument. +// Example of using hookah with a custom protocol. In this case the protocol is +// named numbers:// and it can accept "odd" or "even" as the argument. package main import ( @@ -8,42 +7,37 @@ import ( "fmt" "io" "log" - "net/url" "time" "github.com/wybiral/hookah" + "github.com/wybiral/hookah/pkg/node" ) func main() { // Create hookah API instance h := hookah.New() // Register new protocol - h.RegisterInput("numbers", "numbers://parity", numbersHandler) - // Create hookah input (using new numbers:// protocol) - r, err := h.NewInput("numbers://odd") + h.RegisterProtocol("numbers", "numbers://parity", numbersHandler) + // Create hookah node (using our new numbers:// protocol) + r, err := h.NewNode("numbers://odd") if err != nil { log.Fatal(err) } - defer r.Close() - // Create hookah output (stdout) - w, err := h.NewOutput("stdout") + // Create hookah node (stdout) + w, err := h.NewNode("stdout") if err != nil { log.Fatal(err) } - defer w.Close() // Copy forever - io.Copy(w, r) + io.Copy(w.W, r.R) } -// struct type to implement interface on. -type numbers struct { - counter int64 -} +// type to implement Reader interface on. +type numbers int64 -// Input handlers take an arg string and return an io.ReadCloser for the input -// stream (or an error). -func numbersHandler(arg string, opts url.Values) (io.ReadCloser, error) { - var counter int64 +// Handlers take an arg string and return a Node +func numbersHandler(arg string) (*node.Node, error) { + var counter numbers if arg == "odd" { counter = 1 } else if arg == "even" { @@ -51,23 +45,20 @@ func numbersHandler(arg string, opts url.Values) (io.ReadCloser, error) { } else { return nil, errors.New("numbers requires: odd or even") } - return &numbers{counter: counter}, nil + // Node can have R: Reader, W: Writer, C: Closer + // In this case it's just a Reader + return &node.Node{R: &counter}, nil } -// Read method satisfies the io.ReadCloser interface +// Read the next number (after delay) and increment counter. func (num *numbers) Read(b []byte) (int, error) { // Artificial delay time.Sleep(time.Second) // Format counter - s := fmt.Sprintf("%d\n", num.counter) + s := fmt.Sprintf("%d\n", *num) // Increment counter - num.counter += 2 + *num += 2 // Copy to byte array n := copy(b, []byte(s)) return n, nil } - -// Close method satisfies the io.ReadCloser interface -func (num *numbers) Close() error { - return nil -} diff --git a/hookah.go b/hookah.go index 52f3494..33f1661 100644 --- a/hookah.go +++ b/hookah.go @@ -3,36 +3,29 @@ package hookah import ( "errors" - "io" - "net/url" "sort" "strings" "sync" - "github.com/wybiral/hookah/pkg/input" - "github.com/wybiral/hookah/pkg/output" + "github.com/wybiral/hookah/internal/protocols" + "github.com/wybiral/hookah/pkg/node" ) -// Version of hookah API -const Version = "1.0.5" +// Version of hookah API. +const Version = "2.0.0" // API is an instance of the Hookah API. type API struct { - mu sync.RWMutex - inputHandlers map[string]RegisteredInput - outputHandlers map[string]RegisteredOutput + mu sync.RWMutex + protocols map[string]Protocol } -// RegisteredInput represents a registered input handler. -type RegisteredInput struct { - Handler input.Handler - Proto string - Usage string -} +// Handler is a function that returns a new Node. +type Handler func(arg string) (*node.Node, error) -// RegisteredOutput represents a registered output handler. -type RegisteredOutput struct { - Handler output.Handler +// Protocol represents a registered protocol handler. +type Protocol struct { + Handler Handler Proto string Usage string } @@ -40,65 +33,31 @@ type RegisteredOutput struct { // New returns a Hookah API instance with default handlers. func New() *API { api := &API{ - inputHandlers: make(map[string]RegisteredInput), - outputHandlers: make(map[string]RegisteredOutput), + protocols: make(map[string]Protocol), } - api.registerInputs() - api.registerOutputs() + api.registerProtocols() return api } -// NewInput parses an input option string and returns a new ReadCloser. -func (a *API) NewInput(op string) (io.ReadCloser, error) { - a.mu.RLock() - defer a.mu.RUnlock() - proto, arg, opts, err := parseOptions(op) - if err != nil { - return nil, err - } - reg, ok := a.inputHandlers[proto] - if !ok { - return nil, errors.New("unknown input protocol: " + proto) - } - return reg.Handler(arg, opts) -} - -// NewOutput parses an output option string and returns a new WriteCloser. -func (a *API) NewOutput(op string) (io.WriteCloser, error) { +// NewNode parses an option string and returns a new Node. +func (a *API) NewNode(op string) (*node.Node, error) { a.mu.RLock() defer a.mu.RUnlock() - proto, arg, opts, err := parseOptions(op) - if err != nil { - return nil, err - } - reg, ok := a.outputHandlers[proto] + proto, arg := parseOptions(op) + p, ok := a.protocols[proto] if !ok { - return nil, errors.New("unknown output protocol: " + proto) - } - return reg.Handler(arg, opts) -} - -// ListInputs returns all registered input handlers. -func (a *API) ListInputs() []RegisteredInput { - a.mu.RLock() - defer a.mu.RUnlock() - out := make([]RegisteredInput, 0, len(a.inputHandlers)) - for _, reg := range a.inputHandlers { - out = append(out, reg) + return nil, errors.New("unknown protocol: " + proto) } - sort.Slice(out, func(i, j int) bool { - return out[i].Proto < out[j].Proto - }) - return out + return p.Handler(arg) } -// ListOutputs returns all registered output handlers. -func (a *API) ListOutputs() []RegisteredOutput { +// ListProtocols returns all registered protocols. +func (a *API) ListProtocols() []Protocol { a.mu.RLock() defer a.mu.RUnlock() - out := make([]RegisteredOutput, 0, len(a.outputHandlers)) - for _, reg := range a.outputHandlers { - out = append(out, reg) + out := make([]Protocol, 0, len(a.protocols)) + for _, p := range a.protocols { + out = append(out, p) } sort.Slice(out, func(i, j int) bool { return out[i].Proto < out[j].Proto @@ -106,75 +65,42 @@ func (a *API) ListOutputs() []RegisteredOutput { return out } -// RegisterInput registers a new input protocol. -func (a *API) RegisterInput(proto, usage string, h input.Handler) { - a.mu.Lock() - defer a.mu.Unlock() - a.inputHandlers[proto] = RegisteredInput{ - Handler: h, - Proto: proto, - Usage: usage, - } -} - -// RegisterOutput registers a new output protocol. -func (a *API) RegisterOutput(proto, usage string, h output.Handler) { +// RegisterProtocol registers a new protocol handler. +func (a *API) RegisterProtocol(proto, usage string, h Handler) { a.mu.Lock() defer a.mu.Unlock() - a.outputHandlers[proto] = RegisteredOutput{ + a.protocols[proto] = Protocol{ Handler: h, Proto: proto, Usage: usage, } } -func (a *API) registerInputs() { - a.RegisterInput("file", "file://path/to/file", input.File) - a.RegisterInput("http", "http://address", input.HTTP) - a.RegisterInput("https", "https://address", input.HTTPS) - a.RegisterInput("http-listen", "http-listen://address", input.HTTPListen) - a.RegisterInput("serial", "serial://device?baud=baudrate", input.Serial) - a.RegisterInput("stdin", "stdin", input.Stdin) - a.RegisterInput("tcp", "tcp://address", input.TCP) - a.RegisterInput("tcp-listen", "tcp-listen://address", input.TCPListen) - a.RegisterInput("udp-listen", "udp-listen://address", input.UDPListen) - a.RegisterInput("udp-multicast", "udp-multicast://address?iface=interface", input.UDPMulticast) - a.RegisterInput("unix", "unix://path/to/sock", input.Unix) - a.RegisterInput("unix-listen", "unix-listen://path/to/sock", input.UnixListen) - a.RegisterInput("ws", "ws://address", input.WS) - a.RegisterInput("wss", "wss://address", input.WSS) - a.RegisterInput("ws-listen", "ws-listen://address", input.WSListen) +func (a *API) registerProtocols() { + a.RegisterProtocol("exec", "exec://command", protocols.Exec) + a.RegisterProtocol("file", "file://path/to/file", protocols.File) + a.RegisterProtocol("serial", "serial://device?baud=baudrate", protocols.Serial) + a.RegisterProtocol("stderr", "stderr", protocols.Stderr) + a.RegisterProtocol("stdin", "stdin", protocols.Stdin) + a.RegisterProtocol("stdio", "stdio", protocols.Stdio) + a.RegisterProtocol("stdout", "stdout", protocols.Stdout) + a.RegisterProtocol("tcp", "tcp://address", protocols.TCP) + a.RegisterProtocol("tcp-listen", "tcp-listen://address", protocols.TCPListen) + a.RegisterProtocol("unix", "unix://path/to/sock", protocols.Unix) + a.RegisterProtocol("unix-listen", "unix-listen://path/to/sock", protocols.UnixListen) + a.RegisterProtocol("ws", "ws://address", protocols.WS) + a.RegisterProtocol("wss", "wss://address", protocols.WSS) + a.RegisterProtocol("ws-listen", "ws-listen://address", protocols.WSListen) } -func (a *API) registerOutputs() { - a.RegisterOutput("file", "file://path/to/file", output.File) - a.RegisterOutput("http", "http://address", output.HTTP) - a.RegisterOutput("https", "https://address", output.HTTPS) - a.RegisterOutput("http-listen", "http-listen://address", output.HTTPListen) - a.RegisterOutput("serial", "serial://device?baud=baudrate", output.Serial) - a.RegisterOutput("stderr", "stderr", output.Stderr) - a.RegisterOutput("stdout", "stdout", output.Stdout) - a.RegisterOutput("tcp", "tcp://address", output.TCP) - a.RegisterOutput("tcp-listen", "tcp-listen://address", output.TCPListen) - a.RegisterOutput("udp", "udp://address", output.UDP) - a.RegisterOutput("unix", "unix://path/to/sock", output.Unix) - a.RegisterOutput("unix-listen", "unix-listen://path/to/sock", output.UnixListen) - a.RegisterOutput("ws", "ws://address", output.WS) - a.RegisterOutput("wss", "wss://address", output.WSS) - a.RegisterOutput("ws-listen", "ws-listen://address", output.WSListen) -} - -func parseOptions(op string) (proto, arg string, opts url.Values, err error) { +func parseOptions(op string) (string, string) { protoarg := strings.SplitN(op, "://", 2) - proto = protoarg[0] - if len(protoarg) == 1 { - return + if len(protoarg) == 0 { + return "", "" } - argopts := strings.SplitN(protoarg[1], "?", 2) - arg = argopts[0] - if len(argopts) == 1 { - return + proto := protoarg[0] + if len(protoarg) == 1 { + return proto, "" } - opts, err = url.ParseQuery(argopts[1]) - return + return proto, protoarg[1] } diff --git a/internal/app/app.go b/internal/app/app.go new file mode 100644 index 0000000..6940461 --- /dev/null +++ b/internal/app/app.go @@ -0,0 +1,130 @@ +package app + +import ( + "io" + + "github.com/wybiral/hookah/pkg/node" +) + +// App manages the main hookah CLI app +type App struct { + // Channel for outgoing updates + Ch chan *Update + // Channel for quit signal + Quit chan error + // All nodes + Nodes []*node.Node + // Reader nodes (inputs) + Readers []*node.Node + // Writer nodes (outputs) + Writers []*node.Node + // Current configuration + Config *Config +} + +// New creates a new App instance from the config. +func New(config *Config) *App { + if config == nil { + config = &Config{} + } + if config.BufferSize == 0 { + config.BufferSize = DefaultBufferSize + } + a := &App{ + Ch: make(chan *Update), + Quit: make(chan error, 1), + Nodes: make([]*node.Node, 0), + Readers: make([]*node.Node, 0), + Writers: make([]*node.Node, 0), + Config: config, + } + return a +} + +// AddNode appends the node to internal lists for processing in Run. +func (a *App) AddNode(n *node.Node) { + a.Nodes = append(a.Nodes, n) + if n.R != nil { + a.Readers = append(a.Readers, n) + } + if n.W != nil { + a.Writers = append(a.Writers, n) + } +} + +// Run the App and block until complete or Quit channel received. +func (a *App) Run() (err error) { + // Closing this channel will stop readerloops and writerloop + done := make(chan struct{}) + go func() { + err = <-a.Quit + close(done) + }() + for _, n := range a.Readers { + go a.readerloop(n, done) + } + er := a.writerloop(done) + if er != nil { + err = er + } + return err +} + +// Close the App and all nodes that were created. +func (a *App) Close() { + for _, n := range a.Nodes { + if n.C != nil { + n.C.Close() + } + } +} + +// Called in a goroutine for each reader node. Continuously calls Read on the +// Node and sends the []byte (wrapped in an Update) to App.Ch. Failure here +// should signal on App.Quit. +func (a *App) readerloop(n *node.Node, done chan struct{}) { + for { + b := make([]byte, a.Config.BufferSize) + i, err := n.R.Read(b) + if i > 0 { + select { + case a.Ch <- &Update{Bytes: b[:i], Source: n}: + case <-done: + return + } + } + // EOF shouldn't really be an error but it should exit + if err == io.EOF { + a.Quit <- nil + return + } + // Other errors should be reported + if err != nil { + a.Quit <- err + return + } + } +} + +// Receive updates from App.Ch and Write out to all App.Writers. Doesn't Write +// to the Node if it's the source of the Update (to avoid loops). +func (a *App) writerloop(done chan struct{}) error { + var u *Update + for { + select { + case u = <-a.Ch: + case <-done: + return nil + } + for _, n := range a.Writers { + // Don't send to self + if u.Source == n { + continue + } + _, err := n.W.Write(u.Bytes) + if err != nil { + return err + } + } + } +} diff --git a/internal/app/config.go b/internal/app/config.go new file mode 100644 index 0000000..7ed244f --- /dev/null +++ b/internal/app/config.go @@ -0,0 +1,9 @@ +package app + +// DefaultBufferSize is default internal buffer size for readers. +const DefaultBufferSize = 8 * 1024 + +// Config options for App. +type Config struct { + BufferSize int +} diff --git a/internal/app/update.go b/internal/app/update.go new file mode 100644 index 0000000..e1d395c --- /dev/null +++ b/internal/app/update.go @@ -0,0 +1,11 @@ +package app + +import "github.com/wybiral/hookah/pkg/node" + +// Update contains the outgoing bytes and source node +type Update struct { + // Bytes being sent + Bytes []byte + // Source of update + Source *node.Node +} diff --git a/internal/protocols/exec.go b/internal/protocols/exec.go new file mode 100644 index 0000000..ede8615 --- /dev/null +++ b/internal/protocols/exec.go @@ -0,0 +1,39 @@ +package protocols + +import ( + "os/exec" + + "github.com/google/shlex" + "github.com/wybiral/hookah/pkg/node" +) + +// Exec creates an exec Node +func Exec(cmd string) (*node.Node, error) { + parts, err := shlex.Split(cmd) + if err != nil { + return nil, err + } + c := exec.Command(parts[0], parts[1:]...) + w, err := c.StdinPipe() + if err != nil { + return nil, err + } + r, err := c.StdoutPipe() + if err != nil { + return nil, err + } + err = c.Start() + if err != nil { + return nil, err + } + closer := &cmdcloser{c: c} + return &node.Node{R: r, W: w, C: closer}, nil +} + +type cmdcloser struct { + c *exec.Cmd +} + +func (c *cmdcloser) Close() error { + return c.c.Process.Kill() +} diff --git a/internal/protocols/file.go b/internal/protocols/file.go new file mode 100644 index 0000000..e4f816f --- /dev/null +++ b/internal/protocols/file.go @@ -0,0 +1,59 @@ +package protocols + +import ( + "net/url" + "os" + "strconv" + "strings" + + "github.com/wybiral/hookah/pkg/node" +) + +// File creates a file Node +func File(arg string) (*node.Node, error) { + var opts url.Values + pathopts := strings.SplitN(arg, "?", 2) + path := pathopts[0] + if len(pathopts) == 2 { + op, err := url.ParseQuery(pathopts[1]) + if err != nil { + return nil, err + } + opts = op + } + perm := os.FileMode(0666) + permstr := opts.Get("perm") + if len(permstr) > 0 { + p, err := strconv.ParseInt(permstr, 10, 32) + if err != nil { + return nil, err + } + perm = os.FileMode(p) + } + flags := os.O_CREATE + mode := "rwa" + m := opts.Get("mode") + if len(m) > 0 { + mode = m + } + if strings.Contains(mode, "a") { + flags |= os.O_APPEND + } + if strings.Contains(mode, "t") { + flags |= os.O_TRUNC + } + read := strings.Contains(mode, "r") + write := strings.Contains(mode, "w") + if read && write { + flags |= os.O_RDWR + } else if read { + flags |= os.O_RDONLY + } else if write { + flags |= os.O_WRONLY + } + f, err := os.OpenFile(path, flags, perm) + if err != nil { + return nil, err + } + return node.New(f), nil +} diff --git a/internal/protocols/listen.go b/internal/protocols/listen.go new file mode 100644 index 0000000..75ec1da --- /dev/null +++ b/internal/protocols/listen.go @@ -0,0 +1,102 @@ +package protocols + +import ( + "errors" + "net" + "sync" + + "github.com/wybiral/hookah/pkg/fanout" + "github.com/wybiral/hookah/pkg/node" +) + +type listenApp struct { + sync.Mutex + ln net.Listener + fan *fanout.Fanout + // Channel of messages + ch chan []byte + b []byte +} + +// listen creates a generic listener and returns Node +func listen(network, addr string) (*node.Node, error) { + app := &listenApp{} + ln, err := net.Listen(network, addr) + if err != nil { + return nil, err + } + app.ln = ln + app.fan = fanout.New() + app.ch = make(chan []byte) + go app.serve() + return node.New(app), nil +} + +func (app *listenApp) Read(b []byte) (int, error) { + app.Lock() + defer app.Unlock() + if len(app.b) == 0 { + app.b = <-app.ch + } + if len(app.b) == 0 { + return 0, errors.New("listen channel closed") + } + n := copy(b, app.b) + app.b = app.b[n:] + return n, nil +} + +func (app *listenApp) Write(b []byte) (int, error) { + app.fan.Send(b) + return len(b), nil +} + +func (app *listenApp) Close() error { + return app.ln.Close() +} + +func (app *listenApp) serve() { + for { + conn, err := app.ln.Accept() + if err != nil { + return + } + go app.handle(conn) + } +} + +func (app *listenApp) handle(conn net.Conn) { + defer conn.Close() + wg := &sync.WaitGroup{} + wg.Add(2) + go app.reader(conn, wg) + go app.writer(conn, wg) + wg.Wait() +} + +func (app *listenApp) reader(conn net.Conn, wg *sync.WaitGroup) { + defer wg.Done() + for { + b := make([]byte, bufferSize) + n, err := conn.Read(b) + if n > 0 { + app.ch <- b[:n] + } + if err != nil { + return + } + } +} + +func (app *listenApp) writer(conn net.Conn, wg *sync.WaitGroup) { + defer wg.Done() + ch := make(chan []byte, queueSize) + app.fan.Add(ch) + defer app.fan.Remove(ch) + for chunk := range ch { + _, err := conn.Write(chunk) + if err != nil { + return + } + } +} diff --git a/internal/protocols/protocols.go b/internal/protocols/protocols.go new file mode 100644 index 0000000..2848289 --- /dev/null +++ b/internal/protocols/protocols.go @@ -0,0 +1,4 @@ +package protocols + +const queueSize = 10 +const bufferSize = 8 * 1024 diff --git a/internal/protocols/serial.go b/internal/protocols/serial.go new file mode 100644 index 0000000..5597dfd --- /dev/null +++ b/internal/protocols/serial.go @@ -0,0 +1,41 @@ +package protocols + +import ( + "net/url" + "strconv" + "strings" + + "github.com/tarm/serial" + "github.com/wybiral/hookah/pkg/node" +) + +// Serial creates a serial node +func Serial(arg string) (*node.Node, error) { + var opts url.Values + devopts := strings.SplitN(arg, "?", 2) + device := devopts[0] + if len(devopts) == 2 { + op, err := url.ParseQuery(devopts[1]) + if err != nil { + return nil, err + } + opts = op + } + baudstr := opts.Get("baud") + if len(baudstr) == 0 { + baudstr = "9600" + } + baud, err := strconv.ParseInt(baudstr, 10, 32) + if err != nil { + return nil, err + } + c := &serial.Config{ + Name: device, + Baud: int(baud), + } + s, err := serial.OpenPort(c) + if err != nil { + return nil, err + } + return node.New(s), nil +} diff --git a/internal/protocols/stdio.go b/internal/protocols/stdio.go new file mode 100644 index 0000000..819a5a4 --- /dev/null +++ b/internal/protocols/stdio.go @@ -0,0 +1,27 @@ +package protocols + +import ( + "os" + + "github.com/wybiral/hookah/pkg/node" +) + +// Stdin creates a stdin Node +func Stdin(path string) (*node.Node, error) { + return &node.Node{R: os.Stdin}, nil +} + +// Stdout creates a stdout Node +func Stdout(path string) (*node.Node, error) { + return &node.Node{W: os.Stdout}, nil +} + +// Stderr creates a stderr Node +func Stderr(path string) (*node.Node, error) { + return &node.Node{W: os.Stderr}, nil +} + +// Stdio creates a stdio Node +func Stdio(path string) (*node.Node, error) { + return &node.Node{R: os.Stdin, W: os.Stdout}, nil +} diff --git a/internal/protocols/tcp.go b/internal/protocols/tcp.go new file mode 100644 index 0000000..258319b --- /dev/null +++ b/internal/protocols/tcp.go @@ -0,0 +1,16 @@ +package protocols + +import ( + "net" + + "github.com/wybiral/hookah/pkg/node" +) + +// TCP creates a TCP client node +func TCP(addr string) (*node.Node, error) { + rwc, err := net.Dial("tcp", addr) + if err != nil { + return nil, err + } + return node.New(rwc), nil +} diff --git a/internal/protocols/tcplisten.go b/internal/protocols/tcplisten.go new file mode 100644 index 0000000..c44c33f --- /dev/null +++ b/internal/protocols/tcplisten.go @@ -0,0 +1,10 @@ +package protocols + +import ( + "github.com/wybiral/hookah/pkg/node" +) + +// TCPListen creates a TCP listener Node +func TCPListen(addr string) (*node.Node, error) { + return listen("tcp", addr) +} diff --git a/internal/protocols/unix.go b/internal/protocols/unix.go new file mode 100644 index 0000000..095b038 --- /dev/null +++ b/internal/protocols/unix.go @@ -0,0 +1,16 @@ +package protocols + +import ( + "net" + + "github.com/wybiral/hookah/pkg/node" +) + +// Unix creates a Unix domain socket client Node +func Unix(addr string) (*node.Node, error) { + rwc, err := net.Dial("unix", addr) + if err != nil { + return nil, err + } + return node.New(rwc), nil +} diff --git a/internal/protocols/unixlisten.go b/internal/protocols/unixlisten.go new file mode 100644 index 0000000..f27738b --- /dev/null +++ b/internal/protocols/unixlisten.go @@ -0,0 +1,10 @@ +package protocols + +import ( + "github.com/wybiral/hookah/pkg/node" +) + +// UnixListen creates a Unix domain socket listener Node +func UnixListen(path string) (*node.Node, error) { + return listen("unix", path) +} diff --git a/internal/protocols/ws.go b/internal/protocols/ws.go new file mode 100644 index 0000000..1fd3b31 --- /dev/null +++ b/internal/protocols/ws.go @@ -0,0 +1,73 @@ +package protocols + +import ( + "io" + "sync" + + "github.com/gorilla/websocket" + "github.com/wybiral/hookah/pkg/node" +) + +type wsconn struct { + // WebSocket connection + conn *websocket.Conn + // Lock for reader + rmu sync.Mutex + // Current active reader + reader io.Reader + // Lock for writes + wmu sync.Mutex +} + +// WS creates a WebSocket client Node +func WS(addr string) (*node.Node, error) { + return wsrequest("ws://" + addr) +} + +// WSS creates a secure WebSocket client Node +func WSS(addr string) (*node.Node, error) { + return wsrequest("wss://" + addr) +} + +func wsrequest(addr string) (*node.Node, error) { + conn, _, err := websocket.DefaultDialer.Dial(addr, nil) + if err != nil { + return nil, err + } + ws := &wsconn{ + conn: conn, + } + return node.New(ws), nil +} + +func (ws *wsconn) Read(b []byte) (int, error) { + ws.rmu.Lock() + defer ws.rmu.Unlock() + if ws.reader == nil { + _, reader, err := ws.conn.NextReader() + if err != nil { + return 0, err + } + ws.reader = reader + } + n, err := ws.reader.Read(b) + if err == io.EOF { + ws.reader = nil + } else if err != nil { + return n, err + } + return n, nil +} + +func (ws *wsconn) Write(b []byte) (int, error) { + w, err := ws.conn.NextWriter(websocket.TextMessage) + if err != nil { + return 0, err + } + defer w.Close() + return w.Write(b) +} + +func (ws *wsconn) Close() error { + return ws.conn.Close() +} diff --git a/pkg/output/wslisten.go b/internal/protocols/wslisten.go similarity index 53% rename from pkg/output/wslisten.go rename to internal/protocols/wslisten.go index 6eb611a..565135b 100644 --- a/pkg/output/wslisten.go +++ b/internal/protocols/wslisten.go @@ -1,17 +1,22 @@ -package output +package protocols import ( - "io" + "errors" "net/http" - "net/url" + "sync" "github.com/gorilla/websocket" "github.com/wybiral/hookah/pkg/fanout" + "github.com/wybiral/hookah/pkg/node" ) type wsListenApp struct { + sync.Mutex server *http.Server fan *fanout.Fanout + // Channel of messages + ch chan []byte + b []byte } // WebSocket upgrader @@ -23,16 +28,31 @@ var upgrader = websocket.Upgrader{ }, } -// WSListen creates a WebSocket listener and returns ReadCloser -func WSListen(addr string, opts url.Values) (io.WriteCloser, error) { +// WSListen creates a WebSocket listener Node +func WSListen(addr string) (*node.Node, error) { app := &wsListenApp{} app.server = &http.Server{ Addr: addr, Handler: http.HandlerFunc(app.handle), } app.fan = fanout.New() + app.ch = make(chan []byte) go app.server.ListenAndServe() - return app, nil + return node.New(app), nil +} + +func (app *wsListenApp) Read(b []byte) (int, error) { + app.Lock() + defer app.Unlock() + if len(app.b) == 0 { + app.b = <-app.ch + } + if len(app.b) == 0 { + return 0, errors.New("listen channel closed") + } + n := copy(b, app.b) + app.b = app.b[n:] + return n, nil } func (app *wsListenApp) Write(b []byte) (int, error) { @@ -50,23 +70,34 @@ func (app *wsListenApp) handle(w http.ResponseWriter, r *http.Request) { return } defer ws.Close() - go app.writeLoop(ws) - // Read from connection to process WebSocket control messages + wg := &sync.WaitGroup{} + wg.Add(2) + go app.reader(ws, wg) + go app.writer(ws, wg) + wg.Wait() +} + +// Pump WebSocket messages to app.ch +func (app *wsListenApp) reader(ws *websocket.Conn, wg *sync.WaitGroup) { + defer wg.Done() for { - _, _, err := ws.NextReader() + _, msg, err := ws.ReadMessage() if err != nil { return } + if len(msg) > 0 { + app.ch <- msg + } } } -// Register with fanout instance and pump messages to WebSocket client -func (app *wsListenApp) writeLoop(ws *websocket.Conn) { +// Pump app.fan messages to WebSocket +func (app *wsListenApp) writer(ws *websocket.Conn, wg *sync.WaitGroup) { ch := make(chan []byte, queueSize) app.fan.Add(ch) defer func() { app.fan.Remove(ch) - ws.Close() + wg.Done() }() for chunk := range ch { err := ws.WriteMessage(websocket.TextMessage, chunk) diff --git a/pkg/chreader/chreader.go b/pkg/chreader/chreader.go deleted file mode 100644 index 8f45877..0000000 --- a/pkg/chreader/chreader.go +++ /dev/null @@ -1,40 +0,0 @@ -// Package chreader converts a []byte channel to an io.ReadCloser. -package chreader - -import ( - "io" - "sync" -) - -type chReader struct { - ch chan []byte - mu sync.Mutex - top []byte -} - -// New returns an io.ReadCloser that reads from and closes ch. -func New(ch chan []byte) io.ReadCloser { - return &chReader{ch: ch} -} - -func (c *chReader) Read(b []byte) (int, error) { - c.mu.Lock() - defer c.mu.Unlock() - if len(c.top) == 0 { - top := <-c.ch - c.top = make([]byte, len(top)) - copy(c.top, top) - if len(c.top) == 0 { - // ch is closed - return 0, io.EOF - } - } - n := copy(b, c.top) - c.top = c.top[n:] - return n, nil -} - -func (c *chReader) Close() error { - close(c.ch) - return nil -} diff --git a/pkg/flagslice/flagslice.go b/pkg/flagslice/flagslice.go new file mode 100644 index 0000000..33017c1 --- /dev/null +++ b/pkg/flagslice/flagslice.go @@ -0,0 +1,17 @@ +package flagslice + +import "strings" + +// FlagSlice provides a []string flag.Var type to handle duplicate options. +type FlagSlice []string + +// String satisfies the flag.Value interface. +func (f *FlagSlice) String() string { + return "" +} + +// Set handles duplicate Set calls by appending to the slice. +func (f *FlagSlice) Set(value string) error { + *f = append(*f, strings.TrimSpace(value)) + return nil +} diff --git a/pkg/input/file.go b/pkg/input/file.go deleted file mode 100644 index 720a346..0000000 --- a/pkg/input/file.go +++ /dev/null @@ -1,12 +0,0 @@ -package input - -import ( - "io" - "net/url" - "os" -) - -// File creates a file input and returns ReadCloser -func File(path string, opts url.Values) (io.ReadCloser, error) { - return os.Open(path) -} diff --git a/pkg/input/http.go b/pkg/input/http.go deleted file mode 100644 index f16c11a..0000000 --- a/pkg/input/http.go +++ /dev/null @@ -1,29 +0,0 @@ -package input - -import ( - "io" - "net/http" - "net/url" -) - -// HTTP creates a streaming HTTP client and returns ReadCloser -func HTTP(addr string, opts url.Values) (io.ReadCloser, error) { - return httprequest("http://" + addr) -} - -// HTTPS creates a streaming HTTPS client and returns as ReadCloser -func HTTPS(addr string, opts url.Values) (io.ReadCloser, error) { - return httprequest("https://" + addr) -} - -func httprequest(addr string) (io.ReadCloser, error) { - req, err := http.NewRequest("GET", addr, nil) - if err != nil { - return nil, err - } - res, err := http.DefaultClient.Do(req) - if err != nil { - return nil, err - } - return res.Body, nil -} diff --git a/pkg/input/httplisten.go b/pkg/input/httplisten.go deleted file mode 100644 index 08c6c01..0000000 --- a/pkg/input/httplisten.go +++ /dev/null @@ -1,55 +0,0 @@ -package input - -import ( - "io" - "net/http" - "net/url" - - "github.com/wybiral/hookah/pkg/chreader" -) - -type httpListenApp struct { - server *http.Server - // Channel of messages - ch chan []byte - // ch Reader - r io.Reader -} - -// HTTPListen creates an HTTP listener and returns ReadCloser -func HTTPListen(addr string, opts url.Values) (io.ReadCloser, error) { - app := &httpListenApp{} - app.server = &http.Server{ - Addr: addr, - Handler: http.HandlerFunc(app.handle), - } - app.ch = make(chan []byte) - app.r = chreader.New(app.ch) - go app.server.ListenAndServe() - return app, nil -} - -func (app *httpListenApp) Read(b []byte) (int, error) { - return app.r.Read(b) -} - -func (app *httpListenApp) Close() error { - // Closing ch causes r.Read to return EOF - close(app.ch) - return app.server.Close() -} - -func (app *httpListenApp) handle(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - for { - b := make([]byte, bufferSize) - n, err := r.Body.Read(b) - if err != nil && err != io.EOF { - return - } - app.ch <- b[:n] - if err == io.EOF { - return - } - } -} diff --git a/pkg/input/input.go b/pkg/input/input.go deleted file mode 100644 index c9ed50b..0000000 --- a/pkg/input/input.go +++ /dev/null @@ -1,13 +0,0 @@ -// Package input provides input stream sources. -package input - -import ( - "io" - "net/url" -) - -// Handler is the function type for user defined input protocols. -type Handler func(arg string, opts url.Values) (io.ReadCloser, error) - -// Buffer size used for incoming messages to servers -const bufferSize = 8 * 1024 diff --git a/pkg/input/listen.go b/pkg/input/listen.go deleted file mode 100644 index 893a0cf..0000000 --- a/pkg/input/listen.go +++ /dev/null @@ -1,63 +0,0 @@ -package input - -import ( - "io" - "net" - - "github.com/wybiral/hookah/pkg/chreader" -) - -type listenApp struct { - ln net.Listener - // Channel of messages - ch chan []byte - // ch Reader - r io.Reader -} - -// listen creates a generic listener and returns ReadCloser -func listen(network, addr string) (io.ReadCloser, error) { - app := &listenApp{} - ln, err := net.Listen(network, addr) - if err != nil { - return nil, err - } - app.ln = ln - app.ch = make(chan []byte) - app.r = chreader.New(app.ch) - go app.serve() - return app, nil -} - -func (app *listenApp) Read(b []byte) (int, error) { - return app.r.Read(b) -} - -func (app *listenApp) Close() error { - // Closing ch causes r.Read to return EOF - close(app.ch) - return app.ln.Close() -} - -func (app *listenApp) serve() { - defer app.ln.Close() - for { - conn, err := app.ln.Accept() - if err != nil { - return - } - go app.handle(conn) - } -} - -func (app *listenApp) handle(conn net.Conn) { - defer conn.Close() - for { - b := make([]byte, bufferSize) - n, err := conn.Read(b) - if err != nil { - return - } - app.ch <- b[:n] - } -} diff --git a/pkg/input/serial.go b/pkg/input/serial.go deleted file mode 100644 index f477977..0000000 --- a/pkg/input/serial.go +++ /dev/null @@ -1,26 +0,0 @@ -package input - -import ( - "io" - "net/url" - "strconv" - - "github.com/tarm/serial" -) - -// Serial creates a serial input and returns ReadCloser -func Serial(device string, opts url.Values) (io.ReadCloser, error) { - baudstr := opts.Get("baud") - if len(baudstr) == 0 { - baudstr = "9600" - } - baud, err := strconv.ParseInt(baudstr, 10, 32) - if err != nil { - return nil, err - } - c := &serial.Config{ - Name: device, - Baud: int(baud), - } - return serial.OpenPort(c) -} diff --git a/pkg/input/stdin.go b/pkg/input/stdin.go deleted file mode 100644 index 4fd2c2f..0000000 --- a/pkg/input/stdin.go +++ /dev/null @@ -1,12 +0,0 @@ -package input - -import ( - "io" - "net/url" - "os" -) - -// Stdin returns stdin ReadCloser. -func Stdin(path string, opts url.Values) (io.ReadCloser, error) { - return os.Stdin, nil -} diff --git a/pkg/input/tcp.go b/pkg/input/tcp.go deleted file mode 100644 index d7d38b5..0000000 --- a/pkg/input/tcp.go +++ /dev/null @@ -1,12 +0,0 @@ -package input - -import ( - "io" - "net" - "net/url" -) - -// TCP creates a TCP client and returns ReadCloser -func TCP(addr string, opts url.Values) (io.ReadCloser, error) { - return net.Dial("tcp", addr) -} diff --git a/pkg/input/tcplisten.go b/pkg/input/tcplisten.go deleted file mode 100644 index 1136c38..0000000 --- a/pkg/input/tcplisten.go +++ /dev/null @@ -1,11 +0,0 @@ -package input - -import ( - "io" - "net/url" -) - -// TCPListen creates a TCP listener and returns ReadCloser -func TCPListen(addr string, opts url.Values) (io.ReadCloser, error) { - return listen("tcp", addr) -} diff --git a/pkg/input/udplisten.go b/pkg/input/udplisten.go deleted file mode 100644 index dbfed32..0000000 --- a/pkg/input/udplisten.go +++ /dev/null @@ -1,16 +0,0 @@ -package input - -import ( - "io" - "net" - "net/url" -) - -// UDPListen creates a UDP listener and returns ReadCloser -func UDPListen(addr string, opts url.Values) (io.ReadCloser, error) { - a, err := net.ResolveUDPAddr("udp", addr) - if err != nil { - return nil, err - } - return net.ListenUDP("udp", a) -} diff --git a/pkg/input/udpmulticast.go b/pkg/input/udpmulticast.go deleted file mode 100644 index 5fdf253..0000000 --- a/pkg/input/udpmulticast.go +++ /dev/null @@ -1,26 +0,0 @@ -package input - -import ( - "io" - "net" - "net/url" -) - -// UDPMulticast creates a UDP multicast listener and returns ReadCloser -func UDPMulticast(addr string, opts url.Values) (io.ReadCloser, error) { - var err error - var iface *net.Interface - ifi := opts.Get("iface") - if len(ifi) > 0 { - // If interface is supplied, look it up - iface, err = net.InterfaceByName(ifi) - if err != nil { - return nil, err - } - } - a, err := net.ResolveUDPAddr("udp", addr) - if err != nil { - return nil, err - } - return net.ListenMulticastUDP("udp", iface, a) -} diff --git a/pkg/input/unix.go b/pkg/input/unix.go deleted file mode 100644 index 14bd897..0000000 --- a/pkg/input/unix.go +++ /dev/null @@ -1,12 +0,0 @@ -package input - -import ( - "io" - "net" - "net/url" -) - -// Unix creates a Unix domain socket client and returns ReadCloser -func Unix(path string, opts url.Values) (io.ReadCloser, error) { - return net.Dial("unix", path) -} diff --git a/pkg/input/unixlisten.go b/pkg/input/unixlisten.go deleted file mode 100644 index ca8c8b9..0000000 --- a/pkg/input/unixlisten.go +++ /dev/null @@ -1,11 +0,0 @@ -package input - -import ( - "io" - "net/url" -) - -// UnixListen creates a Unix domain socket listener and returns ReadCloser -func UnixListen(path string, opts url.Values) (io.ReadCloser, error) { - return listen("unix", path) -} diff --git a/pkg/input/ws.go b/pkg/input/ws.go deleted file mode 100644 index a9520a7..0000000 --- a/pkg/input/ws.go +++ /dev/null @@ -1,76 +0,0 @@ -package input - -import ( - "io" - "net/url" - "sync" - - "github.com/gorilla/websocket" -) - -type wsconn struct { - // WebSocket connection - conn *websocket.Conn - // Lock for reader - mu *sync.Mutex - // Current active reader - reader io.Reader -} - -// WS Creates a WebSocket client and returns ReadCloser -func WS(addr string, opts url.Values) (io.ReadCloser, error) { - return wsrequest("ws://" + addr) -} - -// WSS Creates a secure WebSocket client and returns ReadCloser -func WSS(addr string, opts url.Values) (io.ReadCloser, error) { - return wsrequest("wss://" + addr) -} - -func wsrequest(addr string) (io.ReadCloser, error) { - conn, _, err := websocket.DefaultDialer.Dial(addr, nil) - if err != nil { - return nil, err - } - ws := &wsconn{ - conn: conn, - mu: &sync.Mutex{}, - reader: nil, - } - return ws, nil -} - -func (ws *wsconn) Read(b []byte) (int, error) { - ws.mu.Lock() - defer ws.mu.Unlock() - // When WebSocket is closed conn will be nil - if ws.conn == nil { - return 0, io.EOF - } - // No active reader, get the next one - if ws.reader == nil { - _, reader, err := ws.conn.NextReader() - if err != nil { - ws.conn.Close() - ws.conn = nil - return 0, io.EOF - } - ws.reader = reader - } - n, err := ws.reader.Read(b) - // EOF is for this active reader, not socket - if err == io.EOF { - ws.reader = nil - } - return n, nil -} - -func (ws *wsconn) Close() error { - ws.mu.Lock() - defer ws.mu.Unlock() - if ws.conn != nil { - ws.conn.Close() - ws.conn = nil - } - return nil -} diff --git a/pkg/input/wslisten.go b/pkg/input/wslisten.go deleted file mode 100644 index 6aeb123..0000000 --- a/pkg/input/wslisten.go +++ /dev/null @@ -1,67 +0,0 @@ -package input - -import ( - "io" - "net/http" - "net/url" - - "github.com/gorilla/websocket" - "github.com/wybiral/hookah/pkg/chreader" -) - -type wsListenApp struct { - server *http.Server - // Channel of messages - ch chan []byte - // ch Reader - r io.Reader -} - -// WebSocket upgrader -var upgrader = websocket.Upgrader{ - ReadBufferSize: 1024, - WriteBufferSize: 1024, - CheckOrigin: func(r *http.Request) bool { - return true - }, -} - -// WSListen creates a WebSocket listener and returns ReadCloser -func WSListen(addr string, opts url.Values) (io.ReadCloser, error) { - app := &wsListenApp{} - app.server = &http.Server{ - Addr: addr, - Handler: http.HandlerFunc(app.handle), - } - app.ch = make(chan []byte) - app.r = chreader.New(app.ch) - go app.server.ListenAndServe() - return app, nil -} - -func (app *wsListenApp) Read(b []byte) (int, error) { - return app.r.Read(b) -} - -func (app *wsListenApp) Close() error { - // Closing ch causes r.Read to return EOF - close(app.ch) - return app.server.Close() -} - -func (app *wsListenApp) handle(w http.ResponseWriter, r *http.Request) { - ws, err := upgrader.Upgrade(w, r, nil) - if err != nil { - return - } - defer ws.Close() - for { - _, msg, err := ws.ReadMessage() - if err != nil { - return - } - if len(msg) > 0 { - app.ch <- msg - } - } -} diff --git a/pkg/node/node.go b/pkg/node/node.go new file mode 100644 index 0000000..6e678ca --- /dev/null +++ b/pkg/node/node.go @@ -0,0 +1,17 @@ +package node + +import ( + "io" +) + +// Node is the main interface used by all hookah nodes. +type Node struct { + R io.Reader + W io.Writer + C io.Closer +} + +// New returns a new Node from a ReadWriteCloser. +func New(rwc io.ReadWriteCloser) *Node { + return &Node{R: rwc, W: rwc, C: rwc} +} diff --git a/pkg/output/file.go b/pkg/output/file.go deleted file mode 100644 index 41f9a5b..0000000 --- a/pkg/output/file.go +++ /dev/null @@ -1,13 +0,0 @@ -package output - -import ( - "io" - "net/url" - "os" -) - -// File creates a file output (in append mode) and returns WriteCloser -func File(path string, opts url.Values) (io.WriteCloser, error) { - flags := os.O_APPEND | os.O_WRONLY | os.O_CREATE - return os.OpenFile(path, flags, 0600) -} diff --git a/pkg/output/http.go b/pkg/output/http.go deleted file mode 100644 index 2385291..0000000 --- a/pkg/output/http.go +++ /dev/null @@ -1,56 +0,0 @@ -package output - -import ( - "io" - "net/http" - "net/url" -) - -// HTTP creates an HTTP client and returns WriteCloser -func HTTP(addr string, opts url.Values) (io.WriteCloser, error) { - return httprequest("http://" + addr) -} - -// HTTPS creates an HTTPS client and returns WriteCloser -func HTTPS(addr string, opts url.Values) (io.WriteCloser, error) { - return httprequest("https://" + addr) -} - -func httprequest(addr string) (io.WriteCloser, error) { - pr, pw := io.Pipe() - req, err := http.NewRequest("PUT", addr, pr) - if err != nil { - return nil, err - } - e := make(chan error) - go func() { - res, err := http.DefaultClient.Do(req) - if err != nil { - e <- err - return - } - e <- res.Body.Close() - }() - cl := &httpClient{ - w: pw, - e: e, - } - return cl, nil -} - -type httpClient struct { - w io.WriteCloser - e chan error -} - -func (h *httpClient) Write(b []byte) (int, error) { - return h.w.Write(b) -} - -func (h *httpClient) Close() error { - err := h.w.Close() - if err != nil { - return err - } - return <-h.e -} diff --git a/pkg/output/httplisten.go b/pkg/output/httplisten.go deleted file mode 100644 index 38a81a7..0000000 --- a/pkg/output/httplisten.go +++ /dev/null @@ -1,53 +0,0 @@ -package output - -import ( - "io" - "net/http" - "net/url" - - "github.com/wybiral/hookah/pkg/fanout" -) - -type httpListenApp struct { - server *http.Server - fan *fanout.Fanout -} - -// HTTPListen creates an HTTP listener and returns WriteCloser -func HTTPListen(addr string, opts url.Values) (io.WriteCloser, error) { - app := &httpListenApp{} - app.server = &http.Server{ - Addr: addr, - Handler: http.HandlerFunc(app.handle), - } - app.fan = fanout.New() - go app.server.ListenAndServe() - return app, nil -} - -func (app *httpListenApp) Write(b []byte) (int, error) { - app.fan.Send(b) - return len(b), nil -} - -func (app *httpListenApp) Close() error { - return app.server.Close() -} - -func (app *httpListenApp) handle(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - flusher, ok := w.(http.Flusher) - if !ok { - return - } - ch := make(chan []byte, queueSize) - app.fan.Add(ch) - defer app.fan.Remove(ch) - for chunk := range ch { - _, err := w.Write(chunk) - if err != nil { - return - } - flusher.Flush() - } -} diff --git a/pkg/output/listen.go b/pkg/output/listen.go deleted file mode 100644 index 37c4e69..0000000 --- a/pkg/output/listen.go +++ /dev/null @@ -1,59 +0,0 @@ -package output - -import ( - "io" - "net" - - "github.com/wybiral/hookah/pkg/fanout" -) - -type listenApp struct { - ln net.Listener - fan *fanout.Fanout -} - -// listen creates a generic listen server and returns ReadCloser -func listen(network, addr string) (io.WriteCloser, error) { - app := &listenApp{} - ln, err := net.Listen(network, addr) - if err != nil { - return nil, err - } - app.ln = ln - app.fan = fanout.New() - go app.serve() - return app, nil -} - -func (app *listenApp) Write(b []byte) (int, error) { - app.fan.Send(b) - return len(b), nil -} - -func (app *listenApp) Close() error { - return app.ln.Close() -} - -func (app *listenApp) serve() { - defer app.ln.Close() - for { - conn, err := app.ln.Accept() - if err != nil { - return - } - go app.handle(conn) - } -} - -func (app *listenApp) handle(conn net.Conn) { - defer conn.Close() - ch := make(chan []byte, queueSize) - app.fan.Add(ch) - defer app.fan.Remove(ch) - for chunk := range ch { - _, err := conn.Write(chunk) - if err != nil { - return - } - } -} diff --git a/pkg/output/output.go b/pkg/output/output.go deleted file mode 100644 index 8d1f174..0000000 --- a/pkg/output/output.go +++ /dev/null @@ -1,13 +0,0 @@ -// Package output provides output stream destinations. -package output - -import ( - "io" - "net/url" -) - -// Handler is the function type for user defined input protocols. -type Handler func(arg string, opts url.Values) (io.WriteCloser, error) - -// Number of buffered messages for each incoming server connection. -const queueSize = 10 diff --git a/pkg/output/serial.go b/pkg/output/serial.go deleted file mode 100644 index 987a2fe..0000000 --- a/pkg/output/serial.go +++ /dev/null @@ -1,26 +0,0 @@ -package output - -import ( - "io" - "net/url" - "strconv" - - "github.com/tarm/serial" -) - -// Serial creates a serial output and returns WriteCloser -func Serial(device string, opts url.Values) (io.WriteCloser, error) { - baudstr := opts.Get("baud") - if len(baudstr) == 0 { - baudstr = "9600" - } - baud, err := strconv.ParseInt(baudstr, 10, 32) - if err != nil { - return nil, err - } - c := &serial.Config{ - Name: device, - Baud: int(baud), - } - return serial.OpenPort(c) -} diff --git a/pkg/output/stdout.go b/pkg/output/stdout.go deleted file mode 100644 index 6fda000..0000000 --- a/pkg/output/stdout.go +++ /dev/null @@ -1,17 +0,0 @@ -package output - -import ( - "io" - "net/url" - "os" -) - -// Stdout returns stdout WriteCloser. -func Stdout(path string, opts url.Values) (io.WriteCloser, error) { - return os.Stdout, nil -} - -// Stderr returns stderr WriteCloser. -func Stderr(path string, opts url.Values) (io.WriteCloser, error) { - return os.Stderr, nil -} diff --git a/pkg/output/tcp.go b/pkg/output/tcp.go deleted file mode 100644 index 12ca0ea..0000000 --- a/pkg/output/tcp.go +++ /dev/null @@ -1,12 +0,0 @@ -package output - -import ( - "io" - "net" - "net/url" -) - -// TCP creates a TCP client and returns WriteCloser -func TCP(addr string, opts url.Values) (io.WriteCloser, error) { - return net.Dial("tcp", addr) -} diff --git a/pkg/output/tcplisten.go b/pkg/output/tcplisten.go deleted file mode 100644 index 47964ff..0000000 --- a/pkg/output/tcplisten.go +++ /dev/null @@ -1,11 +0,0 @@ -package output - -import ( - "io" - "net/url" -) - -// TCPListen creates a TCP server and returns ReadCloser -func TCPListen(addr string, opts url.Values) (io.WriteCloser, error) { - return listen("tcp", addr) -} diff --git a/pkg/output/udp.go b/pkg/output/udp.go deleted file mode 100644 index f05d0c5..0000000 --- a/pkg/output/udp.go +++ /dev/null @@ -1,12 +0,0 @@ -package output - -import ( - "io" - "net" - "net/url" -) - -// UDP creates a UDP client and returns WriteCloser -func UDP(addr string, opts url.Values) (io.WriteCloser, error) { - return net.Dial("udp", addr) -} diff --git a/pkg/output/unix.go b/pkg/output/unix.go deleted file mode 100644 index 9221fcf..0000000 --- a/pkg/output/unix.go +++ /dev/null @@ -1,12 +0,0 @@ -package output - -import ( - "io" - "net" - "net/url" -) - -// Unix creates a Unix client and returns WriteCloser -func Unix(path string, opts url.Values) (io.WriteCloser, error) { - return net.Dial("unix", path) -} diff --git a/pkg/output/unixlisten.go b/pkg/output/unixlisten.go deleted file mode 100644 index c0f95f6..0000000 --- a/pkg/output/unixlisten.go +++ /dev/null @@ -1,11 +0,0 @@ -package output - -import ( - "io" - "net/url" -) - -// UnixListen creates a Unix domain socket listener and returns ReadCloser -func UnixListen(path string, opts url.Values) (io.WriteCloser, error) { - return listen("unix", path) -} diff --git a/pkg/output/ws.go b/pkg/output/ws.go deleted file mode 100644 index cb4052b..0000000 --- a/pkg/output/ws.go +++ /dev/null @@ -1,66 +0,0 @@ -package output - -import ( - "io" - "net/url" - "os" - "sync" - - "github.com/gorilla/websocket" -) - -type wsconn struct { - // WebSocket connection - conn *websocket.Conn - // Lock for writer - mu *sync.Mutex -} - -// WS creates a WebSocket client and returns WriteCloser -func WS(addr string, opts url.Values) (io.WriteCloser, error) { - return wsrequest("ws://" + addr) -} - -// WSS creates a secure WebSocket client and returns WriteCloser -func WSS(addr string, opts url.Values) (io.WriteCloser, error) { - return wsrequest("wss://" + addr) -} - -func wsrequest(addr string) (io.WriteCloser, error) { - conn, _, err := websocket.DefaultDialer.Dial(addr, nil) - if err != nil { - return nil, err - } - ws := &wsconn{ - conn: conn, - mu: &sync.Mutex{}, - } - return ws, nil -} - -func (ws *wsconn) Write(b []byte) (int, error) { - ws.mu.Lock() - defer ws.mu.Unlock() - // When WebSocket is closed conn will be nil - if ws.conn == nil { - return 0, os.ErrClosed - } - w, err := ws.conn.NextWriter(websocket.TextMessage) - if err != nil { - return 0, err - } - defer w.Close() - n, err := w.Write(b) - return n, err -} - -func (ws *wsconn) Close() error { - var err error - ws.mu.Lock() - defer ws.mu.Unlock() - if ws.conn != nil { - err = ws.conn.Close() - ws.conn = nil - } - return err -} diff --git a/vendor/github.com/google/shlex/COPYING b/vendor/github.com/google/shlex/COPYING new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/vendor/github.com/google/shlex/COPYING @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/google/shlex/README b/vendor/github.com/google/shlex/README new file mode 100644 index 0000000..c86bcc0 --- /dev/null +++ b/vendor/github.com/google/shlex/README @@ -0,0 +1,2 @@ +go-shlex is a simple lexer for go that supports shell-style quoting, +commenting, and escaping. diff --git a/vendor/github.com/google/shlex/shlex.go b/vendor/github.com/google/shlex/shlex.go new file mode 100644 index 0000000..3cb37b7 --- /dev/null +++ b/vendor/github.com/google/shlex/shlex.go @@ -0,0 +1,417 @@ +/* +Copyright 2012 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package shlex implements a simple lexer which splits input in to tokens using +shell-style rules for quoting and commenting. + +The basic use case uses the default ASCII lexer to split a string into sub-strings: + + shlex.Split("one \"two three\" four") -> []string{"one", "two three", "four"} + +To process a stream of strings: + + l := NewLexer(os.Stdin) + for ; token, err := l.Next(); err != nil { + // process token + } + +To access the raw token stream (which includes tokens for comments): + + t := NewTokenizer(os.Stdin) + for ; token, err := t.Next(); err != nil { + // process token + } + +*/ +package shlex + +import ( + "bufio" + "fmt" + "io" + "strings" +) + +// TokenType is a top-level token classification: A word, space, comment, unknown. +type TokenType int + +// runeTokenClass is the type of a UTF-8 character classification: A quote, space, escape. +type runeTokenClass int + +// the internal state used by the lexer state machine +type lexerState int + +// Token is a (type, value) pair representing a lexographical token. +type Token struct { + tokenType TokenType + value string +} + +// Equal reports whether tokens a, and b, are equal. +// Two tokens are equal if both their types and values are equal. A nil token can +// never be equal to another token. +func (a *Token) Equal(b *Token) bool { + if a == nil || b == nil { + return false + } + if a.tokenType != b.tokenType { + return false + } + return a.value == b.value +} + +// Named classes of UTF-8 runes +const ( + spaceRunes = " \t\r\n" + escapingQuoteRunes = `"` + nonEscapingQuoteRunes = "'" + escapeRunes = `\` + commentRunes = "#" +) + +// Classes of rune token +const ( + unknownRuneClass runeTokenClass = iota + spaceRuneClass + escapingQuoteRuneClass + nonEscapingQuoteRuneClass + escapeRuneClass + commentRuneClass + eofRuneClass +) + +// Classes of lexographic token +const ( + UnknownToken TokenType = iota + WordToken + SpaceToken + CommentToken +) + +// Lexer state machine states +const ( + startState lexerState = iota // no runes have been seen + inWordState // processing regular runes in a word + escapingState // we have just consumed an escape rune; the next rune is literal + escapingQuotedState // we have just consumed an escape rune within a quoted string + quotingEscapingState // we are within a quoted string that supports escaping ("...") + quotingState // we are within a string that does not support escaping ('...') + commentState // we are within a comment (everything following an unquoted or unescaped # +) + +// tokenClassifier is used for classifying rune characters. +type tokenClassifier map[rune]runeTokenClass + +func (typeMap tokenClassifier) addRuneClass(runes string, tokenType runeTokenClass) { + for _, runeChar := range runes { + typeMap[runeChar] = tokenType + } +} + +// newDefaultClassifier creates a new classifier for ASCII characters. +func newDefaultClassifier() tokenClassifier { + t := tokenClassifier{} + t.addRuneClass(spaceRunes, spaceRuneClass) + t.addRuneClass(escapingQuoteRunes, escapingQuoteRuneClass) + t.addRuneClass(nonEscapingQuoteRunes, nonEscapingQuoteRuneClass) + t.addRuneClass(escapeRunes, escapeRuneClass) + t.addRuneClass(commentRunes, commentRuneClass) + return t +} + +// ClassifyRune classifiees a rune +func (t tokenClassifier) ClassifyRune(runeVal rune) runeTokenClass { + return t[runeVal] +} + +// Lexer turns an input stream into a sequence of tokens. Whitespace and comments are skipped. +type Lexer Tokenizer + +// NewLexer creates a new lexer from an input stream. +func NewLexer(r io.Reader) *Lexer { + + return (*Lexer)(NewTokenizer(r)) +} + +// Next returns the next word, or an error. If there are no more words, +// the error will be io.EOF. +func (l *Lexer) Next() (string, error) { + for { + token, err := (*Tokenizer)(l).Next() + if err != nil { + return "", err + } + switch token.tokenType { + case WordToken: + return token.value, nil + case CommentToken: + // skip comments + default: + return "", fmt.Errorf("Unknown token type: %v", token.tokenType) + } + } +} + +// Tokenizer turns an input stream into a sequence of typed tokens +type Tokenizer struct { + input bufio.Reader + classifier tokenClassifier +} + +// NewTokenizer creates a new tokenizer from an input stream. +func NewTokenizer(r io.Reader) *Tokenizer { + input := bufio.NewReader(r) + classifier := newDefaultClassifier() + return &Tokenizer{ + input: *input, + classifier: classifier} +} + +// scanStream scans the stream for the next token using the internal state machine. +// It will panic if it encounters a rune which it does not know how to handle. +func (t *Tokenizer) scanStream() (*Token, error) { + state := startState + var tokenType TokenType + var value []rune + var nextRune rune + var nextRuneType runeTokenClass + var err error + + for { + nextRune, _, err = t.input.ReadRune() + nextRuneType = t.classifier.ClassifyRune(nextRune) + + if err == io.EOF { + nextRuneType = eofRuneClass + err = nil + } else if err != nil { + return nil, err + } + + switch state { + case startState: // no runes read yet + { + switch nextRuneType { + case eofRuneClass: + { + return nil, io.EOF + } + case spaceRuneClass: + { + } + case escapingQuoteRuneClass: + { + tokenType = WordToken + state = quotingEscapingState + } + case nonEscapingQuoteRuneClass: + { + tokenType = WordToken + state = quotingState + } + case escapeRuneClass: + { + tokenType = WordToken + state = escapingState + } + case commentRuneClass: + { + tokenType = CommentToken + state = commentState + } + default: + { + tokenType = WordToken + value = append(value, nextRune) + state = inWordState + } + } + } + case inWordState: // in a regular word + { + switch nextRuneType { + case eofRuneClass: + { + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + case spaceRuneClass: + { + t.input.UnreadRune() + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + case escapingQuoteRuneClass: + { + state = quotingEscapingState + } + case nonEscapingQuoteRuneClass: + { + state = quotingState + } + case escapeRuneClass: + { + state = escapingState + } + default: + { + value = append(value, nextRune) + } + } + } + case escapingState: // the rune after an escape character + { + switch nextRuneType { + case eofRuneClass: + { + err = fmt.Errorf("EOF found after escape character") + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + default: + { + state = inWordState + value = append(value, nextRune) + } + } + } + case escapingQuotedState: // the next rune after an escape character, in double quotes + { + switch nextRuneType { + case eofRuneClass: + { + err = fmt.Errorf("EOF found after escape character") + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + default: + { + state = quotingEscapingState + value = append(value, nextRune) + } + } + } + case quotingEscapingState: // in escaping double quotes + { + switch nextRuneType { + case eofRuneClass: + { + err = fmt.Errorf("EOF found when expecting closing quote") + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + case escapingQuoteRuneClass: + { + state = inWordState + } + case escapeRuneClass: + { + state = escapingQuotedState + } + default: + { + value = append(value, nextRune) + } + } + } + case quotingState: // in non-escaping single quotes + { + switch nextRuneType { + case eofRuneClass: + { + err = fmt.Errorf("EOF found when expecting closing quote") + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + case nonEscapingQuoteRuneClass: + { + state = inWordState + } + default: + { + value = append(value, nextRune) + } + } + } + case commentState: // in a comment + { + switch nextRuneType { + case eofRuneClass: + { + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } + case spaceRuneClass: + { + if nextRune == '\n' { + state = startState + token := &Token{ + tokenType: tokenType, + value: string(value)} + return token, err + } else { + value = append(value, nextRune) + } + } + default: + { + value = append(value, nextRune) + } + } + } + default: + { + return nil, fmt.Errorf("Unexpected state: %v", state) + } + } + } +} + +// Next returns the next token in the stream. +func (t *Tokenizer) Next() (*Token, error) { + return t.scanStream() +} + +// Split partitions a string into a slice of strings. +func Split(s string) ([]string, error) { + l := NewLexer(strings.NewReader(s)) + subStrings := make([]string, 0) + for { + word, err := l.Next() + if err != nil { + if err == io.EOF { + return subStrings, nil + } + return subStrings, err + } + subStrings = append(subStrings, word) + } +} diff --git a/vendor/golang.org/x/sys/unix/syscall_bsd.go b/vendor/golang.org/x/sys/unix/syscall_bsd.go index 53fb851..33c8b5f 100644 --- a/vendor/golang.org/x/sys/unix/syscall_bsd.go +++ b/vendor/golang.org/x/sys/unix/syscall_bsd.go @@ -206,7 +206,7 @@ func (sa *SockaddrDatalink) sockaddr() (unsafe.Pointer, _Socklen, error) { return unsafe.Pointer(&sa.raw), SizeofSockaddrDatalink, nil } -func anyToSockaddr(rsa *RawSockaddrAny) (Sockaddr, error) { +func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) { switch rsa.Addr.Family { case AF_LINK: pp := (*RawSockaddrDatalink)(unsafe.Pointer(rsa)) @@ -286,7 +286,7 @@ func Accept(fd int) (nfd int, sa Sockaddr, err error) { Close(nfd) return 0, nil, ECONNABORTED } - sa, err = anyToSockaddr(&rsa) + sa, err = anyToSockaddr(fd, &rsa) if err != nil { Close(nfd) nfd = 0 @@ -306,7 +306,7 @@ func Getsockname(fd int) (sa Sockaddr, err error) { rsa.Addr.Family = AF_UNIX rsa.Addr.Len = SizeofSockaddrUnix } - return anyToSockaddr(&rsa) + return anyToSockaddr(fd, &rsa) } //sysnb socketpair(domain int, typ int, proto int, fd *[2]int32) (err error) @@ -356,7 +356,7 @@ func Recvmsg(fd int, p, oob []byte, flags int) (n, oobn int, recvflags int, from recvflags = int(msg.Flags) // source address is only specified if the socket is unconnected if rsa.Addr.Family != AF_UNSPEC { - from, err = anyToSockaddr(&rsa) + from, err = anyToSockaddr(fd, &rsa) } return } diff --git a/vendor/golang.org/x/sys/unix/syscall_dragonfly.go b/vendor/golang.org/x/sys/unix/syscall_dragonfly.go index b5072de..e34abe2 100644 --- a/vendor/golang.org/x/sys/unix/syscall_dragonfly.go +++ b/vendor/golang.org/x/sys/unix/syscall_dragonfly.go @@ -87,7 +87,7 @@ func Accept4(fd, flags int) (nfd int, sa Sockaddr, err error) { if len > SizeofSockaddrAny { panic("RawSockaddrAny too small") } - sa, err = anyToSockaddr(&rsa) + sa, err = anyToSockaddr(fd, &rsa) if err != nil { Close(nfd) nfd = 0 diff --git a/vendor/golang.org/x/sys/unix/syscall_freebsd.go b/vendor/golang.org/x/sys/unix/syscall_freebsd.go index ba9df4a..5561a3e 100644 --- a/vendor/golang.org/x/sys/unix/syscall_freebsd.go +++ b/vendor/golang.org/x/sys/unix/syscall_freebsd.go @@ -89,7 +89,7 @@ func Accept4(fd, flags int) (nfd int, sa Sockaddr, err error) { if len > SizeofSockaddrAny { panic("RawSockaddrAny too small") } - sa, err = anyToSockaddr(&rsa) + sa, err = anyToSockaddr(fd, &rsa) if err != nil { Close(nfd) nfd = 0 diff --git a/vendor/golang.org/x/sys/unix/syscall_linux.go b/vendor/golang.org/x/sys/unix/syscall_linux.go index 9908030..690c2c8 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux.go @@ -489,6 +489,47 @@ func (sa *SockaddrL2) sockaddr() (unsafe.Pointer, _Socklen, error) { return unsafe.Pointer(&sa.raw), SizeofSockaddrL2, nil } +// SockaddrRFCOMM implements the Sockaddr interface for AF_BLUETOOTH type sockets +// using the RFCOMM protocol. +// +// Server example: +// +// fd, _ := Socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM) +// _ = unix.Bind(fd, &unix.SockaddrRFCOMM{ +// Channel: 1, +// Addr: [6]uint8{0, 0, 0, 0, 0, 0}, // BDADDR_ANY or 00:00:00:00:00:00 +// }) +// _ = Listen(fd, 1) +// nfd, sa, _ := Accept(fd) +// fmt.Printf("conn addr=%v fd=%d", sa.(*unix.SockaddrRFCOMM).Addr, nfd) +// Read(nfd, buf) +// +// Client example: +// +// fd, _ := Socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM) +// _ = Connect(fd, &SockaddrRFCOMM{ +// Channel: 1, +// Addr: [6]byte{0x11, 0x22, 0x33, 0xaa, 0xbb, 0xcc}, // CC:BB:AA:33:22:11 +// }) +// Write(fd, []byte(`hello`)) +type SockaddrRFCOMM struct { + // Addr represents a bluetooth address, byte ordering is little-endian. + Addr [6]uint8 + + // Channel is a designated bluetooth channel, only 1-30 are available for use. + // Since Linux 2.6.7 and further zero value is the first available channel. + Channel uint8 + + raw RawSockaddrRFCOMM +} + +func (sa *SockaddrRFCOMM) sockaddr() (unsafe.Pointer, _Socklen, error) { + sa.raw.Family = AF_BLUETOOTH + sa.raw.Channel = sa.Channel + sa.raw.Bdaddr = sa.Addr + return unsafe.Pointer(&sa.raw), SizeofSockaddrRFCOMM, nil +} + // SockaddrCAN implements the Sockaddr interface for AF_CAN type sockets. // The RxID and TxID fields are used for transport protocol addressing in // (CAN_TP16, CAN_TP20, CAN_MCNET, and CAN_ISOTP), they can be left with @@ -651,7 +692,7 @@ func (sa *SockaddrVM) sockaddr() (unsafe.Pointer, _Socklen, error) { return unsafe.Pointer(&sa.raw), SizeofSockaddrVM, nil } -func anyToSockaddr(rsa *RawSockaddrAny) (Sockaddr, error) { +func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) { switch rsa.Addr.Family { case AF_NETLINK: pp := (*RawSockaddrNetlink)(unsafe.Pointer(rsa)) @@ -728,6 +769,30 @@ func anyToSockaddr(rsa *RawSockaddrAny) (Sockaddr, error) { Port: pp.Port, } return sa, nil + case AF_BLUETOOTH: + proto, err := GetsockoptInt(fd, SOL_SOCKET, SO_PROTOCOL) + if err != nil { + return nil, err + } + // only BTPROTO_L2CAP and BTPROTO_RFCOMM can accept connections + switch proto { + case BTPROTO_L2CAP: + pp := (*RawSockaddrL2)(unsafe.Pointer(rsa)) + sa := &SockaddrL2{ + PSM: pp.Psm, + CID: pp.Cid, + Addr: pp.Bdaddr, + AddrType: pp.Bdaddr_type, + } + return sa, nil + case BTPROTO_RFCOMM: + pp := (*RawSockaddrRFCOMM)(unsafe.Pointer(rsa)) + sa := &SockaddrRFCOMM{ + Channel: pp.Channel, + Addr: pp.Bdaddr, + } + return sa, nil + } } return nil, EAFNOSUPPORT } @@ -739,7 +804,7 @@ func Accept(fd int) (nfd int, sa Sockaddr, err error) { if err != nil { return } - sa, err = anyToSockaddr(&rsa) + sa, err = anyToSockaddr(fd, &rsa) if err != nil { Close(nfd) nfd = 0 @@ -757,7 +822,7 @@ func Accept4(fd int, flags int) (nfd int, sa Sockaddr, err error) { if len > SizeofSockaddrAny { panic("RawSockaddrAny too small") } - sa, err = anyToSockaddr(&rsa) + sa, err = anyToSockaddr(fd, &rsa) if err != nil { Close(nfd) nfd = 0 @@ -771,7 +836,7 @@ func Getsockname(fd int) (sa Sockaddr, err error) { if err = getsockname(fd, &rsa, &len); err != nil { return } - return anyToSockaddr(&rsa) + return anyToSockaddr(fd, &rsa) } func GetsockoptIPMreqn(fd, level, opt int) (*IPMreqn, error) { @@ -960,7 +1025,7 @@ func Recvmsg(fd int, p, oob []byte, flags int) (n, oobn int, recvflags int, from recvflags = int(msg.Flags) // source address is only specified if the socket is unconnected if rsa.Addr.Family != AF_UNSPEC { - from, err = anyToSockaddr(&rsa) + from, err = anyToSockaddr(fd, &rsa) } return } diff --git a/vendor/golang.org/x/sys/unix/syscall_solaris.go b/vendor/golang.org/x/sys/unix/syscall_solaris.go index 820ef77..a05337d 100644 --- a/vendor/golang.org/x/sys/unix/syscall_solaris.go +++ b/vendor/golang.org/x/sys/unix/syscall_solaris.go @@ -112,7 +112,7 @@ func Getsockname(fd int) (sa Sockaddr, err error) { if err = getsockname(fd, &rsa, &len); err != nil { return } - return anyToSockaddr(&rsa) + return anyToSockaddr(fd, &rsa) } // GetsockoptString returns the string value of the socket option opt for the @@ -360,7 +360,7 @@ func Futimes(fd int, tv []Timeval) error { return futimesat(fd, nil, (*[2]Timeval)(unsafe.Pointer(&tv[0]))) } -func anyToSockaddr(rsa *RawSockaddrAny) (Sockaddr, error) { +func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) { switch rsa.Addr.Family { case AF_UNIX: pp := (*RawSockaddrUnix)(unsafe.Pointer(rsa)) @@ -411,7 +411,7 @@ func Accept(fd int) (nfd int, sa Sockaddr, err error) { if nfd == -1 { return } - sa, err = anyToSockaddr(&rsa) + sa, err = anyToSockaddr(fd, &rsa) if err != nil { Close(nfd) nfd = 0 @@ -448,7 +448,7 @@ func Recvmsg(fd int, p, oob []byte, flags int) (n, oobn int, recvflags int, from oobn = int(msg.Accrightslen) // source address is only specified if the socket is unconnected if rsa.Addr.Family != AF_UNSPEC { - from, err = anyToSockaddr(&rsa) + from, err = anyToSockaddr(fd, &rsa) } return } diff --git a/vendor/golang.org/x/sys/unix/syscall_unix.go b/vendor/golang.org/x/sys/unix/syscall_unix.go index b835bad..95b2180 100644 --- a/vendor/golang.org/x/sys/unix/syscall_unix.go +++ b/vendor/golang.org/x/sys/unix/syscall_unix.go @@ -219,7 +219,7 @@ func Getpeername(fd int) (sa Sockaddr, err error) { if err = getpeername(fd, &rsa, &len); err != nil { return } - return anyToSockaddr(&rsa) + return anyToSockaddr(fd, &rsa) } func GetsockoptByte(fd, level, opt int) (value byte, err error) { @@ -291,7 +291,7 @@ func Recvfrom(fd int, p []byte, flags int) (n int, from Sockaddr, err error) { return } if rsa.Addr.Family != AF_UNSPEC { - from, err = anyToSockaddr(&rsa) + from, err = anyToSockaddr(fd, &rsa) } return } diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64.go index 0ae2aa8..7cc1bfd 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64.go @@ -1474,8 +1474,13 @@ func Munlockall() (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func Dup2(oldfd int, newfd int) (err error) { - _, _, e1 := Syscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0) +func faccessat(dirfd int, path string, mode uint32) (err error) { + var _p0 *byte + _p0, err = BytePtrFromString(path) + if err != nil { + return + } + _, _, e1 := Syscall(SYS_FACCESSAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(mode)) if e1 != 0 { err = errnoErr(e1) } @@ -1484,9 +1489,8 @@ func Dup2(oldfd int, newfd int) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func EpollCreate(size int) (fd int, err error) { - r0, _, e1 := RawSyscall(SYS_EPOLL_CREATE, uintptr(size), 0, 0) - fd = int(r0) +func Dup2(oldfd int, newfd int) (err error) { + _, _, e1 := Syscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0) if e1 != 0 { err = errnoErr(e1) } @@ -1495,13 +1499,9 @@ func EpollCreate(size int) (fd int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func faccessat(dirfd int, path string, mode uint32) (err error) { - var _p0 *byte - _p0, err = BytePtrFromString(path) - if err != nil { - return - } - _, _, e1 := Syscall(SYS_FACCESSAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(mode)) +func EpollCreate(size int) (fd int, err error) { + r0, _, e1 := RawSyscall(SYS_EPOLL_CREATE, uintptr(size), 0, 0) + fd = int(r0) if e1 != 0 { err = errnoErr(e1) } diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64le.go index fa16c16..c3dcb38 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64le.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64le.go @@ -1474,8 +1474,13 @@ func Munlockall() (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func Dup2(oldfd int, newfd int) (err error) { - _, _, e1 := Syscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0) +func faccessat(dirfd int, path string, mode uint32) (err error) { + var _p0 *byte + _p0, err = BytePtrFromString(path) + if err != nil { + return + } + _, _, e1 := Syscall(SYS_FACCESSAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(mode)) if e1 != 0 { err = errnoErr(e1) } @@ -1484,9 +1489,8 @@ func Dup2(oldfd int, newfd int) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func EpollCreate(size int) (fd int, err error) { - r0, _, e1 := RawSyscall(SYS_EPOLL_CREATE, uintptr(size), 0, 0) - fd = int(r0) +func Dup2(oldfd int, newfd int) (err error) { + _, _, e1 := Syscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0) if e1 != 0 { err = errnoErr(e1) } @@ -1495,13 +1499,9 @@ func EpollCreate(size int) (fd int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func faccessat(dirfd int, path string, mode uint32) (err error) { - var _p0 *byte - _p0, err = BytePtrFromString(path) - if err != nil { - return - } - _, _, e1 := Syscall(SYS_FACCESSAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(mode)) +func EpollCreate(size int) (fd int, err error) { + r0, _, e1 := RawSyscall(SYS_EPOLL_CREATE, uintptr(size), 0, 0) + fd = int(r0) if e1 != 0 { err = errnoErr(e1) } diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_386.go b/vendor/golang.org/x/sys/unix/ztypes_linux_386.go index e89bc6b..4c25003 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_386.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_386.go @@ -248,6 +248,13 @@ type RawSockaddrL2 struct { _ [1]byte } +type RawSockaddrRFCOMM struct { + Family uint16 + Bdaddr [6]uint8 + Channel uint8 + _ [1]byte +} + type RawSockaddrCAN struct { Family uint16 _ [2]byte @@ -401,6 +408,7 @@ const ( SizeofSockaddrNetlink = 0xc SizeofSockaddrHCI = 0x6 SizeofSockaddrL2 = 0xe + SizeofSockaddrRFCOMM = 0xa SizeofSockaddrCAN = 0x10 SizeofSockaddrALG = 0x58 SizeofSockaddrVM = 0x10 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go index d95372b..2e4d709 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go @@ -250,6 +250,13 @@ type RawSockaddrL2 struct { _ [1]byte } +type RawSockaddrRFCOMM struct { + Family uint16 + Bdaddr [6]uint8 + Channel uint8 + _ [1]byte +} + type RawSockaddrCAN struct { Family uint16 _ [2]byte @@ -405,6 +412,7 @@ const ( SizeofSockaddrNetlink = 0xc SizeofSockaddrHCI = 0x6 SizeofSockaddrL2 = 0xe + SizeofSockaddrRFCOMM = 0xa SizeofSockaddrCAN = 0x10 SizeofSockaddrALG = 0x58 SizeofSockaddrVM = 0x10 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go b/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go index 77875ba..bf38e5e 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go @@ -251,6 +251,13 @@ type RawSockaddrL2 struct { _ [1]byte } +type RawSockaddrRFCOMM struct { + Family uint16 + Bdaddr [6]uint8 + Channel uint8 + _ [1]byte +} + type RawSockaddrCAN struct { Family uint16 _ [2]byte @@ -404,6 +411,7 @@ const ( SizeofSockaddrNetlink = 0xc SizeofSockaddrHCI = 0x6 SizeofSockaddrL2 = 0xe + SizeofSockaddrRFCOMM = 0xa SizeofSockaddrCAN = 0x10 SizeofSockaddrALG = 0x58 SizeofSockaddrVM = 0x10 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go index 5a9df69..972c1b8 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go @@ -251,6 +251,13 @@ type RawSockaddrL2 struct { _ [1]byte } +type RawSockaddrRFCOMM struct { + Family uint16 + Bdaddr [6]uint8 + Channel uint8 + _ [1]byte +} + type RawSockaddrCAN struct { Family uint16 _ [2]byte @@ -406,6 +413,7 @@ const ( SizeofSockaddrNetlink = 0xc SizeofSockaddrHCI = 0x6 SizeofSockaddrL2 = 0xe + SizeofSockaddrRFCOMM = 0xa SizeofSockaddrCAN = 0x10 SizeofSockaddrALG = 0x58 SizeofSockaddrVM = 0x10 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go index dcb239d..783e70e 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go @@ -249,6 +249,13 @@ type RawSockaddrL2 struct { _ [1]byte } +type RawSockaddrRFCOMM struct { + Family uint16 + Bdaddr [6]uint8 + Channel uint8 + _ [1]byte +} + type RawSockaddrCAN struct { Family uint16 _ [2]byte @@ -402,6 +409,7 @@ const ( SizeofSockaddrNetlink = 0xc SizeofSockaddrHCI = 0x6 SizeofSockaddrL2 = 0xe + SizeofSockaddrRFCOMM = 0xa SizeofSockaddrCAN = 0x10 SizeofSockaddrALG = 0x58 SizeofSockaddrVM = 0x10 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go index 9cf85f7..5c6ea71 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go @@ -251,6 +251,13 @@ type RawSockaddrL2 struct { _ [1]byte } +type RawSockaddrRFCOMM struct { + Family uint16 + Bdaddr [6]uint8 + Channel uint8 + _ [1]byte +} + type RawSockaddrCAN struct { Family uint16 _ [2]byte @@ -406,6 +413,7 @@ const ( SizeofSockaddrNetlink = 0xc SizeofSockaddrHCI = 0x6 SizeofSockaddrL2 = 0xe + SizeofSockaddrRFCOMM = 0xa SizeofSockaddrCAN = 0x10 SizeofSockaddrALG = 0x58 SizeofSockaddrVM = 0x10 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go index 6fd66e7..93effc8 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go @@ -251,6 +251,13 @@ type RawSockaddrL2 struct { _ [1]byte } +type RawSockaddrRFCOMM struct { + Family uint16 + Bdaddr [6]uint8 + Channel uint8 + _ [1]byte +} + type RawSockaddrCAN struct { Family uint16 _ [2]byte @@ -406,6 +413,7 @@ const ( SizeofSockaddrNetlink = 0xc SizeofSockaddrHCI = 0x6 SizeofSockaddrL2 = 0xe + SizeofSockaddrRFCOMM = 0xa SizeofSockaddrCAN = 0x10 SizeofSockaddrALG = 0x58 SizeofSockaddrVM = 0x10 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go index faa5b3e..cc5ca24 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go @@ -249,6 +249,13 @@ type RawSockaddrL2 struct { _ [1]byte } +type RawSockaddrRFCOMM struct { + Family uint16 + Bdaddr [6]uint8 + Channel uint8 + _ [1]byte +} + type RawSockaddrCAN struct { Family uint16 _ [2]byte @@ -402,6 +409,7 @@ const ( SizeofSockaddrNetlink = 0xc SizeofSockaddrHCI = 0x6 SizeofSockaddrL2 = 0xe + SizeofSockaddrRFCOMM = 0xa SizeofSockaddrCAN = 0x10 SizeofSockaddrALG = 0x58 SizeofSockaddrVM = 0x10 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go index ad4c452..712f640 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go @@ -252,6 +252,13 @@ type RawSockaddrL2 struct { _ [1]byte } +type RawSockaddrRFCOMM struct { + Family uint16 + Bdaddr [6]uint8 + Channel uint8 + _ [1]byte +} + type RawSockaddrCAN struct { Family uint16 _ [2]byte @@ -407,6 +414,7 @@ const ( SizeofSockaddrNetlink = 0xc SizeofSockaddrHCI = 0x6 SizeofSockaddrL2 = 0xe + SizeofSockaddrRFCOMM = 0xa SizeofSockaddrCAN = 0x10 SizeofSockaddrALG = 0x58 SizeofSockaddrVM = 0x10 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go index 1fdb2f2..1be4532 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go @@ -252,6 +252,13 @@ type RawSockaddrL2 struct { _ [1]byte } +type RawSockaddrRFCOMM struct { + Family uint16 + Bdaddr [6]uint8 + Channel uint8 + _ [1]byte +} + type RawSockaddrCAN struct { Family uint16 _ [2]byte @@ -407,6 +414,7 @@ const ( SizeofSockaddrNetlink = 0xc SizeofSockaddrHCI = 0x6 SizeofSockaddrL2 = 0xe + SizeofSockaddrRFCOMM = 0xa SizeofSockaddrCAN = 0x10 SizeofSockaddrALG = 0x58 SizeofSockaddrVM = 0x10 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go b/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go index d32079d..932b655 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go @@ -250,6 +250,13 @@ type RawSockaddrL2 struct { _ [1]byte } +type RawSockaddrRFCOMM struct { + Family uint16 + Bdaddr [6]uint8 + Channel uint8 + _ [1]byte +} + type RawSockaddrCAN struct { Family uint16 _ [2]byte @@ -405,6 +412,7 @@ const ( SizeofSockaddrNetlink = 0xc SizeofSockaddrHCI = 0x6 SizeofSockaddrL2 = 0xe + SizeofSockaddrRFCOMM = 0xa SizeofSockaddrCAN = 0x10 SizeofSockaddrALG = 0x58 SizeofSockaddrVM = 0x10