main.go 7.21 KB
Newer Older
1 2 3
package main

import (
4
	"errors"
5 6 7
	"fmt"
	"io"
	"os"
Matt Bell's avatar
Matt Bell committed
8
	"os/signal"
9 10
	"runtime/pprof"

11
	logging "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-logging"
12 13 14
	ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
	manet "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr/net"

15 16 17 18 19 20 21 22 23 24 25 26
	cmds "github.com/jbenet/go-ipfs/commands"
	cmdsCli "github.com/jbenet/go-ipfs/commands/cli"
	cmdsHttp "github.com/jbenet/go-ipfs/commands/http"
	"github.com/jbenet/go-ipfs/config"
	"github.com/jbenet/go-ipfs/core"
	daemon "github.com/jbenet/go-ipfs/daemon2"
	u "github.com/jbenet/go-ipfs/util"
)

// log is the command logger
var log = u.Logger("cmd/ipfs")

27 28 29
// signal to output help
var errHelpRequested = errors.New("Help Requested")

30
const (
31 32
	cpuProfile  = "ipfs.cpuprof"
	heapProfile = "ipfs.memprof"
33 34
	errorFormat = "ERROR: %v\n\n"
)
35

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
36 37 38 39 40 41 42 43 44 45 46 47 48
type cmdInvocation struct {
	path []string
	cmd  *cmds.Command
	root *cmds.Command
	req  cmds.Request
}

// main roadmap:
// - parse the commandline to get a cmdInvocation
// - if user requests, help, print it and exit.
// - run the command invocation
// - output the response
// - if anything fails, print error, maybe with help
49
func main() {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
	var invoc cmdInvocation
	var err error

	// we'll call this local helper to output errors.
	// this is so we control how to print errors in one place.
	printErr := func(err error) {
		fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
	}

	// this is a local helper to print out help text.
	// there's some considerations that this makes easier.
	printHelp := func(long bool) {
		helpFunc := cmdsCli.ShortHelp
		if long {
			helpFunc = cmdsCli.LongHelp
		}

		helpFunc("ipfs", invoc.root, invoc.path, os.Stderr)
	}

	// parse the commandline into a command invocation
	err = invoc.Parse(os.Args[1:])

	// BEFORE handling the parse error, if we have enough information
	// AND the user requested help, print it out and exit
	if invoc.cmd != nil {
		longH, shortH, err := invoc.requestedHelp()
		if err != nil {
			printErr(err)
			os.Exit(1)
		}
		if longH || shortH {
			printHelp(longH)
			os.Exit(0)
		}
	}

	// ok now handle handle parse error (which means cli input was wrong,
	// e.g. incorrect number of args, or nonexistent subcommand)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
89
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
90 91 92 93 94 95 96 97
		printErr(err)

		// this was a user error, print help.
		if invoc.cmd != nil {
			// we need a newline space.
			fmt.Fprintf(os.Stderr, "\n")
			printHelp(false)
		}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
98 99
		os.Exit(1)
	}
Matt Bell's avatar
Matt Bell committed
100

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
101 102
	// ok, finally, run the command invocation.
	output, err := invoc.Run()
103
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
104 105 106 107 108
		printErr(err)

		// if this error was a client error, print short help too.
		if isClientError(err) {
			printHelp(false)
109
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
110
		os.Exit(1)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
111 112
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
113 114 115 116 117 118 119 120 121
	// everything went better than expected :)
	io.Copy(os.Stdout, output)
}

func (i *cmdInvocation) Run() (output io.Reader, err error) {
	handleInterrupt()

	// check if user wants to debug. option OR env var.
	debug, _, err := i.req.Option("debug").Bool()
Brian Tiger Chow's avatar
Brian Tiger Chow committed
122
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
123
		return nil, err
Brian Tiger Chow's avatar
Brian Tiger Chow committed
124
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
125
	if debug || u.GetenvBool("DEBUG") {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
126 127
		u.Debug = true
		u.SetAllLoggers(logging.DEBUG)
128
	}
129

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
130 131
	// if debugging, let's profile.
	// TODO maybe change this to its own option... profiling makes it slower.
132
	if u.Debug {
133
		stopProfilingFunc, err := startProfiling()
134
		if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
135
			return nil, err
136
		}
137
		defer stopProfilingFunc() // to be executed as late as possible
138
	}
139

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
140
	res, err := callCommand(i.req, i.root)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
141
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
142
		return nil, err
143
	}
144

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
145
	return res.Reader()
146
}
147

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
148 149
func (i *cmdInvocation) Parse(args []string) error {
	var err error
150

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
151
	i.req, i.root, i.cmd, i.path, err = cmdsCli.Parse(args, Root)
152
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
153
		return err
154 155
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
156
	configPath, err := getConfigRoot(i.req)
157
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
158
		return err
159 160 161 162
	}

	conf, err := getConfig(configPath)
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
163
		return err
