Merge pull request #10658 from darwin-containers/reorganize-mount-unmount
Reorganize mount/unmount code so it is easier to add Darwin-specific implementation
This commit is contained in:
		
							
								
								
									
										50
									
								
								core/mount/fuse_linux.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								core/mount/fuse_linux.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| /* | ||||
|    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/exec" | ||||
|  | ||||
| 	"golang.org/x/sys/unix" | ||||
| ) | ||||
|  | ||||
| // fuseSuperMagic is defined in statfs(2) | ||||
| const fuseSuperMagic = 0x65735546 | ||||
|  | ||||
| func isFUSE(dir string) bool { | ||||
| 	var st unix.Statfs_t | ||||
| 	if err := unix.Statfs(dir, &st); err != nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	return st.Type == fuseSuperMagic | ||||
| } | ||||
|  | ||||
| // unmountFUSE attempts to unmount using fusermount/fusermount3 helper binary. | ||||
| // | ||||
| // For FUSE mounts, using these helper binaries is preferred, see: | ||||
| // https://github.com/containerd/containerd/pull/3765#discussion_r342083514 | ||||
| func unmountFUSE(target string) error { | ||||
| 	var err error | ||||
| 	for _, helperBinary := range []string{"fusermount3", "fusermount"} { | ||||
| 		cmd := exec.Command(helperBinary, "-u", target) | ||||
| 		err = cmd.Run() | ||||
| 		if err == nil { | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
							
								
								
									
										30
									
								
								core/mount/fuse_unsupported.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								core/mount/fuse_unsupported.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| //go:build !linux && !windows | ||||
|  | ||||
| /* | ||||
|    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 "fmt" | ||||
|  | ||||
| func isFUSE(dir string) bool { | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // unmountFUSE is not implemented on this platform | ||||
| func unmountFUSE(target string) error { | ||||
| 	return fmt.Errorf("FUSE is not supported on this platform") | ||||
| } | ||||
| @@ -19,12 +19,16 @@ package mount | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"path/filepath" | ||||
| 	"runtime" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/containerd/containerd/api/types" | ||||
| 	"github.com/containerd/continuity/fs" | ||||
| ) | ||||
|  | ||||
| // HasBindMounts This is a flag to conditionally disable code that relies on working bind-mount support, so such code is easier to find across codebase. | ||||
| const HasBindMounts = runtime.GOOS != "darwin" && runtime.GOOS != "openbsd" | ||||
|  | ||||
| // Mount is the lingua franca of containerd. A mount represents a | ||||
| // serialized mount syscall. Components either emit or consume mounts. | ||||
| type Mount struct { | ||||
|   | ||||
							
								
								
									
										24
									
								
								core/mount/mount_darwin.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								core/mount/mount_darwin.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| /* | ||||
|    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 "github.com/containerd/errdefs" | ||||
|  | ||||
| // Mount to the provided target. | ||||
| func (m *Mount) mount(target string) error { | ||||
| 	return errdefs.ErrNotImplemented | ||||
| } | ||||
| @@ -19,18 +19,11 @@ package mount | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"time" | ||||
|  | ||||
| 	"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. | ||||
| // | ||||
| // The "syscall" and "golang.org/x/sys/unix" packages do not define a Mount | ||||
| @@ -77,57 +70,3 @@ func (m *Mount) mount(target string) error { | ||||
| 	} | ||||
| 	return fmt.Errorf("mount [%v] failed with ECHILD (retried %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 fmt.Errorf("failed to unmount target %s: %w", target, unix.EBUSY) | ||||
| } | ||||
|  | ||||
| // 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 | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -26,7 +26,6 @@ import ( | ||||
| 	"runtime" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/containerd/log" | ||||
| 	"github.com/moby/sys/userns" | ||||
| @@ -274,92 +273,6 @@ func doPrepareIDMappedOverlay(lowerDirs []string, usernsFd int) (tmpLowerDirs [] | ||||
| 	return tmpLowerDirs, cleanUp, nil | ||||
| } | ||||
|  | ||||
| // 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 | ||||
| } | ||||
|  | ||||
| // fuseSuperMagic is defined in statfs(2) | ||||
| const fuseSuperMagic = 0x65735546 | ||||
|  | ||||
| func isFUSE(dir string) bool { | ||||
| 	var st unix.Statfs_t | ||||
| 	if err := unix.Statfs(dir, &st); err != nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	return st.Type == fuseSuperMagic | ||||
| } | ||||
|  | ||||
| // unmountFUSE attempts to unmount using fusermount/fusermount3 helper binary. | ||||
| // | ||||
| // For FUSE mounts, using these helper binaries is preferred, see: | ||||
| // https://github.com/containerd/containerd/pull/3765#discussion_r342083514 | ||||
| func unmountFUSE(target string) error { | ||||
| 	var err error | ||||
| 	for _, helperBinary := range []string{"fusermount3", "fusermount"} { | ||||
| 		cmd := exec.Command(helperBinary, "-u", target) | ||||
| 		err = cmd.Run() | ||||
| 		if err == nil { | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func unmount(target string, flags int) error { | ||||
| 	if isFUSE(target) { | ||||
| 		if err := unmountFUSE(target); err == nil { | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
| 	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 fmt.Errorf("failed to unmount target %s: %w", target, unix.EBUSY) | ||||
| } | ||||
|  | ||||
| // 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 | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // parseMountOptions takes fstab style mount options and parses them for | ||||
| // use with a standard mount() syscall | ||||
| func parseMountOptions(options []string) (opt mountOpt) { | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| //go:build !windows && !darwin && !openbsd | ||||
| //go:build !windows && !openbsd | ||||
|  | ||||
| /* | ||||
|    Copyright The containerd Authors. | ||||
| @@ -19,10 +19,13 @@ | ||||
| package mount | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"sort" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/moby/sys/mountinfo" | ||||
| 	"golang.org/x/sys/unix" | ||||
| ) | ||||
|  | ||||
| // UnmountRecursive unmounts the target and all mounts underneath, starting | ||||
| @@ -69,3 +72,64 @@ func UnmountRecursive(target string, flags int) error { | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func unmount(target string, flags int) error { | ||||
| 	if isFUSE(target) { | ||||
| 		// TODO: Why error is ignored? | ||||
| 		// Shouldn't this just be unconditional "return unmountFUSE(target)"? | ||||
| 		if err := unmountFUSE(target); err == nil { | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
| 	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 fmt.Errorf("failed to unmount target %s: %w", target, unix.EBUSY) | ||||
| } | ||||
|  | ||||
| // 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 | ||||
| } | ||||
|  | ||||
| // 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 | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| //go:build darwin || openbsd | ||||
| //go:build openbsd | ||||
|  | ||||
| /* | ||||
|    Copyright The containerd Authors. | ||||
| @@ -18,29 +18,24 @@ | ||||
|  | ||||
| package mount | ||||
|  | ||||
| import "errors" | ||||
|  | ||||
| var ( | ||||
| 	// ErrNotImplementOnUnix is returned for methods that are not implemented | ||||
| 	ErrNotImplementOnUnix = errors.New("not implemented under unix") | ||||
| ) | ||||
| import "github.com/containerd/errdefs" | ||||
|  | ||||
| // Mount is not implemented on this platform | ||||
| func (m *Mount) mount(target string) error { | ||||
| 	return ErrNotImplementOnUnix | ||||
| 	return errdefs.ErrNotImplemented | ||||
| } | ||||
|  | ||||
| // Unmount is not implemented on this platform | ||||
| func Unmount(mount string, flags int) error { | ||||
| 	return ErrNotImplementOnUnix | ||||
| 	return errdefs.ErrNotImplemented | ||||
| } | ||||
|  | ||||
| // UnmountAll is not implemented on this platform | ||||
| func UnmountAll(mount string, flags int) error { | ||||
| 	return ErrNotImplementOnUnix | ||||
| 	return errdefs.ErrNotImplemented | ||||
| } | ||||
|  | ||||
| // UnmountRecursive is not implemented on this platform | ||||
| func UnmountRecursive(mount string, flags int) error { | ||||
| 	return ErrNotImplementOnUnix | ||||
| 	return errdefs.ErrNotImplemented | ||||
| } | ||||
|   | ||||
| @@ -33,11 +33,6 @@ import ( | ||||
|  | ||||
| const sourceStreamName = "containerd.io-source" | ||||
|  | ||||
| var ( | ||||
| 	// ErrNotImplementOnWindows is returned when an action is not implemented for windows | ||||
| 	ErrNotImplementOnWindows = errors.New("not implemented under windows") | ||||
| ) | ||||
|  | ||||
| // Mount to the provided target. | ||||
| func (m *Mount) mount(target string) (retErr error) { | ||||
| 	if m.Type != "windows-layer" { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Akihiro Suda
					Akihiro Suda