193 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			193 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// +build !windows
 | 
						|
 | 
						|
/*
 | 
						|
   Copyright The containerd Authors.
 | 
						|
 | 
						|
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
   you may not use this file except in compliance with the License.
 | 
						|
   You may obtain a copy of the License at
 | 
						|
 | 
						|
       http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
   Unless required by applicable law or agreed to in writing, software
 | 
						|
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
   See the License for the specific language governing permissions and
 | 
						|
   limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
package process
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"encoding/json"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"strings"
 | 
						|
	"sync"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/containerd/containerd/errdefs"
 | 
						|
	runc "github.com/containerd/go-runc"
 | 
						|
	"github.com/pkg/errors"
 | 
						|
	"golang.org/x/sys/unix"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	// RuncRoot is the path to the root runc state directory
 | 
						|
	RuncRoot = "/run/containerd/runc"
 | 
						|
	// StoppedPID is the pid assigned after a container has run and stopped
 | 
						|
	StoppedPID = -1
 | 
						|
	// InitPidFile name of the file that contains the init pid
 | 
						|
	InitPidFile = "init.pid"
 | 
						|
)
 | 
						|
 | 
						|
// safePid is a thread safe wrapper for pid.
 | 
						|
type safePid struct {
 | 
						|
	sync.Mutex
 | 
						|
	pid int
 | 
						|
}
 | 
						|
 | 
						|
func (s *safePid) get() int {
 | 
						|
	s.Lock()
 | 
						|
	defer s.Unlock()
 | 
						|
	return s.pid
 | 
						|
}
 | 
						|
 | 
						|
func (s *safePid) set(pid int) {
 | 
						|
	s.Lock()
 | 
						|
	s.pid = pid
 | 
						|
	s.Unlock()
 | 
						|
}
 | 
						|
 | 
						|
// TODO(mlaventure): move to runc package?
 | 
						|
func getLastRuntimeError(r *runc.Runc) (string, error) {
 | 
						|
	if r.Log == "" {
 | 
						|
		return "", nil
 | 
						|
	}
 | 
						|
 | 
						|
	f, err := os.OpenFile(r.Log, os.O_RDONLY, 0400)
 | 
						|
	if err != nil {
 | 
						|
		return "", err
 | 
						|
	}
 | 
						|
	defer f.Close()
 | 
						|
 | 
						|
	var (
 | 
						|
		errMsg string
 | 
						|
		log    struct {
 | 
						|
			Level string
 | 
						|
			Msg   string
 | 
						|
			Time  time.Time
 | 
						|
		}
 | 
						|
	)
 | 
						|
 | 
						|
	dec := json.NewDecoder(f)
 | 
						|
	for err = nil; err == nil; {
 | 
						|
		if err = dec.Decode(&log); err != nil && err != io.EOF {
 | 
						|
			return "", err
 | 
						|
		}
 | 
						|
		if log.Level == "error" {
 | 
						|
			errMsg = strings.TrimSpace(log.Msg)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return errMsg, nil
 | 
						|
}
 | 
						|
 | 
						|
// criuError returns only the first line of the error message from criu
 | 
						|
// it tries to add an invalid dump log location when returning the message
 | 
						|
func criuError(err error) string {
 | 
						|
	parts := strings.Split(err.Error(), "\n")
 | 
						|
	return parts[0]
 | 
						|
}
 | 
						|
 | 
						|
func copyFile(to, from string) error {
 | 
						|
	ff, err := os.Open(from)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	defer ff.Close()
 | 
						|
	tt, err := os.Create(to)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	defer tt.Close()
 | 
						|
 | 
						|
	p := bufPool.Get().(*[]byte)
 | 
						|
	defer bufPool.Put(p)
 | 
						|
	_, err = io.CopyBuffer(tt, ff, *p)
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
func checkKillError(err error) error {
 | 
						|
	if err == nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	if strings.Contains(err.Error(), "os: process already finished") ||
 | 
						|
		strings.Contains(err.Error(), "container not running") ||
 | 
						|
		err == unix.ESRCH {
 | 
						|
		return errors.Wrapf(errdefs.ErrNotFound, "process already finished")
 | 
						|
	}
 | 
						|
	return errors.Wrapf(err, "unknown error after kill")
 | 
						|
}
 | 
						|
 | 
						|
func newPidFile(bundle string) *pidFile {
 | 
						|
	return &pidFile{
 | 
						|
		path: filepath.Join(bundle, InitPidFile),
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func newExecPidFile(bundle, id string) *pidFile {
 | 
						|
	return &pidFile{
 | 
						|
		path: filepath.Join(bundle, fmt.Sprintf("%s.pid", id)),
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type pidFile struct {
 | 
						|
	path string
 | 
						|
}
 | 
						|
 | 
						|
func (p *pidFile) Path() string {
 | 
						|
	return p.path
 | 
						|
}
 | 
						|
 | 
						|
func (p *pidFile) Read() (int, error) {
 | 
						|
	return runc.ReadPidFile(p.path)
 | 
						|
}
 | 
						|
 | 
						|
// waitTimeout handles waiting on a waitgroup with a specified timeout.
 | 
						|
// this is commonly used for waiting on IO to finish after a process has exited
 | 
						|
func waitTimeout(ctx context.Context, wg *sync.WaitGroup, timeout time.Duration) error {
 | 
						|
	ctx, cancel := context.WithTimeout(ctx, timeout)
 | 
						|
	defer cancel()
 | 
						|
	done := make(chan struct{}, 1)
 | 
						|
	go func() {
 | 
						|
		wg.Wait()
 | 
						|
		close(done)
 | 
						|
	}()
 | 
						|
	select {
 | 
						|
	case <-done:
 | 
						|
		return nil
 | 
						|
	case <-ctx.Done():
 | 
						|
		return ctx.Err()
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func stateName(v interface{}) string {
 | 
						|
	switch v.(type) {
 | 
						|
	case *runningState, *execRunningState:
 | 
						|
		return "running"
 | 
						|
	case *createdState, *execCreatedState, *createdCheckpointState:
 | 
						|
		return "created"
 | 
						|
	case *pausedState:
 | 
						|
		return "paused"
 | 
						|
	case *deletedState:
 | 
						|
		return "deleted"
 | 
						|
	case *stoppedState:
 | 
						|
		return "stopped"
 | 
						|
	}
 | 
						|
	panic(errors.Errorf("invalid state %v", v))
 | 
						|
}
 |