github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/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/nicocha30/gvisor-ligolo/pkg/cleanup"
    36  
    37  	"github.com/nicocha30/gvisor-ligolo/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() {
   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  }
   125  
   126  func (e *execProcess) Resize(ws console.WinSize) error {
   127  	e.mu.Lock()
   128  	defer e.mu.Unlock()
   129  
   130  	return e.execState.Resize(ws)
   131  }
   132  
   133  func (e *execProcess) resize(ws console.WinSize) error {
   134  	if e.console == nil {
   135  		return nil
   136  	}
   137  	return e.console.Resize(ws)
   138  }
   139  
   140  func (e *execProcess) Kill(ctx context.Context, sig uint32, _ bool) error {
   141  	e.mu.Lock()
   142  	defer e.mu.Unlock()
   143  
   144  	return e.execState.Kill(ctx, sig, false)
   145  }
   146  
   147  func (e *execProcess) kill(ctx context.Context, sig uint32, _ bool) error {
   148  	internalPid := e.internalPid
   149  	if internalPid == 0 {
   150  		return nil
   151  	}
   152  
   153  	opts := runsc.KillOpts{Pid: internalPid}
   154  	if err := e.parent.runtime.Kill(ctx, e.parent.id, int(sig), &opts); err != nil {
   155  		return fmt.Errorf("%s: %w", err.Error(), errdefs.ErrNotFound)
   156  	}
   157  	return nil
   158  }
   159  
   160  func (e *execProcess) Stdin() io.Closer {
   161  	return e.stdin
   162  }
   163  
   164  func (e *execProcess) Stdio() stdio.Stdio {
   165  	return e.stdio
   166  }
   167  
   168  func (e *execProcess) Start(ctx context.Context) error {
   169  	e.mu.Lock()
   170  	defer e.mu.Unlock()
   171  
   172  	return e.execState.Start(ctx)
   173  }
   174  
   175  func (e *execProcess) start(ctx context.Context) error {
   176  	var socket *runc.Socket
   177  
   178  	switch {
   179  	case e.stdio.Terminal:
   180  		s, err := runc.NewTempConsoleSocket()
   181  		if err != nil {
   182  			return fmt.Errorf("failed to create runc console socket: %w", err)
   183  		}
   184  		defer s.Close()
   185  		socket = s
   186  
   187  	case e.stdio.IsNull():
   188  		io, err := runc.NewNullIO()
   189  		if err != nil {
   190  			return fmt.Errorf("creating new NULL IO: %w", err)
   191  		}
   192  		e.io = io
   193  
   194  	default:
   195  		io, err := runc.NewPipeIO(e.parent.IoUID, e.parent.IoGID, withConditionalIO(e.stdio))
   196  		if err != nil {
   197  			return fmt.Errorf("failed to create runc io pipes: %w", err)
   198  		}
   199  		e.io = io
   200  	}
   201  
   202  	opts := &runsc.ExecOpts{
   203  		PidFile:         filepath.Join(e.path, fmt.Sprintf("%s.pid", e.id)),
   204  		InternalPidFile: filepath.Join(e.path, fmt.Sprintf("%s-internal.pid", e.id)),
   205  		IO:              e.io,
   206  		Detach:          true,
   207  	}
   208  	defer func() {
   209  		_ = os.Remove(opts.PidFile)
   210  		_ = os.Remove(opts.InternalPidFile)
   211  	}()
   212  	if socket != nil {
   213  		opts.ConsoleSocket = socket
   214  	}
   215  
   216  	eventCh := e.parent.Monitor.Subscribe()
   217  	cu := cleanup.Make(func() {
   218  		e.parent.Monitor.Unsubscribe(eventCh)
   219  	})
   220  	defer cu.Clean()
   221  
   222  	if err := e.parent.runtime.Exec(ctx, e.parent.id, e.spec, opts); err != nil {
   223  		close(e.waitBlock)
   224  		return e.parent.runtimeError(err, "OCI runtime exec failed")
   225  	}
   226  	if e.stdio.Stdin != "" {
   227  		sc, err := fifo.OpenFifo(context.Background(), e.stdio.Stdin, unix.O_WRONLY|unix.O_NONBLOCK, 0)
   228  		if err != nil {
   229  			return fmt.Errorf("failed to open stdin fifo %s: %w", e.stdio.Stdin, err)
   230  		}
   231  		e.closers = append(e.closers, sc)
   232  		e.stdin = sc
   233  	}
   234  	ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
   235  	defer cancel()
   236  	if socket != nil {
   237  		console, err := socket.ReceiveMaster()
   238  		if err != nil {
   239  			return fmt.Errorf("failed to retrieve console master: %w", err)
   240  		}
   241  		if e.console, err = e.parent.Platform.CopyConsole(ctx, console, e.stdio.Stdin, e.stdio.Stdout, e.stdio.Stderr, &e.wg); err != nil {
   242  			return fmt.Errorf("failed to start console copy: %w", err)
   243  		}
   244  	} else if !e.stdio.IsNull() {
   245  		if err := copyPipes(ctx, e.io, e.stdio.Stdin, e.stdio.Stdout, e.stdio.Stderr, &e.wg); err != nil {
   246  			return fmt.Errorf("failed to start io pipe copy: %w", err)
   247  		}
   248  	}
   249  
   250  	pid, err := runc.ReadPidFile(opts.PidFile)
   251  	if err != nil {
   252  		return fmt.Errorf("failed to retrieve OCI runtime exec pid: %w", err)
   253  	}
   254  	e.pid = pid
   255  	internalPid, err := runc.ReadPidFile(opts.InternalPidFile)
   256  	if err != nil {
   257  		return fmt.Errorf("failed to retrieve OCI runtime exec internal pid: %w", err)
   258  	}
   259  	e.internalPid = internalPid
   260  
   261  	go func() {
   262  		defer e.parent.Monitor.Unsubscribe(eventCh)
   263  		for event := range eventCh {
   264  			if event.Pid == e.pid {
   265  				ExitCh <- Exit{
   266  					Timestamp: event.Timestamp,
   267  					ID:        e.id,
   268  					Status:    event.Status,
   269  				}
   270  				break
   271  			}
   272  		}
   273  	}()
   274  
   275  	cu.Release() // cancel cleanup on success.
   276  	return nil
   277  }
   278  
   279  func (e *execProcess) Status(context.Context) (string, error) {
   280  	e.mu.Lock()
   281  	defer e.mu.Unlock()
   282  	// if we don't have a pid then the exec process has just been created
   283  	if e.pid == 0 {
   284  		return "created", nil
   285  	}
   286  	// This checks that `runsc exec` process is still running. This process has
   287  	// the same lifetime as the process executing inside the container. So instead
   288  	// of calling `runsc kill --pid`, just do a quick check that `runsc exec` is
   289  	// still running.
   290  	if err := unix.Kill(e.pid, 0); err != nil {
   291  		// Can't signal the process, it must have exited.
   292  		return "stopped", nil
   293  	}
   294  	return "running", nil
   295  }