diff --git a/addons/bootstrap/user-groups_other.go b/addons/bootstrap/user-groups_other.go new file mode 100644 index 00000000..20601663 --- /dev/null +++ b/addons/bootstrap/user-groups_other.go @@ -0,0 +1,12 @@ +//go:build !linux + +package bootstrap + +import ( + "fmt" + "runtime" +) + +func addUserToGroup(_ *Context, userName, groupName string) error { + return fmt.Errorf("adding user %s to group %s is not supported on %s", userName, groupName, runtime.GOOS) +} diff --git a/addons/diags/file-collector.go b/addons/diags/file-collector.go index 4a8b5914..0ac4ca12 100644 --- a/addons/diags/file-collector.go +++ b/addons/diags/file-collector.go @@ -12,7 +12,6 @@ import ( "io/fs" "os" "sync" - "syscall" "time" "fastcat.org/go/gdev/instance" @@ -91,12 +90,7 @@ func fillTarHeader(th *tar.Header, contents io.Reader) (bool, error) { th.Size = fi.Size() th.ModTime = fi.ModTime() th.Mode = int64(fi.Mode().Perm()) - if fis, ok := fi.Sys().(syscall.Stat_t); ok { - // don't bother trying to look up user information - th.Uid, th.Gid = int(fis.Uid), int(fis.Gid) - th.AccessTime = time.Unix(fis.Atim.Unix()) - th.ChangeTime = time.Unix(fis.Ctim.Unix()) - } + fillPlatformStatFields(th, fi) return true, nil } diff --git a/addons/diags/stat-times_linux.go b/addons/diags/stat-times_linux.go new file mode 100644 index 00000000..facd6bad --- /dev/null +++ b/addons/diags/stat-times_linux.go @@ -0,0 +1,18 @@ +package diags + +import ( + "archive/tar" + "io/fs" + "syscall" + "time" +) + +func fillPlatformStatFields(th *tar.Header, fi fs.FileInfo) { + st, ok := fi.Sys().(*syscall.Stat_t) + if !ok { + return + } + th.Uid, th.Gid = int(st.Uid), int(st.Gid) + th.AccessTime = time.Unix(st.Atim.Unix()) + th.ChangeTime = time.Unix(st.Ctim.Unix()) +} diff --git a/addons/diags/stat-times_other.go b/addons/diags/stat-times_other.go new file mode 100644 index 00000000..921806c6 --- /dev/null +++ b/addons/diags/stat-times_other.go @@ -0,0 +1,14 @@ +//go:build !linux + +package diags + +import ( + "archive/tar" + "io/fs" +) + +func fillPlatformStatFields(_ *tar.Header, _ fs.FileInfo) { + // On non-Linux platforms, Uid, Gid, AccessTime, and ChangeTime + // are left at their zero values. Darwin uses different syscall.Stat_t + // field names (Atimespec/Ctimespec) and Windows has no syscall.Stat_t. +} diff --git a/addons/pm/server/child.go b/addons/pm/server/child.go index 213b8888..d3254c87 100644 --- a/addons/pm/server/child.go +++ b/addons/pm/server/child.go @@ -341,8 +341,7 @@ func (c *child) start( for k, v := range e.Env { cmd.Env = append(cmd.Env, k+"="+v) } - // set pgid so we can kill process groups - cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + setProcGroup(cmd) // if logfile is not set, pass output to stdout/stderr and let journalctl // capture it. note that this only works if we're using systemd for isolation. if e.Logfile == "" { @@ -388,16 +387,14 @@ func (c *child) start( } func (c *child) terminate(p *os.Process, s *api.ExecStatus) { - // signal the whole process group - if err := syscall.Kill(-p.Pid, syscall.SIGTERM); err != nil { + if err := terminateProcessGroup(p.Pid); err != nil { log.Printf("failed to terminate %d: %v", p.Pid, err) } s.State = api.ExecStopping } func (c *child) kill(p *os.Process, s *api.ExecStatus) { - // signal the whole process group - if err := syscall.Kill(-p.Pid, syscall.SIGKILL); err != nil { + if err := killProcessGroup(p.Pid); err != nil { log.Printf("failed to kill %d: %v", p.Pid, err) } if s.Group != "" { diff --git a/addons/pm/server/proc_linux.go b/addons/pm/server/proc_linux.go new file mode 100644 index 00000000..b69a9e4f --- /dev/null +++ b/addons/pm/server/proc_linux.go @@ -0,0 +1,22 @@ +package server + +import ( + "os/exec" + "syscall" +) + +// setProcGroup configures the command to start in a new process group, +// enabling group-wide signal delivery via negative PID. +func setProcGroup(cmd *exec.Cmd) { + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} +} + +// terminateProcessGroup sends SIGTERM to the entire process group. +func terminateProcessGroup(pid int) error { + return syscall.Kill(-pid, syscall.SIGTERM) +} + +// killProcessGroup sends SIGKILL to the entire process group. +func killProcessGroup(pid int) error { + return syscall.Kill(-pid, syscall.SIGKILL) +} diff --git a/addons/pm/server/proc_other.go b/addons/pm/server/proc_other.go new file mode 100644 index 00000000..d3cf7f92 --- /dev/null +++ b/addons/pm/server/proc_other.go @@ -0,0 +1,19 @@ +//go:build !linux + +package server + +import ( + "fmt" + "os/exec" + "runtime" +) + +func setProcGroup(_ *exec.Cmd) {} + +func terminateProcessGroup(_ int) error { + return fmt.Errorf("process group termination not supported on %s", runtime.GOOS) +} + +func killProcessGroup(_ int) error { + return fmt.Errorf("process group kill not supported on %s", runtime.GOOS) +} diff --git a/instance/version.go b/instance/version.go index d188d27c..e5e5a5db 100644 --- a/instance/version.go +++ b/instance/version.go @@ -8,12 +8,28 @@ import ( var info = sync.OnceValue(loadVersionInfo) +// versionOverride, if non-empty, replaces MainVersion from build info. +var versionOverride string + +// SetVersion overrides the main version string reported by Version() and +// VersionInfo(). Call before any use of the version functions. +func SetVersion(v string) { + versionOverride = v +} + func Version() string { + if versionOverride != "" { + return versionOverride + } return info().MainVersion } func VersionInfo() versionInfo { - return info() + vi := info() + if versionOverride != "" { + vi.MainVersion = versionOverride + } + return vi } type versionInfo struct { diff --git a/lib/sys/daemon_other.go b/lib/sys/daemon_other.go index 02cc0421..3a145050 100644 --- a/lib/sys/daemon_other.go +++ b/lib/sys/daemon_other.go @@ -8,6 +8,12 @@ import ( "runtime" ) +// FallbackLogFileEnv is an environment variable that can be set to provide +// a fallback log file path for daemons started without systemd support. +// +// This will not be passed to the actual daemon. +const FallbackLogFileEnv = "__FALLBACK_LOG_FILE" + func StartDaemon( ctx context.Context, name string,