main.go 10.9 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
	cmds "github.com/jbenet/go-ipfs/commands"
	cmdsCli "github.com/jbenet/go-ipfs/commands/cli"
	cmdsHttp "github.com/jbenet/go-ipfs/commands/http"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
18 19
	config "github.com/jbenet/go-ipfs/config"
	core "github.com/jbenet/go-ipfs/core"
20
	daemon "github.com/jbenet/go-ipfs/daemon2"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
21
	updates "github.com/jbenet/go-ipfs/updates"
22
	u "github.com/jbenet/go-ipfs/util"
23
	"github.com/jbenet/go-ipfs/util/debugerror"
24 25 26 27 28
)

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

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

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

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
38 39 40 41 42 43 44 45 46 47 48 49
type cmdInvocation struct {
	path []string
	cmd  *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
50
func main() {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
	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
		}

Matt Bell's avatar
Matt Bell committed
68
		helpFunc("ipfs", Root, invoc.path, os.Stderr)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
69 70 71
	}

	// parse the commandline into a command invocation
72
	parseErr := invoc.Parse(os.Args[1:])
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
73 74 75

	// BEFORE handling the parse error, if we have enough information
	// AND the user requested help, print it out and exit
76
	if invoc.req != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
77 78 79 80 81 82 83 84 85 86 87
		longH, shortH, err := invoc.requestedHelp()
		if err != nil {
			printErr(err)
			os.Exit(1)
		}
		if longH || shortH {
			printHelp(longH)
			os.Exit(0)
		}
	}

88 89 90 91 92 93 94 95 96
	// here we handle the cases where
	// - commands with no Run func are invoked directly.
	// - the main command is invoked.
	if invoc.cmd == nil || invoc.cmd.Run == nil {
		printHelp(false)
		os.Exit(0)
	}

	// ok now handle parse error (which means cli input was wrong,
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
97
	// e.g. incorrect number of args, or nonexistent subcommand)
98 99
	if parseErr != nil {
		printErr(parseErr)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
100 101 102 103 104 105 106

		// 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
107 108
		os.Exit(1)
	}
Matt Bell's avatar
Matt Bell committed
109

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
110 111
	// ok, finally, run the command invocation.
	output, err := invoc.Run()
112
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
113 114 115 116 117
		printErr(err)

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

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
122 123 124 125 126 127 128 129 130
	// 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
131
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
132
		return nil, err
Brian Tiger Chow's avatar
Brian Tiger Chow committed
133
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
134
	if debug || u.GetenvBool("DEBUG") {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
135 136
		u.Debug = true
		u.SetAllLoggers(logging.DEBUG)
137
	}
138

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
139 140
	// if debugging, let's profile.
	// TODO maybe change this to its own option... profiling makes it slower.
141
	if u.Debug {
142
		stopProfilingFunc, err := startProfiling()
143
		if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
144
			return nil, err
145
		}
146
		defer stopProfilingFunc() // to be executed as late as possible
147
	}
148

Matt Bell's avatar
Matt Bell committed
149
	res, err := callCommand(i.req, Root)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
150
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
151
		return nil, err
152
	}
153

154 155 156 157
	if err := res.Error(); err != nil {
		return nil, err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
158
	return res.Reader()
159
}
160

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
161 162
func (i *cmdInvocation) Parse(args []string) error {
	var err error
163

164
	i.req, i.cmd, i.path, err = cmdsCli.Parse(args, os.Stdin, Root)
165
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
166
		return err
167 168
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
169
	configPath, err := getConfigRoot(i.req)
170
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
171
		return err
172
	}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
173
	log.Debugf("config path is %s", configPath)
174

175
	// this sets up the function that will initialize the config lazily.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
176
	ctx := i.req.Context()
177
	ctx.ConfigRoot = configPath
178
	ctx.LoadConfig = loadConfig
179

180 181
	// 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
182
	if !i.req.Option("encoding").Found() {
183
		if i.req.Command().Marshalers != nil && i.req.Command().Marshalers[cmds.Text] != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
184
			i.req.SetOption("encoding", cmds.Text)
185
		} else {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
186
			i.req.SetOption("encoding", cmds.JSON)
187 188 189
		}
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
190
	return nil
191 192
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
193 194
func (i *cmdInvocation) requestedHelp() (short bool, long bool, err error) {
	longHelp, _, err := i.req.Option("help").Bool()
195
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
196
		return false, false, err
197
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
198
	shortHelp, _, err := i.req.Option("h").Bool()
199
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
200
		return false, false, err
201
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
202
	return longHelp, shortHelp, nil
203
}
204

205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
func callPreCommandHooks(details cmdDetails, req cmds.Request, root *cmds.Command) error {

	log.Debug("Calling pre-command hooks...")

	// some hooks only run when the command is executed locally
	daemon, err := commandShouldRunOnDaemon(details, req, root)
	if err != nil {
		return err
	}

	// check for updates when 1) commands is going to be run locally, 2) the
	// command does not initialize the config, and 3) the command does not
	// pre-empt updates
	if !daemon && !details.initializesConfig && !details.preemptsUpdates {

		log.Debug("Calling hook: Check for updates")

		cfg, err := req.Context().GetConfig()
		if err != nil {
			return err
		}
		// Check for updates and potentially install one.
		if err := updates.CliCheckForUpdates(cfg, req.Context().ConfigRoot); err != nil {
			return err
		}
	}

	return nil
}

