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  }