2016-02-21 05:51:55 +08:00
|
|
|
// Elvish is an experimental Unix shell. It tries to incorporate a powerful
|
2014-02-08 19:26:06 +08:00
|
|
|
// programming language with an extensible, friendly user interface.
|
2013-06-14 17:22:55 +08:00
|
|
|
package main
|
|
|
|
|
2017-05-29 00:59:50 +08:00
|
|
|
// This package sets up the basic environment and calls the appropriate
|
|
|
|
// "subprogram", one of the daemon, the terminal interface, or the web
|
|
|
|
// interface.
|
|
|
|
|
2017-02-28 10:03:22 +08:00
|
|
|
import (
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
2017-02-28 11:44:30 +08:00
|
|
|
"log"
|
2017-02-28 10:03:22 +08:00
|
|
|
"os"
|
2017-02-28 11:44:30 +08:00
|
|
|
"path/filepath"
|
2017-02-28 10:03:22 +08:00
|
|
|
"runtime/pprof"
|
2017-02-28 11:44:30 +08:00
|
|
|
"strconv"
|
|
|
|
"syscall"
|
2017-02-28 10:03:22 +08:00
|
|
|
|
2017-02-28 11:44:30 +08:00
|
|
|
"github.com/elves/elvish/daemon"
|
2017-05-29 00:59:50 +08:00
|
|
|
"github.com/elves/elvish/eval"
|
2017-02-28 10:03:22 +08:00
|
|
|
"github.com/elves/elvish/shell"
|
2017-05-29 00:59:50 +08:00
|
|
|
"github.com/elves/elvish/store"
|
2017-02-28 10:03:22 +08:00
|
|
|
"github.com/elves/elvish/util"
|
2017-05-29 07:54:34 +08:00
|
|
|
"github.com/elves/elvish/web"
|
2017-02-28 10:03:22 +08:00
|
|
|
)
|
|
|
|
|
2017-02-28 11:44:30 +08:00
|
|
|
// closeFd is used in syscall.ProcAttr.Files to signify closing a fd.
|
|
|
|
const closeFd = ^uintptr(0)
|
2017-02-28 10:03:22 +08:00
|
|
|
|
2017-05-29 07:54:34 +08:00
|
|
|
// defaultPort is the default port on which the web interface runs. The number
|
|
|
|
// is chosen because it resembles "elvi".
|
|
|
|
const defaultWebPort = 3171
|
|
|
|
|
2017-02-28 10:03:22 +08:00
|
|
|
var (
|
2017-02-28 11:44:30 +08:00
|
|
|
// Flags handled in this package, or common to shell and daemon.
|
2017-05-29 00:59:50 +08:00
|
|
|
help = flag.Bool("help", false, "show usage help and quit")
|
|
|
|
|
2017-02-28 11:44:30 +08:00
|
|
|
logpath = flag.String("log", "", "a file to write debug log to")
|
|
|
|
cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
|
|
|
|
dbpath = flag.String("db", "", "path to the database")
|
|
|
|
sockpath = flag.String("sock", "", "path to the daemon socket")
|
|
|
|
|
2017-05-29 00:59:50 +08:00
|
|
|
isdaemon = flag.Bool("daemon", false, "run daemon instead of shell")
|
|
|
|
isweb = flag.Bool("web", false, "run backend of web interface")
|
2017-05-29 07:54:34 +08:00
|
|
|
webport = flag.Int("port", defaultWebPort, "the port of the web backend")
|
2017-05-29 00:59:50 +08:00
|
|
|
|
|
|
|
// Flags for shell and web.
|
2017-02-28 11:44:30 +08:00
|
|
|
cmd = flag.Bool("c", false, "take first argument as a command to execute")
|
|
|
|
|
|
|
|
// Flags for daemon.
|
|
|
|
forked = flag.Int("forked", 0, "how many times the daemon has forked")
|
|
|
|
binpath = flag.String("bin", "", "path to the elvish binary")
|
2017-02-28 10:03:22 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
func usage() {
|
|
|
|
fmt.Println("usage: elvish [flags] [script]")
|
|
|
|
fmt.Println("flags:")
|
|
|
|
flag.PrintDefaults()
|
|
|
|
}
|
2016-02-21 19:00:02 +08:00
|
|
|
|
2016-02-06 03:16:22 +08:00
|
|
|
func main() {
|
2017-05-29 00:59:50 +08:00
|
|
|
// This is needed for defers to be honored.
|
|
|
|
ret := 0
|
|
|
|
defer os.Exit(ret)
|
|
|
|
|
2017-02-28 10:03:22 +08:00
|
|
|
flag.Usage = usage
|
|
|
|
flag.Parse()
|
|
|
|
args := flag.Args()
|
|
|
|
|
|
|
|
if *help {
|
|
|
|
usage()
|
2017-05-29 00:59:50 +08:00
|
|
|
return
|
2017-02-28 10:03:22 +08:00
|
|
|
}
|
|
|
|
|
2017-05-29 00:59:50 +08:00
|
|
|
// The daemon takes no argument.
|
|
|
|
if *isdaemon && len(args) > 0 {
|
2017-02-28 10:03:22 +08:00
|
|
|
usage()
|
2017-05-29 00:59:50 +08:00
|
|
|
ret = 2
|
|
|
|
return
|
2017-02-28 10:03:22 +08:00
|
|
|
}
|
|
|
|
|
2017-02-28 11:44:30 +08:00
|
|
|
if *logpath != "" {
|
|
|
|
err := util.SetOutputFile(*logpath)
|
2017-02-28 10:03:22 +08:00
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if *cpuprofile != "" {
|
|
|
|
f, err := os.Create(*cpuprofile)
|
|
|
|
if err != nil {
|
2017-02-28 11:44:30 +08:00
|
|
|
log.Fatal(err)
|
2017-02-28 10:03:22 +08:00
|
|
|
}
|
|
|
|
pprof.StartCPUProfile(f)
|
|
|
|
defer pprof.StopCPUProfile()
|
|
|
|
}
|
|
|
|
|
2017-02-28 11:44:30 +08:00
|
|
|
if *isdaemon {
|
2017-05-29 00:59:50 +08:00
|
|
|
ret = doDaemon()
|
2017-02-28 11:44:30 +08:00
|
|
|
} else {
|
2017-05-29 00:59:50 +08:00
|
|
|
// Set up Evaler and Store. This is needed for both the terminal and web
|
|
|
|
// interfaces.
|
|
|
|
ev, st := newEvalerAndStore(*dbpath)
|
|
|
|
defer func() {
|
|
|
|
err := st.Close()
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintln(os.Stderr, "failed to close database:", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
if *isweb {
|
2017-05-29 07:54:34 +08:00
|
|
|
if *cmd {
|
|
|
|
fmt.Fprintln(os.Stderr, "-c -web not yet supported")
|
|
|
|
ret = 2
|
|
|
|
return
|
|
|
|
}
|
|
|
|
w := web.NewWeb(ev, st, *webport)
|
|
|
|
ret = w.Run(args)
|
2017-05-29 00:59:50 +08:00
|
|
|
} else {
|
|
|
|
sh := shell.NewShell(ev, st, *cmd)
|
|
|
|
ret = sh.Run(args)
|
2017-02-28 11:44:30 +08:00
|
|
|
}
|
2017-05-29 00:59:50 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func newEvalerAndStore(db string) (*eval.Evaler, *store.Store) {
|
|
|
|
dataDir, err := store.EnsureDataDir()
|
2017-02-28 11:44:30 +08:00
|
|
|
|
2017-05-29 00:59:50 +08:00
|
|
|
var st *store.Store
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintln(os.Stderr, "Warning: cannot create data dir ~/.elvish")
|
|
|
|
} else {
|
|
|
|
if db == "" {
|
|
|
|
db = dataDir + "/db"
|
|
|
|
}
|
|
|
|
st, err = store.NewStore(db)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintln(os.Stderr, "Warning: cannot connect to store:", err)
|
|
|
|
}
|
2017-02-28 11:44:30 +08:00
|
|
|
}
|
2017-05-29 00:59:50 +08:00
|
|
|
|
|
|
|
return eval.NewEvaler(st, dataDir), st
|
2017-02-28 11:44:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func doDaemon() int {
|
|
|
|
switch *forked {
|
|
|
|
case 0:
|
|
|
|
errored := false
|
|
|
|
absify := func(f string, s *string) {
|
|
|
|
if *s == "" {
|
|
|
|
log.Println("flag", f, "is required for daemon")
|
|
|
|
errored = true
|
|
|
|
return
|
|
|
|
}
|
|
|
|
p, err := filepath.Abs(*s)
|
|
|
|
if err != nil {
|
|
|
|
log.Println("abs:", err)
|
|
|
|
errored = true
|
|
|
|
} else {
|
|
|
|
*s = p
|
|
|
|
}
|
|
|
|
}
|
|
|
|
absify("-bin", binpath)
|
|
|
|
absify("-db", dbpath)
|
|
|
|
absify("-sock", sockpath)
|
2017-02-28 22:15:22 +08:00
|
|
|
absify("-log", logpath)
|
2017-02-28 11:44:30 +08:00
|
|
|
if errored {
|
|
|
|
return 2
|
|
|
|
}
|
2017-02-28 10:03:22 +08:00
|
|
|
|
2017-02-28 11:44:30 +08:00
|
|
|
syscall.Umask(0077)
|
|
|
|
return forkDaemon(
|
|
|
|
&syscall.ProcAttr{
|
|
|
|
// cd to /
|
|
|
|
Dir: "/",
|
|
|
|
// empty environment
|
|
|
|
Env: nil,
|
|
|
|
// inherit stderr only for logging
|
|
|
|
Files: []uintptr{closeFd, closeFd, 2},
|
|
|
|
Sys: &syscall.SysProcAttr{Setsid: true},
|
|
|
|
})
|
|
|
|
case 1:
|
|
|
|
return forkDaemon(
|
|
|
|
&syscall.ProcAttr{
|
|
|
|
Files: []uintptr{closeFd, closeFd, 2},
|
|
|
|
})
|
|
|
|
case 2:
|
|
|
|
d := daemon.New(*sockpath, *dbpath)
|
|
|
|
return d.Main()
|
|
|
|
default:
|
|
|
|
return 2
|
2017-02-28 10:03:22 +08:00
|
|
|
}
|
2017-02-28 11:44:30 +08:00
|
|
|
}
|
2017-02-28 10:03:22 +08:00
|
|
|
|
2017-02-28 22:15:22 +08:00
|
|
|
func forkDaemon(attr *syscall.ProcAttr) int {
|
|
|
|
_, err := syscall.ForkExec(*binpath, []string{
|
|
|
|
*binpath,
|
2017-02-28 11:44:30 +08:00
|
|
|
"-daemon",
|
2017-02-28 22:15:22 +08:00
|
|
|
"-forked", strconv.Itoa(*forked + 1),
|
|
|
|
"-bin", *binpath,
|
|
|
|
"-db", *dbpath,
|
|
|
|
"-sock", *sockpath,
|
|
|
|
"-log", *logpath,
|
2017-02-28 11:44:30 +08:00
|
|
|
}, attr)
|
|
|
|
if err != nil {
|
|
|
|
log.Println("fork/exec:", err)
|
|
|
|
return 2
|
|
|
|
}
|
|
|
|
return 0
|
2014-05-25 17:55:53 +08:00
|
|
|
}
|