github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/shim/proc/exec.go (about)

     1  // Copyright 2018 The containerd Authors.
     2  // Copyright 2018 The gVisor Authors.
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //     https://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  package proc
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"io"
    22  	"os"
    23  	"path/filepath"
    24  	"sync"
    25  	"time"
    26  
    27  	"github.com/containerd/console"
    28  	"github.com/containerd/containerd/errdefs"
    29  	"github.com/containerd/containerd/log"
    30  	"github.com/containerd/containerd/pkg/stdio"
    31  	"github.com/containerd/fifo"
    32  	runc "github.com/containerd/go-runc"
    33  	specs "github.com/opencontainers/runtime-spec/specs-go"
    34  	"golang.org/x/sys/unix"
    35  	"github.com/SagerNet/gvisor/pkg/cleanup"
    36  
    37  	"github.com/SagerNet/gvisor/pkg/shim/runsc"
    38  )
    39  
    40  type execProcess struct {
    41  	wg sync.WaitGroup
    42  
    43  	execState execState
    44  
    45  	mu          sync.Mutex
    46  	id          string
    47  	console     console.Console
    48  	io          runc.IO
    49  	status      int
    50  	exited      time.Time
    51  	pid         int
    52  	internalPid int
    53  	closers     []io.Closer
    54  	stdin       io.Closer
    55  	stdio       stdio.Stdio
    56  	path        string
    57  	spec        specs.Process
    58  
    59  	parent    *Init
    60  	waitBlock chan struct{}
    61  }
    62  
    63  func (e *execProcess) Wait() {
    64  	<-e.waitBlock
    65  }
    66  
    67  func (e *execProcess) ID() string {
    68  	return e.id
    69  }
    70  
    71  func (e *execProcess) Pid() int {
    72  	e.mu.Lock()
    73  	defer e.mu.Unlock()
    74  	return e.pid
    75  }
    76  
    77  func (e *execProcess) ExitStatus() int {
    78  	e.mu.Lock()
    79  	defer e.mu.Unlock()
    80  	return e.status
    81  }
    82  
    83  func (e *execProcess) ExitedAt() time.Time {
    84  	e.mu.Lock()
    85  	defer e.mu.Unlock()
    86  	return e.exited
    87  }
    88  
    89  func (e *execProcess) SetExited(status int) {
    90  	e.mu.Lock()
    91  	defer e.mu.Unlock()
    92  
    93  	e.execState.SetExited(status)
    94  }
    95  
    96  func (e *execProcess) setExited(status int) {
    97  	if !e.exited.IsZero() {
    98  		log.L.Debugf("Exec: status already set to %d, ignoring status: %d", e.status, status)
    99  		return
   100  	}
   101  
   102  	log.L.Debugf("Exec: setting status: %d", status)
   103  	e.status = status
   104  	e.exited = time.Now()
   105  	e.parent.Platform.ShutdownConsole(context.Background(), e.console)
   106  	close(e.waitBlock)
   107  }
   108  
   109  func (e *execProcess) Delete(ctx context.Context) error {
   110  	e.mu.Lock()
   111  	defer e.mu.Unlock()
   112  
   113  	return e.execState.Delete(ctx)
   114  }
   115  
   116  func (e *execProcess) delete() error {
   117  	e.wg.Wait()
   118  	if e.io != nil {
   119  		for _, c := range e.closers {
   120  			c.Close()
   121  		}
   122  		e.io.Close()
   123  	}
   124  	return nil
   125  }
   126  
   127  func (e *execProcess) Resize(ws console.WinSize) error {
   128  	e.mu.Lock()
   129  	defer e.mu.Unlock()
   130  
   131  	return e.execState.Resize(ws)
   132  }
   133  
   134  func (e *execProcess) resize(ws console.WinSize) error {
   135  	if e.console == nil {
   136  		return nil
   137  	}
   138  	return e.console.Resize(ws)
   139  }
   140  
   141  func (e *execProcess) Kill(ctx context.Context, sig uint32, _ bool) error {
   142  	e.mu.Lock()
   143  	defer e.mu.Unlock()
   144  
   145  	return e.execState.Kill(ctx, sig, false)
   146  }
   147  
   148  func (e *execProcess) kill(ctx context.Context, sig uint32, _ bool) error {
   149  	internalPid := e.internalPid
   150  	if internalPid == 0 {
   151  		return nil
   152  	}
   153  
   154  	opts := runsc.KillOpts{Pid: internalPid}
   155  	if err := e.parent.runtime.Kill(ctx, e.parent.id, int(sig), &opts); err != nil {
   156  		return fmt.Errorf("%s: %w", err.Error(), errdefs.ErrNotFound)
   157  	}
   158  	return nil
   159  }
   160  
   161  func (e *execProcess) Stdin() io.Closer {
   162  	return e.stdin
   163  }
   164  
   165  func (e *execProcess) Stdio() stdio.Stdio {
   166  	return e.stdio
   167  }
   168  
   169  func (e *execProcess) Start(ctx context.Context) error {
   170  	e.mu.Lock()
   171  	defer e.mu.Unlock()
   172  
   173  	return e.execState.Start(ctx)
   174  }
   175  
   176  func (e *execProcess) start(ctx context.Context) error {
   177  	var socket *runc.Socket
   178  
   179  	switch {
   180  	case e.stdio.Terminal:
   181  		s, err := runc.NewTempConsoleSocket()
   182  		if err != nil {
   183  			return fmt.Errorf("failed to create runc console socket: %w", err)
   184  		}
   185  		defer s.Close()
   186  		socket = s
   187  
   188  	case e.stdio.IsNull():
   189  		io, err := runc.NewNullIO()
   190  		if err != nil {
   191  			return fmt.Errorf("creating new NULL IO: %w", err)
   192  		}
   193  		e.io = io
   194  
   195  	default:
   196  		io, err := runc.NewPipeIO(e.parent.IoUID, e.parent.IoGID, withConditionalIO(e.stdio))
   197  		if err != nil {
   198  			return fmt.Errorf("failed to create runc io pipes: %w", err)
   199  		}
   200  		e.io = io
   201  	}
   202  
   203  	opts := &runsc.ExecOpts{
   204  		PidFile:         filepath.Join(e.path, fmt.Sprintf("%s.pid", e.id)),
   205  		InternalPidFile: filepath.Join(e.path, fmt.Sprintf("%s-internal.pid", e.id)),
   206  		IO:              e.io,
   207  		Detach:          true,
   208  	}
   209  	defer func() {
   210  		_ = os.Remove(opts.PidFile)
   211  		_ = os.Remove(opts.InternalPidFile)
   212  	}()
   213  	if socket != nil {
   214  		opts.ConsoleSocket = socket
   215  	}
   216  
   217  	eventCh := e.parent.Monitor.Subscribe()
   218  	cu := cleanup.Make(func() {
   219  		e.parent.Monitor.Unsubscribe(eventCh)
   220  	})
   221  	defer cu.Clean()
   222  
   223  	if err := e.parent.runtime.Exec(ctx, e.parent.id, e.spec, opts); err != nil {
   224  		close(e.waitBlock)
   225  		return e.parent.runtimeError(err, "OCI runtime exec failed")
   226  	}
   227  	if e.stdio.Stdin != "" {
   228  		sc, err := fifo.OpenFifo(context.Background(), e.stdio.Stdin, unix.O_WRONLY|unix.O_NONBLOCK, 0)
   229  		if err != nil {
   230  			return fmt.Errorf("failed to open stdin fifo %s: %w", e.stdio.Stdin, err)
   231  		}
   232  		e.closers = append(e.closers, sc)
   233  		e.stdin = sc
   234  	}
   235  	ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
   236  	defer cancel()
   237  	if socket != nil {
   238  		console, err := socket.ReceiveMaster()
   239  		if err != nil {
   240  			return fmt.Errorf("failed to retrieve console master: %w", err)
   241  		}
   242  		if e.console, err = e.parent.Platform.CopyConsole(ctx, console, e.stdio.Stdin, e.stdio.Stdout, e.stdio.Stderr, &e.wg); err != nil {
   243  			return fmt.Errorf("failed to start console copy: %w", err)
   244  		}
   245  	} else if !e.stdio.IsNull() {
   246  		if err := copyPipes(ctx, e.io, e.stdio.Stdin, e.stdio.Stdout, e.stdio.Stderr, &e.wg); err != nil {
   247  			return fmt.Errorf("failed to start io pipe copy: %w", err)
   248  		}
   249  	}
   250  
   251  	pid, err := runc.ReadPidFile(opts.PidFile)
   252  	if err != nil {
   253  		return fmt.Errorf("failed to retrieve OCI runtime exec pid: %w", err)
   254  	}
   255  	e.pid = pid
   256  	internalPid, err := runc.ReadPidFile(opts.InternalPidFile)
   257  	if err != nil {
   258  		return fmt.Errorf("failed to retrieve OCI runtime exec internal pid: %w", err)
   259  	}
   260  	e.internalPid = internalPid
   261  
   262  	go func() {
   263  		defer e.parent.Monitor.Unsubscribe(eventCh)
   264  		for event := range eventCh {
   265  			if event.Pid == e.pid {
   266  				ExitCh <- Exit{
   267  					Timestamp: event.Timestamp,
   268  					ID:        e.id,
   269  					Status:    event.Status,
   270  				}
   271  				break
   272  			}
   273  		}
   274  	}()
   275  
   276  	cu.Release() // cancel cleanup on success.
   277  	return nil
   278  }
   279  
   280  func (e *execProcess) Status(context.Context) (string, error) {
   281  	e.mu.Lock()
   282  	defer e.mu.Unlock()
   283  	// if we don't have a pid then the exec process has just been created
   284  	if e.pid == 0 {
   285  		return "created", nil
   286  	}
   287  	// This checks that `runsc exec` process is still running. This process has
   288  	// the same lifetime as the process executing inside the container. So instead
   289  	// of calling `runsc kill --pid`, just do a quick check that `runsc exec` is
   290  	// still running.
   291  	if err := unix.Kill(e.pid, 0); err != nil {
   292  		// Can't signal the process, it must have exited.
   293  		return "stopped", nil
   294  	}
   295  	return "running", nil
   296  }