podman
627 строк · 18.9 Кб
1//go:build freebsd
2// +build freebsd
3
4package buildah
5
6import (
7"errors"
8"fmt"
9"os"
10"path/filepath"
11"strings"
12"unsafe"
13
14"github.com/containers/buildah/bind"
15"github.com/containers/buildah/chroot"
16"github.com/containers/buildah/copier"
17"github.com/containers/buildah/define"
18"github.com/containers/buildah/internal"
19"github.com/containers/buildah/internal/tmpdir"
20"github.com/containers/buildah/pkg/jail"
21"github.com/containers/buildah/pkg/overlay"
22"github.com/containers/buildah/pkg/parse"
23butil "github.com/containers/buildah/pkg/util"
24"github.com/containers/buildah/util"
25"github.com/containers/common/libnetwork/etchosts"
26"github.com/containers/common/libnetwork/resolvconf"
27nettypes "github.com/containers/common/libnetwork/types"
28netUtil "github.com/containers/common/libnetwork/util"
29"github.com/containers/common/pkg/config"
30"github.com/containers/storage/pkg/idtools"
31"github.com/containers/storage/pkg/lockfile"
32"github.com/containers/storage/pkg/stringid"
33"github.com/docker/go-units"
34"github.com/opencontainers/runtime-spec/specs-go"
35spec "github.com/opencontainers/runtime-spec/specs-go"
36"github.com/opencontainers/runtime-tools/generate"
37"github.com/sirupsen/logrus"
38"golang.org/x/exp/slices"
39"golang.org/x/sys/unix"
40)
41
42const (
43P_PID = 0
44P_PGID = 2
45PROC_REAP_ACQUIRE = 2
46PROC_REAP_RELEASE = 3
47)
48
49var (
50// We dont want to remove destinations with /etc, /dev as
51// rootfs already contains these files and unionfs will create
52// a `whiteout` i.e `.wh` files on removal of overlapping
53// files from these directories. everything other than these
54// will be cleaned up
55nonCleanablePrefixes = []string{
56"/etc", "/dev",
57}
58)
59
60func procctl(idtype int, id int, cmd int, arg *byte) error {
61_, _, e1 := unix.Syscall6(
62unix.SYS_PROCCTL, uintptr(idtype), uintptr(id),
63uintptr(cmd), uintptr(unsafe.Pointer(arg)), 0, 0)
64if e1 != 0 {
65return unix.Errno(e1)
66}
67return nil
68}
69
70func setChildProcess() error {
71if err := procctl(P_PID, unix.Getpid(), PROC_REAP_ACQUIRE, nil); err != nil {
72fmt.Fprintf(os.Stderr, "procctl(PROC_REAP_ACQUIRE): %v\n", err)
73return err
74}
75return nil
76}
77
78func (b *Builder) Run(command []string, options RunOptions) error {
79p, err := os.MkdirTemp(tmpdir.GetTempDir(), define.Package)
80if err != nil {
81return err
82}
83// On some hosts like AH, /tmp is a symlink and we need an
84// absolute path.
85path, err := filepath.EvalSymlinks(p)
86if err != nil {
87return err
88}
89logrus.Debugf("using %q to hold bundle data", path)
90defer func() {
91if err2 := os.RemoveAll(path); err2 != nil {
92logrus.Errorf("error removing %q: %v", path, err2)
93}
94}()
95
96gp, err := generate.New("freebsd")
97if err != nil {
98return fmt.Errorf("generating new 'freebsd' runtime spec: %w", err)
99}
100g := &gp
101
102isolation := options.Isolation
103if isolation == IsolationDefault {
104isolation = b.Isolation
105if isolation == IsolationDefault {
106isolation, err = parse.IsolationOption("")
107if err != nil {
108logrus.Debugf("got %v while trying to determine default isolation, guessing OCI", err)
109isolation = IsolationOCI
110} else if isolation == IsolationDefault {
111isolation = IsolationOCI
112}
113}
114}
115if err := checkAndOverrideIsolationOptions(isolation, &options); err != nil {
116return err
117}
118
119// hardwire the environment to match docker build to avoid subtle and hard-to-debug differences due to containers.conf
120b.configureEnvironment(g, options, []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"})
121
122if b.CommonBuildOpts == nil {
123return fmt.Errorf("invalid format on container you must recreate the container")
124}
125
126if err := addCommonOptsToSpec(b.CommonBuildOpts, g); err != nil {
127return err
128}
129
130if options.WorkingDir != "" {
131g.SetProcessCwd(options.WorkingDir)
132} else if b.WorkDir() != "" {
133g.SetProcessCwd(b.WorkDir())
134}
135mountPoint, err := b.Mount(b.MountLabel)
136if err != nil {
137return fmt.Errorf("mounting container %q: %w", b.ContainerID, err)
138}
139defer func() {
140if err := b.Unmount(); err != nil {
141logrus.Errorf("error unmounting container: %v", err)
142}
143}()
144g.SetRootPath(mountPoint)
145if len(command) > 0 {
146command = runLookupPath(g, command)
147g.SetProcessArgs(command)
148} else {
149g.SetProcessArgs(nil)
150}
151
152setupTerminal(g, options.Terminal, options.TerminalSize)
153
154configureNetwork, networkString, err := b.configureNamespaces(g, &options)
155if err != nil {
156return err
157}
158
159containerName := Package + "-" + filepath.Base(path)
160if configureNetwork {
161if jail.NeedVnetJail() {
162g.AddAnnotation("org.freebsd.parentJail", containerName+"-vnet")
163} else {
164g.AddAnnotation("org.freebsd.jail.vnet", "new")
165}
166}
167
168homeDir, err := b.configureUIDGID(g, mountPoint, options)
169if err != nil {
170return err
171}
172
173// Now grab the spec from the generator. Set the generator to nil so that future contributors
174// will quickly be able to tell that they're supposed to be modifying the spec directly from here.
175spec := g.Config
176g = nil
177
178// Set the seccomp configuration using the specified profile name. Some syscalls are
179// allowed if certain capabilities are to be granted (example: CAP_SYS_CHROOT and chroot),
180// so we sorted out the capabilities lists first.
181if err = setupSeccomp(spec, b.CommonBuildOpts.SeccompProfilePath); err != nil {
182return err
183}
184
185uid, gid := spec.Process.User.UID, spec.Process.User.GID
186idPair := &idtools.IDPair{UID: int(uid), GID: int(gid)}
187
188mode := os.FileMode(0755)
189coptions := copier.MkdirOptions{
190ChownNew: idPair,
191ChmodNew: &mode,
192}
193if err := copier.Mkdir(mountPoint, filepath.Join(mountPoint, spec.Process.Cwd), coptions); err != nil {
194return err
195}
196
197bindFiles := make(map[string]string)
198volumes := b.Volumes()
199
200// Figure out who owns files that will appear to be owned by UID/GID 0 in the container.
201rootUID, rootGID, err := util.GetHostRootIDs(spec)
202if err != nil {
203return err
204}
205rootIDPair := &idtools.IDPair{UID: int(rootUID), GID: int(rootGID)}
206
207hostsFile := ""
208if !options.NoHosts && !slices.Contains(volumes, config.DefaultHostsFile) && options.ConfigureNetwork != define.NetworkDisabled {
209hostsFile, err = b.createHostsFile(path, rootIDPair)
210if err != nil {
211return err
212}
213bindFiles[config.DefaultHostsFile] = hostsFile
214
215// Only add entries here if we do not have to setup network,
216// if we do we have to do it much later after the network setup.
217if !configureNetwork {
218var entries etchosts.HostEntries
219// add host entry for local ip when running in host network
220if spec.Hostname != "" {
221ip := netUtil.GetLocalIP()
222if ip != "" {
223entries = append(entries, etchosts.HostEntry{
224Names: []string{spec.Hostname},
225IP: ip,
226})
227}
228}
229err = b.addHostsEntries(hostsFile, mountPoint, entries, nil)
230if err != nil {
231return err
232}
233}
234}
235
236resolvFile := ""
237if !slices.Contains(volumes, resolvconf.DefaultResolvConf) && options.ConfigureNetwork != define.NetworkDisabled && !(len(b.CommonBuildOpts.DNSServers) == 1 && strings.ToLower(b.CommonBuildOpts.DNSServers[0]) == "none") {
238resolvFile, err = b.createResolvConf(path, rootIDPair)
239if err != nil {
240return err
241}
242bindFiles[resolvconf.DefaultResolvConf] = resolvFile
243
244// Only add entries here if we do not have to do setup network,
245// if we do we have to do it much later after the network setup.
246if !configureNetwork {
247err = b.addResolvConfEntries(resolvFile, nil, nil, false, true)
248if err != nil {
249return err
250}
251}
252}
253
254runMountInfo := runMountInfo{
255ContextDir: options.ContextDir,
256Secrets: options.Secrets,
257SSHSources: options.SSHSources,
258StageMountPoints: options.StageMountPoints,
259SystemContext: options.SystemContext,
260}
261
262runArtifacts, err := b.setupMounts(mountPoint, spec, path, options.Mounts, bindFiles, volumes, b.CommonBuildOpts.Volumes, options.RunMounts, runMountInfo)
263if err != nil {
264return fmt.Errorf("resolving mountpoints for container %q: %w", b.ContainerID, err)
265}
266if runArtifacts.SSHAuthSock != "" {
267sshenv := "SSH_AUTH_SOCK=" + runArtifacts.SSHAuthSock
268spec.Process.Env = append(spec.Process.Env, sshenv)
269}
270
271// following run was called from `buildah run`
272// and some images were mounted for this run
273// add them to cleanup artifacts
274if len(options.ExternalImageMounts) > 0 {
275runArtifacts.MountedImages = append(runArtifacts.MountedImages, options.ExternalImageMounts...)
276}
277
278defer func() {
279if err := b.cleanupRunMounts(options.SystemContext, mountPoint, runArtifacts); err != nil {
280options.Logger.Errorf("unable to cleanup run mounts %v", err)
281}
282}()
283
284defer b.cleanupTempVolumes()
285
286// If we are creating a network, make the vnet here so that we can
287// execute the OCI runtime inside it. For FreeBSD-13.3 and later, we can
288// configure the container network settings from outside the jail, which
289// removes the need for a separate jail to manage the vnet.
290if configureNetwork && jail.NeedVnetJail() {
291mynetns := containerName + "-vnet"
292
293jconf := jail.NewConfig()
294jconf.Set("name", mynetns)
295jconf.Set("vnet", jail.NEW)
296jconf.Set("children.max", 1)
297jconf.Set("persist", true)
298jconf.Set("enforce_statfs", 0)
299jconf.Set("devfs_ruleset", 4)
300jconf.Set("allow.raw_sockets", true)
301jconf.Set("allow.chflags", true)
302jconf.Set("securelevel", -1)
303netjail, err := jail.Create(jconf)
304if err != nil {
305return err
306}
307defer func() {
308jconf := jail.NewConfig()
309jconf.Set("persist", false)
310err2 := netjail.Set(jconf)
311if err2 != nil {
312logrus.Errorf("error releasing vnet jail %q: %v", mynetns, err2)
313}
314}()
315}
316
317switch isolation {
318case IsolationOCI:
319var moreCreateArgs []string
320if options.NoPivot {
321moreCreateArgs = []string{"--no-pivot"}
322} else {
323moreCreateArgs = nil
324}
325err = b.runUsingRuntimeSubproc(isolation, options, configureNetwork, networkString, moreCreateArgs, spec, mountPoint, path, containerName, b.Container, hostsFile, resolvFile)
326case IsolationChroot:
327err = chroot.RunUsingChroot(spec, path, homeDir, options.Stdin, options.Stdout, options.Stderr)
328default:
329err = errors.New("don't know how to run this command")
330}
331return err
332}
333
334func addCommonOptsToSpec(commonOpts *define.CommonBuildOptions, g *generate.Generator) error {
335defaultContainerConfig, err := config.Default()
336if err != nil {
337return fmt.Errorf("failed to get container config: %w", err)
338}
339// Other process resource limits
340if err := addRlimits(commonOpts.Ulimit, g, defaultContainerConfig.Containers.DefaultUlimits.Get()); err != nil {
341return err
342}
343
344logrus.Debugf("Resources: %#v", commonOpts)
345return nil
346}
347
348// setupSpecialMountSpecChanges creates special mounts for depending
349// on the namespaces - nothing yet for freebsd
350func setupSpecialMountSpecChanges(spec *spec.Spec, shmSize string) ([]specs.Mount, error) {
351return spec.Mounts, nil
352}
353
354// If this function succeeds and returns a non-nil *lockfile.LockFile, the caller must unlock it (when??).
355func (b *Builder) getCacheMount(tokens []string, stageMountPoints map[string]internal.StageMountDetails, idMaps IDMaps, workDir string) (*spec.Mount, *lockfile.LockFile, error) {
356return nil, nil, errors.New("cache mounts not supported on freebsd")
357}
358
359func (b *Builder) runSetupVolumeMounts(mountLabel string, volumeMounts []string, optionMounts []specs.Mount, idMaps IDMaps) (mounts []specs.Mount, Err error) {
360// Make sure the overlay directory is clean before running
361_, err := b.store.ContainerDirectory(b.ContainerID)
362if err != nil {
363return nil, fmt.Errorf("looking up container directory for %s: %w", b.ContainerID, err)
364}
365
366parseMount := func(mountType, host, container string, options []string) (specs.Mount, error) {
367var foundrw, foundro, foundO bool
368var upperDir string
369for _, opt := range options {
370switch opt {
371case "rw":
372foundrw = true
373case "ro":
374foundro = true
375case "O":
376foundO = true
377}
378if strings.HasPrefix(opt, "upperdir") {
379splitOpt := strings.SplitN(opt, "=", 2)
380if len(splitOpt) > 1 {
381upperDir = splitOpt[1]
382}
383}
384}
385if !foundrw && !foundro {
386options = append(options, "rw")
387}
388if mountType == "bind" || mountType == "rbind" {
389mountType = "nullfs"
390}
391if foundO {
392containerDir, err := b.store.ContainerDirectory(b.ContainerID)
393if err != nil {
394return specs.Mount{}, err
395}
396
397contentDir, err := overlay.TempDir(containerDir, idMaps.rootUID, idMaps.rootGID)
398if err != nil {
399return specs.Mount{}, fmt.Errorf("failed to create TempDir in the %s directory: %w", containerDir, err)
400}
401
402overlayOpts := overlay.Options{
403RootUID: idMaps.rootUID,
404RootGID: idMaps.rootGID,
405UpperDirOptionFragment: upperDir,
406GraphOpts: b.store.GraphOptions(),
407}
408
409overlayMount, err := overlay.MountWithOptions(contentDir, host, container, &overlayOpts)
410if err == nil {
411b.TempVolumes[contentDir] = true
412}
413return overlayMount, err
414}
415return specs.Mount{
416Destination: container,
417Type: mountType,
418Source: host,
419Options: options,
420}, nil
421}
422
423// Bind mount volumes specified for this particular Run() invocation
424for _, i := range optionMounts {
425logrus.Debugf("setting up mounted volume at %q", i.Destination)
426mount, err := parseMount(i.Type, i.Source, i.Destination, i.Options)
427if err != nil {
428return nil, err
429}
430mounts = append(mounts, mount)
431}
432// Bind mount volumes given by the user when the container was created
433for _, i := range volumeMounts {
434var options []string
435spliti := strings.Split(i, ":")
436if len(spliti) > 2 {
437options = strings.Split(spliti[2], ",")
438}
439mount, err := parseMount("nullfs", spliti[0], spliti[1], options)
440if err != nil {
441return nil, err
442}
443mounts = append(mounts, mount)
444}
445return mounts, nil
446}
447
448func setupCapabilities(g *generate.Generator, defaultCapabilities, adds, drops []string) error {
449return nil
450}
451
452func (b *Builder) runConfigureNetwork(pid int, isolation define.Isolation, options RunOptions, networkString string, containerName string, hostnames []string) (func(), *netResult, error) {
453//if isolation == IsolationOCIRootless {
454//return setupRootlessNetwork(pid)
455//}
456
457var configureNetworks []string
458if len(networkString) > 0 {
459configureNetworks = strings.Split(networkString, ",")
460}
461
462if len(configureNetworks) == 0 {
463configureNetworks = []string{b.NetworkInterface.DefaultNetworkName()}
464}
465logrus.Debugf("configureNetworks: %v", configureNetworks)
466
467var mynetns string
468if jail.NeedVnetJail() {
469mynetns = containerName + "-vnet"
470} else {
471mynetns = containerName
472}
473
474networks := make(map[string]nettypes.PerNetworkOptions, len(configureNetworks))
475for i, network := range configureNetworks {
476networks[network] = nettypes.PerNetworkOptions{
477InterfaceName: fmt.Sprintf("eth%d", i),
478}
479}
480
481opts := nettypes.NetworkOptions{
482ContainerID: containerName,
483ContainerName: containerName,
484Networks: networks,
485}
486netStatus, err := b.NetworkInterface.Setup(mynetns, nettypes.SetupOptions{NetworkOptions: opts})
487if err != nil {
488return nil, nil, err
489}
490
491teardown := func() {
492err := b.NetworkInterface.Teardown(mynetns, nettypes.TeardownOptions{NetworkOptions: opts})
493if err != nil {
494logrus.Errorf("failed to cleanup network: %v", err)
495}
496}
497
498return teardown, netStatusToNetResult(netStatus, hostnames), nil
499}
500
501func setupNamespaces(logger *logrus.Logger, g *generate.Generator, namespaceOptions define.NamespaceOptions, idmapOptions define.IDMappingOptions, policy define.NetworkConfigurationPolicy) (configureNetwork bool, networkString string, configureUTS bool, err error) {
502// Set namespace options in the container configuration.
503for _, namespaceOption := range namespaceOptions {
504switch namespaceOption.Name {
505case string(specs.NetworkNamespace):
506configureNetwork = false
507if !namespaceOption.Host && (namespaceOption.Path == "" || !filepath.IsAbs(namespaceOption.Path)) {
508if namespaceOption.Path != "" && !filepath.IsAbs(namespaceOption.Path) {
509networkString = namespaceOption.Path
510namespaceOption.Path = ""
511}
512configureNetwork = (policy != define.NetworkDisabled)
513}
514case string(specs.UTSNamespace):
515configureUTS = false
516if !namespaceOption.Host && namespaceOption.Path == "" {
517configureUTS = true
518}
519}
520// TODO: re-visit this when there is consensus on a
521// FreeBSD runtime-spec. FreeBSD jails have rough
522// equivalents for UTS and and network namespaces.
523}
524
525return configureNetwork, networkString, configureUTS, nil
526}
527
528func (b *Builder) configureNamespaces(g *generate.Generator, options *RunOptions) (bool, string, error) {
529defaultNamespaceOptions, err := DefaultNamespaceOptions()
530if err != nil {
531return false, "", err
532}
533
534namespaceOptions := defaultNamespaceOptions
535namespaceOptions.AddOrReplace(b.NamespaceOptions...)
536namespaceOptions.AddOrReplace(options.NamespaceOptions...)
537
538networkPolicy := options.ConfigureNetwork
539//Nothing was specified explicitly so network policy should be inherited from builder
540if networkPolicy == NetworkDefault {
541networkPolicy = b.ConfigureNetwork
542
543// If builder policy was NetworkDisabled and
544// we want to disable network for this run.
545// reset options.ConfigureNetwork to NetworkDisabled
546// since it will be treated as source of truth later.
547if networkPolicy == NetworkDisabled {
548options.ConfigureNetwork = networkPolicy
549}
550}
551
552configureNetwork, networkString, configureUTS, err := setupNamespaces(options.Logger, g, namespaceOptions, b.IDMappingOptions, networkPolicy)
553if err != nil {
554return false, "", err
555}
556
557if configureUTS {
558if options.Hostname != "" {
559g.SetHostname(options.Hostname)
560} else if b.Hostname() != "" {
561g.SetHostname(b.Hostname())
562} else {
563g.SetHostname(stringid.TruncateID(b.ContainerID))
564}
565} else {
566g.SetHostname("")
567}
568
569found := false
570spec := g.Config
571for i := range spec.Process.Env {
572if strings.HasPrefix(spec.Process.Env[i], "HOSTNAME=") {
573found = true
574break
575}
576}
577if !found {
578spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("HOSTNAME=%s", spec.Hostname))
579}
580
581return configureNetwork, networkString, nil
582}
583
584func runSetupBoundFiles(bundlePath string, bindFiles map[string]string) (mounts []specs.Mount) {
585for dest, src := range bindFiles {
586options := []string{}
587if strings.HasPrefix(src, bundlePath) {
588options = append(options, bind.NoBindOption)
589}
590mounts = append(mounts, specs.Mount{
591Source: src,
592Destination: dest,
593Type: "nullfs",
594Options: options,
595})
596}
597return mounts
598}
599
600func addRlimits(ulimit []string, g *generate.Generator, defaultUlimits []string) error {
601var (
602ul *units.Ulimit
603err error
604)
605
606ulimit = append(defaultUlimits, ulimit...)
607for _, u := range ulimit {
608if ul, err = butil.ParseUlimit(u); err != nil {
609return fmt.Errorf("ulimit option %q requires name=SOFT:HARD, failed to be parsed: %w", u, err)
610}
611
612g.AddProcessRlimits("RLIMIT_"+strings.ToUpper(ul.Name), uint64(ul.Hard), uint64(ul.Soft))
613}
614return nil
615}
616
617// Create pipes to use for relaying stdio.
618func runMakeStdioPipe(uid, gid int) ([][]int, error) {
619stdioPipe := make([][]int, 3)
620for i := range stdioPipe {
621stdioPipe[i] = make([]int, 2)
622if err := unix.Pipe(stdioPipe[i]); err != nil {
623return nil, fmt.Errorf("creating pipe for container FD %d: %w", i, err)
624}
625}
626return stdioPipe, nil
627}
628