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

import (
Brian Tiger Chow's avatar
Brian Tiger Chow committed
4
	"errors"
5 6
	"fmt"
	"io"
7
	"math/rand"
8
	"os"
Matt Bell's avatar
Matt Bell committed
9
	"os/signal"
10
	"runtime"
11
	"runtime/pprof"
12
	"syscall"
13
	"time"
14

15
	ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
16
	manet "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net"
17

18
	context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
19 20 21
	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
22 23
	config "github.com/jbenet/go-ipfs/config"
	core "github.com/jbenet/go-ipfs/core"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
24
	daemon "github.com/jbenet/go-ipfs/core/daemon"
25
	repo "github.com/jbenet/go-ipfs/repo"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
26
	updates "github.com/jbenet/go-ipfs/updates"
27
	u "github.com/jbenet/go-ipfs/util"
Brian Tiger Chow's avatar
Brian Tiger Chow committed
28
	"github.com/jbenet/go-ipfs/util/debugerror"
Brian Tiger Chow's avatar
Brian Tiger Chow committed
29
	eventlog "github.com/jbenet/go-ipfs/util/eventlog"
30 31 32
)

// log is the command logger
33
var log = eventlog.Logger("cmd/ipfs")
34

35 36 37
// signal to output help
var errHelpRequested = errors.New("Help Requested")

38
const (
39 40
	cpuProfile  = "ipfs.cpuprof"
	heapProfile = "ipfs.memprof"
41 42
	errorFormat = "ERROR: %v\n\n"
)
43

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
44 45 46 47
type cmdInvocation struct {
	path []string
	cmd  *cmds.Command
	req  cmds.Request
48
	node *core.IpfsNode
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
49 50 51 52 53 54 55 56
}

// 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
57
func main() {
58 59
	rand.Seed(time.Now().UnixNano())
	runtime.GOMAXPROCS(3) // FIXME rm arbitrary choice for n
60
	ctx := eventlog.ContextWithLoggable(context.Background(), eventlog.Uuid("session"))
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
61
	var err error
62 63
	var invoc cmdInvocation
	defer invoc.close()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
64 65 66 67 68 69 70 71 72

	// 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.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
73
	printHelp := func(long bool, w io.Writer) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
74 75 76 77 78
		helpFunc := cmdsCli.ShortHelp
		if long {
			helpFunc = cmdsCli.LongHelp
		}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
79
		helpFunc("ipfs", Root, invoc.path, w)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
80 81 82
	}

	// parse the commandline into a command invocation
83
	parseErr := invoc.Parse(ctx, os.Args[1:])
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
84 85 86

	// BEFORE handling the parse error, if we have enough information
	// AND the user requested help, print it out and exit
87
	if invoc.req != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
88 89 90 91 92 93
		longH, shortH, err := invoc.requestedHelp()
		if err != nil {
			printErr(err)
			os.Exit(1)
		}
		if longH || shortH {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
94
			printHelp(longH, os.Stdout)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
95 96 97 98
			os.Exit(0)
		}
	}

99 100 101 102
	// 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 {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
103
		printHelp(false, os.Stdout)
104 105 106 107
		os.Exit(0)
	}

	// ok now handle parse error (which means cli input was wrong,
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
108
	// e.g. incorrect number of args, or nonexistent subcommand)
109 110
	if parseErr != nil {
		printErr(parseErr)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
111 112 113 114 115

		// this was a user error, print help.
		if invoc.cmd != nil {
			// we need a newline space.
			fmt.Fprintf(os.Stderr, "\n")
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
116
			printHelp(false, os.Stderr)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
117
		}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
118 119
		os.Exit(1)
	}
Matt Bell's avatar
Matt Bell committed
120

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
121
	// ok, finally, run the command invocation.
122
	output, err := invoc.Run(ctx)
123
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
124 125 126 127
		printErr(err)

		// if this error was a client error, print short help too.
		if isClientError(err) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
128
			printHelp(false, os.Stderr)
129
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
130
		os.Exit(1)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
131 132
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
133 134 135 136
	// everything went better than expected :)
	io.Copy(os.Stdout, output)
}

137
func (i *cmdInvocation) Run(ctx context.Context) (output io.Reader, err error) {
138 139
	// setup our global interrupt handler.
	i.setupInterruptHandler()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
140 141 142

	// 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
143
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
144
		return nil, err
Brian Tiger Chow's avatar
Brian Tiger Chow committed
145
	}
146
	if debug || u.GetenvBool("DEBUG") || os.Getenv("IPFS_LOGGING") == "debug" {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
147
		u.Debug = true
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
148
		u.SetDebugLogging()
149
	}
