github.com/containers/podman/v4@v4.9.4/libpod/util.go (about) 1 //go:build !remote 2 // +build !remote 3 4 package libpod 5 6 import ( 7 "bufio" 8 "encoding/binary" 9 "fmt" 10 "io" 11 "net/http" 12 "os" 13 "path/filepath" 14 "sort" 15 "strconv" 16 "strings" 17 "time" 18 19 "github.com/containers/common/libnetwork/types" 20 "github.com/containers/common/pkg/config" 21 "github.com/containers/podman/v4/libpod/define" 22 "github.com/containers/podman/v4/pkg/api/handlers/utils/apiutil" 23 spec "github.com/opencontainers/runtime-spec/specs-go" 24 "github.com/opencontainers/selinux/go-selinux/label" 25 "github.com/sirupsen/logrus" 26 ) 27 28 // FuncTimer helps measure the execution time of a function 29 // For debug purposes, do not leave in code 30 // used like defer FuncTimer("foo") 31 func FuncTimer(funcName string) { 32 elapsed := time.Since(time.Now()) 33 fmt.Printf("%s executed in %d ms\n", funcName, elapsed) 34 } 35 36 // MountExists returns true if dest exists in the list of mounts 37 func MountExists(specMounts []spec.Mount, dest string) bool { 38 for _, m := range specMounts { 39 if m.Destination == dest { 40 return true 41 } 42 } 43 return false 44 } 45 46 type byDestination []spec.Mount 47 48 func (m byDestination) Len() int { 49 return len(m) 50 } 51 52 func (m byDestination) Less(i, j int) bool { 53 return m.parts(i) < m.parts(j) 54 } 55 56 func (m byDestination) Swap(i, j int) { 57 m[i], m[j] = m[j], m[i] 58 } 59 60 func (m byDestination) parts(i int) int { 61 return strings.Count(filepath.Clean(m[i].Destination), string(os.PathSeparator)) 62 } 63 64 func sortMounts(m []spec.Mount) []spec.Mount { 65 sort.Sort(byDestination(m)) 66 return m 67 } 68 69 func validPodNSOption(p *Pod, ctrPod string) error { 70 if p == nil { 71 return fmt.Errorf("pod passed in was nil. Container may not be associated with a pod: %w", define.ErrInvalidArg) 72 } 73 74 if ctrPod == "" { 75 return fmt.Errorf("container is not a member of any pod: %w", define.ErrInvalidArg) 76 } 77 78 if ctrPod != p.ID() { 79 return fmt.Errorf("pod passed in is not the pod the container is associated with: %w", define.ErrInvalidArg) 80 } 81 return nil 82 } 83 84 // JSONDeepCopy performs a deep copy by performing a JSON encode/decode of the 85 // given structures. From and To should be identically typed structs. 86 func JSONDeepCopy(from, to interface{}) error { 87 tmp, err := json.Marshal(from) 88 if err != nil { 89 return err 90 } 91 return json.Unmarshal(tmp, to) 92 } 93 94 // DefaultSeccompPath returns the path to the default seccomp.json file 95 // if it exists, first it checks OverrideSeccomp and then default. 96 // If neither exist function returns "" 97 func DefaultSeccompPath() (string, error) { 98 def, err := config.Default() 99 if err != nil { 100 return "", err 101 } 102 if def.Containers.SeccompProfile != "" { 103 return def.Containers.SeccompProfile, nil 104 } 105 106 _, err = os.Stat(config.SeccompOverridePath) 107 if err == nil { 108 return config.SeccompOverridePath, nil 109 } 110 if !os.IsNotExist(err) { 111 return "", err 112 } 113 if _, err := os.Stat(config.SeccompDefaultPath); err != nil { 114 if !os.IsNotExist(err) { 115 return "", err 116 } 117 return "", nil 118 } 119 return config.SeccompDefaultPath, nil 120 } 121 122 // CheckDependencyContainer verifies the given container can be used as a 123 // dependency of another container. 124 // Both the dependency to check and the container that will be using the 125 // dependency must be passed in. 126 // It is assumed that ctr is locked, and depCtr is unlocked. 127 func checkDependencyContainer(depCtr, ctr *Container) error { 128 state, err := depCtr.State() 129 if err != nil { 130 return fmt.Errorf("accessing dependency container %s state: %w", depCtr.ID(), err) 131 } 132 if state == define.ContainerStateRemoving { 133 return fmt.Errorf("cannot use container %s as a dependency as it is being removed: %w", depCtr.ID(), define.ErrCtrStateInvalid) 134 } 135 136 if depCtr.ID() == ctr.ID() { 137 return fmt.Errorf("must specify another container: %w", define.ErrInvalidArg) 138 } 139 140 if ctr.config.Pod != "" && depCtr.PodID() != ctr.config.Pod { 141 return fmt.Errorf("container has joined pod %s and dependency container %s is not a member of the pod: %w", ctr.config.Pod, depCtr.ID(), define.ErrInvalidArg) 142 } 143 144 return nil 145 } 146 147 // hijackWriteError writes an error to a hijacked HTTP session. 148 func hijackWriteError(toWrite error, cid string, terminal bool, httpBuf *bufio.ReadWriter) { 149 if toWrite != nil { 150 errString := []byte(fmt.Sprintf("Error: %v\n", toWrite)) 151 if !terminal { 152 // We need a header. 153 header := makeHTTPAttachHeader(2, uint32(len(errString))) 154 if _, err := httpBuf.Write(header); err != nil { 155 logrus.Errorf("Writing header for container %s attach connection error: %v", cid, err) 156 } 157 } 158 if _, err := httpBuf.Write(errString); err != nil { 159 logrus.Errorf("Writing error to container %s HTTP attach connection: %v", cid, err) 160 } 161 if err := httpBuf.Flush(); err != nil { 162 logrus.Errorf("Flushing HTTP buffer for container %s HTTP attach connection: %v", cid, err) 163 } 164 } 165 } 166 167 // hijackWriteErrorAndClose writes an error to a hijacked HTTP session and 168 // closes it. Intended to HTTPAttach function. 169 // If error is nil, it will not be written; we'll only close the connection. 170 func hijackWriteErrorAndClose(toWrite error, cid string, terminal bool, httpCon io.Closer, httpBuf *bufio.ReadWriter) { 171 hijackWriteError(toWrite, cid, terminal, httpBuf) 172 173 if err := httpCon.Close(); err != nil { 174 logrus.Errorf("Closing container %s HTTP attach connection: %v", cid, err) 175 } 176 } 177 178 // makeHTTPAttachHeader makes an 8-byte HTTP header for a buffer of the given 179 // length and stream. Accepts an integer indicating which stream we are sending 180 // to (STDIN = 0, STDOUT = 1, STDERR = 2). 181 func makeHTTPAttachHeader(stream byte, length uint32) []byte { 182 header := make([]byte, 8) 183 header[0] = stream 184 binary.BigEndian.PutUint32(header[4:], length) 185 return header 186 } 187 188 // writeHijackHeader writes a header appropriate for the type of HTTP Hijack 189 // that occurred in a hijacked HTTP connection used for attach. 190 func writeHijackHeader(r *http.Request, conn io.Writer, tty bool) { 191 // AttachHeader is the literal header sent for upgraded/hijacked connections for 192 // attach, sourced from Docker at: 193 // https://raw.githubusercontent.com/moby/moby/b95fad8e51bd064be4f4e58a996924f343846c85/api/server/router/container/container_routes.go 194 // Using literally to ensure compatibility with existing clients. 195 196 // New docker API uses a different header for the non tty case. 197 // Lets do the same for libpod. Only do this for the new api versions to not break older clients. 198 header := "application/vnd.docker.raw-stream" 199 if !tty { 200 version := "4.7.0" 201 if !apiutil.IsLibpodRequest(r) { 202 version = "1.42.0" // docker only used two digest "1.42" but our semver lib needs the extra .0 to work 203 } 204 if _, err := apiutil.SupportedVersion(r, ">= "+version); err == nil { 205 header = "application/vnd.docker.multiplexed-stream" 206 } 207 } 208 209 c := r.Header.Get("Connection") 210 proto := r.Header.Get("Upgrade") 211 if len(proto) == 0 || !strings.EqualFold(c, "Upgrade") { 212 // OK - can't upgrade if not requested or protocol is not specified 213 fmt.Fprintf(conn, 214 "HTTP/1.1 200 OK\r\nContent-Type: %s\r\n\r\n", header) 215 } else { 216 // Upgraded 217 fmt.Fprintf(conn, 218 "HTTP/1.1 101 UPGRADED\r\nContent-Type: %s\r\nConnection: Upgrade\r\nUpgrade: %s\r\n\r\n", 219 proto, header) 220 } 221 } 222 223 // Convert OCICNI port bindings into Inspect-formatted port bindings. 224 func makeInspectPortBindings(bindings []types.PortMapping) map[string][]define.InspectHostPort { 225 return makeInspectPorts(bindings, nil) 226 } 227 228 // Convert OCICNI port bindings into Inspect-formatted port bindings with exposed, but not bound ports set to nil. 229 func makeInspectPorts(bindings []types.PortMapping, expose map[uint16][]string) map[string][]define.InspectHostPort { 230 portBindings := make(map[string][]define.InspectHostPort) 231 for _, port := range bindings { 232 protocols := strings.Split(port.Protocol, ",") 233 for _, protocol := range protocols { 234 for i := uint16(0); i < port.Range; i++ { 235 key := fmt.Sprintf("%d/%s", port.ContainerPort+i, protocol) 236 hostPorts := portBindings[key] 237 hostPorts = append(hostPorts, define.InspectHostPort{ 238 HostIP: port.HostIP, 239 HostPort: strconv.FormatUint(uint64(port.HostPort+i), 10), 240 }) 241 portBindings[key] = hostPorts 242 } 243 } 244 } 245 // add exposed ports without host port information to match docker 246 for port, protocols := range expose { 247 for _, protocol := range protocols { 248 key := fmt.Sprintf("%d/%s", port, protocol) 249 if _, ok := portBindings[key]; !ok { 250 portBindings[key] = nil 251 } 252 } 253 } 254 return portBindings 255 } 256 257 // Write a given string to a new file at a given path. 258 // Will error if a file with the given name already exists. 259 // Will be chown'd to the UID/GID provided and have the provided SELinux label 260 // set. 261 func writeStringToPath(path, contents, mountLabel string, uid, gid int) error { 262 f, err := os.Create(path) 263 if err != nil { 264 return fmt.Errorf("unable to create %s: %w", path, err) 265 } 266 defer f.Close() 267 if err := f.Chown(uid, gid); err != nil { 268 return err 269 } 270 271 if _, err := f.WriteString(contents); err != nil { 272 return fmt.Errorf("unable to write %s: %w", path, err) 273 } 274 // Relabel runDirResolv for the container 275 if err := label.Relabel(path, mountLabel, false); err != nil { 276 return err 277 } 278 279 return nil 280 }