Felix Niederwanger
7721f4fcb9
This is a work in progress commit. Do not merge! Adds privilege droppings, such that the main webserver process must not run as root anymore.
169 lines
3.1 KiB
Go
169 lines
3.1 KiB
Go
package main
|
|
|
|
/* Child process handling */
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"syscall"
|
|
)
|
|
|
|
type Process struct {
|
|
pid int // Child PID or 0, if not running
|
|
cmd string // Command
|
|
argv []string // Arguments
|
|
pipeIn int // stdin pipe file descriptor
|
|
pipeOut int // stdout pipe file descriptor
|
|
pipeErr int // stderr pipe file descriptor
|
|
env []string
|
|
}
|
|
|
|
type ProcessPool struct {
|
|
Processes []Process
|
|
}
|
|
|
|
// Create a Unix pipe
|
|
func pipe() (int, int, error) {
|
|
fd := make([]int, 2)
|
|
err := syscall.Pipe(fd)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
return fd[0], fd[1], nil
|
|
}
|
|
|
|
func CreateProcess(cmd string, argv []string) Process {
|
|
child := Process{cmd: cmd, argv: argv}
|
|
return child
|
|
}
|
|
|
|
func (c *Process) SetEnv(env []string) {
|
|
c.env = env
|
|
}
|
|
|
|
func (c *Process) Close() {
|
|
// Close all pipes
|
|
if c.pipeIn != 0 {
|
|
syscall.Close(c.pipeIn)
|
|
c.pipeIn = 0
|
|
}
|
|
if c.pipeOut != 0 {
|
|
syscall.Close(c.pipeOut)
|
|
c.pipeOut = 0
|
|
}
|
|
if c.pipeErr != 0 {
|
|
syscall.Close(c.pipeErr)
|
|
c.pipeErr = 0
|
|
}
|
|
}
|
|
|
|
func (c *Process) Exec() error {
|
|
var err error
|
|
var (
|
|
pInIn, pOutOut, pOutErr int
|
|
)
|
|
|
|
// Create in- and output pipe
|
|
pInIn, c.pipeIn, err = pipe()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.pipeOut, pOutOut, err = pipe()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.pipeErr, pOutErr, err = pipe()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
attr := syscall.ProcAttr{
|
|
// Use pipes as stdin, stdout and stderr
|
|
Files: []uintptr{uintptr(pInIn), uintptr(pOutOut), uintptr(pOutErr)},
|
|
Env: c.env,
|
|
}
|
|
c.pid, err = syscall.ForkExec(c.cmd, c.argv, &attr)
|
|
// Close pipe ends which go to the process
|
|
syscall.Close(pInIn)
|
|
syscall.Close(pOutOut)
|
|
syscall.Close(pOutErr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Wait waits for the process to complete, returning the return value
|
|
func (c *Process) Wait() (int, error) {
|
|
if c.pid == 0 {
|
|
return 0, fmt.Errorf("no process")
|
|
}
|
|
proc, err := os.FindProcess(c.pid)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
state, err := proc.Wait()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
c.pid = 0
|
|
return state.ExitCode(), err
|
|
}
|
|
|
|
// Write writes a given buffer to the stdin of the child process
|
|
func (c *Process) Write(p []byte) (int, error) {
|
|
if c.pid == 0 {
|
|
return 0, fmt.Errorf("no process")
|
|
}
|
|
if c.pipeIn == 0 {
|
|
return 0, fmt.Errorf("no pipe")
|
|
}
|
|
return syscall.Write(c.pipeIn, p)
|
|
}
|
|
|
|
// ReadStdout reads from the stdout of the child process
|
|
func (c *Process) ReadStdout(p []byte) (int, error) {
|
|
if c.pid == 0 {
|
|
return 0, fmt.Errorf("no process")
|
|
}
|
|
if c.pipeOut == 0 {
|
|
return 0, fmt.Errorf("no pipe")
|
|
}
|
|
return syscall.Read(c.pipeOut, p)
|
|
}
|
|
|
|
// ReadStderr reads from the stderr of the child process
|
|
func (c *Process) ReadStderr(p []byte) (int, error) {
|
|
if c.pid == 0 {
|
|
return 0, fmt.Errorf("no process")
|
|
}
|
|
if c.pipeErr == 0 {
|
|
return 0, fmt.Errorf("no pipe")
|
|
}
|
|
return syscall.Read(c.pipeErr, p)
|
|
}
|
|
|
|
func (c *Process) Terminate() {
|
|
if c.pid != 0 {
|
|
c.Close()
|
|
syscall.Kill(c.pid, syscall.SIGTERM)
|
|
c.pid = 0
|
|
}
|
|
}
|
|
|
|
func (c *Process) Abort() {
|
|
if c.pid != 0 {
|
|
c.Close()
|
|
syscall.Kill(c.pid, syscall.SIGABRT)
|
|
c.pid = 0
|
|
}
|
|
}
|
|
|
|
func (c *Process) Kill() {
|
|
if c.pid != 0 {
|
|
c.Close()
|
|
syscall.Kill(c.pid, syscall.SIGKILL)
|
|
c.pid = 0
|
|
}
|
|
}
|