diff --git a/commands/cli/parse.go b/commands/cli/parse.go index f758429b8..ea12881ec 100644 --- a/commands/cli/parse.go +++ b/commands/cli/parse.go @@ -204,6 +204,13 @@ func parseOpts(args []string, root *cmds.Command) ( if err != nil { return } + + // If we've come across an external binary call, pass all the remaining + // arguments on to it + if cmd.External { + stringVals = append(stringVals, args[i+1:]...) + return + } } else { stringVals = append(stringVals, arg) } diff --git a/commands/command.go b/commands/command.go index 538976748..febfb3358 100644 --- a/commands/command.go +++ b/commands/command.go @@ -9,9 +9,12 @@ output to the user, including text, JSON, and XML marshallers. package commands import ( + "bytes" "errors" "fmt" "io" + "os" + "os/exec" "reflect" "strings" @@ -59,6 +62,10 @@ type Command struct { Marshalers map[EncodingType]Marshaler Helptext HelpText + // External denotes that a command is actually an external binary. + // fewer checks and validations will be performed on such commands. + External bool + // Type describes the type of the output of the Command's Run Function. // In precise terms, the value of Type is an instance of the return type of // the Run Function. @@ -262,3 +269,67 @@ func checkArgValue(v string, found bool, def Argument) error { func ClientError(msg string) error { return &Error{Code: ErrClient, Message: msg} } + +func ExternalBinary() *Command { + return &Command{ + Arguments: []Argument{ + StringArg("args", false, true, "arguments for subcommand"), + }, + External: true, + Run: func(req Request, res Response) { + binname := strings.Join(append([]string{"ipfs"}, req.Path()...), "-") + _, err := exec.LookPath(binname) + if err != nil { + // special case for '--help' on uninstalled binaries. + if req.Arguments()[0] == "--help" { + buf := new(bytes.Buffer) + fmt.Fprintf(buf, "%s is an 'external' command.\n", binname) + fmt.Fprintf(buf, "it does not currently appear to be installated.\n") + fmt.Fprintf(buf, "please refer to the ipfs documentation for instructions\n") + res.SetOutput(buf) + return + } + + res.SetError(fmt.Errorf("%s not installed."), ErrNormal) + return + } + + r, w := io.Pipe() + + cmd := exec.Command(binname, req.Arguments()...) + + // 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 + env := os.Environ() + + nd, err := req.InvocContext().GetNode() + if err == nil { + env = append(env, fmt.Sprintf("IPFS_ONLINE=%t", nd.OnlineMode())) + } + + cmd.Env = env + + err = cmd.Start() + if err != nil { + res.SetError(fmt.Errorf("failed to start subcommand: %s", err), ErrNormal) + return + } + + res.SetOutput(r) + + go func() { + err = cmd.Wait() + if err != nil { + res.SetError(err, ErrNormal) + } + + w.Close() + }() + }, + } +} diff --git a/core/commands/root.go b/core/commands/root.go index ce6721750..e3c206e05 100644 --- a/core/commands/root.go +++ b/core/commands/root.go @@ -111,7 +111,7 @@ var rootSubcommands = map[string]*cmds.Command{ "tar": TarCmd, "tour": tourCmd, "file": unixfs.UnixFSCmd, - "update": UpdateCmd, + "update": cmds.ExternalBinary(), "version": VersionCmd, "bitswap": BitswapCmd, }