mirror of
https://github.com/containers/podman.git
synced 2025-08-06 11:32:07 +08:00
480 lines
12 KiB
Go
480 lines
12 KiB
Go
//go:build freebsd
|
|
// +build freebsd
|
|
|
|
package libpod
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
|
|
"github.com/containers/buildah/pkg/overlay"
|
|
"github.com/containers/common/libnetwork/types"
|
|
"github.com/containers/common/pkg/chown"
|
|
"github.com/containers/common/pkg/umask"
|
|
"github.com/containers/podman/v4/libpod/define"
|
|
"github.com/containers/podman/v4/pkg/rootless"
|
|
"github.com/containers/storage/pkg/idtools"
|
|
securejoin "github.com/cyphar/filepath-securejoin"
|
|
spec "github.com/opencontainers/runtime-spec/specs-go"
|
|
"github.com/opencontainers/runtime-tools/generate"
|
|
"github.com/opencontainers/selinux/go-selinux"
|
|
"github.com/opencontainers/selinux/go-selinux/label"
|
|
"github.com/sirupsen/logrus"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
var (
|
|
bindOptions = []string{}
|
|
)
|
|
|
|
// Network stubs to decouple container_internal_freebsd.go from
|
|
// networking_freebsd.go so they can be reviewed separately.
|
|
func (r *Runtime) createNetNS(ctr *Container) (netJail string, q map[string]types.StatusBlock, retErr error) {
|
|
return "", nil, errors.New("not implemented (*Runtime) createNetNS")
|
|
}
|
|
|
|
func (r *Runtime) teardownNetNS(ctr *Container) error {
|
|
return errors.New("not implemented (*Runtime) teardownNetNS")
|
|
}
|
|
|
|
func (r *Runtime) reloadContainerNetwork(ctr *Container) (map[string]types.StatusBlock, error) {
|
|
return nil, errors.New("not implemented (*Runtime) reloadContainerNetwork")
|
|
}
|
|
|
|
func (c *Container) mountSHM(shmOptions string) error {
|
|
return nil
|
|
}
|
|
|
|
func (c *Container) unmountSHM(path string) error {
|
|
return nil
|
|
}
|
|
|
|
// prepare mounts the container and sets up other required resources like net
|
|
// namespaces
|
|
func (c *Container) prepare() error {
|
|
var (
|
|
wg sync.WaitGroup
|
|
jailName string
|
|
networkStatus map[string]types.StatusBlock
|
|
createNetNSErr, mountStorageErr error
|
|
mountPoint string
|
|
tmpStateLock sync.Mutex
|
|
)
|
|
|
|
wg.Add(2)
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
// Set up network namespace if not already set up
|
|
noNetNS := c.state.NetworkJail == ""
|
|
if c.config.CreateNetNS && noNetNS && !c.config.PostConfigureNetNS {
|
|
jailName, networkStatus, createNetNSErr = c.runtime.createNetNS(c)
|
|
if createNetNSErr != nil {
|
|
return
|
|
}
|
|
|
|
tmpStateLock.Lock()
|
|
defer tmpStateLock.Unlock()
|
|
|
|
// Assign NetNS attributes to container
|
|
c.state.NetworkJail = jailName
|
|
c.state.NetworkStatus = networkStatus
|
|
}
|
|
}()
|
|
// Mount storage if not mounted
|
|
go func() {
|
|
defer wg.Done()
|
|
mountPoint, mountStorageErr = c.mountStorage()
|
|
|
|
if mountStorageErr != nil {
|
|
return
|
|
}
|
|
|
|
tmpStateLock.Lock()
|
|
defer tmpStateLock.Unlock()
|
|
|
|
// Finish up mountStorage
|
|
c.state.Mounted = true
|
|
c.state.Mountpoint = mountPoint
|
|
|
|
logrus.Debugf("Created root filesystem for container %s at %s", c.ID(), c.state.Mountpoint)
|
|
}()
|
|
|
|
wg.Wait()
|
|
|
|
var createErr error
|
|
if mountStorageErr != nil {
|
|
if createErr != nil {
|
|
logrus.Errorf("Preparing container %s: %v", c.ID(), createErr)
|
|
}
|
|
createErr = mountStorageErr
|
|
}
|
|
|
|
if createErr != nil {
|
|
return createErr
|
|
}
|
|
|
|
// Save changes to container state
|
|
if err := c.save(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// cleanupNetwork unmounts and cleans up the container's network
|
|
func (c *Container) cleanupNetwork() error {
|
|
if c.config.NetNsCtr != "" {
|
|
return nil
|
|
}
|
|
netDisabled, err := c.NetworkDisabled()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if netDisabled {
|
|
return nil
|
|
}
|
|
|
|
// Stop the container's network namespace (if it has one)
|
|
if err := c.runtime.teardownNetNS(c); err != nil {
|
|
logrus.Errorf("Unable to cleanup network for container %s: %q", c.ID(), err)
|
|
}
|
|
|
|
if c.valid {
|
|
return c.save()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// reloadNetwork reloads the network for the given container, recreating
|
|
// firewall rules.
|
|
func (c *Container) reloadNetwork() error {
|
|
result, err := c.runtime.reloadContainerNetwork(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.state.NetworkStatus = result
|
|
|
|
return c.save()
|
|
}
|
|
|
|
// Add an existing container's network jail
|
|
func (c *Container) addNetworkContainer(g *generate.Generator, ctr string) error {
|
|
nsCtr, err := c.runtime.state.Container(ctr)
|
|
c.runtime.state.UpdateContainer(nsCtr)
|
|
if err != nil {
|
|
return fmt.Errorf("error retrieving dependency %s of container %s from state: %w", ctr, c.ID(), err)
|
|
}
|
|
g.AddAnnotation("org.freebsd.parentJail", nsCtr.state.NetworkJail)
|
|
return nil
|
|
}
|
|
|
|
func isRootlessCgroupSet(cgroup string) bool {
|
|
return false
|
|
}
|
|
|
|
func (c *Container) expectPodCgroup() (bool, error) {
|
|
return false, nil
|
|
}
|
|
|
|
func (c *Container) getOCICgroupPath() (string, error) {
|
|
return "", nil
|
|
}
|
|
|
|
func (c *Container) copyTimezoneFile(zonePath string) (string, error) {
|
|
var localtimeCopy string = filepath.Join(c.state.RunDir, "localtime")
|
|
file, err := os.Stat(zonePath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if file.IsDir() {
|
|
return "", errors.New("Invalid timezone: is a directory")
|
|
}
|
|
src, err := os.Open(zonePath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer src.Close()
|
|
dest, err := os.Create(localtimeCopy)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer dest.Close()
|
|
_, err = io.Copy(dest, src)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if err := c.relabel(localtimeCopy, c.config.MountLabel, false); err != nil {
|
|
return "", err
|
|
}
|
|
if err := dest.Chown(c.RootUID(), c.RootGID()); err != nil {
|
|
return "", err
|
|
}
|
|
return localtimeCopy, err
|
|
}
|
|
|
|
func (c *Container) cleanupOverlayMounts() error {
|
|
return overlay.CleanupContent(c.config.StaticDir)
|
|
}
|
|
|
|
// Check if a file exists at the given path in the container's root filesystem.
|
|
// Container must already be mounted for this to be used.
|
|
func (c *Container) checkFileExistsInRootfs(file string) (bool, error) {
|
|
checkPath, err := securejoin.SecureJoin(c.state.Mountpoint, file)
|
|
if err != nil {
|
|
return false, fmt.Errorf("cannot create path to container %s file %q: %w", c.ID(), file, err)
|
|
}
|
|
stat, err := os.Stat(checkPath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return false, nil
|
|
}
|
|
return false, fmt.Errorf("container %s: %w", c.ID(), err)
|
|
}
|
|
if stat.IsDir() {
|
|
return false, nil
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// Creates and mounts an empty dir to mount secrets into, if it does not already exist
|
|
func (c *Container) createSecretMountDir() error {
|
|
src := filepath.Join(c.state.RunDir, "/run/secrets")
|
|
_, err := os.Stat(src)
|
|
if os.IsNotExist(err) {
|
|
oldUmask := umask.Set(0)
|
|
defer umask.Set(oldUmask)
|
|
|
|
if err := os.MkdirAll(src, 0755); err != nil {
|
|
return err
|
|
}
|
|
if err := label.Relabel(src, c.config.MountLabel, false); err != nil {
|
|
return err
|
|
}
|
|
if err := os.Chown(src, c.RootUID(), c.RootGID()); err != nil {
|
|
return err
|
|
}
|
|
c.state.BindMounts["/run/secrets"] = src
|
|
return nil
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// Fix ownership and permissions of the specified volume if necessary.
|
|
func (c *Container) fixVolumePermissions(v *ContainerNamedVolume) error {
|
|
vol, err := c.runtime.state.Volume(v.Name)
|
|
if err != nil {
|
|
return fmt.Errorf("error retrieving named volume %s for container %s: %w", v.Name, c.ID(), err)
|
|
}
|
|
|
|
vol.lock.Lock()
|
|
defer vol.lock.Unlock()
|
|
|
|
// The volume may need a copy-up. Check the state.
|
|
if err := vol.update(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// TODO: For now, I've disabled chowning volumes owned by non-Podman
|
|
// drivers. This may be safe, but it's really going to be a case-by-case
|
|
// thing, I think - safest to leave disabled now and re-enable later if
|
|
// there is a demand.
|
|
if vol.state.NeedsChown && !vol.UsesVolumeDriver() {
|
|
vol.state.NeedsChown = false
|
|
|
|
uid := int(c.config.Spec.Process.User.UID)
|
|
gid := int(c.config.Spec.Process.User.GID)
|
|
|
|
if c.config.IDMappings.UIDMap != nil {
|
|
p := idtools.IDPair{
|
|
UID: uid,
|
|
GID: gid,
|
|
}
|
|
mappings := idtools.NewIDMappingsFromMaps(c.config.IDMappings.UIDMap, c.config.IDMappings.GIDMap)
|
|
newPair, err := mappings.ToHost(p)
|
|
if err != nil {
|
|
return fmt.Errorf("error mapping user %d:%d: %w", uid, gid, err)
|
|
}
|
|
uid = newPair.UID
|
|
gid = newPair.GID
|
|
}
|
|
|
|
vol.state.UIDChowned = uid
|
|
vol.state.GIDChowned = gid
|
|
|
|
if err := vol.save(); err != nil {
|
|
return err
|
|
}
|
|
|
|
mountPoint, err := vol.MountPoint()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := os.Lchown(mountPoint, uid, gid); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Make sure the new volume matches the permissions of the target directory.
|
|
// https://github.com/containers/podman/issues/10188
|
|
st, err := os.Lstat(filepath.Join(c.state.Mountpoint, v.Dest))
|
|
if err == nil {
|
|
if stat, ok := st.Sys().(*syscall.Stat_t); ok {
|
|
if err := os.Lchown(mountPoint, int(stat.Uid), int(stat.Gid)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := os.Chmod(mountPoint, st.Mode()); err != nil {
|
|
return err
|
|
}
|
|
/*
|
|
stat := st.Sys().(*syscall.Stat_t)
|
|
atime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
|
|
if err := os.Chtimes(mountPoint, atime, st.ModTime()); err != nil {
|
|
return err
|
|
}*/
|
|
} else if !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Container) relabel(src, mountLabel string, recurse bool) error {
|
|
if !selinux.GetEnabled() || mountLabel == "" {
|
|
return nil
|
|
}
|
|
// only relabel on initial creation of container
|
|
if !c.ensureState(define.ContainerStateConfigured, define.ContainerStateUnknown) {
|
|
label, err := label.FileLabel(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// If labels are different, might be on a tmpfs
|
|
if label == mountLabel {
|
|
return nil
|
|
}
|
|
}
|
|
return label.Relabel(src, mountLabel, recurse)
|
|
}
|
|
|
|
func (c *Container) ChangeHostPathOwnership(src string, recurse bool, uid, gid int) error {
|
|
// only chown on initial creation of container
|
|
if !c.ensureState(define.ContainerStateConfigured, define.ContainerStateUnknown) {
|
|
st, err := os.Stat(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// If labels are different, might be on a tmpfs
|
|
if int(st.Sys().(*syscall.Stat_t).Uid) == uid && int(st.Sys().(*syscall.Stat_t).Gid) == gid {
|
|
return nil
|
|
}
|
|
}
|
|
return chown.ChangeHostPathOwnership(src, recurse, uid, gid)
|
|
}
|
|
|
|
func openDirectory(path string) (fd int, err error) {
|
|
const O_PATH = 0x00400000
|
|
return unix.Open(path, unix.O_RDONLY|O_PATH, 0)
|
|
}
|
|
|
|
func (c *Container) addNetworkNamespace(g *generate.Generator) error {
|
|
if c.config.CreateNetNS {
|
|
g.AddAnnotation("org.freebsd.parentJail", c.state.NetworkJail)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Container) addSystemdMounts(g *generate.Generator) error {
|
|
return nil
|
|
}
|
|
|
|
func (c *Container) addSharedNamespaces(g *generate.Generator) error {
|
|
if c.config.NetNsCtr != "" {
|
|
if err := c.addNetworkContainer(g, c.config.NetNsCtr); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
availableUIDs, availableGIDs, err := rootless.GetAvailableIDMaps()
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
// The kernel-provided files only exist if user namespaces are supported
|
|
logrus.Debugf("User or group ID mappings not available: %s", err)
|
|
} else {
|
|
return err
|
|
}
|
|
} else {
|
|
g.Config.Linux.UIDMappings = rootless.MaybeSplitMappings(g.Config.Linux.UIDMappings, availableUIDs)
|
|
g.Config.Linux.GIDMappings = rootless.MaybeSplitMappings(g.Config.Linux.GIDMappings, availableGIDs)
|
|
}
|
|
|
|
// Hostname handling:
|
|
// If we have a UTS namespace, set Hostname in the OCI spec.
|
|
// Set the HOSTNAME environment variable unless explicitly overridden by
|
|
// the user (already present in OCI spec). If we don't have a UTS ns,
|
|
// set it to the host's hostname instead.
|
|
hostname := c.Hostname()
|
|
foundUTS := false
|
|
|
|
// TODO: make this optional, needs progress on adding FreeBSD section to the spec
|
|
foundUTS = true
|
|
g.SetHostname(hostname)
|
|
|
|
if !foundUTS {
|
|
tmpHostname, err := os.Hostname()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
hostname = tmpHostname
|
|
}
|
|
needEnv := true
|
|
for _, checkEnv := range g.Config.Process.Env {
|
|
if strings.SplitN(checkEnv, "=", 2)[0] == "HOSTNAME" {
|
|
needEnv = false
|
|
break
|
|
}
|
|
}
|
|
if needEnv {
|
|
g.AddProcessEnv("HOSTNAME", hostname)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Container) addRootPropagation(g *generate.Generator, mounts []spec.Mount) error {
|
|
return nil
|
|
}
|
|
|
|
func (c *Container) setProcessLabel(g *generate.Generator) {
|
|
}
|
|
|
|
func (c *Container) setMountLabel(g *generate.Generator) {
|
|
}
|
|
|
|
func (c *Container) setCgroupsPath(g *generate.Generator) error {
|
|
return nil
|
|
}
|
|
|
|
func (c *Container) addSlirp4netnsDNS(nameservers []string) []string {
|
|
return nameservers
|
|
}
|
|
|
|
func (c *Container) isSlirp4netnsIPv6() (bool, error) {
|
|
return false, nil
|
|
}
|
|
|
|
// check for net=none
|
|
func (c *Container) hasNetNone() bool {
|
|
return c.state.NetworkJail == ""
|
|
}
|