main.go 7.64 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 27
	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"
	commands "github.com/jbenet/go-ipfs/core/commands2"
	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")

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

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

37
func main() {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
38 39 40 41 42 43 44 45
	err := run()
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

func run() error {
Matt Bell's avatar
Matt Bell committed
46 47
	handleInterrupt()

48
	args := os.Args[1:]
49 50
	req, root, err := createRequest(args)
	if err != nil {
51 52 53 54
		// when the error is errOutputHelp, just exit gracefully.
		if err == errHelpRequested {
			return nil
		}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
55 56 57
		return err
	}

58
	debug, _, err := req.Option("debug").Bool()
Brian Tiger Chow's avatar
Brian Tiger Chow committed
59 60 61 62 63 64
	if err != nil {
		return err
	}
	if debug {
		u.Debug = true
		u.SetAllLoggers(logging.DEBUG)
65
	}
66 67

	if u.Debug {
68
		stopProfilingFunc, err := startProfiling()
69
		if err != nil {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
70
			return err
71
		}
72
		defer stopProfilingFunc() // to be executed as late as possible
73
	}
74

Brian Tiger Chow's avatar
Brian Tiger Chow committed
75 76 77 78 79 80 81 82
	helpTextDisplayed, err := handleHelpOption(req, root)
	if err != nil {
		return err
	}
	if helpTextDisplayed {
		return nil
	}

83 84 85 86
	res, err := callCommand(req, root)
	if err != nil {
		return err
	}
87 88 89 90 91

	err = outputResponse(res, root)
	if err != nil {
		return err
	}
92

Brian Tiger Chow's avatar
Brian Tiger Chow committed
93
	return nil
94
}
95

96
func createRequest(args []string) (cmds.Request, *cmds.Command, error) {
97 98 99 100
	req, root, cmd, path, err := cmdsCli.Parse(args, Root, commands.Root)

	// handle parse error (which means the commandline input was wrong,
	// e.g. incorrect number of args, or nonexistent subcommand)
101
	if err != nil {
102
		return nil, nil, handleParseError(req, root, cmd, path, err)
103 104
	}

105
	configPath, err := getConfigRoot(req)
106
	if err != nil {
107
		return nil, nil, err
108 109 110 111
	}

	conf, err := getConfig(configPath)
	if err != nil {
112
		return nil, nil, err
113 114 115 116 117
	}
	ctx := req.Context()
	ctx.ConfigRoot = configPath
	ctx.Config = conf

118 119
	// if no encoding was specified by user, default to plaintext encoding
	// (if command doesn't support plaintext, use JSON instead)
120
	if !req.Option("encoding").Found() {
121
		if req.Command().Marshallers != nil && req.Command().Marshallers[cmds.Text] != nil {
122 123 124 125 126 127
			req.SetOption("encoding", cmds.Text)
		} else {
			req.SetOption("encoding", cmds.JSON)
		}
	}

128
	return req, root, nil
129 130
}

131
func handleParseError(req cmds.Request, root *cmds.Command, cmd *cmds.Command, path []string, parseError error) error {
132 133 134 135
	var longHelp, shortHelp bool

	if req != nil {
		// help and h are defined in the root. We expect them to be bool.
136
		var err error
137 138 139 140 141 142 143 144
		longHelp, _, err = req.Option("help").Bool()
		if err != nil {
			return err
		}
		shortHelp, _, err = req.Option("h").Bool()
		if err != nil {
			return err
		}
145 146 147

		// override the error to avoid signaling other issues.
		parseError = errHelpRequested
148 149 150 151 152 153 154
	}

	// if the -help flag wasn't specified, show the error message
	// or if a path was returned (user specified a valid subcommand), show the error message
	// (this means there was an option or argument error)
	if path != nil && len(path) > 0 {
		if !longHelp && !shortHelp {
155
			fmt.Printf(errorFormat, parseError)
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
		}
	}

	if cmd == nil {
		root = commands.Root
	}

	// show the long help text if the -help flag was specified or we are at the root command
	// otherwise, show short help text
	helpFunc := cmdsCli.ShortHelp
	if longHelp || len(path) == 0 {
		helpFunc = cmdsCli.LongHelp
	}

	htErr := helpFunc("ipfs", root, path, os.Stdout)
	if htErr != nil {
		fmt.Println(htErr)
	}
174
	return parseError
175 176
}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
177
func handleHelpOption(req cmds.Request, root *cmds.Command) (helpTextDisplayed bool, err error) {
178
	longHelp, _, err := req.Option("help").Bool()
179
	if err != nil {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
180
		return false, err
181
	}
182
	shortHelp, _, err := req.Option("h").Bool()
183 184 185 186
	if err != nil {
		return false, err
	}
	if !longHelp && !shortHelp {
187 188
		return false, nil
	}
189 190 191 192 193 194
	helpFunc := cmdsCli.ShortHelp
	if longHelp || len(req.Path()) == 0 {
		helpFunc = cmdsCli.LongHelp
	}

	err = helpFunc("ipfs", root, req.Path(), os.Stdout)
195 196
	if err != nil {
		return false, err
197
	}
198
	return true, nil
199
}
200

201
func callCommand(req cmds.Request, root *cmds.Command) (cmds.Response, error) {
202
	var res cmds.Response
203

204
	if root == Root { // TODO explain what it means when root == Root
205
		res = root.Call(req)
206 207

	} else {
208
		local, found, err := req.Option("local").Bool()
209
		if err != nil {
210
			return nil, err
211
		}
212

213 214
		remote := !found || !local

215
		log.Info("Checking if daemon is running...")
216
		if remote && daemon.Locked(req.Context().ConfigRoot) {
217 218
			addr, err := ma.NewMultiaddr(req.Context().Config.Addresses.API)
			if err != nil {
219
				return nil, err
220 221 222 223
			}

			_, host, err := manet.DialArgs(addr)
			if err != nil {
224
				return nil, err
225 226 227 228 229
			}

			client := cmdsHttp.NewClient(host)

			res, err = client.Send(req)
230
			if err != nil {
231
				return nil, err
232 233 234
			}

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

243
			res = root.Call(req)
244 245 246
		}
	}

247
	return res, nil
248 249
}

250
func outputResponse(res cmds.Response, root *cmds.Command) error {
251
	if res.Error() != nil {
252
		fmt.Printf(errorFormat, res.Error().Error())
253

254
		if res.Error().Code != cmds.ErrClient {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
255 256
			// TODO does ErrClient mean "error the client should see" or "this
			// is an error caused by the user ie. user error"?
257 258 259 260
			return res.Error()
		}

		// if this is a client error, we try to display help text
261
		if res.Error().Code == cmds.ErrClient {
262
			err := cmdsCli.ShortHelp("ipfs", root, res.Request().Path(), os.Stdout)
263
			if err != nil {
264
				fmt.Println(err)
265
			}
266 267
		}

268
		emptyErr := errors.New("") // already displayed error text
269
		return emptyErr
270 271
	}

272
	out, err := res.Reader()
273
	if err != nil {
274
		return err
275
	}
276 277

	io.Copy(os.Stdout, out)
278
	return nil
279 280 281
}

func getConfigRoot(req cmds.Request) (string, error) {
282
	configOpt, found, err := req.Option("config").String()
283 284 285
	if err != nil {
		return "", err
	}
286
	if found && configOpt != "" {
287
		return configOpt, nil
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
	}

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

306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
// 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
}

328 329 330
func writeHeapProfileToFile() error {
	mprof, err := os.Create(heapProfile)
	if err != nil {
331
		return err
332
	}
333
	defer mprof.Close() // _after_ writing the heap profile
334 335
	return pprof.WriteHeapProfile(mprof)
}
336

Matt Bell's avatar
Matt Bell committed
337 338 339 340 341 342 343 344
// 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...")
345
			os.Exit(0)
Matt Bell's avatar
Matt Bell committed
346 347 348
		}
	}()
}