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" ) // 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) } // NewMounter creates a new mounter for the provided snapshotter func NewMounter(home string, snapshotter string, idMap idtools.IdentityMapping) *refCountMounter { return &refCountMounter{ base: mounter{ home: home, snapshotter: snapshotter, idMap: idMap, }, rc: graphdriver.NewRefCounter(checker()), locker: locker.New(), } } 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) }