150

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
151 152
	// if debugging, let's profile.
	// TODO maybe change this to its own option... profiling makes it slower.
153
	if u.Debug {
154
		stopProfilingFunc, err := startProfiling()
155
		if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
156
			return nil, err
157
		}
158
		defer stopProfilingFunc() // to be executed as late as possible
159
	}
160

161
	res, err := callCommand(ctx, i.req, Root)
Brian Tiger Chow's avatar
Brian Tiger Chow committed
162
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
163
		return nil, err
164
	}
165

166 167 168 169
	if err := res.Error(); err != nil {
		return nil, err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
170
	return res.Reader()
171
}
172

Brian Tiger Chow's avatar
Brian Tiger Chow committed
173
func (i *cmdInvocation) constructNodeFunc(ctx context.Context) func() (*core.IpfsNode, error) {
174 175 176 177
	return func() (*core.IpfsNode, error) {
		if i.req == nil {
			return nil, errors.New("constructing node without a request")
		}
178

179 180 181 182
		cmdctx := i.req.Context()
		if cmdctx == nil {
			return nil, errors.New("constructing node without a request context")
		}
183

184 185 186 187
		cfg, err := cmdctx.GetConfig()
		if err != nil {
			return nil, fmt.Errorf("constructing node without a config: %s", err)
		}
188

189 190
		// ok everything is good. set it on the invocation (for ownership)
		// and return it.
191
		i.node, err = core.NewIPFSNode(ctx, core.Standard(cfg, cmdctx.Online))
192 193
		return i.node, err
	}
194 195 196 197 198 199 200 201 202 203 204 205
}

func (i *cmdInvocation) close() {
	// 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.
	if i.node != nil {
		log.Info("Shutting down node...")
		i.node.Close()
	}
}

206
func (i *cmdInvocation) Parse(ctx context.Context, args []string) error {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
207
	var err error
208

209
	i.req, i.cmd, i.path, err = cmdsCli.Parse(args, os.Stdin, Root)
210
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
211
		return err
212 213
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
214
	configPath, err := getConfigRoot(i.req)
215
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
216
		return err
217
	}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
218
	log.Debugf("config path is %s", configPath)
219

220
	// this sets up the function that will initialize the config lazily.
221 222 223
	cmdctx := i.req.Context()
	cmdctx.ConfigRoot = configPath
	cmdctx.LoadConfig = loadConfig
224 225
	// this sets up the function that will initialize the node
	// this is so that we can construct the node lazily.
Brian Tiger Chow's avatar
Brian Tiger Chow committed
226
	cmdctx.ConstructNode = i.constructNodeFunc(ctx)
227

228 229
	// 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
230
	if !i.req.Option("encoding").Found() {
231
		if i.req.Command().Marshalers != nil && i.req.Command().Marshalers[cmds.Text] != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
232
			i.req.SetOption("encoding", cmds.Text)
233
		} else {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
234
			i.req.SetOption("encoding", cmds.JSON)
235 236 237
		}
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
238
	return nil
239 240
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
241 242
func (i *cmdInvocation) requestedHelp() (short bool, long bool, err error) {
	longHelp, _, err := i.req.Option("help").Bool()
243
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
244
		return false, false, err
245
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
246
	shortHelp, _, err := i.req.Option("h").Bool()
247
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
248
		return false, false, err
249
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
250
	return longHelp, shortHelp, nil
251
}
252

253
func callPreCommandHooks(ctx context.Context, details cmdDetails, req cmds.Request, root *cmds.Command) error {
254

255
	log.Event(ctx, "callPreCommandHooks", &details)
256 257 258
	log.Debug("Calling pre-command hooks...")

	// some hooks only run when the command is executed locally
259
	daemon, err := commandShouldRunOnDaemon(details, req, root)
260 261 262 263 264 265 266
	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
267
	if !daemon && details.usesConfigAsInput() && details.doesNotPreemptAutoUpdate() {
268 269 270 271 272 273 274 275 276 277 278 279 280

		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
		}
	}

281 282
	// When the upcoming command may use the config and repo, we know it's safe
	// for the log config hook to touch the config/repo
283
	if repo.IsInitialized(req.Context().ConfigRoot) {
284 285 286 287 288
		log.Debug("Calling hook: Configure Event Logger")
		cfg, err := req.Context().GetConfig()
		if err != nil {
			return err
		}
289
		repo.ConfigureEventLogger(cfg.Logs)
290 291
	}

292 293 294
	return nil
}

