Merge pkg/daemon/client into pkg/daemon.

Also merge and rename files to make the client/server separation clearer.
This commit is contained in:
Qi Xiao 2021-09-30 23:21:43 +01:00
parent 025728b35d
commit 8e117a2875
13 changed files with 113 additions and 137 deletions

View File

@ -9,7 +9,6 @@ import (
"src.elv.sh/pkg/buildinfo"
"src.elv.sh/pkg/daemon"
"src.elv.sh/pkg/daemon/client"
"src.elv.sh/pkg/prog"
"src.elv.sh/pkg/shell"
)
@ -19,5 +18,5 @@ func main() {
[3]*os.File{os.Stdin, os.Stdout, os.Stderr}, os.Args,
prog.Composite(
buildinfo.Program, daemon.Program,
shell.Program{ActivateDaemon: client.Activate})))
shell.Program{ActivateDaemon: daemon.Activate})))
}

View File

@ -1,4 +1,4 @@
package client
package daemon
import (
"errors"
@ -6,12 +6,14 @@ import (
"io"
"net/rpc"
"os"
"path/filepath"
"time"
bolt "go.etcd.io/bbolt"
"src.elv.sh/pkg/daemon/daemondefs"
"src.elv.sh/pkg/daemon/internal/api"
"src.elv.sh/pkg/fsutil"
)
const (
@ -75,7 +77,7 @@ func Activate(stderr io.Writer, spawnCfg *daemondefs.SpawnConfig) (daemondefs.Cl
return cl, nil
}
err = Spawn(spawnCfg)
err = spawn(spawnCfg)
if err != nil {
return cl, fmt.Errorf("failed to spawn daemon: %v", err)
}
@ -145,3 +147,65 @@ func killDaemon(cl daemondefs.Client) error {
}
return process.Signal(os.Interrupt)
}
// Spawns a daemon process in the background by invoking BinPath, passing
// BinPath, DbPath and SockPath as command-line arguments after resolving them
// to absolute paths. The daemon log file is created in RunDir, and the stdout
// and stderr of the daemon is redirected to the log file.
//
// A suitable ProcAttr is chosen depending on the OS and makes sure that the
// daemon is detached from the current terminal, so that it is not affected by
// I/O or signals in the current terminal and keeps running after the current
// process quits.
func spawn(cfg *daemondefs.SpawnConfig) error {
binPath, err := os.Executable()
if err != nil {
return errors.New("cannot find elvish: " + err.Error())
}
dbPath, err := abs("DbPath", cfg.DbPath)
if err != nil {
return err
}
sockPath, err := abs("SockPath", cfg.SockPath)
if err != nil {
return err
}
args := []string{
binPath,
"-daemon",
"-db", dbPath,
"-sock", sockPath,
}
// The daemon does not read any input; open DevNull and use it for stdin. We
// could also just close the stdin, but on Unix that would make the first
// file opened by the daemon take FD 0.
in, err := os.OpenFile(os.DevNull, os.O_RDONLY, 0)
if err != nil {
return err
}
defer in.Close()
out, err := fsutil.ClaimFile(cfg.RunDir, "daemon-*.log")
if err != nil {
return err
}
defer out.Close()
procattrs := procAttrForSpawn([]*os.File{in, out, out})
_, err = os.StartProcess(binPath, args, procattrs)
return err
}
func abs(name, path string) (string, error) {
if path == "" {
return "", fmt.Errorf("%s is required for spawning daemon", name)
}
absPath, err := filepath.Abs(path)
if err != nil {
return "", fmt.Errorf("cannot resolve %s to absolute path: %s", name, err)
}
return absPath, nil
}

View File

@ -1,4 +1,4 @@
package client
package daemon
import (
"errors"

View File

@ -1,73 +0,0 @@
package client
import (
"errors"
"fmt"
"os"
"path/filepath"
"src.elv.sh/pkg/daemon/daemondefs"
"src.elv.sh/pkg/fsutil"
)
// Spawn spawns a daemon process in the background by invoking BinPath, passing
// BinPath, DbPath and SockPath as command-line arguments after resolving them
// to absolute paths. The daemon log file is created in RunDir, and the stdout
// and stderr of the daemon is redirected to the log file.
//
// A suitable ProcAttr is chosen depending on the OS and makes sure that the
// daemon is detached from the current terminal, so that it is not affected by
// I/O or signals in the current terminal and keeps running after the current
// process quits.
func Spawn(cfg *daemondefs.SpawnConfig) error {
binPath, err := os.Executable()
if err != nil {
return errors.New("cannot find elvish: " + err.Error())
}
dbPath, err := abs("DbPath", cfg.DbPath)
if err != nil {
return err
}
sockPath, err := abs("SockPath", cfg.SockPath)
if err != nil {
return err
}
args := []string{
binPath,
"-daemon",
"-db", dbPath,
"-sock", sockPath,
}
// The daemon does not read any input; open DevNull and use it for stdin. We
// could also just close the stdin, but on Unix that would make the first
// file opened by the daemon take FD 0.
in, err := os.OpenFile(os.DevNull, os.O_RDONLY, 0)
if err != nil {
return err
}
defer in.Close()
out, err := fsutil.ClaimFile(cfg.RunDir, "daemon-*.log")
if err != nil {
return err
}
defer out.Close()
procattrs := procAttrForSpawn([]*os.File{in, out, out})
_, err = os.StartProcess(binPath, args, procattrs)
return err
}
func abs(name, path string) (string, error) {
if path == "" {
return "", fmt.Errorf("%s is required for spawning daemon", name)
}
absPath, err := filepath.Abs(path)
if err != nil {
return "", fmt.Errorf("cannot resolve %s to absolute path: %s", name, err)
}
return absPath, nil
}

View File

@ -1,34 +0,0 @@
// Package daemon implements a service for mediating access to the data store,
// and its client.
//
// Most RPCs exposed by the service correspond to the methods of Store in the
// store package and are not documented here.
package daemon
import (
"os"
"src.elv.sh/pkg/logutil"
"src.elv.sh/pkg/prog"
)
var logger = logutil.GetLogger("[daemon] ")
// Program is the daemon subprogram.
var Program prog.Program = program{}
type program struct {
ServeChans ServeChans
}
func (p program) Run(fds [3]*os.File, f *prog.Flags, args []string) error {
if !f.Daemon {
return prog.ErrNotSuitable
}
if len(args) > 0 {
return prog.BadUsage("arguments are not allowed with -daemon")
}
setUmaskForDaemon()
exit := Serve(f.Sock, f.DB, p.ServeChans)
return prog.Exit(exit)
}

View File

@ -1,16 +1,44 @@
// Package daemon implements a service for mediating access to the data store,
// and its client.
//
// Most RPCs exposed by the service correspond to the methods of Store in the
// store package and are not documented here.
package daemon
import (
"net"
"net/rpc"
"os"
"os/signal"
"syscall"
"src.elv.sh/pkg/daemon/internal/api"
"src.elv.sh/pkg/rpc"
"src.elv.sh/pkg/logutil"
"src.elv.sh/pkg/prog"
"src.elv.sh/pkg/store"
)
var logger = logutil.GetLogger("[daemon] ")
// Program is the daemon subprogram.
var Program prog.Program = program{}
type program struct {
ServeChans ServeChans
}
func (p program) Run(fds [3]*os.File, f *prog.Flags, args []string) error {
if !f.Daemon {
return prog.ErrNotSuitable
}
if len(args) > 0 {
return prog.BadUsage("arguments are not allowed with -daemon")
}
setUmaskForDaemon()
exit := Serve(f.Sock, f.DB, p.ServeChans)
return prog.Exit(exit)
}
// ServeChans keeps channels that can be passed to Serve.
type ServeChans struct {
// If not nil, will be closed when the daemon is ready to serve requests.

View File

@ -7,7 +7,6 @@ import (
"testing"
"time"
"src.elv.sh/pkg/daemon/client"
"src.elv.sh/pkg/daemon/daemondefs"
"src.elv.sh/pkg/daemon/internal/api"
. "src.elv.sh/pkg/prog/progtest"
@ -131,7 +130,7 @@ func startServerSigCh(t *testing.T, sigCh <-chan os.Signal) <-chan struct{} {
}
func startClient(t *testing.T) daemondefs.Client {
cl := client.NewClient("sock")
cl := NewClient("sock")
if _, err := cl.Version(); err != nil {
t.Errorf("failed to start client: %v", err)
}

View File

@ -1,13 +1,18 @@
//go:build !windows && !plan9 && !js
// +build !windows,!plan9,!js
package client
package daemon
import (
"os"
"syscall"
"golang.org/x/sys/unix"
)
// Make sure that files created by the daemon is not accessible to other users.
func setUmaskForDaemon() { unix.Umask(0077) }
func procAttrForSpawn(files []*os.File) *os.ProcAttr {
return &os.ProcAttr{
Dir: "/",

View File

@ -1,18 +1,20 @@
package client
package daemon
import (
"os"
"syscall"
)
// No-op on Windows.
func setUmaskForDaemon() {}
// A subset of possible process creation flags, value taken from
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863(v=vs.85).aspx
const (
CREATE_BREAKAWAY_FROM_JOB = 0x01000000
CREATE_NEW_PROCESS_GROUP = 0x00000200
DETACHED_PROCESS = 0x00000008
daemonCreationFlags = CREATE_BREAKAWAY_FROM_JOB | CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS
createBreakwayFromJob = 0x01000000
createNewProcessGroup = 0x00000200
detachedProcess = 0x00000008
daemonCreationFlags = createBreakwayFromJob | createNewProcessGroup | detachedProcess
)
func procAttrForSpawn(files []*os.File) *os.ProcAttr {

View File

@ -1,9 +0,0 @@
//go:build !windows && !plan9 && !js
// +build !windows,!plan9,!js
package daemon
import "golang.org/x/sys/unix"
// Make sure that files created by the daemon is not accessible to other users.
func setUmaskForDaemon() { unix.Umask(0077) }

View File

@ -1,4 +0,0 @@
package daemon
// No-op on Windows.
func setUmaskForDaemon() {}

View File

@ -11,7 +11,6 @@ import (
"time"
"src.elv.sh/pkg/daemon"
"src.elv.sh/pkg/daemon/client"
"src.elv.sh/pkg/env"
. "src.elv.sh/pkg/prog/progtest"
@ -58,7 +57,7 @@ func TestInteract_ConnectsToDaemon(t *testing.T) {
t.Fatalf("timed out waiting for daemon to start")
}
Test(t, Program{client.Activate},
Test(t, Program{daemon.Activate},
thatElvishInteract("-sock", "sock", "-db", "db").
WithStdin("use daemon; echo $daemon:pid\n").
WritesStdout(fmt.Sprintln(os.Getpid())),