main.go 7.42 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 215
		remote := !found || !local

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

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

			client := cmdsHttp.NewClient(host)

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

		} else {
234
			node, err := core.NewIpfsNode(req.Context().Config, false)
235
			if err != nil {
236
				return nil, err
237
			}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
238
			defer node.Close()
239
			req.Context().Node = node
240

241
			res = root.Call(req)
242 243 244
		}
	}

245
	return res, nil
246 247
}

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

252 253 254 255 256
		if res.Error().Code != cmds.ErrClient {
			return res.Error()
		}

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

264
		emptyErr := errors.New("") // already displayed error text
265
		return emptyErr
266 267
	}

268
	out, err := res.Reader()
269
	if err != nil {
270
		return err
271
	}
272 273

	io.Copy(os.Stdout, out)
274
	return nil
275 276 277
}

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

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

302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
// 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
}

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

Matt Bell's avatar
Matt Bell committed
333 334 335 336 337 338 339 340
// 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...")
341
			os.Exit(0)
Matt Bell's avatar
Matt Bell committed
342 343 344
		}
	}()
}