2018-04-27 17:37:02 +00:00
// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
2018-07-25 16:10:32 +00:00
"path/filepath"
2018-04-27 17:37:02 +00:00
"strconv"
"strings"
"syscall"
"time"
"context"
"flag"
"github.com/google/subcommands"
specs "github.com/opencontainers/runtime-spec/specs-go"
"gvisor.googlesource.com/gvisor/pkg/log"
"gvisor.googlesource.com/gvisor/pkg/sentry/control"
"gvisor.googlesource.com/gvisor/pkg/sentry/kernel/auth"
"gvisor.googlesource.com/gvisor/pkg/urpc"
"gvisor.googlesource.com/gvisor/runsc/boot"
2018-08-25 00:42:30 +00:00
"gvisor.googlesource.com/gvisor/runsc/console"
2018-05-15 17:17:19 +00:00
"gvisor.googlesource.com/gvisor/runsc/container"
2018-04-27 17:37:02 +00:00
"gvisor.googlesource.com/gvisor/runsc/specutils"
)
// Exec implements subcommands.Command for the "exec" command.
type Exec struct {
cwd string
env stringSlice
// user contains the UID and GID with which to run the new process.
2018-09-12 22:22:24 +00:00
user user
extraKGIDs stringSlice
caps stringSlice
detach bool
processPath string
pidFile string
internalPidFile string
2018-08-25 00:42:30 +00:00
// consoleSocket is the path to an AF_UNIX socket which will receive a
// file descriptor referencing the master end of the console's
// pseudoterminal.
consoleSocket string
2018-04-27 17:37:02 +00:00
}
// Name implements subcommands.Command.Name.
func ( * Exec ) Name ( ) string {
return "exec"
}
// Synopsis implements subcommands.Command.Synopsis.
func ( * Exec ) Synopsis ( ) string {
return "execute new process inside the container"
}
// Usage implements subcommands.Command.Usage.
func ( * Exec ) Usage ( ) string {
return ` exec [ command options ] < container - id > < command > [ command options ] || -- process process . json < container - id >
Where "<container-id>" is the name for the instance of the container and
"<command>" is the command to be executed in the container .
"<command>" can ' t be empty unless a "-process" flag provided .
EXAMPLE :
If the container is configured to run / bin / ps the following will
output a list of processes running in the container :
# runc exec < container - id > ps
OPTIONS :
`
}
// SetFlags implements subcommands.Command.SetFlags.
func ( ex * Exec ) SetFlags ( f * flag . FlagSet ) {
f . StringVar ( & ex . cwd , "cwd" , "" , "current working directory" )
f . Var ( & ex . env , "env" , "set environment variables (e.g. '-env PATH=/bin -env TERM=xterm')" )
f . Var ( & ex . user , "user" , "UID (format: <uid>[:<gid>])" )
f . Var ( & ex . extraKGIDs , "additional-gids" , "additional gids" )
f . Var ( & ex . caps , "cap" , "add a capability to the bounding set for the process" )
f . BoolVar ( & ex . detach , "detach" , false , "detach from the container's process" )
f . StringVar ( & ex . processPath , "process" , "" , "path to the process.json" )
2018-05-15 17:17:19 +00:00
f . StringVar ( & ex . pidFile , "pid-file" , "" , "filename that the container pid will be written to" )
2018-09-12 22:22:24 +00:00
f . StringVar ( & ex . internalPidFile , "internal-pid-file" , "" , "filename that the container-internal pid will be written to" )
2018-08-25 00:42:30 +00:00
f . StringVar ( & ex . consoleSocket , "console-socket" , "" , "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal" )
2018-04-27 17:37:02 +00:00
}
// Execute implements subcommands.Command.Execute. It starts a process in an
2018-05-15 17:17:19 +00:00
// already created container.
2018-04-27 17:37:02 +00:00
func ( ex * Exec ) Execute ( _ context . Context , f * flag . FlagSet , args ... interface { } ) subcommands . ExitStatus {
e , id , err := ex . parseArgs ( f )
if err != nil {
Fatalf ( "error parsing process spec: %v" , err )
}
conf := args [ 0 ] . ( * boot . Config )
waitStatus := args [ 1 ] . ( * syscall . WaitStatus )
2018-05-15 17:17:19 +00:00
c , err := container . Load ( conf . RootDir , id )
2018-04-27 17:37:02 +00:00
if err != nil {
2018-06-20 04:42:21 +00:00
Fatalf ( "error loading sandbox: %v" , err )
2018-04-27 17:37:02 +00:00
}
2018-09-06 01:31:37 +00:00
// Replace empty settings with defaults from container.
2018-04-27 17:37:02 +00:00
if e . WorkingDirectory == "" {
2018-05-15 17:17:19 +00:00
e . WorkingDirectory = c . Spec . Process . Cwd
2018-04-27 17:37:02 +00:00
}
if e . Envv == nil {
2018-05-15 17:17:19 +00:00
e . Envv , err = resolveEnvs ( c . Spec . Process . Env , ex . env )
2018-04-27 17:37:02 +00:00
if err != nil {
Fatalf ( "error getting environment variables: %v" , err )
}
}
2018-09-06 01:31:37 +00:00
if e . Capabilities == nil {
e . Capabilities , err = specutils . Capabilities ( c . Spec . Process . Capabilities )
if err != nil {
Fatalf ( "error creating capabilities: %v" , err )
}
}
2018-04-27 17:37:02 +00:00
// containerd expects an actual process to represent the container being
// executed. If detach was specified, starts a child in non-detach mode,
// write the child's PID to the pid file. So when the container returns, the
// child process will also return and signal containerd.
2018-05-03 00:39:12 +00:00
if ex . detach {
2018-05-04 04:08:38 +00:00
return ex . execAndWait ( waitStatus )
2018-04-27 17:37:02 +00:00
}
if ex . pidFile != "" {
if err := ioutil . WriteFile ( ex . pidFile , [ ] byte ( strconv . Itoa ( os . Getpid ( ) ) ) , 0644 ) ; err != nil {
Fatalf ( "error writing pid file: %v" , err )
}
}
2018-09-12 22:22:24 +00:00
// Start the new process and get it pid.
pid , err := c . Execute ( e )
2018-04-27 17:37:02 +00:00
if err != nil {
2018-05-15 17:17:19 +00:00
Fatalf ( "error getting processes for container: %v" , err )
2018-04-27 17:37:02 +00:00
}
2018-09-12 22:22:24 +00:00
// Write the sandbox-internal pid if required.
if ex . internalPidFile != "" {
pidStr := [ ] byte ( strconv . Itoa ( int ( pid ) ) )
if err := ioutil . WriteFile ( ex . internalPidFile , pidStr , 0644 ) ; err != nil {
Fatalf ( "error writing internal pid file %q: %v" , ex . internalPidFile , err )
}
}
// Wait for the process to exit.
ws , err := c . WaitPID ( pid )
if err != nil {
Fatalf ( "error waiting on pid %d: %v" , pid , err )
}
2018-04-27 17:37:02 +00:00
* waitStatus = ws
return subcommands . ExitSuccess
}
2018-05-04 04:08:38 +00:00
func ( ex * Exec ) execAndWait ( waitStatus * syscall . WaitStatus ) subcommands . ExitStatus {
binPath , err := specutils . BinPath ( )
if err != nil {
Fatalf ( "error getting bin path: %v" , err )
}
var args [ ] string
2018-07-25 16:10:32 +00:00
// The command needs to write a pid file so that execAndWait can tell
// when it has started. If no pid-file was provided, we should use a
// filename in a temp directory.
pidFile := ex . pidFile
if pidFile == "" {
tmpDir , err := ioutil . TempDir ( "" , "exec-pid-" )
if err != nil {
Fatalf ( "error creating TempDir: %v" , err )
}
defer os . RemoveAll ( tmpDir )
pidFile = filepath . Join ( tmpDir , "pid" )
args = append ( args , "--pid-file=" + pidFile )
}
// Add the rest of the args, excluding the "detach" flag.
2018-05-04 04:08:38 +00:00
for _ , a := range os . Args [ 1 : ] {
if ! strings . Contains ( a , "detach" ) {
args = append ( args , a )
}
}
cmd := exec . Command ( binPath , args ... )
2018-08-25 00:42:30 +00:00
// Exec stdio defaults to current process stdio.
2018-05-04 04:08:38 +00:00
cmd . Stdin = os . Stdin
cmd . Stdout = os . Stdout
cmd . Stderr = os . Stderr
2018-08-25 00:42:30 +00:00
// If the console control socket file is provided, then create a new
// pty master/slave pair and set the tty on the sandbox process.
if ex . consoleSocket != "" {
// Create a new tty pair and send the master on the provided
// socket.
tty , err := console . NewWithSocket ( ex . consoleSocket )
if err != nil {
Fatalf ( "error setting up console with socket %q: %v" , ex . consoleSocket , err )
}
defer tty . Close ( )
// Set stdio to the new tty slave.
cmd . Stdin = tty
cmd . Stdout = tty
cmd . Stderr = tty
cmd . SysProcAttr = & syscall . SysProcAttr {
Setsid : true ,
Setctty : true ,
Ctty : int ( tty . Fd ( ) ) ,
}
}
2018-05-04 04:08:38 +00:00
if err := cmd . Start ( ) ; err != nil {
Fatalf ( "failure to start child exec process, err: %v" , err )
}
log . Infof ( "Started child (PID: %d) to exec and wait: %s %s" , cmd . Process . Pid , binPath , args )
// Wait for PID file to ensure that child process has started. Otherwise,
// '--process' file is deleted as soon as this process returns and the child
// may fail to read it.
ready := func ( ) ( bool , error ) {
2018-07-25 16:10:32 +00:00
_ , err := os . Stat ( pidFile )
2018-05-04 04:08:38 +00:00
if err == nil {
// File appeared, we're done!
return true , nil
}
if pe , ok := err . ( * os . PathError ) ; ! ok || pe . Err != syscall . ENOENT {
return false , err
}
// No file yet, continue to wait...
return false , nil
}
if err := specutils . WaitForReady ( cmd . Process . Pid , 10 * time . Second , ready ) ; err != nil {
Fatalf ( "unexpected error waiting for PID file, err: %v" , err )
}
* waitStatus = 0
return subcommands . ExitSuccess
}
2018-04-27 17:37:02 +00:00
// parseArgs parses exec information from the command line or a JSON file
// depending on whether the --process flag was used. Returns an ExecArgs and
2018-05-15 17:17:19 +00:00
// the ID of the container to be used.
2018-04-27 17:37:02 +00:00
func ( ex * Exec ) parseArgs ( f * flag . FlagSet ) ( * control . ExecArgs , string , error ) {
if ex . processPath == "" {
// Requires at least a container ID and command.
if f . NArg ( ) < 2 {
f . Usage ( )
return nil , "" , fmt . Errorf ( "both a container-id and command are required" )
}
e , err := ex . argsFromCLI ( f . Args ( ) [ 1 : ] )
return e , f . Arg ( 0 ) , err
}
// Requires only the container ID.
if f . NArg ( ) != 1 {
f . Usage ( )
return nil , "" , fmt . Errorf ( "a container-id is required" )
}
e , err := ex . argsFromProcessFile ( )
return e , f . Arg ( 0 ) , err
}
func ( ex * Exec ) argsFromCLI ( argv [ ] string ) ( * control . ExecArgs , error ) {
extraKGIDs := make ( [ ] auth . KGID , 0 , len ( ex . extraKGIDs ) )
for _ , s := range ex . extraKGIDs {
kgid , err := strconv . Atoi ( s )
if err != nil {
Fatalf ( "error parsing GID: %s, %v" , s , err )
}
extraKGIDs = append ( extraKGIDs , auth . KGID ( kgid ) )
}
2018-09-06 01:31:37 +00:00
var caps * auth . TaskCapabilities
if len ( ex . caps ) > 0 {
var err error
caps , err = capabilities ( ex . caps )
if err != nil {
return nil , fmt . Errorf ( "capabilities error: %v" , err )
}
2018-04-27 17:37:02 +00:00
}
return & control . ExecArgs {
Argv : argv ,
WorkingDirectory : ex . cwd ,
KUID : ex . user . kuid ,
KGID : ex . user . kgid ,
ExtraKGIDs : extraKGIDs ,
Capabilities : caps ,
2018-08-25 00:42:30 +00:00
StdioIsPty : ex . consoleSocket != "" ,
FilePayload : urpc . FilePayload { [ ] * os . File { os . Stdin , os . Stdout , os . Stderr } } ,
2018-04-27 17:37:02 +00:00
} , nil
}
func ( ex * Exec ) argsFromProcessFile ( ) ( * control . ExecArgs , error ) {
f , err := os . Open ( ex . processPath )
if err != nil {
return nil , fmt . Errorf ( "error opening process file: %s, %v" , ex . processPath , err )
}
defer f . Close ( )
var p specs . Process
if err := json . NewDecoder ( f ) . Decode ( & p ) ; err != nil {
return nil , fmt . Errorf ( "error parsing process file: %s, %v" , ex . processPath , err )
}
return argsFromProcess ( & p )
}
// argsFromProcess performs all the non-IO conversion from the Process struct
// to ExecArgs.
func argsFromProcess ( p * specs . Process ) ( * control . ExecArgs , error ) {
// Create capabilities.
2018-09-06 01:31:37 +00:00
var caps * auth . TaskCapabilities
if p . Capabilities != nil {
var err error
caps , err = specutils . Capabilities ( p . Capabilities )
if err != nil {
return nil , fmt . Errorf ( "error creating capabilities: %v" , err )
}
2018-04-27 17:37:02 +00:00
}
// Convert the spec's additional GIDs to KGIDs.
extraKGIDs := make ( [ ] auth . KGID , 0 , len ( p . User . AdditionalGids ) )
for _ , GID := range p . User . AdditionalGids {
extraKGIDs = append ( extraKGIDs , auth . KGID ( GID ) )
}
return & control . ExecArgs {
Argv : p . Args ,
Envv : p . Env ,
WorkingDirectory : p . Cwd ,
KUID : auth . KUID ( p . User . UID ) ,
KGID : auth . KGID ( p . User . GID ) ,
ExtraKGIDs : extraKGIDs ,
Capabilities : caps ,
2018-08-25 00:42:30 +00:00
StdioIsPty : p . Terminal ,
FilePayload : urpc . FilePayload { Files : [ ] * os . File { os . Stdin , os . Stdout , os . Stderr } } ,
2018-04-27 17:37:02 +00:00
} , nil
}
// resolveEnvs transforms lists of environment variables into a single list of
// environment variables. If a variable is defined multiple times, the last
// value is used.
func resolveEnvs ( envs ... [ ] string ) ( [ ] string , error ) {
// First create a map of variable names to values. This removes any
// duplicates.
envMap := make ( map [ string ] string )
for _ , env := range envs {
for _ , str := range env {
parts := strings . SplitN ( str , "=" , 2 )
if len ( parts ) != 2 {
return nil , fmt . Errorf ( "invalid variable: %s" , str )
}
envMap [ parts [ 0 ] ] = parts [ 1 ]
}
}
// Reassemble envMap into a list of environment variables of the form
// NAME=VALUE.
env := make ( [ ] string , 0 , len ( envMap ) )
for k , v := range envMap {
env = append ( env , fmt . Sprintf ( "%s=%s" , k , v ) )
}
return env , nil
}
// capabilities takes a list of capabilities as strings and returns an
// auth.TaskCapabilities struct with those capabilities in every capability set.
// This mimics runc's behavior.
func capabilities ( cs [ ] string ) ( * auth . TaskCapabilities , error ) {
var specCaps specs . LinuxCapabilities
for _ , cap := range cs {
specCaps . Ambient = append ( specCaps . Ambient , cap )
specCaps . Bounding = append ( specCaps . Bounding , cap )
specCaps . Effective = append ( specCaps . Effective , cap )
specCaps . Inheritable = append ( specCaps . Inheritable , cap )
specCaps . Permitted = append ( specCaps . Permitted , cap )
}
return specutils . Capabilities ( & specCaps )
}
// stringSlice allows a flag to be used multiple times, where each occurrence
// adds a value to the flag. For example, a flag called "x" could be invoked
// via "runsc exec -x foo -x bar", and the corresponding stringSlice would be
// {"x", "y"}.
type stringSlice [ ] string
// String implements flag.Value.String.
func ( ss * stringSlice ) String ( ) string {
return fmt . Sprintf ( "%v" , * ss )
}
// Get implements flag.Value.Get.
func ( ss * stringSlice ) Get ( ) interface { } {
return ss
}
// Set implements flag.Value.Set.
func ( ss * stringSlice ) Set ( s string ) error {
* ss = append ( * ss , s )
return nil
}
// user allows -user to convey a UID and, optionally, a GID separated by a
// colon.
type user struct {
kuid auth . KUID
kgid auth . KGID
}
func ( u * user ) String ( ) string {
return fmt . Sprintf ( "%+v" , * u )
}
func ( u * user ) Get ( ) interface { } {
return u
}
func ( u * user ) Set ( s string ) error {
parts := strings . SplitN ( s , ":" , 2 )
kuid , err := strconv . Atoi ( parts [ 0 ] )
if err != nil {
return fmt . Errorf ( "couldn't parse UID: %s" , parts [ 0 ] )
}
u . kuid = auth . KUID ( kuid )
if len ( parts ) > 1 {
kgid , err := strconv . Atoi ( parts [ 1 ] )
if err != nil {
return fmt . Errorf ( "couldn't parse GID: %s" , parts [ 1 ] )
}
u . kgid = auth . KGID ( kgid )
}
return nil
}