164
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
165
	ctx := i.req.Context()
166 167 168
	ctx.ConfigRoot = configPath
	ctx.Config = conf

169 170
	// if no encoding was specified by user, default to plaintext encoding
	// (if command doesn't support plaintext, use JSON instead)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
171 172 173
	if !i.req.Option("encoding").Found() {
		if i.req.Command().Marshallers != nil && i.req.Command().Marshallers[cmds.Text] != nil {
			i.req.SetOption("encoding", cmds.Text)
174
		} else {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
175
			i.req.SetOption("encoding", cmds.JSON)
176 177 178
		}
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
179
	return nil
180 181
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
182 183
func (i *cmdInvocation) requestedHelp() (short bool, long bool, err error) {
	longHelp, _, err := i.req.Option("help").Bool()
184
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
185
		return false, false, err
186
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
187
	shortHelp, _, err := i.req.Option("h").Bool()
188
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
189
		return false, false, err
190
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
191
	return longHelp, shortHelp, nil
192
}
193

194
func callCommand(req cmds.Request, root *cmds.Command) (cmds.Response, error) {
195
	var res cmds.Response
196

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
197 198 199
	// TODO explain what it means when root == Root
	// @mappum o/
	if root == Root {
200
		res = root.Call(req)
201 202

	} else {
203
		local, found, err := req.Option("local").Bool()
204
		if err != nil {
205
			return nil, err
206
		}
207

208 209
		remote := !found || !local

210
		log.Info("Checking if daemon is running...")
211
		if remote && daemon.Locked(req.Context().ConfigRoot) {
212 213
			addr, err := ma.NewMultiaddr(req.Context().Config.Addresses.API)
			if err != nil {
214
				return nil, err
215 216 217 218
			}

			_, host, err := manet.DialArgs(addr)
			if err != nil {
219
				return nil, err
220 221 222 223 224
			}

			client := cmdsHttp.NewClient(host)

			res, err = client.Send(req)
225
			if err != nil {
226
				return nil, err
227 228 229
			}

		} else {
230
			log.Info("Executing command locally: daemon not running")
231
			node, err := core.NewIpfsNode(req.Context().Config, false)
232
			if err != nil {
233
				return nil, err
234
			}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
235
			defer node.Close()
236
			req.Context().Node = node
237

238
			res = root.Call(req)
239 240 241
		}
	}

242
	return res, nil
243 244
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
245 246 247 248 249
func isClientError(err error) bool {
	// cast to cmds.Error
	cmdErr, ok := err.(*cmds.Error)
	if !ok {
		return false
250 251
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
252 253 254 255
	// here we handle the case where commands with
	// no Run func are invoked directly. As help requests.
	if err == cmds.ErrNotCallable {
		return true
256
	}
257

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
258
	return cmdErr.Code == cmds.ErrClient
259 260 261
}

func getConfigRoot(req cmds.Request) (string, error) {
262
	configOpt, found, err := req.Option("config").String()
263 264 265
	if err != nil {
		return "", err
	}
266
	if found && configOpt != "" {
267
		return configOpt, nil
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
	}

	configPath, err := config.PathRoot()
	if err != nil {
		return "", err
	}
	return configPath, nil
}

func getConfig(path string) (*config.Config, error) {
	configFile, err := config.Filename(path)
	if err != nil {
		return nil, err
	}

	return config.Load(configFile)
}
285

286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
// startProfiling begins CPU profiling and returns a `stop` function to be
// executed as late as possible. The stop function captures the memprofile.
func startProfiling() (func(), error) {

	// start CPU profiling as early as possible
	ofi, err := os.Create(cpuProfile)
	if err != nil {
		return nil, err
	}
	pprof.StartCPUProfile(ofi)

	stopProfiling := func() {
		pprof.StopCPUProfile()
		defer ofi.Close() // captured by the closure
		err := writeHeapProfileToFile()
		if err != nil {
			log.Critical(err)
		}
	}
	return stopProfiling, nil
}

308 309 310
func writeHeapProfileToFile() error {
	mprof, err := os.Create(heapProfile)
	if err != nil {
311
		return err
312
	}
313
	defer mprof.Close() // _after_ writing the heap profile
314 315
	return pprof.WriteHeapProfile(mprof)
}
316

Matt Bell's avatar
Matt Bell committed
317 318 319 320 321 322 323 324
// listen for and handle SIGTERM
func handleInterrupt() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)

	go func() {
		for _ = range c {
			log.Info("Received interrupt signal, terminating...")
325
			os.Exit(0)
Matt Bell's avatar
Matt Bell committed
326 327 328
		}
	}()
}