github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/runtime/v2/shim/util.go (about)

     1  /*
     2     Copyright The containerd 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         http://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  
    17  package shim
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"net"
    25  	"os"
    26  	"os/exec"
    27  	"path/filepath"
    28  	"strings"
    29  	"sync"
    30  	"time"
    31  
    32  	"github.com/containerd/containerd/namespaces"
    33  	"github.com/gogo/protobuf/proto"
    34  	"github.com/gogo/protobuf/types"
    35  	"github.com/pkg/errors"
    36  )
    37  
    38  var runtimePaths sync.Map
    39  
    40  // Command returns the shim command with the provided args and configuration
    41  func Command(ctx context.Context, runtime, containerdAddress, containerdTTRPCAddress, path string, opts *types.Any, cmdArgs ...string) (*exec.Cmd, error) {
    42  	ns, err := namespaces.NamespaceRequired(ctx)
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  	self, err := os.Executable()
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  	args := []string{
    51  		"-namespace", ns,
    52  		"-address", containerdAddress,
    53  		"-publish-binary", self,
    54  	}
    55  	args = append(args, cmdArgs...)
    56  	name := BinaryName(runtime)
    57  	if name == "" {
    58  		return nil, fmt.Errorf("invalid runtime name %s, correct runtime name should format like io.containerd.runc.v1", runtime)
    59  	}
    60  
    61  	var cmdPath string
    62  	cmdPathI, cmdPathFound := runtimePaths.Load(name)
    63  	if cmdPathFound {
    64  		cmdPath = cmdPathI.(string)
    65  	} else {
    66  		var lerr error
    67  		if cmdPath, lerr = exec.LookPath(name); lerr != nil {
    68  			if eerr, ok := lerr.(*exec.Error); ok {
    69  				if eerr.Err == exec.ErrNotFound {
    70  					// LookPath only finds current directory matches based on
    71  					// the callers current directory but the caller is not
    72  					// likely in the same directory as the containerd
    73  					// executables. Instead match the calling binaries path
    74  					// (containerd) and see if they are side by side. If so
    75  					// execute the shim found there.
    76  					testPath := filepath.Join(filepath.Dir(self), name)
    77  					if _, serr := os.Stat(testPath); serr == nil {
    78  						cmdPath = testPath
    79  					}
    80  					if cmdPath == "" {
    81  						return nil, errors.Wrapf(os.ErrNotExist, "runtime %q binary not installed %q", runtime, name)
    82  					}
    83  				}
    84  			}
    85  		}
    86  		cmdPath, err = filepath.Abs(cmdPath)
    87  		if err != nil {
    88  			return nil, err
    89  		}
    90  		if cmdPathI, cmdPathFound = runtimePaths.LoadOrStore(name, cmdPath); cmdPathFound {
    91  			// We didn't store cmdPath we loaded an already cached value. Use it.
    92  			cmdPath = cmdPathI.(string)
    93  		}
    94  	}
    95  
    96  	cmd := exec.Command(cmdPath, args...)
    97  	cmd.Dir = path
    98  	cmd.Env = append(
    99  		os.Environ(),
   100  		"GOMAXPROCS=2",
   101  		fmt.Sprintf("%s=%s", ttrpcAddressEnv, containerdTTRPCAddress),
   102  	)
   103  	cmd.SysProcAttr = getSysProcAttr()
   104  	if opts != nil {
   105  		d, err := proto.Marshal(opts)
   106  		if err != nil {
   107  			return nil, err
   108  		}
   109  		cmd.Stdin = bytes.NewReader(d)
   110  	}
   111  	return cmd, nil
   112  }
   113  
   114  // BinaryName returns the shim binary name from the runtime name,
   115  // empty string returns means runtime name is invalid
   116  func BinaryName(runtime string) string {
   117  	// runtime name should format like $prefix.name.version
   118  	parts := strings.Split(runtime, ".")
   119  	if len(parts) < 2 {
   120  		return ""
   121  	}
   122  
   123  	return fmt.Sprintf(shimBinaryFormat, parts[len(parts)-2], parts[len(parts)-1])
   124  }
   125  
   126  // Connect to the provided address
   127  func Connect(address string, d func(string, time.Duration) (net.Conn, error)) (net.Conn, error) {
   128  	return d(address, 100*time.Second)
   129  }
   130  
   131  // WritePidFile writes a pid file atomically
   132  func WritePidFile(path string, pid int) error {
   133  	path, err := filepath.Abs(path)
   134  	if err != nil {
   135  		return err
   136  	}
   137  	tempPath := filepath.Join(filepath.Dir(path), fmt.Sprintf(".%s", filepath.Base(path)))
   138  	f, err := os.OpenFile(tempPath, os.O_RDWR|os.O_CREATE|os.O_EXCL|os.O_SYNC, 0666)
   139  	if err != nil {
   140  		return err
   141  	}
   142  	_, err = fmt.Fprintf(f, "%d", pid)
   143  	f.Close()
   144  	if err != nil {
   145  		return err
   146  	}
   147  	return os.Rename(tempPath, path)
   148  }
   149  
   150  // WriteAddress writes a address file atomically
   151  func WriteAddress(path, address string) error {
   152  	path, err := filepath.Abs(path)
   153  	if err != nil {
   154  		return err
   155  	}
   156  	tempPath := filepath.Join(filepath.Dir(path), fmt.Sprintf(".%s", filepath.Base(path)))
   157  	f, err := os.OpenFile(tempPath, os.O_RDWR|os.O_CREATE|os.O_EXCL|os.O_SYNC, 0666)
   158  	if err != nil {
   159  		return err
   160  	}
   161  	_, err = f.WriteString(address)
   162  	f.Close()
   163  	if err != nil {
   164  		return err
   165  	}
   166  	return os.Rename(tempPath, path)
   167  }
   168  
   169  // ErrNoAddress is returned when the address file has no content
   170  var ErrNoAddress = errors.New("no shim address")
   171  
   172  // ReadAddress returns the shim's abstract socket address from the path
   173  func ReadAddress(path string) (string, error) {
   174  	path, err := filepath.Abs(path)
   175  	if err != nil {
   176  		return "", err
   177  	}
   178  	data, err := ioutil.ReadFile(path)
   179  	if err != nil {
   180  		return "", err
   181  	}
   182  	if len(data) == 0 {
   183  		return "", ErrNoAddress
   184  	}
   185  	return string(data), nil
   186  }