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 }