2015-09-15 21:03:03 +00:00
|
|
|
package executor
|
2015-09-11 19:35:03 +00:00
|
|
|
|
2015-09-14 22:57:21 +00:00
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"os/user"
|
2015-09-15 02:38:21 +00:00
|
|
|
"runtime"
|
2015-09-14 22:57:21 +00:00
|
|
|
"strconv"
|
|
|
|
"syscall"
|
2015-09-12 00:12:48 +00:00
|
|
|
|
2015-09-14 22:57:21 +00:00
|
|
|
"github.com/hashicorp/go-multierror"
|
|
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
|
|
)
|
|
|
|
|
|
|
|
// SetUID changes the Uid for this command (must be set before starting)
|
|
|
|
func SetUID(command *cmd, userid string) error {
|
|
|
|
uid, err := strconv.ParseUint(userid, 10, 32)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Unable to convert userid to uint32: %s", err)
|
|
|
|
}
|
|
|
|
if command.SysProcAttr == nil {
|
|
|
|
command.SysProcAttr = &syscall.SysProcAttr{}
|
|
|
|
}
|
|
|
|
if command.SysProcAttr.Credential == nil {
|
|
|
|
command.SysProcAttr.Credential = &syscall.Credential{}
|
|
|
|
}
|
|
|
|
command.SysProcAttr.Credential.Uid = uint32(uid)
|
|
|
|
return nil
|
|
|
|
}
|
2015-09-12 00:12:48 +00:00
|
|
|
|
2015-09-14 22:57:21 +00:00
|
|
|
// SetGID changes the Gid for this command (must be set before starting)
|
|
|
|
func SetGID(command *cmd, groupid string) error {
|
|
|
|
gid, err := strconv.ParseUint(groupid, 10, 32)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Unable to convert groupid to uint32: %s", err)
|
|
|
|
}
|
|
|
|
if command.SysProcAttr == nil {
|
|
|
|
command.SysProcAttr = &syscall.SysProcAttr{}
|
|
|
|
}
|
|
|
|
if command.SysProcAttr.Credential == nil {
|
|
|
|
command.SysProcAttr.Credential = &syscall.Credential{}
|
|
|
|
}
|
|
|
|
command.SysProcAttr.Credential.Uid = uint32(gid)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-09-15 02:38:21 +00:00
|
|
|
// Linux executor is designed to run on linux kernel 2.8+. It will fork/exec as
|
|
|
|
// a user you specify and limit resources using rlimit.
|
|
|
|
type LinuxExecutor struct {
|
|
|
|
cmd
|
|
|
|
user *user.User
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *LinuxExecutor) Available() bool {
|
|
|
|
return runtime.GOOS == "linux"
|
|
|
|
}
|
|
|
|
|
2015-09-15 20:11:56 +00:00
|
|
|
func (e *LinuxExecutor) Limit(resources *structs.Resources) error {
|
2015-09-15 02:38:21 +00:00
|
|
|
// TODO limit some things
|
2015-09-14 22:57:21 +00:00
|
|
|
return nil
|
2015-09-12 00:12:48 +00:00
|
|
|
}
|
|
|
|
|
2015-09-14 22:57:21 +00:00
|
|
|
func (e *LinuxExecutor) RunAs(userid string) error {
|
|
|
|
errs := new(multierror.Error)
|
2015-09-12 00:12:48 +00:00
|
|
|
|
2015-09-14 22:57:21 +00:00
|
|
|
// First, try to lookup the user by uid
|
|
|
|
u, err := user.LookupId(userid)
|
|
|
|
if err == nil {
|
|
|
|
e.user = u
|
|
|
|
return nil
|
|
|
|
} else {
|
|
|
|
errs = multierror.Append(errs, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lookup failed, so try by username instead
|
|
|
|
u, err = user.Lookup(userid)
|
|
|
|
if err == nil {
|
|
|
|
e.user = u
|
|
|
|
return nil
|
|
|
|
} else {
|
|
|
|
errs = multierror.Append(errs, err)
|
|
|
|
}
|
|
|
|
|
2015-09-15 02:38:21 +00:00
|
|
|
// If we got here we failed to lookup based on id and username, so we'll
|
2015-09-14 22:57:21 +00:00
|
|
|
// return those errors.
|
|
|
|
return fmt.Errorf("Failed to identify user to run as: %s", errs)
|
2015-09-12 00:12:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (e *LinuxExecutor) Start() error {
|
2015-09-15 02:04:29 +00:00
|
|
|
if e.user == nil {
|
|
|
|
// If no user has been specified, try to run as "nobody" user so we
|
|
|
|
// don't leak root privilege to the spawned process.
|
|
|
|
e.RunAs("nobody")
|
|
|
|
}
|
|
|
|
|
2015-09-14 22:57:21 +00:00
|
|
|
// Set the user and group this process should run as
|
|
|
|
if e.user != nil {
|
|
|
|
SetUID(&e.cmd, e.user.Uid)
|
|
|
|
SetGID(&e.cmd, e.user.Gid)
|
|
|
|
}
|
|
|
|
|
|
|
|
// We don't want to call ourself. We want to call Start on our embedded Cmd.
|
|
|
|
return e.cmd.Start()
|
2015-09-12 00:12:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (e *LinuxExecutor) Open(pid int) error {
|
2015-09-14 22:57:21 +00:00
|
|
|
process, err := os.FindProcess(pid)
|
2015-09-14 23:16:56 +00:00
|
|
|
// FindProcess doesn't do any checking against the process table so it's
|
|
|
|
// unlikely we'll ever see this error.
|
2015-09-14 22:57:21 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to reopen pid %d: %s", pid, err)
|
|
|
|
}
|
2015-09-14 23:16:56 +00:00
|
|
|
|
|
|
|
// On linux FindProcess() will return a pid but doesn't actually check to
|
|
|
|
// see whether that process is running. We'll send signal 0 to see if the
|
|
|
|
// process is alive.
|
2015-09-15 02:38:21 +00:00
|
|
|
err = process.Signal(syscall.Signal(0))
|
2015-09-14 23:16:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Unable to signal pid %d: %s", err)
|
|
|
|
}
|
2015-09-14 22:57:21 +00:00
|
|
|
e.Process = process
|
2015-09-12 00:12:48 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-09-15 20:11:56 +00:00
|
|
|
func (e *LinuxExecutor) Wait() error {
|
|
|
|
// We don't want to call ourself. We want to call Start on our embedded Cmd
|
|
|
|
return e.cmd.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *LinuxExecutor) Pid() (int, error) {
|
|
|
|
if e.cmd.Process != nil {
|
|
|
|
return e.cmd.Process.Pid, nil
|
|
|
|
} else {
|
|
|
|
return 0, fmt.Errorf("Process has finished or was never started")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-12 00:12:48 +00:00
|
|
|
func (e *LinuxExecutor) Shutdown() error {
|
2015-09-14 22:57:21 +00:00
|
|
|
return e.ForceStop()
|
2015-09-12 00:12:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (e *LinuxExecutor) ForceStop() error {
|
2015-09-14 22:57:21 +00:00
|
|
|
return e.Process.Kill()
|
2015-09-12 00:12:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (e *LinuxExecutor) Command() *cmd {
|
|
|
|
return &e.cmd
|
|
|
|
}
|