gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/shim/proc/init_state.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  
    22  	"github.com/containerd/containerd/errdefs"
    23  	"github.com/containerd/containerd/pkg/process"
    24  	runc "github.com/containerd/go-runc"
    25  	"golang.org/x/sys/unix"
    26  )
    27  
    28  type stateTransition int
    29  
    30  const (
    31  	running stateTransition = iota
    32  	stopped
    33  	deleted
    34  )
    35  
    36  func (s stateTransition) String() string {
    37  	switch s {
    38  	case running:
    39  		return "running"
    40  	case stopped:
    41  		return "stopped"
    42  	case deleted:
    43  		return "deleted"
    44  	default:
    45  		panic(fmt.Sprintf("unknown state: %d", s))
    46  	}
    47  }
    48  
    49  type initState interface {
    50  	Start(context.Context) error
    51  	Delete(context.Context) error
    52  	Exec(context.Context, string, *ExecConfig) (process.Process, error)
    53  	State(ctx context.Context) (string, error)
    54  	Stats(context.Context, string) (*runc.Stats, error)
    55  	Kill(context.Context, uint32, bool) error
    56  	SetExited(int)
    57  }
    58  
    59  type createdState struct {
    60  	p *Init
    61  }
    62  
    63  func (s *createdState) name() string {
    64  	return "created"
    65  }
    66  
    67  func (s *createdState) transition(transition stateTransition) {
    68  	switch transition {
    69  	case running:
    70  		s.p.initState = &runningState{p: s.p}
    71  	case stopped:
    72  		s.p.initState = &stoppedState{process: s.p}
    73  	case deleted:
    74  		s.p.initState = &deletedState{}
    75  	default:
    76  		panic(fmt.Sprintf("invalid state transition %q to %q", s.name(), transition))
    77  	}
    78  }
    79  
    80  func (s *createdState) Start(ctx context.Context) error {
    81  	if err := s.p.start(ctx); err != nil {
    82  		// Containerd doesn't allow deleting container in created state.
    83  		// However, for gvisor, a non-root container in created state can
    84  		// only go to running state. If the container can't be started,
    85  		// it can only stay in created state, and never be deleted.
    86  		// To work around that, we treat non-root container in start failure
    87  		// state as stopped.
    88  		if !s.p.Sandbox {
    89  			s.p.io.Close()
    90  			s.p.setExited(internalErrorCode)
    91  			s.transition(stopped)
    92  		}
    93  		return err
    94  	}
    95  	s.transition(running)
    96  	return nil
    97  }
    98  
    99  func (s *createdState) Delete(ctx context.Context) error {
   100  	if err := s.p.delete(ctx); err != nil {
   101  		return err
   102  	}
   103  	s.transition(deleted)
   104  	return nil
   105  }
   106  
   107  func (s *createdState) Kill(ctx context.Context, sig uint32, all bool) error {
   108  	return s.p.kill(ctx, sig, all)
   109  }
   110  
   111  func (s *createdState) SetExited(status int) {
   112  	s.p.setExited(status)
   113  	s.transition(stopped)
   114  }
   115  
   116  func (s *createdState) Exec(ctx context.Context, path string, r *ExecConfig) (process.Process, error) {
   117  	return s.p.exec(path, r)
   118  }
   119  
   120  func (s *createdState) State(ctx context.Context) (string, error) {
   121  	state, err := s.p.state(ctx)
   122  	if err == nil && state == statusStopped {
   123  		s.transition(stopped)
   124  	}
   125  	return state, err
   126  }
   127  
   128  func (s *createdState) Stats(ctx context.Context, id string) (*runc.Stats, error) {
   129  	return s.p.stats(ctx, id)
   130  }
   131  
   132  type runningState struct {
   133  	p *Init
   134  }
   135  
   136  func (s *runningState) name() string {
   137  	return "running"
   138  }
   139  
   140  func (s *runningState) transition(transition stateTransition) {
   141  	switch transition {
   142  	case stopped:
   143  		s.p.initState = &stoppedState{process: s.p}
   144  	default:
   145  		panic(fmt.Sprintf("invalid state transition %q to %q", s.name(), transition))
   146  	}
   147  }
   148  
   149  func (s *runningState) Start(ctx context.Context) error {
   150  	return fmt.Errorf("cannot start a running container")
   151  }
   152  
   153  func (s *runningState) Delete(ctx context.Context) error {
   154  	return fmt.Errorf("cannot delete a running container")
   155  }
   156  
   157  func (s *runningState) Kill(ctx context.Context, sig uint32, all bool) error {
   158  	return s.p.kill(ctx, sig, all)
   159  }
   160  
   161  func (s *runningState) SetExited(status int) {
   162  	s.p.setExited(status)
   163  	s.transition(stopped)
   164  }
   165  
   166  func (s *runningState) Exec(_ context.Context, path string, r *ExecConfig) (process.Process, error) {
   167  	return s.p.exec(path, r)
   168  }
   169  
   170  func (s *runningState) State(ctx context.Context) (string, error) {
   171  	state, err := s.p.state(ctx)
   172  	if err == nil && state == "stopped" {
   173  		s.transition(stopped)
   174  	}
   175  	return state, err
   176  }
   177  
   178  func (s *runningState) Stats(ctx context.Context, id string) (*runc.Stats, error) {
   179  	return s.p.stats(ctx, id)
   180  }
   181  
   182  type stoppedState struct {
   183  	process *Init
   184  }
   185  
   186  func (s *stoppedState) name() string {
   187  	return "stopped"
   188  }
   189  
   190  func (s *stoppedState) transition(transition stateTransition) {
   191  	switch transition {
   192  	case deleted:
   193  		s.process.initState = &deletedState{}
   194  	default:
   195  		panic(fmt.Sprintf("invalid state transition %q to %q", s.name(), transition))
   196  	}
   197  }
   198  
   199  func (s *stoppedState) Start(context.Context) error {
   200  	return fmt.Errorf("cannot start a stopped container")
   201  }
   202  
   203  func (s *stoppedState) Delete(ctx context.Context) error {
   204  	if err := s.process.delete(ctx); err != nil {
   205  		return err
   206  	}
   207  	s.transition(deleted)
   208  	return nil
   209  }
   210  
   211  func (s *stoppedState) Kill(_ context.Context, signal uint32, _ bool) error {
   212  	return handleStoppedKill(signal)
   213  }
   214  
   215  func (s *stoppedState) SetExited(status int) {
   216  	s.process.setExited(status)
   217  }
   218  
   219  func (s *stoppedState) Exec(context.Context, string, *ExecConfig) (process.Process, error) {
   220  	return nil, fmt.Errorf("cannot exec in a stopped state")
   221  }
   222  
   223  func (s *stoppedState) State(context.Context) (string, error) {
   224  	return "stopped", nil
   225  }
   226  
   227  func (s *stoppedState) Stats(context.Context, string) (*runc.Stats, error) {
   228  	return nil, fmt.Errorf("cannot stat a stopped container")
   229  }
   230  
   231  func handleStoppedKill(signal uint32) error {
   232  	switch unix.Signal(signal) {
   233  	case unix.SIGTERM, unix.SIGKILL:
   234  		// Container is already stopped, so everything inside the container has
   235  		// already been killed.
   236  		return nil
   237  	default:
   238  		return errdefs.ToGRPCf(errdefs.ErrNotFound, "process not found")
   239  	}
   240  }