mirror of
https://github.com/ipfs/kubo.git
synced 2026-02-25 12:27:43 +08:00
349 lines
7.6 KiB
Go
349 lines
7.6 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/signal"
|
|
"runtime/pprof"
|
|
|
|
logging "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-logging"
|
|
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"
|
|
|
|
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")
|
|
|
|
// signal to output help
|
|
var errHelpRequested = errors.New("Help Requested")
|
|
|
|
const (
|
|
cpuProfile = "ipfs.cpuprof"
|
|
heapProfile = "ipfs.memprof"
|
|
errorFormat = "ERROR: %v\n\n"
|
|
)
|
|
|
|
func main() {
|
|
err := run()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func run() error {
|
|
handleInterrupt()
|
|
|
|
args := os.Args[1:]
|
|
req, root, err := createRequest(args)
|
|
if err != nil {
|
|
// when the error is errOutputHelp, just exit gracefully.
|
|
if err == errHelpRequested {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
debug, _, err := req.Option("debug").Bool()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if debug {
|
|
u.Debug = true
|
|
u.SetAllLoggers(logging.DEBUG)
|
|
}
|
|
|
|
if u.Debug {
|
|
stopProfilingFunc, err := startProfiling()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer stopProfilingFunc() // to be executed as late as possible
|
|
}
|
|
|
|
helpTextDisplayed, err := handleHelpOption(req, root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if helpTextDisplayed {
|
|
return nil
|
|
}
|
|
|
|
res, err := callCommand(req, root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = outputResponse(res, root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func createRequest(args []string) (cmds.Request, *cmds.Command, error) {
|
|
req, root, cmd, path, err := cmdsCli.Parse(args, Root)
|
|
|
|
// handle parse error (which means the commandline input was wrong,
|
|
// e.g. incorrect number of args, or nonexistent subcommand)
|
|
if err != nil {
|
|
return nil, nil, handleParseError(req, root, cmd, path, err)
|
|
}
|
|
|
|
configPath, err := getConfigRoot(req)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
conf, err := getConfig(configPath)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
ctx := req.Context()
|
|
ctx.ConfigRoot = configPath
|
|
ctx.Config = conf
|
|
|
|
// if no encoding was specified by user, default to plaintext encoding
|
|
// (if command doesn't support plaintext, use JSON instead)
|
|
if !req.Option("encoding").Found() {
|
|
if req.Command().Marshallers != nil && req.Command().Marshallers[cmds.Text] != nil {
|
|
req.SetOption("encoding", cmds.Text)
|
|
} else {
|
|
req.SetOption("encoding", cmds.JSON)
|
|
}
|
|
}
|
|
|
|
return req, root, nil
|
|
}
|
|
|
|
func handleParseError(req cmds.Request, root *cmds.Command, cmd *cmds.Command, path []string, parseError error) error {
|
|
var longHelp, shortHelp bool
|
|
|
|
if req != nil {
|
|
// help and h are defined in the root. We expect them to be bool.
|
|
var err error
|
|
longHelp, _, err = req.Option("help").Bool()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
shortHelp, _, err = req.Option("h").Bool()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// override the error to avoid signaling other issues.
|
|
parseError = errHelpRequested
|
|
}
|
|
|
|
// 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 {
|
|
fmt.Printf(errorFormat, parseError)
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
return parseError
|
|
}
|
|
|
|
func handleHelpOption(req cmds.Request, root *cmds.Command) (helpTextDisplayed bool, err error) {
|
|
longHelp, _, err := req.Option("help").Bool()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
shortHelp, _, err := req.Option("h").Bool()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if !longHelp && !shortHelp {
|
|
return false, nil
|
|
}
|
|
helpFunc := cmdsCli.ShortHelp
|
|
if longHelp || len(req.Path()) == 0 {
|
|
helpFunc = cmdsCli.LongHelp
|
|
}
|
|
|
|
err = helpFunc("ipfs", root, req.Path(), os.Stdout)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func callCommand(req cmds.Request, root *cmds.Command) (cmds.Response, error) {
|
|
var res cmds.Response
|
|
|
|
if root == Root { // TODO explain what it means when root == Root
|
|
res = root.Call(req)
|
|
|
|
} else {
|
|
local, found, err := req.Option("local").Bool()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
remote := !found || !local
|
|
|
|
log.Info("Checking if daemon is running...")
|
|
if remote && daemon.Locked(req.Context().ConfigRoot) {
|
|
addr, err := ma.NewMultiaddr(req.Context().Config.Addresses.API)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, host, err := manet.DialArgs(addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
client := cmdsHttp.NewClient(host)
|
|
|
|
res, err = client.Send(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
} else {
|
|
log.Info("Executing command locally: daemon not running")
|
|
node, err := core.NewIpfsNode(req.Context().Config, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer node.Close()
|
|
req.Context().Node = node
|
|
|
|
res = root.Call(req)
|
|
}
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
func outputResponse(res cmds.Response, root *cmds.Command) error {
|
|
if res.Error() != nil {
|
|
fmt.Printf(errorFormat, res.Error().Error())
|
|
|
|
if res.Error().Code != cmds.ErrClient {
|
|
// TODO does ErrClient mean "error the client should see" or "this
|
|
// is an error caused by the user ie. user error"?
|
|
return res.Error()
|
|
}
|
|
|
|
// if this is a client error, we try to display help text
|
|
if res.Error().Code == cmds.ErrClient {
|
|
err := cmdsCli.ShortHelp("ipfs", root, res.Request().Path(), os.Stdout)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
}
|
|
|
|
emptyErr := errors.New("") // already displayed error text
|
|
return emptyErr
|
|
}
|
|
|
|
out, err := res.Reader()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
io.Copy(os.Stdout, out)
|
|
return nil
|
|
}
|
|
|
|
func getConfigRoot(req cmds.Request) (string, error) {
|
|
configOpt, found, err := req.Option("config").String()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if found && configOpt != "" {
|
|
return configOpt, nil
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
func writeHeapProfileToFile() error {
|
|
mprof, err := os.Create(heapProfile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer mprof.Close() // _after_ writing the heap profile
|
|
return pprof.WriteHeapProfile(mprof)
|
|
}
|
|
|
|
// 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...")
|
|
os.Exit(0)
|
|
}
|
|
}()
|
|
}
|