blob: 16bd83782790e3ad6567383588ee2c9548fd7983 [file] [log] [blame]
// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package launcher contains utilities to launch v23agentd.
package launcher
import (
"bufio"
"fmt"
"os"
"os/exec"
"path/filepath"
"syscall"
"time"
"v.io/v23/verror"
"v.io/x/lib/vlog"
"v.io/x/ref/services/agent/internal/constants"
)
const pkgPath = "v.io/x/ref/services/agent/internal/launcher"
var (
errFilepathAbs = verror.Register(pkgPath+".errAbsFailed", verror.NoRetry, "{1:}{2:} filepath.Abs failed for {3}")
errCantCreate = verror.Register(pkgPath+".errCantCreate", verror.NoRetry, "{1:}{2:} failed to create {3}{:_}")
)
// LaunchAgent launches the agent as a separate process and waits for it to
// serve the principal.
func LaunchAgent(credsDir, agentBin string, printCredsEnv bool, flags ...string) error {
// Use an absolute path to the credentials directory to avoid relying on
// a specific cwd setting when starting the agent.
if path, err := filepath.Abs(credsDir); err != nil {
return verror.New(errFilepathAbs, nil, credsDir, err)
} else {
credsDir = path
}
// Use an absolute path to the agent to avoid relying on a specific cwd
// setting when starting the agent.
agentBin, err := exec.LookPath(agentBin)
if err != nil {
return err
}
if path, err := filepath.Abs(agentBin); err != nil {
return verror.New(errFilepathAbs, nil, agentBin, err)
} else {
agentBin = path
}
agentRead, agentWrite, err := os.Pipe()
if err != nil {
return fmt.Errorf("failed to create pipe: %v", err)
}
defer agentRead.Close()
flags = append(flags,
fmt.Sprintf("--%s=false", constants.DaemonFlag),
fmt.Sprintf("--%s=%s", constants.CredentialsFlag, credsDir))
cmd := exec.Command(agentBin, flags...)
agentDir := constants.AgentDir(credsDir)
cmd.Dir = agentDir
if err := os.MkdirAll(agentDir, 0700); err != nil {
return verror.New(errCantCreate, nil, agentDir, err)
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.SysProcAttr = new(syscall.SysProcAttr)
cmd.SysProcAttr.Setsid = true
// The agentRead/Write pipe is used to get notification from the agent
// when it's ready to serve the principal.
cmd.ExtraFiles = append(cmd.ExtraFiles, agentWrite)
cmd.Env = []string{
fmt.Sprintf("%s=3", constants.EnvAgentParentPipeFD),
fmt.Sprintf("%s=%s", constants.EnvAgentNoPrintCredsEnv, map[bool]string{true: "", false: "1"}[printCredsEnv]),
"PATH=" + os.Getenv("PATH"),
}
err = cmd.Start()
agentWrite.Close()
if err != nil {
return fmt.Errorf("failed to run %v: %v", cmd.Args, err)
}
pid := cmd.Process.Pid
vlog.Infof("Started agent for credentials %v with PID %d", credsDir, pid)
// Make sure we don't leave a disfunctional or zombie agent behind upon
// error.
cleanUpAgent := func() {
defer cmd.Wait()
if err := syscall.Kill(pid, syscall.SIGINT); err == syscall.ESRCH {
return
}
for i := 0; i < 10; i++ {
time.Sleep(100 * time.Millisecond)
if err := syscall.Kill(pid, 0); err == syscall.ESRCH {
return
}
}
syscall.Kill(pid, syscall.SIGKILL)
}
scanner := bufio.NewScanner(agentRead)
if !scanner.Scan() || scanner.Text() != constants.ServingMsg {
cleanUpAgent()
return fmt.Errorf("failed to receive \"%s\" from agent", constants.ServingMsg)
}
if err := scanner.Err(); err != nil {
cleanUpAgent()
return fmt.Errorf("failed reading status from agent: %v", err)
}
return nil
}