Add library for spawning a daemon

This commit is contained in:
Shreyas Goenka 2025-02-13 19:00:13 +01:00
parent 2d09636611
commit d86ccf6b49
No known key found for this signature in database
GPG Key ID: 92A07DF49CCB0622
6 changed files with 159 additions and 0 deletions

View File

@ -75,5 +75,9 @@ func New(ctx context.Context) *cobra.Command {
cli.AddCommand(sync.New()) cli.AddCommand(sync.New())
cli.AddCommand(version.New()) cli.AddCommand(version.New())
cli.AddCommand(&cobra.Command{
Use: "stream-",
})
return cli return cli
} }

View File

@ -0,0 +1,26 @@
package selftest
import (
"os"
"github.com/spf13/cobra"
)
const (
PrintStdinParentPid = "DATABRICKS_CLI_PRINT_STDIN_PARENT_PID"
)
// TODO CONTINUE: Write command that wait for each other via the PID.
// Ensure to check the process name as the PID otherwise can be reused pretty
// quick.
func newPrintStdin() *cobra.Command {
return &cobra.Command{
Use: "print-stdin",
RunE: func(cmd *cobra.Command, args []string) error {
if os.Getenv(PrintStdinParentPid) != "" {
return nil
}
},
}
}

15
cmd/selftest/selftest.go Normal file
View File

@ -0,0 +1,15 @@
package selftest
import (
"github.com/spf13/cobra"
)
func New() *cobra.Command {
cmd := &cobra.Command{
Use: "selftest",
Short: "Non functional CLI commands that are useful for testing",
}
cmd.AddCommand(newPrintStdin())
return cmd
}

75
libs/daemon/daemon.go Normal file
View File

@ -0,0 +1,75 @@
package daemon
import (
"fmt"
"io"
"os"
"os/exec"
"strconv"
)
type Daemon struct {
// If provided, the child process will create a pid file at this path.
PidFilePath string
// Environment variables to set in the child process.
Env []string
// Arguments to pass to the child process.
Args []string
cmd *exec.Cmd
stdin io.WriteCloser
}
func (d *Daemon) Start() error {
cli, err := os.Executable()
if err != nil {
return err
}
d.cmd = exec.Command(cli, d.Args...)
d.cmd.Env = d.Env
d.cmd.SysProcAttr = sysProcAttr()
d.stdin, err = d.cmd.StdinPipe()
if err != nil {
return fmt.Errorf("failed to get stdin pipe: %w", err)
}
err = d.cmd.Start()
if err != nil {
return err
}
if d.PidFilePath != "" {
err = os.WriteFile(d.PidFilePath, []byte(strconv.Itoa(d.cmd.Process.Pid)), 0o644)
if err != nil {
return fmt.Errorf("failed to write pid file: %w", err)
}
}
return nil
}
func (d *Daemon) Release() error {
if d.PidFilePath != "" {
err := os.Remove(d.PidFilePath)
if err != nil {
return fmt.Errorf("failed to remove pid file: %w", err)
}
}
if d.stdin != nil {
err := d.stdin.Close()
if err != nil {
return fmt.Errorf("failed to close stdin: %w", err)
}
}
if d.cmd == nil {
return nil
}
return d.cmd.Process.Release()
}

View File

@ -0,0 +1,24 @@
//go:build linux || darwin
package daemon
import "syscall"
// References:
// 1. linux: https://go.dev/src/syscall/exec_linux.go
// 2. macos (arm): https://go.dev/src/syscall/exec_libc2.go
func sysProcAttr() *syscall.SysProcAttr {
return &syscall.SysProcAttr{
// Create a new session for the child process. This ensures that the daemon
// is not terminated when the parent session is closed. This can happen
// for example when a ssh session is terminated.
// TODO: Test this.
Setsid: true,
Noctty: true,
// Start a new process group for the child process. This ensures that
// termination signals to the parent's process group are not propagated to
// the child process.
Setpgid: true,
}
}

View File

@ -0,0 +1,15 @@
//go:build windows
package daemon
import (
"syscall"
"golang.org/x/sys/windows"
)
func sysProcAttr() *syscall.SysProcAttr {
return &syscall.SysProcAttr{
CreationFlags: windows.CREATE_NEW_PROCESS_GROUP | windows.DETACHED_PROCESS | windows.CREATE_NO_WINDOW,
}
}