github.com/demonoid81/containerd@v1.3.4/runtime/v2/runc/v1/service.go (about)

     1  // +build linux
     2  
     3  /*
     4     Copyright The containerd Authors.
     5  
     6     Licensed under the Apache License, Version 2.0 (the "License");
     7     you may not use this file except in compliance with the License.
     8     You may obtain a copy of the License at
     9  
    10         http://www.apache.org/licenses/LICENSE-2.0
    11  
    12     Unless required by applicable law or agreed to in writing, software
    13     distributed under the License is distributed on an "AS IS" BASIS,
    14     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15     See the License for the specific language governing permissions and
    16     limitations under the License.
    17  */
    18  
    19  package v1
    20  
    21  import (
    22  	"context"
    23  	"encoding/json"
    24  	"io/ioutil"
    25  	"os"
    26  	"os/exec"
    27  	"path/filepath"
    28  	"sync"
    29  	"syscall"
    30  	"time"
    31  
    32  	"github.com/containerd/cgroups"
    33  	eventstypes "github.com/containerd/containerd/api/events"
    34  	"github.com/containerd/containerd/api/types/task"
    35  	"github.com/containerd/containerd/errdefs"
    36  	"github.com/containerd/containerd/log"
    37  	"github.com/containerd/containerd/mount"
    38  	"github.com/containerd/containerd/namespaces"
    39  	"github.com/containerd/containerd/pkg/oom"
    40  	"github.com/containerd/containerd/pkg/process"
    41  	"github.com/containerd/containerd/pkg/stdio"
    42  	"github.com/containerd/containerd/runtime/v2/runc"
    43  	"github.com/containerd/containerd/runtime/v2/runc/options"
    44  	"github.com/containerd/containerd/runtime/v2/shim"
    45  	taskAPI "github.com/containerd/containerd/runtime/v2/task"
    46  	"github.com/containerd/containerd/sys/reaper"
    47  	runcC "github.com/containerd/go-runc"
    48  	"github.com/containerd/typeurl"
    49  	"github.com/gogo/protobuf/proto"
    50  	"github.com/gogo/protobuf/types"
    51  	ptypes "github.com/gogo/protobuf/types"
    52  	specs "github.com/opencontainers/runtime-spec/specs-go"
    53  	"github.com/pkg/errors"
    54  	"github.com/sirupsen/logrus"
    55  	"golang.org/x/sys/unix"
    56  )
    57  
    58  var (
    59  	_     = (taskAPI.TaskService)(&service{})
    60  	empty = &ptypes.Empty{}
    61  )
    62  
    63  // New returns a new shim service that can be used via GRPC
    64  func New(ctx context.Context, id string, publisher shim.Publisher, shutdown func()) (shim.Shim, error) {
    65  	ep, err := oom.New(publisher)
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  	go ep.Run(ctx)
    70  	s := &service{
    71  		id:      id,
    72  		context: ctx,
    73  		events:  make(chan interface{}, 128),
    74  		ec:      reaper.Default.Subscribe(),
    75  		ep:      ep,
    76  		cancel:  shutdown,
    77  	}
    78  	go s.processExits()
    79  	runcC.Monitor = reaper.Default
    80  	if err := s.initPlatform(); err != nil {
    81  		shutdown()
    82  		return nil, errors.Wrap(err, "failed to initialized platform behavior")
    83  	}
    84  	go s.forward(ctx, publisher)
    85  	return s, nil
    86  }
    87  
    88  // service is the shim implementation of a remote shim over GRPC
    89  type service struct {
    90  	mu          sync.Mutex
    91  	eventSendMu sync.Mutex
    92  
    93  	context  context.Context
    94  	events   chan interface{}
    95  	platform stdio.Platform
    96  	ec       chan runcC.Exit
    97  	ep       *oom.Epoller
    98  
    99  	id        string
   100  	container *runc.Container
   101  
   102  	cancel func()
   103  }
   104  
   105  func newCommand(ctx context.Context, id, containerdBinary, containerdAddress, containerdTTRPCAddress string) (*exec.Cmd, error) {
   106  	ns, err := namespaces.NamespaceRequired(ctx)
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  	self, err := os.Executable()
   111  	if err != nil {
   112  		return nil, err
   113  	}
   114  	cwd, err := os.Getwd()
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  	args := []string{
   119  		"-namespace", ns,
   120  		"-id", id,
   121  		"-address", containerdAddress,
   122  	}
   123  	cmd := exec.Command(self, args...)
   124  	cmd.Dir = cwd
   125  	cmd.Env = append(os.Environ(), "GOMAXPROCS=2")
   126  	cmd.SysProcAttr = &syscall.SysProcAttr{
   127  		Setpgid: true,
   128  	}
   129  	return cmd, nil
   130  }
   131  
   132  func (s *service) StartShim(ctx context.Context, id, containerdBinary, containerdAddress, containerdTTRPCAddress string) (string, error) {
   133  	cmd, err := newCommand(ctx, id, containerdBinary, containerdAddress, containerdTTRPCAddress)
   134  	if err != nil {
   135  		return "", err
   136  	}
   137  	address, err := shim.SocketAddress(ctx, id)
   138  	if err != nil {
   139  		return "", err
   140  	}
   141  	socket, err := shim.NewSocket(address)
   142  	if err != nil {
   143  		return "", err
   144  	}
   145  	defer socket.Close()
   146  	f, err := socket.File()
   147  	if err != nil {
   148  		return "", err
   149  	}
   150  	defer f.Close()
   151  
   152  	cmd.ExtraFiles = append(cmd.ExtraFiles, f)
   153  
   154  	if err := cmd.Start(); err != nil {
   155  		return "", err
   156  	}
   157  	defer func() {
   158  		if err != nil {
   159  			cmd.Process.Kill()
   160  		}
   161  	}()
   162  	// make sure to wait after start
   163  	go cmd.Wait()
   164  	if err := shim.WritePidFile("shim.pid", cmd.Process.Pid); err != nil {
   165  		return "", err
   166  	}
   167  	if err := shim.WriteAddress("address", address); err != nil {
   168  		return "", err
   169  	}
   170  	if data, err := ioutil.ReadAll(os.Stdin); err == nil {
   171  		if len(data) > 0 {
   172  			var any types.Any
   173  			if err := proto.Unmarshal(data, &any); err != nil {
   174  				return "", err
   175  			}
   176  			v, err := typeurl.UnmarshalAny(&any)
   177  			if err != nil {
   178  				return "", err
   179  			}
   180  			if opts, ok := v.(*options.Options); ok {
   181  				if opts.ShimCgroup != "" {
   182  					cg, err := cgroups.Load(cgroups.V1, cgroups.StaticPath(opts.ShimCgroup))
   183  					if err != nil {
   184  						return "", errors.Wrapf(err, "failed to load cgroup %s", opts.ShimCgroup)
   185  					}
   186  					if err := cg.Add(cgroups.Process{
   187  						Pid: cmd.Process.Pid,
   188  					}); err != nil {
   189  						return "", errors.Wrapf(err, "failed to join cgroup %s", opts.ShimCgroup)
   190  					}
   191  				}
   192  			}
   193  		}
   194  	}
   195  	if err := shim.AdjustOOMScore(cmd.Process.Pid); err != nil {
   196  		return "", errors.Wrap(err, "failed to adjust OOM score for shim")
   197  	}
   198  	return address, nil
   199  }
   200  
   201  func (s *service) Cleanup(ctx context.Context) (*taskAPI.DeleteResponse, error) {
   202  	path, err := os.Getwd()
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  	ns, err := namespaces.NamespaceRequired(ctx)
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  	runtime, err := runc.ReadRuntime(path)
   211  	if err != nil {
   212  		return nil, err
   213  	}
   214  	r := process.NewRunc(process.RuncRoot, path, ns, runtime, "", false)
   215  	if err := r.Delete(ctx, s.id, &runcC.DeleteOpts{
   216  		Force: true,
   217  	}); err != nil {
   218  		logrus.WithError(err).Warn("failed to remove runc container")
   219  	}
   220  	if err := mount.UnmountAll(filepath.Join(path, "rootfs"), 0); err != nil {
   221  		logrus.WithError(err).Warn("failed to cleanup rootfs mount")
   222  	}
   223  	return &taskAPI.DeleteResponse{
   224  		ExitedAt:   time.Now(),
   225  		ExitStatus: 128 + uint32(unix.SIGKILL),
   226  	}, nil
   227  }
   228  
   229  // Create a new initial process and container with the underlying OCI runtime
   230  func (s *service) Create(ctx context.Context, r *taskAPI.CreateTaskRequest) (_ *taskAPI.CreateTaskResponse, err error) {
   231  	s.mu.Lock()
   232  	defer s.mu.Unlock()
   233  
   234  	container, err := runc.NewContainer(ctx, s.platform, r)
   235  	if err != nil {
   236  		return nil, err
   237  	}
   238  
   239  	s.container = container
   240  
   241  	s.send(&eventstypes.TaskCreate{
   242  		ContainerID: r.ID,
   243  		Bundle:      r.Bundle,
   244  		Rootfs:      r.Rootfs,
   245  		IO: &eventstypes.TaskIO{
   246  			Stdin:    r.Stdin,
   247  			Stdout:   r.Stdout,
   248  			Stderr:   r.Stderr,
   249  			Terminal: r.Terminal,
   250  		},
   251  		Checkpoint: r.Checkpoint,
   252  		Pid:        uint32(container.Pid()),
   253  	})
   254  
   255  	return &taskAPI.CreateTaskResponse{
   256  		Pid: uint32(container.Pid()),
   257  	}, nil
   258  }
   259  
   260  // Start a process
   261  func (s *service) Start(ctx context.Context, r *taskAPI.StartRequest) (*taskAPI.StartResponse, error) {
   262  	container, err := s.getContainer()
   263  	if err != nil {
   264  		return nil, err
   265  	}
   266  
   267  	// hold the send lock so that the start events are sent before any exit events in the error case
   268  	s.eventSendMu.Lock()
   269  	p, err := container.Start(ctx, r)
   270  	if err != nil {
   271  		s.eventSendMu.Unlock()
   272  		return nil, errdefs.ToGRPC(err)
   273  	}
   274  	switch r.ExecID {
   275  	case "":
   276  		if err := s.ep.Add(container.ID, container.Cgroup()); err != nil {
   277  			logrus.WithError(err).Error("add cg to OOM monitor")
   278  		}
   279  		s.send(&eventstypes.TaskStart{
   280  			ContainerID: container.ID,
   281  			Pid:         uint32(p.Pid()),
   282  		})
   283  	default:
   284  		s.send(&eventstypes.TaskExecStarted{
   285  			ContainerID: container.ID,
   286  			ExecID:      r.ExecID,
   287  			Pid:         uint32(p.Pid()),
   288  		})
   289  	}
   290  	s.eventSendMu.Unlock()
   291  	return &taskAPI.StartResponse{
   292  		Pid: uint32(p.Pid()),
   293  	}, nil
   294  }
   295  
   296  // Delete the initial process and container
   297  func (s *service) Delete(ctx context.Context, r *taskAPI.DeleteRequest) (*taskAPI.DeleteResponse, error) {
   298  	container, err := s.getContainer()
   299  	if err != nil {
   300  		return nil, err
   301  	}
   302  	p, err := container.Delete(ctx, r)
   303  	if err != nil {
   304  		return nil, errdefs.ToGRPC(err)
   305  	}
   306  	// if we deleted our init task, close the platform and send the task delete event
   307  	if r.ExecID == "" {
   308  		if s.platform != nil {
   309  			s.platform.Close()
   310  		}
   311  		s.send(&eventstypes.TaskDelete{
   312  			ContainerID: container.ID,
   313  			Pid:         uint32(p.Pid()),
   314  			ExitStatus:  uint32(p.ExitStatus()),
   315  			ExitedAt:    p.ExitedAt(),
   316  		})
   317  	}
   318  	return &taskAPI.DeleteResponse{
   319  		ExitStatus: uint32(p.ExitStatus()),
   320  		ExitedAt:   p.ExitedAt(),
   321  		Pid:        uint32(p.Pid()),
   322  	}, nil
   323  }
   324  
   325  // Exec an additional process inside the container
   326  func (s *service) Exec(ctx context.Context, r *taskAPI.ExecProcessRequest) (*ptypes.Empty, error) {
   327  	container, err := s.getContainer()
   328  	if err != nil {
   329  		return nil, err
   330  	}
   331  	ok, cancel := container.ReserveProcess(r.ExecID)
   332  	if !ok {
   333  		return nil, errdefs.ToGRPCf(errdefs.ErrAlreadyExists, "id %s", r.ExecID)
   334  	}
   335  	process, err := container.Exec(ctx, r)
   336  	if err != nil {
   337  		cancel()
   338  		return nil, errdefs.ToGRPC(err)
   339  	}
   340  
   341  	s.send(&eventstypes.TaskExecAdded{
   342  		ContainerID: s.container.ID,
   343  		ExecID:      process.ID(),
   344  	})
   345  	return empty, nil
   346  }
   347  
   348  // ResizePty of a process
   349  func (s *service) ResizePty(ctx context.Context, r *taskAPI.ResizePtyRequest) (*ptypes.Empty, error) {
   350  	container, err := s.getContainer()
   351  	if err != nil {
   352  		return nil, err
   353  	}
   354  	if err := container.ResizePty(ctx, r); err != nil {
   355  		return nil, errdefs.ToGRPC(err)
   356  	}
   357  	return empty, nil
   358  }
   359  
   360  // State returns runtime state information for a process
   361  func (s *service) State(ctx context.Context, r *taskAPI.StateRequest) (*taskAPI.StateResponse, error) {
   362  	p, err := s.getProcess(r.ExecID)
   363  	if err != nil {
   364  		return nil, err
   365  	}
   366  	st, err := p.Status(ctx)
   367  	if err != nil {
   368  		return nil, err
   369  	}
   370  	status := task.StatusUnknown
   371  	switch st {
   372  	case "created":
   373  		status = task.StatusCreated
   374  	case "running":
   375  		status = task.StatusRunning
   376  	case "stopped":
   377  		status = task.StatusStopped
   378  	case "paused":
   379  		status = task.StatusPaused
   380  	case "pausing":
   381  		status = task.StatusPausing
   382  	}
   383  	sio := p.Stdio()
   384  	return &taskAPI.StateResponse{
   385  		ID:         p.ID(),
   386  		Bundle:     s.container.Bundle,
   387  		Pid:        uint32(p.Pid()),
   388  		Status:     status,
   389  		Stdin:      sio.Stdin,
   390  		Stdout:     sio.Stdout,
   391  		Stderr:     sio.Stderr,
   392  		Terminal:   sio.Terminal,
   393  		ExitStatus: uint32(p.ExitStatus()),
   394  		ExitedAt:   p.ExitedAt(),
   395  	}, nil
   396  }
   397  
   398  // Pause the container
   399  func (s *service) Pause(ctx context.Context, r *taskAPI.PauseRequest) (*ptypes.Empty, error) {
   400  	container, err := s.getContainer()
   401  	if err != nil {
   402  		return nil, err
   403  	}
   404  	if err := container.Pause(ctx); err != nil {
   405  		return nil, errdefs.ToGRPC(err)
   406  	}
   407  	s.send(&eventstypes.TaskPaused{
   408  		ContainerID: container.ID,
   409  	})
   410  	return empty, nil
   411  }
   412  
   413  // Resume the container
   414  func (s *service) Resume(ctx context.Context, r *taskAPI.ResumeRequest) (*ptypes.Empty, error) {
   415  	container, err := s.getContainer()
   416  	if err != nil {
   417  		return nil, err
   418  	}
   419  	if err := container.Resume(ctx); err != nil {
   420  		return nil, errdefs.ToGRPC(err)
   421  	}
   422  	s.send(&eventstypes.TaskResumed{
   423  		ContainerID: container.ID,
   424  	})
   425  	return empty, nil
   426  }
   427  
   428  // Kill a process with the provided signal
   429  func (s *service) Kill(ctx context.Context, r *taskAPI.KillRequest) (*ptypes.Empty, error) {
   430  	container, err := s.getContainer()
   431  	if err != nil {
   432  		return nil, err
   433  	}
   434  	if err := container.Kill(ctx, r); err != nil {
   435  		return nil, errdefs.ToGRPC(err)
   436  	}
   437  	return empty, nil
   438  }
   439  
   440  // Pids returns all pids inside the container
   441  func (s *service) Pids(ctx context.Context, r *taskAPI.PidsRequest) (*taskAPI.PidsResponse, error) {
   442  	container, err := s.getContainer()
   443  	if err != nil {
   444  		return nil, err
   445  	}
   446  	pids, err := s.getContainerPids(ctx, r.ID)
   447  	if err != nil {
   448  		return nil, errdefs.ToGRPC(err)
   449  	}
   450  	var processes []*task.ProcessInfo
   451  	for _, pid := range pids {
   452  		pInfo := task.ProcessInfo{
   453  			Pid: pid,
   454  		}
   455  		for _, p := range container.ExecdProcesses() {
   456  			if p.Pid() == int(pid) {
   457  				d := &options.ProcessDetails{
   458  					ExecID: p.ID(),
   459  				}
   460  				a, err := typeurl.MarshalAny(d)
   461  				if err != nil {
   462  					return nil, errors.Wrapf(err, "failed to marshal process %d info", pid)
   463  				}
   464  				pInfo.Info = a
   465  				break
   466  			}
   467  		}
   468  		processes = append(processes, &pInfo)
   469  	}
   470  	return &taskAPI.PidsResponse{
   471  		Processes: processes,
   472  	}, nil
   473  }
   474  
   475  // CloseIO of a process
   476  func (s *service) CloseIO(ctx context.Context, r *taskAPI.CloseIORequest) (*ptypes.Empty, error) {
   477  	container, err := s.getContainer()
   478  	if err != nil {
   479  		return nil, err
   480  	}
   481  	if err := container.CloseIO(ctx, r); err != nil {
   482  		return nil, err
   483  	}
   484  	return empty, nil
   485  }
   486  
   487  // Checkpoint the container
   488  func (s *service) Checkpoint(ctx context.Context, r *taskAPI.CheckpointTaskRequest) (*ptypes.Empty, error) {
   489  	container, err := s.getContainer()
   490  	if err != nil {
   491  		return nil, err
   492  	}
   493  	if err := container.Checkpoint(ctx, r); err != nil {
   494  		return nil, errdefs.ToGRPC(err)
   495  	}
   496  	return empty, nil
   497  }
   498  
   499  // Update a running container
   500  func (s *service) Update(ctx context.Context, r *taskAPI.UpdateTaskRequest) (*ptypes.Empty, error) {
   501  	container, err := s.getContainer()
   502  	if err != nil {
   503  		return nil, err
   504  	}
   505  	if err := container.Update(ctx, r); err != nil {
   506  		return nil, errdefs.ToGRPC(err)
   507  	}
   508  	return empty, nil
   509  }
   510  
   511  // Wait for a process to exit
   512  func (s *service) Wait(ctx context.Context, r *taskAPI.WaitRequest) (*taskAPI.WaitResponse, error) {
   513  	container, err := s.getContainer()
   514  	if err != nil {
   515  		return nil, err
   516  	}
   517  	p, err := container.Process(r.ExecID)
   518  	if err != nil {
   519  		return nil, errdefs.ToGRPC(err)
   520  	}
   521  	p.Wait()
   522  
   523  	return &taskAPI.WaitResponse{
   524  		ExitStatus: uint32(p.ExitStatus()),
   525  		ExitedAt:   p.ExitedAt(),
   526  	}, nil
   527  }
   528  
   529  // Connect returns shim information such as the shim's pid
   530  func (s *service) Connect(ctx context.Context, r *taskAPI.ConnectRequest) (*taskAPI.ConnectResponse, error) {
   531  	var pid int
   532  	if s.container != nil {
   533  		pid = s.container.Pid()
   534  	}
   535  	return &taskAPI.ConnectResponse{
   536  		ShimPid: uint32(os.Getpid()),
   537  		TaskPid: uint32(pid),
   538  	}, nil
   539  }
   540  
   541  func (s *service) Shutdown(ctx context.Context, r *taskAPI.ShutdownRequest) (*ptypes.Empty, error) {
   542  	s.cancel()
   543  	close(s.events)
   544  	return empty, nil
   545  }
   546  
   547  func (s *service) Stats(ctx context.Context, r *taskAPI.StatsRequest) (*taskAPI.StatsResponse, error) {
   548  	cg := s.container.Cgroup()
   549  	if cg == nil {
   550  		return nil, errdefs.ToGRPCf(errdefs.ErrNotFound, "cgroup does not exist")
   551  	}
   552  	stats, err := cg.Stat(cgroups.IgnoreNotExist)
   553  	if err != nil {
   554  		return nil, err
   555  	}
   556  	data, err := typeurl.MarshalAny(stats)
   557  	if err != nil {
   558  		return nil, err
   559  	}
   560  	return &taskAPI.StatsResponse{
   561  		Stats: data,
   562  	}, nil
   563  }
   564  
   565  func (s *service) processExits() {
   566  	for e := range s.ec {
   567  		s.checkProcesses(e)
   568  	}
   569  }
   570  
   571  func (s *service) send(evt interface{}) {
   572  	s.events <- evt
   573  }
   574  
   575  func (s *service) sendL(evt interface{}) {
   576  	s.eventSendMu.Lock()
   577  	s.events <- evt
   578  	s.eventSendMu.Unlock()
   579  }
   580  
   581  func (s *service) checkProcesses(e runcC.Exit) {
   582  	container, err := s.getContainer()
   583  	if err != nil {
   584  		return
   585  	}
   586  
   587  	shouldKillAll, err := shouldKillAllOnExit(container.Bundle)
   588  	if err != nil {
   589  		log.G(s.context).WithError(err).Error("failed to check shouldKillAll")
   590  	}
   591  
   592  	for _, p := range container.All() {
   593  		if p.Pid() == e.Pid {
   594  			if shouldKillAll {
   595  				if ip, ok := p.(*process.Init); ok {
   596  					// Ensure all children are killed
   597  					if err := ip.KillAll(s.context); err != nil {
   598  						logrus.WithError(err).WithField("id", ip.ID()).
   599  							Error("failed to kill init's children")
   600  					}
   601  				}
   602  			}
   603  			p.SetExited(e.Status)
   604  			s.sendL(&eventstypes.TaskExit{
   605  				ContainerID: container.ID,
   606  				ID:          p.ID(),
   607  				Pid:         uint32(e.Pid),
   608  				ExitStatus:  uint32(e.Status),
   609  				ExitedAt:    p.ExitedAt(),
   610  			})
   611  			return
   612  		}
   613  	}
   614  }
   615  
   616  func shouldKillAllOnExit(bundlePath string) (bool, error) {
   617  	var bundleSpec specs.Spec
   618  	bundleConfigContents, err := ioutil.ReadFile(filepath.Join(bundlePath, "config.json"))
   619  	if err != nil {
   620  		return false, err
   621  	}
   622  	json.Unmarshal(bundleConfigContents, &bundleSpec)
   623  
   624  	if bundleSpec.Linux != nil {
   625  		for _, ns := range bundleSpec.Linux.Namespaces {
   626  			if ns.Type == specs.PIDNamespace && ns.Path == "" {
   627  				return false, nil
   628  			}
   629  		}
   630  	}
   631  
   632  	return true, nil
   633  }
   634  
   635  func (s *service) getContainerPids(ctx context.Context, id string) ([]uint32, error) {
   636  	p, err := s.container.Process("")
   637  	if err != nil {
   638  		return nil, errdefs.ToGRPC(err)
   639  	}
   640  	ps, err := p.(*process.Init).Runtime().Ps(ctx, id)
   641  	if err != nil {
   642  		return nil, err
   643  	}
   644  	pids := make([]uint32, 0, len(ps))
   645  	for _, pid := range ps {
   646  		pids = append(pids, uint32(pid))
   647  	}
   648  	return pids, nil
   649  }
   650  
   651  func (s *service) forward(ctx context.Context, publisher shim.Publisher) {
   652  	ns, _ := namespaces.Namespace(ctx)
   653  	ctx = namespaces.WithNamespace(context.Background(), ns)
   654  	for e := range s.events {
   655  		ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
   656  		err := publisher.Publish(ctx, runc.GetTopic(e), e)
   657  		cancel()
   658  		if err != nil {
   659  			logrus.WithError(err).Error("post event")
   660  		}
   661  	}
   662  	publisher.Close()
   663  }
   664  
   665  func (s *service) getContainer() (*runc.Container, error) {
   666  	s.mu.Lock()
   667  	container := s.container
   668  	s.mu.Unlock()
   669  	if container == nil {
   670  		return nil, errdefs.ToGRPCf(errdefs.ErrNotFound, "container not created")
   671  	}
   672  	return container, nil
   673  }
   674  
   675  func (s *service) getProcess(execID string) (process.Process, error) {
   676  	container, err := s.getContainer()
   677  	if err != nil {
   678  		return nil, err
   679  	}
   680  	p, err := container.Process(execID)
   681  	if err != nil {
   682  		return nil, errdefs.ToGRPC(err)
   683  	}
   684  	return p, nil
   685  }
   686  
   687  // initialize a single epoll fd to manage our consoles. `initPlatform` should
   688  // only be called once.
   689  func (s *service) initPlatform() error {
   690  	if s.platform != nil {
   691  		return nil
   692  	}
   693  	p, err := runc.NewPlatform()
   694  	if err != nil {
   695  		return err
   696  	}
   697  	s.platform = p
   698  	return nil
   699  }