external.go 1.62 KB
Newer Older
Jeromy's avatar
Jeromy committed
1 2 3 4 5 6 7 8 9 10
package commands

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"

Jakub Sztandera's avatar
Jakub Sztandera committed
11
	cmds "github.com/ipfs/go-ipfs-cmds"
Jeromy's avatar
Jeromy committed
12 13
)

14
func ExternalBinary(instructions string) *cmds.Command {
Jeromy's avatar
Jeromy committed
15
	return &cmds.Command{
Steven Allen's avatar
Steven Allen committed
16 17
		Arguments: []cmds.Argument{
			cmds.StringArg("args", false, true, "Arguments for subcommand."),
Jeromy's avatar
Jeromy committed
18 19
		},
		External: true,
20
		NoRemote: true,
Overbool's avatar
Overbool committed
21 22
		Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
			binname := strings.Join(append([]string{"ipfs"}, req.Path...), "-")
Jeromy's avatar
Jeromy committed
23 24 25
			_, err := exec.LookPath(binname)
			if err != nil {
				// special case for '--help' on uninstalled binaries.
Overbool's avatar
Overbool committed
26
				for _, arg := range req.Arguments {
Jeromy's avatar
Jeromy committed
27 28 29
					if arg == "--help" || arg == "-h" {
						buf := new(bytes.Buffer)
						fmt.Fprintf(buf, "%s is an 'external' command.\n", binname)
30
						fmt.Fprintf(buf, "It does not currently appear to be installed.\n")
31
						fmt.Fprintf(buf, "%s\n", instructions)
Overbool's avatar
Overbool committed
32
						return res.Emit(buf)
Jeromy's avatar
Jeromy committed
33 34 35
					}
				}

Overbool's avatar
Overbool committed
36
				return fmt.Errorf("%s not installed", binname)
Jeromy's avatar
Jeromy committed
37 38 39 40
			}

			r, w := io.Pipe()

Overbool's avatar
Overbool committed
41
			cmd := exec.Command(binname, req.Arguments...)
Jeromy's avatar
Jeromy committed
42 43 44 45 46 47 48 49

			// TODO: make commands lib be able to pass stdin through daemon
			//cmd.Stdin = req.Stdin()
			cmd.Stdin = io.LimitReader(nil, 0)
			cmd.Stdout = w
			cmd.Stderr = w

			// setup env of child program
Overbool's avatar
Overbool committed
50
			osenv := os.Environ()
Jeromy's avatar
Jeromy committed
51

Overbool's avatar
Overbool committed
52
			cmd.Env = osenv
Jeromy's avatar
Jeromy committed
53 54 55

			err = cmd.Start()
			if err != nil {
Overbool's avatar
Overbool committed
56
				return fmt.Errorf("failed to start subcommand: %s", err)
Jeromy's avatar
Jeromy committed
57 58
			}

Overbool's avatar
Overbool committed
59
			errC := make(chan error)
Jeromy's avatar
Jeromy committed
60 61

			go func() {
Overbool's avatar
Overbool committed
62 63
				var err error
				defer func() { errC <- err }()
Jeromy's avatar
Jeromy committed
64 65 66
				err = cmd.Wait()
				w.Close()
			}()
Overbool's avatar
Overbool committed
67 68 69 70 71 72 73

			err = res.Emit(r)
			if err != nil {
				return err
			}

			return <-errC
Jeromy's avatar
Jeromy committed
74 75 76
		},
	}
}