package shell
import (
bolt
daemonmod
mathmod
pathmod
)
const (
daemonWaitLoops = 100
daemonWaitPerLoop = 10 * time.Millisecond
)
type daemonStatus int
const (
daemonOK daemonStatus = iota
sockfileMissing
sockfileOtherError
connectionShutdown
connectionOtherError
daemonInvalidDB
daemonOutdated
)
const (
daemonWontWorkMsg = "Daemon-related functions will likely not work."
connectionShutdownFmt = "Socket file %s exists but is not responding to request. This is likely due to abnormal shutdown of the daemon. Going to remove socket file and re-spawn a daemon.\n"
)
var errInvalidDB = errors.New("daemon reported that database is invalid. If you upgraded Elvish from a pre-0.10 version, you need to upgrade your database by following instructions in https://github.com/elves/upgrade-db-for-0.10/")
func ( io.Writer, Paths, bool) *eval.Evaler {
:= eval.NewEvaler()
.SetLibDir(.LibDir)
.AddModule("math", mathmod.Ns)
.AddModule("path", pathmod.Ns)
.AddModule("platform", platform.Ns)
.AddModule("re", re.Ns)
.AddModule("str", str.Ns)
.AddModule("file", file.Ns)
if unix.ExposeUnixNs {
.AddModule("unix", unix.Ns)
}
if && .Sock != "" && .Db != "" {
:= &daemon.SpawnConfig{
RunDir: .RunDir,
BinPath: .Bin,
DbPath: .Db,
SockPath: .Sock,
}
, := connectToDaemon(, )
if != nil {
fmt.Fprintln(, "Cannot connect to daemon:", )
fmt.Fprintln(, daemonWontWorkMsg)
}
.SetDaemonClient()
.AddModule("store", store.Ns())
.AddModule("daemon", daemonmod.Ns(, ))
}
return
}
func ( io.Writer, *eval.Evaler) {
:= .DaemonClient()
if != nil {
:= .Close()
if != nil {
fmt.Fprintln(,
"warning: failed to close connection to daemon:", )
}
}
}
func ( io.Writer, *daemon.SpawnConfig) (daemon.Client, error) {
:= .SockPath
:= daemon.NewClient()
, := detectDaemon(, )
:= false
switch {
case daemonOK:
case sockfileMissing:
= true
case sockfileOtherError:
return , fmt.Errorf("socket file %s inaccessible: %v", , )
case connectionShutdown:
fmt.Fprintf(, connectionShutdownFmt, )
:= os.Remove()
if != nil {
return , fmt.Errorf("failed to remove socket file: %v", )
}
= true
case connectionOtherError:
return , fmt.Errorf("unexpected RPC error on socket %s: %v", , )
case daemonInvalidDB:
return , errInvalidDB
case daemonOutdated:
fmt.Fprintln(, "Daemon is outdated; going to kill old daemon and re-spawn")
:= killDaemon()
if != nil {
return , fmt.Errorf("failed to kill old daemon: %v", )
}
= true
default:
return , fmt.Errorf("code bug: unknown daemon status %d", )
}
if ! {
return , nil
}
= daemon.Spawn()
if != nil {
return , fmt.Errorf("failed to spawn daemon: %v", )
}
logger.Println("Spawned daemon")
for := 0; <= daemonWaitLoops; ++ {
.ResetConn()
, := detectDaemon(, )
switch {
case daemonOK:
return , nil
case sockfileMissing:
case sockfileOtherError:
return , fmt.Errorf("socket file %s inaccessible: %v", , )
case connectionShutdown:
case connectionOtherError:
return , fmt.Errorf("unexpected RPC error on socket %s: %v", , )
case daemonInvalidDB:
return , errInvalidDB
case daemonOutdated:
return , fmt.Errorf("code bug: newly spawned daemon is outdated")
default:
return , fmt.Errorf("code bug: unknown daemon status %d", )
}
time.Sleep(daemonWaitPerLoop)
}
return , fmt.Errorf("daemon unreachable after waiting for %s", daemonWaitLoops*daemonWaitPerLoop)
}
func ( string, daemon.Client) (daemonStatus, error) {
, := os.Stat()
if != nil {
if os.IsNotExist() {
return sockfileMissing,
}
return sockfileOtherError,
}
, := .Version()
if != nil {
switch {
case == rpc.ErrShutdown:
return connectionShutdown,
case .Error() == bolt.ErrInvalid.Error():
return daemonInvalidDB,
default:
return connectionOtherError,
}
}
if < daemon.Version {
return daemonOutdated, nil
}
return daemonOK, nil
}
func ( daemon.Client) error {
, := .Pid()
if != nil {
return fmt.Errorf("cannot get pid of daemon: %v", )
}
, := os.FindProcess()
if != nil {
return fmt.Errorf("cannot find daemon process (pid=%d): %v", , )
}
return .Signal(os.Interrupt)
}