235
func callCommand(req cmds.Request, root *cmds.Command) (cmds.Response, error) {
236
	var res cmds.Response
237

238 239 240 241 242 243
	details, err := commandDetails(req.Path(), root)
	if err != nil {
		return nil, err
	}

	useDaemon, err := commandShouldRunOnDaemon(*details, req, root)
244 245 246
	if err != nil {
		return nil, err
	}
247

248
	err = callPreCommandHooks(*details, req, root)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
249 250 251
	if err != nil {
		return nil, err
	}
252

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
253
	if useDaemon {
254

255 256 257 258 259
		cfg, err := req.Context().GetConfig()
		if err != nil {
			return nil, err
		}

260 261 262 263
		addr, err := ma.NewMultiaddr(cfg.Addresses.API)
		if err != nil {
			return nil, err
		}
264

265
		log.Infof("Executing command on daemon running at %s", addr)
266 267 268 269
		_, host, err := manet.DialArgs(addr)
		if err != nil {
			return nil, err
		}
270

271
		client := cmdsHttp.NewClient(host)
272

273 274 275 276
		res, err = client.Send(req)
		if err != nil {
			return nil, err
		}
277

278
	} else {
279
		log.Info("Executing command locally")
280

281 282 283 284 285
		// this sets up the function that will initialize the node
		// this is so that we can construct the node lazily.
		ctx := req.Context()
		ctx.ConstructNode = func() (*core.IpfsNode, error) {
			cfg, err := ctx.GetConfig()
286
			if err != nil {
287
				return nil, err
288
			}
289 290 291 292 293
			return core.NewIpfsNode(cfg, false)
		}

		// Okay!!!!! NOW we can call the command.
		res = root.Call(req)
294

295 296 297 298 299 300
		// let's not forget teardown. If a node was initialized, we must close it.
		// Note that this means the underlying req.Context().Node variable is exposed.
		// this is gross, and should be changed when we extract out the exec Context.
		node := req.Context().NodeWithoutConstructing()
		if node != nil {
			node.Close()
301 302
		}
	}
303
	return res, nil
304 305
}

306 307 308 309 310
// commandDetails returns a command's details for the command given by |path|
// within the |root| command tree.
//
// Returns an error if the command is not found in the Command tree.
func commandDetails(path []string, root *cmds.Command) (*cmdDetails, error) {
311 312 313 314 315 316 317
	var details cmdDetails
	// find the last command in path that has a cmdDetailsMap entry
	cmd := root
	for _, cmp := range path {
		var found bool
		cmd, found = cmd.Subcommands[cmp]
		if !found {
318
			return nil, debugerror.Errorf("subcommand %s should be in root", cmp)
319
		}
320

321 322 323
		if cmdDetails, found := cmdDetailsMap[cmd]; found {
			details = cmdDetails
		}
324
	}
325
	log.Debugf("cmd perms for +%v: %s", path, details.String())
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
	return &details, nil
}

// commandShouldRunOnDaemon determines, from commmand details, whether a
// command ought to be executed on an IPFS daemon.
//
// It returns true if the command should be executed on a daemon and false if
// it should be executed on a client. It returns an error if the command must
// NOT be executed on either.
func commandShouldRunOnDaemon(details cmdDetails, req cmds.Request, root *cmds.Command) (bool, error) {
	path := req.Path()
	// root command.
	if len(path) < 1 {
		return false, nil
	}
341 342 343 344 345

	if details.cannotRunOnClient && details.cannotRunOnDaemon {
		return false, fmt.Errorf("command disabled: %s", path[0])
	}

346
	if details.doesNotUseRepo && details.canRunOnClient() {
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
		return false, nil
	}

	// at this point need to know whether daemon is running. we defer
	// to this point so that some commands dont open files unnecessarily.
	daemonLocked := daemon.Locked(req.Context().ConfigRoot)
	log.Info("Daemon is running.")

	if daemonLocked {

		if details.cannotRunOnDaemon {
			e := "ipfs daemon is running. please stop it to run this command"
			return false, cmds.ClientError(e)
		}

		return true, nil
	}

	if details.cannotRunOnClient {
		return false, cmds.ClientError("must run on the ipfs daemon")
	}

	return false, nil
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
372
func isClientError(err error) bool {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
373 374 375 376

	// Somewhat suprisingly, the pointer cast fails to recognize commands.Error
	// passed as values, so we check both.

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
377
	// cast to cmds.Error
Brian Tiger Chow's avatar
Brian Tiger Chow committed
378 379 380 381 382
	switch e := err.(type) {
	case *cmds.Error:
		return e.Code == cmds.ErrClient
	case cmds.Error:
		return e.Code == cmds.ErrClient
383
	}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
384
	return false
385 386 387
}

func getConfigRoot(req cmds.Request) (string, error) {
388
	configOpt, found, err := req.Option("config").String()
389 390 391
	if err != nil {
		return "", err
	}
392
	if found && configOpt != "" {
393
		return configOpt, nil
394 395 396 397 398 399 400 401 402
	}

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

403
func loadConfig(path string) (*config.Config, error) {
404 405 406 407 408 409 410
	configFile, err := config.Filename(path)
	if err != nil {
		return nil, err
	}

	return config.Load(configFile)
}
411

412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433
// 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
}

434 435 436
func writeHeapProfileToFile() error {
	mprof, err := os.Create(heapProfile)
	if err != nil {
437
		return err
438
	}
439
	defer mprof.Close() // _after_ writing the heap profile
440 441
	return pprof.WriteHeapProfile(mprof)
}
442

Matt Bell's avatar
Matt Bell committed
443 444 445 446 447 448 449 450
// 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...")
451
			os.Exit(0)
Matt Bell's avatar
Matt Bell committed
452 453 454
		}
	}()
}