203bac0ec4
BaseFS is not serialized and is lost after an unclean shutdown. Unmount method in the containerd image service implementation will not work correctly in that case. This patch will allow Unmount to restore the BaseFS if the target is still mounted. The reason it works with graphdrivers is that it doesn't directly operate on BaseFS. It uses RWLayer, which is explicitly restored immediately as soon as container is loaded. Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
172 lines
4.2 KiB
Go
172 lines
4.2 KiB
Go
package snapshotter
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/containerd/containerd/mount"
|
|
"github.com/containerd/log"
|
|
"github.com/docker/docker/daemon/graphdriver"
|
|
"github.com/docker/docker/pkg/idtools"
|
|
"github.com/moby/locker"
|
|
"github.com/moby/sys/mountinfo"
|
|
)
|
|
|
|
// List of known filesystems that can't be re-mounted or have shared layers
|
|
var refCountedFileSystems = []string{"fuse-overlayfs", "overlayfs", "stargz", "zfs"}
|
|
|
|
// Mounter handles mounting/unmounting things coming in from a snapshotter
|
|
// with optional reference counting if needed by the filesystem
|
|
type Mounter interface {
|
|
// Mount mounts the rootfs for a container and returns the mount point
|
|
Mount(mounts []mount.Mount, containerID string) (string, error)
|
|
// Unmount unmounts the container rootfs
|
|
Unmount(target string) error
|
|
// Mounted returns a target mountpoint if it's already mounted
|
|
Mounted(containerID string) (string, error)
|
|
}
|
|
|
|
// inSlice tests whether a string is contained in a slice of strings or not.
|
|
// Comparison is case sensitive
|
|
func inSlice(slice []string, s string) bool {
|
|
for _, ss := range slice {
|
|
if s == ss {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// NewMounter creates a new mounter for the provided snapshotter
|
|
func NewMounter(home string, snapshotter string, idMap idtools.IdentityMapping) Mounter {
|
|
mnter := mounter{
|
|
home: home,
|
|
snapshotter: snapshotter,
|
|
idMap: idMap,
|
|
}
|
|
|
|
if inSlice(refCountedFileSystems, snapshotter) {
|
|
return &refCountMounter{
|
|
base: mnter,
|
|
rc: graphdriver.NewRefCounter(checker()),
|
|
locker: locker.New(),
|
|
}
|
|
}
|
|
|
|
return &mnter
|
|
}
|
|
|
|
type refCountMounter struct {
|
|
rc *graphdriver.RefCounter
|
|
locker *locker.Locker
|
|
base mounter
|
|
}
|
|
|
|
func (m *refCountMounter) Mount(mounts []mount.Mount, containerID string) (target string, retErr error) {
|
|
target = m.base.target(containerID)
|
|
|
|
_, err := os.Stat(target)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return "", err
|
|
}
|
|
|
|
if count := m.rc.Increment(target); count > 1 {
|
|
return target, nil
|
|
}
|
|
|
|
m.locker.Lock(target)
|
|
defer m.locker.Unlock(target)
|
|
|
|
defer func() {
|
|
if retErr != nil {
|
|
if c := m.rc.Decrement(target); c <= 0 {
|
|
if mntErr := unmount(target); mntErr != nil {
|
|
log.G(context.TODO()).Errorf("error unmounting %s: %v", target, mntErr)
|
|
}
|
|
if rmErr := os.Remove(target); rmErr != nil && !os.IsNotExist(rmErr) {
|
|
log.G(context.TODO()).Debugf("Failed to remove %s: %v: %v", target, rmErr, err)
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
|
|
return m.base.Mount(mounts, containerID)
|
|
}
|
|
|
|
func (m *refCountMounter) Unmount(target string) error {
|
|
if count := m.rc.Decrement(target); count > 0 {
|
|
return nil
|
|
}
|
|
|
|
m.locker.Lock(target)
|
|
defer m.locker.Unlock(target)
|
|
|
|
if err := unmount(target); err != nil {
|
|
log.G(context.TODO()).Debugf("Failed to unmount %s: %v", target, err)
|
|
}
|
|
|
|
if err := os.Remove(target); err != nil {
|
|
log.G(context.TODO()).WithError(err).WithField("dir", target).Error("failed to remove mount temp dir")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *refCountMounter) Mounted(containerID string) (string, error) {
|
|
mounted, err := m.base.Mounted(containerID)
|
|
if err != nil || mounted == "" {
|
|
return mounted, err
|
|
}
|
|
|
|
target := m.base.target(containerID)
|
|
|
|
// Check if the refcount is non-zero.
|
|
m.rc.Increment(target)
|
|
if m.rc.Decrement(target) > 0 {
|
|
return mounted, nil
|
|
}
|
|
|
|
return "", nil
|
|
}
|
|
|
|
type mounter struct {
|
|
home string
|
|
snapshotter string
|
|
idMap idtools.IdentityMapping
|
|
}
|
|
|
|
func (m mounter) Mount(mounts []mount.Mount, containerID string) (string, error) {
|
|
target := m.target(containerID)
|
|
|
|
root := m.idMap.RootPair()
|
|
if err := idtools.MkdirAllAndChown(filepath.Dir(target), 0o710, idtools.Identity{
|
|
UID: idtools.CurrentIdentity().UID,
|
|
GID: root.GID,
|
|
}); err != nil {
|
|
return "", err
|
|
}
|
|
if err := idtools.MkdirAllAndChown(target, 0o710, root); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return target, mount.All(mounts, target)
|
|
}
|
|
|
|
func (m mounter) Unmount(target string) error {
|
|
return unmount(target)
|
|
}
|
|
|
|
func (m mounter) Mounted(containerID string) (string, error) {
|
|
target := m.target(containerID)
|
|
|
|
mounted, err := mountinfo.Mounted(target)
|
|
if err != nil || !mounted {
|
|
return "", err
|
|
}
|
|
return target, nil
|
|
}
|
|
|
|
func (m mounter) target(containerID string) string {
|
|
return filepath.Join(m.home, "rootfs", m.snapshotter, containerID)
|
|
}
|