295
func callCommand(ctx context.Context, req cmds.Request, root *cmds.Command) (cmds.Response, error) {
296
	var res cmds.Response
297

298 299 300 301 302
	details, err := commandDetails(req.Path(), root)
	if err != nil {
		return nil, err
	}

303
	log.Info("looking for running daemon...")
304
	useDaemon, err := commandShouldRunOnDaemon(*details, req, root)
305 306 307
	if err != nil {
		return nil, err
	}
308

309
	err = callPreCommandHooks(ctx, *details, req, root)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
310 311 312
	if err != nil {
		return nil, err
	}
313

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
314
	if useDaemon {
315

316 317 318 319 320
		cfg, err := req.Context().GetConfig()
		if err != nil {
			return nil, err
		}

321 322 323 324
		addr, err := ma.NewMultiaddr(cfg.Addresses.API)
		if err != nil {
			return nil, err
		}
325

326
		log.Infof("Executing command on daemon running at %s", addr)
327 328 329 330
		_, host, err := manet.DialArgs(addr)
		if err != nil {
			return nil, err
		}
331

332
		client := cmdsHttp.NewClient(host)
333

334 335 336 337
		res, err = client.Send(req)
		if err != nil {
			return nil, err
		}
338

339
	} else {
340
		log.Info("Executing command locally")
341

342 343
		// Okay!!!!! NOW we can call the command.
		res = root.Call(req)
344 345

	}
346
	return res, nil
347 348
}

349 350 351 352 353
// 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) {
354 355 356 357 358 359 360
	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 {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
361
			return nil, debugerror.Errorf("subcommand %s should be in root", cmp)
362
		}
363

364 365 366
		if cmdDetails, found := cmdDetailsMap[cmd]; found {
			details = cmdDetails
		}
367
	}
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
	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
	}
383 384 385 386 387

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

388
	if details.doesNotUseRepo && details.canRunOnClient() {
389 390 391 392 393 394 395 396 397
		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)

	if daemonLocked {

398 399
		log.Info("a daemon is running...")

400 401 402 403 404 405 406 407 408 409 410 411 412 413 414
		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
415
func isClientError(err error) bool {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
416 417 418 419

	// 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
420
	// cast to cmds.Error
Brian Tiger Chow's avatar
Brian Tiger Chow committed
421 422 423 424 425
	switch e := err.(type) {
	case *cmds.Error:
		return e.Code == cmds.ErrClient
	case cmds.Error:
		return e.Code == cmds.ErrClient
426
	}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
427
	return false
428 429 430
}

func getConfigRoot(req cmds.Request) (string, error) {
431
	configOpt, found, err := req.Option("config").String()
432 433 434
	if err != nil {
		return "", err
	}
435
	if found && configOpt != "" {
436
		return configOpt, nil
437 438 439 440 441 442 443 444 445
	}

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

446
func loadConfig(path string) (*config.Config, error) {
447 448 449 450 451 452 453
	configFile, err := config.Filename(path)
	if err != nil {
		return nil, err
	}

	return config.Load(configFile)
}
454

455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
// 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
}

477 478 479
func writeHeapProfileToFile() error {
	mprof, err := os.Create(heapProfile)
	if err != nil {
480
		return err
481
	}
482
	defer mprof.Close() // _after_ writing the heap profile
483 484
	return pprof.WriteHeapProfile(mprof)
}
485

Matt Bell's avatar
Matt Bell committed
486
// listen for and handle SIGTERM
487 488 489 490
func (i *cmdInvocation) setupInterruptHandler() {

	ctx := i.req.Context()
	sig := allInterruptSignals()
Matt Bell's avatar
Matt Bell committed
491 492

	go func() {
493
		// first time, try to shut down.
494

495 496
		// loop because we may be
		for count := 0; ; count++ {
497 498 499
			<-sig

			n, err := ctx.GetNode()
500 501 502 503
			if err != nil {
				log.Error(err)
				log.Critical("Received interrupt signal, terminating...")
				os.Exit(-1)
504 505
			}

506 507 508 509 510 511 512 513 514 515 516 517
			switch count {
			case 0:
				log.Critical("Received interrupt signal, shutting down...")
				go func() {
					n.Close()
					log.Info("Gracefully shut down.")
				}()

			default:
				log.Critical("Received another interrupt before graceful shutdown, terminating...")
				os.Exit(-1)
			}
Matt Bell's avatar
Matt Bell committed
518 519 520
		}
	}()
}
521 522 523 524

func allInterruptSignals() chan os.Signal {
	sigc := make(chan os.Signal, 1)
	signal.Notify(sigc, syscall.SIGHUP, syscall.SIGINT,
Jeromy's avatar
Jeromy committed
525
		syscall.SIGTERM)
526 527
	return sigc
}