diff --git a/mount/mount_freebsd.go b/mount/mount_freebsd.go new file mode 100644 index 000000000..d52450013 --- /dev/null +++ b/mount/mount_freebsd.go @@ -0,0 +1,137 @@ +// +build freebsd + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package mount + +import ( + "os" + "os/exec" + "time" + + "github.com/pkg/errors" + "golang.org/x/sys/unix" +) + +var ( + // ErrNotImplementOnUnix is returned for methods that are not implemented + ErrNotImplementOnUnix = errors.New("not implemented under unix") +) + +// Mount to the provided target path. +func (m *Mount) Mount(target string) error { + // The "syscall" and "golang.org/x/sys/unix" packages do not define a Mount + // function for FreeBSD, so instead we execute mount(8) and trust it to do + // the right thing + return m.mountWithHelper(target) +} + +func (m *Mount) mountWithHelper(target string) error { + // target: "/foo/target" + // command: "mount -o ro -t nullfs /foo/source /foo/merged" + // Note: FreeBSD mount(8) is particular about the order of flags and arguments + var args []string + for _, o := range m.Options { + args = append(args, "-o", o) + } + args = append(args, "-t", m.Type) + args = append(args, m.Source, target) + + infoBeforeMount, err := Lookup(target) + if err != nil { + return err + } + + // cmd.CombinedOutput() may intermittently return ECHILD because of our signal handling in shim. + // See #4387 and wait(2). + const retriesOnECHILD = 10 + for i := 0; i < retriesOnECHILD; i++ { + cmd := exec.Command("mount", args...) + out, err := cmd.CombinedOutput() + if err == nil { + return nil + } + if !errors.Is(err, unix.ECHILD) { + return errors.Wrapf(err, "mount [%v] failed: %q", args, string(out)) + } + // We got ECHILD, we are not sure whether the mount was successful. + // If the mount ID has changed, we are sure we got some new mount, but still not sure it is fully completed. + // So we attempt to unmount the new mount before retrying. + infoAfterMount, err := Lookup(target) + if err != nil { + return err + } + if infoAfterMount.ID != infoBeforeMount.ID { + _ = unmount(target, 0) + } + } + return errors.Errorf("mount [%v] failed with ECHILD (retired %d times)", args, retriesOnECHILD) +} + +// Unmount the provided mount path with the flags +func Unmount(target string, flags int) error { + if err := unmount(target, flags); err != nil && err != unix.EINVAL { + return err + } + return nil +} + +func unmount(target string, flags int) error { + for i := 0; i < 50; i++ { + if err := unix.Unmount(target, flags); err != nil { + switch err { + case unix.EBUSY: + time.Sleep(50 * time.Millisecond) + continue + default: + return err + } + } + return nil + } + return errors.Wrapf(unix.EBUSY, "failed to unmount target %s", target) +} + +// UnmountAll repeatedly unmounts the given mount point until there +// are no mounts remaining (EINVAL is returned by mount), which is +// useful for undoing a stack of mounts on the same mount point. +// UnmountAll all is noop when the first argument is an empty string. +// This is done when the containerd client did not specify any rootfs +// mounts (e.g. because the rootfs is managed outside containerd) +// UnmountAll is noop when the mount path does not exist. +func UnmountAll(mount string, flags int) error { + if mount == "" { + return nil + } + if _, err := os.Stat(mount); os.IsNotExist(err) { + return nil + } + + for { + if err := unmount(mount, flags); err != nil { + // EINVAL is returned if the target is not a + // mount point, indicating that we are + // done. It can also indicate a few other + // things (such as invalid flags) which we + // unfortunately end up squelching here too. + if err == unix.EINVAL { + return nil + } + return err + } + } +} diff --git a/mount/mount_unix.go b/mount/mount_unix.go index 95da9428e..ecf569d9c 100644 --- a/mount/mount_unix.go +++ b/mount/mount_unix.go @@ -1,4 +1,4 @@ -// +build darwin freebsd openbsd +// +build darwin openbsd /* Copyright The containerd Authors. diff --git a/snapshots/native/native.go b/snapshots/native/native.go index ba3a40e10..81d3deb4a 100644 --- a/snapshots/native/native.go +++ b/snapshots/native/native.go @@ -327,12 +327,9 @@ func (o *snapshotter) mounts(s storage.Snapshot) []mount.Mount { return []mount.Mount{ { - Source: source, - Type: "bind", - Options: []string{ - roFlag, - "rbind", - }, + Source: source, + Type: mountType, + Options: append(defaultMountOptions, roFlag), }, } } diff --git a/snapshots/native/native_default.go b/snapshots/native/native_default.go new file mode 100644 index 000000000..ee594baae --- /dev/null +++ b/snapshots/native/native_default.go @@ -0,0 +1,23 @@ +// +build !freebsd + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package native + +const mountType = "bind" + +var defaultMountOptions = []string{"rbind"} diff --git a/snapshots/native/native_freebsd.go b/snapshots/native/native_freebsd.go new file mode 100644 index 000000000..b97eb8815 --- /dev/null +++ b/snapshots/native/native_freebsd.go @@ -0,0 +1,23 @@ +// +build freebsd + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package native + +const mountType = "nullfs" + +var defaultMountOptions = []string{}