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